From 68ebb9faaa71ba5d8a3f99fa0ed97552b0c76b19 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 1 Jun 2022 14:19:10 +0400 Subject: [PATCH 001/218] Boilerplate for new project --- .gitignore | 21 + Cargo.lock | 680 ++++++++++++++++++ Cargo.toml | 14 + README.md | 25 + contracts/example/Cargo.toml | 29 + contracts/example/src/contract.rs | 37 + contracts/example/src/contract_tests.rs | 28 + contracts/example/src/lib.rs | 5 + contracts/example/src/state.rs | 4 + packages/fields-credit-manager/Cargo.toml | 21 + packages/fields-credit-manager/README.md | 5 + packages/fields-credit-manager/src/example.rs | 30 + packages/fields-credit-manager/src/lib.rs | 1 + rustfmt.toml | 5 + 14 files changed, 905 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 contracts/example/Cargo.toml create mode 100644 contracts/example/src/contract.rs create mode 100644 contracts/example/src/contract_tests.rs create mode 100644 contracts/example/src/lib.rs create mode 100644 contracts/example/src/state.rs create mode 100644 packages/fields-credit-manager/Cargo.toml create mode 100644 packages/fields-credit-manager/README.md create mode 100644 packages/fields-credit-manager/src/example.rs create mode 100644 packages/fields-credit-manager/src/lib.rs create mode 100644 rustfmt.toml diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..fd8b0b077 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# cosmwasm compiled contract binaries +artifacts + +# cargo files +target + +# javascript dependencies +node_modules + +# environment variables +.env* + +# macOS system files +.DS_Store + +# intellij local files +.idea + +# private scripts that I don't want to commit are prefixed by an underscore +**/_*.js +**/_*.ts diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..1e565cea8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,680 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" + +[[package]] +name = "cosmwasm-crypto" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b110e31d47bd265e17ec88dd7328fcf40e1ee67a6131c1ab492f77fef8cd83" +dependencies = [ + "digest", + "ed25519-zebra", + "k256", + "rand_core 0.5.1", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0faf9bad5eb0a43a00406e64f8d33407a06bd1826fa976195a69db70e6c18d9d" +dependencies = [ + "syn", +] + +[[package]] +name = "cosmwasm-schema" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021bdefb9d855c5135e83046e5407a9fddba869e42e78b6036b53a606dc8c10" +dependencies = [ + "schemars", + "serde_json", +] + +[[package]] +name = "cosmwasm-std" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47306c113f4d964c35a74a87ceb8ccfb5811e9810a9dc427101148b5b9134ca" +dependencies = [ + "base64", + "cosmwasm-crypto", + "cosmwasm-derive", + "schemars", + "serde", + "serde-json-wasm", + "thiserror", + "uint", +] + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" +dependencies = [ + "generic-array", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "cw-controllers" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71bbd224e31c87615d2b9162190bc33fabb47056d03c6c06f254e3b5b9757318" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "cw0", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-storage-plus" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e401ed71bd64abb9b91151a9ff4f7b34e81b2b3eceab23e3cb67fe47e39938" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw0" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d759bb5418a3bdf091e1f1be17de2a15d95d2be4fee28045c2e461f4c6d9d1ca" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw2" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6380164fb236412ff43c7ca075d95847c6fa8c51b2d3a513c23127a0f2a8f6" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + +[[package]] +name = "cw20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac49b013ca1e355fd988cc1926acc9a16d7fd45cfb595ee330455582a788b100" +dependencies = [ + "cosmwasm-std", + "cw0", + "schemars", + "serde", +] + +[[package]] +name = "cw721" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44a81e5d775c056f5c1dd5eaaf68b82599cbc21d21c9a38eca0b74808d934806" +dependencies = [ + "cosmwasm-std", + "cw0", + "schemars", + "serde", +] + +[[package]] +name = "cw721-base" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239bdfcae1ba72cfcdd3e4eb6300eea2673b63f74425ece5b6bc45cf964fbc1a" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "cw0", + "cw2", + "cw721", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "der" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" +dependencies = [ + "const-oid", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dyn-clone" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" + +[[package]] +name = "ecdsa" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" +dependencies = [ + "der", + "elliptic-curve", + "hmac", + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409" +dependencies = [ + "curve25519-dalek", + "hex", + "rand_core 0.5.1", + "serde", + "sha2", + "thiserror", +] + +[[package]] +name = "elliptic-curve" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b" +dependencies = [ + "crypto-bigint", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + +[[package]] +name = "example" +version = "0.1.0" +dependencies = [ + "base64", + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers", + "cw-storage-plus", + "cw721", + "cw721-base", + "fields-credit-manager", + "k256", + "schemars", + "serde", + "sha2", +] + +[[package]] +name = "ff" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "fields-credit-manager" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw20", + "cw721", + "schemars", + "serde", + "terra-cosmwasm", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "group" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "k256" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pkcs8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.6", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "schemars" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "042ac496d97e5885149d34139bad1d617192770d7eb8f1866da2317ff4501853" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "signature" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" +dependencies = [ + "digest", + "rand_core 0.6.3", +] + +[[package]] +name = "spki" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" +dependencies = [ + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terra-cosmwasm" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552f18cba2b535d1f8c0e3b3f37696820b954bc7535d2e33909f2a6342302718" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "uint" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "zeroize" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..ac4d7e7e3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[workspace] +members = ["packages/*", "contracts/*"] + +[profile.release.package.fields-credit-manager] +codegen-units = 1 +incremental = false + +[profile.release] +rpath = false +lto = true +overflow-checks = true +opt-level = 3 +debug = false +debug-assertions = false \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..78f767919 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# Fields of Mars: Credit Manager + +## Bug bounty + +## Overview + +## Development + +### Dependencies + +### Environment Setup + +### Test + +### Deploy + +### Notes + +## Deployment + +### Mainnet + +### Testnet + +## License diff --git a/contracts/example/Cargo.toml b/contracts/example/Cargo.toml new file mode 100644 index 000000000..c246cb22c --- /dev/null +++ b/contracts/example/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "example" +version = "0.1.0" +authors = ["grod220 , larry_0x "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +fields-credit-manager = { version = "0.1.0", path = "../../packages/fields-credit-manager" } + +cosmwasm-std = "0.16" +cw721 = "0.9" +cw721-base = { version = "0.9", features = ["library"] } # must be imported as library!!! +cw-controllers = "0.9" +cw-storage-plus = "0.9" +schemars = "0.8.1" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +sha2 = "0.9" +base64 = "0.13.0" + +[dev-dependencies] +cosmwasm-schema = "0.16" +k256 = { version = "0.9.6", features = ["ecdsa"] } diff --git a/contracts/example/src/contract.rs b/contracts/example/src/contract.rs new file mode 100644 index 000000000..18d04466f --- /dev/null +++ b/contracts/example/src/contract.rs @@ -0,0 +1,37 @@ +use cosmwasm_std::{ + entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, +}; + +use fields_credit_manager::example::{ExecuteMsg, InstantiateMsg, QueryMsg, StoredStringResponse}; + +use crate::state::SOME_STRING; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + SOME_STRING.save(deps.storage, &msg.some_string)?; + Ok(Response::new().add_attribute("method", "instantiate")) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute(_: DepsMut, _env: Env, _: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg {} +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetStoredString {} => to_binary(&try_get_stored_str(deps)?), + } +} + +fn try_get_stored_str(deps: Deps) -> StdResult { + let str = SOME_STRING.load(deps.storage)?; + Ok(StoredStringResponse { + str, + }) +} diff --git a/contracts/example/src/contract_tests.rs b/contracts/example/src/contract_tests.rs new file mode 100644 index 000000000..5994bfbda --- /dev/null +++ b/contracts/example/src/contract_tests.rs @@ -0,0 +1,28 @@ +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; +use cosmwasm_std::{coins, from_binary}; + +use fields_credit_manager::example::{InstantiateMsg, QueryMsg, StoredStringResponse}; + +use crate::contract::{instantiate, query}; + +#[test] +fn test_proper_initialization() { + let mut deps = mock_dependencies(&[]); + let info = mock_info("creator", &coins(1000, "luna")); + + let example_string = String::from("spiderman123"); + let res = instantiate( + deps.as_mut(), + mock_env(), + info, + InstantiateMsg { + some_string: example_string.clone(), + }, + ) + .unwrap(); + assert_eq!(0, res.messages.len()); + + let res = query(deps.as_ref(), mock_env(), QueryMsg::GetStoredString {}).unwrap(); + let value: StoredStringResponse = from_binary(&res).unwrap(); + assert_eq!(example_string, value.str); +} diff --git a/contracts/example/src/lib.rs b/contracts/example/src/lib.rs new file mode 100644 index 000000000..9121fd34b --- /dev/null +++ b/contracts/example/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; +pub mod state; + +#[cfg(test)] +mod contract_tests; diff --git a/contracts/example/src/state.rs b/contracts/example/src/state.rs new file mode 100644 index 000000000..f785703ee --- /dev/null +++ b/contracts/example/src/state.rs @@ -0,0 +1,4 @@ +use cw_storage_plus::Item; + +/// The mint contract for the collection. Set on instantiation. +pub const SOME_STRING: Item = Item::new("some_string"); diff --git a/packages/fields-credit-manager/Cargo.toml b/packages/fields-credit-manager/Cargo.toml new file mode 100644 index 000000000..c91e5279b --- /dev/null +++ b/packages/fields-credit-manager/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "fields-credit-manager" +version = "0.1.0" +authors = ["larry_0x ", "Gabe R. "] +edition = "2018" + +[lib] +doctest = false + +[dependencies] +cosmwasm-std = "=0.16.2" +cw20 = "=0.9.1" +cw721 = "=0.9.2" + +terra-cosmwasm = "=2.2.0" + +schemars = "0.8.1" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } + +[dev-dependencies] +cosmwasm-schema = "0.16.2" \ No newline at end of file diff --git a/packages/fields-credit-manager/README.md b/packages/fields-credit-manager/README.md new file mode 100644 index 000000000..9bd42bb50 --- /dev/null +++ b/packages/fields-credit-manager/README.md @@ -0,0 +1,5 @@ +# Fields of Mars: Common Types + +## Adapters + +## License diff --git a/packages/fields-credit-manager/src/example.rs b/packages/fields-credit-manager/src/example.rs new file mode 100644 index 000000000..df5bb4a9c --- /dev/null +++ b/packages/fields-credit-manager/src/example.rs @@ -0,0 +1,30 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +//-------------------------------------------------------------------------------------------------- +// Types +//-------------------------------------------------------------------------------------------------- + +//-------------------------------------------------------------------------------------------------- +// Messages +//-------------------------------------------------------------------------------------------------- + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct InstantiateMsg { + pub some_string: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg {} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + GetStoredString {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct StoredStringResponse { + pub str: String, +} diff --git a/packages/fields-credit-manager/src/lib.rs b/packages/fields-credit-manager/src/lib.rs new file mode 100644 index 000000000..d4d8a94c8 --- /dev/null +++ b/packages/fields-credit-manager/src/lib.rs @@ -0,0 +1 @@ +pub mod example; diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..e58a79bcc --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,5 @@ +hard_tabs = false +max_width = 100 +newline_style = "unix" +tab_spaces = 4 +use_small_heuristics = "off" \ No newline at end of file From cafc5642cad892f90396e952abe7dcfd5d4a3d6f Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 1 Jun 2022 14:25:12 +0400 Subject: [PATCH 002/218] Updating deps to cosmwasm 1.0 --- Cargo.lock | 218 ++++++++++------------ contracts/example/Cargo.toml | 12 +- contracts/example/src/contract_tests.rs | 2 +- packages/fields-credit-manager/Cargo.toml | 9 +- 4 files changed, 104 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e565cea8..9470f24ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,24 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base64" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179" + [[package]] name = "block-buffer" version = "0.9.0" @@ -31,37 +43,37 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "const-oid" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "cosmwasm-crypto" -version = "0.16.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b110e31d47bd265e17ec88dd7328fcf40e1ee67a6131c1ab492f77fef8cd83" +checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd" dependencies = [ "digest", "ed25519-zebra", "k256", - "rand_core 0.5.1", + "rand_core 0.6.3", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "0.16.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0faf9bad5eb0a43a00406e64f8d33407a06bd1826fa976195a69db70e6c18d9d" +checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" dependencies = [ "syn", ] [[package]] name = "cosmwasm-schema" -version = "0.16.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021bdefb9d855c5135e83046e5407a9fddba869e42e78b6036b53a606dc8c10" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" dependencies = [ "schemars", "serde_json", @@ -69,13 +81,14 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "0.16.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47306c113f4d964c35a74a87ceb8ccfb5811e9810a9dc427101148b5b9134ca" +checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" dependencies = [ "base64", "cosmwasm-crypto", "cosmwasm-derive", + "forward_ref", "schemars", "serde", "serde-json-wasm", @@ -100,9 +113,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.2.11" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ "generic-array", "rand_core 0.6.3", @@ -133,25 +146,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "cw-controllers" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71bbd224e31c87615d2b9162190bc33fabb47056d03c6c06f254e3b5b9757318" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "cw0", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw-storage-plus" -version = "0.9.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e401ed71bd64abb9b91151a9ff4f7b34e81b2b3eceab23e3cb67fe47e39938" +checksum = "9336ecef1e19d56cf6e3e932475fc6a3dee35eec5a386e07917a1d1ba6bb0e35" dependencies = [ "cosmwasm-std", "schemars", @@ -159,10 +158,10 @@ dependencies = [ ] [[package]] -name = "cw0" -version = "0.9.1" +name = "cw-utils" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d759bb5418a3bdf091e1f1be17de2a15d95d2be4fee28045c2e461f4c6d9d1ca" +checksum = "babd2c090f39d07ce5bf2556962305e795daa048ce20a93709eb591476e4a29e" dependencies = [ "cosmwasm-std", "schemars", @@ -170,63 +169,23 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw2" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6380164fb236412ff43c7ca075d95847c6fa8c51b2d3a513c23127a0f2a8f6" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "schemars", - "serde", -] - [[package]] name = "cw20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac49b013ca1e355fd988cc1926acc9a16d7fd45cfb595ee330455582a788b100" -dependencies = [ - "cosmwasm-std", - "cw0", - "schemars", - "serde", -] - -[[package]] -name = "cw721" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44a81e5d775c056f5c1dd5eaaf68b82599cbc21d21c9a38eca0b74808d934806" -dependencies = [ - "cosmwasm-std", - "cw0", - "schemars", - "serde", -] - -[[package]] -name = "cw721-base" -version = "0.9.2" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239bdfcae1ba72cfcdd3e4eb6300eea2673b63f74425ece5b6bc45cf964fbc1a" +checksum = "356d364602c5fe763544ea00d485b825d6ef519a2fc6a3145528d7df3a603f40" dependencies = [ "cosmwasm-std", - "cw-storage-plus", - "cw0", - "cw2", - "cw721", + "cw-utils", "schemars", "serde", - "thiserror", ] [[package]] name = "der" -version = "0.4.5" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" dependencies = [ "const-oid", ] @@ -248,42 +207,45 @@ checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" [[package]] name = "ecdsa" -version = "0.12.4" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" dependencies = [ "der", "elliptic-curve", - "hmac", + "rfc6979", "signature", ] [[package]] name = "ed25519-zebra" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409" +checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" dependencies = [ "curve25519-dalek", "hex", - "rand_core 0.5.1", + "rand_core 0.6.3", "serde", "sha2", "thiserror", + "zeroize", ] [[package]] name = "elliptic-curve" -version = "0.10.6" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" dependencies = [ + "base16ct", "crypto-bigint", + "der", "ff", "generic-array", "group", - "pkcs8", "rand_core 0.6.3", + "sec1", "subtle", "zeroize", ] @@ -292,25 +254,19 @@ dependencies = [ name = "example" version = "0.1.0" dependencies = [ - "base64", "cosmwasm-schema", "cosmwasm-std", - "cw-controllers", "cw-storage-plus", - "cw721", - "cw721-base", "fields-credit-manager", - "k256", "schemars", "serde", - "sha2", ] [[package]] name = "ff" -version = "0.10.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ "rand_core 0.6.3", "subtle", @@ -323,12 +279,16 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw20", - "cw721", "schemars", "serde", - "terra-cosmwasm", ] +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + [[package]] name = "generic-array" version = "0.14.5" @@ -363,9 +323,9 @@ dependencies = [ [[package]] name = "group" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "ff", "rand_core 0.6.3", @@ -396,13 +356,14 @@ checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] name = "k256" -version = "0.9.6" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", + "sec1", "sha2", ] @@ -420,12 +381,13 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "pkcs8" -version = "0.7.6" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" dependencies = [ "der", "spki", + "zeroize", ] [[package]] @@ -464,6 +426,17 @@ dependencies = [ "getrandom 0.2.6", ] +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + [[package]] name = "ryu" version = "1.0.10" @@ -494,6 +467,19 @@ dependencies = [ "syn", ] +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "serde" version = "1.0.137" @@ -505,9 +491,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "042ac496d97e5885149d34139bad1d617192770d7eb8f1866da2317ff4501853" +checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" dependencies = [ "serde", ] @@ -560,9 +546,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest", "rand_core 0.6.3", @@ -570,10 +556,11 @@ dependencies = [ [[package]] name = "spki" -version = "0.4.1" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" dependencies = [ + "base64ct", "der", ] @@ -600,17 +587,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "terra-cosmwasm" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552f18cba2b535d1f8c0e3b3f37696820b954bc7535d2e33909f2a6342302718" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - [[package]] name = "thiserror" version = "1.0.31" @@ -675,6 +651,6 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "zeroize" -version = "1.4.3" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "94693807d016b2f2d2e14420eb3bfcca689311ff775dcf113d74ea624b7cdf07" diff --git a/contracts/example/Cargo.toml b/contracts/example/Cargo.toml index c246cb22c..3fff95477 100644 --- a/contracts/example/Cargo.toml +++ b/contracts/example/Cargo.toml @@ -14,16 +14,10 @@ library = [] [dependencies] fields-credit-manager = { version = "0.1.0", path = "../../packages/fields-credit-manager" } -cosmwasm-std = "0.16" -cw721 = "0.9" -cw721-base = { version = "0.9", features = ["library"] } # must be imported as library!!! -cw-controllers = "0.9" -cw-storage-plus = "0.9" +cosmwasm-std = "1.0.0" +cw-storage-plus = "0.13.2" schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -sha2 = "0.9" -base64 = "0.13.0" [dev-dependencies] -cosmwasm-schema = "0.16" -k256 = { version = "0.9.6", features = ["ecdsa"] } +cosmwasm-schema = "1.0.0" diff --git a/contracts/example/src/contract_tests.rs b/contracts/example/src/contract_tests.rs index 5994bfbda..0ce973b88 100644 --- a/contracts/example/src/contract_tests.rs +++ b/contracts/example/src/contract_tests.rs @@ -7,7 +7,7 @@ use crate::contract::{instantiate, query}; #[test] fn test_proper_initialization() { - let mut deps = mock_dependencies(&[]); + let mut deps = mock_dependencies(); let info = mock_info("creator", &coins(1000, "luna")); let example_string = String::from("spiderman123"); diff --git a/packages/fields-credit-manager/Cargo.toml b/packages/fields-credit-manager/Cargo.toml index c91e5279b..95046e7a7 100644 --- a/packages/fields-credit-manager/Cargo.toml +++ b/packages/fields-credit-manager/Cargo.toml @@ -8,14 +8,11 @@ edition = "2018" doctest = false [dependencies] -cosmwasm-std = "=0.16.2" -cw20 = "=0.9.1" -cw721 = "=0.9.2" - -terra-cosmwasm = "=2.2.0" +cosmwasm-std = "1.0.0" +cw20 = "0.13.2" schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } [dev-dependencies] -cosmwasm-schema = "0.16.2" \ No newline at end of file +cosmwasm-schema = "1.0.0" \ No newline at end of file From e26c7939dd00cfacb4ab6afcbf4b26f30a960b50 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 1 Jun 2022 15:52:31 +0400 Subject: [PATCH 003/218] Adding scripts boilerplate --- README.md | 24 + scripts/.eslintrc | 10 + scripts/.prettierrc | 5 + scripts/app.test.ts | 5 + scripts/app.ts | 5 + scripts/babel.config.js | 3 + scripts/package-lock.json | 11374 +++++++++++++++++++++++++++++++++ scripts/package.json | 22 + scripts/tsconfig.json | 33 + scripts/tsconfig.tsbuildinfo | 1 + 10 files changed, 11482 insertions(+) create mode 100644 scripts/.eslintrc create mode 100644 scripts/.prettierrc create mode 100644 scripts/app.test.ts create mode 100644 scripts/app.ts create mode 100644 scripts/babel.config.js create mode 100644 scripts/package-lock.json create mode 100644 scripts/package.json create mode 100644 scripts/tsconfig.json create mode 100644 scripts/tsconfig.tsbuildinfo diff --git a/README.md b/README.md index 78f767919..efd8b4974 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,30 @@ ### Environment Setup +Docker +https://docs.docker.com/get-docker/ +v8 + +Osmosisd +Select option 3 (localosmosis), the installer will configure everything for you. +The osmosisd dameon on your local computer is used to communicate with the localosmosis daemin running inside the Docker container. +https://get.osmosis.zone/ + +install localosmosis +https://docs.osmosis.zone/developing/tools/localosmosis.html#install-localosmosis + +cd localOsmosis +make start + +now creating blocks + + +osmosjs? + +SCRIPTS +npm install + + ### Test ### Deploy diff --git a/scripts/.eslintrc b/scripts/.eslintrc new file mode 100644 index 000000000..79bd6ef23 --- /dev/null +++ b/scripts/.eslintrc @@ -0,0 +1,10 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ] +} diff --git a/scripts/.prettierrc b/scripts/.prettierrc new file mode 100644 index 000000000..dc8c7109f --- /dev/null +++ b/scripts/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "printWidth": 120, + "trailingComma": "all" +} diff --git a/scripts/app.test.ts b/scripts/app.test.ts new file mode 100644 index 000000000..40baea5ed --- /dev/null +++ b/scripts/app.test.ts @@ -0,0 +1,5 @@ +import { hello } from './app'; + +test('adds 1 + 2 to equal 3', () => { + expect(hello('George')).toBe('Hello George!'); +}); diff --git a/scripts/app.ts b/scripts/app.ts new file mode 100644 index 000000000..36ea554a3 --- /dev/null +++ b/scripts/app.ts @@ -0,0 +1,5 @@ +const world = 'world'; + +export function hello(who: string = world): string { + return `Hello ${who}!`; +} diff --git a/scripts/babel.config.js b/scripts/babel.config.js new file mode 100644 index 000000000..e6ffbd417 --- /dev/null +++ b/scripts/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], +}; diff --git a/scripts/package-lock.json b/scripts/package-lock.json new file mode 100644 index 000000000..ecd95c753 --- /dev/null +++ b/scripts/package-lock.json @@ -0,0 +1,11374 @@ +{ + "name": "fields-scripts", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "fields-scripts", + "version": "1.0.0", + "devDependencies": { + "@babel/preset-env": "^7.18.2", + "@babel/preset-typescript": "^7.17.12", + "@types/jest": "^27.5.1", + "@typescript-eslint/eslint-plugin": "^5.27.0", + "@typescript-eslint/parser": "^5.27.0", + "eslint": "^8.16.0", + "jest": "^28.1.0", + "prettier": "2.6.2", + "ts-node": "^10.5.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", + "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.2.tgz", + "integrity": "sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.2", + "@babel/helper-compilation-targets": "^7.18.2", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helpers": "^7.18.2", + "@babel/parser": "^7.18.0", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", + "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.2", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", + "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", + "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", + "dev": true, + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz", + "integrity": "sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.0.tgz", + "integrity": "sha512-Kh8zTGR9de3J63e5nS0rQUdRs/kbtwoeQQ0sriS0lItjC96u8XXZN6lKpuyWd2coKSU13py/y+LTmThLuVX0Pg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-member-expression-to-functions": "^7.17.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.12.tgz", + "integrity": "sha512-b2aZrV4zvutr9AIa6/gA3wsZKRwTKYoDxYiFKcESS3Ug2GTXzwBEvMuuFLhCQpEnRXs1zng4ISAXSUxxKBIcxw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "regexpu-core": "^5.0.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", + "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz", + "integrity": "sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", + "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.16.7", + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", + "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz", + "integrity": "sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.0", + "@babel/types": "^7.18.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz", + "integrity": "sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", + "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-wrap-function": "^7.16.8", + "@babel/types": "^7.16.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.2.tgz", + "integrity": "sha512-XzAIyxx+vFnrOxiQrToSUOzUOn0e1J2Li40ntddek1Y69AXUTXoDJ40/D5RdjFu7s7qHiaeoTiempZcbuVXh2Q==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.2", + "@babel/helper-member-expression-to-functions": "^7.17.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz", + "integrity": "sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", + "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.2.tgz", + "integrity": "sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", + "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz", + "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.17.12.tgz", + "integrity": "sha512-xCJQXl4EeQ3J9C4yOmpTrtVGmzpm2iSzyxbkZHw7UCnZBftHpF/hpII80uWVyVrc40ytIClHjgWGTG1g/yB+aw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.17.12.tgz", + "integrity": "sha512-/vt0hpIw0x4b6BLKUkwlvEoiGZYYLNZ96CzyHYPbtG2jZGz6LBe7/V+drYrc/d+ovrF9NBi0pmtvmNb/FsWtRQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.17.12.tgz", + "integrity": "sha512-RWVvqD1ooLKP6IqWTA5GyFVX2isGEgC5iFxKzfYOIy/QEFdxYyCybBDtIGjipHpb9bDWHzcqGqFakf+mVmBTdQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-remap-async-to-generator": "^7.16.8", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.17.12.tgz", + "integrity": "sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.0.tgz", + "integrity": "sha512-t+8LsRMMDE74c6sV7KShIw13sqbqd58tlqNrsWoWBTIMw7SVQ0cZ905wLNS/FBCy/3PyooRHLFFlfrUNyyz5lA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", + "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.17.12.tgz", + "integrity": "sha512-j7Ye5EWdwoXOpRmo5QmRyHPsDIe6+u70ZYZrd7uz+ebPYFKfRcLcNu3Ro0vOlJ5zuv8rU7xa+GttNiRzX56snQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.17.12.tgz", + "integrity": "sha512-rKJ+rKBoXwLnIn7n6o6fulViHMrOThz99ybH+hKHcOZbnN14VuMnH9fo2eHE69C8pO4uX1Q7t2HYYIDmv8VYkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.17.12.tgz", + "integrity": "sha512-EqFo2s1Z5yy+JeJu7SFfbIUtToJTVlC61/C7WLKDntSw4Sz6JNAIfL7zQ74VvirxpjB5kz/kIx0gCcb+5OEo2Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.17.12.tgz", + "integrity": "sha512-ws/g3FSGVzv+VH86+QvgtuJL/kR67xaEIF2x0iPqdDfYW6ra6JF3lKVBkWynRLcNtIC1oCTfDRVxmm2mKzy+ag==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", + "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.0.tgz", + "integrity": "sha512-nbTv371eTrFabDfHLElkn9oyf9VG+VKK6WMzhY2o4eHKaG19BToD9947zzGMO6I/Irstx9d8CwX6njPNIAR/yw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-compilation-targets": "^7.17.10", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", + "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.17.12.tgz", + "integrity": "sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.17.12.tgz", + "integrity": "sha512-SllXoxo19HmxhDWm3luPz+cPhtoTSKLJE9PXshsfrOzBqs60QP0r8OaJItrPhAj0d7mZMnNF0Y1UUggCDgMz1A==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.17.12.tgz", + "integrity": "sha512-/6BtVi57CJfrtDNKfK5b66ydK2J5pXUKBKSPD2G1whamMuEnZWgoOIfO8Vf9F/DoD4izBLD/Au4NMQfruzzykg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.17.12.tgz", + "integrity": "sha512-Wb9qLjXf3ZazqXA7IvI7ozqRIXIGPtSo+L5coFmEkhTQK18ao4UDDD0zdTGAarmbLj2urpRwrc6893cu5Bfh0A==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.17.12.tgz", + "integrity": "sha512-n/loy2zkq9ZEM8tEOwON9wTQSTNDTDEz6NujPtJGLU7qObzT1N4c4YZZf8E6ATB2AjNQg/Ib2AIpO03EZaCehw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz", + "integrity": "sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.17.12.tgz", + "integrity": "sha512-PHln3CNi/49V+mza4xMwrg+WGYevSF1oaiXaC2EQfdp4HWlSjRsrDXWJiQBKpP7749u6vQ9mcry2uuFOv5CXvA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.17.12.tgz", + "integrity": "sha512-J8dbrWIOO3orDzir57NRsjg4uxucvhby0L/KZuGsWDj0g7twWK3g7JhJhOrXtuXiw8MeiSdJ3E0OW9H8LYEzLQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-remap-async-to-generator": "^7.16.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", + "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.4.tgz", + "integrity": "sha512-+Hq10ye+jlvLEogSOtq4mKvtk7qwcUQ1f0Mrueai866C82f844Yom2cttfJdMdqRLTxWpsbfbkIkOIfovyUQXw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.4.tgz", + "integrity": "sha512-e42NSG2mlKWgxKUAD9EJJSkZxR67+wZqzNxLSpc51T8tRU5SLFHsPmgYR5yr7sdgX4u+iHA1C5VafJ6AyImV3A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.18.2", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-replace-supers": "^7.18.2", + "@babel/helper-split-export-declaration": "^7.16.7", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.17.12.tgz", + "integrity": "sha512-a7XINeplB5cQUWMg1E/GI1tFz3LfK021IjV1rj1ypE+R7jHm+pIHmHl25VNkZxtx9uuYp7ThGk8fur1HHG7PgQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.0.tgz", + "integrity": "sha512-Mo69klS79z6KEfrLg/1WkmVnB8javh75HX4pi2btjvlIoasuxilEyjtsQW6XPrubNd7AQy0MMaNIaQE4e7+PQw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", + "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.17.12.tgz", + "integrity": "sha512-EA5eYFUG6xeerdabina/xIoB95jJ17mAkR8ivx6ZSu9frKShBjpOGZPn511MTDTkiCO+zXnzNczvUM69YSf3Zw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", + "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.1.tgz", + "integrity": "sha512-+TTB5XwvJ5hZbO8xvl2H4XaMDOAK57zF4miuC9qQJgysPNEAZZ9Z69rdF5LJkozGdZrjBIUAIyKUWRMmebI7vg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", + "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.17.12.tgz", + "integrity": "sha512-8iRkvaTjJciWycPIZ9k9duu663FT7VrBdNqNgxnVXEFwOIp55JWcZd23VBRySYbnS3PwQ3rGiabJBBBGj5APmQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", + "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.0.tgz", + "integrity": "sha512-h8FjOlYmdZwl7Xm2Ug4iX2j7Qy63NANI+NQVWQzv6r25fqgg7k2dZl03p95kvqNclglHs4FZ+isv4p1uXMA+QA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.2.tgz", + "integrity": "sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-simple-access": "^7.18.2", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.4.tgz", + "integrity": "sha512-lH2UaQaHVOAeYrUUuZ8i38o76J/FnO8vu21OE+tD1MyP9lxdZoSfz+pDbWkq46GogUrdrMz3tiz/FYGB+bVThg==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-identifier": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.0.tgz", + "integrity": "sha512-d/zZ8I3BWli1tmROLxXLc9A6YXvGK8egMxHp+E/rRwMh1Kip0AP77VwZae3snEJ33iiWwvNv2+UIIhfalqhzZA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.12.tgz", + "integrity": "sha512-vWoWFM5CKaTeHrdUJ/3SIOTRV+MBVGybOC9mhJkaprGNt5demMymDW24yC74avb915/mIRe3TgNb/d8idvnCRA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.17.12.tgz", + "integrity": "sha512-CaOtzk2fDYisbjAD4Sd1MTKGVIpRtx9bWLyj24Y/k6p4s4gQ3CqDGJauFJxt8M/LEx003d0i3klVqnN73qvK3w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", + "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.17.12.tgz", + "integrity": "sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", + "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.0.tgz", + "integrity": "sha512-C8YdRw9uzx25HSIzwA7EM7YP0FhCe5wNvJbZzjVNHHPGVcDJ3Aie+qGYYdS1oVQgn+B3eAIJbWFLrJ4Jipv7nw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "regenerator-transform": "^0.15.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.17.12.tgz", + "integrity": "sha512-1KYqwbJV3Co03NIi14uEHW8P50Md6KqFgt0FfpHdK6oyAHQVTosgPuPSiWud1HX0oYJ1hGRRlk0fP87jFpqXZA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", + "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.17.12.tgz", + "integrity": "sha512-9pgmuQAtFi3lpNUstvG9nGfk9DkrdmWNp9KeKPFmuZCpEnxRzYlS8JgwPjYj+1AWDOSvoGN0H30p1cBOmT/Svg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", + "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.2.tgz", + "integrity": "sha512-/cmuBVw9sZBGZVOMkpAEaVLwm4JmK2GZ1dFKOGGpMzEHWFmyZZ59lUU0PdRr8YNYeQdNzTDwuxP2X2gzydTc9g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.17.12.tgz", + "integrity": "sha512-Q8y+Jp7ZdtSPXCThB6zjQ74N3lj0f6TDh1Hnf5B+sYlzQ8i5Pjp8gW0My79iekSpT4WnI06blqP6DT0OmaXXmw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.4.tgz", + "integrity": "sha512-l4vHuSLUajptpHNEOUDEGsnpl9pfRLsN1XUoDQDD/YBuXTM+v37SHGS+c6n4jdcZy96QtuUuSvZYMLSSsjH8Mw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-typescript": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", + "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", + "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.2.tgz", + "integrity": "sha512-PfpdxotV6afmXMU47S08F9ZKIm2bJIQ0YbAAtDfIENX7G1NUAXigLREh69CWDjtgUy7dYn7bsMzkgdtAlmS68Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-compilation-targets": "^7.18.2", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.17.12", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.17.12", + "@babel/plugin-proposal-async-generator-functions": "^7.17.12", + "@babel/plugin-proposal-class-properties": "^7.17.12", + "@babel/plugin-proposal-class-static-block": "^7.18.0", + "@babel/plugin-proposal-dynamic-import": "^7.16.7", + "@babel/plugin-proposal-export-namespace-from": "^7.17.12", + "@babel/plugin-proposal-json-strings": "^7.17.12", + "@babel/plugin-proposal-logical-assignment-operators": "^7.17.12", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.17.12", + "@babel/plugin-proposal-numeric-separator": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.18.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", + "@babel/plugin-proposal-optional-chaining": "^7.17.12", + "@babel/plugin-proposal-private-methods": "^7.17.12", + "@babel/plugin-proposal-private-property-in-object": "^7.17.12", + "@babel/plugin-proposal-unicode-property-regex": "^7.17.12", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.17.12", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.17.12", + "@babel/plugin-transform-async-to-generator": "^7.17.12", + "@babel/plugin-transform-block-scoped-functions": "^7.16.7", + "@babel/plugin-transform-block-scoping": "^7.17.12", + "@babel/plugin-transform-classes": "^7.17.12", + "@babel/plugin-transform-computed-properties": "^7.17.12", + "@babel/plugin-transform-destructuring": "^7.18.0", + "@babel/plugin-transform-dotall-regex": "^7.16.7", + "@babel/plugin-transform-duplicate-keys": "^7.17.12", + "@babel/plugin-transform-exponentiation-operator": "^7.16.7", + "@babel/plugin-transform-for-of": "^7.18.1", + "@babel/plugin-transform-function-name": "^7.16.7", + "@babel/plugin-transform-literals": "^7.17.12", + "@babel/plugin-transform-member-expression-literals": "^7.16.7", + "@babel/plugin-transform-modules-amd": "^7.18.0", + "@babel/plugin-transform-modules-commonjs": "^7.18.2", + "@babel/plugin-transform-modules-systemjs": "^7.18.0", + "@babel/plugin-transform-modules-umd": "^7.18.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.17.12", + "@babel/plugin-transform-new-target": "^7.17.12", + "@babel/plugin-transform-object-super": "^7.16.7", + "@babel/plugin-transform-parameters": "^7.17.12", + "@babel/plugin-transform-property-literals": "^7.16.7", + "@babel/plugin-transform-regenerator": "^7.18.0", + "@babel/plugin-transform-reserved-words": "^7.17.12", + "@babel/plugin-transform-shorthand-properties": "^7.16.7", + "@babel/plugin-transform-spread": "^7.17.12", + "@babel/plugin-transform-sticky-regex": "^7.16.7", + "@babel/plugin-transform-template-literals": "^7.18.2", + "@babel/plugin-transform-typeof-symbol": "^7.17.12", + "@babel/plugin-transform-unicode-escapes": "^7.16.7", + "@babel/plugin-transform-unicode-regex": "^7.16.7", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.2", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.17.12.tgz", + "integrity": "sha512-S1ViF8W2QwAKUGJXxP9NAfNaqGDdEBJKpYkxHf5Yy2C4NPPzXGeR3Lhk7G8xJaaLcFTRfNjVbtbVtm8Gb0mqvg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-transform-typescript": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz", + "integrity": "sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.2.tgz", + "integrity": "sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.2", + "@babel/helper-environment-visitor": "^7.18.2", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.18.0", + "@babel/types": "^7.18.2", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", + "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.0.tgz", + "integrity": "sha512-tscn3dlJFGay47kb4qVruQg/XWlmvU0xp3EJOjzzY+sBaI+YgwKcvAmTcyYU7xEiLLIY5HCdWRooAL8dqkFlDA==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.0", + "jest-util": "^28.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/core": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.0.tgz", + "integrity": "sha512-/2PTt0ywhjZ4NwNO4bUqD9IVJfmFVhVKGlhvSpmEfUCuxYf/3NHcKmRFI+I71lYzbTT3wMuYpETDCTHo81gC/g==", + "dev": true, + "dependencies": { + "@jest/console": "^28.1.0", + "@jest/reporters": "^28.1.0", + "@jest/test-result": "^28.1.0", + "@jest/transform": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^28.0.2", + "jest-config": "^28.1.0", + "jest-haste-map": "^28.1.0", + "jest-message-util": "^28.1.0", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.0", + "jest-resolve-dependencies": "^28.1.0", + "jest-runner": "^28.1.0", + "jest-runtime": "^28.1.0", + "jest-snapshot": "^28.1.0", + "jest-util": "^28.1.0", + "jest-validate": "^28.1.0", + "jest-watcher": "^28.1.0", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/core/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + }, + "node_modules/@jest/environment": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.0.tgz", + "integrity": "sha512-S44WGSxkRngzHslhV6RoAExekfF7Qhwa6R5+IYFa81mpcj0YgdBnRSmvHe3SNwOt64yXaE5GG8Y2xM28ii5ssA==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/node": "*", + "jest-mock": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.0.tgz", + "integrity": "sha512-be9ETznPLaHOmeJqzYNIXv1ADEzENuQonIoobzThOYPuK/6GhrWNIJDVTgBLCrz3Am73PyEU2urQClZp0hLTtA==", + "dev": true, + "dependencies": { + "expect": "^28.1.0", + "jest-snapshot": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.0.tgz", + "integrity": "sha512-5BrG48dpC0sB80wpeIX5FU6kolDJI4K0n5BM9a5V38MGx0pyRvUBSS0u2aNTdDzmOrCjhOg8pGs6a20ivYkdmw==", + "dev": true, + "dependencies": { + "jest-get-type": "^28.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect-utils/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.0.tgz", + "integrity": "sha512-Xqsf/6VLeAAq78+GNPzI7FZQRf5cCHj1qgQxCjws9n8rKw8r1UYoeaALwBvyuzOkpU3c1I6emeMySPa96rxtIg==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.0", + "@sinonjs/fake-timers": "^9.1.1", + "@types/node": "*", + "jest-message-util": "^28.1.0", + "jest-mock": "^28.1.0", + "jest-util": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.0.tgz", + "integrity": "sha512-3m7sTg52OTQR6dPhsEQSxAvU+LOBbMivZBwOvKEZ+Rb+GyxVnXi9HKgOTYkx/S99T8yvh17U4tNNJPIEQmtwYw==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.0", + "@jest/expect": "^28.1.0", + "@jest/types": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.0.tgz", + "integrity": "sha512-qxbFfqap/5QlSpIizH9c/bFCDKsQlM4uAKSOvZrP+nIdrjqre3FmKzpTtYyhsaVcOSNK7TTt2kjm+4BJIjysFA==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^28.1.0", + "@jest/test-result": "^28.1.0", + "@jest/transform": "^28.1.0", + "@jest/types": "^28.1.0", + "@jridgewell/trace-mapping": "^0.3.7", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-util": "^28.1.0", + "jest-worker": "^28.1.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.0.2.tgz", + "integrity": "sha512-YVDJZjd4izeTDkij00vHHAymNXQ6WWsdChFRK86qck6Jpr3DCL5W3Is3vslviRlP+bLuMYRLbdp98amMvqudhA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.23.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.0.2.tgz", + "integrity": "sha512-Y9dxC8ZpN3kImkk0LkK5XCEneYMAXlZ8m5bflmSL5vrwyeUpJfentacCUg6fOb8NOpOO7hz2+l37MV77T6BFPw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.7", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.0.tgz", + "integrity": "sha512-sBBFIyoPzrZho3N+80P35A5oAkSKlGfsEFfXFWuPGBsW40UAjCkGakZhn4UQK4iQlW2vgCDMRDOob9FGKV8YoQ==", + "dev": true, + "dependencies": { + "@jest/console": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.0.tgz", + "integrity": "sha512-tZCEiVWlWNTs/2iK9yi6o3AlMfbbYgV4uuZInSVdzZ7ftpHZhCMuhvk2HLYhCZzLgPFQ9MnM1YaxMnh3TILFiQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^28.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.0.tgz", + "integrity": "sha512-omy2xe5WxlAfqmsTjTPxw+iXRTRnf+NtX0ToG+4S0tABeb4KsKmPUHq5UBuwunHg3tJRwgEQhEp0M/8oiatLEA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^28.1.0", + "@jridgewell/trace-mapping": "^0.3.7", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.0", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/types": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.0.tgz", + "integrity": "sha512-xmEggMPr317MIOjjDoZ4ejCSr9Lpbt/u34+dvc99t7DS8YirW5rwZEhzKPC2BMUFkUhI48qs6qLUSGw5FuL0GA==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.0.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", + "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", + "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz", + "integrity": "sha512-AFBVi/iT4g20DHoujvMH1aEDn8fGJh4xsRGCP6d8RpLPMqsNPvW01Jcn0QysXTsg++/xj25NmJsGyH9xug/wKg==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.1.19", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", + "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.17.1.tgz", + "integrity": "sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.1.tgz", + "integrity": "sha512-fUy7YRpT+rHXto1YlL+J9rs0uLGyiqVt3ZOTQR+4ROc47yNl8WLdVLgUloBRhOxP1PZvguHl44T3H0wAWxahYQ==", + "dev": true, + "dependencies": { + "jest-matcher-utils": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "17.0.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.38.tgz", + "integrity": "sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.3.tgz", + "integrity": "sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.10", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.10.tgz", + "integrity": "sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.0.tgz", + "integrity": "sha512-DDrIA7GXtmHXr1VCcx9HivA39eprYBIFxbQEHI6NyraRDxCGpxAFiYQAT/1Y0vh1C+o2vfBiy4IuPoXxtTZCAQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.27.0", + "@typescript-eslint/type-utils": "5.27.0", + "@typescript-eslint/utils": "5.27.0", + "debug": "^4.3.4", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.27.0.tgz", + "integrity": "sha512-8oGjQF46c52l7fMiPPvX4It3u3V3JipssqDfHQ2hcR0AeR8Zge+OYyKUCm5b70X72N1qXt0qgHenwN6Gc2SXZA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.27.0", + "@typescript-eslint/types": "5.27.0", + "@typescript-eslint/typescript-estree": "5.27.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.0.tgz", + "integrity": "sha512-VnykheBQ/sHd1Vt0LJ1JLrMH1GzHO+SzX6VTXuStISIsvRiurue/eRkTqSrG0CexHQgKG8shyJfR4o5VYioB9g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.27.0", + "@typescript-eslint/visitor-keys": "5.27.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.27.0.tgz", + "integrity": "sha512-vpTvRRchaf628Hb/Xzfek+85o//zEUotr1SmexKvTfs7czXfYjXVT/a5yDbpzLBX1rhbqxjDdr1Gyo0x1Fc64g==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "5.27.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.0.tgz", + "integrity": "sha512-lY6C7oGm9a/GWhmUDOs3xAVRz4ty/XKlQ2fOLr8GAIryGn0+UBOoJDWyHer3UgrHkenorwvBnphhP+zPmzmw0A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.0.tgz", + "integrity": "sha512-QywPMFvgZ+MHSLRofLI7BDL+UczFFHyj0vF5ibeChDAJgdTV8k4xgEwF0geFhVlPc1p8r70eYewzpo6ps+9LJQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.27.0", + "@typescript-eslint/visitor-keys": "5.27.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.0.tgz", + "integrity": "sha512-nZvCrkIJppym7cIbP3pOwIkAefXOmfGPnCM0LQfzNaKxJHI6VjI8NC662uoiPlaf5f6ymkTy9C3NQXev2mdXmA==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.27.0", + "@typescript-eslint/types": "5.27.0", + "@typescript-eslint/typescript-estree": "5.27.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.0.tgz", + "integrity": "sha512-46cYrteA2MrIAjv9ai44OQDUoCZyHeGIc4lsjCUX2WT6r4C+kidz1bNiR4017wHOPUythYeH+Sc7/cFP97KEAA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.27.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.0.tgz", + "integrity": "sha512-zNKk0yhDZ6QUwfxh9k07GII6siNGMJWVUU49gmFj5gfdqDKLqa2RArXOF2CODp4Dr7dLxN2cvAV+667dGJ4b4w==", + "dev": true, + "dependencies": { + "@jest/transform": "^28.1.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^28.0.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.0.2.tgz", + "integrity": "sha512-Kizhn/ZL+68ZQHxSnHyuvJv8IchXD62KQxV77TBDV/xoBFBOfgRAk97GNs6hXdTTCiVES9nB2I6+7MXXrk5llQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", + "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.3.1", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", + "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.1", + "core-js-compat": "^3.21.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.0.2.tgz", + "integrity": "sha512-sYzXIdgIXXroJTFeB3S6sNDWtlJ2dllCdTEsnZ65ACrMojj3hVNFRmnJ1HZtomGi+Be7aqpY/HJ92fr8OhKVkQ==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^28.0.2", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", + "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001332", + "electron-to-chromium": "^1.4.118", + "escalade": "^3.1.1", + "node-releases": "^2.0.3", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001344", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001344.tgz", + "integrity": "sha512-0ZFjnlCaXNOAYcV7i+TtdKBp0L/3XEU2MF/x6Du1lrh+SRX4IfzIVL4HNJg5pB2PmFb8rszIGyOvsZnqqRoc2g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.1.tgz", + "integrity": "sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg==", + "dev": true + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/core-js-compat": { + "version": "3.22.7", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.22.7.tgz", + "integrity": "sha512-uI9DAQKKiiE/mclIC5g4AjRpio27g+VMRhe6rQoz+q4Wm4L6A/fJhiLtBw+sfOpDG9wZ3O0pxIw7GbfOlBgjOA==", + "dev": true, + "dependencies": { + "browserslist": "^4.20.3", + "semver": "7.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.143", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.143.tgz", + "integrity": "sha512-2hIgvu0+pDfXIqmVmV5X6iwMjQ2KxDsWKwM+oI1fABEOy/Dqmll0QJRmIQ3rm+XaoUa/qKrmy5h7LSTFQ6Ldzg==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.16.0.tgz", + "integrity": "sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "dependencies": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.0.tgz", + "integrity": "sha512-qFXKl8Pmxk8TBGfaFKRtcQjfXEnKAs+dmlxdwvukJZorwrAabT7M3h8oLOG01I2utEhkmUTi17CHaPBovZsKdw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^28.1.0", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.0", + "jest-message-util": "^28.1.0", + "jest-util": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/expect/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/expect/node_modules/diff-sequences": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.0.2.tgz", + "integrity": "sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/expect/node_modules/jest-diff": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.0.tgz", + "integrity": "sha512-8eFd3U3OkIKRtlasXfiAQfbovgFgRDb0Ngcs2E+FMeBZ4rUezqIaGjuyggJBp+llosQXNEWofk/Sz4Hr5gMUhA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^28.0.2", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/expect/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/expect/node_modules/jest-matcher-utils": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.0.tgz", + "integrity": "sha512-onnax0n2uTLRQFKAjC7TuaxibrPSvZgKTcSCnNUz/tOjJ9UhxNm7ZmPpoQavmTDUjXvUQ8KesWk2/VdrxIFzTQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.0", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/expect/node_modules/pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/expect/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.0.tgz", + "integrity": "sha512-TZR+tHxopPhzw3c3560IJXZWLNHgpcz1Zh0w5A65vynLGNcg/5pZ+VildAd7+XGOu6jd58XMY/HNn0IkZIXVXg==", + "dev": true, + "dependencies": { + "@jest/core": "^28.1.0", + "import-local": "^3.0.2", + "jest-cli": "^28.1.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.0.2.tgz", + "integrity": "sha512-QX9u+5I2s54ZnGoMEjiM2WeBvJR2J7w/8ZUmH2um/WLAuGAYFQcsVXY9+1YL6k0H/AGUdH8pXUAv6erDqEsvIA==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "throat": "^6.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-circus": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.0.tgz", + "integrity": "sha512-rNYfqfLC0L0zQKRKsg4n4J+W1A2fbyGH7Ss/kDIocp9KXD9iaL111glsLu7+Z7FHuZxwzInMDXq+N1ZIBkI/TQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.0", + "@jest/expect": "^28.1.0", + "@jest/test-result": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^28.1.0", + "jest-matcher-utils": "^28.1.0", + "jest-message-util": "^28.1.0", + "jest-runtime": "^28.1.0", + "jest-snapshot": "^28.1.0", + "jest-util": "^28.1.0", + "pretty-format": "^28.1.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/diff-sequences": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.0.2.tgz", + "integrity": "sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-diff": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.0.tgz", + "integrity": "sha512-8eFd3U3OkIKRtlasXfiAQfbovgFgRDb0Ngcs2E+FMeBZ4rUezqIaGjuyggJBp+llosQXNEWofk/Sz4Hr5gMUhA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^28.0.2", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-matcher-utils": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.0.tgz", + "integrity": "sha512-onnax0n2uTLRQFKAjC7TuaxibrPSvZgKTcSCnNUz/tOjJ9UhxNm7ZmPpoQavmTDUjXvUQ8KesWk2/VdrxIFzTQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.0", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-circus/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + }, + "node_modules/jest-cli": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.0.tgz", + "integrity": "sha512-fDJRt6WPRriHrBsvvgb93OxgajHHsJbk4jZxiPqmZbMDRcHskfJBBfTyjFko0jjfprP544hOktdSi9HVgl4VUQ==", + "dev": true, + "dependencies": { + "@jest/core": "^28.1.0", + "@jest/test-result": "^28.1.0", + "@jest/types": "^28.1.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^28.1.0", + "jest-util": "^28.1.0", + "jest-validate": "^28.1.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.0.tgz", + "integrity": "sha512-aOV80E9LeWrmflp7hfZNn/zGA4QKv/xsn2w8QCBP0t0+YqObuCWTSgNbHJ0j9YsTuCO08ZR/wsvlxqqHX20iUA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^28.1.0", + "@jest/types": "^28.1.0", + "babel-jest": "^28.1.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^28.1.0", + "jest-environment-node": "^28.1.0", + "jest-get-type": "^28.0.2", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.0", + "jest-runner": "^28.1.0", + "jest-util": "^28.1.0", + "jest-validate": "^28.1.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^28.1.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-config/node_modules/pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-config/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + }, + "node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.0.2.tgz", + "integrity": "sha512-FH10WWw5NxLoeSdQlJwu+MTiv60aXV/t8KEwIRGEv74WARE1cXIqh1vGdy2CraHuWOOrnzTWj/azQKqW4fO7xg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-each": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.0.tgz", + "integrity": "sha512-a/XX02xF5NTspceMpHujmOexvJ4GftpYXqr6HhhmKmExtMXsyIN/fvanQlt/BcgFoRKN4OCXxLQKth9/n6OPFg==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.0", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "jest-util": "^28.1.0", + "pretty-format": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-each/node_modules/pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-each/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + }, + "node_modules/jest-environment-node": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.0.tgz", + "integrity": "sha512-gBLZNiyrPw9CSMlTXF1yJhaBgWDPVvH0Pq6bOEwGMXaYNzhzhw2kA/OijNF8egbCgDS0/veRv97249x2CX+udQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.0", + "@jest/fake-timers": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/node": "*", + "jest-mock": "^28.1.0", + "jest-util": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.0.tgz", + "integrity": "sha512-xyZ9sXV8PtKi6NCrJlmq53PyNVHzxmcfXNVvIRHpHmh1j/HChC4pwKgyjj7Z9us19JMw8PpQTJsFWOsIfT93Dw==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.0", + "jest-worker": "^28.1.0", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.0.tgz", + "integrity": "sha512-uIJDQbxwEL2AMMs2xjhZl2hw8s77c3wrPaQ9v6tXJLGaaQ+4QrNJH5vuw7hA7w/uGT/iJ42a83opAqxGHeyRIA==", + "dev": true, + "dependencies": { + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + }, + "node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.0.tgz", + "integrity": "sha512-RpA8mpaJ/B2HphDMiDlrAZdDytkmwFqgjDZovM21F35lHGeUeCvYmm6W+sbQ0ydaLpg5bFAUuWG1cjqOl8vqrw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-message-util/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + }, + "node_modules/jest-mock": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.0.tgz", + "integrity": "sha512-H7BrhggNn77WhdL7O1apG0Q/iwl0Bdd5E1ydhCJzL3oBLh/UYxAwR3EJLsBZ9XA3ZU4PA3UNw4tQjduBTCTmLw==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.0", + "@types/node": "*" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.0.tgz", + "integrity": "sha512-vvfN7+tPNnnhDvISuzD1P+CRVP8cK0FHXRwPAcdDaQv4zgvwvag2n55/h5VjYcM5UJG7L4TwE5tZlzcI0X2Lhw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^28.1.0", + "jest-validate": "^28.1.0", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.0.tgz", + "integrity": "sha512-Ue1VYoSZquPwEvng7Uefw8RmZR+me/1kr30H2jMINjGeHgeO/JgrR6wxj2ofkJ7KSAA11W3cOrhNCbj5Dqqd9g==", + "dev": true, + "dependencies": { + "jest-regex-util": "^28.0.2", + "jest-snapshot": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-runner": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.0.tgz", + "integrity": "sha512-FBpmuh1HB2dsLklAlRdOxNTTHKFR6G1Qmd80pVDvwbZXTriqjWqjei5DKFC1UlM732KjYcE6yuCdiF0WUCOS2w==", + "dev": true, + "dependencies": { + "@jest/console": "^28.1.0", + "@jest/environment": "^28.1.0", + "@jest/test-result": "^28.1.0", + "@jest/transform": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^28.0.2", + "jest-environment-node": "^28.1.0", + "jest-haste-map": "^28.1.0", + "jest-leak-detector": "^28.1.0", + "jest-message-util": "^28.1.0", + "jest-resolve": "^28.1.0", + "jest-runtime": "^28.1.0", + "jest-util": "^28.1.0", + "jest-watcher": "^28.1.0", + "jest-worker": "^28.1.0", + "source-map-support": "0.5.13", + "throat": "^6.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.0.tgz", + "integrity": "sha512-wNYDiwhdH/TV3agaIyVF0lsJ33MhyujOe+lNTUiolqKt8pchy1Hq4+tDMGbtD5P/oNLA3zYrpx73T9dMTOCAcg==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.0", + "@jest/fake-timers": "^28.1.0", + "@jest/globals": "^28.1.0", + "@jest/source-map": "^28.0.2", + "@jest/test-result": "^28.1.0", + "@jest/transform": "^28.1.0", + "@jest/types": "^28.1.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.0", + "jest-message-util": "^28.1.0", + "jest-mock": "^28.1.0", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.0", + "jest-snapshot": "^28.1.0", + "jest-util": "^28.1.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.0.tgz", + "integrity": "sha512-ex49M2ZrZsUyQLpLGxQtDbahvgBjlLPgklkqGM0hq/F7W/f8DyqZxVHjdy19QKBm4O93eDp+H5S23EiTbbUmHw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^28.1.0", + "@jest/transform": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^28.1.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^28.1.0", + "jest-get-type": "^28.0.2", + "jest-haste-map": "^28.1.0", + "jest-matcher-utils": "^28.1.0", + "jest-message-util": "^28.1.0", + "jest-util": "^28.1.0", + "natural-compare": "^1.4.0", + "pretty-format": "^28.1.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/diff-sequences": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.0.2.tgz", + "integrity": "sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-diff": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.0.tgz", + "integrity": "sha512-8eFd3U3OkIKRtlasXfiAQfbovgFgRDb0Ngcs2E+FMeBZ4rUezqIaGjuyggJBp+llosQXNEWofk/Sz4Hr5gMUhA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^28.0.2", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-matcher-utils": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.0.tgz", + "integrity": "sha512-onnax0n2uTLRQFKAjC7TuaxibrPSvZgKTcSCnNUz/tOjJ9UhxNm7ZmPpoQavmTDUjXvUQ8KesWk2/VdrxIFzTQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.0", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.0.tgz", + "integrity": "sha512-qYdCKD77k4Hwkose2YBEqQk7PzUf/NSE+rutzceduFveQREeH6b+89Dc9+wjX9dAwHcgdx4yedGA3FQlU/qCTA==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-validate": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.0.tgz", + "integrity": "sha512-Lly7CJYih3vQBfjLeANGgBSBJ7pEa18cxpQfQEq2go2xyEzehnHfQTjoUia8xUv4x4J80XKFIDwJJThXtRFQXQ==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "leven": "^3.1.0", + "pretty-format": "^28.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-validate/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + }, + "node_modules/jest-watcher": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.0.tgz", + "integrity": "sha512-tNHMtfLE8Njcr2IRS+5rXYA4BhU90gAOwI9frTGOqd+jX0P/Au/JfRSNqsf5nUTcWdbVYuLxS1KjnzILSoR5hA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-worker": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.0.tgz", + "integrity": "sha512-ZHwM6mNwaWBR52Snff8ZvsCTqQsvhCxP/bT1I6T6DAnb6ygkshsyLQIMxFwHpYxht0HOoqt23JlC01viI7T03A==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", + "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", + "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", + "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/throat": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", + "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", + "dev": true + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-node": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.0.tgz", + "integrity": "sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz", + "integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.0.tgz", + "integrity": "sha512-HcvgY/xaRm7isYmyx+lFKA4uQmfUbN0J4M0nNItvzTvH/iQ9kW5j/t4YSR+Ge323/lrgDAWJoF46tzGQHwBHFw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.7", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.1.tgz", + "integrity": "sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.7" + } + }, + "@babel/compat-data": { + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", + "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", + "dev": true + }, + "@babel/core": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.2.tgz", + "integrity": "sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.2", + "@babel/helper-compilation-targets": "^7.18.2", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helpers": "^7.18.2", + "@babel/parser": "^7.18.0", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + } + }, + "@babel/generator": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", + "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", + "dev": true, + "requires": { + "@babel/types": "^7.18.2", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", + "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", + "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.16.7", + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz", + "integrity": "sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.0.tgz", + "integrity": "sha512-Kh8zTGR9de3J63e5nS0rQUdRs/kbtwoeQQ0sriS0lItjC96u8XXZN6lKpuyWd2coKSU13py/y+LTmThLuVX0Pg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-member-expression-to-functions": "^7.17.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.12.tgz", + "integrity": "sha512-b2aZrV4zvutr9AIa6/gA3wsZKRwTKYoDxYiFKcESS3Ug2GTXzwBEvMuuFLhCQpEnRXs1zng4ISAXSUxxKBIcxw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "regexpu-core": "^5.0.1" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", + "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz", + "integrity": "sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==", + "dev": true + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", + "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-function-name": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", + "dev": true, + "requires": { + "@babel/template": "^7.16.7", + "@babel/types": "^7.17.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", + "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", + "dev": true, + "requires": { + "@babel/types": "^7.17.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-module-transforms": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz", + "integrity": "sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.0", + "@babel/types": "^7.18.0" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz", + "integrity": "sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", + "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-wrap-function": "^7.16.8", + "@babel/types": "^7.16.8" + } + }, + "@babel/helper-replace-supers": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.2.tgz", + "integrity": "sha512-XzAIyxx+vFnrOxiQrToSUOzUOn0e1J2Li40ntddek1Y69AXUTXoDJ40/D5RdjFu7s7qHiaeoTiempZcbuVXh2Q==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.2", + "@babel/helper-member-expression-to-functions": "^7.17.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2" + } + }, + "@babel/helper-simple-access": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz", + "integrity": "sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==", + "dev": true, + "requires": { + "@babel/types": "^7.18.2" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", + "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8" + } + }, + "@babel/helpers": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.2.tgz", + "integrity": "sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg==", + "dev": true, + "requires": { + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.2" + } + }, + "@babel/highlight": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", + "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz", + "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==", + "dev": true + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.17.12.tgz", + "integrity": "sha512-xCJQXl4EeQ3J9C4yOmpTrtVGmzpm2iSzyxbkZHw7UCnZBftHpF/hpII80uWVyVrc40ytIClHjgWGTG1g/yB+aw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.17.12.tgz", + "integrity": "sha512-/vt0hpIw0x4b6BLKUkwlvEoiGZYYLNZ96CzyHYPbtG2jZGz6LBe7/V+drYrc/d+ovrF9NBi0pmtvmNb/FsWtRQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.17.12" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.17.12.tgz", + "integrity": "sha512-RWVvqD1ooLKP6IqWTA5GyFVX2isGEgC5iFxKzfYOIy/QEFdxYyCybBDtIGjipHpb9bDWHzcqGqFakf+mVmBTdQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-remap-async-to-generator": "^7.16.8", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.17.12.tgz", + "integrity": "sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.0.tgz", + "integrity": "sha512-t+8LsRMMDE74c6sV7KShIw13sqbqd58tlqNrsWoWBTIMw7SVQ0cZ905wLNS/FBCy/3PyooRHLFFlfrUNyyz5lA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", + "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.17.12.tgz", + "integrity": "sha512-j7Ye5EWdwoXOpRmo5QmRyHPsDIe6+u70ZYZrd7uz+ebPYFKfRcLcNu3Ro0vOlJ5zuv8rU7xa+GttNiRzX56snQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.17.12.tgz", + "integrity": "sha512-rKJ+rKBoXwLnIn7n6o6fulViHMrOThz99ybH+hKHcOZbnN14VuMnH9fo2eHE69C8pO4uX1Q7t2HYYIDmv8VYkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.17.12.tgz", + "integrity": "sha512-EqFo2s1Z5yy+JeJu7SFfbIUtToJTVlC61/C7WLKDntSw4Sz6JNAIfL7zQ74VvirxpjB5kz/kIx0gCcb+5OEo2Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.17.12.tgz", + "integrity": "sha512-ws/g3FSGVzv+VH86+QvgtuJL/kR67xaEIF2x0iPqdDfYW6ra6JF3lKVBkWynRLcNtIC1oCTfDRVxmm2mKzy+ag==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", + "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.0.tgz", + "integrity": "sha512-nbTv371eTrFabDfHLElkn9oyf9VG+VKK6WMzhY2o4eHKaG19BToD9947zzGMO6I/Irstx9d8CwX6njPNIAR/yw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-compilation-targets": "^7.17.10", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.17.12" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", + "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.17.12.tgz", + "integrity": "sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.17.12.tgz", + "integrity": "sha512-SllXoxo19HmxhDWm3luPz+cPhtoTSKLJE9PXshsfrOzBqs60QP0r8OaJItrPhAj0d7mZMnNF0Y1UUggCDgMz1A==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.17.12.tgz", + "integrity": "sha512-/6BtVi57CJfrtDNKfK5b66ydK2J5pXUKBKSPD2G1whamMuEnZWgoOIfO8Vf9F/DoD4izBLD/Au4NMQfruzzykg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.17.12.tgz", + "integrity": "sha512-Wb9qLjXf3ZazqXA7IvI7ozqRIXIGPtSo+L5coFmEkhTQK18ao4UDDD0zdTGAarmbLj2urpRwrc6893cu5Bfh0A==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.17.12.tgz", + "integrity": "sha512-n/loy2zkq9ZEM8tEOwON9wTQSTNDTDEz6NujPtJGLU7qObzT1N4c4YZZf8E6ATB2AjNQg/Ib2AIpO03EZaCehw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz", + "integrity": "sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.17.12.tgz", + "integrity": "sha512-PHln3CNi/49V+mza4xMwrg+WGYevSF1oaiXaC2EQfdp4HWlSjRsrDXWJiQBKpP7749u6vQ9mcry2uuFOv5CXvA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.17.12.tgz", + "integrity": "sha512-J8dbrWIOO3orDzir57NRsjg4uxucvhby0L/KZuGsWDj0g7twWK3g7JhJhOrXtuXiw8MeiSdJ3E0OW9H8LYEzLQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-remap-async-to-generator": "^7.16.8" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", + "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.4.tgz", + "integrity": "sha512-+Hq10ye+jlvLEogSOtq4mKvtk7qwcUQ1f0Mrueai866C82f844Yom2cttfJdMdqRLTxWpsbfbkIkOIfovyUQXw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.4.tgz", + "integrity": "sha512-e42NSG2mlKWgxKUAD9EJJSkZxR67+wZqzNxLSpc51T8tRU5SLFHsPmgYR5yr7sdgX4u+iHA1C5VafJ6AyImV3A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.18.2", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-replace-supers": "^7.18.2", + "@babel/helper-split-export-declaration": "^7.16.7", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.17.12.tgz", + "integrity": "sha512-a7XINeplB5cQUWMg1E/GI1tFz3LfK021IjV1rj1ypE+R7jHm+pIHmHl25VNkZxtx9uuYp7ThGk8fur1HHG7PgQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.0.tgz", + "integrity": "sha512-Mo69klS79z6KEfrLg/1WkmVnB8javh75HX4pi2btjvlIoasuxilEyjtsQW6XPrubNd7AQy0MMaNIaQE4e7+PQw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", + "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.17.12.tgz", + "integrity": "sha512-EA5eYFUG6xeerdabina/xIoB95jJ17mAkR8ivx6ZSu9frKShBjpOGZPn511MTDTkiCO+zXnzNczvUM69YSf3Zw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", + "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.1.tgz", + "integrity": "sha512-+TTB5XwvJ5hZbO8xvl2H4XaMDOAK57zF4miuC9qQJgysPNEAZZ9Z69rdF5LJkozGdZrjBIUAIyKUWRMmebI7vg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", + "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.17.12.tgz", + "integrity": "sha512-8iRkvaTjJciWycPIZ9k9duu663FT7VrBdNqNgxnVXEFwOIp55JWcZd23VBRySYbnS3PwQ3rGiabJBBBGj5APmQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", + "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.0.tgz", + "integrity": "sha512-h8FjOlYmdZwl7Xm2Ug4iX2j7Qy63NANI+NQVWQzv6r25fqgg7k2dZl03p95kvqNclglHs4FZ+isv4p1uXMA+QA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.2.tgz", + "integrity": "sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-simple-access": "^7.18.2", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.4.tgz", + "integrity": "sha512-lH2UaQaHVOAeYrUUuZ8i38o76J/FnO8vu21OE+tD1MyP9lxdZoSfz+pDbWkq46GogUrdrMz3tiz/FYGB+bVThg==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-identifier": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.0.tgz", + "integrity": "sha512-d/zZ8I3BWli1tmROLxXLc9A6YXvGK8egMxHp+E/rRwMh1Kip0AP77VwZae3snEJ33iiWwvNv2+UIIhfalqhzZA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.12.tgz", + "integrity": "sha512-vWoWFM5CKaTeHrdUJ/3SIOTRV+MBVGybOC9mhJkaprGNt5demMymDW24yC74avb915/mIRe3TgNb/d8idvnCRA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.17.12.tgz", + "integrity": "sha512-CaOtzk2fDYisbjAD4Sd1MTKGVIpRtx9bWLyj24Y/k6p4s4gQ3CqDGJauFJxt8M/LEx003d0i3klVqnN73qvK3w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", + "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.17.12.tgz", + "integrity": "sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", + "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.0.tgz", + "integrity": "sha512-C8YdRw9uzx25HSIzwA7EM7YP0FhCe5wNvJbZzjVNHHPGVcDJ3Aie+qGYYdS1oVQgn+B3eAIJbWFLrJ4Jipv7nw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "regenerator-transform": "^0.15.0" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.17.12.tgz", + "integrity": "sha512-1KYqwbJV3Co03NIi14uEHW8P50Md6KqFgt0FfpHdK6oyAHQVTosgPuPSiWud1HX0oYJ1hGRRlk0fP87jFpqXZA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", + "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.17.12.tgz", + "integrity": "sha512-9pgmuQAtFi3lpNUstvG9nGfk9DkrdmWNp9KeKPFmuZCpEnxRzYlS8JgwPjYj+1AWDOSvoGN0H30p1cBOmT/Svg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", + "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.2.tgz", + "integrity": "sha512-/cmuBVw9sZBGZVOMkpAEaVLwm4JmK2GZ1dFKOGGpMzEHWFmyZZ59lUU0PdRr8YNYeQdNzTDwuxP2X2gzydTc9g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.17.12.tgz", + "integrity": "sha512-Q8y+Jp7ZdtSPXCThB6zjQ74N3lj0f6TDh1Hnf5B+sYlzQ8i5Pjp8gW0My79iekSpT4WnI06blqP6DT0OmaXXmw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.4.tgz", + "integrity": "sha512-l4vHuSLUajptpHNEOUDEGsnpl9pfRLsN1XUoDQDD/YBuXTM+v37SHGS+c6n4jdcZy96QtuUuSvZYMLSSsjH8Mw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-typescript": "^7.17.12" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", + "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", + "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/preset-env": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.2.tgz", + "integrity": "sha512-PfpdxotV6afmXMU47S08F9ZKIm2bJIQ0YbAAtDfIENX7G1NUAXigLREh69CWDjtgUy7dYn7bsMzkgdtAlmS68Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-compilation-targets": "^7.18.2", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.17.12", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.17.12", + "@babel/plugin-proposal-async-generator-functions": "^7.17.12", + "@babel/plugin-proposal-class-properties": "^7.17.12", + "@babel/plugin-proposal-class-static-block": "^7.18.0", + "@babel/plugin-proposal-dynamic-import": "^7.16.7", + "@babel/plugin-proposal-export-namespace-from": "^7.17.12", + "@babel/plugin-proposal-json-strings": "^7.17.12", + "@babel/plugin-proposal-logical-assignment-operators": "^7.17.12", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.17.12", + "@babel/plugin-proposal-numeric-separator": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.18.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", + "@babel/plugin-proposal-optional-chaining": "^7.17.12", + "@babel/plugin-proposal-private-methods": "^7.17.12", + "@babel/plugin-proposal-private-property-in-object": "^7.17.12", + "@babel/plugin-proposal-unicode-property-regex": "^7.17.12", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.17.12", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.17.12", + "@babel/plugin-transform-async-to-generator": "^7.17.12", + "@babel/plugin-transform-block-scoped-functions": "^7.16.7", + "@babel/plugin-transform-block-scoping": "^7.17.12", + "@babel/plugin-transform-classes": "^7.17.12", + "@babel/plugin-transform-computed-properties": "^7.17.12", + "@babel/plugin-transform-destructuring": "^7.18.0", + "@babel/plugin-transform-dotall-regex": "^7.16.7", + "@babel/plugin-transform-duplicate-keys": "^7.17.12", + "@babel/plugin-transform-exponentiation-operator": "^7.16.7", + "@babel/plugin-transform-for-of": "^7.18.1", + "@babel/plugin-transform-function-name": "^7.16.7", + "@babel/plugin-transform-literals": "^7.17.12", + "@babel/plugin-transform-member-expression-literals": "^7.16.7", + "@babel/plugin-transform-modules-amd": "^7.18.0", + "@babel/plugin-transform-modules-commonjs": "^7.18.2", + "@babel/plugin-transform-modules-systemjs": "^7.18.0", + "@babel/plugin-transform-modules-umd": "^7.18.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.17.12", + "@babel/plugin-transform-new-target": "^7.17.12", + "@babel/plugin-transform-object-super": "^7.16.7", + "@babel/plugin-transform-parameters": "^7.17.12", + "@babel/plugin-transform-property-literals": "^7.16.7", + "@babel/plugin-transform-regenerator": "^7.18.0", + "@babel/plugin-transform-reserved-words": "^7.17.12", + "@babel/plugin-transform-shorthand-properties": "^7.16.7", + "@babel/plugin-transform-spread": "^7.17.12", + "@babel/plugin-transform-sticky-regex": "^7.16.7", + "@babel/plugin-transform-template-literals": "^7.18.2", + "@babel/plugin-transform-typeof-symbol": "^7.17.12", + "@babel/plugin-transform-unicode-escapes": "^7.16.7", + "@babel/plugin-transform-unicode-regex": "^7.16.7", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.2", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/preset-typescript": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.17.12.tgz", + "integrity": "sha512-S1ViF8W2QwAKUGJXxP9NAfNaqGDdEBJKpYkxHf5Yy2C4NPPzXGeR3Lhk7G8xJaaLcFTRfNjVbtbVtm8Gb0mqvg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-transform-typescript": "^7.17.12" + } + }, + "@babel/runtime": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz", + "integrity": "sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" + } + }, + "@babel/traverse": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.2.tgz", + "integrity": "sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.2", + "@babel/helper-environment-visitor": "^7.18.2", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.18.0", + "@babel/types": "^7.18.2", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", + "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, + "@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.0.tgz", + "integrity": "sha512-tscn3dlJFGay47kb4qVruQg/XWlmvU0xp3EJOjzzY+sBaI+YgwKcvAmTcyYU7xEiLLIY5HCdWRooAL8dqkFlDA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.0", + "jest-util": "^28.1.0", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.0.tgz", + "integrity": "sha512-/2PTt0ywhjZ4NwNO4bUqD9IVJfmFVhVKGlhvSpmEfUCuxYf/3NHcKmRFI+I71lYzbTT3wMuYpETDCTHo81gC/g==", + "dev": true, + "requires": { + "@jest/console": "^28.1.0", + "@jest/reporters": "^28.1.0", + "@jest/test-result": "^28.1.0", + "@jest/transform": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^28.0.2", + "jest-config": "^28.1.0", + "jest-haste-map": "^28.1.0", + "jest-message-util": "^28.1.0", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.0", + "jest-resolve-dependencies": "^28.1.0", + "jest-runner": "^28.1.0", + "jest-runtime": "^28.1.0", + "jest-snapshot": "^28.1.0", + "jest-util": "^28.1.0", + "jest-validate": "^28.1.0", + "jest-watcher": "^28.1.0", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + } + } + }, + "@jest/environment": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.0.tgz", + "integrity": "sha512-S44WGSxkRngzHslhV6RoAExekfF7Qhwa6R5+IYFa81mpcj0YgdBnRSmvHe3SNwOt64yXaE5GG8Y2xM28ii5ssA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/node": "*", + "jest-mock": "^28.1.0" + } + }, + "@jest/expect": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.0.tgz", + "integrity": "sha512-be9ETznPLaHOmeJqzYNIXv1ADEzENuQonIoobzThOYPuK/6GhrWNIJDVTgBLCrz3Am73PyEU2urQClZp0hLTtA==", + "dev": true, + "requires": { + "expect": "^28.1.0", + "jest-snapshot": "^28.1.0" + } + }, + "@jest/expect-utils": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.0.tgz", + "integrity": "sha512-5BrG48dpC0sB80wpeIX5FU6kolDJI4K0n5BM9a5V38MGx0pyRvUBSS0u2aNTdDzmOrCjhOg8pGs6a20ivYkdmw==", + "dev": true, + "requires": { + "jest-get-type": "^28.0.2" + }, + "dependencies": { + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + } + } + }, + "@jest/fake-timers": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.0.tgz", + "integrity": "sha512-Xqsf/6VLeAAq78+GNPzI7FZQRf5cCHj1qgQxCjws9n8rKw8r1UYoeaALwBvyuzOkpU3c1I6emeMySPa96rxtIg==", + "dev": true, + "requires": { + "@jest/types": "^28.1.0", + "@sinonjs/fake-timers": "^9.1.1", + "@types/node": "*", + "jest-message-util": "^28.1.0", + "jest-mock": "^28.1.0", + "jest-util": "^28.1.0" + } + }, + "@jest/globals": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.0.tgz", + "integrity": "sha512-3m7sTg52OTQR6dPhsEQSxAvU+LOBbMivZBwOvKEZ+Rb+GyxVnXi9HKgOTYkx/S99T8yvh17U4tNNJPIEQmtwYw==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.0", + "@jest/expect": "^28.1.0", + "@jest/types": "^28.1.0" + } + }, + "@jest/reporters": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.0.tgz", + "integrity": "sha512-qxbFfqap/5QlSpIizH9c/bFCDKsQlM4uAKSOvZrP+nIdrjqre3FmKzpTtYyhsaVcOSNK7TTt2kjm+4BJIjysFA==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^28.1.0", + "@jest/test-result": "^28.1.0", + "@jest/transform": "^28.1.0", + "@jest/types": "^28.1.0", + "@jridgewell/trace-mapping": "^0.3.7", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-util": "^28.1.0", + "jest-worker": "^28.1.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.0" + } + }, + "@jest/schemas": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.0.2.tgz", + "integrity": "sha512-YVDJZjd4izeTDkij00vHHAymNXQ6WWsdChFRK86qck6Jpr3DCL5W3Is3vslviRlP+bLuMYRLbdp98amMvqudhA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.23.3" + } + }, + "@jest/source-map": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.0.2.tgz", + "integrity": "sha512-Y9dxC8ZpN3kImkk0LkK5XCEneYMAXlZ8m5bflmSL5vrwyeUpJfentacCUg6fOb8NOpOO7hz2+l37MV77T6BFPw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.7", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.0.tgz", + "integrity": "sha512-sBBFIyoPzrZho3N+80P35A5oAkSKlGfsEFfXFWuPGBsW40UAjCkGakZhn4UQK4iQlW2vgCDMRDOob9FGKV8YoQ==", + "dev": true, + "requires": { + "@jest/console": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.0.tgz", + "integrity": "sha512-tZCEiVWlWNTs/2iK9yi6o3AlMfbbYgV4uuZInSVdzZ7ftpHZhCMuhvk2HLYhCZzLgPFQ9MnM1YaxMnh3TILFiQ==", + "dev": true, + "requires": { + "@jest/test-result": "^28.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.0", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.0.tgz", + "integrity": "sha512-omy2xe5WxlAfqmsTjTPxw+iXRTRnf+NtX0ToG+4S0tABeb4KsKmPUHq5UBuwunHg3tJRwgEQhEp0M/8oiatLEA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^28.1.0", + "@jridgewell/trace-mapping": "^0.3.7", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.0", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + } + }, + "@jest/types": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.0.tgz", + "integrity": "sha512-xmEggMPr317MIOjjDoZ4ejCSr9Lpbt/u34+dvc99t7DS8YirW5rwZEhzKPC2BMUFkUhI48qs6qLUSGw5FuL0GA==", + "dev": true, + "requires": { + "@jest/schemas": "^28.0.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", + "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", + "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@sinclair/typebox": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz", + "integrity": "sha512-AFBVi/iT4g20DHoujvMH1aEDn8fGJh4xsRGCP6d8RpLPMqsNPvW01Jcn0QysXTsg++/xj25NmJsGyH9xug/wKg==", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "@types/babel__core": { + "version": "7.1.19", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", + "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.17.1.tgz", + "integrity": "sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.1.tgz", + "integrity": "sha512-fUy7YRpT+rHXto1YlL+J9rs0uLGyiqVt3ZOTQR+4ROc47yNl8WLdVLgUloBRhOxP1PZvguHl44T3H0wAWxahYQ==", + "dev": true, + "requires": { + "jest-matcher-utils": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/node": { + "version": "17.0.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.38.tgz", + "integrity": "sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==", + "dev": true + }, + "@types/prettier": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.3.tgz", + "integrity": "sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.10", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.10.tgz", + "integrity": "sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.0.tgz", + "integrity": "sha512-DDrIA7GXtmHXr1VCcx9HivA39eprYBIFxbQEHI6NyraRDxCGpxAFiYQAT/1Y0vh1C+o2vfBiy4IuPoXxtTZCAQ==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.27.0", + "@typescript-eslint/type-utils": "5.27.0", + "@typescript-eslint/utils": "5.27.0", + "debug": "^4.3.4", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.27.0.tgz", + "integrity": "sha512-8oGjQF46c52l7fMiPPvX4It3u3V3JipssqDfHQ2hcR0AeR8Zge+OYyKUCm5b70X72N1qXt0qgHenwN6Gc2SXZA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.27.0", + "@typescript-eslint/types": "5.27.0", + "@typescript-eslint/typescript-estree": "5.27.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.0.tgz", + "integrity": "sha512-VnykheBQ/sHd1Vt0LJ1JLrMH1GzHO+SzX6VTXuStISIsvRiurue/eRkTqSrG0CexHQgKG8shyJfR4o5VYioB9g==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.27.0", + "@typescript-eslint/visitor-keys": "5.27.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.27.0.tgz", + "integrity": "sha512-vpTvRRchaf628Hb/Xzfek+85o//zEUotr1SmexKvTfs7czXfYjXVT/a5yDbpzLBX1rhbqxjDdr1Gyo0x1Fc64g==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "5.27.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.0.tgz", + "integrity": "sha512-lY6C7oGm9a/GWhmUDOs3xAVRz4ty/XKlQ2fOLr8GAIryGn0+UBOoJDWyHer3UgrHkenorwvBnphhP+zPmzmw0A==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.0.tgz", + "integrity": "sha512-QywPMFvgZ+MHSLRofLI7BDL+UczFFHyj0vF5ibeChDAJgdTV8k4xgEwF0geFhVlPc1p8r70eYewzpo6ps+9LJQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.27.0", + "@typescript-eslint/visitor-keys": "5.27.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/utils": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.0.tgz", + "integrity": "sha512-nZvCrkIJppym7cIbP3pOwIkAefXOmfGPnCM0LQfzNaKxJHI6VjI8NC662uoiPlaf5f6ymkTy9C3NQXev2mdXmA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.27.0", + "@typescript-eslint/types": "5.27.0", + "@typescript-eslint/typescript-estree": "5.27.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.0.tgz", + "integrity": "sha512-46cYrteA2MrIAjv9ai44OQDUoCZyHeGIc4lsjCUX2WT6r4C+kidz1bNiR4017wHOPUythYeH+Sc7/cFP97KEAA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.27.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "babel-jest": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.0.tgz", + "integrity": "sha512-zNKk0yhDZ6QUwfxh9k07GII6siNGMJWVUU49gmFj5gfdqDKLqa2RArXOF2CODp4Dr7dLxN2cvAV+667dGJ4b4w==", + "dev": true, + "requires": { + "@jest/transform": "^28.1.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^28.0.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.0.2.tgz", + "integrity": "sha512-Kizhn/ZL+68ZQHxSnHyuvJv8IchXD62KQxV77TBDV/xoBFBOfgRAk97GNs6hXdTTCiVES9nB2I6+7MXXrk5llQ==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", + "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.3.1", + "semver": "^6.1.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", + "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.1", + "core-js-compat": "^3.21.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.1" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.0.2.tgz", + "integrity": "sha512-sYzXIdgIXXroJTFeB3S6sNDWtlJ2dllCdTEsnZ65ACrMojj3hVNFRmnJ1HZtomGi+Be7aqpY/HJ92fr8OhKVkQ==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^28.0.2", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", + "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001332", + "electron-to-chromium": "^1.4.118", + "escalade": "^3.1.1", + "node-releases": "^2.0.3", + "picocolors": "^1.0.0" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001344", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001344.tgz", + "integrity": "sha512-0ZFjnlCaXNOAYcV7i+TtdKBp0L/3XEU2MF/x6Du1lrh+SRX4IfzIVL4HNJg5pB2PmFb8rszIGyOvsZnqqRoc2g==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "ci-info": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.1.tgz", + "integrity": "sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "core-js-compat": { + "version": "3.22.7", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.22.7.tgz", + "integrity": "sha512-uI9DAQKKiiE/mclIC5g4AjRpio27g+VMRhe6rQoz+q4Wm4L6A/fJhiLtBw+sfOpDG9wZ3O0pxIw7GbfOlBgjOA==", + "dev": true, + "requires": { + "browserslist": "^4.20.3", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "electron-to-chromium": { + "version": "1.4.143", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.143.tgz", + "integrity": "sha512-2hIgvu0+pDfXIqmVmV5X6iwMjQ2KxDsWKwM+oI1fABEOy/Dqmll0QJRmIQ3rm+XaoUa/qKrmy5h7LSTFQ6Ldzg==", + "dev": true + }, + "emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "eslint": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.16.0.tgz", + "integrity": "sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + } + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "requires": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expect": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.0.tgz", + "integrity": "sha512-qFXKl8Pmxk8TBGfaFKRtcQjfXEnKAs+dmlxdwvukJZorwrAabT7M3h8oLOG01I2utEhkmUTi17CHaPBovZsKdw==", + "dev": true, + "requires": { + "@jest/expect-utils": "^28.1.0", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.0", + "jest-message-util": "^28.1.0", + "jest-util": "^28.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "diff-sequences": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.0.2.tgz", + "integrity": "sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ==", + "dev": true + }, + "jest-diff": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.0.tgz", + "integrity": "sha512-8eFd3U3OkIKRtlasXfiAQfbovgFgRDb0Ngcs2E+FMeBZ4rUezqIaGjuyggJBp+llosQXNEWofk/Sz4Hr5gMUhA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^28.0.2", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + } + }, + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + }, + "jest-matcher-utils": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.0.tgz", + "integrity": "sha512-onnax0n2uTLRQFKAjC7TuaxibrPSvZgKTcSCnNUz/tOjJ9UhxNm7ZmPpoQavmTDUjXvUQ8KesWk2/VdrxIFzTQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.0", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + } + }, + "pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + } + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.0.tgz", + "integrity": "sha512-TZR+tHxopPhzw3c3560IJXZWLNHgpcz1Zh0w5A65vynLGNcg/5pZ+VildAd7+XGOu6jd58XMY/HNn0IkZIXVXg==", + "dev": true, + "requires": { + "@jest/core": "^28.1.0", + "import-local": "^3.0.2", + "jest-cli": "^28.1.0" + } + }, + "jest-changed-files": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.0.2.tgz", + "integrity": "sha512-QX9u+5I2s54ZnGoMEjiM2WeBvJR2J7w/8ZUmH2um/WLAuGAYFQcsVXY9+1YL6k0H/AGUdH8pXUAv6erDqEsvIA==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "throat": "^6.0.1" + } + }, + "jest-circus": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.0.tgz", + "integrity": "sha512-rNYfqfLC0L0zQKRKsg4n4J+W1A2fbyGH7Ss/kDIocp9KXD9iaL111glsLu7+Z7FHuZxwzInMDXq+N1ZIBkI/TQ==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.0", + "@jest/expect": "^28.1.0", + "@jest/test-result": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^28.1.0", + "jest-matcher-utils": "^28.1.0", + "jest-message-util": "^28.1.0", + "jest-runtime": "^28.1.0", + "jest-snapshot": "^28.1.0", + "jest-util": "^28.1.0", + "pretty-format": "^28.1.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "diff-sequences": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.0.2.tgz", + "integrity": "sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ==", + "dev": true + }, + "jest-diff": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.0.tgz", + "integrity": "sha512-8eFd3U3OkIKRtlasXfiAQfbovgFgRDb0Ngcs2E+FMeBZ4rUezqIaGjuyggJBp+llosQXNEWofk/Sz4Hr5gMUhA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^28.0.2", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + } + }, + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + }, + "jest-matcher-utils": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.0.tgz", + "integrity": "sha512-onnax0n2uTLRQFKAjC7TuaxibrPSvZgKTcSCnNUz/tOjJ9UhxNm7ZmPpoQavmTDUjXvUQ8KesWk2/VdrxIFzTQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.0", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + } + }, + "pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + } + } + }, + "jest-cli": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.0.tgz", + "integrity": "sha512-fDJRt6WPRriHrBsvvgb93OxgajHHsJbk4jZxiPqmZbMDRcHskfJBBfTyjFko0jjfprP544hOktdSi9HVgl4VUQ==", + "dev": true, + "requires": { + "@jest/core": "^28.1.0", + "@jest/test-result": "^28.1.0", + "@jest/types": "^28.1.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^28.1.0", + "jest-util": "^28.1.0", + "jest-validate": "^28.1.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + } + }, + "jest-config": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.0.tgz", + "integrity": "sha512-aOV80E9LeWrmflp7hfZNn/zGA4QKv/xsn2w8QCBP0t0+YqObuCWTSgNbHJ0j9YsTuCO08ZR/wsvlxqqHX20iUA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^28.1.0", + "@jest/types": "^28.1.0", + "babel-jest": "^28.1.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^28.1.0", + "jest-environment-node": "^28.1.0", + "jest-get-type": "^28.0.2", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.0", + "jest-runner": "^28.1.0", + "jest-util": "^28.1.0", + "jest-validate": "^28.1.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^28.1.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + }, + "pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + } + } + }, + "jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + } + }, + "jest-docblock": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.0.2.tgz", + "integrity": "sha512-FH10WWw5NxLoeSdQlJwu+MTiv60aXV/t8KEwIRGEv74WARE1cXIqh1vGdy2CraHuWOOrnzTWj/azQKqW4fO7xg==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.0.tgz", + "integrity": "sha512-a/XX02xF5NTspceMpHujmOexvJ4GftpYXqr6HhhmKmExtMXsyIN/fvanQlt/BcgFoRKN4OCXxLQKth9/n6OPFg==", + "dev": true, + "requires": { + "@jest/types": "^28.1.0", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "jest-util": "^28.1.0", + "pretty-format": "^28.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + }, + "pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + } + } + }, + "jest-environment-node": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.0.tgz", + "integrity": "sha512-gBLZNiyrPw9CSMlTXF1yJhaBgWDPVvH0Pq6bOEwGMXaYNzhzhw2kA/OijNF8egbCgDS0/veRv97249x2CX+udQ==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.0", + "@jest/fake-timers": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/node": "*", + "jest-mock": "^28.1.0", + "jest-util": "^28.1.0" + } + }, + "jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "dev": true + }, + "jest-haste-map": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.0.tgz", + "integrity": "sha512-xyZ9sXV8PtKi6NCrJlmq53PyNVHzxmcfXNVvIRHpHmh1j/HChC4pwKgyjj7Z9us19JMw8PpQTJsFWOsIfT93Dw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.0", + "jest-worker": "^28.1.0", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + } + }, + "jest-leak-detector": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.0.tgz", + "integrity": "sha512-uIJDQbxwEL2AMMs2xjhZl2hw8s77c3wrPaQ9v6tXJLGaaQ+4QrNJH5vuw7hA7w/uGT/iJ42a83opAqxGHeyRIA==", + "dev": true, + "requires": { + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + }, + "pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + } + } + }, + "jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + } + }, + "jest-message-util": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.0.tgz", + "integrity": "sha512-RpA8mpaJ/B2HphDMiDlrAZdDytkmwFqgjDZovM21F35lHGeUeCvYmm6W+sbQ0ydaLpg5bFAUuWG1cjqOl8vqrw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + } + } + }, + "jest-mock": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.0.tgz", + "integrity": "sha512-H7BrhggNn77WhdL7O1apG0Q/iwl0Bdd5E1ydhCJzL3oBLh/UYxAwR3EJLsBZ9XA3ZU4PA3UNw4tQjduBTCTmLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.0", + "@types/node": "*" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "dev": true + }, + "jest-resolve": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.0.tgz", + "integrity": "sha512-vvfN7+tPNnnhDvISuzD1P+CRVP8cK0FHXRwPAcdDaQv4zgvwvag2n55/h5VjYcM5UJG7L4TwE5tZlzcI0X2Lhw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^28.1.0", + "jest-validate": "^28.1.0", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.0.tgz", + "integrity": "sha512-Ue1VYoSZquPwEvng7Uefw8RmZR+me/1kr30H2jMINjGeHgeO/JgrR6wxj2ofkJ7KSAA11W3cOrhNCbj5Dqqd9g==", + "dev": true, + "requires": { + "jest-regex-util": "^28.0.2", + "jest-snapshot": "^28.1.0" + } + }, + "jest-runner": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.0.tgz", + "integrity": "sha512-FBpmuh1HB2dsLklAlRdOxNTTHKFR6G1Qmd80pVDvwbZXTriqjWqjei5DKFC1UlM732KjYcE6yuCdiF0WUCOS2w==", + "dev": true, + "requires": { + "@jest/console": "^28.1.0", + "@jest/environment": "^28.1.0", + "@jest/test-result": "^28.1.0", + "@jest/transform": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^28.0.2", + "jest-environment-node": "^28.1.0", + "jest-haste-map": "^28.1.0", + "jest-leak-detector": "^28.1.0", + "jest-message-util": "^28.1.0", + "jest-resolve": "^28.1.0", + "jest-runtime": "^28.1.0", + "jest-util": "^28.1.0", + "jest-watcher": "^28.1.0", + "jest-worker": "^28.1.0", + "source-map-support": "0.5.13", + "throat": "^6.0.1" + } + }, + "jest-runtime": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.0.tgz", + "integrity": "sha512-wNYDiwhdH/TV3agaIyVF0lsJ33MhyujOe+lNTUiolqKt8pchy1Hq4+tDMGbtD5P/oNLA3zYrpx73T9dMTOCAcg==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.0", + "@jest/fake-timers": "^28.1.0", + "@jest/globals": "^28.1.0", + "@jest/source-map": "^28.0.2", + "@jest/test-result": "^28.1.0", + "@jest/transform": "^28.1.0", + "@jest/types": "^28.1.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.0", + "jest-message-util": "^28.1.0", + "jest-mock": "^28.1.0", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.0", + "jest-snapshot": "^28.1.0", + "jest-util": "^28.1.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + } + }, + "jest-snapshot": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.0.tgz", + "integrity": "sha512-ex49M2ZrZsUyQLpLGxQtDbahvgBjlLPgklkqGM0hq/F7W/f8DyqZxVHjdy19QKBm4O93eDp+H5S23EiTbbUmHw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^28.1.0", + "@jest/transform": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^28.1.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^28.1.0", + "jest-get-type": "^28.0.2", + "jest-haste-map": "^28.1.0", + "jest-matcher-utils": "^28.1.0", + "jest-message-util": "^28.1.0", + "jest-util": "^28.1.0", + "natural-compare": "^1.4.0", + "pretty-format": "^28.1.0", + "semver": "^7.3.5" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "diff-sequences": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.0.2.tgz", + "integrity": "sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ==", + "dev": true + }, + "jest-diff": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.0.tgz", + "integrity": "sha512-8eFd3U3OkIKRtlasXfiAQfbovgFgRDb0Ngcs2E+FMeBZ4rUezqIaGjuyggJBp+llosQXNEWofk/Sz4Hr5gMUhA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^28.0.2", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + } + }, + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + }, + "jest-matcher-utils": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.0.tgz", + "integrity": "sha512-onnax0n2uTLRQFKAjC7TuaxibrPSvZgKTcSCnNUz/tOjJ9UhxNm7ZmPpoQavmTDUjXvUQ8KesWk2/VdrxIFzTQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.0", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.0" + } + }, + "pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "jest-util": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.0.tgz", + "integrity": "sha512-qYdCKD77k4Hwkose2YBEqQk7PzUf/NSE+rutzceduFveQREeH6b+89Dc9+wjX9dAwHcgdx4yedGA3FQlU/qCTA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.0.tgz", + "integrity": "sha512-Lly7CJYih3vQBfjLeANGgBSBJ7pEa18cxpQfQEq2go2xyEzehnHfQTjoUia8xUv4x4J80XKFIDwJJThXtRFQXQ==", + "dev": true, + "requires": { + "@jest/types": "^28.1.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "leven": "^3.1.0", + "pretty-format": "^28.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + }, + "pretty-format": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", + "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.0.2", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.0.tgz", + "integrity": "sha512-tNHMtfLE8Njcr2IRS+5rXYA4BhU90gAOwI9frTGOqd+jX0P/Au/JfRSNqsf5nUTcWdbVYuLxS1KjnzILSoR5hA==", + "dev": true, + "requires": { + "@jest/test-result": "^28.1.0", + "@jest/types": "^28.1.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.0", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.0.tgz", + "integrity": "sha512-ZHwM6mNwaWBR52Snff8ZvsCTqQsvhCxP/bT1I6T6DAnb6ygkshsyLQIMxFwHpYxht0HOoqt23JlC01viI7T03A==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", + "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "prettier": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", + "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", + "dev": true + }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "regexpu-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", + "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", + "dev": true, + "requires": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + } + }, + "regjsgen": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", + "dev": true + }, + "regjsparser": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "throat": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", + "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", + "dev": true + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.0.tgz", + "integrity": "sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + } + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "typescript": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz", + "integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==", + "dev": true, + "peer": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "v8-to-istanbul": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.0.tgz", + "integrity": "sha512-HcvgY/xaRm7isYmyx+lFKA4uQmfUbN0J4M0nNItvzTvH/iQ9kW5j/t4YSR+Ge323/lrgDAWJoF46tzGQHwBHFw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.7", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.1.tgz", + "integrity": "sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 000000000..9a5a6234d --- /dev/null +++ b/scripts/package.json @@ -0,0 +1,22 @@ +{ + "name": "fields-scripts", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "npm run format-check && npm run lint && jest", + "format": "prettier --write .", + "format-check": "prettier --ignore-path .gitignore --check .", + "lint": "tsc && eslint . --ext .ts" + }, + "devDependencies": { + "@babel/preset-env": "^7.18.2", + "@babel/preset-typescript": "^7.17.12", + "@types/jest": "^27.5.1", + "@typescript-eslint/eslint-plugin": "^5.27.0", + "@typescript-eslint/parser": "^5.27.0", + "eslint": "^8.16.0", + "jest": "^28.1.0", + "prettier": "2.6.2", + "ts-node": "^10.5.0" + } +} diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 000000000..63ab1481c --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "es2021", + "lib": ["esnext"], + "types": ["jest", "node"], + "module": "commonjs", + "declaration": true, + "noImplicitAny": true, + "removeComments": true, + "experimentalDecorators": true, + "rootDir": ".", + "sourceMap": true, + "noEmitOnError": true, + "incremental": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noEmit": true, + "moduleResolution": "node", + "isolatedModules": true, + "resolveJsonModule": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true + }, + "include": ["**/*.ts", "**/*.tsx", "types.d.ts"], + "exclude": ["node_modules"] +} diff --git a/scripts/tsconfig.tsbuildinfo b/scripts/tsconfig.tsbuildinfo new file mode 100644 index 000000000..1de0eaeee --- /dev/null +++ b/scripts/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"program":{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.esnext.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.esnext.intl.d.ts","./app.ts","./app.test.ts","./node_modules/chalk/index.d.ts","./node_modules/jest-diff/build/cleanupsemantic.d.ts","./node_modules/pretty-format/build/types.d.ts","./node_modules/pretty-format/build/index.d.ts","./node_modules/jest-diff/build/types.d.ts","./node_modules/jest-diff/build/difflines.d.ts","./node_modules/jest-diff/build/printdiffs.d.ts","./node_modules/jest-diff/build/index.d.ts","./node_modules/jest-matcher-utils/build/index.d.ts","./node_modules/@types/jest/index.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/globals.global.d.ts","./node_modules/@types/node/index.d.ts"],"fileInfos":[{"version":"f5c28122bee592cfaf5c72ed7bcc47f453b79778ffa6e301f45d21a0970719d4","affectsGlobalScope":true},"dc47c4fa66b9b9890cf076304de2a9c5201e94b740cffdf09f87296d877d71f6","7a387c58583dfca701b6c85e0adaf43fb17d590fb16d5b2dc0a2fbd89f35c467","8a12173c586e95f4433e0c6dc446bc88346be73ffe9ca6eec7aa63c8f3dca7f9","5f4e733ced4e129482ae2186aae29fde948ab7182844c3a5a51dd346182c7b06","e6b724280c694a9f588847f754198fb96c43d805f065c3a5b28bbc9594541c84","1fc5ab7a764205c68fa10d381b08417795fc73111d6dd16b5b1ed36badb743d9","746d62152361558ea6d6115cf0da4dd10ede041d14882ede3568bce5dc4b4f1f","3eb679a56cab01203a1ba7edeade937f6a2a4c718513b2cd930b579807fa9359","aea179452def8a6152f98f63b191b84e7cbd69b0e248c91e61fb2e52328abe8c",{"version":"adb996790133eb33b33aadb9c09f15c2c575e71fb57a62de8bf74dbf59ec7dfb","affectsGlobalScope":true},{"version":"43fb1d932e4966a39a41b464a12a81899d9ae5f2c829063f5571b6b87e6d2f9c","affectsGlobalScope":true},{"version":"cdccba9a388c2ee3fd6ad4018c640a471a6c060e96f1232062223063b0a5ac6a","affectsGlobalScope":true},{"version":"c5c05907c02476e4bde6b7e76a79ffcd948aedd14b6a8f56e4674221b0417398","affectsGlobalScope":true},{"version":"0d5f52b3174bee6edb81260ebcd792692c32c81fd55499d69531496f3f2b25e7","affectsGlobalScope":true},{"version":"810627a82ac06fb5166da5ada4159c4ec11978dfbb0805fe804c86406dab8357","affectsGlobalScope":true},{"version":"181f1784c6c10b751631b24ce60c7f78b20665db4550b335be179217bacc0d5f","affectsGlobalScope":true},{"version":"3013574108c36fd3aaca79764002b3717da09725a36a6fc02eac386593110f93","affectsGlobalScope":true},{"version":"75ec0bdd727d887f1b79ed6619412ea72ba3c81d92d0787ccb64bab18d261f14","affectsGlobalScope":true},{"version":"3be5a1453daa63e031d266bf342f3943603873d890ab8b9ada95e22389389006","affectsGlobalScope":true},{"version":"17bb1fc99591b00515502d264fa55dc8370c45c5298f4a5c2083557dccba5a2a","affectsGlobalScope":true},{"version":"7ce9f0bde3307ca1f944119f6365f2d776d281a393b576a18a2f2893a2d75c98","affectsGlobalScope":true},{"version":"6a6b173e739a6a99629a8594bfb294cc7329bfb7b227f12e1f7c11bc163b8577","affectsGlobalScope":true},{"version":"12a310447c5d23c7d0d5ca2af606e3bd08afda69100166730ab92c62999ebb9d","affectsGlobalScope":true},{"version":"b0124885ef82641903d232172577f2ceb5d3e60aed4da1153bab4221e1f6dd4e","affectsGlobalScope":true},{"version":"0eb85d6c590b0d577919a79e0084fa1744c1beba6fd0d4e951432fa1ede5510a","affectsGlobalScope":true},{"version":"da233fc1c8a377ba9e0bed690a73c290d843c2c3d23a7bd7ec5cd3d7d73ba1e0","affectsGlobalScope":true},{"version":"d154ea5bb7f7f9001ed9153e876b2d5b8f5c2bb9ec02b3ae0d239ec769f1f2ae","affectsGlobalScope":true},{"version":"bb2d3fb05a1d2ffbca947cc7cbc95d23e1d053d6595391bd325deb265a18d36c","affectsGlobalScope":true},{"version":"c80df75850fea5caa2afe43b9949338ce4e2de086f91713e9af1a06f973872b8","affectsGlobalScope":true},{"version":"9d57b2b5d15838ed094aa9ff1299eecef40b190722eb619bac4616657a05f951","affectsGlobalScope":true},{"version":"6c51b5dd26a2c31dbf37f00cfc32b2aa6a92e19c995aefb5b97a3a64f1ac99de","affectsGlobalScope":true},{"version":"6e7997ef61de3132e4d4b2250e75343f487903ddf5370e7ce33cf1b9db9a63ed","affectsGlobalScope":true},{"version":"2ad234885a4240522efccd77de6c7d99eecf9b4de0914adb9a35c0c22433f993","affectsGlobalScope":true},{"version":"09aa50414b80c023553090e2f53827f007a301bc34b0495bfb2c3c08ab9ad1eb","affectsGlobalScope":true},{"version":"d7f680a43f8cd12a6b6122c07c54ba40952b0c8aa140dcfcf32eb9e6cb028596","affectsGlobalScope":true},{"version":"3787b83e297de7c315d55d4a7c546ae28e5f6c0a361b7a1dcec1f1f50a54ef11","affectsGlobalScope":true},{"version":"e7e8e1d368290e9295ef18ca23f405cf40d5456fa9f20db6373a61ca45f75f40","affectsGlobalScope":true},{"version":"faf0221ae0465363c842ce6aa8a0cbda5d9296940a8e26c86e04cc4081eea21e","affectsGlobalScope":true},{"version":"06393d13ea207a1bfe08ec8d7be562549c5e2da8983f2ee074e00002629d1871","affectsGlobalScope":true},{"version":"cd483c056da900716879771893a3c9772b66c3c88f8943b4205aec738a94b1d0","affectsGlobalScope":true},{"version":"b248e32ca52e8f5571390a4142558ae4f203ae2f94d5bac38a3084d529ef4e58","affectsGlobalScope":true},{"version":"6c55633c733c8378db65ac3da7a767c3cf2cf3057f0565a9124a16a3a2019e87","affectsGlobalScope":true},{"version":"fb4416144c1bf0323ccbc9afb0ab289c07312214e8820ad17d709498c865a3fe","affectsGlobalScope":true},{"version":"5b0ca94ec819d68d33da516306c15297acec88efeb0ae9e2b39f71dbd9685ef7","affectsGlobalScope":true},{"version":"ff667ee99e5a28c3dc5063a3cfd4d3436699e3fb035d4451037da7f567da542a","affectsGlobalScope":true},{"version":"34478567f8a80171f88f2f30808beb7da15eac0538ae91282dd33dce928d98ed","affectsGlobalScope":true},{"version":"6ea9ab679ea030cf46c16a711a316078e9e02619ebaf07a7fcd16964aba88f2d","affectsGlobalScope":true},{"version":"6bda95ea27a59a276e46043b7065b55bd4b316c25e70e29b572958fa77565d43","affectsGlobalScope":true},{"version":"aedb8de1abb2ff1095c153854a6df7deae4a5709c37297f9d6e9948b6806fa66","affectsGlobalScope":true},{"version":"11ffe3c281f375fff9ffdde8bbec7669b4dd671905509079f866f2354a788064","affectsGlobalScope":true},{"version":"c37f8a49593a0030eecb51bbfa270e709bec9d79a6cc3bb851ef348d4e6b26f8","affectsGlobalScope":true},{"version":"e16fac47d957aa10ec362521c463e0b7977f2e2ea46d56ee9c38811b88ee8319","signature":"f66ef4029acb9685e164e51d45eee1db234bdeb443fd13ea0f07d5475777dfb5"},{"version":"7f16e48f606bc17763f896f394b1416b01ea9ec8bbcae2c13e3e730832d73bc3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","d8aab31ba8e618cc3eea10b0945de81cb93b7e8150a013a482332263b9305322","462bccdf75fcafc1ae8c30400c9425e1a4681db5d605d1a0edb4f990a54d8094","5923d8facbac6ecf7c84739a5c701a57af94a6f6648d6229a6c768cf28f0f8cb","7adecb2c3238794c378d336a8182d4c3dd2c4fa6fa1785e2797a3db550edea62","dc12dc0e5aa06f4e1a7692149b78f89116af823b9e1f1e4eae140cd3e0e674e6","1bfc6565b90c8771615cd8cfcf9b36efc0275e5e83ac7d9181307e96eb495161","8a8a96898906f065f296665e411f51010b51372fa260d5373bf9f64356703190","7f82ef88bdb67d9a850dd1c7cd2d690f33e0f0acd208e3c9eba086f3670d4f73",{"version":"4564f780fd20582c57ae218a4cd017717181ab0e228639d905ef054288655b5e","affectsGlobalScope":true},"0cba3a5d7b81356222594442753cf90dd2892e5ccfe1d262aaca6896ba6c1380","a69c09dbea52352f479d3e7ac949fde3d17b195abe90b045d619f747b38d6d1a",{"version":"77f0b5c6a193a699c9f7d7fb0578e64e562d271afa740783665d2a827104a873","affectsGlobalScope":true},"e5979905796fe2740d85fbaf4f11f42b7ee1851421afe750823220813421b1af",{"version":"fcdcb42da18dd98dc286b1876dd425791772036012ae61263c011a76b13a190f","affectsGlobalScope":true},"1dab5ab6bcf11de47ab9db295df8c4f1d92ffa750e8f095e88c71ce4c3299628","f71f46ccd5a90566f0a37b25b23bc4684381ab2180bdf6733f4e6624474e1894",{"version":"54e65985a3ee3cec182e6a555e20974ea936fc8b8d1738c14e8ed8a42bd921d4","affectsGlobalScope":true},"82408ed3e959ddc60d3e9904481b5a8dc16469928257af22a3f7d1a3bc7fd8c4","5b30f550565fd0a7524282c81c27fe8534099e2cd26170ca80852308f07ae68d","34e5de87d983bc6aefef8b17658556e3157003e8d9555d3cb098c6bef0b5fbc8","d97cd8a4a42f557fc62271369ed0461c8e50d47b7f9c8ad0b5462f53306f6060","f27371653aded82b2b160f7a7033fb4a5b1534b6f6081ef7be1468f0f15327d3","c762cd6754b13a461c54b59d0ae0ab7aeef3c292c6cf889873f786ee4d8e75c9","f4ea7d5df644785bd9fbf419930cbaec118f0d8b4160037d2339b8e23c059e79",{"version":"bfea28e6162ed21a0aeed181b623dcf250aa79abf49e24a6b7e012655af36d81","affectsGlobalScope":true},"58df92fa3b18e84865bb0d2fe4b9d2d5bcb9952d4548c871f10ef02702b386f8","8821ee08124e5defd25fdc7840ae865cd9748fe57492f74f7d656e4b66cca91c","10d4796a130577d57003a77b95d8723530bbec84718e364aa2129fa8ffba0378","063f53ff674228c190efa19dd9448bcbd540acdbb48a928f4cf3a1b9f9478e43","bf73c576885408d4a176f44a9035d798827cc5020d58284cb18d7573430d9022","7ae078ca42a670445ae0c6a97c029cb83d143d62abd1730efb33f68f0b2c0e82",{"version":"e8b18c6385ff784228a6f369694fcf1a6b475355ba89090a88de13587a9391d5","affectsGlobalScope":true},"963fe86b2ebd07a34b92b52c6532ab45ec5ccda218a6c477de354fcad2aae0cb","12eea70b5e11e924bb0543aea5eadc16ced318aa26001b453b0d561c2fd0bd1e","08777cd9318d294646b121838574e1dd7acbb22c21a03df84e1f2c87b1ad47f2","08a90bcdc717df3d50a2ce178d966a8c353fd23e5c392fd3594a6e39d9bb6304",{"version":"9f8dd3922db205bc8a362a6b18078708fd699185b11648522159cbf3743561b5","affectsGlobalScope":true},"2a12d2da5ac4c4979401a3f6eaafa874747a37c365e4bc18aa2b171ae134d21b","002b837927b53f3714308ecd96f72ee8a053b8aeb28213d8ec6de23ed1608b66","1dc9c847473bb47279e398b22c740c83ea37a5c88bf66629666e3cf4c5b9f99c","a9e4a5a24bf2c44de4c98274975a1a705a0abbaad04df3557c2d3cd8b1727949","821dcb2b571bf698841d8ec25fde9d5f615ef3958957227962602f9dbfa8d800","1b952304137851e45bc009785de89ada562d9376177c97e37702e39e60c2f1ff",{"version":"806ef4cac3b3d9fa4a48d849c8e084d7c72fcd7b16d76e06049a9ed742ff79c0","affectsGlobalScope":true},"44b8b584a338b190a59f4f6929d072431950c7bd92ec2694821c11bce180c8a5","5f0ed51db151c2cdc4fa3bb0f44ce6066912ad001b607a34e65a96c52eb76248",{"version":"f234fa210fdce190f851211bccf105301f62736fe9d536aed1abc1639967fdec","affectsGlobalScope":true},"664d8f2d59164f2e08c543981453893bc7e003e4dfd29651ce09db13e9457980","103d70bfbeb3cd3a3f26d1705bf986322d8738c2c143f38ebb743b1e228d7444","f52fbf64c7e480271a9096763c4882d356b05cab05bf56a64e68a95313cd2ce2","59bdb65f28d7ce52ccfc906e9aaf422f8b8534b2d21c32a27d7819be5ad81df7",{"version":"3a2da34079a2567161c1359316a32e712404b56566c45332ac9dcee015ecce9f","affectsGlobalScope":true},"28a2e7383fd898c386ffdcacedf0ec0845e5d1a86b5a43f25b86bc315f556b79","3aff9c8c36192e46a84afe7b926136d520487155154ab9ba982a8b544ea8fc95","a880cf8d85af2e4189c709b0fea613741649c0e40fffb4360ec70762563d5de0","85bbf436a15bbeda4db888be3062d47f99c66fd05d7c50f0f6473a9151b6a070","9f9c49c95ecd25e0cb2587751925976cf64fd184714cb11e213749c80cf0f927","f0c75c08a71f9212c93a719a25fb0320d53f2e50ca89a812640e08f8ad8c408c",{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true},"aee3379fb20741a337a779530cc3e608aba5f34776511033d1d2db7ca45c4193"],"options":{"allowSyntheticDefaultImports":true,"declaration":true,"esModuleInterop":true,"experimentalDecorators":true,"module":1,"noEmitOnError":true,"noImplicitAny":true,"noImplicitReturns":true,"noImplicitThis":true,"noUnusedLocals":true,"noUnusedParameters":true,"removeComments":true,"rootDir":"./","skipLibCheck":true,"sourceMap":true,"strict":true,"strictNullChecks":true,"suppressImplicitAnyIndexErrors":true,"target":8},"fileIdsList":[[53,108],[108],[58,63,108],[65,108],[68,108],[69,74,108],[70,80,81,88,97,107,108],[70,71,80,88,108],[72,108],[73,74,81,89,108],[74,97,104,108],[75,77,80,88,108],[76,108],[77,78,108],[79,80,108],[80,108],[80,81,82,97,107,108],[80,81,82,97,108],[108,112],[83,88,97,107,108],[80,81,83,84,88,97,104,107,108],[83,85,97,104,107,108],[65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114],[80,86,108],[87,107,108],[77,80,88,97,108],[89,108],[90,108],[68,91,108],[92,106,108,112],[93,108],[94,108],[80,95,108],[95,96,108,110],[80,97,98,99,108],[97,99,108],[97,98,108],[100,108],[101,108],[80,102,103,108],[102,103,108],[74,88,97,104,108],[105,108],[88,106,108],[69,83,94,107,108],[74,108],[97,108,109],[108,110],[108,111],[69,74,80,82,91,97,107,108,110,112],[97,108,113],[56,59,108],[56,59,60,61,108],[58,108],[55,62,108],[57,108]],"referencedMap":[[54,1],[53,2],[64,3],[65,4],[66,4],[68,5],[69,6],[70,7],[71,8],[72,9],[73,10],[74,11],[75,12],[76,13],[77,14],[78,14],[79,15],[80,16],[81,17],[82,18],[67,19],[114,2],[83,20],[84,21],[85,22],[115,23],[86,24],[87,25],[88,26],[89,27],[90,28],[91,29],[92,30],[93,31],[94,32],[95,33],[96,34],[97,35],[99,36],[98,37],[100,38],[101,39],[102,40],[103,41],[104,42],[105,43],[106,44],[107,45],[108,46],[109,47],[110,48],[111,49],[112,50],[113,51],[55,2],[56,2],[60,52],[62,53],[61,52],[59,54],[63,55],[58,56],[57,2],[12,2],[11,2],[2,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[3,2],[4,2],[24,2],[21,2],[22,2],[23,2],[25,2],[26,2],[27,2],[5,2],[28,2],[29,2],[30,2],[31,2],[6,2],[32,2],[33,2],[34,2],[35,2],[7,2],[36,2],[41,2],[42,2],[37,2],[38,2],[39,2],[40,2],[8,2],[46,2],[43,2],[44,2],[45,2],[47,2],[9,2],[48,2],[49,2],[50,2],[51,2],[1,2],[10,2],[52,2]],"exportedModulesMap":[[64,3],[65,4],[66,4],[68,5],[69,6],[70,7],[71,8],[72,9],[73,10],[74,11],[75,12],[76,13],[77,14],[78,14],[79,15],[80,16],[81,17],[82,18],[67,19],[114,2],[83,20],[84,21],[85,22],[115,23],[86,24],[87,25],[88,26],[89,27],[90,28],[91,29],[92,30],[93,31],[94,32],[95,33],[96,34],[97,35],[99,36],[98,37],[100,38],[101,39],[102,40],[103,41],[104,42],[105,43],[106,44],[107,45],[108,46],[109,47],[110,48],[111,49],[112,50],[113,51],[55,2],[56,2],[60,52],[62,53],[61,52],[59,54],[63,55],[58,56],[57,2],[12,2],[11,2],[2,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[3,2],[4,2],[24,2],[21,2],[22,2],[23,2],[25,2],[26,2],[27,2],[5,2],[28,2],[29,2],[30,2],[31,2],[6,2],[32,2],[33,2],[34,2],[35,2],[7,2],[36,2],[41,2],[42,2],[37,2],[38,2],[39,2],[40,2],[8,2],[46,2],[43,2],[44,2],[45,2],[47,2],[9,2],[48,2],[49,2],[50,2],[51,2],[1,2],[10,2],[52,2]],"semanticDiagnosticsPerFile":[54,53,64,65,66,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,67,114,83,84,85,115,86,87,88,89,90,91,92,93,94,95,96,97,99,98,100,101,102,103,104,105,106,107,108,109,110,111,112,113,55,56,60,62,61,59,63,58,57,12,11,2,13,14,15,16,17,18,19,20,3,4,24,21,22,23,25,26,27,5,28,29,30,31,6,32,33,34,35,7,36,41,42,37,38,39,40,8,46,43,44,45,47,9,48,49,50,51,1,10,52],"affectedFilesPendingEmit":[[54,1],[53,1],[64,1],[65,1],[66,1],[68,1],[69,1],[70,1],[71,1],[72,1],[73,1],[74,1],[75,1],[76,1],[77,1],[78,1],[79,1],[80,1],[81,1],[82,1],[67,1],[114,1],[83,1],[84,1],[85,1],[115,1],[86,1],[87,1],[88,1],[89,1],[90,1],[91,1],[92,1],[93,1],[94,1],[95,1],[96,1],[97,1],[99,1],[98,1],[100,1],[101,1],[102,1],[103,1],[104,1],[105,1],[106,1],[107,1],[108,1],[109,1],[110,1],[111,1],[112,1],[113,1],[55,1],[56,1],[60,1],[62,1],[61,1],[59,1],[63,1],[58,1],[57,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1],[10,1]]},"version":"4.7.2"} \ No newline at end of file From 63257638d14e885f7e58cd90ee3cb0069efbde6d Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 1 Jun 2022 15:54:08 +0400 Subject: [PATCH 004/218] Adding scripts boilerplate --- scripts/{ => tests}/app.test.ts | 0 scripts/{ => tests}/app.ts | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename scripts/{ => tests}/app.test.ts (100%) rename scripts/{ => tests}/app.ts (100%) diff --git a/scripts/app.test.ts b/scripts/tests/app.test.ts similarity index 100% rename from scripts/app.test.ts rename to scripts/tests/app.test.ts diff --git a/scripts/app.ts b/scripts/tests/app.ts similarity index 100% rename from scripts/app.ts rename to scripts/tests/app.ts From c64670bebaf48e5959561bbfdd1c791ee1bfb424 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 2 Jun 2022 21:48:25 +0400 Subject: [PATCH 005/218] Adding osmosis contract deploy test [in progress] --- README.md | 9 + scripts/package-lock.json | 1217 ++++++++++++++++++++++++++++++- scripts/package.json | 7 +- scripts/tests/client.test.ts | 24 + scripts/tests/contract.test.ts | 34 + scripts/tsconfig.json | 3 +- scripts/tsconfig.tsbuildinfo | 1 - scripts/utils/config.ts | 30 + scripts/utils/osmosis-client.ts | 32 + scripts/utils/test-wallets.ts | 92 +++ 10 files changed, 1413 insertions(+), 36 deletions(-) create mode 100644 scripts/tests/client.test.ts create mode 100644 scripts/tests/contract.test.ts delete mode 100644 scripts/tsconfig.tsbuildinfo create mode 100644 scripts/utils/config.ts create mode 100644 scripts/utils/osmosis-client.ts create mode 100644 scripts/utils/test-wallets.ts diff --git a/README.md b/README.md index efd8b4974..186b4b174 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,15 @@ now creating blocks osmosjs? + +cargo wasm? + +docker run --rm -v "$(pwd)":/code \ +--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ +--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ +cosmwasm/workspace-optimizer-arm64:0.12.6 + + SCRIPTS npm install diff --git a/scripts/package-lock.json b/scripts/package-lock.json index ecd95c753..cc5d03087 100644 --- a/scripts/package-lock.json +++ b/scripts/package-lock.json @@ -7,6 +7,11 @@ "": { "name": "fields-scripts", "version": "1.0.0", + "dependencies": { + "@cosmjs/stargate": "^0.28.4", + "cosmjs-types": "^0.5.0", + "osmojs": "^0.4.46" + }, "devDependencies": { "@babel/preset-env": "^7.18.2", "@babel/preset-typescript": "^7.17.12", @@ -1692,7 +1697,6 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz", "integrity": "sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -1754,6 +1758,280 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@confio/ics23": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@confio/ics23/-/ics23-0.6.8.tgz", + "integrity": "sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w==", + "dependencies": { + "@noble/hashes": "^1.0.0", + "protobufjs": "^6.8.8" + } + }, + "node_modules/@cosmjs/amino": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.4.tgz", + "integrity": "sha512-b8y5gFC0eGrH0IoYSNtDmTdsTgeQ1KFZ5YVOeIiKmzF91MeiciYO/MNqc027kctacZ+UbnVWGEUGyRBPi9ta/g==", + "dependencies": { + "@cosmjs/crypto": "0.28.4", + "@cosmjs/encoding": "0.28.4", + "@cosmjs/math": "0.28.4", + "@cosmjs/utils": "0.28.4" + } + }, + "node_modules/@cosmjs/crypto": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.4.tgz", + "integrity": "sha512-JRxNLlED3DDh9d04A0RcRw3mYkoobN7q7wafUFy3vI1TjoyWx33v0gqqaYE6/hoo9ghUrJSVOfzVihl8fZajJA==", + "dependencies": { + "@cosmjs/encoding": "0.28.4", + "@cosmjs/math": "0.28.4", + "@cosmjs/utils": "0.28.4", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "elliptic": "^6.5.3", + "libsodium-wrappers": "^0.7.6" + } + }, + "node_modules/@cosmjs/encoding": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.4.tgz", + "integrity": "sha512-N6Qnjs4dd8KwjW5m9t3L+rWYYGW2wyS+iLtJJ9DD8DiTTxpW9h7/AmUVO/dsRe5H2tV8/DzH/B9pFfpsgro22A==", + "dependencies": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "node_modules/@cosmjs/json-rpc": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.4.tgz", + "integrity": "sha512-An8ZQi9OKbnS8ew/MyHhF90zQpXBF8RTj2wdvIH+Hr8yA6QjynY8hxRpUwYUt3Skc5NeUnTZNuWCzlluHnoxVg==", + "dependencies": { + "@cosmjs/stream": "0.28.4", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/math": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.4.tgz", + "integrity": "sha512-wsWjbxFXvk46Dsx8jQ5vsBZOIQuiUIyaaZbUvxsgIhAMpuuBnV5O/drK87+B+4cL+umTelFqTbWnkqueVCIFxQ==", + "dependencies": { + "bn.js": "^5.2.0" + } + }, + "node_modules/@cosmjs/proto-signing": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.4.tgz", + "integrity": "sha512-4vgCLK9gOsdWzD78V5XbAsupSSyntPEzokWYhgRQNwgVTcKX1kg0eKZqUvF5ua5iL9x6MevfH/sgwPyiYleMBw==", + "dependencies": { + "@cosmjs/amino": "0.28.4", + "@cosmjs/crypto": "0.28.4", + "@cosmjs/encoding": "0.28.4", + "@cosmjs/math": "0.28.4", + "@cosmjs/utils": "0.28.4", + "cosmjs-types": "^0.4.0", + "long": "^4.0.0", + "protobufjs": "~6.10.2" + } + }, + "node_modules/@cosmjs/proto-signing/node_modules/@types/node": { + "version": "13.13.52", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", + "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" + }, + "node_modules/@cosmjs/proto-signing/node_modules/cosmjs-types": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", + "integrity": "sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog==", + "dependencies": { + "long": "^4.0.0", + "protobufjs": "~6.11.2" + } + }, + "node_modules/@cosmjs/proto-signing/node_modules/cosmjs-types/node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/@cosmjs/proto-signing/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/@cosmjs/proto-signing/node_modules/protobufjs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz", + "integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/@cosmjs/socket": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.4.tgz", + "integrity": "sha512-jAEL3Ri+s8XuBM3mqgO4yvmeQu+R+704V37lGROC1B6kAbGxWRyOWrMdOOiFJzCZ35sSMB7L+xKjpE8ug0vJjg==", + "dependencies": { + "@cosmjs/stream": "0.28.4", + "isomorphic-ws": "^4.0.1", + "ws": "^7", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/stargate": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.4.tgz", + "integrity": "sha512-tdwudilP5iLNwDm4TOMBjWuL5YehLPqGlC5/7hjJM/kVHyzLFo4Lzt0dVEwr5YegH+RsRXH/VtFLQz+NYlCobw==", + "dependencies": { + "@confio/ics23": "^0.6.8", + "@cosmjs/amino": "0.28.4", + "@cosmjs/encoding": "0.28.4", + "@cosmjs/math": "0.28.4", + "@cosmjs/proto-signing": "0.28.4", + "@cosmjs/stream": "0.28.4", + "@cosmjs/tendermint-rpc": "0.28.4", + "@cosmjs/utils": "0.28.4", + "cosmjs-types": "^0.4.0", + "long": "^4.0.0", + "protobufjs": "~6.10.2", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/stargate/node_modules/@types/node": { + "version": "13.13.52", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", + "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" + }, + "node_modules/@cosmjs/stargate/node_modules/cosmjs-types": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", + "integrity": "sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog==", + "dependencies": { + "long": "^4.0.0", + "protobufjs": "~6.11.2" + } + }, + "node_modules/@cosmjs/stargate/node_modules/cosmjs-types/node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/@cosmjs/stargate/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/@cosmjs/stargate/node_modules/protobufjs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz", + "integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/@cosmjs/stream": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.4.tgz", + "integrity": "sha512-BDwDdFOrOgRx/Wm5nknb9YCV9HHIUcsOxykTDZqdArCUsn4QJBq79QIjp919G05Z8UemkoHwiUCUNB2BfoKmFw==", + "dependencies": { + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/tendermint-rpc": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.4.tgz", + "integrity": "sha512-iz6p4UW2QUZNh55WeJy9wHbMdqM8COo0AJdrGU4Ikb/xU0/H6b0dFPoEK+i6ngR0cSizh+hpTMzh3AA7ySUKlA==", + "dependencies": { + "@cosmjs/crypto": "0.28.4", + "@cosmjs/encoding": "0.28.4", + "@cosmjs/json-rpc": "0.28.4", + "@cosmjs/math": "0.28.4", + "@cosmjs/socket": "0.28.4", + "@cosmjs/stream": "0.28.4", + "@cosmjs/utils": "0.28.4", + "axios": "^0.21.2", + "readonly-date": "^1.0.0", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/utils": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.4.tgz", + "integrity": "sha512-lb3TU6833arPoPZF8HTeG9V418CpurvqH5Aa/ls0I0wYdPDEMO6622+PQNQhQ8Vw8Az2MXoSyc8jsqrgawT84Q==" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -2253,6 +2531,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@noble/hashes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.0.0.tgz", + "integrity": "sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2288,6 +2571,87 @@ "node": ">= 8" } }, + "node_modules/@osmonauts/helpers": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@osmonauts/helpers/-/helpers-0.3.4.tgz", + "integrity": "sha512-c20B25IhqKBoEDSE+VysG/R19kNFq+RgD2p838GLo9bOOH3tT7UOuUYBmqat9qGHFaNpFkaYZ+3yqtMTh9zxzg==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "long": "^5.2.0", + "protobufjs": "^6.11.2" + } + }, + "node_modules/@osmonauts/lcd": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@osmonauts/lcd/-/lcd-0.3.4.tgz", + "integrity": "sha512-VlPN+ip2PgFuF3lVcCwoFSZ5ZTd8FdNl+LKv0dmr14OK6wkuAOzuAK6XqPOe2QbZ6Wii6arEqj/e/bLKAQcvWw==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "axios": "0.26.1" + } + }, + "node_modules/@osmonauts/lcd/node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@sinclair/typebox": { "version": "0.23.5", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz", @@ -2426,11 +2790,15 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "node_modules/@types/node": { "version": "17.0.38", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.38.tgz", - "integrity": "sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==", - "dev": true + "integrity": "sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==" }, "node_modules/@types/prettier": { "version": "2.6.3", @@ -2786,6 +3154,14 @@ "node": ">=8" } }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, "node_modules/babel-jest": { "version": "28.1.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.0.tgz", @@ -2931,6 +3307,35 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2953,6 +3358,11 @@ "node": ">=8" } }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, "node_modules/browserslist": { "version": "4.20.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", @@ -3164,6 +3574,20 @@ "semver": "bin/semver.js" } }, + "node_modules/cosmjs-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.5.0.tgz", + "integrity": "sha512-Qy2yxDp5HasBUnBV5OL9EOuTJw94LAbCWfiRb5QcW1sqh93KSSY5PpNMB3rTsxwuPcf/k3CNRY02DeQMjrsJuA==", + "dependencies": { + "long": "^4.0.0", + "protobufjs": "~6.11.2" + } + }, + "node_modules/cosmjs-types/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -3226,7 +3650,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, "dependencies": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -3295,6 +3718,25 @@ "integrity": "sha512-2hIgvu0+pDfXIqmVmV5X6iwMjQ2KxDsWKwM+oI1fABEOy/Dqmll0QJRmIQ3rm+XaoUa/qKrmy5h7LSTFQ6Ldzg==", "dev": true }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "node_modules/emittery": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", @@ -3889,6 +4331,25 @@ "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3912,8 +4373,7 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/functional-red-black-tree": { "version": "1.0.1", @@ -3943,7 +4403,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -4015,6 +4474,20 @@ "node": ">=4" } }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -4045,7 +4518,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -4066,7 +4538,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.1" }, @@ -4078,7 +4549,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -4086,6 +4556,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -4176,8 +4665,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/is-arrayish": { "version": "0.2.1", @@ -4263,6 +4751,14 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -5340,6 +5836,19 @@ "node": ">=6" } }, + "node_modules/libsodium": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz", + "integrity": "sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ==" + }, + "node_modules/libsodium-wrappers": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz", + "integrity": "sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg==", + "dependencies": { + "libsodium": "^0.7.0" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -5370,6 +5879,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/long": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", + "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -5449,6 +5963,16 @@ "node": ">=6" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5510,7 +6034,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -5557,6 +6080,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/osmojs": { + "version": "0.4.46", + "resolved": "https://registry.npmjs.org/osmojs/-/osmojs-0.4.46.tgz", + "integrity": "sha512-d965zJncEtqV1dOVDBS5+rmDlYvVWJdzkeRgN3+WBkThGOV0IxBQ/W6cSP4IZvmTr2rXlI1erKG6FYIjxMlZfA==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@cosmjs/amino": "0.28.4", + "@cosmjs/proto-signing": "0.28.4", + "@cosmjs/stargate": "0.28.4", + "@cosmjs/tendermint-rpc": "^0.28.4", + "@osmonauts/helpers": "^0.3.4", + "@osmonauts/lcd": "^0.3.4", + "long": "^5.2.0", + "protobufjs": "^6.11.2" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -5758,6 +6297,36 @@ "node": ">= 6" } }, + "node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/protobufjs/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -5793,6 +6362,11 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "node_modules/readonly-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz", + "integrity": "sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==" + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -5814,8 +6388,7 @@ "node_modules/regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "node_modules/regenerator-transform": { "version": "0.15.0", @@ -6186,6 +6759,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-observable": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz", + "integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -6507,6 +7088,35 @@ "node": "^12.13.0 || ^14.15.0 || >=16" } }, + "node_modules/ws": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz", + "integrity": "sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xstream": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/xstream/-/xstream-11.14.0.tgz", + "integrity": "sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw==", + "dependencies": { + "globalthis": "^1.0.1", + "symbol-observable": "^2.0.3" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -7726,7 +8336,6 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz", "integrity": "sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } @@ -7776,6 +8385,268 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@confio/ics23": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@confio/ics23/-/ics23-0.6.8.tgz", + "integrity": "sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w==", + "requires": { + "@noble/hashes": "^1.0.0", + "protobufjs": "^6.8.8" + } + }, + "@cosmjs/amino": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.4.tgz", + "integrity": "sha512-b8y5gFC0eGrH0IoYSNtDmTdsTgeQ1KFZ5YVOeIiKmzF91MeiciYO/MNqc027kctacZ+UbnVWGEUGyRBPi9ta/g==", + "requires": { + "@cosmjs/crypto": "0.28.4", + "@cosmjs/encoding": "0.28.4", + "@cosmjs/math": "0.28.4", + "@cosmjs/utils": "0.28.4" + } + }, + "@cosmjs/crypto": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.4.tgz", + "integrity": "sha512-JRxNLlED3DDh9d04A0RcRw3mYkoobN7q7wafUFy3vI1TjoyWx33v0gqqaYE6/hoo9ghUrJSVOfzVihl8fZajJA==", + "requires": { + "@cosmjs/encoding": "0.28.4", + "@cosmjs/math": "0.28.4", + "@cosmjs/utils": "0.28.4", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "elliptic": "^6.5.3", + "libsodium-wrappers": "^0.7.6" + } + }, + "@cosmjs/encoding": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.4.tgz", + "integrity": "sha512-N6Qnjs4dd8KwjW5m9t3L+rWYYGW2wyS+iLtJJ9DD8DiTTxpW9h7/AmUVO/dsRe5H2tV8/DzH/B9pFfpsgro22A==", + "requires": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "@cosmjs/json-rpc": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.4.tgz", + "integrity": "sha512-An8ZQi9OKbnS8ew/MyHhF90zQpXBF8RTj2wdvIH+Hr8yA6QjynY8hxRpUwYUt3Skc5NeUnTZNuWCzlluHnoxVg==", + "requires": { + "@cosmjs/stream": "0.28.4", + "xstream": "^11.14.0" + } + }, + "@cosmjs/math": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.4.tgz", + "integrity": "sha512-wsWjbxFXvk46Dsx8jQ5vsBZOIQuiUIyaaZbUvxsgIhAMpuuBnV5O/drK87+B+4cL+umTelFqTbWnkqueVCIFxQ==", + "requires": { + "bn.js": "^5.2.0" + } + }, + "@cosmjs/proto-signing": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.4.tgz", + "integrity": "sha512-4vgCLK9gOsdWzD78V5XbAsupSSyntPEzokWYhgRQNwgVTcKX1kg0eKZqUvF5ua5iL9x6MevfH/sgwPyiYleMBw==", + "requires": { + "@cosmjs/amino": "0.28.4", + "@cosmjs/crypto": "0.28.4", + "@cosmjs/encoding": "0.28.4", + "@cosmjs/math": "0.28.4", + "@cosmjs/utils": "0.28.4", + "cosmjs-types": "^0.4.0", + "long": "^4.0.0", + "protobufjs": "~6.10.2" + }, + "dependencies": { + "@types/node": { + "version": "13.13.52", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", + "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" + }, + "cosmjs-types": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", + "integrity": "sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog==", + "requires": { + "long": "^4.0.0", + "protobufjs": "~6.11.2" + }, + "dependencies": { + "protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + } + } + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "protobufjs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz", + "integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + } + } + } + }, + "@cosmjs/socket": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.4.tgz", + "integrity": "sha512-jAEL3Ri+s8XuBM3mqgO4yvmeQu+R+704V37lGROC1B6kAbGxWRyOWrMdOOiFJzCZ35sSMB7L+xKjpE8ug0vJjg==", + "requires": { + "@cosmjs/stream": "0.28.4", + "isomorphic-ws": "^4.0.1", + "ws": "^7", + "xstream": "^11.14.0" + } + }, + "@cosmjs/stargate": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.4.tgz", + "integrity": "sha512-tdwudilP5iLNwDm4TOMBjWuL5YehLPqGlC5/7hjJM/kVHyzLFo4Lzt0dVEwr5YegH+RsRXH/VtFLQz+NYlCobw==", + "requires": { + "@confio/ics23": "^0.6.8", + "@cosmjs/amino": "0.28.4", + "@cosmjs/encoding": "0.28.4", + "@cosmjs/math": "0.28.4", + "@cosmjs/proto-signing": "0.28.4", + "@cosmjs/stream": "0.28.4", + "@cosmjs/tendermint-rpc": "0.28.4", + "@cosmjs/utils": "0.28.4", + "cosmjs-types": "^0.4.0", + "long": "^4.0.0", + "protobufjs": "~6.10.2", + "xstream": "^11.14.0" + }, + "dependencies": { + "@types/node": { + "version": "13.13.52", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", + "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" + }, + "cosmjs-types": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", + "integrity": "sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog==", + "requires": { + "long": "^4.0.0", + "protobufjs": "~6.11.2" + }, + "dependencies": { + "protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + } + } + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "protobufjs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz", + "integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + } + } + } + }, + "@cosmjs/stream": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.4.tgz", + "integrity": "sha512-BDwDdFOrOgRx/Wm5nknb9YCV9HHIUcsOxykTDZqdArCUsn4QJBq79QIjp919G05Z8UemkoHwiUCUNB2BfoKmFw==", + "requires": { + "xstream": "^11.14.0" + } + }, + "@cosmjs/tendermint-rpc": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.4.tgz", + "integrity": "sha512-iz6p4UW2QUZNh55WeJy9wHbMdqM8COo0AJdrGU4Ikb/xU0/H6b0dFPoEK+i6ngR0cSizh+hpTMzh3AA7ySUKlA==", + "requires": { + "@cosmjs/crypto": "0.28.4", + "@cosmjs/encoding": "0.28.4", + "@cosmjs/json-rpc": "0.28.4", + "@cosmjs/math": "0.28.4", + "@cosmjs/socket": "0.28.4", + "@cosmjs/stream": "0.28.4", + "@cosmjs/utils": "0.28.4", + "axios": "^0.21.2", + "readonly-date": "^1.0.0", + "xstream": "^11.14.0" + } + }, + "@cosmjs/utils": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.4.tgz", + "integrity": "sha512-lb3TU6833arPoPZF8HTeG9V418CpurvqH5Aa/ls0I0wYdPDEMO6622+PQNQhQ8Vw8Az2MXoSyc8jsqrgawT84Q==" + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -8174,6 +9045,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@noble/hashes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.0.0.tgz", + "integrity": "sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg==" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -8200,6 +9076,89 @@ "fastq": "^1.6.0" } }, + "@osmonauts/helpers": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@osmonauts/helpers/-/helpers-0.3.4.tgz", + "integrity": "sha512-c20B25IhqKBoEDSE+VysG/R19kNFq+RgD2p838GLo9bOOH3tT7UOuUYBmqat9qGHFaNpFkaYZ+3yqtMTh9zxzg==", + "requires": { + "@babel/runtime": "^7.11.2", + "long": "^5.2.0", + "protobufjs": "^6.11.2" + } + }, + "@osmonauts/lcd": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@osmonauts/lcd/-/lcd-0.3.4.tgz", + "integrity": "sha512-VlPN+ip2PgFuF3lVcCwoFSZ5ZTd8FdNl+LKv0dmr14OK6wkuAOzuAK6XqPOe2QbZ6Wii6arEqj/e/bLKAQcvWw==", + "requires": { + "@babel/runtime": "^7.11.2", + "axios": "0.26.1" + }, + "dependencies": { + "axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "requires": { + "follow-redirects": "^1.14.8" + } + } + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "@sinclair/typebox": { "version": "0.23.5", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz", @@ -8338,11 +9297,15 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "@types/node": { "version": "17.0.38", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.38.tgz", - "integrity": "sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==", - "dev": true + "integrity": "sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==" }, "@types/prettier": { "version": "2.6.3", @@ -8568,6 +9531,14 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "requires": { + "follow-redirects": "^1.14.0" + } + }, "babel-jest": { "version": "28.1.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.0.tgz", @@ -8683,6 +9654,21 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + }, + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -8702,6 +9688,11 @@ "fill-range": "^7.0.1" } }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, "browserslist": { "version": "4.20.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", @@ -8857,6 +9848,22 @@ } } }, + "cosmjs-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.5.0.tgz", + "integrity": "sha512-Qy2yxDp5HasBUnBV5OL9EOuTJw94LAbCWfiRb5QcW1sqh93KSSY5PpNMB3rTsxwuPcf/k3CNRY02DeQMjrsJuA==", + "requires": { + "long": "^4.0.0", + "protobufjs": "~6.11.2" + }, + "dependencies": { + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + } + } + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -8905,7 +9912,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, "requires": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -8953,6 +9959,27 @@ "integrity": "sha512-2hIgvu0+pDfXIqmVmV5X6iwMjQ2KxDsWKwM+oI1fABEOy/Dqmll0QJRmIQ3rm+XaoUa/qKrmy5h7LSTFQ6Ldzg==", "dev": true }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + } + } + }, "emittery": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", @@ -9406,6 +10433,11 @@ "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, + "follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -9422,8 +10454,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -9447,7 +10478,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -9495,6 +10525,14 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "requires": { + "define-properties": "^1.1.3" + } + }, "globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -9519,7 +10557,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -9534,7 +10571,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, "requires": { "get-intrinsic": "^1.1.1" } @@ -9542,8 +10578,26 @@ "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } }, "html-escaper": { "version": "2.0.2", @@ -9610,8 +10664,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-arrayish": { "version": "0.2.1", @@ -9673,6 +10726,12 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "requires": {} + }, "istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -10492,6 +11551,19 @@ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true }, + "libsodium": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz", + "integrity": "sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ==" + }, + "libsodium-wrappers": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz", + "integrity": "sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg==", + "requires": { + "libsodium": "^0.7.0" + } + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -10519,6 +11591,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "long": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", + "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -10580,6 +11657,16 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -10631,8 +11718,7 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object.assign": { "version": "4.1.2", @@ -10664,6 +11750,22 @@ "mimic-fn": "^2.1.0" } }, + "osmojs": { + "version": "0.4.46", + "resolved": "https://registry.npmjs.org/osmojs/-/osmojs-0.4.46.tgz", + "integrity": "sha512-d965zJncEtqV1dOVDBS5+rmDlYvVWJdzkeRgN3+WBkThGOV0IxBQ/W6cSP4IZvmTr2rXlI1erKG6FYIjxMlZfA==", + "requires": { + "@babel/runtime": "^7.11.2", + "@cosmjs/amino": "0.28.4", + "@cosmjs/proto-signing": "0.28.4", + "@cosmjs/stargate": "0.28.4", + "@cosmjs/tendermint-rpc": "^0.28.4", + "@osmonauts/helpers": "^0.3.4", + "@osmonauts/lcd": "^0.3.4", + "long": "^5.2.0", + "protobufjs": "^6.11.2" + } + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -10801,6 +11903,33 @@ "sisteransi": "^1.0.5" } }, + "protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "dependencies": { + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + } + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -10819,6 +11948,11 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "readonly-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz", + "integrity": "sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==" + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -10837,8 +11971,7 @@ "regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "regenerator-transform": { "version": "0.15.0", @@ -11103,6 +12236,11 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "symbol-observable": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz", + "integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==" + }, "terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -11331,6 +12469,21 @@ "signal-exit": "^3.0.7" } }, + "ws": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz", + "integrity": "sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==", + "requires": {} + }, + "xstream": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/xstream/-/xstream-11.14.0.tgz", + "integrity": "sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw==", + "requires": { + "globalthis": "^1.0.1", + "symbol-observable": "^2.0.3" + } + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/scripts/package.json b/scripts/package.json index 9a5a6234d..c23a8309c 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -3,11 +3,16 @@ "version": "1.0.0", "main": "index.js", "scripts": { - "test": "npm run format-check && npm run lint && jest", + "test": "jest --testTimeout=20000 && npm run lint && npm run format-check", "format": "prettier --write .", "format-check": "prettier --ignore-path .gitignore --check .", "lint": "tsc && eslint . --ext .ts" }, + "dependencies": { + "@cosmjs/stargate": "^0.28.4", + "cosmjs-types": "^0.5.0", + "osmojs": "^0.4.46" + }, "devDependencies": { "@babel/preset-env": "^7.18.2", "@babel/preset-typescript": "^7.17.12", diff --git a/scripts/tests/client.test.ts b/scripts/tests/client.test.ts new file mode 100644 index 000000000..644d9b7f7 --- /dev/null +++ b/scripts/tests/client.test.ts @@ -0,0 +1,24 @@ +import { testWallet1, testWallet2 } from '../utils/test-wallets'; +import { getOsmosisClient } from '../utils/osmosis-client'; +import { assertIsDeliverTxSuccess } from '@cosmjs/stargate'; +import { Network, networks } from '../utils/config'; + +describe('example client test', () => { + test('can get client and transfer tokens', async () => { + const client = await getOsmosisClient(testWallet1); + + const result = await client.sendTokens( + testWallet1.address, + testWallet2.address, + [ + { + denom: 'uosmo', + amount: '12345', + }, + ], + networks[Network.OSMOSIS].defaultSendFee, + ); + + assertIsDeliverTxSuccess(result); + }); +}); diff --git a/scripts/tests/contract.test.ts b/scripts/tests/contract.test.ts new file mode 100644 index 000000000..db1a39215 --- /dev/null +++ b/scripts/tests/contract.test.ts @@ -0,0 +1,34 @@ +import { testWallet1 } from '../utils/test-wallets'; +import { getOsmosisClient } from '../utils/osmosis-client'; +import fs from 'fs'; +import path from 'path'; +import { EncodeObject } from '@cosmjs/proto-signing'; +import { AccessType } from 'cosmjs-types/cosmwasm/wasm/v1/types'; +import { Network, networks } from '../utils/config'; + +describe('example contract', () => { + test('can deploy contract', async () => { + const client = await getOsmosisClient(testWallet1); + + const contractCode = fs.readFileSync(path.resolve(__dirname, '../../artifacts/example-aarch64.wasm')); + const storeCode: EncodeObject = { + typeUrl: '/cosmwasm.wasm.v1.MsgStoreCode', + value: { + instantiate_permission: { + address: testWallet1.address, + permission: AccessType.ACCESS_TYPE_UNSPECIFIED, + }, + sender: testWallet1.address, + wasm_byte_code: contractCode, + }, + }; + + const result = await client.signAndBroadcast( + testWallet1.address, + [storeCode], + networks[Network.OSMOSIS].defaultSendFee, + ); + + console.log(result); + }); +}); diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index 63ab1481c..ae63e9592 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -3,7 +3,7 @@ "target": "es2021", "lib": ["esnext"], "types": ["jest", "node"], - "module": "commonjs", + "module": "esnext", "declaration": true, "noImplicitAny": true, "removeComments": true, @@ -11,7 +11,6 @@ "rootDir": ".", "sourceMap": true, "noEmitOnError": true, - "incremental": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, diff --git a/scripts/tsconfig.tsbuildinfo b/scripts/tsconfig.tsbuildinfo deleted file mode 100644 index 1de0eaeee..000000000 --- a/scripts/tsconfig.tsbuildinfo +++ /dev/null @@ -1 +0,0 @@ -{"program":{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.esnext.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.esnext.intl.d.ts","./app.ts","./app.test.ts","./node_modules/chalk/index.d.ts","./node_modules/jest-diff/build/cleanupsemantic.d.ts","./node_modules/pretty-format/build/types.d.ts","./node_modules/pretty-format/build/index.d.ts","./node_modules/jest-diff/build/types.d.ts","./node_modules/jest-diff/build/difflines.d.ts","./node_modules/jest-diff/build/printdiffs.d.ts","./node_modules/jest-diff/build/index.d.ts","./node_modules/jest-matcher-utils/build/index.d.ts","./node_modules/@types/jest/index.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/globals.global.d.ts","./node_modules/@types/node/index.d.ts"],"fileInfos":[{"version":"f5c28122bee592cfaf5c72ed7bcc47f453b79778ffa6e301f45d21a0970719d4","affectsGlobalScope":true},"dc47c4fa66b9b9890cf076304de2a9c5201e94b740cffdf09f87296d877d71f6","7a387c58583dfca701b6c85e0adaf43fb17d590fb16d5b2dc0a2fbd89f35c467","8a12173c586e95f4433e0c6dc446bc88346be73ffe9ca6eec7aa63c8f3dca7f9","5f4e733ced4e129482ae2186aae29fde948ab7182844c3a5a51dd346182c7b06","e6b724280c694a9f588847f754198fb96c43d805f065c3a5b28bbc9594541c84","1fc5ab7a764205c68fa10d381b08417795fc73111d6dd16b5b1ed36badb743d9","746d62152361558ea6d6115cf0da4dd10ede041d14882ede3568bce5dc4b4f1f","3eb679a56cab01203a1ba7edeade937f6a2a4c718513b2cd930b579807fa9359","aea179452def8a6152f98f63b191b84e7cbd69b0e248c91e61fb2e52328abe8c",{"version":"adb996790133eb33b33aadb9c09f15c2c575e71fb57a62de8bf74dbf59ec7dfb","affectsGlobalScope":true},{"version":"43fb1d932e4966a39a41b464a12a81899d9ae5f2c829063f5571b6b87e6d2f9c","affectsGlobalScope":true},{"version":"cdccba9a388c2ee3fd6ad4018c640a471a6c060e96f1232062223063b0a5ac6a","affectsGlobalScope":true},{"version":"c5c05907c02476e4bde6b7e76a79ffcd948aedd14b6a8f56e4674221b0417398","affectsGlobalScope":true},{"version":"0d5f52b3174bee6edb81260ebcd792692c32c81fd55499d69531496f3f2b25e7","affectsGlobalScope":true},{"version":"810627a82ac06fb5166da5ada4159c4ec11978dfbb0805fe804c86406dab8357","affectsGlobalScope":true},{"version":"181f1784c6c10b751631b24ce60c7f78b20665db4550b335be179217bacc0d5f","affectsGlobalScope":true},{"version":"3013574108c36fd3aaca79764002b3717da09725a36a6fc02eac386593110f93","affectsGlobalScope":true},{"version":"75ec0bdd727d887f1b79ed6619412ea72ba3c81d92d0787ccb64bab18d261f14","affectsGlobalScope":true},{"version":"3be5a1453daa63e031d266bf342f3943603873d890ab8b9ada95e22389389006","affectsGlobalScope":true},{"version":"17bb1fc99591b00515502d264fa55dc8370c45c5298f4a5c2083557dccba5a2a","affectsGlobalScope":true},{"version":"7ce9f0bde3307ca1f944119f6365f2d776d281a393b576a18a2f2893a2d75c98","affectsGlobalScope":true},{"version":"6a6b173e739a6a99629a8594bfb294cc7329bfb7b227f12e1f7c11bc163b8577","affectsGlobalScope":true},{"version":"12a310447c5d23c7d0d5ca2af606e3bd08afda69100166730ab92c62999ebb9d","affectsGlobalScope":true},{"version":"b0124885ef82641903d232172577f2ceb5d3e60aed4da1153bab4221e1f6dd4e","affectsGlobalScope":true},{"version":"0eb85d6c590b0d577919a79e0084fa1744c1beba6fd0d4e951432fa1ede5510a","affectsGlobalScope":true},{"version":"da233fc1c8a377ba9e0bed690a73c290d843c2c3d23a7bd7ec5cd3d7d73ba1e0","affectsGlobalScope":true},{"version":"d154ea5bb7f7f9001ed9153e876b2d5b8f5c2bb9ec02b3ae0d239ec769f1f2ae","affectsGlobalScope":true},{"version":"bb2d3fb05a1d2ffbca947cc7cbc95d23e1d053d6595391bd325deb265a18d36c","affectsGlobalScope":true},{"version":"c80df75850fea5caa2afe43b9949338ce4e2de086f91713e9af1a06f973872b8","affectsGlobalScope":true},{"version":"9d57b2b5d15838ed094aa9ff1299eecef40b190722eb619bac4616657a05f951","affectsGlobalScope":true},{"version":"6c51b5dd26a2c31dbf37f00cfc32b2aa6a92e19c995aefb5b97a3a64f1ac99de","affectsGlobalScope":true},{"version":"6e7997ef61de3132e4d4b2250e75343f487903ddf5370e7ce33cf1b9db9a63ed","affectsGlobalScope":true},{"version":"2ad234885a4240522efccd77de6c7d99eecf9b4de0914adb9a35c0c22433f993","affectsGlobalScope":true},{"version":"09aa50414b80c023553090e2f53827f007a301bc34b0495bfb2c3c08ab9ad1eb","affectsGlobalScope":true},{"version":"d7f680a43f8cd12a6b6122c07c54ba40952b0c8aa140dcfcf32eb9e6cb028596","affectsGlobalScope":true},{"version":"3787b83e297de7c315d55d4a7c546ae28e5f6c0a361b7a1dcec1f1f50a54ef11","affectsGlobalScope":true},{"version":"e7e8e1d368290e9295ef18ca23f405cf40d5456fa9f20db6373a61ca45f75f40","affectsGlobalScope":true},{"version":"faf0221ae0465363c842ce6aa8a0cbda5d9296940a8e26c86e04cc4081eea21e","affectsGlobalScope":true},{"version":"06393d13ea207a1bfe08ec8d7be562549c5e2da8983f2ee074e00002629d1871","affectsGlobalScope":true},{"version":"cd483c056da900716879771893a3c9772b66c3c88f8943b4205aec738a94b1d0","affectsGlobalScope":true},{"version":"b248e32ca52e8f5571390a4142558ae4f203ae2f94d5bac38a3084d529ef4e58","affectsGlobalScope":true},{"version":"6c55633c733c8378db65ac3da7a767c3cf2cf3057f0565a9124a16a3a2019e87","affectsGlobalScope":true},{"version":"fb4416144c1bf0323ccbc9afb0ab289c07312214e8820ad17d709498c865a3fe","affectsGlobalScope":true},{"version":"5b0ca94ec819d68d33da516306c15297acec88efeb0ae9e2b39f71dbd9685ef7","affectsGlobalScope":true},{"version":"ff667ee99e5a28c3dc5063a3cfd4d3436699e3fb035d4451037da7f567da542a","affectsGlobalScope":true},{"version":"34478567f8a80171f88f2f30808beb7da15eac0538ae91282dd33dce928d98ed","affectsGlobalScope":true},{"version":"6ea9ab679ea030cf46c16a711a316078e9e02619ebaf07a7fcd16964aba88f2d","affectsGlobalScope":true},{"version":"6bda95ea27a59a276e46043b7065b55bd4b316c25e70e29b572958fa77565d43","affectsGlobalScope":true},{"version":"aedb8de1abb2ff1095c153854a6df7deae4a5709c37297f9d6e9948b6806fa66","affectsGlobalScope":true},{"version":"11ffe3c281f375fff9ffdde8bbec7669b4dd671905509079f866f2354a788064","affectsGlobalScope":true},{"version":"c37f8a49593a0030eecb51bbfa270e709bec9d79a6cc3bb851ef348d4e6b26f8","affectsGlobalScope":true},{"version":"e16fac47d957aa10ec362521c463e0b7977f2e2ea46d56ee9c38811b88ee8319","signature":"f66ef4029acb9685e164e51d45eee1db234bdeb443fd13ea0f07d5475777dfb5"},{"version":"7f16e48f606bc17763f896f394b1416b01ea9ec8bbcae2c13e3e730832d73bc3","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","d8aab31ba8e618cc3eea10b0945de81cb93b7e8150a013a482332263b9305322","462bccdf75fcafc1ae8c30400c9425e1a4681db5d605d1a0edb4f990a54d8094","5923d8facbac6ecf7c84739a5c701a57af94a6f6648d6229a6c768cf28f0f8cb","7adecb2c3238794c378d336a8182d4c3dd2c4fa6fa1785e2797a3db550edea62","dc12dc0e5aa06f4e1a7692149b78f89116af823b9e1f1e4eae140cd3e0e674e6","1bfc6565b90c8771615cd8cfcf9b36efc0275e5e83ac7d9181307e96eb495161","8a8a96898906f065f296665e411f51010b51372fa260d5373bf9f64356703190","7f82ef88bdb67d9a850dd1c7cd2d690f33e0f0acd208e3c9eba086f3670d4f73",{"version":"4564f780fd20582c57ae218a4cd017717181ab0e228639d905ef054288655b5e","affectsGlobalScope":true},"0cba3a5d7b81356222594442753cf90dd2892e5ccfe1d262aaca6896ba6c1380","a69c09dbea52352f479d3e7ac949fde3d17b195abe90b045d619f747b38d6d1a",{"version":"77f0b5c6a193a699c9f7d7fb0578e64e562d271afa740783665d2a827104a873","affectsGlobalScope":true},"e5979905796fe2740d85fbaf4f11f42b7ee1851421afe750823220813421b1af",{"version":"fcdcb42da18dd98dc286b1876dd425791772036012ae61263c011a76b13a190f","affectsGlobalScope":true},"1dab5ab6bcf11de47ab9db295df8c4f1d92ffa750e8f095e88c71ce4c3299628","f71f46ccd5a90566f0a37b25b23bc4684381ab2180bdf6733f4e6624474e1894",{"version":"54e65985a3ee3cec182e6a555e20974ea936fc8b8d1738c14e8ed8a42bd921d4","affectsGlobalScope":true},"82408ed3e959ddc60d3e9904481b5a8dc16469928257af22a3f7d1a3bc7fd8c4","5b30f550565fd0a7524282c81c27fe8534099e2cd26170ca80852308f07ae68d","34e5de87d983bc6aefef8b17658556e3157003e8d9555d3cb098c6bef0b5fbc8","d97cd8a4a42f557fc62271369ed0461c8e50d47b7f9c8ad0b5462f53306f6060","f27371653aded82b2b160f7a7033fb4a5b1534b6f6081ef7be1468f0f15327d3","c762cd6754b13a461c54b59d0ae0ab7aeef3c292c6cf889873f786ee4d8e75c9","f4ea7d5df644785bd9fbf419930cbaec118f0d8b4160037d2339b8e23c059e79",{"version":"bfea28e6162ed21a0aeed181b623dcf250aa79abf49e24a6b7e012655af36d81","affectsGlobalScope":true},"58df92fa3b18e84865bb0d2fe4b9d2d5bcb9952d4548c871f10ef02702b386f8","8821ee08124e5defd25fdc7840ae865cd9748fe57492f74f7d656e4b66cca91c","10d4796a130577d57003a77b95d8723530bbec84718e364aa2129fa8ffba0378","063f53ff674228c190efa19dd9448bcbd540acdbb48a928f4cf3a1b9f9478e43","bf73c576885408d4a176f44a9035d798827cc5020d58284cb18d7573430d9022","7ae078ca42a670445ae0c6a97c029cb83d143d62abd1730efb33f68f0b2c0e82",{"version":"e8b18c6385ff784228a6f369694fcf1a6b475355ba89090a88de13587a9391d5","affectsGlobalScope":true},"963fe86b2ebd07a34b92b52c6532ab45ec5ccda218a6c477de354fcad2aae0cb","12eea70b5e11e924bb0543aea5eadc16ced318aa26001b453b0d561c2fd0bd1e","08777cd9318d294646b121838574e1dd7acbb22c21a03df84e1f2c87b1ad47f2","08a90bcdc717df3d50a2ce178d966a8c353fd23e5c392fd3594a6e39d9bb6304",{"version":"9f8dd3922db205bc8a362a6b18078708fd699185b11648522159cbf3743561b5","affectsGlobalScope":true},"2a12d2da5ac4c4979401a3f6eaafa874747a37c365e4bc18aa2b171ae134d21b","002b837927b53f3714308ecd96f72ee8a053b8aeb28213d8ec6de23ed1608b66","1dc9c847473bb47279e398b22c740c83ea37a5c88bf66629666e3cf4c5b9f99c","a9e4a5a24bf2c44de4c98274975a1a705a0abbaad04df3557c2d3cd8b1727949","821dcb2b571bf698841d8ec25fde9d5f615ef3958957227962602f9dbfa8d800","1b952304137851e45bc009785de89ada562d9376177c97e37702e39e60c2f1ff",{"version":"806ef4cac3b3d9fa4a48d849c8e084d7c72fcd7b16d76e06049a9ed742ff79c0","affectsGlobalScope":true},"44b8b584a338b190a59f4f6929d072431950c7bd92ec2694821c11bce180c8a5","5f0ed51db151c2cdc4fa3bb0f44ce6066912ad001b607a34e65a96c52eb76248",{"version":"f234fa210fdce190f851211bccf105301f62736fe9d536aed1abc1639967fdec","affectsGlobalScope":true},"664d8f2d59164f2e08c543981453893bc7e003e4dfd29651ce09db13e9457980","103d70bfbeb3cd3a3f26d1705bf986322d8738c2c143f38ebb743b1e228d7444","f52fbf64c7e480271a9096763c4882d356b05cab05bf56a64e68a95313cd2ce2","59bdb65f28d7ce52ccfc906e9aaf422f8b8534b2d21c32a27d7819be5ad81df7",{"version":"3a2da34079a2567161c1359316a32e712404b56566c45332ac9dcee015ecce9f","affectsGlobalScope":true},"28a2e7383fd898c386ffdcacedf0ec0845e5d1a86b5a43f25b86bc315f556b79","3aff9c8c36192e46a84afe7b926136d520487155154ab9ba982a8b544ea8fc95","a880cf8d85af2e4189c709b0fea613741649c0e40fffb4360ec70762563d5de0","85bbf436a15bbeda4db888be3062d47f99c66fd05d7c50f0f6473a9151b6a070","9f9c49c95ecd25e0cb2587751925976cf64fd184714cb11e213749c80cf0f927","f0c75c08a71f9212c93a719a25fb0320d53f2e50ca89a812640e08f8ad8c408c",{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true},"aee3379fb20741a337a779530cc3e608aba5f34776511033d1d2db7ca45c4193"],"options":{"allowSyntheticDefaultImports":true,"declaration":true,"esModuleInterop":true,"experimentalDecorators":true,"module":1,"noEmitOnError":true,"noImplicitAny":true,"noImplicitReturns":true,"noImplicitThis":true,"noUnusedLocals":true,"noUnusedParameters":true,"removeComments":true,"rootDir":"./","skipLibCheck":true,"sourceMap":true,"strict":true,"strictNullChecks":true,"suppressImplicitAnyIndexErrors":true,"target":8},"fileIdsList":[[53,108],[108],[58,63,108],[65,108],[68,108],[69,74,108],[70,80,81,88,97,107,108],[70,71,80,88,108],[72,108],[73,74,81,89,108],[74,97,104,108],[75,77,80,88,108],[76,108],[77,78,108],[79,80,108],[80,108],[80,81,82,97,107,108],[80,81,82,97,108],[108,112],[83,88,97,107,108],[80,81,83,84,88,97,104,107,108],[83,85,97,104,107,108],[65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114],[80,86,108],[87,107,108],[77,80,88,97,108],[89,108],[90,108],[68,91,108],[92,106,108,112],[93,108],[94,108],[80,95,108],[95,96,108,110],[80,97,98,99,108],[97,99,108],[97,98,108],[100,108],[101,108],[80,102,103,108],[102,103,108],[74,88,97,104,108],[105,108],[88,106,108],[69,83,94,107,108],[74,108],[97,108,109],[108,110],[108,111],[69,74,80,82,91,97,107,108,110,112],[97,108,113],[56,59,108],[56,59,60,61,108],[58,108],[55,62,108],[57,108]],"referencedMap":[[54,1],[53,2],[64,3],[65,4],[66,4],[68,5],[69,6],[70,7],[71,8],[72,9],[73,10],[74,11],[75,12],[76,13],[77,14],[78,14],[79,15],[80,16],[81,17],[82,18],[67,19],[114,2],[83,20],[84,21],[85,22],[115,23],[86,24],[87,25],[88,26],[89,27],[90,28],[91,29],[92,30],[93,31],[94,32],[95,33],[96,34],[97,35],[99,36],[98,37],[100,38],[101,39],[102,40],[103,41],[104,42],[105,43],[106,44],[107,45],[108,46],[109,47],[110,48],[111,49],[112,50],[113,51],[55,2],[56,2],[60,52],[62,53],[61,52],[59,54],[63,55],[58,56],[57,2],[12,2],[11,2],[2,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[3,2],[4,2],[24,2],[21,2],[22,2],[23,2],[25,2],[26,2],[27,2],[5,2],[28,2],[29,2],[30,2],[31,2],[6,2],[32,2],[33,2],[34,2],[35,2],[7,2],[36,2],[41,2],[42,2],[37,2],[38,2],[39,2],[40,2],[8,2],[46,2],[43,2],[44,2],[45,2],[47,2],[9,2],[48,2],[49,2],[50,2],[51,2],[1,2],[10,2],[52,2]],"exportedModulesMap":[[64,3],[65,4],[66,4],[68,5],[69,6],[70,7],[71,8],[72,9],[73,10],[74,11],[75,12],[76,13],[77,14],[78,14],[79,15],[80,16],[81,17],[82,18],[67,19],[114,2],[83,20],[84,21],[85,22],[115,23],[86,24],[87,25],[88,26],[89,27],[90,28],[91,29],[92,30],[93,31],[94,32],[95,33],[96,34],[97,35],[99,36],[98,37],[100,38],[101,39],[102,40],[103,41],[104,42],[105,43],[106,44],[107,45],[108,46],[109,47],[110,48],[111,49],[112,50],[113,51],[55,2],[56,2],[60,52],[62,53],[61,52],[59,54],[63,55],[58,56],[57,2],[12,2],[11,2],[2,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[3,2],[4,2],[24,2],[21,2],[22,2],[23,2],[25,2],[26,2],[27,2],[5,2],[28,2],[29,2],[30,2],[31,2],[6,2],[32,2],[33,2],[34,2],[35,2],[7,2],[36,2],[41,2],[42,2],[37,2],[38,2],[39,2],[40,2],[8,2],[46,2],[43,2],[44,2],[45,2],[47,2],[9,2],[48,2],[49,2],[50,2],[51,2],[1,2],[10,2],[52,2]],"semanticDiagnosticsPerFile":[54,53,64,65,66,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,67,114,83,84,85,115,86,87,88,89,90,91,92,93,94,95,96,97,99,98,100,101,102,103,104,105,106,107,108,109,110,111,112,113,55,56,60,62,61,59,63,58,57,12,11,2,13,14,15,16,17,18,19,20,3,4,24,21,22,23,25,26,27,5,28,29,30,31,6,32,33,34,35,7,36,41,42,37,38,39,40,8,46,43,44,45,47,9,48,49,50,51,1,10,52],"affectedFilesPendingEmit":[[54,1],[53,1],[64,1],[65,1],[66,1],[68,1],[69,1],[70,1],[71,1],[72,1],[73,1],[74,1],[75,1],[76,1],[77,1],[78,1],[79,1],[80,1],[81,1],[82,1],[67,1],[114,1],[83,1],[84,1],[85,1],[115,1],[86,1],[87,1],[88,1],[89,1],[90,1],[91,1],[92,1],[93,1],[94,1],[95,1],[96,1],[97,1],[99,1],[98,1],[100,1],[101,1],[102,1],[103,1],[104,1],[105,1],[106,1],[107,1],[108,1],[109,1],[110,1],[111,1],[112,1],[113,1],[55,1],[56,1],[60,1],[62,1],[61,1],[59,1],[63,1],[58,1],[57,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1],[10,1]]},"version":"4.7.2"} \ No newline at end of file diff --git a/scripts/utils/config.ts b/scripts/utils/config.ts new file mode 100644 index 000000000..d8f88d2a4 --- /dev/null +++ b/scripts/utils/config.ts @@ -0,0 +1,30 @@ +import { calculateFee, GasPrice } from '@cosmjs/stargate'; +import { StdFee } from '@cosmjs/amino'; + +export type NetworkConfig = { + localRpcEndpoint: string; + provider: string; + transactionLink: (arg0: string) => string; + walletLink: (arg0: string) => string; + networkName: string; + bech32Prefix: string; + nativeDenom: string; + defaultSendFee: StdFee; +}; + +export enum Network { + OSMOSIS, +} + +export const networks: Record = { + [Network.OSMOSIS]: { + localRpcEndpoint: 'tcp://localhost:26657', + provider: 'https://rpc-osmosis.keplr.app/', + transactionLink: (hash) => `https://www.mintscan.io/osmosis/txs/${hash}`, + walletLink: (address) => `https://www.mintscan.io/osmosis/account/${address}`, + networkName: 'osmosis', + bech32Prefix: 'osmo', + nativeDenom: 'uosmo', + defaultSendFee: calculateFee(100_000, GasPrice.fromString('0.025uosmo')), + }, +}; diff --git a/scripts/utils/osmosis-client.ts b/scripts/utils/osmosis-client.ts new file mode 100644 index 000000000..ff1fd1b89 --- /dev/null +++ b/scripts/utils/osmosis-client.ts @@ -0,0 +1,32 @@ +import { getSigningOsmosisClient } from 'osmojs'; +import { SigningStargateClient } from '@cosmjs/stargate'; +import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; +import { walletDataType } from './test-wallets'; +import { Network, networks } from './config'; +import { Slip10RawIndex } from '@cosmjs/crypto'; +import { MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'; + +const hdPath = [ + Slip10RawIndex.hardened(44), + Slip10RawIndex.hardened(118), + Slip10RawIndex.hardened(0), + Slip10RawIndex.normal(0), + Slip10RawIndex.normal(0), +]; + +type ClientGetter = (wallet: walletDataType) => Promise; + +export const getOsmosisClient: ClientGetter = async (wallet) => { + const signer = await DirectSecp256k1HdWallet.fromMnemonic(wallet.mnemonic, { + prefix: networks[Network.OSMOSIS].bech32Prefix, + hdPaths: [hdPath], + }); + + const client = await getSigningOsmosisClient({ + rpcEndpoint: networks[Network.OSMOSIS].localRpcEndpoint, + signer, + }); + + client.registry.register('/cosmwasm.wasm.v1.MsgStoreCode', MsgStoreCode); + return client; +}; diff --git a/scripts/utils/test-wallets.ts b/scripts/utils/test-wallets.ts new file mode 100644 index 000000000..2463068ad --- /dev/null +++ b/scripts/utils/test-wallets.ts @@ -0,0 +1,92 @@ +// Taken from mnemonics.json in LocalOsmosis repo +// All test users have uion & uosmo balances + +export interface walletDataType { + address: string; + name: string; + mnemonic: string; + pubkey: { '@type': string; key: string }; +} + +const walletData: walletDataType[] = [ + { + name: 'validator', + address: 'osmo1phaxpevm5wecex2jyaqty2a4v02qj7qmlmzk5a', + pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'AkNVLtIlk2c3zweQXS6jyVshzVhAy0M59crUeksc2pak' }, + mnemonic: + 'satisfy adjust timber high purchase tuition stool faith fine install that you unaware feed domain license impose boss human eager hat rent enjoy dawn', + }, + { + name: 'test1', + address: 'osmo1cyyzpxplxdzkeea7kwsydadg87357qnahakaks', + pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'AuwYyCUBxQiBGSUWebU46c+OrlApVsyGLHd4qhSDZeiG' }, + mnemonic: + 'notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius', + }, + { + name: 'test2', + address: 'osmo18s5lynnmx37hq4wlrw9gdn68sg2uxp5rgk26vv', + pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'A2G5GnZLlHyxQJUI6LW2ww1lnFEBy+3CCl8LsK2OY6Tj' }, + mnemonic: + 'quality vacuum heart guard buzz spike sight swarm shove special gym robust assume sudden deposit grid alcohol choice devote leader tilt noodle tide penalty', + }, + { + name: 'test3', + address: 'osmo1qwexv7c6sm95lwhzn9027vyu2ccneaqad4w8ka', + pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'ApNMBAr8lFRS6DaOKXgGXFcrpf78KHyqPvRCLZrM0Zzg' }, + mnemonic: + 'symbol force gallery make bulk round subway violin worry mixture penalty kingdom boring survey tool fringe patrol sausage hard admit remember broken alien absorb', + }, + { + name: 'test4', + address: 'osmo14hcxlnwlqtq75ttaxf674vk6mafspg8xwgnn53', + pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'A0RRfnW/yIHOgFjGpknpT/j19OP3YMsXj6OhuCCHfyu6' }, + mnemonic: + 'bounce success option birth apple portion aunt rural episode solution hockey pencil lend session cause hedgehog slender journey system canvas decorate razor catch empty', + }, + { + name: 'test5', + address: 'osmo12rr534cer5c0vj53eq4y32lcwguyy7nndt0u2t', + pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'A5sEEVq3yKGF/pDihGjtSe3SElOd05zXzMxCBPMAhspC' }, + mnemonic: + 'second render cat sing soup reward cluster island bench diet lumber grocery repeat balcony perfect diesel stumble piano distance caught occur example ozone loyal', + }, + { + name: 'test6', + address: 'osmo1nt33cjd5auzh36syym6azgc8tve0jlvklnq7jq', + pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'AweL0IVkZAHjmdSPJucxcln3AcPuMHD4EcnjKBFLZkcA' }, + mnemonic: + 'spatial forest elevator battle also spoon fun skirt flight initial nasty transfer glory palm drama gossip remove fan joke shove label dune debate quick', + }, + { + name: 'test7', + address: 'osmo10qfrpash5g2vk3hppvu45x0g860czur8ff5yx0', + pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'A5aDi6tH57PDZossksf820HI+kVGO5etFqjGbFw/tACu' }, + mnemonic: + 'noble width taxi input there patrol clown public spell aunt wish punch moment will misery eight excess arena pen turtle minimum grain vague inmate', + }, + { + name: 'test8', + address: 'osmo1f4tvsdukfwh6s9swrc24gkuz23tp8pd3e9r5fa', + pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'AgZffLI+SEDH5qCrGoG4HjPy8AIDVmjGZJzy7L3YNkb9' }, + mnemonic: + 'cream sport mango believe inhale text fish rely elegant below earth april wall rug ritual blossom cherry detail length blind digital proof identify ride', + }, + { + name: 'test9', + address: 'osmo1myv43sqgnj5sm4zl98ftl45af9cfzk7nhjxjqh', + pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'A65FjujcdnaFQutpnfkj82QSKtYOMBJPaW4pfTiERwMu' }, + mnemonic: + 'index light average senior silent limit usual local involve delay update rack cause inmate wall render magnet common feature laundry exact casual resource hundred', + }, + { + name: 'test10', + address: 'osmo14gs9zqh8m49yy9kscjqu9h72exyf295afg6kgk', + pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'A2Kc6ERRH6B4REjY6ryTO+ZdNbxuJATDVKXA89irZpKO' }, + mnemonic: + 'prefer forget visit mistake mixture feel eyebrow autumn shop pair address airport diesel street pass vague innocent poem method awful require hurry unhappy shoulder', + }, +]; + +export const testWallet1 = walletData[1]; +export const testWallet2 = walletData[2]; From 8ff572166334cfd484dcfe751d4a2730d35a1f39 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 6 Jun 2022 14:27:42 +0400 Subject: [PATCH 006/218] Pushing to compile on linux vm --- .gitignore | 3 --- scripts/tests/client.test.ts | 2 +- scripts/tests/contract.test.ts | 15 +++++---------- scripts/utils/config.ts | 2 +- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index fd8b0b077..a57c1261e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -# cosmwasm compiled contract binaries -artifacts - # cargo files target diff --git a/scripts/tests/client.test.ts b/scripts/tests/client.test.ts index 644d9b7f7..4098044c4 100644 --- a/scripts/tests/client.test.ts +++ b/scripts/tests/client.test.ts @@ -3,7 +3,7 @@ import { getOsmosisClient } from '../utils/osmosis-client'; import { assertIsDeliverTxSuccess } from '@cosmjs/stargate'; import { Network, networks } from '../utils/config'; -describe('example client test', () => { +describe.skip('example client test', () => { test('can get client and transfer tokens', async () => { const client = await getOsmosisClient(testWallet1); diff --git a/scripts/tests/contract.test.ts b/scripts/tests/contract.test.ts index db1a39215..ddffbc25e 100644 --- a/scripts/tests/contract.test.ts +++ b/scripts/tests/contract.test.ts @@ -2,25 +2,20 @@ import { testWallet1 } from '../utils/test-wallets'; import { getOsmosisClient } from '../utils/osmosis-client'; import fs from 'fs'; import path from 'path'; -import { EncodeObject } from '@cosmjs/proto-signing'; -import { AccessType } from 'cosmjs-types/cosmwasm/wasm/v1/types'; import { Network, networks } from '../utils/config'; +import { MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'; describe('example contract', () => { test('can deploy contract', async () => { const client = await getOsmosisClient(testWallet1); const contractCode = fs.readFileSync(path.resolve(__dirname, '../../artifacts/example-aarch64.wasm')); - const storeCode: EncodeObject = { + const storeCode = { typeUrl: '/cosmwasm.wasm.v1.MsgStoreCode', - value: { - instantiate_permission: { - address: testWallet1.address, - permission: AccessType.ACCESS_TYPE_UNSPECIFIED, - }, + value: MsgStoreCode.fromPartial({ sender: testWallet1.address, - wasm_byte_code: contractCode, - }, + wasmByteCode: contractCode, + }), }; const result = await client.signAndBroadcast( diff --git a/scripts/utils/config.ts b/scripts/utils/config.ts index d8f88d2a4..eed23c656 100644 --- a/scripts/utils/config.ts +++ b/scripts/utils/config.ts @@ -25,6 +25,6 @@ export const networks: Record = { networkName: 'osmosis', bech32Prefix: 'osmo', nativeDenom: 'uosmo', - defaultSendFee: calculateFee(100_000, GasPrice.fromString('0.025uosmo')), + defaultSendFee: calculateFee(3_000_000, GasPrice.fromString('0.025uosmo')), }, }; From 32ae0c587d13a6219ddab219c7ace1e4a91a4158 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 6 Jun 2022 10:32:10 +0000 Subject: [PATCH 007/218] Compiling contracts from linux --- artifacts/checksums.txt | 1 + artifacts/checksums_intermediate.txt | 1 + artifacts/example.wasm | Bin 0 -> 137534 bytes scripts/tests/contract.test.ts | 2 +- 4 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 artifacts/checksums.txt create mode 100644 artifacts/checksums_intermediate.txt create mode 100644 artifacts/example.wasm diff --git a/artifacts/checksums.txt b/artifacts/checksums.txt new file mode 100644 index 000000000..632bdd07a --- /dev/null +++ b/artifacts/checksums.txt @@ -0,0 +1 @@ +15deb82a78f868bcb954e8825a5989546ef00b49144f5dbe94d3e0f49dbc73aa example.wasm diff --git a/artifacts/checksums_intermediate.txt b/artifacts/checksums_intermediate.txt new file mode 100644 index 000000000..cf80079db --- /dev/null +++ b/artifacts/checksums_intermediate.txt @@ -0,0 +1 @@ +222079eb7ade43eb8d5c34391dc356e571338eca3d06407da8dff3a433852289 ./target/wasm32-unknown-unknown/release/example.wasm diff --git a/artifacts/example.wasm b/artifacts/example.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a6710b79cf35d507ac0b889f062e26d5e6f4ae81 GIT binary patch literal 137534 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%0A1QE#g2Bz-PW|!) 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εE z%s`T1Pk>wP;IAWNFX|l)hxTD~NXutb;?lSlH7W`qR-7pl8Q{dhZ5UxYaUbi{q&^a@ zB889NU%n$#fI*J$^-7;Rd1PpNb^XZbaNppSV*>-5tAj(^d$$jb4UTRc7~0f7I@~)r z(z|K2Z)kAS&>$&p2RB!*-dgRua_i`5-}dUJt-XDN>-#p3RCzwUWo&TsNbly&!_|=y z&&zl)8h%-RKEEKo?+wrC8`&|?yF2XLzGI-ey*fDBt0uzX>gd?;V0ClYI~Z1nhlhs4 zvBB!qJF1&TtD6UQFB%>j8QrvK<;tZitDPH{Z(6pvbLo=BD>kp#vbk&1mQ~9ZZ(P~C zan;7n8+&^qTT_zvS?(OpY?O$FtGi(SN1Ldo-IwDH;t<`H`MRt zxYwUx&+{X@)`w>9OZ?QI--JF4Wg7eNn(D}op}`RlIx^CGWp$)?l>TlU8?BC1cY))P z&Ap?&V}t#JLst!kyLyNFdIv|t4IAc%qg$)P)v%Y};82nQioE^QkF4-Ae%YJNm7>XL zgh}3f@-)WV`$k5fp>RuIbzn2Kp3n0(o?p&YxZFIpW1w$S?`SnC!LzO8ss5yl>d{|< z`@X?ly#sxl!`Jo=j;>f1o^(>UAp8LDlqqvJSB-BUWN;2Q*oA8J4@f@5*7G3|6mNziDW56~+QO z`UXcvq18Sz;)lj(26{JE2dY=b77~w0-+BLkB%6x z_+qx~y$Kxl^P9sjNxx%4`g?KuSgr|8s(d@Y-xQksvs@!h&ILC8C36l84#0pnz)Qvk zM!UPm2Co|K-7)Xv4I%s)w!Goo;o%K1iGi`IcO379{EGZu!7n9CwpT~D4sA{i-0|e= zPwj;KI`~!irJw&6=@a<9lHZ9F@=hW>kKf7sPT_Ydzxn)-b>2dL>3Rr|^3EQTpsGnT z?}>cbc>7aa#n+8bjjZ1^T;1F^x_*1_APjT3ySsXIuS8OJ_qmR)M7uG8ASE|Uni}WeQee}c z=7wlfX~C-BGJebXt>BlMU*46~Q3z548f$Ol-%q`gr&A3yqj9d30Aaqa7etAM&|lr{ z=5)DP5>)eNP*O@-naQZ`5II7gPo;bA5>Nw@O101fhy}8z>|BbQM?Whpz07UYeyGLVw5=zJ+|ru%sMn%4oO+ zC3Q3Mo;>~C$5ri}-8-n<%_b?r*Iu@!3pmk$Ty-%`hMxB*<;7#x#yW;{qj03}t<~U2zw~!~L~%^lc@A|t6vu@fLSyvNN{p~e3}2)vpML*b(rKKjL^^*B`Il}TxOD6A zB|}$TG&X>S6>Z)eQ%HCfNUzbcCE32Uaw)1$;W?YS6ZB8YV%DcX`Y`X1xxr*k}<64JjaP!M)6rE2y z3C~>1{j2!NM(RB>d(GPm?0P@3u{1XNOZePR!1FWwe!%a0{Jz8QNq$GJ%x$z8xuA35 z;-!fmhr+qJiW-+pTCoIRxNTb5do5S-SIup>O%)9w)C3g|3$NN*9SqTiHjNE1?C_(F zV_UXRKs+M#zH|-o+za_>o@y>$#P6~TPdn!|-Qnw`D23ZmmyKy{tcF#IFt|}DE{)QG zpsbEk`KX?uVUN=22>fdI&=_}v;n+yEUUay+9RMjgGFt83yfD0Q2*4x;c6W#8RWZm( zx(1B4M#3$_L))zYErop}2Efq`&jY=;zF+1jHr0BKrw_`>i~uHn#@5bi=dqgC|! z0e7Vj^}^S;F@F(mM^4j;<}|@T z!F7@tC|jpH`(yyRYvs3&Um_DO;r`Y9Uc>KFetm;GAhpq

RO7Bb3NgThGY!$rfu! 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 EHx-*Y#sB~S literal 0 HcmV?d00001 diff --git a/scripts/tests/contract.test.ts b/scripts/tests/contract.test.ts index ddffbc25e..0e510332d 100644 --- a/scripts/tests/contract.test.ts +++ b/scripts/tests/contract.test.ts @@ -9,7 +9,7 @@ describe('example contract', () => { test('can deploy contract', async () => { const client = await getOsmosisClient(testWallet1); - const contractCode = fs.readFileSync(path.resolve(__dirname, '../../artifacts/example-aarch64.wasm')); + const contractCode = fs.readFileSync(path.resolve(__dirname, '../../artifacts/example.wasm')); const storeCode = { typeUrl: '/cosmwasm.wasm.v1.MsgStoreCode', value: MsgStoreCode.fromPartial({ From d71996a00530c8017793df962e89b806614fa382 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 6 Jun 2022 15:14:27 +0000 Subject: [PATCH 008/218] Adding instantiation tests --- scripts/package-lock.json | 182 ++++++++++++++++++++++++++++++++ scripts/package.json | 2 + scripts/tests/client.test.ts | 8 +- scripts/tests/contract.test.ts | 79 ++++++++++++-- scripts/utils/osmosis-client.ts | 19 +++- 5 files changed, 274 insertions(+), 16 deletions(-) diff --git a/scripts/package-lock.json b/scripts/package-lock.json index cc5d03087..e240576e6 100644 --- a/scripts/package-lock.json +++ b/scripts/package-lock.json @@ -8,8 +8,10 @@ "name": "fields-scripts", "version": "1.0.0", "dependencies": { + "@cosmjs/cosmwasm-stargate": "^0.28.4", "@cosmjs/stargate": "^0.28.4", "cosmjs-types": "^0.5.0", + "long": "^5.2.0", "osmojs": "^0.4.46" }, "devDependencies": { @@ -1778,6 +1780,94 @@ "@cosmjs/utils": "0.28.4" } }, + "node_modules/@cosmjs/cosmwasm-stargate": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.28.4.tgz", + "integrity": "sha512-dkTwTD+j2mjk7+l3pQQ3io2D0U7NIA4LXzkKtfBN87PGlj2G+VJFzcXk1T4DYmvrXjsQOi1kYeQRGWFA0XdvnQ==", + "dependencies": { + "@cosmjs/amino": "0.28.4", + "@cosmjs/crypto": "0.28.4", + "@cosmjs/encoding": "0.28.4", + "@cosmjs/math": "0.28.4", + "@cosmjs/proto-signing": "0.28.4", + "@cosmjs/stargate": "0.28.4", + "@cosmjs/tendermint-rpc": "0.28.4", + "@cosmjs/utils": "0.28.4", + "cosmjs-types": "^0.4.0", + "long": "^4.0.0", + "pako": "^2.0.2", + "protobufjs": "~6.10.2" + } + }, + "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@types/node": { + "version": "13.13.52", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", + "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" + }, + "node_modules/@cosmjs/cosmwasm-stargate/node_modules/cosmjs-types": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", + "integrity": "sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog==", + "dependencies": { + "long": "^4.0.0", + "protobufjs": "~6.11.2" + } + }, + "node_modules/@cosmjs/cosmwasm-stargate/node_modules/cosmjs-types/node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/@cosmjs/cosmwasm-stargate/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/@cosmjs/cosmwasm-stargate/node_modules/protobufjs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz", + "integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, "node_modules/@cosmjs/crypto": { "version": "0.28.4", "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.4.tgz", @@ -6132,6 +6222,11 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -8405,6 +8500,88 @@ "@cosmjs/utils": "0.28.4" } }, + "@cosmjs/cosmwasm-stargate": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.28.4.tgz", + "integrity": "sha512-dkTwTD+j2mjk7+l3pQQ3io2D0U7NIA4LXzkKtfBN87PGlj2G+VJFzcXk1T4DYmvrXjsQOi1kYeQRGWFA0XdvnQ==", + "requires": { + "@cosmjs/amino": "0.28.4", + "@cosmjs/crypto": "0.28.4", + "@cosmjs/encoding": "0.28.4", + "@cosmjs/math": "0.28.4", + "@cosmjs/proto-signing": "0.28.4", + "@cosmjs/stargate": "0.28.4", + "@cosmjs/tendermint-rpc": "0.28.4", + "@cosmjs/utils": "0.28.4", + "cosmjs-types": "^0.4.0", + "long": "^4.0.0", + "pako": "^2.0.2", + "protobufjs": "~6.10.2" + }, + "dependencies": { + "@types/node": { + "version": "13.13.52", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", + "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" + }, + "cosmjs-types": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", + "integrity": "sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog==", + "requires": { + "long": "^4.0.0", + "protobufjs": "~6.11.2" + }, + "dependencies": { + "protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + } + } + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "protobufjs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz", + "integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + } + } + } + }, "@cosmjs/crypto": { "version": "0.28.4", "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.4.tgz", @@ -11790,6 +11967,11 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", diff --git a/scripts/package.json b/scripts/package.json index c23a8309c..fa18c7218 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -9,8 +9,10 @@ "lint": "tsc && eslint . --ext .ts" }, "dependencies": { + "@cosmjs/cosmwasm-stargate": "^0.28.4", "@cosmjs/stargate": "^0.28.4", "cosmjs-types": "^0.5.0", + "long": "^5.2.0", "osmojs": "^0.4.46" }, "devDependencies": { diff --git a/scripts/tests/client.test.ts b/scripts/tests/client.test.ts index 4098044c4..856c36046 100644 --- a/scripts/tests/client.test.ts +++ b/scripts/tests/client.test.ts @@ -1,9 +1,9 @@ -import { testWallet1, testWallet2 } from '../utils/test-wallets'; -import { getOsmosisClient } from '../utils/osmosis-client'; import { assertIsDeliverTxSuccess } from '@cosmjs/stargate'; import { Network, networks } from '../utils/config'; +import { getOsmosisClient } from '../utils/osmosis-client'; +import { testWallet1, testWallet2 } from '../utils/test-wallets'; -describe.skip('example client test', () => { +describe('example client test', () => { test('can get client and transfer tokens', async () => { const client = await getOsmosisClient(testWallet1); @@ -20,5 +20,7 @@ describe.skip('example client test', () => { ); assertIsDeliverTxSuccess(result); + + client.disconnect() }); }); diff --git a/scripts/tests/contract.test.ts b/scripts/tests/contract.test.ts index 0e510332d..8fff80343 100644 --- a/scripts/tests/contract.test.ts +++ b/scripts/tests/contract.test.ts @@ -1,14 +1,31 @@ -import { testWallet1 } from '../utils/test-wallets'; -import { getOsmosisClient } from '../utils/osmosis-client'; +import { toUtf8 } from "@cosmjs/encoding"; +import { Uint53 } from "@cosmjs/math"; +import { assertIsDeliverTxSuccess, SigningStargateClient } from '@cosmjs/stargate'; +import { findAttribute, parseRawLog } from '@cosmjs/stargate/build/logs'; +import { MsgInstantiateContract, MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'; import fs from 'fs'; +import Long from "long"; import path from 'path'; import { Network, networks } from '../utils/config'; -import { MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'; +import { getOsmosisClient, getQueryClient } from '../utils/osmosis-client'; +import { testWallet1 } from '../utils/test-wallets'; + +const INSTANTIATE_STR = "test-instantiate-string-123" describe('example contract', () => { - test('can deploy contract', async () => { - const client = await getOsmosisClient(testWallet1); + let client: SigningStargateClient; + let codeId: number; + let contractAddr: string; + beforeAll(async () => { + client = await getOsmosisClient(testWallet1); + }) + + afterAll(() => { + client.disconnect(); + }) + + test('can be deployed', async () => { const contractCode = fs.readFileSync(path.resolve(__dirname, '../../artifacts/example.wasm')); const storeCode = { typeUrl: '/cosmwasm.wasm.v1.MsgStoreCode', @@ -18,12 +35,58 @@ describe('example contract', () => { }), }; - const result = await client.signAndBroadcast( + const uploadResult = await client.signAndBroadcast( testWallet1.address, [storeCode], networks[Network.OSMOSIS].defaultSendFee, ); - console.log(result); + assertIsDeliverTxSuccess(uploadResult); + + const parsedLog = parseRawLog(uploadResult.rawLog); + const codeIdAttr = findAttribute(parsedLog, "store_code", "code_id"); + codeId = Number.parseInt(codeIdAttr.value, 10); + + expect(codeId).toBeDefined(); + }); + + test('can be instantiated', async () => { + const instantiateContractMsg = { + typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", + value: MsgInstantiateContract.fromPartial({ + sender: testWallet1.address, + codeId: Long.fromString(new Uint53(codeId).toString()), + label: "instantiate-example-contract", + msg: toUtf8(JSON.stringify({ some_string: INSTANTIATE_STR })), + }), + }; + + const instantiateResult = await client.signAndBroadcast( + testWallet1.address, + [instantiateContractMsg], + networks[Network.OSMOSIS].defaultSendFee, + ); + + assertIsDeliverTxSuccess(instantiateResult); + + const parsedLogs = parseRawLog(instantiateResult.rawLog); + const contractAddressAttr = findAttribute(parsedLogs, "instantiate", "_contract_address"); + contractAddr = contractAddressAttr.value; + + expect(contractAddr).toBeDefined(); + }); + + test.skip('can save item', async () => { + const queryClient = await getQueryClient(); + const beforeRes: { str: string } = await queryClient.queryContractSmart(contractAddr, { get_stored_string: {} }) + expect(beforeRes.str).toBe(INSTANTIATE_STR) + + const updatedString = "spiderman123" + + const afterRes: { str: string } = await queryClient.queryContractSmart(contractAddr, { get_stored_string: {} }) + expect(afterRes.str).toBe(updatedString) + console.log('afterRes', afterRes) + + queryClient.disconnect() }); -}); +}) diff --git a/scripts/utils/osmosis-client.ts b/scripts/utils/osmosis-client.ts index ff1fd1b89..e126d7a53 100644 --- a/scripts/utils/osmosis-client.ts +++ b/scripts/utils/osmosis-client.ts @@ -1,10 +1,11 @@ -import { getSigningOsmosisClient } from 'osmojs'; -import { SigningStargateClient } from '@cosmjs/stargate'; +import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'; +import { Slip10RawIndex } from '@cosmjs/crypto'; import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; -import { walletDataType } from './test-wallets'; +import { SigningStargateClient } from '@cosmjs/stargate'; +import { MsgInstantiateContract, MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'; +import { getSigningOsmosisClient } from 'osmojs'; import { Network, networks } from './config'; -import { Slip10RawIndex } from '@cosmjs/crypto'; -import { MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'; +import { walletDataType } from './test-wallets'; const hdPath = [ Slip10RawIndex.hardened(44), @@ -14,6 +15,7 @@ const hdPath = [ Slip10RawIndex.normal(0), ]; + type ClientGetter = (wallet: walletDataType) => Promise; export const getOsmosisClient: ClientGetter = async (wallet) => { @@ -28,5 +30,12 @@ export const getOsmosisClient: ClientGetter = async (wallet) => { }); client.registry.register('/cosmwasm.wasm.v1.MsgStoreCode', MsgStoreCode); + client.registry.register('/cosmwasm.wasm.v1.MsgInstantiateContract', MsgInstantiateContract); + return client; }; + +/* Separate client needed as querying not available in signed Osmosis client at the moment */ +export const getQueryClient: () => Promise = async () => { + return await CosmWasmClient.connect(networks[Network.OSMOSIS].localRpcEndpoint); +}; From 961319388b30782efb0190f3e1c2708acaa6c07f Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 6 Jun 2022 19:26:24 +0400 Subject: [PATCH 009/218] Adding execute function --- contracts/example/src/contract.rs | 17 ++++++-- contracts/example/src/contract_tests.rs | 41 ++++++++++++++++++- packages/fields-credit-manager/src/example.rs | 9 +++- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/contracts/example/src/contract.rs b/contracts/example/src/contract.rs index 18d04466f..d372fba50 100644 --- a/contracts/example/src/contract.rs +++ b/contracts/example/src/contract.rs @@ -2,7 +2,9 @@ use cosmwasm_std::{ entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, }; -use fields_credit_manager::example::{ExecuteMsg, InstantiateMsg, QueryMsg, StoredStringResponse}; +use fields_credit_manager::example::{ + ExecuteMsg, InstantiateMsg, QueryMsg, StoredStringResponse, UpdateItemStringResponse, +}; use crate::state::SOME_STRING; @@ -18,8 +20,12 @@ pub fn instantiate( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute(_: DepsMut, _env: Env, _: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg {} +pub fn execute(deps: DepsMut, _env: Env, _: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::UpdateItemString { + str, + } => try_update_item(deps, str), + } } #[cfg_attr(not(feature = "library"), entry_point)] @@ -35,3 +41,8 @@ fn try_get_stored_str(deps: Deps) -> StdResult { str, }) } + +fn try_update_item(deps: DepsMut, str: String) -> StdResult { + SOME_STRING.save(deps.storage, &str)?; + Ok(Response::new().add_attribute("method", "UpdateItemString")) +} diff --git a/contracts/example/src/contract_tests.rs b/contracts/example/src/contract_tests.rs index 0ce973b88..6d2020a59 100644 --- a/contracts/example/src/contract_tests.rs +++ b/contracts/example/src/contract_tests.rs @@ -1,9 +1,9 @@ use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{coins, from_binary}; -use fields_credit_manager::example::{InstantiateMsg, QueryMsg, StoredStringResponse}; +use fields_credit_manager::example::{ExecuteMsg, InstantiateMsg, QueryMsg, StoredStringResponse}; -use crate::contract::{instantiate, query}; +use crate::contract::{execute, instantiate, query}; #[test] fn test_proper_initialization() { @@ -26,3 +26,40 @@ fn test_proper_initialization() { let value: StoredStringResponse = from_binary(&res).unwrap(); assert_eq!(example_string, value.str); } + +#[test] +fn test_can_update_value() { + let mut deps = mock_dependencies(); + let info = mock_info("creator", &coins(1000, "luna")); + + let example_string = String::from("spiderman123"); + instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + InstantiateMsg { + some_string: example_string.clone(), + }, + ) + .unwrap(); + + let res = query(deps.as_ref(), mock_env(), QueryMsg::GetStoredString {}).unwrap(); + let value: StoredStringResponse = from_binary(&res).unwrap(); + assert_eq!(example_string, value.str); + + let new_str = String::from("blackwidow"); + + execute( + deps.as_mut(), + mock_env(), + info, + ExecuteMsg::UpdateItemString { + str: new_str.clone(), + }, + ) + .unwrap(); + + let res = query(deps.as_ref(), mock_env(), QueryMsg::GetStoredString {}).unwrap(); + let value: StoredStringResponse = from_binary(&res).unwrap(); + assert_eq!(new_str, value.str); +} diff --git a/packages/fields-credit-manager/src/example.rs b/packages/fields-credit-manager/src/example.rs index df5bb4a9c..65524fbfc 100644 --- a/packages/fields-credit-manager/src/example.rs +++ b/packages/fields-credit-manager/src/example.rs @@ -16,7 +16,14 @@ pub struct InstantiateMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub enum ExecuteMsg {} +pub enum ExecuteMsg { + UpdateItemString { + str: String, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct UpdateItemStringResponse {} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] From 978a93396de6650121f1e961fac9888153f7b6e7 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 6 Jun 2022 19:26:24 +0400 Subject: [PATCH 010/218] Adding execute function --- artifacts/checksums.txt | 1 - artifacts/checksums_intermediate.txt | 1 - artifacts/example.wasm | Bin 137534 -> 0 bytes contracts/example/src/contract.rs | 13 +++++- contracts/example/src/contract_tests.rs | 41 +++++++++++++++++- packages/fields-credit-manager/src/example.rs | 9 +++- 6 files changed, 58 insertions(+), 7 deletions(-) delete mode 100644 artifacts/checksums.txt delete mode 100644 artifacts/checksums_intermediate.txt delete mode 100644 artifacts/example.wasm diff --git a/artifacts/checksums.txt b/artifacts/checksums.txt deleted file mode 100644 index 632bdd07a..000000000 --- a/artifacts/checksums.txt +++ /dev/null @@ -1 +0,0 @@ -15deb82a78f868bcb954e8825a5989546ef00b49144f5dbe94d3e0f49dbc73aa example.wasm diff --git a/artifacts/checksums_intermediate.txt b/artifacts/checksums_intermediate.txt deleted file mode 100644 index cf80079db..000000000 --- a/artifacts/checksums_intermediate.txt +++ /dev/null @@ -1 +0,0 @@ -222079eb7ade43eb8d5c34391dc356e571338eca3d06407da8dff3a433852289 ./target/wasm32-unknown-unknown/release/example.wasm diff --git a/artifacts/example.wasm b/artifacts/example.wasm deleted file mode 100644 index a6710b79cf35d507ac0b889f062e26d5e6f4ae81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 137534 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%0A1QE#g2Bz-PW|!) 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εE z%s`T1Pk>wP;IAWNFX|l)hxTD~NXutb;?lSlH7W`qR-7pl8Q{dhZ5UxYaUbi{q&^a@ zB889NU%n$#fI*J$^-7;Rd1PpNb^XZbaNppSV*>-5tAj(^d$$jb4UTRc7~0f7I@~)r z(z|K2Z)kAS&>$&p2RB!*-dgRua_i`5-}dUJt-XDN>-#p3RCzwUWo&TsNbly&!_|=y z&&zl)8h%-RKEEKo?+wrC8`&|?yF2XLzGI-ey*fDBt0uzX>gd?;V0ClYI~Z1nhlhs4 zvBB!qJF1&TtD6UQFB%>j8QrvK<;tZitDPH{Z(6pvbLo=BD>kp#vbk&1mQ~9ZZ(P~C zan;7n8+&^qTT_zvS?(OpY?O$FtGi(SN1Ldo-IwDH;t<`H`MRt zxYwUx&+{X@)`w>9OZ?QI--JF4Wg7eNn(D}op}`RlIx^CGWp$)?l>TlU8?BC1cY))P z&Ap?&V}t#JLst!kyLyNFdIv|t4IAc%qg$)P)v%Y};82nQioE^QkF4-Ae%YJNm7>XL zgh}3f@-)WV`$k5fp>RuIbzn2Kp3n0(o?p&YxZFIpW1w$S?`SnC!LzO8ss5yl>d{|< z`@X?ly#sxl!`Jo=j;>f1o^(>UAp8LDlqqvJSB-BUWN;2Q*oA8J4@f@5*7G3|6mNziDW56~+QO z`UXcvq18Sz;)lj(26{JE2dY=b77~w0-+BLkB%6x z_+qx~y$Kxl^P9sjNxx%4`g?KuSgr|8s(d@Y-xQksvs@!h&ILC8C36l84#0pnz)Qvk zM!UPm2Co|K-7)Xv4I%s)w!Goo;o%K1iGi`IcO379{EGZu!7n9CwpT~D4sA{i-0|e= zPwj;KI`~!irJw&6=@a<9lHZ9F@=hW>kKf7sPT_Ydzxn)-b>2dL>3Rr|^3EQTpsGnT z?}>cbc>7aa#n+8bjjZ1^T;1F^x_*1_APjT3ySsXIuS8OJ_qmR)M7uG8ASE|Uni}WeQee}c z=7wlfX~C-BGJebXt>BlMU*46~Q3z548f$Ol-%q`gr&A3yqj9d30Aaqa7etAM&|lr{ z=5)DP5>)eNP*O@-naQZ`5II7gPo;bA5>Nw@O101fhy}8z>|BbQM?Whpz07UYeyGLVw5=zJ+|ru%sMn%4oO+ zC3Q3Mo;>~C$5ri}-8-n<%_b?r*Iu@!3pmk$Ty-%`hMxB*<;7#x#yW;{qj03}t<~U2zw~!~L~%^lc@A|t6vu@fLSyvNN{p~e3}2)vpML*b(rKKjL^^*B`Il}TxOD6A zB|}$TG&X>S6>Z)eQ%HCfNUzbcCE32Uaw)1$;W?YS6ZB8YV%DcX`Y`X1xxr*k}<64JjaP!M)6rE2y z3C~>1{j2!NM(RB>d(GPm?0P@3u{1XNOZePR!1FWwe!%a0{Jz8QNq$GJ%x$z8xuA35 z;-!fmhr+qJiW-+pTCoIRxNTb5do5S-SIup>O%)9w)C3g|3$NN*9SqTiHjNE1?C_(F zV_UXRKs+M#zH|-o+za_>o@y>$#P6~TPdn!|-Qnw`D23ZmmyKy{tcF#IFt|}DE{)QG zpsbEk`KX?uVUN=22>fdI&=_}v;n+yEUUay+9RMjgGFt83yfD0Q2*4x;c6W#8RWZm( zx(1B4M#3$_L))zYErop}2Efq`&jY=;zF+1jHr0BKrw_`>i~uHn#@5bi=dqgC|! z0e7Vj^}^S;F@F(mM^4j;<}|@T z!F7@tC|jpH`(yyRYvs3&Um_DO;r`Y9Uc>KFetm;GAhpq

RO7Bb3NgThGY!$rfu! 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 EHx-*Y#sB~S diff --git a/contracts/example/src/contract.rs b/contracts/example/src/contract.rs index 18d04466f..2aa88114a 100644 --- a/contracts/example/src/contract.rs +++ b/contracts/example/src/contract.rs @@ -18,8 +18,12 @@ pub fn instantiate( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute(_: DepsMut, _env: Env, _: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg {} +pub fn execute(deps: DepsMut, _env: Env, _: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::UpdateItemString { + str, + } => try_update_item(deps, str), + } } #[cfg_attr(not(feature = "library"), entry_point)] @@ -35,3 +39,8 @@ fn try_get_stored_str(deps: Deps) -> StdResult { str, }) } + +fn try_update_item(deps: DepsMut, str: String) -> StdResult { + SOME_STRING.save(deps.storage, &str)?; + Ok(Response::new().add_attribute("method", "UpdateItemString")) +} diff --git a/contracts/example/src/contract_tests.rs b/contracts/example/src/contract_tests.rs index 0ce973b88..6d2020a59 100644 --- a/contracts/example/src/contract_tests.rs +++ b/contracts/example/src/contract_tests.rs @@ -1,9 +1,9 @@ use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{coins, from_binary}; -use fields_credit_manager::example::{InstantiateMsg, QueryMsg, StoredStringResponse}; +use fields_credit_manager::example::{ExecuteMsg, InstantiateMsg, QueryMsg, StoredStringResponse}; -use crate::contract::{instantiate, query}; +use crate::contract::{execute, instantiate, query}; #[test] fn test_proper_initialization() { @@ -26,3 +26,40 @@ fn test_proper_initialization() { let value: StoredStringResponse = from_binary(&res).unwrap(); assert_eq!(example_string, value.str); } + +#[test] +fn test_can_update_value() { + let mut deps = mock_dependencies(); + let info = mock_info("creator", &coins(1000, "luna")); + + let example_string = String::from("spiderman123"); + instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + InstantiateMsg { + some_string: example_string.clone(), + }, + ) + .unwrap(); + + let res = query(deps.as_ref(), mock_env(), QueryMsg::GetStoredString {}).unwrap(); + let value: StoredStringResponse = from_binary(&res).unwrap(); + assert_eq!(example_string, value.str); + + let new_str = String::from("blackwidow"); + + execute( + deps.as_mut(), + mock_env(), + info, + ExecuteMsg::UpdateItemString { + str: new_str.clone(), + }, + ) + .unwrap(); + + let res = query(deps.as_ref(), mock_env(), QueryMsg::GetStoredString {}).unwrap(); + let value: StoredStringResponse = from_binary(&res).unwrap(); + assert_eq!(new_str, value.str); +} diff --git a/packages/fields-credit-manager/src/example.rs b/packages/fields-credit-manager/src/example.rs index df5bb4a9c..65524fbfc 100644 --- a/packages/fields-credit-manager/src/example.rs +++ b/packages/fields-credit-manager/src/example.rs @@ -16,7 +16,14 @@ pub struct InstantiateMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub enum ExecuteMsg {} +pub enum ExecuteMsg { + UpdateItemString { + str: String, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct UpdateItemStringResponse {} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] From a33fc466b6ecdf092d83ef9f04a21f472ec7d3f6 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 6 Jun 2022 16:21:19 +0000 Subject: [PATCH 011/218] working execute example test --- artifacts/checksums.txt | 1 + artifacts/checksums_intermediate.txt | 1 + artifacts/example.wasm | Bin 0 -> 141301 bytes contracts/example/src/contract.rs | 2 +- scripts/package.json | 2 +- scripts/tests/contract.test.ts | 23 ++++++++++++++++++++--- scripts/utils/osmosis-client.ts | 3 ++- 7 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 artifacts/checksums.txt create mode 100644 artifacts/checksums_intermediate.txt create mode 100644 artifacts/example.wasm diff --git a/artifacts/checksums.txt b/artifacts/checksums.txt new file mode 100644 index 000000000..41ee4f8ea --- /dev/null +++ b/artifacts/checksums.txt @@ -0,0 +1 @@ +6ed0c7c562272e3016075d4413c083245b6b6597d5fabc4dd18a48c1b3992031 example.wasm diff --git a/artifacts/checksums_intermediate.txt b/artifacts/checksums_intermediate.txt new file mode 100644 index 000000000..ed0169e31 --- /dev/null +++ b/artifacts/checksums_intermediate.txt @@ -0,0 +1 @@ +3cc7d46f21acb94f4107960599a1899c99b1452e801114c5805367970148c5ea target/wasm32-unknown-unknown/release/example.wasm diff --git a/artifacts/example.wasm b/artifacts/example.wasm new file mode 100644 index 0000000000000000000000000000000000000000..684fd1167b47b1966a3dfcf01431ce10d363f4d5 GIT binary patch literal 141301 zcmeFa3%Fh9S>L%Xd+&4E=dzD1+p;CuYww8W2;_;vB$kr6K}!=kb`ywUT<|cS+DyFSz;qPn2w87_Rgq%XH=o;RC=9VpEZ?vO51nxWa7OZ-l;~4 zJJncJD?U^#l>2YKed@+HUUz8!4L4>T-A!}%9f#g>+x{%q?TlZ(^=-HB&w5Sa zx4-Fzo3o<1yLtaRZ;Pt)H{5vRq3dqH;ik9Tcmws_+>)PB;cYj+6q-oF1$w|wo(FFp9u>u%qF=q=y&uIOab`0=6rZ(<~euDj(OZ#=mFU0Gi( zE$qMXYhV8Im%d^}osG@IH{NjDo8G+QRrkjIZ+yqM$87qyzGMHPcU||UH{bA%wE3$4`cDrRZJzW%KH}7?)%BiW6UtVMdi#*5wlmAg*K3|~9%>2v@ zHOjJ=nkze8fff8Wo0pxujA~Pq7At zoVVP}^|#&dru|yDx4-3WH($3q`__bSK@rqY${ic8TKVN;# z|MIQ>>$U&eKf3OEp1=C*_S~HxdUyVYeV1SHKW+U7ue@{*7C| z`Gz;XiJPmoe#3VxZ++V>ul~9_)z}Z^%UgdS|G|9gcbv%YDVC3N`=j}faeFNP@%*Rx zeK7x-{Acr@%RiC-^Ze)YNAj)zCjZy@?{V|5@=xVY=KnGOPx=3wzq{D_C;5@$&+Xlmt@TR z*3KSX<@M;b^Erj?%d4`UKETySbhYWga*+)St~UBg&j!BgRONCpH|!SFUoNKi(N1?C zO=MN)(xOvk_1uA~xU|T)Qgd9@G&I-iQ=%Is25kw-szj8fel`bkG0-Znvu~i~eBNW9 zEBb?fnP+_-=Yx7w?3*w9RZ(>q?S~3R=}yND#g#_Uao-*Kt~I(js%7SGI+ilQ#4&1$BRMvlx9b9-( zCvRv}q<8Xv@Cc??)}w<9eF!c6*X3CetMdK1TAz-E<7#GDmaIVih5T0f*l{_Rf0SK* zh@ZdaXMQS{wBRRd=EG7AXyxa#zQ9ll+zSab+1$LkVr4W*l^jrV-+V`xOG1)vYO#JM zQz5XVsPD-WauoI9xTKTi;*KHJmWy}J_o|NYBdgCvS%8&XeTb{&Vw2{h!EK?&E3=!pVM1>PCWS^g>`c&rAXwB-Wd}x6 zVD+dM6afai8>k?Gm8+Rix8CvE4sWV10OhKhx+Lp>-<`U1pq|n^y8SvEc6pG!GRwKE zrgYT-Nvodeh#%8jcNMJ-yDH;`jO$>dZ-lRUqaHO|KRibd!BWv34K9k>28{09D3Q~* zh7m0?!E$@CaTlc2QTejoqLrZuHzvX}o-^1^)qQ0KV=$fzfbX0)9Nz_6s4>$=LWleF z10%p(AMxUOk2>|aubrRPLI!Glub;njl*d@p>a)^R-(K9YtK||2 zf7EVfJ}?Jj7WK|CI_xw$Olg=et^Zp_Hw`K*k5OT{L4_Sgg{uPL;=TgY#HW z%~j}VRH!?Lgr)U;`2k&ag*caF@7gC|=%J=|6{zxIQ@rl(%OSLLg%N;odyxgOfqWPI z3Seu!+`1QxI&W5$%jf<0G3ch#7G;nfHB#;g$`k0)P-CE`(xO(aQ!M~P7W_4;`Pni2 zzi&R#MS^IOah+3F>NJ(qh?fCIfYf6qFwIj!2v{S58{o4HX+gvu5`-+>{w4sl zAprE$y#Q##N&tPHXKMrKl$APls`UH-=x4sB*mPItPGrb^C;-za0Mp>|Xi%~KgYV9{ zgQ*PXV88`so}f&JGG@Jl7S{@4MkgexxP}C&cvxNyBqqcQxI&zDz@;HSDl(VE8M+PP zj6!f*{vx~v7wG-rTr8|p!9!FfZwF^*`0QTb!#S&Q&gyow5LCGf4KP zk@^92^}0KF5l@0iJqU*vZ(>T~QV--}Q>Y5Aq2a>U%o%lke=ezHzF0r*!BV{uKB;=~ zsHO)~IG&3~cIx94ED>tdKQ1{T`O)P0^JU!SZ)|uDE zwgqgqRFR;GE!==Xo2T!orC3ucupCL~q=i$Jay@c?RI{==S|XElbKHLlChx;98L zi}Jsq@;#_$t@3k=QBd_ZC3QmAyBnG*>pw)om-VCWN~_3}w-=Y~Dy|Og4@T3de^*)k zlc;yCG_>u-)x*u|XLcWIzekm(unjp43)*s(0R*ag5Hl=(I|MOhRT=hJGi=5#Y^)3R zwDPjs%1f^Duoh>uRML7Vh`j73Yw-fELS9Oh4S6|Jb!9DHs2|D83kC9CHB;w$3D|%Y;$C&3u)G&I-xGdaAh`9UcSv{PN<-sH zprC8ZKnBq^|{=VFY^xQz+DCL$`kELD`S>%o~`- zOludrbWc`UU&dpX(R_UG7xMQEH-~Vk=(M8yee;Yk2y5q+6nn+%4$Wui(WyS`vUzW| z7X~r-U{Up8nR~NuVwJfXjJA%Oy<@bw+IlsM<7IP~v?`NK9GpwpWIM0qx?8{UN~^`1 zikYXU;z@g|5^SEdYzkq}V{xN+AGQerICh#k`e?z6H?^t+RJ(Fd| z4O457M(s}pDo&}lPPMpie!iL&D#Lue#4weFNvB{EDBIYO(G+0cWQj6`4OcD3L*&A2 zT+#JKk{TY-R*?j(-MG0G+}s>!70jf?XHSJSM}IG+xCFA!HbQ`j8et{EPlK=k*T9(_ zEVx0f4pr9%B?~j_{+5f^3}M;J#h#%}*vp1yU(9lngL_p=EWd``E=w>6yWPgfS>mY) zrT}usZ7>DiHUpX62mC=pF94-tNty-H!u0}y_CKZqSC7S=gOIvgHE<_?GJL6fEI=lUjnFzjRd8<*jweyeL>O?S&%G z;Lly4U?w58;1iMsQj+Cc;m@bg9m5G=f&Kbj#l|2osnDR~@~AL@>H3P%RA|U${TaPg zWmZL)*i^oeE4)kBhtLNlF*Hmt*GoHQu~N5Bh02<0me;FWq?7DWim2YIzONTviL?{x zZ!c~X|0wGxJrc&#js&p1KKf@4!-dcYa%dx$Vp@b8nqqyBMHQH^DTF{hAHqqGE*SMK zjEX$ZK!bv%63F>~ik4Z)Zq*-)9gs&QXdl8XBy9Q!8_A@oq87ss$rw--X&gz@ZzPRY zOVzs{-#dHfXv^N}$V0Ta<)P7H+s=Vew^~GK&;=;wE@&ta3+JeWi`rzN`w}iWh~3wG z>U;>ME@(+Ie$3c&L}KU?SJ(l48bpx3z?>%VW}E2O$OPhsbh!e&^UBO7=>iVHxI((L z7{W;KHb6N&yB*!ka`F11bpkcnVtL?rVx`8>%T}Q1%fJ=1M0du5id~KSt6DTzE;cre z&xZV*6+t04q1VJIAHZcEv~35y1_xPVh|;J;kc|__0}js6;}vKYucBC=cW{RX^1hmq zG{1r$tfklRLw|ebY!b>)q}ZFst~loFrvOh?qx@EcT+)%*r6 zTkpsG1}vH6Hy5g{roEkJI>vs8piFO?w=V_##=Hl$rQtnO<~`7;aOhu)_e_m>&!<=N zo@>Pf&><2{i}y$q71vuXt{E*f^bbx^hRQ9)qta0M^~?vi-W4tFqr#c@T(Oe(pgJU^ z5!WiONW5ptnm|JLG}LXfw5O~$o6$5lf6b^Dqp|rA-D7bC&W}e1{fY3plGYtHg&dMb zh2-suCh;YNzSNb{Z`H^R%IsbR38^{Ex;jwY72tpsht(qnn3S0t5p=-n`r*8Ou0`Gn za$zyRafP~8f8eiNN8xK#I6>29hxviWBGo2b0A}hNgZ3Ljy>NYmI<=39ACenUQ)hK? z@DH~zab~QplJx0TTjUfCh>5_ujiQt!v2Rh7LfL6a_3b^QFi%9-s4lwMWEa z8eA?O%!lWS3XkT)jX|_Sblc7vzA1B`MuEC_r5ouUJ8}?ZbIk~07VT}T%2_=S)+aB| z>D^M*IWj}S-7!;VuN|HzCl8*FLz0N{ee@}UH>f)AkRL_Sj@YKh2}Y0XhmotR+%wv; zt6aw40+2V0JKt9_-UzNd8VA7<>;krN+-eUFhXm zP=_7x)7#5s4Iq==VsBI?^fZBC^B<@vU~g6?)5oC5YMfc}7*h#2;Z@Sr0JnacOeVt= za5X0SH0~D$f-3{u32-uBOkaU>H+36uBA&yAukJ-tX^3GYyAL;$I8+vVfMpQP7XVuh zaA-k`KJewVc586@|HP=c_^wheTa4#fp7O&AFcPs<) z@b_Mmgi0EIq7V&V7gPsHJpPZIbF{h_|qYNN)-Vvr5Cc!2Uki-xs+~ z?0fZF%;su|ZS^+nqpw!It=+aT`Q=s`ytmQ25%8ulbf++=K`*XP8eu29e3>JjY@_GZHu@&7ii<-OjL=H^hD zrl;Tqp3j>4pvP4*sK)-pZ#dQmFXINSWjBL?8OUQY&{9(-F$8OAVhCG#E1;aiuU4-b z11E-{D)neOn8S3-9D3m6=qhc7G)>hXJ)C$#vPmWsMB&;%12htCtOjB$Jy@sErfuMR z)WC#wd+G-8$4bm@w%;j?NTKfZS_4^aT`}CoVUafAfswJ6wX`38*Dt; zeu3sBV^YoX78Fj}(}d6jy+EVzZL0}1w%wyeYkSMZEr3lM?-jt^UfiZ8&e8goHDgND zgDkzYxK^0f*lG!k+nP?~S~LD6Ti6?c)nY3(s_9Fz3>slUxJusfMWL;PG6nIDPeWAV z5*Kk5-tlv}3h(%$g!bIp8LUpl3<$|Dupax6)>}YH$`u;L>jzlB}Eh^e|H;E)Ob@xKAqM4 zB!+@~S{CGmzFOvLqJ>*9xP^9CuZ+B1r7 zMQ-$0jqoEc7h9wZYO|}^EJJFT2~BAh2S(GcLn-YMCPrn9?))~YO*7Bw*KNlCr>S;) z^@T5d;p=X#rY^6`+lIWaySEL`Mdst81qx_7fK{w=fftvG;mN0q*c}A7)D_^W;38i5 z!T%swXRd5;jWQD`4&W*qD85neJM^TXg+r^Sr4xOM{Ao6VD>YMjz4+baVQG^b0PPl{ zz<(@9;9+{UY^E%WVi2IBG1$sEVG`i8>d8!G_>g-Xn8| z3~qxkM`)1R_d1lGCIIt-Y@$dCgEY|flF~s<%#F^bw5d7ezO;g)avuZj-sUX=&C_MO z3HSOa)YPY1KTts4N@_6~em2=jD9dR;gqt2T9{ahNMH?Xi4%v9}uDy1kY~>0*{p|(z z59W*}m%7tRv>W(vZ5(aQELnO@F~@bE2VU_eZ>A`JBw8n{$X0tyaUT%#?5LTl(pn+*BVdIfGbnr;z~tu^ zfXUBILrCi+bIFrPcEOmt+M0JYbHf?4+(yiwpPu5Gzzs$p>O@H5D|2>j6;bcJb z3cE0NyA;wA+}%QGMh0JpZA{DW8R3a>X-1sM=7cvtWyfp%Wcy&CMzBhYkZmL0Vwh1G zZ-mZ>dRa^yCTD8cyIi3Z_NKtu3~oSFf*`<~ zj348pTsgxGJ^{|=tGGdiBSbhmYtro>?nQ7rj*>8ZfR4DzsFO5X={n&DF=)A4Vy{AR z(G20Q9Tm;iNe70Q)dV67Py|OS;zD$o)rWhp%1whV1xv68T?|k?sP))^L2r@?R|u0a z`v{_z^P?cjB=sZ>ezFT`Dx!SJ$zhK1#$+SJvl+Su9RJn~_4G&_>e-19_Q0eborZGG zUJF1LcHwW5a!&AZDCbjPSJO75gpJ^d%+p_tD`+$|MYPlCJvkJt=sAU)-MYTqfAc_s~8cjZP zMDJud(7~vlTN@H?diTu4p;oL!Xc@d_+A~X6=@+$2Do95Xs99OmkOB;aqFHh?Kwn)z z7&|M>(tBNnM>09gb?h}PA!4iVLNdZPZzzDvaAE`uO#x+4 zfX{NAf&#?&8wxNHL*i}F79eKoDZnn7r5=Hmv_hCK3KEtpVk|IUQEq|x2uEQ)o8N)? z1(;8aLD68pG|7VtoIA@Daa5Nz3ts>Gh6FZ4&jmYKHLBh+<9Lb}{9dl-xN6vIM}g$| zXL!!o)Z0)dN&LZ(7YAc3B57HPA^=0t?M&lQA5MU_AKxkju|XV9MhscDG~5AiFWZ~vnR1EMX06oXiUcp z%S^1{(TtV3#33IzEizO*yod$-uN)_Pe%V*&?;873z(TJs!-T&_ysAVR%Qjex`>6EP zx=Me=F^}WVyXah{JpSz$og7h395qzCEC=Vh^}XK>7D^`s3-J~;;*p(>`g`S?!;jFPgbcdHZ`4);pD#OKNk>@W0ZXY*pV>~wom)A*1wTy^pqt06@)_}2~y?vr;xepxvmaAx4WV0%Dr zSUnmXkbCP?uk3tSyOwKr4)jN9{QKDEZnhoz{~#xxPWu3!nwuZ7zn6{5gRfKnY#bMZ zOSI+3zAD}GTb1}jc>1B@LSp{#;IK)Iv#9@Mk(csn4D8cyz=FT!aIOQ2m`RrwbGjYq zFrZRQb>6@h1jjh)ue+5(hhCX2_-^j@`h@Q0eAm0ZeLY{7h5>F(wzkulFYnx?VGdrR z!A;})j8QU1d0aeId{m5aT6HITn{V1b?%rzeV@wuUbb$@G*H5oGoOZO$@aOAJf5_CD zp{)tdd<}3G^3*FCk|2t6R}huMeSo_U$o{81&jr~w_*HShb1JG+TOFc(o!N$~ZQ+Zm zI(rL+vvlf@edlpS9B@U*zz%+>qIg&Ky@+?^e%V|6y4uavPCuLGYMCe0yn?0__s!iC zB;ZkyBnS8pzlV$u&*(;6c<>n~HNeM30Ct22d#5wGKp$%(w$i5HV!yth2I^hZCq%41 z{9bMzxQ8Y`OSr?Yr^&X5%VM%+Ot#b%z0p?26wCVYG)0`_nj)K0X;Qk1so>&vNS~&% zlc#&wW8-HheW#f?21hf&`IrXhsJ>N$6Q*r1cdnkK!UUlVR*hDPj>1WM*3)?U%W@ij znZqDJy*-4;N4Si*oQPTZy$T;6(rM%fK`CnG_tNeW4iXSqOag*1CV{8|KUf0NjI^ot z3D#X$BWFo}wa4CZ@Y<9K&@(KBkJKA=UQ8>`jd&do zgNF)rw`Ig)YnY0xsUK$qMnevZpLieFPw>ZB_yKI=FLK`6-2){~Iq%rN%7Lm+wq*8J zvq`Q>8BFo1UlZwWs&c>eKIc9e@eA&EJu!$ z+%0mo!*s7_9j4=eub!2_A~0%vE}n3wX`dqLXy;a)d0$m6xJew*6v;+} zZ+-j@1(ba&d;5$m6{chFDo#Ndb&3e_u0h_H^iqU2(=vvznWIU0V*(zeBOg)2yYNe| z%wEj&iMsJ-1eYA<&{=wbB1TDAbN%3Zg+GdHX1T65qlBl6$BR?Gn5%1iC7yW|PnN~+ z9RdAB9{uKn-_aG^9SO2r>|i|nENekY#u)7HgxTK`qlW(U2Qf9cyXZeM9TX)Y2GRp& zItNAr?~uV4a%Zn8KWcK>_k>HfB_I4D!U+g{1 ze@e13%{B4-IGoLW*$vfJ2^&$LeE0F}C0X_=5UE!`!VTNFmx9Wy6&Q;TU-%Ix=2-f1{RD>HSDthk+czVn^ zXs9oknpcRer*#xgDDF_%0R3L~+jJl6=Jz{vAM3_FI-+`)4!2R*rB3#c9gxFJ@^6^jA|pdBzz#RT$` zBL5<6aSd8VOw8HI;|QOT9Ri9Z6!6a&Nl4@>2xG2QQkve-#2BmMi^a9#)WXSJt0CNA zWR~q}^!^$BiQ{IQR z2ZIg8p4I>K{$U%JrH+Y?&^mApH1fREQQDE5U{6jwgJ)n}kS$~484`OWQ87+UXEV*} z&2g6-T_}yfCf^8`M*T=U^>NzByb+hYI@Z3r9(9Sw=uhe7;E>}ifXXVL+p99`6`0!m z4oN>&bEul7AeoL%1?c=r4IDHv1*DS@jUfI3NoRDxj7(V%agfh2Kzx)sT0A<>YY&Ap zVsOvLvqtCh5H*R1j=Up!T-RH771tWgIPk9MxFPW~Iy6I)Aay#VN#z>YG^Cm3V&|{} zU3VvR6`DlcW3cHIuH?0|^2#pHq?C&LOsAl=DvL-UkcO+s^pf+Ih@p%hPTENuB&!bf z#+et0W0s=#mZD@32sM{*^Gbdn(K}{=0+@s-O zoWiMWHpJLjA#wW2k=f6~^El%}Q?n8OqyxbUZIi#pQ$P}=?1f>6Fs;ZPt63NlT|=T^ zVVMsHVca`S|6? z@^ck&jr_b`X}%_~Bk;m3EkU%+km7ZW3@P1xz|M&m6!Jv_O1y9p6K1BWmb&hch|6%0 z)hr9J(;Up>mTGaE5-%v%ku0$SRaJKCCI_S))t_!BAm~}lg++SHMI1yT1Tr<5xjGFSEZ^-vTw>Qo!KZi-mm0+tyUK@|TeWm)@rZtzoV`;7 zD`EiK*Kk5}zn@Mez1@Z?-j84FK1q!yVK+~|@3@9@BKq>*XZ-iR;y``Rk>lWXwlu9G z57SO0>(JdX?&64>?jGUJQbpKm$#mRLIR4r`dCX79EYUn+EaZbJk1`aF;lTj4jv76MoPVZYc~lv>-1?If8FvPGwEuQLg72 zxkmLdAf#c{u$B7$?>&yIh?}DYVtMdYT;0!}LOGFu199&sP+f}*NBku5(9|$Iv^*^D z1*3VA_T^!G0>;miCjj^3@5h~|HLMR)IUBr23COv7vS?^aqoaln9O(G$7CuBYBHU6G9mXh4oxIqF$vqnr=Nfp?&Jw{n1BMlwHE801PUiw zjWOkug_qOVbiz+QG4TYLQ1WBrCt9}$3&Yd!eI~*8X{{p=8fkV<1LYG)*MKrZHk4H& z2KmfNNI$(A($pASY`sLzq&Uw9@^0bgTyt-3=JXu{=d?+-aDVylEX7Vj3aLpxUA25XyaFNl1Wm zPCZFH&Pdt4P(LGmXBT6E*VP}!+^Ro+H*54+eBM5%y5zRVbeW|rEI@hEnJ50CCE%xB z5>_Eg=fo1_s{Q=HV3)>uh(S^Ms2#5;<{~%bAHz<(R$^E>Ymh5!pkcf`!tE$FHTcJ= zMY$-CmEjlQa-y+dxn?*4VHOc+6!EHZ+onh_?j za`u}iXkx7qv?2DDl|TbTlGP?v3CR#f5DA~9`JE@1wH^Ngu=$rh)H=c zx{!xq1YPWhRxL+WYXr@Q5#)>m*OL&-okDIjy*%0+_`7qMhXK?@*^`fVgt~;`iUH)4 z<9Z9_P4RZhhvAaJq70mIVu!07j2y*L2Qm;LhI4C{N97jv5tRu=6TQ=C>N|s_0#BmM zVP(F+X|^z&mKtik4v)^9=48EYDx8>*g5Fwv!PG*rv(}2q#TeNS$z-E3#wB+!>ai;# zF4CkI&?_>lO+I47idNWEAc>+mtZ3FAN@r=7&yog2fKeuj!r?Gk;bku9D7WK_jKtLp^RXq;dp+m10bYSr0nxC zyT$?>&Q)PRo{x>&EB7?Tqryu^ixWoV;@F7nO&XCO5K}GB#)wol6ZIe?a?5&*NYZXh z7?F!-V?;Wwhn7M{WE$!==M;}Komj%avoRu-XAF?9(};BTF4B|F#;J50k=_LaS%eNY zt=^0htBlAtQ3$ZOel#1spD+Bf5vk+nED-i}LNE>vuP`E&C`4c!8<9GkFHMZ@ zvue&)VBpb)Dn9cUGyk%wv9XI=NhJX*U3 z>Rmi30DWNWh(|e=8W^)7z7oc`NKXx8Mb&8MqtS{8@Yu*GCtzGiK_mScLkGrEr+~3s z>8=^>B6cTurLeJ-{!G_Ij!eQhg^oybS+S?Z3y&G(pue01ZbdCc zQK{UDkMG@d=cvbb2d=8gZ^f?qL;f^^=Al?KXIlZJ;LS~%NX*k6*K4$`yAuM7>8O zrktu^fhoIbDk~4u8QPN(WGhY3hAY_-rY~$rUT=aUV@uZ=QL%lc``~!dfDg?9L>9vC zBSovwsd|o1xo^y?xKdFKl6iTME-!dRdAYeF4%R^vKJURU44x?nOHHeM6s>bUEMTvc zTvo)6-Lp{4#Fro;C%L0Ei4nHrG~pEuIpJtj{i-Ka;G+egf=X~j7&|W#Ik7f86*=Mj z7?YD~2RWU(zR782eK@$rQuTb$hQ2t%a*2D8!$RFwJ+OwSA}2BYUUbX?q~RuVqHZm6 z(YHss{jV{(%3QuxjM$--z3G0KCSL=D0@ne8H{0@NnnQ{*&L@m+G(0Zj_YBXZ(H zWe0}yiJWHWdX!i5G$0sK8W`#!G)rbb9w!*i_$e?PlT(|)X{I43hG%jz|H5n}gdXZ` z$;r_Ua$Thq3``&H`*KZ18CdmtIJcJ=o8TX_nlPoc>e)!tG=ym$izp{R;Zho-tiIYkg_E^3c8PrC%6jiG)_) zML;rvq8)KseZAA_udbe$sb8nn-@{-5hQGbRF#>It>W-Ls5UxJKzL~c1_`sV2lf1>o zm2xNRE2_DOr+)FJ#l1Pdg9F8#)xzGxhpTTj|HZvVa_+r<{JR_$FFpQ|yY~9C zaT08P@w$sCQT0Q7!U@65p)idtZHoaIB@h#e3=Y)kp4;2Ww&P zCGTb2=N!3b?=St>yMO#cKiN6lr@=)geB#*8|H#ij@E6(MyYyW=nj2JuJLut0m`|?o zuEBqnXrk~vSIHq%{}o8IQ?!0>UT-`&xK}FzQVWbUNIwheXSCS%L{eNq_N0!;{k*y( z8c5^2+I{`$L|=LIRSh1hcXFC;Kiwi8?qzoU`cwZAS;OEoC>GzNKTz`>$Ag3aFCXf` zeG0v09(^%{SuoHXNi1e?#LhRxFzT5N9VL2oD%$!P?Y`~;oj6i+uO!;%zYnzfEE{Ll zoYFUD&>0trefvWlvum(#?SYDJ6DMG^g`Rc1ype)DNm6hsWZje`L1%>&Y~Gk9NWeHI z0jc4#QW_YKBqlI9Q+p@{AqB-Gk%B$$hKMgqLD?lq3KpxRApA4Uby@>d&(SGTxRHW! zC008*NS7ZEQqIyO1@R9HjChSFrJ%}3(_<+p2QyOesdO}xaf$(021<`Y=WS$Q3O}tn zVuU5C%}cClAt1av-!MX3CuW404`Ugq+93l^UEhpwW&MzVor85LMo6#huUz7OWui#w zt2)Gsd>3dGV$em{loKOtN1Pa;-iZ;$#KzSVBlK%ALUTOqq<31Vg2DqN7eAX4Vs8{f zGgiYK8%W~<9_t%Y(N;rRj%y(OSb{X#h6Jr4O(S1{+D2#>Fx|oXa!^%dW1MZs!J6Bc z9L6{sXx{(7Uw|ZPoe?(aX=aCJzs~}kzgomWP7o6piV$;pBy+&(k7ypn37Xe))4VYE z0AJ9-4y<$W3BL-(O{!~sufj^Y(2Ol0(W=dr^|kxTQ#Bjfk8~MPqgq9y^I#=5Ck88f zEe4CJ5rh3}4R*yq6zhz6m;{2z4**Zz&41%`*`781ZbS4xoYT9Nv1bj~o z-Ya>pA%yOX_PKGad4l)SjN$BJ83Oc_wM;#?mMOv*1DcZFNl$>DrQ-Jp&_x62*`}ZM zwVMFFve}qJY|dkuP9?qD3yEy8?#=g~ICLd;$-h?jj(IJDK(ZlIrT|7F^EzqN4O=ck zkZ7I6k-HV^p!H0Kz*rFA2tkl^oIlu1-OW~~uD?oTNGne!T5078uaja8QOy981qOlUJVWbTwq6RE zKSB+8*JW0fpv$7{pbom~fL@-;ZbD0bEx6(9ArUaLu$F{i7bFX7$qiN)(3SwZgaCU< zrH2MA7Y$&S{B(td72l#}rI#jGnga-4>7J}=uQXTfO53Qi5l}yIj}VZpf|sczCRSiU zpBc+Y_%g(Tq)w2lb`31}T}`J^Cn%Dzz(;D%f7Vza;xPdYj908jgpnvpM<$z3R)8n6 zc0~^kuxfb%xOOs~G|ZP!C09v2L9t#x$A)L!X?TK=UkJ9&cEuA`G#UKBUf*^a(tOlr z0ddgbW6|LUb1`UH*=KZhJ+2xYJ7DgOw@XejTC*qY*YujKtUMTeZ8VC8W-B(FT|b;F zB}(f4msfTVxKz?et43Sbz#8q-eYCIHoZJCVk{v;C5w-w~2sM4en}#BK#xf)pIb#_( z$QjEZjm}i2^~Pz@S%-D5dC0~|JVG{}E7?ewNjCO1b4{O?lT~!em#XKm0C`({G^|CU z7?QAb0=?I;CmQsYYfH08=$%&lpb6Aq*#i$76vRWTwzY*J08!6IL#j6kSTEa|C$Z+TK^FLHJWd-O zQes3?!}(y$&kc+Di6FV8-#rg3MvL0~v*%C@vr`s1pAkrFRsWF&(H}Hy2jVyr-9M!o z^KACEY-bI+cLZCrs;8iP&VwaX{0lWX*9;=K$mw~8>Yn{T*V}T-L+_Sh-1WwGvtz*d zfHQFrWuQtkd#@UpI-8lP?(;LVW>l6AAJoA3JS6yy)*;GY4{#=%2&nvzuB&hDoe?VJ z<4REZA$NW@P*L4Ap%Mgn4l>{B<_35}Y(C6Y8wxH`q=1JRgF zlrLUsFVC9~YPLFc{Z*R}s$WHwgLPN>GJRpmr45ACZ8dkG4*W{{4-`2ta$SLmL!|A% zlfJB0+HHu5R+7H#*V32Kenp+5IRWEBQRg{qh<%syB5+k*NIUk-!Tj}&uX88|Xt+r1 z)L;2Jr@iAjuG54KL$tv{~rMarCq4|V0N zoA}wL2@cZ6_M(Cb&WQJHHYL3Od4u3=@LqM-#QSK{fIXQZuV-e>sMN&S0(P?Q8h~{$ z>zTAZ?P#B=OlXGCYOAn@9ys|Vw7$tF;502yJQ4i8zxrjR(eL< zZH5=4BPT(#?e;941ngXi>8W;XyPchrKqHZpV5V^r z%&cJ^{+`Uknbt`#Gie^8s)kalR?f;v0Mi@j1Py!KWu}jwGK<9XxB}D4Y5b&ce0wr1 z2^zKBUyL8CK#BY*8;k*jP2S6=*`X{Us2Frr&1=W1d{><0ZsDEn4<0CC(H&( zlYp+;0rXSXU$xh%`gWmLW%A4@WI&#o7+G{%aYpUm3h3dPaS>7?sjh8u92^Pgv|^yE zNW0~k2Cr7yEl*yDq-pibfuc$;$!wI9`>V`Mh4rKAw>Nmd11liQ`d9Rzs|P{3^&@&P zr3XQ{_0KgWp7ml$I3 z&)6OlJYyN=e5NuwDrEL0Hy0aY1Ah#e^dOM?Caw>THl?jGlFhFO5WxqQ_{yUAislf* zuQ-B}4A99kHqTcT6->-}&Z`!w3}SPUT0Kv#s(iFMCY;5nB3CCCwA_DoXV(7#{~*z# z#NWJB-p`hm$$$1`>hE*}fjpIpAP6R#cE0d;{<-{}y_S^`KQi`r;-q|*tZd9_7I3^M zpduqQ%UleUZbGvfgHe$e(A}^P2jqa6oSqC<3`n6_bL=mJCmPDIpFEv5jXB>!yX$lz zi%l^q{PJyRR@uaoFg$w=&ZwvSf;OuaOSeX7)-Ul6K4`>f8k`j35twyGg+VMOIhmM# zcpc_b%#+foA)dCyWA~r~A zKu8Kd-niy>qXN$tymM_z6E{5NT93J5ifd>#erA3AOef78YJp_pNy5X~z+MGxuwZS# zA(E+i!{fl~g@o5Gt8bPw8}W1`(;3@?PR>{c`Z!aW=17{uPGI!w+=>gI+vGMJu2Sm* z8{4>*je@{9ZbiDmDPQW9MOVbMsH{J$J;hpG${r&Ja~YYL6#KfUhizQrIIA0na;c`* z5&4-m&zluc1B%DdUZU1Noy?8Xb^Q;|@VT){@zdUVQarZs%6vAD3%n9+uWBDkr(>%w z(eoSvS2VQ#d82iq$l1$i6e>MrrD zr#|M@)FfUiXEh|H5lq78G{#s2uQs!Q6waaQ|e-_MQ%HIW;}%CUA|2MxtZjEJ%*l#P?`ZH6VgH;HnzK?x{fE3^gro!f}0p zR27?#WNRPIOXT95MRBVEDRmRos!jt@qgz0X&gWSh)Vc+8nT9FXgdy|n1*6*DlII=+cp6JsOvJg}C{j%;I zJsS5rbst$F^4THfyM)$LjCN<}T#kyK#=@IYf(w6( ze42yyopc&|ma908&8C7*W8=DoLvZQS*e`Kkt_$FB zR5}xX68+Hrvc|+C?oTQxO2Fei!@0)omIMu?}7ZQ3XspG~C)(uv>= zLEfU>#ewo>pD)3V=3_V^Z+omX)mr>Rr!#)+q&NW9O4R29*qsLkUzJ2IEjv-pjC=02 zVX<0irOO0<2z>rRP)D5EQd9?SeOSi|=vD#b;OU-x{FiC~ps`ZRTn+Qe8UEl`$= zChub>jSZs}E5}+OsMfr3+cF%S51ZAnbu-SHt>w4R04VI13@MawlfRE<4L)G6Z7(j{ zRUYHza&cpDy6X+qLfK_pBjJw9Qc#3jI8SI<|IU3(2|qsIAs-M{hFT}dOyUGiRbSLc zCmw)~MScIo1Fb+5uj2?EdPv&{>)~Z`=IUr>G#`(gc{UmvE%*_&2&bm4(W1V};nBFh zhv8}xI+aWNL>}cb${C@PsuI#6#70tyEcH`TX;w&IK}TyFBay&zy++EGw>3s%6kA1T znCX}du$AJ-Pa~!+TGs3g1{s}6<08cX=;kEu3VYpDERdn!$zP8b4ZmbcO8#01`bz$4 zFsQG=XjVU)R;N4C!}}6dz-&iC^~c&JDeA*K6SB~GTF6340J69_fLu15=LtUGp}$FA5j)6qmxq!u$q4RnY#<9>j3(6!xe`Y<#4S_K9JmKA z-T1V#u9imG%6vn7s6bp6rI94fx&k?LYKs;I^2ctXf^rP;+MmNV?nIJ{bXcn!B0#LD z7kMc~-ED56TuUFV(d?*%rD}prvwB5tp~*+st8iu5s&WgNjmP1z#IYt0hn4CNM&eUk zaqvqMZa6l~mYh)BB94I133P|xdyuQL(eml&jPfupT-diF1W2SrCrxf4$pn(pBBGRA z2yU0@t=`imxrMfIL;n?HTo!j@|M|RPPc*oU$Y%K|yIqtQ8L?=h8s$xnRPL@46qH=Y zUt{1eA7Y>zfs%f<7gz5pUL&zwii_o>_@T}oLry^>;?GfYvd(K@{&7mb9h8Q#%=8RS z*V#SMO{3E|tJn^YC|ak!QpwimYT8PC<0L15(xItAudELg%AfRG9NNpw|&c3LP0l{_xOIuB!{&L2R)gbiXnq<88r{Q_qa%4@ec~`jyErlEv3i6mVStl%rvbF)`tZsTGBsuWWR12#km0iA1_x#t zIV=%f)@Aq$Y#XQvf;<7X?Gw)RspR;HdLwCbc@>Y>f@~UX3t8g^eE2nVy)Nj2%Y%z6 zrRxlh33Z=?uPCPQg(gD?+6drTm3TAFmY*ur*yOQQ(oRiRnn7}iXRuM({hZOw;&=hI z?6i}f=ul!b21#KkXnLe$&~}2zMUMA_ja@zWIS!H7_;>*&{VK&;f>x`J7f5C!=GNP; zgDh7zDel_CM@`$(0QlI1e(a;>SBI-o+Aw+oa_gKxIAte$YR-0aIfEHtmbQP#nuPfD zDPPvPPM859u?>07wuOgPqep(oF)OWx(IpegnTMb<+o@wd5{4o=L$Zz1>(((#1JbXx z3R>o*)}Zi7QraSArD0Wqd2qqhc4D+YD)w+d9DuZN9v@`fwurXtrV z5Y``^8!9aD9c6)jK2o8v5{5Bq8NsIxmy6d39nrrGv5wfgwN@d8%1KIIMQu(<=Z(4V z4sBDSkjMw32~*w{T!)2i@1<}jXOrIRtgu{4!b>;4XEd!3yQz5%o|D%gZ>X7!NbJCa5KvUz%*s_qyJ(BCg33?sULG}oSQtXL; zItZGr@5^YOwQ_Bs46pTpG;~OgZ_n=a32lOX8EVvnQ!KVQvX{w>q~AxrfmYemw&99k)k8lL*fg+>bx9rZp2M1vu!`PP`~m34NEkURdenv3w21Q+8$@A6(oHS8HX7-=nP4W z?S-$!0_XPQyU*flvERBH5rFV>gbOofV}qZ{`|qD(PqE2r1***G6TfmH{!~7i;;Wz; z`e%Lk@EtY#`h#DaEEy-ftPl>c&M%z7x|PBzB-hvCBh@YWb`p#UE{Jl zs9UXG{W*s0KQvCY-a))$BB=X5#B=>orz`;ffRo-lqZ<07H-JnudZpfwn>Rrfd4sdA z^E&O0vUb3c(R1;u>g~G?I2zm)@n~@qbqmGsY*dYM8yrVLmF!iSN8q-+3z?_r1J6Amh%W zXT(7YbRn0Gb}a;zj*}qkD@1d6F_cw+uIf*{AHeAkMd~|2f%5k6pkWl3z(r8XLo~o9 zXD@&$f(U?l#M3-r$a`W}yOW|GLd^OK>|je5ayd z`{F{s!S~?CsdgciAItmwtF3YB}`?=Ab%u#BLHSuNXXVP z5Mx}DK>AcIeyTNEdc19E_qto+@Xi`7eXMQi73*%vM|@72-G`q=OFntRiHTQg(jFav z;0nm-YpS?{^$}LV?lT{4U`N@2-C!-)!G9>NSCdfmnSql=e7tRQ{d0Ga{J5nwx+Bk` zB_f1mIM?*OdhWuC$+>@|nR`3VvzWUb8tdxk(J!hW`yR%SNjQJBZSz&lx}6c{ZF-&5 z(!FgpbIP33n=efyz2$e#`BvAV^}jR)$q)5rkR z(rs(5K-*0$9s6e${jjUHw+s){<3Fg5HH5!^snD5%a}>PH$f_!=HZL#ZD%gNNDqs28 z%ee9Y;RY0uW48asN!FB9LEbf=vNqB1?b-Uo52;&7LL=QqbKLE4tMNf=_1S#Tx{&(n zE#x*QoS4^%Nee`qNyyl8+hY^>3@v{CX>iRngOrs?y|~ z#HTc;V|pP_&&lY0ggf=+X^U7@{XsPqNBpiB(EskUPH36}Y|QLz>peOz_t)@by}XVa+o2#tQ?R3n{4 zo=;N9ODdK@rTb4clF3RBHkHI3sPr^~tKP82@Fy!BNtLKFcv^(0==uIXT0w}QINC(4 zH9R3aPGU71DIdHCIcL-uYt?Mr$jV{}YsHNur^e zgmx_VsM`)uXxl0iLpjJV_QS+WU>N$nTUVm_;0- zJq-TZrcgfk@BY=3?<^J-F|Tj2EBIPYyJ5jC#XJ0@$j@>Q-wGs5iB`0kjMwB~y_4i_ z#Y3aAUc8mBl+R+U^$sYXM6q|EOn|sw2q$x?Ql~vAcr=$qS9flAIW*LR^iu3S%qQrJ zz0*gA(u%z;oYB6#2U&f0ekIX68ejc~e+IOFKj(2?e}?NC43KGcIIl7#tBdg}*YdNS z&%a?b>zEM5zO*yyUjPVL!jb*}414%lyTe8Q7v)2V^@Z?Pt*@S($QQ}U>d*qMv3AI{ zgD@<#q&h0~1vUk|lv>A**RhkIs@^jI!GK;J zJOFK>G*{)tW|I79uG$RJ-ah^W@vcERgl&1j;Zf+A0Sv_mhc|wTt0@fzI6T1o)QX2Y zxs2^J-INygBPEfq-r@+QoYBi=H0X@nbix+k4U!CJ1MGYiaF_LQrnM77A?ik&(N2W{ z9^?YIZo#6cuHrr!EBm0EMzi7CB^Fml1BN+;cHrXDHL{iM*P(RE-R#&!CCale!Xh8k zDG=N*mFs~mDvPEY-;m(PpOIkXKBE)3UTOHH;bBzMmy?A5xZeXZ?j*zXkjDiw(5Pco zk!6WAz?HNPg>8gI=tmw1(*dpr(*d?cWBELNEU-=>sh{D-ba47QK|AtaP^YDxGRym) z;cTcGDodePQV+WXK)*W>D^g7`_xG{11A>HbV0MJ?< zpf2+sPdeNL^&?=<_K5f`L@mcB?>SKO*yKHfeqwwtDDFK<5b$BnT?wdESF4iPr917= zsa8P|SbiU%27g#Bx#!dJL567+MC4jI`SUeq5r;<#jU>*Mcor+;$(S!~fiFGH$jqqf z-{x0I^R@jDS|KF|bVe_V{2x4#bCW=k1}#Dmb2UAPu;LTWI?l0zH`W5DnWUzexMhPS z4Yx!Q4J>K6CG2y>ivK{T$b%5qTJKe>4HWSf$7-)&`t@;U5nl zBa`VV?OXF!Uw)5A;crJ{2U@g zY;K>75MBf!!kFI*V$T*e?k$UA5;e4FI!mv$Yc?C2#Rgw38Zeq;<=A3i#p~Y>yZ~_s zN-$nH(!bpM`^F2fiBhmok>S)R4m%3IqF06E2{jDWNz^bA3t?(bsI)Xn!>zDhNu%%| zyv-*s%adohETd|==m-ECEqd=C(V|zTT@A*q6A9FgZ@6(xLbSSun_Nw^8q=dr;~>xYZ_2uH5?N3z zhKU+rXC03SKiXx^;scDt-0K=ntV2|dUe_p1tzLKkKNY#4l!%WXorjiOuZDH<_nEhnJ7LY*2z zna0Hn94Ku8?vq;nHZmT-xpILm{}>v%zRiy z6U5m8_t*q*0eK{f71)Rb4xj>Wc9S9!+2E71*MiUUUya)1l|4yg4^1Vl>{wHo#Dx506*$bd_RBQ>CY$<&uWV$|>O1Yzv(wY?{zCP3Ayxjxuq2EFE!K zB#VN;DuO>dgLSU5!TPh;@o8{}YNfAfh{eXgv|bm`=LNxN{nO#jTK5Hmc`03aXpR@! z7DZ1UvH>F$+Zwz*S4@~wPYWfLqj?b-!fyA4)mEJ;Sp-{#gBSU%;u3F{s+sYDlBG*B zq=i1i%oRJ;aiF9i0k|S21}i^c9k>2AtQh4I@`SeDqNVLun-ybp2^EDjf6JyD&BHgS?Bmmi#rT*Ec8Dk|2vnm%7UogK*s@s({R z&v(gnAciKnni-X`z-3zC?#cy@+jfDcQnQSCR_ozj=trT7vuVBfy#RKa-m_k_S}%Pj z9n_o!`Q*L6NKdy6OH?d?Kg+@dHtD?pNc~GP&J0zvFUhv*`r>+U+oArb5QY(U8Oaod zX!YQB;o8K&g#@8&#W&gYfw(l9Z*y1bK%KT-X(&CWp^*w)O8vmxIG&UbrwhnlBS zxLV>Aj3y(0L4VluQ6H*tmdV$0r1%=m&(|sgLJEE?JZYqc1r7Hqz7QC*52`|u;blp@ zR9~O}BbrGvz)_J>7GsK<7*`TrX<5?L_OEwps=8`w3DuaI4)}brQ`5JqzWAvL0G7A2 zu`>?R5;|-O@>wQ2*$^2I0N92^ccn6A&NUXFZX6)G!MsyBJTsp%%XOLiEE8YG5+7R7 zid$+fh}6x;l?KI##4$h1@dZmsDfI0FbCJ_l*2*uKUQM8tC4(fHmVTl3>1U-&rJq|Jf`+)r7%yH8`oP{b4A?EhqYaQok2(kXux0l7gdaX0<}ha^u{S4#yjuPa zN16@6qNMORyW;R79BM&*8d$=5@P45cG+dn|XnHI8P~5hB$ncv1u2l6h#QRpzEYy54 zjdzP7X!!EeOR{+h8qQUw_0-kI#2R`kJ14^DV7J&nuN5>-jAX9mb7!WOadIRr@Ja^E z3=hNv(_iwHrZMt6Vq@7sYV zIgRzC1vsqmanPrjZTG}9|!qKeA*8MMLe%#Ko*WX&N5ltYm4TwXt&9T=Wp zCM`&iQ?YLxZK_LwXVXGa7K%`>y4=~VFXuTtRMf03ctM*}^$8@NZr*I^WR;0@1VRWh; zX*L=`0?M-IpXTwMn!l#2M9+d;fs!==pB3+;b2`3)jf4;9u?8B(=$x3v`BGZ$kt$c6 zua)<#+WGJe_nX9ayavGK+YA9zRXt(?=TapY1@~zQ&h>*G!V0INUgq1Y=cwK-2rGWb zV-igYEFRg^%8{M7o{^omVq{Z;{bx0@DMz$uWaq`mIJ&KoIsR?}wF9kv3OC_gI>Okj zHm7~t^@7cp=5N8USmd}3{w4v<$1{gyPM+FV%}3gm*j<_?6U)A8?$99^cJhd8(?(hc zfQzHLf*r)6XAm1k4u{V9(V1mlL|z3=hD{Zf(NT0CmmNBsw5&K+(kP*@ch~_-i*hQy zVxei}S5~LjMl`q$B<5Ac4@RYe!8rD*O_gQwhwlRmb-wuAMy*aAB()lykrh%0D1Fq^ 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 G{^VcO0K97e literal 0 HcmV?d00001 diff --git a/contracts/example/src/contract.rs b/contracts/example/src/contract.rs index d372fba50..e0ea009a8 100644 --- a/contracts/example/src/contract.rs +++ b/contracts/example/src/contract.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{ }; use fields_credit_manager::example::{ - ExecuteMsg, InstantiateMsg, QueryMsg, StoredStringResponse, UpdateItemStringResponse, + ExecuteMsg, InstantiateMsg, QueryMsg, StoredStringResponse, }; use crate::state::SOME_STRING; diff --git a/scripts/package.json b/scripts/package.json index fa18c7218..5927736fe 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -26,4 +26,4 @@ "prettier": "2.6.2", "ts-node": "^10.5.0" } -} +} \ No newline at end of file diff --git a/scripts/tests/contract.test.ts b/scripts/tests/contract.test.ts index 8fff80343..496dfc861 100644 --- a/scripts/tests/contract.test.ts +++ b/scripts/tests/contract.test.ts @@ -1,8 +1,9 @@ +import { MsgExecuteContractEncodeObject } from "@cosmjs/cosmwasm-stargate"; import { toUtf8 } from "@cosmjs/encoding"; import { Uint53 } from "@cosmjs/math"; import { assertIsDeliverTxSuccess, SigningStargateClient } from '@cosmjs/stargate'; import { findAttribute, parseRawLog } from '@cosmjs/stargate/build/logs'; -import { MsgInstantiateContract, MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'; +import { MsgExecuteContract, MsgInstantiateContract, MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'; import fs from 'fs'; import Long from "long"; import path from 'path'; @@ -76,16 +77,32 @@ describe('example contract', () => { expect(contractAddr).toBeDefined(); }); - test.skip('can save item', async () => { + test('can save item', async () => { const queryClient = await getQueryClient(); const beforeRes: { str: string } = await queryClient.queryContractSmart(contractAddr, { get_stored_string: {} }) expect(beforeRes.str).toBe(INSTANTIATE_STR) const updatedString = "spiderman123" + const executeContractMsg: MsgExecuteContractEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: testWallet1.address, + contract: contractAddr, + msg: toUtf8(JSON.stringify({ "update_item_string": { str: updatedString } })), + funds: [], + }), + }; + + const execResult = await client.signAndBroadcast( + testWallet1.address, + [executeContractMsg], + networks[Network.OSMOSIS].defaultSendFee, + ); + assertIsDeliverTxSuccess(execResult); + const afterRes: { str: string } = await queryClient.queryContractSmart(contractAddr, { get_stored_string: {} }) expect(afterRes.str).toBe(updatedString) - console.log('afterRes', afterRes) queryClient.disconnect() }); diff --git a/scripts/utils/osmosis-client.ts b/scripts/utils/osmosis-client.ts index e126d7a53..08afa99b4 100644 --- a/scripts/utils/osmosis-client.ts +++ b/scripts/utils/osmosis-client.ts @@ -2,7 +2,7 @@ import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'; import { Slip10RawIndex } from '@cosmjs/crypto'; import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; import { SigningStargateClient } from '@cosmjs/stargate'; -import { MsgInstantiateContract, MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'; +import { MsgExecuteContract, MsgInstantiateContract, MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'; import { getSigningOsmosisClient } from 'osmojs'; import { Network, networks } from './config'; import { walletDataType } from './test-wallets'; @@ -31,6 +31,7 @@ export const getOsmosisClient: ClientGetter = async (wallet) => { client.registry.register('/cosmwasm.wasm.v1.MsgStoreCode', MsgStoreCode); client.registry.register('/cosmwasm.wasm.v1.MsgInstantiateContract', MsgInstantiateContract); + client.registry.register('/cosmwasm.wasm.v1.MsgExecuteContract', MsgExecuteContract); return client; }; From cf76b423ecad6a69b91cd5ea2dea54caa897a90d Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 22 Jun 2022 14:28:05 +0200 Subject: [PATCH 012/218] Adding allow-list for vaults and assets --- Cargo.lock | 87 +- Cargo.toml | 2 +- README.md | 2 +- artifacts/checksums.txt | 2 +- artifacts/checksums_intermediate.txt | 2 +- artifacts/credit_manager.wasm | Bin 0 -> 168520 bytes artifacts/example.wasm | Bin 141301 -> 0 bytes .../{example => credit-manager}/Cargo.toml | 7 +- contracts/credit-manager/src/contract.rs | 82 + .../credit-manager/src/instantiate_tests.rs | 108 + .../{example => credit-manager}/src/lib.rs | 2 +- contracts/credit-manager/src/state.rs | 7 + contracts/example/src/contract.rs | 48 - contracts/example/src/contract_tests.rs | 65 - contracts/example/src/state.rs | 4 - packages/fields-credit-manager/src/lib.rs | 1 - .../Cargo.toml | 6 +- .../README.md | 0 packages/fields/src/lib.rs | 2 + .../src/example.rs => fields/src/messages.rs} | 30 +- packages/fields/src/types.rs | 32 + scripts/package-lock.json | 2191 ++++++----------- scripts/package.json | 11 +- scripts/tests/app.test.ts | 5 - scripts/tests/app.ts | 5 - scripts/tests/client.test.ts | 26 - scripts/tests/contract.test.ts | 109 - scripts/tests/instantiate.test.ts | 83 + scripts/utils/client.ts | 13 + scripts/utils/osmosis-client.ts | 42 - scripts/utils/types.ts | 7 + 31 files changed, 1195 insertions(+), 1786 deletions(-) create mode 100644 artifacts/credit_manager.wasm delete mode 100644 artifacts/example.wasm rename contracts/{example => credit-manager}/Cargo.toml (76%) create mode 100644 contracts/credit-manager/src/contract.rs create mode 100644 contracts/credit-manager/src/instantiate_tests.rs rename contracts/{example => credit-manager}/src/lib.rs (67%) create mode 100644 contracts/credit-manager/src/state.rs delete mode 100644 contracts/example/src/contract.rs delete mode 100644 contracts/example/src/contract_tests.rs delete mode 100644 contracts/example/src/state.rs delete mode 100644 packages/fields-credit-manager/src/lib.rs rename packages/{fields-credit-manager => fields}/Cargo.toml (81%) rename packages/{fields-credit-manager => fields}/README.md (100%) create mode 100644 packages/fields/src/lib.rs rename packages/{fields-credit-manager/src/example.rs => fields/src/messages.rs} (52%) create mode 100644 packages/fields/src/types.rs delete mode 100644 scripts/tests/app.test.ts delete mode 100644 scripts/tests/app.ts delete mode 100644 scripts/tests/client.test.ts delete mode 100644 scripts/tests/contract.test.ts create mode 100644 scripts/tests/instantiate.test.ts create mode 100644 scripts/utils/client.ts delete mode 100644 scripts/utils/osmosis-client.ts create mode 100644 scripts/utils/types.ts diff --git a/Cargo.lock b/Cargo.lock index 9470f24ba..35edfe913 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,6 +105,19 @@ dependencies = [ "libc", ] +[[package]] +name = "credit-manager" +version = "2.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-asset", + "cw-storage-plus", + "fields", + "schemars", + "serde", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -146,11 +159,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-asset" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26767ad8483411eb37b71e5aaf6bdad1c3d015ecdd2e0369f264c0335d79b3b3" +dependencies = [ + "cosmwasm-std", + "cw20", + "schemars", + "serde", +] + [[package]] name = "cw-storage-plus" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9336ecef1e19d56cf6e3e932475fc6a3dee35eec5a386e07917a1d1ba6bb0e35" +checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" dependencies = [ "cosmwasm-std", "schemars", @@ -159,9 +184,9 @@ dependencies = [ [[package]] name = "cw-utils" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "babd2c090f39d07ce5bf2556962305e795daa048ce20a93709eb591476e4a29e" +checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" dependencies = [ "cosmwasm-std", "schemars", @@ -171,9 +196,9 @@ dependencies = [ [[package]] name = "cw20" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "356d364602c5fe763544ea00d485b825d6ef519a2fc6a3145528d7df3a603f40" +checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" dependencies = [ "cosmwasm-std", "cw-utils", @@ -201,9 +226,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" +checksum = "140206b78fb2bc3edbcfc9b5ccbd0b30699cfe8d348b8b31b330e47df5291a5a" [[package]] name = "ecdsa" @@ -250,18 +275,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "example" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "fields-credit-manager", - "schemars", - "serde", -] - [[package]] name = "ff" version = "0.11.1" @@ -273,11 +286,13 @@ dependencies = [ ] [[package]] -name = "fields-credit-manager" -version = "0.1.0" +name = "fields" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "cw-asset", + "cw-storage-plus", "cw20", "schemars", "serde", @@ -312,13 +327,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -392,18 +407,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] @@ -423,7 +438,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", ] [[package]] @@ -578,9 +593,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", @@ -627,9 +642,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "version_check" @@ -645,9 +660,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" diff --git a/Cargo.toml b/Cargo.toml index ac4d7e7e3..3457c096a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] members = ["packages/*", "contracts/*"] -[profile.release.package.fields-credit-manager] +[profile.release.package.fields] codegen-units = 1 incremental = false diff --git a/README.md b/README.md index 186b4b174..deef1685d 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ cargo wasm? docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ -cosmwasm/workspace-optimizer-arm64:0.12.6 +cosmwasm/workspace-optimizer:0.12.6 SCRIPTS diff --git a/artifacts/checksums.txt b/artifacts/checksums.txt index 41ee4f8ea..89262ae9b 100644 --- a/artifacts/checksums.txt +++ b/artifacts/checksums.txt @@ -1 +1 @@ -6ed0c7c562272e3016075d4413c083245b6b6597d5fabc4dd18a48c1b3992031 example.wasm +e5b986b673efa5847747254aae33b7babe339e89a1b3f454314f420a6a7eb93c credit_manager.wasm diff --git a/artifacts/checksums_intermediate.txt b/artifacts/checksums_intermediate.txt index ed0169e31..f4c3dae92 100644 --- a/artifacts/checksums_intermediate.txt +++ b/artifacts/checksums_intermediate.txt @@ -1 +1 @@ -3cc7d46f21acb94f4107960599a1899c99b1452e801114c5805367970148c5ea target/wasm32-unknown-unknown/release/example.wasm +a0787373c53017e5f198a58e205be8c91992def3ec8be353d4974abb6f779bb0 ./target/wasm32-unknown-unknown/release/credit_manager.wasm diff --git a/artifacts/credit_manager.wasm b/artifacts/credit_manager.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6c3e8605c7ebb0d57be79cdcd0e3dd8cfa2aef20 GIT binary patch literal 168520 zcmeFa4Y*y`S>L%o&ey&7+^Zv5vL#DS_SrGyir7J6f~6!bXba1Bnm|m(ZCxfamWf3j zNl6?I5TPu4v6qrc+cNAx@V{(N!p`oph( z@eZ71G%JbT+EuX_W}-*ek*4{K`Q{JJ;3;nscG+ZJ!V6~w#k z*28bOz08+xz4ea6x7~T`ci;Br!)3AX9{^L6FFl(7a=v=`bDn#{jz^3CobR~u$}6Au zyyx-0W9J<^{>oqZD{p@5iMPD{@BEK<{ZDt_b25KN{w?2j-NEY*UVr1u|EjVy2M+AG z?Z5l)|A$)-?f4tFtbQQ>q5P5jP2cmkUU&PBV@HqtME?5sUe6XXgpXVM?Z2*x*X(Hj>t>-Y}{K?H>z>yuW*r2KrY+D9>OJS|Mik_l+OkiUsGuDYJb z-_5Q+&d-11XL%SiTJRGk^J%FHH1W&XlE6?3+A9e(*?3t^F) zsI4 zJ5mop>Ut0a0Rp=jh#-NLr-i!TTy;Z_FI69a@>C74&U&DCujw6Wh8jnIsmZ2&60(fL3ww|u}8aaY}Iw%xEi(nJPo{9~uTte`GaDF#m_@T^h7Nm-4nuYGMa`dQG}EHO>I@ZDTU5BpsBm+j!cD0^Me}raWb#c+ zsKzREG%7T`GQ{RyEicaC z|4%lAzi9rv@TZonSz>A+Pe8}W z4c?_X#Ea<$$kX8gh;2*-S5>!pw4Y|!2moDbe^AGZ01yu~rNwj%yAVVz5uuAqIsqAqEZPu}}{o2-cLKRw2Ft^&VX*vxOWG=Bu>v z?suQc_`QC4VGJx$ns{#plMfYeX?g%5^8*JF&|O16YUtRZr*aHgyOIwKC>ddTM6!!#L`2JLJ*7>`bS<_s_tci!MnKdRfX07j8gI{j3 zm}!3PhZxuv<_3*MI48B|ca)Y-s(^rLA}J}ovlr{liWxpC1~W%-3Ba5c%b^!^nhhtU z#c~7bqi09*Dl6_0A{m)30?jWRV1-nVg`+FOY(f7S)thGprv<|QdSqIJ3e)4O22mrKoz~|Dv9ze^rjcLdQ;3G$#cUh zrK*%rcfX><8{xHsQUR`ANHE<~DYRbQpW?LUfSdjGck5=o{#N9#qVb+MiA zMH-(jFg(rqNo;HNdcx>Vt2fEONwDwZt2IUag=o>>6*< zAfuk<8BO_MG9~3o0qnDxASNi64ZYYNU^}*#VyGouvENs*{)H5);LnTIk|Yc&i!0Ee z_tm)-Ha*`VU0Gc&n@^!bxf4lW@TAfDYv>0we~S>TY(7b@JoM~aq_lzXsr#qtJ_@h8 z&vci!&~H9zAQI>S1Z)!^*rXxU?RP}403~}`pA%r_KdEFcVaOoxH}1F(M}ikqos z)u6*{o;G9ob&iRFYPIDz0FcvhH4uXLRYK@J9Z@NK)SR*!VT(ycXEgNRcG@-Y@ZN#Z z-g4B8rX#o(hGcK@f?%N7k|F}Jlj$bR0Qlb?aZvL?GUMzBlWTNjI-wyPEBz}ommO7D zjz{X{S1f1M1V+J_>r0yaL_MmOEB^nAo%N{ck*VjWFMQz(-}tpp zZ1|a$)V0c$PpW0N!C0@90v1jcjdUtZ2CD9sB+(|@c=SiG-^8EpomtyN``tUh+(h@& zyM~(|F~k#?RqytL*fVZG@K<{Xqp?h&T~g_pMfGbe4%!1N6SwGlaDZt{rEo}DX(-2n zv%c^H2M}zSx;~|unz8LBS{T>;gM8_qICvbWXlUXVT80Aaz{6J_ zV49kXoCXI@997g~tUV!%KnoOdppbV^$Xh7nf`a5ZQ0N&7GXRv8M-NLF9(cz|d2Jz9 zF^YISjWK3@JauGMqdiOKt8RGeBQ6i@b?GlKA?wU$Dw~Bb>p1>qC+w1CN#+yNfqw3s z4)t?vYHqv#D7<4Pnmh>CS2iD?i4ZZH>t;My4A`pTf`=Akfoa8Sj1*+Uhob@GySZVy z%}krV1KEKfi=*oNkOd2l)&qBHc#t_WYGp7I;b3vFS~^&5JAPceM1o0N({N_M$M!pfLL>0i&`2mF6lx4TY==V#mE|A?Ra4cw!xPL0Dzhy&y!Azg5{KXHW+4 z08o90kX#3qnC*-**^ zrDQGur3H=CDq?bNOVx`sz^~z3p=_%#JkFh+*|PbunDlAqPJ@byqY_O}ugV#U9H!tg zPv!^VA>?zRGgTp zf~~c>=`mgy>m3Y>N#-wl!sHwE{bU}J89$`d)64jVhi5uz3Nb;=^V)wZ^A5S`dRF{a z%3g`GS}ozm?lng;GZ3>N($<~M##aqa`UC>$763Dj4!re@iT9|m0n$B(_;RFw-$pb*s;el+rQ_3*^U*UNd|9=k8 z+xfi$nmC=!S!|ov(!?3|nrLEkXRmGU>@`*kX7(ByxG)GyiRm+2pZquaT8SWJOis6M zL;xZOWt)Pfrh$G-13Pb0$z3dM~|)$ zNyuHK?q30J?hLdFX42uashv5;^HNH231nSrg#f5)gq4DD4ul1`2F_AAT>z!o$i|># zVwT+AYH{5Zmc3f+50A^WQ@ghqr4;j)gD10QrthyxFbBK6lAbfgot4IrWh}Oq4X=Eq z0e1w0V+HFrO9C)WNjpJOa;>7;)ZEErYD}jysnHhpHrHA`WFsxerjl_ z?cVgVdek>)J|sTX`jgSe(5P-$F6nEoIdCqsm1jGl4E>}P`Jt*05g9bcdM525F&C_@ zLgSXq{5?&SRRaXHddXEge$)2cgkNTcXhx1Zd^}eVsn(|FedHr%m z#-hoO5?z(a@@_e_#PGk+n8Nc39hA){)>^x-+*@o7gocPU{i}6hgx2$o5|rWS{ItHR zFw#i0YUxVh+j)Xq4~38g1z2TpK*1r2SexE%_L;Sg&O>SzX)u%=GwQ5w`;ehOlMB0x z+eNR*U)a!}t|x%)mC-(9m@bC|&cVf81YZQ-UtxZRz$w2v+a64}Synd-(2da8UOxgNr6U{`f; zribxf2ZwU-mpTVoWB5_2Sbl4=!7=H2d;-nlQ)FA>8+_*Bd`VSFdfvznru%jLfQkF< zxVK?OwmW8V9=x6E;yff&9(+hG@9CTOu;3xy^GD)6L$A6RmV_<4h-qWip?!z#bIS=HEA5m!|kCA9KwqY$X_MuS~J0Bh@X7m5~Ug_=qjFsyL+ z$(0Swn7}&4Bs$$GCZSwByPjYA;*orMNf7N6)pR3Y zkhxE*u-w1aZF8?3Cy27eIeV&m4SNdJ$?AbnDS1_wpWjyb5=aKC93_bi zQyj_HCUo1x#8D5-w?q@4=lkTptrbe* zm@a;C@u5h(T4mc(me0HuQ}7qviq298s12qD?3f3QWa*>oWhhp}55S>OeyZX~oojtE z`G`>qSf8ZN{Oi4ZC;A&9mCfiCJFOQB2om1`89JzS$kJYAR`h~R!5UeIL=SFIjhQ#r zAvJK9oLh$!2-J%e-VLPCwel@biOGTRi(ko3NsMeRlJwYs{d_vN{IU(yE!2}kd7 zfT;I0>-l!?3!yH(2k`V>{&RXyD42S$h!jnrS5Zmpdaq~V{N{U4%^3yedS9ZEZtI{- zb*(3U>6h~ijOhZhcuxPEWo2n`F)tR&UVktgVWDMsmBbj;npA%O3WM~Hmgiqhe)KjP ziX|u^xXi<|eG*2UrIcx&<)^6i!+d)kVID3(I?pbQo7fgS;|frxY+|P zI@L8x6o!(RL#=``GjXHn!?0zt3)xROq(N6eQAj{xOjKgZamNE^WRDRe)du+|o+Ej^ zzV6nl}qm#KDd7#lX z#aOQsInA^qsrM@IN(BC`Zz9w2mu4$|8^5(Vt+%n?25;>?FE^#B2A?g0Hud@X-aYE( zgs3jk;niRwy`+y3LZ(!QIgC_xzP07H{!?zPDU8u`-Pz;}Ti)6{yJ2^_-nQLeZhA`~ zaNc%pLvZGkGr?I%Fvflu@q0s5#5IBE0@=?Oc`kmdaI^4)LkD9V;vnQmJjzRreWZ2p z(?08`jY*|{pg=pvsQvW4r^HWrBK<0gB9N}-2llv+pFV>8nxgX4KA!gYX~fei-za%w z)juG;jW^W)T24dESvJ+V8Fw$%+(D~Yw{2}aWF zC4sfK=G6D{^5nZ1+x_JF-|~QdXDW&dq4OL1RVi3|BxQ`GZ0@JHU|j~R(J>Nq^%OA0 z3FZ(#jbsn0`&s(sXU}XPlGg?)%r~Xzv$L!($di5u@Pt_maB6a03^)y3OobbtjB1UQ1P7i7M{FirM$Ds z4iNuy@+2rCBGO8&a%S|6^%6gAKjJKooZ-Xd$bnvb7^N&kX+y1vQ_iV6bP=-y(cVTQ zNVBpY&4af}#FAO4~{;0 zO)mO0ae5ZvG?BELvYJ4be(%y^(=6~IW?sgiTIpcC|0wTsAVyzUGx0`*xrxX6Su^pK z-sdJ>w$1#+8xhW!_%l8 zxv9gIJ1V3ztwRj<*Gf>tLLD#>#@3R8^$O2mW{ruM;T+{>#N=4ZQxx8&&4WzRWbHo% z%sKYhOPT|PcTrrJI8apFb%biN#>p}_tY7sm5Jkm9uV&*Zl&S^y3@P@^drs*yF4a$^ zC#jwAy~WA@lJg`wUZf&WW08D{_Zdwd&EfhK(@L?-(pqXD)@ABIR%Nmn;h^NXF7Rf5 zLM6j}E+0Iyyou5iDrru=`&8Ck!5ghkzTH^EOXmv}R`DLFKwrF_Q4}cp*1KE;wt1zE z(}{9_y=^ZtLDj4Cy-E(MZI8OY;HlDBY2LbdD0)g^XpKBW0s=S#D-)Qp+s9fV83RLR zXADe7fhtoO*N)(4=|-`IREZOYOxq#NQKHtZ&G=Z5EXL$?QWd6G%}%u<7}3{YIhE$x zO%i3GS;Vo!0}kbDinsB!Z7=*s<(iK&TP#=-$SYPdxheXI_lbT|+ZfcUCO4Smmzv&* zn#B^C(F3^-1{xZW5^!h@^5zc{qohkC=}ILr3rf1oNxTCwy>E}mLCDhOMXQyISvESi z=({4H#GHjN0lWf@LJl^A39`!uK(K2>ymY`!@+Ugt1m2=eMcM#`>j)4SPJ1%-`_ru0 zUqTaxAI4S-%}!%B{mcQ>p{Fbj7{1pT9Su8&B(s7}NLH#%EK&P1qU$}B0dT0-eCo%f zKa8X&ZSeII@m1gcIoYlR*%A#)vD1qoAk7U|7Gqazik?El!&45-CVwDryqBLn%b#dx zdD6~sPcxiTo0vXsMYQ@Mvn;#A*ASM3c{-hWr*$lIUoK| zAC~k1Hj{W!pC_2@Ca6(85srFGLDiav=<`T5+(rv>da>Rs!o@9*(`yXJf#=}#B5Z`E zDh1^)JbqyN-SswB_ik#4Ngl&qr;89$-(AC%WEY!kk?qIAo1`t|=;5~Nq&wN0vraTJp=o5J zwrE}qTQP-WZ^Q27pY_C~h+ZsZBuN=s=voS~=z4WI;CJ)>6OpX z&t$S~t>UOReK_UN;8G3MBdYpb7e~u3Owtr||Lj~E`f$BK+B0)$pOY0s+UdD8)R9_U z`=rzA7l_H(M27r`CyLaoA{EwG#Ur1d>OLL9iYNI-5?q8{d>&Yv$IJm_!R4z$qHDVN^)3_?BNp;Gwl#A6XN6qrdsRshE$zZSGs$0t3j;Z z!O|10@ttt(w)f$TsJ6F5DRt%qn++hvdXPv^gCwB#E>fXpYR(gldij>hd)>OKvR-yg zaXUY(Su)46*A9t_rSq~ND{n{iYgNKJXq-|=ur(ELSEmtr9nzHh_LkD10Rj{evG|AB zIq3MEu{2W<>voaw7$PU*EUv2EI2T*am`~|?`~-`Zq7N3T2lQ>Nydu*sOAqPrVaY>T zux_MPZnK1jr&$TLP*z04sT4f9Oj6(pt!j6d^gwU$MXSexh`*ql8+d+^4?DiBiRxbS#Sb!d#L(+2iC4B2Si&WyaPA5MY&&=YcK zMZJ8CwIlSDYHpF@00Dr ztf>^w2|S!*fMBwMNXGD_Skr|G{z24ud2pl^PQIb(O>?tJus|Et0v~qOmn(jSS*oU2 z3qd$BKtDDQq+71vDVu{>2(&djWl1?vZzB$Fx!Q(Oj$(rbBBgx!@dM+#>le04`L^nX zk4~>(o`s+CX@yeGLIte#92;e?sJ4;)!Z*t1gt)R|>8SdN;X?8CO8I08qA*hVW5+1v zd3Ck0I#-;&pek5cO;HeTqiqxr<9+AxTZM7(XN+_Fac zH0KSl8*cr?4ASZe##`U298HW_L3Jm+0ak5F5YT(tOB-O9f)?Fe@-i_B^~Xkhk#@|5 zaRLp|cQ9%fP4S}*u=ZhX)&^K5)M>x^K){o|XG-^0h4ch^Do8zSeO~Z#6S9G+AikEG z6CB(94zc?&xDUgp;^0V~4$QDxj%53l#@aoagg6Oij2!{O zdf{E0(F>+wZO`#yrLl=_w3=o}T_6uBQpgiLmEyM zorOQoOnN88OtvKskXO4^QqdCC6-jz+#9Xdg!(2wwVeptl-(i)Gw{$EjCPZ^RtWPEory=RJ3mGgf_smU5tRAZgahPixOJig6BlM z#f!GcFu}(Z>K$4dr`@ZPgYD*eFC(Ys1XFxL{+Z`3;6%7&&dy$Jp;K4OVa;3(fU+7U z_R?R=UgDM5!(cC|BOQC$pz}Dd((u$raioi3FGFvDu!@3n)+)-5#9q8J;tWelLP>0K zJlAi0PVpN3#>(16U9vFL%PF)Ox8TAu{RgRazS28*HnbnPC)Dl~_jAri0ZeqBbMe zPj&$um@%Sm)>n5H%%S>)Qu2LD1hO90wR&MFvN1;~Y7=wVGYcNKJI#kY1#WxgX;+-V zszoSo`Cn|Vm0CU>kKvC^0mIq-n;Fs+3CJ5x1iy#HsG9H(_+Vdz_hfCxXL7n(YK99YK zOEzY>Hn_{?5xbs}+eIi;%0+?aDY<7N30FnENK&^vTQ(mdX%LfE+E25$#1(;Ks9=Zd zdrx^@%jRC*p6uQ}kMrZ9?hV76{8+KcvMifVkn&tX-0my|5@2ZR4Tl$hy~{TeK3E*7 zc16xdc=Y^~&BHu;|906t#N#)9i>rE&$LB@fXQ?Oo!ahy^M^zQ+_mPD*OC(w(oFpM0 z)r%YV;ugRQR$o&*tzQQB0N%A~dQI_+-pTnnjr}P~!TZzLPw}?cjqM}6b^f&G3EtA` zDdlP6A(+x*^4PJ6(>(pXr!;C@g#5*?#@`1hz|;WFPw|m9HjfM@lAmvr&tT&1>$|sS zTm&yem+~wr5vSqm{hd(i_B;Jx_eA+L-5}l{+8P`xw81?<0B#Lbag$NI4(tb&@&+WdSET`B=FI(vOsT*Fu^Slbv*N zwYUmS8BTp(cJcCc=7+qnVWw9-hc{_jfnw*Hkzbylk)oTCFBQG+OTz~o8Xg^Ahh7B( z7n_cO1I~FAJGp+~MzQL75)U4yM!ZWAN=3C3x|^O=gm?|5&>BjJ5UYjh(5amQ5?iyn zNIN*ls5EuL0X zHR(cgaSK3@I5I8(o>|>6xxxlo_Ds?(lz>f5{+?)0Rqp2ZbSc{fO<+xJ=I*X*RujDK zXYYdw|BVn*aQ$|EgL%$#l+$KS^Bg6Z)jUTDvVFQ}p1q$6UqC&Kw0ZbFyxc2ZqCm># z<2(^$NuPo4X=QwxKmNA#ThA#jzo#DyP)}Uo0+jrMz;09%Yk7_ZD13&7ElM@MU(3{v zH{M5@aPO0yFB}XdP2A$?xisKmvPE=vWq}BGpaD->tzGZD0TnMmeZ)Cy?~11`<}Dt{ zf>NOZydOda@-w#prGp*OZij6v@&Ms}CPDEQYZbpyUgjYI#a4m}qG%)5PM_{sJT9>MV=hmOSMkWv#Of zG98!+L(FaQJYv%f11MezS_o~|+x>cz&8kM2fGeJ812z~te8#(dhr5fh#^l|_!&n`K z8nnT|iVjO$XykV4u+&8#W#8EEO1&7|jAKBMyi|`=J)`PrJK=m1=aNxNBf^t0h*b;S zIvUfysxCwaE86X`!$>44RYCBe??O_3$J3>NQ+vGAR0rfM_}HCN%}WV(Rn{%o@zgHZ zEos3nBh%y2!zn8$%GvHyr3VTQlIM^=?2G?qIcw~&Ay@2E9S9tmf}(^*V;(`^>1BAI z2W86K7I+bs&1~EJrrFljft*CCisl0+Pw6OtDJfVt*<64RecN4l+dN)-=NDX8>rJun z#q*8zu5im|T26_=Nkp`TZ)b>T#fPHXVv?I!^rCFLZ9X-dSe5`U!a9_;t`4+@rKw=F zZCC4RF>PIqPAzG<$GsUxZz4Lweb>?vc9w^gdIq#LzpSFj+@q>RHV`9Zv@{mmSwR-( zkQv8vrs`mB9ob|^XcUG-ceK66Lx`Z4C@vG&ywapin>0OJX~Kj}JNTpL1C~zIaj%>h z5L&5%zC{%n$$s+j0pz+Fe0ULJbsmti+hWeS0si&;YUhS919l6gq)ak(-QR4SJKq#av?q`%3eA&O(}U4P=f< zRm37vsivMG<%;)#D?;iK1sXg^5jV->NS4Hv#CgES8t5@oa+4Z-?aav+s50=@E7dt% z5?TY%6KVGSn?!lE4?`@N9O02rVtbk%ELMFg0v){-B|@o;S7}6;-YVUB#5Tx=c9$wL z7#MA#8i;21!a=IReHFGBG0q~~Xh2D#LJl8J)zPq!TzOSJ8lna9B`J;hfozq_BFzSe zGYL9jfPWUlWoCeXT4qdVa$~s78Q=(DYJ;Vqs{neEzhZGpd^^Ya5JX`i1r ztFg1I%MDU>`U&24bZ;``9VpYK$k4ZYrAv9rDQBA2k62ZZX)O&5oz<8X{~|GOlu8dy=lF{05oxD4xpb*Yt01{dqIP+ zHTsM(<)bsRxjF5WC+AYAj!Hi~OOa{)K}>6V5kw0KzRzkNflw_dKMR!G{b25LXIe)m zKex7%&#s3wgPb$1Wg3D-VOq}{b-hZV8(~_XZ$^Gyo*SLjtr-c^`pb-70>GKG)0);7 zF+0^Zgj1N-D?F`)X{~_6X;$)OqXpe_nbyl8`evpzyl%Z|y_tr#8qPk(;gS~Qa&5@M zXy0Y)^FBWdtl7m^gI3f3*gDhtkz`s6%{MZwvD=L8r_r|(<0Wa0X{{<{T4N)(ruAHH zX7EzS5JYWFYfNjAu1xEX7@g-i$`ekR=P1G?8<8!KNo4!ToM{abg=ziZ51wIK3%ct} z>wjDpy|}?bn{i5HtB(t4w=jmq(^oQ+T=%pgShh&dw@Eb8TSB!e@Y+k8dVx2S>tw8= z$yw8o#={eS6{$MtFPM510a+qJWv~hd&7$`EvTimzeh;H|*M)~NwSV^E~DKiaQX&z#FV|qcSD3h+oH-Bguk704gijG>DXi!1`iASAu z5hdW%tgF8?dp^VeIC6>Q89{e=={3?oe^C4;j}%J} zE^y5qg;^vinFxK=cql)r^LQY59@ssT9pl(cfZu0H9m&+5&?p0b#EE8DzU_2sLIDv~ zaZF=T_G}Yd(^Uju3M7WQbee(oO_Gt8X6Z_`KIc^Ho~Fe-md4Vq0Uw8+LXgEp3L!?v zMWnR2P=V}cx_fdF#G&YUtgC@m^SO7NlNZ0zbJrUkzZ3p!ji&Xz*`=-fAofBBR zUZ{Xc?Y@g4hBa{xC@M}X4;5ZRlpS#{pa-$FP!H<@@L~s1&YP0lr!E|vW#)i%jtPdf zkl>@!8$`ACpem2%C})l~j4)u1a%O`$k`px=^^jr`G*3(-fS6oFH6mMw%10bjI{CWm zFB__E@B(7Ty${kk@H}lQZO5h9ES^mc8U@eZ6}kPG$D_S1Br#7AO+Z2nFC_)UjERbN zkAiK;WV@%uF5~T~T}UuQk4)OStVLgSKhS_qV8^f(WZDxX>kaWeHa4gPL@)-fDslF} zv9_yea@|!SNEB>?9UM-mDRTC>S)uK*nu%gnuZ5`bC*a;@>%b%LP^~vGM2fWGCjx78 zEPaAYce~4x3V5yB)%0_KMC+tl8wAPj%XDjFFWuUROwp~4nXUb2?by?9ngf2aILL@y z5;>IeY}u$(<%*R|;p#jsu%C-8QA_6@j8~#rlW>Sz8zc1z__~ve#Hw+Vfb++N;OHD8 z9`Pj7!W|zc$)GngxV|=%$)~3S6tW6~TNZj2UISp&6-=1{OcWe+1u3C|gD5D)L&bde z&?76wBz8MXUJv_#3!rsNzH38!otMzLT2)Y&u` zo?1tydqdE;9GM!sc$SAnw_ciE%L{%AwqMI%E;c)Pk-qK^t`{aI4r}kpo-Xn$VYs<; zgVAi;bW``5QZVavcP5D{x7-wBFsf>PRxV5(s4f10 zz{Okb#R;Bp zZrvF?8Wj_d6Bh+=k%vz6!>yNkk<`&tlEdcde+&p;r6IIM+pm3+Ky+gy-?u6PY*dw3 z0C#tBLip?h;oYaMH_Xv%ge=lD1cd~~x^1K#VtdrLZ!Bo{rZ8Dm@9HceB2Ve4@7Rf34{-a~wV-yv;NkDDgnp<~KRYD4hi zH0YvF1JS$a(?ID#HIWL}hQ5mB`D`@D&)Ti;1poz_s2}#yY0_AZ?Z7^VJssL4u;cyp zS&A*h65ezW_j7X#R;#ko5N0Us%#y`%nP86R~*K?GKmWcMXSaUITrhC>Bt0xm6nw{0_NyR zI3KZF^cHy_Y_it7$Dg;5$Se=F2B13RJJqh%GT5m%M_xj+B!H7!;|bZZC7ys;+6^fm ztk|wPZ<8S1DdHM5=0rF4`%qnYN7wO_=rVoM&Qq;aO6D@8Z!WU%JJN?OBF9ubDifqx z+LVw6EkeA3JB7S)vawvEP(WIdT%+|Rs-zIRE80!@Tb;zaU836AzZIe4p^{f}rE)1L zU;x5tbjLgRvpM-lzM}b^s`ogFfb$W+6 zhG78kW_kzQV(y9)HWshDEqc9xWLB(qXj61W$HNqDMWb~O8=KZWsC;g#95~;J!?Wlf z?Tyi$bff|30YlJ|aK`0=XR9I@?tH;@7;&zcE{`7haaj5(ri~;AyMq4RMM6M;N(+#<_RlWtp~W57BmX zrk0~&7d5pG<@E|mgz5^#ypAFCmM-CL>e%NL;2ydRbMLMqUGOzTJg}2Pym& z2?LgH_kt@-yP#T;=^|D6%Ps+r9J|PI^6odepyQ?Kx-x?aOX%=ECDmQ^La~)Kya+lH zy2_8k#G5>=6a%o-G{<_S6%|4$qpH;Abf6vsVs$MD2qf18sv5rWr9(QNHtU9W8FieF zyksL4T*bRW-kAa& z{i@DL1@Nft%JQhqa0IRZ?M00b(p^8kIj!^J#|Ojo_SN1sh z6w>D8;AOcPl5^2rk&{J+T2Sqp2jLTs3$ZB$B!Xb&87YE!sHKRdm^Fdw62cN}nC1*Y zw!DM_O{~9rTj)&pbPzUAN%wM33JI^CbT46*q~(AoTeP5MvOZDRp_DNqiXZ7-I-XwO zh<~t;KIdE)Iw8K-{KyZSl6w6ioH^EC@FME8Y&eRZ$ndZ-^xF%cImiyEX_pSzqo=t2 zB-PASap$br@eQt-xQ%r+529utV&j^v2N-~IiFg-^=7{2&H>c^i=kSuHG)OV6EQ3@= zTAMvySXBuJZg&zUIeVBr513zgt89qby0Owk!d51ka`PV~JY17fAdxbtLb(XFOyWh? zFi~M=Nr?(v6K|QvI^7Rt0XBz;a)S!|^ZC?!=`C8HUHY?LM*(XEur3JDG)Sa{0B+wl z*I}hn#f4BnZ!HD*jDJgDwV>Kt3gA&3vxS{0pbQF7OJ}11Sq&`(B(Hc!0Rk*T7v}Sn zFyB2e4Hm+D`H6)2(!c`q2Rwx+bvTxedcaj8%$G+UE~hZ}p%lk{^8umr5OkKMhrBfL zf=+SgaZlScAealvT@k-hhP}zm&f}$KOfx&5W?T)z3j76Ey^Ua9XhXtUut~U}c{lC> z?1XzfZG?NU)fw*1T@ea6g`!^)?!}o!OP}w5rEzccmksyI*wSKBGM;11%e z$R->}gm#I-8hvg}HR+ig1R`ZPnQRV+cS7oe9W1uoym*hUhF z$SWd~rs_#k4b%Q>${(8eMH(LS+8Fz6kFNCdGSju3D0x!-?rm-Fr26Gj@>a8~`2jxg zd%e=~_bX)!LCp=Z%-)z?z%qAz0d_h@+5 z-J?zZ?Wt^WX4&Q^KbyVG-NxlkXzPe21Aw4KM}>iD{4^^!CxPuhjpx@r5VvO#Fn&?Ry7kbv42^mtAP-K2v86~Ie5Z&=0hSTW`FniAOl!*% zFkuQD`0^{-!oa%BElg9h6^rOHyL77=K7L-^FyK$!O~gdmM#L1OiY}A6B_@9sF|lmP z+Li_?+~>lJ%A0{RgOi=hGjM9rb3Hh<4{-tIs0yySzcgQgZD%f?m!`d*ejUz!3&qW6^WtBcCH=904%UD%4f3jUbKz;aIW0 z&g!CS1J!E*ShJ^gW4(_jMJN+^-6BjNxP-6LEqsM(3FaMw&OuzYA;ahxAo~#EjOpe& zhF4jxi#D&EU87<7u`3E_4Kca$%ny+fDuxJAebc1_*;E&(BQO_07UhoHcZ|hc+>*Fm zi|iKtl0@g}eNr7pUhwzGU2Uii8D!uZyFLf+6;m``}iCaeW5xCXs8aYGcw4N zEJ=IldfbsG>cIxZJMx@q5BsSCU?3BWecB(XH|AHk_{{s+)AXYXmo9bQgp>$9je+6= zT?Fy-uPN@&4e;V_4#7KlvidIJg`y%?*j@BT|KyXu^5Z}Liy!~tdk$oi$Eizms}KM3 z-~9Cd{h3F9?fd?O@bdV;79J|tsL1}K{%~fIryMUwrK{>0zxy0NH&&OcyXxg?d^4Ms zh_&5a?!T+%SZ`jdG5;LOt0DwO?k;b-t6Js@IsUq`ANZF02$WnPeaU_I9{9zdeCJQS_d~stOH{bbfKNa1^FQ(P5C3s?;2w{IBb%$0yJ+FxGM-## zgiIbNAR98@dy{v^{6|P&k4Wc!g1nASJ}ju96bXz}NIPS-Q!jTdab1)kdqzW9HD2Ek z6{P;%?6&^hTw8gxRjoYQ?BPVaA-#*ym+5k~UV*Hl3?#hEGydzk!#93aUEvTAanTHrq`;!bH~P0|8KoKvV^ zBWjXVWIq6M1!4;=T2k}~(WTgwje-tg1bjl3Hdp3Vy27JjN{Pa3_mBevZB3=J(V|i& zH!6Lp?9?;rgE&k|E$L=RNfo?+3jdROxu!;Nc;J{N9Wb$x>Lw&btZe*IpS)%fTSS+B z=_UD&bfdKm5`c#68FDpUvEO6T;H7O(w}v!df-C6I9#qS8bCDp73J5Tij#W%Z;u;8( z)1MNA`P}^4yWmVk3*@B)iL$*6$^w!)>O6_sKBKPlkH!k<%fg66F0x@|#HVeUaB(KNj zpn)AUA;507*F%Y{v$11>{K50_ z-g||BY@vUtgeY4hV#H%ctnYsTu^^d}gduH!1rKOAVVrA@s8w~^$yGK#84Z3o@0?%f^OyYtm5_zN??+qkfI%%mzqFF1a{j3PyzLH0P2- zS2=GUX?EwU2P!>p9;tWdsi$-4Y0{C67dYo38|Nn-*|;d#IF{0A+2|v&HGFDD`I2lr z>qmWBl!m4qbUyB)L!#)Cu(VSunAuJYbiSk(y*Vdv9=+3yf1mLqVYmsa_0)RA=db<@ zEKjH`Z)VpM?&Z&QX2aQi}XB76}nX5AbJy6*~-OS;`xfW>H1+kd|NP7JeG z7CDhEX|9@|S0|PxE!%-O&PVsZsuJ8mGh;g&(7n&sYiIQ=bk7FMgo?kRD#vXnf{UD+ zXQ=GOcl1JQ?s({Lt7CoN^Vcpr=x(#~a1d>WNFzI~3WgUmGL`+xjI8aIrNh5h$8>{X zbp)+Lt!{3C0n|v=5x&bU zNne(LB)M9Y9>6^TFiPd%b^z>WJ|wly4pOgSz;+qvme7|~x>mL4C~tjvZTTvS9Bum2 zmvuWqA5AHG7R^?RN1DJd>&woM+96r#?4nTk8u}*t@CA@?^E+4Wo~#2b>*U~xWBD}(^^B}(uR-oas$?^65hYh zAh;O3SJ@5mKB_ce&t%9KGP1T;s^Ve+J70DKz&4B1%qDHsvOVjAARD?#yohsEhD36Ckd6$Xuqy_N-@sc^1mcOh;v)VkMr(6PQ+x@aVyM?a8ntXw`B% zQDCQhUk990pZC2ozG*atjj8xnDty*=@NMehlf-v7fW&G zN>}TQa5hB|xjNyu_JQi8)aJLD+G>RYlWXMt>{yxnXUgyw(9@MxWw}It16KBLg{p&& zm9Z*2^LKvnR5jx?y78!%y~`?`xfm$j#&@*_qgK42yXhDX=pdF}4ZCBW9N)EAjpgYS zSDlWNr!#ppBz!JVI!Ga=SM45kX4h-O zDoiKDBf9In3WHcm&^B2U1urKGw%ciWM!}&boYTx)t2l5U5z{e+MF3E47CTxmCb}E1 zqdTS{QHT%en28oBxuv^!(u~bVcRBxz&1g6#KDK@Y@$rzVw$$1um4B%)$8bsmS*(Tg_>wp;-c0Q zK-v{HUlQkiBob{A0ztv$-DKDbwy!AmS=gk9w zoG*_$XT&`mu@C^osVG| zCT)W$dl_qu9CJelJq0Wqq9%4hk~*Lz+96JWmh`#sd^m=3*iD2gP^<$#j&(@u{zF4y z>DTubn`b8m=1h4mmDH?AT*zT6#I2Cf9UJZwt>hV1f1E)Z!x%^RUg_P3O~vv*ai>&$7drs+8+~%41RqpZTL0c&_G^!UM zg64`QpS-%pk*mWPJbB#ueWdwU@Aj6ay;)XiOI!2w{5u-NQ}geqW-nUiIPmvRo;>+9 zH&wB!EbMV;ht7a&w8(7rQVd*OjpHHG)x2?Z4B7`Y$!aqf(G`l8LjpJY5=U6Xs!UTdyY(A*#^Tf^Wv8aZaGfuVG=y^pUE=^ zVLs$zZxt|vXyjfA5VXEjXv;Za(#_NLO_#+nR~(AgZ?A3I9!VUE*4J6%D!9W*)ulQN ztzfz;7t_i?<=pndIFsm4`4CzmkZ~0;Z9UDiCRr*s;jeeb&qKM013fzs<)dJBA}P^n zEg87~XiXfI6r8L%`Ck1@zKi55kHo~6%{2&pWz!El; z0L!(4unJRM-JbOaWZK& zw*Xz)9lM90aBRGotYBy+^tghbeZno_2CU-6LXTp<7xC-yT*Xya@tom8jZtw}HmaD3 zK6}M{VyYdv#h6kr@Oi&RO^;El`58^e%mUW&tLt9)uHYBPPyzV4gC7nJyqzB?@(@2n z(7%Qst|_^NA9`~WXwTtStMQYO2+iM&PwnIH(^2I;;tath5v9Z85WNDmC?HxjEr538 zV5*82=S}g^{G?;`Bl3;+U7>t1D58p7yRWe4W0~Y0;W67(}8=D zBb-(|27f^TaKz$Z?U*>u2=dXEnScTqY$<6#Y!>U^pb(cHNtKIUZ3qWJx9)Cc6)+@M z=Yb*N74LFF(A>E4Sw?3aDm{Tu44Cz>1k4&xzO-Fq`4!AvT4$(n3Y_2)Ki2lD$;)@v ztRaBJx)gr-@dM+#>&p&Q_uZsR;V0E)k50ER;7!2Jq1VRmo?HRPbS1n)9GNDwhMp#j zE8%-AFmU`Uz2{1JxJu^2mNHTU9H@y@i7Vmx-h=C?XK}#lAkKCTO&Z1!Jd4q@TSa~% z_GKIsEA~X1Izhh}BVu-Pmf7&x=|+Ulhy||2Ci*u+CanlF(IX+9?(L#N>ktq&lVHl0 ziS|%l6TqsO?Ke+x7fW9k-Y{0tzk+{0jHHq_b_&Xr*y0l!MZfuAJTCHx6jUl+A*dhX z4Pk`XOrtz;CaMT&Eh5mqo3I-At>Bgx25 zNJbv2cu$fsgd#h;R54f0w?Z-7NGNh%bP|eUk5chapaL*LyhIcTmxO}SGhk8Q$!tSz zdm2_p_A^FHq(Mj|k)b})u}pGFk8*2e*fRxNf&ZQwvM5+Dg_t2ZpKvT^XA?vWT9~S+ zOdlsc6GXieA&8dFD~PHwU|>n~B4ysn!HR_jE(zh-oKn;#d8Vs{EY&pM$YaCuI-XFL z5!q6bujL1x#ZAAGU>ULJJYl0Td)me_9R-?)DTsQOrHEfE>oTOhr0Qs2p1|Czz0a9a zZEPwNS6kU;R<+lb>vltP3@VAewoEW_D#`&XV{$?E6sH2AlPY|ZZYol-A6ANht35|~ z$EmC>Z%Oa-Wl@~U`dU() zia#Hr?*S7<`ilo*YeHUnWWz*kVHcDHQV|vG4wW5n#nheBQ=lZ96$${zh zhS_#u$e?xIbW0R)x!J{{&Oc+;nppesVLfz*!Sax*mRpqiTHolrquE6;1-c+|fmarm zqN1|eQ-XTF4&}U`7)tFR1j8finJ9-?0p*Z*a{U|SRBh~M-mv^Slv8;rPUP)St~W!u z)NIu|(){8)%8>=-US-#z9JM5ri*3m(GI zfxrbSHqJuLl*y9>6c@CZBs$3)mO&!Ol79MpmffrSOx zoJM}h*^L7r6kOWoWWyZvgJRN(2u~JU8HC-6EgmJ~po1n3XTtc|+m$KxgnJ~-D|1Fl zn%6SswG^w;)m#B%%~kuwb!%QhQZL0guk@P8E5Xvv*}iVh7!CJT%+J`h?TnoO)il3p z#_Utxz_gWUk4bw_N02_3S3(lM409%=^lw@uc070>cnFCK`zl!Y@*04S zgaO4E*Ic1H&sxZuDr3D}>nHpkl7>U`oMUeUT`l(e-akI=Lj|K%)F%X^ST=2hcsz%@ z;M%qB0#y@GT`gW&ySNl|I=xz4C!w32t?mspz#F4(X@DX`YEb7v3}11APIgH8ZGnpb zn1JeOu1!H!)OEsrtdH2EpDr8#uD1CK5Gd|>DU5tEs!zNv~cCVnB$bKNolir=K$`S=3(K7qGhyDRyhtUEpGJ)MUTs`^a^6-!|5yWQLk z7Y&hBA7(C9?t9GgqG05SnF-0Mxjk-PsrX4}1y<(@>`{F#5JRErO{xeF`_84M-+FFw z87q;6c2hoGw6~YROtQ(32??VdFze;4020#_s1rEn71gH9v zU{t)_$D68z((0{U>PgMJY@AY zrS6DSES&n%Dqi?3DqcyS4}tr9xRK<~lIt>{g9EXU|B2mxZEMNTyw8kwx{L4Z(N|V3 zPtp&&{&W!!x+vpb#k<4Lo@oXY_qWF-*z=CVMC*io`NH#HtWvZPrn9r_(d%L4I=h~y z92rr^5%_-|w~@@%K&Z1jKPNHV1m~vT_}Uw*}HHdyT*seL-yPoi4n_9 zGCCqE*A+43-lS}j#2Oe+aA|~r4>=k_5oWr|EwLk}q3y(RwM;Z2k{oc~MGQU+9`J^~ z7&&~KtMBJSuJ48yl)|ckZY4wRuQFcFC{^T&=9nBQ#Rt>m4#%hcRP#u`|=MtrT_f(b^ZEaW5NmP9MzKBYB5H>+n zwpbETEp$ZXLME!HMS8S4UG0374syQVE$~-<{^nGg#;0;FS0~6qtrpGi@b+Z)_6gn| zN^dLGrLEl8!dt8$E^8~mKB*JqYJ2*;wb~W>#mC5{W4)>NwDh9psRA`75nJgSBp z_m-@#Tit$4zc7Gj^h;R4DgBc3X?~?lI3R>AScT%Jn4bsf9zt&OP|bYK&7d7h9NSJj zS*T$IMVE3PDVKF`5BU4cY9;Pg_nnr{(j50gf^*i7qiOk0EDY{{(riZvUm<_;E@wH)&eoc02 zZ5>t89lO8iM$|J@}V04{(Q~D4zF*|pMoGub>IUtv1x0v(MsCf^{Do zE;N+iG2J^Qk)iy_5A-pnj$M>J#nYBV57J3{kI;UFY6Q_Fhfm9M8kg4Rh^JlW4^jnE z-T5)dH>}nt=2BpuZ zC^1}ow=~b*Pkk@oqeR5R@8RWxVtk^D=HondG;>-RpXQIhE&XbdFQh|~I8cQ0;8i~P zYsG&3)94g(MI>)o^>m&vKd%6rNS{m}x(W_U9yAT{I14zeS(L+Wf@5kTNPYPJs$_k$5K1ieFNf&l%#)O%hmYUOl z7wAlzkMlG6yn=YBy1DlO-u_H)8k-kcmHeQRKS%O=xpg4|wc#vc7kM~ZiL44S?5J$% zMD4rO9$4uJ>rywU3-TIaEdzv(%lw7o03?2T0<;n(yO>?Qj9EKQ_tQ--(LABkn4f6+ zq}j+|Q{;{fEuN%l3Bv)7E=c82BjU@L$YHQmYW|#2>r6p$C*KGQD4RchH=xl*7!cK> z0{>5eUIi-F3o@?+ME*Qw8C@?%63QTy5lKrBF#2tXKQlv?6z;-VtXw1wMTA4*`q6g?q-0w$@4{#9!>d zWC4IPsT84s{-W8H{z8#MRUv)o7Wa@)w%`ouAWraZj{}flx{}GYmhb>FXCLGHxms(? zfnd#_xpmUj4BIPt9fSM!z{}Y}+E8mxTJgd&hWF4{zYvv-zM`n)(|<12nom5B$hiu6 zY9V{_4&6S&qgoiNW!KWxJgd*Fxr=iC@Y(BAZF@j~U{JS!D$-@c`=(`oW+UR(vsg_? zsD^H1Qt9B|h*T0Izi6t!DY1?!o^vKueEr#}Vuj9J7*(WEn2vq_F!Yg*$TX3zKO@tD z`@#7N*bab#>6l>tY*=i&C%+r4c%tJ*s6}XPW=08_dAX@x0FHTbJn1g@n9TyIYSs#1cVjhK9q`RD|Ex?o#?CF|B2H;-Ka1$^)TTa=if( z;1(MuM;sfvV~rnPNYGGwf%6{CvaE!9v7Lo5gXmf2PXxHsXL@Mdh#(1M zD8>Opt%F``x~+8p*V_^-PZC8Yd)F-l2MO#w3aPlrOD7^nSl5xv3Y4F25qsD?+1V66 zSUfsQA4;lZywsj5_I4R#yoTr;bjRcM3$o9!;DxYYk+veHgkINc8D@)o@q1lYb> zqu_KXxJt`~eeNN2RP-v31wGXaBoEhwx{%4x$=SxBq4=GxCAplTT>=YIxgv;S6pp@j zSAO=QVgMv)=PJ04fG$#T=u|!z@WS;k=01{7x z8q(+vGY!4T-`eDFHUo%9*g=6v3&`A5_pyPPDTWY4jY0?=3NI7;m5eNcV%XbhFEUXs z=o}gy$j=ZAM~_bYxJ}?QGuBeMf(4d-lM?`fKc#&$LGRK71cGV5RKL7pkWao%a(Go! zWsk3{JEM#@z$79t%E*(|y@N5@L6hF4w|b#Ls4mZKxmpi+rj@~?!fuIrJLIHc#(HDl z_ITAx+Y=fsLNf4a&OpIB4KE~_NR>hTDvNb%2cMT@C%!Q zF}3e97N!021-Ig}?>nVDI1Jw*--iX#t2p%*^pYJ14MseeppxPid>VcUKou9Jc&4fP z#BT+MfsJ{KuK=zn9k6Zb_httYtD~xSnt{x$$O7{mY4|GOUI;IBqW8(k%i;R`p2V)8 zQ#^j_o5;jb*DHP-zZH)jZ&JDPX7JYT^Kw(BeH3VdV6wX&{(`de7ho8v2XxhHx?XVb zN{h1BX0A2lQPh9Rtu@K7f$CN}Ss6`8BhFAtV3Xz+w zFHg*k7xTGbG~%OjWwSW1Zhj`m22MJkwmGa*`F@a_phcfw!nn2R;#}jVb}We;sIr2q zz5>u3C0>Oz2haTPq5kK9Eokf4mB4-!K0@>Vvr!s^61KRYnPite%InnlKcd)`Oowz< z85DZDil@p?J)W+bRVsBfnF1Tnhs@yh^Wo4BTcT&oM;((8^z0Z6quRwx#`pJ%{%kw9 z7r=!a@Mp^f00|q_&KEPfz8Ii@>4OnihJpQdh?`%XWz7qGGEQcHQyxM`H~}k`V4?)E z1VR_~C=ZMMkV>ShC^I%_7RB1w1;afFiwAshf>&%ubMNSS2eMatb359z5ce(=>(R}} z-LHam9QuWqJQ{QBQ|crRy2k0p1qh>R>1GV$MgHk^;jgnPYU#UnHuM+n!R--I1!IsZ zSS^UMq;gi$F~ix%$rjGEBwpFGc&LnQDS#og1%+Ny)&yp)$GUIpE-YD_e`)r7Ad9{g zs#PfyrC~27@Rq51EgwJmexc?*o|u=LCHEz&uPJWyMA24b&_=nK<1Z_>ogtGi^@tGjqBeWRt?r=vU=KJC4?4R29F) zgF8Ha{J_?`>n&`ld=#cQmMN^_dUyTX(Hi{tw9}G zw(C?tQm4@fXP6Aji&?N@&zRN4c<835VP;sUgxa!V+Hrd!gMc<}2Vs;L*-auqc4Vip z+t6S+mNRm!*iGDk2r?0*Z3{=i0?qHg_c{07S5^HX2{1DwHPpNJp6`A3*04MxZkCKQB8lCze^d3gEsnUtR3hrJ*L-M4A z$`)ttr(at2-D2#QOL-(Jk-TXja}3GVaM9k5=*A8UA59tO0Nbu+ zB0#TXlXlY--!CTp9LKSjgBkHBF`z4csMUf4KB$V-Po&U}DkyvS*#}p%g)Dn9QveF>4L`>_6*DE8 z{SE6bwLze|6?>;yw|H}1cX;B_Km6qm^`ac{85F}wKoR!jHw#H^N86E#*!KP_t|@ma z&r6hNp;$EVwarYUMp8b`v6U~Uw1AXs)i`0A|Fn^3vpgix3uYtJL@kPeT5?=S@4CuI zx{JoDPLzwo%@SW!z0iXrSlB{XXX-BoZ+(N-Z)F#rUdD^abY|YToDqFZsdnY~ai`IV zB*=-v2wfhZF1EOUu*!OoB*6H(1MzU7sAh1pe!$J?;TE)QEF(Ga@JBzd)f~z;xAg0FR31iNU`jC?08cxk zFj*k(rEmH7A6N_2DKz z5;;TS%mzmyadGq`kz^3l+X_BxqJAw6KN8vI7K_r3M7AwJA69=`1A;B5?%RY$nz2>T z{}C<5%nI9_NN-AbBKMKa_YCwsMkbP$-*7)S#`o~ zh+w3ejU}nZY*Y#{J4w_Y{o2rQbmZpqGKX&IB+FgY!*QcuMzS_FrUS(cpZoGBZ!KXq zz?L3~$x!9=uoOpzmS{TXt?IL7@E}SMv$~>MrY+2P|a0+!r zSPI?>OL6h>U5!HG?5p^4ib4`ui}2h0vOsu}>hxNBf=>8y!QLGnSN$t)*~xDLAxB-d zIpre6klXnnz|4s14#NqNsJz24S6@W4K6Q%B=!;A7X8dXLrSOh2p4POz2tWb96b04# z6j|_sWy3`pQhhD9i3thM9*F(kECHk{o^)lMp7Ua)BxJtaQ1 z43>#_N`WdtnU?(0`AeV$N)0cPq|uUBI)6Rff=*HBn|2La(amXSv4QOBFwV~q9_US+ zH^S_W&x|tbyzbCWXy_R-T6!>{DUT$iaAzLop(n!|qM36aL=0$IWJSD)bkr$=sK1B} z2#e^Ho#vKa3*-d{DmL0GO%Qfs;0UW`_npl2rT=?*@24j6&MhNa875^5V;zw;RPX{h zxnc@?bKpXp{v!x1_OmxY%z53r9NxGul-K}RSpJDa*Jz-0N5CNq%y!CT=}dSOSq z|LJ@Nv0pXea$-9^yZO*1rXLib6IP1dY$3J!pRl`1)FP4~D8^@mRYm^0b)~zTFHo%3 zP5W4`#&t3C(0AXjx(o-rl@{Y3v_d9Tn=KJFB?+?4c_<0*Zp&a%d>-d{vukKZmi5YX zw8Eow+5RvHiglLpR%2?y$~*b4KYZ752mS#kr97q*`lA#;#y?<7%Jd{?VUUD=axw$& zjFtc|IMRC_PK(90($A-)uZKsxCiRZW;Xr2c)5`^+jGjSO$uN5egvUUOZr#=K>y zWIV2F2PmEWv~2B*eM}>SVsP)$R5Ya^ zFI}}oi3)~8?hZ>8Gr=;{78NyCiCQh=w8vGOs5N+xtHxG7Qz!u%NEl$8^JXq+X^43R zpXqW*8J&n|g3%A!b+eYcgG(^iU9s`dGxj(%H=7R-Ruf@j8RvhHunti6sR`V)!u)~# zp?^?kgAkiFGUQ}C_3X7t;xT#J>#|BzC$gO`t;cgm*UnmwSkzpf=&v+%wc7$l$b)e| zLS$m&UWwv+U5o;0ri)d)4u-Y@dW9X~v3DZbNcZmlLuNOAJW85snd+f7p;Sr7AiJ+xOy*lw_#7}cLy01=}01Fp#UhU zTK(#ZVBlek+Ez6s<4y5aiUm9&SrvVNiKk{LckRzTZyAnfzT4+a9SlJn2 zrLn)v?2*wb+MLFTSZofx3PG4O?HnW;$`{s;9G+L8g7` zpv0Ow-k3UIW$WEmg2oI!8BQ|Sj`bHbYOpSidSc^Iui+l*+ZgwdbR$rMmrMswI^zYS z=a`~f^W(E!8ylZn-h0aAs)a3Z%aj}}4mNs3aI6vCENyprXu04%Hz6L#>PWock);C>*Of}#yU z2<1(O>SNhs>Pbb1*qHRrKY=)epUscx)i8g3<>oSkeRFEvuli-}2QLKAaZD9W6%uc7stxOmt*tgBTu>I;nD4dyvgo_-=;Sj8--t6O zIa`Y!gV?&+GgdP`dzf|;WHc)mFng3IpY~N4J|J`u;2NIrRWN|uR#5b@wXmQ889Gg1 zvH)LoJuDSff)RUa$zD`}wXshIB(n#SVSK)TS480$vjnUkhrtM`%pOzsGa=P;?&k=0 zbw7ni?j0ye0(p&o_Yp4mu@zB4F_~aVko`?)>qZ(7@MQBU^dottWsG>_yHFz;qDP`H z$iObmYu|@N3BCdTypEFgwI;NY_yw^!dyOt`6!+SJux>oMIzKdyKt!0as?xiSLN~| zqatWXr7}w+w;9_Mq?iY@EpZ1NFmm5ohU5S;yRz>LySjlbOzrfEti<+OU1F-09a0Cls z%@rDi!4Xp&V2Ymnb^w8{@#fP^LR5up|jSj=F2MDCE&B+9Hlmj01d;2&H1D6dX(GO7NN zAEHJ0M}!Ff*wM$1f5Z+4!;*Od-ZpRnkGRDZbBhz1SUk(5Pj5>!EdI>y*-saA9-xOc z3(XUU>0d7RWQeSCwyb2V)3Ow}W+cEy2$$8=O*4vqk@XTZiS@e{nt&3sT?D194%+uT z7brVt10@OTLCMGUpycCvQ1Wp-DEYV^lziM6l=&J^3Whfcd^hhCnm6I%7H)7YJa`lu z-P!la=-abR&fK~tQpOM79(L<;bOjW-k)QM5y7y3 z{)7{gL}8=Hk{XS;WDZZbKzB?cx+JB@w%#yUwBn^$?m!V-t}y%LbQ@k zY4`-Ki{_+*LxPUZ32~^&j}Won`rZUUWd;?=(%%SxvOgA<)C47>>;~gMQCIp5#*f*c zS(P1eBIwVvsmhdY#;?}bj$hc(9RJU!@sGOt493qQFi2lOODCUOONdQP@I?H-yRP(P zJ$>iJf0p*e($=sC{}UM)nG22?C@i;RTFhyJOW)@*-@U)m<;G&!#`yj2-y zoz&}iTCk^GJXQ8|HBZ(@r~_FfF!p|=Nvn(^4OBVawo(+mDf; zbFD0dqh#v;WU8s4S=-b*jQOsnT~Vp?9G@ z5jCRFKd}ZkDD(~`2=AI>7TuxHUke`H4GP_6T}a(epc?CbgeA`136LT&RQixAo!+3* zQ%WF@!z2|uPo<(JRC@o}dfG&u)zlNw4GJASTThyW?n#A2At>|&d@O9~2hhrg% z^nXUH#I%N2kF7*d9D&m38nO@baD>5P^`-&1|Xw#wgRr;>=sg2>@+@uLL z;SmT`Y|{vYUfECF%~z=)4MZFR-`R(EOch?Tw!(3Ix}Q90Y$iKs01=PDpJr-WfhSe1 zbjJ}cHiq}{vdG_}ARkf-5GDFY%@sfI|GfR`%lnHtIcLhft)=}<3=@NbSF)RHh_p8| z-f0UFNz@|p$zI3(AcQaZz_1K+`{ad>i$uq4b;VNuhY{G>QNzc}a+L~7N5*5h{3t@} zpyfkoU8a>{X$2qkVySz_K#u*$#8PFk8zO{e;nsXNg0uP;PW=_oiS~rYc@AKw92!>g zDqHUVtb8cU$L86O=a1;k1q_I6-`WxN{*{h>`7E-&*UX~6JWZ5KO-`Ii84I*wJ19yS z2+&WT#yQF+OtaOp*=ogE5U9bkxjp=89UK(4c_~ohHF0Y^Pj)u&Q!^O@Gh{PRrWv?^ zz6jXgiq7;drFXz$s6SwmZ5jcxUnqLho?`H8yu82YZHg(BfwJ_E6}?}HgbT;TT;fCE zPA=Klz;rBZ5=!g`uN>QyFJugoUk&M*Ea-0jQ$NuBgt?~v1A`cErZCfy3`i_y`4uvu zYT`@S&G^zdi1bm(KuA#OAksa*=0cHn`oeKu%mMCSj{2LqxFgGT>~(Y)MQPLdB{P{q zL+E5o9~oE~y$q}^eyqb}>(fJZQ&vL8=6&TMb&-fIAMh?_-o-3@@Rz-d(CNM5$OmH= z>+#hOSEmjbv8jg-l+;uEY4KxxDC+}70qxf`QV=e8NzN2?pBz~MG0fZS*biaoZFbCx zKR#C5`)5!-R;k+Bhhe8&w5wkZAX_%!a*>3=7%*h=b06smp3EEw9F8$Q)neUkPGdbx zt4b65&a&v%XFp6THOobx%{^5eu?2X8Bp53V?6_>}ejMwefc@m9w;)BqCTh5cb z>zEZ4#?nYH%vehtlTyOmf?tO)P|hoHtRS0qP_dMobr|z>EJS{;6k?XC)N%nlWd>iA z8DSR}WO~WZyr!W5<44$T=1_NiG-Qi$9RP|!IP+G_dTge8DZ+7HVXkp;fGt8kvqsd1 z1+T2Vs2{VHfPRq_pfmS7-U%q-NnYF)w5c?$E90w2Mh(i;tEbHJ-s8B(%4CWK#a6td zSNaPJSSfC5t|)9>j%xNJb1_)Uc>8U;0S@y{$T@R@A4Fy5c`&IBx;Qdx(3ZUj4}d>c zWZ5+tQ>XQEqWR9GZ>+cR_LK7*0(qb`6{@H`S^wwKDAqAUMM}4MPQB!(=ch zZD<>@qF-qiw9I9miu0bfK1GA?g1T4W(7Zwo_LJOa{UWX75Q1dqsA$+@KJwn(d2oXjUR}g!)sBySV z0S+8BG->$u+1w>3-!$1Gh)xCUM>8%87XH}rLNRV)Rucotr9U>j0Gr5FSmFKzJ9pG$ z@TCCc#8Jbb+C&Xo#6p;m4a$0r3ip^2=TKv76#9dE()wvR^$e%wYzx$7m?+w;pB96` z*tB?OKnbYHg)Y?pP`J+Kf>;D54jTZE0|$~5?1$|g!^$)<+NEYQL+SQY&y259fJVRN zbx3LbXxr2{;Rd#IRSmi94qM4;p7Gy=DJ+p?v2svNQ~|z77)AKe>EBHEEoE|g&@+^{ zDcL2lsg~@H{)(21R9hf^H!sf5tZGq6s)n%Jt7e;5tOoXKIOopv>W2s`yAkrFFeEY} zqiu1jcKL0zRPHSkAxxsKSY8C9$vFvv^ zi!X;J(AF)Xm1dbc>N4NpHS<)Q1V#19(Y>``jYJ#vPTzqMNMuK?m!s9kmNaY8_K8%p;1ujG8;WQ^` zw|<9iPl)I6!AcIAY^z?NOFHII1qXeS*%aCY&Thhq>m#}MpxSg*2Kmz^9XxSJBn)wd zs0!T+f%@CGgA)W!cFn?HCE!{BxlOPX>iWmTI=(8aB*}P&)0Q6&wJA*PRoZtA37esY zH+^^Zox}3`@-9gWQe4qUHzB6rk~DZBw8tO{+8ndP566o3rKP-3ax$l2 zXjI7DH*P0sXi~QvY$9P$GE8JQS)8TkS?`b+a4DWw|MYV8HNeTRBby5fDg`mcVVjM$H@QFSM3=9k-fyca4Qs5Eo#2fyqZMqc_* z_*zzcP=hM3V%ol{HZis8>n56s5aQ+%|59<)A0vccZzktvjhJXFcISuY-h)aAKEq-( z#S`hykBX$e5bq_V^V0Ydysi=Em0lDbPiwb>j#2UanoSkO~u+f^eww7;!_J z9vor0O@oo51W4Uisy zyjpZT*;O4(gv9}*w1pf!>ieTW(uPYhA?#(`%uaXWwpj2AFAm0dG7JS*ea?COW-I5wTm zcxQ#%aqEr*RCK88^@=j~%<5(&}(5bnNg@iY9CZ}uD z*hx*KOEWx9gc;%;k)irP0Wm1y$O6qZN=+unmvBf7=LY6TXr^7b7cviyY5-IoS}jgZ zc7Uz0kIcFyiuAhY5j}g%l=q6CL}{(VHfsuIqbs6HorguK@+s2RUrSRYPAP%JStaN? z5(+?d7-ghTJZ*}%L>X$!&_v#QbC*G|V=41AOa9vIWxODv-UusWaq*WU`~5pbrpR_x z_Ht3fLa`@=hZpg9Cfhr>q>R##K&Na!G@K6>#B_ePD7hVuuWbsNjbA*{h2TPHd*$?u z_@>tw9{SKKTcrK>(KQ9*u#3#m`kxtPUrf2eX37gVm|#280hwBF3K zj2Z-o8;A2uuDZWaymq+Vr`PUQ^IkwlMQKe8p!q}eu;MWEimlp0dui2t|0fNkE4qAU zM(znd1`hJeg*=unSCFl)Bu&Fn7a~*^1kw-(a<>3r8SwlN=n8m((j$Pz(!F&XL)*sS zqgcs@TYMOV?!AH@s|S~8?$ecVKAYDlHHL=?MPtSw_N7rdr&{9mBVsOAfH-%)%FDRSReYvdPCpA(Vd?KUv@}oN?|m(V(Cetqp!1XcBs45 zCE!UKtxU*eawXnETa0y@6f6zYCS1$#a%PjHiXZe#xrgCit1XrV(FpEZQ}e@%l6)N- zh*C4MBXgt%5IM{(mt!9>@XC3cPZGIZ#S|rS1A^BmT8(^tk+DM~cXaA+|{t=BN%NPp<`K@KR_vcTU%zva|^}eFe>X)A$Nr<XT{dJ1WF9K@qLp=T#2pTkX{*d zYZpU@+O;zx5e>X$p+ViUa_JE0Sj+Xtv6j=<5gLH-jt0(|*-bOYN2L0{DgOKRZwmiK zPL$6Q2=I;J!KcK7yZ;~H!EqpCJob4WYyrogd&~HhX&-38tdGPZ*SmN3^1Am?e zI}a{5;=!vM>A<2V9^u5r6^xo5&=U&H^#^f3CI+sMZzyy-e{j1&KRl zqza3Z9wSu6j8e)q>hP`7#7Y8|T|bi{Ds7R0pvV=@uZ_9M?xm4#_ox_X%NZzlVv!K< zvqY3Ly_5Sai&weUO0$y8-!$A(Ypr+X64Pasz%R-APH|)zNrH888O$Y9ACG#g!fhtPVt(!eh+MTo)8W#62arp#U#q|XhUwRO|XD9X!T3f9e+Ks%uBqL3$zz%DNLzemY8Vqht)(%o_w zG_$6b)8bHeD?PoQ^AVA_67`fEYpyRoa82=}W^XbYHxWtdYpj&r0KB%rDC%MiH7Zuq z12epe)RYTq_hnepSka9Iq}Mg(8M>xD-$eJ$ZBYRLE!(0RH(QlU@hUABA2w??*m?EjszUF`O^M7Co`^?BJ&o3s}$+r0zUB!%R8+3tDMIcybH zt&M|kJJUJa;3#s$4qzAGycl|Mj|W;aDD4x3e6ii8;B*av`u~L69Mf046#rUU2LKvO3jg{W`#!9>1Sm`jgg!Rb%UR_zcyuBxF$^}#Q+`#lp=Yw z;JO0n+?z#;v+3M@36-+j)bA#O`fVXhS0B7Gxim_v<`H9pEDkVMMinxBV|8eXv=M48 zQVE_gIPbUi`;8;khp{4Zm9ZYr#eLgJJTraYmVi$Yr(3$i;!5A4(xdI&+O z2J!fSM2HR)NK^ni%_z)k4g=TEpY5g8x~w141i1 zcST{Uhvklt@y5K8YLj|u=WGcT%z4eY!8b>z*}9$6_HMniSGDTx-AVG!X>llzcK_7D zNf3YhBi&Gq<6lvo03{64$fKd(T=W|wK%wvsm*LlFe?6yp!y&hI077}I^Tn`C{*d|t z03!|!EBj*jH5}-s9=pcR6dpSwX(j9okmbxOGG)~ch40^{kz>J;CpF<06INnhfujpw zInSx{cZ^tPa|V;X^@u7g1(>TLf&~qv*R62!BbW{Eh_UVpp#c^lj#(m1b@od-;dO>yC=daI4)?#G=X?0mKP=B#2Bd!HrkCg7x@o`yI2NcJ{D|-P$%6_z{7M!DDN=jsI-xy{!Oe_~#@`4fwP>n~oe zx$={)@iuWwHFDi2P4h7RPl&X0wl_PQ7qjyIn&{fNSjz*AgB8wJIDR3TbNj*_Lm*(O z*fZ7Z#duR8_*X96NJ<-+*@<#(EtuF=g$NtNKl@vQb>%C?-mx?Il?$gmDsA;w&p3=L8aQZAOJh2JE zpi>p{#LP-&)eR|m|Bb4ntH5zq+q_=4VrS(?hYZ|Cie-uW=6H249~>i~$`B~r0OgFC zc^pY|7iB_(L?!5qF!bYoSwU=H&EoZm5z5Rab>o}_!Lb-WWN#ctM{nF#8Lf)qX@$#B zW+o*X>Az82Ei;Y){qyGDkb$lqAWv-xO0%uJDE$CeYp)+qvxQ8xB>Otn5)io+9dq0m zPqK#aH67}hPK3PbR=0Ye&Fe9m6_2jf#}-grGWRm(DGx*Ovb^4&$@?G98P&Y1XQVNAi}9%3j@*OCxb< zj(#~Qfax)D8H0yM8V5CUu2jL?rtvp=9%1%aZK~V_0jbA1P9xHr)HY&G<72Hadi%S> zl9SiVv)|FmJNdc(Yv&yrFT;>9fJK)6(=3XM(DFwCO)`$^X>w2+R(V{PE3c>Qr?cz# z^Yb-+W(b^MP_Iks;3>rb07vcJu4g#G~8hm&}fj#sckl$W94y^=CK`Xv`ft5 zL>{soj{*5*`-@Y#P`p`l%Qlv-!VwS70((=|f8Vk5(nImRibiKRBk0^PDhq_W!*pc{8(VO~c-=e$a6p`u1X zoLYEd6GwThA_F-xxV;GTl%f_jTKgy;KP`A;+Z|b+p%Hyqo;{17W=&NxLVUruioMD8 zic%IImO899+n$pTIlaY;&5ofOq6>m=VxjT#e}EUZdUT8=;sbkEx;{t*8OW(NvTCl4 zS07>GnQe2Mo{__7#ElQx_csx<%=#LqpQ)z!6~&vir(|od2#!FimxZlX%~^Bj2p@JD zKClUPRrYt-47h=_rx6zxM!2w$aN$bBg;zK(ygc^DOz-{g@df!N;iKV#xU@(RPJw!4 zd)I(qXSwuILwh;6BKKjHT_}#^tY+IS0EAIxF(1Dezh3=k!hE09W{g6wRT<3q`mW6a z6{tb!*UB#X+Iy1J*jJks_>)1yw-`2_0qfVBty{VkAWeWfc&?oQt@`s$0JRWbO+!I2 zi8II4Mj~P7s0}@AL)bgTT7dmY2>{t_E=5?;pqW)8UaAemi*?{zt8SD>*qw}s0A?F< zd3onqwnB_WS$jsuDYaM^GI-VnMa=|&W7>o0{pY_C7WJM6i+aym7Nz6o0ti%OG_=vT zdXOpKIk=S7;!Bzl8^8Lk@~k{r=bZ+jfU3}-xS|nrK8G?WSB>{%TWQ(Ik zATjj1$;n*O?03`1L&W^x_xhPhd6E+tk#~*&5Px}s-ivneqnignGpjBxVCc|uw%rEkDDqA183(N>(+(Uy48#vdJR`3!M> zL}MVA~A!ez$HKEQ#5<^?65Z9avF%Q^f-RM6%ezE3LY zGoQn{nmC7}YW@c1@E(of8P8!|>b!HvbzT!u|7g2f57rvRAC{ry@*q=5O78v~>hL$Z z9oPNrUC4;z!W}$dLb{V@zn%| zVc+4{zhH!eaOz*8IoVCoh$AWdK98Jq_KTN1(!Pu?%^M3)mUP5Lr`W|`b%To}NA5Ef zb;l+(%@1~^E(st>WMfqb5H7dU#e%FHp|>LJ*Lq|-JdS23vshc9qjVke z9rt-`0Ws5Xgcpk~fGWpP)C_xK&0@@|Wx>wOF+Ni?m9IMcP;^{681DabuPd{J%PEUS zNZ6x!ds4{Q1wHA2gg8`qi2Ny(fk&yiw?zIQ(M?^-)`8lIzjNvn)v)5KH3{JSQ(Pv* zB;BJW?oJwGNY|7X=^l43Ajk_ZP!KE}KPh5YMbc+CVd3X)ge6S~XL3D`kr703N;)jK zn{C>_!Hme_hRuzmee_0M6Gp8;9neFFCZ8fsTei5vicp4NU%aEOtGOkeFgHkty2zoh ziKlj6WzD2jFpirCg5Atb+n5F}(MO{2h@JD!7Oyh0rqzSe3~K3qDdG11YGVJ3Gmys? z#E>fl3ab?fHq&)^s~{}5tQ}8yOMIy#|H?ky^{*o;hch4zxP2_;0nk2OKiY^FFF@&L zj?CaWLU1TQ@D#w@v|+c`13U+7E_7f)`e_S-IZ}<+UE4u^Atwf;+ZJ-I?Ml`e8?DWP zfk+y%BupvFR2>ojTVz0a4)+({4_)--ECx8I3F$`j&zk!S$8+}Hg;tvaV*;DLE!Kub z0#N}#+D*qLtS%346$RczfP9I0d3gN8yk4wVm}VXx(d$;d8eh%Befp5XFoTgGnh~M! zo0d>Xkq9Swwy>=kp;t9?7D@LV#aP#zj-0(~&G?ClI%Q6svhYD#5PE|qEOCX^7v9(O zj5{bJJ*!I`Sy#e(mMm!-lsFS9;HVA3KyA2ClKOHy=F4#5(^0Pkl`DB_(zz6dJN8(0 zSsd=$J8s(mAPQ4jN)$3i)Qa{EDXUWdBYom+6C_1yG4SW?62DyX(9!}<(kfD|#biJ& z$ihnJ`*M%1q+C!M(~5x`PZ$@dPbbl3z~ONc!@sQ+>Cie;T3~I6EulA!HK9!ob8^zQ zLGDY@O;D-piDavut1`ML-FqbLT2Bh^$%k3%(G(?Hy$ib%dtKNy6UH>8GYY#CQP}ks zpge8Kt9i~rSjhgvzrO0S5z&!^$5;>jf?9hb1{i($_vGRChs+Z*D13%wV@Q~uCQJ;D zm3Pitxm_-0%^(u2t(xRH)c zoTzX+#`)^A-kU~c#u8g!qi>Q9jEXdHRK1r;#n!=FpI--Bk>7xIK(NjWTL$3?t&<76 z=N?2s=@WXMYZaWU>aj(zrt0U{fL8i>*T9n)relaz_s~ncAjPSvT4TI`w@t&+d4WvL z=jR2jabBSCC?+ObbQ0B`cmaa+Yw+22kVyy3fpc01~mbBW$Zi1`lm=Bcm(4LrYfJHi}y~2}<*O!@vX> zqkM1{kwk^7BlEFVaorraOiOe50tB*WG8Irng%jcesUz9)msa2v70j#v>vhaQ6&`fRcNwhmtghb4$B!ANH57 zz2gIWp{_1eYP$qatGT6x+wH&s;fkNmZaYM_tMB+g_`!XnoJGnxA9J4m!P_X;^|v#Z z-F8T&UbLo^%n1asTUhfMIZ>a7Yd#Zhs8F4Es4q{>ss4FAKQ(dNA@z~xub29|>ke9* z`@nE2Tzqh>mkMu(2U~yqt6e%jSM?7q?O4+yq*lhC&fc>DXwrJB#?J>JBJ(*YpAmp` zsrUu}LUi8i~zL7O0B~n zIeu+`K^N=ubHboYRKq$jTx9ibghBGd0zjM33ZP4s;ewXfwv_W30cg9GS_dHc%54Ck z7o;|xJqEo{Re(XQwTtgEC9sR#TznZeO2VBXs}0MW;~h1kYtXyLMXW@GXdNIu zw{*sOAnZ7}e=M6(Pii<&%_{kYW6AjdQw!VmlQ+*ujMh$nCp@~Awm+QuD{yW2&+Ojd zuFf+PiU%t~HF=Y~ZL0Ro1l89i#_T(&!#EyVU)fS=46}70?A(W;S>}mhYmct#lu7(P zLGDRl?)Ax*MtzfO>!N|WE^ZVTL_0UnOnGfc$Tt&NjQHS=Bfu-G`>+mPaBI!0^_u%b zm{2b1yl!7la8eD-Rhk_104z#y|Ej%*p-zSJ4ML&}OS#4>3po`qCiY+k1vrDr7t2fd ze(}EoP$B2L&zL3IoB@ij?sW0$m z*DVR9S5sOzEGGTmg!_f}cSx#NwI}L|RP^EcO%lPQ^&3M7PtE&hqz^0`$t1acT7tRu2wIJT+E<+h z^w=~Jmgqsa^EVj}Q-bjGu+SqJ^%15xBR;K&rCgGuBLMa@%@5t`h7Z$V;LYlwH{k>y z%v-Scmsb^Yw@ki;W)(_L(jA)`Y8R|iyY_aSzwM%i#pS^!?_=M0?M4fORJ?i9&dOvG z2Pe%fNkg5fCc!Imlt13^bIx5=oRvNcez5Z}tDOfA-KapiQsMAXH>>g_RY=Zae=t1p z?-1@EtSId>1S^DULY6?e-TY7qW}Hi^zIrpOAKR7T_FjTM z3#>R1B0duF5$dy-Z^Bu&t@aY%z{{vxM4svfiI#`I;9{jYVI?@IgIrrR+sWGUE=03s zR>6ocu#jCDT!lJ}wTEU}HLRi z2uJsO4+-{Kq)t#h!^O=Ek`KFeHmA#;?UJ?G`gOL_NSREvdvmdKI6%=jbxiGl5I$Hjf6Nq8qt$bJ;E~I zd+1O-L4|^*etS;O3j57WPror1TXqo!cX;xqMsjm{j(GCsMsjm{aP3v)TN=sD={e@f zTN}yE>AA;~FKQ$=r{}mQU))F@o1RM=361HwA3CybdQh#tJ*Q{Y2RYvuOLKZodh(@> zm?yuWk=&e~GoJjyM)KJ7T-HcvOwW@W zO%Gb)x7YMk=nr!HDWsI>NFoQpwrcUO;4cl0Mr1;;9kzDkGKcUUj==N6AUs;LzR4Ly z3pQcmONdF~L*k7u3a9yTG0=Hqc(~@CN0eg_R+PiIr$iN`fyfWT_Y=7YCT+Zhki<9W z?OrHQg|t!Keh5xc^p^4gk&TdlkrePTg5|@vC*X%N z3Q_=UXbOUcC24B6Gzkgj7}0hkLr>@v;<&haTg6%bMDihBTz!oClQI#Rxqna0yyUBI z9VmzManF(XxU{ZO4rk#F;?I;w2^{5c?(I1e_m-2(D2Fp{&yg5+8QP5;&a*v7;@Kro z7}eqI+H)j!T_OouV0fE}GR;Kf+23wRjgTjdY742`(t)HLg4vw+MYn4r@`>L{SO`Ld zLwak9cuyoe4^ig3v(7)ooZhFmuSq)qQT(l0`tCY&fj4pqx=u-(LzB?|v4rZ_81p+P zfp~e4gJ-pOR}*#5^$c3uSnZ^vA@yeA_6;+ekZ0NQ9?UEe^Os-jwS$uXPw{`<0wH;~ zAZ_dPcIo7-hRvL2EKw4*Bv|yjXXaS@;Qtn|UsVE3m|(pK%31`HJf~IIr%wK@{0>kt zl{&8`sVWq^XSOz0i5GP8u2aUvO>AO~mWU@id1oQx+{Hpvvab(McJkgPxpfoERmr|M zJlV+eNA|>*2E9Z%rJLd zCJhB<4)YId{h60G_uNs zr4Dpb)|HQ^4&cHPj_A!WsKvZ_Rx0p;9ILZ-LMI$=WHnMwtwqYZT4!Yj!$Xa%wtoOu z!yg-B)?_ZzHN+mRm+vG<&jkm<{lQdRPg*}(M_N;g#5ci86dyo;nV?=@v(=Q?MIOFi zi-(*q1Jg07*pOt`GnTfr^3_aiZO)lkVFz=3j>;R!)P}m0+o2$ZTqYr97{T@qr?kjf z0IWN_Q{=X5`9k*hw^O~@2Sue*<*dyJzq=D6q!0J4{cs|E!1gzF{S1x5jitvL<}(tt z1_X7_sfGaf;Ly&SXX|zzt!vp#J@j}c{GYE2Kd|rK+p8@MmysYgFrv|D5HLiL0A?Y8 z^8Yb{HBJVrnc2yHhT5NT{xy>k2!-BUa@jC%_@=e0EZq0WXapj#tWLqRJ$^!8-1Al! z%J=#95Z}z-cBxAza9`#e*izpgdDY?BrTNio$n~LWet@{@I2E54)_*3chmyOrbK@kE z3!QF((uO+)IQoLIyo~C->yGP;)^JZd(v*n5JOPX| zV8Fqx8&Rn=1CJQ6p@z`p>HIcO<oZi1j3U8nVb>@((xi`1m{cEMO=t(%*;+(+KwY!g>Iw#7 zhdjXfmJCFjs+R5=*%a79O90;#*ss%I#Tqt27MAdI!d#ol-38B5 zxrOjlyU>^JLSKec|IW?3x{gXtrouCKq322d^ZGEsdai^a098J*;HK58!SM8;)xjBO z_gpQ*iCF2V1S5V{-CKDU-Bah}_~#SEJ~&oy3pt8)*yd}Yc%?etMLE;mmwIB7Fgq(O zpc6XHM)5knJo!<<=e0bE(7j6YrkPQYSv_`n(xfe-ZCRdp!LpFAe$zVf_Hm6^~)Aq#Cz;C;@XS7%A)CA zKagy9c+YyjnEPeh&~^(*QRQ^Z+=4}SbOD3#k?irBSmLz$v=$-Vi&-%2(S6S6E|c!x zv@)anH$fXULLcoeQ5GBHrzSff^}Bf{8anz%!o>3IOy2VUimb&AJ#yjr<6m3F2^=>= zjUjw7y+K{s1Cnc&`6;sua`%n>s&dFJ5lY8CojY$)@^|%p&fFEFm-4On+=MO4NiPGW zqrURBQa|rha+@<^B+6aO&Jkd6*SqZQ={@WHkM*sg?!gRjb<$2poS9>b79wEF9}lpm zcfo}?BJ5wVq{BVzobbfryH$#q0qKX=)(a+lgR1`he9WPKt*^h&iy<$}Vzz_CU;+aJ zIIJz6uH5>~?f9*Fozkm$9RK-m@M=c_Z1Vm@uXZHBJmF62bxVCh;6A-JPY4{*hmB4M zyxS5g5!-M#&+AVJT+AGbKFW@nj?o9SPAEh2|^FVe9!i{!`BO65i7D&uoH-Mubn{la)G-htp`Lc}w9}r%0~Yu~ksIxt8+uFI z__M4Mi_77jlOQGWd+hSRy#F?%K8i>k4>`jkL}cWDAA?8nK|+Fc(V$d!@i7y;Qx{)i z9avmWol;lSI9;J{K(&B0l?4}1nqAo$53s8rgaXjfzOYJpLbtRP z0U$gAxWbdFQBiAucvh?sfnt+m?GstH?j+5V> z`B`$wLO0clLRqzR=NfW%}v&ODlb~ME@;R2ErJ*p%jAE16e@ z6LlT%wWUY1S5y-NbOn#RbLMwJt3kNyK@iKds;!(zjnvWX*nbPlNmqC3!9sednj3kZB>p?;_~$M<{i@N zbBdMk5CyM)Zx~(+h#&@hl{#)Z0!lVyr(pO}AG8yXnN$^V!=>uhaZ=>B;>s)=g55g7 zSW1y$dk~W|A_zT84qi&^b_b@joSJk#ZK~MDpw$*l+n?FHeCk zS*Q7+jHP9q44ZXG^GwDmYTU0xxDop`$}qQYI2opPLwL?z9AKQmRMR?WY-1hCJWt_CN!8k zxQxZM9zdc56+}M;4^WzWgI3R32ad$-&En9R*j$5tM&7oT`D7!$HgqKi9wO#8fmnnv^K#h=Qgz1K z6oY42#1sBG3_>nt`ZzE_-q@s@YK&o$fcIqh@)tk!xefwBPwFrv9lqB8K;C-=&!|&a zSjJdo0jtz^MDWBa|HSZ=>}HJcUYl2-A9k|r*7a`hA4>hYz-s^c!mB+}xZwjn-x8l! zzvaAYCNpPL?YiGlgxHH!43rDlpc%A^cpnji5)9z<3Jf7*Q=`vALDVbV02tQ_jqCb` z=qa#zgY;R8tQjZ!xR!s zcw!5pTnw8jZM%VN(VNosfdH`yLskpN0xS$fE6Au3augvxyg{Lc zXq@uQhq@r<%gpHXgm9e!SbR^r)CRKVd5r>*8_+6H4`10EwzYz@FnSPnRfU{%PxpD2 zq?nBC|9;NZg2!tA!pa#H#~sPang0fTv_0q!zY`7xvy$8R3lv7%)+_0SX>Pjb3l?Gl zeCSLe;z`VMQB&@ebI969-F8OW4u>abIt_*m%TTfK)FkVp32JK&AFh2G;uOC$V`>&V z@wnYkLh;ss0j7{;+XlnA3TiK>nD8+Ik^ZgB{_CCfzm(3ZEEUm6hJW^?O=O}89W`Js zIRA`3CsXeZI*TO~zjy>W^W?II?3K7v()B-+c~>~bv5D6|%9iP+osC8t1*&wsIYzP& z1@#r#T;sp7m&W%EA3bwUS`pJrep_>G_Wjl6Y1cnxH#ZfOPkoc0c#t(hOjzF-C|iVV z6rZqUSF&GhPM>=KN(Q$IBq0&VY}bz{d*##~KiV zl`~G~TjB8UAq&`kZ5gfs4;)v!2jRY8Q72(h1`4-@u#QKPD%wpvlGRdp)X|b{t0u*Q zAdaS?em_<>^-Qaqs;ir7R5yRnalGBEu7TyQ3^uS_Yf`+RbK4>v4^nKYj5B5@&UNRT+*8cOdr`+!cI@?RP;GAbXwFirmeQ~T17 z_{GEs10ZBJ+J-O+UO{;4q?VhBiDvAmc6+bIzWh_ zH5A1`OB1aQVr-Om;9!#fV{e-l6~om-7S~)(YT^aXIkcXu8GxfEdE}>y1RIEKCyzWy z{uQgsHV?`1Quv{=XhRyGlH;u8z)4CiX!9u%yZaJ$KMyBZjI%t|rcFzDU1E75@3H&9 z&8S>_f|NkjmAD!#SS`{~sIzrJbXw|(i3wGkJlYgi7w5=HHIl5TTYLK;1l$=_E<=@L z&w<{#6vNd3vhe?beIQ8+N3NgxklJ*kpg8n&m8mQ7klF)($Z#`((+{cP4!NHS*rB&b z8rqi!$T8BRy3NKGTeZVk8OO1!^PeslpbLy<)}JV%-EJXPT|Snn%guKhhHoyfKM`O5mdPC9)Bm@ZT~NUFBK%T(y~ZOy zKmBh#^}9SA`X}P+E{}%&GxaOWxd@;61JC>-W5wZ<@pWENl#1}{@paB`t^AevI<0;8 zBD_7mZr8p$!J;4Xs1cmbwge_j zhic79M2N}L6BNB%*|c9;_+D-iK4^7FT`uC5o}S#VNN8g_;QVSS_nt#I!-s6@lAU7i z9N`SdbN2ooYaSYR0fwtjyM;*A6odemi2xUJS~K87+#WYfbv)Yv{@iUJVA@8ExDWb0?5%I3 zaCrcoUb8aDip6r#5p=7002p%uSJ&5e+-qyrrS$@)3$^hPP?Pt^Op1_bEK@^RFihVH zS_ntv-J_o5fP)P`?KEOzz!I$EG^LocR$i1eG(2BR>s$DGY(WFD<>qbGE!YZWT?S>; zYNRfD!;{?m)Z2&Z-Ztx!RgFy6J)u?T0uJw>XVHSQrhZTFq@Zv&N?0C-J zWm&OEhsUld8QN7bep|_a^`aayPMGJWU=Q513u|&j?}3r$TPe#1<7LFSM(iO=ta-?E zAJEP(|603onhh-J(8WbHr-0nd6q~{)3@iDIaq@ILLmJa;lqL@GODRdC{k{DkG1IP< zbg{u){xJ6f1iyDj&)A;S1+&9e%#9|fv<>Pxm$W`N;HbN8(j)am%M3-nBJmfY_szs{ z?e-txiYxos8~Xmc9NxGupaT>EamjNO2T%!^^Y-rTRi=|7YS@yHE>FJwW5PCRAmHt? zhRwfQ^b%HNm)RCB#fPfKR za4@v?F;4rO-LPE=+gbIfL9{?vV0e8gun$b~f-p!rDFT+C1O0!`T~$8PeL^DUu>Z&T zYsWjx=Uzz{wNTVeS#Eizlg#e!eM4G9Tmeyd$jS0&`Oq50kpW4MSfTGQP!fiTJObv; zGUHG||MU5PTKk{Ki^Vu8m7On8+GmwF?|%*q1mMy+^?K6O5(^O^;uapH^8~VBVovxl zQ1ZaH1H)2h9mLweMoSY|_6wVG?M_j-7Chsv1z*X(G66cf2krOvKO@fH|3a>Xq=ti> z`N5HhK<(u!+p|4ljNu0uqTf+x7`~zz930|hX-2FX95xZxii z5n#=>t7(YvXC&pGA%{2^5isRJvlOR5a*IJ3GzZExjAiPJFb2h33?qj}WqEuB(23`F^&N5K!Senmq3K{0YVWGx>eS+P(Rtu+oLlL1y ztGs%vu$;QpG`L3F5lRk57M*$`$hXFTCgc>%8Ynq}Sra)~lQnW0&_>AFG2}$91See& z154FMM(005&Yn$$tA|=wt}+TofeX58mTagXe}X@VFLOLI`j`NW~_hn`AdCoVNNW4 z8L~58`v)q$lGhMPG@0Q}IxzS&NHiT_F*(*Bp{&VE3AZ`3D2TZXMO#9s%mIgy4@im} z6_&@ik3-tU2wNnUM`Xzhp?}aFY*67*O$5&bBeG|DWE*x_cuWoIkNV9z;a_2w1Q?Y# z?FsO$$9WoLv!8%Ry6>a^MK}XL{|VZw)6CvX zi1iMM?1Y^`Uty@7J-9>mAmvuss763Wm40&p$jRnU?2WoekF31;H3?zqkvXT0x2^kr zQmc*fb!nsC8X22|u!LBV?Pqz^~9_{XY|CPC33o+REy=e7)2$hg2fPwW%_hY7ZVi^f?o58G-=?^105Tc`&Rp`NDXb zBcbFYJ3wVMq;WA;!9eP)MvY7@jHE%Ale7?w{EJRltFmY`0bO^11t+U;sl&4~fe(P*UlyZna>ZpF} zYQlNgSTMAi_gYm z9@$T%p3jMdNp#`%mYR+#Ai{Gc;Jzy&mpplPwawFPe$Xlw3mBI;+4S|MXmBw(++!;S z8`1D`yJ=Tg+0-SOu0`M^Y*Ii&mIfjAELN9OP`fbL2aq62ZGs_)QX2eNMG#i(K^Gn4f_x{}O`SG)NGZJDZgsreG6dcF4*_#3fD#MQ|3z|1wW z9z=7E#kO{HO^&$+g%op5;pUp6HrD_vSqzJ4vypd_|4Tg{V#ch9fY`Qh3XB|;t-zim zD~J?xWDTSv&fLQ+G*UR1TOQ2XV4Dppub6lNW^)>dtk`4@!&-l0A z{wH&ijJv^R)HHY^I%yth*6e?UdZL{nRZU*A{|QU1WW)Z)H!=1vu^XrH+3sY+{wMjD z;yURW?SFsVvMFZmBdQcWGgR;&OcS!n5PlL#+`XbC?+6V2|0p>p+A%aiYD|(L=jIzq zA6ZF4&M;IgWma>u+1Z3fIv*_zZ3&Io?le2h_<4oKqW@_fIZh3NEyZGz%&J0_6^;J7 zz@%AqEj9^Enm56SY7m%kLN&qD@{I|ejeJRXY}RfX9_#* z%JOcOs3;*s6(^Z}_ol6cEF^mb%mV3xS;dZ`9fPP}=`jj1@GX~Jk`Z=LgpBo`BnZ|} zrT$7?BKi$HGS-2V;l?2|#*l>$*5(v;Y!bhhr3e8)G(TE}9EjVvZ7^j02lHi!UTQ9l zlmN7DrMLyKXuY%C#>B%3rA*!1VqlG0+|EJx;g|{7V|b)1J>_!-8AIL?oHQmm zp+(M3DpF5NqiD<- zN#x0v**H)@C)4}o`rTlf{nLD=mH6{1x048}DhyMWWIzUO^lCM8Z%6KnIyHx-!$ZiM z?M9_qu{JlKpH}%+-)k;K>aj#vP1}0Y+0tKq_Q?0qw=!2mHc zxM;eK&8ug!HuO2ND#?Sc>7=yCym*tgCkq@fpfrt86P4Z@p(aWUbJvt|*V;;0kN><% zCECEgm5p>ewyVQSs+zTWMaj8gNi0YvfM3ZmQ8?#4&}HYcV#yao%ezC}F+hnOL!OTJ zO#&L~o0knx_)aw3vBJSBa2I_h*Ub0!M#Srh17u0M2m&BhLI*KBt;#2j>W48fcf-Hs@Q|*)1R$Ti+f< z#iSRLWxDZ@8!sAs$qM1uFnp+kS?o*ej5y1jb^SXmN4|+&Nb|$GUNHf7LR{!(>pEn; zPYc3U-=O#on4P&90v@O&_~IU?nUD+}6US57CWUomn>1WNXY376So0X2#$!|prIZ-0 z)p*`)QS_?+Ntzc4$I{2EO&p8Y20+*Fgfpo@dq>9n8jZ|ZBgxNnIyIU4Fl!$g&bM4T zAtKrAXPxJ?pKw%K!+)1UtGz)k07aOJq&Q4V_ge+3c@0wyA$Hu(o%yZOBA7CHi$SyU zz}Kwz!|vNhumPV;O`I0NGmMH8N!3_XoIZ1k+z$E#ewX+4=CpuN>1Y>%zrQ!-4^_6W zx0%Oqsge)EkiuoxZzol=-U@Hp*8}}oJA`Y*h<>n6$oKVnF@b&U-goirCA9auKNQhC z&1y*B^`-Yh+(!kJ$rlpyKKMA zS^Qn@T`He42G}1;m&{p`ZdT~U@!|ElZ}fQ3U|`FHX7~2Cc`uL0#f{AkwBOFc&3cRV zjArJKI|@1TDE!1!ad!=bnAwLeO|uWQa%R;71fD7LZoG53B}vxgTvn_)d7|K8k8T9aiBz_4Ko|osX~GZIu;Htmop7Brl`=eOmwXM_UUeT=jSjS4 z_TPT|osYj&{9TB@JLB&z`{j-W|Ib+PqOJx(>cu(7Hlo6H%h)3jTIjc&UDD#P%y@-i zo|A0ZCUaCws!8$m5(A1<6$J}~00Mb_;NKiQms=4rG#CfMs-)hMY=xghGC7fBs_x>c z14%kXP!)FcF1c#Qi>}G0!dj8c4O8Kg4Rl#F7t|7d>Apqgp=OpkGs=6-ELqsbHu2YI zgyDg|Sfwb*$PMB*OlsQC4}_c zEPJEw`(54$P2#|i?LkiG;X4|G@ljBRF_?(%gMF=I=7@2G(|=BPX8P|f6xGC72v()= zC4GhSAjV)Be1bO%BSku2MCglfNaMh7Bpl|!Q@Mtk1d+AgpPrp(*bvuo4JjN_x9qrr zP<|chJ}X()E|=KGv*9P2_C{mtdDR*w@htevw>8%=L}kf0W2t>~GjAjj1y9FE(L5?}|bSm3O46na7*475Dv ze3P1rU=ZO}KSs@4IQ3Vcltm;(@8vm2SE6n*2C#Iw|FfcAyvZJTo}T;>9sJgGrT3S) zq+IduhM*+;y4O!aEb++6iSToWkxwD!95N&?2Kr4!w*8P6psib^m+)R?5lONIc=Ri_ z#FPwmVf}@Fma4dq2UeegU$)FX$AjoBwdI%I$Lg~idS5Mzj-Qc@RK*&L=FkC5Rp6lG>!9$f8#LfhXq$UmO^l|I z(wXE%NRbKntzuD!m&FM*Sl=7bH;vM^FbLM|Zg!~atZ)Qo0D;G`fd4DrtRi;Sz}JCI zb#nq@&2;uhi%{JBIT9%LDh3_?HXR_}R<_xyGh zmgovLRha&&yeg}%&35St#-C5NI>dP1 z;cY-}oyyNTYHceQu`gwqYAod0PA3aU!{VH%8Un^y;;!OGj@Mr}Z14j#;j84nT$~-g zDCqGdXKZ~C=2!fpc@`E&`PsL3^sAb$v$SUoS(D$e_8xdv?fto~YImu?Bm-ukJ0#}D z4O&!v=xBaJ^d@Dq32>va;R%&C$2!6h(7|nk!b7p|2G)BV00MQM zWhO$M{de+eE{U%Nxqgi{(0JiUdMmu%UOeb<9ZU;^E@9k4Yz5*{fG!@5mlJ?%|9<#8 z2Pf@#w1s&=^~Hq;B7tVhIampQ2we07?w}5obbtn5vwJ^(U%y*dZtx(wRyWy-P4b(X zC@C0`^Co-4`vcoy%Z<_!1@X0JdGDnMVUYmo^ocHF@Y9YOj3+rd z^FUfxf@O3b8W5JfVr@t}1>F6+ir7@MS+<*y%9Rg!ougmYAysdk3UA(L4&ijbWO5de z`th%C`TMd{d8gt_VLgm5W&I*T2MRL`&Z{S=iX~{8N3~ftmOrB%Eq{QYGzB6>;NY5K zhXhtS)C%vYb||mkZq8J8QDPy<8>e^gJ}7yR8u>x#JgB3Y>N8&=;?XFmD{%5NKgfzj zIz<*JhJg_PYugwCM}Hy3n0EmKZ93urXu(x^#4{o5%$91v&rNVd(oOA+*JN!}o!xUi z0a=DdwQqHeEs5TZwdy9xrj!jZlCOuMy%>C|(k*#L-FDD2AB{>upN-BvXWDe;S<#B` z6l5fcu?vAPbriMKEI-6rjgS4m<}^0b_}@Z}^ZqXtz15NB%q$P$5IPPF*|vRLjkXA@ zPiWkd_#Fb>+62TWEFiF+>7nUcD;Vp--wr4FiDzR)uVk7HG3k*yDNA~a@d%c?V8@(f z14bbXfmX>9)VM=2X~AU?{p;b0W<84cb(ZYlQrz0IS#}<2A_1b2aroM=S{8$7V?2rq zZu)~vx>sd=Kk1rj-jz!l6zF@$M!8nn`BXd^=IC;{n#}_~**Yl!%**&f>c`Sb*ma4u1LrAS80(>OD(pIALTvLSWbyVdN;W83;gqo-dDU zghP&kD-FXprGE1w0YYoij$=v206eOHh)iWGByH-Ng9U2dfq?`+?*Me2C2e6a05_{I zbwa!PqfXG`s1uM*s6sU*!@OOSWzb<=w`6Tzs{XNCKj+mssEG}^%WO{a|13hO?XaO2 zOnAXE*4YFC)C{M=mvEQ)TkvQ|gb#dfW=)xL?NE}CSrKQ|z^F#s85-4?#TsR2h&3vF znUztEc!=AA*Sg0oc%I|V7Ei4_fC59V2xLkN!mK0*t!w#1f`|B-V7d7+#MW**QQ|Pu zWLL80*rp9ft@O?xD0U4-kRpxCHUX-vw;$6L3(}2;^pM3NobBIMz(b?#E?HI7t{KhT z*D~MS5LJZ3w#+S}DV@e_L3v?|)nx`*GgMS%?>8gakot#p%4dgW)nkkneG^HFQ0aJ2 zO>A(Vh=@4In9~BZfs7|@G3jWfsf*>9x`>vjj-W9DJ;qQgi(1|z?U1?}+9AzJv!_QJ zH5ZlBZ)9r{vgklVb6xX_!>ZI(ri^{9TkrqpZ~w|)e*W>Ty_JsbX>o9PfRvR~nGsAC z3F)#<(QfDM4gM?jtp%ezMRA~U3gJpNukaVV>-Wq01rmTwp~PUALq5p<9#+^b#2ois z#<U181y>2JO0tj%1`TJ|K|zec2f_)|Ppv63>!5+saXDvd zty<;9wx?B7$U__92|Ew%YeiT}Z1^%2EvS$df4j|Cig1}Z(u9+#fr}6ONj1r;%IUu$MtrI!+);Lv!|rGmVf<>a~~>5-N}ng10)SaHj{N8B3x)iGgbp=zRw|Dg-70oP9Cops=|}?o5mI%ORw|-He~>C z!xzKRLsuIpdHCEOT|xu*tciD5?0ReeEP@s z=y7FwIsE(q%fIs0UCZH9cfW#6*0nRY?LzvejbM7PPc`cB{vQj)!7%X#H+MY5r9%)j zQ6ICeCbO!udWcLlq1{s~g%b*2H|eP0a|3h_C{i$s=<9fAbq^ZKB^ahG6 zicy2j@V#i&4EQQkhUg>G(|y)mxYYD8R~$*R158w1;Th+N{0&taAj>tQvQMcN z#t(Mp)r{sBay6;Q0c&bgNQrK^n@R9+6SDl;n}wU`XdJ8smwgqVWsqwHfsWKnlKRRH z&w()izq69C^kO(dDz3P82B&#OMRl6JE`f_>94`n;j1s~+!XV35Bex6_H4HNX7_Q;5 zrox@j@bI_8ed?11Cn3we-rLK9IClzSh6Tm9NTsGAE6RexPn%jqDu?2A=$CFg;blIU z3FLj~|JCI7Pz1Q%cGzx1horO0sE+9&8VKUoa4%|oSd+jCa``=XdyCuot+k^vG)Qe( z*FZMlgbJXO!Np?MCxeRxk#9$X3z5sjlmUA*xbWKri}>k-KzzT-4K6&vS!Hl(S3Qdg zUrov2BJBo&f?9Sek_n>xPm2%gV;qZr5LID6z|;^JJvkb-7@m_YMyIpf?gDRDWJv|U zHzx2SwISXFM@gC3K_ntdzJ{G=$|m*0;gh2m$6B@WoDCW?-B$`6Z_S8Mq`kruRw*nOwdt_>ADd zLb5H5mE6bZSO;uvjNrHoFJqhNfF&K(Z?iQO-fd&HApguzIBv!v=GDf0wawB_#=Kxp zeV%sNlbgzXX3ReHnRY&@O=@`%3j`6*MOUO-OxS|dg_~NeQ{fR|D;Hw9;LJDEKTRG@ zjM++n^4KhjMgPY-?TQnj7w<)RuiDI=M0q~rP6P2UQAC;qGCW*H;*Z!1I3$*$3($=5 zh_?_y)8%jE|CaI@olq9jsbNRK)L|Jo;>SWC+1FCeB7KC2;BV+3k#f8^1`aIm{t>&p ze-w)gVz?AV==W^PY#YH-uZ? zdZ@ds80@BLKu$>PjwfOVBG;QtP!ui=!D8HmkO}~iT%q_0kNnrP2f&+%B0rKL)UNN? zHGwE$L3R&qd+s%AugY-9EPI7u&DDKOry}lWyG5#Y%b|mX;8OtDk-ecVnbK|o+8z3o zj4dyVSI|mgkPJgaKlK&g5Gx=n2ZF>9*-Km(^p~f`Qy^3SpJ<0~if_VRQ)rUkVp#sSe)~v2p=Gw*=v1)z< z9!b}ToFju=DUr(tSy2@4WUN+zMH3A4VN~W0N8Q9k4`B2`{5|yG!Q909TLVldrWWagW#7(*jSgw5Q8? zdbvGm3*trvky_t)>XaJSo`pK7ew%dLUTS$i9{&1y`{AvtAKtq5*S&S|vl}IOLJLGw zaFjHH0(6DW_^SdoYt$@RZPdRJu+*>E$N#6hF9DOQD$~AO?Y)NV>+L2>C+Ven@2;%X z*&!q$Aqh*MySn>!x{~UuPAy3qL#Qk&$gYl}4o+|oP(jqk5l0!pQBcPb&_{()e<2!w z`RDgnW)$@q!T#U(oO`QpbvglqJfqWaa__lk`_6a1^KIw5xA^rNBIWMa2S$QGU?d1c z%0@DOj2Ha{kScUtThRnOLdyhrD2Kvog9qOP?(^V%N1y=+MHA~xMU#X{ienZmNHtZS zC{QJNL#LC%s-aps(b;6E8vGkR26^s@gUS10Hbd{#!0fQ>^sm7AG?~9JqI8Xpp7`Va zKR#V~rObW#xrSZPsBx4i0!DxZ5X=c-XAYMgrSM{k>hD&pgr6ZC`?#J7Pl$^UcW(?6cdia2PQpdNlt1yebC z!vo+U(a-(<34ZPTySUg+Ut$c3hK(`8O>E(c7C_}0B?%@yLAOvj_5)qORPsC^LKnx< z@GJx`~>O}bHwK&#t^(Lx8b%F-M>3T&qSn8Mtzy@4Y{W%lB zVe-2UKw8BI5M2dgX1S=tLsa?liI<86SQpgP15k?j0RyZs`h`BDvcRn7M@w8sVyXr= z)ws?LehUC9kcp3s5)}aLpoCBylR>x>2L#Brz7WAhdj%pG?2fJxpiC^E2xG(NsQICh zamQqwv6R37%r!C52Z$M}v)e#trV2GVfWnDsH)0SU2fW0o`|# zoCsPQz&L)8RUDQ89dOx;$y1v)-EfF8B=XcMO(NPsA%0-pjY35QV}X;{p&WQ)h%P#1 zct_2p7+nHShBF(`YZ1EOC8E9WOu;@?WPcJ}%7hpaPiMmj5*XsrgR0Rl^nc)oOUU1_ zf^&-wdZ%wb-f!~l8A@$@Puj!}C1}fo7+c2! zxFSLdSMhOQf^UD^*BvcK&+XVR?=V*!#4MbY45mhSE0sXjaANIBMa~_n&3)^tP%d&~ zVb8ZHKr-1UJ6-ih_@)PUnoh%H}0_O571O$08xzMzSnGnE(6AX@8J7fsDOf0}LK8|59v3jSL5K!~Zz$N+uHHm7 zY;MLcLxM1VnVT8D1O;%3IAj|5M-IUMZx0IQ1aUxf5W0D-B5Y=|b6*tZtNlFODD}}NaEE_jw!)o6>kZ;CYYX64A(Z{%bYBW$ff6jpK5M*#+{-E$g zK{~}AYe#32k{a;5QE?(@)9tawRu!sV1%I3^jtFn!$-js>24;* zt15v`5euiu*8#AHU_ZrR>{9+Zrf6IE2z0v%YOhk0R@r&JD@pjOl{Lc(Z{(~?L zT~qPeCLj3CV4Z?jBEci^0M;3f=P>UH99_cQA;}T3cLq%jah3*+OS;40dK5Y|7==x= z@Xz9l|3IEDe&KY#3FOM~Ffl6lFd?X-RKbk_+fB?u1{qV2ufiu5;A|Hw1(uEY5|y3= z@%t1g*HI4r7+*M+b?kbWpvT}8efTOqirEE}D^&MHU*SEJSPl1o;62Vtu~G1oeR#mp z1-IiDV)1K!V3L$HQ9PW*bFLB~h@^o{2X}-L0v%{VtsVnYfI#15nXc*za3*05OaQBJ zVU{sWK-B9uu{`E4(Kp`+e4gqYT1Jfr^m4ZBKsi}&ka1kStz_b zAREUEh}$tQAjyedKpL6wF?r3({S*n*sw?D?Eq~$*3k#_Tv2Zs}1cV2Szd;2kMZ_e^ zTz*nf-Qi}N`I3-L7^pTz7z%#TpjzO#3O`R06w0YtN>(I>Qlk$#LxsTL_>`w-FX)Az z#`Vm%{a4)A-smE5Jp0VDqS_~RH4G?n2?hRkY!0lC_%$eCi-(DLj=jUsp(<4@C2kD} zcTxtpCKg<+!b?I}ho~xRh+2xX;4m0P7%VCQ3s^^$#A$Cq@FbF^Sa5tC6K6vtL5<-c zAWMi*;7u$ze?mz(g3$>`Q|MSxQmKIt1K}MGEI1Ff?FGk46~B-y)h9u%sD@nv@36@! zlUn%$VG{rkhzgtP+;D5FZOF$ql?@@FEkG_dl|3VSQweazrjo$QO+_tlVlZ)*BHm=@ zx)E~j9$}t*O;cUJ2uIZsa+D|T{N52rkij7Q3=V^7fiu{Cf*F&8c)3YIymbn8369UO z`WPOLFsekz;FK0xFFw>jp3RI1^2NrL?n)k-C4@2vQbBMZD8n=|&fytLaioT~8b~=T z@bOg5XP>pXkJCt9)$jz!<1|nkKOfEE3&&|dGagH|R2K>w^-#?u&wup3qKj{G2*^TRQVuZfIc`!QxEVM=LCM={tk%N&^ z^hJcACXODUngRfP;@fUQl?Ra&0xZrC-vClVIBDrp@tb0BVsOtD{|6@Y@*m*a1$d-r z9V`{UY@rL}(CWwGQ;nhUgtQ z@B=P%sDRZlzqf(~+JlI*KfVmI`BTgf#|6lHQWHOjI+(V1ko5aL`dBCM$|;$LQDt>=x3YWcx%qg$7m>=UQNH zp)Sn^PA#k;=@MrnRB1zqtK@MlUcACj8m|fUAD<*W5fMWWNe?qyh zQm`20)b@*t=L4lGlT~2Jge&{J2V}=t7 z2KxklM*D5WYQhSYO0LAMTnWlH*Gh=>mBlwrP#jVCjTLbOt8tsrxS($@s#d8eP_S3! zD+FV&@+tEn6)-*`?(s{dA{-i#m-5RsbSi`WVCCiOe9qwmxH(Bh1f_j~L1_kq(!evI zh`OPr`NA7 z!w&*ShF;;7zOgs_eDV!HIK~QZ_~8zdk^x(IwXX(6J_ejA=t_hwVHR*=!*q24ZJ&)6 zUh>0S?7%YoJQNX5ZYcSch$8aNCP@x1!sx(u>^Y866X%GqA1ZuE|M_m!2L_pcLJ7SxEp;eqrDKUi>X6mn zeK3|wWHU}KmdRMzOeTxsjFrw=xkRGhN~VTlL#bRc+nY%B?MGQM6YI;yQ^~$m5;@!^ z`>n%+R($VZHX9$Z`UYe1WOuwjW8ryvAeZdV#QOWwRwlD1l^IH9F3apa6w3_t}zOmZ*I53y{&yM zjp62|#pf&PxZfzFo3-u75;XK#OREY@eW_cbKqz3Etbq=9SE*N{mgWz+HG-uiS# zQTD18%B6s1AfPB^xQgLY`?p(};Z!mMm@=8zUMmyJqDe32lCcf~%nUFS+indfMz$uC zc#_WK`ubQkaVR#D*^%o7S_O9Y#xmWB_)t8XX65cwGMC(+1f7@%W9c~1X7=>dn%O}s zZJ9BoWXdT3Xv%!8LaD^H71!ywqN;-h(<$pg8F4!l&txzsb0BUd`qAqVJTJ%dTX0oz z?avJ-;(b^orv{$=Ey~!xQ^$vU{;|FvDaZ`|m=7UwoKhtBbK;ESR3Ig>dIzn(Lrr0< zuU;Y6TfvoU9Zw#NCF1?&&UiB0)?zMNWY(FpQO0()cHKTuF$uUklkJa0Y$CNXkw_F& zE+&S1XGJ2B%X3zGgyfw{CC)C5avyGvXR;Y*EsML)jtsNw%j5!YN)DvX?qky!ZF8R# zfro9!4IYU&o3&d$iA2`eyBFi4#`5B3z40VyF%sz#*OkSE7uaW^8EypCMj{wn+^sx& zQd`NTobuxFZJex29z46*I=PpL?>Eb>8giL*13HN%8v0`Cy{U$@wHFJ8Ey~TgH=Z5L z_15>LhUyxvzP`4m&d&bcPOGn@sky-cw+<9i*H|B}4~r()6{&PK!wp^IsZg!~P3=dT ziR9#O9+&^$cAoU_@TN()zX<6sg*WNmV%{_vu<_5?pFm&&OtJ@y2||lRa>+yK*l^9_ z9uqtitg~lbI^6??lE_)g6tpvuxUZ)oIf6-wDMfh*V{tF5PJxtI%9HkUA=^;?Bjm}y zhpg;is^7)@@oX9r{|F!X)eI!Af&2L^CZbM~6T`sWdoy-Ebn8sSDRPHrKZZTH0@okdTIb70Moru?dOz;va;7H8h+xvnex^ z>lG5(JOpYrQ;+}yU^l4aZby7N`Acw5-uafmJRHjo0_ORC3G-r%Lzvxp!s6ti(+F5< za2-Y>O|Z=pq@_r;NE4N@T|Lq|AYlYou7@Mj5s|2X?bDN?tVvOEDQGuJNkGy}C9d3C zud657nnxVyhAg10RyLt6_b0MSKklhh?6tDpqA%VEx`nI-!yiU@0OcdNQZ^NFyy#hS z!PeA-hUOMijZGa=PJIRakWYLUSI)Ny$>ABzxNkxFOW_&q!D61#3fTC^J;`-`OX$zu zGxuR^_nvtp`^DXcH0tD?{j!eg7Uj~I1{QU6F$^o1Tc@>9%al; zp$erN<8ZH3Li@iP+W%cMU9*MiQp_0KEl8P8ezoYKIPWfBGp?L@mZy@55%8d4A@)ot z_Yn6`b~9#fnA|C0CHI0Yg%7XCpEK4jyX{*-0obbP?lBZSSW=<%V?1&ZN(%%lE0kVb zq2)+$kt!>ZRw1oMB4x5HiqwT9HOUBU#0rI4DRoM&>>lLVA11q>YI%dyQv~A{$_th! zfjX2hlsw%1vM)t(o>KyG*E)ID<&HMv8R*|xGe(ICO;rd$lf5d+6X=tFys}@XEu|Ry zyHJy)FO{|g@L+2V;Nrkyk`(qT#@UK~AH}w%yv zjQ3k+?}&N5l}<&>Jf5@FR$Dl+?%jAs?H!5%j4D{7aZL4k+6|ni$tuc2s6)>F6lvBz(^#t+;V9sLU9e9;;Io_IBlBn$$CZf6XiuE0U6ULP6PO^9o zJ{$Ep-%Yl)Kr_qC#ILs^1Kf`w7&G>>x5X9En^ zbiA*CxKnse`q&PjfFg4^*9(SHC{{j?a!S!%Xf;67$%XajQAT6z&=B)K+SBUfW(++|x(=CpJ_=i2YESI5F)$zHn!F}mw*L%pHQ-fdVMR?9-0hA3pU zEvf8!uElfVXH$q|n^0c+&6SmM@2|s^()ChYx%VA>q2E+G z1)eNcrWl4xQBM>l`y1_R)7fh!dMJYJ3bYdmAq&fGxj=_`c zzfgWaC1$*fVkgEaK_V@YhwnzZ5-E0J-V>!{Qn7}>wc($mAuPk(I*?kCnvfchYI(h2 ztF2e;NU3Do1{*w0{T4W#vuVi^0K*0nGkBF-5Ofy}1KdGc{m>yI>rOiC9A33<$Xo zm;{536B}qjcbwU4We-`vR}8aE)>((48>7f1m*WPbb4^TJKvi#Ur=Jm357j!;WS40R zss`i@q3N{-V+Z4e0id|rX6nr=Qs!_v1s`BmG!`?=rc=4zgk@r>xQ5AE^N_`951Vv& zaZ>5@h&cdB#Lk8H5!zndO7>Y*=5EUz1V9)Zn1M6E68%E>^wGT{d~sBD*1#!4KW42t z6a%k=ARB}nAA(#pGbxJkRMIlxlr!y7QgpV}-**xAFNE8L)ATt!5pZNWo`}y*k)DVI z=#G}(9wbL5T#Nhbkf=rWAjOlzAhm1?svBvCD53icPGn30*el9N5oPs5VD6bJW!t&h zgDg@>$Sw$Z*@ydqtDjToXBOZP;|%78Vo5Y`m4DS9$M%+8kxB^~FYKVf*zmBG1QihZ z_&|Yo}ERJ=5uP zwcV%S%b*(QMosV|W9RmRU4X_4v}3~jZ#_5gfb(wj9d*IGY8?DMF+-8 zG`17;XEBOxZYjzyFb>CX+O*@I*4j?n=PxjdU2D#inx`ZD(ozE*F`;C`lmoK_ZiAB8GW2KOxPR( zDsiAg3F>gDfwA_szBd9bGd5)Rxx*P!Zd3c|*uI6bCH!O3HJREk2C14QEsBeACEhun zWBKjHy~9(b{~t6b9qgGA$gQD*dwqw<%8ti5*9%RAjDQkbXjBZ(xMt&g@+)m{m1k{u zzhlE2x2+PX&qU0Z@SODYXD8nwC=}@ts7Kr37>Dc~hC_`alol#Z4zHdqEfWP-YBi)y z;?u5;^Ef!esX?%^(-`G9p*}5EryZ?twv!kIDWu?P6CwwA6Z?KFD~}VBscxKf4T4(a z^4h0axuorjz`3X0@|zfo*3}PiC64~xzWx`kwCL42k`BpDmgAmuNA9xGz83_8^%SQV zD7ymB3F}q3l4k5-3eU56&VKoKzWqFYFFu5Jgwv*As5`c#Mk@bpvKb}*o=!#|g2BoA{j^J4c&nDX2aXf#6 zds`ZIMH~C|w)>cIDBuApaXe>l@5B9mBsUDyiiVJqNEa$43*HlYpBlh#;@M0?tgkOO zL>~$~OY@{PaU1R_)h>LWo^8QMSB4ztL3^BmH=a z)BpR>H@!Em{?C$ED0Y`mEN@ZA=~D+{D5u={`L4Wy^2M%v74o#Kocc?~l`}upRbD@? zocS78c^&eUnNEMr$h-TGB2SCkDPN2HqH%d@l1}*slm}e-E0O2^amojfchBcA^4v#G z`ElgkJVaZZb`U+SH_fjQ=Y6&WhnD&J@hFd>4fn>kaHYI*$D=r9zegG8 zI6fX_@&YMmFUA#QFkw7O3(DBPQ-_{K{;@t~$so%4*Cl;E(1r$};NUa>&$&-7oWH{p z-fz|I%fL;-02H{YTIw66V;+)aDAiBWh`#(ym^0s-?P9+Zv)2Izc>%=$i$?x)NP?6u z*nxI#@cOL=ghplhV#9)Zm70YWN-xG+fJC*t*4pT?9`Bd`m+yGd92((hnsVwJ^Hqz<`r1mQG9m(NJu-zR-{^wL^o@JjBVH zN$n#+|5E{7=)2IT=&oKk4A_$T@Iti>Vd;y<-}fVJq_ z;@;_wEb6{PcHi~--Ltm{d)vC6s|`L(%#d7)Q8z)cy`mGeOqwY{0TcGhK!|!_^`;VI zG%2?*3C!*`hrQsa=?8It26ypB^clLxix4> z%XCl+Ws66Q_#dj+g-eI?X#WoYJ|0*+hD6o8s`}#j3l=V_S-fOvZC$vrskx=Kt-Ygj zb+l{E+I8zUY}|CorJFC?vhDKiJFeKd>&mOHzNRPE+t+Um#`o<{3=JPh7uVZrSKN)7 zqTaw>Su>eJ-Ap!j@X+Cr>&J#!T3g@n=6a}a80+mqdb*G{V?ZlS$Rd}q>Cm`nJQaNn?7Uatl4ws&NIbIj@nB(S_j0ihFsCnQLJfX z+49lR6)RWChJStk3dkjSVk#@_2^8UH3_HM!tl-9uL+6rc{`xR5s;JzYoPA*#UDQ!D z`+P#W!W&owkmn(#bEg+x{s|lxNZB62P+=6jp-tueG2yq|HbrNIf+4poz|DlZO+vCah50 z!;huR#uY;ruEM728Ov`(Im4cudKa#~O78*RmS}8l7fy|Wb&dEhoK`>;AI6D)pE8Is zsm;CwlYmau8<8CAAdmZFNXK2zPT(HF1>z-yt;0NV9sd%7*#l>SWxs^ryoiODHv|}B zPzG(#yRx)hhV!ila_AIv@*9w!i~QS>9Ov0PaQ{vu2ezAVe=`zodmeJU3+Walcb!|2 zzs-KeI!w1C-GOu`k^|hB<1QUL)Q#7f036&aF<=QnBz7DU>Yo9;bXI-chT97`J;=xG zlU5P+BhDmV_wBZ`haMQp5+`dqq@!&o`sTh{e0EI$XnRT)o0-8>4ljSfT3)__T*j<{ zU!W(J?J@C|9Tu6k)?x7weVszz^bp;Q>oi;wxRM@+aOGGZ@JVOudc-O*a!?!(il9wJ zxeMhE4$1k1zY$mW^Y_?!TtvTID|h*O$L0S9`S&5cAIY}i4ms0dKr7KI;+RB+-V3`H zqh>NgST_122E;T?83-xjR9&?a43C_+R@@0VjpOyB@m8|!gOSb*@&|^bi3led17+LN z^-3Tj_x7K#CzN|IU;YvIA4a;OQ+lZ6V&Oc945XKyw@gI(r?9YCR}q{hYiDj4Ssq`*zmp?{MD(&#%+}!#1p}*I0#k^8z8z&h9z03 z$1uL|;cz(I7;XwThg-s};kIylxFg)z7;bEAY-((7Y-wz5Y-?<9>}c$43O6-2H8nLi zwKTOhwKcUjbu@K0hnpLlo0^-OTbf&&+nU>(JDNLN!Yz$0O)bqWEiJ7rZ7uCB9W9-$ z;nv31rq<@xme$tRw$}F6j@Hh$a9d+rQ(JReOIvGOTU&cuM_XrmxV^Ex32&jcw70gm zwYRr-w0CxdI~qHhI+{CLI$As0I@&urIyyT6Vkd_0MAw~Y)`>?9E>DYy;sN*#8Oyb# z4-P?drP+wnd}rQ215uEtxN&wL@*hRIA8A6=*?!X_%O&pKfsIH0>Ev%ie(FSRGmr%E zx>`ehgK6OY^Js6P-Ze;s0dFT`^GJW6q)h}P3gVO{+@7Kf(yRRf!b|9bbnsueRu}rf zJIFRGs$C(21DtZ%UiLl~#ULmhjYLR;vKeg%e=n}|3^8nwZx6#?l*O7t_YK~Zeh&ikj2`!o25 z6AU(ee9ATT_4U^Z7n{A3;2uSshN$RKlvh^DfC6Qe#Q8(m?;uH~7hx$$3K=eC4R039 zqiil9y_#qnMw>F!U5P7sP#3P`Y)&6zK5IIZC5OYCvCB+yaC+@Be4D|nk?#pC77#h` zWYO0`^z}hpDOWy%D|IEO@3Zy=IJ9$~@GR=iLfu!8<{+I_*Y=q=3?mpO%QG`dVnuls z?H1rlFCXQjlP7IZ2Ub-4V4l~j`MiE#pfWf+G^1o@X+>FSxly4{nsiZMsyfY>uFlYB z`ev!KwRuxbeW_j-s#n8$qt>K8pnXjHxbX@9e`$a4yr%t89}PTwc;uGbJ`&!2MQt$g&li zF1hyP?I`^07ryw^zdZfSnV%^}X_+XEEL*pJ(+ZL1yX#|U^5i#OIPbLSIKl9j^9{=8RFaO7hx8HK#{aA%nyFJ z@$P^6)>BVEvuVrL-B(@Lee>;ieEM^bKK|vWzV=+@)M?l3`R(uiIGP_i@Z%TC<|R|J z=XBq2^plU=_{GPjPMbS#{e~@Due^HCbw_XfhbO=Ly&t{&AFre{cVu(#TUg)lz$1@7 z{)4Wqlw;5~nI$bp?z2zklW6pwZbI91YApdpG z@lWY9y~khEukuX|Ob<*hnOw5Z8}!cfUgcZrSs$#$&pN1jW2n}c=?&@mhfvhec$uER z&%azR*O&V`{7XH@M=PiM8!GGcs`9Gx{H@0E_s(-H zC7%4KC;vjpf8L|F2afHTod3K(|4mPD`Z7J}?eMSnmw29d1V1DlQf zyS$&cuVk9hc&~Bn2Mc{A9#8)Mies<%RC9?JrMDURFX^-N@>0dCs+gSS@%c2rKcEFY zA+5})P%E`bo{K6ct5dXT+KkfKo;m*c>ONz?_K5zN_Kfyz?Ykx43w&RDPWyrSqUW^s zbK@oLm*&gH?==vrTC!-_$}L;(`0$7S?rpcc=Y1df%opDAX|FHPwqoV3UqAb8WAgO2 z_FcPg{P-sy`KQ(wC%yCL+do{OL=xhbt^L-X&pkS8w$C37O`g`)8F}zy&;MJX{m#1{ z^aYpUz4bfZo$7x4m%qBY_cyPOe(*!}4U1}a-gD~SzrXK+2Os{zV_)@#N~X+-tXg;Z z{SW->w@&$H%&cCpa@Eg&@vG4%pEAsg7c8u4?ucx>Wb?KiaI=Hxditz^{h7lz-1x5h zKK{t3o_O|?k0ev?xvu(c9^I(Z2XwWeK7V|U-dH}{m>-zyS?XD9lr71B+&kZxZ`Al( zLR;1xYY$8f`ll~j*QxjU1L3LoS*uwdb#;evsi(mR`U1Yy<|3mc(56Q`Gkr#hZ`-E! z=F(Pr59aT`u6kW4=qWY9p65_;og5d-sxLn?C~xPt`F9DE;|0H zYphF+PG99}(!l$F??3tdC4rCp{8)3HUTOG`-FmaJ-&3jwd=>BR*$~Js&;K@<@efZ~ zpa0Dkg?M0`Q zNz=@EC1%gOU)7#kx+HAYrtW{SR(r6gZtfp?>Xkp5Z66-(X?smQ-KGYs+UJ*^ZvRAC ztg~VIy`ACNr#Jk1Zq??O_nqFlHC46kp2zOpraTk7-1_#tmn%Q0+OE8KdPn$lZ09F` za__EZUpl?ZRFb=2R!4W^O>Uo32jxV=f9l3icuIw8fw5_tYFw<&opntp5(uc%4K)Bh z;906K_b-{Qn(b&{_`$$@L2Zs2VROTeDnV_gs%f2I7={Lhsm|4OHN?CJ71YVvRIn4& zM&Ewbrw6sU>N2z~LCYF+k6y8Os^QZ@qE~{1K{e)QYn_148D);TQ8jQRqpE)Oa(qL! z#NVrGfspqSZ5H}d)%G$KBYHyW{D3-Os9pf2&Cm?J!oXJ=HLnVfkFMx*w7K|iH9lkH zSG7<;1shj$TD5vmH?)B2)qe;i09qe=)coF{riSM=8e!x;Y7IUyXkwD8-ho0i)FXaP zyIWUFRUZe`wWn4q>Q|~1{Wi77RJ?IbG1Q=`ZPOs}0s9QiqrP98IjK}#=${d)*Ta~b zrY%y}0P`9?qvTf`)MoUGZ~0-~OEkawOQKDMB2`gAb5Z??`Zpd$$1IH+-B3S@{uS*5 zp+@6vYFqhY%rdAqqDP;)LZ9zZ{VVYmv6cXKnA(l+1FOKnDOLAR5m;5#scM-|_k6|A ziA*C#u}-X|YX28-d2yYk?ez1>KH>tUmW~DWC;?S_1*;BfQ18TOhKkP_d&MewHN76l zR`5$Ms=9qD06{O;docj;4NBusD&_%Mrl_lo%Xwd~O@pG0gEqfk^UXEht1InBlV2@U vr+U*dL%Xd+&4E=dzD1+p;CuYww8W2;_;vB$kr6K}!=kb`ywUT<|cS+DyFSz;qPn2w87_Rgq%XH=o;RC=9VpEZ?vO51nxWa7OZ-l;~4 zJJncJD?U^#l>2YKed@+HUUz8!4L4>T-A!}%9f#g>+x{%q?TlZ(^=-HB&w5Sa zx4-Fzo3o<1yLtaRZ;Pt)H{5vRq3dqH;ik9Tcmws_+>)PB;cYj+6q-oF1$w|wo(FFp9u>u%qF=q=y&uIOab`0=6rZ(<~euDj(OZ#=mFU0Gi( zE$qMXYhV8Im%d^}osG@IH{NjDo8G+QRrkjIZ+yqM$87qyzGMHPcU||UH{bA%wE3$4`cDrRZJzW%KH}7?)%BiW6UtVMdi#*5wlmAg*K3|~9%>2v@ zHOjJ=nkze8fff8Wo0pxujA~Pq7At zoVVP}^|#&dru|yDx4-3WH($3q`__bSK@rqY${ic8TKVN;# z|MIQ>>$U&eKf3OEp1=C*_S~HxdUyVYeV1SHKW+U7ue@{*7C| z`Gz;XiJPmoe#3VxZ++V>ul~9_)z}Z^%UgdS|G|9gcbv%YDVC3N`=j}faeFNP@%*Rx zeK7x-{Acr@%RiC-^Ze)YNAj)zCjZy@?{V|5@=xVY=KnGOPx=3wzq{D_C;5@$&+Xlmt@TR z*3KSX<@M;b^Erj?%d4`UKETySbhYWga*+)St~UBg&j!BgRONCpH|!SFUoNKi(N1?C zO=MN)(xOvk_1uA~xU|T)Qgd9@G&I-iQ=%Is25kw-szj8fel`bkG0-Znvu~i~eBNW9 zEBb?fnP+_-=Yx7w?3*w9RZ(>q?S~3R=}yND#g#_Uao-*Kt~I(js%7SGI+ilQ#4&1$BRMvlx9b9-( zCvRv}q<8Xv@Cc??)}w<9eF!c6*X3CetMdK1TAz-E<7#GDmaIVih5T0f*l{_Rf0SK* zh@ZdaXMQS{wBRRd=EG7AXyxa#zQ9ll+zSab+1$LkVr4W*l^jrV-+V`xOG1)vYO#JM zQz5XVsPD-WauoI9xTKTi;*KHJmWy}J_o|NYBdgCvS%8&XeTb{&Vw2{h!EK?&E3=!pVM1>PCWS^g>`c&rAXwB-Wd}x6 zVD+dM6afai8>k?Gm8+Rix8CvE4sWV10OhKhx+Lp>-<`U1pq|n^y8SvEc6pG!GRwKE zrgYT-Nvodeh#%8jcNMJ-yDH;`jO$>dZ-lRUqaHO|KRibd!BWv34K9k>28{09D3Q~* zh7m0?!E$@CaTlc2QTejoqLrZuHzvX}o-^1^)qQ0KV=$fzfbX0)9Nz_6s4>$=LWleF z10%p(AMxUOk2>|aubrRPLI!Glub;njl*d@p>a)^R-(K9YtK||2 zf7EVfJ}?Jj7WK|CI_xw$Olg=et^Zp_Hw`K*k5OT{L4_Sgg{uPL;=TgY#HW z%~j}VRH!?Lgr)U;`2k&ag*caF@7gC|=%J=|6{zxIQ@rl(%OSLLg%N;odyxgOfqWPI z3Seu!+`1QxI&W5$%jf<0G3ch#7G;nfHB#;g$`k0)P-CE`(xO(aQ!M~P7W_4;`Pni2 zzi&R#MS^IOah+3F>NJ(qh?fCIfYf6qFwIj!2v{S58{o4HX+gvu5`-+>{w4sl zAprE$y#Q##N&tPHXKMrKl$APls`UH-=x4sB*mPItPGrb^C;-za0Mp>|Xi%~KgYV9{ zgQ*PXV88`so}f&JGG@Jl7S{@4MkgexxP}C&cvxNyBqqcQxI&zDz@;HSDl(VE8M+PP zj6!f*{vx~v7wG-rTr8|p!9!FfZwF^*`0QTb!#S&Q&gyow5LCGf4KP zk@^92^}0KF5l@0iJqU*vZ(>T~QV--}Q>Y5Aq2a>U%o%lke=ezHzF0r*!BV{uKB;=~ zsHO)~IG&3~cIx94ED>tdKQ1{T`O)P0^JU!SZ)|uDE zwgqgqRFR;GE!==Xo2T!orC3ucupCL~q=i$Jay@c?RI{==S|XElbKHLlChx;98L zi}Jsq@;#_$t@3k=QBd_ZC3QmAyBnG*>pw)om-VCWN~_3}w-=Y~Dy|Og4@T3de^*)k zlc;yCG_>u-)x*u|XLcWIzekm(unjp43)*s(0R*ag5Hl=(I|MOhRT=hJGi=5#Y^)3R zwDPjs%1f^Duoh>uRML7Vh`j73Yw-fELS9Oh4S6|Jb!9DHs2|D83kC9CHB;w$3D|%Y;$C&3u)G&I-xGdaAh`9UcSv{PN<-sH zprC8ZKnBq^|{=VFY^xQz+DCL$`kELD`S>%o~`- zOludrbWc`UU&dpX(R_UG7xMQEH-~Vk=(M8yee;Yk2y5q+6nn+%4$Wui(WyS`vUzW| z7X~r-U{Up8nR~NuVwJfXjJA%Oy<@bw+IlsM<7IP~v?`NK9GpwpWIM0qx?8{UN~^`1 zikYXU;z@g|5^SEdYzkq}V{xN+AGQerICh#k`e?z6H?^t+RJ(Fd| z4O457M(s}pDo&}lPPMpie!iL&D#Lue#4weFNvB{EDBIYO(G+0cWQj6`4OcD3L*&A2 zT+#JKk{TY-R*?j(-MG0G+}s>!70jf?XHSJSM}IG+xCFA!HbQ`j8et{EPlK=k*T9(_ zEVx0f4pr9%B?~j_{+5f^3}M;J#h#%}*vp1yU(9lngL_p=EWd``E=w>6yWPgfS>mY) zrT}usZ7>DiHUpX62mC=pF94-tNty-H!u0}y_CKZqSC7S=gOIvgHE<_?GJL6fEI=lUjnFzjRd8<*jweyeL>O?S&%G z;Lly4U?w58;1iMsQj+Cc;m@bg9m5G=f&Kbj#l|2osnDR~@~AL@>H3P%RA|U${TaPg zWmZL)*i^oeE4)kBhtLNlF*Hmt*GoHQu~N5Bh02<0me;FWq?7DWim2YIzONTviL?{x zZ!c~X|0wGxJrc&#js&p1KKf@4!-dcYa%dx$Vp@b8nqqyBMHQH^DTF{hAHqqGE*SMK zjEX$ZK!bv%63F>~ik4Z)Zq*-)9gs&QXdl8XBy9Q!8_A@oq87ss$rw--X&gz@ZzPRY zOVzs{-#dHfXv^N}$V0Ta<)P7H+s=Vew^~GK&;=;wE@&ta3+JeWi`rzN`w}iWh~3wG z>U;>ME@(+Ie$3c&L}KU?SJ(l48bpx3z?>%VW}E2O$OPhsbh!e&^UBO7=>iVHxI((L z7{W;KHb6N&yB*!ka`F11bpkcnVtL?rVx`8>%T}Q1%fJ=1M0du5id~KSt6DTzE;cre z&xZV*6+t04q1VJIAHZcEv~35y1_xPVh|;J;kc|__0}js6;}vKYucBC=cW{RX^1hmq zG{1r$tfklRLw|ebY!b>)q}ZFst~loFrvOh?qx@EcT+)%*r6 zTkpsG1}vH6Hy5g{roEkJI>vs8piFO?w=V_##=Hl$rQtnO<~`7;aOhu)_e_m>&!<=N zo@>Pf&><2{i}y$q71vuXt{E*f^bbx^hRQ9)qta0M^~?vi-W4tFqr#c@T(Oe(pgJU^ z5!WiONW5ptnm|JLG}LXfw5O~$o6$5lf6b^Dqp|rA-D7bC&W}e1{fY3plGYtHg&dMb zh2-suCh;YNzSNb{Z`H^R%IsbR38^{Ex;jwY72tpsht(qnn3S0t5p=-n`r*8Ou0`Gn za$zyRafP~8f8eiNN8xK#I6>29hxviWBGo2b0A}hNgZ3Ljy>NYmI<=39ACenUQ)hK? z@DH~zab~QplJx0TTjUfCh>5_ujiQt!v2Rh7LfL6a_3b^QFi%9-s4lwMWEa z8eA?O%!lWS3XkT)jX|_Sblc7vzA1B`MuEC_r5ouUJ8}?ZbIk~07VT}T%2_=S)+aB| z>D^M*IWj}S-7!;VuN|HzCl8*FLz0N{ee@}UH>f)AkRL_Sj@YKh2}Y0XhmotR+%wv; zt6aw40+2V0JKt9_-UzNd8VA7<>;krN+-eUFhXm zP=_7x)7#5s4Iq==VsBI?^fZBC^B<@vU~g6?)5oC5YMfc}7*h#2;Z@Sr0JnacOeVt= za5X0SH0~D$f-3{u32-uBOkaU>H+36uBA&yAukJ-tX^3GYyAL;$I8+vVfMpQP7XVuh zaA-k`KJewVc586@|HP=c_^wheTa4#fp7O&AFcPs<) z@b_Mmgi0EIq7V&V7gPsHJpPZIbF{h_|qYNN)-Vvr5Cc!2Uki-xs+~ z?0fZF%;su|ZS^+nqpw!It=+aT`Q=s`ytmQ25%8ulbf++=K`*XP8eu29e3>JjY@_GZHu@&7ii<-OjL=H^hD zrl;Tqp3j>4pvP4*sK)-pZ#dQmFXINSWjBL?8OUQY&{9(-F$8OAVhCG#E1;aiuU4-b z11E-{D)neOn8S3-9D3m6=qhc7G)>hXJ)C$#vPmWsMB&;%12htCtOjB$Jy@sErfuMR z)WC#wd+G-8$4bm@w%;j?NTKfZS_4^aT`}CoVUafAfswJ6wX`38*Dt; zeu3sBV^YoX78Fj}(}d6jy+EVzZL0}1w%wyeYkSMZEr3lM?-jt^UfiZ8&e8goHDgND zgDkzYxK^0f*lG!k+nP?~S~LD6Ti6?c)nY3(s_9Fz3>slUxJusfMWL;PG6nIDPeWAV z5*Kk5-tlv}3h(%$g!bIp8LUpl3<$|Dupax6)>}YH$`u;L>jzlB}Eh^e|H;E)Ob@xKAqM4 zB!+@~S{CGmzFOvLqJ>*9xP^9CuZ+B1r7 zMQ-$0jqoEc7h9wZYO|}^EJJFT2~BAh2S(GcLn-YMCPrn9?))~YO*7Bw*KNlCr>S;) z^@T5d;p=X#rY^6`+lIWaySEL`Mdst81qx_7fK{w=fftvG;mN0q*c}A7)D_^W;38i5 z!T%swXRd5;jWQD`4&W*qD85neJM^TXg+r^Sr4xOM{Ao6VD>YMjz4+baVQG^b0PPl{ zz<(@9;9+{UY^E%WVi2IBG1$sEVG`i8>d8!G_>g-Xn8| z3~qxkM`)1R_d1lGCIIt-Y@$dCgEY|flF~s<%#F^bw5d7ezO;g)avuZj-sUX=&C_MO z3HSOa)YPY1KTts4N@_6~em2=jD9dR;gqt2T9{ahNMH?Xi4%v9}uDy1kY~>0*{p|(z z59W*}m%7tRv>W(vZ5(aQELnO@F~@bE2VU_eZ>A`JBw8n{$X0tyaUT%#?5LTl(pn+*BVdIfGbnr;z~tu^ zfXUBILrCi+bIFrPcEOmt+M0JYbHf?4+(yiwpPu5Gzzs$p>O@H5D|2>j6;bcJb z3cE0NyA;wA+}%QGMh0JpZA{DW8R3a>X-1sM=7cvtWyfp%Wcy&CMzBhYkZmL0Vwh1G zZ-mZ>dRa^yCTD8cyIi3Z_NKtu3~oSFf*`<~ zj348pTsgxGJ^{|=tGGdiBSbhmYtro>?nQ7rj*>8ZfR4DzsFO5X={n&DF=)A4Vy{AR z(G20Q9Tm;iNe70Q)dV67Py|OS;zD$o)rWhp%1whV1xv68T?|k?sP))^L2r@?R|u0a z`v{_z^P?cjB=sZ>ezFT`Dx!SJ$zhK1#$+SJvl+Su9RJn~_4G&_>e-19_Q0eborZGG zUJF1LcHwW5a!&AZDCbjPSJO75gpJ^d%+p_tD`+$|MYPlCJvkJt=sAU)-MYTqfAc_s~8cjZP zMDJud(7~vlTN@H?diTu4p;oL!Xc@d_+A~X6=@+$2Do95Xs99OmkOB;aqFHh?Kwn)z z7&|M>(tBNnM>09gb?h}PA!4iVLNdZPZzzDvaAE`uO#x+4 zfX{NAf&#?&8wxNHL*i}F79eKoDZnn7r5=Hmv_hCK3KEtpVk|IUQEq|x2uEQ)o8N)? z1(;8aLD68pG|7VtoIA@Daa5Nz3ts>Gh6FZ4&jmYKHLBh+<9Lb}{9dl-xN6vIM}g$| zXL!!o)Z0)dN&LZ(7YAc3B57HPA^=0t?M&lQA5MU_AKxkju|XV9MhscDG~5AiFWZ~vnR1EMX06oXiUcp z%S^1{(TtV3#33IzEizO*yod$-uN)_Pe%V*&?;873z(TJs!-T&_ysAVR%Qjex`>6EP zx=Me=F^}WVyXah{JpSz$og7h395qzCEC=Vh^}XK>7D^`s3-J~;;*p(>`g`S?!;jFPgbcdHZ`4);pD#OKNk>@W0ZXY*pV>~wom)A*1wTy^pqt06@)_}2~y?vr;xepxvmaAx4WV0%Dr zSUnmXkbCP?uk3tSyOwKr4)jN9{QKDEZnhoz{~#xxPWu3!nwuZ7zn6{5gRfKnY#bMZ zOSI+3zAD}GTb1}jc>1B@LSp{#;IK)Iv#9@Mk(csn4D8cyz=FT!aIOQ2m`RrwbGjYq zFrZRQb>6@h1jjh)ue+5(hhCX2_-^j@`h@Q0eAm0ZeLY{7h5>F(wzkulFYnx?VGdrR z!A;})j8QU1d0aeId{m5aT6HITn{V1b?%rzeV@wuUbb$@G*H5oGoOZO$@aOAJf5_CD zp{)tdd<}3G^3*FCk|2t6R}huMeSo_U$o{81&jr~w_*HShb1JG+TOFc(o!N$~ZQ+Zm zI(rL+vvlf@edlpS9B@U*zz%+>qIg&Ky@+?^e%V|6y4uavPCuLGYMCe0yn?0__s!iC zB;ZkyBnS8pzlV$u&*(;6c<>n~HNeM30Ct22d#5wGKp$%(w$i5HV!yth2I^hZCq%41 z{9bMzxQ8Y`OSr?Yr^&X5%VM%+Ot#b%z0p?26wCVYG)0`_nj)K0X;Qk1so>&vNS~&% zlc#&wW8-HheW#f?21hf&`IrXhsJ>N$6Q*r1cdnkK!UUlVR*hDPj>1WM*3)?U%W@ij znZqDJy*-4;N4Si*oQPTZy$T;6(rM%fK`CnG_tNeW4iXSqOag*1CV{8|KUf0NjI^ot z3D#X$BWFo}wa4CZ@Y<9K&@(KBkJKA=UQ8>`jd&do zgNF)rw`Ig)YnY0xsUK$qMnevZpLieFPw>ZB_yKI=FLK`6-2){~Iq%rN%7Lm+wq*8J zvq`Q>8BFo1UlZwWs&c>eKIc9e@eA&EJu!$ z+%0mo!*s7_9j4=eub!2_A~0%vE}n3wX`dqLXy;a)d0$m6xJew*6v;+} zZ+-j@1(ba&d;5$m6{chFDo#Ndb&3e_u0h_H^iqU2(=vvznWIU0V*(zeBOg)2yYNe| z%wEj&iMsJ-1eYA<&{=wbB1TDAbN%3Zg+GdHX1T65qlBl6$BR?Gn5%1iC7yW|PnN~+ z9RdAB9{uKn-_aG^9SO2r>|i|nENekY#u)7HgxTK`qlW(U2Qf9cyXZeM9TX)Y2GRp& zItNAr?~uV4a%Zn8KWcK>_k>HfB_I4D!U+g{1 ze@e13%{B4-IGoLW*$vfJ2^&$LeE0F}C0X_=5UE!`!VTNFmx9Wy6&Q;TU-%Ix=2-f1{RD>HSDthk+czVn^ zXs9oknpcRer*#xgDDF_%0R3L~+jJl6=Jz{vAM3_FI-+`)4!2R*rB3#c9gxFJ@^6^jA|pdBzz#RT$` zBL5<6aSd8VOw8HI;|QOT9Ri9Z6!6a&Nl4@>2xG2QQkve-#2BmMi^a9#)WXSJt0CNA zWR~q}^!^$BiQ{IQR z2ZIg8p4I>K{$U%JrH+Y?&^mApH1fREQQDE5U{6jwgJ)n}kS$~484`OWQ87+UXEV*} z&2g6-T_}yfCf^8`M*T=U^>NzByb+hYI@Z3r9(9Sw=uhe7;E>}ifXXVL+p99`6`0!m z4oN>&bEul7AeoL%1?c=r4IDHv1*DS@jUfI3NoRDxj7(V%agfh2Kzx)sT0A<>YY&Ap zVsOvLvqtCh5H*R1j=Up!T-RH771tWgIPk9MxFPW~Iy6I)Aay#VN#z>YG^Cm3V&|{} zU3VvR6`DlcW3cHIuH?0|^2#pHq?C&LOsAl=DvL-UkcO+s^pf+Ih@p%hPTENuB&!bf z#+et0W0s=#mZD@32sM{*^Gbdn(K}{=0+@s-O zoWiMWHpJLjA#wW2k=f6~^El%}Q?n8OqyxbUZIi#pQ$P}=?1f>6Fs;ZPt63NlT|=T^ zVVMsHVca`S|6? z@^ck&jr_b`X}%_~Bk;m3EkU%+km7ZW3@P1xz|M&m6!Jv_O1y9p6K1BWmb&hch|6%0 z)hr9J(;Up>mTGaE5-%v%ku0$SRaJKCCI_S))t_!BAm~}lg++SHMI1yT1Tr<5xjGFSEZ^-vTw>Qo!KZi-mm0+tyUK@|TeWm)@rZtzoV`;7 zD`EiK*Kk5}zn@Mez1@Z?-j84FK1q!yVK+~|@3@9@BKq>*XZ-iR;y``Rk>lWXwlu9G z57SO0>(JdX?&64>?jGUJQbpKm$#mRLIR4r`dCX79EYUn+EaZbJk1`aF;lTj4jv76MoPVZYc~lv>-1?If8FvPGwEuQLg72 zxkmLdAf#c{u$B7$?>&yIh?}DYVtMdYT;0!}LOGFu199&sP+f}*NBku5(9|$Iv^*^D z1*3VA_T^!G0>;miCjj^3@5h~|HLMR)IUBr23COv7vS?^aqoaln9O(G$7CuBYBHU6G9mXh4oxIqF$vqnr=Nfp?&Jw{n1BMlwHE801PUiw zjWOkug_qOVbiz+QG4TYLQ1WBrCt9}$3&Yd!eI~*8X{{p=8fkV<1LYG)*MKrZHk4H& z2KmfNNI$(A($pASY`sLzq&Uw9@^0bgTyt-3=JXu{=d?+-aDVylEX7Vj3aLpxUA25XyaFNl1Wm zPCZFH&Pdt4P(LGmXBT6E*VP}!+^Ro+H*54+eBM5%y5zRVbeW|rEI@hEnJ50CCE%xB z5>_Eg=fo1_s{Q=HV3)>uh(S^Ms2#5;<{~%bAHz<(R$^E>Ymh5!pkcf`!tE$FHTcJ= zMY$-CmEjlQa-y+dxn?*4VHOc+6!EHZ+onh_?j za`u}iXkx7qv?2DDl|TbTlGP?v3CR#f5DA~9`JE@1wH^Ngu=$rh)H=c zx{!xq1YPWhRxL+WYXr@Q5#)>m*OL&-okDIjy*%0+_`7qMhXK?@*^`fVgt~;`iUH)4 z<9Z9_P4RZhhvAaJq70mIVu!07j2y*L2Qm;LhI4C{N97jv5tRu=6TQ=C>N|s_0#BmM zVP(F+X|^z&mKtik4v)^9=48EYDx8>*g5Fwv!PG*rv(}2q#TeNS$z-E3#wB+!>ai;# zF4CkI&?_>lO+I47idNWEAc>+mtZ3FAN@r=7&yog2fKeuj!r?Gk;bku9D7WK_jKtLp^RXq;dp+m10bYSr0nxC zyT$?>&Q)PRo{x>&EB7?Tqryu^ixWoV;@F7nO&XCO5K}GB#)wol6ZIe?a?5&*NYZXh z7?F!-V?;Wwhn7M{WE$!==M;}Komj%avoRu-XAF?9(};BTF4B|F#;J50k=_LaS%eNY zt=^0htBlAtQ3$ZOel#1spD+Bf5vk+nED-i}LNE>vuP`E&C`4c!8<9GkFHMZ@ zvue&)VBpb)Dn9cUGyk%wv9XI=NhJX*U3 z>Rmi30DWNWh(|e=8W^)7z7oc`NKXx8Mb&8MqtS{8@Yu*GCtzGiK_mScLkGrEr+~3s z>8=^>B6cTurLeJ-{!G_Ij!eQhg^oybS+S?Z3y&G(pue01ZbdCc zQK{UDkMG@d=cvbb2d=8gZ^f?qL;f^^=Al?KXIlZJ;LS~%NX*k6*K4$`yAuM7>8O zrktu^fhoIbDk~4u8QPN(WGhY3hAY_-rY~$rUT=aUV@uZ=QL%lc``~!dfDg?9L>9vC zBSovwsd|o1xo^y?xKdFKl6iTME-!dRdAYeF4%R^vKJURU44x?nOHHeM6s>bUEMTvc zTvo)6-Lp{4#Fro;C%L0Ei4nHrG~pEuIpJtj{i-Ka;G+egf=X~j7&|W#Ik7f86*=Mj z7?YD~2RWU(zR782eK@$rQuTb$hQ2t%a*2D8!$RFwJ+OwSA}2BYUUbX?q~RuVqHZm6 z(YHss{jV{(%3QuxjM$--z3G0KCSL=D0@ne8H{0@NnnQ{*&L@m+G(0Zj_YBXZ(H zWe0}yiJWHWdX!i5G$0sK8W`#!G)rbb9w!*i_$e?PlT(|)X{I43hG%jz|H5n}gdXZ` z$;r_Ua$Thq3``&H`*KZ18CdmtIJcJ=o8TX_nlPoc>e)!tG=ym$izp{R;Zho-tiIYkg_E^3c8PrC%6jiG)_) zML;rvq8)KseZAA_udbe$sb8nn-@{-5hQGbRF#>It>W-Ls5UxJKzL~c1_`sV2lf1>o zm2xNRE2_DOr+)FJ#l1Pdg9F8#)xzGxhpTTj|HZvVa_+r<{JR_$FFpQ|yY~9C zaT08P@w$sCQT0Q7!U@65p)idtZHoaIB@h#e3=Y)kp4;2Ww&P zCGTb2=N!3b?=St>yMO#cKiN6lr@=)geB#*8|H#ij@E6(MyYyW=nj2JuJLut0m`|?o zuEBqnXrk~vSIHq%{}o8IQ?!0>UT-`&xK}FzQVWbUNIwheXSCS%L{eNq_N0!;{k*y( z8c5^2+I{`$L|=LIRSh1hcXFC;Kiwi8?qzoU`cwZAS;OEoC>GzNKTz`>$Ag3aFCXf` zeG0v09(^%{SuoHXNi1e?#LhRxFzT5N9VL2oD%$!P?Y`~;oj6i+uO!;%zYnzfEE{Ll zoYFUD&>0trefvWlvum(#?SYDJ6DMG^g`Rc1ype)DNm6hsWZje`L1%>&Y~Gk9NWeHI z0jc4#QW_YKBqlI9Q+p@{AqB-Gk%B$$hKMgqLD?lq3KpxRApA4Uby@>d&(SGTxRHW! zC008*NS7ZEQqIyO1@R9HjChSFrJ%}3(_<+p2QyOesdO}xaf$(021<`Y=WS$Q3O}tn zVuU5C%}cClAt1av-!MX3CuW404`Ugq+93l^UEhpwW&MzVor85LMo6#huUz7OWui#w zt2)Gsd>3dGV$em{loKOtN1Pa;-iZ;$#KzSVBlK%ALUTOqq<31Vg2DqN7eAX4Vs8{f zGgiYK8%W~<9_t%Y(N;rRj%y(OSb{X#h6Jr4O(S1{+D2#>Fx|oXa!^%dW1MZs!J6Bc z9L6{sXx{(7Uw|ZPoe?(aX=aCJzs~}kzgomWP7o6piV$;pBy+&(k7ypn37Xe))4VYE z0AJ9-4y<$W3BL-(O{!~sufj^Y(2Ol0(W=dr^|kxTQ#Bjfk8~MPqgq9y^I#=5Ck88f zEe4CJ5rh3}4R*yq6zhz6m;{2z4**Zz&41%`*`781ZbS4xoYT9Nv1bj~o z-Ya>pA%yOX_PKGad4l)SjN$BJ83Oc_wM;#?mMOv*1DcZFNl$>DrQ-Jp&_x62*`}ZM zwVMFFve}qJY|dkuP9?qD3yEy8?#=g~ICLd;$-h?jj(IJDK(ZlIrT|7F^EzqN4O=ck zkZ7I6k-HV^p!H0Kz*rFA2tkl^oIlu1-OW~~uD?oTNGne!T5078uaja8QOy981qOlUJVWbTwq6RE zKSB+8*JW0fpv$7{pbom~fL@-;ZbD0bEx6(9ArUaLu$F{i7bFX7$qiN)(3SwZgaCU< zrH2MA7Y$&S{B(td72l#}rI#jGnga-4>7J}=uQXTfO53Qi5l}yIj}VZpf|sczCRSiU zpBc+Y_%g(Tq)w2lb`31}T}`J^Cn%Dzz(;D%f7Vza;xPdYj908jgpnvpM<$z3R)8n6 zc0~^kuxfb%xOOs~G|ZP!C09v2L9t#x$A)L!X?TK=UkJ9&cEuA`G#UKBUf*^a(tOlr z0ddgbW6|LUb1`UH*=KZhJ+2xYJ7DgOw@XejTC*qY*YujKtUMTeZ8VC8W-B(FT|b;F zB}(f4msfTVxKz?et43Sbz#8q-eYCIHoZJCVk{v;C5w-w~2sM4en}#BK#xf)pIb#_( z$QjEZjm}i2^~Pz@S%-D5dC0~|JVG{}E7?ewNjCO1b4{O?lT~!em#XKm0C`({G^|CU z7?QAb0=?I;CmQsYYfH08=$%&lpb6Aq*#i$76vRWTwzY*J08!6IL#j6kSTEa|C$Z+TK^FLHJWd-O zQes3?!}(y$&kc+Di6FV8-#rg3MvL0~v*%C@vr`s1pAkrFRsWF&(H}Hy2jVyr-9M!o z^KACEY-bI+cLZCrs;8iP&VwaX{0lWX*9;=K$mw~8>Yn{T*V}T-L+_Sh-1WwGvtz*d zfHQFrWuQtkd#@UpI-8lP?(;LVW>l6AAJoA3JS6yy)*;GY4{#=%2&nvzuB&hDoe?VJ z<4REZA$NW@P*L4Ap%Mgn4l>{B<_35}Y(C6Y8wxH`q=1JRgF zlrLUsFVC9~YPLFc{Z*R}s$WHwgLPN>GJRpmr45ACZ8dkG4*W{{4-`2ta$SLmL!|A% zlfJB0+HHu5R+7H#*V32Kenp+5IRWEBQRg{qh<%syB5+k*NIUk-!Tj}&uX88|Xt+r1 z)L;2Jr@iAjuG54KL$tv{~rMarCq4|V0N zoA}wL2@cZ6_M(Cb&WQJHHYL3Od4u3=@LqM-#QSK{fIXQZuV-e>sMN&S0(P?Q8h~{$ z>zTAZ?P#B=OlXGCYOAn@9ys|Vw7$tF;502yJQ4i8zxrjR(eL< zZH5=4BPT(#?e;941ngXi>8W;XyPchrKqHZpV5V^r z%&cJ^{+`Uknbt`#Gie^8s)kalR?f;v0Mi@j1Py!KWu}jwGK<9XxB}D4Y5b&ce0wr1 z2^zKBUyL8CK#BY*8;k*jP2S6=*`X{Us2Frr&1=W1d{><0ZsDEn4<0CC(H&( zlYp+;0rXSXU$xh%`gWmLW%A4@WI&#o7+G{%aYpUm3h3dPaS>7?sjh8u92^Pgv|^yE zNW0~k2Cr7yEl*yDq-pibfuc$;$!wI9`>V`Mh4rKAw>Nmd11liQ`d9Rzs|P{3^&@&P zr3XQ{_0KgWp7ml$I3 z&)6OlJYyN=e5NuwDrEL0Hy0aY1Ah#e^dOM?Caw>THl?jGlFhFO5WxqQ_{yUAislf* zuQ-B}4A99kHqTcT6->-}&Z`!w3}SPUT0Kv#s(iFMCY;5nB3CCCwA_DoXV(7#{~*z# z#NWJB-p`hm$$$1`>hE*}fjpIpAP6R#cE0d;{<-{}y_S^`KQi`r;-q|*tZd9_7I3^M zpduqQ%UleUZbGvfgHe$e(A}^P2jqa6oSqC<3`n6_bL=mJCmPDIpFEv5jXB>!yX$lz zi%l^q{PJyRR@uaoFg$w=&ZwvSf;OuaOSeX7)-Ul6K4`>f8k`j35twyGg+VMOIhmM# zcpc_b%#+foA)dCyWA~r~A zKu8Kd-niy>qXN$tymM_z6E{5NT93J5ifd>#erA3AOef78YJp_pNy5X~z+MGxuwZS# zA(E+i!{fl~g@o5Gt8bPw8}W1`(;3@?PR>{c`Z!aW=17{uPGI!w+=>gI+vGMJu2Sm* z8{4>*je@{9ZbiDmDPQW9MOVbMsH{J$J;hpG${r&Ja~YYL6#KfUhizQrIIA0na;c`* z5&4-m&zluc1B%DdUZU1Noy?8Xb^Q;|@VT){@zdUVQarZs%6vAD3%n9+uWBDkr(>%w z(eoSvS2VQ#d82iq$l1$i6e>MrrD zr#|M@)FfUiXEh|H5lq78G{#s2uQs!Q6waaQ|e-_MQ%HIW;}%CUA|2MxtZjEJ%*l#P?`ZH6VgH;HnzK?x{fE3^gro!f}0p zR27?#WNRPIOXT95MRBVEDRmRos!jt@qgz0X&gWSh)Vc+8nT9FXgdy|n1*6*DlII=+cp6JsOvJg}C{j%;I zJsS5rbst$F^4THfyM)$LjCN<}T#kyK#=@IYf(w6( ze42yyopc&|ma908&8C7*W8=DoLvZQS*e`Kkt_$FB zR5}xX68+Hrvc|+C?oTQxO2Fei!@0)omIMu?}7ZQ3XspG~C)(uv>= zLEfU>#ewo>pD)3V=3_V^Z+omX)mr>Rr!#)+q&NW9O4R29*qsLkUzJ2IEjv-pjC=02 zVX<0irOO0<2z>rRP)D5EQd9?SeOSi|=vD#b;OU-x{FiC~ps`ZRTn+Qe8UEl`$= zChub>jSZs}E5}+OsMfr3+cF%S51ZAnbu-SHt>w4R04VI13@MawlfRE<4L)G6Z7(j{ zRUYHza&cpDy6X+qLfK_pBjJw9Qc#3jI8SI<|IU3(2|qsIAs-M{hFT}dOyUGiRbSLc zCmw)~MScIo1Fb+5uj2?EdPv&{>)~Z`=IUr>G#`(gc{UmvE%*_&2&bm4(W1V};nBFh zhv8}xI+aWNL>}cb${C@PsuI#6#70tyEcH`TX;w&IK}TyFBay&zy++EGw>3s%6kA1T znCX}du$AJ-Pa~!+TGs3g1{s}6<08cX=;kEu3VYpDERdn!$zP8b4ZmbcO8#01`bz$4 zFsQG=XjVU)R;N4C!}}6dz-&iC^~c&JDeA*K6SB~GTF6340J69_fLu15=LtUGp}$FA5j)6qmxq!u$q4RnY#<9>j3(6!xe`Y<#4S_K9JmKA z-T1V#u9imG%6vn7s6bp6rI94fx&k?LYKs;I^2ctXf^rP;+MmNV?nIJ{bXcn!B0#LD z7kMc~-ED56TuUFV(d?*%rD}prvwB5tp~*+st8iu5s&WgNjmP1z#IYt0hn4CNM&eUk zaqvqMZa6l~mYh)BB94I133P|xdyuQL(eml&jPfupT-diF1W2SrCrxf4$pn(pBBGRA z2yU0@t=`imxrMfIL;n?HTo!j@|M|RPPc*oU$Y%K|yIqtQ8L?=h8s$xnRPL@46qH=Y zUt{1eA7Y>zfs%f<7gz5pUL&zwii_o>_@T}oLry^>;?GfYvd(K@{&7mb9h8Q#%=8RS z*V#SMO{3E|tJn^YC|ak!QpwimYT8PC<0L15(xItAudELg%AfRG9NNpw|&c3LP0l{_xOIuB!{&L2R)gbiXnq<88r{Q_qa%4@ec~`jyErlEv3i6mVStl%rvbF)`tZsTGBsuWWR12#km0iA1_x#t zIV=%f)@Aq$Y#XQvf;<7X?Gw)RspR;HdLwCbc@>Y>f@~UX3t8g^eE2nVy)Nj2%Y%z6 zrRxlh33Z=?uPCPQg(gD?+6drTm3TAFmY*ur*yOQQ(oRiRnn7}iXRuM({hZOw;&=hI z?6i}f=ul!b21#KkXnLe$&~}2zMUMA_ja@zWIS!H7_;>*&{VK&;f>x`J7f5C!=GNP; zgDh7zDel_CM@`$(0QlI1e(a;>SBI-o+Aw+oa_gKxIAte$YR-0aIfEHtmbQP#nuPfD zDPPvPPM859u?>07wuOgPqep(oF)OWx(IpegnTMb<+o@wd5{4o=L$Zz1>(((#1JbXx z3R>o*)}Zi7QraSArD0Wqd2qqhc4D+YD)w+d9DuZN9v@`fwurXtrV z5Y``^8!9aD9c6)jK2o8v5{5Bq8NsIxmy6d39nrrGv5wfgwN@d8%1KIIMQu(<=Z(4V z4sBDSkjMw32~*w{T!)2i@1<}jXOrIRtgu{4!b>;4XEd!3yQz5%o|D%gZ>X7!NbJCa5KvUz%*s_qyJ(BCg33?sULG}oSQtXL; zItZGr@5^YOwQ_Bs46pTpG;~OgZ_n=a32lOX8EVvnQ!KVQvX{w>q~AxrfmYemw&99k)k8lL*fg+>bx9rZp2M1vu!`PP`~m34NEkURdenv3w21Q+8$@A6(oHS8HX7-=nP4W z?S-$!0_XPQyU*flvERBH5rFV>gbOofV}qZ{`|qD(PqE2r1***G6TfmH{!~7i;;Wz; z`e%Lk@EtY#`h#DaEEy-ftPl>c&M%z7x|PBzB-hvCBh@YWb`p#UE{Jl zs9UXG{W*s0KQvCY-a))$BB=X5#B=>orz`;ffRo-lqZ<07H-JnudZpfwn>Rrfd4sdA z^E&O0vUb3c(R1;u>g~G?I2zm)@n~@qbqmGsY*dYM8yrVLmF!iSN8q-+3z?_r1J6Amh%W zXT(7YbRn0Gb}a;zj*}qkD@1d6F_cw+uIf*{AHeAkMd~|2f%5k6pkWl3z(r8XLo~o9 zXD@&$f(U?l#M3-r$a`W}yOW|GLd^OK>|je5ayd z`{F{s!S~?CsdgciAItmwtF3YB}`?=Ab%u#BLHSuNXXVP z5Mx}DK>AcIeyTNEdc19E_qto+@Xi`7eXMQi73*%vM|@72-G`q=OFntRiHTQg(jFav z;0nm-YpS?{^$}LV?lT{4U`N@2-C!-)!G9>NSCdfmnSql=e7tRQ{d0Ga{J5nwx+Bk` zB_f1mIM?*OdhWuC$+>@|nR`3VvzWUb8tdxk(J!hW`yR%SNjQJBZSz&lx}6c{ZF-&5 z(!FgpbIP33n=efyz2$e#`BvAV^}jR)$q)5rkR z(rs(5K-*0$9s6e${jjUHw+s){<3Fg5HH5!^snD5%a}>PH$f_!=HZL#ZD%gNNDqs28 z%ee9Y;RY0uW48asN!FB9LEbf=vNqB1?b-Uo52;&7LL=QqbKLE4tMNf=_1S#Tx{&(n zE#x*QoS4^%Nee`qNyyl8+hY^>3@v{CX>iRngOrs?y|~ z#HTc;V|pP_&&lY0ggf=+X^U7@{XsPqNBpiB(EskUPH36}Y|QLz>peOz_t)@by}XVa+o2#tQ?R3n{4 zo=;N9ODdK@rTb4clF3RBHkHI3sPr^~tKP82@Fy!BNtLKFcv^(0==uIXT0w}QINC(4 zH9R3aPGU71DIdHCIcL-uYt?Mr$jV{}YsHNur^e zgmx_VsM`)uXxl0iLpjJV_QS+WU>N$nTUVm_;0- zJq-TZrcgfk@BY=3?<^J-F|Tj2EBIPYyJ5jC#XJ0@$j@>Q-wGs5iB`0kjMwB~y_4i_ z#Y3aAUc8mBl+R+U^$sYXM6q|EOn|sw2q$x?Ql~vAcr=$qS9flAIW*LR^iu3S%qQrJ zz0*gA(u%z;oYB6#2U&f0ekIX68ejc~e+IOFKj(2?e}?NC43KGcIIl7#tBdg}*YdNS z&%a?b>zEM5zO*yyUjPVL!jb*}414%lyTe8Q7v)2V^@Z?Pt*@S($QQ}U>d*qMv3AI{ zgD@<#q&h0~1vUk|lv>A**RhkIs@^jI!GK;J zJOFK>G*{)tW|I79uG$RJ-ah^W@vcERgl&1j;Zf+A0Sv_mhc|wTt0@fzI6T1o)QX2Y zxs2^J-INygBPEfq-r@+QoYBi=H0X@nbix+k4U!CJ1MGYiaF_LQrnM77A?ik&(N2W{ z9^?YIZo#6cuHrr!EBm0EMzi7CB^Fml1BN+;cHrXDHL{iM*P(RE-R#&!CCale!Xh8k zDG=N*mFs~mDvPEY-;m(PpOIkXKBE)3UTOHH;bBzMmy?A5xZeXZ?j*zXkjDiw(5Pco zk!6WAz?HNPg>8gI=tmw1(*dpr(*d?cWBELNEU-=>sh{D-ba47QK|AtaP^YDxGRym) z;cTcGDodePQV+WXK)*W>D^g7`_xG{11A>HbV0MJ?< zpf2+sPdeNL^&?=<_K5f`L@mcB?>SKO*yKHfeqwwtDDFK<5b$BnT?wdESF4iPr917= zsa8P|SbiU%27g#Bx#!dJL567+MC4jI`SUeq5r;<#jU>*Mcor+;$(S!~fiFGH$jqqf z-{x0I^R@jDS|KF|bVe_V{2x4#bCW=k1}#Dmb2UAPu;LTWI?l0zH`W5DnWUzexMhPS z4Yx!Q4J>K6CG2y>ivK{T$b%5qTJKe>4HWSf$7-)&`t@;U5nl zBa`VV?OXF!Uw)5A;crJ{2U@g zY;K>75MBf!!kFI*V$T*e?k$UA5;e4FI!mv$Yc?C2#Rgw38Zeq;<=A3i#p~Y>yZ~_s zN-$nH(!bpM`^F2fiBhmok>S)R4m%3IqF06E2{jDWNz^bA3t?(bsI)Xn!>zDhNu%%| zyv-*s%adohETd|==m-ECEqd=C(V|zTT@A*q6A9FgZ@6(xLbSSun_Nw^8q=dr;~>xYZ_2uH5?N3z zhKU+rXC03SKiXx^;scDt-0K=ntV2|dUe_p1tzLKkKNY#4l!%WXorjiOuZDH<_nEhnJ7LY*2z zna0Hn94Ku8?vq;nHZmT-xpILm{}>v%zRiy z6U5m8_t*q*0eK{f71)Rb4xj>Wc9S9!+2E71*MiUUUya)1l|4yg4^1Vl>{wHo#Dx506*$bd_RBQ>CY$<&uWV$|>O1Yzv(wY?{zCP3Ayxjxuq2EFE!K zB#VN;DuO>dgLSU5!TPh;@o8{}YNfAfh{eXgv|bm`=LNxN{nO#jTK5Hmc`03aXpR@! z7DZ1UvH>F$+Zwz*S4@~wPYWfLqj?b-!fyA4)mEJ;Sp-{#gBSU%;u3F{s+sYDlBG*B zq=i1i%oRJ;aiF9i0k|S21}i^c9k>2AtQh4I@`SeDqNVLun-ybp2^EDjf6JyD&BHgS?Bmmi#rT*Ec8Dk|2vnm%7UogK*s@s({R z&v(gnAciKnni-X`z-3zC?#cy@+jfDcQnQSCR_ozj=trT7vuVBfy#RKa-m_k_S}%Pj z9n_o!`Q*L6NKdy6OH?d?Kg+@dHtD?pNc~GP&J0zvFUhv*`r>+U+oArb5QY(U8Oaod zX!YQB;o8K&g#@8&#W&gYfw(l9Z*y1bK%KT-X(&CWp^*w)O8vmxIG&UbrwhnlBS zxLV>Aj3y(0L4VluQ6H*tmdV$0r1%=m&(|sgLJEE?JZYqc1r7Hqz7QC*52`|u;blp@ zR9~O}BbrGvz)_J>7GsK<7*`TrX<5?L_OEwps=8`w3DuaI4)}brQ`5JqzWAvL0G7A2 zu`>?R5;|-O@>wQ2*$^2I0N92^ccn6A&NUXFZX6)G!MsyBJTsp%%XOLiEE8YG5+7R7 zid$+fh}6x;l?KI##4$h1@dZmsDfI0FbCJ_l*2*uKUQM8tC4(fHmVTl3>1U-&rJq|Jf`+)r7%yH8`oP{b4A?EhqYaQok2(kXux0l7gdaX0<}ha^u{S4#yjuPa zN16@6qNMORyW;R79BM&*8d$=5@P45cG+dn|XnHI8P~5hB$ncv1u2l6h#QRpzEYy54 zjdzP7X!!EeOR{+h8qQUw_0-kI#2R`kJ14^DV7J&nuN5>-jAX9mb7!WOadIRr@Ja^E z3=hNv(_iwHrZMt6Vq@7sYV zIgRzC1vsqmanPrjZTG}9|!qKeA*8MMLe%#Ko*WX&N5ltYm4TwXt&9T=Wp zCM`&iQ?YLxZK_LwXVXGa7K%`>y4=~VFXuTtRMf03ctM*}^$8@NZr*I^WR;0@1VRWh; zX*L=`0?M-IpXTwMn!l#2M9+d;fs!==pB3+;b2`3)jf4;9u?8B(=$x3v`BGZ$kt$c6 zua)<#+WGJe_nX9ayavGK+YA9zRXt(?=TapY1@~zQ&h>*G!V0INUgq1Y=cwK-2rGWb zV-igYEFRg^%8{M7o{^omVq{Z;{bx0@DMz$uWaq`mIJ&KoIsR?}wF9kv3OC_gI>Okj zHm7~t^@7cp=5N8USmd}3{w4v<$1{gyPM+FV%}3gm*j<_?6U)A8?$99^cJhd8(?(hc zfQzHLf*r)6XAm1k4u{V9(V1mlL|z3=hD{Zf(NT0CmmNBsw5&K+(kP*@ch~_-i*hQy zVxei}S5~LjMl`q$B<5Ac4@RYe!8rD*O_gQwhwlRmb-wuAMy*aAB()lykrh%0D1Fq^ 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 G{^VcO0K97e diff --git a/contracts/example/Cargo.toml b/contracts/credit-manager/Cargo.toml similarity index 76% rename from contracts/example/Cargo.toml rename to contracts/credit-manager/Cargo.toml index 3fff95477..08506f55c 100644 --- a/contracts/example/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "example" -version = "0.1.0" +name = "credit-manager" +version = "2.0.0" authors = ["grod220 , larry_0x "] edition = "2018" @@ -12,8 +12,9 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -fields-credit-manager = { version = "0.1.0", path = "../../packages/fields-credit-manager" } +fields = { version = "2.0.0", path = "../../packages/fields" } +cw-asset = "2.0.0" cosmwasm-std = "1.0.0" cw-storage-plus = "0.13.2" schemars = "0.8.1" diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs new file mode 100644 index 000000000..fcb164392 --- /dev/null +++ b/contracts/credit-manager/src/contract.rs @@ -0,0 +1,82 @@ +use cosmwasm_std::{ + entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, +}; + +use fields::messages::{AllowListsResponse, ExecuteMsg, InstantiateMsg, OwnerResponse, QueryMsg}; +use fields::types::AssetInfo; + +use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let owner = deps.api.addr_validate(&msg.owner)?; + OWNER.save(deps.storage, &owner)?; + store_allow_lists(deps, msg.allowed_vaults, msg.allowed_assets)?; + Ok(Response::new().add_attribute("method", "instantiate")) +} + +fn store_allow_lists( + deps: DepsMut, + allowed_vaults: Vec, + allowed_assets: Vec, +) -> StdResult<()> { + for unverified_addr in &allowed_vaults { + let addr = deps.api.addr_validate(unverified_addr)?; + ALLOWED_VAULTS.save(deps.storage, addr, &true)?; + } + + for denom_or_addr in &allowed_assets { + match denom_or_addr { + AssetInfo::Cw20(unverified_addr) => { + deps.api.addr_validate(unverified_addr.as_str())?; + } + _ => {} + } + ALLOWED_ASSETS.save(deps.storage, denom_or_addr.to_string(), &true)?; + } + Ok(()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute(_: DepsMut, _env: Env, _: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::UpdateItemString { + str: _, + } => Ok(Response::new()), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetOwner {} => to_binary(&try_get_owner(deps)?), + QueryMsg::GetAllowLists {} => to_binary(&try_get_allow_lists(deps)?), + } +} + +fn try_get_owner(deps: Deps) -> StdResult { + let str = OWNER.load(deps.storage)?; + Ok(OwnerResponse { + owner: str, + }) +} + +fn try_get_allow_lists(deps: Deps) -> StdResult { + let vaults = ALLOWED_VAULTS + .keys(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + let assets = ALLOWED_ASSETS + .keys(deps.storage, None, None, Order::Ascending) + .map(|asset_str| Ok(AssetInfo::from_str(asset_str?))) + .collect::>>()?; + + Ok(AllowListsResponse { + vaults, + assets, + }) +} diff --git a/contracts/credit-manager/src/instantiate_tests.rs b/contracts/credit-manager/src/instantiate_tests.rs new file mode 100644 index 000000000..517c52b76 --- /dev/null +++ b/contracts/credit-manager/src/instantiate_tests.rs @@ -0,0 +1,108 @@ +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; +use cosmwasm_std::{coins, from_binary, Addr}; + +use fields::messages::{AllowListsResponse, InstantiateMsg, OwnerResponse, QueryMsg}; +use fields::types::AssetInfo; + +use crate::contract::{instantiate, query}; + +#[test] +fn test_owner_set_on_instantiate() { + let mut deps = mock_dependencies(); + let info = mock_info("creator", &coins(1000, "uosmo")); + + let owner_str = String::from("spiderman123"); + let res = instantiate( + deps.as_mut(), + mock_env(), + info, + InstantiateMsg { + owner: owner_str.clone(), + allowed_vaults: vec![], + allowed_assets: vec![], + }, + ) + .unwrap(); + assert_eq!(0, res.messages.len()); + + let res = query(deps.as_ref(), mock_env(), QueryMsg::GetOwner {}).unwrap(); + let value: OwnerResponse = from_binary(&res).unwrap(); + assert_eq!(owner_str, value.owner); +} + +#[test] +fn test_allowed_vaults_and_assets_stored_on_instantiate() { + let mut deps = mock_dependencies(); + let info = mock_info("creator", &coins(1000, "uosmo")); + + let allowed_vaults = vec![ + String::from("vaultcontract1"), + String::from("vaultcontract2"), + String::from("vaultcontract3"), + ]; + + let allowed_assets = vec![ + AssetInfo::Native(String::from("uosmo")), + AssetInfo::Cw20(Addr::unchecked("osmo85wwjycfxjlaxsae9asmxlk3bsgxbw")), + AssetInfo::Cw20(Addr::unchecked("osmompbtkt3jezatztteo577lxkqbkdyke")), + AssetInfo::Cw20(Addr::unchecked("osmos6kmpxz9xcstleqnu2fnz8gskgf6gx")), + ]; + + instantiate( + deps.as_mut(), + mock_env(), + info, + InstantiateMsg { + owner: String::from("spiderman123"), + allowed_vaults: allowed_vaults.clone(), + allowed_assets: allowed_assets.clone(), + }, + ) + .unwrap(); + + let res = query(deps.as_ref(), mock_env(), QueryMsg::GetAllowLists {}).unwrap(); + let res: AllowListsResponse = from_binary(&res).unwrap(); + assert_eq!(res.vaults.len(), 3); + assert_eq!(allowed_vaults, res.vaults); + + assert_eq!(res.assets.len(), 4); + assert!(allowed_assets.iter().all(|item| res.assets.contains(item))); +} + +#[test] +fn test_panics_on_invalid_instantiation_addrs() { + let mut deps = mock_dependencies(); + let info = mock_info("creator", &coins(1000, "uosmo")); + + let res = instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + InstantiateMsg { + owner: String::from("spiderman123"), + allowed_vaults: vec![String::from("123INVALID")], + allowed_assets: vec![], + }, + ); + + match res { + Err(_) => {} + Ok(_) => panic!("Should have thrown an error"), + } + + let res = instantiate( + deps.as_mut(), + mock_env(), + info, + InstantiateMsg { + owner: String::from("spiderman123"), + allowed_vaults: vec![], + allowed_assets: vec![AssetInfo::Cw20(Addr::unchecked("123INVALID"))], + }, + ); + + match res { + Err(_) => {} + Ok(_) => panic!("Should have thrown an error"), + } +} diff --git a/contracts/example/src/lib.rs b/contracts/credit-manager/src/lib.rs similarity index 67% rename from contracts/example/src/lib.rs rename to contracts/credit-manager/src/lib.rs index 9121fd34b..91b970d23 100644 --- a/contracts/example/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -2,4 +2,4 @@ pub mod contract; pub mod state; #[cfg(test)] -mod contract_tests; +mod instantiate_tests; diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs new file mode 100644 index 000000000..720b1e885 --- /dev/null +++ b/contracts/credit-manager/src/state.rs @@ -0,0 +1,7 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; + +pub const OWNER: Item = Item::new("owner"); + +pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); +pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); diff --git a/contracts/example/src/contract.rs b/contracts/example/src/contract.rs deleted file mode 100644 index e0ea009a8..000000000 --- a/contracts/example/src/contract.rs +++ /dev/null @@ -1,48 +0,0 @@ -use cosmwasm_std::{ - entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, -}; - -use fields_credit_manager::example::{ - ExecuteMsg, InstantiateMsg, QueryMsg, StoredStringResponse, -}; - -use crate::state::SOME_STRING; - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - SOME_STRING.save(deps.storage, &msg.some_string)?; - Ok(Response::new().add_attribute("method", "instantiate")) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute(deps: DepsMut, _env: Env, _: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::UpdateItemString { - str, - } => try_update_item(deps, str), - } -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GetStoredString {} => to_binary(&try_get_stored_str(deps)?), - } -} - -fn try_get_stored_str(deps: Deps) -> StdResult { - let str = SOME_STRING.load(deps.storage)?; - Ok(StoredStringResponse { - str, - }) -} - -fn try_update_item(deps: DepsMut, str: String) -> StdResult { - SOME_STRING.save(deps.storage, &str)?; - Ok(Response::new().add_attribute("method", "UpdateItemString")) -} diff --git a/contracts/example/src/contract_tests.rs b/contracts/example/src/contract_tests.rs deleted file mode 100644 index 6d2020a59..000000000 --- a/contracts/example/src/contract_tests.rs +++ /dev/null @@ -1,65 +0,0 @@ -use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; -use cosmwasm_std::{coins, from_binary}; - -use fields_credit_manager::example::{ExecuteMsg, InstantiateMsg, QueryMsg, StoredStringResponse}; - -use crate::contract::{execute, instantiate, query}; - -#[test] -fn test_proper_initialization() { - let mut deps = mock_dependencies(); - let info = mock_info("creator", &coins(1000, "luna")); - - let example_string = String::from("spiderman123"); - let res = instantiate( - deps.as_mut(), - mock_env(), - info, - InstantiateMsg { - some_string: example_string.clone(), - }, - ) - .unwrap(); - assert_eq!(0, res.messages.len()); - - let res = query(deps.as_ref(), mock_env(), QueryMsg::GetStoredString {}).unwrap(); - let value: StoredStringResponse = from_binary(&res).unwrap(); - assert_eq!(example_string, value.str); -} - -#[test] -fn test_can_update_value() { - let mut deps = mock_dependencies(); - let info = mock_info("creator", &coins(1000, "luna")); - - let example_string = String::from("spiderman123"); - instantiate( - deps.as_mut(), - mock_env(), - info.clone(), - InstantiateMsg { - some_string: example_string.clone(), - }, - ) - .unwrap(); - - let res = query(deps.as_ref(), mock_env(), QueryMsg::GetStoredString {}).unwrap(); - let value: StoredStringResponse = from_binary(&res).unwrap(); - assert_eq!(example_string, value.str); - - let new_str = String::from("blackwidow"); - - execute( - deps.as_mut(), - mock_env(), - info, - ExecuteMsg::UpdateItemString { - str: new_str.clone(), - }, - ) - .unwrap(); - - let res = query(deps.as_ref(), mock_env(), QueryMsg::GetStoredString {}).unwrap(); - let value: StoredStringResponse = from_binary(&res).unwrap(); - assert_eq!(new_str, value.str); -} diff --git a/contracts/example/src/state.rs b/contracts/example/src/state.rs deleted file mode 100644 index f785703ee..000000000 --- a/contracts/example/src/state.rs +++ /dev/null @@ -1,4 +0,0 @@ -use cw_storage_plus::Item; - -/// The mint contract for the collection. Set on instantiation. -pub const SOME_STRING: Item = Item::new("some_string"); diff --git a/packages/fields-credit-manager/src/lib.rs b/packages/fields-credit-manager/src/lib.rs deleted file mode 100644 index d4d8a94c8..000000000 --- a/packages/fields-credit-manager/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod example; diff --git a/packages/fields-credit-manager/Cargo.toml b/packages/fields/Cargo.toml similarity index 81% rename from packages/fields-credit-manager/Cargo.toml rename to packages/fields/Cargo.toml index 95046e7a7..852f6e8c5 100644 --- a/packages/fields-credit-manager/Cargo.toml +++ b/packages/fields/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "fields-credit-manager" -version = "0.1.0" +name = "fields" +version = "2.0.0" authors = ["larry_0x ", "Gabe R. "] edition = "2018" @@ -10,6 +10,8 @@ doctest = false [dependencies] cosmwasm-std = "1.0.0" cw20 = "0.13.2" +cw-asset = "2.0.0" +cw-storage-plus = "0.13" schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/packages/fields-credit-manager/README.md b/packages/fields/README.md similarity index 100% rename from packages/fields-credit-manager/README.md rename to packages/fields/README.md diff --git a/packages/fields/src/lib.rs b/packages/fields/src/lib.rs new file mode 100644 index 000000000..3c759906b --- /dev/null +++ b/packages/fields/src/lib.rs @@ -0,0 +1,2 @@ +pub mod messages; +pub mod types; diff --git a/packages/fields-credit-manager/src/example.rs b/packages/fields/src/messages.rs similarity index 52% rename from packages/fields-credit-manager/src/example.rs rename to packages/fields/src/messages.rs index 65524fbfc..0f4d1d93d 100644 --- a/packages/fields-credit-manager/src/example.rs +++ b/packages/fields/src/messages.rs @@ -1,17 +1,13 @@ +use crate::types::AssetInfo; +use cosmwasm_std::Addr; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -//-------------------------------------------------------------------------------------------------- -// Types -//-------------------------------------------------------------------------------------------------- - -//-------------------------------------------------------------------------------------------------- -// Messages -//-------------------------------------------------------------------------------------------------- - #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] pub struct InstantiateMsg { - pub some_string: String, + pub owner: String, + pub allowed_vaults: Vec, + pub allowed_assets: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -22,16 +18,20 @@ pub enum ExecuteMsg { }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct UpdateItemStringResponse {} - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { - GetStoredString {}, + GetOwner {}, + GetAllowLists {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct OwnerResponse { + pub owner: Addr, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct StoredStringResponse { - pub str: String, +pub struct AllowListsResponse { + pub vaults: Vec, + pub assets: Vec, } diff --git a/packages/fields/src/types.rs b/packages/fields/src/types.rs new file mode 100644 index 000000000..fbc19b439 --- /dev/null +++ b/packages/fields/src/types.rs @@ -0,0 +1,32 @@ +use cosmwasm_std::Addr; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +// TODO: Local AssetInfo should be replaced by cw-asset when fix is merged on that side +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AssetInfo { + Cw20(Addr), + Native(String), +} + +impl ToString for AssetInfo { + fn to_string(&self) -> String { + match self { + AssetInfo::Cw20(addr) => format!("cw20:{}", addr.as_str()), + AssetInfo::Native(denom) => format!("native:{}", denom.as_str()), + } + } +} + +impl AssetInfo { + pub fn from_str(asset_str: String) -> Self { + let words: Vec<&str> = asset_str.split(':').collect(); + + match words[0] { + "native" => Self::Native(String::from(words[1])), + "cw20" => Self::Cw20(Addr::unchecked(words[1])), + asset_type => panic!("{} is not a valid asset type", asset_type), + } + } +} diff --git a/scripts/package-lock.json b/scripts/package-lock.json index e240576e6..a9eab35ce 100644 --- a/scripts/package-lock.json +++ b/scripts/package-lock.json @@ -1,12 +1,12 @@ { - "name": "fields-scripts", - "version": "1.0.0", + "name": "credit-manager-scripts", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "fields-scripts", - "version": "1.0.0", + "name": "credit-manager-scripts", + "version": "2.0.0", "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.28.4", "@cosmjs/stargate": "^0.28.4", @@ -17,13 +17,14 @@ "devDependencies": { "@babel/preset-env": "^7.18.2", "@babel/preset-typescript": "^7.17.12", - "@types/jest": "^27.5.1", + "@types/jest": "^28.1.3", "@typescript-eslint/eslint-plugin": "^5.27.0", "@typescript-eslint/parser": "^5.27.0", "eslint": "^8.16.0", "jest": "^28.1.0", - "prettier": "2.6.2", - "ts-node": "^10.5.0" + "prettier": "2.7.1", + "ts-node": "^10.5.0", + "typescript": "^4.7.4" } }, "node_modules/@ampproject/remapping": { @@ -1770,39 +1771,122 @@ } }, "node_modules/@cosmjs/amino": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.4.tgz", - "integrity": "sha512-b8y5gFC0eGrH0IoYSNtDmTdsTgeQ1KFZ5YVOeIiKmzF91MeiciYO/MNqc027kctacZ+UbnVWGEUGyRBPi9ta/g==", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.7.tgz", + "integrity": "sha512-NTCUS3+u9bxwW8moC0N1RS4Gk/fs3JPHTKcp7ksH39V4+7uOKM2rGsyFAekiHNxYngnQ+1hU5x2tddS25soK6Q==", "dependencies": { - "@cosmjs/crypto": "0.28.4", - "@cosmjs/encoding": "0.28.4", - "@cosmjs/math": "0.28.4", - "@cosmjs/utils": "0.28.4" + "@cosmjs/crypto": "0.28.7", + "@cosmjs/encoding": "0.28.7", + "@cosmjs/math": "0.28.7", + "@cosmjs/utils": "0.28.7" } }, "node_modules/@cosmjs/cosmwasm-stargate": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.28.4.tgz", - "integrity": "sha512-dkTwTD+j2mjk7+l3pQQ3io2D0U7NIA4LXzkKtfBN87PGlj2G+VJFzcXk1T4DYmvrXjsQOi1kYeQRGWFA0XdvnQ==", - "dependencies": { - "@cosmjs/amino": "0.28.4", - "@cosmjs/crypto": "0.28.4", - "@cosmjs/encoding": "0.28.4", - "@cosmjs/math": "0.28.4", - "@cosmjs/proto-signing": "0.28.4", - "@cosmjs/stargate": "0.28.4", - "@cosmjs/tendermint-rpc": "0.28.4", - "@cosmjs/utils": "0.28.4", + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.28.9.tgz", + "integrity": "sha512-Icgmiq/hM72mWaCR3FBKb6VZSdeETfTSMZ8E26wBXsvmU24pFqIIqRmJ9y7jSyqRKnGY/0skYNitCob7ylqPSg==", + "dependencies": { + "@cosmjs/amino": "0.28.9", + "@cosmjs/crypto": "0.28.9", + "@cosmjs/encoding": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/proto-signing": "0.28.9", + "@cosmjs/stargate": "0.28.9", + "@cosmjs/tendermint-rpc": "0.28.9", + "@cosmjs/utils": "0.28.9", + "cosmjs-types": "^0.4.0", + "long": "^4.0.0", + "pako": "^2.0.2" + } + }, + "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/amino": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.9.tgz", + "integrity": "sha512-8yaWIlU0W5nF7xyR1v81Wr0+Eo5xJ1bDCkTAsb1JV3xtXvfsl2jF1l4aV64vi+WWEghE+9embhZm7zxBljdunQ==", + "dependencies": { + "@cosmjs/crypto": "0.28.9", + "@cosmjs/encoding": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/utils": "0.28.9" + } + }, + "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/crypto": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.9.tgz", + "integrity": "sha512-2U8cuH7ofRiYni3EPAUsPuqgHN0VM+HdHiY1Ftl2rqRRnZArZNJsbf4t21jVx7rlc+qP32Y1v7PvQyPALHvuyw==", + "dependencies": { + "@cosmjs/encoding": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/utils": "0.28.9", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "elliptic": "^6.5.3", + "libsodium-wrappers": "^0.7.6" + } + }, + "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/encoding": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.9.tgz", + "integrity": "sha512-AE+uL5LC2f9ZE8ehFPgb7yNMuGr4wx/cbH25gglRwl9utdND2lPVeYmbEF2MRJwB69hXaiPHblCDIz3KXZV9pQ==", + "dependencies": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/math": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.9.tgz", + "integrity": "sha512-CY4k3GMQqXL3M+dGUptgCL6mtkcfvhKhmsrzejSZB8yVOIdKA40Wu6buRMMh2+t3SkWpvjVQt8UiJF7C4C4NXQ==", + "dependencies": { + "bn.js": "^5.2.0" + } + }, + "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/proto-signing": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.9.tgz", + "integrity": "sha512-8MnCvIa2s9myhXEMwsjKOh9/zGp1850QiW9XUy6YtNaQ9wgc58/WMq7HtfkGPqIZnhOeR7KXIyQ0/KHbmjhtnA==", + "dependencies": { + "@cosmjs/amino": "0.28.9", + "@cosmjs/crypto": "0.28.9", + "@cosmjs/encoding": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/utils": "0.28.9", + "cosmjs-types": "^0.4.0", + "long": "^4.0.0" + } + }, + "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/stargate": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.9.tgz", + "integrity": "sha512-1Jh/+qsyzM+7+ek/peQa3xumgU99SvAI5ecDZNS6+qOaWgGdGSW8n40sEfv6viRVLDppfoYo6/9QfR5h3PoKgQ==", + "dependencies": { + "@confio/ics23": "^0.6.8", + "@cosmjs/amino": "0.28.9", + "@cosmjs/encoding": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/proto-signing": "0.28.9", + "@cosmjs/stream": "0.28.9", + "@cosmjs/tendermint-rpc": "0.28.9", + "@cosmjs/utils": "0.28.9", "cosmjs-types": "^0.4.0", "long": "^4.0.0", - "pako": "^2.0.2", - "protobufjs": "~6.10.2" + "protobufjs": "~6.11.3", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/stream": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", + "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", + "dependencies": { + "xstream": "^11.14.0" } }, - "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@types/node": { - "version": "13.13.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", - "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" + "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/utils": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.9.tgz", + "integrity": "sha512-5NJ2dSJlT0wtwdcTvprYNtucSk/+aQANEfobFGOBt6n5JT3VlCH5We3a29YC4Z176vy7RsfVf0j2xJabDmp2iw==" }, "node_modules/@cosmjs/cosmwasm-stargate/node_modules/cosmjs-types": { "version": "0.4.1", @@ -1813,69 +1897,19 @@ "protobufjs": "~6.11.2" } }, - "node_modules/@cosmjs/cosmwasm-stargate/node_modules/cosmjs-types/node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, "node_modules/@cosmjs/cosmwasm-stargate/node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, - "node_modules/@cosmjs/cosmwasm-stargate/node_modules/protobufjs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz", - "integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": "^13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, "node_modules/@cosmjs/crypto": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.4.tgz", - "integrity": "sha512-JRxNLlED3DDh9d04A0RcRw3mYkoobN7q7wafUFy3vI1TjoyWx33v0gqqaYE6/hoo9ghUrJSVOfzVihl8fZajJA==", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.7.tgz", + "integrity": "sha512-Fuq90nnqXQb6VvUadGtPy1X6arXY+yhqB95VzN8GM/ZdBLeigu6a3XjOvFki4lKO94QdLn2e9huD8dm4tDQDsA==", "dependencies": { - "@cosmjs/encoding": "0.28.4", - "@cosmjs/math": "0.28.4", - "@cosmjs/utils": "0.28.4", + "@cosmjs/encoding": "0.28.7", + "@cosmjs/math": "0.28.7", + "@cosmjs/utils": "0.28.7", "@noble/hashes": "^1", "bn.js": "^5.2.0", "elliptic": "^6.5.3", @@ -1883,9 +1917,9 @@ } }, "node_modules/@cosmjs/encoding": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.4.tgz", - "integrity": "sha512-N6Qnjs4dd8KwjW5m9t3L+rWYYGW2wyS+iLtJJ9DD8DiTTxpW9h7/AmUVO/dsRe5H2tV8/DzH/B9pFfpsgro22A==", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.7.tgz", + "integrity": "sha512-m2OuRhxux0YacvtjTocEOHyjnKO/KKGjqXlAY5HXGJpyntr+PIlJFdoS9tHax+1rNRrblZXwYIT+UqOs+kSk5Q==", "dependencies": { "base64-js": "^1.3.0", "bech32": "^1.1.4", @@ -1893,42 +1927,44 @@ } }, "node_modules/@cosmjs/json-rpc": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.4.tgz", - "integrity": "sha512-An8ZQi9OKbnS8ew/MyHhF90zQpXBF8RTj2wdvIH+Hr8yA6QjynY8hxRpUwYUt3Skc5NeUnTZNuWCzlluHnoxVg==", + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.9.tgz", + "integrity": "sha512-+Fs0dzRg8tdwnXd6ulN37bmGwOnD5wPiMdQLby5LOZk417Ay5lOFpmIOqnoeti+u9jHBbqnCDNteZ8aFRyarMg==", + "dependencies": { + "@cosmjs/stream": "0.28.9", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/json-rpc/node_modules/@cosmjs/stream": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", + "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", "dependencies": { - "@cosmjs/stream": "0.28.4", "xstream": "^11.14.0" } }, "node_modules/@cosmjs/math": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.4.tgz", - "integrity": "sha512-wsWjbxFXvk46Dsx8jQ5vsBZOIQuiUIyaaZbUvxsgIhAMpuuBnV5O/drK87+B+4cL+umTelFqTbWnkqueVCIFxQ==", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.7.tgz", + "integrity": "sha512-wSXIOOGVzgtRFsGwScpvQ6C6DUx97K7Ez3KyvRALNzspEnIoKPDgTXf438zhZnb+0+ERAwgdkb/6zWaDoVBfUA==", "dependencies": { "bn.js": "^5.2.0" } }, "node_modules/@cosmjs/proto-signing": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.4.tgz", - "integrity": "sha512-4vgCLK9gOsdWzD78V5XbAsupSSyntPEzokWYhgRQNwgVTcKX1kg0eKZqUvF5ua5iL9x6MevfH/sgwPyiYleMBw==", - "dependencies": { - "@cosmjs/amino": "0.28.4", - "@cosmjs/crypto": "0.28.4", - "@cosmjs/encoding": "0.28.4", - "@cosmjs/math": "0.28.4", - "@cosmjs/utils": "0.28.4", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.7.tgz", + "integrity": "sha512-cOwDQVv95KnpHWlkrtxZ+2Q+z2Qp8Co//g+bwNc+YZ403iGHXSK5PRe3YsPa4f3elKJkJpHVi5oyzp4DB8p7mA==", + "dependencies": { + "@cosmjs/amino": "0.28.7", + "@cosmjs/crypto": "0.28.7", + "@cosmjs/encoding": "0.28.7", + "@cosmjs/math": "0.28.7", + "@cosmjs/utils": "0.28.7", "cosmjs-types": "^0.4.0", - "long": "^4.0.0", - "protobufjs": "~6.10.2" + "long": "^4.0.0" } }, - "node_modules/@cosmjs/proto-signing/node_modules/@types/node": { - "version": "13.13.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", - "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" - }, "node_modules/@cosmjs/proto-signing/node_modules/cosmjs-types": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", @@ -1938,95 +1974,85 @@ "protobufjs": "~6.11.2" } }, - "node_modules/@cosmjs/proto-signing/node_modules/cosmjs-types/node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, "node_modules/@cosmjs/proto-signing/node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, - "node_modules/@cosmjs/proto-signing/node_modules/protobufjs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz", - "integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": "^13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, "node_modules/@cosmjs/socket": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.4.tgz", - "integrity": "sha512-jAEL3Ri+s8XuBM3mqgO4yvmeQu+R+704V37lGROC1B6kAbGxWRyOWrMdOOiFJzCZ35sSMB7L+xKjpE8ug0vJjg==", + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.9.tgz", + "integrity": "sha512-RdYAWoFf5TyhQKGPJPoCvYpnpPc2fSS5yrRjv7AnF7FB38N/y+PRZ0sKO94MkjP4679txLr1PVNnIZgykR0afQ==", "dependencies": { - "@cosmjs/stream": "0.28.4", + "@cosmjs/stream": "0.28.9", "isomorphic-ws": "^4.0.1", "ws": "^7", "xstream": "^11.14.0" } }, + "node_modules/@cosmjs/socket/node_modules/@cosmjs/stream": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", + "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", + "dependencies": { + "xstream": "^11.14.0" + } + }, "node_modules/@cosmjs/stargate": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.4.tgz", - "integrity": "sha512-tdwudilP5iLNwDm4TOMBjWuL5YehLPqGlC5/7hjJM/kVHyzLFo4Lzt0dVEwr5YegH+RsRXH/VtFLQz+NYlCobw==", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.7.tgz", + "integrity": "sha512-3nY7czFUaY74gacbQAZuNji1qJWWQjv2C1cxYNqe7qAZAvCiz6A9adJVUmCJRL4peeG7tKiOny1J5IFbsgRu0g==", "dependencies": { "@confio/ics23": "^0.6.8", - "@cosmjs/amino": "0.28.4", - "@cosmjs/encoding": "0.28.4", - "@cosmjs/math": "0.28.4", - "@cosmjs/proto-signing": "0.28.4", - "@cosmjs/stream": "0.28.4", - "@cosmjs/tendermint-rpc": "0.28.4", - "@cosmjs/utils": "0.28.4", + "@cosmjs/amino": "0.28.7", + "@cosmjs/encoding": "0.28.7", + "@cosmjs/math": "0.28.7", + "@cosmjs/proto-signing": "0.28.7", + "@cosmjs/stream": "0.28.7", + "@cosmjs/tendermint-rpc": "0.28.7", + "@cosmjs/utils": "0.28.7", "cosmjs-types": "^0.4.0", "long": "^4.0.0", - "protobufjs": "~6.10.2", + "protobufjs": "~6.11.3", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/stargate/node_modules/@cosmjs/json-rpc": { + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.7.tgz", + "integrity": "sha512-2qgRL/9ih/ZYU/8LmtDQopaCKJBKqsuoSXfb2XO3yv6EkE28yiA8YAwLW5IrXjl1tfSiolHaBWarEASS/8Q5xA==", + "dependencies": { + "@cosmjs/stream": "0.28.7", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/stargate/node_modules/@cosmjs/socket": { + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.7.tgz", + "integrity": "sha512-+sCR5AzjjsKlA0K7m8YdxldMvgJa3Tqnx0AAyQXlNw2VXmW1zu9DkKZWW475XFhwO9UYfrdIsOe1vbhnUjQb5g==", + "dependencies": { + "@cosmjs/stream": "0.28.7", + "isomorphic-ws": "^4.0.1", + "ws": "^7", "xstream": "^11.14.0" } }, - "node_modules/@cosmjs/stargate/node_modules/@types/node": { - "version": "13.13.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", - "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" + "node_modules/@cosmjs/stargate/node_modules/@cosmjs/tendermint-rpc": { + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.7.tgz", + "integrity": "sha512-xhIVJL3ol+fZxywP76Ik9pHqCvBdU/BGAw6p48mhla3L3xNcFN2Nf+EnJWcIZPqZl8bHm5QHzPk9VqVFVYCMCw==", + "dependencies": { + "@cosmjs/crypto": "0.28.7", + "@cosmjs/encoding": "0.28.7", + "@cosmjs/json-rpc": "0.28.7", + "@cosmjs/math": "0.28.7", + "@cosmjs/socket": "0.28.7", + "@cosmjs/stream": "0.28.7", + "@cosmjs/utils": "0.28.7", + "axios": "^0.21.2", + "readonly-date": "^1.0.0", + "xstream": "^11.14.0" + } }, "node_modules/@cosmjs/stargate/node_modules/cosmjs-types": { "version": "0.4.1", @@ -2067,60 +2093,80 @@ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, - "node_modules/@cosmjs/stargate/node_modules/protobufjs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz", - "integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": "^13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, "node_modules/@cosmjs/stream": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.4.tgz", - "integrity": "sha512-BDwDdFOrOgRx/Wm5nknb9YCV9HHIUcsOxykTDZqdArCUsn4QJBq79QIjp919G05Z8UemkoHwiUCUNB2BfoKmFw==", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.7.tgz", + "integrity": "sha512-zTMZadlpmxAMtKUHX/dnYs1STung392+P50WgSUAjllG7CZYl7SAY03AKgc+aoF1ohYQcgH9H7VSgabEgLPhmg==", "dependencies": { "xstream": "^11.14.0" } }, "node_modules/@cosmjs/tendermint-rpc": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.4.tgz", - "integrity": "sha512-iz6p4UW2QUZNh55WeJy9wHbMdqM8COo0AJdrGU4Ikb/xU0/H6b0dFPoEK+i6ngR0cSizh+hpTMzh3AA7ySUKlA==", - "dependencies": { - "@cosmjs/crypto": "0.28.4", - "@cosmjs/encoding": "0.28.4", - "@cosmjs/json-rpc": "0.28.4", - "@cosmjs/math": "0.28.4", - "@cosmjs/socket": "0.28.4", - "@cosmjs/stream": "0.28.4", - "@cosmjs/utils": "0.28.4", + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.9.tgz", + "integrity": "sha512-a8PCFWG32wyQE6R9XA3dZpbwzvAQCOpcF1m9AsShgVYXRKm+AihbFwu7q75jaxf2XaRnTnrjpCCnJKVFeMnFEQ==", + "dependencies": { + "@cosmjs/crypto": "0.28.9", + "@cosmjs/encoding": "0.28.9", + "@cosmjs/json-rpc": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/socket": "0.28.9", + "@cosmjs/stream": "0.28.9", + "@cosmjs/utils": "0.28.9", "axios": "^0.21.2", "readonly-date": "^1.0.0", "xstream": "^11.14.0" } }, + "node_modules/@cosmjs/tendermint-rpc/node_modules/@cosmjs/crypto": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.9.tgz", + "integrity": "sha512-2U8cuH7ofRiYni3EPAUsPuqgHN0VM+HdHiY1Ftl2rqRRnZArZNJsbf4t21jVx7rlc+qP32Y1v7PvQyPALHvuyw==", + "dependencies": { + "@cosmjs/encoding": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/utils": "0.28.9", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "elliptic": "^6.5.3", + "libsodium-wrappers": "^0.7.6" + } + }, + "node_modules/@cosmjs/tendermint-rpc/node_modules/@cosmjs/encoding": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.9.tgz", + "integrity": "sha512-AE+uL5LC2f9ZE8ehFPgb7yNMuGr4wx/cbH25gglRwl9utdND2lPVeYmbEF2MRJwB69hXaiPHblCDIz3KXZV9pQ==", + "dependencies": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "node_modules/@cosmjs/tendermint-rpc/node_modules/@cosmjs/math": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.9.tgz", + "integrity": "sha512-CY4k3GMQqXL3M+dGUptgCL6mtkcfvhKhmsrzejSZB8yVOIdKA40Wu6buRMMh2+t3SkWpvjVQt8UiJF7C4C4NXQ==", + "dependencies": { + "bn.js": "^5.2.0" + } + }, + "node_modules/@cosmjs/tendermint-rpc/node_modules/@cosmjs/stream": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", + "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", + "dependencies": { + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/tendermint-rpc/node_modules/@cosmjs/utils": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.9.tgz", + "integrity": "sha512-5NJ2dSJlT0wtwdcTvprYNtucSk/+aQANEfobFGOBt6n5JT3VlCH5We3a29YC4Z176vy7RsfVf0j2xJabDmp2iw==" + }, "node_modules/@cosmjs/utils": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.4.tgz", - "integrity": "sha512-lb3TU6833arPoPZF8HTeG9V418CpurvqH5Aa/ls0I0wYdPDEMO6622+PQNQhQ8Vw8Az2MXoSyc8jsqrgawT84Q==" + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.7.tgz", + "integrity": "sha512-0ya5mRaDY956n87dKjx6wfqgH/uEfrZyMswIhcEPqQPegwp4FDd6cwJtSdv/d5S7fgvoEs2UABvUFNzCT5C0hg==" }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", @@ -2319,39 +2365,6 @@ } } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/core/node_modules/react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - }, "node_modules/@jest/environment": { "version": "28.1.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.0.tgz", @@ -2392,15 +2405,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/expect-utils/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, "node_modules/@jest/fake-timers": { "version": "28.1.0", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.0.tgz", @@ -2662,30 +2666,31 @@ } }, "node_modules/@osmonauts/helpers": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@osmonauts/helpers/-/helpers-0.3.4.tgz", - "integrity": "sha512-c20B25IhqKBoEDSE+VysG/R19kNFq+RgD2p838GLo9bOOH3tT7UOuUYBmqat9qGHFaNpFkaYZ+3yqtMTh9zxzg==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@osmonauts/helpers/-/helpers-0.3.5.tgz", + "integrity": "sha512-NW8S0aAyOd2GLl6STyDcV1JbIKy2yJryj19zC+P647LYD/1eV4GEzgBXQ3G691ia+dZNQEQFpcfu+SFqIL5Q7A==", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.18.3", "long": "^5.2.0", - "protobufjs": "^6.11.2" + "protobufjs": "^6.11.3" } }, "node_modules/@osmonauts/lcd": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@osmonauts/lcd/-/lcd-0.3.4.tgz", - "integrity": "sha512-VlPN+ip2PgFuF3lVcCwoFSZ5ZTd8FdNl+LKv0dmr14OK6wkuAOzuAK6XqPOe2QbZ6Wii6arEqj/e/bLKAQcvWw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@osmonauts/lcd/-/lcd-0.3.5.tgz", + "integrity": "sha512-VUIwDp90ZD0GQGQ3jbttAfZCSKUU1dJbKFlMYqc0GZGIiWa9Ka6ZYUKQCpiwYXqjksLGWoFvTIX3YfLkQwvTqA==", "dependencies": { - "@babel/runtime": "^7.11.2", - "axios": "0.26.1" + "@babel/runtime": "^7.18.3", + "axios": "0.27.2" } }, "node_modules/@osmonauts/lcd/node_modules/axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", "dependencies": { - "follow-redirects": "^1.14.8" + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" } }, "node_modules/@protobufjs/aspromise": { @@ -2865,13 +2870,13 @@ } }, "node_modules/@types/jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.1.tgz", - "integrity": "sha512-fUy7YRpT+rHXto1YlL+J9rs0uLGyiqVt3ZOTQR+4ROc47yNl8WLdVLgUloBRhOxP1PZvguHl44T3H0wAWxahYQ==", + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.3.tgz", + "integrity": "sha512-Tsbjk8Y2hkBaY/gJsataeb4q9Mubw9EOz7+4RjPkzD5KjTvHHs7cpws22InaoXxAVAhF5HfFbzJjo6oKWqSZLw==", "dev": true, "dependencies": { - "jest-matcher-utils": "^27.0.0", - "pretty-format": "^27.0.0" + "jest-matcher-utils": "^28.0.0", + "pretty-format": "^28.0.0" } }, "node_modules/@types/json-schema": { @@ -3244,6 +3249,11 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/axios": { "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", @@ -3626,6 +3636,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3751,6 +3772,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3770,12 +3799,12 @@ } }, "node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", "dev": true, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/dir-glob": { @@ -4220,92 +4249,11 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/expect/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/expect/node_modules/diff-sequences": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.0.2.tgz", - "integrity": "sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/expect/node_modules/jest-diff": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.0.tgz", - "integrity": "sha512-8eFd3U3OkIKRtlasXfiAQfbovgFgRDb0Ngcs2E+FMeBZ4rUezqIaGjuyggJBp+llosQXNEWofk/Sz4Hr5gMUhA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.0.2", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/expect/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/expect/node_modules/jest-matcher-utils": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.0.tgz", - "integrity": "sha512-onnax0n2uTLRQFKAjC7TuaxibrPSvZgKTcSCnNUz/tOjJ9UhxNm7ZmPpoQavmTDUjXvUQ8KesWk2/VdrxIFzTQ==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.0", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/expect/node_modules/pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/expect/node_modules/react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true }, "node_modules/fast-glob": { "version": "3.2.11", @@ -4440,6 +4388,19 @@ } } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4983,87 +4944,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/diff-sequences": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.0.2.tgz", - "integrity": "sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-diff": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.0.tgz", - "integrity": "sha512-8eFd3U3OkIKRtlasXfiAQfbovgFgRDb0Ngcs2E+FMeBZ4rUezqIaGjuyggJBp+llosQXNEWofk/Sz4Hr5gMUhA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.0.2", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-matcher-utils": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.0.tgz", - "integrity": "sha512-onnax0n2uTLRQFKAjC7TuaxibrPSvZgKTcSCnNUz/tOjJ9UhxNm7ZmPpoQavmTDUjXvUQ8KesWk2/VdrxIFzTQ==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.0", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - }, "node_modules/jest-cli": { "version": "28.1.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.0.tgz", @@ -5143,61 +5023,19 @@ } } }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-config/node_modules/pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-config/node_modules/react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - }, "node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.1.tgz", + "integrity": "sha512-/MUUxeR2fHbqHoMMiffe/Afm+U8U4olFRJ0hiVG2lZatPJcnGxx292ustVu7bULhjV65IYMxRdploAKLbcrsyg==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/jest-docblock": { @@ -5228,48 +5066,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-each/node_modules/pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-each/node_modules/react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - }, "node_modules/jest-environment-node": { "version": "28.1.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.0.tgz", @@ -5288,12 +5084,12 @@ } }, "node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", "dev": true, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/jest-haste-map": { @@ -5334,61 +5130,19 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-leak-detector/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-leak-detector/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - }, "node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.1.tgz", + "integrity": "sha512-NPJPRWrbmR2nAJ+1nmnfcKKzSwgfaciCCrYZzVnNoxVoyusYWIjkBMNvu0RHJe7dNj4hH3uZOPZsQA+xAYWqsw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "jest-diff": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/jest-message-util": { @@ -5411,39 +5165,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - }, "node_modules/jest-mock": { "version": "28.1.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.0.tgz", @@ -5615,87 +5336,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/diff-sequences": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.0.2.tgz", - "integrity": "sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/jest-diff": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.0.tgz", - "integrity": "sha512-8eFd3U3OkIKRtlasXfiAQfbovgFgRDb0Ngcs2E+FMeBZ4rUezqIaGjuyggJBp+llosQXNEWofk/Sz4Hr5gMUhA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.0.2", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/jest-matcher-utils": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.0.tgz", - "integrity": "sha512-onnax0n2uTLRQFKAjC7TuaxibrPSvZgKTcSCnNUz/tOjJ9UhxNm7ZmPpoQavmTDUjXvUQ8KesWk2/VdrxIFzTQ==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.0", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - }, "node_modules/jest-snapshot/node_modules/semver": { "version": "7.3.7", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", @@ -5745,18 +5385,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -5769,36 +5397,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-validate/node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-validate/node_modules/pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-validate/node_modules/react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - }, "node_modules/jest-watcher": { "version": "28.1.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.0.tgz", @@ -6044,6 +5642,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -6171,19 +5788,19 @@ } }, "node_modules/osmojs": { - "version": "0.4.46", - "resolved": "https://registry.npmjs.org/osmojs/-/osmojs-0.4.46.tgz", - "integrity": "sha512-d965zJncEtqV1dOVDBS5+rmDlYvVWJdzkeRgN3+WBkThGOV0IxBQ/W6cSP4IZvmTr2rXlI1erKG6FYIjxMlZfA==", - "dependencies": { - "@babel/runtime": "^7.11.2", - "@cosmjs/amino": "0.28.4", - "@cosmjs/proto-signing": "0.28.4", - "@cosmjs/stargate": "0.28.4", - "@cosmjs/tendermint-rpc": "^0.28.4", - "@osmonauts/helpers": "^0.3.4", - "@osmonauts/lcd": "^0.3.4", + "version": "0.4.53", + "resolved": "https://registry.npmjs.org/osmojs/-/osmojs-0.4.53.tgz", + "integrity": "sha512-5ml35Nx6gpVQLho+XrbzZptA1025S5XloRHLluvlNtIfVwILYp1E509WakGnjE9r5nxj/3eG9ThM5Yf8MfmWmg==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@cosmjs/amino": "0.28.7", + "@cosmjs/proto-signing": "0.28.7", + "@cosmjs/stargate": "0.28.7", + "@cosmjs/tendermint-rpc": "^0.28.7", + "@osmonauts/helpers": "^0.3.5", + "@osmonauts/lcd": "^0.3.5", "long": "^5.2.0", - "protobufjs": "^6.11.2" + "protobufjs": "^6.11.3" } }, "node_modules/p-limit": { @@ -6339,9 +5956,9 @@ } }, "node_modules/prettier": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", - "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -6354,17 +5971,18 @@ } }, "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.1.tgz", + "integrity": "sha512-wwJbVTGFHeucr5Jw2bQ9P+VYHyLdAqedFLEkdQUVaBF/eiidDwH5OpilINq4mEfhbCjLnirt6HTTDhv1HaTIQw==", "dev": true, "dependencies": { + "@jest/schemas": "^28.0.2", "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -6452,9 +6070,9 @@ ] }, "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, "node_modules/readonly-date": { @@ -7026,11 +6644,10 @@ } }, "node_modules/typescript": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz", - "integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8490,39 +8107,122 @@ } }, "@cosmjs/amino": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.4.tgz", - "integrity": "sha512-b8y5gFC0eGrH0IoYSNtDmTdsTgeQ1KFZ5YVOeIiKmzF91MeiciYO/MNqc027kctacZ+UbnVWGEUGyRBPi9ta/g==", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.7.tgz", + "integrity": "sha512-NTCUS3+u9bxwW8moC0N1RS4Gk/fs3JPHTKcp7ksH39V4+7uOKM2rGsyFAekiHNxYngnQ+1hU5x2tddS25soK6Q==", "requires": { - "@cosmjs/crypto": "0.28.4", - "@cosmjs/encoding": "0.28.4", - "@cosmjs/math": "0.28.4", - "@cosmjs/utils": "0.28.4" + "@cosmjs/crypto": "0.28.7", + "@cosmjs/encoding": "0.28.7", + "@cosmjs/math": "0.28.7", + "@cosmjs/utils": "0.28.7" } }, "@cosmjs/cosmwasm-stargate": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.28.4.tgz", - "integrity": "sha512-dkTwTD+j2mjk7+l3pQQ3io2D0U7NIA4LXzkKtfBN87PGlj2G+VJFzcXk1T4DYmvrXjsQOi1kYeQRGWFA0XdvnQ==", - "requires": { - "@cosmjs/amino": "0.28.4", - "@cosmjs/crypto": "0.28.4", - "@cosmjs/encoding": "0.28.4", - "@cosmjs/math": "0.28.4", - "@cosmjs/proto-signing": "0.28.4", - "@cosmjs/stargate": "0.28.4", - "@cosmjs/tendermint-rpc": "0.28.4", - "@cosmjs/utils": "0.28.4", + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.28.9.tgz", + "integrity": "sha512-Icgmiq/hM72mWaCR3FBKb6VZSdeETfTSMZ8E26wBXsvmU24pFqIIqRmJ9y7jSyqRKnGY/0skYNitCob7ylqPSg==", + "requires": { + "@cosmjs/amino": "0.28.9", + "@cosmjs/crypto": "0.28.9", + "@cosmjs/encoding": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/proto-signing": "0.28.9", + "@cosmjs/stargate": "0.28.9", + "@cosmjs/tendermint-rpc": "0.28.9", + "@cosmjs/utils": "0.28.9", "cosmjs-types": "^0.4.0", "long": "^4.0.0", - "pako": "^2.0.2", - "protobufjs": "~6.10.2" + "pako": "^2.0.2" }, "dependencies": { - "@types/node": { - "version": "13.13.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", - "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" + "@cosmjs/amino": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.9.tgz", + "integrity": "sha512-8yaWIlU0W5nF7xyR1v81Wr0+Eo5xJ1bDCkTAsb1JV3xtXvfsl2jF1l4aV64vi+WWEghE+9embhZm7zxBljdunQ==", + "requires": { + "@cosmjs/crypto": "0.28.9", + "@cosmjs/encoding": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/utils": "0.28.9" + } + }, + "@cosmjs/crypto": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.9.tgz", + "integrity": "sha512-2U8cuH7ofRiYni3EPAUsPuqgHN0VM+HdHiY1Ftl2rqRRnZArZNJsbf4t21jVx7rlc+qP32Y1v7PvQyPALHvuyw==", + "requires": { + "@cosmjs/encoding": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/utils": "0.28.9", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "elliptic": "^6.5.3", + "libsodium-wrappers": "^0.7.6" + } + }, + "@cosmjs/encoding": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.9.tgz", + "integrity": "sha512-AE+uL5LC2f9ZE8ehFPgb7yNMuGr4wx/cbH25gglRwl9utdND2lPVeYmbEF2MRJwB69hXaiPHblCDIz3KXZV9pQ==", + "requires": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "@cosmjs/math": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.9.tgz", + "integrity": "sha512-CY4k3GMQqXL3M+dGUptgCL6mtkcfvhKhmsrzejSZB8yVOIdKA40Wu6buRMMh2+t3SkWpvjVQt8UiJF7C4C4NXQ==", + "requires": { + "bn.js": "^5.2.0" + } + }, + "@cosmjs/proto-signing": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.9.tgz", + "integrity": "sha512-8MnCvIa2s9myhXEMwsjKOh9/zGp1850QiW9XUy6YtNaQ9wgc58/WMq7HtfkGPqIZnhOeR7KXIyQ0/KHbmjhtnA==", + "requires": { + "@cosmjs/amino": "0.28.9", + "@cosmjs/crypto": "0.28.9", + "@cosmjs/encoding": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/utils": "0.28.9", + "cosmjs-types": "^0.4.0", + "long": "^4.0.0" + } + }, + "@cosmjs/stargate": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.9.tgz", + "integrity": "sha512-1Jh/+qsyzM+7+ek/peQa3xumgU99SvAI5ecDZNS6+qOaWgGdGSW8n40sEfv6viRVLDppfoYo6/9QfR5h3PoKgQ==", + "requires": { + "@confio/ics23": "^0.6.8", + "@cosmjs/amino": "0.28.9", + "@cosmjs/encoding": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/proto-signing": "0.28.9", + "@cosmjs/stream": "0.28.9", + "@cosmjs/tendermint-rpc": "0.28.9", + "@cosmjs/utils": "0.28.9", + "cosmjs-types": "^0.4.0", + "long": "^4.0.0", + "protobufjs": "~6.11.3", + "xstream": "^11.14.0" + } + }, + "@cosmjs/stream": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", + "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", + "requires": { + "xstream": "^11.14.0" + } + }, + "@cosmjs/utils": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.9.tgz", + "integrity": "sha512-5NJ2dSJlT0wtwdcTvprYNtucSk/+aQANEfobFGOBt6n5JT3VlCH5We3a29YC4Z176vy7RsfVf0j2xJabDmp2iw==" }, "cosmjs-types": { "version": "0.4.1", @@ -8531,65 +8231,23 @@ "requires": { "long": "^4.0.0", "protobufjs": "~6.11.2" - }, - "dependencies": { - "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - } - } } }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "protobufjs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz", - "integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": "^13.7.0", - "long": "^4.0.0" - } } } }, "@cosmjs/crypto": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.4.tgz", - "integrity": "sha512-JRxNLlED3DDh9d04A0RcRw3mYkoobN7q7wafUFy3vI1TjoyWx33v0gqqaYE6/hoo9ghUrJSVOfzVihl8fZajJA==", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.7.tgz", + "integrity": "sha512-Fuq90nnqXQb6VvUadGtPy1X6arXY+yhqB95VzN8GM/ZdBLeigu6a3XjOvFki4lKO94QdLn2e9huD8dm4tDQDsA==", "requires": { - "@cosmjs/encoding": "0.28.4", - "@cosmjs/math": "0.28.4", - "@cosmjs/utils": "0.28.4", + "@cosmjs/encoding": "0.28.7", + "@cosmjs/math": "0.28.7", + "@cosmjs/utils": "0.28.7", "@noble/hashes": "^1", "bn.js": "^5.2.0", "elliptic": "^6.5.3", @@ -8597,9 +8255,9 @@ } }, "@cosmjs/encoding": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.4.tgz", - "integrity": "sha512-N6Qnjs4dd8KwjW5m9t3L+rWYYGW2wyS+iLtJJ9DD8DiTTxpW9h7/AmUVO/dsRe5H2tV8/DzH/B9pFfpsgro22A==", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.7.tgz", + "integrity": "sha512-m2OuRhxux0YacvtjTocEOHyjnKO/KKGjqXlAY5HXGJpyntr+PIlJFdoS9tHax+1rNRrblZXwYIT+UqOs+kSk5Q==", "requires": { "base64-js": "^1.3.0", "bech32": "^1.1.4", @@ -8607,42 +8265,46 @@ } }, "@cosmjs/json-rpc": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.4.tgz", - "integrity": "sha512-An8ZQi9OKbnS8ew/MyHhF90zQpXBF8RTj2wdvIH+Hr8yA6QjynY8hxRpUwYUt3Skc5NeUnTZNuWCzlluHnoxVg==", + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.9.tgz", + "integrity": "sha512-+Fs0dzRg8tdwnXd6ulN37bmGwOnD5wPiMdQLby5LOZk417Ay5lOFpmIOqnoeti+u9jHBbqnCDNteZ8aFRyarMg==", "requires": { - "@cosmjs/stream": "0.28.4", + "@cosmjs/stream": "0.28.9", "xstream": "^11.14.0" + }, + "dependencies": { + "@cosmjs/stream": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", + "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", + "requires": { + "xstream": "^11.14.0" + } + } } }, "@cosmjs/math": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.4.tgz", - "integrity": "sha512-wsWjbxFXvk46Dsx8jQ5vsBZOIQuiUIyaaZbUvxsgIhAMpuuBnV5O/drK87+B+4cL+umTelFqTbWnkqueVCIFxQ==", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.7.tgz", + "integrity": "sha512-wSXIOOGVzgtRFsGwScpvQ6C6DUx97K7Ez3KyvRALNzspEnIoKPDgTXf438zhZnb+0+ERAwgdkb/6zWaDoVBfUA==", "requires": { "bn.js": "^5.2.0" } }, "@cosmjs/proto-signing": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.4.tgz", - "integrity": "sha512-4vgCLK9gOsdWzD78V5XbAsupSSyntPEzokWYhgRQNwgVTcKX1kg0eKZqUvF5ua5iL9x6MevfH/sgwPyiYleMBw==", - "requires": { - "@cosmjs/amino": "0.28.4", - "@cosmjs/crypto": "0.28.4", - "@cosmjs/encoding": "0.28.4", - "@cosmjs/math": "0.28.4", - "@cosmjs/utils": "0.28.4", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.7.tgz", + "integrity": "sha512-cOwDQVv95KnpHWlkrtxZ+2Q+z2Qp8Co//g+bwNc+YZ403iGHXSK5PRe3YsPa4f3elKJkJpHVi5oyzp4DB8p7mA==", + "requires": { + "@cosmjs/amino": "0.28.7", + "@cosmjs/crypto": "0.28.7", + "@cosmjs/encoding": "0.28.7", + "@cosmjs/math": "0.28.7", + "@cosmjs/utils": "0.28.7", "cosmjs-types": "^0.4.0", - "long": "^4.0.0", - "protobufjs": "~6.10.2" + "long": "^4.0.0" }, "dependencies": { - "@types/node": { - "version": "13.13.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", - "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" - }, "cosmjs-types": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", @@ -8650,91 +8312,91 @@ "requires": { "long": "^4.0.0", "protobufjs": "~6.11.2" - }, - "dependencies": { - "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - } - } } }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "protobufjs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz", - "integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": "^13.7.0", - "long": "^4.0.0" - } } } }, "@cosmjs/socket": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.4.tgz", - "integrity": "sha512-jAEL3Ri+s8XuBM3mqgO4yvmeQu+R+704V37lGROC1B6kAbGxWRyOWrMdOOiFJzCZ35sSMB7L+xKjpE8ug0vJjg==", + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.9.tgz", + "integrity": "sha512-RdYAWoFf5TyhQKGPJPoCvYpnpPc2fSS5yrRjv7AnF7FB38N/y+PRZ0sKO94MkjP4679txLr1PVNnIZgykR0afQ==", "requires": { - "@cosmjs/stream": "0.28.4", + "@cosmjs/stream": "0.28.9", "isomorphic-ws": "^4.0.1", "ws": "^7", "xstream": "^11.14.0" + }, + "dependencies": { + "@cosmjs/stream": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", + "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", + "requires": { + "xstream": "^11.14.0" + } + } } }, "@cosmjs/stargate": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.4.tgz", - "integrity": "sha512-tdwudilP5iLNwDm4TOMBjWuL5YehLPqGlC5/7hjJM/kVHyzLFo4Lzt0dVEwr5YegH+RsRXH/VtFLQz+NYlCobw==", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.7.tgz", + "integrity": "sha512-3nY7czFUaY74gacbQAZuNji1qJWWQjv2C1cxYNqe7qAZAvCiz6A9adJVUmCJRL4peeG7tKiOny1J5IFbsgRu0g==", "requires": { "@confio/ics23": "^0.6.8", - "@cosmjs/amino": "0.28.4", - "@cosmjs/encoding": "0.28.4", - "@cosmjs/math": "0.28.4", - "@cosmjs/proto-signing": "0.28.4", - "@cosmjs/stream": "0.28.4", - "@cosmjs/tendermint-rpc": "0.28.4", - "@cosmjs/utils": "0.28.4", + "@cosmjs/amino": "0.28.7", + "@cosmjs/encoding": "0.28.7", + "@cosmjs/math": "0.28.7", + "@cosmjs/proto-signing": "0.28.7", + "@cosmjs/stream": "0.28.7", + "@cosmjs/tendermint-rpc": "0.28.7", + "@cosmjs/utils": "0.28.7", "cosmjs-types": "^0.4.0", "long": "^4.0.0", - "protobufjs": "~6.10.2", + "protobufjs": "~6.11.3", "xstream": "^11.14.0" }, "dependencies": { - "@types/node": { - "version": "13.13.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", - "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" + "@cosmjs/json-rpc": { + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.7.tgz", + "integrity": "sha512-2qgRL/9ih/ZYU/8LmtDQopaCKJBKqsuoSXfb2XO3yv6EkE28yiA8YAwLW5IrXjl1tfSiolHaBWarEASS/8Q5xA==", + "requires": { + "@cosmjs/stream": "0.28.7", + "xstream": "^11.14.0" + } + }, + "@cosmjs/socket": { + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.7.tgz", + "integrity": "sha512-+sCR5AzjjsKlA0K7m8YdxldMvgJa3Tqnx0AAyQXlNw2VXmW1zu9DkKZWW475XFhwO9UYfrdIsOe1vbhnUjQb5g==", + "requires": { + "@cosmjs/stream": "0.28.7", + "isomorphic-ws": "^4.0.1", + "ws": "^7", + "xstream": "^11.14.0" + } + }, + "@cosmjs/tendermint-rpc": { + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.7.tgz", + "integrity": "sha512-xhIVJL3ol+fZxywP76Ik9pHqCvBdU/BGAw6p48mhla3L3xNcFN2Nf+EnJWcIZPqZl8bHm5QHzPk9VqVFVYCMCw==", + "requires": { + "@cosmjs/crypto": "0.28.7", + "@cosmjs/encoding": "0.28.7", + "@cosmjs/json-rpc": "0.28.7", + "@cosmjs/math": "0.28.7", + "@cosmjs/socket": "0.28.7", + "@cosmjs/stream": "0.28.7", + "@cosmjs/utils": "0.28.7", + "axios": "^0.21.2", + "readonly-date": "^1.0.0", + "xstream": "^11.14.0" + } }, "cosmjs-types": { "version": "0.4.1", @@ -8771,58 +8433,85 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "protobufjs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz", - "integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": "^13.7.0", - "long": "^4.0.0" - } } } }, "@cosmjs/stream": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.4.tgz", - "integrity": "sha512-BDwDdFOrOgRx/Wm5nknb9YCV9HHIUcsOxykTDZqdArCUsn4QJBq79QIjp919G05Z8UemkoHwiUCUNB2BfoKmFw==", + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.7.tgz", + "integrity": "sha512-zTMZadlpmxAMtKUHX/dnYs1STung392+P50WgSUAjllG7CZYl7SAY03AKgc+aoF1ohYQcgH9H7VSgabEgLPhmg==", "requires": { "xstream": "^11.14.0" } }, "@cosmjs/tendermint-rpc": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.4.tgz", - "integrity": "sha512-iz6p4UW2QUZNh55WeJy9wHbMdqM8COo0AJdrGU4Ikb/xU0/H6b0dFPoEK+i6ngR0cSizh+hpTMzh3AA7ySUKlA==", - "requires": { - "@cosmjs/crypto": "0.28.4", - "@cosmjs/encoding": "0.28.4", - "@cosmjs/json-rpc": "0.28.4", - "@cosmjs/math": "0.28.4", - "@cosmjs/socket": "0.28.4", - "@cosmjs/stream": "0.28.4", - "@cosmjs/utils": "0.28.4", + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.9.tgz", + "integrity": "sha512-a8PCFWG32wyQE6R9XA3dZpbwzvAQCOpcF1m9AsShgVYXRKm+AihbFwu7q75jaxf2XaRnTnrjpCCnJKVFeMnFEQ==", + "requires": { + "@cosmjs/crypto": "0.28.9", + "@cosmjs/encoding": "0.28.9", + "@cosmjs/json-rpc": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/socket": "0.28.9", + "@cosmjs/stream": "0.28.9", + "@cosmjs/utils": "0.28.9", "axios": "^0.21.2", "readonly-date": "^1.0.0", "xstream": "^11.14.0" + }, + "dependencies": { + "@cosmjs/crypto": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.9.tgz", + "integrity": "sha512-2U8cuH7ofRiYni3EPAUsPuqgHN0VM+HdHiY1Ftl2rqRRnZArZNJsbf4t21jVx7rlc+qP32Y1v7PvQyPALHvuyw==", + "requires": { + "@cosmjs/encoding": "0.28.9", + "@cosmjs/math": "0.28.9", + "@cosmjs/utils": "0.28.9", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "elliptic": "^6.5.3", + "libsodium-wrappers": "^0.7.6" + } + }, + "@cosmjs/encoding": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.9.tgz", + "integrity": "sha512-AE+uL5LC2f9ZE8ehFPgb7yNMuGr4wx/cbH25gglRwl9utdND2lPVeYmbEF2MRJwB69hXaiPHblCDIz3KXZV9pQ==", + "requires": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "@cosmjs/math": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.9.tgz", + "integrity": "sha512-CY4k3GMQqXL3M+dGUptgCL6mtkcfvhKhmsrzejSZB8yVOIdKA40Wu6buRMMh2+t3SkWpvjVQt8UiJF7C4C4NXQ==", + "requires": { + "bn.js": "^5.2.0" + } + }, + "@cosmjs/stream": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", + "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", + "requires": { + "xstream": "^11.14.0" + } + }, + "@cosmjs/utils": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.9.tgz", + "integrity": "sha512-5NJ2dSJlT0wtwdcTvprYNtucSk/+aQANEfobFGOBt6n5JT3VlCH5We3a29YC4Z176vy7RsfVf0j2xJabDmp2iw==" + } } }, "@cosmjs/utils": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.4.tgz", - "integrity": "sha512-lb3TU6833arPoPZF8HTeG9V418CpurvqH5Aa/ls0I0wYdPDEMO6622+PQNQhQ8Vw8Az2MXoSyc8jsqrgawT84Q==" + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.7.tgz", + "integrity": "sha512-0ya5mRaDY956n87dKjx6wfqgH/uEfrZyMswIhcEPqQPegwp4FDd6cwJtSdv/d5S7fgvoEs2UABvUFNzCT5C0hg==" }, "@cspotcode/source-map-support": { "version": "0.8.1", @@ -8979,32 +8668,6 @@ "rimraf": "^3.0.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - }, - "react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - } } }, "@jest/environment": { @@ -9036,14 +8699,6 @@ "dev": true, "requires": { "jest-get-type": "^28.0.2" - }, - "dependencies": { - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - } } }, "@jest/fake-timers": { @@ -9254,30 +8909,31 @@ } }, "@osmonauts/helpers": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@osmonauts/helpers/-/helpers-0.3.4.tgz", - "integrity": "sha512-c20B25IhqKBoEDSE+VysG/R19kNFq+RgD2p838GLo9bOOH3tT7UOuUYBmqat9qGHFaNpFkaYZ+3yqtMTh9zxzg==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@osmonauts/helpers/-/helpers-0.3.5.tgz", + "integrity": "sha512-NW8S0aAyOd2GLl6STyDcV1JbIKy2yJryj19zC+P647LYD/1eV4GEzgBXQ3G691ia+dZNQEQFpcfu+SFqIL5Q7A==", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.18.3", "long": "^5.2.0", - "protobufjs": "^6.11.2" + "protobufjs": "^6.11.3" } }, "@osmonauts/lcd": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@osmonauts/lcd/-/lcd-0.3.4.tgz", - "integrity": "sha512-VlPN+ip2PgFuF3lVcCwoFSZ5ZTd8FdNl+LKv0dmr14OK6wkuAOzuAK6XqPOe2QbZ6Wii6arEqj/e/bLKAQcvWw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@osmonauts/lcd/-/lcd-0.3.5.tgz", + "integrity": "sha512-VUIwDp90ZD0GQGQ3jbttAfZCSKUU1dJbKFlMYqc0GZGIiWa9Ka6ZYUKQCpiwYXqjksLGWoFvTIX3YfLkQwvTqA==", "requires": { - "@babel/runtime": "^7.11.2", - "axios": "0.26.1" + "@babel/runtime": "^7.18.3", + "axios": "0.27.2" }, "dependencies": { "axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", "requires": { - "follow-redirects": "^1.14.8" + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" } } } @@ -9459,13 +9115,13 @@ } }, "@types/jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.1.tgz", - "integrity": "sha512-fUy7YRpT+rHXto1YlL+J9rs0uLGyiqVt3ZOTQR+4ROc47yNl8WLdVLgUloBRhOxP1PZvguHl44T3H0wAWxahYQ==", + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.3.tgz", + "integrity": "sha512-Tsbjk8Y2hkBaY/gJsataeb4q9Mubw9EOz7+4RjPkzD5KjTvHHs7cpws22InaoXxAVAhF5HfFbzJjo6oKWqSZLw==", "dev": true, "requires": { - "jest-matcher-utils": "^27.0.0", - "pretty-format": "^27.0.0" + "jest-matcher-utils": "^28.0.0", + "pretty-format": "^28.0.0" } }, "@types/json-schema": { @@ -9708,6 +9364,11 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "axios": { "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", @@ -9992,6 +9653,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -10094,6 +9763,11 @@ "object-keys": "^1.1.1" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -10107,9 +9781,9 @@ "dev": true }, "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", "dev": true }, "dir-glob": { @@ -10442,68 +10116,6 @@ "jest-matcher-utils": "^28.1.0", "jest-message-util": "^28.1.0", "jest-util": "^28.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "diff-sequences": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.0.2.tgz", - "integrity": "sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ==", - "dev": true - }, - "jest-diff": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.0.tgz", - "integrity": "sha512-8eFd3U3OkIKRtlasXfiAQfbovgFgRDb0Ngcs2E+FMeBZ4rUezqIaGjuyggJBp+llosQXNEWofk/Sz4Hr5gMUhA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^28.0.2", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "jest-matcher-utils": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.0.tgz", - "integrity": "sha512-onnax0n2uTLRQFKAjC7TuaxibrPSvZgKTcSCnNUz/tOjJ9UhxNm7ZmPpoQavmTDUjXvUQ8KesWk2/VdrxIFzTQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.0", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - } - }, - "pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - }, - "react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - } } }, "fast-deep-equal": { @@ -10615,6 +10227,16 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -11006,68 +10628,6 @@ "slash": "^3.0.0", "stack-utils": "^2.0.3", "throat": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "diff-sequences": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.0.2.tgz", - "integrity": "sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ==", - "dev": true - }, - "jest-diff": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.0.tgz", - "integrity": "sha512-8eFd3U3OkIKRtlasXfiAQfbovgFgRDb0Ngcs2E+FMeBZ4rUezqIaGjuyggJBp+llosQXNEWofk/Sz4Hr5gMUhA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^28.0.2", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "jest-matcher-utils": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.0.tgz", - "integrity": "sha512-onnax0n2uTLRQFKAjC7TuaxibrPSvZgKTcSCnNUz/tOjJ9UhxNm7ZmPpoQavmTDUjXvUQ8KesWk2/VdrxIFzTQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.0", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - } - }, - "pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - }, - "react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - } } }, "jest-cli": { @@ -11118,50 +10678,18 @@ "pretty-format": "^28.1.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - }, - "react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - } } }, "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.1.tgz", + "integrity": "sha512-/MUUxeR2fHbqHoMMiffe/Afm+U8U4olFRJ0hiVG2lZatPJcnGxx292ustVu7bULhjV65IYMxRdploAKLbcrsyg==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.1" } }, "jest-docblock": { @@ -11184,38 +10712,6 @@ "jest-get-type": "^28.0.2", "jest-util": "^28.1.0", "pretty-format": "^28.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - }, - "react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - } } }, "jest-environment-node": { @@ -11233,9 +10729,9 @@ } }, "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", "dev": true }, "jest-haste-map": { @@ -11266,50 +10762,18 @@ "requires": { "jest-get-type": "^28.0.2", "pretty-format": "^28.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - }, - "react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - } } }, "jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.1.tgz", + "integrity": "sha512-NPJPRWrbmR2nAJ+1nmnfcKKzSwgfaciCCrYZzVnNoxVoyusYWIjkBMNvu0RHJe7dNj4hH3uZOPZsQA+xAYWqsw==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "jest-diff": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.1" } }, "jest-message-util": { @@ -11327,32 +10791,6 @@ "pretty-format": "^28.1.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - }, - "react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - } } }, "jest-mock": { @@ -11495,66 +10933,6 @@ "semver": "^7.3.5" }, "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "diff-sequences": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.0.2.tgz", - "integrity": "sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ==", - "dev": true - }, - "jest-diff": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.0.tgz", - "integrity": "sha512-8eFd3U3OkIKRtlasXfiAQfbovgFgRDb0Ngcs2E+FMeBZ4rUezqIaGjuyggJBp+llosQXNEWofk/Sz4Hr5gMUhA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^28.0.2", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "jest-matcher-utils": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.0.tgz", - "integrity": "sha512-onnax0n2uTLRQFKAjC7TuaxibrPSvZgKTcSCnNUz/tOjJ9UhxNm7ZmPpoQavmTDUjXvUQ8KesWk2/VdrxIFzTQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.0", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - } - }, - "pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - }, - "react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true - }, "semver": { "version": "7.3.7", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", @@ -11594,41 +10972,11 @@ "pretty-format": "^28.1.0" }, "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "pretty-format": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.0.tgz", - "integrity": "sha512-79Z4wWOYCdvQkEoEuSlBhHJqWeZ8D8YRPiPctJFCtvuaClGpiwiQYSCUOE6IEKUbbFukKOTFIUAXE8N4EQTo1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - }, - "react-is": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", - "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", - "dev": true } } }, @@ -11828,6 +11176,19 @@ "picomatch": "^2.3.1" } }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -11928,19 +11289,19 @@ } }, "osmojs": { - "version": "0.4.46", - "resolved": "https://registry.npmjs.org/osmojs/-/osmojs-0.4.46.tgz", - "integrity": "sha512-d965zJncEtqV1dOVDBS5+rmDlYvVWJdzkeRgN3+WBkThGOV0IxBQ/W6cSP4IZvmTr2rXlI1erKG6FYIjxMlZfA==", - "requires": { - "@babel/runtime": "^7.11.2", - "@cosmjs/amino": "0.28.4", - "@cosmjs/proto-signing": "0.28.4", - "@cosmjs/stargate": "0.28.4", - "@cosmjs/tendermint-rpc": "^0.28.4", - "@osmonauts/helpers": "^0.3.4", - "@osmonauts/lcd": "^0.3.4", + "version": "0.4.53", + "resolved": "https://registry.npmjs.org/osmojs/-/osmojs-0.4.53.tgz", + "integrity": "sha512-5ml35Nx6gpVQLho+XrbzZptA1025S5XloRHLluvlNtIfVwILYp1E509WakGnjE9r5nxj/3eG9ThM5Yf8MfmWmg==", + "requires": { + "@babel/runtime": "^7.18.3", + "@cosmjs/amino": "0.28.7", + "@cosmjs/proto-signing": "0.28.7", + "@cosmjs/stargate": "0.28.7", + "@cosmjs/tendermint-rpc": "^0.28.7", + "@osmonauts/helpers": "^0.3.5", + "@osmonauts/lcd": "^0.3.5", "long": "^5.2.0", - "protobufjs": "^6.11.2" + "protobufjs": "^6.11.3" } }, "p-limit": { @@ -12051,20 +11412,21 @@ } }, "prettier": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", - "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", "dev": true }, "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.1.tgz", + "integrity": "sha512-wwJbVTGFHeucr5Jw2bQ9P+VYHyLdAqedFLEkdQUVaBF/eiidDwH5OpilINq4mEfhbCjLnirt6HTTDhv1HaTIQw==", "dev": true, "requires": { + "@jest/schemas": "^28.0.2", "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" }, "dependencies": { "ansi-styles": { @@ -12125,9 +11487,9 @@ "dev": true }, "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, "readonly-date": { @@ -12534,11 +11896,10 @@ "dev": true }, "typescript": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz", - "integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==", - "dev": true, - "peer": true + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", diff --git a/scripts/package.json b/scripts/package.json index 5927736fe..c65d40673 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -1,6 +1,6 @@ { - "name": "fields-scripts", - "version": "1.0.0", + "name": "credit-manager-scripts", + "version": "2.0.0", "main": "index.js", "scripts": { "test": "jest --testTimeout=20000 && npm run lint && npm run format-check", @@ -18,12 +18,13 @@ "devDependencies": { "@babel/preset-env": "^7.18.2", "@babel/preset-typescript": "^7.17.12", - "@types/jest": "^27.5.1", + "@types/jest": "^28.1.3", "@typescript-eslint/eslint-plugin": "^5.27.0", "@typescript-eslint/parser": "^5.27.0", "eslint": "^8.16.0", "jest": "^28.1.0", - "prettier": "2.6.2", + "prettier": "2.7.1", + "typescript": "^4.7.4", "ts-node": "^10.5.0" } -} \ No newline at end of file +} diff --git a/scripts/tests/app.test.ts b/scripts/tests/app.test.ts deleted file mode 100644 index 40baea5ed..000000000 --- a/scripts/tests/app.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { hello } from './app'; - -test('adds 1 + 2 to equal 3', () => { - expect(hello('George')).toBe('Hello George!'); -}); diff --git a/scripts/tests/app.ts b/scripts/tests/app.ts deleted file mode 100644 index 36ea554a3..000000000 --- a/scripts/tests/app.ts +++ /dev/null @@ -1,5 +0,0 @@ -const world = 'world'; - -export function hello(who: string = world): string { - return `Hello ${who}!`; -} diff --git a/scripts/tests/client.test.ts b/scripts/tests/client.test.ts deleted file mode 100644 index 856c36046..000000000 --- a/scripts/tests/client.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { assertIsDeliverTxSuccess } from '@cosmjs/stargate'; -import { Network, networks } from '../utils/config'; -import { getOsmosisClient } from '../utils/osmosis-client'; -import { testWallet1, testWallet2 } from '../utils/test-wallets'; - -describe('example client test', () => { - test('can get client and transfer tokens', async () => { - const client = await getOsmosisClient(testWallet1); - - const result = await client.sendTokens( - testWallet1.address, - testWallet2.address, - [ - { - denom: 'uosmo', - amount: '12345', - }, - ], - networks[Network.OSMOSIS].defaultSendFee, - ); - - assertIsDeliverTxSuccess(result); - - client.disconnect() - }); -}); diff --git a/scripts/tests/contract.test.ts b/scripts/tests/contract.test.ts deleted file mode 100644 index 496dfc861..000000000 --- a/scripts/tests/contract.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { MsgExecuteContractEncodeObject } from "@cosmjs/cosmwasm-stargate"; -import { toUtf8 } from "@cosmjs/encoding"; -import { Uint53 } from "@cosmjs/math"; -import { assertIsDeliverTxSuccess, SigningStargateClient } from '@cosmjs/stargate'; -import { findAttribute, parseRawLog } from '@cosmjs/stargate/build/logs'; -import { MsgExecuteContract, MsgInstantiateContract, MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'; -import fs from 'fs'; -import Long from "long"; -import path from 'path'; -import { Network, networks } from '../utils/config'; -import { getOsmosisClient, getQueryClient } from '../utils/osmosis-client'; -import { testWallet1 } from '../utils/test-wallets'; - -const INSTANTIATE_STR = "test-instantiate-string-123" - -describe('example contract', () => { - let client: SigningStargateClient; - let codeId: number; - let contractAddr: string; - - beforeAll(async () => { - client = await getOsmosisClient(testWallet1); - }) - - afterAll(() => { - client.disconnect(); - }) - - test('can be deployed', async () => { - const contractCode = fs.readFileSync(path.resolve(__dirname, '../../artifacts/example.wasm')); - const storeCode = { - typeUrl: '/cosmwasm.wasm.v1.MsgStoreCode', - value: MsgStoreCode.fromPartial({ - sender: testWallet1.address, - wasmByteCode: contractCode, - }), - }; - - const uploadResult = await client.signAndBroadcast( - testWallet1.address, - [storeCode], - networks[Network.OSMOSIS].defaultSendFee, - ); - - assertIsDeliverTxSuccess(uploadResult); - - const parsedLog = parseRawLog(uploadResult.rawLog); - const codeIdAttr = findAttribute(parsedLog, "store_code", "code_id"); - codeId = Number.parseInt(codeIdAttr.value, 10); - - expect(codeId).toBeDefined(); - }); - - test('can be instantiated', async () => { - const instantiateContractMsg = { - typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", - value: MsgInstantiateContract.fromPartial({ - sender: testWallet1.address, - codeId: Long.fromString(new Uint53(codeId).toString()), - label: "instantiate-example-contract", - msg: toUtf8(JSON.stringify({ some_string: INSTANTIATE_STR })), - }), - }; - - const instantiateResult = await client.signAndBroadcast( - testWallet1.address, - [instantiateContractMsg], - networks[Network.OSMOSIS].defaultSendFee, - ); - - assertIsDeliverTxSuccess(instantiateResult); - - const parsedLogs = parseRawLog(instantiateResult.rawLog); - const contractAddressAttr = findAttribute(parsedLogs, "instantiate", "_contract_address"); - contractAddr = contractAddressAttr.value; - - expect(contractAddr).toBeDefined(); - }); - - test('can save item', async () => { - const queryClient = await getQueryClient(); - const beforeRes: { str: string } = await queryClient.queryContractSmart(contractAddr, { get_stored_string: {} }) - expect(beforeRes.str).toBe(INSTANTIATE_STR) - - const updatedString = "spiderman123" - - const executeContractMsg: MsgExecuteContractEncodeObject = { - typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", - value: MsgExecuteContract.fromPartial({ - sender: testWallet1.address, - contract: contractAddr, - msg: toUtf8(JSON.stringify({ "update_item_string": { str: updatedString } })), - funds: [], - }), - }; - - const execResult = await client.signAndBroadcast( - testWallet1.address, - [executeContractMsg], - networks[Network.OSMOSIS].defaultSendFee, - ); - assertIsDeliverTxSuccess(execResult); - - const afterRes: { str: string } = await queryClient.queryContractSmart(contractAddr, { get_stored_string: {} }) - expect(afterRes.str).toBe(updatedString) - - queryClient.disconnect() - }); -}) diff --git a/scripts/tests/instantiate.test.ts b/scripts/tests/instantiate.test.ts new file mode 100644 index 000000000..f94f1cf1c --- /dev/null +++ b/scripts/tests/instantiate.test.ts @@ -0,0 +1,83 @@ +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; +import { toHex } from '@cosmjs/encoding'; +import fs from 'fs'; +import path from 'path'; +import { Network, networks } from '../utils/config'; +import { testWallet1 } from '../utils/test-wallets'; +import { getCosmWasmClient } from '../utils/client'; +import { sha256 } from '@cosmjs/crypto'; +import { GetAllowListResponse, serializeAssetInfo } from '../utils/types'; + +describe('instantiating fields contract', () => { + let client: SigningCosmWasmClient; + let codeId: number; + let contractAddr: string; + + beforeAll(async () => { + client = await getCosmWasmClient(testWallet1); + }); + + afterAll(() => { + client.disconnect(); + }); + + test('can be uploaded', async () => { + const wasm = fs.readFileSync(path.resolve(__dirname, '../../artifacts/credit_manager.wasm')); + const { + codeId: uploadCodeId, + originalChecksum, + originalSize, + compressedChecksum, + compressedSize, + } = await client.upload(testWallet1.address, wasm, networks[Network.OSMOSIS].defaultSendFee); + + expect(originalChecksum).toEqual(toHex(sha256(wasm))); + expect(originalSize).toEqual(wasm.length); + expect(compressedChecksum).toMatch(/^[0-9a-f]{64}$/); + expect(compressedSize).toBeLessThan(wasm.length * 0.5); + expect(uploadCodeId).toBeGreaterThanOrEqual(1); + codeId = uploadCodeId; + expect(codeId).toBeDefined(); + }); + + test('can be instantiated', async () => { + const owner = 'osmo105e4n2f2gr92x8pxvmhxj5v7e2m9j08zelxdnq'; + const allowed_vaults = [ + 'osmo1r4c2g5wex39kcdeahgxjaxnr2wnv7jvxc5je0e', + 'osmo1av54qcmavhjkqsd67cf6f4cedqjrdeh73k52l2', + 'osmo18zhhdrjd5qfvewnu5lkkgv6w7rtcmzh3hq7qes', + ]; + const allowed_assets = [ + { cw20: 'osmo1ptlhw66xg7nznag8sy4mnlsj04xklxqjgqrpz4' }, + { native: 'uosmo' }, + { cw20: 'osmo1ewn73qp0aqrtya38p0nv5c2xsshdea7ad34qkc' }, + ]; + + const { contractAddress } = await client.instantiate( + testWallet1.address, + codeId, + { owner, allowed_vaults, allowed_assets }, + 'test-instantiate-string-123', + networks[Network.OSMOSIS].defaultSendFee, + ); + contractAddr = contractAddress; + expect(contractAddr).toBeDefined(); + + const ownerFromQuery = await client.queryContractSmart(contractAddress, { get_owner: {} }); + expect(ownerFromQuery).toEqual({ owner }); + + const allowListsFromQuery: GetAllowListResponse = await client.queryContractSmart(contractAddress, { + get_allow_lists: {}, + }); + + expect(allowListsFromQuery.vaults.length).toEqual(allowed_vaults.length); + expect(allowListsFromQuery.vaults.every((v) => allowed_vaults.includes(v))).toBeTruthy(); + + expect(allowListsFromQuery.assets.length).toEqual(allowed_assets.length); + expect( + allowListsFromQuery.assets + .map(serializeAssetInfo) + .every((asset_str) => allowed_assets.map(serializeAssetInfo).includes(asset_str)), + ).toBeTruthy(); + }); +}); diff --git a/scripts/utils/client.ts b/scripts/utils/client.ts new file mode 100644 index 000000000..9194a355f --- /dev/null +++ b/scripts/utils/client.ts @@ -0,0 +1,13 @@ +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; +import { Network, networks } from './config'; +import { walletDataType } from './test-wallets'; +import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; + +type ClientGetter = (wallet: walletDataType) => Promise; + +export const getCosmWasmClient: ClientGetter = async (wallet) => { + const signer = await DirectSecp256k1HdWallet.fromMnemonic(wallet.mnemonic, { + prefix: networks[Network.OSMOSIS].bech32Prefix, + }); + return await SigningCosmWasmClient.connectWithSigner(networks[Network.OSMOSIS].localRpcEndpoint, signer); +}; diff --git a/scripts/utils/osmosis-client.ts b/scripts/utils/osmosis-client.ts deleted file mode 100644 index 08afa99b4..000000000 --- a/scripts/utils/osmosis-client.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'; -import { Slip10RawIndex } from '@cosmjs/crypto'; -import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; -import { SigningStargateClient } from '@cosmjs/stargate'; -import { MsgExecuteContract, MsgInstantiateContract, MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'; -import { getSigningOsmosisClient } from 'osmojs'; -import { Network, networks } from './config'; -import { walletDataType } from './test-wallets'; - -const hdPath = [ - Slip10RawIndex.hardened(44), - Slip10RawIndex.hardened(118), - Slip10RawIndex.hardened(0), - Slip10RawIndex.normal(0), - Slip10RawIndex.normal(0), -]; - - -type ClientGetter = (wallet: walletDataType) => Promise; - -export const getOsmosisClient: ClientGetter = async (wallet) => { - const signer = await DirectSecp256k1HdWallet.fromMnemonic(wallet.mnemonic, { - prefix: networks[Network.OSMOSIS].bech32Prefix, - hdPaths: [hdPath], - }); - - const client = await getSigningOsmosisClient({ - rpcEndpoint: networks[Network.OSMOSIS].localRpcEndpoint, - signer, - }); - - client.registry.register('/cosmwasm.wasm.v1.MsgStoreCode', MsgStoreCode); - client.registry.register('/cosmwasm.wasm.v1.MsgInstantiateContract', MsgInstantiateContract); - client.registry.register('/cosmwasm.wasm.v1.MsgExecuteContract', MsgExecuteContract); - - return client; -}; - -/* Separate client needed as querying not available in signed Osmosis client at the moment */ -export const getQueryClient: () => Promise = async () => { - return await CosmWasmClient.connect(networks[Network.OSMOSIS].localRpcEndpoint); -}; diff --git a/scripts/utils/types.ts b/scripts/utils/types.ts new file mode 100644 index 000000000..da1896ca8 --- /dev/null +++ b/scripts/utils/types.ts @@ -0,0 +1,7 @@ +export type AssetInfo = { cw20: string } | { native: string }; +export const serializeAssetInfo = (obj: AssetInfo) => Object.entries(obj).flat().join(':'); + +export interface GetAllowListResponse { + vaults: string[]; + assets: AssetInfo[]; +} From 14332e3ba9f39f5d0ff690f010dcf6ce1952f8cc Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 24 Jun 2022 12:02:24 +0200 Subject: [PATCH 013/218] Converting unit tests to cw-multi-tests --- Cargo.lock | 91 +++++++++++++++ contracts/credit-manager/Cargo.toml | 1 + contracts/credit-manager/src/contract.rs | 6 +- .../credit-manager/src/instantiate_tests.rs | 108 ------------------ contracts/credit-manager/src/lib.rs | 3 - contracts/credit-manager/src/state.rs | 4 +- contracts/credit-manager/tests/helpers.rs | 12 ++ .../credit-manager/tests/instantiate_tests.rs | 103 +++++++++++++++++ packages/fields/src/messages.rs | 6 +- 9 files changed, 212 insertions(+), 122 deletions(-) delete mode 100644 contracts/credit-manager/src/instantiate_tests.rs create mode 100644 contracts/credit-manager/tests/helpers.rs create mode 100644 contracts/credit-manager/tests/instantiate_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 35edfe913..65444f444 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anyhow" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" + [[package]] name = "base16ct" version = "0.1.1" @@ -35,6 +41,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + [[package]] name = "cfg-if" version = "1.0.0" @@ -96,6 +108,16 @@ dependencies = [ "uint", ] +[[package]] +name = "cosmwasm-storage" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18403b07304d15d304dad11040d45bbcaf78d603b4be3fb5e2685c16f9229b5" +dependencies = [ + "cosmwasm-std", + "serde", +] + [[package]] name = "cpufeatures" version = "0.2.2" @@ -112,6 +134,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-asset", + "cw-multi-test", "cw-storage-plus", "fields", "schemars", @@ -171,6 +194,25 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-multi-test" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f9a8ab7c3c29ec93cb7a39ce4b14a05e053153b4a17ef7cf2246af1b7c087e" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus", + "cw-utils", + "derivative", + "itertools", + "prost", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw-storage-plus" version = "0.13.4" @@ -215,6 +257,17 @@ dependencies = [ "const-oid", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.9.0" @@ -257,6 +310,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "elliptic-curve" version = "0.11.12" @@ -363,6 +422,15 @@ dependencies = [ "digest", ] +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.2" @@ -414,6 +482,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.20" diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 08506f55c..9b474a3ad 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -22,3 +22,4 @@ serde = { version = "1.0.103", default-features = false, features = ["derive"] } [dev-dependencies] cosmwasm-schema = "1.0.0" +cw-multi-test = "0.13.4" \ No newline at end of file diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index fcb164392..7837b9500 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -44,11 +44,7 @@ fn store_allow_lists( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute(_: DepsMut, _env: Env, _: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::UpdateItemString { - str: _, - } => Ok(Response::new()), - } + match msg {} } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/contracts/credit-manager/src/instantiate_tests.rs b/contracts/credit-manager/src/instantiate_tests.rs deleted file mode 100644 index 517c52b76..000000000 --- a/contracts/credit-manager/src/instantiate_tests.rs +++ /dev/null @@ -1,108 +0,0 @@ -use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; -use cosmwasm_std::{coins, from_binary, Addr}; - -use fields::messages::{AllowListsResponse, InstantiateMsg, OwnerResponse, QueryMsg}; -use fields::types::AssetInfo; - -use crate::contract::{instantiate, query}; - -#[test] -fn test_owner_set_on_instantiate() { - let mut deps = mock_dependencies(); - let info = mock_info("creator", &coins(1000, "uosmo")); - - let owner_str = String::from("spiderman123"); - let res = instantiate( - deps.as_mut(), - mock_env(), - info, - InstantiateMsg { - owner: owner_str.clone(), - allowed_vaults: vec![], - allowed_assets: vec![], - }, - ) - .unwrap(); - assert_eq!(0, res.messages.len()); - - let res = query(deps.as_ref(), mock_env(), QueryMsg::GetOwner {}).unwrap(); - let value: OwnerResponse = from_binary(&res).unwrap(); - assert_eq!(owner_str, value.owner); -} - -#[test] -fn test_allowed_vaults_and_assets_stored_on_instantiate() { - let mut deps = mock_dependencies(); - let info = mock_info("creator", &coins(1000, "uosmo")); - - let allowed_vaults = vec![ - String::from("vaultcontract1"), - String::from("vaultcontract2"), - String::from("vaultcontract3"), - ]; - - let allowed_assets = vec![ - AssetInfo::Native(String::from("uosmo")), - AssetInfo::Cw20(Addr::unchecked("osmo85wwjycfxjlaxsae9asmxlk3bsgxbw")), - AssetInfo::Cw20(Addr::unchecked("osmompbtkt3jezatztteo577lxkqbkdyke")), - AssetInfo::Cw20(Addr::unchecked("osmos6kmpxz9xcstleqnu2fnz8gskgf6gx")), - ]; - - instantiate( - deps.as_mut(), - mock_env(), - info, - InstantiateMsg { - owner: String::from("spiderman123"), - allowed_vaults: allowed_vaults.clone(), - allowed_assets: allowed_assets.clone(), - }, - ) - .unwrap(); - - let res = query(deps.as_ref(), mock_env(), QueryMsg::GetAllowLists {}).unwrap(); - let res: AllowListsResponse = from_binary(&res).unwrap(); - assert_eq!(res.vaults.len(), 3); - assert_eq!(allowed_vaults, res.vaults); - - assert_eq!(res.assets.len(), 4); - assert!(allowed_assets.iter().all(|item| res.assets.contains(item))); -} - -#[test] -fn test_panics_on_invalid_instantiation_addrs() { - let mut deps = mock_dependencies(); - let info = mock_info("creator", &coins(1000, "uosmo")); - - let res = instantiate( - deps.as_mut(), - mock_env(), - info.clone(), - InstantiateMsg { - owner: String::from("spiderman123"), - allowed_vaults: vec![String::from("123INVALID")], - allowed_assets: vec![], - }, - ); - - match res { - Err(_) => {} - Ok(_) => panic!("Should have thrown an error"), - } - - let res = instantiate( - deps.as_mut(), - mock_env(), - info, - InstantiateMsg { - owner: String::from("spiderman123"), - allowed_vaults: vec![], - allowed_assets: vec![AssetInfo::Cw20(Addr::unchecked("123INVALID"))], - }, - ); - - match res { - Err(_) => {} - Ok(_) => panic!("Should have thrown an error"), - } -} diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 91b970d23..3407c199d 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -1,5 +1,2 @@ pub mod contract; pub mod state; - -#[cfg(test)] -mod instantiate_tests; diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 720b1e885..613bfa4ee 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -3,5 +3,7 @@ use cw_storage_plus::{Item, Map}; pub const OWNER: Item = Item::new("owner"); -pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); +// e.g. cw20:osmo23905809 or native:uosmo +type AssetInfoStr = String; +pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); diff --git a/contracts/credit-manager/tests/helpers.rs b/contracts/credit-manager/tests/helpers.rs new file mode 100644 index 000000000..1e56a6b6b --- /dev/null +++ b/contracts/credit-manager/tests/helpers.rs @@ -0,0 +1,12 @@ +use cosmwasm_std::Empty; +use credit_manager::contract::{execute, instantiate, query}; +use cw_multi_test::{App, Contract, ContractWrapper}; + +pub fn mock_app() -> App { + App::default() +} + +pub fn mock_contract() -> Box> { + let contract = ContractWrapper::new(execute, instantiate, query); + Box::new(contract) +} diff --git a/contracts/credit-manager/tests/instantiate_tests.rs b/contracts/credit-manager/tests/instantiate_tests.rs new file mode 100644 index 000000000..13a7e8c12 --- /dev/null +++ b/contracts/credit-manager/tests/instantiate_tests.rs @@ -0,0 +1,103 @@ +use cosmwasm_std::Addr; +use cw_multi_test::Executor; + +use fields::messages::{AllowListsResponse, InstantiateMsg, OwnerResponse, QueryMsg}; +use fields::types::AssetInfo; + +use crate::helpers::{mock_app, mock_contract}; + +mod helpers; + +#[test] +fn test_owner_set_on_instantiate() { + let mut app = mock_app(); + let code_id = app.store_code(mock_contract()); + let owner = Addr::unchecked("owner"); + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_assets: vec![], + }; + + let contract_addr = + app.instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None).unwrap(); + + let res: OwnerResponse = + app.wrap().query_wasm_smart(contract_addr.clone(), &QueryMsg::GetOwner {}).unwrap(); + + assert_eq!(owner, res.owner); +} + +#[test] +fn test_allowed_vaults_and_assets_stored_on_instantiate() { + let mut app = mock_app(); + let code_id = app.store_code(mock_contract()); + let owner = Addr::unchecked("owner"); + + let allowed_vaults = vec![ + String::from("vaultcontract1"), + String::from("vaultcontract2"), + String::from("vaultcontract3"), + ]; + + let allowed_assets = vec![ + AssetInfo::Native(String::from("uosmo")), + AssetInfo::Cw20(Addr::unchecked("osmo85wwjycfxjlaxsae9asmxlk3bsgxbw")), + AssetInfo::Cw20(Addr::unchecked("osmompbtkt3jezatztteo577lxkqbkdyke")), + AssetInfo::Cw20(Addr::unchecked("osmos6kmpxz9xcstleqnu2fnz8gskgf6gx")), + ]; + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: allowed_vaults.clone(), + allowed_assets: allowed_assets.clone(), + }; + + let contract_addr = + app.instantiate_contract(code_id, owner, &msg, &[], "mock-contract", None).unwrap(); + + let res: AllowListsResponse = + app.wrap().query_wasm_smart(contract_addr.clone(), &QueryMsg::GetAllowLists {}).unwrap(); + + assert_eq!(res.vaults.len(), 3); + assert_eq!(allowed_vaults, res.vaults); + + assert_eq!(res.assets.len(), 4); + assert!(allowed_assets.iter().all(|item| res.assets.contains(item))); +} + +#[test] +fn test_panics_on_invalid_instantiation_addrs() { + let mut app = mock_app(); + let code_id = app.store_code(mock_contract()); + let owner = Addr::unchecked("owner"); + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![String::from("123INVALID")], + allowed_assets: vec![], + }; + + let instantiate_res = + app.instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None); + + match instantiate_res { + Err(_) => {} + Ok(_) => panic!("Should have thrown an error"), + } + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_assets: vec![AssetInfo::Cw20(Addr::unchecked("123INVALID"))], + }; + + let instantiate_res = + app.instantiate_contract(code_id, owner, &msg, &[], "mock-contract", None); + + match instantiate_res { + Err(_) => {} + Ok(_) => panic!("Should have thrown an error"), + } +} diff --git a/packages/fields/src/messages.rs b/packages/fields/src/messages.rs index 0f4d1d93d..55cfd7824 100644 --- a/packages/fields/src/messages.rs +++ b/packages/fields/src/messages.rs @@ -12,11 +12,7 @@ pub struct InstantiateMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - UpdateItemString { - str: String, - }, -} +pub enum ExecuteMsg {} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] From d9eabb5e565623439cde9bcf4bbda2e9339217fc Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 28 Jun 2022 21:09:38 +0200 Subject: [PATCH 014/218] Updating based on cw-asset 2.1 bump --- Cargo.lock | 5 +-- contracts/credit-manager/Cargo.toml | 2 +- contracts/credit-manager/src/contract.rs | 8 +++-- contracts/credit-manager/src/state.rs | 5 ++- .../credit-manager/tests/instantiate_tests.rs | 2 +- packages/fields/Cargo.toml | 2 +- packages/fields/src/lib.rs | 1 - packages/fields/src/messages.rs | 2 +- packages/fields/src/types.rs | 32 ------------------- 9 files changed, 14 insertions(+), 45 deletions(-) delete mode 100644 packages/fields/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 65444f444..3013077bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,11 +184,12 @@ dependencies = [ [[package]] name = "cw-asset" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26767ad8483411eb37b71e5aaf6bdad1c3d015ecdd2e0369f264c0335d79b3b3" +checksum = "4a2015b1cdbe475b1c8b6a2a04e6b7be1f576ec41d8e6811bd7d069959f5c7f7" dependencies = [ "cosmwasm-std", + "cw-storage-plus", "cw20", "schemars", "serde", diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 9b474a3ad..dd1ae9f14 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -14,7 +14,7 @@ library = [] [dependencies] fields = { version = "2.0.0", path = "../../packages/fields" } -cw-asset = "2.0.0" +cw-asset = "2.1.0" cosmwasm-std = "1.0.0" cw-storage-plus = "0.13.2" schemars = "0.8.1" diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 7837b9500..7beccfc29 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -1,9 +1,10 @@ use cosmwasm_std::{ entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, }; +use cw_asset::{AssetInfo, AssetInfoUnchecked}; +use std::convert::TryFrom; use fields::messages::{AllowListsResponse, ExecuteMsg, InstantiateMsg, OwnerResponse, QueryMsg}; -use fields::types::AssetInfo; use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; @@ -37,7 +38,7 @@ fn store_allow_lists( } _ => {} } - ALLOWED_ASSETS.save(deps.storage, denom_or_addr.to_string(), &true)?; + ALLOWED_ASSETS.save(deps.storage, denom_or_addr.into(), &true)?; } Ok(()) } @@ -68,7 +69,8 @@ fn try_get_allow_lists(deps: Deps) -> StdResult { .collect::>>()?; let assets = ALLOWED_ASSETS .keys(deps.storage, None, None, Order::Ascending) - .map(|asset_str| Ok(AssetInfo::from_str(asset_str?))) + .map(|key| AssetInfoUnchecked::try_from(key?)) + .map(|unchecked| unchecked?.check(deps.api, None)) .collect::>>()?; Ok(AllowListsResponse { diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 613bfa4ee..2ae4d6096 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,9 +1,8 @@ use cosmwasm_std::Addr; +use cw_asset::AssetInfoKey; use cw_storage_plus::{Item, Map}; pub const OWNER: Item = Item::new("owner"); -// e.g. cw20:osmo23905809 or native:uosmo -type AssetInfoStr = String; -pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); +pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); diff --git a/contracts/credit-manager/tests/instantiate_tests.rs b/contracts/credit-manager/tests/instantiate_tests.rs index 13a7e8c12..9ee363207 100644 --- a/contracts/credit-manager/tests/instantiate_tests.rs +++ b/contracts/credit-manager/tests/instantiate_tests.rs @@ -1,8 +1,8 @@ use cosmwasm_std::Addr; +use cw_asset::AssetInfo; use cw_multi_test::Executor; use fields::messages::{AllowListsResponse, InstantiateMsg, OwnerResponse, QueryMsg}; -use fields::types::AssetInfo; use crate::helpers::{mock_app, mock_contract}; diff --git a/packages/fields/Cargo.toml b/packages/fields/Cargo.toml index 852f6e8c5..5825dd8dc 100644 --- a/packages/fields/Cargo.toml +++ b/packages/fields/Cargo.toml @@ -10,7 +10,7 @@ doctest = false [dependencies] cosmwasm-std = "1.0.0" cw20 = "0.13.2" -cw-asset = "2.0.0" +cw-asset = "2.1.0" cw-storage-plus = "0.13" schemars = "0.8.1" diff --git a/packages/fields/src/lib.rs b/packages/fields/src/lib.rs index 3c759906b..ba63992f3 100644 --- a/packages/fields/src/lib.rs +++ b/packages/fields/src/lib.rs @@ -1,2 +1 @@ pub mod messages; -pub mod types; diff --git a/packages/fields/src/messages.rs b/packages/fields/src/messages.rs index 55cfd7824..236ef54d4 100644 --- a/packages/fields/src/messages.rs +++ b/packages/fields/src/messages.rs @@ -1,5 +1,5 @@ -use crate::types::AssetInfo; use cosmwasm_std::Addr; +use cw_asset::AssetInfo; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/packages/fields/src/types.rs b/packages/fields/src/types.rs deleted file mode 100644 index fbc19b439..000000000 --- a/packages/fields/src/types.rs +++ /dev/null @@ -1,32 +0,0 @@ -use cosmwasm_std::Addr; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -// TODO: Local AssetInfo should be replaced by cw-asset when fix is merged on that side -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum AssetInfo { - Cw20(Addr), - Native(String), -} - -impl ToString for AssetInfo { - fn to_string(&self) -> String { - match self { - AssetInfo::Cw20(addr) => format!("cw20:{}", addr.as_str()), - AssetInfo::Native(denom) => format!("native:{}", denom.as_str()), - } - } -} - -impl AssetInfo { - pub fn from_str(asset_str: String) -> Self { - let words: Vec<&str> = asset_str.split(':').collect(); - - match words[0] { - "native" => Self::Native(String::from(words[1])), - "cw20" => Self::Cw20(Addr::unchecked(words[1])), - asset_type => panic!("{} is not a valid asset type", asset_type), - } - } -} From 27b7ea63a2cc9ea14db354ec7771e4da7dec325a Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Wed, 29 Jun 2022 11:43:58 +0100 Subject: [PATCH 015/218] Remove build artifacts --- .gitignore | 3 +++ artifacts/checksums.txt | 1 - artifacts/checksums_intermediate.txt | 1 - artifacts/credit_manager.wasm | Bin 168520 -> 0 bytes 4 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 artifacts/checksums.txt delete mode 100644 artifacts/checksums_intermediate.txt delete mode 100644 artifacts/credit_manager.wasm diff --git a/.gitignore b/.gitignore index a57c1261e..df64e0632 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ node_modules # intellij local files .idea +# build artifacts +artifacts + # private scripts that I don't want to commit are prefixed by an underscore **/_*.js **/_*.ts diff --git a/artifacts/checksums.txt b/artifacts/checksums.txt deleted file mode 100644 index 89262ae9b..000000000 --- a/artifacts/checksums.txt +++ /dev/null @@ -1 +0,0 @@ -e5b986b673efa5847747254aae33b7babe339e89a1b3f454314f420a6a7eb93c credit_manager.wasm diff --git a/artifacts/checksums_intermediate.txt b/artifacts/checksums_intermediate.txt deleted file mode 100644 index f4c3dae92..000000000 --- a/artifacts/checksums_intermediate.txt +++ /dev/null @@ -1 +0,0 @@ -a0787373c53017e5f198a58e205be8c91992def3ec8be353d4974abb6f779bb0 ./target/wasm32-unknown-unknown/release/credit_manager.wasm diff --git a/artifacts/credit_manager.wasm b/artifacts/credit_manager.wasm deleted file mode 100644 index 6c3e8605c7ebb0d57be79cdcd0e3dd8cfa2aef20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 168520 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 vr+U*d Date: Wed, 29 Jun 2022 11:46:44 +0100 Subject: [PATCH 016/218] Rename `fields` package to `rover` --- Cargo.lock | 28 +++++++++---------- contracts/credit-manager/Cargo.toml | 2 +- contracts/credit-manager/src/contract.rs | 4 +-- .../credit-manager/tests/instantiate_tests.rs | 4 +-- packages/{fields => rover}/Cargo.toml | 2 +- packages/{fields => rover}/README.md | 0 packages/{fields => rover}/src/lib.rs | 0 packages/{fields => rover}/src/messages.rs | 0 packages/{fields => rover}/src/types.rs | 0 9 files changed, 20 insertions(+), 20 deletions(-) rename packages/{fields => rover}/Cargo.toml (96%) rename packages/{fields => rover}/README.md (100%) rename packages/{fields => rover}/src/lib.rs (100%) rename packages/{fields => rover}/src/messages.rs (100%) rename packages/{fields => rover}/src/types.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 65444f444..8156b6af7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,7 +136,7 @@ dependencies = [ "cw-asset", "cw-multi-test", "cw-storage-plus", - "fields", + "rover", "schemars", "serde", ] @@ -344,19 +344,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "fields" -version = "2.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-asset", - "cw-storage-plus", - "cw20", - "schemars", - "serde", -] - [[package]] name = "forward_ref" version = "1.0.0" @@ -543,6 +530,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rover" +version = "2.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-asset", + "cw-storage-plus", + "cw20", + "schemars", + "serde", +] + [[package]] name = "ryu" version = "1.0.10" diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 9b474a3ad..b5811bbf9 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -12,7 +12,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -fields = { version = "2.0.0", path = "../../packages/fields" } +rover = { version = "2.0.0", path = "../../packages/rover" } cw-asset = "2.0.0" cosmwasm-std = "1.0.0" diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 7837b9500..404190541 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -2,8 +2,8 @@ use cosmwasm_std::{ entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, }; -use fields::messages::{AllowListsResponse, ExecuteMsg, InstantiateMsg, OwnerResponse, QueryMsg}; -use fields::types::AssetInfo; +use rover::messages::{AllowListsResponse, ExecuteMsg, InstantiateMsg, OwnerResponse, QueryMsg}; +use rover::types::AssetInfo; use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; diff --git a/contracts/credit-manager/tests/instantiate_tests.rs b/contracts/credit-manager/tests/instantiate_tests.rs index 13a7e8c12..e311ef486 100644 --- a/contracts/credit-manager/tests/instantiate_tests.rs +++ b/contracts/credit-manager/tests/instantiate_tests.rs @@ -1,8 +1,8 @@ use cosmwasm_std::Addr; use cw_multi_test::Executor; -use fields::messages::{AllowListsResponse, InstantiateMsg, OwnerResponse, QueryMsg}; -use fields::types::AssetInfo; +use rover::messages::{AllowListsResponse, InstantiateMsg, OwnerResponse, QueryMsg}; +use rover::types::AssetInfo; use crate::helpers::{mock_app, mock_contract}; diff --git a/packages/fields/Cargo.toml b/packages/rover/Cargo.toml similarity index 96% rename from packages/fields/Cargo.toml rename to packages/rover/Cargo.toml index 852f6e8c5..5c6cae3c0 100644 --- a/packages/fields/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "fields" +name = "rover" version = "2.0.0" authors = ["larry_0x ", "Gabe R. "] edition = "2018" diff --git a/packages/fields/README.md b/packages/rover/README.md similarity index 100% rename from packages/fields/README.md rename to packages/rover/README.md diff --git a/packages/fields/src/lib.rs b/packages/rover/src/lib.rs similarity index 100% rename from packages/fields/src/lib.rs rename to packages/rover/src/lib.rs diff --git a/packages/fields/src/messages.rs b/packages/rover/src/messages.rs similarity index 100% rename from packages/fields/src/messages.rs rename to packages/rover/src/messages.rs diff --git a/packages/fields/src/types.rs b/packages/rover/src/types.rs similarity index 100% rename from packages/fields/src/types.rs rename to packages/rover/src/types.rs From 1577233577b9be6d6be7c395332bb5261e109fba Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Wed, 29 Jun 2022 11:47:39 +0100 Subject: [PATCH 017/218] Some formatting of `Cargo.toml` --- Cargo.lock | 12 ------------ contracts/credit-manager/Cargo.toml | 3 +-- packages/rover/Cargo.toml | 4 ---- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8156b6af7..68ec3122c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,16 +81,6 @@ dependencies = [ "syn", ] -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - [[package]] name = "cosmwasm-std" version = "1.0.0" @@ -131,7 +121,6 @@ dependencies = [ name = "credit-manager" version = "2.0.0" dependencies = [ - "cosmwasm-schema", "cosmwasm-std", "cw-asset", "cw-multi-test", @@ -534,7 +523,6 @@ dependencies = [ name = "rover" version = "2.0.0" dependencies = [ - "cosmwasm-schema", "cosmwasm-std", "cw-asset", "cw-storage-plus", diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index b5811bbf9..6d02482e7 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -21,5 +21,4 @@ schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } [dev-dependencies] -cosmwasm-schema = "1.0.0" -cw-multi-test = "0.13.4" \ No newline at end of file +cw-multi-test = "0.13.4" diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 5c6cae3c0..8108e101b 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -12,9 +12,5 @@ cosmwasm-std = "1.0.0" cw20 = "0.13.2" cw-asset = "2.0.0" cw-storage-plus = "0.13" - schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } - -[dev-dependencies] -cosmwasm-schema = "1.0.0" \ No newline at end of file From 4ad7bf0a798672572bcb295d424b7d36ba71e7f9 Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Wed, 29 Jun 2022 11:55:49 +0100 Subject: [PATCH 018/218] Reorganize rover package; use unchecked types in msgs/responses --- packages/rover/src/lib.rs | 35 ++++++++++++++++++++++++++++++++-- packages/rover/src/messages.rs | 33 -------------------------------- packages/rover/src/types.rs | 32 ------------------------------- 3 files changed, 33 insertions(+), 67 deletions(-) delete mode 100644 packages/rover/src/messages.rs delete mode 100644 packages/rover/src/types.rs diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index 3c759906b..f90ba18b3 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -1,2 +1,33 @@ -pub mod messages; -pub mod types; +use cosmwasm_std::Addr; +use cw_asset::AssetInfoUnchecked; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct InstantiateMsg { + pub owner: String, + pub allowed_vaults: Vec, + pub allowed_assets: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg {} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + GetOwner {}, + GetAllowLists {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct OwnerResponse { + pub owner: Addr, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct AllowListsResponse { + pub vaults: Vec, + pub assets: Vec, +} diff --git a/packages/rover/src/messages.rs b/packages/rover/src/messages.rs deleted file mode 100644 index 55cfd7824..000000000 --- a/packages/rover/src/messages.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::types::AssetInfo; -use cosmwasm_std::Addr; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct InstantiateMsg { - pub owner: String, - pub allowed_vaults: Vec, - pub allowed_assets: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg {} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - GetOwner {}, - GetAllowLists {}, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct OwnerResponse { - pub owner: Addr, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct AllowListsResponse { - pub vaults: Vec, - pub assets: Vec, -} diff --git a/packages/rover/src/types.rs b/packages/rover/src/types.rs deleted file mode 100644 index fbc19b439..000000000 --- a/packages/rover/src/types.rs +++ /dev/null @@ -1,32 +0,0 @@ -use cosmwasm_std::Addr; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -// TODO: Local AssetInfo should be replaced by cw-asset when fix is merged on that side -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum AssetInfo { - Cw20(Addr), - Native(String), -} - -impl ToString for AssetInfo { - fn to_string(&self) -> String { - match self { - AssetInfo::Cw20(addr) => format!("cw20:{}", addr.as_str()), - AssetInfo::Native(denom) => format!("native:{}", denom.as_str()), - } - } -} - -impl AssetInfo { - pub fn from_str(asset_str: String) -> Self { - let words: Vec<&str> = asset_str.split(':').collect(); - - match words[0] { - "native" => Self::Native(String::from(words[1])), - "cw20" => Self::Cw20(Addr::unchecked(words[1])), - asset_type => panic!("{} is not a valid asset type", asset_type), - } - } -} From 6698167e38592e1771d1cb9f9639851c3c8364cc Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Wed, 29 Jun 2022 12:18:44 +0100 Subject: [PATCH 019/218] Adjust package and dependency versions --- Cargo.lock | 9 +++++---- contracts/credit-manager/Cargo.toml | 12 ++++++------ packages/rover/Cargo.toml | 8 ++++---- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 68ec3122c..b60218053 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,7 +119,7 @@ dependencies = [ [[package]] name = "credit-manager" -version = "2.0.0" +version = "0.1.0" dependencies = [ "cosmwasm-std", "cw-asset", @@ -173,11 +173,12 @@ dependencies = [ [[package]] name = "cw-asset" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26767ad8483411eb37b71e5aaf6bdad1c3d015ecdd2e0369f264c0335d79b3b3" +checksum = "4a2015b1cdbe475b1c8b6a2a04e6b7be1f576ec41d8e6811bd7d069959f5c7f7" dependencies = [ "cosmwasm-std", + "cw-storage-plus", "cw20", "schemars", "serde", @@ -521,7 +522,7 @@ dependencies = [ [[package]] name = "rover" -version = "2.0.0" +version = "0.1.0" dependencies = [ "cosmwasm-std", "cw-asset", diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 6d02482e7..d1cc09040 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "credit-manager" -version = "2.0.0" +version = "0.1.0" authors = ["grod220 , larry_0x "] edition = "2018" @@ -12,13 +12,13 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -rover = { version = "2.0.0", path = "../../packages/rover" } +rover = { version = "0.1", path = "../../packages/rover" } -cw-asset = "2.0.0" -cosmwasm-std = "1.0.0" -cw-storage-plus = "0.13.2" +cw-asset = "2.1" +cosmwasm-std = "1.0" +cw-storage-plus = "0.13" schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } [dev-dependencies] -cw-multi-test = "0.13.4" +cw-multi-test = "0.13" diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 8108e101b..b26b57a23 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rover" -version = "2.0.0" +version = "0.1.0" authors = ["larry_0x ", "Gabe R. "] edition = "2018" @@ -8,9 +8,9 @@ edition = "2018" doctest = false [dependencies] -cosmwasm-std = "1.0.0" -cw20 = "0.13.2" -cw-asset = "2.0.0" +cosmwasm-std = "1.0" +cw20 = "0.13" +cw-asset = "2.1" cw-storage-plus = "0.13" schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } From e0a49bd47abae105fb218750eb02fa0826b9bf6c Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Wed, 29 Jun 2022 12:19:04 +0100 Subject: [PATCH 020/218] Some refactoring (see PR for details) --- contracts/credit-manager/src/contract.rs | 45 +++++++++---------- contracts/credit-manager/src/state.rs | 5 +-- .../credit-manager/tests/instantiate_tests.rs | 4 +- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 404190541..cfb70ae7e 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -1,9 +1,11 @@ +use std::convert::TryFrom; + use cosmwasm_std::{ entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, }; +use cw_asset::AssetInfoUnchecked; -use rover::messages::{AllowListsResponse, ExecuteMsg, InstantiateMsg, OwnerResponse, QueryMsg}; -use rover::types::AssetInfo; +use rover::{AllowListsResponse, ExecuteMsg, InstantiateMsg, OwnerResponse, QueryMsg}; use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; @@ -16,30 +18,22 @@ pub fn instantiate( ) -> StdResult { let owner = deps.api.addr_validate(&msg.owner)?; OWNER.save(deps.storage, &owner)?; - store_allow_lists(deps, msg.allowed_vaults, msg.allowed_assets)?; - Ok(Response::new().add_attribute("method", "instantiate")) -} -fn store_allow_lists( - deps: DepsMut, - allowed_vaults: Vec, - allowed_assets: Vec, -) -> StdResult<()> { - for unverified_addr in &allowed_vaults { - let addr = deps.api.addr_validate(unverified_addr)?; - ALLOWED_VAULTS.save(deps.storage, addr, &true)?; - } + msg + .allowed_vaults + .iter() + .try_for_each(|vault| { + ALLOWED_VAULTS.save(deps.storage, deps.api.addr_validate(vault)?, &true) + })?; - for denom_or_addr in &allowed_assets { - match denom_or_addr { - AssetInfo::Cw20(unverified_addr) => { - deps.api.addr_validate(unverified_addr.as_str())?; - } - _ => {} - } - ALLOWED_ASSETS.save(deps.storage, denom_or_addr.to_string(), &true)?; - } - Ok(()) + msg + .allowed_assets + .iter() + .try_for_each(|info| { + ALLOWED_ASSETS.save(deps.storage, info.check(deps.api, None)?.into(), &true) + })?; + + Ok(Response::new()) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -66,9 +60,10 @@ fn try_get_allow_lists(deps: Deps) -> StdResult { let vaults = ALLOWED_VAULTS .keys(deps.storage, None, None, Order::Ascending) .collect::>>()?; + let assets = ALLOWED_ASSETS .keys(deps.storage, None, None, Order::Ascending) - .map(|asset_str| Ok(AssetInfo::from_str(asset_str?))) + .map(|key| AssetInfoUnchecked::try_from(key?)) .collect::>>()?; Ok(AllowListsResponse { diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 613bfa4ee..2ae4d6096 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,9 +1,8 @@ use cosmwasm_std::Addr; +use cw_asset::AssetInfoKey; use cw_storage_plus::{Item, Map}; pub const OWNER: Item = Item::new("owner"); -// e.g. cw20:osmo23905809 or native:uosmo -type AssetInfoStr = String; -pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); +pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); diff --git a/contracts/credit-manager/tests/instantiate_tests.rs b/contracts/credit-manager/tests/instantiate_tests.rs index e311ef486..13a7e8c12 100644 --- a/contracts/credit-manager/tests/instantiate_tests.rs +++ b/contracts/credit-manager/tests/instantiate_tests.rs @@ -1,8 +1,8 @@ use cosmwasm_std::Addr; use cw_multi_test::Executor; -use rover::messages::{AllowListsResponse, InstantiateMsg, OwnerResponse, QueryMsg}; -use rover::types::AssetInfo; +use fields::messages::{AllowListsResponse, InstantiateMsg, OwnerResponse, QueryMsg}; +use fields::types::AssetInfo; use crate::helpers::{mock_app, mock_contract}; From 00158c631856fa6ce03f8bdc5c5238e97a57df16 Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Wed, 29 Jun 2022 12:55:11 +0100 Subject: [PATCH 021/218] Refector query function (see PR for details) --- contracts/credit-manager/src/contract.rs | 90 ++++++++++++++++++------ packages/rover/src/lib.rs | 26 ++++--- 2 files changed, 80 insertions(+), 36 deletions(-) diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index cfb70ae7e..d5fb5b18c 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -1,14 +1,18 @@ use std::convert::TryFrom; use cosmwasm_std::{ - entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, + entry_point, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, }; -use cw_asset::AssetInfoUnchecked; +use cw_asset::{AssetInfo, AssetInfoKey, AssetInfoUnchecked}; -use rover::{AllowListsResponse, ExecuteMsg, InstantiateMsg, OwnerResponse, QueryMsg}; +use cw_storage_plus::Bound; +use rover::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; +const MAX_LIMIT: u32 = 30; +const DEFAULT_LIMIT: u32 = 10; + #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, @@ -44,30 +48,72 @@ pub fn execute(_: DepsMut, _env: Env, _: MessageInfo, msg: ExecuteMsg) -> StdRes #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::GetOwner {} => to_binary(&try_get_owner(deps)?), - QueryMsg::GetAllowLists {} => to_binary(&try_get_allow_lists(deps)?), + QueryMsg::Owner {} => to_binary(&query_owner(deps)?), + QueryMsg::AllowedVaults { + start_after, + limit, + } => to_binary(&query_allowed_vaults(deps, start_after, limit)?), + QueryMsg::AllowedAssets { + start_after, + limit, + } => to_binary(&query_allowed_assets(deps, start_after, limit)?), } } -fn try_get_owner(deps: Deps) -> StdResult { - let str = OWNER.load(deps.storage)?; - Ok(OwnerResponse { - owner: str, - }) +fn query_owner(deps: Deps) -> StdResult { + Ok(OWNER.load(deps.storage)?.into()) +} + +/// NOTE: This implementation of the query function assumes the map `ALLOWED_VAULTS` only saves `true`. +/// If a vault is to be removed from the whitelist, the map must remove the correspoinding key, instead +/// of setting the value to `false`. +fn query_allowed_vaults( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult> { + let addr: Addr; + let start = match &start_after { + Some(addr_str) => { + addr = deps.api.addr_validate(addr_str)?; + Some(Bound::exclusive(addr)) + }, + None => None, + }; + + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + ALLOWED_VAULTS + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|res| res.map(|vault_addr| vault_addr.to_string())) + .collect() } -fn try_get_allow_lists(deps: Deps) -> StdResult { - let vaults = ALLOWED_VAULTS - .keys(deps.storage, None, None, Order::Ascending) - .collect::>>()?; +/// NOTE: This implementation of the query function assumes the map `ALLOWED_ASSETS` only saves `true`. +/// If an asset is to be removed from the whitelist, the map must remove the correspoinding key, instead +/// of setting the value to `false`. +fn query_allowed_assets( + deps: Deps, + start_after: Option, + limit: Option +) -> StdResult> { + let info: AssetInfo; + let start = match &start_after { + Some(unchecked) => { + info = unchecked.check(deps.api, None)?; + Some(Bound::exclusive(AssetInfoKey::from(info))) + }, + None => None, + }; - let assets = ALLOWED_ASSETS - .keys(deps.storage, None, None, Order::Ascending) - .map(|key| AssetInfoUnchecked::try_from(key?)) - .collect::>>()?; + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - Ok(AllowListsResponse { - vaults, - assets, - }) + ALLOWED_ASSETS + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>>()? + .into_iter() + .map(|key| AssetInfoUnchecked::try_from(key)) + .collect() } diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index f90ba18b3..d5656279f 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -1,4 +1,3 @@ -use cosmwasm_std::Addr; use cw_asset::AssetInfoUnchecked; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -17,17 +16,16 @@ pub enum ExecuteMsg {} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { - GetOwner {}, - GetAllowLists {}, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct OwnerResponse { - pub owner: Addr, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct AllowListsResponse { - pub vaults: Vec, - pub assets: Vec, + /// The contract's owner. Response type: `String` + Owner {}, + /// Whitelisted vaults. Response type: `Vec` + AllowedVaults { + start_after: Option, + limit: Option, + }, + /// Whitelisted assets. Response type: `Vec` + AllowedAssets { + start_after: Option, + limit: Option, + } } From b50199c558ff4ec8cfc0d9045735701594dc26c5 Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Wed, 29 Jun 2022 12:59:58 +0100 Subject: [PATCH 022/218] Add cw2 contract version --- Cargo.lock | 13 +++++++++++++ contracts/credit-manager/Cargo.toml | 3 ++- contracts/credit-manager/src/contract.rs | 12 +++++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b60218053..92bde5e87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,7 @@ dependencies = [ "cw-asset", "cw-multi-test", "cw-storage-plus", + "cw2", "rover", "schemars", "serde", @@ -226,6 +227,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw2" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + [[package]] name = "cw20" version = "0.13.4" diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index d1cc09040..79960d3b5 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -14,8 +14,9 @@ library = [] [dependencies] rover = { version = "0.1", path = "../../packages/rover" } -cw-asset = "2.1" cosmwasm-std = "1.0" +cw2 = "0.13" +cw-asset = "2.1" cw-storage-plus = "0.13" schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index d5fb5b18c..f875d3ee4 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -3,13 +3,17 @@ use std::convert::TryFrom; use cosmwasm_std::{ entry_point, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, }; +use cw2::set_contract_version; use cw_asset::{AssetInfo, AssetInfoKey, AssetInfoUnchecked}; - use cw_storage_plus::Bound; + use rover::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; +const CONTRACT_NAME: &str = "crates.io:rover-credit-manager"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; @@ -17,9 +21,11 @@ const DEFAULT_LIMIT: u32 = 10; pub fn instantiate( deps: DepsMut, _env: Env, - _: MessageInfo, + _info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + let owner = deps.api.addr_validate(&msg.owner)?; OWNER.save(deps.storage, &owner)?; @@ -41,7 +47,7 @@ pub fn instantiate( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute(_: DepsMut, _env: Env, _: MessageInfo, msg: ExecuteMsg) -> StdResult { +pub fn execute(_deps: DepsMut, _env: Env, _info: MessageInfo, msg: ExecuteMsg) -> StdResult { match msg {} } From 47f69742b5a4609f3b41b6bae14a4f35f40f500e Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Wed, 29 Jun 2022 13:01:45 +0100 Subject: [PATCH 023/218] Remove rustfmt --- rustfmt.toml | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index e58a79bcc..000000000 --- a/rustfmt.toml +++ /dev/null @@ -1,5 +0,0 @@ -hard_tabs = false -max_width = 100 -newline_style = "unix" -tab_spaces = 4 -use_small_heuristics = "off" \ No newline at end of file From 5d71abe344998fff314431f0ad8e4d3576b5f71a Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 29 Jun 2022 17:54:04 +0200 Subject: [PATCH 024/218] Adding tests --- Cargo.toml | 2 +- contracts/credit-manager/src/contract.rs | 57 ++-- .../tests/allow_list_query_test.rs | 293 ++++++++++++++++++ ...stantiate_tests.rs => instantiate_test.rs} | 71 +++-- 4 files changed, 371 insertions(+), 52 deletions(-) create mode 100644 contracts/credit-manager/tests/allow_list_query_test.rs rename contracts/credit-manager/tests/{instantiate_tests.rs => instantiate_test.rs} (50%) diff --git a/Cargo.toml b/Cargo.toml index 3457c096a..57fb8971a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] members = ["packages/*", "contracts/*"] -[profile.release.package.fields] +[profile.release.package.rover] codegen-units = 1 incremental = false diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index f875d3ee4..454644a31 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -1,7 +1,8 @@ use std::convert::TryFrom; use cosmwasm_std::{ - entry_point, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, + entry_point, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, + StdResult, }; use cw2::set_contract_version; use cw_asset::{AssetInfo, AssetInfoKey, AssetInfoUnchecked}; @@ -29,25 +30,29 @@ pub fn instantiate( let owner = deps.api.addr_validate(&msg.owner)?; OWNER.save(deps.storage, &owner)?; - msg - .allowed_vaults - .iter() - .try_for_each(|vault| { - ALLOWED_VAULTS.save(deps.storage, deps.api.addr_validate(vault)?, &true) - })?; - - msg - .allowed_assets - .iter() - .try_for_each(|info| { - ALLOWED_ASSETS.save(deps.storage, info.check(deps.api, None)?.into(), &true) - })?; + store_allow_lists(deps, msg)?; Ok(Response::new()) } +fn store_allow_lists(deps: DepsMut, msg: InstantiateMsg) -> StdResult<()> { + msg.allowed_vaults.iter().try_for_each(|vault| { + ALLOWED_VAULTS.save(deps.storage, deps.api.addr_validate(vault)?, &true) + })?; + + msg.allowed_assets.iter().try_for_each(|info| { + ALLOWED_ASSETS.save(deps.storage, info.check(deps.api, None)?.into(), &true) + })?; + Ok(()) +} + #[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute(_deps: DepsMut, _env: Env, _info: MessageInfo, msg: ExecuteMsg) -> StdResult { +pub fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { match msg {} } @@ -55,14 +60,12 @@ pub fn execute(_deps: DepsMut, _env: Env, _info: MessageInfo, msg: ExecuteMsg) - pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Owner {} => to_binary(&query_owner(deps)?), - QueryMsg::AllowedVaults { - start_after, - limit, - } => to_binary(&query_allowed_vaults(deps, start_after, limit)?), - QueryMsg::AllowedAssets { - start_after, - limit, - } => to_binary(&query_allowed_assets(deps, start_after, limit)?), + QueryMsg::AllowedVaults { start_after, limit } => { + to_binary(&query_allowed_vaults(deps, start_after, limit)?) + } + QueryMsg::AllowedAssets { start_after, limit } => { + to_binary(&query_allowed_assets(deps, start_after, limit)?) + } } } @@ -83,7 +86,7 @@ fn query_allowed_vaults( Some(addr_str) => { addr = deps.api.addr_validate(addr_str)?; Some(Bound::exclusive(addr)) - }, + } None => None, }; @@ -97,19 +100,19 @@ fn query_allowed_vaults( } /// NOTE: This implementation of the query function assumes the map `ALLOWED_ASSETS` only saves `true`. -/// If an asset is to be removed from the whitelist, the map must remove the correspoinding key, instead +/// If an asset is to be removed from the whitelist, the map must remove the corresponding key, instead /// of setting the value to `false`. fn query_allowed_assets( deps: Deps, start_after: Option, - limit: Option + limit: Option, ) -> StdResult> { let info: AssetInfo; let start = match &start_after { Some(unchecked) => { info = unchecked.check(deps.api, None)?; Some(Bound::exclusive(AssetInfoKey::from(info))) - }, + } None => None, }; diff --git a/contracts/credit-manager/tests/allow_list_query_test.rs b/contracts/credit-manager/tests/allow_list_query_test.rs new file mode 100644 index 000000000..4d924643a --- /dev/null +++ b/contracts/credit-manager/tests/allow_list_query_test.rs @@ -0,0 +1,293 @@ +use cosmwasm_std::Addr; +use cw_asset::AssetInfoUnchecked; +use cw_multi_test::Executor; + +use rover::{InstantiateMsg, QueryMsg}; + +use crate::helpers::{mock_app, mock_contract}; + +mod helpers; + +#[test] +fn test_pagination_on_allowed_vaults_query_works() { + let mut app = mock_app(); + let code_id = app.store_code(mock_contract()); + let owner = Addr::unchecked("owner"); + + let allowed_vaults = vec![ + String::from("addr1"), + String::from("addr2"), + String::from("addr3"), + String::from("addr4"), + String::from("addr5"), + String::from("addr6"), + String::from("addr7"), + String::from("addr8"), + String::from("addr9"), + String::from("addr10"), + String::from("addr11"), + String::from("addr12"), + String::from("addr13"), + String::from("addr14"), + String::from("addr15"), + String::from("addr16"), + String::from("addr17"), + String::from("addr18"), + String::from("addr19"), + String::from("addr20"), + String::from("addr21"), + String::from("addr22"), + String::from("addr23"), + String::from("addr24"), + String::from("addr25"), + String::from("addr26"), + String::from("addr27"), + String::from("addr28"), + String::from("addr29"), + String::from("addr30"), + String::from("addr31"), + String::from("addr32"), + ]; + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: allowed_vaults.clone(), + allowed_assets: vec![], + }; + + let contract_addr = app + .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) + .unwrap(); + + let vaults_res: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: None, + limit: Some(58 as u32), + }, + ) + .unwrap(); + + // Assert maximum is observed + assert_eq!(vaults_res.len(), 30); + + let vaults_res: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: None, + limit: Some(2 as u32), + }, + ) + .unwrap(); + + // Assert limit request is observed + assert_eq!(vaults_res.len(), 2); + + let vaults_res_a: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + let vaults_res_b: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: Some(vaults_res_a.last().unwrap().clone()), + limit: None, + }, + ) + .unwrap(); + + let vaults_res_c: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: Some(vaults_res_b.last().unwrap().clone()), + limit: None, + }, + ) + .unwrap(); + + let vaults_res_d: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: Some(vaults_res_c.last().unwrap().clone()), + limit: None, + }, + ) + .unwrap(); + + // Assert default is observed + assert_eq!(vaults_res_a.len(), 10); + assert_eq!(vaults_res_b.len(), 10); + assert_eq!(vaults_res_c.len(), 10); + + assert_eq!(vaults_res_d.len(), 2); + + let combined: Vec = vaults_res_a + .iter() + .cloned() + .chain(vaults_res_b.iter().cloned()) + .chain(vaults_res_c.iter().cloned()) + .chain(vaults_res_d.iter().cloned()) + .collect(); + + assert_eq!(combined.len(), allowed_vaults.len()); + assert!(allowed_vaults.iter().all(|item| combined.contains(item))); +} + +#[test] +fn test_pagination_on_allowed_assets_query_works() { + let mut app = mock_app(); + let code_id = app.store_code(mock_contract()); + let owner = Addr::unchecked("owner"); + + let allowed_assets = vec![ + AssetInfoUnchecked::Native(String::from("native_asset_1")), + AssetInfoUnchecked::Native(String::from("native_asset_2")), + AssetInfoUnchecked::Native(String::from("native_asset_3")), + AssetInfoUnchecked::Native(String::from("native_asset_4")), + AssetInfoUnchecked::Native(String::from("native_asset_5")), + AssetInfoUnchecked::Cw20(String::from("cw_token_1")), + AssetInfoUnchecked::Cw20(String::from("cw_token_2")), + AssetInfoUnchecked::Cw20(String::from("cw_token_3")), + AssetInfoUnchecked::Cw20(String::from("cw_token_4")), + AssetInfoUnchecked::Cw20(String::from("cw_token_5")), + AssetInfoUnchecked::Cw20(String::from("cw_token_6")), + AssetInfoUnchecked::Cw20(String::from("cw_token_7")), + AssetInfoUnchecked::Cw20(String::from("cw_token_8")), + AssetInfoUnchecked::Cw20(String::from("cw_token_9")), + AssetInfoUnchecked::Native(String::from("native_asset_6")), + AssetInfoUnchecked::Native(String::from("native_asset_7")), + AssetInfoUnchecked::Cw20(String::from("cw_token_10")), + AssetInfoUnchecked::Cw20(String::from("cw_token_11")), + AssetInfoUnchecked::Cw20(String::from("cw_token_12")), + AssetInfoUnchecked::Cw20(String::from("cw_token_13")), + AssetInfoUnchecked::Cw20(String::from("cw_token_14")), + AssetInfoUnchecked::Native(String::from("native_asset_8")), + AssetInfoUnchecked::Native(String::from("native_asset_9")), + AssetInfoUnchecked::Native(String::from("native_asset_10")), + AssetInfoUnchecked::Cw20(String::from("cw_token_15")), + AssetInfoUnchecked::Cw20(String::from("cw_token_16")), + AssetInfoUnchecked::Cw20(String::from("cw_token_17")), + AssetInfoUnchecked::Cw20(String::from("cw_token_18")), + AssetInfoUnchecked::Cw20(String::from("cw_token_19")), + AssetInfoUnchecked::Cw20(String::from("cw_token_20")), + AssetInfoUnchecked::Native(String::from("native_asset_11")), + AssetInfoUnchecked::Native(String::from("native_asset_12")), + ]; + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_assets: allowed_assets.clone(), + }; + + let contract_addr = app + .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) + .unwrap(); + + let assets_res: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedAssets { + start_after: None, + limit: Some(58 as u32), + }, + ) + .unwrap(); + + // Assert maximum is observed + assert_eq!(assets_res.len(), 30); + + let assets_res: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedAssets { + start_after: None, + limit: Some(2 as u32), + }, + ) + .unwrap(); + + // Assert limit request is observed + assert_eq!(assets_res.len(), 2); + + let assets_res_a: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedAssets { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + let assets_res_b: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedAssets { + start_after: Some(assets_res_a.last().unwrap().clone()), + limit: None, + }, + ) + .unwrap(); + + let assets_res_c: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedAssets { + start_after: Some(assets_res_b.last().unwrap().clone()), + limit: None, + }, + ) + .unwrap(); + + let assets_res_d: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedAssets { + start_after: Some(assets_res_c.last().unwrap().clone()), + limit: None, + }, + ) + .unwrap(); + + // Assert default is observed + assert_eq!(assets_res_a.len(), 10); + assert_eq!(assets_res_b.len(), 10); + assert_eq!(assets_res_c.len(), 10); + + assert_eq!(assets_res_d.len(), 2); + + let combined: Vec = assets_res_a + .iter() + .cloned() + .chain(assets_res_b.iter().cloned()) + .chain(assets_res_c.iter().cloned()) + .chain(assets_res_d.iter().cloned()) + .collect(); + + assert_eq!(combined.len(), allowed_assets.len()); + assert!(allowed_assets.iter().all(|item| combined.contains(item))); +} diff --git a/contracts/credit-manager/tests/instantiate_tests.rs b/contracts/credit-manager/tests/instantiate_test.rs similarity index 50% rename from contracts/credit-manager/tests/instantiate_tests.rs rename to contracts/credit-manager/tests/instantiate_test.rs index 9ee363207..12d5028ba 100644 --- a/contracts/credit-manager/tests/instantiate_tests.rs +++ b/contracts/credit-manager/tests/instantiate_test.rs @@ -1,8 +1,8 @@ use cosmwasm_std::Addr; -use cw_asset::AssetInfo; +use cw_asset::AssetInfoUnchecked; use cw_multi_test::Executor; -use fields::messages::{AllowListsResponse, InstantiateMsg, OwnerResponse, QueryMsg}; +use rover::{InstantiateMsg, QueryMsg}; use crate::helpers::{mock_app, mock_contract}; @@ -20,13 +20,16 @@ fn test_owner_set_on_instantiate() { allowed_assets: vec![], }; - let contract_addr = - app.instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None).unwrap(); + let contract_addr = app + .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) + .unwrap(); - let res: OwnerResponse = - app.wrap().query_wasm_smart(contract_addr.clone(), &QueryMsg::GetOwner {}).unwrap(); + let res: String = app + .wrap() + .query_wasm_smart(contract_addr.clone(), &QueryMsg::Owner {}) + .unwrap(); - assert_eq!(owner, res.owner); + assert_eq!(owner, res); } #[test] @@ -42,10 +45,10 @@ fn test_allowed_vaults_and_assets_stored_on_instantiate() { ]; let allowed_assets = vec![ - AssetInfo::Native(String::from("uosmo")), - AssetInfo::Cw20(Addr::unchecked("osmo85wwjycfxjlaxsae9asmxlk3bsgxbw")), - AssetInfo::Cw20(Addr::unchecked("osmompbtkt3jezatztteo577lxkqbkdyke")), - AssetInfo::Cw20(Addr::unchecked("osmos6kmpxz9xcstleqnu2fnz8gskgf6gx")), + AssetInfoUnchecked::Native(String::from("uosmo")), + AssetInfoUnchecked::Cw20(String::from("osmo85wwjycfxjlaxsae9asmxlk3bsgxbw")), + AssetInfoUnchecked::Cw20(String::from("osmompbtkt3jezatztteo577lxkqbkdyke")), + AssetInfoUnchecked::Cw20(String::from("osmos6kmpxz9xcstleqnu2fnz8gskgf6gx")), ]; let msg = InstantiateMsg { @@ -54,17 +57,37 @@ fn test_allowed_vaults_and_assets_stored_on_instantiate() { allowed_assets: allowed_assets.clone(), }; - let contract_addr = - app.instantiate_contract(code_id, owner, &msg, &[], "mock-contract", None).unwrap(); - - let res: AllowListsResponse = - app.wrap().query_wasm_smart(contract_addr.clone(), &QueryMsg::GetAllowLists {}).unwrap(); - - assert_eq!(res.vaults.len(), 3); - assert_eq!(allowed_vaults, res.vaults); - - assert_eq!(res.assets.len(), 4); - assert!(allowed_assets.iter().all(|item| res.assets.contains(item))); + let contract_addr = app + .instantiate_contract(code_id, owner, &msg, &[], "mock-contract", None) + .unwrap(); + + let assets_res: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedAssets { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(assets_res.len(), 4); + assert!(allowed_assets.iter().all(|item| assets_res.contains(item))); + + let vaults_res: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(vaults_res.len(), 3); + assert_eq!(allowed_vaults, vaults_res); } #[test] @@ -75,7 +98,7 @@ fn test_panics_on_invalid_instantiation_addrs() { let msg = InstantiateMsg { owner: owner.to_string(), - allowed_vaults: vec![String::from("123INVALID")], + allowed_vaults: vec![String::from("%%%INVALID%%%")], allowed_assets: vec![], }; @@ -90,7 +113,7 @@ fn test_panics_on_invalid_instantiation_addrs() { let msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], - allowed_assets: vec![AssetInfo::Cw20(Addr::unchecked("123INVALID"))], + allowed_assets: vec![AssetInfoUnchecked::Cw20(String::from("AA"))], // Because cw-asset lowercases before passing to validate, in the test env, two letter strings is only one that triggers a fail }; let instantiate_res = From 148ac44a7fb1b76722eb127bd7bc49225cfee976 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 29 Jun 2022 16:08:35 +0000 Subject: [PATCH 025/218] Fixing e2e tests --- scripts/tests/instantiate.test.ts | 28 ++++++++++++++++------------ scripts/utils/types.ts | 7 +------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/scripts/tests/instantiate.test.ts b/scripts/tests/instantiate.test.ts index f94f1cf1c..14e00f9b3 100644 --- a/scripts/tests/instantiate.test.ts +++ b/scripts/tests/instantiate.test.ts @@ -1,12 +1,12 @@ import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; +import { sha256 } from '@cosmjs/crypto'; import { toHex } from '@cosmjs/encoding'; import fs from 'fs'; import path from 'path'; +import { getCosmWasmClient } from '../utils/client'; import { Network, networks } from '../utils/config'; import { testWallet1 } from '../utils/test-wallets'; -import { getCosmWasmClient } from '../utils/client'; -import { sha256 } from '@cosmjs/crypto'; -import { GetAllowListResponse, serializeAssetInfo } from '../utils/types'; +import { AssetInfo, serializeAssetInfo } from '../utils/types'; describe('instantiating fields contract', () => { let client: SigningCosmWasmClient; @@ -47,7 +47,7 @@ describe('instantiating fields contract', () => { 'osmo1av54qcmavhjkqsd67cf6f4cedqjrdeh73k52l2', 'osmo18zhhdrjd5qfvewnu5lkkgv6w7rtcmzh3hq7qes', ]; - const allowed_assets = [ + const allowed_assets: AssetInfo[] = [ { cw20: 'osmo1ptlhw66xg7nznag8sy4mnlsj04xklxqjgqrpz4' }, { native: 'uosmo' }, { cw20: 'osmo1ewn73qp0aqrtya38p0nv5c2xsshdea7ad34qkc' }, @@ -63,19 +63,23 @@ describe('instantiating fields contract', () => { contractAddr = contractAddress; expect(contractAddr).toBeDefined(); - const ownerFromQuery = await client.queryContractSmart(contractAddress, { get_owner: {} }); - expect(ownerFromQuery).toEqual({ owner }); + const ownerFromQuery = await client.queryContractSmart(contractAddress, { owner: {} }); + expect(ownerFromQuery).toEqual(owner); - const allowListsFromQuery: GetAllowListResponse = await client.queryContractSmart(contractAddress, { - get_allow_lists: {}, + const allowedVaultsFromQuery: string[] = await client.queryContractSmart(contractAddress, { + allowed_vaults: {}, }); - expect(allowListsFromQuery.vaults.length).toEqual(allowed_vaults.length); - expect(allowListsFromQuery.vaults.every((v) => allowed_vaults.includes(v))).toBeTruthy(); + expect(allowedVaultsFromQuery.length).toEqual(allowed_vaults.length); + expect(allowedVaultsFromQuery.every((v) => allowed_vaults.includes(v))).toBeTruthy(); + + const allowedAssetsFromQuery: AssetInfo[] = await client.queryContractSmart(contractAddress, { + allowed_assets: {}, + }); - expect(allowListsFromQuery.assets.length).toEqual(allowed_assets.length); + expect(allowedAssetsFromQuery.length).toEqual(allowed_assets.length); expect( - allowListsFromQuery.assets + allowedAssetsFromQuery .map(serializeAssetInfo) .every((asset_str) => allowed_assets.map(serializeAssetInfo).includes(asset_str)), ).toBeTruthy(); diff --git a/scripts/utils/types.ts b/scripts/utils/types.ts index da1896ca8..e0ddbe62f 100644 --- a/scripts/utils/types.ts +++ b/scripts/utils/types.ts @@ -1,7 +1,2 @@ export type AssetInfo = { cw20: string } | { native: string }; -export const serializeAssetInfo = (obj: AssetInfo) => Object.entries(obj).flat().join(':'); - -export interface GetAllowListResponse { - vaults: string[]; - assets: AssetInfo[]; -} +export const serializeAssetInfo = (obj: AssetInfo) => Object.entries(obj).flat().join(':'); \ No newline at end of file From c81e889dbf684619fe02222273c9ae45cae3262a Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 24 Jun 2022 16:34:31 +0200 Subject: [PATCH 026/218] User can create credit account --- Cargo.lock | 55 ++++ README.md | 288 ++++++++++++++++-- contracts/account-nft/Cargo.toml | 22 ++ contracts/account-nft/src/contract.rs | 59 ++++ contracts/account-nft/src/lib.rs | 4 + contracts/account-nft/src/tests.rs | 124 ++++++++ contracts/credit-manager/Cargo.toml | 4 + contracts/credit-manager/src/contract.rs | 118 ++----- contracts/credit-manager/src/execute.rs | 21 ++ contracts/credit-manager/src/instantiate.rs | 78 +++++ contracts/credit-manager/src/lib.rs | 4 + contracts/credit-manager/src/query.rs | 70 +++++ contracts/credit-manager/src/state.rs | 1 + .../tests/allow_list_query_test.rs | 28 +- .../tests/create_credit_account_test.rs | 78 +++++ contracts/credit-manager/tests/helpers.rs | 13 +- .../credit-manager/tests/instantiate_test.rs | 100 +++++- packages/rover/src/lib.rs | 8 +- scripts/tests/instantiate.test.ts | 64 ++-- 19 files changed, 989 insertions(+), 150 deletions(-) create mode 100644 contracts/account-nft/Cargo.toml create mode 100644 contracts/account-nft/src/contract.rs create mode 100644 contracts/account-nft/src/lib.rs create mode 100644 contracts/account-nft/src/tests.rs create mode 100644 contracts/credit-manager/src/execute.rs create mode 100644 contracts/credit-manager/src/instantiate.rs create mode 100644 contracts/credit-manager/src/query.rs create mode 100644 contracts/credit-manager/tests/create_credit_account_test.rs diff --git a/Cargo.lock b/Cargo.lock index 92bde5e87..725f35090 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,19 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "account-nft" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw721", + "cw721-base", + "schemars", + "serde", +] + [[package]] name = "anyhow" version = "1.0.58" @@ -81,6 +94,16 @@ dependencies = [ "syn", ] +[[package]] +name = "cosmwasm-schema" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" +dependencies = [ + "schemars", + "serde_json", +] + [[package]] name = "cosmwasm-std" version = "1.0.0" @@ -121,11 +144,15 @@ dependencies = [ name = "credit-manager" version = "0.1.0" dependencies = [ + "account-nft", "cosmwasm-std", "cw-asset", "cw-multi-test", "cw-storage-plus", + "cw-utils", "cw2", + "cw721", + "cw721-base", "rover", "schemars", "serde", @@ -251,6 +278,34 @@ dependencies = [ "serde", ] +[[package]] +name = "cw721" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b9fd71276795554c35899bb3a378561ed0c288d231113e9915f6ee1f42b7b5" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw721-base" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b61200af4e027af2d7485dbdc37c2a9c4093b6b2f2b811732329ef456076f97e" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "cw2", + "cw721", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "der" version = "0.5.1" diff --git a/README.md b/README.md index deef1685d..99b07a91a 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,295 @@ -# Fields of Mars: Credit Manager +# Rover +A generalized credit protocol built on Mars lending market ## Bug bounty ## Overview -## Development +DeFi lending protocols, such as Aave and Compound, typically require users to first deposit some collateral assets before they can borrow. Once deposited, this collateral is locked inside the lending market smart contracts; the users are not allowed to put them into productive use as they see fit. + +Rover takes a different approach for Mars protocol, which utilizes a generalized credit account manager built on top of Mars' Red Bank. This approach will allow borrowers to retain control of their collateral assets, while at the same time providing a return for lenders. + +### Credit manager and credit accounts + + +The target audience of the credit manager is risk-seeking investors who wish to undertake leveraged trading or yield farming activities. + +To start, a user first needs to access the Mars credit manager contract and request the opening of a credit account. The credit account is analogous to a "sub-account" on centralized trading platforms such as FTX, and is represented by a non-fungible token (NFT). + +Users interact with their credit accounts by executing actions. In Rust/[CosmWasm](https://cosmwasm.com/) code, this can be expressed as (using the [cw-asset](https://github.com/mars-protocol/cw-asset) library): + +```rust +use cosmwasm_std::Uint128; +use cw_asset::{Asset, AssetList, AssetInfo}; + +enum Action { + /// deposit the specified asset into the credit account + Deposit(Asset), + /// withdraw the specified asset from the credit account + Withdraw(Asset), + /// borrow the specified asset from Red Bank + Borrow(Asset), + /// repay the specified asset to Red Bank + Repay(Asset), + /// swap the asset in the credit account + Swap { + offer: Asset, + ask: AssetInfo, + minimum_receive: Option, + }, + /// deposit assets into a vault (e.g. an automated + /// yield farming strategy) + EnterVault { + vault_addr: String, + deposits: AssetList, + }, + /// withdraw assets from a vault + ExitVault { + vault_addr: String, + shares: Uint128, + }, +} + +enum ExecuteMsg { + /// mint a new credit account NFT + CreateCreditAccount { + initial_deposits: AssetList, + }, + /// update a credit account specified by the token id + /// by executing an array of actions + UpdateCreditAccount { + token_id: String, + actions: Vec, + }, +} + +``` + +The credit manager contract executes the list of actions specified by `ExecuteMsg::UpdateCreditAccount { actions }` in order. *After all actions have been executed, it calculates the overall health factor of the credit account. If the account is unhealthy as a result of the actions, an error is thrown and all actions reverted.* + +Some readers might have noticed that this design resembles [the Fields of Mars contract](https://github.com/mars-protocol/fields-of-mars/blob/v1.0.0/packages/fields-of-mars/src/martian_field.rs#L264-L318) in Mars V1. Indeed, the credit account can be considered a direct extension of Fields, of which many code components can be reused. + +### Deposit and borrowing + + +In the example below, the user funds a freshly-opened credit account with 50 USDC, and borrows 100 USDC from Mars liquidity pool. To do this, provide the following execute message: + +```json +{ + "update_credit_account": { + "token_id": "...", + "actions": [ + { + "deposit": { + "info": { + "native": "uusdc" + }, + "amount": "50000000" + } + }, + { + "borrow": { + "info": { + "native": "uusdc" + }, + "amount": "100000000" + } + } + ] + } +} + +``` + +The actions results in the following credit account position: + +[![Screen Shot 2022-06-24 at 12.43.09 AM](https://aws1.discourse-cdn.com/standard17/uploads/mars/optimized/1X/c12fca3fae645092b9deba67c9398fe0795b0c38_2_690x280.png) + +This example highlights a few characteristics of the credit account: + +1. The credit manager does not need to explicitly deposit collateral into Red Bank before borrowing from it. In other words, the loan is extended to the credit manager in the form of uncollateralized debt. (*NOTE: Martian Council must have approved an uncollateralized limit*) +2. The user borrows more assets ($100) than their deposit ($50). To our knowledge, this is not possible with other lending protocols. Despite this, the credit account remains over-collateralized ($150 in assets vs $100 in liabilities). +3. A production-ready credit manager contract will probably need to use a more sophisticated algorithm to assess the health of credit accounts, which takes into consideration various risk factors such as the volatility and market liquidity of each supported asset. In this article, we use LTV for simplicity sake. + +### Leveraged trading + +Credit accounts support trading of assets. Continuing from the previous example, the user may provide the following execute message, which swaps 50 USDC for OSMO (assuming OSMO price is ~$2): + +```json +{ + "update_credit_account": { + "token_id": "...", + "actions": [ + { + "swap": { + "offer": { + "info": { + "native": "uusdc" + }, + "amount": "50000000" + }, + "ask": { + "native": "uosmo" + }, + "minimum_receive": "25000000" + } + } + ] + } +} + +``` + +Which results in the following credit account position: + +[![Screen Shot 2022-06-24 at 12.43.17 AM](https://aws1.discourse-cdn.com/standard17/uploads/mars/optimized/1X/fee50699046039aa809df51489d5c0d4b5000636_2_690x285.png) + +Although the trade takes place on a spot exchange, since it is (partially) funded by borrowed assets from Red Bank, the user here effectively takes a leveraged long position on OSMO. -### Dependencies +### Vaults + +The credit manager may support vaults created by third party protocols, which provide automated trading or yield farming strategies, as long as these vaults are approved by Martian Council (governance), and implement a standard API. + +The API includes execute functions for entering or exiting, and query functions for assessing the asset value locked in the vault. The execute functions also must emit events in a specific format (out of scope for this article) so that they can be parsed by the credit manager. + +```rust +/// each vault contract must implement this +enum VaultExecuteMsg { + Enter { + deposits: AssetList, + }, + Exit { + shares: Uint128, + }, +} + +/// each vault contract must implement this +enum VaultQueryMsg { + /// the amount of assets under management of the vault that + /// belongs to a specific user; response type: `cw_asset::AssetList` + Deposit { + user: String, + }, +} + +``` + +For example, assume a vault that takes OSMO and USDC deposits, provides them to an Osmosis DEX pool, stakes the LP share tokens, and auto-compounds staking rewards. To enter this vault, the user executes: + +```json +{ + "update_credit_account": { + "token_id": "...", + "actions": [ + { + "enter_vault": { + "vault_addr": "...", + "deposits": [ + { + "info": { + "native": "uosmo" + }, + "amount": "25000000" + }, + { + "info": { + "native": "uusdc" + }, + "amount": "50000000" + } + ] + } + } + ] + } +} + +``` + +Which results in: + +[![Screen Shot 2022-06-24 at 12.43.24 AM](https://aws1.discourse-cdn.com/standard17/uploads/mars/optimized/1X/30e208b40a0812560bb2606260efb94035517f42_2_690x286.png) + +*NOTE: It is possible to execute the two previous steps (swap and enter vault) in one transaction, improving user experience.* + +### Liquidations + +To ensure solvency (i.e. that the protocol always has more assets than liabilities), the credit manager must monitor the health factor of each credit account, and execute liquidations when necessary. + +A credit account's health factor may drop due to: + +- the value of assets going down (e.g. prices dropping) +- the value of liabilities going up (e.g. borrow interest accrues, or prices of the debt assets going up) + +Once the health factor drops below a preset threshold (liquidation threshold, a governance-decided parameter), any person can trigger a liquidation of the credit account. The liquidator must pay back some amounts of debts on behalf of the credit account; they will in return be rewarded some of the account's assets as the liquidation bonus: + +[![Screen Shot 2022-06-24 at 12.46.36 AM](https://aws1.discourse-cdn.com/standard17/uploads/mars/optimized/1X/a5c0da751deceb28652bbd82127c41ee2c01d507_2_493x500.png) + +The liquidator may then sell the vault shares in the secondary market. + +In this example, the user account's net value falls from $20 ($120 in assets minus $100 in liabilities) to $15 (losing $5), while the liquidator's account net value increases by $5. The transfer of this $5 is the liquidation bonus, rewarded to the liquidator for deploying capital to ensure the protocol's solvency. The maximum allowed amount of bonus for each liquidation event, the bonus rate, can either be set by governance (i.e. the approach used by Aave) or by [free market mechanisms 2](https://twitter.com/larry0x/status/1538515908049747971) ([Euler](https://twitter.com/euler_mab/status/1537091423748517889)). + +### Additional thoughts + +A generalized credit protocol would enable leveraged trading or yield farming capabilities that are largely available only on centralized exchanges today. End users would be able to borrow more than they've deposited into the lending protocol while still ensuring all credit accounts are fully collateralized. Mars Hub's decentralized architecture would also give third parties the ability to propose and write new trading and yield farming strategies that could tap Red Bank deposits on any supported blockchain. The proposed protocol would increase utility for traders. This new source of demand may generate higher yields for depositors and more fees for Mars Hub and MARS stakers. + +## Development ### Environment Setup -Docker -https://docs.docker.com/get-docker/ -v8 +#### ==== For building/testing contracts in Rust === -Osmosisd -Select option 3 (localosmosis), the installer will configure everything for you. -The osmosisd dameon on your local computer is used to communicate with the localosmosis daemin running inside the Docker container. -https://get.osmosis.zone/ +[Install rustup](https://rustup.rs/). Once installed, make sure you have the wasm32 target: +```shell +rustup default stable +cargo version +# If this is lower than 1.55.0+, update +rustup update stable +rustup target list --installed +rustup target add wasm32-unknown-unknown +``` -install localosmosis -https://docs.osmosis.zone/developing/tools/localosmosis.html#install-localosmosis +Run `cargo build` and you're good to go! -cd localOsmosis -make start +#### ==== For end-to-end tests and deployment scripts === -now creating blocks + - Dependencies + - [Docker](https://docs.docker.com/get-docker/) + - [Node.js v16](https://github.com/nvm-sh/nvm) + - [LocalOsmosis](https://docs.osmosis.zone/developing/dapps/get_started/cosmwasm-localosmosis.html#initial-setup) + - Intel/Amd 64-bit processor + - while there is experimental Arm support for CosmWasm/rust-optimizer, it's discouraged to use in production and LocalOsmosis is likely to have issues. +### Test -osmosjs? +For contract tests in rust +```shell +cargo test +``` +For end-to-end tests via Typescript. Start LocalOsmosis: +```shell +cd LocalOsmosis +make start +``` -cargo wasm? +In another shell, compile contracts +```shell +# In rover directory at the top level docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ cosmwasm/workspace-optimizer:0.12.6 +``` +Run test scripts -SCRIPTS +```shell +cd scripts npm install - - -### Test +npm run test +``` ### Deploy diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml new file mode 100644 index 000000000..f501aac68 --- /dev/null +++ b/contracts/account-nft/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "account-nft" +version = "1.0.0" +authors = ["larry_0x , grod220 "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] + +[dependencies] +cw721 = "0.13" +cw721-base = { version = "0.13", features = ["library"] } +cosmwasm-std = "1.0" +schemars = "0.8" +serde = { version = "1.0", default-features = false, features = ["derive"] } + +[dev-dependencies] +cosmwasm-schema = "1.0" +cw-multi-test = "0.13" \ No newline at end of file diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs new file mode 100644 index 000000000..5c300445d --- /dev/null +++ b/contracts/account-nft/src/contract.rs @@ -0,0 +1,59 @@ +use cosmwasm_std::{ + entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +}; +use cw721::NumTokensResponse; +use cw721_base::{ + ContractError, Cw721Contract, ExecuteMsg, Extension, InstantiateMsg, MintMsg, QueryMsg, +}; + +// Extending CW721 base contract +pub type Parent<'a> = Cw721Contract<'a, Extension, Empty>; + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + Parent::default().instantiate(deps, env, info, msg) +} + +#[entry_point] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Mint(mint_msg) => mint_override(deps, env, info, mint_msg), + _ => Parent::default().execute(deps, env, info, msg), + } +} + +fn mint_override( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: MintMsg, +) -> Result { + let parent = Parent::default(); + + let num_tokens: NumTokensResponse = + deps.querier.query_wasm_smart(&env.contract.address, &QueryMsg::NumTokens {})?; + + let mint_msg_override = MintMsg { + token_id: (num_tokens.count + 1).to_string(), + owner: msg.owner, + token_uri: None, + extension: None, + }; + + parent.mint(deps, env, info, mint_msg_override) +} + +#[entry_point] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + Parent::default().query(deps, env, msg) +} diff --git a/contracts/account-nft/src/lib.rs b/contracts/account-nft/src/lib.rs new file mode 100644 index 000000000..7380e09bd --- /dev/null +++ b/contracts/account-nft/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; + +#[cfg(test)] +mod tests; diff --git a/contracts/account-nft/src/tests.rs b/contracts/account-nft/src/tests.rs new file mode 100644 index 000000000..373505de9 --- /dev/null +++ b/contracts/account-nft/src/tests.rs @@ -0,0 +1,124 @@ +use cosmwasm_std::Addr; +use cw721::OwnerOfResponse; +use cw721_base::{ExecuteMsg, Extension, InstantiateMsg, MintMsg, QueryMsg}; +use cw_multi_test::{App, AppResponse, BasicApp, ContractWrapper, Executor}; +use std::fmt::Error; + +use crate::contract::{execute, instantiate, query}; + +#[test] +fn test_id_incrementer() { + let mut app = App::default(); + let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); + let owner = Addr::unchecked("owner"); + let code_id = app.store_code(contract); + let contract_addr = instantiate_mock_nft_contract(&mut app, &owner, code_id); + + let user_1 = Addr::unchecked("user_1"); + let res = mint_action(&mut app, &owner, &contract_addr, &user_1).unwrap(); + let token_id = get_token_id(res); + assert_eq!(token_id, "1"); + assert_owner_is_correct(&mut app, &contract_addr, &user_1, &token_id); + + let user_2 = Addr::unchecked("user_2"); + let res = mint_action(&mut app, &owner, &contract_addr, &user_2).unwrap(); + let token_id = get_token_id(res); + assert_eq!(token_id, "2"); + assert_owner_is_correct(&mut app, &contract_addr, &user_2, &token_id); + + let user_3 = Addr::unchecked("user_3"); + let res = mint_action(&mut app, &owner, &contract_addr, &user_3).unwrap(); + let token_id = get_token_id(res); + assert_eq!(token_id, "3"); + assert_owner_is_correct(&mut app, &contract_addr, &user_3, &token_id); +} + +#[test] +fn test_only_owner_can_mint() { + let mut app = App::default(); + let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); + let owner = Addr::unchecked("owner"); + let code_id = app.store_code(contract); + let contract_addr = instantiate_mock_nft_contract(&mut app, &owner, code_id); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = mint_action(&mut app, &bad_guy, &contract_addr, &bad_guy); + match res { + Ok(_) => panic!("Should have thrown an error"), + Err(_) => {} + } +} + +// Double checking ownership by querying NFT account-nft for correct owner +fn assert_owner_is_correct( + app: &mut BasicApp, + contract_addr: &Addr, + user: &Addr, + token_id: &String, +) { + let owner_res: OwnerOfResponse = app + .wrap() + .query_wasm_smart( + contract_addr, + &QueryMsg::OwnerOf { + token_id: token_id.clone(), + include_expired: None, + }, + ) + .unwrap(); + + assert_eq!(user.to_string(), owner_res.owner) +} + +fn instantiate_mock_nft_contract(app: &mut BasicApp, owner: &Addr, code_id: u64) -> Addr { + let contract_addr = app + .instantiate_contract( + code_id, + owner.clone(), + &InstantiateMsg { + name: String::from("mock_nft"), + symbol: String::from("MOCK"), + minter: owner.to_string(), + }, + &[], + "mock-account-nft", + None, + ) + .unwrap(); + contract_addr +} + +fn mint_action( + app: &mut BasicApp, + owner: &Addr, + contract_addr: &Addr, + user: &Addr, +) -> Result { + app.execute_contract( + owner.clone(), + contract_addr.clone(), + &ExecuteMsg::Mint { + 0: MintMsg { + token_id: String::from("some_token_id_that_will_be_ignored"), + owner: user.to_string(), + token_uri: None, + extension: Extension::None, + }, + }, + &[], + ) + .map_err(|_| Error::default()) +} + +fn get_token_id(res: AppResponse) -> String { + let attr: Vec<&String> = res + .events + .iter() + .flat_map(|event| &event.attributes) + .filter(|attr| attr.key == "token_id") + .map(|attr| &attr.value) + .collect(); + + assert_eq!(attr.len(), 1); + attr.first().unwrap().to_string() +} diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 79960d3b5..81fe25066 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -13,11 +13,15 @@ library = [] [dependencies] rover = { version = "0.1", path = "../../packages/rover" } +account-nft = { version = "1.0.0", path = "../account-nft" } cosmwasm-std = "1.0" cw2 = "0.13" cw-asset = "2.1" cw-storage-plus = "0.13" +cw-utils = "0.13" +cw721 = "0.13" +cw721-base = "0.13" schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 454644a31..8b9a029d6 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -1,59 +1,56 @@ -use std::convert::TryFrom; - use cosmwasm_std::{ - entry_point, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, + entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError, StdResult, }; use cw2::set_contract_version; -use cw_asset::{AssetInfo, AssetInfoKey, AssetInfoUnchecked}; -use cw_storage_plus::Bound; use rover::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; +use crate::execute::try_create_credit_account; +use crate::instantiate::{ + instantiate_nft_contract, store_config, store_nft_contract_addr, + NFT_CONTRACT_INSTANTIATE_REPLY_ID, +}; +use crate::query::{ + query_allowed_assets, query_allowed_vaults, query_nft_contract_addr, query_owner, +}; const CONTRACT_NAME: &str = "crates.io:rover-credit-manager"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -const MAX_LIMIT: u32 = 30; -const DEFAULT_LIMIT: u32 = 10; - #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, - _env: Env, + env: Env, _info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - let owner = deps.api.addr_validate(&msg.owner)?; - OWNER.save(deps.storage, &owner)?; - - store_allow_lists(deps, msg)?; - - Ok(Response::new()) -} - -fn store_allow_lists(deps: DepsMut, msg: InstantiateMsg) -> StdResult<()> { - msg.allowed_vaults.iter().try_for_each(|vault| { - ALLOWED_VAULTS.save(deps.storage, deps.api.addr_validate(vault)?, &true) - })?; - - msg.allowed_assets.iter().try_for_each(|info| { - ALLOWED_ASSETS.save(deps.storage, info.check(deps.api, None)?.into(), &true) - })?; - Ok(()) + store_config(deps, &msg)?; + let sub_message = instantiate_nft_contract(msg.nft_contract_code_id, msg.owner, env)?; + Ok(Response::new() + .add_submessage(sub_message) + .add_attribute("method", "instantiate")) } #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( - _deps: DepsMut, + deps: DepsMut, _env: Env, - _info: MessageInfo, + info: MessageInfo, msg: ExecuteMsg, ) -> StdResult { - match msg {} + match msg { + ExecuteMsg::CreateCreditAccount {} => try_create_credit_account(deps, info.sender), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, _: Env, reply: Reply) -> StdResult { + match reply.id { + id if id == NFT_CONTRACT_INSTANTIATE_REPLY_ID => store_nft_contract_addr(deps, reply), + id => Err(StdError::generic_err(format!("invalid reply id: {}", id))), + } } #[cfg_attr(not(feature = "library"), entry_point)] @@ -66,63 +63,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::AllowedAssets { start_after, limit } => { to_binary(&query_allowed_assets(deps, start_after, limit)?) } + QueryMsg::CreditAccountNftAddress {} => to_binary(&query_nft_contract_addr(deps)?), } } - -fn query_owner(deps: Deps) -> StdResult { - Ok(OWNER.load(deps.storage)?.into()) -} - -/// NOTE: This implementation of the query function assumes the map `ALLOWED_VAULTS` only saves `true`. -/// If a vault is to be removed from the whitelist, the map must remove the correspoinding key, instead -/// of setting the value to `false`. -fn query_allowed_vaults( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult> { - let addr: Addr; - let start = match &start_after { - Some(addr_str) => { - addr = deps.api.addr_validate(addr_str)?; - Some(Bound::exclusive(addr)) - } - None => None, - }; - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - - ALLOWED_VAULTS - .keys(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|res| res.map(|vault_addr| vault_addr.to_string())) - .collect() -} - -/// NOTE: This implementation of the query function assumes the map `ALLOWED_ASSETS` only saves `true`. -/// If an asset is to be removed from the whitelist, the map must remove the corresponding key, instead -/// of setting the value to `false`. -fn query_allowed_assets( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult> { - let info: AssetInfo; - let start = match &start_after { - Some(unchecked) => { - info = unchecked.check(deps.api, None)?; - Some(Bound::exclusive(AssetInfoKey::from(info))) - } - None => None, - }; - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - - ALLOWED_ASSETS - .keys(deps.storage, start, None, Order::Ascending) - .take(limit) - .collect::>>()? - .into_iter() - .map(|key| AssetInfoUnchecked::try_from(key)) - .collect() -} diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs new file mode 100644 index 000000000..f8dfc24fc --- /dev/null +++ b/contracts/credit-manager/src/execute.rs @@ -0,0 +1,21 @@ +use cosmwasm_std::{to_binary, Addr, CosmosMsg, DepsMut, Response, StdResult, WasmMsg}; +use cw721_base::{ExecuteMsg, Extension, MintMsg}; + +use crate::state::CREDIT_ACCOUNT_NFT_CONTRACT; + +pub fn try_create_credit_account(deps: DepsMut, user: Addr) -> StdResult { + let contract_addr = CREDIT_ACCOUNT_NFT_CONTRACT.load(deps.storage)?; + + let nft_mint_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: contract_addr.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::Mint(MintMsg:: { + token_id: String::from("contract-will-generate"), + owner: user.to_string(), + token_uri: None, + extension: None, + }))?, + }); + + Ok(Response::new().add_message(nft_mint_msg).add_attribute("method", "CreateCreditAccount")) +} diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs new file mode 100644 index 000000000..04bea1f4d --- /dev/null +++ b/contracts/credit-manager/src/instantiate.rs @@ -0,0 +1,78 @@ +use cosmwasm_std::{ + to_binary, CosmosMsg, DepsMut, Env, Reply, Response, StdError, StdResult, SubMsg, SubMsgResult, + WasmMsg, +}; +use cw721_base::InstantiateMsg as NftInstantiateMsg; +use cw_utils::parse_instantiate_response_data; + +use rover::InstantiateMsg; + +use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, CREDIT_ACCOUNT_NFT_CONTRACT, OWNER}; + +pub const NFT_CONTRACT_INSTANTIATE_REPLY_ID: u64 = 1; + +/// Rover credit accounts are NFTs +pub fn instantiate_nft_contract(code_id: u64, owner: String, env: Env) -> StdResult { + let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Instantiate { + admin: Some(owner.clone()), + code_id, + msg: to_binary(&NftInstantiateMsg { + name: String::from("Rover Credit Account"), + symbol: String::from("RCA"), + minter: env.contract.address.to_string(), + })?, + funds: vec![], + label: "rover_credit_account_nft".to_string(), + }); + Ok(SubMsg::reply_on_success( + cosmos_msg, + NFT_CONTRACT_INSTANTIATE_REPLY_ID, + )) +} + +/// After successful NFT account-nft instantiation, save the account-nft address +pub fn store_nft_contract_addr(deps: DepsMut, reply: Reply) -> StdResult { + let contract_str = parse_reply_for_contract_addr(reply)?; + let contract_addr = deps.api.addr_validate(&contract_str)?; + CREDIT_ACCOUNT_NFT_CONTRACT.save(deps.storage, &contract_addr)?; + Ok(Response::new()) +} + +fn parse_reply_for_contract_addr(reply: Reply) -> StdResult { + return match reply.result { + SubMsgResult::Ok(res) => match res.data { + None => Err(StdError::generic_err( + "Submessage did not have data to parse", + )), + Some(data) => { + let parsed = parse_instantiate_response_data(&data) + .map_err(|_| StdError::generic_err("Could not parse binary response data"))?; + Ok(parsed.contract_address) + } + }, + SubMsgResult::Err(err) => Err(StdError::generic_err(err)), + }; +} + +pub fn store_config(mut deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { + store_owner(&mut deps, msg)?; + store_assets_and_vaults(&mut deps, msg)?; + Ok(()) +} + +fn store_owner(deps: &mut DepsMut, msg: &InstantiateMsg) -> StdResult<()> { + let owner = deps.api.addr_validate(&msg.owner)?; + OWNER.save(deps.storage, &owner)?; + Ok(()) +} + +fn store_assets_and_vaults(deps: &mut DepsMut, msg: &InstantiateMsg) -> StdResult<()> { + msg.allowed_vaults.iter().try_for_each(|vault| { + ALLOWED_VAULTS.save(deps.storage, deps.api.addr_validate(vault)?, &true) + })?; + + msg.allowed_assets.iter().try_for_each(|info| { + ALLOWED_ASSETS.save(deps.storage, info.check(deps.api, None)?.into(), &true) + })?; + Ok(()) +} diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 3407c199d..cf3255bea 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -1,2 +1,6 @@ pub mod contract; + +pub mod execute; +pub mod instantiate; +pub mod query; pub mod state; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs new file mode 100644 index 000000000..2d0d13339 --- /dev/null +++ b/contracts/credit-manager/src/query.rs @@ -0,0 +1,70 @@ +use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, CREDIT_ACCOUNT_NFT_CONTRACT, OWNER}; +use cosmwasm_std::{Addr, Deps, Order, StdResult}; +use cw_asset::{AssetInfo, AssetInfoKey, AssetInfoUnchecked}; +use cw_storage_plus::Bound; +use std::convert::TryFrom; + +const MAX_LIMIT: u32 = 30; +const DEFAULT_LIMIT: u32 = 10; + +pub fn query_nft_contract_addr(deps: Deps) -> StdResult { + Ok(CREDIT_ACCOUNT_NFT_CONTRACT.load(deps.storage)?.into()) +} + +pub fn query_owner(deps: Deps) -> StdResult { + Ok(OWNER.load(deps.storage)?.into()) +} + +/// NOTE: This implementation of the query function assumes the map `ALLOWED_VAULTS` only saves `true`. +/// If a vault is to be removed from the whitelist, the map must remove the correspoinding key, instead +/// of setting the value to `false`. +pub fn query_allowed_vaults( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult> { + let addr: Addr; + let start = match &start_after { + Some(addr_str) => { + addr = deps.api.addr_validate(addr_str)?; + Some(Bound::exclusive(addr)) + } + None => None, + }; + + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + ALLOWED_VAULTS + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|res| res.map(|vault_addr| vault_addr.to_string())) + .collect() +} + +/// NOTE: This implementation of the query function assumes the map `ALLOWED_ASSETS` only saves `true`. +/// If an asset is to be removed from the whitelist, the map must remove the corresponding key, instead +/// of setting the value to `false`. +pub fn query_allowed_assets( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult> { + let info: AssetInfo; + let start = match &start_after { + Some(unchecked) => { + info = unchecked.check(deps.api, None)?; + Some(Bound::exclusive(AssetInfoKey::from(info))) + } + None => None, + }; + + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + ALLOWED_ASSETS + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>>()? + .into_iter() + .map(|key| AssetInfoUnchecked::try_from(key)) + .collect() +} diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 2ae4d6096..431ef06fd 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -3,6 +3,7 @@ use cw_asset::AssetInfoKey; use cw_storage_plus::{Item, Map}; pub const OWNER: Item = Item::new("owner"); +pub const CREDIT_ACCOUNT_NFT_CONTRACT: Item = Item::new("credit-account-nft"); pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); diff --git a/contracts/credit-manager/tests/allow_list_query_test.rs b/contracts/credit-manager/tests/allow_list_query_test.rs index 4d924643a..c3c62b89a 100644 --- a/contracts/credit-manager/tests/allow_list_query_test.rs +++ b/contracts/credit-manager/tests/allow_list_query_test.rs @@ -4,14 +4,15 @@ use cw_multi_test::Executor; use rover::{InstantiateMsg, QueryMsg}; -use crate::helpers::{mock_app, mock_contract}; +use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract}; mod helpers; #[test] fn test_pagination_on_allowed_vaults_query_works() { let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); + let nft_contract_code_id = app.store_code(mock_account_nft_contract()); + let credit_manager_code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); let allowed_vaults = vec![ @@ -53,10 +54,18 @@ fn test_pagination_on_allowed_vaults_query_works() { owner: owner.to_string(), allowed_vaults: allowed_vaults.clone(), allowed_assets: vec![], + nft_contract_code_id, }; let contract_addr = app - .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) + .instantiate_contract( + credit_manager_code_id, + owner.clone(), + &msg, + &[], + "mock-contract", + None, + ) .unwrap(); let vaults_res: Vec = app @@ -153,7 +162,8 @@ fn test_pagination_on_allowed_vaults_query_works() { #[test] fn test_pagination_on_allowed_assets_query_works() { let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); + let nft_contract_code_id = app.store_code(mock_account_nft_contract()); + let credit_manager_code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); let allowed_assets = vec![ @@ -195,10 +205,18 @@ fn test_pagination_on_allowed_assets_query_works() { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: allowed_assets.clone(), + nft_contract_code_id, }; let contract_addr = app - .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) + .instantiate_contract( + credit_manager_code_id, + owner.clone(), + &msg, + &[], + "mock-contract", + None, + ) .unwrap(); let assets_res: Vec = app diff --git a/contracts/credit-manager/tests/create_credit_account_test.rs b/contracts/credit-manager/tests/create_credit_account_test.rs new file mode 100644 index 000000000..c58f394e9 --- /dev/null +++ b/contracts/credit-manager/tests/create_credit_account_test.rs @@ -0,0 +1,78 @@ +use cosmwasm_std::Addr; +use cw721::OwnerOfResponse; +use cw721_base::QueryMsg as NftQueryMsg; +use cw_multi_test::Executor; + +use rover::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract}; + +mod helpers; + +#[test] +fn test_create_credit_account() { + let mut app = mock_app(); + let owner = Addr::unchecked("owner"); + + let nft_contract_code_id = app.store_code(mock_account_nft_contract()); + + let credit_manager_code_id = app.store_code(mock_contract()); + let manager_initiate_msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_assets: vec![], + nft_contract_code_id, + }; + + let contract_addr = app + .instantiate_contract( + credit_manager_code_id, + owner.clone(), + &manager_initiate_msg, + &[], + "manager-mock-account-nft", + None, + ) + .unwrap(); + + let user = Addr::unchecked("some_user"); + let res = app + .execute_contract( + user.clone(), + contract_addr.clone(), + &ExecuteMsg::CreateCreditAccount {}, + &[], + ) + .unwrap(); + + let attr: Vec<&String> = res + .events + .iter() + .flat_map(|event| &event.attributes) + .filter(|attr| attr.key == "token_id") + .map(|attr| &attr.value) + .collect(); + + assert_eq!(attr.len(), 1); + let token_id = attr.first().unwrap().as_str(); + assert_eq!(token_id, "1"); + + // Double checking ownership by querying NFT account-nft for correct owner + let nft_contract_res: String = app + .wrap() + .query_wasm_smart(contract_addr.clone(), &QueryMsg::CreditAccountNftAddress {}) + .unwrap(); + + let owner_res: OwnerOfResponse = app + .wrap() + .query_wasm_smart( + nft_contract_res, + &NftQueryMsg::OwnerOf { + token_id: token_id.to_string(), + include_expired: None, + }, + ) + .unwrap(); + + assert_eq!(user, owner_res.owner) +} diff --git a/contracts/credit-manager/tests/helpers.rs b/contracts/credit-manager/tests/helpers.rs index 1e56a6b6b..9d352562e 100644 --- a/contracts/credit-manager/tests/helpers.rs +++ b/contracts/credit-manager/tests/helpers.rs @@ -1,12 +1,21 @@ use cosmwasm_std::Empty; -use credit_manager::contract::{execute, instantiate, query}; use cw_multi_test::{App, Contract, ContractWrapper}; +use account_nft::contract::{ + execute as cw721Execute, instantiate as cw721Instantiate, query as cw721Query, +}; +use credit_manager::contract::{execute, instantiate, query, reply}; + pub fn mock_app() -> App { App::default() } pub fn mock_contract() -> Box> { - let contract = ContractWrapper::new(execute, instantiate, query); + let contract = ContractWrapper::new(execute, instantiate, query).with_reply(reply); + Box::new(contract) +} + +pub fn mock_account_nft_contract() -> Box> { + let contract = ContractWrapper::new(cw721Execute, cw721Instantiate, cw721Query); Box::new(contract) } diff --git a/contracts/credit-manager/tests/instantiate_test.rs b/contracts/credit-manager/tests/instantiate_test.rs index 12d5028ba..fe99a23a5 100644 --- a/contracts/credit-manager/tests/instantiate_test.rs +++ b/contracts/credit-manager/tests/instantiate_test.rs @@ -4,24 +4,33 @@ use cw_multi_test::Executor; use rover::{InstantiateMsg, QueryMsg}; -use crate::helpers::{mock_app, mock_contract}; +use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract}; mod helpers; #[test] fn test_owner_set_on_instantiate() { let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); + let nft_contract_code_id = app.store_code(mock_account_nft_contract()); + let manager_code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); let msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![], + nft_contract_code_id, }; let contract_addr = app - .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) + .instantiate_contract( + manager_code_id, + owner.clone(), + &msg, + &[], + "mock-account-nft", + None, + ) .unwrap(); let res: String = app @@ -32,10 +41,45 @@ fn test_owner_set_on_instantiate() { assert_eq!(owner, res); } +#[test] +fn test_nft_contract_addr_set_on_instantiate() { + let mut app = mock_app(); + let owner = Addr::unchecked("owner"); + + let nft_contract_code_id = app.store_code(mock_account_nft_contract()); + + let credit_manager_code_id = app.store_code(mock_contract()); + let manager_initiate_msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_assets: vec![], + nft_contract_code_id, + }; + + let contract_addr = app + .instantiate_contract( + credit_manager_code_id, + owner.clone(), + &manager_initiate_msg, + &[], + "manager-mock-account-nft", + None, + ) + .unwrap(); + + let res: String = app + .wrap() + .query_wasm_smart(contract_addr.clone(), &QueryMsg::CreditAccountNftAddress {}) + .unwrap(); + + assert!(res.as_str().chars().count() > 0); +} + #[test] fn test_allowed_vaults_and_assets_stored_on_instantiate() { let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); + let nft_contract_code_id = app.store_code(mock_account_nft_contract()); + let credit_manager_code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); let allowed_vaults = vec![ @@ -55,10 +99,18 @@ fn test_allowed_vaults_and_assets_stored_on_instantiate() { owner: owner.to_string(), allowed_vaults: allowed_vaults.clone(), allowed_assets: allowed_assets.clone(), + nft_contract_code_id, }; let contract_addr = app - .instantiate_contract(code_id, owner, &msg, &[], "mock-contract", None) + .instantiate_contract( + credit_manager_code_id, + owner, + &msg, + &[], + "mock-credit-manager-contract", + None, + ) .unwrap(); let assets_res: Vec = app @@ -93,17 +145,25 @@ fn test_allowed_vaults_and_assets_stored_on_instantiate() { #[test] fn test_panics_on_invalid_instantiation_addrs() { let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); + let nft_contract_code_id = app.store_code(mock_account_nft_contract()); + let manager_code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); let msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![String::from("%%%INVALID%%%")], allowed_assets: vec![], + nft_contract_code_id, }; - let instantiate_res = - app.instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None); + let instantiate_res = app.instantiate_contract( + manager_code_id, + owner.clone(), + &msg, + &[], + "mock-contract", + None, + ); match instantiate_res { Err(_) => {} @@ -114,10 +174,32 @@ fn test_panics_on_invalid_instantiation_addrs() { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![AssetInfoUnchecked::Cw20(String::from("AA"))], // Because cw-asset lowercases before passing to validate, in the test env, two letter strings is only one that triggers a fail + nft_contract_code_id, + }; + + let instantiate_res = app.instantiate_contract( + manager_code_id, + owner.clone(), + &msg, + &[], + "mock-contract", + None, + ); + + match instantiate_res { + Err(_) => {} + Ok(_) => panic!("Should have thrown an error"), + } + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_assets: vec![], + nft_contract_code_id: 0, }; let instantiate_res = - app.instantiate_contract(code_id, owner, &msg, &[], "mock-contract", None); + app.instantiate_contract(manager_code_id, owner, &msg, &[], "mock-contract", None); match instantiate_res { Err(_) => {} diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index d5656279f..8b495bef3 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -7,11 +7,14 @@ pub struct InstantiateMsg { pub owner: String, pub allowed_vaults: Vec, pub allowed_assets: Vec, + pub nft_contract_code_id: u64, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub enum ExecuteMsg {} +pub enum ExecuteMsg { + CreateCreditAccount {}, +} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -27,5 +30,6 @@ pub enum QueryMsg { AllowedAssets { start_after: Option, limit: Option, - } + }, + CreditAccountNftAddress {}, } diff --git a/scripts/tests/instantiate.test.ts b/scripts/tests/instantiate.test.ts index 14e00f9b3..2afa7d72e 100644 --- a/scripts/tests/instantiate.test.ts +++ b/scripts/tests/instantiate.test.ts @@ -6,12 +6,16 @@ import path from 'path'; import { getCosmWasmClient } from '../utils/client'; import { Network, networks } from '../utils/config'; import { testWallet1 } from '../utils/test-wallets'; +import { getCosmWasmClient } from '../utils/client'; +import { sha256 } from '@cosmjs/crypto'; +import { GetAllowListResponse, GetNftAddressResponse, serializeAssetInfo } from '../utils/types'; import { AssetInfo, serializeAssetInfo } from '../utils/types'; describe('instantiating fields contract', () => { let client: SigningCosmWasmClient; - let codeId: number; - let contractAddr: string; + let managerCodeId: number; + let managerContractAddr: string; + let accountNftCodeId: number; beforeAll(async () => { client = await getCosmWasmClient(testWallet1); @@ -21,23 +25,16 @@ describe('instantiating fields contract', () => { client.disconnect(); }); - test('can be uploaded', async () => { + test('credit manager wasm can be uploaded', async () => { const wasm = fs.readFileSync(path.resolve(__dirname, '../../artifacts/credit_manager.wasm')); - const { - codeId: uploadCodeId, - originalChecksum, - originalSize, - compressedChecksum, - compressedSize, - } = await client.upload(testWallet1.address, wasm, networks[Network.OSMOSIS].defaultSendFee); - - expect(originalChecksum).toEqual(toHex(sha256(wasm))); - expect(originalSize).toEqual(wasm.length); - expect(compressedChecksum).toMatch(/^[0-9a-f]{64}$/); - expect(compressedSize).toBeLessThan(wasm.length * 0.5); - expect(uploadCodeId).toBeGreaterThanOrEqual(1); - codeId = uploadCodeId; - expect(codeId).toBeDefined(); + managerCodeId = await uploadAndAssert(client, wasm); + expect(managerCodeId).toBeDefined(); + }); + + test('account nft wasm can be uploaded', async () => { + const wasm = fs.readFileSync(path.resolve(__dirname, '../../artifacts/account_nft.wasm')); + accountNftCodeId = await uploadAndAssert(client, wasm); + expect(accountNftCodeId).toBeDefined(); }); test('can be instantiated', async () => { @@ -55,13 +52,13 @@ describe('instantiating fields contract', () => { const { contractAddress } = await client.instantiate( testWallet1.address, - codeId, - { owner, allowed_vaults, allowed_assets }, + managerCodeId, + { owner, allowed_vaults, allowed_assets, nft_contract_code_id: accountNftCodeId }, 'test-instantiate-string-123', networks[Network.OSMOSIS].defaultSendFee, ); - contractAddr = contractAddress; - expect(contractAddr).toBeDefined(); + managerContractAddr = contractAddress; + expect(managerContractAddr).toBeDefined(); const ownerFromQuery = await client.queryContractSmart(contractAddress, { owner: {} }); expect(ownerFromQuery).toEqual(owner); @@ -83,5 +80,28 @@ describe('instantiating fields contract', () => { .map(serializeAssetInfo) .every((asset_str) => allowed_assets.map(serializeAssetInfo).includes(asset_str)), ).toBeTruthy(); + + const nftAddressRes: GetNftAddressResponse = await client.queryContractSmart(contractAddress, { + get_credit_account_nft_address: {}, + }); + + expect(nftAddressRes.address).toBeDefined(); }); }); + +async function uploadAndAssert(client: SigningCosmWasmClient, wasm: Buffer) { + const { + codeId: uploadCodeId, + originalChecksum, + originalSize, + compressedChecksum, + compressedSize, + } = await client.upload(testWallet1.address, wasm, networks[Network.OSMOSIS].defaultSendFee); + + expect(originalChecksum).toEqual(toHex(sha256(wasm))); + expect(originalSize).toEqual(wasm.length); + expect(compressedChecksum).toMatch(/^[0-9a-f]{64}$/); + expect(compressedSize).toBeLessThan(wasm.length * 0.5); + expect(uploadCodeId).toBeGreaterThanOrEqual(1); + return uploadCodeId; +} From c3bd05f10ed87da60deabb2a52e44d19c5b9fbe6 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 30 Jun 2022 13:01:37 +0200 Subject: [PATCH 027/218] Review updates: Config setting actions for contracts --- Cargo.lock | 14 +- contracts/account-nft/Cargo.toml | 5 +- contracts/account-nft/src/contract.rs | 42 ++---- contracts/account-nft/src/execute.rs | 30 ++++ contracts/account-nft/src/lib.rs | 2 + contracts/account-nft/src/msg.rs | 99 +++++++++++++ contracts/account-nft/src/tests.rs | 97 ++++++++++--- contracts/credit-manager/Cargo.toml | 9 +- contracts/credit-manager/src/contract.rs | 35 ++--- contracts/credit-manager/src/execute.rs | 59 +++++++- contracts/credit-manager/src/instantiate.rs | 67 +-------- contracts/credit-manager/src/query.rs | 17 ++- contracts/credit-manager/src/state.rs | 2 +- .../tests/allow_list_query_test.rs | 30 +--- .../tests/create_credit_account_test.rs | 89 +++++++++--- contracts/credit-manager/tests/helpers.rs | 4 +- .../credit-manager/tests/instantiate_test.rs | 77 +++------- .../tests/update_config_test.rs | 133 ++++++++++++++++++ packages/rover/Cargo.toml | 4 +- packages/rover/src/lib.rs | 17 ++- scripts/tests/instantiate.test.ts | 15 +- scripts/utils/types.ts | 7 +- 22 files changed, 568 insertions(+), 286 deletions(-) create mode 100644 contracts/account-nft/src/execute.rs create mode 100644 contracts/account-nft/src/msg.rs create mode 100644 contracts/credit-manager/tests/update_config_test.rs diff --git a/Cargo.lock b/Cargo.lock index 725f35090..35d3406d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,8 @@ version = 3 [[package]] name = "account-nft" -version = "1.0.0" +version = "0.1.0" dependencies = [ - "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", "cw721", @@ -94,16 +93,6 @@ dependencies = [ "syn", ] -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - [[package]] name = "cosmwasm-std" version = "1.0.0" @@ -149,7 +138,6 @@ dependencies = [ "cw-asset", "cw-multi-test", "cw-storage-plus", - "cw-utils", "cw2", "cw721", "cw721-base", diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index f501aac68..91e3de249 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "account-nft" -version = "1.0.0" +version = "0.1.0" authors = ["larry_0x , grod220 "] edition = "2018" @@ -18,5 +18,4 @@ schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } [dev-dependencies] -cosmwasm-schema = "1.0" -cw-multi-test = "0.13" \ No newline at end of file +cw-multi-test = "0.13" diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 5c300445d..de6a4ee54 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -1,15 +1,17 @@ +use std::convert::TryInto; + use cosmwasm_std::{ entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; -use cw721::NumTokensResponse; -use cw721_base::{ - ContractError, Cw721Contract, ExecuteMsg, Extension, InstantiateMsg, MintMsg, QueryMsg, -}; +use cw721_base::{ContractError, Cw721Contract, Extension, InstantiateMsg, QueryMsg}; + +use crate::execute::{try_mint, try_update_owner}; +use crate::msg::ExecuteMsg; // Extending CW721 base contract pub type Parent<'a> = Cw721Contract<'a, Extension, Empty>; -#[entry_point] +#[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, env: Env, @@ -19,7 +21,7 @@ pub fn instantiate( Parent::default().instantiate(deps, env, info, msg) } -#[entry_point] +#[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, env: Env, @@ -27,33 +29,13 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Mint(mint_msg) => mint_override(deps, env, info, mint_msg), - _ => Parent::default().execute(deps, env, info, msg), + ExecuteMsg::Mint(mint_msg) => try_mint(deps, env, info, mint_msg), + ExecuteMsg::UpdateOwner { new_owner } => try_update_owner(deps, new_owner), + _ => Parent::default().execute(deps, env, info, msg.try_into()?), } } -fn mint_override( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: MintMsg, -) -> Result { - let parent = Parent::default(); - - let num_tokens: NumTokensResponse = - deps.querier.query_wasm_smart(&env.contract.address, &QueryMsg::NumTokens {})?; - - let mint_msg_override = MintMsg { - token_id: (num_tokens.count + 1).to_string(), - owner: msg.owner, - token_uri: None, - extension: None, - }; - - parent.mint(deps, env, info, mint_msg_override) -} - -#[entry_point] +#[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { Parent::default().query(deps, env, msg) } diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs new file mode 100644 index 000000000..0e5910d91 --- /dev/null +++ b/contracts/account-nft/src/execute.rs @@ -0,0 +1,30 @@ +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; +use cw721_base::{ContractError, Extension, MintMsg}; + +use crate::contract::Parent; + +pub fn try_mint( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: MintMsg, +) -> Result { + let parent = Parent::default(); + let num_tokens = parent.token_count(deps.storage)?; + let mint_msg_override = MintMsg { + token_id: (num_tokens + 1).to_string(), + owner: msg.owner, + token_uri: None, + extension: None, + }; + parent.mint(deps, env, info, mint_msg_override) +} + +pub fn try_update_owner(deps: DepsMut, new_owner: String) -> Result { + let validated_addr = deps.api.addr_validate(new_owner.as_str())?; + Parent::default() + .minter + .save(deps.storage, &validated_addr)?; + + Ok(Response::new().add_attribute("action", "rover/account_nft/update_owner")) +} diff --git a/contracts/account-nft/src/lib.rs b/contracts/account-nft/src/lib.rs index 7380e09bd..e0f7102af 100644 --- a/contracts/account-nft/src/lib.rs +++ b/contracts/account-nft/src/lib.rs @@ -1,4 +1,6 @@ pub mod contract; +pub mod execute; +pub mod msg; #[cfg(test)] mod tests; diff --git a/contracts/account-nft/src/msg.rs b/contracts/account-nft/src/msg.rs new file mode 100644 index 000000000..6c16b4c47 --- /dev/null +++ b/contracts/account-nft/src/msg.rs @@ -0,0 +1,99 @@ +use std::convert::TryInto; + +use cosmwasm_std::{Binary, StdError}; +use cw721::Expiration; +use cw721_base::{ContractError, ExecuteMsg as ParentExecuteMsg, MintMsg}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + //-------------------------------------------------------------------------------------------------- + // Extended and overridden messages + //-------------------------------------------------------------------------------------------------- + /// Due to some chains being permissioned via governance, we must instantiate this contract first + /// and give ownership access to Rover with this action after both are independently deployed. + UpdateOwner { new_owner: String }, + + /// Mint a new NFT, can only be called by the contract minter + Mint(MintMsg), + + //-------------------------------------------------------------------------------------------------- + // Base cw721 messages + //-------------------------------------------------------------------------------------------------- + /// Transfer is a base message to move a token to another account without triggering actions + TransferNft { recipient: String, token_id: String }, + /// Send is a base message to transfer a token to a contract and trigger an action + /// on the receiving contract. + SendNft { + contract: String, + token_id: String, + msg: Binary, + }, + /// Allows operator to transfer / send the token from the owner's account. + /// If expiration is set, then this allowance has a time/height limit + Approve { + spender: String, + token_id: String, + expires: Option, + }, + /// Remove previously granted Approval + Revoke { spender: String, token_id: String }, + /// Allows operator to transfer / send any token from the owner's account. + /// If expiration is set, then this allowance has a time/height limit + ApproveAll { + operator: String, + expires: Option, + }, + /// Remove previously granted ApproveAll permission + RevokeAll { operator: String }, + + /// Burn an NFT the sender has access to + Burn { token_id: String }, +} + +impl TryInto> for ExecuteMsg { + type Error = ContractError; + + fn try_into(self) -> Result, Self::Error> { + match self { + ExecuteMsg::TransferNft { + recipient, + token_id, + } => Ok(ParentExecuteMsg::TransferNft { + recipient, + token_id, + }), + ExecuteMsg::SendNft { + contract, + token_id, + msg, + } => Ok(ParentExecuteMsg::SendNft { + contract, + token_id, + msg, + }), + ExecuteMsg::Approve { + spender, + token_id, + expires, + } => Ok(ParentExecuteMsg::Approve { + spender, + token_id, + expires, + }), + ExecuteMsg::Revoke { spender, token_id } => { + Ok(ParentExecuteMsg::Revoke { spender, token_id }) + } + ExecuteMsg::ApproveAll { operator, expires } => { + Ok(ParentExecuteMsg::ApproveAll { operator, expires }) + } + ExecuteMsg::RevokeAll { operator } => Ok(ParentExecuteMsg::RevokeAll { operator }), + ExecuteMsg::Burn { token_id } => Ok(ParentExecuteMsg::Burn { token_id }), + _ => Err(ContractError::Std { + 0: StdError::generic_err("Attempting to convert to a non-cw721 compatible message"), + }), + } + } +} diff --git a/contracts/account-nft/src/tests.rs b/contracts/account-nft/src/tests.rs index 373505de9..9c86cf0a9 100644 --- a/contracts/account-nft/src/tests.rs +++ b/contracts/account-nft/src/tests.rs @@ -2,9 +2,11 @@ use cosmwasm_std::Addr; use cw721::OwnerOfResponse; use cw721_base::{ExecuteMsg, Extension, InstantiateMsg, MintMsg, QueryMsg}; use cw_multi_test::{App, AppResponse, BasicApp, ContractWrapper, Executor}; +use msg::ExecuteMsg as ExtendedExecuteMsg; use std::fmt::Error; use crate::contract::{execute, instantiate, query}; +use crate::msg; #[test] fn test_id_incrementer() { @@ -44,11 +46,50 @@ fn test_only_owner_can_mint() { let bad_guy = Addr::unchecked("bad_guy"); let res = mint_action(&mut app, &bad_guy, &contract_addr, &bad_guy); match res { - Ok(_) => panic!("Should have thrown an error"), + Ok(_) => panic!("Unauthorized access to minting function"), Err(_) => {} } } +#[test] +fn test_can_change_owner() { + let mut app = App::default(); + let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); + let original_owner = Addr::unchecked("original_owner"); + let code_id = app.store_code(contract); + let contract_addr = instantiate_mock_nft_contract(&mut app, &original_owner, code_id); + + let new_owner = Addr::unchecked("new_owner"); + replace_owner(&mut app, &original_owner, &contract_addr, &new_owner).unwrap(); + + let rover_user = Addr::unchecked("rover_user"); + mint_action(&mut app, &new_owner, &contract_addr, &rover_user).unwrap(); + + let res = mint_action(&mut app, &original_owner, &contract_addr, &rover_user); + match res { + Ok(_) => panic!("Original owner should no longer have access"), + Err(_) => {} + } +} + +#[test] +fn test_normal_base_cw721_actions_can_still_be_taken() { + let mut app = App::default(); + let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); + let owner = Addr::unchecked("owner"); + let code_id = app.store_code(contract); + let contract_addr = instantiate_mock_nft_contract(&mut app, &owner, code_id); + + let rover_user = Addr::unchecked("rover_user"); + let res = mint_action(&mut app, &owner, &contract_addr, &rover_user).unwrap(); + let token_id = get_token_id(res); + + let burn_msg: ExtendedExecuteMsg = ExtendedExecuteMsg::Burn { token_id }; + app.execute_contract(rover_user, contract_addr.clone(), &burn_msg, &[]) + .map_err(|_| Error::default()) + .unwrap(); +} + // Double checking ownership by querying NFT account-nft for correct owner fn assert_owner_is_correct( app: &mut BasicApp, @@ -71,36 +112,52 @@ fn assert_owner_is_correct( } fn instantiate_mock_nft_contract(app: &mut BasicApp, owner: &Addr, code_id: u64) -> Addr { - let contract_addr = app - .instantiate_contract( - code_id, - owner.clone(), - &InstantiateMsg { - name: String::from("mock_nft"), - symbol: String::from("MOCK"), - minter: owner.to_string(), - }, - &[], - "mock-account-nft", - None, - ) - .unwrap(); - contract_addr + app.instantiate_contract( + code_id, + owner.clone(), + &InstantiateMsg { + name: String::from("mock_nft"), + symbol: String::from("MOCK"), + minter: owner.to_string(), + }, + &[], + "mock-account-nft", + None, + ) + .unwrap() +} + +fn replace_owner( + app: &mut BasicApp, + current_owner: &Addr, + contract_addr: &Addr, + new_owner: &Addr, +) -> Result { + let update_msg: ExtendedExecuteMsg = ExtendedExecuteMsg::UpdateOwner { + new_owner: new_owner.to_string(), + }; + app.execute_contract( + current_owner.clone(), + contract_addr.clone(), + &update_msg, + &[], + ) + .map_err(|_| Error::default()) } fn mint_action( app: &mut BasicApp, - owner: &Addr, + sender: &Addr, contract_addr: &Addr, - user: &Addr, + token_owner: &Addr, ) -> Result { app.execute_contract( - owner.clone(), + sender.clone(), contract_addr.clone(), &ExecuteMsg::Mint { 0: MintMsg { token_id: String::from("some_token_id_that_will_be_ignored"), - owner: user.to_string(), + owner: token_owner.to_string(), token_uri: None, extension: Extension::None, }, diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 81fe25066..c40031b47 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -13,17 +13,16 @@ library = [] [dependencies] rover = { version = "0.1", path = "../../packages/rover" } -account-nft = { version = "1.0.0", path = "../account-nft" } cosmwasm-std = "1.0" cw2 = "0.13" cw-asset = "2.1" cw-storage-plus = "0.13" -cw-utils = "0.13" -cw721 = "0.13" -cw721-base = "0.13" -schemars = "0.8.1" +cw721-base = { version = "0.13", features = ["library"] } +schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } [dev-dependencies] cw-multi-test = "0.13" +account-nft = { version = "0.1", path = "../account-nft" } +cw721 = "0.13" diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 8b9a029d6..302973274 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -1,19 +1,13 @@ use cosmwasm_std::{ - entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError, - StdResult, + entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, }; use cw2::set_contract_version; use rover::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::execute::try_create_credit_account; -use crate::instantiate::{ - instantiate_nft_contract, store_config, store_nft_contract_addr, - NFT_CONTRACT_INSTANTIATE_REPLY_ID, -}; -use crate::query::{ - query_allowed_assets, query_allowed_vaults, query_nft_contract_addr, query_owner, -}; +use crate::execute::{try_create_credit_account, try_update_config}; +use crate::instantiate::store_config; +use crate::query::{query_allowed_assets, query_allowed_vaults, query_config}; const CONTRACT_NAME: &str = "crates.io:rover-credit-manager"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -21,16 +15,13 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, - env: Env, + _env: Env, _info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; store_config(deps, &msg)?; - let sub_message = instantiate_nft_contract(msg.nft_contract_code_id, msg.owner, env)?; - Ok(Response::new() - .add_submessage(sub_message) - .add_attribute("method", "instantiate")) + Ok(Response::new().add_attribute("method", "instantiate")) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -42,27 +33,21 @@ pub fn execute( ) -> StdResult { match msg { ExecuteMsg::CreateCreditAccount {} => try_create_credit_account(deps, info.sender), - } -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn reply(deps: DepsMut, _: Env, reply: Reply) -> StdResult { - match reply.id { - id if id == NFT_CONTRACT_INSTANTIATE_REPLY_ID => store_nft_contract_addr(deps, reply), - id => Err(StdError::generic_err(format!("invalid reply id: {}", id))), + ExecuteMsg::UpdateConfig { account_nft, owner } => { + try_update_config(deps, info, account_nft, owner) + } } } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::Owner {} => to_binary(&query_owner(deps)?), + QueryMsg::Config {} => to_binary(&query_config(deps)?), QueryMsg::AllowedVaults { start_after, limit } => { to_binary(&query_allowed_vaults(deps, start_after, limit)?) } QueryMsg::AllowedAssets { start_after, limit } => { to_binary(&query_allowed_assets(deps, start_after, limit)?) } - QueryMsg::CreditAccountNftAddress {} => to_binary(&query_nft_contract_addr(deps)?), } } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index f8dfc24fc..b6214f706 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -1,13 +1,22 @@ -use cosmwasm_std::{to_binary, Addr, CosmosMsg, DepsMut, Response, StdResult, WasmMsg}; +use cosmwasm_std::{ + to_binary, Addr, Attribute, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, + WasmMsg, +}; use cw721_base::{ExecuteMsg, Extension, MintMsg}; -use crate::state::CREDIT_ACCOUNT_NFT_CONTRACT; +use crate::state::{ACCOUNT_NFT, OWNER}; pub fn try_create_credit_account(deps: DepsMut, user: Addr) -> StdResult { - let contract_addr = CREDIT_ACCOUNT_NFT_CONTRACT.load(deps.storage)?; + let contract_addr = ACCOUNT_NFT.load(deps.storage)?; + + if let None = contract_addr { + return Err(StdError::generic_err( + "No account nft contract address is set", + )); + } let nft_mint_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_addr.to_string(), + contract_addr: contract_addr.unwrap().to_string(), funds: vec![], msg: to_binary(&ExecuteMsg::Mint(MintMsg:: { token_id: String::from("contract-will-generate"), @@ -17,5 +26,45 @@ pub fn try_create_credit_account(deps: DepsMut, user: Addr) -> StdResult, + new_owner: Option, +) -> StdResult { + let owner = OWNER.load(deps.storage)?; + + if info.sender != owner { + return Err(StdError::generic_err(format!( + "{} is not authorized to update config", + info.sender + ))); + } + + let mut attributes: Vec = vec![]; + + if let Some(addr_str) = new_account_nft { + let validated = deps.api.addr_validate(addr_str.as_str())?; + ACCOUNT_NFT.save(deps.storage, &Some(validated))?; + attributes.push(Attribute::new( + "action", + "rover/credit_manager/update_config/account_nft", + )); + } + + if let Some(addr_str) = new_owner { + let validated = deps.api.addr_validate(addr_str.as_str())?; + OWNER.save(deps.storage, &validated)?; + attributes.push(Attribute::new( + "action", + "rover/credit_manager/update_config/owner", + )); + } + + Ok(Response::new().add_attributes(attributes)) } diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 04bea1f4d..40b15b635 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,72 +1,15 @@ -use cosmwasm_std::{ - to_binary, CosmosMsg, DepsMut, Env, Reply, Response, StdError, StdResult, SubMsg, SubMsgResult, - WasmMsg, -}; -use cw721_base::InstantiateMsg as NftInstantiateMsg; -use cw_utils::parse_instantiate_response_data; +use cosmwasm_std::{DepsMut, StdResult}; use rover::InstantiateMsg; -use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, CREDIT_ACCOUNT_NFT_CONTRACT, OWNER}; +use crate::state::{ACCOUNT_NFT, ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; -pub const NFT_CONTRACT_INSTANTIATE_REPLY_ID: u64 = 1; - -/// Rover credit accounts are NFTs -pub fn instantiate_nft_contract(code_id: u64, owner: String, env: Env) -> StdResult { - let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Instantiate { - admin: Some(owner.clone()), - code_id, - msg: to_binary(&NftInstantiateMsg { - name: String::from("Rover Credit Account"), - symbol: String::from("RCA"), - minter: env.contract.address.to_string(), - })?, - funds: vec![], - label: "rover_credit_account_nft".to_string(), - }); - Ok(SubMsg::reply_on_success( - cosmos_msg, - NFT_CONTRACT_INSTANTIATE_REPLY_ID, - )) -} - -/// After successful NFT account-nft instantiation, save the account-nft address -pub fn store_nft_contract_addr(deps: DepsMut, reply: Reply) -> StdResult { - let contract_str = parse_reply_for_contract_addr(reply)?; - let contract_addr = deps.api.addr_validate(&contract_str)?; - CREDIT_ACCOUNT_NFT_CONTRACT.save(deps.storage, &contract_addr)?; - Ok(Response::new()) -} - -fn parse_reply_for_contract_addr(reply: Reply) -> StdResult { - return match reply.result { - SubMsgResult::Ok(res) => match res.data { - None => Err(StdError::generic_err( - "Submessage did not have data to parse", - )), - Some(data) => { - let parsed = parse_instantiate_response_data(&data) - .map_err(|_| StdError::generic_err("Could not parse binary response data"))?; - Ok(parsed.contract_address) - } - }, - SubMsgResult::Err(err) => Err(StdError::generic_err(err)), - }; -} - -pub fn store_config(mut deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { - store_owner(&mut deps, msg)?; - store_assets_and_vaults(&mut deps, msg)?; - Ok(()) -} - -fn store_owner(deps: &mut DepsMut, msg: &InstantiateMsg) -> StdResult<()> { +pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { let owner = deps.api.addr_validate(&msg.owner)?; OWNER.save(deps.storage, &owner)?; - Ok(()) -} -fn store_assets_and_vaults(deps: &mut DepsMut, msg: &InstantiateMsg) -> StdResult<()> { + ACCOUNT_NFT.save(deps.storage, &None)?; + msg.allowed_vaults.iter().try_for_each(|vault| { ALLOWED_VAULTS.save(deps.storage, deps.api.addr_validate(vault)?, &true) })?; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 2d0d13339..ee9c3f82d 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,18 +1,21 @@ -use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, CREDIT_ACCOUNT_NFT_CONTRACT, OWNER}; +use crate::state::{ACCOUNT_NFT, ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; use cosmwasm_std::{Addr, Deps, Order, StdResult}; use cw_asset::{AssetInfo, AssetInfoKey, AssetInfoUnchecked}; use cw_storage_plus::Bound; +use rover::ConfigResponse; use std::convert::TryFrom; const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; -pub fn query_nft_contract_addr(deps: Deps) -> StdResult { - Ok(CREDIT_ACCOUNT_NFT_CONTRACT.load(deps.storage)?.into()) -} - -pub fn query_owner(deps: Deps) -> StdResult { - Ok(OWNER.load(deps.storage)?.into()) +pub fn query_config(deps: Deps) -> StdResult { + Ok(ConfigResponse { + owner: OWNER.load(deps.storage)?.into(), + account_nft: match ACCOUNT_NFT.load(deps.storage)? { + None => String::from(""), + Some(addr) => addr.into(), + }, + }) } /// NOTE: This implementation of the query function assumes the map `ALLOWED_VAULTS` only saves `true`. diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 431ef06fd..bbae2919d 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -3,7 +3,7 @@ use cw_asset::AssetInfoKey; use cw_storage_plus::{Item, Map}; pub const OWNER: Item = Item::new("owner"); -pub const CREDIT_ACCOUNT_NFT_CONTRACT: Item = Item::new("credit-account-nft"); +pub const ACCOUNT_NFT: Item> = Item::new("account_nft"); pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); diff --git a/contracts/credit-manager/tests/allow_list_query_test.rs b/contracts/credit-manager/tests/allow_list_query_test.rs index c3c62b89a..f883981d8 100644 --- a/contracts/credit-manager/tests/allow_list_query_test.rs +++ b/contracts/credit-manager/tests/allow_list_query_test.rs @@ -4,15 +4,14 @@ use cw_multi_test::Executor; use rover::{InstantiateMsg, QueryMsg}; -use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract}; +use crate::helpers::{mock_app, mock_contract}; -mod helpers; +pub mod helpers; #[test] fn test_pagination_on_allowed_vaults_query_works() { let mut app = mock_app(); - let nft_contract_code_id = app.store_code(mock_account_nft_contract()); - let credit_manager_code_id = app.store_code(mock_contract()); + let code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); let allowed_vaults = vec![ @@ -54,18 +53,10 @@ fn test_pagination_on_allowed_vaults_query_works() { owner: owner.to_string(), allowed_vaults: allowed_vaults.clone(), allowed_assets: vec![], - nft_contract_code_id, }; let contract_addr = app - .instantiate_contract( - credit_manager_code_id, - owner.clone(), - &msg, - &[], - "mock-contract", - None, - ) + .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) .unwrap(); let vaults_res: Vec = app @@ -162,8 +153,7 @@ fn test_pagination_on_allowed_vaults_query_works() { #[test] fn test_pagination_on_allowed_assets_query_works() { let mut app = mock_app(); - let nft_contract_code_id = app.store_code(mock_account_nft_contract()); - let credit_manager_code_id = app.store_code(mock_contract()); + let code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); let allowed_assets = vec![ @@ -205,18 +195,10 @@ fn test_pagination_on_allowed_assets_query_works() { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: allowed_assets.clone(), - nft_contract_code_id, }; let contract_addr = app - .instantiate_contract( - credit_manager_code_id, - owner.clone(), - &msg, - &[], - "mock-contract", - None, - ) + .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) .unwrap(); let assets_res: Vec = app diff --git a/contracts/credit-manager/tests/create_credit_account_test.rs b/contracts/credit-manager/tests/create_credit_account_test.rs index c58f394e9..72323bba9 100644 --- a/contracts/credit-manager/tests/create_credit_account_test.rs +++ b/contracts/credit-manager/tests/create_credit_account_test.rs @@ -1,13 +1,18 @@ +extern crate core; + use cosmwasm_std::Addr; use cw721::OwnerOfResponse; -use cw721_base::QueryMsg as NftQueryMsg; -use cw_multi_test::Executor; +use cw721_base::{Extension, InstantiateMsg as NftInstantiateMsg, QueryMsg as NftQueryMsg}; +use cw_multi_test::{App, AppResponse, Executor}; +use std::fmt::Error; -use rover::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use rover::ExecuteMsg::{CreateCreditAccount, UpdateConfig}; +use rover::{ConfigResponse, InstantiateMsg, QueryMsg}; use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract}; +use account_nft::msg::ExecuteMsg as NftExecuteMsg; -mod helpers; +pub mod helpers; #[test] fn test_create_credit_account() { @@ -16,15 +21,29 @@ fn test_create_credit_account() { let nft_contract_code_id = app.store_code(mock_account_nft_contract()); + let nft_contract_addr = app + .instantiate_contract( + nft_contract_code_id, + owner.clone(), + &NftInstantiateMsg { + name: String::from("Rover Credit Account"), + symbol: String::from("RCA"), + minter: owner.to_string(), + }, + &[], + "manager-mock-account-nft", + None, + ) + .unwrap(); + let credit_manager_code_id = app.store_code(mock_contract()); let manager_initiate_msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![], - nft_contract_code_id, }; - let contract_addr = app + let manager_contract_addr = app .instantiate_contract( credit_manager_code_id, owner.clone(), @@ -36,15 +55,39 @@ fn test_create_credit_account() { .unwrap(); let user = Addr::unchecked("some_user"); - let res = app - .execute_contract( - user.clone(), - contract_addr.clone(), - &ExecuteMsg::CreateCreditAccount {}, - &[], - ) + let res = mock_create_credit_account(&mut app, &manager_contract_addr, &user); + + match res { + Ok(_) => panic!("Should have thrown error due to nft contract not yet set"), + Err(_) => {} + } + + app.execute_contract( + owner.clone(), + manager_contract_addr.clone(), + &UpdateConfig { + account_nft: Some(nft_contract_addr.to_string()), + owner: None, + }, + &[], + ) + .unwrap(); + + let res = mock_create_credit_account(&mut app, &manager_contract_addr, &user); + + match res { + Ok(_) => panic!("Should have thrown error due to nft contract not setting new owner yet"), + Err(_) => {} + } + + let update_msg: NftExecuteMsg = NftExecuteMsg::UpdateOwner { + new_owner: manager_contract_addr.to_string(), + }; + app.execute_contract(user.clone(), nft_contract_addr.clone(), &update_msg, &[]) .unwrap(); + let res = mock_create_credit_account(&mut app, &manager_contract_addr, &user).unwrap(); + let attr: Vec<&String> = res .events .iter() @@ -58,15 +101,15 @@ fn test_create_credit_account() { assert_eq!(token_id, "1"); // Double checking ownership by querying NFT account-nft for correct owner - let nft_contract_res: String = app + let config_res: ConfigResponse = app .wrap() - .query_wasm_smart(contract_addr.clone(), &QueryMsg::CreditAccountNftAddress {}) + .query_wasm_smart(manager_contract_addr.clone(), &QueryMsg::Config {}) .unwrap(); let owner_res: OwnerOfResponse = app .wrap() .query_wasm_smart( - nft_contract_res, + config_res.account_nft, &NftQueryMsg::OwnerOf { token_id: token_id.to_string(), include_expired: None, @@ -76,3 +119,17 @@ fn test_create_credit_account() { assert_eq!(user, owner_res.owner) } + +fn mock_create_credit_account( + app: &mut App, + manager_contract_addr: &Addr, + user: &Addr, +) -> Result { + app.execute_contract( + user.clone(), + manager_contract_addr.clone(), + &CreateCreditAccount {}, + &[], + ) + .map_err(|_| Error::default()) +} diff --git a/contracts/credit-manager/tests/helpers.rs b/contracts/credit-manager/tests/helpers.rs index 9d352562e..2946df607 100644 --- a/contracts/credit-manager/tests/helpers.rs +++ b/contracts/credit-manager/tests/helpers.rs @@ -4,14 +4,14 @@ use cw_multi_test::{App, Contract, ContractWrapper}; use account_nft::contract::{ execute as cw721Execute, instantiate as cw721Instantiate, query as cw721Query, }; -use credit_manager::contract::{execute, instantiate, query, reply}; +use credit_manager::contract::{execute, instantiate, query}; pub fn mock_app() -> App { App::default() } pub fn mock_contract() -> Box> { - let contract = ContractWrapper::new(execute, instantiate, query).with_reply(reply); + let contract = ContractWrapper::new(execute, instantiate, query); Box::new(contract) } diff --git a/contracts/credit-manager/tests/instantiate_test.rs b/contracts/credit-manager/tests/instantiate_test.rs index fe99a23a5..809da4a4e 100644 --- a/contracts/credit-manager/tests/instantiate_test.rs +++ b/contracts/credit-manager/tests/instantiate_test.rs @@ -2,84 +2,68 @@ use cosmwasm_std::Addr; use cw_asset::AssetInfoUnchecked; use cw_multi_test::Executor; -use rover::{InstantiateMsg, QueryMsg}; +use crate::helpers::{mock_app, mock_contract}; +use rover::{ConfigResponse, InstantiateMsg, QueryMsg}; -use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract}; - -mod helpers; +pub mod helpers; #[test] fn test_owner_set_on_instantiate() { let mut app = mock_app(); - let nft_contract_code_id = app.store_code(mock_account_nft_contract()); - let manager_code_id = app.store_code(mock_contract()); + let code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); let msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![], - nft_contract_code_id, }; let contract_addr = app - .instantiate_contract( - manager_code_id, - owner.clone(), - &msg, - &[], - "mock-account-nft", - None, - ) + .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-account-nft", None) .unwrap(); - let res: String = app + let res: ConfigResponse = app .wrap() - .query_wasm_smart(contract_addr.clone(), &QueryMsg::Owner {}) + .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) .unwrap(); - assert_eq!(owner, res); + assert_eq!(owner, res.owner); } #[test] -fn test_nft_contract_addr_set_on_instantiate() { +fn test_nft_contract_addr_not_set_on_instantiate() { let mut app = mock_app(); let owner = Addr::unchecked("owner"); - - let nft_contract_code_id = app.store_code(mock_account_nft_contract()); - - let credit_manager_code_id = app.store_code(mock_contract()); - let manager_initiate_msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec![], - allowed_assets: vec![], - nft_contract_code_id, - }; + let code_id = app.store_code(mock_contract()); let contract_addr = app .instantiate_contract( - credit_manager_code_id, + code_id, owner.clone(), - &manager_initiate_msg, + &InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_assets: vec![], + }, &[], "manager-mock-account-nft", None, ) .unwrap(); - let res: String = app + let res: ConfigResponse = app .wrap() - .query_wasm_smart(contract_addr.clone(), &QueryMsg::CreditAccountNftAddress {}) + .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) .unwrap(); - assert!(res.as_str().chars().count() > 0); + assert_eq!(res.account_nft, ""); } #[test] fn test_allowed_vaults_and_assets_stored_on_instantiate() { let mut app = mock_app(); - let nft_contract_code_id = app.store_code(mock_account_nft_contract()); - let credit_manager_code_id = app.store_code(mock_contract()); + let code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); let allowed_vaults = vec![ @@ -99,12 +83,11 @@ fn test_allowed_vaults_and_assets_stored_on_instantiate() { owner: owner.to_string(), allowed_vaults: allowed_vaults.clone(), allowed_assets: allowed_assets.clone(), - nft_contract_code_id, }; let contract_addr = app .instantiate_contract( - credit_manager_code_id, + code_id, owner, &msg, &[], @@ -145,7 +128,6 @@ fn test_allowed_vaults_and_assets_stored_on_instantiate() { #[test] fn test_panics_on_invalid_instantiation_addrs() { let mut app = mock_app(); - let nft_contract_code_id = app.store_code(mock_account_nft_contract()); let manager_code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); @@ -153,7 +135,6 @@ fn test_panics_on_invalid_instantiation_addrs() { owner: owner.to_string(), allowed_vaults: vec![String::from("%%%INVALID%%%")], allowed_assets: vec![], - nft_contract_code_id, }; let instantiate_res = app.instantiate_contract( @@ -174,7 +155,6 @@ fn test_panics_on_invalid_instantiation_addrs() { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![AssetInfoUnchecked::Cw20(String::from("AA"))], // Because cw-asset lowercases before passing to validate, in the test env, two letter strings is only one that triggers a fail - nft_contract_code_id, }; let instantiate_res = app.instantiate_contract( @@ -190,19 +170,4 @@ fn test_panics_on_invalid_instantiation_addrs() { Err(_) => {} Ok(_) => panic!("Should have thrown an error"), } - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec![], - allowed_assets: vec![], - nft_contract_code_id: 0, - }; - - let instantiate_res = - app.instantiate_contract(manager_code_id, owner, &msg, &[], "mock-contract", None); - - match instantiate_res { - Err(_) => {} - Ok(_) => panic!("Should have thrown an error"), - } } diff --git a/contracts/credit-manager/tests/update_config_test.rs b/contracts/credit-manager/tests/update_config_test.rs new file mode 100644 index 000000000..df3d9db51 --- /dev/null +++ b/contracts/credit-manager/tests/update_config_test.rs @@ -0,0 +1,133 @@ +extern crate core; + +use cosmwasm_std::Addr; +use cw_multi_test::{App, Executor}; +use rover::ExecuteMsg::UpdateConfig; + +use crate::helpers::{mock_app, mock_contract}; +use rover::{ConfigResponse, InstantiateMsg, QueryMsg}; + +pub mod helpers; + +#[test] +fn test_update_config_works_with_full_config() { + let mut app = mock_app(); + let original_owner = Addr::unchecked("original_owner"); + let code_id = app.store_code(mock_contract()); + let contract_addr = instantiate(&mut app, &original_owner, code_id); + + let config_res = query_config(&mut app, &contract_addr.clone()); + + assert_eq!(config_res.account_nft, ""); + assert_eq!(config_res.owner, original_owner.to_string()); + + let new_owner = Addr::unchecked("new_owner"); + let account_nft_contract = Addr::unchecked("account_nft_contract"); + app.execute_contract( + original_owner.clone(), + contract_addr.clone(), + &UpdateConfig { + account_nft: Some(account_nft_contract.to_string()), + owner: Some(new_owner.to_string()), + }, + &[], + ) + .unwrap(); + + let config_res = query_config(&mut app, &contract_addr.clone()); + + assert_eq!(config_res.account_nft, account_nft_contract.to_string()); + assert_eq!(config_res.owner, new_owner.to_string()); +} + +#[test] +fn test_update_config_works_with_some_config() { + let mut app = mock_app(); + let original_owner = Addr::unchecked("original_owner"); + let code_id = app.store_code(mock_contract()); + let contract_addr = instantiate(&mut app, &original_owner, code_id); + + let config_res = query_config(&mut app, &contract_addr.clone()); + + assert_eq!(config_res.account_nft, ""); + assert_eq!(config_res.owner, original_owner.to_string()); + + let account_nft_contract = Addr::unchecked("account_nft_contract"); + app.execute_contract( + original_owner.clone(), + contract_addr.clone(), + &UpdateConfig { + account_nft: Some(account_nft_contract.to_string()), + owner: None, + }, + &[], + ) + .unwrap(); + + let config_res = query_config(&mut app, &contract_addr.clone()); + + assert_eq!(config_res.account_nft, account_nft_contract.to_string()); + assert_eq!(config_res.owner, original_owner.to_string()); + + let new_owner = Addr::unchecked("new_owner"); + app.execute_contract( + original_owner.clone(), + contract_addr.clone(), + &UpdateConfig { + account_nft: None, + owner: Some(new_owner.to_string()), + }, + &[], + ) + .unwrap(); + + let config_res = query_config(&mut app, &contract_addr.clone()); + assert_eq!(config_res.account_nft, account_nft_contract.to_string()); + assert_eq!(config_res.owner, new_owner.to_string()); +} + +#[test] +fn test_update_config_does_nothing_when_nothing_is_passed() { + let mut app = mock_app(); + let original_owner = Addr::unchecked("original_owner"); + let code_id = app.store_code(mock_contract()); + let contract_addr = instantiate(&mut app, &original_owner, code_id); + + app.execute_contract( + original_owner.clone(), + contract_addr.clone(), + &UpdateConfig { + account_nft: None, + owner: None, + }, + &[], + ) + .unwrap(); + + let config_res = query_config(&mut app, &contract_addr.clone()); + + assert_eq!(config_res.account_nft, ""); + assert_eq!(config_res.owner, original_owner.to_string()); +} + +fn query_config(app: &mut App, contract_addr: &Addr) -> ConfigResponse { + app.wrap() + .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) + .unwrap() +} + +fn instantiate(app: &mut App, original_owner: &Addr, code_id: u64) -> Addr { + app.instantiate_contract( + code_id, + original_owner.clone(), + &InstantiateMsg { + owner: original_owner.to_string(), + allowed_vaults: vec![], + allowed_assets: vec![], + }, + &[], + "mock_manager_contract", + None, + ) + .unwrap() +} diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index b26b57a23..a15f89672 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -12,5 +12,5 @@ cosmwasm-std = "1.0" cw20 = "0.13" cw-asset = "2.1" cw-storage-plus = "0.13" -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } +schemars = "0.8" +serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index 8b495bef3..b3628070a 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -7,20 +7,23 @@ pub struct InstantiateMsg { pub owner: String, pub allowed_vaults: Vec, pub allowed_assets: Vec, - pub nft_contract_code_id: u64, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { CreateCreditAccount {}, + UpdateConfig { + account_nft: Option, + owner: Option, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { - /// The contract's owner. Response type: `String` - Owner {}, + /// Owner & account nft address. Response type: `ConfigResponse` + Config {}, /// Whitelisted vaults. Response type: `Vec` AllowedVaults { start_after: Option, @@ -31,5 +34,11 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, - CreditAccountNftAddress {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct ConfigResponse { + pub owner: String, + pub account_nft: String, } diff --git a/scripts/tests/instantiate.test.ts b/scripts/tests/instantiate.test.ts index 2afa7d72e..6e8a254a2 100644 --- a/scripts/tests/instantiate.test.ts +++ b/scripts/tests/instantiate.test.ts @@ -6,10 +6,7 @@ import path from 'path'; import { getCosmWasmClient } from '../utils/client'; import { Network, networks } from '../utils/config'; import { testWallet1 } from '../utils/test-wallets'; -import { getCosmWasmClient } from '../utils/client'; -import { sha256 } from '@cosmjs/crypto'; -import { GetAllowListResponse, GetNftAddressResponse, serializeAssetInfo } from '../utils/types'; -import { AssetInfo, serializeAssetInfo } from '../utils/types'; +import { AssetInfo, Config, serializeAssetInfo } from '../utils/types'; describe('instantiating fields contract', () => { let client: SigningCosmWasmClient; @@ -60,9 +57,6 @@ describe('instantiating fields contract', () => { managerContractAddr = contractAddress; expect(managerContractAddr).toBeDefined(); - const ownerFromQuery = await client.queryContractSmart(contractAddress, { owner: {} }); - expect(ownerFromQuery).toEqual(owner); - const allowedVaultsFromQuery: string[] = await client.queryContractSmart(contractAddress, { allowed_vaults: {}, }); @@ -81,11 +75,12 @@ describe('instantiating fields contract', () => { .every((asset_str) => allowed_assets.map(serializeAssetInfo).includes(asset_str)), ).toBeTruthy(); - const nftAddressRes: GetNftAddressResponse = await client.queryContractSmart(contractAddress, { - get_credit_account_nft_address: {}, + const configRes: Config = await client.queryContractSmart(contractAddress, { + config: {}, }); - expect(nftAddressRes.address).toBeDefined(); + expect(configRes.owner).toEqual(owner);; + expect(configRes.account_nft).toEqual(""); }); }); diff --git a/scripts/utils/types.ts b/scripts/utils/types.ts index e0ddbe62f..35909c7b1 100644 --- a/scripts/utils/types.ts +++ b/scripts/utils/types.ts @@ -1,2 +1,7 @@ export type AssetInfo = { cw20: string } | { native: string }; -export const serializeAssetInfo = (obj: AssetInfo) => Object.entries(obj).flat().join(':'); \ No newline at end of file +export const serializeAssetInfo = (obj: AssetInfo) => Object.entries(obj).flat().join(':'); + +export interface Config { + owner: string; + account_nft: string; +} From 4e4c727034e7b2c4f77489955b9823ae89408a3e Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Sun, 3 Jul 2022 16:29:15 +0100 Subject: [PATCH 028/218] simplify mint execute msg --- contracts/account-nft/src/contract.rs | 8 ++++---- contracts/account-nft/src/execute.rs | 6 +++--- contracts/account-nft/src/msg.rs | 16 +++++++++------- contracts/account-nft/src/tests.rs | 15 ++++----------- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index de6a4ee54..4b0380d38 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -3,13 +3,13 @@ use std::convert::TryInto; use cosmwasm_std::{ entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; -use cw721_base::{ContractError, Cw721Contract, Extension, InstantiateMsg, QueryMsg}; +use cw721_base::{ContractError, Cw721Contract, InstantiateMsg, QueryMsg}; use crate::execute::{try_mint, try_update_owner}; use crate::msg::ExecuteMsg; // Extending CW721 base contract -pub type Parent<'a> = Cw721Contract<'a, Extension, Empty>; +pub type Parent<'a> = Cw721Contract<'a, Option, Empty>; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( @@ -26,10 +26,10 @@ pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, - msg: ExecuteMsg, + msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Mint(mint_msg) => try_mint(deps, env, info, mint_msg), + ExecuteMsg::Mint { user } => try_mint(deps, env, info, user), ExecuteMsg::UpdateOwner { new_owner } => try_update_owner(deps, new_owner), _ => Parent::default().execute(deps, env, info, msg.try_into()?), } diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index 0e5910d91..3436b15d8 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; -use cw721_base::{ContractError, Extension, MintMsg}; +use cw721_base::{ContractError, MintMsg}; use crate::contract::Parent; @@ -7,13 +7,13 @@ pub fn try_mint( deps: DepsMut, env: Env, info: MessageInfo, - msg: MintMsg, + user: String, ) -> Result { let parent = Parent::default(); let num_tokens = parent.token_count(deps.storage)?; let mint_msg_override = MintMsg { token_id: (num_tokens + 1).to_string(), - owner: msg.owner, + owner: user, token_uri: None, extension: None, }; diff --git a/contracts/account-nft/src/msg.rs b/contracts/account-nft/src/msg.rs index 6c16b4c47..5626c21fc 100644 --- a/contracts/account-nft/src/msg.rs +++ b/contracts/account-nft/src/msg.rs @@ -1,14 +1,14 @@ use std::convert::TryInto; -use cosmwasm_std::{Binary, StdError}; +use cosmwasm_std::{Binary, Empty, StdError}; use cw721::Expiration; -use cw721_base::{ContractError, ExecuteMsg as ParentExecuteMsg, MintMsg}; +use cw721_base::{ContractError, ExecuteMsg as ParentExecuteMsg}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { +pub enum ExecuteMsg { //-------------------------------------------------------------------------------------------------- // Extended and overridden messages //-------------------------------------------------------------------------------------------------- @@ -16,8 +16,10 @@ pub enum ExecuteMsg { /// and give ownership access to Rover with this action after both are independently deployed. UpdateOwner { new_owner: String }, - /// Mint a new NFT, can only be called by the contract minter - Mint(MintMsg), + /// Mint a new NFT to the specified user; can only be called by the contract minter + Mint { + user: String, + }, //-------------------------------------------------------------------------------------------------- // Base cw721 messages @@ -53,10 +55,10 @@ pub enum ExecuteMsg { Burn { token_id: String }, } -impl TryInto> for ExecuteMsg { +impl TryInto>> for ExecuteMsg { type Error = ContractError; - fn try_into(self) -> Result, Self::Error> { + fn try_into(self) -> Result>, Self::Error> { match self { ExecuteMsg::TransferNft { recipient, diff --git a/contracts/account-nft/src/tests.rs b/contracts/account-nft/src/tests.rs index 9c86cf0a9..fc48c4633 100644 --- a/contracts/account-nft/src/tests.rs +++ b/contracts/account-nft/src/tests.rs @@ -1,6 +1,6 @@ use cosmwasm_std::Addr; use cw721::OwnerOfResponse; -use cw721_base::{ExecuteMsg, Extension, InstantiateMsg, MintMsg, QueryMsg}; +use cw721_base::{InstantiateMsg, QueryMsg}; use cw_multi_test::{App, AppResponse, BasicApp, ContractWrapper, Executor}; use msg::ExecuteMsg as ExtendedExecuteMsg; use std::fmt::Error; @@ -84,7 +84,7 @@ fn test_normal_base_cw721_actions_can_still_be_taken() { let res = mint_action(&mut app, &owner, &contract_addr, &rover_user).unwrap(); let token_id = get_token_id(res); - let burn_msg: ExtendedExecuteMsg = ExtendedExecuteMsg::Burn { token_id }; + let burn_msg: ExtendedExecuteMsg = ExtendedExecuteMsg::Burn { token_id }; app.execute_contract(rover_user, contract_addr.clone(), &burn_msg, &[]) .map_err(|_| Error::default()) .unwrap(); @@ -133,7 +133,7 @@ fn replace_owner( contract_addr: &Addr, new_owner: &Addr, ) -> Result { - let update_msg: ExtendedExecuteMsg = ExtendedExecuteMsg::UpdateOwner { + let update_msg: ExtendedExecuteMsg = ExtendedExecuteMsg::UpdateOwner { new_owner: new_owner.to_string(), }; app.execute_contract( @@ -154,14 +154,7 @@ fn mint_action( app.execute_contract( sender.clone(), contract_addr.clone(), - &ExecuteMsg::Mint { - 0: MintMsg { - token_id: String::from("some_token_id_that_will_be_ignored"), - owner: token_owner.to_string(), - token_uri: None, - extension: Extension::None, - }, - }, + &ExtendedExecuteMsg::Mint { user: token_owner.into() }, &[], ) .map_err(|_| Error::default()) From 5f994c938dd786ac0189d475bc0747fb0b9d79e3 Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Sun, 3 Jul 2022 16:33:37 +0100 Subject: [PATCH 029/218] error casting --- contracts/account-nft/src/msg.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/account-nft/src/msg.rs b/contracts/account-nft/src/msg.rs index 5626c21fc..1937edac1 100644 --- a/contracts/account-nft/src/msg.rs +++ b/contracts/account-nft/src/msg.rs @@ -93,9 +93,7 @@ impl TryInto>> for ExecuteMsg { } ExecuteMsg::RevokeAll { operator } => Ok(ParentExecuteMsg::RevokeAll { operator }), ExecuteMsg::Burn { token_id } => Ok(ParentExecuteMsg::Burn { token_id }), - _ => Err(ContractError::Std { - 0: StdError::generic_err("Attempting to convert to a non-cw721 compatible message"), - }), + _ => Err(StdError::generic_err("Attempting to convert to a non-cw721 compatible message").into()), } } } From 9a6174d7527927469ef13719c954f51654be1868 Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Sun, 3 Jul 2022 16:55:34 +0100 Subject: [PATCH 030/218] change many things --- Cargo.lock | 1 + contracts/account-nft/Cargo.toml | 1 + contracts/account-nft/src/contract.rs | 6 +++--- contracts/credit-manager/Cargo.toml | 7 ++++--- contracts/credit-manager/src/execute.rs | 19 ++++--------------- contracts/credit-manager/src/instantiate.rs | 5 ++--- contracts/credit-manager/src/query.rs | 5 +---- contracts/credit-manager/src/state.rs | 2 +- .../tests/create_credit_account_test.rs | 11 +++++------ .../credit-manager/tests/instantiate_test.rs | 2 +- .../tests/update_config_test.rs | 12 ++++++------ packages/rover/src/lib.rs | 2 +- 12 files changed, 30 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35d3406d0..9f267621c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,6 +134,7 @@ name = "credit-manager" version = "0.1.0" dependencies = [ "account-nft", + "anyhow", "cosmwasm-std", "cw-asset", "cw-multi-test", diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 91e3de249..90e4f2255 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["cdylib", "rlib"] [features] backtraces = ["cosmwasm-std/backtraces"] +library = [] [dependencies] cw721 = "0.13" diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 4b0380d38..9b0b0432d 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -1,8 +1,8 @@ use std::convert::TryInto; -use cosmwasm_std::{ - entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, -}; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult}; use cw721_base::{ContractError, Cw721Contract, InstantiateMsg, QueryMsg}; use crate::execute::{try_mint, try_update_owner}; diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index c40031b47..2f851dcea 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -12,17 +12,18 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] +account-nft = { version = "0.1", path = "../account-nft", features = ["library"] } rover = { version = "0.1", path = "../../packages/rover" } cosmwasm-std = "1.0" cw2 = "0.13" cw-asset = "2.1" cw-storage-plus = "0.13" -cw721-base = { version = "0.13", features = ["library"] } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } [dev-dependencies] -cw-multi-test = "0.13" -account-nft = { version = "0.1", path = "../account-nft" } +anyhow = "1" cw721 = "0.13" +cw721-base = { version = "0.13", features = ["library"] } +cw-multi-test = "0.13" diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index b6214f706..1a5c0ffeb 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -2,28 +2,17 @@ use cosmwasm_std::{ to_binary, Addr, Attribute, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, WasmMsg, }; -use cw721_base::{ExecuteMsg, Extension, MintMsg}; +use account_nft::msg::{ExecuteMsg as NftExecuteMsg}; use crate::state::{ACCOUNT_NFT, OWNER}; pub fn try_create_credit_account(deps: DepsMut, user: Addr) -> StdResult { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; - if let None = contract_addr { - return Err(StdError::generic_err( - "No account nft contract address is set", - )); - } - let nft_mint_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_addr.unwrap().to_string(), + contract_addr: contract_addr.to_string(), funds: vec![], - msg: to_binary(&ExecuteMsg::Mint(MintMsg:: { - token_id: String::from("contract-will-generate"), - owner: user.to_string(), - token_uri: None, - extension: None, - }))?, + msg: to_binary(&NftExecuteMsg::Mint { user: user.to_string() })?, }); Ok(Response::new() @@ -50,7 +39,7 @@ pub fn try_update_config( if let Some(addr_str) = new_account_nft { let validated = deps.api.addr_validate(addr_str.as_str())?; - ACCOUNT_NFT.save(deps.storage, &Some(validated))?; + ACCOUNT_NFT.save(deps.storage, &validated)?; attributes.push(Attribute::new( "action", "rover/credit_manager/update_config/account_nft", diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 40b15b635..6babfef61 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -2,14 +2,12 @@ use cosmwasm_std::{DepsMut, StdResult}; use rover::InstantiateMsg; -use crate::state::{ACCOUNT_NFT, ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; +use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { let owner = deps.api.addr_validate(&msg.owner)?; OWNER.save(deps.storage, &owner)?; - ACCOUNT_NFT.save(deps.storage, &None)?; - msg.allowed_vaults.iter().try_for_each(|vault| { ALLOWED_VAULTS.save(deps.storage, deps.api.addr_validate(vault)?, &true) })?; @@ -17,5 +15,6 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { msg.allowed_assets.iter().try_for_each(|info| { ALLOWED_ASSETS.save(deps.storage, info.check(deps.api, None)?.into(), &true) })?; + Ok(()) } diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index ee9c3f82d..22600cf46 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -11,10 +11,7 @@ const DEFAULT_LIMIT: u32 = 10; pub fn query_config(deps: Deps) -> StdResult { Ok(ConfigResponse { owner: OWNER.load(deps.storage)?.into(), - account_nft: match ACCOUNT_NFT.load(deps.storage)? { - None => String::from(""), - Some(addr) => addr.into(), - }, + account_nft: ACCOUNT_NFT.may_load(deps.storage)?.map(|addr| addr.to_string()), }) } diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index bbae2919d..2f05ed17a 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -3,7 +3,7 @@ use cw_asset::AssetInfoKey; use cw_storage_plus::{Item, Map}; pub const OWNER: Item = Item::new("owner"); -pub const ACCOUNT_NFT: Item> = Item::new("account_nft"); +pub const ACCOUNT_NFT: Item = Item::new("account_nft"); pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); diff --git a/contracts/credit-manager/tests/create_credit_account_test.rs b/contracts/credit-manager/tests/create_credit_account_test.rs index 72323bba9..94eecc7e4 100644 --- a/contracts/credit-manager/tests/create_credit_account_test.rs +++ b/contracts/credit-manager/tests/create_credit_account_test.rs @@ -1,10 +1,10 @@ extern crate core; +use anyhow::Result as AnyResult; use cosmwasm_std::Addr; use cw721::OwnerOfResponse; -use cw721_base::{Extension, InstantiateMsg as NftInstantiateMsg, QueryMsg as NftQueryMsg}; +use cw721_base::{InstantiateMsg as NftInstantiateMsg, QueryMsg as NftQueryMsg}; use cw_multi_test::{App, AppResponse, Executor}; -use std::fmt::Error; use rover::ExecuteMsg::{CreateCreditAccount, UpdateConfig}; use rover::{ConfigResponse, InstantiateMsg, QueryMsg}; @@ -80,7 +80,7 @@ fn test_create_credit_account() { Err(_) => {} } - let update_msg: NftExecuteMsg = NftExecuteMsg::UpdateOwner { + let update_msg: NftExecuteMsg = NftExecuteMsg::UpdateOwner { new_owner: manager_contract_addr.to_string(), }; app.execute_contract(user.clone(), nft_contract_addr.clone(), &update_msg, &[]) @@ -109,7 +109,7 @@ fn test_create_credit_account() { let owner_res: OwnerOfResponse = app .wrap() .query_wasm_smart( - config_res.account_nft, + config_res.account_nft.unwrap(), &NftQueryMsg::OwnerOf { token_id: token_id.to_string(), include_expired: None, @@ -124,12 +124,11 @@ fn mock_create_credit_account( app: &mut App, manager_contract_addr: &Addr, user: &Addr, -) -> Result { +) -> AnyResult { app.execute_contract( user.clone(), manager_contract_addr.clone(), &CreateCreditAccount {}, &[], ) - .map_err(|_| Error::default()) } diff --git a/contracts/credit-manager/tests/instantiate_test.rs b/contracts/credit-manager/tests/instantiate_test.rs index 809da4a4e..4f46c20ad 100644 --- a/contracts/credit-manager/tests/instantiate_test.rs +++ b/contracts/credit-manager/tests/instantiate_test.rs @@ -57,7 +57,7 @@ fn test_nft_contract_addr_not_set_on_instantiate() { .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) .unwrap(); - assert_eq!(res.account_nft, ""); + assert_eq!(res.account_nft, None); } #[test] diff --git a/contracts/credit-manager/tests/update_config_test.rs b/contracts/credit-manager/tests/update_config_test.rs index df3d9db51..755ad80da 100644 --- a/contracts/credit-manager/tests/update_config_test.rs +++ b/contracts/credit-manager/tests/update_config_test.rs @@ -18,7 +18,7 @@ fn test_update_config_works_with_full_config() { let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!(config_res.account_nft, ""); + assert_eq!(config_res.account_nft, None); assert_eq!(config_res.owner, original_owner.to_string()); let new_owner = Addr::unchecked("new_owner"); @@ -36,7 +36,7 @@ fn test_update_config_works_with_full_config() { let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!(config_res.account_nft, account_nft_contract.to_string()); + assert_eq!(config_res.account_nft, Some(account_nft_contract.to_string())); assert_eq!(config_res.owner, new_owner.to_string()); } @@ -49,7 +49,7 @@ fn test_update_config_works_with_some_config() { let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!(config_res.account_nft, ""); + assert_eq!(config_res.account_nft, None); assert_eq!(config_res.owner, original_owner.to_string()); let account_nft_contract = Addr::unchecked("account_nft_contract"); @@ -66,7 +66,7 @@ fn test_update_config_works_with_some_config() { let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!(config_res.account_nft, account_nft_contract.to_string()); + assert_eq!(config_res.account_nft, Some(account_nft_contract.to_string())); assert_eq!(config_res.owner, original_owner.to_string()); let new_owner = Addr::unchecked("new_owner"); @@ -82,7 +82,7 @@ fn test_update_config_works_with_some_config() { .unwrap(); let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!(config_res.account_nft, account_nft_contract.to_string()); + assert_eq!(config_res.account_nft, Some(account_nft_contract.to_string())); assert_eq!(config_res.owner, new_owner.to_string()); } @@ -106,7 +106,7 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!(config_res.account_nft, ""); + assert_eq!(config_res.account_nft, None); assert_eq!(config_res.owner, original_owner.to_string()); } diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index b3628070a..a12cac5af 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -40,5 +40,5 @@ pub enum QueryMsg { #[serde(rename_all = "snake_case")] pub struct ConfigResponse { pub owner: String, - pub account_nft: String, + pub account_nft: Option, } From dec0d313ea9f3afe5ef698a8dcbdfe4d1ba8b336 Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Sun, 3 Jul 2022 17:01:54 +0100 Subject: [PATCH 031/218] update some tests --- Cargo.lock | 1 + contracts/account-nft/Cargo.toml | 1 + contracts/account-nft/src/tests.rs | 15 +++++++-------- .../tests/create_credit_account_test.rs | 15 ++++++--------- .../credit-manager/tests/instantiate_test.rs | 13 ++++++------- .../credit-manager/tests/update_config_test.rs | 14 ++++++-------- 6 files changed, 27 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f267621c..53c185c16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,7 @@ version = 3 name = "account-nft" version = "0.1.0" dependencies = [ + "anyhow", "cosmwasm-std", "cw-multi-test", "cw721", diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 90e4f2255..304bcc619 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -19,4 +19,5 @@ schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } [dev-dependencies] +anyhow = "1" cw-multi-test = "0.13" diff --git a/contracts/account-nft/src/tests.rs b/contracts/account-nft/src/tests.rs index fc48c4633..9e0664280 100644 --- a/contracts/account-nft/src/tests.rs +++ b/contracts/account-nft/src/tests.rs @@ -1,12 +1,13 @@ +use std::fmt::Error; + +use anyhow::Result as AnyResult; use cosmwasm_std::Addr; use cw721::OwnerOfResponse; use cw721_base::{InstantiateMsg, QueryMsg}; use cw_multi_test::{App, AppResponse, BasicApp, ContractWrapper, Executor}; -use msg::ExecuteMsg as ExtendedExecuteMsg; -use std::fmt::Error; use crate::contract::{execute, instantiate, query}; -use crate::msg; +use crate::msg::ExecuteMsg as ExtendedExecuteMsg; #[test] fn test_id_incrementer() { @@ -66,9 +67,8 @@ fn test_can_change_owner() { mint_action(&mut app, &new_owner, &contract_addr, &rover_user).unwrap(); let res = mint_action(&mut app, &original_owner, &contract_addr, &rover_user); - match res { - Ok(_) => panic!("Original owner should no longer have access"), - Err(_) => {} + if res.is_ok() { + panic!("Original owner should no longer have access"); } } @@ -150,14 +150,13 @@ fn mint_action( sender: &Addr, contract_addr: &Addr, token_owner: &Addr, -) -> Result { +) -> AnyResult { app.execute_contract( sender.clone(), contract_addr.clone(), &ExtendedExecuteMsg::Mint { user: token_owner.into() }, &[], ) - .map_err(|_| Error::default()) } fn get_token_id(res: AppResponse) -> String { diff --git a/contracts/credit-manager/tests/create_credit_account_test.rs b/contracts/credit-manager/tests/create_credit_account_test.rs index 94eecc7e4..bdbae9763 100644 --- a/contracts/credit-manager/tests/create_credit_account_test.rs +++ b/contracts/credit-manager/tests/create_credit_account_test.rs @@ -1,16 +1,14 @@ -extern crate core; - use anyhow::Result as AnyResult; use cosmwasm_std::Addr; use cw721::OwnerOfResponse; use cw721_base::{InstantiateMsg as NftInstantiateMsg, QueryMsg as NftQueryMsg}; use cw_multi_test::{App, AppResponse, Executor}; +use account_nft::msg::ExecuteMsg as NftExecuteMsg; use rover::ExecuteMsg::{CreateCreditAccount, UpdateConfig}; use rover::{ConfigResponse, InstantiateMsg, QueryMsg}; use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract}; -use account_nft::msg::ExecuteMsg as NftExecuteMsg; pub mod helpers; @@ -57,9 +55,8 @@ fn test_create_credit_account() { let user = Addr::unchecked("some_user"); let res = mock_create_credit_account(&mut app, &manager_contract_addr, &user); - match res { - Ok(_) => panic!("Should have thrown error due to nft contract not yet set"), - Err(_) => {} + if res.is_ok() { + panic!("Should have thrown error due to nft contract not yet set"); } app.execute_contract( @@ -75,9 +72,8 @@ fn test_create_credit_account() { let res = mock_create_credit_account(&mut app, &manager_contract_addr, &user); - match res { - Ok(_) => panic!("Should have thrown error due to nft contract not setting new owner yet"), - Err(_) => {} + if res.is_ok() { + panic!("Should have thrown error due to nft contract not setting new owner yet"); } let update_msg: NftExecuteMsg = NftExecuteMsg::UpdateOwner { @@ -97,6 +93,7 @@ fn test_create_credit_account() { .collect(); assert_eq!(attr.len(), 1); + let token_id = attr.first().unwrap().as_str(); assert_eq!(token_id, "1"); diff --git a/contracts/credit-manager/tests/instantiate_test.rs b/contracts/credit-manager/tests/instantiate_test.rs index 4f46c20ad..07a37f6ca 100644 --- a/contracts/credit-manager/tests/instantiate_test.rs +++ b/contracts/credit-manager/tests/instantiate_test.rs @@ -2,9 +2,10 @@ use cosmwasm_std::Addr; use cw_asset::AssetInfoUnchecked; use cw_multi_test::Executor; -use crate::helpers::{mock_app, mock_contract}; use rover::{ConfigResponse, InstantiateMsg, QueryMsg}; +use crate::helpers::{mock_app, mock_contract}; + pub mod helpers; #[test] @@ -146,9 +147,8 @@ fn test_panics_on_invalid_instantiation_addrs() { None, ); - match instantiate_res { - Err(_) => {} - Ok(_) => panic!("Should have thrown an error"), + if instantiate_res.is_ok() { + panic!("Should have thrown an error"); } let msg = InstantiateMsg { @@ -166,8 +166,7 @@ fn test_panics_on_invalid_instantiation_addrs() { None, ); - match instantiate_res { - Err(_) => {} - Ok(_) => panic!("Should have thrown an error"), + if instantiate_res.is_ok() { + panic!("Should have thrown an error"); } } diff --git a/contracts/credit-manager/tests/update_config_test.rs b/contracts/credit-manager/tests/update_config_test.rs index 755ad80da..bb467488c 100644 --- a/contracts/credit-manager/tests/update_config_test.rs +++ b/contracts/credit-manager/tests/update_config_test.rs @@ -1,11 +1,9 @@ -extern crate core; - use cosmwasm_std::Addr; use cw_multi_test::{App, Executor}; -use rover::ExecuteMsg::UpdateConfig; + +use rover::{ConfigResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::helpers::{mock_app, mock_contract}; -use rover::{ConfigResponse, InstantiateMsg, QueryMsg}; pub mod helpers; @@ -26,7 +24,7 @@ fn test_update_config_works_with_full_config() { app.execute_contract( original_owner.clone(), contract_addr.clone(), - &UpdateConfig { + &ExecuteMsg::UpdateConfig { account_nft: Some(account_nft_contract.to_string()), owner: Some(new_owner.to_string()), }, @@ -56,7 +54,7 @@ fn test_update_config_works_with_some_config() { app.execute_contract( original_owner.clone(), contract_addr.clone(), - &UpdateConfig { + &ExecuteMsg::UpdateConfig { account_nft: Some(account_nft_contract.to_string()), owner: None, }, @@ -73,7 +71,7 @@ fn test_update_config_works_with_some_config() { app.execute_contract( original_owner.clone(), contract_addr.clone(), - &UpdateConfig { + &ExecuteMsg::UpdateConfig { account_nft: None, owner: Some(new_owner.to_string()), }, @@ -96,7 +94,7 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { app.execute_contract( original_owner.clone(), contract_addr.clone(), - &UpdateConfig { + &ExecuteMsg::UpdateConfig { account_nft: None, owner: None, }, From 71d469283f20d21198cafb0e9ea4bc080609fadf Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Sun, 3 Jul 2022 17:04:37 +0100 Subject: [PATCH 032/218] fix a clippy error --- contracts/credit-manager/src/query.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 22600cf46..b38c4ea7a 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -65,6 +65,6 @@ pub fn query_allowed_assets( .take(limit) .collect::>>()? .into_iter() - .map(|key| AssetInfoUnchecked::try_from(key)) + .map(AssetInfoUnchecked::try_from) .collect() } From 68466c411e6b31d438582c9d661c87a45655f594 Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Sun, 3 Jul 2022 17:12:03 +0100 Subject: [PATCH 033/218] use empty extension --- contracts/account-nft/src/contract.rs | 2 +- contracts/account-nft/src/execute.rs | 4 ++-- contracts/account-nft/src/msg.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 9b0b0432d..9d5ca6acf 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -9,7 +9,7 @@ use crate::execute::{try_mint, try_update_owner}; use crate::msg::ExecuteMsg; // Extending CW721 base contract -pub type Parent<'a> = Cw721Contract<'a, Option, Empty>; +pub type Parent<'a> = Cw721Contract<'a, Empty, Empty>; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index 3436b15d8..1cbd101a6 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; +use cosmwasm_std::{DepsMut, Empty, Env, MessageInfo, Response}; use cw721_base::{ContractError, MintMsg}; use crate::contract::Parent; @@ -15,7 +15,7 @@ pub fn try_mint( token_id: (num_tokens + 1).to_string(), owner: user, token_uri: None, - extension: None, + extension: Empty {}, }; parent.mint(deps, env, info, mint_msg_override) } diff --git a/contracts/account-nft/src/msg.rs b/contracts/account-nft/src/msg.rs index 1937edac1..e2f0fadc9 100644 --- a/contracts/account-nft/src/msg.rs +++ b/contracts/account-nft/src/msg.rs @@ -55,10 +55,10 @@ pub enum ExecuteMsg { Burn { token_id: String }, } -impl TryInto>> for ExecuteMsg { +impl TryInto> for ExecuteMsg { type Error = ContractError; - fn try_into(self) -> Result>, Self::Error> { + fn try_into(self) -> Result, Self::Error> { match self { ExecuteMsg::TransferNft { recipient, From 7231a9d9eb264b61a48437dab5d6aa66555eb76e Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 4 Jul 2022 14:40:39 +0200 Subject: [PATCH 034/218] Updates from review --- Cargo.lock | 1 + contracts/account-nft/Cargo.toml | 1 + contracts/account-nft/src/contract.rs | 22 ++- contracts/account-nft/src/execute.rs | 41 ++++- .../src/{msg.rs => execute_msg.rs} | 16 +- contracts/account-nft/src/lib.rs | 8 +- contracts/account-nft/src/query.rs | 6 + contracts/account-nft/src/query_msg.rs | 160 ++++++++++++++++++ contracts/account-nft/src/state.rs | 4 + contracts/account-nft/tests/helpers.rs | 42 +++++ .../{src/tests.rs => tests/mint_test.rs} | 95 ++--------- contracts/account-nft/tests/ownership_test.rs | 112 ++++++++++++ contracts/credit-manager/src/contract.rs | 6 +- contracts/credit-manager/src/execute.rs | 37 ++-- .../tests/create_credit_account_test.rs | 26 ++- .../tests/update_config_test.rs | 50 +++++- 16 files changed, 486 insertions(+), 141 deletions(-) rename contracts/account-nft/src/{msg.rs => execute_msg.rs} (89%) create mode 100644 contracts/account-nft/src/query.rs create mode 100644 contracts/account-nft/src/query_msg.rs create mode 100644 contracts/account-nft/src/state.rs create mode 100644 contracts/account-nft/tests/helpers.rs rename contracts/account-nft/{src/tests.rs => tests/mint_test.rs} (51%) create mode 100644 contracts/account-nft/tests/ownership_test.rs diff --git a/Cargo.lock b/Cargo.lock index 53c185c16..8f3240f8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,7 @@ dependencies = [ "anyhow", "cosmwasm-std", "cw-multi-test", + "cw-storage-plus", "cw721", "cw721-base", "schemars", diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 304bcc619..1229110d3 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -12,6 +12,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] +cw-storage-plus = "0.13" cw721 = "0.13" cw721-base = { version = "0.13", features = ["library"] } cosmwasm-std = "1.0" diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 9d5ca6acf..eab6f0f07 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -2,11 +2,15 @@ use std::convert::TryInto; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult}; -use cw721_base::{ContractError, Cw721Contract, InstantiateMsg, QueryMsg}; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +}; +use cw721_base::{ContractError, Cw721Contract, InstantiateMsg}; -use crate::execute::{try_mint, try_update_owner}; -use crate::msg::ExecuteMsg; +use crate::execute::{accept_ownership, mint, propose_new_owner}; +use crate::execute_msg::ExecuteMsg; +use crate::query::query_proposed_new_owner; +use crate::query_msg::QueryMsg; // Extending CW721 base contract pub type Parent<'a> = Cw721Contract<'a, Empty, Empty>; @@ -29,13 +33,17 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Mint { user } => try_mint(deps, env, info, user), - ExecuteMsg::UpdateOwner { new_owner } => try_update_owner(deps, new_owner), + ExecuteMsg::Mint { user } => mint(deps, env, info, user), + ExecuteMsg::ProposeNewOwner { new_owner } => propose_new_owner(deps, info, new_owner), + ExecuteMsg::AcceptOwnership {} => accept_ownership(deps, info), _ => Parent::default().execute(deps, env, info, msg.try_into()?), } } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - Parent::default().query(deps, env, msg) + match msg { + QueryMsg::ProposedNewOwner {} => to_binary(&query_proposed_new_owner(deps)?), + _ => Parent::default().query(deps, env, msg.try_into()?), + } } diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index 1cbd101a6..9d3500c95 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -1,9 +1,10 @@ -use cosmwasm_std::{DepsMut, Empty, Env, MessageInfo, Response}; +use cosmwasm_std::{DepsMut, Empty, Env, Event, MessageInfo, Response}; use cw721_base::{ContractError, MintMsg}; use crate::contract::Parent; +use crate::state::PENDING_OWNER; -pub fn try_mint( +pub fn mint( deps: DepsMut, env: Env, info: MessageInfo, @@ -20,11 +21,39 @@ pub fn try_mint( parent.mint(deps, env, info, mint_msg_override) } -pub fn try_update_owner(deps: DepsMut, new_owner: String) -> Result { - let validated_addr = deps.api.addr_validate(new_owner.as_str())?; +pub fn propose_new_owner( + deps: DepsMut, + info: MessageInfo, + new_owner: String, +) -> Result { + let proposed_owner_addr = deps.api.addr_validate(new_owner.as_str())?; + let current_owner = Parent::default().minter.load(deps.storage)?; + + if info.sender != current_owner { + return Err(ContractError::Unauthorized {}); + } + + PENDING_OWNER.save(deps.storage, &proposed_owner_addr)?; + + Ok(Response::new().add_attribute("action", "rover/account_nft/propose_new_owner")) +} + +pub fn accept_ownership(deps: DepsMut, info: MessageInfo) -> Result { + let pending_owner = PENDING_OWNER.load(deps.storage)?; + let previous_owner = Parent::default().minter.load(deps.storage)?; + + if info.sender != pending_owner { + return Err(ContractError::Unauthorized {}); + } + Parent::default() .minter - .save(deps.storage, &validated_addr)?; + .save(deps.storage, &pending_owner)?; + + PENDING_OWNER.remove(deps.storage); - Ok(Response::new().add_attribute("action", "rover/account_nft/update_owner")) + let event = Event::new("rover/account_nft/accept_ownership") + .add_attribute("previous_owner", previous_owner) + .add_attribute("new_owner", pending_owner); + Ok(Response::new().add_event(event)) } diff --git a/contracts/account-nft/src/msg.rs b/contracts/account-nft/src/execute_msg.rs similarity index 89% rename from contracts/account-nft/src/msg.rs rename to contracts/account-nft/src/execute_msg.rs index e2f0fadc9..38225214b 100644 --- a/contracts/account-nft/src/msg.rs +++ b/contracts/account-nft/src/execute_msg.rs @@ -13,13 +13,14 @@ pub enum ExecuteMsg { // Extended and overridden messages //-------------------------------------------------------------------------------------------------- /// Due to some chains being permissioned via governance, we must instantiate this contract first - /// and give ownership access to Rover with this action after both are independently deployed. - UpdateOwner { new_owner: String }, + /// and give ownership access to Rover contract with this action after both are independently deployed. + ProposeNewOwner { new_owner: String }, + + /// Accept the proposed ownership transfer + AcceptOwnership {}, /// Mint a new NFT to the specified user; can only be called by the contract minter - Mint { - user: String, - }, + Mint { user: String }, //-------------------------------------------------------------------------------------------------- // Base cw721 messages @@ -93,7 +94,10 @@ impl TryInto> for ExecuteMsg { } ExecuteMsg::RevokeAll { operator } => Ok(ParentExecuteMsg::RevokeAll { operator }), ExecuteMsg::Burn { token_id } => Ok(ParentExecuteMsg::Burn { token_id }), - _ => Err(StdError::generic_err("Attempting to convert to a non-cw721 compatible message").into()), + _ => Err(StdError::generic_err( + "Attempting to convert to a non-cw721 compatible message", + ) + .into()), } } } diff --git a/contracts/account-nft/src/lib.rs b/contracts/account-nft/src/lib.rs index e0f7102af..99dbdc298 100644 --- a/contracts/account-nft/src/lib.rs +++ b/contracts/account-nft/src/lib.rs @@ -1,6 +1,6 @@ pub mod contract; pub mod execute; -pub mod msg; - -#[cfg(test)] -mod tests; +pub mod execute_msg; +pub mod query; +pub mod query_msg; +pub mod state; diff --git a/contracts/account-nft/src/query.rs b/contracts/account-nft/src/query.rs new file mode 100644 index 000000000..580934835 --- /dev/null +++ b/contracts/account-nft/src/query.rs @@ -0,0 +1,6 @@ +use crate::state::PENDING_OWNER; +use cosmwasm_std::{Deps, StdResult}; + +pub fn query_proposed_new_owner(deps: Deps) -> StdResult { + Ok(PENDING_OWNER.load(deps.storage)?.into()) +} diff --git a/contracts/account-nft/src/query_msg.rs b/contracts/account-nft/src/query_msg.rs new file mode 100644 index 000000000..c94cb7aec --- /dev/null +++ b/contracts/account-nft/src/query_msg.rs @@ -0,0 +1,160 @@ +use cosmwasm_std::StdError; +use std::convert::TryInto; + +use cw721_base::QueryMsg as ParentQueryMsg; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + //-------------------------------------------------------------------------------------------------- + // Extended messages + //-------------------------------------------------------------------------------------------------- + ProposedNewOwner {}, + + //-------------------------------------------------------------------------------------------------- + // Base cw721 messages + //-------------------------------------------------------------------------------------------------- + /// Return the owner of the given token, error if token does not exist + /// Return type: OwnerOfResponse + OwnerOf { + token_id: String, + /// unset or false will filter out expired approvals, you must set to true to see them + include_expired: Option, + }, + + /// Return operator that can access all of the owner's tokens. + /// Return type: `ApprovalResponse` + Approval { + token_id: String, + spender: String, + include_expired: Option, + }, + + /// Return approvals that a token has + /// Return type: `ApprovalsResponse` + Approvals { + token_id: String, + include_expired: Option, + }, + + /// List all operators that can access all of the owner's tokens + /// Return type: `OperatorsResponse` + AllOperators { + owner: String, + /// unset or false will filter out expired items, you must set to true to see them + include_expired: Option, + start_after: Option, + limit: Option, + }, + /// Total number of tokens issued + NumTokens {}, + + /// With MetaData Extension. + /// Returns top-level metadata about the contract: `ContractInfoResponse` + ContractInfo {}, + /// With MetaData Extension. + /// Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* + /// but directly from the contract: `NftInfoResponse` + NftInfo { + token_id: String, + }, + /// With MetaData Extension. + /// Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization + /// for clients: `AllNftInfo` + AllNftInfo { + token_id: String, + /// unset or false will filter out expired approvals, you must set to true to see them + include_expired: Option, + }, + + /// With Enumerable extension. + /// Returns all tokens owned by the given address, [] if unset. + /// Return type: TokensResponse. + Tokens { + owner: String, + start_after: Option, + limit: Option, + }, + /// With Enumerable extension. + /// Requires pagination. Lists all token_ids controlled by the contract. + /// Return type: TokensResponse. + AllTokens { + start_after: Option, + limit: Option, + }, + + /// Return the minter + Minter {}, +} + +impl TryInto for QueryMsg { + type Error = StdError; + + fn try_into(self) -> Result { + match self { + QueryMsg::OwnerOf { + token_id, + include_expired, + } => Ok(ParentQueryMsg::OwnerOf { + token_id, + include_expired, + }), + QueryMsg::Approval { + token_id, + spender, + include_expired, + } => Ok(ParentQueryMsg::Approval { + token_id, + spender, + include_expired, + }), + QueryMsg::Approvals { + token_id, + include_expired, + } => Ok(ParentQueryMsg::Approvals { + token_id, + include_expired, + }), + QueryMsg::AllOperators { + owner, + include_expired, + start_after, + limit, + } => Ok(ParentQueryMsg::AllOperators { + owner, + include_expired, + start_after, + limit, + }), + QueryMsg::NumTokens {} => Ok(ParentQueryMsg::NumTokens {}), + QueryMsg::ContractInfo {} => Ok(ParentQueryMsg::ContractInfo {}), + QueryMsg::NftInfo { token_id } => Ok(ParentQueryMsg::NftInfo { token_id }), + QueryMsg::AllNftInfo { + token_id, + include_expired, + } => Ok(ParentQueryMsg::AllNftInfo { + token_id, + include_expired, + }), + QueryMsg::Tokens { + owner, + start_after, + limit, + } => Ok(ParentQueryMsg::Tokens { + owner, + start_after, + limit, + }), + QueryMsg::AllTokens { start_after, limit } => { + Ok(ParentQueryMsg::AllTokens { start_after, limit }) + } + QueryMsg::Minter {} => Ok(ParentQueryMsg::Minter {}), + _ => Err(StdError::generic_err( + "Attempting to convert to a non-cw721 compatible message", + ) + .into()), + } + } +} diff --git a/contracts/account-nft/src/state.rs b/contracts/account-nft/src/state.rs new file mode 100644 index 000000000..326b460e2 --- /dev/null +++ b/contracts/account-nft/src/state.rs @@ -0,0 +1,4 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::Item; + +pub const PENDING_OWNER: Item = Item::new("pending_owner"); diff --git a/contracts/account-nft/tests/helpers.rs b/contracts/account-nft/tests/helpers.rs new file mode 100644 index 000000000..bfd53d774 --- /dev/null +++ b/contracts/account-nft/tests/helpers.rs @@ -0,0 +1,42 @@ +use account_nft::contract::{execute, instantiate, query}; +use anyhow::Result as AnyResult; +use cosmwasm_std::Addr; +use cw721_base::InstantiateMsg; +use cw_multi_test::{AppResponse, BasicApp, ContractWrapper, Executor}; + +use account_nft::execute_msg::ExecuteMsg as ExtendedExecuteMsg; + +pub fn instantiate_mock_nft_contract(app: &mut BasicApp, owner: &Addr) -> Addr { + let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); + let code_id = app.store_code(contract); + + app.instantiate_contract( + code_id, + owner.clone(), + &InstantiateMsg { + name: String::from("mock_nft"), + symbol: String::from("MOCK"), + minter: owner.to_string(), + }, + &[], + "mock-account-nft", + None, + ) + .unwrap() +} + +pub fn mint_action( + app: &mut BasicApp, + sender: &Addr, + contract_addr: &Addr, + token_owner: &Addr, +) -> AnyResult { + app.execute_contract( + sender.clone(), + contract_addr.clone(), + &ExtendedExecuteMsg::Mint { + user: token_owner.into(), + }, + &[], + ) +} diff --git a/contracts/account-nft/src/tests.rs b/contracts/account-nft/tests/mint_test.rs similarity index 51% rename from contracts/account-nft/src/tests.rs rename to contracts/account-nft/tests/mint_test.rs index 9e0664280..182e7a33f 100644 --- a/contracts/account-nft/src/tests.rs +++ b/contracts/account-nft/tests/mint_test.rs @@ -1,21 +1,21 @@ use std::fmt::Error; -use anyhow::Result as AnyResult; use cosmwasm_std::Addr; use cw721::OwnerOfResponse; -use cw721_base::{InstantiateMsg, QueryMsg}; -use cw_multi_test::{App, AppResponse, BasicApp, ContractWrapper, Executor}; +use cw721_base::QueryMsg; +use cw_multi_test::{App, AppResponse, BasicApp, Executor}; -use crate::contract::{execute, instantiate, query}; -use crate::msg::ExecuteMsg as ExtendedExecuteMsg; +use account_nft::execute_msg::ExecuteMsg as ExtendedExecuteMsg; + +use crate::helpers::{instantiate_mock_nft_contract, mint_action}; + +pub mod helpers; #[test] fn test_id_incrementer() { let mut app = App::default(); - let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); let owner = Addr::unchecked("owner"); - let code_id = app.store_code(contract); - let contract_addr = instantiate_mock_nft_contract(&mut app, &owner, code_id); + let contract_addr = instantiate_mock_nft_contract(&mut app, &owner); let user_1 = Addr::unchecked("user_1"); let res = mint_action(&mut app, &owner, &contract_addr, &user_1).unwrap(); @@ -39,46 +39,21 @@ fn test_id_incrementer() { #[test] fn test_only_owner_can_mint() { let mut app = App::default(); - let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); let owner = Addr::unchecked("owner"); - let code_id = app.store_code(contract); - let contract_addr = instantiate_mock_nft_contract(&mut app, &owner, code_id); + let contract_addr = instantiate_mock_nft_contract(&mut app, &owner); let bad_guy = Addr::unchecked("bad_guy"); let res = mint_action(&mut app, &bad_guy, &contract_addr, &bad_guy); - match res { - Ok(_) => panic!("Unauthorized access to minting function"), - Err(_) => {} - } -} - -#[test] -fn test_can_change_owner() { - let mut app = App::default(); - let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); - let original_owner = Addr::unchecked("original_owner"); - let code_id = app.store_code(contract); - let contract_addr = instantiate_mock_nft_contract(&mut app, &original_owner, code_id); - - let new_owner = Addr::unchecked("new_owner"); - replace_owner(&mut app, &original_owner, &contract_addr, &new_owner).unwrap(); - - let rover_user = Addr::unchecked("rover_user"); - mint_action(&mut app, &new_owner, &contract_addr, &rover_user).unwrap(); - - let res = mint_action(&mut app, &original_owner, &contract_addr, &rover_user); if res.is_ok() { - panic!("Original owner should no longer have access"); + panic!("Unauthorized access to minting function"); } } #[test] fn test_normal_base_cw721_actions_can_still_be_taken() { let mut app = App::default(); - let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); let owner = Addr::unchecked("owner"); - let code_id = app.store_code(contract); - let contract_addr = instantiate_mock_nft_contract(&mut app, &owner, code_id); + let contract_addr = instantiate_mock_nft_contract(&mut app, &owner); let rover_user = Addr::unchecked("rover_user"); let res = mint_action(&mut app, &owner, &contract_addr, &rover_user).unwrap(); @@ -111,54 +86,6 @@ fn assert_owner_is_correct( assert_eq!(user.to_string(), owner_res.owner) } -fn instantiate_mock_nft_contract(app: &mut BasicApp, owner: &Addr, code_id: u64) -> Addr { - app.instantiate_contract( - code_id, - owner.clone(), - &InstantiateMsg { - name: String::from("mock_nft"), - symbol: String::from("MOCK"), - minter: owner.to_string(), - }, - &[], - "mock-account-nft", - None, - ) - .unwrap() -} - -fn replace_owner( - app: &mut BasicApp, - current_owner: &Addr, - contract_addr: &Addr, - new_owner: &Addr, -) -> Result { - let update_msg: ExtendedExecuteMsg = ExtendedExecuteMsg::UpdateOwner { - new_owner: new_owner.to_string(), - }; - app.execute_contract( - current_owner.clone(), - contract_addr.clone(), - &update_msg, - &[], - ) - .map_err(|_| Error::default()) -} - -fn mint_action( - app: &mut BasicApp, - sender: &Addr, - contract_addr: &Addr, - token_owner: &Addr, -) -> AnyResult { - app.execute_contract( - sender.clone(), - contract_addr.clone(), - &ExtendedExecuteMsg::Mint { user: token_owner.into() }, - &[], - ) -} - fn get_token_id(res: AppResponse) -> String { let attr: Vec<&String> = res .events diff --git a/contracts/account-nft/tests/ownership_test.rs b/contracts/account-nft/tests/ownership_test.rs new file mode 100644 index 000000000..ee2b465cd --- /dev/null +++ b/contracts/account-nft/tests/ownership_test.rs @@ -0,0 +1,112 @@ +use anyhow::Result as AnyResult; +use cosmwasm_std::{Addr, StdResult}; +use cw721_base::MinterResponse; +use cw_multi_test::{App, AppResponse, BasicApp, Executor}; + +use account_nft::execute_msg::ExecuteMsg as ExtendedExecuteMsg; +use account_nft::query_msg::QueryMsg; + +use crate::helpers::instantiate_mock_nft_contract; + +pub mod helpers; + +#[test] +fn test_only_owner_can_propose_ownership_transfer() { + let mut app = App::default(); + let owner = Addr::unchecked("owner"); + let contract_addr = instantiate_mock_nft_contract(&mut app, &owner); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = propose_new_owner(&mut app, &contract_addr, &bad_guy, &bad_guy); + + if res.is_ok() { + panic!("Non-owner should not be able to propose ownership transfer"); + } +} + +#[test] +fn test_propose_ownership_stores() { + let mut app = App::default(); + let original_owner = Addr::unchecked("owner"); + let contract_addr = instantiate_mock_nft_contract(&mut app, &original_owner); + + let new_owner = Addr::unchecked("new_owner"); + propose_new_owner(&mut app, &contract_addr, &original_owner, &new_owner).unwrap(); + + let pending_owner_in_storage = query_pending_owner(&app, &contract_addr).unwrap(); + assert_eq!(pending_owner_in_storage, new_owner); +} + +#[test] +fn test_proposed_owner_can_accept_ownership() { + let mut app = App::default(); + let original_owner = Addr::unchecked("owner"); + let contract_addr = instantiate_mock_nft_contract(&mut app, &original_owner); + + let new_owner = Addr::unchecked("new_owner"); + propose_new_owner(&mut app, &contract_addr, &original_owner, &new_owner).unwrap(); + + accept_proposed_owner(&mut app, &contract_addr, &new_owner).unwrap(); + + let res = query_pending_owner(&app, &contract_addr); + if res.is_ok() { + panic!("Proposed owner should have been removed from storage"); + } + + let res: MinterResponse = app + .wrap() + .query_wasm_smart(contract_addr, &QueryMsg::Minter {}) + .unwrap(); + + assert_eq!(res.minter, new_owner) +} + +#[test] +fn test_only_proposed_owner_can_accept() { + let mut app = App::default(); + let original_owner = Addr::unchecked("owner"); + let contract_addr = instantiate_mock_nft_contract(&mut app, &original_owner); + + let new_owner = Addr::unchecked("new_owner"); + propose_new_owner(&mut app, &contract_addr, &original_owner, &new_owner).unwrap(); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = accept_proposed_owner(&mut app, &contract_addr, &bad_guy); + if res.is_ok() { + panic!("Only proposed owner can accept ownership"); + } +} + +fn query_pending_owner(app: &BasicApp, contract_addr: &Addr) -> StdResult { + app.wrap() + .query_wasm_smart(contract_addr, &QueryMsg::ProposedNewOwner {}) +} + +fn propose_new_owner( + app: &mut BasicApp, + contract_addr: &Addr, + sender: &Addr, + proposed_new_owner: &Addr, +) -> AnyResult { + app.execute_contract( + sender.clone(), + contract_addr.clone(), + &ExtendedExecuteMsg::ProposeNewOwner { + new_owner: proposed_new_owner.into(), + }, + &[], + ) +} + +fn accept_proposed_owner( + app: &mut BasicApp, + contract_addr: &Addr, + sender: &Addr, +) -> AnyResult { + app.execute_contract( + sender.clone(), + contract_addr.clone(), + &ExtendedExecuteMsg::AcceptOwnership {}, + &[], + ) +} diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 302973274..c47b04185 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -5,7 +5,7 @@ use cw2::set_contract_version; use rover::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::execute::{try_create_credit_account, try_update_config}; +use crate::execute::{create_credit_account, update_config}; use crate::instantiate::store_config; use crate::query::{query_allowed_assets, query_allowed_vaults, query_config}; @@ -32,9 +32,9 @@ pub fn execute( msg: ExecuteMsg, ) -> StdResult { match msg { - ExecuteMsg::CreateCreditAccount {} => try_create_credit_account(deps, info.sender), + ExecuteMsg::CreateCreditAccount {} => create_credit_account(deps, info.sender), ExecuteMsg::UpdateConfig { account_nft, owner } => { - try_update_config(deps, info, account_nft, owner) + update_config(deps, info, account_nft, owner) } } } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 1a5c0ffeb..cad874b84 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -1,18 +1,19 @@ +use account_nft::execute_msg::ExecuteMsg as NftExecuteMsg; use cosmwasm_std::{ - to_binary, Addr, Attribute, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, - WasmMsg, + to_binary, Addr, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, WasmMsg, }; -use account_nft::msg::{ExecuteMsg as NftExecuteMsg}; use crate::state::{ACCOUNT_NFT, OWNER}; -pub fn try_create_credit_account(deps: DepsMut, user: Addr) -> StdResult { +pub fn create_credit_account(deps: DepsMut, user: Addr) -> StdResult { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; let nft_mint_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: contract_addr.to_string(), funds: vec![], - msg: to_binary(&NftExecuteMsg::Mint { user: user.to_string() })?, + msg: to_binary(&NftExecuteMsg::Mint { + user: user.to_string(), + })?, }); Ok(Response::new() @@ -20,7 +21,7 @@ pub fn try_create_credit_account(deps: DepsMut, user: Addr) -> StdResult, @@ -35,25 +36,29 @@ pub fn try_update_config( ))); } - let mut attributes: Vec = vec![]; + let mut response = Response::new(); if let Some(addr_str) = new_account_nft { let validated = deps.api.addr_validate(addr_str.as_str())?; ACCOUNT_NFT.save(deps.storage, &validated)?; - attributes.push(Attribute::new( - "action", - "rover/credit_manager/update_config/account_nft", - )); + + // Accept ownership. NFT contract owner must have proposed Rover as a new owner first. + let accept_ownership_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: addr_str, + funds: vec![], + msg: to_binary(&NftExecuteMsg::AcceptOwnership {})?, + }); + + response = response + .add_message(accept_ownership_msg) + .add_attribute("action", "rover/credit_manager/update_config/account_nft"); } if let Some(addr_str) = new_owner { let validated = deps.api.addr_validate(addr_str.as_str())?; OWNER.save(deps.storage, &validated)?; - attributes.push(Attribute::new( - "action", - "rover/credit_manager/update_config/owner", - )); + response = response.add_attribute("action", "rover/credit_manager/update_config/owner"); } - Ok(Response::new().add_attributes(attributes)) + Ok(response) } diff --git a/contracts/credit-manager/tests/create_credit_account_test.rs b/contracts/credit-manager/tests/create_credit_account_test.rs index bdbae9763..391732978 100644 --- a/contracts/credit-manager/tests/create_credit_account_test.rs +++ b/contracts/credit-manager/tests/create_credit_account_test.rs @@ -4,7 +4,7 @@ use cw721::OwnerOfResponse; use cw721_base::{InstantiateMsg as NftInstantiateMsg, QueryMsg as NftQueryMsg}; use cw_multi_test::{App, AppResponse, Executor}; -use account_nft::msg::ExecuteMsg as NftExecuteMsg; +use account_nft::execute_msg::ExecuteMsg as NftExecuteMsg; use rover::ExecuteMsg::{CreateCreditAccount, UpdateConfig}; use rover::{ConfigResponse, InstantiateMsg, QueryMsg}; @@ -59,7 +59,7 @@ fn test_create_credit_account() { panic!("Should have thrown error due to nft contract not yet set"); } - app.execute_contract( + let res = app.execute_contract( owner.clone(), manager_contract_addr.clone(), &UpdateConfig { @@ -67,21 +67,29 @@ fn test_create_credit_account() { owner: None, }, &[], - ) - .unwrap(); - - let res = mock_create_credit_account(&mut app, &manager_contract_addr, &user); + ); if res.is_ok() { - panic!("Should have thrown error due to nft contract not setting new owner yet"); + panic!("Should have thrown error due to nft contract not proposing a new owner yet"); } - let update_msg: NftExecuteMsg = NftExecuteMsg::UpdateOwner { + let proposal_msg: NftExecuteMsg = NftExecuteMsg::ProposeNewOwner { new_owner: manager_contract_addr.to_string(), }; - app.execute_contract(user.clone(), nft_contract_addr.clone(), &update_msg, &[]) + app.execute_contract(owner.clone(), nft_contract_addr.clone(), &proposal_msg, &[]) .unwrap(); + app.execute_contract( + owner.clone(), + manager_contract_addr.clone(), + &UpdateConfig { + account_nft: Some(nft_contract_addr.to_string()), + owner: None, + }, + &[], + ) + .unwrap(); + let res = mock_create_credit_account(&mut app, &manager_contract_addr, &user).unwrap(); let attr: Vec<&String> = res diff --git a/contracts/credit-manager/tests/update_config_test.rs b/contracts/credit-manager/tests/update_config_test.rs index bb467488c..96496a463 100644 --- a/contracts/credit-manager/tests/update_config_test.rs +++ b/contracts/credit-manager/tests/update_config_test.rs @@ -1,9 +1,11 @@ +use account_nft::execute_msg::ExecuteMsg as NftExecuteMsg; use cosmwasm_std::Addr; +use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, Executor}; use rover::{ConfigResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::helpers::{mock_app, mock_contract}; +use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract}; pub mod helpers; @@ -20,7 +22,9 @@ fn test_update_config_works_with_full_config() { assert_eq!(config_res.owner, original_owner.to_string()); let new_owner = Addr::unchecked("new_owner"); - let account_nft_contract = Addr::unchecked("account_nft_contract"); + + let account_nft_contract = setup_nft_contract(&mut app, &original_owner, &contract_addr); + app.execute_contract( original_owner.clone(), contract_addr.clone(), @@ -34,7 +38,10 @@ fn test_update_config_works_with_full_config() { let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!(config_res.account_nft, Some(account_nft_contract.to_string())); + assert_eq!( + config_res.account_nft, + Some(account_nft_contract.to_string()) + ); assert_eq!(config_res.owner, new_owner.to_string()); } @@ -50,7 +57,7 @@ fn test_update_config_works_with_some_config() { assert_eq!(config_res.account_nft, None); assert_eq!(config_res.owner, original_owner.to_string()); - let account_nft_contract = Addr::unchecked("account_nft_contract"); + let account_nft_contract = setup_nft_contract(&mut app, &original_owner, &contract_addr); app.execute_contract( original_owner.clone(), contract_addr.clone(), @@ -64,7 +71,10 @@ fn test_update_config_works_with_some_config() { let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!(config_res.account_nft, Some(account_nft_contract.to_string())); + assert_eq!( + config_res.account_nft, + Some(account_nft_contract.to_string()) + ); assert_eq!(config_res.owner, original_owner.to_string()); let new_owner = Addr::unchecked("new_owner"); @@ -80,7 +90,10 @@ fn test_update_config_works_with_some_config() { .unwrap(); let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!(config_res.account_nft, Some(account_nft_contract.to_string())); + assert_eq!( + config_res.account_nft, + Some(account_nft_contract.to_string()) + ); assert_eq!(config_res.owner, new_owner.to_string()); } @@ -129,3 +142,28 @@ fn instantiate(app: &mut App, original_owner: &Addr, code_id: u64) -> Addr { ) .unwrap() } + +fn setup_nft_contract(app: &mut App, owner: &Addr, contract: &Addr) -> Addr { + let nft_contract_code_id = app.store_code(mock_account_nft_contract()); + let nft_contract_addr = app + .instantiate_contract( + nft_contract_code_id, + owner.clone(), + &NftInstantiateMsg { + name: String::from("Rover Credit Account"), + symbol: String::from("RCA"), + minter: owner.to_string(), + }, + &[], + "manager-mock-account-nft", + None, + ) + .unwrap(); + + let proposal_msg: NftExecuteMsg = NftExecuteMsg::ProposeNewOwner { + new_owner: contract.to_string(), + }; + app.execute_contract(owner.clone(), nft_contract_addr.clone(), &proposal_msg, &[]) + .unwrap(); + nft_contract_addr +} From 383e57b1ddd19b64ec4c05aeb76408fb8ef335b4 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 11 Jul 2022 14:05:49 +0200 Subject: [PATCH 035/218] Moving messages to new folder --- contracts/account-nft/src/contract.rs | 3 +-- contracts/account-nft/src/lib.rs | 3 +-- contracts/account-nft/src/{execute_msg.rs => msg/execute.rs} | 0 contracts/account-nft/src/msg/mod.rs | 5 +++++ contracts/account-nft/src/{query_msg.rs => msg/query.rs} | 2 +- contracts/account-nft/tests/helpers.rs | 4 ++-- contracts/account-nft/tests/mint_test.rs | 2 +- contracts/account-nft/tests/ownership_test.rs | 3 +-- contracts/credit-manager/src/execute.rs | 2 +- contracts/credit-manager/tests/create_credit_account_test.rs | 2 +- contracts/credit-manager/tests/update_config_test.rs | 2 +- 11 files changed, 15 insertions(+), 13 deletions(-) rename contracts/account-nft/src/{execute_msg.rs => msg/execute.rs} (100%) create mode 100644 contracts/account-nft/src/msg/mod.rs rename contracts/account-nft/src/{query_msg.rs => msg/query.rs} (100%) diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index eab6f0f07..169a20877 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -8,9 +8,8 @@ use cosmwasm_std::{ use cw721_base::{ContractError, Cw721Contract, InstantiateMsg}; use crate::execute::{accept_ownership, mint, propose_new_owner}; -use crate::execute_msg::ExecuteMsg; +use crate::msg::{ExecuteMsg, QueryMsg}; use crate::query::query_proposed_new_owner; -use crate::query_msg::QueryMsg; // Extending CW721 base contract pub type Parent<'a> = Cw721Contract<'a, Empty, Empty>; diff --git a/contracts/account-nft/src/lib.rs b/contracts/account-nft/src/lib.rs index 99dbdc298..735a7a3ea 100644 --- a/contracts/account-nft/src/lib.rs +++ b/contracts/account-nft/src/lib.rs @@ -1,6 +1,5 @@ pub mod contract; pub mod execute; -pub mod execute_msg; +pub mod msg; pub mod query; -pub mod query_msg; pub mod state; diff --git a/contracts/account-nft/src/execute_msg.rs b/contracts/account-nft/src/msg/execute.rs similarity index 100% rename from contracts/account-nft/src/execute_msg.rs rename to contracts/account-nft/src/msg/execute.rs diff --git a/contracts/account-nft/src/msg/mod.rs b/contracts/account-nft/src/msg/mod.rs new file mode 100644 index 000000000..3b2787b8a --- /dev/null +++ b/contracts/account-nft/src/msg/mod.rs @@ -0,0 +1,5 @@ +mod execute; +mod query; + +pub use execute::ExecuteMsg; +pub use query::QueryMsg; diff --git a/contracts/account-nft/src/query_msg.rs b/contracts/account-nft/src/msg/query.rs similarity index 100% rename from contracts/account-nft/src/query_msg.rs rename to contracts/account-nft/src/msg/query.rs index c94cb7aec..294c2951f 100644 --- a/contracts/account-nft/src/query_msg.rs +++ b/contracts/account-nft/src/msg/query.rs @@ -1,6 +1,6 @@ -use cosmwasm_std::StdError; use std::convert::TryInto; +use cosmwasm_std::StdError; use cw721_base::QueryMsg as ParentQueryMsg; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/contracts/account-nft/tests/helpers.rs b/contracts/account-nft/tests/helpers.rs index bfd53d774..4605df196 100644 --- a/contracts/account-nft/tests/helpers.rs +++ b/contracts/account-nft/tests/helpers.rs @@ -1,10 +1,10 @@ -use account_nft::contract::{execute, instantiate, query}; use anyhow::Result as AnyResult; use cosmwasm_std::Addr; use cw721_base::InstantiateMsg; use cw_multi_test::{AppResponse, BasicApp, ContractWrapper, Executor}; -use account_nft::execute_msg::ExecuteMsg as ExtendedExecuteMsg; +use account_nft::contract::{execute, instantiate, query}; +use account_nft::msg::ExecuteMsg as ExtendedExecuteMsg; pub fn instantiate_mock_nft_contract(app: &mut BasicApp, owner: &Addr) -> Addr { let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); diff --git a/contracts/account-nft/tests/mint_test.rs b/contracts/account-nft/tests/mint_test.rs index 182e7a33f..6f5420b8d 100644 --- a/contracts/account-nft/tests/mint_test.rs +++ b/contracts/account-nft/tests/mint_test.rs @@ -5,7 +5,7 @@ use cw721::OwnerOfResponse; use cw721_base::QueryMsg; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; -use account_nft::execute_msg::ExecuteMsg as ExtendedExecuteMsg; +use account_nft::msg::ExecuteMsg as ExtendedExecuteMsg; use crate::helpers::{instantiate_mock_nft_contract, mint_action}; diff --git a/contracts/account-nft/tests/ownership_test.rs b/contracts/account-nft/tests/ownership_test.rs index ee2b465cd..4309542a3 100644 --- a/contracts/account-nft/tests/ownership_test.rs +++ b/contracts/account-nft/tests/ownership_test.rs @@ -3,8 +3,7 @@ use cosmwasm_std::{Addr, StdResult}; use cw721_base::MinterResponse; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; -use account_nft::execute_msg::ExecuteMsg as ExtendedExecuteMsg; -use account_nft::query_msg::QueryMsg; +use account_nft::msg::{ExecuteMsg as ExtendedExecuteMsg, QueryMsg}; use crate::helpers::instantiate_mock_nft_contract; diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index cad874b84..6cca95b60 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -1,4 +1,4 @@ -use account_nft::execute_msg::ExecuteMsg as NftExecuteMsg; +use account_nft::msg::ExecuteMsg as NftExecuteMsg; use cosmwasm_std::{ to_binary, Addr, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, WasmMsg, }; diff --git a/contracts/credit-manager/tests/create_credit_account_test.rs b/contracts/credit-manager/tests/create_credit_account_test.rs index 391732978..c80b99ed3 100644 --- a/contracts/credit-manager/tests/create_credit_account_test.rs +++ b/contracts/credit-manager/tests/create_credit_account_test.rs @@ -4,7 +4,7 @@ use cw721::OwnerOfResponse; use cw721_base::{InstantiateMsg as NftInstantiateMsg, QueryMsg as NftQueryMsg}; use cw_multi_test::{App, AppResponse, Executor}; -use account_nft::execute_msg::ExecuteMsg as NftExecuteMsg; +use account_nft::msg::ExecuteMsg as NftExecuteMsg; use rover::ExecuteMsg::{CreateCreditAccount, UpdateConfig}; use rover::{ConfigResponse, InstantiateMsg, QueryMsg}; diff --git a/contracts/credit-manager/tests/update_config_test.rs b/contracts/credit-manager/tests/update_config_test.rs index 96496a463..efcec558b 100644 --- a/contracts/credit-manager/tests/update_config_test.rs +++ b/contracts/credit-manager/tests/update_config_test.rs @@ -1,4 +1,4 @@ -use account_nft::execute_msg::ExecuteMsg as NftExecuteMsg; +use account_nft::msg::ExecuteMsg as NftExecuteMsg; use cosmwasm_std::Addr; use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, Executor}; From 29abb19aaf8050327142e5973597cdbbb47a4ed1 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 24 Jun 2022 16:34:31 +0200 Subject: [PATCH 036/218] User can deposit native & cw20 assets --- Cargo.lock | 19 + artifacts/account_nft.wasm | Bin 0 -> 290078 bytes contracts/credit-manager/Cargo.toml | 7 +- contracts/credit-manager/src/contract.rs | 20 +- contracts/credit-manager/src/deposit.rs | 110 ++++++ contracts/credit-manager/src/error.rs | 33 ++ contracts/credit-manager/src/execute.rs | 109 +++++- contracts/credit-manager/src/instantiate.rs | 3 +- contracts/credit-manager/src/lib.rs | 2 + contracts/credit-manager/src/query.rs | 32 +- contracts/credit-manager/src/state.rs | 8 +- .../tests/allow_list_query_test.rs | 2 +- .../tests/create_credit_account_test.rs | 62 +--- .../credit-manager/tests/deposit_cw20_test.rs | 176 +++++++++ .../tests/deposit_native_test.rs | 341 ++++++++++++++++++ .../credit-manager/tests/dispatch_test.rs | 70 ++++ contracts/credit-manager/tests/helpers.rs | 165 ++++++++- .../credit-manager/tests/instantiate_test.rs | 3 +- .../tests/position_query_test.rs | 99 +++++ .../tests/update_config_test.rs | 45 ++- packages/rover/src/lib.rs | 45 +-- packages/rover/src/msg/execute.rs | 68 ++++ packages/rover/src/msg/instantiate.rs | 10 + packages/rover/src/msg/mod.rs | 7 + packages/rover/src/msg/query.rs | 36 ++ scripts/tests/instantiate.test.ts | 2 +- 26 files changed, 1327 insertions(+), 147 deletions(-) create mode 100644 artifacts/account_nft.wasm create mode 100644 contracts/credit-manager/src/deposit.rs create mode 100644 contracts/credit-manager/src/error.rs create mode 100644 contracts/credit-manager/tests/deposit_cw20_test.rs create mode 100644 contracts/credit-manager/tests/deposit_native_test.rs create mode 100644 contracts/credit-manager/tests/dispatch_test.rs create mode 100644 contracts/credit-manager/tests/position_query_test.rs create mode 100644 packages/rover/src/msg/execute.rs create mode 100644 packages/rover/src/msg/instantiate.rs create mode 100644 packages/rover/src/msg/mod.rs create mode 100644 packages/rover/src/msg/query.rs diff --git a/Cargo.lock b/Cargo.lock index 8f3240f8d..e4ee96149 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,11 +142,14 @@ dependencies = [ "cw-multi-test", "cw-storage-plus", "cw2", + "cw20", + "cw20-base", "cw721", "cw721-base", "rover", "schemars", "serde", + "thiserror", ] [[package]] @@ -269,6 +272,22 @@ dependencies = [ "serde", ] +[[package]] +name = "cw20-base" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0306e606581f4fb45e82bcbb7f0333179ed53dd949c6523f01a99b4bfc1475a0" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "cw2", + "cw20", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw721" version = "0.13.2" diff --git a/artifacts/account_nft.wasm b/artifacts/account_nft.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2c37b1267e58a7ce32a73f9cf053adedb56553d4 GIT binary patch literal 290078 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%0D8APm+b%Ew6}Ho}=ZChkv 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 literal 0 HcmV?d00001 diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 2f851dcea..4587585d5 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -17,13 +17,16 @@ rover = { version = "0.1", path = "../../packages/rover" } cosmwasm-std = "1.0" cw2 = "0.13" +cw20 = "0.13" +cw20-base = { version = "0.13", features = ["library"] } +cw721 = "0.13" +cw721-base = { version = "0.13", features = ["library"] } cw-asset = "2.1" cw-storage-plus = "0.13" schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } +thiserror = "1.0" [dev-dependencies] anyhow = "1" -cw721 = "0.13" -cw721-base = { version = "0.13", features = ["library"] } cw-multi-test = "0.13" diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index c47b04185..46f75c3b8 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -3,11 +3,13 @@ use cosmwasm_std::{ }; use cw2::set_contract_version; -use rover::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::execute::{create_credit_account, update_config}; +use crate::deposit::receive_cw20; +use crate::error::ContractError; +use crate::execute::{create_credit_account, dispatch_actions, execute_callback, update_config}; use crate::instantiate::store_config; -use crate::query::{query_allowed_assets, query_allowed_vaults, query_config}; +use crate::query::{query_allowed_assets, query_allowed_vaults, query_config, query_position}; const CONTRACT_NAME: &str = "crates.io:rover-credit-manager"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -18,7 +20,7 @@ pub fn instantiate( _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> StdResult { +) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; store_config(deps, &msg)?; Ok(Response::new().add_attribute("method", "instantiate")) @@ -27,15 +29,20 @@ pub fn instantiate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, - _env: Env, + env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> StdResult { +) -> Result { match msg { ExecuteMsg::CreateCreditAccount {} => create_credit_account(deps, info.sender), ExecuteMsg::UpdateConfig { account_nft, owner } => { update_config(deps, info, account_nft, owner) } + ExecuteMsg::Callback(callback) => execute_callback(deps, info, env, callback), + ExecuteMsg::UpdateCreditAccount { token_id, actions } => { + dispatch_actions(deps, env, info, token_id, actions) + } + ExecuteMsg::Receive(msg) => receive_cw20(deps, info, msg), } } @@ -49,5 +56,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::AllowedAssets { start_after, limit } => { to_binary(&query_allowed_assets(deps, start_after, limit)?) } + QueryMsg::Position { token_id } => to_binary(&query_position(deps, token_id)?), } } diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs new file mode 100644 index 000000000..95957be48 --- /dev/null +++ b/contracts/credit-manager/src/deposit.rs @@ -0,0 +1,110 @@ +use std::ops::Add; + +use cosmwasm_std::{from_binary, Api, DepsMut, MessageInfo, Response, StdResult, Storage, Uint128}; +use cw20::Cw20ReceiveMsg; +use cw_asset::{Asset, AssetInfo, AssetInfoUnchecked, AssetList, AssetUnchecked}; + +use rover::msg::execute::ReceiveMsg; + +use crate::error::ContractError; +use crate::error::ContractError::{FundsMismatch, NotWhitelisted, WrongDepositMethodForCW20}; +use crate::execute::assert_is_token_owner; +use crate::state::{ALLOWED_ASSETS, ASSETS}; + +pub fn native_deposit( + storage: &mut dyn Storage, + api: &dyn Api, + response: Response, + nft_token_id: &String, + asset_unchecked: AssetUnchecked, + received_coins: &mut AssetList, +) -> Result { + let asset = asset_unchecked.check(api, None)?; + assert_asset_is_whitelisted(storage, &asset.info)?; + + if asset.amount.is_zero() { + return Ok(response); + } + + match &asset.info { + AssetInfo::Native(_) => { + assert_sent_fund(&asset, received_coins)?; + received_coins.deduct(&asset)?; + } + AssetInfo::Cw20(_) => { + return Err(WrongDepositMethodForCW20 {}); + } + } + + // increase the user asset amount + increment_position(storage, &nft_token_id, &asset.info, &asset.amount)?; + + Ok(response + .add_attribute("deposit_received", asset.to_string()) + .add_attribute("action", "rover/credit_manager/callback/deposit")) +} + +/// Assert that fund of exactly the same type and amount was sent along with a message +fn assert_sent_fund(expected: &Asset, received_coins: &AssetList) -> Result<(), ContractError> { + let received_amount = if let Some(coin) = received_coins.find(&expected.info) { + coin.amount + } else { + Uint128::zero() + }; + + if received_amount != expected.amount { + return Err(FundsMismatch { + expected: expected.amount, + received: received_amount, + }); + } + + Ok(()) +} + +pub fn receive_cw20( + deps: DepsMut, + info: MessageInfo, + cw20_msg: Cw20ReceiveMsg, +) -> Result { + match from_binary(&cw20_msg.msg)? { + ReceiveMsg::Deposit { token_id } => { + let sender = deps.api.addr_validate(&cw20_msg.sender)?; + assert_is_token_owner(&deps, &sender, &token_id.clone().into())?; + let asset = AssetInfoUnchecked::cw20(&info.sender).check(deps.api, None)?; + assert_asset_is_whitelisted(deps.storage, &asset)?; + increment_position(deps.storage, &token_id, &asset, &cw20_msg.amount)?; + Ok(Response::new() + .add_attribute("deposit_received", asset.to_string()) + .add_attribute("action", "rover/execute/receive_cw20")) + } + } +} + +fn assert_asset_is_whitelisted( + storage: &mut dyn Storage, + asset: &AssetInfo, +) -> Result<(), ContractError> { + let is_whitelisted = ALLOWED_ASSETS.has(storage, asset.clone().into()); + if !is_whitelisted { + return Err(NotWhitelisted(asset.to_string())); + } + Ok(()) +} + +fn increment_position( + storage: &mut dyn Storage, + token_id: &String, + asset: &AssetInfo, + amount: &Uint128, +) -> StdResult<()> { + let position = ASSETS + .load(storage, (token_id.clone(), asset.clone().into())) + .unwrap_or(Uint128::zero()); + ASSETS.save( + storage, + (token_id.clone(), asset.clone().into()), + &position.add(amount), + )?; + Ok(()) +} diff --git a/contracts/credit-manager/src/error.rs b/contracts/credit-manager/src/error.rs new file mode 100644 index 000000000..ecd469852 --- /dev/null +++ b/contracts/credit-manager/src/error.rs @@ -0,0 +1,33 @@ +use cosmwasm_std::{Addr, StdError, Uint128}; +use cw_asset::AssetListBase; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{user:?} is not authorized to {action:?}")] + Unauthorized { user: String, action: String }, + + #[error("{0} is not whitelisted")] + NotWhitelisted(String), + + #[error("Extra funds received: {0}")] + ExtraFundsReceived(AssetListBase), + + #[error("Deposits of CW20's should come via Cw20ExecuteMsg::Send to cw20 contract specifying Rover's ReceiveMsg")] + WrongDepositMethodForCW20 {}, + + #[error("Sent fund mismatch. Expected: {expected:?}, received {received:?}")] + FundsMismatch { + expected: Uint128, + received: Uint128, + }, + + #[error("This method cannot be invoked externally")] + ExternalInvocation {}, + + #[error("{user:?} is not the owner of {token_id:?}")] + NotTokenOwner { user: String, token_id: String }, +} diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 6cca95b60..21d279c46 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -1,11 +1,21 @@ -use account_nft::msg::ExecuteMsg as NftExecuteMsg; use cosmwasm_std::{ - to_binary, Addr, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, WasmMsg, + to_binary, Addr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdResult, WasmMsg, }; +use cw721::OwnerOfResponse; +use cw721_base::QueryMsg; +use cw_asset::AssetList; + +use account_nft::msg::ExecuteMsg as NftExecuteMsg; +use rover::msg::execute::{Action, CallbackMsg}; +use crate::deposit::native_deposit; +use crate::error::ContractError; +use crate::error::ContractError::{ + ExternalInvocation, ExtraFundsReceived, NotTokenOwner, Unauthorized, +}; use crate::state::{ACCOUNT_NFT, OWNER}; -pub fn create_credit_account(deps: DepsMut, user: Addr) -> StdResult { +pub fn create_credit_account(deps: DepsMut, user: Addr) -> Result { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; let nft_mint_msg = CosmosMsg::Wasm(WasmMsg::Execute { @@ -26,14 +36,14 @@ pub fn update_config( info: MessageInfo, new_account_nft: Option, new_owner: Option, -) -> StdResult { +) -> Result { let owner = OWNER.load(deps.storage)?; if info.sender != owner { - return Err(StdError::generic_err(format!( - "{} is not authorized to update config", - info.sender - ))); + return Err(Unauthorized { + user: info.sender.into(), + action: String::from("update config"), + }); } let mut response = Response::new(); @@ -62,3 +72,86 @@ pub fn update_config( Ok(response) } + +pub fn dispatch_actions( + deps: DepsMut, + env: Env, + info: MessageInfo, + token_id: String, + actions: Vec, +) -> Result { + assert_is_token_owner(&deps, &info.sender, &token_id)?; + + let mut response = Response::new(); + let mut callbacks: Vec = vec![]; + let mut received_coins = AssetList::from(&info.funds); + + for action in actions { + match action { + Action::NativeDeposit(asset) => { + response = native_deposit( + deps.storage, + deps.api, + response, + &token_id, + asset, + &mut received_coins, + )?; + } + Action::Placeholder { .. } => callbacks.push(CallbackMsg::Placeholder {}), + } + } + + // after all deposits have been handled, we assert that the `received_natives` list is empty + // this way, we ensure that the user does not send any extra fund which will get lost in the contract + if received_coins.len() > 0 { + return Err(ExtraFundsReceived(received_coins)); + } + + let callback_msgs = callbacks + .iter() + .map(|callback| callback.into_cosmos_msg(&env.contract.address)) + .collect::>>()?; + + Ok(response + .add_messages(callback_msgs) + .add_attribute("action", "rover/execute/update_credit_account")) +} + +pub fn execute_callback( + _deps: DepsMut, + info: MessageInfo, + env: Env, + callback: CallbackMsg, +) -> Result { + if info.sender != env.contract.address { + return Err(ExternalInvocation {}); + } + match callback { + CallbackMsg::Placeholder { .. } => Ok(Response::new()), + } +} + +pub fn assert_is_token_owner( + deps: &DepsMut, + user: &Addr, + token_id: &String, +) -> Result<(), ContractError> { + let contract_addr = ACCOUNT_NFT.load(deps.storage)?; + let owner_res: OwnerOfResponse = deps.querier.query_wasm_smart( + contract_addr, + &QueryMsg::OwnerOf { + token_id: token_id.clone(), + include_expired: None, + }, + )?; + + if user.as_str() != owner_res.owner { + return Err(NotTokenOwner { + user: user.to_string(), + token_id: token_id.clone(), + }); + } + + Ok(()) +} diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 6babfef61..da6708ad4 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,6 +1,5 @@ use cosmwasm_std::{DepsMut, StdResult}; - -use rover::InstantiateMsg; +use rover::msg::InstantiateMsg; use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index cf3255bea..13b3a6eee 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -1,5 +1,7 @@ pub mod contract; +pub mod deposit; +pub mod error; pub mod execute; pub mod instantiate; pub mod query; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index b38c4ea7a..20f81d1a2 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,9 +1,11 @@ -use crate::state::{ACCOUNT_NFT, ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; +use std::convert::TryFrom; + use cosmwasm_std::{Addr, Deps, Order, StdResult}; -use cw_asset::{AssetInfo, AssetInfoKey, AssetInfoUnchecked}; +use cw_asset::{AssetInfo, AssetInfoKey, AssetInfoUnchecked, AssetUnchecked}; use cw_storage_plus::Bound; -use rover::ConfigResponse; -use std::convert::TryFrom; +use rover::msg::query::{ConfigResponse, PositionResponse}; + +use crate::state::{ACCOUNT_NFT, ALLOWED_ASSETS, ALLOWED_VAULTS, ASSETS, OWNER}; const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; @@ -11,7 +13,27 @@ const DEFAULT_LIMIT: u32 = 10; pub fn query_config(deps: Deps) -> StdResult { Ok(ConfigResponse { owner: OWNER.load(deps.storage)?.into(), - account_nft: ACCOUNT_NFT.may_load(deps.storage)?.map(|addr| addr.to_string()), + account_nft: ACCOUNT_NFT + .may_load(deps.storage)? + .map(|addr| addr.to_string()), + }) +} + +pub fn query_position(deps: Deps, token_id: String) -> StdResult { + let res: StdResult> = ASSETS + .prefix(token_id.clone()) + .range(deps.storage, None, None, Order::Ascending) + .into_iter() + .map(|res| { + let (asset_info_key, amount) = res?; + let info_unchecked = AssetInfoUnchecked::try_from(asset_info_key.clone())?; + Ok(AssetUnchecked::new(info_unchecked, amount.u128())) + }) + .collect(); + + Ok(PositionResponse { + token_id, + assets: res?, }) } diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 2f05ed17a..b9307734a 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,9 +1,13 @@ -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Uint128}; use cw_asset::AssetInfoKey; use cw_storage_plus::{Item, Map}; +/* Contract config */ pub const OWNER: Item = Item::new("owner"); pub const ACCOUNT_NFT: Item = Item::new("account_nft"); - pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); + +/* Positions */ +type NftTokenId = String; +pub const ASSETS: Map<(NftTokenId, AssetInfoKey), Uint128> = Map::new("assets"); diff --git a/contracts/credit-manager/tests/allow_list_query_test.rs b/contracts/credit-manager/tests/allow_list_query_test.rs index f883981d8..e55e8e089 100644 --- a/contracts/credit-manager/tests/allow_list_query_test.rs +++ b/contracts/credit-manager/tests/allow_list_query_test.rs @@ -2,7 +2,7 @@ use cosmwasm_std::Addr; use cw_asset::AssetInfoUnchecked; use cw_multi_test::Executor; -use rover::{InstantiateMsg, QueryMsg}; +use rover::msg::{InstantiateMsg, QueryMsg}; use crate::helpers::{mock_app, mock_contract}; diff --git a/contracts/credit-manager/tests/create_credit_account_test.rs b/contracts/credit-manager/tests/create_credit_account_test.rs index c80b99ed3..1d3e581ef 100644 --- a/contracts/credit-manager/tests/create_credit_account_test.rs +++ b/contracts/credit-manager/tests/create_credit_account_test.rs @@ -1,14 +1,16 @@ -use anyhow::Result as AnyResult; use cosmwasm_std::Addr; use cw721::OwnerOfResponse; -use cw721_base::{InstantiateMsg as NftInstantiateMsg, QueryMsg as NftQueryMsg}; -use cw_multi_test::{App, AppResponse, Executor}; - -use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use rover::ExecuteMsg::{CreateCreditAccount, UpdateConfig}; -use rover::{ConfigResponse, InstantiateMsg, QueryMsg}; - -use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract}; +use cw721_base::InstantiateMsg as NftInstantiateMsg; +use cw721_base::QueryMsg as NftQueryMsg; +use cw_multi_test::Executor; +use rover::msg::query::ConfigResponse; +use rover::msg::ExecuteMsg::UpdateConfig; +use rover::msg::{InstantiateMsg, QueryMsg}; + +use crate::helpers::{ + get_token_id, mock_account_nft_contract, mock_app, mock_contract, mock_create_credit_account, + transfer_nft_contract_ownership, +}; pub mod helpers; @@ -73,36 +75,11 @@ fn test_create_credit_account() { panic!("Should have thrown error due to nft contract not proposing a new owner yet"); } - let proposal_msg: NftExecuteMsg = NftExecuteMsg::ProposeNewOwner { - new_owner: manager_contract_addr.to_string(), - }; - app.execute_contract(owner.clone(), nft_contract_addr.clone(), &proposal_msg, &[]) - .unwrap(); - - app.execute_contract( - owner.clone(), - manager_contract_addr.clone(), - &UpdateConfig { - account_nft: Some(nft_contract_addr.to_string()), - owner: None, - }, - &[], - ) - .unwrap(); + transfer_nft_contract_ownership(&mut app, &owner, &nft_contract_addr, &manager_contract_addr); let res = mock_create_credit_account(&mut app, &manager_contract_addr, &user).unwrap(); - let attr: Vec<&String> = res - .events - .iter() - .flat_map(|event| &event.attributes) - .filter(|attr| attr.key == "token_id") - .map(|attr| &attr.value) - .collect(); - - assert_eq!(attr.len(), 1); - - let token_id = attr.first().unwrap().as_str(); + let token_id = get_token_id(res); assert_eq!(token_id, "1"); // Double checking ownership by querying NFT account-nft for correct owner @@ -124,16 +101,3 @@ fn test_create_credit_account() { assert_eq!(user, owner_res.owner) } - -fn mock_create_credit_account( - app: &mut App, - manager_contract_addr: &Addr, - user: &Addr, -) -> AnyResult { - app.execute_contract( - user.clone(), - manager_contract_addr.clone(), - &CreateCreditAccount {}, - &[], - ) -} diff --git a/contracts/credit-manager/tests/deposit_cw20_test.rs b/contracts/credit-manager/tests/deposit_cw20_test.rs new file mode 100644 index 000000000..d7e949525 --- /dev/null +++ b/contracts/credit-manager/tests/deposit_cw20_test.rs @@ -0,0 +1,176 @@ +extern crate core; + +use cosmwasm_std::{to_binary, Addr, Uint128}; +use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, Cw20QueryMsg}; +use cw_asset::{AssetInfo, AssetInfoUnchecked}; +use cw_multi_test::Executor; + +use credit_manager::error::ContractError::{NotTokenOwner, NotWhitelisted}; +use rover::msg::execute::ReceiveMsg; + +use crate::helpers::{ + assert_err, deploy_mock_cw20, get_position, get_token_id, mock_app, mock_create_credit_account, + setup_credit_manager, +}; + +pub mod helpers; + +#[test] +fn test_only_token_owner_can_deposit() { + let mut app = mock_app(); + let user = Addr::unchecked("user"); + let another_user = Addr::unchecked("another_user"); + + let cw20_contract = deploy_mock_cw20( + &mut app, + String::from("jakecoin"), + vec![Cw20Coin { + address: another_user.clone().into(), + amount: Uint128::from(500u128), + }], + ); + + let manager_contract = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![AssetInfoUnchecked::Cw20(cw20_contract.clone().to_string())], + ); + let res = mock_create_credit_account(&mut app, &manager_contract, &user).unwrap(); + let token_id = get_token_id(res); + let amount = Uint128::from(300u128); + + let res = app.execute_contract( + another_user.clone(), + cw20_contract.clone(), + &Cw20ExecuteMsg::Send { + contract: manager_contract.clone().into(), + amount, + msg: to_binary(&ReceiveMsg::Deposit { + token_id: token_id.clone(), + }) + .unwrap(), + }, + &[], + ); + + assert_err( + res, + NotTokenOwner { + user: another_user.to_string(), + token_id: token_id.clone(), + }, + ); + + let res = get_position(&app, &manager_contract, &token_id); + assert_eq!(res.assets.len(), 0); +} + +#[test] +fn test_can_only_deposit_allowed_assets() { + let mut app = mock_app(); + let user = Addr::unchecked("user"); + let cw20_contract_a = deploy_mock_cw20( + &mut app, + String::from("jakecoin"), + vec![Cw20Coin { + address: user.clone().into(), + amount: Uint128::from(500u128), + }], + ); + + let cw20_contract_b = deploy_mock_cw20( + &mut app, + String::from("sparkycoin"), + vec![Cw20Coin { + address: user.clone().into(), + amount: Uint128::from(500u128), + }], + ); + + let contract_addr = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![AssetInfoUnchecked::Cw20(cw20_contract_b.to_string())], + ); + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + let amount = Uint128::from(300u128); + + let res = app.execute_contract( + user.clone(), + cw20_contract_a.clone(), + &Cw20ExecuteMsg::Send { + contract: contract_addr.clone().into(), + amount, + msg: to_binary(&ReceiveMsg::Deposit { + token_id: token_id.clone(), + }) + .unwrap(), + }, + &[], + ); + + assert_err( + res, + NotWhitelisted(AssetInfo::Cw20(cw20_contract_a).to_string()), + ); + + let res = get_position(&app, &contract_addr, &token_id); + assert_eq!(res.assets.len(), 0); +} + +#[test] +fn test_cw20_deposit_success() { + let mut app = mock_app(); + let user = Addr::unchecked("user"); + let cw20_contract = deploy_mock_cw20( + &mut app, + String::from("jakecoin"), + vec![Cw20Coin { + address: user.clone().into(), + amount: Uint128::from(500u128), + }], + ); + let asset_info = AssetInfoUnchecked::cw20(cw20_contract.clone()); + + let contract_addr = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![asset_info.clone()], + ); + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + let amount = Uint128::from(300u128); + + app.execute_contract( + user.clone(), + cw20_contract.clone(), + &Cw20ExecuteMsg::Send { + contract: contract_addr.clone().into(), + amount, + msg: to_binary(&ReceiveMsg::Deposit { + token_id: token_id.clone(), + }) + .unwrap(), + }, + &[], + ) + .unwrap(); + + let res = get_position(&app, &contract_addr, &token_id); + assert_eq!(res.assets.len(), 1); + assert_eq!(res.assets.first().unwrap().amount, amount); + assert_eq!(res.assets.first().unwrap().info, asset_info); + + let res: BalanceResponse = app + .wrap() + .query_wasm_smart( + cw20_contract.clone(), + &Cw20QueryMsg::Balance { + address: contract_addr.into(), + }, + ) + .unwrap(); + + assert_eq!(res.balance, amount) +} diff --git a/contracts/credit-manager/tests/deposit_native_test.rs b/contracts/credit-manager/tests/deposit_native_test.rs new file mode 100644 index 000000000..82a1010a2 --- /dev/null +++ b/contracts/credit-manager/tests/deposit_native_test.rs @@ -0,0 +1,341 @@ +extern crate core; + +use cosmwasm_std::{Addr, Coin, Uint128}; +use credit_manager::error::ContractError::{ + ExtraFundsReceived, FundsMismatch, NotTokenOwner, NotWhitelisted, +}; +use cw20::Cw20Coin; +use cw_asset::{AssetInfo, AssetInfoUnchecked, AssetList, AssetUnchecked}; +use cw_multi_test::{App, Executor}; + +use rover::msg::execute::Action; +use rover::msg::ExecuteMsg; + +use crate::helpers::{ + assert_err, deploy_mock_cw20, get_position, get_token_id, mock_app, mock_create_credit_account, + setup_credit_manager, +}; + +pub mod helpers; + +#[test] +fn test_only_owner_of_token_can_deposit() { + let mut app = mock_app(); + let info = AssetInfoUnchecked::native("uosmo"); + let asset = AssetUnchecked::new(info.clone(), Uint128::zero()); + + let contract_addr = + setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); + + let user = Addr::unchecked("user"); + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + let another_user = Addr::unchecked("another_user"); + let res = app.execute_contract( + another_user.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Action::NativeDeposit(asset)], + }, + &[], + ); + + assert_err( + res, + NotTokenOwner { + user: another_user.into(), + token_id, + }, + ) +} + +#[test] +fn test_deposit_nothing() { + let mut app = mock_app(); + let info = AssetInfoUnchecked::native("uosmo"); + let asset = AssetUnchecked::new(info.clone(), Uint128::zero()); + let contract_addr = + setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); + + let user = Addr::unchecked("user"); + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + let res = get_position(&app, &contract_addr, &token_id); + assert_eq!(res.assets.len(), 0); + + app.execute_contract( + user.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Action::NativeDeposit(asset)], + }, + &[], + ) + .unwrap(); + + let res = get_position(&app, &contract_addr, &token_id); + assert_eq!(res.assets.len(), 0); +} + +#[test] +fn test_deposit_but_no_funds() { + let mut app = mock_app(); + let info = AssetInfoUnchecked::native("uosmo"); + let amount = Uint128::from(234u128); + let asset = AssetUnchecked::new(info.clone(), amount); + let contract_addr = setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info]); + + let user = Addr::unchecked("user"); + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + let res = app.execute_contract( + user.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Action::NativeDeposit(asset.clone())], + }, + &[], + ); + + assert_err( + res, + FundsMismatch { + expected: asset.amount, + received: Uint128::zero(), + }, + ); + + let res = get_position(&app, &contract_addr, &token_id); + assert_eq!(res.assets.len(), 0); +} + +#[test] +fn test_deposit_but_not_enough_funds() { + let user = Addr::unchecked("user"); + let funds = Coin::new(300u128, "uosmo"); + let info = AssetInfoUnchecked::native("uosmo"); + let amount = Uint128::from(350u128); + let asset = AssetUnchecked::new(info.clone(), amount); + + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user, vec![funds]) + .unwrap(); + }); + let contract_addr = + setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); + + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + let res = app.execute_contract( + user.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Action::NativeDeposit(asset.clone())], + }, + &[Coin::new(250u128, "uosmo")], + ); + + assert_err( + res, + FundsMismatch { + expected: asset.amount, + received: Uint128::from(250u128), + }, + ); +} + +#[test] +fn test_can_only_deposit_allowed_assets() { + let user = Addr::unchecked("user"); + let funds = Coin::new(300u128, "uosmo"); + + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user, vec![funds]) + .unwrap(); + }); + let cw20_contract = deploy_mock_cw20( + &mut app, + String::from("jakecoin"), + vec![Cw20Coin { + address: user.clone().into(), + amount: Uint128::from(500u128), + }], + ); + let contract_addr = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![ + AssetInfoUnchecked::native("ucosmos"), + AssetInfoUnchecked::cw20(cw20_contract), + ], + ); + + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + let info = AssetInfoUnchecked::native("uosmo"); + let amount = Uint128::from(234u128); + let asset = AssetUnchecked::new(info.clone(), amount); + + let res = app.execute_contract( + user.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Action::NativeDeposit(asset.clone())], + }, + &[Coin::new(234u128, "uosmo")], + ); + + assert_err(res, NotWhitelisted(AssetInfo::native("uosmo").to_string())); + + let res = get_position(&app, &contract_addr, &token_id); + assert_eq!(res.assets.len(), 0); +} + +#[test] +fn test_extra_funds_received() { + let user = Addr::unchecked("user"); + + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance( + storage, + &user, + vec![Coin::new(300u128, "uosmo"), Coin::new(50u128, "ucosmos")], + ) + .unwrap(); + }); + + let info = AssetInfoUnchecked::native("uosmo"); + let amount = Uint128::from(234u128); + let asset = AssetUnchecked::new(info.clone(), amount); + + let contract_addr = + setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); + + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + let extra_funds = Coin::new(25u128, "ucosmos"); + let res = app.execute_contract( + user.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Action::NativeDeposit(asset.clone())], + }, + &[Coin::new(234u128, "uosmo"), extra_funds.clone()], + ); + + assert_err(res, ExtraFundsReceived(AssetList::from(vec![extra_funds]))); + + let res = get_position(&app, &contract_addr, &token_id); + assert_eq!(res.assets.len(), 0); +} + +#[test] +fn test_native_deposit_success() { + let user = Addr::unchecked("user"); + let funds = Coin::new(300u128, "uosmo"); + let info = AssetInfoUnchecked::native("uosmo"); + let amount = Uint128::from(234u128); + let asset = AssetUnchecked::new(info.clone(), amount); + + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user, vec![funds]) + .unwrap(); + }); + let contract_addr = + setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); + + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + app.execute_contract( + user.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Action::NativeDeposit(asset.clone())], + }, + &[Coin::new(234u128, "uosmo")], + ) + .unwrap(); + + let res = get_position(&app, &contract_addr, &token_id); + assert_eq!(res.assets.len(), 1); + assert_eq!(res.assets.first().unwrap().amount, amount); + assert_eq!(res.assets.first().unwrap().info, info); + + let coin = app.wrap().query_balance(contract_addr, "uosmo").unwrap(); + assert_eq!(coin.amount, amount) +} + +#[test] +fn test_multiple_deposit_actions() { + let user = Addr::unchecked("user"); + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance( + storage, + &user, + vec![Coin::new(300u128, "uosmo"), Coin::new(50u128, "ucosmos")], + ) + .unwrap(); + }); + + let asset_a = AssetUnchecked::new(AssetInfoUnchecked::native("uosmo"), Uint128::from(234u128)); + let asset_b = AssetUnchecked::new(AssetInfoUnchecked::native("ucosmos"), Uint128::from(25u128)); + + let contract_addr = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![asset_a.clone().info, asset_b.clone().info], + ); + + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + app.execute_contract( + user.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![ + Action::NativeDeposit(asset_a.clone()), + Action::NativeDeposit(asset_b.clone()), + ], + }, + &[Coin::new(234u128, "uosmo"), Coin::new(25u128, "ucosmos")], + ) + .unwrap(); + + let res = get_position(&app, &contract_addr, &token_id); + assert_eq!(res.assets.len(), 2); + + let coin = app + .wrap() + .query_balance(contract_addr.clone(), "uosmo") + .unwrap(); + assert_eq!(coin.amount, Uint128::from(234u128)); + + let coin = app.wrap().query_balance(contract_addr, "ucosmos").unwrap(); + assert_eq!(coin.amount, Uint128::from(25u128)); +} diff --git a/contracts/credit-manager/tests/dispatch_test.rs b/contracts/credit-manager/tests/dispatch_test.rs new file mode 100644 index 000000000..66bc14fca --- /dev/null +++ b/contracts/credit-manager/tests/dispatch_test.rs @@ -0,0 +1,70 @@ +extern crate core; + +use crate::helpers::{ + assert_err, get_position, get_token_id, mock_app, mock_create_credit_account, + setup_credit_manager, +}; +use cosmwasm_std::Addr; +use credit_manager::error::ContractError::NotTokenOwner; +use cw_multi_test::Executor; +use rover::msg::ExecuteMsg::UpdateCreditAccount; + +pub mod helpers; + +#[test] +fn test_dispatch_only_allowed_for_token_owner() { + let mut app = mock_app(); + let owner = Addr::unchecked("owner"); + let contract_addr = setup_credit_manager(&mut app, &owner, vec![]); + + let user = Addr::unchecked("user"); + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = app.execute_contract( + bad_guy.clone(), + contract_addr.clone(), + &UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![], + }, + &[], + ); + + assert_err( + res, + NotTokenOwner { + user: bad_guy.into(), + token_id, + }, + ) +} + +#[test] +fn test_nothing_happens_if_no_actions_are_passed() { + let mut app = mock_app(); + let owner = Addr::unchecked("owner"); + let contract_addr = setup_credit_manager(&mut app, &owner, vec![]); + + let user = Addr::unchecked("user"); + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + let res = get_position(&app, &contract_addr, &token_id); + assert_eq!(res.assets.len(), 0); + + app.execute_contract( + user.clone(), + contract_addr.clone(), + &UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![], + }, + &[], + ) + .unwrap(); + + let res = get_position(&app, &contract_addr, &token_id); + assert_eq!(res.assets.len(), 0); +} diff --git a/contracts/credit-manager/tests/helpers.rs b/contracts/credit-manager/tests/helpers.rs index 2946df607..2ca12c296 100644 --- a/contracts/credit-manager/tests/helpers.rs +++ b/contracts/credit-manager/tests/helpers.rs @@ -1,10 +1,23 @@ -use cosmwasm_std::Empty; -use cw_multi_test::{App, Contract, ContractWrapper}; +use anyhow::Result as AnyResult; +use cosmwasm_std::{Addr, Empty}; +use cw20::Cw20Coin; +use cw20_base::contract::{ + execute as cw20Execute, instantiate as cw20Instantiate, query as cw20Query, +}; +use cw20_base::msg::InstantiateMsg as cw20InstantiateMsg; +use cw721_base::InstantiateMsg as NftInstantiateMsg; +use cw_asset::AssetInfoUnchecked; +use cw_multi_test::{App, AppResponse, Contract, ContractWrapper, Executor}; use account_nft::contract::{ execute as cw721Execute, instantiate as cw721Instantiate, query as cw721Query, }; +use account_nft::msg::ExecuteMsg as NftExecuteMsg; use credit_manager::contract::{execute, instantiate, query}; +use credit_manager::error::ContractError; +use rover::msg::execute::ExecuteMsg::{CreateCreditAccount, UpdateConfig}; +use rover::msg::query::{PositionResponse, QueryMsg}; +use rover::msg::InstantiateMsg; pub fn mock_app() -> App { App::default() @@ -19,3 +32,151 @@ pub fn mock_account_nft_contract() -> Box> { let contract = ContractWrapper::new(cw721Execute, cw721Instantiate, cw721Query); Box::new(contract) } + +pub fn mock_cw20_contract() -> Box> { + let contract = ContractWrapper::new(cw20Execute, cw20Instantiate, cw20Query); + Box::new(contract) +} + +pub fn mock_create_credit_account( + app: &mut App, + manager_contract_addr: &Addr, + user: &Addr, +) -> AnyResult { + app.execute_contract( + user.clone(), + manager_contract_addr.clone(), + &CreateCreditAccount {}, + &[], + ) +} + +pub fn deploy_mock_cw20(app: &mut App, symbol: String, initial_balances: Vec) -> Addr { + let code_id = app.store_code(mock_cw20_contract()); + app.instantiate_contract( + code_id, + Addr::unchecked("cw20-instantiator"), + &cw20InstantiateMsg { + name: format!("Token: {}", symbol.clone()), + symbol, + decimals: 9, + initial_balances, + mint: None, + marketing: None, + }, + &[], + "mock-cw20", + None, + ) + .unwrap() +} + +pub fn transfer_nft_contract_ownership( + app: &mut App, + owner: &Addr, + nft_contract_addr: &Addr, + manager_contract_addr: &Addr, +) { + let proposal_msg: NftExecuteMsg = NftExecuteMsg::ProposeNewOwner { + new_owner: manager_contract_addr.to_string(), + }; + app.execute_contract(owner.clone(), nft_contract_addr.clone(), &proposal_msg, &[]) + .unwrap(); + + app.execute_contract( + owner.clone(), + manager_contract_addr.clone(), + &UpdateConfig { + account_nft: Some(nft_contract_addr.to_string()), + owner: None, + }, + &[], + ) + .unwrap(); +} + +pub fn setup_nft_contract(app: &mut App, owner: &Addr, manager_contract_addr: &Addr) -> Addr { + let nft_contract_code_id = app.store_code(mock_account_nft_contract()); + let nft_contract_addr = app + .instantiate_contract( + nft_contract_code_id, + owner.clone(), + &NftInstantiateMsg { + name: String::from("Rover Credit Account"), + symbol: String::from("RCA"), + minter: owner.to_string(), + }, + &[], + "manager-mock-account-nft", + None, + ) + .unwrap(); + + transfer_nft_contract_ownership(app, owner, &nft_contract_addr, &manager_contract_addr); + nft_contract_addr +} + +pub fn setup_credit_manager( + mut app: &mut App, + owner: &Addr, + allowed_assets: Vec, +) -> Addr { + let credit_manager_code_id = app.store_code(mock_contract()); + let manager_initiate_msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_assets, + }; + + let manager_contract_addr = app + .instantiate_contract( + credit_manager_code_id, + owner.clone(), + &manager_initiate_msg, + &[], + "manager-mock", + None, + ) + .unwrap(); + + setup_nft_contract(&mut app, &owner, &manager_contract_addr); + manager_contract_addr +} + +pub fn get_token_id(res: AppResponse) -> String { + let attr: Vec<&String> = res + .events + .iter() + .flat_map(|event| &event.attributes) + .filter(|attr| attr.key == "token_id") + .map(|attr| &attr.value) + .collect(); + + assert_eq!(attr.len(), 1); + attr.first().unwrap().to_string() +} + +pub fn get_position( + app: &App, + manager_contract_addr: &Addr, + token_id: &String, +) -> PositionResponse { + app.wrap() + .query_wasm_smart( + manager_contract_addr.clone(), + &QueryMsg::Position { + token_id: token_id.clone(), + }, + ) + .unwrap() +} + +pub fn assert_err(res: AnyResult, err: ContractError) { + match res { + Ok(_) => panic!("Result was not an error"), + Err(generic_err) => { + let contract_err: ContractError = generic_err.downcast().unwrap(); + assert_eq!(contract_err, err); + } + } +} diff --git a/contracts/credit-manager/tests/instantiate_test.rs b/contracts/credit-manager/tests/instantiate_test.rs index 07a37f6ca..adb67cde5 100644 --- a/contracts/credit-manager/tests/instantiate_test.rs +++ b/contracts/credit-manager/tests/instantiate_test.rs @@ -2,7 +2,8 @@ use cosmwasm_std::Addr; use cw_asset::AssetInfoUnchecked; use cw_multi_test::Executor; -use rover::{ConfigResponse, InstantiateMsg, QueryMsg}; +use rover::msg::query::{ConfigResponse, QueryMsg}; +use rover::msg::InstantiateMsg; use crate::helpers::{mock_app, mock_contract}; diff --git a/contracts/credit-manager/tests/position_query_test.rs b/contracts/credit-manager/tests/position_query_test.rs new file mode 100644 index 000000000..d98ccdf5c --- /dev/null +++ b/contracts/credit-manager/tests/position_query_test.rs @@ -0,0 +1,99 @@ +use cosmwasm_std::testing::{mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage}; +use cosmwasm_std::{from_binary, Addr, OwnedDeps, Uint128}; +use cw_asset::{AssetInfo, AssetInfoBase}; + +use credit_manager::contract::query; +use credit_manager::state::ASSETS; +use rover::msg::query::{PositionResponse, QueryMsg}; + +pub mod helpers; + +#[test] +fn test_position_query_when_no_result() { + let deps = mock_dependencies(); + let position_token = Addr::unchecked("position_token"); + let value = query_position(&deps, &position_token.clone()); + assert_eq!(value.token_id, position_token); + assert_eq!(value.assets.len(), 0); +} + +#[test] +fn test_position_query_when_assets_deposited() { + let mut deps = mock_dependencies(); + + let position_token = Addr::unchecked("position_token"); + let native_asset = AssetInfo::Native(String::from("native_asset")); + let amount = Uint128::new(123); + save_position(&mut deps, &position_token, &native_asset, &amount); + + let value = query_position(&deps, &position_token); + assert_eq!(value.assets.len(), 1); + assert_eq!(value.assets.first().unwrap().amount, amount); + assert_eq!(value.assets.first().unwrap().info, native_asset.into()); +} + +#[test] +fn test_position_query_with_multiple_results() { + let mut deps = mock_dependencies(); + + let position_token_a = Addr::unchecked("position_token_a"); + let asset_a = AssetInfo::Native(String::from("asset_a")); + let amount_a = Uint128::new(123); + save_position(&mut deps, &position_token_a, &asset_a, &amount_a); + + let asset_b = AssetInfo::Cw20(Addr::unchecked(String::from("asset_b"))); + let amount_b = Uint128::new(444); + save_position(&mut deps, &position_token_a, &asset_b, &amount_b); + + let asset_c = AssetInfo::Cw20(Addr::unchecked(String::from("asset_c"))); + let amount_c = Uint128::new(98); + save_position(&mut deps, &position_token_a, &asset_c, &amount_c); + + let position_token_b = Addr::unchecked("position_token_b"); + let amount_d = Uint128::new(567); + save_position(&mut deps, &position_token_b, &asset_a, &amount_d); + + let value = query_position(&deps, &position_token_a); + assert_eq!(value.assets.len(), 3); + + assert_present(&value, &asset_a, &amount_a); + assert_present(&value, &asset_b, &amount_b); + assert_present(&value, &asset_c, &amount_c); +} + +fn assert_present(res: &PositionResponse, asset: &AssetInfoBase, amount: &Uint128) { + res.assets + .iter() + .find(|item| item.info == asset.clone().into() && &item.amount == amount) + .unwrap(); +} + +fn save_position( + deps: &mut OwnedDeps, + position_token: &Addr, + asset: &AssetInfoBase, + amount: &Uint128, +) { + ASSETS + .save( + &mut deps.storage, + (position_token.clone().into(), asset.into()), + &amount, + ) + .unwrap(); +} + +fn query_position( + deps: &OwnedDeps, + position_token_a: &Addr, +) -> PositionResponse { + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::Position { + token_id: position_token_a.into(), + }, + ) + .unwrap(); + from_binary(&res).unwrap() +} diff --git a/contracts/credit-manager/tests/update_config_test.rs b/contracts/credit-manager/tests/update_config_test.rs index efcec558b..2dc21a62d 100644 --- a/contracts/credit-manager/tests/update_config_test.rs +++ b/contracts/credit-manager/tests/update_config_test.rs @@ -1,9 +1,10 @@ -use account_nft::msg::ExecuteMsg as NftExecuteMsg; use cosmwasm_std::Addr; use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, Executor}; -use rover::{ConfigResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; +use account_nft::msg::ExecuteMsg as NftExecuteMsg; +use rover::msg::query::ConfigResponse; +use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract}; @@ -23,13 +24,13 @@ fn test_update_config_works_with_full_config() { let new_owner = Addr::unchecked("new_owner"); - let account_nft_contract = setup_nft_contract(&mut app, &original_owner, &contract_addr); + let nft_contract_addr = setup_nft_and_propose_owner(&mut app, &original_owner, &contract_addr); app.execute_contract( original_owner.clone(), contract_addr.clone(), &ExecuteMsg::UpdateConfig { - account_nft: Some(account_nft_contract.to_string()), + account_nft: Some(nft_contract_addr.to_string()), owner: Some(new_owner.to_string()), }, &[], @@ -38,10 +39,7 @@ fn test_update_config_works_with_full_config() { let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!( - config_res.account_nft, - Some(account_nft_contract.to_string()) - ); + assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); assert_eq!(config_res.owner, new_owner.to_string()); } @@ -57,12 +55,12 @@ fn test_update_config_works_with_some_config() { assert_eq!(config_res.account_nft, None); assert_eq!(config_res.owner, original_owner.to_string()); - let account_nft_contract = setup_nft_contract(&mut app, &original_owner, &contract_addr); + let nft_contract_addr = setup_nft_and_propose_owner(&mut app, &original_owner, &contract_addr); app.execute_contract( original_owner.clone(), contract_addr.clone(), &ExecuteMsg::UpdateConfig { - account_nft: Some(account_nft_contract.to_string()), + account_nft: Some(nft_contract_addr.to_string()), owner: None, }, &[], @@ -71,10 +69,7 @@ fn test_update_config_works_with_some_config() { let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!( - config_res.account_nft, - Some(account_nft_contract.to_string()) - ); + assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); assert_eq!(config_res.owner, original_owner.to_string()); let new_owner = Addr::unchecked("new_owner"); @@ -90,10 +85,7 @@ fn test_update_config_works_with_some_config() { .unwrap(); let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!( - config_res.account_nft, - Some(account_nft_contract.to_string()) - ); + assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); assert_eq!(config_res.owner, new_owner.to_string()); } @@ -143,16 +135,16 @@ fn instantiate(app: &mut App, original_owner: &Addr, code_id: u64) -> Addr { .unwrap() } -fn setup_nft_contract(app: &mut App, owner: &Addr, contract: &Addr) -> Addr { +fn setup_nft_and_propose_owner(app: &mut App, original_owner: &Addr, contract_addr: &Addr) -> Addr { let nft_contract_code_id = app.store_code(mock_account_nft_contract()); let nft_contract_addr = app .instantiate_contract( nft_contract_code_id, - owner.clone(), + original_owner.clone(), &NftInstantiateMsg { name: String::from("Rover Credit Account"), symbol: String::from("RCA"), - minter: owner.to_string(), + minter: original_owner.to_string(), }, &[], "manager-mock-account-nft", @@ -161,9 +153,14 @@ fn setup_nft_contract(app: &mut App, owner: &Addr, contract: &Addr) -> Addr { .unwrap(); let proposal_msg: NftExecuteMsg = NftExecuteMsg::ProposeNewOwner { - new_owner: contract.to_string(), + new_owner: contract_addr.to_string(), }; - app.execute_contract(owner.clone(), nft_contract_addr.clone(), &proposal_msg, &[]) - .unwrap(); + app.execute_contract( + original_owner.clone(), + nft_contract_addr.clone(), + &proposal_msg, + &[], + ) + .unwrap(); nft_contract_addr } diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index a12cac5af..d0e87a0d3 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -1,44 +1 @@ -use cw_asset::AssetInfoUnchecked; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct InstantiateMsg { - pub owner: String, - pub allowed_vaults: Vec, - pub allowed_assets: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - CreateCreditAccount {}, - UpdateConfig { - account_nft: Option, - owner: Option, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - /// Owner & account nft address. Response type: `ConfigResponse` - Config {}, - /// Whitelisted vaults. Response type: `Vec` - AllowedVaults { - start_after: Option, - limit: Option, - }, - /// Whitelisted assets. Response type: `Vec` - AllowedAssets { - start_after: Option, - limit: Option, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct ConfigResponse { - pub owner: String, - pub account_nft: Option, -} +pub mod msg; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs new file mode 100644 index 000000000..70ede8603 --- /dev/null +++ b/packages/rover/src/msg/execute.rs @@ -0,0 +1,68 @@ +use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg}; +use cw20::Cw20ReceiveMsg; +use cw_asset::AssetUnchecked; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + //-------------------------------------------------------------------------------------------------- + // Public messages + //-------------------------------------------------------------------------------------------------- + /// Mints NFT representing a credit account for user. User can have many. + CreateCreditAccount {}, + /// Update user's position on their credit account + UpdateCreditAccount { + token_id: String, + actions: Vec, + }, + /// The receiving message for Cw20ExecuteMsg::Send deposits + Receive(Cw20ReceiveMsg), + + //-------------------------------------------------------------------------------------------------- + // Privileged messages + //-------------------------------------------------------------------------------------------------- + /// Update owner or stored account nft contract + UpdateConfig { + account_nft: Option, + owner: Option, + }, + /// Internal actions only callable by the contract itself + Callback(CallbackMsg), +} + +/// The list of actions that users can perform on their positions +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Action { + /// Deposit native asset of specified type and amount. Verifies if the correct amount is sent with transaction. + /// NOTE: CW20 Deposits should use Cw20ExecuteMsg::Send {} + NativeDeposit(AssetUnchecked), + + Placeholder {}, +} + +/// Internal actions made by the contract with pre-validated inputs +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CallbackMsg { + Placeholder {}, +} + +impl CallbackMsg { + pub fn into_cosmos_msg(&self, contract_addr: &Addr) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: String::from(contract_addr), + msg: to_binary(&ExecuteMsg::Callback(self.clone()))?, + funds: vec![], + })) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ReceiveMsg { + /// Deposit for CW20's. Requires using Cw20ExecuteMsg::Send and specifying this receive message + Deposit { token_id: String }, +} diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs new file mode 100644 index 000000000..6aa417c93 --- /dev/null +++ b/packages/rover/src/msg/instantiate.rs @@ -0,0 +1,10 @@ +use cw_asset::AssetInfoUnchecked; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct InstantiateMsg { + pub owner: String, + pub allowed_vaults: Vec, + pub allowed_assets: Vec, +} diff --git a/packages/rover/src/msg/mod.rs b/packages/rover/src/msg/mod.rs new file mode 100644 index 000000000..50cb8a531 --- /dev/null +++ b/packages/rover/src/msg/mod.rs @@ -0,0 +1,7 @@ +pub mod execute; +pub mod instantiate; +pub mod query; + +pub use execute::ExecuteMsg; +pub use instantiate::InstantiateMsg; +pub use query::QueryMsg; diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs new file mode 100644 index 000000000..6a93a5028 --- /dev/null +++ b/packages/rover/src/msg/query.rs @@ -0,0 +1,36 @@ +use cw_asset::{AssetInfoUnchecked, AssetUnchecked}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + /// Owner & account nft address. Response type: `ConfigResponse` + Config {}, + /// Whitelisted vaults. Response type: `Vec` + AllowedVaults { + start_after: Option, + limit: Option, + }, + /// Whitelisted assets. Response type: `Vec` + AllowedAssets { + start_after: Option, + limit: Option, + }, + /// The entire position represented by token. Response type: `PositionResponse` + Position { token_id: String }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct PositionResponse { + pub token_id: String, + pub assets: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct ConfigResponse { + pub owner: String, + pub account_nft: Option, +} diff --git a/scripts/tests/instantiate.test.ts b/scripts/tests/instantiate.test.ts index 6e8a254a2..639fba018 100644 --- a/scripts/tests/instantiate.test.ts +++ b/scripts/tests/instantiate.test.ts @@ -79,7 +79,7 @@ describe('instantiating fields contract', () => { config: {}, }); - expect(configRes.owner).toEqual(owner);; + expect(configRes.owner).toEqual(owner); expect(configRes.account_nft).toEqual(""); }); }); From d78bf47b61937034382036ad20239a1e54a72544 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 14 Jul 2022 00:53:07 +0200 Subject: [PATCH 037/218] review updates --- .github/workflows/main.yml | 80 ++++++++++++++++++ artifacts/account_nft.wasm | Bin 290078 -> 0 bytes contracts/account-nft/src/contract.rs | 4 +- contracts/account-nft/src/execute.rs | 8 +- contracts/account-nft/src/msg/query.rs | 3 +- contracts/credit-manager/src/contract.rs | 4 +- contracts/credit-manager/src/deposit.rs | 21 ++--- contracts/credit-manager/src/execute.rs | 22 ++--- contracts/credit-manager/src/query.rs | 8 +- contracts/credit-manager/src/state.rs | 2 +- .../credit-manager/tests/deposit_cw20_test.rs | 22 ++--- .../tests/deposit_native_test.rs | 4 +- contracts/credit-manager/tests/helpers.rs | 4 +- .../tests/position_query_test.rs | 38 ++++----- scripts/build_artifacts.sh | 9 ++ 15 files changed, 155 insertions(+), 74 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 artifacts/account_nft.wasm create mode 100755 scripts/build_artifacts.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..587bf75ba --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,80 @@ +# Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml + +on: push + +name: Main + +jobs: + + check: + name: Check + 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: stable + override: true + + - name: Run cargo check + uses: actions-rs/cargo@v1 + with: + command: check + + - name: Compile Contracts to WASM + run: | + $GITHUB_WORKSPACE/scripts/build_artifacts.sh + + 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: stable + target: wasm32-unknown-unknown + override: true + + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --locked + env: + RUST_BACKTRACE: 1 + + 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: stable + components: rustfmt, clippy + override: true + + - 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 diff --git a/artifacts/account_nft.wasm b/artifacts/account_nft.wasm deleted file mode 100644 index 2c37b1267e58a7ce32a73f9cf053adedb56553d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 290078 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%0D8APm+b%Ew6}Ho}=ZChkv zfA&i}Cm%?2IN&GI*c`dNd@s-8-CU^OVXvly_)_+TBCe5JaUC(fTpx~O{H4@k1;xV= zdPMu?MtmoGKsSnJB|?{3C`HDFrIOYmYs8rk$;sMDBz%Kc75BRmIa=1wWP9C-b6!Qz z=P;c=wjA#SmS}PVOS*+YoWA0K={@+f;BZIEuQ@kFB?uT4c++7F6Zl67ON4G(8^qgR zrqrl0UpTVDTY`&G#1ImOMuNsmc>namm10WP^U6x;p z<)4HU;qM9xY_btR{><<*cRtW%@7*$=y=UPN*?SYI4Z=ODMO-a=?+1c)Q!!x1>y<13 zuSZ*v_h!0VciM6~>ht><=g&QQf3t^Tf>7I@UBiX6*mJhjQ`rCvf=n}lll982;Sj3L zPL%7PRhYyR9YQh(D)?u(ei1OU+RjmztBY%SDzbHk1!Eu?ZJk+MVElB`7fU!Q6J zC-r@Ka!wJM6&rT$nsYectu8*K*c=P!a7O0^bm5)m-lIg-`FVH>Fr16nqQwUDr4wt3 zJvx4-qR;9AIVmaak&h?9om0jJQdNc#SG%AOahfbpScKPEIbI;QRisSlXK=?|97VQT zc+^>r>dYI*efYrdr!l*?4o@M=V>ss~lQ-9)Sy~|~mqMZ2P7EFLBGR6L7^oS9EF%#w zq{Bdhz@B8FIdRUBponHk6^}^Jo`?hqoA%5bXm07AH3UCCJqK0%(8RcuZjuwkO~-z~ zWV+qO-^9C2gAjoSbdCF#)K|YRaFDI!ys2(B(bZ~yLo`z!>YR=aP<8n)oANDxV9rPb z#5u>JV;U4Asx`5l!Bs})pY1OUE>5KpD-ZcCET-Ou-pIt<75Nx}+uB_3bp4iy4kfkV zNL6ESTlhotR{n@%&*Z;Gj7n;G5{p%}d@{D|Ug0Q`W>`N--HYi+Q!kA@6_i>g;R`C} z14`fpU~uc@v|P2|C|1NO9E`I`%r}?dB*~iYxC`rz zV1$ZPDm4fcix)0gk-WwW1HT~fc#-Q^!jv@vz)>7lMrKqBbFwEWZoSe)zLk96oeMCG zzdHwba_@BKMrdr={JwKc85Ydd*OWX8(B|3{U>FJ7hO$8BeUje+1M~X=(L(3=wi%c` zsUEuapA!Q!i^H2UFq0knx`CNAiJm|CFS>#GeXkpsTJ>@Kb)O-B2imbD>mzUL^C~kx7YO;8C1sksQT~b;%-xOS&*&F2Ss#Js46J(F7*5BiF8YB;w zlp@yBb-pLn5yCNM-;?g3joXi-;=+m*3?`qXJ!K-16D}2WJwin5Rxgrcc3l&lD;BIv z&CU6A>ryMS18H6QL|%iwE=2-GDX%t64Mt4GHwV3>qxFcXtV=~yu?P@C9vWio~>%j~-{87gryLyTz@vS6XEOBkVDahv%K_;bvj ze{en`|+rBL0cRiRb7l>`#6Vh*0W{(eE|X1?$@`*+h6C9LFbAXjExJ? zoIWmN_>u}l+(yL%{h}kWtbJG?{K4|eI_FV#6#7A`!$iOX<+8pN<2nHHx zldfyk^E2IU7;X8m9{U#d*XIpitN42%yRG=e!HTxNwcDa^(Av zs@FQFb578Tb%K9Y{D6!L)h9U487^ylTrh-OoB_?2rMm+>n_c-cX`8g5Vwyx5&+Zx) zWi^BqY8XN21DY$xTzK+X)qojs^iTOcR7XRNn`qCFq26ybAg zh9=zeH`NS}=5{Znc4u#3AYSv0Z@39!2;Dc(?63L8A8mQV9Ba!MpGnWDlvy6*knvn+ zjQYAn2Go7g*jyWZ;JP#)kVs40|bmR_~%mrFt#C*qvLqyJy z4xFyrZoutru$z-=LjhOymFKC@0$V4VZ*|Ej~=328nc3ZQS znpw4yK;;keIy0z7dc``JL9U>=BvEH!#uYT2e1(Q304@+ZeF{wZe(r*wQLW?~o2!*r zA!yI*!CAGE|EDNi2$d)cbxJxvMA;7hGJq-9)4bCz@m~#!f6zJ`G2{`sq60X{!X4oZGZh!q>i21HW)%19zxga)0Z< zDTM2R&vtkST(>`MXQN&R@<2_0(DEi9t5*L%!GY-*o|zWqN#7MoP@qQB6%r+sIZTqGr^j>uI-n}8w##3q^Ovvp>)rl8#NuyNQw{dBs z1L?c;IoJ4Qlcr+wv+AIoxQ#oQpXNTgYEA4_XxNz;pJWX@8BK2#yTA#j03r`&9p&xXLH`=i0U)J*=Uh`sBvO z#*5xZ);!0p3Pz5`Xv{9BEg>H)z6xWqWLireL((r&P=d1CF8-nLZzqJ z_I6V3^GKX|5$~Mi-l~1ptO(S@hBO2sdk@WopTeA8EwyZS-m>%R#!`Zv1msZWvdFQC zRED(Jbh%Yg_g!g#E>^TG-)XbEyM$vSno#vjZ)CX~%Lr>~W+3n=ox{eP`g~Kx#+z}U ze#+8cWR|Tg1WEWo@Lw@l+^p_Wz?|Ys@qxp7W(ZjL301|t*YE{mJw|L8O_kkb>#pKuPb2dCXcFq#=+i(lIyN%<3Emzmf$dSl;$oAG}?r+ST{h)G(zaE|BpddhxssYZsSTi=8#Qfx(h^C|UN)Mb|!k5*qGap zl$zFrnlV`L5UdfMX3X*sL+xRI2)%F+Ii)P0W-pIIjkuNX;tuW)O(!3|GtQV*p2LHq zf??`MLk9AY97BEv(;-iDFp|O*08U#XPS2|vs;`{f4dm6n}?@w$j%mj$F0Z+gF0Q$hrDTLaV+8q_M+(@3Xl zy2ucD;?#K*R^_I#(U-(3BwH_#wL{+)+l9X_ZWj=^&i>YmVs(pVbz_Uc1?AHYVZuTM zesJ!cr9pvlFVjKNF9XjrT|IO=qh4Nf65e;6`Y`#a*z$frFrNG_BzB}4O+jT%D@m4P z5wTDWlJ0R1b67E)6*C;Ypg|H|H6`~4tGL-iLghn3<-=udo~b0Ya`K-#iR+N&M!0S* zNSYZR)6|=P@X`?=2G@-WfwQ)0vK7J~(gY`)#>%nve6E=u9Ua?UTT*a^00~+XUppbK zNqvF*6bIj=HHqy5gB~+DJUcfpsAuP>Cz37c`DIiwKJWy5+Xok%?FO933eWQw;nI`$ zV+ZOjgS`Jd9N2OQc=9YvA|$?n5sX68>>;qsOqtSI>SU7mV|j1H3}DUdI0Ws{MxWaX z?+k5qkD<$vZEE6ZpO7HC%EWbmb<^nqp1p(O7y~e3M=p6uSGUpE1NcIC;yE}RdcYW+ zj^}22z_`6^J>a+AQtk^q;6?O+CG1AF3k5E+mO>HOA2!JwCVyjQkIWsVin%Z*!j;a* zUP|=H?o?bl2OnEXIkF9oXt;=qqFJD?W9NLb;w-)ztR0>XkFk=p$-EGYM8ns25V}i} ziflF5wJrMh7NU04va=}SNZUo9K#Ac8j(coyNDPQBP_xV|b#Ih~)r;sTVbv~4RMQ2+ z){W|QS`kaTi^dl37~IbfiXw>eK3)Z6?2obpBDgS|U>TW;=sKd0<`%(ofBgoy)t<>; zOY#badRT-Gx6`7&(2>;P0cRv1_Z4%RC9*#jNb1xF7F@QM1>)lLa2P#Cg>F6C;hy4^}x^jjtGqcKj8J z1Fm&nQ6)t%^fb6CLZxe-GWnLuhx(fys zYy=H!FCB)2kC`mAQg{F%=7<_Rj$o*n!g_9Ss|XVaS+&fj%IM8OLu=BGwY zx5+UdWP^}39r!+g|%R##^*&0nbG(hwM+P#?!#1& z9Els;AEUc3_u$;7(LH#hqhoPRAMqHqoK)xnpcg0zqg_gQnlQp8Nm*T$+VTw>E$Aal685^f!eN*)j7F-HTVCeGa#BQo zT}FyZh(pKXZSgeImnk_Ir|P2zWk;`X1K-GaMV1d~A&k(HbUVeI_;S{LZ+;nF+n2U)jVF3uP~n zQSibt7-SI)a;b(hJ``%VwXT_$1AWawWPl~b&XAL(%CWb@wCWg}GIoxI;O}qbmG$)e z)tivrEA3O2kC$X&%!Xs`VEvJ6-N4N{mZX(&RRgtbuo%$LWzAVIAP4TOix}o8hPfPL zzt(7X(&@G3=q#TSOy3m!(4l6Gj_U9rhY3}shR3RH;6IQXcuDmFfS~xEeK>ShM>T3~ zTtnfq%yOj8^J<$&v##Z7OgvoHP)N+*YWbbs_su}aA;2YBBqUNTIo&3sig>c16`j4k zx=0^YpHTw{K=n=j>JbLo{P`dOFvMaJ5~R!$K9O_WoZfS{wb@f?q@n= z3vY>)rBm{fXdjt}ZBu~JqOT_P3TgJWr!HgvP43}3@N=4Uj$SbWd94jVWtV=PM|lk3 zHH(wG5mJiUr70uGmmftaw11{n@!POE&!7jRdymTP9mM7QiJ$rm503z;OiLpGTT+co zDB8{=0q2=6hgb8;^Z*&%1VQu!^CMAe&>@M?c_`2j-%fl1c#5Vq1OT1!o;lh?6`6S|Yu`1nB5R#%c+0$F_&dHCqu$|{5O=>MJ~YzXz+uV?6G#> zoIMCqK25NZ^duwY>a^fBJZ{9X&2(4)peTmQPY_zkP^Xs#e!8amAwZVgM)>86M*%u@ zu*8hQC-5Y+S}NkV~-wmg+-a zN{0eBCV!KuHRb{))@YqL>DSfIX zR6{p$G!$$(&ZipIXYr5lff~TMTbnQEW6^&X#K8Hmkp*eg^of4E~yx5=9^Uq-{N_Uh{AG9)Vx1 zdh|oLI+0IFd8z^t+zT)Y%;4<|%vA!jriTekVAk0EY7fkULI$&r&6C8y1I$k2tomU7 z!vfaf1fXl;58u@Q0J8^Bfj|5Qf2z-)8vjJIc&a}CzLxp`!+P;)Z6{odDH?t3p=Vaw z@wscFkCtxS0QwY0?ZBHL1m5qV&()}6Zd$6%)3QBP=ql`8!>aPI5>=1|ac!!&!C^7& z2%J1!4l2(L>*~|B!-0MN)Pe1$s~cCEF7WX+ov!PR3o5F%V`A4HRiBEw5g66&o>y?I zM@41FtVBgOgl*UB{_j`CvbxX89rNJeTVm!zD){p=XjD^7R3Pa&T{63mf;9E$*kHgt zj6`l(ka7Yp6Wv+FH4YmRqeVxfr4G};`{5o!{?sW((voYA1FxDE)k#r1 zcMu+FBq*-Eku*3WG>j?_qnHij{nEZH52TqOMJ7MV2n8WnE%805%eq2ZIB7K^k3s2k z-O@D%KHq)bMFt|}M*x=Z5BT~(5J@2>Kc3ZH!Bx%$i!!dVYQGkTI~)#{*gXQo3zkR=9qbd`LZ|M_uN9t}&( znS2o-OS$Cz-tsT&Wz=%xmeoa6B3W6=N#{ZJth=tO4lwzuLm5_}21F9nscw*vOemTf zC$mduvlT2n_Dh~_CTgb2g8I?SX`YuVi<5xC9#xMPf6tx|qW;;DD#o$^%mx9uJ+!`q0Ee#m61ox>^`aAPO3+61t z4If4CKtc&4pwG8&02igMr9OsMZJB12p4>n)A{FBUeQ{5iYNvz>GhkISosGMJuVo>n z@0#%0f|MuEhMJ!6M1$@yd^Qjn1+yFa#_=<&5R^M;P^J0tD{dfOamS2#=rd*xtItUS zZK70>@&O37f(*1Ts@tWnJwkA!&PwFscnXWjiz>F|5B{)6>se38kNN2rvtcMc$Cb1O zW6PR}@WK!3DtdmYDvMbnX#{Ptv3OlfP8u70^{;_yaAl{BE`hk1A(33ujBLx;BbN?& zZ7C&k#XXxy#4qd;2z!Soh;rl$7+sMrN~iNDaW?X(cED1w(cbxV3=Y#385ouminVaC zuL+K2Lz;7kD+U#i@8B->nLuVnTh15|FHB}ADQZ#^ewYL2zxk^NjTT%NJ!E%W_-cRz zR_>A_d3FcM09=ksq*~f+(1VBDgsh@U)KPV(U^V`1kwASmgqR}{m zv~DMHpQtK1yJ}S?J*rVUQ}{y5wSJJ``bg-oJlQB}3Sica=7obpr1bD}Vv13JxI8{x zVJ1d$=Au`CQ_2S>Zth^Ta-bPn0xwVg6TYMgUjzx;J`Z5!Zl(U=uMhB$v8ru+Ew-Wx|NqXf|+ z6{KV`z(u%LdF#!BR(P!ma8Pd;;5mD;<@~-b^+v49&&6L|U^DAE)yxU_~YD+nH0xsg( zPOK1I#z{6bcKwCTkY^DB*0A*jS5ZAM!Tod#JZqg1W1h2spg@>oR zK(hsU6wTGUXi4y0K_~!UT?L*1k?RS*E4~VR1>Hc01>ucmcO{{Nz+zPJWEd4VnxTRp zmP5JytT4I$u;{#?It?r14D{7 zoW=mnhbPaLACK}S)_*j6P5yd?XSF*!Bh<5E{7TljMth3x$eCccqu&rkBcrFq=yCQ) zGl846gN1t6s%umZ?wS0Wlpjq=szb4=kLt_uQIZnWJAJ$VjC;K4QSLJUzP;OdG)xr- zsny+TRnJGOZ9x@22iTSO!YiUrxxhFJI{b){7SzH+`PoE@vOn_uj|=#VSO$|{iMxWo zH}QnDj|~ietxC}18N|n5&SDEv?5^7$u)OXw!18*} zXrZU(5o*C{uhnW==hI*u_vD4D2uOQ5u4m+0=I5k^6zjvKN2MurwSnWx@ba?hqW$d8 zgW3NSVIsyk`JhviIO!4-m9oTnZJQb0gj>ML&zY91g$H~sEms?g|APXs{s$QYKM!~e9Gp}L>uy?YQQHI>Nsz>U@DeP^!1S>9KM2I#Ou9rK)I-^7; z4`}~aTtg$8aPAWvSnhuM^}}c#ETGa`kgMJVC-rAI`5m92Jj)#0mlj~hVmP_PVsTn& zD!{$*%HIQJr|p;ciI|zR$0t6^**}+ps__Z#`eyr-F8?#mu2ig4v$~1|w|yZWwS*KH z!B}|~C@%c)@Fd_u4*Q_g$-?JP?fZ#ljCc5HOttaTIYCgFfdXl+R*ieglF>?gdg1OykaPz#EacRs{Q*(_c4 zwaTe5+1coCoxHtzM&TEXxaopml@#hqISP%EP|4m|jnSZBuf|ZO6ZMV=pVIt71ZiS< zx|1s;nI$JI3d(^f&cPZd;fD&Vq0T%<{Uz1sAigsBJr)u{Ad)3cq?cxf1t-CiTV!^a z3uY+HWwaIonWvQSDdk9HLfqdlMa@!6ULL-h z+0(SLy)8_zLVWk8?|5&%^c~hM9Ol`D_xn*zr(THRa7a=ff1QUQ$A%~MuqIUjL$TDz z8!T1Rsf-~LpH6TRMfW=>itdf!R5hK*Z3t^SHRKSDhz_09xDna!mF(j8-Xy!0^@$42 zT?Ax2hJ4J3X2iy*)l1)ghD-`P{px~;k%nZqgYh;l&}K>r_;gh+2G7>&;*O#*C`{2# z;Se4~tx<;FI3{7D9+{{$bFx5&ZK-5R5vG#mAtk&1VL@X4jByhDSncw{m>6KC=1 zip(5mn7Bjk;s?R6cHn-Eo}uR1Wig=hnpQa$gxTB(C5)d?!m1EkK3glZ zIois2bGPygNyQ-Gq5)C+G0o3z;BE`s^8-gknIgkmAVpW<=cpzPpFzO+5d506gE66z zSZ#hH(=sx}9lJ=mF+A3B8u2XJf(c^lhhRO80g#hhx~#)g9y~I%v`Y=s;i=d%tGi=X zcSwb5#?yy><~j^arDvIQgE3QFhFgC+AqRMAOqrEbPucD?Wh#Ze`g|GDeiR|*OJWcL zv5El+$*jh-kXfx8NMeK!*UzxQ1uBF3#(mui+ZvK!Q>>o>XBH+;*vTTW+s@xO(-wX|>*Hy!i`vV9*3N6~HR&}PRc*#|7>Xrd2 zQ4SRUEI|<@c`cwQLNB22#RNrb13)rdRhpt5j9HM}G&^n=e}};5RVW%RXcX=RDecUb zNSk@It~Znqz)-NQoIun$DTIS+i|h`VDcTC&jhJyqbFT&3S2uG#_{$nz{moOwz7%bj z^I7-+I~d^vEKeE*#DA3OHC+$)lvadec@f0qa)&&)2!uSkf#1=7;vTMZ!?zNy%g_AB z!u2xaI_uGl>pN{Y;re{L;htKif8L9J)c01B^KTSj-G1^`sy;&YR>sKI{HAK7&>O81 z91+K6VVK!v3U$?$NQJ4=X!QsjG~`cmm^lDuha?#rI3!^?a!BfRw|lJy?2w#qAY6{s z*>)s{B;O^fk3%v#7ay)MEr0eSKyze=q@O{Ah~s zXAa2~;38L{Rshij4o6KDzQ~m{Zz~W0NbD%)np}pe53rlhD7ankmNU}FtR5;)164v2 zXQun5_%kY&;nQHfpSA>CG;;x0R}EYavNv+#tARzy8ySn~p!!LFy2)JhbhBt9O}Ez9 z(vO&}zy-bTcCXcdabdoJ;NuIJy21sUg2DxTS-W)U!}Y?M_D?jV%DCWXQtE@$ z9oAxXeNVWcM~n-bn@PCf`@#ir()^EgaS1di7hMv9aD2KHw4~sIT7=!EFKTM>Txe=_ z188PgWr@ga`&Qx-(hL8^rx?pHa|x*yNwwTj!{+1tHiJX-(sJN&*`013v1)(nSNz3a zG8xJ{?E^ctIhvu6AD5-y{M0B^iQ)U-)EKl*0;l)q(|uWQB0izi zc=A~ndQBbpmoenr0Xchk@>(6}K0pW32Lz?9U*Jk|-QqEkup&&SPIgiWCFQ!$!0xX5 z3{!jEXAlzCdqxx^A!VJCsn{t(h0fBScIz| zOyF+&GBY)KLfK57=+_wazal(X`gWpzN6jJu5pr1!fKkH3Up87#G9H43*Tcil=#8~` zLI8a`Lih*yenfvvD4;;T&x`e$30@W5*am@Npw~kfMT1lK|KVKsgB-4n{Cf9#L;PK} zsufx@THk5YyB=mOG{*U_nD9_1GzN~k@O@qqa5|az=)(y(+?b^^r zU|o0MuPr<3v)f=ky(2u@-{r@wZ*_|Q^cQRfk@ZI1zuwb`1_#=C3ncT zL;1@RP`}1mtFskyaE4k>2@_X4)B**s$4`GF zoL}$WL{E3xbQWjr3+kEN^)soiRl!Sp| zf`KT-WGC>MYvX^VjeySQl4`RPnLkrx{x|pGMxe73MW&lI_$$dEI~`f>mZ9^f>X~>n zMUHL&I&*DofzD3gJJ-V3)C=L?!Or@*;WqwdZi6(-r}HfBK#9pJP}TmKVR0j~v=hy2 z8&0y5vE^=gmTn5db1iHGVTG`+OW`|L@%lnN?92X@uqOLg{=BGfY*CgIw>U_a=Uy4z z*GSxw?`o|KH;R%~?1qx`Yh?f8h8n&~H`K7_hElsX>V}GUQjVnjL(Y+m&duCV|GV5! zkt6A6`fez>c9IM2#@$djpA0N%b}0oA_X&4B+u1^Dm<`(UR=l!ZrLA(3B3F{KOY zT`MBG#W_z`zGH8&k`${5+{Ckr5Q0E*4Z)GF+a3eF?lX+}de5kel+93F=mQL%-Chv3 zn{Z)O@8kGvvyUicJCOHuKHU_rd*Ux-0g}kldA3M3bg~{_?>}RxYC^O_=FLiX?V+lMxnq%Xi^h7KbNFEuIz5&JQAm9L=2Kepr;# zzNGM;78{{X=_OF3y9wh8)X1$l;eh0WM8DPmAVvpH`=07*M!(|l$%pNRMZZyUTq%e~ z)sB9xEnK@jxpQakpGc`9TR>z-MF%n+6VCFh&MgfcmXnH7c(OLvM2jv}gSvlym@ z|22Z3+r=>BowMyQQ`m4j3i~Bd*s!B8Vq~)zCIoS_6b7m6QWa-KFeNNtok?GCj@z!u ze^2@n^rpoK>?F$10UTrc-KPbDQST2-j#=R#O}McvRc1y?%oyTAU{a1akHrBA#mfciQ@Vy*T%EE=7iIA+k;rH`wT>Ky=RoyZz55&9)jL! zus*^u{R{xYF)pdGPW|6VA@Odr8XHXu1a#-BH+9S6OY2B-bxyd{h{^gmFKVj~`esI@ z{B*7J9@fsl(DJGHLhEnncJP^WJBPy5fbyL9ao!}wL+BQH3j)CEm%mVyduPRQ3y7|e zQtHiDI8?pZ3%{y$c)FpCNEbXTRPw9+#qP$*v-vvt!?VlmdUhz@d{@^Z339Urk9L<2 zooQVaRLd{gm3V1icQ@WROc#FI->fqE3vUn5OYSX{0%tDXJ)zo9 zVvhNO(;`(A3uyAJQp;i*xp3JRNEMF6*}|#2;-=jN<$6kV<_lulq>~b^7$^;kJIGGb zugFf;e7(Z&BJA`+Wqz8~-q-GzZzxNc*soRS6;$Zq+IV4cX(Ubh*g#@O$=#&jDbbrw zEqJfX#aX^8+afV26~0U$VhYOnbWbP=c9!)>qXJAXEixwpD~|N1bmeGJXuC_b90-shMAyt=0yr`4VFRmRT+)yd;Z+W%3_s*+yp0qKumvTm@E z)8o8Z2q5Xz|qT^^#l}WXzRGCZ^M5Q4v@WykggYB1k z7hlX5K^EqORT@YxWlr|c;W%Rt;*4!TVl%OXD_UdaO(uYLdZz~B+`X3u0dq5m4akI( z-nNTw{;>LUsXV>LUI;U6qo*;6J@h%uL`L_|$BHS>SiIGH9#r#op(YUe|FI`758B&! zjIXk`OWa2v?+Vl#GQ&2mb^$8D&X{PBB;M+L{5i>oTGIQktPwkXJ zBvk3muu0$SVDe(o>_toI)43WJoawpf$nbsvq}r#Ri7)VRk@<5XbF|NA^~?Ds|1xwl zgWuK}Oh#Pd`8bCCcvE)c=v3wtS;5LD3=XBmxzqh#6sVH-bfHmhq1J>pQl{R-3tIk< zeGIs`KZ1+!Gj86XQbFSTBxNp246_bnQK|;AK?Qj!q%R@hCHYG=8b7$%&|@--K)t+y zh@^mLQP&JKiGi3(uDIZ$n1%(eYOtb8=>b0~Wix7!?ur_u)MM7#3iglFmfP&1$Nu4T z+a>)cfSrk^AacTd?=7}6lR|b7s90@xpVpN=n9kkmd!yBR0~q^|lhWMDyO47J1+V0x zlFyOqKK0Z`?~@!Muh6Y%ZI2eR&?>;ytjfBZR&qnZDIb*gn%@V~0t^oC6vN~9ggM=y zenTw8T6eWK)J>Q8azS?HRPzJs;3A>GGep8l6#-lm$)s{kU-XUuD2!*>iv+%nKqtZ3 zmU`aoL|oA2M3OI*5z72`52C+JEo^)dHf~f=`+SYB6@)P3+NPRPyv^EDt|gHsxszC2yG*qG+?A1X1Y2Ri)Ue!SulR zZ4zNmS#G?Dq9N)~MTqEaC!pOysjx73KNSj!x-1#RLZ|~t{ZOARKa6wd3-NR-C{hfh zph#Kh6cmPZ6$Qn>K?yjIj-&4AQ*^4^xqIOhHw2b|)VNt~_q5*8SybU;uhSf~e}W$0 z?!khVq984sDH)$>+2RLj&UB1Gcf}7&cwylgo?E!i4G(H3xBktHgHDvCnY&#e|k4!4pS`XsUNy&BLyvv4h@tEUIF9)Tm4-k}49 z#!tC}#Kyg*O}WNEA&{7{#hASY+`Kqedb2x#c(&U21||}OtISOTm^M@z_4c3|xlo>* zZ1c=g+nwB)b}hn2%k@rL_S>qXC1b%C9NSQ-?Cl|`%~8B}*J*ar>g^@5Ceapb?FKaL zm*Eo0NRzx5+8l=I`pDL~%&#lrQJ$4ho~urIHirP1vU8C!1Mp7ln^>AWz0yXL`-CI= zd3C?bpZBBYDBr+oho1MVMfVCrXA0^;@^>L}xsseLWw>lbrZks*Ee-~yJMKl_PwHp; zep31?rzh-QD!;ZwgVFI9ODtgtY&k4OQT|m`e%|Umj|iGm`TxnsE)Uwb1TDTsD*s!6 z1br7l@8Za#m$IGg{UB@%Zot=B{woG1RQ+9W00dJNdo}&>Yt|!vH)F2f63+`<+|EY0 zQh;B_1HXc=dAqx?3Z$!B{cXH!ukZ;a1`P_IAet+M0z}Q;Z{Wbw(vD0d!Y)M}BUKrM zAL7c(ST>fYFQJ#qu|W^v=@aNduESZ+6x-vxbC-zeao%}LG-}SXo|^oB321@uDiXm*i$2sBS>n-sU|3+c$d;K*}L_nQ2|3yx<0;oiWQ# zc_o*UNE4663`oPmvvc@*&JGO`3%K^*#u1MzMRukgl{n7i%w*$;;|#uadymGRJoY-p!Uj0_h7bKQlE|<5TYX9bHhCl+n=p$&piOy zj2tV{c7Rf1*(9=EEf8=W(HuT{QZ`sig5Bi{wq0vARq&Uq+PGv*2|`jAysWobx{#>!l&ci)Pd`et5u z^LxOhSA;oVHAjCYTjW`iEwtZ~&Hb_-fu5XhDr{0%nRuFduDM<>7?xjuie#MqSPGy*j*vQ#=mA;co{8pWyeBO8L{tR=?omf+Z`V!&T<4DGOY zq3BQ>bodawY^Zt|vhnp8){*ra0ZE?rfh1VIA_S5wdO#bYo*v52}oe<0WT z0!@fY!F|{H6%D*em$aBVkWC&ne>i_Y^AaD_{w^90A(G zi}t)~PR_xPKBKmxS_yu1rk1|-lP1S{ zY7w^V+978{59SdAGilu!W>&m2Oi?pgE~iN8;cWoofSsZU84K+ZrW3g*8HL_u6bjxO z7?kQK>D5|hvI`GNelk4hO5Mj$joKd@giuV_KwuFxiOoIP3{iSLEWuURw%G= z@OnQvumT1_$paJ+;OJV#prIeO86);#Hny!E`J~*@G|o4W#vPL#vt+@^SZJ9=5X%Zh`Co?;O*xW=z_7++?sOeTka03<8NT8yGcbJp;Ow zdV5rtn5NmVgfx#A+G6lfgeW{Yj!pO-2PHgg#X_;!fluVn;8LP7n5Ah&4nWkd(8+XV z7z%6P(neu&3x!qy1}#Nkx0XL4fLxNlZg>(mnSwEa6%s^tXz_PO7IR3hNMzpb$r>x$ zJ>^)=3!QM}6$LfTfv+$l+CMYCsZ5Dy!>jc;U_+N znK&I9De3S4&L;GC)*e>%(U;};3s`q7q8B2$ibX6=CWT4!g5zM$8vZYSk#W6d)^NtU zgWI|iDWUh>3U&)rWj~2)(8Ey$t7< zFZoKcAshW-X>IZ)mjt^h?`-dE@+>Hz7v6@w9EItwqWKlcN{}h>Qc- zQKEbN6v7He1g%g9>C?zQ@g4TKq&GP}CeJ65iS7Zy$>Zwhvk`5>uX?!rT!AK5*${-* z)hY^)-K7{OviixAlBqAvS9p$to0$;KzUdoGx&+o3yCETM>@d2*gf~%2qy*9D zH_j|+_qht=RpJ+wOa>KoVi@CuJ>*o4f!CMYf5Mcq|9ty<{AveXs#Bx74LFkT%}27X z$i%WS5fd1fQN+y9>D4&&m28%$(&k+<0Q@^EJn~jxf_WDucPq>i;dPg|+Qkw??7P@T8eSQ~nV=vC}Gn2p0@d@?I z`VMNhWHAj3ICBjrquiC=O#=c;*KpMERl}&Kat&EaU!#)Pg zwwS`G_5`4UVwr}y1~3@#zRAamnI%kgRf1XD-IkhtygjmOm#8Qyh|kQ+Y?NHnuJzuu zQvwYB5R=;3tdJeexgWDO!(=@ko(^Fj$OkzMg@c~(Y=|CY?g6s6gbd5AhNtW-o1}07 zI{hw>QAa2EZp(go;Cor6fl_n7kO_5CYzNmst*zFh&)9*pJ|FM*nJ-|I%*;f1PB}pX z<>~?LLB{4f6gQ9Uz*nGp4TKSc9q_?qL~rULn7|IDOb^}6Hemit2KUh|HelLWPy_HM z932uR&~<<&sbT8~1+Nuo1HrI8Tw_=gfQ^;;OE@*F_=<3FCAmQ8_P%<5z9+>4*9V+k z0$`)UIOF$xm7>|>bMk5$wn;97PIx?$s;2=$LhYD1yNAfTkMr(n;4)6NaN|*Hp>*PN zfJ<~6;+wwMPK!B*NhC!ZLyb#zxMur7Sz-o=jg4$S$c_rD0)X*8Pgp+ktMoCv1oW4m zI#~rahlL+p^X52KlOqEyWe&Md*faDj>Ak)Z7F*Jbb%px~Rl|`_5w|VbiUp$dmlR)G zP2c>6p?_0WlVmPN5oQBM(NOHrAlQ+8VY>!nfPO}1T$1C8a%Z`%$*&~RoovHz7{A*A zE#N4a;Zg664#k%*&?ZQczhKvuC7sZVECpJ=BEH8j34wCSb*zomcAbD@&Y>~~%*+WC z7z2~gA}YE6MM=k{p+I;r0lQy#<%E`J+%Y)Hp}7Yk9f4)j& zfY0U73%WyoY4!@WdI9J}H28bJaG7u*XL#LHeqD){WkXL!syHAR*PSDMQrKFqBb&2; zNPf9AfHJvlkoF@htrGiUAFu+uhqleRfrI!FoWPlbJQ|rWVjyd5_Hej-7%+|5IXJ~E z+7*sGAMAgK7n}d@xfQ(!fDWf?`XSnft}V?wYze!II17)oS%XMG*e=aEROs_rg@(U! z9eaX?G8Q`<_Fj}}WC3mglNHZ(PcQ*$P56Fq@E+-Mmy}{4Z@Jc#;@=lMCB4%m$QU@u zMy>7F$)arsGv$`+hjL&DB~V+_l{n<7Lz>VY&B<|vVmn2uspSY0Aybk;RYT_HYXH2^I(M~If&6d_7_ zlOsfAFG@?Qq?qpGhXoF#-AQ*H^FvHcO?qtt5VvyLKG&(KW)WF=fc0&S5W#Q8upR|8 zL_6q`a4CKwg@S`R&JQNR#@ac1Y;iUR1Iio#A~=xsT}u}9UL|OW|C>rsv$foZad&dy zhE&|2FaF}IxEiAn{d-r%{dtxnK``Gj755TK<=x2U?1=6=#={MLIeEmTR)!Ng`^c@Z z;kzuuzYH#e>4=uYH?Qa(+FsA44}ARM<&Zy_@T9*Z4cRY(tokgeibxmqx_9p6>l^m9NN}q>hBN(f9QE+)TLaI7P z1*U!J7LxH+aMcGXp>Y8hd4u^VuI7*9%6EhL9Ot+jOy}ZyJc%8Z7mh;mNkvLJ2FAYI zvZvfVqhV_oeD-l<-C)etq2aU&TL=a%x)@IWh9>ZtVol!5^HxK{`PIqn&*8+|5WXNe z=MqGpp_>{XBERREELv%E`{|qbBN1hFn*&jD^SNl)@;BiIjB?ps#^AoIxYFNAzA}(@`N$tn7}i4 z&8wI>rYy{s@6wb73U4!ESFS!`=4(l1vi6(#PMWl2WtcLunSoz33}@RB*cqLr?F(o` z=0r(#nzuBVwEavPI)8bO7nms-j_03I-imBX1OeSae*wij4Z|>wvPWZhWCy*jI4d%qS578HSNM=Tpo zO#ngPPSb#)kl>Gr6i`-vOFY-{UwK-2LF|_W!u3ey=Yz0ktKEb}(osiPanQAmI4kVA zG=r35IV83(jK*7O!zNjtjSUY3mzJ-vGTjx<2VtPri>eMl=7TCV>k(FkVkv;gslvG> z9$rp1q(RHqqHtFS(CcpZS`D~5zwT9t*FBfB-u!+lXYKnCABqD;kJb)`3&L>$k2~+$hpRx5z-)xg*7qwD zMJ6=x&Rbb3ZU1`~uCBVEal7imX1M$#!8NKb_?g!Nu8xvT{+(9^TA{KHKO7UYx*%2% zT^EEHn~2RGv0ephjLCe*pEp@st?PVW_FeI=(h{!Mp8P!hbaQDF-wx6AI|{J>&!T!{ z_-i7Y7RP&~P~d3iFA^e2uvm&^`Osat=x1J>J;NKxR;kXz18$13vgIz#oVT_#=VE7b zUZ^&6Rr5=>ljc(2#U88?Y4Tq5?>dQ>O|ItTe#V2*Pi3)dE@IpxI)E&Oh~)qgm*J1n z!;>S9BU5**1tQ?=CG6N8Jc=`K^y8M;Uko9xy+mJ#L+SVgHh5~f!Xbu>4h3z38#>gQ zqEP?6kO-AgS9jMVAQ>Tu&IH*jPv+#u#qo#jIw#r?b|^P7?iC#|czaQXg!L7m)iu-< zSEFE$L25F^H8hnfT7hlhh0$|>2Jgl|J6ahM^ig17SOUa!fKVWsy8`pVi)jw1KSSNk zq4@G-gktwXk8KmdTSBUARTuOp$=AMo;qu^}1Mg+4X>6}>eq=DXouO!^+b{ReS%&sv z|NMp7i53@~`V|sFFFGVBA6%%uN_q5hHPNgVkD{2!)u_Q}6s#}^ujXFh`^)mv^N5mp zHoAmf;8RXMUCw+p#|D$OctkUDY_QI*Jc=}zlTQE}!L6M99YZNH>3M{LEUa?Avxenp zKPw=2mOoU!Jl5K=d~9&i;Q&>Ufa%Bu++HEJFsV-FZvsmiYMXs+bq^ogx2vqmVwJ1P z8Hh-s8b3HDTrx=Qv>(~HEdKmsTyg9mZM`mJ)(j9<6s_E^7y?V2y4QYAuPyH6wR-I| zR;#iF$CQk9144ik&A!Q}0GE#MYxcMM)#X9^4U;OJ0Q5q#{PjmUVioGcnxM#Y-Zya~ z#l!9XyVodale$khMtmo|Sv@TOaSTLX43!7%RAS`LQ*hr|*K4OqAMzxWFqVVD zfHB?Ug$E&XejW(l|NUI=0K(-H&GKBo_UxcvxKexlTE4-4EqjA>^=nV`%PBqgPd(Kg z3JWils*HCd%@aQG5RXez6x~|z|>uE1^ zi{))=B-yg|?SsCQ*OJ)QRoW5gEpi5YuoN9RdU6oG7r-3|+H%NLBWaKNkzInW9llC3 zwnE@MVRKAp9gF{Qi`f>RjLCp~mXp70@;=b4gP&kk*NCy&$;SX*ufk%@POsZ=c!V5h z(|a(SG^S`LdRLQmGjz~00I*Ae{{WA;l@-f0eyNnPNK6Y`bBYpb=f3HZSc)}J!9}4m z0c;mX0Yoj@GQ<0%t$~RN0B*IQ3f=pi@e6 z6CTgCA7^s>E>1mkHhoZ?ia`k|7!m%FltMkzG}x4bBY|~3EQz%spIAksNaQM!$dzR0 zfT~HaTUGCqN}}nzjbjaV4bcXQp!fAa0u-hQDN??ODCH_LZHdn2wfkWBp1e@+Toi?0m zhk2@2BZ-;jOrym2kUQ2J`V)8>9>3WKLdoj>( zrpmXtg&0-e0FVnw=$Tb~Dp^2zH@*T{L5)OE8RKzyacNGsSH{Uzbo-a&ibuaqIq7q_ zzP)lUcU$Mpo%R3b%p7UsH|{Vxx@FMs+A?Nx39mBbl$pQt5%1H887Y4glSrN=!L?&J zDOE|}?-)*`BIVFi$6%OwsJP4FN!$h4r#`_dk~xLUOtT;^t;)2H!2`9$9EV*DIedC; zkFF_QY`jMu#`xhch&E#>;!n=LQF@p2L@a3|PlO9P8;@`1WwftSy6=sD)^DP>-PD_l zVOHJfQ2FMf`ethvU~JN4g<3l;)~pFhwHhhFuio6ri}6hSBzcA zAk?@Yadr|Ny5hoVvh*Ep1@Syowe}KX5#e>!K$*3t{B%{PN^_7Kfu5unkaJ(WCVm-f zpmY(($UJRW#%@O&?ZCD^-QQBX44}~ndH))m*Pq3Tt``ee31(i}BSCGUlt6Uloa z#pQB1Cj{c;y^yCPoFJ&>W;=WYY~dT9iwI-q$0x%>h*MTlLhV)IIW*L)yWy`^gadzd zq+~9gMlsH=`;a&@pNJVoI5tQ{zhj`R3@N3qy@#4=G+-K7yXKqo>V1x?+Nnj7d*Us82#6CpqfSy<*D_ zJa4I2r1U0)+EDJllk$W^S5L~rQ-_NuC*_gW>)J%l%JYPJwWHuU`^X`w?sTZO_I@ql z1-O#%LLHvL?(ilaS>G6-$oGw2nn!w;)D_JSy_%1YVa)AycO|Y}`6^Ya>$Tv)d^Lsm zuTkqtt#4YWxQ7YfZ7^ga7m-}QhGQdSztF`;7npkoAD`@9VvW2aQx|8KEa}a154=z2 zY3J)ip+E_Mza|@n{o<2-g8GrGgE{viC3A+H#EX{7zp6WO&g5sKbYyxMW)ZCuO|yYA zV-u2bUca)8OK@xOY-UL7Xv`vx|J&eVWY8C7S<8$$3}YzPsZix1Zo?pDmIv>}9_VaD`w4xRCS z3Y`(iR$=bV?{}dy?x)Zh{+fxFqbPg#=0az}j40aC#dpw(xROc*<*MoOLd;wSBJq63 zpYO+aTvA;Y36O--bg`Rx(Ez74UT-5Du3SC0K$F-BhD~_|zoBFh<>`DI7T3B$OSdRH(776bS+P^pPgLrxqtNXx>u{seYpWMu>K9D|fp~8hjC)$fjExsK< z;Vfuh*2$OoGx?_Giu&2|ey#pGWgOl{Y3)**lB)u<_C^S%F!@Mwjudf(lA=8jCe0i4 zb6|2%Ua{FfU3|`~2#3jL9Ez(pw{~o2gEJW;LWzUjReT=}azGkica07=Z{*sSdGfaI zDxv@8&0MAD&0DzI90}eA$7*V>zu*>{cf?hapqptOC)~WuOO0OQ%6*6(e#9%k%2RIz znB=SuaalEoy4n-N`i=fF>)rUBejoQA59RHanmyqM(>HMin2;FF$m`~9ILtUWJS_xC z0wKGAq>6bmx_7!?=POwz1>ZrsMUJC{?5T#Q@*=k6&uK>;iwU> zGo8MJ{_^bDyiy^ug_><-&eVR@ar$@2<0jG%{gG}%tit3*>v<+ zy(jgT-{t)Lrzq|Q!v_KmBkZArnt^xd>KGxVeQ;K)p$d2}udGUH=?0H){KO+DzHxK&VxB z-+IU3-&cGnf4Tki#$}1LALEJ|DCBX#uoMlEOmk)MtXw>KFN(8hxd8S~cg6EQ2 z{csV#&&2rS`Tgew7$UBh3n5a}nO`EP#rv1R1V^><=Y!Jo=c~*9JYiQluee#!!V(nI z`V;kg_vD;>2ObQ6&!h6)AsEY-tVy`Wh;a4>1XFtn4(G<)h#tmJ%N%BMXPbo>?3#x5 zyeQVChN57RDg-+o-MEz`K2Pmf__== zZNbC1evJjd>fQxITEl&3pcE=E5vM%hPs~r@6M~`cLdp-+<>`8XB{*OzLPhOApe3ZP z+0WnLgOPq!;0#A4b*pR=7T*4%tw~Pe;@2Xsg$i2WE1>RAp&(%Q6bde}lMW3Yq>>j; z0Bop-h>^ZaPVIhC7272osU})e)rC_C&}!c#y*^U7-nD{!LV#jf%-bhWyj5Q~6b473 zNV0oNFG5xrg&%CFiFtyCGJ_b^E~XPf6PXc?tuUhxMzeVC(CO3HQ(%Rm3~8LYyt~Dg zMm;7C_~I08rYC@Dl~79&)Gx#6K5J@TCN3!Qg@CgwNQ?U<;JL=MKGmJqA}nWhNC2wH zR)BUjZ0t$n0F#@+mg@RJg1K`mk0UY7l0elbuPr?N7^<*54j09Am$PS@`a$MjndrQ$S zPwn83{(;=_2g8?6c>X z;QYn5!S)uXs&-YwDzXyfnIRGe+3)w>li)MCr=uGFa5%86zymi?HH?_)BOaDq2=-89feIwX5M8bQsFq~j1u z59_tNmCHdw(W-`!@*6xH}7PGAE%xSFVFk7EokE0Fj#l zASo^2lGrj$wh4si#Q#QtV!E6r>71CG(tFZiHQ_L~JY9hSW^gM)`;Ng`mY}HzJX5BH zA!^WQj3#tvfQVda5&#Eh5kN9fHEJbVXfE8lJ}wwR0=%>7SAs=|2ZMGbPdINs7&7qz zvKlzMe>&30V$wK2(JX|RzS%x5yqZ3)Sv^u8Ew3M-X0-dbCP9&5ZPY>hb*Mw;hyd_c zgF=2ZDC9mUL|VYH!H=q{%N8z_#Qcjh!uw*^r2H}+;oTk0o{nbkqFG@=PiS?g7Frf* zN;V-%MsmsC@J2?}t=*@=ta@K0n|N(Ad}ZO?h1kpD9as=6%9k60T7gNzFqQ@i*W_`C zBTz(KO+zv_=Vnhhk}GH7xU^L`I{l+39Gy_o6OKzTPa5rnqf-?9(WOo}vYj8cSiLk8 zjvQ_kj(Rmv7}*0~RMUwY4#XPu>F%7NscZRMtEarMe9#F;?dD9+B4yLYYV_!agribn z=7eKh=!D}^fD;V-SJ1X8U$Q+osuM z+w7NZ)0Xq@rup+o?xVRugr&jqBTQ~%XB=w6e7Ea5C6jY|xE#xaU zjgD|KjA~*=Xl--t-)eKQ9zMLQvbjc^?U{^y$>tK4 z%z&h+Nj6v2*;{E2AUN4uV>{Svme}&z4zUTt-tf?q&+~yW?{E;Pjgx2j14bqGG;j0> zs9TL51NQC!?CAjPUC>%*<#Z|aVFP8O2YMh=ZaaJ(WAcO*6h$F1dBWDgeejdF>WL>68LeQZ#|FPd+yP8%iAaiUE)-s+ zqQlEYBX75PF4pbK&Bka~Xb=q_8i2V}f zM!y8EL3OKj+Lr);`_^e+uJcQjj3}HT5J+;`m*JNvr+x`8H~J;?Y1c2IsuOUm*sAIF zo}sC0&Aptv>--Ys2ERn~=$vLK2{N;OiFnZZCCcQN7@VJ><`B<)h9Y*(&(LmI?feX7 zjz^-y*}`gDouU0^hAtpO7c4_{l-*Q@S}H~=i$uw}hSbiG74glDS*bcV&54krhXmwU zC4w#8fSbM`A7Z&K50cTaoFU7Xl{ix589wvEial}nb-}8$0lV1^%jcNg;3GL~EOdH(GmAWz=OV#D!I)%JGZ3PM)-XeG+uNOcYse-n zpQ7Ig3ersNdrqOp4MBQAIfDLHMP!Y$HA9L#=QY~y_gj_{8{Z^?zZ}qs5KxMrJDlCb z&)XD3($A$OIDSq@N8zK0`4cNr%LAze%G`{bPFPY2$C0S&Vi14I28gVbq-%k z-doAfD72`T#0*H->3+!$0^L%d1Bn_-N@sA6rrE$C*g$|<`q7{h=b^f|U6-7|CMHuo zhmB*b=PYd0bG)B^N6+yo)Ezx%VV$0{(CRtZm`TrB2t9}Gak7-0z~x3gN1t}}997-u zF`@xA-QGh6>RNNJq>y!b&cX&gCwg>Fdd|XnJtrQtdd@=9b9xOI{ZA>kZ0>yg<$V0* zd~VtK+_Fr<`)_k@S=q4fuWkJ08>Jm|a?5rSdnvlAu9ajq$=5%Xt=U%G*<}kWWdvQ6 zTV~ykY^BiPK!x|}a9UZ(nLOmEPJ=F9yx>4f8>L~FJjGMc{5ZoT>Tv8uIc9C_B_*KA zx!gF4o+ZtUj7KYTj<7U2o6a$tCY&L~Q^Hvr7Lfuxjj+5p`)@Vw@2y9NZc#2vri{{5^7*+t4VesPLM92%gfCLaWM?SPQ^15y zbzoZtBt;fjN(^hVz!qj=A*>@&3{tC57VADY+JVgphqhcDB1wS} z>@Zoa8r%7Fuye?j-fCc^I0tzqBHDse2eMMhSn8rYX^`5@lfD`mMfzpPGvwI9RF3Is z&C&_2;7GEpzfu$t7?VU{;#@QF{7q@QX_d41aMpyASdcAb|@H(WZg zW;mC_-&7UzU#^3N&3H8N(e=D2F(B7LfKr3`ZbcYVufhxAQHU!yTpedem#120Oj86L z-BwMnb4|fn#7Q{ijhfrqrZIK0GMRs8=JBjZahw(&Kkqdx8(L573nN(E%EKYVo;nd? zL0*?o0KD?U8zMT3Be|I5PC}hn5bRP?Mhy=@d$5`@kD8$b4RwF2Bp4~I|1tkH@)B2Q zAgC6b)GMoS!Z^MBDNI-&6M zjQFb*q0Spdd_F=AjWG$&IYQ0;CFkQ`=i^`UC7+Y$c8l>ZRv6=EX1P3Tq`f~in>nyS zBIfqWp5padv9}@<1cz@W=k&x$4=dadSkW?RySTL)oRwW#!PX`KtSFgLTe7rE6IRd< zFtMYCv}xihUm}?KOFW%DCe!QMHm%Cq^N&Msl+>k(E(*>DEa8@nLd?kG$?8 zZp8MzMeE9o3g>Z*=pn7KdNY~l+XYtvhB z_Ot;t3r{=n>?u4rVdbOx&$2|Lf(#v2^K$~so$onr_d~CKnpoiDcQx0dcC{)<|y?Tr7m%MIst;uI8iUR~Z# zUxe-l^?T_{i&Ofg#A)^M1AN}NNEk17_K~b^Y@gG(v|(6JTUjJZWxoVJK^bkIJjj!8Xzw24PFqg} zSbHxjUh8u#K;lhkr`~Zcnx3AB6A(#Xq}@bCdTxHAZk&iU$!Q410}O#($=C;iAso|| zq1vT^Z9gVC2iCLUgZoI-v&B#Jnz@pms8S|o^ImZI?o=oJxQmYJv~QCGkZ~U%%=+%5 z?2~Z>wU>QYK9Q48mBs@Nc7`kvGaK3scjy=dPj(XDx(<0+-?*h- zfKzl^BlL(hLJfmHwg|`wbAkXd1&SAU~@#p^_!q0sph z#a`?a%u&S&gL15$@uk3_*U8Y7%vE`@dRpm^-p!4c@6x~K#3Ydzq^?5-C^w&4iF{{> zIqh?lUVEcw@gpkeF=&3YZyH;RSWbXxNhclWg>(YJX=knbHQl1_knu4wB=|fvoMsRl zkw@a}nzy4L5m~!OG(~MaQ)iF;b~LVr12#3s_aUg7T9Nj5{p9hCgXF16WUgadr4o3L zoMv`e2l4(m?Gstq&CWO$PC!D!?56szd#LX}-^W!uSB~i2{qIfh zhJQ3y%&}om%KlL#TUnpLPfEx9n7!y2;CRt02=?{Bc@8{VnrQny?`b-^%=C<4yTcm> z%}K`H$-)*+Kz}tS*xzxC7RU8dNT|`DN~;~nxjCrQmKAW%0s9Q_g?4Nh2o7xi@`T`1 za*KcEU?mK4jo&bVLMj0do@#}GX=xzOa9cu&AMK449%#-zRlh(TazIDM4@8#Vr#=Ya z6Zk)QqwMpuR55t=UL5@@h-Zc|sBGfh?l@Fsdd7pEv#o{dlaGbL3q+Hg<@zxefR&9d z#oAZ$g$e;B{OSX3aKzJA0aAL5Hg-5p`}NPgi{XC~$kY%a02tIKKz%d5$+9w226FlG zH-Io%iDx=Gq=D3Ao1a;)n6TRh2QUbbergE%8-va@YBE%mV59I0(oht9mxg%?22<(b?z-ZQ@uCiGIZif!+}|ZiXNnASCa;7!t7+N(CL{iH|Vr_ zF3L2Z>9l&D?&s)Q=nS;X}&wA`6?gtg=i{G z^p*V+&Oly2(dT2LkJeB0)n=ma_CyCtX`(Zj4fFh!G0g>oO{V!9XJ&Z}U;016q%^?s zPOd!k@rBTp>1Y-*7sm7gCOhMbv0V^Xo^M}^>C39BeoS9nu7mm|?UbBc5De)GJ+VzU z@i_+0`fH~6g~6a~>0iV#8QmG0rth|#bj9(>p3sWH%}i*5 zm`t7v-p^(XJKxW-7#F>t!?Mj(=Bpi^u7i7N)a%d7yBi zClY7Uzc%RlrucakKLqVx$@pY&04J&Anr!y%TU691F_L8s#Uw9BrwSBu#{NcBR>bg| zM1Xq=ZFH!4ZrvseY2dCW`s#MQcC2q3Uu<{rK39G1jxD)>7Tqs(-|TSgd4h7rqsn@> z^>8Q4;{{)CcQ_8EpEX&d?wYJE0I07_O>xUpXXcjmV?Y6&XrPSEdhKVpdHkn=i|vOF zw2^^t=6#dSrxsf87@XwbPRajK9<0N!1C=dW_RCl#&DZmDc zkOlOkeJMx(n6FM{VPc_6&oiZEfjM@z_W)}m--#(Br52zF(7YSHtAM)$?3?JF(ju@k z^R#iN`X!gM{1;cL%qvu8PqEh{iZE5<=TJEv<$e=ikEo>4Io%v8x#hj}i$+w#Xw8(G zdq&v_A7NpHx+%1}^1V{ZNJgf5`YnwRx?Fj``-|+*GnO0e#A!n>L+Zm67z5u+8HKoH zU+_{e@eq2zz%-}xRLD9J`AGbKy2$6W>F0TxY-kcd0Y&IcggE-gKwp%Myyj<*;J&(D zL>xpO%qD=(2kU_QTSdMg;Nl8sj}Fb5-Ph9`=SC^cn6uC`+ZY}i zQ;I%vvn2Ui~+k@%x*JKNw>ljOUp6g(m)-CO%E2 zi7&ZI0Uf-AhQi52OrbRKc}%}v1G6t!VaQjo34LkGgv!JexY;?$u7&g)rqDOll?-a& ztHSiRZP-|KCx4;Q_NcsmRCiwp`VG2NoAncV{5c+P4#*P#NB_ZqQuNpoQASEO;OT7O8e`!Zj(KshJb{*3EYK?7P$%gE;_q2c3&8k!B8Q(X1A8z&nT=9# z)MH6$k{*2UvAn0kzc`n53l2V35!T$IYw#u=e2k6HQyqMauHb}3@7}J@C#v^s6?pgd z*$Uhj%(i;r2IxmCjTaK==v*{hk&jmJSf?JlKr{_MN$G*d-^+;$ zhbphk?6k#qH2U965?=b$;~Z(J%l2NPo5Mzt%&R&`Wb`jmF>t7|xPh>HTdk;FEajYfHDJz;;`_LdCq)8@))M#Q-&X3Ww5}KOH+!LDM zFxq5aMNqe50w*-F`AR|)&ao+)^m(A^EEO0{vlRrIdI#!7p-G%=QD}yl zpI*ud77p%3Htl9N!qNX(s1M*G(Y_C&RLjvpR2!MrYP(w|+#MRrw5Uen!^W-U#f;kG z`Q*dT_40n2QKF)+#~I}l-$n60@qPV__~eg65pm+X%RE0bO7!a)h3jwKj1tol&52lH zdscX%j_Hh=lN`I5iKI!T^Te4{Ig?7}jVBeejXJiR98{^mmpZasKw>)8 zbO{r^Dy}~Rm6>`uEjf|)l*6VuUo2rNRn;lf}#KBIlw=l$+hjMx?lh@I5&0B@Mq|(WoK~16l z6%vM_{=vmJQ~x|~Vcn=8H_7^zROllk#)uuNFJ)xwytB=uf*f3h=c`vjYkE@w;(+Pb zNQG`0ONDhsksHla6{ql2MJBq|v*=kSu#ZotYO7~4-{!qD0eto9Ht-7Y$?A)@wfuFt?dRU4|S%5n9{nM520U5?%j&vIG1+ zEVlZ+|Lk}Mcf;l?bW}NBk}%v*_?@lL)Lp;@C*qWo{6K9lo{ar|vR2S%(CzhrYC{LA z6y>i!{>1TV3>-`5_qnGz@@J$xY+89K7ojTet*^Y-t-OBnvETo>!Amnt{hkBOG7ZI1 zO%f!RO<9)Gsdz!prrL$P>&V2%zW(P|zcCFPU}LvVO~cO}mTA+PhW)Qpkp=G}^DvLf zWW*=)ut#V?P(J60_xKdb=LLiOB%)I?MIdy9L;|wJh0H$tJ`fjzv=ji-b0gR-f~SIg zO3xv+ho4W7{yO$Zm5sJ+5iDhB)*}@nST+ZF#BzpNrveaiXZbKez1xyU%4P>l{G$r~ zIX7v9Zvwr>DS-Sc1u(@nY#15VTTplGR{Hc*pKhs|I=kLKj3SE@d;f@TNt~#Ny?W+P48@+$}JjBUaDzG@2t)NkN`c`)oRWq!ka~NX==2YF0tqS8XtlJ(ZSo3kfdfms3 zy5skfC81BxBUeS-=+ko52)m6>Njn*d;$Kg14e#N%sXHc_HDMC=C6UEHv%DwWVlt_}0HdEGxyl{fBkQS7&D^vMDil#f1I za6rq$#kg1O?f&D-)fmKcc9RPDSk1_cS*pgnhDD#%5JbOhTnt6gTjI zp99Q3avZr;=HM@Tn^U!j%ct;TF~j0O{noYl8?1v7mOV(Ck`-d641HI6L{VRc%DL8K z+~Z!&$y_?2hkn75_@3NPhpo^+@l3&B9xiIOeA6{T>VR&UGrjs=y6is;LOl3DEDot3 z)N}pextz-U15VP>j_Hm5=#9{gLV1JUu&y{k70M{^j^3#4w!s~6lrD_9QxP^p9lUp1SFfa?`)xRmKB+wO7CSAPoNnPJGNJAgDS-PMccFkn+p(l#E(*3UF7+E63$7gcCfj%PiDEds z4%Aw8=Z9GRG#lBe36Ue}EXF2ot6Xk<<3tlOgMB}IK7zeR?>i2a(Th&7_jw8&ZdUb0 zj6&3{Ke}Q8#OR&na48-OJozy3tl!RuM{>f>$XAkABEN!(#WYv)nmSE#4)dqJpCNb| zkHAxoK1MXAeu!tJaTR0%)sOuW*AMe2vbgwC@GDs5rntDqPG_JNum>jQb%dU;4D-S{ zAH%oh1XCOCS?tw6na_8D>91rYeur8Ph2yrdd+n) zti8ju%0SDb^n5tZ=>|#Bikiz|HGDZ;fGs;MOsvw^&h;&Hv?u-dUVlD4(O>e15jjRP ze0}Qz&Cpa>XK?><12e0ya1-?tAJzk20SqY+(&holf)r3DK;=Nrn#BlC{+OoM9S1}R5&cvXGL z4ABCm2QXEfpPayfztKwr7{VaZ!w1eLoolm6aWE=PPZ_niCixIph)8j{#YhkhV*fe+ za3sv*HpNGp=!0`J5Hqh4XAXb~>aDhq8W0+&Q37)jE{I{%Ifdrt5ApHSb6NwidgvB5 zTlv-u?CG0Eqcsw#PRSY8DdbmZYEh-cKokqd8>v{hi==O*%faI26(MmofQgpds_dfJ ztY4DPQ5ge~2IE|`NJX#Lps{*){n#%v7nRa7`d2w9PEYUVrJtUXreY9{!Gdzp$C!@s z57S)Nh!j91NK;x|&W;~VqBBDgY3e9I zk>+4ZnpRh`bm$6@27U|d$I>Cp#UMmp3ZLx|Ac9t~kG&JdWMO1}OhgX{OS`sZ8q;iNyXY*=7goz)m8bZHP5rt!g4X0oM zFhvKWy$m)T*aMd^1mP0wDXLrqnn6C(5t0hJ1m8jX^fc`6vkn~6YX(j-Y-dKc179aU z)Vmq^k-UN;&ghUfZ$UZRuRX3rx9QsJH`8@5`tLH*g_<&>A+m8Dm~9I;$U?&xubV=f zP2&=@r!jzBRGR~8wIX^FD>iiCLhEX`mrm6|@l7M92q{=o{sIz*`;cUA*>&*A_PzyA zPd2~ezWmI?ka*+dLOFl978~P*9u5CsF;ZJ1ueFQy>9zfJD`k!dvaj@&Qe7)$CoxZ~ zqmrDdDZ=6&_8~?@XwMpW;!9BuFuGN757LM1HqqqbjX)`DX94cID*aSh*}{D4)`XLjU&-UyzG=jj zBqGk1G-Qa2dR|(*B!+wyLng(%sb4!frG#~SH*|t@ZnD<*hpGX`<3?dBRPk#9Fi~o} zbVe``E_i;xfbQRPGr*wUyoAB2n<5XkMT=zhh8XPnX-Pp8AXyFLd?`7$pJR%G3p(KaUK-7pd z4xVYBqx~wN#}HS_G6?h`M-pktO!6Lgw6_6;mOZm5TsU_WQUSQuG6ebv6cZH4L2d|F zV1WHru=tr-u}7UZSt$n}HD^8%ZY4O8PaXOSyc}7YP-Z|e#en;Iqye);%Ly2e|12D) zwB>B+X-^y4`pSDc1z_7#k&}7`{eqRsgg!#$U;wN7Qz5TFvjPaC?U*m-6L`&74GN3p zM_NqJ;(5BTa7t@%_J-S_ASVsqi$U=>h!^>XG+3$2S~6~{0ANYAr1r}-t)qM4*^QL! ztiBu7e5y~=`INsd9$>?vxtN*~>XlyIU$p$yrk51~{lT-jv^~pWVmV|2`=kTbdsA@h z8ktq38;99iXxU2v;3mz^75F^-&gu4AJeyWROiF~WYKD@+*Q6DhKo!j%t6b`hsCc3pH0Ea~D}&Aq zGiVOEEK9;LV?QxKGAAOUNnlEA%4&>dX0RTu9?)9Sw4-R1a;L&dO{R_kS30i`t)C%o z0v#E#VNL>^7-^3G>eq6Ru92FQ=+j4XiL@XpY48qOzUGV;@J7>D@U@$Pj$6%v%i%i+ zbQfmL!|TpiUliEfqNbOrDpN#nxSZ|#_|M;U;-}vJi+^?aEzgTr=E$!chhiXu=ie){ zS=I8m>??4p4F5h55ov)9X5947AN@3PIF~&Jy@$_| zmFls<{hGUR*LaZ$iuA89-H-E}QrLRpG>*+N4*9M5sZajM zM`E>0eFs+{o$u!3?(}7EKdi65W!+aS9^k8e zd)7Vf#pB7k$B6GzbskY)p4z7RS9SgL+}jVUk6iyvsi)T*p|x%ApA74Zv;bq;OV~_V zVQ$Sng<(@~H9EX+_qrD0e;6U0nRg}tEvUej0JPYYJSPBQR&E9$@`&|#J%E;*Hns|) zjU!%B6`O%>n_g@HojjcYXz7dq+TL{OSt)2oQ}Ububb-IxKtagK%>ZDdUHc&eiue+{@e+Lyu?W8z#d8lSPFL(G+sQkL|IA6>^Q@6!YGN)%lAAQP|b z!5lf~skpV_Djfc+fwA&KPbMoJhJz}mSFEf$#QWUu8S9~X_pP_g6f^gvhC>yb-5si5 zIP__%&?E260~ zR64wfP|#S&N$U)=cc{MVprxd;LRmn1DLv`ePpaY1NjAGa={D4d?`XCzyiaXivN(b0 z+-umrUP6Y!@rg7+H%|)0A@J1nVFSF1aJstJCB3Gbhw7IflnyAl#;0Y29KvdNm(UHW z{*4TESeHL2A|eRk{z;vu0tYkabi{PU@m7+i-zNE20xGn4Yn_l0cU})td3h5n2rB}- zeH~7_2tpoi!60&ati2P6PW)H{BE5Uy2zOT1Mbc|XdxNh&$@BV~>!L%O1W6v(m3Hg6RfS3oGjnFBI+e4+`+ zTxpZ7KK1)&49N64A8riD-1D!GOLGBk4#?2O#@bVU$I{jKxG+~`SNdm(AG$SPe}o2W z?ifXV@)Xw(@xU>&;cpjg!{xmVPG>f2AXW#0a1d*JzT3$Ucgg|~_eEtNeLUBUuu&q# zzI;)!8`HeJBsjHB~7O%~jm(l`>XhP}YzA9-TWv z$$F1$n0pHt3I+s)@G9_>&1E80Mc)>$;K>~i2%z+WohRk>(zjRPM7zmX^IWL~aYTOh z9vJ-r=o3bJqtEJ!W6623r`WB5D3eP505oLuQZ!88mD*=Y%kS%_=^gRMQoB~wCm!bJ zKK>Y(AIs6PtzCPwITpY|0JE-PI;l97IL5%i#ewb1yM@^K(U18o?7V)LZWncnCoiwx zs@v_lMRU&UFZ^HJUZh*8y7eQvy+F708eLbnJ9R57iw8&bU~%*lr31}y2j!eDRayNn z{DO<6{q=oZkDeloosHJ0Rq<$2Nq?!F8PHW*9gy}z5qcqj`T^r+6*B~6soA?|N|cpz zjTv;i$9&c=4sZAZu$=XaCw_H(8$j-?Up%zo3xnY-<2bhA3scQmzPNqE7dC|XLZmi@ z&`E;~-ctW1kh&Z!2GXwJDuct|9ro1~TvbyzYJ^t_ZLb4s@Db1zvGt~bB2ZA&M~?@1 ziu$kJ0F&UQ#TD@HBGs;|rgj^vx5Edb@^4ZUSk8K)5SW76u*4*9yO2P`d&+BJ{=?yk zJ;lmf7PM&!^I(~Iu($gUx^x;LPMl;n&M8+r*_OoI4k0SbADHG6Id!|v$15UhF+dzd zD>ySaFh!oU9zQDr;kuYTYuNZe9@ns1%tAKW*1GX_+3 zP{4-#5Z=Sjh#|{>lpREKSgS<2 zaX}F=P+yU@Z0k=mMG1rx4ku2F!3iW~Oy2MlzsBITO+$V9BS2$!eV-m!8Jm}KI3(|g zZjoU_4cM>N$%_4d7nNnS@0t9&XlQ#?eDJWKZ^3PCCJOB67LXx1-xdo6E{a{SzSyWO zX60fM#dfYQw%Qi!bsD{JeX*)7*6$R%XnnCgZLvY8*v0FMUDXzw>l9n1*nbH`4GN9) zn!`g3^6^M2%Y4PoVnwxM-%D=840zdXhpP+reG7kgV(K5RF5EZa??wA|^Y`L?7x8y> z-{>}3@YksLtS!fbu0=AMG5UEqH6{cpbh@(X%E!TkAKjWWsGk&yje& zz4N+@o;%|8j?U{YdhUwX7j#~C(Q`Cj@9ezpqUQtg`ohlZE_#l|>x(+CXV7zT=S2rS z_rpgvpa&n!*XituL3)k;Cg^!EUSHA~Y8O2Z#p_Euue<1ZBwp|8yzZjsRJ`8ZdEG_N zQnrLrVz5Repp{vdfuTD z<9bacEIrvX*4lvKnAhLNN!4`AvcFuu!v3Zyh&{tzctZwChX}SONr-950^Jbz3&|1F5exua>1fjuA;P0XBk8r= zWm+Pnca&&^yjG%4ON8K#5{-OkSHVt+kl9h95$%1TFs&oRb(9dLOI-I+1r@F?jfsAP zi6pZjnO0)&u*AN=5__j5_Jx+%7g=Io{I!tSsCf2^l+NOp2T37C!sUhBRWpQq;@2B0 zgdpm}y6dWVPd0Sk=zMp!<+ahueY*Riydn@v+;zXcr!7owXVr&X=e;7bmX)3pGLe@E z{4Po%J}}OqvlL(I67JcrA#0D-+X|VecZjyHU*3*BOE_n|v@c_>kfq1}i3W|OPM7?j z1dMTyciWeC$rHtF*g?_-f#p(Df{^Gz!UsXb|NwqB{WBaWNBm%)7xHLkC z=HJ?H0u}q-@CT9AS;IZp*+*mcEQt?2OFRR~zE@^onWjj*4(`u1tXVxlIvlTKe#GnG z{^TIA>QuflUdQB!*TMa@4hLVVmK>GLfoZ3M`;*I14_=M8Pp@NI#Mi<7mEef-&vr7r zjyVypgZp!iSUnHY;EyP+n(OC23(lyaVp4ruhUW)-Y*||KT zNQi0<2~zCUzshN|$TUqJO1{dHCjjL@fm+?S_Sc!j+BOL>?Dm5$YaI2Jt488()!zjN zSs;1WX-;ZpzS?%ZHW6mt;h2#?bGbVEFSP!`OFFuhGdC0b?m5wbIz6~={eu(D12#cj zypH)JX)HUok!%*9Sh%T`u54au+j*?5WjpoI6k`D?r-(t(I06Mj2***6ZT|#7TldAKv1fU2jmY!oB$0a@J&A$+@|k-v@s~ zPr?VZiVtYSvxj&#`g111zIi&4ll{6h+&79#Zu?7gZ*_WG=Xy|eAHZC5j(p(po{f)r zwSbYk@2bsT@w%rY65+J}vvPYV&pQd=LISuD8ESq#lfRvrc6eVQVrTJ3>dz^G-HIU8UEzO4=Iol} z%3Fs0sJ*LFdrB45`MOFKV&^*-$W>rZ6sU-k_#H$m5|w8vdJ6TiA@I5{*_RsAKMh3% z$n5D>O)O=!ZEi1P;eq`eRi>DPxps@X!?ujkJd ze2c&E<@}IxcMU&%!VL1G>gpPX-pSJXIN zp>OJnI@WY$&;Cuil1EqeZ0w46QKu*B2m91Jq;Z^~5Yp)DT#5P-z*Rq`!D(mg-^>I4 zX9x}n+8#<7&@StH60{=}a|L`|VH^!bR{>Qd&ly5L4Nw(gGEi~frrt%sWwv*WlElNS z){nA`9pZ(66t_9|aXk3-ISwtBvb+6CW)3mhhlxEY*jATBe1<7+p zhpLF5H|$Rxd)LQ!p*;m4&ks;Y@pH32KKjdtcsF{MkB&4SiOKQN7xYmRsqeOQST7WT zrKKln)h$`w&g(zWyX8$<+VmqaE?W8pebgP3n3l$LTrUJ`l;OO7kUM7us56?hD)NYm zkmb|9)#)Uv^#9O{;Z}PhZp26b)D3R((LdJ<(Oq}U;yZlwjx&Aq6YfIO{RUKgGwpr? zOFjjpEl`e=5IUDO>B-}ILBj!)EmkUSLQhZV+xbl@?Gn$)2BqQ~eDr}c^`!gJea%PW z5PY+8bn}PRPex`1h?1ng8BqEc50mcnB0)wqJ?<#<4&a6laiCEzj8}Y@kw2p@EY5Od)!y4#3mk$5Zw8P39 z+p7*?|0MLVl2xf3^pxB_{V2(jIS5&__@g5-ZsqghNBFa(r`#y#|8!JKqZnQEmWye& z(i2z`b}9uXvibv`3QS0&t#AKy!bCg5_TuliJqH_`^fQjKUYRnk9Vg~Y9j94;;^EU0PVYi9_ zl7r&dBTa`(d2JN1xSKJm8uz%1%yvw!OT_<^Wij8L2xZN}sY@f4Ijd^T0%!)9Ln^qm z4SM1?S6i*33s*6VosL+btFdd+-!Ri;@)lr5AG7R=UT7`d>SR&dmWazvSP%%nxV{1p zs;=Q6tP#Yys@+huj8h*`{whe?mAVw*Q(=fkg;7`{(~%!%Is$yhUxMZ<4Iqw?HN|Pc zp}ZEjHUc(og$zUTVbz`lYeFr|EKLz8Mg(Y!GOeiVDzP!?(GeRZHYWWl!&vQ3`m=r$ zlRkaZPWm#-hksVa-ZJFBdE#p(P5AVe4}f5w>}1>)?efIB zYWZfLk!$wx!?0bR{c50~7iHPi8FAm2%@2FzeZ1SmE`CPRs z$Fq!9@$jMlWHGf>AfOHRYS#CgYekP@;wR*3fuq>+l%wmYYVRhR*w+=w?l&La4fi@t z*UXsfcQeeL%qT5z4Drqbxttld&dQ2x+60T#YfS<1S1?7vIt2Y9EfP^^{saCEL~%bg zKaWHaOHelwh4CO_m@;00?75Q1Pn1oHdyt2)rVWz$Ay^Zfp+z@DYb?WZ19!v&zpQ_+Z481Q11JbX#<*~T;b10b1YsK9~Fo-Z4Ft-!+`CQ2j7UA{#8*EkQ1NvR22b& z6%!kxyfam6LNuFU=8<#6%$eqC=rl>y*@ziKVFokNnRA=3pDjWI56!HBCRraEyT+)asb&QE%c8#Lp; zIe3u~qr2Yk!en0A)RS^$F3<2QXWBUpzeRAsB(9WiHBXsqiOe?B zW1*T6Y9{v>BcMJf@E+@c7od9!9bm>)16CkTrgfYWZvaaBYXN8gtf%irM{eZNSH)Vn zt^7Kg6N_K%Iq}FJbmqh*Bk zm?rV*Ra#c3O;^~CH$FwGND+ZI$&BU4YXkJ@hA9fGqk9;)!^vP%Ru>#{tE`SA=o|ek zaP-Qs-d|3q1FzR`bq)Kh{^1m_XZt7+n?5@I91$CO?4IHZ#BLaLp~Qr(6FY)uYs%H7#F)J?FGT;eLU=V=Qoh*0coz_(O*HJOy;Xgs~3#^S;5_&{o@g@ z@%L}%?RJLvz9PC%ZND;u4JtQ>EBrq7S0czrn7c7zLc zJl;4`q^J=0|rI)WbG_CBsCQllx94 zqkAVRzcO1@|Mf>g#rzNf6@9 zws^wPILaT=T2Mx*gN=MBT;Q`BT$uqAaLI_d-Y#q^Lo2X}=cB=9xhlm%N6Q&tC;|a6 zK^Aldz}$(_p9DfZG8Rez#BCEmYUeO#C8=vd>Y}KrWJ03A@8BIz8Z_!^d;Mk4M3%bN zL#aGyU`PT~6QNMQJeDVbDZs#XRDH6{lufsQ)xh(`G|zx7FSYJ`!QNgSKnkkje#n6A z#M$P~c!V0YnUF^SP6*RTG_-E z&XTeVHBtS45cSrDvj zTg%D{l{G8FuoNp}19-^VT(D&j8f$22%w}N=C=0_ez8#EjN6^EU;IW;5Z>e^qPV&3Z z6oP;4Ko&6W3&q8E(5r@j_4H;5fGSm|r$ilKt49( ztgaMgkq_yPj94}@gwi2oHK9I3wpT2%??qE%{phLV8HWk|o{GIh%|DqXLggAb?NMJ} zRNQWN6`N9$H?8?bOx`sJ4zHZ7BAf)bJ;m!My8^vseY-X=Q7^!j z`<$9TOb;uL1X#1d4}vMTI{Is#?%|n)8*%D1h9kLm;M0itx?DpOBh${qQ5Oa%wS00x4C7*T`mhwh>SEElht&?~f|nLI zYf=O#=0+D71fVJn;KG(!q&_i>3(Y;Q5Lu{)w0&so@7^%>-OW7r_kxRiHkj?%MO*3F zMKe7cNWxLq>$lpqft*0@+C}<#!+2RgH*1IFS?UhT_D04DWsgqDPOy7z2V;`(9R&iW;CR&wRC&o}+s1cQBcl=z=u^Z!GIANvykD zk{B7}_6v^ialVqi(mpVl&-$@oS#x zMEHu>#PcJ5AD8%@|NnvbO#_(`uhLx3i(e+|S4aHbhKP}3d0zaU7r*DluQat;*?nI8 zqOWXX`5xcQ@)auycdM>s_38kC&8%KlTB}G{y>ANb_l@v*8!TE{HTvP?j+ZrcWwu79 z6{6UBc3Iz*%Nnx~J<84t>_9ahtT%$+D*LPCus$}KppFaV7AW!zZEAp%si~trE!o)% z+4-1F?t8Eo!&zfv_ihxQ*M+T0v(io8IJuy;Rqx8BoXgfO>j!MC@;T~@w8QrE`_QMP zNgqqr>M;slkgU~XQ)@Mvzkk+TmEQF_h0v3o`Un9f4AInWBqa%eb9x{4qm|LJrm6|r zwtG>ZVpvu~B}Unhw5*>9`>Ldm=-3l%ovw|Iwm(^_h5~|1;Cy>s4CZzC{3uHwTI1xq z*^JcVZ|sTJwf4j?!J#pTq&>@+APefLT>b+r1Me&^n`vs&DF03!4_gNJPE^RuT#B1O zhJa{fbWPi196=S`!8UtQai5jex?a ziZC8%^PYT6sXfI}Ru1d;ZnlxtXIsR7YDS3Qlc7tL! z_G4-$?VZSlrmHuwfpg-ka4SkYJ?jKKbanClb}*T}%_VO7ef~h3ro%+WexwZV8*0i5Z)Lj{tS7(74s z@#<_c_rK~I-8;TE8r_qBJ2kr0?XQVOM_~25MkgJA>l)oV>?c{%-Wx2~4;9gYobwvp zc{lmjg`1pcHD6+G54Qe@#XQmotR1sQAe?8Fo$xvc|Hg_=&STBIPIE zV(B^GgCkUvH)b*P8FtT;{6=|dA2@z;){iCb7d!Ej1;tOufKmJ;t!F?%9klTiZJ@uP zP>ISAKZ%n?iJ#0qlzpZ6$;?w21L_+_q*o@S_(?P#@sqPPuK3CG(KwMFIzi(@I;Z#v z09V#Zle0o{3Q&uDl{PPBBrQ_rQDSE!>70s7wfB|oR~r-BIWJbFSl;Y1s!*8g)*?c;V=Wwvzc=WSA zgafqmk}HtqfOI@?s^nVnW(^L}LwvIZn8n+z(;hPdFe267(KR8=m2S-0+Ni zpbgJRu+cNRCmQBd6+=H-3-HkZsV>i6DAD8DYw{AFb=_wXj#e_HHGN-`B{ubwAeDX2 za;R&6{eWz$d->yi&MNI(AX=2PGc+#YFGZcVF?^(m%??J*J~HgEz{|DVdjyJ(w0FVU zhy%AsgvdFGig&lXEL-;7;8r}9+%47SLNukrGqiAp#(aJTshRJ{)rWZ;$Sj)TIrOBo zL;mrUT_&M)u`pU$^M2ReX0*F!UhlZIKJ8|jMw?!&_`#A+2}|9oBZIxu)3(g1O{-Y0 z%i%bO)1+SW%i@Drte6gahwbRd3_TmCF>q=Wd<0n`Msq3IDoj2G;w*UWJi;c93k-0d z{Ncm3Jnw5?4st~C*ExHdMAod_3#gr7n2#QF7RS6!^){yP-0L({d#tsnBbIsKs6)r$ zHd3NU2tnonJJKkT+}b#jVMu(nyAqV}3bf2OaER5k18SG7S^4xNQev;+PZlGsqkxOxWrfTfKw@ftp#g?MD9eZXJuH3~+=M;133L zCh$`;Ft5RoF_65bTk4B|{k7=K@Dh3lEGGKHz|0f@*&XF@Dc*P%bZ$-;!yn9`dU$aB z^Y<&goKtiRo8EpIRN_!n38_=F=O&Lv`^7}h^vW}oH1|pBhS-d{C4n&fqr7DgUPnW> z+Vxc|fMHH(tpis%>v50+xt1#x!8-oAu54+HrMc}?$I#OX`lG8{)UZS@AU`@=E{@%< z;C7>v-XR%IfS!7i2Z}9dI}wj?Y6E&yzHL|W5Nv1P1Tjft3~< z@K=X%HuzETE)gkOLT8)}z-NCo=4(89J=fhon3VOWvjZ#N(8~w;+5YS2I{&P!Cu~Jo zWX*rVVglSf04@yhfGz7?m@>`uzT!GAKa*W|3qLHNxrKcg0_yz4ms+_%1wu}+V()#o zv?3Z1G?Xh|fCUdyB^B$}>U%D?3y=L&qXPI`8j*(`b`{n%yAmq*aOMP`5lcr`PaCQb zAa@H{tt;(vOOl+Bhe9Wax2;xgb&51#ZfsN>%7w;k3z39HFk`WR{-YKnHxuATtTlf&LRhw;DEnlIx#EvBW zN)x&u_(mogKmRjy;n`E`{IH-00_Z4ch4jQ!8%2d{TMl=Cc(4td#+XKxHsrz3Pa8vT zWZyjZHIOb;Gml)W5&$loPC+U?+Q~w6Le4 zg)2-82x-y6O{qVH?3mFvfKiQAYK3V*#)DW8daW+|q8xzWMW=eGq1^b1waeA?Wa{(S9%(X)TtXSZq*XtVsih3^VOdqtMWJ3#LlY0Z!npdf>(NoXU z`07j;aKGNc|yk{az-(P8uS_%OaV)f$JF(N%92C>r5N6l+|X@??<|MERpxWaEbC(z z?gjALN`?26vMxCS(WWA1OPr60J0qLS?cbAS=*O#vz~cNNZ04uu4&I{cTWe0VXGw3p zdh0E>Oy*f$UByWdT5-=>3q;=BxL~Mr6{l0M{|L^gwa)^9as9*};Vk%5%{N3H$1u4o zT(VI_?6R1z+hs9%+%TKKqE3lnEn!%~QcGC5%2?%zUE+r@{yaa>z8>0p7Jj6d0yvuF zjMB+YtPw-CGySufqL$3qsO0J?ycr5|XpeX*?bmnxacr|8PF)}6#@lS5BCxDHlQr4! zFC`Ix7piiYBGcASKW9f@rtN>}VExoPKSJg7(JwCNTQm)f`Bh(OOXNMelCN46Rp0S0 z%t0MU0k&fAreh__rGRF@6?J)NGGHo+q&UV4-K82>9+5m_F9I^y$1zYF?-&F2Y7!3V z%+LZoyVy%{3G3nzZc8?HbM0i`eJ3KXZ;WWYNbGxwVVpMj@^k~#f}H>#y0YK_O5|u} z0WZsb5RdAcXlfntKp-MDR>Y#OS3R5{Sb%65J~tGCo)>m0V22KqLY_o9L8u<9SYCXZ z72THyI3>ui^SC3NuB_Nd*d%cpF zXPgS9*(z`<#}Ifiq#i$sf*ycv1Tl+_wy7^FE{C5sv1c^k9_rwP;F*%cpTb~hjA=*} zmm8$oG4|lXaI*s|?DAKdy51!Q<>Qe7T3$c>7`Hwi8Ge-4kL$L39`Xr2Xm(i!8^G98 zu>Kvts3e-}N4WN;voq%*|L?_2*J2i(xoh3+HJiDa#oT1nv>?ppG#JMq+h%QMGZQY& z=Jt!D8@@2ZJx&16QxpM zRGP9iRJEYmW#~vN;ZhqWnBmBF(K6x^eIT`@1h{S!ih4-^dx==yv8-sKHT`F8olq{^ zgRsA3F)G`Q0n1OBlnZ6^g>nIVkm*mMP%aeds@bZSEeCosiuwq$`qbx-Ye&E9$EIlM z51MB(!?c$bg;~V^?1hzYJ>66SuIzds|5h{WK5I0y_8FRk#S+`!Mb?>-9NL6XMG)BUMqQF#^2LiZ$ULakQ3v)UR;hOtEdV(BwXSCPowb$n=?XMz`AG-|v$7hk`3~QWl}2t2rya zsq(?IR_^5y&>*sSW95UW9HVRyl{?a8Y7j*RT{}cIeP+7umV+p){+O<<8;f~`6=J#; z)rTI=%=7Mi$U?3(e9uL1l-mTR(=w(|J%nzwur|KvFE+s<>~=ZOQC*%ldaeft;S&~x}4(8{;Of6rF+4EtSI^?B|q8FOYW zByrzwml0)~F4vF3w`mVYhiuy{2y{B@;QGOIOm@`py-42V{y+mjVa=rvOMmtT)UEkn zv}Y+4VPX_@?3Rt;i9DO`md$NrGHmkA1Ah{xfWJAxP1>zCEMxkZHY1KL?JeVm|ES%% zG;|c(eNs6Pkdbaz%FdGZMNjPfTTAC6r8B~OjuGd0=!CE?#sEp1 zjwwO~km$WH*_2FSCA*oPuWlWEF5QwNCUwjHQg;i(wt<$25kLe;M5hA4)8O6!Fl2PH zlrZeWr??UXR=W^Tw8pjYk!a>#f3B0NQhldVeI2MtMLlaDPMljG)B~q=X2Xx>S+TeK zk5_XCR+jT#{6`3yy}P6={sj=_P^kBmn!)wkq)F=zTxL>a^Dt*oRUKZ->vySyIJr$< zoUM)p=k*_}o^9sv$)xxxmApVDq2`*c7u!vw4Y+&tx{nK&m(qlNo{Lmi?xn#t_u24$ za=wJIE^ZTk7z_f&G-Vq1AYp`)qG2=-_&!jSe( zn#irnTxr~@dN$Tn64%Cu09?YS8Xj=q$x|@}c~>xezML1mI2eMMcsQRfmbX0PYf1$2 zIY(Y6tN^-wi*I|sd$+5;fQ^VCfPIUvrTt#L3-i+i;8ZKE?$oMM<=)uO#F^N%y>D@C zGS}9T@!+xSwH46>6ft<#us-(0ars#v;U^9?-ih;9n~#<=L)XCjY2dhri<6?MOb<3+(r3V1GS< zEdXCV)e+Z@$$4|iCCJ|URwJ%-T)S%0?$lH+@4cXQMTT+`4tQs3Fv5aL%oG%W`;LQn z&CB|-gY27H*551V_d-25RLzsuO39)rSx~a#nEOMwxcnQSD%|J`^%4(MuynzNdWRh1 zr!%5DS7v4VO_b55l{o09PTFE`{G;Xh11q}_=5ri?y2*bzLcXjYwg0iCffMZfFsC*C zqQVM)+nGT3XgCFCVYkJP0^G{I-Qox5)=FSCH6pjkS7RuD6yr~=K(b$zR-T+`1-kvJ zwDQH7R#0TVDy{sh@?Ady^**8w3T|gm#6+O%MK;(3O zkX+4>bnCh3vwh7Pw_9LxR}(Va<<6&x^z2}bczj~n@}PGSx9q!aDmn^Dx4<^vO_Rvi zVYWs56xBqqCA(rkt)#ZVQYX5S+FKrnz#T@Zj*-VNQWzCTrjEMSZM^%pkCc5!!p-X`>x z>;2N!>wR|-1}!Q;Pbz?VPD*1}`uC?jPwK^fQqG{fn!Gv2t#6{2=tvD1Py&?bFUz0< zjfsGIW_Y|zvI2<0Cxe5)ED1aqkbK#-wmfd$c{P1rND>2?VmKg z@%*SSRBGDrmkZ)qHMt{&Ycvh237E7SW2=(TZKS0ZH0F|o64f9k&%yzYjmYNa_}}{f zx{u1Vkq`5)5j8xa{4HT0b4iZ!{(J5+OIiP8-_{h7=K2K1)$rG}vZR4bORt7piyTzf7G8bBYmneI7-WMD z|8*Z}mFv1b2iDl8UR-#fP@tu; z5Ku1?&m*7aHDt`tEa<0)S{E}}6rS`@CQMSyQII)mi#4EQqPD~$mGmU#SF<~EAtCmr zMvI#U`R`zIQ%dmSSRat+8B?o^e^MAuL z{@1)`S~|VVn;>&YfN~jWVnLx~%=sFA=aBerItxuKG>yrtLPNeK3sAm*&o;NOS>Dd= zC$j_e(wQ09s(_V_$H>z7O09QzZMLoR-^@+t`KFgqIOkoF-k1FLw)K}p^rVx7bt+$6 z*!DadS&vePKZdGuq&|>Yr;O)k|EQjq@Sc7|Kz|D#ag;*$Bh1<@e6%?Gky5Uobxig0 z>_=!aTli>U_9HaxWvFsHRJkRXq=O+EKti0$!S8$Z$v;IW_?%Ravc4K}2$G$@6)6lc z`EX)WE|lD#l9RNHewUn-e=BnG_Tzokq<|cW^PT~tRNZ1jo7ontTNEd7b&H+qR&Nbb z?{}-~5PR5&9b(tiRPVgL!{d}5rnG(zL^%|W8YZ!3Y46Z2xX4^Hei<{9LA`dmWI^gN zpX-3JneMLB@=lU5sMa$bl+eyIJEB`a*}`9M&5_{@iy$GE2Cq0|2L925W|=h7F) zO09C#*J_{j8ud2wiZxax_%VH5q;sn5JE(`I(8b`!^ye9wI>$;aGLrkp>x(2f8w+Tc zI9ZS4?4?kIq%DaoJ^6JpJ9#gVkCQsUtEz$0+=|qhe!&c!GgBNTz5}s zMPoeuS%T{r7LLA*;((M?S7csB0 zh}qu#Y4rgI2D+xT!%@7kBR4d5X`Rf*1PeKM0}|ot+yDuQwvOd!Q6-+B44si=*8=~? zEyu>INMvjqT(dcH)IuRdh^BP440O~cm;B5VY^ljNW>^1+PB0-HuAqbtY*i73)NoLr zW-iy``cX~kKh>0`3Fom!PH{0S`&64k8wkBd_SN}tA}=e#8K;Y?t|Z>5NG;OQlx;+Y zY-HzKVW|3)QJ7U-WS1Fq2U`KuttbdcKu%-VW!QCDee8=7x6NucDNsP^oqoV8nI5@* z$k^4-{CKJJp+xmkm8mOOKXjme`Y#!7CUA~^U85Z;h>%;YP_O5)p@2Y2z(?uBC9BlW z$%?$_gkShiTOGKvY9+4tgm$cewBM(1+V8jddjTUpsl22}@;a32b7ipzYAyjD>{u1~D?1 zNXT}0%_3s>_iPx>v_^=;>OA66%A=%ne86100kJgvd^sD5>uL~Zzc$L51uHuhs>ZoJ zzpw~bUfDLGn6$?ccHTQNJhM?$H{{qW+VAABC@u#a{n9-F{Q7fP6=~y(SjGee5ry@HO(X z7xjD7?YCGO*FTbOSCvdy)PMcwQFxo2*$$KPL{oYAmE_sq@&Br`jmzR}dG@mJrPHDE#fN0~ynh+~HK|^b5=B=g__5rm1u5o;qh@en$^(H#H%rju*`%4u>u| z8+ulD-TqHLVrXPqf-Bkeb-g0luZ4TS9ra~RcS=mtA>8Pz+mp}q~ z#T7{PQIk)lLPh=ays!_g!S+tDttq6`7xg3u4un*3YRXX~tCRIlrSuvxt)!)iw5SLo z+t|W~QVW{gE)ta0wqWK4ORH5dk!cXDs8qeZ!)TBe)2&OcNs7CBrr~w-iZqGyfnLmQwM{xCEydc-@rS!J<#lCE*!X>4|8SKvknDxCwcEf&79rmBhd z6}YVUKYwc$cGJD=mCG3hf&ju&PR1k{6!HDJiENvPF4kxz;V_93*|l-hv% zkfH#J&Fa}GOzMw_ug>|*cl4-7C02~22pYWZ?(mPt#kR=NK$lIPH(vjxtQu^YH?eAL ze)L;p()nk9&B?&r?mtlf(7_tpuBcIhqeO8CQxC%Ivx^N`IRO*D!e+SRzK;m!vMiZW z2ZRvAK=W%&ez!~O%eZ7=;T#B+>rURf`CI>oNp|2?{7`f$KXjn}c0yNkEH$j}{^gIv z_IpB3kFAD&n*;0QJu6Bo200%s=uD4_iT|-Ql}L)b^t`Y7GEWRr!6VjV@Sfxi;5MjV zaBJN=$T+2IOtS0Qkq0{uhJ<-b3snXSsN{1i$JVEdykl$6@Y^{(lLAM|Fj#znJ%J)zns_&%p z`$5iI>f2)&81K;gp-HLTB&3x1uxKB;z0snL{l&UXAY{gSry< z1$`cDfPMkQO@DMmfVJ;lXh`sRs$B~vQCZ$1!p;C`SdM6DC)<`p%&0k-z^M@G#eX` zP6pa0cI0p?6qvnDk=3u?TxCCW5PZo6t*Sri_nrFn$O7yn&hHkk;b8eg58Ez zi)49)wp{KQAFRuOH3ywKs`% zzU8_}&boC4Dl%?IhbrAt)`TRU%y1V17(5y`JRM+x9QQ{gtD3WhwppDoPUL3-u?Q*) zz-bSUm?$w5ENKGLq2YLvxQy6Re1ug=cknIN_foB{(N=v;j~tN2%c2JLNBtI=!tT5v zUr?++5HA4UeO!&R5;7Wn8Zy_x5;#8Qd{bga}#N_ z)3!ElX_y~xT%0LOi03;EfXq1RFn`8 ziv4Iy1L0u~%3iS`NhF7RFX|b?u7fu(xo-?q{A0qN%Bpkmz+{>46yVHEyf6sdX-n+! zJlxMQm-~s~h~yR(UefW$P5r~B{-vh=Wfh{XYMIY*${pB`Zr-a;fHVktO|#0q{CI#C zN7i_R!x`V%0~Fy_POrI2Ua7zq3>tIe|3?17~844R@qmm_x#vsml6=Wm*uS z^rMO3Aq4hoGLby^fjgP|vBB1qx(tahvN&vyacc7O?4|Vkg-z4VWN7s4G3H0$&3L970H++O%_f8FQL*QMkD0RmKqX!HNcBsCjlsa! z!5;K0^;`Ue2heY)gf*>>QrIQqu2M)lEjsC9D22}^cbj(L#V3W&XD$qWM_o zFgA4~T}U_jZQmTz%{}Rje%oUA>*n6{M!#*b_Oc;klyIGE%uOZPNX;b zZHqmko0I8{e%oTFbn|d}qu;jJqun=;>qbvBxW~FTr*)$zdh^}jh^Vuo!y(Gy3rH8`G9WjPB;2(-yGA; zJ?V{p+hX_Y=HB#1ziqMOy16gC(QjMqLESu%-srb2_K+b3)=#E4`fZCH(VM5!8~wJ$ z?$DdV{4uh+#qQG0?dgqv+hTWi--IDQZdnY+JSEYPxNBE>hpPQhH4bP9UcRI~J8%Hl4Bd$< zN$Otdt*_)pQI`~u5QCGlNn}z(mIWd4EK!&9xngVh2hbo`PE$z{rz&}kCf+r_*%Dp> zW|_azCeR!~qU<|7J{dn2ExTJc!9?C5$xIR^YaE|^i}>A@989^Fvq)@F30|ViHg9A2 z%GhE#q9rNuIoP^F9y}UNM_WWVN@WkhM%z+_Z9uuZF|-737l> z)Vgc(skoOQ={^{fHIk-jdle} z=0ODAmFEK3awj~k0!4(d5XN&9Nbk56g?YLp&!iphtZ~wsJ;9hMvCcDD(t*jDJd=^a z)p#at!E=^_kVXUcOcv^i;(b(JB+sOX1dJBRBA0K?&O_w7m*UBhDC@W5nUwm=2U^{m zJd>a58N*C{G?k**tAAARZ=qAS?_|Mj^gBVzvS)HZVf6b_Gm{%b_)%j%8GAYDc9(Z-P)~#`71T)*$xEcdyh-!(^L zsHRL?OOryn#Q)db+rZaVReAsCoco&Gq)nle(iS+0Knt{K(xyoo-g*iY3Z+F}RnYb} zxoy+D_9khmBBan!R1_Rh83hMs7(_<}XPl8yNAM9}he4db3Nz!Q;NYXrjHCF@XGHsa ze`}v}b8ni00{$Q8x%r%&m;JW(+H0@1_S$Rj9p{0P;cP=@i=hl+SF9;aB_-ls26yDY zSbl7naY$Uiu6~i%^vPgA4I3Ai)bs*AJ*57AEof1Hr@P4~4DzJAxq*&GJCwczl=L;i#!?k805rV|dt4Cgd>kyMg zh@!(F8P&O+1mT8-;~rD%SXP2$J)R1on^7`#uQ@F^$m5Ks17{G zt%b?W5w6$tH*(Tv6c{%nWcd7ZPU%`oVbF?=bYbWZXxa&?IrOwbOJ%Zw&8Y?`T-t$} z=P`!q2Bp>&hk;EkNd2?93pfh74(w8_Ab}JdOF4n=z&d?nfO&(3^k^IQ%d@{qXf}OC z>?}xbf9K33!m1L}I#LXXpebne=10&zU8hcpV;~Q85z9A0=Ew?C ze?tuFkzfHk_pklMcb@s^PoBK@dznJY^9wfy$+B#GVNqGYnAuy@+?rFd_Li|h2N9X@ z3Im)Ra4%DRwI2l9iO#mt9I4E0DPTvb;Js>lk(7mK_L@yNp1$cbio?|UPnhDjMcY!& z#QuYwsauhD0J7qL8E~MvI<3eR8@1gC@$U)~b;6gaF zwOq;?q$jwj1~*pQ%)Mis)s?lTB0S`2+D2r>|eWL=u%9H+bak|(~^|2*JytXNh-|-6z*BN zx$x06$5Xx;+ajJNHgVI><#UoV<>0t6AVf29B)b?|8Q4l8#Q#{%cqep%NIPH_Yo`KCle}WLC7E9eIWSDGXbF&4DWw z?@`bLpM+SQC$T3gV~`miXCx|49G)A=ijh|BaYi~#F&oeg5{SB_3k0`b`=ieF*-`YyAeszvFjERs#Z@&SM4f5 zcJ!Te6ktW)T?z1a6$)_x6WfKwJi8Q{{4Ga8Y@en#B;WO=aE#(H*Kmkno#2uL7vvAb zOJeHgtO2n>s2@y$;B<5i^TVRngX82Z>CS??57?bHi|+%4qvR(@1)Ewln)KxVP*uBpe}h6C#+6uYyqzybD5-2c7kZ~V|(NXPXGYbq^DMFwgACzwHG+sep9VOz6h*-Z&XoQj?yc8Yy9P#R?lGJ=Jybz&rmn&SKX_#Vdh zCGov2zIWR_wKM+75W+S`6oB4~caF_OMW+s6kU(vb&<>Ka7&0j`9&6a=q+Mo+91l&x zI!X1E?SD#zb5eO&G_JjRk>G4KAde^ZP@x{Es|8hoXo1^7#56@#BHQLxk!|^7`)x{Z zOU2uIgCi3<5%H87tyxS=<`h$I+zcF&o{wq7d+p9-ZbxE3kHsZI%br+>K^3EC*~r{f z0(v6?=TTZVT4^7mZxR&U#>VLG47s!!HVRM18y>Hr-Dq+XuwusV)M9zKvn_gVh+j=y zG!d!IsWVOQqVDcbaobw5LLEo+H6uyg$)1`_mRz$l+!8`WH)yuGFRCa)MX0NHK~2i6 zf=osT!ez1_x)8?(-XK(MAe@E3h_QpS%_I~Tg#fM<8s@h~&lFPxn)#^S+(Io`!8oGb zXdGA65P&%`l9CZ-K<1pHKrTSa04P#%24S-bB%>C%BU)?pA+Wfdm4Vq)Jc?ltnCK&X zLB_Y_v`9;|B?@r>a)M)1S855F8rGKV1Z&EzttI2u!Y&C;)q4l(a12FK33$y^C788T zB>)BSsqKWODgjU`%pw1`u0~h^Kb#J>@Ou!n>LWBJ^_f4l*n!qV%A_DBI(f|9g|tphOR5!u28=ZB2;>$dXeJ8BMyl+UpQyq%TP41eI8oQ;|B zrA2tbtCY$Uka(5eb(>>$iT=F=pkr;?hA37-qPJA#Je8k_k^E4eWsH6Ly@$+CTDU2i z+fjZ*yTKYVbo01@V+qDOp$H>p86c8{IPr#H@DRw52ncGr4dpssz|F)9pj6>@~D0Ekc+~qlS*jQq)1o#LE+nn zR=%u`2dQ5gyUTK6>$%h`5(Vb@^&8C>BLj5K8ORX<&S4eH7d@PjCx|csI6wM{D>gno%^_ba z+tq40jjAg#zv~cG6BE0rENF^Do;}`?+&Xqf`-v*1FT(`?j?~bdPLFSscJstKGhY@R z?s@I*b|TZ0G=G>{=7l|;c08qYCSRn1)JfL+VZJLBa+5IV4BDh}$eI2bSS@hQ@o~OY zBeq5V=3wNx!&QYf?yxMJjH)8rVp~BAM-SOBQ?R=YJ<+%6iBmePs3*;M-zTPMhSKIo z``>Mp!1E$khQyX}`B{#%7NpkL0W2dGL2bIAlroSmnS$7dj+~%PO|211N#^1|8$Akv zOKzp&19xZ-R05}(q1B@O@IjOxa!1_@Y`pQp%X{KI&rzNgWJSJYw5_yJCMEF0j;EQkq7ldejZe*}V8Tofib=&* zLK6knjr(Vq!*vt>m4Yb3l=d}Dqk?FCKnfKVcD+R_2Y6xgj&w9?FDxQ}$yD|vgrHLh zZV|TjGt@qZJen+LAH#AP`OeaE^)~9vzF26XIK#mtFcGm+fN6-VFJM=eFr93yNZc*! zb?D{j`mznJYKu(AMRHGy)cTb;gyl6AEIw@vM64-ll9>l|H@ztp7gb#>b80%sn0O3)ZTCmtIf&E;2^Wv1wkpB{mfmWaBjs0Vk}JN^q76!`VO%SMz78&4grW^8+X$H zQe!^jb1#}Vee4DCisOYDX<|(1p-er2eaB3wcvZ!ZO+N^jo(R2FaAUve)>PraDHF6N zCIU$aMntNZc|^DY)P7^llEV%I`HC%-PnZ%BJq;~pxOnhw$cdAq8!&#ydk2VLX_h03 zgKn(^QJfp6n2eyi3wN}M*Gw%$A@C|v+ZnIu6`pAv&YYAA%!W%3imgc^L;5BQ8X*VY z%Cc5=G7>wd@-rDb3(4JNahlM9AZ}^~Lt+=s zPDYn%VYB5_1p_h#*zjITfh_Y>`M9``V6_llDG|9TI~FfeAk-&q&82ho>$D6} zYr{7QLm9ddg~Cm&%6VF|j?0U&lySHL3L0Do^k|h2TY0rK(7w@^%yWrHBO=_1eVZSU z?P#v+rA-vzP63+A(P}xBqZj8WgI%1XHhdIN;zn$3VYhg0#Lo6+Vy3=Ig2ib(%vHWW?Mr8G^0Ulc&)P} zFPqsggI|~`d^%`h zDfS6s4WJzcAJ8n~0y+#HvSq{w_NYT;u&C}G-S=*I4wf3_RW&-KI7rSl+`+|lDc4;s zFJ);`vH+lJnC~ zzUkyR^E?FcepDF8!W^=mG!$8-h(aU;G?HC*OlDQaqZ>Z#=wTgAml zu3i&ptpmyvb{@(%O+g`X!N)3TK!%j~*$j0FqGQb`ExIu&;0P3p0&OX$SkVSML6QdG zv;j~pEO1E=EsD%3I5P#~5%Ryfc5J7Z8Y=+VV2YeRi%!Qs)-A&}MRREiky3$vs8tw( z<%W+?OSqOM%(L19lB$Rab0#M=iLeqv{ap^zVRW0uwyF|L5HA z4Tyk&Tk_A3o)8|QC;qX19WM|5uzn!A>ijZ~zp?M{d)mI=cY9$V+I40L(L_JI<%*hb}OJPnMebzZraBTxN$F<=)orn``@pU!%!&sxa(Nsnm&6^uw zqZl<8n)_8)3^jvtg9=2%t9S(~o%)q@~M zawbTyVpSfG=1dpNbxbheszz&j_af6cj|+Rd`1#U`J3HXt1C-UxOTgpexFP!YxBCVwLU~0-PF* zgCqFnM`i-~9yW%sZebKeNWF!K-KsGpz$&5u(?e_^h`&ZZMt6ie*96dmoc``RdyAVe zv(lcBF+;%!#H|Se;`=gZaN!Bb zD*ZhZ7G!YAg!MAGXmo=@K`w3D=?PW-HS6*kZJP z%-LO#?XtUMl7m!wD~5Pe9Hnz(2a$>>{TgvDjaMW2h!CFKpo4`Srj0A;%LFVE!2q#p zt#2BF;5!}~Ar0?qJs1Dp(9iefl8SvUEZxqmy%)(@JV$MHUoilDiCKLV4>P)*@ zoja_R+Ni0TP3^D4b6m@*-CWCm=>ISUc!&6eqS%-kaRj5ZkACto&_@(B#SD^mmC8JmUs6toqUvtizIMKo=H9;!PyeRePqYLa~jj8O<9#%*nrS406#=ePZ8TF zAXCFa^yXXcdDI(=_FcC>KcJ&5+)RI7f%)1%>_Ei2%Giu(sRb&=#P$nGID?lf+15(oBM2!!jbW*7)$P5FAUs`c`WqJOw1 z0PX1Ba2L8xn7sgOXMrg(wrrsO+F45)gLIfI0vcFxva$rSa-c}GpnFN`(hcPD^dQ94 zbU@(O^9}y#sEV;LMvXw|i}!_~0qv-x>hernngpVM;VlJKAcnA7kh@Ywk0!a_R)^a+ z9OG%C!P{NGYT%~fbwZ-LyJJD<-uqN8t4k)yX{k3E$h4##K@I@rQtyDDy#L1De^Yt? zwO;^xc4z_}0DGetDjiiWWg5$xc>sYgy?{?P?utV`_P)=+>!S=?`ibMlR`FoH@*ng;$Q2Ml_rHW)yWty8{Wfh=T)v{?WFr1(fs@LwGPMOM=UM98}cNaUIbsp%24 zb4zs&ka2YmIv2JXNrA~NAy=ma*-7ajNEnbL0cw}chDJ!v0@7~U*wx4<6e{=572=!? zR8`d3GqiQIS&C~Qn`{=9LO7*)WK3mCx5#izB0GjAOQfZPc;!UJ+Dmk^ z_FD8Z+Ke;O0h_f9nUZi4ih;VMvuo2%9x+bpWs*&UG&O3HK&+e!!OEAyNFQRmQv5QY z=I94JO48~1sjBC1^89f8{cAk`V=OW%0j`oW)SG%H~scYqD#o{885wKJd}@F+RC z!=5BPw%C(4u6u<&9mms!_M|C@bG6Cd!G$M4sc`3V;NUjxHnTSE;JEeq;c%r34p+M7 z*Int7>*tE|z#7C;a6>kR0(M2txPF5+LzY>xG7SHwkR`Zc7`4Tfkn&Bs;qxHHfgoTU z2tvxaV*ZpD-FHW<>&A*E@R*hfc!Z;{#^A~IBW(isYBT^-G-+oknj%b5oL*r;e&}mP zK`O~_H9M(pC#@u!X>PL60{_Ow;O0G&ZE^7U7Hc z-f{e)V$8A_cii$9Z#|kQ8jZ}96D`Ot>QwT7U;oi>%~M3?c4jD%W2vahHLrg^JS6(B zTTz-8MYsM*GUhOhp=iVy6WqcUcT#{#j*eq%j2W^;BK|~j)X*3sNN4y;Z zM3L)L-k=>gCV-+qS=Tm=Nnt^B(<7b@3-J{bfuVaa^MRk2jQWCxDBK2ZD{F_k;TE)o z+IAN5R0{joSji5{wUJB}u>gjA9Hs~HF+6sKGQx`sO&f~ze^3TrZ2(bIl&lK&xn63g zjSUenVv+D$P0h`a2Gv0kCCjQJw#m>|xIU+oHcpPjy8-3q@ZI!QC?`A+8=xI)+FK{5$#5ErL=E2(O^Z1$jdZI* z5|MYFhWIiUGYGCz`_xG%vSD^@ZWayI2@OQm%~~`@>Qn>t1D8~P)dV;~w0CJ3G z2r(<%;^YvF&H%1+2*tWkQ+J^hYby|1n0(P^dE*uD;c=Uxgr^YKtZ`hqoBUI9w&)h-Tw_jWKOq(WCjhzGru&Rn#9uPCC zbM*jSFU`bM0iG%rTuUK2eb>8+kU9;986OE5jkrZw?%y6BK3vy$Xe%4`abdoKOzd7@89hmJsYRFJDV@{?daXehRw9k} zlPbisiptNS%bO8H_H_}1Aj6QF9;zl^^ndUp>e5kw7`HUfk>qVe7iuAzq&8dbhuknH zken`<;Zkm{F{-T`g{SqvS zm}tO#!=qPMehu)i-i7@?E)?d3IM8qq-F&HMV`j1Q zT*UA-{%8YjJ|eWDKu!|UOKcLC>Z|f7_0fYo@sD9E<|O1OjE*@GCxD+tDCcc<-w9&3 zun{YT&63g+n^h6;CQVoK98r#W8hU@(Y%;rcf>CFaIYeOMcRXWCCk|&y38G{mY=Pg& zd?gKvfh-8a6h<}as=K2D`<~5lWU0IFjP^~Y2iZaom5Jt*sa%OvH>Kmi5UuZ!+HDW) zlSuPYqtq_&?;AxxGVy5iS>mu>gaJ}KsrY}X`T&kQ0>*iv0G%NQexTV;J!hjB_P~RZ zLv5D)d2oglu|=3>5E7XnO)a&d#?(o56ctQosq_o4x|4X-YL+@{5>5R}ous-tp)|YN z+$61$p8A!Xr6DbUyN7fj6a4c|#4CiF%sVg@traNSq zm}cG4@xx^l@c@rH2(=C_CF_nNxSA~#9~hl6Jx9Hb<-_uvsm}~uX6|t0i0@rmLnfO` zL*sI>Fg?a6AZBBt#Xif4C?GFoCq)m0AlJEvS*h@0LC`Fz!b6&|o7KWr&#(z&ggR;y z=+;uOZ0&Y0OM;!?LwJgC^kYu8(>!=wOOg%D%A{4r9H)j5tT}j;E23wjpX(VVHsJYJ zdS0LYd{F7e?X=f22cW_vC21l)F4R0%0|*jn(CIKocm?PnFtvILOaoQ6$yq5s z<$*H?Yuya6nhA53+ywjpNGlNG9Z-Q3s4F!9YFKHQn+9|nrytpjXE46I+IiwRfG=oB zB`XhPlUM=7cz^UGp)38yj$cpl#A0$N;U~u}X*|Tq_Do2cG zj*IPA>etz5F&rMw6K6Y*%*wSPo-|0JY9cuQiDS1pI+(j91 zO&Z*9W@|IrA@W@eivO7S%Yg*J8p2Mp|sbyQ=g1liAFDxKZdF-YSmGSGXM`n zl}>eNxPvt@uHIwAlSeW^ zj_uh2L;{cg$WpP&L;}NHbPt>2V~(Ish*zVe5{3YQX)+0FaFhC|wX!ubL5~?^7rpRW z&eWo{A!R%~JfVpcEg}NSH^nTO7i%O$(_+NLVva_IaAc47IEf@R4gg3mVxbtNY|=1S zSnG3UAY*SqRr^QZ`PgmFgGCLIf)j9HjU~nuzrHa`Wio5M=6VjUNggY~n;IK{G%a2W z&1Rz~WL1&BL^f)~(?_Hfr>;t3W;B;10fqQai!Oc7^E54%i)Q2gMOy{u(^B5XGXK|X zDmOMlMLa%@{aT@IlrPbkj&5R}3S6WRXDxueiJlw7Wm8sit_Pn7g=}cdtW7!0=;R&T z*mp&DTzgJqoh#>#Yd1EU^(q$~>q=~pSuMu`?P&NMqEA6)(b86!b-)A;*%u5y78Kj_ zrar)(C9EB{64J%%Xgh7ok}ee11~G7cr3J-@FzHJZE8q^OSw_|iac1dh_Jrq5#(mSI zhvQlSz6XJ5I3{o3c9NL4Zxb&HC07mNyuzQR<5O1Cw(b@q4-G7GwswS@XgdZZ zy-{^8rfQrrwIm>ERCfnk)9DK_MhDy(-GbLNPucYg=^Kn}IAjh-&78HOonBNMNkpb$ zlZcFw79)v-$fjp!F%vL|Rzq7Hd{?0@7D;a`H_CXp2CpCO8Jw9SE_tJw^-aXZJddW3 zX;xO`&J1|QVnAlk5hF!s)-*yUp)^~9k%8zovh_8joUfs@d`xKur=X+{ShS3hlRCrm z03~jbVmU>M(Ke@ax_NX*KhCe3zKY+Fn%*}wIuc>sE$wSNbZ8RZnwk*N(-irzWZRL? z0|B!setc+b8r62O4Ldx|Y+OQzan3xjsI({*9D;@cNX;Tlw&WB~69Ddf!pO}6)Ec%` z%>l$_me83^-!Nn*PZ7v#4`|9J%$ZXtyhLXFfAh5NIUo=4ASiG!<2$*UL0lzj_F?9n zrDvuhXHl=tf&N-!d{_Gg$dyVF2uib}BOK&Y8+i!=5H4<^V%jb7T?8W07NW1={};*l zSmoqoJyn80&bEcm1eJ|TJcbf8`q{FVfVoekVn5rYo12`sQB1Q(V^|+_D>9$U$5xGn zgDn_1Xh|l0@X%hi=832w)AYig!^8H4mqk|)HlE+d77JiH@p*HL65(Z1#=P=0F>4eBtA)Qet8001cegrvYR#f|9C5`NolB&!~Fn zGA1g~j$H=-N!mziLRA{moCN^rRH3p+0k=HsylT%n*>w2gJnK+VoR1l67pImo!p7xU zhXt^*l#wSY$5Wi1)E;jP(7WWZ{MK#XzNRFk;6%wx1c;Wpp4 zKBDi@ZsH)GG)DZGpbd5_Rlc^15Mii$5Q9?+AspB@b}`JXGEUOWo_tswjB>*oG)FTs z5xr`}4s2emQJUQdUZx^p*JeDcEk*|gakBKVpS^}CDdx-%9GHE53#==7%wE%?`67R9 zctcD6nifqmOJ=s_AQ~(93r+f1FU(pmw6^bB2-cwJa$JHS*eaw z)TBD%-=rY|*PH*^FTfxtAM^@$E-Vg?mC6&PiNV3);!tU9@6gtf!J+=)@lv^5>g^xx zFAa~6j|`NCH~07Tjtq~Ni@oDRW823@OT&Goa_Oqk{&Hz-WVBQ+j*pZ__6(QGF7`yZ zzjW1jX?U!EWVnC0cW|PwwAsb(8yhc{$2S+ZQN&>XQ2%)E$i(n?adfmivKwIfO2Z>V z#UXv$IyllhK#9X+6l(eOm9E-R>fgR&e7t|C)VrhDFR;c8lx-8kePhMGKEN59UK7h> zTdBNxc-y#Q>+TAq3cIOY{IaF-JWss{2N zj`L2^e2w=Mc~)AL=coBr$32O20X4p0Z2KXaaVp;7L6esu-F)s!-yGjxK2a*~B?FMR z+3Gwty{+9>h0_F^`-iuUOe2oub5a$HGdwXA=ckkj_QonSlROyUn@6@i3_QZ~L-GBS zF?=84-pKd6xT~Gs9lzhiUGc}ctDT3rt6eYQu6W;y+xa!TtDXC~tNsZdrhiZHIZ3?P zRq>MdL&Z|M2$E+UcUwu8Gpvcu(kg-zUxj;+#oqRP{fvDedZc1b5*>G%ft- zuKMen=(HG~)ju{mSlk=-4~-6%hTs`Rb=a_68m9x5`oiL{gD0FAhS*EJg{N6Te+gWv#)boUw7}e)gA3yyNX*^Z|&P!EcTYVdY2FOZ!H(g zdzV85BfZPV%3R}R>eE^t^SsvqJlX#H%lJgeO>@p#%SUc}8YCU0HF^@%gM z3pYL7g{w{CjhnV5Wf`d^bUPL$+^>eM_74MIe_wbhWYE5%J6yatTo%5U{N|9~hq$X9 zlB7w-NuJ5~u_Rzq%%m$HD*b5V#Ly*T@s$wc$Y;jfem$Ku$zzJK8Xbg`FBsIPgmck) z6PIWtFHTNi~t)R?=Oy*5Np-Jd><#45uIrE z#b8t|Rb8sFXO&me9xNR>Nc9o_$rR7Y;|#7Yj~>aHg@X-aVTq#`_0F6%{^6 z^H-j0L5_*f>Jy8bLp5g&k=5oBD|Mo?2>q_M4U;BLh@dvKmM0v9uUh%qyJy7|@m*Fb zOLeWy?qEh))5#9zPA{ezY8oGEcb)Nf>n&URhY^@PJ!|cL`ozi=FR9E8;WG(uZRWX! z>qYQZ??;7NeppR7VoY?p0pxuzYQxCT*5dHMMWxZfy_<$j5|2%oC@{HS9NbgfJ9hEJ zR=PreAAMtcacr{$9FIs2#Z|9BT2y6FB_b^ z%{Mwdig<|z6DDD8gb5Er{Srmn!)^Vg!9KFwz<0^SE4YiN_f3or_V>a#k{Em&Bup|g ziSsg^^-E+x+KV1~FC(?vXD3ZRwj80S@zFArWmAKa5L1M_hsg1Ga=@|NB(8Ep`%w=I z$45fCQ@E#pd`CEf`m$|sWRK$NrxH)#TFkYCtNQziyr0B%^0eNIV< zV{Y_TaINI(;9AAi$<@VG9k-kJwKS-;xRm^@#lhk*qCM{X@vAAPtBIRX9m8CC&%YR| zdr5o5!r8SHY(f+Gs_QB(a{0tqc{y5aad3HWvAlg`dAYQ`A8mH8wdZ!4XJTt>@5s=y z_EK+e=Ze*<`?jtw^>(jVxjf;G%MjAb+FRRN+bjt*IZ_@U69wE13|COb0GBkLZD;GH!= z1OM@n%`rh;>{tmg%B8;2U}-zOk`cvb)3{~Jz)Ugqf^8v=GZh|xVB}K)LU7S^edXew z&2fT}GJ4IPBH5M3H&+5-99onFs7-c9@asd`P)W4l^M`^gU zXLIieb`|4r{Zpz+{Lw5TgT<`?>huEI^$afk67B*44l+F3g7`$f^z=*&?~Wg;AW4b*(0IrF-@Mrd zV=tmhN(->+FLU*e$EKi1{qbdoH+ioBUz@lz0@Clf_;C{dQsT`Je{5o_S>bA#IC0l< zX#^qd|4gp4xGMT-F&SZ6oj2DEs^TEi=t^VW-yG&fuE))F_m{FmpAAe2uUW_QIb1J- z*LbG_WA&(CIM6Sl?-$dkaM+PD_JC!hgA-$}Wjm~8u^WidK1118Qr3E|d91AcAX56B znW~mOWJ^`2)Yv$AKaXdPrGGC_j~2&w0QJX?a8OI4YwTV9E?AOxNp-;@n35I~rZ;fC zh!%S(iOIWZUTy17Hy9^uR93Cu%5Y5GF5uEHp`auW<0O1vbELB&o*A1*l7T|}icY0* zgXdjnxww2@&htfF7jyky+SV#q8e2XD6Ym$#LFxl%iTkU#IQ!t{`=HJzv%d{>CMl&u zNP8U}m}03(SlM!&Dn%tK?(d~`aeo_~U)|q;tLj~po4hBzC>HUiQdY!vB6(3UE8m!N zFa>~it4M9>pi`g6wkoKPFHBBITwA6U(om+ze8D-y+{Vz@cD26vu&ztE#LF+^I#@^{ zs<7hTe3TnqoLL-}_(E9-6OPTWq&Ob-kA+3%CW?J~HT+=GrO&&MG}6Z%N5@W>MCctX zVv_88D$euL;o`*jj*)WzYfF9U1#YCGF#TT1mB1-VQJagWy_zfO^{?T1GuIZbKLktf z8>vGz&o2U(eo2qr%CmG7)vK55_d=69N`u%_P2Wy0Y%=_)E#@$<_VMX*0P?66*3~IA z8c5#PSPr%C)!~xr8Y?F8^{()d;laJ(*up2?7(Uv@u+QF+qrgd^>Zbyx0B}qu0gJZ z8i#m>y?P^De+bmL@aS4<90e}@5^B`D>ZxlNmpG@mWo%hO_bv4!G-SqfPg6#`Go@F? zpBW#xdCsJ_!(#y z`}%k~f=xq4QlFGD*1vtY$lMW@5{q&_;hG^x!gmzMb|k4EC+u*-&S6AUCS!w}_n+}y z8qC__c<+w*Ys0Z_1~0B7qZ^BD&B=0MEug*W>UHVQEeirYGr@bFm z^?nZF;`B-USMaX>lDu!@y}CYE@veT8gkQ&db@^}R{fMgYxAET2`}(2L5$vB^2TMKS z5GGno2(V0P_~nT(TJW8*DNravXH z+)i25b-#!AYFIzYdv#s^iT7ixV10br`@c?0|Kzmrr>2Gf`?T=?oEHAmY2m+^7XIwC z@B`DrGeIr9dETq*$Bwo&^_dfgzchTK*jF5de~c|J;c=n4ZR^cXzs%Va+rXo6`$Ee? zr9Nod{&nP+NcO9Fem&PUTrW_vdsiM;D~-+kG+Z2X?7t^uVjmlSA8rWf(l`)7tf9CT zV$RDS-()uK-+F2QI8P;w`o;y^C7-Hi2a>R>2vZ%W&ko$ecgfX1=MEXnI6Lq$!jyj! z=USfiQ+&0>_X*dJ(P%vKOlpdE4`Jd1XZ7#y?<<8{_lB><6xS0roZx1W6PByGAICTG zQN+>+9wt@3BARv#6~*f&j%MmNafj^UxF`WO=K-ciLW|r-xNwogn;Jb%!Fyv|UOe7$ zq9eTYl6Bp{>DU9=Irk0~eh=|AHxUclP|g|Q7$X{*H1;PsEyV0NQ5-z&apKAj_A~C1 zuSuG?9vhh`_mMh%T$NGiLfMP_4haPJ@Moz(O53m&BD7^9q%o~ z(=e3AQ8Ya6wqt)*w2dR<>tq~dBCRjR%%$Voy5q;Q!0W}JH9=~1o;Q-GLvbA0A#_Ty zi4o?MBT6NHb$aO@)i_g(>hQM_e({dMi+7YS9NBZh#30jmQD0w7Ax=P;PWj~}&Mx^? z?pe}2#$BU|1YhKfE24S3m?fsVj5iR^f`A2o##L1dNI87R$#9lR|{|YCA{Vf;Q1l0&v1R3 z>yupfbG?*0WQnN{(;<^iE0zEZgC{wCvV&*!ZSnQ(sDmSiR2yS`Q9Pn5y>wCW(@Qp- za@IvX;bofGaLS6zE?Y~X`5{O-x!4(hdwOJxVU9`Pn0i8CfsKrXdq*aC8V)DMN>fqG zr6B+!CJuVVzSeNV2>Pq6$a{Ojb4oa^^6Cn3qoic_ zvLeVvU>c;!IZw_#ZtT_?UL+4&qV8FmR8v`&OK5X&Izh9wgj&}x7yzXz?O1Dg$w-JN z?>H`gme^`IULIl0S3=OH^S3nq-GNRf3 z*6?KdE5huB)9@kYV{wE`=40EIIsIu6x|1$<3s)io-pRAZFz@ENmCI?!<0BYtL_0zW zyY@6X$7s*tETH1qvWNw+|Hp8mrJw$n@Q+LjzBqbC+X7 zsml9|Y4C5E_P#lOe<}I?pHP0n_us?wd%0c=-@lnURr7t(r+$ELI;P5#P9vJVjjJU2 zT6W&U_$8U|o#;gx1;O^K8t>0M-hRnD+!!1Oac>q`&%3kD%@t@IEe*K}o|)|r#Hmh` z@uEk+gQe`B>Z(#w$y%dDl43QfrSYEWn6J$A=+FjFVzBV8Zg)P`Nt!O=OM{Y55h0wxlf~OqPL}tYf$HJ>gn=rt+W3I`({~Fy`JxLX2uC zPa}!H;4TeLWk`EcIq&0o$%$kL0bSmUN$eurZIET7IJ`?FN%XP;=7g0_vws+PUPbw8 zSHTs>#>WH~8%bnEP7)J-3F(gJo}`nPh0-OmVVXm&bKtM|yIySm{LTa8Q0|c?>rlQc ziBMY23t+}%)7<_}|5IQ|+U^dX@8tS3u1kmGNdq|HE;$O@0Inxo+TmyrK*8223S zM{!qw5NErDXW>wsZcF?eb3#`vVS{{Eopx~-9mfDxzTe1q<$D)*{nk30Ce*hLg2Jbt z@3L;aaJpyY-d9?-a||_49#WWRJ6hYFI|_E;p^-kBgQNR?0BigDqAJ!lGn3w?#bGR? z0j$>m)?-{|0D8gBYljA}ue6+{A!EJ8QD;Hk(b?o}rM%<0WNb{{SMq!!mwwL+|3=WT zqjXgmU3}*H^;jCQ;Is0ij9SG2cq&f`xZfArsaK43TnDj&he-+32r+KARAO$*(4s2JM1I#ajIylMjtDPSo1MJB0D{~aohwpJ2q zNU&!I^9Ey@GYmC%FsOk_1zqH=wR)DPW!Gv31WH6lMZ2DWnah0+t$s(pID*C#8e^zf zD@Ma7zDhyV5X;7ReNx&P-7V{G-L!S5h6^&Xh!GPD+m2h=lSsh{lvf6ZPJ1)1-I@f+ zsF6Hlw%m$VpS(JHLKMR%(DPc)j!i{wY*4A7ATi!fxdyMRXw7gwbdV)snpHC%GmnQd*~nS94t zOy8dPyJinqi(g%vyM|LW*>}6_*FJyt%B@GaZCa<+W=xmZ|7uY;LvY-pGo&02M~nhy z%yke#YBgy$GB`z(t_zF6;%x7xFC?nN`+2^H>jPEaKFD)I7S=J`O4pF4+qO+Z{8rBk zPjRBHgS-0YY22j*pPdjaD_RpewrP!G*tu+LzlO#`MY-UVTfJA1=H#lfWX%kDzme}6 z{7uh0FnrMR`Xue76o z=fL35=&o{YyiIY$%ZX|6wrzJYhewDzHa@X?&sBS0J5|idOIw$}xExxSPv!PPa(W?Y zrb=A4`NgF_dASzRKj+W$gH$?`&E@Or3k?SjG|rmcH0Q9x=N>W7r8;u{Q41C>I{KJn zLu<(caZ?^x24WP$wdjEZv}w;Nrye+P+UaMwg#Z2i8|02Wsmf__1#0j!g&pwXB2;51 zp>szx|NSs9sxx zf&Y!raQcA*za#yhap3&iEbcf8YHd%<7)h-&v=Eq=04ZxCMoT2NfB}-?;q#6XWF|) zfJyi-d6uC~zpDIa9=#GLN%t(zn)<1J*ECY{{%f9PG^>7Zu6jQlH4p`{60Ub?h)Mjp z)7}fy;_JOSek1QPJS6EGriIUr-=|z^rgY4&o$a*LByHkel)Rr3r>)EmnU&(XT_2j( zIoF39p|A~H4O|bO+T`8HwSjAk26||suO^-h7)wKp9X!i;cO`d zo5XwJ>Z`LJXw6&u$}Tf%R7Tg#SL0KNDn81LMXz@cWlEd62X2$gAW0J@226zbTHc2e=;O`V3bB+?2bGmZR-1 z^#UB`l_FTe2oTSk692e!;{~qV)hDvRDI-=EIBb8+c7f18d?U-r=BK86E49tu%$S@C1j%!+F8S`tL)>y zk+&>F4|6|~`@P&nkN0s`S$_h-YUH(!xmI~Pmj_rSiQ1sW&6q%D_5@USh;Ft=gO{?-78mjv~{$1tms(T(b2K0qqC!{qq}4Esj|sTHV>!+1|OLb7f~o=c>-m&aTex&edIQUF}^f*omj3YgJcg zS65ee*Xr)J?)L5#-7C90x>t2~c6W7mcduRzh^r}nHCeAF*=jx&$KcH71+@*^q2{Ac z>cuD+o*uR{gOb$yyL3wNm1>-Qo%e5WJ;pU7>SEjJgBu*~-T%%I*+sYIogz)v53=!Xx`nj&?l$ngi9}Qba*=VmP#Hh zDvo*4DBnfxNw`bza-W(xGD;_rO(S_XlSc60&0Rc7t`FKl37gTx__k%v8Lnc#blueD zh*h36Cs`WImH0Qf1c|LvfXA+M*127(XGo(d(Ze>ny7>Q>e_r&9+ff!*2G99RP5Y(w z^CF!VT6o}yD_UDyUu|Z#xFz8pHJnDM*urGqCg=Kp7I)$NX?O-C>1|~ya9C1CO}Ru5 zCpu%C8c>FC(vFj67IClRE*|s-?rQ5Kk14ygjLMF~vGK+!p*XlKcVqIv43{|H#uE*q z1fKiJ>jd)p7I*0Z|BJiyr6liz`UM<1nNyg1q3ahvC%z^i4~iSx&6kg|Pu94+T_uf~ zO(5Oz+-2#Lj7;7|8=YL9pUPyi*&vtA=j!IvFDe|>u%NMNR^#k+Q|hq84zHW%ADN!- zAC+2=Tj(zej-3~#PEIW=wEAtS_F#qo{@??_2h)F<|F7UznO_ILNgb&B@Kt-?^433V zyZjYzx$)*j|8v&t^EdqJ+1BM}T>0wF-`W4xx4r$3Kk(5{eDZUj|I(Mg_0)F{ce>(6`j{y>sNbjw0{{Tro_oZ(?_1&i%=bXL1uQa*;uRitZ2fqH;(?7ockKS_E z-JgEob6YwzOsJfrpsUXn$2%|+f5(++mAo+na_XWu{raOyyD7V z{PH&kCWm%?>)W%A9UfV9^yb%J^O1Y6{p6?S9eK>L>(1G@=@qZK@-^37`!}Ea+SkAN z^pAgD9=mCL;$0`SE`R^MAAjHrUwQ1??^*NiTib3r_Mg7?$bpTUUUfw-Kf7u1@*n4VZH{6n0o;f8K%$i>}`RSJNhRJU%Xvj<+$V`5_;U~AJy6W~_Id}5@ z{Nz7m>gS)5s?T=k*X0|sUrxw1 zWHOU?H|_g*&JRz@68hHk^4UAp&`fJ~O*9Y&t z@4-hua_{iSJ6_ZBx=bp)EVV7=FK?Z^?&wte>_zG0>W;~roH;8!>!isKW{*oBmtK?O~ z@s@My#!sF6Mg3TQ^oVtn@0r_hdEHTyZ`^lI>W|jWK4Slc$4)+Z^5oZ-r52=veQS=L z)06S{KQZ~s6E~#m)4}y~&f0L=!&ZxEbIvO&03o7Yq+d_@*~|xH!e-rLHF6o z_gw!tm2OOpH@q@OFPhzu?xr$J@+}+pUD9wwDwE399i1v!m#J`gG{MJ+sAo_2GAThaDLn+YoL!_D4(aIQgWuaOue1Pb>}Y*|O}I zUu|jieiL@S@4%MMU;9sX`t{9S$2C6L^_R1XtC!Eeb9LLIC(rrmG0hvE-udLFO(V@0 z-u|gOFZ8}re0k}scfQ>Fm*$JSC!V~x?aAV$ANkImmp%IZCoc=V;me=)4_wX;fjMs( z%1OYVe{P}eh$g=TV+#U5{WAZUg;x}M>gxRYX}=CWkU2SZYW}48e%M8VbRGtls}GL$ zdz3t#CrW*=zz>4eFpP8n!}N~{Qhq`286x;|gL$wMVv~2?&!y^vWBgM{+d#@CWKXU% zo}bQfNR4t8NE90AeNnI)_>xkN_RsaxOw0Iw-hVlV=rrWF`axYGdtR`R{CvM_mQRV9 zf`43{zb)-&0V+5uNT-_8oc9rAeXKqyFLiWq41a6b-Z<|Eg*qQL?oR|Q{_a#dsPnU_ ze+3CZn^TTKK3gC7ZO68!+j!6TOE|0|q>_HBn?Mq#dh$VVYszo*b1EGJgJab{FrcqlfvMIUvag2Y2|X{1Jv#-=F8t%B3=&&8s3u3Zt}>V)?;;09Tg# z!r;=pzU&k(2rZ>((2Q5-2S2CPp$7kEN=y5k=ajWp$_A-cknM4lt?yqn4?xJ}wOI-P z-%y&0@~H>1%=6Dkzg*9)!I2(yPG|D@Aa_jqmXz0(UXk}_`SUXVY;u}oxn!VN|FpE1 h8_IcGCZBd&X-OxOZkJq((K+k6o?=}5X)eaE{|CXT9~1xp diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 169a20877..6a4a41d75 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -32,8 +32,8 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Mint { user } => mint(deps, env, info, user), - ExecuteMsg::ProposeNewOwner { new_owner } => propose_new_owner(deps, info, new_owner), + ExecuteMsg::Mint { user } => mint(deps, env, info, &user), + ExecuteMsg::ProposeNewOwner { new_owner } => propose_new_owner(deps, info, &new_owner), ExecuteMsg::AcceptOwnership {} => accept_ownership(deps, info), _ => Parent::default().execute(deps, env, info, msg.try_into()?), } diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index 9d3500c95..f615e6a26 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -8,13 +8,13 @@ pub fn mint( deps: DepsMut, env: Env, info: MessageInfo, - user: String, + user: &str, ) -> Result { let parent = Parent::default(); let num_tokens = parent.token_count(deps.storage)?; let mint_msg_override = MintMsg { token_id: (num_tokens + 1).to_string(), - owner: user, + owner: user.to_string(), token_uri: None, extension: Empty {}, }; @@ -24,9 +24,9 @@ pub fn mint( pub fn propose_new_owner( deps: DepsMut, info: MessageInfo, - new_owner: String, + new_owner: &str, ) -> Result { - let proposed_owner_addr = deps.api.addr_validate(new_owner.as_str())?; + let proposed_owner_addr = deps.api.addr_validate(new_owner)?; let current_owner = Parent::default().minter.load(deps.storage)?; if info.sender != current_owner { diff --git a/contracts/account-nft/src/msg/query.rs b/contracts/account-nft/src/msg/query.rs index 294c2951f..004110a37 100644 --- a/contracts/account-nft/src/msg/query.rs +++ b/contracts/account-nft/src/msg/query.rs @@ -153,8 +153,7 @@ impl TryInto for QueryMsg { QueryMsg::Minter {} => Ok(ParentQueryMsg::Minter {}), _ => Err(StdError::generic_err( "Attempting to convert to a non-cw721 compatible message", - ) - .into()), + )), } } } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 46f75c3b8..2ff51a58e 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -40,7 +40,7 @@ pub fn execute( } ExecuteMsg::Callback(callback) => execute_callback(deps, info, env, callback), ExecuteMsg::UpdateCreditAccount { token_id, actions } => { - dispatch_actions(deps, env, info, token_id, actions) + dispatch_actions(deps, env, info, &token_id, &actions) } ExecuteMsg::Receive(msg) => receive_cw20(deps, info, msg), } @@ -56,6 +56,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::AllowedAssets { start_after, limit } => { to_binary(&query_allowed_assets(deps, start_after, limit)?) } - QueryMsg::Position { token_id } => to_binary(&query_position(deps, token_id)?), + QueryMsg::Position { token_id } => to_binary(&query_position(deps, &token_id)?), } } diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index 95957be48..8f5687287 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -15,8 +15,8 @@ pub fn native_deposit( storage: &mut dyn Storage, api: &dyn Api, response: Response, - nft_token_id: &String, - asset_unchecked: AssetUnchecked, + nft_token_id: &str, + asset_unchecked: &AssetUnchecked, received_coins: &mut AssetList, ) -> Result { let asset = asset_unchecked.check(api, None)?; @@ -37,7 +37,7 @@ pub fn native_deposit( } // increase the user asset amount - increment_position(storage, &nft_token_id, &asset.info, &asset.amount)?; + increment_position(storage, nft_token_id, &asset.info, &asset.amount)?; Ok(response .add_attribute("deposit_received", asset.to_string()) @@ -70,7 +70,7 @@ pub fn receive_cw20( match from_binary(&cw20_msg.msg)? { ReceiveMsg::Deposit { token_id } => { let sender = deps.api.addr_validate(&cw20_msg.sender)?; - assert_is_token_owner(&deps, &sender, &token_id.clone().into())?; + assert_is_token_owner(&deps, &sender, &token_id)?; let asset = AssetInfoUnchecked::cw20(&info.sender).check(deps.api, None)?; assert_asset_is_whitelisted(deps.storage, &asset)?; increment_position(deps.storage, &token_id, &asset, &cw20_msg.amount)?; @@ -85,7 +85,7 @@ fn assert_asset_is_whitelisted( storage: &mut dyn Storage, asset: &AssetInfo, ) -> Result<(), ContractError> { - let is_whitelisted = ALLOWED_ASSETS.has(storage, asset.clone().into()); + let is_whitelisted = ALLOWED_ASSETS.has(storage, asset.into()); if !is_whitelisted { return Err(NotWhitelisted(asset.to_string())); } @@ -94,17 +94,14 @@ fn assert_asset_is_whitelisted( fn increment_position( storage: &mut dyn Storage, - token_id: &String, + token_id: &str, asset: &AssetInfo, amount: &Uint128, ) -> StdResult<()> { - let position = ASSETS - .load(storage, (token_id.clone(), asset.clone().into())) - .unwrap_or(Uint128::zero()); - ASSETS.save( + ASSETS.update( storage, - (token_id.clone(), asset.clone().into()), - &position.add(amount), + (token_id, asset.into()), + |value_opt| -> StdResult<_> { Ok(value_opt.unwrap_or(Uint128::zero()).add(amount)) }, )?; Ok(()) } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 21d279c46..a23279f8c 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -49,7 +49,7 @@ pub fn update_config( let mut response = Response::new(); if let Some(addr_str) = new_account_nft { - let validated = deps.api.addr_validate(addr_str.as_str())?; + let validated = deps.api.addr_validate(&addr_str)?; ACCOUNT_NFT.save(deps.storage, &validated)?; // Accept ownership. NFT contract owner must have proposed Rover as a new owner first. @@ -65,7 +65,7 @@ pub fn update_config( } if let Some(addr_str) = new_owner { - let validated = deps.api.addr_validate(addr_str.as_str())?; + let validated = deps.api.addr_validate(&addr_str)?; OWNER.save(deps.storage, &validated)?; response = response.add_attribute("action", "rover/credit_manager/update_config/owner"); } @@ -77,10 +77,10 @@ pub fn dispatch_actions( deps: DepsMut, env: Env, info: MessageInfo, - token_id: String, - actions: Vec, + token_id: &str, + actions: &[Action], ) -> Result { - assert_is_token_owner(&deps, &info.sender, &token_id)?; + assert_is_token_owner(&deps, &info.sender, token_id)?; let mut response = Response::new(); let mut callbacks: Vec = vec![]; @@ -93,7 +93,7 @@ pub fn dispatch_actions( deps.storage, deps.api, response, - &token_id, + token_id, asset, &mut received_coins, )?; @@ -104,7 +104,7 @@ pub fn dispatch_actions( // after all deposits have been handled, we assert that the `received_natives` list is empty // this way, we ensure that the user does not send any extra fund which will get lost in the contract - if received_coins.len() > 0 { + if !received_coins.is_empty() { return Err(ExtraFundsReceived(received_coins)); } @@ -135,21 +135,21 @@ pub fn execute_callback( pub fn assert_is_token_owner( deps: &DepsMut, user: &Addr, - token_id: &String, + token_id: &str, ) -> Result<(), ContractError> { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; let owner_res: OwnerOfResponse = deps.querier.query_wasm_smart( contract_addr, &QueryMsg::OwnerOf { - token_id: token_id.clone(), + token_id: token_id.to_string(), include_expired: None, }, )?; - if user.as_str() != owner_res.owner { + if user != &owner_res.owner { return Err(NotTokenOwner { user: user.to_string(), - token_id: token_id.clone(), + token_id: token_id.to_string(), }); } diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 20f81d1a2..566b85a98 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -19,20 +19,20 @@ pub fn query_config(deps: Deps) -> StdResult { }) } -pub fn query_position(deps: Deps, token_id: String) -> StdResult { +pub fn query_position(deps: Deps, token_id: &str) -> StdResult { let res: StdResult> = ASSETS - .prefix(token_id.clone()) + .prefix(token_id) .range(deps.storage, None, None, Order::Ascending) .into_iter() .map(|res| { let (asset_info_key, amount) = res?; - let info_unchecked = AssetInfoUnchecked::try_from(asset_info_key.clone())?; + let info_unchecked = AssetInfoUnchecked::try_from(asset_info_key)?; Ok(AssetUnchecked::new(info_unchecked, amount.u128())) }) .collect(); Ok(PositionResponse { - token_id, + token_id: token_id.to_string(), assets: res?, }) } diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index b9307734a..86ab1c112 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -9,5 +9,5 @@ pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); /* Positions */ -type NftTokenId = String; +type NftTokenId<'a> = &'a str; pub const ASSETS: Map<(NftTokenId, AssetInfoKey), Uint128> = Map::new("assets"); diff --git a/contracts/credit-manager/tests/deposit_cw20_test.rs b/contracts/credit-manager/tests/deposit_cw20_test.rs index d7e949525..5a41942c4 100644 --- a/contracts/credit-manager/tests/deposit_cw20_test.rs +++ b/contracts/credit-manager/tests/deposit_cw20_test.rs @@ -23,9 +23,9 @@ fn test_only_token_owner_can_deposit() { let cw20_contract = deploy_mock_cw20( &mut app, - String::from("jakecoin"), + "jakecoin", vec![Cw20Coin { - address: another_user.clone().into(), + address: another_user.to_string(), amount: Uint128::from(500u128), }], ); @@ -43,7 +43,7 @@ fn test_only_token_owner_can_deposit() { another_user.clone(), cw20_contract.clone(), &Cw20ExecuteMsg::Send { - contract: manager_contract.clone().into(), + contract: manager_contract.to_string(), amount, msg: to_binary(&ReceiveMsg::Deposit { token_id: token_id.clone(), @@ -71,18 +71,18 @@ fn test_can_only_deposit_allowed_assets() { let user = Addr::unchecked("user"); let cw20_contract_a = deploy_mock_cw20( &mut app, - String::from("jakecoin"), + "jakecoin", vec![Cw20Coin { - address: user.clone().into(), + address: user.to_string(), amount: Uint128::from(500u128), }], ); let cw20_contract_b = deploy_mock_cw20( &mut app, - String::from("sparkycoin"), + "sparkycoin", vec![Cw20Coin { - address: user.clone().into(), + address: user.to_string(), amount: Uint128::from(500u128), }], ); @@ -100,7 +100,7 @@ fn test_can_only_deposit_allowed_assets() { user.clone(), cw20_contract_a.clone(), &Cw20ExecuteMsg::Send { - contract: contract_addr.clone().into(), + contract: contract_addr.to_string(), amount, msg: to_binary(&ReceiveMsg::Deposit { token_id: token_id.clone(), @@ -125,9 +125,9 @@ fn test_cw20_deposit_success() { let user = Addr::unchecked("user"); let cw20_contract = deploy_mock_cw20( &mut app, - String::from("jakecoin"), + "jakecoin", vec![Cw20Coin { - address: user.clone().into(), + address: user.to_string(), amount: Uint128::from(500u128), }], ); @@ -146,7 +146,7 @@ fn test_cw20_deposit_success() { user.clone(), cw20_contract.clone(), &Cw20ExecuteMsg::Send { - contract: contract_addr.clone().into(), + contract: contract_addr.to_string(), amount, msg: to_binary(&ReceiveMsg::Deposit { token_id: token_id.clone(), diff --git a/contracts/credit-manager/tests/deposit_native_test.rs b/contracts/credit-manager/tests/deposit_native_test.rs index 82a1010a2..9cba87b14 100644 --- a/contracts/credit-manager/tests/deposit_native_test.rs +++ b/contracts/credit-manager/tests/deposit_native_test.rs @@ -167,9 +167,9 @@ fn test_can_only_deposit_allowed_assets() { }); let cw20_contract = deploy_mock_cw20( &mut app, - String::from("jakecoin"), + "jakecoin", vec![Cw20Coin { - address: user.clone().into(), + address: user.to_string(), amount: Uint128::from(500u128), }], ); diff --git a/contracts/credit-manager/tests/helpers.rs b/contracts/credit-manager/tests/helpers.rs index 2ca12c296..f9fda644a 100644 --- a/contracts/credit-manager/tests/helpers.rs +++ b/contracts/credit-manager/tests/helpers.rs @@ -51,14 +51,14 @@ pub fn mock_create_credit_account( ) } -pub fn deploy_mock_cw20(app: &mut App, symbol: String, initial_balances: Vec) -> Addr { +pub fn deploy_mock_cw20(app: &mut App, symbol: &str, initial_balances: Vec) -> Addr { let code_id = app.store_code(mock_cw20_contract()); app.instantiate_contract( code_id, Addr::unchecked("cw20-instantiator"), &cw20InstantiateMsg { name: format!("Token: {}", symbol.clone()), - symbol, + symbol: symbol.to_string(), decimals: 9, initial_balances, mint: None, diff --git a/contracts/credit-manager/tests/position_query_test.rs b/contracts/credit-manager/tests/position_query_test.rs index d98ccdf5c..7bf1f4811 100644 --- a/contracts/credit-manager/tests/position_query_test.rs +++ b/contracts/credit-manager/tests/position_query_test.rs @@ -11,9 +11,9 @@ pub mod helpers; #[test] fn test_position_query_when_no_result() { let deps = mock_dependencies(); - let position_token = Addr::unchecked("position_token"); - let value = query_position(&deps, &position_token.clone()); - assert_eq!(value.token_id, position_token); + let token_id = "token_id"; + let value = query_position(&deps, token_id); + assert_eq!(value.token_id, token_id); assert_eq!(value.assets.len(), 0); } @@ -21,12 +21,12 @@ fn test_position_query_when_no_result() { fn test_position_query_when_assets_deposited() { let mut deps = mock_dependencies(); - let position_token = Addr::unchecked("position_token"); + let token_id = "token_id"; let native_asset = AssetInfo::Native(String::from("native_asset")); let amount = Uint128::new(123); - save_position(&mut deps, &position_token, &native_asset, &amount); + save_position(&mut deps, token_id, &native_asset, &amount); - let value = query_position(&deps, &position_token); + let value = query_position(&deps, token_id); assert_eq!(value.assets.len(), 1); assert_eq!(value.assets.first().unwrap().amount, amount); assert_eq!(value.assets.first().unwrap().info, native_asset.into()); @@ -36,24 +36,24 @@ fn test_position_query_when_assets_deposited() { fn test_position_query_with_multiple_results() { let mut deps = mock_dependencies(); - let position_token_a = Addr::unchecked("position_token_a"); + let token_id_a = "token_id_a"; let asset_a = AssetInfo::Native(String::from("asset_a")); let amount_a = Uint128::new(123); - save_position(&mut deps, &position_token_a, &asset_a, &amount_a); + save_position(&mut deps, token_id_a, &asset_a, &amount_a); let asset_b = AssetInfo::Cw20(Addr::unchecked(String::from("asset_b"))); let amount_b = Uint128::new(444); - save_position(&mut deps, &position_token_a, &asset_b, &amount_b); + save_position(&mut deps, token_id_a, &asset_b, &amount_b); let asset_c = AssetInfo::Cw20(Addr::unchecked(String::from("asset_c"))); let amount_c = Uint128::new(98); - save_position(&mut deps, &position_token_a, &asset_c, &amount_c); + save_position(&mut deps, token_id_a, &asset_c, &amount_c); - let position_token_b = Addr::unchecked("position_token_b"); + let token_id_b = "token_i_b"; let amount_d = Uint128::new(567); - save_position(&mut deps, &position_token_b, &asset_a, &amount_d); + save_position(&mut deps, token_id_b, &asset_a, &amount_d); - let value = query_position(&deps, &position_token_a); + let value = query_position(&deps, token_id_a); assert_eq!(value.assets.len(), 3); assert_present(&value, &asset_a, &amount_a); @@ -70,28 +70,24 @@ fn assert_present(res: &PositionResponse, asset: &AssetInfoBase, amount: & fn save_position( deps: &mut OwnedDeps, - position_token: &Addr, + token_id: &str, asset: &AssetInfoBase, amount: &Uint128, ) { ASSETS - .save( - &mut deps.storage, - (position_token.clone().into(), asset.into()), - &amount, - ) + .save(&mut deps.storage, (token_id, asset.into()), &amount) .unwrap(); } fn query_position( deps: &OwnedDeps, - position_token_a: &Addr, + token_id: &str, ) -> PositionResponse { let res = query( deps.as_ref(), mock_env(), QueryMsg::Position { - token_id: position_token_a.into(), + token_id: token_id.into(), }, ) .unwrap(); diff --git a/scripts/build_artifacts.sh b/scripts/build_artifacts.sh new file mode 100755 index 000000000..8787f14ff --- /dev/null +++ b/scripts/build_artifacts.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/workspace-optimizer:0.12.6 From 999ca9ec316038d6af827b0c8d3bfaa4b6cc974c Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 14 Jul 2022 19:25:25 +0200 Subject: [PATCH 038/218] Review updates pt. 2 --- .github/workflows/main.yml | 43 ++++-- Cargo.toml | 2 +- Makefile.toml | 39 ++++++ contracts/account-nft/tests/helpers.rs | 4 +- contracts/credit-manager/src/contract.rs | 17 ++- contracts/credit-manager/src/deposit.rs | 53 ++++---- contracts/credit-manager/src/error.rs | 2 +- contracts/credit-manager/src/execute.rs | 14 +- contracts/credit-manager/src/state.rs | 4 +- ...list_query_test.rs => allow_list_query.rs} | 128 +++++++++--------- ...count_test.rs => create_credit_account.rs} | 4 +- .../{deposit_cw20_test.rs => deposit_cw20.rs} | 0 ...posit_native_test.rs => deposit_native.rs} | 0 .../tests/{dispatch_test.rs => dispatch.rs} | 0 contracts/credit-manager/tests/helpers.rs | 4 +- .../{instantiate_test.rs => instantiate.rs} | 18 +-- ...sition_query_test.rs => position_query.rs} | 30 ++-- ...update_config_test.rs => update_config.rs} | 4 +- scripts/build_artifacts.sh | 9 -- 19 files changed, 217 insertions(+), 158 deletions(-) create mode 100644 Makefile.toml rename contracts/credit-manager/tests/{allow_list_query_test.rs => allow_list_query.rs} (66%) rename contracts/credit-manager/tests/{create_credit_account_test.rs => create_credit_account.rs} (96%) rename contracts/credit-manager/tests/{deposit_cw20_test.rs => deposit_cw20.rs} (100%) rename contracts/credit-manager/tests/{deposit_native_test.rs => deposit_native.rs} (100%) rename contracts/credit-manager/tests/{dispatch_test.rs => dispatch.rs} (100%) rename contracts/credit-manager/tests/{instantiate_test.rs => instantiate.rs} (84%) rename contracts/credit-manager/tests/{position_query_test.rs => position_query.rs} (70%) rename contracts/credit-manager/tests/{update_config_test.rs => update_config.rs} (97%) delete mode 100755 scripts/build_artifacts.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 587bf75ba..c9ba3a63c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,15 +19,31 @@ jobs: profile: minimal toolchain: stable override: true + target: wasm32-unknown-unknown + + - name: Install cargo make + uses: davidB/rust-cargo-make@v1 - name: Run cargo check uses: actions-rs/cargo@v1 with: - command: check + command: make + args: check + + - name: Compile WASM contract + uses: actions-rs/cargo@v1 + with: + command: make + args: build + env: + RUSTFLAGS: "-C link-arg=-s" - - name: Compile Contracts to WASM - run: | - $GITHUB_WORKSPACE/scripts/build_artifacts.sh + + - name: Run Rust-Optimizer + uses: actions-rs/cargo@v1 + with: + command: make + args: rust-optimizer test: name: Test Suite @@ -41,14 +57,16 @@ jobs: with: profile: minimal toolchain: stable - target: wasm32-unknown-unknown override: true + - name: Install cargo make + uses: davidB/rust-cargo-make@v1 + - name: Run tests uses: actions-rs/cargo@v1 with: - command: test - args: --locked + command: make + args: test env: RUST_BACKTRACE: 1 @@ -67,14 +85,17 @@ jobs: components: rustfmt, clippy override: true + - name: Install cargo make + uses: davidB/rust-cargo-make@v1 + - name: Run cargo fmt uses: actions-rs/cargo@v1 with: - command: fmt - args: --all -- --check + command: make + args: fmt - name: Run cargo clippy uses: actions-rs/cargo@v1 with: - command: clippy - args: -- -D warnings + command: make + args: clippy diff --git a/Cargo.toml b/Cargo.toml index 57fb8971a..7f0befe91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,4 @@ lto = true overflow-checks = true opt-level = 3 debug = false -debug-assertions = false \ No newline at end of file +debug-assertions = false diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 000000000..f8fcd34d3 --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,39 @@ +[config] +default_to_workspace = false + +[tasks.check] +command = "cargo" +args = ["check"] + +[tasks.build] +command = "cargo" +args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked"] + +[tasks.rust-optimizer] +script = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.12.6 +""" + +[tasks.test] +command = "cargo" +args = ["test", "--locked"] + +[tasks.fmt] +command = "cargo" +args = ["fmt", "--all", "--check"] + +[tasks.clippy] +command = "cargo" +args = ["clippy", "--", "-D", "warnings"] + +[tasks.all-github-actions] +dependencies = [ + "check", + "build", + "rust-optimizer", + "test", + "fmt", + "clippy", +] diff --git a/contracts/account-nft/tests/helpers.rs b/contracts/account-nft/tests/helpers.rs index 4605df196..6c8273b87 100644 --- a/contracts/account-nft/tests/helpers.rs +++ b/contracts/account-nft/tests/helpers.rs @@ -14,8 +14,8 @@ pub fn instantiate_mock_nft_contract(app: &mut BasicApp, owner: &Addr) -> Addr { code_id, owner.clone(), &InstantiateMsg { - name: String::from("mock_nft"), - symbol: String::from("MOCK"), + name: "mock_nft".to_string(), + symbol: "MOCK".to_string(), minter: owner.to_string(), }, &[], diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 2ff51a58e..1485723bc 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -1,11 +1,14 @@ use cosmwasm_std::{ - entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, + entry_point, from_binary, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, + StdResult, }; use cw2::set_contract_version; +use cw20::Cw20ReceiveMsg; +use rover::msg::execute::ReceiveMsg; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::deposit::receive_cw20; +use crate::deposit::cw20_deposit; use crate::error::ContractError; use crate::execute::{create_credit_account, dispatch_actions, execute_callback, update_config}; use crate::instantiate::store_config; @@ -59,3 +62,13 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::Position { token_id } => to_binary(&query_position(deps, &token_id)?), } } + +pub fn receive_cw20( + deps: DepsMut, + info: MessageInfo, + cw20_msg: Cw20ReceiveMsg, +) -> Result { + match from_binary(&cw20_msg.msg)? { + ReceiveMsg::Deposit { token_id } => cw20_deposit(deps, info, &cw20_msg, &token_id), + } +} diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index 8f5687287..d700bae99 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -1,13 +1,8 @@ -use std::ops::Add; - -use cosmwasm_std::{from_binary, Api, DepsMut, MessageInfo, Response, StdResult, Storage, Uint128}; +use cosmwasm_std::{Api, DepsMut, MessageInfo, Response, StdError, StdResult, Storage, Uint128}; use cw20::Cw20ReceiveMsg; use cw_asset::{Asset, AssetInfo, AssetInfoUnchecked, AssetList, AssetUnchecked}; -use rover::msg::execute::ReceiveMsg; - use crate::error::ContractError; -use crate::error::ContractError::{FundsMismatch, NotWhitelisted, WrongDepositMethodForCW20}; use crate::execute::assert_is_token_owner; use crate::state::{ALLOWED_ASSETS, ASSETS}; @@ -32,16 +27,16 @@ pub fn native_deposit( received_coins.deduct(&asset)?; } AssetInfo::Cw20(_) => { - return Err(WrongDepositMethodForCW20 {}); + return Err(ContractError::WrongDepositMethodForCW20 {}); } } // increase the user asset amount - increment_position(storage, nft_token_id, &asset.info, &asset.amount)?; + increment_position(storage, nft_token_id, &asset.info, asset.amount)?; Ok(response - .add_attribute("deposit_received", asset.to_string()) - .add_attribute("action", "rover/credit_manager/callback/deposit")) + .add_attribute("action", "rover/credit_manager/callback/deposit") + .add_attribute("deposit_received", asset.to_string())) } /// Assert that fund of exactly the same type and amount was sent along with a message @@ -53,7 +48,7 @@ fn assert_sent_fund(expected: &Asset, received_coins: &AssetList) -> Result<(), }; if received_amount != expected.amount { - return Err(FundsMismatch { + return Err(ContractError::FundsMismatch { expected: expected.amount, received: received_amount, }); @@ -62,23 +57,20 @@ fn assert_sent_fund(expected: &Asset, received_coins: &AssetList) -> Result<(), Ok(()) } -pub fn receive_cw20( +pub fn cw20_deposit( deps: DepsMut, info: MessageInfo, - cw20_msg: Cw20ReceiveMsg, + cw20_msg: &Cw20ReceiveMsg, + token_id: &str, ) -> Result { - match from_binary(&cw20_msg.msg)? { - ReceiveMsg::Deposit { token_id } => { - let sender = deps.api.addr_validate(&cw20_msg.sender)?; - assert_is_token_owner(&deps, &sender, &token_id)?; - let asset = AssetInfoUnchecked::cw20(&info.sender).check(deps.api, None)?; - assert_asset_is_whitelisted(deps.storage, &asset)?; - increment_position(deps.storage, &token_id, &asset, &cw20_msg.amount)?; - Ok(Response::new() - .add_attribute("deposit_received", asset.to_string()) - .add_attribute("action", "rover/execute/receive_cw20")) - } - } + let sender = deps.api.addr_validate(&cw20_msg.sender)?; + assert_is_token_owner(&deps, &sender, token_id)?; + let asset = AssetInfoUnchecked::cw20(&info.sender).check(deps.api, None)?; + assert_asset_is_whitelisted(deps.storage, &asset)?; + increment_position(deps.storage, token_id, &asset, cw20_msg.amount)?; + Ok(Response::new() + .add_attribute("action", "rover/execute/receive_cw20") + .add_attribute("deposit_received", asset.to_string())) } fn assert_asset_is_whitelisted( @@ -87,7 +79,7 @@ fn assert_asset_is_whitelisted( ) -> Result<(), ContractError> { let is_whitelisted = ALLOWED_ASSETS.has(storage, asset.into()); if !is_whitelisted { - return Err(NotWhitelisted(asset.to_string())); + return Err(ContractError::NotWhitelisted(asset.to_string())); } Ok(()) } @@ -96,12 +88,17 @@ fn increment_position( storage: &mut dyn Storage, token_id: &str, asset: &AssetInfo, - amount: &Uint128, + amount: Uint128, ) -> StdResult<()> { ASSETS.update( storage, (token_id, asset.into()), - |value_opt| -> StdResult<_> { Ok(value_opt.unwrap_or(Uint128::zero()).add(amount)) }, + |value_opt| -> StdResult<_> { + value_opt + .unwrap_or_else(Uint128::zero) + .checked_add(amount) + .map_err(|_| StdError::generic_err("add overflow error")) + }, )?; Ok(()) } diff --git a/contracts/credit-manager/src/error.rs b/contracts/credit-manager/src/error.rs index ecd469852..fc113d792 100644 --- a/contracts/credit-manager/src/error.rs +++ b/contracts/credit-manager/src/error.rs @@ -25,7 +25,7 @@ pub enum ContractError { received: Uint128, }, - #[error("This method cannot be invoked externally")] + #[error("Callbacks cannot be invoked externally")] ExternalInvocation {}, #[error("{user:?} is not the owner of {token_id:?}")] diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index a23279f8c..9cb9e98fa 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -10,9 +10,7 @@ use rover::msg::execute::{Action, CallbackMsg}; use crate::deposit::native_deposit; use crate::error::ContractError; -use crate::error::ContractError::{ - ExternalInvocation, ExtraFundsReceived, NotTokenOwner, Unauthorized, -}; + use crate::state::{ACCOUNT_NFT, OWNER}; pub fn create_credit_account(deps: DepsMut, user: Addr) -> Result { @@ -40,9 +38,9 @@ pub fn update_config( let owner = OWNER.load(deps.storage)?; if info.sender != owner { - return Err(Unauthorized { + return Err(ContractError::Unauthorized { user: info.sender.into(), - action: String::from("update config"), + action: "update config".to_string(), }); } @@ -105,7 +103,7 @@ pub fn dispatch_actions( // after all deposits have been handled, we assert that the `received_natives` list is empty // this way, we ensure that the user does not send any extra fund which will get lost in the contract if !received_coins.is_empty() { - return Err(ExtraFundsReceived(received_coins)); + return Err(ContractError::ExtraFundsReceived(received_coins)); } let callback_msgs = callbacks @@ -125,7 +123,7 @@ pub fn execute_callback( callback: CallbackMsg, ) -> Result { if info.sender != env.contract.address { - return Err(ExternalInvocation {}); + return Err(ContractError::ExternalInvocation {}); } match callback { CallbackMsg::Placeholder { .. } => Ok(Response::new()), @@ -147,7 +145,7 @@ pub fn assert_is_token_owner( )?; if user != &owner_res.owner { - return Err(NotTokenOwner { + return Err(ContractError::NotTokenOwner { user: user.to_string(), token_id: token_id.to_string(), }); diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 86ab1c112..fb490564a 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -2,12 +2,12 @@ use cosmwasm_std::{Addr, Uint128}; use cw_asset::AssetInfoKey; use cw_storage_plus::{Item, Map}; -/* Contract config */ +// Contract config pub const OWNER: Item = Item::new("owner"); pub const ACCOUNT_NFT: Item = Item::new("account_nft"); pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); -/* Positions */ +// Positions type NftTokenId<'a> = &'a str; pub const ASSETS: Map<(NftTokenId, AssetInfoKey), Uint128> = Map::new("assets"); diff --git a/contracts/credit-manager/tests/allow_list_query_test.rs b/contracts/credit-manager/tests/allow_list_query.rs similarity index 66% rename from contracts/credit-manager/tests/allow_list_query_test.rs rename to contracts/credit-manager/tests/allow_list_query.rs index e55e8e089..4d3b209fc 100644 --- a/contracts/credit-manager/tests/allow_list_query_test.rs +++ b/contracts/credit-manager/tests/allow_list_query.rs @@ -15,38 +15,38 @@ fn test_pagination_on_allowed_vaults_query_works() { let owner = Addr::unchecked("owner"); let allowed_vaults = vec![ - String::from("addr1"), - String::from("addr2"), - String::from("addr3"), - String::from("addr4"), - String::from("addr5"), - String::from("addr6"), - String::from("addr7"), - String::from("addr8"), - String::from("addr9"), - String::from("addr10"), - String::from("addr11"), - String::from("addr12"), - String::from("addr13"), - String::from("addr14"), - String::from("addr15"), - String::from("addr16"), - String::from("addr17"), - String::from("addr18"), - String::from("addr19"), - String::from("addr20"), - String::from("addr21"), - String::from("addr22"), - String::from("addr23"), - String::from("addr24"), - String::from("addr25"), - String::from("addr26"), - String::from("addr27"), - String::from("addr28"), - String::from("addr29"), - String::from("addr30"), - String::from("addr31"), - String::from("addr32"), + "addr1".to_string(), + "addr2".to_string(), + "addr3".to_string(), + "addr4".to_string(), + "addr5".to_string(), + "addr6".to_string(), + "addr7".to_string(), + "addr8".to_string(), + "addr9".to_string(), + "addr10".to_string(), + "addr11".to_string(), + "addr12".to_string(), + "addr13".to_string(), + "addr14".to_string(), + "addr15".to_string(), + "addr16".to_string(), + "addr17".to_string(), + "addr18".to_string(), + "addr19".to_string(), + "addr20".to_string(), + "addr21".to_string(), + "addr22".to_string(), + "addr23".to_string(), + "addr24".to_string(), + "addr25".to_string(), + "addr26".to_string(), + "addr27".to_string(), + "addr28".to_string(), + "addr29".to_string(), + "addr30".to_string(), + "addr31".to_string(), + "addr32".to_string(), ]; let msg = InstantiateMsg { @@ -157,38 +157,38 @@ fn test_pagination_on_allowed_assets_query_works() { let owner = Addr::unchecked("owner"); let allowed_assets = vec![ - AssetInfoUnchecked::Native(String::from("native_asset_1")), - AssetInfoUnchecked::Native(String::from("native_asset_2")), - AssetInfoUnchecked::Native(String::from("native_asset_3")), - AssetInfoUnchecked::Native(String::from("native_asset_4")), - AssetInfoUnchecked::Native(String::from("native_asset_5")), - AssetInfoUnchecked::Cw20(String::from("cw_token_1")), - AssetInfoUnchecked::Cw20(String::from("cw_token_2")), - AssetInfoUnchecked::Cw20(String::from("cw_token_3")), - AssetInfoUnchecked::Cw20(String::from("cw_token_4")), - AssetInfoUnchecked::Cw20(String::from("cw_token_5")), - AssetInfoUnchecked::Cw20(String::from("cw_token_6")), - AssetInfoUnchecked::Cw20(String::from("cw_token_7")), - AssetInfoUnchecked::Cw20(String::from("cw_token_8")), - AssetInfoUnchecked::Cw20(String::from("cw_token_9")), - AssetInfoUnchecked::Native(String::from("native_asset_6")), - AssetInfoUnchecked::Native(String::from("native_asset_7")), - AssetInfoUnchecked::Cw20(String::from("cw_token_10")), - AssetInfoUnchecked::Cw20(String::from("cw_token_11")), - AssetInfoUnchecked::Cw20(String::from("cw_token_12")), - AssetInfoUnchecked::Cw20(String::from("cw_token_13")), - AssetInfoUnchecked::Cw20(String::from("cw_token_14")), - AssetInfoUnchecked::Native(String::from("native_asset_8")), - AssetInfoUnchecked::Native(String::from("native_asset_9")), - AssetInfoUnchecked::Native(String::from("native_asset_10")), - AssetInfoUnchecked::Cw20(String::from("cw_token_15")), - AssetInfoUnchecked::Cw20(String::from("cw_token_16")), - AssetInfoUnchecked::Cw20(String::from("cw_token_17")), - AssetInfoUnchecked::Cw20(String::from("cw_token_18")), - AssetInfoUnchecked::Cw20(String::from("cw_token_19")), - AssetInfoUnchecked::Cw20(String::from("cw_token_20")), - AssetInfoUnchecked::Native(String::from("native_asset_11")), - AssetInfoUnchecked::Native(String::from("native_asset_12")), + AssetInfoUnchecked::Native("native_asset_1".to_string()), + AssetInfoUnchecked::Native("native_asset_2".to_string()), + AssetInfoUnchecked::Native("native_asset_3".to_string()), + AssetInfoUnchecked::Native("native_asset_4".to_string()), + AssetInfoUnchecked::Native("native_asset_5".to_string()), + AssetInfoUnchecked::Cw20("cw_token_1".to_string()), + AssetInfoUnchecked::Cw20("cw_token_2".to_string()), + AssetInfoUnchecked::Cw20("cw_token_3".to_string()), + AssetInfoUnchecked::Cw20("cw_token_4".to_string()), + AssetInfoUnchecked::Cw20("cw_token_5".to_string()), + AssetInfoUnchecked::Cw20("cw_token_6".to_string()), + AssetInfoUnchecked::Cw20("cw_token_7".to_string()), + AssetInfoUnchecked::Cw20("cw_token_8".to_string()), + AssetInfoUnchecked::Cw20("cw_token_9".to_string()), + AssetInfoUnchecked::Native("native_asset_6".to_string()), + AssetInfoUnchecked::Native("native_asset_7".to_string()), + AssetInfoUnchecked::Cw20("cw_token_10".to_string()), + AssetInfoUnchecked::Cw20("cw_token_11".to_string()), + AssetInfoUnchecked::Cw20("cw_token_12".to_string()), + AssetInfoUnchecked::Cw20("cw_token_13".to_string()), + AssetInfoUnchecked::Cw20("cw_token_14".to_string()), + AssetInfoUnchecked::Native("native_asset_8".to_string()), + AssetInfoUnchecked::Native("native_asset_9".to_string()), + AssetInfoUnchecked::Native("native_asset_10".to_string()), + AssetInfoUnchecked::Cw20("cw_token_15".to_string()), + AssetInfoUnchecked::Cw20("cw_token_16".to_string()), + AssetInfoUnchecked::Cw20("cw_token_17".to_string()), + AssetInfoUnchecked::Cw20("cw_token_18".to_string()), + AssetInfoUnchecked::Cw20("cw_token_19".to_string()), + AssetInfoUnchecked::Cw20("cw_token_20".to_string()), + AssetInfoUnchecked::Native("native_asset_11".to_string()), + AssetInfoUnchecked::Native("native_asset_12".to_string()), ]; let msg = InstantiateMsg { diff --git a/contracts/credit-manager/tests/create_credit_account_test.rs b/contracts/credit-manager/tests/create_credit_account.rs similarity index 96% rename from contracts/credit-manager/tests/create_credit_account_test.rs rename to contracts/credit-manager/tests/create_credit_account.rs index 1d3e581ef..2c372a17b 100644 --- a/contracts/credit-manager/tests/create_credit_account_test.rs +++ b/contracts/credit-manager/tests/create_credit_account.rs @@ -26,8 +26,8 @@ fn test_create_credit_account() { nft_contract_code_id, owner.clone(), &NftInstantiateMsg { - name: String::from("Rover Credit Account"), - symbol: String::from("RCA"), + name: "Rover Credit Account".to_string(), + symbol: "RCA".to_string(), minter: owner.to_string(), }, &[], diff --git a/contracts/credit-manager/tests/deposit_cw20_test.rs b/contracts/credit-manager/tests/deposit_cw20.rs similarity index 100% rename from contracts/credit-manager/tests/deposit_cw20_test.rs rename to contracts/credit-manager/tests/deposit_cw20.rs diff --git a/contracts/credit-manager/tests/deposit_native_test.rs b/contracts/credit-manager/tests/deposit_native.rs similarity index 100% rename from contracts/credit-manager/tests/deposit_native_test.rs rename to contracts/credit-manager/tests/deposit_native.rs diff --git a/contracts/credit-manager/tests/dispatch_test.rs b/contracts/credit-manager/tests/dispatch.rs similarity index 100% rename from contracts/credit-manager/tests/dispatch_test.rs rename to contracts/credit-manager/tests/dispatch.rs diff --git a/contracts/credit-manager/tests/helpers.rs b/contracts/credit-manager/tests/helpers.rs index f9fda644a..a7a6a9304 100644 --- a/contracts/credit-manager/tests/helpers.rs +++ b/contracts/credit-manager/tests/helpers.rs @@ -102,8 +102,8 @@ pub fn setup_nft_contract(app: &mut App, owner: &Addr, manager_contract_addr: &A nft_contract_code_id, owner.clone(), &NftInstantiateMsg { - name: String::from("Rover Credit Account"), - symbol: String::from("RCA"), + name: "Rover Credit Account".to_string(), + symbol: "RCA".to_string(), minter: owner.to_string(), }, &[], diff --git a/contracts/credit-manager/tests/instantiate_test.rs b/contracts/credit-manager/tests/instantiate.rs similarity index 84% rename from contracts/credit-manager/tests/instantiate_test.rs rename to contracts/credit-manager/tests/instantiate.rs index adb67cde5..9a2bc612f 100644 --- a/contracts/credit-manager/tests/instantiate_test.rs +++ b/contracts/credit-manager/tests/instantiate.rs @@ -69,16 +69,16 @@ fn test_allowed_vaults_and_assets_stored_on_instantiate() { let owner = Addr::unchecked("owner"); let allowed_vaults = vec![ - String::from("vaultcontract1"), - String::from("vaultcontract2"), - String::from("vaultcontract3"), + "vaultcontract1".to_string(), + "vaultcontract2".to_string(), + "vaultcontract3".to_string(), ]; let allowed_assets = vec![ - AssetInfoUnchecked::Native(String::from("uosmo")), - AssetInfoUnchecked::Cw20(String::from("osmo85wwjycfxjlaxsae9asmxlk3bsgxbw")), - AssetInfoUnchecked::Cw20(String::from("osmompbtkt3jezatztteo577lxkqbkdyke")), - AssetInfoUnchecked::Cw20(String::from("osmos6kmpxz9xcstleqnu2fnz8gskgf6gx")), + AssetInfoUnchecked::Native("uosmo".to_string()), + AssetInfoUnchecked::Cw20("osmo85wwjycfxjlaxsae9asmxlk3bsgxbw".to_string()), + AssetInfoUnchecked::Cw20("osmompbtkt3jezatztteo577lxkqbkdyke".to_string()), + AssetInfoUnchecked::Cw20("osmos6kmpxz9xcstleqnu2fnz8gskgf6gx".to_string()), ]; let msg = InstantiateMsg { @@ -135,7 +135,7 @@ fn test_panics_on_invalid_instantiation_addrs() { let msg = InstantiateMsg { owner: owner.to_string(), - allowed_vaults: vec![String::from("%%%INVALID%%%")], + allowed_vaults: vec!["%%%INVALID%%%".to_string()], allowed_assets: vec![], }; @@ -155,7 +155,7 @@ fn test_panics_on_invalid_instantiation_addrs() { let msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], - allowed_assets: vec![AssetInfoUnchecked::Cw20(String::from("AA"))], // Because cw-asset lowercases before passing to validate, in the test env, two letter strings is only one that triggers a fail + allowed_assets: vec![AssetInfoUnchecked::Cw20("AA".to_string())], // Because cw-asset lowercases before passing to validate, in the test env, two letter strings is only one that triggers a fail }; let instantiate_res = app.instantiate_contract( diff --git a/contracts/credit-manager/tests/position_query_test.rs b/contracts/credit-manager/tests/position_query.rs similarity index 70% rename from contracts/credit-manager/tests/position_query_test.rs rename to contracts/credit-manager/tests/position_query.rs index 7bf1f4811..fdc673c04 100644 --- a/contracts/credit-manager/tests/position_query_test.rs +++ b/contracts/credit-manager/tests/position_query.rs @@ -22,9 +22,9 @@ fn test_position_query_when_assets_deposited() { let mut deps = mock_dependencies(); let token_id = "token_id"; - let native_asset = AssetInfo::Native(String::from("native_asset")); + let native_asset = AssetInfo::Native("native_asset".to_string()); let amount = Uint128::new(123); - save_position(&mut deps, token_id, &native_asset, &amount); + save_position(&mut deps, token_id, &native_asset, amount); let value = query_position(&deps, token_id); assert_eq!(value.assets.len(), 1); @@ -37,34 +37,34 @@ fn test_position_query_with_multiple_results() { let mut deps = mock_dependencies(); let token_id_a = "token_id_a"; - let asset_a = AssetInfo::Native(String::from("asset_a")); + let asset_a = AssetInfo::Native("asset_a".to_string()); let amount_a = Uint128::new(123); - save_position(&mut deps, token_id_a, &asset_a, &amount_a); + save_position(&mut deps, token_id_a, &asset_a, amount_a); - let asset_b = AssetInfo::Cw20(Addr::unchecked(String::from("asset_b"))); + let asset_b = AssetInfo::Cw20(Addr::unchecked("asset_b".to_string())); let amount_b = Uint128::new(444); - save_position(&mut deps, token_id_a, &asset_b, &amount_b); + save_position(&mut deps, token_id_a, &asset_b, amount_b); - let asset_c = AssetInfo::Cw20(Addr::unchecked(String::from("asset_c"))); + let asset_c = AssetInfo::Cw20(Addr::unchecked("asset_c".to_string())); let amount_c = Uint128::new(98); - save_position(&mut deps, token_id_a, &asset_c, &amount_c); + save_position(&mut deps, token_id_a, &asset_c, amount_c); let token_id_b = "token_i_b"; let amount_d = Uint128::new(567); - save_position(&mut deps, token_id_b, &asset_a, &amount_d); + save_position(&mut deps, token_id_b, &asset_a, amount_d); let value = query_position(&deps, token_id_a); assert_eq!(value.assets.len(), 3); - assert_present(&value, &asset_a, &amount_a); - assert_present(&value, &asset_b, &amount_b); - assert_present(&value, &asset_c, &amount_c); + assert_present(&value, &asset_a, amount_a); + assert_present(&value, &asset_b, amount_b); + assert_present(&value, &asset_c, amount_c); } -fn assert_present(res: &PositionResponse, asset: &AssetInfoBase, amount: &Uint128) { +fn assert_present(res: &PositionResponse, asset: &AssetInfoBase, amount: Uint128) { res.assets .iter() - .find(|item| item.info == asset.clone().into() && &item.amount == amount) + .find(|item| item.info == asset.clone().into() && item.amount == amount) .unwrap(); } @@ -72,7 +72,7 @@ fn save_position( deps: &mut OwnedDeps, token_id: &str, asset: &AssetInfoBase, - amount: &Uint128, + amount: Uint128, ) { ASSETS .save(&mut deps.storage, (token_id, asset.into()), &amount) diff --git a/contracts/credit-manager/tests/update_config_test.rs b/contracts/credit-manager/tests/update_config.rs similarity index 97% rename from contracts/credit-manager/tests/update_config_test.rs rename to contracts/credit-manager/tests/update_config.rs index 2dc21a62d..4e2578feb 100644 --- a/contracts/credit-manager/tests/update_config_test.rs +++ b/contracts/credit-manager/tests/update_config.rs @@ -142,8 +142,8 @@ fn setup_nft_and_propose_owner(app: &mut App, original_owner: &Addr, contract_ad nft_contract_code_id, original_owner.clone(), &NftInstantiateMsg { - name: String::from("Rover Credit Account"), - symbol: String::from("RCA"), + name: "Rover Credit Account".to_string(), + symbol: "RCA".to_string(), minter: original_owner.to_string(), }, &[], diff --git a/scripts/build_artifacts.sh b/scripts/build_artifacts.sh deleted file mode 100755 index 8787f14ff..000000000 --- a/scripts/build_artifacts.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ - --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/workspace-optimizer:0.12.6 From ea5929426b3bfe83569b9ddb4cb93cf4aaf7ee2c Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 12 Jul 2022 19:05:09 +0200 Subject: [PATCH 039/218] Borrowing from red bank --- Cargo.lock | 24 +- contracts/credit-manager/Cargo.toml | 4 +- contracts/credit-manager/src/borrow.rs | 68 +++ contracts/credit-manager/src/contract.rs | 18 +- contracts/credit-manager/src/deposit.rs | 5 +- contracts/credit-manager/src/execute.rs | 22 +- contracts/credit-manager/src/instantiate.rs | 4 +- contracts/credit-manager/src/lib.rs | 2 +- contracts/credit-manager/src/query.rs | 46 +- contracts/credit-manager/src/state.rs | 9 +- .../credit-manager/tests/allow_list_query.rs | 7 + contracts/credit-manager/tests/borrow.rs | 415 ++++++++++++++++++ .../tests/create_credit_account.rs | 5 + .../credit-manager/tests/deposit_cw20.rs | 12 +- .../credit-manager/tests/deposit_native.rs | 24 +- contracts/credit-manager/tests/dispatch.rs | 8 +- contracts/credit-manager/tests/helpers.rs | 39 +- contracts/credit-manager/tests/instantiate.rs | 68 ++- .../credit-manager/tests/update_config.rs | 47 +- contracts/mock-red-bank/Cargo.toml | 21 + contracts/mock-red-bank/src/contract.rs | 43 ++ contracts/mock-red-bank/src/execute.rs | 17 + contracts/mock-red-bank/src/helpers.rs | 10 + contracts/mock-red-bank/src/lib.rs | 7 + contracts/mock-red-bank/src/msg.rs | 32 ++ contracts/mock-red-bank/src/query.rs | 26 ++ contracts/mock-red-bank/src/state.rs | 5 + packages/rover/Cargo.toml | 3 + packages/rover/src/adapters/mod.rs | 3 + packages/rover/src/adapters/red_bank.rs | 64 +++ .../rover}/src/error.rs | 11 +- packages/rover/src/lib.rs | 2 + packages/rover/src/msg/execute.rs | 13 +- packages/rover/src/msg/instantiate.rs | 2 + packages/rover/src/msg/query.rs | 9 + 35 files changed, 1019 insertions(+), 76 deletions(-) create mode 100644 contracts/credit-manager/src/borrow.rs create mode 100644 contracts/credit-manager/tests/borrow.rs create mode 100644 contracts/mock-red-bank/Cargo.toml create mode 100644 contracts/mock-red-bank/src/contract.rs create mode 100644 contracts/mock-red-bank/src/execute.rs create mode 100644 contracts/mock-red-bank/src/helpers.rs create mode 100644 contracts/mock-red-bank/src/lib.rs create mode 100644 contracts/mock-red-bank/src/msg.rs create mode 100644 contracts/mock-red-bank/src/query.rs create mode 100644 contracts/mock-red-bank/src/state.rs create mode 100644 packages/rover/src/adapters/mod.rs create mode 100644 packages/rover/src/adapters/red_bank.rs rename {contracts/credit-manager => packages/rover}/src/error.rs (76%) diff --git a/Cargo.lock b/Cargo.lock index e4ee96149..8281e658f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,10 +146,10 @@ dependencies = [ "cw20-base", "cw721", "cw721-base", + "mock-red-bank", "rover", "schemars", "serde", - "thiserror", ] [[package]] @@ -511,6 +511,18 @@ version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +[[package]] +name = "mock-red-bank" +version = "1.0.0" +dependencies = [ + "cosmwasm-std", + "cw-asset", + "cw-storage-plus", + "cw20", + "schemars", + "serde", +] + [[package]] name = "opaque-debug" version = "0.3.0" @@ -606,8 +618,10 @@ dependencies = [ "cw-asset", "cw-storage-plus", "cw20", + "mock-red-bank", "schemars", "serde", + "thiserror", ] [[package]] @@ -719,9 +733,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.4.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" dependencies = [ "digest", "rand_core 0.6.3", @@ -824,6 +838,6 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" -version = "1.5.5" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94693807d016b2f2d2e14420eb3bfcca689311ff775dcf113d74ea624b7cdf07" +checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 4587585d5..e5a902311 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -24,9 +24,9 @@ cw721-base = { version = "0.13", features = ["library"] } cw-asset = "2.1" cw-storage-plus = "0.13" schemars = "0.8" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } -thiserror = "1.0" +serde = { version = "1.0", default-features = false, features = ["derive"] } [dev-dependencies] anyhow = "1" cw-multi-test = "0.13" +mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs new file mode 100644 index 000000000..e4b386e46 --- /dev/null +++ b/contracts/credit-manager/src/borrow.rs @@ -0,0 +1,68 @@ +use cosmwasm_std::{DepsMut, Env, Response, StdResult, Uint128}; +use cw_asset::Asset; + +use crate::deposit::assert_asset_is_whitelisted; +use rover::error::ContractError; +use rover::error::ContractError::NoAmount; + +use crate::state::{ASSETS, DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; + +pub static DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED: Uint128 = Uint128::new(1_000_000); + +/// calculate by how many the user's debt units should be increased +/// if total debt is zero, then we define 1 unit of asset borrowed = 1,000,000 debt unit +/// else, get debt ownership % and multiply by total existing shares +/// +/// increment total debt shares, token debt shares, and asset amount +pub fn borrow( + deps: DepsMut, + env: Env, + token_id: &str, + asset: Asset, +) -> Result { + if asset.amount.is_zero() { + return Err(NoAmount {}); + } + + assert_asset_is_whitelisted(deps.storage, &asset.info)?; + + let red_bank = RED_BANK.load(deps.storage)?; + let total_debt_amount = + red_bank.query_user_debt(&deps.querier, &env.contract.address, &asset.info)?; + + let debt_shares_to_add = if total_debt_amount.is_zero() { + asset + .amount + .checked_mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED)? + } else { + TOTAL_DEBT_SHARES + .load(deps.storage, asset.clone().info.into())? + .checked_multiply_ratio(asset.amount, total_debt_amount)? + }; + + TOTAL_DEBT_SHARES.update( + deps.storage, + asset.clone().info.into(), + |shares| -> StdResult<_> { Ok(shares.unwrap_or(Uint128::zero()) + debt_shares_to_add) }, + )?; + + DEBT_SHARES.update( + deps.storage, + (token_id, asset.clone().info.into()), + |current_debt| -> StdResult<_> { + Ok(current_debt.unwrap_or(Uint128::zero()) + debt_shares_to_add) + }, + )?; + + ASSETS.update( + deps.storage, + (token_id, asset.clone().info.into()), + |amount| -> StdResult<_> { Ok(amount.unwrap_or(Uint128::zero()) + asset.amount) }, + )?; + + Ok(Response::new() + .add_message(red_bank.borrow_msg(&asset)?) + .add_attribute("action", "rover/credit_manager/borrow") + .add_attribute("debt_shares_added", debt_shares_to_add) + .add_attribute("assets_borrowed", asset.amount)) +} diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 1485723bc..e98d35f14 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -4,15 +4,18 @@ use cosmwasm_std::{ }; use cw2::set_contract_version; use cw20::Cw20ReceiveMsg; +use rover::error::ContractError; use rover::msg::execute::ReceiveMsg; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::deposit::cw20_deposit; -use crate::error::ContractError; use crate::execute::{create_credit_account, dispatch_actions, execute_callback, update_config}; use crate::instantiate::store_config; -use crate::query::{query_allowed_assets, query_allowed_vaults, query_config, query_position}; +use crate::query::{ + query_allowed_assets, query_allowed_vaults, query_config, query_position, + query_total_debt_shares, +}; const CONTRACT_NAME: &str = "crates.io:rover-credit-manager"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -38,9 +41,11 @@ pub fn execute( ) -> Result { match msg { ExecuteMsg::CreateCreditAccount {} => create_credit_account(deps, info.sender), - ExecuteMsg::UpdateConfig { account_nft, owner } => { - update_config(deps, info, account_nft, owner) - } + ExecuteMsg::UpdateConfig { + account_nft, + owner, + red_bank, + } => update_config(deps, info, account_nft, owner, red_bank), ExecuteMsg::Callback(callback) => execute_callback(deps, info, env, callback), ExecuteMsg::UpdateCreditAccount { token_id, actions } => { dispatch_actions(deps, env, info, &token_id, &actions) @@ -60,6 +65,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { to_binary(&query_allowed_assets(deps, start_after, limit)?) } QueryMsg::Position { token_id } => to_binary(&query_position(deps, &token_id)?), + QueryMsg::TotalDebtShares(asset_info) => { + to_binary(&query_total_debt_shares(deps, asset_info)?) + } } } diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index d700bae99..7638fef0b 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -2,7 +2,8 @@ use cosmwasm_std::{Api, DepsMut, MessageInfo, Response, StdError, StdResult, Sto use cw20::Cw20ReceiveMsg; use cw_asset::{Asset, AssetInfo, AssetInfoUnchecked, AssetList, AssetUnchecked}; -use crate::error::ContractError; +use rover::error::ContractError; + use crate::execute::assert_is_token_owner; use crate::state::{ALLOWED_ASSETS, ASSETS}; @@ -73,7 +74,7 @@ pub fn cw20_deposit( .add_attribute("deposit_received", asset.to_string())) } -fn assert_asset_is_whitelisted( +pub fn assert_asset_is_whitelisted( storage: &mut dyn Storage, asset: &AssetInfo, ) -> Result<(), ContractError> { diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 9cb9e98fa..04c31320e 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -6,12 +6,13 @@ use cw721_base::QueryMsg; use cw_asset::AssetList; use account_nft::msg::ExecuteMsg as NftExecuteMsg; +use rover::adapters::RedBankUnchecked; +use rover::error::ContractError; use rover::msg::execute::{Action, CallbackMsg}; +use crate::borrow::borrow; use crate::deposit::native_deposit; -use crate::error::ContractError; - -use crate::state::{ACCOUNT_NFT, OWNER}; +use crate::state::{ACCOUNT_NFT, OWNER, RED_BANK}; pub fn create_credit_account(deps: DepsMut, user: Addr) -> Result { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; @@ -34,6 +35,7 @@ pub fn update_config( info: MessageInfo, new_account_nft: Option, new_owner: Option, + new_red_bank: Option, ) -> Result { let owner = OWNER.load(deps.storage)?; @@ -62,6 +64,11 @@ pub fn update_config( .add_attribute("action", "rover/credit_manager/update_config/account_nft"); } + if let Some(unchecked) = new_red_bank { + RED_BANK.save(deps.storage, &unchecked.check(deps.api)?)?; + response = response.add_attribute("action", "rover/credit_manager/update_config/red_bank"); + } + if let Some(addr_str) = new_owner { let validated = deps.api.addr_validate(&addr_str)?; OWNER.save(deps.storage, &validated)?; @@ -96,7 +103,10 @@ pub fn dispatch_actions( &mut received_coins, )?; } - Action::Placeholder { .. } => callbacks.push(CallbackMsg::Placeholder {}), + Action::Borrow(asset) => callbacks.push(CallbackMsg::Borrow { + token_id: token_id.to_string(), + asset: asset.check(deps.api, None)?, + }), } } @@ -117,7 +127,7 @@ pub fn dispatch_actions( } pub fn execute_callback( - _deps: DepsMut, + deps: DepsMut, info: MessageInfo, env: Env, callback: CallbackMsg, @@ -126,7 +136,7 @@ pub fn execute_callback( return Err(ContractError::ExternalInvocation {}); } match callback { - CallbackMsg::Placeholder { .. } => Ok(Response::new()), + CallbackMsg::Borrow { asset, token_id } => borrow(deps, env, &token_id, asset), } } diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index da6708ad4..4098ce315 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,12 +1,14 @@ use cosmwasm_std::{DepsMut, StdResult}; use rover::msg::InstantiateMsg; -use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER}; +use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER, RED_BANK}; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { let owner = deps.api.addr_validate(&msg.owner)?; OWNER.save(deps.storage, &owner)?; + RED_BANK.save(deps.storage, &msg.red_bank.check(deps.api)?)?; + msg.allowed_vaults.iter().try_for_each(|vault| { ALLOWED_VAULTS.save(deps.storage, deps.api.addr_validate(vault)?, &true) })?; diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 13b3a6eee..82eccb816 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -1,7 +1,7 @@ pub mod contract; +pub mod borrow; pub mod deposit; -pub mod error; pub mod execute; pub mod instantiate; pub mod query; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 566b85a98..7511b5e79 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,11 +1,14 @@ use std::convert::TryFrom; -use cosmwasm_std::{Addr, Deps, Order, StdResult}; +use cosmwasm_std::{Addr, Deps, Order, StdResult, Uint128}; use cw_asset::{AssetInfo, AssetInfoKey, AssetInfoUnchecked, AssetUnchecked}; -use cw_storage_plus::Bound; -use rover::msg::query::{ConfigResponse, PositionResponse}; +use cw_storage_plus::{Bound, Map}; +use rover::msg::query::{ConfigResponse, PositionResponse, TotalDebtSharesResponse}; -use crate::state::{ACCOUNT_NFT, ALLOWED_ASSETS, ALLOWED_VAULTS, ASSETS, OWNER}; +use crate::state::{ + NftTokenId, ACCOUNT_NFT, ALLOWED_ASSETS, ALLOWED_VAULTS, ASSETS, DEBT_SHARES, OWNER, RED_BANK, + TOTAL_DEBT_SHARES, +}; const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; @@ -16,11 +19,24 @@ pub fn query_config(deps: Deps) -> StdResult { account_nft: ACCOUNT_NFT .may_load(deps.storage)? .map(|addr| addr.to_string()), + red_bank: RED_BANK.load(deps.storage)?.contract_addr.into(), }) } pub fn query_position(deps: Deps, token_id: &str) -> StdResult { - let res: StdResult> = ASSETS + Ok(PositionResponse { + token_id: token_id.to_string(), + assets: get_asset_list(deps, token_id, ASSETS)?, + debt_shares: get_asset_list(deps, token_id, DEBT_SHARES)?, + }) +} + +fn get_asset_list( + deps: Deps, + token_id: &str, + asset_amount_map: Map<(NftTokenId, AssetInfoKey), Uint128>, +) -> StdResult> { + asset_amount_map .prefix(token_id) .range(deps.storage, None, None, Order::Ascending) .into_iter() @@ -29,16 +45,11 @@ pub fn query_position(deps: Deps, token_id: &str) -> StdResult let info_unchecked = AssetInfoUnchecked::try_from(asset_info_key)?; Ok(AssetUnchecked::new(info_unchecked, amount.u128())) }) - .collect(); - - Ok(PositionResponse { - token_id: token_id.to_string(), - assets: res?, - }) + .collect() } /// NOTE: This implementation of the query function assumes the map `ALLOWED_VAULTS` only saves `true`. -/// If a vault is to be removed from the whitelist, the map must remove the correspoinding key, instead +/// If a vault is to be removed from the whitelist, the map must remove the corresponding key, instead /// of setting the value to `false`. pub fn query_allowed_vaults( deps: Deps, @@ -90,3 +101,14 @@ pub fn query_allowed_assets( .map(AssetInfoUnchecked::try_from) .collect() } + +pub fn query_total_debt_shares( + deps: Deps, + unchecked_asset_info: AssetInfoUnchecked, +) -> StdResult { + let asset_info = unchecked_asset_info.check(deps.api, None)?; + let total_debt = TOTAL_DEBT_SHARES.load(deps.storage, asset_info.clone().into())?; + Ok(TotalDebtSharesResponse(AssetUnchecked::new( + asset_info, total_debt, + ))) +} diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index fb490564a..068b1ab9d 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,13 +1,20 @@ use cosmwasm_std::{Addr, Uint128}; use cw_asset::AssetInfoKey; use cw_storage_plus::{Item, Map}; +use rover::adapters::RedBank; // Contract config pub const OWNER: Item = Item::new("owner"); pub const ACCOUNT_NFT: Item = Item::new("account_nft"); pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); +pub const RED_BANK: Item = Item::new("red_bank"); // Positions -type NftTokenId<'a> = &'a str; +pub type NftTokenId<'a> = &'a str; pub const ASSETS: Map<(NftTokenId, AssetInfoKey), Uint128> = Map::new("assets"); + +type Shares = Uint128; +pub const DEBT_SHARES: Map<(NftTokenId, AssetInfoKey), Shares> = Map::new("debt_shares"); +/// Used to calculate each user's share of the debt +pub const TOTAL_DEBT_SHARES: Map = Map::new("total_debt_shares"); diff --git a/contracts/credit-manager/tests/allow_list_query.rs b/contracts/credit-manager/tests/allow_list_query.rs index 4d3b209fc..36a8791a6 100644 --- a/contracts/credit-manager/tests/allow_list_query.rs +++ b/contracts/credit-manager/tests/allow_list_query.rs @@ -1,6 +1,7 @@ use cosmwasm_std::Addr; use cw_asset::AssetInfoUnchecked; use cw_multi_test::Executor; +use rover::adapters::RedBankBase; use rover::msg::{InstantiateMsg, QueryMsg}; @@ -53,6 +54,9 @@ fn test_pagination_on_allowed_vaults_query_works() { owner: owner.to_string(), allowed_vaults: allowed_vaults.clone(), allowed_assets: vec![], + red_bank: RedBankBase { + contract_addr: String::from("redbankaddr"), + }, }; let contract_addr = app @@ -195,6 +199,9 @@ fn test_pagination_on_allowed_assets_query_works() { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: allowed_assets.clone(), + red_bank: RedBankBase { + contract_addr: String::from("redbankaddr"), + }, }; let contract_addr = app diff --git a/contracts/credit-manager/tests/borrow.rs b/contracts/credit-manager/tests/borrow.rs new file mode 100644 index 000000000..cd9115a61 --- /dev/null +++ b/contracts/credit-manager/tests/borrow.rs @@ -0,0 +1,415 @@ +use std::ops::{Mul, Sub}; + +use cosmwasm_std::{Addr, Coin, Uint128}; +use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg}; +use cw20_base::msg::QueryMsg::Balance; +use cw_asset::{AssetInfoUnchecked, AssetUnchecked}; +use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; + +use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED; +use mock_red_bank::msg::QueryMsg::UserAssetDebt; +use mock_red_bank::msg::UserAssetDebtResponse; +use rover::error::ContractError; +use rover::msg::execute::Action::Borrow; +use rover::msg::query::TotalDebtSharesResponse; +use rover::msg::ExecuteMsg::UpdateCreditAccount; +use rover::msg::QueryMsg; + +use crate::helpers::{ + assert_err, deploy_mock_cw20, get_token_id, mock_app, mock_create_credit_account, query_config, + query_position, setup_credit_manager, +}; + +pub mod helpers; + +#[test] +fn test_only_token_owner_can_borrow() { + let mut app = mock_app(); + let owner = Addr::unchecked("owner"); + let info = AssetInfoUnchecked::native("uosmo"); + let asset = AssetUnchecked::new(info.clone(), Uint128::zero()); + + let contract_addr = setup_credit_manager(&mut app, &owner, vec![info.clone()]); + let res = + mock_create_credit_account(&mut app, &contract_addr, &Addr::unchecked("user")).unwrap(); + let token_id = get_token_id(res); + + let another_user = Addr::unchecked("another_user"); + let res = app.execute_contract( + another_user.clone(), + contract_addr.clone(), + &UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(asset)], + }, + &[], + ); + + assert_err( + res, + ContractError::NotTokenOwner { + user: another_user.into(), + token_id, + }, + ) +} + +#[test] +fn test_can_only_borrow_what_is_whitelisted() { + let mut app = mock_app(); + let owner = Addr::unchecked("owner"); + let info = AssetInfoUnchecked::native("uosmo"); + + let contract_addr = setup_credit_manager(&mut app, &owner, vec![info.clone()]); + let user = Addr::unchecked("user"); + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + let res = app.execute_contract( + user.clone(), + contract_addr.clone(), + &UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(AssetUnchecked::new( + AssetInfoUnchecked::native("usomething"), + Uint128::from(234u128), + ))], + }, + &[], + ); + + assert_err( + res, + ContractError::NotWhitelisted(String::from("native:usomething")), + ) +} + +#[test] +fn test_borrowing_zero_does_nothing() { + let mut app = mock_app(); + let info = AssetInfoUnchecked::native("uosmo"); + + let contract_addr = + setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); + let user = Addr::unchecked("user"); + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + let res = app.execute_contract( + user.clone(), + contract_addr.clone(), + &UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(AssetUnchecked::new(info, Uint128::zero()))], + }, + &[], + ); + + assert_err(res, ContractError::NoAmount {}); + + let position = query_position(&mut app, &contract_addr, &token_id); + assert_eq!(position.assets.len(), 0); + assert_eq!(position.debt_shares.len(), 0); +} + +#[test] +fn test_success_when_new_debt_asset() { + let user = Addr::unchecked("user"); + let funds = Coin::new(300u128, "uosmo"); + let info = AssetInfoUnchecked::native("uosmo"); + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user, vec![funds]) + .unwrap(); + }); + + let contract_addr = + setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + let config = query_config(&mut app, &contract_addr.clone()); + + fund_red_bank_native( + &mut app, + config.red_bank.clone(), + vec![Coin::new(1000u128, "uosmo")], + ); + + let position = query_position(&mut app, &contract_addr, &token_id); + assert_eq!(position.assets.len(), 0); + assert_eq!(position.debt_shares.len(), 0); + + app.execute_contract( + user, + contract_addr.clone(), + &UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(AssetUnchecked::new( + info.clone(), + Uint128::from(42u128), + ))], + }, + &[], + ) + .unwrap(); + + let position = query_position(&mut app, &contract_addr, &token_id); + assert_eq!(position.assets.len(), 1); + assert_eq!( + position.assets.first().unwrap().amount, + Uint128::from(42u128) + ); + assert_eq!(position.assets.first().unwrap().info, info); + assert_eq!(position.debt_shares.len(), 1); + assert_eq!( + position.debt_shares.first().unwrap().amount, + Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED) + ); + assert_eq!(position.debt_shares.first().unwrap().info, info); + + let coin = app + .wrap() + .query_balance(contract_addr.clone(), "uosmo") + .unwrap(); + assert_eq!(coin.amount, Uint128::from(42u128)); + + let coin = app.wrap().query_balance(config.red_bank, "uosmo").unwrap(); + assert_eq!( + coin.amount, + Uint128::from(1000u128).sub(Uint128::from(42u128)) + ); + + let res: TotalDebtSharesResponse = app + .wrap() + .query_wasm_smart(contract_addr, &QueryMsg::TotalDebtShares(info)) + .unwrap(); + assert_eq!( + res.0.amount, + Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED) + ); +} + +#[test] +fn test_debt_shares_with_debt_amount() { + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let info = AssetInfoUnchecked::native("uosmo"); + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user_a, vec![Coin::new(300u128, "uosmo")]) + .unwrap(); + router + .bank + .init_balance(storage, &user_b, vec![Coin::new(450u128, "uosmo")]) + .unwrap(); + }); + + let contract_addr = + setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); + let res = mock_create_credit_account(&mut app, &contract_addr, &user_a).unwrap(); + let token_id_a = get_token_id(res); + let res = mock_create_credit_account(&mut app, &contract_addr, &user_b).unwrap(); + let token_id_b = get_token_id(res); + + let config = query_config(&mut app, &contract_addr.clone()); + + fund_red_bank_native( + &mut app, + config.red_bank.clone(), + vec![Coin::new(1000u128, "uosmo")], + ); + + app.execute_contract( + user_a, + contract_addr.clone(), + &UpdateCreditAccount { + token_id: token_id_a.clone(), + actions: vec![Borrow(AssetUnchecked::new( + info.clone(), + Uint128::from(50u128), + ))], + }, + &[], + ) + .unwrap(); + + let interim_red_bank_debt: UserAssetDebtResponse = app + .wrap() + .query_wasm_smart( + config.red_bank, + &UserAssetDebt { + user_address: contract_addr.clone().into(), + asset: info.clone(), + }, + ) + .unwrap(); + + app.execute_contract( + user_b, + contract_addr.clone(), + &UpdateCreditAccount { + token_id: token_id_b.clone(), + actions: vec![Borrow(AssetUnchecked::new( + info.clone(), + Uint128::from(50u128), + ))], + }, + &[], + ) + .unwrap(); + + let token_a_shares = Uint128::from(50u128).mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED); + let position = query_position(&mut app, &contract_addr, &token_id_a); + assert_eq!( + position.debt_shares.first().unwrap().amount, + token_a_shares.clone() + ); + + let token_b_shares = Uint128::from(50u128) + .mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED) + .multiply_ratio(Uint128::from(50u128), interim_red_bank_debt.amount); + + let position = query_position(&mut app, &contract_addr, &token_id_b); + assert_eq!( + position.debt_shares.first().unwrap().amount, + token_b_shares.clone() + ); + + let res: TotalDebtSharesResponse = app + .wrap() + .query_wasm_smart(contract_addr, &QueryMsg::TotalDebtShares(info)) + .unwrap(); + assert_eq!(res.0.amount, token_a_shares + token_b_shares); +} + +#[test] +fn test_can_borrow_cw20() { + let owner = Addr::unchecked("owner"); + let user = Addr::unchecked("user"); + let mut app = mock_app(); + + let cw20_contract = deploy_mock_cw20( + &mut app, + "jakecoin", + vec![Cw20Coin { + address: owner.clone().into(), + amount: Uint128::from(1000u128), + }], + ); + let cw20_info = AssetInfoUnchecked::cw20(cw20_contract.clone()); + + let contract_addr = setup_credit_manager(&mut app, &owner, vec![cw20_info.clone()]); + let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + let config = query_config(&mut app, &contract_addr.clone()); + fund_red_bank_cw20( + &mut app, + owner, + config.red_bank.clone(), + cw20_contract.clone(), + Uint128::from(1000u128), + ); + + app.execute_contract( + user, + contract_addr.clone(), + &UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(AssetUnchecked::new( + cw20_info.clone(), + Uint128::from(42u128), + ))], + }, + &[], + ) + .unwrap(); + + let position = query_position(&mut app, &contract_addr, &token_id); + assert_eq!(position.assets.len(), 1); + assert_eq!( + position.assets.first().unwrap().amount, + Uint128::from(42u128) + ); + assert_eq!(position.assets.first().unwrap().info, cw20_info); + assert_eq!(position.debt_shares.len(), 1); + assert_eq!( + position.debt_shares.first().unwrap().amount, + Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED) + ); + assert_eq!(position.debt_shares.first().unwrap().info, cw20_info); + + let balance = query_cw20_balance( + &mut app, + cw20_contract.clone(), + contract_addr.clone().into(), + ); + assert_eq!(balance, Uint128::from(42u128)); + + let balance = query_cw20_balance(&mut app, cw20_contract, config.red_bank); + assert_eq!(balance, Uint128::from(1000u128).sub(Uint128::from(42u128))); + + let res: TotalDebtSharesResponse = app + .wrap() + .query_wasm_smart(contract_addr, &QueryMsg::TotalDebtShares(cw20_info)) + .unwrap(); + assert_eq!( + res.0.amount, + Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED) + ); +} + +// TODO: After health check implemented +#[test] +fn test_cannot_borrow_more_than_healthy() {} + +fn fund_red_bank_native(app: &mut BasicApp, red_bank_addr: String, funds: Vec) { + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: red_bank_addr, + amount: funds, + })) + .unwrap(); +} + +fn fund_red_bank_cw20( + app: &mut BasicApp, + sender: Addr, + red_bank_addr: String, + cw20_contract: Addr, + amount: Uint128, +) -> AppResponse { + let res = app + .execute_contract( + sender, + cw20_contract.clone(), + &Cw20ExecuteMsg::Transfer { + recipient: red_bank_addr.clone(), + amount, + }, + &[], + ) + .unwrap(); + let balance = query_cw20_balance(app, cw20_contract, red_bank_addr); + assert_eq!(balance, amount); + res +} + +fn query_cw20_balance( + app: &mut BasicApp, + cw20_contract: Addr, + address_to_query: String, +) -> Uint128 { + let res: BalanceResponse = app + .wrap() + .query_wasm_smart( + cw20_contract.clone(), + &Balance { + address: address_to_query, + }, + ) + .unwrap(); + res.balance +} diff --git a/contracts/credit-manager/tests/create_credit_account.rs b/contracts/credit-manager/tests/create_credit_account.rs index 2c372a17b..1710da899 100644 --- a/contracts/credit-manager/tests/create_credit_account.rs +++ b/contracts/credit-manager/tests/create_credit_account.rs @@ -3,6 +3,7 @@ use cw721::OwnerOfResponse; use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw721_base::QueryMsg as NftQueryMsg; use cw_multi_test::Executor; +use rover::adapters::RedBankBase; use rover::msg::query::ConfigResponse; use rover::msg::ExecuteMsg::UpdateConfig; use rover::msg::{InstantiateMsg, QueryMsg}; @@ -41,6 +42,9 @@ fn test_create_credit_account() { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![], + red_bank: RedBankBase { + contract_addr: String::from("redbankaddr"), + }, }; let manager_contract_addr = app @@ -67,6 +71,7 @@ fn test_create_credit_account() { &UpdateConfig { account_nft: Some(nft_contract_addr.to_string()), owner: None, + red_bank: None, }, &[], ); diff --git a/contracts/credit-manager/tests/deposit_cw20.rs b/contracts/credit-manager/tests/deposit_cw20.rs index 5a41942c4..0ef11616a 100644 --- a/contracts/credit-manager/tests/deposit_cw20.rs +++ b/contracts/credit-manager/tests/deposit_cw20.rs @@ -5,12 +5,12 @@ use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, Cw20QueryMsg}; use cw_asset::{AssetInfo, AssetInfoUnchecked}; use cw_multi_test::Executor; -use credit_manager::error::ContractError::{NotTokenOwner, NotWhitelisted}; +use rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; use rover::msg::execute::ReceiveMsg; use crate::helpers::{ - assert_err, deploy_mock_cw20, get_position, get_token_id, mock_app, mock_create_credit_account, - setup_credit_manager, + assert_err, deploy_mock_cw20, get_token_id, mock_app, mock_create_credit_account, + query_position, setup_credit_manager, }; pub mod helpers; @@ -61,7 +61,7 @@ fn test_only_token_owner_can_deposit() { }, ); - let res = get_position(&app, &manager_contract, &token_id); + let res = query_position(&app, &manager_contract, &token_id); assert_eq!(res.assets.len(), 0); } @@ -115,7 +115,7 @@ fn test_can_only_deposit_allowed_assets() { NotWhitelisted(AssetInfo::Cw20(cw20_contract_a).to_string()), ); - let res = get_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &contract_addr, &token_id); assert_eq!(res.assets.len(), 0); } @@ -157,7 +157,7 @@ fn test_cw20_deposit_success() { ) .unwrap(); - let res = get_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &contract_addr, &token_id); assert_eq!(res.assets.len(), 1); assert_eq!(res.assets.first().unwrap().amount, amount); assert_eq!(res.assets.first().unwrap().info, asset_info); diff --git a/contracts/credit-manager/tests/deposit_native.rs b/contracts/credit-manager/tests/deposit_native.rs index 9cba87b14..5d0776512 100644 --- a/contracts/credit-manager/tests/deposit_native.rs +++ b/contracts/credit-manager/tests/deposit_native.rs @@ -1,19 +1,19 @@ extern crate core; use cosmwasm_std::{Addr, Coin, Uint128}; -use credit_manager::error::ContractError::{ - ExtraFundsReceived, FundsMismatch, NotTokenOwner, NotWhitelisted, -}; use cw20::Cw20Coin; use cw_asset::{AssetInfo, AssetInfoUnchecked, AssetList, AssetUnchecked}; use cw_multi_test::{App, Executor}; +use rover::error::ContractError::{ + ExtraFundsReceived, FundsMismatch, NotTokenOwner, NotWhitelisted, +}; use rover::msg::execute::Action; use rover::msg::ExecuteMsg; use crate::helpers::{ - assert_err, deploy_mock_cw20, get_position, get_token_id, mock_app, mock_create_credit_account, - setup_credit_manager, + assert_err, deploy_mock_cw20, get_token_id, mock_app, mock_create_credit_account, + query_position, setup_credit_manager, }; pub mod helpers; @@ -63,7 +63,7 @@ fn test_deposit_nothing() { let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); let token_id = get_token_id(res); - let res = get_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &contract_addr, &token_id); assert_eq!(res.assets.len(), 0); app.execute_contract( @@ -77,7 +77,7 @@ fn test_deposit_nothing() { ) .unwrap(); - let res = get_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &contract_addr, &token_id); assert_eq!(res.assets.len(), 0); } @@ -111,7 +111,7 @@ fn test_deposit_but_no_funds() { }, ); - let res = get_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &contract_addr, &token_id); assert_eq!(res.assets.len(), 0); } @@ -201,7 +201,7 @@ fn test_can_only_deposit_allowed_assets() { assert_err(res, NotWhitelisted(AssetInfo::native("uosmo").to_string())); - let res = get_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &contract_addr, &token_id); assert_eq!(res.assets.len(), 0); } @@ -243,7 +243,7 @@ fn test_extra_funds_received() { assert_err(res, ExtraFundsReceived(AssetList::from(vec![extra_funds]))); - let res = get_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &contract_addr, &token_id); assert_eq!(res.assets.len(), 0); } @@ -278,7 +278,7 @@ fn test_native_deposit_success() { ) .unwrap(); - let res = get_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &contract_addr, &token_id); assert_eq!(res.assets.len(), 1); assert_eq!(res.assets.first().unwrap().amount, amount); assert_eq!(res.assets.first().unwrap().info, info); @@ -327,7 +327,7 @@ fn test_multiple_deposit_actions() { ) .unwrap(); - let res = get_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &contract_addr, &token_id); assert_eq!(res.assets.len(), 2); let coin = app diff --git a/contracts/credit-manager/tests/dispatch.rs b/contracts/credit-manager/tests/dispatch.rs index 66bc14fca..bb8429a08 100644 --- a/contracts/credit-manager/tests/dispatch.rs +++ b/contracts/credit-manager/tests/dispatch.rs @@ -1,12 +1,12 @@ extern crate core; use crate::helpers::{ - assert_err, get_position, get_token_id, mock_app, mock_create_credit_account, + assert_err, get_token_id, mock_app, mock_create_credit_account, query_position, setup_credit_manager, }; use cosmwasm_std::Addr; -use credit_manager::error::ContractError::NotTokenOwner; use cw_multi_test::Executor; +use rover::error::ContractError::NotTokenOwner; use rover::msg::ExecuteMsg::UpdateCreditAccount; pub mod helpers; @@ -51,7 +51,7 @@ fn test_nothing_happens_if_no_actions_are_passed() { let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); let token_id = get_token_id(res); - let res = get_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &contract_addr, &token_id); assert_eq!(res.assets.len(), 0); app.execute_contract( @@ -65,6 +65,6 @@ fn test_nothing_happens_if_no_actions_are_passed() { ) .unwrap(); - let res = get_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &contract_addr, &token_id); assert_eq!(res.assets.len(), 0); } diff --git a/contracts/credit-manager/tests/helpers.rs b/contracts/credit-manager/tests/helpers.rs index a7a6a9304..976b1f49c 100644 --- a/contracts/credit-manager/tests/helpers.rs +++ b/contracts/credit-manager/tests/helpers.rs @@ -14,9 +14,13 @@ use account_nft::contract::{ }; use account_nft::msg::ExecuteMsg as NftExecuteMsg; use credit_manager::contract::{execute, instantiate, query}; -use credit_manager::error::ContractError; +use mock_red_bank::contract::{ + execute as redBankExecute, instantiate as redBankInstantiate, query as redBankQuery, +}; +use rover::adapters::RedBankBase; +use rover::error::ContractError; use rover::msg::execute::ExecuteMsg::{CreateCreditAccount, UpdateConfig}; -use rover::msg::query::{PositionResponse, QueryMsg}; +use rover::msg::query::{ConfigResponse, PositionResponse, QueryMsg}; use rover::msg::InstantiateMsg; pub fn mock_app() -> App { @@ -38,6 +42,11 @@ pub fn mock_cw20_contract() -> Box> { Box::new(contract) } +pub fn mock_red_bank_contract() -> Box> { + let contract = ContractWrapper::new(redBankExecute, redBankInstantiate, redBankQuery); + Box::new(contract) +} + pub fn mock_create_credit_account( app: &mut App, manager_contract_addr: &Addr, @@ -89,12 +98,26 @@ pub fn transfer_nft_contract_ownership( &UpdateConfig { account_nft: Some(nft_contract_addr.to_string()), owner: None, + red_bank: None, }, &[], ) .unwrap(); } +pub fn setup_red_bank(app: &mut App) -> Addr { + let contract_code_id = app.store_code(mock_red_bank_contract()); + app.instantiate_contract( + contract_code_id, + Addr::unchecked("red_bank_contract_owner"), + &Empty {}, + &[], + "mock-red-bank", + None, + ) + .unwrap() +} + pub fn setup_nft_contract(app: &mut App, owner: &Addr, manager_contract_addr: &Addr) -> Addr { let nft_contract_code_id = app.store_code(mock_account_nft_contract()); let nft_contract_addr = app @@ -122,10 +145,14 @@ pub fn setup_credit_manager( allowed_assets: Vec, ) -> Addr { let credit_manager_code_id = app.store_code(mock_contract()); + let red_bank_addr = setup_red_bank(app); let manager_initiate_msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets, + red_bank: RedBankBase { + contract_addr: red_bank_addr.to_string(), + }, }; let manager_contract_addr = app @@ -156,7 +183,7 @@ pub fn get_token_id(res: AppResponse) -> String { attr.first().unwrap().to_string() } -pub fn get_position( +pub fn query_position( app: &App, manager_contract_addr: &Addr, token_id: &String, @@ -180,3 +207,9 @@ pub fn assert_err(res: AnyResult, err: ContractError) { } } } + +pub fn query_config(app: &mut App, contract_addr: &Addr) -> ConfigResponse { + app.wrap() + .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) + .unwrap() +} diff --git a/contracts/credit-manager/tests/instantiate.rs b/contracts/credit-manager/tests/instantiate.rs index 9a2bc612f..b690fee09 100644 --- a/contracts/credit-manager/tests/instantiate.rs +++ b/contracts/credit-manager/tests/instantiate.rs @@ -1,6 +1,7 @@ use cosmwasm_std::Addr; use cw_asset::AssetInfoUnchecked; use cw_multi_test::Executor; +use rover::adapters::RedBankBase; use rover::msg::query::{ConfigResponse, QueryMsg}; use rover::msg::InstantiateMsg; @@ -19,6 +20,9 @@ fn test_owner_set_on_instantiate() { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![], + red_bank: RedBankBase { + contract_addr: String::from("redbankaddr"), + }, }; let contract_addr = app @@ -47,6 +51,9 @@ fn test_nft_contract_addr_not_set_on_instantiate() { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![], + red_bank: RedBankBase { + contract_addr: String::from("redbankaddr"), + }, }, &[], "manager-mock-account-nft", @@ -85,6 +92,9 @@ fn test_allowed_vaults_and_assets_stored_on_instantiate() { owner: owner.to_string(), allowed_vaults: allowed_vaults.clone(), allowed_assets: allowed_assets.clone(), + red_bank: RedBankBase { + contract_addr: String::from("redbankaddr"), + }, }; let contract_addr = app @@ -127,6 +137,34 @@ fn test_allowed_vaults_and_assets_stored_on_instantiate() { assert_eq!(allowed_vaults, vaults_res); } +#[test] +fn test_red_bank_set_on_instantiate() { + let mut app = mock_app(); + let code_id = app.store_code(mock_contract()); + let owner = Addr::unchecked("owner"); + let red_bank_addr = String::from("redbankaddr"); + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_assets: vec![], + red_bank: RedBankBase { + contract_addr: String::from("redbankaddr"), + }, + }; + + let contract_addr = app + .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-account-nft", None) + .unwrap(); + + let res: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) + .unwrap(); + + assert_eq!(red_bank_addr, res.red_bank); +} + #[test] fn test_panics_on_invalid_instantiation_addrs() { let mut app = mock_app(); @@ -137,6 +175,31 @@ fn test_panics_on_invalid_instantiation_addrs() { owner: owner.to_string(), allowed_vaults: vec!["%%%INVALID%%%".to_string()], allowed_assets: vec![], + red_bank: RedBankBase { + contract_addr: String::from("redbankaddr"), + }, + }; + + let instantiate_res = app.instantiate_contract( + manager_code_id, + owner.clone(), + &msg, + &[], + "mock-contract", + None, + ); + + if instantiate_res.is_ok() { + panic!("Should have thrown an error"); + } + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_assets: vec![AssetInfoUnchecked::Cw20(String::from("AA"))], // Because cw-asset lowercases before passing to validate, in the test env, two letter strings is only one that triggers a fail + red_bank: RedBankBase { + contract_addr: String::from("redbankaddr"), + }, }; let instantiate_res = app.instantiate_contract( @@ -155,7 +218,10 @@ fn test_panics_on_invalid_instantiation_addrs() { let msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], - allowed_assets: vec![AssetInfoUnchecked::Cw20("AA".to_string())], // Because cw-asset lowercases before passing to validate, in the test env, two letter strings is only one that triggers a fail + allowed_assets: vec![], // Because cw-asset lowercases before passing to validate, in the test env, two letter strings is only one that triggers a fail + red_bank: RedBankBase { + contract_addr: String::from("%%%INVALID%%%"), + }, }; let instantiate_res = app.instantiate_contract( diff --git a/contracts/credit-manager/tests/update_config.rs b/contracts/credit-manager/tests/update_config.rs index 4e2578feb..92acb858e 100644 --- a/contracts/credit-manager/tests/update_config.rs +++ b/contracts/credit-manager/tests/update_config.rs @@ -3,10 +3,10 @@ use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, Executor}; use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use rover::msg::query::ConfigResponse; -use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use rover::adapters::{RedBankBase, RedBankUnchecked}; +use rover::msg::{ExecuteMsg, InstantiateMsg}; -use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract}; +use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract, query_config}; pub mod helpers; @@ -23,8 +23,10 @@ fn test_update_config_works_with_full_config() { assert_eq!(config_res.owner, original_owner.to_string()); let new_owner = Addr::unchecked("new_owner"); - let nft_contract_addr = setup_nft_and_propose_owner(&mut app, &original_owner, &contract_addr); + let new_red_bank_addr = RedBankUnchecked { + contract_addr: String::from("new_red_bank_addr"), + }; app.execute_contract( original_owner.clone(), @@ -32,6 +34,7 @@ fn test_update_config_works_with_full_config() { &ExecuteMsg::UpdateConfig { account_nft: Some(nft_contract_addr.to_string()), owner: Some(new_owner.to_string()), + red_bank: Some(new_red_bank_addr.clone()), }, &[], ) @@ -41,6 +44,7 @@ fn test_update_config_works_with_full_config() { assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); assert_eq!(config_res.owner, new_owner.to_string()); + assert_eq!(config_res.red_bank, new_red_bank_addr.contract_addr); } #[test] @@ -54,6 +58,7 @@ fn test_update_config_works_with_some_config() { assert_eq!(config_res.account_nft, None); assert_eq!(config_res.owner, original_owner.to_string()); + assert_eq!(config_res.red_bank, String::from("initial_red_bank")); let nft_contract_addr = setup_nft_and_propose_owner(&mut app, &original_owner, &contract_addr); app.execute_contract( @@ -62,6 +67,7 @@ fn test_update_config_works_with_some_config() { &ExecuteMsg::UpdateConfig { account_nft: Some(nft_contract_addr.to_string()), owner: None, + red_bank: None, }, &[], ) @@ -71,6 +77,7 @@ fn test_update_config_works_with_some_config() { assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); assert_eq!(config_res.owner, original_owner.to_string()); + assert_eq!(config_res.red_bank, String::from("initial_red_bank")); let new_owner = Addr::unchecked("new_owner"); app.execute_contract( @@ -79,6 +86,27 @@ fn test_update_config_works_with_some_config() { &ExecuteMsg::UpdateConfig { account_nft: None, owner: Some(new_owner.to_string()), + red_bank: None, + }, + &[], + ) + .unwrap(); + + let config_res = query_config(&mut app, &contract_addr.clone()); + assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); + assert_eq!(config_res.owner, new_owner.to_string()); + assert_eq!(config_res.red_bank, String::from("initial_red_bank")); + + let new_red_bank = RedBankUnchecked { + contract_addr: String::from("new_red_bank_addr"), + }; + app.execute_contract( + new_owner.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateConfig { + account_nft: None, + owner: None, + red_bank: Some(new_red_bank.clone()), }, &[], ) @@ -87,6 +115,7 @@ fn test_update_config_works_with_some_config() { let config_res = query_config(&mut app, &contract_addr.clone()); assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); assert_eq!(config_res.owner, new_owner.to_string()); + assert_eq!(config_res.red_bank, new_red_bank.contract_addr); } #[test] @@ -102,6 +131,7 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { &ExecuteMsg::UpdateConfig { account_nft: None, owner: None, + red_bank: None, }, &[], ) @@ -113,12 +143,6 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { assert_eq!(config_res.owner, original_owner.to_string()); } -fn query_config(app: &mut App, contract_addr: &Addr) -> ConfigResponse { - app.wrap() - .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) - .unwrap() -} - fn instantiate(app: &mut App, original_owner: &Addr, code_id: u64) -> Addr { app.instantiate_contract( code_id, @@ -127,6 +151,9 @@ fn instantiate(app: &mut App, original_owner: &Addr, code_id: u64) -> Addr { owner: original_owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![], + red_bank: RedBankBase { + contract_addr: String::from("initial_red_bank"), + }, }, &[], "mock_manager_contract", diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml new file mode 100644 index 000000000..fddf8942f --- /dev/null +++ b/contracts/mock-red-bank/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "mock-red-bank" +version = "1.0.0" +authors = ["larry_0x , grod220 "] +edition = "2018" +license = "GPL-3.0-or-later" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +cosmwasm-std = "1.0" +cw20 = "0.13" +cw-asset = "2.1" +cw-storage-plus = "0.13" +schemars = "0.8" +serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs new file mode 100644 index 000000000..f7b933fdb --- /dev/null +++ b/contracts/mock-red-bank/src/contract.rs @@ -0,0 +1,43 @@ +#![allow(unused_imports)] +use cosmwasm_std::{ + entry_point, to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +}; + +use crate::execute::execute_borrow; +use crate::msg::{ExecuteMsg, QueryMsg}; +use crate::query::query_debt; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: Empty, +) -> StdResult { + Ok(Response::default()) // do nothing +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + match msg { + ExecuteMsg::Borrow { + asset, + recipient: _recipient, + } => execute_borrow(deps, info, asset), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::UserAssetDebt { + user_address, + asset, + } => to_binary(&query_debt(deps, env, user_address, asset)?), + } +} diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs new file mode 100644 index 000000000..259b34bd7 --- /dev/null +++ b/contracts/mock-red-bank/src/execute.rs @@ -0,0 +1,17 @@ +use cosmwasm_std::{DepsMut, MessageInfo, Response, StdResult, Uint128}; +use cw_asset::Asset; + +use crate::helpers::load_debt_amount; +use crate::state::DEBT_AMOUNT; + +pub fn execute_borrow(deps: DepsMut, info: MessageInfo, asset: Asset) -> StdResult { + let debt_amount = load_debt_amount(deps.storage, &info.sender, &asset.info); + + DEBT_AMOUNT.save( + deps.storage, + (info.sender.clone(), asset.info.clone().into()), + &(debt_amount + asset.amount + Uint128::from(1u128)), // The extra unit is simulated accrued interest + )?; + + Ok(Response::new().add_message(asset.transfer_msg(&info.sender)?)) +} diff --git a/contracts/mock-red-bank/src/helpers.rs b/contracts/mock-red-bank/src/helpers.rs new file mode 100644 index 000000000..a5746d552 --- /dev/null +++ b/contracts/mock-red-bank/src/helpers.rs @@ -0,0 +1,10 @@ +use cosmwasm_std::{Addr, Storage, Uint128}; +use cw_asset::AssetInfo; + +use crate::state::DEBT_AMOUNT; + +pub fn load_debt_amount(storage: &dyn Storage, user: &Addr, asset: &AssetInfo) -> Uint128 { + DEBT_AMOUNT + .load(storage, (user.clone(), asset.into())) + .unwrap_or(Uint128::zero()) +} diff --git a/contracts/mock-red-bank/src/lib.rs b/contracts/mock-red-bank/src/lib.rs new file mode 100644 index 000000000..5e9df43e4 --- /dev/null +++ b/contracts/mock-red-bank/src/lib.rs @@ -0,0 +1,7 @@ +pub mod contract; + +pub mod execute; +pub mod helpers; +pub mod msg; +pub mod query; +pub mod state; diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs new file mode 100644 index 000000000..33ec4fae8 --- /dev/null +++ b/contracts/mock-red-bank/src/msg.rs @@ -0,0 +1,32 @@ +use cosmwasm_std::Uint128; +use cw_asset::{Asset, AssetInfoUnchecked}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + Borrow { + asset: Asset, + recipient: Option, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + UserAssetDebt { + user_address: String, + asset: AssetInfoUnchecked, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct UserAssetDebtResponse { + pub denom: String, + pub asset_label: String, + pub asset_reference: Vec, + pub asset_info: AssetInfoUnchecked, + pub amount_scaled: Uint128, + pub amount: Uint128, +} diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs new file mode 100644 index 000000000..0ca675cb4 --- /dev/null +++ b/contracts/mock-red-bank/src/query.rs @@ -0,0 +1,26 @@ +use cosmwasm_std::{Deps, Env, StdResult, Uint128}; +use cw_asset::AssetInfoUnchecked; + +use crate::helpers::load_debt_amount; +use crate::msg::UserAssetDebtResponse; + +pub fn query_debt( + deps: Deps, + _env: Env, + user_address: String, + asset_info_unchecked: AssetInfoUnchecked, +) -> StdResult { + let user_addr = deps.api.addr_validate(&user_address)?; + let asset_info = asset_info_unchecked.check(deps.api, None)?; + let debt_amount = load_debt_amount(deps.storage, &user_addr, &asset_info); + Ok(UserAssetDebtResponse { + // only amount matters for our testing + amount: debt_amount, + // for other attributes we fill in some random value + denom: "".to_string(), + asset_label: asset_info.to_string(), + asset_reference: vec![], + asset_info: asset_info.into(), + amount_scaled: Uint128::zero(), + }) +} diff --git a/contracts/mock-red-bank/src/state.rs b/contracts/mock-red-bank/src/state.rs new file mode 100644 index 000000000..f38e85a98 --- /dev/null +++ b/contracts/mock-red-bank/src/state.rs @@ -0,0 +1,5 @@ +use cosmwasm_std::{Addr, Uint128}; +use cw_asset::AssetInfoKey; +use cw_storage_plus::Map; + +pub const DEBT_AMOUNT: Map<(Addr, AssetInfoKey), Uint128> = Map::new("debt_amount"); diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index a15f89672..d235a939a 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -8,9 +8,12 @@ edition = "2018" doctest = false [dependencies] +mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } + cosmwasm-std = "1.0" cw20 = "0.13" cw-asset = "2.1" cw-storage-plus = "0.13" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } +thiserror = "1.0" diff --git a/packages/rover/src/adapters/mod.rs b/packages/rover/src/adapters/mod.rs new file mode 100644 index 000000000..2201ea23e --- /dev/null +++ b/packages/rover/src/adapters/mod.rs @@ -0,0 +1,3 @@ +mod red_bank; + +pub use self::red_bank::*; diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs new file mode 100644 index 000000000..050ad391d --- /dev/null +++ b/packages/rover/src/adapters/red_bank.rs @@ -0,0 +1,64 @@ +use cosmwasm_std::{ + to_binary, Addr, Api, CosmosMsg, QuerierWrapper, QueryRequest, StdResult, Uint128, WasmMsg, + WasmQuery, +}; +use cw_asset::{Asset, AssetInfo}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use mock_red_bank::msg::{ExecuteMsg, QueryMsg, UserAssetDebtResponse}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct RedBankBase { + pub contract_addr: T, +} + +pub type RedBankUnchecked = RedBankBase; +pub type RedBank = RedBankBase; + +impl From for RedBankUnchecked { + fn from(red_bank: RedBank) -> Self { + RedBankUnchecked { + contract_addr: red_bank.contract_addr.to_string(), + } + } +} + +impl RedBankUnchecked { + pub fn check(&self, api: &dyn Api) -> StdResult { + Ok(RedBank { + contract_addr: api.addr_validate(&self.contract_addr)?, + }) + } +} + +impl RedBank { + /// Generate message for borrowing a specified amount of asset + pub fn borrow_msg(&self, asset: &Asset) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.contract_addr.to_string(), + msg: to_binary(&ExecuteMsg::Borrow { + asset: asset.clone(), + recipient: None, + })?, + funds: vec![], + })) + } + + pub fn query_user_debt( + &self, + querier: &QuerierWrapper, + user_address: &Addr, + asset_info: &AssetInfo, + ) -> StdResult { + let response: UserAssetDebtResponse = + querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.contract_addr.to_string(), + msg: to_binary(&QueryMsg::UserAssetDebt { + user_address: user_address.to_string(), + asset: asset_info.clone().into(), + })?, + }))?; + Ok(response.amount) + } +} diff --git a/contracts/credit-manager/src/error.rs b/packages/rover/src/error.rs similarity index 76% rename from contracts/credit-manager/src/error.rs rename to packages/rover/src/error.rs index fc113d792..1fed94286 100644 --- a/contracts/credit-manager/src/error.rs +++ b/packages/rover/src/error.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, StdError, Uint128}; +use cosmwasm_std::{Addr, CheckedMultiplyRatioError, OverflowError, StdError, Uint128}; use cw_asset::AssetListBase; use thiserror::Error; @@ -7,6 +7,12 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), + #[error("{0}")] + Overflow(#[from] OverflowError), + + #[error("{0}")] + CheckedMultiply(#[from] CheckedMultiplyRatioError), + #[error("{user:?} is not authorized to {action:?}")] Unauthorized { user: String, action: String }, @@ -16,6 +22,9 @@ pub enum ContractError { #[error("Extra funds received: {0}")] ExtraFundsReceived(AssetListBase), + #[error("No asset amount set for action")] + NoAmount {}, + #[error("Deposits of CW20's should come via Cw20ExecuteMsg::Send to cw20 contract specifying Rover's ReceiveMsg")] WrongDepositMethodForCW20 {}, diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index d0e87a0d3..56549ee63 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -1 +1,3 @@ +pub mod adapters; +pub mod error; pub mod msg; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 70ede8603..7c4458480 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,6 +1,7 @@ +use crate::adapters::RedBankUnchecked; use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg}; use cw20::Cw20ReceiveMsg; -use cw_asset::AssetUnchecked; +use cw_asset::{Asset, AssetUnchecked}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -23,10 +24,11 @@ pub enum ExecuteMsg { //-------------------------------------------------------------------------------------------------- // Privileged messages //-------------------------------------------------------------------------------------------------- - /// Update owner or stored account nft contract + /// Update owner, stored account nft contract, or red bank contract addr UpdateConfig { account_nft: Option, owner: Option, + red_bank: Option, }, /// Internal actions only callable by the contract itself Callback(CallbackMsg), @@ -40,14 +42,17 @@ pub enum Action { /// NOTE: CW20 Deposits should use Cw20ExecuteMsg::Send {} NativeDeposit(AssetUnchecked), - Placeholder {}, + /// Borrow asset of specified amount from Red Bank + Borrow(AssetUnchecked), } /// Internal actions made by the contract with pre-validated inputs #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum CallbackMsg { - Placeholder {}, + /// Borrow specified amount of asset from Red Bank; + /// Increase the token's asset amount and debt shares; + Borrow { token_id: String, asset: Asset }, } impl CallbackMsg { diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index 6aa417c93..7bfc863d2 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -1,3 +1,4 @@ +use crate::adapters::RedBankUnchecked; use cw_asset::AssetInfoUnchecked; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -7,4 +8,5 @@ pub struct InstantiateMsg { pub owner: String, pub allowed_vaults: Vec, pub allowed_assets: Vec, + pub red_bank: RedBankUnchecked, } diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 6a93a5028..b51ae33ce 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -19,6 +19,9 @@ pub enum QueryMsg { }, /// The entire position represented by token. Response type: `PositionResponse` Position { token_id: String }, + + /// Total debt shares issued for Asset. Response type: `TotalDebtSharesResponse` + TotalDebtShares(AssetInfoUnchecked), } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -26,6 +29,7 @@ pub enum QueryMsg { pub struct PositionResponse { pub token_id: String, pub assets: Vec, + pub debt_shares: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -33,4 +37,9 @@ pub struct PositionResponse { pub struct ConfigResponse { pub owner: String, pub account_nft: Option, + pub red_bank: String, } + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct TotalDebtSharesResponse(pub AssetUnchecked); From 73a97a4ffc69e5cb2c170c4d90ce064245b2bf95 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 25 Jul 2022 09:29:48 +0200 Subject: [PATCH 040/218] Review updates --- contracts/credit-manager/src/query.rs | 2 +- .../credit-manager/tests/allow_list_query.rs | 10 ++---- .../tests/create_credit_account.rs | 4 +-- contracts/credit-manager/tests/helpers.rs | 4 +-- contracts/credit-manager/tests/instantiate.rs | 32 ++++++------------- .../credit-manager/tests/update_config.rs | 25 ++++++--------- contracts/mock-red-bank/src/execute.rs | 9 ++++-- contracts/mock-red-bank/src/msg.rs | 8 ++--- contracts/mock-red-bank/src/query.rs | 8 +---- packages/rover/src/adapters/red_bank.rs | 20 ++++-------- packages/rover/src/error.rs | 6 ++-- 11 files changed, 45 insertions(+), 83 deletions(-) diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 7511b5e79..dd4bf5a48 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -19,7 +19,7 @@ pub fn query_config(deps: Deps) -> StdResult { account_nft: ACCOUNT_NFT .may_load(deps.storage)? .map(|addr| addr.to_string()), - red_bank: RED_BANK.load(deps.storage)?.contract_addr.into(), + red_bank: RED_BANK.load(deps.storage)?.0.into(), }) } diff --git a/contracts/credit-manager/tests/allow_list_query.rs b/contracts/credit-manager/tests/allow_list_query.rs index 36a8791a6..77f861604 100644 --- a/contracts/credit-manager/tests/allow_list_query.rs +++ b/contracts/credit-manager/tests/allow_list_query.rs @@ -1,8 +1,8 @@ use cosmwasm_std::Addr; use cw_asset::AssetInfoUnchecked; use cw_multi_test::Executor; -use rover::adapters::RedBankBase; +use rover::adapters::RedBankBase; use rover::msg::{InstantiateMsg, QueryMsg}; use crate::helpers::{mock_app, mock_contract}; @@ -54,9 +54,7 @@ fn test_pagination_on_allowed_vaults_query_works() { owner: owner.to_string(), allowed_vaults: allowed_vaults.clone(), allowed_assets: vec![], - red_bank: RedBankBase { - contract_addr: String::from("redbankaddr"), - }, + red_bank: RedBankBase("redbankaddr".to_string()), }; let contract_addr = app @@ -199,9 +197,7 @@ fn test_pagination_on_allowed_assets_query_works() { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: allowed_assets.clone(), - red_bank: RedBankBase { - contract_addr: String::from("redbankaddr"), - }, + red_bank: RedBankBase("redbankaddr".to_string()), }; let contract_addr = app diff --git a/contracts/credit-manager/tests/create_credit_account.rs b/contracts/credit-manager/tests/create_credit_account.rs index 1710da899..0400f20b9 100644 --- a/contracts/credit-manager/tests/create_credit_account.rs +++ b/contracts/credit-manager/tests/create_credit_account.rs @@ -42,9 +42,7 @@ fn test_create_credit_account() { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![], - red_bank: RedBankBase { - contract_addr: String::from("redbankaddr"), - }, + red_bank: RedBankBase("redbankaddr".to_string()), }; let manager_contract_addr = app diff --git a/contracts/credit-manager/tests/helpers.rs b/contracts/credit-manager/tests/helpers.rs index 976b1f49c..092e1d098 100644 --- a/contracts/credit-manager/tests/helpers.rs +++ b/contracts/credit-manager/tests/helpers.rs @@ -150,9 +150,7 @@ pub fn setup_credit_manager( owner: owner.to_string(), allowed_vaults: vec![], allowed_assets, - red_bank: RedBankBase { - contract_addr: red_bank_addr.to_string(), - }, + red_bank: RedBankBase(red_bank_addr.to_string()), }; let manager_contract_addr = app diff --git a/contracts/credit-manager/tests/instantiate.rs b/contracts/credit-manager/tests/instantiate.rs index b690fee09..91d10848b 100644 --- a/contracts/credit-manager/tests/instantiate.rs +++ b/contracts/credit-manager/tests/instantiate.rs @@ -20,9 +20,7 @@ fn test_owner_set_on_instantiate() { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![], - red_bank: RedBankBase { - contract_addr: String::from("redbankaddr"), - }, + red_bank: RedBankBase("redbankaddr".to_string()), }; let contract_addr = app @@ -51,9 +49,7 @@ fn test_nft_contract_addr_not_set_on_instantiate() { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![], - red_bank: RedBankBase { - contract_addr: String::from("redbankaddr"), - }, + red_bank: RedBankBase("redbankaddr".to_string()), }, &[], "manager-mock-account-nft", @@ -92,9 +88,7 @@ fn test_allowed_vaults_and_assets_stored_on_instantiate() { owner: owner.to_string(), allowed_vaults: allowed_vaults.clone(), allowed_assets: allowed_assets.clone(), - red_bank: RedBankBase { - contract_addr: String::from("redbankaddr"), - }, + red_bank: RedBankBase("redbankaddr".to_string()), }; let contract_addr = app @@ -142,15 +136,13 @@ fn test_red_bank_set_on_instantiate() { let mut app = mock_app(); let code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); - let red_bank_addr = String::from("redbankaddr"); + let red_bank_addr = "redbankaddr".to_string(); let msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![], - red_bank: RedBankBase { - contract_addr: String::from("redbankaddr"), - }, + red_bank: RedBankBase("redbankaddr".to_string()), }; let contract_addr = app @@ -175,9 +167,7 @@ fn test_panics_on_invalid_instantiation_addrs() { owner: owner.to_string(), allowed_vaults: vec!["%%%INVALID%%%".to_string()], allowed_assets: vec![], - red_bank: RedBankBase { - contract_addr: String::from("redbankaddr"), - }, + red_bank: RedBankBase("redbankaddr".to_string()), }; let instantiate_res = app.instantiate_contract( @@ -197,9 +187,7 @@ fn test_panics_on_invalid_instantiation_addrs() { owner: owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![AssetInfoUnchecked::Cw20(String::from("AA"))], // Because cw-asset lowercases before passing to validate, in the test env, two letter strings is only one that triggers a fail - red_bank: RedBankBase { - contract_addr: String::from("redbankaddr"), - }, + red_bank: RedBankBase("redbankaddr".to_string()), }; let instantiate_res = app.instantiate_contract( @@ -218,10 +206,8 @@ fn test_panics_on_invalid_instantiation_addrs() { let msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], - allowed_assets: vec![], // Because cw-asset lowercases before passing to validate, in the test env, two letter strings is only one that triggers a fail - red_bank: RedBankBase { - contract_addr: String::from("%%%INVALID%%%"), - }, + allowed_assets: vec![], + red_bank: RedBankBase("%%%INVALID%%%".to_string()), }; let instantiate_res = app.instantiate_contract( diff --git a/contracts/credit-manager/tests/update_config.rs b/contracts/credit-manager/tests/update_config.rs index 92acb858e..0ef76f508 100644 --- a/contracts/credit-manager/tests/update_config.rs +++ b/contracts/credit-manager/tests/update_config.rs @@ -3,7 +3,7 @@ use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, Executor}; use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use rover::adapters::{RedBankBase, RedBankUnchecked}; +use rover::adapters::RedBankBase; use rover::msg::{ExecuteMsg, InstantiateMsg}; use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract, query_config}; @@ -24,9 +24,7 @@ fn test_update_config_works_with_full_config() { let new_owner = Addr::unchecked("new_owner"); let nft_contract_addr = setup_nft_and_propose_owner(&mut app, &original_owner, &contract_addr); - let new_red_bank_addr = RedBankUnchecked { - contract_addr: String::from("new_red_bank_addr"), - }; + let new_red_bank_addr = RedBankBase("new_red_bank_addr".to_string()); app.execute_contract( original_owner.clone(), @@ -44,7 +42,7 @@ fn test_update_config_works_with_full_config() { assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); assert_eq!(config_res.owner, new_owner.to_string()); - assert_eq!(config_res.red_bank, new_red_bank_addr.contract_addr); + assert_eq!(config_res.red_bank, new_red_bank_addr.0); } #[test] @@ -58,7 +56,7 @@ fn test_update_config_works_with_some_config() { assert_eq!(config_res.account_nft, None); assert_eq!(config_res.owner, original_owner.to_string()); - assert_eq!(config_res.red_bank, String::from("initial_red_bank")); + assert_eq!(config_res.red_bank, "initial_red_bank".to_string()); let nft_contract_addr = setup_nft_and_propose_owner(&mut app, &original_owner, &contract_addr); app.execute_contract( @@ -77,7 +75,7 @@ fn test_update_config_works_with_some_config() { assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); assert_eq!(config_res.owner, original_owner.to_string()); - assert_eq!(config_res.red_bank, String::from("initial_red_bank")); + assert_eq!(config_res.red_bank, "initial_red_bank".to_string()); let new_owner = Addr::unchecked("new_owner"); app.execute_contract( @@ -95,11 +93,10 @@ fn test_update_config_works_with_some_config() { let config_res = query_config(&mut app, &contract_addr.clone()); assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); assert_eq!(config_res.owner, new_owner.to_string()); - assert_eq!(config_res.red_bank, String::from("initial_red_bank")); + assert_eq!(config_res.red_bank, "initial_red_bank".to_string()); + + let new_red_bank = RedBankBase("new_red_bank_addr".to_string()); - let new_red_bank = RedBankUnchecked { - contract_addr: String::from("new_red_bank_addr"), - }; app.execute_contract( new_owner.clone(), contract_addr.clone(), @@ -115,7 +112,7 @@ fn test_update_config_works_with_some_config() { let config_res = query_config(&mut app, &contract_addr.clone()); assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); assert_eq!(config_res.owner, new_owner.to_string()); - assert_eq!(config_res.red_bank, new_red_bank.contract_addr); + assert_eq!(config_res.red_bank, new_red_bank.0); } #[test] @@ -151,9 +148,7 @@ fn instantiate(app: &mut App, original_owner: &Addr, code_id: u64) -> Addr { owner: original_owner.to_string(), allowed_vaults: vec![], allowed_assets: vec![], - red_bank: RedBankBase { - contract_addr: String::from("initial_red_bank"), - }, + red_bank: RedBankBase("initial_red_bank".to_string()), }, &[], "mock_manager_contract", diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs index 259b34bd7..8bac90a0f 100644 --- a/contracts/mock-red-bank/src/execute.rs +++ b/contracts/mock-red-bank/src/execute.rs @@ -1,10 +1,15 @@ use cosmwasm_std::{DepsMut, MessageInfo, Response, StdResult, Uint128}; -use cw_asset::Asset; +use cw_asset::AssetUnchecked; use crate::helpers::load_debt_amount; use crate::state::DEBT_AMOUNT; -pub fn execute_borrow(deps: DepsMut, info: MessageInfo, asset: Asset) -> StdResult { +pub fn execute_borrow( + deps: DepsMut, + info: MessageInfo, + asset_unchecked: AssetUnchecked, +) -> StdResult { + let asset = asset_unchecked.check(deps.api, None)?; let debt_amount = load_debt_amount(deps.storage, &info.sender, &asset.info); DEBT_AMOUNT.save( diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs index 33ec4fae8..a555e9b3b 100644 --- a/contracts/mock-red-bank/src/msg.rs +++ b/contracts/mock-red-bank/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_std::Uint128; -use cw_asset::{Asset, AssetInfoUnchecked}; +use cw_asset::{AssetInfoUnchecked, AssetUnchecked}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { Borrow { - asset: Asset, + asset: AssetUnchecked, recipient: Option, }, } @@ -23,10 +23,6 @@ pub enum QueryMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct UserAssetDebtResponse { - pub denom: String, - pub asset_label: String, - pub asset_reference: Vec, pub asset_info: AssetInfoUnchecked, - pub amount_scaled: Uint128, pub amount: Uint128, } diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index 0ca675cb4..ab08ac466 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Deps, Env, StdResult, Uint128}; +use cosmwasm_std::{Deps, Env, StdResult}; use cw_asset::AssetInfoUnchecked; use crate::helpers::load_debt_amount; @@ -14,13 +14,7 @@ pub fn query_debt( let asset_info = asset_info_unchecked.check(deps.api, None)?; let debt_amount = load_debt_amount(deps.storage, &user_addr, &asset_info); Ok(UserAssetDebtResponse { - // only amount matters for our testing amount: debt_amount, - // for other attributes we fill in some random value - denom: "".to_string(), - asset_label: asset_info.to_string(), - asset_reference: vec![], asset_info: asset_info.into(), - amount_scaled: Uint128::zero(), }) } diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index 050ad391d..86146d5aa 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -2,33 +2,27 @@ use cosmwasm_std::{ to_binary, Addr, Api, CosmosMsg, QuerierWrapper, QueryRequest, StdResult, Uint128, WasmMsg, WasmQuery, }; -use cw_asset::{Asset, AssetInfo}; +use cw_asset::{Asset, AssetInfo, AssetUnchecked}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use mock_red_bank::msg::{ExecuteMsg, QueryMsg, UserAssetDebtResponse}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct RedBankBase { - pub contract_addr: T, -} +pub struct RedBankBase(pub T); pub type RedBankUnchecked = RedBankBase; pub type RedBank = RedBankBase; impl From for RedBankUnchecked { fn from(red_bank: RedBank) -> Self { - RedBankUnchecked { - contract_addr: red_bank.contract_addr.to_string(), - } + Self(red_bank.0.to_string()) } } impl RedBankUnchecked { pub fn check(&self, api: &dyn Api) -> StdResult { - Ok(RedBank { - contract_addr: api.addr_validate(&self.contract_addr)?, - }) + Ok(RedBankBase(api.addr_validate(&self.0)?)) } } @@ -36,9 +30,9 @@ impl RedBank { /// Generate message for borrowing a specified amount of asset pub fn borrow_msg(&self, asset: &Asset) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: self.contract_addr.to_string(), + contract_addr: self.0.to_string(), msg: to_binary(&ExecuteMsg::Borrow { - asset: asset.clone(), + asset: AssetUnchecked::from(asset.clone()), recipient: None, })?, funds: vec![], @@ -53,7 +47,7 @@ impl RedBank { ) -> StdResult { let response: UserAssetDebtResponse = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: self.contract_addr.to_string(), + contract_addr: self.0.to_string(), msg: to_binary(&QueryMsg::UserAssetDebt { user_address: user_address.to_string(), asset: asset_info.clone().into(), diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 1fed94286..44819e032 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -23,10 +23,10 @@ pub enum ContractError { ExtraFundsReceived(AssetListBase), #[error("No asset amount set for action")] - NoAmount {}, + NoAmount, #[error("Deposits of CW20's should come via Cw20ExecuteMsg::Send to cw20 contract specifying Rover's ReceiveMsg")] - WrongDepositMethodForCW20 {}, + WrongDepositMethodForCW20, #[error("Sent fund mismatch. Expected: {expected:?}, received {received:?}")] FundsMismatch { @@ -35,7 +35,7 @@ pub enum ContractError { }, #[error("Callbacks cannot be invoked externally")] - ExternalInvocation {}, + ExternalInvocation, #[error("{user:?} is not the owner of {token_id:?}")] NotTokenOwner { user: String, token_id: String }, From a78a9e8e18b01f0c9ca3a0ba41b10c35f537f59a Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 3 Aug 2022 18:20:46 +0200 Subject: [PATCH 041/218] Review updates + removal of cw20 --- Cargo.lock | 179 ++++---- contracts/account-nft/Cargo.toml | 4 +- contracts/account-nft/src/msg/execute.rs | 2 +- contracts/account-nft/src/msg/query.rs | 8 +- contracts/account-nft/tests/ownership_test.rs | 6 +- contracts/credit-manager/Cargo.toml | 9 +- contracts/credit-manager/src/borrow.rs | 69 +-- contracts/credit-manager/src/contract.rs | 48 +- contracts/credit-manager/src/deposit.rs | 94 ++-- contracts/credit-manager/src/execute.rs | 97 ++-- contracts/credit-manager/src/instantiate.rs | 8 +- contracts/credit-manager/src/lib.rs | 2 + contracts/credit-manager/src/query.rs | 163 +++++-- contracts/credit-manager/src/state.rs | 15 +- .../credit-manager/tests/allow_list_query.rs | 296 ------------- contracts/credit-manager/tests/borrow.rs | 415 ------------------ contracts/credit-manager/tests/borrow_test.rs | 298 +++++++++++++ ...count.rs => create_credit_account_test.rs} | 13 +- .../credit-manager/tests/deposit_cw20.rs | 176 -------- .../{deposit_native.rs => deposit_test.rs} | 235 +++++----- .../tests/{dispatch.rs => dispatch_test.rs} | 26 +- .../tests/enumerate_allowed_coins_test.rs | 152 +++++++ .../tests/enumerate_allowed_vaults_test.rs | 152 +++++++ .../tests/enumerate_assets_test.rs | 278 ++++++++++++ .../tests/enumerate_debt_shares_test.rs | 365 +++++++++++++++ .../tests/enumerate_total_debt_shares_test.rs | 356 +++++++++++++++ contracts/credit-manager/tests/helpers.rs | 213 --------- .../tests/helpers/assertions.rs | 20 + .../credit-manager/tests/helpers/contracts.rs | 29 ++ .../credit-manager/tests/helpers/deploys.rs | 131 ++++++ contracts/credit-manager/tests/helpers/mod.rs | 11 + .../credit-manager/tests/helpers/queries.rs | 38 ++ .../credit-manager/tests/helpers/types.rs | 10 + .../{instantiate.rs => instantiate_test.rs} | 167 ++++--- .../credit-manager/tests/position_query.rs | 95 ---- .../credit-manager/tests/update_config.rs | 188 -------- .../tests/update_config_test.rs | 221 ++++++++++ contracts/mock-red-bank/Cargo.toml | 4 +- contracts/mock-red-bank/src/contract.rs | 15 +- contracts/mock-red-bank/src/execute.rs | 25 +- contracts/mock-red-bank/src/helpers.rs | 11 +- contracts/mock-red-bank/src/msg.rs | 12 +- contracts/mock-red-bank/src/query.rs | 14 +- contracts/mock-red-bank/src/state.rs | 4 +- packages/rover/Cargo.toml | 4 +- packages/rover/src/adapters/red_bank.rs | 15 +- packages/rover/src/coin_list.rs | 59 +++ packages/rover/src/error.rs | 15 +- packages/rover/src/lib.rs | 10 + packages/rover/src/msg/execute.rs | 39 +- packages/rover/src/msg/instantiate.rs | 16 +- packages/rover/src/msg/query.rs | 62 ++- 52 files changed, 2883 insertions(+), 2011 deletions(-) delete mode 100644 contracts/credit-manager/tests/allow_list_query.rs delete mode 100644 contracts/credit-manager/tests/borrow.rs create mode 100644 contracts/credit-manager/tests/borrow_test.rs rename contracts/credit-manager/tests/{create_credit_account.rs => create_credit_account_test.rs} (89%) delete mode 100644 contracts/credit-manager/tests/deposit_cw20.rs rename contracts/credit-manager/tests/{deposit_native.rs => deposit_test.rs} (50%) rename contracts/credit-manager/tests/{dispatch.rs => dispatch_test.rs} (71%) create mode 100644 contracts/credit-manager/tests/enumerate_allowed_coins_test.rs create mode 100644 contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs create mode 100644 contracts/credit-manager/tests/enumerate_assets_test.rs create mode 100644 contracts/credit-manager/tests/enumerate_debt_shares_test.rs create mode 100644 contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs delete mode 100644 contracts/credit-manager/tests/helpers.rs create mode 100644 contracts/credit-manager/tests/helpers/assertions.rs create mode 100644 contracts/credit-manager/tests/helpers/contracts.rs create mode 100644 contracts/credit-manager/tests/helpers/deploys.rs create mode 100644 contracts/credit-manager/tests/helpers/mod.rs create mode 100644 contracts/credit-manager/tests/helpers/queries.rs create mode 100644 contracts/credit-manager/tests/helpers/types.rs rename contracts/credit-manager/tests/{instantiate.rs => instantiate_test.rs} (73%) delete mode 100644 contracts/credit-manager/tests/position_query.rs delete mode 100644 contracts/credit-manager/tests/update_config.rs create mode 100644 contracts/credit-manager/tests/update_config_test.rs create mode 100644 packages/rover/src/coin_list.rs diff --git a/Cargo.lock b/Cargo.lock index 8281e658f..e8b51f632 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,7 +9,7 @@ dependencies = [ "anyhow", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", + "cw-storage-plus 0.14.0", "cw721", "cw721-base", "schemars", @@ -18,9 +18,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" [[package]] name = "base16ct" @@ -36,9 +36,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64ct" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179" +checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" [[package]] name = "block-buffer" @@ -57,9 +57,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cfg-if" @@ -138,12 +138,9 @@ dependencies = [ "account-nft", "anyhow", "cosmwasm-std", - "cw-asset", "cw-multi-test", - "cw-storage-plus", - "cw2", - "cw20", - "cw20-base", + "cw-storage-plus 0.14.0", + "cw2 0.14.0", "cw721", "cw721-base", "mock-red-bank", @@ -182,9 +179,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" dependencies = [ "byteorder", "digest", @@ -193,30 +190,17 @@ dependencies = [ "zeroize", ] -[[package]] -name = "cw-asset" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a2015b1cdbe475b1c8b6a2a04e6b7be1f576ec41d8e6811bd7d069959f5c7f7" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "cw20", - "schemars", - "serde", -] - [[package]] name = "cw-multi-test" -version = "0.13.4" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f9a8ab7c3c29ec93cb7a39ce4b14a05e053153b4a17ef7cf2246af1b7c087e" +checksum = "ca153120cf5b91af88be106b0c6c0263423d959bc813b1592982c02c4691a4ae" dependencies = [ "anyhow", "cosmwasm-std", "cosmwasm-storage", - "cw-storage-plus", - "cw-utils", + "cw-storage-plus 0.14.0", + "cw-utils 0.14.0", "derivative", "itertools", "prost", @@ -236,6 +220,17 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-storage-plus" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c8b264257c4f44c49b7ce09377af63aa040768ecd3fd7bdd2d48a09323a1e90" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cw-utils" version = "0.13.4" @@ -249,67 +244,65 @@ dependencies = [ ] [[package]] -name = "cw2" -version = "0.13.4" +name = "cw-utils" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" +checksum = "414b91f3d7a619bb26c835119d7095804596a1382ddc1d184c33c1d2c17f6c5e" dependencies = [ "cosmwasm-std", - "cw-storage-plus", + "cw2 0.14.0", "schemars", + "semver", "serde", + "thiserror", ] [[package]] -name = "cw20" +name = "cw2" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" +checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" dependencies = [ "cosmwasm-std", - "cw-utils", + "cw-storage-plus 0.13.4", "schemars", "serde", ] [[package]] -name = "cw20-base" -version = "0.13.4" +name = "cw2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0306e606581f4fb45e82bcbb7f0333179ed53dd949c6523f01a99b4bfc1475a0" +checksum = "aa74c324af8e3506fd8d50759a265bead3f87402e413c840042af5d2808463d6" dependencies = [ "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "cw2", - "cw20", + "cw-storage-plus 0.14.0", "schemars", "serde", - "thiserror", ] [[package]] name = "cw721" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b9fd71276795554c35899bb3a378561ed0c288d231113e9915f6ee1f42b7b5" +checksum = "035818368a74c07dd9ed5c5a93340199ba251530162010b9f34c3809e3b97df1" dependencies = [ "cosmwasm-std", - "cw-utils", + "cw-utils 0.13.4", "schemars", "serde", ] [[package]] name = "cw721-base" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b61200af4e027af2d7485dbdc37c2a9c4093b6b2f2b811732329ef456076f97e" +checksum = "423d4efe8b649d228d1533e141c238415f49aa8a9ee4e40fce192d7a93ffd057" dependencies = [ "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "cw2", + "cw-storage-plus 0.13.4", + "cw-utils 0.13.4", + "cw2 0.13.4", "cw721", "schemars", "serde", @@ -347,9 +340,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140206b78fb2bc3edbcfc9b5ccbd0b30699cfe8d348b8b31b330e47df5291a5a" +checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" [[package]] name = "ecdsa" @@ -380,9 +373,9 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "elliptic-curve" @@ -420,9 +413,9 @@ checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -488,9 +481,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "k256" @@ -516,9 +509,7 @@ name = "mock-red-bank" version = "1.0.0" dependencies = [ "cosmwasm-std", - "cw-asset", - "cw-storage-plus", - "cw20", + "cw-storage-plus 0.14.0", "schemars", "serde", ] @@ -542,9 +533,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] @@ -574,9 +565,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -615,9 +606,7 @@ name = "rover" version = "0.1.0" dependencies = [ "cosmwasm-std", - "cw-asset", - "cw-storage-plus", - "cw20", + "cw-storage-plus 0.14.0", "mock-red-bank", "schemars", "serde", @@ -626,9 +615,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "schemars" @@ -667,11 +656,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "semver" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" + [[package]] name = "serde" -version = "1.0.137" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" dependencies = [ "serde_derive", ] @@ -687,9 +682,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e" dependencies = [ "proc-macro2", "quote", @@ -709,9 +704,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" dependencies = [ "itoa", "ryu", @@ -733,9 +728,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest", "rand_core 0.6.3", @@ -765,9 +760,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", @@ -776,18 +771,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" dependencies = [ "proc-macro2", "quote", @@ -814,9 +809,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "version_check" @@ -838,6 +833,6 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" -version = "1.4.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 1229110d3..a28b906a9 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -12,7 +12,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cw-storage-plus = "0.13" +cw-storage-plus = "0.14" cw721 = "0.13" cw721-base = { version = "0.13", features = ["library"] } cosmwasm-std = "1.0" @@ -21,4 +21,4 @@ serde = { version = "1.0", default-features = false, features = ["derive"] } [dev-dependencies] anyhow = "1" -cw-multi-test = "0.13" +cw-multi-test = "0.14" diff --git a/contracts/account-nft/src/msg/execute.rs b/contracts/account-nft/src/msg/execute.rs index 38225214b..d419dc5d5 100644 --- a/contracts/account-nft/src/msg/execute.rs +++ b/contracts/account-nft/src/msg/execute.rs @@ -17,7 +17,7 @@ pub enum ExecuteMsg { ProposeNewOwner { new_owner: String }, /// Accept the proposed ownership transfer - AcceptOwnership {}, + AcceptOwnership, /// Mint a new NFT to the specified user; can only be called by the contract minter Mint { user: String }, diff --git a/contracts/account-nft/src/msg/query.rs b/contracts/account-nft/src/msg/query.rs index 004110a37..0c3a5ddee 100644 --- a/contracts/account-nft/src/msg/query.rs +++ b/contracts/account-nft/src/msg/query.rs @@ -11,7 +11,7 @@ pub enum QueryMsg { //-------------------------------------------------------------------------------------------------- // Extended messages //-------------------------------------------------------------------------------------------------- - ProposedNewOwner {}, + ProposedNewOwner, //-------------------------------------------------------------------------------------------------- // Base cw721 messages @@ -49,11 +49,11 @@ pub enum QueryMsg { limit: Option, }, /// Total number of tokens issued - NumTokens {}, + NumTokens, /// With MetaData Extension. /// Returns top-level metadata about the contract: `ContractInfoResponse` - ContractInfo {}, + ContractInfo, /// With MetaData Extension. /// Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* /// but directly from the contract: `NftInfoResponse` @@ -86,7 +86,7 @@ pub enum QueryMsg { }, /// Return the minter - Minter {}, + Minter, } impl TryInto for QueryMsg { diff --git a/contracts/account-nft/tests/ownership_test.rs b/contracts/account-nft/tests/ownership_test.rs index 4309542a3..1c17b12a4 100644 --- a/contracts/account-nft/tests/ownership_test.rs +++ b/contracts/account-nft/tests/ownership_test.rs @@ -54,7 +54,7 @@ fn test_proposed_owner_can_accept_ownership() { let res: MinterResponse = app .wrap() - .query_wasm_smart(contract_addr, &QueryMsg::Minter {}) + .query_wasm_smart(contract_addr, &QueryMsg::Minter) .unwrap(); assert_eq!(res.minter, new_owner) @@ -78,7 +78,7 @@ fn test_only_proposed_owner_can_accept() { fn query_pending_owner(app: &BasicApp, contract_addr: &Addr) -> StdResult { app.wrap() - .query_wasm_smart(contract_addr, &QueryMsg::ProposedNewOwner {}) + .query_wasm_smart(contract_addr, &QueryMsg::ProposedNewOwner) } fn propose_new_owner( @@ -105,7 +105,7 @@ fn accept_proposed_owner( app.execute_contract( sender.clone(), contract_addr.clone(), - &ExtendedExecuteMsg::AcceptOwnership {}, + &ExtendedExecuteMsg::AcceptOwnership, &[], ) } diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index e5a902311..d8b4346d2 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -16,17 +16,14 @@ account-nft = { version = "0.1", path = "../account-nft", features = ["library"] rover = { version = "0.1", path = "../../packages/rover" } cosmwasm-std = "1.0" -cw2 = "0.13" -cw20 = "0.13" -cw20-base = { version = "0.13", features = ["library"] } +cw2 = "0.14" cw721 = "0.13" cw721-base = { version = "0.13", features = ["library"] } -cw-asset = "2.1" -cw-storage-plus = "0.13" +cw-storage-plus = "0.14" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } [dev-dependencies] anyhow = "1" -cw-multi-test = "0.13" +cw-multi-test = "0.14" mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index e4b386e46..719f1f0a0 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -1,68 +1,71 @@ -use cosmwasm_std::{DepsMut, Env, Response, StdResult, Uint128}; -use cw_asset::Asset; +use cosmwasm_std::{Coin, DepsMut, Env, Response, StdError, StdResult, Uint128}; +use rover::ContractResult; -use crate::deposit::assert_asset_is_whitelisted; use rover::error::ContractError; -use rover::error::ContractError::NoAmount; +use crate::deposit::assert_coin_is_whitelisted; use crate::state::{ASSETS, DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; -pub static DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED: Uint128 = Uint128::new(1_000_000); +pub static DEFAULT_DEBT_UNITS_PER_COIN_BORROWED: Uint128 = Uint128::new(1_000_000); /// calculate by how many the user's debt units should be increased -/// if total debt is zero, then we define 1 unit of asset borrowed = 1,000,000 debt unit +/// if total debt is zero, then we define 1 unit of coin borrowed = 1,000,000 debt unit /// else, get debt ownership % and multiply by total existing shares /// /// increment total debt shares, token debt shares, and asset amount -pub fn borrow( - deps: DepsMut, - env: Env, - token_id: &str, - asset: Asset, -) -> Result { - if asset.amount.is_zero() { - return Err(NoAmount {}); +pub fn borrow(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractResult { + if coin.amount.is_zero() { + return Err(ContractError::NoAmount); } - assert_asset_is_whitelisted(deps.storage, &asset.info)?; + assert_coin_is_whitelisted(deps.storage, &coin.denom)?; let red_bank = RED_BANK.load(deps.storage)?; let total_debt_amount = - red_bank.query_user_debt(&deps.querier, &env.contract.address, &asset.info)?; + red_bank.query_user_debt(&deps.querier, &env.contract.address, &coin.denom)?; let debt_shares_to_add = if total_debt_amount.is_zero() { - asset - .amount - .checked_mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED)? + coin.amount + .checked_mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) + .map_err(StdError::overflow)? } else { TOTAL_DEBT_SHARES - .load(deps.storage, asset.clone().info.into())? - .checked_multiply_ratio(asset.amount, total_debt_amount)? + .load(deps.storage, &coin.denom)? + .checked_multiply_ratio(coin.amount, total_debt_amount)? }; - TOTAL_DEBT_SHARES.update( - deps.storage, - asset.clone().info.into(), - |shares| -> StdResult<_> { Ok(shares.unwrap_or(Uint128::zero()) + debt_shares_to_add) }, - )?; + TOTAL_DEBT_SHARES.update(deps.storage, &coin.denom, |shares| -> StdResult<_> { + shares + .unwrap_or_else(Uint128::zero) + .checked_add(debt_shares_to_add) + .map_err(StdError::overflow) + })?; DEBT_SHARES.update( deps.storage, - (token_id, asset.clone().info.into()), - |current_debt| -> StdResult<_> { - Ok(current_debt.unwrap_or(Uint128::zero()) + debt_shares_to_add) + (token_id, &coin.denom), + |shares| -> StdResult<_> { + shares + .unwrap_or_else(Uint128::zero) + .checked_add(debt_shares_to_add) + .map_err(StdError::overflow) }, )?; ASSETS.update( deps.storage, - (token_id, asset.clone().info.into()), - |amount| -> StdResult<_> { Ok(amount.unwrap_or(Uint128::zero()) + asset.amount) }, + (token_id, &coin.denom), + |current_amount| -> StdResult<_> { + current_amount + .unwrap_or_else(Uint128::zero) + .checked_add(coin.amount) + .map_err(StdError::overflow) + }, )?; Ok(Response::new() - .add_message(red_bank.borrow_msg(&asset)?) + .add_message(red_bank.borrow_msg(&coin)?) .add_attribute("action", "rover/credit_manager/borrow") .add_attribute("debt_shares_added", debt_shares_to_add) - .add_attribute("assets_borrowed", asset.amount)) + .add_attribute("coins_borrowed", coin.amount)) } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index e98d35f14..3c8652e8d 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -1,20 +1,16 @@ use cosmwasm_std::{ - entry_point, from_binary, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, - StdResult, + entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, }; use cw2::set_contract_version; -use cw20::Cw20ReceiveMsg; -use rover::error::ContractError; -use rover::msg::execute::ReceiveMsg; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use rover::ContractResult; -use crate::deposit::cw20_deposit; use crate::execute::{create_credit_account, dispatch_actions, execute_callback, update_config}; use crate::instantiate::store_config; use crate::query::{ - query_allowed_assets, query_allowed_vaults, query_config, query_position, - query_total_debt_shares, + query_all_assets, query_all_debt_shares, query_all_total_debt_shares, query_allowed_coins, + query_allowed_vaults, query_config, query_position, query_total_debt_shares, }; const CONTRACT_NAME: &str = "crates.io:rover-credit-manager"; @@ -26,7 +22,7 @@ pub fn instantiate( _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> Result { +) -> ContractResult { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; store_config(deps, &msg)?; Ok(Response::new().add_attribute("method", "instantiate")) @@ -38,19 +34,14 @@ pub fn execute( env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result { +) -> ContractResult { match msg { ExecuteMsg::CreateCreditAccount {} => create_credit_account(deps, info.sender), - ExecuteMsg::UpdateConfig { - account_nft, - owner, - red_bank, - } => update_config(deps, info, account_nft, owner, red_bank), + ExecuteMsg::UpdateConfig { new_config } => update_config(deps, info, new_config), ExecuteMsg::Callback(callback) => execute_callback(deps, info, env, callback), ExecuteMsg::UpdateCreditAccount { token_id, actions } => { dispatch_actions(deps, env, info, &token_id, &actions) } - ExecuteMsg::Receive(msg) => receive_cw20(deps, info, msg), } } @@ -61,22 +52,19 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::AllowedVaults { start_after, limit } => { to_binary(&query_allowed_vaults(deps, start_after, limit)?) } - QueryMsg::AllowedAssets { start_after, limit } => { - to_binary(&query_allowed_assets(deps, start_after, limit)?) + QueryMsg::AllowedCoins { start_after, limit } => { + to_binary(&query_allowed_coins(deps, start_after, limit)?) } QueryMsg::Position { token_id } => to_binary(&query_position(deps, &token_id)?), - QueryMsg::TotalDebtShares(asset_info) => { - to_binary(&query_total_debt_shares(deps, asset_info)?) + QueryMsg::AllAssets { start_after, limit } => { + to_binary(&query_all_assets(deps, start_after, limit)?) + } + QueryMsg::AllDebtShares { start_after, limit } => { + to_binary(&query_all_debt_shares(deps, start_after, limit)?) + } + QueryMsg::TotalDebtShares(denom) => to_binary(&query_total_debt_shares(deps, &denom)?), + QueryMsg::AllTotalDebtShares { start_after, limit } => { + to_binary(&query_all_total_debt_shares(deps, start_after, limit)?) } - } -} - -pub fn receive_cw20( - deps: DepsMut, - info: MessageInfo, - cw20_msg: Cw20ReceiveMsg, -) -> Result { - match from_binary(&cw20_msg.msg)? { - ReceiveMsg::Deposit { token_id } => cw20_deposit(deps, info, &cw20_msg, &token_id), } } diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index 7638fef0b..91a00b163 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -1,104 +1,70 @@ -use cosmwasm_std::{Api, DepsMut, MessageInfo, Response, StdError, StdResult, Storage, Uint128}; -use cw20::Cw20ReceiveMsg; -use cw_asset::{Asset, AssetInfo, AssetInfoUnchecked, AssetList, AssetUnchecked}; +use cosmwasm_std::{Coin, Response, StdError, StdResult, Storage, Uint128}; +use rover::coin_list::CoinList; use rover::error::ContractError; +use rover::ContractResult; -use crate::execute::assert_is_token_owner; -use crate::state::{ALLOWED_ASSETS, ASSETS}; +use crate::state::{ALLOWED_COINS, ASSETS}; -pub fn native_deposit( +pub fn deposit( storage: &mut dyn Storage, - api: &dyn Api, response: Response, nft_token_id: &str, - asset_unchecked: &AssetUnchecked, - received_coins: &mut AssetList, -) -> Result { - let asset = asset_unchecked.check(api, None)?; - assert_asset_is_whitelisted(storage, &asset.info)?; + coin: &Coin, + received_coins: &mut CoinList, +) -> ContractResult { + assert_coin_is_whitelisted(storage, &coin.denom)?; - if asset.amount.is_zero() { + if coin.amount.is_zero() { return Ok(response); } - match &asset.info { - AssetInfo::Native(_) => { - assert_sent_fund(&asset, received_coins)?; - received_coins.deduct(&asset)?; - } - AssetInfo::Cw20(_) => { - return Err(ContractError::WrongDepositMethodForCW20 {}); - } - } + assert_sent_fund(coin, received_coins)?; + + received_coins.deduct(coin)?; - // increase the user asset amount - increment_position(storage, nft_token_id, &asset.info, asset.amount)?; + // increase the token's asset amount + increment_position(storage, nft_token_id, coin)?; Ok(response .add_attribute("action", "rover/credit_manager/callback/deposit") - .add_attribute("deposit_received", asset.to_string())) + .add_attribute("deposit_received", coin.to_string())) } /// Assert that fund of exactly the same type and amount was sent along with a message -fn assert_sent_fund(expected: &Asset, received_coins: &AssetList) -> Result<(), ContractError> { - let received_amount = if let Some(coin) = received_coins.find(&expected.info) { - coin.amount - } else { - Uint128::zero() - }; +fn assert_sent_fund(expected: &Coin, received_coins: &CoinList) -> ContractResult<()> { + let received = received_coins + .find_denom(&expected.denom) + .map(|c| c.amount) + .unwrap_or_else(Uint128::zero); - if received_amount != expected.amount { + if received != expected.amount { return Err(ContractError::FundsMismatch { expected: expected.amount, - received: received_amount, + received, }); } Ok(()) } -pub fn cw20_deposit( - deps: DepsMut, - info: MessageInfo, - cw20_msg: &Cw20ReceiveMsg, - token_id: &str, -) -> Result { - let sender = deps.api.addr_validate(&cw20_msg.sender)?; - assert_is_token_owner(&deps, &sender, token_id)?; - let asset = AssetInfoUnchecked::cw20(&info.sender).check(deps.api, None)?; - assert_asset_is_whitelisted(deps.storage, &asset)?; - increment_position(deps.storage, token_id, &asset, cw20_msg.amount)?; - Ok(Response::new() - .add_attribute("action", "rover/execute/receive_cw20") - .add_attribute("deposit_received", asset.to_string())) -} - -pub fn assert_asset_is_whitelisted( - storage: &mut dyn Storage, - asset: &AssetInfo, -) -> Result<(), ContractError> { - let is_whitelisted = ALLOWED_ASSETS.has(storage, asset.into()); +pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, denom: &str) -> ContractResult<()> { + let is_whitelisted = ALLOWED_COINS.has(storage, denom); if !is_whitelisted { - return Err(ContractError::NotWhitelisted(asset.to_string())); + return Err(ContractError::NotWhitelisted(denom.to_string())); } Ok(()) } -fn increment_position( - storage: &mut dyn Storage, - token_id: &str, - asset: &AssetInfo, - amount: Uint128, -) -> StdResult<()> { +fn increment_position(storage: &mut dyn Storage, token_id: &str, coin: &Coin) -> StdResult<()> { ASSETS.update( storage, - (token_id, asset.into()), + (token_id, &coin.denom), |value_opt| -> StdResult<_> { value_opt .unwrap_or_else(Uint128::zero) - .checked_add(amount) - .map_err(|_| StdError::generic_err("add overflow error")) + .checked_add(coin.amount) + .map_err(StdError::overflow) }, )?; Ok(()) diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 04c31320e..2a8549849 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -3,18 +3,19 @@ use cosmwasm_std::{ }; use cw721::OwnerOfResponse; use cw721_base::QueryMsg; -use cw_asset::AssetList; use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use rover::adapters::RedBankUnchecked; +use rover::coin_list::CoinList; use rover::error::ContractError; use rover::msg::execute::{Action, CallbackMsg}; +use rover::msg::instantiate::ConfigUpdates; +use rover::ContractResult; use crate::borrow::borrow; -use crate::deposit::native_deposit; -use crate::state::{ACCOUNT_NFT, OWNER, RED_BANK}; +use crate::deposit::deposit; +use crate::state::{ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, OWNER, RED_BANK}; -pub fn create_credit_account(deps: DepsMut, user: Addr) -> Result { +pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; let nft_mint_msg = CosmosMsg::Wasm(WasmMsg::Execute { @@ -33,10 +34,8 @@ pub fn create_credit_account(deps: DepsMut, user: Addr) -> Result, - new_owner: Option, - new_red_bank: Option, -) -> Result { + new_config: ConfigUpdates, +) -> ContractResult { let owner = OWNER.load(deps.storage)?; if info.sender != owner { @@ -46,33 +45,58 @@ pub fn update_config( }); } - let mut response = Response::new(); + let mut response = + Response::new().add_attribute("action", "rover/credit_manager/update_config"); - if let Some(addr_str) = new_account_nft { + if let Some(addr_str) = new_config.account_nft { let validated = deps.api.addr_validate(&addr_str)?; ACCOUNT_NFT.save(deps.storage, &validated)?; // Accept ownership. NFT contract owner must have proposed Rover as a new owner first. let accept_ownership_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: addr_str, + contract_addr: addr_str.clone(), funds: vec![], - msg: to_binary(&NftExecuteMsg::AcceptOwnership {})?, + msg: to_binary(&NftExecuteMsg::AcceptOwnership)?, }); response = response .add_message(accept_ownership_msg) - .add_attribute("action", "rover/credit_manager/update_config/account_nft"); - } - - if let Some(unchecked) = new_red_bank { - RED_BANK.save(deps.storage, &unchecked.check(deps.api)?)?; - response = response.add_attribute("action", "rover/credit_manager/update_config/red_bank"); + .add_attribute("key", "account_nft") + .add_attribute("value", addr_str); } - if let Some(addr_str) = new_owner { + if let Some(addr_str) = new_config.owner { let validated = deps.api.addr_validate(&addr_str)?; OWNER.save(deps.storage, &validated)?; - response = response.add_attribute("action", "rover/credit_manager/update_config/owner"); + response = response + .add_attribute("key", "owner") + .add_attribute("value", addr_str); + } + + if let Some(coins) = new_config.allowed_coins { + coins + .iter() + .try_for_each(|denom| ALLOWED_COINS.save(deps.storage, denom, &true))?; + response = response + .add_attribute("key", "allowed_coins") + .add_attribute("value", coins.join(", ")); + } + + if let Some(vaults) = new_config.allowed_vaults { + vaults.iter().try_for_each(|unchecked| { + let vault = deps.api.addr_validate(unchecked)?; + ALLOWED_VAULTS.save(deps.storage, vault, &true) + })?; + response = response + .add_attribute("key", "allowed_vaults") + .add_attribute("value", vaults.join(", ")); + } + + if let Some(unchecked) = new_config.red_bank { + RED_BANK.save(deps.storage, &unchecked.check(deps.api)?)?; + response = response + .add_attribute("key", "red_bank") + .add_attribute("value", unchecked.0); } Ok(response) @@ -84,28 +108,21 @@ pub fn dispatch_actions( info: MessageInfo, token_id: &str, actions: &[Action], -) -> Result { +) -> ContractResult { assert_is_token_owner(&deps, &info.sender, token_id)?; let mut response = Response::new(); let mut callbacks: Vec = vec![]; - let mut received_coins = AssetList::from(&info.funds); + let mut received_coins = CoinList::from(&info.funds); for action in actions { match action { - Action::NativeDeposit(asset) => { - response = native_deposit( - deps.storage, - deps.api, - response, - token_id, - asset, - &mut received_coins, - )?; + Action::Deposit(coin) => { + response = deposit(deps.storage, response, token_id, coin, &mut received_coins)?; } - Action::Borrow(asset) => callbacks.push(CallbackMsg::Borrow { + Action::Borrow(coin) => callbacks.push(CallbackMsg::Borrow { token_id: token_id.to_string(), - asset: asset.check(deps.api, None)?, + coin: coin.clone(), }), } } @@ -131,20 +148,16 @@ pub fn execute_callback( info: MessageInfo, env: Env, callback: CallbackMsg, -) -> Result { +) -> ContractResult { if info.sender != env.contract.address { - return Err(ContractError::ExternalInvocation {}); + return Err(ContractError::ExternalInvocation); } match callback { - CallbackMsg::Borrow { asset, token_id } => borrow(deps, env, &token_id, asset), + CallbackMsg::Borrow { coin, token_id } => borrow(deps, env, &token_id, coin), } } -pub fn assert_is_token_owner( - deps: &DepsMut, - user: &Addr, - token_id: &str, -) -> Result<(), ContractError> { +pub fn assert_is_token_owner(deps: &DepsMut, user: &Addr, token_id: &str) -> ContractResult<()> { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; let owner_res: OwnerOfResponse = deps.querier.query_wasm_smart( contract_addr, diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 4098ce315..aad47cf82 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{DepsMut, StdResult}; use rover::msg::InstantiateMsg; -use crate::state::{ALLOWED_ASSETS, ALLOWED_VAULTS, OWNER, RED_BANK}; +use crate::state::{ALLOWED_COINS, ALLOWED_VAULTS, OWNER, RED_BANK}; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { let owner = deps.api.addr_validate(&msg.owner)?; @@ -13,9 +13,9 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { ALLOWED_VAULTS.save(deps.storage, deps.api.addr_validate(vault)?, &true) })?; - msg.allowed_assets.iter().try_for_each(|info| { - ALLOWED_ASSETS.save(deps.storage, info.check(deps.api, None)?.into(), &true) - })?; + msg.allowed_coins + .iter() + .try_for_each(|denom| ALLOWED_COINS.save(deps.storage, denom, &true))?; Ok(()) } diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 82eccb816..2bfb1c1f3 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -1,3 +1,5 @@ +extern crate core; + pub mod contract; pub mod borrow; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index dd4bf5a48..3b7e6630d 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,12 +1,13 @@ -use std::convert::TryFrom; +use cosmwasm_std::{Coin, Deps, Order, StdResult}; +use cw_storage_plus::Bound; -use cosmwasm_std::{Addr, Deps, Order, StdResult, Uint128}; -use cw_asset::{AssetInfo, AssetInfoKey, AssetInfoUnchecked, AssetUnchecked}; -use cw_storage_plus::{Bound, Map}; -use rover::msg::query::{ConfigResponse, PositionResponse, TotalDebtSharesResponse}; +use rover::msg::query::{ + AssetResponseItem, CoinShares, ConfigResponse, PositionResponse, SharesResponseItem, +}; +use rover::Denom; use crate::state::{ - NftTokenId, ACCOUNT_NFT, ALLOWED_ASSETS, ALLOWED_VAULTS, ASSETS, DEBT_SHARES, OWNER, RED_BANK, + ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, ASSETS, DEBT_SHARES, OWNER, RED_BANK, TOTAL_DEBT_SHARES, }; @@ -26,28 +27,87 @@ pub fn query_config(deps: Deps) -> StdResult { pub fn query_position(deps: Deps, token_id: &str) -> StdResult { Ok(PositionResponse { token_id: token_id.to_string(), - assets: get_asset_list(deps, token_id, ASSETS)?, - debt_shares: get_asset_list(deps, token_id, DEBT_SHARES)?, + assets: get_assets(deps, token_id)?, + debt_shares: get_debt_shares(deps, token_id)?, }) } -fn get_asset_list( +fn get_assets(deps: Deps, token_id: &str) -> StdResult> { + ASSETS + .prefix(token_id) + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()? + .iter() + .map(|(denom, amount)| { + Ok(Coin { + denom: denom.clone(), + amount: *amount, + }) + }) + .collect() +} + +pub fn query_all_assets( deps: Deps, - token_id: &str, - asset_amount_map: Map<(NftTokenId, AssetInfoKey), Uint128>, -) -> StdResult> { - asset_amount_map + start_after: Option<(String, String)>, + limit: Option, +) -> StdResult> { + let start = start_after + .as_ref() + .map(|(token_id, denom)| Bound::exclusive((token_id.as_str(), denom.as_str()))); + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + Ok(ASSETS + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>>()? + .iter() + .map(|((token_id, denom), amount)| AssetResponseItem { + token_id: token_id.to_string(), + denom: denom.to_string(), + amount: *amount, + }) + .collect()) +} + +fn get_debt_shares(deps: Deps, token_id: &str) -> StdResult> { + DEBT_SHARES .prefix(token_id) .range(deps.storage, None, None, Order::Ascending) - .into_iter() - .map(|res| { - let (asset_info_key, amount) = res?; - let info_unchecked = AssetInfoUnchecked::try_from(asset_info_key)?; - Ok(AssetUnchecked::new(info_unchecked, amount.u128())) + .collect::>>()? + .iter() + .map(|(denom, shares)| { + Ok(CoinShares { + denom: denom.clone(), + shares: *shares, + }) }) .collect() } +pub fn query_all_debt_shares( + deps: Deps, + start_after: Option<(String, String)>, + limit: Option, +) -> StdResult> { + let start = start_after + .as_ref() + .map(|(token_id, denom)| Bound::exclusive((token_id.as_str(), denom.as_str()))); + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + Ok(DEBT_SHARES + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>>()? + .iter() + .map(|((token_id, denom), shares)| SharesResponseItem { + token_id: token_id.to_string(), + denom: denom.to_string(), + shares: *shares, + }) + .collect()) +} + /// NOTE: This implementation of the query function assumes the map `ALLOWED_VAULTS` only saves `true`. /// If a vault is to be removed from the whitelist, the map must remove the corresponding key, instead /// of setting the value to `false`. @@ -56,10 +116,9 @@ pub fn query_allowed_vaults( start_after: Option, limit: Option, ) -> StdResult> { - let addr: Addr; let start = match &start_after { Some(addr_str) => { - addr = deps.api.addr_validate(addr_str)?; + let addr = deps.api.addr_validate(addr_str)?; Some(Bound::exclusive(addr)) } None => None, @@ -74,41 +133,53 @@ pub fn query_allowed_vaults( .collect() } -/// NOTE: This implementation of the query function assumes the map `ALLOWED_ASSETS` only saves `true`. -/// If an asset is to be removed from the whitelist, the map must remove the corresponding key, instead +/// NOTE: This implementation of the query function assumes the map `ALLOWED_COINS` only saves `true`. +/// If a coin is to be removed from the whitelist, the map must remove the corresponding key, instead /// of setting the value to `false`. -pub fn query_allowed_assets( +pub fn query_allowed_coins( deps: Deps, - start_after: Option, + start_after: Option, limit: Option, -) -> StdResult> { - let info: AssetInfo; - let start = match &start_after { - Some(unchecked) => { - info = unchecked.check(deps.api, None)?; - Some(Bound::exclusive(AssetInfoKey::from(info))) - } - None => None, - }; +) -> StdResult> { + let start = start_after + .as_ref() + .map(|denom| Bound::exclusive(denom.as_str())); let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - ALLOWED_ASSETS + ALLOWED_COINS .keys(deps.storage, start, None, Order::Ascending) .take(limit) - .collect::>>()? - .into_iter() - .map(AssetInfoUnchecked::try_from) - .collect() + .collect::>>() } -pub fn query_total_debt_shares( +pub fn query_total_debt_shares(deps: Deps, denom: Denom) -> StdResult { + let shares = TOTAL_DEBT_SHARES.load(deps.storage, denom)?; + Ok(CoinShares { + denom: denom.to_string(), + shares, + }) +} + +pub fn query_all_total_debt_shares( deps: Deps, - unchecked_asset_info: AssetInfoUnchecked, -) -> StdResult { - let asset_info = unchecked_asset_info.check(deps.api, None)?; - let total_debt = TOTAL_DEBT_SHARES.load(deps.storage, asset_info.clone().into())?; - Ok(TotalDebtSharesResponse(AssetUnchecked::new( - asset_info, total_debt, - ))) + start_after: Option, + limit: Option, +) -> StdResult> { + let start = start_after + .as_ref() + .map(|denom| Bound::exclusive(denom.as_str())); + + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + Ok(TOTAL_DEBT_SHARES + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>>()? + .iter() + .map(|(denom, shares)| CoinShares { + denom: denom.to_string(), + shares: *shares, + }) + .collect()) } diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 068b1ab9d..bb6656d52 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,20 +1,17 @@ use cosmwasm_std::{Addr, Uint128}; -use cw_asset::AssetInfoKey; use cw_storage_plus::{Item, Map}; + use rover::adapters::RedBank; +use rover::{Denom, NftTokenId, Shares}; // Contract config pub const OWNER: Item = Item::new("owner"); pub const ACCOUNT_NFT: Item = Item::new("account_nft"); -pub const ALLOWED_ASSETS: Map = Map::new("allowed_assets"); +pub const ALLOWED_COINS: Map = Map::new("allowed_coins"); pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); pub const RED_BANK: Item = Item::new("red_bank"); // Positions -pub type NftTokenId<'a> = &'a str; -pub const ASSETS: Map<(NftTokenId, AssetInfoKey), Uint128> = Map::new("assets"); - -type Shares = Uint128; -pub const DEBT_SHARES: Map<(NftTokenId, AssetInfoKey), Shares> = Map::new("debt_shares"); -/// Used to calculate each user's share of the debt -pub const TOTAL_DEBT_SHARES: Map = Map::new("total_debt_shares"); +pub const ASSETS: Map<(NftTokenId, Denom), Uint128> = Map::new("assets"); +pub const DEBT_SHARES: Map<(NftTokenId, Denom), Shares> = Map::new("debt_shares"); +pub const TOTAL_DEBT_SHARES: Map = Map::new("total_debt_shares"); diff --git a/contracts/credit-manager/tests/allow_list_query.rs b/contracts/credit-manager/tests/allow_list_query.rs deleted file mode 100644 index 77f861604..000000000 --- a/contracts/credit-manager/tests/allow_list_query.rs +++ /dev/null @@ -1,296 +0,0 @@ -use cosmwasm_std::Addr; -use cw_asset::AssetInfoUnchecked; -use cw_multi_test::Executor; - -use rover::adapters::RedBankBase; -use rover::msg::{InstantiateMsg, QueryMsg}; - -use crate::helpers::{mock_app, mock_contract}; - -pub mod helpers; - -#[test] -fn test_pagination_on_allowed_vaults_query_works() { - let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); - let owner = Addr::unchecked("owner"); - - let allowed_vaults = vec![ - "addr1".to_string(), - "addr2".to_string(), - "addr3".to_string(), - "addr4".to_string(), - "addr5".to_string(), - "addr6".to_string(), - "addr7".to_string(), - "addr8".to_string(), - "addr9".to_string(), - "addr10".to_string(), - "addr11".to_string(), - "addr12".to_string(), - "addr13".to_string(), - "addr14".to_string(), - "addr15".to_string(), - "addr16".to_string(), - "addr17".to_string(), - "addr18".to_string(), - "addr19".to_string(), - "addr20".to_string(), - "addr21".to_string(), - "addr22".to_string(), - "addr23".to_string(), - "addr24".to_string(), - "addr25".to_string(), - "addr26".to_string(), - "addr27".to_string(), - "addr28".to_string(), - "addr29".to_string(), - "addr30".to_string(), - "addr31".to_string(), - "addr32".to_string(), - ]; - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: allowed_vaults.clone(), - allowed_assets: vec![], - red_bank: RedBankBase("redbankaddr".to_string()), - }; - - let contract_addr = app - .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) - .unwrap(); - - let vaults_res: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedVaults { - start_after: None, - limit: Some(58 as u32), - }, - ) - .unwrap(); - - // Assert maximum is observed - assert_eq!(vaults_res.len(), 30); - - let vaults_res: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedVaults { - start_after: None, - limit: Some(2 as u32), - }, - ) - .unwrap(); - - // Assert limit request is observed - assert_eq!(vaults_res.len(), 2); - - let vaults_res_a: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedVaults { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - let vaults_res_b: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedVaults { - start_after: Some(vaults_res_a.last().unwrap().clone()), - limit: None, - }, - ) - .unwrap(); - - let vaults_res_c: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedVaults { - start_after: Some(vaults_res_b.last().unwrap().clone()), - limit: None, - }, - ) - .unwrap(); - - let vaults_res_d: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedVaults { - start_after: Some(vaults_res_c.last().unwrap().clone()), - limit: None, - }, - ) - .unwrap(); - - // Assert default is observed - assert_eq!(vaults_res_a.len(), 10); - assert_eq!(vaults_res_b.len(), 10); - assert_eq!(vaults_res_c.len(), 10); - - assert_eq!(vaults_res_d.len(), 2); - - let combined: Vec = vaults_res_a - .iter() - .cloned() - .chain(vaults_res_b.iter().cloned()) - .chain(vaults_res_c.iter().cloned()) - .chain(vaults_res_d.iter().cloned()) - .collect(); - - assert_eq!(combined.len(), allowed_vaults.len()); - assert!(allowed_vaults.iter().all(|item| combined.contains(item))); -} - -#[test] -fn test_pagination_on_allowed_assets_query_works() { - let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); - let owner = Addr::unchecked("owner"); - - let allowed_assets = vec![ - AssetInfoUnchecked::Native("native_asset_1".to_string()), - AssetInfoUnchecked::Native("native_asset_2".to_string()), - AssetInfoUnchecked::Native("native_asset_3".to_string()), - AssetInfoUnchecked::Native("native_asset_4".to_string()), - AssetInfoUnchecked::Native("native_asset_5".to_string()), - AssetInfoUnchecked::Cw20("cw_token_1".to_string()), - AssetInfoUnchecked::Cw20("cw_token_2".to_string()), - AssetInfoUnchecked::Cw20("cw_token_3".to_string()), - AssetInfoUnchecked::Cw20("cw_token_4".to_string()), - AssetInfoUnchecked::Cw20("cw_token_5".to_string()), - AssetInfoUnchecked::Cw20("cw_token_6".to_string()), - AssetInfoUnchecked::Cw20("cw_token_7".to_string()), - AssetInfoUnchecked::Cw20("cw_token_8".to_string()), - AssetInfoUnchecked::Cw20("cw_token_9".to_string()), - AssetInfoUnchecked::Native("native_asset_6".to_string()), - AssetInfoUnchecked::Native("native_asset_7".to_string()), - AssetInfoUnchecked::Cw20("cw_token_10".to_string()), - AssetInfoUnchecked::Cw20("cw_token_11".to_string()), - AssetInfoUnchecked::Cw20("cw_token_12".to_string()), - AssetInfoUnchecked::Cw20("cw_token_13".to_string()), - AssetInfoUnchecked::Cw20("cw_token_14".to_string()), - AssetInfoUnchecked::Native("native_asset_8".to_string()), - AssetInfoUnchecked::Native("native_asset_9".to_string()), - AssetInfoUnchecked::Native("native_asset_10".to_string()), - AssetInfoUnchecked::Cw20("cw_token_15".to_string()), - AssetInfoUnchecked::Cw20("cw_token_16".to_string()), - AssetInfoUnchecked::Cw20("cw_token_17".to_string()), - AssetInfoUnchecked::Cw20("cw_token_18".to_string()), - AssetInfoUnchecked::Cw20("cw_token_19".to_string()), - AssetInfoUnchecked::Cw20("cw_token_20".to_string()), - AssetInfoUnchecked::Native("native_asset_11".to_string()), - AssetInfoUnchecked::Native("native_asset_12".to_string()), - ]; - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec![], - allowed_assets: allowed_assets.clone(), - red_bank: RedBankBase("redbankaddr".to_string()), - }; - - let contract_addr = app - .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) - .unwrap(); - - let assets_res: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedAssets { - start_after: None, - limit: Some(58 as u32), - }, - ) - .unwrap(); - - // Assert maximum is observed - assert_eq!(assets_res.len(), 30); - - let assets_res: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedAssets { - start_after: None, - limit: Some(2 as u32), - }, - ) - .unwrap(); - - // Assert limit request is observed - assert_eq!(assets_res.len(), 2); - - let assets_res_a: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedAssets { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - let assets_res_b: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedAssets { - start_after: Some(assets_res_a.last().unwrap().clone()), - limit: None, - }, - ) - .unwrap(); - - let assets_res_c: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedAssets { - start_after: Some(assets_res_b.last().unwrap().clone()), - limit: None, - }, - ) - .unwrap(); - - let assets_res_d: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedAssets { - start_after: Some(assets_res_c.last().unwrap().clone()), - limit: None, - }, - ) - .unwrap(); - - // Assert default is observed - assert_eq!(assets_res_a.len(), 10); - assert_eq!(assets_res_b.len(), 10); - assert_eq!(assets_res_c.len(), 10); - - assert_eq!(assets_res_d.len(), 2); - - let combined: Vec = assets_res_a - .iter() - .cloned() - .chain(assets_res_b.iter().cloned()) - .chain(assets_res_c.iter().cloned()) - .chain(assets_res_d.iter().cloned()) - .collect(); - - assert_eq!(combined.len(), allowed_assets.len()); - assert!(allowed_assets.iter().all(|item| combined.contains(item))); -} diff --git a/contracts/credit-manager/tests/borrow.rs b/contracts/credit-manager/tests/borrow.rs deleted file mode 100644 index cd9115a61..000000000 --- a/contracts/credit-manager/tests/borrow.rs +++ /dev/null @@ -1,415 +0,0 @@ -use std::ops::{Mul, Sub}; - -use cosmwasm_std::{Addr, Coin, Uint128}; -use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg}; -use cw20_base::msg::QueryMsg::Balance; -use cw_asset::{AssetInfoUnchecked, AssetUnchecked}; -use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; - -use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED; -use mock_red_bank::msg::QueryMsg::UserAssetDebt; -use mock_red_bank::msg::UserAssetDebtResponse; -use rover::error::ContractError; -use rover::msg::execute::Action::Borrow; -use rover::msg::query::TotalDebtSharesResponse; -use rover::msg::ExecuteMsg::UpdateCreditAccount; -use rover::msg::QueryMsg; - -use crate::helpers::{ - assert_err, deploy_mock_cw20, get_token_id, mock_app, mock_create_credit_account, query_config, - query_position, setup_credit_manager, -}; - -pub mod helpers; - -#[test] -fn test_only_token_owner_can_borrow() { - let mut app = mock_app(); - let owner = Addr::unchecked("owner"); - let info = AssetInfoUnchecked::native("uosmo"); - let asset = AssetUnchecked::new(info.clone(), Uint128::zero()); - - let contract_addr = setup_credit_manager(&mut app, &owner, vec![info.clone()]); - let res = - mock_create_credit_account(&mut app, &contract_addr, &Addr::unchecked("user")).unwrap(); - let token_id = get_token_id(res); - - let another_user = Addr::unchecked("another_user"); - let res = app.execute_contract( - another_user.clone(), - contract_addr.clone(), - &UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Borrow(asset)], - }, - &[], - ); - - assert_err( - res, - ContractError::NotTokenOwner { - user: another_user.into(), - token_id, - }, - ) -} - -#[test] -fn test_can_only_borrow_what_is_whitelisted() { - let mut app = mock_app(); - let owner = Addr::unchecked("owner"); - let info = AssetInfoUnchecked::native("uosmo"); - - let contract_addr = setup_credit_manager(&mut app, &owner, vec![info.clone()]); - let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); - let token_id = get_token_id(res); - - let res = app.execute_contract( - user.clone(), - contract_addr.clone(), - &UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Borrow(AssetUnchecked::new( - AssetInfoUnchecked::native("usomething"), - Uint128::from(234u128), - ))], - }, - &[], - ); - - assert_err( - res, - ContractError::NotWhitelisted(String::from("native:usomething")), - ) -} - -#[test] -fn test_borrowing_zero_does_nothing() { - let mut app = mock_app(); - let info = AssetInfoUnchecked::native("uosmo"); - - let contract_addr = - setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); - let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); - let token_id = get_token_id(res); - - let res = app.execute_contract( - user.clone(), - contract_addr.clone(), - &UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Borrow(AssetUnchecked::new(info, Uint128::zero()))], - }, - &[], - ); - - assert_err(res, ContractError::NoAmount {}); - - let position = query_position(&mut app, &contract_addr, &token_id); - assert_eq!(position.assets.len(), 0); - assert_eq!(position.debt_shares.len(), 0); -} - -#[test] -fn test_success_when_new_debt_asset() { - let user = Addr::unchecked("user"); - let funds = Coin::new(300u128, "uosmo"); - let info = AssetInfoUnchecked::native("uosmo"); - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &user, vec![funds]) - .unwrap(); - }); - - let contract_addr = - setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); - let token_id = get_token_id(res); - - let config = query_config(&mut app, &contract_addr.clone()); - - fund_red_bank_native( - &mut app, - config.red_bank.clone(), - vec![Coin::new(1000u128, "uosmo")], - ); - - let position = query_position(&mut app, &contract_addr, &token_id); - assert_eq!(position.assets.len(), 0); - assert_eq!(position.debt_shares.len(), 0); - - app.execute_contract( - user, - contract_addr.clone(), - &UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Borrow(AssetUnchecked::new( - info.clone(), - Uint128::from(42u128), - ))], - }, - &[], - ) - .unwrap(); - - let position = query_position(&mut app, &contract_addr, &token_id); - assert_eq!(position.assets.len(), 1); - assert_eq!( - position.assets.first().unwrap().amount, - Uint128::from(42u128) - ); - assert_eq!(position.assets.first().unwrap().info, info); - assert_eq!(position.debt_shares.len(), 1); - assert_eq!( - position.debt_shares.first().unwrap().amount, - Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED) - ); - assert_eq!(position.debt_shares.first().unwrap().info, info); - - let coin = app - .wrap() - .query_balance(contract_addr.clone(), "uosmo") - .unwrap(); - assert_eq!(coin.amount, Uint128::from(42u128)); - - let coin = app.wrap().query_balance(config.red_bank, "uosmo").unwrap(); - assert_eq!( - coin.amount, - Uint128::from(1000u128).sub(Uint128::from(42u128)) - ); - - let res: TotalDebtSharesResponse = app - .wrap() - .query_wasm_smart(contract_addr, &QueryMsg::TotalDebtShares(info)) - .unwrap(); - assert_eq!( - res.0.amount, - Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED) - ); -} - -#[test] -fn test_debt_shares_with_debt_amount() { - let user_a = Addr::unchecked("user_a"); - let user_b = Addr::unchecked("user_b"); - let info = AssetInfoUnchecked::native("uosmo"); - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &user_a, vec![Coin::new(300u128, "uosmo")]) - .unwrap(); - router - .bank - .init_balance(storage, &user_b, vec![Coin::new(450u128, "uosmo")]) - .unwrap(); - }); - - let contract_addr = - setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); - let res = mock_create_credit_account(&mut app, &contract_addr, &user_a).unwrap(); - let token_id_a = get_token_id(res); - let res = mock_create_credit_account(&mut app, &contract_addr, &user_b).unwrap(); - let token_id_b = get_token_id(res); - - let config = query_config(&mut app, &contract_addr.clone()); - - fund_red_bank_native( - &mut app, - config.red_bank.clone(), - vec![Coin::new(1000u128, "uosmo")], - ); - - app.execute_contract( - user_a, - contract_addr.clone(), - &UpdateCreditAccount { - token_id: token_id_a.clone(), - actions: vec![Borrow(AssetUnchecked::new( - info.clone(), - Uint128::from(50u128), - ))], - }, - &[], - ) - .unwrap(); - - let interim_red_bank_debt: UserAssetDebtResponse = app - .wrap() - .query_wasm_smart( - config.red_bank, - &UserAssetDebt { - user_address: contract_addr.clone().into(), - asset: info.clone(), - }, - ) - .unwrap(); - - app.execute_contract( - user_b, - contract_addr.clone(), - &UpdateCreditAccount { - token_id: token_id_b.clone(), - actions: vec![Borrow(AssetUnchecked::new( - info.clone(), - Uint128::from(50u128), - ))], - }, - &[], - ) - .unwrap(); - - let token_a_shares = Uint128::from(50u128).mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED); - let position = query_position(&mut app, &contract_addr, &token_id_a); - assert_eq!( - position.debt_shares.first().unwrap().amount, - token_a_shares.clone() - ); - - let token_b_shares = Uint128::from(50u128) - .mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED) - .multiply_ratio(Uint128::from(50u128), interim_red_bank_debt.amount); - - let position = query_position(&mut app, &contract_addr, &token_id_b); - assert_eq!( - position.debt_shares.first().unwrap().amount, - token_b_shares.clone() - ); - - let res: TotalDebtSharesResponse = app - .wrap() - .query_wasm_smart(contract_addr, &QueryMsg::TotalDebtShares(info)) - .unwrap(); - assert_eq!(res.0.amount, token_a_shares + token_b_shares); -} - -#[test] -fn test_can_borrow_cw20() { - let owner = Addr::unchecked("owner"); - let user = Addr::unchecked("user"); - let mut app = mock_app(); - - let cw20_contract = deploy_mock_cw20( - &mut app, - "jakecoin", - vec![Cw20Coin { - address: owner.clone().into(), - amount: Uint128::from(1000u128), - }], - ); - let cw20_info = AssetInfoUnchecked::cw20(cw20_contract.clone()); - - let contract_addr = setup_credit_manager(&mut app, &owner, vec![cw20_info.clone()]); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); - let token_id = get_token_id(res); - - let config = query_config(&mut app, &contract_addr.clone()); - fund_red_bank_cw20( - &mut app, - owner, - config.red_bank.clone(), - cw20_contract.clone(), - Uint128::from(1000u128), - ); - - app.execute_contract( - user, - contract_addr.clone(), - &UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Borrow(AssetUnchecked::new( - cw20_info.clone(), - Uint128::from(42u128), - ))], - }, - &[], - ) - .unwrap(); - - let position = query_position(&mut app, &contract_addr, &token_id); - assert_eq!(position.assets.len(), 1); - assert_eq!( - position.assets.first().unwrap().amount, - Uint128::from(42u128) - ); - assert_eq!(position.assets.first().unwrap().info, cw20_info); - assert_eq!(position.debt_shares.len(), 1); - assert_eq!( - position.debt_shares.first().unwrap().amount, - Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED) - ); - assert_eq!(position.debt_shares.first().unwrap().info, cw20_info); - - let balance = query_cw20_balance( - &mut app, - cw20_contract.clone(), - contract_addr.clone().into(), - ); - assert_eq!(balance, Uint128::from(42u128)); - - let balance = query_cw20_balance(&mut app, cw20_contract, config.red_bank); - assert_eq!(balance, Uint128::from(1000u128).sub(Uint128::from(42u128))); - - let res: TotalDebtSharesResponse = app - .wrap() - .query_wasm_smart(contract_addr, &QueryMsg::TotalDebtShares(cw20_info)) - .unwrap(); - assert_eq!( - res.0.amount, - Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_ASSET_BORROWED) - ); -} - -// TODO: After health check implemented -#[test] -fn test_cannot_borrow_more_than_healthy() {} - -fn fund_red_bank_native(app: &mut BasicApp, red_bank_addr: String, funds: Vec) { - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: red_bank_addr, - amount: funds, - })) - .unwrap(); -} - -fn fund_red_bank_cw20( - app: &mut BasicApp, - sender: Addr, - red_bank_addr: String, - cw20_contract: Addr, - amount: Uint128, -) -> AppResponse { - let res = app - .execute_contract( - sender, - cw20_contract.clone(), - &Cw20ExecuteMsg::Transfer { - recipient: red_bank_addr.clone(), - amount, - }, - &[], - ) - .unwrap(); - let balance = query_cw20_balance(app, cw20_contract, red_bank_addr); - assert_eq!(balance, amount); - res -} - -fn query_cw20_balance( - app: &mut BasicApp, - cw20_contract: Addr, - address_to_query: String, -) -> Uint128 { - let res: BalanceResponse = app - .wrap() - .query_wasm_smart( - cw20_contract.clone(), - &Balance { - address: address_to_query, - }, - ) - .unwrap(); - res.balance -} diff --git a/contracts/credit-manager/tests/borrow_test.rs b/contracts/credit-manager/tests/borrow_test.rs new file mode 100644 index 000000000..8805f5b3e --- /dev/null +++ b/contracts/credit-manager/tests/borrow_test.rs @@ -0,0 +1,298 @@ +use std::ops::{Mul, Sub}; + +use cosmwasm_std::{Addr, Coin, Uint128}; +use cw_multi_test::{App, Executor}; + +use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; +use mock_red_bank::msg::QueryMsg::UserAssetDebt; +use mock_red_bank::msg::UserAssetDebtResponse; +use rover::error::ContractError; +use rover::msg::execute::Action::Borrow; +use rover::msg::query::CoinShares; +use rover::msg::ExecuteMsg::UpdateCreditAccount; +use rover::msg::QueryMsg; + +use crate::helpers::{ + assert_err, fund_red_bank_native, get_token_id, mock_app, mock_create_credit_account, + query_config, query_position, setup_credit_manager, +}; + +pub mod helpers; + +#[test] +fn test_only_token_owner_can_borrow() { + let mut app = mock_app(); + let owner = Addr::unchecked("owner"); + let coin = Coin { + denom: "uosmo".to_string(), + amount: Uint128::zero(), + }; + + let mock = setup_credit_manager(&mut app, &owner, vec![coin.denom.clone()], vec![]); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &Addr::unchecked("user")) + .unwrap(); + let token_id = get_token_id(res); + + let another_user = Addr::unchecked("another_user"); + let res = app.execute_contract( + another_user.clone(), + mock.credit_manager.clone(), + &UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(coin)], + }, + &[], + ); + + assert_err( + res, + ContractError::NotTokenOwner { + user: another_user.into(), + token_id, + }, + ) +} + +#[test] +fn test_can_only_borrow_what_is_whitelisted() { + let mut app = mock_app(); + let owner = Addr::unchecked("owner"); + let coin = Coin { + denom: "usomething".to_string(), + amount: Uint128::from(234u128), + }; + + let mock = setup_credit_manager(&mut app, &owner, vec!["uosmo".to_string()], vec![]); + let user = Addr::unchecked("user"); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); + let token_id = get_token_id(res); + + let res = app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(coin)], + }, + &[], + ); + + assert_err( + res, + ContractError::NotWhitelisted(String::from("usomething")), + ) +} + +#[test] +fn test_borrowing_zero_does_nothing() { + let mut app = mock_app(); + let coin = Coin { + denom: "uosmo".to_string(), + amount: Uint128::zero(), + }; + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin.denom.clone()], + vec![], + ); + let user = Addr::unchecked("user"); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); + let token_id = get_token_id(res); + + let res = app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(coin)], + }, + &[], + ); + + assert_err(res, ContractError::NoAmount); + + let position = query_position(&mut app, &mock.credit_manager, &token_id); + assert_eq!(position.assets.len(), 0); + assert_eq!(position.debt_shares.len(), 0); +} + +#[test] +fn test_success_when_new_debt_asset() { + let user = Addr::unchecked("user"); + let funds = Coin::new(300u128, "uosmo"); + let coin = Coin { + denom: "uosmo".to_string(), + amount: Uint128::from(42u128), + }; + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user, vec![funds]) + .unwrap(); + }); + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin.denom.clone()], + vec![], + ); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); + let token_id = get_token_id(res); + + let config = query_config(&mut app, &mock.credit_manager.clone()); + + fund_red_bank_native( + &mut app, + config.red_bank.clone(), + vec![Coin::new(1000u128, "uosmo")], + ); + + let position = query_position(&mut app, &mock.credit_manager, &token_id); + assert_eq!(position.assets.len(), 0); + assert_eq!(position.debt_shares.len(), 0); + + app.execute_contract( + user, + mock.credit_manager.clone(), + &UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(coin.clone())], + }, + &[], + ) + .unwrap(); + + let position = query_position(&mut app, &mock.credit_manager, &token_id); + assert_eq!(position.assets.len(), 1); + assert_eq!( + position.assets.first().unwrap().amount, + Uint128::from(42u128) + ); + assert_eq!(position.assets.first().unwrap().denom, coin.denom); + assert_eq!(position.debt_shares.len(), 1); + assert_eq!( + position.debt_shares.first().unwrap().shares, + Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) + ); + assert_eq!(position.debt_shares.first().unwrap().denom, coin.denom); + + let coin = app + .wrap() + .query_balance(mock.credit_manager.clone(), "uosmo") + .unwrap(); + assert_eq!(coin.amount, Uint128::from(42u128)); + + let coin = app.wrap().query_balance(config.red_bank, "uosmo").unwrap(); + assert_eq!( + coin.amount, + Uint128::from(1000u128).sub(Uint128::from(42u128)) + ); + + let res: CoinShares = app + .wrap() + .query_wasm_smart(mock.credit_manager, &QueryMsg::TotalDebtShares(coin.denom)) + .unwrap(); + assert_eq!( + res.shares, + Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) + ); +} + +#[test] +fn test_debt_shares_with_debt_amount() { + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let coin = Coin { + denom: "uosmo".to_string(), + amount: Uint128::from(50u128), + }; + + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user_a, vec![Coin::new(300u128, "uosmo")]) + .unwrap(); + router + .bank + .init_balance(storage, &user_b, vec![Coin::new(450u128, "uosmo")]) + .unwrap(); + }); + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin.denom.clone()], + vec![], + ); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); + let token_id_a = get_token_id(res); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_b).unwrap(); + let token_id_b = get_token_id(res); + + let config = query_config(&mut app, &mock.credit_manager.clone()); + + fund_red_bank_native( + &mut app, + config.red_bank.clone(), + vec![Coin::new(1000u128, "uosmo")], + ); + + app.execute_contract( + user_a, + mock.credit_manager.clone(), + &UpdateCreditAccount { + token_id: token_id_a.clone(), + actions: vec![Borrow(coin.clone())], + }, + &[], + ) + .unwrap(); + + let interim_red_bank_debt: UserAssetDebtResponse = app + .wrap() + .query_wasm_smart( + config.red_bank, + &UserAssetDebt { + user_address: mock.credit_manager.clone().into(), + denom: coin.denom.clone(), + }, + ) + .unwrap(); + + app.execute_contract( + user_b, + mock.credit_manager.clone(), + &UpdateCreditAccount { + token_id: token_id_b.clone(), + actions: vec![Borrow(coin.clone())], + }, + &[], + ) + .unwrap(); + + let token_a_shares = Uint128::from(50u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED); + let position = query_position(&mut app, &mock.credit_manager, &token_id_a); + assert_eq!( + position.debt_shares.first().unwrap().shares, + token_a_shares.clone() + ); + + let token_b_shares = Uint128::from(50u128) + .mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) + .multiply_ratio(Uint128::from(50u128), interim_red_bank_debt.amount); + + let position = query_position(&mut app, &mock.credit_manager, &token_id_b); + assert_eq!( + position.debt_shares.first().unwrap().shares, + token_b_shares.clone() + ); + + let res: CoinShares = app + .wrap() + .query_wasm_smart(mock.credit_manager, &QueryMsg::TotalDebtShares(coin.denom)) + .unwrap(); + assert_eq!(res.shares, token_a_shares + token_b_shares); +} diff --git a/contracts/credit-manager/tests/create_credit_account.rs b/contracts/credit-manager/tests/create_credit_account_test.rs similarity index 89% rename from contracts/credit-manager/tests/create_credit_account.rs rename to contracts/credit-manager/tests/create_credit_account_test.rs index 0400f20b9..8e602570e 100644 --- a/contracts/credit-manager/tests/create_credit_account.rs +++ b/contracts/credit-manager/tests/create_credit_account_test.rs @@ -4,6 +4,7 @@ use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw721_base::QueryMsg as NftQueryMsg; use cw_multi_test::Executor; use rover::adapters::RedBankBase; +use rover::msg::instantiate::ConfigUpdates; use rover::msg::query::ConfigResponse; use rover::msg::ExecuteMsg::UpdateConfig; use rover::msg::{InstantiateMsg, QueryMsg}; @@ -41,7 +42,7 @@ fn test_create_credit_account() { let manager_initiate_msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], - allowed_assets: vec![], + allowed_coins: vec![], red_bank: RedBankBase("redbankaddr".to_string()), }; @@ -67,9 +68,13 @@ fn test_create_credit_account() { owner.clone(), manager_contract_addr.clone(), &UpdateConfig { - account_nft: Some(nft_contract_addr.to_string()), - owner: None, - red_bank: None, + new_config: ConfigUpdates { + account_nft: Some(nft_contract_addr.to_string()), + owner: None, + allowed_coins: None, + allowed_vaults: None, + red_bank: None, + }, }, &[], ); diff --git a/contracts/credit-manager/tests/deposit_cw20.rs b/contracts/credit-manager/tests/deposit_cw20.rs deleted file mode 100644 index 0ef11616a..000000000 --- a/contracts/credit-manager/tests/deposit_cw20.rs +++ /dev/null @@ -1,176 +0,0 @@ -extern crate core; - -use cosmwasm_std::{to_binary, Addr, Uint128}; -use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, Cw20QueryMsg}; -use cw_asset::{AssetInfo, AssetInfoUnchecked}; -use cw_multi_test::Executor; - -use rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; -use rover::msg::execute::ReceiveMsg; - -use crate::helpers::{ - assert_err, deploy_mock_cw20, get_token_id, mock_app, mock_create_credit_account, - query_position, setup_credit_manager, -}; - -pub mod helpers; - -#[test] -fn test_only_token_owner_can_deposit() { - let mut app = mock_app(); - let user = Addr::unchecked("user"); - let another_user = Addr::unchecked("another_user"); - - let cw20_contract = deploy_mock_cw20( - &mut app, - "jakecoin", - vec![Cw20Coin { - address: another_user.to_string(), - amount: Uint128::from(500u128), - }], - ); - - let manager_contract = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![AssetInfoUnchecked::Cw20(cw20_contract.clone().to_string())], - ); - let res = mock_create_credit_account(&mut app, &manager_contract, &user).unwrap(); - let token_id = get_token_id(res); - let amount = Uint128::from(300u128); - - let res = app.execute_contract( - another_user.clone(), - cw20_contract.clone(), - &Cw20ExecuteMsg::Send { - contract: manager_contract.to_string(), - amount, - msg: to_binary(&ReceiveMsg::Deposit { - token_id: token_id.clone(), - }) - .unwrap(), - }, - &[], - ); - - assert_err( - res, - NotTokenOwner { - user: another_user.to_string(), - token_id: token_id.clone(), - }, - ); - - let res = query_position(&app, &manager_contract, &token_id); - assert_eq!(res.assets.len(), 0); -} - -#[test] -fn test_can_only_deposit_allowed_assets() { - let mut app = mock_app(); - let user = Addr::unchecked("user"); - let cw20_contract_a = deploy_mock_cw20( - &mut app, - "jakecoin", - vec![Cw20Coin { - address: user.to_string(), - amount: Uint128::from(500u128), - }], - ); - - let cw20_contract_b = deploy_mock_cw20( - &mut app, - "sparkycoin", - vec![Cw20Coin { - address: user.to_string(), - amount: Uint128::from(500u128), - }], - ); - - let contract_addr = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![AssetInfoUnchecked::Cw20(cw20_contract_b.to_string())], - ); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); - let token_id = get_token_id(res); - let amount = Uint128::from(300u128); - - let res = app.execute_contract( - user.clone(), - cw20_contract_a.clone(), - &Cw20ExecuteMsg::Send { - contract: contract_addr.to_string(), - amount, - msg: to_binary(&ReceiveMsg::Deposit { - token_id: token_id.clone(), - }) - .unwrap(), - }, - &[], - ); - - assert_err( - res, - NotWhitelisted(AssetInfo::Cw20(cw20_contract_a).to_string()), - ); - - let res = query_position(&app, &contract_addr, &token_id); - assert_eq!(res.assets.len(), 0); -} - -#[test] -fn test_cw20_deposit_success() { - let mut app = mock_app(); - let user = Addr::unchecked("user"); - let cw20_contract = deploy_mock_cw20( - &mut app, - "jakecoin", - vec![Cw20Coin { - address: user.to_string(), - amount: Uint128::from(500u128), - }], - ); - let asset_info = AssetInfoUnchecked::cw20(cw20_contract.clone()); - - let contract_addr = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![asset_info.clone()], - ); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); - let token_id = get_token_id(res); - let amount = Uint128::from(300u128); - - app.execute_contract( - user.clone(), - cw20_contract.clone(), - &Cw20ExecuteMsg::Send { - contract: contract_addr.to_string(), - amount, - msg: to_binary(&ReceiveMsg::Deposit { - token_id: token_id.clone(), - }) - .unwrap(), - }, - &[], - ) - .unwrap(); - - let res = query_position(&app, &contract_addr, &token_id); - assert_eq!(res.assets.len(), 1); - assert_eq!(res.assets.first().unwrap().amount, amount); - assert_eq!(res.assets.first().unwrap().info, asset_info); - - let res: BalanceResponse = app - .wrap() - .query_wasm_smart( - cw20_contract.clone(), - &Cw20QueryMsg::Balance { - address: contract_addr.into(), - }, - ) - .unwrap(); - - assert_eq!(res.balance, amount) -} diff --git a/contracts/credit-manager/tests/deposit_native.rs b/contracts/credit-manager/tests/deposit_test.rs similarity index 50% rename from contracts/credit-manager/tests/deposit_native.rs rename to contracts/credit-manager/tests/deposit_test.rs index 5d0776512..875531a8d 100644 --- a/contracts/credit-manager/tests/deposit_native.rs +++ b/contracts/credit-manager/tests/deposit_test.rs @@ -1,19 +1,18 @@ extern crate core; use cosmwasm_std::{Addr, Coin, Uint128}; -use cw20::Cw20Coin; -use cw_asset::{AssetInfo, AssetInfoUnchecked, AssetList, AssetUnchecked}; use cw_multi_test::{App, Executor}; + +use rover::coin_list::CoinList; use rover::error::ContractError::{ ExtraFundsReceived, FundsMismatch, NotTokenOwner, NotWhitelisted, }; - use rover::msg::execute::Action; use rover::msg::ExecuteMsg; use crate::helpers::{ - assert_err, deploy_mock_cw20, get_token_id, mock_app, mock_create_credit_account, - query_position, setup_credit_manager, + assert_err, get_token_id, mock_app, mock_create_credit_account, query_position, + setup_credit_manager, }; pub mod helpers; @@ -21,23 +20,29 @@ pub mod helpers; #[test] fn test_only_owner_of_token_can_deposit() { let mut app = mock_app(); - let info = AssetInfoUnchecked::native("uosmo"); - let asset = AssetUnchecked::new(info.clone(), Uint128::zero()); + let coin = Coin { + denom: "uosmo".to_string(), + amount: Uint128::zero(), + }; - let contract_addr = - setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin.denom.clone()], + vec![], + ); let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); let another_user = Addr::unchecked("another_user"); let res = app.execute_contract( another_user.clone(), - contract_addr.clone(), + mock.credit_manager, &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::NativeDeposit(asset)], + actions: vec![Action::Deposit(coin)], }, &[], ); @@ -54,51 +59,63 @@ fn test_only_owner_of_token_can_deposit() { #[test] fn test_deposit_nothing() { let mut app = mock_app(); - let info = AssetInfoUnchecked::native("uosmo"); - let asset = AssetUnchecked::new(info.clone(), Uint128::zero()); - let contract_addr = - setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); + let coin = Coin { + denom: "uosmo".to_string(), + amount: Uint128::zero(), + }; + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin.denom.clone()], + vec![], + ); let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); - let res = query_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.assets.len(), 0); app.execute_contract( user.clone(), - contract_addr.clone(), + mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::NativeDeposit(asset)], + actions: vec![Action::Deposit(coin)], }, &[], ) .unwrap(); - let res = query_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.assets.len(), 0); } #[test] fn test_deposit_but_no_funds() { let mut app = mock_app(); - let info = AssetInfoUnchecked::native("uosmo"); - let amount = Uint128::from(234u128); - let asset = AssetUnchecked::new(info.clone(), amount); - let contract_addr = setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info]); + let coin = Coin { + denom: "uosmo".to_string(), + amount: Uint128::from(234u128), + }; + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin.denom.clone()], + vec![], + ); let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); let res = app.execute_contract( user.clone(), - contract_addr.clone(), + mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::NativeDeposit(asset.clone())], + actions: vec![Action::Deposit(coin.clone())], }, &[], ); @@ -106,41 +123,45 @@ fn test_deposit_but_no_funds() { assert_err( res, FundsMismatch { - expected: asset.amount, + expected: coin.amount, received: Uint128::zero(), }, ); - let res = query_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.assets.len(), 0); } #[test] fn test_deposit_but_not_enough_funds() { let user = Addr::unchecked("user"); - let funds = Coin::new(300u128, "uosmo"); - let info = AssetInfoUnchecked::native("uosmo"); - let amount = Uint128::from(350u128); - let asset = AssetUnchecked::new(info.clone(), amount); - let mut app = App::new(|router, _, storage| { router .bank - .init_balance(storage, &user, vec![funds]) + .init_balance(storage, &user, vec![Coin::new(300u128, "uosmo")]) .unwrap(); }); - let contract_addr = - setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let coin = Coin { + denom: "uosmo".to_string(), + amount: Uint128::from(350u128), + }; + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin.denom.clone()], + vec![], + ); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); let res = app.execute_contract( user.clone(), - contract_addr.clone(), + mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::NativeDeposit(asset.clone())], + actions: vec![Action::Deposit(coin.clone())], }, &[Coin::new(250u128, "uosmo")], ); @@ -148,7 +169,7 @@ fn test_deposit_but_not_enough_funds() { assert_err( res, FundsMismatch { - expected: asset.amount, + expected: coin.amount, received: Uint128::from(250u128), }, ); @@ -165,43 +186,34 @@ fn test_can_only_deposit_allowed_assets() { .init_balance(storage, &user, vec![funds]) .unwrap(); }); - let cw20_contract = deploy_mock_cw20( - &mut app, - "jakecoin", - vec![Cw20Coin { - address: user.to_string(), - amount: Uint128::from(500u128), - }], - ); - let contract_addr = setup_credit_manager( + + let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![ - AssetInfoUnchecked::native("ucosmos"), - AssetInfoUnchecked::cw20(cw20_contract), - ], + vec!["ucosmos".to_string()], + vec![], ); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); - let info = AssetInfoUnchecked::native("uosmo"); - let amount = Uint128::from(234u128); - let asset = AssetUnchecked::new(info.clone(), amount); - + let coin = Coin { + denom: "uosmo".to_string(), + amount: Uint128::from(234u128), + }; let res = app.execute_contract( user.clone(), - contract_addr.clone(), + mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::NativeDeposit(asset.clone())], + actions: vec![Action::Deposit(coin.clone())], }, &[Coin::new(234u128, "uosmo")], ); - assert_err(res, NotWhitelisted(AssetInfo::native("uosmo").to_string())); + assert_err(res, NotWhitelisted(coin.denom)); - let res = query_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.assets.len(), 0); } @@ -220,40 +232,47 @@ fn test_extra_funds_received() { .unwrap(); }); - let info = AssetInfoUnchecked::native("uosmo"); - let amount = Uint128::from(234u128); - let asset = AssetUnchecked::new(info.clone(), amount); + let coin = Coin { + denom: "uosmo".to_string(), + amount: Uint128::from(234u128), + }; - let contract_addr = - setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin.denom.to_string()], + vec![], + ); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); let extra_funds = Coin::new(25u128, "ucosmos"); let res = app.execute_contract( user.clone(), - contract_addr.clone(), + mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::NativeDeposit(asset.clone())], + actions: vec![Action::Deposit(coin)], }, &[Coin::new(234u128, "uosmo"), extra_funds.clone()], ); - assert_err(res, ExtraFundsReceived(AssetList::from(vec![extra_funds]))); + assert_err(res, ExtraFundsReceived(CoinList::from(&vec![extra_funds]))); - let res = query_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.assets.len(), 0); } #[test] -fn test_native_deposit_success() { +fn test_deposit_success() { let user = Addr::unchecked("user"); let funds = Coin::new(300u128, "uosmo"); - let info = AssetInfoUnchecked::native("uosmo"); - let amount = Uint128::from(234u128); - let asset = AssetUnchecked::new(info.clone(), amount); + + let coin = Coin { + denom: "uosmo".to_string(), + amount: Uint128::from(234u128), + }; let mut app = App::new(|router, _, storage| { router @@ -261,30 +280,37 @@ fn test_native_deposit_success() { .init_balance(storage, &user, vec![funds]) .unwrap(); }); - let contract_addr = - setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![info.clone()]); + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin.denom.clone()], + vec![], + ); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); app.execute_contract( user.clone(), - contract_addr.clone(), + mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::NativeDeposit(asset.clone())], + actions: vec![Action::Deposit(coin.clone())], }, &[Coin::new(234u128, "uosmo")], ) .unwrap(); - let res = query_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.assets.len(), 1); - assert_eq!(res.assets.first().unwrap().amount, amount); - assert_eq!(res.assets.first().unwrap().info, info); + assert_eq!(res.assets.first().unwrap().amount, coin.amount); + assert_eq!(res.assets.first().unwrap().denom, coin.denom); - let coin = app.wrap().query_balance(contract_addr, "uosmo").unwrap(); - assert_eq!(coin.amount, amount) + let coin = app + .wrap() + .query_balance(mock.credit_manager, "uosmo") + .unwrap(); + assert_eq!(coin.amount, coin.amount) } #[test] @@ -301,41 +327,52 @@ fn test_multiple_deposit_actions() { .unwrap(); }); - let asset_a = AssetUnchecked::new(AssetInfoUnchecked::native("uosmo"), Uint128::from(234u128)); - let asset_b = AssetUnchecked::new(AssetInfoUnchecked::native("ucosmos"), Uint128::from(25u128)); + let coin_a = Coin { + denom: "uosmo".to_string(), + amount: Uint128::from(234u128), + }; - let contract_addr = setup_credit_manager( + let coin_b = Coin { + denom: "ucosmos".to_string(), + amount: Uint128::from(25u128), + }; + + let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![asset_a.clone().info, asset_b.clone().info], + vec![coin_a.clone().denom, coin_b.clone().denom], + vec![], ); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); app.execute_contract( user.clone(), - contract_addr.clone(), + mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), actions: vec![ - Action::NativeDeposit(asset_a.clone()), - Action::NativeDeposit(asset_b.clone()), + Action::Deposit(coin_a.clone()), + Action::Deposit(coin_b.clone()), ], }, &[Coin::new(234u128, "uosmo"), Coin::new(25u128, "ucosmos")], ) .unwrap(); - let res = query_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.assets.len(), 2); let coin = app .wrap() - .query_balance(contract_addr.clone(), "uosmo") + .query_balance(mock.credit_manager.clone(), "uosmo") .unwrap(); assert_eq!(coin.amount, Uint128::from(234u128)); - let coin = app.wrap().query_balance(contract_addr, "ucosmos").unwrap(); + let coin = app + .wrap() + .query_balance(mock.credit_manager, "ucosmos") + .unwrap(); assert_eq!(coin.amount, Uint128::from(25u128)); } diff --git a/contracts/credit-manager/tests/dispatch.rs b/contracts/credit-manager/tests/dispatch_test.rs similarity index 71% rename from contracts/credit-manager/tests/dispatch.rs rename to contracts/credit-manager/tests/dispatch_test.rs index bb8429a08..e12bf9112 100644 --- a/contracts/credit-manager/tests/dispatch.rs +++ b/contracts/credit-manager/tests/dispatch_test.rs @@ -1,30 +1,32 @@ extern crate core; -use crate::helpers::{ - assert_err, get_token_id, mock_app, mock_create_credit_account, query_position, - setup_credit_manager, -}; use cosmwasm_std::Addr; use cw_multi_test::Executor; + use rover::error::ContractError::NotTokenOwner; use rover::msg::ExecuteMsg::UpdateCreditAccount; +use helpers::{ + assert_err, get_token_id, mock_app, mock_create_credit_account, query_position, + setup_credit_manager, +}; + pub mod helpers; #[test] fn test_dispatch_only_allowed_for_token_owner() { let mut app = mock_app(); let owner = Addr::unchecked("owner"); - let contract_addr = setup_credit_manager(&mut app, &owner, vec![]); + let mock = setup_credit_manager(&mut app, &owner, vec![], vec![]); let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); let bad_guy = Addr::unchecked("bad_guy"); let res = app.execute_contract( bad_guy.clone(), - contract_addr.clone(), + mock.credit_manager.clone(), &UpdateCreditAccount { token_id: token_id.clone(), actions: vec![], @@ -45,18 +47,18 @@ fn test_dispatch_only_allowed_for_token_owner() { fn test_nothing_happens_if_no_actions_are_passed() { let mut app = mock_app(); let owner = Addr::unchecked("owner"); - let contract_addr = setup_credit_manager(&mut app, &owner, vec![]); + let mock = setup_credit_manager(&mut app, &owner, vec![], vec![]); let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &contract_addr, &user).unwrap(); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); - let res = query_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.assets.len(), 0); app.execute_contract( user.clone(), - contract_addr.clone(), + mock.credit_manager.clone(), &UpdateCreditAccount { token_id: token_id.clone(), actions: vec![], @@ -65,6 +67,6 @@ fn test_nothing_happens_if_no_actions_are_passed() { ) .unwrap(); - let res = query_position(&app, &contract_addr, &token_id); + let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.assets.len(), 0); } diff --git a/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs b/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs new file mode 100644 index 000000000..9a993e131 --- /dev/null +++ b/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs @@ -0,0 +1,152 @@ +use cosmwasm_std::Addr; +use cw_multi_test::Executor; + +use rover::adapters::RedBankBase; +use rover::msg::{InstantiateMsg, QueryMsg}; + +use crate::helpers::{mock_app, mock_contract}; + +pub mod helpers; + +#[test] +fn test_pagination_on_allowed_coins_query_works() { + let mut app = mock_app(); + let code_id = app.store_code(mock_contract()); + let owner = Addr::unchecked("owner"); + + let allowed_coins = vec![ + "coin_1".to_string(), + "coin_2".to_string(), + "coin_3".to_string(), + "coin_4".to_string(), + "coin_5".to_string(), + "coin_6".to_string(), + "coin_7".to_string(), + "coin_8".to_string(), + "coin_9".to_string(), + "coin_10".to_string(), + "coin_11".to_string(), + "coin_12".to_string(), + "coin_13".to_string(), + "coin_14".to_string(), + "coin_15".to_string(), + "coin_16".to_string(), + "coin_17".to_string(), + "coin_18".to_string(), + "coin_19".to_string(), + "coin_20".to_string(), + "coin_21".to_string(), + "coin_22".to_string(), + "coin_23".to_string(), + "coin_24".to_string(), + "coin_25".to_string(), + "coin_26".to_string(), + "coin_27".to_string(), + "coin_28".to_string(), + "coin_29".to_string(), + "coin_30".to_string(), + "coin_31".to_string(), + "coin_32".to_string(), + ]; + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_coins: allowed_coins.clone(), + red_bank: RedBankBase("redbankaddr".to_string()), + }; + + let contract_addr = app + .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) + .unwrap(); + + let coins_res: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedCoins { + start_after: None, + limit: Some(58 as u32), + }, + ) + .unwrap(); + + // Assert maximum is observed + assert_eq!(coins_res.len(), 30); + + let coins_res: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedCoins { + start_after: None, + limit: Some(2 as u32), + }, + ) + .unwrap(); + + // Assert limit request is observed + assert_eq!(coins_res.len(), 2); + + let coins_res_a: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedCoins { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + let coins_res_b: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedCoins { + start_after: Some(coins_res_a.last().unwrap().clone()), + limit: None, + }, + ) + .unwrap(); + + let coins_res_c: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedCoins { + start_after: Some(coins_res_b.last().unwrap().clone()), + limit: None, + }, + ) + .unwrap(); + + let coins_res_d: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedCoins { + start_after: Some(coins_res_c.last().unwrap().clone()), + limit: None, + }, + ) + .unwrap(); + + // Assert default is observed + assert_eq!(coins_res_a.len(), 10); + assert_eq!(coins_res_b.len(), 10); + assert_eq!(coins_res_c.len(), 10); + + assert_eq!(coins_res_d.len(), 2); + + let combined: Vec = coins_res_a + .iter() + .cloned() + .chain(coins_res_b.iter().cloned()) + .chain(coins_res_c.iter().cloned()) + .chain(coins_res_d.iter().cloned()) + .collect(); + + assert_eq!(combined.len(), allowed_coins.len()); + assert!(allowed_coins.iter().all(|item| combined.contains(item))); +} diff --git a/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs b/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs new file mode 100644 index 000000000..a60428f42 --- /dev/null +++ b/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs @@ -0,0 +1,152 @@ +use cosmwasm_std::Addr; +use cw_multi_test::Executor; + +use rover::adapters::RedBankBase; +use rover::msg::{InstantiateMsg, QueryMsg}; + +use crate::helpers::{mock_app, mock_contract}; + +pub mod helpers; + +#[test] +fn test_pagination_on_allowed_vaults_query_works() { + let mut app = mock_app(); + let code_id = app.store_code(mock_contract()); + let owner = Addr::unchecked("owner"); + + let allowed_vaults = vec![ + "addr1".to_string(), + "addr2".to_string(), + "addr3".to_string(), + "addr4".to_string(), + "addr5".to_string(), + "addr6".to_string(), + "addr7".to_string(), + "addr8".to_string(), + "addr9".to_string(), + "addr10".to_string(), + "addr11".to_string(), + "addr12".to_string(), + "addr13".to_string(), + "addr14".to_string(), + "addr15".to_string(), + "addr16".to_string(), + "addr17".to_string(), + "addr18".to_string(), + "addr19".to_string(), + "addr20".to_string(), + "addr21".to_string(), + "addr22".to_string(), + "addr23".to_string(), + "addr24".to_string(), + "addr25".to_string(), + "addr26".to_string(), + "addr27".to_string(), + "addr28".to_string(), + "addr29".to_string(), + "addr30".to_string(), + "addr31".to_string(), + "addr32".to_string(), + ]; + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: allowed_vaults.clone(), + allowed_coins: vec![], + red_bank: RedBankBase("redbankaddr".to_string()), + }; + + let contract_addr = app + .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) + .unwrap(); + + let vaults_res: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: None, + limit: Some(58 as u32), + }, + ) + .unwrap(); + + // Assert maximum is observed + assert_eq!(vaults_res.len(), 30); + + let vaults_res: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: None, + limit: Some(2 as u32), + }, + ) + .unwrap(); + + // Assert limit request is observed + assert_eq!(vaults_res.len(), 2); + + let vaults_res_a: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + let vaults_res_b: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: Some(vaults_res_a.last().unwrap().clone()), + limit: None, + }, + ) + .unwrap(); + + let vaults_res_c: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: Some(vaults_res_b.last().unwrap().clone()), + limit: None, + }, + ) + .unwrap(); + + let vaults_res_d: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: Some(vaults_res_c.last().unwrap().clone()), + limit: None, + }, + ) + .unwrap(); + + // Assert default is observed + assert_eq!(vaults_res_a.len(), 10); + assert_eq!(vaults_res_b.len(), 10); + assert_eq!(vaults_res_c.len(), 10); + + assert_eq!(vaults_res_d.len(), 2); + + let combined: Vec = vaults_res_a + .iter() + .cloned() + .chain(vaults_res_b.iter().cloned()) + .chain(vaults_res_c.iter().cloned()) + .chain(vaults_res_d.iter().cloned()) + .collect(); + + assert_eq!(combined.len(), allowed_vaults.len()); + assert!(allowed_vaults.iter().all(|item| combined.contains(item))); +} diff --git a/contracts/credit-manager/tests/enumerate_assets_test.rs b/contracts/credit-manager/tests/enumerate_assets_test.rs new file mode 100644 index 000000000..9c8e28f68 --- /dev/null +++ b/contracts/credit-manager/tests/enumerate_assets_test.rs @@ -0,0 +1,278 @@ +use cosmwasm_std::{Addr, Coin, Uint128}; +use cw_multi_test::{App, Executor}; + +use rover::msg::execute::Action; +use rover::msg::query::AssetResponseItem; +use rover::msg::{ExecuteMsg, QueryMsg}; + +use crate::helpers::{get_token_id, mock_create_credit_account, setup_credit_manager}; + +pub mod helpers; + +#[test] +fn test_pagination_on_all_assets_query_works() { + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let user_c = Addr::unchecked("user_c"); + + let user_a_coins = vec![ + Coin::new(1u128, "coin_1"), + Coin::new(1u128, "coin_2"), + Coin::new(1u128, "coin_3"), + Coin::new(1u128, "coin_4"), + Coin::new(1u128, "coin_5"), + Coin::new(1u128, "coin_6"), + Coin::new(1u128, "coin_7"), + Coin::new(1u128, "coin_8"), + Coin::new(1u128, "coin_9"), + Coin::new(1u128, "coin_10"), + Coin::new(1u128, "coin_11"), + Coin::new(1u128, "coin_12"), + Coin::new(1u128, "coin_13"), + Coin::new(1u128, "coin_14"), + ]; + + let user_b_coins = vec![ + Coin::new(1u128, "coin_1"), + Coin::new(1u128, "coin_2"), + Coin::new(1u128, "coin_3"), + Coin::new(1u128, "coin_4"), + Coin::new(1u128, "coin_5"), + Coin::new(1u128, "coin_6"), + Coin::new(1u128, "coin_7"), + Coin::new(1u128, "coin_8"), + Coin::new(1u128, "coin_9"), + Coin::new(1u128, "coin_10"), + ]; + + let user_c_coins = vec![ + Coin::new(1u128, "coin_1"), + Coin::new(1u128, "coin_2"), + Coin::new(1u128, "coin_3"), + Coin::new(1u128, "coin_4"), + Coin::new(1u128, "coin_5"), + Coin::new(1u128, "coin_6"), + Coin::new(1u128, "coin_7"), + Coin::new(1u128, "coin_8"), + ]; + + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user_a, user_a_coins.clone()) + .unwrap(); + router + .bank + .init_balance(storage, &user_b, user_b_coins.clone()) + .unwrap(); + router + .bank + .init_balance(storage, &user_c, user_c_coins.clone()) + .unwrap(); + }); + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![ + "coin_1".to_string(), + "coin_2".to_string(), + "coin_3".to_string(), + "coin_4".to_string(), + "coin_5".to_string(), + "coin_6".to_string(), + "coin_7".to_string(), + "coin_8".to_string(), + "coin_9".to_string(), + "coin_10".to_string(), + "coin_11".to_string(), + "coin_12".to_string(), + "coin_13".to_string(), + "coin_14".to_string(), + ], + vec![], + ); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); + let token_id_a = get_token_id(res); + app.execute_contract( + user_a.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id_a.clone(), + actions: user_a_coins + .iter() + .map(|coin| Action::Deposit(coin.clone())) + .collect(), + }, + &user_a_coins, + ) + .unwrap(); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_b).unwrap(); + let token_id_b = get_token_id(res); + app.execute_contract( + user_b.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id_b.clone(), + actions: user_b_coins + .iter() + .map(|coin| Action::Deposit(coin.clone())) + .collect(), + }, + &user_b_coins, + ) + .unwrap(); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_c).unwrap(); + let token_id_c = get_token_id(res); + app.execute_contract( + user_c.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id_c.clone(), + actions: user_c_coins + .iter() + .map(|coin| Action::Deposit(coin.clone())) + .collect(), + }, + &user_c_coins, + ) + .unwrap(); + + let all_assets_res: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllAssets { + start_after: None, + limit: Some(58 as u32), + }, + ) + .unwrap(); + + // Assert maximum is observed + assert_eq!(all_assets_res.len(), 30); + + let all_assets_res: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllAssets { + start_after: None, + limit: Some(2 as u32), + }, + ) + .unwrap(); + + // Assert limit request is observed + assert_eq!(all_assets_res.len(), 2); + + let all_assets_res_a: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllAssets { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + let AssetResponseItem { + token_id, denom, .. + } = all_assets_res_a.last().unwrap().clone(); + let all_assets_res_b: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllAssets { + start_after: Some((token_id, denom)), + limit: None, + }, + ) + .unwrap(); + + let AssetResponseItem { + token_id, denom, .. + } = all_assets_res_b.last().unwrap().clone(); + let all_assets_res_c: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllAssets { + start_after: Some((token_id, denom)), + limit: None, + }, + ) + .unwrap(); + + let AssetResponseItem { + token_id, denom, .. + } = all_assets_res_c.last().unwrap().clone(); + let all_assets_res_d: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllAssets { + start_after: Some((token_id, denom)), + limit: None, + }, + ) + .unwrap(); + + // Assert default is observed + assert_eq!(all_assets_res_a.len(), 10); + assert_eq!(all_assets_res_b.len(), 10); + assert_eq!(all_assets_res_c.len(), 10); + + assert_eq!(all_assets_res_d.len(), 2); + + let combined_res: Vec = all_assets_res_a + .iter() + .cloned() + .chain(all_assets_res_b.iter().cloned()) + .chain(all_assets_res_c.iter().cloned()) + .chain(all_assets_res_d.iter().cloned()) + .collect(); + + let user_a_response_items = user_a_coins + .iter() + .map(|coin| AssetResponseItem { + token_id: token_id_a.clone(), + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }) + .collect::>(); + + let user_b_response_items = user_b_coins + .iter() + .map(|coin| AssetResponseItem { + token_id: token_id_b.clone(), + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }) + .collect::>(); + + let user_c_response_items = user_c_coins + .iter() + .map(|coin| AssetResponseItem { + token_id: token_id_c.clone(), + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }) + .collect::>(); + + let combined_starting_vals: Vec = user_a_response_items + .iter() + .cloned() + .chain(user_b_response_items) + .chain(user_c_response_items) + .collect(); + + assert_eq!(combined_res.len(), combined_starting_vals.len()); + assert!(combined_starting_vals + .iter() + .all(|item| combined_res.contains(item))); +} diff --git a/contracts/credit-manager/tests/enumerate_debt_shares_test.rs b/contracts/credit-manager/tests/enumerate_debt_shares_test.rs new file mode 100644 index 000000000..ae85bea0c --- /dev/null +++ b/contracts/credit-manager/tests/enumerate_debt_shares_test.rs @@ -0,0 +1,365 @@ +use cosmwasm_std::{Addr, Coin, Uint128}; +use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; +use cw_multi_test::{App, Executor}; + +use rover::msg::execute::Action; +use rover::msg::query::SharesResponseItem; +use rover::msg::{ExecuteMsg, QueryMsg}; + +use crate::helpers::{ + fund_red_bank_native, get_token_id, mock_create_credit_account, query_config, + setup_credit_manager, +}; + +pub mod helpers; + +#[test] +fn test_pagination_on_all_debt_shares_query_works() { + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let user_c = Addr::unchecked("user_c"); + + let user_a_coins = vec![ + Coin::new(10u128, "coin_1"), + Coin::new(10u128, "coin_2"), + Coin::new(10u128, "coin_3"), + Coin::new(10u128, "coin_4"), + Coin::new(10u128, "coin_5"), + Coin::new(10u128, "coin_6"), + Coin::new(10u128, "coin_7"), + Coin::new(10u128, "coin_8"), + Coin::new(10u128, "coin_9"), + Coin::new(10u128, "coin_10"), + Coin::new(10u128, "coin_11"), + Coin::new(10u128, "coin_12"), + Coin::new(10u128, "coin_13"), + Coin::new(10u128, "coin_14"), + ]; + + let user_b_coins = vec![ + Coin::new(10u128, "coin_15"), + Coin::new(10u128, "coin_16"), + Coin::new(10u128, "coin_17"), + Coin::new(10u128, "coin_18"), + Coin::new(10u128, "coin_19"), + Coin::new(10u128, "coin_20"), + Coin::new(10u128, "coin_21"), + Coin::new(10u128, "coin_22"), + Coin::new(10u128, "coin_23"), + Coin::new(10u128, "coin_24"), + ]; + + let user_c_coins = vec![ + Coin::new(10u128, "coin_25"), + Coin::new(10u128, "coin_26"), + Coin::new(10u128, "coin_27"), + Coin::new(10u128, "coin_28"), + Coin::new(10u128, "coin_29"), + Coin::new(10u128, "coin_30"), + Coin::new(10u128, "coin_31"), + Coin::new(10u128, "coin_32"), + ]; + + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user_a, user_a_coins.clone()) + .unwrap(); + router + .bank + .init_balance(storage, &user_b, user_b_coins.clone()) + .unwrap(); + router + .bank + .init_balance(storage, &user_c, user_c_coins.clone()) + .unwrap(); + }); + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![ + "coin_1".to_string(), + "coin_2".to_string(), + "coin_3".to_string(), + "coin_4".to_string(), + "coin_5".to_string(), + "coin_6".to_string(), + "coin_7".to_string(), + "coin_8".to_string(), + "coin_9".to_string(), + "coin_10".to_string(), + "coin_11".to_string(), + "coin_12".to_string(), + "coin_13".to_string(), + "coin_14".to_string(), + "coin_15".to_string(), + "coin_16".to_string(), + "coin_17".to_string(), + "coin_18".to_string(), + "coin_19".to_string(), + "coin_20".to_string(), + "coin_21".to_string(), + "coin_22".to_string(), + "coin_23".to_string(), + "coin_24".to_string(), + "coin_25".to_string(), + "coin_26".to_string(), + "coin_27".to_string(), + "coin_28".to_string(), + "coin_29".to_string(), + "coin_30".to_string(), + "coin_31".to_string(), + "coin_32".to_string(), + ], + vec![], + ); + + let config = query_config(&mut app, &mock.credit_manager.clone()); + + fund_red_bank_native( + &mut app, + config.red_bank.clone(), + vec![ + Coin::new(1000u128, "coin_1"), + Coin::new(1000u128, "coin_2"), + Coin::new(1000u128, "coin_3"), + Coin::new(1000u128, "coin_4"), + Coin::new(1000u128, "coin_5"), + Coin::new(1000u128, "coin_6"), + Coin::new(1000u128, "coin_7"), + Coin::new(1000u128, "coin_8"), + Coin::new(1000u128, "coin_9"), + Coin::new(1000u128, "coin_10"), + Coin::new(1000u128, "coin_11"), + Coin::new(1000u128, "coin_12"), + Coin::new(1000u128, "coin_13"), + Coin::new(1000u128, "coin_14"), + Coin::new(1000u128, "coin_15"), + Coin::new(1000u128, "coin_16"), + Coin::new(1000u128, "coin_17"), + Coin::new(1000u128, "coin_18"), + Coin::new(1000u128, "coin_19"), + Coin::new(1000u128, "coin_20"), + Coin::new(1000u128, "coin_21"), + Coin::new(1000u128, "coin_22"), + Coin::new(1000u128, "coin_23"), + Coin::new(1000u128, "coin_24"), + Coin::new(1000u128, "coin_25"), + Coin::new(1000u128, "coin_26"), + Coin::new(1000u128, "coin_27"), + Coin::new(1000u128, "coin_28"), + Coin::new(1000u128, "coin_29"), + Coin::new(1000u128, "coin_30"), + Coin::new(1000u128, "coin_31"), + Coin::new(1000u128, "coin_32"), + ], + ); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); + let token_id_a = get_token_id(res); + app.execute_contract( + user_a.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id_a.clone(), + actions: user_a_coins + .iter() + .flat_map(|coin| { + vec![ + Action::Deposit(coin.clone()), + Action::Borrow(Coin { + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }), + ] + }) + .collect::>(), + }, + &user_a_coins, + ) + .unwrap(); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_b).unwrap(); + let token_id_b = get_token_id(res); + app.execute_contract( + user_b.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id_b.clone(), + actions: user_b_coins + .iter() + .flat_map(|coin| { + vec![ + Action::Deposit(coin.clone()), + Action::Borrow(Coin { + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }), + ] + }) + .collect::>(), + }, + &user_b_coins, + ) + .unwrap(); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_c).unwrap(); + let token_id_c = get_token_id(res); + app.execute_contract( + user_c.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id_c.clone(), + actions: user_c_coins + .iter() + .flat_map(|coin| { + vec![ + Action::Deposit(coin.clone()), + Action::Borrow(Coin { + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }), + ] + }) + .collect::>(), + }, + &user_c_coins, + ) + .unwrap(); + + let all_debt_shares_res: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllDebtShares { + start_after: None, + limit: Some(58 as u32), + }, + ) + .unwrap(); + + // Assert maximum is observed + assert_eq!(all_debt_shares_res.len(), 30); + + let all_debt_shares_res: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllDebtShares { + start_after: None, + limit: Some(2 as u32), + }, + ) + .unwrap(); + + // Assert limit request is observed + assert_eq!(all_debt_shares_res.len(), 2); + + let all_debt_shares_res_a: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllDebtShares { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + let SharesResponseItem { + token_id, denom, .. + } = all_debt_shares_res_a.last().unwrap().clone(); + let all_debt_shares_res_b: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllDebtShares { + start_after: Some((token_id, denom)), + limit: None, + }, + ) + .unwrap(); + + let SharesResponseItem { + token_id, denom, .. + } = all_debt_shares_res_b.last().unwrap().clone(); + let all_debt_shares_res_c: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllDebtShares { + start_after: Some((token_id, denom)), + limit: None, + }, + ) + .unwrap(); + + let SharesResponseItem { + token_id, denom, .. + } = all_debt_shares_res_c.last().unwrap().clone(); + let all_debt_shares_res_d: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllDebtShares { + start_after: Some((token_id, denom)), + limit: None, + }, + ) + .unwrap(); + + // Assert default is observed + assert_eq!(all_debt_shares_res_a.len(), 10); + assert_eq!(all_debt_shares_res_b.len(), 10); + assert_eq!(all_debt_shares_res_c.len(), 10); + + assert_eq!(all_debt_shares_res_d.len(), 2); + + let combined_res: Vec = all_debt_shares_res_a + .iter() + .cloned() + .chain(all_debt_shares_res_b.iter().cloned()) + .chain(all_debt_shares_res_c.iter().cloned()) + .chain(all_debt_shares_res_d.iter().cloned()) + .collect(); + + let user_a_response_items = user_a_coins + .iter() + .map(|coin| SharesResponseItem { + token_id: token_id_a.clone(), + denom: coin.denom.clone(), + shares: Uint128::from(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED), + }) + .collect::>(); + + let user_b_response_items = user_b_coins + .iter() + .map(|coin| SharesResponseItem { + token_id: token_id_b.clone(), + denom: coin.denom.clone(), + shares: Uint128::from(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED), + }) + .collect::>(); + + let user_c_response_items = user_c_coins + .iter() + .map(|coin| SharesResponseItem { + token_id: token_id_c.clone(), + denom: coin.denom.clone(), + shares: Uint128::from(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED), + }) + .collect::>(); + + let combined_starting_vals: Vec = user_a_response_items + .iter() + .cloned() + .chain(user_b_response_items) + .chain(user_c_response_items) + .collect(); + + assert_eq!(combined_res.len(), combined_starting_vals.len()); + assert!(combined_starting_vals + .iter() + .all(|item| combined_res.contains(item))); +} diff --git a/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs b/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs new file mode 100644 index 000000000..8239ea2a4 --- /dev/null +++ b/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs @@ -0,0 +1,356 @@ +use cosmwasm_std::{Addr, Coin, Uint128}; +use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; +use cw_multi_test::{App, Executor}; + +use rover::msg::execute::Action; +use rover::msg::query::CoinShares; +use rover::msg::{ExecuteMsg, QueryMsg}; + +use crate::helpers::{ + fund_red_bank_native, get_token_id, mock_create_credit_account, query_config, + setup_credit_manager, +}; + +pub mod helpers; + +#[test] +fn test_pagination_on_all_total_debt_shares_query_works() { + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let user_c = Addr::unchecked("user_c"); + + let user_a_coins = vec![ + Coin::new(10u128, "coin_1"), + Coin::new(10u128, "coin_2"), + Coin::new(10u128, "coin_3"), + Coin::new(10u128, "coin_4"), + Coin::new(10u128, "coin_5"), + Coin::new(10u128, "coin_6"), + Coin::new(10u128, "coin_7"), + Coin::new(10u128, "coin_8"), + Coin::new(10u128, "coin_9"), + Coin::new(10u128, "coin_10"), + Coin::new(10u128, "coin_11"), + Coin::new(10u128, "coin_12"), + Coin::new(10u128, "coin_13"), + Coin::new(10u128, "coin_14"), + ]; + + let user_b_coins = vec![ + Coin::new(10u128, "coin_15"), + Coin::new(10u128, "coin_16"), + Coin::new(10u128, "coin_17"), + Coin::new(10u128, "coin_18"), + Coin::new(10u128, "coin_19"), + Coin::new(10u128, "coin_20"), + Coin::new(10u128, "coin_21"), + Coin::new(10u128, "coin_22"), + Coin::new(10u128, "coin_23"), + Coin::new(10u128, "coin_24"), + ]; + + let user_c_coins = vec![ + Coin::new(10u128, "coin_25"), + Coin::new(10u128, "coin_26"), + Coin::new(10u128, "coin_27"), + Coin::new(10u128, "coin_28"), + Coin::new(10u128, "coin_29"), + Coin::new(10u128, "coin_30"), + Coin::new(10u128, "coin_31"), + Coin::new(10u128, "coin_32"), + ]; + + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user_a, user_a_coins.clone()) + .unwrap(); + router + .bank + .init_balance(storage, &user_b, user_b_coins.clone()) + .unwrap(); + router + .bank + .init_balance(storage, &user_c, user_c_coins.clone()) + .unwrap(); + }); + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![ + "coin_1".to_string(), + "coin_2".to_string(), + "coin_3".to_string(), + "coin_4".to_string(), + "coin_5".to_string(), + "coin_6".to_string(), + "coin_7".to_string(), + "coin_8".to_string(), + "coin_9".to_string(), + "coin_10".to_string(), + "coin_11".to_string(), + "coin_12".to_string(), + "coin_13".to_string(), + "coin_14".to_string(), + "coin_15".to_string(), + "coin_16".to_string(), + "coin_17".to_string(), + "coin_18".to_string(), + "coin_19".to_string(), + "coin_20".to_string(), + "coin_21".to_string(), + "coin_22".to_string(), + "coin_23".to_string(), + "coin_24".to_string(), + "coin_25".to_string(), + "coin_26".to_string(), + "coin_27".to_string(), + "coin_28".to_string(), + "coin_29".to_string(), + "coin_30".to_string(), + "coin_31".to_string(), + "coin_32".to_string(), + ], + vec![], + ); + + let config = query_config(&mut app, &mock.credit_manager.clone()); + + fund_red_bank_native( + &mut app, + config.red_bank.clone(), + vec![ + Coin::new(1000u128, "coin_1"), + Coin::new(1000u128, "coin_2"), + Coin::new(1000u128, "coin_3"), + Coin::new(1000u128, "coin_4"), + Coin::new(1000u128, "coin_5"), + Coin::new(1000u128, "coin_6"), + Coin::new(1000u128, "coin_7"), + Coin::new(1000u128, "coin_8"), + Coin::new(1000u128, "coin_9"), + Coin::new(1000u128, "coin_10"), + Coin::new(1000u128, "coin_11"), + Coin::new(1000u128, "coin_12"), + Coin::new(1000u128, "coin_13"), + Coin::new(1000u128, "coin_14"), + Coin::new(1000u128, "coin_15"), + Coin::new(1000u128, "coin_16"), + Coin::new(1000u128, "coin_17"), + Coin::new(1000u128, "coin_18"), + Coin::new(1000u128, "coin_19"), + Coin::new(1000u128, "coin_20"), + Coin::new(1000u128, "coin_21"), + Coin::new(1000u128, "coin_22"), + Coin::new(1000u128, "coin_23"), + Coin::new(1000u128, "coin_24"), + Coin::new(1000u128, "coin_25"), + Coin::new(1000u128, "coin_26"), + Coin::new(1000u128, "coin_27"), + Coin::new(1000u128, "coin_28"), + Coin::new(1000u128, "coin_29"), + Coin::new(1000u128, "coin_30"), + Coin::new(1000u128, "coin_31"), + Coin::new(1000u128, "coin_32"), + ], + ); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); + let token_id_a = get_token_id(res); + app.execute_contract( + user_a.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id_a.clone(), + actions: user_a_coins + .iter() + .flat_map(|coin| { + vec![ + Action::Deposit(coin.clone()), + Action::Borrow(Coin { + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }), + ] + }) + .collect::>(), + }, + &user_a_coins, + ) + .unwrap(); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_b).unwrap(); + let token_id_b = get_token_id(res); + app.execute_contract( + user_b.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id_b.clone(), + actions: user_b_coins + .iter() + .flat_map(|coin| { + vec![ + Action::Deposit(coin.clone()), + Action::Borrow(Coin { + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }), + ] + }) + .collect::>(), + }, + &user_b_coins, + ) + .unwrap(); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_c).unwrap(); + let token_id_c = get_token_id(res); + app.execute_contract( + user_c.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id_c.clone(), + actions: user_c_coins + .iter() + .flat_map(|coin| { + vec![ + Action::Deposit(coin.clone()), + Action::Borrow(Coin { + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }), + ] + }) + .collect::>(), + }, + &user_c_coins, + ) + .unwrap(); + + let all_total_debt_shares_res: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllTotalDebtShares { + start_after: None, + limit: Some(58 as u32), + }, + ) + .unwrap(); + + // Assert maximum is observed + assert_eq!(all_total_debt_shares_res.len(), 30); + + let all_total_debt_shares_res: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllTotalDebtShares { + start_after: None, + limit: Some(2 as u32), + }, + ) + .unwrap(); + + // Assert limit request is observed + assert_eq!(all_total_debt_shares_res.len(), 2); + + let all_total_debt_shares_res_a: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllTotalDebtShares { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + let CoinShares { denom, .. } = all_total_debt_shares_res_a.last().unwrap().clone(); + let all_total_debt_shares_res_b: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllTotalDebtShares { + start_after: Some(denom), + limit: None, + }, + ) + .unwrap(); + + let CoinShares { denom, .. } = all_total_debt_shares_res_b.last().unwrap().clone(); + let all_total_debt_shares_res_c: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllTotalDebtShares { + start_after: Some(denom), + limit: None, + }, + ) + .unwrap(); + + let CoinShares { denom, .. } = all_total_debt_shares_res_c.last().unwrap().clone(); + let all_total_debt_shares_res_d: Vec = app + .wrap() + .query_wasm_smart( + mock.credit_manager.clone(), + &QueryMsg::AllTotalDebtShares { + start_after: Some(denom), + limit: None, + }, + ) + .unwrap(); + + // Assert default is observed + assert_eq!(all_total_debt_shares_res_a.len(), 10); + assert_eq!(all_total_debt_shares_res_b.len(), 10); + assert_eq!(all_total_debt_shares_res_c.len(), 10); + + assert_eq!(all_total_debt_shares_res_d.len(), 2); + + let combined_res: Vec = all_total_debt_shares_res_a + .iter() + .cloned() + .chain(all_total_debt_shares_res_b.iter().cloned()) + .chain(all_total_debt_shares_res_c.iter().cloned()) + .chain(all_total_debt_shares_res_d.iter().cloned()) + .collect(); + + let user_a_response_items = user_a_coins + .iter() + .map(|coin| CoinShares { + denom: coin.denom.clone(), + shares: Uint128::from(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED), + }) + .collect::>(); + + let user_b_response_items = user_b_coins + .iter() + .map(|coin| CoinShares { + denom: coin.denom.clone(), + shares: Uint128::from(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED), + }) + .collect::>(); + + let user_c_response_items = user_c_coins + .iter() + .map(|coin| CoinShares { + denom: coin.denom.clone(), + shares: Uint128::from(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED), + }) + .collect::>(); + + let combined_starting_vals: Vec = user_a_response_items + .iter() + .cloned() + .chain(user_b_response_items) + .chain(user_c_response_items) + .collect(); + + assert_eq!(combined_res.len(), combined_starting_vals.len()); + assert!(combined_starting_vals + .iter() + .all(|item| combined_res.contains(item))); +} diff --git a/contracts/credit-manager/tests/helpers.rs b/contracts/credit-manager/tests/helpers.rs deleted file mode 100644 index 092e1d098..000000000 --- a/contracts/credit-manager/tests/helpers.rs +++ /dev/null @@ -1,213 +0,0 @@ -use anyhow::Result as AnyResult; -use cosmwasm_std::{Addr, Empty}; -use cw20::Cw20Coin; -use cw20_base::contract::{ - execute as cw20Execute, instantiate as cw20Instantiate, query as cw20Query, -}; -use cw20_base::msg::InstantiateMsg as cw20InstantiateMsg; -use cw721_base::InstantiateMsg as NftInstantiateMsg; -use cw_asset::AssetInfoUnchecked; -use cw_multi_test::{App, AppResponse, Contract, ContractWrapper, Executor}; - -use account_nft::contract::{ - execute as cw721Execute, instantiate as cw721Instantiate, query as cw721Query, -}; -use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use credit_manager::contract::{execute, instantiate, query}; -use mock_red_bank::contract::{ - execute as redBankExecute, instantiate as redBankInstantiate, query as redBankQuery, -}; -use rover::adapters::RedBankBase; -use rover::error::ContractError; -use rover::msg::execute::ExecuteMsg::{CreateCreditAccount, UpdateConfig}; -use rover::msg::query::{ConfigResponse, PositionResponse, QueryMsg}; -use rover::msg::InstantiateMsg; - -pub fn mock_app() -> App { - App::default() -} - -pub fn mock_contract() -> Box> { - let contract = ContractWrapper::new(execute, instantiate, query); - Box::new(contract) -} - -pub fn mock_account_nft_contract() -> Box> { - let contract = ContractWrapper::new(cw721Execute, cw721Instantiate, cw721Query); - Box::new(contract) -} - -pub fn mock_cw20_contract() -> Box> { - let contract = ContractWrapper::new(cw20Execute, cw20Instantiate, cw20Query); - Box::new(contract) -} - -pub fn mock_red_bank_contract() -> Box> { - let contract = ContractWrapper::new(redBankExecute, redBankInstantiate, redBankQuery); - Box::new(contract) -} - -pub fn mock_create_credit_account( - app: &mut App, - manager_contract_addr: &Addr, - user: &Addr, -) -> AnyResult { - app.execute_contract( - user.clone(), - manager_contract_addr.clone(), - &CreateCreditAccount {}, - &[], - ) -} - -pub fn deploy_mock_cw20(app: &mut App, symbol: &str, initial_balances: Vec) -> Addr { - let code_id = app.store_code(mock_cw20_contract()); - app.instantiate_contract( - code_id, - Addr::unchecked("cw20-instantiator"), - &cw20InstantiateMsg { - name: format!("Token: {}", symbol.clone()), - symbol: symbol.to_string(), - decimals: 9, - initial_balances, - mint: None, - marketing: None, - }, - &[], - "mock-cw20", - None, - ) - .unwrap() -} - -pub fn transfer_nft_contract_ownership( - app: &mut App, - owner: &Addr, - nft_contract_addr: &Addr, - manager_contract_addr: &Addr, -) { - let proposal_msg: NftExecuteMsg = NftExecuteMsg::ProposeNewOwner { - new_owner: manager_contract_addr.to_string(), - }; - app.execute_contract(owner.clone(), nft_contract_addr.clone(), &proposal_msg, &[]) - .unwrap(); - - app.execute_contract( - owner.clone(), - manager_contract_addr.clone(), - &UpdateConfig { - account_nft: Some(nft_contract_addr.to_string()), - owner: None, - red_bank: None, - }, - &[], - ) - .unwrap(); -} - -pub fn setup_red_bank(app: &mut App) -> Addr { - let contract_code_id = app.store_code(mock_red_bank_contract()); - app.instantiate_contract( - contract_code_id, - Addr::unchecked("red_bank_contract_owner"), - &Empty {}, - &[], - "mock-red-bank", - None, - ) - .unwrap() -} - -pub fn setup_nft_contract(app: &mut App, owner: &Addr, manager_contract_addr: &Addr) -> Addr { - let nft_contract_code_id = app.store_code(mock_account_nft_contract()); - let nft_contract_addr = app - .instantiate_contract( - nft_contract_code_id, - owner.clone(), - &NftInstantiateMsg { - name: "Rover Credit Account".to_string(), - symbol: "RCA".to_string(), - minter: owner.to_string(), - }, - &[], - "manager-mock-account-nft", - None, - ) - .unwrap(); - - transfer_nft_contract_ownership(app, owner, &nft_contract_addr, &manager_contract_addr); - nft_contract_addr -} - -pub fn setup_credit_manager( - mut app: &mut App, - owner: &Addr, - allowed_assets: Vec, -) -> Addr { - let credit_manager_code_id = app.store_code(mock_contract()); - let red_bank_addr = setup_red_bank(app); - let manager_initiate_msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec![], - allowed_assets, - red_bank: RedBankBase(red_bank_addr.to_string()), - }; - - let manager_contract_addr = app - .instantiate_contract( - credit_manager_code_id, - owner.clone(), - &manager_initiate_msg, - &[], - "manager-mock", - None, - ) - .unwrap(); - - setup_nft_contract(&mut app, &owner, &manager_contract_addr); - manager_contract_addr -} - -pub fn get_token_id(res: AppResponse) -> String { - let attr: Vec<&String> = res - .events - .iter() - .flat_map(|event| &event.attributes) - .filter(|attr| attr.key == "token_id") - .map(|attr| &attr.value) - .collect(); - - assert_eq!(attr.len(), 1); - attr.first().unwrap().to_string() -} - -pub fn query_position( - app: &App, - manager_contract_addr: &Addr, - token_id: &String, -) -> PositionResponse { - app.wrap() - .query_wasm_smart( - manager_contract_addr.clone(), - &QueryMsg::Position { - token_id: token_id.clone(), - }, - ) - .unwrap() -} - -pub fn assert_err(res: AnyResult, err: ContractError) { - match res { - Ok(_) => panic!("Result was not an error"), - Err(generic_err) => { - let contract_err: ContractError = generic_err.downcast().unwrap(); - assert_eq!(contract_err, err); - } - } -} - -pub fn query_config(app: &mut App, contract_addr: &Addr) -> ConfigResponse { - app.wrap() - .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) - .unwrap() -} diff --git a/contracts/credit-manager/tests/helpers/assertions.rs b/contracts/credit-manager/tests/helpers/assertions.rs new file mode 100644 index 000000000..d1b5cdd5d --- /dev/null +++ b/contracts/credit-manager/tests/helpers/assertions.rs @@ -0,0 +1,20 @@ +use anyhow::Result as AnyResult; +use cw_multi_test::AppResponse; + +use rover::error::ContractError; + +pub fn assert_err(res: AnyResult, err: ContractError) { + match res { + Ok(_) => panic!("Result was not an error"), + Err(generic_err) => { + let contract_err: ContractError = generic_err.downcast().unwrap(); + assert_eq!(contract_err, err); + } + } +} + +pub fn assert_contents_equal(vec_a: Vec, vec_b: Vec) { + assert_eq!(vec_a.len(), vec_b.len()); + assert!(vec_a.iter().all(|item| vec_b.contains(item))); + assert!(vec_b.iter().all(|item| vec_a.contains(item))); +} diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs new file mode 100644 index 000000000..ee869b2e8 --- /dev/null +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -0,0 +1,29 @@ +use cosmwasm_std::Empty; +use cw_multi_test::{App, Contract, ContractWrapper}; + +use account_nft::contract::{ + execute as cw721Execute, instantiate as cw721Instantiate, query as cw721Query, +}; +use credit_manager::contract::{execute, instantiate, query}; +use mock_red_bank::contract::{ + execute as redBankExecute, instantiate as redBankInstantiate, query as redBankQuery, +}; + +pub fn mock_app() -> App { + App::default() +} + +pub fn mock_contract() -> Box> { + let contract = ContractWrapper::new(execute, instantiate, query); + Box::new(contract) +} + +pub fn mock_account_nft_contract() -> Box> { + let contract = ContractWrapper::new(cw721Execute, cw721Instantiate, cw721Query); + Box::new(contract) +} + +pub fn mock_red_bank_contract() -> Box> { + let contract = ContractWrapper::new(redBankExecute, redBankInstantiate, redBankQuery); + Box::new(contract) +} diff --git a/contracts/credit-manager/tests/helpers/deploys.rs b/contracts/credit-manager/tests/helpers/deploys.rs new file mode 100644 index 000000000..0c11aea41 --- /dev/null +++ b/contracts/credit-manager/tests/helpers/deploys.rs @@ -0,0 +1,131 @@ +use anyhow::Result as AnyResult; +use cosmwasm_std::{Addr, Coin, Empty}; +use cw721_base::InstantiateMsg as NftInstantiateMsg; +use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; + +use account_nft::msg::ExecuteMsg as NftExecuteMsg; +use rover::adapters::RedBankBase; +use rover::msg::execute::ExecuteMsg; +use rover::msg::instantiate::ConfigUpdates; +use rover::msg::InstantiateMsg; + +use crate::helpers::contracts::{mock_account_nft_contract, mock_contract, mock_red_bank_contract}; +use crate::helpers::types::MockEnv; + +pub fn mock_create_credit_account( + app: &mut App, + manager_contract_addr: &Addr, + user: &Addr, +) -> AnyResult { + app.execute_contract( + user.clone(), + manager_contract_addr.clone(), + &ExecuteMsg::CreateCreditAccount {}, + &[], + ) +} + +pub fn transfer_nft_contract_ownership( + app: &mut App, + owner: &Addr, + nft_contract_addr: &Addr, + manager_contract_addr: &Addr, +) { + let proposal_msg: NftExecuteMsg = NftExecuteMsg::ProposeNewOwner { + new_owner: manager_contract_addr.to_string(), + }; + app.execute_contract(owner.clone(), nft_contract_addr.clone(), &proposal_msg, &[]) + .unwrap(); + + app.execute_contract( + owner.clone(), + manager_contract_addr.clone(), + &ExecuteMsg::UpdateConfig { + new_config: ConfigUpdates { + account_nft: Some(nft_contract_addr.to_string()), + owner: None, + allowed_vaults: None, + allowed_coins: None, + red_bank: None, + }, + }, + &[], + ) + .unwrap(); +} + +pub fn setup_red_bank(app: &mut App) -> Addr { + let contract_code_id = app.store_code(mock_red_bank_contract()); + app.instantiate_contract( + contract_code_id, + Addr::unchecked("red_bank_contract_owner"), + &Empty {}, + &[], + "mock-red-bank", + None, + ) + .unwrap() +} + +pub fn setup_nft_contract(app: &mut App, owner: &Addr, manager_contract_addr: &Addr) -> Addr { + let nft_contract_code_id = app.store_code(mock_account_nft_contract()); + let nft_contract_addr = app + .instantiate_contract( + nft_contract_code_id, + owner.clone(), + &NftInstantiateMsg { + name: "Rover Credit Account".to_string(), + symbol: "RCA".to_string(), + minter: owner.to_string(), + }, + &[], + "manager-mock-account-nft", + None, + ) + .unwrap(); + + transfer_nft_contract_ownership(app, owner, &nft_contract_addr, &manager_contract_addr); + nft_contract_addr +} + +pub fn setup_credit_manager( + mut app: &mut App, + owner: &Addr, + allowed_assets: Vec, + allowed_vaults: Vec, +) -> MockEnv { + let credit_manager_code_id = app.store_code(mock_contract()); + let red_bank = setup_red_bank(app); + let manager_initiate_msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults, + red_bank: RedBankBase(red_bank.to_string()), + allowed_coins: allowed_assets, + }; + + let credit_manager = app + .instantiate_contract( + credit_manager_code_id, + owner.clone(), + &manager_initiate_msg, + &[], + "manager-mock", + None, + ) + .unwrap(); + + let nft = setup_nft_contract(&mut app, &owner, &credit_manager); + MockEnv { + credit_manager, + red_bank, + nft, + } +} + +pub fn fund_red_bank_native(app: &mut BasicApp, red_bank_addr: String, funds: Vec) { + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: red_bank_addr, + amount: funds, + })) + .unwrap(); +} diff --git a/contracts/credit-manager/tests/helpers/mod.rs b/contracts/credit-manager/tests/helpers/mod.rs new file mode 100644 index 000000000..86123ab8e --- /dev/null +++ b/contracts/credit-manager/tests/helpers/mod.rs @@ -0,0 +1,11 @@ +pub use self::assertions::*; +pub use self::contracts::*; +pub use self::deploys::*; +pub use self::queries::*; +pub use self::types::*; + +mod assertions; +mod contracts; +mod deploys; +mod queries; +mod types; diff --git a/contracts/credit-manager/tests/helpers/queries.rs b/contracts/credit-manager/tests/helpers/queries.rs new file mode 100644 index 000000000..f949b9293 --- /dev/null +++ b/contracts/credit-manager/tests/helpers/queries.rs @@ -0,0 +1,38 @@ +use cosmwasm_std::Addr; +use cw_multi_test::{App, AppResponse}; + +use rover::msg::query::{ConfigResponse, PositionResponse, QueryMsg}; + +pub fn get_token_id(res: AppResponse) -> String { + let attr: Vec<&String> = res + .events + .iter() + .flat_map(|event| &event.attributes) + .filter(|attr| attr.key == "token_id") + .map(|attr| &attr.value) + .collect(); + + assert_eq!(attr.len(), 1); + attr.first().unwrap().to_string() +} + +pub fn query_position( + app: &App, + manager_contract_addr: &Addr, + token_id: &String, +) -> PositionResponse { + app.wrap() + .query_wasm_smart( + manager_contract_addr.clone(), + &QueryMsg::Position { + token_id: token_id.clone(), + }, + ) + .unwrap() +} + +pub fn query_config(app: &mut App, contract_addr: &Addr) -> ConfigResponse { + app.wrap() + .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) + .unwrap() +} diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs new file mode 100644 index 000000000..dde7f3c57 --- /dev/null +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -0,0 +1,10 @@ +use cosmwasm_std::Addr; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct MockEnv { + pub credit_manager: Addr, + pub red_bank: Addr, + pub nft: Addr, +} diff --git a/contracts/credit-manager/tests/instantiate.rs b/contracts/credit-manager/tests/instantiate_test.rs similarity index 73% rename from contracts/credit-manager/tests/instantiate.rs rename to contracts/credit-manager/tests/instantiate_test.rs index 91d10848b..1baf85467 100644 --- a/contracts/credit-manager/tests/instantiate.rs +++ b/contracts/credit-manager/tests/instantiate_test.rs @@ -1,12 +1,11 @@ use cosmwasm_std::Addr; -use cw_asset::AssetInfoUnchecked; use cw_multi_test::Executor; -use rover::adapters::RedBankBase; +use rover::adapters::RedBankBase; use rover::msg::query::{ConfigResponse, QueryMsg}; use rover::msg::InstantiateMsg; -use crate::helpers::{mock_app, mock_contract}; +use crate::helpers::{assert_contents_equal, mock_app, mock_contract}; pub mod helpers; @@ -19,7 +18,7 @@ fn test_owner_set_on_instantiate() { let msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], - allowed_assets: vec![], + allowed_coins: vec![], red_bank: RedBankBase("redbankaddr".to_string()), }; @@ -35,6 +34,33 @@ fn test_owner_set_on_instantiate() { assert_eq!(owner, res.owner); } +#[test] +fn test_raises_on_invalid_owner_addr() { + let mut app = mock_app(); + let manager_code_id = app.store_code(mock_contract()); + let owner = Addr::unchecked("%%%INVALID%%%"); + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_coins: vec![], + red_bank: RedBankBase("redbankaddr".to_string()), + }; + + let instantiate_res = app.instantiate_contract( + manager_code_id, + owner.clone(), + &msg, + &[], + "mock-contract", + None, + ); + + if instantiate_res.is_ok() { + panic!("Should have thrown an error"); + } +} + #[test] fn test_nft_contract_addr_not_set_on_instantiate() { let mut app = mock_app(); @@ -48,7 +74,7 @@ fn test_nft_contract_addr_not_set_on_instantiate() { &InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], - allowed_assets: vec![], + allowed_coins: vec![], red_bank: RedBankBase("redbankaddr".to_string()), }, &[], @@ -66,7 +92,7 @@ fn test_nft_contract_addr_not_set_on_instantiate() { } #[test] -fn test_allowed_vaults_and_assets_stored_on_instantiate() { +fn test_allowed_vaults_set_on_instantiate() { let mut app = mock_app(); let code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); @@ -77,17 +103,10 @@ fn test_allowed_vaults_and_assets_stored_on_instantiate() { "vaultcontract3".to_string(), ]; - let allowed_assets = vec![ - AssetInfoUnchecked::Native("uosmo".to_string()), - AssetInfoUnchecked::Cw20("osmo85wwjycfxjlaxsae9asmxlk3bsgxbw".to_string()), - AssetInfoUnchecked::Cw20("osmompbtkt3jezatztteo577lxkqbkdyke".to_string()), - AssetInfoUnchecked::Cw20("osmos6kmpxz9xcstleqnu2fnz8gskgf6gx".to_string()), - ]; - let msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: allowed_vaults.clone(), - allowed_assets: allowed_assets.clone(), + allowed_coins: vec![], red_bank: RedBankBase("redbankaddr".to_string()), }; @@ -102,33 +121,91 @@ fn test_allowed_vaults_and_assets_stored_on_instantiate() { ) .unwrap(); - let assets_res: Vec = app + let vaults_res: Vec = app .wrap() .query_wasm_smart( contract_addr.clone(), - &QueryMsg::AllowedAssets { + &QueryMsg::AllowedVaults { start_after: None, limit: None, }, ) .unwrap(); - assert_eq!(assets_res.len(), 4); - assert!(allowed_assets.iter().all(|item| assets_res.contains(item))); + assert_contents_equal(vaults_res, allowed_vaults); +} - let vaults_res: Vec = app +#[test] +fn test_raises_on_invalid_vaults_addr() { + let mut app = mock_app(); + let manager_code_id = app.store_code(mock_contract()); + let owner = Addr::unchecked("owner"); + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec!["%%%INVALID%%%".to_string()], + allowed_coins: vec![], + red_bank: RedBankBase("redbankaddr".to_string()), + }; + + let instantiate_res = app.instantiate_contract( + manager_code_id, + owner.clone(), + &msg, + &[], + "mock-contract", + None, + ); + + if instantiate_res.is_ok() { + panic!("Should have thrown an error"); + } +} + +#[test] +fn test_allowed_coins_set_on_instantiate() { + let mut app = mock_app(); + let code_id = app.store_code(mock_contract()); + let owner = Addr::unchecked("owner"); + + let allowed_coins = vec![ + "uosmo".to_string(), + "uatom".to_string(), + "umars".to_string(), + "ujake".to_string(), + ]; + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_coins: allowed_coins.clone(), + red_bank: RedBankBase("redbankaddr".to_string()), + }; + + let contract_addr = app + .instantiate_contract( + code_id, + owner, + &msg, + &[], + "mock-credit-manager-contract", + None, + ) + .unwrap(); + + let coins_res: Vec = app .wrap() .query_wasm_smart( contract_addr.clone(), - &QueryMsg::AllowedVaults { + &QueryMsg::AllowedCoins { start_after: None, limit: None, }, ) .unwrap(); - assert_eq!(vaults_res.len(), 3); - assert_eq!(allowed_vaults, vaults_res); + assert_eq!(coins_res.len(), 4); + assert!(allowed_coins.iter().all(|item| coins_res.contains(item))); } #[test] @@ -141,7 +218,7 @@ fn test_red_bank_set_on_instantiate() { let msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], - allowed_assets: vec![], + allowed_coins: vec![], red_bank: RedBankBase("redbankaddr".to_string()), }; @@ -158,55 +235,15 @@ fn test_red_bank_set_on_instantiate() { } #[test] -fn test_panics_on_invalid_instantiation_addrs() { +fn test_raises_on_invalid_red_bank_addr() { let mut app = mock_app(); let manager_code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec!["%%%INVALID%%%".to_string()], - allowed_assets: vec![], - red_bank: RedBankBase("redbankaddr".to_string()), - }; - - let instantiate_res = app.instantiate_contract( - manager_code_id, - owner.clone(), - &msg, - &[], - "mock-contract", - None, - ); - - if instantiate_res.is_ok() { - panic!("Should have thrown an error"); - } - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec![], - allowed_assets: vec![AssetInfoUnchecked::Cw20(String::from("AA"))], // Because cw-asset lowercases before passing to validate, in the test env, two letter strings is only one that triggers a fail - red_bank: RedBankBase("redbankaddr".to_string()), - }; - - let instantiate_res = app.instantiate_contract( - manager_code_id, - owner.clone(), - &msg, - &[], - "mock-contract", - None, - ); - - if instantiate_res.is_ok() { - panic!("Should have thrown an error"); - } - let msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], - allowed_assets: vec![], + allowed_coins: vec![], red_bank: RedBankBase("%%%INVALID%%%".to_string()), }; diff --git a/contracts/credit-manager/tests/position_query.rs b/contracts/credit-manager/tests/position_query.rs deleted file mode 100644 index fdc673c04..000000000 --- a/contracts/credit-manager/tests/position_query.rs +++ /dev/null @@ -1,95 +0,0 @@ -use cosmwasm_std::testing::{mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage}; -use cosmwasm_std::{from_binary, Addr, OwnedDeps, Uint128}; -use cw_asset::{AssetInfo, AssetInfoBase}; - -use credit_manager::contract::query; -use credit_manager::state::ASSETS; -use rover::msg::query::{PositionResponse, QueryMsg}; - -pub mod helpers; - -#[test] -fn test_position_query_when_no_result() { - let deps = mock_dependencies(); - let token_id = "token_id"; - let value = query_position(&deps, token_id); - assert_eq!(value.token_id, token_id); - assert_eq!(value.assets.len(), 0); -} - -#[test] -fn test_position_query_when_assets_deposited() { - let mut deps = mock_dependencies(); - - let token_id = "token_id"; - let native_asset = AssetInfo::Native("native_asset".to_string()); - let amount = Uint128::new(123); - save_position(&mut deps, token_id, &native_asset, amount); - - let value = query_position(&deps, token_id); - assert_eq!(value.assets.len(), 1); - assert_eq!(value.assets.first().unwrap().amount, amount); - assert_eq!(value.assets.first().unwrap().info, native_asset.into()); -} - -#[test] -fn test_position_query_with_multiple_results() { - let mut deps = mock_dependencies(); - - let token_id_a = "token_id_a"; - let asset_a = AssetInfo::Native("asset_a".to_string()); - let amount_a = Uint128::new(123); - save_position(&mut deps, token_id_a, &asset_a, amount_a); - - let asset_b = AssetInfo::Cw20(Addr::unchecked("asset_b".to_string())); - let amount_b = Uint128::new(444); - save_position(&mut deps, token_id_a, &asset_b, amount_b); - - let asset_c = AssetInfo::Cw20(Addr::unchecked("asset_c".to_string())); - let amount_c = Uint128::new(98); - save_position(&mut deps, token_id_a, &asset_c, amount_c); - - let token_id_b = "token_i_b"; - let amount_d = Uint128::new(567); - save_position(&mut deps, token_id_b, &asset_a, amount_d); - - let value = query_position(&deps, token_id_a); - assert_eq!(value.assets.len(), 3); - - assert_present(&value, &asset_a, amount_a); - assert_present(&value, &asset_b, amount_b); - assert_present(&value, &asset_c, amount_c); -} - -fn assert_present(res: &PositionResponse, asset: &AssetInfoBase, amount: Uint128) { - res.assets - .iter() - .find(|item| item.info == asset.clone().into() && item.amount == amount) - .unwrap(); -} - -fn save_position( - deps: &mut OwnedDeps, - token_id: &str, - asset: &AssetInfoBase, - amount: Uint128, -) { - ASSETS - .save(&mut deps.storage, (token_id, asset.into()), &amount) - .unwrap(); -} - -fn query_position( - deps: &OwnedDeps, - token_id: &str, -) -> PositionResponse { - let res = query( - deps.as_ref(), - mock_env(), - QueryMsg::Position { - token_id: token_id.into(), - }, - ) - .unwrap(); - from_binary(&res).unwrap() -} diff --git a/contracts/credit-manager/tests/update_config.rs b/contracts/credit-manager/tests/update_config.rs deleted file mode 100644 index 0ef76f508..000000000 --- a/contracts/credit-manager/tests/update_config.rs +++ /dev/null @@ -1,188 +0,0 @@ -use cosmwasm_std::Addr; -use cw721_base::InstantiateMsg as NftInstantiateMsg; -use cw_multi_test::{App, Executor}; - -use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use rover::adapters::RedBankBase; -use rover::msg::{ExecuteMsg, InstantiateMsg}; - -use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract, query_config}; - -pub mod helpers; - -#[test] -fn test_update_config_works_with_full_config() { - let mut app = mock_app(); - let original_owner = Addr::unchecked("original_owner"); - let code_id = app.store_code(mock_contract()); - let contract_addr = instantiate(&mut app, &original_owner, code_id); - - let config_res = query_config(&mut app, &contract_addr.clone()); - - assert_eq!(config_res.account_nft, None); - assert_eq!(config_res.owner, original_owner.to_string()); - - let new_owner = Addr::unchecked("new_owner"); - let nft_contract_addr = setup_nft_and_propose_owner(&mut app, &original_owner, &contract_addr); - let new_red_bank_addr = RedBankBase("new_red_bank_addr".to_string()); - - app.execute_contract( - original_owner.clone(), - contract_addr.clone(), - &ExecuteMsg::UpdateConfig { - account_nft: Some(nft_contract_addr.to_string()), - owner: Some(new_owner.to_string()), - red_bank: Some(new_red_bank_addr.clone()), - }, - &[], - ) - .unwrap(); - - let config_res = query_config(&mut app, &contract_addr.clone()); - - assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); - assert_eq!(config_res.owner, new_owner.to_string()); - assert_eq!(config_res.red_bank, new_red_bank_addr.0); -} - -#[test] -fn test_update_config_works_with_some_config() { - let mut app = mock_app(); - let original_owner = Addr::unchecked("original_owner"); - let code_id = app.store_code(mock_contract()); - let contract_addr = instantiate(&mut app, &original_owner, code_id); - - let config_res = query_config(&mut app, &contract_addr.clone()); - - assert_eq!(config_res.account_nft, None); - assert_eq!(config_res.owner, original_owner.to_string()); - assert_eq!(config_res.red_bank, "initial_red_bank".to_string()); - - let nft_contract_addr = setup_nft_and_propose_owner(&mut app, &original_owner, &contract_addr); - app.execute_contract( - original_owner.clone(), - contract_addr.clone(), - &ExecuteMsg::UpdateConfig { - account_nft: Some(nft_contract_addr.to_string()), - owner: None, - red_bank: None, - }, - &[], - ) - .unwrap(); - - let config_res = query_config(&mut app, &contract_addr.clone()); - - assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); - assert_eq!(config_res.owner, original_owner.to_string()); - assert_eq!(config_res.red_bank, "initial_red_bank".to_string()); - - let new_owner = Addr::unchecked("new_owner"); - app.execute_contract( - original_owner.clone(), - contract_addr.clone(), - &ExecuteMsg::UpdateConfig { - account_nft: None, - owner: Some(new_owner.to_string()), - red_bank: None, - }, - &[], - ) - .unwrap(); - - let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); - assert_eq!(config_res.owner, new_owner.to_string()); - assert_eq!(config_res.red_bank, "initial_red_bank".to_string()); - - let new_red_bank = RedBankBase("new_red_bank_addr".to_string()); - - app.execute_contract( - new_owner.clone(), - contract_addr.clone(), - &ExecuteMsg::UpdateConfig { - account_nft: None, - owner: None, - red_bank: Some(new_red_bank.clone()), - }, - &[], - ) - .unwrap(); - - let config_res = query_config(&mut app, &contract_addr.clone()); - assert_eq!(config_res.account_nft, Some(nft_contract_addr.to_string())); - assert_eq!(config_res.owner, new_owner.to_string()); - assert_eq!(config_res.red_bank, new_red_bank.0); -} - -#[test] -fn test_update_config_does_nothing_when_nothing_is_passed() { - let mut app = mock_app(); - let original_owner = Addr::unchecked("original_owner"); - let code_id = app.store_code(mock_contract()); - let contract_addr = instantiate(&mut app, &original_owner, code_id); - - app.execute_contract( - original_owner.clone(), - contract_addr.clone(), - &ExecuteMsg::UpdateConfig { - account_nft: None, - owner: None, - red_bank: None, - }, - &[], - ) - .unwrap(); - - let config_res = query_config(&mut app, &contract_addr.clone()); - - assert_eq!(config_res.account_nft, None); - assert_eq!(config_res.owner, original_owner.to_string()); -} - -fn instantiate(app: &mut App, original_owner: &Addr, code_id: u64) -> Addr { - app.instantiate_contract( - code_id, - original_owner.clone(), - &InstantiateMsg { - owner: original_owner.to_string(), - allowed_vaults: vec![], - allowed_assets: vec![], - red_bank: RedBankBase("initial_red_bank".to_string()), - }, - &[], - "mock_manager_contract", - None, - ) - .unwrap() -} - -fn setup_nft_and_propose_owner(app: &mut App, original_owner: &Addr, contract_addr: &Addr) -> Addr { - let nft_contract_code_id = app.store_code(mock_account_nft_contract()); - let nft_contract_addr = app - .instantiate_contract( - nft_contract_code_id, - original_owner.clone(), - &NftInstantiateMsg { - name: "Rover Credit Account".to_string(), - symbol: "RCA".to_string(), - minter: original_owner.to_string(), - }, - &[], - "manager-mock-account-nft", - None, - ) - .unwrap(); - - let proposal_msg: NftExecuteMsg = NftExecuteMsg::ProposeNewOwner { - new_owner: contract_addr.to_string(), - }; - app.execute_contract( - original_owner.clone(), - nft_contract_addr.clone(), - &proposal_msg, - &[], - ) - .unwrap(); - nft_contract_addr -} diff --git a/contracts/credit-manager/tests/update_config_test.rs b/contracts/credit-manager/tests/update_config_test.rs new file mode 100644 index 000000000..60bb21213 --- /dev/null +++ b/contracts/credit-manager/tests/update_config_test.rs @@ -0,0 +1,221 @@ +use cosmwasm_std::Addr; +use cw721_base::InstantiateMsg as NftInstantiateMsg; +use cw_multi_test::{App, Executor}; + +use account_nft::msg::ExecuteMsg as NftExecuteMsg; +use rover::adapters::RedBankBase; +use rover::msg::instantiate::ConfigUpdates; +use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract, query_config}; + +pub mod helpers; + +#[test] +fn test_update_config_works_with_full_config() { + let mut app = mock_app(); + let original_owner = Addr::unchecked("original_owner"); + let code_id = app.store_code(mock_contract()); + let contract_addr = instantiate(&mut app, &original_owner, code_id); + + let original_config = query_config(&mut app, &contract_addr.clone()); + let original_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); + let original_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); + + let nft_contract_addr = setup_nft_and_propose_owner(&mut app, &original_owner, &contract_addr); + let new_owner = Addr::unchecked("new_owner"); + let new_red_bank = RedBankBase("new_red_bank".to_string()); + let new_allowed_vaults = vec!["vaultcontract1".to_string()]; + let new_allowed_assets = vec!["uosmo".to_string()]; + + app.execute_contract( + original_owner.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateConfig { + new_config: ConfigUpdates { + account_nft: Some(nft_contract_addr.to_string()), + owner: Some(new_owner.to_string()), + allowed_vaults: Some(new_allowed_vaults.clone()), + allowed_coins: Some(new_allowed_assets.clone()), + red_bank: Some(new_red_bank.clone()), + }, + }, + &[], + ) + .unwrap(); + + let new_config = query_config(&mut app, &contract_addr.clone()); + let new_queried_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); + let new_queried_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); + + assert_eq!(new_config.account_nft, Some(nft_contract_addr.to_string())); + assert_ne!(new_config.account_nft, original_config.account_nft); + + assert_eq!(new_config.owner, new_owner.to_string()); + assert_ne!(new_config.owner, original_config.owner); + + assert_eq!(new_queried_allowed_vaults, new_allowed_vaults); + assert_ne!(new_queried_allowed_vaults, original_allowed_vaults); + + assert_eq!(new_queried_allowed_assets, new_allowed_assets); + assert_ne!(new_queried_allowed_assets, original_allowed_assets); + + assert_eq!(new_config.red_bank, new_red_bank.0); + assert_ne!(new_config.red_bank, original_config.red_bank); +} + +#[test] +fn test_update_config_works_with_some_config() { + let mut app = mock_app(); + let original_owner = Addr::unchecked("original_owner"); + let code_id = app.store_code(mock_contract()); + let contract_addr = instantiate(&mut app, &original_owner, code_id); + + let original_config = query_config(&mut app, &contract_addr.clone()); + let original_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); + let original_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); + + let nft_contract_addr = setup_nft_and_propose_owner(&mut app, &original_owner, &contract_addr); + let new_allowed_vaults = vec!["vaultcontract1".to_string()]; + + app.execute_contract( + original_owner.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateConfig { + new_config: ConfigUpdates { + account_nft: Some(nft_contract_addr.to_string()), + owner: None, + allowed_vaults: Some(new_allowed_vaults.clone()), + allowed_coins: None, + red_bank: None, + }, + }, + &[], + ) + .unwrap(); + + let new_config = query_config(&mut app, &contract_addr.clone()); + let new_queried_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); + let new_queried_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); + + // Changed configs + assert_eq!(new_config.account_nft, Some(nft_contract_addr.to_string())); + assert_ne!(new_config.account_nft, original_config.account_nft); + + assert_eq!(new_queried_allowed_vaults, new_allowed_vaults); + assert_ne!(new_queried_allowed_vaults, original_allowed_vaults); + + // Unchanged configs + assert_eq!(new_config.owner, original_config.owner); + assert_eq!(original_allowed_assets, new_queried_allowed_assets); + assert_eq!(new_config.red_bank, original_config.red_bank); +} + +#[test] +fn test_update_config_does_nothing_when_nothing_is_passed() { + let mut app = mock_app(); + let original_owner = Addr::unchecked("original_owner"); + let code_id = app.store_code(mock_contract()); + let contract_addr = instantiate(&mut app, &original_owner, code_id); + + let original_config = query_config(&mut app, &contract_addr.clone()); + let original_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); + let original_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); + + app.execute_contract( + original_owner.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateConfig { + new_config: ConfigUpdates { + account_nft: None, + owner: None, + allowed_vaults: None, + allowed_coins: None, + red_bank: None, + }, + }, + &[], + ) + .unwrap(); + + let new_config = query_config(&mut app, &contract_addr.clone()); + let new_queried_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); + let new_queried_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); + + assert_eq!(new_config.account_nft, original_config.account_nft); + assert_eq!(new_config.owner, original_config.owner); + assert_eq!(new_queried_allowed_vaults, original_allowed_vaults); + assert_eq!(new_queried_allowed_assets, original_allowed_assets); + assert_eq!(new_config.red_bank, original_config.red_bank); +} + +fn instantiate(app: &mut App, original_owner: &Addr, code_id: u64) -> Addr { + app.instantiate_contract( + code_id, + original_owner.clone(), + &InstantiateMsg { + owner: original_owner.to_string(), + allowed_vaults: vec![], + allowed_coins: vec![], + red_bank: RedBankBase("initial_red_bank".to_string()), + }, + &[], + "mock_manager_contract", + None, + ) + .unwrap() +} + +fn setup_nft_and_propose_owner(app: &mut App, original_owner: &Addr, contract_addr: &Addr) -> Addr { + let nft_contract_code_id = app.store_code(mock_account_nft_contract()); + let nft_contract_addr = app + .instantiate_contract( + nft_contract_code_id, + original_owner.clone(), + &NftInstantiateMsg { + name: "Rover Credit Account".to_string(), + symbol: "RCA".to_string(), + minter: original_owner.to_string(), + }, + &[], + "manager-mock-account-nft", + None, + ) + .unwrap(); + + let proposal_msg: NftExecuteMsg = NftExecuteMsg::ProposeNewOwner { + new_owner: contract_addr.to_string(), + }; + app.execute_contract( + original_owner.clone(), + nft_contract_addr.clone(), + &proposal_msg, + &[], + ) + .unwrap(); + nft_contract_addr +} + +fn query_allowed_vaults(app: &mut App, contract_addr: &Addr) -> Vec { + app.wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedVaults { + start_after: None, + limit: None, + }, + ) + .unwrap() +} + +fn query_allowed_assets(app: &mut App, contract_addr: &Addr) -> Vec { + app.wrap() + .query_wasm_smart( + contract_addr.clone(), + &QueryMsg::AllowedCoins { + start_after: None, + limit: None, + }, + ) + .unwrap() +} diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index fddf8942f..1547e7e92 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -14,8 +14,6 @@ library = [] [dependencies] cosmwasm-std = "1.0" -cw20 = "0.13" -cw-asset = "2.1" -cw-storage-plus = "0.13" +cw-storage-plus = "0.14" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index f7b933fdb..674f5acf4 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -1,6 +1,7 @@ -#![allow(unused_imports)] +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; use cosmwasm_std::{ - entry_point, to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, + to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; use crate::execute::execute_borrow; @@ -26,18 +27,18 @@ pub fn execute( ) -> StdResult { match msg { ExecuteMsg::Borrow { - asset, + coin, recipient: _recipient, - } => execute_borrow(deps, info, asset), + } => execute_borrow(deps, info, coin), } } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::UserAssetDebt { user_address, - asset, - } => to_binary(&query_debt(deps, env, user_address, asset)?), + denom, + } => to_binary(&query_debt(deps, user_address, denom)?), } } diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs index 8bac90a0f..bbcc19ff1 100644 --- a/contracts/mock-red-bank/src/execute.rs +++ b/contracts/mock-red-bank/src/execute.rs @@ -1,22 +1,23 @@ -use cosmwasm_std::{DepsMut, MessageInfo, Response, StdResult, Uint128}; -use cw_asset::AssetUnchecked; +use cosmwasm_std::{BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdResult, Uint128}; use crate::helpers::load_debt_amount; use crate::state::DEBT_AMOUNT; -pub fn execute_borrow( - deps: DepsMut, - info: MessageInfo, - asset_unchecked: AssetUnchecked, -) -> StdResult { - let asset = asset_unchecked.check(deps.api, None)?; - let debt_amount = load_debt_amount(deps.storage, &info.sender, &asset.info); +pub fn execute_borrow(deps: DepsMut, info: MessageInfo, coin: Coin) -> StdResult { + let debt_amount = load_debt_amount(deps.storage, &info.sender, &coin.denom)?; DEBT_AMOUNT.save( deps.storage, - (info.sender.clone(), asset.info.clone().into()), - &(debt_amount + asset.amount + Uint128::from(1u128)), // The extra unit is simulated accrued interest + (info.sender.clone(), coin.denom.clone()), + &debt_amount + .checked_add(coin.amount)? + .checked_add(Uint128::from(1u128))?, // The extra unit is simulated accrued interest )?; - Ok(Response::new().add_message(asset.transfer_msg(&info.sender)?)) + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.to_string(), + amount: vec![coin], + }); + + Ok(Response::new().add_message(transfer_msg)) } diff --git a/contracts/mock-red-bank/src/helpers.rs b/contracts/mock-red-bank/src/helpers.rs index a5746d552..4d1e06adb 100644 --- a/contracts/mock-red-bank/src/helpers.rs +++ b/contracts/mock-red-bank/src/helpers.rs @@ -1,10 +1,9 @@ -use cosmwasm_std::{Addr, Storage, Uint128}; -use cw_asset::AssetInfo; +use cosmwasm_std::{Addr, StdResult, Storage, Uint128}; use crate::state::DEBT_AMOUNT; -pub fn load_debt_amount(storage: &dyn Storage, user: &Addr, asset: &AssetInfo) -> Uint128 { - DEBT_AMOUNT - .load(storage, (user.clone(), asset.into())) - .unwrap_or(Uint128::zero()) +pub fn load_debt_amount(storage: &dyn Storage, user: &Addr, denom: &str) -> StdResult { + Ok(DEBT_AMOUNT + .may_load(storage, (user.clone(), denom.to_string()))? + .unwrap_or_else(Uint128::zero)) } diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs index a555e9b3b..36fbf4c5d 100644 --- a/contracts/mock-red-bank/src/msg.rs +++ b/contracts/mock-red-bank/src/msg.rs @@ -1,5 +1,4 @@ -use cosmwasm_std::Uint128; -use cw_asset::{AssetInfoUnchecked, AssetUnchecked}; +use cosmwasm_std::{Coin, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -7,7 +6,7 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { Borrow { - asset: AssetUnchecked, + coin: Coin, recipient: Option, }, } @@ -15,14 +14,11 @@ pub enum ExecuteMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { - UserAssetDebt { - user_address: String, - asset: AssetInfoUnchecked, - }, + UserAssetDebt { user_address: String, denom: String }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct UserAssetDebtResponse { - pub asset_info: AssetInfoUnchecked, + pub denom: String, pub amount: Uint128, } diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index ab08ac466..30bd4bbec 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -1,20 +1,14 @@ -use cosmwasm_std::{Deps, Env, StdResult}; -use cw_asset::AssetInfoUnchecked; +use cosmwasm_std::{Deps, StdResult}; use crate::helpers::load_debt_amount; use crate::msg::UserAssetDebtResponse; pub fn query_debt( deps: Deps, - _env: Env, user_address: String, - asset_info_unchecked: AssetInfoUnchecked, + denom: String, ) -> StdResult { let user_addr = deps.api.addr_validate(&user_address)?; - let asset_info = asset_info_unchecked.check(deps.api, None)?; - let debt_amount = load_debt_amount(deps.storage, &user_addr, &asset_info); - Ok(UserAssetDebtResponse { - amount: debt_amount, - asset_info: asset_info.into(), - }) + let amount = load_debt_amount(deps.storage, &user_addr, &denom)?; + Ok(UserAssetDebtResponse { denom, amount }) } diff --git a/contracts/mock-red-bank/src/state.rs b/contracts/mock-red-bank/src/state.rs index f38e85a98..1aec53e91 100644 --- a/contracts/mock-red-bank/src/state.rs +++ b/contracts/mock-red-bank/src/state.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{Addr, Uint128}; -use cw_asset::AssetInfoKey; use cw_storage_plus::Map; -pub const DEBT_AMOUNT: Map<(Addr, AssetInfoKey), Uint128> = Map::new("debt_amount"); +// Map<(DebtHolder, AssetDenom), AmountOfDebt> +pub const DEBT_AMOUNT: Map<(Addr, String), Uint128> = Map::new("debt_amount"); diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index d235a939a..6aeb5dd24 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -11,9 +11,7 @@ doctest = false mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } cosmwasm-std = "1.0" -cw20 = "0.13" -cw-asset = "2.1" -cw-storage-plus = "0.13" +cw-storage-plus = "0.14" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index 86146d5aa..ab2d7161a 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -1,8 +1,7 @@ use cosmwasm_std::{ - to_binary, Addr, Api, CosmosMsg, QuerierWrapper, QueryRequest, StdResult, Uint128, WasmMsg, - WasmQuery, + to_binary, Addr, Api, Coin, CosmosMsg, QuerierWrapper, QueryRequest, StdResult, Uint128, + WasmMsg, WasmQuery, }; -use cw_asset::{Asset, AssetInfo, AssetUnchecked}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -27,12 +26,12 @@ impl RedBankUnchecked { } impl RedBank { - /// Generate message for borrowing a specified amount of asset - pub fn borrow_msg(&self, asset: &Asset) -> StdResult { + /// Generate message for borrowing a specified amount of coin + pub fn borrow_msg(&self, coin: &Coin) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.0.to_string(), msg: to_binary(&ExecuteMsg::Borrow { - asset: AssetUnchecked::from(asset.clone()), + coin: coin.clone(), recipient: None, })?, funds: vec![], @@ -43,14 +42,14 @@ impl RedBank { &self, querier: &QuerierWrapper, user_address: &Addr, - asset_info: &AssetInfo, + denom: &str, ) -> StdResult { let response: UserAssetDebtResponse = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.0.to_string(), msg: to_binary(&QueryMsg::UserAssetDebt { user_address: user_address.to_string(), - asset: asset_info.clone().into(), + denom: denom.to_string(), })?, }))?; Ok(response.amount) diff --git a/packages/rover/src/coin_list.rs b/packages/rover/src/coin_list.rs new file mode 100644 index 000000000..d4abab3dc --- /dev/null +++ b/packages/rover/src/coin_list.rs @@ -0,0 +1,59 @@ +use std::fmt; + +use cosmwasm_std::{Coin, StdError, StdResult}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Partial integration of cw-asset's `AssetList` but just for native `Coin` +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct CoinList(Vec); + +impl From<&Vec> for CoinList { + fn from(coins: &Vec) -> Self { + Self(coins.clone()) + } +} + +impl CoinList { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn find_denom(&self, denom: &str) -> Option<&Coin> { + self.0.iter().find(|item| item.denom == denom) + } + + pub fn deduct(&mut self, to_deduct: &Coin) -> StdResult<&mut Self> { + match self.0.iter_mut().find(|coin| coin.denom == to_deduct.denom) { + Some(coin) => { + coin.amount = coin.amount.checked_sub(to_deduct.amount)?; + } + None => { + return Err(StdError::generic_err(format!( + "not found in asset list: {}", + to_deduct.denom + ))); + } + } + Ok(self.purge()) + } + + pub fn purge(&mut self) -> &mut Self { + self.0.retain(|coin| !coin.amount.is_zero()); + self + } +} + +impl fmt::Display for CoinList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + self.0 + .iter() + .map(|coin| coin.to_string()) + .collect::>() + .join(",") + ) + } +} diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 44819e032..662ece7bb 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -1,15 +1,13 @@ -use cosmwasm_std::{Addr, CheckedMultiplyRatioError, OverflowError, StdError, Uint128}; -use cw_asset::AssetListBase; +use cosmwasm_std::{CheckedMultiplyRatioError, StdError, Uint128}; use thiserror::Error; +use crate::coin_list::CoinList; + #[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] Std(#[from] StdError), - #[error("{0}")] - Overflow(#[from] OverflowError), - #[error("{0}")] CheckedMultiply(#[from] CheckedMultiplyRatioError), @@ -20,14 +18,11 @@ pub enum ContractError { NotWhitelisted(String), #[error("Extra funds received: {0}")] - ExtraFundsReceived(AssetListBase), + ExtraFundsReceived(CoinList), - #[error("No asset amount set for action")] + #[error("No coin amount set for action")] NoAmount, - #[error("Deposits of CW20's should come via Cw20ExecuteMsg::Send to cw20 contract specifying Rover's ReceiveMsg")] - WrongDepositMethodForCW20, - #[error("Sent fund mismatch. Expected: {expected:?}, received {received:?}")] FundsMismatch { expected: Uint128, diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index 56549ee63..19c735a48 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -1,3 +1,13 @@ +use cosmwasm_std::Uint128; + +use crate::error::ContractError; + pub mod adapters; +pub mod coin_list; pub mod error; pub mod msg; + +pub type ContractResult = Result; +pub type NftTokenId<'a> = &'a str; +pub type Denom<'a> = &'a str; +pub type Shares = Uint128; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 7c4458480..a6580d2d2 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,10 +1,9 @@ -use crate::adapters::RedBankUnchecked; -use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg}; -use cw20::Cw20ReceiveMsg; -use cw_asset::{Asset, AssetUnchecked}; +use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, StdResult, WasmMsg}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::msg::instantiate::ConfigUpdates; + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { @@ -12,24 +11,18 @@ pub enum ExecuteMsg { // Public messages //-------------------------------------------------------------------------------------------------- /// Mints NFT representing a credit account for user. User can have many. - CreateCreditAccount {}, + CreateCreditAccount, /// Update user's position on their credit account UpdateCreditAccount { token_id: String, actions: Vec, }, - /// The receiving message for Cw20ExecuteMsg::Send deposits - Receive(Cw20ReceiveMsg), //-------------------------------------------------------------------------------------------------- // Privileged messages //-------------------------------------------------------------------------------------------------- - /// Update owner, stored account nft contract, or red bank contract addr - UpdateConfig { - account_nft: Option, - owner: Option, - red_bank: Option, - }, + /// Update contract config constants + UpdateConfig { new_config: ConfigUpdates }, /// Internal actions only callable by the contract itself Callback(CallbackMsg), } @@ -38,21 +31,20 @@ pub enum ExecuteMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Action { - /// Deposit native asset of specified type and amount. Verifies if the correct amount is sent with transaction. - /// NOTE: CW20 Deposits should use Cw20ExecuteMsg::Send {} - NativeDeposit(AssetUnchecked), + /// Deposit native coin of specified type and amount. Verifies if the correct amount is sent with transaction. + Deposit(Coin), - /// Borrow asset of specified amount from Red Bank - Borrow(AssetUnchecked), + /// Borrow coin of specified amount from Red Bank + Borrow(Coin), } /// Internal actions made by the contract with pre-validated inputs #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum CallbackMsg { - /// Borrow specified amount of asset from Red Bank; + /// Borrow specified amount of coin from Red Bank; /// Increase the token's asset amount and debt shares; - Borrow { token_id: String, asset: Asset }, + Borrow { token_id: String, coin: Coin }, } impl CallbackMsg { @@ -64,10 +56,3 @@ impl CallbackMsg { })) } } - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ReceiveMsg { - /// Deposit for CW20's. Requires using Cw20ExecuteMsg::Send and specifying this receive message - Deposit { token_id: String }, -} diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index 7bfc863d2..cbe1e11ae 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -1,12 +1,22 @@ -use crate::adapters::RedBankUnchecked; -use cw_asset::AssetInfoUnchecked; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::adapters::RedBankUnchecked; + #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] pub struct InstantiateMsg { pub owner: String, pub allowed_vaults: Vec, - pub allowed_assets: Vec, + pub allowed_coins: Vec, pub red_bank: RedBankUnchecked, } + +/// Used when you want to update fields on Instantiate config +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct ConfigUpdates { + pub account_nft: Option, + pub owner: Option, + pub allowed_coins: Option>, + pub allowed_vaults: Option>, + pub red_bank: Option, +} diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index b51ae33ce..a79b9c371 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -1,4 +1,4 @@ -use cw_asset::{AssetInfoUnchecked, AssetUnchecked}; +use cosmwasm_std::{Coin, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -6,30 +6,70 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "snake_case")] pub enum QueryMsg { /// Owner & account nft address. Response type: `ConfigResponse` - Config {}, + Config, /// Whitelisted vaults. Response type: `Vec` AllowedVaults { start_after: Option, limit: Option, }, - /// Whitelisted assets. Response type: `Vec` - AllowedAssets { - start_after: Option, + /// Whitelisted coins. Response type: `Vec` + AllowedCoins { + start_after: Option, limit: Option, }, /// The entire position represented by token. Response type: `PositionResponse` Position { token_id: String }, + /// Enumerate assets for all token positions. Response type: `Vec` + /// start_after accepts (token_id, denom) + AllAssets { + start_after: Option<(String, String)>, + limit: Option, + }, + /// Enumerate debt shares for all token positions. Response type: `Vec` + /// start_after accepts (token_id, denom) + AllDebtShares { + start_after: Option<(String, String)>, + limit: Option, + }, + /// Total debt shares issued for Coin. Response type: `CoinShares` + TotalDebtShares(String), + /// Enumerate total debt shares for all supported coins. Response type: `Vec` + /// start_after accepts denom string + AllTotalDebtShares { + start_after: Option, + limit: Option, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct AssetResponseItem { + pub token_id: String, + pub denom: String, + pub amount: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct SharesResponseItem { + pub token_id: String, + pub denom: String, + pub shares: Uint128, +} - /// Total debt shares issued for Asset. Response type: `TotalDebtSharesResponse` - TotalDebtShares(AssetInfoUnchecked), +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct CoinShares { + pub denom: String, + pub shares: Uint128, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct PositionResponse { pub token_id: String, - pub assets: Vec, - pub debt_shares: Vec, + pub assets: Vec, + pub debt_shares: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -39,7 +79,3 @@ pub struct ConfigResponse { pub account_nft: Option, pub red_bank: String, } - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct TotalDebtSharesResponse(pub AssetUnchecked); From faad95618dffcfb8e325d03d0747ab367e8e5d00 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 4 Aug 2022 10:17:24 +0200 Subject: [PATCH 042/218] Updating to Coins BTreeMap implementation --- contracts/credit-manager/src/deposit.rs | 9 ++- contracts/credit-manager/src/execute.rs | 4 +- .../credit-manager/tests/deposit_test.rs | 4 +- packages/rover/src/coin_list.rs | 59 ---------------- packages/rover/src/coins.rs | 70 +++++++++++++++++++ packages/rover/src/error.rs | 4 +- packages/rover/src/lib.rs | 2 +- 7 files changed, 81 insertions(+), 71 deletions(-) delete mode 100644 packages/rover/src/coin_list.rs create mode 100644 packages/rover/src/coins.rs diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index 91a00b163..4435673f7 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Coin, Response, StdError, StdResult, Storage, Uint128}; -use rover::coin_list::CoinList; +use rover::coins::Coins; use rover::error::ContractError; use rover::ContractResult; @@ -11,7 +11,7 @@ pub fn deposit( response: Response, nft_token_id: &str, coin: &Coin, - received_coins: &mut CoinList, + received_coins: &mut Coins, ) -> ContractResult { assert_coin_is_whitelisted(storage, &coin.denom)?; @@ -32,10 +32,9 @@ pub fn deposit( } /// Assert that fund of exactly the same type and amount was sent along with a message -fn assert_sent_fund(expected: &Coin, received_coins: &CoinList) -> ContractResult<()> { +fn assert_sent_fund(expected: &Coin, received_coins: &Coins) -> ContractResult<()> { let received = received_coins - .find_denom(&expected.denom) - .map(|c| c.amount) + .amount(&expected.denom) .unwrap_or_else(Uint128::zero); if received != expected.amount { diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 2a8549849..4d9374c4b 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -5,7 +5,7 @@ use cw721::OwnerOfResponse; use cw721_base::QueryMsg; use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use rover::coin_list::CoinList; +use rover::coins::Coins; use rover::error::ContractError; use rover::msg::execute::{Action, CallbackMsg}; use rover::msg::instantiate::ConfigUpdates; @@ -113,7 +113,7 @@ pub fn dispatch_actions( let mut response = Response::new(); let mut callbacks: Vec = vec![]; - let mut received_coins = CoinList::from(&info.funds); + let mut received_coins = Coins::from(info.funds.as_slice()); for action in actions { match action { diff --git a/contracts/credit-manager/tests/deposit_test.rs b/contracts/credit-manager/tests/deposit_test.rs index 875531a8d..da56c6d88 100644 --- a/contracts/credit-manager/tests/deposit_test.rs +++ b/contracts/credit-manager/tests/deposit_test.rs @@ -3,7 +3,7 @@ extern crate core; use cosmwasm_std::{Addr, Coin, Uint128}; use cw_multi_test::{App, Executor}; -use rover::coin_list::CoinList; +use rover::coins::Coins; use rover::error::ContractError::{ ExtraFundsReceived, FundsMismatch, NotTokenOwner, NotWhitelisted, }; @@ -258,7 +258,7 @@ fn test_extra_funds_received() { &[Coin::new(234u128, "uosmo"), extra_funds.clone()], ); - assert_err(res, ExtraFundsReceived(CoinList::from(&vec![extra_funds]))); + assert_err(res, ExtraFundsReceived(Coins::from(vec![extra_funds]))); let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.assets.len(), 0); diff --git a/packages/rover/src/coin_list.rs b/packages/rover/src/coin_list.rs deleted file mode 100644 index d4abab3dc..000000000 --- a/packages/rover/src/coin_list.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::fmt; - -use cosmwasm_std::{Coin, StdError, StdResult}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// Partial integration of cw-asset's `AssetList` but just for native `Coin` -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct CoinList(Vec); - -impl From<&Vec> for CoinList { - fn from(coins: &Vec) -> Self { - Self(coins.clone()) - } -} - -impl CoinList { - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - pub fn find_denom(&self, denom: &str) -> Option<&Coin> { - self.0.iter().find(|item| item.denom == denom) - } - - pub fn deduct(&mut self, to_deduct: &Coin) -> StdResult<&mut Self> { - match self.0.iter_mut().find(|coin| coin.denom == to_deduct.denom) { - Some(coin) => { - coin.amount = coin.amount.checked_sub(to_deduct.amount)?; - } - None => { - return Err(StdError::generic_err(format!( - "not found in asset list: {}", - to_deduct.denom - ))); - } - } - Ok(self.purge()) - } - - pub fn purge(&mut self) -> &mut Self { - self.0.retain(|coin| !coin.amount.is_zero()); - self - } -} - -impl fmt::Display for CoinList { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - self.0 - .iter() - .map(|coin| coin.to_string()) - .collect::>() - .join(",") - ) - } -} diff --git a/packages/rover/src/coins.rs b/packages/rover/src/coins.rs new file mode 100644 index 000000000..b40ad902f --- /dev/null +++ b/packages/rover/src/coins.rs @@ -0,0 +1,70 @@ +use std::collections::BTreeMap; +use std::fmt; + +use cosmwasm_std::{Coin, StdError, StdResult, Uint128}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Pending integration into cosmwasm_std: https://github.com/CosmWasm/cosmwasm/issues/1377#issuecomment-1204232193 +/// Copying from here: https://github.com/mars-protocol/cw-coins/blob/main/src/lib.rs +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Coins(pub BTreeMap); + +impl From> for Coins { + fn from(coins: Vec) -> Self { + let map = coins + .into_iter() + .map(|coin| (coin.denom, coin.amount)) + .collect(); + Self(map) + } +} + +impl From<&[Coin]> for Coins { + fn from(coins: &[Coin]) -> Self { + coins.to_vec().into() + } +} + +impl Coins { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn amount(&self, denom: &str) -> Option { + self.0.get(denom).map(Clone::clone) + } + + pub fn deduct(&mut self, to_deduct: &Coin) -> StdResult<&mut Self> { + if let Some(amount) = self.amount(&to_deduct.denom) { + let new_amount = amount.checked_sub(to_deduct.amount)?; + if new_amount.is_zero() { + self.0.remove(&to_deduct.denom); + } else { + self.0.insert(to_deduct.denom.clone(), new_amount); + } + Ok(self) + } else { + Err(StdError::generic_err(format!( + "not found in coin list: {}", + to_deduct.denom + ))) + } + } +} + +impl fmt::Display for Coins { + // TODO: For empty coins, this stringifies to am empty string, which may cause confusions. + // Should it stringify to a more informative string, such as `[]`? + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // NOTE: The `iter` method for BTreeMap returns an Iterator where entries are already sorted + // by key, so we don't need sort the coins manually + let s = self + .0 + .iter() + .map(|(denom, amount)| format!("{}{}", amount, denom)) + .collect::>() + .join(","); + write!(f, "{}", s) + } +} diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 662ece7bb..7f15c9ad8 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{CheckedMultiplyRatioError, StdError, Uint128}; use thiserror::Error; -use crate::coin_list::CoinList; +use crate::coins::Coins; #[derive(Error, Debug, PartialEq)] pub enum ContractError { @@ -18,7 +18,7 @@ pub enum ContractError { NotWhitelisted(String), #[error("Extra funds received: {0}")] - ExtraFundsReceived(CoinList), + ExtraFundsReceived(Coins), #[error("No coin amount set for action")] NoAmount, diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index 19c735a48..87312d1f7 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -3,7 +3,7 @@ use cosmwasm_std::Uint128; use crate::error::ContractError; pub mod adapters; -pub mod coin_list; +pub mod coins; pub mod error; pub mod msg; From 8824233d4106eb71ee41a9c7a84b4b94a2bad072 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 4 Aug 2022 11:52:39 +0200 Subject: [PATCH 043/218] Adding oracle & health check support --- .github/workflows/main.yml | 2 +- Cargo.lock | 448 ++++++++++++-- contracts/account-nft/Cargo.toml | 2 + contracts/credit-manager/Cargo.toml | 5 +- contracts/credit-manager/src/borrow.rs | 2 +- contracts/credit-manager/src/contract.rs | 13 +- contracts/credit-manager/src/deposit.rs | 3 +- contracts/credit-manager/src/execute.rs | 16 +- contracts/credit-manager/src/health.rs | 97 +++ contracts/credit-manager/src/instantiate.rs | 9 +- contracts/credit-manager/src/lib.rs | 1 + contracts/credit-manager/src/query.rs | 107 +++- contracts/credit-manager/src/state.rs | 3 +- contracts/credit-manager/tests/borrow_test.rs | 138 +++-- .../tests/create_credit_account_test.rs | 9 +- .../credit-manager/tests/deposit_test.rs | 199 +++--- .../tests/enumerate_allowed_coins_test.rs | 3 +- .../tests/enumerate_allowed_vaults_test.rs | 3 +- .../tests/enumerate_assets_test.rs | 90 ++- .../tests/enumerate_debt_shares_test.rs | 200 ++++-- .../tests/enumerate_total_debt_shares_test.rs | 200 ++++-- contracts/credit-manager/tests/health_test.rs | 576 ++++++++++++++++++ .../credit-manager/tests/helpers/contracts.rs | 8 + .../credit-manager/tests/helpers/deploys.rs | 95 ++- .../credit-manager/tests/helpers/types.rs | 19 +- .../credit-manager/tests/instantiate_test.rs | 70 ++- .../tests/update_config_test.rs | 20 +- contracts/mock-oracle/Cargo.toml | 22 + contracts/mock-oracle/src/contract.rs | 54 ++ contracts/mock-oracle/src/lib.rs | 3 + contracts/mock-oracle/src/msg.rs | 30 + contracts/mock-oracle/src/state.rs | 4 + contracts/mock-red-bank/Cargo.toml | 1 + contracts/mock-red-bank/src/contract.rs | 19 +- contracts/mock-red-bank/src/msg.rs | 22 +- contracts/mock-red-bank/src/query.rs | 8 +- contracts/mock-red-bank/src/state.rs | 6 +- packages/rover/Cargo.toml | 3 + packages/rover/src/adapters/mod.rs | 2 + packages/rover/src/adapters/oracle.rs | 37 ++ packages/rover/src/adapters/red_bank.rs | 2 +- packages/rover/src/error.rs | 39 +- packages/rover/src/health.rs | 12 + packages/rover/src/lib.rs | 1 + packages/rover/src/msg/execute.rs | 5 +- packages/rover/src/msg/instantiate.rs | 13 +- packages/rover/src/msg/query.rs | 32 +- 47 files changed, 2266 insertions(+), 387 deletions(-) create mode 100644 contracts/credit-manager/src/health.rs create mode 100644 contracts/credit-manager/tests/health_test.rs create mode 100644 contracts/mock-oracle/Cargo.toml create mode 100644 contracts/mock-oracle/src/contract.rs create mode 100644 contracts/mock-oracle/src/lib.rs create mode 100644 contracts/mock-oracle/src/msg.rs create mode 100644 contracts/mock-oracle/src/state.rs create mode 100644 packages/rover/src/adapters/oracle.rs create mode 100644 packages/rover/src/health.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c9ba3a63c..cc50f4f9e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,6 @@ # Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml -on: push +on: pull_request name: Main diff --git a/Cargo.lock b/Cargo.lock index e8b51f632..e1918333f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,7 +7,7 @@ name = "account-nft" version = "0.1.0" dependencies = [ "anyhow", - "cosmwasm-std", + "cosmwasm-std 1.0.0", "cw-multi-test", "cw-storage-plus 0.14.0", "cw721", @@ -22,6 +22,21 @@ version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" +[[package]] +name = "astroport" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b56c6d8a5a14a5f569a3473a962ed1d12bfec2a19ef43584c86aae5667bdeaa8" +dependencies = [ + "cosmwasm-std 0.16.7", + "cw-storage-plus 0.8.1", + "cw20 0.8.1", + "schemars", + "serde", + "terra-cosmwasm", + "uint", +] + [[package]] name = "base16ct" version = "0.1.1" @@ -67,12 +82,31 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "const-oid" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" + [[package]] name = "const-oid" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +[[package]] +name = "cosmwasm-crypto" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b110e31d47bd265e17ec88dd7328fcf40e1ee67a6131c1ab492f77fef8cd83" +dependencies = [ + "digest", + "ed25519-zebra 2.2.0", + "k256 0.9.6", + "rand_core 0.5.1", + "thiserror", +] + [[package]] name = "cosmwasm-crypto" version = "1.0.0" @@ -80,12 +114,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd" dependencies = [ "digest", - "ed25519-zebra", - "k256", + "ed25519-zebra 3.0.0", + "k256 0.10.4", "rand_core 0.6.3", "thiserror", ] +[[package]] +name = "cosmwasm-derive" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0faf9bad5eb0a43a00406e64f8d33407a06bd1826fa976195a69db70e6c18d9d" +dependencies = [ + "syn", +] + [[package]] name = "cosmwasm-derive" version = "1.0.0" @@ -95,6 +138,23 @@ dependencies = [ "syn", ] +[[package]] +name = "cosmwasm-std" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0d4e46ab20939af6366a71783324ae4babdedb111f0dd797d063a2e68718bc" +dependencies = [ + "base64", + "cosmwasm-crypto 0.16.7", + "cosmwasm-derive 0.16.7", + "forward_ref", + "schemars", + "serde", + "serde-json-wasm 0.3.2", + "thiserror", + "uint", +] + [[package]] name = "cosmwasm-std" version = "1.0.0" @@ -102,12 +162,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" dependencies = [ "base64", - "cosmwasm-crypto", - "cosmwasm-derive", + "cosmwasm-crypto 1.0.0", + "cosmwasm-derive 1.0.0", "forward_ref", "schemars", "serde", - "serde-json-wasm", + "serde-json-wasm 0.4.1", "thiserror", "uint", ] @@ -118,7 +178,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d18403b07304d15d304dad11040d45bbcaf78d603b4be3fb5e2685c16f9229b5" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 1.0.0", "serde", ] @@ -137,12 +197,13 @@ version = "0.1.0" dependencies = [ "account-nft", "anyhow", - "cosmwasm-std", + "cosmwasm-std 1.0.0", "cw-multi-test", "cw-storage-plus 0.14.0", "cw2 0.14.0", "cw721", "cw721-base", + "mock-oracle", "mock-red-bank", "rover", "schemars", @@ -155,6 +216,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" +dependencies = [ + "generic-array", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + [[package]] name = "crypto-bigint" version = "0.3.2" @@ -179,9 +252,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", "digest", @@ -190,6 +263,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-asset" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dadc245d6c76cd7ae2c5ec39aefa4a5310cde653d07e23c5577ee0c2cac864e" +dependencies = [ + "cosmwasm-std 1.0.0", + "cw-storage-plus 0.13.4", + "cw1155", + "cw20 0.13.4", + "schemars", + "serde", +] + [[package]] name = "cw-multi-test" version = "0.14.0" @@ -197,7 +284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca153120cf5b91af88be106b0c6c0263423d959bc813b1592982c02c4691a4ae" dependencies = [ "anyhow", - "cosmwasm-std", + "cosmwasm-std 1.0.0", "cosmwasm-storage", "cw-storage-plus 0.14.0", "cw-utils 0.14.0", @@ -209,13 +296,35 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-storage-plus" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e867b9972b83b32e00e878dfbff48299ba26618dabeb19b9c56fae176dc225" +dependencies = [ + "cosmwasm-std 0.16.7", + "schemars", + "serde", +] + +[[package]] +name = "cw-storage-plus" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140b764eb9aca3fa8b06ce7ce773705058db1b11cc97bb1bac6702c19887894e" +dependencies = [ + "cosmwasm-std 0.16.7", + "schemars", + "serde", +] + [[package]] name = "cw-storage-plus" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 1.0.0", "schemars", "serde", ] @@ -226,7 +335,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c8b264257c4f44c49b7ce09377af63aa040768ecd3fd7bdd2d48a09323a1e90" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 1.0.0", "schemars", "serde", ] @@ -237,7 +346,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 1.0.0", "schemars", "serde", "thiserror", @@ -249,7 +358,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "414b91f3d7a619bb26c835119d7095804596a1382ddc1d184c33c1d2c17f6c5e" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 1.0.0", "cw2 0.14.0", "schemars", "semver", @@ -257,13 +366,61 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw0" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c497f885a40918a02df7d938c81809965fa05cfc21b3dc591e9950237b5de0a9" +dependencies = [ + "cosmwasm-std 0.16.7", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw0" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d759bb5418a3bdf091e1f1be17de2a15d95d2be4fee28045c2e461f4c6d9d1ca" +dependencies = [ + "cosmwasm-std 0.16.7", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw1155" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42197c9a0fd844653177009125f24157e486578289327a3781923e427a8f9bbc" +dependencies = [ + "cosmwasm-std 1.0.0", + "cw-utils 0.13.4", + "schemars", + "serde", +] + +[[package]] +name = "cw2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022d9f06ea46f055f0e107c5cf076949f0b1e7befb78acbee42a28f07b6cd636" +dependencies = [ + "cosmwasm-std 0.16.7", + "cw-storage-plus 0.9.2", + "schemars", + "serde", +] + [[package]] name = "cw2" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 1.0.0", "cw-storage-plus 0.13.4", "schemars", "serde", @@ -275,19 +432,71 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa74c324af8e3506fd8d50759a265bead3f87402e413c840042af5d2808463d6" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 1.0.0", "cw-storage-plus 0.14.0", "schemars", "serde", ] +[[package]] +name = "cw20" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11a2adbd52258f5b4ed5323f62bc6e559f2cefbe52ef0e58290016fde5bb083" +dependencies = [ + "cosmwasm-std 0.16.7", + "cw0 0.8.1", + "schemars", + "serde", +] + +[[package]] +name = "cw20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac49b013ca1e355fd988cc1926acc9a16d7fd45cfb595ee330455582a788b100" +dependencies = [ + "cosmwasm-std 0.16.7", + "cw0 0.9.1", + "schemars", + "serde", +] + +[[package]] +name = "cw20" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" +dependencies = [ + "cosmwasm-std 1.0.0", + "cw-utils 0.13.4", + "schemars", + "serde", +] + +[[package]] +name = "cw20-base" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b731a291b63cc26d484b159e8469f0965b0082a20e874616f869537b31d64bb" +dependencies = [ + "cosmwasm-std 0.16.7", + "cw-storage-plus 0.9.2", + "cw0 0.9.1", + "cw2 0.9.2", + "cw20 0.9.1", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw721" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "035818368a74c07dd9ed5c5a93340199ba251530162010b9f34c3809e3b97df1" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 1.0.0", "cw-utils 0.13.4", "schemars", "serde", @@ -299,7 +508,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "423d4efe8b649d228d1533e141c238415f49aa8a9ee4e40fce192d7a93ffd057" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 1.0.0", "cw-storage-plus 0.13.4", "cw-utils 0.13.4", "cw2 0.13.4", @@ -309,13 +518,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "der" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" +dependencies = [ + "const-oid 0.6.2", +] + [[package]] name = "der" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" dependencies = [ - "const-oid", + "const-oid 0.7.1", ] [[package]] @@ -344,18 +562,44 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" +[[package]] +name = "ecdsa" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" +dependencies = [ + "der 0.4.5", + "elliptic-curve 0.10.6", + "hmac", + "signature", +] + [[package]] name = "ecdsa" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" dependencies = [ - "der", - "elliptic-curve", + "der 0.5.1", + "elliptic-curve 0.11.12", "rfc6979", "signature", ] +[[package]] +name = "ed25519-zebra" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409" +dependencies = [ + "curve25519-dalek", + "hex", + "rand_core 0.5.1", + "serde", + "sha2", + "thiserror", +] + [[package]] name = "ed25519-zebra" version = "3.0.0" @@ -377,6 +621,22 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +[[package]] +name = "elliptic-curve" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b" +dependencies = [ + "crypto-bigint 0.2.11", + "ff 0.10.1", + "generic-array", + "group 0.10.0", + "pkcs8 0.7.6", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + [[package]] name = "elliptic-curve" version = "0.11.12" @@ -384,17 +644,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" dependencies = [ "base16ct", - "crypto-bigint", - "der", - "ff", + "crypto-bigint 0.3.2", + "der 0.5.1", + "ff 0.11.1", "generic-array", - "group", + "group 0.11.0", "rand_core 0.6.3", "sec1", "subtle", "zeroize", ] +[[package]] +name = "ff" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "ff" version = "0.11.1" @@ -443,13 +713,24 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "group" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" +dependencies = [ + "ff 0.10.1", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "group" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ - "ff", + "ff 0.11.1", "rand_core 0.6.3", "subtle", ] @@ -485,6 +766,18 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +[[package]] +name = "k256" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea" +dependencies = [ + "cfg-if", + "ecdsa 0.12.4", + "elliptic-curve 0.10.6", + "sha2", +] + [[package]] name = "k256" version = "0.10.4" @@ -492,23 +785,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ "cfg-if", - "ecdsa", - "elliptic-curve", + "ecdsa 0.13.4", + "elliptic-curve 0.11.12", "sec1", "sha2", ] [[package]] name = "libc" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" + +[[package]] +name = "mars-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7466180f16cf2ec2218ed1dfd11543a5dc0620dbf5e08288d41a6aa6e559748" +dependencies = [ + "astroport", + "cosmwasm-std 0.16.7", + "cw2 0.9.2", + "cw20 0.9.1", + "cw20-base", + "schemars", + "serde", + "terra-cosmwasm", + "thiserror", +] + +[[package]] +name = "mock-oracle" +version = "1.0.0" +dependencies = [ + "cosmwasm-std 1.0.0", + "cw-asset", + "cw-storage-plus 0.13.4", + "mars-core", + "schemars", + "serde", +] [[package]] name = "mock-red-bank" version = "1.0.0" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 1.0.0", "cw-storage-plus 0.14.0", "schemars", "serde", @@ -520,14 +842,24 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "pkcs8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" +dependencies = [ + "der 0.4.5", + "spki 0.4.1", +] + [[package]] name = "pkcs8" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" dependencies = [ - "der", - "spki", + "der 0.5.1", + "spki 0.5.4", "zeroize", ] @@ -596,7 +928,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.3.2", "hmac", "zeroize", ] @@ -605,8 +937,9 @@ dependencies = [ name = "rover" version = "0.1.0" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 1.0.0", "cw-storage-plus 0.14.0", + "mock-oracle", "mock-red-bank", "schemars", "serde", @@ -649,9 +982,9 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ - "der", + "der 0.5.1", "generic-array", - "pkcs8", + "pkcs8 0.8.0", "subtle", "zeroize", ] @@ -671,6 +1004,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-json-wasm" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "042ac496d97e5885149d34139bad1d617192770d7eb8f1866da2317ff4501853" +dependencies = [ + "serde", +] + [[package]] name = "serde-json-wasm" version = "0.4.1" @@ -728,14 +1070,23 @@ dependencies = [ [[package]] name = "signature" -version = "1.4.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" dependencies = [ "digest", "rand_core 0.6.3", ] +[[package]] +name = "spki" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" +dependencies = [ + "der 0.4.5", +] + [[package]] name = "spki" version = "0.5.4" @@ -743,7 +1094,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" dependencies = [ "base64ct", - "der", + "der 0.5.1", ] [[package]] @@ -769,6 +1120,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "terra-cosmwasm" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552f18cba2b535d1f8c0e3b3f37696820b954bc7535d2e33909f2a6342302718" +dependencies = [ + "cosmwasm-std 0.16.7", + "schemars", + "serde", +] + [[package]] name = "thiserror" version = "1.0.32" @@ -833,6 +1195,6 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" -version = "1.3.0" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index a28b906a9..e6a76cc7f 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -3,6 +3,8 @@ name = "account-nft" version = "0.1.0" authors = ["larry_0x , grod220 "] edition = "2018" +license = "GPL-3.0-or-later" +repository = "https://github.com/mars-protocol/rover" [lib] crate-type = ["cdylib", "rlib"] diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index d8b4346d2..341d6d4ad 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -3,6 +3,8 @@ name = "credit-manager" version = "0.1.0" authors = ["grod220 , larry_0x "] edition = "2018" +license = "GPL-3.0-or-later" +repository = "https://github.com/mars-protocol/rover" [lib] crate-type = ["cdylib", "rlib"] @@ -13,6 +15,8 @@ library = [] [dependencies] account-nft = { version = "0.1", path = "../account-nft", features = ["library"] } +mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } +mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } rover = { version = "0.1", path = "../../packages/rover" } cosmwasm-std = "1.0" @@ -26,4 +30,3 @@ serde = { version = "1.0", default-features = false, features = ["derive"] } [dev-dependencies] anyhow = "1" cw-multi-test = "0.14" -mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index 719f1f0a0..982163f56 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -22,7 +22,7 @@ pub fn borrow(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRe let red_bank = RED_BANK.load(deps.storage)?; let total_debt_amount = - red_bank.query_user_debt(&deps.querier, &env.contract.address, &coin.denom)?; + red_bank.query_debt(&deps.querier, &env.contract.address, &coin.denom)?; let debt_shares_to_add = if total_debt_amount.is_zero() { coin.amount diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 3c8652e8d..6f7b3f289 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -1,6 +1,4 @@ -use cosmwasm_std::{ - entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, -}; +use cosmwasm_std::{entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; use cw2::set_contract_version; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -46,8 +44,8 @@ pub fn execute( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { + let res = match msg { QueryMsg::Config {} => to_binary(&query_config(deps)?), QueryMsg::AllowedVaults { start_after, limit } => { to_binary(&query_allowed_vaults(deps, start_after, limit)?) @@ -55,7 +53,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::AllowedCoins { start_after, limit } => { to_binary(&query_allowed_coins(deps, start_after, limit)?) } - QueryMsg::Position { token_id } => to_binary(&query_position(deps, &token_id)?), + QueryMsg::Position { token_id } => to_binary(&query_position(deps, &env, &token_id)?), QueryMsg::AllAssets { start_after, limit } => { to_binary(&query_all_assets(deps, start_after, limit)?) } @@ -66,5 +64,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::AllTotalDebtShares { start_after, limit } => { to_binary(&query_all_total_debt_shares(deps, start_after, limit)?) } - } + }; + res.map_err(Into::into) } diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index 4435673f7..44d819441 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -1,8 +1,7 @@ use cosmwasm_std::{Coin, Response, StdError, StdResult, Storage, Uint128}; use rover::coins::Coins; -use rover::error::ContractError; -use rover::ContractResult; +use rover::error::{ContractError, ContractResult}; use crate::state::{ALLOWED_COINS, ASSETS}; diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 4d9374c4b..fe84b5980 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -13,7 +13,8 @@ use rover::ContractResult; use crate::borrow::borrow; use crate::deposit::deposit; -use crate::state::{ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, OWNER, RED_BANK}; +use crate::health::assert_health; +use crate::state::{ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, ORACLE, OWNER, RED_BANK}; pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; @@ -99,6 +100,13 @@ pub fn update_config( .add_attribute("value", unchecked.0); } + if let Some(unchecked) = new_config.oracle { + ORACLE.save(deps.storage, &unchecked.check(deps.api)?)?; + response = response + .add_attribute("key", "oracle") + .add_attribute("value", unchecked.0); + } + Ok(response) } @@ -133,6 +141,11 @@ pub fn dispatch_actions( return Err(ContractError::ExtraFundsReceived(received_coins)); } + // after user selected actions, we assert LTV is healthy; if not, throw error and revert all actions + callbacks.extend([CallbackMsg::AssertHealth { + token_id: token_id.to_string(), + }]); + let callback_msgs = callbacks .iter() .map(|callback| callback.into_cosmos_msg(&env.contract.address)) @@ -154,6 +167,7 @@ pub fn execute_callback( } match callback { CallbackMsg::Borrow { coin, token_id } => borrow(deps, env, &token_id, coin), + CallbackMsg::AssertHealth { token_id } => assert_health(deps, env, &token_id), } } diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs new file mode 100644 index 000000000..f590e12b4 --- /dev/null +++ b/contracts/credit-manager/src/health.rs @@ -0,0 +1,97 @@ +use std::ops::{Add, Div, Mul}; + +use cosmwasm_std::{Decimal, Deps, DepsMut, Env, Event, Response, StdResult, Uint128}; + +use mock_red_bank::msg::{Market, QueryMsg}; +use rover::error::{ContractError, ContractResult}; +use rover::health::Health; +use rover::msg::query::{CoinSharesValue, CoinValue}; +use rover::NftTokenId; + +use crate::query::query_position; +use crate::state::RED_BANK; + +/// Compute the health of a token's position +pub fn compute_health( + deps: &Deps, + assets: &[CoinValue], + debts: &[CoinSharesValue], +) -> ContractResult { + // The sum of the position's assets weighted by max LTV + let (ltv_adjusted_assets_value, assets_value) = assets.iter().try_fold::<_, _, StdResult<_>>( + (Decimal::zero(), Decimal::zero()), + |(ltv_adjusted_total, base_total), item| { + let red_bank = RED_BANK.load(deps.storage)?; + let market: Market = deps.querier.query_wasm_smart( + red_bank.0, + &QueryMsg::Market { + denom: item.denom.clone(), + }, + )?; + Ok(( + ltv_adjusted_total.add(item.value.mul(market.max_loan_to_value)), + base_total.add(item.value), + )) + }, + )?; + + let (debts_shares, debts_value) = debts.iter().fold( + (Uint128::zero(), Decimal::zero()), + |(total_shares, total_value), item| { + (total_shares.add(item.shares), total_value.add(item.value)) + }, + ); + + // Health Factor = Sum(Value of Asset * Max LTV) / Sum (Value of Total Borrowed) + // If there aren't any debts a health factor can't be computed (divide by zero) + let health_factor = if debts_value.is_zero() { + None + } else { + Some(ltv_adjusted_assets_value.div(debts_value)) + }; + + // If Some(health_factor), we assert it is no less than 1 + // If it is None, meaning `debts_value` is zero, we assert debt shares are also zero + // + // NOTE: We assert debt shares are zero, instead of `debts_value`. + // This is because value can be zero as a result of rounding down. + let healthy = if let Some(hf) = health_factor { + hf > Decimal::one() + } else { + debts_shares.is_zero() + }; + + Ok(Health { + assets_value, + ltv_adjusted_assets_value, + debts_value, + health_factor, + healthy, + }) +} + +pub fn assert_health(deps: DepsMut, env: Env, token_id: NftTokenId) -> ContractResult { + let position = query_position(deps.as_ref(), &env, token_id)?; + let hf_str = position + .health_factor + .map_or("n/a".to_string(), |dec| dec.to_string()); + + if !position.healthy { + return Err(ContractError::AccountUnhealthy { + health_factor: hf_str, + }); + } + + let event = Event::new("position_changed") + .add_attribute("timestamp", env.block.time.seconds().to_string()) + .add_attribute("height", env.block.height.to_string()) + .add_attribute("token_id", token_id) + .add_attribute("assets_value", position.assets_value.to_string()) + .add_attribute("debt_value", position.debts_value.to_string()) + .add_attribute("health_factor", hf_str) + .add_attribute("healthy", position.healthy.to_string()); + + Ok(Response::new() + .add_attribute("action", "rover/credit_manager/callback/assert_health") + .add_event(event)) +} diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index aad47cf82..a9b427fc4 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,16 +1,17 @@ use cosmwasm_std::{DepsMut, StdResult}; use rover::msg::InstantiateMsg; -use crate::state::{ALLOWED_COINS, ALLOWED_VAULTS, OWNER, RED_BANK}; +use crate::state::{ALLOWED_COINS, ALLOWED_VAULTS, ORACLE, OWNER, RED_BANK}; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { let owner = deps.api.addr_validate(&msg.owner)?; OWNER.save(deps.storage, &owner)?; - RED_BANK.save(deps.storage, &msg.red_bank.check(deps.api)?)?; + ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; - msg.allowed_vaults.iter().try_for_each(|vault| { - ALLOWED_VAULTS.save(deps.storage, deps.api.addr_validate(vault)?, &true) + msg.allowed_vaults.iter().try_for_each(|unchecked| { + let vault = deps.api.addr_validate(unchecked)?; + ALLOWED_VAULTS.save(deps.storage, vault, &true) })?; msg.allowed_coins diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 2bfb1c1f3..e63c5d67b 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -5,6 +5,7 @@ pub mod contract; pub mod borrow; pub mod deposit; pub mod execute; +pub mod health; pub mod instantiate; pub mod query; pub mod state; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 3b7e6630d..f52dc2be8 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,13 +1,15 @@ -use cosmwasm_std::{Coin, Deps, Order, StdResult}; +use cosmwasm_std::{Decimal, Deps, Env, Order, StdResult, Uint128}; use cw_storage_plus::Bound; use rover::msg::query::{ - AssetResponseItem, CoinShares, ConfigResponse, PositionResponse, SharesResponseItem, + AssetResponseItem, CoinShares, CoinSharesValue, CoinValue, ConfigResponse, PositionResponse, + SharesResponseItem, }; -use rover::Denom; +use rover::{ContractResult, Denom, NftTokenId, Shares}; +use crate::health::compute_health; use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, ASSETS, DEBT_SHARES, OWNER, RED_BANK, + ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, ASSETS, DEBT_SHARES, ORACLE, OWNER, RED_BANK, TOTAL_DEBT_SHARES, }; @@ -21,32 +23,31 @@ pub fn query_config(deps: Deps) -> StdResult { .may_load(deps.storage)? .map(|addr| addr.to_string()), red_bank: RED_BANK.load(deps.storage)?.0.into(), + oracle: ORACLE.load(deps.storage)?.0.into(), }) } -pub fn query_position(deps: Deps, token_id: &str) -> StdResult { +pub fn query_position( + deps: Deps, + env: &Env, + token_id: NftTokenId, +) -> ContractResult { + let assets = get_assets_value(deps, token_id)?; + let debt_shares = get_debts_value(deps, env, token_id)?; + let health = compute_health(&deps, &assets, &debt_shares)?; + Ok(PositionResponse { token_id: token_id.to_string(), - assets: get_assets(deps, token_id)?, - debt_shares: get_debt_shares(deps, token_id)?, + assets, + debt_shares, + assets_value: health.assets_value, + ltv_adjusted_assets_value: health.ltv_adjusted_assets_value, + debts_value: health.debts_value, + health_factor: health.health_factor, + healthy: health.healthy, }) } -fn get_assets(deps: Deps, token_id: &str) -> StdResult> { - ASSETS - .prefix(token_id) - .range(deps.storage, None, None, Order::Ascending) - .collect::>>()? - .iter() - .map(|(denom, amount)| { - Ok(Coin { - denom: denom.clone(), - amount: *amount, - }) - }) - .collect() -} - pub fn query_all_assets( deps: Deps, start_after: Option<(String, String)>, @@ -70,14 +71,41 @@ pub fn query_all_assets( .collect()) } -fn get_debt_shares(deps: Deps, token_id: &str) -> StdResult> { +fn get_debts_value( + deps: Deps, + env: &Env, + token_id: NftTokenId, +) -> ContractResult> { + let oracle = ORACLE.load(deps.storage)?; DEBT_SHARES .prefix(token_id) .range(deps.storage, None, None, Order::Ascending) - .collect::>>()? + .collect::>>()? .iter() .map(|(denom, shares)| { - Ok(CoinShares { + // proportion of debt this token represents + let total_debt_shares = TOTAL_DEBT_SHARES + .load(deps.storage, denom) + .unwrap_or(Uint128::zero()); + let token_share_ratio = Decimal::checked_from_ratio(*shares, total_debt_shares)?; + + // total rover debt for asset + let total_debt_amount = RED_BANK.load(deps.storage)?.query_debt( + &deps.querier, + &env.contract.address, + denom, + )?; + let total_debt_amount = Decimal::from_atomics(total_debt_amount, 0)?; + + // debt value of token's position + let price_per_unit = oracle.query_price(&deps.querier, denom)?; + let position_debt_value = price_per_unit + .checked_mul(total_debt_amount)? + .checked_mul(token_share_ratio)?; + + Ok(CoinSharesValue { + value: position_debt_value, + price_per_unit, denom: denom.clone(), shares: *shares, }) @@ -85,6 +113,27 @@ fn get_debt_shares(deps: Deps, token_id: &str) -> StdResult> { .collect() } +fn get_assets_value(deps: Deps, token_id: &str) -> ContractResult> { + let oracle = ORACLE.load(deps.storage)?; + ASSETS + .prefix(token_id) + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()? + .iter() + .map(|(denom, amount)| { + let price_per_unit = oracle.query_price(&deps.querier, denom)?; + let decimal_amount = Decimal::from_atomics(*amount, 0)?; + let value = price_per_unit.checked_mul(decimal_amount)?; + Ok(CoinValue { + value, + price_per_unit, + denom: denom.clone(), + amount: *amount, + }) + }) + .collect() +} + pub fn query_all_debt_shares( deps: Deps, start_after: Option<(String, String)>, @@ -126,11 +175,13 @@ pub fn query_allowed_vaults( let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - ALLOWED_VAULTS + Ok(ALLOWED_VAULTS .keys(deps.storage, start, None, Order::Ascending) .take(limit) - .map(|res| res.map(|vault_addr| vault_addr.to_string())) - .collect() + .collect::>>()? + .iter() + .map(|addr| addr.to_string()) + .collect()) } /// NOTE: This implementation of the query function assumes the map `ALLOWED_COINS` only saves `true`. diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index bb6656d52..35a81b766 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::{Item, Map}; -use rover::adapters::RedBank; +use rover::adapters::{Oracle, RedBank}; use rover::{Denom, NftTokenId, Shares}; // Contract config @@ -10,6 +10,7 @@ pub const ACCOUNT_NFT: Item = Item::new("account_nft"); pub const ALLOWED_COINS: Map = Map::new("allowed_coins"); pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); pub const RED_BANK: Item = Item::new("red_bank"); +pub const ORACLE: Item = Item::new("oracle"); // Positions pub const ASSETS: Map<(NftTokenId, Denom), Uint128> = Map::new("assets"); diff --git a/contracts/credit-manager/tests/borrow_test.rs b/contracts/credit-manager/tests/borrow_test.rs index 8805f5b3e..0ac1c7755 100644 --- a/contracts/credit-manager/tests/borrow_test.rs +++ b/contracts/credit-manager/tests/borrow_test.rs @@ -1,20 +1,20 @@ use std::ops::{Mul, Sub}; -use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use cw_multi_test::{App, Executor}; use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; use mock_red_bank::msg::QueryMsg::UserAssetDebt; use mock_red_bank::msg::UserAssetDebtResponse; use rover::error::ContractError; -use rover::msg::execute::Action::Borrow; +use rover::msg::execute::Action::{Borrow, Deposit}; use rover::msg::query::CoinShares; use rover::msg::ExecuteMsg::UpdateCreditAccount; use rover::msg::QueryMsg; use crate::helpers::{ - assert_err, fund_red_bank_native, get_token_id, mock_app, mock_create_credit_account, - query_config, query_position, setup_credit_manager, + assert_err, fund_red_bank, get_token_id, mock_app, mock_create_credit_account, query_config, + query_position, setup_credit_manager, CoinPriceLTV, }; pub mod helpers; @@ -23,12 +23,14 @@ pub mod helpers; fn test_only_token_owner_can_borrow() { let mut app = mock_app(); let owner = Addr::unchecked("owner"); - let coin = Coin { + + let coin_info = CoinPriceLTV { denom: "uosmo".to_string(), - amount: Uint128::zero(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), }; - let mock = setup_credit_manager(&mut app, &owner, vec![coin.denom.clone()], vec![]); + let mock = setup_credit_manager(&mut app, &owner, vec![coin_info.clone()], vec![]); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &Addr::unchecked("user")) .unwrap(); let token_id = get_token_id(res); @@ -39,7 +41,7 @@ fn test_only_token_owner_can_borrow() { mock.credit_manager.clone(), &UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Borrow(coin)], + actions: vec![Borrow(coin_info.to_coin(Uint128::new(12312u128)))], }, &[], ); @@ -57,12 +59,13 @@ fn test_only_token_owner_can_borrow() { fn test_can_only_borrow_what_is_whitelisted() { let mut app = mock_app(); let owner = Addr::unchecked("owner"); - let coin = Coin { - denom: "usomething".to_string(), - amount: Uint128::from(234u128), + let coin_info = CoinPriceLTV { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), }; - let mock = setup_credit_manager(&mut app, &owner, vec!["uosmo".to_string()], vec![]); + let mock = setup_credit_manager(&mut app, &owner, vec![coin_info], vec![]); let user = Addr::unchecked("user"); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); @@ -72,7 +75,10 @@ fn test_can_only_borrow_what_is_whitelisted() { mock.credit_manager.clone(), &UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Borrow(coin)], + actions: vec![Borrow(Coin { + denom: "usomething".to_string(), + amount: Uint128::from(234u128), + })], }, &[], ); @@ -86,15 +92,16 @@ fn test_can_only_borrow_what_is_whitelisted() { #[test] fn test_borrowing_zero_does_nothing() { let mut app = mock_app(); - let coin = Coin { + let coin_info = CoinPriceLTV { denom: "uosmo".to_string(), - amount: Uint128::zero(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), }; let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![coin.denom.clone()], + vec![coin_info.clone()], vec![], ); let user = Addr::unchecked("user"); @@ -106,7 +113,7 @@ fn test_borrowing_zero_does_nothing() { mock.credit_manager.clone(), &UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Borrow(coin)], + actions: vec![Borrow(coin_info.to_coin(Uint128::zero()))], }, &[], ); @@ -121,22 +128,26 @@ fn test_borrowing_zero_does_nothing() { #[test] fn test_success_when_new_debt_asset() { let user = Addr::unchecked("user"); - let funds = Coin::new(300u128, "uosmo"); - let coin = Coin { + let coin_info = CoinPriceLTV { denom: "uosmo".to_string(), - amount: Uint128::from(42u128), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), }; let mut app = App::new(|router, _, storage| { router .bank - .init_balance(storage, &user, vec![funds]) + .init_balance( + storage, + &user, + vec![Coin::new(300u128, coin_info.denom.clone())], + ) .unwrap(); }); let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![coin.denom.clone()], + vec![coin_info.clone()], vec![], ); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); @@ -144,24 +155,32 @@ fn test_success_when_new_debt_asset() { let config = query_config(&mut app, &mock.credit_manager.clone()); - fund_red_bank_native( + fund_red_bank( &mut app, config.red_bank.clone(), - vec![Coin::new(1000u128, "uosmo")], + vec![Coin::new(1000u128, coin_info.denom.clone())], ); let position = query_position(&mut app, &mock.credit_manager, &token_id); assert_eq!(position.assets.len(), 0); assert_eq!(position.debt_shares.len(), 0); - app.execute_contract( user, mock.credit_manager.clone(), &UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Borrow(coin.clone())], + actions: vec![ + Deposit(Coin { + denom: coin_info.denom.clone(), + amount: Uint128::from(300u128), + }), + Borrow(Coin { + denom: coin_info.denom.clone(), + amount: Uint128::from(42u128), + }), + ], }, - &[], + &[Coin::new(300u128, coin_info.denom.clone())], ) .unwrap(); @@ -169,23 +188,26 @@ fn test_success_when_new_debt_asset() { assert_eq!(position.assets.len(), 1); assert_eq!( position.assets.first().unwrap().amount, - Uint128::from(42u128) + Uint128::from(342u128) // Deposit + Borrow ); - assert_eq!(position.assets.first().unwrap().denom, coin.denom); + assert_eq!(position.assets.first().unwrap().denom, coin_info.denom); assert_eq!(position.debt_shares.len(), 1); assert_eq!( position.debt_shares.first().unwrap().shares, Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) ); - assert_eq!(position.debt_shares.first().unwrap().denom, coin.denom); + assert_eq!(position.debt_shares.first().unwrap().denom, coin_info.denom); let coin = app .wrap() - .query_balance(mock.credit_manager.clone(), "uosmo") + .query_balance(mock.credit_manager.clone(), coin_info.denom.clone()) .unwrap(); - assert_eq!(coin.amount, Uint128::from(42u128)); + assert_eq!(coin.amount, Uint128::from(342u128)); - let coin = app.wrap().query_balance(config.red_bank, "uosmo").unwrap(); + let coin = app + .wrap() + .query_balance(config.red_bank, coin_info.denom.clone()) + .unwrap(); assert_eq!( coin.amount, Uint128::from(1000u128).sub(Uint128::from(42u128)) @@ -193,7 +215,10 @@ fn test_success_when_new_debt_asset() { let res: CoinShares = app .wrap() - .query_wasm_smart(mock.credit_manager, &QueryMsg::TotalDebtShares(coin.denom)) + .query_wasm_smart( + mock.credit_manager, + &QueryMsg::TotalDebtShares(coin_info.denom), + ) .unwrap(); assert_eq!( res.shares, @@ -205,26 +230,34 @@ fn test_success_when_new_debt_asset() { fn test_debt_shares_with_debt_amount() { let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); - let coin = Coin { + let coin_info = CoinPriceLTV { denom: "uosmo".to_string(), - amount: Uint128::from(50u128), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), }; - let mut app = App::new(|router, _, storage| { router .bank - .init_balance(storage, &user_a, vec![Coin::new(300u128, "uosmo")]) + .init_balance( + storage, + &user_a, + vec![Coin::new(300u128, coin_info.denom.clone())], + ) .unwrap(); router .bank - .init_balance(storage, &user_b, vec![Coin::new(450u128, "uosmo")]) + .init_balance( + storage, + &user_b, + vec![Coin::new(450u128, coin_info.denom.clone())], + ) .unwrap(); }); let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![coin.denom.clone()], + vec![coin_info.clone()], vec![], ); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); @@ -234,10 +267,10 @@ fn test_debt_shares_with_debt_amount() { let config = query_config(&mut app, &mock.credit_manager.clone()); - fund_red_bank_native( + fund_red_bank( &mut app, config.red_bank.clone(), - vec![Coin::new(1000u128, "uosmo")], + vec![Coin::new(1000u128, coin_info.denom.clone())], ); app.execute_contract( @@ -245,9 +278,12 @@ fn test_debt_shares_with_debt_amount() { mock.credit_manager.clone(), &UpdateCreditAccount { token_id: token_id_a.clone(), - actions: vec![Borrow(coin.clone())], + actions: vec![ + Deposit(coin_info.to_coin(Uint128::from(300u128))), + Borrow(coin_info.to_coin(Uint128::from(50u128))), + ], }, - &[], + &[Coin::new(300u128, coin_info.denom.clone())], ) .unwrap(); @@ -257,7 +293,7 @@ fn test_debt_shares_with_debt_amount() { config.red_bank, &UserAssetDebt { user_address: mock.credit_manager.clone().into(), - denom: coin.denom.clone(), + denom: coin_info.denom.clone(), }, ) .unwrap(); @@ -267,9 +303,12 @@ fn test_debt_shares_with_debt_amount() { mock.credit_manager.clone(), &UpdateCreditAccount { token_id: token_id_b.clone(), - actions: vec![Borrow(coin.clone())], + actions: vec![ + Deposit(coin_info.to_coin(Uint128::from(450u128))), + Borrow(coin_info.to_coin(Uint128::from(50u128))), + ], }, - &[], + &[Coin::new(450u128, coin_info.denom.clone())], ) .unwrap(); @@ -292,7 +331,10 @@ fn test_debt_shares_with_debt_amount() { let res: CoinShares = app .wrap() - .query_wasm_smart(mock.credit_manager, &QueryMsg::TotalDebtShares(coin.denom)) + .query_wasm_smart( + mock.credit_manager, + &QueryMsg::TotalDebtShares(coin_info.denom), + ) .unwrap(); assert_eq!(res.shares, token_a_shares + token_b_shares); } diff --git a/contracts/credit-manager/tests/create_credit_account_test.rs b/contracts/credit-manager/tests/create_credit_account_test.rs index 8e602570e..5ff85df68 100644 --- a/contracts/credit-manager/tests/create_credit_account_test.rs +++ b/contracts/credit-manager/tests/create_credit_account_test.rs @@ -3,7 +3,8 @@ use cw721::OwnerOfResponse; use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw721_base::QueryMsg as NftQueryMsg; use cw_multi_test::Executor; -use rover::adapters::RedBankBase; + +use rover::adapters::{OracleBase, RedBankBase}; use rover::msg::instantiate::ConfigUpdates; use rover::msg::query::ConfigResponse; use rover::msg::ExecuteMsg::UpdateConfig; @@ -44,6 +45,7 @@ fn test_create_credit_account() { allowed_vaults: vec![], allowed_coins: vec![], red_bank: RedBankBase("redbankaddr".to_string()), + oracle: OracleBase("oracle_contract".to_string()), }; let manager_contract_addr = app @@ -70,10 +72,7 @@ fn test_create_credit_account() { &UpdateConfig { new_config: ConfigUpdates { account_nft: Some(nft_contract_addr.to_string()), - owner: None, - allowed_coins: None, - allowed_vaults: None, - red_bank: None, + ..Default::default() }, }, &[], diff --git a/contracts/credit-manager/tests/deposit_test.rs b/contracts/credit-manager/tests/deposit_test.rs index da56c6d88..8c508d64d 100644 --- a/contracts/credit-manager/tests/deposit_test.rs +++ b/contracts/credit-manager/tests/deposit_test.rs @@ -1,6 +1,6 @@ extern crate core; -use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use cw_multi_test::{App, Executor}; use rover::coins::Coins; @@ -8,11 +8,12 @@ use rover::error::ContractError::{ ExtraFundsReceived, FundsMismatch, NotTokenOwner, NotWhitelisted, }; use rover::msg::execute::Action; +use rover::msg::query::PositionResponse; use rover::msg::ExecuteMsg; use crate::helpers::{ assert_err, get_token_id, mock_app, mock_create_credit_account, query_position, - setup_credit_manager, + setup_credit_manager, CoinPriceLTV, }; pub mod helpers; @@ -25,12 +26,7 @@ fn test_only_owner_of_token_can_deposit() { amount: Uint128::zero(), }; - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin.denom.clone()], - vec![], - ); + let mock = setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![], vec![]); let user = Addr::unchecked("user"); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); @@ -59,14 +55,15 @@ fn test_only_owner_of_token_can_deposit() { #[test] fn test_deposit_nothing() { let mut app = mock_app(); - let coin = Coin { + let coin_info = CoinPriceLTV { denom: "uosmo".to_string(), - amount: Uint128::zero(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), }; let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![coin.denom.clone()], + vec![coin_info.clone()], vec![], ); @@ -82,7 +79,7 @@ fn test_deposit_nothing() { mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::Deposit(coin)], + actions: vec![Action::Deposit(coin_info.to_coin(Uint128::zero()))], }, &[], ) @@ -95,14 +92,16 @@ fn test_deposit_nothing() { #[test] fn test_deposit_but_no_funds() { let mut app = mock_app(); - let coin = Coin { + let coin_info = CoinPriceLTV { denom: "uosmo".to_string(), - amount: Uint128::from(234u128), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), }; + let deposit_amount = Uint128::from(234u128); let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![coin.denom.clone()], + vec![coin_info.clone()], vec![], ); @@ -115,7 +114,7 @@ fn test_deposit_but_no_funds() { mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::Deposit(coin.clone())], + actions: vec![Action::Deposit(coin_info.to_coin(deposit_amount.clone()))], }, &[], ); @@ -123,7 +122,7 @@ fn test_deposit_but_no_funds() { assert_err( res, FundsMismatch { - expected: coin.amount, + expected: deposit_amount, received: Uint128::zero(), }, ); @@ -135,21 +134,26 @@ fn test_deposit_but_no_funds() { #[test] fn test_deposit_but_not_enough_funds() { let user = Addr::unchecked("user"); + let coin_info = CoinPriceLTV { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + }; let mut app = App::new(|router, _, storage| { router .bank - .init_balance(storage, &user, vec![Coin::new(300u128, "uosmo")]) + .init_balance( + storage, + &user, + vec![Coin::new(300u128, coin_info.denom.clone())], + ) .unwrap(); }); - let coin = Coin { - denom: "uosmo".to_string(), - amount: Uint128::from(350u128), - }; let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![coin.denom.clone()], + vec![coin_info.clone()], vec![], ); @@ -161,15 +165,15 @@ fn test_deposit_but_not_enough_funds() { mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::Deposit(coin.clone())], + actions: vec![Action::Deposit(coin_info.to_coin(Uint128::from(350u128)))], }, - &[Coin::new(250u128, "uosmo")], + &[Coin::new(250u128, coin_info.denom.clone())], ); assert_err( res, FundsMismatch { - expected: coin.amount, + expected: Uint128::from(350u128), received: Uint128::from(250u128), }, ); @@ -178,27 +182,34 @@ fn test_deposit_but_not_enough_funds() { #[test] fn test_can_only_deposit_allowed_assets() { let user = Addr::unchecked("user"); - let funds = Coin::new(300u128, "uosmo"); - + let coin_info = CoinPriceLTV { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + }; let mut app = App::new(|router, _, storage| { router .bank - .init_balance(storage, &user, vec![funds]) + .init_balance( + storage, + &user, + vec![Coin::new(300u128, coin_info.denom.clone())], + ) .unwrap(); }); let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec!["ucosmos".to_string()], + vec![coin_info.clone()], vec![], ); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); - let coin = Coin { - denom: "uosmo".to_string(), + let not_allowed_coin = Coin { + denom: "ujakecoin".to_string(), amount: Uint128::from(234u128), }; let res = app.execute_contract( @@ -206,12 +217,12 @@ fn test_can_only_deposit_allowed_assets() { mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::Deposit(coin.clone())], + actions: vec![Action::Deposit(not_allowed_coin.clone())], }, - &[Coin::new(234u128, "uosmo")], + &[Coin::new(234u128, coin_info.denom.clone())], ); - assert_err(res, NotWhitelisted(coin.denom)); + assert_err(res, NotWhitelisted(not_allowed_coin.denom)); let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.assets.len(), 0); @@ -220,42 +231,52 @@ fn test_can_only_deposit_allowed_assets() { #[test] fn test_extra_funds_received() { let user = Addr::unchecked("user"); - + let uosmo_info = CoinPriceLTV { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + }; + let uatom_info = CoinPriceLTV { + denom: "uatom".to_string(), + price: Decimal::from_atomics(10u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), + }; let mut app = App::new(|router, _, storage| { router .bank .init_balance( storage, &user, - vec![Coin::new(300u128, "uosmo"), Coin::new(50u128, "ucosmos")], + vec![ + Coin::new(300u128, uosmo_info.denom.clone()), + Coin::new(250u128, uatom_info.denom.clone()), + ], ) .unwrap(); }); - let coin = Coin { - denom: "uosmo".to_string(), - amount: Uint128::from(234u128), - }; - let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![coin.denom.to_string()], + vec![uosmo_info.clone(), uatom_info.clone()], vec![], ); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); - let extra_funds = Coin::new(25u128, "ucosmos"); + let extra_funds = Coin::new(25u128, uatom_info.denom.clone()); let res = app.execute_contract( user.clone(), mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::Deposit(coin)], + actions: vec![Action::Deposit(uosmo_info.to_coin(Uint128::from(234u128)))], }, - &[Coin::new(234u128, "uosmo"), extra_funds.clone()], + &[ + Coin::new(234u128, uosmo_info.denom.clone()), + extra_funds.clone(), + ], ); assert_err(res, ExtraFundsReceived(Coins::from(vec![extra_funds]))); @@ -267,112 +288,136 @@ fn test_extra_funds_received() { #[test] fn test_deposit_success() { let user = Addr::unchecked("user"); - let funds = Coin::new(300u128, "uosmo"); - - let coin = Coin { + let coin_info = CoinPriceLTV { denom: "uosmo".to_string(), - amount: Uint128::from(234u128), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), }; let mut app = App::new(|router, _, storage| { router .bank - .init_balance(storage, &user, vec![funds]) + .init_balance( + storage, + &user, + vec![Coin::new(300u128, coin_info.denom.clone())], + ) .unwrap(); }); let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![coin.denom.clone()], + vec![coin_info.clone()], vec![], ); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); + let deposit_amount = Uint128::from(234u128); + app.execute_contract( user.clone(), mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::Deposit(coin.clone())], + actions: vec![Action::Deposit(coin_info.to_coin(deposit_amount))], }, - &[Coin::new(234u128, "uosmo")], + &[Coin::new(deposit_amount.into(), coin_info.denom.clone())], ) .unwrap(); let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.assets.len(), 1); - assert_eq!(res.assets.first().unwrap().amount, coin.amount); - assert_eq!(res.assets.first().unwrap().denom, coin.denom); + assert_eq!(res.assets.first().unwrap().amount, deposit_amount); + assert_eq!(res.assets.first().unwrap().denom, coin_info.denom); + assert_eq!(res.assets.first().unwrap().price_per_unit, coin_info.price); let coin = app .wrap() - .query_balance(mock.credit_manager, "uosmo") + .query_balance(mock.credit_manager, coin_info.denom.clone()) .unwrap(); - assert_eq!(coin.amount, coin.amount) + assert_eq!(coin.amount, deposit_amount) } #[test] fn test_multiple_deposit_actions() { let user = Addr::unchecked("user"); + let uosmo_info = CoinPriceLTV { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + }; + let uatom_info = CoinPriceLTV { + denom: "uatom".to_string(), + price: Decimal::from_atomics(10u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), + }; let mut app = App::new(|router, _, storage| { router .bank .init_balance( storage, &user, - vec![Coin::new(300u128, "uosmo"), Coin::new(50u128, "ucosmos")], + vec![ + Coin::new(300u128, uosmo_info.denom.clone()), + Coin::new(50u128, uatom_info.denom.clone()), + ], ) .unwrap(); }); - let coin_a = Coin { - denom: "uosmo".to_string(), - amount: Uint128::from(234u128), - }; - - let coin_b = Coin { - denom: "ucosmos".to_string(), - amount: Uint128::from(25u128), - }; - let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![coin_a.clone().denom, coin_b.clone().denom], + vec![uosmo_info.clone(), uatom_info.clone()], vec![], ); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); + let uosmo_amount = Uint128::from(234u128); + let uatom_amount = Uint128::from(25u128); + app.execute_contract( user.clone(), mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), actions: vec![ - Action::Deposit(coin_a.clone()), - Action::Deposit(coin_b.clone()), + Action::Deposit(uosmo_info.to_coin(uosmo_amount)), + Action::Deposit(uatom_info.to_coin(uatom_amount)), ], }, - &[Coin::new(234u128, "uosmo"), Coin::new(25u128, "ucosmos")], + &[ + Coin::new(234u128, uosmo_info.denom.clone()), + Coin::new(25u128, uatom_info.denom.clone()), + ], ) .unwrap(); let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.assets.len(), 2); + assert_present(&res, &uosmo_info, uosmo_amount); + assert_present(&res, &uatom_info, uatom_amount); let coin = app .wrap() - .query_balance(mock.credit_manager.clone(), "uosmo") + .query_balance(mock.credit_manager.clone(), uosmo_info.denom.clone()) .unwrap(); - assert_eq!(coin.amount, Uint128::from(234u128)); + assert_eq!(coin.amount, uosmo_amount); let coin = app .wrap() - .query_balance(mock.credit_manager, "ucosmos") + .query_balance(mock.credit_manager, "uatom") + .unwrap(); + assert_eq!(coin.amount, uatom_amount); +} + +fn assert_present(res: &PositionResponse, coin: &CoinPriceLTV, amount: Uint128) { + res.assets + .iter() + .find(|item| item.denom == coin.denom && &item.amount == &amount) .unwrap(); - assert_eq!(coin.amount, Uint128::from(25u128)); } diff --git a/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs b/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs index 9a993e131..6b2cba7ba 100644 --- a/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs +++ b/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs @@ -1,7 +1,7 @@ use cosmwasm_std::Addr; use cw_multi_test::Executor; -use rover::adapters::RedBankBase; +use rover::adapters::{OracleBase, RedBankBase}; use rover::msg::{InstantiateMsg, QueryMsg}; use crate::helpers::{mock_app, mock_contract}; @@ -54,6 +54,7 @@ fn test_pagination_on_allowed_coins_query_works() { allowed_vaults: vec![], allowed_coins: allowed_coins.clone(), red_bank: RedBankBase("redbankaddr".to_string()), + oracle: OracleBase("oracle_contract".to_string()), }; let contract_addr = app diff --git a/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs b/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs index a60428f42..4db9b1f2b 100644 --- a/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs +++ b/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs @@ -1,7 +1,7 @@ use cosmwasm_std::Addr; use cw_multi_test::Executor; -use rover::adapters::RedBankBase; +use rover::adapters::{OracleBase, RedBankBase}; use rover::msg::{InstantiateMsg, QueryMsg}; use crate::helpers::{mock_app, mock_contract}; @@ -54,6 +54,7 @@ fn test_pagination_on_allowed_vaults_query_works() { allowed_vaults: allowed_vaults.clone(), allowed_coins: vec![], red_bank: RedBankBase("redbankaddr".to_string()), + oracle: OracleBase("oracle_contract".to_string()), }; let contract_addr = app diff --git a/contracts/credit-manager/tests/enumerate_assets_test.rs b/contracts/credit-manager/tests/enumerate_assets_test.rs index 9c8e28f68..ad2aa6ce1 100644 --- a/contracts/credit-manager/tests/enumerate_assets_test.rs +++ b/contracts/credit-manager/tests/enumerate_assets_test.rs @@ -1,11 +1,13 @@ -use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use cw_multi_test::{App, Executor}; use rover::msg::execute::Action; use rover::msg::query::AssetResponseItem; use rover::msg::{ExecuteMsg, QueryMsg}; -use crate::helpers::{get_token_id, mock_create_credit_account, setup_credit_manager}; +use crate::helpers::{ + get_token_id, mock_create_credit_account, setup_credit_manager, CoinPriceLTV, +}; pub mod helpers; @@ -75,20 +77,76 @@ fn test_pagination_on_all_assets_query_works() { &mut app, &Addr::unchecked("owner"), vec![ - "coin_1".to_string(), - "coin_2".to_string(), - "coin_3".to_string(), - "coin_4".to_string(), - "coin_5".to_string(), - "coin_6".to_string(), - "coin_7".to_string(), - "coin_8".to_string(), - "coin_9".to_string(), - "coin_10".to_string(), - "coin_11".to_string(), - "coin_12".to_string(), - "coin_13".to_string(), - "coin_14".to_string(), + CoinPriceLTV { + denom: "coin_1".to_string(), + price: Decimal::from_atomics(1u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(1u128, 2).unwrap(), + }, + CoinPriceLTV { + denom: "coin_2".to_string(), + price: Decimal::from_atomics(2u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(2u128, 2).unwrap(), + }, + CoinPriceLTV { + denom: "coin_3".to_string(), + price: Decimal::from_atomics(3u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(3u128, 2).unwrap(), + }, + CoinPriceLTV { + denom: "coin_4".to_string(), + price: Decimal::from_atomics(4u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(4u128, 2).unwrap(), + }, + CoinPriceLTV { + denom: "coin_5".to_string(), + price: Decimal::from_atomics(5u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(5u128, 2).unwrap(), + }, + CoinPriceLTV { + denom: "coin_6".to_string(), + price: Decimal::from_atomics(6u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(6u128, 2).unwrap(), + }, + CoinPriceLTV { + denom: "coin_7".to_string(), + price: Decimal::from_atomics(7u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 2).unwrap(), + }, + CoinPriceLTV { + denom: "coin_8".to_string(), + price: Decimal::from_atomics(8u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(8u128, 2).unwrap(), + }, + CoinPriceLTV { + denom: "coin_9".to_string(), + price: Decimal::from_atomics(9u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(9u128, 2).unwrap(), + }, + CoinPriceLTV { + denom: "coin_10".to_string(), + price: Decimal::from_atomics(10u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(10u128, 2).unwrap(), + }, + CoinPriceLTV { + denom: "coin_11".to_string(), + price: Decimal::from_atomics(11u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(11u128, 2).unwrap(), + }, + CoinPriceLTV { + denom: "coin_12".to_string(), + price: Decimal::from_atomics(12u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(12u128, 2).unwrap(), + }, + CoinPriceLTV { + denom: "coin_13".to_string(), + price: Decimal::from_atomics(13u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(13u128, 2).unwrap(), + }, + CoinPriceLTV { + denom: "coin_14".to_string(), + price: Decimal::from_atomics(14u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(14u128, 2).unwrap(), + }, ], vec![], ); diff --git a/contracts/credit-manager/tests/enumerate_debt_shares_test.rs b/contracts/credit-manager/tests/enumerate_debt_shares_test.rs index ae85bea0c..f3d92d3fe 100644 --- a/contracts/credit-manager/tests/enumerate_debt_shares_test.rs +++ b/contracts/credit-manager/tests/enumerate_debt_shares_test.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; use cw_multi_test::{App, Executor}; @@ -7,8 +7,8 @@ use rover::msg::query::SharesResponseItem; use rover::msg::{ExecuteMsg, QueryMsg}; use crate::helpers::{ - fund_red_bank_native, get_token_id, mock_create_credit_account, query_config, - setup_credit_manager, + fund_red_bank, get_token_id, mock_create_credit_account, query_config, setup_credit_manager, + CoinPriceLTV, }; pub mod helpers; @@ -79,45 +79,173 @@ fn test_pagination_on_all_debt_shares_query_works() { &mut app, &Addr::unchecked("owner"), vec![ - "coin_1".to_string(), - "coin_2".to_string(), - "coin_3".to_string(), - "coin_4".to_string(), - "coin_5".to_string(), - "coin_6".to_string(), - "coin_7".to_string(), - "coin_8".to_string(), - "coin_9".to_string(), - "coin_10".to_string(), - "coin_11".to_string(), - "coin_12".to_string(), - "coin_13".to_string(), - "coin_14".to_string(), - "coin_15".to_string(), - "coin_16".to_string(), - "coin_17".to_string(), - "coin_18".to_string(), - "coin_19".to_string(), - "coin_20".to_string(), - "coin_21".to_string(), - "coin_22".to_string(), - "coin_23".to_string(), - "coin_24".to_string(), - "coin_25".to_string(), - "coin_26".to_string(), - "coin_27".to_string(), - "coin_28".to_string(), - "coin_29".to_string(), - "coin_30".to_string(), - "coin_31".to_string(), - "coin_32".to_string(), + CoinPriceLTV { + denom: "coin_1".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_2".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_3".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_4".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_5".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_6".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_7".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_8".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_9".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_10".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_11".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_12".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_13".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_14".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_15".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_16".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_17".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_18".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_19".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_20".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_21".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_22".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_23".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_24".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_25".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_26".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_27".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_28".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_29".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_30".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_31".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_32".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, ], vec![], ); let config = query_config(&mut app, &mock.credit_manager.clone()); - fund_red_bank_native( + fund_red_bank( &mut app, config.red_bank.clone(), vec![ diff --git a/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs b/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs index 8239ea2a4..295cea17f 100644 --- a/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs +++ b/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; use cw_multi_test::{App, Executor}; @@ -7,8 +7,8 @@ use rover::msg::query::CoinShares; use rover::msg::{ExecuteMsg, QueryMsg}; use crate::helpers::{ - fund_red_bank_native, get_token_id, mock_create_credit_account, query_config, - setup_credit_manager, + fund_red_bank, get_token_id, mock_create_credit_account, query_config, setup_credit_manager, + CoinPriceLTV, }; pub mod helpers; @@ -79,45 +79,173 @@ fn test_pagination_on_all_total_debt_shares_query_works() { &mut app, &Addr::unchecked("owner"), vec![ - "coin_1".to_string(), - "coin_2".to_string(), - "coin_3".to_string(), - "coin_4".to_string(), - "coin_5".to_string(), - "coin_6".to_string(), - "coin_7".to_string(), - "coin_8".to_string(), - "coin_9".to_string(), - "coin_10".to_string(), - "coin_11".to_string(), - "coin_12".to_string(), - "coin_13".to_string(), - "coin_14".to_string(), - "coin_15".to_string(), - "coin_16".to_string(), - "coin_17".to_string(), - "coin_18".to_string(), - "coin_19".to_string(), - "coin_20".to_string(), - "coin_21".to_string(), - "coin_22".to_string(), - "coin_23".to_string(), - "coin_24".to_string(), - "coin_25".to_string(), - "coin_26".to_string(), - "coin_27".to_string(), - "coin_28".to_string(), - "coin_29".to_string(), - "coin_30".to_string(), - "coin_31".to_string(), - "coin_32".to_string(), + CoinPriceLTV { + denom: "coin_1".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_2".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_3".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_4".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_5".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_6".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_7".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_8".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_9".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_10".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_11".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_12".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_13".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_14".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_15".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_16".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_17".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_18".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_19".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_20".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_21".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_22".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_23".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_24".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_25".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_26".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_27".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_28".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_29".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_30".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_31".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, + CoinPriceLTV { + denom: "coin_32".to_string(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }, ], vec![], ); let config = query_config(&mut app, &mock.credit_manager.clone()); - fund_red_bank_native( + fund_red_bank( &mut app, config.red_bank.clone(), vec![ diff --git a/contracts/credit-manager/tests/health_test.rs b/contracts/credit-manager/tests/health_test.rs new file mode 100644 index 000000000..b0822ade2 --- /dev/null +++ b/contracts/credit-manager/tests/health_test.rs @@ -0,0 +1,576 @@ +extern crate core; + +use std::ops::{Add, Div, Mul}; + +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use cw_multi_test::{App, BasicApp, Executor}; + +use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; +use mock_oracle::msg::{CoinPrice, ExecuteMsg as OracleExecuteMsg}; +use mock_red_bank::msg::QueryMsg::UserAssetDebt; +use mock_red_bank::msg::UserAssetDebtResponse; +use rover::error::ContractError::AccountUnhealthy; +use rover::msg::execute::Action::{Borrow, Deposit}; +use rover::msg::ExecuteMsg; + +use crate::helpers::{ + assert_err, fund_red_bank, get_token_id, mock_app, mock_create_credit_account, query_config, + query_position, setup_credit_manager, CoinPriceLTV, MockEnv, +}; + +pub mod helpers; + +#[test] +fn test_only_assets_with_no_debts() { + let user = Addr::unchecked("user"); + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user, vec![Coin::new(300u128, "uosmo")]) + .unwrap(); + }); + + let coin_info = CoinPriceLTV { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + }; + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin_info.clone()], + vec![], + ); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); + let token_id = get_token_id(res); + + let deposit_amount = Uint128::from(234u128); + + app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Deposit(coin_info.to_coin(deposit_amount))], + }, + &[Coin::new(deposit_amount.into(), "uosmo")], + ) + .unwrap(); + + let res = query_position(&app, &mock.credit_manager, &token_id); + assert_eq!(res.token_id, token_id); + assert_eq!(res.assets.len(), 1); + assert_eq!(res.debt_shares.len(), 0); + + let assets_value = coin_info.price * Decimal::from_atomics(deposit_amount, 0).unwrap(); + assert_eq!(res.assets_value, assets_value); + assert_eq!( + res.ltv_adjusted_assets_value, + assets_value * coin_info.max_ltv + ); + + assert_eq!(res.debts_value, Decimal::zero()); + assert_eq!(res.health_factor, None); + assert_eq!(res.healthy, true); +} + +#[test] +fn test_terra_ragnarok() { + // Assets drop in value to zero with zero debt value but debt shares outstanding + let user = Addr::unchecked("user"); + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user, vec![Coin::new(300u128, "uluna")]) + .unwrap(); + }); + + let coin_info = CoinPriceLTV { + denom: "uluna".to_string(), + price: Decimal::from_atomics(100u128, 0).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + }; + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin_info.clone()], + vec![], + ); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); + let token_id = get_token_id(res); + + let config = query_config(&mut app, &mock.credit_manager.clone()); + + fund_red_bank( + &mut app, + config.red_bank.clone(), + vec![Coin::new(1000u128, "uluna")], + ); + + let deposit_amount = Uint128::from(234u128); + + app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![ + Deposit(coin_info.to_coin(deposit_amount)), + Borrow(coin_info.to_coin(Uint128::from(42u128))), + ], + }, + &[Coin::new(deposit_amount.into(), "uluna")], + ) + .unwrap(); + + let res = query_position(&app, &mock.credit_manager, &token_id); + assert_eq!(res.healthy, true); + + price_change( + &mut app, + &mock, + CoinPrice { + denom: coin_info.denom, + price: Decimal::zero(), + }, + ); + + let res = query_position(&app, &mock.credit_manager, &token_id); + assert_eq!(res.assets.len(), 1); + assert!(res.debt_shares.len() > 0); + assert_eq!(res.assets_value, Decimal::zero()); + assert_eq!(res.ltv_adjusted_assets_value, Decimal::zero()); + assert_eq!(res.debts_value, Decimal::zero()); + assert_eq!(res.health_factor, None); + assert_eq!(res.healthy, false); +} + +#[test] +fn test_debts_no_assets() { + let user = Addr::unchecked("user"); + let mut app = mock_app(); + + let coin_info = CoinPriceLTV { + denom: "uosmo".to_string(), + price: Decimal::one(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + }; + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin_info.clone()], + vec![], + ); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); + let token_id = get_token_id(res); + + let config = query_config(&mut app, &mock.credit_manager.clone()); + + fund_red_bank( + &mut app, + config.red_bank.clone(), + vec![Coin::new(1000u128, coin_info.denom.clone())], + ); + + let borrowed_amount = Uint128::from(100u128); + let res = app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(coin_info.to_coin(borrowed_amount))], + }, + &[], + ); + + let borrowed_amount_dec = Decimal::from_atomics(borrowed_amount, 0).unwrap(); + let value_of_debt = coin_info.price * borrowed_amount_dec + Decimal::one(); // Simulated interest from mock_red_bank == 1 + let ltv_adjusted_value_of_assets = coin_info.price * borrowed_amount_dec * coin_info.max_ltv; + assert_err( + res, + AccountUnhealthy { + health_factor: ltv_adjusted_value_of_assets.div(value_of_debt).to_string(), + }, + ); + + let res = query_position(&app, &mock.credit_manager, &token_id); + assert_eq!(res.token_id, token_id); + assert_eq!(res.assets.len(), 0); + assert_eq!(res.debt_shares.len(), 0); + assert_eq!(res.assets_value, Decimal::zero()); + assert_eq!(res.ltv_adjusted_assets_value, Decimal::zero()); + assert_eq!(res.debts_value, Decimal::zero()); + assert_eq!(res.health_factor, None); + assert_eq!(res.healthy, true); +} + +#[test] +fn no_assets_no_debt_value_but_shares_outstanding() { + let user = Addr::unchecked("user"); + let mut app = mock_app(); + + let coin_info = CoinPriceLTV { + denom: "junkcoin".to_string(), + price: Decimal::zero(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + }; + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin_info.clone()], + vec![], + ); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); + let token_id = get_token_id(res); + + let config = query_config(&mut app, &mock.credit_manager.clone()); + + fund_red_bank( + &mut app, + config.red_bank.clone(), + vec![Coin::new(1000u128, coin_info.denom.clone())], + ); + + let borrowed_amount = Uint128::from(100u128); + let res = app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(coin_info.to_coin(borrowed_amount))], + }, + &[], + ); + + assert_err( + res, + AccountUnhealthy { + health_factor: "n/a".to_string(), + }, + ); + + let res = query_position(&app, &mock.credit_manager, &token_id); + assert_eq!(res.token_id, token_id); + assert_eq!(res.assets.len(), 0); + assert_eq!(res.debt_shares.len(), 0); + assert_eq!(res.assets_value, Decimal::zero()); + assert_eq!(res.ltv_adjusted_assets_value, Decimal::zero()); + assert_eq!(res.debts_value, Decimal::zero()); + assert_eq!(res.health_factor, None); + assert_eq!(res.healthy, true); +} + +#[test] +fn test_assets_and_ltv_adjusted_value() { + let user = Addr::unchecked("user"); + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user, vec![Coin::new(300u128, "uosmo")]) + .unwrap(); + }); + + let uosmo_info = CoinPriceLTV { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), + max_ltv: Decimal::from_atomics(3u128, 1).unwrap(), + }; + + let uatom_info = CoinPriceLTV { + denom: "uatom".to_string(), + price: Decimal::from_atomics(7012302005u128, 3).unwrap(), + max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), + }; + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![uosmo_info.clone(), uatom_info.clone()], + vec![], + ); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); + let token_id = get_token_id(res); + + let config = query_config(&mut app, &mock.credit_manager.clone()); + + fund_red_bank( + &mut app, + config.red_bank.clone(), + vec![Coin::new(1000u128, "uatom")], + ); + + let deposit_amount = Uint128::from(298u128); + let borrowed_amount = Uint128::from(49u128); + + app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![ + Deposit(uosmo_info.to_coin(deposit_amount)), + Borrow(uatom_info.to_coin(deposit_amount)), + ], + }, + &[Coin::new(deposit_amount.into(), "uosmo")], + ) + .unwrap(); + + let res = query_position(&app, &mock.credit_manager, &token_id); + assert_eq!(res.token_id, token_id); + assert_eq!(res.assets.len(), 2); + + let borrowed_amount_dec = Decimal::from_atomics(borrowed_amount, 0).unwrap(); + let deposit_amount_dec = Decimal::from_atomics(deposit_amount, 0).unwrap(); + assert_eq!( + res.assets_value, + uosmo_info.price * deposit_amount_dec + uatom_info.price * borrowed_amount_dec + ); + let ltv_adjusted_assets_value = uosmo_info.price * deposit_amount_dec * uosmo_info.max_ltv + + uatom_info.price * borrowed_amount_dec * uatom_info.max_ltv; + assert_eq!(res.ltv_adjusted_assets_value, ltv_adjusted_assets_value); + + assert_eq!( + res.health_factor.unwrap(), + ltv_adjusted_assets_value.div(uatom_info.price.mul(borrowed_amount_dec + Decimal::one())) + ); + assert_eq!(res.healthy, true); +} + +#[test] +fn test_debt_value() { + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user_a, vec![Coin::new(300u128, "uosmo")]) + .unwrap(); + router + .bank + .init_balance(storage, &user_b, vec![Coin::new(140u128, "uosmo")]) + .unwrap(); + }); + + let uosmo_info = CoinPriceLTV { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), + max_ltv: Decimal::from_atomics(3u128, 1).unwrap(), + }; + + let uatom_info = CoinPriceLTV { + denom: "uatom".to_string(), + price: Decimal::from_atomics(7012302005u128, 3).unwrap(), + max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), + }; + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![uosmo_info.clone(), uatom_info.clone()], + vec![], + ); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); + let token_id_a = get_token_id(res); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_b).unwrap(); + let token_id_b = get_token_id(res); + + let config = query_config(&mut app, &mock.credit_manager.clone()); + + fund_red_bank( + &mut app, + config.red_bank.clone(), + vec![Coin::new(1000u128, "uatom"), Coin::new(1000u128, "uosmo")], + ); + + let user_a_deposit_amount_osmo = Uint128::from(298u128); + let user_a_borrowed_amount_atom = Uint128::from(49u128); + let user_a_borrowed_amount_osmo = Uint128::from(30u128); + + app.execute_contract( + user_a.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id_a.clone(), + actions: vec![ + Borrow(uatom_info.to_coin(user_a_borrowed_amount_atom)), + Borrow(uosmo_info.to_coin(user_a_borrowed_amount_osmo)), + Deposit(uosmo_info.to_coin(user_a_deposit_amount_osmo)), + ], + }, + &[Coin::new(user_a_deposit_amount_osmo.into(), "uosmo")], + ) + .unwrap(); + + let interim_red_bank_debt: UserAssetDebtResponse = app + .wrap() + .query_wasm_smart( + config.red_bank.clone(), + &UserAssetDebt { + user_address: mock.credit_manager.clone().into(), + denom: uatom_info.denom.clone(), + }, + ) + .unwrap(); + + let user_b_deposit_amount = Uint128::from(101u128); + let user_b_borrowed_amount_atom = Uint128::from(24u128); + + app.execute_contract( + user_b.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id_b.clone(), + actions: vec![ + Borrow(uatom_info.to_coin(user_b_borrowed_amount_atom)), + Deposit(uosmo_info.to_coin(user_b_deposit_amount)), + ], + }, + &[Coin::new(user_b_deposit_amount.into(), "uosmo")], + ) + .unwrap(); + + let res = query_position(&app, &mock.credit_manager, &token_id_a); + assert_eq!(res.token_id, token_id_a); + assert_eq!(res.assets.len(), 2); + + let user_a_borrowed_amount_atom_dec = + Decimal::from_atomics(user_a_borrowed_amount_atom, 0).unwrap(); + let user_b_borrowed_amount_atom_dec = + Decimal::from_atomics(user_b_borrowed_amount_atom, 0).unwrap(); + + let interest = Decimal::one() + Decimal::one(); // simulated from mock_oracle + let total_debt_shares_value = uatom_info.price + * (user_a_borrowed_amount_atom_dec + user_b_borrowed_amount_atom_dec + interest); + + let user_a_debt_shares_atom = + user_a_borrowed_amount_atom.mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED); + + let user_b_debt_shares_atom = user_a_debt_shares_atom + .multiply_ratio(user_b_borrowed_amount_atom, interim_red_bank_debt.amount); + + let debt_shares_ownership_ratio = Decimal::checked_from_ratio( + user_a_debt_shares_atom, + user_a_debt_shares_atom + user_b_debt_shares_atom, + ) + .unwrap(); + + let atom_debt_value = total_debt_shares_value.mul(debt_shares_ownership_ratio); + + let user_a_borrowed_amount_osmo_dec = + Decimal::from_atomics(user_a_borrowed_amount_osmo, 0).unwrap(); + let osmo_debt_value = uosmo_info.price * (user_a_borrowed_amount_osmo_dec + Decimal::one()); + + let total_debt_value = atom_debt_value.add(osmo_debt_value); + assert_eq!(res.debts_value, total_debt_value); + + let user_a_deposit_amount_osmo_dec = + Decimal::from_atomics(user_a_deposit_amount_osmo, 0).unwrap(); + let ltv_adjusted_assets_value = + (uosmo_info.price * user_a_deposit_amount_osmo_dec * uosmo_info.max_ltv) + + (uatom_info.price * user_a_borrowed_amount_atom_dec * uatom_info.max_ltv) + + (uosmo_info.price * user_a_borrowed_amount_osmo_dec * uosmo_info.max_ltv); + assert_eq!(res.ltv_adjusted_assets_value, ltv_adjusted_assets_value); + + assert_eq!( + res.health_factor.unwrap(), + ltv_adjusted_assets_value.div(total_debt_value) + ); + assert_eq!(res.healthy, true); +} + +#[test] +fn test_cannot_borrow_more_than_healthy() { + let user = Addr::unchecked("user"); + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user, vec![Coin::new(300u128, "uosmo")]) + .unwrap(); + }); + + let coin_info = CoinPriceLTV { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(23654u128, 4).unwrap(), + max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), + }; + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin_info.clone()], + vec![], + ); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); + let token_id = get_token_id(res); + + let config = query_config(&mut app, &mock.credit_manager.clone()); + + fund_red_bank( + &mut app, + config.red_bank.clone(), + vec![Coin::new(1000u128, "uatom"), Coin::new(1000u128, "uosmo")], + ); + + app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![ + Deposit(coin_info.to_coin(Uint128::from(300u128))), + Borrow(coin_info.to_coin(Uint128::from(50u128))), + ], + }, + &[Coin::new(Uint128::from(300u128).into(), "uosmo")], + ) + .unwrap(); + + app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(coin_info.to_coin(Uint128::from(100u128)))], + }, + &[], + ) + .unwrap(); + + let res = app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(coin_info.to_coin(Uint128::from(100u128)))], + }, + &[], + ); + + assert_err( + res, + AccountUnhealthy { + health_factor: "1.086956521739130434".to_string(), + }, + ); +} + +fn price_change(app: &mut BasicApp, mock: &MockEnv, coin: CoinPrice) -> () { + app.execute_contract( + Addr::unchecked("anyone"), + mock.oracle.clone(), + &OracleExecuteMsg::ChangePrice(coin), + &[], + ) + .unwrap(); +} diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index ee869b2e8..4e5423426 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -5,6 +5,9 @@ use account_nft::contract::{ execute as cw721Execute, instantiate as cw721Instantiate, query as cw721Query, }; use credit_manager::contract::{execute, instantiate, query}; +use mock_oracle::contract::{ + execute as oracleExecute, instantiate as oracleInstantiate, query as oracleQuery, +}; use mock_red_bank::contract::{ execute as redBankExecute, instantiate as redBankInstantiate, query as redBankQuery, }; @@ -27,3 +30,8 @@ pub fn mock_red_bank_contract() -> Box> { let contract = ContractWrapper::new(redBankExecute, redBankInstantiate, redBankQuery); Box::new(contract) } + +pub fn mock_oracle_contract() -> Box> { + let contract = ContractWrapper::new(oracleExecute, oracleInstantiate, oracleQuery); + Box::new(contract) +} diff --git a/contracts/credit-manager/tests/helpers/deploys.rs b/contracts/credit-manager/tests/helpers/deploys.rs index 0c11aea41..fc89713d7 100644 --- a/contracts/credit-manager/tests/helpers/deploys.rs +++ b/contracts/credit-manager/tests/helpers/deploys.rs @@ -1,16 +1,19 @@ use anyhow::Result as AnyResult; -use cosmwasm_std::{Addr, Coin, Empty}; +use cosmwasm_std::{Addr, Coin}; use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use rover::adapters::RedBankBase; +use mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; +use mock_red_bank::msg::{DenomWithLTV, InstantiateMsg as RedBankInstantiateMsg}; +use rover::adapters::{OracleBase, RedBankBase}; use rover::msg::execute::ExecuteMsg; use rover::msg::instantiate::ConfigUpdates; use rover::msg::InstantiateMsg; use crate::helpers::contracts::{mock_account_nft_contract, mock_contract, mock_red_bank_contract}; use crate::helpers::types::MockEnv; +use crate::helpers::{mock_oracle_contract, CoinPriceLTV}; pub fn mock_create_credit_account( app: &mut App, @@ -43,10 +46,7 @@ pub fn transfer_nft_contract_ownership( &ExecuteMsg::UpdateConfig { new_config: ConfigUpdates { account_nft: Some(nft_contract_addr.to_string()), - owner: None, - allowed_vaults: None, - allowed_coins: None, - red_bank: None, + ..Default::default() }, }, &[], @@ -54,19 +54,6 @@ pub fn transfer_nft_contract_ownership( .unwrap(); } -pub fn setup_red_bank(app: &mut App) -> Addr { - let contract_code_id = app.store_code(mock_red_bank_contract()); - app.instantiate_contract( - contract_code_id, - Addr::unchecked("red_bank_contract_owner"), - &Empty {}, - &[], - "mock-red-bank", - None, - ) - .unwrap() -} - pub fn setup_nft_contract(app: &mut App, owner: &Addr, manager_contract_addr: &Addr) -> Addr { let nft_contract_code_id = app.store_code(mock_account_nft_contract()); let nft_contract_addr = app @@ -88,19 +75,74 @@ pub fn setup_nft_contract(app: &mut App, owner: &Addr, manager_contract_addr: &A nft_contract_addr } +pub fn setup_oracle(app: &mut App, coins: &Vec) -> Addr { + let contract_code_id = app.store_code(mock_oracle_contract()); + app.instantiate_contract( + contract_code_id, + Addr::unchecked("oracle_contract_owner"), + &OracleInstantiateMsg { + coins: coins + .iter() + .map(|item| CoinPrice { + denom: item.denom.to_string(), + price: item.price, + }) + .collect(), + }, + &[], + "mock-oracle", + None, + ) + .unwrap() +} + +pub fn setup_red_bank(app: &mut App, coins: &Vec) -> Addr { + let contract_code_id = app.store_code(mock_red_bank_contract()); + app.instantiate_contract( + contract_code_id, + Addr::unchecked("red_bank_contract_owner"), + &RedBankInstantiateMsg { + coins: coins + .iter() + .map(|item| DenomWithLTV { + denom: item.denom.to_string(), + max_ltv: item.max_ltv, + }) + .collect(), + }, + &[], + "mock-red-bank", + None, + ) + .unwrap() +} + +pub fn fund_red_bank(app: &mut BasicApp, red_bank_addr: String, funds: Vec) { + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: red_bank_addr, + amount: funds, + })) + .unwrap(); +} + pub fn setup_credit_manager( mut app: &mut App, owner: &Addr, - allowed_assets: Vec, + allowed_coins: Vec, allowed_vaults: Vec, ) -> MockEnv { let credit_manager_code_id = app.store_code(mock_contract()); - let red_bank = setup_red_bank(app); + let red_bank = setup_red_bank(app, &allowed_coins); + let oracle = setup_oracle(app, &allowed_coins); let manager_initiate_msg = InstantiateMsg { owner: owner.to_string(), + allowed_coins: allowed_coins + .iter() + .map(|item| item.denom.clone()) + .collect(), allowed_vaults, red_bank: RedBankBase(red_bank.to_string()), - allowed_coins: allowed_assets, + oracle: OracleBase(oracle.to_string()), }; let credit_manager = app @@ -117,15 +159,8 @@ pub fn setup_credit_manager( let nft = setup_nft_contract(&mut app, &owner, &credit_manager); MockEnv { credit_manager, + oracle, red_bank, nft, } } - -pub fn fund_red_bank_native(app: &mut BasicApp, red_bank_addr: String, funds: Vec) { - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: red_bank_addr, - amount: funds, - })) - .unwrap(); -} diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index dde7f3c57..afd47662d 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -1,10 +1,27 @@ -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] pub struct MockEnv { pub credit_manager: Addr, + pub oracle: Addr, pub red_bank: Addr, pub nft: Addr, } + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct CoinPriceLTV { + pub denom: String, + pub price: Decimal, + pub max_ltv: Decimal, +} + +impl CoinPriceLTV { + pub fn to_coin(&self, amount: Uint128) -> Coin { + Coin { + denom: self.denom.clone(), + amount, + } + } +} diff --git a/contracts/credit-manager/tests/instantiate_test.rs b/contracts/credit-manager/tests/instantiate_test.rs index 1baf85467..e046a8593 100644 --- a/contracts/credit-manager/tests/instantiate_test.rs +++ b/contracts/credit-manager/tests/instantiate_test.rs @@ -1,7 +1,7 @@ use cosmwasm_std::Addr; use cw_multi_test::Executor; -use rover::adapters::RedBankBase; +use rover::adapters::{OracleBase, RedBankBase}; use rover::msg::query::{ConfigResponse, QueryMsg}; use rover::msg::InstantiateMsg; @@ -20,6 +20,7 @@ fn test_owner_set_on_instantiate() { allowed_vaults: vec![], allowed_coins: vec![], red_bank: RedBankBase("redbankaddr".to_string()), + oracle: OracleBase("oracle_contract".to_string()), }; let contract_addr = app @@ -45,6 +46,7 @@ fn test_raises_on_invalid_owner_addr() { allowed_vaults: vec![], allowed_coins: vec![], red_bank: RedBankBase("redbankaddr".to_string()), + oracle: OracleBase("oracle_contract".to_string()), }; let instantiate_res = app.instantiate_contract( @@ -76,6 +78,7 @@ fn test_nft_contract_addr_not_set_on_instantiate() { allowed_vaults: vec![], allowed_coins: vec![], red_bank: RedBankBase("redbankaddr".to_string()), + oracle: OracleBase("oracle_contract".to_string()), }, &[], "manager-mock-account-nft", @@ -108,6 +111,7 @@ fn test_allowed_vaults_set_on_instantiate() { allowed_vaults: allowed_vaults.clone(), allowed_coins: vec![], red_bank: RedBankBase("redbankaddr".to_string()), + oracle: OracleBase("oracle_contract".to_string()), }; let contract_addr = app @@ -146,6 +150,7 @@ fn test_raises_on_invalid_vaults_addr() { allowed_vaults: vec!["%%%INVALID%%%".to_string()], allowed_coins: vec![], red_bank: RedBankBase("redbankaddr".to_string()), + oracle: OracleBase("oracle_contract".to_string()), }; let instantiate_res = app.instantiate_contract( @@ -180,6 +185,7 @@ fn test_allowed_coins_set_on_instantiate() { allowed_vaults: vec![], allowed_coins: allowed_coins.clone(), red_bank: RedBankBase("redbankaddr".to_string()), + oracle: OracleBase("oracle_contract".to_string()), }; let contract_addr = app @@ -204,8 +210,7 @@ fn test_allowed_coins_set_on_instantiate() { ) .unwrap(); - assert_eq!(coins_res.len(), 4); - assert!(allowed_coins.iter().all(|item| coins_res.contains(item))); + assert_contents_equal(coins_res, allowed_coins) } #[test] @@ -220,6 +225,7 @@ fn test_red_bank_set_on_instantiate() { allowed_vaults: vec![], allowed_coins: vec![], red_bank: RedBankBase("redbankaddr".to_string()), + oracle: OracleBase("oracle_contract".to_string()), }; let contract_addr = app @@ -242,9 +248,65 @@ fn test_raises_on_invalid_red_bank_addr() { let msg = InstantiateMsg { owner: owner.to_string(), - allowed_vaults: vec![], allowed_coins: vec![], + allowed_vaults: vec![], red_bank: RedBankBase("%%%INVALID%%%".to_string()), + oracle: OracleBase("oracle_contract".to_string()), + }; + + let instantiate_res = app.instantiate_contract( + manager_code_id, + owner.clone(), + &msg, + &[], + "mock-contract", + None, + ); + + if instantiate_res.is_ok() { + panic!("Should have thrown an error"); + } +} + +#[test] +fn test_oracle_set_on_instantiate() { + let mut app = mock_app(); + let code_id = app.store_code(mock_contract()); + let owner = Addr::unchecked("owner"); + let oracle_contract = "oracle_contract".to_string(); + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_coins: vec![], + allowed_vaults: vec![], + red_bank: RedBankBase("redbankaddr".to_string()), + oracle: OracleBase("oracle_contract".to_string()), + }; + + let contract_addr = app + .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-account-nft", None) + .unwrap(); + + let res: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) + .unwrap(); + + assert_eq!(oracle_contract, res.oracle); +} + +#[test] +fn test_raises_on_invalid_oracle_addr() { + let mut app = mock_app(); + let manager_code_id = app.store_code(mock_contract()); + let owner = Addr::unchecked("owner"); + + let msg = InstantiateMsg { + owner: owner.to_string(), + allowed_vaults: vec![], + allowed_coins: vec![], + red_bank: RedBankBase("redbankaddr".to_string()), + oracle: OracleBase("%%%INVALID%%%".to_string()), }; let instantiate_res = app.instantiate_contract( diff --git a/contracts/credit-manager/tests/update_config_test.rs b/contracts/credit-manager/tests/update_config_test.rs index 60bb21213..69d0a2701 100644 --- a/contracts/credit-manager/tests/update_config_test.rs +++ b/contracts/credit-manager/tests/update_config_test.rs @@ -3,7 +3,7 @@ use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, Executor}; use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use rover::adapters::RedBankBase; +use rover::adapters::{OracleBase, RedBankBase}; use rover::msg::instantiate::ConfigUpdates; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -27,6 +27,7 @@ fn test_update_config_works_with_full_config() { let new_red_bank = RedBankBase("new_red_bank".to_string()); let new_allowed_vaults = vec!["vaultcontract1".to_string()]; let new_allowed_assets = vec!["uosmo".to_string()]; + let new_oracle = OracleBase("new_oracle".to_string()); app.execute_contract( original_owner.clone(), @@ -38,6 +39,7 @@ fn test_update_config_works_with_full_config() { allowed_vaults: Some(new_allowed_vaults.clone()), allowed_coins: Some(new_allowed_assets.clone()), red_bank: Some(new_red_bank.clone()), + oracle: Some(new_oracle.clone()), }, }, &[], @@ -62,6 +64,9 @@ fn test_update_config_works_with_full_config() { assert_eq!(new_config.red_bank, new_red_bank.0); assert_ne!(new_config.red_bank, original_config.red_bank); + + assert_eq!(new_config.oracle, new_oracle.0); + assert_ne!(new_config.oracle, original_config.oracle); } #[test] @@ -84,10 +89,8 @@ fn test_update_config_works_with_some_config() { &ExecuteMsg::UpdateConfig { new_config: ConfigUpdates { account_nft: Some(nft_contract_addr.to_string()), - owner: None, allowed_vaults: Some(new_allowed_vaults.clone()), - allowed_coins: None, - red_bank: None, + ..Default::default() }, }, &[], @@ -126,13 +129,7 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { original_owner.clone(), contract_addr.clone(), &ExecuteMsg::UpdateConfig { - new_config: ConfigUpdates { - account_nft: None, - owner: None, - allowed_vaults: None, - allowed_coins: None, - red_bank: None, - }, + new_config: Default::default(), }, &[], ) @@ -158,6 +155,7 @@ fn instantiate(app: &mut App, original_owner: &Addr, code_id: u64) -> Addr { allowed_vaults: vec![], allowed_coins: vec![], red_bank: RedBankBase("initial_red_bank".to_string()), + oracle: OracleBase("initial_oracle".to_string()), }, &[], "mock_manager_contract", diff --git a/contracts/mock-oracle/Cargo.toml b/contracts/mock-oracle/Cargo.toml new file mode 100644 index 000000000..d4386a483 --- /dev/null +++ b/contracts/mock-oracle/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "mock-oracle" +version = "1.0.0" +authors = ["larry_0x ", "grod220 "] +edition = "2018" +license = "GPL-3.0-or-later" +repository = "https://github.com/mars-protocol/rover" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +cosmwasm-std = "1.0" +cw-asset = "2.1" +cw-storage-plus = "0.13" +mars-core = "1.0" +schemars = "0.8" +serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/contracts/mock-oracle/src/contract.rs b/contracts/mock-oracle/src/contract.rs new file mode 100644 index 000000000..bd50d4925 --- /dev/null +++ b/contracts/mock-oracle/src/contract.rs @@ -0,0 +1,54 @@ +use cosmwasm_std::{ + to_binary, to_vec, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Response, StdError, + StdResult, +}; + +use crate::msg::{CoinPrice, ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::state::ASSET_PRICE; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + for item in msg.coins { + ASSET_PRICE.save(deps.storage, item.denom, &item.price)? + } + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + match msg { + ExecuteMsg::ChangePrice(item) => change_price(deps, item), + } +} + +fn change_price(deps: DepsMut, coin: CoinPrice) -> StdResult { + ASSET_PRICE.save(deps.storage, coin.denom, &coin.price)?; + Ok(Response::new()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::AssetPrice { denom } => to_binary(&query_asset_price(deps, denom)?), + _ => Err(StdError::generic_err(format!( + "[mock] unimplemented query: {}", + String::from_utf8(to_vec(&msg)?)? + ))), + } +} + +// TODO: After mars-core bumps to the next version https://crates.io/crates/mars-core (currently 1.0.0) +// should update this mock to return MarsDecimal: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/math/decimal.rs +fn query_asset_price(deps: Deps, denom: String) -> StdResult { + ASSET_PRICE.load(deps.storage, denom) +} diff --git a/contracts/mock-oracle/src/lib.rs b/contracts/mock-oracle/src/lib.rs new file mode 100644 index 000000000..4934c19d5 --- /dev/null +++ b/contracts/mock-oracle/src/lib.rs @@ -0,0 +1,3 @@ +pub mod contract; +pub mod msg; +pub mod state; diff --git a/contracts/mock-oracle/src/msg.rs b/contracts/mock-oracle/src/msg.rs new file mode 100644 index 000000000..5f00818c4 --- /dev/null +++ b/contracts/mock-oracle/src/msg.rs @@ -0,0 +1,30 @@ +use cosmwasm_std::Decimal; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct CoinPrice { + pub denom: String, + pub price: Decimal, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct InstantiateMsg { + pub coins: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + // Meant to simulate price changes for tests. Not available in prod. + ChangePrice(CoinPrice), +} + +// mocked from: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/oracle.rs#L155 +// cw-asset needs to be implemented before we can import +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + Config {}, + AssetPrice { denom: String }, +} diff --git a/contracts/mock-oracle/src/state.rs b/contracts/mock-oracle/src/state.rs new file mode 100644 index 000000000..f723992ea --- /dev/null +++ b/contracts/mock-oracle/src/state.rs @@ -0,0 +1,4 @@ +use cosmwasm_std::Decimal; +use cw_storage_plus::Map; + +pub const ASSET_PRICE: Map = Map::new("asset_price"); diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index 1547e7e92..4546706c3 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -4,6 +4,7 @@ version = "1.0.0" authors = ["larry_0x , grod220 "] edition = "2018" license = "GPL-3.0-or-later" +repository = "https://github.com/mars-protocol/rover" [lib] crate-type = ["cdylib", "rlib"] diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index 674f5acf4..8c3abffdf 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -1,21 +1,23 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, -}; +use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use crate::execute::execute_borrow; -use crate::msg::{ExecuteMsg, QueryMsg}; -use crate::query::query_debt; +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::query::{query_debt, query_market}; +use crate::state::ASSET_LTV; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - _deps: DepsMut, + deps: DepsMut, _env: Env, _info: MessageInfo, - _msg: Empty, + msg: InstantiateMsg, ) -> StdResult { - Ok(Response::default()) // do nothing + for item in msg.coins { + ASSET_LTV.save(deps.storage, item.denom, &item.max_ltv)? + } + Ok(Response::default()) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -40,5 +42,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { user_address, denom, } => to_binary(&query_debt(deps, user_address, denom)?), + QueryMsg::Market { denom } => to_binary(&query_market(deps, denom)?), } } diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs index 36fbf4c5d..5f3ada6a8 100644 --- a/contracts/mock-red-bank/src/msg.rs +++ b/contracts/mock-red-bank/src/msg.rs @@ -1,7 +1,18 @@ -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::{Coin, Decimal, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct InstantiateMsg { + pub coins: Vec, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct DenomWithLTV { + pub denom: String, + pub max_ltv: Decimal, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { @@ -15,6 +26,7 @@ pub enum ExecuteMsg { #[serde(rename_all = "snake_case")] pub enum QueryMsg { UserAssetDebt { user_address: String, denom: String }, + Market { denom: String }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -22,3 +34,11 @@ pub struct UserAssetDebtResponse { pub denom: String, pub amount: Uint128, } + +// Schema reference: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/red_bank/mod.rs#L47 +// TODO: After mars-core bumps to the next version https://crates.io/crates/mars-core (currently 1.0.0) +// should update this mock to return MarsDecimal: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/math/decimal.rs +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Market { + pub max_loan_to_value: Decimal, +} diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index 30bd4bbec..14f13f5a2 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -1,7 +1,8 @@ use cosmwasm_std::{Deps, StdResult}; use crate::helpers::load_debt_amount; -use crate::msg::UserAssetDebtResponse; +use crate::msg::{Market, UserAssetDebtResponse}; +use crate::state::ASSET_LTV; pub fn query_debt( deps: Deps, @@ -12,3 +13,8 @@ pub fn query_debt( let amount = load_debt_amount(deps.storage, &user_addr, &denom)?; Ok(UserAssetDebtResponse { denom, amount }) } + +pub fn query_market(deps: Deps, denom: String) -> StdResult { + let max_loan_to_value = ASSET_LTV.load(deps.storage, denom)?; + Ok(Market { max_loan_to_value }) +} diff --git a/contracts/mock-red-bank/src/state.rs b/contracts/mock-red-bank/src/state.rs index 1aec53e91..54bccce62 100644 --- a/contracts/mock-red-bank/src/state.rs +++ b/contracts/mock-red-bank/src/state.rs @@ -1,5 +1,7 @@ -use cosmwasm_std::{Addr, Uint128}; +use cosmwasm_std::{Addr, Decimal, Uint128}; use cw_storage_plus::Map; -// Map<(DebtHolder, AssetDenom), AmountOfDebt> +// Map<(DebtHolder, CoinDenom), AmountOfDebt> pub const DEBT_AMOUNT: Map<(Addr, String), Uint128> = Map::new("debt_amount"); +// Map +pub const ASSET_LTV: Map = Map::new("asset_ltv"); diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 6aeb5dd24..e2a97537f 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -3,12 +3,15 @@ name = "rover" version = "0.1.0" authors = ["larry_0x ", "Gabe R. "] edition = "2018" +license = "GPL-3.0-or-later" +repository = "https://github.com/mars-protocol/rover" [lib] doctest = false [dependencies] mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } +mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } cosmwasm-std = "1.0" cw-storage-plus = "0.14" diff --git a/packages/rover/src/adapters/mod.rs b/packages/rover/src/adapters/mod.rs index 2201ea23e..bce05757a 100644 --- a/packages/rover/src/adapters/mod.rs +++ b/packages/rover/src/adapters/mod.rs @@ -1,3 +1,5 @@ +mod oracle; mod red_bank; +pub use self::oracle::*; pub use self::red_bank::*; diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs new file mode 100644 index 000000000..2947f44c6 --- /dev/null +++ b/packages/rover/src/adapters/oracle.rs @@ -0,0 +1,37 @@ +use cosmwasm_std::{ + to_binary, Addr, Api, Decimal, QuerierWrapper, QueryRequest, StdResult, WasmQuery, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use mock_oracle::msg::QueryMsg; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct OracleBase(pub T); + +pub type OracleUnchecked = OracleBase; +pub type Oracle = OracleBase; + +impl From for OracleUnchecked { + fn from(oracle: Oracle) -> Self { + Self(oracle.0.to_string()) + } +} + +impl OracleUnchecked { + pub fn check(&self, api: &dyn Api) -> StdResult { + Ok(OracleBase(api.addr_validate(&self.0)?)) + } +} + +impl Oracle { + pub fn query_price(&self, querier: &QuerierWrapper, denom: &str) -> StdResult { + let response: Decimal = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.0.to_string(), + msg: to_binary(&QueryMsg::AssetPrice { + denom: denom.to_string(), + })?, + }))?; + Ok(response) + } +} diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index ab2d7161a..7ff990ef1 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -38,7 +38,7 @@ impl RedBank { })) } - pub fn query_user_debt( + pub fn query_debt( &self, querier: &QuerierWrapper, user_address: &Addr, diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 7f15c9ad8..c40c7a15d 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -1,37 +1,54 @@ -use cosmwasm_std::{CheckedMultiplyRatioError, StdError, Uint128}; +use cosmwasm_std::{ + CheckedFromRatioError, CheckedMultiplyRatioError, DecimalRangeExceeded, OverflowError, + StdError, Uint128, +}; use thiserror::Error; use crate::coins::Coins; +pub type ContractResult = Result; + #[derive(Error, Debug, PartialEq)] pub enum ContractError { + #[error("Health factor {health_factor:?} less than 1")] + AccountUnhealthy { health_factor: String }, + #[error("{0}")] - Std(#[from] StdError), + CheckedFromRatioError(#[from] CheckedFromRatioError), #[error("{0}")] CheckedMultiply(#[from] CheckedMultiplyRatioError), - #[error("{user:?} is not authorized to {action:?}")] - Unauthorized { user: String, action: String }, + #[error("{0}")] + DecimalRangeExceeded(#[from] DecimalRangeExceeded), - #[error("{0} is not whitelisted")] - NotWhitelisted(String), + #[error("Callbacks cannot be invoked externally")] + ExternalInvocation, #[error("Extra funds received: {0}")] ExtraFundsReceived(Coins), - #[error("No coin amount set for action")] - NoAmount, - #[error("Sent fund mismatch. Expected: {expected:?}, received {received:?}")] FundsMismatch { expected: Uint128, received: Uint128, }, - #[error("Callbacks cannot be invoked externally")] - ExternalInvocation, + #[error("No coin amount set for action")] + NoAmount, #[error("{user:?} is not the owner of {token_id:?}")] NotTokenOwner { user: String, token_id: String }, + + #[error("{0} is not whitelisted")] + NotWhitelisted(String), + + #[error("{0}")] + Overflow(#[from] OverflowError), + + #[error("{0}")] + Std(#[from] StdError), + + #[error("{user:?} is not authorized to {action:?}")] + Unauthorized { user: String, action: String }, } diff --git a/packages/rover/src/health.rs b/packages/rover/src/health.rs new file mode 100644 index 000000000..26bc2f4df --- /dev/null +++ b/packages/rover/src/health.rs @@ -0,0 +1,12 @@ +use cosmwasm_std::Decimal; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Health { + pub assets_value: Decimal, + pub ltv_adjusted_assets_value: Decimal, + pub debts_value: Decimal, + pub health_factor: Option, + pub healthy: bool, +} diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index 87312d1f7..c456bf5b0 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -5,6 +5,7 @@ use crate::error::ContractError; pub mod adapters; pub mod coins; pub mod error; +pub mod health; pub mod msg; pub type ContractResult = Result; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index a6580d2d2..6d5e98879 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -45,12 +45,15 @@ pub enum CallbackMsg { /// Borrow specified amount of coin from Red Bank; /// Increase the token's asset amount and debt shares; Borrow { token_id: String, coin: Coin }, + /// Calculate a token's current LTV. If below the maximum LTV, emits a `position_updated` + /// event; if above the maximum LTV, throw an error + AssertHealth { token_id: String }, } impl CallbackMsg { pub fn into_cosmos_msg(&self, contract_addr: &Addr) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: String::from(contract_addr), + contract_addr: contract_addr.to_string(), msg: to_binary(&ExecuteMsg::Callback(self.clone()))?, funds: vec![], })) diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index cbe1e11ae..a340f1e68 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -1,22 +1,29 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::adapters::RedBankUnchecked; +use crate::adapters::{OracleUnchecked, RedBankUnchecked}; #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] pub struct InstantiateMsg { + /// The address with privileged access to update config pub owner: String, - pub allowed_vaults: Vec, + /// Whitelisted coin denoms approved by governance pub allowed_coins: Vec, + /// Whitelisted vaults approved by governance that implement credit manager's vault interface + pub allowed_vaults: Vec, + /// The Mars Protocol money market contract where we borrow assets from pub red_bank: RedBankUnchecked, + /// The Mars Protocol oracle contract. We read prices of assets here. + pub oracle: OracleUnchecked, } /// Used when you want to update fields on Instantiate config -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)] pub struct ConfigUpdates { pub account_nft: Option, pub owner: Option, pub allowed_coins: Option>, pub allowed_vaults: Option>, pub red_bank: Option, + pub oracle: Option, } diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index a79b9c371..fd98ef4a0 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::{Decimal, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; pub enum QueryMsg { /// Owner & account nft address. Response type: `ConfigResponse` Config, - /// Whitelisted vaults. Response type: `Vec` + /// Whitelisted vaults. Response type: `Vec>` AllowedVaults { start_after: Option, limit: Option, @@ -64,12 +64,35 @@ pub struct CoinShares { pub shares: Uint128, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct CoinValue { + pub value: Decimal, + pub price_per_unit: Decimal, + pub denom: String, + pub amount: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct CoinSharesValue { + pub value: Decimal, + pub price_per_unit: Decimal, + pub denom: String, + pub shares: Uint128, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct PositionResponse { pub token_id: String, - pub assets: Vec, - pub debt_shares: Vec, + pub assets: Vec, + pub debt_shares: Vec, + pub assets_value: Decimal, + pub ltv_adjusted_assets_value: Decimal, + pub debts_value: Decimal, + pub health_factor: Option, + pub healthy: bool, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -78,4 +101,5 @@ pub struct ConfigResponse { pub owner: String, pub account_nft: Option, pub red_bank: String, + pub oracle: String, } From 2a69ec28d4f609bbbf149c6495fce9a488050dda Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 5 Aug 2022 19:02:16 +0200 Subject: [PATCH 044/218] Adding liquidation threshold support --- contracts/credit-manager/src/contract.rs | 2 + contracts/credit-manager/src/execute.rs | 6 +- contracts/credit-manager/src/health.rs | 131 +++-- contracts/credit-manager/src/query.rs | 56 +- contracts/credit-manager/tests/borrow_test.rs | 27 +- .../credit-manager/tests/deposit_test.rs | 55 +- .../credit-manager/tests/dispatch_test.rs | 4 +- .../tests/enumerate_assets_test.rs | 104 ++-- .../tests/enumerate_debt_shares_test.rs | 98 ++-- .../tests/enumerate_total_debt_shares_test.rs | 98 ++-- contracts/credit-manager/tests/health_test.rs | 533 ++++++++++++------ .../credit-manager/tests/helpers/deploys.rs | 13 +- .../credit-manager/tests/helpers/queries.rs | 12 + .../credit-manager/tests/helpers/types.rs | 5 +- contracts/mock-oracle/src/contract.rs | 8 +- contracts/mock-oracle/src/msg.rs | 3 +- contracts/mock-oracle/src/state.rs | 2 +- contracts/mock-red-bank/src/contract.rs | 4 +- contracts/mock-red-bank/src/msg.rs | 6 +- contracts/mock-red-bank/src/query.rs | 9 +- contracts/mock-red-bank/src/state.rs | 8 +- packages/rover/src/error.rs | 6 +- packages/rover/src/health.rs | 16 +- packages/rover/src/msg/execute.rs | 6 +- packages/rover/src/msg/query.rs | 25 +- 25 files changed, 774 insertions(+), 463 deletions(-) diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 6f7b3f289..3eae738e7 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -5,6 +5,7 @@ use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use rover::ContractResult; use crate::execute::{create_credit_account, dispatch_actions, execute_callback, update_config}; +use crate::health::compute_health; use crate::instantiate::store_config; use crate::query::{ query_all_assets, query_all_debt_shares, query_all_total_debt_shares, query_allowed_coins, @@ -54,6 +55,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { to_binary(&query_allowed_coins(deps, start_after, limit)?) } QueryMsg::Position { token_id } => to_binary(&query_position(deps, &env, &token_id)?), + QueryMsg::Health { token_id } => to_binary(&compute_health(deps, &env, &token_id)?), QueryMsg::AllAssets { start_after, limit } => { to_binary(&query_all_assets(deps, start_after, limit)?) } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index fe84b5980..0342c0a31 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -13,7 +13,7 @@ use rover::ContractResult; use crate::borrow::borrow; use crate::deposit::deposit; -use crate::health::assert_health; +use crate::health::assert_below_max_ltv; use crate::state::{ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, ORACLE, OWNER, RED_BANK}; pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult { @@ -142,7 +142,7 @@ pub fn dispatch_actions( } // after user selected actions, we assert LTV is healthy; if not, throw error and revert all actions - callbacks.extend([CallbackMsg::AssertHealth { + callbacks.extend([CallbackMsg::AssertBelowMaxLTV { token_id: token_id.to_string(), }]); @@ -167,7 +167,7 @@ pub fn execute_callback( } match callback { CallbackMsg::Borrow { coin, token_id } => borrow(deps, env, &token_id, coin), - CallbackMsg::AssertHealth { token_id } => assert_health(deps, env, &token_id), + CallbackMsg::AssertBelowMaxLTV { token_id } => assert_below_max_ltv(deps, env, &token_id), } } diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index f590e12b4..cc5c8ee63 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -1,85 +1,82 @@ use std::ops::{Add, Div, Mul}; -use cosmwasm_std::{Decimal, Deps, DepsMut, Env, Event, Response, StdResult, Uint128}; +use cosmwasm_std::{Decimal, Deps, DepsMut, Env, Event, Response, StdResult}; use mock_red_bank::msg::{Market, QueryMsg}; use rover::error::{ContractError, ContractResult}; use rover::health::Health; -use rover::msg::query::{CoinSharesValue, CoinValue}; use rover::NftTokenId; use crate::query::query_position; use crate::state::RED_BANK; /// Compute the health of a token's position -pub fn compute_health( - deps: &Deps, - assets: &[CoinValue], - debts: &[CoinSharesValue], -) -> ContractResult { - // The sum of the position's assets weighted by max LTV - let (ltv_adjusted_assets_value, assets_value) = assets.iter().try_fold::<_, _, StdResult<_>>( - (Decimal::zero(), Decimal::zero()), - |(ltv_adjusted_total, base_total), item| { - let red_bank = RED_BANK.load(deps.storage)?; - let market: Market = deps.querier.query_wasm_smart( - red_bank.0, - &QueryMsg::Market { - denom: item.denom.clone(), - }, - )?; - Ok(( - ltv_adjusted_total.add(item.value.mul(market.max_loan_to_value)), - base_total.add(item.value), - )) - }, - )?; +/// max_tvl = maximum loan to value +/// lqdt = liquidation threshold +pub fn compute_health(deps: Deps, env: &Env, token_id: NftTokenId) -> ContractResult { + let position = query_position(deps, env, token_id)?; + let red_bank = RED_BANK.load(deps.storage)?; - let (debts_shares, debts_value) = debts.iter().fold( - (Uint128::zero(), Decimal::zero()), - |(total_shares, total_value), item| { - (total_shares.add(item.shares), total_value.add(item.value)) - }, - ); + // The sum of the position's coin asset values w/ loan-to-value adjusted & liquidation threshold adjusted + let (total_assets_value, max_ltv_adjusted_value, lqdt_adjusted_value) = + position.coin_assets.iter().try_fold::<_, _, StdResult<_>>( + (Decimal::zero(), Decimal::zero(), Decimal::zero()), + |(total, max_ltv_adjusted_total, lqdt_adjusted_total), item| { + let market: Market = deps.querier.query_wasm_smart( + red_bank.0.clone(), + &QueryMsg::Market { + denom: item.denom.clone(), + }, + )?; + Ok(( + total.add(item.total_value), + max_ltv_adjusted_total.add(item.total_value.mul(market.max_loan_to_value)), + lqdt_adjusted_total.add(item.total_value.mul(market.liquidation_threshold)), + )) + }, + )?; - // Health Factor = Sum(Value of Asset * Max LTV) / Sum (Value of Total Borrowed) + // The sum of the position's debt share values + let total_debts_value = position + .debt_shares + .iter() + .fold(Decimal::zero(), |total_value, item| { + total_value.add(item.total_value) + }); + + // Health Factor = Sum(Value of Asset * Liquidation Threshold or Max LTV) / Sum (Value of Total Borrowed) // If there aren't any debts a health factor can't be computed (divide by zero) - let health_factor = if debts_value.is_zero() { - None + let (lqdt_health_factor, max_ltv_health_factor) = if total_debts_value.is_zero() { + (None, None) } else { - Some(ltv_adjusted_assets_value.div(debts_value)) + ( + Some(lqdt_adjusted_value.div(total_debts_value)), + Some(max_ltv_adjusted_value.div(total_debts_value)), + ) }; - // If Some(health_factor), we assert it is no less than 1 - // If it is None, meaning `debts_value` is zero, we assert debt shares are also zero - // - // NOTE: We assert debt shares are zero, instead of `debts_value`. - // This is because value can be zero as a result of rounding down. - let healthy = if let Some(hf) = health_factor { - hf > Decimal::one() - } else { - debts_shares.is_zero() - }; + let liquidatable = lqdt_health_factor.map_or_else(|| false, |hf| hf <= Decimal::one()); + let above_max_ltv = max_ltv_health_factor.map_or_else(|| false, |hf| hf <= Decimal::one()); Ok(Health { - assets_value, - ltv_adjusted_assets_value, - debts_value, - health_factor, - healthy, + assets_value: total_assets_value, + debts_value: total_debts_value, + lqdt_health_factor, + liquidatable, + max_ltv_health_factor, + above_max_ltv, }) } -pub fn assert_health(deps: DepsMut, env: Env, token_id: NftTokenId) -> ContractResult { - let position = query_position(deps.as_ref(), &env, token_id)?; - let hf_str = position - .health_factor - .map_or("n/a".to_string(), |dec| dec.to_string()); +pub fn assert_below_max_ltv( + deps: DepsMut, + env: Env, + token_id: NftTokenId, +) -> ContractResult { + let position = compute_health(deps.as_ref(), &env, token_id)?; - if !position.healthy { - return Err(ContractError::AccountUnhealthy { - health_factor: hf_str, - }); + if position.above_max_ltv { + return Err(ContractError::AboveMaxLTV); } let event = Event::new("position_changed") @@ -87,11 +84,23 @@ pub fn assert_health(deps: DepsMut, env: Env, token_id: NftTokenId) -> ContractR .add_attribute("height", env.block.height.to_string()) .add_attribute("token_id", token_id) .add_attribute("assets_value", position.assets_value.to_string()) - .add_attribute("debt_value", position.debts_value.to_string()) - .add_attribute("health_factor", hf_str) - .add_attribute("healthy", position.healthy.to_string()); + .add_attribute("debts_value", position.debts_value.to_string()) + .add_attribute( + "lqdt_health_factor", + val_or_not_applicable(position.lqdt_health_factor), + ) + .add_attribute("liquidatable", position.liquidatable.to_string()) + .add_attribute( + "max_ltv_health_factor", + val_or_not_applicable(position.max_ltv_health_factor), + ) + .add_attribute("above_max_ltv", position.above_max_ltv.to_string()); Ok(Response::new() .add_attribute("action", "rover/credit_manager/callback/assert_health") .add_event(event)) } + +fn val_or_not_applicable(opt: Option) -> String { + opt.map_or_else(|| "n/a".to_string(), |dec| dec.to_string()) +} diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index f52dc2be8..d12e3f2a7 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -2,12 +2,11 @@ use cosmwasm_std::{Decimal, Deps, Env, Order, StdResult, Uint128}; use cw_storage_plus::Bound; use rover::msg::query::{ - AssetResponseItem, CoinShares, CoinSharesValue, CoinValue, ConfigResponse, PositionResponse, + AssetResponseItem, CoinShares, CoinValue, ConfigResponse, DebtSharesValue, PositionResponse, SharesResponseItem, }; -use rover::{ContractResult, Denom, NftTokenId, Shares}; +use rover::{ContractResult, Denom, NftTokenId}; -use crate::health::compute_health; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, ASSETS, DEBT_SHARES, ORACLE, OWNER, RED_BANK, TOTAL_DEBT_SHARES, @@ -32,19 +31,13 @@ pub fn query_position( env: &Env, token_id: NftTokenId, ) -> ContractResult { - let assets = get_assets_value(deps, token_id)?; - let debt_shares = get_debts_value(deps, env, token_id)?; - let health = compute_health(&deps, &assets, &debt_shares)?; + let coin_asset_values = get_coin_asset_values(deps, token_id)?; + let debt_shares_values = get_debt_shares_values(deps, env, token_id)?; Ok(PositionResponse { token_id: token_id.to_string(), - assets, - debt_shares, - assets_value: health.assets_value, - ltv_adjusted_assets_value: health.ltv_adjusted_assets_value, - debts_value: health.debts_value, - health_factor: health.health_factor, - healthy: health.healthy, + coin_assets: coin_asset_values, + debt_shares: debt_shares_values, }) } @@ -71,16 +64,16 @@ pub fn query_all_assets( .collect()) } -fn get_debts_value( +fn get_debt_shares_values( deps: Deps, env: &Env, token_id: NftTokenId, -) -> ContractResult> { +) -> ContractResult> { let oracle = ORACLE.load(deps.storage)?; DEBT_SHARES .prefix(token_id) .range(deps.storage, None, None, Order::Ascending) - .collect::>>()? + .collect::>>()? .iter() .map(|(denom, shares)| { // proportion of debt this token represents @@ -90,22 +83,19 @@ fn get_debts_value( let token_share_ratio = Decimal::checked_from_ratio(*shares, total_debt_shares)?; // total rover debt for asset - let total_debt_amount = RED_BANK.load(deps.storage)?.query_debt( - &deps.querier, - &env.contract.address, - denom, - )?; - let total_debt_amount = Decimal::from_atomics(total_debt_amount, 0)?; + let red_bank = RED_BANK.load(deps.storage)?; + let total_debt_amount = + red_bank.query_debt(&deps.querier, &env.contract.address, denom)?; + let total_debt_amount_dec = Decimal::from_atomics(total_debt_amount, 0)?; // debt value of token's position - let price_per_unit = oracle.query_price(&deps.querier, denom)?; - let position_debt_value = price_per_unit - .checked_mul(total_debt_amount)? + let coin_price = oracle.query_price(&deps.querier, denom)?; + let position_debt_value = coin_price + .checked_mul(total_debt_amount_dec)? .checked_mul(token_share_ratio)?; - Ok(CoinSharesValue { - value: position_debt_value, - price_per_unit, + Ok(DebtSharesValue { + total_value: position_debt_value, denom: denom.clone(), shares: *shares, }) @@ -113,7 +103,7 @@ fn get_debts_value( .collect() } -fn get_assets_value(deps: Deps, token_id: &str) -> ContractResult> { +fn get_coin_asset_values(deps: Deps, token_id: &str) -> ContractResult> { let oracle = ORACLE.load(deps.storage)?; ASSETS .prefix(token_id) @@ -121,14 +111,14 @@ fn get_assets_value(deps: Deps, token_id: &str) -> ContractResult .collect::>>()? .iter() .map(|(denom, amount)| { - let price_per_unit = oracle.query_price(&deps.querier, denom)?; + let price = oracle.query_price(&deps.querier, denom)?; let decimal_amount = Decimal::from_atomics(*amount, 0)?; - let value = price_per_unit.checked_mul(decimal_amount)?; + let total_value = price.checked_mul(decimal_amount)?; Ok(CoinValue { - value, - price_per_unit, denom: denom.clone(), amount: *amount, + price, + total_value, }) }) .collect() diff --git a/contracts/credit-manager/tests/borrow_test.rs b/contracts/credit-manager/tests/borrow_test.rs index 0ac1c7755..1d5c807fb 100644 --- a/contracts/credit-manager/tests/borrow_test.rs +++ b/contracts/credit-manager/tests/borrow_test.rs @@ -14,7 +14,7 @@ use rover::msg::QueryMsg; use crate::helpers::{ assert_err, fund_red_bank, get_token_id, mock_app, mock_create_credit_account, query_config, - query_position, setup_credit_manager, CoinPriceLTV, + query_position, setup_credit_manager, CoinInfo, }; pub mod helpers; @@ -24,10 +24,11 @@ fn test_only_token_owner_can_borrow() { let mut app = mock_app(); let owner = Addr::unchecked("owner"); - let coin_info = CoinPriceLTV { + let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; let mock = setup_credit_manager(&mut app, &owner, vec![coin_info.clone()], vec![]); @@ -59,10 +60,11 @@ fn test_only_token_owner_can_borrow() { fn test_can_only_borrow_what_is_whitelisted() { let mut app = mock_app(); let owner = Addr::unchecked("owner"); - let coin_info = CoinPriceLTV { + let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; let mock = setup_credit_manager(&mut app, &owner, vec![coin_info], vec![]); @@ -92,10 +94,11 @@ fn test_can_only_borrow_what_is_whitelisted() { #[test] fn test_borrowing_zero_does_nothing() { let mut app = mock_app(); - let coin_info = CoinPriceLTV { + let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; let mock = setup_credit_manager( @@ -121,17 +124,18 @@ fn test_borrowing_zero_does_nothing() { assert_err(res, ContractError::NoAmount); let position = query_position(&mut app, &mock.credit_manager, &token_id); - assert_eq!(position.assets.len(), 0); + assert_eq!(position.coin_assets.len(), 0); assert_eq!(position.debt_shares.len(), 0); } #[test] fn test_success_when_new_debt_asset() { let user = Addr::unchecked("user"); - let coin_info = CoinPriceLTV { + let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; let mut app = App::new(|router, _, storage| { router @@ -162,7 +166,7 @@ fn test_success_when_new_debt_asset() { ); let position = query_position(&mut app, &mock.credit_manager, &token_id); - assert_eq!(position.assets.len(), 0); + assert_eq!(position.coin_assets.len(), 0); assert_eq!(position.debt_shares.len(), 0); app.execute_contract( user, @@ -185,12 +189,12 @@ fn test_success_when_new_debt_asset() { .unwrap(); let position = query_position(&mut app, &mock.credit_manager, &token_id); - assert_eq!(position.assets.len(), 1); + assert_eq!(position.coin_assets.len(), 1); assert_eq!( - position.assets.first().unwrap().amount, + position.coin_assets.first().unwrap().amount, Uint128::from(342u128) // Deposit + Borrow ); - assert_eq!(position.assets.first().unwrap().denom, coin_info.denom); + assert_eq!(position.coin_assets.first().unwrap().denom, coin_info.denom); assert_eq!(position.debt_shares.len(), 1); assert_eq!( position.debt_shares.first().unwrap().shares, @@ -230,10 +234,11 @@ fn test_success_when_new_debt_asset() { fn test_debt_shares_with_debt_amount() { let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); - let coin_info = CoinPriceLTV { + let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; let mut app = App::new(|router, _, storage| { router diff --git a/contracts/credit-manager/tests/deposit_test.rs b/contracts/credit-manager/tests/deposit_test.rs index 8c508d64d..5338a45e4 100644 --- a/contracts/credit-manager/tests/deposit_test.rs +++ b/contracts/credit-manager/tests/deposit_test.rs @@ -13,11 +13,13 @@ use rover::msg::ExecuteMsg; use crate::helpers::{ assert_err, get_token_id, mock_app, mock_create_credit_account, query_position, - setup_credit_manager, CoinPriceLTV, + setup_credit_manager, CoinInfo, }; pub mod helpers; +// TODO: Assert values + #[test] fn test_only_owner_of_token_can_deposit() { let mut app = mock_app(); @@ -55,10 +57,11 @@ fn test_only_owner_of_token_can_deposit() { #[test] fn test_deposit_nothing() { let mut app = mock_app(); - let coin_info = CoinPriceLTV { + let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; let mock = setup_credit_manager( &mut app, @@ -72,7 +75,7 @@ fn test_deposit_nothing() { let token_id = get_token_id(res); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.assets.len(), 0); + assert_eq!(res.coin_assets.len(), 0); app.execute_contract( user.clone(), @@ -86,16 +89,17 @@ fn test_deposit_nothing() { .unwrap(); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.assets.len(), 0); + assert_eq!(res.coin_assets.len(), 0); } #[test] fn test_deposit_but_no_funds() { let mut app = mock_app(); - let coin_info = CoinPriceLTV { + let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; let deposit_amount = Uint128::from(234u128); let mock = setup_credit_manager( @@ -128,16 +132,17 @@ fn test_deposit_but_no_funds() { ); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.assets.len(), 0); + assert_eq!(res.coin_assets.len(), 0); } #[test] fn test_deposit_but_not_enough_funds() { let user = Addr::unchecked("user"); - let coin_info = CoinPriceLTV { + let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; let mut app = App::new(|router, _, storage| { router @@ -182,10 +187,11 @@ fn test_deposit_but_not_enough_funds() { #[test] fn test_can_only_deposit_allowed_assets() { let user = Addr::unchecked("user"); - let coin_info = CoinPriceLTV { + let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; let mut app = App::new(|router, _, storage| { router @@ -225,21 +231,23 @@ fn test_can_only_deposit_allowed_assets() { assert_err(res, NotWhitelisted(not_allowed_coin.denom)); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.assets.len(), 0); + assert_eq!(res.coin_assets.len(), 0); } #[test] fn test_extra_funds_received() { let user = Addr::unchecked("user"); - let uosmo_info = CoinPriceLTV { + let uosmo_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; - let uatom_info = CoinPriceLTV { + let uatom_info = CoinInfo { denom: "uatom".to_string(), price: Decimal::from_atomics(10u128, 1).unwrap(), max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), + liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), }; let mut app = App::new(|router, _, storage| { router @@ -282,16 +290,17 @@ fn test_extra_funds_received() { assert_err(res, ExtraFundsReceived(Coins::from(vec![extra_funds]))); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.assets.len(), 0); + assert_eq!(res.coin_assets.len(), 0); } #[test] fn test_deposit_success() { let user = Addr::unchecked("user"); - let coin_info = CoinPriceLTV { + let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; let mut app = App::new(|router, _, storage| { @@ -328,10 +337,10 @@ fn test_deposit_success() { .unwrap(); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.assets.len(), 1); - assert_eq!(res.assets.first().unwrap().amount, deposit_amount); - assert_eq!(res.assets.first().unwrap().denom, coin_info.denom); - assert_eq!(res.assets.first().unwrap().price_per_unit, coin_info.price); + assert_eq!(res.coin_assets.len(), 1); + assert_eq!(res.coin_assets.first().unwrap().amount, deposit_amount); + assert_eq!(res.coin_assets.first().unwrap().denom, coin_info.denom); + assert_eq!(res.coin_assets.first().unwrap().price, coin_info.price); let coin = app .wrap() @@ -343,15 +352,17 @@ fn test_deposit_success() { #[test] fn test_multiple_deposit_actions() { let user = Addr::unchecked("user"); - let uosmo_info = CoinPriceLTV { + let uosmo_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; - let uatom_info = CoinPriceLTV { + let uatom_info = CoinInfo { denom: "uatom".to_string(), price: Decimal::from_atomics(10u128, 1).unwrap(), max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), + liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), }; let mut app = App::new(|router, _, storage| { router @@ -398,7 +409,7 @@ fn test_multiple_deposit_actions() { .unwrap(); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.assets.len(), 2); + assert_eq!(res.coin_assets.len(), 2); assert_present(&res, &uosmo_info, uosmo_amount); assert_present(&res, &uatom_info, uatom_amount); @@ -415,8 +426,8 @@ fn test_multiple_deposit_actions() { assert_eq!(coin.amount, uatom_amount); } -fn assert_present(res: &PositionResponse, coin: &CoinPriceLTV, amount: Uint128) { - res.assets +fn assert_present(res: &PositionResponse, coin: &CoinInfo, amount: Uint128) { + res.coin_assets .iter() .find(|item| item.denom == coin.denom && &item.amount == &amount) .unwrap(); diff --git a/contracts/credit-manager/tests/dispatch_test.rs b/contracts/credit-manager/tests/dispatch_test.rs index e12bf9112..c86442756 100644 --- a/contracts/credit-manager/tests/dispatch_test.rs +++ b/contracts/credit-manager/tests/dispatch_test.rs @@ -54,7 +54,7 @@ fn test_nothing_happens_if_no_actions_are_passed() { let token_id = get_token_id(res); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.assets.len(), 0); + assert_eq!(res.coin_assets.len(), 0); app.execute_contract( user.clone(), @@ -68,5 +68,5 @@ fn test_nothing_happens_if_no_actions_are_passed() { .unwrap(); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.assets.len(), 0); + assert_eq!(res.coin_assets.len(), 0); } diff --git a/contracts/credit-manager/tests/enumerate_assets_test.rs b/contracts/credit-manager/tests/enumerate_assets_test.rs index ad2aa6ce1..9c6b43841 100644 --- a/contracts/credit-manager/tests/enumerate_assets_test.rs +++ b/contracts/credit-manager/tests/enumerate_assets_test.rs @@ -5,14 +5,12 @@ use rover::msg::execute::Action; use rover::msg::query::AssetResponseItem; use rover::msg::{ExecuteMsg, QueryMsg}; -use crate::helpers::{ - get_token_id, mock_create_credit_account, setup_credit_manager, CoinPriceLTV, -}; +use crate::helpers::{get_token_id, mock_create_credit_account, setup_credit_manager, CoinInfo}; pub mod helpers; #[test] -fn test_pagination_on_all_assets_query_works() { +fn test_pagination_on_all_coin_assets_query_works() { let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); let user_c = Addr::unchecked("user_c"); @@ -77,75 +75,89 @@ fn test_pagination_on_all_assets_query_works() { &mut app, &Addr::unchecked("owner"), vec![ - CoinPriceLTV { + CoinInfo { denom: "coin_1".to_string(), - price: Decimal::from_atomics(1u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(1u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_2".to_string(), - price: Decimal::from_atomics(2u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(2u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_3".to_string(), - price: Decimal::from_atomics(3u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(3u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_4".to_string(), - price: Decimal::from_atomics(4u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(4u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_5".to_string(), - price: Decimal::from_atomics(5u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(5u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_6".to_string(), - price: Decimal::from_atomics(6u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(6u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_7".to_string(), - price: Decimal::from_atomics(7u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_8".to_string(), - price: Decimal::from_atomics(8u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(8u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_9".to_string(), - price: Decimal::from_atomics(9u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(9u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_10".to_string(), - price: Decimal::from_atomics(10u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(10u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_11".to_string(), - price: Decimal::from_atomics(11u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(11u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_12".to_string(), - price: Decimal::from_atomics(12u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(12u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_13".to_string(), - price: Decimal::from_atomics(13u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(13u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_14".to_string(), - price: Decimal::from_atomics(14u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(14u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), }, ], vec![], diff --git a/contracts/credit-manager/tests/enumerate_debt_shares_test.rs b/contracts/credit-manager/tests/enumerate_debt_shares_test.rs index f3d92d3fe..17300763c 100644 --- a/contracts/credit-manager/tests/enumerate_debt_shares_test.rs +++ b/contracts/credit-manager/tests/enumerate_debt_shares_test.rs @@ -8,7 +8,7 @@ use rover::msg::{ExecuteMsg, QueryMsg}; use crate::helpers::{ fund_red_bank, get_token_id, mock_create_credit_account, query_config, setup_credit_manager, - CoinPriceLTV, + CoinInfo, }; pub mod helpers; @@ -79,164 +79,196 @@ fn test_pagination_on_all_debt_shares_query_works() { &mut app, &Addr::unchecked("owner"), vec![ - CoinPriceLTV { + CoinInfo { denom: "coin_1".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_2".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_3".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_4".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_5".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_6".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_7".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_8".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_9".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_10".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_11".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_12".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_13".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_14".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_15".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_16".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_17".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_18".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_19".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_20".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_21".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_22".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_23".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_24".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_25".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_26".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_27".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_28".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_29".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_30".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_31".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_32".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, ], diff --git a/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs b/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs index 295cea17f..0d4ccecea 100644 --- a/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs +++ b/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs @@ -8,7 +8,7 @@ use rover::msg::{ExecuteMsg, QueryMsg}; use crate::helpers::{ fund_red_bank, get_token_id, mock_create_credit_account, query_config, setup_credit_manager, - CoinPriceLTV, + CoinInfo, }; pub mod helpers; @@ -79,164 +79,196 @@ fn test_pagination_on_all_total_debt_shares_query_works() { &mut app, &Addr::unchecked("owner"), vec![ - CoinPriceLTV { + CoinInfo { denom: "coin_1".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_2".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_3".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_4".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_5".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_6".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_7".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_8".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_9".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_10".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_11".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_12".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_13".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_14".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_15".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_16".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_17".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_18".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_19".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_20".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_21".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_22".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_23".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_24".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_25".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_26".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_27".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_28".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_29".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_30".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_31".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, - CoinPriceLTV { + CoinInfo { denom: "coin_32".to_string(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), }, ], diff --git a/contracts/credit-manager/tests/health_test.rs b/contracts/credit-manager/tests/health_test.rs index b0822ade2..e06a10024 100644 --- a/contracts/credit-manager/tests/health_test.rs +++ b/contracts/credit-manager/tests/health_test.rs @@ -9,17 +9,22 @@ use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; use mock_oracle::msg::{CoinPrice, ExecuteMsg as OracleExecuteMsg}; use mock_red_bank::msg::QueryMsg::UserAssetDebt; use mock_red_bank::msg::UserAssetDebtResponse; -use rover::error::ContractError::AccountUnhealthy; +use rover::error::ContractError; use rover::msg::execute::Action::{Borrow, Deposit}; use rover::msg::ExecuteMsg; use crate::helpers::{ assert_err, fund_red_bank, get_token_id, mock_app, mock_create_credit_account, query_config, - query_position, setup_credit_manager, CoinPriceLTV, MockEnv, + query_health, query_position, setup_credit_manager, CoinInfo, MockEnv, }; pub mod helpers; +/// Action: User deposits 300 osmo (.25 price) +/// Health: assets_value: 75 +/// debt value 0 +/// liquidatable: false +/// above_max_ltv: false #[test] fn test_only_assets_with_no_debts() { let user = Addr::unchecked("user"); @@ -30,10 +35,11 @@ fn test_only_assets_with_no_debts() { .unwrap(); }); - let coin_info = CoinPriceLTV { + let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; let mock = setup_credit_manager( @@ -46,7 +52,7 @@ fn test_only_assets_with_no_debts() { let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); - let deposit_amount = Uint128::from(234u128); + let deposit_amount = Uint128::from(300u128); app.execute_contract( user.clone(), @@ -59,26 +65,32 @@ fn test_only_assets_with_no_debts() { ) .unwrap(); - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.token_id, token_id); - assert_eq!(res.assets.len(), 1); - assert_eq!(res.debt_shares.len(), 0); + let position = query_position(&app, &mock.credit_manager, &token_id); + assert_eq!(position.coin_assets.len(), 1); + assert_eq!(position.debt_shares.len(), 0); + let health = query_health(&app, &mock.credit_manager, &token_id); let assets_value = coin_info.price * Decimal::from_atomics(deposit_amount, 0).unwrap(); - assert_eq!(res.assets_value, assets_value); - assert_eq!( - res.ltv_adjusted_assets_value, - assets_value * coin_info.max_ltv - ); - - assert_eq!(res.debts_value, Decimal::zero()); - assert_eq!(res.health_factor, None); - assert_eq!(res.healthy, true); + assert_eq!(health.assets_value, assets_value); + assert_eq!(health.debts_value, Decimal::zero()); + assert_eq!(health.lqdt_health_factor, None); + assert_eq!(health.max_ltv_health_factor, None); + assert_eq!(health.liquidatable, false); + assert_eq!(health.above_max_ltv, false); } +/// Step 1: User deposits 12 luna (100 price) and borrows 2 luna +/// Health: assets_value: 1400 +/// debt value 200 +/// liquidatable: false +/// above_max_ltv: false +/// Step 2: luna price goes to zero +/// Health: assets_value: 0 +/// debt value 0 (still debt shares outstanding) +/// liquidatable: false +/// above_max_ltv: false #[test] fn test_terra_ragnarok() { - // Assets drop in value to zero with zero debt value but debt shares outstanding let user = Addr::unchecked("user"); let mut app = App::new(|router, _, storage| { router @@ -87,10 +99,11 @@ fn test_terra_ragnarok() { .unwrap(); }); - let coin_info = CoinPriceLTV { + let coin_info = CoinInfo { denom: "uluna".to_string(), price: Decimal::from_atomics(100u128, 0).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; let mock = setup_credit_manager( @@ -111,8 +124,8 @@ fn test_terra_ragnarok() { vec![Coin::new(1000u128, "uluna")], ); - let deposit_amount = Uint128::from(234u128); - + let deposit_amount = Uint128::from(12u128); + let borrow_amount = Uint128::from(2u128); app.execute_contract( user.clone(), mock.credit_manager.clone(), @@ -120,15 +133,35 @@ fn test_terra_ragnarok() { token_id: token_id.clone(), actions: vec![ Deposit(coin_info.to_coin(deposit_amount)), - Borrow(coin_info.to_coin(Uint128::from(42u128))), + Borrow(coin_info.to_coin(borrow_amount)), ], }, &[Coin::new(deposit_amount.into(), "uluna")], ) .unwrap(); - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.healthy, true); + let position = query_position(&app, &mock.credit_manager, &token_id); + assert_eq!(position.coin_assets.len(), 1); + assert_eq!(position.debt_shares.len(), 1); + + let health = query_health(&app, &mock.credit_manager, &token_id); + let assets_value = + coin_info.price * Decimal::from_atomics(deposit_amount + borrow_amount, 0).unwrap(); + assert_eq!(health.assets_value, assets_value); + // Note: Simulated yield from mock_red_bank makes debt position more expensive + let debts_value = coin_info.price + * Decimal::from_atomics(borrow_amount.add(Uint128::from(1u128)), 0).unwrap(); + assert_eq!(health.debts_value, debts_value); + assert_eq!( + health.lqdt_health_factor, + Some(assets_value * coin_info.liquidation_threshold / debts_value) + ); + assert_eq!( + health.max_ltv_health_factor, + Some(assets_value * coin_info.max_ltv / debts_value) + ); + assert_eq!(health.liquidatable, false); + assert_eq!(health.above_max_ltv, false); price_change( &mut app, @@ -139,25 +172,34 @@ fn test_terra_ragnarok() { }, ); - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.assets.len(), 1); - assert!(res.debt_shares.len() > 0); - assert_eq!(res.assets_value, Decimal::zero()); - assert_eq!(res.ltv_adjusted_assets_value, Decimal::zero()); - assert_eq!(res.debts_value, Decimal::zero()); - assert_eq!(res.health_factor, None); - assert_eq!(res.healthy, false); + let position = query_position(&app, &mock.credit_manager, &token_id); + assert_eq!(position.coin_assets.len(), 1); + assert_eq!(position.debt_shares.len(), 1); + + let health = query_health(&app, &mock.credit_manager, &token_id); + assert_eq!(health.assets_value, Decimal::zero()); + assert_eq!(health.debts_value, Decimal::zero()); + assert_eq!(health.lqdt_health_factor, None); + assert_eq!(health.max_ltv_health_factor, None); + assert_eq!(health.liquidatable, false); + assert_eq!(health.above_max_ltv, false); } +/// Action: User borrows 100 osmo (at price of 1). Zero deposits. +/// Health: assets_value: 100 +/// debt value: 100 +/// liquidatable: true +/// above_max_ltv: true #[test] fn test_debts_no_assets() { let user = Addr::unchecked("user"); let mut app = mock_app(); - let coin_info = CoinPriceLTV { + let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::one(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; let mock = setup_credit_manager( @@ -188,44 +230,190 @@ fn test_debts_no_assets() { &[], ); - let borrowed_amount_dec = Decimal::from_atomics(borrowed_amount, 0).unwrap(); - let value_of_debt = coin_info.price * borrowed_amount_dec + Decimal::one(); // Simulated interest from mock_red_bank == 1 - let ltv_adjusted_value_of_assets = coin_info.price * borrowed_amount_dec * coin_info.max_ltv; - assert_err( - res, - AccountUnhealthy { - health_factor: ltv_adjusted_value_of_assets.div(value_of_debt).to_string(), + assert_err(res, ContractError::AboveMaxLTV); + + let position = query_position(&app, &mock.credit_manager, &token_id); + assert_eq!(position.token_id, token_id); + assert_eq!(position.coin_assets.len(), 0); + assert_eq!(position.debt_shares.len(), 0); + + let health = query_health(&app, &mock.credit_manager, &token_id); + assert_eq!(health.assets_value, Decimal::zero()); + assert_eq!(health.debts_value, Decimal::zero()); + assert_eq!(health.lqdt_health_factor, None); + assert_eq!(health.max_ltv_health_factor, None); + assert_eq!(health.liquidatable, false); + assert_eq!(health.above_max_ltv, false); +} + +/// Step 1: User deposits 300 osmo and borrows 50 (at price of 2.3654) +/// Health: assets_value: 827.89 +/// debt value: 121 (simulated interest incurred) +/// liquidatable: false +/// above_max_ltv: false +/// Step 2: User borrows 100 +/// Health: assets_value: 1,064.43 +/// debt value: 360 (simulated interest incurred) +/// liquidatable: false +/// above_max_ltv: false +/// Step 3: User borrows 100 +/// AboveMaxLtv error thrown +#[test] +fn test_cannot_borrow_more_than_healthy() { + let user = Addr::unchecked("user"); + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user, vec![Coin::new(300u128, "uosmo")]) + .unwrap(); + }); + + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(23654u128, 4).unwrap(), + max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), + }; + + let mock = setup_credit_manager( + &mut app, + &Addr::unchecked("owner"), + vec![coin_info.clone()], + vec![], + ); + + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); + let token_id = get_token_id(res); + + let config = query_config(&mut app, &mock.credit_manager.clone()); + + fund_red_bank( + &mut app, + config.red_bank.clone(), + vec![Coin::new(1000u128, coin_info.denom.clone())], + ); + + app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![ + Deposit(coin_info.to_coin(Uint128::from(300u128))), + Borrow(coin_info.to_coin(Uint128::from(50u128))), + ], }, + &[Coin::new( + Uint128::from(300u128).into(), + coin_info.denom.clone(), + )], + ) + .unwrap(); + + let position = query_position(&app, &mock.credit_manager, &token_id); + assert_eq!(position.token_id, token_id); + assert_eq!(position.coin_assets.len(), 1); + assert_eq!(position.debt_shares.len(), 1); + + let health = query_health(&app, &mock.credit_manager, &token_id); + let assets_value = Decimal::from_atomics(82789u128, 2).unwrap(); + assert_eq!(health.assets_value, assets_value); + let debts_value = Decimal::from_atomics(1206354u128, 4).unwrap(); + assert_eq!(health.debts_value, debts_value); + assert_eq!( + health.lqdt_health_factor, + Some(assets_value * coin_info.liquidation_threshold / debts_value) + ); + assert_eq!( + health.max_ltv_health_factor, + Some(assets_value * coin_info.max_ltv / debts_value) + ); + assert_eq!(health.liquidatable, false); + assert_eq!(health.above_max_ltv, false); + + app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(coin_info.to_coin(Uint128::from(100u128)))], + }, + &[], + ) + .unwrap(); + + let res = app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![Borrow(coin_info.to_coin(Uint128::from(150u128)))], + }, + &[], ); - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.token_id, token_id); - assert_eq!(res.assets.len(), 0); - assert_eq!(res.debt_shares.len(), 0); - assert_eq!(res.assets_value, Decimal::zero()); - assert_eq!(res.ltv_adjusted_assets_value, Decimal::zero()); - assert_eq!(res.debts_value, Decimal::zero()); - assert_eq!(res.health_factor, None); - assert_eq!(res.healthy, true); + assert_err(res, ContractError::AboveMaxLTV); + + // All valid on step 2 as well (meaning step 3 did not go through) + let health = query_health(&app, &mock.credit_manager, &token_id); + let assets_value = Decimal::from_atomics(106443u128, 2).unwrap(); + assert_eq!(health.assets_value, assets_value); + let debts_value = Decimal::from_atomics(3595408u128, 4).unwrap(); + assert_eq!(health.debts_value, debts_value); + assert_eq!( + health.lqdt_health_factor, + Some(assets_value * coin_info.liquidation_threshold / debts_value) + ); + assert_eq!( + health.max_ltv_health_factor, + Some(assets_value * coin_info.max_ltv / debts_value) + ); + assert_eq!(health.liquidatable, false); + assert_eq!(health.above_max_ltv, false); } +/// Step 1: User deposits 300 osmo (2.3654) and borrows 50 atom (price 10.2) +/// Health: liquidatable: false +/// above_max_ltv: false +/// Step 2: Atom's price increases to 24 +/// Health: liquidatable: false +/// above_max_ltv: true +/// Step 3: User borrows 2 atom +/// AboveMaxLtv error thrown +/// Step 4: Atom's price increases to 35 +/// Health: liquidatable: true +/// above_max_ltv: true #[test] -fn no_assets_no_debt_value_but_shares_outstanding() { +fn test_cannot_borrow_more_but_not_liquidatable() { let user = Addr::unchecked("user"); - let mut app = mock_app(); + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &user, vec![Coin::new(300u128, "uosmo")]) + .unwrap(); + }); - let coin_info = CoinPriceLTV { - denom: "junkcoin".to_string(), - price: Decimal::zero(), + let uosmo_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(23654u128, 4).unwrap(), + max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), + }; + + let uatom_info = CoinInfo { + denom: "uatom".to_string(), + price: Decimal::from_atomics(102u128, 1).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(75u128, 2).unwrap(), }; let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![coin_info.clone()], + vec![uosmo_info.clone(), uatom_info.clone()], vec![], ); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); @@ -234,40 +422,74 @@ fn no_assets_no_debt_value_but_shares_outstanding() { fund_red_bank( &mut app, config.red_bank.clone(), - vec![Coin::new(1000u128, coin_info.denom.clone())], + vec![Coin::new(1000u128, "uatom")], ); - let borrowed_amount = Uint128::from(100u128); + app.execute_contract( + user.clone(), + mock.credit_manager.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.clone(), + actions: vec![ + Deposit(uosmo_info.to_coin(Uint128::from(300u128))), + Borrow(uatom_info.to_coin(Uint128::from(50u128))), + ], + }, + &[Coin::new(300, "uosmo")], + ) + .unwrap(); + + let health = query_health(&app, &mock.credit_manager, &token_id); + assert_eq!(health.liquidatable, false); + assert_eq!(health.above_max_ltv, false); + + price_change( + &mut app, + &mock, + CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(24u128, 0).unwrap(), + }, + ); + + let health = query_health(&app, &mock.credit_manager, &token_id); + assert_eq!(health.liquidatable, false); + assert_eq!(health.above_max_ltv, true); + let res = app.execute_contract( user.clone(), mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Borrow(coin_info.to_coin(borrowed_amount))], + actions: vec![Borrow(uatom_info.to_coin(Uint128::from(2u128)))], }, &[], ); - assert_err( - res, - AccountUnhealthy { - health_factor: "n/a".to_string(), + assert_err(res, ContractError::AboveMaxLTV); + + price_change( + &mut app, + &mock, + CoinPrice { + denom: uatom_info.denom, + price: Decimal::from_atomics(35u128, 0).unwrap(), }, ); - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.token_id, token_id); - assert_eq!(res.assets.len(), 0); - assert_eq!(res.debt_shares.len(), 0); - assert_eq!(res.assets_value, Decimal::zero()); - assert_eq!(res.ltv_adjusted_assets_value, Decimal::zero()); - assert_eq!(res.debts_value, Decimal::zero()); - assert_eq!(res.health_factor, None); - assert_eq!(res.healthy, true); + let health = query_health(&app, &mock.credit_manager, &token_id); + assert_eq!(health.liquidatable, true); + assert_eq!(health.above_max_ltv, true); } +/// Actions: User deposits 300 osmo (5265478965.412365487125 price) +/// and borrows 49 atom ( price) +/// Health: assets_value: 1569456334491.12991516325 +/// debt value 350615100.25 +/// liquidatable: false +/// above_max_ltv: false #[test] -fn test_assets_and_ltv_adjusted_value() { +fn test_assets_and_ltv_lqdt_adjusted_value() { let user = Addr::unchecked("user"); let mut app = App::new(|router, _, storage| { router @@ -276,16 +498,18 @@ fn test_assets_and_ltv_adjusted_value() { .unwrap(); }); - let uosmo_info = CoinPriceLTV { + let uosmo_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), - max_ltv: Decimal::from_atomics(3u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), }; - let uatom_info = CoinPriceLTV { + let uatom_info = CoinInfo { denom: "uatom".to_string(), price: Decimal::from_atomics(7012302005u128, 3).unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), }; let mock = setup_credit_manager( @@ -316,34 +540,59 @@ fn test_assets_and_ltv_adjusted_value() { token_id: token_id.clone(), actions: vec![ Deposit(uosmo_info.to_coin(deposit_amount)), - Borrow(uatom_info.to_coin(deposit_amount)), + Borrow(uatom_info.to_coin(borrowed_amount)), ], }, &[Coin::new(deposit_amount.into(), "uosmo")], ) .unwrap(); - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.token_id, token_id); - assert_eq!(res.assets.len(), 2); + let position = query_position(&app, &mock.credit_manager, &token_id); + assert_eq!(position.token_id, token_id); + assert_eq!(position.coin_assets.len(), 2); + assert_eq!(position.debt_shares.len(), 1); - let borrowed_amount_dec = Decimal::from_atomics(borrowed_amount, 0).unwrap(); + let health = query_health(&app, &mock.credit_manager, &token_id); let deposit_amount_dec = Decimal::from_atomics(deposit_amount, 0).unwrap(); + let borrowed_amount_dec = Decimal::from_atomics(borrowed_amount, 0).unwrap(); assert_eq!( - res.assets_value, + health.assets_value, uosmo_info.price * deposit_amount_dec + uatom_info.price * borrowed_amount_dec ); + assert_eq!( + health.debts_value, + uatom_info.price * (borrowed_amount_dec + Decimal::one()) // simulated interest + ); + + let lqdt_adjusted_assets_value = + uosmo_info.price * deposit_amount_dec * uosmo_info.liquidation_threshold + + uatom_info.price * borrowed_amount_dec * uatom_info.liquidation_threshold; + assert_eq!( + health.lqdt_health_factor, + Some( + lqdt_adjusted_assets_value + .div(uatom_info.price.mul(borrowed_amount_dec + Decimal::one())) + ) + ); let ltv_adjusted_assets_value = uosmo_info.price * deposit_amount_dec * uosmo_info.max_ltv + uatom_info.price * borrowed_amount_dec * uatom_info.max_ltv; - assert_eq!(res.ltv_adjusted_assets_value, ltv_adjusted_assets_value); - assert_eq!( - res.health_factor.unwrap(), - ltv_adjusted_assets_value.div(uatom_info.price.mul(borrowed_amount_dec + Decimal::one())) + health.max_ltv_health_factor, + Some( + ltv_adjusted_assets_value + .div(uatom_info.price.mul(borrowed_amount_dec + Decimal::one())) + ) ); - assert_eq!(res.healthy, true); + assert_eq!(health.liquidatable, false); + assert_eq!(health.above_max_ltv, false); } +/// User A: Borrows 30 osmo +/// Borrows 49 atom +/// Deposits 298 osmo +/// User B: Borrows 24 atom +/// Deposits 101 osmo +/// Test validates User A's debt value & health factors #[test] fn test_debt_value() { let user_a = Addr::unchecked("user_a"); @@ -359,16 +608,18 @@ fn test_debt_value() { .unwrap(); }); - let uosmo_info = CoinPriceLTV { + let uosmo_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), max_ltv: Decimal::from_atomics(3u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), }; - let uatom_info = CoinPriceLTV { + let uatom_info = CoinInfo { denom: "uatom".to_string(), price: Decimal::from_atomics(7012302005u128, 3).unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), }; let mock = setup_credit_manager( @@ -439,9 +690,11 @@ fn test_debt_value() { ) .unwrap(); - let res = query_position(&app, &mock.credit_manager, &token_id_a); - assert_eq!(res.token_id, token_id_a); - assert_eq!(res.assets.len(), 2); + let position = query_position(&app, &mock.credit_manager, &token_id_a); + assert_eq!(position.token_id, token_id_a); + assert_eq!(position.coin_assets.len(), 2); + + let health = query_health(&app, &mock.credit_manager, &token_id_a); let user_a_borrowed_amount_atom_dec = Decimal::from_atomics(user_a_borrowed_amount_atom, 0).unwrap(); @@ -471,98 +724,32 @@ fn test_debt_value() { let osmo_debt_value = uosmo_info.price * (user_a_borrowed_amount_osmo_dec + Decimal::one()); let total_debt_value = atom_debt_value.add(osmo_debt_value); - assert_eq!(res.debts_value, total_debt_value); + assert_eq!(health.debts_value, total_debt_value); let user_a_deposit_amount_osmo_dec = Decimal::from_atomics(user_a_deposit_amount_osmo, 0).unwrap(); + + let lqdt_adjusted_assets_value = (uosmo_info.price + * user_a_deposit_amount_osmo_dec + * uosmo_info.liquidation_threshold) + + (uatom_info.price * user_a_borrowed_amount_atom_dec * uatom_info.liquidation_threshold) + + (uosmo_info.price * user_a_borrowed_amount_osmo_dec * uosmo_info.liquidation_threshold); + assert_eq!( + health.lqdt_health_factor, + Some(lqdt_adjusted_assets_value.div(total_debt_value)) + ); + let ltv_adjusted_assets_value = (uosmo_info.price * user_a_deposit_amount_osmo_dec * uosmo_info.max_ltv) + (uatom_info.price * user_a_borrowed_amount_atom_dec * uatom_info.max_ltv) + (uosmo_info.price * user_a_borrowed_amount_osmo_dec * uosmo_info.max_ltv); - assert_eq!(res.ltv_adjusted_assets_value, ltv_adjusted_assets_value); assert_eq!( - res.health_factor.unwrap(), - ltv_adjusted_assets_value.div(total_debt_value) - ); - assert_eq!(res.healthy, true); -} - -#[test] -fn test_cannot_borrow_more_than_healthy() { - let user = Addr::unchecked("user"); - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &user, vec![Coin::new(300u128, "uosmo")]) - .unwrap(); - }); - - let coin_info = CoinPriceLTV { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(23654u128, 4).unwrap(), - max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), - }; - - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin_info.clone()], - vec![], - ); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let config = query_config(&mut app, &mock.credit_manager.clone()); - - fund_red_bank( - &mut app, - config.red_bank.clone(), - vec![Coin::new(1000u128, "uatom"), Coin::new(1000u128, "uosmo")], - ); - - app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![ - Deposit(coin_info.to_coin(Uint128::from(300u128))), - Borrow(coin_info.to_coin(Uint128::from(50u128))), - ], - }, - &[Coin::new(Uint128::from(300u128).into(), "uosmo")], - ) - .unwrap(); - - app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Borrow(coin_info.to_coin(Uint128::from(100u128)))], - }, - &[], - ) - .unwrap(); - - let res = app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Borrow(coin_info.to_coin(Uint128::from(100u128)))], - }, - &[], - ); - - assert_err( - res, - AccountUnhealthy { - health_factor: "1.086956521739130434".to_string(), - }, + health.max_ltv_health_factor, + Some(ltv_adjusted_assets_value.div(total_debt_value)) ); + assert_eq!(health.above_max_ltv, false); + assert_eq!(health.liquidatable, false); } fn price_change(app: &mut BasicApp, mock: &MockEnv, coin: CoinPrice) -> () { diff --git a/contracts/credit-manager/tests/helpers/deploys.rs b/contracts/credit-manager/tests/helpers/deploys.rs index fc89713d7..793b48ee6 100644 --- a/contracts/credit-manager/tests/helpers/deploys.rs +++ b/contracts/credit-manager/tests/helpers/deploys.rs @@ -5,7 +5,7 @@ use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use account_nft::msg::ExecuteMsg as NftExecuteMsg; use mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; -use mock_red_bank::msg::{DenomWithLTV, InstantiateMsg as RedBankInstantiateMsg}; +use mock_red_bank::msg::{CoinMarketInfo, InstantiateMsg as RedBankInstantiateMsg}; use rover::adapters::{OracleBase, RedBankBase}; use rover::msg::execute::ExecuteMsg; use rover::msg::instantiate::ConfigUpdates; @@ -13,7 +13,7 @@ use rover::msg::InstantiateMsg; use crate::helpers::contracts::{mock_account_nft_contract, mock_contract, mock_red_bank_contract}; use crate::helpers::types::MockEnv; -use crate::helpers::{mock_oracle_contract, CoinPriceLTV}; +use crate::helpers::{mock_oracle_contract, CoinInfo}; pub fn mock_create_credit_account( app: &mut App, @@ -75,7 +75,7 @@ pub fn setup_nft_contract(app: &mut App, owner: &Addr, manager_contract_addr: &A nft_contract_addr } -pub fn setup_oracle(app: &mut App, coins: &Vec) -> Addr { +pub fn setup_oracle(app: &mut App, coins: &Vec) -> Addr { let contract_code_id = app.store_code(mock_oracle_contract()); app.instantiate_contract( contract_code_id, @@ -96,7 +96,7 @@ pub fn setup_oracle(app: &mut App, coins: &Vec) -> Addr { .unwrap() } -pub fn setup_red_bank(app: &mut App, coins: &Vec) -> Addr { +pub fn setup_red_bank(app: &mut App, coins: &Vec) -> Addr { let contract_code_id = app.store_code(mock_red_bank_contract()); app.instantiate_contract( contract_code_id, @@ -104,9 +104,10 @@ pub fn setup_red_bank(app: &mut App, coins: &Vec) -> Addr { &RedBankInstantiateMsg { coins: coins .iter() - .map(|item| DenomWithLTV { + .map(|item| CoinMarketInfo { denom: item.denom.to_string(), max_ltv: item.max_ltv, + liquidation_threshold: item.liquidation_threshold, }) .collect(), }, @@ -128,7 +129,7 @@ pub fn fund_red_bank(app: &mut BasicApp, red_bank_addr: String, funds: Vec pub fn setup_credit_manager( mut app: &mut App, owner: &Addr, - allowed_coins: Vec, + allowed_coins: Vec, allowed_vaults: Vec, ) -> MockEnv { let credit_manager_code_id = app.store_code(mock_contract()); diff --git a/contracts/credit-manager/tests/helpers/queries.rs b/contracts/credit-manager/tests/helpers/queries.rs index f949b9293..a63ef8b44 100644 --- a/contracts/credit-manager/tests/helpers/queries.rs +++ b/contracts/credit-manager/tests/helpers/queries.rs @@ -1,5 +1,6 @@ use cosmwasm_std::Addr; use cw_multi_test::{App, AppResponse}; +use rover::health::Health; use rover::msg::query::{ConfigResponse, PositionResponse, QueryMsg}; @@ -31,6 +32,17 @@ pub fn query_position( .unwrap() } +pub fn query_health(app: &App, manager_contract_addr: &Addr, token_id: &String) -> Health { + app.wrap() + .query_wasm_smart( + manager_contract_addr.clone(), + &QueryMsg::Health { + token_id: token_id.clone(), + }, + ) + .unwrap() +} + pub fn query_config(app: &mut App, contract_addr: &Addr) -> ConfigResponse { app.wrap() .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index afd47662d..8ed0a1934 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -11,13 +11,14 @@ pub struct MockEnv { } #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct CoinPriceLTV { +pub struct CoinInfo { pub denom: String, pub price: Decimal, pub max_ltv: Decimal, + pub liquidation_threshold: Decimal, } -impl CoinPriceLTV { +impl CoinInfo { pub fn to_coin(&self, amount: Uint128) -> Coin { Coin { denom: self.denom.clone(), diff --git a/contracts/mock-oracle/src/contract.rs b/contracts/mock-oracle/src/contract.rs index bd50d4925..369bf8e8b 100644 --- a/contracts/mock-oracle/src/contract.rs +++ b/contracts/mock-oracle/src/contract.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ }; use crate::msg::{CoinPrice, ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::ASSET_PRICE; +use crate::state::COIN_PRICE; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( @@ -14,7 +14,7 @@ pub fn instantiate( msg: InstantiateMsg, ) -> StdResult { for item in msg.coins { - ASSET_PRICE.save(deps.storage, item.denom, &item.price)? + COIN_PRICE.save(deps.storage, item.denom, &item.price)? } Ok(Response::default()) } @@ -32,7 +32,7 @@ pub fn execute( } fn change_price(deps: DepsMut, coin: CoinPrice) -> StdResult { - ASSET_PRICE.save(deps.storage, coin.denom, &coin.price)?; + COIN_PRICE.save(deps.storage, coin.denom, &coin.price)?; Ok(Response::new()) } @@ -50,5 +50,5 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { // TODO: After mars-core bumps to the next version https://crates.io/crates/mars-core (currently 1.0.0) // should update this mock to return MarsDecimal: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/math/decimal.rs fn query_asset_price(deps: Deps, denom: String) -> StdResult { - ASSET_PRICE.load(deps.storage, denom) + COIN_PRICE.load(deps.storage, denom) } diff --git a/contracts/mock-oracle/src/msg.rs b/contracts/mock-oracle/src/msg.rs index 5f00818c4..8ba4b1043 100644 --- a/contracts/mock-oracle/src/msg.rs +++ b/contracts/mock-oracle/src/msg.rs @@ -21,10 +21,11 @@ pub enum ExecuteMsg { } // mocked from: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/oracle.rs#L155 -// cw-asset needs to be implemented before we can import +// cw-asset needs to be removed before we can import #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { Config {}, AssetPrice { denom: String }, + AssetPriceSource { denom: String }, } diff --git a/contracts/mock-oracle/src/state.rs b/contracts/mock-oracle/src/state.rs index f723992ea..dc549d09e 100644 --- a/contracts/mock-oracle/src/state.rs +++ b/contracts/mock-oracle/src/state.rs @@ -1,4 +1,4 @@ use cosmwasm_std::Decimal; use cw_storage_plus::Map; -pub const ASSET_PRICE: Map = Map::new("asset_price"); +pub const COIN_PRICE: Map = Map::new("coin_price"); diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index 8c3abffdf..7d0e4f44d 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -5,7 +5,7 @@ use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, use crate::execute::execute_borrow; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::query::{query_debt, query_market}; -use crate::state::ASSET_LTV; +use crate::state::COIN_MARKET_INFO; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( @@ -15,7 +15,7 @@ pub fn instantiate( msg: InstantiateMsg, ) -> StdResult { for item in msg.coins { - ASSET_LTV.save(deps.storage, item.denom, &item.max_ltv)? + COIN_MARKET_INFO.save(deps.storage, item.denom.clone(), &item)?; } Ok(Response::default()) } diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs index 5f3ada6a8..5181c2c5e 100644 --- a/contracts/mock-red-bank/src/msg.rs +++ b/contracts/mock-red-bank/src/msg.rs @@ -4,13 +4,14 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] pub struct InstantiateMsg { - pub coins: Vec, + pub coins: Vec, } #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct DenomWithLTV { +pub struct CoinMarketInfo { pub denom: String, pub max_ltv: Decimal, + pub liquidation_threshold: Decimal, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -41,4 +42,5 @@ pub struct UserAssetDebtResponse { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Market { pub max_loan_to_value: Decimal, + pub liquidation_threshold: Decimal, } diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index 14f13f5a2..a83915261 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{Deps, StdResult}; use crate::helpers::load_debt_amount; use crate::msg::{Market, UserAssetDebtResponse}; -use crate::state::ASSET_LTV; +use crate::state::COIN_MARKET_INFO; pub fn query_debt( deps: Deps, @@ -15,6 +15,9 @@ pub fn query_debt( } pub fn query_market(deps: Deps, denom: String) -> StdResult { - let max_loan_to_value = ASSET_LTV.load(deps.storage, denom)?; - Ok(Market { max_loan_to_value }) + let market_info = COIN_MARKET_INFO.load(deps.storage, denom)?; + Ok(Market { + max_loan_to_value: market_info.max_ltv, + liquidation_threshold: market_info.liquidation_threshold, + }) } diff --git a/contracts/mock-red-bank/src/state.rs b/contracts/mock-red-bank/src/state.rs index 54bccce62..0589d126b 100644 --- a/contracts/mock-red-bank/src/state.rs +++ b/contracts/mock-red-bank/src/state.rs @@ -1,7 +1,9 @@ -use cosmwasm_std::{Addr, Decimal, Uint128}; +use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::Map; +use crate::msg::CoinMarketInfo; + // Map<(DebtHolder, CoinDenom), AmountOfDebt> pub const DEBT_AMOUNT: Map<(Addr, String), Uint128> = Map::new("debt_amount"); -// Map -pub const ASSET_LTV: Map = Map::new("asset_ltv"); +// Map +pub const COIN_MARKET_INFO: Map = Map::new("coin_market_info"); diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index c40c7a15d..53316f31a 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -10,9 +10,6 @@ pub type ContractResult = Result; #[derive(Error, Debug, PartialEq)] pub enum ContractError { - #[error("Health factor {health_factor:?} less than 1")] - AccountUnhealthy { health_factor: String }, - #[error("{0}")] CheckedFromRatioError(#[from] CheckedFromRatioError), @@ -22,6 +19,9 @@ pub enum ContractError { #[error("{0}")] DecimalRangeExceeded(#[from] DecimalRangeExceeded), + #[error("Actions resulted in exceeding maximum allowed loan-to-value")] + AboveMaxLTV, + #[error("Callbacks cannot be invoked externally")] ExternalInvocation, diff --git a/packages/rover/src/health.rs b/packages/rover/src/health.rs index 26bc2f4df..d4e56be3f 100644 --- a/packages/rover/src/health.rs +++ b/packages/rover/src/health.rs @@ -4,9 +4,19 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Health { + /// Total value of assets pub assets_value: Decimal, - pub ltv_adjusted_assets_value: Decimal, + /// Total value of debts pub debts_value: Decimal, - pub health_factor: Option, - pub healthy: bool, + /// The sum of the value of all assets (multiplied by their liquidation threshold) over the + /// sum of the value of all debts. Main health factor used throughout app. + pub lqdt_health_factor: Option, + /// Liquidation Health factor <= 1 + pub liquidatable: bool, + /// The sum of the value of all assets (multiplied by their max LTV) over the sum of the value + /// of all debts. Used to enforce a leverage limit that does not liquidate with a little volatility. + pub max_ltv_health_factor: Option, + /// Exceeding the maximum LTV that we allow users to take a new position. + /// Uses max_ltv (instead of liquidation threshold) to calculate health factor. + pub above_max_ltv: bool, } diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 6d5e98879..af49d5176 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -45,9 +45,9 @@ pub enum CallbackMsg { /// Borrow specified amount of coin from Red Bank; /// Increase the token's asset amount and debt shares; Borrow { token_id: String, coin: Coin }, - /// Calculate a token's current LTV. If below the maximum LTV, emits a `position_updated` - /// event; if above the maximum LTV, throw an error - AssertHealth { token_id: String }, + /// Calculate the account's max loan-to-value health factor. If above 1, + /// emits a `position_changed` event. If 1 or below, raises an error. + AssertBelowMaxLTV { token_id: String }, } impl CallbackMsg { diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index fd98ef4a0..dfbbfeb3e 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; pub enum QueryMsg { /// Owner & account nft address. Response type: `ConfigResponse` Config, - /// Whitelisted vaults. Response type: `Vec>` + /// Whitelisted vaults. Response type: `Vec` AllowedVaults { start_after: Option, limit: Option, @@ -19,6 +19,8 @@ pub enum QueryMsg { }, /// The entire position represented by token. Response type: `PositionResponse` Position { token_id: String }, + /// The health of the entire position represented by token. Response type: `Health` + Health { token_id: String }, /// Enumerate assets for all token positions. Response type: `Vec` /// start_after accepts (token_id, denom) AllAssets { @@ -67,32 +69,29 @@ pub struct CoinShares { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct CoinValue { - pub value: Decimal, - pub price_per_unit: Decimal, pub denom: String, pub amount: Uint128, + pub price: Decimal, + pub total_value: Decimal, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub struct CoinSharesValue { - pub value: Decimal, - pub price_per_unit: Decimal, +pub struct DebtSharesValue { pub denom: String, pub shares: Uint128, + pub total_value: Decimal, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct PositionResponse { + /// Unique NFT token id that represents the cross-margin account. The owner of this NFT, owns the account. pub token_id: String, - pub assets: Vec, - pub debt_shares: Vec, - pub assets_value: Decimal, - pub ltv_adjusted_assets_value: Decimal, - pub debts_value: Decimal, - pub health_factor: Option, - pub healthy: bool, + /// All coin asset positions with its value + pub coin_assets: Vec, + /// All debt positions with its value + pub debt_shares: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] From 83ff4fb367df847ea77c9e2735579b0898340128 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Sat, 6 Aug 2022 10:24:02 +0200 Subject: [PATCH 045/218] More tests + debt value adjustment --- contracts/credit-manager/src/borrow.rs | 3 +- contracts/credit-manager/src/contract.rs | 2 +- contracts/credit-manager/src/execute.rs | 3 +- contracts/credit-manager/src/lib.rs | 2 - contracts/credit-manager/src/query.rs | 32 +++--- contracts/credit-manager/tests/borrow_test.rs | 105 ++++++++++++------ .../credit-manager/tests/deposit_test.rs | 27 +++-- .../credit-manager/tests/dispatch_test.rs | 2 - contracts/credit-manager/tests/health_test.rs | 101 ++++++++++------- .../credit-manager/tests/helpers/queries.rs | 19 ++++ packages/rover/src/error.rs | 6 +- packages/rover/src/lib.rs | 5 +- 12 files changed, 195 insertions(+), 112 deletions(-) diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index 982163f56..256b34012 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -1,7 +1,6 @@ use cosmwasm_std::{Coin, DepsMut, Env, Response, StdError, StdResult, Uint128}; -use rover::ContractResult; -use rover::error::ContractError; +use rover::error::{ContractError, ContractResult}; use crate::deposit::assert_coin_is_whitelisted; use crate::state::{ASSETS, DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 3eae738e7..0ff6be893 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -1,8 +1,8 @@ use cosmwasm_std::{entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; use cw2::set_contract_version; +use rover::error::ContractResult; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use rover::ContractResult; use crate::execute::{create_credit_account, dispatch_actions, execute_callback, update_config}; use crate::health::compute_health; diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 0342c0a31..9abb2ccf4 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -6,10 +6,9 @@ use cw721_base::QueryMsg; use account_nft::msg::ExecuteMsg as NftExecuteMsg; use rover::coins::Coins; -use rover::error::ContractError; +use rover::error::{ContractError, ContractResult}; use rover::msg::execute::{Action, CallbackMsg}; use rover::msg::instantiate::ConfigUpdates; -use rover::ContractResult; use crate::borrow::borrow; use crate::deposit::deposit; diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index e63c5d67b..35228e3bd 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -1,5 +1,3 @@ -extern crate core; - pub mod contract; pub mod borrow; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index d12e3f2a7..a04df7670 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,11 +1,12 @@ use cosmwasm_std::{Decimal, Deps, Env, Order, StdResult, Uint128}; use cw_storage_plus::Bound; +use rover::error::ContractResult; use rover::msg::query::{ AssetResponseItem, CoinShares, CoinValue, ConfigResponse, DebtSharesValue, PositionResponse, SharesResponseItem, }; -use rover::{ContractResult, Denom, NftTokenId}; +use rover::{Denom, NftTokenId}; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, ASSETS, DEBT_SHARES, ORACLE, OWNER, RED_BANK, @@ -76,23 +77,25 @@ fn get_debt_shares_values( .collect::>>()? .iter() .map(|(denom, shares)| { - // proportion of debt this token represents + // total shares of debt issued for denom let total_debt_shares = TOTAL_DEBT_SHARES .load(deps.storage, denom) .unwrap_or(Uint128::zero()); - let token_share_ratio = Decimal::checked_from_ratio(*shares, total_debt_shares)?; - // total rover debt for asset + // total rover debt amount in Redbank for asset let red_bank = RED_BANK.load(deps.storage)?; let total_debt_amount = red_bank.query_debt(&deps.querier, &env.contract.address, denom)?; - let total_debt_amount_dec = Decimal::from_atomics(total_debt_amount, 0)?; + + // amount of debt for token's position + // NOTE: Given the nature of integers, the debt is rounded down. This means that the + // remaining share owners will take a small hit of the remainder. + let owed = total_debt_amount.checked_multiply_ratio(*shares, total_debt_shares)?; + let owed_dec = Decimal::from_atomics(owed, 0)?; // debt value of token's position let coin_price = oracle.query_price(&deps.querier, denom)?; - let position_debt_value = coin_price - .checked_mul(total_debt_amount_dec)? - .checked_mul(token_share_ratio)?; + let position_debt_value = coin_price.checked_mul(owed_dec)?; Ok(DebtSharesValue { total_value: position_debt_value, @@ -155,13 +158,12 @@ pub fn query_allowed_vaults( start_after: Option, limit: Option, ) -> StdResult> { - let start = match &start_after { - Some(addr_str) => { - let addr = deps.api.addr_validate(addr_str)?; - Some(Bound::exclusive(addr)) - } - None => None, - }; + let start = start_after + .map(|addr_str| -> StdResult<_> { + let addr = deps.api.addr_validate(&addr_str)?; + Ok(Bound::exclusive(addr)) + }) + .transpose()?; let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; diff --git a/contracts/credit-manager/tests/borrow_test.rs b/contracts/credit-manager/tests/borrow_test.rs index 1d5c807fb..bd3190547 100644 --- a/contracts/credit-manager/tests/borrow_test.rs +++ b/contracts/credit-manager/tests/borrow_test.rs @@ -4,8 +4,6 @@ use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use cw_multi_test::{App, Executor}; use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; -use mock_red_bank::msg::QueryMsg::UserAssetDebt; -use mock_red_bank::msg::UserAssetDebtResponse; use rover::error::ContractError; use rover::msg::execute::Action::{Borrow, Deposit}; use rover::msg::query::CoinShares; @@ -14,7 +12,7 @@ use rover::msg::QueryMsg; use crate::helpers::{ assert_err, fund_red_bank, get_token_id, mock_app, mock_create_credit_account, query_config, - query_position, setup_credit_manager, CoinInfo, + query_position, query_red_bank_debt, setup_credit_manager, CoinInfo, }; pub mod helpers; @@ -69,7 +67,7 @@ fn test_can_only_borrow_what_is_whitelisted() { let mock = setup_credit_manager(&mut app, &owner, vec![coin_info], vec![]); let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); + let res = mock_create_credit_account(&mut app, &mock.credit_manager.clone(), &user).unwrap(); let token_id = get_token_id(res); let res = app.execute_contract( @@ -190,17 +188,30 @@ fn test_success_when_new_debt_asset() { let position = query_position(&mut app, &mock.credit_manager, &token_id); assert_eq!(position.coin_assets.len(), 1); + let asset_res = position.coin_assets.first().unwrap(); assert_eq!( - position.coin_assets.first().unwrap().amount, + asset_res.amount, Uint128::from(342u128) // Deposit + Borrow ); - assert_eq!(position.coin_assets.first().unwrap().denom, coin_info.denom); + assert_eq!(asset_res.denom, coin_info.denom); + assert_eq!(asset_res.price, coin_info.price); + assert_eq!( + asset_res.total_value, + coin_info.price * Decimal::from_atomics(342u128, 0).unwrap() + ); + + let debt_shares_res = position.debt_shares.first().unwrap(); assert_eq!(position.debt_shares.len(), 1); assert_eq!( - position.debt_shares.first().unwrap().shares, + debt_shares_res.shares, Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) ); - assert_eq!(position.debt_shares.first().unwrap().denom, coin_info.denom); + assert_eq!(debt_shares_res.denom, coin_info.denom); + let debt_amount = Uint128::from(42u128) + Uint128::new(1u128); // simulated yield + assert_eq!( + debt_shares_res.total_value, + coin_info.price * Decimal::from_atomics(debt_amount, 0).unwrap() + ); let coin = app .wrap() @@ -292,16 +303,12 @@ fn test_debt_shares_with_debt_amount() { ) .unwrap(); - let interim_red_bank_debt: UserAssetDebtResponse = app - .wrap() - .query_wasm_smart( - config.red_bank, - &UserAssetDebt { - user_address: mock.credit_manager.clone().into(), - denom: coin_info.denom.clone(), - }, - ) - .unwrap(); + let interim_red_bank_debt = query_red_bank_debt( + &mut app, + &mock.credit_manager, + &config.red_bank, + &coin_info.denom, + ); app.execute_contract( user_b, @@ -319,27 +326,63 @@ fn test_debt_shares_with_debt_amount() { let token_a_shares = Uint128::from(50u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED); let position = query_position(&mut app, &mock.credit_manager, &token_id_a); - assert_eq!( - position.debt_shares.first().unwrap().shares, - token_a_shares.clone() - ); + let debt_position_a = position.debt_shares.first().unwrap(); + assert_eq!(debt_position_a.shares, token_a_shares.clone()); + assert_eq!(debt_position_a.denom, coin_info.denom); let token_b_shares = Uint128::from(50u128) .mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) .multiply_ratio(Uint128::from(50u128), interim_red_bank_debt.amount); - let position = query_position(&mut app, &mock.credit_manager, &token_id_b); - assert_eq!( - position.debt_shares.first().unwrap().shares, - token_b_shares.clone() - ); + let debt_position_b = position.debt_shares.first().unwrap(); + assert_eq!(debt_position_b.shares, token_b_shares.clone()); + assert_eq!(debt_position_b.denom, coin_info.denom); - let res: CoinShares = app + let total: CoinShares = app .wrap() .query_wasm_smart( - mock.credit_manager, - &QueryMsg::TotalDebtShares(coin_info.denom), + mock.credit_manager.clone(), + &QueryMsg::TotalDebtShares(coin_info.denom.clone()), ) .unwrap(); - assert_eq!(res.shares, token_a_shares + token_b_shares); + assert_eq!( + total.shares, + debt_position_a.shares + debt_position_b.shares + ); + + let red_bank_debt = query_red_bank_debt( + &mut app, + &mock.credit_manager, + &config.red_bank, + &coin_info.denom, + ); + + let a_amount_owed = red_bank_debt + .amount + .multiply_ratio(debt_position_a.shares, total.shares); + assert_eq!( + debt_position_a.total_value, + coin_info.price * Decimal::from_atomics(a_amount_owed, 0).unwrap() + ); + + let b_amount_owed = red_bank_debt + .amount + .multiply_ratio(debt_position_b.shares, total.shares); + assert_eq!( + debt_position_b.total_value, + coin_info.price * Decimal::from_atomics(b_amount_owed, 0).unwrap() + ); + + // NOTE: There is an expected rounding error. This will not pass. + // let total_borrowed_plus_interest = Decimal::from_atomics(Uint128::from(102u128), 0).unwrap(); + // assert_eq!( + // total_borrowed_plus_interest * coin_info.price, + // debt_position_a.total_value + debt_position_b.total_value + // ) + // This test below asserts the rounding down that's happening + let total_owed = Decimal::from_atomics(a_amount_owed + b_amount_owed, 0).unwrap(); + assert_eq!( + total_owed * coin_info.price, + debt_position_a.total_value + debt_position_b.total_value + ) } diff --git a/contracts/credit-manager/tests/deposit_test.rs b/contracts/credit-manager/tests/deposit_test.rs index 5338a45e4..5db5b5c8f 100644 --- a/contracts/credit-manager/tests/deposit_test.rs +++ b/contracts/credit-manager/tests/deposit_test.rs @@ -1,5 +1,3 @@ -extern crate core; - use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use cw_multi_test::{App, Executor}; @@ -18,8 +16,6 @@ use crate::helpers::{ pub mod helpers; -// TODO: Assert values - #[test] fn test_only_owner_of_token_can_deposit() { let mut app = mock_app(); @@ -337,10 +333,15 @@ fn test_deposit_success() { .unwrap(); let res = query_position(&app, &mock.credit_manager, &token_id); + let assets_res = res.coin_assets.first().unwrap(); assert_eq!(res.coin_assets.len(), 1); - assert_eq!(res.coin_assets.first().unwrap().amount, deposit_amount); - assert_eq!(res.coin_assets.first().unwrap().denom, coin_info.denom); - assert_eq!(res.coin_assets.first().unwrap().price, coin_info.price); + assert_eq!(assets_res.amount, deposit_amount); + assert_eq!(assets_res.denom, coin_info.denom); + assert_eq!(assets_res.price, coin_info.price); + assert_eq!( + assets_res.total_value, + coin_info.price * Decimal::from_atomics(deposit_amount, 0).unwrap() + ); let coin = app .wrap() @@ -410,8 +411,10 @@ fn test_multiple_deposit_actions() { let res = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(res.coin_assets.len(), 2); - assert_present(&res, &uosmo_info, uosmo_amount); - assert_present(&res, &uatom_info, uatom_amount); + let uosmo_value = Decimal::from_atomics(uosmo_amount, 0).unwrap() * uosmo_info.price; + assert_present(&res, &uosmo_info, uosmo_amount, uosmo_value); + let uatom_value = Decimal::from_atomics(uatom_amount, 0).unwrap() * uatom_info.price; + assert_present(&res, &uatom_info, uatom_amount, uatom_value); let coin = app .wrap() @@ -426,9 +429,11 @@ fn test_multiple_deposit_actions() { assert_eq!(coin.amount, uatom_amount); } -fn assert_present(res: &PositionResponse, coin: &CoinInfo, amount: Uint128) { +fn assert_present(res: &PositionResponse, coin: &CoinInfo, amount: Uint128, total_val: Decimal) { res.coin_assets .iter() - .find(|item| item.denom == coin.denom && &item.amount == &amount) + .find(|item| { + item.denom == coin.denom && &item.amount == &amount && item.total_value == total_val + }) .unwrap(); } diff --git a/contracts/credit-manager/tests/dispatch_test.rs b/contracts/credit-manager/tests/dispatch_test.rs index c86442756..7cdd1e62b 100644 --- a/contracts/credit-manager/tests/dispatch_test.rs +++ b/contracts/credit-manager/tests/dispatch_test.rs @@ -1,5 +1,3 @@ -extern crate core; - use cosmwasm_std::Addr; use cw_multi_test::Executor; diff --git a/contracts/credit-manager/tests/health_test.rs b/contracts/credit-manager/tests/health_test.rs index e06a10024..ea63fdadf 100644 --- a/contracts/credit-manager/tests/health_test.rs +++ b/contracts/credit-manager/tests/health_test.rs @@ -1,5 +1,3 @@ -extern crate core; - use std::ops::{Add, Div, Mul}; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; @@ -7,15 +5,14 @@ use cw_multi_test::{App, BasicApp, Executor}; use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; use mock_oracle::msg::{CoinPrice, ExecuteMsg as OracleExecuteMsg}; -use mock_red_bank::msg::QueryMsg::UserAssetDebt; -use mock_red_bank::msg::UserAssetDebtResponse; use rover::error::ContractError; use rover::msg::execute::Action::{Borrow, Deposit}; -use rover::msg::ExecuteMsg; +use rover::msg::query::{CoinShares, DebtSharesValue}; +use rover::msg::{ExecuteMsg, QueryMsg}; use crate::helpers::{ assert_err, fund_red_bank, get_token_id, mock_app, mock_create_credit_account, query_config, - query_health, query_position, setup_credit_manager, CoinInfo, MockEnv, + query_health, query_position, query_red_bank_debt, setup_credit_manager, CoinInfo, MockEnv, }; pub mod helpers; @@ -662,16 +659,12 @@ fn test_debt_value() { ) .unwrap(); - let interim_red_bank_debt: UserAssetDebtResponse = app - .wrap() - .query_wasm_smart( - config.red_bank.clone(), - &UserAssetDebt { - user_address: mock.credit_manager.clone().into(), - denom: uatom_info.denom.clone(), - }, - ) - .unwrap(); + let interim_red_bank_debt = query_red_bank_debt( + &mut app, + &mock.credit_manager, + &config.red_bank, + &uatom_info.denom, + ); let user_b_deposit_amount = Uint128::from(101u128); let user_b_borrowed_amount_atom = Uint128::from(24u128); @@ -690,44 +683,69 @@ fn test_debt_value() { ) .unwrap(); - let position = query_position(&app, &mock.credit_manager, &token_id_a); - assert_eq!(position.token_id, token_id_a); - assert_eq!(position.coin_assets.len(), 2); + let position_a = query_position(&app, &mock.credit_manager, &token_id_a); + assert_eq!(position_a.token_id, token_id_a); + assert_eq!(position_a.coin_assets.len(), 2); + assert_eq!(position_a.debt_shares.len(), 2); let health = query_health(&app, &mock.credit_manager, &token_id_a); + assert_eq!(health.above_max_ltv, false); + assert_eq!(health.liquidatable, false); - let user_a_borrowed_amount_atom_dec = - Decimal::from_atomics(user_a_borrowed_amount_atom, 0).unwrap(); - let user_b_borrowed_amount_atom_dec = - Decimal::from_atomics(user_b_borrowed_amount_atom, 0).unwrap(); - - let interest = Decimal::one() + Decimal::one(); // simulated from mock_oracle - let total_debt_shares_value = uatom_info.price - * (user_a_borrowed_amount_atom_dec + user_b_borrowed_amount_atom_dec + interest); + let red_bank_atom_debt = query_red_bank_debt( + &mut app, + &mock.credit_manager, + &config.red_bank, + &uatom_info.denom, + ); let user_a_debt_shares_atom = user_a_borrowed_amount_atom.mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED); + assert_eq!( + user_a_debt_shares_atom, + find_by_denom(&uatom_info.denom, &position_a.debt_shares).shares + ); + let position_b = query_position(&app, &mock.credit_manager, &token_id_b); let user_b_debt_shares_atom = user_a_debt_shares_atom .multiply_ratio(user_b_borrowed_amount_atom, interim_red_bank_debt.amount); + assert_eq!( + user_b_debt_shares_atom, + find_by_denom(&uatom_info.denom, &position_b.debt_shares).shares + ); - let debt_shares_ownership_ratio = Decimal::checked_from_ratio( - user_a_debt_shares_atom, - user_a_debt_shares_atom + user_b_debt_shares_atom, - ) - .unwrap(); + let red_bank_atom_res: CoinShares = app + .wrap() + .query_wasm_smart( + &mock.credit_manager, + &QueryMsg::TotalDebtShares(uatom_info.denom.clone()), + ) + .unwrap(); - let atom_debt_value = total_debt_shares_value.mul(debt_shares_ownership_ratio); + assert_eq!( + red_bank_atom_res.shares, + user_a_debt_shares_atom + user_b_debt_shares_atom + ); - let user_a_borrowed_amount_osmo_dec = - Decimal::from_atomics(user_a_borrowed_amount_osmo, 0).unwrap(); - let osmo_debt_value = uosmo_info.price * (user_a_borrowed_amount_osmo_dec + Decimal::one()); + let user_a_owed_atom = red_bank_atom_debt + .amount + .multiply_ratio(user_a_debt_shares_atom, red_bank_atom_res.shares); + let user_a_owed_atom_value = + uatom_info.price * Decimal::from_atomics(user_a_owed_atom, 0).unwrap(); + + let osmo_borrowed_amount_dec = + Decimal::from_atomics(user_a_borrowed_amount_osmo + Uint128::new(1u128), 0).unwrap(); + let osmo_debt_value = uosmo_info.price * osmo_borrowed_amount_dec; - let total_debt_value = atom_debt_value.add(osmo_debt_value); + let total_debt_value = user_a_owed_atom_value.add(osmo_debt_value); assert_eq!(health.debts_value, total_debt_value); let user_a_deposit_amount_osmo_dec = Decimal::from_atomics(user_a_deposit_amount_osmo, 0).unwrap(); + let user_a_borrowed_amount_osmo_dec = + Decimal::from_atomics(user_a_borrowed_amount_osmo, 0).unwrap(); + let user_a_borrowed_amount_atom_dec = + Decimal::from_atomics(user_a_borrowed_amount_atom, 0).unwrap(); let lqdt_adjusted_assets_value = (uosmo_info.price * user_a_deposit_amount_osmo_dec @@ -748,8 +766,6 @@ fn test_debt_value() { health.max_ltv_health_factor, Some(ltv_adjusted_assets_value.div(total_debt_value)) ); - assert_eq!(health.above_max_ltv, false); - assert_eq!(health.liquidatable, false); } fn price_change(app: &mut BasicApp, mock: &MockEnv, coin: CoinPrice) -> () { @@ -761,3 +777,10 @@ fn price_change(app: &mut BasicApp, mock: &MockEnv, coin: CoinPrice) -> () { ) .unwrap(); } + +fn find_by_denom<'a>(denom: &'a str, shares: &'a Vec) -> &'a DebtSharesValue { + shares + .iter() + .find(|item| item.denom == denom.to_string()) + .unwrap() +} diff --git a/contracts/credit-manager/tests/helpers/queries.rs b/contracts/credit-manager/tests/helpers/queries.rs index a63ef8b44..4194ae94f 100644 --- a/contracts/credit-manager/tests/helpers/queries.rs +++ b/contracts/credit-manager/tests/helpers/queries.rs @@ -1,5 +1,7 @@ use cosmwasm_std::Addr; use cw_multi_test::{App, AppResponse}; +use mock_red_bank::msg::QueryMsg::UserAssetDebt; +use mock_red_bank::msg::UserAssetDebtResponse; use rover::health::Health; use rover::msg::query::{ConfigResponse, PositionResponse, QueryMsg}; @@ -48,3 +50,20 @@ pub fn query_config(app: &mut App, contract_addr: &Addr) -> ConfigResponse { .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) .unwrap() } + +pub fn query_red_bank_debt( + app: &mut App, + credit_manager_addr: &Addr, + red_bank_addr: &str, + denom: &str, +) -> UserAssetDebtResponse { + app.wrap() + .query_wasm_smart( + red_bank_addr, + &UserAssetDebt { + user_address: credit_manager_addr.into(), + denom: denom.into(), + }, + ) + .unwrap() +} diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 53316f31a..18d0517ab 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -10,6 +10,9 @@ pub type ContractResult = Result; #[derive(Error, Debug, PartialEq)] pub enum ContractError { + #[error("Actions resulted in exceeding maximum allowed loan-to-value")] + AboveMaxLTV, + #[error("{0}")] CheckedFromRatioError(#[from] CheckedFromRatioError), @@ -19,9 +22,6 @@ pub enum ContractError { #[error("{0}")] DecimalRangeExceeded(#[from] DecimalRangeExceeded), - #[error("Actions resulted in exceeding maximum allowed loan-to-value")] - AboveMaxLTV, - #[error("Callbacks cannot be invoked externally")] ExternalInvocation, diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index c456bf5b0..429d319cb 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -1,14 +1,11 @@ use cosmwasm_std::Uint128; -use crate::error::ContractError; - pub mod adapters; pub mod coins; pub mod error; pub mod health; pub mod msg; -pub type ContractResult = Result; -pub type NftTokenId<'a> = &'a str; pub type Denom<'a> = &'a str; +pub type NftTokenId<'a> = &'a str; pub type Shares = Uint128; From 3e83ad282787fa64613b65ee6a171abc2e09ca27 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 11 Aug 2022 12:18:36 +0200 Subject: [PATCH 046/218] Review updates --- contracts/credit-manager/src/borrow.rs | 4 +- contracts/credit-manager/src/contract.rs | 2 +- contracts/credit-manager/src/deposit.rs | 4 +- contracts/credit-manager/src/execute.rs | 14 +- contracts/credit-manager/src/health.rs | 28 +- contracts/credit-manager/src/instantiate.rs | 6 +- contracts/credit-manager/src/query.rs | 88 +++---- contracts/credit-manager/src/state.rs | 8 +- contracts/credit-manager/tests/borrow_test.rs | 10 +- .../tests/create_credit_account_test.rs | 4 +- .../credit-manager/tests/deposit_test.rs | 24 +- .../credit-manager/tests/dispatch_test.rs | 4 +- .../tests/enumerate_allowed_coins_test.rs | 4 +- .../tests/enumerate_allowed_vaults_test.rs | 4 +- ...est.rs => enumerate_coin_balances_test.rs} | 150 +++-------- .../tests/enumerate_debt_shares_test.rs | 240 +----------------- .../tests/enumerate_total_debt_shares_test.rs | 240 +----------------- contracts/credit-manager/tests/health_test.rs | 44 ++-- .../credit-manager/tests/helpers/builders.rs | 29 +++ .../credit-manager/tests/helpers/deploys.rs | 4 +- contracts/credit-manager/tests/helpers/mod.rs | 2 + .../credit-manager/tests/instantiate_test.rs | 42 +-- .../tests/update_config_test.rs | 12 +- contracts/mock-oracle/src/contract.rs | 2 - contracts/mock-red-bank/src/msg.rs | 2 - packages/rover/src/adapters/oracle.rs | 29 ++- packages/rover/src/adapters/red_bank.rs | 12 +- packages/rover/src/health.rs | 4 +- packages/rover/src/msg/query.rs | 12 +- 29 files changed, 270 insertions(+), 758 deletions(-) rename contracts/credit-manager/tests/{enumerate_assets_test.rs => enumerate_coin_balances_test.rs} (53%) create mode 100644 contracts/credit-manager/tests/helpers/builders.rs diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index 256b34012..ffd54efc7 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{Coin, DepsMut, Env, Response, StdError, StdResult, Uint128}; use rover::error::{ContractError, ContractResult}; use crate::deposit::assert_coin_is_whitelisted; -use crate::state::{ASSETS, DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; +use crate::state::{COIN_BALANCES, DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; pub static DEFAULT_DEBT_UNITS_PER_COIN_BORROWED: Uint128 = Uint128::new(1_000_000); @@ -51,7 +51,7 @@ pub fn borrow(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRe }, )?; - ASSETS.update( + COIN_BALANCES.update( deps.storage, (token_id, &coin.denom), |current_amount| -> StdResult<_> { diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 0ff6be893..8a9cf65b0 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -56,7 +56,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { } QueryMsg::Position { token_id } => to_binary(&query_position(deps, &env, &token_id)?), QueryMsg::Health { token_id } => to_binary(&compute_health(deps, &env, &token_id)?), - QueryMsg::AllAssets { start_after, limit } => { + QueryMsg::AllCoinBalances { start_after, limit } => { to_binary(&query_all_assets(deps, start_after, limit)?) } QueryMsg::AllDebtShares { start_after, limit } => { diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index 44d819441..aea01e609 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{Coin, Response, StdError, StdResult, Storage, Uint128}; use rover::coins::Coins; use rover::error::{ContractError, ContractResult}; -use crate::state::{ALLOWED_COINS, ASSETS}; +use crate::state::{ALLOWED_COINS, COIN_BALANCES}; pub fn deposit( storage: &mut dyn Storage, @@ -55,7 +55,7 @@ pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, denom: &str) -> Con } fn increment_position(storage: &mut dyn Storage, token_id: &str, coin: &Coin) -> StdResult<()> { - ASSETS.update( + COIN_BALANCES.update( storage, (token_id, &coin.denom), |value_opt| -> StdResult<_> { diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 9abb2ccf4..7261d2784 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - to_binary, Addr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdResult, WasmMsg, + to_binary, Addr, CosmosMsg, DepsMut, Empty, Env, MessageInfo, Response, StdResult, WasmMsg, }; use cw721::OwnerOfResponse; use cw721_base::QueryMsg; @@ -76,7 +76,7 @@ pub fn update_config( if let Some(coins) = new_config.allowed_coins { coins .iter() - .try_for_each(|denom| ALLOWED_COINS.save(deps.storage, denom, &true))?; + .try_for_each(|denom| ALLOWED_COINS.save(deps.storage, denom, &Empty {}))?; response = response .add_attribute("key", "allowed_coins") .add_attribute("value", coins.join(", ")); @@ -85,7 +85,7 @@ pub fn update_config( if let Some(vaults) = new_config.allowed_vaults { vaults.iter().try_for_each(|unchecked| { let vault = deps.api.addr_validate(unchecked)?; - ALLOWED_VAULTS.save(deps.storage, vault, &true) + ALLOWED_VAULTS.save(deps.storage, &vault, &Empty {}) })?; response = response .add_attribute("key", "allowed_vaults") @@ -96,14 +96,14 @@ pub fn update_config( RED_BANK.save(deps.storage, &unchecked.check(deps.api)?)?; response = response .add_attribute("key", "red_bank") - .add_attribute("value", unchecked.0); + .add_attribute("value", unchecked.address()); } if let Some(unchecked) = new_config.oracle { ORACLE.save(deps.storage, &unchecked.check(deps.api)?)?; response = response .add_attribute("key", "oracle") - .add_attribute("value", unchecked.0); + .add_attribute("value", unchecked.address()); } Ok(response) @@ -166,7 +166,9 @@ pub fn execute_callback( } match callback { CallbackMsg::Borrow { coin, token_id } => borrow(deps, env, &token_id, coin), - CallbackMsg::AssertBelowMaxLTV { token_id } => assert_below_max_ltv(deps, env, &token_id), + CallbackMsg::AssertBelowMaxLTV { token_id } => { + assert_below_max_ltv(deps.as_ref(), env, &token_id) + } } } diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index cc5c8ee63..5032a8211 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -1,6 +1,6 @@ use std::ops::{Add, Div, Mul}; -use cosmwasm_std::{Decimal, Deps, DepsMut, Env, Event, Response, StdResult}; +use cosmwasm_std::{Decimal, Deps, Env, Event, Response, StdResult}; use mock_red_bank::msg::{Market, QueryMsg}; use rover::error::{ContractError, ContractResult}; @@ -19,19 +19,19 @@ pub fn compute_health(deps: Deps, env: &Env, token_id: NftTokenId) -> ContractRe // The sum of the position's coin asset values w/ loan-to-value adjusted & liquidation threshold adjusted let (total_assets_value, max_ltv_adjusted_value, lqdt_adjusted_value) = - position.coin_assets.iter().try_fold::<_, _, StdResult<_>>( + position.coins.iter().try_fold::<_, _, StdResult<_>>( (Decimal::zero(), Decimal::zero(), Decimal::zero()), |(total, max_ltv_adjusted_total, lqdt_adjusted_total), item| { let market: Market = deps.querier.query_wasm_smart( - red_bank.0.clone(), + red_bank.address().clone(), &QueryMsg::Market { denom: item.denom.clone(), }, )?; Ok(( - total.add(item.total_value), - max_ltv_adjusted_total.add(item.total_value.mul(market.max_loan_to_value)), - lqdt_adjusted_total.add(item.total_value.mul(market.liquidation_threshold)), + total.add(item.value), + max_ltv_adjusted_total.add(item.value.mul(market.max_loan_to_value)), + lqdt_adjusted_total.add(item.value.mul(market.liquidation_threshold)), )) }, )?; @@ -55,12 +55,12 @@ pub fn compute_health(deps: Deps, env: &Env, token_id: NftTokenId) -> ContractRe ) }; - let liquidatable = lqdt_health_factor.map_or_else(|| false, |hf| hf <= Decimal::one()); - let above_max_ltv = max_ltv_health_factor.map_or_else(|| false, |hf| hf <= Decimal::one()); + let liquidatable = lqdt_health_factor.map_or(false, |hf| hf <= Decimal::one()); + let above_max_ltv = max_ltv_health_factor.map_or(false, |hf| hf <= Decimal::one()); Ok(Health { - assets_value: total_assets_value, - debts_value: total_debts_value, + total_assets_value, + total_debts_value, lqdt_health_factor, liquidatable, max_ltv_health_factor, @@ -69,11 +69,11 @@ pub fn compute_health(deps: Deps, env: &Env, token_id: NftTokenId) -> ContractRe } pub fn assert_below_max_ltv( - deps: DepsMut, + deps: Deps, env: Env, token_id: NftTokenId, ) -> ContractResult { - let position = compute_health(deps.as_ref(), &env, token_id)?; + let position = compute_health(deps, &env, token_id)?; if position.above_max_ltv { return Err(ContractError::AboveMaxLTV); @@ -83,8 +83,8 @@ pub fn assert_below_max_ltv( .add_attribute("timestamp", env.block.time.seconds().to_string()) .add_attribute("height", env.block.height.to_string()) .add_attribute("token_id", token_id) - .add_attribute("assets_value", position.assets_value.to_string()) - .add_attribute("debts_value", position.debts_value.to_string()) + .add_attribute("assets_value", position.total_assets_value.to_string()) + .add_attribute("debts_value", position.total_debts_value.to_string()) .add_attribute( "lqdt_health_factor", val_or_not_applicable(position.lqdt_health_factor), diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index a9b427fc4..e3e2e4491 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{DepsMut, StdResult}; +use cosmwasm_std::{DepsMut, Empty, StdResult}; use rover::msg::InstantiateMsg; use crate::state::{ALLOWED_COINS, ALLOWED_VAULTS, ORACLE, OWNER, RED_BANK}; @@ -11,12 +11,12 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { msg.allowed_vaults.iter().try_for_each(|unchecked| { let vault = deps.api.addr_validate(unchecked)?; - ALLOWED_VAULTS.save(deps.storage, vault, &true) + ALLOWED_VAULTS.save(deps.storage, &vault, &Empty {}) })?; msg.allowed_coins .iter() - .try_for_each(|denom| ALLOWED_COINS.save(deps.storage, denom, &true))?; + .try_for_each(|denom| ALLOWED_COINS.save(deps.storage, denom, &Empty {}))?; Ok(()) } diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index a04df7670..3f23c280f 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,16 +1,16 @@ -use cosmwasm_std::{Decimal, Deps, Env, Order, StdResult, Uint128}; +use cosmwasm_std::{Addr, Decimal, Deps, Env, Order, StdResult, Uint128}; use cw_storage_plus::Bound; use rover::error::ContractResult; use rover::msg::query::{ - AssetResponseItem, CoinShares, CoinValue, ConfigResponse, DebtSharesValue, PositionResponse, - SharesResponseItem, + CoinBalanceResponseItem, CoinShares, CoinValue, ConfigResponse, DebtSharesValue, + PositionResponse, SharesResponseItem, }; use rover::{Denom, NftTokenId}; use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, ASSETS, DEBT_SHARES, ORACLE, OWNER, RED_BANK, - TOTAL_DEBT_SHARES, + ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, COIN_BALANCES, DEBT_SHARES, ORACLE, OWNER, + RED_BANK, TOTAL_DEBT_SHARES, }; const MAX_LIMIT: u32 = 30; @@ -22,8 +22,8 @@ pub fn query_config(deps: Deps) -> StdResult { account_nft: ACCOUNT_NFT .may_load(deps.storage)? .map(|addr| addr.to_string()), - red_bank: RED_BANK.load(deps.storage)?.0.into(), - oracle: ORACLE.load(deps.storage)?.0.into(), + red_bank: RED_BANK.load(deps.storage)?.address().into(), + oracle: ORACLE.load(deps.storage)?.address().into(), }) } @@ -32,12 +32,12 @@ pub fn query_position( env: &Env, token_id: NftTokenId, ) -> ContractResult { - let coin_asset_values = get_coin_asset_values(deps, token_id)?; + let coin_asset_values = get_coin_balances_values(deps, token_id)?; let debt_shares_values = get_debt_shares_values(deps, env, token_id)?; Ok(PositionResponse { token_id: token_id.to_string(), - coin_assets: coin_asset_values, + coins: coin_asset_values, debt_shares: debt_shares_values, }) } @@ -46,18 +46,18 @@ pub fn query_all_assets( deps: Deps, start_after: Option<(String, String)>, limit: Option, -) -> StdResult> { +) -> StdResult> { let start = start_after .as_ref() .map(|(token_id, denom)| Bound::exclusive((token_id.as_str(), denom.as_str()))); let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - Ok(ASSETS + Ok(COIN_BALANCES .range(deps.storage, start, None, Order::Ascending) .take(limit) .collect::>>()? .iter() - .map(|((token_id, denom), amount)| AssetResponseItem { + .map(|((token_id, denom), amount)| CoinBalanceResponseItem { token_id: token_id.to_string(), denom: denom.to_string(), amount: *amount, @@ -71,57 +71,56 @@ fn get_debt_shares_values( token_id: NftTokenId, ) -> ContractResult> { let oracle = ORACLE.load(deps.storage)?; + DEBT_SHARES .prefix(token_id) .range(deps.storage, None, None, Order::Ascending) - .collect::>>()? - .iter() - .map(|(denom, shares)| { + .map(|res| { + let (denom, shares) = res?; // total shares of debt issued for denom let total_debt_shares = TOTAL_DEBT_SHARES - .load(deps.storage, denom) + .load(deps.storage, &denom) .unwrap_or(Uint128::zero()); // total rover debt amount in Redbank for asset let red_bank = RED_BANK.load(deps.storage)?; let total_debt_amount = - red_bank.query_debt(&deps.querier, &env.contract.address, denom)?; + red_bank.query_debt(&deps.querier, &env.contract.address, &denom)?; // amount of debt for token's position // NOTE: Given the nature of integers, the debt is rounded down. This means that the // remaining share owners will take a small hit of the remainder. - let owed = total_debt_amount.checked_multiply_ratio(*shares, total_debt_shares)?; + let owed = total_debt_amount.checked_multiply_ratio(shares, total_debt_shares)?; let owed_dec = Decimal::from_atomics(owed, 0)?; // debt value of token's position - let coin_price = oracle.query_price(&deps.querier, denom)?; + let coin_price = oracle.query_price(&deps.querier, &denom)?; let position_debt_value = coin_price.checked_mul(owed_dec)?; Ok(DebtSharesValue { total_value: position_debt_value, - denom: denom.clone(), - shares: *shares, + denom, + shares, }) }) .collect() } -fn get_coin_asset_values(deps: Deps, token_id: &str) -> ContractResult> { +fn get_coin_balances_values(deps: Deps, token_id: &str) -> ContractResult> { let oracle = ORACLE.load(deps.storage)?; - ASSETS + COIN_BALANCES .prefix(token_id) .range(deps.storage, None, None, Order::Ascending) - .collect::>>()? - .iter() - .map(|(denom, amount)| { - let price = oracle.query_price(&deps.querier, denom)?; - let decimal_amount = Decimal::from_atomics(*amount, 0)?; - let total_value = price.checked_mul(decimal_amount)?; + .map(|item| { + let (denom, amount) = item?; + let price = oracle.query_price(&deps.querier, &denom)?; + let decimal_amount = Decimal::from_atomics(amount, 0)?; + let value = price.checked_mul(decimal_amount)?; Ok(CoinValue { - denom: denom.clone(), - amount: *amount, + denom, + amount, price, - total_value, + value, }) }) .collect() @@ -158,22 +157,25 @@ pub fn query_allowed_vaults( start_after: Option, limit: Option, ) -> StdResult> { - let start = start_after - .map(|addr_str| -> StdResult<_> { - let addr = deps.api.addr_validate(&addr_str)?; - Ok(Bound::exclusive(addr)) - }) - .transpose()?; + let addr: Addr; + let start = match &start_after { + Some(addr_str) => { + addr = deps.api.addr_validate(addr_str)?; + Some(Bound::exclusive(&addr)) + } + None => None, + }; let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - Ok(ALLOWED_VAULTS + ALLOWED_VAULTS .keys(deps.storage, start, None, Order::Ascending) .take(limit) - .collect::>>()? - .iter() - .map(|addr| addr.to_string()) - .collect()) + .map(|res| { + let addr = res?; + Ok(addr.to_string()) + }) + .collect() } /// NOTE: This implementation of the query function assumes the map `ALLOWED_COINS` only saves `true`. diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 35a81b766..9ae87188d 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Uint128}; +use cosmwasm_std::{Addr, Empty, Uint128}; use cw_storage_plus::{Item, Map}; use rover::adapters::{Oracle, RedBank}; @@ -7,12 +7,12 @@ use rover::{Denom, NftTokenId, Shares}; // Contract config pub const OWNER: Item = Item::new("owner"); pub const ACCOUNT_NFT: Item = Item::new("account_nft"); -pub const ALLOWED_COINS: Map = Map::new("allowed_coins"); -pub const ALLOWED_VAULTS: Map = Map::new("allowed_vaults"); +pub const ALLOWED_COINS: Map = Map::new("allowed_coins"); +pub const ALLOWED_VAULTS: Map<&Addr, Empty> = Map::new("allowed_vaults"); pub const RED_BANK: Item = Item::new("red_bank"); pub const ORACLE: Item = Item::new("oracle"); // Positions -pub const ASSETS: Map<(NftTokenId, Denom), Uint128> = Map::new("assets"); +pub const COIN_BALANCES: Map<(NftTokenId, Denom), Uint128> = Map::new("coin_balance"); pub const DEBT_SHARES: Map<(NftTokenId, Denom), Shares> = Map::new("debt_shares"); pub const TOTAL_DEBT_SHARES: Map = Map::new("total_debt_shares"); diff --git a/contracts/credit-manager/tests/borrow_test.rs b/contracts/credit-manager/tests/borrow_test.rs index bd3190547..d58c4a053 100644 --- a/contracts/credit-manager/tests/borrow_test.rs +++ b/contracts/credit-manager/tests/borrow_test.rs @@ -122,7 +122,7 @@ fn test_borrowing_zero_does_nothing() { assert_err(res, ContractError::NoAmount); let position = query_position(&mut app, &mock.credit_manager, &token_id); - assert_eq!(position.coin_assets.len(), 0); + assert_eq!(position.coins.len(), 0); assert_eq!(position.debt_shares.len(), 0); } @@ -164,7 +164,7 @@ fn test_success_when_new_debt_asset() { ); let position = query_position(&mut app, &mock.credit_manager, &token_id); - assert_eq!(position.coin_assets.len(), 0); + assert_eq!(position.coins.len(), 0); assert_eq!(position.debt_shares.len(), 0); app.execute_contract( user, @@ -187,8 +187,8 @@ fn test_success_when_new_debt_asset() { .unwrap(); let position = query_position(&mut app, &mock.credit_manager, &token_id); - assert_eq!(position.coin_assets.len(), 1); - let asset_res = position.coin_assets.first().unwrap(); + assert_eq!(position.coins.len(), 1); + let asset_res = position.coins.first().unwrap(); assert_eq!( asset_res.amount, Uint128::from(342u128) // Deposit + Borrow @@ -196,7 +196,7 @@ fn test_success_when_new_debt_asset() { assert_eq!(asset_res.denom, coin_info.denom); assert_eq!(asset_res.price, coin_info.price); assert_eq!( - asset_res.total_value, + asset_res.value, coin_info.price * Decimal::from_atomics(342u128, 0).unwrap() ); diff --git a/contracts/credit-manager/tests/create_credit_account_test.rs b/contracts/credit-manager/tests/create_credit_account_test.rs index 5ff85df68..d2fea1493 100644 --- a/contracts/credit-manager/tests/create_credit_account_test.rs +++ b/contracts/credit-manager/tests/create_credit_account_test.rs @@ -44,8 +44,8 @@ fn test_create_credit_account() { owner: owner.to_string(), allowed_vaults: vec![], allowed_coins: vec![], - red_bank: RedBankBase("redbankaddr".to_string()), - oracle: OracleBase("oracle_contract".to_string()), + red_bank: RedBankBase::new("red_bank_contract".to_string()), + oracle: OracleBase::new("oracle_contract".to_string()), }; let manager_contract_addr = app diff --git a/contracts/credit-manager/tests/deposit_test.rs b/contracts/credit-manager/tests/deposit_test.rs index 5db5b5c8f..d2ed3a9ab 100644 --- a/contracts/credit-manager/tests/deposit_test.rs +++ b/contracts/credit-manager/tests/deposit_test.rs @@ -71,7 +71,7 @@ fn test_deposit_nothing() { let token_id = get_token_id(res); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coin_assets.len(), 0); + assert_eq!(res.coins.len(), 0); app.execute_contract( user.clone(), @@ -85,7 +85,7 @@ fn test_deposit_nothing() { .unwrap(); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coin_assets.len(), 0); + assert_eq!(res.coins.len(), 0); } #[test] @@ -128,7 +128,7 @@ fn test_deposit_but_no_funds() { ); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coin_assets.len(), 0); + assert_eq!(res.coins.len(), 0); } #[test] @@ -227,7 +227,7 @@ fn test_can_only_deposit_allowed_assets() { assert_err(res, NotWhitelisted(not_allowed_coin.denom)); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coin_assets.len(), 0); + assert_eq!(res.coins.len(), 0); } #[test] @@ -286,7 +286,7 @@ fn test_extra_funds_received() { assert_err(res, ExtraFundsReceived(Coins::from(vec![extra_funds]))); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coin_assets.len(), 0); + assert_eq!(res.coins.len(), 0); } #[test] @@ -333,13 +333,13 @@ fn test_deposit_success() { .unwrap(); let res = query_position(&app, &mock.credit_manager, &token_id); - let assets_res = res.coin_assets.first().unwrap(); - assert_eq!(res.coin_assets.len(), 1); + let assets_res = res.coins.first().unwrap(); + assert_eq!(res.coins.len(), 1); assert_eq!(assets_res.amount, deposit_amount); assert_eq!(assets_res.denom, coin_info.denom); assert_eq!(assets_res.price, coin_info.price); assert_eq!( - assets_res.total_value, + assets_res.value, coin_info.price * Decimal::from_atomics(deposit_amount, 0).unwrap() ); @@ -410,7 +410,7 @@ fn test_multiple_deposit_actions() { .unwrap(); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coin_assets.len(), 2); + assert_eq!(res.coins.len(), 2); let uosmo_value = Decimal::from_atomics(uosmo_amount, 0).unwrap() * uosmo_info.price; assert_present(&res, &uosmo_info, uosmo_amount, uosmo_value); let uatom_value = Decimal::from_atomics(uatom_amount, 0).unwrap() * uatom_info.price; @@ -430,10 +430,8 @@ fn test_multiple_deposit_actions() { } fn assert_present(res: &PositionResponse, coin: &CoinInfo, amount: Uint128, total_val: Decimal) { - res.coin_assets + res.coins .iter() - .find(|item| { - item.denom == coin.denom && &item.amount == &amount && item.total_value == total_val - }) + .find(|item| item.denom == coin.denom && &item.amount == &amount && item.value == total_val) .unwrap(); } diff --git a/contracts/credit-manager/tests/dispatch_test.rs b/contracts/credit-manager/tests/dispatch_test.rs index 7cdd1e62b..5090e7299 100644 --- a/contracts/credit-manager/tests/dispatch_test.rs +++ b/contracts/credit-manager/tests/dispatch_test.rs @@ -52,7 +52,7 @@ fn test_nothing_happens_if_no_actions_are_passed() { let token_id = get_token_id(res); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coin_assets.len(), 0); + assert_eq!(res.coins.len(), 0); app.execute_contract( user.clone(), @@ -66,5 +66,5 @@ fn test_nothing_happens_if_no_actions_are_passed() { .unwrap(); let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coin_assets.len(), 0); + assert_eq!(res.coins.len(), 0); } diff --git a/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs b/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs index 6b2cba7ba..a85bd4bfc 100644 --- a/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs +++ b/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs @@ -53,8 +53,8 @@ fn test_pagination_on_allowed_coins_query_works() { owner: owner.to_string(), allowed_vaults: vec![], allowed_coins: allowed_coins.clone(), - red_bank: RedBankBase("redbankaddr".to_string()), - oracle: OracleBase("oracle_contract".to_string()), + red_bank: RedBankBase::new("red_bank_contract".to_string()), + oracle: OracleBase::new("oracle_contract".to_string()), }; let contract_addr = app diff --git a/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs b/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs index 4db9b1f2b..8132fde35 100644 --- a/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs +++ b/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs @@ -53,8 +53,8 @@ fn test_pagination_on_allowed_vaults_query_works() { owner: owner.to_string(), allowed_vaults: allowed_vaults.clone(), allowed_coins: vec![], - red_bank: RedBankBase("redbankaddr".to_string()), - oracle: OracleBase("oracle_contract".to_string()), + red_bank: RedBankBase::new("red_bank_contract".to_string()), + oracle: OracleBase::new("oracle_contract".to_string()), }; let contract_addr = app diff --git a/contracts/credit-manager/tests/enumerate_assets_test.rs b/contracts/credit-manager/tests/enumerate_coin_balances_test.rs similarity index 53% rename from contracts/credit-manager/tests/enumerate_assets_test.rs rename to contracts/credit-manager/tests/enumerate_coin_balances_test.rs index 9c6b43841..5f161da01 100644 --- a/contracts/credit-manager/tests/enumerate_assets_test.rs +++ b/contracts/credit-manager/tests/enumerate_coin_balances_test.rs @@ -1,16 +1,18 @@ -use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use cosmwasm_std::{Addr, Coin, Uint128}; use cw_multi_test::{App, Executor}; use rover::msg::execute::Action; -use rover::msg::query::AssetResponseItem; +use rover::msg::query::CoinBalanceResponseItem; use rover::msg::{ExecuteMsg, QueryMsg}; -use crate::helpers::{get_token_id, mock_create_credit_account, setup_credit_manager, CoinInfo}; +use crate::helpers::{ + build_mock_coin_infos, get_token_id, mock_create_credit_account, setup_credit_manager, +}; pub mod helpers; #[test] -fn test_pagination_on_all_coin_assets_query_works() { +fn test_pagination_on_all_coin_balances_query_works() { let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); let user_c = Addr::unchecked("user_c"); @@ -71,97 +73,9 @@ fn test_pagination_on_all_coin_assets_query_works() { .unwrap(); }); - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![ - CoinInfo { - denom: "coin_1".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_2".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_3".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_4".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_5".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_6".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_7".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_8".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_9".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_10".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_11".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_12".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_13".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_14".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - ], - vec![], - ); + let mock_coin_infos = build_mock_coin_infos(14); + + let mock = setup_credit_manager(&mut app, &Addr::unchecked("owner"), mock_coin_infos, vec![]); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); let token_id_a = get_token_id(res); @@ -211,11 +125,11 @@ fn test_pagination_on_all_coin_assets_query_works() { ) .unwrap(); - let all_assets_res: Vec = app + let all_assets_res: Vec = app .wrap() .query_wasm_smart( mock.credit_manager.clone(), - &QueryMsg::AllAssets { + &QueryMsg::AllCoinBalances { start_after: None, limit: Some(58 as u32), }, @@ -225,11 +139,11 @@ fn test_pagination_on_all_coin_assets_query_works() { // Assert maximum is observed assert_eq!(all_assets_res.len(), 30); - let all_assets_res: Vec = app + let all_assets_res: Vec = app .wrap() .query_wasm_smart( mock.credit_manager.clone(), - &QueryMsg::AllAssets { + &QueryMsg::AllCoinBalances { start_after: None, limit: Some(2 as u32), }, @@ -239,53 +153,53 @@ fn test_pagination_on_all_coin_assets_query_works() { // Assert limit request is observed assert_eq!(all_assets_res.len(), 2); - let all_assets_res_a: Vec = app + let all_assets_res_a: Vec = app .wrap() .query_wasm_smart( mock.credit_manager.clone(), - &QueryMsg::AllAssets { + &QueryMsg::AllCoinBalances { start_after: None, limit: None, }, ) .unwrap(); - let AssetResponseItem { + let CoinBalanceResponseItem { token_id, denom, .. } = all_assets_res_a.last().unwrap().clone(); - let all_assets_res_b: Vec = app + let all_assets_res_b: Vec = app .wrap() .query_wasm_smart( mock.credit_manager.clone(), - &QueryMsg::AllAssets { + &QueryMsg::AllCoinBalances { start_after: Some((token_id, denom)), limit: None, }, ) .unwrap(); - let AssetResponseItem { + let CoinBalanceResponseItem { token_id, denom, .. } = all_assets_res_b.last().unwrap().clone(); - let all_assets_res_c: Vec = app + let all_assets_res_c: Vec = app .wrap() .query_wasm_smart( mock.credit_manager.clone(), - &QueryMsg::AllAssets { + &QueryMsg::AllCoinBalances { start_after: Some((token_id, denom)), limit: None, }, ) .unwrap(); - let AssetResponseItem { + let CoinBalanceResponseItem { token_id, denom, .. } = all_assets_res_c.last().unwrap().clone(); - let all_assets_res_d: Vec = app + let all_assets_res_d: Vec = app .wrap() .query_wasm_smart( mock.credit_manager.clone(), - &QueryMsg::AllAssets { + &QueryMsg::AllCoinBalances { start_after: Some((token_id, denom)), limit: None, }, @@ -299,7 +213,7 @@ fn test_pagination_on_all_coin_assets_query_works() { assert_eq!(all_assets_res_d.len(), 2); - let combined_res: Vec = all_assets_res_a + let combined_res: Vec = all_assets_res_a .iter() .cloned() .chain(all_assets_res_b.iter().cloned()) @@ -309,32 +223,32 @@ fn test_pagination_on_all_coin_assets_query_works() { let user_a_response_items = user_a_coins .iter() - .map(|coin| AssetResponseItem { + .map(|coin| CoinBalanceResponseItem { token_id: token_id_a.clone(), denom: coin.denom.clone(), amount: Uint128::from(1u128), }) - .collect::>(); + .collect::>(); let user_b_response_items = user_b_coins .iter() - .map(|coin| AssetResponseItem { + .map(|coin| CoinBalanceResponseItem { token_id: token_id_b.clone(), denom: coin.denom.clone(), amount: Uint128::from(1u128), }) - .collect::>(); + .collect::>(); let user_c_response_items = user_c_coins .iter() - .map(|coin| AssetResponseItem { + .map(|coin| CoinBalanceResponseItem { token_id: token_id_c.clone(), denom: coin.denom.clone(), amount: Uint128::from(1u128), }) - .collect::>(); + .collect::>(); - let combined_starting_vals: Vec = user_a_response_items + let combined_starting_vals: Vec = user_a_response_items .iter() .cloned() .chain(user_b_response_items) diff --git a/contracts/credit-manager/tests/enumerate_debt_shares_test.rs b/contracts/credit-manager/tests/enumerate_debt_shares_test.rs index 17300763c..ec4360827 100644 --- a/contracts/credit-manager/tests/enumerate_debt_shares_test.rs +++ b/contracts/credit-manager/tests/enumerate_debt_shares_test.rs @@ -1,14 +1,14 @@ -use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; -use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; +use cosmwasm_std::{Addr, Coin, Uint128}; use cw_multi_test::{App, Executor}; +use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; use rover::msg::execute::Action; use rover::msg::query::SharesResponseItem; use rover::msg::{ExecuteMsg, QueryMsg}; use crate::helpers::{ - fund_red_bank, get_token_id, mock_create_credit_account, query_config, setup_credit_manager, - CoinInfo, + build_mock_coin_infos, fund_red_bank, get_token_id, mock_create_credit_account, query_config, + setup_credit_manager, CoinCreator, }; pub mod helpers; @@ -75,203 +75,12 @@ fn test_pagination_on_all_debt_shares_query_works() { .unwrap(); }); + let mock_coin_infos = build_mock_coin_infos(32); + let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![ - CoinInfo { - denom: "coin_1".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_2".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_3".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_4".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_5".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_6".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_7".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_8".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_9".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_10".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_11".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_12".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_13".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_14".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_15".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_16".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_17".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_18".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_19".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_20".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_21".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_22".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_23".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_24".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_25".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_26".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_27".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_28".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_29".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_30".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_31".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_32".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - ], + mock_coin_infos.clone(), vec![], ); @@ -280,40 +89,7 @@ fn test_pagination_on_all_debt_shares_query_works() { fund_red_bank( &mut app, config.red_bank.clone(), - vec![ - Coin::new(1000u128, "coin_1"), - Coin::new(1000u128, "coin_2"), - Coin::new(1000u128, "coin_3"), - Coin::new(1000u128, "coin_4"), - Coin::new(1000u128, "coin_5"), - Coin::new(1000u128, "coin_6"), - Coin::new(1000u128, "coin_7"), - Coin::new(1000u128, "coin_8"), - Coin::new(1000u128, "coin_9"), - Coin::new(1000u128, "coin_10"), - Coin::new(1000u128, "coin_11"), - Coin::new(1000u128, "coin_12"), - Coin::new(1000u128, "coin_13"), - Coin::new(1000u128, "coin_14"), - Coin::new(1000u128, "coin_15"), - Coin::new(1000u128, "coin_16"), - Coin::new(1000u128, "coin_17"), - Coin::new(1000u128, "coin_18"), - Coin::new(1000u128, "coin_19"), - Coin::new(1000u128, "coin_20"), - Coin::new(1000u128, "coin_21"), - Coin::new(1000u128, "coin_22"), - Coin::new(1000u128, "coin_23"), - Coin::new(1000u128, "coin_24"), - Coin::new(1000u128, "coin_25"), - Coin::new(1000u128, "coin_26"), - Coin::new(1000u128, "coin_27"), - Coin::new(1000u128, "coin_28"), - Coin::new(1000u128, "coin_29"), - Coin::new(1000u128, "coin_30"), - Coin::new(1000u128, "coin_31"), - Coin::new(1000u128, "coin_32"), - ], + mock_coin_infos.to_coins(1000), ); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); diff --git a/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs b/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs index 0d4ccecea..b2fb70b3c 100644 --- a/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs +++ b/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs @@ -1,14 +1,14 @@ -use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; -use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; +use cosmwasm_std::{Addr, Coin, Uint128}; use cw_multi_test::{App, Executor}; +use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; use rover::msg::execute::Action; use rover::msg::query::CoinShares; use rover::msg::{ExecuteMsg, QueryMsg}; use crate::helpers::{ - fund_red_bank, get_token_id, mock_create_credit_account, query_config, setup_credit_manager, - CoinInfo, + build_mock_coin_infos, fund_red_bank, get_token_id, mock_create_credit_account, query_config, + setup_credit_manager, CoinCreator, }; pub mod helpers; @@ -75,203 +75,12 @@ fn test_pagination_on_all_total_debt_shares_query_works() { .unwrap(); }); + let mock_coin_infos = build_mock_coin_infos(32); + let mock = setup_credit_manager( &mut app, &Addr::unchecked("owner"), - vec![ - CoinInfo { - denom: "coin_1".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_2".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_3".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_4".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_5".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_6".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_7".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_8".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_9".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_10".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_11".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_12".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_13".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_14".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_15".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_16".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_17".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_18".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_19".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_20".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_21".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_22".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_23".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_24".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_25".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_26".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_27".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_28".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_29".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_30".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_31".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - CoinInfo { - denom: "coin_32".to_string(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), - }, - ], + mock_coin_infos.clone(), vec![], ); @@ -280,40 +89,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { fund_red_bank( &mut app, config.red_bank.clone(), - vec![ - Coin::new(1000u128, "coin_1"), - Coin::new(1000u128, "coin_2"), - Coin::new(1000u128, "coin_3"), - Coin::new(1000u128, "coin_4"), - Coin::new(1000u128, "coin_5"), - Coin::new(1000u128, "coin_6"), - Coin::new(1000u128, "coin_7"), - Coin::new(1000u128, "coin_8"), - Coin::new(1000u128, "coin_9"), - Coin::new(1000u128, "coin_10"), - Coin::new(1000u128, "coin_11"), - Coin::new(1000u128, "coin_12"), - Coin::new(1000u128, "coin_13"), - Coin::new(1000u128, "coin_14"), - Coin::new(1000u128, "coin_15"), - Coin::new(1000u128, "coin_16"), - Coin::new(1000u128, "coin_17"), - Coin::new(1000u128, "coin_18"), - Coin::new(1000u128, "coin_19"), - Coin::new(1000u128, "coin_20"), - Coin::new(1000u128, "coin_21"), - Coin::new(1000u128, "coin_22"), - Coin::new(1000u128, "coin_23"), - Coin::new(1000u128, "coin_24"), - Coin::new(1000u128, "coin_25"), - Coin::new(1000u128, "coin_26"), - Coin::new(1000u128, "coin_27"), - Coin::new(1000u128, "coin_28"), - Coin::new(1000u128, "coin_29"), - Coin::new(1000u128, "coin_30"), - Coin::new(1000u128, "coin_31"), - Coin::new(1000u128, "coin_32"), - ], + mock_coin_infos.to_coins(1000), ); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); diff --git a/contracts/credit-manager/tests/health_test.rs b/contracts/credit-manager/tests/health_test.rs index ea63fdadf..b164d4262 100644 --- a/contracts/credit-manager/tests/health_test.rs +++ b/contracts/credit-manager/tests/health_test.rs @@ -63,13 +63,13 @@ fn test_only_assets_with_no_debts() { .unwrap(); let position = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(position.coin_assets.len(), 1); + assert_eq!(position.coins.len(), 1); assert_eq!(position.debt_shares.len(), 0); let health = query_health(&app, &mock.credit_manager, &token_id); let assets_value = coin_info.price * Decimal::from_atomics(deposit_amount, 0).unwrap(); - assert_eq!(health.assets_value, assets_value); - assert_eq!(health.debts_value, Decimal::zero()); + assert_eq!(health.total_assets_value, assets_value); + assert_eq!(health.total_debts_value, Decimal::zero()); assert_eq!(health.lqdt_health_factor, None); assert_eq!(health.max_ltv_health_factor, None); assert_eq!(health.liquidatable, false); @@ -138,17 +138,17 @@ fn test_terra_ragnarok() { .unwrap(); let position = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(position.coin_assets.len(), 1); + assert_eq!(position.coins.len(), 1); assert_eq!(position.debt_shares.len(), 1); let health = query_health(&app, &mock.credit_manager, &token_id); let assets_value = coin_info.price * Decimal::from_atomics(deposit_amount + borrow_amount, 0).unwrap(); - assert_eq!(health.assets_value, assets_value); + assert_eq!(health.total_assets_value, assets_value); // Note: Simulated yield from mock_red_bank makes debt position more expensive let debts_value = coin_info.price * Decimal::from_atomics(borrow_amount.add(Uint128::from(1u128)), 0).unwrap(); - assert_eq!(health.debts_value, debts_value); + assert_eq!(health.total_debts_value, debts_value); assert_eq!( health.lqdt_health_factor, Some(assets_value * coin_info.liquidation_threshold / debts_value) @@ -170,12 +170,12 @@ fn test_terra_ragnarok() { ); let position = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(position.coin_assets.len(), 1); + assert_eq!(position.coins.len(), 1); assert_eq!(position.debt_shares.len(), 1); let health = query_health(&app, &mock.credit_manager, &token_id); - assert_eq!(health.assets_value, Decimal::zero()); - assert_eq!(health.debts_value, Decimal::zero()); + assert_eq!(health.total_assets_value, Decimal::zero()); + assert_eq!(health.total_debts_value, Decimal::zero()); assert_eq!(health.lqdt_health_factor, None); assert_eq!(health.max_ltv_health_factor, None); assert_eq!(health.liquidatable, false); @@ -231,12 +231,12 @@ fn test_debts_no_assets() { let position = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(position.token_id, token_id); - assert_eq!(position.coin_assets.len(), 0); + assert_eq!(position.coins.len(), 0); assert_eq!(position.debt_shares.len(), 0); let health = query_health(&app, &mock.credit_manager, &token_id); - assert_eq!(health.assets_value, Decimal::zero()); - assert_eq!(health.debts_value, Decimal::zero()); + assert_eq!(health.total_assets_value, Decimal::zero()); + assert_eq!(health.total_debts_value, Decimal::zero()); assert_eq!(health.lqdt_health_factor, None); assert_eq!(health.max_ltv_health_factor, None); assert_eq!(health.liquidatable, false); @@ -309,14 +309,14 @@ fn test_cannot_borrow_more_than_healthy() { let position = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(position.token_id, token_id); - assert_eq!(position.coin_assets.len(), 1); + assert_eq!(position.coins.len(), 1); assert_eq!(position.debt_shares.len(), 1); let health = query_health(&app, &mock.credit_manager, &token_id); let assets_value = Decimal::from_atomics(82789u128, 2).unwrap(); - assert_eq!(health.assets_value, assets_value); + assert_eq!(health.total_assets_value, assets_value); let debts_value = Decimal::from_atomics(1206354u128, 4).unwrap(); - assert_eq!(health.debts_value, debts_value); + assert_eq!(health.total_debts_value, debts_value); assert_eq!( health.lqdt_health_factor, Some(assets_value * coin_info.liquidation_threshold / debts_value) @@ -354,9 +354,9 @@ fn test_cannot_borrow_more_than_healthy() { // All valid on step 2 as well (meaning step 3 did not go through) let health = query_health(&app, &mock.credit_manager, &token_id); let assets_value = Decimal::from_atomics(106443u128, 2).unwrap(); - assert_eq!(health.assets_value, assets_value); + assert_eq!(health.total_assets_value, assets_value); let debts_value = Decimal::from_atomics(3595408u128, 4).unwrap(); - assert_eq!(health.debts_value, debts_value); + assert_eq!(health.total_debts_value, debts_value); assert_eq!( health.lqdt_health_factor, Some(assets_value * coin_info.liquidation_threshold / debts_value) @@ -546,18 +546,18 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { let position = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(position.token_id, token_id); - assert_eq!(position.coin_assets.len(), 2); + assert_eq!(position.coins.len(), 2); assert_eq!(position.debt_shares.len(), 1); let health = query_health(&app, &mock.credit_manager, &token_id); let deposit_amount_dec = Decimal::from_atomics(deposit_amount, 0).unwrap(); let borrowed_amount_dec = Decimal::from_atomics(borrowed_amount, 0).unwrap(); assert_eq!( - health.assets_value, + health.total_assets_value, uosmo_info.price * deposit_amount_dec + uatom_info.price * borrowed_amount_dec ); assert_eq!( - health.debts_value, + health.total_debts_value, uatom_info.price * (borrowed_amount_dec + Decimal::one()) // simulated interest ); @@ -685,7 +685,7 @@ fn test_debt_value() { let position_a = query_position(&app, &mock.credit_manager, &token_id_a); assert_eq!(position_a.token_id, token_id_a); - assert_eq!(position_a.coin_assets.len(), 2); + assert_eq!(position_a.coins.len(), 2); assert_eq!(position_a.debt_shares.len(), 2); let health = query_health(&app, &mock.credit_manager, &token_id_a); @@ -738,7 +738,7 @@ fn test_debt_value() { let osmo_debt_value = uosmo_info.price * osmo_borrowed_amount_dec; let total_debt_value = user_a_owed_atom_value.add(osmo_debt_value); - assert_eq!(health.debts_value, total_debt_value); + assert_eq!(health.total_debts_value, total_debt_value); let user_a_deposit_amount_osmo_dec = Decimal::from_atomics(user_a_deposit_amount_osmo, 0).unwrap(); diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs new file mode 100644 index 000000000..46dee7fa0 --- /dev/null +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -0,0 +1,29 @@ +use crate::helpers::CoinInfo; +use cosmwasm_std::{Coin, Decimal, Uint128}; + +pub fn build_mock_coin_infos(count: usize) -> Vec { + (1..=count) + .into_iter() + .map(|i| CoinInfo { + denom: format!("coin_{}", i), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), + }) + .collect() +} + +pub trait CoinCreator { + fn to_coins(&self, amount: u128) -> Vec; +} + +impl CoinCreator for Vec { + fn to_coins(&self, amount: u128) -> Vec { + self.iter() + .map(|info| Coin { + denom: info.denom.clone(), + amount: Uint128::from(amount), + }) + .collect() + } +} diff --git a/contracts/credit-manager/tests/helpers/deploys.rs b/contracts/credit-manager/tests/helpers/deploys.rs index 793b48ee6..8b94adc8b 100644 --- a/contracts/credit-manager/tests/helpers/deploys.rs +++ b/contracts/credit-manager/tests/helpers/deploys.rs @@ -142,8 +142,8 @@ pub fn setup_credit_manager( .map(|item| item.denom.clone()) .collect(), allowed_vaults, - red_bank: RedBankBase(red_bank.to_string()), - oracle: OracleBase(oracle.to_string()), + red_bank: RedBankBase::new(red_bank.to_string()), + oracle: OracleBase::new(oracle.to_string()), }; let credit_manager = app diff --git a/contracts/credit-manager/tests/helpers/mod.rs b/contracts/credit-manager/tests/helpers/mod.rs index 86123ab8e..d356ad15e 100644 --- a/contracts/credit-manager/tests/helpers/mod.rs +++ b/contracts/credit-manager/tests/helpers/mod.rs @@ -1,10 +1,12 @@ pub use self::assertions::*; +pub use self::builders::*; pub use self::contracts::*; pub use self::deploys::*; pub use self::queries::*; pub use self::types::*; mod assertions; +mod builders; mod contracts; mod deploys; mod queries; diff --git a/contracts/credit-manager/tests/instantiate_test.rs b/contracts/credit-manager/tests/instantiate_test.rs index e046a8593..bbe8f7549 100644 --- a/contracts/credit-manager/tests/instantiate_test.rs +++ b/contracts/credit-manager/tests/instantiate_test.rs @@ -19,8 +19,8 @@ fn test_owner_set_on_instantiate() { owner: owner.to_string(), allowed_vaults: vec![], allowed_coins: vec![], - red_bank: RedBankBase("redbankaddr".to_string()), - oracle: OracleBase("oracle_contract".to_string()), + red_bank: RedBankBase::new("red_bank_contract".to_string()), + oracle: OracleBase::new("oracle_contract".to_string()), }; let contract_addr = app @@ -45,8 +45,8 @@ fn test_raises_on_invalid_owner_addr() { owner: owner.to_string(), allowed_vaults: vec![], allowed_coins: vec![], - red_bank: RedBankBase("redbankaddr".to_string()), - oracle: OracleBase("oracle_contract".to_string()), + red_bank: RedBankBase::new("red_bank_contract".to_string()), + oracle: OracleBase::new("oracle_contract".to_string()), }; let instantiate_res = app.instantiate_contract( @@ -77,8 +77,8 @@ fn test_nft_contract_addr_not_set_on_instantiate() { owner: owner.to_string(), allowed_vaults: vec![], allowed_coins: vec![], - red_bank: RedBankBase("redbankaddr".to_string()), - oracle: OracleBase("oracle_contract".to_string()), + red_bank: RedBankBase::new("red_bank_contract".to_string()), + oracle: OracleBase::new("oracle_contract".to_string()), }, &[], "manager-mock-account-nft", @@ -110,8 +110,8 @@ fn test_allowed_vaults_set_on_instantiate() { owner: owner.to_string(), allowed_vaults: allowed_vaults.clone(), allowed_coins: vec![], - red_bank: RedBankBase("redbankaddr".to_string()), - oracle: OracleBase("oracle_contract".to_string()), + red_bank: RedBankBase::new("red_bank_contract".to_string()), + oracle: OracleBase::new("oracle_contract".to_string()), }; let contract_addr = app @@ -149,8 +149,8 @@ fn test_raises_on_invalid_vaults_addr() { owner: owner.to_string(), allowed_vaults: vec!["%%%INVALID%%%".to_string()], allowed_coins: vec![], - red_bank: RedBankBase("redbankaddr".to_string()), - oracle: OracleBase("oracle_contract".to_string()), + red_bank: RedBankBase::new("red_bank_contract".to_string()), + oracle: OracleBase::new("oracle_contract".to_string()), }; let instantiate_res = app.instantiate_contract( @@ -184,8 +184,8 @@ fn test_allowed_coins_set_on_instantiate() { owner: owner.to_string(), allowed_vaults: vec![], allowed_coins: allowed_coins.clone(), - red_bank: RedBankBase("redbankaddr".to_string()), - oracle: OracleBase("oracle_contract".to_string()), + red_bank: RedBankBase::new("red_bank_contract".to_string()), + oracle: OracleBase::new("oracle_contract".to_string()), }; let contract_addr = app @@ -218,14 +218,14 @@ fn test_red_bank_set_on_instantiate() { let mut app = mock_app(); let code_id = app.store_code(mock_contract()); let owner = Addr::unchecked("owner"); - let red_bank_addr = "redbankaddr".to_string(); + let red_bank_addr = "red_bank_contract".to_string(); let msg = InstantiateMsg { owner: owner.to_string(), allowed_vaults: vec![], allowed_coins: vec![], - red_bank: RedBankBase("redbankaddr".to_string()), - oracle: OracleBase("oracle_contract".to_string()), + red_bank: RedBankBase::new("red_bank_contract".to_string()), + oracle: OracleBase::new("oracle_contract".to_string()), }; let contract_addr = app @@ -250,8 +250,8 @@ fn test_raises_on_invalid_red_bank_addr() { owner: owner.to_string(), allowed_coins: vec![], allowed_vaults: vec![], - red_bank: RedBankBase("%%%INVALID%%%".to_string()), - oracle: OracleBase("oracle_contract".to_string()), + red_bank: RedBankBase::new("%%%INVALID%%%".to_string()), + oracle: OracleBase::new("oracle_contract".to_string()), }; let instantiate_res = app.instantiate_contract( @@ -279,8 +279,8 @@ fn test_oracle_set_on_instantiate() { owner: owner.to_string(), allowed_coins: vec![], allowed_vaults: vec![], - red_bank: RedBankBase("redbankaddr".to_string()), - oracle: OracleBase("oracle_contract".to_string()), + red_bank: RedBankBase::new("red_bank_contract".to_string()), + oracle: OracleBase::new("oracle_contract".to_string()), }; let contract_addr = app @@ -305,8 +305,8 @@ fn test_raises_on_invalid_oracle_addr() { owner: owner.to_string(), allowed_vaults: vec![], allowed_coins: vec![], - red_bank: RedBankBase("redbankaddr".to_string()), - oracle: OracleBase("%%%INVALID%%%".to_string()), + red_bank: RedBankBase::new("red_bank_contract".to_string()), + oracle: OracleBase::new("%%%INVALID%%%".to_string()), }; let instantiate_res = app.instantiate_contract( diff --git a/contracts/credit-manager/tests/update_config_test.rs b/contracts/credit-manager/tests/update_config_test.rs index 69d0a2701..3d6ac1d11 100644 --- a/contracts/credit-manager/tests/update_config_test.rs +++ b/contracts/credit-manager/tests/update_config_test.rs @@ -24,10 +24,10 @@ fn test_update_config_works_with_full_config() { let nft_contract_addr = setup_nft_and_propose_owner(&mut app, &original_owner, &contract_addr); let new_owner = Addr::unchecked("new_owner"); - let new_red_bank = RedBankBase("new_red_bank".to_string()); + let new_red_bank = RedBankBase::new("new_red_bank".to_string()); let new_allowed_vaults = vec!["vaultcontract1".to_string()]; let new_allowed_assets = vec!["uosmo".to_string()]; - let new_oracle = OracleBase("new_oracle".to_string()); + let new_oracle = OracleBase::new("new_oracle".to_string()); app.execute_contract( original_owner.clone(), @@ -62,10 +62,10 @@ fn test_update_config_works_with_full_config() { assert_eq!(new_queried_allowed_assets, new_allowed_assets); assert_ne!(new_queried_allowed_assets, original_allowed_assets); - assert_eq!(new_config.red_bank, new_red_bank.0); + assert_eq!(&new_config.red_bank, new_red_bank.address()); assert_ne!(new_config.red_bank, original_config.red_bank); - assert_eq!(new_config.oracle, new_oracle.0); + assert_eq!(&new_config.oracle, new_oracle.address()); assert_ne!(new_config.oracle, original_config.oracle); } @@ -154,8 +154,8 @@ fn instantiate(app: &mut App, original_owner: &Addr, code_id: u64) -> Addr { owner: original_owner.to_string(), allowed_vaults: vec![], allowed_coins: vec![], - red_bank: RedBankBase("initial_red_bank".to_string()), - oracle: OracleBase("initial_oracle".to_string()), + red_bank: RedBankBase::new("initial_red_bank".to_string()), + oracle: OracleBase::new("initial_oracle".to_string()), }, &[], "mock_manager_contract", diff --git a/contracts/mock-oracle/src/contract.rs b/contracts/mock-oracle/src/contract.rs index 369bf8e8b..0b57915eb 100644 --- a/contracts/mock-oracle/src/contract.rs +++ b/contracts/mock-oracle/src/contract.rs @@ -47,8 +47,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } } -// TODO: After mars-core bumps to the next version https://crates.io/crates/mars-core (currently 1.0.0) -// should update this mock to return MarsDecimal: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/math/decimal.rs fn query_asset_price(deps: Deps, denom: String) -> StdResult { COIN_PRICE.load(deps.storage, denom) } diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs index 5181c2c5e..2b057e6fc 100644 --- a/contracts/mock-red-bank/src/msg.rs +++ b/contracts/mock-red-bank/src/msg.rs @@ -37,8 +37,6 @@ pub struct UserAssetDebtResponse { } // Schema reference: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/red_bank/mod.rs#L47 -// TODO: After mars-core bumps to the next version https://crates.io/crates/mars-core (currently 1.0.0) -// should update this mock to return MarsDecimal: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/math/decimal.rs #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Market { pub max_loan_to_value: Decimal, diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs index 2947f44c6..8503bb0ab 100644 --- a/packages/rover/src/adapters/oracle.rs +++ b/packages/rover/src/adapters/oracle.rs @@ -1,13 +1,21 @@ -use cosmwasm_std::{ - to_binary, Addr, Api, Decimal, QuerierWrapper, QueryRequest, StdResult, WasmQuery, -}; +use cosmwasm_std::{Addr, Api, Decimal, QuerierWrapper, StdResult}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use mock_oracle::msg::QueryMsg; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct OracleBase(pub T); +pub struct OracleBase(T); + +impl OracleBase { + pub fn new(address: T) -> OracleBase { + OracleBase(address) + } + + pub fn address(&self) -> &T { + &self.0 + } +} pub type OracleUnchecked = OracleBase; pub type Oracle = OracleBase; @@ -20,18 +28,17 @@ impl From for OracleUnchecked { impl OracleUnchecked { pub fn check(&self, api: &dyn Api) -> StdResult { - Ok(OracleBase(api.addr_validate(&self.0)?)) + Ok(OracleBase::new(api.addr_validate(&self.0)?)) } } impl Oracle { pub fn query_price(&self, querier: &QuerierWrapper, denom: &str) -> StdResult { - let response: Decimal = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: self.0.to_string(), - msg: to_binary(&QueryMsg::AssetPrice { + querier.query_wasm_smart( + self.0.to_string(), + &QueryMsg::AssetPrice { denom: denom.to_string(), - })?, - }))?; - Ok(response) + }, + ) } } diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index 7ff990ef1..d95e5bcd4 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -8,7 +8,17 @@ use serde::{Deserialize, Serialize}; use mock_red_bank::msg::{ExecuteMsg, QueryMsg, UserAssetDebtResponse}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct RedBankBase(pub T); +pub struct RedBankBase(T); + +impl RedBankBase { + pub fn new(address: T) -> RedBankBase { + RedBankBase(address) + } + + pub fn address(&self) -> &T { + &self.0 + } +} pub type RedBankUnchecked = RedBankBase; pub type RedBank = RedBankBase; diff --git a/packages/rover/src/health.rs b/packages/rover/src/health.rs index d4e56be3f..14c44e02d 100644 --- a/packages/rover/src/health.rs +++ b/packages/rover/src/health.rs @@ -5,9 +5,9 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Health { /// Total value of assets - pub assets_value: Decimal, + pub total_assets_value: Decimal, /// Total value of debts - pub debts_value: Decimal, + pub total_debts_value: Decimal, /// The sum of the value of all assets (multiplied by their liquidation threshold) over the /// sum of the value of all debts. Main health factor used throughout app. pub lqdt_health_factor: Option, diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index dfbbfeb3e..2c87a5126 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -21,9 +21,9 @@ pub enum QueryMsg { Position { token_id: String }, /// The health of the entire position represented by token. Response type: `Health` Health { token_id: String }, - /// Enumerate assets for all token positions. Response type: `Vec` + /// Enumerate coin balances for all token positions. Response type: `Vec` /// start_after accepts (token_id, denom) - AllAssets { + AllCoinBalances { start_after: Option<(String, String)>, limit: Option, }, @@ -45,7 +45,7 @@ pub enum QueryMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub struct AssetResponseItem { +pub struct CoinBalanceResponseItem { pub token_id: String, pub denom: String, pub amount: Uint128, @@ -72,7 +72,7 @@ pub struct CoinValue { pub denom: String, pub amount: Uint128, pub price: Decimal, - pub total_value: Decimal, + pub value: Decimal, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -88,8 +88,8 @@ pub struct DebtSharesValue { pub struct PositionResponse { /// Unique NFT token id that represents the cross-margin account. The owner of this NFT, owns the account. pub token_id: String, - /// All coin asset positions with its value - pub coin_assets: Vec, + /// All coin balances with its value + pub coins: Vec, /// All debt positions with its value pub debt_shares: Vec, } From b686afdfb4a6d46763da36a4fc06dbfea1e045ee Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 16 Aug 2022 10:43:54 +0200 Subject: [PATCH 047/218] Updates from clippy (#13) --- Makefile.toml | 2 +- contracts/account-nft/src/msg/query.rs | 2 +- contracts/account-nft/tests/mint_test.rs | 13 +-- contracts/credit-manager/tests/borrow_test.rs | 24 +++--- .../tests/create_credit_account_test.rs | 2 +- .../credit-manager/tests/deposit_test.rs | 21 +++-- .../tests/enumerate_allowed_coins_test.rs | 8 +- .../tests/enumerate_allowed_vaults_test.rs | 8 +- .../tests/enumerate_coin_balances_test.rs | 4 +- .../tests/enumerate_debt_shares_test.rs | 18 ++--- .../tests/enumerate_total_debt_shares_test.rs | 24 +++--- contracts/credit-manager/tests/health_test.rs | 79 +++++++++---------- .../credit-manager/tests/helpers/deploys.rs | 10 +-- .../credit-manager/tests/helpers/queries.rs | 20 ++--- .../credit-manager/tests/helpers/types.rs | 4 +- .../credit-manager/tests/instantiate_test.rs | 56 ++++--------- .../tests/update_config_test.rs | 20 ++--- contracts/mock-oracle/src/msg.rs | 8 +- contracts/mock-red-bank/src/msg.rs | 10 +-- packages/rover/src/adapters/oracle.rs | 2 +- packages/rover/src/adapters/red_bank.rs | 2 +- packages/rover/src/coins.rs | 2 +- packages/rover/src/health.rs | 2 +- packages/rover/src/msg/instantiate.rs | 4 +- packages/rover/src/msg/query.rs | 16 ++-- 25 files changed, 157 insertions(+), 204 deletions(-) diff --git a/Makefile.toml b/Makefile.toml index f8fcd34d3..60474bdd7 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -26,7 +26,7 @@ args = ["fmt", "--all", "--check"] [tasks.clippy] command = "cargo" -args = ["clippy", "--", "-D", "warnings"] +args = ["clippy", "--tests", "--", "-D", "warnings"] [tasks.all-github-actions] dependencies = [ diff --git a/contracts/account-nft/src/msg/query.rs b/contracts/account-nft/src/msg/query.rs index 0c3a5ddee..a66917cf0 100644 --- a/contracts/account-nft/src/msg/query.rs +++ b/contracts/account-nft/src/msg/query.rs @@ -5,7 +5,7 @@ use cw721_base::QueryMsg as ParentQueryMsg; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { //-------------------------------------------------------------------------------------------------- diff --git a/contracts/account-nft/tests/mint_test.rs b/contracts/account-nft/tests/mint_test.rs index 6f5420b8d..bdcea23c7 100644 --- a/contracts/account-nft/tests/mint_test.rs +++ b/contracts/account-nft/tests/mint_test.rs @@ -66,18 +66,13 @@ fn test_normal_base_cw721_actions_can_still_be_taken() { } // Double checking ownership by querying NFT account-nft for correct owner -fn assert_owner_is_correct( - app: &mut BasicApp, - contract_addr: &Addr, - user: &Addr, - token_id: &String, -) { +fn assert_owner_is_correct(app: &mut BasicApp, contract_addr: &Addr, user: &Addr, token_id: &str) { let owner_res: OwnerOfResponse = app .wrap() .query_wasm_smart( contract_addr, &QueryMsg::OwnerOf { - token_id: token_id.clone(), + token_id: token_id.to_string(), include_expired: None, }, ) @@ -87,12 +82,12 @@ fn assert_owner_is_correct( } fn get_token_id(res: AppResponse) -> String { - let attr: Vec<&String> = res + let attr: Vec<&str> = res .events .iter() .flat_map(|event| &event.attributes) .filter(|attr| attr.key == "token_id") - .map(|attr| &attr.value) + .map(|attr| attr.value.as_str()) .collect(); assert_eq!(attr.len(), 1); diff --git a/contracts/credit-manager/tests/borrow_test.rs b/contracts/credit-manager/tests/borrow_test.rs index d58c4a053..f35358d25 100644 --- a/contracts/credit-manager/tests/borrow_test.rs +++ b/contracts/credit-manager/tests/borrow_test.rs @@ -67,14 +67,14 @@ fn test_can_only_borrow_what_is_whitelisted() { let mock = setup_credit_manager(&mut app, &owner, vec![coin_info], vec![]); let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &mock.credit_manager.clone(), &user).unwrap(); + let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); let res = app.execute_contract( user.clone(), - mock.credit_manager.clone(), + mock.credit_manager, &UpdateCreditAccount { - token_id: token_id.clone(), + token_id, actions: vec![Borrow(Coin { denom: "usomething".to_string(), amount: Uint128::from(234u128), @@ -121,7 +121,7 @@ fn test_borrowing_zero_does_nothing() { assert_err(res, ContractError::NoAmount); - let position = query_position(&mut app, &mock.credit_manager, &token_id); + let position = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(position.coins.len(), 0); assert_eq!(position.debt_shares.len(), 0); } @@ -155,7 +155,7 @@ fn test_success_when_new_debt_asset() { let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); - let config = query_config(&mut app, &mock.credit_manager.clone()); + let config = query_config(&app, &mock.credit_manager.clone()); fund_red_bank( &mut app, @@ -163,7 +163,7 @@ fn test_success_when_new_debt_asset() { vec![Coin::new(1000u128, coin_info.denom.clone())], ); - let position = query_position(&mut app, &mock.credit_manager, &token_id); + let position = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(position.coins.len(), 0); assert_eq!(position.debt_shares.len(), 0); app.execute_contract( @@ -186,7 +186,7 @@ fn test_success_when_new_debt_asset() { ) .unwrap(); - let position = query_position(&mut app, &mock.credit_manager, &token_id); + let position = query_position(&app, &mock.credit_manager, &token_id); assert_eq!(position.coins.len(), 1); let asset_res = position.coins.first().unwrap(); assert_eq!( @@ -281,7 +281,7 @@ fn test_debt_shares_with_debt_amount() { let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_b).unwrap(); let token_id_b = get_token_id(res); - let config = query_config(&mut app, &mock.credit_manager.clone()); + let config = query_config(&app, &mock.credit_manager.clone()); fund_red_bank( &mut app, @@ -304,7 +304,7 @@ fn test_debt_shares_with_debt_amount() { .unwrap(); let interim_red_bank_debt = query_red_bank_debt( - &mut app, + &app, &mock.credit_manager, &config.red_bank, &coin_info.denom, @@ -325,7 +325,7 @@ fn test_debt_shares_with_debt_amount() { .unwrap(); let token_a_shares = Uint128::from(50u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED); - let position = query_position(&mut app, &mock.credit_manager, &token_id_a); + let position = query_position(&app, &mock.credit_manager, &token_id_a); let debt_position_a = position.debt_shares.first().unwrap(); assert_eq!(debt_position_a.shares, token_a_shares.clone()); assert_eq!(debt_position_a.denom, coin_info.denom); @@ -333,7 +333,7 @@ fn test_debt_shares_with_debt_amount() { let token_b_shares = Uint128::from(50u128) .mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) .multiply_ratio(Uint128::from(50u128), interim_red_bank_debt.amount); - let position = query_position(&mut app, &mock.credit_manager, &token_id_b); + let position = query_position(&app, &mock.credit_manager, &token_id_b); let debt_position_b = position.debt_shares.first().unwrap(); assert_eq!(debt_position_b.shares, token_b_shares.clone()); assert_eq!(debt_position_b.denom, coin_info.denom); @@ -351,7 +351,7 @@ fn test_debt_shares_with_debt_amount() { ); let red_bank_debt = query_red_bank_debt( - &mut app, + &app, &mock.credit_manager, &config.red_bank, &coin_info.denom, diff --git a/contracts/credit-manager/tests/create_credit_account_test.rs b/contracts/credit-manager/tests/create_credit_account_test.rs index d2fea1493..00a1a1b4c 100644 --- a/contracts/credit-manager/tests/create_credit_account_test.rs +++ b/contracts/credit-manager/tests/create_credit_account_test.rs @@ -100,7 +100,7 @@ fn test_create_credit_account() { .query_wasm_smart( config_res.account_nft.unwrap(), &NftQueryMsg::OwnerOf { - token_id: token_id.to_string(), + token_id, include_expired: None, }, ) diff --git a/contracts/credit-manager/tests/deposit_test.rs b/contracts/credit-manager/tests/deposit_test.rs index d2ed3a9ab..b08d4fb26 100644 --- a/contracts/credit-manager/tests/deposit_test.rs +++ b/contracts/credit-manager/tests/deposit_test.rs @@ -114,7 +114,7 @@ fn test_deposit_but_no_funds() { mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { token_id: token_id.clone(), - actions: vec![Action::Deposit(coin_info.to_coin(deposit_amount.clone()))], + actions: vec![Action::Deposit(coin_info.to_coin(deposit_amount))], }, &[], ); @@ -165,10 +165,10 @@ fn test_deposit_but_not_enough_funds() { user.clone(), mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), + token_id, actions: vec![Action::Deposit(coin_info.to_coin(Uint128::from(350u128)))], }, - &[Coin::new(250u128, coin_info.denom.clone())], + &[Coin::new(250u128, coin_info.denom)], ); assert_err( @@ -221,7 +221,7 @@ fn test_can_only_deposit_allowed_assets() { token_id: token_id.clone(), actions: vec![Action::Deposit(not_allowed_coin.clone())], }, - &[Coin::new(234u128, coin_info.denom.clone())], + &[Coin::new(234u128, coin_info.denom)], ); assert_err(res, NotWhitelisted(not_allowed_coin.denom)); @@ -269,7 +269,7 @@ fn test_extra_funds_received() { let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); - let extra_funds = Coin::new(25u128, uatom_info.denom.clone()); + let extra_funds = Coin::new(25u128, uatom_info.denom); let res = app.execute_contract( user.clone(), mock.credit_manager.clone(), @@ -277,10 +277,7 @@ fn test_extra_funds_received() { token_id: token_id.clone(), actions: vec![Action::Deposit(uosmo_info.to_coin(Uint128::from(234u128)))], }, - &[ - Coin::new(234u128, uosmo_info.denom.clone()), - extra_funds.clone(), - ], + &[Coin::new(234u128, uosmo_info.denom), extra_funds.clone()], ); assert_err(res, ExtraFundsReceived(Coins::from(vec![extra_funds]))); @@ -345,7 +342,7 @@ fn test_deposit_success() { let coin = app .wrap() - .query_balance(mock.credit_manager, coin_info.denom.clone()) + .query_balance(mock.credit_manager, coin_info.denom) .unwrap(); assert_eq!(coin.amount, deposit_amount) } @@ -418,7 +415,7 @@ fn test_multiple_deposit_actions() { let coin = app .wrap() - .query_balance(mock.credit_manager.clone(), uosmo_info.denom.clone()) + .query_balance(mock.credit_manager.clone(), uosmo_info.denom) .unwrap(); assert_eq!(coin.amount, uosmo_amount); @@ -432,6 +429,6 @@ fn test_multiple_deposit_actions() { fn assert_present(res: &PositionResponse, coin: &CoinInfo, amount: Uint128, total_val: Decimal) { res.coins .iter() - .find(|item| item.denom == coin.denom && &item.amount == &amount && item.value == total_val) + .find(|item| item.denom == coin.denom && item.amount == amount && item.value == total_val) .unwrap(); } diff --git a/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs b/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs index a85bd4bfc..4f8594ea2 100644 --- a/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs +++ b/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs @@ -58,7 +58,7 @@ fn test_pagination_on_allowed_coins_query_works() { }; let contract_addr = app - .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) + .instantiate_contract(code_id, owner, &msg, &[], "mock-contract", None) .unwrap(); let coins_res: Vec = app @@ -67,7 +67,7 @@ fn test_pagination_on_allowed_coins_query_works() { contract_addr.clone(), &QueryMsg::AllowedCoins { start_after: None, - limit: Some(58 as u32), + limit: Some(58u32), }, ) .unwrap(); @@ -81,7 +81,7 @@ fn test_pagination_on_allowed_coins_query_works() { contract_addr.clone(), &QueryMsg::AllowedCoins { start_after: None, - limit: Some(2 as u32), + limit: Some(2u32), }, ) .unwrap(); @@ -125,7 +125,7 @@ fn test_pagination_on_allowed_coins_query_works() { let coins_res_d: Vec = app .wrap() .query_wasm_smart( - contract_addr.clone(), + contract_addr, &QueryMsg::AllowedCoins { start_after: Some(coins_res_c.last().unwrap().clone()), limit: None, diff --git a/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs b/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs index 8132fde35..e463c5cfc 100644 --- a/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs +++ b/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs @@ -58,7 +58,7 @@ fn test_pagination_on_allowed_vaults_query_works() { }; let contract_addr = app - .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-contract", None) + .instantiate_contract(code_id, owner, &msg, &[], "mock-contract", None) .unwrap(); let vaults_res: Vec = app @@ -67,7 +67,7 @@ fn test_pagination_on_allowed_vaults_query_works() { contract_addr.clone(), &QueryMsg::AllowedVaults { start_after: None, - limit: Some(58 as u32), + limit: Some(58u32), }, ) .unwrap(); @@ -81,7 +81,7 @@ fn test_pagination_on_allowed_vaults_query_works() { contract_addr.clone(), &QueryMsg::AllowedVaults { start_after: None, - limit: Some(2 as u32), + limit: Some(2u32), }, ) .unwrap(); @@ -125,7 +125,7 @@ fn test_pagination_on_allowed_vaults_query_works() { let vaults_res_d: Vec = app .wrap() .query_wasm_smart( - contract_addr.clone(), + contract_addr, &QueryMsg::AllowedVaults { start_after: Some(vaults_res_c.last().unwrap().clone()), limit: None, diff --git a/contracts/credit-manager/tests/enumerate_coin_balances_test.rs b/contracts/credit-manager/tests/enumerate_coin_balances_test.rs index 5f161da01..dab84d73c 100644 --- a/contracts/credit-manager/tests/enumerate_coin_balances_test.rs +++ b/contracts/credit-manager/tests/enumerate_coin_balances_test.rs @@ -131,7 +131,7 @@ fn test_pagination_on_all_coin_balances_query_works() { mock.credit_manager.clone(), &QueryMsg::AllCoinBalances { start_after: None, - limit: Some(58 as u32), + limit: Some(58u32), }, ) .unwrap(); @@ -145,7 +145,7 @@ fn test_pagination_on_all_coin_balances_query_works() { mock.credit_manager.clone(), &QueryMsg::AllCoinBalances { start_after: None, - limit: Some(2 as u32), + limit: Some(2u32), }, ) .unwrap(); diff --git a/contracts/credit-manager/tests/enumerate_debt_shares_test.rs b/contracts/credit-manager/tests/enumerate_debt_shares_test.rs index ec4360827..70cc4d21f 100644 --- a/contracts/credit-manager/tests/enumerate_debt_shares_test.rs +++ b/contracts/credit-manager/tests/enumerate_debt_shares_test.rs @@ -84,13 +84,9 @@ fn test_pagination_on_all_debt_shares_query_works() { vec![], ); - let config = query_config(&mut app, &mock.credit_manager.clone()); + let config = query_config(&app, &mock.credit_manager.clone()); - fund_red_bank( - &mut app, - config.red_bank.clone(), - mock_coin_infos.to_coins(1000), - ); + fund_red_bank(&mut app, config.red_bank, mock_coin_infos.to_coins(1000)); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); let token_id_a = get_token_id(res); @@ -170,7 +166,7 @@ fn test_pagination_on_all_debt_shares_query_works() { mock.credit_manager.clone(), &QueryMsg::AllDebtShares { start_after: None, - limit: Some(58 as u32), + limit: Some(58u32), }, ) .unwrap(); @@ -184,7 +180,7 @@ fn test_pagination_on_all_debt_shares_query_works() { mock.credit_manager.clone(), &QueryMsg::AllDebtShares { start_after: None, - limit: Some(2 as u32), + limit: Some(2u32), }, ) .unwrap(); @@ -265,7 +261,7 @@ fn test_pagination_on_all_debt_shares_query_works() { .map(|coin| SharesResponseItem { token_id: token_id_a.clone(), denom: coin.denom.clone(), - shares: Uint128::from(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED), + shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, }) .collect::>(); @@ -274,7 +270,7 @@ fn test_pagination_on_all_debt_shares_query_works() { .map(|coin| SharesResponseItem { token_id: token_id_b.clone(), denom: coin.denom.clone(), - shares: Uint128::from(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED), + shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, }) .collect::>(); @@ -283,7 +279,7 @@ fn test_pagination_on_all_debt_shares_query_works() { .map(|coin| SharesResponseItem { token_id: token_id_c.clone(), denom: coin.denom.clone(), - shares: Uint128::from(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED), + shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, }) .collect::>(); diff --git a/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs b/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs index b2fb70b3c..346010f3f 100644 --- a/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs +++ b/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs @@ -84,13 +84,9 @@ fn test_pagination_on_all_total_debt_shares_query_works() { vec![], ); - let config = query_config(&mut app, &mock.credit_manager.clone()); + let config = query_config(&app, &mock.credit_manager.clone()); - fund_red_bank( - &mut app, - config.red_bank.clone(), - mock_coin_infos.to_coins(1000), - ); + fund_red_bank(&mut app, config.red_bank, mock_coin_infos.to_coins(1000)); let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); let token_id_a = get_token_id(res); @@ -98,7 +94,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { user_a.clone(), mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_a.clone(), + token_id: token_id_a, actions: user_a_coins .iter() .flat_map(|coin| { @@ -122,7 +118,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { user_b.clone(), mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_b.clone(), + token_id: token_id_b, actions: user_b_coins .iter() .flat_map(|coin| { @@ -146,7 +142,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { user_c.clone(), mock.credit_manager.clone(), &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_c.clone(), + token_id: token_id_c, actions: user_c_coins .iter() .flat_map(|coin| { @@ -170,7 +166,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { mock.credit_manager.clone(), &QueryMsg::AllTotalDebtShares { start_after: None, - limit: Some(58 as u32), + limit: Some(58u32), }, ) .unwrap(); @@ -184,7 +180,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { mock.credit_manager.clone(), &QueryMsg::AllTotalDebtShares { start_after: None, - limit: Some(2 as u32), + limit: Some(2u32), }, ) .unwrap(); @@ -258,7 +254,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { .iter() .map(|coin| CoinShares { denom: coin.denom.clone(), - shares: Uint128::from(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED), + shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, }) .collect::>(); @@ -266,7 +262,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { .iter() .map(|coin| CoinShares { denom: coin.denom.clone(), - shares: Uint128::from(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED), + shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, }) .collect::>(); @@ -274,7 +270,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { .iter() .map(|coin| CoinShares { denom: coin.denom.clone(), - shares: Uint128::from(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED), + shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, }) .collect::>(); diff --git a/contracts/credit-manager/tests/health_test.rs b/contracts/credit-manager/tests/health_test.rs index b164d4262..d9ab2d99f 100644 --- a/contracts/credit-manager/tests/health_test.rs +++ b/contracts/credit-manager/tests/health_test.rs @@ -72,8 +72,8 @@ fn test_only_assets_with_no_debts() { assert_eq!(health.total_debts_value, Decimal::zero()); assert_eq!(health.lqdt_health_factor, None); assert_eq!(health.max_ltv_health_factor, None); - assert_eq!(health.liquidatable, false); - assert_eq!(health.above_max_ltv, false); + assert!(!health.liquidatable); + assert!(!health.above_max_ltv); } /// Step 1: User deposits 12 luna (100 price) and borrows 2 luna @@ -113,11 +113,11 @@ fn test_terra_ragnarok() { let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); - let config = query_config(&mut app, &mock.credit_manager.clone()); + let config = query_config(&app, &mock.credit_manager.clone()); fund_red_bank( &mut app, - config.red_bank.clone(), + config.red_bank, vec![Coin::new(1000u128, "uluna")], ); @@ -157,8 +157,8 @@ fn test_terra_ragnarok() { health.max_ltv_health_factor, Some(assets_value * coin_info.max_ltv / debts_value) ); - assert_eq!(health.liquidatable, false); - assert_eq!(health.above_max_ltv, false); + assert!(!health.liquidatable); + assert!(!health.above_max_ltv); price_change( &mut app, @@ -178,8 +178,8 @@ fn test_terra_ragnarok() { assert_eq!(health.total_debts_value, Decimal::zero()); assert_eq!(health.lqdt_health_factor, None); assert_eq!(health.max_ltv_health_factor, None); - assert_eq!(health.liquidatable, false); - assert_eq!(health.above_max_ltv, false); + assert!(!health.liquidatable); + assert!(!health.above_max_ltv); } /// Action: User borrows 100 osmo (at price of 1). Zero deposits. @@ -208,11 +208,11 @@ fn test_debts_no_assets() { let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); - let config = query_config(&mut app, &mock.credit_manager.clone()); + let config = query_config(&app, &mock.credit_manager.clone()); fund_red_bank( &mut app, - config.red_bank.clone(), + config.red_bank, vec![Coin::new(1000u128, coin_info.denom.clone())], ); @@ -239,8 +239,8 @@ fn test_debts_no_assets() { assert_eq!(health.total_debts_value, Decimal::zero()); assert_eq!(health.lqdt_health_factor, None); assert_eq!(health.max_ltv_health_factor, None); - assert_eq!(health.liquidatable, false); - assert_eq!(health.above_max_ltv, false); + assert!(!health.liquidatable); + assert!(!health.above_max_ltv); } /// Step 1: User deposits 300 osmo and borrows 50 (at price of 2.3654) @@ -282,11 +282,11 @@ fn test_cannot_borrow_more_than_healthy() { let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); - let config = query_config(&mut app, &mock.credit_manager.clone()); + let config = query_config(&app, &mock.credit_manager.clone()); fund_red_bank( &mut app, - config.red_bank.clone(), + config.red_bank, vec![Coin::new(1000u128, coin_info.denom.clone())], ); @@ -325,8 +325,8 @@ fn test_cannot_borrow_more_than_healthy() { health.max_ltv_health_factor, Some(assets_value * coin_info.max_ltv / debts_value) ); - assert_eq!(health.liquidatable, false); - assert_eq!(health.above_max_ltv, false); + assert!(!health.liquidatable); + assert!(!health.above_max_ltv); app.execute_contract( user.clone(), @@ -365,8 +365,8 @@ fn test_cannot_borrow_more_than_healthy() { health.max_ltv_health_factor, Some(assets_value * coin_info.max_ltv / debts_value) ); - assert_eq!(health.liquidatable, false); - assert_eq!(health.above_max_ltv, false); + assert!(!health.liquidatable); + assert!(!health.above_max_ltv); } /// Step 1: User deposits 300 osmo (2.3654) and borrows 50 atom (price 10.2) @@ -414,11 +414,11 @@ fn test_cannot_borrow_more_but_not_liquidatable() { let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); - let config = query_config(&mut app, &mock.credit_manager.clone()); + let config = query_config(&app, &mock.credit_manager.clone()); fund_red_bank( &mut app, - config.red_bank.clone(), + config.red_bank, vec![Coin::new(1000u128, "uatom")], ); @@ -437,8 +437,8 @@ fn test_cannot_borrow_more_but_not_liquidatable() { .unwrap(); let health = query_health(&app, &mock.credit_manager, &token_id); - assert_eq!(health.liquidatable, false); - assert_eq!(health.above_max_ltv, false); + assert!(!health.liquidatable); + assert!(!health.above_max_ltv); price_change( &mut app, @@ -450,8 +450,8 @@ fn test_cannot_borrow_more_but_not_liquidatable() { ); let health = query_health(&app, &mock.credit_manager, &token_id); - assert_eq!(health.liquidatable, false); - assert_eq!(health.above_max_ltv, true); + assert!(!health.liquidatable); + assert!(health.above_max_ltv); let res = app.execute_contract( user.clone(), @@ -475,8 +475,8 @@ fn test_cannot_borrow_more_but_not_liquidatable() { ); let health = query_health(&app, &mock.credit_manager, &token_id); - assert_eq!(health.liquidatable, true); - assert_eq!(health.above_max_ltv, true); + assert!(health.liquidatable); + assert!(health.above_max_ltv); } /// Actions: User deposits 300 osmo (5265478965.412365487125 price) @@ -519,11 +519,11 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); let token_id = get_token_id(res); - let config = query_config(&mut app, &mock.credit_manager.clone()); + let config = query_config(&app, &mock.credit_manager.clone()); fund_red_bank( &mut app, - config.red_bank.clone(), + config.red_bank, vec![Coin::new(1000u128, "uatom")], ); @@ -580,8 +580,8 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { .div(uatom_info.price.mul(borrowed_amount_dec + Decimal::one())) ) ); - assert_eq!(health.liquidatable, false); - assert_eq!(health.above_max_ltv, false); + assert!(!health.liquidatable); + assert!(!health.above_max_ltv); } /// User A: Borrows 30 osmo @@ -632,7 +632,7 @@ fn test_debt_value() { let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_b).unwrap(); let token_id_b = get_token_id(res); - let config = query_config(&mut app, &mock.credit_manager.clone()); + let config = query_config(&app, &mock.credit_manager.clone()); fund_red_bank( &mut app, @@ -660,7 +660,7 @@ fn test_debt_value() { .unwrap(); let interim_red_bank_debt = query_red_bank_debt( - &mut app, + &app, &mock.credit_manager, &config.red_bank, &uatom_info.denom, @@ -689,11 +689,11 @@ fn test_debt_value() { assert_eq!(position_a.debt_shares.len(), 2); let health = query_health(&app, &mock.credit_manager, &token_id_a); - assert_eq!(health.above_max_ltv, false); - assert_eq!(health.liquidatable, false); + assert!(!health.above_max_ltv); + assert!(!health.liquidatable); let red_bank_atom_debt = query_red_bank_debt( - &mut app, + &app, &mock.credit_manager, &config.red_bank, &uatom_info.denom, @@ -768,7 +768,7 @@ fn test_debt_value() { ); } -fn price_change(app: &mut BasicApp, mock: &MockEnv, coin: CoinPrice) -> () { +fn price_change(app: &mut BasicApp, mock: &MockEnv, coin: CoinPrice) { app.execute_contract( Addr::unchecked("anyone"), mock.oracle.clone(), @@ -778,9 +778,6 @@ fn price_change(app: &mut BasicApp, mock: &MockEnv, coin: CoinPrice) -> () { .unwrap(); } -fn find_by_denom<'a>(denom: &'a str, shares: &'a Vec) -> &'a DebtSharesValue { - shares - .iter() - .find(|item| item.denom == denom.to_string()) - .unwrap() +fn find_by_denom<'a>(denom: &'a str, shares: &'a [DebtSharesValue]) -> &'a DebtSharesValue { + shares.iter().find(|item| item.denom == *denom).unwrap() } diff --git a/contracts/credit-manager/tests/helpers/deploys.rs b/contracts/credit-manager/tests/helpers/deploys.rs index 8b94adc8b..0266192e4 100644 --- a/contracts/credit-manager/tests/helpers/deploys.rs +++ b/contracts/credit-manager/tests/helpers/deploys.rs @@ -71,11 +71,11 @@ pub fn setup_nft_contract(app: &mut App, owner: &Addr, manager_contract_addr: &A ) .unwrap(); - transfer_nft_contract_ownership(app, owner, &nft_contract_addr, &manager_contract_addr); + transfer_nft_contract_ownership(app, owner, &nft_contract_addr, manager_contract_addr); nft_contract_addr } -pub fn setup_oracle(app: &mut App, coins: &Vec) -> Addr { +pub fn setup_oracle(app: &mut App, coins: &[CoinInfo]) -> Addr { let contract_code_id = app.store_code(mock_oracle_contract()); app.instantiate_contract( contract_code_id, @@ -96,7 +96,7 @@ pub fn setup_oracle(app: &mut App, coins: &Vec) -> Addr { .unwrap() } -pub fn setup_red_bank(app: &mut App, coins: &Vec) -> Addr { +pub fn setup_red_bank(app: &mut App, coins: &[CoinInfo]) -> Addr { let contract_code_id = app.store_code(mock_red_bank_contract()); app.instantiate_contract( contract_code_id, @@ -127,7 +127,7 @@ pub fn fund_red_bank(app: &mut BasicApp, red_bank_addr: String, funds: Vec } pub fn setup_credit_manager( - mut app: &mut App, + app: &mut App, owner: &Addr, allowed_coins: Vec, allowed_vaults: Vec, @@ -157,7 +157,7 @@ pub fn setup_credit_manager( ) .unwrap(); - let nft = setup_nft_contract(&mut app, &owner, &credit_manager); + let nft = setup_nft_contract(app, owner, &credit_manager); MockEnv { credit_manager, oracle, diff --git a/contracts/credit-manager/tests/helpers/queries.rs b/contracts/credit-manager/tests/helpers/queries.rs index 4194ae94f..ea7fc5c17 100644 --- a/contracts/credit-manager/tests/helpers/queries.rs +++ b/contracts/credit-manager/tests/helpers/queries.rs @@ -7,52 +7,48 @@ use rover::health::Health; use rover::msg::query::{ConfigResponse, PositionResponse, QueryMsg}; pub fn get_token_id(res: AppResponse) -> String { - let attr: Vec<&String> = res + let attr: Vec<&str> = res .events .iter() .flat_map(|event| &event.attributes) .filter(|attr| attr.key == "token_id") - .map(|attr| &attr.value) + .map(|attr| attr.value.as_str()) .collect(); assert_eq!(attr.len(), 1); attr.first().unwrap().to_string() } -pub fn query_position( - app: &App, - manager_contract_addr: &Addr, - token_id: &String, -) -> PositionResponse { +pub fn query_position(app: &App, manager_contract_addr: &Addr, token_id: &str) -> PositionResponse { app.wrap() .query_wasm_smart( manager_contract_addr.clone(), &QueryMsg::Position { - token_id: token_id.clone(), + token_id: token_id.to_string(), }, ) .unwrap() } -pub fn query_health(app: &App, manager_contract_addr: &Addr, token_id: &String) -> Health { +pub fn query_health(app: &App, manager_contract_addr: &Addr, token_id: &str) -> Health { app.wrap() .query_wasm_smart( manager_contract_addr.clone(), &QueryMsg::Health { - token_id: token_id.clone(), + token_id: token_id.to_string(), }, ) .unwrap() } -pub fn query_config(app: &mut App, contract_addr: &Addr) -> ConfigResponse { +pub fn query_config(app: &App, contract_addr: &Addr) -> ConfigResponse { app.wrap() .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) .unwrap() } pub fn query_red_bank_debt( - app: &mut App, + app: &App, credit_manager_addr: &Addr, red_bank_addr: &str, denom: &str, diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 8ed0a1934..2db61739a 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] pub struct MockEnv { pub credit_manager: Addr, pub oracle: Addr, @@ -10,7 +10,7 @@ pub struct MockEnv { pub nft: Addr, } -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] pub struct CoinInfo { pub denom: String, pub price: Decimal, diff --git a/contracts/credit-manager/tests/instantiate_test.rs b/contracts/credit-manager/tests/instantiate_test.rs index bbe8f7549..a42572aac 100644 --- a/contracts/credit-manager/tests/instantiate_test.rs +++ b/contracts/credit-manager/tests/instantiate_test.rs @@ -29,7 +29,7 @@ fn test_owner_set_on_instantiate() { let res: ConfigResponse = app .wrap() - .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) + .query_wasm_smart(contract_addr, &QueryMsg::Config {}) .unwrap(); assert_eq!(owner, res.owner); @@ -49,14 +49,8 @@ fn test_raises_on_invalid_owner_addr() { oracle: OracleBase::new("oracle_contract".to_string()), }; - let instantiate_res = app.instantiate_contract( - manager_code_id, - owner.clone(), - &msg, - &[], - "mock-contract", - None, - ); + let instantiate_res = + app.instantiate_contract(manager_code_id, owner, &msg, &[], "mock-contract", None); if instantiate_res.is_ok() { panic!("Should have thrown an error"); @@ -88,7 +82,7 @@ fn test_nft_contract_addr_not_set_on_instantiate() { let res: ConfigResponse = app .wrap() - .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) + .query_wasm_smart(contract_addr, &QueryMsg::Config {}) .unwrap(); assert_eq!(res.account_nft, None); @@ -128,7 +122,7 @@ fn test_allowed_vaults_set_on_instantiate() { let vaults_res: Vec = app .wrap() .query_wasm_smart( - contract_addr.clone(), + contract_addr, &QueryMsg::AllowedVaults { start_after: None, limit: None, @@ -153,14 +147,8 @@ fn test_raises_on_invalid_vaults_addr() { oracle: OracleBase::new("oracle_contract".to_string()), }; - let instantiate_res = app.instantiate_contract( - manager_code_id, - owner.clone(), - &msg, - &[], - "mock-contract", - None, - ); + let instantiate_res = + app.instantiate_contract(manager_code_id, owner, &msg, &[], "mock-contract", None); if instantiate_res.is_ok() { panic!("Should have thrown an error"); @@ -202,7 +190,7 @@ fn test_allowed_coins_set_on_instantiate() { let coins_res: Vec = app .wrap() .query_wasm_smart( - contract_addr.clone(), + contract_addr, &QueryMsg::AllowedCoins { start_after: None, limit: None, @@ -229,12 +217,12 @@ fn test_red_bank_set_on_instantiate() { }; let contract_addr = app - .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-account-nft", None) + .instantiate_contract(code_id, owner, &msg, &[], "mock-account-nft", None) .unwrap(); let res: ConfigResponse = app .wrap() - .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) + .query_wasm_smart(contract_addr, &QueryMsg::Config {}) .unwrap(); assert_eq!(red_bank_addr, res.red_bank); @@ -254,14 +242,8 @@ fn test_raises_on_invalid_red_bank_addr() { oracle: OracleBase::new("oracle_contract".to_string()), }; - let instantiate_res = app.instantiate_contract( - manager_code_id, - owner.clone(), - &msg, - &[], - "mock-contract", - None, - ); + let instantiate_res = + app.instantiate_contract(manager_code_id, owner, &msg, &[], "mock-contract", None); if instantiate_res.is_ok() { panic!("Should have thrown an error"); @@ -284,12 +266,12 @@ fn test_oracle_set_on_instantiate() { }; let contract_addr = app - .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-account-nft", None) + .instantiate_contract(code_id, owner, &msg, &[], "mock-account-nft", None) .unwrap(); let res: ConfigResponse = app .wrap() - .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) + .query_wasm_smart(contract_addr, &QueryMsg::Config {}) .unwrap(); assert_eq!(oracle_contract, res.oracle); @@ -309,14 +291,8 @@ fn test_raises_on_invalid_oracle_addr() { oracle: OracleBase::new("%%%INVALID%%%".to_string()), }; - let instantiate_res = app.instantiate_contract( - manager_code_id, - owner.clone(), - &msg, - &[], - "mock-contract", - None, - ); + let instantiate_res = + app.instantiate_contract(manager_code_id, owner, &msg, &[], "mock-contract", None); if instantiate_res.is_ok() { panic!("Should have thrown an error"); diff --git a/contracts/credit-manager/tests/update_config_test.rs b/contracts/credit-manager/tests/update_config_test.rs index 3d6ac1d11..076763734 100644 --- a/contracts/credit-manager/tests/update_config_test.rs +++ b/contracts/credit-manager/tests/update_config_test.rs @@ -18,7 +18,7 @@ fn test_update_config_works_with_full_config() { let code_id = app.store_code(mock_contract()); let contract_addr = instantiate(&mut app, &original_owner, code_id); - let original_config = query_config(&mut app, &contract_addr.clone()); + let original_config = query_config(&app, &contract_addr.clone()); let original_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); let original_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); @@ -46,7 +46,7 @@ fn test_update_config_works_with_full_config() { ) .unwrap(); - let new_config = query_config(&mut app, &contract_addr.clone()); + let new_config = query_config(&app, &contract_addr.clone()); let new_queried_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); let new_queried_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); @@ -76,7 +76,7 @@ fn test_update_config_works_with_some_config() { let code_id = app.store_code(mock_contract()); let contract_addr = instantiate(&mut app, &original_owner, code_id); - let original_config = query_config(&mut app, &contract_addr.clone()); + let original_config = query_config(&app, &contract_addr.clone()); let original_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); let original_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); @@ -97,7 +97,7 @@ fn test_update_config_works_with_some_config() { ) .unwrap(); - let new_config = query_config(&mut app, &contract_addr.clone()); + let new_config = query_config(&app, &contract_addr.clone()); let new_queried_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); let new_queried_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); @@ -121,9 +121,9 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { let code_id = app.store_code(mock_contract()); let contract_addr = instantiate(&mut app, &original_owner, code_id); - let original_config = query_config(&mut app, &contract_addr.clone()); - let original_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); - let original_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); + let original_config = query_config(&app, &contract_addr); + let original_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr); + let original_allowed_assets = query_allowed_assets(&mut app, &contract_addr); app.execute_contract( original_owner.clone(), @@ -135,9 +135,9 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { ) .unwrap(); - let new_config = query_config(&mut app, &contract_addr.clone()); - let new_queried_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); - let new_queried_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); + let new_config = query_config(&app, &contract_addr); + let new_queried_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr); + let new_queried_allowed_assets = query_allowed_assets(&mut app, &contract_addr); assert_eq!(new_config.account_nft, original_config.account_nft); assert_eq!(new_config.owner, original_config.owner); diff --git a/contracts/mock-oracle/src/msg.rs b/contracts/mock-oracle/src/msg.rs index 8ba4b1043..c9d8638c3 100644 --- a/contracts/mock-oracle/src/msg.rs +++ b/contracts/mock-oracle/src/msg.rs @@ -2,18 +2,18 @@ use cosmwasm_std::Decimal; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] pub struct CoinPrice { pub denom: String, pub price: Decimal, } -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] pub struct InstantiateMsg { pub coins: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { // Meant to simulate price changes for tests. Not available in prod. @@ -22,7 +22,7 @@ pub enum ExecuteMsg { // mocked from: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/oracle.rs#L155 // cw-asset needs to be removed before we can import -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { Config {}, diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs index 2b057e6fc..372b8e324 100644 --- a/contracts/mock-red-bank/src/msg.rs +++ b/contracts/mock-red-bank/src/msg.rs @@ -2,12 +2,12 @@ use cosmwasm_std::{Coin, Decimal, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] pub struct InstantiateMsg { pub coins: Vec, } -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] pub struct CoinMarketInfo { pub denom: String, pub max_ltv: Decimal, @@ -23,21 +23,21 @@ pub enum ExecuteMsg { }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { UserAssetDebt { user_address: String, denom: String }, Market { denom: String }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct UserAssetDebtResponse { pub denom: String, pub amount: Uint128, } // Schema reference: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/red_bank/mod.rs#L47 -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct Market { pub max_loan_to_value: Decimal, pub liquidation_threshold: Decimal, diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs index 8503bb0ab..86afcaef2 100644 --- a/packages/rover/src/adapters/oracle.rs +++ b/packages/rover/src/adapters/oracle.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use mock_oracle::msg::QueryMsg; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct OracleBase(T); impl OracleBase { diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index d95e5bcd4..ef6b87ff3 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use mock_red_bank::msg::{ExecuteMsg, QueryMsg, UserAssetDebtResponse}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct RedBankBase(T); impl RedBankBase { diff --git a/packages/rover/src/coins.rs b/packages/rover/src/coins.rs index b40ad902f..524bff105 100644 --- a/packages/rover/src/coins.rs +++ b/packages/rover/src/coins.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; /// Pending integration into cosmwasm_std: https://github.com/CosmWasm/cosmwasm/issues/1377#issuecomment-1204232193 /// Copying from here: https://github.com/mars-protocol/cw-coins/blob/main/src/lib.rs -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct Coins(pub BTreeMap); impl From> for Coins { diff --git a/packages/rover/src/health.rs b/packages/rover/src/health.rs index 14c44e02d..7f159d5ba 100644 --- a/packages/rover/src/health.rs +++ b/packages/rover/src/health.rs @@ -2,7 +2,7 @@ use cosmwasm_std::Decimal; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct Health { /// Total value of assets pub total_assets_value: Decimal, diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index a340f1e68..cdb9cf63d 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::adapters::{OracleUnchecked, RedBankUnchecked}; -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] pub struct InstantiateMsg { /// The address with privileged access to update config pub owner: String, @@ -18,7 +18,7 @@ pub struct InstantiateMsg { } /// Used when you want to update fields on Instantiate config -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)] pub struct ConfigUpdates { pub account_nft: Option, pub owner: Option, diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 2c87a5126..178174c4a 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{Decimal, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { /// Owner & account nft address. Response type: `ConfigResponse` @@ -43,7 +43,7 @@ pub enum QueryMsg { }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct CoinBalanceResponseItem { pub token_id: String, @@ -51,7 +51,7 @@ pub struct CoinBalanceResponseItem { pub amount: Uint128, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct SharesResponseItem { pub token_id: String, @@ -59,14 +59,14 @@ pub struct SharesResponseItem { pub shares: Uint128, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct CoinShares { pub denom: String, pub shares: Uint128, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct CoinValue { pub denom: String, @@ -75,7 +75,7 @@ pub struct CoinValue { pub value: Decimal, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct DebtSharesValue { pub denom: String, @@ -83,7 +83,7 @@ pub struct DebtSharesValue { pub total_value: Decimal, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct PositionResponse { /// Unique NFT token id that represents the cross-margin account. The owner of this NFT, owns the account. @@ -94,7 +94,7 @@ pub struct PositionResponse { pub debt_shares: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct ConfigResponse { pub owner: String, From 84441f9523cc951d28437a3a9e5eb57d23826946 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 17 Aug 2022 12:31:25 +0200 Subject: [PATCH 048/218] Converting mock env to builder pattern (#12) * Converting mock env to builder pattern * test_* file renames --- Makefile.toml | 2 +- contracts/account-nft/Cargo.toml | 2 +- .../tests/{mint_test.rs => test_mint.rs} | 0 .../{ownership_test.rs => test_ownership.rs} | 0 contracts/credit-manager/Cargo.toml | 2 +- contracts/credit-manager/tests/borrow_test.rs | 388 ------------ .../tests/create_credit_account_test.rs | 110 ---- .../credit-manager/tests/deposit_test.rs | 434 -------------- .../credit-manager/tests/dispatch_test.rs | 70 --- .../tests/enumerate_allowed_coins_test.rs | 153 ----- .../tests/enumerate_allowed_vaults_test.rs | 153 ----- .../tests/enumerate_debt_shares_test.rs | 297 ---------- .../tests/enumerate_total_debt_shares_test.rs | 288 --------- .../credit-manager/tests/helpers/builders.rs | 18 +- .../credit-manager/tests/helpers/contracts.rs | 2 +- .../credit-manager/tests/helpers/deploys.rs | 167 ------ .../credit-manager/tests/helpers/mock_env.rs | 544 +++++++++++++++++ contracts/credit-manager/tests/helpers/mod.rs | 6 +- .../credit-manager/tests/helpers/queries.rs | 65 --- .../credit-manager/tests/helpers/types.rs | 10 +- .../credit-manager/tests/instantiate_test.rs | 300 ---------- contracts/credit-manager/tests/test_borrow.rs | 288 +++++++++ .../tests/test_create_credit_account.rs | 54 ++ .../credit-manager/tests/test_deposit.rs | 332 +++++++++++ .../credit-manager/tests/test_dispatch.rs | 62 ++ .../tests/test_enumerate_allowed_coins.rs | 47 ++ .../tests/test_enumerate_allowed_vaults.rs | 79 +++ ...est.rs => test_enumerate_coin_balances.rs} | 166 ++---- .../tests/test_enumerate_debt_shares.rs | 215 +++++++ .../tests/test_enumerate_total_debt_shares.rs | 206 +++++++ .../tests/{health_test.rs => test_health.rs} | 550 ++++++------------ .../credit-manager/tests/test_instantiate.rs | 130 +++++ .../tests/test_update_config.rs | 141 +++++ .../tests/update_config_test.rs | 219 ------- contracts/mock-oracle/Cargo.toml | 2 +- contracts/mock-red-bank/Cargo.toml | 2 +- packages/rover/Cargo.toml | 2 +- 37 files changed, 2351 insertions(+), 3155 deletions(-) rename contracts/account-nft/tests/{mint_test.rs => test_mint.rs} (100%) rename contracts/account-nft/tests/{ownership_test.rs => test_ownership.rs} (100%) delete mode 100644 contracts/credit-manager/tests/borrow_test.rs delete mode 100644 contracts/credit-manager/tests/create_credit_account_test.rs delete mode 100644 contracts/credit-manager/tests/deposit_test.rs delete mode 100644 contracts/credit-manager/tests/dispatch_test.rs delete mode 100644 contracts/credit-manager/tests/enumerate_allowed_coins_test.rs delete mode 100644 contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs delete mode 100644 contracts/credit-manager/tests/enumerate_debt_shares_test.rs delete mode 100644 contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs delete mode 100644 contracts/credit-manager/tests/helpers/deploys.rs create mode 100644 contracts/credit-manager/tests/helpers/mock_env.rs delete mode 100644 contracts/credit-manager/tests/helpers/queries.rs delete mode 100644 contracts/credit-manager/tests/instantiate_test.rs create mode 100644 contracts/credit-manager/tests/test_borrow.rs create mode 100644 contracts/credit-manager/tests/test_create_credit_account.rs create mode 100644 contracts/credit-manager/tests/test_deposit.rs create mode 100644 contracts/credit-manager/tests/test_dispatch.rs create mode 100644 contracts/credit-manager/tests/test_enumerate_allowed_coins.rs create mode 100644 contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs rename contracts/credit-manager/tests/{enumerate_coin_balances_test.rs => test_enumerate_coin_balances.rs} (50%) create mode 100644 contracts/credit-manager/tests/test_enumerate_debt_shares.rs create mode 100644 contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs rename contracts/credit-manager/tests/{health_test.rs => test_health.rs} (56%) create mode 100644 contracts/credit-manager/tests/test_instantiate.rs create mode 100644 contracts/credit-manager/tests/test_update_config.rs delete mode 100644 contracts/credit-manager/tests/update_config_test.rs diff --git a/Makefile.toml b/Makefile.toml index 60474bdd7..42c859581 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -32,8 +32,8 @@ args = ["clippy", "--tests", "--", "-D", "warnings"] dependencies = [ "check", "build", - "rust-optimizer", "test", "fmt", "clippy", + "rust-optimizer", ] diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index e6a76cc7f..fe97bd8fc 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -2,7 +2,7 @@ name = "account-nft" version = "0.1.0" authors = ["larry_0x , grod220 "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later" repository = "https://github.com/mars-protocol/rover" diff --git a/contracts/account-nft/tests/mint_test.rs b/contracts/account-nft/tests/test_mint.rs similarity index 100% rename from contracts/account-nft/tests/mint_test.rs rename to contracts/account-nft/tests/test_mint.rs diff --git a/contracts/account-nft/tests/ownership_test.rs b/contracts/account-nft/tests/test_ownership.rs similarity index 100% rename from contracts/account-nft/tests/ownership_test.rs rename to contracts/account-nft/tests/test_ownership.rs diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 341d6d4ad..10d621808 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -2,7 +2,7 @@ name = "credit-manager" version = "0.1.0" authors = ["grod220 , larry_0x "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later" repository = "https://github.com/mars-protocol/rover" diff --git a/contracts/credit-manager/tests/borrow_test.rs b/contracts/credit-manager/tests/borrow_test.rs deleted file mode 100644 index f35358d25..000000000 --- a/contracts/credit-manager/tests/borrow_test.rs +++ /dev/null @@ -1,388 +0,0 @@ -use std::ops::{Mul, Sub}; - -use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; -use cw_multi_test::{App, Executor}; - -use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; -use rover::error::ContractError; -use rover::msg::execute::Action::{Borrow, Deposit}; -use rover::msg::query::CoinShares; -use rover::msg::ExecuteMsg::UpdateCreditAccount; -use rover::msg::QueryMsg; - -use crate::helpers::{ - assert_err, fund_red_bank, get_token_id, mock_app, mock_create_credit_account, query_config, - query_position, query_red_bank_debt, setup_credit_manager, CoinInfo, -}; - -pub mod helpers; - -#[test] -fn test_only_token_owner_can_borrow() { - let mut app = mock_app(); - let owner = Addr::unchecked("owner"); - - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - - let mock = setup_credit_manager(&mut app, &owner, vec![coin_info.clone()], vec![]); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &Addr::unchecked("user")) - .unwrap(); - let token_id = get_token_id(res); - - let another_user = Addr::unchecked("another_user"); - let res = app.execute_contract( - another_user.clone(), - mock.credit_manager.clone(), - &UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Borrow(coin_info.to_coin(Uint128::new(12312u128)))], - }, - &[], - ); - - assert_err( - res, - ContractError::NotTokenOwner { - user: another_user.into(), - token_id, - }, - ) -} - -#[test] -fn test_can_only_borrow_what_is_whitelisted() { - let mut app = mock_app(); - let owner = Addr::unchecked("owner"); - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - - let mock = setup_credit_manager(&mut app, &owner, vec![coin_info], vec![]); - let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let res = app.execute_contract( - user.clone(), - mock.credit_manager, - &UpdateCreditAccount { - token_id, - actions: vec![Borrow(Coin { - denom: "usomething".to_string(), - amount: Uint128::from(234u128), - })], - }, - &[], - ); - - assert_err( - res, - ContractError::NotWhitelisted(String::from("usomething")), - ) -} - -#[test] -fn test_borrowing_zero_does_nothing() { - let mut app = mock_app(); - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin_info.clone()], - vec![], - ); - let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let res = app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Borrow(coin_info.to_coin(Uint128::zero()))], - }, - &[], - ); - - assert_err(res, ContractError::NoAmount); - - let position = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(position.coins.len(), 0); - assert_eq!(position.debt_shares.len(), 0); -} - -#[test] -fn test_success_when_new_debt_asset() { - let user = Addr::unchecked("user"); - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance( - storage, - &user, - vec![Coin::new(300u128, coin_info.denom.clone())], - ) - .unwrap(); - }); - - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin_info.clone()], - vec![], - ); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let config = query_config(&app, &mock.credit_manager.clone()); - - fund_red_bank( - &mut app, - config.red_bank.clone(), - vec![Coin::new(1000u128, coin_info.denom.clone())], - ); - - let position = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(position.coins.len(), 0); - assert_eq!(position.debt_shares.len(), 0); - app.execute_contract( - user, - mock.credit_manager.clone(), - &UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![ - Deposit(Coin { - denom: coin_info.denom.clone(), - amount: Uint128::from(300u128), - }), - Borrow(Coin { - denom: coin_info.denom.clone(), - amount: Uint128::from(42u128), - }), - ], - }, - &[Coin::new(300u128, coin_info.denom.clone())], - ) - .unwrap(); - - let position = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(position.coins.len(), 1); - let asset_res = position.coins.first().unwrap(); - assert_eq!( - asset_res.amount, - Uint128::from(342u128) // Deposit + Borrow - ); - assert_eq!(asset_res.denom, coin_info.denom); - assert_eq!(asset_res.price, coin_info.price); - assert_eq!( - asset_res.value, - coin_info.price * Decimal::from_atomics(342u128, 0).unwrap() - ); - - let debt_shares_res = position.debt_shares.first().unwrap(); - assert_eq!(position.debt_shares.len(), 1); - assert_eq!( - debt_shares_res.shares, - Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) - ); - assert_eq!(debt_shares_res.denom, coin_info.denom); - let debt_amount = Uint128::from(42u128) + Uint128::new(1u128); // simulated yield - assert_eq!( - debt_shares_res.total_value, - coin_info.price * Decimal::from_atomics(debt_amount, 0).unwrap() - ); - - let coin = app - .wrap() - .query_balance(mock.credit_manager.clone(), coin_info.denom.clone()) - .unwrap(); - assert_eq!(coin.amount, Uint128::from(342u128)); - - let coin = app - .wrap() - .query_balance(config.red_bank, coin_info.denom.clone()) - .unwrap(); - assert_eq!( - coin.amount, - Uint128::from(1000u128).sub(Uint128::from(42u128)) - ); - - let res: CoinShares = app - .wrap() - .query_wasm_smart( - mock.credit_manager, - &QueryMsg::TotalDebtShares(coin_info.denom), - ) - .unwrap(); - assert_eq!( - res.shares, - Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) - ); -} - -#[test] -fn test_debt_shares_with_debt_amount() { - let user_a = Addr::unchecked("user_a"); - let user_b = Addr::unchecked("user_b"); - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance( - storage, - &user_a, - vec![Coin::new(300u128, coin_info.denom.clone())], - ) - .unwrap(); - router - .bank - .init_balance( - storage, - &user_b, - vec![Coin::new(450u128, coin_info.denom.clone())], - ) - .unwrap(); - }); - - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin_info.clone()], - vec![], - ); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); - let token_id_a = get_token_id(res); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_b).unwrap(); - let token_id_b = get_token_id(res); - - let config = query_config(&app, &mock.credit_manager.clone()); - - fund_red_bank( - &mut app, - config.red_bank.clone(), - vec![Coin::new(1000u128, coin_info.denom.clone())], - ); - - app.execute_contract( - user_a, - mock.credit_manager.clone(), - &UpdateCreditAccount { - token_id: token_id_a.clone(), - actions: vec![ - Deposit(coin_info.to_coin(Uint128::from(300u128))), - Borrow(coin_info.to_coin(Uint128::from(50u128))), - ], - }, - &[Coin::new(300u128, coin_info.denom.clone())], - ) - .unwrap(); - - let interim_red_bank_debt = query_red_bank_debt( - &app, - &mock.credit_manager, - &config.red_bank, - &coin_info.denom, - ); - - app.execute_contract( - user_b, - mock.credit_manager.clone(), - &UpdateCreditAccount { - token_id: token_id_b.clone(), - actions: vec![ - Deposit(coin_info.to_coin(Uint128::from(450u128))), - Borrow(coin_info.to_coin(Uint128::from(50u128))), - ], - }, - &[Coin::new(450u128, coin_info.denom.clone())], - ) - .unwrap(); - - let token_a_shares = Uint128::from(50u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED); - let position = query_position(&app, &mock.credit_manager, &token_id_a); - let debt_position_a = position.debt_shares.first().unwrap(); - assert_eq!(debt_position_a.shares, token_a_shares.clone()); - assert_eq!(debt_position_a.denom, coin_info.denom); - - let token_b_shares = Uint128::from(50u128) - .mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) - .multiply_ratio(Uint128::from(50u128), interim_red_bank_debt.amount); - let position = query_position(&app, &mock.credit_manager, &token_id_b); - let debt_position_b = position.debt_shares.first().unwrap(); - assert_eq!(debt_position_b.shares, token_b_shares.clone()); - assert_eq!(debt_position_b.denom, coin_info.denom); - - let total: CoinShares = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::TotalDebtShares(coin_info.denom.clone()), - ) - .unwrap(); - assert_eq!( - total.shares, - debt_position_a.shares + debt_position_b.shares - ); - - let red_bank_debt = query_red_bank_debt( - &app, - &mock.credit_manager, - &config.red_bank, - &coin_info.denom, - ); - - let a_amount_owed = red_bank_debt - .amount - .multiply_ratio(debt_position_a.shares, total.shares); - assert_eq!( - debt_position_a.total_value, - coin_info.price * Decimal::from_atomics(a_amount_owed, 0).unwrap() - ); - - let b_amount_owed = red_bank_debt - .amount - .multiply_ratio(debt_position_b.shares, total.shares); - assert_eq!( - debt_position_b.total_value, - coin_info.price * Decimal::from_atomics(b_amount_owed, 0).unwrap() - ); - - // NOTE: There is an expected rounding error. This will not pass. - // let total_borrowed_plus_interest = Decimal::from_atomics(Uint128::from(102u128), 0).unwrap(); - // assert_eq!( - // total_borrowed_plus_interest * coin_info.price, - // debt_position_a.total_value + debt_position_b.total_value - // ) - // This test below asserts the rounding down that's happening - let total_owed = Decimal::from_atomics(a_amount_owed + b_amount_owed, 0).unwrap(); - assert_eq!( - total_owed * coin_info.price, - debt_position_a.total_value + debt_position_b.total_value - ) -} diff --git a/contracts/credit-manager/tests/create_credit_account_test.rs b/contracts/credit-manager/tests/create_credit_account_test.rs deleted file mode 100644 index 00a1a1b4c..000000000 --- a/contracts/credit-manager/tests/create_credit_account_test.rs +++ /dev/null @@ -1,110 +0,0 @@ -use cosmwasm_std::Addr; -use cw721::OwnerOfResponse; -use cw721_base::InstantiateMsg as NftInstantiateMsg; -use cw721_base::QueryMsg as NftQueryMsg; -use cw_multi_test::Executor; - -use rover::adapters::{OracleBase, RedBankBase}; -use rover::msg::instantiate::ConfigUpdates; -use rover::msg::query::ConfigResponse; -use rover::msg::ExecuteMsg::UpdateConfig; -use rover::msg::{InstantiateMsg, QueryMsg}; - -use crate::helpers::{ - get_token_id, mock_account_nft_contract, mock_app, mock_contract, mock_create_credit_account, - transfer_nft_contract_ownership, -}; - -pub mod helpers; - -#[test] -fn test_create_credit_account() { - let mut app = mock_app(); - let owner = Addr::unchecked("owner"); - - let nft_contract_code_id = app.store_code(mock_account_nft_contract()); - - let nft_contract_addr = app - .instantiate_contract( - nft_contract_code_id, - owner.clone(), - &NftInstantiateMsg { - name: "Rover Credit Account".to_string(), - symbol: "RCA".to_string(), - minter: owner.to_string(), - }, - &[], - "manager-mock-account-nft", - None, - ) - .unwrap(); - - let credit_manager_code_id = app.store_code(mock_contract()); - let manager_initiate_msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec![], - allowed_coins: vec![], - red_bank: RedBankBase::new("red_bank_contract".to_string()), - oracle: OracleBase::new("oracle_contract".to_string()), - }; - - let manager_contract_addr = app - .instantiate_contract( - credit_manager_code_id, - owner.clone(), - &manager_initiate_msg, - &[], - "manager-mock-account-nft", - None, - ) - .unwrap(); - - let user = Addr::unchecked("some_user"); - let res = mock_create_credit_account(&mut app, &manager_contract_addr, &user); - - if res.is_ok() { - panic!("Should have thrown error due to nft contract not yet set"); - } - - let res = app.execute_contract( - owner.clone(), - manager_contract_addr.clone(), - &UpdateConfig { - new_config: ConfigUpdates { - account_nft: Some(nft_contract_addr.to_string()), - ..Default::default() - }, - }, - &[], - ); - - if res.is_ok() { - panic!("Should have thrown error due to nft contract not proposing a new owner yet"); - } - - transfer_nft_contract_ownership(&mut app, &owner, &nft_contract_addr, &manager_contract_addr); - - let res = mock_create_credit_account(&mut app, &manager_contract_addr, &user).unwrap(); - - let token_id = get_token_id(res); - assert_eq!(token_id, "1"); - - // Double checking ownership by querying NFT account-nft for correct owner - let config_res: ConfigResponse = app - .wrap() - .query_wasm_smart(manager_contract_addr.clone(), &QueryMsg::Config {}) - .unwrap(); - - let owner_res: OwnerOfResponse = app - .wrap() - .query_wasm_smart( - config_res.account_nft.unwrap(), - &NftQueryMsg::OwnerOf { - token_id, - include_expired: None, - }, - ) - .unwrap(); - - assert_eq!(user, owner_res.owner) -} diff --git a/contracts/credit-manager/tests/deposit_test.rs b/contracts/credit-manager/tests/deposit_test.rs deleted file mode 100644 index b08d4fb26..000000000 --- a/contracts/credit-manager/tests/deposit_test.rs +++ /dev/null @@ -1,434 +0,0 @@ -use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; -use cw_multi_test::{App, Executor}; - -use rover::coins::Coins; -use rover::error::ContractError::{ - ExtraFundsReceived, FundsMismatch, NotTokenOwner, NotWhitelisted, -}; -use rover::msg::execute::Action; -use rover::msg::query::PositionResponse; -use rover::msg::ExecuteMsg; - -use crate::helpers::{ - assert_err, get_token_id, mock_app, mock_create_credit_account, query_position, - setup_credit_manager, CoinInfo, -}; - -pub mod helpers; - -#[test] -fn test_only_owner_of_token_can_deposit() { - let mut app = mock_app(); - let coin = Coin { - denom: "uosmo".to_string(), - amount: Uint128::zero(), - }; - - let mock = setup_credit_manager(&mut app, &Addr::unchecked("owner"), vec![], vec![]); - - let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let another_user = Addr::unchecked("another_user"); - let res = app.execute_contract( - another_user.clone(), - mock.credit_manager, - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Action::Deposit(coin)], - }, - &[], - ); - - assert_err( - res, - NotTokenOwner { - user: another_user.into(), - token_id, - }, - ) -} - -#[test] -fn test_deposit_nothing() { - let mut app = mock_app(); - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin_info.clone()], - vec![], - ); - - let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coins.len(), 0); - - app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Action::Deposit(coin_info.to_coin(Uint128::zero()))], - }, - &[], - ) - .unwrap(); - - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coins.len(), 0); -} - -#[test] -fn test_deposit_but_no_funds() { - let mut app = mock_app(); - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - let deposit_amount = Uint128::from(234u128); - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin_info.clone()], - vec![], - ); - - let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let res = app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Action::Deposit(coin_info.to_coin(deposit_amount))], - }, - &[], - ); - - assert_err( - res, - FundsMismatch { - expected: deposit_amount, - received: Uint128::zero(), - }, - ); - - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coins.len(), 0); -} - -#[test] -fn test_deposit_but_not_enough_funds() { - let user = Addr::unchecked("user"); - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance( - storage, - &user, - vec![Coin::new(300u128, coin_info.denom.clone())], - ) - .unwrap(); - }); - - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin_info.clone()], - vec![], - ); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let res = app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id, - actions: vec![Action::Deposit(coin_info.to_coin(Uint128::from(350u128)))], - }, - &[Coin::new(250u128, coin_info.denom)], - ); - - assert_err( - res, - FundsMismatch { - expected: Uint128::from(350u128), - received: Uint128::from(250u128), - }, - ); -} - -#[test] -fn test_can_only_deposit_allowed_assets() { - let user = Addr::unchecked("user"); - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance( - storage, - &user, - vec![Coin::new(300u128, coin_info.denom.clone())], - ) - .unwrap(); - }); - - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin_info.clone()], - vec![], - ); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let not_allowed_coin = Coin { - denom: "ujakecoin".to_string(), - amount: Uint128::from(234u128), - }; - let res = app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Action::Deposit(not_allowed_coin.clone())], - }, - &[Coin::new(234u128, coin_info.denom)], - ); - - assert_err(res, NotWhitelisted(not_allowed_coin.denom)); - - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coins.len(), 0); -} - -#[test] -fn test_extra_funds_received() { - let user = Addr::unchecked("user"); - let uosmo_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - let uatom_info = CoinInfo { - denom: "uatom".to_string(), - price: Decimal::from_atomics(10u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), - liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), - }; - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance( - storage, - &user, - vec![ - Coin::new(300u128, uosmo_info.denom.clone()), - Coin::new(250u128, uatom_info.denom.clone()), - ], - ) - .unwrap(); - }); - - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![uosmo_info.clone(), uatom_info.clone()], - vec![], - ); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let extra_funds = Coin::new(25u128, uatom_info.denom); - let res = app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Action::Deposit(uosmo_info.to_coin(Uint128::from(234u128)))], - }, - &[Coin::new(234u128, uosmo_info.denom), extra_funds.clone()], - ); - - assert_err(res, ExtraFundsReceived(Coins::from(vec![extra_funds]))); - - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coins.len(), 0); -} - -#[test] -fn test_deposit_success() { - let user = Addr::unchecked("user"); - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance( - storage, - &user, - vec![Coin::new(300u128, coin_info.denom.clone())], - ) - .unwrap(); - }); - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin_info.clone()], - vec![], - ); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let deposit_amount = Uint128::from(234u128); - - app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Action::Deposit(coin_info.to_coin(deposit_amount))], - }, - &[Coin::new(deposit_amount.into(), coin_info.denom.clone())], - ) - .unwrap(); - - let res = query_position(&app, &mock.credit_manager, &token_id); - let assets_res = res.coins.first().unwrap(); - assert_eq!(res.coins.len(), 1); - assert_eq!(assets_res.amount, deposit_amount); - assert_eq!(assets_res.denom, coin_info.denom); - assert_eq!(assets_res.price, coin_info.price); - assert_eq!( - assets_res.value, - coin_info.price * Decimal::from_atomics(deposit_amount, 0).unwrap() - ); - - let coin = app - .wrap() - .query_balance(mock.credit_manager, coin_info.denom) - .unwrap(); - assert_eq!(coin.amount, deposit_amount) -} - -#[test] -fn test_multiple_deposit_actions() { - let user = Addr::unchecked("user"); - let uosmo_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - let uatom_info = CoinInfo { - denom: "uatom".to_string(), - price: Decimal::from_atomics(10u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), - liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), - }; - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance( - storage, - &user, - vec![ - Coin::new(300u128, uosmo_info.denom.clone()), - Coin::new(50u128, uatom_info.denom.clone()), - ], - ) - .unwrap(); - }); - - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![uosmo_info.clone(), uatom_info.clone()], - vec![], - ); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let uosmo_amount = Uint128::from(234u128); - let uatom_amount = Uint128::from(25u128); - - app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![ - Action::Deposit(uosmo_info.to_coin(uosmo_amount)), - Action::Deposit(uatom_info.to_coin(uatom_amount)), - ], - }, - &[ - Coin::new(234u128, uosmo_info.denom.clone()), - Coin::new(25u128, uatom_info.denom.clone()), - ], - ) - .unwrap(); - - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coins.len(), 2); - let uosmo_value = Decimal::from_atomics(uosmo_amount, 0).unwrap() * uosmo_info.price; - assert_present(&res, &uosmo_info, uosmo_amount, uosmo_value); - let uatom_value = Decimal::from_atomics(uatom_amount, 0).unwrap() * uatom_info.price; - assert_present(&res, &uatom_info, uatom_amount, uatom_value); - - let coin = app - .wrap() - .query_balance(mock.credit_manager.clone(), uosmo_info.denom) - .unwrap(); - assert_eq!(coin.amount, uosmo_amount); - - let coin = app - .wrap() - .query_balance(mock.credit_manager, "uatom") - .unwrap(); - assert_eq!(coin.amount, uatom_amount); -} - -fn assert_present(res: &PositionResponse, coin: &CoinInfo, amount: Uint128, total_val: Decimal) { - res.coins - .iter() - .find(|item| item.denom == coin.denom && item.amount == amount && item.value == total_val) - .unwrap(); -} diff --git a/contracts/credit-manager/tests/dispatch_test.rs b/contracts/credit-manager/tests/dispatch_test.rs deleted file mode 100644 index 5090e7299..000000000 --- a/contracts/credit-manager/tests/dispatch_test.rs +++ /dev/null @@ -1,70 +0,0 @@ -use cosmwasm_std::Addr; -use cw_multi_test::Executor; - -use rover::error::ContractError::NotTokenOwner; -use rover::msg::ExecuteMsg::UpdateCreditAccount; - -use helpers::{ - assert_err, get_token_id, mock_app, mock_create_credit_account, query_position, - setup_credit_manager, -}; - -pub mod helpers; - -#[test] -fn test_dispatch_only_allowed_for_token_owner() { - let mut app = mock_app(); - let owner = Addr::unchecked("owner"); - let mock = setup_credit_manager(&mut app, &owner, vec![], vec![]); - - let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let bad_guy = Addr::unchecked("bad_guy"); - let res = app.execute_contract( - bad_guy.clone(), - mock.credit_manager.clone(), - &UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![], - }, - &[], - ); - - assert_err( - res, - NotTokenOwner { - user: bad_guy.into(), - token_id, - }, - ) -} - -#[test] -fn test_nothing_happens_if_no_actions_are_passed() { - let mut app = mock_app(); - let owner = Addr::unchecked("owner"); - let mock = setup_credit_manager(&mut app, &owner, vec![], vec![]); - - let user = Addr::unchecked("user"); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coins.len(), 0); - - app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![], - }, - &[], - ) - .unwrap(); - - let res = query_position(&app, &mock.credit_manager, &token_id); - assert_eq!(res.coins.len(), 0); -} diff --git a/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs b/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs deleted file mode 100644 index 4f8594ea2..000000000 --- a/contracts/credit-manager/tests/enumerate_allowed_coins_test.rs +++ /dev/null @@ -1,153 +0,0 @@ -use cosmwasm_std::Addr; -use cw_multi_test::Executor; - -use rover::adapters::{OracleBase, RedBankBase}; -use rover::msg::{InstantiateMsg, QueryMsg}; - -use crate::helpers::{mock_app, mock_contract}; - -pub mod helpers; - -#[test] -fn test_pagination_on_allowed_coins_query_works() { - let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); - let owner = Addr::unchecked("owner"); - - let allowed_coins = vec![ - "coin_1".to_string(), - "coin_2".to_string(), - "coin_3".to_string(), - "coin_4".to_string(), - "coin_5".to_string(), - "coin_6".to_string(), - "coin_7".to_string(), - "coin_8".to_string(), - "coin_9".to_string(), - "coin_10".to_string(), - "coin_11".to_string(), - "coin_12".to_string(), - "coin_13".to_string(), - "coin_14".to_string(), - "coin_15".to_string(), - "coin_16".to_string(), - "coin_17".to_string(), - "coin_18".to_string(), - "coin_19".to_string(), - "coin_20".to_string(), - "coin_21".to_string(), - "coin_22".to_string(), - "coin_23".to_string(), - "coin_24".to_string(), - "coin_25".to_string(), - "coin_26".to_string(), - "coin_27".to_string(), - "coin_28".to_string(), - "coin_29".to_string(), - "coin_30".to_string(), - "coin_31".to_string(), - "coin_32".to_string(), - ]; - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec![], - allowed_coins: allowed_coins.clone(), - red_bank: RedBankBase::new("red_bank_contract".to_string()), - oracle: OracleBase::new("oracle_contract".to_string()), - }; - - let contract_addr = app - .instantiate_contract(code_id, owner, &msg, &[], "mock-contract", None) - .unwrap(); - - let coins_res: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedCoins { - start_after: None, - limit: Some(58u32), - }, - ) - .unwrap(); - - // Assert maximum is observed - assert_eq!(coins_res.len(), 30); - - let coins_res: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedCoins { - start_after: None, - limit: Some(2u32), - }, - ) - .unwrap(); - - // Assert limit request is observed - assert_eq!(coins_res.len(), 2); - - let coins_res_a: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedCoins { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - let coins_res_b: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedCoins { - start_after: Some(coins_res_a.last().unwrap().clone()), - limit: None, - }, - ) - .unwrap(); - - let coins_res_c: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedCoins { - start_after: Some(coins_res_b.last().unwrap().clone()), - limit: None, - }, - ) - .unwrap(); - - let coins_res_d: Vec = app - .wrap() - .query_wasm_smart( - contract_addr, - &QueryMsg::AllowedCoins { - start_after: Some(coins_res_c.last().unwrap().clone()), - limit: None, - }, - ) - .unwrap(); - - // Assert default is observed - assert_eq!(coins_res_a.len(), 10); - assert_eq!(coins_res_b.len(), 10); - assert_eq!(coins_res_c.len(), 10); - - assert_eq!(coins_res_d.len(), 2); - - let combined: Vec = coins_res_a - .iter() - .cloned() - .chain(coins_res_b.iter().cloned()) - .chain(coins_res_c.iter().cloned()) - .chain(coins_res_d.iter().cloned()) - .collect(); - - assert_eq!(combined.len(), allowed_coins.len()); - assert!(allowed_coins.iter().all(|item| combined.contains(item))); -} diff --git a/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs b/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs deleted file mode 100644 index e463c5cfc..000000000 --- a/contracts/credit-manager/tests/enumerate_allowed_vaults_test.rs +++ /dev/null @@ -1,153 +0,0 @@ -use cosmwasm_std::Addr; -use cw_multi_test::Executor; - -use rover::adapters::{OracleBase, RedBankBase}; -use rover::msg::{InstantiateMsg, QueryMsg}; - -use crate::helpers::{mock_app, mock_contract}; - -pub mod helpers; - -#[test] -fn test_pagination_on_allowed_vaults_query_works() { - let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); - let owner = Addr::unchecked("owner"); - - let allowed_vaults = vec![ - "addr1".to_string(), - "addr2".to_string(), - "addr3".to_string(), - "addr4".to_string(), - "addr5".to_string(), - "addr6".to_string(), - "addr7".to_string(), - "addr8".to_string(), - "addr9".to_string(), - "addr10".to_string(), - "addr11".to_string(), - "addr12".to_string(), - "addr13".to_string(), - "addr14".to_string(), - "addr15".to_string(), - "addr16".to_string(), - "addr17".to_string(), - "addr18".to_string(), - "addr19".to_string(), - "addr20".to_string(), - "addr21".to_string(), - "addr22".to_string(), - "addr23".to_string(), - "addr24".to_string(), - "addr25".to_string(), - "addr26".to_string(), - "addr27".to_string(), - "addr28".to_string(), - "addr29".to_string(), - "addr30".to_string(), - "addr31".to_string(), - "addr32".to_string(), - ]; - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: allowed_vaults.clone(), - allowed_coins: vec![], - red_bank: RedBankBase::new("red_bank_contract".to_string()), - oracle: OracleBase::new("oracle_contract".to_string()), - }; - - let contract_addr = app - .instantiate_contract(code_id, owner, &msg, &[], "mock-contract", None) - .unwrap(); - - let vaults_res: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedVaults { - start_after: None, - limit: Some(58u32), - }, - ) - .unwrap(); - - // Assert maximum is observed - assert_eq!(vaults_res.len(), 30); - - let vaults_res: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedVaults { - start_after: None, - limit: Some(2u32), - }, - ) - .unwrap(); - - // Assert limit request is observed - assert_eq!(vaults_res.len(), 2); - - let vaults_res_a: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedVaults { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - let vaults_res_b: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedVaults { - start_after: Some(vaults_res_a.last().unwrap().clone()), - limit: None, - }, - ) - .unwrap(); - - let vaults_res_c: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedVaults { - start_after: Some(vaults_res_b.last().unwrap().clone()), - limit: None, - }, - ) - .unwrap(); - - let vaults_res_d: Vec = app - .wrap() - .query_wasm_smart( - contract_addr, - &QueryMsg::AllowedVaults { - start_after: Some(vaults_res_c.last().unwrap().clone()), - limit: None, - }, - ) - .unwrap(); - - // Assert default is observed - assert_eq!(vaults_res_a.len(), 10); - assert_eq!(vaults_res_b.len(), 10); - assert_eq!(vaults_res_c.len(), 10); - - assert_eq!(vaults_res_d.len(), 2); - - let combined: Vec = vaults_res_a - .iter() - .cloned() - .chain(vaults_res_b.iter().cloned()) - .chain(vaults_res_c.iter().cloned()) - .chain(vaults_res_d.iter().cloned()) - .collect(); - - assert_eq!(combined.len(), allowed_vaults.len()); - assert!(allowed_vaults.iter().all(|item| combined.contains(item))); -} diff --git a/contracts/credit-manager/tests/enumerate_debt_shares_test.rs b/contracts/credit-manager/tests/enumerate_debt_shares_test.rs deleted file mode 100644 index 70cc4d21f..000000000 --- a/contracts/credit-manager/tests/enumerate_debt_shares_test.rs +++ /dev/null @@ -1,297 +0,0 @@ -use cosmwasm_std::{Addr, Coin, Uint128}; -use cw_multi_test::{App, Executor}; - -use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; -use rover::msg::execute::Action; -use rover::msg::query::SharesResponseItem; -use rover::msg::{ExecuteMsg, QueryMsg}; - -use crate::helpers::{ - build_mock_coin_infos, fund_red_bank, get_token_id, mock_create_credit_account, query_config, - setup_credit_manager, CoinCreator, -}; - -pub mod helpers; - -#[test] -fn test_pagination_on_all_debt_shares_query_works() { - let user_a = Addr::unchecked("user_a"); - let user_b = Addr::unchecked("user_b"); - let user_c = Addr::unchecked("user_c"); - - let user_a_coins = vec![ - Coin::new(10u128, "coin_1"), - Coin::new(10u128, "coin_2"), - Coin::new(10u128, "coin_3"), - Coin::new(10u128, "coin_4"), - Coin::new(10u128, "coin_5"), - Coin::new(10u128, "coin_6"), - Coin::new(10u128, "coin_7"), - Coin::new(10u128, "coin_8"), - Coin::new(10u128, "coin_9"), - Coin::new(10u128, "coin_10"), - Coin::new(10u128, "coin_11"), - Coin::new(10u128, "coin_12"), - Coin::new(10u128, "coin_13"), - Coin::new(10u128, "coin_14"), - ]; - - let user_b_coins = vec![ - Coin::new(10u128, "coin_15"), - Coin::new(10u128, "coin_16"), - Coin::new(10u128, "coin_17"), - Coin::new(10u128, "coin_18"), - Coin::new(10u128, "coin_19"), - Coin::new(10u128, "coin_20"), - Coin::new(10u128, "coin_21"), - Coin::new(10u128, "coin_22"), - Coin::new(10u128, "coin_23"), - Coin::new(10u128, "coin_24"), - ]; - - let user_c_coins = vec![ - Coin::new(10u128, "coin_25"), - Coin::new(10u128, "coin_26"), - Coin::new(10u128, "coin_27"), - Coin::new(10u128, "coin_28"), - Coin::new(10u128, "coin_29"), - Coin::new(10u128, "coin_30"), - Coin::new(10u128, "coin_31"), - Coin::new(10u128, "coin_32"), - ]; - - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &user_a, user_a_coins.clone()) - .unwrap(); - router - .bank - .init_balance(storage, &user_b, user_b_coins.clone()) - .unwrap(); - router - .bank - .init_balance(storage, &user_c, user_c_coins.clone()) - .unwrap(); - }); - - let mock_coin_infos = build_mock_coin_infos(32); - - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - mock_coin_infos.clone(), - vec![], - ); - - let config = query_config(&app, &mock.credit_manager.clone()); - - fund_red_bank(&mut app, config.red_bank, mock_coin_infos.to_coins(1000)); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); - let token_id_a = get_token_id(res); - app.execute_contract( - user_a.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_a.clone(), - actions: user_a_coins - .iter() - .flat_map(|coin| { - vec![ - Action::Deposit(coin.clone()), - Action::Borrow(Coin { - denom: coin.denom.clone(), - amount: Uint128::from(1u128), - }), - ] - }) - .collect::>(), - }, - &user_a_coins, - ) - .unwrap(); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_b).unwrap(); - let token_id_b = get_token_id(res); - app.execute_contract( - user_b.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_b.clone(), - actions: user_b_coins - .iter() - .flat_map(|coin| { - vec![ - Action::Deposit(coin.clone()), - Action::Borrow(Coin { - denom: coin.denom.clone(), - amount: Uint128::from(1u128), - }), - ] - }) - .collect::>(), - }, - &user_b_coins, - ) - .unwrap(); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_c).unwrap(); - let token_id_c = get_token_id(res); - app.execute_contract( - user_c.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_c.clone(), - actions: user_c_coins - .iter() - .flat_map(|coin| { - vec![ - Action::Deposit(coin.clone()), - Action::Borrow(Coin { - denom: coin.denom.clone(), - amount: Uint128::from(1u128), - }), - ] - }) - .collect::>(), - }, - &user_c_coins, - ) - .unwrap(); - - let all_debt_shares_res: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllDebtShares { - start_after: None, - limit: Some(58u32), - }, - ) - .unwrap(); - - // Assert maximum is observed - assert_eq!(all_debt_shares_res.len(), 30); - - let all_debt_shares_res: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllDebtShares { - start_after: None, - limit: Some(2u32), - }, - ) - .unwrap(); - - // Assert limit request is observed - assert_eq!(all_debt_shares_res.len(), 2); - - let all_debt_shares_res_a: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllDebtShares { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - let SharesResponseItem { - token_id, denom, .. - } = all_debt_shares_res_a.last().unwrap().clone(); - let all_debt_shares_res_b: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllDebtShares { - start_after: Some((token_id, denom)), - limit: None, - }, - ) - .unwrap(); - - let SharesResponseItem { - token_id, denom, .. - } = all_debt_shares_res_b.last().unwrap().clone(); - let all_debt_shares_res_c: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllDebtShares { - start_after: Some((token_id, denom)), - limit: None, - }, - ) - .unwrap(); - - let SharesResponseItem { - token_id, denom, .. - } = all_debt_shares_res_c.last().unwrap().clone(); - let all_debt_shares_res_d: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllDebtShares { - start_after: Some((token_id, denom)), - limit: None, - }, - ) - .unwrap(); - - // Assert default is observed - assert_eq!(all_debt_shares_res_a.len(), 10); - assert_eq!(all_debt_shares_res_b.len(), 10); - assert_eq!(all_debt_shares_res_c.len(), 10); - - assert_eq!(all_debt_shares_res_d.len(), 2); - - let combined_res: Vec = all_debt_shares_res_a - .iter() - .cloned() - .chain(all_debt_shares_res_b.iter().cloned()) - .chain(all_debt_shares_res_c.iter().cloned()) - .chain(all_debt_shares_res_d.iter().cloned()) - .collect(); - - let user_a_response_items = user_a_coins - .iter() - .map(|coin| SharesResponseItem { - token_id: token_id_a.clone(), - denom: coin.denom.clone(), - shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, - }) - .collect::>(); - - let user_b_response_items = user_b_coins - .iter() - .map(|coin| SharesResponseItem { - token_id: token_id_b.clone(), - denom: coin.denom.clone(), - shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, - }) - .collect::>(); - - let user_c_response_items = user_c_coins - .iter() - .map(|coin| SharesResponseItem { - token_id: token_id_c.clone(), - denom: coin.denom.clone(), - shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, - }) - .collect::>(); - - let combined_starting_vals: Vec = user_a_response_items - .iter() - .cloned() - .chain(user_b_response_items) - .chain(user_c_response_items) - .collect(); - - assert_eq!(combined_res.len(), combined_starting_vals.len()); - assert!(combined_starting_vals - .iter() - .all(|item| combined_res.contains(item))); -} diff --git a/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs b/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs deleted file mode 100644 index 346010f3f..000000000 --- a/contracts/credit-manager/tests/enumerate_total_debt_shares_test.rs +++ /dev/null @@ -1,288 +0,0 @@ -use cosmwasm_std::{Addr, Coin, Uint128}; -use cw_multi_test::{App, Executor}; - -use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; -use rover::msg::execute::Action; -use rover::msg::query::CoinShares; -use rover::msg::{ExecuteMsg, QueryMsg}; - -use crate::helpers::{ - build_mock_coin_infos, fund_red_bank, get_token_id, mock_create_credit_account, query_config, - setup_credit_manager, CoinCreator, -}; - -pub mod helpers; - -#[test] -fn test_pagination_on_all_total_debt_shares_query_works() { - let user_a = Addr::unchecked("user_a"); - let user_b = Addr::unchecked("user_b"); - let user_c = Addr::unchecked("user_c"); - - let user_a_coins = vec![ - Coin::new(10u128, "coin_1"), - Coin::new(10u128, "coin_2"), - Coin::new(10u128, "coin_3"), - Coin::new(10u128, "coin_4"), - Coin::new(10u128, "coin_5"), - Coin::new(10u128, "coin_6"), - Coin::new(10u128, "coin_7"), - Coin::new(10u128, "coin_8"), - Coin::new(10u128, "coin_9"), - Coin::new(10u128, "coin_10"), - Coin::new(10u128, "coin_11"), - Coin::new(10u128, "coin_12"), - Coin::new(10u128, "coin_13"), - Coin::new(10u128, "coin_14"), - ]; - - let user_b_coins = vec![ - Coin::new(10u128, "coin_15"), - Coin::new(10u128, "coin_16"), - Coin::new(10u128, "coin_17"), - Coin::new(10u128, "coin_18"), - Coin::new(10u128, "coin_19"), - Coin::new(10u128, "coin_20"), - Coin::new(10u128, "coin_21"), - Coin::new(10u128, "coin_22"), - Coin::new(10u128, "coin_23"), - Coin::new(10u128, "coin_24"), - ]; - - let user_c_coins = vec![ - Coin::new(10u128, "coin_25"), - Coin::new(10u128, "coin_26"), - Coin::new(10u128, "coin_27"), - Coin::new(10u128, "coin_28"), - Coin::new(10u128, "coin_29"), - Coin::new(10u128, "coin_30"), - Coin::new(10u128, "coin_31"), - Coin::new(10u128, "coin_32"), - ]; - - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &user_a, user_a_coins.clone()) - .unwrap(); - router - .bank - .init_balance(storage, &user_b, user_b_coins.clone()) - .unwrap(); - router - .bank - .init_balance(storage, &user_c, user_c_coins.clone()) - .unwrap(); - }); - - let mock_coin_infos = build_mock_coin_infos(32); - - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - mock_coin_infos.clone(), - vec![], - ); - - let config = query_config(&app, &mock.credit_manager.clone()); - - fund_red_bank(&mut app, config.red_bank, mock_coin_infos.to_coins(1000)); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); - let token_id_a = get_token_id(res); - app.execute_contract( - user_a.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_a, - actions: user_a_coins - .iter() - .flat_map(|coin| { - vec![ - Action::Deposit(coin.clone()), - Action::Borrow(Coin { - denom: coin.denom.clone(), - amount: Uint128::from(1u128), - }), - ] - }) - .collect::>(), - }, - &user_a_coins, - ) - .unwrap(); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_b).unwrap(); - let token_id_b = get_token_id(res); - app.execute_contract( - user_b.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_b, - actions: user_b_coins - .iter() - .flat_map(|coin| { - vec![ - Action::Deposit(coin.clone()), - Action::Borrow(Coin { - denom: coin.denom.clone(), - amount: Uint128::from(1u128), - }), - ] - }) - .collect::>(), - }, - &user_b_coins, - ) - .unwrap(); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_c).unwrap(); - let token_id_c = get_token_id(res); - app.execute_contract( - user_c.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_c, - actions: user_c_coins - .iter() - .flat_map(|coin| { - vec![ - Action::Deposit(coin.clone()), - Action::Borrow(Coin { - denom: coin.denom.clone(), - amount: Uint128::from(1u128), - }), - ] - }) - .collect::>(), - }, - &user_c_coins, - ) - .unwrap(); - - let all_total_debt_shares_res: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllTotalDebtShares { - start_after: None, - limit: Some(58u32), - }, - ) - .unwrap(); - - // Assert maximum is observed - assert_eq!(all_total_debt_shares_res.len(), 30); - - let all_total_debt_shares_res: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllTotalDebtShares { - start_after: None, - limit: Some(2u32), - }, - ) - .unwrap(); - - // Assert limit request is observed - assert_eq!(all_total_debt_shares_res.len(), 2); - - let all_total_debt_shares_res_a: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllTotalDebtShares { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - let CoinShares { denom, .. } = all_total_debt_shares_res_a.last().unwrap().clone(); - let all_total_debt_shares_res_b: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllTotalDebtShares { - start_after: Some(denom), - limit: None, - }, - ) - .unwrap(); - - let CoinShares { denom, .. } = all_total_debt_shares_res_b.last().unwrap().clone(); - let all_total_debt_shares_res_c: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllTotalDebtShares { - start_after: Some(denom), - limit: None, - }, - ) - .unwrap(); - - let CoinShares { denom, .. } = all_total_debt_shares_res_c.last().unwrap().clone(); - let all_total_debt_shares_res_d: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllTotalDebtShares { - start_after: Some(denom), - limit: None, - }, - ) - .unwrap(); - - // Assert default is observed - assert_eq!(all_total_debt_shares_res_a.len(), 10); - assert_eq!(all_total_debt_shares_res_b.len(), 10); - assert_eq!(all_total_debt_shares_res_c.len(), 10); - - assert_eq!(all_total_debt_shares_res_d.len(), 2); - - let combined_res: Vec = all_total_debt_shares_res_a - .iter() - .cloned() - .chain(all_total_debt_shares_res_b.iter().cloned()) - .chain(all_total_debt_shares_res_c.iter().cloned()) - .chain(all_total_debt_shares_res_d.iter().cloned()) - .collect(); - - let user_a_response_items = user_a_coins - .iter() - .map(|coin| CoinShares { - denom: coin.denom.clone(), - shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, - }) - .collect::>(); - - let user_b_response_items = user_b_coins - .iter() - .map(|coin| CoinShares { - denom: coin.denom.clone(), - shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, - }) - .collect::>(); - - let user_c_response_items = user_c_coins - .iter() - .map(|coin| CoinShares { - denom: coin.denom.clone(), - shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, - }) - .collect::>(); - - let combined_starting_vals: Vec = user_a_response_items - .iter() - .cloned() - .chain(user_b_response_items) - .chain(user_c_response_items) - .collect(); - - assert_eq!(combined_res.len(), combined_starting_vals.len()); - assert!(combined_starting_vals - .iter() - .all(|item| combined_res.contains(item))); -} diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index 46dee7fa0..a53f77642 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -1,5 +1,6 @@ +use cosmwasm_std::Decimal; + use crate::helpers::CoinInfo; -use cosmwasm_std::{Coin, Decimal, Uint128}; pub fn build_mock_coin_infos(count: usize) -> Vec { (1..=count) @@ -12,18 +13,3 @@ pub fn build_mock_coin_infos(count: usize) -> Vec { }) .collect() } - -pub trait CoinCreator { - fn to_coins(&self, amount: u128) -> Vec; -} - -impl CoinCreator for Vec { - fn to_coins(&self, amount: u128) -> Vec { - self.iter() - .map(|info| Coin { - denom: info.denom.clone(), - amount: Uint128::from(amount), - }) - .collect() - } -} diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index 4e5423426..10bf5b422 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -16,7 +16,7 @@ pub fn mock_app() -> App { App::default() } -pub fn mock_contract() -> Box> { +pub fn mock_rover_contract() -> Box> { let contract = ContractWrapper::new(execute, instantiate, query); Box::new(contract) } diff --git a/contracts/credit-manager/tests/helpers/deploys.rs b/contracts/credit-manager/tests/helpers/deploys.rs deleted file mode 100644 index 0266192e4..000000000 --- a/contracts/credit-manager/tests/helpers/deploys.rs +++ /dev/null @@ -1,167 +0,0 @@ -use anyhow::Result as AnyResult; -use cosmwasm_std::{Addr, Coin}; -use cw721_base::InstantiateMsg as NftInstantiateMsg; -use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; - -use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; -use mock_red_bank::msg::{CoinMarketInfo, InstantiateMsg as RedBankInstantiateMsg}; -use rover::adapters::{OracleBase, RedBankBase}; -use rover::msg::execute::ExecuteMsg; -use rover::msg::instantiate::ConfigUpdates; -use rover::msg::InstantiateMsg; - -use crate::helpers::contracts::{mock_account_nft_contract, mock_contract, mock_red_bank_contract}; -use crate::helpers::types::MockEnv; -use crate::helpers::{mock_oracle_contract, CoinInfo}; - -pub fn mock_create_credit_account( - app: &mut App, - manager_contract_addr: &Addr, - user: &Addr, -) -> AnyResult { - app.execute_contract( - user.clone(), - manager_contract_addr.clone(), - &ExecuteMsg::CreateCreditAccount {}, - &[], - ) -} - -pub fn transfer_nft_contract_ownership( - app: &mut App, - owner: &Addr, - nft_contract_addr: &Addr, - manager_contract_addr: &Addr, -) { - let proposal_msg: NftExecuteMsg = NftExecuteMsg::ProposeNewOwner { - new_owner: manager_contract_addr.to_string(), - }; - app.execute_contract(owner.clone(), nft_contract_addr.clone(), &proposal_msg, &[]) - .unwrap(); - - app.execute_contract( - owner.clone(), - manager_contract_addr.clone(), - &ExecuteMsg::UpdateConfig { - new_config: ConfigUpdates { - account_nft: Some(nft_contract_addr.to_string()), - ..Default::default() - }, - }, - &[], - ) - .unwrap(); -} - -pub fn setup_nft_contract(app: &mut App, owner: &Addr, manager_contract_addr: &Addr) -> Addr { - let nft_contract_code_id = app.store_code(mock_account_nft_contract()); - let nft_contract_addr = app - .instantiate_contract( - nft_contract_code_id, - owner.clone(), - &NftInstantiateMsg { - name: "Rover Credit Account".to_string(), - symbol: "RCA".to_string(), - minter: owner.to_string(), - }, - &[], - "manager-mock-account-nft", - None, - ) - .unwrap(); - - transfer_nft_contract_ownership(app, owner, &nft_contract_addr, manager_contract_addr); - nft_contract_addr -} - -pub fn setup_oracle(app: &mut App, coins: &[CoinInfo]) -> Addr { - let contract_code_id = app.store_code(mock_oracle_contract()); - app.instantiate_contract( - contract_code_id, - Addr::unchecked("oracle_contract_owner"), - &OracleInstantiateMsg { - coins: coins - .iter() - .map(|item| CoinPrice { - denom: item.denom.to_string(), - price: item.price, - }) - .collect(), - }, - &[], - "mock-oracle", - None, - ) - .unwrap() -} - -pub fn setup_red_bank(app: &mut App, coins: &[CoinInfo]) -> Addr { - let contract_code_id = app.store_code(mock_red_bank_contract()); - app.instantiate_contract( - contract_code_id, - Addr::unchecked("red_bank_contract_owner"), - &RedBankInstantiateMsg { - coins: coins - .iter() - .map(|item| CoinMarketInfo { - denom: item.denom.to_string(), - max_ltv: item.max_ltv, - liquidation_threshold: item.liquidation_threshold, - }) - .collect(), - }, - &[], - "mock-red-bank", - None, - ) - .unwrap() -} - -pub fn fund_red_bank(app: &mut BasicApp, red_bank_addr: String, funds: Vec) { - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: red_bank_addr, - amount: funds, - })) - .unwrap(); -} - -pub fn setup_credit_manager( - app: &mut App, - owner: &Addr, - allowed_coins: Vec, - allowed_vaults: Vec, -) -> MockEnv { - let credit_manager_code_id = app.store_code(mock_contract()); - let red_bank = setup_red_bank(app, &allowed_coins); - let oracle = setup_oracle(app, &allowed_coins); - let manager_initiate_msg = InstantiateMsg { - owner: owner.to_string(), - allowed_coins: allowed_coins - .iter() - .map(|item| item.denom.clone()) - .collect(), - allowed_vaults, - red_bank: RedBankBase::new(red_bank.to_string()), - oracle: OracleBase::new(oracle.to_string()), - }; - - let credit_manager = app - .instantiate_contract( - credit_manager_code_id, - owner.clone(), - &manager_initiate_msg, - &[], - "manager-mock", - None, - ) - .unwrap(); - - let nft = setup_nft_contract(app, owner, &credit_manager); - MockEnv { - credit_manager, - oracle, - red_bank, - nft, - } -} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs new file mode 100644 index 000000000..7d378e5cd --- /dev/null +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -0,0 +1,544 @@ +use std::mem::take; + +use anyhow::Result as AnyResult; +use cosmwasm_std::{Addr, Coin, Uint128}; +use cw721_base::InstantiateMsg as NftInstantiateMsg; +use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; + +use account_nft::msg::ExecuteMsg as NftExecuteMsg; +use mock_oracle::msg::{ + CoinPrice, ExecuteMsg as OracleExecuteMsg, InstantiateMsg as OracleInstantiateMsg, +}; +use mock_red_bank::msg::QueryMsg::UserAssetDebt; +use mock_red_bank::msg::{ + CoinMarketInfo, InstantiateMsg as RedBankInstantiateMsg, UserAssetDebtResponse, +}; + +use rover::adapters::{OracleBase, RedBankBase}; +use rover::health::Health; +use rover::msg::execute::{Action, CallbackMsg}; +use rover::msg::instantiate::ConfigUpdates; +use rover::msg::query::{ + CoinBalanceResponseItem, CoinShares, ConfigResponse, PositionResponse, SharesResponseItem, +}; +use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +use crate::helpers::{ + mock_account_nft_contract, mock_oracle_contract, mock_red_bank_contract, mock_rover_contract, + AccountToFund, CoinInfo, +}; + +pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000u128); + +pub struct MockEnv { + pub app: BasicApp, + pub rover: Addr, +} + +pub struct MockEnvBuilder { + pub app: BasicApp, + pub owner: Option, + pub allowed_vaults: Option>, + pub allowed_coins: Option>, + pub oracle: Option>, + pub red_bank: Option>, + pub setup_nft_contract: bool, + pub setup_nft_contract_owner: bool, + pub accounts_to_fund: Vec, +} + +#[allow(clippy::new_ret_no_self)] +impl MockEnv { + pub fn new() -> MockEnvBuilder { + MockEnvBuilder { + app: App::default(), + owner: None, + allowed_vaults: None, + allowed_coins: None, + oracle: None, + red_bank: None, + setup_nft_contract: true, + setup_nft_contract_owner: true, + accounts_to_fund: vec![], + } + } + + //-------------------------------------------------------------------------------------------------- + // Execute Msgs + //-------------------------------------------------------------------------------------------------- + + pub fn update_credit_account( + &mut self, + token_id: &str, + sender: &Addr, + actions: Vec, + send_funds: &[Coin], + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.rover.clone(), + &ExecuteMsg::UpdateCreditAccount { + token_id: token_id.to_string(), + actions, + }, + send_funds, + ) + } + + pub fn update_config( + &mut self, + sender: &Addr, + new_config: ConfigUpdates, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.rover.clone(), + &ExecuteMsg::UpdateConfig { new_config }, + &[], + ) + } + + pub fn setup_new_nft_contract(&mut self) -> AnyResult { + let nft_contract = setup_nft_contract(&mut self.app, &self.rover.clone()); + propose_new_nft_contract_owner( + &mut self.app, + nft_contract.clone(), + &self.rover.clone(), + &self.rover.clone(), + ); + Ok(nft_contract) + } + + pub fn create_credit_account(&mut self, sender: &Addr) -> AnyResult { + let res = self.app.execute_contract( + sender.clone(), + self.rover.clone(), + &ExecuteMsg::CreateCreditAccount {}, + &[], + )?; + Ok(self.get_token_id(res)) + } + + fn get_token_id(&mut self, res: AppResponse) -> String { + let attr: Vec<&String> = res + .events + .iter() + .flat_map(|event| &event.attributes) + .filter(|attr| attr.key == "token_id") + .map(|attr| &attr.value) + .collect(); + + assert_eq!(attr.len(), 1); + attr.first().unwrap().to_string() + } + + pub fn price_change(&mut self, coin: CoinPrice) { + let config = self.query_config(); + self.app + .execute_contract( + Addr::unchecked("anyone"), + Addr::unchecked(config.oracle), + &OracleExecuteMsg::ChangePrice(coin), + &[], + ) + .unwrap(); + } + + pub fn execute_callback(&mut self, sender: &Addr, msg: CallbackMsg) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.rover.clone(), + &ExecuteMsg::Callback(msg), + &[], + ) + } + + //-------------------------------------------------------------------------------------------------- + // Queries + //-------------------------------------------------------------------------------------------------- + + pub fn query_position(&self, token_id: &str) -> PositionResponse { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::Position { + token_id: token_id.to_string(), + }, + ) + .unwrap() + } + + pub fn query_health(&self, token_id: &str) -> Health { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::Health { + token_id: token_id.to_string(), + }, + ) + .unwrap() + } + + pub fn query_balance(&self, addr: &Addr, denom: &str) -> Coin { + self.app.wrap().query_balance(addr.clone(), denom).unwrap() + } + + pub fn query_config(&self) -> ConfigResponse { + self.app + .wrap() + .query_wasm_smart(self.rover.clone(), &QueryMsg::Config {}) + .unwrap() + } + + pub fn query_allowed_vaults( + &self, + start_after: Option, + limit: Option, + ) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::AllowedVaults { start_after, limit }, + ) + .unwrap() + } + + pub fn query_allowed_coins( + &self, + start_after: Option, + limit: Option, + ) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::AllowedCoins { start_after, limit }, + ) + .unwrap() + } + + pub fn query_all_coin_balances( + &self, + start_after: Option<(String, String)>, + limit: Option, + ) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::AllCoinBalances { start_after, limit }, + ) + .unwrap() + } + + pub fn query_all_debt_shares( + &self, + start_after: Option<(String, String)>, + limit: Option, + ) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::AllDebtShares { start_after, limit }, + ) + .unwrap() + } + + pub fn query_all_total_debt_shares( + &self, + start_after: Option, + limit: Option, + ) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::AllTotalDebtShares { start_after, limit }, + ) + .unwrap() + } + + pub fn query_total_debt_shares(&self, denom: &str) -> CoinShares { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::TotalDebtShares(denom.to_string()), + ) + .unwrap() + } + + pub fn query_red_bank_debt(&self, denom: &str) -> UserAssetDebtResponse { + let config = self.query_config(); + self.app + .wrap() + .query_wasm_smart( + config.red_bank, + &UserAssetDebt { + user_address: self.rover.to_string(), + denom: denom.into(), + }, + ) + .unwrap() + } +} + +impl MockEnvBuilder { + pub fn build(&mut self) -> AnyResult { + let rover = self.get_rover()?; + self.deploy_nft_contract(&rover); + self.fund_users(); + + Ok(MockEnv { + app: take(&mut self.app), + rover, + }) + } + + //-------------------------------------------------------------------------------------------------- + // Execute Msgs + //-------------------------------------------------------------------------------------------------- + + fn fund_users(&mut self) { + for account in &self.accounts_to_fund { + self.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: account.addr.to_string(), + amount: account.funds.clone(), + })) + .unwrap(); + } + } + + fn deploy_nft_contract(&mut self, rover: &Addr) { + let nft_contract_owner = Addr::unchecked("original_nft_contract_owner"); + + if self.setup_nft_contract { + let nft_contract = setup_nft_contract(&mut self.app, &nft_contract_owner); + if self.setup_nft_contract_owner { + propose_new_nft_contract_owner( + &mut self.app, + nft_contract.clone(), + &nft_contract_owner, + rover, + ); + // Update config to save new nft_contract + self.app + .execute_contract( + self.get_owner(), + rover.clone(), + &ExecuteMsg::UpdateConfig { + new_config: ConfigUpdates { + account_nft: Some(nft_contract.to_string()), + ..Default::default() + }, + }, + &[], + ) + .unwrap(); + } + } + } + + //-------------------------------------------------------------------------------------------------- + // Get or defaults + //-------------------------------------------------------------------------------------------------- + + fn get_rover(&mut self) -> AnyResult { + let code_id = self.app.store_code(mock_rover_contract()); + let oracle = self.get_oracle().into(); + let red_bank = self.get_red_bank().into(); + let allowed_coins = self + .get_allowed_coins() + .iter() + .map(|info| info.denom.clone()) + .collect(); + + self.app.instantiate_contract( + code_id, + self.get_owner(), + &InstantiateMsg { + owner: self.get_owner().to_string(), + allowed_coins, + allowed_vaults: self.get_allowed_vaults(), + red_bank, + oracle, + }, + &[], + "mock-rover-contract", + None, + ) + } + + fn get_owner(&self) -> Addr { + self.owner + .clone() + .unwrap_or_else(|| Addr::unchecked("owner")) + } + + fn get_oracle(&mut self) -> OracleBase { + self.oracle.clone().unwrap_or_else(|| self.setup_oracle()) + } + + fn setup_oracle(&mut self) -> OracleBase { + let contract_code_id = self.app.store_code(mock_oracle_contract()); + let addr = self + .app + .instantiate_contract( + contract_code_id, + Addr::unchecked("oracle_contract_owner"), + &OracleInstantiateMsg { + coins: self + .get_allowed_coins() + .iter() + .map(|item| CoinPrice { + denom: item.denom.clone(), + price: item.price, + }) + .collect(), + }, + &[], + "mock-oracle", + None, + ) + .unwrap(); + OracleBase::new(addr) + } + + fn get_red_bank(&mut self) -> RedBankBase { + self.red_bank + .clone() + .unwrap_or_else(|| self.setup_red_bank()) + } + + pub fn setup_red_bank(&mut self) -> RedBankBase { + let contract_code_id = self.app.store_code(mock_red_bank_contract()); + let addr = self + .app + .instantiate_contract( + contract_code_id, + Addr::unchecked("red_bank_contract_owner"), + &RedBankInstantiateMsg { + coins: self + .get_allowed_coins() + .iter() + .map(|item| CoinMarketInfo { + denom: item.denom.to_string(), + max_ltv: item.max_ltv, + liquidation_threshold: item.liquidation_threshold, + }) + .collect(), + }, + &[], + "mock-red-bank", + None, + ) + .unwrap(); + + // fund red bank with whitelisted coins + if !self.get_allowed_coins().is_empty() { + self.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: addr.to_string(), + amount: self + .get_allowed_coins() + .iter() + .map(|info| info.to_coin(DEFAULT_RED_BANK_COIN_BALANCE)) + .collect(), + })) + .unwrap(); + } + + RedBankBase::new(addr) + } + + fn get_allowed_vaults(&self) -> Vec { + self.allowed_vaults.clone().unwrap_or_default() + } + + fn get_allowed_coins(&self) -> Vec { + self.allowed_coins.clone().unwrap_or_default() + } + + //-------------------------------------------------------------------------------------------------- + // Setter functions + //-------------------------------------------------------------------------------------------------- + + pub fn fund_account(&mut self, account: AccountToFund) -> &mut Self { + self.accounts_to_fund.push(account); + self + } + + pub fn owner(&mut self, owner: &str) -> &mut Self { + self.owner = Some(Addr::unchecked(owner)); + self + } + + pub fn allowed_vaults(&mut self, allowed_vaults: &[String]) -> &mut Self { + self.allowed_vaults = Some(allowed_vaults.to_vec()); + self + } + + pub fn allowed_coins(&mut self, allowed_coins: &[CoinInfo]) -> &mut Self { + self.allowed_coins = Some(allowed_coins.to_vec()); + self + } + + pub fn red_bank(&mut self, red_bank: &str) -> &mut Self { + self.red_bank = Some(RedBankBase::new(Addr::unchecked(red_bank))); + self + } + + pub fn oracle(&mut self, oracle: &str) -> &mut Self { + self.oracle = Some(OracleBase::new(Addr::unchecked(oracle))); + self + } + + pub fn no_nft_contract(&mut self) -> &mut Self { + self.setup_nft_contract = false; + self + } + + pub fn no_nft_contract_owner(&mut self) -> &mut Self { + self.setup_nft_contract_owner = false; + self + } +} + +//-------------------------------------------------------------------------------------------------- +// Shared utils between MockBuilder & MockEnv +//-------------------------------------------------------------------------------------------------- + +fn setup_nft_contract(app: &mut App, owner: &Addr) -> Addr { + let nft_contract_code_id = app.store_code(mock_account_nft_contract()); + app.instantiate_contract( + nft_contract_code_id, + owner.clone(), + &NftInstantiateMsg { + name: "Rover Credit Account".to_string(), + symbol: "RCA".to_string(), + minter: owner.to_string(), + }, + &[], + "manager-mock-account-nft", + None, + ) + .unwrap() +} + +fn propose_new_nft_contract_owner( + app: &mut App, + nft_contract: Addr, + nft_contract_owner: &Addr, + rover: &Addr, +) { + let proposal_msg: NftExecuteMsg = NftExecuteMsg::ProposeNewOwner { + new_owner: rover.to_string(), + }; + app.execute_contract(nft_contract_owner.clone(), nft_contract, &proposal_msg, &[]) + .unwrap(); +} diff --git a/contracts/credit-manager/tests/helpers/mod.rs b/contracts/credit-manager/tests/helpers/mod.rs index d356ad15e..f9967ae9e 100644 --- a/contracts/credit-manager/tests/helpers/mod.rs +++ b/contracts/credit-manager/tests/helpers/mod.rs @@ -1,13 +1,11 @@ pub use self::assertions::*; pub use self::builders::*; pub use self::contracts::*; -pub use self::deploys::*; -pub use self::queries::*; +pub use self::mock_env::*; pub use self::types::*; mod assertions; mod builders; mod contracts; -mod deploys; -mod queries; +mod mock_env; mod types; diff --git a/contracts/credit-manager/tests/helpers/queries.rs b/contracts/credit-manager/tests/helpers/queries.rs deleted file mode 100644 index ea7fc5c17..000000000 --- a/contracts/credit-manager/tests/helpers/queries.rs +++ /dev/null @@ -1,65 +0,0 @@ -use cosmwasm_std::Addr; -use cw_multi_test::{App, AppResponse}; -use mock_red_bank::msg::QueryMsg::UserAssetDebt; -use mock_red_bank::msg::UserAssetDebtResponse; -use rover::health::Health; - -use rover::msg::query::{ConfigResponse, PositionResponse, QueryMsg}; - -pub fn get_token_id(res: AppResponse) -> String { - let attr: Vec<&str> = res - .events - .iter() - .flat_map(|event| &event.attributes) - .filter(|attr| attr.key == "token_id") - .map(|attr| attr.value.as_str()) - .collect(); - - assert_eq!(attr.len(), 1); - attr.first().unwrap().to_string() -} - -pub fn query_position(app: &App, manager_contract_addr: &Addr, token_id: &str) -> PositionResponse { - app.wrap() - .query_wasm_smart( - manager_contract_addr.clone(), - &QueryMsg::Position { - token_id: token_id.to_string(), - }, - ) - .unwrap() -} - -pub fn query_health(app: &App, manager_contract_addr: &Addr, token_id: &str) -> Health { - app.wrap() - .query_wasm_smart( - manager_contract_addr.clone(), - &QueryMsg::Health { - token_id: token_id.to_string(), - }, - ) - .unwrap() -} - -pub fn query_config(app: &App, contract_addr: &Addr) -> ConfigResponse { - app.wrap() - .query_wasm_smart(contract_addr.clone(), &QueryMsg::Config {}) - .unwrap() -} - -pub fn query_red_bank_debt( - app: &App, - credit_manager_addr: &Addr, - red_bank_addr: &str, - denom: &str, -) -> UserAssetDebtResponse { - app.wrap() - .query_wasm_smart( - red_bank_addr, - &UserAssetDebt { - user_address: credit_manager_addr.into(), - denom: denom.into(), - }, - ) - .unwrap() -} diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 2db61739a..47a91a344 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -2,12 +2,10 @@ use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] -pub struct MockEnv { - pub credit_manager: Addr, - pub oracle: Addr, - pub red_bank: Addr, - pub nft: Addr, +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct AccountToFund { + pub addr: Addr, + pub funds: Vec, } #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] diff --git a/contracts/credit-manager/tests/instantiate_test.rs b/contracts/credit-manager/tests/instantiate_test.rs deleted file mode 100644 index a42572aac..000000000 --- a/contracts/credit-manager/tests/instantiate_test.rs +++ /dev/null @@ -1,300 +0,0 @@ -use cosmwasm_std::Addr; -use cw_multi_test::Executor; - -use rover::adapters::{OracleBase, RedBankBase}; -use rover::msg::query::{ConfigResponse, QueryMsg}; -use rover::msg::InstantiateMsg; - -use crate::helpers::{assert_contents_equal, mock_app, mock_contract}; - -pub mod helpers; - -#[test] -fn test_owner_set_on_instantiate() { - let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); - let owner = Addr::unchecked("owner"); - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec![], - allowed_coins: vec![], - red_bank: RedBankBase::new("red_bank_contract".to_string()), - oracle: OracleBase::new("oracle_contract".to_string()), - }; - - let contract_addr = app - .instantiate_contract(code_id, owner.clone(), &msg, &[], "mock-account-nft", None) - .unwrap(); - - let res: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr, &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(owner, res.owner); -} - -#[test] -fn test_raises_on_invalid_owner_addr() { - let mut app = mock_app(); - let manager_code_id = app.store_code(mock_contract()); - let owner = Addr::unchecked("%%%INVALID%%%"); - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec![], - allowed_coins: vec![], - red_bank: RedBankBase::new("red_bank_contract".to_string()), - oracle: OracleBase::new("oracle_contract".to_string()), - }; - - let instantiate_res = - app.instantiate_contract(manager_code_id, owner, &msg, &[], "mock-contract", None); - - if instantiate_res.is_ok() { - panic!("Should have thrown an error"); - } -} - -#[test] -fn test_nft_contract_addr_not_set_on_instantiate() { - let mut app = mock_app(); - let owner = Addr::unchecked("owner"); - let code_id = app.store_code(mock_contract()); - - let contract_addr = app - .instantiate_contract( - code_id, - owner.clone(), - &InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec![], - allowed_coins: vec![], - red_bank: RedBankBase::new("red_bank_contract".to_string()), - oracle: OracleBase::new("oracle_contract".to_string()), - }, - &[], - "manager-mock-account-nft", - None, - ) - .unwrap(); - - let res: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr, &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(res.account_nft, None); -} - -#[test] -fn test_allowed_vaults_set_on_instantiate() { - let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); - let owner = Addr::unchecked("owner"); - - let allowed_vaults = vec![ - "vaultcontract1".to_string(), - "vaultcontract2".to_string(), - "vaultcontract3".to_string(), - ]; - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: allowed_vaults.clone(), - allowed_coins: vec![], - red_bank: RedBankBase::new("red_bank_contract".to_string()), - oracle: OracleBase::new("oracle_contract".to_string()), - }; - - let contract_addr = app - .instantiate_contract( - code_id, - owner, - &msg, - &[], - "mock-credit-manager-contract", - None, - ) - .unwrap(); - - let vaults_res: Vec = app - .wrap() - .query_wasm_smart( - contract_addr, - &QueryMsg::AllowedVaults { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - assert_contents_equal(vaults_res, allowed_vaults); -} - -#[test] -fn test_raises_on_invalid_vaults_addr() { - let mut app = mock_app(); - let manager_code_id = app.store_code(mock_contract()); - let owner = Addr::unchecked("owner"); - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec!["%%%INVALID%%%".to_string()], - allowed_coins: vec![], - red_bank: RedBankBase::new("red_bank_contract".to_string()), - oracle: OracleBase::new("oracle_contract".to_string()), - }; - - let instantiate_res = - app.instantiate_contract(manager_code_id, owner, &msg, &[], "mock-contract", None); - - if instantiate_res.is_ok() { - panic!("Should have thrown an error"); - } -} - -#[test] -fn test_allowed_coins_set_on_instantiate() { - let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); - let owner = Addr::unchecked("owner"); - - let allowed_coins = vec![ - "uosmo".to_string(), - "uatom".to_string(), - "umars".to_string(), - "ujake".to_string(), - ]; - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec![], - allowed_coins: allowed_coins.clone(), - red_bank: RedBankBase::new("red_bank_contract".to_string()), - oracle: OracleBase::new("oracle_contract".to_string()), - }; - - let contract_addr = app - .instantiate_contract( - code_id, - owner, - &msg, - &[], - "mock-credit-manager-contract", - None, - ) - .unwrap(); - - let coins_res: Vec = app - .wrap() - .query_wasm_smart( - contract_addr, - &QueryMsg::AllowedCoins { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - assert_contents_equal(coins_res, allowed_coins) -} - -#[test] -fn test_red_bank_set_on_instantiate() { - let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); - let owner = Addr::unchecked("owner"); - let red_bank_addr = "red_bank_contract".to_string(); - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec![], - allowed_coins: vec![], - red_bank: RedBankBase::new("red_bank_contract".to_string()), - oracle: OracleBase::new("oracle_contract".to_string()), - }; - - let contract_addr = app - .instantiate_contract(code_id, owner, &msg, &[], "mock-account-nft", None) - .unwrap(); - - let res: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr, &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(red_bank_addr, res.red_bank); -} - -#[test] -fn test_raises_on_invalid_red_bank_addr() { - let mut app = mock_app(); - let manager_code_id = app.store_code(mock_contract()); - let owner = Addr::unchecked("owner"); - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_coins: vec![], - allowed_vaults: vec![], - red_bank: RedBankBase::new("%%%INVALID%%%".to_string()), - oracle: OracleBase::new("oracle_contract".to_string()), - }; - - let instantiate_res = - app.instantiate_contract(manager_code_id, owner, &msg, &[], "mock-contract", None); - - if instantiate_res.is_ok() { - panic!("Should have thrown an error"); - } -} - -#[test] -fn test_oracle_set_on_instantiate() { - let mut app = mock_app(); - let code_id = app.store_code(mock_contract()); - let owner = Addr::unchecked("owner"); - let oracle_contract = "oracle_contract".to_string(); - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_coins: vec![], - allowed_vaults: vec![], - red_bank: RedBankBase::new("red_bank_contract".to_string()), - oracle: OracleBase::new("oracle_contract".to_string()), - }; - - let contract_addr = app - .instantiate_contract(code_id, owner, &msg, &[], "mock-account-nft", None) - .unwrap(); - - let res: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr, &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(oracle_contract, res.oracle); -} - -#[test] -fn test_raises_on_invalid_oracle_addr() { - let mut app = mock_app(); - let manager_code_id = app.store_code(mock_contract()); - let owner = Addr::unchecked("owner"); - - let msg = InstantiateMsg { - owner: owner.to_string(), - allowed_vaults: vec![], - allowed_coins: vec![], - red_bank: RedBankBase::new("red_bank_contract".to_string()), - oracle: OracleBase::new("%%%INVALID%%%".to_string()), - }; - - let instantiate_res = - app.instantiate_contract(manager_code_id, owner, &msg, &[], "mock-contract", None); - - if instantiate_res.is_ok() { - panic!("Should have thrown an error"); - } -} diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs new file mode 100644 index 000000000..3b0e0be8e --- /dev/null +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -0,0 +1,288 @@ +use std::ops::{Mul, Sub}; + +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; + +use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; +use rover::error::ContractError; +use rover::msg::execute::Action::{Borrow, Deposit}; + +use crate::helpers::{assert_err, AccountToFund, CoinInfo, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE}; + +pub mod helpers; + +#[test] +fn test_only_token_owner_can_borrow() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let another_user = Addr::unchecked("another_user"); + let res = mock.update_credit_account( + &token_id, + &another_user, + vec![Borrow(coin_info.to_coin(Uint128::new(12312u128)))], + &[], + ); + + assert_err( + res, + ContractError::NotTokenOwner { + user: another_user.into(), + token_id, + }, + ) +} + +#[test] +fn test_can_only_borrow_what_is_whitelisted() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().allowed_coins(&[coin_info]).build().unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![Borrow(Coin { + denom: "usomething".to_string(), + amount: Uint128::from(234u128), + })], + &[], + ); + + assert_err( + res, + ContractError::NotWhitelisted(String::from("usomething")), + ) +} + +#[test] +fn test_borrowing_zero_does_nothing() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![Borrow(coin_info.to_coin(Uint128::zero()))], + &[], + ); + + assert_err(res, ContractError::NoAmount); + + let position = mock.query_position(&token_id); + assert_eq!(position.coins.len(), 0); + assert_eq!(position.debt_shares.len(), 0); +} + +#[test] +fn test_success_when_new_debt_asset() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let position = mock.query_position(&token_id); + assert_eq!(position.coins.len(), 0); + assert_eq!(position.debt_shares.len(), 0); + mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(Coin { + denom: coin_info.denom.clone(), + amount: Uint128::from(300u128), + }), + Borrow(Coin { + denom: coin_info.denom.clone(), + amount: Uint128::from(42u128), + }), + ], + &[Coin::new(300u128, coin_info.denom.clone())], + ) + .unwrap(); + + let position = mock.query_position(&token_id); + assert_eq!(position.coins.len(), 1); + let asset_res = position.coins.first().unwrap(); + assert_eq!( + asset_res.amount, + Uint128::from(342u128) // Deposit + Borrow + ); + assert_eq!(asset_res.denom, coin_info.denom); + assert_eq!(asset_res.price, coin_info.price); + assert_eq!( + asset_res.value, + coin_info.price * Decimal::from_atomics(342u128, 0).unwrap() + ); + + let debt_shares_res = position.debt_shares.first().unwrap(); + assert_eq!(position.debt_shares.len(), 1); + assert_eq!( + debt_shares_res.shares, + Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) + ); + assert_eq!(debt_shares_res.denom, coin_info.denom); + let debt_amount = Uint128::from(42u128) + Uint128::new(1u128); // simulated yield + assert_eq!( + debt_shares_res.total_value, + coin_info.price * Decimal::from_atomics(debt_amount, 0).unwrap() + ); + + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, Uint128::from(342u128)); + + let config = mock.query_config(); + let coin = mock.query_balance(&Addr::unchecked(config.red_bank), &coin_info.denom); + assert_eq!( + coin.amount, + DEFAULT_RED_BANK_COIN_BALANCE.sub(Uint128::from(42u128)) + ); + + let res = mock.query_total_debt_shares(&coin_info.denom); + assert_eq!( + res.shares, + Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) + ); +} + +#[test] +fn test_debt_shares_with_debt_amount() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: vec![Coin::new(450u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id_a = mock.create_credit_account(&user_a).unwrap(); + let token_id_b = mock.create_credit_account(&user_b).unwrap(); + + mock.update_credit_account( + &token_id_a, + &user_a, + vec![ + Deposit(coin_info.to_coin(Uint128::from(300u128))), + Borrow(coin_info.to_coin(Uint128::from(50u128))), + ], + &[Coin::new(300u128, coin_info.denom.clone())], + ) + .unwrap(); + + let interim_red_bank_debt = mock.query_red_bank_debt(&coin_info.denom); + + mock.update_credit_account( + &token_id_b, + &user_b, + vec![ + Deposit(coin_info.to_coin(Uint128::from(450u128))), + Borrow(coin_info.to_coin(Uint128::from(50u128))), + ], + &[Coin::new(450u128, coin_info.denom.clone())], + ) + .unwrap(); + + let token_a_shares = Uint128::from(50u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED); + let position = mock.query_position(&token_id_a); + let debt_position_a = position.debt_shares.first().unwrap(); + assert_eq!(debt_position_a.shares, token_a_shares.clone()); + assert_eq!(debt_position_a.denom, coin_info.denom); + + let token_b_shares = Uint128::from(50u128) + .mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) + .multiply_ratio(Uint128::from(50u128), interim_red_bank_debt.amount); + let position = mock.query_position(&token_id_b); + let debt_position_b = position.debt_shares.first().unwrap(); + assert_eq!(debt_position_b.shares, token_b_shares.clone()); + assert_eq!(debt_position_b.denom, coin_info.denom); + + let total = mock.query_total_debt_shares(&coin_info.denom); + + assert_eq!( + total.shares, + debt_position_a.shares + debt_position_b.shares + ); + + let red_bank_debt = mock.query_red_bank_debt(&coin_info.denom); + + let a_amount_owed = red_bank_debt + .amount + .multiply_ratio(debt_position_a.shares, total.shares); + assert_eq!( + debt_position_a.total_value, + coin_info.price * Decimal::from_atomics(a_amount_owed, 0).unwrap() + ); + + let b_amount_owed = red_bank_debt + .amount + .multiply_ratio(debt_position_b.shares, total.shares); + assert_eq!( + debt_position_b.total_value, + coin_info.price * Decimal::from_atomics(b_amount_owed, 0).unwrap() + ); + + // NOTE: There is an expected rounding error. This will not pass. + // let total_borrowed_plus_interest = Decimal::from_atomics(Uint128::from(102u128), 0).unwrap(); + // assert_eq!( + // total_borrowed_plus_interest * coin_info.price, + // debt_position_a.total_value + debt_position_b.total_value + // ) + // This test below asserts the rounding down that's happening + let total_owed = Decimal::from_atomics(a_amount_owed + b_amount_owed, 0).unwrap(); + assert_eq!( + total_owed * coin_info.price, + debt_position_a.total_value + debt_position_b.total_value + ) +} diff --git a/contracts/credit-manager/tests/test_create_credit_account.rs b/contracts/credit-manager/tests/test_create_credit_account.rs new file mode 100644 index 000000000..392f8ac9d --- /dev/null +++ b/contracts/credit-manager/tests/test_create_credit_account.rs @@ -0,0 +1,54 @@ +use crate::helpers::MockEnv; +use cosmwasm_std::Addr; +use cw721::OwnerOfResponse; +use cw721_base::QueryMsg as NftQueryMsg; + +pub mod helpers; + +#[test] +fn test_create_credit_account_fails_without_nft_contract_set() { + let mut mock = MockEnv::new().no_nft_contract().build().unwrap(); + let user = Addr::unchecked("user"); + let res = mock.create_credit_account(&user); + + if res.is_ok() { + panic!("Should have thrown error due to nft contract not yet set"); + } +} + +#[test] +fn test_create_credit_account_fails_without_nft_contract_owner() { + let mut mock = MockEnv::new().no_nft_contract_owner().build().unwrap(); + + let user = Addr::unchecked("user"); + let res = mock.create_credit_account(&user); + + if res.is_ok() { + panic!("Should have thrown error due to nft contract not proposing a new owner yet"); + } +} + +#[test] +fn test_create_credit_account_success() { + let mut mock = MockEnv::new().build().unwrap(); + + let user = Addr::unchecked("user"); + let token_id = mock.create_credit_account(&user).unwrap(); + + // Double checking ownership by querying NFT account-nft for correct owner + let config = mock.query_config(); + + let owner_res: OwnerOfResponse = mock + .app + .wrap() + .query_wasm_smart( + config.account_nft.unwrap(), + &NftQueryMsg::OwnerOf { + token_id, + include_expired: None, + }, + ) + .unwrap(); + + assert_eq!(user, owner_res.owner) +} diff --git a/contracts/credit-manager/tests/test_deposit.rs b/contracts/credit-manager/tests/test_deposit.rs new file mode 100644 index 000000000..bdd544e31 --- /dev/null +++ b/contracts/credit-manager/tests/test_deposit.rs @@ -0,0 +1,332 @@ +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; + +use rover::coins::Coins; +use rover::error::ContractError::{ + ExtraFundsReceived, FundsMismatch, NotTokenOwner, NotWhitelisted, +}; +use rover::msg::execute::Action; +use rover::msg::query::PositionResponse; + +use crate::helpers::{assert_err, AccountToFund, CoinInfo, MockEnv}; + +pub mod helpers; + +#[test] +fn test_only_owner_of_token_can_deposit() { + let mut mock = MockEnv::new().build().unwrap(); + let user = Addr::unchecked("user"); + let token_id = mock.create_credit_account(&user).unwrap(); + + let another_user = Addr::unchecked("another_user"); + let res = mock.update_credit_account( + &token_id, + &another_user, + vec![Action::Deposit(Coin { + denom: "uosmo".to_string(), + amount: Uint128::zero(), + })], + &[], + ); + + assert_err( + res, + NotTokenOwner { + user: another_user.into(), + token_id, + }, + ) +} + +#[test] +fn test_deposit_nothing() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .build() + .unwrap(); + let user = Addr::unchecked("user"); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); + + mock.update_credit_account( + &token_id, + &user, + vec![Action::Deposit(coin_info.to_coin(Uint128::zero()))], + &[], + ) + .unwrap(); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); +} + +#[test] +fn test_deposit_but_no_funds() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .build() + .unwrap(); + let user = Addr::unchecked("user"); + let token_id = mock.create_credit_account(&user).unwrap(); + + let deposit_amount = Uint128::from(234u128); + let res = mock.update_credit_account( + &token_id, + &user, + vec![Action::Deposit(coin_info.to_coin(deposit_amount))], + &[], + ); + + assert_err( + res, + FundsMismatch { + expected: deposit_amount, + received: Uint128::zero(), + }, + ); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); +} + +#[test] +fn test_deposit_but_not_enough_funds() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![Action::Deposit(coin_info.to_coin(Uint128::from(350u128)))], + &[Coin::new(250u128, coin_info.denom)], + ); + + assert_err( + res, + FundsMismatch { + expected: Uint128::from(350u128), + received: Uint128::from(250u128), + }, + ); +} + +#[test] +fn test_can_only_deposit_allowed_assets() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let not_allowed_coin = Coin { + denom: "ujakecoin".to_string(), + amount: Uint128::from(234u128), + }; + + let res = mock.update_credit_account( + &token_id, + &user, + vec![Action::Deposit(not_allowed_coin.clone())], + &[Coin::new(250u128, coin_info.denom)], + ); + + assert_err(res, NotWhitelisted(not_allowed_coin.denom)); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); +} + +#[test] +fn test_extra_funds_received() { + let uosmo_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + let uatom_info = CoinInfo { + denom: "uatom".to_string(), + price: Decimal::from_atomics(10u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), + liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![ + Coin::new(300u128, uosmo_info.denom.clone()), + Coin::new(250u128, uatom_info.denom.clone()), + ], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let extra_funds = Coin::new(25u128, uatom_info.denom); + let res = mock.update_credit_account( + &token_id, + &user, + vec![Action::Deposit(uosmo_info.to_coin(Uint128::from(234u128)))], + &[Coin::new(234u128, uosmo_info.denom), extra_funds.clone()], + ); + + assert_err(res, ExtraFundsReceived(Coins::from(vec![extra_funds]))); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); +} + +#[test] +fn test_deposit_success() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let deposit_amount = Uint128::from(234u128); + mock.update_credit_account( + &token_id, + &user, + vec![Action::Deposit(coin_info.to_coin(deposit_amount))], + &[Coin::new(deposit_amount.into(), coin_info.denom.clone())], + ) + .unwrap(); + + let res = mock.query_position(&token_id); + let assets_res = res.coins.first().unwrap(); + assert_eq!(res.coins.len(), 1); + assert_eq!(assets_res.amount, deposit_amount); + assert_eq!(assets_res.denom, coin_info.denom); + assert_eq!(assets_res.price, coin_info.price); + assert_eq!( + assets_res.value, + coin_info.price * Decimal::from_atomics(deposit_amount, 0).unwrap() + ); + + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, deposit_amount) +} + +#[test] +fn test_multiple_deposit_actions() { + let uosmo_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + let uatom_info = CoinInfo { + denom: "uatom".to_string(), + price: Decimal::from_atomics(10u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), + liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![ + Coin::new(300u128, uosmo_info.denom.clone()), + Coin::new(50u128, uatom_info.denom.clone()), + ], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let uosmo_amount = Uint128::from(234u128); + let uatom_amount = Uint128::from(25u128); + + mock.update_credit_account( + &token_id, + &user, + vec![ + Action::Deposit(uosmo_info.to_coin(uosmo_amount)), + Action::Deposit(uatom_info.to_coin(uatom_amount)), + ], + &[ + Coin::new(234u128, uosmo_info.denom.clone()), + Coin::new(25u128, uatom_info.denom.clone()), + ], + ) + .unwrap(); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 2); + let uosmo_value = Decimal::from_atomics(uosmo_amount, 0).unwrap() * uosmo_info.price; + assert_present(&res, &uosmo_info, uosmo_amount, uosmo_value); + let uatom_value = Decimal::from_atomics(uatom_amount, 0).unwrap() * uatom_info.price; + assert_present(&res, &uatom_info, uatom_amount, uatom_value); + + let coin = mock.query_balance(&mock.rover, &uosmo_info.denom); + assert_eq!(coin.amount, uosmo_amount); + + let coin = mock.query_balance(&mock.rover, &uatom_info.denom); + assert_eq!(coin.amount, uatom_amount); +} + +fn assert_present(res: &PositionResponse, coin: &CoinInfo, amount: Uint128, total_val: Decimal) { + res.coins + .iter() + .find(|item| item.denom == coin.denom && item.amount == amount && item.value == total_val) + .unwrap(); +} diff --git a/contracts/credit-manager/tests/test_dispatch.rs b/contracts/credit-manager/tests/test_dispatch.rs new file mode 100644 index 000000000..81b46d895 --- /dev/null +++ b/contracts/credit-manager/tests/test_dispatch.rs @@ -0,0 +1,62 @@ +use cosmwasm_std::{Addr, Coin, Uint128}; + +use helpers::assert_err; +use rover::error::ContractError; +use rover::error::ContractError::NotTokenOwner; +use rover::msg::execute::CallbackMsg; + +use crate::helpers::MockEnv; + +pub mod helpers; + +#[test] +fn test_dispatch_only_allowed_for_token_owner() { + let mut mock = MockEnv::new().build().unwrap(); + let user = Addr::unchecked("user"); + let token_id = mock.create_credit_account(&user).unwrap(); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.update_credit_account(&token_id, &bad_guy, vec![], &[]); + + assert_err( + res, + NotTokenOwner { + user: bad_guy.into(), + token_id, + }, + ) +} + +#[test] +fn test_nothing_happens_if_no_actions_are_passed() { + let mut mock = MockEnv::new().build().unwrap(); + let user = Addr::unchecked("user"); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); + + mock.update_credit_account(&token_id, &user, vec![], &[]) + .unwrap(); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); +} + +#[test] +fn test_only_rover_can_execute_callbacks() { + let mut mock = MockEnv::new().build().unwrap(); + let external_user = Addr::unchecked("external_user"); + + let res = mock.execute_callback( + &external_user, + CallbackMsg::Borrow { + token_id: "1234".to_string(), + coin: Coin { + denom: "uatom".to_string(), + amount: Uint128::new(1000u128), + }, + }, + ); + assert_err(res, ContractError::ExternalInvocation); +} diff --git a/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs b/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs new file mode 100644 index 000000000..9d89db373 --- /dev/null +++ b/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs @@ -0,0 +1,47 @@ +use crate::helpers::{build_mock_coin_infos, MockEnv}; + +pub mod helpers; + +#[test] +fn test_pagination_on_allowed_coins_query_works() { + let allowed_coins = build_mock_coin_infos(32); + let mock = MockEnv::new() + .allowed_coins(&build_mock_coin_infos(32)) + .build() + .unwrap(); + + let coins_res = mock.query_allowed_coins(None, Some(58_u32)); + + // Assert maximum is observed + assert_eq!(coins_res.len(), 30); + + let coins_res = mock.query_allowed_coins(None, Some(2_u32)); + + // Assert limit request is observed + assert_eq!(coins_res.len(), 2); + + let coins_res_a = mock.query_allowed_coins(None, None); + let coins_res_b = mock.query_allowed_coins(Some(coins_res_a.last().unwrap().clone()), None); + let coins_res_c = mock.query_allowed_coins(Some(coins_res_b.last().unwrap().clone()), None); + let coins_res_d = mock.query_allowed_coins(Some(coins_res_c.last().unwrap().clone()), None); + + // Assert default is observed + assert_eq!(coins_res_a.len(), 10); + assert_eq!(coins_res_b.len(), 10); + assert_eq!(coins_res_c.len(), 10); + + assert_eq!(coins_res_d.len(), 2); + + let combined: Vec = coins_res_a + .iter() + .cloned() + .chain(coins_res_b.iter().cloned()) + .chain(coins_res_c.iter().cloned()) + .chain(coins_res_d.iter().cloned()) + .collect(); + + assert_eq!(combined.len(), allowed_coins.len()); + assert!(allowed_coins + .iter() + .all(|item| combined.contains(&item.denom))); +} diff --git a/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs b/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs new file mode 100644 index 000000000..b1a68cd94 --- /dev/null +++ b/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs @@ -0,0 +1,79 @@ +use crate::helpers::MockEnv; + +pub mod helpers; + +#[test] +fn test_pagination_on_allowed_vaults_query_works() { + let allowed_vaults = vec![ + "addr1".to_string(), + "addr2".to_string(), + "addr3".to_string(), + "addr4".to_string(), + "addr5".to_string(), + "addr6".to_string(), + "addr7".to_string(), + "addr8".to_string(), + "addr9".to_string(), + "addr10".to_string(), + "addr11".to_string(), + "addr12".to_string(), + "addr13".to_string(), + "addr14".to_string(), + "addr15".to_string(), + "addr16".to_string(), + "addr17".to_string(), + "addr18".to_string(), + "addr19".to_string(), + "addr20".to_string(), + "addr21".to_string(), + "addr22".to_string(), + "addr23".to_string(), + "addr24".to_string(), + "addr25".to_string(), + "addr26".to_string(), + "addr27".to_string(), + "addr28".to_string(), + "addr29".to_string(), + "addr30".to_string(), + "addr31".to_string(), + "addr32".to_string(), + ]; + + let mock = MockEnv::new() + .allowed_vaults(&allowed_vaults) + .build() + .unwrap(); + + let vaults_res = mock.query_allowed_vaults(None, Some(58_u32)); + + // Assert maximum is observed + assert_eq!(vaults_res.len(), 30); + + let vaults_res = mock.query_allowed_vaults(None, Some(2_u32)); + + // Assert limit request is observed + assert_eq!(vaults_res.len(), 2); + + let vaults_res_a = mock.query_allowed_vaults(None, None); + let vaults_res_b = mock.query_allowed_vaults(Some(vaults_res_a.last().unwrap().clone()), None); + let vaults_res_c = mock.query_allowed_vaults(Some(vaults_res_b.last().unwrap().clone()), None); + let vaults_res_d = mock.query_allowed_vaults(Some(vaults_res_c.last().unwrap().clone()), None); + + // Assert default is observed + assert_eq!(vaults_res_a.len(), 10); + assert_eq!(vaults_res_b.len(), 10); + assert_eq!(vaults_res_c.len(), 10); + + assert_eq!(vaults_res_d.len(), 2); + + let combined: Vec = vaults_res_a + .iter() + .cloned() + .chain(vaults_res_b.iter().cloned()) + .chain(vaults_res_c.iter().cloned()) + .chain(vaults_res_d.iter().cloned()) + .collect(); + + assert_eq!(combined.len(), allowed_vaults.len()); + assert!(allowed_vaults.iter().all(|item| combined.contains(item))); +} diff --git a/contracts/credit-manager/tests/enumerate_coin_balances_test.rs b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs similarity index 50% rename from contracts/credit-manager/tests/enumerate_coin_balances_test.rs rename to contracts/credit-manager/tests/test_enumerate_coin_balances.rs index dab84d73c..7139cca20 100644 --- a/contracts/credit-manager/tests/enumerate_coin_balances_test.rs +++ b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs @@ -1,13 +1,9 @@ use cosmwasm_std::{Addr, Coin, Uint128}; -use cw_multi_test::{App, Executor}; use rover::msg::execute::Action; use rover::msg::query::CoinBalanceResponseItem; -use rover::msg::{ExecuteMsg, QueryMsg}; -use crate::helpers::{ - build_mock_coin_infos, get_token_id, mock_create_credit_account, setup_credit_manager, -}; +use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; pub mod helpers; @@ -58,153 +54,85 @@ fn test_pagination_on_all_coin_balances_query_works() { Coin::new(1u128, "coin_8"), ]; - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &user_a, user_a_coins.clone()) - .unwrap(); - router - .bank - .init_balance(storage, &user_b, user_b_coins.clone()) - .unwrap(); - router - .bank - .init_balance(storage, &user_c, user_c_coins.clone()) - .unwrap(); - }); - - let mock_coin_infos = build_mock_coin_infos(14); - - let mock = setup_credit_manager(&mut app, &Addr::unchecked("owner"), mock_coin_infos, vec![]); + let mut mock = MockEnv::new() + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: user_a_coins.clone(), + }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: user_b_coins.clone(), + }) + .fund_account(AccountToFund { + addr: user_c.clone(), + funds: user_c_coins.clone(), + }) + .allowed_coins(&build_mock_coin_infos(14)) + .build() + .unwrap(); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); - let token_id_a = get_token_id(res); - app.execute_contract( - user_a.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_a.clone(), - actions: user_a_coins - .iter() - .map(|coin| Action::Deposit(coin.clone())) - .collect(), - }, + let token_id_a = mock.create_credit_account(&user_a).unwrap(); + mock.update_credit_account( + &token_id_a, + &user_a, + user_a_coins + .iter() + .map(|coin| Action::Deposit(coin.clone())) + .collect(), &user_a_coins, ) .unwrap(); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_b).unwrap(); - let token_id_b = get_token_id(res); - app.execute_contract( - user_b.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_b.clone(), - actions: user_b_coins - .iter() - .map(|coin| Action::Deposit(coin.clone())) - .collect(), - }, + let token_id_b = mock.create_credit_account(&user_b).unwrap(); + mock.update_credit_account( + &token_id_b, + &user_b, + user_b_coins + .iter() + .map(|coin| Action::Deposit(coin.clone())) + .collect(), &user_b_coins, ) .unwrap(); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_c).unwrap(); - let token_id_c = get_token_id(res); - app.execute_contract( - user_c.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_c.clone(), - actions: user_c_coins - .iter() - .map(|coin| Action::Deposit(coin.clone())) - .collect(), - }, + let token_id_c = mock.create_credit_account(&user_c).unwrap(); + mock.update_credit_account( + &token_id_c, + &user_c, + user_c_coins + .iter() + .map(|coin| Action::Deposit(coin.clone())) + .collect(), &user_c_coins, ) .unwrap(); - let all_assets_res: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllCoinBalances { - start_after: None, - limit: Some(58u32), - }, - ) - .unwrap(); + let all_assets_res = mock.query_all_coin_balances(None, Some(58_u32)); // Assert maximum is observed assert_eq!(all_assets_res.len(), 30); - let all_assets_res: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllCoinBalances { - start_after: None, - limit: Some(2u32), - }, - ) - .unwrap(); + let all_assets_res = mock.query_all_coin_balances(None, Some(2_u32)); // Assert limit request is observed assert_eq!(all_assets_res.len(), 2); - let all_assets_res_a: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllCoinBalances { - start_after: None, - limit: None, - }, - ) - .unwrap(); + let all_assets_res_a = mock.query_all_coin_balances(None, None); let CoinBalanceResponseItem { token_id, denom, .. } = all_assets_res_a.last().unwrap().clone(); - let all_assets_res_b: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllCoinBalances { - start_after: Some((token_id, denom)), - limit: None, - }, - ) - .unwrap(); + let all_assets_res_b = mock.query_all_coin_balances(Some((token_id, denom)), None); let CoinBalanceResponseItem { token_id, denom, .. } = all_assets_res_b.last().unwrap().clone(); - let all_assets_res_c: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllCoinBalances { - start_after: Some((token_id, denom)), - limit: None, - }, - ) - .unwrap(); + let all_assets_res_c = mock.query_all_coin_balances(Some((token_id, denom)), None); let CoinBalanceResponseItem { token_id, denom, .. } = all_assets_res_c.last().unwrap().clone(); - let all_assets_res_d: Vec = app - .wrap() - .query_wasm_smart( - mock.credit_manager.clone(), - &QueryMsg::AllCoinBalances { - start_after: Some((token_id, denom)), - limit: None, - }, - ) - .unwrap(); + let all_assets_res_d = mock.query_all_coin_balances(Some((token_id, denom)), None); // Assert default is observed assert_eq!(all_assets_res_a.len(), 10); diff --git a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs new file mode 100644 index 000000000..89ff025ec --- /dev/null +++ b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs @@ -0,0 +1,215 @@ +use cosmwasm_std::{Addr, Coin, Uint128}; + +use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; +use rover::msg::execute::Action; +use rover::msg::query::SharesResponseItem; + +use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn test_pagination_on_all_debt_shares_query_works() { + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let user_c = Addr::unchecked("user_c"); + + let user_a_coins = vec![ + Coin::new(10u128, "coin_1"), + Coin::new(10u128, "coin_2"), + Coin::new(10u128, "coin_3"), + Coin::new(10u128, "coin_4"), + Coin::new(10u128, "coin_5"), + Coin::new(10u128, "coin_6"), + Coin::new(10u128, "coin_7"), + Coin::new(10u128, "coin_8"), + Coin::new(10u128, "coin_9"), + Coin::new(10u128, "coin_10"), + Coin::new(10u128, "coin_11"), + Coin::new(10u128, "coin_12"), + Coin::new(10u128, "coin_13"), + Coin::new(10u128, "coin_14"), + ]; + + let user_b_coins = vec![ + Coin::new(10u128, "coin_15"), + Coin::new(10u128, "coin_16"), + Coin::new(10u128, "coin_17"), + Coin::new(10u128, "coin_18"), + Coin::new(10u128, "coin_19"), + Coin::new(10u128, "coin_20"), + Coin::new(10u128, "coin_21"), + Coin::new(10u128, "coin_22"), + Coin::new(10u128, "coin_23"), + Coin::new(10u128, "coin_24"), + ]; + + let user_c_coins = vec![ + Coin::new(10u128, "coin_25"), + Coin::new(10u128, "coin_26"), + Coin::new(10u128, "coin_27"), + Coin::new(10u128, "coin_28"), + Coin::new(10u128, "coin_29"), + Coin::new(10u128, "coin_30"), + Coin::new(10u128, "coin_31"), + Coin::new(10u128, "coin_32"), + ]; + + let mut mock = MockEnv::new() + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: user_a_coins.clone(), + }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: user_b_coins.clone(), + }) + .fund_account(AccountToFund { + addr: user_c.clone(), + funds: user_c_coins.clone(), + }) + .allowed_coins(&build_mock_coin_infos(32)) + .build() + .unwrap(); + + let token_id_a = mock.create_credit_account(&user_a).unwrap(); + mock.update_credit_account( + &token_id_a, + &user_a, + user_a_coins + .iter() + .flat_map(|coin| { + vec![ + Action::Deposit(coin.clone()), + Action::Borrow(Coin { + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }), + ] + }) + .collect::>(), + &user_a_coins, + ) + .unwrap(); + + let token_id_b = mock.create_credit_account(&user_b).unwrap(); + mock.update_credit_account( + &token_id_b, + &user_b, + user_b_coins + .iter() + .flat_map(|coin| { + vec![ + Action::Deposit(coin.clone()), + Action::Borrow(Coin { + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }), + ] + }) + .collect::>(), + &user_b_coins, + ) + .unwrap(); + + let token_id_c = mock.create_credit_account(&user_c).unwrap(); + mock.update_credit_account( + &token_id_c, + &user_c, + user_c_coins + .iter() + .flat_map(|coin| { + vec![ + Action::Deposit(coin.clone()), + Action::Borrow(Coin { + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }), + ] + }) + .collect::>(), + &user_c_coins, + ) + .unwrap(); + + let all_debt_shares_res = mock.query_all_debt_shares(None, Some(58_u32)); + + // Assert maximum is observed + assert_eq!(all_debt_shares_res.len(), 30); + + let all_debt_shares_res = mock.query_all_debt_shares(None, Some(2_u32)); + + // Assert limit request is observed + assert_eq!(all_debt_shares_res.len(), 2); + + let all_debt_shares_res_a = mock.query_all_debt_shares(None, None); + + let SharesResponseItem { + token_id, denom, .. + } = all_debt_shares_res_a.last().unwrap().clone(); + let all_debt_shares_res_b = mock.query_all_debt_shares(Some((token_id, denom)), None); + + let SharesResponseItem { + token_id, denom, .. + } = all_debt_shares_res_b.last().unwrap().clone(); + let all_debt_shares_res_c = mock.query_all_debt_shares(Some((token_id, denom)), None); + + let SharesResponseItem { + token_id, denom, .. + } = all_debt_shares_res_c.last().unwrap().clone(); + let all_debt_shares_res_d = mock.query_all_debt_shares(Some((token_id, denom)), None); + + // Assert default is observed + assert_eq!(all_debt_shares_res_a.len(), 10); + assert_eq!(all_debt_shares_res_b.len(), 10); + assert_eq!(all_debt_shares_res_c.len(), 10); + + assert_eq!(all_debt_shares_res_d.len(), 2); + + let combined_res: Vec = all_debt_shares_res_a + .iter() + .cloned() + .chain(all_debt_shares_res_b.iter().cloned()) + .chain(all_debt_shares_res_c.iter().cloned()) + .chain(all_debt_shares_res_d.iter().cloned()) + .collect(); + + let user_a_response_items = user_a_coins + .iter() + .map(|coin| SharesResponseItem { + token_id: token_id_a.clone(), + denom: coin.denom.clone(), + shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, + }) + .collect::>(); + + let user_b_response_items = user_b_coins + .iter() + .map(|coin| SharesResponseItem { + token_id: token_id_b.clone(), + denom: coin.denom.clone(), + shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, + }) + .collect::>(); + + let user_c_response_items = user_c_coins + .iter() + .map(|coin| SharesResponseItem { + token_id: token_id_c.clone(), + denom: coin.denom.clone(), + shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, + }) + .collect::>(); + + let combined_starting_vals: Vec = user_a_response_items + .iter() + .cloned() + .chain(user_b_response_items) + .chain(user_c_response_items) + .collect(); + + assert_eq!(combined_res.len(), combined_starting_vals.len()); + assert!(combined_starting_vals + .iter() + .all(|item| combined_res.contains(item))); +} diff --git a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs new file mode 100644 index 000000000..4345531ce --- /dev/null +++ b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs @@ -0,0 +1,206 @@ +use cosmwasm_std::{Addr, Coin, Uint128}; + +use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; +use rover::msg::execute::Action; +use rover::msg::query::CoinShares; + +use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn test_pagination_on_all_total_debt_shares_query_works() { + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let user_c = Addr::unchecked("user_c"); + + let user_a_coins = vec![ + Coin::new(10u128, "coin_1"), + Coin::new(10u128, "coin_2"), + Coin::new(10u128, "coin_3"), + Coin::new(10u128, "coin_4"), + Coin::new(10u128, "coin_5"), + Coin::new(10u128, "coin_6"), + Coin::new(10u128, "coin_7"), + Coin::new(10u128, "coin_8"), + Coin::new(10u128, "coin_9"), + Coin::new(10u128, "coin_10"), + Coin::new(10u128, "coin_11"), + Coin::new(10u128, "coin_12"), + Coin::new(10u128, "coin_13"), + Coin::new(10u128, "coin_14"), + ]; + + let user_b_coins = vec![ + Coin::new(10u128, "coin_15"), + Coin::new(10u128, "coin_16"), + Coin::new(10u128, "coin_17"), + Coin::new(10u128, "coin_18"), + Coin::new(10u128, "coin_19"), + Coin::new(10u128, "coin_20"), + Coin::new(10u128, "coin_21"), + Coin::new(10u128, "coin_22"), + Coin::new(10u128, "coin_23"), + Coin::new(10u128, "coin_24"), + ]; + + let user_c_coins = vec![ + Coin::new(10u128, "coin_25"), + Coin::new(10u128, "coin_26"), + Coin::new(10u128, "coin_27"), + Coin::new(10u128, "coin_28"), + Coin::new(10u128, "coin_29"), + Coin::new(10u128, "coin_30"), + Coin::new(10u128, "coin_31"), + Coin::new(10u128, "coin_32"), + ]; + + let mut mock = MockEnv::new() + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: user_a_coins.clone(), + }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: user_b_coins.clone(), + }) + .fund_account(AccountToFund { + addr: user_c.clone(), + funds: user_c_coins.clone(), + }) + .allowed_coins(&build_mock_coin_infos(32)) + .build() + .unwrap(); + + let token_id_a = mock.create_credit_account(&user_a).unwrap(); + mock.update_credit_account( + &token_id_a, + &user_a, + user_a_coins + .iter() + .flat_map(|coin| { + vec![ + Action::Deposit(coin.clone()), + Action::Borrow(Coin { + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }), + ] + }) + .collect::>(), + &user_a_coins, + ) + .unwrap(); + + let token_id_b = mock.create_credit_account(&user_b).unwrap(); + mock.update_credit_account( + &token_id_b, + &user_b, + user_b_coins + .iter() + .flat_map(|coin| { + vec![ + Action::Deposit(coin.clone()), + Action::Borrow(Coin { + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }), + ] + }) + .collect::>(), + &user_b_coins, + ) + .unwrap(); + + let token_id_c = mock.create_credit_account(&user_c).unwrap(); + mock.update_credit_account( + &token_id_c, + &user_c, + user_c_coins + .iter() + .flat_map(|coin| { + vec![ + Action::Deposit(coin.clone()), + Action::Borrow(Coin { + denom: coin.denom.clone(), + amount: Uint128::from(1u128), + }), + ] + }) + .collect::>(), + &user_c_coins, + ) + .unwrap(); + + let all_total_debt_shares_res = mock.query_all_total_debt_shares(None, Some(58_u32)); + + // Assert maximum is observed + assert_eq!(all_total_debt_shares_res.len(), 30); + + let all_total_debt_shares_res = mock.query_all_total_debt_shares(None, Some(2_u32)); + + // Assert limit request is observed + assert_eq!(all_total_debt_shares_res.len(), 2); + + let all_total_debt_shares_res_a = mock.query_all_total_debt_shares(None, None); + + let CoinShares { denom, .. } = all_total_debt_shares_res_a.last().unwrap().clone(); + let all_total_debt_shares_res_b = mock.query_all_total_debt_shares(Some(denom), None); + + let CoinShares { denom, .. } = all_total_debt_shares_res_b.last().unwrap().clone(); + let all_total_debt_shares_res_c = mock.query_all_total_debt_shares(Some(denom), None); + + let CoinShares { denom, .. } = all_total_debt_shares_res_c.last().unwrap().clone(); + let all_total_debt_shares_res_d = mock.query_all_total_debt_shares(Some(denom), None); + + // Assert default is observed + assert_eq!(all_total_debt_shares_res_a.len(), 10); + assert_eq!(all_total_debt_shares_res_b.len(), 10); + assert_eq!(all_total_debt_shares_res_c.len(), 10); + + assert_eq!(all_total_debt_shares_res_d.len(), 2); + + let combined_res: Vec = all_total_debt_shares_res_a + .iter() + .cloned() + .chain(all_total_debt_shares_res_b.iter().cloned()) + .chain(all_total_debt_shares_res_c.iter().cloned()) + .chain(all_total_debt_shares_res_d.iter().cloned()) + .collect(); + + let user_a_response_items = user_a_coins + .iter() + .map(|coin| CoinShares { + denom: coin.denom.clone(), + shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, + }) + .collect::>(); + + let user_b_response_items = user_b_coins + .iter() + .map(|coin| CoinShares { + denom: coin.denom.clone(), + shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, + }) + .collect::>(); + + let user_c_response_items = user_c_coins + .iter() + .map(|coin| CoinShares { + denom: coin.denom.clone(), + shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, + }) + .collect::>(); + + let combined_starting_vals: Vec = user_a_response_items + .iter() + .cloned() + .chain(user_b_response_items) + .chain(user_c_response_items) + .collect(); + + assert_eq!(combined_res.len(), combined_starting_vals.len()); + assert!(combined_starting_vals + .iter() + .all(|item| combined_res.contains(item))); +} diff --git a/contracts/credit-manager/tests/health_test.rs b/contracts/credit-manager/tests/test_health.rs similarity index 56% rename from contracts/credit-manager/tests/health_test.rs rename to contracts/credit-manager/tests/test_health.rs index d9ab2d99f..047bfca52 100644 --- a/contracts/credit-manager/tests/health_test.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -1,19 +1,14 @@ use std::ops::{Add, Div, Mul}; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; -use cw_multi_test::{App, BasicApp, Executor}; use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; -use mock_oracle::msg::{CoinPrice, ExecuteMsg as OracleExecuteMsg}; +use mock_oracle::msg::CoinPrice; use rover::error::ContractError; use rover::msg::execute::Action::{Borrow, Deposit}; -use rover::msg::query::{CoinShares, DebtSharesValue}; -use rover::msg::{ExecuteMsg, QueryMsg}; +use rover::msg::query::DebtSharesValue; -use crate::helpers::{ - assert_err, fund_red_bank, get_token_id, mock_app, mock_create_credit_account, query_config, - query_health, query_position, query_red_bank_debt, setup_credit_manager, CoinInfo, MockEnv, -}; +use crate::helpers::{assert_err, AccountToFund, CoinInfo, MockEnv}; pub mod helpers; @@ -24,14 +19,6 @@ pub mod helpers; /// above_max_ltv: false #[test] fn test_only_assets_with_no_debts() { - let user = Addr::unchecked("user"); - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &user, vec![Coin::new(300u128, "uosmo")]) - .unwrap(); - }); - let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), @@ -39,34 +26,31 @@ fn test_only_assets_with_no_debts() { liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin_info.clone()], - vec![], - ); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); let deposit_amount = Uint128::from(300u128); - - app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Deposit(coin_info.to_coin(deposit_amount))], - }, - &[Coin::new(deposit_amount.into(), "uosmo")], + mock.update_credit_account( + &token_id, + &user, + vec![Deposit(coin_info.to_coin(deposit_amount))], + &[Coin::new(deposit_amount.into(), coin_info.denom.clone())], ) .unwrap(); - let position = query_position(&app, &mock.credit_manager, &token_id); + let position = mock.query_position(&token_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.debt_shares.len(), 0); - let health = query_health(&app, &mock.credit_manager, &token_id); + let health = mock.query_health(&token_id); let assets_value = coin_info.price * Decimal::from_atomics(deposit_amount, 0).unwrap(); assert_eq!(health.total_assets_value, assets_value); assert_eq!(health.total_debts_value, Decimal::zero()); @@ -88,14 +72,6 @@ fn test_only_assets_with_no_debts() { /// above_max_ltv: false #[test] fn test_terra_ragnarok() { - let user = Addr::unchecked("user"); - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &user, vec![Coin::new(300u128, "uluna")]) - .unwrap(); - }); - let coin_info = CoinInfo { denom: "uluna".to_string(), price: Decimal::from_atomics(100u128, 0).unwrap(), @@ -103,45 +79,36 @@ fn test_terra_ragnarok() { liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin_info.clone()], - vec![], - ); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let config = query_config(&app, &mock.credit_manager.clone()); - - fund_red_bank( - &mut app, - config.red_bank, - vec![Coin::new(1000u128, "uluna")], - ); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); let deposit_amount = Uint128::from(12u128); let borrow_amount = Uint128::from(2u128); - app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![ - Deposit(coin_info.to_coin(deposit_amount)), - Borrow(coin_info.to_coin(borrow_amount)), - ], - }, - &[Coin::new(deposit_amount.into(), "uluna")], + + mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(coin_info.to_coin(deposit_amount)), + Borrow(coin_info.to_coin(borrow_amount)), + ], + &[Coin::new(deposit_amount.into(), coin_info.denom.clone())], ) .unwrap(); - let position = query_position(&app, &mock.credit_manager, &token_id); + let position = mock.query_position(&token_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.debt_shares.len(), 1); - let health = query_health(&app, &mock.credit_manager, &token_id); + let health = mock.query_health(&token_id); let assets_value = coin_info.price * Decimal::from_atomics(deposit_amount + borrow_amount, 0).unwrap(); assert_eq!(health.total_assets_value, assets_value); @@ -160,20 +127,16 @@ fn test_terra_ragnarok() { assert!(!health.liquidatable); assert!(!health.above_max_ltv); - price_change( - &mut app, - &mock, - CoinPrice { - denom: coin_info.denom, - price: Decimal::zero(), - }, - ); + mock.price_change(CoinPrice { + denom: coin_info.denom, + price: Decimal::zero(), + }); - let position = query_position(&app, &mock.credit_manager, &token_id); + let position = mock.query_position(&token_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.debt_shares.len(), 1); - let health = query_health(&app, &mock.credit_manager, &token_id); + let health = mock.query_health(&token_id); assert_eq!(health.total_assets_value, Decimal::zero()); assert_eq!(health.total_debts_value, Decimal::zero()); assert_eq!(health.lqdt_health_factor, None); @@ -189,52 +152,39 @@ fn test_terra_ragnarok() { /// above_max_ltv: true #[test] fn test_debts_no_assets() { - let user = Addr::unchecked("user"); - let mut app = mock_app(); - let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::one(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; - - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin_info.clone()], - vec![], - ); - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let config = query_config(&app, &mock.credit_manager.clone()); - - fund_red_bank( - &mut app, - config.red_bank, - vec![Coin::new(1000u128, coin_info.denom.clone())], - ); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); let borrowed_amount = Uint128::from(100u128); - let res = app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Borrow(coin_info.to_coin(borrowed_amount))], - }, + let res = mock.update_credit_account( + &token_id, + &user, + vec![Borrow(coin_info.to_coin(borrowed_amount))], &[], ); assert_err(res, ContractError::AboveMaxLTV); - let position = query_position(&app, &mock.credit_manager, &token_id); + let position = mock.query_position(&token_id); assert_eq!(position.token_id, token_id); assert_eq!(position.coins.len(), 0); assert_eq!(position.debt_shares.len(), 0); - let health = query_health(&app, &mock.credit_manager, &token_id); + let health = mock.query_health(&token_id); assert_eq!(health.total_assets_value, Decimal::zero()); assert_eq!(health.total_debts_value, Decimal::zero()); assert_eq!(health.lqdt_health_factor, None); @@ -257,14 +207,6 @@ fn test_debts_no_assets() { /// AboveMaxLtv error thrown #[test] fn test_cannot_borrow_more_than_healthy() { - let user = Addr::unchecked("user"); - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &user, vec![Coin::new(300u128, "uosmo")]) - .unwrap(); - }); - let coin_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(23654u128, 4).unwrap(), @@ -272,34 +214,24 @@ fn test_cannot_borrow_more_than_healthy() { liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), }; - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![coin_info.clone()], - vec![], - ); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let config = query_config(&app, &mock.credit_manager.clone()); - - fund_red_bank( - &mut app, - config.red_bank, - vec![Coin::new(1000u128, coin_info.denom.clone())], - ); - - app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![ - Deposit(coin_info.to_coin(Uint128::from(300u128))), - Borrow(coin_info.to_coin(Uint128::from(50u128))), - ], - }, + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(coin_info.to_coin(Uint128::from(300u128))), + Borrow(coin_info.to_coin(Uint128::from(50u128))), + ], &[Coin::new( Uint128::from(300u128).into(), coin_info.denom.clone(), @@ -307,12 +239,12 @@ fn test_cannot_borrow_more_than_healthy() { ) .unwrap(); - let position = query_position(&app, &mock.credit_manager, &token_id); + let position = mock.query_position(&token_id); assert_eq!(position.token_id, token_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.debt_shares.len(), 1); - let health = query_health(&app, &mock.credit_manager, &token_id); + let health = mock.query_health(&token_id); let assets_value = Decimal::from_atomics(82789u128, 2).unwrap(); assert_eq!(health.total_assets_value, assets_value); let debts_value = Decimal::from_atomics(1206354u128, 4).unwrap(); @@ -328,31 +260,25 @@ fn test_cannot_borrow_more_than_healthy() { assert!(!health.liquidatable); assert!(!health.above_max_ltv); - app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Borrow(coin_info.to_coin(Uint128::from(100u128)))], - }, + mock.update_credit_account( + &token_id, + &user, + vec![Borrow(coin_info.to_coin(Uint128::from(100u128)))], &[], ) .unwrap(); - let res = app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Borrow(coin_info.to_coin(Uint128::from(150u128)))], - }, + let res = mock.update_credit_account( + &token_id, + &user, + vec![Borrow(coin_info.to_coin(Uint128::from(150u128)))], &[], ); assert_err(res, ContractError::AboveMaxLTV); // All valid on step 2 as well (meaning step 3 did not go through) - let health = query_health(&app, &mock.credit_manager, &token_id); + let health = mock.query_health(&token_id); let assets_value = Decimal::from_atomics(106443u128, 2).unwrap(); assert_eq!(health.total_assets_value, assets_value); let debts_value = Decimal::from_atomics(3595408u128, 4).unwrap(); @@ -382,21 +308,12 @@ fn test_cannot_borrow_more_than_healthy() { /// above_max_ltv: true #[test] fn test_cannot_borrow_more_but_not_liquidatable() { - let user = Addr::unchecked("user"); - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &user, vec![Coin::new(300u128, "uosmo")]) - .unwrap(); - }); - let uosmo_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(23654u128, 4).unwrap(), max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), }; - let uatom_info = CoinInfo { denom: "uatom".to_string(), price: Decimal::from_atomics(102u128, 1).unwrap(), @@ -404,77 +321,56 @@ fn test_cannot_borrow_more_but_not_liquidatable() { liquidation_threshold: Decimal::from_atomics(75u128, 2).unwrap(), }; - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![uosmo_info.clone(), uatom_info.clone()], - vec![], - ); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let config = query_config(&app, &mock.credit_manager.clone()); - - fund_red_bank( - &mut app, - config.red_bank, - vec![Coin::new(1000u128, "uatom")], - ); - - app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![ - Deposit(uosmo_info.to_coin(Uint128::from(300u128))), - Borrow(uatom_info.to_coin(Uint128::from(50u128))), - ], - }, - &[Coin::new(300, "uosmo")], + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(uosmo_info.to_coin(Uint128::from(300u128))), + Borrow(uatom_info.to_coin(Uint128::from(50u128))), + ], + &[Coin::new(300, uosmo_info.denom)], ) .unwrap(); - let health = query_health(&app, &mock.credit_manager, &token_id); + let health = mock.query_health(&token_id); assert!(!health.liquidatable); assert!(!health.above_max_ltv); - price_change( - &mut app, - &mock, - CoinPrice { - denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(24u128, 0).unwrap(), - }, - ); + mock.price_change(CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(24u128, 0).unwrap(), + }); - let health = query_health(&app, &mock.credit_manager, &token_id); + let health = mock.query_health(&token_id); assert!(!health.liquidatable); assert!(health.above_max_ltv); - let res = app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![Borrow(uatom_info.to_coin(Uint128::from(2u128)))], - }, + let res = mock.update_credit_account( + &token_id, + &user, + vec![Borrow(uatom_info.to_coin(Uint128::from(2u128)))], &[], ); assert_err(res, ContractError::AboveMaxLTV); - price_change( - &mut app, - &mock, - CoinPrice { - denom: uatom_info.denom, - price: Decimal::from_atomics(35u128, 0).unwrap(), - }, - ); + mock.price_change(CoinPrice { + denom: uatom_info.denom, + price: Decimal::from_atomics(35u128, 0).unwrap(), + }); - let health = query_health(&app, &mock.credit_manager, &token_id); + let health = mock.query_health(&token_id); assert!(health.liquidatable); assert!(health.above_max_ltv); } @@ -487,21 +383,12 @@ fn test_cannot_borrow_more_but_not_liquidatable() { /// above_max_ltv: false #[test] fn test_assets_and_ltv_lqdt_adjusted_value() { - let user = Addr::unchecked("user"); - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &user, vec![Coin::new(300u128, "uosmo")]) - .unwrap(); - }); - let uosmo_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), }; - let uatom_info = CoinInfo { denom: "uatom".to_string(), price: Decimal::from_atomics(7012302005u128, 3).unwrap(), @@ -509,47 +396,36 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), }; - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![uosmo_info.clone(), uatom_info.clone()], - vec![], - ); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user).unwrap(); - let token_id = get_token_id(res); - - let config = query_config(&app, &mock.credit_manager.clone()); - - fund_red_bank( - &mut app, - config.red_bank, - vec![Coin::new(1000u128, "uatom")], - ); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); let deposit_amount = Uint128::from(298u128); let borrowed_amount = Uint128::from(49u128); - - app.execute_contract( - user.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.clone(), - actions: vec![ - Deposit(uosmo_info.to_coin(deposit_amount)), - Borrow(uatom_info.to_coin(borrowed_amount)), - ], - }, - &[Coin::new(deposit_amount.into(), "uosmo")], + mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(uosmo_info.to_coin(deposit_amount)), + Borrow(uatom_info.to_coin(borrowed_amount)), + ], + &[Coin::new(deposit_amount.into(), uosmo_info.denom.clone())], ) .unwrap(); - let position = query_position(&app, &mock.credit_manager, &token_id); + let position = mock.query_position(&token_id); assert_eq!(position.token_id, token_id); assert_eq!(position.coins.len(), 2); assert_eq!(position.debt_shares.len(), 1); - let health = query_health(&app, &mock.credit_manager, &token_id); + let health = mock.query_health(&token_id); let deposit_amount_dec = Decimal::from_atomics(deposit_amount, 0).unwrap(); let borrowed_amount_dec = Decimal::from_atomics(borrowed_amount, 0).unwrap(); assert_eq!( @@ -592,26 +468,12 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { /// Test validates User A's debt value & health factors #[test] fn test_debt_value() { - let user_a = Addr::unchecked("user_a"); - let user_b = Addr::unchecked("user_b"); - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &user_a, vec![Coin::new(300u128, "uosmo")]) - .unwrap(); - router - .bank - .init_balance(storage, &user_b, vec![Coin::new(140u128, "uosmo")]) - .unwrap(); - }); - let uosmo_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), max_ltv: Decimal::from_atomics(3u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), }; - let uatom_info = CoinInfo { denom: "uatom".to_string(), price: Decimal::from_atomics(7012302005u128, 3).unwrap(), @@ -619,85 +481,71 @@ fn test_debt_value() { liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), }; - let mock = setup_credit_manager( - &mut app, - &Addr::unchecked("owner"), - vec![uosmo_info.clone(), uatom_info.clone()], - vec![], - ); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_a).unwrap(); - let token_id_a = get_token_id(res); - - let res = mock_create_credit_account(&mut app, &mock.credit_manager, &user_b).unwrap(); - let token_id_b = get_token_id(res); - - let config = query_config(&app, &mock.credit_manager.clone()); - - fund_red_bank( - &mut app, - config.red_bank.clone(), - vec![Coin::new(1000u128, "uatom"), Coin::new(1000u128, "uosmo")], - ); + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: vec![Coin::new(140u128, uosmo_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id_a = mock.create_credit_account(&user_a).unwrap(); + let token_id_b = mock.create_credit_account(&user_b).unwrap(); let user_a_deposit_amount_osmo = Uint128::from(298u128); let user_a_borrowed_amount_atom = Uint128::from(49u128); let user_a_borrowed_amount_osmo = Uint128::from(30u128); - app.execute_contract( - user_a.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_a.clone(), - actions: vec![ - Borrow(uatom_info.to_coin(user_a_borrowed_amount_atom)), - Borrow(uosmo_info.to_coin(user_a_borrowed_amount_osmo)), - Deposit(uosmo_info.to_coin(user_a_deposit_amount_osmo)), - ], - }, - &[Coin::new(user_a_deposit_amount_osmo.into(), "uosmo")], + mock.update_credit_account( + &token_id_a, + &user_a, + vec![ + Borrow(uatom_info.to_coin(user_a_borrowed_amount_atom)), + Borrow(uosmo_info.to_coin(user_a_borrowed_amount_osmo)), + Deposit(uosmo_info.to_coin(user_a_deposit_amount_osmo)), + ], + &[Coin::new( + user_a_deposit_amount_osmo.into(), + uosmo_info.denom.clone(), + )], ) .unwrap(); - let interim_red_bank_debt = query_red_bank_debt( - &app, - &mock.credit_manager, - &config.red_bank, - &uatom_info.denom, - ); + let interim_red_bank_debt = mock.query_red_bank_debt(&uatom_info.denom); let user_b_deposit_amount = Uint128::from(101u128); let user_b_borrowed_amount_atom = Uint128::from(24u128); - app.execute_contract( - user_b.clone(), - mock.credit_manager.clone(), - &ExecuteMsg::UpdateCreditAccount { - token_id: token_id_b.clone(), - actions: vec![ - Borrow(uatom_info.to_coin(user_b_borrowed_amount_atom)), - Deposit(uosmo_info.to_coin(user_b_deposit_amount)), - ], - }, - &[Coin::new(user_b_deposit_amount.into(), "uosmo")], + mock.update_credit_account( + &token_id_b, + &user_b, + vec![ + Borrow(uatom_info.to_coin(user_b_borrowed_amount_atom)), + Deposit(uosmo_info.to_coin(user_b_deposit_amount)), + ], + &[Coin::new( + user_b_deposit_amount.into(), + uosmo_info.denom.clone(), + )], ) .unwrap(); - let position_a = query_position(&app, &mock.credit_manager, &token_id_a); + let position_a = mock.query_position(&token_id_a); assert_eq!(position_a.token_id, token_id_a); assert_eq!(position_a.coins.len(), 2); assert_eq!(position_a.debt_shares.len(), 2); - let health = query_health(&app, &mock.credit_manager, &token_id_a); + let health = mock.query_health(&token_id_a); assert!(!health.above_max_ltv); assert!(!health.liquidatable); - let red_bank_atom_debt = query_red_bank_debt( - &app, - &mock.credit_manager, - &config.red_bank, - &uatom_info.denom, - ); + let red_bank_atom_debt = mock.query_red_bank_debt(&uatom_info.denom); let user_a_debt_shares_atom = user_a_borrowed_amount_atom.mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED); @@ -706,7 +554,7 @@ fn test_debt_value() { find_by_denom(&uatom_info.denom, &position_a.debt_shares).shares ); - let position_b = query_position(&app, &mock.credit_manager, &token_id_b); + let position_b = mock.query_position(&token_id_b); let user_b_debt_shares_atom = user_a_debt_shares_atom .multiply_ratio(user_b_borrowed_amount_atom, interim_red_bank_debt.amount); assert_eq!( @@ -714,13 +562,7 @@ fn test_debt_value() { find_by_denom(&uatom_info.denom, &position_b.debt_shares).shares ); - let red_bank_atom_res: CoinShares = app - .wrap() - .query_wasm_smart( - &mock.credit_manager, - &QueryMsg::TotalDebtShares(uatom_info.denom.clone()), - ) - .unwrap(); + let red_bank_atom_res = mock.query_total_debt_shares(&uatom_info.denom); assert_eq!( red_bank_atom_res.shares, @@ -768,16 +610,6 @@ fn test_debt_value() { ); } -fn price_change(app: &mut BasicApp, mock: &MockEnv, coin: CoinPrice) { - app.execute_contract( - Addr::unchecked("anyone"), - mock.oracle.clone(), - &OracleExecuteMsg::ChangePrice(coin), - &[], - ) - .unwrap(); -} - fn find_by_denom<'a>(denom: &'a str, shares: &'a [DebtSharesValue]) -> &'a DebtSharesValue { shares.iter().find(|item| item.denom == *denom).unwrap() } diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs new file mode 100644 index 000000000..5e41c32c9 --- /dev/null +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -0,0 +1,130 @@ +use crate::helpers::{assert_contents_equal, CoinInfo, MockEnv}; +use cosmwasm_std::Decimal; + +pub mod helpers; + +#[test] +fn test_owner_set_on_instantiate() { + let owner = "owner_addr"; + let mock = MockEnv::new().owner(owner).build().unwrap(); + let res = mock.query_config(); + assert_eq!(owner, res.owner); +} + +#[test] +fn test_raises_on_invalid_owner_addr() { + let owner = "%%%INVALID%%%"; + let res = MockEnv::new().owner(owner).build(); + if res.is_ok() { + panic!("Should have thrown an error"); + } +} + +#[test] +fn test_nft_contract_addr_not_set_on_instantiate() { + let mock = MockEnv::new().no_nft_contract().build().unwrap(); + let res = mock.query_config(); + assert_eq!(res.account_nft, None); +} + +#[test] +fn test_allowed_vaults_set_on_instantiate() { + let allowed_vaults = vec![ + "vault_contract_1".to_string(), + "vault_contract_2".to_string(), + "vault_contract_3".to_string(), + ]; + + let mock = MockEnv::new() + .allowed_vaults(&allowed_vaults) + .build() + .unwrap(); + let res = mock.query_allowed_vaults(None, None); + assert_contents_equal(res, allowed_vaults); +} + +#[test] +fn test_raises_on_invalid_vaults_addr() { + let mock = MockEnv::new() + .allowed_vaults(&["%%%INVALID%%%".to_string()]) + .build(); + + if mock.is_ok() { + panic!("Should have thrown an error"); + } +} + +#[test] +fn test_allowed_coins_set_on_instantiate() { + let allowed_coins = vec![ + CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }, + CoinInfo { + denom: "uatom".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }, + CoinInfo { + denom: "umars".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }, + CoinInfo { + denom: "ujake".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }, + ]; + let mock = MockEnv::new() + .allowed_coins(&allowed_coins) + .build() + .unwrap(); + + let res = mock.query_allowed_coins(None, None); + assert_contents_equal( + res, + allowed_coins + .iter() + .map(|info| info.denom.clone()) + .collect(), + ) +} + +#[test] +fn test_red_bank_set_on_instantiate() { + let red_bank_addr = "mars_red_bank_contract_123".to_string(); + let mock = MockEnv::new().red_bank(&red_bank_addr).build().unwrap(); + let res = mock.query_config(); + assert_eq!(red_bank_addr, res.red_bank); +} + +#[test] +fn test_raises_on_invalid_red_bank_addr() { + let mock = MockEnv::new().red_bank("%%%INVALID%%%").build(); + if mock.is_ok() { + panic!("Should have thrown an error"); + } +} + +#[test] +fn test_oracle_set_on_instantiate() { + let oracle_contract = "oracle_contract_456".to_string(); + let mock = MockEnv::new().oracle(&oracle_contract).build().unwrap(); + let res = mock.query_config(); + assert_eq!(oracle_contract, res.oracle); +} + +#[test] +fn test_raises_on_invalid_oracle_addr() { + let mock = MockEnv::new().oracle("%%%INVALID%%%").build(); + if mock.is_ok() { + panic!("Should have thrown an error"); + } +} diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs new file mode 100644 index 000000000..5f3a529da --- /dev/null +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -0,0 +1,141 @@ +use cosmwasm_std::Addr; + +use rover::adapters::{OracleBase, RedBankBase}; +use rover::msg::instantiate::ConfigUpdates; + +use crate::helpers::MockEnv; + +pub mod helpers; + +#[test] +fn test_only_owner_can_update_config() { + let mut mock = MockEnv::new().build().unwrap(); + let new_owner = Addr::unchecked("bad_guy"); + + let res = mock.update_config( + &new_owner, + ConfigUpdates { + account_nft: None, + owner: Some(new_owner.to_string()), + allowed_vaults: None, + allowed_coins: None, + red_bank: None, + oracle: None, + }, + ); + + if res.is_ok() { + panic!("only owner should be able to update config"); + } +} + +#[test] +fn test_update_config_works_with_full_config() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_config(); + let original_allowed_vaults = mock.query_allowed_vaults(None, None); + let original_allowed_coins = mock.query_allowed_coins(None, None); + + let new_nft_contract = mock.setup_new_nft_contract().unwrap(); + let new_owner = Addr::unchecked("new_owner"); + let new_red_bank = RedBankBase::new("new_red_bank".to_string()); + let new_allowed_vaults = vec!["vault_contract_1".to_string()]; + let new_allowed_coins = vec!["uosmo".to_string()]; + let new_oracle = OracleBase::new("new_oracle".to_string()); + + mock.update_config( + &Addr::unchecked(original_config.owner.clone()), + ConfigUpdates { + account_nft: Some(new_nft_contract.to_string()), + owner: Some(new_owner.to_string()), + allowed_vaults: Some(new_allowed_vaults.clone()), + allowed_coins: Some(new_allowed_coins.clone()), + red_bank: Some(new_red_bank.clone()), + oracle: Some(new_oracle.clone()), + }, + ) + .unwrap(); + + let new_config = mock.query_config(); + let new_queried_allowed_vaults = mock.query_allowed_vaults(None, None); + let new_queried_allowed_coins = mock.query_allowed_coins(None, None); + + assert_eq!(new_config.account_nft, Some(new_nft_contract.to_string())); + assert_ne!(new_config.account_nft, original_config.account_nft); + + assert_eq!(new_config.owner, new_owner.to_string()); + assert_ne!(new_config.owner, original_config.owner); + + assert_eq!(new_queried_allowed_vaults, new_allowed_vaults); + assert_ne!(new_queried_allowed_vaults, original_allowed_vaults); + + assert_eq!(new_queried_allowed_coins, new_allowed_coins); + assert_ne!(new_queried_allowed_coins, original_allowed_coins); + + assert_eq!(&new_config.red_bank, new_red_bank.address()); + assert_ne!(new_config.red_bank, original_config.red_bank); + + assert_eq!(&new_config.oracle, new_oracle.address()); + assert_ne!(new_config.oracle, original_config.oracle); +} + +#[test] +fn test_update_config_works_with_some_config() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_config(); + let original_allowed_vaults = mock.query_allowed_vaults(None, None); + let original_allowed_coins = mock.query_allowed_coins(None, None); + + let new_nft_contract = mock.setup_new_nft_contract().unwrap(); + let new_allowed_vaults = vec!["vault_contract_1".to_string()]; + + mock.update_config( + &Addr::unchecked(original_config.owner.clone()), + ConfigUpdates { + account_nft: Some(new_nft_contract.to_string()), + allowed_vaults: Some(new_allowed_vaults.clone()), + ..Default::default() + }, + ) + .unwrap(); + + let new_config = mock.query_config(); + let new_queried_allowed_vaults = mock.query_allowed_vaults(None, None); + let new_queried_allowed_coins = mock.query_allowed_coins(None, None); + + // Changed configs + assert_eq!(new_config.account_nft, Some(new_nft_contract.to_string())); + assert_ne!(new_config.account_nft, original_config.account_nft); + + assert_eq!(new_queried_allowed_vaults, new_allowed_vaults); + assert_ne!(new_queried_allowed_vaults, original_allowed_vaults); + + // Unchanged configs + assert_eq!(new_config.owner, original_config.owner); + assert_eq!(original_allowed_coins, new_queried_allowed_coins); + assert_eq!(new_config.red_bank, original_config.red_bank); +} + +#[test] +fn test_update_config_does_nothing_when_nothing_is_passed() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_config(); + let original_allowed_vaults = mock.query_allowed_vaults(None, None); + let original_allowed_coins = mock.query_allowed_coins(None, None); + + mock.update_config( + &Addr::unchecked(original_config.owner.clone()), + Default::default(), + ) + .unwrap(); + + let new_config = mock.query_config(); + let new_queried_allowed_vaults = mock.query_allowed_vaults(None, None); + let new_queried_allowed_coins = mock.query_allowed_coins(None, None); + + assert_eq!(new_config.account_nft, original_config.account_nft); + assert_eq!(new_config.owner, original_config.owner); + assert_eq!(new_queried_allowed_vaults, original_allowed_vaults); + assert_eq!(new_queried_allowed_coins, original_allowed_coins); + assert_eq!(new_config.red_bank, original_config.red_bank); +} diff --git a/contracts/credit-manager/tests/update_config_test.rs b/contracts/credit-manager/tests/update_config_test.rs deleted file mode 100644 index 076763734..000000000 --- a/contracts/credit-manager/tests/update_config_test.rs +++ /dev/null @@ -1,219 +0,0 @@ -use cosmwasm_std::Addr; -use cw721_base::InstantiateMsg as NftInstantiateMsg; -use cw_multi_test::{App, Executor}; - -use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use rover::adapters::{OracleBase, RedBankBase}; -use rover::msg::instantiate::ConfigUpdates; -use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; - -use crate::helpers::{mock_account_nft_contract, mock_app, mock_contract, query_config}; - -pub mod helpers; - -#[test] -fn test_update_config_works_with_full_config() { - let mut app = mock_app(); - let original_owner = Addr::unchecked("original_owner"); - let code_id = app.store_code(mock_contract()); - let contract_addr = instantiate(&mut app, &original_owner, code_id); - - let original_config = query_config(&app, &contract_addr.clone()); - let original_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); - let original_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); - - let nft_contract_addr = setup_nft_and_propose_owner(&mut app, &original_owner, &contract_addr); - let new_owner = Addr::unchecked("new_owner"); - let new_red_bank = RedBankBase::new("new_red_bank".to_string()); - let new_allowed_vaults = vec!["vaultcontract1".to_string()]; - let new_allowed_assets = vec!["uosmo".to_string()]; - let new_oracle = OracleBase::new("new_oracle".to_string()); - - app.execute_contract( - original_owner.clone(), - contract_addr.clone(), - &ExecuteMsg::UpdateConfig { - new_config: ConfigUpdates { - account_nft: Some(nft_contract_addr.to_string()), - owner: Some(new_owner.to_string()), - allowed_vaults: Some(new_allowed_vaults.clone()), - allowed_coins: Some(new_allowed_assets.clone()), - red_bank: Some(new_red_bank.clone()), - oracle: Some(new_oracle.clone()), - }, - }, - &[], - ) - .unwrap(); - - let new_config = query_config(&app, &contract_addr.clone()); - let new_queried_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); - let new_queried_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); - - assert_eq!(new_config.account_nft, Some(nft_contract_addr.to_string())); - assert_ne!(new_config.account_nft, original_config.account_nft); - - assert_eq!(new_config.owner, new_owner.to_string()); - assert_ne!(new_config.owner, original_config.owner); - - assert_eq!(new_queried_allowed_vaults, new_allowed_vaults); - assert_ne!(new_queried_allowed_vaults, original_allowed_vaults); - - assert_eq!(new_queried_allowed_assets, new_allowed_assets); - assert_ne!(new_queried_allowed_assets, original_allowed_assets); - - assert_eq!(&new_config.red_bank, new_red_bank.address()); - assert_ne!(new_config.red_bank, original_config.red_bank); - - assert_eq!(&new_config.oracle, new_oracle.address()); - assert_ne!(new_config.oracle, original_config.oracle); -} - -#[test] -fn test_update_config_works_with_some_config() { - let mut app = mock_app(); - let original_owner = Addr::unchecked("original_owner"); - let code_id = app.store_code(mock_contract()); - let contract_addr = instantiate(&mut app, &original_owner, code_id); - - let original_config = query_config(&app, &contract_addr.clone()); - let original_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); - let original_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); - - let nft_contract_addr = setup_nft_and_propose_owner(&mut app, &original_owner, &contract_addr); - let new_allowed_vaults = vec!["vaultcontract1".to_string()]; - - app.execute_contract( - original_owner.clone(), - contract_addr.clone(), - &ExecuteMsg::UpdateConfig { - new_config: ConfigUpdates { - account_nft: Some(nft_contract_addr.to_string()), - allowed_vaults: Some(new_allowed_vaults.clone()), - ..Default::default() - }, - }, - &[], - ) - .unwrap(); - - let new_config = query_config(&app, &contract_addr.clone()); - let new_queried_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr.clone()); - let new_queried_allowed_assets = query_allowed_assets(&mut app, &contract_addr.clone()); - - // Changed configs - assert_eq!(new_config.account_nft, Some(nft_contract_addr.to_string())); - assert_ne!(new_config.account_nft, original_config.account_nft); - - assert_eq!(new_queried_allowed_vaults, new_allowed_vaults); - assert_ne!(new_queried_allowed_vaults, original_allowed_vaults); - - // Unchanged configs - assert_eq!(new_config.owner, original_config.owner); - assert_eq!(original_allowed_assets, new_queried_allowed_assets); - assert_eq!(new_config.red_bank, original_config.red_bank); -} - -#[test] -fn test_update_config_does_nothing_when_nothing_is_passed() { - let mut app = mock_app(); - let original_owner = Addr::unchecked("original_owner"); - let code_id = app.store_code(mock_contract()); - let contract_addr = instantiate(&mut app, &original_owner, code_id); - - let original_config = query_config(&app, &contract_addr); - let original_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr); - let original_allowed_assets = query_allowed_assets(&mut app, &contract_addr); - - app.execute_contract( - original_owner.clone(), - contract_addr.clone(), - &ExecuteMsg::UpdateConfig { - new_config: Default::default(), - }, - &[], - ) - .unwrap(); - - let new_config = query_config(&app, &contract_addr); - let new_queried_allowed_vaults = query_allowed_vaults(&mut app, &contract_addr); - let new_queried_allowed_assets = query_allowed_assets(&mut app, &contract_addr); - - assert_eq!(new_config.account_nft, original_config.account_nft); - assert_eq!(new_config.owner, original_config.owner); - assert_eq!(new_queried_allowed_vaults, original_allowed_vaults); - assert_eq!(new_queried_allowed_assets, original_allowed_assets); - assert_eq!(new_config.red_bank, original_config.red_bank); -} - -fn instantiate(app: &mut App, original_owner: &Addr, code_id: u64) -> Addr { - app.instantiate_contract( - code_id, - original_owner.clone(), - &InstantiateMsg { - owner: original_owner.to_string(), - allowed_vaults: vec![], - allowed_coins: vec![], - red_bank: RedBankBase::new("initial_red_bank".to_string()), - oracle: OracleBase::new("initial_oracle".to_string()), - }, - &[], - "mock_manager_contract", - None, - ) - .unwrap() -} - -fn setup_nft_and_propose_owner(app: &mut App, original_owner: &Addr, contract_addr: &Addr) -> Addr { - let nft_contract_code_id = app.store_code(mock_account_nft_contract()); - let nft_contract_addr = app - .instantiate_contract( - nft_contract_code_id, - original_owner.clone(), - &NftInstantiateMsg { - name: "Rover Credit Account".to_string(), - symbol: "RCA".to_string(), - minter: original_owner.to_string(), - }, - &[], - "manager-mock-account-nft", - None, - ) - .unwrap(); - - let proposal_msg: NftExecuteMsg = NftExecuteMsg::ProposeNewOwner { - new_owner: contract_addr.to_string(), - }; - app.execute_contract( - original_owner.clone(), - nft_contract_addr.clone(), - &proposal_msg, - &[], - ) - .unwrap(); - nft_contract_addr -} - -fn query_allowed_vaults(app: &mut App, contract_addr: &Addr) -> Vec { - app.wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedVaults { - start_after: None, - limit: None, - }, - ) - .unwrap() -} - -fn query_allowed_assets(app: &mut App, contract_addr: &Addr) -> Vec { - app.wrap() - .query_wasm_smart( - contract_addr.clone(), - &QueryMsg::AllowedCoins { - start_after: None, - limit: None, - }, - ) - .unwrap() -} diff --git a/contracts/mock-oracle/Cargo.toml b/contracts/mock-oracle/Cargo.toml index d4386a483..a823fe3bb 100644 --- a/contracts/mock-oracle/Cargo.toml +++ b/contracts/mock-oracle/Cargo.toml @@ -2,7 +2,7 @@ name = "mock-oracle" version = "1.0.0" authors = ["larry_0x ", "grod220 "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later" repository = "https://github.com/mars-protocol/rover" diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index 4546706c3..f3f4dca85 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -2,7 +2,7 @@ name = "mock-red-bank" version = "1.0.0" authors = ["larry_0x , grod220 "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later" repository = "https://github.com/mars-protocol/rover" diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index e2a97537f..2053aac42 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -2,7 +2,7 @@ name = "rover" version = "0.1.0" authors = ["larry_0x ", "Gabe R. "] -edition = "2018" +edition = "2021" license = "GPL-3.0-or-later" repository = "https://github.com/mars-protocol/rover" From 7f1873bd94aed5ba97a07c7acec026d0e9663309 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 17 Aug 2022 13:17:20 +0200 Subject: [PATCH 049/218] Adding support for repaying debts (#10) --- Cargo.lock | 424 ++---------------- contracts/credit-manager/src/borrow.rs | 4 +- contracts/credit-manager/src/execute.rs | 6 + contracts/credit-manager/src/lib.rs | 1 + contracts/credit-manager/src/repay.rs | 76 ++++ contracts/credit-manager/tests/test_borrow.rs | 46 +- .../tests/test_enumerate_debt_shares.rs | 8 +- .../tests/test_enumerate_total_debt_shares.rs | 8 +- contracts/credit-manager/tests/test_health.rs | 4 +- contracts/credit-manager/tests/test_repay.rs | 321 +++++++++++++ contracts/mock-oracle/Cargo.toml | 4 +- contracts/mock-red-bank/src/contract.rs | 8 +- contracts/mock-red-bank/src/execute.rs | 15 +- contracts/mock-red-bank/src/msg.rs | 4 + packages/rover/src/adapters/oracle.rs | 6 +- packages/rover/src/adapters/red_bank.rs | 18 +- packages/rover/src/error.rs | 3 + packages/rover/src/msg/execute.rs | 6 +- 18 files changed, 542 insertions(+), 420 deletions(-) create mode 100644 contracts/credit-manager/src/repay.rs create mode 100644 contracts/credit-manager/tests/test_repay.rs diff --git a/Cargo.lock b/Cargo.lock index e1918333f..1f62d6efb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,7 +7,7 @@ name = "account-nft" version = "0.1.0" dependencies = [ "anyhow", - "cosmwasm-std 1.0.0", + "cosmwasm-std", "cw-multi-test", "cw-storage-plus 0.14.0", "cw721", @@ -22,21 +22,6 @@ version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" -[[package]] -name = "astroport" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b56c6d8a5a14a5f569a3473a962ed1d12bfec2a19ef43584c86aae5667bdeaa8" -dependencies = [ - "cosmwasm-std 0.16.7", - "cw-storage-plus 0.8.1", - "cw20 0.8.1", - "schemars", - "serde", - "terra-cosmwasm", - "uint", -] - [[package]] name = "base16ct" version = "0.1.1" @@ -82,31 +67,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "const-oid" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" - [[package]] name = "const-oid" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" -[[package]] -name = "cosmwasm-crypto" -version = "0.16.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b110e31d47bd265e17ec88dd7328fcf40e1ee67a6131c1ab492f77fef8cd83" -dependencies = [ - "digest", - "ed25519-zebra 2.2.0", - "k256 0.9.6", - "rand_core 0.5.1", - "thiserror", -] - [[package]] name = "cosmwasm-crypto" version = "1.0.0" @@ -114,21 +80,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd" dependencies = [ "digest", - "ed25519-zebra 3.0.0", - "k256 0.10.4", + "ed25519-zebra", + "k256", "rand_core 0.6.3", "thiserror", ] -[[package]] -name = "cosmwasm-derive" -version = "0.16.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0faf9bad5eb0a43a00406e64f8d33407a06bd1826fa976195a69db70e6c18d9d" -dependencies = [ - "syn", -] - [[package]] name = "cosmwasm-derive" version = "1.0.0" @@ -138,23 +95,6 @@ dependencies = [ "syn", ] -[[package]] -name = "cosmwasm-std" -version = "0.16.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0d4e46ab20939af6366a71783324ae4babdedb111f0dd797d063a2e68718bc" -dependencies = [ - "base64", - "cosmwasm-crypto 0.16.7", - "cosmwasm-derive 0.16.7", - "forward_ref", - "schemars", - "serde", - "serde-json-wasm 0.3.2", - "thiserror", - "uint", -] - [[package]] name = "cosmwasm-std" version = "1.0.0" @@ -162,12 +102,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" dependencies = [ "base64", - "cosmwasm-crypto 1.0.0", - "cosmwasm-derive 1.0.0", + "cosmwasm-crypto", + "cosmwasm-derive", "forward_ref", "schemars", "serde", - "serde-json-wasm 0.4.1", + "serde-json-wasm", "thiserror", "uint", ] @@ -178,7 +118,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d18403b07304d15d304dad11040d45bbcaf78d603b4be3fb5e2685c16f9229b5" dependencies = [ - "cosmwasm-std 1.0.0", + "cosmwasm-std", "serde", ] @@ -197,7 +137,7 @@ version = "0.1.0" dependencies = [ "account-nft", "anyhow", - "cosmwasm-std 1.0.0", + "cosmwasm-std", "cw-multi-test", "cw-storage-plus 0.14.0", "cw2 0.14.0", @@ -216,18 +156,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" -[[package]] -name = "crypto-bigint" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" -dependencies = [ - "generic-array", - "rand_core 0.6.3", - "subtle", - "zeroize", -] - [[package]] name = "crypto-bigint" version = "0.3.2" @@ -263,20 +191,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "cw-asset" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dadc245d6c76cd7ae2c5ec39aefa4a5310cde653d07e23c5577ee0c2cac864e" -dependencies = [ - "cosmwasm-std 1.0.0", - "cw-storage-plus 0.13.4", - "cw1155", - "cw20 0.13.4", - "schemars", - "serde", -] - [[package]] name = "cw-multi-test" version = "0.14.0" @@ -284,7 +198,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca153120cf5b91af88be106b0c6c0263423d959bc813b1592982c02c4691a4ae" dependencies = [ "anyhow", - "cosmwasm-std 1.0.0", + "cosmwasm-std", "cosmwasm-storage", "cw-storage-plus 0.14.0", "cw-utils 0.14.0", @@ -296,35 +210,13 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw-storage-plus" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e867b9972b83b32e00e878dfbff48299ba26618dabeb19b9c56fae176dc225" -dependencies = [ - "cosmwasm-std 0.16.7", - "schemars", - "serde", -] - -[[package]] -name = "cw-storage-plus" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140b764eb9aca3fa8b06ce7ce773705058db1b11cc97bb1bac6702c19887894e" -dependencies = [ - "cosmwasm-std 0.16.7", - "schemars", - "serde", -] - [[package]] name = "cw-storage-plus" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" dependencies = [ - "cosmwasm-std 1.0.0", + "cosmwasm-std", "schemars", "serde", ] @@ -335,7 +227,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c8b264257c4f44c49b7ce09377af63aa040768ecd3fd7bdd2d48a09323a1e90" dependencies = [ - "cosmwasm-std 1.0.0", + "cosmwasm-std", "schemars", "serde", ] @@ -346,7 +238,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" dependencies = [ - "cosmwasm-std 1.0.0", + "cosmwasm-std", "schemars", "serde", "thiserror", @@ -358,7 +250,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "414b91f3d7a619bb26c835119d7095804596a1382ddc1d184c33c1d2c17f6c5e" dependencies = [ - "cosmwasm-std 1.0.0", + "cosmwasm-std", "cw2 0.14.0", "schemars", "semver", @@ -366,61 +258,13 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw0" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c497f885a40918a02df7d938c81809965fa05cfc21b3dc591e9950237b5de0a9" -dependencies = [ - "cosmwasm-std 0.16.7", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw0" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d759bb5418a3bdf091e1f1be17de2a15d95d2be4fee28045c2e461f4c6d9d1ca" -dependencies = [ - "cosmwasm-std 0.16.7", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw1155" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42197c9a0fd844653177009125f24157e486578289327a3781923e427a8f9bbc" -dependencies = [ - "cosmwasm-std 1.0.0", - "cw-utils 0.13.4", - "schemars", - "serde", -] - -[[package]] -name = "cw2" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022d9f06ea46f055f0e107c5cf076949f0b1e7befb78acbee42a28f07b6cd636" -dependencies = [ - "cosmwasm-std 0.16.7", - "cw-storage-plus 0.9.2", - "schemars", - "serde", -] - [[package]] name = "cw2" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" dependencies = [ - "cosmwasm-std 1.0.0", + "cosmwasm-std", "cw-storage-plus 0.13.4", "schemars", "serde", @@ -432,71 +276,19 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa74c324af8e3506fd8d50759a265bead3f87402e413c840042af5d2808463d6" dependencies = [ - "cosmwasm-std 1.0.0", + "cosmwasm-std", "cw-storage-plus 0.14.0", "schemars", "serde", ] -[[package]] -name = "cw20" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a11a2adbd52258f5b4ed5323f62bc6e559f2cefbe52ef0e58290016fde5bb083" -dependencies = [ - "cosmwasm-std 0.16.7", - "cw0 0.8.1", - "schemars", - "serde", -] - -[[package]] -name = "cw20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac49b013ca1e355fd988cc1926acc9a16d7fd45cfb595ee330455582a788b100" -dependencies = [ - "cosmwasm-std 0.16.7", - "cw0 0.9.1", - "schemars", - "serde", -] - -[[package]] -name = "cw20" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" -dependencies = [ - "cosmwasm-std 1.0.0", - "cw-utils 0.13.4", - "schemars", - "serde", -] - -[[package]] -name = "cw20-base" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b731a291b63cc26d484b159e8469f0965b0082a20e874616f869537b31d64bb" -dependencies = [ - "cosmwasm-std 0.16.7", - "cw-storage-plus 0.9.2", - "cw0 0.9.1", - "cw2 0.9.2", - "cw20 0.9.1", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw721" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "035818368a74c07dd9ed5c5a93340199ba251530162010b9f34c3809e3b97df1" dependencies = [ - "cosmwasm-std 1.0.0", + "cosmwasm-std", "cw-utils 0.13.4", "schemars", "serde", @@ -508,7 +300,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "423d4efe8b649d228d1533e141c238415f49aa8a9ee4e40fce192d7a93ffd057" dependencies = [ - "cosmwasm-std 1.0.0", + "cosmwasm-std", "cw-storage-plus 0.13.4", "cw-utils 0.13.4", "cw2 0.13.4", @@ -518,22 +310,13 @@ dependencies = [ "thiserror", ] -[[package]] -name = "der" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" -dependencies = [ - "const-oid 0.6.2", -] - [[package]] name = "der" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" dependencies = [ - "const-oid 0.7.1", + "const-oid", ] [[package]] @@ -562,44 +345,18 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" -[[package]] -name = "ecdsa" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" -dependencies = [ - "der 0.4.5", - "elliptic-curve 0.10.6", - "hmac", - "signature", -] - [[package]] name = "ecdsa" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" dependencies = [ - "der 0.5.1", - "elliptic-curve 0.11.12", + "der", + "elliptic-curve", "rfc6979", "signature", ] -[[package]] -name = "ed25519-zebra" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409" -dependencies = [ - "curve25519-dalek", - "hex", - "rand_core 0.5.1", - "serde", - "sha2", - "thiserror", -] - [[package]] name = "ed25519-zebra" version = "3.0.0" @@ -621,22 +378,6 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" -[[package]] -name = "elliptic-curve" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b" -dependencies = [ - "crypto-bigint 0.2.11", - "ff 0.10.1", - "generic-array", - "group 0.10.0", - "pkcs8 0.7.6", - "rand_core 0.6.3", - "subtle", - "zeroize", -] - [[package]] name = "elliptic-curve" version = "0.11.12" @@ -644,27 +385,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" dependencies = [ "base16ct", - "crypto-bigint 0.3.2", - "der 0.5.1", - "ff 0.11.1", + "crypto-bigint", + "der", + "ff", "generic-array", - "group 0.11.0", + "group", "rand_core 0.6.3", "sec1", "subtle", "zeroize", ] -[[package]] -name = "ff" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" -dependencies = [ - "rand_core 0.6.3", - "subtle", -] - [[package]] name = "ff" version = "0.11.1" @@ -713,24 +444,13 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] -[[package]] -name = "group" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" -dependencies = [ - "ff 0.10.1", - "rand_core 0.6.3", - "subtle", -] - [[package]] name = "group" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ - "ff 0.11.1", + "ff", "rand_core 0.6.3", "subtle", ] @@ -766,18 +486,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" -[[package]] -name = "k256" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea" -dependencies = [ - "cfg-if", - "ecdsa 0.12.4", - "elliptic-curve 0.10.6", - "sha2", -] - [[package]] name = "k256" version = "0.10.4" @@ -785,8 +493,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ "cfg-if", - "ecdsa 0.13.4", - "elliptic-curve 0.11.12", + "ecdsa", + "elliptic-curve", "sec1", "sha2", ] @@ -797,31 +505,12 @@ version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" -[[package]] -name = "mars-core" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7466180f16cf2ec2218ed1dfd11543a5dc0620dbf5e08288d41a6aa6e559748" -dependencies = [ - "astroport", - "cosmwasm-std 0.16.7", - "cw2 0.9.2", - "cw20 0.9.1", - "cw20-base", - "schemars", - "serde", - "terra-cosmwasm", - "thiserror", -] - [[package]] name = "mock-oracle" version = "1.0.0" dependencies = [ - "cosmwasm-std 1.0.0", - "cw-asset", - "cw-storage-plus 0.13.4", - "mars-core", + "cosmwasm-std", + "cw-storage-plus 0.14.0", "schemars", "serde", ] @@ -830,7 +519,7 @@ dependencies = [ name = "mock-red-bank" version = "1.0.0" dependencies = [ - "cosmwasm-std 1.0.0", + "cosmwasm-std", "cw-storage-plus 0.14.0", "schemars", "serde", @@ -842,24 +531,14 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "pkcs8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" -dependencies = [ - "der 0.4.5", - "spki 0.4.1", -] - [[package]] name = "pkcs8" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" dependencies = [ - "der 0.5.1", - "spki 0.5.4", + "der", + "spki", "zeroize", ] @@ -928,7 +607,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" dependencies = [ - "crypto-bigint 0.3.2", + "crypto-bigint", "hmac", "zeroize", ] @@ -937,7 +616,7 @@ dependencies = [ name = "rover" version = "0.1.0" dependencies = [ - "cosmwasm-std 1.0.0", + "cosmwasm-std", "cw-storage-plus 0.14.0", "mock-oracle", "mock-red-bank", @@ -982,9 +661,9 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ - "der 0.5.1", + "der", "generic-array", - "pkcs8 0.8.0", + "pkcs8", "subtle", "zeroize", ] @@ -1004,15 +683,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-json-wasm" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "042ac496d97e5885149d34139bad1d617192770d7eb8f1866da2317ff4501853" -dependencies = [ - "serde", -] - [[package]] name = "serde-json-wasm" version = "0.4.1" @@ -1078,15 +748,6 @@ dependencies = [ "rand_core 0.6.3", ] -[[package]] -name = "spki" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" -dependencies = [ - "der 0.4.5", -] - [[package]] name = "spki" version = "0.5.4" @@ -1094,7 +755,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" dependencies = [ "base64ct", - "der 0.5.1", + "der", ] [[package]] @@ -1120,17 +781,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "terra-cosmwasm" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552f18cba2b535d1f8c0e3b3f37696820b954bc7535d2e33909f2a6342302718" -dependencies = [ - "cosmwasm-std 0.16.7", - "schemars", - "serde", -] - [[package]] name = "thiserror" version = "1.0.32" diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index ffd54efc7..9100cf026 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -5,7 +5,7 @@ use rover::error::{ContractError, ContractResult}; use crate::deposit::assert_coin_is_whitelisted; use crate::state::{COIN_BALANCES, DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; -pub static DEFAULT_DEBT_UNITS_PER_COIN_BORROWED: Uint128 = Uint128::new(1_000_000); +pub static DEFAULT_DEBT_SHARES_PER_COIN_BORROWED: Uint128 = Uint128::new(1_000_000); /// calculate by how many the user's debt units should be increased /// if total debt is zero, then we define 1 unit of coin borrowed = 1,000,000 debt unit @@ -25,7 +25,7 @@ pub fn borrow(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRe let debt_shares_to_add = if total_debt_amount.is_zero() { coin.amount - .checked_mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) + .checked_mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) .map_err(StdError::overflow)? } else { TOTAL_DEBT_SHARES diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 7261d2784..9710cf9ef 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -13,6 +13,7 @@ use rover::msg::instantiate::ConfigUpdates; use crate::borrow::borrow; use crate::deposit::deposit; use crate::health::assert_below_max_ltv; +use crate::repay::repay; use crate::state::{ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, ORACLE, OWNER, RED_BANK}; pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult { @@ -131,6 +132,10 @@ pub fn dispatch_actions( token_id: token_id.to_string(), coin: coin.clone(), }), + Action::Repay(coin) => callbacks.push(CallbackMsg::Repay { + token_id: token_id.to_string(), + coin: coin.clone(), + }), } } @@ -166,6 +171,7 @@ pub fn execute_callback( } match callback { CallbackMsg::Borrow { coin, token_id } => borrow(deps, env, &token_id, coin), + CallbackMsg::Repay { token_id, coin } => repay(deps, env, &token_id, coin), CallbackMsg::AssertBelowMaxLTV { token_id } => { assert_below_max_ltv(deps.as_ref(), env, &token_id) } diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 35228e3bd..b1b86677c 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -6,4 +6,5 @@ pub mod execute; pub mod health; pub mod instantiate; pub mod query; +pub mod repay; pub mod state; diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs new file mode 100644 index 000000000..cede8f7ff --- /dev/null +++ b/contracts/credit-manager/src/repay.rs @@ -0,0 +1,76 @@ +use cosmwasm_std::{Coin, DepsMut, Env, Response, StdError, StdResult, Uint128}; +use std::cmp::min; + +use rover::error::{ContractError, ContractResult}; + +use crate::deposit::assert_coin_is_whitelisted; +use crate::state::{COIN_BALANCES, DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; + +pub fn repay(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractResult { + if coin.amount.is_zero() { + return Err(ContractError::NoAmount); + } + + assert_coin_is_whitelisted(deps.storage, &coin.denom)?; + + let red_bank = RED_BANK.load(deps.storage)?; + let total_debt_amount = + red_bank.query_debt(&deps.querier, &env.contract.address, &coin.denom)?; + + // Calculate how many shares user is attempting to pay back + let total_debt_shares = TOTAL_DEBT_SHARES.load(deps.storage, &coin.denom)?; + let debt_shares_to_decrement = + total_debt_shares.checked_multiply_ratio(coin.amount, total_debt_amount)?; + + // Payback amount should not exceed token's current debt + let current_debt = DEBT_SHARES + .load(deps.storage, (token_id, &coin.denom)) + .map_err(|_| ContractError::NoDebt)?; + let shares_to_repay = min(current_debt, debt_shares_to_decrement); + let amount_to_repay = if current_debt > debt_shares_to_decrement { + coin.amount + } else { + total_debt_amount.checked_multiply_ratio(current_debt, total_debt_shares)? + }; + + // Decrement token's debt position + if shares_to_repay >= current_debt { + DEBT_SHARES.remove(deps.storage, (token_id, &coin.denom)); + } else { + DEBT_SHARES.save( + deps.storage, + (token_id, &coin.denom), + ¤t_debt.checked_sub(shares_to_repay)?, + )?; + } + + // Decrement total debt shares for coin + TOTAL_DEBT_SHARES.save( + deps.storage, + &coin.denom, + &total_debt_shares.checked_sub(shares_to_repay)?, + )?; + + // Decrement token's coin balance position + COIN_BALANCES.update( + deps.storage, + (token_id, &coin.denom), + |current_amount| -> StdResult<_> { + current_amount + .unwrap_or_else(Uint128::zero) + .checked_sub(amount_to_repay) + .map_err(StdError::overflow) + }, + )?; + + let red_bank_repay_msg = red_bank.repay_msg(&Coin { + denom: coin.denom, + amount: amount_to_repay, + })?; + + Ok(Response::new() + .add_message(red_bank_repay_msg) + .add_attribute("action", "rover/credit_manager/repay") + .add_attribute("debt_shares_repaid", shares_to_repay) + .add_attribute("coins_repaid", amount_to_repay)) +} diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index 3b0e0be8e..70aec196f 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -2,7 +2,7 @@ use std::ops::{Mul, Sub}; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; -use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; +use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use rover::error::ContractError; use rover::msg::execute::Action::{Borrow, Deposit}; @@ -102,6 +102,42 @@ fn test_borrowing_zero_does_nothing() { assert_eq!(position.debt_shares.len(), 0); } +#[test] +fn test_cannot_borrow_above_max_ltv() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let position = mock.query_position(&token_id); + assert_eq!(position.coins.len(), 0); + assert_eq!(position.debt_shares.len(), 0); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(coin_info.to_coin(Uint128::from(300u128))), + Borrow(coin_info.to_coin(Uint128::from(700u128))), + ], + &[Coin::new(300u128, coin_info.denom)], + ); + + assert_err(res, ContractError::AboveMaxLTV); +} + #[test] fn test_success_when_new_debt_asset() { let coin_info = CoinInfo { @@ -159,7 +195,7 @@ fn test_success_when_new_debt_asset() { assert_eq!(position.debt_shares.len(), 1); assert_eq!( debt_shares_res.shares, - Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) + Uint128::from(42u128).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) ); assert_eq!(debt_shares_res.denom, coin_info.denom); let debt_amount = Uint128::from(42u128) + Uint128::new(1u128); // simulated yield @@ -181,7 +217,7 @@ fn test_success_when_new_debt_asset() { let res = mock.query_total_debt_shares(&coin_info.denom); assert_eq!( res.shares, - Uint128::from(42u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) + Uint128::from(42u128).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) ); } @@ -234,14 +270,14 @@ fn test_debt_shares_with_debt_amount() { ) .unwrap(); - let token_a_shares = Uint128::from(50u128).mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED); + let token_a_shares = Uint128::from(50u128).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); let position = mock.query_position(&token_id_a); let debt_position_a = position.debt_shares.first().unwrap(); assert_eq!(debt_position_a.shares, token_a_shares.clone()); assert_eq!(debt_position_a.denom, coin_info.denom); let token_b_shares = Uint128::from(50u128) - .mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED) + .mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) .multiply_ratio(Uint128::from(50u128), interim_red_bank_debt.amount); let position = mock.query_position(&token_id_b); let debt_position_b = position.debt_shares.first().unwrap(); diff --git a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs index 89ff025ec..1e4794d5b 100644 --- a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Addr, Coin, Uint128}; -use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; +use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use rover::msg::execute::Action; use rover::msg::query::SharesResponseItem; @@ -179,7 +179,7 @@ fn test_pagination_on_all_debt_shares_query_works() { .map(|coin| SharesResponseItem { token_id: token_id_a.clone(), denom: coin.denom.clone(), - shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, + shares: DEFAULT_DEBT_SHARES_PER_COIN_BORROWED, }) .collect::>(); @@ -188,7 +188,7 @@ fn test_pagination_on_all_debt_shares_query_works() { .map(|coin| SharesResponseItem { token_id: token_id_b.clone(), denom: coin.denom.clone(), - shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, + shares: DEFAULT_DEBT_SHARES_PER_COIN_BORROWED, }) .collect::>(); @@ -197,7 +197,7 @@ fn test_pagination_on_all_debt_shares_query_works() { .map(|coin| SharesResponseItem { token_id: token_id_c.clone(), denom: coin.denom.clone(), - shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, + shares: DEFAULT_DEBT_SHARES_PER_COIN_BORROWED, }) .collect::>(); diff --git a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs index 4345531ce..19631155c 100644 --- a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Addr, Coin, Uint128}; -use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; +use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use rover::msg::execute::Action; use rover::msg::query::CoinShares; @@ -172,7 +172,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { .iter() .map(|coin| CoinShares { denom: coin.denom.clone(), - shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, + shares: DEFAULT_DEBT_SHARES_PER_COIN_BORROWED, }) .collect::>(); @@ -180,7 +180,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { .iter() .map(|coin| CoinShares { denom: coin.denom.clone(), - shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, + shares: DEFAULT_DEBT_SHARES_PER_COIN_BORROWED, }) .collect::>(); @@ -188,7 +188,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { .iter() .map(|coin| CoinShares { denom: coin.denom.clone(), - shares: DEFAULT_DEBT_UNITS_PER_COIN_BORROWED, + shares: DEFAULT_DEBT_SHARES_PER_COIN_BORROWED, }) .collect::>(); diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index 047bfca52..05d6c1228 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -2,7 +2,7 @@ use std::ops::{Add, Div, Mul}; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; -use credit_manager::borrow::DEFAULT_DEBT_UNITS_PER_COIN_BORROWED; +use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use mock_oracle::msg::CoinPrice; use rover::error::ContractError; use rover::msg::execute::Action::{Borrow, Deposit}; @@ -548,7 +548,7 @@ fn test_debt_value() { let red_bank_atom_debt = mock.query_red_bank_debt(&uatom_info.denom); let user_a_debt_shares_atom = - user_a_borrowed_amount_atom.mul(DEFAULT_DEBT_UNITS_PER_COIN_BORROWED); + user_a_borrowed_amount_atom.mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); assert_eq!( user_a_debt_shares_atom, find_by_denom(&uatom_info.denom, &position_a.debt_shares).shares diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs new file mode 100644 index 000000000..9a520a5e6 --- /dev/null +++ b/contracts/credit-manager/tests/test_repay.rs @@ -0,0 +1,321 @@ +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; +use std::ops::{Add, Mul, Sub}; + +use rover::error::ContractError; +use rover::msg::execute::Action::{Borrow, Deposit, Repay}; + +use crate::helpers::{assert_err, AccountToFund, CoinInfo, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE}; + +pub mod helpers; + +#[test] +fn test_only_token_owner_can_repay() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + let owner = Addr::unchecked("owner"); + let mut mock = MockEnv::new().build().unwrap(); + let token_id = mock.create_credit_account(&owner).unwrap(); + + let another_user = Addr::unchecked("another_user"); + let res = mock.update_credit_account( + &token_id, + &another_user, + vec![Repay(coin_info.to_coin(Uint128::new(12312u128)))], + &[], + ); + + assert_err( + res, + ContractError::NotTokenOwner { + user: another_user.into(), + token_id, + }, + ) +} + +#[test] +fn test_can_only_repay_what_is_whitelisted() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().allowed_coins(&[coin_info]).build().unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![Repay(Coin { + denom: "usomething".to_string(), + amount: Uint128::from(234u128), + })], + &[], + ); + + assert_err( + res, + ContractError::NotWhitelisted(String::from("usomething")), + ) +} + +#[test] +fn test_repaying_zero_raises() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![Repay(coin_info.to_coin(Uint128::zero()))], + &[], + ); + + assert_err(res, ContractError::NoAmount) +} + +#[test] +fn test_raises_when_repaying_what_is_not_owed() { + let uosmo_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + + let uatom_info = CoinInfo { + denom: "atom".to_string(), + price: Decimal::from_atomics(9u128, 0).unwrap(), + max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), + }; + + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: vec![Coin::new(300u128, uatom_info.denom.clone())], + }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: vec![Coin::new(100u128, uatom_info.denom.clone())], + }) + .build() + .unwrap(); + + let token_id_a = mock.create_credit_account(&user_a).unwrap(); + let token_id_b = mock.create_credit_account(&user_b).unwrap(); + + // Seeding uatom with existing total debt shares from another user + mock.update_credit_account( + &token_id_b, + &user_b, + vec![ + Deposit(uatom_info.to_coin(Uint128::from(100u128))), + Borrow(uatom_info.to_coin(Uint128::from(12u128))), + ], + &[uatom_info.to_coin(Uint128::from(100u128))], + ) + .unwrap(); + + let res = mock.update_credit_account( + &token_id_a, + &user_a, + vec![ + Deposit(uatom_info.to_coin(Uint128::from(300u128))), + Borrow(uosmo_info.to_coin(Uint128::from(42u128))), + Repay(uatom_info.to_coin(Uint128::from(42u128))), + ], + &[uatom_info.to_coin(Uint128::from(300u128))], + ); + + assert_err(res, ContractError::NoDebt) +} + +// TODO: After withdraw is implemented, complete this test +// Should do a deposit, borrow another denom, withdraw some +// and then attempt to repay with not enough in assets +#[test] +fn test_raises_when_not_enough_assets_to_repay() {} + +#[test] +fn test_successful_repay() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + + let user = Addr::unchecked("user"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + + let token_id = mock.create_credit_account(&user).unwrap(); + + let position = mock.query_position(&token_id); + assert_eq!(position.coins.len(), 0); + assert_eq!(position.debt_shares.len(), 0); + + mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(coin_info.to_coin(Uint128::from(300u128))), + Borrow(coin_info.to_coin(Uint128::from(50u128))), + ], + &[Coin::new(300u128, coin_info.denom.clone())], + ) + .unwrap(); + + let interim_red_bank_debt = mock.query_red_bank_debt(&coin_info.denom); + + mock.update_credit_account( + &token_id, + &user, + vec![Repay(coin_info.to_coin(Uint128::from(20u128)))], + &[], + ) + .unwrap(); + + let position = mock.query_position(&token_id); + assert_eq!(position.coins.len(), 1); + let asset_res = position.coins.first().unwrap(); + let expected_net_asset_amount = Uint128::from(330u128); // Deposit + Borrow - Repay + assert_eq!(asset_res.amount, expected_net_asset_amount); + + let debt_shares_res = position.debt_shares.first().unwrap(); + assert_eq!(position.debt_shares.len(), 1); + assert_eq!(debt_shares_res.denom, coin_info.denom); + + let former_total_debt_shares = Uint128::from(50u128).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); + let debt_shares_paid = former_total_debt_shares + .multiply_ratio(Uint128::from(20u128), interim_red_bank_debt.amount); + let new_total_debt_shares = former_total_debt_shares.sub(debt_shares_paid); + assert_eq!(debt_shares_res.shares, new_total_debt_shares); + + let res = mock.query_total_debt_shares(&coin_info.denom); + assert_eq!(res.shares, new_total_debt_shares); + + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, Uint128::from(330u128)); + + let config = mock.query_config(); + let red_bank_addr = Addr::unchecked(config.red_bank); + let coin = mock.query_balance(&red_bank_addr, &coin_info.denom); + assert_eq!( + coin.amount, + DEFAULT_RED_BANK_COIN_BALANCE.sub(Uint128::from(30u128)) + ); + + mock.update_credit_account( + &token_id, + &user, + vec![Repay(coin_info.to_coin(Uint128::from(31u128)))], // Interest accrued paid back as well + &[], + ) + .unwrap(); + + let position = mock.query_position(&token_id); + assert_eq!(position.coins.len(), 1); + let asset_res = position.coins.first().unwrap(); + let expected_net_asset_amount = Uint128::from(299u128); // Deposit + Borrow - full repay - interest + assert_eq!(asset_res.amount, expected_net_asset_amount); + + // Full debt repaid and purged from storage + assert_eq!(position.debt_shares.len(), 0); + + let res = mock.query_total_debt_shares(&coin_info.denom); + assert_eq!(res.shares, Uint128::zero()); + + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, Uint128::from(299u128)); + let coin = mock.query_balance(&red_bank_addr, &coin_info.denom); + assert_eq!( + coin.amount, + DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::from(1u128)) + ); +} + +#[test] +fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { + let coin_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + }; + + let user = Addr::unchecked("user"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + + let token_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(coin_info.to_coin(Uint128::from(300u128))), + Borrow(coin_info.to_coin(Uint128::from(50u128))), + Repay(coin_info.to_coin(Uint128::from(75u128))), + ], + &[Coin::new(300u128, coin_info.denom.clone())], + ) + .unwrap(); + + let position = mock.query_position(&token_id); + assert_eq!(position.coins.len(), 1); + let asset_res = position.coins.first().unwrap(); + let expected_net_asset_amount = Uint128::from(299u128); // Deposit + Borrow - Repay - interest + assert_eq!(asset_res.amount, expected_net_asset_amount); + + assert_eq!(position.debt_shares.len(), 0); + + let res = mock.query_total_debt_shares(&coin_info.denom); + assert_eq!(res.shares, Uint128::zero()); + + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, Uint128::from(299u128)); + + let config = mock.query_config(); + let coin = mock.query_balance(&Addr::unchecked(config.red_bank), &coin_info.denom); + assert_eq!( + coin.amount, + DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::from(1u128)) + ); +} diff --git a/contracts/mock-oracle/Cargo.toml b/contracts/mock-oracle/Cargo.toml index a823fe3bb..f538f694c 100644 --- a/contracts/mock-oracle/Cargo.toml +++ b/contracts/mock-oracle/Cargo.toml @@ -15,8 +15,6 @@ library = [] [dependencies] cosmwasm-std = "1.0" -cw-asset = "2.1" -cw-storage-plus = "0.13" -mars-core = "1.0" +cw-storage-plus = "0.14" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index 7d0e4f44d..9d21f41fa 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -2,7 +2,7 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; -use crate::execute::execute_borrow; +use crate::execute::{borrow, repay}; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::query::{query_debt, query_market}; use crate::state::COIN_MARKET_INFO; @@ -28,10 +28,8 @@ pub fn execute( msg: ExecuteMsg, ) -> StdResult { match msg { - ExecuteMsg::Borrow { - coin, - recipient: _recipient, - } => execute_borrow(deps, info, coin), + ExecuteMsg::Borrow { coin, .. } => borrow(deps, info, coin), + ExecuteMsg::Repay { denom, .. } => repay(deps, info, denom), } } diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs index bbcc19ff1..15bc8c140 100644 --- a/contracts/mock-red-bank/src/execute.rs +++ b/contracts/mock-red-bank/src/execute.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, Std use crate::helpers::load_debt_amount; use crate::state::DEBT_AMOUNT; -pub fn execute_borrow(deps: DepsMut, info: MessageInfo, coin: Coin) -> StdResult { +pub fn borrow(deps: DepsMut, info: MessageInfo, coin: Coin) -> StdResult { let debt_amount = load_debt_amount(deps.storage, &info.sender, &coin.denom)?; DEBT_AMOUNT.save( @@ -21,3 +21,16 @@ pub fn execute_borrow(deps: DepsMut, info: MessageInfo, coin: Coin) -> StdResult Ok(Response::new().add_message(transfer_msg)) } + +pub fn repay(deps: DepsMut, info: MessageInfo, denom: String) -> StdResult { + let debt_amount = load_debt_amount(deps.storage, &info.sender, &denom)?; + let coin_sent = info.funds.first().unwrap(); + + DEBT_AMOUNT.save( + deps.storage, + (info.sender.clone(), denom), + &debt_amount.checked_sub(coin_sent.amount)?, + )?; + + Ok(Response::new()) +} diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs index 372b8e324..79c183543 100644 --- a/contracts/mock-red-bank/src/msg.rs +++ b/contracts/mock-red-bank/src/msg.rs @@ -21,6 +21,10 @@ pub enum ExecuteMsg { coin: Coin, recipient: Option, }, + Repay { + denom: String, + on_behalf_of: Option, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs index 86afcaef2..2be25a245 100644 --- a/packages/rover/src/adapters/oracle.rs +++ b/packages/rover/src/adapters/oracle.rs @@ -22,20 +22,20 @@ pub type Oracle = OracleBase; impl From for OracleUnchecked { fn from(oracle: Oracle) -> Self { - Self(oracle.0.to_string()) + Self(oracle.address().to_string()) } } impl OracleUnchecked { pub fn check(&self, api: &dyn Api) -> StdResult { - Ok(OracleBase::new(api.addr_validate(&self.0)?)) + Ok(OracleBase::new(api.addr_validate(self.address())?)) } } impl Oracle { pub fn query_price(&self, querier: &QuerierWrapper, denom: &str) -> StdResult { querier.query_wasm_smart( - self.0.to_string(), + self.address().to_string(), &QueryMsg::AssetPrice { denom: denom.to_string(), }, diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index ef6b87ff3..f303da803 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -31,7 +31,7 @@ impl From for RedBankUnchecked { impl RedBankUnchecked { pub fn check(&self, api: &dyn Api) -> StdResult { - Ok(RedBankBase(api.addr_validate(&self.0)?)) + Ok(RedBankBase(api.addr_validate(self.address())?)) } } @@ -39,7 +39,7 @@ impl RedBank { /// Generate message for borrowing a specified amount of coin pub fn borrow_msg(&self, coin: &Coin) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: self.0.to_string(), + contract_addr: self.address().to_string(), msg: to_binary(&ExecuteMsg::Borrow { coin: coin.clone(), recipient: None, @@ -48,6 +48,18 @@ impl RedBank { })) } + /// Generate message for repaying a specified amount of coin + pub fn repay_msg(&self, coin: &Coin) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address().to_string(), + msg: to_binary(&ExecuteMsg::Repay { + denom: coin.denom.clone(), + on_behalf_of: None, + })?, + funds: vec![coin.clone()], + })) + } + pub fn query_debt( &self, querier: &QuerierWrapper, @@ -56,7 +68,7 @@ impl RedBank { ) -> StdResult { let response: UserAssetDebtResponse = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: self.0.to_string(), + contract_addr: self.address().to_string(), msg: to_binary(&QueryMsg::UserAssetDebt { user_address: user_address.to_string(), denom: denom.to_string(), diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 18d0517ab..f9f20278d 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -37,6 +37,9 @@ pub enum ContractError { #[error("No coin amount set for action")] NoAmount, + #[error("No debt to repay")] + NoDebt, + #[error("{user:?} is not the owner of {token_id:?}")] NotTokenOwner { user: String, token_id: String }, diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index af49d5176..942a13b1a 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -33,9 +33,10 @@ pub enum ExecuteMsg { pub enum Action { /// Deposit native coin of specified type and amount. Verifies if the correct amount is sent with transaction. Deposit(Coin), - /// Borrow coin of specified amount from Red Bank Borrow(Coin), + /// Repay coin of specified amount back to Red Bank + Repay(Coin), } /// Internal actions made by the contract with pre-validated inputs @@ -45,6 +46,9 @@ pub enum CallbackMsg { /// Borrow specified amount of coin from Red Bank; /// Increase the token's asset amount and debt shares; Borrow { token_id: String, coin: Coin }, + /// Repay coin of specified amount back to Red Bank; + /// Decrement the token's asset amount and debt shares; + Repay { token_id: String, coin: Coin }, /// Calculate the account's max loan-to-value health factor. If above 1, /// emits a `position_changed` event. If 1 or below, raises an error. AssertBelowMaxLTV { token_id: String }, From 3f7b62fe4094fe4bc618fe08b32591ee8df85413 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 24 Aug 2022 13:59:37 +0200 Subject: [PATCH 050/218] Adding support for withdrawing from credit account (#9) * Adding support for withdrawing from credit account * Shortening Uint128 syntax * Review updates --- contracts/credit-manager/src/borrow.rs | 42 +-- contracts/credit-manager/src/deposit.rs | 31 +- contracts/credit-manager/src/execute.rs | 11 + contracts/credit-manager/src/lib.rs | 2 + contracts/credit-manager/src/repay.rs | 22 +- contracts/credit-manager/src/utils.rs | 42 +++ contracts/credit-manager/src/withdraw.rs | 31 ++ .../tests/helpers/mock_coin_info.rs | 28 ++ .../credit-manager/tests/helpers/mock_env.rs | 2 +- contracts/credit-manager/tests/helpers/mod.rs | 2 + contracts/credit-manager/tests/test_borrow.rs | 84 ++--- .../credit-manager/tests/test_deposit.rs | 88 ++--- .../tests/test_enumerate_coin_balances.rs | 6 +- .../tests/test_enumerate_debt_shares.rs | 6 +- .../tests/test_enumerate_total_debt_shares.rs | 6 +- contracts/credit-manager/tests/test_health.rs | 68 ++-- .../credit-manager/tests/test_instantiate.rs | 25 +- contracts/credit-manager/tests/test_repay.rs | 155 +++++---- .../credit-manager/tests/test_withdraw.rs | 327 ++++++++++++++++++ contracts/mock-red-bank/src/execute.rs | 2 +- packages/rover/src/msg/execute.rs | 11 +- 21 files changed, 656 insertions(+), 335 deletions(-) create mode 100644 contracts/credit-manager/src/utils.rs create mode 100644 contracts/credit-manager/src/withdraw.rs create mode 100644 contracts/credit-manager/tests/helpers/mock_coin_info.rs create mode 100644 contracts/credit-manager/tests/test_withdraw.rs diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index 9100cf026..56eaa168c 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -1,9 +1,9 @@ -use cosmwasm_std::{Coin, DepsMut, Env, Response, StdError, StdResult, Uint128}; +use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; use rover::error::{ContractError, ContractResult}; -use crate::deposit::assert_coin_is_whitelisted; -use crate::state::{COIN_BALANCES, DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; +use crate::state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; +use crate::utils::{assert_coin_is_whitelisted, increment_coin_balance}; pub static DEFAULT_DEBT_SHARES_PER_COIN_BORROWED: Uint128 = Uint128::new(1_000_000); @@ -17,7 +17,7 @@ pub fn borrow(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRe return Err(ContractError::NoAmount); } - assert_coin_is_whitelisted(deps.storage, &coin.denom)?; + assert_coin_is_whitelisted(deps.storage, &coin)?; let red_bank = RED_BANK.load(deps.storage)?; let total_debt_amount = @@ -25,42 +25,28 @@ pub fn borrow(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRe let debt_shares_to_add = if total_debt_amount.is_zero() { coin.amount - .checked_mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) - .map_err(StdError::overflow)? + .checked_mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED)? } else { TOTAL_DEBT_SHARES .load(deps.storage, &coin.denom)? .checked_multiply_ratio(coin.amount, total_debt_amount)? }; - TOTAL_DEBT_SHARES.update(deps.storage, &coin.denom, |shares| -> StdResult<_> { + TOTAL_DEBT_SHARES.update(deps.storage, &coin.denom, |shares| { shares .unwrap_or_else(Uint128::zero) .checked_add(debt_shares_to_add) - .map_err(StdError::overflow) + .map_err(ContractError::Overflow) })?; - DEBT_SHARES.update( - deps.storage, - (token_id, &coin.denom), - |shares| -> StdResult<_> { - shares - .unwrap_or_else(Uint128::zero) - .checked_add(debt_shares_to_add) - .map_err(StdError::overflow) - }, - )?; + DEBT_SHARES.update(deps.storage, (token_id, &coin.denom), |shares| { + shares + .unwrap_or_else(Uint128::zero) + .checked_add(debt_shares_to_add) + .map_err(ContractError::Overflow) + })?; - COIN_BALANCES.update( - deps.storage, - (token_id, &coin.denom), - |current_amount| -> StdResult<_> { - current_amount - .unwrap_or_else(Uint128::zero) - .checked_add(coin.amount) - .map_err(StdError::overflow) - }, - )?; + increment_coin_balance(deps.storage, token_id, &coin)?; Ok(Response::new() .add_message(red_bank.borrow_msg(&coin)?) diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index aea01e609..3eccc8cda 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -1,9 +1,9 @@ -use cosmwasm_std::{Coin, Response, StdError, StdResult, Storage, Uint128}; +use cosmwasm_std::{Coin, Response, Storage, Uint128}; use rover::coins::Coins; use rover::error::{ContractError, ContractResult}; -use crate::state::{ALLOWED_COINS, COIN_BALANCES}; +use crate::utils::{assert_coin_is_whitelisted, increment_coin_balance}; pub fn deposit( storage: &mut dyn Storage, @@ -12,7 +12,7 @@ pub fn deposit( coin: &Coin, received_coins: &mut Coins, ) -> ContractResult { - assert_coin_is_whitelisted(storage, &coin.denom)?; + assert_coin_is_whitelisted(storage, coin)?; if coin.amount.is_zero() { return Ok(response); @@ -22,8 +22,7 @@ pub fn deposit( received_coins.deduct(coin)?; - // increase the token's asset amount - increment_position(storage, nft_token_id, coin)?; + increment_coin_balance(storage, nft_token_id, coin)?; Ok(response .add_attribute("action", "rover/credit_manager/callback/deposit") @@ -45,25 +44,3 @@ fn assert_sent_fund(expected: &Coin, received_coins: &Coins) -> ContractResult<( Ok(()) } - -pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, denom: &str) -> ContractResult<()> { - let is_whitelisted = ALLOWED_COINS.has(storage, denom); - if !is_whitelisted { - return Err(ContractError::NotWhitelisted(denom.to_string())); - } - Ok(()) -} - -fn increment_position(storage: &mut dyn Storage, token_id: &str, coin: &Coin) -> StdResult<()> { - COIN_BALANCES.update( - storage, - (token_id, &coin.denom), - |value_opt| -> StdResult<_> { - value_opt - .unwrap_or_else(Uint128::zero) - .checked_add(coin.amount) - .map_err(StdError::overflow) - }, - )?; - Ok(()) -} diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 9710cf9ef..ea36705b4 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -15,6 +15,7 @@ use crate::deposit::deposit; use crate::health::assert_below_max_ltv; use crate::repay::repay; use crate::state::{ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, ORACLE, OWNER, RED_BANK}; +use crate::withdraw::withdraw; pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; @@ -128,6 +129,11 @@ pub fn dispatch_actions( Action::Deposit(coin) => { response = deposit(deps.storage, response, token_id, coin, &mut received_coins)?; } + Action::Withdraw(coin) => callbacks.push(CallbackMsg::Withdraw { + token_id: token_id.to_string(), + coin: coin.clone(), + recipient: info.sender.clone(), + }), Action::Borrow(coin) => callbacks.push(CallbackMsg::Borrow { token_id: token_id.to_string(), coin: coin.clone(), @@ -170,6 +176,11 @@ pub fn execute_callback( return Err(ContractError::ExternalInvocation); } match callback { + CallbackMsg::Withdraw { + token_id, + coin, + recipient, + } => withdraw(deps, &token_id, coin, recipient), CallbackMsg::Borrow { coin, token_id } => borrow(deps, env, &token_id, coin), CallbackMsg::Repay { token_id, coin } => repay(deps, env, &token_id, coin), CallbackMsg::AssertBelowMaxLTV { token_id } => { diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index b1b86677c..5108a81fb 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -8,3 +8,5 @@ pub mod instantiate; pub mod query; pub mod repay; pub mod state; +pub mod utils; +pub mod withdraw; diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index cede8f7ff..87905f997 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -1,17 +1,18 @@ -use cosmwasm_std::{Coin, DepsMut, Env, Response, StdError, StdResult, Uint128}; use std::cmp::min; +use cosmwasm_std::{Coin, DepsMut, Env, Response}; + use rover::error::{ContractError, ContractResult}; -use crate::deposit::assert_coin_is_whitelisted; -use crate::state::{COIN_BALANCES, DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; +use crate::state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; +use crate::utils::{assert_coin_is_whitelisted, decrement_coin_balance}; pub fn repay(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractResult { if coin.amount.is_zero() { return Err(ContractError::NoAmount); } - assert_coin_is_whitelisted(deps.storage, &coin.denom)?; + assert_coin_is_whitelisted(deps.storage, &coin)?; let red_bank = RED_BANK.load(deps.storage)?; let total_debt_amount = @@ -51,15 +52,12 @@ pub fn repay(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRes &total_debt_shares.checked_sub(shares_to_repay)?, )?; - // Decrement token's coin balance position - COIN_BALANCES.update( + decrement_coin_balance( deps.storage, - (token_id, &coin.denom), - |current_amount| -> StdResult<_> { - current_amount - .unwrap_or_else(Uint128::zero) - .checked_sub(amount_to_repay) - .map_err(StdError::overflow) + token_id, + &Coin { + denom: coin.denom.clone(), + amount: amount_to_repay, }, )?; diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs new file mode 100644 index 000000000..69cce9e83 --- /dev/null +++ b/contracts/credit-manager/src/utils.rs @@ -0,0 +1,42 @@ +use crate::state::{ALLOWED_COINS, COIN_BALANCES}; +use cosmwasm_std::{Coin, Storage, Uint128}; +use rover::error::{ContractError, ContractResult}; + +pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, coin: &Coin) -> ContractResult<()> { + let is_whitelisted = ALLOWED_COINS.has(storage, &coin.denom); + if !is_whitelisted { + return Err(ContractError::NotWhitelisted(coin.denom.clone())); + } + Ok(()) +} + +pub fn increment_coin_balance( + storage: &mut dyn Storage, + token_id: &str, + coin: &Coin, +) -> ContractResult { + COIN_BALANCES.update(storage, (token_id, &coin.denom), |value_opt| { + value_opt + .unwrap_or_else(Uint128::zero) + .checked_add(coin.amount) + .map_err(ContractError::Overflow) + }) +} + +pub fn decrement_coin_balance( + storage: &mut dyn Storage, + token_id: &str, + coin: &Coin, +) -> ContractResult { + let path = COIN_BALANCES.key((token_id, &coin.denom)); + let value_opt = path.may_load(storage)?; + let new_value = value_opt + .unwrap_or_else(Uint128::zero) + .checked_sub(coin.amount)?; + if new_value.is_zero() { + path.remove(storage); + } else { + path.save(storage, &new_value)?; + } + Ok(new_value) +} diff --git a/contracts/credit-manager/src/withdraw.rs b/contracts/credit-manager/src/withdraw.rs new file mode 100644 index 000000000..3bd2dde76 --- /dev/null +++ b/contracts/credit-manager/src/withdraw.rs @@ -0,0 +1,31 @@ +use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, DepsMut, Response}; + +use rover::error::{ContractError, ContractResult}; + +use crate::utils::{assert_coin_is_whitelisted, decrement_coin_balance}; + +pub fn withdraw( + deps: DepsMut, + token_id: &str, + coin: Coin, + recipient: Addr, +) -> ContractResult { + assert_coin_is_whitelisted(deps.storage, &coin)?; + + if coin.amount.is_zero() { + return Err(ContractError::NoAmount); + } + + decrement_coin_balance(deps.storage, token_id, &coin)?; + + // send coin to recipient + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: recipient.to_string(), + amount: vec![coin.clone()], + }); + + Ok(Response::new() + .add_message(transfer_msg) + .add_attribute("action", "rover/credit_manager/callback/withdraw") + .add_attribute("withdrawn", coin.to_string())) +} diff --git a/contracts/credit-manager/tests/helpers/mock_coin_info.rs b/contracts/credit-manager/tests/helpers/mock_coin_info.rs new file mode 100644 index 000000000..ff6bb8846 --- /dev/null +++ b/contracts/credit-manager/tests/helpers/mock_coin_info.rs @@ -0,0 +1,28 @@ +use crate::helpers::CoinInfo; +use cosmwasm_std::Decimal; + +pub fn uosmo_info() -> CoinInfo { + CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + } +} +pub fn uatom_info() -> CoinInfo { + CoinInfo { + denom: "uatom".to_string(), + price: Decimal::from_atomics(10u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), + liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), + } +} + +pub fn ujake_info() -> CoinInfo { + CoinInfo { + denom: "ujake".to_string(), + price: Decimal::from_atomics(23654u128, 4).unwrap(), + max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), + } +} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 7d378e5cd..27d2250fa 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -415,7 +415,7 @@ impl MockEnvBuilder { .unwrap_or_else(|| self.setup_red_bank()) } - pub fn setup_red_bank(&mut self) -> RedBankBase { + fn setup_red_bank(&mut self) -> RedBankBase { let contract_code_id = self.app.store_code(mock_red_bank_contract()); let addr = self .app diff --git a/contracts/credit-manager/tests/helpers/mod.rs b/contracts/credit-manager/tests/helpers/mod.rs index f9967ae9e..a8de1eb73 100644 --- a/contracts/credit-manager/tests/helpers/mod.rs +++ b/contracts/credit-manager/tests/helpers/mod.rs @@ -1,11 +1,13 @@ pub use self::assertions::*; pub use self::builders::*; pub use self::contracts::*; +pub use self::mock_coin_info::*; pub use self::mock_env::*; pub use self::types::*; mod assertions; mod builders; mod contracts; +mod mock_coin_info; mod mock_env; mod types; diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index 70aec196f..1e3560314 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -6,18 +6,15 @@ use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use rover::error::ContractError; use rover::msg::execute::Action::{Borrow, Deposit}; -use crate::helpers::{assert_err, AccountToFund, CoinInfo, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE}; +use crate::helpers::{ + assert_err, uosmo_info, AccountToFund, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, +}; pub mod helpers; #[test] fn test_only_token_owner_can_borrow() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -45,12 +42,7 @@ fn test_only_token_owner_can_borrow() { #[test] fn test_can_only_borrow_what_is_whitelisted() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new().allowed_coins(&[coin_info]).build().unwrap(); @@ -61,7 +53,7 @@ fn test_can_only_borrow_what_is_whitelisted() { &user, vec![Borrow(Coin { denom: "usomething".to_string(), - amount: Uint128::from(234u128), + amount: Uint128::new(234), })], &[], ); @@ -74,12 +66,7 @@ fn test_can_only_borrow_what_is_whitelisted() { #[test] fn test_borrowing_zero_does_nothing() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -104,12 +91,7 @@ fn test_borrowing_zero_does_nothing() { #[test] fn test_cannot_borrow_above_max_ltv() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[coin_info.clone()]) @@ -129,8 +111,8 @@ fn test_cannot_borrow_above_max_ltv() { &token_id, &user, vec![ - Deposit(coin_info.to_coin(Uint128::from(300u128))), - Borrow(coin_info.to_coin(Uint128::from(700u128))), + Deposit(coin_info.to_coin(Uint128::new(300))), + Borrow(coin_info.to_coin(Uint128::new(700))), ], &[Coin::new(300u128, coin_info.denom)], ); @@ -140,12 +122,7 @@ fn test_cannot_borrow_above_max_ltv() { #[test] fn test_success_when_new_debt_asset() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[coin_info.clone()]) @@ -166,11 +143,11 @@ fn test_success_when_new_debt_asset() { vec![ Deposit(Coin { denom: coin_info.denom.clone(), - amount: Uint128::from(300u128), + amount: Uint128::new(300), }), Borrow(Coin { denom: coin_info.denom.clone(), - amount: Uint128::from(42u128), + amount: Uint128::new(42), }), ], &[Coin::new(300u128, coin_info.denom.clone())], @@ -182,7 +159,7 @@ fn test_success_when_new_debt_asset() { let asset_res = position.coins.first().unwrap(); assert_eq!( asset_res.amount, - Uint128::from(342u128) // Deposit + Borrow + Uint128::new(342) // Deposit + Borrow ); assert_eq!(asset_res.denom, coin_info.denom); assert_eq!(asset_res.price, coin_info.price); @@ -195,40 +172,35 @@ fn test_success_when_new_debt_asset() { assert_eq!(position.debt_shares.len(), 1); assert_eq!( debt_shares_res.shares, - Uint128::from(42u128).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) + Uint128::new(42).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) ); assert_eq!(debt_shares_res.denom, coin_info.denom); - let debt_amount = Uint128::from(42u128) + Uint128::new(1u128); // simulated yield + let debt_amount = Uint128::new(42u128) + Uint128::new(1); // simulated yield assert_eq!( debt_shares_res.total_value, coin_info.price * Decimal::from_atomics(debt_amount, 0).unwrap() ); let coin = mock.query_balance(&mock.rover, &coin_info.denom); - assert_eq!(coin.amount, Uint128::from(342u128)); + assert_eq!(coin.amount, Uint128::new(342)); let config = mock.query_config(); let coin = mock.query_balance(&Addr::unchecked(config.red_bank), &coin_info.denom); assert_eq!( coin.amount, - DEFAULT_RED_BANK_COIN_BALANCE.sub(Uint128::from(42u128)) + DEFAULT_RED_BANK_COIN_BALANCE.sub(Uint128::new(42)) ); let res = mock.query_total_debt_shares(&coin_info.denom); assert_eq!( res.shares, - Uint128::from(42u128).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) + Uint128::new(42).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) ); } #[test] fn test_debt_shares_with_debt_amount() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); let mut mock = MockEnv::new() @@ -250,8 +222,8 @@ fn test_debt_shares_with_debt_amount() { &token_id_a, &user_a, vec![ - Deposit(coin_info.to_coin(Uint128::from(300u128))), - Borrow(coin_info.to_coin(Uint128::from(50u128))), + Deposit(coin_info.to_coin(Uint128::new(300))), + Borrow(coin_info.to_coin(Uint128::new(50))), ], &[Coin::new(300u128, coin_info.denom.clone())], ) @@ -263,22 +235,22 @@ fn test_debt_shares_with_debt_amount() { &token_id_b, &user_b, vec![ - Deposit(coin_info.to_coin(Uint128::from(450u128))), - Borrow(coin_info.to_coin(Uint128::from(50u128))), + Deposit(coin_info.to_coin(Uint128::new(450))), + Borrow(coin_info.to_coin(Uint128::new(50))), ], &[Coin::new(450u128, coin_info.denom.clone())], ) .unwrap(); - let token_a_shares = Uint128::from(50u128).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); + let token_a_shares = Uint128::new(50).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); let position = mock.query_position(&token_id_a); let debt_position_a = position.debt_shares.first().unwrap(); assert_eq!(debt_position_a.shares, token_a_shares.clone()); assert_eq!(debt_position_a.denom, coin_info.denom); - let token_b_shares = Uint128::from(50u128) + let token_b_shares = Uint128::new(50) .mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) - .multiply_ratio(Uint128::from(50u128), interim_red_bank_debt.amount); + .multiply_ratio(Uint128::new(50), interim_red_bank_debt.amount); let position = mock.query_position(&token_id_b); let debt_position_b = position.debt_shares.first().unwrap(); assert_eq!(debt_position_b.shares, token_b_shares.clone()); @@ -310,7 +282,7 @@ fn test_debt_shares_with_debt_amount() { ); // NOTE: There is an expected rounding error. This will not pass. - // let total_borrowed_plus_interest = Decimal::from_atomics(Uint128::from(102u128), 0).unwrap(); + // let total_borrowed_plus_interest = Decimal::from_atomics(Uint128::new(102), 0).unwrap(); // assert_eq!( // total_borrowed_plus_interest * coin_info.price, // debt_position_a.total_value + debt_position_b.total_value diff --git a/contracts/credit-manager/tests/test_deposit.rs b/contracts/credit-manager/tests/test_deposit.rs index bdd544e31..b52e6fa86 100644 --- a/contracts/credit-manager/tests/test_deposit.rs +++ b/contracts/credit-manager/tests/test_deposit.rs @@ -7,7 +7,9 @@ use rover::error::ContractError::{ use rover::msg::execute::Action; use rover::msg::query::PositionResponse; -use crate::helpers::{assert_err, AccountToFund, CoinInfo, MockEnv}; +use crate::helpers::{ + assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv, +}; pub mod helpers; @@ -39,12 +41,7 @@ fn test_only_owner_of_token_can_deposit() { #[test] fn test_deposit_nothing() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let mut mock = MockEnv::new() .allowed_coins(&[coin_info.clone()]) @@ -70,12 +67,7 @@ fn test_deposit_nothing() { #[test] fn test_deposit_but_no_funds() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let mut mock = MockEnv::new() .allowed_coins(&[coin_info.clone()]) @@ -84,7 +76,7 @@ fn test_deposit_but_no_funds() { let user = Addr::unchecked("user"); let token_id = mock.create_credit_account(&user).unwrap(); - let deposit_amount = Uint128::from(234u128); + let deposit_amount = Uint128::new(234); let res = mock.update_credit_account( &token_id, &user, @@ -106,12 +98,7 @@ fn test_deposit_but_no_funds() { #[test] fn test_deposit_but_not_enough_funds() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -127,27 +114,22 @@ fn test_deposit_but_not_enough_funds() { let res = mock.update_credit_account( &token_id, &user, - vec![Action::Deposit(coin_info.to_coin(Uint128::from(350u128)))], + vec![Action::Deposit(coin_info.to_coin(Uint128::new(350)))], &[Coin::new(250u128, coin_info.denom)], ); assert_err( res, FundsMismatch { - expected: Uint128::from(350u128), - received: Uint128::from(250u128), + expected: Uint128::new(350), + received: Uint128::new(250), }, ); } #[test] fn test_can_only_deposit_allowed_assets() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[coin_info.clone()]) @@ -159,10 +141,7 @@ fn test_can_only_deposit_allowed_assets() { .unwrap(); let token_id = mock.create_credit_account(&user).unwrap(); - let not_allowed_coin = Coin { - denom: "ujakecoin".to_string(), - amount: Uint128::from(234u128), - }; + let not_allowed_coin = ujake_info().to_coin(Uint128::new(234)); let res = mock.update_credit_account( &token_id, @@ -179,18 +158,8 @@ fn test_can_only_deposit_allowed_assets() { #[test] fn test_extra_funds_received() { - let uosmo_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - let uatom_info = CoinInfo { - denom: "uatom".to_string(), - price: Decimal::from_atomics(10u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), - liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), - }; + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -210,7 +179,7 @@ fn test_extra_funds_received() { let res = mock.update_credit_account( &token_id, &user, - vec![Action::Deposit(uosmo_info.to_coin(Uint128::from(234u128)))], + vec![Action::Deposit(uosmo_info.to_coin(Uint128::new(234)))], &[Coin::new(234u128, uosmo_info.denom), extra_funds.clone()], ); @@ -222,12 +191,7 @@ fn test_extra_funds_received() { #[test] fn test_deposit_success() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -240,7 +204,7 @@ fn test_deposit_success() { .unwrap(); let token_id = mock.create_credit_account(&user).unwrap(); - let deposit_amount = Uint128::from(234u128); + let deposit_amount = Uint128::new(234); mock.update_credit_account( &token_id, &user, @@ -266,18 +230,8 @@ fn test_deposit_success() { #[test] fn test_multiple_deposit_actions() { - let uosmo_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; - let uatom_info = CoinInfo { - denom: "uatom".to_string(), - price: Decimal::from_atomics(10u128, 1).unwrap(), - max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), - liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), - }; + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -293,8 +247,8 @@ fn test_multiple_deposit_actions() { .unwrap(); let token_id = mock.create_credit_account(&user).unwrap(); - let uosmo_amount = Uint128::from(234u128); - let uatom_amount = Uint128::from(25u128); + let uosmo_amount = Uint128::new(234); + let uatom_amount = Uint128::new(25); mock.update_credit_account( &token_id, diff --git a/contracts/credit-manager/tests/test_enumerate_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs index 7139cca20..c4b448c01 100644 --- a/contracts/credit-manager/tests/test_enumerate_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs @@ -154,7 +154,7 @@ fn test_pagination_on_all_coin_balances_query_works() { .map(|coin| CoinBalanceResponseItem { token_id: token_id_a.clone(), denom: coin.denom.clone(), - amount: Uint128::from(1u128), + amount: Uint128::new(1), }) .collect::>(); @@ -163,7 +163,7 @@ fn test_pagination_on_all_coin_balances_query_works() { .map(|coin| CoinBalanceResponseItem { token_id: token_id_b.clone(), denom: coin.denom.clone(), - amount: Uint128::from(1u128), + amount: Uint128::new(1), }) .collect::>(); @@ -172,7 +172,7 @@ fn test_pagination_on_all_coin_balances_query_works() { .map(|coin| CoinBalanceResponseItem { token_id: token_id_c.clone(), denom: coin.denom.clone(), - amount: Uint128::from(1u128), + amount: Uint128::new(1), }) .collect::>(); diff --git a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs index 1e4794d5b..444dcf088 100644 --- a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs @@ -83,7 +83,7 @@ fn test_pagination_on_all_debt_shares_query_works() { Action::Deposit(coin.clone()), Action::Borrow(Coin { denom: coin.denom.clone(), - amount: Uint128::from(1u128), + amount: Uint128::new(1), }), ] }) @@ -103,7 +103,7 @@ fn test_pagination_on_all_debt_shares_query_works() { Action::Deposit(coin.clone()), Action::Borrow(Coin { denom: coin.denom.clone(), - amount: Uint128::from(1u128), + amount: Uint128::new(1), }), ] }) @@ -123,7 +123,7 @@ fn test_pagination_on_all_debt_shares_query_works() { Action::Deposit(coin.clone()), Action::Borrow(Coin { denom: coin.denom.clone(), - amount: Uint128::from(1u128), + amount: Uint128::new(1), }), ] }) diff --git a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs index 19631155c..eaa4c6baf 100644 --- a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs @@ -83,7 +83,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { Action::Deposit(coin.clone()), Action::Borrow(Coin { denom: coin.denom.clone(), - amount: Uint128::from(1u128), + amount: Uint128::new(1), }), ] }) @@ -103,7 +103,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { Action::Deposit(coin.clone()), Action::Borrow(Coin { denom: coin.denom.clone(), - amount: Uint128::from(1u128), + amount: Uint128::new(1), }), ] }) @@ -123,7 +123,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { Action::Deposit(coin.clone()), Action::Borrow(Coin { denom: coin.denom.clone(), - amount: Uint128::from(1u128), + amount: Uint128::new(1), }), ] }) diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index 05d6c1228..bad38840b 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -8,7 +8,7 @@ use rover::error::ContractError; use rover::msg::execute::Action::{Borrow, Deposit}; use rover::msg::query::DebtSharesValue; -use crate::helpers::{assert_err, AccountToFund, CoinInfo, MockEnv}; +use crate::helpers::{assert_err, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv}; pub mod helpers; @@ -19,12 +19,7 @@ pub mod helpers; /// above_max_ltv: false #[test] fn test_only_assets_with_no_debts() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -37,7 +32,7 @@ fn test_only_assets_with_no_debts() { .unwrap(); let token_id = mock.create_credit_account(&user).unwrap(); - let deposit_amount = Uint128::from(300u128); + let deposit_amount = Uint128::new(300); mock.update_credit_account( &token_id, &user, @@ -90,8 +85,8 @@ fn test_terra_ragnarok() { .unwrap(); let token_id = mock.create_credit_account(&user).unwrap(); - let deposit_amount = Uint128::from(12u128); - let borrow_amount = Uint128::from(2u128); + let deposit_amount = Uint128::new(12); + let borrow_amount = Uint128::new(2); mock.update_credit_account( &token_id, @@ -113,8 +108,8 @@ fn test_terra_ragnarok() { coin_info.price * Decimal::from_atomics(deposit_amount + borrow_amount, 0).unwrap(); assert_eq!(health.total_assets_value, assets_value); // Note: Simulated yield from mock_red_bank makes debt position more expensive - let debts_value = coin_info.price - * Decimal::from_atomics(borrow_amount.add(Uint128::from(1u128)), 0).unwrap(); + let debts_value = + coin_info.price * Decimal::from_atomics(borrow_amount.add(Uint128::new(1)), 0).unwrap(); assert_eq!(health.total_debts_value, debts_value); assert_eq!( health.lqdt_health_factor, @@ -152,12 +147,7 @@ fn test_terra_ragnarok() { /// above_max_ltv: true #[test] fn test_debts_no_assets() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::one(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[coin_info.clone()]) @@ -169,7 +159,7 @@ fn test_debts_no_assets() { .unwrap(); let token_id = mock.create_credit_account(&user).unwrap(); - let borrowed_amount = Uint128::from(100u128); + let borrowed_amount = Uint128::new(100); let res = mock.update_credit_account( &token_id, &user, @@ -207,12 +197,7 @@ fn test_debts_no_assets() { /// AboveMaxLtv error thrown #[test] fn test_cannot_borrow_more_than_healthy() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(23654u128, 4).unwrap(), - max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), - }; + let coin_info = ujake_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -229,13 +214,10 @@ fn test_cannot_borrow_more_than_healthy() { &token_id, &user, vec![ - Deposit(coin_info.to_coin(Uint128::from(300u128))), - Borrow(coin_info.to_coin(Uint128::from(50u128))), + Deposit(coin_info.to_coin(Uint128::new(300))), + Borrow(coin_info.to_coin(Uint128::new(50))), ], - &[Coin::new( - Uint128::from(300u128).into(), - coin_info.denom.clone(), - )], + &[Coin::new(Uint128::new(300).into(), coin_info.denom.clone())], ) .unwrap(); @@ -263,7 +245,7 @@ fn test_cannot_borrow_more_than_healthy() { mock.update_credit_account( &token_id, &user, - vec![Borrow(coin_info.to_coin(Uint128::from(100u128)))], + vec![Borrow(coin_info.to_coin(Uint128::new(100)))], &[], ) .unwrap(); @@ -271,7 +253,7 @@ fn test_cannot_borrow_more_than_healthy() { let res = mock.update_credit_account( &token_id, &user, - vec![Borrow(coin_info.to_coin(Uint128::from(150u128)))], + vec![Borrow(coin_info.to_coin(Uint128::new(150)))], &[], ); @@ -336,8 +318,8 @@ fn test_cannot_borrow_more_but_not_liquidatable() { &token_id, &user, vec![ - Deposit(uosmo_info.to_coin(Uint128::from(300u128))), - Borrow(uatom_info.to_coin(Uint128::from(50u128))), + Deposit(uosmo_info.to_coin(Uint128::new(300))), + Borrow(uatom_info.to_coin(Uint128::new(50))), ], &[Coin::new(300, uosmo_info.denom)], ) @@ -359,7 +341,7 @@ fn test_cannot_borrow_more_but_not_liquidatable() { let res = mock.update_credit_account( &token_id, &user, - vec![Borrow(uatom_info.to_coin(Uint128::from(2u128)))], + vec![Borrow(uatom_info.to_coin(Uint128::new(2)))], &[], ); @@ -407,8 +389,8 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { .unwrap(); let token_id = mock.create_credit_account(&user).unwrap(); - let deposit_amount = Uint128::from(298u128); - let borrowed_amount = Uint128::from(49u128); + let deposit_amount = Uint128::new(298); + let borrowed_amount = Uint128::new(49); mock.update_credit_account( &token_id, &user, @@ -498,9 +480,9 @@ fn test_debt_value() { let token_id_a = mock.create_credit_account(&user_a).unwrap(); let token_id_b = mock.create_credit_account(&user_b).unwrap(); - let user_a_deposit_amount_osmo = Uint128::from(298u128); - let user_a_borrowed_amount_atom = Uint128::from(49u128); - let user_a_borrowed_amount_osmo = Uint128::from(30u128); + let user_a_deposit_amount_osmo = Uint128::new(298); + let user_a_borrowed_amount_atom = Uint128::new(49); + let user_a_borrowed_amount_osmo = Uint128::new(30); mock.update_credit_account( &token_id_a, @@ -519,8 +501,8 @@ fn test_debt_value() { let interim_red_bank_debt = mock.query_red_bank_debt(&uatom_info.denom); - let user_b_deposit_amount = Uint128::from(101u128); - let user_b_borrowed_amount_atom = Uint128::from(24u128); + let user_b_deposit_amount = Uint128::new(101); + let user_b_borrowed_amount_atom = Uint128::new(24); mock.update_credit_account( &token_id_b, diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 5e41c32c9..482824955 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -1,4 +1,6 @@ -use crate::helpers::{assert_contents_equal, CoinInfo, MockEnv}; +use crate::helpers::{ + assert_contents_equal, uatom_info, ujake_info, uosmo_info, CoinInfo, MockEnv, +}; use cosmwasm_std::Decimal; pub mod helpers; @@ -57,30 +59,15 @@ fn test_raises_on_invalid_vaults_addr() { #[test] fn test_allowed_coins_set_on_instantiate() { let allowed_coins = vec![ - CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }, - CoinInfo { - denom: "uatom".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }, + uosmo_info(), + uatom_info(), + ujake_info(), CoinInfo { denom: "umars".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }, - CoinInfo { - denom: "ujake".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }, ]; let mock = MockEnv::new() .allowed_coins(&allowed_coins) diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index 9a520a5e6..a5a0b46c7 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -1,22 +1,20 @@ -use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; -use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use std::ops::{Add, Mul, Sub}; +use cosmwasm_std::{Addr, Coin, Decimal, OverflowError, OverflowOperation, Uint128}; + +use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use rover::error::ContractError; -use rover::msg::execute::Action::{Borrow, Deposit, Repay}; +use rover::msg::execute::Action::{Borrow, Deposit, Repay, Withdraw}; -use crate::helpers::{assert_err, AccountToFund, CoinInfo, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE}; +use crate::helpers::{ + assert_err, uosmo_info, AccountToFund, CoinInfo, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, +}; pub mod helpers; #[test] fn test_only_token_owner_can_repay() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let owner = Addr::unchecked("owner"); let mut mock = MockEnv::new().build().unwrap(); let token_id = mock.create_credit_account(&owner).unwrap(); @@ -40,12 +38,7 @@ fn test_only_token_owner_can_repay() { #[test] fn test_can_only_repay_what_is_whitelisted() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new().allowed_coins(&[coin_info]).build().unwrap(); let token_id = mock.create_credit_account(&user).unwrap(); @@ -55,7 +48,7 @@ fn test_can_only_repay_what_is_whitelisted() { &user, vec![Repay(Coin { denom: "usomething".to_string(), - amount: Uint128::from(234u128), + amount: Uint128::new(234), })], &[], ); @@ -68,12 +61,7 @@ fn test_can_only_repay_what_is_whitelisted() { #[test] fn test_repaying_zero_raises() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[coin_info.clone()]) @@ -93,12 +81,7 @@ fn test_repaying_zero_raises() { #[test] fn test_raises_when_repaying_what_is_not_owed() { - let uosmo_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let uosmo_info = uosmo_info(); let uatom_info = CoinInfo { denom: "atom".to_string(), @@ -131,10 +114,10 @@ fn test_raises_when_repaying_what_is_not_owed() { &token_id_b, &user_b, vec![ - Deposit(uatom_info.to_coin(Uint128::from(100u128))), - Borrow(uatom_info.to_coin(Uint128::from(12u128))), + Deposit(uatom_info.to_coin(Uint128::new(100))), + Borrow(uatom_info.to_coin(Uint128::new(12))), ], - &[uatom_info.to_coin(Uint128::from(100u128))], + &[uatom_info.to_coin(Uint128::new(100))], ) .unwrap(); @@ -142,30 +125,65 @@ fn test_raises_when_repaying_what_is_not_owed() { &token_id_a, &user_a, vec![ - Deposit(uatom_info.to_coin(Uint128::from(300u128))), - Borrow(uosmo_info.to_coin(Uint128::from(42u128))), - Repay(uatom_info.to_coin(Uint128::from(42u128))), + Deposit(uatom_info.to_coin(Uint128::new(300))), + Borrow(uosmo_info.to_coin(Uint128::new(42))), + Repay(uatom_info.to_coin(Uint128::new(42))), ], - &[uatom_info.to_coin(Uint128::from(300u128))], + &[uatom_info.to_coin(Uint128::new(300))], ); assert_err(res, ContractError::NoDebt) } -// TODO: After withdraw is implemented, complete this test -// Should do a deposit, borrow another denom, withdraw some -// and then attempt to repay with not enough in assets #[test] -fn test_raises_when_not_enough_assets_to_repay() {} +fn test_raises_when_not_enough_assets_to_repay() { + let uosmo_info = uosmo_info(); + + let uatom_info = CoinInfo { + denom: "atom".to_string(), + price: Decimal::from_atomics(9u128, 0).unwrap(), + max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), + }; + + let user = Addr::unchecked("user"); + + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, uatom_info.denom.clone())], + }) + .build() + .unwrap(); + + let token_id_a = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id_a, + &user, + vec![ + Deposit(uatom_info.to_coin(Uint128::new(300))), + Borrow(uosmo_info.to_coin(Uint128::new(50))), + Withdraw(uosmo_info.to_coin(Uint128::new(10))), + Repay(uosmo_info.to_coin(Uint128::new(50))), + ], + &[uatom_info.to_coin(Uint128::new(300))], + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: OverflowOperation::Sub, + operand1: "40".to_string(), + operand2: "50".to_string(), + }), + ) +} #[test] fn test_successful_repay() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -188,8 +206,8 @@ fn test_successful_repay() { &token_id, &user, vec![ - Deposit(coin_info.to_coin(Uint128::from(300u128))), - Borrow(coin_info.to_coin(Uint128::from(50u128))), + Deposit(coin_info.to_coin(Uint128::new(300))), + Borrow(coin_info.to_coin(Uint128::new(50))), ], &[Coin::new(300u128, coin_info.denom.clone())], ) @@ -200,7 +218,7 @@ fn test_successful_repay() { mock.update_credit_account( &token_id, &user, - vec![Repay(coin_info.to_coin(Uint128::from(20u128)))], + vec![Repay(coin_info.to_coin(Uint128::new(20)))], &[], ) .unwrap(); @@ -208,16 +226,16 @@ fn test_successful_repay() { let position = mock.query_position(&token_id); assert_eq!(position.coins.len(), 1); let asset_res = position.coins.first().unwrap(); - let expected_net_asset_amount = Uint128::from(330u128); // Deposit + Borrow - Repay + let expected_net_asset_amount = Uint128::new(330); // Deposit + Borrow - Repay assert_eq!(asset_res.amount, expected_net_asset_amount); let debt_shares_res = position.debt_shares.first().unwrap(); assert_eq!(position.debt_shares.len(), 1); assert_eq!(debt_shares_res.denom, coin_info.denom); - let former_total_debt_shares = Uint128::from(50u128).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); - let debt_shares_paid = former_total_debt_shares - .multiply_ratio(Uint128::from(20u128), interim_red_bank_debt.amount); + let former_total_debt_shares = Uint128::new(50).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); + let debt_shares_paid = + former_total_debt_shares.multiply_ratio(Uint128::new(20), interim_red_bank_debt.amount); let new_total_debt_shares = former_total_debt_shares.sub(debt_shares_paid); assert_eq!(debt_shares_res.shares, new_total_debt_shares); @@ -225,20 +243,20 @@ fn test_successful_repay() { assert_eq!(res.shares, new_total_debt_shares); let coin = mock.query_balance(&mock.rover, &coin_info.denom); - assert_eq!(coin.amount, Uint128::from(330u128)); + assert_eq!(coin.amount, Uint128::new(330)); let config = mock.query_config(); let red_bank_addr = Addr::unchecked(config.red_bank); let coin = mock.query_balance(&red_bank_addr, &coin_info.denom); assert_eq!( coin.amount, - DEFAULT_RED_BANK_COIN_BALANCE.sub(Uint128::from(30u128)) + DEFAULT_RED_BANK_COIN_BALANCE.sub(Uint128::new(30)) ); mock.update_credit_account( &token_id, &user, - vec![Repay(coin_info.to_coin(Uint128::from(31u128)))], // Interest accrued paid back as well + vec![Repay(coin_info.to_coin(Uint128::new(31)))], // Interest accrued paid back as well &[], ) .unwrap(); @@ -246,7 +264,7 @@ fn test_successful_repay() { let position = mock.query_position(&token_id); assert_eq!(position.coins.len(), 1); let asset_res = position.coins.first().unwrap(); - let expected_net_asset_amount = Uint128::from(299u128); // Deposit + Borrow - full repay - interest + let expected_net_asset_amount = Uint128::new(299); // Deposit + Borrow - full repay - interest assert_eq!(asset_res.amount, expected_net_asset_amount); // Full debt repaid and purged from storage @@ -256,22 +274,17 @@ fn test_successful_repay() { assert_eq!(res.shares, Uint128::zero()); let coin = mock.query_balance(&mock.rover, &coin_info.denom); - assert_eq!(coin.amount, Uint128::from(299u128)); + assert_eq!(coin.amount, Uint128::new(299)); let coin = mock.query_balance(&red_bank_addr, &coin_info.denom); assert_eq!( coin.amount, - DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::from(1u128)) + DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1)) ); } #[test] fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { - let coin_info = CoinInfo { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - }; + let coin_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -290,9 +303,9 @@ fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { &token_id, &user, vec![ - Deposit(coin_info.to_coin(Uint128::from(300u128))), - Borrow(coin_info.to_coin(Uint128::from(50u128))), - Repay(coin_info.to_coin(Uint128::from(75u128))), + Deposit(coin_info.to_coin(Uint128::new(300))), + Borrow(coin_info.to_coin(Uint128::new(50))), + Repay(coin_info.to_coin(Uint128::new(75))), ], &[Coin::new(300u128, coin_info.denom.clone())], ) @@ -301,7 +314,7 @@ fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { let position = mock.query_position(&token_id); assert_eq!(position.coins.len(), 1); let asset_res = position.coins.first().unwrap(); - let expected_net_asset_amount = Uint128::from(299u128); // Deposit + Borrow - Repay - interest + let expected_net_asset_amount = Uint128::new(299); // Deposit + Borrow - Repay - interest assert_eq!(asset_res.amount, expected_net_asset_amount); assert_eq!(position.debt_shares.len(), 0); @@ -310,12 +323,12 @@ fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { assert_eq!(res.shares, Uint128::zero()); let coin = mock.query_balance(&mock.rover, &coin_info.denom); - assert_eq!(coin.amount, Uint128::from(299u128)); + assert_eq!(coin.amount, Uint128::new(299)); let config = mock.query_config(); let coin = mock.query_balance(&Addr::unchecked(config.red_bank), &coin_info.denom); assert_eq!( coin.amount, - DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::from(1u128)) + DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1)) ); } diff --git a/contracts/credit-manager/tests/test_withdraw.rs b/contracts/credit-manager/tests/test_withdraw.rs new file mode 100644 index 000000000..f4e43da38 --- /dev/null +++ b/contracts/credit-manager/tests/test_withdraw.rs @@ -0,0 +1,327 @@ +use cosmwasm_std::OverflowOperation::Sub; +use cosmwasm_std::{Addr, Coin, OverflowError, Uint128}; + +use rover::error::ContractError; +use rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; +use rover::msg::execute::Action; + +use crate::helpers::{assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn test_only_owner_of_token_can_withdraw() { + let coin_info = uosmo_info(); + let owner = Addr::unchecked("owner"); + let mut mock = MockEnv::new().build().unwrap(); + let token_id = mock.create_credit_account(&owner).unwrap(); + + let another_user = Addr::unchecked("another_user"); + let res = mock.update_credit_account( + &token_id, + &another_user, + vec![Action::Withdraw(Coin { + denom: coin_info.denom, + amount: Uint128::new(382), + })], + &[], + ); + + assert_err( + res, + NotTokenOwner { + user: another_user.into(), + token_id: token_id.clone(), + }, + ); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); +} + +#[test] +fn test_withdraw_nothing() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![Action::Withdraw(Coin { + denom: coin_info.denom, + amount: Uint128::new(0), + })], + &[], + ); + + assert_err(res, ContractError::NoAmount); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); +} + +#[test] +fn test_withdraw_but_no_funds() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let withdraw_amount = Uint128::new(234); + let res = mock.update_credit_account( + &token_id, + &user, + vec![Action::Withdraw(coin_info.to_coin(withdraw_amount))], + &[], + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: Sub, + operand1: "0".to_string(), + operand2: "234".to_string(), + }), + ); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); +} + +#[test] +fn test_withdraw_but_not_enough_funds() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![ + Action::Deposit(coin_info.to_coin(Uint128::new(300))), + Action::Withdraw(coin_info.to_coin(Uint128::new(400))), + ], + &[Coin::new(300u128, coin_info.denom)], + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: Sub, + operand1: "300".to_string(), + operand2: "400".to_string(), + }), + ); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); +} + +#[test] +fn test_can_only_withdraw_allowed_assets() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let not_allowed_coin = ujake_info().to_coin(Uint128::new(234)); + let res = mock.update_credit_account( + &token_id, + &user, + vec![ + Action::Deposit(coin_info.to_coin(Uint128::new(234))), + Action::Withdraw(not_allowed_coin.clone()), + ], + &[Coin::new(234u128, coin_info.denom)], + ); + + assert_err(res, NotWhitelisted(not_allowed_coin.denom)); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); +} + +#[test] +fn test_cannot_withdraw_more_than_healthy() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let deposit_amount = Uint128::new(200); + let res = mock.update_credit_account( + &token_id, + &user, + vec![ + Action::Deposit(coin_info.to_coin(deposit_amount)), + Action::Borrow(coin_info.to_coin(Uint128::new(400))), + Action::Withdraw(coin_info.to_coin(Uint128::new(50))), + ], + &[Coin::new(200u128, coin_info.denom)], + ); + + assert_err(res, ContractError::AboveMaxLTV); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); +} + +#[test] +fn test_withdraw_success() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, coin_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let deposit_amount = Uint128::new(234); + mock.update_credit_account( + &token_id, + &user, + vec![ + Action::Deposit(coin_info.to_coin(deposit_amount)), + Action::Withdraw(coin_info.to_coin(deposit_amount)), + ], + &[Coin::new(deposit_amount.into(), coin_info.denom.clone())], + ) + .unwrap(); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); + + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, Uint128::zero()) +} + +#[test] +fn test_multiple_withdraw_actions() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![ + Coin::new(234u128, uosmo_info.denom.clone()), + Coin::new(25u128, uatom_info.denom.clone()), + ], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let uosmo_amount = Uint128::new(234); + let uatom_amount = Uint128::new(25); + + mock.update_credit_account( + &token_id, + &user, + vec![ + Action::Deposit(uosmo_info.to_coin(uosmo_amount)), + Action::Deposit(uatom_info.to_coin(uatom_amount)), + ], + &[ + Coin::new(234u128, uosmo_info.denom.clone()), + Coin::new(25u128, uatom_info.denom.clone()), + ], + ) + .unwrap(); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 2); + + let coin = mock.query_balance(&user, &uosmo_info.denom); + assert_eq!(coin.amount, Uint128::zero()); + + let coin = mock.query_balance(&user, &uatom_info.denom); + assert_eq!(coin.amount, Uint128::zero()); + + mock.update_credit_account( + &token_id, + &user, + vec![Action::Withdraw(uosmo_info.to_coin(uosmo_amount))], + &[], + ) + .unwrap(); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 1); + + let coin = mock.query_balance(&mock.rover, &uosmo_info.denom); + assert_eq!(coin.amount, Uint128::zero()); + + let coin = mock.query_balance(&user, &uosmo_info.denom); + assert_eq!(coin.amount, uosmo_amount); + + mock.update_credit_account( + &token_id, + &user, + vec![Action::Withdraw(uatom_info.to_coin(Uint128::new(20)))], + &[], + ) + .unwrap(); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 1); + + let coin = mock.query_balance(&mock.rover, &uatom_info.denom); + assert_eq!(coin.amount, Uint128::new(5)); + + let coin = mock.query_balance(&user, &uatom_info.denom); + assert_eq!(coin.amount, Uint128::new(20)); + + mock.update_credit_account( + &token_id, + &user, + vec![Action::Withdraw(uatom_info.to_coin(Uint128::new(5)))], + &[], + ) + .unwrap(); + + let res = mock.query_position(&token_id); + assert_eq!(res.coins.len(), 0); + + let coin = mock.query_balance(&mock.rover, &uatom_info.denom); + assert_eq!(coin.amount, Uint128::zero()); + + let coin = mock.query_balance(&user, &uatom_info.denom); + assert_eq!(coin.amount, uatom_amount); +} diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs index 15bc8c140..79277cf74 100644 --- a/contracts/mock-red-bank/src/execute.rs +++ b/contracts/mock-red-bank/src/execute.rs @@ -11,7 +11,7 @@ pub fn borrow(deps: DepsMut, info: MessageInfo, coin: Coin) -> StdResult Date: Wed, 31 Aug 2022 11:24:13 +0200 Subject: [PATCH 051/218] Use mars-health for health factor calculations (#16) * Use mars-health for health factor calculations * fix github action --- .github/workflows/main.yml | 79 ++++++------ Cargo.lock | 99 ++++++++++++---- contracts/credit-manager/Cargo.toml | 1 + contracts/credit-manager/src/contract.rs | 13 +- contracts/credit-manager/src/health.rs | 93 +++++---------- contracts/credit-manager/src/query.rs | 112 +++++++++--------- contracts/credit-manager/src/utils.rs | 46 ++++++- .../credit-manager/tests/helpers/mock_env.rs | 15 ++- contracts/credit-manager/tests/test_borrow.rs | 22 ++-- .../credit-manager/tests/test_deposit.rs | 9 +- .../tests/test_enumerate_total_debt_shares.rs | 24 ++-- contracts/credit-manager/tests/test_health.rs | 64 +++++----- contracts/credit-manager/tests/test_repay.rs | 10 +- contracts/mock-oracle/Cargo.toml | 2 + contracts/mock-oracle/src/contract.rs | 11 +- contracts/mock-oracle/src/msg.rs | 5 +- contracts/mock-red-bank/Cargo.toml | 2 + contracts/mock-red-bank/src/msg.rs | 7 -- contracts/mock-red-bank/src/query.rs | 4 +- packages/rover/Cargo.toml | 2 + packages/rover/src/adapters/oracle.rs | 7 +- packages/rover/src/health.rs | 22 ---- packages/rover/src/lib.rs | 1 - packages/rover/src/msg/query.rs | 69 +++++++++-- 24 files changed, 406 insertions(+), 313 deletions(-) delete mode 100644 packages/rover/src/health.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cc50f4f9e..d41615de1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,7 @@ -# Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml +# NOTE: after Mars outposts publishes external crate, this workflow should revert to: +# https://github.com/mars-protocol/rover/blob/8824233d4106eb71ee41a9c7a84b4b94a2bad072/.github/workflows/main.yml +# Rust optimizer does not support local package dependencies from other workspaces. For this reason it is +# temporarily disabled until it can be replaced by a crates.io dep. on: pull_request @@ -11,7 +14,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v3 + with: + path: rover + + - name: Checkout mars-outposts + uses: actions/checkout@v3 + with: + repository: mars-protocol/outposts + token: ${{ secrets.TEMP_ACCESS_TOKEN }} + path: outposts - name: Install stable toolchain uses: actions-rs/toolchain@v1 @@ -25,32 +37,28 @@ jobs: uses: davidB/rust-cargo-make@v1 - name: Run cargo check - uses: actions-rs/cargo@v1 - with: - command: make - args: check + working-directory: rover + run: cargo make check - name: Compile WASM contract - uses: actions-rs/cargo@v1 - with: - command: make - args: build - env: - RUSTFLAGS: "-C link-arg=-s" - - - - name: Run Rust-Optimizer - uses: actions-rs/cargo@v1 - with: - command: make - args: rust-optimizer + working-directory: rover + run: cargo make build test: name: Test Suite runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v3 + with: + path: rover + + - name: Checkout mars-outposts + uses: actions/checkout@v3 + with: + repository: mars-protocol/outposts + token: ${{ secrets.TEMP_ACCESS_TOKEN }} + path: outposts - name: Install stable toolchain uses: actions-rs/toolchain@v1 @@ -63,10 +71,8 @@ jobs: uses: davidB/rust-cargo-make@v1 - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: make - args: test + working-directory: rover + run: cargo make test env: RUST_BACKTRACE: 1 @@ -75,7 +81,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v3 + with: + path: rover + + - name: Checkout mars-outposts + uses: actions/checkout@v3 + with: + repository: mars-protocol/outposts + token: ${{ secrets.TEMP_ACCESS_TOKEN }} + path: outposts - name: Install stable toolchain uses: actions-rs/toolchain@v1 @@ -88,14 +103,10 @@ jobs: - name: Install cargo make uses: davidB/rust-cargo-make@v1 - - name: Run cargo fmt - uses: actions-rs/cargo@v1 - with: - command: make - args: fmt + - name: Run tests + working-directory: rover + run: cargo make fmt - name: Run cargo clippy - uses: actions-rs/cargo@v1 - with: - command: make - args: clippy + working-directory: rover + run: cargo make clippy diff --git a/Cargo.lock b/Cargo.lock index 1f62d6efb..97e5b800c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,9 +18,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.59" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" +checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" [[package]] name = "base16ct" @@ -36,9 +36,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64ct" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" +checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" [[package]] name = "block-buffer" @@ -124,9 +124,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813" dependencies = [ "libc", ] @@ -143,6 +143,7 @@ dependencies = [ "cw2 0.14.0", "cw721", "cw721-base", + "mars-health", "mock-oracle", "mock-red-bank", "rover", @@ -180,9 +181,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" dependencies = [ "byteorder", "digest", @@ -282,6 +283,34 @@ dependencies = [ "serde", ] +[[package]] +name = "cw20" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" +dependencies = [ + "cosmwasm-std", + "cw-utils 0.13.4", + "schemars", + "serde", +] + +[[package]] +name = "cw20-base" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0306e606581f4fb45e82bcbb7f0333179ed53dd949c6523f01a99b4bfc1475a0" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "cw-utils 0.13.4", + "cw2 0.13.4", + "cw20", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw721" version = "0.13.4" @@ -374,9 +403,9 @@ dependencies = [ [[package]] name = "either" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" @@ -501,9 +530,31 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.127" +version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "mars-health" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "mars-outpost", + "thiserror", +] + +[[package]] +name = "mars-outpost" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw2 0.13.4", + "cw20", + "cw20-base", + "schemars", + "serde", + "thiserror", +] [[package]] name = "mock-oracle" @@ -511,6 +562,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-std", "cw-storage-plus 0.14.0", + "mars-outpost", "schemars", "serde", ] @@ -521,6 +573,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-std", "cw-storage-plus 0.14.0", + "mars-outpost", "schemars", "serde", ] @@ -618,6 +671,8 @@ version = "0.1.0" dependencies = [ "cosmwasm-std", "cw-storage-plus 0.14.0", + "mars-health", + "mars-outpost", "mock-oracle", "mock-red-bank", "schemars", @@ -676,9 +731,9 @@ checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" [[package]] name = "serde" -version = "1.0.142" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" dependencies = [ "serde_derive", ] @@ -694,9 +749,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.142" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" dependencies = [ "proc-macro2", "quote", @@ -716,9 +771,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.83" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", @@ -740,9 +795,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest", "rand_core 0.6.3", @@ -845,6 +900,6 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" -version = "1.4.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 10d621808..7d05f89a2 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -15,6 +15,7 @@ library = [] [dependencies] account-nft = { version = "0.1", path = "../account-nft", features = ["library"] } +mars-health = { version = "0.1", path = "../../../outposts/packages/mars-health" } mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } rover = { version = "0.1", path = "../../packages/rover" } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 8a9cf65b0..dc1caef48 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -1,7 +1,8 @@ use cosmwasm_std::{entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; use cw2::set_contract_version; -use rover::error::ContractResult; +use rover::error::ContractResult; +use rover::msg::query::HealthResponse; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::execute::{create_credit_account, dispatch_actions, execute_callback, update_config}; @@ -9,7 +10,7 @@ use crate::health::compute_health; use crate::instantiate::store_config; use crate::query::{ query_all_assets, query_all_debt_shares, query_all_total_debt_shares, query_allowed_coins, - query_allowed_vaults, query_config, query_position, query_total_debt_shares, + query_allowed_vaults, query_config, query_position_with_value, query_total_debt_shares, }; const CONTRACT_NAME: &str = "crates.io:rover-credit-manager"; @@ -54,8 +55,12 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { QueryMsg::AllowedCoins { start_after, limit } => { to_binary(&query_allowed_coins(deps, start_after, limit)?) } - QueryMsg::Position { token_id } => to_binary(&query_position(deps, &env, &token_id)?), - QueryMsg::Health { token_id } => to_binary(&compute_health(deps, &env, &token_id)?), + QueryMsg::Positions { token_id } => { + to_binary(&query_position_with_value(deps, &env, &token_id)?) + } + QueryMsg::Health { token_id } => { + to_binary::(&Into::into(compute_health(deps, &env, &token_id)?)) + } QueryMsg::AllCoinBalances { start_after, limit } => { to_binary(&query_all_assets(deps, start_after, limit)?) } diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 5032a8211..cc6fc8266 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -1,71 +1,32 @@ -use std::ops::{Add, Div, Mul}; +use cosmwasm_std::{Decimal, Deps, Env, Event, Response}; +use mars_health::health::Health; -use cosmwasm_std::{Decimal, Deps, Env, Event, Response, StdResult}; - -use mock_red_bank::msg::{Market, QueryMsg}; use rover::error::{ContractError, ContractResult}; -use rover::health::Health; use rover::NftTokenId; use crate::query::query_position; -use crate::state::RED_BANK; +use crate::state::{ORACLE, RED_BANK}; +use crate::utils::debt_shares_to_amount; -/// Compute the health of a token's position -/// max_tvl = maximum loan to value -/// lqdt = liquidation threshold pub fn compute_health(deps: Deps, env: &Env, token_id: NftTokenId) -> ContractResult { - let position = query_position(deps, env, token_id)?; - let red_bank = RED_BANK.load(deps.storage)?; - - // The sum of the position's coin asset values w/ loan-to-value adjusted & liquidation threshold adjusted - let (total_assets_value, max_ltv_adjusted_value, lqdt_adjusted_value) = - position.coins.iter().try_fold::<_, _, StdResult<_>>( - (Decimal::zero(), Decimal::zero(), Decimal::zero()), - |(total, max_ltv_adjusted_total, lqdt_adjusted_total), item| { - let market: Market = deps.querier.query_wasm_smart( - red_bank.address().clone(), - &QueryMsg::Market { - denom: item.denom.clone(), - }, - )?; - Ok(( - total.add(item.value), - max_ltv_adjusted_total.add(item.value.mul(market.max_loan_to_value)), - lqdt_adjusted_total.add(item.value.mul(market.liquidation_threshold)), - )) - }, - )?; - - // The sum of the position's debt share values - let total_debts_value = position - .debt_shares + let res = query_position(deps, token_id)?; + let debt_amounts = res + .debt .iter() - .fold(Decimal::zero(), |total_value, item| { - total_value.add(item.total_value) - }); + .map(|item| debt_shares_to_amount(deps, &env.contract.address, &item.denom, item.shares)) + .collect::>>()?; - // Health Factor = Sum(Value of Asset * Liquidation Threshold or Max LTV) / Sum (Value of Total Borrowed) - // If there aren't any debts a health factor can't be computed (divide by zero) - let (lqdt_health_factor, max_ltv_health_factor) = if total_debts_value.is_zero() { - (None, None) - } else { - ( - Some(lqdt_adjusted_value.div(total_debts_value)), - Some(max_ltv_adjusted_value.div(total_debts_value)), - ) - }; - - let liquidatable = lqdt_health_factor.map_or(false, |hf| hf <= Decimal::one()); - let above_max_ltv = max_ltv_health_factor.map_or(false, |hf| hf <= Decimal::one()); + let oracle = ORACLE.load(deps.storage)?; + let red_bank = RED_BANK.load(deps.storage)?; + let health = Health::compute_health_from_coins( + &deps.querier, + oracle.address(), + red_bank.address(), + &res.coins, + debt_amounts.as_slice(), + )?; - Ok(Health { - total_assets_value, - total_debts_value, - lqdt_health_factor, - liquidatable, - max_ltv_health_factor, - above_max_ltv, - }) + Ok(health) } pub fn assert_below_max_ltv( @@ -73,9 +34,9 @@ pub fn assert_below_max_ltv( env: Env, token_id: NftTokenId, ) -> ContractResult { - let position = compute_health(deps, &env, token_id)?; + let health = compute_health(deps, &env, token_id)?; - if position.above_max_ltv { + if health.is_above_max_ltv() { return Err(ContractError::AboveMaxLTV); } @@ -83,18 +44,18 @@ pub fn assert_below_max_ltv( .add_attribute("timestamp", env.block.time.seconds().to_string()) .add_attribute("height", env.block.height.to_string()) .add_attribute("token_id", token_id) - .add_attribute("assets_value", position.total_assets_value.to_string()) - .add_attribute("debts_value", position.total_debts_value.to_string()) + .add_attribute("assets_value", health.total_collateral_value.to_string()) + .add_attribute("debts_value", health.total_debt_value.to_string()) .add_attribute( "lqdt_health_factor", - val_or_not_applicable(position.lqdt_health_factor), + val_or_not_applicable(health.liquidation_health_factor), ) - .add_attribute("liquidatable", position.liquidatable.to_string()) + .add_attribute("liquidatable", health.is_liquidatable().to_string()) .add_attribute( "max_ltv_health_factor", - val_or_not_applicable(position.max_ltv_health_factor), + val_or_not_applicable(health.max_ltv_health_factor), ) - .add_attribute("above_max_ltv", position.above_max_ltv.to_string()); + .add_attribute("above_max_ltv", health.is_above_max_ltv().to_string()); Ok(Response::new() .add_attribute("action", "rover/credit_manager/callback/assert_health") diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 3f23c280f..b88f739e3 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,10 +1,10 @@ -use cosmwasm_std::{Addr, Decimal, Deps, Env, Order, StdResult, Uint128}; +use cosmwasm_std::{Addr, Coin, Deps, Env, Order, StdResult}; use cw_storage_plus::Bound; use rover::error::ContractResult; use rover::msg::query::{ - CoinBalanceResponseItem, CoinShares, CoinValue, ConfigResponse, DebtSharesValue, - PositionResponse, SharesResponseItem, + CoinBalanceResponseItem, ConfigResponse, DebtShares, DebtSharesValue, Positions, + PositionsWithValueResponse, SharesResponseItem, }; use rover::{Denom, NftTokenId}; @@ -12,6 +12,7 @@ use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, COIN_BALANCES, DEBT_SHARES, ORACLE, OWNER, RED_BANK, TOTAL_DEBT_SHARES, }; +use crate::utils::{coin_value, debt_shares_to_amount}; const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; @@ -27,18 +28,50 @@ pub fn query_config(deps: Deps) -> StdResult { }) } -pub fn query_position( +pub fn query_position(deps: Deps, token_id: NftTokenId) -> ContractResult { + Ok(Positions { + token_id: token_id.to_string(), + coins: query_coin_balances(deps, token_id)?, + debt: query_debt_shares(deps, token_id)?, + }) +} + +pub fn query_position_with_value( deps: Deps, env: &Env, - token_id: NftTokenId, -) -> ContractResult { - let coin_asset_values = get_coin_balances_values(deps, token_id)?; - let debt_shares_values = get_debt_shares_values(deps, env, token_id)?; + token_id: &str, +) -> ContractResult { + let Positions { + token_id, + coins, + debt, + } = query_position(deps, token_id)?; + + let coin_balances_value = coins + .iter() + .map(|coin| coin_value(&deps, coin)) + .collect::>>()?; - Ok(PositionResponse { - token_id: token_id.to_string(), - coins: coin_asset_values, - debt_shares: debt_shares_values, + let debt_with_values = debt + .iter() + .map(|item| { + let coin = + debt_shares_to_amount(deps, &env.contract.address, &item.denom, item.shares)?; + let cv = coin_value(&deps, &coin)?; + Ok(DebtSharesValue { + denom: cv.denom, + shares: item.shares, + amount: cv.amount, + price: cv.price, + value: cv.value, + }) + }) + .collect::>>()?; + + Ok(PositionsWithValueResponse { + token_id, + coins: coin_balances_value, + debt: debt_with_values, }) } @@ -65,63 +98,24 @@ pub fn query_all_assets( .collect()) } -fn get_debt_shares_values( - deps: Deps, - env: &Env, - token_id: NftTokenId, -) -> ContractResult> { - let oracle = ORACLE.load(deps.storage)?; - +fn query_debt_shares(deps: Deps, token_id: NftTokenId) -> ContractResult> { DEBT_SHARES .prefix(token_id) .range(deps.storage, None, None, Order::Ascending) .map(|res| { let (denom, shares) = res?; - // total shares of debt issued for denom - let total_debt_shares = TOTAL_DEBT_SHARES - .load(deps.storage, &denom) - .unwrap_or(Uint128::zero()); - - // total rover debt amount in Redbank for asset - let red_bank = RED_BANK.load(deps.storage)?; - let total_debt_amount = - red_bank.query_debt(&deps.querier, &env.contract.address, &denom)?; - - // amount of debt for token's position - // NOTE: Given the nature of integers, the debt is rounded down. This means that the - // remaining share owners will take a small hit of the remainder. - let owed = total_debt_amount.checked_multiply_ratio(shares, total_debt_shares)?; - let owed_dec = Decimal::from_atomics(owed, 0)?; - - // debt value of token's position - let coin_price = oracle.query_price(&deps.querier, &denom)?; - let position_debt_value = coin_price.checked_mul(owed_dec)?; - - Ok(DebtSharesValue { - total_value: position_debt_value, - denom, - shares, - }) + Ok(DebtShares { denom, shares }) }) .collect() } -fn get_coin_balances_values(deps: Deps, token_id: &str) -> ContractResult> { - let oracle = ORACLE.load(deps.storage)?; +fn query_coin_balances(deps: Deps, token_id: &str) -> ContractResult> { COIN_BALANCES .prefix(token_id) .range(deps.storage, None, None, Order::Ascending) .map(|item| { let (denom, amount) = item?; - let price = oracle.query_price(&deps.querier, &denom)?; - let decimal_amount = Decimal::from_atomics(amount, 0)?; - let value = price.checked_mul(decimal_amount)?; - Ok(CoinValue { - denom, - amount, - price, - value, - }) + Ok(Coin { denom, amount }) }) .collect() } @@ -198,9 +192,9 @@ pub fn query_allowed_coins( .collect::>>() } -pub fn query_total_debt_shares(deps: Deps, denom: Denom) -> StdResult { +pub fn query_total_debt_shares(deps: Deps, denom: Denom) -> StdResult { let shares = TOTAL_DEBT_SHARES.load(deps.storage, denom)?; - Ok(CoinShares { + Ok(DebtShares { denom: denom.to_string(), shares, }) @@ -210,7 +204,7 @@ pub fn query_all_total_debt_shares( deps: Deps, start_after: Option, limit: Option, -) -> StdResult> { +) -> StdResult> { let start = start_after .as_ref() .map(|denom| Bound::exclusive(denom.as_str())); @@ -222,7 +216,7 @@ pub fn query_all_total_debt_shares( .take(limit) .collect::>>()? .iter() - .map(|(denom, shares)| CoinShares { + .map(|(denom, shares)| DebtShares { denom: denom.to_string(), shares: *shares, }) diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 69cce9e83..697602303 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -1,6 +1,9 @@ -use crate::state::{ALLOWED_COINS, COIN_BALANCES}; -use cosmwasm_std::{Coin, Storage, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, Deps, Storage, Uint128}; + use rover::error::{ContractError, ContractResult}; +use rover::msg::query::CoinValue; + +use crate::state::{ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES}; pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, coin: &Coin) -> ContractResult<()> { let is_whitelisted = ALLOWED_COINS.has(storage, &coin.denom); @@ -40,3 +43,42 @@ pub fn decrement_coin_balance( } Ok(new_value) } + +pub fn debt_shares_to_amount( + deps: Deps, + rover_addr: &Addr, + denom: &str, + shares: Uint128, +) -> ContractResult { + // total shares of debt issued for denom + let total_debt_shares = TOTAL_DEBT_SHARES + .load(deps.storage, denom) + .unwrap_or(Uint128::zero()); + + // total rover debt amount in Redbank for asset + let red_bank = RED_BANK.load(deps.storage)?; + let total_debt_amount = red_bank.query_debt(&deps.querier, rover_addr, denom)?; + + // amount of debt for token's position + // NOTE: Given the nature of integers, the debt is rounded down. This means that the + // remaining share owners will take a small hit of the remainder. + let amount = total_debt_amount.checked_multiply_ratio(shares, total_debt_shares)?; + + Ok(Coin { + denom: denom.to_string(), + amount, + }) +} + +pub fn coin_value(deps: &Deps, coin: &Coin) -> ContractResult { + let oracle = ORACLE.load(deps.storage)?; + let res = oracle.query_price(&deps.querier, &coin.denom)?; + let decimal_amount = Decimal::from_atomics(coin.amount, 0)?; + let value = res.price.checked_mul(decimal_amount)?; + Ok(CoinValue { + denom: coin.denom.clone(), + amount: coin.amount, + price: res.price, + value, + }) +} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 27d2250fa..44c67cee5 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -13,13 +13,12 @@ use mock_red_bank::msg::QueryMsg::UserAssetDebt; use mock_red_bank::msg::{ CoinMarketInfo, InstantiateMsg as RedBankInstantiateMsg, UserAssetDebtResponse, }; - use rover::adapters::{OracleBase, RedBankBase}; -use rover::health::Health; use rover::msg::execute::{Action, CallbackMsg}; use rover::msg::instantiate::ConfigUpdates; use rover::msg::query::{ - CoinBalanceResponseItem, CoinShares, ConfigResponse, PositionResponse, SharesResponseItem, + CoinBalanceResponseItem, ConfigResponse, DebtShares, HealthResponse, + PositionsWithValueResponse, SharesResponseItem, }; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -157,19 +156,19 @@ impl MockEnv { // Queries //-------------------------------------------------------------------------------------------------- - pub fn query_position(&self, token_id: &str) -> PositionResponse { + pub fn query_position(&self, token_id: &str) -> PositionsWithValueResponse { self.app .wrap() .query_wasm_smart( self.rover.clone(), - &QueryMsg::Position { + &QueryMsg::Positions { token_id: token_id.to_string(), }, ) .unwrap() } - pub fn query_health(&self, token_id: &str) -> Health { + pub fn query_health(&self, token_id: &str) -> HealthResponse { self.app .wrap() .query_wasm_smart( @@ -252,7 +251,7 @@ impl MockEnv { &self, start_after: Option, limit: Option, - ) -> Vec { + ) -> Vec { self.app .wrap() .query_wasm_smart( @@ -262,7 +261,7 @@ impl MockEnv { .unwrap() } - pub fn query_total_debt_shares(&self, denom: &str) -> CoinShares { + pub fn query_total_debt_shares(&self, denom: &str) -> DebtShares { self.app .wrap() .query_wasm_smart( diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index 1e3560314..b98e85bef 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -86,7 +86,7 @@ fn test_borrowing_zero_does_nothing() { let position = mock.query_position(&token_id); assert_eq!(position.coins.len(), 0); - assert_eq!(position.debt_shares.len(), 0); + assert_eq!(position.debt.len(), 0); } #[test] @@ -105,7 +105,7 @@ fn test_cannot_borrow_above_max_ltv() { let position = mock.query_position(&token_id); assert_eq!(position.coins.len(), 0); - assert_eq!(position.debt_shares.len(), 0); + assert_eq!(position.debt.len(), 0); let res = mock.update_credit_account( &token_id, @@ -136,7 +136,7 @@ fn test_success_when_new_debt_asset() { let position = mock.query_position(&token_id); assert_eq!(position.coins.len(), 0); - assert_eq!(position.debt_shares.len(), 0); + assert_eq!(position.debt.len(), 0); mock.update_credit_account( &token_id, &user, @@ -168,8 +168,8 @@ fn test_success_when_new_debt_asset() { coin_info.price * Decimal::from_atomics(342u128, 0).unwrap() ); - let debt_shares_res = position.debt_shares.first().unwrap(); - assert_eq!(position.debt_shares.len(), 1); + let debt_shares_res = position.debt.first().unwrap(); + assert_eq!(position.debt.len(), 1); assert_eq!( debt_shares_res.shares, Uint128::new(42).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) @@ -177,7 +177,7 @@ fn test_success_when_new_debt_asset() { assert_eq!(debt_shares_res.denom, coin_info.denom); let debt_amount = Uint128::new(42u128) + Uint128::new(1); // simulated yield assert_eq!( - debt_shares_res.total_value, + debt_shares_res.value, coin_info.price * Decimal::from_atomics(debt_amount, 0).unwrap() ); @@ -244,7 +244,7 @@ fn test_debt_shares_with_debt_amount() { let token_a_shares = Uint128::new(50).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); let position = mock.query_position(&token_id_a); - let debt_position_a = position.debt_shares.first().unwrap(); + let debt_position_a = position.debt.first().unwrap(); assert_eq!(debt_position_a.shares, token_a_shares.clone()); assert_eq!(debt_position_a.denom, coin_info.denom); @@ -252,7 +252,7 @@ fn test_debt_shares_with_debt_amount() { .mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) .multiply_ratio(Uint128::new(50), interim_red_bank_debt.amount); let position = mock.query_position(&token_id_b); - let debt_position_b = position.debt_shares.first().unwrap(); + let debt_position_b = position.debt.first().unwrap(); assert_eq!(debt_position_b.shares, token_b_shares.clone()); assert_eq!(debt_position_b.denom, coin_info.denom); @@ -269,7 +269,7 @@ fn test_debt_shares_with_debt_amount() { .amount .multiply_ratio(debt_position_a.shares, total.shares); assert_eq!( - debt_position_a.total_value, + debt_position_a.value, coin_info.price * Decimal::from_atomics(a_amount_owed, 0).unwrap() ); @@ -277,7 +277,7 @@ fn test_debt_shares_with_debt_amount() { .amount .multiply_ratio(debt_position_b.shares, total.shares); assert_eq!( - debt_position_b.total_value, + debt_position_b.value, coin_info.price * Decimal::from_atomics(b_amount_owed, 0).unwrap() ); @@ -291,6 +291,6 @@ fn test_debt_shares_with_debt_amount() { let total_owed = Decimal::from_atomics(a_amount_owed + b_amount_owed, 0).unwrap(); assert_eq!( total_owed * coin_info.price, - debt_position_a.total_value + debt_position_b.total_value + debt_position_a.value + debt_position_b.value ) } diff --git a/contracts/credit-manager/tests/test_deposit.rs b/contracts/credit-manager/tests/test_deposit.rs index b52e6fa86..cd71c48fc 100644 --- a/contracts/credit-manager/tests/test_deposit.rs +++ b/contracts/credit-manager/tests/test_deposit.rs @@ -5,7 +5,7 @@ use rover::error::ContractError::{ ExtraFundsReceived, FundsMismatch, NotTokenOwner, NotWhitelisted, }; use rover::msg::execute::Action; -use rover::msg::query::PositionResponse; +use rover::msg::query::PositionsWithValueResponse; use crate::helpers::{ assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv, @@ -278,7 +278,12 @@ fn test_multiple_deposit_actions() { assert_eq!(coin.amount, uatom_amount); } -fn assert_present(res: &PositionResponse, coin: &CoinInfo, amount: Uint128, total_val: Decimal) { +fn assert_present( + res: &PositionsWithValueResponse, + coin: &CoinInfo, + amount: Uint128, + total_val: Decimal, +) { res.coins .iter() .find(|item| item.denom == coin.denom && item.amount == amount && item.value == total_val) diff --git a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs index eaa4c6baf..332ca647d 100644 --- a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{Addr, Coin, Uint128}; use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use rover::msg::execute::Action; -use rover::msg::query::CoinShares; +use rover::msg::query::DebtShares; use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; @@ -144,13 +144,13 @@ fn test_pagination_on_all_total_debt_shares_query_works() { let all_total_debt_shares_res_a = mock.query_all_total_debt_shares(None, None); - let CoinShares { denom, .. } = all_total_debt_shares_res_a.last().unwrap().clone(); + let DebtShares { denom, .. } = all_total_debt_shares_res_a.last().unwrap().clone(); let all_total_debt_shares_res_b = mock.query_all_total_debt_shares(Some(denom), None); - let CoinShares { denom, .. } = all_total_debt_shares_res_b.last().unwrap().clone(); + let DebtShares { denom, .. } = all_total_debt_shares_res_b.last().unwrap().clone(); let all_total_debt_shares_res_c = mock.query_all_total_debt_shares(Some(denom), None); - let CoinShares { denom, .. } = all_total_debt_shares_res_c.last().unwrap().clone(); + let DebtShares { denom, .. } = all_total_debt_shares_res_c.last().unwrap().clone(); let all_total_debt_shares_res_d = mock.query_all_total_debt_shares(Some(denom), None); // Assert default is observed @@ -160,7 +160,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { assert_eq!(all_total_debt_shares_res_d.len(), 2); - let combined_res: Vec = all_total_debt_shares_res_a + let combined_res: Vec = all_total_debt_shares_res_a .iter() .cloned() .chain(all_total_debt_shares_res_b.iter().cloned()) @@ -170,29 +170,29 @@ fn test_pagination_on_all_total_debt_shares_query_works() { let user_a_response_items = user_a_coins .iter() - .map(|coin| CoinShares { + .map(|coin| DebtShares { denom: coin.denom.clone(), shares: DEFAULT_DEBT_SHARES_PER_COIN_BORROWED, }) - .collect::>(); + .collect::>(); let user_b_response_items = user_b_coins .iter() - .map(|coin| CoinShares { + .map(|coin| DebtShares { denom: coin.denom.clone(), shares: DEFAULT_DEBT_SHARES_PER_COIN_BORROWED, }) - .collect::>(); + .collect::>(); let user_c_response_items = user_c_coins .iter() - .map(|coin| CoinShares { + .map(|coin| DebtShares { denom: coin.denom.clone(), shares: DEFAULT_DEBT_SHARES_PER_COIN_BORROWED, }) - .collect::>(); + .collect::>(); - let combined_starting_vals: Vec = user_a_response_items + let combined_starting_vals: Vec = user_a_response_items .iter() .cloned() .chain(user_b_response_items) diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index bad38840b..527429158 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -43,13 +43,13 @@ fn test_only_assets_with_no_debts() { let position = mock.query_position(&token_id); assert_eq!(position.coins.len(), 1); - assert_eq!(position.debt_shares.len(), 0); + assert_eq!(position.debt.len(), 0); let health = mock.query_health(&token_id); let assets_value = coin_info.price * Decimal::from_atomics(deposit_amount, 0).unwrap(); - assert_eq!(health.total_assets_value, assets_value); - assert_eq!(health.total_debts_value, Decimal::zero()); - assert_eq!(health.lqdt_health_factor, None); + assert_eq!(health.total_collateral_value, assets_value); + assert_eq!(health.total_debt_value, Decimal::zero()); + assert_eq!(health.liquidation_health_factor, None); assert_eq!(health.max_ltv_health_factor, None); assert!(!health.liquidatable); assert!(!health.above_max_ltv); @@ -101,18 +101,18 @@ fn test_terra_ragnarok() { let position = mock.query_position(&token_id); assert_eq!(position.coins.len(), 1); - assert_eq!(position.debt_shares.len(), 1); + assert_eq!(position.debt.len(), 1); let health = mock.query_health(&token_id); let assets_value = coin_info.price * Decimal::from_atomics(deposit_amount + borrow_amount, 0).unwrap(); - assert_eq!(health.total_assets_value, assets_value); + assert_eq!(health.total_collateral_value, assets_value); // Note: Simulated yield from mock_red_bank makes debt position more expensive let debts_value = coin_info.price * Decimal::from_atomics(borrow_amount.add(Uint128::new(1)), 0).unwrap(); - assert_eq!(health.total_debts_value, debts_value); + assert_eq!(health.total_debt_value, debts_value); assert_eq!( - health.lqdt_health_factor, + health.liquidation_health_factor, Some(assets_value * coin_info.liquidation_threshold / debts_value) ); assert_eq!( @@ -129,12 +129,12 @@ fn test_terra_ragnarok() { let position = mock.query_position(&token_id); assert_eq!(position.coins.len(), 1); - assert_eq!(position.debt_shares.len(), 1); + assert_eq!(position.debt.len(), 1); let health = mock.query_health(&token_id); - assert_eq!(health.total_assets_value, Decimal::zero()); - assert_eq!(health.total_debts_value, Decimal::zero()); - assert_eq!(health.lqdt_health_factor, None); + assert_eq!(health.total_collateral_value, Decimal::zero()); + assert_eq!(health.total_debt_value, Decimal::zero()); + assert_eq!(health.liquidation_health_factor, None); assert_eq!(health.max_ltv_health_factor, None); assert!(!health.liquidatable); assert!(!health.above_max_ltv); @@ -172,12 +172,12 @@ fn test_debts_no_assets() { let position = mock.query_position(&token_id); assert_eq!(position.token_id, token_id); assert_eq!(position.coins.len(), 0); - assert_eq!(position.debt_shares.len(), 0); + assert_eq!(position.debt.len(), 0); let health = mock.query_health(&token_id); - assert_eq!(health.total_assets_value, Decimal::zero()); - assert_eq!(health.total_debts_value, Decimal::zero()); - assert_eq!(health.lqdt_health_factor, None); + assert_eq!(health.total_collateral_value, Decimal::zero()); + assert_eq!(health.total_debt_value, Decimal::zero()); + assert_eq!(health.liquidation_health_factor, None); assert_eq!(health.max_ltv_health_factor, None); assert!(!health.liquidatable); assert!(!health.above_max_ltv); @@ -224,15 +224,15 @@ fn test_cannot_borrow_more_than_healthy() { let position = mock.query_position(&token_id); assert_eq!(position.token_id, token_id); assert_eq!(position.coins.len(), 1); - assert_eq!(position.debt_shares.len(), 1); + assert_eq!(position.debt.len(), 1); let health = mock.query_health(&token_id); let assets_value = Decimal::from_atomics(82789u128, 2).unwrap(); - assert_eq!(health.total_assets_value, assets_value); + assert_eq!(health.total_collateral_value, assets_value); let debts_value = Decimal::from_atomics(1206354u128, 4).unwrap(); - assert_eq!(health.total_debts_value, debts_value); + assert_eq!(health.total_debt_value, debts_value); assert_eq!( - health.lqdt_health_factor, + health.liquidation_health_factor, Some(assets_value * coin_info.liquidation_threshold / debts_value) ); assert_eq!( @@ -262,11 +262,11 @@ fn test_cannot_borrow_more_than_healthy() { // All valid on step 2 as well (meaning step 3 did not go through) let health = mock.query_health(&token_id); let assets_value = Decimal::from_atomics(106443u128, 2).unwrap(); - assert_eq!(health.total_assets_value, assets_value); + assert_eq!(health.total_collateral_value, assets_value); let debts_value = Decimal::from_atomics(3595408u128, 4).unwrap(); - assert_eq!(health.total_debts_value, debts_value); + assert_eq!(health.total_debt_value, debts_value); assert_eq!( - health.lqdt_health_factor, + health.liquidation_health_factor, Some(assets_value * coin_info.liquidation_threshold / debts_value) ); assert_eq!( @@ -405,17 +405,17 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { let position = mock.query_position(&token_id); assert_eq!(position.token_id, token_id); assert_eq!(position.coins.len(), 2); - assert_eq!(position.debt_shares.len(), 1); + assert_eq!(position.debt.len(), 1); let health = mock.query_health(&token_id); let deposit_amount_dec = Decimal::from_atomics(deposit_amount, 0).unwrap(); let borrowed_amount_dec = Decimal::from_atomics(borrowed_amount, 0).unwrap(); assert_eq!( - health.total_assets_value, + health.total_collateral_value, uosmo_info.price * deposit_amount_dec + uatom_info.price * borrowed_amount_dec ); assert_eq!( - health.total_debts_value, + health.total_debt_value, uatom_info.price * (borrowed_amount_dec + Decimal::one()) // simulated interest ); @@ -423,7 +423,7 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { uosmo_info.price * deposit_amount_dec * uosmo_info.liquidation_threshold + uatom_info.price * borrowed_amount_dec * uatom_info.liquidation_threshold; assert_eq!( - health.lqdt_health_factor, + health.liquidation_health_factor, Some( lqdt_adjusted_assets_value .div(uatom_info.price.mul(borrowed_amount_dec + Decimal::one())) @@ -521,7 +521,7 @@ fn test_debt_value() { let position_a = mock.query_position(&token_id_a); assert_eq!(position_a.token_id, token_id_a); assert_eq!(position_a.coins.len(), 2); - assert_eq!(position_a.debt_shares.len(), 2); + assert_eq!(position_a.debt.len(), 2); let health = mock.query_health(&token_id_a); assert!(!health.above_max_ltv); @@ -533,7 +533,7 @@ fn test_debt_value() { user_a_borrowed_amount_atom.mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); assert_eq!( user_a_debt_shares_atom, - find_by_denom(&uatom_info.denom, &position_a.debt_shares).shares + find_by_denom(&uatom_info.denom, &position_a.debt).shares ); let position_b = mock.query_position(&token_id_b); @@ -541,7 +541,7 @@ fn test_debt_value() { .multiply_ratio(user_b_borrowed_amount_atom, interim_red_bank_debt.amount); assert_eq!( user_b_debt_shares_atom, - find_by_denom(&uatom_info.denom, &position_b.debt_shares).shares + find_by_denom(&uatom_info.denom, &position_b.debt).shares ); let red_bank_atom_res = mock.query_total_debt_shares(&uatom_info.denom); @@ -562,7 +562,7 @@ fn test_debt_value() { let osmo_debt_value = uosmo_info.price * osmo_borrowed_amount_dec; let total_debt_value = user_a_owed_atom_value.add(osmo_debt_value); - assert_eq!(health.total_debts_value, total_debt_value); + assert_eq!(health.total_debt_value, total_debt_value); let user_a_deposit_amount_osmo_dec = Decimal::from_atomics(user_a_deposit_amount_osmo, 0).unwrap(); @@ -577,7 +577,7 @@ fn test_debt_value() { + (uatom_info.price * user_a_borrowed_amount_atom_dec * uatom_info.liquidation_threshold) + (uosmo_info.price * user_a_borrowed_amount_osmo_dec * uosmo_info.liquidation_threshold); assert_eq!( - health.lqdt_health_factor, + health.liquidation_health_factor, Some(lqdt_adjusted_assets_value.div(total_debt_value)) ); diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index a5a0b46c7..ab9025145 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -200,7 +200,7 @@ fn test_successful_repay() { let position = mock.query_position(&token_id); assert_eq!(position.coins.len(), 0); - assert_eq!(position.debt_shares.len(), 0); + assert_eq!(position.debt.len(), 0); mock.update_credit_account( &token_id, @@ -229,8 +229,8 @@ fn test_successful_repay() { let expected_net_asset_amount = Uint128::new(330); // Deposit + Borrow - Repay assert_eq!(asset_res.amount, expected_net_asset_amount); - let debt_shares_res = position.debt_shares.first().unwrap(); - assert_eq!(position.debt_shares.len(), 1); + let debt_shares_res = position.debt.first().unwrap(); + assert_eq!(position.debt.len(), 1); assert_eq!(debt_shares_res.denom, coin_info.denom); let former_total_debt_shares = Uint128::new(50).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); @@ -268,7 +268,7 @@ fn test_successful_repay() { assert_eq!(asset_res.amount, expected_net_asset_amount); // Full debt repaid and purged from storage - assert_eq!(position.debt_shares.len(), 0); + assert_eq!(position.debt.len(), 0); let res = mock.query_total_debt_shares(&coin_info.denom); assert_eq!(res.shares, Uint128::zero()); @@ -317,7 +317,7 @@ fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { let expected_net_asset_amount = Uint128::new(299); // Deposit + Borrow - Repay - interest assert_eq!(asset_res.amount, expected_net_asset_amount); - assert_eq!(position.debt_shares.len(), 0); + assert_eq!(position.debt.len(), 0); let res = mock.query_total_debt_shares(&coin_info.denom); assert_eq!(res.shares, Uint128::zero()); diff --git a/contracts/mock-oracle/Cargo.toml b/contracts/mock-oracle/Cargo.toml index f538f694c..60c40a521 100644 --- a/contracts/mock-oracle/Cargo.toml +++ b/contracts/mock-oracle/Cargo.toml @@ -14,6 +14,8 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] +mars-outpost = { version = "0.1", path = "../../../outposts/packages/mars-outpost" } + cosmwasm-std = "1.0" cw-storage-plus = "0.14" schemars = "0.8" diff --git a/contracts/mock-oracle/src/contract.rs b/contracts/mock-oracle/src/contract.rs index 0b57915eb..f5091c3b1 100644 --- a/contracts/mock-oracle/src/contract.rs +++ b/contracts/mock-oracle/src/contract.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{ - to_binary, to_vec, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Response, StdError, - StdResult, + to_binary, to_vec, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, }; +use mars_outpost::oracle::PriceResponse; use crate::msg::{CoinPrice, ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::COIN_PRICE; @@ -39,7 +39,7 @@ fn change_price(deps: DepsMut, coin: CoinPrice) -> StdResult { #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::AssetPrice { denom } => to_binary(&query_asset_price(deps, denom)?), + QueryMsg::Price { denom } => to_binary(&query_price(deps, denom)?), _ => Err(StdError::generic_err(format!( "[mock] unimplemented query: {}", String::from_utf8(to_vec(&msg)?)? @@ -47,6 +47,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } } -fn query_asset_price(deps: Deps, denom: String) -> StdResult { - COIN_PRICE.load(deps.storage, denom) +fn query_price(deps: Deps, denom: String) -> StdResult { + let price = COIN_PRICE.load(deps.storage, denom.clone())?; + Ok(PriceResponse { denom, price }) } diff --git a/contracts/mock-oracle/src/msg.rs b/contracts/mock-oracle/src/msg.rs index c9d8638c3..91def6eae 100644 --- a/contracts/mock-oracle/src/msg.rs +++ b/contracts/mock-oracle/src/msg.rs @@ -20,12 +20,9 @@ pub enum ExecuteMsg { ChangePrice(CoinPrice), } -// mocked from: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/oracle.rs#L155 -// cw-asset needs to be removed before we can import #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { Config {}, - AssetPrice { denom: String }, - AssetPriceSource { denom: String }, + Price { denom: String }, } diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index f3f4dca85..26b23d075 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -14,6 +14,8 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] +mars-outpost = { version = "0.1", path = "../../../outposts/packages/mars-outpost" } + cosmwasm-std = "1.0" cw-storage-plus = "0.14" schemars = "0.8" diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs index 79c183543..4d325f8d6 100644 --- a/contracts/mock-red-bank/src/msg.rs +++ b/contracts/mock-red-bank/src/msg.rs @@ -39,10 +39,3 @@ pub struct UserAssetDebtResponse { pub denom: String, pub amount: Uint128, } - -// Schema reference: https://github.com/mars-protocol/mars-core/blob/master/packages/mars-core/src/red_bank/mod.rs#L47 -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct Market { - pub max_loan_to_value: Decimal, - pub liquidation_threshold: Decimal, -} diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index a83915261..9dc844515 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -1,7 +1,8 @@ use cosmwasm_std::{Deps, StdResult}; +use mars_outpost::red_bank::Market; use crate::helpers::load_debt_amount; -use crate::msg::{Market, UserAssetDebtResponse}; +use crate::msg::UserAssetDebtResponse; use crate::state::COIN_MARKET_INFO; pub fn query_debt( @@ -19,5 +20,6 @@ pub fn query_market(deps: Deps, denom: String) -> StdResult { Ok(Market { max_loan_to_value: market_info.max_ltv, liquidation_threshold: market_info.liquidation_threshold, + ..Default::default() }) } diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 2053aac42..ff83002ed 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -10,6 +10,8 @@ repository = "https://github.com/mars-protocol/rover" doctest = false [dependencies] +mars-health = { version = "0.1", path = "../../../outposts/packages/mars-health" } +mars-outpost = { version = "0.1", path = "../../../outposts/packages/mars-outpost" } mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs index 2be25a245..0829a4d65 100644 --- a/packages/rover/src/adapters/oracle.rs +++ b/packages/rover/src/adapters/oracle.rs @@ -1,4 +1,5 @@ -use cosmwasm_std::{Addr, Api, Decimal, QuerierWrapper, StdResult}; +use cosmwasm_std::{Addr, Api, QuerierWrapper, StdResult}; +use mars_outpost::oracle::PriceResponse; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -33,10 +34,10 @@ impl OracleUnchecked { } impl Oracle { - pub fn query_price(&self, querier: &QuerierWrapper, denom: &str) -> StdResult { + pub fn query_price(&self, querier: &QuerierWrapper, denom: &str) -> StdResult { querier.query_wasm_smart( self.address().to_string(), - &QueryMsg::AssetPrice { + &QueryMsg::Price { denom: denom.to_string(), }, ) diff --git a/packages/rover/src/health.rs b/packages/rover/src/health.rs deleted file mode 100644 index 7f159d5ba..000000000 --- a/packages/rover/src/health.rs +++ /dev/null @@ -1,22 +0,0 @@ -use cosmwasm_std::Decimal; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct Health { - /// Total value of assets - pub total_assets_value: Decimal, - /// Total value of debts - pub total_debts_value: Decimal, - /// The sum of the value of all assets (multiplied by their liquidation threshold) over the - /// sum of the value of all debts. Main health factor used throughout app. - pub lqdt_health_factor: Option, - /// Liquidation Health factor <= 1 - pub liquidatable: bool, - /// The sum of the value of all assets (multiplied by their max LTV) over the sum of the value - /// of all debts. Used to enforce a leverage limit that does not liquidate with a little volatility. - pub max_ltv_health_factor: Option, - /// Exceeding the maximum LTV that we allow users to take a new position. - /// Uses max_ltv (instead of liquidation threshold) to calculate health factor. - pub above_max_ltv: bool, -} diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index 429d319cb..bf1c3b729 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -3,7 +3,6 @@ use cosmwasm_std::Uint128; pub mod adapters; pub mod coins; pub mod error; -pub mod health; pub mod msg; pub type Denom<'a> = &'a str; diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 178174c4a..c3e25690f 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -1,4 +1,5 @@ -use cosmwasm_std::{Decimal, Uint128}; +use cosmwasm_std::{Coin, Decimal, Uint128}; +use mars_health::health::Health; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -17,9 +18,9 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, - /// The entire position represented by token. Response type: `PositionResponse` - Position { token_id: String }, - /// The health of the entire position represented by token. Response type: `Health` + /// All positions represented by token with value. Response type: `PositionsWithValueResponse` + Positions { token_id: String }, + /// The health of the account represented by token. Response type: `HealthResponse` Health { token_id: String }, /// Enumerate coin balances for all token positions. Response type: `Vec` /// start_after accepts (token_id, denom) @@ -61,37 +62,51 @@ pub struct SharesResponseItem { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub struct CoinShares { +pub struct DebtShares { pub denom: String, pub shares: Uint128, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub struct CoinValue { +pub struct DebtSharesValue { pub denom: String, + /// number of shares in debt pool + pub shares: Uint128, + /// amount of coins pub amount: Uint128, + /// price per coin pub price: Decimal, + /// price * amount pub value: Decimal, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub struct DebtSharesValue { +pub struct CoinValue { pub denom: String, - pub shares: Uint128, - pub total_value: Decimal, + pub amount: Uint128, + pub price: Decimal, + pub value: Decimal, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct Positions { + pub token_id: String, + pub coins: Vec, + pub debt: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub struct PositionResponse { +pub struct PositionsWithValueResponse { /// Unique NFT token id that represents the cross-margin account. The owner of this NFT, owns the account. pub token_id: String, - /// All coin balances with its value + /// All coin balances value pub coins: Vec, - /// All debt positions with its value - pub debt_shares: Vec, + /// All debt positions with value + pub debt: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] @@ -102,3 +117,31 @@ pub struct ConfigResponse { pub red_bank: String, pub oracle: String, } + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct HealthResponse { + pub total_debt_value: Decimal, + pub total_collateral_value: Decimal, + pub max_ltv_adjusted_collateral: Decimal, + pub liquidation_threshold_adjusted_collateral: Decimal, + pub max_ltv_health_factor: Option, + pub liquidation_health_factor: Option, + pub liquidatable: bool, + pub above_max_ltv: bool, +} + +impl From for HealthResponse { + fn from(h: Health) -> Self { + Self { + total_debt_value: h.total_debt_value, + total_collateral_value: h.total_collateral_value, + max_ltv_adjusted_collateral: h.max_ltv_adjusted_collateral, + liquidation_threshold_adjusted_collateral: h.liquidation_threshold_adjusted_collateral, + max_ltv_health_factor: h.max_ltv_health_factor, + liquidation_health_factor: h.liquidation_health_factor, + liquidatable: h.is_liquidatable(), + above_max_ltv: h.is_above_max_ltv(), + } + } +} From 9b952cec173fde936c5a3af6579af13587211d19 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 5 Sep 2022 12:15:22 +0200 Subject: [PATCH 052/218] Adding support for vault deposits (#15) * Adding support for vault deposits * review updates * Review updates pt 2 --- Cargo.lock | 41 ++- contracts/credit-manager/Cargo.toml | 4 +- contracts/credit-manager/src/contract.rs | 17 +- contracts/credit-manager/src/execute.rs | 43 ++- contracts/credit-manager/src/instantiate.rs | 4 +- contracts/credit-manager/src/lib.rs | 1 + contracts/credit-manager/src/query.rs | 129 ++++++- contracts/credit-manager/src/state.rs | 6 +- contracts/credit-manager/src/utils.rs | 22 +- contracts/credit-manager/src/vault/deposit.rs | 114 ++++++ contracts/credit-manager/src/vault/mod.rs | 3 + .../credit-manager/tests/helpers/builders.rs | 15 +- .../credit-manager/tests/helpers/contracts.rs | 8 + .../credit-manager/tests/helpers/mock_env.rs | 217 ++++++++--- .../credit-manager/tests/helpers/types.rs | 7 + .../tests/test_enumerate_allowed_vaults.rs | 56 +-- .../test_enumerate_vault_coin_balances.rs | 148 ++++++++ .../tests/test_enumerate_vault_positions.rs | 149 ++++++++ .../credit-manager/tests/test_instantiate.rs | 30 +- .../tests/test_update_config.rs | 10 +- .../tests/test_vault_deposit.rs | 342 ++++++++++++++++++ contracts/mock-oracle/Cargo.toml | 2 +- contracts/mock-red-bank/Cargo.toml | 2 +- contracts/mock-vault/Cargo.toml | 23 ++ contracts/mock-vault/src/contract.rs | 56 +++ contracts/mock-vault/src/deposit.rs | 57 +++ contracts/mock-vault/src/error.rs | 15 + contracts/mock-vault/src/lib.rs | 6 + contracts/mock-vault/src/msg.rs | 16 + contracts/mock-vault/src/query.rs | 43 +++ contracts/mock-vault/src/state.rs | 14 + packages/rover/Cargo.toml | 4 +- packages/rover/src/adapters/mod.rs | 2 + packages/rover/src/adapters/oracle.rs | 23 +- packages/rover/src/adapters/vault.rs | 112 ++++++ packages/rover/src/coins.rs | 10 + packages/rover/src/error.rs | 6 + packages/rover/src/extensions/mod.rs | 3 + packages/rover/src/lib.rs | 4 +- packages/rover/src/msg/execute.rs | 26 +- packages/rover/src/msg/instantiate.rs | 6 +- packages/rover/src/msg/mod.rs | 1 + packages/rover/src/msg/query.rs | 46 ++- packages/rover/src/msg/vault.rs | 34 ++ 44 files changed, 1722 insertions(+), 155 deletions(-) create mode 100644 contracts/credit-manager/src/vault/deposit.rs create mode 100644 contracts/credit-manager/src/vault/mod.rs create mode 100644 contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs create mode 100644 contracts/credit-manager/tests/test_enumerate_vault_positions.rs create mode 100644 contracts/credit-manager/tests/test_vault_deposit.rs create mode 100644 contracts/mock-vault/Cargo.toml create mode 100644 contracts/mock-vault/src/contract.rs create mode 100644 contracts/mock-vault/src/deposit.rs create mode 100644 contracts/mock-vault/src/error.rs create mode 100644 contracts/mock-vault/src/lib.rs create mode 100644 contracts/mock-vault/src/msg.rs create mode 100644 contracts/mock-vault/src/query.rs create mode 100644 contracts/mock-vault/src/state.rs create mode 100644 packages/rover/src/adapters/vault.rs create mode 100644 packages/rover/src/extensions/mod.rs create mode 100644 packages/rover/src/msg/vault.rs diff --git a/Cargo.lock b/Cargo.lock index 97e5b800c..21dad04fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,9 +143,11 @@ dependencies = [ "cw2 0.14.0", "cw721", "cw721-base", + "itertools", "mars-health", "mock-oracle", "mock-red-bank", + "mock-vault", "rover", "schemars", "serde", @@ -285,28 +287,29 @@ dependencies = [ [[package]] name = "cw20" -version = "0.13.4" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" +checksum = "4f446f59c519fbac5ab8b9f6c7f8dcaa05ee761703971406b28221ea778bb737" dependencies = [ "cosmwasm-std", - "cw-utils 0.13.4", + "cw-utils 0.14.0", "schemars", "serde", ] [[package]] name = "cw20-base" -version = "0.13.4" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0306e606581f4fb45e82bcbb7f0333179ed53dd949c6523f01a99b4bfc1475a0" +checksum = "e39bf97c985a50f2e340833137b3f14999f58708c4ca9928cd8f87d530c4109c" dependencies = [ "cosmwasm-std", - "cw-storage-plus 0.13.4", - "cw-utils 0.13.4", - "cw2 0.13.4", + "cw-storage-plus 0.14.0", + "cw-utils 0.14.0", + "cw2 0.14.0", "cw20", "schemars", + "semver", "serde", "thiserror", ] @@ -540,7 +543,6 @@ version = "0.1.0" dependencies = [ "cosmwasm-std", "mars-outpost", - "thiserror", ] [[package]] @@ -548,7 +550,6 @@ name = "mars-outpost" version = "0.1.0" dependencies = [ "cosmwasm-std", - "cw2 0.13.4", "cw20", "cw20-base", "schemars", @@ -578,6 +579,18 @@ dependencies = [ "serde", ] +[[package]] +name = "mock-vault" +version = "1.0.0" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.14.0", + "rover", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "opaque-debug" version = "0.3.0" @@ -838,18 +851,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.32" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.32" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" dependencies = [ "proc-macro2", "quote", diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 7d05f89a2..52b3c8fbe 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -15,9 +15,10 @@ library = [] [dependencies] account-nft = { version = "0.1", path = "../account-nft", features = ["library"] } -mars-health = { version = "0.1", path = "../../../outposts/packages/mars-health" } +mars-health = { version = "0.1", path = "../../../outposts/packages/health" } mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } +mock-vault = { version = "1.0", path = "../../contracts/mock-vault", features = ["library"] } rover = { version = "0.1", path = "../../packages/rover" } cosmwasm-std = "1.0" @@ -31,3 +32,4 @@ serde = { version = "1.0", default-features = false, features = ["derive"] } [dev-dependencies] anyhow = "1" cw-multi-test = "0.14" +itertools = "0.10" diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index dc1caef48..695963fec 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -9,8 +9,10 @@ use crate::execute::{create_credit_account, dispatch_actions, execute_callback, use crate::health::compute_health; use crate::instantiate::store_config; use crate::query::{ - query_all_assets, query_all_debt_shares, query_all_total_debt_shares, query_allowed_coins, + query_all_coin_balances, query_all_debt_shares, query_all_total_debt_shares, + query_all_total_vault_coin_balances, query_all_vault_positions, query_allowed_coins, query_allowed_vaults, query_config, query_position_with_value, query_total_debt_shares, + query_total_vault_coin_balance, }; const CONTRACT_NAME: &str = "crates.io:rover-credit-manager"; @@ -62,7 +64,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { to_binary::(&Into::into(compute_health(deps, &env, &token_id)?)) } QueryMsg::AllCoinBalances { start_after, limit } => { - to_binary(&query_all_assets(deps, start_after, limit)?) + to_binary(&query_all_coin_balances(deps, start_after, limit)?) } QueryMsg::AllDebtShares { start_after, limit } => { to_binary(&query_all_debt_shares(deps, start_after, limit)?) @@ -71,6 +73,17 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { QueryMsg::AllTotalDebtShares { start_after, limit } => { to_binary(&query_all_total_debt_shares(deps, start_after, limit)?) } + QueryMsg::TotalVaultCoinBalance { vault } => to_binary(&query_total_vault_coin_balance( + deps, + &vault, + &env.contract.address, + )?), + QueryMsg::AllTotalVaultCoinBalances { start_after, limit } => to_binary( + &query_all_total_vault_coin_balances(deps, &env.contract.address, start_after, limit)?, + ), + QueryMsg::AllVaultPositions { start_after, limit } => { + to_binary(&query_all_vault_positions(deps, start_after, limit)?) + } }; res.map_err(Into::into) } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index ea36705b4..e78e8bbd1 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -4,18 +4,19 @@ use cosmwasm_std::{ use cw721::OwnerOfResponse; use cw721_base::QueryMsg; -use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use rover::coins::Coins; -use rover::error::{ContractError, ContractResult}; -use rover::msg::execute::{Action, CallbackMsg}; -use rover::msg::instantiate::ConfigUpdates; - use crate::borrow::borrow; use crate::deposit::deposit; use crate::health::assert_below_max_ltv; use crate::repay::repay; use crate::state::{ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, ORACLE, OWNER, RED_BANK}; +use crate::vault::{deposit_into_vault, update_vault_coin_balance}; use crate::withdraw::withdraw; +use account_nft::msg::ExecuteMsg as NftExecuteMsg; +use rover::coins::Coins; +use rover::error::{ContractError, ContractResult}; +use rover::extensions::Stringify; +use rover::msg::execute::{Action, CallbackMsg}; +use rover::msg::instantiate::ConfigUpdates; pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; @@ -86,12 +87,12 @@ pub fn update_config( if let Some(vaults) = new_config.allowed_vaults { vaults.iter().try_for_each(|unchecked| { - let vault = deps.api.addr_validate(unchecked)?; - ALLOWED_VAULTS.save(deps.storage, &vault, &Empty {}) + let vault = unchecked.check(deps.api)?; + ALLOWED_VAULTS.save(deps.storage, vault.address(), &Empty {}) })?; response = response .add_attribute("key", "allowed_vaults") - .add_attribute("value", vaults.join(", ")); + .add_attribute("value", vaults.to_string()) } if let Some(unchecked) = new_config.red_bank { @@ -142,6 +143,14 @@ pub fn dispatch_actions( token_id: token_id.to_string(), coin: coin.clone(), }), + Action::VaultDeposit { + vault, + coins: assets, + } => callbacks.push(CallbackMsg::VaultDeposit { + token_id: token_id.to_string(), + vault: vault.check(deps.api)?, + coins: assets.clone(), + }), } } @@ -186,6 +195,22 @@ pub fn execute_callback( CallbackMsg::AssertBelowMaxLTV { token_id } => { assert_below_max_ltv(deps.as_ref(), env, &token_id) } + CallbackMsg::VaultDeposit { + token_id, + vault, + coins, + } => deposit_into_vault(deps, &env.contract.address, &token_id, vault, &coins), + CallbackMsg::UpdateVaultCoinBalance { + vault, + token_id, + previous_total_balance, + } => update_vault_coin_balance( + deps, + vault, + &token_id, + previous_total_balance, + &env.contract.address, + ), } } diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index e3e2e4491..ff57200ae 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -10,8 +10,8 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; msg.allowed_vaults.iter().try_for_each(|unchecked| { - let vault = deps.api.addr_validate(unchecked)?; - ALLOWED_VAULTS.save(deps.storage, &vault, &Empty {}) + let vault = unchecked.check(deps.api)?; + ALLOWED_VAULTS.save(deps.storage, vault.address(), &Empty {}) })?; msg.allowed_coins diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 5108a81fb..23510523b 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -9,4 +9,5 @@ pub mod query; pub mod repay; pub mod state; pub mod utils; +pub mod vault; pub mod withdraw; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index b88f739e3..e33f7634f 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,16 +1,18 @@ -use cosmwasm_std::{Addr, Coin, Deps, Env, Order, StdResult}; +use cosmwasm_std::{Addr, Coin, Deps, Env, Order, StdResult, Uint128}; use cw_storage_plus::Bound; +use rover::adapters::{Vault, VaultBase, VaultPosition, VaultUnchecked}; use rover::error::ContractResult; use rover::msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtShares, DebtSharesValue, Positions, - PositionsWithValueResponse, SharesResponseItem, + PositionsWithValueResponse, SharesResponseItem, VaultPositionResponseItem, + VaultPositionWithAddr, VaultWithBalance, }; use rover::{Denom, NftTokenId}; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, COIN_BALANCES, DEBT_SHARES, ORACLE, OWNER, - RED_BANK, TOTAL_DEBT_SHARES, + RED_BANK, TOTAL_DEBT_SHARES, VAULT_POSITIONS, }; use crate::utils::{coin_value, debt_shares_to_amount}; @@ -33,6 +35,7 @@ pub fn query_position(deps: Deps, token_id: NftTokenId) -> ContractResult, limit: Option, @@ -143,19 +148,18 @@ pub fn query_all_debt_shares( .collect()) } -/// NOTE: This implementation of the query function assumes the map `ALLOWED_VAULTS` only saves `true`. -/// If a vault is to be removed from the whitelist, the map must remove the corresponding key, instead -/// of setting the value to `false`. +/// NOTE: This implementation of the query function assumes the map `ALLOWED_VAULTS` only saves `Empty`. +/// If a vault is to be removed from the whitelist, the map must remove the corresponding key. pub fn query_allowed_vaults( deps: Deps, - start_after: Option, + start_after: Option, limit: Option, -) -> StdResult> { - let addr: Addr; +) -> StdResult> { + let vault: Vault; let start = match &start_after { - Some(addr_str) => { - addr = deps.api.addr_validate(addr_str)?; - Some(Bound::exclusive(&addr)) + Some(unchecked) => { + vault = unchecked.check(deps.api)?; + Some(Bound::exclusive(vault.address())) } None => None, }; @@ -167,14 +171,63 @@ pub fn query_allowed_vaults( .take(limit) .map(|res| { let addr = res?; - Ok(addr.to_string()) + Ok(VaultBase::new(addr.to_string())) }) .collect() } -/// NOTE: This implementation of the query function assumes the map `ALLOWED_COINS` only saves `true`. -/// If a coin is to be removed from the whitelist, the map must remove the corresponding key, instead -/// of setting the value to `false`. +fn get_vault_positions( + deps: Deps, + token_id: NftTokenId, +) -> ContractResult> { + VAULT_POSITIONS + .prefix(token_id) + .range(deps.storage, None, None, Order::Ascending) + .map(|res| { + let (a, p) = res?; + Ok(VaultPositionWithAddr { + addr: a.to_string(), + position: VaultPosition { + unlocked: p.unlocked, + locked: p.locked, + }, + }) + }) + .collect() +} + +pub fn query_all_vault_positions( + deps: Deps, + start_after: Option<(String, String)>, + limit: Option, +) -> StdResult> { + let start = match &start_after { + Some((token_id, unchecked)) => { + let addr = deps.api.addr_validate(unchecked)?; + Some(Bound::exclusive((token_id.as_str(), addr))) + } + None => None, + }; + + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + Ok(VAULT_POSITIONS + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>>()? + .iter() + .map( + |((token_id, addr), vault_position)| VaultPositionResponseItem { + token_id: token_id.clone(), + addr: addr.to_string(), + vault_position: vault_position.clone(), + }, + ) + .collect()) +} + +/// NOTE: This implementation of the query function assumes the map `ALLOWED_COINS` only saves `Empty`. +/// If a coin is to be removed from the whitelist, the map must remove the corresponding key. pub fn query_allowed_coins( deps: Deps, start_after: Option, @@ -222,3 +275,45 @@ pub fn query_all_total_debt_shares( }) .collect()) } + +pub fn query_total_vault_coin_balance( + deps: Deps, + unchecked: &VaultUnchecked, + rover_addr: &Addr, +) -> StdResult { + let vault = unchecked.check(deps.api)?; + vault.query_balance(&deps.querier, rover_addr) +} + +pub fn query_all_total_vault_coin_balances( + deps: Deps, + rover_addr: &Addr, + start_after: Option, + limit: Option, +) -> StdResult> { + let vault: Vault; + let start = match &start_after { + Some(unchecked) => { + vault = unchecked.check(deps.api)?; + Some(Bound::exclusive(vault.address())) + } + None => None, + }; + + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + ALLOWED_VAULTS + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|res| { + let addr = res?; + let unchecked = VaultBase::new(addr.to_string()); + let vault = unchecked.check(deps.api)?; + let balance = vault.query_balance(&deps.querier, rover_addr)?; + Ok(VaultWithBalance { + vault: vault.into(), + balance, + }) + }) + .collect() +} diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 9ae87188d..9073cdecc 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,8 +1,8 @@ use cosmwasm_std::{Addr, Empty, Uint128}; use cw_storage_plus::{Item, Map}; -use rover::adapters::{Oracle, RedBank}; -use rover::{Denom, NftTokenId, Shares}; +use rover::adapters::{Oracle, RedBank, VaultPosition}; +use rover::{Denom, NftTokenId, Shares, VaultAddr}; // Contract config pub const OWNER: Item = Item::new("owner"); @@ -16,3 +16,5 @@ pub const ORACLE: Item = Item::new("oracle"); pub const COIN_BALANCES: Map<(NftTokenId, Denom), Uint128> = Map::new("coin_balance"); pub const DEBT_SHARES: Map<(NftTokenId, Denom), Shares> = Map::new("debt_shares"); pub const TOTAL_DEBT_SHARES: Map = Map::new("total_debt_shares"); +pub const VAULT_POSITIONS: Map<(NftTokenId, VaultAddr), VaultPosition> = + Map::new("vault_positions"); diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 697602303..158e4bc47 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -1,9 +1,12 @@ use cosmwasm_std::{Addr, Coin, Decimal, Deps, Storage, Uint128}; +use rover::adapters::Vault; use rover::error::{ContractError, ContractResult}; use rover::msg::query::CoinValue; -use crate::state::{ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES}; +use crate::state::{ + ALLOWED_COINS, ALLOWED_VAULTS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES, +}; pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, coin: &Coin) -> ContractResult<()> { let is_whitelisted = ALLOWED_COINS.has(storage, &coin.denom); @@ -13,6 +16,23 @@ pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, coin: &Coin) -> Con Ok(()) } +pub fn assert_coins_are_whitelisted( + storage: &mut dyn Storage, + assets: &[Coin], +) -> ContractResult<()> { + assets + .iter() + .try_for_each(|asset| assert_coin_is_whitelisted(storage, asset)) +} + +pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> ContractResult<()> { + let is_whitelisted = ALLOWED_VAULTS.has(storage, vault.address()); + if !is_whitelisted { + return Err(ContractError::NotWhitelisted(vault.address().to_string())); + } + Ok(()) +} + pub fn increment_coin_balance( storage: &mut dyn Storage, token_id: &str, diff --git a/contracts/credit-manager/src/vault/deposit.rs b/contracts/credit-manager/src/vault/deposit.rs new file mode 100644 index 000000000..62295ac53 --- /dev/null +++ b/contracts/credit-manager/src/vault/deposit.rs @@ -0,0 +1,114 @@ +use cosmwasm_std::{ + to_binary, Addr, Coin, CosmosMsg, DepsMut, QuerierWrapper, Response, Uint128, WasmMsg, +}; + +use rover::adapters::{Vault, VaultPosition}; +use rover::error::{ContractError, ContractResult}; +use rover::extensions::Stringify; +use rover::msg::execute::CallbackMsg; +use rover::msg::ExecuteMsg; +use rover::NftTokenId; + +use crate::state::VAULT_POSITIONS; +use crate::utils::{ + assert_coins_are_whitelisted, assert_vault_is_whitelisted, decrement_coin_balance, +}; + +pub fn deposit_into_vault( + deps: DepsMut, + rover_addr: &Addr, + token_id: NftTokenId, + vault: Vault, + coins: &[Coin], +) -> ContractResult { + assert_coins_are_whitelisted(deps.storage, coins)?; + assert_vault_is_whitelisted(deps.storage, &vault)?; + assert_denoms_match_vault_reqs(deps.querier, &vault, coins)?; + + // Decrement token's coin balance amount + coins.iter().try_for_each(|coin| -> ContractResult<_> { + decrement_coin_balance(deps.storage, token_id, coin)?; + Ok(()) + })?; + + let current_balance = vault.query_balance(&deps.querier, rover_addr)?; + let update_vault_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: rover_addr.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateVaultCoinBalance { + vault: vault.clone(), + token_id: token_id.to_string(), + previous_total_balance: current_balance, + }))?, + }); + + Ok(Response::new() + .add_message(vault.deposit_msg(coins)?) + .add_message(update_vault_balance_msg) + .add_attribute("action", "rover/credit_manager/vault/deposit")) +} + +pub fn update_vault_coin_balance( + deps: DepsMut, + vault: Vault, + token_id: &str, + previous_total_balance: Uint128, + rover_addr: &Addr, +) -> ContractResult { + let current_balance = vault.query_balance(&deps.querier, rover_addr)?; + + if previous_total_balance >= current_balance { + return Err(ContractError::NoVaultCoinsReceived); + } + + let diff = current_balance.checked_sub(previous_total_balance)?; + let vault_info = vault.query_vault_info(&deps.querier)?; + + // Increment token's vault position + VAULT_POSITIONS.update( + deps.storage, + (token_id, vault.address().clone()), + |position_opt| -> ContractResult<_> { + let p = position_opt.unwrap_or_default(); + match vault_info.lockup { + None => Ok(VaultPosition { + unlocked: p.unlocked.checked_add(diff)?, + locked: p.locked, + }), + Some(_) => Ok(VaultPosition { + unlocked: p.unlocked, + locked: p.locked.checked_add(diff)?, + }), + } + }, + )?; + + Ok(Response::new() + .add_attribute("action", "rover/credit_manager/vault/update_balance") + .add_attribute( + "amount_incremented", + current_balance.checked_sub(previous_total_balance)?, + )) +} + +pub fn assert_denoms_match_vault_reqs( + querier: QuerierWrapper, + vault: &Vault, + assets: &[Coin], +) -> ContractResult<()> { + let vault_info = vault.query_vault_info(&querier)?; + + let all_req_coins_present = vault_info + .coins + .iter() + .all(|coin| assets.iter().any(|req_coin| req_coin.denom == coin.denom)); + + if !all_req_coins_present || assets.len() != vault_info.coins.len() { + return Err(ContractError::RequirementsNotMet(format!( + "Required assets: {} -- do not match given assets: {}", + vault_info.coins.as_slice().to_string(), + assets.to_string() + ))); + } + Ok(()) +} diff --git a/contracts/credit-manager/src/vault/mod.rs b/contracts/credit-manager/src/vault/mod.rs new file mode 100644 index 000000000..2fec8f0f5 --- /dev/null +++ b/contracts/credit-manager/src/vault/mod.rs @@ -0,0 +1,3 @@ +pub use self::deposit::*; + +mod deposit; diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index a53f77642..4ff5f2161 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -1,6 +1,6 @@ use cosmwasm_std::Decimal; -use crate::helpers::CoinInfo; +use crate::helpers::{CoinInfo, VaultTestInfo}; pub fn build_mock_coin_infos(count: usize) -> Vec { (1..=count) @@ -13,3 +13,16 @@ pub fn build_mock_coin_infos(count: usize) -> Vec { }) .collect() } + +pub fn build_mock_vaults(count: usize) -> Vec { + (1..=count) + .into_iter() + .map(|i| { + VaultTestInfo { + lp_token_denom: format!("vault_{}", i), + lockup: Some(1_209_600), // 14 days + asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + } + }) + .collect() +} diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index 10bf5b422..41cef0e7a 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -11,6 +11,9 @@ use mock_oracle::contract::{ use mock_red_bank::contract::{ execute as redBankExecute, instantiate as redBankInstantiate, query as redBankQuery, }; +use mock_vault::contract::{ + execute as vaultExecute, instantiate as vaultInstantiate, query as vaultQuery, +}; pub fn mock_app() -> App { App::default() @@ -35,3 +38,8 @@ pub fn mock_oracle_contract() -> Box> { let contract = ContractWrapper::new(oracleExecute, oracleInstantiate, oracleQuery); Box::new(contract) } + +pub fn mock_vault_contract() -> Box> { + let contract = ContractWrapper::new(vaultExecute, vaultInstantiate, vaultQuery); + Box::new(contract) +} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 44c67cee5..4d7312715 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -1,6 +1,7 @@ use std::mem::take; use anyhow::Result as AnyResult; +use cosmwasm_std::testing::MockApi; use cosmwasm_std::{Addr, Coin, Uint128}; use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; @@ -13,18 +14,20 @@ use mock_red_bank::msg::QueryMsg::UserAssetDebt; use mock_red_bank::msg::{ CoinMarketInfo, InstantiateMsg as RedBankInstantiateMsg, UserAssetDebtResponse, }; -use rover::adapters::{OracleBase, RedBankBase}; +use mock_vault::contract::DEFAULT_VAULT_TOKEN_PREFUND; +use mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; +use rover::adapters::{OracleBase, RedBankBase, Vault, VaultBase, VaultUnchecked}; use rover::msg::execute::{Action, CallbackMsg}; use rover::msg::instantiate::ConfigUpdates; use rover::msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtShares, HealthResponse, - PositionsWithValueResponse, SharesResponseItem, + PositionsWithValueResponse, SharesResponseItem, VaultPositionResponseItem, VaultWithBalance, }; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::helpers::{ mock_account_nft_contract, mock_oracle_contract, mock_red_bank_contract, mock_rover_contract, - AccountToFund, CoinInfo, + mock_vault_contract, AccountToFund, CoinInfo, VaultTestInfo, }; pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000u128); @@ -37,12 +40,13 @@ pub struct MockEnv { pub struct MockEnvBuilder { pub app: BasicApp, pub owner: Option, - pub allowed_vaults: Option>, + pub allowed_vaults: Option>, + pub pre_deployed_vaults: Option>, pub allowed_coins: Option>, pub oracle: Option>, pub red_bank: Option>, - pub setup_nft_contract: bool, - pub setup_nft_contract_owner: bool, + pub deploy_nft_contract: bool, + pub set_nft_contract_owner: bool, pub accounts_to_fund: Vec, } @@ -53,11 +57,12 @@ impl MockEnv { app: App::default(), owner: None, allowed_vaults: None, + pre_deployed_vaults: None, allowed_coins: None, oracle: None, red_bank: None, - setup_nft_contract: true, - setup_nft_contract_owner: true, + deploy_nft_contract: true, + set_nft_contract_owner: true, accounts_to_fund: vec![], } } @@ -97,8 +102,8 @@ impl MockEnv { ) } - pub fn setup_new_nft_contract(&mut self) -> AnyResult { - let nft_contract = setup_nft_contract(&mut self.app, &self.rover.clone()); + pub fn deploy_nft_contract(&mut self) -> AnyResult { + let nft_contract = deploy_nft_contract(&mut self.app, &self.rover.clone()); propose_new_nft_contract_owner( &mut self.app, nft_contract.clone(), @@ -193,9 +198,9 @@ impl MockEnv { pub fn query_allowed_vaults( &self, - start_after: Option, + start_after: Option, limit: Option, - ) -> Vec { + ) -> Vec { self.app .wrap() .query_wasm_smart( @@ -205,6 +210,21 @@ impl MockEnv { .unwrap() } + pub fn get_vault(&self, vault: &VaultTestInfo) -> VaultUnchecked { + self.query_allowed_vaults(None, Some(30)) // Max limit + .iter() + .find(|v| { + let info = v + .check(&MockApi::default()) + .unwrap() + .query_vault_info(&self.app.wrap()) + .unwrap(); + vault.lp_token_denom == info.token_denom + }) + .unwrap() + .clone() + } + pub fn query_allowed_coins( &self, start_after: Option, @@ -284,6 +304,54 @@ impl MockEnv { ) .unwrap() } + + pub fn query_preview_redeem(&self, vault: &VaultUnchecked, shares: Uint128) -> Vec { + vault + .check(&MockApi::default()) + .unwrap() + .query_redeem_preview(&self.app.wrap(), shares) + .unwrap() + } + + pub fn query_total_vault_coin_balance(&self, vault: &VaultUnchecked) -> Uint128 { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::TotalVaultCoinBalance { + vault: vault.clone(), + }, + ) + .unwrap() + } + + pub fn query_all_vault_positions( + &self, + start_after: Option<(String, String)>, + limit: Option, + ) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::AllVaultPositions { start_after, limit }, + ) + .unwrap() + } + + pub fn query_all_total_vault_coin_balances( + &self, + start_after: Option, + limit: Option, + ) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::AllTotalVaultCoinBalances { start_after, limit }, + ) + .unwrap() + } } impl MockEnvBuilder { @@ -316,33 +384,37 @@ impl MockEnvBuilder { fn deploy_nft_contract(&mut self, rover: &Addr) { let nft_contract_owner = Addr::unchecked("original_nft_contract_owner"); - if self.setup_nft_contract { - let nft_contract = setup_nft_contract(&mut self.app, &nft_contract_owner); - if self.setup_nft_contract_owner { + if self.deploy_nft_contract { + let nft_contract = deploy_nft_contract(&mut self.app, &nft_contract_owner); + if self.set_nft_contract_owner { propose_new_nft_contract_owner( &mut self.app, nft_contract.clone(), &nft_contract_owner, rover, ); - // Update config to save new nft_contract - self.app - .execute_contract( - self.get_owner(), - rover.clone(), - &ExecuteMsg::UpdateConfig { - new_config: ConfigUpdates { - account_nft: Some(nft_contract.to_string()), - ..Default::default() - }, - }, - &[], - ) - .unwrap(); + self.update_config( + rover, + ConfigUpdates { + account_nft: Some(nft_contract.to_string()), + ..Default::default() + }, + ) } } } + pub fn update_config(&mut self, rover: &Addr, new_config: ConfigUpdates) { + self.app + .execute_contract( + self.get_owner(), + rover.clone(), + &ExecuteMsg::UpdateConfig { new_config }, + &[], + ) + .unwrap(); + } + //-------------------------------------------------------------------------------------------------- // Get or defaults //-------------------------------------------------------------------------------------------------- @@ -357,13 +429,17 @@ impl MockEnvBuilder { .map(|info| info.denom.clone()) .collect(); + let mut allowed_vaults = vec![]; + allowed_vaults.extend(self.deploy_vaults()); + allowed_vaults.extend(self.pre_deployed_vaults.clone().unwrap_or_default()); + self.app.instantiate_contract( code_id, self.get_owner(), &InstantiateMsg { owner: self.get_owner().to_string(), allowed_coins, - allowed_vaults: self.get_allowed_vaults(), + allowed_vaults, red_bank, oracle, }, @@ -380,10 +456,14 @@ impl MockEnvBuilder { } fn get_oracle(&mut self) -> OracleBase { - self.oracle.clone().unwrap_or_else(|| self.setup_oracle()) + if self.oracle.is_none() { + let addr = self.deploy_oracle(); + self.oracle = Some(addr); + } + self.oracle.clone().unwrap() } - fn setup_oracle(&mut self) -> OracleBase { + fn deploy_oracle(&mut self) -> OracleBase { let contract_code_id = self.app.store_code(mock_oracle_contract()); let addr = self .app @@ -409,12 +489,14 @@ impl MockEnvBuilder { } fn get_red_bank(&mut self) -> RedBankBase { - self.red_bank - .clone() - .unwrap_or_else(|| self.setup_red_bank()) + if self.red_bank.is_none() { + let addr = self.deploy_red_bank(); + self.red_bank = Some(addr); + } + self.red_bank.clone().unwrap() } - fn setup_red_bank(&mut self) -> RedBankBase { + pub fn deploy_red_bank(&mut self) -> RedBankBase { let contract_code_id = self.app.store_code(mock_red_bank_contract()); let addr = self .app @@ -455,8 +537,50 @@ impl MockEnvBuilder { RedBankBase::new(addr) } - fn get_allowed_vaults(&self) -> Vec { - self.allowed_vaults.clone().unwrap_or_default() + fn deploy_vault(&mut self, vault: &VaultTestInfo) -> Vault { + let code_id = self.app.store_code(mock_vault_contract()); + let oracle = self.get_oracle().into(); + let addr = self + .app + .instantiate_contract( + code_id, + Addr::unchecked("vault-instantiator"), + &VaultInstantiateMsg { + lp_token_denom: vault.clone().lp_token_denom, + lockup: vault.lockup, + asset_denoms: vault.clone().asset_denoms, + oracle, + }, + &[], + "mock-vault", + None, + ) + .unwrap(); + self.fund_vault(&addr, &vault.lp_token_denom); + VaultBase::new(addr) + } + + /// cw-multi-test does not yet have the ability to mint sdk coins. For this reason, + /// this contract expects to be pre-funded with vault tokens and it will simulate the mint. + fn fund_vault(&mut self, vault_addr: &Addr, denom: &str) { + self.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: vault_addr.to_string(), + amount: vec![Coin { + denom: denom.into(), + amount: DEFAULT_VAULT_TOKEN_PREFUND, + }], + })) + .unwrap(); + } + + fn deploy_vaults(&mut self) -> Vec { + self.allowed_vaults + .clone() + .unwrap_or_default() + .iter() + .map(|v| self.deploy_vault(v).into()) + .collect() } fn get_allowed_coins(&self) -> Vec { @@ -477,7 +601,7 @@ impl MockEnvBuilder { self } - pub fn allowed_vaults(&mut self, allowed_vaults: &[String]) -> &mut Self { + pub fn allowed_vaults(&mut self, allowed_vaults: &[VaultTestInfo]) -> &mut Self { self.allowed_vaults = Some(allowed_vaults.to_vec()); self } @@ -498,12 +622,21 @@ impl MockEnvBuilder { } pub fn no_nft_contract(&mut self) -> &mut Self { - self.setup_nft_contract = false; + self.deploy_nft_contract = false; self } pub fn no_nft_contract_owner(&mut self) -> &mut Self { - self.setup_nft_contract_owner = false; + self.set_nft_contract_owner = false; + self + } + + pub fn pre_deployed_vaults(&mut self, vaults: &[&str]) -> &mut Self { + let vaults = vaults + .iter() + .map(|v| VaultBase::new(v.to_string())) + .collect::>(); + self.pre_deployed_vaults = Some(vaults); self } } @@ -512,7 +645,7 @@ impl MockEnvBuilder { // Shared utils between MockBuilder & MockEnv //-------------------------------------------------------------------------------------------------- -fn setup_nft_contract(app: &mut App, owner: &Addr) -> Addr { +fn deploy_nft_contract(app: &mut App, owner: &Addr) -> Addr { let nft_contract_code_id = app.store_code(mock_account_nft_contract()); app.instantiate_contract( nft_contract_code_id, diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 47a91a344..1c685900b 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -16,6 +16,13 @@ pub struct CoinInfo { pub liquidation_threshold: Decimal, } +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct VaultTestInfo { + pub lp_token_denom: String, + pub lockup: Option, + pub asset_denoms: Vec, +} + impl CoinInfo { pub fn to_coin(&self, amount: Uint128) -> Coin { Coin { diff --git a/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs b/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs index b1a68cd94..04f1dd682 100644 --- a/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs +++ b/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs @@ -1,44 +1,12 @@ -use crate::helpers::MockEnv; +use cosmwasm_std::testing::MockApi; + +use crate::helpers::{assert_contents_equal, build_mock_vaults, MockEnv}; pub mod helpers; #[test] fn test_pagination_on_allowed_vaults_query_works() { - let allowed_vaults = vec![ - "addr1".to_string(), - "addr2".to_string(), - "addr3".to_string(), - "addr4".to_string(), - "addr5".to_string(), - "addr6".to_string(), - "addr7".to_string(), - "addr8".to_string(), - "addr9".to_string(), - "addr10".to_string(), - "addr11".to_string(), - "addr12".to_string(), - "addr13".to_string(), - "addr14".to_string(), - "addr15".to_string(), - "addr16".to_string(), - "addr17".to_string(), - "addr18".to_string(), - "addr19".to_string(), - "addr20".to_string(), - "addr21".to_string(), - "addr22".to_string(), - "addr23".to_string(), - "addr24".to_string(), - "addr25".to_string(), - "addr26".to_string(), - "addr27".to_string(), - "addr28".to_string(), - "addr29".to_string(), - "addr30".to_string(), - "addr31".to_string(), - "addr32".to_string(), - ]; - + let allowed_vaults = build_mock_vaults(32); let mock = MockEnv::new() .allowed_vaults(&allowed_vaults) .build() @@ -66,14 +34,24 @@ fn test_pagination_on_allowed_vaults_query_works() { assert_eq!(vaults_res_d.len(), 2); - let combined: Vec = vaults_res_a + let combined = vaults_res_a .iter() .cloned() .chain(vaults_res_b.iter().cloned()) .chain(vaults_res_c.iter().cloned()) .chain(vaults_res_d.iter().cloned()) - .collect(); + .map(|v| v.check(&MockApi::default()).unwrap()) + .map(|v| v.query_vault_info(&mock.app.wrap()).unwrap()) + .map(|info| info.token_denom) + .collect::>(); assert_eq!(combined.len(), allowed_vaults.len()); - assert!(allowed_vaults.iter().all(|item| combined.contains(item))); + + assert_contents_equal( + allowed_vaults + .iter() + .map(|v| v.lp_token_denom.clone()) + .collect(), + combined, + ) } diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs new file mode 100644 index 000000000..522e23f8a --- /dev/null +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -0,0 +1,148 @@ +use cosmwasm_std::testing::MockApi; +use cosmwasm_std::{Addr, Coin, Uint128}; + +use rover::msg::execute::Action; + +use crate::helpers::{ + assert_contents_equal, build_mock_vaults, uatom_info, uosmo_info, AccountToFund, MockEnv, +}; + +pub mod helpers; + +#[test] +fn test_pagination_on_all_vault_coin_balances_query_works() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let user_c = Addr::unchecked("user_c"); + + let all_vaults = build_mock_vaults(22); + let mut mock = MockEnv::new() + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: vec![ + Coin::new(1000u128, uosmo.denom.clone()), + Coin::new(1000u128, uatom.denom.clone()), + ], + }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: vec![ + Coin::new(1000u128, uosmo.denom.clone()), + Coin::new(1000u128, uatom.denom.clone()), + ], + }) + .fund_account(AccountToFund { + addr: user_c.clone(), + funds: vec![ + Coin::new(1000u128, uosmo.denom.clone()), + Coin::new(1000u128, uatom.denom.clone()), + ], + }) + .allowed_coins(&[uosmo.clone(), uatom.clone()]) + .allowed_vaults(&all_vaults) + .build() + .unwrap(); + + let mut actions = vec![ + Action::Deposit(uatom.to_coin(Uint128::new(220))), + Action::Deposit(uosmo.to_coin(Uint128::new(220))), + ]; + + all_vaults.iter().for_each(|v| { + actions.extend([Action::VaultDeposit { + vault: mock.get_vault(v), + coins: vec![ + uatom.to_coin(Uint128::new(10)), + uosmo.to_coin(Uint128::new(10)), + ], + }]); + }); + + let token_id_a = mock.create_credit_account(&user_a).unwrap(); + mock.update_credit_account( + &token_id_a, + &user_a, + actions.clone(), + &[ + uatom.to_coin(Uint128::new(220)), + uosmo.to_coin(Uint128::new(220)), + ], + ) + .unwrap(); + + let token_id_b = mock.create_credit_account(&user_b).unwrap(); + mock.update_credit_account( + &token_id_b, + &user_b, + actions.clone(), + &[ + uatom.to_coin(Uint128::new(220)), + uosmo.to_coin(Uint128::new(220)), + ], + ) + .unwrap(); + + let token_id_c = mock.create_credit_account(&user_c).unwrap(); + mock.update_credit_account( + &token_id_c, + &user_c, + actions, + &[ + uatom.to_coin(Uint128::new(220)), + uosmo.to_coin(Uint128::new(220)), + ], + ) + .unwrap(); + + let vaults_res = mock.query_all_total_vault_coin_balances(None, Some(58_u32)); + // Assert maximum is observed + assert_eq!(vaults_res.len(), 22); + + let vaults_res = mock.query_all_total_vault_coin_balances(None, Some(2_u32)); + // Assert limit request is observed + assert_eq!(vaults_res.len(), 2); + + let vaults_res_a = mock.query_all_total_vault_coin_balances(None, None); + let vaults_res_b = mock.query_all_total_vault_coin_balances( + Some(vaults_res_a.last().unwrap().clone().vault), + None, + ); + let vaults_res_c = mock.query_all_total_vault_coin_balances( + Some(vaults_res_b.last().unwrap().clone().vault), + None, + ); + let vaults_res_d = mock.query_all_total_vault_coin_balances( + Some(vaults_res_c.last().unwrap().clone().vault), + None, + ); + + // Assert default is observed + assert_eq!(vaults_res_a.len(), 10); + assert_eq!(vaults_res_b.len(), 10); + assert_eq!(vaults_res_c.len(), 2); + assert_eq!(vaults_res_d.len(), 0); + + let combined = vaults_res_a + .iter() + .cloned() + .chain(vaults_res_b.iter().cloned()) + .chain(vaults_res_c.iter().cloned()) + .chain(vaults_res_d.iter().cloned()) + .map(|v| v.vault.check(&MockApi::default()).unwrap()) + .map(|v| v.query_vault_info(&mock.app.wrap()).unwrap()) + .map(|info| info.token_denom) + .collect::>(); + + assert_eq!(combined.len(), all_vaults.len()); + + assert_contents_equal( + all_vaults + .iter() + .map(|v| v.lp_token_denom.clone()) + .collect(), + combined, + ) +} diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs new file mode 100644 index 000000000..3524144ef --- /dev/null +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -0,0 +1,149 @@ +use cosmwasm_std::testing::MockApi; +use cosmwasm_std::{Addr, Api, Coin, Uint128}; +use itertools::Itertools; + +use rover::adapters::VaultBase; +use rover::msg::execute::Action; + +use crate::helpers::{ + assert_contents_equal, build_mock_vaults, uatom_info, uosmo_info, AccountToFund, MockEnv, +}; + +pub mod helpers; + +#[test] +fn test_pagination_on_all_vault_positions_query_works() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let user_c = Addr::unchecked("user_c"); + + let all_vaults = build_mock_vaults(22); + let mut mock = MockEnv::new() + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: vec![ + Coin::new(1000u128, uosmo.denom.clone()), + Coin::new(1000u128, uatom.denom.clone()), + ], + }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: vec![ + Coin::new(1000u128, uosmo.denom.clone()), + Coin::new(1000u128, uatom.denom.clone()), + ], + }) + .fund_account(AccountToFund { + addr: user_c.clone(), + funds: vec![ + Coin::new(1000u128, uosmo.denom.clone()), + Coin::new(1000u128, uatom.denom.clone()), + ], + }) + .allowed_coins(&[uosmo.clone(), uatom.clone()]) + .allowed_vaults(&all_vaults) + .build() + .unwrap(); + + let mut actions = vec![ + Action::Deposit(uatom.to_coin(Uint128::new(220))), + Action::Deposit(uosmo.to_coin(Uint128::new(220))), + ]; + + all_vaults.iter().for_each(|v| { + actions.extend([Action::VaultDeposit { + vault: mock.get_vault(v), + coins: vec![ + uatom.to_coin(Uint128::new(10)), + uosmo.to_coin(Uint128::new(10)), + ], + }]); + }); + + let token_id_a = mock.create_credit_account(&user_a).unwrap(); + mock.update_credit_account( + &token_id_a, + &user_a, + actions.clone(), + &[ + uatom.to_coin(Uint128::new(220)), + uosmo.to_coin(Uint128::new(220)), + ], + ) + .unwrap(); + + let token_id_b = mock.create_credit_account(&user_b).unwrap(); + mock.update_credit_account( + &token_id_b, + &user_b, + actions.clone(), + &[ + uatom.to_coin(Uint128::new(220)), + uosmo.to_coin(Uint128::new(220)), + ], + ) + .unwrap(); + + let token_id_c = mock.create_credit_account(&user_c).unwrap(); + mock.update_credit_account( + &token_id_c, + &user_c, + actions, + &[ + uatom.to_coin(Uint128::new(220)), + uosmo.to_coin(Uint128::new(220)), + ], + ) + .unwrap(); + + let vaults_res = mock.query_all_vault_positions(None, Some(58_u32)); + // Assert maximum is observed + assert_eq!(vaults_res.len(), 30); + + let vaults_res = mock.query_all_vault_positions(None, Some(2_u32)); + // Assert limit request is observed + assert_eq!(vaults_res.len(), 2); + + let vaults_res_a = mock.query_all_vault_positions(None, None); + let item = vaults_res_a.last().unwrap(); + let vaults_res_b = + mock.query_all_vault_positions(Some((item.token_id.clone(), item.addr.clone())), Some(30)); + let item = vaults_res_b.last().unwrap(); + let vaults_res_c = + mock.query_all_vault_positions(Some((item.token_id.clone(), item.addr.clone())), Some(30)); + let item = vaults_res_c.last().unwrap(); + let vaults_res_d = + mock.query_all_vault_positions(Some((item.token_id.clone(), item.addr.clone())), None); + + // Assert default is observed + assert_eq!(vaults_res_a.len(), 10); + assert_eq!(vaults_res_b.len(), 30); + assert_eq!(vaults_res_c.len(), 26); + assert_eq!(vaults_res_d.len(), 0); + + let combined = vaults_res_a + .iter() + .cloned() + .chain(vaults_res_b.iter().cloned()) + .chain(vaults_res_c.iter().cloned()) + .chain(vaults_res_d.iter().cloned()) + .map(|v| VaultBase::new(MockApi::default().addr_validate(v.addr.as_str()).unwrap())) + .map(|v| v.query_vault_info(&mock.app.wrap()).unwrap()) + .map(|info| info.token_denom) + .collect::>(); + + let deduped = combined.iter().unique().cloned().collect::>(); + + assert_eq!(deduped.len(), all_vaults.len()); + + assert_contents_equal( + all_vaults + .iter() + .map(|v| v.lp_token_denom.clone()) + .collect(), + deduped, + ) +} diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 482824955..2fa73110b 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -1,5 +1,5 @@ use crate::helpers::{ - assert_contents_equal, uatom_info, ujake_info, uosmo_info, CoinInfo, MockEnv, + assert_contents_equal, uatom_info, ujake_info, uosmo_info, CoinInfo, MockEnv, VaultTestInfo, }; use cosmwasm_std::Decimal; @@ -32,9 +32,21 @@ fn test_nft_contract_addr_not_set_on_instantiate() { #[test] fn test_allowed_vaults_set_on_instantiate() { let allowed_vaults = vec![ - "vault_contract_1".to_string(), - "vault_contract_2".to_string(), - "vault_contract_3".to_string(), + VaultTestInfo { + lp_token_denom: "vault_contract_1".to_string(), + lockup: None, + asset_denoms: vec![], + }, + VaultTestInfo { + lp_token_denom: "vault_contract_2".to_string(), + lockup: None, + asset_denoms: vec![], + }, + VaultTestInfo { + lp_token_denom: "vault_contract_3".to_string(), + lockup: None, + asset_denoms: vec![], + }, ]; let mock = MockEnv::new() @@ -42,13 +54,19 @@ fn test_allowed_vaults_set_on_instantiate() { .build() .unwrap(); let res = mock.query_allowed_vaults(None, None); - assert_contents_equal(res, allowed_vaults); + assert_contents_equal( + res, + allowed_vaults + .iter() + .map(|info| mock.get_vault(info)) + .collect(), + ); } #[test] fn test_raises_on_invalid_vaults_addr() { let mock = MockEnv::new() - .allowed_vaults(&["%%%INVALID%%%".to_string()]) + .pre_deployed_vaults(&["%%%INVALID%%%"]) .build(); if mock.is_ok() { diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 5f3a529da..d6ae8b7e1 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -1,6 +1,6 @@ use cosmwasm_std::Addr; -use rover::adapters::{OracleBase, RedBankBase}; +use rover::adapters::{OracleBase, RedBankBase, VaultBase}; use rover::msg::instantiate::ConfigUpdates; use crate::helpers::MockEnv; @@ -36,10 +36,10 @@ fn test_update_config_works_with_full_config() { let original_allowed_vaults = mock.query_allowed_vaults(None, None); let original_allowed_coins = mock.query_allowed_coins(None, None); - let new_nft_contract = mock.setup_new_nft_contract().unwrap(); + let new_nft_contract = mock.deploy_nft_contract().unwrap(); let new_owner = Addr::unchecked("new_owner"); let new_red_bank = RedBankBase::new("new_red_bank".to_string()); - let new_allowed_vaults = vec!["vault_contract_1".to_string()]; + let new_allowed_vaults = vec![VaultBase::new("vault_contract_1".to_string())]; let new_allowed_coins = vec!["uosmo".to_string()]; let new_oracle = OracleBase::new("new_oracle".to_string()); @@ -86,8 +86,8 @@ fn test_update_config_works_with_some_config() { let original_allowed_vaults = mock.query_allowed_vaults(None, None); let original_allowed_coins = mock.query_allowed_coins(None, None); - let new_nft_contract = mock.setup_new_nft_contract().unwrap(); - let new_allowed_vaults = vec!["vault_contract_1".to_string()]; + let new_nft_contract = mock.deploy_nft_contract().unwrap(); + let new_allowed_vaults = vec![VaultBase::new("vault_contract_1".to_string())]; mock.update_config( &Addr::unchecked(original_config.owner.clone()), diff --git a/contracts/credit-manager/tests/test_vault_deposit.rs b/contracts/credit-manager/tests/test_vault_deposit.rs new file mode 100644 index 000000000..7510cfcfc --- /dev/null +++ b/contracts/credit-manager/tests/test_vault_deposit.rs @@ -0,0 +1,342 @@ +use cosmwasm_std::OverflowOperation::Sub; +use cosmwasm_std::{Addr, Coin, OverflowError, Uint128}; + +use mock_vault::contract::STARTING_VAULT_SHARES; +use rover::adapters::VaultBase; +use rover::error::ContractError; +use rover::msg::execute::Action::{Deposit, VaultDeposit}; + +use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv, VaultTestInfo}; + +pub mod helpers; + +#[test] +fn test_only_account_owner_can_take_action() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().build().unwrap(); + + let token_id = mock.create_credit_account(&user).unwrap(); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.update_credit_account( + &token_id, + &bad_guy, + vec![VaultDeposit { + vault: VaultBase::new("xyz".to_string()), + coins: vec![], + }], + &[], + ); + + assert_err( + res, + ContractError::NotTokenOwner { + user: bad_guy.to_string(), + token_id, + }, + ); +} + +#[test] +fn test_all_deposit_coins_are_whitelisted() { + let uatom = uatom_info(); + let leverage_vault = VaultTestInfo { + lp_token_denom: "uleverage".to_string(), + lockup: None, + asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom]) + .allowed_vaults(&[leverage_vault.clone()]) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![VaultDeposit { + vault, + coins: vec![Coin::new(200u128, "uatom"), Coin::new(400u128, "uosmo")], + }], + &[], + ); + + assert_err(res, ContractError::NotWhitelisted("uosmo".to_string())); +} + +#[test] +fn test_vault_is_whitelisted() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + lp_token_denom: "uleverage".to_string(), + lockup: None, + asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom, uosmo]) + .allowed_vaults(&[leverage_vault]) + .build() + .unwrap(); + + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![VaultDeposit { + vault: VaultBase::new("unknown_vault".to_string()), + coins: vec![Coin::new(200u128, "uatom")], + }], + &[], + ); + + assert_err( + res, + ContractError::NotWhitelisted("unknown_vault".to_string()), + ); +} + +#[test] +fn test_deposited_coins_match_vault_requirements() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + lp_token_denom: "uleverage".to_string(), + lockup: None, + asset_denoms: vec!["uatom".to_string(), "ujake".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom, uosmo]) + .allowed_vaults(&[leverage_vault.clone()]) + .build() + .unwrap(); + + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![VaultDeposit { + vault: mock.get_vault(&leverage_vault), + coins: vec![Coin::new(200u128, "uatom"), Coin::new(200u128, "uosmo")], + }], + &[], + ); + + assert_err( + res, + ContractError::RequirementsNotMet( + "Required assets: uatom, ujake -- do not match given assets: uatom, uosmo".to_string(), + ), + ); +} + +#[test] +fn test_fails_if_not_enough_funds_for_deposit() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + lp_token_denom: "uleverage".to_string(), + lockup: None, + asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom, uosmo]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, "uatom"), Coin::new(500u128, "uosmo")], + }) + .build() + .unwrap(); + + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![VaultDeposit { + vault: mock.get_vault(&leverage_vault), + coins: vec![Coin::new(200u128, "uatom"), Coin::new(200u128, "uosmo")], + }], + &[], + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: Sub, + operand1: "0".to_string(), + operand2: "200".to_string(), + }), + ); +} + +#[test] +fn test_successful_deposit_into_locked_vault() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + lp_token_denom: "uleverage".to_string(), + lockup: Some(1_209_600u64), + asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, "uatom"), Coin::new(500u128, "uosmo")], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let token_id = mock.create_credit_account(&user).unwrap(); + let balance = mock.query_total_vault_coin_balance(&vault); + assert_eq!(balance, Uint128::zero()); + + mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(Coin { + denom: uatom.denom, + amount: Uint128::new(200), + }), + Deposit(Coin { + denom: uosmo.denom, + amount: Uint128::new(400), + }), + VaultDeposit { + vault: vault.clone(), + coins: vec![Coin::new(23u128, "uatom"), Coin::new(120u128, "uosmo")], + }, + ], + &[Coin::new(200u128, "uatom"), Coin::new(400u128, "uosmo")], + ) + .unwrap(); + + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); + + let res = mock.query_position(&token_id); + assert_eq!(res.vault_positions.len(), 1); + assert_eq!( + STARTING_VAULT_SHARES, + res.vault_positions.first().unwrap().position.locked + ); + assert_eq!( + Uint128::zero(), + res.vault_positions.first().unwrap().position.unlocked + ); + + let assets = + mock.query_preview_redeem(&vault, res.vault_positions.first().unwrap().position.locked); + + let osmo_withdraw = assets.iter().find(|coin| coin.denom == "uosmo").unwrap(); + assert_eq!(osmo_withdraw.amount, Uint128::new(120)); + let atom_withdraw = assets.iter().find(|coin| coin.denom == "uatom").unwrap(); + assert_eq!(atom_withdraw.amount, Uint128::new(23)); + + let balance = mock.query_total_vault_coin_balance(&vault); + assert_eq!(balance, STARTING_VAULT_SHARES); + + let vault_token_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + assert_eq!(vault_token_balance.amount, STARTING_VAULT_SHARES) +} + +#[test] +fn test_successful_deposit_into_unlocked_vault() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + lp_token_denom: "uleverage".to_string(), + lockup: None, + asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, "uatom"), Coin::new(500u128, "uosmo")], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let token_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(Coin { + denom: uatom.denom, + amount: Uint128::new(200), + }), + Deposit(Coin { + denom: uosmo.denom, + amount: Uint128::new(400), + }), + VaultDeposit { + vault: vault.clone(), + coins: vec![Coin::new(23u128, "uatom"), Coin::new(120u128, "uosmo")], + }, + ], + &[Coin::new(200u128, "uatom"), Coin::new(400u128, "uosmo")], + ) + .unwrap(); + + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); + + let res = mock.query_position(&token_id); + assert_eq!(res.vault_positions.len(), 1); + assert_eq!( + STARTING_VAULT_SHARES, + res.vault_positions.first().unwrap().position.unlocked + ); + assert_eq!( + Uint128::zero(), + res.vault_positions.first().unwrap().position.locked + ); + + let assets = mock.query_preview_redeem( + &vault, + res.vault_positions.first().unwrap().position.unlocked, + ); + + let osmo_withdraw = assets.iter().find(|coin| coin.denom == "uosmo").unwrap(); + assert_eq!(osmo_withdraw.amount, Uint128::new(120)); + let atom_withdraw = assets.iter().find(|coin| coin.denom == "uatom").unwrap(); + assert_eq!(atom_withdraw.amount, Uint128::new(23)); + + let balance = mock.query_total_vault_coin_balance(&vault); + assert_eq!(balance, STARTING_VAULT_SHARES); + + let vault_token_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + assert_eq!(vault_token_balance.amount, STARTING_VAULT_SHARES) +} diff --git a/contracts/mock-oracle/Cargo.toml b/contracts/mock-oracle/Cargo.toml index 60c40a521..c009d8c74 100644 --- a/contracts/mock-oracle/Cargo.toml +++ b/contracts/mock-oracle/Cargo.toml @@ -14,7 +14,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -mars-outpost = { version = "0.1", path = "../../../outposts/packages/mars-outpost" } +mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } cosmwasm-std = "1.0" cw-storage-plus = "0.14" diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index 26b23d075..2c49fb1d8 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -14,7 +14,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -mars-outpost = { version = "0.1", path = "../../../outposts/packages/mars-outpost" } +mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } cosmwasm-std = "1.0" cw-storage-plus = "0.14" diff --git a/contracts/mock-vault/Cargo.toml b/contracts/mock-vault/Cargo.toml new file mode 100644 index 000000000..9a2537773 --- /dev/null +++ b/contracts/mock-vault/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "mock-vault" +version = "1.0.0" +authors = ["larry_0x ", "grod220 "] +edition = "2021" +license = "GPL-3.0-or-later" +repository = "https://github.com/mars-protocol/rover" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +rover = { version = "0.1", path = "../../packages/rover" } + +cosmwasm-std = "1.0" +cw-storage-plus = "0.14" +schemars = "0.8" +serde = { version = "1.0", default-features = false, features = ["derive"] } +thiserror = "1.0" diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs new file mode 100644 index 000000000..c54a4751c --- /dev/null +++ b/contracts/mock-vault/src/contract.rs @@ -0,0 +1,56 @@ +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, +}; + +use rover::msg::vault::{ExecuteMsg, QueryMsg}; + +use crate::deposit::deposit; +use crate::error::ContractError; +use crate::msg::InstantiateMsg; +use crate::query::{query_coins_for_shares, query_vault_info}; +use crate::state::{ASSETS, CHAIN_BANK, LOCKUP_TIME, LP_TOKEN_DENOM, ORACLE}; + +pub const STARTING_VAULT_SHARES: Uint128 = Uint128::new(1_000_000); + +/// cw-multi-test does not yet have the ability to mint sdk coins. For this reason, +/// this contract expects to be pre-funded with vault tokens and it will simulate the mint. +pub const DEFAULT_VAULT_TOKEN_PREFUND: Uint128 = Uint128::new(1_000_000_000); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + for denom in msg.asset_denoms { + ASSETS.save(deps.storage, denom, &Uint128::zero())?; + } + LOCKUP_TIME.save(deps.storage, &msg.lockup)?; + ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; + LP_TOKEN_DENOM.save(deps.storage, &msg.lp_token_denom)?; + CHAIN_BANK.save(deps.storage, &DEFAULT_VAULT_TOKEN_PREFUND)?; + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Deposit {} => deposit(deps, info), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Info => to_binary(&query_vault_info(deps)?), + QueryMsg::PreviewRedeem { shares } => { + to_binary(&query_coins_for_shares(deps.storage, shares)?) + } + } +} diff --git a/contracts/mock-vault/src/deposit.rs b/contracts/mock-vault/src/deposit.rs new file mode 100644 index 000000000..415ec510b --- /dev/null +++ b/contracts/mock-vault/src/deposit.rs @@ -0,0 +1,57 @@ +use cosmwasm_std::{BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdResult, Uint128}; + +use crate::contract::STARTING_VAULT_SHARES; +use crate::error::ContractError; +use crate::query::get_all_vault_coins; +use crate::state::{ASSETS, CHAIN_BANK, LP_TOKEN_DENOM, ORACLE, TOTAL_VAULT_SHARES}; + +pub fn deposit(deps: DepsMut, info: MessageInfo) -> Result { + let total_shares_opt = TOTAL_VAULT_SHARES.may_load(deps.storage)?; + let oracle = ORACLE.load(deps.storage)?; + let all_vault_assets = get_all_vault_coins(deps.storage)?; + + let shares_to_add = match total_shares_opt { + None => { + TOTAL_VAULT_SHARES.save(deps.storage, &STARTING_VAULT_SHARES)?; + STARTING_VAULT_SHARES + } + Some(total_shares) => { + let total_vault_value = oracle.query_total_value(&deps.querier, &all_vault_assets)?; + let assets_value = oracle.query_total_value(&deps.querier, &info.funds)?; + let shares_to_add = total_shares + .checked_multiply_ratio(assets_value.atomics(), total_vault_value.atomics())?; + TOTAL_VAULT_SHARES.save(deps.storage, &(total_shares + shares_to_add))?; + shares_to_add + } + }; + + info.funds.iter().try_for_each(|asset| -> StdResult<()> { + ASSETS.update( + deps.storage, + asset.clone().denom, + |current_amount| -> StdResult<_> { + Ok(current_amount.unwrap_or(Uint128::zero()) + asset.amount) + }, + )?; + Ok(()) + })?; + + // Send vault tokens to + let minted = mock_lp_token_mint(deps, shares_to_add)?; + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.to_string(), + amount: vec![minted], + }); + + Ok(Response::new().add_message(transfer_msg)) +} + +fn mock_lp_token_mint(deps: DepsMut, amount: Uint128) -> StdResult { + let denom = LP_TOKEN_DENOM.load(deps.storage)?; + + CHAIN_BANK.update(deps.storage, |bank_amount| -> StdResult<_> { + Ok(bank_amount - amount) + })?; + + Ok(Coin { denom, amount }) +} diff --git a/contracts/mock-vault/src/error.rs b/contracts/mock-vault/src/error.rs new file mode 100644 index 000000000..22cfa47d4 --- /dev/null +++ b/contracts/mock-vault/src/error.rs @@ -0,0 +1,15 @@ +use cosmwasm_std::{CheckedMultiplyRatioError, StdError}; +use rover::error::ContractError as RoverError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + RoverError(#[from] RoverError), + + #[error("{0}")] + CheckedMultiply(#[from] CheckedMultiplyRatioError), +} diff --git a/contracts/mock-vault/src/lib.rs b/contracts/mock-vault/src/lib.rs new file mode 100644 index 000000000..7473747d8 --- /dev/null +++ b/contracts/mock-vault/src/lib.rs @@ -0,0 +1,6 @@ +pub mod contract; +pub mod deposit; +pub mod error; +pub mod msg; +pub mod query; +pub mod state; diff --git a/contracts/mock-vault/src/msg.rs b/contracts/mock-vault/src/msg.rs new file mode 100644 index 000000000..97b4aa4ca --- /dev/null +++ b/contracts/mock-vault/src/msg.rs @@ -0,0 +1,16 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use rover::adapters::OracleUnchecked; + +// Remaining messages in packages/rover/msg/vault +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct InstantiateMsg { + /// Denom for vault LP share token + pub lp_token_denom: String, + /// Denoms for assets in vault + pub asset_denoms: Vec, + /// Time in seconds for unlock period + pub lockup: Option, + pub oracle: OracleUnchecked, +} diff --git a/contracts/mock-vault/src/query.rs b/contracts/mock-vault/src/query.rs new file mode 100644 index 000000000..a8bd19d8c --- /dev/null +++ b/contracts/mock-vault/src/query.rs @@ -0,0 +1,43 @@ +use cosmwasm_std::{Coin, Deps, Order, StdResult, Storage, Uint128}; + +use rover::msg::vault::VaultInfo; + +use crate::state::{ASSETS, LOCKUP_TIME, LP_TOKEN_DENOM, TOTAL_VAULT_SHARES}; + +pub fn query_coins_for_shares(storage: &dyn Storage, shares: Uint128) -> StdResult> { + let total_shares_opt = TOTAL_VAULT_SHARES.may_load(storage)?; + match total_shares_opt { + None => Ok(vec![]), + Some(total_vault_shares) => { + let all_vault_coins = get_all_vault_coins(storage)?; + let coins_for_shares = all_vault_coins + .iter() + .map(|asset| Coin { + denom: asset.clone().denom, + amount: asset.amount.multiply_ratio(shares, total_vault_shares), + }) + .collect::>(); + Ok(coins_for_shares) + } + } +} + +pub fn query_vault_info(deps: Deps) -> StdResult { + Ok(VaultInfo { + coins: get_all_vault_coins(deps.storage)?, + lockup: LOCKUP_TIME.load(deps.storage)?, + token_denom: LP_TOKEN_DENOM.load(deps.storage)?, + }) +} + +pub fn get_all_vault_coins(storage: &dyn Storage) -> StdResult> { + Ok(ASSETS + .range(storage, None, None, Order::Ascending) + .collect::>>()? + .iter() + .map(|(denom, amount)| Coin { + denom: denom.clone(), + amount: *amount, + }) + .collect::>()) +} diff --git a/contracts/mock-vault/src/state.rs b/contracts/mock-vault/src/state.rs new file mode 100644 index 000000000..7edb77fec --- /dev/null +++ b/contracts/mock-vault/src/state.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::Uint128; +use cw_storage_plus::{Item, Map}; + +use rover::adapters::Oracle; +use rover::Shares; + +pub const LP_TOKEN_DENOM: Item = Item::new("lp_token_denom"); +pub const TOTAL_VAULT_SHARES: Item = Item::new("total_vault_shares"); +pub const LOCKUP_TIME: Item> = Item::new("lockup_time"); +pub const ASSETS: Map = Map::new("assets"); // Denom -> Amount +pub const ORACLE: Item = Item::new("oracle"); + +// Used for mock LP token minting +pub const CHAIN_BANK: Item = Item::new("chain_bank"); diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index ff83002ed..5e50f932a 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -10,8 +10,8 @@ repository = "https://github.com/mars-protocol/rover" doctest = false [dependencies] -mars-health = { version = "0.1", path = "../../../outposts/packages/mars-health" } -mars-outpost = { version = "0.1", path = "../../../outposts/packages/mars-outpost" } +mars-health = { version = "0.1", path = "../../../outposts/packages/health" } +mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } diff --git a/packages/rover/src/adapters/mod.rs b/packages/rover/src/adapters/mod.rs index bce05757a..68223958c 100644 --- a/packages/rover/src/adapters/mod.rs +++ b/packages/rover/src/adapters/mod.rs @@ -1,5 +1,7 @@ mod oracle; mod red_bank; +mod vault; pub use self::oracle::*; pub use self::red_bank::*; +pub use self::vault::*; diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs index 0829a4d65..fbd0765cb 100644 --- a/packages/rover/src/adapters/oracle.rs +++ b/packages/rover/src/adapters/oracle.rs @@ -1,8 +1,10 @@ -use cosmwasm_std::{Addr, Api, QuerierWrapper, StdResult}; +use cosmwasm_std::{Addr, Api, Coin, Decimal, QuerierWrapper, StdResult}; use mars_outpost::oracle::PriceResponse; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::ops::Add; +use crate::error::ContractResult; use mock_oracle::msg::QueryMsg; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] @@ -42,4 +44,23 @@ impl Oracle { }, ) } + + pub fn query_total_value( + &self, + querier: &QuerierWrapper, + coins: &[Coin], + ) -> ContractResult { + Ok(coins + .iter() + .map(|coin| { + let res = self.query_price(querier, &coin.denom)?; + let asset_amount_dec = Decimal::from_atomics(coin.amount, 0)?; + Ok(res.price.checked_mul(asset_amount_dec)?) + }) + .collect::>>()? + .iter() + .fold(Decimal::zero(), |total_value, amount| { + total_value.add(amount) + })) + } } diff --git a/packages/rover/src/adapters/vault.rs b/packages/rover/src/adapters/vault.rs new file mode 100644 index 000000000..3226b9d22 --- /dev/null +++ b/packages/rover/src/adapters/vault.rs @@ -0,0 +1,112 @@ +use cosmwasm_std::{ + to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, Decimal, QuerierWrapper, + QueryRequest, StdResult, Uint128, WasmMsg, WasmQuery, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::adapters::Oracle; +use crate::error::ContractResult; +use crate::extensions::Stringify; +use crate::msg::vault::{ExecuteMsg, QueryMsg, VaultInfo}; +use crate::Shares; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)] +#[serde(rename_all = "snake_case")] +pub struct VaultPosition { + pub unlocked: Uint128, + pub locked: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct VaultBase(T); + +impl VaultBase { + pub fn new(address: T) -> VaultBase { + VaultBase(address) + } + + pub fn address(&self) -> &T { + &self.0 + } +} + +pub type VaultUnchecked = VaultBase; +pub type Vault = VaultBase; + +impl From<&Vault> for VaultUnchecked { + fn from(vault: &Vault) -> Self { + Self(vault.address().to_string()) + } +} + +impl VaultUnchecked { + pub fn check(&self, api: &dyn Api) -> StdResult { + Ok(VaultBase(api.addr_validate(&self.0)?)) + } +} + +impl From for VaultUnchecked { + fn from(v: Vault) -> Self { + Self(v.0.to_string()) + } +} + +impl Stringify for Vec { + fn to_string(&self) -> String { + self.iter() + .map(|v| v.address().clone()) + .collect::>() + .join(", ") + } +} + +impl Vault { + pub fn deposit_msg(&self, funds: &[Coin]) -> StdResult { + let deposit_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address().to_string(), + funds: funds.to_vec(), + msg: to_binary(&ExecuteMsg::Deposit {})?, + }); + Ok(deposit_msg) + } + + pub fn query_vault_info(&self, querier: &QuerierWrapper) -> StdResult { + querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.0.to_string(), + msg: to_binary(&QueryMsg::Info {})?, + })) + } + + pub fn query_balance(&self, querier: &QuerierWrapper, addr: &Addr) -> StdResult { + let vault_info = self.query_vault_info(querier)?; + let res: BalanceResponse = querier.query(&QueryRequest::Bank(BankQuery::Balance { + address: addr.to_string(), + denom: vault_info.token_denom, + }))?; + Ok(res.amount.amount) + } + + pub fn query_total_value( + &self, + querier: &QuerierWrapper, + oracle: &Oracle, + addr: &Addr, + ) -> ContractResult { + let balance = self.query_balance(querier, addr)?; + let assets = self.query_redeem_preview(querier, balance)?; + oracle.query_total_value(querier, &assets) + } + + pub fn query_redeem_preview( + &self, + querier: &QuerierWrapper, + shares: Shares, + ) -> StdResult> { + let response: Vec = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.0.to_string(), + msg: to_binary(&QueryMsg::PreviewRedeem { shares })?, + }))?; + Ok(response) + } +} diff --git a/packages/rover/src/coins.rs b/packages/rover/src/coins.rs index 524bff105..c3b798708 100644 --- a/packages/rover/src/coins.rs +++ b/packages/rover/src/coins.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use std::fmt; +use crate::extensions::Stringify; use cosmwasm_std::{Coin, StdError, StdResult, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -26,6 +27,15 @@ impl From<&[Coin]> for Coins { } } +impl Stringify for &[Coin] { + fn to_string(&self) -> String { + self.iter() + .map(|coin| coin.clone().denom) + .collect::>() + .join(", ") + } +} + impl Coins { pub fn is_empty(&self) -> bool { self.0.is_empty() diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index f9f20278d..5aac13f2f 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -46,9 +46,15 @@ pub enum ContractError { #[error("{0} is not whitelisted")] NotWhitelisted(String), + #[error("Expected vault coins in exchange for deposit, but none were sent")] + NoVaultCoinsReceived, + #[error("{0}")] Overflow(#[from] OverflowError), + #[error("{0}")] + RequirementsNotMet(String), + #[error("{0}")] Std(#[from] StdError), diff --git a/packages/rover/src/extensions/mod.rs b/packages/rover/src/extensions/mod.rs new file mode 100644 index 000000000..f424c4f61 --- /dev/null +++ b/packages/rover/src/extensions/mod.rs @@ -0,0 +1,3 @@ +pub trait Stringify { + fn to_string(&self) -> String; +} diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index bf1c3b729..ca399be4c 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -1,10 +1,12 @@ -use cosmwasm_std::Uint128; +use cosmwasm_std::{Addr, Uint128}; pub mod adapters; pub mod coins; pub mod error; +pub mod extensions; pub mod msg; pub type Denom<'a> = &'a str; pub type NftTokenId<'a> = &'a str; pub type Shares = Uint128; +pub type VaultAddr = Addr; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 7f3fb740f..91dd9135d 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,7 +1,8 @@ -use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, StdResult, WasmMsg}; +use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, StdResult, Uint128, WasmMsg}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::adapters::{Vault, VaultUnchecked}; use crate::msg::instantiate::ConfigUpdates; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -39,6 +40,11 @@ pub enum Action { Borrow(Coin), /// Repay coin of specified amount back to Red Bank Repay(Coin), + /// Deposit coins into vault strategy + VaultDeposit { + vault: VaultUnchecked, + coins: Vec, + }, } /// Internal actions made by the contract with pre-validated inputs @@ -53,14 +59,28 @@ pub enum CallbackMsg { recipient: Addr, }, /// Borrow specified amount of coin from Red Bank; - /// Increase the token's asset amount and debt shares; + /// Increase the token's coin amount and debt shares; Borrow { token_id: String, coin: Coin }, /// Repay coin of specified amount back to Red Bank; - /// Decrement the token's asset amount and debt shares; + /// Decrement the token's coin amount and debt shares; Repay { token_id: String, coin: Coin }, /// Calculate the account's max loan-to-value health factor. If above 1, /// emits a `position_changed` event. If 1 or below, raises an error. AssertBelowMaxLTV { token_id: String }, + /// Adds list of coins to a vault strategy + VaultDeposit { + token_id: String, + vault: Vault, + coins: Vec, + }, + /// Used to update the account balance of vault coins after a deposit + UpdateVaultCoinBalance { + vault: Vault, + /// Account that needs vault coin balance adjustment + token_id: String, + /// Total vault coin balance in Rover + previous_total_balance: Uint128, + }, } impl CallbackMsg { diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index cdb9cf63d..eac7bb989 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::adapters::{OracleUnchecked, RedBankUnchecked}; +use crate::adapters::{OracleUnchecked, RedBankUnchecked, VaultUnchecked}; #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] pub struct InstantiateMsg { @@ -10,7 +10,7 @@ pub struct InstantiateMsg { /// Whitelisted coin denoms approved by governance pub allowed_coins: Vec, /// Whitelisted vaults approved by governance that implement credit manager's vault interface - pub allowed_vaults: Vec, + pub allowed_vaults: Vec, /// The Mars Protocol money market contract where we borrow assets from pub red_bank: RedBankUnchecked, /// The Mars Protocol oracle contract. We read prices of assets here. @@ -23,7 +23,7 @@ pub struct ConfigUpdates { pub account_nft: Option, pub owner: Option, pub allowed_coins: Option>, - pub allowed_vaults: Option>, + pub allowed_vaults: Option>, pub red_bank: Option, pub oracle: Option, } diff --git a/packages/rover/src/msg/mod.rs b/packages/rover/src/msg/mod.rs index 50cb8a531..f3358abc2 100644 --- a/packages/rover/src/msg/mod.rs +++ b/packages/rover/src/msg/mod.rs @@ -1,6 +1,7 @@ pub mod execute; pub mod instantiate; pub mod query; +pub mod vault; pub use execute::ExecuteMsg; pub use instantiate::InstantiateMsg; diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index c3e25690f..4a1ba4e38 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -3,14 +3,16 @@ use mars_health::health::Health; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::adapters::{VaultPosition, VaultUnchecked}; + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { /// Owner & account nft address. Response type: `ConfigResponse` Config, - /// Whitelisted vaults. Response type: `Vec` + /// Whitelisted vaults. Response type: `Vec` AllowedVaults { - start_after: Option, + start_after: Option, limit: Option, }, /// Whitelisted coins. Response type: `Vec` @@ -42,6 +44,20 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, + /// Enumerate all vault positions. Response type: `Vec` + /// start_after accepts (token_id, addr) + AllVaultPositions { + start_after: Option<(String, String)>, + limit: Option, + }, + /// Get total vault coin balance in Rover for vault `Uint128` + TotalVaultCoinBalance { vault: VaultUnchecked }, + /// Enumerate all total vault coin balances. Response type: `Vec` + /// start_after accepts vault addr + AllTotalVaultCoinBalances { + start_after: Option, + limit: Option, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] @@ -96,6 +112,29 @@ pub struct Positions { pub token_id: String, pub coins: Vec, pub debt: Vec, + pub vault_positions: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct VaultPositionResponseItem { + pub token_id: String, + pub addr: String, + pub vault_position: VaultPosition, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct VaultWithBalance { + pub vault: VaultUnchecked, + pub balance: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct VaultPositionWithAddr { + pub addr: String, + pub position: VaultPosition, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] @@ -107,6 +146,9 @@ pub struct PositionsWithValueResponse { pub coins: Vec, /// All debt positions with value pub debt: Vec, + // TODO: After pricing method is complete, add to response + /// All vault positions + pub vault_positions: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] diff --git a/packages/rover/src/msg/vault.rs b/packages/rover/src/msg/vault.rs new file mode 100644 index 000000000..fa90e3b0f --- /dev/null +++ b/packages/rover/src/msg/vault.rs @@ -0,0 +1,34 @@ +use cosmwasm_std::{Coin, Uint128}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Partial compatibility with EIP-4626 +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + /// Enters list of `Vec` into a vault strategy in exchange for vault tokens. + Deposit, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + /// Returns `VaultInfo` representing vault requirements, lockup, & vault token denom + Info, + /// Returns `Vec` representing all the coins that would be redeemed for in exchange for + /// vault coins. Used by Rover to calculate vault position values. + PreviewRedeem { shares: Uint128 }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct VaultInfo { + /// Coins required to enter vault. + /// Amount will be proportional to the share of which it should occupy in the group + /// (e.g. { denom: osmo, amount: 1 }, { denom: atom, amount: 1 } indicate a 50-50 split) + pub coins: Vec, + /// Time in seconds for unlock period + pub lockup: Option, + /// Denom of vault token + pub token_denom: String, +} From 488ff827af83f5bdfcf22e5a03be4b9c617b5e07 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 15 Sep 2022 09:58:17 +0200 Subject: [PATCH 053/218] Coin Liquidation action (#14) * Coin Liquidation action * Simplified liquidation method implemented --- contracts/credit-manager/src/execute.rs | 48 +- contracts/credit-manager/src/health.rs | 11 +- contracts/credit-manager/src/instantiate.rs | 6 +- contracts/credit-manager/src/lib.rs | 1 + contracts/credit-manager/src/liquidate.rs | 165 ++++ contracts/credit-manager/src/query.rs | 6 +- contracts/credit-manager/src/repay.rs | 64 +- contracts/credit-manager/src/state.rs | 4 +- contracts/credit-manager/src/utils.rs | 10 + .../credit-manager/tests/helpers/mock_env.rs | 30 +- contracts/credit-manager/tests/test_borrow.rs | 8 +- contracts/credit-manager/tests/test_health.rs | 24 +- .../credit-manager/tests/test_instantiate.rs | 16 + .../tests/test_liquidate_coin.rs | 711 ++++++++++++++++++ .../tests/test_update_config.rs | 29 +- .../credit-manager/tests/test_withdraw.rs | 8 +- packages/rover/src/coins.rs | 3 +- packages/rover/src/error.rs | 23 +- packages/rover/src/msg/execute.rs | 31 +- packages/rover/src/msg/instantiate.rs | 7 + packages/rover/src/msg/query.rs | 2 + 21 files changed, 1164 insertions(+), 43 deletions(-) create mode 100644 contracts/credit-manager/src/liquidate.rs create mode 100644 contracts/credit-manager/tests/test_liquidate_coin.rs diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index e78e8bbd1..b2c371216 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -7,9 +7,14 @@ use cw721_base::QueryMsg; use crate::borrow::borrow; use crate::deposit::deposit; use crate::health::assert_below_max_ltv; +use crate::liquidate::{assert_health_factor_improved, liquidate_coin}; use crate::repay::repay; -use crate::state::{ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, ORACLE, OWNER, RED_BANK}; +use crate::state::{ + ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, + OWNER, RED_BANK, +}; use crate::vault::{deposit_into_vault, update_vault_coin_balance}; + use crate::withdraw::withdraw; use account_nft::msg::ExecuteMsg as NftExecuteMsg; use rover::coins::Coins; @@ -109,6 +114,20 @@ pub fn update_config( .add_attribute("value", unchecked.address()); } + if let Some(bonus) = new_config.max_liquidation_bonus { + MAX_LIQUIDATION_BONUS.save(deps.storage, &bonus)?; + response = response + .add_attribute("key", "max_liquidation_bonus") + .add_attribute("value", bonus.to_string()); + } + + if let Some(cf) = new_config.max_close_factor { + MAX_CLOSE_FACTOR.save(deps.storage, &cf)?; + response = response + .add_attribute("key", "max_close_factor") + .add_attribute("value", cf.to_string()); + } + Ok(response) } @@ -151,6 +170,16 @@ pub fn dispatch_actions( vault: vault.check(deps.api)?, coins: assets.clone(), }), + Action::LiquidateCoin { + liquidatee_token_id, + debt_coin, + request_coin_denom, + } => callbacks.push(CallbackMsg::LiquidateCoin { + liquidator_token_id: token_id.to_string(), + liquidatee_token_id: liquidatee_token_id.to_string(), + debt_coin: debt_coin.clone(), + request_coin_denom: request_coin_denom.clone(), + }), } } @@ -211,6 +240,23 @@ pub fn execute_callback( previous_total_balance, &env.contract.address, ), + CallbackMsg::LiquidateCoin { + liquidator_token_id, + liquidatee_token_id, + debt_coin, + request_coin_denom, + } => liquidate_coin( + deps, + env, + &liquidator_token_id, + &liquidatee_token_id, + debt_coin, + &request_coin_denom, + ), + CallbackMsg::AssertHealthFactorImproved { + token_id, + previous_health_factor, + } => assert_health_factor_improved(deps.as_ref(), env, &token_id, previous_health_factor), } } diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index cc6fc8266..ac25993a5 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -37,7 +37,10 @@ pub fn assert_below_max_ltv( let health = compute_health(deps, &env, token_id)?; if health.is_above_max_ltv() { - return Err(ContractError::AboveMaxLTV); + return Err(ContractError::AboveMaxLTV { + token_id: token_id.to_string(), + max_ltv_health_factor: val_or_na(health.max_ltv_health_factor), + }); } let event = Event::new("position_changed") @@ -48,12 +51,12 @@ pub fn assert_below_max_ltv( .add_attribute("debts_value", health.total_debt_value.to_string()) .add_attribute( "lqdt_health_factor", - val_or_not_applicable(health.liquidation_health_factor), + val_or_na(health.liquidation_health_factor), ) .add_attribute("liquidatable", health.is_liquidatable().to_string()) .add_attribute( "max_ltv_health_factor", - val_or_not_applicable(health.max_ltv_health_factor), + val_or_na(health.max_ltv_health_factor), ) .add_attribute("above_max_ltv", health.is_above_max_ltv().to_string()); @@ -62,6 +65,6 @@ pub fn assert_below_max_ltv( .add_event(event)) } -fn val_or_not_applicable(opt: Option) -> String { +pub fn val_or_na(opt: Option) -> String { opt.map_or_else(|| "n/a".to_string(), |dec| dec.to_string()) } diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index ff57200ae..c1c9c771a 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,13 +1,17 @@ use cosmwasm_std::{DepsMut, Empty, StdResult}; use rover::msg::InstantiateMsg; -use crate::state::{ALLOWED_COINS, ALLOWED_VAULTS, ORACLE, OWNER, RED_BANK}; +use crate::state::{ + ALLOWED_COINS, ALLOWED_VAULTS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, +}; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { let owner = deps.api.addr_validate(&msg.owner)?; OWNER.save(deps.storage, &owner)?; RED_BANK.save(deps.storage, &msg.red_bank.check(deps.api)?)?; ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; + MAX_LIQUIDATION_BONUS.save(deps.storage, &msg.max_liquidation_bonus)?; + MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; msg.allowed_vaults.iter().try_for_each(|unchecked| { let vault = unchecked.check(deps.api)?; diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 23510523b..6deb04c7b 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -5,6 +5,7 @@ pub mod deposit; pub mod execute; pub mod health; pub mod instantiate; +pub mod liquidate; pub mod query; pub mod repay; pub mod state; diff --git a/contracts/credit-manager/src/liquidate.rs b/contracts/credit-manager/src/liquidate.rs new file mode 100644 index 000000000..c8461921d --- /dev/null +++ b/contracts/credit-manager/src/liquidate.rs @@ -0,0 +1,165 @@ +use std::ops::{Add, Div}; + +use cosmwasm_std::{Coin, Decimal, Deps, DepsMut, Env, Response, StdError}; +use mars_health::health::Health; + +use rover::error::{ContractError, ContractResult}; +use rover::msg::execute::CallbackMsg; +use rover::NftTokenId; + +use crate::health::{compute_health, val_or_na}; +use crate::repay::current_debt_for_denom; +use crate::state::{COIN_BALANCES, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE}; +use crate::utils::{decrement_coin_balance, increment_coin_balance, IntoUint128}; + +pub fn liquidate_coin( + deps: DepsMut, + env: Env, + liquidator_token_id: &str, + liquidatee_token_id: &str, + debt_coin: Coin, + request_coin_denom: &str, +) -> ContractResult { + // Assert the liquidatee's credit account is liquidatable + let health = compute_health(deps.as_ref(), &env, liquidatee_token_id)?; + if !health.is_liquidatable() { + return Err(ContractError::NotLiquidatable { + token_id: liquidatee_token_id.to_string(), + lqdt_health_factor: val_or_na(health.liquidation_health_factor), + }); + } + + let (debt, request) = calculate_liquidation_request( + &deps, + &env, + liquidatee_token_id, + &debt_coin, + request_coin_denom, + &health, + )?; + + // Transfer debt coin from liquidator's coin balance to liquidatee + // Will be used to pay off the debt via CallbackMsg::Repay {} + decrement_coin_balance(deps.storage, liquidator_token_id, &debt)?; + increment_coin_balance(deps.storage, liquidatee_token_id, &debt)?; + let repay_msg = (CallbackMsg::Repay { + token_id: liquidatee_token_id.to_string(), + coin: debt.clone(), + }) + .into_cosmos_msg(&env.contract.address)?; + + // Transfer requested coin from liquidatee to liquidator + decrement_coin_balance(deps.storage, liquidatee_token_id, &request)?; + increment_coin_balance(deps.storage, liquidator_token_id, &request)?; + + // Ensure health factor has improved as a consequence of liquidation event + let assert_healthier_msg = (CallbackMsg::AssertHealthFactorImproved { + token_id: liquidatee_token_id.to_string(), + previous_health_factor: health.liquidation_health_factor.unwrap(), // safe unwrap given it was liquidatable + }) + .into_cosmos_msg(&env.contract.address)?; + + Ok(Response::new() + .add_message(repay_msg) + .add_message(assert_healthier_msg) + .add_attribute("action", "rover/credit_manager/liquidate") + .add_attribute("liquidatee_token_id", liquidatee_token_id) + .add_attribute("debt_repaid_denom", debt.denom) + .add_attribute("debt_repaid_amount", debt.amount) + .add_attribute("request_coin_denom", request.denom) + .add_attribute("request_coin_amount", request.amount)) +} + +/// Calculates precise debt & request coin amounts to liquidate +/// The debt amount will be adjusted down if: +/// - Exceeds liquidatee's total debt for denom +/// - Not enough liquidatee request coin balance to match +/// - The value of the debt repaid exceeds the maximum close factor % +fn calculate_liquidation_request( + deps: &DepsMut, + env: &Env, + liquidatee_token_id: &str, + debt_coin: &Coin, + request_coin: &str, + health: &Health, +) -> ContractResult<(Coin, Coin)> { + // Ensure debt repaid does not exceed liquidatee's total debt for denom + let (total_debt_amount, _) = + current_debt_for_denom(deps.as_ref(), env, liquidatee_token_id, debt_coin)?; + + // Ensure debt amount does not exceed close factor % of the liquidatee's total debt value + let close_factor = MAX_CLOSE_FACTOR.load(deps.storage)?; + let max_close_value = close_factor.checked_mul(health.total_debt_value)?; + let oracle = ORACLE.load(deps.storage)?; + let debt_res = oracle.query_price(&deps.querier, &debt_coin.denom)?; + let max_close_amount = max_close_value.div(debt_res.price).uint128(); + + // Calculate the maximum debt possible to repay given liquidatee's request coin balance + // FORMULA: debt amount = request value / (1 + liquidation bonus %) / debt price + let liquidatee_balance = COIN_BALANCES + .load(deps.storage, (liquidatee_token_id, request_coin)) + .map_err(|_| ContractError::CoinNotAvailable(request_coin.to_string()))?; + let request_res = oracle.query_price(&deps.querier, request_coin)?; + let balance_dec = Decimal::from_atomics(liquidatee_balance, 0)?; + let max_request_value = request_res.price.checked_mul(balance_dec)?; + let liq_bonus_rate = MAX_LIQUIDATION_BONUS.load(deps.storage)?; + let request_coin_adjusted_max_debt = max_request_value + .div(liq_bonus_rate.add(Decimal::one())) + .div(debt_res.price) + .uint128(); + + let final_debt_to_repay = *vec![ + debt_coin.amount, + total_debt_amount, + max_close_amount, + request_coin_adjusted_max_debt, + ] + .iter() + .min() + .ok_or_else(|| StdError::generic_err("Minimum not found"))?; + + // Calculate exact request coin amount to give to liquidator + // FORMULA: request amount = (1 + liquidation bonus %) * debt value / request coin price + let debt_amount_dec = Decimal::from_atomics(final_debt_to_repay, 0)?; + let request_amount = liq_bonus_rate + .add(Decimal::one()) + .checked_mul(debt_res.price.checked_mul(debt_amount_dec)?)? + .div(request_res.price) + .uint128(); + + // (Debt Coin, Request Coin) + Ok(( + Coin { + denom: debt_coin.denom.clone(), + amount: final_debt_to_repay, + }, + Coin { + denom: request_coin.to_string(), + amount: request_amount, + }, + )) +} + +pub fn assert_health_factor_improved( + deps: Deps, + env: Env, + token_id: NftTokenId, + prev_hf: Decimal, +) -> ContractResult { + let health = compute_health(deps, &env, token_id)?; + if let Some(hf) = health.liquidation_health_factor { + if prev_hf > hf { + return Err(ContractError::HealthNotImproved { + prev_hf: prev_hf.to_string(), + new_hf: hf.to_string(), + }); + } + } + Ok(Response::new() + .add_attribute( + "action", + "rover/credit_manager/assert_health_factor_improved", + ) + .add_attribute("prev_hf", prev_hf.to_string()) + .add_attribute("new_hf", val_or_na(health.liquidation_health_factor))) +} diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index e33f7634f..3298b7031 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -11,8 +11,8 @@ use rover::msg::query::{ use rover::{Denom, NftTokenId}; use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, COIN_BALANCES, DEBT_SHARES, ORACLE, OWNER, - RED_BANK, TOTAL_DEBT_SHARES, VAULT_POSITIONS, + ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, + MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, TOTAL_DEBT_SHARES, VAULT_POSITIONS, }; use crate::utils::{coin_value, debt_shares_to_amount}; @@ -27,6 +27,8 @@ pub fn query_config(deps: Deps) -> StdResult { .map(|addr| addr.to_string()), red_bank: RED_BANK.load(deps.storage)?.address().into(), oracle: ORACLE.load(deps.storage)?.address().into(), + max_liquidation_bonus: MAX_LIQUIDATION_BONUS.load(deps.storage)?, + max_close_factor: MAX_CLOSE_FACTOR.load(deps.storage)?, }) } diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index 87905f997..9c93ea21d 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -1,11 +1,12 @@ use std::cmp::min; -use cosmwasm_std::{Coin, DepsMut, Env, Response}; +use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; use rover::error::{ContractError, ContractResult}; +use rover::Shares; use crate::state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; -use crate::utils::{assert_coin_is_whitelisted, decrement_coin_balance}; +use crate::utils::{assert_coin_is_whitelisted, debt_shares_to_amount, decrement_coin_balance}; pub fn repay(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractResult { if coin.amount.is_zero() { @@ -14,38 +15,31 @@ pub fn repay(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRes assert_coin_is_whitelisted(deps.storage, &coin)?; - let red_bank = RED_BANK.load(deps.storage)?; - let total_debt_amount = - red_bank.query_debt(&deps.querier, &env.contract.address, &coin.denom)?; - - // Calculate how many shares user is attempting to pay back - let total_debt_shares = TOTAL_DEBT_SHARES.load(deps.storage, &coin.denom)?; - let debt_shares_to_decrement = - total_debt_shares.checked_multiply_ratio(coin.amount, total_debt_amount)?; - - // Payback amount should not exceed token's current debt - let current_debt = DEBT_SHARES - .load(deps.storage, (token_id, &coin.denom)) - .map_err(|_| ContractError::NoDebt)?; - let shares_to_repay = min(current_debt, debt_shares_to_decrement); - let amount_to_repay = if current_debt > debt_shares_to_decrement { - coin.amount - } else { - total_debt_amount.checked_multiply_ratio(current_debt, total_debt_shares)? - }; + // Ensure repayment does not exceed max debt on account + let (debt_amount, debt_shares) = current_debt_for_denom(deps.as_ref(), &env, token_id, &coin)?; + let amount_to_repay = min(debt_amount, coin.amount); + let shares_to_repay = debt_amount_to_shares( + deps.as_ref(), + &env, + &Coin { + denom: coin.denom.clone(), + amount: amount_to_repay, + }, + )?; // Decrement token's debt position - if shares_to_repay >= current_debt { + if amount_to_repay == debt_amount { DEBT_SHARES.remove(deps.storage, (token_id, &coin.denom)); } else { DEBT_SHARES.save( deps.storage, (token_id, &coin.denom), - ¤t_debt.checked_sub(shares_to_repay)?, + &debt_shares.checked_sub(shares_to_repay)?, )?; } // Decrement total debt shares for coin + let total_debt_shares = TOTAL_DEBT_SHARES.load(deps.storage, &coin.denom)?; TOTAL_DEBT_SHARES.save( deps.storage, &coin.denom, @@ -61,6 +55,7 @@ pub fn repay(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRes }, )?; + let red_bank = RED_BANK.load(deps.storage)?; let red_bank_repay_msg = red_bank.repay_msg(&Coin { denom: coin.denom, amount: amount_to_repay, @@ -72,3 +67,26 @@ pub fn repay(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRes .add_attribute("debt_shares_repaid", shares_to_repay) .add_attribute("coins_repaid", amount_to_repay)) } + +fn debt_amount_to_shares(deps: Deps, env: &Env, coin: &Coin) -> ContractResult { + let red_bank = RED_BANK.load(deps.storage)?; + let total_debt_shares = TOTAL_DEBT_SHARES.load(deps.storage, &coin.denom)?; + let total_debt_amount = + red_bank.query_debt(&deps.querier, &env.contract.address, &coin.denom)?; + let shares = total_debt_shares.checked_multiply_ratio(coin.amount, total_debt_amount)?; + Ok(shares) +} + +/// Get token's current total debt for denom +pub fn current_debt_for_denom( + deps: Deps, + env: &Env, + token_id: &str, + coin: &Coin, +) -> ContractResult<(Uint128, Shares)> { + let debt_shares = DEBT_SHARES + .load(deps.storage, (token_id, &coin.denom)) + .map_err(|_| ContractError::NoDebt)?; + let coin = debt_shares_to_amount(deps, &env.contract.address, &coin.denom, debt_shares)?; + Ok((coin.amount, debt_shares)) +} diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 9073cdecc..a59963381 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Empty, Uint128}; +use cosmwasm_std::{Addr, Decimal, Empty, Uint128}; use cw_storage_plus::{Item, Map}; use rover::adapters::{Oracle, RedBank, VaultPosition}; @@ -11,6 +11,8 @@ pub const ALLOWED_COINS: Map = Map::new("allowed_coins"); pub const ALLOWED_VAULTS: Map<&Addr, Empty> = Map::new("allowed_vaults"); pub const RED_BANK: Item = Item::new("red_bank"); pub const ORACLE: Item = Item::new("oracle"); +pub const MAX_LIQUIDATION_BONUS: Item = Item::new("max_liquidation_bonus"); +pub const MAX_CLOSE_FACTOR: Item = Item::new("max_close_factor"); // Positions pub const COIN_BALANCES: Map<(NftTokenId, Denom), Uint128> = Map::new("coin_balance"); diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 158e4bc47..2081f4691 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -102,3 +102,13 @@ pub fn coin_value(deps: &Deps, coin: &Coin) -> ContractResult { value, }) } + +pub trait IntoUint128 { + fn uint128(&self) -> Uint128; +} + +impl IntoUint128 for Decimal { + fn uint128(&self) -> Uint128 { + *self * Uint128::new(1) + } +} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 4d7312715..a164e00be 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -2,7 +2,7 @@ use std::mem::take; use anyhow::Result as AnyResult; use cosmwasm_std::testing::MockApi; -use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; @@ -48,6 +48,8 @@ pub struct MockEnvBuilder { pub deploy_nft_contract: bool, pub set_nft_contract_owner: bool, pub accounts_to_fund: Vec, + pub max_liquidation_bonus: Option, + pub max_close_factor: Option, } #[allow(clippy::new_ret_no_self)] @@ -64,6 +66,8 @@ impl MockEnv { deploy_nft_contract: true, set_nft_contract_owner: true, accounts_to_fund: vec![], + max_liquidation_bonus: None, + max_close_factor: None, } } @@ -428,6 +432,8 @@ impl MockEnvBuilder { .iter() .map(|info| info.denom.clone()) .collect(); + let max_liquidation_bonus = self.get_max_liquidation_bonus(); + let max_close_factor = self.get_max_close_factor(); let mut allowed_vaults = vec![]; allowed_vaults.extend(self.deploy_vaults()); @@ -442,6 +448,8 @@ impl MockEnvBuilder { allowed_vaults, red_bank, oracle, + max_liquidation_bonus, + max_close_factor, }, &[], "mock-rover-contract", @@ -587,6 +595,16 @@ impl MockEnvBuilder { self.allowed_coins.clone().unwrap_or_default() } + fn get_max_liquidation_bonus(&self) -> Decimal { + self.max_liquidation_bonus + .unwrap_or_else(|| Decimal::from_atomics(5u128, 2).unwrap()) // 5% + } + + fn get_max_close_factor(&self) -> Decimal { + self.max_close_factor + .unwrap_or_else(|| Decimal::from_atomics(5u128, 1).unwrap()) // 50% + } + //-------------------------------------------------------------------------------------------------- // Setter functions //-------------------------------------------------------------------------------------------------- @@ -639,6 +657,16 @@ impl MockEnvBuilder { self.pre_deployed_vaults = Some(vaults); self } + + pub fn max_liquidation_bonus(&mut self, bonus: Decimal) -> &mut Self { + self.max_liquidation_bonus = Some(bonus); + self + } + + pub fn max_close_factor(&mut self, cf: Decimal) -> &mut Self { + self.max_close_factor = Some(cf); + self + } } //-------------------------------------------------------------------------------------------------- diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index b98e85bef..9755b4af6 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -117,7 +117,13 @@ fn test_cannot_borrow_above_max_ltv() { &[Coin::new(300u128, coin_info.denom)], ); - assert_err(res, ContractError::AboveMaxLTV); + assert_err( + res, + ContractError::AboveMaxLTV { + token_id, + max_ltv_health_factor: "0.998573466476462196".to_string(), + }, + ); } #[test] diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index 527429158..52510eefe 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -167,7 +167,13 @@ fn test_debts_no_assets() { &[], ); - assert_err(res, ContractError::AboveMaxLTV); + assert_err( + res, + ContractError::AboveMaxLTV { + token_id: token_id.clone(), + max_ltv_health_factor: "0.693069306930693069".to_string(), + }, + ); let position = mock.query_position(&token_id); assert_eq!(position.token_id, token_id); @@ -257,7 +263,13 @@ fn test_cannot_borrow_more_than_healthy() { &[], ); - assert_err(res, ContractError::AboveMaxLTV); + assert_err( + res, + ContractError::AboveMaxLTV { + token_id: token_id.clone(), + max_ltv_health_factor: "0.990099009900990099".to_string(), + }, + ); // All valid on step 2 as well (meaning step 3 did not go through) let health = mock.query_health(&token_id); @@ -345,7 +357,13 @@ fn test_cannot_borrow_more_but_not_liquidatable() { &[], ); - assert_err(res, ContractError::AboveMaxLTV); + assert_err( + res, + ContractError::AboveMaxLTV { + token_id: token_id.clone(), + max_ltv_health_factor: "0.947847222222222222".to_string(), + }, + ); mock.price_change(CoinPrice { denom: uatom_info.denom, diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 2fa73110b..3b0e42020 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -133,3 +133,19 @@ fn test_raises_on_invalid_oracle_addr() { panic!("Should have thrown an error"); } } + +#[test] +fn test_max_liq_bonus_set_on_instantiate() { + let mock = MockEnv::new().build().unwrap(); + let res = mock.query_config(); + let mock_default = Decimal::from_atomics(5u128, 2).unwrap(); + assert_eq!(mock_default, res.max_liquidation_bonus); +} + +#[test] +fn test_max_close_factor_set_on_instantiate() { + let mock = MockEnv::new().build().unwrap(); + let res = mock.query_config(); + let mock_default = Decimal::from_atomics(5u128, 1).unwrap(); + assert_eq!(mock_default, res.max_close_factor); +} diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs new file mode 100644 index 000000000..371321af2 --- /dev/null +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -0,0 +1,711 @@ +use cosmwasm_std::{Addr, Coin, Decimal, OverflowError, OverflowOperation, Uint128}; + +use mock_oracle::msg::CoinPrice; +use rover::error::ContractError; +use rover::error::ContractError::{AboveMaxLTV, NotLiquidatable}; +use rover::msg::execute::Action::{Borrow, Deposit, LiquidateCoin}; +use rover::msg::query::{CoinValue, DebtSharesValue}; + +use crate::helpers::{assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv}; + +pub mod helpers; + +// Reference figures behind various scenarios +// https://docs.google.com/spreadsheets/d/1_Bs1Fc1RLf5IARvaXZ0QjigoMWSJQhhrRUtQ8uyoLdI/edit?pli=1#gid=1857897311 + +#[test] +fn test_can_only_liquidate_unhealthy_accounts() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .build() + .unwrap(); + let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_token_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(Uint128::from(300u128))), + Borrow(uatom_info.to_coin(Uint128::from(50u128))), + ], + &[Coin::new(300, uosmo_info.clone().denom)], + ) + .unwrap(); + + let health = mock.query_health(&liquidatee_token_id); + assert!(!health.liquidatable); + + let liquidator = Addr::unchecked("liquidator"); + let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_token_id, + &liquidator, + vec![LiquidateCoin { + liquidatee_token_id: liquidatee_token_id.clone(), + debt_coin: uatom_info.to_coin(Uint128::from(10u128)), + request_coin_denom: uosmo_info.denom, + }], + &[], + ); + + assert_err( + res, + NotLiquidatable { + token_id: liquidatee_token_id, + lqdt_health_factor: "2.029411764705882352".to_string(), + }, + ) +} + +#[test] +fn test_liquidatee_does_not_have_requested_asset() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + let ujake_info = ujake_info(); + + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .build() + .unwrap(); + let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_token_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(Uint128::from(300u128))), + Borrow(uatom_info.to_coin(Uint128::from(105u128))), + ], + &[Coin::new(300, uosmo_info.denom)], + ) + .unwrap(); + + let health = mock.query_health(&liquidatee_token_id); + assert!(!health.liquidatable); + + mock.price_change(CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(20u128, 0).unwrap(), + }); + + let liquidator = Addr::unchecked("liquidator"); + let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_token_id, + &liquidator, + vec![ + Borrow(uatom_info.to_coin(Uint128::from(50u128))), + LiquidateCoin { + liquidatee_token_id: liquidatee_token_id.clone(), + debt_coin: uatom_info.to_coin(Uint128::from(10u128)), + request_coin_denom: ujake_info.denom.clone(), + }, + ], + &[], + ); + + assert_err(res, ContractError::CoinNotAvailable(ujake_info.denom)) +} + +#[test] +fn test_liquidatee_does_not_have_debt_coin() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + let ujake_info = ujake_info(); + + let liquidatee = Addr::unchecked("liquidatee"); + let random_user = Addr::unchecked("random_user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .fund_account(AccountToFund { + addr: random_user.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .build() + .unwrap(); + let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_token_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(Uint128::from(300u128))), + Borrow(uatom_info.to_coin(Uint128::from(105u128))), + ], + &[Coin::new(300, uosmo_info.denom.clone())], + ) + .unwrap(); + + let health = mock.query_health(&liquidatee_token_id); + assert!(!health.liquidatable); + + // Seeding a jakecoin borrow + let random_user_token = mock.create_credit_account(&random_user).unwrap(); + mock.update_credit_account( + &random_user_token, + &random_user, + vec![ + Deposit(uosmo_info.to_coin(Uint128::from(300u128))), + Borrow(ujake_info.to_coin(Uint128::from(10u128))), + ], + &[Coin::new(300, uosmo_info.denom)], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(20u128, 0).unwrap(), + }); + + let liquidator = Addr::unchecked("liquidator"); + let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_token_id, + &liquidator, + vec![ + Borrow(uatom_info.to_coin(Uint128::from(50u128))), + LiquidateCoin { + liquidatee_token_id: liquidatee_token_id.clone(), + debt_coin: ujake_info.to_coin(Uint128::from(10u128)), + request_coin_denom: uatom_info.denom, + }, + ], + &[], + ); + + assert_err(res, ContractError::NoDebt) +} + +#[test] +fn test_liquidator_does_not_have_enough_to_pay_debt() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .build() + .unwrap(); + let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_token_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(Uint128::from(300u128))), + Borrow(uatom_info.to_coin(Uint128::from(100u128))), + ], + &[Coin::new(300, uosmo_info.clone().denom)], + ) + .unwrap(); + + let health = mock.query_health(&liquidatee_token_id); + assert!(!health.liquidatable); + + mock.price_change(CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(20u128, 0).unwrap(), + }); + + let liquidator = Addr::unchecked("liquidator"); + let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_token_id, + &liquidator, + vec![LiquidateCoin { + liquidatee_token_id: liquidatee_token_id.clone(), + debt_coin: uatom_info.to_coin(Uint128::from(10u128)), + request_coin_denom: uosmo_info.denom, + }], + &[], + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: OverflowOperation::Sub, + operand1: "0".to_string(), + operand2: "3".to_string(), + }), + ) +} + +#[test] +fn test_liquidator_left_in_unhealthy_state() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .build() + .unwrap(); + let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_token_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(Uint128::from(300u128))), + Borrow(uatom_info.to_coin(Uint128::from(100u128))), + ], + &[Coin::new(300, uosmo_info.clone().denom)], + ) + .unwrap(); + + let health = mock.query_health(&liquidatee_token_id); + assert!(!health.liquidatable); + + mock.price_change(CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(20u128, 0).unwrap(), + }); + + let liquidator = Addr::unchecked("liquidator"); + let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_token_id, + &liquidator, + vec![ + Borrow(uatom_info.to_coin(Uint128::from(10u128))), + LiquidateCoin { + liquidatee_token_id: liquidatee_token_id.clone(), + debt_coin: uatom_info.to_coin(Uint128::from(10u128)), + request_coin_denom: uosmo_info.denom, + }, + ], + &[], + ); + + assert_err( + res, + AboveMaxLTV { + token_id: liquidator_token_id, + max_ltv_health_factor: "0.7945".to_string(), + }, + ) +} + +#[test] +fn test_liquidatee_not_healthier_after_liquidation() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + let liquidator = Addr::unchecked("liquidator"); + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + // an absurdly high liquidation bonus + .max_liquidation_bonus(Decimal::from_atomics(8u128, 1).unwrap()) + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: vec![Coin::new(300u128, uatom_info.denom.clone())], + }) + .build() + .unwrap(); + let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_token_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(Uint128::from(300u128))), + Borrow(uatom_info.to_coin(Uint128::from(100u128))), + ], + &[Coin::new(300, uosmo_info.denom.clone())], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(20u128, 0).unwrap(), + }); + + let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_token_id, + &liquidator, + vec![ + Deposit(uatom_info.to_coin(Uint128::from(50u128))), + LiquidateCoin { + liquidatee_token_id: liquidatee_token_id.clone(), + debt_coin: uatom_info.to_coin(Uint128::from(50u128)), + request_coin_denom: uosmo_info.denom, + }, + ], + &[uatom_info.to_coin(Uint128::from(50u128))], + ); + + assert_err( + res, + ContractError::HealthNotImproved { + prev_hf: "0.920049504950495049".to_string(), + new_hf: "0.910272727272727272".to_string(), + }, + ) +} + +#[test] +fn test_debt_amount_adjusted_to_close_factor_max() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + let liquidator = Addr::unchecked("liquidator"); + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .max_close_factor(Decimal::from_atomics(1u128, 1).unwrap()) + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: vec![Coin::new(300u128, uatom_info.denom.clone())], + }) + .build() + .unwrap(); + let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_token_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(Uint128::from(300u128))), + Borrow(uatom_info.to_coin(Uint128::from(100u128))), + ], + &[Coin::new(300, uosmo_info.denom.clone())], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(6u128, 0).unwrap(), + }); + + let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + + mock.update_credit_account( + &liquidator_token_id, + &liquidator, + vec![ + Deposit(uatom_info.to_coin(Uint128::from(50u128))), + LiquidateCoin { + liquidatee_token_id: liquidatee_token_id.clone(), + debt_coin: uatom_info.to_coin(Uint128::from(50u128)), + request_coin_denom: uosmo_info.denom, + }, + ], + &[uatom_info.to_coin(Uint128::from(50u128))], + ) + .unwrap(); + + // Assert liquidatee's new position + let position = mock.query_position(&liquidatee_token_id); + assert_eq!(position.coins.len(), 2); + let osmo_balance = get_coin(&position.coins, "uosmo"); + assert_eq!(osmo_balance.amount, Uint128::new(48)); + let atom_balance = get_coin(&position.coins, "uatom"); + assert_eq!(atom_balance.amount, Uint128::new(100)); + + assert_eq!(position.debt.len(), 1); + let atom_debt = get_debt(&position.debt, "uatom"); + assert_eq!(atom_debt.amount, Uint128::new(91)); + + // Assert liquidator's new position + let position = mock.query_position(&liquidator_token_id); + assert_eq!(position.coins.len(), 2); + assert_eq!(position.debt.len(), 0); + let atom_balance = get_coin(&position.coins, "uatom"); + assert_eq!(atom_balance.amount, Uint128::new(40)); + let osmo_balance = get_coin(&position.coins, "uosmo"); + assert_eq!(osmo_balance.amount, Uint128::new(252)); +} + +#[test] +fn test_debt_amount_adjusted_to_total_debt_for_denom() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + let ujake_info = ujake_info(); + let liquidator = Addr::unchecked("liquidator"); + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .max_close_factor(Decimal::from_atomics(1u128, 1).unwrap()) + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: vec![Coin::new(300u128, ujake_info.denom.clone())], + }) + .build() + .unwrap(); + let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_token_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(Uint128::from(300u128))), + Borrow(uatom_info.to_coin(Uint128::from(100u128))), + Borrow(ujake_info.to_coin(Uint128::from(10u128))), + ], + &[Coin::new(300, uosmo_info.denom.clone())], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: uatom_info.denom, + price: Decimal::from_atomics(20u128, 0).unwrap(), + }); + + let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + + mock.update_credit_account( + &liquidator_token_id, + &liquidator, + vec![ + Deposit(ujake_info.to_coin(Uint128::from(50u128))), + LiquidateCoin { + liquidatee_token_id: liquidatee_token_id.clone(), + debt_coin: ujake_info.to_coin(Uint128::from(50u128)), + request_coin_denom: uosmo_info.denom, + }, + ], + &[ujake_info.to_coin(Uint128::from(50u128))], + ) + .unwrap(); + + // Assert liquidatee's new position + let position = mock.query_position(&liquidatee_token_id); + assert_eq!(position.coins.len(), 3); + let osmo_balance = get_coin(&position.coins, "uosmo"); + assert_eq!(osmo_balance.amount, Uint128::new(191)); + let atom_balance = get_coin(&position.coins, "uatom"); + assert_eq!(atom_balance.amount, Uint128::new(100)); + let jake_balance = get_coin(&position.coins, "ujake"); + assert_eq!(jake_balance.amount, Uint128::new(10)); + + assert_eq!(position.debt.len(), 1); + let atom_debt = get_debt(&position.debt, "uatom"); + assert_eq!(atom_debt.amount, Uint128::new(101)); + + // Assert liquidator's new position + let position = mock.query_position(&liquidator_token_id); + assert_eq!(position.coins.len(), 2); + assert_eq!(position.debt.len(), 0); + let jake_balance = get_coin(&position.coins, "ujake"); + assert_eq!(jake_balance.amount, Uint128::new(39)); + let osmo_balance = get_coin(&position.coins, "uosmo"); + assert_eq!(osmo_balance.amount, Uint128::new(109)); +} + +#[test] +fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + let liquidator = Addr::unchecked("liquidator"); + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .max_close_factor(Decimal::from_atomics(1u128, 1).unwrap()) + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: vec![Coin::new(300u128, uatom_info.denom.clone())], + }) + .build() + .unwrap(); + let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_token_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(Uint128::from(300u128))), + Borrow(uatom_info.to_coin(Uint128::from(100u128))), + ], + &[Coin::new(300, uosmo_info.denom.clone())], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(20u128, 0).unwrap(), + }); + + let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + + mock.update_credit_account( + &liquidator_token_id, + &liquidator, + vec![ + Deposit(uatom_info.to_coin(Uint128::from(50u128))), + LiquidateCoin { + liquidatee_token_id: liquidatee_token_id.clone(), + debt_coin: uatom_info.to_coin(Uint128::from(50u128)), + request_coin_denom: uosmo_info.denom, + }, + ], + &[uatom_info.to_coin(Uint128::from(50u128))], + ) + .unwrap(); + + // Assert liquidatee's new position + let position = mock.query_position(&liquidatee_token_id); + assert_eq!(position.coins.len(), 2); + let osmo_balance = get_coin(&position.coins, "uosmo"); + assert_eq!(osmo_balance.amount, Uint128::new(48)); + let atom_balance = get_coin(&position.coins, "uatom"); + assert_eq!(atom_balance.amount, Uint128::new(100)); + + assert_eq!(position.debt.len(), 1); + let atom_debt = get_debt(&position.debt, "uatom"); + assert_eq!(atom_debt.amount, Uint128::new(98)); + + // Assert liquidator's new position + let position = mock.query_position(&liquidator_token_id); + assert_eq!(position.coins.len(), 2); + assert_eq!(position.debt.len(), 0); + let atom_balance = get_coin(&position.coins, "uatom"); + assert_eq!(atom_balance.amount, Uint128::new(47)); + let osmo_balance = get_coin(&position.coins, "uosmo"); + assert_eq!(osmo_balance.amount, Uint128::new(252)); +} + +#[test] +fn test_debt_amount_no_adjustment() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + let liquidator = Addr::unchecked("liquidator"); + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .max_close_factor(Decimal::from_atomics(1u128, 1).unwrap()) + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: vec![Coin::new(300u128, uatom_info.denom.clone())], + }) + .build() + .unwrap(); + let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_token_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(Uint128::from(300u128))), + Borrow(uatom_info.to_coin(Uint128::from(100u128))), + ], + &[Coin::new(300, uosmo_info.denom.clone())], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(55u128, 1).unwrap(), + }); + + let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + + mock.update_credit_account( + &liquidator_token_id, + &liquidator, + vec![ + Deposit(uatom_info.to_coin(Uint128::from(10u128))), + LiquidateCoin { + liquidatee_token_id: liquidatee_token_id.clone(), + debt_coin: uatom_info.to_coin(Uint128::from(10u128)), + request_coin_denom: uosmo_info.denom, + }, + ], + &[uatom_info.to_coin(Uint128::from(10u128))], + ) + .unwrap(); + + // Assert liquidatee's new position + let position = mock.query_position(&liquidatee_token_id); + assert_eq!(position.coins.len(), 2); + let osmo_balance = get_coin(&position.coins, "uosmo"); + assert_eq!(osmo_balance.amount, Uint128::new(69)); + let atom_balance = get_coin(&position.coins, "uatom"); + assert_eq!(atom_balance.amount, Uint128::new(100)); + + assert_eq!(position.debt.len(), 1); + let atom_debt = get_debt(&position.debt, "uatom"); + assert_eq!(atom_debt.amount, Uint128::new(91)); + + // Assert liquidator's new position + let position = mock.query_position(&liquidator_token_id); + assert_eq!(position.coins.len(), 1); + assert_eq!(position.debt.len(), 0); + let osmo_balance = get_coin(&position.coins, "uosmo"); + assert_eq!(osmo_balance.amount, Uint128::new(231)); +} + +// TODO: After swap is implemented, attempt to liquidate with no deposited funds: +// - Borrow atom +// - Liquidate and collect osmo +// - Swap osmo for atom +// - Repay debt +// - Withdraw +#[test] +fn test_liquidate_with_no_deposited_funds() {} + +fn get_coin(coins: &[CoinValue], denom: &str) -> CoinValue { + coins + .iter() + .find(|coin| coin.denom.as_str() == denom) + .unwrap() + .clone() +} + +fn get_debt(coins: &[DebtSharesValue], denom: &str) -> DebtSharesValue { + coins + .iter() + .find(|coin| coin.denom.as_str() == denom) + .unwrap() + .clone() +} diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index d6ae8b7e1..1ea12efa0 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Decimal}; use rover::adapters::{OracleBase, RedBankBase, VaultBase}; use rover::msg::instantiate::ConfigUpdates; @@ -21,6 +21,8 @@ fn test_only_owner_can_update_config() { allowed_coins: None, red_bank: None, oracle: None, + max_liquidation_bonus: None, + max_close_factor: None, }, ); @@ -42,6 +44,8 @@ fn test_update_config_works_with_full_config() { let new_allowed_vaults = vec![VaultBase::new("vault_contract_1".to_string())]; let new_allowed_coins = vec!["uosmo".to_string()]; let new_oracle = OracleBase::new("new_oracle".to_string()); + let new_liq_bonus = Decimal::from_atomics(17u128, 2).unwrap(); + let new_close_factor = Decimal::from_atomics(32u128, 2).unwrap(); mock.update_config( &Addr::unchecked(original_config.owner.clone()), @@ -52,6 +56,8 @@ fn test_update_config_works_with_full_config() { allowed_coins: Some(new_allowed_coins.clone()), red_bank: Some(new_red_bank.clone()), oracle: Some(new_oracle.clone()), + max_liquidation_bonus: Some(new_liq_bonus), + max_close_factor: Some(new_close_factor), }, ) .unwrap(); @@ -77,6 +83,18 @@ fn test_update_config_works_with_full_config() { assert_eq!(&new_config.oracle, new_oracle.address()); assert_ne!(new_config.oracle, original_config.oracle); + + assert_eq!(new_config.max_liquidation_bonus, new_liq_bonus); + assert_ne!( + new_config.max_liquidation_bonus, + original_config.max_liquidation_bonus + ); + + assert_eq!(new_config.max_close_factor, new_close_factor); + assert_ne!( + new_config.max_close_factor, + original_config.max_close_factor + ); } #[test] @@ -138,4 +156,13 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { assert_eq!(new_queried_allowed_vaults, original_allowed_vaults); assert_eq!(new_queried_allowed_coins, original_allowed_coins); assert_eq!(new_config.red_bank, original_config.red_bank); + assert_eq!(new_config.oracle, original_config.oracle); + assert_eq!( + new_config.max_liquidation_bonus, + original_config.max_liquidation_bonus + ); + assert_eq!( + new_config.max_close_factor, + original_config.max_close_factor + ); } diff --git a/contracts/credit-manager/tests/test_withdraw.rs b/contracts/credit-manager/tests/test_withdraw.rs index f4e43da38..425a88bc3 100644 --- a/contracts/credit-manager/tests/test_withdraw.rs +++ b/contracts/credit-manager/tests/test_withdraw.rs @@ -190,7 +190,13 @@ fn test_cannot_withdraw_more_than_healthy() { &[Coin::new(200u128, coin_info.denom)], ); - assert_err(res, ContractError::AboveMaxLTV); + assert_err( + res, + ContractError::AboveMaxLTV { + token_id: token_id.clone(), + max_ltv_health_factor: "0.960099750623441396".to_string(), + }, + ); let res = mock.query_position(&token_id); assert_eq!(res.coins.len(), 0); diff --git a/packages/rover/src/coins.rs b/packages/rover/src/coins.rs index c3b798708..7bc860236 100644 --- a/packages/rover/src/coins.rs +++ b/packages/rover/src/coins.rs @@ -1,11 +1,12 @@ use std::collections::BTreeMap; use std::fmt; -use crate::extensions::Stringify; use cosmwasm_std::{Coin, StdError, StdResult, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::extensions::Stringify; + /// Pending integration into cosmwasm_std: https://github.com/CosmWasm/cosmwasm/issues/1377#issuecomment-1204232193 /// Copying from here: https://github.com/mars-protocol/cw-coins/blob/main/src/lib.rs #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 5aac13f2f..94d10a8e0 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -10,8 +10,14 @@ pub type ContractResult = Result; #[derive(Error, Debug, PartialEq)] pub enum ContractError { - #[error("Actions resulted in exceeding maximum allowed loan-to-value")] - AboveMaxLTV, + #[error("Actions resulted in exceeding maximum allowed loan-to-value. Max LTV health factor: {max_ltv_health_factor:?}")] + AboveMaxLTV { + token_id: String, + max_ltv_health_factor: String, + }, + + #[error("{0} is not an available coin to request")] + CoinNotAvailable(String), #[error("{0}")] CheckedFromRatioError(#[from] CheckedFromRatioError), @@ -34,12 +40,25 @@ pub enum ContractError { received: Uint128, }, + #[error( + "Actions did not result in improved health factor: before: {prev_hf:?}, after: {new_hf:?}" + )] + HealthNotImproved { prev_hf: String, new_hf: String }, + #[error("No coin amount set for action")] NoAmount, #[error("No debt to repay")] NoDebt, + #[error( + "{token_id:?} is not a liquidatable credit account. Health factor: {lqdt_health_factor:?}." + )] + NotLiquidatable { + token_id: String, + lqdt_health_factor: String, + }, + #[error("{user:?} is not the owner of {token_id:?}")] NotTokenOwner { user: String, token_id: String }, diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 91dd9135d..ae6635b4c 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, StdResult, Uint128, WasmMsg}; +use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -45,6 +45,23 @@ pub enum Action { vault: VaultUnchecked, coins: Vec, }, + /// Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt + /// denom/amount of what the liquidator wants to payoff and 2) the request coin denom which the + /// liquidatee should have a balance of. The amount returned to liquidator will be the request coin + /// of the amount that precisely matches the value of the debt + a liquidation bonus. + /// The debt amount will be adjusted down if: + /// - Exceeds liquidatee's total debt for denom + /// - Not enough liquidatee request coin balance to match + /// - The value of the debt repaid exceeds the maximum close factor % + LiquidateCoin { + /// The credit account id of the one with a liquidation threshold health factor 1 or below + liquidatee_token_id: String, + /// The coin debt that the liquidator wishes to pay back on behalf of the liquidatee. + /// The liquidator must already have these assets in their credit account. + debt_coin: Coin, + /// The coin they wish to acquire from the liquidatee (amount returned will include the bonus) + request_coin_denom: String, + }, } /// Internal actions made by the contract with pre-validated inputs @@ -81,6 +98,18 @@ pub enum CallbackMsg { /// Total vault coin balance in Rover previous_total_balance: Uint128, }, + /// Pay back debts of a liquidatable rover account for a bonus + LiquidateCoin { + liquidator_token_id: String, + liquidatee_token_id: String, + debt_coin: Coin, + request_coin_denom: String, + }, + /// Determine health factor improved as a consequence of liquidation event + AssertHealthFactorImproved { + token_id: String, + previous_health_factor: Decimal, + }, } impl CallbackMsg { diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index eac7bb989..b65025fb7 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -1,3 +1,4 @@ +use cosmwasm_std::Decimal; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -15,6 +16,10 @@ pub struct InstantiateMsg { pub red_bank: RedBankUnchecked, /// The Mars Protocol oracle contract. We read prices of assets here. pub oracle: OracleUnchecked, + /// The maximum percent a liquidator can profit from a liquidation action + pub max_liquidation_bonus: Decimal, + /// The maximum percent a liquidator can decrease the debt amount of the liquidatee + pub max_close_factor: Decimal, } /// Used when you want to update fields on Instantiate config @@ -26,4 +31,6 @@ pub struct ConfigUpdates { pub allowed_vaults: Option>, pub red_bank: Option, pub oracle: Option, + pub max_liquidation_bonus: Option, + pub max_close_factor: Option, } diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 4a1ba4e38..53e7c31ab 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -158,6 +158,8 @@ pub struct ConfigResponse { pub account_nft: Option, pub red_bank: String, pub oracle: String, + pub max_liquidation_bonus: Decimal, + pub max_close_factor: Decimal, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] From ee57e270b93e31ab16cda4c8fc795283c540f80b Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 15 Sep 2022 10:19:30 +0200 Subject: [PATCH 054/218] Adding swap functionality to Rover (#18) Creating new Swapper contract --- Cargo.lock | 96 ++++- Cargo.toml | 10 +- contracts/credit-manager/Cargo.toml | 2 + contracts/credit-manager/src/borrow.rs | 2 +- contracts/credit-manager/src/deposit.rs | 2 +- contracts/credit-manager/src/execute.rs | 31 +- contracts/credit-manager/src/instantiate.rs | 5 +- contracts/credit-manager/src/lib.rs | 2 + contracts/credit-manager/src/query.rs | 3 +- contracts/credit-manager/src/repay.rs | 2 +- contracts/credit-manager/src/state.rs | 2 + contracts/credit-manager/src/swap.rs | 45 +++ .../src/update_coin_balances.rs | 63 ++++ contracts/credit-manager/src/utils.rs | 12 +- contracts/credit-manager/src/vault/deposit.rs | 3 +- contracts/credit-manager/src/withdraw.rs | 2 +- .../credit-manager/tests/helpers/contracts.rs | 8 + .../credit-manager/tests/helpers/mock_env.rs | 66 +++- contracts/credit-manager/tests/helpers/mod.rs | 2 + .../credit-manager/tests/helpers/utils.rs | 5 + .../tests/test_coin_balances.rs | 190 ++++++++++ contracts/credit-manager/tests/test_swap.rs | 228 +++++++++++ .../tests/test_update_config.rs | 8 + contracts/swapper/base/Cargo.toml | 23 ++ contracts/swapper/base/src/contract.rs | 310 +++++++++++++++ contracts/swapper/base/src/error.rs | 26 ++ contracts/swapper/base/src/lib.rs | 7 + contracts/swapper/base/src/traits.rs | 43 +++ contracts/swapper/mock/Cargo.toml | 28 ++ contracts/swapper/mock/src/contract.rs | 86 +++++ contracts/swapper/mock/src/lib.rs | 3 + contracts/swapper/osmosis/Cargo.toml | 30 ++ contracts/swapper/osmosis/src/contract.rs | 35 ++ contracts/swapper/osmosis/src/helpers.rs | 32 ++ contracts/swapper/osmosis/src/lib.rs | 3 + contracts/swapper/osmosis/src/route.rs | 170 +++++++++ contracts/swapper/osmosis/tests/helpers.rs | 65 ++++ .../osmosis/tests/test_enumerate_routes.rs | 182 +++++++++ .../swapper/osmosis/tests/test_estimate.rs | 187 +++++++++ .../swapper/osmosis/tests/test_instantiate.rs | 57 +++ .../swapper/osmosis/tests/test_set_route.rs | 357 ++++++++++++++++++ contracts/swapper/osmosis/tests/test_swap.rs | 201 ++++++++++ .../osmosis/tests/test_update_config.rs | 84 +++++ packages/rover/src/adapters/mod.rs | 2 + packages/rover/src/adapters/swap/mod.rs | 5 + packages/rover/src/adapters/swap/msgs.rs | 71 ++++ packages/rover/src/adapters/swap/swapper.rs | 53 +++ packages/rover/src/msg/execute.rs | 20 + packages/rover/src/msg/instantiate.rs | 4 + packages/rover/src/msg/query.rs | 1 + 50 files changed, 2855 insertions(+), 19 deletions(-) create mode 100644 contracts/credit-manager/src/swap.rs create mode 100644 contracts/credit-manager/src/update_coin_balances.rs create mode 100644 contracts/credit-manager/tests/helpers/utils.rs create mode 100644 contracts/credit-manager/tests/test_coin_balances.rs create mode 100644 contracts/credit-manager/tests/test_swap.rs create mode 100644 contracts/swapper/base/Cargo.toml create mode 100644 contracts/swapper/base/src/contract.rs create mode 100644 contracts/swapper/base/src/error.rs create mode 100644 contracts/swapper/base/src/lib.rs create mode 100644 contracts/swapper/base/src/traits.rs create mode 100644 contracts/swapper/mock/Cargo.toml create mode 100644 contracts/swapper/mock/src/contract.rs create mode 100644 contracts/swapper/mock/src/lib.rs create mode 100644 contracts/swapper/osmosis/Cargo.toml create mode 100644 contracts/swapper/osmosis/src/contract.rs create mode 100644 contracts/swapper/osmosis/src/helpers.rs create mode 100644 contracts/swapper/osmosis/src/lib.rs create mode 100644 contracts/swapper/osmosis/src/route.rs create mode 100644 contracts/swapper/osmosis/tests/helpers.rs create mode 100644 contracts/swapper/osmosis/tests/test_enumerate_routes.rs create mode 100644 contracts/swapper/osmosis/tests/test_estimate.rs create mode 100644 contracts/swapper/osmosis/tests/test_instantiate.rs create mode 100644 contracts/swapper/osmosis/tests/test_set_route.rs create mode 100644 contracts/swapper/osmosis/tests/test_swap.rs create mode 100644 contracts/swapper/osmosis/tests/test_update_config.rs create mode 100644 packages/rover/src/adapters/swap/mod.rs create mode 100644 packages/rover/src/adapters/swap/msgs.rs create mode 100644 packages/rover/src/adapters/swap/swapper.rs diff --git a/Cargo.lock b/Cargo.lock index 21dad04fc..b87bf9faa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.1.0" dependencies = [ "anyhow", "cosmwasm-std", - "cw-multi-test", + "cw-multi-test 0.14.0", "cw-storage-plus 0.14.0", "cw721", "cw721-base", @@ -138,7 +138,7 @@ dependencies = [ "account-nft", "anyhow", "cosmwasm-std", - "cw-multi-test", + "cw-multi-test 0.14.0", "cw-storage-plus 0.14.0", "cw2 0.14.0", "cw721", @@ -151,6 +151,7 @@ dependencies = [ "rover", "schemars", "serde", + "swapper-mock", ] [[package]] @@ -194,6 +195,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-multi-test" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f9a8ab7c3c29ec93cb7a39ce4b14a05e053153b4a17ef7cf2246af1b7c087e" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 0.13.4", + "cw-utils 0.13.4", + "derivative", + "itertools", + "prost", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw-multi-test" version = "0.14.0" @@ -597,6 +617,34 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "osmo-bindings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29a49bf5df140714e63e3a13b6d6c7b7903620c4e3451476bb59cf50c1c7f88" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "osmo-bindings-test" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f96719fd1b214bb8aecbe066bb9965052cae11df4f540978122ab243d9f0a15e" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cw-multi-test 0.13.4", + "cw-storage-plus 0.13.4", + "itertools", + "osmo-bindings", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "pkcs8" version = "0.8.0" @@ -838,6 +886,50 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "swapper-base" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.14.0", + "rover", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "swapper-mock" +version = "0.1.0" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cw-multi-test 0.14.0", + "cw-storage-plus 0.14.0", + "rover", + "schemars", + "serde", + "swapper-base", + "thiserror", +] + +[[package]] +name = "swapper-osmosis" +version = "0.1.0" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cw-multi-test 0.13.4", + "cw-storage-plus 0.14.0", + "osmo-bindings", + "osmo-bindings-test", + "rover", + "schemars", + "serde", + "swapper-base", + "thiserror", +] + [[package]] name = "syn" version = "1.0.99" diff --git a/Cargo.toml b/Cargo.toml index 7f0befe91..0c13366c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,13 @@ [workspace] -members = ["packages/*", "contracts/*"] +members = [ + "contracts/account-nft", + "contracts/credit-manager", + "contracts/mock-oracle", + "contracts/mock-red-bank", + "contracts/mock-vault", + "contracts/swapper/*", + "packages/*", +] [profile.release.package.rover] codegen-units = 1 diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 52b3c8fbe..f8839ce35 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -30,6 +30,8 @@ schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } [dev-dependencies] +swapper-mock = { version = "0.1", path = "../../contracts/swapper/mock", features = ["library"] } + anyhow = "1" cw-multi-test = "0.14" itertools = "0.10" diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index 56eaa168c..195c4ee77 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -17,7 +17,7 @@ pub fn borrow(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRe return Err(ContractError::NoAmount); } - assert_coin_is_whitelisted(deps.storage, &coin)?; + assert_coin_is_whitelisted(deps.storage, &coin.denom)?; let red_bank = RED_BANK.load(deps.storage)?; let total_debt_amount = diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index 3eccc8cda..7cdf3876d 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -12,7 +12,7 @@ pub fn deposit( coin: &Coin, received_coins: &mut Coins, ) -> ContractResult { - assert_coin_is_whitelisted(storage, coin)?; + assert_coin_is_whitelisted(storage, &coin.denom)?; if coin.amount.is_zero() { return Ok(response); diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index b2c371216..4f0ed3e53 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -11,10 +11,12 @@ use crate::liquidate::{assert_health_factor_improved, liquidate_coin}; use crate::repay::repay; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, - OWNER, RED_BANK, + OWNER, RED_BANK, SWAPPER, }; use crate::vault::{deposit_into_vault, update_vault_coin_balance}; +use crate::swap::swap_exact_in; +use crate::update_coin_balances::update_coin_balances; use crate::withdraw::withdraw; use account_nft::msg::ExecuteMsg as NftExecuteMsg; use rover::coins::Coins; @@ -114,6 +116,13 @@ pub fn update_config( .add_attribute("value", unchecked.address()); } + if let Some(unchecked) = new_config.swapper { + SWAPPER.save(deps.storage, &unchecked.check(deps.api)?)?; + response = response + .add_attribute("key", "swapper") + .add_attribute("value", unchecked.address()); + } + if let Some(bonus) = new_config.max_liquidation_bonus { MAX_LIQUIDATION_BONUS.save(deps.storage, &bonus)?; response = response @@ -180,6 +189,16 @@ pub fn dispatch_actions( debt_coin: debt_coin.clone(), request_coin_denom: request_coin_denom.clone(), }), + Action::SwapExactIn { + coin_in, + denom_out, + slippage, + } => callbacks.push(CallbackMsg::SwapExactIn { + token_id: token_id.to_string(), + coin_in: coin_in.clone(), + denom_out: denom_out.to_string(), + slippage: *slippage, + }), } } @@ -257,6 +276,16 @@ pub fn execute_callback( token_id, previous_health_factor, } => assert_health_factor_improved(deps.as_ref(), env, &token_id, previous_health_factor), + CallbackMsg::SwapExactIn { + token_id, + coin_in, + denom_out, + slippage, + } => swap_exact_in(deps, env, &token_id, coin_in, &denom_out, slippage), + CallbackMsg::UpdateCoinBalances { + token_id, + previous_balances, + } => update_coin_balances(deps, env, &token_id, &previous_balances), } } diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index c1c9c771a..634a7ee4c 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,8 +1,10 @@ use cosmwasm_std::{DepsMut, Empty, StdResult}; + use rover::msg::InstantiateMsg; use crate::state::{ - ALLOWED_COINS, ALLOWED_VAULTS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, + ALLOWED_COINS, ALLOWED_VAULTS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, + RED_BANK, SWAPPER, }; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { @@ -12,6 +14,7 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; MAX_LIQUIDATION_BONUS.save(deps.storage, &msg.max_liquidation_bonus)?; MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; + SWAPPER.save(deps.storage, &msg.swapper.check(deps.api)?)?; msg.allowed_vaults.iter().try_for_each(|unchecked| { let vault = unchecked.check(deps.api)?; diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 6deb04c7b..52bbf634d 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -9,6 +9,8 @@ pub mod liquidate; pub mod query; pub mod repay; pub mod state; +pub mod swap; +pub mod update_coin_balances; pub mod utils; pub mod vault; pub mod withdraw; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 3298b7031..edff286f4 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -12,7 +12,7 @@ use rover::{Denom, NftTokenId}; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, - MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, TOTAL_DEBT_SHARES, VAULT_POSITIONS, + MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_POSITIONS, }; use crate::utils::{coin_value, debt_shares_to_amount}; @@ -29,6 +29,7 @@ pub fn query_config(deps: Deps) -> StdResult { oracle: ORACLE.load(deps.storage)?.address().into(), max_liquidation_bonus: MAX_LIQUIDATION_BONUS.load(deps.storage)?, max_close_factor: MAX_CLOSE_FACTOR.load(deps.storage)?, + swapper: SWAPPER.load(deps.storage)?.address().into(), }) } diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index 9c93ea21d..24bc3b90f 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -13,7 +13,7 @@ pub fn repay(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRes return Err(ContractError::NoAmount); } - assert_coin_is_whitelisted(deps.storage, &coin)?; + assert_coin_is_whitelisted(deps.storage, &coin.denom)?; // Ensure repayment does not exceed max debt on account let (debt_amount, debt_shares) = current_debt_for_denom(deps.as_ref(), &env, token_id, &coin)?; diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index a59963381..043e36547 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,6 +1,7 @@ use cosmwasm_std::{Addr, Decimal, Empty, Uint128}; use cw_storage_plus::{Item, Map}; +use rover::adapters::swap::Swapper; use rover::adapters::{Oracle, RedBank, VaultPosition}; use rover::{Denom, NftTokenId, Shares, VaultAddr}; @@ -13,6 +14,7 @@ pub const RED_BANK: Item = Item::new("red_bank"); pub const ORACLE: Item = Item::new("oracle"); pub const MAX_LIQUIDATION_BONUS: Item = Item::new("max_liquidation_bonus"); pub const MAX_CLOSE_FACTOR: Item = Item::new("max_close_factor"); +pub const SWAPPER: Item = Item::new("swapper"); // Positions pub const COIN_BALANCES: Map<(NftTokenId, Denom), Uint128> = Map::new("coin_balance"); diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs new file mode 100644 index 000000000..d71948616 --- /dev/null +++ b/contracts/credit-manager/src/swap.rs @@ -0,0 +1,45 @@ +use cosmwasm_std::{to_binary, Coin, CosmosMsg, Decimal, DepsMut, Env, Response, WasmMsg}; + +use rover::error::{ContractError, ContractResult}; +use rover::msg::execute::CallbackMsg; +use rover::msg::ExecuteMsg; +use rover::NftTokenId; + +use crate::state::SWAPPER; +use crate::update_coin_balances::query_balance; +use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance}; + +pub fn swap_exact_in( + deps: DepsMut, + env: Env, + token_id: NftTokenId, + coin_in: Coin, + denom_out: &str, + slippage: Decimal, +) -> ContractResult { + assert_coins_are_whitelisted(deps.storage, vec![coin_in.denom.as_str(), denom_out])?; + + if coin_in.amount.is_zero() { + return Err(ContractError::NoAmount); + } + + decrement_coin_balance(deps.storage, token_id, &coin_in)?; + + // Updates coin balances for account after the swap has taken place + let previous_balance = query_balance(deps.as_ref(), &env, denom_out)?; + let update_coin_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalances { + token_id: token_id.to_string(), + previous_balances: vec![previous_balance], + }))?, + }); + + let swapper = SWAPPER.load(deps.storage)?; + + Ok(Response::new() + .add_message(swapper.swap_exact_in_msg(&coin_in, denom_out, slippage)?) + .add_message(update_coin_balance_msg) + .add_attribute("action", "rover/credit_manager/swapper")) +} diff --git a/contracts/credit-manager/src/update_coin_balances.rs b/contracts/credit-manager/src/update_coin_balances.rs new file mode 100644 index 000000000..58e1446b0 --- /dev/null +++ b/contracts/credit-manager/src/update_coin_balances.rs @@ -0,0 +1,63 @@ +use cosmwasm_std::{ + BalanceResponse, BankQuery, Coin, Deps, DepsMut, Env, QueryRequest, Response, StdResult, +}; + +use rover::error::ContractResult; +use rover::NftTokenId; + +use crate::utils::{decrement_coin_balance, increment_coin_balance}; + +pub fn query_balance(deps: Deps, env: &Env, denom: &str) -> StdResult { + let res: BalanceResponse = deps.querier.query(&QueryRequest::Bank(BankQuery::Balance { + address: env.contract.address.to_string(), + denom: denom.to_string(), + }))?; + Ok(Coin { + denom: denom.to_string(), + amount: res.amount.amount, + }) +} + +pub fn update_coin_balances( + deps: DepsMut, + env: Env, + token_id: NftTokenId, + previous_balances: &[Coin], +) -> ContractResult { + let mut response = Response::new(); + + for prev in previous_balances { + let curr = query_balance(deps.as_ref(), &env, &prev.denom)?; + if prev.amount > curr.amount { + let new_amount = prev.amount.checked_sub(curr.amount)?; + decrement_coin_balance( + deps.storage, + token_id, + &Coin { + denom: curr.denom.clone(), + amount: new_amount, + }, + )?; + response = response + .clone() + .add_attribute("denom", curr.denom.clone()) + .add_attribute("decremented", new_amount); + } else { + let new_amount = curr.amount.checked_sub(prev.amount)?; + increment_coin_balance( + deps.storage, + token_id, + &Coin { + denom: curr.denom.clone(), + amount: new_amount, + }, + )?; + response = response + .clone() + .add_attribute("denom", curr.denom.clone()) + .add_attribute("incremented", new_amount); + } + } + + Ok(response.add_attribute("action", "rover/credit_manager/update_coin_balance")) +} diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 2081f4691..da3dea31f 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -8,21 +8,21 @@ use crate::state::{ ALLOWED_COINS, ALLOWED_VAULTS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES, }; -pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, coin: &Coin) -> ContractResult<()> { - let is_whitelisted = ALLOWED_COINS.has(storage, &coin.denom); +pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, denom: &str) -> ContractResult<()> { + let is_whitelisted = ALLOWED_COINS.has(storage, denom); if !is_whitelisted { - return Err(ContractError::NotWhitelisted(coin.denom.clone())); + return Err(ContractError::NotWhitelisted(denom.to_string())); } Ok(()) } pub fn assert_coins_are_whitelisted( storage: &mut dyn Storage, - assets: &[Coin], + denoms: Vec<&str>, ) -> ContractResult<()> { - assets + denoms .iter() - .try_for_each(|asset| assert_coin_is_whitelisted(storage, asset)) + .try_for_each(|denom| assert_coin_is_whitelisted(storage, denom)) } pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> ContractResult<()> { diff --git a/contracts/credit-manager/src/vault/deposit.rs b/contracts/credit-manager/src/vault/deposit.rs index 62295ac53..3c98b43da 100644 --- a/contracts/credit-manager/src/vault/deposit.rs +++ b/contracts/credit-manager/src/vault/deposit.rs @@ -21,7 +21,8 @@ pub fn deposit_into_vault( vault: Vault, coins: &[Coin], ) -> ContractResult { - assert_coins_are_whitelisted(deps.storage, coins)?; + let denoms = coins.iter().map(|c| c.denom.as_str()).collect(); + assert_coins_are_whitelisted(deps.storage, denoms)?; assert_vault_is_whitelisted(deps.storage, &vault)?; assert_denoms_match_vault_reqs(deps.querier, &vault, coins)?; diff --git a/contracts/credit-manager/src/withdraw.rs b/contracts/credit-manager/src/withdraw.rs index 3bd2dde76..c4d4f7666 100644 --- a/contracts/credit-manager/src/withdraw.rs +++ b/contracts/credit-manager/src/withdraw.rs @@ -10,7 +10,7 @@ pub fn withdraw( coin: Coin, recipient: Addr, ) -> ContractResult { - assert_coin_is_whitelisted(deps.storage, &coin)?; + assert_coin_is_whitelisted(deps.storage, &coin.denom)?; if coin.amount.is_zero() { return Err(ContractError::NoAmount); diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index 41cef0e7a..1d52bf604 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -14,6 +14,9 @@ use mock_red_bank::contract::{ use mock_vault::contract::{ execute as vaultExecute, instantiate as vaultInstantiate, query as vaultQuery, }; +use swapper_mock::contract::{ + execute as swapperExecute, instantiate as swapperInstantiate, query as swapperQuery, +}; pub fn mock_app() -> App { App::default() @@ -43,3 +46,8 @@ pub fn mock_vault_contract() -> Box> { let contract = ContractWrapper::new(vaultExecute, vaultInstantiate, vaultQuery); Box::new(contract) } + +pub fn mock_swapper_contract() -> Box> { + let contract = ContractWrapper::new(swapperExecute, swapperInstantiate, swapperQuery); + Box::new(contract) +} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index a164e00be..bd5f8ed0b 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -2,7 +2,7 @@ use std::mem::take; use anyhow::Result as AnyResult; use cosmwasm_std::testing::MockApi; -use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128}; use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; @@ -16,6 +16,10 @@ use mock_red_bank::msg::{ }; use mock_vault::contract::DEFAULT_VAULT_TOKEN_PREFUND; use mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; +use rover::adapters::swap::QueryMsg::EstimateExactInSwap; +use rover::adapters::swap::{ + EstimateExactInSwapResponse, InstantiateMsg as SwapperInstantiateMsg, Swapper, SwapperBase, +}; use rover::adapters::{OracleBase, RedBankBase, Vault, VaultBase, VaultUnchecked}; use rover::msg::execute::{Action, CallbackMsg}; use rover::msg::instantiate::ConfigUpdates; @@ -27,7 +31,7 @@ use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::helpers::{ mock_account_nft_contract, mock_oracle_contract, mock_red_bank_contract, mock_rover_contract, - mock_vault_contract, AccountToFund, CoinInfo, VaultTestInfo, + mock_swapper_contract, mock_vault_contract, AccountToFund, CoinInfo, VaultTestInfo, }; pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000u128); @@ -93,6 +97,19 @@ impl MockEnv { ) } + pub fn invoke_callback( + &mut self, + sender: &Addr, + callback: CallbackMsg, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.rover.clone(), + &ExecuteMsg::Callback(callback), + &[], + ) + } + pub fn update_config( &mut self, sender: &Addr, @@ -356,6 +373,24 @@ impl MockEnv { ) .unwrap() } + + pub fn query_swap_estimate( + &self, + coin_in: &Coin, + denom_out: &str, + ) -> EstimateExactInSwapResponse { + let config = self.query_config(); + self.app + .wrap() + .query_wasm_smart( + config.swapper, + &EstimateExactInSwap { + coin_in: coin_in.clone(), + denom_out: denom_out.to_string(), + }, + ) + .unwrap() + } } impl MockEnvBuilder { @@ -427,6 +462,7 @@ impl MockEnvBuilder { let code_id = self.app.store_code(mock_rover_contract()); let oracle = self.get_oracle().into(); let red_bank = self.get_red_bank().into(); + let swapper = self.deploy_swapper().into(); let allowed_coins = self .get_allowed_coins() .iter() @@ -450,6 +486,7 @@ impl MockEnvBuilder { oracle, max_liquidation_bonus, max_close_factor, + swapper, }, &[], "mock-rover-contract", @@ -568,6 +605,31 @@ impl MockEnvBuilder { VaultBase::new(addr) } + fn deploy_swapper(&mut self) -> Swapper { + let code_id = self.app.store_code(mock_swapper_contract()); + let addr = self + .app + .instantiate_contract( + code_id, + Addr::unchecked("swapper-instantiator"), + &SwapperInstantiateMsg { + owner: self.get_owner().to_string(), + }, + &[], + "mock-vault", + None, + ) + .unwrap(); + // Fund with osmo to simulate swaps + self.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: addr.to_string(), + amount: coins(1_000_000, "uosmo"), + })) + .unwrap(); + SwapperBase::new(addr) + } + /// cw-multi-test does not yet have the ability to mint sdk coins. For this reason, /// this contract expects to be pre-funded with vault tokens and it will simulate the mint. fn fund_vault(&mut self, vault_addr: &Addr, denom: &str) { diff --git a/contracts/credit-manager/tests/helpers/mod.rs b/contracts/credit-manager/tests/helpers/mod.rs index a8de1eb73..7a4a84ee3 100644 --- a/contracts/credit-manager/tests/helpers/mod.rs +++ b/contracts/credit-manager/tests/helpers/mod.rs @@ -4,6 +4,7 @@ pub use self::contracts::*; pub use self::mock_coin_info::*; pub use self::mock_env::*; pub use self::types::*; +pub use self::utils::*; mod assertions; mod builders; @@ -11,3 +12,4 @@ mod contracts; mod mock_coin_info; mod mock_env; mod types; +mod utils; diff --git a/contracts/credit-manager/tests/helpers/utils.rs b/contracts/credit-manager/tests/helpers/utils.rs new file mode 100644 index 000000000..1e3f5616b --- /dev/null +++ b/contracts/credit-manager/tests/helpers/utils.rs @@ -0,0 +1,5 @@ +use rover::msg::query::CoinValue; + +pub fn get_coin(denom: &str, coins: &[CoinValue]) -> CoinValue { + coins.iter().find(|cv| cv.denom == denom).unwrap().clone() +} diff --git a/contracts/credit-manager/tests/test_coin_balances.rs b/contracts/credit-manager/tests/test_coin_balances.rs new file mode 100644 index 000000000..1bdf71313 --- /dev/null +++ b/contracts/credit-manager/tests/test_coin_balances.rs @@ -0,0 +1,190 @@ +use cosmwasm_std::OverflowOperation::Sub; +use cosmwasm_std::{coin, coins, Addr, Coin, OverflowError, Uint128}; +use cw_multi_test::{BankSudo, SudoMsg}; + +use rover::error::ContractError; +use rover::msg::execute::Action::Deposit; +use rover::msg::execute::CallbackMsg; + +use crate::helpers::{assert_err, get_coin, uatom_info, uosmo_info, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn test_only_rover_can_call_update_coin_balances() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().build().unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.invoke_callback( + &user, + CallbackMsg::UpdateCoinBalances { + token_id, + previous_balances: vec![], + }, + ); + assert_err(res, ContractError::ExternalInvocation) +} + +#[test] +fn test_user_does_not_have_enough_to_pay_diff() { + let osmo_info = uosmo_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[osmo_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, osmo_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &token_id, + &user, + vec![Deposit(osmo_info.to_coin(Uint128::new(300)))], + &[osmo_info.to_coin(Uint128::new(300))], + ) + .unwrap(); + + let res = mock.invoke_callback( + &mock.rover.clone(), + CallbackMsg::UpdateCoinBalances { + token_id, + previous_balances: coins(601, osmo_info.denom), + }, + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: Sub, + operand1: "300".to_string(), + operand2: "301".to_string(), + }), + ) +} + +#[test] +fn test_user_gets_rebalanced_down() { + let osmo_info = uosmo_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[osmo_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, osmo_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &token_id, + &user, + vec![Deposit(osmo_info.to_coin(Uint128::new(300)))], + &[osmo_info.to_coin(Uint128::new(300))], + ) + .unwrap(); + + mock.invoke_callback( + &mock.rover.clone(), + CallbackMsg::UpdateCoinBalances { + token_id: token_id.clone(), + previous_balances: coins(500, osmo_info.denom.clone()), + }, + ) + .unwrap(); + + let position = mock.query_position(&token_id); + assert_eq!(position.coins.len(), 1); + assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); + assert_eq!(position.coins.first().unwrap().amount.u128(), 100); +} + +#[test] +fn test_user_gets_rebalanced_up() { + let osmo_info = uosmo_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[osmo_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, osmo_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &token_id, + &user, + vec![Deposit(osmo_info.to_coin(Uint128::new(300)))], + &[osmo_info.to_coin(Uint128::new(300))], + ) + .unwrap(); + + mock.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: mock.rover.clone().to_string(), + amount: vec![Coin::new(200u128, osmo_info.denom.clone())], + })) + .unwrap(); + + mock.invoke_callback( + &mock.rover.clone(), + CallbackMsg::UpdateCoinBalances { + token_id: token_id.clone(), + previous_balances: coins(300, osmo_info.denom.clone()), + }, + ) + .unwrap(); + + let position = mock.query_position(&token_id); + assert_eq!(position.coins.len(), 1); + assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); + assert_eq!(position.coins.first().unwrap().amount.u128(), 500); +} + +#[test] +fn test_works_on_multiple() { + let osmo_info = uosmo_info(); + let atom_info = uatom_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + mock.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: mock.rover.clone().to_string(), + amount: vec![ + Coin::new(143u128, osmo_info.denom.clone()), + Coin::new(57u128, atom_info.denom.clone()), + ], + })) + .unwrap(); + + mock.invoke_callback( + &mock.rover.clone(), + CallbackMsg::UpdateCoinBalances { + token_id: token_id.clone(), + previous_balances: vec![coin(0, osmo_info.denom), coin(0, atom_info.denom)], + }, + ) + .unwrap(); + + let position = mock.query_position(&token_id); + assert_eq!(position.coins.len(), 2); + let osmo = get_coin("uosmo", &position.coins); + assert_eq!(osmo.amount.u128(), 143); + let atom = get_coin("uatom", &position.coins); + assert_eq!(atom.amount.u128(), 57); +} diff --git a/contracts/credit-manager/tests/test_swap.rs b/contracts/credit-manager/tests/test_swap.rs new file mode 100644 index 000000000..8c0678e3a --- /dev/null +++ b/contracts/credit-manager/tests/test_swap.rs @@ -0,0 +1,228 @@ +use cosmwasm_std::OverflowOperation::Sub; +use cosmwasm_std::{coin, Addr, Coin, Decimal, OverflowError, Uint128}; + +use rover::error::ContractError; +use rover::msg::execute::Action::{Deposit, SwapExactIn}; +use swapper_mock::contract::MOCK_SWAP_RESULT; + +use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn test_only_token_owner_can_swap_for_account() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().build().unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let another_user = Addr::unchecked("another_user"); + let res = mock.update_credit_account( + &token_id, + &another_user, + vec![SwapExactIn { + coin_in: coin(12, "mars"), + denom_out: "osmo".to_string(), + slippage: Decimal::from_atomics(6u128, 1).unwrap(), + }], + &[], + ); + + assert_err( + res, + ContractError::NotTokenOwner { + user: another_user.into(), + token_id, + }, + ) +} + +#[test] +fn test_coin_in_must_be_whitelisted() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().build().unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![SwapExactIn { + coin_in: coin(12, "mars"), + denom_out: "osmo".to_string(), + slippage: Decimal::from_atomics(6u128, 1).unwrap(), + }], + &[], + ); + + assert_err(res, ContractError::NotWhitelisted("mars".to_string())) +} + +#[test] +fn test_denom_out_must_be_whitelisted() { + let osmo_info = uosmo_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[osmo_info.clone()]) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![SwapExactIn { + coin_in: osmo_info.to_coin(Uint128::new(10_000)), + denom_out: "ujake".to_string(), + slippage: Decimal::from_atomics(6u128, 1).unwrap(), + }], + &[], + ); + + assert_err(res, ContractError::NotWhitelisted("ujake".to_string())) +} + +#[test] +fn test_no_amount_sent() { + let osmo_info = uosmo_info(); + let atom_info = uatom_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![SwapExactIn { + coin_in: osmo_info.to_coin(Uint128::zero()), + denom_out: atom_info.denom, + slippage: Decimal::from_atomics(6u128, 1).unwrap(), + }], + &[], + ); + + assert_err(res, ContractError::NoAmount) +} + +#[test] +fn test_user_has_zero_balance_for_swap_req() { + let osmo_info = uosmo_info(); + let atom_info = uatom_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![SwapExactIn { + coin_in: osmo_info.to_coin(Uint128::new(10_000)), + denom_out: atom_info.denom, + slippage: Decimal::from_atomics(6u128, 1).unwrap(), + }], + &[], + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: Sub, + operand1: "0".to_string(), + operand2: "10000".to_string(), + }), + ) +} + +#[test] +fn test_user_does_not_have_enough_balance_for_swap_req() { + let osmo_info = uosmo_info(); + let atom_info = uatom_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, osmo_info.denom.clone())], + }) + .build() + .unwrap(); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(osmo_info.to_coin(Uint128::new(100))), + SwapExactIn { + coin_in: osmo_info.to_coin(Uint128::new(10_000)), + denom_out: atom_info.denom, + slippage: Decimal::from_atomics(6u128, 1).unwrap(), + }, + ], + &[osmo_info.to_coin(Uint128::new(100))], + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: Sub, + operand1: "100".to_string(), + operand2: "10000".to_string(), + }), + ) +} + +#[test] +fn test_swap_successful() { + let atom_info = uatom_info(); + let osmo_info = uosmo_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(10_000u128, atom_info.denom.clone())], + }) + .build() + .unwrap(); + + let res = mock.query_swap_estimate(&atom_info.to_coin(Uint128::new(10_000)), &osmo_info.denom); + assert_eq!(res.amount, MOCK_SWAP_RESULT); + + let token_id = mock.create_credit_account(&user).unwrap(); + mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(atom_info.to_coin(Uint128::new(10_000))), + SwapExactIn { + coin_in: atom_info.to_coin(Uint128::new(10_000)), + denom_out: osmo_info.denom.clone(), + slippage: Decimal::from_atomics(6u128, 1).unwrap(), + }, + ], + &[atom_info.to_coin(Uint128::new(10_000))], + ) + .unwrap(); + + // assert rover balance + let atom_balance = mock.query_balance(&mock.rover, &atom_info.denom).amount; + let osmo_balance = mock.query_balance(&mock.rover, &osmo_info.denom).amount; + assert_eq!(atom_balance, Uint128::zero()); + assert_eq!(osmo_balance, MOCK_SWAP_RESULT); + + // assert account position + let position = mock.query_position(&token_id); + assert_eq!(position.coins.len(), 1); + assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); + assert_eq!(position.coins.first().unwrap().amount, MOCK_SWAP_RESULT); +} diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 1ea12efa0..fae592be0 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{Addr, Decimal}; +use rover::adapters::swap::SwapperBase; use rover::adapters::{OracleBase, RedBankBase, VaultBase}; use rover::msg::instantiate::ConfigUpdates; @@ -23,6 +24,7 @@ fn test_only_owner_can_update_config() { oracle: None, max_liquidation_bonus: None, max_close_factor: None, + swapper: None, }, ); @@ -46,6 +48,7 @@ fn test_update_config_works_with_full_config() { let new_oracle = OracleBase::new("new_oracle".to_string()); let new_liq_bonus = Decimal::from_atomics(17u128, 2).unwrap(); let new_close_factor = Decimal::from_atomics(32u128, 2).unwrap(); + let new_swapper = SwapperBase::new("new_swapper".to_string()); mock.update_config( &Addr::unchecked(original_config.owner.clone()), @@ -58,6 +61,7 @@ fn test_update_config_works_with_full_config() { oracle: Some(new_oracle.clone()), max_liquidation_bonus: Some(new_liq_bonus), max_close_factor: Some(new_close_factor), + swapper: Some(new_swapper.clone()), }, ) .unwrap(); @@ -95,6 +99,9 @@ fn test_update_config_works_with_full_config() { new_config.max_close_factor, original_config.max_close_factor ); + + assert_eq!(&new_config.swapper, new_swapper.address()); + assert_ne!(new_config.swapper, original_config.swapper); } #[test] @@ -165,4 +172,5 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { new_config.max_close_factor, original_config.max_close_factor ); + assert_eq!(new_config.swapper, original_config.swapper); } diff --git a/contracts/swapper/base/Cargo.toml b/contracts/swapper/base/Cargo.toml new file mode 100644 index 000000000..5322e290c --- /dev/null +++ b/contracts/swapper/base/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "swapper-base" +version = "0.1.0" +authors = ["grod220 "] +edition = "2021" +license = "GPL-3.0-or-later" +repository = "https://github.com/mars-protocol/rover" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +rover = { version = "0.1", path = "../../../packages/rover" } + +cosmwasm-std = "1.0" +cw-storage-plus = "0.14" +schemars = "0.8" +serde = { version = "1.0", default-features = false, features = ["derive"] } +thiserror = "1.0" diff --git a/contracts/swapper/base/src/contract.rs b/contracts/swapper/base/src/contract.rs new file mode 100644 index 000000000..8a6b94d99 --- /dev/null +++ b/contracts/swapper/base/src/contract.rs @@ -0,0 +1,310 @@ +use std::marker::PhantomData; + +use cosmwasm_std::{ + to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, CustomMsg, CustomQuery, Decimal, Deps, + DepsMut, Env, MessageInfo, Order, Response, WasmMsg, +}; +use cw_storage_plus::{Bound, Item, Map}; + +use rover::adapters::swap::{ + Config, EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg, RouteResponse, + RoutesResponse, +}; +use rover::error::ContractError as RoverError; + +use crate::{ContractResult, Route}; + +const DEFAULT_LIMIT: u32 = 5; +const MAX_LIMIT: u32 = 10; + +pub struct SwapBase<'a, Q, M, R> +where + Q: CustomQuery, + M: CustomMsg, + R: Route, +{ + /// The contract's config + pub config: Item<'a, Config>, + /// The trade route for each pair of input/output assets + pub routes: Map<'a, (String, String), R>, + /// Phantom data holds generics + pub custom_query: PhantomData, + pub custom_message: PhantomData, +} + +impl<'a, Q, M, R> Default for SwapBase<'a, Q, M, R> +where + Q: CustomQuery, + M: CustomMsg, + R: Route, +{ + fn default() -> Self { + Self { + config: Item::new("config"), + routes: Map::new("routes"), + custom_query: PhantomData, + custom_message: PhantomData, + } + } +} + +impl<'a, Q, M, R> SwapBase<'a, Q, M, R> +where + Q: CustomQuery, + M: CustomMsg, + R: Route, +{ + pub fn instantiate( + &self, + deps: DepsMut, + msg: InstantiateMsg, + ) -> ContractResult> { + self.config.save( + deps.storage, + &Config { + owner: deps.api.addr_validate(&msg.owner)?, + }, + )?; + + Ok(Response::default()) + } + + pub fn execute( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, + ) -> ContractResult> { + match msg { + ExecuteMsg::UpdateConfig { owner } => self.update_config(deps, info.sender, owner), + ExecuteMsg::SetRoute { + denom_in, + denom_out, + route, + } => self.set_route(deps, info.sender, denom_in, denom_out, route), + ExecuteMsg::SwapExactIn { + coin_in, + denom_out, + slippage, + } => self.swap_exact_in(deps, env, info, coin_in, denom_out, slippage), + ExecuteMsg::TransferResult { + recipient, + denom_in, + denom_out, + } => self.transfer_result(deps, env, info, recipient, denom_in, denom_out), + } + } + + pub fn query(&self, deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { + let res = match msg { + QueryMsg::Config {} => to_binary(&self.query_config(deps)?), + QueryMsg::EstimateExactInSwap { coin_in, denom_out } => { + to_binary(&self.estimate_exact_in_swap(deps, env, coin_in, denom_out)?) + } + QueryMsg::Route { + denom_in, + denom_out, + } => to_binary(&self.query_route(deps, denom_in, denom_out)?), + QueryMsg::Routes { start_after, limit } => { + to_binary(&self.query_routes(deps, start_after, limit)?) + } + }; + res.map_err(Into::into) + } + + fn query_config(&self, deps: Deps) -> ContractResult> { + let cfg = self.config.load(deps.storage)?; + Ok(Config { + owner: cfg.owner.to_string(), + }) + } + + fn query_route( + &self, + deps: Deps, + denom_in: String, + denom_out: String, + ) -> ContractResult> { + Ok(RouteResponse { + denom_in: denom_in.clone(), + denom_out: denom_out.clone(), + route: self.routes.load(deps.storage, (denom_in, denom_out))?, + }) + } + + fn query_routes( + &self, + deps: Deps, + start_after: Option<(String, String)>, + limit: Option, + ) -> ContractResult> { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.map(Bound::exclusive); + + self.routes + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|item| { + let ((denom_in, denom_out), route) = item?; + Ok(RouteResponse { + denom_in, + denom_out, + route, + }) + }) + .collect() + } + + fn estimate_exact_in_swap( + &self, + deps: Deps, + env: Env, + coin_in: Coin, + denom_out: String, + ) -> ContractResult { + let route = self + .routes + .load(deps.storage, (coin_in.denom.clone(), denom_out))?; + route.estimate_exact_in_swap(deps, env, coin_in) + } + + fn swap_exact_in( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + coin_in: Coin, + denom_out: String, + slippage: Decimal, + ) -> ContractResult> { + let swap_msg = self + .routes + .load(deps.storage, (coin_in.denom.clone(), denom_out.clone()))? + .build_exact_in_swap_msg( + &deps.querier, + env.contract.address.clone(), + &coin_in, + slippage, + )?; + + // Check balance of result of swapper and send back result to sender + let transfer_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::::TransferResult { + recipient: info.sender, + denom_in: coin_in.denom.clone(), + denom_out: denom_out.clone(), + })?, + }); + + Ok(Response::new() + .add_message(swap_msg) + .add_message(transfer_msg) + .add_attribute("action", "rover/swapper/swap_fn") + .add_attribute("denom_in", coin_in.denom) + .add_attribute("amount_in", coin_in.amount) + .add_attribute("denom_out", denom_out) + .add_attribute("slippage", slippage.to_string())) + } + + fn transfer_result( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Addr, + denom_in: String, + denom_out: String, + ) -> ContractResult> { + // Internal callback only + if info.sender != env.contract.address { + return Err(RoverError::Unauthorized { + user: info.sender.to_string(), + action: "transfer result".to_string(), + } + .into()); + }; + + let denom_in_balance = deps + .querier + .query_balance(env.contract.address.clone(), denom_in)?; + let denom_out_balance = deps + .querier + .query_balance(env.contract.address, denom_out)?; + + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: recipient.to_string(), + amount: vec![denom_in_balance, denom_out_balance] + .iter() + .filter(|c| !c.amount.is_zero()) + .cloned() + .collect(), + }); + + Ok(Response::new() + .add_attribute("action", "rover/swapper/transfer_result") + .add_message(transfer_msg)) + } + + fn set_route( + &self, + deps: DepsMut, + sender: Addr, + denom_in: String, + denom_out: String, + route: R, + ) -> ContractResult> { + let cfg = self.config.load(deps.storage)?; + + if sender != cfg.owner { + return Err(RoverError::Unauthorized { + user: sender.to_string(), + action: "set route".to_string(), + } + .into()); + }; + + route.validate(&deps.querier, &denom_in, &denom_out)?; + + self.routes + .save(deps.storage, (denom_in.clone(), denom_out.clone()), &route)?; + + Ok(Response::new() + .add_attribute("action", "rover/base/set_route") + .add_attribute("denom_in", denom_in) + .add_attribute("denom_out", denom_out) + .add_attribute("route", route.to_string())) + } + + fn update_config( + &self, + deps: DepsMut, + sender: Addr, + owner: Option, + ) -> ContractResult> { + let mut cfg = self.config.load(deps.storage)?; + if sender != cfg.owner { + return Err(RoverError::Unauthorized { + user: sender.to_string(), + action: "update owner".to_string(), + } + .into()); + }; + + let mut response = + Response::new().add_attribute("action", "rover/swapper-base/update_config"); + + if let Some(addr_str) = owner { + cfg.owner = deps.api.addr_validate(&addr_str)?; + response = response + .add_attribute("key", "owner") + .add_attribute("value", addr_str); + } + + self.config.save(deps.storage, &cfg)?; + + Ok(response) + } +} diff --git a/contracts/swapper/base/src/error.rs b/contracts/swapper/base/src/error.rs new file mode 100644 index 000000000..99e7f22c8 --- /dev/null +++ b/contracts/swapper/base/src/error.rs @@ -0,0 +1,26 @@ +use cosmwasm_std::{DecimalRangeExceeded, OverflowError, StdError}; +use rover::error::ContractError as RoverError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + DecimalRangeExceeded(#[from] DecimalRangeExceeded), + + #[error("Invalid route: {reason}")] + InvalidRoute { reason: String }, + + #[error("{0}")] + Overflow(#[from] OverflowError), + + #[error("{denom_a:?}-{denom_b:?} is not an available pool")] + PoolNotFound { denom_a: String, denom_b: String }, + + #[error("{0}")] + Rover(#[from] RoverError), + + #[error("{0}")] + Std(#[from] StdError), +} + +pub type ContractResult = Result; diff --git a/contracts/swapper/base/src/lib.rs b/contracts/swapper/base/src/lib.rs new file mode 100644 index 000000000..d0ca995d1 --- /dev/null +++ b/contracts/swapper/base/src/lib.rs @@ -0,0 +1,7 @@ +mod contract; +mod error; +mod traits; + +pub use contract::*; +pub use error::*; +pub use traits::*; diff --git a/contracts/swapper/base/src/traits.rs b/contracts/swapper/base/src/traits.rs new file mode 100644 index 000000000..4bc1ca0bb --- /dev/null +++ b/contracts/swapper/base/src/traits.rs @@ -0,0 +1,43 @@ +use std::fmt::{Debug, Display}; + +use cosmwasm_std::{ + Addr, Coin, CosmosMsg, CustomMsg, CustomQuery, Decimal, Deps, Env, QuerierWrapper, +}; +use schemars::JsonSchema; +use serde::{de::DeserializeOwned, Serialize}; + +use rover::adapters::swap::EstimateExactInSwapResponse; + +use crate::ContractResult; + +pub trait Route: + Serialize + DeserializeOwned + Clone + Debug + Display + PartialEq + JsonSchema +where + M: CustomMsg, + Q: CustomQuery, +{ + /// Determine whether the route is valid, given a pair of input and output denoms + fn validate( + &self, + querier: &QuerierWrapper, + denom_in: &str, + denom_out: &str, + ) -> ContractResult<()>; + + /// Build a message for executing the trade, given an input denom and amount + fn build_exact_in_swap_msg( + &self, + querier: &QuerierWrapper, + contract_addr: Addr, + coin_in: &Coin, + slippage: Decimal, + ) -> ContractResult>; + + /// Query to get the estimate result of a swap + fn estimate_exact_in_swap( + &self, + deps: Deps, + env: Env, + coin_in: Coin, + ) -> ContractResult; +} diff --git a/contracts/swapper/mock/Cargo.toml b/contracts/swapper/mock/Cargo.toml new file mode 100644 index 000000000..a8edb4f2c --- /dev/null +++ b/contracts/swapper/mock/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "swapper-mock" +version = "0.1.0" +authors = ["grod220 "] +edition = "2021" +license = "GPL-3.0-or-later" +repository = "https://github.com/mars-protocol/rover" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +rover = { version = "0.1", path = "../../../packages/rover" } +swapper-base = { path = "../base", version = "0.1" } + +cosmwasm-std = "1.0" +cw-storage-plus = "0.14" +schemars = "0.8" +serde = { version = "1.0", default-features = false, features = ["derive"] } +thiserror = "1.0" + +[dev-dependencies] +anyhow = "1" +cw-multi-test = "0.14" diff --git a/contracts/swapper/mock/src/contract.rs b/contracts/swapper/mock/src/contract.rs new file mode 100644 index 000000000..d8f2a036d --- /dev/null +++ b/contracts/swapper/mock/src/contract.rs @@ -0,0 +1,86 @@ +use cosmwasm_std::{ + coins, to_binary, BankMsg, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Empty, Env, + MessageInfo, Response, StdError, StdResult, Uint128, +}; + +use rover::adapters::swap::{EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; + +pub const MOCK_SWAP_RESULT: Uint128 = Uint128::new(1337); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: InstantiateMsg, +) -> StdResult { + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + match msg { + ExecuteMsg::UpdateConfig { .. } => unimplemented!("not implemented"), + ExecuteMsg::SetRoute { .. } => unimplemented!("not implemented"), + ExecuteMsg::TransferResult { .. } => unimplemented!("not implemented"), + ExecuteMsg::SwapExactIn { + coin_in, + denom_out, + slippage, + } => swap_exact_in(deps, env, info, coin_in, denom_out, slippage), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config { .. } => unimplemented!("not implemented"), + QueryMsg::Route { .. } => unimplemented!("not implemented"), + QueryMsg::Routes { .. } => unimplemented!("not implemented"), + QueryMsg::EstimateExactInSwap { .. } => to_binary(&estimate_exact_in_swap()), + } +} + +pub fn estimate_exact_in_swap() -> EstimateExactInSwapResponse { + EstimateExactInSwapResponse { + amount: MOCK_SWAP_RESULT, + } +} + +pub fn swap_exact_in( + deps: DepsMut, + env: Env, + info: MessageInfo, + coin_in: Coin, + denom_out: String, + _slippage: Decimal, +) -> StdResult { + let denom_in_balance = deps + .querier + .query_balance(env.contract.address, coin_in.denom)?; + if denom_in_balance.amount < coin_in.amount { + return Err(StdError::generic_err("Did not send funds")); + } + + if denom_out != "uosmo" { + return Err(StdError::generic_err( + "Mock swapper can only have uosmo as denom out", + )); + } + + // This is dependent on the mock env to pre-fund this contract with uosmo coins + // simulating a swap has taken place + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.to_string(), + amount: coins(MOCK_SWAP_RESULT.u128(), denom_out), + }); + + Ok(Response::new() + .add_attribute("action", "rover/swapper/transfer_result") + .add_message(transfer_msg)) +} diff --git a/contracts/swapper/mock/src/lib.rs b/contracts/swapper/mock/src/lib.rs new file mode 100644 index 000000000..cd99a86b5 --- /dev/null +++ b/contracts/swapper/mock/src/lib.rs @@ -0,0 +1,3 @@ +extern crate core; + +pub mod contract; diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml new file mode 100644 index 000000000..91954104b --- /dev/null +++ b/contracts/swapper/osmosis/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "swapper-osmosis" +version = "0.1.0" +authors = ["grod220 "] +edition = "2021" +license = "GPL-3.0-or-later" +repository = "https://github.com/mars-protocol/rover" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +rover = { version = "0.1", path = "../../../packages/rover" } +swapper-base = { path = "../base", version = "0.1" } + +cosmwasm-std = "1.0" +cw-storage-plus = "0.14" +osmo-bindings = "0.5" +schemars = "0.8" +serde = { version = "1.0", default-features = false, features = ["derive"] } +thiserror = "1.0" + +[dev-dependencies] +anyhow = "1" +cw-multi-test = "0.13" +osmo-bindings-test = "0.5" diff --git a/contracts/swapper/osmosis/src/contract.rs b/contracts/swapper/osmosis/src/contract.rs new file mode 100644 index 000000000..1de0020e2 --- /dev/null +++ b/contracts/swapper/osmosis/src/contract.rs @@ -0,0 +1,35 @@ +use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response}; +use osmo_bindings::{OsmosisMsg, OsmosisQuery}; + +use rover::adapters::swap::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use swapper_base::{ContractResult, SwapBase}; + +use crate::route::OsmosisRoute; + +/// The Osmosis swapper contract inherits logic from the base swapper contract +pub type OsmosisSwap<'a> = SwapBase<'a, OsmosisQuery, OsmosisMsg, OsmosisRoute>; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> ContractResult> { + OsmosisSwap::default().instantiate(deps, msg) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> ContractResult> { + OsmosisSwap::default().execute(deps, env, info, msg) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { + OsmosisSwap::default().query(deps, env, msg) +} diff --git a/contracts/swapper/osmosis/src/helpers.rs b/contracts/swapper/osmosis/src/helpers.rs new file mode 100644 index 000000000..3f461bdb8 --- /dev/null +++ b/contracts/swapper/osmosis/src/helpers.rs @@ -0,0 +1,32 @@ +use cosmwasm_std::{Decimal, Uint128}; +use osmo_bindings::SwapAmount; +use std::collections::HashSet; +use std::hash::Hash; + +/// Build a hashset from array data +pub(crate) fn hashset(data: &[T]) -> HashSet { + data.iter().cloned().collect() +} + +pub trait IntoUint128 { + fn uint128(&self) -> Uint128; +} + +impl IntoUint128 for Decimal { + fn uint128(&self) -> Uint128 { + *self * Uint128::new(1) + } +} + +pub trait GetValue { + fn value(&self) -> Uint128; +} + +impl GetValue for SwapAmount { + fn value(&self) -> Uint128 { + match self { + Self::In(amount) => *amount, + Self::Out(amount) => *amount, + } + } +} diff --git a/contracts/swapper/osmosis/src/lib.rs b/contracts/swapper/osmosis/src/lib.rs new file mode 100644 index 000000000..fdbe29de5 --- /dev/null +++ b/contracts/swapper/osmosis/src/lib.rs @@ -0,0 +1,3 @@ +pub mod contract; +pub mod helpers; +pub mod route; diff --git a/contracts/swapper/osmosis/src/route.rs b/contracts/swapper/osmosis/src/route.rs new file mode 100644 index 000000000..a2151880f --- /dev/null +++ b/contracts/swapper/osmosis/src/route.rs @@ -0,0 +1,170 @@ +use std::fmt; +use std::ops::Sub; + +use cosmwasm_std::{ + to_binary, Addr, Coin, CosmosMsg, Decimal, Deps, Env, QuerierWrapper, QueryRequest, WasmQuery, +}; +use osmo_bindings::{ + EstimatePriceResponse as OsmoResponse, OsmosisMsg, OsmosisQuery, PoolStateResponse, Step, Swap, + SwapAmount, SwapAmountWithLimit, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use rover::adapters::swap::{EstimateExactInSwapResponse, QueryMsg}; +use swapper_base::{ContractError, ContractResult, Route}; + +use crate::helpers::{hashset, GetValue, IntoUint128}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct OsmosisRoute { + pub steps: Vec, +} + +impl fmt::Display for OsmosisRoute { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = self + .steps + .iter() + .map(|step| format!("{}:{}", step.pool_id, step.denom_out)) + .collect::>() + .join("|"); + write!(f, "{}", s) + } +} + +impl Route for OsmosisRoute { + // Perform basic validation of the swapper steps + fn validate( + &self, + querier: &QuerierWrapper, + denom_in: &str, + denom_out: &str, + ) -> ContractResult<()> { + // there must be at least one step + if self.steps.is_empty() { + return Err(ContractError::InvalidRoute { + reason: "the route must contain at least one step".to_string(), + }); + } + + // for each step: + // - the pool must contain the input and output denoms + // - the output denom must not be the same as the input denom of a previous step (i.e. the route must not contain a loop) + let mut prev_denom_out = denom_in; + let mut seen_denoms = hashset(&[denom_in]); + for (i, step) in self.steps.iter().enumerate() { + let pool_state: PoolStateResponse = + querier.query(&QueryRequest::Custom(OsmosisQuery::PoolState { + id: step.pool_id, + }))?; + + if !pool_state.has_denom(prev_denom_out) { + return Err(ContractError::InvalidRoute { + reason: format!( + "step {}: pool {} does not contain input denom {}", + i + 1, + step.pool_id, + prev_denom_out + ), + }); + } + + if !pool_state.has_denom(&step.denom_out) { + return Err(ContractError::InvalidRoute { + reason: format!( + "step {}: pool {} does not contain output denom {}", + i + 1, + step.pool_id, + &step.denom_out + ), + }); + } + + if seen_denoms.contains(step.denom_out.as_str()) { + return Err(ContractError::InvalidRoute { + reason: format!("route contains a loop: denom {} seen twice", step.denom_out), + }); + } + + prev_denom_out = &step.denom_out; + seen_denoms.insert(&step.denom_out); + } + + // the route's final output denom must match the desired output denom + if prev_denom_out != denom_out { + return Err(ContractError::InvalidRoute { + reason: format!( + "the route's output denom {} does not match the desired output {}", + prev_denom_out, denom_out + ), + }); + } + + Ok(()) + } + + /// Build a CosmosMsg that swaps given an input denom and amount + fn build_exact_in_swap_msg( + &self, + querier: &QuerierWrapper, + contract_addr: Addr, + coin_in: &Coin, + slippage: Decimal, + ) -> ContractResult> { + let last_step = self.steps.last().unwrap(); // Safe as contract guarantees at least one step + let res: EstimateExactInSwapResponse = + querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: contract_addr.to_string(), + msg: to_binary(&QueryMsg::EstimateExactInSwap { + coin_in: coin_in.clone(), + denom_out: last_step.denom_out.clone(), + })?, + }))?; + + let swap_estimate_dec = Decimal::from_atomics(res.amount, 0)?; + let swap_amount_with_slippage = SwapAmountWithLimit::ExactIn { + input: coin_in.amount, + min_output: Decimal::one() + .sub(slippage) + .checked_mul(swap_estimate_dec)? + .uint128(), + }; + + let first_swap = self + .steps + .first() + .map(|step| Swap::new(step.pool_id, coin_in.denom.clone(), &step.denom_out)) + .unwrap(); // Safe as contract guarantees at least one step + + Ok(CosmosMsg::Custom(OsmosisMsg::Swap { + first: first_swap, + route: self.steps[1..].to_vec(), + amount: swap_amount_with_slippage, + })) + } + + fn estimate_exact_in_swap( + &self, + deps: Deps, + env: Env, + coin_in: Coin, + ) -> ContractResult { + let first_step = self.steps.first().unwrap(); // Safe as contract guarantees at least one step + let query = OsmosisQuery::EstimateSwap { + sender: env.contract.address.to_string(), + first: Swap { + pool_id: first_step.pool_id, + denom_in: coin_in.denom, + denom_out: first_step.denom_out.clone(), + }, + route: self.steps[1..].to_vec(), + amount: SwapAmount::In(coin_in.amount), + }; + + let res: OsmoResponse = deps.querier.query(&QueryRequest::Custom(query))?; + Ok(EstimateExactInSwapResponse { + amount: res.amount.value(), + }) + } +} diff --git a/contracts/swapper/osmosis/tests/helpers.rs b/contracts/swapper/osmosis/tests/helpers.rs new file mode 100644 index 000000000..4ddd79037 --- /dev/null +++ b/contracts/swapper/osmosis/tests/helpers.rs @@ -0,0 +1,65 @@ +use std::fmt::Debug; + +use anyhow::Result as AnyResult; +use cosmwasm_std::Addr; +use cosmwasm_std::CustomQuery; +use cw_multi_test::{AppResponse, Executor}; +use cw_multi_test::{Contract, ContractWrapper}; +use osmo_bindings::{OsmosisMsg, OsmosisQuery}; +use osmo_bindings_test::OsmosisApp; +use schemars::JsonSchema; + +use rover::adapters::swap::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}; +use swapper_base::ContractError; +use swapper_osmosis::contract::{execute, instantiate, query}; +use swapper_osmosis::route::OsmosisRoute; + +pub fn mock_osmosis_app() -> OsmosisApp { + OsmosisApp::default() +} + +pub fn mock_osmosis_contract() -> Box> +where + C: Clone + Debug + PartialEq + JsonSchema, + Q: CustomQuery, + ContractWrapper< + ExecuteMsg, + Config, + QueryMsg, + ContractError, + ContractError, + ContractError, + OsmosisMsg, + OsmosisQuery, + >: Contract, +{ + let contract = ContractWrapper::new(execute, instantiate, query); //.with_reply(reply); + Box::new(contract) +} + +pub fn assert_err(res: AnyResult, err: ContractError) { + match res { + Ok(_) => panic!("Result was not an error"), + Err(generic_err) => { + let contract_err: ContractError = generic_err.downcast().unwrap(); + assert_eq!(contract_err, err); + } + } +} + +pub fn instantiate_contract(app: &mut OsmosisApp) -> Addr { + let owner = Addr::unchecked("owner"); + let contract = mock_osmosis_contract(); + let code_id = app.store_code(contract); + app.instantiate_contract( + code_id, + owner.clone(), + &InstantiateMsg { + owner: owner.to_string(), + }, + &[], + "mock-swapper-osmosis-contract", + None, + ) + .unwrap() +} diff --git a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs new file mode 100644 index 000000000..2063da17e --- /dev/null +++ b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs @@ -0,0 +1,182 @@ +use crate::helpers::{instantiate_contract, mock_osmosis_app}; +use cosmwasm_std::{coin, Addr}; +use cw_multi_test::Executor; +use osmo_bindings::Step; +use osmo_bindings_test::Pool; +use rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; +use std::collections::HashMap; +use swapper_osmosis::route::OsmosisRoute; + +pub mod helpers; + +#[test] +fn test_enumerating_routes() { + let owner = Addr::unchecked("owner"); + let mut app = mock_osmosis_app(); + let contract_addr = instantiate_contract(&mut app); + + let coin_a = coin(6_000_000, "uatom"); + let coin_b = coin(1_500_000, "uosmo"); + let pool_id_x = 1; + let pool_x = Pool::new(coin_a, coin_b); + + let coin_c = coin(100_000, "uosmo"); + let coin_d = coin(1_000_000, "umars"); + let pool_id_y = 420; + let pool_y = Pool::new(coin_c, coin_d); + + let coin_e = coin(100_000, "uosmo"); + let coin_f = coin(1_000_000, "uusdc"); + let pool_id_z = 69; + let pool_z = Pool::new(coin_e, coin_f); + + app.init_modules(|router, _, storage| { + router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); + router.custom.set_pool(storage, pool_id_y, &pool_y).unwrap(); + router.custom.set_pool(storage, pool_id_z, &pool_z).unwrap(); + }); + + let routes = mock_routes(); + + app.execute_contract( + owner.clone(), + contract_addr.clone(), + &ExecuteMsg::SetRoute { + denom_in: "uatom".to_string(), + denom_out: "umars".to_string(), + route: routes.get(&("uatom", "umars")).unwrap().clone(), + }, + &[], + ) + .unwrap(); + + app.execute_contract( + owner.clone(), + contract_addr.clone(), + &ExecuteMsg::SetRoute { + denom_in: "uatom".to_string(), + denom_out: "uusdc".to_string(), + route: routes.get(&("uatom", "uusdc")).unwrap().clone(), + }, + &[], + ) + .unwrap(); + + app.execute_contract( + owner, + contract_addr.clone(), + &ExecuteMsg::SetRoute { + denom_in: "uosmo".to_string(), + denom_out: "umars".to_string(), + route: routes.get(&("uosmo", "umars")).unwrap().clone(), + }, + &[], + ) + .unwrap(); + + // NOTE: the response is ordered alphabetically + let expected = vec![ + RouteResponse { + denom_in: "uatom".to_string(), + denom_out: "umars".to_string(), + route: routes.get(&("uatom", "umars")).unwrap().clone(), + }, + RouteResponse { + denom_in: "uatom".to_string(), + denom_out: "uusdc".to_string(), + route: routes.get(&("uatom", "uusdc")).unwrap().clone(), + }, + RouteResponse { + denom_in: "uosmo".to_string(), + denom_out: "umars".to_string(), + route: routes.get(&("uosmo", "umars")).unwrap().clone(), + }, + ]; + + let res: Vec> = app + .wrap() + .query_wasm_smart( + contract_addr.to_string(), + &QueryMsg::Routes { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(res, expected); + + let res: Vec> = app + .wrap() + .query_wasm_smart( + contract_addr.to_string(), + &QueryMsg::Routes { + start_after: None, + limit: Some(1), + }, + ) + .unwrap(); + assert_eq!(res, expected[..1]); + + let res: Vec> = app + .wrap() + .query_wasm_smart( + contract_addr.to_string(), + &QueryMsg::Routes { + start_after: Some(("uatom".to_string(), "uosmo".to_string())), + limit: None, + }, + ) + .unwrap(); + assert_eq!(res, expected[1..]); +} + +fn mock_routes() -> HashMap<(&'static str, &'static str), OsmosisRoute> { + let mut map = HashMap::new(); + + // uosmo -> umars + map.insert( + ("uosmo", "umars"), + OsmosisRoute { + steps: vec![Step { + pool_id: 420, + denom_out: "umars".to_string(), + }], + }, + ); + + // uatom -> uosmo -> umars + map.insert( + ("uatom", "umars"), + OsmosisRoute { + steps: vec![ + Step { + pool_id: 1, + denom_out: "uosmo".to_string(), + }, + Step { + pool_id: 420, + denom_out: "umars".to_string(), + }, + ], + }, + ); + + // uatom -> uosmo -> uusdc + map.insert( + ("uatom", "uusdc"), + OsmosisRoute { + steps: vec![ + Step { + pool_id: 1, + denom_out: "uosmo".to_string(), + }, + Step { + pool_id: 69, + denom_out: "uusdc".to_string(), + }, + ], + }, + ); + + map +} diff --git a/contracts/swapper/osmosis/tests/test_estimate.rs b/contracts/swapper/osmosis/tests/test_estimate.rs new file mode 100644 index 000000000..08511c86f --- /dev/null +++ b/contracts/swapper/osmosis/tests/test_estimate.rs @@ -0,0 +1,187 @@ +use cosmwasm_std::{coin, Addr, StdError, StdResult, Uint128}; +use cw_multi_test::Executor; +use osmo_bindings::Step; +use osmo_bindings_test::{Pool as OsmoPool, Pool}; + +use rover::adapters::swap::{EstimateExactInSwapResponse, ExecuteMsg, QueryMsg}; +use swapper_osmosis::route::OsmosisRoute; + +use crate::helpers::instantiate_contract; +use crate::helpers::mock_osmosis_app; + +pub mod helpers; + +#[test] +fn test_error_on_route_not_found() { + let mut app = mock_osmosis_app(); + let contract_addr = instantiate_contract(&mut app); + let res: StdResult = app.wrap().query_wasm_smart( + contract_addr, + &QueryMsg::EstimateExactInSwap { + coin_in: coin(1000, "jake"), + denom_out: "mars".to_string(), + }, + ); + + match res { + Ok(_) => panic!("should have thrown an error"), + Err(err) => assert_eq!( + err, + StdError::generic_err( + "Querier contract error: swapper_osmosis::route::OsmosisRoute not found" + ) + ), + } +} + +#[test] +fn test_estimate_swap_one_step() { + let mut app = mock_osmosis_app(); + + let coin_a = coin(6_000_000, "osmo"); + let coin_b = coin(1_500_000, "atom"); + let pool_id = 43; + let pool = OsmoPool::new(coin_a.clone(), coin_b.clone()); + + app.init_modules(|router, _, storage| { + router.custom.set_pool(storage, pool_id, &pool).unwrap(); + }); + + let owner = Addr::unchecked("owner"); + let contract_addr = instantiate_contract(&mut app); + + app.execute_contract( + owner, + contract_addr.clone(), + &ExecuteMsg::SetRoute { + denom_in: "osmo".to_string(), + denom_out: "atom".to_string(), + route: OsmosisRoute { + steps: vec![Step { + pool_id, + denom_out: "atom".to_string(), + }], + }, + }, + &[], + ) + .unwrap(); + + let res: EstimateExactInSwapResponse = app + .wrap() + .query_wasm_smart( + contract_addr, + &QueryMsg::EstimateExactInSwap { + coin_in: coin(1000, coin_a.denom), + denom_out: coin_b.denom, + }, + ) + .unwrap(); + + assert_eq!(res.amount, Uint128::new(250)); +} + +#[test] +fn test_estimate_swap_multi_step() { + let mut app = mock_osmosis_app(); + + let coin_a = coin(6_000_000, "uatom"); + let coin_b = coin(1_500_000, "uosmo"); + let pool_id_x = 1; + let pool_x = Pool::new(coin_a.clone(), coin_b); + + let coin_c = coin(100_000, "uosmo"); + let coin_d = coin(1_000_000, "umars"); + let pool_id_y = 420; + let pool_y = Pool::new(coin_c, coin_d); + + let coin_e = coin(100_000, "uosmo"); + let coin_f = coin(1_000_000, "uusdc"); + let pool_id_z = 69; + let pool_z = Pool::new(coin_e, coin_f.clone()); + + app.init_modules(|router, _, storage| { + router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); + router.custom.set_pool(storage, pool_id_y, &pool_y).unwrap(); + router.custom.set_pool(storage, pool_id_z, &pool_z).unwrap(); + }); + + let owner = Addr::unchecked("owner"); + let contract_addr = instantiate_contract(&mut app); + + app.execute_contract( + owner.clone(), + contract_addr.clone(), + &ExecuteMsg::SetRoute { + denom_in: "uatom".to_string(), + denom_out: "umars".to_string(), + route: OsmosisRoute { + steps: vec![ + Step { + pool_id: 1, + denom_out: "uosmo".to_string(), + }, + Step { + pool_id: 420, + denom_out: "umars".to_string(), + }, + ], + }, + }, + &[], + ) + .unwrap(); + + app.execute_contract( + owner.clone(), + contract_addr.clone(), + &ExecuteMsg::SetRoute { + denom_in: "uatom".to_string(), + denom_out: "uusdc".to_string(), + route: OsmosisRoute { + steps: vec![ + Step { + pool_id: 1, + denom_out: "uosmo".to_string(), + }, + Step { + pool_id: 69, + denom_out: "uusdc".to_string(), + }, + ], + }, + }, + &[], + ) + .unwrap(); + + app.execute_contract( + owner, + contract_addr.clone(), + &ExecuteMsg::SetRoute { + denom_in: "uosmo".to_string(), + denom_out: "umars".to_string(), + route: OsmosisRoute { + steps: vec![Step { + pool_id: 420, + denom_out: "umars".to_string(), + }], + }, + }, + &[], + ) + .unwrap(); + + let res: EstimateExactInSwapResponse = app + .wrap() + .query_wasm_smart( + contract_addr, + &QueryMsg::EstimateExactInSwap { + coin_in: coin(1000, coin_a.denom), + denom_out: coin_f.denom, + }, + ) + .unwrap(); + + assert_eq!(res.amount, Uint128::new(2484)); +} diff --git a/contracts/swapper/osmosis/tests/test_instantiate.rs b/contracts/swapper/osmosis/tests/test_instantiate.rs new file mode 100644 index 000000000..ad486186b --- /dev/null +++ b/contracts/swapper/osmosis/tests/test_instantiate.rs @@ -0,0 +1,57 @@ +use cosmwasm_std::Addr; +use cw_multi_test::Executor; + +use rover::adapters::swap::{Config, InstantiateMsg, QueryMsg}; + +use crate::helpers::{mock_osmosis_app, mock_osmosis_contract}; + +pub mod helpers; + +#[test] +fn test_owner_set_on_instantiate() { + let owner = Addr::unchecked("owner"); + let mut app = mock_osmosis_app(); + let contract = mock_osmosis_contract(); + let code_id = app.store_code(contract); + let contract_addr = app + .instantiate_contract( + code_id, + owner.clone(), + &InstantiateMsg { + owner: owner.to_string(), + }, + &[], + "mock-swapper-contract", + None, + ) + .unwrap(); + + let config: Config = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + assert_eq!(config.owner, owner); +} + +#[test] +fn test_raises_on_invalid_owner_addr() { + let owner = "%%%INVALID%%%"; + let mut app = mock_osmosis_app(); + let contract = mock_osmosis_contract(); + let code_id = app.store_code(contract); + let res = app.instantiate_contract( + code_id, + Addr::unchecked(owner), + &InstantiateMsg { + owner: owner.to_string(), + }, + &[], + "mock-swapper-contract", + None, + ); + + if res.is_ok() { + panic!("Should have thrown an error"); + } +} diff --git a/contracts/swapper/osmosis/tests/test_set_route.rs b/contracts/swapper/osmosis/tests/test_set_route.rs new file mode 100644 index 000000000..0a5b902c5 --- /dev/null +++ b/contracts/swapper/osmosis/tests/test_set_route.rs @@ -0,0 +1,357 @@ +use cosmwasm_std::StdError::GenericErr; +use cosmwasm_std::{coin, Addr}; +use cw_multi_test::Executor; +use osmo_bindings::Step; +use osmo_bindings_test::Pool; + +use rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; +use rover::error::ContractError as RoverError; +use swapper_base::ContractError; +use swapper_osmosis::route::OsmosisRoute; + +use crate::helpers::mock_osmosis_app; +use crate::helpers::{assert_err, instantiate_contract}; + +pub mod helpers; + +#[test] +fn test_only_owner_can_set_routes() { + let mut app = mock_osmosis_app(); + let contract_addr = instantiate_contract(&mut app); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = app.execute_contract( + bad_guy.clone(), + contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "mars".to_string(), + denom_out: "weth".to_string(), + route: OsmosisRoute { + steps: vec![ + Step { + pool_id: 1, + denom_out: "osmo".to_string(), + }, + Step { + pool_id: 2, + denom_out: "weth".to_string(), + }, + ], + }, + }, + &[], + ); + + assert_err( + res, + ContractError::Rover(RoverError::Unauthorized { + user: bad_guy.to_string(), + action: "set route".to_string(), + }), + ); +} + +#[test] +fn test_must_pass_at_least_one_step() { + let owner = Addr::unchecked("owner"); + let mut app = mock_osmosis_app(); + let contract_addr = instantiate_contract(&mut app); + + let res = app.execute_contract( + owner, + contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "mars".to_string(), + denom_out: "weth".to_string(), + route: OsmosisRoute { steps: vec![] }, + }, + &[], + ); + + assert_err( + res, + ContractError::InvalidRoute { + reason: "the route must contain at least one step".to_string(), + }, + ); +} + +#[test] +fn test_must_be_available_in_osmosis() { + let owner = Addr::unchecked("owner"); + let mut app = mock_osmosis_app(); + let contract_addr = instantiate_contract(&mut app); + + let res = app.execute_contract( + owner, + contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "mars".to_string(), + denom_out: "weth".to_string(), + route: OsmosisRoute { + steps: vec![Step { + pool_id: 1, + denom_out: "osmo".to_string(), + }], + }, + }, + &[], + ); + + assert_err( + res, + ContractError::Std(GenericErr { + msg: "Querier contract error: osmo_bindings_test::multitest::Pool not found" + .to_string(), + }), + ); +} + +#[test] +fn test_step_does_not_contain_input_denom() { + let owner = Addr::unchecked("owner"); + let mut app = mock_osmosis_app(); + + let coin_a = coin(6_000_000, "atom"); + let coin_b = coin(1_500_000, "osmo"); + let pool_id_x = 43; + let pool_x = Pool::new(coin_a, coin_b); + + app.init_modules(|router, _, storage| { + router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); + }); + + let contract_addr = instantiate_contract(&mut app); + + let res = app.execute_contract( + owner, + contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "mars".to_string(), + denom_out: "weth".to_string(), + route: OsmosisRoute { + steps: vec![Step { + pool_id: pool_id_x, + denom_out: "osmo".to_string(), + }], + }, + }, + &[], + ); + + assert_err( + res, + ContractError::InvalidRoute { + reason: "step 1: pool 43 does not contain input denom mars".to_string(), + }, + ); +} + +#[test] +fn test_step_does_not_contain_output_denom() { + let owner = Addr::unchecked("owner"); + let mut app = mock_osmosis_app(); + + let coin_a = coin(6_000_000, "mars"); + let coin_b = coin(1_500_000, "osmo"); + let pool_id_x = 43; + let pool_x = Pool::new(coin_a, coin_b); + + app.init_modules(|router, _, storage| { + router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); + }); + + let contract_addr = instantiate_contract(&mut app); + + let res = app.execute_contract( + owner, + contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "mars".to_string(), + denom_out: "weth".to_string(), + route: OsmosisRoute { + steps: vec![Step { + pool_id: pool_id_x, + denom_out: "weth".to_string(), + }], + }, + }, + &[], + ); + + assert_err( + res, + ContractError::InvalidRoute { + reason: "step 1: pool 43 does not contain output denom weth".to_string(), + }, + ); +} + +#[test] +fn test_steps_do_not_loop() { + let owner = Addr::unchecked("owner"); + let mut app = mock_osmosis_app(); + + let coin_a = coin(6_000_000, "atom"); + let coin_b = coin(1_500_000, "osmo"); + let pool_id_x = 43; + let pool_x = Pool::new(coin_a, coin_b); + + let coin_c = coin(6_000_000, "osmo"); + let coin_d = coin(1_500_000, "usdc"); + let pool_id_y = 101; + let pool_y = Pool::new(coin_c, coin_d); + + let coin_e = coin(6_000_000, "osmo"); + let coin_f = coin(1_500_000, "mars"); + let pool_id_z = 2; + let pool_z = Pool::new(coin_e, coin_f); + + app.init_modules(|router, _, storage| { + router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); + router.custom.set_pool(storage, pool_id_y, &pool_y).unwrap(); + router.custom.set_pool(storage, pool_id_z, &pool_z).unwrap(); + }); + + let contract_addr = instantiate_contract(&mut app); + + let res = app.execute_contract( + owner, + contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "atom".to_string(), + denom_out: "mars".to_string(), + route: OsmosisRoute { + steps: vec![ + Step { + pool_id: pool_id_x, + denom_out: "osmo".to_string(), + }, + Step { + pool_id: pool_id_y, + denom_out: "usdc".to_string(), + }, + Step { + pool_id: pool_id_y, + denom_out: "osmo".to_string(), + }, + Step { + pool_id: pool_id_z, + denom_out: "mars".to_string(), + }, + ], + }, + }, + &[], + ); + + // invalid - route contains a loop + // this example: ATOM -> OSMO -> USDC -> OSMO -> MARS + assert_err( + res, + ContractError::InvalidRoute { + reason: "route contains a loop: denom osmo seen twice".to_string(), + }, + ); +} + +#[test] +fn test_step_output_does_not_match() { + let owner = Addr::unchecked("owner"); + let mut app = mock_osmosis_app(); + + let coin_a = coin(6_000_000, "atom"); + let coin_b = coin(1_500_000, "osmo"); + let pool_id_x = 43; + let pool_x = Pool::new(coin_a, coin_b); + + app.init_modules(|router, _, storage| { + router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); + }); + + let contract_addr = instantiate_contract(&mut app); + + let res = app.execute_contract( + owner, + contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "atom".to_string(), + denom_out: "mars".to_string(), + route: OsmosisRoute { + steps: vec![Step { + pool_id: pool_id_x, + denom_out: "osmo".to_string(), + }], + }, + }, + &[], + ); + + assert_err( + res, + ContractError::InvalidRoute { + reason: "the route's output denom osmo does not match the desired output mars" + .to_string(), + }, + ); +} + +#[test] +fn test_set_route_success() { + let owner = Addr::unchecked("owner"); + let mut app = mock_osmosis_app(); + let contract_addr = instantiate_contract(&mut app); + + let coin_a = coin(6_000_000, "mars"); + let coin_b = coin(1_500_000, "osmo"); + let pool_id_x = 43; + let pool_x = Pool::new(coin_a, coin_b); + + let coin_c = coin(100_000, "weth"); + let coin_d = coin(1_000_000, "osmo"); + let pool_id_y = 15; + let pool_y = Pool::new(coin_c, coin_d); + + app.init_modules(|router, _, storage| { + router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); + router.custom.set_pool(storage, pool_id_y, &pool_y).unwrap(); + }); + + app.execute_contract( + owner, + contract_addr.clone(), + &ExecuteMsg::SetRoute { + denom_in: "mars".to_string(), + denom_out: "weth".to_string(), + route: OsmosisRoute { + steps: vec![ + Step { + pool_id: pool_id_x, + denom_out: "osmo".to_string(), + }, + Step { + pool_id: pool_id_y, + denom_out: "weth".to_string(), + }, + ], + }, + }, + &[], + ) + .unwrap(); + + let res: RouteResponse = app + .wrap() + .query_wasm_smart( + contract_addr.to_string(), + &QueryMsg::Route { + denom_in: "mars".to_string(), + denom_out: "weth".to_string(), + }, + ) + .unwrap(); + + assert_eq!(res.denom_in, "mars".to_string()); + assert_eq!(res.denom_out, "weth".to_string()); + assert_eq!(res.route.to_string(), "43:osmo|15:weth".to_string()); +} diff --git a/contracts/swapper/osmosis/tests/test_swap.rs b/contracts/swapper/osmosis/tests/test_swap.rs new file mode 100644 index 000000000..761e04b85 --- /dev/null +++ b/contracts/swapper/osmosis/tests/test_swap.rs @@ -0,0 +1,201 @@ +use cosmwasm_std::{coin, Addr, Decimal, Querier, QuerierResult, QuerierWrapper, Uint128}; +use cw_multi_test::Executor; +use osmo_bindings::Step; +use osmo_bindings_test::{OsmosisApp, OsmosisError, Pool}; + +use rover::adapters::swap::ExecuteMsg; +use rover::error::ContractError as RoverError; +use swapper_base::ContractError; +use swapper_base::Route; +use swapper_osmosis::route::OsmosisRoute; + +use crate::helpers::mock_osmosis_app; +use crate::helpers::{assert_err, instantiate_contract}; + +pub mod helpers; + +#[test] +fn test_transfer_callback_only_internal() { + let mut app = mock_osmosis_app(); + let contract_addr = instantiate_contract(&mut app); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = app.execute_contract( + bad_guy.clone(), + contract_addr, + &ExecuteMsg::::TransferResult { + recipient: bad_guy.clone(), + denom_in: "mars".to_string(), + denom_out: "osmo".to_string(), + }, + &[], + ); + + assert_err( + res, + ContractError::Rover(RoverError::Unauthorized { + user: bad_guy.to_string(), + action: "transfer result".to_string(), + }), + ); +} + +#[test] +fn test_swap_exact_in_slippage_too_high() { + pub struct MockQuerier<'a> { + app: &'a OsmosisApp, + } + impl Querier for MockQuerier<'_> { + fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { + self.app.raw_query(bin_request) + } + } + + let owner = Addr::unchecked("owner"); + let whale = Addr::unchecked("whale"); + let mut app = mock_osmosis_app(); + let contract_addr = instantiate_contract(&mut app); + + let coin_a = coin(6_000_000, "mars"); + let coin_b = coin(1_500_000, "osmo"); + let pool_id_x = 43; + let pool_x = Pool::new(coin_a, coin_b.clone()); + + app.init_modules(|router, _, storage| { + router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); + router + .bank + .init_balance(storage, &owner, vec![coin(10_000, "mars")]) + .unwrap(); + router + .bank + .init_balance(storage, &whale, vec![coin(1_000_000, "mars")]) + .unwrap(); + }); + + let route = OsmosisRoute { + steps: vec![Step { + pool_id: pool_id_x, + denom_out: coin_b.denom, + }], + }; + + app.execute_contract( + owner.clone(), + contract_addr.clone(), + &ExecuteMsg::SetRoute { + denom_in: "mars".to_string(), + denom_out: "osmo".to_string(), + route: route.clone(), + }, + &[], + ) + .unwrap(); + + let querier = MockQuerier { app: &app }; + let mock_querier = QuerierWrapper::new(&querier); + + // Simulate real-time slippage by generating swapper message first, changing pool ratio, and then swapping with that message + let msg = route + .build_exact_in_swap_msg( + &mock_querier, + contract_addr.clone(), + &coin(10_000, "mars"), + Decimal::from_atomics(6u128, 2).unwrap(), + ) + .unwrap(); + + // whale does a huge trade to make mars less valuable + app.execute_contract( + whale, + contract_addr, + &ExecuteMsg::::SwapExactIn { + coin_in: coin(1_000_000, "mars"), + denom_out: "osmo".to_string(), + slippage: Decimal::from_atomics(1u128, 0).unwrap(), + }, + &[coin(1_000_000, "mars")], + ) + .unwrap(); + + // Resume initial user's trade but the output is less than slippage allowance + let res = app.execute(owner, msg); + + let error: OsmosisError = res.unwrap_err().downcast().unwrap(); + assert_eq!(error, OsmosisError::PriceTooLow); +} + +#[test] +fn test_swap_exact_in_success() { + let owner = Addr::unchecked("owner"); + let mut app = mock_osmosis_app(); + let contract_addr = instantiate_contract(&mut app); + + let coin_a = coin(6_000_000, "mars"); + let coin_b = coin(1_500_000, "osmo"); + let pool_id_x = 43; + let pool_x = Pool::new(coin_a, coin_b); + + app.init_modules(|router, _, storage| { + router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); + router + .bank + .init_balance(storage, &owner, vec![coin(10_000, "mars")]) + .unwrap(); + }); + + app.execute_contract( + owner.clone(), + contract_addr.clone(), + &ExecuteMsg::SetRoute { + denom_in: "mars".to_string(), + denom_out: "osmo".to_string(), + route: OsmosisRoute { + steps: vec![Step { + pool_id: pool_id_x, + denom_out: "osmo".to_string(), + }], + }, + }, + &[], + ) + .unwrap(); + + let mars_balance = app.wrap().query_balance(owner.to_string(), "mars").unwrap(); + let osmo_balance = app.wrap().query_balance(owner.to_string(), "osmo").unwrap(); + + assert_eq!(mars_balance.amount, Uint128::new(10_000)); + assert_eq!(osmo_balance.amount, Uint128::zero()); + + app.execute_contract( + owner.clone(), + contract_addr.clone(), + &ExecuteMsg::::SwapExactIn { + coin_in: coin(10_000, "mars"), + denom_out: "osmo".to_string(), + slippage: Decimal::from_atomics(6u128, 2).unwrap(), + }, + &[coin(10_000, "mars")], + ) + .unwrap(); + + // Assert user receives their new tokens + let mars_balance = app.wrap().query_balance(owner.to_string(), "mars").unwrap(); + let osmo_balance = app.wrap().query_balance(owner.to_string(), "osmo").unwrap(); + + assert_eq!(mars_balance.amount, Uint128::zero()); + assert_eq!(osmo_balance.amount, Uint128::new(2489)); + + // Assert no tokens in contract left over + let mars_balance = app + .wrap() + .query_balance(contract_addr.to_string(), "mars") + .unwrap(); + let osmo_balance = app + .wrap() + .query_balance(contract_addr.to_string(), "osmo") + .unwrap(); + + assert_eq!(mars_balance.amount, Uint128::zero()); + assert_eq!(osmo_balance.amount, Uint128::zero()); +} diff --git a/contracts/swapper/osmosis/tests/test_update_config.rs b/contracts/swapper/osmosis/tests/test_update_config.rs new file mode 100644 index 000000000..8f53760f2 --- /dev/null +++ b/contracts/swapper/osmosis/tests/test_update_config.rs @@ -0,0 +1,84 @@ +use cosmwasm_std::Addr; +use cw_multi_test::Executor; + +use rover::adapters::swap::{Config, ExecuteMsg, QueryMsg}; +use rover::error::ContractError as RoverError; +use swapper_base::ContractError; +use swapper_osmosis::route::OsmosisRoute; + +use crate::helpers::mock_osmosis_app; +use crate::helpers::{assert_err, instantiate_contract}; + +pub mod helpers; + +#[test] +fn test_only_owner_can_update_config() { + let mut app = mock_osmosis_app(); + let contract_addr = instantiate_contract(&mut app); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = app.execute_contract( + bad_guy.clone(), + contract_addr, + &ExecuteMsg::::UpdateConfig { + owner: Some(bad_guy.to_string()), + }, + &[], + ); + + assert_err( + res, + ContractError::Rover(RoverError::Unauthorized { + user: bad_guy.to_string(), + action: "update owner".to_string(), + }), + ); +} + +#[test] +fn test_update_config_works_with_full_config() { + let owner = Addr::unchecked("owner"); + let mut app = mock_osmosis_app(); + let contract_addr = instantiate_contract(&mut app); + + let new_owner = Addr::unchecked("new_owner"); + app.execute_contract( + owner.clone(), + contract_addr.clone(), + &ExecuteMsg::::UpdateConfig { + owner: Some(new_owner.to_string()), + }, + &[], + ) + .unwrap(); + + let new_config: Config = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + assert_ne!(new_config.owner, owner.to_string()); + assert_eq!(new_config.owner, new_owner.to_string()); +} + +#[test] +fn test_update_config_does_nothing_when_nothing_is_passed() { + let owner = Addr::unchecked("owner"); + let mut app = mock_osmosis_app(); + let contract_addr = instantiate_contract(&mut app); + + app.execute_contract( + owner.clone(), + contract_addr.clone(), + &ExecuteMsg::::UpdateConfig { owner: None }, + &[], + ) + .unwrap(); + + let new_config: Config = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + assert_eq!(new_config.owner, owner.to_string()); +} diff --git a/packages/rover/src/adapters/mod.rs b/packages/rover/src/adapters/mod.rs index 68223958c..89c29b93b 100644 --- a/packages/rover/src/adapters/mod.rs +++ b/packages/rover/src/adapters/mod.rs @@ -2,6 +2,8 @@ mod oracle; mod red_bank; mod vault; +pub mod swap; + pub use self::oracle::*; pub use self::red_bank::*; pub use self::vault::*; diff --git a/packages/rover/src/adapters/swap/mod.rs b/packages/rover/src/adapters/swap/mod.rs new file mode 100644 index 000000000..2fcab0307 --- /dev/null +++ b/packages/rover/src/adapters/swap/mod.rs @@ -0,0 +1,5 @@ +mod msgs; +mod swapper; + +pub use self::msgs::*; +pub use self::swapper::*; diff --git a/packages/rover/src/adapters/swap/msgs.rs b/packages/rover/src/adapters/swap/msgs.rs new file mode 100644 index 000000000..591b13e2d --- /dev/null +++ b/packages/rover/src/adapters/swap/msgs.rs @@ -0,0 +1,71 @@ +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Config { + /// The contract's owner, who can update config + pub owner: T, +} + +pub type InstantiateMsg = Config; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + /// Update contract config + UpdateConfig { owner: Option }, + /// Configure the route for swapping an asset + /// + /// This is chain-specific, and can include parameters such as slippage tolerance and the routes + /// for multi-step swaps + SetRoute { + denom_in: String, + denom_out: String, + route: Route, + }, + /// Perform a swapper with an exact-in amount. Requires slippage allowance %. + SwapExactIn { + coin_in: Coin, + denom_out: String, + slippage: Decimal, + }, + /// Send swapper results back to swapper. Also refunds extra if sent more than needed. Internal use only. + TransferResult { + recipient: Addr, + denom_in: String, + denom_out: String, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + /// Query contract config. Returns `Config` + Config {}, + /// Get route for swapping an input denom into an output denom; response: `RouteResponse` + Route { denom_in: String, denom_out: String }, + /// Enumerate all swapper routes; response: `RoutesResponse` + Routes { + start_after: Option<(String, String)>, + limit: Option, + }, + /// Return current spot price swapping In for Out + /// Warning: Do not use this as an oracle price feed. Use Mars-Oracle for pricing. + /// Returns `EstimateExactInSwapResponse` + EstimateExactInSwap { coin_in: Coin, denom_out: String }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct RouteResponse { + pub denom_in: String, + pub denom_out: String, + pub route: Route, +} + +pub type RoutesResponse = Vec>; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct EstimateExactInSwapResponse { + pub amount: Uint128, +} diff --git a/packages/rover/src/adapters/swap/swapper.rs b/packages/rover/src/adapters/swap/swapper.rs new file mode 100644 index 000000000..03c7e9c60 --- /dev/null +++ b/packages/rover/src/adapters/swap/swapper.rs @@ -0,0 +1,53 @@ +use cosmwasm_std::{to_binary, Addr, Api, Coin, CosmosMsg, Decimal, Empty, StdResult, WasmMsg}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::adapters::swap::ExecuteMsg; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct SwapperBase(T); + +impl SwapperBase { + pub fn new(address: T) -> SwapperBase { + SwapperBase(address) + } + + pub fn address(&self) -> &T { + &self.0 + } +} + +pub type SwapperUnchecked = SwapperBase; +pub type Swapper = SwapperBase; + +impl From for SwapperUnchecked { + fn from(s: Swapper) -> Self { + Self(s.address().to_string()) + } +} + +impl SwapperUnchecked { + pub fn check(&self, api: &dyn Api) -> StdResult { + Ok(SwapperBase::new(api.addr_validate(self.address())?)) + } +} + +impl Swapper { + /// Generate message for performing a swapper + pub fn swap_exact_in_msg( + &self, + coin_in: &Coin, + denom_out: &str, + slippage: Decimal, + ) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address().to_string(), + msg: to_binary(&ExecuteMsg::::SwapExactIn { + coin_in: coin_in.clone(), + denom_out: denom_out.to_string(), + slippage, + })?, + funds: vec![coin_in.clone()], + })) + } +} diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index ae6635b4c..7da6a4a0c 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -62,6 +62,12 @@ pub enum Action { /// The coin they wish to acquire from the liquidatee (amount returned will include the bonus) request_coin_denom: String, }, + /// Perform a swapper with an exact-in amount. Requires slippage allowance %. + SwapExactIn { + coin_in: Coin, + denom_out: String, + slippage: Decimal, + }, } /// Internal actions made by the contract with pre-validated inputs @@ -110,6 +116,20 @@ pub enum CallbackMsg { token_id: String, previous_health_factor: Decimal, }, + /// Perform a swapper with an exact-in amount. Requires slippage allowance %. + SwapExactIn { + token_id: String, + coin_in: Coin, + denom_out: String, + slippage: Decimal, + }, + /// Used to update the coin balance of account after an async action + UpdateCoinBalances { + /// Account that needs coin balance adjustment + token_id: String, + /// Total balances for coins in Rover prior to withdraw + previous_balances: Vec, + }, } impl CallbackMsg { diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index b65025fb7..b3bd5c315 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -2,6 +2,7 @@ use cosmwasm_std::Decimal; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::adapters::swap::SwapperUnchecked; use crate::adapters::{OracleUnchecked, RedBankUnchecked, VaultUnchecked}; #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] @@ -20,6 +21,8 @@ pub struct InstantiateMsg { pub max_liquidation_bonus: Decimal, /// The maximum percent a liquidator can decrease the debt amount of the liquidatee pub max_close_factor: Decimal, + /// Helper contract for making swaps + pub swapper: SwapperUnchecked, } /// Used when you want to update fields on Instantiate config @@ -33,4 +36,5 @@ pub struct ConfigUpdates { pub oracle: Option, pub max_liquidation_bonus: Option, pub max_close_factor: Option, + pub swapper: Option, } diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 53e7c31ab..d31819e03 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -160,6 +160,7 @@ pub struct ConfigResponse { pub oracle: String, pub max_liquidation_bonus: Decimal, pub max_close_factor: Decimal, + pub swapper: String, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] From 2d073a7b7e2063073d35349d2e4aa36228d8ff13 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 15 Sep 2022 11:25:45 +0200 Subject: [PATCH 055/218] Add support for withdrawing+force out of vaults (#17) * Add support for withdrawing+force out of vaults * review updates --- contracts/credit-manager/src/execute.rs | 17 +- contracts/credit-manager/src/swap.rs | 6 +- .../src/update_coin_balances.rs | 29 +- contracts/credit-manager/src/utils.rs | 13 +- contracts/credit-manager/src/vault/deposit.rs | 5 +- contracts/credit-manager/src/vault/mod.rs | 3 + contracts/credit-manager/src/vault/utils.rs | 41 +++ .../credit-manager/src/vault/withdraw.rs | 51 +++ .../tests/test_vault_withdraw.rs | 329 ++++++++++++++++++ contracts/mock-vault/src/contract.rs | 3 + contracts/mock-vault/src/error.rs | 6 + contracts/mock-vault/src/lib.rs | 1 + contracts/mock-vault/src/query.rs | 12 +- contracts/mock-vault/src/withdraw.rs | 71 ++++ packages/rover/src/adapters/vault.rs | 24 ++ packages/rover/src/msg/execute.rs | 18 + packages/rover/src/msg/vault.rs | 5 + 17 files changed, 596 insertions(+), 38 deletions(-) create mode 100644 contracts/credit-manager/src/vault/utils.rs create mode 100644 contracts/credit-manager/src/vault/withdraw.rs create mode 100644 contracts/credit-manager/tests/test_vault_withdraw.rs create mode 100644 contracts/mock-vault/src/withdraw.rs diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 4f0ed3e53..65e255197 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -13,7 +13,7 @@ use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, }; -use crate::vault::{deposit_into_vault, update_vault_coin_balance}; +use crate::vault::{deposit_into_vault, update_vault_coin_balance, withdraw_from_vault}; use crate::swap::swap_exact_in; use crate::update_coin_balances::update_coin_balances; @@ -199,6 +199,11 @@ pub fn dispatch_actions( denom_out: denom_out.to_string(), slippage: *slippage, }), + Action::VaultWithdraw { vault, amount } => callbacks.push(CallbackMsg::VaultWithdraw { + token_id: token_id.to_string(), + vault: vault.check(deps.api)?, + amount: *amount, + }), } } @@ -286,6 +291,16 @@ pub fn execute_callback( token_id, previous_balances, } => update_coin_balances(deps, env, &token_id, &previous_balances), + CallbackMsg::VaultWithdraw { + token_id, + vault, + amount, + } => withdraw_from_vault(deps, env, &token_id, vault, amount, false), + CallbackMsg::VaultForceWithdraw { + token_id, + vault, + amount, + } => withdraw_from_vault(deps, env, &token_id, vault, amount, true), } } diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs index d71948616..9e1b6259c 100644 --- a/contracts/credit-manager/src/swap.rs +++ b/contracts/credit-manager/src/swap.rs @@ -6,7 +6,7 @@ use rover::msg::ExecuteMsg; use rover::NftTokenId; use crate::state::SWAPPER; -use crate::update_coin_balances::query_balance; +use crate::update_coin_balances::query_balances; use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance}; pub fn swap_exact_in( @@ -26,13 +26,13 @@ pub fn swap_exact_in( decrement_coin_balance(deps.storage, token_id, &coin_in)?; // Updates coin balances for account after the swap has taken place - let previous_balance = query_balance(deps.as_ref(), &env, denom_out)?; + let previous_balances = query_balances(deps.as_ref(), &env.contract.address, &[denom_out])?; let update_coin_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: env.contract.address.to_string(), funds: vec![], msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalances { token_id: token_id.to_string(), - previous_balances: vec![previous_balance], + previous_balances, }))?, }); diff --git a/contracts/credit-manager/src/update_coin_balances.rs b/contracts/credit-manager/src/update_coin_balances.rs index 58e1446b0..fc4b8cb04 100644 --- a/contracts/credit-manager/src/update_coin_balances.rs +++ b/contracts/credit-manager/src/update_coin_balances.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - BalanceResponse, BankQuery, Coin, Deps, DepsMut, Env, QueryRequest, Response, StdResult, + Addr, BalanceResponse, BankQuery, Coin, Deps, DepsMut, Env, QueryRequest, Response, StdResult, }; use rover::error::ContractResult; @@ -7,9 +7,9 @@ use rover::NftTokenId; use crate::utils::{decrement_coin_balance, increment_coin_balance}; -pub fn query_balance(deps: Deps, env: &Env, denom: &str) -> StdResult { +pub fn query_balance(deps: Deps, addr: &Addr, denom: &str) -> StdResult { let res: BalanceResponse = deps.querier.query(&QueryRequest::Bank(BankQuery::Balance { - address: env.contract.address.to_string(), + address: addr.to_string(), denom: denom.to_string(), }))?; Ok(Coin { @@ -18,6 +18,13 @@ pub fn query_balance(deps: Deps, env: &Env, denom: &str) -> StdResult { }) } +pub fn query_balances(deps: Deps, addr: &Addr, denoms: &[&str]) -> StdResult> { + denoms + .iter() + .map(|denom| query_balance(deps, addr, denom)) + .collect() +} + pub fn update_coin_balances( deps: DepsMut, env: Env, @@ -27,35 +34,33 @@ pub fn update_coin_balances( let mut response = Response::new(); for prev in previous_balances { - let curr = query_balance(deps.as_ref(), &env, &prev.denom)?; + let curr = query_balance(deps.as_ref(), &env.contract.address, &prev.denom)?; if prev.amount > curr.amount { - let new_amount = prev.amount.checked_sub(curr.amount)?; + let amount_to_reduce = prev.amount.checked_sub(curr.amount)?; decrement_coin_balance( deps.storage, token_id, &Coin { denom: curr.denom.clone(), - amount: new_amount, + amount: amount_to_reduce, }, )?; response = response - .clone() .add_attribute("denom", curr.denom.clone()) - .add_attribute("decremented", new_amount); + .add_attribute("decremented", amount_to_reduce); } else { - let new_amount = curr.amount.checked_sub(prev.amount)?; + let amount_to_increment = curr.amount.checked_sub(prev.amount)?; increment_coin_balance( deps.storage, token_id, &Coin { denom: curr.denom.clone(), - amount: new_amount, + amount: amount_to_increment, }, )?; response = response - .clone() .add_attribute("denom", curr.denom.clone()) - .add_attribute("incremented", new_amount); + .add_attribute("incremented", amount_to_increment); } } diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index da3dea31f..fa8bb8892 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -1,12 +1,9 @@ use cosmwasm_std::{Addr, Coin, Decimal, Deps, Storage, Uint128}; -use rover::adapters::Vault; use rover::error::{ContractError, ContractResult}; use rover::msg::query::CoinValue; -use crate::state::{ - ALLOWED_COINS, ALLOWED_VAULTS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES, -}; +use crate::state::{ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES}; pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, denom: &str) -> ContractResult<()> { let is_whitelisted = ALLOWED_COINS.has(storage, denom); @@ -25,14 +22,6 @@ pub fn assert_coins_are_whitelisted( .try_for_each(|denom| assert_coin_is_whitelisted(storage, denom)) } -pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> ContractResult<()> { - let is_whitelisted = ALLOWED_VAULTS.has(storage, vault.address()); - if !is_whitelisted { - return Err(ContractError::NotWhitelisted(vault.address().to_string())); - } - Ok(()) -} - pub fn increment_coin_balance( storage: &mut dyn Storage, token_id: &str, diff --git a/contracts/credit-manager/src/vault/deposit.rs b/contracts/credit-manager/src/vault/deposit.rs index 3c98b43da..39544b9c1 100644 --- a/contracts/credit-manager/src/vault/deposit.rs +++ b/contracts/credit-manager/src/vault/deposit.rs @@ -10,9 +10,8 @@ use rover::msg::ExecuteMsg; use rover::NftTokenId; use crate::state::VAULT_POSITIONS; -use crate::utils::{ - assert_coins_are_whitelisted, assert_vault_is_whitelisted, decrement_coin_balance, -}; +use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance}; +use crate::vault::utils::assert_vault_is_whitelisted; pub fn deposit_into_vault( deps: DepsMut, diff --git a/contracts/credit-manager/src/vault/mod.rs b/contracts/credit-manager/src/vault/mod.rs index 2fec8f0f5..27605ee84 100644 --- a/contracts/credit-manager/src/vault/mod.rs +++ b/contracts/credit-manager/src/vault/mod.rs @@ -1,3 +1,6 @@ pub use self::deposit::*; +pub use self::withdraw::*; mod deposit; +mod utils; +mod withdraw; diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs new file mode 100644 index 000000000..5ef1efeac --- /dev/null +++ b/contracts/credit-manager/src/vault/utils.rs @@ -0,0 +1,41 @@ +use cosmwasm_std::{Storage, Uint128}; + +use rover::adapters::{Vault, VaultPosition}; +use rover::error::{ContractError, ContractResult}; + +use crate::state::{ALLOWED_VAULTS, VAULT_POSITIONS}; + +pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> ContractResult<()> { + let is_whitelisted = ALLOWED_VAULTS.has(storage, vault.address()); + if !is_whitelisted { + return Err(ContractError::NotWhitelisted(vault.address().to_string())); + } + Ok(()) +} + +pub fn decrement_vault_position( + storage: &mut dyn Storage, + token_id: &str, + vault: &Vault, + amount: Uint128, + force: bool, +) -> ContractResult { + let path = VAULT_POSITIONS.key((token_id, vault.address().clone())); + let mut position = path.load(storage)?; + + // Force indicates that the vault is one with a required lockup that needs to be broken + // In this case, we'll need to withdraw from the locked bucket + if force { + position.locked = position.locked.checked_sub(amount)?; + } else { + position.unlocked = position.unlocked.checked_sub(amount)?; + } + + if position == VaultPosition::default() { + path.remove(storage); + } else { + path.save(storage, &position)?; + } + + Ok(position) +} diff --git a/contracts/credit-manager/src/vault/withdraw.rs b/contracts/credit-manager/src/vault/withdraw.rs new file mode 100644 index 000000000..836f99731 --- /dev/null +++ b/contracts/credit-manager/src/vault/withdraw.rs @@ -0,0 +1,51 @@ +use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, Uint128, WasmMsg}; + +use rover::adapters::Vault; +use rover::error::ContractResult; +use rover::msg::execute::CallbackMsg; +use rover::msg::ExecuteMsg as RoverExecuteMsg; +use rover::NftTokenId; + +use crate::update_coin_balances::query_balances; +use crate::vault::utils::{assert_vault_is_whitelisted, decrement_vault_position}; + +pub fn withdraw_from_vault( + deps: DepsMut, + env: Env, + token_id: NftTokenId, + vault: Vault, + amount: Uint128, + force: bool, +) -> ContractResult { + assert_vault_is_whitelisted(deps.storage, &vault)?; + + decrement_vault_position(deps.storage, token_id, &vault, amount, force)?; + + // Sends vault coins to vault in exchange for underlying assets + let withdraw_msg = vault.withdraw_msg(&deps.querier, amount, force)?; + + // Updates coin balances for account after a vault withdraw has taken place + let vault_info = vault.query_vault_info(&deps.querier)?; + let denoms = vault_info + .coins + .iter() + .map(|v| v.denom.as_str()) + .collect::>(); + let previous_balances = + query_balances(deps.as_ref(), &env.contract.address, denoms.as_slice())?; + let update_coin_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + funds: vec![], + msg: to_binary(&RoverExecuteMsg::Callback( + CallbackMsg::UpdateCoinBalances { + token_id: token_id.to_string(), + previous_balances, + }, + ))?, + }); + + Ok(Response::new() + .add_message(withdraw_msg) + .add_message(update_coin_balance_msg) + .add_attribute("action", "rover/credit_manager/vault/withdraw")) +} diff --git a/contracts/credit-manager/tests/test_vault_withdraw.rs b/contracts/credit-manager/tests/test_vault_withdraw.rs new file mode 100644 index 000000000..3c8e733a1 --- /dev/null +++ b/contracts/credit-manager/tests/test_vault_withdraw.rs @@ -0,0 +1,329 @@ +use cosmwasm_std::OverflowOperation::Sub; +use cosmwasm_std::{Addr, Coin, OverflowError, Uint128}; + +use mock_vault::contract::STARTING_VAULT_SHARES; +use rover::adapters::VaultBase; +use rover::error::ContractError; +use rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; +use rover::msg::execute::Action::{Deposit, VaultDeposit, VaultWithdraw}; +use rover::msg::execute::CallbackMsg; +use rover::msg::query::CoinValue; + +use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv, VaultTestInfo}; + +pub mod helpers; + +#[test] +fn test_only_owner_of_token_can_withdraw_from_vault() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().build().unwrap(); + + let token_id = mock.create_credit_account(&user).unwrap(); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.update_credit_account( + &token_id, + &bad_guy, + vec![VaultWithdraw { + vault: VaultBase::new("some_vault".to_string()), + amount: STARTING_VAULT_SHARES, + }], + &[], + ); + + assert_err( + res, + NotTokenOwner { + user: bad_guy.into(), + token_id, + }, + ) +} + +#[test] +fn test_can_only_take_action_on_whitelisted_vaults() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().build().unwrap(); + + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![VaultWithdraw { + vault: VaultBase::new("not_allowed_vault".to_string()), + amount: STARTING_VAULT_SHARES, + }], + &[], + ); + + assert_err(res, NotWhitelisted("not_allowed_vault".to_string())) +} + +#[test] +fn test_no_unlocked_vault_coins_to_withdraw() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + lp_token_denom: "uleverage".to_string(), + lockup: Some(213231), + asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, "uatom"), Coin::new(500u128, "uosmo")], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(Coin { + denom: uatom.denom, + amount: Uint128::from(200u128), + }), + Deposit(Coin { + denom: uosmo.denom, + amount: Uint128::from(200u128), + }), + VaultDeposit { + vault: vault.clone(), + coins: vec![Coin::new(100u128, "uatom"), Coin::new(100u128, "uosmo")], + }, + VaultWithdraw { + vault, + amount: STARTING_VAULT_SHARES, + }, + ], + &[Coin::new(200u128, "uatom"), Coin::new(200u128, "uosmo")], + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: Sub, + operand1: "0".to_string(), + operand2: STARTING_VAULT_SHARES.to_string(), + }), + ) +} + +#[test] +fn test_force_withdraw_can_only_be_called_by_rover() { + let leverage_vault = VaultTestInfo { + lp_token_denom: "uleverage".to_string(), + lockup: Some(213231), + asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_vaults(&[leverage_vault.clone()]) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let token_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.invoke_callback( + &user.clone(), + CallbackMsg::VaultForceWithdraw { + token_id, + vault: VaultBase::new(Addr::unchecked(vault.address().clone())), + amount: STARTING_VAULT_SHARES, + }, + ); + assert_err(res, ContractError::ExternalInvocation) +} + +#[test] +fn test_force_withdraw_breaks_lock() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + lp_token_denom: "uleverage".to_string(), + lockup: Some(213231), + asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, "uatom"), Coin::new(500u128, "uosmo")], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let token_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(Coin { + denom: uatom.denom, + amount: Uint128::from(200u128), + }), + Deposit(Coin { + denom: uosmo.denom, + amount: Uint128::from(200u128), + }), + VaultDeposit { + vault: vault.clone(), + coins: vec![Coin::new(100u128, "uatom"), Coin::new(100u128, "uosmo")], + }, + ], + &[Coin::new(200u128, "uatom"), Coin::new(200u128, "uosmo")], + ) + .unwrap(); + + // Assert token's position + let res = mock.query_position(&token_id); + assert_eq!(res.vault_positions.len(), 1); + let v = res.vault_positions.first().unwrap(); + assert_eq!(v.position.locked, STARTING_VAULT_SHARES); + + mock.invoke_callback( + &mock.rover.clone(), + CallbackMsg::VaultForceWithdraw { + token_id: token_id.clone(), + vault: VaultBase::new(Addr::unchecked(vault.address().clone())), + amount: STARTING_VAULT_SHARES, + }, + ) + .unwrap(); + + // Assert token's updated position + let res = mock.query_position(&token_id); + assert_eq!(res.vault_positions.len(), 0); + let atom = get_coin("uatom", &res.coins); + assert_eq!(atom.amount, Uint128::from(200u128)); + let osmo = get_coin("uosmo", &res.coins); + assert_eq!(osmo.amount, Uint128::from(200u128)); + + // Assert Rover indeed has those on hand in the bank + let atom = mock.query_balance(&mock.rover, "uatom"); + assert_eq!(atom.amount, Uint128::from(200u128)); + let osmo = mock.query_balance(&mock.rover, "uosmo"); + assert_eq!(osmo.amount, Uint128::from(200u128)); + + // Assert Rover does not have the vault tokens anymore + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + assert_eq!(Uint128::zero(), lp_balance.amount); +} + +#[test] +fn test_withdraw_with_unlocked_vault_coins() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + lp_token_denom: "uleverage".to_string(), + lockup: None, + asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(300u128, "uatom"), Coin::new(500u128, "uosmo")], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let token_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &token_id, + &user, + vec![ + Deposit(Coin { + denom: uatom.denom, + amount: Uint128::from(200u128), + }), + Deposit(Coin { + denom: uosmo.denom, + amount: Uint128::from(200u128), + }), + VaultDeposit { + vault: vault.clone(), + coins: vec![Coin::new(100u128, "uatom"), Coin::new(100u128, "uosmo")], + }, + ], + &[Coin::new(200u128, "uatom"), Coin::new(200u128, "uosmo")], + ) + .unwrap(); + + // Assert token's position + let res = mock.query_position(&token_id); + assert_eq!(res.vault_positions.len(), 1); + let v = res.vault_positions.first().unwrap(); + assert_eq!(v.position.unlocked, STARTING_VAULT_SHARES); + let atom = get_coin("uatom", &res.coins); + assert_eq!(atom.amount, Uint128::from(100u128)); + let osmo = get_coin("uosmo", &res.coins); + assert_eq!(osmo.amount, Uint128::from(100u128)); + + // Assert Rover's totals + let atom = mock.query_balance(&mock.rover, "uatom"); + assert_eq!(atom.amount, Uint128::from(100u128)); + let osmo = mock.query_balance(&mock.rover, "uosmo"); + assert_eq!(osmo.amount, Uint128::from(100u128)); + + // Assert Rover has the vault tokens + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); + + mock.update_credit_account( + &token_id, + &user, + vec![VaultWithdraw { + vault, + amount: STARTING_VAULT_SHARES, + }], + &[], + ) + .unwrap(); + + // Assert token's updated position + let res = mock.query_position(&token_id); + assert_eq!(res.vault_positions.len(), 0); + let atom = get_coin("uatom", &res.coins); + assert_eq!(atom.amount, Uint128::from(200u128)); + let osmo = get_coin("uosmo", &res.coins); + assert_eq!(osmo.amount, Uint128::from(200u128)); + + // Assert Rover indeed has those on hand in the bank + let atom = mock.query_balance(&mock.rover, "uatom"); + assert_eq!(atom.amount, Uint128::from(200u128)); + let osmo = mock.query_balance(&mock.rover, "uosmo"); + assert_eq!(osmo.amount, Uint128::from(200u128)); + + // Assert Rover does not have the vault tokens anymore + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + assert_eq!(Uint128::zero(), lp_balance.amount); +} + +fn get_coin(denom: &str, coins: &[CoinValue]) -> CoinValue { + coins.iter().find(|cv| cv.denom == denom).unwrap().clone() +} diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index c54a4751c..46e84a324 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -9,6 +9,7 @@ use crate::error::ContractError; use crate::msg::InstantiateMsg; use crate::query::{query_coins_for_shares, query_vault_info}; use crate::state::{ASSETS, CHAIN_BANK, LOCKUP_TIME, LP_TOKEN_DENOM, ORACLE}; +use crate::withdraw::{withdraw, withdraw_force}; pub const STARTING_VAULT_SHARES: Uint128 = Uint128::new(1_000_000); @@ -42,6 +43,8 @@ pub fn execute( ) -> Result { match msg { ExecuteMsg::Deposit {} => deposit(deps, info), + ExecuteMsg::Withdraw => withdraw(deps, info), + ExecuteMsg::ForceWithdraw => withdraw_force(deps, info), } } diff --git a/contracts/mock-vault/src/error.rs b/contracts/mock-vault/src/error.rs index 22cfa47d4..eb7d08110 100644 --- a/contracts/mock-vault/src/error.rs +++ b/contracts/mock-vault/src/error.rs @@ -12,4 +12,10 @@ pub enum ContractError { #[error("{0}")] CheckedMultiply(#[from] CheckedMultiplyRatioError), + + #[error("You must request an unlock first")] + UnlockRequired, + + #[error("Vault token not sent")] + VaultTokenNotSent, } diff --git a/contracts/mock-vault/src/lib.rs b/contracts/mock-vault/src/lib.rs index 7473747d8..4cf11fd34 100644 --- a/contracts/mock-vault/src/lib.rs +++ b/contracts/mock-vault/src/lib.rs @@ -4,3 +4,4 @@ pub mod error; pub mod msg; pub mod query; pub mod state; +pub mod withdraw; diff --git a/contracts/mock-vault/src/query.rs b/contracts/mock-vault/src/query.rs index a8bd19d8c..2f1056743 100644 --- a/contracts/mock-vault/src/query.rs +++ b/contracts/mock-vault/src/query.rs @@ -31,13 +31,11 @@ pub fn query_vault_info(deps: Deps) -> StdResult { } pub fn get_all_vault_coins(storage: &dyn Storage) -> StdResult> { - Ok(ASSETS + ASSETS .range(storage, None, None, Order::Ascending) - .collect::>>()? - .iter() - .map(|(denom, amount)| Coin { - denom: denom.clone(), - amount: *amount, + .map(|res| { + let (denom, amount) = res?; + Ok(Coin { denom, amount }) }) - .collect::>()) + .collect() } diff --git a/contracts/mock-vault/src/withdraw.rs b/contracts/mock-vault/src/withdraw.rs new file mode 100644 index 000000000..2b16c8f26 --- /dev/null +++ b/contracts/mock-vault/src/withdraw.rs @@ -0,0 +1,71 @@ +use cosmwasm_std::{ + Addr, BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdResult, Storage, Uint128, +}; + +use crate::error::ContractError; +use crate::query::query_coins_for_shares; +use crate::state::{ASSETS, CHAIN_BANK, LOCKUP_TIME, LP_TOKEN_DENOM, TOTAL_VAULT_SHARES}; + +pub fn withdraw(deps: DepsMut, info: MessageInfo) -> Result { + let lockup_time = LOCKUP_TIME.load(deps.storage)?; + if lockup_time.is_some() { + return Err(ContractError::UnlockRequired {}); + } + let vault_tokens = get_vault_token(deps.storage, info.funds.clone())?; + _exchange(deps.storage, info.sender, vault_tokens.amount) +} + +pub fn withdraw_force(deps: DepsMut, info: MessageInfo) -> Result { + let vault_tokens = get_vault_token(deps.storage, info.funds.clone())?; + _exchange(deps.storage, info.sender, vault_tokens.amount) +} + +/// Swap shares for underlying assets +pub fn _exchange( + storage: &mut dyn Storage, + send_to: Addr, + shares: Uint128, +) -> Result { + let coins = query_coins_for_shares(storage, shares)?; + + TOTAL_VAULT_SHARES.update(storage, |current_amount| -> StdResult<_> { + Ok(current_amount - shares) + })?; + + coins.iter().try_for_each(|asset| -> StdResult<()> { + ASSETS.update( + storage, + asset.clone().denom, + |current_amount| -> StdResult<_> { Ok(current_amount.unwrap() - asset.amount) }, + )?; + Ok(()) + })?; + + mock_lp_token_burn(storage, shares)?; + + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: send_to.to_string(), + amount: coins, + }); + + Ok(Response::new().add_message(transfer_msg)) +} + +pub fn get_vault_token(storage: &mut dyn Storage, funds: Vec) -> Result { + let vault_token_denom = LP_TOKEN_DENOM.load(storage)?; + let res = funds.iter().find(|coin| coin.denom == vault_token_denom); + match res { + Some(c) if !c.amount.is_zero() => Ok(Coin { + denom: c.denom.clone(), + amount: c.amount, + }), + _ => Err(ContractError::VaultTokenNotSent {}), + } +} + +fn mock_lp_token_burn(storage: &mut dyn Storage, amount: Uint128) -> StdResult<()> { + CHAIN_BANK.update(storage, |bank_amount| -> StdResult<_> { + Ok(bank_amount + amount) + })?; + Ok(()) +} diff --git a/packages/rover/src/adapters/vault.rs b/packages/rover/src/adapters/vault.rs index 3226b9d22..a75492b1d 100644 --- a/packages/rover/src/adapters/vault.rs +++ b/packages/rover/src/adapters/vault.rs @@ -71,6 +71,30 @@ impl Vault { Ok(deposit_msg) } + pub fn withdraw_msg( + &self, + querier: &QuerierWrapper, + amount: Uint128, + force: bool, + ) -> StdResult { + let vault_info = self.query_vault_info(querier)?; + let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address().to_string(), + funds: vec![Coin { + denom: vault_info.token_denom, + amount, + }], + msg: to_binary( + &(if force { + ExecuteMsg::ForceWithdraw {} + } else { + ExecuteMsg::Withdraw {} + }), + )?, + }); + Ok(withdraw_msg) + } + pub fn query_vault_info(&self, querier: &QuerierWrapper) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.0.to_string(), diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 7da6a4a0c..6f8fef6d1 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -45,6 +45,11 @@ pub enum Action { vault: VaultUnchecked, coins: Vec, }, + /// Withdraw underlying coins from vault + VaultWithdraw { + vault: VaultUnchecked, + amount: Uint128, + }, /// Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt /// denom/amount of what the liquidator wants to payoff and 2) the request coin denom which the /// liquidatee should have a balance of. The amount returned to liquidator will be the request coin @@ -104,6 +109,19 @@ pub enum CallbackMsg { /// Total vault coin balance in Rover previous_total_balance: Uint128, }, + /// Exchanges vault LP shares for assets + VaultWithdraw { + token_id: String, + vault: Vault, + amount: Uint128, + }, + /// A privileged action only to be used by Rover. Same as `VaultWithdraw` except it bypasses any lockup period + /// restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation. + VaultForceWithdraw { + token_id: String, + vault: Vault, + amount: Uint128, + }, /// Pay back debts of a liquidatable rover account for a bonus LiquidateCoin { liquidator_token_id: String, diff --git a/packages/rover/src/msg/vault.rs b/packages/rover/src/msg/vault.rs index fa90e3b0f..1aa2d1dc3 100644 --- a/packages/rover/src/msg/vault.rs +++ b/packages/rover/src/msg/vault.rs @@ -8,6 +8,11 @@ use serde::{Deserialize, Serialize}; pub enum ExecuteMsg { /// Enters list of `Vec` into a vault strategy in exchange for vault tokens. Deposit, + /// Withdraw underlying coins in vault by exchanging vault `Coin` + Withdraw, + /// A privileged action only to be used by Rover. Same as `Withdraw` except it bypasses any lockup period + /// restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation. + ForceWithdraw, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] From 85efa4cb60c45934e715e947aefdacd4c0f9f349 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 16 Sep 2022 11:53:44 +0200 Subject: [PATCH 056/218] Testnet deploy scripts + schema/types generation (#19) * Automatic typescript type generation * Review updates --- .gitignore | 1 + Cargo.lock | 245 +- Makefile.toml | 31 +- contracts/account-nft/Cargo.toml | 7 +- contracts/account-nft/examples/schema.rs | 11 + contracts/account-nft/src/msg/execute.rs | 2 +- contracts/account-nft/src/msg/query.rs | 43 +- contracts/account-nft/tests/test_ownership.rs | 6 +- contracts/credit-manager/Cargo.toml | 9 +- contracts/credit-manager/examples/schema.rs | 10 + contracts/credit-manager/src/execute.rs | 2 +- .../credit-manager/tests/helpers/types.rs | 2 +- contracts/credit-manager/tests/test_borrow.rs | 1 + contracts/mock-oracle/Cargo.toml | 5 +- contracts/mock-oracle/examples/schema.rs | 10 + contracts/mock-oracle/src/contract.rs | 11 +- contracts/mock-oracle/src/msg.rs | 7 +- contracts/mock-red-bank/Cargo.toml | 3 +- contracts/mock-red-bank/examples/schema.rs | 10 + contracts/mock-red-bank/src/msg.rs | 9 +- contracts/mock-vault/Cargo.toml | 5 +- contracts/mock-vault/examples/schema.rs | 11 + contracts/mock-vault/src/contract.rs | 9 +- contracts/swapper/base/Cargo.toml | 5 +- contracts/swapper/base/examples/schema.rs | 11 + contracts/swapper/mock/Cargo.toml | 6 +- contracts/swapper/mock/src/contract.rs | 6 +- contracts/swapper/osmosis/Cargo.toml | 4 +- packages/rover/Cargo.toml | 5 +- packages/rover/src/adapters/swap/msgs.rs | 18 +- packages/rover/src/msg/execute.rs | 8 +- packages/rover/src/msg/query.rs | 50 +- packages/rover/src/msg/vault.rs | 21 +- schemas/account-nft/account-nft.json | 1317 ++ schemas/credit-manager/credit-manager.json | 1668 +++ schemas/mock-oracle/mock-oracle.json | 135 + schemas/mock-red-bank/mock-red-bank.json | 400 + schemas/mock-vault/mock-vault.json | 224 + schemas/swapper-base/swapper-base.json | 396 + scripts/.eslintignore | 5 + scripts/.eslintrc | 10 - scripts/.eslintrc.cjs | 20 + scripts/.prettierignore | 1 + scripts/.prettierrc | 4 +- scripts/babel.config.js | 3 - scripts/codegen-tsconfig.json | 5 + scripts/codegen/index.ts | 39 + scripts/codegen/insertIgnores.ts | 24 + scripts/deploy/addresses/osmo-test-4.json | 8 + scripts/deploy/base/deployer.ts | 207 + scripts/deploy/base/index.ts | 53 + scripts/deploy/base/rover.ts | 143 + scripts/deploy/base/setupDeployer.ts | 36 + scripts/deploy/base/storage.ts | 34 + scripts/deploy/osmosis/config.ts | 33 + scripts/deploy/osmosis/index.ts | 6 + scripts/package-lock.json | 12070 ---------------- scripts/package.json | 42 +- scripts/tests/instantiate.test.ts | 102 - scripts/tsconfig.json | 12 +- scripts/types/config.ts | 26 + .../account-nft/AccountNft.client.ts | 635 + .../account-nft/AccountNft.react-query.ts | 535 + .../generated/account-nft/AccountNft.types.ts | 212 + scripts/types/generated/account-nft/bundle.ts | 13 + .../credit-manager/CreditManager.client.ts | 393 + .../CreditManager.react-query.ts | 473 + .../credit-manager/CreditManager.types.ts | 356 + .../types/generated/credit-manager/bundle.ts | 13 + .../mock-oracle/MockOracle.client.ts | 95 + .../mock-oracle/MockOracle.react-query.ts | 80 + .../generated/mock-oracle/MockOracle.types.ts | 30 + scripts/types/generated/mock-oracle/bundle.ts | 13 + .../mock-red-bank/MockRedBank.client.ts | 161 + .../mock-red-bank/MockRedBank.react-query.ts | 133 + .../mock-red-bank/MockRedBank.types.ts | 83 + .../types/generated/mock-red-bank/bundle.ts | 13 + .../generated/mock-vault/MockVault.client.ts | 127 + .../mock-vault/MockVault.react-query.ts | 129 + .../generated/mock-vault/MockVault.types.ts | 53 + scripts/types/generated/mock-vault/bundle.ts | 13 + .../swapper-base/SwapperBase.client.ts | 292 + .../swapper-base/SwapperBase.react-query.ts | 237 + .../swapper-base/SwapperBase.types.ts | 90 + .../types/generated/swapper-base/bundle.ts | 13 + scripts/types/instantiateMsgs.ts | 14 + scripts/types/storageItems.ts | 22 + scripts/utils/chalk.ts | 21 + scripts/utils/client.ts | 13 - scripts/utils/config.ts | 30 - scripts/utils/environment.ts | 12 + scripts/utils/test-wallets.ts | 92 - scripts/utils/types.ts | 7 - scripts/yarn.lock | 5050 +++++++ 94 files changed, 14529 insertions(+), 12531 deletions(-) create mode 100644 contracts/account-nft/examples/schema.rs create mode 100644 contracts/credit-manager/examples/schema.rs create mode 100644 contracts/mock-oracle/examples/schema.rs create mode 100644 contracts/mock-red-bank/examples/schema.rs create mode 100644 contracts/mock-vault/examples/schema.rs create mode 100644 contracts/swapper/base/examples/schema.rs create mode 100644 schemas/account-nft/account-nft.json create mode 100644 schemas/credit-manager/credit-manager.json create mode 100644 schemas/mock-oracle/mock-oracle.json create mode 100644 schemas/mock-red-bank/mock-red-bank.json create mode 100644 schemas/mock-vault/mock-vault.json create mode 100644 schemas/swapper-base/swapper-base.json create mode 100644 scripts/.eslintignore delete mode 100644 scripts/.eslintrc create mode 100644 scripts/.eslintrc.cjs create mode 100644 scripts/.prettierignore delete mode 100644 scripts/babel.config.js create mode 100644 scripts/codegen-tsconfig.json create mode 100644 scripts/codegen/index.ts create mode 100644 scripts/codegen/insertIgnores.ts create mode 100644 scripts/deploy/addresses/osmo-test-4.json create mode 100644 scripts/deploy/base/deployer.ts create mode 100644 scripts/deploy/base/index.ts create mode 100644 scripts/deploy/base/rover.ts create mode 100644 scripts/deploy/base/setupDeployer.ts create mode 100644 scripts/deploy/base/storage.ts create mode 100644 scripts/deploy/osmosis/config.ts create mode 100644 scripts/deploy/osmosis/index.ts delete mode 100644 scripts/package-lock.json delete mode 100644 scripts/tests/instantiate.test.ts create mode 100644 scripts/types/config.ts create mode 100644 scripts/types/generated/account-nft/AccountNft.client.ts create mode 100644 scripts/types/generated/account-nft/AccountNft.react-query.ts create mode 100644 scripts/types/generated/account-nft/AccountNft.types.ts create mode 100644 scripts/types/generated/account-nft/bundle.ts create mode 100644 scripts/types/generated/credit-manager/CreditManager.client.ts create mode 100644 scripts/types/generated/credit-manager/CreditManager.react-query.ts create mode 100644 scripts/types/generated/credit-manager/CreditManager.types.ts create mode 100644 scripts/types/generated/credit-manager/bundle.ts create mode 100644 scripts/types/generated/mock-oracle/MockOracle.client.ts create mode 100644 scripts/types/generated/mock-oracle/MockOracle.react-query.ts create mode 100644 scripts/types/generated/mock-oracle/MockOracle.types.ts create mode 100644 scripts/types/generated/mock-oracle/bundle.ts create mode 100644 scripts/types/generated/mock-red-bank/MockRedBank.client.ts create mode 100644 scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts create mode 100644 scripts/types/generated/mock-red-bank/MockRedBank.types.ts create mode 100644 scripts/types/generated/mock-red-bank/bundle.ts create mode 100644 scripts/types/generated/mock-vault/MockVault.client.ts create mode 100644 scripts/types/generated/mock-vault/MockVault.react-query.ts create mode 100644 scripts/types/generated/mock-vault/MockVault.types.ts create mode 100644 scripts/types/generated/mock-vault/bundle.ts create mode 100644 scripts/types/generated/swapper-base/SwapperBase.client.ts create mode 100644 scripts/types/generated/swapper-base/SwapperBase.react-query.ts create mode 100644 scripts/types/generated/swapper-base/SwapperBase.types.ts create mode 100644 scripts/types/generated/swapper-base/bundle.ts create mode 100644 scripts/types/instantiateMsgs.ts create mode 100644 scripts/types/storageItems.ts create mode 100644 scripts/utils/chalk.ts delete mode 100644 scripts/utils/client.ts delete mode 100644 scripts/utils/config.ts create mode 100644 scripts/utils/environment.ts delete mode 100644 scripts/utils/test-wallets.ts delete mode 100644 scripts/utils/types.ts create mode 100644 scripts/yarn.lock diff --git a/.gitignore b/.gitignore index df64e0632..44899f623 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ node_modules .idea # build artifacts +build artifacts # private scripts that I don't want to commit are prefixed by an underscore diff --git a/Cargo.lock b/Cargo.lock index b87bf9faa..5c34cf888 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,9 +7,10 @@ name = "account-nft" version = "0.1.0" dependencies = [ "anyhow", + "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.14.0", - "cw-storage-plus 0.14.0", + "cw-multi-test 0.15.0", + "cw-storage-plus 0.15.0", "cw721", "cw721-base", "schemars", @@ -49,6 +50,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -69,17 +79,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "const-oid" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" [[package]] name = "cosmwasm-crypto" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd" +checksum = "16c50d753d44148c7ff3279ac44b87b5b91e790f4c70db0e3900df211310a2bf" dependencies = [ - "digest", + "digest 0.10.3", "ed25519-zebra", "k256", "rand_core 0.6.3", @@ -88,22 +98,47 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.0.0" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9053ebe2ad85831e9f9e2124fa2b22807528a78b25cc447483ce2a4aadfba394" +dependencies = [ + "syn", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c742fc698a88cf02ea304cc2b5bc18ef975c5bb9eff93c3e44d2cd565e1d458" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" +checksum = "88a7c4c07be11add09dd3af3064c4f4cbc2dc99c6859129bdaf820131730e996" dependencies = [ + "proc-macro2", + "quote", "syn", ] [[package]] name = "cosmwasm-std" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" +checksum = "eb8da0ae28693d892af2944319b48adc23c42725dc0fe7271b8baa38c10865da" dependencies = [ "base64", "cosmwasm-crypto", "cosmwasm-derive", + "derivative", "forward_ref", "schemars", "serde", @@ -114,9 +149,9 @@ dependencies = [ [[package]] name = "cosmwasm-storage" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18403b07304d15d304dad11040d45bbcaf78d603b4be3fb5e2685c16f9229b5" +checksum = "3205d5210ba4c7b7cc7329b9607c37d00dc08263c2479fd99541a2194ebe3b22" dependencies = [ "cosmwasm-std", "serde", @@ -137,10 +172,11 @@ version = "0.1.0" dependencies = [ "account-nft", "anyhow", + "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.14.0", - "cw-storage-plus 0.14.0", - "cw2 0.14.0", + "cw-multi-test 0.15.0", + "cw-storage-plus 0.15.0", + "cw2 0.15.0", "cw721", "cw721-base", "itertools", @@ -162,9 +198,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.3.2" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +checksum = "9f2b443d17d49dad5ef0ede301c3179cc923b8822f3393b4d2c28c269dd4a122" dependencies = [ "generic-array", "rand_core 0.6.3", @@ -173,23 +209,23 @@ dependencies = [ ] [[package]] -name = "crypto-mac" -version = "0.11.1" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "subtle", + "typenum", ] [[package]] name = "curve25519-dalek" -version = "3.2.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", - "digest", + "digest 0.9.0", "rand_core 0.5.1", "subtle", "zeroize", @@ -216,15 +252,15 @@ dependencies = [ [[package]] name = "cw-multi-test" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca153120cf5b91af88be106b0c6c0263423d959bc813b1592982c02c4691a4ae" +checksum = "2519fd64b6b8c7c4e77c317e665bd19b438db3e8e4867241fa6a31cf060feda9" dependencies = [ "anyhow", "cosmwasm-std", "cosmwasm-storage", - "cw-storage-plus 0.14.0", - "cw-utils 0.14.0", + "cw-storage-plus 0.15.0", + "cw-utils 0.15.0", "derivative", "itertools", "prost", @@ -255,6 +291,17 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-storage-plus" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ba3fb5fad2dce94263d070848b2befc46b5c8e4929adfb9a3595267823d6ec" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cw-utils" version = "0.13.4" @@ -281,6 +328,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-utils" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a67007ff056f4cd034f361c8ed69780c0180959b9c8037c84f3caa78120faf5" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 0.15.0", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "cw2" version = "0.13.4" @@ -305,6 +367,19 @@ dependencies = [ "serde", ] +[[package]] +name = "cw2" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0a1924a28607bf7cb9fd6681a64feea3e5fa9a8cb71fb4d24cf33635b21065a" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.0", + "schemars", + "serde", +] + [[package]] name = "cw20" version = "0.14.0" @@ -364,11 +439,12 @@ dependencies = [ [[package]] name = "der" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f" dependencies = [ "const-oid", + "zeroize", ] [[package]] @@ -391,6 +467,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.3", + "crypto-common", + "subtle", +] + [[package]] name = "dyn-clone" version = "1.0.9" @@ -399,9 +486,9 @@ checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" [[package]] name = "ecdsa" -version = "0.13.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +checksum = "1826508d57f3140a2e8e3c307b19915a266c92a1b8c2f6bb54e29e5d72a394ae" dependencies = [ "der", "elliptic-curve", @@ -419,7 +506,7 @@ dependencies = [ "hex", "rand_core 0.6.3", "serde", - "sha2", + "sha2 0.9.9", "thiserror", "zeroize", ] @@ -432,16 +519,18 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" -version = "0.11.12" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ "base16ct", "crypto-bigint", "der", + "digest 0.10.3", "ff", "generic-array", "group", + "pkcs8", "rand_core 0.6.3", "sec1", "subtle", @@ -450,9 +539,9 @@ dependencies = [ [[package]] name = "ff" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +checksum = "df689201f395c6b90dfe87127685f8dbfc083a5e779e613575d8bd7314300c3e" dependencies = [ "rand_core 0.6.3", "subtle", @@ -498,9 +587,9 @@ dependencies = [ [[package]] name = "group" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +checksum = "7391856def869c1c81063a03457c676fbcd419709c3dfb33d8d319de484b154d" dependencies = [ "ff", "rand_core 0.6.3", @@ -515,12 +604,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hmac" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "crypto-mac", - "digest", + "digest 0.10.3", ] [[package]] @@ -540,15 +628,14 @@ checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "k256" -version = "0.10.4" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +checksum = "6db2573d3fd3e4cc741affc9b5ce1a8ce36cf29f09f80f36da4309d0ae6d7854" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "sec1", - "sha2", + "sha2 0.10.5", ] [[package]] @@ -581,8 +668,9 @@ dependencies = [ name = "mock-oracle" version = "1.0.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.14.0", + "cw-storage-plus 0.15.0", "mars-outpost", "schemars", "serde", @@ -592,6 +680,7 @@ dependencies = [ name = "mock-red-bank" version = "1.0.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.14.0", "mars-outpost", @@ -603,8 +692,9 @@ dependencies = [ name = "mock-vault" version = "1.0.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.14.0", + "cw-storage-plus 0.15.0", "rover", "schemars", "serde", @@ -647,13 +737,12 @@ dependencies = [ [[package]] name = "pkcs8" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ "der", "spki", - "zeroize", ] [[package]] @@ -717,9 +806,9 @@ dependencies = [ [[package]] name = "rfc6979" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +checksum = "88c86280f057430a52f4861551b092a01b419b8eacefc7c995eacb9dc132fe32" dependencies = [ "crypto-bigint", "hmac", @@ -730,8 +819,9 @@ dependencies = [ name = "rover" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.14.0", + "cw-storage-plus 0.15.0", "mars-health", "mars-outpost", "mock-oracle", @@ -773,10 +863,11 @@ dependencies = [ [[package]] name = "sec1" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ + "base16ct", "der", "generic-array", "pkcs8", @@ -847,28 +938,39 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] +[[package]] +name = "sha2" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + [[package]] name = "signature" -version = "1.4.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "f0ea32af43239f0d353a7dd75a22d94c329c8cdaafdcb4c1c1335aa10c298a4a" dependencies = [ - "digest", + "digest 0.10.3", "rand_core 0.6.3", ] [[package]] name = "spki" -version = "0.5.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", "der", @@ -890,8 +992,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" name = "swapper-base" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.14.0", + "cw-storage-plus 0.15.0", "rover", "schemars", "serde", @@ -904,8 +1007,8 @@ version = "0.1.0" dependencies = [ "anyhow", "cosmwasm-std", - "cw-multi-test 0.14.0", - "cw-storage-plus 0.14.0", + "cw-multi-test 0.15.0", + "cw-storage-plus 0.15.0", "rover", "schemars", "serde", @@ -920,7 +1023,7 @@ dependencies = [ "anyhow", "cosmwasm-std", "cw-multi-test 0.13.4", - "cw-storage-plus 0.14.0", + "cw-storage-plus 0.15.0", "osmo-bindings", "osmo-bindings-test", "rover", @@ -1005,6 +1108,6 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" -version = "1.3.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/Makefile.toml b/Makefile.toml index 42c859581..a91eb16c5 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -10,10 +10,18 @@ command = "cargo" args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked"] [tasks.rust-optimizer] -script = """docker run --rm -v "$(pwd)":/code \ +script = """ +if [[ $(arch) == 'arm64' ]]; then + docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.12.6 + cosmwasm/workspace-optimizer-arm64:0.12.8 +else + docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/workspace-optimizer:0.12.8 +fi """ [tasks.test] @@ -37,3 +45,22 @@ dependencies = [ "clippy", "rust-optimizer", ] + +[tasks.generate-all-schemas] +script = """ + rm -rf schema + rm -rf schemas + mkdir schemas + + array=( credit-manager account-nft mock-red-bank mock-vault mock-oracle swapper-base ) + for i in "${array[@]}" + do + : + echo "$i" + cargo run --package "$i" --example schema + mkdir schemas/"$i" + mv schema/"$i".json schemas/"$i"/"$i".json + done + + rm -rf schema +""" diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index fe97bd8fc..431ddb98c 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -14,13 +14,14 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cw-storage-plus = "0.14" +cw-storage-plus = "0.15" cw721 = "0.13" cw721-base = { version = "0.13", features = ["library"] } -cosmwasm-std = "1.0" +cosmwasm-schema = "1.1" +cosmwasm-std = "1.1" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } [dev-dependencies] anyhow = "1" -cw-multi-test = "0.14" +cw-multi-test = "0.15" diff --git a/contracts/account-nft/examples/schema.rs b/contracts/account-nft/examples/schema.rs new file mode 100644 index 000000000..9f18105f8 --- /dev/null +++ b/contracts/account-nft/examples/schema.rs @@ -0,0 +1,11 @@ +use account_nft::msg::{ExecuteMsg, QueryMsg}; +use cosmwasm_schema::write_api; +use cw721_base::msg::InstantiateMsg; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/account-nft/src/msg/execute.rs b/contracts/account-nft/src/msg/execute.rs index d419dc5d5..38225214b 100644 --- a/contracts/account-nft/src/msg/execute.rs +++ b/contracts/account-nft/src/msg/execute.rs @@ -17,7 +17,7 @@ pub enum ExecuteMsg { ProposeNewOwner { new_owner: String }, /// Accept the proposed ownership transfer - AcceptOwnership, + AcceptOwnership {}, /// Mint a new NFT to the specified user; can only be called by the contract minter Mint { user: String }, diff --git a/contracts/account-nft/src/msg/query.rs b/contracts/account-nft/src/msg/query.rs index a66917cf0..904447d12 100644 --- a/contracts/account-nft/src/msg/query.rs +++ b/contracts/account-nft/src/msg/query.rs @@ -1,23 +1,23 @@ use std::convert::TryInto; +use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::StdError; use cw721_base::QueryMsg as ParentQueryMsg; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[derive(QueryResponses)] pub enum QueryMsg { //-------------------------------------------------------------------------------------------------- // Extended messages //-------------------------------------------------------------------------------------------------- - ProposedNewOwner, + #[returns(String)] + ProposedNewOwner {}, //-------------------------------------------------------------------------------------------------- // Base cw721 messages //-------------------------------------------------------------------------------------------------- /// Return the owner of the given token, error if token does not exist - /// Return type: OwnerOfResponse + #[returns(cw721::OwnerOfResponse)] OwnerOf { token_id: String, /// unset or false will filter out expired approvals, you must set to true to see them @@ -25,7 +25,7 @@ pub enum QueryMsg { }, /// Return operator that can access all of the owner's tokens. - /// Return type: `ApprovalResponse` + #[returns(cw721::ApprovalResponse)] Approval { token_id: String, spender: String, @@ -33,14 +33,14 @@ pub enum QueryMsg { }, /// Return approvals that a token has - /// Return type: `ApprovalsResponse` + #[returns(cw721::ApprovalsResponse)] Approvals { token_id: String, include_expired: Option, }, /// List all operators that can access all of the owner's tokens - /// Return type: `OperatorsResponse` + #[returns(cw721::OperatorsResponse)] AllOperators { owner: String, /// unset or false will filter out expired items, you must set to true to see them @@ -49,20 +49,21 @@ pub enum QueryMsg { limit: Option, }, /// Total number of tokens issued - NumTokens, + #[returns(cw721::NumTokensResponse)] + NumTokens {}, /// With MetaData Extension. - /// Returns top-level metadata about the contract: `ContractInfoResponse` - ContractInfo, + /// Returns top-level metadata about the contract + #[returns(cw721::ContractInfoResponse)] + ContractInfo {}, /// With MetaData Extension. /// Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* - /// but directly from the contract: `NftInfoResponse` - NftInfo { - token_id: String, - }, + /// but directly from the contract + #[returns(cw721::NftInfoResponse)] + NftInfo { token_id: String }, /// With MetaData Extension. - /// Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization - /// for clients: `AllNftInfo` + /// Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization for clients + #[returns(cw721::AllNftInfoResponse)] AllNftInfo { token_id: String, /// unset or false will filter out expired approvals, you must set to true to see them @@ -71,7 +72,7 @@ pub enum QueryMsg { /// With Enumerable extension. /// Returns all tokens owned by the given address, [] if unset. - /// Return type: TokensResponse. + #[returns(cw721::TokensResponse)] Tokens { owner: String, start_after: Option, @@ -80,13 +81,15 @@ pub enum QueryMsg { /// With Enumerable extension. /// Requires pagination. Lists all token_ids controlled by the contract. /// Return type: TokensResponse. + #[returns(cw721::TokensResponse)] AllTokens { start_after: Option, limit: Option, }, /// Return the minter - Minter, + #[returns(cw721_base::MinterResponse)] + Minter {}, } impl TryInto for QueryMsg { diff --git a/contracts/account-nft/tests/test_ownership.rs b/contracts/account-nft/tests/test_ownership.rs index 1c17b12a4..4309542a3 100644 --- a/contracts/account-nft/tests/test_ownership.rs +++ b/contracts/account-nft/tests/test_ownership.rs @@ -54,7 +54,7 @@ fn test_proposed_owner_can_accept_ownership() { let res: MinterResponse = app .wrap() - .query_wasm_smart(contract_addr, &QueryMsg::Minter) + .query_wasm_smart(contract_addr, &QueryMsg::Minter {}) .unwrap(); assert_eq!(res.minter, new_owner) @@ -78,7 +78,7 @@ fn test_only_proposed_owner_can_accept() { fn query_pending_owner(app: &BasicApp, contract_addr: &Addr) -> StdResult { app.wrap() - .query_wasm_smart(contract_addr, &QueryMsg::ProposedNewOwner) + .query_wasm_smart(contract_addr, &QueryMsg::ProposedNewOwner {}) } fn propose_new_owner( @@ -105,7 +105,7 @@ fn accept_proposed_owner( app.execute_contract( sender.clone(), contract_addr.clone(), - &ExtendedExecuteMsg::AcceptOwnership, + &ExtendedExecuteMsg::AcceptOwnership {}, &[], ) } diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index f8839ce35..7bac64d22 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -21,11 +21,12 @@ mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", featu mock-vault = { version = "1.0", path = "../../contracts/mock-vault", features = ["library"] } rover = { version = "0.1", path = "../../packages/rover" } -cosmwasm-std = "1.0" -cw2 = "0.14" +cosmwasm-schema = "1.1" +cosmwasm-std = "1.1" +cw2 = "0.15" cw721 = "0.13" cw721-base = { version = "0.13", features = ["library"] } -cw-storage-plus = "0.14" +cw-storage-plus = "0.15" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } @@ -33,5 +34,5 @@ serde = { version = "1.0", default-features = false, features = ["derive"] } swapper-mock = { version = "0.1", path = "../../contracts/swapper/mock", features = ["library"] } anyhow = "1" -cw-multi-test = "0.14" +cw-multi-test = "0.15" itertools = "0.10" diff --git a/contracts/credit-manager/examples/schema.rs b/contracts/credit-manager/examples/schema.rs new file mode 100644 index 000000000..3b992ec27 --- /dev/null +++ b/contracts/credit-manager/examples/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 65e255197..f92cf0bdc 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -66,7 +66,7 @@ pub fn update_config( let accept_ownership_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: addr_str.clone(), funds: vec![], - msg: to_binary(&NftExecuteMsg::AcceptOwnership)?, + msg: to_binary(&NftExecuteMsg::AcceptOwnership {})?, }); response = response diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 1c685900b..2f1daff30 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] pub struct AccountToFund { pub addr: Addr, pub funds: Vec, diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index 9755b4af6..1cb08f4b1 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -182,6 +182,7 @@ fn test_success_when_new_debt_asset() { ); assert_eq!(debt_shares_res.denom, coin_info.denom); let debt_amount = Uint128::new(42u128) + Uint128::new(1); // simulated yield + assert_eq!(debt_shares_res.amount, debt_amount); assert_eq!( debt_shares_res.value, coin_info.price * Decimal::from_atomics(debt_amount, 0).unwrap() diff --git a/contracts/mock-oracle/Cargo.toml b/contracts/mock-oracle/Cargo.toml index c009d8c74..17fb48d4e 100644 --- a/contracts/mock-oracle/Cargo.toml +++ b/contracts/mock-oracle/Cargo.toml @@ -16,7 +16,8 @@ library = [] [dependencies] mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } -cosmwasm-std = "1.0" -cw-storage-plus = "0.14" +cosmwasm-schema = "1.1" +cosmwasm-std = "1.1" +cw-storage-plus = "0.15" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/contracts/mock-oracle/examples/schema.rs b/contracts/mock-oracle/examples/schema.rs new file mode 100644 index 000000000..75fb53b14 --- /dev/null +++ b/contracts/mock-oracle/examples/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use mock_oracle::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/mock-oracle/src/contract.rs b/contracts/mock-oracle/src/contract.rs index f5091c3b1..3fd2f0b63 100644 --- a/contracts/mock-oracle/src/contract.rs +++ b/contracts/mock-oracle/src/contract.rs @@ -1,6 +1,7 @@ -use cosmwasm_std::{ - to_binary, to_vec, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, -}; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; + use mars_outpost::oracle::PriceResponse; use crate::msg::{CoinPrice, ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -40,10 +41,6 @@ fn change_price(deps: DepsMut, coin: CoinPrice) -> StdResult { pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Price { denom } => to_binary(&query_price(deps, denom)?), - _ => Err(StdError::generic_err(format!( - "[mock] unimplemented query: {}", - String::from_utf8(to_vec(&msg)?)? - ))), } } diff --git a/contracts/mock-oracle/src/msg.rs b/contracts/mock-oracle/src/msg.rs index 91def6eae..5407ddb8a 100644 --- a/contracts/mock-oracle/src/msg.rs +++ b/contracts/mock-oracle/src/msg.rs @@ -1,3 +1,4 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Decimal; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -20,9 +21,9 @@ pub enum ExecuteMsg { ChangePrice(CoinPrice), } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[derive(QueryResponses)] pub enum QueryMsg { - Config {}, + #[returns(mars_outpost::oracle::PriceResponse)] Price { denom: String }, } diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index 2c49fb1d8..e0296bdb7 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -16,7 +16,8 @@ library = [] [dependencies] mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } -cosmwasm-std = "1.0" +cosmwasm-schema = "1.1" +cosmwasm-std = "1.1" cw-storage-plus = "0.14" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/contracts/mock-red-bank/examples/schema.rs b/contracts/mock-red-bank/examples/schema.rs new file mode 100644 index 000000000..2f4f5a8da --- /dev/null +++ b/contracts/mock-red-bank/examples/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use mock_red_bank::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs index 4d325f8d6..7b164a3c2 100644 --- a/contracts/mock-red-bank/src/msg.rs +++ b/contracts/mock-red-bank/src/msg.rs @@ -1,3 +1,4 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Decimal, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -14,7 +15,7 @@ pub struct CoinMarketInfo { pub liquidation_threshold: Decimal, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { Borrow { @@ -27,10 +28,12 @@ pub enum ExecuteMsg { }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[derive(QueryResponses)] pub enum QueryMsg { + #[returns(UserAssetDebtResponse)] UserAssetDebt { user_address: String, denom: String }, + #[returns(mars_outpost::red_bank::Market)] Market { denom: String }, } diff --git a/contracts/mock-vault/Cargo.toml b/contracts/mock-vault/Cargo.toml index 9a2537773..5a7b9a6e8 100644 --- a/contracts/mock-vault/Cargo.toml +++ b/contracts/mock-vault/Cargo.toml @@ -16,8 +16,9 @@ library = [] [dependencies] rover = { version = "0.1", path = "../../packages/rover" } -cosmwasm-std = "1.0" -cw-storage-plus = "0.14" +cosmwasm-schema = "1.1" +cosmwasm-std = "1.1" +cw-storage-plus = "0.15" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" diff --git a/contracts/mock-vault/examples/schema.rs b/contracts/mock-vault/examples/schema.rs new file mode 100644 index 000000000..5a25723b9 --- /dev/null +++ b/contracts/mock-vault/examples/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use mock_vault::msg::InstantiateMsg; +use rover::msg::vault::{ExecuteMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index 46e84a324..0bebc31ba 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -1,3 +1,6 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; + use cosmwasm_std::{ to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, }; @@ -43,15 +46,15 @@ pub fn execute( ) -> Result { match msg { ExecuteMsg::Deposit {} => deposit(deps, info), - ExecuteMsg::Withdraw => withdraw(deps, info), - ExecuteMsg::ForceWithdraw => withdraw_force(deps, info), + ExecuteMsg::Withdraw {} => withdraw(deps, info), + ExecuteMsg::ForceWithdraw {} => withdraw_force(deps, info), } } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::Info => to_binary(&query_vault_info(deps)?), + QueryMsg::Info {} => to_binary(&query_vault_info(deps)?), QueryMsg::PreviewRedeem { shares } => { to_binary(&query_coins_for_shares(deps.storage, shares)?) } diff --git a/contracts/swapper/base/Cargo.toml b/contracts/swapper/base/Cargo.toml index 5322e290c..65f99ba2f 100644 --- a/contracts/swapper/base/Cargo.toml +++ b/contracts/swapper/base/Cargo.toml @@ -16,8 +16,9 @@ library = [] [dependencies] rover = { version = "0.1", path = "../../../packages/rover" } -cosmwasm-std = "1.0" -cw-storage-plus = "0.14" +cosmwasm-schema = "1.1" +cosmwasm-std = "1.1" +cw-storage-plus = "0.15" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" diff --git a/contracts/swapper/base/examples/schema.rs b/contracts/swapper/base/examples/schema.rs new file mode 100644 index 000000000..27d3aca05 --- /dev/null +++ b/contracts/swapper/base/examples/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use cosmwasm_std::Empty; +use rover::adapters::swap::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/swapper/mock/Cargo.toml b/contracts/swapper/mock/Cargo.toml index a8edb4f2c..51fb733b3 100644 --- a/contracts/swapper/mock/Cargo.toml +++ b/contracts/swapper/mock/Cargo.toml @@ -17,12 +17,12 @@ library = [] rover = { version = "0.1", path = "../../../packages/rover" } swapper-base = { path = "../base", version = "0.1" } -cosmwasm-std = "1.0" -cw-storage-plus = "0.14" +cosmwasm-std = "1.1" +cw-storage-plus = "0.15" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" [dev-dependencies] anyhow = "1" -cw-multi-test = "0.14" +cw-multi-test = "0.15" diff --git a/contracts/swapper/mock/src/contract.rs b/contracts/swapper/mock/src/contract.rs index d8f2a036d..9d4dc1dd5 100644 --- a/contracts/swapper/mock/src/contract.rs +++ b/contracts/swapper/mock/src/contract.rs @@ -7,7 +7,7 @@ use rover::adapters::swap::{EstimateExactInSwapResponse, ExecuteMsg, Instantiate pub const MOCK_SWAP_RESULT: Uint128 = Uint128::new(1337); -#[cfg_attr(not(feature = "library"), entry_point)] +#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] pub fn instantiate( _deps: DepsMut, _env: Env, @@ -17,7 +17,7 @@ pub fn instantiate( Ok(Response::default()) } -#[cfg_attr(not(feature = "library"), entry_point)] +#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] pub fn execute( deps: DepsMut, env: Env, @@ -36,7 +36,7 @@ pub fn execute( } } -#[cfg_attr(not(feature = "library"), entry_point)] +#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Config { .. } => unimplemented!("not implemented"), diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index 91954104b..63f9cb4c4 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -17,8 +17,8 @@ library = [] rover = { version = "0.1", path = "../../../packages/rover" } swapper-base = { path = "../base", version = "0.1" } -cosmwasm-std = "1.0" -cw-storage-plus = "0.14" +cosmwasm-std = "1.1" +cw-storage-plus = "0.15" osmo-bindings = "0.5" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 5e50f932a..b53dedad0 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -15,8 +15,9 @@ mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } -cosmwasm-std = "1.0" -cw-storage-plus = "0.14" +cosmwasm-schema = "1.1" +cosmwasm-std = "1.1" +cw-storage-plus = "0.15" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" diff --git a/packages/rover/src/adapters/swap/msgs.rs b/packages/rover/src/adapters/swap/msgs.rs index 591b13e2d..f973fbb1a 100644 --- a/packages/rover/src/adapters/swap/msgs.rs +++ b/packages/rover/src/adapters/swap/msgs.rs @@ -1,3 +1,4 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -10,7 +11,7 @@ pub struct Config { pub type InstantiateMsg = Config; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { /// Update contract config @@ -38,21 +39,24 @@ pub enum ExecuteMsg { }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[derive(QueryResponses)] pub enum QueryMsg { - /// Query contract config. Returns `Config` + /// Query contract config + #[returns(Config)] Config {}, - /// Get route for swapping an input denom into an output denom; response: `RouteResponse` + /// Get route for swapping an input denom into an output denom + #[returns(RouteResponse)] Route { denom_in: String, denom_out: String }, - /// Enumerate all swapper routes; response: `RoutesResponse` + /// Enumerate all swapper routes + #[returns(RoutesResponse)] Routes { start_after: Option<(String, String)>, limit: Option, }, /// Return current spot price swapping In for Out /// Warning: Do not use this as an oracle price feed. Use Mars-Oracle for pricing. - /// Returns `EstimateExactInSwapResponse` + #[returns(EstimateExactInSwapResponse)] EstimateExactInSwap { coin_in: Coin, denom_out: String }, } diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 6f8fef6d1..5e923d51b 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -5,14 +5,14 @@ use serde::{Deserialize, Serialize}; use crate::adapters::{Vault, VaultUnchecked}; use crate::msg::instantiate::ConfigUpdates; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { //-------------------------------------------------------------------------------------------------- // Public messages //-------------------------------------------------------------------------------------------------- /// Mints NFT representing a credit account for user. User can have many. - CreateCreditAccount, + CreateCreditAccount {}, /// Update user's position on their credit account UpdateCreditAccount { token_id: String, @@ -29,7 +29,7 @@ pub enum ExecuteMsg { } /// The list of actions that users can perform on their positions -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Action { /// Deposit coin of specified denom and amount. Verifies if the correct amount is sent with transaction. @@ -76,7 +76,7 @@ pub enum Action { } /// Internal actions made by the contract with pre-validated inputs -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum CallbackMsg { /// Withdraw specified amount of coin from credit account; diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index d31819e03..4f974a7b1 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -1,3 +1,4 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Decimal, Uint128}; use mars_health::health::Health; use schemars::JsonSchema; @@ -5,55 +6,62 @@ use serde::{Deserialize, Serialize}; use crate::adapters::{VaultPosition, VaultUnchecked}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[derive(QueryResponses)] pub enum QueryMsg { - /// Owner & account nft address. Response type: `ConfigResponse` - Config, - /// Whitelisted vaults. Response type: `Vec` + /// Owner & account nft address + #[returns(ConfigResponse)] + Config {}, + /// Whitelisted vaults + #[returns(Vec)] AllowedVaults { start_after: Option, limit: Option, }, - /// Whitelisted coins. Response type: `Vec` + /// Whitelisted coins + #[returns(Vec)] AllowedCoins { start_after: Option, limit: Option, }, - /// All positions represented by token with value. Response type: `PositionsWithValueResponse` + /// All positions represented by token with value + #[returns(PositionsWithValueResponse)] Positions { token_id: String }, - /// The health of the account represented by token. Response type: `HealthResponse` + /// The health of the account represented by token + #[returns(HealthResponse)] Health { token_id: String }, - /// Enumerate coin balances for all token positions. Response type: `Vec` - /// start_after accepts (token_id, denom) + /// Enumerate coin balances for all token positions; start_after accepts (token_id, denom) + #[returns(Vec)] AllCoinBalances { start_after: Option<(String, String)>, limit: Option, }, - /// Enumerate debt shares for all token positions. Response type: `Vec` - /// start_after accepts (token_id, denom) + /// Enumerate debt shares for all token positions; start_after accepts (token_id, denom) + #[returns(Vec)] AllDebtShares { start_after: Option<(String, String)>, limit: Option, }, - /// Total debt shares issued for Coin. Response type: `CoinShares` + /// Total debt shares issued for Coin + #[returns(DebtShares)] TotalDebtShares(String), - /// Enumerate total debt shares for all supported coins. Response type: `Vec` - /// start_after accepts denom string + /// Enumerate total debt shares for all supported coins; start_after accepts denom string + #[returns(Vec)] AllTotalDebtShares { start_after: Option, limit: Option, }, - /// Enumerate all vault positions. Response type: `Vec` - /// start_after accepts (token_id, addr) + /// Enumerate all vault positions; start_after accepts (token_id, addr) + #[returns(Vec)] AllVaultPositions { start_after: Option<(String, String)>, limit: Option, }, - /// Get total vault coin balance in Rover for vault `Uint128` + /// Get total vault coin balance in Rover for vault + #[returns(Uint128)] TotalVaultCoinBalance { vault: VaultUnchecked }, - /// Enumerate all total vault coin balances. Response type: `Vec` - /// start_after accepts vault addr + /// Enumerate all total vault coin balances; start_after accepts vault addr + #[returns(Vec)] AllTotalVaultCoinBalances { start_after: Option, limit: Option, @@ -106,7 +114,7 @@ pub struct CoinValue { pub value: Decimal, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct Positions { pub token_id: String, diff --git a/packages/rover/src/msg/vault.rs b/packages/rover/src/msg/vault.rs index 1aa2d1dc3..2a4de2111 100644 --- a/packages/rover/src/msg/vault.rs +++ b/packages/rover/src/msg/vault.rs @@ -1,3 +1,4 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -7,25 +8,27 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { /// Enters list of `Vec` into a vault strategy in exchange for vault tokens. - Deposit, + Deposit {}, /// Withdraw underlying coins in vault by exchanging vault `Coin` - Withdraw, + Withdraw {}, /// A privileged action only to be used by Rover. Same as `Withdraw` except it bypasses any lockup period /// restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation. - ForceWithdraw, + ForceWithdraw {}, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[derive(QueryResponses)] pub enum QueryMsg { - /// Returns `VaultInfo` representing vault requirements, lockup, & vault token denom - Info, - /// Returns `Vec` representing all the coins that would be redeemed for in exchange for + /// Vault requirements, lockup, & vault token denom + #[returns(VaultInfo)] + Info {}, + /// All the coins that would be redeemed for in exchange for /// vault coins. Used by Rover to calculate vault position values. + #[returns(Vec)] PreviewRedeem { shares: Uint128 }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct VaultInfo { /// Coins required to enter vault. diff --git a/schemas/account-nft/account-nft.json b/schemas/account-nft/account-nft.json new file mode 100644 index 000000000..983aebbe1 --- /dev/null +++ b/schemas/account-nft/account-nft.json @@ -0,0 +1,1317 @@ +{ + "contract_name": "account-nft", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "minter", + "name", + "symbol" + ], + "properties": { + "minter": { + "description": "The minter is the only one who can create new NFTs. This is designed for a base NFT that is controlled by an external program or contract. You will likely replace this with custom logic in custom NFTs", + "type": "string" + }, + "name": { + "description": "Name of the NFT contract", + "type": "string" + }, + "symbol": { + "description": "Symbol of the NFT contract", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Due to some chains being permissioned via governance, we must instantiate this contract first and give ownership access to Rover contract with this action after both are independently deployed.", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "new_owner" + ], + "properties": { + "new_owner": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Accept the proposed ownership transfer", + "type": "object", + "required": [ + "accept_ownership" + ], + "properties": { + "accept_ownership": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Mint a new NFT to the specified user; can only be called by the contract minter", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Transfer is a base message to move a token to another account without triggering actions", + "type": "object", + "required": [ + "transfer_nft" + ], + "properties": { + "transfer_nft": { + "type": "object", + "required": [ + "recipient", + "token_id" + ], + "properties": { + "recipient": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Send is a base message to transfer a token to a contract and trigger an action on the receiving contract.", + "type": "object", + "required": [ + "send_nft" + ], + "properties": { + "send_nft": { + "type": "object", + "required": [ + "contract", + "msg", + "token_id" + ], + "properties": { + "contract": { + "type": "string" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send the token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve" + ], + "properties": { + "approve": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted Approval", + "type": "object", + "required": [ + "revoke" + ], + "properties": { + "revoke": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve_all" + ], + "properties": { + "approve_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "operator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted ApproveAll permission", + "type": "object", + "required": [ + "revoke_all" + ], + "properties": { + "revoke_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Burn an NFT the sender has access to", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "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", + "type": "string" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "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" + } + ] + }, + "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": [ + "proposed_new_owner" + ], + "properties": { + "proposed_new_owner": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the owner of the given token, error if token does not exist", + "type": "object", + "required": [ + "owner_of" + ], + "properties": { + "owner_of": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return operator that can access all of the owner's tokens.", + "type": "object", + "required": [ + "approval" + ], + "properties": { + "approval": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "include_expired": { + "type": [ + "boolean", + "null" + ] + }, + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return approvals that a token has", + "type": "object", + "required": [ + "approvals" + ], + "properties": { + "approvals": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "List all operators that can access all of the owner's tokens", + "type": "object", + "required": [ + "all_operators" + ], + "properties": { + "all_operators": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired items, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Total number of tokens issued", + "type": "object", + "required": [ + "num_tokens" + ], + "properties": { + "num_tokens": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns top-level metadata about the contract", + "type": "object", + "required": [ + "contract_info" + ], + "properties": { + "contract_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* but directly from the contract", + "type": "object", + "required": [ + "nft_info" + ], + "properties": { + "nft_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization for clients", + "type": "object", + "required": [ + "all_nft_info" + ], + "properties": { + "all_nft_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset.", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract. Return type: TokensResponse.", + "type": "object", + "required": [ + "all_tokens" + ], + "properties": { + "all_tokens": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the minter", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "all_nft_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllNftInfoResponse_for_Empty", + "type": "object", + "required": [ + "access", + "info" + ], + "properties": { + "access": { + "description": "Who can transfer the token", + "allOf": [ + { + "$ref": "#/definitions/OwnerOfResponse" + } + ] + }, + "info": { + "description": "Data on the token itself,", + "allOf": [ + { + "$ref": "#/definitions/NftInfoResponse_for_Empty" + } + ] + } + }, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + } + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "NftInfoResponse_for_Empty": { + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "description": "You can add any custom metadata here when you extend cw721-base", + "allOf": [ + { + "$ref": "#/definitions/Empty" + } + ] + }, + "token_uri": { + "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", + "type": [ + "string", + "null" + ] + } + } + }, + "OwnerOfResponse": { + "type": "object", + "required": [ + "approvals", + "owner" + ], + "properties": { + "approvals": { + "description": "If set this address is approved to transfer/send the token as well", + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + }, + "owner": { + "description": "Owner of the token", + "type": "string" + } + } + }, + "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" + } + ] + }, + "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" + } + } + }, + "all_operators": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OperatorsResponse", + "type": "object", + "required": [ + "operators" + ], + "properties": { + "operators": { + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + } + }, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "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" + } + ] + }, + "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" + } + } + }, + "all_tokens": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokensResponse", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "approval": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApprovalResponse", + "type": "object", + "required": [ + "approval" + ], + "properties": { + "approval": { + "$ref": "#/definitions/Approval" + } + }, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "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" + } + ] + }, + "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" + } + } + }, + "approvals": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApprovalsResponse", + "type": "object", + "required": [ + "approvals" + ], + "properties": { + "approvals": { + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + } + }, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "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" + } + ] + }, + "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" + } + } + }, + "contract_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractInfoResponse", + "type": "object", + "required": [ + "name", + "symbol" + ], + "properties": { + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + } + } + }, + "minter": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MinterResponse", + "description": "Shows who can mint these tokens", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "string" + } + } + }, + "nft_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NftInfoResponse_for_Empty", + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "description": "You can add any custom metadata here when you extend cw721-base", + "allOf": [ + { + "$ref": "#/definitions/Empty" + } + ] + }, + "token_uri": { + "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", + "type": [ + "string", + "null" + ] + } + }, + "definitions": { + "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" + } + } + }, + "num_tokens": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NumTokensResponse", + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "owner_of": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OwnerOfResponse", + "type": "object", + "required": [ + "approvals", + "owner" + ], + "properties": { + "approvals": { + "description": "If set this address is approved to transfer/send the token as well", + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + }, + "owner": { + "description": "Owner of the token", + "type": "string" + } + }, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "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" + } + ] + }, + "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" + } + } + }, + "proposed_new_owner": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "String", + "type": "string" + }, + "tokens": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokensResponse", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + } + } + } +} diff --git a/schemas/credit-manager/credit-manager.json b/schemas/credit-manager/credit-manager.json new file mode 100644 index 000000000..b2823a800 --- /dev/null +++ b/schemas/credit-manager/credit-manager.json @@ -0,0 +1,1668 @@ +{ + "contract_name": "credit-manager", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "allowed_coins", + "allowed_vaults", + "max_close_factor", + "max_liquidation_bonus", + "oracle", + "owner", + "red_bank", + "swapper" + ], + "properties": { + "allowed_coins": { + "description": "Whitelisted coin denoms approved by governance", + "type": "array", + "items": { + "type": "string" + } + }, + "allowed_vaults": { + "description": "Whitelisted vaults approved by governance that implement credit manager's vault interface", + "type": "array", + "items": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "max_close_factor": { + "description": "The maximum percent a liquidator can decrease the debt amount of the liquidatee", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "max_liquidation_bonus": { + "description": "The maximum percent a liquidator can profit from a liquidation action", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "oracle": { + "description": "The Mars Protocol oracle contract. We read prices of assets here.", + "allOf": [ + { + "$ref": "#/definitions/OracleBase_for_String" + } + ] + }, + "owner": { + "description": "The address with privileged access to update config", + "type": "string" + }, + "red_bank": { + "description": "The Mars Protocol money market contract where we borrow assets from", + "allOf": [ + { + "$ref": "#/definitions/RedBankBase_for_String" + } + ] + }, + "swapper": { + "description": "Helper contract for making swaps", + "allOf": [ + { + "$ref": "#/definitions/SwapperBase_for_String" + } + ] + } + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "OracleBase_for_String": { + "type": "string" + }, + "RedBankBase_for_String": { + "type": "string" + }, + "SwapperBase_for_String": { + "type": "string" + }, + "VaultBase_for_String": { + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Mints NFT representing a credit account for user. User can have many.", + "type": "object", + "required": [ + "create_credit_account" + ], + "properties": { + "create_credit_account": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Update user's position on their credit account", + "type": "object", + "required": [ + "update_credit_account" + ], + "properties": { + "update_credit_account": { + "type": "object", + "required": [ + "actions", + "token_id" + ], + "properties": { + "actions": { + "type": "array", + "items": { + "$ref": "#/definitions/Action" + } + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Update contract config constants", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "new_config" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/ConfigUpdates" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Internal actions only callable by the contract itself", + "type": "object", + "required": [ + "callback" + ], + "properties": { + "callback": { + "$ref": "#/definitions/CallbackMsg" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Action": { + "description": "The list of actions that users can perform on their positions", + "oneOf": [ + { + "description": "Deposit coin of specified denom and amount. Verifies if the correct amount is sent with transaction.", + "type": "object", + "required": [ + "deposit" + ], + "properties": { + "deposit": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw coin of specified denom and amount", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + }, + { + "description": "Borrow coin of specified amount from Red Bank", + "type": "object", + "required": [ + "borrow" + ], + "properties": { + "borrow": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + }, + { + "description": "Repay coin of specified amount back to Red Bank", + "type": "object", + "required": [ + "repay" + ], + "properties": { + "repay": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + }, + { + "description": "Deposit coins into vault strategy", + "type": "object", + "required": [ + "vault_deposit" + ], + "properties": { + "vault_deposit": { + "type": "object", + "required": [ + "coins", + "vault" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw underlying coins from vault", + "type": "object", + "required": [ + "vault_withdraw" + ], + "properties": { + "vault_withdraw": { + "type": "object", + "required": [ + "amount", + "vault" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt denom/amount of what the liquidator wants to payoff and 2) the request coin denom which the liquidatee should have a balance of. The amount returned to liquidator will be the request coin of the amount that precisely matches the value of the debt + a liquidation bonus. The debt amount will be adjusted down if: - Exceeds liquidatee's total debt for denom - Not enough liquidatee request coin balance to match - The value of the debt repaid exceeds the maximum close factor %", + "type": "object", + "required": [ + "liquidate_coin" + ], + "properties": { + "liquidate_coin": { + "type": "object", + "required": [ + "debt_coin", + "liquidatee_token_id", + "request_coin_denom" + ], + "properties": { + "debt_coin": { + "description": "The coin debt that the liquidator wishes to pay back on behalf of the liquidatee. The liquidator must already have these assets in their credit account.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "liquidatee_token_id": { + "description": "The credit account id of the one with a liquidation threshold health factor 1 or below", + "type": "string" + }, + "request_coin_denom": { + "description": "The coin they wish to acquire from the liquidatee (amount returned will include the bonus)", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %.", + "type": "object", + "required": [ + "swap_exact_in" + ], + "properties": { + "swap_exact_in": { + "type": "object", + "required": [ + "coin_in", + "denom_out", + "slippage" + ], + "properties": { + "coin_in": { + "$ref": "#/definitions/Coin" + }, + "denom_out": { + "type": "string" + }, + "slippage": { + "$ref": "#/definitions/Decimal" + } + } + } + }, + "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" + }, + "CallbackMsg": { + "description": "Internal actions made by the contract with pre-validated inputs", + "oneOf": [ + { + "description": "Withdraw specified amount of coin from credit account; Decrement the token's asset amount;", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "required": [ + "coin", + "recipient", + "token_id" + ], + "properties": { + "coin": { + "$ref": "#/definitions/Coin" + }, + "recipient": { + "$ref": "#/definitions/Addr" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Borrow specified amount of coin from Red Bank; Increase the token's coin amount and debt shares;", + "type": "object", + "required": [ + "borrow" + ], + "properties": { + "borrow": { + "type": "object", + "required": [ + "coin", + "token_id" + ], + "properties": { + "coin": { + "$ref": "#/definitions/Coin" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Repay coin of specified amount back to Red Bank; Decrement the token's coin amount and debt shares;", + "type": "object", + "required": [ + "repay" + ], + "properties": { + "repay": { + "type": "object", + "required": [ + "coin", + "token_id" + ], + "properties": { + "coin": { + "$ref": "#/definitions/Coin" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Calculate the account's max loan-to-value health factor. If above 1, emits a `position_changed` event. If 1 or below, raises an error.", + "type": "object", + "required": [ + "assert_below_max_l_t_v" + ], + "properties": { + "assert_below_max_l_t_v": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Adds list of coins to a vault strategy", + "type": "object", + "required": [ + "vault_deposit" + ], + "properties": { + "vault_deposit": { + "type": "object", + "required": [ + "coins", + "token_id", + "vault" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "token_id": { + "type": "string" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Used to update the account balance of vault coins after a deposit", + "type": "object", + "required": [ + "update_vault_coin_balance" + ], + "properties": { + "update_vault_coin_balance": { + "type": "object", + "required": [ + "previous_total_balance", + "token_id", + "vault" + ], + "properties": { + "previous_total_balance": { + "description": "Total vault coin balance in Rover", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "token_id": { + "description": "Account that needs vault coin balance adjustment", + "type": "string" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Exchanges vault LP shares for assets", + "type": "object", + "required": [ + "vault_withdraw" + ], + "properties": { + "vault_withdraw": { + "type": "object", + "required": [ + "amount", + "token_id", + "vault" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "token_id": { + "type": "string" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "A privileged action only to be used by Rover. Same as `VaultWithdraw` except it bypasses any lockup period restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation.", + "type": "object", + "required": [ + "vault_force_withdraw" + ], + "properties": { + "vault_force_withdraw": { + "type": "object", + "required": [ + "amount", + "token_id", + "vault" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "token_id": { + "type": "string" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Pay back debts of a liquidatable rover account for a bonus", + "type": "object", + "required": [ + "liquidate_coin" + ], + "properties": { + "liquidate_coin": { + "type": "object", + "required": [ + "debt_coin", + "liquidatee_token_id", + "liquidator_token_id", + "request_coin_denom" + ], + "properties": { + "debt_coin": { + "$ref": "#/definitions/Coin" + }, + "liquidatee_token_id": { + "type": "string" + }, + "liquidator_token_id": { + "type": "string" + }, + "request_coin_denom": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Determine health factor improved as a consequence of liquidation event", + "type": "object", + "required": [ + "assert_health_factor_improved" + ], + "properties": { + "assert_health_factor_improved": { + "type": "object", + "required": [ + "previous_health_factor", + "token_id" + ], + "properties": { + "previous_health_factor": { + "$ref": "#/definitions/Decimal" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %.", + "type": "object", + "required": [ + "swap_exact_in" + ], + "properties": { + "swap_exact_in": { + "type": "object", + "required": [ + "coin_in", + "denom_out", + "slippage", + "token_id" + ], + "properties": { + "coin_in": { + "$ref": "#/definitions/Coin" + }, + "denom_out": { + "type": "string" + }, + "slippage": { + "$ref": "#/definitions/Decimal" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Used to update the coin balance of account after an async action", + "type": "object", + "required": [ + "update_coin_balances" + ], + "properties": { + "update_coin_balances": { + "type": "object", + "required": [ + "previous_balances", + "token_id" + ], + "properties": { + "previous_balances": { + "description": "Total balances for coins in Rover prior to withdraw", + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "token_id": { + "description": "Account that needs coin balance adjustment", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "ConfigUpdates": { + "description": "Used when you want to update fields on Instantiate config", + "type": "object", + "properties": { + "account_nft": { + "type": [ + "string", + "null" + ] + }, + "allowed_coins": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "allowed_vaults": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "max_close_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "max_liquidation_bonus": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "oracle": { + "anyOf": [ + { + "$ref": "#/definitions/OracleBase_for_String" + }, + { + "type": "null" + } + ] + }, + "owner": { + "type": [ + "string", + "null" + ] + }, + "red_bank": { + "anyOf": [ + { + "$ref": "#/definitions/RedBankBase_for_String" + }, + { + "type": "null" + } + ] + }, + "swapper": { + "anyOf": [ + { + "$ref": "#/definitions/SwapperBase_for_String" + }, + { + "type": "null" + } + ] + } + } + }, + "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" + }, + "OracleBase_for_String": { + "type": "string" + }, + "RedBankBase_for_String": { + "type": "string" + }, + "SwapperBase_for_String": { + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VaultBase_for_Addr": { + "$ref": "#/definitions/Addr" + }, + "VaultBase_for_String": { + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Owner & account nft address", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Whitelisted vaults", + "type": "object", + "required": [ + "allowed_vaults" + ], + "properties": { + "allowed_vaults": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/VaultBase_for_String" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Whitelisted coins", + "type": "object", + "required": [ + "allowed_coins" + ], + "properties": { + "allowed_coins": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "All positions represented by token with value", + "type": "object", + "required": [ + "positions" + ], + "properties": { + "positions": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The health of the account represented by token", + "type": "object", + "required": [ + "health" + ], + "properties": { + "health": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate coin balances for all token positions; start_after accepts (token_id, denom)", + "type": "object", + "required": [ + "all_coin_balances" + ], + "properties": { + "all_coin_balances": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate debt shares for all token positions; start_after accepts (token_id, denom)", + "type": "object", + "required": [ + "all_debt_shares" + ], + "properties": { + "all_debt_shares": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Total debt shares issued for Coin", + "type": "object", + "required": [ + "total_debt_shares" + ], + "properties": { + "total_debt_shares": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate total debt shares for all supported coins; start_after accepts denom string", + "type": "object", + "required": [ + "all_total_debt_shares" + ], + "properties": { + "all_total_debt_shares": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate all vault positions; start_after accepts (token_id, addr)", + "type": "object", + "required": [ + "all_vault_positions" + ], + "properties": { + "all_vault_positions": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get total vault coin balance in Rover for vault", + "type": "object", + "required": [ + "total_vault_coin_balance" + ], + "properties": { + "total_vault_coin_balance": { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate all total vault coin balances; start_after accepts vault addr", + "type": "object", + "required": [ + "all_total_vault_coin_balances" + ], + "properties": { + "all_total_vault_coin_balances": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/VaultBase_for_String" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "VaultBase_for_String": { + "type": "string" + } + } + }, + "migrate": null, + "sudo": null, + "responses": { + "all_coin_balances": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_CoinBalanceResponseItem", + "type": "array", + "items": { + "$ref": "#/definitions/CoinBalanceResponseItem" + }, + "definitions": { + "CoinBalanceResponseItem": { + "type": "object", + "required": [ + "amount", + "denom", + "token_id" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "all_debt_shares": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_SharesResponseItem", + "type": "array", + "items": { + "$ref": "#/definitions/SharesResponseItem" + }, + "definitions": { + "SharesResponseItem": { + "type": "object", + "required": [ + "denom", + "shares", + "token_id" + ], + "properties": { + "denom": { + "type": "string" + }, + "shares": { + "$ref": "#/definitions/Uint128" + }, + "token_id": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "all_total_debt_shares": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_DebtShares", + "type": "array", + "items": { + "$ref": "#/definitions/DebtShares" + }, + "definitions": { + "DebtShares": { + "type": "object", + "required": [ + "denom", + "shares" + ], + "properties": { + "denom": { + "type": "string" + }, + "shares": { + "$ref": "#/definitions/Uint128" + } + } + }, + "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" + } + } + }, + "all_total_vault_coin_balances": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_VaultWithBalance", + "type": "array", + "items": { + "$ref": "#/definitions/VaultWithBalance" + }, + "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" + }, + "VaultBase_for_String": { + "type": "string" + }, + "VaultWithBalance": { + "type": "object", + "required": [ + "balance", + "vault" + ], + "properties": { + "balance": { + "$ref": "#/definitions/Uint128" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + } + } + } + }, + "all_vault_positions": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_VaultPositionResponseItem", + "type": "array", + "items": { + "$ref": "#/definitions/VaultPositionResponseItem" + }, + "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" + }, + "VaultPosition": { + "type": "object", + "required": [ + "locked", + "unlocked" + ], + "properties": { + "locked": { + "$ref": "#/definitions/Uint128" + }, + "unlocked": { + "$ref": "#/definitions/Uint128" + } + } + }, + "VaultPositionResponseItem": { + "type": "object", + "required": [ + "addr", + "token_id", + "vault_position" + ], + "properties": { + "addr": { + "type": "string" + }, + "token_id": { + "type": "string" + }, + "vault_position": { + "$ref": "#/definitions/VaultPosition" + } + } + } + } + }, + "allowed_coins": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_String", + "type": "array", + "items": { + "type": "string" + } + }, + "allowed_vaults": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_VaultBase_for_String", + "type": "array", + "items": { + "$ref": "#/definitions/VaultBase_for_String" + }, + "definitions": { + "VaultBase_for_String": { + "type": "string" + } + } + }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "type": "object", + "required": [ + "max_close_factor", + "max_liquidation_bonus", + "oracle", + "owner", + "red_bank", + "swapper" + ], + "properties": { + "account_nft": { + "type": [ + "string", + "null" + ] + }, + "max_close_factor": { + "$ref": "#/definitions/Decimal" + }, + "max_liquidation_bonus": { + "$ref": "#/definitions/Decimal" + }, + "oracle": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "red_bank": { + "type": "string" + }, + "swapper": { + "type": "string" + } + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "health": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HealthResponse", + "type": "object", + "required": [ + "above_max_ltv", + "liquidatable", + "liquidation_threshold_adjusted_collateral", + "max_ltv_adjusted_collateral", + "total_collateral_value", + "total_debt_value" + ], + "properties": { + "above_max_ltv": { + "type": "boolean" + }, + "liquidatable": { + "type": "boolean" + }, + "liquidation_health_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "liquidation_threshold_adjusted_collateral": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv_adjusted_collateral": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv_health_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "total_collateral_value": { + "$ref": "#/definitions/Decimal" + }, + "total_debt_value": { + "$ref": "#/definitions/Decimal" + } + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "positions": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PositionsWithValueResponse", + "type": "object", + "required": [ + "coins", + "debt", + "token_id", + "vault_positions" + ], + "properties": { + "coins": { + "description": "All coin balances value", + "type": "array", + "items": { + "$ref": "#/definitions/CoinValue" + } + }, + "debt": { + "description": "All debt positions with value", + "type": "array", + "items": { + "$ref": "#/definitions/DebtSharesValue" + } + }, + "token_id": { + "description": "Unique NFT token id that represents the cross-margin account. The owner of this NFT, owns the account.", + "type": "string" + }, + "vault_positions": { + "description": "All vault positions", + "type": "array", + "items": { + "$ref": "#/definitions/VaultPositionWithAddr" + } + } + }, + "definitions": { + "CoinValue": { + "type": "object", + "required": [ + "amount", + "denom", + "price", + "value" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + }, + "price": { + "$ref": "#/definitions/Decimal" + }, + "value": { + "$ref": "#/definitions/Decimal" + } + } + }, + "DebtSharesValue": { + "type": "object", + "required": [ + "amount", + "denom", + "price", + "shares", + "value" + ], + "properties": { + "amount": { + "description": "amount of coins", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "type": "string" + }, + "price": { + "description": "price per coin", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "shares": { + "description": "number of shares in debt pool", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "value": { + "description": "price * amount", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VaultPosition": { + "type": "object", + "required": [ + "locked", + "unlocked" + ], + "properties": { + "locked": { + "$ref": "#/definitions/Uint128" + }, + "unlocked": { + "$ref": "#/definitions/Uint128" + } + } + }, + "VaultPositionWithAddr": { + "type": "object", + "required": [ + "addr", + "position" + ], + "properties": { + "addr": { + "type": "string" + }, + "position": { + "$ref": "#/definitions/VaultPosition" + } + } + } + } + }, + "total_debt_shares": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DebtShares", + "type": "object", + "required": [ + "denom", + "shares" + ], + "properties": { + "denom": { + "type": "string" + }, + "shares": { + "$ref": "#/definitions/Uint128" + } + }, + "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" + } + } + }, + "total_vault_coin_balance": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/mock-oracle/mock-oracle.json b/schemas/mock-oracle/mock-oracle.json new file mode 100644 index 000000000..e36af95ec --- /dev/null +++ b/schemas/mock-oracle/mock-oracle.json @@ -0,0 +1,135 @@ +{ + "contract_name": "mock-oracle", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "coins" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/CoinPrice" + } + } + }, + "definitions": { + "CoinPrice": { + "type": "object", + "required": [ + "denom", + "price" + ], + "properties": { + "denom": { + "type": "string" + }, + "price": { + "$ref": "#/definitions/Decimal" + } + } + }, + "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" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "change_price" + ], + "properties": { + "change_price": { + "$ref": "#/definitions/CoinPrice" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "CoinPrice": { + "type": "object", + "required": [ + "denom", + "price" + ], + "properties": { + "denom": { + "type": "string" + }, + "price": { + "$ref": "#/definitions/Decimal" + } + } + }, + "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" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "price" + ], + "properties": { + "price": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "price": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PriceResponse", + "type": "object", + "required": [ + "denom", + "price" + ], + "properties": { + "denom": { + "type": "string" + }, + "price": { + "$ref": "#/definitions/Decimal" + } + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + } + } +} diff --git a/schemas/mock-red-bank/mock-red-bank.json b/schemas/mock-red-bank/mock-red-bank.json new file mode 100644 index 000000000..4b84a3f9d --- /dev/null +++ b/schemas/mock-red-bank/mock-red-bank.json @@ -0,0 +1,400 @@ +{ + "contract_name": "mock-red-bank", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "coins" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/CoinMarketInfo" + } + } + }, + "definitions": { + "CoinMarketInfo": { + "type": "object", + "required": [ + "denom", + "liquidation_threshold", + "max_ltv" + ], + "properties": { + "denom": { + "type": "string" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv": { + "$ref": "#/definitions/Decimal" + } + } + }, + "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" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "borrow" + ], + "properties": { + "borrow": { + "type": "object", + "required": [ + "coin" + ], + "properties": { + "coin": { + "$ref": "#/definitions/Coin" + }, + "recipient": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "repay" + ], + "properties": { + "repay": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + }, + "on_behalf_of": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "user_asset_debt" + ], + "properties": { + "user_asset_debt": { + "type": "object", + "required": [ + "denom", + "user_address" + ], + "properties": { + "denom": { + "type": "string" + }, + "user_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "market" + ], + "properties": { + "market": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "market": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Market", + "type": "object", + "required": [ + "borrow_enabled", + "borrow_index", + "borrow_rate", + "debt_total_scaled", + "denom", + "deposit_cap", + "deposit_enabled", + "indexes_last_updated", + "interest_rate_model", + "liquidation_bonus", + "liquidation_threshold", + "liquidity_index", + "liquidity_rate", + "ma_token_address", + "max_loan_to_value", + "reserve_factor" + ], + "properties": { + "borrow_enabled": { + "description": "If false cannot borrow", + "type": "boolean" + }, + "borrow_index": { + "description": "Borrow index (Used to compute borrow interest)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "borrow_rate": { + "description": "Rate charged to borrowers", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "debt_total_scaled": { + "description": "Total debt scaled for the market's currency", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "Denom of the asset", + "type": "string" + }, + "deposit_cap": { + "description": "Deposit Cap (defined in terms of the asset)", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "deposit_enabled": { + "description": "If false cannot deposit", + "type": "boolean" + }, + "indexes_last_updated": { + "description": "Timestamp (seconds) where indexes and where last updated", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "interest_rate_model": { + "description": "model (params + internal state) that defines how interest rate behaves", + "allOf": [ + { + "$ref": "#/definitions/InterestRateModel" + } + ] + }, + "liquidation_bonus": { + "description": "Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral from user in an amount equal to debt repayed + bonus)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "liquidation_threshold": { + "description": "Base asset amount in debt position per \"base asset\" of asset collateral that if surpassed makes the user's position liquidatable.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "liquidity_index": { + "description": "Liquidity index (Used to compute deposit interest)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "liquidity_rate": { + "description": "Rate paid to depositors", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "ma_token_address": { + "description": "maToken contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "max_loan_to_value": { + "description": "Max base asset that can be borrowed per \"base asset\" collateral when using the asset as collateral", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "reserve_factor": { + "description": "Portion of the borrow rate that is kept as protocol rewards", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "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" + }, + "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" + }, + "InterestRateModel": { + "type": "object", + "required": [ + "base", + "optimal_utilization_rate", + "slope_1", + "slope_2" + ], + "properties": { + "base": { + "description": "Base rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "optimal_utilization_rate": { + "description": "Optimal utilization rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope_1": { + "description": "Slope parameter for interest rate model function when utilization_rate < optimal_utilization_rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope_2": { + "description": "Slope parameter for interest rate model function when utilization_rate >= optimal_utilization_rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + } + }, + "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" + } + } + }, + "user_asset_debt": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UserAssetDebtResponse", + "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/schemas/mock-vault/mock-vault.json b/schemas/mock-vault/mock-vault.json new file mode 100644 index 000000000..9a02ff125 --- /dev/null +++ b/schemas/mock-vault/mock-vault.json @@ -0,0 +1,224 @@ +{ + "contract_name": "mock-vault", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "asset_denoms", + "lp_token_denom", + "oracle" + ], + "properties": { + "asset_denoms": { + "description": "Denoms for assets in vault", + "type": "array", + "items": { + "type": "string" + } + }, + "lockup": { + "description": "Time in seconds for unlock period", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "lp_token_denom": { + "description": "Denom for vault LP share token", + "type": "string" + }, + "oracle": { + "$ref": "#/definitions/OracleBase_for_String" + } + }, + "definitions": { + "OracleBase_for_String": { + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "Partial compatibility with EIP-4626", + "oneOf": [ + { + "description": "Enters list of `Vec` into a vault strategy in exchange for vault tokens.", + "type": "object", + "required": [ + "deposit" + ], + "properties": { + "deposit": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw underlying coins in vault by exchanging vault `Coin`", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "A privileged action only to be used by Rover. Same as `Withdraw` except it bypasses any lockup period restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation.", + "type": "object", + "required": [ + "force_withdraw" + ], + "properties": { + "force_withdraw": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Vault requirements, lockup, & vault token denom", + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "All the coins that would be redeemed for in exchange for vault coins. Used by Rover to calculate vault position values.", + "type": "object", + "required": [ + "preview_redeem" + ], + "properties": { + "preview_redeem": { + "type": "object", + "required": [ + "shares" + ], + "properties": { + "shares": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "migrate": null, + "sudo": null, + "responses": { + "info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VaultInfo", + "type": "object", + "required": [ + "coins", + "token_denom" + ], + "properties": { + "coins": { + "description": "Coins required to enter vault. Amount will be proportional to the share of which it should occupy in the group (e.g. { denom: osmo, amount: 1 }, { denom: atom, amount: 1 } indicate a 50-50 split)", + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "lockup": { + "description": "Time in seconds for unlock period", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "token_denom": { + "description": "Denom of vault token", + "type": "string" + } + }, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "preview_redeem": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Coin", + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + }, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + } + } +} diff --git a/schemas/swapper-base/swapper-base.json b/schemas/swapper-base/swapper-base.json new file mode 100644 index 000000000..f50201138 --- /dev/null +++ b/schemas/swapper-base/swapper-base.json @@ -0,0 +1,396 @@ +{ + "contract_name": "swapper-base", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "description": "The contract's owner, who can update config", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Update contract config", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "owner": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Configure the route for swapping an asset\n\nThis is chain-specific, and can include parameters such as slippage tolerance and the routes for multi-step swaps", + "type": "object", + "required": [ + "set_route" + ], + "properties": { + "set_route": { + "type": "object", + "required": [ + "denom_in", + "denom_out", + "route" + ], + "properties": { + "denom_in": { + "type": "string" + }, + "denom_out": { + "type": "string" + }, + "route": { + "$ref": "#/definitions/Empty" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %.", + "type": "object", + "required": [ + "swap_exact_in" + ], + "properties": { + "swap_exact_in": { + "type": "object", + "required": [ + "coin_in", + "denom_out", + "slippage" + ], + "properties": { + "coin_in": { + "$ref": "#/definitions/Coin" + }, + "denom_out": { + "type": "string" + }, + "slippage": { + "$ref": "#/definitions/Decimal" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Send swapper results back to swapper. Also refunds extra if sent more than needed. Internal use only.", + "type": "object", + "required": [ + "transfer_result" + ], + "properties": { + "transfer_result": { + "type": "object", + "required": [ + "denom_in", + "denom_out", + "recipient" + ], + "properties": { + "denom_in": { + "type": "string" + }, + "denom_out": { + "type": "string" + }, + "recipient": { + "$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" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "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" + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Query contract config", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get route for swapping an input denom into an output denom", + "type": "object", + "required": [ + "route" + ], + "properties": { + "route": { + "type": "object", + "required": [ + "denom_in", + "denom_out" + ], + "properties": { + "denom_in": { + "type": "string" + }, + "denom_out": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate all swapper routes", + "type": "object", + "required": [ + "routes" + ], + "properties": { + "routes": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return current spot price swapping In for Out Warning: Do not use this as an oracle price feed. Use Mars-Oracle for pricing.", + "type": "object", + "required": [ + "estimate_exact_in_swap" + ], + "properties": { + "estimate_exact_in_swap": { + "type": "object", + "required": [ + "coin_in", + "denom_out" + ], + "properties": { + "coin_in": { + "$ref": "#/definitions/Coin" + }, + "denom_out": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "migrate": null, + "sudo": null, + "responses": { + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config_for_String", + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "description": "The contract's owner, who can update config", + "type": "string" + } + } + }, + "estimate_exact_in_swap": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "EstimateExactInSwapResponse", + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + }, + "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" + } + } + }, + "route": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RouteResponse_for_Empty", + "type": "object", + "required": [ + "denom_in", + "denom_out", + "route" + ], + "properties": { + "denom_in": { + "type": "string" + }, + "denom_out": { + "type": "string" + }, + "route": { + "$ref": "#/definitions/Empty" + } + }, + "definitions": { + "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" + } + } + }, + "routes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_RouteResponse_for_Empty", + "type": "array", + "items": { + "$ref": "#/definitions/RouteResponse_for_Empty" + }, + "definitions": { + "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" + }, + "RouteResponse_for_Empty": { + "type": "object", + "required": [ + "denom_in", + "denom_out", + "route" + ], + "properties": { + "denom_in": { + "type": "string" + }, + "denom_out": { + "type": "string" + }, + "route": { + "$ref": "#/definitions/Empty" + } + } + } + } + } + } +} diff --git a/scripts/.eslintignore b/scripts/.eslintignore new file mode 100644 index 000000000..ebb89772c --- /dev/null +++ b/scripts/.eslintignore @@ -0,0 +1,5 @@ +# No checking of built files +build/ + +# Automatically generated by ts-codegen +types/generated diff --git a/scripts/.eslintrc b/scripts/.eslintrc deleted file mode 100644 index 79bd6ef23..000000000 --- a/scripts/.eslintrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint"], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" - ] -} diff --git a/scripts/.eslintrc.cjs b/scripts/.eslintrc.cjs new file mode 100644 index 000000000..d8fc35a63 --- /dev/null +++ b/scripts/.eslintrc.cjs @@ -0,0 +1,20 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + plugins: ['@typescript-eslint'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'plugin:@typescript-eslint/strict', + 'prettier', + ], + rules: { + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + }, +} diff --git a/scripts/.prettierignore b/scripts/.prettierignore new file mode 100644 index 000000000..567609b12 --- /dev/null +++ b/scripts/.prettierignore @@ -0,0 +1 @@ +build/ diff --git a/scripts/.prettierrc b/scripts/.prettierrc index dc8c7109f..8c32ca31a 100644 --- a/scripts/.prettierrc +++ b/scripts/.prettierrc @@ -1,5 +1,7 @@ { "singleQuote": true, - "printWidth": 120, + "jsxSingleQuote": true, + "semi": false, + "printWidth": 100, "trailingComma": "all" } diff --git a/scripts/babel.config.js b/scripts/babel.config.js deleted file mode 100644 index e6ffbd417..000000000 --- a/scripts/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], -}; diff --git a/scripts/codegen-tsconfig.json b/scripts/codegen-tsconfig.json new file mode 100644 index 000000000..f27757e2e --- /dev/null +++ b/scripts/codegen-tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["codegen"], + "exclude": ["deploy", "types", "utils", "build"] +} diff --git a/scripts/codegen/index.ts b/scripts/codegen/index.ts new file mode 100644 index 000000000..e8a73f7ee --- /dev/null +++ b/scripts/codegen/index.ts @@ -0,0 +1,39 @@ +import codegen from '@cosmwasm/ts-codegen' +import { join, resolve } from 'path' +import { printGreen, printRed } from '../utils/chalk' +import { readdir } from 'fs/promises' + +void (async function () { + const schemasDir = resolve(join(__dirname, '../../../schemas')) + const schemas = await readdir(schemasDir) + + for (const schema of schemas) { + try { + await codegen({ + contracts: [`${schemasDir}/${schema}`], + outPath: `./types/generated/${schema}`, + options: { + types: { + enabled: true, + }, + client: { + enabled: true, + }, + reactQuery: { + enabled: true, + optionalClient: true, + version: 'v4', + mutations: true, + queryKeys: true, + }, + messageComposer: { + enabled: false, + }, + }, + }) + printGreen(`Success ✨ ${schema} types generated`) + } catch (e) { + printRed(`Error with ${schema}: ${e}`) + } + } +})() diff --git a/scripts/codegen/insertIgnores.ts b/scripts/codegen/insertIgnores.ts new file mode 100644 index 000000000..960608e87 --- /dev/null +++ b/scripts/codegen/insertIgnores.ts @@ -0,0 +1,24 @@ +import { readdir } from 'fs/promises' +import { join, resolve } from 'path' +import prependFile from 'prepend-file' + +// Unfortunately ts-codegen spits out code that does not compile well with typescript +// This adds an ignore at the top of those files so no compile error is thrown +void (async function () { + const generatedTypesDir = resolve(join(__dirname, '../../types/generated')) + const typeFiles = await getFiles(generatedTypesDir) + for (const file of typeFiles) { + await prependFile(file, '// @ts-nocheck\n') + } +})() + +async function getFiles(dir: string): Promise { + const dirents = await readdir(dir, { withFileTypes: true }) + const files = await Promise.all( + dirents.map((dirent) => { + const res = resolve(dir, dirent.name) + return dirent.isDirectory() ? getFiles(res) : res + }), + ) + return Array.prototype.concat(...files) as string[] +} diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json new file mode 100644 index 000000000..8bfee62c8 --- /dev/null +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -0,0 +1,8 @@ +{ + "accountNft": "osmo1zwxt98d4w6dc7nwgh5znmqf2kglyax4gg9hr3497lmfywfcckppsgw3hkx", + "mockRedBank": "osmo1zqsgw96drq3cav4kwfecfy26zyl0wt6zpkh9xwfj08smt6acq45skrakkp", + "mockOracle": "osmo1dqfzp4vqyfcz0f6yuajvky9gwaxnpw2kmh447z0wy37k40hl8g8sdwq9qp", + "mockVault": "osmo1m7cx2r6czyqmqc7nt8zhlk8y63ewk23wfg3tvng9askeztzt7f7qq294hz", + "swapper": "osmo1dkg8rkhgtvav3aufth6ud49pz5ces6vqg5vwdgthlhgtutq74q8qrrt2wd", + "creditManager": "osmo1zf26ahe5gqjtvnedh7ems7naf2wtw3z4ll6atf3t0hptal8ss4vq2mlx6w" +} diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts new file mode 100644 index 000000000..0bff2437f --- /dev/null +++ b/scripts/deploy/base/deployer.ts @@ -0,0 +1,207 @@ +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' +import { DeploymentConfig } from '../../types/config' +import { printBlue, printGray, printGreen } from '../../utils/chalk' +import { ARTIFACTS_PATH, Storage } from './storage' +import fs from 'fs' +import { InstantiateMsgs } from '../../types/instantiateMsgs' +import { InstantiateMsg as NftInstantiateMsg } from '../../types/generated/account-nft/AccountNft.types' +import { InstantiateMsg as RedBankInstantiateMsg } from '../../types/generated/mock-red-bank/MockRedBank.types' +import { InstantiateMsg as VaultInstantiateMsg } from '../../types/generated/mock-vault/MockVault.types' +import { InstantiateMsg as SwapperInstantiateMsg } from '../../types/generated/swapper-base/SwapperBase.types' +import { InstantiateMsg as OracleInstantiateMsg } from '../../types/generated/mock-oracle/MockOracle.types' +import { InstantiateMsg as RoverInstantiateMsg } from '../../types/generated/credit-manager/CreditManager.types' +import { Rover } from './rover' +import { AccountNftClient } from '../../types/generated/account-nft/AccountNft.client' +import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing' +import { getAddress, getWallet, setupClient } from './setupDeployer' +import { coins } from '@cosmjs/stargate' +import { Coin } from '@cosmjs/amino' +import { writeFile } from 'fs/promises' +import { join, resolve } from 'path' +import { + SwapperBaseClient, + SwapperBaseQueryClient, +} from '../../types/generated/swapper-base/SwapperBase.client' +import assert from 'assert' + +export class Deployer { + constructor( + private config: DeploymentConfig, + private cwClient: SigningCosmWasmClient, + private deployerAddr: string, + private storage: Storage, + ) {} + + async saveStorage() { + await this.storage.save() + } + + async upload(name: keyof Storage['codeIds'], file: string) { + if (this.storage.codeIds[name]) { + printGray(`Wasm already uploaded :: ${name} :: ${this.storage.codeIds[name]}`) + return + } + const wasm = fs.readFileSync(ARTIFACTS_PATH + file) + const uploadResult = await this.cwClient.upload(this.deployerAddr, wasm, 'auto') + this.storage.codeIds[name] = uploadResult.codeId + printGreen(`${this.config.chainId} :: ${name} : ${this.storage.codeIds[name]}`) + } + + async instantiate(name: keyof Storage['addresses'], codeId: number, msg: InstantiateMsgs) { + if (this.storage.addresses[name]) { + printGray(`Contract already instantiated :: ${name} :: ${this.storage.addresses[name]}`) + return + } + const { contractAddress } = await this.cwClient.instantiate( + this.deployerAddr, + codeId, + msg, + `mars-${name}`, + 'auto', + ) + this.storage.addresses[name] = contractAddress + printGreen( + `${this.config.chainId} :: ${name} Contract Address : ${this.storage.addresses[name]}`, + ) + } + + async instantiateNftContract() { + const msg: NftInstantiateMsg = { + minter: this.deployerAddr, + name: 'credit-manger-accounts', + symbol: 'rover-nft', + } + await this.instantiate('accountNft', this.storage.codeIds.accountNft!, msg) + } + + async instantiateMockRedBank() { + const msg: RedBankInstantiateMsg = { + coins: this.config.mockRedbankCoins, + } + await this.instantiate('mockRedBank', this.storage.codeIds.mockRedBank!, msg) + printBlue(`Seeding funds in RedBank: ${JSON.stringify(this.config.seededFundsForMockRedBank)}`) + await this.transferFunds( + this.storage.addresses.mockRedBank!, + this.config.seededFundsForMockRedBank, + ) + } + + async instantiateMockOracle() { + const msg: OracleInstantiateMsg = { + coins: this.config.oraclePrices, + } + await this.instantiate('mockOracle', this.storage.codeIds.mockOracle!, msg) + } + + async instantiateMockVault() { + const msg: VaultInstantiateMsg = { + asset_denoms: [this.config.baseDenom], + lp_token_denom: 'xCompounder', + oracle: this.storage.addresses.mockOracle!, + } + await this.instantiate('mockVault', this.storage.codeIds.mockVault!, msg) + } + + async instantiateSwapper() { + const msg: SwapperInstantiateMsg = { + owner: this.deployerAddr, + } + await this.instantiate('swapper', this.storage.codeIds.swapper!, msg) + + await this.transferFunds(this.storage.addresses.swapper!, [ + { denom: this.config.baseDenom, amount: '100' }, + ]) + + const swapClient = new SwapperBaseClient( + this.cwClient, + this.deployerAddr, + this.storage.addresses.swapper!, + ) + printBlue( + `Setting ${this.config.baseDenom}-${this.config.secondaryDenom} route for swap contract`, + ) + await swapClient.setRoute({ + denomIn: this.config.baseDenom, + denomOut: this.config.secondaryDenom, + route: this.config.swapRoute, + }) + + const swapQuery = new SwapperBaseQueryClient(this.cwClient, this.storage.addresses.swapper!) + const routes = await swapQuery.routes({}) + assert.equal(routes.length, 1) + } + + async instantiateCreditManager() { + const msg: RoverInstantiateMsg = { + allowed_coins: [this.config.baseDenom, this.config.secondaryDenom], + allowed_vaults: [this.storage.addresses.mockVault!], + oracle: this.storage.addresses.mockOracle!, + owner: this.deployerAddr, + red_bank: this.storage.addresses.mockRedBank!, + max_close_factor: this.config.maxCloseFactor.toString(), + max_liquidation_bonus: this.config.maxLiquidationBonus.toString(), + swapper: this.storage.addresses.swapper!, + } + await this.instantiate('creditManager', this.storage.codeIds.creditManager!, msg) + } + + async transferNftContractOwnership() { + if (!this.storage.actions.proposedNewOwner) { + const nftClient = new AccountNftClient( + this.cwClient, + this.deployerAddr, + this.storage.addresses.accountNft!, + ) + await nftClient.proposeNewOwner({ newOwner: this.storage.addresses.creditManager! }) + this.storage.actions.proposedNewOwner = true + printBlue('Nft contract owner proposes Rover as new owner') + } else { + printGray('Nft contact owner change already proposed') + } + + if (!this.storage.actions.acceptedOwnership) { + const rover = this.getRoverClient(this.deployerAddr, this.cwClient) + await rover.updateConfig({ account_nft: this.storage.addresses.accountNft }) + this.storage.actions.acceptedOwnership = true + printGreen(`Rover accepts ownership of Nft contract`) + } else { + printGray('Rover already accepted Nft contract ownership') + } + } + + async newUserRoverClient() { + const { client, address } = await this.generateNewAddress() + printBlue(`New user: ${address}`) + await this.transferFunds( + address, + coins(this.config.startingAmountForTestUser, this.config.baseDenom), + ) + return this.getRoverClient(address, client) + } + + async saveDeploymentAddrsToFile() { + const addressesDir = resolve(join(__dirname, '../../../deploy/addresses')) + await writeFile( + `${addressesDir}/${this.config.chainId}.json`, + JSON.stringify(this.storage.addresses), + ) + } + + private async transferFunds(recipient: string, coins: Coin[]) { + await this.cwClient.sendTokens(this.deployerAddr, recipient, coins, 'auto') + const balance = await this.cwClient.getBalance(recipient, this.config.baseDenom) + printBlue(`New balance: ${balance.amount} ${balance.denom}`) + } + + private async generateNewAddress() { + const { mnemonic } = await DirectSecp256k1HdWallet.generate(24) + const wallet = await getWallet(mnemonic, this.config.chainPrefix) + const client = await setupClient(this.config, wallet) + const address = await getAddress(wallet) + return { client, address } + } + + private getRoverClient(address: string, client: SigningCosmWasmClient) { + return new Rover(address, this.storage, this.config, client) + } +} diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts new file mode 100644 index 000000000..8f3ebbcd8 --- /dev/null +++ b/scripts/deploy/base/index.ts @@ -0,0 +1,53 @@ +import { setupDeployer } from './setupDeployer' +import { printRed, printYellow } from '../../utils/chalk' +import { DeploymentConfig } from '../../types/config' +import { wasmFile } from '../../utils/environment' + +export interface TaskRunnerProps { + config: DeploymentConfig + swapperContractName: string +} + +export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProps) => { + const deployer = await setupDeployer(config) + try { + // Upload contracts + await deployer.upload('accountNft', wasmFile('account_nft')) + await deployer.upload('mockRedBank', wasmFile('mock_red_bank')) + await deployer.upload('mockVault', wasmFile('mock_vault')) + await deployer.upload('mockOracle', wasmFile('mock_oracle')) + await deployer.upload('swapper', wasmFile(swapperContractName)) + await deployer.upload('creditManager', wasmFile('credit_manager')) + + // Instantiate contracts + await deployer.instantiateNftContract() + await deployer.instantiateMockRedBank() + await deployer.instantiateMockOracle() + await deployer.instantiateMockVault() + await deployer.instantiateSwapper() + await deployer.instantiateCreditManager() + await deployer.transferNftContractOwnership() + await deployer.saveDeploymentAddrsToFile() + + const rover = await deployer.newUserRoverClient() + + // Test basic user flows + await rover.createCreditAccount() + await rover.deposit() + await rover.borrow() + await rover.repay() + // TODO: Osmosis-bindings need updating + // await rover.swap() + await rover.withdraw() + + // TODO: Use after token factory is launched and integrated into mock_vault + // or Apollo vaults are on testnet + // await rover.vaultDeposit() + + printYellow('COMPLETE') + } catch (e) { + printRed(e) + } finally { + await deployer.saveStorage() + } +} diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts new file mode 100644 index 000000000..2924c6335 --- /dev/null +++ b/scripts/deploy/base/rover.ts @@ -0,0 +1,143 @@ +import { + CreditManagerClient, + CreditManagerQueryClient, +} from '../../types/generated/credit-manager/CreditManager.client' +import { AccountNftQueryClient } from '../../types/generated/account-nft/AccountNft.client' +import { Storage } from './storage' +import { DeploymentConfig } from '../../types/config' +import { difference } from 'lodash' +import assert from 'assert' +import { printBlue, printGreen } from '../../utils/chalk' +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' +import { + Action, + Coin, + ConfigUpdates, +} from '../../types/generated/credit-manager/CreditManager.types' + +export class Rover { + private exec: CreditManagerClient + private query: CreditManagerQueryClient + private nft: AccountNftQueryClient + private accountId?: string + + constructor( + private userAddr: string, + private storage: Storage, + private config: DeploymentConfig, + cwClient: SigningCosmWasmClient, + ) { + this.exec = new CreditManagerClient(cwClient, userAddr, storage.addresses.creditManager!) + this.query = new CreditManagerQueryClient(cwClient, storage.addresses.creditManager!) + this.nft = new AccountNftQueryClient(cwClient, storage.addresses.accountNft!) + } + + async updateConfig(newConfig: ConfigUpdates) { + await this.exec.updateConfig({ newConfig }) + } + + async createCreditAccount() { + const before = await this.nft.tokens({ owner: this.userAddr }) + await this.exec.createCreditAccount() + const after = await this.nft.tokens({ owner: this.userAddr }) + const diff = difference(after.tokens, before.tokens) + assert.equal(diff.length, 1) + this.accountId = diff[0] + printGreen(`Newly created credit account id: #${diff[0]}`) + } + + async deposit() { + const amount = this.config.depositAmount.toString() + await this.updateCreditAccount( + [{ deposit: { amount, denom: this.config.baseDenom } }], + [{ amount, denom: this.config.baseDenom }], + ) + const positions = await this.query.positions({ tokenId: this.accountId! }) + assert.equal(positions.coins.length, 1) + assert.equal(positions.coins[0].amount, amount) + assert.equal(positions.coins[0].denom, this.config.baseDenom) + printGreen(`Deposited: ${amount} ${this.config.baseDenom}`) + } + + async withdraw() { + const amount = this.config.withdrawAmount.toString() + const positionsBefore = await this.query.positions({ tokenId: this.accountId! }) + const beforeWithdraw = parseFloat(positionsBefore.coins[0].amount) + await this.updateCreditAccount([{ withdraw: { amount, denom: this.config.baseDenom } }]) + const positionsAfter = await this.query.positions({ tokenId: this.accountId! }) + const afterWithdraw = parseFloat(positionsAfter.coins[0].amount) + assert.equal(beforeWithdraw - afterWithdraw, amount) + printGreen(`Withdrew: ${amount} ${this.config.baseDenom}`) + } + + async borrow() { + const amount = this.config.borrowAmount.toString() + await this.updateCreditAccount([{ borrow: { amount, denom: this.config.baseDenom } }]) + const positions = await this.query.positions({ tokenId: this.accountId! }) + assert.equal(positions.debt.length, 1) + assert.equal(positions.debt[0].denom, this.config.baseDenom) + printGreen(`Borrowed from RedBank: ${amount} ${this.config.baseDenom}`) + } + + async repay() { + const amount = this.config.repayAmount.toString() + await this.updateCreditAccount([{ repay: { amount, denom: this.config.baseDenom } }]) + const positions = await this.query.positions({ tokenId: this.accountId! }) + printGreen( + `Repaid to RedBank: ${amount} ${this.config.baseDenom}. Debt remaining: ${positions.debt[0].amount} ${positions.debt[0].denom}`, + ) + } + + async swap() { + const amount = this.config.swapAmount.toString() + printBlue(`Swapping ${amount} ${this.config.baseDenom} for ${this.config.secondaryDenom}`) + const prevPositions = await this.query.positions({ tokenId: this.accountId! }) + printBlue( + `Previous account balance: ${prevPositions.coins[0].amount} ${prevPositions.coins[0].denom}`, + ) + await this.updateCreditAccount([ + { + swap_exact_in: { + coin_in: { amount, denom: this.config.baseDenom }, + denom_out: this.config.secondaryDenom, + slippage: this.config.slippage.toString(), + }, + }, + ]) + printGreen(`Swap successful`) + const newPositions = await this.query.positions({ tokenId: this.accountId! }) + printGreen( + `New account balance: ${newPositions.coins[0].amount} ${newPositions.coins[0].denom}, ${newPositions.coins[1].amount} ${newPositions.coins[1].denom}`, + ) + } + + async vaultDeposit() { + const amount = '10' + await this.updateCreditAccount([ + { + vault_deposit: { + coins: [{ amount, denom: this.config.baseDenom }], + vault: this.storage.addresses.mockVault!, + }, + }, + ]) + const positions = await this.query.positions({ tokenId: this.accountId! }) + assert.equal(positions.vault_positions.length, 1) + assert.equal(positions.vault_positions[0].addr, this.storage.addresses.mockVault) + assert.equal(positions.vault_positions[0].position, this.config.baseDenom) + printGreen( + `Deposit into vault: ${amount} ${this.config.baseDenom}, Vault Postition: ${JSON.stringify( + positions.vault_positions[0].position, + )}`, + ) + } + + private async updateCreditAccount(actions: Action[], funds?: Coin[]) { + await this.exec.updateCreditAccount( + { actions, tokenId: this.accountId! }, + 'auto', + undefined, + funds, + ) + } +} diff --git a/scripts/deploy/base/setupDeployer.ts b/scripts/deploy/base/setupDeployer.ts new file mode 100644 index 000000000..0b2c1447d --- /dev/null +++ b/scripts/deploy/base/setupDeployer.ts @@ -0,0 +1,36 @@ +import { SigningCosmWasmClient, SigningCosmWasmClientOptions } from '@cosmjs/cosmwasm-stargate' +import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing' +import { GasPrice } from '@cosmjs/stargate' +import { DeploymentConfig } from '../../types/config' +import { Deployer } from './deployer' +import { Storage } from './storage' +import { printGray } from '../../utils/chalk' + +export const getWallet = async (mnemonic: string, chainPrefix: string) => { + return await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { + prefix: chainPrefix, + }) +} + +export const getAddress = async (wallet: DirectSecp256k1HdWallet) => { + const accounts = await wallet.getAccounts() + return accounts[0].address +} + +export const setupClient = async (config: DeploymentConfig, wallet: DirectSecp256k1HdWallet) => { + const clientOption: SigningCosmWasmClientOptions = { + gasPrice: GasPrice.fromString(`${config.defaultGasPrice}${config.baseDenom}`), + } + return await SigningCosmWasmClient.connectWithSigner(config.rpcEndpoint, wallet, clientOption) +} + +export const setupDeployer = async (config: DeploymentConfig) => { + const wallet = await getWallet(config.deployerMnemonic, config.chainPrefix) + const client = await setupClient(config, wallet) + const addr = await getAddress(wallet) + const balance = await client.getBalance(addr, config.baseDenom) + printGray(`Deployer addr: ${addr}, balance: ${parseInt(balance.amount) / 1e6} ${balance.denom}`) + + const storage = await Storage.load(config.chainId) + return new Deployer(config, client, addr, storage) +} diff --git a/scripts/deploy/base/storage.ts b/scripts/deploy/base/storage.ts new file mode 100644 index 000000000..fb0c52c3f --- /dev/null +++ b/scripts/deploy/base/storage.ts @@ -0,0 +1,34 @@ +import { readFile, writeFile } from 'fs/promises' +import path from 'path' +import { StorageItems as StorageItems } from '../../types/storageItems' + +export const ARTIFACTS_PATH = '../artifacts/' + +export class Storage implements StorageItems { + public addresses: StorageItems['addresses'] + public codeIds: StorageItems['codeIds'] + public actions: StorageItems['actions'] + + constructor(private chainId: string, items: StorageItems) { + this.addresses = items.addresses + this.codeIds = items.codeIds + this.actions = items.actions + } + + static async load(chainId: string): Promise { + try { + const data = await readFile(path.join(ARTIFACTS_PATH, `${chainId}.json`), 'utf8') + const items = JSON.parse(data) as StorageItems + return new this(chainId, items) + } catch (e) { + return new this(chainId, { addresses: {}, codeIds: {}, actions: {} }) + } + } + + async save() { + await writeFile( + path.join(ARTIFACTS_PATH, `${this.chainId}.json`), + JSON.stringify(this, null, 2), + ) + } +} diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts new file mode 100644 index 000000000..6bda2a9b5 --- /dev/null +++ b/scripts/deploy/osmosis/config.ts @@ -0,0 +1,33 @@ +import { DeploymentConfig } from '../../types/config' +import { coins } from '@cosmjs/stargate' + +export const osmosisTestnetConfig: DeploymentConfig = { + baseDenom: 'uosmo', + secondaryDenom: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2', // uatom + chainId: 'osmo-test-4', + chainPrefix: 'osmo', + deployerMnemonic: + 'rely wonder join knock during sudden slow plate segment state agree also arrest mandate grief ordinary lonely lawsuit hurt super banana rule velvet cart', + rpcEndpoint: 'https://rpc-test.osmosis.zone', + defaultGasPrice: 0.1, + startingAmountForTestUser: 1e6, + mockRedbankCoins: [{ denom: 'uosmo', max_ltv: '0.8', liquidation_threshold: '0.9' }], + seededFundsForMockRedBank: coins(100, 'uosmo'), + oraclePrices: [{ denom: 'uosmo', price: '12.1' }], + maxCloseFactor: 0.6, + maxLiquidationBonus: 0.05, + depositAmount: 100, + borrowAmount: 10, + repayAmount: 3, + swapAmount: 12, + swapRoute: { + steps: [ + { + denom_out: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2', + pool_id: 1, + }, + ], + }, + slippage: 0.4, + withdrawAmount: 12, +} diff --git a/scripts/deploy/osmosis/index.ts b/scripts/deploy/osmosis/index.ts new file mode 100644 index 000000000..d7943516b --- /dev/null +++ b/scripts/deploy/osmosis/index.ts @@ -0,0 +1,6 @@ +import { taskRunner } from '../base' +import { osmosisTestnetConfig } from './config' + +void (async function () { + await taskRunner({ config: osmosisTestnetConfig, swapperContractName: 'swapper_osmosis' }) +})() diff --git a/scripts/package-lock.json b/scripts/package-lock.json deleted file mode 100644 index a9eab35ce..000000000 --- a/scripts/package-lock.json +++ /dev/null @@ -1,12070 +0,0 @@ -{ - "name": "credit-manager-scripts", - "version": "2.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "credit-manager-scripts", - "version": "2.0.0", - "dependencies": { - "@cosmjs/cosmwasm-stargate": "^0.28.4", - "@cosmjs/stargate": "^0.28.4", - "cosmjs-types": "^0.5.0", - "long": "^5.2.0", - "osmojs": "^0.4.46" - }, - "devDependencies": { - "@babel/preset-env": "^7.18.2", - "@babel/preset-typescript": "^7.17.12", - "@types/jest": "^28.1.3", - "@typescript-eslint/eslint-plugin": "^5.27.0", - "@typescript-eslint/parser": "^5.27.0", - "eslint": "^8.16.0", - "jest": "^28.1.0", - "prettier": "2.7.1", - "ts-node": "^10.5.0", - "typescript": "^4.7.4" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.17.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", - "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.2.tgz", - "integrity": "sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.18.2", - "@babel/helper-compilation-targets": "^7.18.2", - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helpers": "^7.18.2", - "@babel/parser": "^7.18.0", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.2", - "@babel/types": "^7.18.2", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", - "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.2", - "@jridgewell/gen-mapping": "^0.3.0", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", - "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", - "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", - "dev": true, - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz", - "integrity": "sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.17.10", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.20.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.0.tgz", - "integrity": "sha512-Kh8zTGR9de3J63e5nS0rQUdRs/kbtwoeQQ0sriS0lItjC96u8XXZN6lKpuyWd2coKSU13py/y+LTmThLuVX0Pg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-member-expression-to-functions": "^7.17.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.12.tgz", - "integrity": "sha512-b2aZrV4zvutr9AIa6/gA3wsZKRwTKYoDxYiFKcESS3Ug2GTXzwBEvMuuFLhCQpEnRXs1zng4ISAXSUxxKBIcxw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "regexpu-core": "^5.0.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz", - "integrity": "sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", - "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", - "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.16.7", - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", - "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz", - "integrity": "sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.17.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.0", - "@babel/types": "^7.18.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz", - "integrity": "sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", - "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-wrap-function": "^7.16.8", - "@babel/types": "^7.16.8" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.2.tgz", - "integrity": "sha512-XzAIyxx+vFnrOxiQrToSUOzUOn0e1J2Li40ntddek1Y69AXUTXoDJ40/D5RdjFu7s7qHiaeoTiempZcbuVXh2Q==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.2", - "@babel/helper-member-expression-to-functions": "^7.17.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/traverse": "^7.18.2", - "@babel/types": "^7.18.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz", - "integrity": "sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.2.tgz", - "integrity": "sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.2", - "@babel/types": "^7.18.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", - "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz", - "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.17.12.tgz", - "integrity": "sha512-xCJQXl4EeQ3J9C4yOmpTrtVGmzpm2iSzyxbkZHw7UCnZBftHpF/hpII80uWVyVrc40ytIClHjgWGTG1g/yB+aw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.17.12.tgz", - "integrity": "sha512-/vt0hpIw0x4b6BLKUkwlvEoiGZYYLNZ96CzyHYPbtG2jZGz6LBe7/V+drYrc/d+ovrF9NBi0pmtvmNb/FsWtRQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.17.12.tgz", - "integrity": "sha512-RWVvqD1ooLKP6IqWTA5GyFVX2isGEgC5iFxKzfYOIy/QEFdxYyCybBDtIGjipHpb9bDWHzcqGqFakf+mVmBTdQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-remap-async-to-generator": "^7.16.8", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.17.12.tgz", - "integrity": "sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.17.12", - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.0.tgz", - "integrity": "sha512-t+8LsRMMDE74c6sV7KShIw13sqbqd58tlqNrsWoWBTIMw7SVQ0cZ905wLNS/FBCy/3PyooRHLFFlfrUNyyz5lA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.0", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", - "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.17.12.tgz", - "integrity": "sha512-j7Ye5EWdwoXOpRmo5QmRyHPsDIe6+u70ZYZrd7uz+ebPYFKfRcLcNu3Ro0vOlJ5zuv8rU7xa+GttNiRzX56snQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.17.12.tgz", - "integrity": "sha512-rKJ+rKBoXwLnIn7n6o6fulViHMrOThz99ybH+hKHcOZbnN14VuMnH9fo2eHE69C8pO4uX1Q7t2HYYIDmv8VYkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.17.12.tgz", - "integrity": "sha512-EqFo2s1Z5yy+JeJu7SFfbIUtToJTVlC61/C7WLKDntSw4Sz6JNAIfL7zQ74VvirxpjB5kz/kIx0gCcb+5OEo2Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.17.12.tgz", - "integrity": "sha512-ws/g3FSGVzv+VH86+QvgtuJL/kR67xaEIF2x0iPqdDfYW6ra6JF3lKVBkWynRLcNtIC1oCTfDRVxmm2mKzy+ag==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", - "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.0.tgz", - "integrity": "sha512-nbTv371eTrFabDfHLElkn9oyf9VG+VKK6WMzhY2o4eHKaG19BToD9947zzGMO6I/Irstx9d8CwX6njPNIAR/yw==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.17.10", - "@babel/helper-compilation-targets": "^7.17.10", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", - "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.17.12.tgz", - "integrity": "sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.17.12.tgz", - "integrity": "sha512-SllXoxo19HmxhDWm3luPz+cPhtoTSKLJE9PXshsfrOzBqs60QP0r8OaJItrPhAj0d7mZMnNF0Y1UUggCDgMz1A==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.17.12", - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.17.12.tgz", - "integrity": "sha512-/6BtVi57CJfrtDNKfK5b66ydK2J5pXUKBKSPD2G1whamMuEnZWgoOIfO8Vf9F/DoD4izBLD/Au4NMQfruzzykg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-create-class-features-plugin": "^7.17.12", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.17.12.tgz", - "integrity": "sha512-Wb9qLjXf3ZazqXA7IvI7ozqRIXIGPtSo+L5coFmEkhTQK18ao4UDDD0zdTGAarmbLj2urpRwrc6893cu5Bfh0A==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.17.12", - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.17.12.tgz", - "integrity": "sha512-n/loy2zkq9ZEM8tEOwON9wTQSTNDTDEz6NujPtJGLU7qObzT1N4c4YZZf8E6ATB2AjNQg/Ib2AIpO03EZaCehw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz", - "integrity": "sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.17.12.tgz", - "integrity": "sha512-PHln3CNi/49V+mza4xMwrg+WGYevSF1oaiXaC2EQfdp4HWlSjRsrDXWJiQBKpP7749u6vQ9mcry2uuFOv5CXvA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.17.12.tgz", - "integrity": "sha512-J8dbrWIOO3orDzir57NRsjg4uxucvhby0L/KZuGsWDj0g7twWK3g7JhJhOrXtuXiw8MeiSdJ3E0OW9H8LYEzLQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-remap-async-to-generator": "^7.16.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", - "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.4.tgz", - "integrity": "sha512-+Hq10ye+jlvLEogSOtq4mKvtk7qwcUQ1f0Mrueai866C82f844Yom2cttfJdMdqRLTxWpsbfbkIkOIfovyUQXw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.4.tgz", - "integrity": "sha512-e42NSG2mlKWgxKUAD9EJJSkZxR67+wZqzNxLSpc51T8tRU5SLFHsPmgYR5yr7sdgX4u+iHA1C5VafJ6AyImV3A==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.18.2", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-replace-supers": "^7.18.2", - "@babel/helper-split-export-declaration": "^7.16.7", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.17.12.tgz", - "integrity": "sha512-a7XINeplB5cQUWMg1E/GI1tFz3LfK021IjV1rj1ypE+R7jHm+pIHmHl25VNkZxtx9uuYp7ThGk8fur1HHG7PgQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.0.tgz", - "integrity": "sha512-Mo69klS79z6KEfrLg/1WkmVnB8javh75HX4pi2btjvlIoasuxilEyjtsQW6XPrubNd7AQy0MMaNIaQE4e7+PQw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", - "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.17.12.tgz", - "integrity": "sha512-EA5eYFUG6xeerdabina/xIoB95jJ17mAkR8ivx6ZSu9frKShBjpOGZPn511MTDTkiCO+zXnzNczvUM69YSf3Zw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", - "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.1.tgz", - "integrity": "sha512-+TTB5XwvJ5hZbO8xvl2H4XaMDOAK57zF4miuC9qQJgysPNEAZZ9Z69rdF5LJkozGdZrjBIUAIyKUWRMmebI7vg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", - "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.17.12.tgz", - "integrity": "sha512-8iRkvaTjJciWycPIZ9k9duu663FT7VrBdNqNgxnVXEFwOIp55JWcZd23VBRySYbnS3PwQ3rGiabJBBBGj5APmQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", - "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.0.tgz", - "integrity": "sha512-h8FjOlYmdZwl7Xm2Ug4iX2j7Qy63NANI+NQVWQzv6r25fqgg7k2dZl03p95kvqNclglHs4FZ+isv4p1uXMA+QA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helper-plugin-utils": "^7.17.12", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.2.tgz", - "integrity": "sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-simple-access": "^7.18.2", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.4.tgz", - "integrity": "sha512-lH2UaQaHVOAeYrUUuZ8i38o76J/FnO8vu21OE+tD1MyP9lxdZoSfz+pDbWkq46GogUrdrMz3tiz/FYGB+bVThg==", - "dev": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-validator-identifier": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.0.tgz", - "integrity": "sha512-d/zZ8I3BWli1tmROLxXLc9A6YXvGK8egMxHp+E/rRwMh1Kip0AP77VwZae3snEJ33iiWwvNv2+UIIhfalqhzZA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.12.tgz", - "integrity": "sha512-vWoWFM5CKaTeHrdUJ/3SIOTRV+MBVGybOC9mhJkaprGNt5demMymDW24yC74avb915/mIRe3TgNb/d8idvnCRA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.17.12", - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.17.12.tgz", - "integrity": "sha512-CaOtzk2fDYisbjAD4Sd1MTKGVIpRtx9bWLyj24Y/k6p4s4gQ3CqDGJauFJxt8M/LEx003d0i3klVqnN73qvK3w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", - "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.17.12.tgz", - "integrity": "sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", - "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.0.tgz", - "integrity": "sha512-C8YdRw9uzx25HSIzwA7EM7YP0FhCe5wNvJbZzjVNHHPGVcDJ3Aie+qGYYdS1oVQgn+B3eAIJbWFLrJ4Jipv7nw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12", - "regenerator-transform": "^0.15.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.17.12.tgz", - "integrity": "sha512-1KYqwbJV3Co03NIi14uEHW8P50Md6KqFgt0FfpHdK6oyAHQVTosgPuPSiWud1HX0oYJ1hGRRlk0fP87jFpqXZA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", - "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.17.12.tgz", - "integrity": "sha512-9pgmuQAtFi3lpNUstvG9nGfk9DkrdmWNp9KeKPFmuZCpEnxRzYlS8JgwPjYj+1AWDOSvoGN0H30p1cBOmT/Svg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", - "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.2.tgz", - "integrity": "sha512-/cmuBVw9sZBGZVOMkpAEaVLwm4JmK2GZ1dFKOGGpMzEHWFmyZZ59lUU0PdRr8YNYeQdNzTDwuxP2X2gzydTc9g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.17.12.tgz", - "integrity": "sha512-Q8y+Jp7ZdtSPXCThB6zjQ74N3lj0f6TDh1Hnf5B+sYlzQ8i5Pjp8gW0My79iekSpT4WnI06blqP6DT0OmaXXmw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.4.tgz", - "integrity": "sha512-l4vHuSLUajptpHNEOUDEGsnpl9pfRLsN1XUoDQDD/YBuXTM+v37SHGS+c6n4jdcZy96QtuUuSvZYMLSSsjH8Mw==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.0", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-typescript": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", - "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", - "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.2.tgz", - "integrity": "sha512-PfpdxotV6afmXMU47S08F9ZKIm2bJIQ0YbAAtDfIENX7G1NUAXigLREh69CWDjtgUy7dYn7bsMzkgdtAlmS68Q==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.17.10", - "@babel/helper-compilation-targets": "^7.18.2", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.17.12", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.17.12", - "@babel/plugin-proposal-async-generator-functions": "^7.17.12", - "@babel/plugin-proposal-class-properties": "^7.17.12", - "@babel/plugin-proposal-class-static-block": "^7.18.0", - "@babel/plugin-proposal-dynamic-import": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.17.12", - "@babel/plugin-proposal-json-strings": "^7.17.12", - "@babel/plugin-proposal-logical-assignment-operators": "^7.17.12", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.17.12", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.18.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.17.12", - "@babel/plugin-proposal-private-methods": "^7.17.12", - "@babel/plugin-proposal-private-property-in-object": "^7.17.12", - "@babel/plugin-proposal-unicode-property-regex": "^7.17.12", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.17.12", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.17.12", - "@babel/plugin-transform-async-to-generator": "^7.17.12", - "@babel/plugin-transform-block-scoped-functions": "^7.16.7", - "@babel/plugin-transform-block-scoping": "^7.17.12", - "@babel/plugin-transform-classes": "^7.17.12", - "@babel/plugin-transform-computed-properties": "^7.17.12", - "@babel/plugin-transform-destructuring": "^7.18.0", - "@babel/plugin-transform-dotall-regex": "^7.16.7", - "@babel/plugin-transform-duplicate-keys": "^7.17.12", - "@babel/plugin-transform-exponentiation-operator": "^7.16.7", - "@babel/plugin-transform-for-of": "^7.18.1", - "@babel/plugin-transform-function-name": "^7.16.7", - "@babel/plugin-transform-literals": "^7.17.12", - "@babel/plugin-transform-member-expression-literals": "^7.16.7", - "@babel/plugin-transform-modules-amd": "^7.18.0", - "@babel/plugin-transform-modules-commonjs": "^7.18.2", - "@babel/plugin-transform-modules-systemjs": "^7.18.0", - "@babel/plugin-transform-modules-umd": "^7.18.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.17.12", - "@babel/plugin-transform-new-target": "^7.17.12", - "@babel/plugin-transform-object-super": "^7.16.7", - "@babel/plugin-transform-parameters": "^7.17.12", - "@babel/plugin-transform-property-literals": "^7.16.7", - "@babel/plugin-transform-regenerator": "^7.18.0", - "@babel/plugin-transform-reserved-words": "^7.17.12", - "@babel/plugin-transform-shorthand-properties": "^7.16.7", - "@babel/plugin-transform-spread": "^7.17.12", - "@babel/plugin-transform-sticky-regex": "^7.16.7", - "@babel/plugin-transform-template-literals": "^7.18.2", - "@babel/plugin-transform-typeof-symbol": "^7.17.12", - "@babel/plugin-transform-unicode-escapes": "^7.16.7", - "@babel/plugin-transform-unicode-regex": "^7.16.7", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.18.2", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.5.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", - "core-js-compat": "^3.22.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.17.12.tgz", - "integrity": "sha512-S1ViF8W2QwAKUGJXxP9NAfNaqGDdEBJKpYkxHf5Yy2C4NPPzXGeR3Lhk7G8xJaaLcFTRfNjVbtbVtm8Gb0mqvg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-transform-typescript": "^7.17.12" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz", - "integrity": "sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==", - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.2.tgz", - "integrity": "sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.18.2", - "@babel/helper-environment-visitor": "^7.18.2", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.18.0", - "@babel/types": "^7.18.2", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", - "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@confio/ics23": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@confio/ics23/-/ics23-0.6.8.tgz", - "integrity": "sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w==", - "dependencies": { - "@noble/hashes": "^1.0.0", - "protobufjs": "^6.8.8" - } - }, - "node_modules/@cosmjs/amino": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.7.tgz", - "integrity": "sha512-NTCUS3+u9bxwW8moC0N1RS4Gk/fs3JPHTKcp7ksH39V4+7uOKM2rGsyFAekiHNxYngnQ+1hU5x2tddS25soK6Q==", - "dependencies": { - "@cosmjs/crypto": "0.28.7", - "@cosmjs/encoding": "0.28.7", - "@cosmjs/math": "0.28.7", - "@cosmjs/utils": "0.28.7" - } - }, - "node_modules/@cosmjs/cosmwasm-stargate": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.28.9.tgz", - "integrity": "sha512-Icgmiq/hM72mWaCR3FBKb6VZSdeETfTSMZ8E26wBXsvmU24pFqIIqRmJ9y7jSyqRKnGY/0skYNitCob7ylqPSg==", - "dependencies": { - "@cosmjs/amino": "0.28.9", - "@cosmjs/crypto": "0.28.9", - "@cosmjs/encoding": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/proto-signing": "0.28.9", - "@cosmjs/stargate": "0.28.9", - "@cosmjs/tendermint-rpc": "0.28.9", - "@cosmjs/utils": "0.28.9", - "cosmjs-types": "^0.4.0", - "long": "^4.0.0", - "pako": "^2.0.2" - } - }, - "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/amino": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.9.tgz", - "integrity": "sha512-8yaWIlU0W5nF7xyR1v81Wr0+Eo5xJ1bDCkTAsb1JV3xtXvfsl2jF1l4aV64vi+WWEghE+9embhZm7zxBljdunQ==", - "dependencies": { - "@cosmjs/crypto": "0.28.9", - "@cosmjs/encoding": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/utils": "0.28.9" - } - }, - "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/crypto": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.9.tgz", - "integrity": "sha512-2U8cuH7ofRiYni3EPAUsPuqgHN0VM+HdHiY1Ftl2rqRRnZArZNJsbf4t21jVx7rlc+qP32Y1v7PvQyPALHvuyw==", - "dependencies": { - "@cosmjs/encoding": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/utils": "0.28.9", - "@noble/hashes": "^1", - "bn.js": "^5.2.0", - "elliptic": "^6.5.3", - "libsodium-wrappers": "^0.7.6" - } - }, - "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/encoding": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.9.tgz", - "integrity": "sha512-AE+uL5LC2f9ZE8ehFPgb7yNMuGr4wx/cbH25gglRwl9utdND2lPVeYmbEF2MRJwB69hXaiPHblCDIz3KXZV9pQ==", - "dependencies": { - "base64-js": "^1.3.0", - "bech32": "^1.1.4", - "readonly-date": "^1.0.0" - } - }, - "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/math": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.9.tgz", - "integrity": "sha512-CY4k3GMQqXL3M+dGUptgCL6mtkcfvhKhmsrzejSZB8yVOIdKA40Wu6buRMMh2+t3SkWpvjVQt8UiJF7C4C4NXQ==", - "dependencies": { - "bn.js": "^5.2.0" - } - }, - "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/proto-signing": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.9.tgz", - "integrity": "sha512-8MnCvIa2s9myhXEMwsjKOh9/zGp1850QiW9XUy6YtNaQ9wgc58/WMq7HtfkGPqIZnhOeR7KXIyQ0/KHbmjhtnA==", - "dependencies": { - "@cosmjs/amino": "0.28.9", - "@cosmjs/crypto": "0.28.9", - "@cosmjs/encoding": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/utils": "0.28.9", - "cosmjs-types": "^0.4.0", - "long": "^4.0.0" - } - }, - "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/stargate": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.9.tgz", - "integrity": "sha512-1Jh/+qsyzM+7+ek/peQa3xumgU99SvAI5ecDZNS6+qOaWgGdGSW8n40sEfv6viRVLDppfoYo6/9QfR5h3PoKgQ==", - "dependencies": { - "@confio/ics23": "^0.6.8", - "@cosmjs/amino": "0.28.9", - "@cosmjs/encoding": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/proto-signing": "0.28.9", - "@cosmjs/stream": "0.28.9", - "@cosmjs/tendermint-rpc": "0.28.9", - "@cosmjs/utils": "0.28.9", - "cosmjs-types": "^0.4.0", - "long": "^4.0.0", - "protobufjs": "~6.11.3", - "xstream": "^11.14.0" - } - }, - "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/stream": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", - "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", - "dependencies": { - "xstream": "^11.14.0" - } - }, - "node_modules/@cosmjs/cosmwasm-stargate/node_modules/@cosmjs/utils": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.9.tgz", - "integrity": "sha512-5NJ2dSJlT0wtwdcTvprYNtucSk/+aQANEfobFGOBt6n5JT3VlCH5We3a29YC4Z176vy7RsfVf0j2xJabDmp2iw==" - }, - "node_modules/@cosmjs/cosmwasm-stargate/node_modules/cosmjs-types": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", - "integrity": "sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog==", - "dependencies": { - "long": "^4.0.0", - "protobufjs": "~6.11.2" - } - }, - "node_modules/@cosmjs/cosmwasm-stargate/node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/@cosmjs/crypto": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.7.tgz", - "integrity": "sha512-Fuq90nnqXQb6VvUadGtPy1X6arXY+yhqB95VzN8GM/ZdBLeigu6a3XjOvFki4lKO94QdLn2e9huD8dm4tDQDsA==", - "dependencies": { - "@cosmjs/encoding": "0.28.7", - "@cosmjs/math": "0.28.7", - "@cosmjs/utils": "0.28.7", - "@noble/hashes": "^1", - "bn.js": "^5.2.0", - "elliptic": "^6.5.3", - "libsodium-wrappers": "^0.7.6" - } - }, - "node_modules/@cosmjs/encoding": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.7.tgz", - "integrity": "sha512-m2OuRhxux0YacvtjTocEOHyjnKO/KKGjqXlAY5HXGJpyntr+PIlJFdoS9tHax+1rNRrblZXwYIT+UqOs+kSk5Q==", - "dependencies": { - "base64-js": "^1.3.0", - "bech32": "^1.1.4", - "readonly-date": "^1.0.0" - } - }, - "node_modules/@cosmjs/json-rpc": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.9.tgz", - "integrity": "sha512-+Fs0dzRg8tdwnXd6ulN37bmGwOnD5wPiMdQLby5LOZk417Ay5lOFpmIOqnoeti+u9jHBbqnCDNteZ8aFRyarMg==", - "dependencies": { - "@cosmjs/stream": "0.28.9", - "xstream": "^11.14.0" - } - }, - "node_modules/@cosmjs/json-rpc/node_modules/@cosmjs/stream": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", - "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", - "dependencies": { - "xstream": "^11.14.0" - } - }, - "node_modules/@cosmjs/math": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.7.tgz", - "integrity": "sha512-wSXIOOGVzgtRFsGwScpvQ6C6DUx97K7Ez3KyvRALNzspEnIoKPDgTXf438zhZnb+0+ERAwgdkb/6zWaDoVBfUA==", - "dependencies": { - "bn.js": "^5.2.0" - } - }, - "node_modules/@cosmjs/proto-signing": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.7.tgz", - "integrity": "sha512-cOwDQVv95KnpHWlkrtxZ+2Q+z2Qp8Co//g+bwNc+YZ403iGHXSK5PRe3YsPa4f3elKJkJpHVi5oyzp4DB8p7mA==", - "dependencies": { - "@cosmjs/amino": "0.28.7", - "@cosmjs/crypto": "0.28.7", - "@cosmjs/encoding": "0.28.7", - "@cosmjs/math": "0.28.7", - "@cosmjs/utils": "0.28.7", - "cosmjs-types": "^0.4.0", - "long": "^4.0.0" - } - }, - "node_modules/@cosmjs/proto-signing/node_modules/cosmjs-types": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", - "integrity": "sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog==", - "dependencies": { - "long": "^4.0.0", - "protobufjs": "~6.11.2" - } - }, - "node_modules/@cosmjs/proto-signing/node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/@cosmjs/socket": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.9.tgz", - "integrity": "sha512-RdYAWoFf5TyhQKGPJPoCvYpnpPc2fSS5yrRjv7AnF7FB38N/y+PRZ0sKO94MkjP4679txLr1PVNnIZgykR0afQ==", - "dependencies": { - "@cosmjs/stream": "0.28.9", - "isomorphic-ws": "^4.0.1", - "ws": "^7", - "xstream": "^11.14.0" - } - }, - "node_modules/@cosmjs/socket/node_modules/@cosmjs/stream": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", - "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", - "dependencies": { - "xstream": "^11.14.0" - } - }, - "node_modules/@cosmjs/stargate": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.7.tgz", - "integrity": "sha512-3nY7czFUaY74gacbQAZuNji1qJWWQjv2C1cxYNqe7qAZAvCiz6A9adJVUmCJRL4peeG7tKiOny1J5IFbsgRu0g==", - "dependencies": { - "@confio/ics23": "^0.6.8", - "@cosmjs/amino": "0.28.7", - "@cosmjs/encoding": "0.28.7", - "@cosmjs/math": "0.28.7", - "@cosmjs/proto-signing": "0.28.7", - "@cosmjs/stream": "0.28.7", - "@cosmjs/tendermint-rpc": "0.28.7", - "@cosmjs/utils": "0.28.7", - "cosmjs-types": "^0.4.0", - "long": "^4.0.0", - "protobufjs": "~6.11.3", - "xstream": "^11.14.0" - } - }, - "node_modules/@cosmjs/stargate/node_modules/@cosmjs/json-rpc": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.7.tgz", - "integrity": "sha512-2qgRL/9ih/ZYU/8LmtDQopaCKJBKqsuoSXfb2XO3yv6EkE28yiA8YAwLW5IrXjl1tfSiolHaBWarEASS/8Q5xA==", - "dependencies": { - "@cosmjs/stream": "0.28.7", - "xstream": "^11.14.0" - } - }, - "node_modules/@cosmjs/stargate/node_modules/@cosmjs/socket": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.7.tgz", - "integrity": "sha512-+sCR5AzjjsKlA0K7m8YdxldMvgJa3Tqnx0AAyQXlNw2VXmW1zu9DkKZWW475XFhwO9UYfrdIsOe1vbhnUjQb5g==", - "dependencies": { - "@cosmjs/stream": "0.28.7", - "isomorphic-ws": "^4.0.1", - "ws": "^7", - "xstream": "^11.14.0" - } - }, - "node_modules/@cosmjs/stargate/node_modules/@cosmjs/tendermint-rpc": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.7.tgz", - "integrity": "sha512-xhIVJL3ol+fZxywP76Ik9pHqCvBdU/BGAw6p48mhla3L3xNcFN2Nf+EnJWcIZPqZl8bHm5QHzPk9VqVFVYCMCw==", - "dependencies": { - "@cosmjs/crypto": "0.28.7", - "@cosmjs/encoding": "0.28.7", - "@cosmjs/json-rpc": "0.28.7", - "@cosmjs/math": "0.28.7", - "@cosmjs/socket": "0.28.7", - "@cosmjs/stream": "0.28.7", - "@cosmjs/utils": "0.28.7", - "axios": "^0.21.2", - "readonly-date": "^1.0.0", - "xstream": "^11.14.0" - } - }, - "node_modules/@cosmjs/stargate/node_modules/cosmjs-types": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", - "integrity": "sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog==", - "dependencies": { - "long": "^4.0.0", - "protobufjs": "~6.11.2" - } - }, - "node_modules/@cosmjs/stargate/node_modules/cosmjs-types/node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/@cosmjs/stargate/node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/@cosmjs/stream": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.7.tgz", - "integrity": "sha512-zTMZadlpmxAMtKUHX/dnYs1STung392+P50WgSUAjllG7CZYl7SAY03AKgc+aoF1ohYQcgH9H7VSgabEgLPhmg==", - "dependencies": { - "xstream": "^11.14.0" - } - }, - "node_modules/@cosmjs/tendermint-rpc": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.9.tgz", - "integrity": "sha512-a8PCFWG32wyQE6R9XA3dZpbwzvAQCOpcF1m9AsShgVYXRKm+AihbFwu7q75jaxf2XaRnTnrjpCCnJKVFeMnFEQ==", - "dependencies": { - "@cosmjs/crypto": "0.28.9", - "@cosmjs/encoding": "0.28.9", - "@cosmjs/json-rpc": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/socket": "0.28.9", - "@cosmjs/stream": "0.28.9", - "@cosmjs/utils": "0.28.9", - "axios": "^0.21.2", - "readonly-date": "^1.0.0", - "xstream": "^11.14.0" - } - }, - "node_modules/@cosmjs/tendermint-rpc/node_modules/@cosmjs/crypto": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.9.tgz", - "integrity": "sha512-2U8cuH7ofRiYni3EPAUsPuqgHN0VM+HdHiY1Ftl2rqRRnZArZNJsbf4t21jVx7rlc+qP32Y1v7PvQyPALHvuyw==", - "dependencies": { - "@cosmjs/encoding": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/utils": "0.28.9", - "@noble/hashes": "^1", - "bn.js": "^5.2.0", - "elliptic": "^6.5.3", - "libsodium-wrappers": "^0.7.6" - } - }, - "node_modules/@cosmjs/tendermint-rpc/node_modules/@cosmjs/encoding": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.9.tgz", - "integrity": "sha512-AE+uL5LC2f9ZE8ehFPgb7yNMuGr4wx/cbH25gglRwl9utdND2lPVeYmbEF2MRJwB69hXaiPHblCDIz3KXZV9pQ==", - "dependencies": { - "base64-js": "^1.3.0", - "bech32": "^1.1.4", - "readonly-date": "^1.0.0" - } - }, - "node_modules/@cosmjs/tendermint-rpc/node_modules/@cosmjs/math": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.9.tgz", - "integrity": "sha512-CY4k3GMQqXL3M+dGUptgCL6mtkcfvhKhmsrzejSZB8yVOIdKA40Wu6buRMMh2+t3SkWpvjVQt8UiJF7C4C4NXQ==", - "dependencies": { - "bn.js": "^5.2.0" - } - }, - "node_modules/@cosmjs/tendermint-rpc/node_modules/@cosmjs/stream": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", - "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", - "dependencies": { - "xstream": "^11.14.0" - } - }, - "node_modules/@cosmjs/tendermint-rpc/node_modules/@cosmjs/utils": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.9.tgz", - "integrity": "sha512-5NJ2dSJlT0wtwdcTvprYNtucSk/+aQANEfobFGOBt6n5JT3VlCH5We3a29YC4Z176vy7RsfVf0j2xJabDmp2iw==" - }, - "node_modules/@cosmjs/utils": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.7.tgz", - "integrity": "sha512-0ya5mRaDY956n87dKjx6wfqgH/uEfrZyMswIhcEPqQPegwp4FDd6cwJtSdv/d5S7fgvoEs2UABvUFNzCT5C0hg==" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", - "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.3.2", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", - "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.0.tgz", - "integrity": "sha512-tscn3dlJFGay47kb4qVruQg/XWlmvU0xp3EJOjzzY+sBaI+YgwKcvAmTcyYU7xEiLLIY5HCdWRooAL8dqkFlDA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.0", - "jest-util": "^28.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/core": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.0.tgz", - "integrity": "sha512-/2PTt0ywhjZ4NwNO4bUqD9IVJfmFVhVKGlhvSpmEfUCuxYf/3NHcKmRFI+I71lYzbTT3wMuYpETDCTHo81gC/g==", - "dev": true, - "dependencies": { - "@jest/console": "^28.1.0", - "@jest/reporters": "^28.1.0", - "@jest/test-result": "^28.1.0", - "@jest/transform": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^28.0.2", - "jest-config": "^28.1.0", - "jest-haste-map": "^28.1.0", - "jest-message-util": "^28.1.0", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.0", - "jest-resolve-dependencies": "^28.1.0", - "jest-runner": "^28.1.0", - "jest-runtime": "^28.1.0", - "jest-snapshot": "^28.1.0", - "jest-util": "^28.1.0", - "jest-validate": "^28.1.0", - "jest-watcher": "^28.1.0", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.0.tgz", - "integrity": "sha512-S44WGSxkRngzHslhV6RoAExekfF7Qhwa6R5+IYFa81mpcj0YgdBnRSmvHe3SNwOt64yXaE5GG8Y2xM28ii5ssA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/node": "*", - "jest-mock": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.0.tgz", - "integrity": "sha512-be9ETznPLaHOmeJqzYNIXv1ADEzENuQonIoobzThOYPuK/6GhrWNIJDVTgBLCrz3Am73PyEU2urQClZp0hLTtA==", - "dev": true, - "dependencies": { - "expect": "^28.1.0", - "jest-snapshot": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.0.tgz", - "integrity": "sha512-5BrG48dpC0sB80wpeIX5FU6kolDJI4K0n5BM9a5V38MGx0pyRvUBSS0u2aNTdDzmOrCjhOg8pGs6a20ivYkdmw==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.0.tgz", - "integrity": "sha512-Xqsf/6VLeAAq78+GNPzI7FZQRf5cCHj1qgQxCjws9n8rKw8r1UYoeaALwBvyuzOkpU3c1I6emeMySPa96rxtIg==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.0", - "@sinonjs/fake-timers": "^9.1.1", - "@types/node": "*", - "jest-message-util": "^28.1.0", - "jest-mock": "^28.1.0", - "jest-util": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.0.tgz", - "integrity": "sha512-3m7sTg52OTQR6dPhsEQSxAvU+LOBbMivZBwOvKEZ+Rb+GyxVnXi9HKgOTYkx/S99T8yvh17U4tNNJPIEQmtwYw==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.0", - "@jest/expect": "^28.1.0", - "@jest/types": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.0.tgz", - "integrity": "sha512-qxbFfqap/5QlSpIizH9c/bFCDKsQlM4uAKSOvZrP+nIdrjqre3FmKzpTtYyhsaVcOSNK7TTt2kjm+4BJIjysFA==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.0", - "@jest/test-result": "^28.1.0", - "@jest/transform": "^28.1.0", - "@jest/types": "^28.1.0", - "@jridgewell/trace-mapping": "^0.3.7", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-util": "^28.1.0", - "jest-worker": "^28.1.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.0.2.tgz", - "integrity": "sha512-YVDJZjd4izeTDkij00vHHAymNXQ6WWsdChFRK86qck6Jpr3DCL5W3Is3vslviRlP+bLuMYRLbdp98amMvqudhA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.23.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.0.2.tgz", - "integrity": "sha512-Y9dxC8ZpN3kImkk0LkK5XCEneYMAXlZ8m5bflmSL5vrwyeUpJfentacCUg6fOb8NOpOO7hz2+l37MV77T6BFPw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.7", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.0.tgz", - "integrity": "sha512-sBBFIyoPzrZho3N+80P35A5oAkSKlGfsEFfXFWuPGBsW40UAjCkGakZhn4UQK4iQlW2vgCDMRDOob9FGKV8YoQ==", - "dev": true, - "dependencies": { - "@jest/console": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.0.tgz", - "integrity": "sha512-tZCEiVWlWNTs/2iK9yi6o3AlMfbbYgV4uuZInSVdzZ7ftpHZhCMuhvk2HLYhCZzLgPFQ9MnM1YaxMnh3TILFiQ==", - "dev": true, - "dependencies": { - "@jest/test-result": "^28.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.0.tgz", - "integrity": "sha512-omy2xe5WxlAfqmsTjTPxw+iXRTRnf+NtX0ToG+4S0tABeb4KsKmPUHq5UBuwunHg3tJRwgEQhEp0M/8oiatLEA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.0", - "@jridgewell/trace-mapping": "^0.3.7", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.0", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/types": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.0.tgz", - "integrity": "sha512-xmEggMPr317MIOjjDoZ4ejCSr9Lpbt/u34+dvc99t7DS8YirW5rwZEhzKPC2BMUFkUhI48qs6qLUSGw5FuL0GA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.0.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", - "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", - "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", - "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@noble/hashes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.0.0.tgz", - "integrity": "sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg==" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@osmonauts/helpers": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@osmonauts/helpers/-/helpers-0.3.5.tgz", - "integrity": "sha512-NW8S0aAyOd2GLl6STyDcV1JbIKy2yJryj19zC+P647LYD/1eV4GEzgBXQ3G691ia+dZNQEQFpcfu+SFqIL5Q7A==", - "dependencies": { - "@babel/runtime": "^7.18.3", - "long": "^5.2.0", - "protobufjs": "^6.11.3" - } - }, - "node_modules/@osmonauts/lcd": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@osmonauts/lcd/-/lcd-0.3.5.tgz", - "integrity": "sha512-VUIwDp90ZD0GQGQ3jbttAfZCSKUU1dJbKFlMYqc0GZGIiWa9Ka6ZYUKQCpiwYXqjksLGWoFvTIX3YfLkQwvTqA==", - "dependencies": { - "@babel/runtime": "^7.18.3", - "axios": "0.27.2" - } - }, - "node_modules/@osmonauts/lcd/node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "node_modules/@sinclair/typebox": { - "version": "0.23.5", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz", - "integrity": "sha512-AFBVi/iT4g20DHoujvMH1aEDn8fGJh4xsRGCP6d8RpLPMqsNPvW01Jcn0QysXTsg++/xj25NmJsGyH9xug/wKg==", - "dev": true - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true - }, - "node_modules/@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.17.1.tgz", - "integrity": "sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.3.tgz", - "integrity": "sha512-Tsbjk8Y2hkBaY/gJsataeb4q9Mubw9EOz7+4RjPkzD5KjTvHHs7cpws22InaoXxAVAhF5HfFbzJjo6oKWqSZLw==", - "dev": true, - "dependencies": { - "jest-matcher-utils": "^28.0.0", - "pretty-format": "^28.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, - "node_modules/@types/node": { - "version": "17.0.38", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.38.tgz", - "integrity": "sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==" - }, - "node_modules/@types/prettier": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.3.tgz", - "integrity": "sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.10.tgz", - "integrity": "sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.0.tgz", - "integrity": "sha512-DDrIA7GXtmHXr1VCcx9HivA39eprYBIFxbQEHI6NyraRDxCGpxAFiYQAT/1Y0vh1C+o2vfBiy4IuPoXxtTZCAQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.27.0", - "@typescript-eslint/type-utils": "5.27.0", - "@typescript-eslint/utils": "5.27.0", - "debug": "^4.3.4", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.27.0.tgz", - "integrity": "sha512-8oGjQF46c52l7fMiPPvX4It3u3V3JipssqDfHQ2hcR0AeR8Zge+OYyKUCm5b70X72N1qXt0qgHenwN6Gc2SXZA==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.27.0", - "@typescript-eslint/types": "5.27.0", - "@typescript-eslint/typescript-estree": "5.27.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.0.tgz", - "integrity": "sha512-VnykheBQ/sHd1Vt0LJ1JLrMH1GzHO+SzX6VTXuStISIsvRiurue/eRkTqSrG0CexHQgKG8shyJfR4o5VYioB9g==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.27.0", - "@typescript-eslint/visitor-keys": "5.27.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.27.0.tgz", - "integrity": "sha512-vpTvRRchaf628Hb/Xzfek+85o//zEUotr1SmexKvTfs7czXfYjXVT/a5yDbpzLBX1rhbqxjDdr1Gyo0x1Fc64g==", - "dev": true, - "dependencies": { - "@typescript-eslint/utils": "5.27.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.0.tgz", - "integrity": "sha512-lY6C7oGm9a/GWhmUDOs3xAVRz4ty/XKlQ2fOLr8GAIryGn0+UBOoJDWyHer3UgrHkenorwvBnphhP+zPmzmw0A==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.0.tgz", - "integrity": "sha512-QywPMFvgZ+MHSLRofLI7BDL+UczFFHyj0vF5ibeChDAJgdTV8k4xgEwF0geFhVlPc1p8r70eYewzpo6ps+9LJQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.27.0", - "@typescript-eslint/visitor-keys": "5.27.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.0.tgz", - "integrity": "sha512-nZvCrkIJppym7cIbP3pOwIkAefXOmfGPnCM0LQfzNaKxJHI6VjI8NC662uoiPlaf5f6ymkTy9C3NQXev2mdXmA==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.27.0", - "@typescript-eslint/types": "5.27.0", - "@typescript-eslint/typescript-estree": "5.27.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.0.tgz", - "integrity": "sha512-46cYrteA2MrIAjv9ai44OQDUoCZyHeGIc4lsjCUX2WT6r4C+kidz1bNiR4017wHOPUythYeH+Sc7/cFP97KEAA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.27.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, - "node_modules/babel-jest": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.0.tgz", - "integrity": "sha512-zNKk0yhDZ6QUwfxh9k07GII6siNGMJWVUU49gmFj5gfdqDKLqa2RArXOF2CODp4Dr7dLxN2cvAV+667dGJ4b4w==", - "dev": true, - "dependencies": { - "@jest/transform": "^28.1.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^28.0.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.0.2.tgz", - "integrity": "sha512-Kizhn/ZL+68ZQHxSnHyuvJv8IchXD62KQxV77TBDV/xoBFBOfgRAk97GNs6hXdTTCiVES9nB2I6+7MXXrk5llQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.3.1", - "semver": "^6.1.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", - "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.1", - "core-js-compat": "^3.21.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.0.2.tgz", - "integrity": "sha512-sYzXIdgIXXroJTFeB3S6sNDWtlJ2dllCdTEsnZ65ACrMojj3hVNFRmnJ1HZtomGi+Be7aqpY/HJ92fr8OhKVkQ==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^28.0.2", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" - }, - "node_modules/browserslist": { - "version": "4.20.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", - "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001332", - "electron-to-chromium": "^1.4.118", - "escalade": "^3.1.1", - "node-releases": "^2.0.3", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001344", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001344.tgz", - "integrity": "sha512-0ZFjnlCaXNOAYcV7i+TtdKBp0L/3XEU2MF/x6Du1lrh+SRX4IfzIVL4HNJg5pB2PmFb8rszIGyOvsZnqqRoc2g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.1.tgz", - "integrity": "sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg==", - "dev": true - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/core-js-compat": { - "version": "3.22.7", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.22.7.tgz", - "integrity": "sha512-uI9DAQKKiiE/mclIC5g4AjRpio27g+VMRhe6rQoz+q4Wm4L6A/fJhiLtBw+sfOpDG9wZ3O0pxIw7GbfOlBgjOA==", - "dev": true, - "dependencies": { - "browserslist": "^4.20.3", - "semver": "7.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/cosmjs-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.5.0.tgz", - "integrity": "sha512-Qy2yxDp5HasBUnBV5OL9EOuTJw94LAbCWfiRb5QcW1sqh93KSSY5PpNMB3rTsxwuPcf/k3CNRY02DeQMjrsJuA==", - "dependencies": { - "long": "^4.0.0", - "protobufjs": "~6.11.2" - } - }, - "node_modules/cosmjs-types/node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.143", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.143.tgz", - "integrity": "sha512-2hIgvu0+pDfXIqmVmV5X6iwMjQ2KxDsWKwM+oI1fABEOy/Dqmll0QJRmIQ3rm+XaoUa/qKrmy5h7LSTFQ6Ldzg==", - "dev": true - }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.16.0.tgz", - "integrity": "sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.2", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-scope/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", - "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", - "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", - "dev": true, - "dependencies": { - "acorn": "^8.7.1", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.0.tgz", - "integrity": "sha512-qFXKl8Pmxk8TBGfaFKRtcQjfXEnKAs+dmlxdwvukJZorwrAabT7M3h8oLOG01I2utEhkmUTi17CHaPBovZsKdw==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^28.1.0", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.0", - "jest-message-util": "^28.1.0", - "jest-util": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", - "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.0.tgz", - "integrity": "sha512-TZR+tHxopPhzw3c3560IJXZWLNHgpcz1Zh0w5A65vynLGNcg/5pZ+VildAd7+XGOu6jd58XMY/HNn0IkZIXVXg==", - "dev": true, - "dependencies": { - "@jest/core": "^28.1.0", - "import-local": "^3.0.2", - "jest-cli": "^28.1.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.0.2.tgz", - "integrity": "sha512-QX9u+5I2s54ZnGoMEjiM2WeBvJR2J7w/8ZUmH2um/WLAuGAYFQcsVXY9+1YL6k0H/AGUdH8pXUAv6erDqEsvIA==", - "dev": true, - "dependencies": { - "execa": "^5.0.0", - "throat": "^6.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.0.tgz", - "integrity": "sha512-rNYfqfLC0L0zQKRKsg4n4J+W1A2fbyGH7Ss/kDIocp9KXD9iaL111glsLu7+Z7FHuZxwzInMDXq+N1ZIBkI/TQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.0", - "@jest/expect": "^28.1.0", - "@jest/test-result": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^28.1.0", - "jest-matcher-utils": "^28.1.0", - "jest-message-util": "^28.1.0", - "jest-runtime": "^28.1.0", - "jest-snapshot": "^28.1.0", - "jest-util": "^28.1.0", - "pretty-format": "^28.1.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-cli": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.0.tgz", - "integrity": "sha512-fDJRt6WPRriHrBsvvgb93OxgajHHsJbk4jZxiPqmZbMDRcHskfJBBfTyjFko0jjfprP544hOktdSi9HVgl4VUQ==", - "dev": true, - "dependencies": { - "@jest/core": "^28.1.0", - "@jest/test-result": "^28.1.0", - "@jest/types": "^28.1.0", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^28.1.0", - "jest-util": "^28.1.0", - "jest-validate": "^28.1.0", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.0.tgz", - "integrity": "sha512-aOV80E9LeWrmflp7hfZNn/zGA4QKv/xsn2w8QCBP0t0+YqObuCWTSgNbHJ0j9YsTuCO08ZR/wsvlxqqHX20iUA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^28.1.0", - "@jest/types": "^28.1.0", - "babel-jest": "^28.1.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^28.1.0", - "jest-environment-node": "^28.1.0", - "jest-get-type": "^28.0.2", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.0", - "jest-runner": "^28.1.0", - "jest-util": "^28.1.0", - "jest-validate": "^28.1.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^28.1.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.1.tgz", - "integrity": "sha512-/MUUxeR2fHbqHoMMiffe/Afm+U8U4olFRJ0hiVG2lZatPJcnGxx292ustVu7bULhjV65IYMxRdploAKLbcrsyg==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.0.2.tgz", - "integrity": "sha512-FH10WWw5NxLoeSdQlJwu+MTiv60aXV/t8KEwIRGEv74WARE1cXIqh1vGdy2CraHuWOOrnzTWj/azQKqW4fO7xg==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-each": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.0.tgz", - "integrity": "sha512-a/XX02xF5NTspceMpHujmOexvJ4GftpYXqr6HhhmKmExtMXsyIN/fvanQlt/BcgFoRKN4OCXxLQKth9/n6OPFg==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.0", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "jest-util": "^28.1.0", - "pretty-format": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.0.tgz", - "integrity": "sha512-gBLZNiyrPw9CSMlTXF1yJhaBgWDPVvH0Pq6bOEwGMXaYNzhzhw2kA/OijNF8egbCgDS0/veRv97249x2CX+udQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.0", - "@jest/fake-timers": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/node": "*", - "jest-mock": "^28.1.0", - "jest-util": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.0.tgz", - "integrity": "sha512-xyZ9sXV8PtKi6NCrJlmq53PyNVHzxmcfXNVvIRHpHmh1j/HChC4pwKgyjj7Z9us19JMw8PpQTJsFWOsIfT93Dw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.0", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.0", - "jest-worker": "^28.1.0", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.0.tgz", - "integrity": "sha512-uIJDQbxwEL2AMMs2xjhZl2hw8s77c3wrPaQ9v6tXJLGaaQ+4QrNJH5vuw7hA7w/uGT/iJ42a83opAqxGHeyRIA==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.1.tgz", - "integrity": "sha512-NPJPRWrbmR2nAJ+1nmnfcKKzSwgfaciCCrYZzVnNoxVoyusYWIjkBMNvu0RHJe7dNj4hH3uZOPZsQA+xAYWqsw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.0.tgz", - "integrity": "sha512-RpA8mpaJ/B2HphDMiDlrAZdDytkmwFqgjDZovM21F35lHGeUeCvYmm6W+sbQ0ydaLpg5bFAUuWG1cjqOl8vqrw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.0", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-mock": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.0.tgz", - "integrity": "sha512-H7BrhggNn77WhdL7O1apG0Q/iwl0Bdd5E1ydhCJzL3oBLh/UYxAwR3EJLsBZ9XA3ZU4PA3UNw4tQjduBTCTmLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.0", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.0.tgz", - "integrity": "sha512-vvfN7+tPNnnhDvISuzD1P+CRVP8cK0FHXRwPAcdDaQv4zgvwvag2n55/h5VjYcM5UJG7L4TwE5tZlzcI0X2Lhw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^28.1.0", - "jest-validate": "^28.1.0", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.0.tgz", - "integrity": "sha512-Ue1VYoSZquPwEvng7Uefw8RmZR+me/1kr30H2jMINjGeHgeO/JgrR6wxj2ofkJ7KSAA11W3cOrhNCbj5Dqqd9g==", - "dev": true, - "dependencies": { - "jest-regex-util": "^28.0.2", - "jest-snapshot": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.0.tgz", - "integrity": "sha512-FBpmuh1HB2dsLklAlRdOxNTTHKFR6G1Qmd80pVDvwbZXTriqjWqjei5DKFC1UlM732KjYcE6yuCdiF0WUCOS2w==", - "dev": true, - "dependencies": { - "@jest/console": "^28.1.0", - "@jest/environment": "^28.1.0", - "@jest/test-result": "^28.1.0", - "@jest/transform": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "graceful-fs": "^4.2.9", - "jest-docblock": "^28.0.2", - "jest-environment-node": "^28.1.0", - "jest-haste-map": "^28.1.0", - "jest-leak-detector": "^28.1.0", - "jest-message-util": "^28.1.0", - "jest-resolve": "^28.1.0", - "jest-runtime": "^28.1.0", - "jest-util": "^28.1.0", - "jest-watcher": "^28.1.0", - "jest-worker": "^28.1.0", - "source-map-support": "0.5.13", - "throat": "^6.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.0.tgz", - "integrity": "sha512-wNYDiwhdH/TV3agaIyVF0lsJ33MhyujOe+lNTUiolqKt8pchy1Hq4+tDMGbtD5P/oNLA3zYrpx73T9dMTOCAcg==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.0", - "@jest/fake-timers": "^28.1.0", - "@jest/globals": "^28.1.0", - "@jest/source-map": "^28.0.2", - "@jest/test-result": "^28.1.0", - "@jest/transform": "^28.1.0", - "@jest/types": "^28.1.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.0", - "jest-message-util": "^28.1.0", - "jest-mock": "^28.1.0", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.0", - "jest-snapshot": "^28.1.0", - "jest-util": "^28.1.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.0.tgz", - "integrity": "sha512-ex49M2ZrZsUyQLpLGxQtDbahvgBjlLPgklkqGM0hq/F7W/f8DyqZxVHjdy19QKBm4O93eDp+H5S23EiTbbUmHw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.0", - "@jest/transform": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.0", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.0", - "jest-matcher-utils": "^28.1.0", - "jest-message-util": "^28.1.0", - "jest-util": "^28.1.0", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.0.tgz", - "integrity": "sha512-qYdCKD77k4Hwkose2YBEqQk7PzUf/NSE+rutzceduFveQREeH6b+89Dc9+wjX9dAwHcgdx4yedGA3FQlU/qCTA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-validate": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.0.tgz", - "integrity": "sha512-Lly7CJYih3vQBfjLeANGgBSBJ7pEa18cxpQfQEq2go2xyEzehnHfQTjoUia8xUv4x4J80XKFIDwJJThXtRFQXQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.0", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "leven": "^3.1.0", - "pretty-format": "^28.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.0.tgz", - "integrity": "sha512-tNHMtfLE8Njcr2IRS+5rXYA4BhU90gAOwI9frTGOqd+jX0P/Au/JfRSNqsf5nUTcWdbVYuLxS1KjnzILSoR5hA==", - "dev": true, - "dependencies": { - "@jest/test-result": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-worker": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.0.tgz", - "integrity": "sha512-ZHwM6mNwaWBR52Snff8ZvsCTqQsvhCxP/bT1I6T6DAnb6ygkshsyLQIMxFwHpYxht0HOoqt23JlC01viI7T03A==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/libsodium": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz", - "integrity": "sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ==" - }, - "node_modules/libsodium-wrappers": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz", - "integrity": "sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg==", - "dependencies": { - "libsodium": "^0.7.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/long": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", - "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", - "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/osmojs": { - "version": "0.4.53", - "resolved": "https://registry.npmjs.org/osmojs/-/osmojs-0.4.53.tgz", - "integrity": "sha512-5ml35Nx6gpVQLho+XrbzZptA1025S5XloRHLluvlNtIfVwILYp1E509WakGnjE9r5nxj/3eG9ThM5Yf8MfmWmg==", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@cosmjs/amino": "0.28.7", - "@cosmjs/proto-signing": "0.28.7", - "@cosmjs/stargate": "0.28.7", - "@cosmjs/tendermint-rpc": "^0.28.7", - "@osmonauts/helpers": "^0.3.5", - "@osmonauts/lcd": "^0.3.5", - "long": "^5.2.0", - "protobufjs": "^6.11.3" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pako": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", - "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-format": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.1.tgz", - "integrity": "sha512-wwJbVTGFHeucr5Jw2bQ9P+VYHyLdAqedFLEkdQUVaBF/eiidDwH5OpilINq4mEfhbCjLnirt6HTTDhv1HaTIQw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/protobufjs/node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/readonly-date": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz", - "integrity": "sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==" - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" - }, - "node_modules/regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/regexpu-core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", - "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.0.1", - "regjsgen": "^0.6.0", - "regjsparser": "^0.8.2", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", - "dev": true - }, - "node_modules/regjsparser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", - "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/symbol-observable": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz", - "integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-node": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.0.tgz", - "integrity": "sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.0.tgz", - "integrity": "sha512-HcvgY/xaRm7isYmyx+lFKA4uQmfUbN0J4M0nNItvzTvH/iQ9kW5j/t4YSR+Ge323/lrgDAWJoF46tzGQHwBHFw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.7", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.1.tgz", - "integrity": "sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" - } - }, - "node_modules/ws": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz", - "integrity": "sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xstream": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/xstream/-/xstream-11.14.0.tgz", - "integrity": "sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw==", - "dependencies": { - "globalthis": "^1.0.1", - "symbol-observable": "^2.0.3" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/compat-data": { - "version": "7.17.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", - "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", - "dev": true - }, - "@babel/core": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.2.tgz", - "integrity": "sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.18.2", - "@babel/helper-compilation-targets": "^7.18.2", - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helpers": "^7.18.2", - "@babel/parser": "^7.18.0", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.2", - "@babel/types": "^7.18.2", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - } - }, - "@babel/generator": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", - "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", - "dev": true, - "requires": { - "@babel/types": "^7.18.2", - "@jridgewell/gen-mapping": "^0.3.0", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", - "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", - "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz", - "integrity": "sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.17.10", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.20.2", - "semver": "^6.3.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.0.tgz", - "integrity": "sha512-Kh8zTGR9de3J63e5nS0rQUdRs/kbtwoeQQ0sriS0lItjC96u8XXZN6lKpuyWd2coKSU13py/y+LTmThLuVX0Pg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-member-expression-to-functions": "^7.17.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.12.tgz", - "integrity": "sha512-b2aZrV4zvutr9AIa6/gA3wsZKRwTKYoDxYiFKcESS3Ug2GTXzwBEvMuuFLhCQpEnRXs1zng4ISAXSUxxKBIcxw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "regexpu-core": "^5.0.1" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz", - "integrity": "sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==", - "dev": true - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", - "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-function-name": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", - "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", - "dev": true, - "requires": { - "@babel/template": "^7.16.7", - "@babel/types": "^7.17.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", - "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", - "dev": true, - "requires": { - "@babel/types": "^7.17.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-module-transforms": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz", - "integrity": "sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.17.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.0", - "@babel/types": "^7.18.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz", - "integrity": "sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==", - "dev": true - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", - "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-wrap-function": "^7.16.8", - "@babel/types": "^7.16.8" - } - }, - "@babel/helper-replace-supers": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.2.tgz", - "integrity": "sha512-XzAIyxx+vFnrOxiQrToSUOzUOn0e1J2Li40ntddek1Y69AXUTXoDJ40/D5RdjFu7s7qHiaeoTiempZcbuVXh2Q==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.2", - "@babel/helper-member-expression-to-functions": "^7.17.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/traverse": "^7.18.2", - "@babel/types": "^7.18.2" - } - }, - "@babel/helper-simple-access": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz", - "integrity": "sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==", - "dev": true, - "requires": { - "@babel/types": "^7.18.2" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8" - } - }, - "@babel/helpers": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.2.tgz", - "integrity": "sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg==", - "dev": true, - "requires": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.2", - "@babel/types": "^7.18.2" - } - }, - "@babel/highlight": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", - "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz", - "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==", - "dev": true - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.17.12.tgz", - "integrity": "sha512-xCJQXl4EeQ3J9C4yOmpTrtVGmzpm2iSzyxbkZHw7UCnZBftHpF/hpII80uWVyVrc40ytIClHjgWGTG1g/yB+aw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.17.12.tgz", - "integrity": "sha512-/vt0hpIw0x4b6BLKUkwlvEoiGZYYLNZ96CzyHYPbtG2jZGz6LBe7/V+drYrc/d+ovrF9NBi0pmtvmNb/FsWtRQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.17.12" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.17.12.tgz", - "integrity": "sha512-RWVvqD1ooLKP6IqWTA5GyFVX2isGEgC5iFxKzfYOIy/QEFdxYyCybBDtIGjipHpb9bDWHzcqGqFakf+mVmBTdQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-remap-async-to-generator": "^7.16.8", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.17.12.tgz", - "integrity": "sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.17.12", - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.0.tgz", - "integrity": "sha512-t+8LsRMMDE74c6sV7KShIw13sqbqd58tlqNrsWoWBTIMw7SVQ0cZ905wLNS/FBCy/3PyooRHLFFlfrUNyyz5lA==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.0", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", - "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.17.12.tgz", - "integrity": "sha512-j7Ye5EWdwoXOpRmo5QmRyHPsDIe6+u70ZYZrd7uz+ebPYFKfRcLcNu3Ro0vOlJ5zuv8rU7xa+GttNiRzX56snQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.17.12.tgz", - "integrity": "sha512-rKJ+rKBoXwLnIn7n6o6fulViHMrOThz99ybH+hKHcOZbnN14VuMnH9fo2eHE69C8pO4uX1Q7t2HYYIDmv8VYkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.17.12.tgz", - "integrity": "sha512-EqFo2s1Z5yy+JeJu7SFfbIUtToJTVlC61/C7WLKDntSw4Sz6JNAIfL7zQ74VvirxpjB5kz/kIx0gCcb+5OEo2Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.17.12.tgz", - "integrity": "sha512-ws/g3FSGVzv+VH86+QvgtuJL/kR67xaEIF2x0iPqdDfYW6ra6JF3lKVBkWynRLcNtIC1oCTfDRVxmm2mKzy+ag==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", - "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.0.tgz", - "integrity": "sha512-nbTv371eTrFabDfHLElkn9oyf9VG+VKK6WMzhY2o4eHKaG19BToD9947zzGMO6I/Irstx9d8CwX6njPNIAR/yw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.17.10", - "@babel/helper-compilation-targets": "^7.17.10", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.17.12" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", - "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.17.12.tgz", - "integrity": "sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.17.12.tgz", - "integrity": "sha512-SllXoxo19HmxhDWm3luPz+cPhtoTSKLJE9PXshsfrOzBqs60QP0r8OaJItrPhAj0d7mZMnNF0Y1UUggCDgMz1A==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.17.12", - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.17.12.tgz", - "integrity": "sha512-/6BtVi57CJfrtDNKfK5b66ydK2J5pXUKBKSPD2G1whamMuEnZWgoOIfO8Vf9F/DoD4izBLD/Au4NMQfruzzykg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-create-class-features-plugin": "^7.17.12", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.17.12.tgz", - "integrity": "sha512-Wb9qLjXf3ZazqXA7IvI7ozqRIXIGPtSo+L5coFmEkhTQK18ao4UDDD0zdTGAarmbLj2urpRwrc6893cu5Bfh0A==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.17.12", - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.17.12.tgz", - "integrity": "sha512-n/loy2zkq9ZEM8tEOwON9wTQSTNDTDEz6NujPtJGLU7qObzT1N4c4YZZf8E6ATB2AjNQg/Ib2AIpO03EZaCehw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz", - "integrity": "sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.17.12.tgz", - "integrity": "sha512-PHln3CNi/49V+mza4xMwrg+WGYevSF1oaiXaC2EQfdp4HWlSjRsrDXWJiQBKpP7749u6vQ9mcry2uuFOv5CXvA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.17.12.tgz", - "integrity": "sha512-J8dbrWIOO3orDzir57NRsjg4uxucvhby0L/KZuGsWDj0g7twWK3g7JhJhOrXtuXiw8MeiSdJ3E0OW9H8LYEzLQ==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-remap-async-to-generator": "^7.16.8" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", - "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.4.tgz", - "integrity": "sha512-+Hq10ye+jlvLEogSOtq4mKvtk7qwcUQ1f0Mrueai866C82f844Yom2cttfJdMdqRLTxWpsbfbkIkOIfovyUQXw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.4.tgz", - "integrity": "sha512-e42NSG2mlKWgxKUAD9EJJSkZxR67+wZqzNxLSpc51T8tRU5SLFHsPmgYR5yr7sdgX4u+iHA1C5VafJ6AyImV3A==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.18.2", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-replace-supers": "^7.18.2", - "@babel/helper-split-export-declaration": "^7.16.7", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.17.12.tgz", - "integrity": "sha512-a7XINeplB5cQUWMg1E/GI1tFz3LfK021IjV1rj1ypE+R7jHm+pIHmHl25VNkZxtx9uuYp7ThGk8fur1HHG7PgQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.0.tgz", - "integrity": "sha512-Mo69klS79z6KEfrLg/1WkmVnB8javh75HX4pi2btjvlIoasuxilEyjtsQW6XPrubNd7AQy0MMaNIaQE4e7+PQw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", - "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.17.12.tgz", - "integrity": "sha512-EA5eYFUG6xeerdabina/xIoB95jJ17mAkR8ivx6ZSu9frKShBjpOGZPn511MTDTkiCO+zXnzNczvUM69YSf3Zw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", - "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.1.tgz", - "integrity": "sha512-+TTB5XwvJ5hZbO8xvl2H4XaMDOAK57zF4miuC9qQJgysPNEAZZ9Z69rdF5LJkozGdZrjBIUAIyKUWRMmebI7vg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", - "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.17.12.tgz", - "integrity": "sha512-8iRkvaTjJciWycPIZ9k9duu663FT7VrBdNqNgxnVXEFwOIp55JWcZd23VBRySYbnS3PwQ3rGiabJBBBGj5APmQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", - "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.0.tgz", - "integrity": "sha512-h8FjOlYmdZwl7Xm2Ug4iX2j7Qy63NANI+NQVWQzv6r25fqgg7k2dZl03p95kvqNclglHs4FZ+isv4p1uXMA+QA==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helper-plugin-utils": "^7.17.12", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.2.tgz", - "integrity": "sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-simple-access": "^7.18.2", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.4.tgz", - "integrity": "sha512-lH2UaQaHVOAeYrUUuZ8i38o76J/FnO8vu21OE+tD1MyP9lxdZoSfz+pDbWkq46GogUrdrMz3tiz/FYGB+bVThg==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-validator-identifier": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.0.tgz", - "integrity": "sha512-d/zZ8I3BWli1tmROLxXLc9A6YXvGK8egMxHp+E/rRwMh1Kip0AP77VwZae3snEJ33iiWwvNv2+UIIhfalqhzZA==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.12.tgz", - "integrity": "sha512-vWoWFM5CKaTeHrdUJ/3SIOTRV+MBVGybOC9mhJkaprGNt5demMymDW24yC74avb915/mIRe3TgNb/d8idvnCRA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.17.12", - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.17.12.tgz", - "integrity": "sha512-CaOtzk2fDYisbjAD4Sd1MTKGVIpRtx9bWLyj24Y/k6p4s4gQ3CqDGJauFJxt8M/LEx003d0i3klVqnN73qvK3w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", - "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.17.12.tgz", - "integrity": "sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", - "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.0.tgz", - "integrity": "sha512-C8YdRw9uzx25HSIzwA7EM7YP0FhCe5wNvJbZzjVNHHPGVcDJ3Aie+qGYYdS1oVQgn+B3eAIJbWFLrJ4Jipv7nw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12", - "regenerator-transform": "^0.15.0" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.17.12.tgz", - "integrity": "sha512-1KYqwbJV3Co03NIi14uEHW8P50Md6KqFgt0FfpHdK6oyAHQVTosgPuPSiWud1HX0oYJ1hGRRlk0fP87jFpqXZA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", - "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.17.12.tgz", - "integrity": "sha512-9pgmuQAtFi3lpNUstvG9nGfk9DkrdmWNp9KeKPFmuZCpEnxRzYlS8JgwPjYj+1AWDOSvoGN0H30p1cBOmT/Svg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", - "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.2.tgz", - "integrity": "sha512-/cmuBVw9sZBGZVOMkpAEaVLwm4JmK2GZ1dFKOGGpMzEHWFmyZZ59lUU0PdRr8YNYeQdNzTDwuxP2X2gzydTc9g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.17.12.tgz", - "integrity": "sha512-Q8y+Jp7ZdtSPXCThB6zjQ74N3lj0f6TDh1Hnf5B+sYlzQ8i5Pjp8gW0My79iekSpT4WnI06blqP6DT0OmaXXmw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.4.tgz", - "integrity": "sha512-l4vHuSLUajptpHNEOUDEGsnpl9pfRLsN1XUoDQDD/YBuXTM+v37SHGS+c6n4jdcZy96QtuUuSvZYMLSSsjH8Mw==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.0", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/plugin-syntax-typescript": "^7.17.12" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", - "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", - "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/preset-env": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.2.tgz", - "integrity": "sha512-PfpdxotV6afmXMU47S08F9ZKIm2bJIQ0YbAAtDfIENX7G1NUAXigLREh69CWDjtgUy7dYn7bsMzkgdtAlmS68Q==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.17.10", - "@babel/helper-compilation-targets": "^7.18.2", - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.17.12", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.17.12", - "@babel/plugin-proposal-async-generator-functions": "^7.17.12", - "@babel/plugin-proposal-class-properties": "^7.17.12", - "@babel/plugin-proposal-class-static-block": "^7.18.0", - "@babel/plugin-proposal-dynamic-import": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.17.12", - "@babel/plugin-proposal-json-strings": "^7.17.12", - "@babel/plugin-proposal-logical-assignment-operators": "^7.17.12", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.17.12", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.18.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.17.12", - "@babel/plugin-proposal-private-methods": "^7.17.12", - "@babel/plugin-proposal-private-property-in-object": "^7.17.12", - "@babel/plugin-proposal-unicode-property-regex": "^7.17.12", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.17.12", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.17.12", - "@babel/plugin-transform-async-to-generator": "^7.17.12", - "@babel/plugin-transform-block-scoped-functions": "^7.16.7", - "@babel/plugin-transform-block-scoping": "^7.17.12", - "@babel/plugin-transform-classes": "^7.17.12", - "@babel/plugin-transform-computed-properties": "^7.17.12", - "@babel/plugin-transform-destructuring": "^7.18.0", - "@babel/plugin-transform-dotall-regex": "^7.16.7", - "@babel/plugin-transform-duplicate-keys": "^7.17.12", - "@babel/plugin-transform-exponentiation-operator": "^7.16.7", - "@babel/plugin-transform-for-of": "^7.18.1", - "@babel/plugin-transform-function-name": "^7.16.7", - "@babel/plugin-transform-literals": "^7.17.12", - "@babel/plugin-transform-member-expression-literals": "^7.16.7", - "@babel/plugin-transform-modules-amd": "^7.18.0", - "@babel/plugin-transform-modules-commonjs": "^7.18.2", - "@babel/plugin-transform-modules-systemjs": "^7.18.0", - "@babel/plugin-transform-modules-umd": "^7.18.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.17.12", - "@babel/plugin-transform-new-target": "^7.17.12", - "@babel/plugin-transform-object-super": "^7.16.7", - "@babel/plugin-transform-parameters": "^7.17.12", - "@babel/plugin-transform-property-literals": "^7.16.7", - "@babel/plugin-transform-regenerator": "^7.18.0", - "@babel/plugin-transform-reserved-words": "^7.17.12", - "@babel/plugin-transform-shorthand-properties": "^7.16.7", - "@babel/plugin-transform-spread": "^7.17.12", - "@babel/plugin-transform-sticky-regex": "^7.16.7", - "@babel/plugin-transform-template-literals": "^7.18.2", - "@babel/plugin-transform-typeof-symbol": "^7.17.12", - "@babel/plugin-transform-unicode-escapes": "^7.16.7", - "@babel/plugin-transform-unicode-regex": "^7.16.7", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.18.2", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.5.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", - "core-js-compat": "^3.22.1", - "semver": "^6.3.0" - } - }, - "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-typescript": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.17.12.tgz", - "integrity": "sha512-S1ViF8W2QwAKUGJXxP9NAfNaqGDdEBJKpYkxHf5Yy2C4NPPzXGeR3Lhk7G8xJaaLcFTRfNjVbtbVtm8Gb0mqvg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.17.12", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-transform-typescript": "^7.17.12" - } - }, - "@babel/runtime": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz", - "integrity": "sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/traverse": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.2.tgz", - "integrity": "sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.18.2", - "@babel/helper-environment-visitor": "^7.18.2", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.18.0", - "@babel/types": "^7.18.2", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", - "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@confio/ics23": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@confio/ics23/-/ics23-0.6.8.tgz", - "integrity": "sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w==", - "requires": { - "@noble/hashes": "^1.0.0", - "protobufjs": "^6.8.8" - } - }, - "@cosmjs/amino": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.7.tgz", - "integrity": "sha512-NTCUS3+u9bxwW8moC0N1RS4Gk/fs3JPHTKcp7ksH39V4+7uOKM2rGsyFAekiHNxYngnQ+1hU5x2tddS25soK6Q==", - "requires": { - "@cosmjs/crypto": "0.28.7", - "@cosmjs/encoding": "0.28.7", - "@cosmjs/math": "0.28.7", - "@cosmjs/utils": "0.28.7" - } - }, - "@cosmjs/cosmwasm-stargate": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.28.9.tgz", - "integrity": "sha512-Icgmiq/hM72mWaCR3FBKb6VZSdeETfTSMZ8E26wBXsvmU24pFqIIqRmJ9y7jSyqRKnGY/0skYNitCob7ylqPSg==", - "requires": { - "@cosmjs/amino": "0.28.9", - "@cosmjs/crypto": "0.28.9", - "@cosmjs/encoding": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/proto-signing": "0.28.9", - "@cosmjs/stargate": "0.28.9", - "@cosmjs/tendermint-rpc": "0.28.9", - "@cosmjs/utils": "0.28.9", - "cosmjs-types": "^0.4.0", - "long": "^4.0.0", - "pako": "^2.0.2" - }, - "dependencies": { - "@cosmjs/amino": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.9.tgz", - "integrity": "sha512-8yaWIlU0W5nF7xyR1v81Wr0+Eo5xJ1bDCkTAsb1JV3xtXvfsl2jF1l4aV64vi+WWEghE+9embhZm7zxBljdunQ==", - "requires": { - "@cosmjs/crypto": "0.28.9", - "@cosmjs/encoding": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/utils": "0.28.9" - } - }, - "@cosmjs/crypto": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.9.tgz", - "integrity": "sha512-2U8cuH7ofRiYni3EPAUsPuqgHN0VM+HdHiY1Ftl2rqRRnZArZNJsbf4t21jVx7rlc+qP32Y1v7PvQyPALHvuyw==", - "requires": { - "@cosmjs/encoding": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/utils": "0.28.9", - "@noble/hashes": "^1", - "bn.js": "^5.2.0", - "elliptic": "^6.5.3", - "libsodium-wrappers": "^0.7.6" - } - }, - "@cosmjs/encoding": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.9.tgz", - "integrity": "sha512-AE+uL5LC2f9ZE8ehFPgb7yNMuGr4wx/cbH25gglRwl9utdND2lPVeYmbEF2MRJwB69hXaiPHblCDIz3KXZV9pQ==", - "requires": { - "base64-js": "^1.3.0", - "bech32": "^1.1.4", - "readonly-date": "^1.0.0" - } - }, - "@cosmjs/math": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.9.tgz", - "integrity": "sha512-CY4k3GMQqXL3M+dGUptgCL6mtkcfvhKhmsrzejSZB8yVOIdKA40Wu6buRMMh2+t3SkWpvjVQt8UiJF7C4C4NXQ==", - "requires": { - "bn.js": "^5.2.0" - } - }, - "@cosmjs/proto-signing": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.9.tgz", - "integrity": "sha512-8MnCvIa2s9myhXEMwsjKOh9/zGp1850QiW9XUy6YtNaQ9wgc58/WMq7HtfkGPqIZnhOeR7KXIyQ0/KHbmjhtnA==", - "requires": { - "@cosmjs/amino": "0.28.9", - "@cosmjs/crypto": "0.28.9", - "@cosmjs/encoding": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/utils": "0.28.9", - "cosmjs-types": "^0.4.0", - "long": "^4.0.0" - } - }, - "@cosmjs/stargate": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.9.tgz", - "integrity": "sha512-1Jh/+qsyzM+7+ek/peQa3xumgU99SvAI5ecDZNS6+qOaWgGdGSW8n40sEfv6viRVLDppfoYo6/9QfR5h3PoKgQ==", - "requires": { - "@confio/ics23": "^0.6.8", - "@cosmjs/amino": "0.28.9", - "@cosmjs/encoding": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/proto-signing": "0.28.9", - "@cosmjs/stream": "0.28.9", - "@cosmjs/tendermint-rpc": "0.28.9", - "@cosmjs/utils": "0.28.9", - "cosmjs-types": "^0.4.0", - "long": "^4.0.0", - "protobufjs": "~6.11.3", - "xstream": "^11.14.0" - } - }, - "@cosmjs/stream": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", - "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", - "requires": { - "xstream": "^11.14.0" - } - }, - "@cosmjs/utils": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.9.tgz", - "integrity": "sha512-5NJ2dSJlT0wtwdcTvprYNtucSk/+aQANEfobFGOBt6n5JT3VlCH5We3a29YC4Z176vy7RsfVf0j2xJabDmp2iw==" - }, - "cosmjs-types": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", - "integrity": "sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog==", - "requires": { - "long": "^4.0.0", - "protobufjs": "~6.11.2" - } - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - } - } - }, - "@cosmjs/crypto": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.7.tgz", - "integrity": "sha512-Fuq90nnqXQb6VvUadGtPy1X6arXY+yhqB95VzN8GM/ZdBLeigu6a3XjOvFki4lKO94QdLn2e9huD8dm4tDQDsA==", - "requires": { - "@cosmjs/encoding": "0.28.7", - "@cosmjs/math": "0.28.7", - "@cosmjs/utils": "0.28.7", - "@noble/hashes": "^1", - "bn.js": "^5.2.0", - "elliptic": "^6.5.3", - "libsodium-wrappers": "^0.7.6" - } - }, - "@cosmjs/encoding": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.7.tgz", - "integrity": "sha512-m2OuRhxux0YacvtjTocEOHyjnKO/KKGjqXlAY5HXGJpyntr+PIlJFdoS9tHax+1rNRrblZXwYIT+UqOs+kSk5Q==", - "requires": { - "base64-js": "^1.3.0", - "bech32": "^1.1.4", - "readonly-date": "^1.0.0" - } - }, - "@cosmjs/json-rpc": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.9.tgz", - "integrity": "sha512-+Fs0dzRg8tdwnXd6ulN37bmGwOnD5wPiMdQLby5LOZk417Ay5lOFpmIOqnoeti+u9jHBbqnCDNteZ8aFRyarMg==", - "requires": { - "@cosmjs/stream": "0.28.9", - "xstream": "^11.14.0" - }, - "dependencies": { - "@cosmjs/stream": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", - "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", - "requires": { - "xstream": "^11.14.0" - } - } - } - }, - "@cosmjs/math": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.7.tgz", - "integrity": "sha512-wSXIOOGVzgtRFsGwScpvQ6C6DUx97K7Ez3KyvRALNzspEnIoKPDgTXf438zhZnb+0+ERAwgdkb/6zWaDoVBfUA==", - "requires": { - "bn.js": "^5.2.0" - } - }, - "@cosmjs/proto-signing": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.7.tgz", - "integrity": "sha512-cOwDQVv95KnpHWlkrtxZ+2Q+z2Qp8Co//g+bwNc+YZ403iGHXSK5PRe3YsPa4f3elKJkJpHVi5oyzp4DB8p7mA==", - "requires": { - "@cosmjs/amino": "0.28.7", - "@cosmjs/crypto": "0.28.7", - "@cosmjs/encoding": "0.28.7", - "@cosmjs/math": "0.28.7", - "@cosmjs/utils": "0.28.7", - "cosmjs-types": "^0.4.0", - "long": "^4.0.0" - }, - "dependencies": { - "cosmjs-types": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", - "integrity": "sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog==", - "requires": { - "long": "^4.0.0", - "protobufjs": "~6.11.2" - } - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - } - } - }, - "@cosmjs/socket": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.9.tgz", - "integrity": "sha512-RdYAWoFf5TyhQKGPJPoCvYpnpPc2fSS5yrRjv7AnF7FB38N/y+PRZ0sKO94MkjP4679txLr1PVNnIZgykR0afQ==", - "requires": { - "@cosmjs/stream": "0.28.9", - "isomorphic-ws": "^4.0.1", - "ws": "^7", - "xstream": "^11.14.0" - }, - "dependencies": { - "@cosmjs/stream": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", - "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", - "requires": { - "xstream": "^11.14.0" - } - } - } - }, - "@cosmjs/stargate": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.7.tgz", - "integrity": "sha512-3nY7czFUaY74gacbQAZuNji1qJWWQjv2C1cxYNqe7qAZAvCiz6A9adJVUmCJRL4peeG7tKiOny1J5IFbsgRu0g==", - "requires": { - "@confio/ics23": "^0.6.8", - "@cosmjs/amino": "0.28.7", - "@cosmjs/encoding": "0.28.7", - "@cosmjs/math": "0.28.7", - "@cosmjs/proto-signing": "0.28.7", - "@cosmjs/stream": "0.28.7", - "@cosmjs/tendermint-rpc": "0.28.7", - "@cosmjs/utils": "0.28.7", - "cosmjs-types": "^0.4.0", - "long": "^4.0.0", - "protobufjs": "~6.11.3", - "xstream": "^11.14.0" - }, - "dependencies": { - "@cosmjs/json-rpc": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.7.tgz", - "integrity": "sha512-2qgRL/9ih/ZYU/8LmtDQopaCKJBKqsuoSXfb2XO3yv6EkE28yiA8YAwLW5IrXjl1tfSiolHaBWarEASS/8Q5xA==", - "requires": { - "@cosmjs/stream": "0.28.7", - "xstream": "^11.14.0" - } - }, - "@cosmjs/socket": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.7.tgz", - "integrity": "sha512-+sCR5AzjjsKlA0K7m8YdxldMvgJa3Tqnx0AAyQXlNw2VXmW1zu9DkKZWW475XFhwO9UYfrdIsOe1vbhnUjQb5g==", - "requires": { - "@cosmjs/stream": "0.28.7", - "isomorphic-ws": "^4.0.1", - "ws": "^7", - "xstream": "^11.14.0" - } - }, - "@cosmjs/tendermint-rpc": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.7.tgz", - "integrity": "sha512-xhIVJL3ol+fZxywP76Ik9pHqCvBdU/BGAw6p48mhla3L3xNcFN2Nf+EnJWcIZPqZl8bHm5QHzPk9VqVFVYCMCw==", - "requires": { - "@cosmjs/crypto": "0.28.7", - "@cosmjs/encoding": "0.28.7", - "@cosmjs/json-rpc": "0.28.7", - "@cosmjs/math": "0.28.7", - "@cosmjs/socket": "0.28.7", - "@cosmjs/stream": "0.28.7", - "@cosmjs/utils": "0.28.7", - "axios": "^0.21.2", - "readonly-date": "^1.0.0", - "xstream": "^11.14.0" - } - }, - "cosmjs-types": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz", - "integrity": "sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog==", - "requires": { - "long": "^4.0.0", - "protobufjs": "~6.11.2" - }, - "dependencies": { - "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - } - } - } - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - } - } - }, - "@cosmjs/stream": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.7.tgz", - "integrity": "sha512-zTMZadlpmxAMtKUHX/dnYs1STung392+P50WgSUAjllG7CZYl7SAY03AKgc+aoF1ohYQcgH9H7VSgabEgLPhmg==", - "requires": { - "xstream": "^11.14.0" - } - }, - "@cosmjs/tendermint-rpc": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.9.tgz", - "integrity": "sha512-a8PCFWG32wyQE6R9XA3dZpbwzvAQCOpcF1m9AsShgVYXRKm+AihbFwu7q75jaxf2XaRnTnrjpCCnJKVFeMnFEQ==", - "requires": { - "@cosmjs/crypto": "0.28.9", - "@cosmjs/encoding": "0.28.9", - "@cosmjs/json-rpc": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/socket": "0.28.9", - "@cosmjs/stream": "0.28.9", - "@cosmjs/utils": "0.28.9", - "axios": "^0.21.2", - "readonly-date": "^1.0.0", - "xstream": "^11.14.0" - }, - "dependencies": { - "@cosmjs/crypto": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.9.tgz", - "integrity": "sha512-2U8cuH7ofRiYni3EPAUsPuqgHN0VM+HdHiY1Ftl2rqRRnZArZNJsbf4t21jVx7rlc+qP32Y1v7PvQyPALHvuyw==", - "requires": { - "@cosmjs/encoding": "0.28.9", - "@cosmjs/math": "0.28.9", - "@cosmjs/utils": "0.28.9", - "@noble/hashes": "^1", - "bn.js": "^5.2.0", - "elliptic": "^6.5.3", - "libsodium-wrappers": "^0.7.6" - } - }, - "@cosmjs/encoding": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.9.tgz", - "integrity": "sha512-AE+uL5LC2f9ZE8ehFPgb7yNMuGr4wx/cbH25gglRwl9utdND2lPVeYmbEF2MRJwB69hXaiPHblCDIz3KXZV9pQ==", - "requires": { - "base64-js": "^1.3.0", - "bech32": "^1.1.4", - "readonly-date": "^1.0.0" - } - }, - "@cosmjs/math": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.9.tgz", - "integrity": "sha512-CY4k3GMQqXL3M+dGUptgCL6mtkcfvhKhmsrzejSZB8yVOIdKA40Wu6buRMMh2+t3SkWpvjVQt8UiJF7C4C4NXQ==", - "requires": { - "bn.js": "^5.2.0" - } - }, - "@cosmjs/stream": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.9.tgz", - "integrity": "sha512-KUunbKu6rI0wBlvP2Ewyp3HuQhhmUj3fxmYwA7k5msnEMdc5atLS4iOZFBZfbtsZP1EmDUJPtW/NvyTLCN/Qag==", - "requires": { - "xstream": "^11.14.0" - } - }, - "@cosmjs/utils": { - "version": "0.28.9", - "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.9.tgz", - "integrity": "sha512-5NJ2dSJlT0wtwdcTvprYNtucSk/+aQANEfobFGOBt6n5JT3VlCH5We3a29YC4Z176vy7RsfVf0j2xJabDmp2iw==" - } - } - }, - "@cosmjs/utils": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.7.tgz", - "integrity": "sha512-0ya5mRaDY956n87dKjx6wfqgH/uEfrZyMswIhcEPqQPegwp4FDd6cwJtSdv/d5S7fgvoEs2UABvUFNzCT5C0hg==" - }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "dependencies": { - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - } - } - }, - "@eslint/eslintrc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", - "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.3.2", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "globals": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", - "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.0.tgz", - "integrity": "sha512-tscn3dlJFGay47kb4qVruQg/XWlmvU0xp3EJOjzzY+sBaI+YgwKcvAmTcyYU7xEiLLIY5HCdWRooAL8dqkFlDA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.0", - "jest-util": "^28.1.0", - "slash": "^3.0.0" - } - }, - "@jest/core": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.0.tgz", - "integrity": "sha512-/2PTt0ywhjZ4NwNO4bUqD9IVJfmFVhVKGlhvSpmEfUCuxYf/3NHcKmRFI+I71lYzbTT3wMuYpETDCTHo81gC/g==", - "dev": true, - "requires": { - "@jest/console": "^28.1.0", - "@jest/reporters": "^28.1.0", - "@jest/test-result": "^28.1.0", - "@jest/transform": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^28.0.2", - "jest-config": "^28.1.0", - "jest-haste-map": "^28.1.0", - "jest-message-util": "^28.1.0", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.0", - "jest-resolve-dependencies": "^28.1.0", - "jest-runner": "^28.1.0", - "jest-runtime": "^28.1.0", - "jest-snapshot": "^28.1.0", - "jest-util": "^28.1.0", - "jest-validate": "^28.1.0", - "jest-watcher": "^28.1.0", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "@jest/environment": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.0.tgz", - "integrity": "sha512-S44WGSxkRngzHslhV6RoAExekfF7Qhwa6R5+IYFa81mpcj0YgdBnRSmvHe3SNwOt64yXaE5GG8Y2xM28ii5ssA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/node": "*", - "jest-mock": "^28.1.0" - } - }, - "@jest/expect": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.0.tgz", - "integrity": "sha512-be9ETznPLaHOmeJqzYNIXv1ADEzENuQonIoobzThOYPuK/6GhrWNIJDVTgBLCrz3Am73PyEU2urQClZp0hLTtA==", - "dev": true, - "requires": { - "expect": "^28.1.0", - "jest-snapshot": "^28.1.0" - } - }, - "@jest/expect-utils": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.0.tgz", - "integrity": "sha512-5BrG48dpC0sB80wpeIX5FU6kolDJI4K0n5BM9a5V38MGx0pyRvUBSS0u2aNTdDzmOrCjhOg8pGs6a20ivYkdmw==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2" - } - }, - "@jest/fake-timers": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.0.tgz", - "integrity": "sha512-Xqsf/6VLeAAq78+GNPzI7FZQRf5cCHj1qgQxCjws9n8rKw8r1UYoeaALwBvyuzOkpU3c1I6emeMySPa96rxtIg==", - "dev": true, - "requires": { - "@jest/types": "^28.1.0", - "@sinonjs/fake-timers": "^9.1.1", - "@types/node": "*", - "jest-message-util": "^28.1.0", - "jest-mock": "^28.1.0", - "jest-util": "^28.1.0" - } - }, - "@jest/globals": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.0.tgz", - "integrity": "sha512-3m7sTg52OTQR6dPhsEQSxAvU+LOBbMivZBwOvKEZ+Rb+GyxVnXi9HKgOTYkx/S99T8yvh17U4tNNJPIEQmtwYw==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.0", - "@jest/expect": "^28.1.0", - "@jest/types": "^28.1.0" - } - }, - "@jest/reporters": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.0.tgz", - "integrity": "sha512-qxbFfqap/5QlSpIizH9c/bFCDKsQlM4uAKSOvZrP+nIdrjqre3FmKzpTtYyhsaVcOSNK7TTt2kjm+4BJIjysFA==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.0", - "@jest/test-result": "^28.1.0", - "@jest/transform": "^28.1.0", - "@jest/types": "^28.1.0", - "@jridgewell/trace-mapping": "^0.3.7", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-util": "^28.1.0", - "jest-worker": "^28.1.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^9.0.0" - } - }, - "@jest/schemas": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.0.2.tgz", - "integrity": "sha512-YVDJZjd4izeTDkij00vHHAymNXQ6WWsdChFRK86qck6Jpr3DCL5W3Is3vslviRlP+bLuMYRLbdp98amMvqudhA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.23.3" - } - }, - "@jest/source-map": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.0.2.tgz", - "integrity": "sha512-Y9dxC8ZpN3kImkk0LkK5XCEneYMAXlZ8m5bflmSL5vrwyeUpJfentacCUg6fOb8NOpOO7hz2+l37MV77T6BFPw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.7", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.0.tgz", - "integrity": "sha512-sBBFIyoPzrZho3N+80P35A5oAkSKlGfsEFfXFWuPGBsW40UAjCkGakZhn4UQK4iQlW2vgCDMRDOob9FGKV8YoQ==", - "dev": true, - "requires": { - "@jest/console": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.0.tgz", - "integrity": "sha512-tZCEiVWlWNTs/2iK9yi6o3AlMfbbYgV4uuZInSVdzZ7ftpHZhCMuhvk2HLYhCZzLgPFQ9MnM1YaxMnh3TILFiQ==", - "dev": true, - "requires": { - "@jest/test-result": "^28.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.0", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.0.tgz", - "integrity": "sha512-omy2xe5WxlAfqmsTjTPxw+iXRTRnf+NtX0ToG+4S0tABeb4KsKmPUHq5UBuwunHg3tJRwgEQhEp0M/8oiatLEA==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.0", - "@jridgewell/trace-mapping": "^0.3.7", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.0", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - } - }, - "@jest/types": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.0.tgz", - "integrity": "sha512-xmEggMPr317MIOjjDoZ4ejCSr9Lpbt/u34+dvc99t7DS8YirW5rwZEhzKPC2BMUFkUhI48qs6qLUSGw5FuL0GA==", - "dev": true, - "requires": { - "@jest/schemas": "^28.0.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", - "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", - "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", - "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@noble/hashes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.0.0.tgz", - "integrity": "sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg==" - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@osmonauts/helpers": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@osmonauts/helpers/-/helpers-0.3.5.tgz", - "integrity": "sha512-NW8S0aAyOd2GLl6STyDcV1JbIKy2yJryj19zC+P647LYD/1eV4GEzgBXQ3G691ia+dZNQEQFpcfu+SFqIL5Q7A==", - "requires": { - "@babel/runtime": "^7.18.3", - "long": "^5.2.0", - "protobufjs": "^6.11.3" - } - }, - "@osmonauts/lcd": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@osmonauts/lcd/-/lcd-0.3.5.tgz", - "integrity": "sha512-VUIwDp90ZD0GQGQ3jbttAfZCSKUU1dJbKFlMYqc0GZGIiWa9Ka6ZYUKQCpiwYXqjksLGWoFvTIX3YfLkQwvTqA==", - "requires": { - "@babel/runtime": "^7.18.3", - "axios": "0.27.2" - }, - "dependencies": { - "axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "requires": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - } - } - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "@sinclair/typebox": { - "version": "0.23.5", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz", - "integrity": "sha512-AFBVi/iT4g20DHoujvMH1aEDn8fGJh4xsRGCP6d8RpLPMqsNPvW01Jcn0QysXTsg++/xj25NmJsGyH9xug/wKg==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.17.1.tgz", - "integrity": "sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.3.tgz", - "integrity": "sha512-Tsbjk8Y2hkBaY/gJsataeb4q9Mubw9EOz7+4RjPkzD5KjTvHHs7cpws22InaoXxAVAhF5HfFbzJjo6oKWqSZLw==", - "dev": true, - "requires": { - "jest-matcher-utils": "^28.0.0", - "pretty-format": "^28.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, - "@types/node": { - "version": "17.0.38", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.38.tgz", - "integrity": "sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==" - }, - "@types/prettier": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.3.tgz", - "integrity": "sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/yargs": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.10.tgz", - "integrity": "sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.0.tgz", - "integrity": "sha512-DDrIA7GXtmHXr1VCcx9HivA39eprYBIFxbQEHI6NyraRDxCGpxAFiYQAT/1Y0vh1C+o2vfBiy4IuPoXxtTZCAQ==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.27.0", - "@typescript-eslint/type-utils": "5.27.0", - "@typescript-eslint/utils": "5.27.0", - "debug": "^4.3.4", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/parser": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.27.0.tgz", - "integrity": "sha512-8oGjQF46c52l7fMiPPvX4It3u3V3JipssqDfHQ2hcR0AeR8Zge+OYyKUCm5b70X72N1qXt0qgHenwN6Gc2SXZA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.27.0", - "@typescript-eslint/types": "5.27.0", - "@typescript-eslint/typescript-estree": "5.27.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.0.tgz", - "integrity": "sha512-VnykheBQ/sHd1Vt0LJ1JLrMH1GzHO+SzX6VTXuStISIsvRiurue/eRkTqSrG0CexHQgKG8shyJfR4o5VYioB9g==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.27.0", - "@typescript-eslint/visitor-keys": "5.27.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.27.0.tgz", - "integrity": "sha512-vpTvRRchaf628Hb/Xzfek+85o//zEUotr1SmexKvTfs7czXfYjXVT/a5yDbpzLBX1rhbqxjDdr1Gyo0x1Fc64g==", - "dev": true, - "requires": { - "@typescript-eslint/utils": "5.27.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.0.tgz", - "integrity": "sha512-lY6C7oGm9a/GWhmUDOs3xAVRz4ty/XKlQ2fOLr8GAIryGn0+UBOoJDWyHer3UgrHkenorwvBnphhP+zPmzmw0A==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.0.tgz", - "integrity": "sha512-QywPMFvgZ+MHSLRofLI7BDL+UczFFHyj0vF5ibeChDAJgdTV8k4xgEwF0geFhVlPc1p8r70eYewzpo6ps+9LJQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.27.0", - "@typescript-eslint/visitor-keys": "5.27.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/utils": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.0.tgz", - "integrity": "sha512-nZvCrkIJppym7cIbP3pOwIkAefXOmfGPnCM0LQfzNaKxJHI6VjI8NC662uoiPlaf5f6ymkTy9C3NQXev2mdXmA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.27.0", - "@typescript-eslint/types": "5.27.0", - "@typescript-eslint/typescript-estree": "5.27.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.0.tgz", - "integrity": "sha512-46cYrteA2MrIAjv9ai44OQDUoCZyHeGIc4lsjCUX2WT6r4C+kidz1bNiR4017wHOPUythYeH+Sc7/cFP97KEAA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.27.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "requires": { - "follow-redirects": "^1.14.0" - } - }, - "babel-jest": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.0.tgz", - "integrity": "sha512-zNKk0yhDZ6QUwfxh9k07GII6siNGMJWVUU49gmFj5gfdqDKLqa2RArXOF2CODp4Dr7dLxN2cvAV+667dGJ4b4w==", - "dev": true, - "requires": { - "@jest/transform": "^28.1.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^28.0.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "requires": { - "object.assign": "^4.1.0" - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.0.2.tgz", - "integrity": "sha512-Kizhn/ZL+68ZQHxSnHyuvJv8IchXD62KQxV77TBDV/xoBFBOfgRAk97GNs6hXdTTCiVES9nB2I6+7MXXrk5llQ==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.3.1", - "semver": "^6.1.1" - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", - "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.1", - "core-js-compat": "^3.21.0" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.1" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.0.2.tgz", - "integrity": "sha512-sYzXIdgIXXroJTFeB3S6sNDWtlJ2dllCdTEsnZ65ACrMojj3hVNFRmnJ1HZtomGi+Be7aqpY/HJ92fr8OhKVkQ==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^28.0.2", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" - }, - "browserslist": { - "version": "4.20.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", - "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001332", - "electron-to-chromium": "^1.4.118", - "escalade": "^3.1.1", - "node-releases": "^2.0.3", - "picocolors": "^1.0.0" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001344", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001344.tgz", - "integrity": "sha512-0ZFjnlCaXNOAYcV7i+TtdKBp0L/3XEU2MF/x6Du1lrh+SRX4IfzIVL4HNJg5pB2PmFb8rszIGyOvsZnqqRoc2g==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "ci-info": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.1.tgz", - "integrity": "sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "core-js-compat": { - "version": "3.22.7", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.22.7.tgz", - "integrity": "sha512-uI9DAQKKiiE/mclIC5g4AjRpio27g+VMRhe6rQoz+q4Wm4L6A/fJhiLtBw+sfOpDG9wZ3O0pxIw7GbfOlBgjOA==", - "dev": true, - "requires": { - "browserslist": "^4.20.3", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } - } - }, - "cosmjs-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.5.0.tgz", - "integrity": "sha512-Qy2yxDp5HasBUnBV5OL9EOuTJw94LAbCWfiRb5QcW1sqh93KSSY5PpNMB3rTsxwuPcf/k3CNRY02DeQMjrsJuA==", - "requires": { - "long": "^4.0.0", - "protobufjs": "~6.11.2" - }, - "dependencies": { - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - } - } - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "electron-to-chromium": { - "version": "1.4.143", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.143.tgz", - "integrity": "sha512-2hIgvu0+pDfXIqmVmV5X6iwMjQ2KxDsWKwM+oI1fABEOy/Dqmll0QJRmIQ3rm+XaoUa/qKrmy5h7LSTFQ6Ldzg==", - "dev": true - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - } - } - }, - "emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - }, - "eslint": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.16.0.tgz", - "integrity": "sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.2", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "globals": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", - "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "dependencies": { - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - } - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", - "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", - "dev": true, - "requires": { - "acorn": "^8.7.1", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expect": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.0.tgz", - "integrity": "sha512-qFXKl8Pmxk8TBGfaFKRtcQjfXEnKAs+dmlxdwvukJZorwrAabT7M3h8oLOG01I2utEhkmUTi17CHaPBovZsKdw==", - "dev": true, - "requires": { - "@jest/expect-utils": "^28.1.0", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.0", - "jest-message-util": "^28.1.0", - "jest-util": "^28.1.0" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, - "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "requires": { - "define-properties": "^1.1.3" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "requires": {} - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", - "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.0.tgz", - "integrity": "sha512-TZR+tHxopPhzw3c3560IJXZWLNHgpcz1Zh0w5A65vynLGNcg/5pZ+VildAd7+XGOu6jd58XMY/HNn0IkZIXVXg==", - "dev": true, - "requires": { - "@jest/core": "^28.1.0", - "import-local": "^3.0.2", - "jest-cli": "^28.1.0" - } - }, - "jest-changed-files": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.0.2.tgz", - "integrity": "sha512-QX9u+5I2s54ZnGoMEjiM2WeBvJR2J7w/8ZUmH2um/WLAuGAYFQcsVXY9+1YL6k0H/AGUdH8pXUAv6erDqEsvIA==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "throat": "^6.0.1" - } - }, - "jest-circus": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.0.tgz", - "integrity": "sha512-rNYfqfLC0L0zQKRKsg4n4J+W1A2fbyGH7Ss/kDIocp9KXD9iaL111glsLu7+Z7FHuZxwzInMDXq+N1ZIBkI/TQ==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.0", - "@jest/expect": "^28.1.0", - "@jest/test-result": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^28.1.0", - "jest-matcher-utils": "^28.1.0", - "jest-message-util": "^28.1.0", - "jest-runtime": "^28.1.0", - "jest-snapshot": "^28.1.0", - "jest-util": "^28.1.0", - "pretty-format": "^28.1.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - } - }, - "jest-cli": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.0.tgz", - "integrity": "sha512-fDJRt6WPRriHrBsvvgb93OxgajHHsJbk4jZxiPqmZbMDRcHskfJBBfTyjFko0jjfprP544hOktdSi9HVgl4VUQ==", - "dev": true, - "requires": { - "@jest/core": "^28.1.0", - "@jest/test-result": "^28.1.0", - "@jest/types": "^28.1.0", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^28.1.0", - "jest-util": "^28.1.0", - "jest-validate": "^28.1.0", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - } - }, - "jest-config": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.0.tgz", - "integrity": "sha512-aOV80E9LeWrmflp7hfZNn/zGA4QKv/xsn2w8QCBP0t0+YqObuCWTSgNbHJ0j9YsTuCO08ZR/wsvlxqqHX20iUA==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^28.1.0", - "@jest/types": "^28.1.0", - "babel-jest": "^28.1.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^28.1.0", - "jest-environment-node": "^28.1.0", - "jest-get-type": "^28.0.2", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.0", - "jest-runner": "^28.1.0", - "jest-util": "^28.1.0", - "jest-validate": "^28.1.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^28.1.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - } - }, - "jest-diff": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.1.tgz", - "integrity": "sha512-/MUUxeR2fHbqHoMMiffe/Afm+U8U4olFRJ0hiVG2lZatPJcnGxx292ustVu7bULhjV65IYMxRdploAKLbcrsyg==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.1" - } - }, - "jest-docblock": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.0.2.tgz", - "integrity": "sha512-FH10WWw5NxLoeSdQlJwu+MTiv60aXV/t8KEwIRGEv74WARE1cXIqh1vGdy2CraHuWOOrnzTWj/azQKqW4fO7xg==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.0.tgz", - "integrity": "sha512-a/XX02xF5NTspceMpHujmOexvJ4GftpYXqr6HhhmKmExtMXsyIN/fvanQlt/BcgFoRKN4OCXxLQKth9/n6OPFg==", - "dev": true, - "requires": { - "@jest/types": "^28.1.0", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "jest-util": "^28.1.0", - "pretty-format": "^28.1.0" - } - }, - "jest-environment-node": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.0.tgz", - "integrity": "sha512-gBLZNiyrPw9CSMlTXF1yJhaBgWDPVvH0Pq6bOEwGMXaYNzhzhw2kA/OijNF8egbCgDS0/veRv97249x2CX+udQ==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.0", - "@jest/fake-timers": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/node": "*", - "jest-mock": "^28.1.0", - "jest-util": "^28.1.0" - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "jest-haste-map": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.0.tgz", - "integrity": "sha512-xyZ9sXV8PtKi6NCrJlmq53PyNVHzxmcfXNVvIRHpHmh1j/HChC4pwKgyjj7Z9us19JMw8PpQTJsFWOsIfT93Dw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.0", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.0", - "jest-worker": "^28.1.0", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - } - }, - "jest-leak-detector": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.0.tgz", - "integrity": "sha512-uIJDQbxwEL2AMMs2xjhZl2hw8s77c3wrPaQ9v6tXJLGaaQ+4QrNJH5vuw7hA7w/uGT/iJ42a83opAqxGHeyRIA==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.0" - } - }, - "jest-matcher-utils": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.1.tgz", - "integrity": "sha512-NPJPRWrbmR2nAJ+1nmnfcKKzSwgfaciCCrYZzVnNoxVoyusYWIjkBMNvu0RHJe7dNj4hH3uZOPZsQA+xAYWqsw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.1" - } - }, - "jest-message-util": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.0.tgz", - "integrity": "sha512-RpA8mpaJ/B2HphDMiDlrAZdDytkmwFqgjDZovM21F35lHGeUeCvYmm6W+sbQ0ydaLpg5bFAUuWG1cjqOl8vqrw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.0", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-mock": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.0.tgz", - "integrity": "sha512-H7BrhggNn77WhdL7O1apG0Q/iwl0Bdd5E1ydhCJzL3oBLh/UYxAwR3EJLsBZ9XA3ZU4PA3UNw4tQjduBTCTmLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.0", - "@types/node": "*" - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "dev": true - }, - "jest-resolve": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.0.tgz", - "integrity": "sha512-vvfN7+tPNnnhDvISuzD1P+CRVP8cK0FHXRwPAcdDaQv4zgvwvag2n55/h5VjYcM5UJG7L4TwE5tZlzcI0X2Lhw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^28.1.0", - "jest-validate": "^28.1.0", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.0.tgz", - "integrity": "sha512-Ue1VYoSZquPwEvng7Uefw8RmZR+me/1kr30H2jMINjGeHgeO/JgrR6wxj2ofkJ7KSAA11W3cOrhNCbj5Dqqd9g==", - "dev": true, - "requires": { - "jest-regex-util": "^28.0.2", - "jest-snapshot": "^28.1.0" - } - }, - "jest-runner": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.0.tgz", - "integrity": "sha512-FBpmuh1HB2dsLklAlRdOxNTTHKFR6G1Qmd80pVDvwbZXTriqjWqjei5DKFC1UlM732KjYcE6yuCdiF0WUCOS2w==", - "dev": true, - "requires": { - "@jest/console": "^28.1.0", - "@jest/environment": "^28.1.0", - "@jest/test-result": "^28.1.0", - "@jest/transform": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "graceful-fs": "^4.2.9", - "jest-docblock": "^28.0.2", - "jest-environment-node": "^28.1.0", - "jest-haste-map": "^28.1.0", - "jest-leak-detector": "^28.1.0", - "jest-message-util": "^28.1.0", - "jest-resolve": "^28.1.0", - "jest-runtime": "^28.1.0", - "jest-util": "^28.1.0", - "jest-watcher": "^28.1.0", - "jest-worker": "^28.1.0", - "source-map-support": "0.5.13", - "throat": "^6.0.1" - } - }, - "jest-runtime": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.0.tgz", - "integrity": "sha512-wNYDiwhdH/TV3agaIyVF0lsJ33MhyujOe+lNTUiolqKt8pchy1Hq4+tDMGbtD5P/oNLA3zYrpx73T9dMTOCAcg==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.0", - "@jest/fake-timers": "^28.1.0", - "@jest/globals": "^28.1.0", - "@jest/source-map": "^28.0.2", - "@jest/test-result": "^28.1.0", - "@jest/transform": "^28.1.0", - "@jest/types": "^28.1.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.0", - "jest-message-util": "^28.1.0", - "jest-mock": "^28.1.0", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.0", - "jest-snapshot": "^28.1.0", - "jest-util": "^28.1.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - } - }, - "jest-snapshot": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.0.tgz", - "integrity": "sha512-ex49M2ZrZsUyQLpLGxQtDbahvgBjlLPgklkqGM0hq/F7W/f8DyqZxVHjdy19QKBm4O93eDp+H5S23EiTbbUmHw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.0", - "@jest/transform": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.0", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.0", - "jest-matcher-utils": "^28.1.0", - "jest-message-util": "^28.1.0", - "jest-util": "^28.1.0", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.0", - "semver": "^7.3.5" - }, - "dependencies": { - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "jest-util": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.0.tgz", - "integrity": "sha512-qYdCKD77k4Hwkose2YBEqQk7PzUf/NSE+rutzceduFveQREeH6b+89Dc9+wjX9dAwHcgdx4yedGA3FQlU/qCTA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.0", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-validate": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.0.tgz", - "integrity": "sha512-Lly7CJYih3vQBfjLeANGgBSBJ7pEa18cxpQfQEq2go2xyEzehnHfQTjoUia8xUv4x4J80XKFIDwJJThXtRFQXQ==", - "dev": true, - "requires": { - "@jest/types": "^28.1.0", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "leven": "^3.1.0", - "pretty-format": "^28.1.0" - }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - } - } - }, - "jest-watcher": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.0.tgz", - "integrity": "sha512-tNHMtfLE8Njcr2IRS+5rXYA4BhU90gAOwI9frTGOqd+jX0P/Au/JfRSNqsf5nUTcWdbVYuLxS1KjnzILSoR5hA==", - "dev": true, - "requires": { - "@jest/test-result": "^28.1.0", - "@jest/types": "^28.1.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.0", - "string-length": "^4.0.1" - } - }, - "jest-worker": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.0.tgz", - "integrity": "sha512-ZHwM6mNwaWBR52Snff8ZvsCTqQsvhCxP/bT1I6T6DAnb6ygkshsyLQIMxFwHpYxht0HOoqt23JlC01viI7T03A==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "libsodium": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz", - "integrity": "sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ==" - }, - "libsodium-wrappers": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz", - "integrity": "sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg==", - "requires": { - "libsodium": "^0.7.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "long": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", - "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node-releases": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", - "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "osmojs": { - "version": "0.4.53", - "resolved": "https://registry.npmjs.org/osmojs/-/osmojs-0.4.53.tgz", - "integrity": "sha512-5ml35Nx6gpVQLho+XrbzZptA1025S5XloRHLluvlNtIfVwILYp1E509WakGnjE9r5nxj/3eG9ThM5Yf8MfmWmg==", - "requires": { - "@babel/runtime": "^7.18.3", - "@cosmjs/amino": "0.28.7", - "@cosmjs/proto-signing": "0.28.7", - "@cosmjs/stargate": "0.28.7", - "@cosmjs/tendermint-rpc": "^0.28.7", - "@osmonauts/helpers": "^0.3.5", - "@osmonauts/lcd": "^0.3.5", - "long": "^5.2.0", - "protobufjs": "^6.11.3" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "pako": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", - "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true - }, - "pretty-format": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.1.tgz", - "integrity": "sha512-wwJbVTGFHeucr5Jw2bQ9P+VYHyLdAqedFLEkdQUVaBF/eiidDwH5OpilINq4mEfhbCjLnirt6HTTDhv1HaTIQw==", - "dev": true, - "requires": { - "@jest/schemas": "^28.0.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "dependencies": { - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - } - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "readonly-date": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz", - "integrity": "sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==" - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", - "dev": true, - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" - }, - "regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "regexpu-core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", - "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", - "dev": true, - "requires": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.0.1", - "regjsgen": "^0.6.0", - "regjsparser": "^0.8.2", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - } - }, - "regjsgen": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", - "dev": true - }, - "regjsparser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", - "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dev": true, - "requires": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "symbol-observable": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz", - "integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==" - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-node": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.0.tgz", - "integrity": "sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA==", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "dependencies": { - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - } - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "v8-to-istanbul": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.0.tgz", - "integrity": "sha512-HcvgY/xaRm7isYmyx+lFKA4uQmfUbN0J4M0nNItvzTvH/iQ9kW5j/t4YSR+Ge323/lrgDAWJoF46tzGQHwBHFw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.7", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.1.tgz", - "integrity": "sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "ws": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz", - "integrity": "sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==", - "requires": {} - }, - "xstream": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/xstream/-/xstream-11.14.0.tgz", - "integrity": "sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw==", - "requires": { - "globalthis": "^1.0.1", - "symbol-observable": "^2.0.3" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - } - }, - "yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", - "dev": true - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - } - } -} diff --git a/scripts/package.json b/scripts/package.json index c65d40673..1d90983e7 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -1,30 +1,38 @@ { - "name": "credit-manager-scripts", - "version": "2.0.0", - "main": "index.js", + "name": "rover-scripts", + "version": "1.0.0", + "license": "GPL-3.0-or-later", "scripts": { - "test": "jest --testTimeout=20000 && npm run lint && npm run format-check", + "deploy:osmosis": "yarn build && node build/deploy/osmosis", + "generate-types": "yarn rust-schema && tsc --project codegen-tsconfig.json && rm -rf types/generated && node build/codegen && node build/codegen/insertIgnores.js", + "rust-schema": "cd ../ && cargo make generate-all-schemas && cd scripts", + "compile-wasm": "cd ../ && cargo make rust-optimizer && cd scripts", + "build": "tsc", + "lint": "eslint . && yarn build && yarn format-check", "format": "prettier --write .", - "format-check": "prettier --ignore-path .gitignore --check .", - "lint": "tsc && eslint . --ext .ts" + "format-check": "prettier --check ." }, "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.28.4", "@cosmjs/stargate": "^0.28.4", + "@cosmwasm/ts-codegen": "^0.16.3", + "chalk": "4.1.2", "cosmjs-types": "^0.5.0", + "lodash": "^4.17.21", "long": "^5.2.0", - "osmojs": "^0.4.46" + "osmojs": "^0.15.0", + "prepend-file": "^2.0.1" }, "devDependencies": { - "@babel/preset-env": "^7.18.2", - "@babel/preset-typescript": "^7.17.12", - "@types/jest": "^28.1.3", - "@typescript-eslint/eslint-plugin": "^5.27.0", - "@typescript-eslint/parser": "^5.27.0", - "eslint": "^8.16.0", - "jest": "^28.1.0", - "prettier": "2.7.1", - "typescript": "^4.7.4", - "ts-node": "^10.5.0" + "@babel/preset-env": "^7.19.1", + "@babel/preset-typescript": "^7.18.6", + "@types/jest": "^29.0.2", + "@typescript-eslint/eslint-plugin": "^5.37.0", + "@typescript-eslint/parser": "^5.37.0", + "eslint": "^8.23.1", + "eslint-config-prettier": "^8.5.0", + "jest": "^29.0.3", + "prettier": "^2.7.1", + "typescript": "^4.8.3" } } diff --git a/scripts/tests/instantiate.test.ts b/scripts/tests/instantiate.test.ts deleted file mode 100644 index 639fba018..000000000 --- a/scripts/tests/instantiate.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; -import { sha256 } from '@cosmjs/crypto'; -import { toHex } from '@cosmjs/encoding'; -import fs from 'fs'; -import path from 'path'; -import { getCosmWasmClient } from '../utils/client'; -import { Network, networks } from '../utils/config'; -import { testWallet1 } from '../utils/test-wallets'; -import { AssetInfo, Config, serializeAssetInfo } from '../utils/types'; - -describe('instantiating fields contract', () => { - let client: SigningCosmWasmClient; - let managerCodeId: number; - let managerContractAddr: string; - let accountNftCodeId: number; - - beforeAll(async () => { - client = await getCosmWasmClient(testWallet1); - }); - - afterAll(() => { - client.disconnect(); - }); - - test('credit manager wasm can be uploaded', async () => { - const wasm = fs.readFileSync(path.resolve(__dirname, '../../artifacts/credit_manager.wasm')); - managerCodeId = await uploadAndAssert(client, wasm); - expect(managerCodeId).toBeDefined(); - }); - - test('account nft wasm can be uploaded', async () => { - const wasm = fs.readFileSync(path.resolve(__dirname, '../../artifacts/account_nft.wasm')); - accountNftCodeId = await uploadAndAssert(client, wasm); - expect(accountNftCodeId).toBeDefined(); - }); - - test('can be instantiated', async () => { - const owner = 'osmo105e4n2f2gr92x8pxvmhxj5v7e2m9j08zelxdnq'; - const allowed_vaults = [ - 'osmo1r4c2g5wex39kcdeahgxjaxnr2wnv7jvxc5je0e', - 'osmo1av54qcmavhjkqsd67cf6f4cedqjrdeh73k52l2', - 'osmo18zhhdrjd5qfvewnu5lkkgv6w7rtcmzh3hq7qes', - ]; - const allowed_assets: AssetInfo[] = [ - { cw20: 'osmo1ptlhw66xg7nznag8sy4mnlsj04xklxqjgqrpz4' }, - { native: 'uosmo' }, - { cw20: 'osmo1ewn73qp0aqrtya38p0nv5c2xsshdea7ad34qkc' }, - ]; - - const { contractAddress } = await client.instantiate( - testWallet1.address, - managerCodeId, - { owner, allowed_vaults, allowed_assets, nft_contract_code_id: accountNftCodeId }, - 'test-instantiate-string-123', - networks[Network.OSMOSIS].defaultSendFee, - ); - managerContractAddr = contractAddress; - expect(managerContractAddr).toBeDefined(); - - const allowedVaultsFromQuery: string[] = await client.queryContractSmart(contractAddress, { - allowed_vaults: {}, - }); - - expect(allowedVaultsFromQuery.length).toEqual(allowed_vaults.length); - expect(allowedVaultsFromQuery.every((v) => allowed_vaults.includes(v))).toBeTruthy(); - - const allowedAssetsFromQuery: AssetInfo[] = await client.queryContractSmart(contractAddress, { - allowed_assets: {}, - }); - - expect(allowedAssetsFromQuery.length).toEqual(allowed_assets.length); - expect( - allowedAssetsFromQuery - .map(serializeAssetInfo) - .every((asset_str) => allowed_assets.map(serializeAssetInfo).includes(asset_str)), - ).toBeTruthy(); - - const configRes: Config = await client.queryContractSmart(contractAddress, { - config: {}, - }); - - expect(configRes.owner).toEqual(owner); - expect(configRes.account_nft).toEqual(""); - }); -}); - -async function uploadAndAssert(client: SigningCosmWasmClient, wasm: Buffer) { - const { - codeId: uploadCodeId, - originalChecksum, - originalSize, - compressedChecksum, - compressedSize, - } = await client.upload(testWallet1.address, wasm, networks[Network.OSMOSIS].defaultSendFee); - - expect(originalChecksum).toEqual(toHex(sha256(wasm))); - expect(originalSize).toEqual(wasm.length); - expect(compressedChecksum).toMatch(/^[0-9a-f]{64}$/); - expect(compressedSize).toBeLessThan(wasm.length * 0.5); - expect(uploadCodeId).toBeGreaterThanOrEqual(1); - return uploadCodeId; -} diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index ae63e9592..408f9a5d6 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -1,20 +1,20 @@ { "compilerOptions": { - "target": "es2021", - "lib": ["esnext"], + "target": "ES2021", + "lib": ["ES2021"], "types": ["jest", "node"], - "module": "esnext", + "module": "NodeNext", "declaration": true, "noImplicitAny": true, "removeComments": true, "experimentalDecorators": true, - "rootDir": ".", + "outDir": "./build", "sourceMap": true, "noEmitOnError": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, - "noEmit": true, + "noEmit": false, "moduleResolution": "node", "isolatedModules": true, "resolveJsonModule": true, @@ -28,5 +28,5 @@ "skipLibCheck": true }, "include": ["**/*.ts", "**/*.tsx", "types.d.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "./build/**/*", "types/generated/**/*"] } diff --git a/scripts/types/config.ts b/scripts/types/config.ts new file mode 100644 index 000000000..3b9ca977c --- /dev/null +++ b/scripts/types/config.ts @@ -0,0 +1,26 @@ +import { CoinMarketInfo } from './generated/mock-red-bank/MockRedBank.types' +import { Coin } from '@cosmjs/amino' +import { CoinPrice } from './generated/mock-oracle/MockOracle.types' + +export interface DeploymentConfig { + baseDenom: string + secondaryDenom: string + chainPrefix: string + rpcEndpoint: string + deployerMnemonic: string + chainId: string + defaultGasPrice: number + startingAmountForTestUser: number + depositAmount: number + borrowAmount: number + repayAmount: number + swapAmount: number + slippage: number + swapRoute: { steps: { denom_out: string; pool_id: number }[] } + withdrawAmount: number + mockRedbankCoins: CoinMarketInfo[] + seededFundsForMockRedBank: Coin[] + oraclePrices: CoinPrice[] + maxCloseFactor: number + maxLiquidationBonus: number +} diff --git a/scripts/types/generated/account-nft/AccountNft.client.ts b/scripts/types/generated/account-nft/AccountNft.client.ts new file mode 100644 index 000000000..bd4008a1e --- /dev/null +++ b/scripts/types/generated/account-nft/AccountNft.client.ts @@ -0,0 +1,635 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { Coin, StdFee } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + Binary, + Expiration, + Timestamp, + Uint64, + QueryMsg, + AllNftInfoResponseForEmpty, + OwnerOfResponse, + Approval, + NftInfoResponseForEmpty, + Empty, + OperatorsResponse, + TokensResponse, + ApprovalResponse, + ApprovalsResponse, + ContractInfoResponse, + MinterResponse, + NumTokensResponse, + String, +} from './AccountNft.types' +export interface AccountNftReadOnlyInterface { + contractAddress: string + proposedNewOwner: () => Promise + ownerOf: ({ + includeExpired, + tokenId, + }: { + includeExpired?: boolean + tokenId: string + }) => Promise + approval: ({ + includeExpired, + spender, + tokenId, + }: { + includeExpired?: boolean + spender: string + tokenId: string + }) => Promise + approvals: ({ + includeExpired, + tokenId, + }: { + includeExpired?: boolean + tokenId: string + }) => Promise + allOperators: ({ + includeExpired, + limit, + owner, + startAfter, + }: { + includeExpired?: boolean + limit?: number + owner: string + startAfter?: string + }) => Promise + numTokens: () => Promise + contractInfo: () => Promise + nftInfo: ({ tokenId }: { tokenId: string }) => Promise + allNftInfo: ({ + includeExpired, + tokenId, + }: { + includeExpired?: boolean + tokenId: string + }) => Promise + tokens: ({ + limit, + owner, + startAfter, + }: { + limit?: number + owner: string + startAfter?: string + }) => Promise + allTokens: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }) => Promise + minter: () => Promise +} +export class AccountNftQueryClient implements AccountNftReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.proposedNewOwner = this.proposedNewOwner.bind(this) + this.ownerOf = this.ownerOf.bind(this) + this.approval = this.approval.bind(this) + this.approvals = this.approvals.bind(this) + this.allOperators = this.allOperators.bind(this) + this.numTokens = this.numTokens.bind(this) + this.contractInfo = this.contractInfo.bind(this) + this.nftInfo = this.nftInfo.bind(this) + this.allNftInfo = this.allNftInfo.bind(this) + this.tokens = this.tokens.bind(this) + this.allTokens = this.allTokens.bind(this) + this.minter = this.minter.bind(this) + } + + proposedNewOwner = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + proposed_new_owner: {}, + }) + } + ownerOf = async ({ + includeExpired, + tokenId, + }: { + includeExpired?: boolean + tokenId: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + owner_of: { + include_expired: includeExpired, + token_id: tokenId, + }, + }) + } + approval = async ({ + includeExpired, + spender, + tokenId, + }: { + includeExpired?: boolean + spender: string + tokenId: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + approval: { + include_expired: includeExpired, + spender, + token_id: tokenId, + }, + }) + } + approvals = async ({ + includeExpired, + tokenId, + }: { + includeExpired?: boolean + tokenId: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + approvals: { + include_expired: includeExpired, + token_id: tokenId, + }, + }) + } + allOperators = async ({ + includeExpired, + limit, + owner, + startAfter, + }: { + includeExpired?: boolean + limit?: number + owner: string + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_operators: { + include_expired: includeExpired, + limit, + owner, + start_after: startAfter, + }, + }) + } + numTokens = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + num_tokens: {}, + }) + } + contractInfo = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + contract_info: {}, + }) + } + nftInfo = async ({ tokenId }: { tokenId: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + nft_info: { + token_id: tokenId, + }, + }) + } + allNftInfo = async ({ + includeExpired, + tokenId, + }: { + includeExpired?: boolean + tokenId: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_nft_info: { + include_expired: includeExpired, + token_id: tokenId, + }, + }) + } + tokens = async ({ + limit, + owner, + startAfter, + }: { + limit?: number + owner: string + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + tokens: { + limit, + owner, + start_after: startAfter, + }, + }) + } + allTokens = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_tokens: { + limit, + start_after: startAfter, + }, + }) + } + minter = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + minter: {}, + }) + } +} +export interface AccountNftInterface extends AccountNftReadOnlyInterface { + contractAddress: string + sender: string + proposeNewOwner: ( + { + newOwner, + }: { + newOwner: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + acceptOwnership: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + mint: ( + { + user, + }: { + user: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + transferNft: ( + { + recipient, + tokenId, + }: { + recipient: string + tokenId: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + sendNft: ( + { + contract, + msg, + tokenId, + }: { + contract: string + msg: Binary + tokenId: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + approve: ( + { + expires, + spender, + tokenId, + }: { + expires?: Expiration + spender: string + tokenId: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + revoke: ( + { + spender, + tokenId, + }: { + spender: string + tokenId: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + approveAll: ( + { + expires, + operator, + }: { + expires?: Expiration + operator: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + revokeAll: ( + { + operator, + }: { + operator: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + burn: ( + { + tokenId, + }: { + tokenId: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise +} +export class AccountNftClient extends AccountNftQueryClient implements AccountNftInterface { + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.proposeNewOwner = this.proposeNewOwner.bind(this) + this.acceptOwnership = this.acceptOwnership.bind(this) + this.mint = this.mint.bind(this) + this.transferNft = this.transferNft.bind(this) + this.sendNft = this.sendNft.bind(this) + this.approve = this.approve.bind(this) + this.revoke = this.revoke.bind(this) + this.approveAll = this.approveAll.bind(this) + this.revokeAll = this.revokeAll.bind(this) + this.burn = this.burn.bind(this) + } + + proposeNewOwner = async ( + { + newOwner, + }: { + newOwner: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + propose_new_owner: { + new_owner: newOwner, + }, + }, + fee, + memo, + funds, + ) + } + acceptOwnership = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + accept_ownership: {}, + }, + fee, + memo, + funds, + ) + } + mint = async ( + { + user, + }: { + user: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + mint: { + user, + }, + }, + fee, + memo, + funds, + ) + } + transferNft = async ( + { + recipient, + tokenId, + }: { + recipient: string + tokenId: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + transfer_nft: { + recipient, + token_id: tokenId, + }, + }, + fee, + memo, + funds, + ) + } + sendNft = async ( + { + contract, + msg, + tokenId, + }: { + contract: string + msg: Binary + tokenId: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + send_nft: { + contract, + msg, + token_id: tokenId, + }, + }, + fee, + memo, + funds, + ) + } + approve = async ( + { + expires, + spender, + tokenId, + }: { + expires?: Expiration + spender: string + tokenId: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + approve: { + expires, + spender, + token_id: tokenId, + }, + }, + fee, + memo, + funds, + ) + } + revoke = async ( + { + spender, + tokenId, + }: { + spender: string + tokenId: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + revoke: { + spender, + token_id: tokenId, + }, + }, + fee, + memo, + funds, + ) + } + approveAll = async ( + { + expires, + operator, + }: { + expires?: Expiration + operator: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + approve_all: { + expires, + operator, + }, + }, + fee, + memo, + funds, + ) + } + revokeAll = async ( + { + operator, + }: { + operator: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + revoke_all: { + operator, + }, + }, + fee, + memo, + funds, + ) + } + burn = async ( + { + tokenId, + }: { + tokenId: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + burn: { + token_id: tokenId, + }, + }, + fee, + memo, + funds, + ) + } +} diff --git a/scripts/types/generated/account-nft/AccountNft.react-query.ts b/scripts/types/generated/account-nft/AccountNft.react-query.ts new file mode 100644 index 000000000..3c5f88e73 --- /dev/null +++ b/scripts/types/generated/account-nft/AccountNft.react-query.ts @@ -0,0 +1,535 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee, Coin } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + Binary, + Expiration, + Timestamp, + Uint64, + QueryMsg, + AllNftInfoResponseForEmpty, + OwnerOfResponse, + Approval, + NftInfoResponseForEmpty, + Empty, + OperatorsResponse, + TokensResponse, + ApprovalResponse, + ApprovalsResponse, + ContractInfoResponse, + MinterResponse, + NumTokensResponse, + String, +} from './AccountNft.types' +import { AccountNftQueryClient, AccountNftClient } from './AccountNft.client' +export const accountNftQueryKeys = { + contract: [ + { + contract: 'accountNft', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...accountNftQueryKeys.contract[0], address: contractAddress }] as const, + proposedNewOwner: (contractAddress: string | undefined, args?: Record) => + [ + { ...accountNftQueryKeys.address(contractAddress)[0], method: 'proposed_new_owner', args }, + ] as const, + ownerOf: (contractAddress: string | undefined, args?: Record) => + [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'owner_of', args }] as const, + approval: (contractAddress: string | undefined, args?: Record) => + [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'approval', args }] as const, + approvals: (contractAddress: string | undefined, args?: Record) => + [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'approvals', args }] as const, + allOperators: (contractAddress: string | undefined, args?: Record) => + [ + { ...accountNftQueryKeys.address(contractAddress)[0], method: 'all_operators', args }, + ] as const, + numTokens: (contractAddress: string | undefined, args?: Record) => + [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'num_tokens', args }] as const, + contractInfo: (contractAddress: string | undefined, args?: Record) => + [ + { ...accountNftQueryKeys.address(contractAddress)[0], method: 'contract_info', args }, + ] as const, + nftInfo: (contractAddress: string | undefined, args?: Record) => + [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'nft_info', args }] as const, + allNftInfo: (contractAddress: string | undefined, args?: Record) => + [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'all_nft_info', args }] as const, + tokens: (contractAddress: string | undefined, args?: Record) => + [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'tokens', args }] as const, + allTokens: (contractAddress: string | undefined, args?: Record) => + [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'all_tokens', args }] as const, + minter: (contractAddress: string | undefined, args?: Record) => + [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'minter', args }] as const, +} +export interface AccountNftReactQuery { + client: AccountNftQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface AccountNftMinterQuery extends AccountNftReactQuery {} +export function useAccountNftMinterQuery({ + client, + options, +}: AccountNftMinterQuery) { + return useQuery( + accountNftQueryKeys.minter(client?.contractAddress), + () => (client ? client.minter() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface AccountNftAllTokensQuery + extends AccountNftReactQuery { + args: { + limit?: number + startAfter?: string + } +} +export function useAccountNftAllTokensQuery({ + client, + args, + options, +}: AccountNftAllTokensQuery) { + return useQuery( + accountNftQueryKeys.allTokens(client?.contractAddress, args), + () => + client + ? client.allTokens({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface AccountNftTokensQuery extends AccountNftReactQuery { + args: { + limit?: number + owner: string + startAfter?: string + } +} +export function useAccountNftTokensQuery({ + client, + args, + options, +}: AccountNftTokensQuery) { + return useQuery( + accountNftQueryKeys.tokens(client?.contractAddress, args), + () => + client + ? client.tokens({ + limit: args.limit, + owner: args.owner, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface AccountNftAllNftInfoQuery + extends AccountNftReactQuery { + args: { + includeExpired?: boolean + tokenId: string + } +} +export function useAccountNftAllNftInfoQuery({ + client, + args, + options, +}: AccountNftAllNftInfoQuery) { + return useQuery( + accountNftQueryKeys.allNftInfo(client?.contractAddress, args), + () => + client + ? client.allNftInfo({ + includeExpired: args.includeExpired, + tokenId: args.tokenId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface AccountNftNftInfoQuery + extends AccountNftReactQuery { + args: { + tokenId: string + } +} +export function useAccountNftNftInfoQuery({ + client, + args, + options, +}: AccountNftNftInfoQuery) { + return useQuery( + accountNftQueryKeys.nftInfo(client?.contractAddress, args), + () => + client + ? client.nftInfo({ + tokenId: args.tokenId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface AccountNftContractInfoQuery + extends AccountNftReactQuery {} +export function useAccountNftContractInfoQuery({ + client, + options, +}: AccountNftContractInfoQuery) { + return useQuery( + accountNftQueryKeys.contractInfo(client?.contractAddress), + () => (client ? client.contractInfo() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface AccountNftNumTokensQuery + extends AccountNftReactQuery {} +export function useAccountNftNumTokensQuery({ + client, + options, +}: AccountNftNumTokensQuery) { + return useQuery( + accountNftQueryKeys.numTokens(client?.contractAddress), + () => (client ? client.numTokens() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface AccountNftAllOperatorsQuery + extends AccountNftReactQuery { + args: { + includeExpired?: boolean + limit?: number + owner: string + startAfter?: string + } +} +export function useAccountNftAllOperatorsQuery({ + client, + args, + options, +}: AccountNftAllOperatorsQuery) { + return useQuery( + accountNftQueryKeys.allOperators(client?.contractAddress, args), + () => + client + ? client.allOperators({ + includeExpired: args.includeExpired, + limit: args.limit, + owner: args.owner, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface AccountNftApprovalsQuery + extends AccountNftReactQuery { + args: { + includeExpired?: boolean + tokenId: string + } +} +export function useAccountNftApprovalsQuery({ + client, + args, + options, +}: AccountNftApprovalsQuery) { + return useQuery( + accountNftQueryKeys.approvals(client?.contractAddress, args), + () => + client + ? client.approvals({ + includeExpired: args.includeExpired, + tokenId: args.tokenId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface AccountNftApprovalQuery + extends AccountNftReactQuery { + args: { + includeExpired?: boolean + spender: string + tokenId: string + } +} +export function useAccountNftApprovalQuery({ + client, + args, + options, +}: AccountNftApprovalQuery) { + return useQuery( + accountNftQueryKeys.approval(client?.contractAddress, args), + () => + client + ? client.approval({ + includeExpired: args.includeExpired, + spender: args.spender, + tokenId: args.tokenId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface AccountNftOwnerOfQuery + extends AccountNftReactQuery { + args: { + includeExpired?: boolean + tokenId: string + } +} +export function useAccountNftOwnerOfQuery({ + client, + args, + options, +}: AccountNftOwnerOfQuery) { + return useQuery( + accountNftQueryKeys.ownerOf(client?.contractAddress, args), + () => + client + ? client.ownerOf({ + includeExpired: args.includeExpired, + tokenId: args.tokenId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface AccountNftProposedNewOwnerQuery + extends AccountNftReactQuery {} +export function useAccountNftProposedNewOwnerQuery({ + client, + options, +}: AccountNftProposedNewOwnerQuery) { + return useQuery( + accountNftQueryKeys.proposedNewOwner(client?.contractAddress), + () => (client ? client.proposedNewOwner() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface AccountNftBurnMutation { + client: AccountNftClient + msg: { + tokenId: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useAccountNftBurnMutation( + options?: Omit, 'mutationFn'>, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.burn(msg, fee, memo, funds), + options, + ) +} +export interface AccountNftRevokeAllMutation { + client: AccountNftClient + msg: { + operator: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useAccountNftRevokeAllMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.revokeAll(msg, fee, memo, funds), + options, + ) +} +export interface AccountNftApproveAllMutation { + client: AccountNftClient + msg: { + expires?: Expiration + operator: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useAccountNftApproveAllMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.approveAll(msg, fee, memo, funds), + options, + ) +} +export interface AccountNftRevokeMutation { + client: AccountNftClient + msg: { + spender: string + tokenId: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useAccountNftRevokeMutation( + options?: Omit, 'mutationFn'>, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.revoke(msg, fee, memo, funds), + options, + ) +} +export interface AccountNftApproveMutation { + client: AccountNftClient + msg: { + expires?: Expiration + spender: string + tokenId: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useAccountNftApproveMutation( + options?: Omit, 'mutationFn'>, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.approve(msg, fee, memo, funds), + options, + ) +} +export interface AccountNftSendNftMutation { + client: AccountNftClient + msg: { + contract: string + msg: Binary + tokenId: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useAccountNftSendNftMutation( + options?: Omit, 'mutationFn'>, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.sendNft(msg, fee, memo, funds), + options, + ) +} +export interface AccountNftTransferNftMutation { + client: AccountNftClient + msg: { + recipient: string + tokenId: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useAccountNftTransferNftMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.transferNft(msg, fee, memo, funds), + options, + ) +} +export interface AccountNftMintMutation { + client: AccountNftClient + msg: { + user: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useAccountNftMintMutation( + options?: Omit, 'mutationFn'>, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.mint(msg, fee, memo, funds), + options, + ) +} +export interface AccountNftAcceptOwnershipMutation { + client: AccountNftClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useAccountNftAcceptOwnershipMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, args: { fee, memo, funds } = {} }) => client.acceptOwnership(fee, memo, funds), + options, + ) +} +export interface AccountNftProposeNewOwnerMutation { + client: AccountNftClient + msg: { + newOwner: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useAccountNftProposeNewOwnerMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.proposeNewOwner(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/account-nft/AccountNft.types.ts b/scripts/types/generated/account-nft/AccountNft.types.ts new file mode 100644 index 000000000..332559b3e --- /dev/null +++ b/scripts/types/generated/account-nft/AccountNft.types.ts @@ -0,0 +1,212 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + minter: string + name: string + symbol: string + [k: string]: unknown +} +export type ExecuteMsg = + | { + propose_new_owner: { + new_owner: string + [k: string]: unknown + } + } + | { + accept_ownership: { + [k: string]: unknown + } + } + | { + mint: { + user: string + [k: string]: unknown + } + } + | { + transfer_nft: { + recipient: string + token_id: string + [k: string]: unknown + } + } + | { + send_nft: { + contract: string + msg: Binary + token_id: string + [k: string]: unknown + } + } + | { + approve: { + expires?: Expiration | null + spender: string + token_id: string + [k: string]: unknown + } + } + | { + revoke: { + spender: string + token_id: string + [k: string]: unknown + } + } + | { + approve_all: { + expires?: Expiration | null + operator: string + [k: string]: unknown + } + } + | { + revoke_all: { + operator: string + [k: string]: unknown + } + } + | { + burn: { + token_id: string + [k: string]: unknown + } + } +export type Binary = string +export type Expiration = + | { + at_height: number + } + | { + at_time: Timestamp + } + | { + never: { + [k: string]: unknown + } + } +export type Timestamp = Uint64 +export type Uint64 = string +export type QueryMsg = + | { + proposed_new_owner: {} + } + | { + owner_of: { + include_expired?: boolean | null + token_id: string + } + } + | { + approval: { + include_expired?: boolean | null + spender: string + token_id: string + } + } + | { + approvals: { + include_expired?: boolean | null + token_id: string + } + } + | { + all_operators: { + include_expired?: boolean | null + limit?: number | null + owner: string + start_after?: string | null + } + } + | { + num_tokens: {} + } + | { + contract_info: {} + } + | { + nft_info: { + token_id: string + } + } + | { + all_nft_info: { + include_expired?: boolean | null + token_id: string + } + } + | { + tokens: { + limit?: number | null + owner: string + start_after?: string | null + } + } + | { + all_tokens: { + limit?: number | null + start_after?: string | null + } + } + | { + minter: {} + } +export interface AllNftInfoResponseForEmpty { + access: OwnerOfResponse + info: NftInfoResponseForEmpty + [k: string]: unknown +} +export interface OwnerOfResponse { + approvals: Approval[] + owner: string + [k: string]: unknown +} +export interface Approval { + expires: Expiration + spender: string + [k: string]: unknown +} +export interface NftInfoResponseForEmpty { + extension: Empty + token_uri?: string | null + [k: string]: unknown +} +export interface Empty { + [k: string]: unknown +} +export interface OperatorsResponse { + operators: Approval[] + [k: string]: unknown +} +export interface TokensResponse { + tokens: string[] + [k: string]: unknown +} +export interface ApprovalResponse { + approval: Approval + [k: string]: unknown +} +export interface ApprovalsResponse { + approvals: Approval[] + [k: string]: unknown +} +export interface ContractInfoResponse { + name: string + symbol: string + [k: string]: unknown +} +export interface MinterResponse { + minter: string + [k: string]: unknown +} +export interface NumTokensResponse { + count: number + [k: string]: unknown +} +export type String = string diff --git a/scripts/types/generated/account-nft/bundle.ts b/scripts/types/generated/account-nft/bundle.ts new file mode 100644 index 000000000..9622030ec --- /dev/null +++ b/scripts/types/generated/account-nft/bundle.ts @@ -0,0 +1,13 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _0 from './AccountNft.types' +import * as _1 from './AccountNft.client' +import * as _2 from './AccountNft.react-query' +export namespace contracts { + export const AccountNft = { ..._0, ..._1, ..._2 } +} diff --git a/scripts/types/generated/credit-manager/CreditManager.client.ts b/scripts/types/generated/credit-manager/CreditManager.client.ts new file mode 100644 index 000000000..f542cc0c2 --- /dev/null +++ b/scripts/types/generated/credit-manager/CreditManager.client.ts @@ -0,0 +1,393 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + VaultBaseForString, + Decimal, + OracleBaseForString, + RedBankBaseForString, + SwapperBaseForString, + InstantiateMsg, + ExecuteMsg, + Action, + Uint128, + CallbackMsg, + Addr, + VaultBaseForAddr, + VaultBaseForAddr1, + VaultBaseForAddr2, + VaultBaseForAddr3, + Coin, + ConfigUpdates, + QueryMsg, + ArrayOfCoinBalanceResponseItem, + CoinBalanceResponseItem, + ArrayOfSharesResponseItem, + SharesResponseItem, + ArrayOfDebtShares, + DebtShares, + ArrayOfVaultWithBalance, + VaultWithBalance, + ArrayOfVaultPositionResponseItem, + VaultPositionResponseItem, + VaultPosition, + ArrayOfString, + ArrayOfVaultBaseForString, + ConfigResponse, + HealthResponse, + PositionsWithValueResponse, + CoinValue, + DebtSharesValue, + VaultPositionWithAddr, +} from './CreditManager.types' +export interface CreditManagerReadOnlyInterface { + contractAddress: string + config: () => Promise + allowedVaults: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: VaultBaseForString + }) => Promise + allowedCoins: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }) => Promise + positions: ({ tokenId }: { tokenId: string }) => Promise + health: ({ tokenId }: { tokenId: string }) => Promise + allCoinBalances: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }) => Promise + allDebtShares: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }) => Promise + totalDebtShares: () => Promise + allTotalDebtShares: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }) => Promise + allVaultPositions: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }) => Promise + totalVaultCoinBalance: ({ vault }: { vault: VaultBaseForString }) => Promise + allTotalVaultCoinBalances: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: VaultBaseForString + }) => Promise +} +export class CreditManagerQueryClient implements CreditManagerReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.config = this.config.bind(this) + this.allowedVaults = this.allowedVaults.bind(this) + this.allowedCoins = this.allowedCoins.bind(this) + this.positions = this.positions.bind(this) + this.health = this.health.bind(this) + this.allCoinBalances = this.allCoinBalances.bind(this) + this.allDebtShares = this.allDebtShares.bind(this) + this.totalDebtShares = this.totalDebtShares.bind(this) + this.allTotalDebtShares = this.allTotalDebtShares.bind(this) + this.allVaultPositions = this.allVaultPositions.bind(this) + this.totalVaultCoinBalance = this.totalVaultCoinBalance.bind(this) + this.allTotalVaultCoinBalances = this.allTotalVaultCoinBalances.bind(this) + } + + config = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + config: {}, + }) + } + allowedVaults = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: VaultBaseForString + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + allowed_vaults: { + limit, + start_after: startAfter, + }, + }) + } + allowedCoins = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + allowed_coins: { + limit, + start_after: startAfter, + }, + }) + } + positions = async ({ tokenId }: { tokenId: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + positions: { + token_id: tokenId, + }, + }) + } + health = async ({ tokenId }: { tokenId: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + health: { + token_id: tokenId, + }, + }) + } + allCoinBalances = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_coin_balances: { + limit, + start_after: startAfter, + }, + }) + } + allDebtShares = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_debt_shares: { + limit, + start_after: startAfter, + }, + }) + } + totalDebtShares = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + total_debt_shares: {}, + }) + } + allTotalDebtShares = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_total_debt_shares: { + limit, + start_after: startAfter, + }, + }) + } + allVaultPositions = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_vault_positions: { + limit, + start_after: startAfter, + }, + }) + } + totalVaultCoinBalance = async ({ vault }: { vault: VaultBaseForString }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + total_vault_coin_balance: { + vault, + }, + }) + } + allTotalVaultCoinBalances = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: VaultBaseForString + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_total_vault_coin_balances: { + limit, + start_after: startAfter, + }, + }) + } +} +export interface CreditManagerInterface extends CreditManagerReadOnlyInterface { + contractAddress: string + sender: string + createCreditAccount: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + updateCreditAccount: ( + { + actions, + tokenId, + }: { + actions: Action[] + tokenId: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + updateConfig: ( + { + newConfig, + }: { + newConfig: ConfigUpdates + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + callback: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise +} +export class CreditManagerClient + extends CreditManagerQueryClient + implements CreditManagerInterface +{ + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.createCreditAccount = this.createCreditAccount.bind(this) + this.updateCreditAccount = this.updateCreditAccount.bind(this) + this.updateConfig = this.updateConfig.bind(this) + this.callback = this.callback.bind(this) + } + + createCreditAccount = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + create_credit_account: {}, + }, + fee, + memo, + funds, + ) + } + updateCreditAccount = async ( + { + actions, + tokenId, + }: { + actions: Action[] + tokenId: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_credit_account: { + actions, + token_id: tokenId, + }, + }, + fee, + memo, + funds, + ) + } + updateConfig = async ( + { + newConfig, + }: { + newConfig: ConfigUpdates + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_config: { + new_config: newConfig, + }, + }, + fee, + memo, + funds, + ) + } + callback = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + callback: {}, + }, + fee, + memo, + funds, + ) + } +} diff --git a/scripts/types/generated/credit-manager/CreditManager.react-query.ts b/scripts/types/generated/credit-manager/CreditManager.react-query.ts new file mode 100644 index 000000000..3e1537155 --- /dev/null +++ b/scripts/types/generated/credit-manager/CreditManager.react-query.ts @@ -0,0 +1,473 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + VaultBaseForString, + Decimal, + OracleBaseForString, + RedBankBaseForString, + SwapperBaseForString, + InstantiateMsg, + ExecuteMsg, + Action, + Uint128, + CallbackMsg, + Addr, + VaultBaseForAddr, + VaultBaseForAddr1, + VaultBaseForAddr2, + VaultBaseForAddr3, + Coin, + ConfigUpdates, + QueryMsg, + ArrayOfCoinBalanceResponseItem, + CoinBalanceResponseItem, + ArrayOfSharesResponseItem, + SharesResponseItem, + ArrayOfDebtShares, + DebtShares, + ArrayOfVaultWithBalance, + VaultWithBalance, + ArrayOfVaultPositionResponseItem, + VaultPositionResponseItem, + VaultPosition, + ArrayOfString, + ArrayOfVaultBaseForString, + ConfigResponse, + HealthResponse, + PositionsWithValueResponse, + CoinValue, + DebtSharesValue, + VaultPositionWithAddr, +} from './CreditManager.types' +import { CreditManagerQueryClient, CreditManagerClient } from './CreditManager.client' +export const creditManagerQueryKeys = { + contract: [ + { + contract: 'creditManager', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...creditManagerQueryKeys.contract[0], address: contractAddress }] as const, + config: (contractAddress: string | undefined, args?: Record) => + [{ ...creditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, + allowedVaults: (contractAddress: string | undefined, args?: Record) => + [ + { ...creditManagerQueryKeys.address(contractAddress)[0], method: 'allowed_vaults', args }, + ] as const, + allowedCoins: (contractAddress: string | undefined, args?: Record) => + [ + { ...creditManagerQueryKeys.address(contractAddress)[0], method: 'allowed_coins', args }, + ] as const, + positions: (contractAddress: string | undefined, args?: Record) => + [{ ...creditManagerQueryKeys.address(contractAddress)[0], method: 'positions', args }] as const, + health: (contractAddress: string | undefined, args?: Record) => + [{ ...creditManagerQueryKeys.address(contractAddress)[0], method: 'health', args }] as const, + allCoinBalances: (contractAddress: string | undefined, args?: Record) => + [ + { ...creditManagerQueryKeys.address(contractAddress)[0], method: 'all_coin_balances', args }, + ] as const, + allDebtShares: (contractAddress: string | undefined, args?: Record) => + [ + { ...creditManagerQueryKeys.address(contractAddress)[0], method: 'all_debt_shares', args }, + ] as const, + totalDebtShares: (contractAddress: string | undefined, args?: Record) => + [ + { ...creditManagerQueryKeys.address(contractAddress)[0], method: 'total_debt_shares', args }, + ] as const, + allTotalDebtShares: (contractAddress: string | undefined, args?: Record) => + [ + { + ...creditManagerQueryKeys.address(contractAddress)[0], + method: 'all_total_debt_shares', + args, + }, + ] as const, + allVaultPositions: (contractAddress: string | undefined, args?: Record) => + [ + { + ...creditManagerQueryKeys.address(contractAddress)[0], + method: 'all_vault_positions', + args, + }, + ] as const, + totalVaultCoinBalance: (contractAddress: string | undefined, args?: Record) => + [ + { + ...creditManagerQueryKeys.address(contractAddress)[0], + method: 'total_vault_coin_balance', + args, + }, + ] as const, + allTotalVaultCoinBalances: ( + contractAddress: string | undefined, + args?: Record, + ) => + [ + { + ...creditManagerQueryKeys.address(contractAddress)[0], + method: 'all_total_vault_coin_balances', + args, + }, + ] as const, +} +export interface CreditManagerReactQuery { + client: CreditManagerQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface CreditManagerAllTotalVaultCoinBalancesQuery + extends CreditManagerReactQuery { + args: { + limit?: number + startAfter?: VaultBaseForString + } +} +export function useCreditManagerAllTotalVaultCoinBalancesQuery({ + client, + args, + options, +}: CreditManagerAllTotalVaultCoinBalancesQuery) { + return useQuery( + creditManagerQueryKeys.allTotalVaultCoinBalances(client?.contractAddress, args), + () => + client + ? client.allTotalVaultCoinBalances({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface CreditManagerTotalVaultCoinBalanceQuery + extends CreditManagerReactQuery { + args: { + vault: VaultBaseForString + } +} +export function useCreditManagerTotalVaultCoinBalanceQuery({ + client, + args, + options, +}: CreditManagerTotalVaultCoinBalanceQuery) { + return useQuery( + creditManagerQueryKeys.totalVaultCoinBalance(client?.contractAddress, args), + () => + client + ? client.totalVaultCoinBalance({ + vault: args.vault, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface CreditManagerAllVaultPositionsQuery + extends CreditManagerReactQuery { + args: { + limit?: number + startAfter?: string[][] + } +} +export function useCreditManagerAllVaultPositionsQuery({ + client, + args, + options, +}: CreditManagerAllVaultPositionsQuery) { + return useQuery( + creditManagerQueryKeys.allVaultPositions(client?.contractAddress, args), + () => + client + ? client.allVaultPositions({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface CreditManagerAllTotalDebtSharesQuery + extends CreditManagerReactQuery { + args: { + limit?: number + startAfter?: string + } +} +export function useCreditManagerAllTotalDebtSharesQuery({ + client, + args, + options, +}: CreditManagerAllTotalDebtSharesQuery) { + return useQuery( + creditManagerQueryKeys.allTotalDebtShares(client?.contractAddress, args), + () => + client + ? client.allTotalDebtShares({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface CreditManagerTotalDebtSharesQuery + extends CreditManagerReactQuery {} +export function useCreditManagerTotalDebtSharesQuery({ + client, + options, +}: CreditManagerTotalDebtSharesQuery) { + return useQuery( + creditManagerQueryKeys.totalDebtShares(client?.contractAddress), + () => (client ? client.totalDebtShares() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface CreditManagerAllDebtSharesQuery + extends CreditManagerReactQuery { + args: { + limit?: number + startAfter?: string[][] + } +} +export function useCreditManagerAllDebtSharesQuery({ + client, + args, + options, +}: CreditManagerAllDebtSharesQuery) { + return useQuery( + creditManagerQueryKeys.allDebtShares(client?.contractAddress, args), + () => + client + ? client.allDebtShares({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface CreditManagerAllCoinBalancesQuery + extends CreditManagerReactQuery { + args: { + limit?: number + startAfter?: string[][] + } +} +export function useCreditManagerAllCoinBalancesQuery({ + client, + args, + options, +}: CreditManagerAllCoinBalancesQuery) { + return useQuery( + creditManagerQueryKeys.allCoinBalances(client?.contractAddress, args), + () => + client + ? client.allCoinBalances({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface CreditManagerHealthQuery + extends CreditManagerReactQuery { + args: { + tokenId: string + } +} +export function useCreditManagerHealthQuery({ + client, + args, + options, +}: CreditManagerHealthQuery) { + return useQuery( + creditManagerQueryKeys.health(client?.contractAddress, args), + () => + client + ? client.health({ + tokenId: args.tokenId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface CreditManagerPositionsQuery + extends CreditManagerReactQuery { + args: { + tokenId: string + } +} +export function useCreditManagerPositionsQuery({ + client, + args, + options, +}: CreditManagerPositionsQuery) { + return useQuery( + creditManagerQueryKeys.positions(client?.contractAddress, args), + () => + client + ? client.positions({ + tokenId: args.tokenId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface CreditManagerAllowedCoinsQuery + extends CreditManagerReactQuery { + args: { + limit?: number + startAfter?: string + } +} +export function useCreditManagerAllowedCoinsQuery({ + client, + args, + options, +}: CreditManagerAllowedCoinsQuery) { + return useQuery( + creditManagerQueryKeys.allowedCoins(client?.contractAddress, args), + () => + client + ? client.allowedCoins({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface CreditManagerAllowedVaultsQuery + extends CreditManagerReactQuery { + args: { + limit?: number + startAfter?: VaultBaseForString + } +} +export function useCreditManagerAllowedVaultsQuery({ + client, + args, + options, +}: CreditManagerAllowedVaultsQuery) { + return useQuery( + creditManagerQueryKeys.allowedVaults(client?.contractAddress, args), + () => + client + ? client.allowedVaults({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface CreditManagerConfigQuery + extends CreditManagerReactQuery {} +export function useCreditManagerConfigQuery({ + client, + options, +}: CreditManagerConfigQuery) { + return useQuery( + creditManagerQueryKeys.config(client?.contractAddress), + () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface CreditManagerCallbackMutation { + client: CreditManagerClient + msg: CallbackMsg + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useCreditManagerCallbackMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.callback(msg, fee, memo, funds), + options, + ) +} +export interface CreditManagerUpdateConfigMutation { + client: CreditManagerClient + msg: { + newConfig: ConfigUpdates + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useCreditManagerUpdateConfigMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateConfig(msg, fee, memo, funds), + options, + ) +} +export interface CreditManagerUpdateCreditAccountMutation { + client: CreditManagerClient + msg: { + actions: Action[] + tokenId: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useCreditManagerUpdateCreditAccountMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateCreditAccount(msg, fee, memo, funds), + options, + ) +} +export interface CreditManagerCreateCreditAccountMutation { + client: CreditManagerClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useCreditManagerCreateCreditAccountMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, args: { fee, memo, funds } = {} }) => client.createCreditAccount(fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/credit-manager/CreditManager.types.ts b/scripts/types/generated/credit-manager/CreditManager.types.ts new file mode 100644 index 000000000..6e7e7a35e --- /dev/null +++ b/scripts/types/generated/credit-manager/CreditManager.types.ts @@ -0,0 +1,356 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export type VaultBaseForString = string +export type Decimal = string +export type OracleBaseForString = string +export type RedBankBaseForString = string +export type SwapperBaseForString = string +export interface InstantiateMsg { + allowed_coins: string[] + allowed_vaults: VaultBaseForString[] + max_close_factor: Decimal + max_liquidation_bonus: Decimal + oracle: OracleBaseForString + owner: string + red_bank: RedBankBaseForString + swapper: SwapperBaseForString + [k: string]: unknown +} +export type ExecuteMsg = + | { + create_credit_account: { + [k: string]: unknown + } + } + | { + update_credit_account: { + actions: Action[] + token_id: string + [k: string]: unknown + } + } + | { + update_config: { + new_config: ConfigUpdates + [k: string]: unknown + } + } + | { + callback: CallbackMsg + } +export type Action = + | { + deposit: Coin + } + | { + withdraw: Coin + } + | { + borrow: Coin + } + | { + repay: Coin + } + | { + vault_deposit: { + coins: Coin[] + vault: VaultBaseForString + [k: string]: unknown + } + } + | { + vault_withdraw: { + amount: Uint128 + vault: VaultBaseForString + [k: string]: unknown + } + } + | { + liquidate_coin: { + debt_coin: Coin + liquidatee_token_id: string + request_coin_denom: string + [k: string]: unknown + } + } + | { + swap_exact_in: { + coin_in: Coin + denom_out: string + slippage: Decimal + [k: string]: unknown + } + } +export type Uint128 = string +export type CallbackMsg = + | { + withdraw: { + coin: Coin + recipient: Addr + token_id: string + [k: string]: unknown + } + } + | { + borrow: { + coin: Coin + token_id: string + [k: string]: unknown + } + } + | { + repay: { + coin: Coin + token_id: string + [k: string]: unknown + } + } + | { + assert_below_max_l_t_v: { + token_id: string + [k: string]: unknown + } + } + | { + vault_deposit: { + coins: Coin[] + token_id: string + vault: VaultBaseForAddr + [k: string]: unknown + } + } + | { + update_vault_coin_balance: { + previous_total_balance: Uint128 + token_id: string + vault: VaultBaseForAddr1 + [k: string]: unknown + } + } + | { + vault_withdraw: { + amount: Uint128 + token_id: string + vault: VaultBaseForAddr2 + [k: string]: unknown + } + } + | { + vault_force_withdraw: { + amount: Uint128 + token_id: string + vault: VaultBaseForAddr3 + [k: string]: unknown + } + } + | { + liquidate_coin: { + debt_coin: Coin + liquidatee_token_id: string + liquidator_token_id: string + request_coin_denom: string + [k: string]: unknown + } + } + | { + assert_health_factor_improved: { + previous_health_factor: Decimal + token_id: string + [k: string]: unknown + } + } + | { + swap_exact_in: { + coin_in: Coin + denom_out: string + slippage: Decimal + token_id: string + [k: string]: unknown + } + } + | { + update_coin_balances: { + previous_balances: Coin[] + token_id: string + [k: string]: unknown + } + } +export type Addr = string +export type VaultBaseForAddr = string +export type VaultBaseForAddr1 = string +export type VaultBaseForAddr2 = string +export type VaultBaseForAddr3 = string +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} +export interface ConfigUpdates { + account_nft?: string | null + allowed_coins?: string[] | null + allowed_vaults?: VaultBaseForString[] | null + max_close_factor?: Decimal | null + max_liquidation_bonus?: Decimal | null + oracle?: OracleBaseForString | null + owner?: string | null + red_bank?: RedBankBaseForString | null + swapper?: SwapperBaseForString | null + [k: string]: unknown +} +export type QueryMsg = + | { + config: {} + } + | { + allowed_vaults: { + limit?: number | null + start_after?: VaultBaseForString | null + } + } + | { + allowed_coins: { + limit?: number | null + start_after?: string | null + } + } + | { + positions: { + token_id: string + } + } + | { + health: { + token_id: string + } + } + | { + all_coin_balances: { + limit?: number | null + start_after?: [string, string] | null + } + } + | { + all_debt_shares: { + limit?: number | null + start_after?: [string, string] | null + } + } + | { + total_debt_shares: string + } + | { + all_total_debt_shares: { + limit?: number | null + start_after?: string | null + } + } + | { + all_vault_positions: { + limit?: number | null + start_after?: [string, string] | null + } + } + | { + total_vault_coin_balance: { + vault: VaultBaseForString + } + } + | { + all_total_vault_coin_balances: { + limit?: number | null + start_after?: VaultBaseForString | null + } + } +export type ArrayOfCoinBalanceResponseItem = CoinBalanceResponseItem[] +export interface CoinBalanceResponseItem { + amount: Uint128 + denom: string + token_id: string + [k: string]: unknown +} +export type ArrayOfSharesResponseItem = SharesResponseItem[] +export interface SharesResponseItem { + denom: string + shares: Uint128 + token_id: string + [k: string]: unknown +} +export type ArrayOfDebtShares = DebtShares[] +export interface DebtShares { + denom: string + shares: Uint128 + [k: string]: unknown +} +export type ArrayOfVaultWithBalance = VaultWithBalance[] +export interface VaultWithBalance { + balance: Uint128 + vault: VaultBaseForString + [k: string]: unknown +} +export type ArrayOfVaultPositionResponseItem = VaultPositionResponseItem[] +export interface VaultPositionResponseItem { + addr: string + token_id: string + vault_position: VaultPosition + [k: string]: unknown +} +export interface VaultPosition { + locked: Uint128 + unlocked: Uint128 + [k: string]: unknown +} +export type ArrayOfString = string[] +export type ArrayOfVaultBaseForString = VaultBaseForString[] +export interface ConfigResponse { + account_nft?: string | null + max_close_factor: Decimal + max_liquidation_bonus: Decimal + oracle: string + owner: string + red_bank: string + swapper: string + [k: string]: unknown +} +export interface HealthResponse { + above_max_ltv: boolean + liquidatable: boolean + liquidation_health_factor?: Decimal | null + liquidation_threshold_adjusted_collateral: Decimal + max_ltv_adjusted_collateral: Decimal + max_ltv_health_factor?: Decimal | null + total_collateral_value: Decimal + total_debt_value: Decimal + [k: string]: unknown +} +export interface PositionsWithValueResponse { + coins: CoinValue[] + debt: DebtSharesValue[] + token_id: string + vault_positions: VaultPositionWithAddr[] + [k: string]: unknown +} +export interface CoinValue { + amount: Uint128 + denom: string + price: Decimal + value: Decimal + [k: string]: unknown +} +export interface DebtSharesValue { + amount: Uint128 + denom: string + price: Decimal + shares: Uint128 + value: Decimal + [k: string]: unknown +} +export interface VaultPositionWithAddr { + addr: string + position: VaultPosition + [k: string]: unknown +} diff --git a/scripts/types/generated/credit-manager/bundle.ts b/scripts/types/generated/credit-manager/bundle.ts new file mode 100644 index 000000000..abc245fb8 --- /dev/null +++ b/scripts/types/generated/credit-manager/bundle.ts @@ -0,0 +1,13 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _3 from './CreditManager.types' +import * as _4 from './CreditManager.client' +import * as _5 from './CreditManager.react-query' +export namespace contracts { + export const CreditManager = { ..._3, ..._4, ..._5 } +} diff --git a/scripts/types/generated/mock-oracle/MockOracle.client.ts b/scripts/types/generated/mock-oracle/MockOracle.client.ts new file mode 100644 index 000000000..1ac3024a9 --- /dev/null +++ b/scripts/types/generated/mock-oracle/MockOracle.client.ts @@ -0,0 +1,95 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { Coin, StdFee } from '@cosmjs/amino' +import { + Decimal, + InstantiateMsg, + CoinPrice, + ExecuteMsg, + QueryMsg, + PriceResponse, +} from './MockOracle.types' +export interface MockOracleReadOnlyInterface { + contractAddress: string + price: ({ denom }: { denom: string }) => Promise +} +export class MockOracleQueryClient implements MockOracleReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.price = this.price.bind(this) + } + + price = async ({ denom }: { denom: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + price: { + denom, + }, + }) + } +} +export interface MockOracleInterface extends MockOracleReadOnlyInterface { + contractAddress: string + sender: string + changePrice: ( + { + denom, + price, + }: { + denom: string + price: Decimal + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise +} +export class MockOracleClient extends MockOracleQueryClient implements MockOracleInterface { + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.changePrice = this.changePrice.bind(this) + } + + changePrice = async ( + { + denom, + price, + }: { + denom: string + price: Decimal + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + change_price: { + denom, + price, + }, + }, + fee, + memo, + funds, + ) + } +} diff --git a/scripts/types/generated/mock-oracle/MockOracle.react-query.ts b/scripts/types/generated/mock-oracle/MockOracle.react-query.ts new file mode 100644 index 000000000..740a328fc --- /dev/null +++ b/scripts/types/generated/mock-oracle/MockOracle.react-query.ts @@ -0,0 +1,80 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee, Coin } from '@cosmjs/amino' +import { + Decimal, + InstantiateMsg, + CoinPrice, + ExecuteMsg, + QueryMsg, + PriceResponse, +} from './MockOracle.types' +import { MockOracleQueryClient, MockOracleClient } from './MockOracle.client' +export const mockOracleQueryKeys = { + contract: [ + { + contract: 'mockOracle', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...mockOracleQueryKeys.contract[0], address: contractAddress }] as const, + price: (contractAddress: string | undefined, args?: Record) => + [{ ...mockOracleQueryKeys.address(contractAddress)[0], method: 'price', args }] as const, +} +export interface MockOracleReactQuery { + client: MockOracleQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MockOraclePriceQuery extends MockOracleReactQuery { + args: { + denom: string + } +} +export function useMockOraclePriceQuery({ + client, + args, + options, +}: MockOraclePriceQuery) { + return useQuery( + mockOracleQueryKeys.price(client?.contractAddress, args), + () => + client + ? client.price({ + denom: args.denom, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockOracleChangePriceMutation { + client: MockOracleClient + msg: CoinPrice + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockOracleChangePriceMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.changePrice(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mock-oracle/MockOracle.types.ts b/scripts/types/generated/mock-oracle/MockOracle.types.ts new file mode 100644 index 000000000..7a9d5d195 --- /dev/null +++ b/scripts/types/generated/mock-oracle/MockOracle.types.ts @@ -0,0 +1,30 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export type Decimal = string +export interface InstantiateMsg { + coins: CoinPrice[] + [k: string]: unknown +} +export interface CoinPrice { + denom: string + price: Decimal + [k: string]: unknown +} +export type ExecuteMsg = { + change_price: CoinPrice +} +export type QueryMsg = { + price: { + denom: string + } +} +export interface PriceResponse { + denom: string + price: Decimal + [k: string]: unknown +} diff --git a/scripts/types/generated/mock-oracle/bundle.ts b/scripts/types/generated/mock-oracle/bundle.ts new file mode 100644 index 000000000..fa20c3b1d --- /dev/null +++ b/scripts/types/generated/mock-oracle/bundle.ts @@ -0,0 +1,13 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _6 from './MockOracle.types' +import * as _7 from './MockOracle.client' +import * as _8 from './MockOracle.react-query' +export namespace contracts { + export const MockOracle = { ..._6, ..._7, ..._8 } +} diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.client.ts b/scripts/types/generated/mock-red-bank/MockRedBank.client.ts new file mode 100644 index 000000000..23b31d02d --- /dev/null +++ b/scripts/types/generated/mock-red-bank/MockRedBank.client.ts @@ -0,0 +1,161 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + Decimal, + InstantiateMsg, + CoinMarketInfo, + ExecuteMsg, + Uint128, + Coin, + QueryMsg, + Addr, + Market, + InterestRateModel, + UserAssetDebtResponse, +} from './MockRedBank.types' +export interface MockRedBankReadOnlyInterface { + contractAddress: string + userAssetDebt: ({ + denom, + userAddress, + }: { + denom: string + userAddress: string + }) => Promise + market: ({ denom }: { denom: string }) => Promise +} +export class MockRedBankQueryClient implements MockRedBankReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.userAssetDebt = this.userAssetDebt.bind(this) + this.market = this.market.bind(this) + } + + userAssetDebt = async ({ + denom, + userAddress, + }: { + denom: string + userAddress: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + user_asset_debt: { + denom, + user_address: userAddress, + }, + }) + } + market = async ({ denom }: { denom: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + market: { + denom, + }, + }) + } +} +export interface MockRedBankInterface extends MockRedBankReadOnlyInterface { + contractAddress: string + sender: string + borrow: ( + { + coin, + recipient, + }: { + coin: Coin + recipient?: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + repay: ( + { + denom, + onBehalfOf, + }: { + denom: string + onBehalfOf?: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise +} +export class MockRedBankClient extends MockRedBankQueryClient implements MockRedBankInterface { + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.borrow = this.borrow.bind(this) + this.repay = this.repay.bind(this) + } + + borrow = async ( + { + coin, + recipient, + }: { + coin: Coin + recipient?: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + borrow: { + coin, + recipient, + }, + }, + fee, + memo, + funds, + ) + } + repay = async ( + { + denom, + onBehalfOf, + }: { + denom: string + onBehalfOf?: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + repay: { + denom, + on_behalf_of: onBehalfOf, + }, + }, + fee, + memo, + funds, + ) + } +} diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts b/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts new file mode 100644 index 000000000..93a50d5f2 --- /dev/null +++ b/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts @@ -0,0 +1,133 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + Decimal, + InstantiateMsg, + CoinMarketInfo, + ExecuteMsg, + Uint128, + Coin, + QueryMsg, + Addr, + Market, + InterestRateModel, + UserAssetDebtResponse, +} from './MockRedBank.types' +import { MockRedBankQueryClient, MockRedBankClient } from './MockRedBank.client' +export const mockRedBankQueryKeys = { + contract: [ + { + contract: 'mockRedBank', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...mockRedBankQueryKeys.contract[0], address: contractAddress }] as const, + userAssetDebt: (contractAddress: string | undefined, args?: Record) => + [ + { ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'user_asset_debt', args }, + ] as const, + market: (contractAddress: string | undefined, args?: Record) => + [{ ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'market', args }] as const, +} +export interface MockRedBankReactQuery { + client: MockRedBankQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MockRedBankMarketQuery extends MockRedBankReactQuery { + args: { + denom: string + } +} +export function useMockRedBankMarketQuery({ + client, + args, + options, +}: MockRedBankMarketQuery) { + return useQuery( + mockRedBankQueryKeys.market(client?.contractAddress, args), + () => + client + ? client.market({ + denom: args.denom, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankUserAssetDebtQuery + extends MockRedBankReactQuery { + args: { + denom: string + userAddress: string + } +} +export function useMockRedBankUserAssetDebtQuery({ + client, + args, + options, +}: MockRedBankUserAssetDebtQuery) { + return useQuery( + mockRedBankQueryKeys.userAssetDebt(client?.contractAddress, args), + () => + client + ? client.userAssetDebt({ + denom: args.denom, + userAddress: args.userAddress, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankRepayMutation { + client: MockRedBankClient + msg: { + denom: string + onBehalfOf?: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockRedBankRepayMutation( + options?: Omit, 'mutationFn'>, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.repay(msg, fee, memo, funds), + options, + ) +} +export interface MockRedBankBorrowMutation { + client: MockRedBankClient + msg: { + coin: Coin + recipient?: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockRedBankBorrowMutation( + options?: Omit, 'mutationFn'>, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.borrow(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.types.ts b/scripts/types/generated/mock-red-bank/MockRedBank.types.ts new file mode 100644 index 000000000..f2dfbc20e --- /dev/null +++ b/scripts/types/generated/mock-red-bank/MockRedBank.types.ts @@ -0,0 +1,83 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export type Decimal = string +export interface InstantiateMsg { + coins: CoinMarketInfo[] + [k: string]: unknown +} +export interface CoinMarketInfo { + denom: string + liquidation_threshold: Decimal + max_ltv: Decimal + [k: string]: unknown +} +export type ExecuteMsg = + | { + borrow: { + coin: Coin + recipient?: string | null + [k: string]: unknown + } + } + | { + repay: { + denom: string + on_behalf_of?: string | null + [k: string]: unknown + } + } +export type Uint128 = string +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} +export type QueryMsg = + | { + user_asset_debt: { + denom: string + user_address: string + } + } + | { + market: { + denom: string + } + } +export type Addr = string +export interface Market { + borrow_enabled: boolean + borrow_index: Decimal + borrow_rate: Decimal + debt_total_scaled: Uint128 + denom: string + deposit_cap: Uint128 + deposit_enabled: boolean + indexes_last_updated: number + interest_rate_model: InterestRateModel + liquidation_bonus: Decimal + liquidation_threshold: Decimal + liquidity_index: Decimal + liquidity_rate: Decimal + ma_token_address: Addr + max_loan_to_value: Decimal + reserve_factor: Decimal + [k: string]: unknown +} +export interface InterestRateModel { + base: Decimal + optimal_utilization_rate: Decimal + slope_1: Decimal + slope_2: Decimal + [k: string]: unknown +} +export interface UserAssetDebtResponse { + amount: Uint128 + denom: string + [k: string]: unknown +} diff --git a/scripts/types/generated/mock-red-bank/bundle.ts b/scripts/types/generated/mock-red-bank/bundle.ts new file mode 100644 index 000000000..321b4a2fc --- /dev/null +++ b/scripts/types/generated/mock-red-bank/bundle.ts @@ -0,0 +1,13 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _9 from './MockRedBank.types' +import * as _10 from './MockRedBank.client' +import * as _11 from './MockRedBank.react-query' +export namespace contracts { + export const MockRedBank = { ..._9, ..._10, ..._11 } +} diff --git a/scripts/types/generated/mock-vault/MockVault.client.ts b/scripts/types/generated/mock-vault/MockVault.client.ts new file mode 100644 index 000000000..947787997 --- /dev/null +++ b/scripts/types/generated/mock-vault/MockVault.client.ts @@ -0,0 +1,127 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + OracleBaseForString, + InstantiateMsg, + ExecuteMsg, + QueryMsg, + Uint128, + VaultInfo, + Coin, + ArrayOfCoin, +} from './MockVault.types' +export interface MockVaultReadOnlyInterface { + contractAddress: string + info: () => Promise + previewRedeem: ({ shares }: { shares: Uint128 }) => Promise +} +export class MockVaultQueryClient implements MockVaultReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.info = this.info.bind(this) + this.previewRedeem = this.previewRedeem.bind(this) + } + + info = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + info: {}, + }) + } + previewRedeem = async ({ shares }: { shares: Uint128 }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + preview_redeem: { + shares, + }, + }) + } +} +export interface MockVaultInterface extends MockVaultReadOnlyInterface { + contractAddress: string + sender: string + deposit: (fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[]) => Promise + withdraw: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + forceWithdraw: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise +} +export class MockVaultClient extends MockVaultQueryClient implements MockVaultInterface { + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.deposit = this.deposit.bind(this) + this.withdraw = this.withdraw.bind(this) + this.forceWithdraw = this.forceWithdraw.bind(this) + } + + deposit = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + deposit: {}, + }, + fee, + memo, + funds, + ) + } + withdraw = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + withdraw: {}, + }, + fee, + memo, + funds, + ) + } + forceWithdraw = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + force_withdraw: {}, + }, + fee, + memo, + funds, + ) + } +} diff --git a/scripts/types/generated/mock-vault/MockVault.react-query.ts b/scripts/types/generated/mock-vault/MockVault.react-query.ts new file mode 100644 index 000000000..82e2ff5e9 --- /dev/null +++ b/scripts/types/generated/mock-vault/MockVault.react-query.ts @@ -0,0 +1,129 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + OracleBaseForString, + InstantiateMsg, + ExecuteMsg, + QueryMsg, + Uint128, + VaultInfo, + Coin, + ArrayOfCoin, +} from './MockVault.types' +import { MockVaultQueryClient, MockVaultClient } from './MockVault.client' +export const mockVaultQueryKeys = { + contract: [ + { + contract: 'mockVault', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...mockVaultQueryKeys.contract[0], address: contractAddress }] as const, + info: (contractAddress: string | undefined, args?: Record) => + [{ ...mockVaultQueryKeys.address(contractAddress)[0], method: 'info', args }] as const, + previewRedeem: (contractAddress: string | undefined, args?: Record) => + [ + { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'preview_redeem', args }, + ] as const, +} +export interface MockVaultReactQuery { + client: MockVaultQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MockVaultPreviewRedeemQuery + extends MockVaultReactQuery { + args: { + shares: Uint128 + } +} +export function useMockVaultPreviewRedeemQuery({ + client, + args, + options, +}: MockVaultPreviewRedeemQuery) { + return useQuery( + mockVaultQueryKeys.previewRedeem(client?.contractAddress, args), + () => + client + ? client.previewRedeem({ + shares: args.shares, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockVaultInfoQuery extends MockVaultReactQuery {} +export function useMockVaultInfoQuery({ + client, + options, +}: MockVaultInfoQuery) { + return useQuery( + mockVaultQueryKeys.info(client?.contractAddress), + () => (client ? client.info() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockVaultForceWithdrawMutation { + client: MockVaultClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockVaultForceWithdrawMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, args: { fee, memo, funds } = {} }) => client.forceWithdraw(fee, memo, funds), + options, + ) +} +export interface MockVaultWithdrawMutation { + client: MockVaultClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockVaultWithdrawMutation( + options?: Omit, 'mutationFn'>, +) { + return useMutation( + ({ client, args: { fee, memo, funds } = {} }) => client.withdraw(fee, memo, funds), + options, + ) +} +export interface MockVaultDepositMutation { + client: MockVaultClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockVaultDepositMutation( + options?: Omit, 'mutationFn'>, +) { + return useMutation( + ({ client, args: { fee, memo, funds } = {} }) => client.deposit(fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mock-vault/MockVault.types.ts b/scripts/types/generated/mock-vault/MockVault.types.ts new file mode 100644 index 000000000..0f0463939 --- /dev/null +++ b/scripts/types/generated/mock-vault/MockVault.types.ts @@ -0,0 +1,53 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export type OracleBaseForString = string +export interface InstantiateMsg { + asset_denoms: string[] + lockup?: number | null + lp_token_denom: string + oracle: OracleBaseForString + [k: string]: unknown +} +export type ExecuteMsg = + | { + deposit: { + [k: string]: unknown + } + } + | { + withdraw: { + [k: string]: unknown + } + } + | { + force_withdraw: { + [k: string]: unknown + } + } +export type QueryMsg = + | { + info: {} + } + | { + preview_redeem: { + shares: Uint128 + } + } +export type Uint128 = string +export interface VaultInfo { + coins: Coin[] + lockup?: number | null + token_denom: string + [k: string]: unknown +} +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} +export type ArrayOfCoin = Coin[] diff --git a/scripts/types/generated/mock-vault/bundle.ts b/scripts/types/generated/mock-vault/bundle.ts new file mode 100644 index 000000000..e8f7a9b68 --- /dev/null +++ b/scripts/types/generated/mock-vault/bundle.ts @@ -0,0 +1,13 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _12 from './MockVault.types' +import * as _13 from './MockVault.client' +import * as _14 from './MockVault.react-query' +export namespace contracts { + export const MockVault = { ..._12, ..._13, ..._14 } +} diff --git a/scripts/types/generated/swapper-base/SwapperBase.client.ts b/scripts/types/generated/swapper-base/SwapperBase.client.ts new file mode 100644 index 000000000..5c6a9ddda --- /dev/null +++ b/scripts/types/generated/swapper-base/SwapperBase.client.ts @@ -0,0 +1,292 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + Uint128, + Decimal, + Addr, + Empty, + Coin, + QueryMsg, + ConfigForString, + EstimateExactInSwapResponse, + RouteResponseForEmpty, + ArrayOfRouteResponseForEmpty, +} from './SwapperBase.types' +export interface SwapperBaseReadOnlyInterface { + contractAddress: string + config: () => Promise + route: ({ + denomIn, + denomOut, + }: { + denomIn: string + denomOut: string + }) => Promise + routes: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }) => Promise + estimateExactInSwap: ({ + coinIn, + denomOut, + }: { + coinIn: Coin + denomOut: string + }) => Promise +} +export class SwapperBaseQueryClient implements SwapperBaseReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.config = this.config.bind(this) + this.route = this.route.bind(this) + this.routes = this.routes.bind(this) + this.estimateExactInSwap = this.estimateExactInSwap.bind(this) + } + + config = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + config: {}, + }) + } + route = async ({ + denomIn, + denomOut, + }: { + denomIn: string + denomOut: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + route: { + denom_in: denomIn, + denom_out: denomOut, + }, + }) + } + routes = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + routes: { + limit, + start_after: startAfter, + }, + }) + } + estimateExactInSwap = async ({ + coinIn, + denomOut, + }: { + coinIn: Coin + denomOut: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + estimate_exact_in_swap: { + coin_in: coinIn, + denom_out: denomOut, + }, + }) + } +} +export interface SwapperBaseInterface extends SwapperBaseReadOnlyInterface { + contractAddress: string + sender: string + updateConfig: ( + { + owner, + }: { + owner?: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + setRoute: ( + { + denomIn, + denomOut, + route, + }: { + denomIn: string + denomOut: string + route: Empty + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + swapExactIn: ( + { + coinIn, + denomOut, + slippage, + }: { + coinIn: Coin + denomOut: string + slippage: Decimal + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + transferResult: ( + { + denomIn, + denomOut, + recipient, + }: { + denomIn: string + denomOut: string + recipient: Addr + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise +} +export class SwapperBaseClient extends SwapperBaseQueryClient implements SwapperBaseInterface { + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.updateConfig = this.updateConfig.bind(this) + this.setRoute = this.setRoute.bind(this) + this.swapExactIn = this.swapExactIn.bind(this) + this.transferResult = this.transferResult.bind(this) + } + + updateConfig = async ( + { + owner, + }: { + owner?: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_config: { + owner, + }, + }, + fee, + memo, + funds, + ) + } + setRoute = async ( + { + denomIn, + denomOut, + route, + }: { + denomIn: string + denomOut: string + route: Empty + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + set_route: { + denom_in: denomIn, + denom_out: denomOut, + route, + }, + }, + fee, + memo, + funds, + ) + } + swapExactIn = async ( + { + coinIn, + denomOut, + slippage, + }: { + coinIn: Coin + denomOut: string + slippage: Decimal + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + swap_exact_in: { + coin_in: coinIn, + denom_out: denomOut, + slippage, + }, + }, + fee, + memo, + funds, + ) + } + transferResult = async ( + { + denomIn, + denomOut, + recipient, + }: { + denomIn: string + denomOut: string + recipient: Addr + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + transfer_result: { + denom_in: denomIn, + denom_out: denomOut, + recipient, + }, + }, + fee, + memo, + funds, + ) + } +} diff --git a/scripts/types/generated/swapper-base/SwapperBase.react-query.ts b/scripts/types/generated/swapper-base/SwapperBase.react-query.ts new file mode 100644 index 000000000..1619c9218 --- /dev/null +++ b/scripts/types/generated/swapper-base/SwapperBase.react-query.ts @@ -0,0 +1,237 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + Uint128, + Decimal, + Addr, + Empty, + Coin, + QueryMsg, + ConfigForString, + EstimateExactInSwapResponse, + RouteResponseForEmpty, + ArrayOfRouteResponseForEmpty, +} from './SwapperBase.types' +import { SwapperBaseQueryClient, SwapperBaseClient } from './SwapperBase.client' +export const swapperBaseQueryKeys = { + contract: [ + { + contract: 'swapperBase', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...swapperBaseQueryKeys.contract[0], address: contractAddress }] as const, + config: (contractAddress: string | undefined, args?: Record) => + [{ ...swapperBaseQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, + route: (contractAddress: string | undefined, args?: Record) => + [{ ...swapperBaseQueryKeys.address(contractAddress)[0], method: 'route', args }] as const, + routes: (contractAddress: string | undefined, args?: Record) => + [{ ...swapperBaseQueryKeys.address(contractAddress)[0], method: 'routes', args }] as const, + estimateExactInSwap: (contractAddress: string | undefined, args?: Record) => + [ + { + ...swapperBaseQueryKeys.address(contractAddress)[0], + method: 'estimate_exact_in_swap', + args, + }, + ] as const, +} +export interface SwapperBaseReactQuery { + client: SwapperBaseQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface SwapperBaseEstimateExactInSwapQuery + extends SwapperBaseReactQuery { + args: { + coinIn: Coin + denomOut: string + } +} +export function useSwapperBaseEstimateExactInSwapQuery({ + client, + args, + options, +}: SwapperBaseEstimateExactInSwapQuery) { + return useQuery( + swapperBaseQueryKeys.estimateExactInSwap(client?.contractAddress, args), + () => + client + ? client.estimateExactInSwap({ + coinIn: args.coinIn, + denomOut: args.denomOut, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface SwapperBaseRoutesQuery + extends SwapperBaseReactQuery { + args: { + limit?: number + startAfter?: string[][] + } +} +export function useSwapperBaseRoutesQuery({ + client, + args, + options, +}: SwapperBaseRoutesQuery) { + return useQuery( + swapperBaseQueryKeys.routes(client?.contractAddress, args), + () => + client + ? client.routes({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface SwapperBaseRouteQuery + extends SwapperBaseReactQuery { + args: { + denomIn: string + denomOut: string + } +} +export function useSwapperBaseRouteQuery({ + client, + args, + options, +}: SwapperBaseRouteQuery) { + return useQuery( + swapperBaseQueryKeys.route(client?.contractAddress, args), + () => + client + ? client.route({ + denomIn: args.denomIn, + denomOut: args.denomOut, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface SwapperBaseConfigQuery + extends SwapperBaseReactQuery {} +export function useSwapperBaseConfigQuery({ + client, + options, +}: SwapperBaseConfigQuery) { + return useQuery( + swapperBaseQueryKeys.config(client?.contractAddress), + () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface SwapperBaseTransferResultMutation { + client: SwapperBaseClient + msg: { + denomIn: string + denomOut: string + recipient: Addr + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useSwapperBaseTransferResultMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.transferResult(msg, fee, memo, funds), + options, + ) +} +export interface SwapperBaseSwapExactInMutation { + client: SwapperBaseClient + msg: { + coinIn: Coin + denomOut: string + slippage: Decimal + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useSwapperBaseSwapExactInMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.swapExactIn(msg, fee, memo, funds), + options, + ) +} +export interface SwapperBaseSetRouteMutation { + client: SwapperBaseClient + msg: { + denomIn: string + denomOut: string + route: Empty + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useSwapperBaseSetRouteMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.setRoute(msg, fee, memo, funds), + options, + ) +} +export interface SwapperBaseUpdateConfigMutation { + client: SwapperBaseClient + msg: { + owner?: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useSwapperBaseUpdateConfigMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateConfig(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/swapper-base/SwapperBase.types.ts b/scripts/types/generated/swapper-base/SwapperBase.types.ts new file mode 100644 index 000000000..93d8fdd21 --- /dev/null +++ b/scripts/types/generated/swapper-base/SwapperBase.types.ts @@ -0,0 +1,90 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + owner: string + [k: string]: unknown +} +export type ExecuteMsg = + | { + update_config: { + owner?: string | null + [k: string]: unknown + } + } + | { + set_route: { + denom_in: string + denom_out: string + route: Empty + [k: string]: unknown + } + } + | { + swap_exact_in: { + coin_in: Coin + denom_out: string + slippage: Decimal + [k: string]: unknown + } + } + | { + transfer_result: { + denom_in: string + denom_out: string + recipient: Addr + [k: string]: unknown + } + } +export type Uint128 = string +export type Decimal = string +export type Addr = string +export interface Empty { + [k: string]: unknown +} +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} +export type QueryMsg = + | { + config: {} + } + | { + route: { + denom_in: string + denom_out: string + } + } + | { + routes: { + limit?: number | null + start_after?: [string, string] | null + } + } + | { + estimate_exact_in_swap: { + coin_in: Coin + denom_out: string + } + } +export interface ConfigForString { + owner: string + [k: string]: unknown +} +export interface EstimateExactInSwapResponse { + amount: Uint128 + [k: string]: unknown +} +export interface RouteResponseForEmpty { + denom_in: string + denom_out: string + route: Empty + [k: string]: unknown +} +export type ArrayOfRouteResponseForEmpty = RouteResponseForEmpty[] diff --git a/scripts/types/generated/swapper-base/bundle.ts b/scripts/types/generated/swapper-base/bundle.ts new file mode 100644 index 000000000..a625602d6 --- /dev/null +++ b/scripts/types/generated/swapper-base/bundle.ts @@ -0,0 +1,13 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _15 from './SwapperBase.types' +import * as _16 from './SwapperBase.client' +import * as _17 from './SwapperBase.react-query' +export namespace contracts { + export const SwapperBase = { ..._15, ..._16, ..._17 } +} diff --git a/scripts/types/instantiateMsgs.ts b/scripts/types/instantiateMsgs.ts new file mode 100644 index 000000000..f52477d16 --- /dev/null +++ b/scripts/types/instantiateMsgs.ts @@ -0,0 +1,14 @@ +import { InstantiateMsg as NftInstantiateMsg } from './generated/account-nft/AccountNft.types' +import { InstantiateMsg as RedBankInstantiateMsg } from './generated/mock-red-bank/MockRedBank.types' +import { InstantiateMsg as VaultInstantiateMsg } from './generated/mock-vault/MockVault.types' +import { InstantiateMsg as OracleInstantiateMsg } from './generated/mock-oracle/MockOracle.types' +import { InstantiateMsg as RoverInstantiateMsg } from './generated/credit-manager/CreditManager.types' +import { InstantiateMsg as SwapperInstantiateMsg } from './generated/swapper-base/SwapperBase.types' + +export type InstantiateMsgs = + | NftInstantiateMsg + | RedBankInstantiateMsg + | VaultInstantiateMsg + | OracleInstantiateMsg + | RoverInstantiateMsg + | SwapperInstantiateMsg diff --git a/scripts/types/storageItems.ts b/scripts/types/storageItems.ts new file mode 100644 index 000000000..5f62605d1 --- /dev/null +++ b/scripts/types/storageItems.ts @@ -0,0 +1,22 @@ +export interface StorageItems { + codeIds: { + accountNft?: number + mockRedBank?: number + mockVault?: number + mockOracle?: number + swapper?: number + creditManager?: number + } + addresses: { + accountNft?: string + mockRedBank?: string + mockVault?: string + mockOracle?: string + swapper?: string + creditManager?: string + } + actions: { + proposedNewOwner?: boolean + acceptedOwnership?: boolean + } +} diff --git a/scripts/utils/chalk.ts b/scripts/utils/chalk.ts new file mode 100644 index 000000000..d03b00175 --- /dev/null +++ b/scripts/utils/chalk.ts @@ -0,0 +1,21 @@ +import chalk from 'chalk' + +export const printRed = (text: unknown) => { + console.log(chalk.red(text)) +} + +export const printBlue = (text: string) => { + console.log(chalk.blue(text)) +} + +export const printGreen = (text: string) => { + console.log(chalk.green(text)) +} + +export const printYellow = (text: string) => { + console.log(chalk.yellow(text)) +} + +export const printGray = (text: string) => { + console.log(chalk.gray(text)) +} diff --git a/scripts/utils/client.ts b/scripts/utils/client.ts deleted file mode 100644 index 9194a355f..000000000 --- a/scripts/utils/client.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; -import { Network, networks } from './config'; -import { walletDataType } from './test-wallets'; -import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; - -type ClientGetter = (wallet: walletDataType) => Promise; - -export const getCosmWasmClient: ClientGetter = async (wallet) => { - const signer = await DirectSecp256k1HdWallet.fromMnemonic(wallet.mnemonic, { - prefix: networks[Network.OSMOSIS].bech32Prefix, - }); - return await SigningCosmWasmClient.connectWithSigner(networks[Network.OSMOSIS].localRpcEndpoint, signer); -}; diff --git a/scripts/utils/config.ts b/scripts/utils/config.ts deleted file mode 100644 index eed23c656..000000000 --- a/scripts/utils/config.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { calculateFee, GasPrice } from '@cosmjs/stargate'; -import { StdFee } from '@cosmjs/amino'; - -export type NetworkConfig = { - localRpcEndpoint: string; - provider: string; - transactionLink: (arg0: string) => string; - walletLink: (arg0: string) => string; - networkName: string; - bech32Prefix: string; - nativeDenom: string; - defaultSendFee: StdFee; -}; - -export enum Network { - OSMOSIS, -} - -export const networks: Record = { - [Network.OSMOSIS]: { - localRpcEndpoint: 'tcp://localhost:26657', - provider: 'https://rpc-osmosis.keplr.app/', - transactionLink: (hash) => `https://www.mintscan.io/osmosis/txs/${hash}`, - walletLink: (address) => `https://www.mintscan.io/osmosis/account/${address}`, - networkName: 'osmosis', - bech32Prefix: 'osmo', - nativeDenom: 'uosmo', - defaultSendFee: calculateFee(3_000_000, GasPrice.fromString('0.025uosmo')), - }, -}; diff --git a/scripts/utils/environment.ts b/scripts/utils/environment.ts new file mode 100644 index 000000000..8baf38452 --- /dev/null +++ b/scripts/utils/environment.ts @@ -0,0 +1,12 @@ +import os from 'os' + +// for m1 macs, the binaries should look like: rover-aarch64.wasm vs rover.wasm +export const wasmFile = (contractName: string) => { + let fileStr = contractName + const env = os.arch() + if (env === 'arm64') { + fileStr += '-aarch64' + } + fileStr += '.wasm' + return fileStr +} diff --git a/scripts/utils/test-wallets.ts b/scripts/utils/test-wallets.ts deleted file mode 100644 index 2463068ad..000000000 --- a/scripts/utils/test-wallets.ts +++ /dev/null @@ -1,92 +0,0 @@ -// Taken from mnemonics.json in LocalOsmosis repo -// All test users have uion & uosmo balances - -export interface walletDataType { - address: string; - name: string; - mnemonic: string; - pubkey: { '@type': string; key: string }; -} - -const walletData: walletDataType[] = [ - { - name: 'validator', - address: 'osmo1phaxpevm5wecex2jyaqty2a4v02qj7qmlmzk5a', - pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'AkNVLtIlk2c3zweQXS6jyVshzVhAy0M59crUeksc2pak' }, - mnemonic: - 'satisfy adjust timber high purchase tuition stool faith fine install that you unaware feed domain license impose boss human eager hat rent enjoy dawn', - }, - { - name: 'test1', - address: 'osmo1cyyzpxplxdzkeea7kwsydadg87357qnahakaks', - pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'AuwYyCUBxQiBGSUWebU46c+OrlApVsyGLHd4qhSDZeiG' }, - mnemonic: - 'notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius', - }, - { - name: 'test2', - address: 'osmo18s5lynnmx37hq4wlrw9gdn68sg2uxp5rgk26vv', - pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'A2G5GnZLlHyxQJUI6LW2ww1lnFEBy+3CCl8LsK2OY6Tj' }, - mnemonic: - 'quality vacuum heart guard buzz spike sight swarm shove special gym robust assume sudden deposit grid alcohol choice devote leader tilt noodle tide penalty', - }, - { - name: 'test3', - address: 'osmo1qwexv7c6sm95lwhzn9027vyu2ccneaqad4w8ka', - pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'ApNMBAr8lFRS6DaOKXgGXFcrpf78KHyqPvRCLZrM0Zzg' }, - mnemonic: - 'symbol force gallery make bulk round subway violin worry mixture penalty kingdom boring survey tool fringe patrol sausage hard admit remember broken alien absorb', - }, - { - name: 'test4', - address: 'osmo14hcxlnwlqtq75ttaxf674vk6mafspg8xwgnn53', - pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'A0RRfnW/yIHOgFjGpknpT/j19OP3YMsXj6OhuCCHfyu6' }, - mnemonic: - 'bounce success option birth apple portion aunt rural episode solution hockey pencil lend session cause hedgehog slender journey system canvas decorate razor catch empty', - }, - { - name: 'test5', - address: 'osmo12rr534cer5c0vj53eq4y32lcwguyy7nndt0u2t', - pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'A5sEEVq3yKGF/pDihGjtSe3SElOd05zXzMxCBPMAhspC' }, - mnemonic: - 'second render cat sing soup reward cluster island bench diet lumber grocery repeat balcony perfect diesel stumble piano distance caught occur example ozone loyal', - }, - { - name: 'test6', - address: 'osmo1nt33cjd5auzh36syym6azgc8tve0jlvklnq7jq', - pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'AweL0IVkZAHjmdSPJucxcln3AcPuMHD4EcnjKBFLZkcA' }, - mnemonic: - 'spatial forest elevator battle also spoon fun skirt flight initial nasty transfer glory palm drama gossip remove fan joke shove label dune debate quick', - }, - { - name: 'test7', - address: 'osmo10qfrpash5g2vk3hppvu45x0g860czur8ff5yx0', - pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'A5aDi6tH57PDZossksf820HI+kVGO5etFqjGbFw/tACu' }, - mnemonic: - 'noble width taxi input there patrol clown public spell aunt wish punch moment will misery eight excess arena pen turtle minimum grain vague inmate', - }, - { - name: 'test8', - address: 'osmo1f4tvsdukfwh6s9swrc24gkuz23tp8pd3e9r5fa', - pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'AgZffLI+SEDH5qCrGoG4HjPy8AIDVmjGZJzy7L3YNkb9' }, - mnemonic: - 'cream sport mango believe inhale text fish rely elegant below earth april wall rug ritual blossom cherry detail length blind digital proof identify ride', - }, - { - name: 'test9', - address: 'osmo1myv43sqgnj5sm4zl98ftl45af9cfzk7nhjxjqh', - pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'A65FjujcdnaFQutpnfkj82QSKtYOMBJPaW4pfTiERwMu' }, - mnemonic: - 'index light average senior silent limit usual local involve delay update rack cause inmate wall render magnet common feature laundry exact casual resource hundred', - }, - { - name: 'test10', - address: 'osmo14gs9zqh8m49yy9kscjqu9h72exyf295afg6kgk', - pubkey: { '@type': '/cosmos.crypto.secp256k1.PubKey', key: 'A2Kc6ERRH6B4REjY6ryTO+ZdNbxuJATDVKXA89irZpKO' }, - mnemonic: - 'prefer forget visit mistake mixture feel eyebrow autumn shop pair address airport diesel street pass vague innocent poem method awful require hurry unhappy shoulder', - }, -]; - -export const testWallet1 = walletData[1]; -export const testWallet2 = walletData[2]; diff --git a/scripts/utils/types.ts b/scripts/utils/types.ts deleted file mode 100644 index 35909c7b1..000000000 --- a/scripts/utils/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type AssetInfo = { cw20: string } | { native: string }; -export const serializeAssetInfo = (obj: AssetInfo) => Object.entries(obj).flat().join(':'); - -export interface Config { - owner: string; - account_nft: string; -} diff --git a/scripts/yarn.lock b/scripts/yarn.lock new file mode 100644 index 000000000..5ac045ef9 --- /dev/null +++ b/scripts/yarn.lock @@ -0,0 +1,5050 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.0.tgz" + integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== + +"@babel/compat-data@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.1.tgz#72d647b4ff6a4f82878d184613353af1dd0290f9" + integrity sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg== + +"@babel/core@7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz" + integrity sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.10" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-module-transforms" "^7.18.9" + "@babel/helpers" "^7.18.9" + "@babel/parser" "^7.18.10" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.18.10" + "@babel/types" "^7.18.10" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/core@^7.11.6", "@babel/core@^7.12.3": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz" + integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helpers" "^7.19.0" + "@babel/parser" "^7.19.0" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/generator@7.18.12": + version "7.18.12" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz" + integrity sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg== + dependencies: + "@babel/types" "^7.18.10" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/generator@^7.18.10", "@babel/generator@^7.19.0", "@babel/generator@^7.7.2": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz" + integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== + dependencies: + "@babel/types" "^7.19.0" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz" + integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.18.6" + "@babel/types" "^7.18.9" + +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz" + integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA== + dependencies: + "@babel/compat-data" "^7.19.0" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.20.2" + semver "^6.3.0" + +"@babel/helper-compilation-targets@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz#7f630911d83b408b76fe584831c98e5395d7a17c" + integrity sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg== + dependencies: + "@babel/compat-data" "^7.19.1" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" + semver "^6.3.0" + +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz" + integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz" + integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + regexpu-core "^5.1.0" + +"@babel/helper-define-polyfill-provider@^0.3.2": + version "0.3.2" + resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz" + integrity sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg== + dependencies: + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-define-polyfill-provider@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" + integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== + dependencies: + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + +"@babel/helper-explode-assignable-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz" + integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-member-expression-to-functions@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz" + integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== + dependencies: + "@babel/types" "^7.18.9" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz" + integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + +"@babel/helper-optimise-call-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz" + integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== + +"@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz" + integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-wrap-function" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz" + integrity sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/helper-simple-access@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz" + integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz" + integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw== + dependencies: + "@babel/types" "^7.18.9" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz" + integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== + +"@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helper-wrap-function@^7.18.9": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz" + integrity sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg== + dependencies: + "@babel/helper-function-name" "^7.19.0" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + +"@babel/helpers@^7.18.9", "@babel/helpers@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz" + integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== + dependencies: + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@7.18.11": + version "7.18.11" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz" + integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz" + integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz" + integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz" + integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + +"@babel/plugin-proposal-async-generator-functions@^7.18.10": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.0.tgz" + integrity sha512-nhEByMUTx3uZueJ/QkJuSlCfN4FGg+xy+vRsfGQGzSauq5ks2Deid2+05Q3KhfaUjvec1IGhw/Zm3cFm8JigTQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-proposal-async-generator-functions@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz#34f6f5174b688529342288cd264f80c9ea9fb4a7" + integrity sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-proposal-class-properties@7.18.6", "@babel/plugin-proposal-class-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz" + integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-proposal-class-static-block@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz" + integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-proposal-dynamic-import@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz" + integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-proposal-export-default-from@7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz" + integrity sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-export-default-from" "^7.18.6" + +"@babel/plugin-proposal-export-namespace-from@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz" + integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-proposal-json-strings@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz" + integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz" + integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz" + integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-proposal-numeric-separator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz" + integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-proposal-object-rest-spread@7.18.9", "@babel/plugin-proposal-object-rest-spread@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz" + integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q== + dependencies: + "@babel/compat-data" "^7.18.8" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.18.8" + +"@babel/plugin-proposal-optional-catch-binding@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz" + integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-proposal-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz" + integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-proposal-private-methods@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz" + integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-proposal-private-property-in-object@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz" + integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz" + integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-default-from@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz" + integrity sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-import-assertions@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz" + integrity sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.18.6", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz" + integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-arrow-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz" + integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-async-to-generator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz" + integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-remap-async-to-generator" "^7.18.6" + +"@babel/plugin-transform-block-scoped-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz" + integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-block-scoping@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz" + integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-classes@^7.18.9", "@babel/plugin-transform-classes@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz" + integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz" + integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-destructuring@^7.18.13", "@babel/plugin-transform-destructuring@^7.18.9": + version "7.18.13" + resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz" + integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz" + integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-duplicate-keys@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz" + integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-exponentiation-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz" + integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-for-of@^7.18.8": + version "7.18.8" + resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz" + integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-function-name@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz" + integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== + dependencies: + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz" + integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-member-expression-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz" + integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-modules-amd@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz" + integrity sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-commonjs@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz" + integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-systemjs@^7.18.9", "@babel/plugin-transform-modules-systemjs@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz" + integrity sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A== + dependencies: + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-validator-identifier" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-umd@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz" + integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.0.tgz" + integrity sha512-HDSuqOQzkU//kfGdiHBt71/hkDTApw4U/cMVgKgX7PqfB3LOaK+2GtCEsBu1dL9CkswDm0Gwehht1dCr421ULQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz#ec7455bab6cd8fb05c525a94876f435a48128888" + integrity sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" + +"@babel/plugin-transform-new-target@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz" + integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-object-super@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz" + integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.6" + +"@babel/plugin-transform-parameters@^7.18.8": + version "7.18.8" + resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz" + integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-property-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz" + integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-regenerator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz" + integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + regenerator-transform "^0.15.0" + +"@babel/plugin-transform-reserved-words@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz" + integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-runtime@7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz" + integrity sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.9" + babel-plugin-polyfill-corejs2 "^0.3.2" + babel-plugin-polyfill-corejs3 "^0.5.3" + babel-plugin-polyfill-regenerator "^0.4.0" + semver "^6.3.0" + +"@babel/plugin-transform-shorthand-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz" + integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-spread@^7.18.9", "@babel/plugin-transform-spread@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz" + integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + +"@babel/plugin-transform-sticky-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz" + integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-template-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz" + integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-typeof-symbol@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz" + integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-typescript@^7.18.6": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.0.tgz" + integrity sha512-DOOIywxPpkQHXijXv+s9MDAyZcLp12oYRl3CMWZ6u7TjSoCBq/KqHR/nNFR3+i2xqheZxoF0H2XyL7B6xeSRuA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-typescript" "^7.18.6" + +"@babel/plugin-transform-unicode-escapes@^7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz" + integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-unicode-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz" + integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/preset-env@7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz" + integrity sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA== + dependencies: + "@babel/compat-data" "^7.18.8" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions" "^7.18.10" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-dynamic-import" "^7.18.6" + "@babel/plugin-proposal-export-namespace-from" "^7.18.9" + "@babel/plugin-proposal-json-strings" "^7.18.6" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" + "@babel/plugin-proposal-numeric-separator" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.18.9" + "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.18.6" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.18.6" + "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions" "^7.18.6" + "@babel/plugin-transform-block-scoping" "^7.18.9" + "@babel/plugin-transform-classes" "^7.18.9" + "@babel/plugin-transform-computed-properties" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.18.9" + "@babel/plugin-transform-dotall-regex" "^7.18.6" + "@babel/plugin-transform-duplicate-keys" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-function-name" "^7.18.9" + "@babel/plugin-transform-literals" "^7.18.9" + "@babel/plugin-transform-member-expression-literals" "^7.18.6" + "@babel/plugin-transform-modules-amd" "^7.18.6" + "@babel/plugin-transform-modules-commonjs" "^7.18.6" + "@babel/plugin-transform-modules-systemjs" "^7.18.9" + "@babel/plugin-transform-modules-umd" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.18.6" + "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-object-super" "^7.18.6" + "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-transform-property-literals" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-reserved-words" "^7.18.6" + "@babel/plugin-transform-shorthand-properties" "^7.18.6" + "@babel/plugin-transform-spread" "^7.18.9" + "@babel/plugin-transform-sticky-regex" "^7.18.6" + "@babel/plugin-transform-template-literals" "^7.18.9" + "@babel/plugin-transform-typeof-symbol" "^7.18.9" + "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.18.10" + babel-plugin-polyfill-corejs2 "^0.3.2" + babel-plugin-polyfill-corejs3 "^0.5.3" + babel-plugin-polyfill-regenerator "^0.4.0" + core-js-compat "^3.22.1" + semver "^6.3.0" + +"@babel/preset-env@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.1.tgz#9f04c916f9c0205a48ebe5cc1be7768eb1983f67" + integrity sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA== + dependencies: + "@babel/compat-data" "^7.19.1" + "@babel/helper-compilation-targets" "^7.19.1" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions" "^7.19.1" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-dynamic-import" "^7.18.6" + "@babel/plugin-proposal-export-namespace-from" "^7.18.9" + "@babel/plugin-proposal-json-strings" "^7.18.6" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" + "@babel/plugin-proposal-numeric-separator" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.18.9" + "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.18.6" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.18.6" + "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions" "^7.18.6" + "@babel/plugin-transform-block-scoping" "^7.18.9" + "@babel/plugin-transform-classes" "^7.19.0" + "@babel/plugin-transform-computed-properties" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.18.13" + "@babel/plugin-transform-dotall-regex" "^7.18.6" + "@babel/plugin-transform-duplicate-keys" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-function-name" "^7.18.9" + "@babel/plugin-transform-literals" "^7.18.9" + "@babel/plugin-transform-member-expression-literals" "^7.18.6" + "@babel/plugin-transform-modules-amd" "^7.18.6" + "@babel/plugin-transform-modules-commonjs" "^7.18.6" + "@babel/plugin-transform-modules-systemjs" "^7.19.0" + "@babel/plugin-transform-modules-umd" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" + "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-object-super" "^7.18.6" + "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-transform-property-literals" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-reserved-words" "^7.18.6" + "@babel/plugin-transform-shorthand-properties" "^7.18.6" + "@babel/plugin-transform-spread" "^7.19.0" + "@babel/plugin-transform-sticky-regex" "^7.18.6" + "@babel/plugin-transform-template-literals" "^7.18.9" + "@babel/plugin-transform-typeof-symbol" "^7.18.9" + "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.19.0" + babel-plugin-polyfill-corejs2 "^0.3.3" + babel-plugin-polyfill-corejs3 "^0.6.0" + babel-plugin-polyfill-regenerator "^0.4.1" + core-js-compat "^3.25.1" + semver "^6.3.0" + +"@babel/preset-modules@^0.1.5": + version "0.1.5" + resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz" + integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/preset-typescript@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz" + integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-typescript" "^7.18.6" + +"@babel/runtime@^7.11.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.19.0", "@babel/runtime@^7.8.4": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz" + integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.18.10", "@babel/template@^7.3.3": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" + +"@babel/traverse@7.18.11": + version "7.18.11" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz" + integrity sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.10" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.18.11" + "@babel/types" "^7.18.10" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/traverse@^7.18.10", "@babel/traverse@^7.18.9", "@babel/traverse@^7.19.0", "@babel/traverse@^7.7.2": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.0.tgz" + integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.19.0" + "@babel/types" "^7.19.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz" + integrity sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ== + dependencies: + "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz" + integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== + dependencies: + "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@confio/ics23@^0.6.8": + version "0.6.8" + resolved "https://registry.npmjs.org/@confio/ics23/-/ics23-0.6.8.tgz" + integrity sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w== + dependencies: + "@noble/hashes" "^1.0.0" + protobufjs "^6.8.8" + +"@cosmjs/amino@0.28.13": + version "0.28.13" + resolved "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.13.tgz" + integrity sha512-IHnH2zGwaY69qT4mVAavr/pfzx6YE+ud1NHJbvVePlbGiz68CXTi5LHR+K0lrKB5mQ7E+ZErWz2mw5U/x+V1wQ== + dependencies: + "@cosmjs/crypto" "0.28.13" + "@cosmjs/encoding" "0.28.13" + "@cosmjs/math" "0.28.13" + "@cosmjs/utils" "0.28.13" + +"@cosmjs/cosmwasm-stargate@^0.28.4": + version "0.28.13" + resolved "https://registry.npmjs.org/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.28.13.tgz" + integrity sha512-dVZNOiRd8btQreRUabncGhVXGCS2wToXqxi9l3KEHwCJQ2RWTshuqV+EZAdCaYHE5W6823s2Ol2W/ukA9AXJPw== + dependencies: + "@cosmjs/amino" "0.28.13" + "@cosmjs/crypto" "0.28.13" + "@cosmjs/encoding" "0.28.13" + "@cosmjs/math" "0.28.13" + "@cosmjs/proto-signing" "0.28.13" + "@cosmjs/stargate" "0.28.13" + "@cosmjs/tendermint-rpc" "0.28.13" + "@cosmjs/utils" "0.28.13" + cosmjs-types "^0.4.0" + long "^4.0.0" + pako "^2.0.2" + +"@cosmjs/crypto@0.28.13": + version "0.28.13" + resolved "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.13.tgz" + integrity sha512-ynKfM0q/tMBQMHJby6ad8lR3gkgBKaelQhIsCZTjClsnuC7oYT9y3ThSZCUWr7Pa9h0J8ahU2YV2oFWFVWJQzQ== + dependencies: + "@cosmjs/encoding" "0.28.13" + "@cosmjs/math" "0.28.13" + "@cosmjs/utils" "0.28.13" + "@noble/hashes" "^1" + bn.js "^5.2.0" + elliptic "^6.5.3" + libsodium-wrappers "^0.7.6" + +"@cosmjs/encoding@0.28.13": + version "0.28.13" + resolved "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.13.tgz" + integrity sha512-jtXbAYtV77rLHxoIrjGFsvgGjeTKttuHRv6cvuy3toCZzY7JzTclKH5O2g36IIE4lXwD9xwuhGJ2aa6A3dhNkA== + dependencies: + base64-js "^1.3.0" + bech32 "^1.1.4" + readonly-date "^1.0.0" + +"@cosmjs/json-rpc@0.28.13": + version "0.28.13" + resolved "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.13.tgz" + integrity sha512-fInSvg7x9P6p+GWqet+TMhrMTM3OWWdLJOGS5w2ryubMjgpR1rLiAx77MdTNkArW+/6sUwku0sN4veM4ENQu6A== + dependencies: + "@cosmjs/stream" "0.28.13" + xstream "^11.14.0" + +"@cosmjs/math@0.28.13": + version "0.28.13" + resolved "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.13.tgz" + integrity sha512-PDpL8W/kbyeWi0mQ2OruyqE8ZUAdxPs1xCbDX3WXJwy2oU+X2UTbkuweJHVpS9CIqmZulBoWQAmlf6t6zr1N/g== + dependencies: + bn.js "^5.2.0" + +"@cosmjs/proto-signing@0.28.13": + version "0.28.13" + resolved "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.13.tgz" + integrity sha512-nSl/2ZLsUJYz3Ad0RY3ihZUgRHIow2OnYqKsESMu+3RA/jTi9bDYhiBu8mNMHI0xrEJry918B2CyI56pOUHdPQ== + dependencies: + "@cosmjs/amino" "0.28.13" + "@cosmjs/crypto" "0.28.13" + "@cosmjs/encoding" "0.28.13" + "@cosmjs/math" "0.28.13" + "@cosmjs/utils" "0.28.13" + cosmjs-types "^0.4.0" + long "^4.0.0" + +"@cosmjs/socket@0.28.13": + version "0.28.13" + resolved "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.13.tgz" + integrity sha512-lavwGxQ5VdeltyhpFtwCRVfxeWjH5D5mmN7jgx9nuCf3XSFbTcOYxrk2pQ4usenu1Q1KZdL4Yl5RCNrJuHD9Ug== + dependencies: + "@cosmjs/stream" "0.28.13" + isomorphic-ws "^4.0.1" + ws "^7" + xstream "^11.14.0" + +"@cosmjs/stargate@0.28.13", "@cosmjs/stargate@^0.28.4": + version "0.28.13" + resolved "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.13.tgz" + integrity sha512-dVBMazDz8/eActHsRcZjDHHptOBMqvibj5CFgEtZBp22gP6ASzoAUXTlkSVk5FBf4sfuUHoff6st134/+PGMAg== + dependencies: + "@confio/ics23" "^0.6.8" + "@cosmjs/amino" "0.28.13" + "@cosmjs/encoding" "0.28.13" + "@cosmjs/math" "0.28.13" + "@cosmjs/proto-signing" "0.28.13" + "@cosmjs/stream" "0.28.13" + "@cosmjs/tendermint-rpc" "0.28.13" + "@cosmjs/utils" "0.28.13" + cosmjs-types "^0.4.0" + long "^4.0.0" + protobufjs "~6.11.3" + xstream "^11.14.0" + +"@cosmjs/stream@0.28.13": + version "0.28.13" + resolved "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.13.tgz" + integrity sha512-AnjtfwT8NwPPkd3lhZhjOlOzT0Kn9bgEu2IPOZjQ1nmG2bplsr6TJmnwn0dJxHT7UGtex17h6whKB5N4wU37Wg== + dependencies: + xstream "^11.14.0" + +"@cosmjs/tendermint-rpc@0.28.13", "@cosmjs/tendermint-rpc@^0.28.13": + version "0.28.13" + resolved "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.13.tgz" + integrity sha512-GB+ZmfuJIGQm0hsRtLYjeR3lOxF7Z6XyCBR0cX5AAYOZzSEBJjevPgUHD6tLn8zIhvzxaW3/VKnMB+WmlxdH4w== + dependencies: + "@cosmjs/crypto" "0.28.13" + "@cosmjs/encoding" "0.28.13" + "@cosmjs/json-rpc" "0.28.13" + "@cosmjs/math" "0.28.13" + "@cosmjs/socket" "0.28.13" + "@cosmjs/stream" "0.28.13" + "@cosmjs/utils" "0.28.13" + axios "^0.21.2" + readonly-date "^1.0.0" + xstream "^11.14.0" + +"@cosmjs/utils@0.28.13": + version "0.28.13" + resolved "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.13.tgz" + integrity sha512-dVeMBiyg+46x7XBZEfJK8yTihphbCFpjVYmLJVqmTsHfJwymQ65cpyW/C+V/LgWARGK8hWQ/aX9HM5Ao8QmMSg== + +"@cosmwasm/ts-codegen@^0.16.3": + version "0.16.3" + resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.16.3.tgz#2eeec718657314601896b42625979ffbac6ab5e8" + integrity sha512-VKqIR8DCSWN7GtfgM5coEa+cGg0gbpSBTauAqr2tMipnfRZcJO5SVznt7z7XqAm7ctjiozh+oQifAFxnwp+8/w== + dependencies: + "@babel/core" "7.18.10" + "@babel/generator" "7.18.12" + "@babel/parser" "7.18.11" + "@babel/plugin-proposal-class-properties" "7.18.6" + "@babel/plugin-proposal-export-default-from" "7.18.10" + "@babel/plugin-proposal-object-rest-spread" "7.18.9" + "@babel/plugin-transform-runtime" "7.18.10" + "@babel/preset-env" "7.18.10" + "@babel/preset-typescript" "^7.18.6" + "@babel/runtime" "^7.18.9" + "@babel/traverse" "7.18.11" + "@babel/types" "7.18.10" + "@pyramation/json-schema-to-typescript" " 11.0.4" + case "1.6.3" + dargs "7.0.0" + deepmerge "4.2.2" + dotty "0.1.2" + fuzzy "0.1.3" + glob "8.0.3" + inquirerer "0.1.3" + long "^5.2.0" + minimist "1.2.6" + mkdirp "1.0.4" + parse-package-name "1.0.0" + rimraf "3.0.2" + shelljs "0.8.5" + wasm-ast-types "^0.11.3" + +"@eslint/eslintrc@^1.3.2": + version "1.3.2" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz" + integrity sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.4.0" + globals "^13.15.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@humanwhocodes/config-array@^0.10.4": + version "0.10.4" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz" + integrity sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/gitignore-to-minimatch@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz" + integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA== + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.0.3": + version "29.0.3" + resolved "https://registry.npmjs.org/@jest/console/-/console-29.0.3.tgz" + integrity sha512-cGg0r+klVHSYnfE977S9wmpuQ9L+iYuYgL+5bPXiUlUynLLYunRxswEmhBzvrSKGof5AKiHuTTmUKAqRcDY9dg== + dependencies: + "@jest/types" "^29.0.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.0.3" + jest-util "^29.0.3" + slash "^3.0.0" + +"@jest/core@^29.0.3": + version "29.0.3" + resolved "https://registry.npmjs.org/@jest/core/-/core-29.0.3.tgz" + integrity sha512-1d0hLbOrM1qQE3eP3DtakeMbKTcXiXP3afWxqz103xPyddS2NhnNghS7MaXx1dcDt4/6p4nlhmeILo2ofgi8cQ== + dependencies: + "@jest/console" "^29.0.3" + "@jest/reporters" "^29.0.3" + "@jest/test-result" "^29.0.3" + "@jest/transform" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.0.0" + jest-config "^29.0.3" + jest-haste-map "^29.0.3" + jest-message-util "^29.0.3" + jest-regex-util "^29.0.0" + jest-resolve "^29.0.3" + jest-resolve-dependencies "^29.0.3" + jest-runner "^29.0.3" + jest-runtime "^29.0.3" + jest-snapshot "^29.0.3" + jest-util "^29.0.3" + jest-validate "^29.0.3" + jest-watcher "^29.0.3" + micromatch "^4.0.4" + pretty-format "^29.0.3" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.0.3": + version "29.0.3" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-29.0.3.tgz" + integrity sha512-iKl272NKxYNQNqXMQandAIwjhQaGw5uJfGXduu8dS9llHi8jV2ChWrtOAVPnMbaaoDhnI3wgUGNDvZgHeEJQCA== + dependencies: + "@jest/fake-timers" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + jest-mock "^29.0.3" + +"@jest/expect-utils@^29.0.3": + version "29.0.3" + resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.0.3.tgz" + integrity sha512-i1xUkau7K/63MpdwiRqaxgZOjxYs4f0WMTGJnYwUKubsNRZSeQbLorS7+I4uXVF9KQ5r61BUPAUMZ7Lf66l64Q== + dependencies: + jest-get-type "^29.0.0" + +"@jest/expect@^29.0.3": + version "29.0.3" + resolved "https://registry.npmjs.org/@jest/expect/-/expect-29.0.3.tgz" + integrity sha512-6W7K+fsI23FQ01H/BWccPyDZFrnU9QlzDcKOjrNVU5L8yUORFAJJIpmyxWPW70+X624KUNqzZwPThPMX28aXEQ== + dependencies: + expect "^29.0.3" + jest-snapshot "^29.0.3" + +"@jest/fake-timers@^29.0.3": + version "29.0.3" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.3.tgz" + integrity sha512-tmbUIo03x0TdtcZCESQ0oQSakPCpo7+s6+9mU19dd71MptkP4zCwoeZqna23//pgbhtT1Wq02VmA9Z9cNtvtCQ== + dependencies: + "@jest/types" "^29.0.3" + "@sinonjs/fake-timers" "^9.1.2" + "@types/node" "*" + jest-message-util "^29.0.3" + jest-mock "^29.0.3" + jest-util "^29.0.3" + +"@jest/globals@^29.0.3": + version "29.0.3" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-29.0.3.tgz" + integrity sha512-YqGHT65rFY2siPIHHFjuCGUsbzRjdqkwbat+Of6DmYRg5shIXXrLdZoVE/+TJ9O1dsKsFmYhU58JvIbZRU1Z9w== + dependencies: + "@jest/environment" "^29.0.3" + "@jest/expect" "^29.0.3" + "@jest/types" "^29.0.3" + jest-mock "^29.0.3" + +"@jest/reporters@^29.0.3": + version "29.0.3" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-29.0.3.tgz" + integrity sha512-3+QU3d4aiyOWfmk1obDerie4XNCaD5Xo1IlKNde2yGEi02WQD+ZQD0i5Hgqm1e73sMV7kw6pMlCnprtEwEVwxw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.0.3" + "@jest/test-result" "^29.0.3" + "@jest/transform" "^29.0.3" + "@jest/types" "^29.0.3" + "@jridgewell/trace-mapping" "^0.3.15" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.0.3" + jest-util "^29.0.3" + jest-worker "^29.0.3" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + terminal-link "^2.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz" + integrity sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg== + dependencies: + "@sinclair/typebox" "^0.24.1" + +"@jest/schemas@^29.0.0": + version "29.0.0" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz" + integrity sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA== + dependencies: + "@sinclair/typebox" "^0.24.1" + +"@jest/source-map@^29.0.0": + version "29.0.0" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-29.0.0.tgz" + integrity sha512-nOr+0EM8GiHf34mq2GcJyz/gYFyLQ2INDhAylrZJ9mMWoW21mLBfZa0BUVPPMxVYrLjeiRe2Z7kWXOGnS0TFhQ== + dependencies: + "@jridgewell/trace-mapping" "^0.3.15" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.0.3": + version "29.0.3" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-29.0.3.tgz" + integrity sha512-vViVnQjCgTmbhDKEonKJPtcFe9G/CJO4/Np4XwYJah+lF2oI7KKeRp8t1dFvv44wN2NdbDb/qC6pi++Vpp0Dlg== + dependencies: + "@jest/console" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.0.3": + version "29.0.3" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.0.3.tgz" + integrity sha512-Hf4+xYSWZdxTNnhDykr8JBs0yBN/nxOXyUQWfotBUqqy0LF9vzcFB0jm/EDNZCx587znLWTIgxcokW7WeZMobQ== + dependencies: + "@jest/test-result" "^29.0.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.0.3" + slash "^3.0.0" + +"@jest/transform@28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz" + integrity sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^28.1.3" + "@jridgewell/trace-mapping" "^0.3.13" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.1" + +"@jest/transform@^29.0.3": + version "29.0.3" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-29.0.3.tgz" + integrity sha512-C5ihFTRYaGDbi/xbRQRdbo5ddGtI4VSpmL6AIcZxdhwLbXMa7PcXxxqyI91vGOFHnn5aVM3WYnYKCHEqmLVGzg== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.0.3" + "@jridgewell/trace-mapping" "^0.3.15" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.0.3" + jest-regex-util "^29.0.0" + jest-util "^29.0.3" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.1" + +"@jest/types@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz" + integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ== + dependencies: + "@jest/schemas" "^28.1.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jest/types@^29.0.3": + version "29.0.3" + resolved "https://registry.npmjs.org/@jest/types/-/types-29.0.3.tgz" + integrity sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A== + dependencies: + "@jest/schemas" "^29.0.0" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.15" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz" + integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jsdevtools/ono@^7.1.3": + version "7.1.3" + resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz" + integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== + +"@noble/hashes@^1", "@noble/hashes@^1.0.0": + version "1.1.2" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz" + integrity sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@osmonauts/helpers@^0.4.3": + version "0.4.3" + resolved "https://registry.npmjs.org/@osmonauts/helpers/-/helpers-0.4.3.tgz" + integrity sha512-Dd6CXSKoAP4kLptE8Fx2vsEqc0Ai4kF07l4zPsfOYop9SH5ayOKkRTfAkrBjBr84FsMPyu5TEhJeqj1i//Q/jg== + dependencies: + "@babel/runtime" "^7.18.9" + "@cosmjs/amino" "0.28.13" + "@cosmjs/crypto" "0.28.13" + "@cosmjs/proto-signing" "0.28.13" + "@cosmjs/stargate" "0.28.13" + cosmjs-types "0.5.1" + long "^5.2.0" + protobufjs "^6.11.3" + +"@osmonauts/lcd@^0.4.0": + version "0.4.0" + resolved "https://registry.npmjs.org/@osmonauts/lcd/-/lcd-0.4.0.tgz" + integrity sha512-DjzU2ZqJ6DHkRpNfIfut1LIVYxdqo5IQf9sgYPiDF2hmTCxvYeAZ6uvCuiUiKJO0QdjBs3criv0/CH+Ytl5dsA== + dependencies: + "@babel/runtime" "^7.18.9" + axios "0.27.2" + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + +"@pyramation/json-schema-ref-parser@9.0.6": + version "9.0.6" + resolved "https://registry.npmjs.org/@pyramation/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz" + integrity sha512-L5kToHAEc1Q87R8ZwWFaNa4tPHr8Hnm+U+DRdUVq3tUtk+EX4pCqSd34Z6EMxNi/bjTzt1syAG9J2Oo1YFlqSg== + dependencies: + "@jsdevtools/ono" "^7.1.3" + call-me-maybe "^1.0.1" + js-yaml "^3.13.1" + +"@pyramation/json-schema-to-typescript@ 11.0.4": + version "11.0.4" + resolved "https://registry.npmjs.org/@pyramation/json-schema-to-typescript/-/json-schema-to-typescript-11.0.4.tgz" + integrity sha512-+aSzXDLhMHOEdV2cJ7Tjg/9YenjHU5BCmClVygzwxJZ1R16NOfEn7lTAwVzb/2jivOSnhjHzMJbnSf8b6rd1zg== + dependencies: + "@pyramation/json-schema-ref-parser" "9.0.6" + "@types/json-schema" "^7.0.11" + "@types/lodash" "^4.14.182" + "@types/prettier" "^2.6.1" + cli-color "^2.0.2" + get-stdin "^8.0.0" + glob "^7.1.6" + glob-promise "^4.2.2" + is-glob "^4.0.3" + lodash "^4.17.21" + minimist "^1.2.6" + mkdirp "^1.0.4" + mz "^2.7.0" + prettier "^2.6.2" + +"@sinclair/typebox@^0.24.1": + version "0.24.40" + resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.40.tgz" + integrity sha512-Xint60L8rF0+nRy+6fCjW9jQMmu7fTpbwTBrXZiK6eq/RHDJS7LvWX/0oXC8O7fCePmrY/XdfaTv2HiUDeCq4g== + +"@sinonjs/commons@^1.7.0": + version "1.8.3" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^9.1.2": + version "9.1.2" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz" + integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@types/babel__core@^7.1.14": + version "7.1.19" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz" + integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.18.1" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz" + integrity sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA== + dependencies: + "@babel/types" "^7.3.0" + +"@types/glob@^7.1.3": + version "7.2.0" + resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/graceful-fs@^4.1.3": + version "4.1.5" + resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.0.2": + version "29.0.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.0.2.tgz#05dcb2d78d2fcc444be89f95b7389f2c3601d336" + integrity sha512-TaklkwSEtvwJpleiKBHgEBySIQlcZ08gYP/s5wdtdLnjz9uxjnDd7U+Y0JWACebkqBc+jtbol2PEtEW0wQV2zQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@^7.0.11", "@types/json-schema@^7.0.9": + version "7.0.11" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + +"@types/lodash@^4.14.182": + version "4.14.185" + resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.185.tgz" + integrity sha512-evMDG1bC4rgQg4ku9tKpuMh5iBNEwNa3tf9zRHdP1qlv+1WUg44xat4IxCE14gIpZRGUUWAx2VhItCZc25NfMA== + +"@types/long@^4.0.1": + version "4.0.2" + resolved "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== + +"@types/minimatch@*": + version "5.1.2" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz" + integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== + +"@types/node@*", "@types/node@>=13.7.0": + version "18.7.16" + resolved "https://registry.npmjs.org/@types/node/-/node-18.7.16.tgz" + integrity sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg== + +"@types/prettier@^2.1.5", "@types/prettier@^2.6.1": + version "2.7.0" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz" + integrity sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A== + +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^17.0.8": + version "17.0.12" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz" + integrity sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^5.37.0": + version "5.37.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.37.0.tgz" + integrity sha512-Fde6W0IafXktz1UlnhGkrrmnnGpAo1kyX7dnyHHVrmwJOn72Oqm3eYtddrpOwwel2W8PAK9F3pIL5S+lfoM0og== + dependencies: + "@typescript-eslint/scope-manager" "5.37.0" + "@typescript-eslint/type-utils" "5.37.0" + "@typescript-eslint/utils" "5.37.0" + debug "^4.3.4" + functional-red-black-tree "^1.0.1" + ignore "^5.2.0" + regexpp "^3.2.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.37.0": + version "5.37.0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.37.0.tgz" + integrity sha512-01VzI/ipYKuaG5PkE5+qyJ6m02fVALmMPY3Qq5BHflDx3y4VobbLdHQkSMg9VPRS4KdNt4oYTMaomFoHonBGAw== + dependencies: + "@typescript-eslint/scope-manager" "5.37.0" + "@typescript-eslint/types" "5.37.0" + "@typescript-eslint/typescript-estree" "5.37.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.37.0": + version "5.37.0" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.37.0.tgz" + integrity sha512-F67MqrmSXGd/eZnujjtkPgBQzgespu/iCZ+54Ok9X5tALb9L2v3G+QBSoWkXG0p3lcTJsL+iXz5eLUEdSiJU9Q== + dependencies: + "@typescript-eslint/types" "5.37.0" + "@typescript-eslint/visitor-keys" "5.37.0" + +"@typescript-eslint/type-utils@5.37.0": + version "5.37.0" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.37.0.tgz" + integrity sha512-BSx/O0Z0SXOF5tY0bNTBcDEKz2Ec20GVYvq/H/XNKiUorUFilH7NPbFUuiiyzWaSdN3PA8JV0OvYx0gH/5aFAQ== + dependencies: + "@typescript-eslint/typescript-estree" "5.37.0" + "@typescript-eslint/utils" "5.37.0" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.37.0": + version "5.37.0" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.37.0.tgz" + integrity sha512-3frIJiTa5+tCb2iqR/bf7XwU20lnU05r/sgPJnRpwvfZaqCJBrl8Q/mw9vr3NrNdB/XtVyMA0eppRMMBqdJ1bA== + +"@typescript-eslint/typescript-estree@5.37.0": + version "5.37.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.37.0.tgz" + integrity sha512-JkFoFIt/cx59iqEDSgIGnQpCTRv96MQnXCYvJi7QhBC24uyuzbD8wVbajMB1b9x4I0octYFJ3OwjAwNqk1AjDA== + dependencies: + "@typescript-eslint/types" "5.37.0" + "@typescript-eslint/visitor-keys" "5.37.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.37.0": + version "5.37.0" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.37.0.tgz" + integrity sha512-jUEJoQrWbZhmikbcWSMDuUSxEE7ID2W/QCV/uz10WtQqfOuKZUqFGjqLJ+qhDd17rjgp+QJPqTdPIBWwoob2NQ== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.37.0" + "@typescript-eslint/types" "5.37.0" + "@typescript-eslint/typescript-estree" "5.37.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/visitor-keys@5.37.0": + version "5.37.0" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.37.0.tgz" + integrity sha512-Hp7rT4cENBPIzMwrlehLW/28EVCOcE9U1Z1BQTc8EA8v5qpr7GRGuG+U58V5tTY48zvUOA3KHvw3rA8tY9fbdA== + dependencies: + "@typescript-eslint/types" "5.37.0" + eslint-visitor-keys "^3.3.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.8.0: + version "8.8.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz" + integrity sha512-tH/fSoQp4DrEodDK3QpdiWiZTSe7sBJ9eOqcQBZ0o9HTM+5M/viSEn+sPMoTuPjQQ8n++w3QJoPEjt8LVPcrCg== + +ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== + +ansi-regex@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz" + integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== + +ansi-regex@^4.1.0: + version "4.1.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" + integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@^3.0.3: + version "3.1.2" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +ast-stringify@0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/ast-stringify/-/ast-stringify-0.1.0.tgz" + integrity sha512-J1PgFYV3RG6r37+M6ySZJH406hR82okwGvFM9hLXpOvdx4WC4GEW8/qiw6pi1hKTrqcRvoHP8a7mp87egYr6iA== + dependencies: + "@babel/runtime" "^7.11.2" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@0.27.2: + version "0.27.2" + resolved "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + +axios@^0.21.2: + version "0.21.4" + resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== + dependencies: + follow-redirects "^1.14.0" + +babel-jest@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.0.3.tgz" + integrity sha512-ApPyHSOhS/sVzwUOQIWJmdvDhBsMG01HX9z7ogtkp1TToHGGUWFlnXJUIzCgKPSfiYLn3ibipCYzsKSURHEwLg== + dependencies: + "@jest/transform" "^29.0.3" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.0.2" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.0.2: + version "29.0.2" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.0.2.tgz" + integrity sha512-eBr2ynAEFjcebVvu8Ktx580BD1QKCrBG1XwEUTXJe285p9HA/4hOhfWCFRQhTKSyBV0VzjhG7H91Eifz9s29hg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-plugin-polyfill-corejs2@^0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz" + integrity sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q== + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-define-polyfill-provider" "^0.3.2" + semver "^6.1.1" + +babel-plugin-polyfill-corejs2@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" + integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-define-polyfill-provider" "^0.3.3" + semver "^6.1.1" + +babel-plugin-polyfill-corejs3@^0.5.3: + version "0.5.3" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz" + integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.2" + core-js-compat "^3.21.0" + +babel-plugin-polyfill-corejs3@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a" + integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.3" + core-js-compat "^3.25.1" + +babel-plugin-polyfill-regenerator@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz" + integrity sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.2" + +babel-plugin-polyfill-regenerator@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" + integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.3" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^29.0.2: + version "29.0.2" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.0.2.tgz" + integrity sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA== + dependencies: + babel-plugin-jest-hoist "^29.0.2" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.0: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bech32@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + +bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.2.0: + version "5.2.1" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +browserslist@^4.20.2, browserslist@^4.21.3: + version "4.21.3" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz" + integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== + dependencies: + caniuse-lite "^1.0.30001370" + electron-to-chromium "^1.4.202" + node-releases "^2.0.6" + update-browserslist-db "^1.0.5" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +call-me-maybe@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz" + integrity sha512-wCyFsDQkKPwwF8BDwOiWNx/9K45L/hvggQiDbve+viMNMQnWhrlYIuBk09offfwCRtCO9P6XwUttufzU11WCVw== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001370: + version "1.0.30001397" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001397.tgz" + integrity sha512-SW9N2TbCdLf0eiNDRrrQXx2sOkaakNZbCjgNpPyMJJbiOrU5QzMIrXOVMRM1myBXTD5iTkdrtU/EguCrBocHlA== + +case@1.6.3: + version "1.6.3" + resolved "https://registry.npmjs.org/case/-/case-1.6.3.tgz" + integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== + +chalk@4.1.2, chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^1.0.0, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" + integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +chardet@^0.4.0: + version "0.4.2" + resolved "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz" + integrity sha512-j/Toj7f1z98Hh2cYo2BVr85EpIRWqUi7rtRSGxh/cqUjqrnJe9l9UE7IUGd2vQ2p+kSHLkSzObQPZPLUC6TQwg== + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +ci-info@^3.2.0: + version "3.4.0" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz" + integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== + +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + +cli-color@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/cli-color/-/cli-color-2.0.3.tgz" + integrity sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ== + dependencies: + d "^1.0.1" + es5-ext "^0.10.61" + es6-iterator "^2.0.3" + memoizee "^0.4.15" + timers-ext "^0.1.7" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz" + integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== + dependencies: + restore-cursor "^2.0.0" + +cli-width@^2.0.0: + version "2.2.1" + resolved "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz" + integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colors@^1.1.2: + version "1.4.0" + resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.25.1: + version "3.25.1" + resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.25.1.tgz" + integrity sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw== + dependencies: + browserslist "^4.21.3" + +cosmjs-types@0.5.1, cosmjs-types@^0.5.0: + version "0.5.1" + resolved "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.5.1.tgz" + integrity sha512-NcC58xUIVLlKdIimWWQAmSlmCjiMrJnuHf4i3LiD8PCextfHR0fT3V5/WlXZZreyMgdmh6ML1zPUfGTbbo3Z5g== + dependencies: + long "^4.0.0" + protobufjs "~6.11.2" + +cosmjs-types@^0.4.0: + version "0.4.1" + resolved "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz" + integrity sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog== + dependencies: + long "^4.0.0" + protobufjs "~6.11.2" + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + +dargs@7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz" + integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@4.2.2, deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +define-properties@^1.1.3, define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.0.0: + version "29.0.0" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.0.0.tgz" + integrity sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dotty@0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/dotty/-/dotty-0.1.2.tgz" + integrity sha512-V0EWmKeH3DEhMwAZ+8ZB2Ao4OK6p++Z0hsDtZq3N0+0ZMVqkzrcEGROvOnZpLnvBg5PTNG23JEDLAm64gPaotQ== + +electron-to-chromium@^1.4.202: + version "1.4.247" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.247.tgz" + integrity sha512-FLs6R4FQE+1JHM0hh3sfdxnYjKvJpHZyhQDjc2qFq/xFvmmRt/TATNToZhrcGUFzpF2XjeiuozrA8lI0PZmYYw== + +elliptic@^6.5.3: + version "6.5.4" + resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emittery@^0.10.2: + version "0.10.2" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz" + integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.61, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: + version "0.10.62" + resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz" + integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +es6-weak-map@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + dependencies: + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + es6-symbol "^3.1.1" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^8.5.0: + version "8.5.0" + resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz" + integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint@^8.23.1: + version "8.23.1" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.23.1.tgz" + integrity sha512-w7C1IXCc6fNqjpuYd0yPlcTKKmHlHHktRkzmBPZ+7cvNBQuiNjx0xaMTjAJGCafJhQkrFJooREv0CtrVzmHwqg== + dependencies: + "@eslint/eslintrc" "^1.3.2" + "@humanwhocodes/config-array" "^0.10.4" + "@humanwhocodes/gitignore-to-minimatch" "^1.0.2" + "@humanwhocodes/module-importer" "^1.0.1" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.1.1" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.3.0" + espree "^9.4.0" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.1" + globals "^13.15.0" + globby "^11.1.0" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-sdsl "^4.1.4" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + +espree@^9.4.0: + version "9.4.0" + resolved "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz" + integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw== + dependencies: + acorn "^8.8.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.3.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz" + integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== + dependencies: + d "1" + es5-ext "~0.10.14" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/expect/-/expect-29.0.3.tgz" + integrity sha512-t8l5DTws3212VbmPL+tBFXhjRHLmctHB0oQbL8eUc6S7NzZtYUhycrFO9mkxA0ZUC6FAWdNi7JchJSkODtcu1Q== + dependencies: + "@jest/expect-utils" "^29.0.3" + jest-get-type "^29.0.0" + jest-matcher-utils "^29.0.3" + jest-message-util "^29.0.3" + jest-util "^29.0.3" + +ext@^1.1.2: + version "1.7.0" + resolved "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + +external-editor@^2.0.4: + version "2.2.0" + resolved "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz" + integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== + dependencies: + chardet "^0.4.0" + iconv-lite "^0.4.17" + tmp "^0.0.33" + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.2.12" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz" + integrity sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.7" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== + +follow-redirects@^1.14.0, follow-redirects@^1.14.9: + version "1.15.1" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz" + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + +fuzzy@0.1.3: + version "0.1.3" + resolved "https://registry.npmjs.org/fuzzy/-/fuzzy-0.1.3.tgz" + integrity sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: + version "1.1.2" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz" + integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-promise@^4.2.2: + version "4.2.2" + resolved "https://registry.npmjs.org/glob-promise/-/glob-promise-4.2.2.tgz" + integrity sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw== + dependencies: + "@types/glob" "^7.1.3" + +glob@8.0.3: + version "8.0.3" + resolved "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz" + integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.15.0: + version "13.17.0" + resolved "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz" + integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.1: + version "1.0.3" + resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.1.15, graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" + integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== + dependencies: + ansi-regex "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@^0.4.17, iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + +inquirer-autocomplete-prompt@^0.11.1: + version "0.11.1" + resolved "https://registry.npmjs.org/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-0.11.1.tgz" + integrity sha512-VM4eNiyRD4CeUc2cyKni+F8qgHwL9WC4LdOr+mEC85qP/QNsDV+ysVqUrJYhw1TmDQu1QVhc8hbaL7wfk8SJxw== + dependencies: + ansi-escapes "^2.0.0" + chalk "^1.1.3" + figures "^2.0.0" + inquirer "3.1.1" + lodash "^4.17.4" + run-async "^2.3.0" + util "^0.10.3" + +inquirer@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-3.1.1.tgz" + integrity sha512-H50sHQwgvvaTBd3HpKMVtL/u6LoHDvYym51gd7bGQe/+9HkCE+J0/3N5FJLfd6O6oz44hHewC2Pc2LodzWVafQ== + dependencies: + ansi-escapes "^2.0.0" + chalk "^1.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.0.4" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.0.0" + strip-ansi "^3.0.0" + through "^2.3.6" + +inquirer@^6.0.0: + version "6.5.2" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz" + integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== + dependencies: + ansi-escapes "^3.2.0" + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^2.0.0" + lodash "^4.17.12" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.4.0" + string-width "^2.1.0" + strip-ansi "^5.1.0" + through "^2.3.6" + +inquirerer@0.1.3: + version "0.1.3" + resolved "https://registry.npmjs.org/inquirerer/-/inquirerer-0.1.3.tgz" + integrity sha512-yGgLUOqPxTsINBjZNZeLi3cv2zgxXtw9feaAOSJf2j6AqIT5Uxs5ZOqOrfAf+xP65Sicla1FD3iDxa3D6TsCAQ== + dependencies: + colors "^1.1.2" + inquirer "^6.0.0" + inquirer-autocomplete-prompt "^0.11.1" + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.9.0: + version "2.10.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz" + integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== + dependencies: + has "^1.0.3" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" + integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-promise@^2.2.2: + version "2.2.2" + resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz" + integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.0" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz" + integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.5" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz" + integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^29.0.0: + version "29.0.0" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.0.0.tgz" + integrity sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ== + dependencies: + execa "^5.0.0" + p-limit "^3.1.0" + +jest-circus@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-29.0.3.tgz" + integrity sha512-QeGzagC6Hw5pP+df1+aoF8+FBSgkPmraC1UdkeunWh0jmrp7wC0Hr6umdUAOELBQmxtKAOMNC3KAdjmCds92Zg== + dependencies: + "@jest/environment" "^29.0.3" + "@jest/expect" "^29.0.3" + "@jest/test-result" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + is-generator-fn "^2.0.0" + jest-each "^29.0.3" + jest-matcher-utils "^29.0.3" + jest-message-util "^29.0.3" + jest-runtime "^29.0.3" + jest-snapshot "^29.0.3" + jest-util "^29.0.3" + p-limit "^3.1.0" + pretty-format "^29.0.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-29.0.3.tgz" + integrity sha512-aUy9Gd/Kut1z80eBzG10jAn6BgS3BoBbXyv+uXEqBJ8wnnuZ5RpNfARoskSrTIy1GY4a8f32YGuCMwibtkl9CQ== + dependencies: + "@jest/core" "^29.0.3" + "@jest/test-result" "^29.0.3" + "@jest/types" "^29.0.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^29.0.3" + jest-util "^29.0.3" + jest-validate "^29.0.3" + prompts "^2.0.1" + yargs "^17.3.1" + +jest-config@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-29.0.3.tgz" + integrity sha512-U5qkc82HHVYe3fNu2CRXLN4g761Na26rWKf7CjM8LlZB3In1jadEkZdMwsE37rd9RSPV0NfYaCjHdk/gu3v+Ew== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.0.3" + "@jest/types" "^29.0.3" + babel-jest "^29.0.3" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.0.3" + jest-environment-node "^29.0.3" + jest-get-type "^29.0.0" + jest-regex-util "^29.0.0" + jest-resolve "^29.0.3" + jest-runner "^29.0.3" + jest-util "^29.0.3" + jest-validate "^29.0.3" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.0.3" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-29.0.3.tgz" + integrity sha512-+X/AIF5G/vX9fWK+Db9bi9BQas7M9oBME7egU7psbn4jlszLFCu0dW63UgeE6cs/GANq4fLaT+8sGHQQ0eCUfg== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.0.0" + jest-get-type "^29.0.0" + pretty-format "^29.0.3" + +jest-docblock@^29.0.0: + version "29.0.0" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.0.0.tgz" + integrity sha512-s5Kpra/kLzbqu9dEjov30kj1n4tfu3e7Pl8v+f8jOkeWNqM6Ds8jRaJfZow3ducoQUrf2Z4rs2N5S3zXnb83gw== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-29.0.3.tgz" + integrity sha512-wILhZfESURHHBNvPMJ0lZlYZrvOQJxAo3wNHi+ycr90V7M+uGR9Gh4+4a/BmaZF0XTyZsk4OiYEf3GJN7Ltqzg== + dependencies: + "@jest/types" "^29.0.3" + chalk "^4.0.0" + jest-get-type "^29.0.0" + jest-util "^29.0.3" + pretty-format "^29.0.3" + +jest-environment-node@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.0.3.tgz" + integrity sha512-cdZqRCnmIlTXC+9vtvmfiY/40Cj6s2T0czXuq1whvQdmpzAnj4sbqVYuZ4zFHk766xTTJ+Ij3uUqkk8KCfXoyg== + dependencies: + "@jest/environment" "^29.0.3" + "@jest/fake-timers" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + jest-mock "^29.0.3" + jest-util "^29.0.3" + +jest-get-type@^29.0.0: + version "29.0.0" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz" + integrity sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw== + +jest-haste-map@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz" + integrity sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA== + dependencies: + "@jest/types" "^28.1.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" + jest-worker "^28.1.3" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-haste-map@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.3.tgz" + integrity sha512-uMqR99+GuBHo0RjRhOE4iA6LmsxEwRdgiIAQgMU/wdT2XebsLDz5obIwLZm/Psj+GwSEQhw9AfAVKGYbh2G55A== + dependencies: + "@jest/types" "^29.0.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.0.0" + jest-util "^29.0.3" + jest-worker "^29.0.3" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.0.3.tgz" + integrity sha512-YfW/G63dAuiuQ3QmQlh8hnqLDe25WFY3eQhuc/Ev1AGmkw5zREblTh7TCSKLoheyggu6G9gxO2hY8p9o6xbaRQ== + dependencies: + jest-get-type "^29.0.0" + pretty-format "^29.0.3" + +jest-matcher-utils@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.0.3.tgz" + integrity sha512-RsR1+cZ6p1hDV4GSCQTg+9qjeotQCgkaleIKLK7dm+U4V/H2bWedU3RAtLm8+mANzZ7eDV33dMar4pejd7047w== + dependencies: + chalk "^4.0.0" + jest-diff "^29.0.3" + jest-get-type "^29.0.0" + pretty-format "^29.0.3" + +jest-message-util@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.3.tgz" + integrity sha512-7T8JiUTtDfppojosORAflABfLsLKMLkBHSWkjNQrjIltGoDzNGn7wEPOSfjqYAGTYME65esQzMJxGDjuLBKdOg== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.0.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.0.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.3.tgz" + integrity sha512-ort9pYowltbcrCVR43wdlqfAiFJXBx8l4uJDsD8U72LgBcetvEp+Qxj1W9ZYgMRoeAo+ov5cnAGF2B6+Oth+ww== + dependencies: + "@jest/types" "^29.0.3" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^28.0.2: + version "28.0.2" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz" + integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== + +jest-regex-util@^29.0.0: + version "29.0.0" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz" + integrity sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug== + +jest-resolve-dependencies@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.0.3.tgz" + integrity sha512-KzuBnXqNvbuCdoJpv8EanbIGObk7vUBNt/PwQPPx2aMhlv/jaXpUJsqWYRpP/0a50faMBY7WFFP8S3/CCzwfDw== + dependencies: + jest-regex-util "^29.0.0" + jest-snapshot "^29.0.3" + +jest-resolve@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.0.3.tgz" + integrity sha512-toVkia85Y/BPAjJasTC9zIPY6MmVXQPtrCk8SmiheC4MwVFE/CMFlOtMN6jrwPMC6TtNh8+sTMllasFeu1wMPg== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.0.3" + jest-pnp-resolver "^1.2.2" + jest-util "^29.0.3" + jest-validate "^29.0.3" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-29.0.3.tgz" + integrity sha512-Usu6VlTOZlCZoNuh3b2Tv/yzDpKqtiNAetG9t3kJuHfUyVMNW7ipCCJOUojzKkjPoaN7Bl1f7Buu6PE0sGpQxw== + dependencies: + "@jest/console" "^29.0.3" + "@jest/environment" "^29.0.3" + "@jest/test-result" "^29.0.3" + "@jest/transform" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.10.2" + graceful-fs "^4.2.9" + jest-docblock "^29.0.0" + jest-environment-node "^29.0.3" + jest-haste-map "^29.0.3" + jest-leak-detector "^29.0.3" + jest-message-util "^29.0.3" + jest-resolve "^29.0.3" + jest-runtime "^29.0.3" + jest-util "^29.0.3" + jest-watcher "^29.0.3" + jest-worker "^29.0.3" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.0.3.tgz" + integrity sha512-12gZXRQ7ozEeEHKTY45a+YLqzNDR/x4c//X6AqwKwKJPpWM8FY4vwn4VQJOcLRS3Nd1fWwgP7LU4SoynhuUMHQ== + dependencies: + "@jest/environment" "^29.0.3" + "@jest/fake-timers" "^29.0.3" + "@jest/globals" "^29.0.3" + "@jest/source-map" "^29.0.0" + "@jest/test-result" "^29.0.3" + "@jest/transform" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.0.3" + jest-message-util "^29.0.3" + jest-mock "^29.0.3" + jest-regex-util "^29.0.0" + jest-resolve "^29.0.3" + jest-snapshot "^29.0.3" + jest-util "^29.0.3" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.0.3.tgz" + integrity sha512-52q6JChm04U3deq+mkQ7R/7uy7YyfVIrebMi6ZkBoDJ85yEjm/sJwdr1P0LOIEHmpyLlXrxy3QP0Zf5J2kj0ew== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.0.3" + "@jest/transform" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/babel__traverse" "^7.0.6" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.0.3" + graceful-fs "^4.2.9" + jest-diff "^29.0.3" + jest-get-type "^29.0.0" + jest-haste-map "^29.0.3" + jest-matcher-utils "^29.0.3" + jest-message-util "^29.0.3" + jest-util "^29.0.3" + natural-compare "^1.4.0" + pretty-format "^29.0.3" + semver "^7.3.5" + +jest-util@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz" + integrity sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-util@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.0.3.tgz" + integrity sha512-Q0xaG3YRG8QiTC4R6fHjHQPaPpz9pJBEi0AeOE4mQh/FuWOijFjGXMMOfQEaU9i3z76cNR7FobZZUQnL6IyfdQ== + dependencies: + "@jest/types" "^29.0.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-29.0.3.tgz" + integrity sha512-OebiqqT6lK8cbMPtrSoS3aZP4juID762lZvpf1u+smZnwTEBCBInan0GAIIhv36MxGaJvmq5uJm7dl5gVt+Zrw== + dependencies: + "@jest/types" "^29.0.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.0.0" + leven "^3.1.0" + pretty-format "^29.0.3" + +jest-watcher@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.0.3.tgz" + integrity sha512-tQX9lU91A+9tyUQKUMp0Ns8xAcdhC9fo73eqA3LFxP2bSgiF49TNcc+vf3qgGYYK9qRjFpXW9+4RgF/mbxyOOw== + dependencies: + "@jest/test-result" "^29.0.3" + "@jest/types" "^29.0.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.10.2" + jest-util "^29.0.3" + string-length "^4.0.1" + +jest-worker@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz" + integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest-worker@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.3.tgz" + integrity sha512-Tl/YWUugQOjoTYwjKdfJWkSOfhufJHO5LhXTSZC3TRoQKO+fuXnZAdoXXBlpLXKGODBL3OvdUasfDD4PcMe6ng== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/jest/-/jest-29.0.3.tgz" + integrity sha512-ElgUtJBLgXM1E8L6K1RW1T96R897YY/3lRYqq9uVcPWtP2AAl/nQ16IYDh/FzQOOQ12VEuLdcPU83mbhG2C3PQ== + dependencies: + "@jest/core" "^29.0.3" + "@jest/types" "^29.0.3" + import-local "^3.0.2" + jest-cli "^29.0.3" + +js-sdsl@^4.1.4: + version "4.1.4" + resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz" + integrity sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +libsodium-wrappers@^0.7.6: + version "0.7.10" + resolved "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz" + integrity sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg== + dependencies: + libsodium "^0.7.0" + +libsodium@^0.7.0: + version "0.7.10" + resolved "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz" + integrity sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.17.12, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0: + version "4.17.21" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +long@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +long@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/long/-/long-5.2.0.tgz" + integrity sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lru-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz" + integrity sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ== + dependencies: + es5-ext "~0.10.2" + +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +memoizee@^0.4.15: + version "0.4.15" + resolved "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz" + integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== + dependencies: + d "^1.0.1" + es5-ext "^0.10.53" + es6-weak-map "^2.0.3" + event-emitter "^0.3.5" + is-promise "^2.2.2" + lru-queue "^0.1.0" + next-tick "^1.1.0" + timers-ext "^0.1.7" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.0" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz" + integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + dependencies: + brace-expansion "^2.0.1" + +minimist@1.2.6, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +mkdirp@1.0.4, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz" + integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ== + +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +next-tick@1, next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.4" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz" + integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== + dependencies: + mimic-fn "^1.0.0" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +osmojs@^0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/osmojs/-/osmojs-0.15.0.tgz#e389b9fd22904b0979821cefc5ec94c291f3c2da" + integrity sha512-A8LxWplqsW6Kwmx1wxU6pK7EKrg+G/ganmEu/EHr4xLxQiDiGD3OfLhV0wAsJcHfaD2IUTWzlzA3RZURATWWLQ== + dependencies: + "@babel/runtime" "^7.19.0" + "@cosmjs/amino" "0.28.13" + "@cosmjs/proto-signing" "0.28.13" + "@cosmjs/stargate" "0.28.13" + "@cosmjs/tendermint-rpc" "^0.28.13" + "@osmonauts/helpers" "^0.4.3" + "@osmonauts/lcd" "^0.4.0" + long "^5.2.0" + protobufjs "^6.11.3" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@^2.0.2: + version "2.0.4" + resolved "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz" + integrity sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-package-name@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/parse-package-name/-/parse-package-name-1.0.0.tgz" + integrity sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.5" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prepend-file@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/prepend-file/-/prepend-file-2.0.1.tgz" + integrity sha512-0hXWjmOpz5YBIk6xujS0lYtCw6IAA0wCR3fw49UGTLc3E9BIhcxgqdMa8rzGvrtt2F8wFiGP42oEpQ8fo9zhRw== + dependencies: + temp-write "^4.0.0" + +prettier@^2.6.2, prettier@^2.7.1: + version "2.7.1" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz" + integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== + +pretty-format@^29.0.0, pretty-format@^29.0.3: + version "29.0.3" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.3.tgz" + integrity sha512-cHudsvQr1K5vNVLbvYF/nv3Qy/F/BcEKxGuIeMiVMRHxPOO1RxXooP8g/ZrwAp7Dx+KdMZoOc7NxLHhMrP2f9Q== + dependencies: + "@jest/schemas" "^29.0.0" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +protobufjs@^6.11.3, protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: + version "6.11.3" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz" + integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +readonly-date@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz" + integrity sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ== + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + +regenerate-unicode-properties@^10.0.1: + version "10.0.1" + resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz" + integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +regenerator-transform@^0.15.0: + version "0.15.0" + resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz" + integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== + dependencies: + "@babel/runtime" "^7.8.4" + +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +regexpu-core@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz" + integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.0.1" + regjsgen "^0.6.0" + regjsparser "^0.8.2" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.0.0" + +regjsgen@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz" + integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== + +regjsparser@^0.8.2: + version "0.8.4" + resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz" + integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== + dependencies: + jsesc "~0.5.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + +resolve@^1.1.6, resolve@^1.14.2, resolve@^1.20.0: + version "1.22.1" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz" + integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@3.0.2, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-async@^2.2.0, run-async@^2.3.0: + version "2.4.1" + resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz" + integrity sha512-3xPNZGW93oCjiO7PtKxRK6iOVYBWBvtf9QHDfU23Oc+dLIQmAV//UnyXV/yihv81VS/UqoQPk4NegS8EFi55Hg== + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz" + integrity sha512-Cun9QucwK6MIrp3mry/Y7hqD1oFqTYLQ4pGxaHTjIdaFDWRGGLikqp6u8LcWJnzpoALg9hap+JGk8sFIUuEGNA== + +rxjs@^6.4.0: + version "6.6.7" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.5, semver@^7.3.7: + version "7.3.7" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shelljs@0.8.5: + version "0.8.5" + resolved "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.5" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^2.0.0, string-width@^2.1.0: + version "2.1.1" + resolved "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz" + integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow== + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.1.0: + version "5.2.0" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.3.0" + resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +symbol-observable@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz" + integrity sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA== + +temp-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz" + integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== + +temp-write@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/temp-write/-/temp-write-4.0.0.tgz" + integrity sha512-HIeWmj77uOOHb0QX7siN3OtwV3CTntquin6TNVg6SHOqCP3hYKmox90eeFOGaY1MqJ9WYDDjkyZrW6qS5AWpbw== + dependencies: + graceful-fs "^4.1.15" + is-stream "^2.0.0" + make-dir "^3.0.0" + temp-dir "^1.0.0" + uuid "^3.3.2" + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +timers-ext@^0.1.7: + version "0.1.7" + resolved "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz" + integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== + dependencies: + es5-ext "~0.10.46" + next-tick "1" + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tslib@^1.8.1, tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type@^1.0.1: + version "1.2.0" + resolved "https://registry.npmjs.org/type/-/type-1.2.0.tgz" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.7.2: + version "2.7.2" + resolved "https://registry.npmjs.org/type/-/type-2.7.2.tgz" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + +typescript@^4.8.3: + version "4.8.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz" + integrity sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig== + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz" + integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz" + integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== + +update-browserslist-db@^1.0.5: + version "1.0.7" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz" + integrity sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util@^0.10.3: + version "0.10.4" + resolved "https://registry.npmjs.org/util/-/util-0.10.4.tgz" + integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== + dependencies: + inherits "2.0.3" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +v8-to-istanbul@^9.0.1: + version "9.0.1" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz" + integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +wasm-ast-types@^0.11.3: + version "0.11.3" + resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.11.3.tgz#d91b6456e11b3cb23d77b34e5156d13d2ead3a22" + integrity sha512-rNJNKznUMgt8Bb01xPoyjM13VQmXzDIBG49oV09k4RusRELZu7pQWejBy08lV5BQjcIYKpWkqiUnAZJp73ZmOA== + dependencies: + "@babel/runtime" "^7.18.9" + "@babel/types" "7.18.10" + "@jest/transform" "28.1.3" + ast-stringify "0.1.0" + case "1.6.3" + deepmerge "4.2.2" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +ws@^7: + version "7.5.9" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + +xstream@^11.14.0: + version "11.14.0" + resolved "https://registry.npmjs.org/xstream/-/xstream-11.14.0.tgz" + integrity sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw== + dependencies: + globalthis "^1.0.1" + symbol-observable "^2.0.3" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^21.0.0: + version "21.1.1" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.5.1" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz" + integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From a907dbe27d5f34ae0896ad069f3b8dbf4f2f35bc Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 19 Sep 2022 14:17:29 +0200 Subject: [PATCH 057/218] House cleaning smorgasbord (#20) * coins refactor * Update tokenid naming to account id * Updating build scripts --- Cargo.lock | 222 +++++-------- contracts/account-nft/Cargo.toml | 6 +- contracts/account-nft/src/contract.rs | 2 +- contracts/account-nft/src/msg/execute.rs | 4 +- contracts/account-nft/src/msg/query.rs | 6 +- contracts/account-nft/tests/test_mint.rs | 4 +- contracts/credit-manager/Cargo.toml | 12 +- contracts/credit-manager/src/borrow.rs | 6 +- contracts/credit-manager/src/contract.rs | 15 +- contracts/credit-manager/src/deposit.rs | 4 +- contracts/credit-manager/src/execute.rs | 88 +++--- contracts/credit-manager/src/health.rs | 17 +- contracts/credit-manager/src/liquidate.rs | 35 +- contracts/credit-manager/src/query.rs | 56 ++-- contracts/credit-manager/src/repay.rs | 20 +- contracts/credit-manager/src/state.rs | 12 +- contracts/credit-manager/src/swap.rs | 7 +- .../src/update_coin_balances.rs | 7 +- contracts/credit-manager/src/utils.rs | 8 +- contracts/credit-manager/src/vault/deposit.rs | 11 +- contracts/credit-manager/src/vault/utils.rs | 4 +- .../credit-manager/src/vault/withdraw.rs | 7 +- contracts/credit-manager/src/withdraw.rs | 4 +- .../credit-manager/tests/helpers/mock_env.rs | 18 +- .../credit-manager/tests/helpers/types.rs | 9 +- contracts/credit-manager/tests/test_borrow.rs | 97 +++--- .../tests/test_coin_balances.rs | 58 ++-- .../tests/test_create_credit_account.rs | 8 +- .../credit-manager/tests/test_deposit.rs | 97 +++--- .../credit-manager/tests/test_dispatch.rs | 23 +- .../tests/test_enumerate_coin_balances.rs | 96 +++--- .../tests/test_enumerate_debt_shares.rs | 123 ++++--- .../tests/test_enumerate_total_debt_shares.rs | 105 +++--- .../test_enumerate_vault_coin_balances.rs | 50 ++- .../tests/test_enumerate_vault_positions.rs | 60 ++-- contracts/credit-manager/tests/test_health.rs | 149 +++++---- .../tests/test_liquidate_coin.rs | 256 +++++++-------- contracts/credit-manager/tests/test_repay.rs | 118 ++++--- contracts/credit-manager/tests/test_swap.rs | 56 ++-- .../tests/test_vault_deposit.rs | 78 ++--- .../tests/test_vault_withdraw.rs | 88 ++---- .../credit-manager/tests/test_withdraw.rs | 130 ++++---- contracts/mock-red-bank/Cargo.toml | 2 +- contracts/mock-vault/Cargo.toml | 2 +- contracts/mock-vault/src/state.rs | 3 +- contracts/swapper/base/Cargo.toml | 4 +- contracts/swapper/mock/Cargo.toml | 6 +- contracts/swapper/osmosis/Cargo.toml | 6 +- packages/rover/Cargo.toml | 2 +- packages/rover/src/adapters/vault.rs | 3 +- packages/rover/src/error.rs | 10 +- packages/rover/src/lib.rs | 7 - packages/rover/src/msg/execute.rs | 30 +- packages/rover/src/msg/query.rs | 20 +- schemas/account-nft/account-nft.json | 65 ++-- schemas/credit-manager/credit-manager.json | 174 +++++----- schemas/mock-red-bank/mock-red-bank.json | 22 +- schemas/swapper-base/swapper-base.json | 2 +- scripts/deploy/addresses/osmo-test-4.json | 12 +- scripts/deploy/base/deployer.ts | 2 +- scripts/deploy/base/rover.ts | 18 +- scripts/package.json | 9 +- .../account-nft/AccountNft.client.ts | 2 +- .../account-nft/AccountNft.react-query.ts | 2 +- .../generated/account-nft/AccountNft.types.ts | 18 +- scripts/types/generated/account-nft/bundle.ts | 2 +- .../credit-manager/CreditManager.client.ts | 24 +- .../CreditManager.react-query.ts | 12 +- .../credit-manager/CreditManager.types.ts | 44 +-- .../types/generated/credit-manager/bundle.ts | 2 +- .../mock-oracle/MockOracle.client.ts | 2 +- .../mock-oracle/MockOracle.react-query.ts | 2 +- .../generated/mock-oracle/MockOracle.types.ts | 2 +- scripts/types/generated/mock-oracle/bundle.ts | 2 +- .../mock-red-bank/MockRedBank.client.ts | 3 +- .../mock-red-bank/MockRedBank.react-query.ts | 3 +- .../mock-red-bank/MockRedBank.types.ts | 5 +- .../types/generated/mock-red-bank/bundle.ts | 2 +- .../generated/mock-vault/MockVault.client.ts | 2 +- .../mock-vault/MockVault.react-query.ts | 2 +- .../generated/mock-vault/MockVault.types.ts | 2 +- scripts/types/generated/mock-vault/bundle.ts | 2 +- .../swapper-base/SwapperBase.client.ts | 2 +- .../swapper-base/SwapperBase.react-query.ts | 2 +- .../swapper-base/SwapperBase.types.ts | 2 +- .../types/generated/swapper-base/bundle.ts | 2 +- scripts/yarn.lock | 299 ++++++------------ 87 files changed, 1357 insertions(+), 1660 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c34cf888..1826b554c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "account-nft" -version = "0.1.0" +version = "1.0.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -19,9 +19,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.62" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "base16ct" @@ -85,31 +85,31 @@ checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" [[package]] name = "cosmwasm-crypto" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c50d753d44148c7ff3279ac44b87b5b91e790f4c70db0e3900df211310a2bf" +checksum = "10cb32eb596428347033db2da3028558ef8ec506ae00605932eef4b24972baa2" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", "ed25519-zebra", "k256", - "rand_core 0.6.3", + "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9053ebe2ad85831e9f9e2124fa2b22807528a78b25cc447483ce2a4aadfba394" +checksum = "0faf3a02389f78d6173b7e680751205015d5406f8abbaa9aa36fd216adc9f10d" dependencies = [ "syn", ] [[package]] name = "cosmwasm-schema" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c742fc698a88cf02ea304cc2b5bc18ef975c5bb9eff93c3e44d2cd565e1d458" +checksum = "d5d13e9ee950418b0ac90a2579b8769640e0b07e6d06d9d5f5b512ba64265e5a" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -120,9 +120,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a7c4c07be11add09dd3af3064c4f4cbc2dc99c6859129bdaf820131730e996" +checksum = "73d5e04d338db6af813d0633fe9ab6a5cd36ae83796ca769ae13d6910a5861df" dependencies = [ "proc-macro2", "quote", @@ -131,9 +131,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb8da0ae28693d892af2944319b48adc23c42725dc0fe7271b8baa38c10865da" +checksum = "b3409b349f282924c8099b554aae6fe70e4eb97d6a64697ae13c8be25a7eb158" dependencies = [ "base64", "cosmwasm-crypto", @@ -149,9 +149,9 @@ dependencies = [ [[package]] name = "cosmwasm-storage" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3205d5210ba4c7b7cc7329b9607c37d00dc08263c2479fd99541a2194ebe3b22" +checksum = "f024880dd46c053f30dd31f3b3aab8197b5cfaafe86c9e302c845df0cff44a0a" dependencies = [ "cosmwasm-std", "serde", @@ -159,16 +159,16 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] [[package]] name = "credit-manager" -version = "0.1.0" +version = "1.0.0" dependencies = [ "account-nft", "anyhow", @@ -176,7 +176,7 @@ dependencies = [ "cosmwasm-std", "cw-multi-test 0.15.0", "cw-storage-plus 0.15.0", - "cw2 0.15.0", + "cw2", "cw721", "cw721-base", "itertools", @@ -203,7 +203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f2b443d17d49dad5ef0ede301c3179cc923b8822f3393b4d2c28c269dd4a122" dependencies = [ "generic-array", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -280,17 +280,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cw-storage-plus" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c8b264257c4f44c49b7ce09377af63aa040768ecd3fd7bdd2d48a09323a1e90" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - [[package]] name = "cw-storage-plus" version = "0.15.0" @@ -314,20 +303,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw-utils" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414b91f3d7a619bb26c835119d7095804596a1382ddc1d184c33c1d2c17f6c5e" -dependencies = [ - "cosmwasm-std", - "cw2 0.14.0", - "schemars", - "semver", - "serde", - "thiserror", -] - [[package]] name = "cw-utils" version = "0.15.0" @@ -336,37 +311,13 @@ checksum = "7a67007ff056f4cd034f361c8ed69780c0180959b9c8037c84f3caa78120faf5" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 0.15.0", + "cw2", "schemars", "semver", "serde", "thiserror", ] -[[package]] -name = "cw2" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus 0.13.4", - "schemars", - "serde", -] - -[[package]] -name = "cw2" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa74c324af8e3506fd8d50759a265bead3f87402e413c840042af5d2808463d6" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus 0.14.0", - "schemars", - "serde", -] - [[package]] name = "cw2" version = "0.15.0" @@ -380,57 +331,30 @@ dependencies = [ "serde", ] -[[package]] -name = "cw20" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f446f59c519fbac5ab8b9f6c7f8dcaa05ee761703971406b28221ea778bb737" -dependencies = [ - "cosmwasm-std", - "cw-utils 0.14.0", - "schemars", - "serde", -] - -[[package]] -name = "cw20-base" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e39bf97c985a50f2e340833137b3f14999f58708c4ca9928cd8f87d530c4109c" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus 0.14.0", - "cw-utils 0.14.0", - "cw2 0.14.0", - "cw20", - "schemars", - "semver", - "serde", - "thiserror", -] - [[package]] name = "cw721" -version = "0.13.4" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "035818368a74c07dd9ed5c5a93340199ba251530162010b9f34c3809e3b97df1" +checksum = "20dfe04f86e5327956b559ffcc86d9a43167391f37402afd8bf40b0be16bee4d" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", - "cw-utils 0.13.4", + "cw-utils 0.15.0", "schemars", "serde", ] [[package]] name = "cw721-base" -version = "0.13.4" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "423d4efe8b649d228d1533e141c238415f49aa8a9ee4e40fce192d7a93ffd057" +checksum = "62c3ee3b669fc2a8094301a73fd7be97a7454d4df2650c33599f737e8f254d24" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.13.4", - "cw-utils 0.13.4", - "cw2 0.13.4", + "cw-storage-plus 0.15.0", + "cw-utils 0.15.0", + "cw2", "cw721", "schemars", "serde", @@ -469,9 +393,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -486,9 +410,9 @@ checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" [[package]] name = "ecdsa" -version = "0.14.5" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1826508d57f3140a2e8e3c307b19915a266c92a1b8c2f6bb54e29e5d72a394ae" +checksum = "85789ce7dfbd0f0624c07ef653a08bb2ebf43d3e16531361f46d36dd54334fed" dependencies = [ "der", "elliptic-curve", @@ -504,7 +428,7 @@ checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" dependencies = [ "curve25519-dalek", "hex", - "rand_core 0.6.3", + "rand_core 0.6.4", "serde", "sha2 0.9.9", "thiserror", @@ -526,12 +450,12 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.3", + "digest 0.10.5", "ff", "generic-array", "group", "pkcs8", - "rand_core 0.6.3", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -543,7 +467,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df689201f395c6b90dfe87127685f8dbfc083a5e779e613575d8bd7314300c3e" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] @@ -592,7 +516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7391856def869c1c81063a03457c676fbcd419709c3dfb33d8d319de484b154d" dependencies = [ "ff", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] @@ -608,14 +532,14 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", ] [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "d8bf247779e67a9082a4790b45e71ac7cfd1321331a5c856a74a9faebdab78d0" dependencies = [ "either", ] @@ -628,14 +552,14 @@ checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "k256" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2573d3fd3e4cc741affc9b5ce1a8ce36cf29f09f80f36da4309d0ae6d7854" +checksum = "3636d281d46c3b64182eb3a0a42b7b483191a2ecc3f05301fa67403f7c9bc949" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "sha2 0.10.5", + "sha2 0.10.6", ] [[package]] @@ -657,8 +581,6 @@ name = "mars-outpost" version = "0.1.0" dependencies = [ "cosmwasm-std", - "cw20", - "cw20-base", "schemars", "serde", "thiserror", @@ -682,7 +604,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.14.0", + "cw-storage-plus 0.15.0", "mars-outpost", "schemars", "serde", @@ -797,9 +719,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.7", ] @@ -817,7 +739,7 @@ dependencies = [ [[package]] name = "rover" -version = "0.1.0" +version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -877,9 +799,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "serde" @@ -947,23 +869,23 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] name = "signature" -version = "1.6.0" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ea32af43239f0d353a7dd75a22d94c329c8cdaafdcb4c1c1335aa10c298a4a" +checksum = "deb766570a2825fa972bceff0d195727876a9cdf2460ab2e52d455dc2de47fd9" dependencies = [ - "digest 0.10.3", - "rand_core 0.6.3", + "digest 0.10.5", + "rand_core 0.6.4", ] [[package]] @@ -990,7 +912,7 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "swapper-base" -version = "0.1.0" +version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1003,7 +925,7 @@ dependencies = [ [[package]] name = "swapper-mock" -version = "0.1.0" +version = "1.0.0" dependencies = [ "anyhow", "cosmwasm-std", @@ -1018,7 +940,7 @@ dependencies = [ [[package]] name = "swapper-osmosis" -version = "0.1.0" +version = "1.0.0" dependencies = [ "anyhow", "cosmwasm-std", @@ -1035,9 +957,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" dependencies = [ "proc-macro2", "quote", @@ -1046,18 +968,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" +checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" +checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" dependencies = [ "proc-macro2", "quote", @@ -1084,9 +1006,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "version_check" diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 431ddb98c..b77e335dc 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "account-nft" -version = "0.1.0" +version = "1.0.0" authors = ["larry_0x , grod220 "] edition = "2021" license = "GPL-3.0-or-later" @@ -15,8 +15,8 @@ library = [] [dependencies] cw-storage-plus = "0.15" -cw721 = "0.13" -cw721-base = { version = "0.13", features = ["library"] } +cw721 = "0.15" +cw721-base = { version = "0.15", features = ["library"] } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" schemars = "0.8" diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 6a4a41d75..b03c03571 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -12,7 +12,7 @@ use crate::msg::{ExecuteMsg, QueryMsg}; use crate::query::query_proposed_new_owner; // Extending CW721 base contract -pub type Parent<'a> = Cw721Contract<'a, Empty, Empty>; +pub type Parent<'a> = Cw721Contract<'a, Empty, Empty, Empty, Empty>; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( diff --git a/contracts/account-nft/src/msg/execute.rs b/contracts/account-nft/src/msg/execute.rs index 38225214b..9dff120fa 100644 --- a/contracts/account-nft/src/msg/execute.rs +++ b/contracts/account-nft/src/msg/execute.rs @@ -56,10 +56,10 @@ pub enum ExecuteMsg { Burn { token_id: String }, } -impl TryInto> for ExecuteMsg { +impl TryInto> for ExecuteMsg { type Error = ContractError; - fn try_into(self) -> Result, Self::Error> { + fn try_into(self) -> Result, Self::Error> { match self { ExecuteMsg::TransferNft { recipient, diff --git a/contracts/account-nft/src/msg/query.rs b/contracts/account-nft/src/msg/query.rs index 904447d12..8e593bbb1 100644 --- a/contracts/account-nft/src/msg/query.rs +++ b/contracts/account-nft/src/msg/query.rs @@ -1,7 +1,7 @@ use std::convert::TryInto; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::StdError; +use cosmwasm_std::{Empty, StdError}; use cw721_base::QueryMsg as ParentQueryMsg; #[cw_serde] @@ -92,10 +92,10 @@ pub enum QueryMsg { Minter {}, } -impl TryInto for QueryMsg { +impl TryInto> for QueryMsg { type Error = StdError; - fn try_into(self) -> Result { + fn try_into(self) -> Result, Self::Error> { match self { QueryMsg::OwnerOf { token_id, diff --git a/contracts/account-nft/tests/test_mint.rs b/contracts/account-nft/tests/test_mint.rs index bdcea23c7..4b38f7331 100644 --- a/contracts/account-nft/tests/test_mint.rs +++ b/contracts/account-nft/tests/test_mint.rs @@ -1,6 +1,6 @@ use std::fmt::Error; -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Empty}; use cw721::OwnerOfResponse; use cw721_base::QueryMsg; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; @@ -71,7 +71,7 @@ fn assert_owner_is_correct(app: &mut BasicApp, contract_addr: &Addr, user: &Addr .wrap() .query_wasm_smart( contract_addr, - &QueryMsg::OwnerOf { + &QueryMsg::::OwnerOf { token_id: token_id.to_string(), include_expired: None, }, diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 7bac64d22..8eeb4a24f 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "credit-manager" -version = "0.1.0" +version = "1.0.0" authors = ["grod220 , larry_0x "] edition = "2021" license = "GPL-3.0-or-later" @@ -14,24 +14,24 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -account-nft = { version = "0.1", path = "../account-nft", features = ["library"] } +account-nft = { version = "1.0", path = "../account-nft", features = ["library"] } mars-health = { version = "0.1", path = "../../../outposts/packages/health" } mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } mock-vault = { version = "1.0", path = "../../contracts/mock-vault", features = ["library"] } -rover = { version = "0.1", path = "../../packages/rover" } +rover = { version = "1.0", path = "../../packages/rover" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" cw2 = "0.15" -cw721 = "0.13" -cw721-base = { version = "0.13", features = ["library"] } +cw721 = "0.15" +cw721-base = { version = "0.15", features = ["library"] } cw-storage-plus = "0.15" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } [dev-dependencies] -swapper-mock = { version = "0.1", path = "../../contracts/swapper/mock", features = ["library"] } +swapper-mock = { version = "1.0", path = "../../contracts/swapper/mock", features = ["library"] } anyhow = "1" cw-multi-test = "0.15" diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index 195c4ee77..4a8e4b148 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -12,7 +12,7 @@ pub static DEFAULT_DEBT_SHARES_PER_COIN_BORROWED: Uint128 = Uint128::new(1_000_0 /// else, get debt ownership % and multiply by total existing shares /// /// increment total debt shares, token debt shares, and asset amount -pub fn borrow(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractResult { +pub fn borrow(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> ContractResult { if coin.amount.is_zero() { return Err(ContractError::NoAmount); } @@ -39,14 +39,14 @@ pub fn borrow(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRe .map_err(ContractError::Overflow) })?; - DEBT_SHARES.update(deps.storage, (token_id, &coin.denom), |shares| { + DEBT_SHARES.update(deps.storage, (account_id, &coin.denom), |shares| { shares .unwrap_or_else(Uint128::zero) .checked_add(debt_shares_to_add) .map_err(ContractError::Overflow) })?; - increment_coin_balance(deps.storage, token_id, &coin)?; + increment_coin_balance(deps.storage, account_id, &coin)?; Ok(Response::new() .add_message(red_bank.borrow_msg(&coin)?) diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 695963fec..d10c03f62 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -41,9 +41,10 @@ pub fn execute( ExecuteMsg::CreateCreditAccount {} => create_credit_account(deps, info.sender), ExecuteMsg::UpdateConfig { new_config } => update_config(deps, info, new_config), ExecuteMsg::Callback(callback) => execute_callback(deps, info, env, callback), - ExecuteMsg::UpdateCreditAccount { token_id, actions } => { - dispatch_actions(deps, env, info, &token_id, &actions) - } + ExecuteMsg::UpdateCreditAccount { + account_id, + actions, + } => dispatch_actions(deps, env, info, &account_id, &actions), } } @@ -57,11 +58,11 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { QueryMsg::AllowedCoins { start_after, limit } => { to_binary(&query_allowed_coins(deps, start_after, limit)?) } - QueryMsg::Positions { token_id } => { - to_binary(&query_position_with_value(deps, &env, &token_id)?) + QueryMsg::Positions { account_id } => { + to_binary(&query_position_with_value(deps, &env, &account_id)?) } - QueryMsg::Health { token_id } => { - to_binary::(&Into::into(compute_health(deps, &env, &token_id)?)) + QueryMsg::Health { account_id } => { + to_binary::(&Into::into(compute_health(deps, &env, &account_id)?)) } QueryMsg::AllCoinBalances { start_after, limit } => { to_binary(&query_all_coin_balances(deps, start_after, limit)?) diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index 7cdf3876d..a9cfe7981 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -8,7 +8,7 @@ use crate::utils::{assert_coin_is_whitelisted, increment_coin_balance}; pub fn deposit( storage: &mut dyn Storage, response: Response, - nft_token_id: &str, + account_id: &str, coin: &Coin, received_coins: &mut Coins, ) -> ContractResult { @@ -22,7 +22,7 @@ pub fn deposit( received_coins.deduct(coin)?; - increment_coin_balance(storage, nft_token_id, coin)?; + increment_coin_balance(storage, account_id, coin)?; Ok(response .add_attribute("action", "rover/credit_manager/callback/deposit") diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index f92cf0bdc..8edf7b119 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -144,10 +144,10 @@ pub fn dispatch_actions( deps: DepsMut, env: Env, info: MessageInfo, - token_id: &str, + account_id: &str, actions: &[Action], ) -> ContractResult { - assert_is_token_owner(&deps, &info.sender, token_id)?; + assert_is_token_owner(&deps, &info.sender, account_id)?; let mut response = Response::new(); let mut callbacks: Vec = vec![]; @@ -156,36 +156,42 @@ pub fn dispatch_actions( for action in actions { match action { Action::Deposit(coin) => { - response = deposit(deps.storage, response, token_id, coin, &mut received_coins)?; + response = deposit( + deps.storage, + response, + account_id, + coin, + &mut received_coins, + )?; } Action::Withdraw(coin) => callbacks.push(CallbackMsg::Withdraw { - token_id: token_id.to_string(), + account_id: account_id.to_string(), coin: coin.clone(), recipient: info.sender.clone(), }), Action::Borrow(coin) => callbacks.push(CallbackMsg::Borrow { - token_id: token_id.to_string(), + account_id: account_id.to_string(), coin: coin.clone(), }), Action::Repay(coin) => callbacks.push(CallbackMsg::Repay { - token_id: token_id.to_string(), + account_id: account_id.to_string(), coin: coin.clone(), }), Action::VaultDeposit { vault, coins: assets, } => callbacks.push(CallbackMsg::VaultDeposit { - token_id: token_id.to_string(), + account_id: account_id.to_string(), vault: vault.check(deps.api)?, coins: assets.clone(), }), Action::LiquidateCoin { - liquidatee_token_id, + liquidatee_account_id, debt_coin, request_coin_denom, } => callbacks.push(CallbackMsg::LiquidateCoin { - liquidator_token_id: token_id.to_string(), - liquidatee_token_id: liquidatee_token_id.to_string(), + liquidator_account_id: account_id.to_string(), + liquidatee_account_id: liquidatee_account_id.to_string(), debt_coin: debt_coin.clone(), request_coin_denom: request_coin_denom.clone(), }), @@ -194,13 +200,13 @@ pub fn dispatch_actions( denom_out, slippage, } => callbacks.push(CallbackMsg::SwapExactIn { - token_id: token_id.to_string(), + account_id: account_id.to_string(), coin_in: coin_in.clone(), denom_out: denom_out.to_string(), slippage: *slippage, }), Action::VaultWithdraw { vault, amount } => callbacks.push(CallbackMsg::VaultWithdraw { - token_id: token_id.to_string(), + account_id: account_id.to_string(), vault: vault.check(deps.api)?, amount: *amount, }), @@ -215,7 +221,7 @@ pub fn dispatch_actions( // after user selected actions, we assert LTV is healthy; if not, throw error and revert all actions callbacks.extend([CallbackMsg::AssertBelowMaxLTV { - token_id: token_id.to_string(), + account_id: account_id.to_string(), }]); let callback_msgs = callbacks @@ -239,77 +245,77 @@ pub fn execute_callback( } match callback { CallbackMsg::Withdraw { - token_id, + account_id, coin, recipient, - } => withdraw(deps, &token_id, coin, recipient), - CallbackMsg::Borrow { coin, token_id } => borrow(deps, env, &token_id, coin), - CallbackMsg::Repay { token_id, coin } => repay(deps, env, &token_id, coin), - CallbackMsg::AssertBelowMaxLTV { token_id } => { - assert_below_max_ltv(deps.as_ref(), env, &token_id) + } => withdraw(deps, &account_id, coin, recipient), + CallbackMsg::Borrow { coin, account_id } => borrow(deps, env, &account_id, coin), + CallbackMsg::Repay { account_id, coin } => repay(deps, env, &account_id, coin), + CallbackMsg::AssertBelowMaxLTV { account_id } => { + assert_below_max_ltv(deps.as_ref(), env, &account_id) } CallbackMsg::VaultDeposit { - token_id, + account_id, vault, coins, - } => deposit_into_vault(deps, &env.contract.address, &token_id, vault, &coins), + } => deposit_into_vault(deps, &env.contract.address, &account_id, vault, &coins), CallbackMsg::UpdateVaultCoinBalance { vault, - token_id, + account_id, previous_total_balance, } => update_vault_coin_balance( deps, vault, - &token_id, + &account_id, previous_total_balance, &env.contract.address, ), CallbackMsg::LiquidateCoin { - liquidator_token_id, - liquidatee_token_id, + liquidator_account_id, + liquidatee_account_id, debt_coin, request_coin_denom, } => liquidate_coin( deps, env, - &liquidator_token_id, - &liquidatee_token_id, + &liquidator_account_id, + &liquidatee_account_id, debt_coin, &request_coin_denom, ), CallbackMsg::AssertHealthFactorImproved { - token_id, + account_id, previous_health_factor, - } => assert_health_factor_improved(deps.as_ref(), env, &token_id, previous_health_factor), + } => assert_health_factor_improved(deps.as_ref(), env, &account_id, previous_health_factor), CallbackMsg::SwapExactIn { - token_id, + account_id, coin_in, denom_out, slippage, - } => swap_exact_in(deps, env, &token_id, coin_in, &denom_out, slippage), + } => swap_exact_in(deps, env, &account_id, coin_in, &denom_out, slippage), CallbackMsg::UpdateCoinBalances { - token_id, + account_id, previous_balances, - } => update_coin_balances(deps, env, &token_id, &previous_balances), + } => update_coin_balances(deps, env, &account_id, &previous_balances), CallbackMsg::VaultWithdraw { - token_id, + account_id, vault, amount, - } => withdraw_from_vault(deps, env, &token_id, vault, amount, false), + } => withdraw_from_vault(deps, env, &account_id, vault, amount, false), CallbackMsg::VaultForceWithdraw { - token_id, + account_id, vault, amount, - } => withdraw_from_vault(deps, env, &token_id, vault, amount, true), + } => withdraw_from_vault(deps, env, &account_id, vault, amount, true), } } -pub fn assert_is_token_owner(deps: &DepsMut, user: &Addr, token_id: &str) -> ContractResult<()> { +pub fn assert_is_token_owner(deps: &DepsMut, user: &Addr, account_id: &str) -> ContractResult<()> { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; let owner_res: OwnerOfResponse = deps.querier.query_wasm_smart( contract_addr, - &QueryMsg::OwnerOf { - token_id: token_id.to_string(), + &QueryMsg::::OwnerOf { + token_id: account_id.to_string(), include_expired: None, }, )?; @@ -317,7 +323,7 @@ pub fn assert_is_token_owner(deps: &DepsMut, user: &Addr, token_id: &str) -> Con if user != &owner_res.owner { return Err(ContractError::NotTokenOwner { user: user.to_string(), - token_id: token_id.to_string(), + account_id: account_id.to_string(), }); } diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index ac25993a5..de6f51368 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -2,14 +2,13 @@ use cosmwasm_std::{Decimal, Deps, Env, Event, Response}; use mars_health::health::Health; use rover::error::{ContractError, ContractResult}; -use rover::NftTokenId; use crate::query::query_position; use crate::state::{ORACLE, RED_BANK}; use crate::utils::debt_shares_to_amount; -pub fn compute_health(deps: Deps, env: &Env, token_id: NftTokenId) -> ContractResult { - let res = query_position(deps, token_id)?; +pub fn compute_health(deps: Deps, env: &Env, account_id: &str) -> ContractResult { + let res = query_position(deps, account_id)?; let debt_amounts = res .debt .iter() @@ -29,16 +28,12 @@ pub fn compute_health(deps: Deps, env: &Env, token_id: NftTokenId) -> ContractRe Ok(health) } -pub fn assert_below_max_ltv( - deps: Deps, - env: Env, - token_id: NftTokenId, -) -> ContractResult { - let health = compute_health(deps, &env, token_id)?; +pub fn assert_below_max_ltv(deps: Deps, env: Env, account_id: &str) -> ContractResult { + let health = compute_health(deps, &env, account_id)?; if health.is_above_max_ltv() { return Err(ContractError::AboveMaxLTV { - token_id: token_id.to_string(), + account_id: account_id.to_string(), max_ltv_health_factor: val_or_na(health.max_ltv_health_factor), }); } @@ -46,7 +41,7 @@ pub fn assert_below_max_ltv( let event = Event::new("position_changed") .add_attribute("timestamp", env.block.time.seconds().to_string()) .add_attribute("height", env.block.height.to_string()) - .add_attribute("token_id", token_id) + .add_attribute("account_id", account_id) .add_attribute("assets_value", health.total_collateral_value.to_string()) .add_attribute("debts_value", health.total_debt_value.to_string()) .add_attribute( diff --git a/contracts/credit-manager/src/liquidate.rs b/contracts/credit-manager/src/liquidate.rs index c8461921d..06f8c62d0 100644 --- a/contracts/credit-manager/src/liquidate.rs +++ b/contracts/credit-manager/src/liquidate.rs @@ -5,7 +5,6 @@ use mars_health::health::Health; use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; -use rover::NftTokenId; use crate::health::{compute_health, val_or_na}; use crate::repay::current_debt_for_denom; @@ -15,16 +14,16 @@ use crate::utils::{decrement_coin_balance, increment_coin_balance, IntoUint128}; pub fn liquidate_coin( deps: DepsMut, env: Env, - liquidator_token_id: &str, - liquidatee_token_id: &str, + liquidator_account_id: &str, + liquidatee_account_id: &str, debt_coin: Coin, request_coin_denom: &str, ) -> ContractResult { // Assert the liquidatee's credit account is liquidatable - let health = compute_health(deps.as_ref(), &env, liquidatee_token_id)?; + let health = compute_health(deps.as_ref(), &env, liquidatee_account_id)?; if !health.is_liquidatable() { return Err(ContractError::NotLiquidatable { - token_id: liquidatee_token_id.to_string(), + account_id: liquidatee_account_id.to_string(), lqdt_health_factor: val_or_na(health.liquidation_health_factor), }); } @@ -32,7 +31,7 @@ pub fn liquidate_coin( let (debt, request) = calculate_liquidation_request( &deps, &env, - liquidatee_token_id, + liquidatee_account_id, &debt_coin, request_coin_denom, &health, @@ -40,21 +39,21 @@ pub fn liquidate_coin( // Transfer debt coin from liquidator's coin balance to liquidatee // Will be used to pay off the debt via CallbackMsg::Repay {} - decrement_coin_balance(deps.storage, liquidator_token_id, &debt)?; - increment_coin_balance(deps.storage, liquidatee_token_id, &debt)?; + decrement_coin_balance(deps.storage, liquidator_account_id, &debt)?; + increment_coin_balance(deps.storage, liquidatee_account_id, &debt)?; let repay_msg = (CallbackMsg::Repay { - token_id: liquidatee_token_id.to_string(), + account_id: liquidatee_account_id.to_string(), coin: debt.clone(), }) .into_cosmos_msg(&env.contract.address)?; // Transfer requested coin from liquidatee to liquidator - decrement_coin_balance(deps.storage, liquidatee_token_id, &request)?; - increment_coin_balance(deps.storage, liquidator_token_id, &request)?; + decrement_coin_balance(deps.storage, liquidatee_account_id, &request)?; + increment_coin_balance(deps.storage, liquidator_account_id, &request)?; // Ensure health factor has improved as a consequence of liquidation event let assert_healthier_msg = (CallbackMsg::AssertHealthFactorImproved { - token_id: liquidatee_token_id.to_string(), + account_id: liquidatee_account_id.to_string(), previous_health_factor: health.liquidation_health_factor.unwrap(), // safe unwrap given it was liquidatable }) .into_cosmos_msg(&env.contract.address)?; @@ -63,7 +62,7 @@ pub fn liquidate_coin( .add_message(repay_msg) .add_message(assert_healthier_msg) .add_attribute("action", "rover/credit_manager/liquidate") - .add_attribute("liquidatee_token_id", liquidatee_token_id) + .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("debt_repaid_denom", debt.denom) .add_attribute("debt_repaid_amount", debt.amount) .add_attribute("request_coin_denom", request.denom) @@ -78,14 +77,14 @@ pub fn liquidate_coin( fn calculate_liquidation_request( deps: &DepsMut, env: &Env, - liquidatee_token_id: &str, + liquidatee_account_id: &str, debt_coin: &Coin, request_coin: &str, health: &Health, ) -> ContractResult<(Coin, Coin)> { // Ensure debt repaid does not exceed liquidatee's total debt for denom let (total_debt_amount, _) = - current_debt_for_denom(deps.as_ref(), env, liquidatee_token_id, debt_coin)?; + current_debt_for_denom(deps.as_ref(), env, liquidatee_account_id, debt_coin)?; // Ensure debt amount does not exceed close factor % of the liquidatee's total debt value let close_factor = MAX_CLOSE_FACTOR.load(deps.storage)?; @@ -97,7 +96,7 @@ fn calculate_liquidation_request( // Calculate the maximum debt possible to repay given liquidatee's request coin balance // FORMULA: debt amount = request value / (1 + liquidation bonus %) / debt price let liquidatee_balance = COIN_BALANCES - .load(deps.storage, (liquidatee_token_id, request_coin)) + .load(deps.storage, (liquidatee_account_id, request_coin)) .map_err(|_| ContractError::CoinNotAvailable(request_coin.to_string()))?; let request_res = oracle.query_price(&deps.querier, request_coin)?; let balance_dec = Decimal::from_atomics(liquidatee_balance, 0)?; @@ -143,10 +142,10 @@ fn calculate_liquidation_request( pub fn assert_health_factor_improved( deps: Deps, env: Env, - token_id: NftTokenId, + account_id: &str, prev_hf: Decimal, ) -> ContractResult { - let health = compute_health(deps, &env, token_id)?; + let health = compute_health(deps, &env, account_id)?; if let Some(hf) = health.liquidation_health_factor { if prev_hf > hf { return Err(ContractError::HealthNotImproved { diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index edff286f4..4c5b37d55 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -8,7 +8,6 @@ use rover::msg::query::{ PositionsWithValueResponse, SharesResponseItem, VaultPositionResponseItem, VaultPositionWithAddr, VaultWithBalance, }; -use rover::{Denom, NftTokenId}; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, @@ -33,26 +32,26 @@ pub fn query_config(deps: Deps) -> StdResult { }) } -pub fn query_position(deps: Deps, token_id: NftTokenId) -> ContractResult { +pub fn query_position(deps: Deps, account_id: &str) -> ContractResult { Ok(Positions { - token_id: token_id.to_string(), - coins: query_coin_balances(deps, token_id)?, - debt: query_debt_shares(deps, token_id)?, - vault_positions: get_vault_positions(deps, token_id)?, + account_id: account_id.to_string(), + coins: query_coin_balances(deps, account_id)?, + debt: query_debt_shares(deps, account_id)?, + vault_positions: get_vault_positions(deps, account_id)?, }) } pub fn query_position_with_value( deps: Deps, env: &Env, - token_id: &str, + account_id: &str, ) -> ContractResult { let Positions { - token_id, + account_id, coins, debt, vault_positions, - } = query_position(deps, token_id)?; + } = query_position(deps, account_id)?; let coin_balances_value = coins .iter() @@ -76,7 +75,7 @@ pub fn query_position_with_value( .collect::>>()?; Ok(PositionsWithValueResponse { - token_id, + account_id, coins: coin_balances_value, debt: debt_with_values, vault_positions, // TODO: add vault values here @@ -90,7 +89,7 @@ pub fn query_all_coin_balances( ) -> StdResult> { let start = start_after .as_ref() - .map(|(token_id, denom)| Bound::exclusive((token_id.as_str(), denom.as_str()))); + .map(|(account_id, denom)| Bound::exclusive((account_id.as_str(), denom.as_str()))); let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; Ok(COIN_BALANCES @@ -98,17 +97,17 @@ pub fn query_all_coin_balances( .take(limit) .collect::>>()? .iter() - .map(|((token_id, denom), amount)| CoinBalanceResponseItem { - token_id: token_id.to_string(), + .map(|((account_id, denom), amount)| CoinBalanceResponseItem { + account_id: account_id.to_string(), denom: denom.to_string(), amount: *amount, }) .collect()) } -fn query_debt_shares(deps: Deps, token_id: NftTokenId) -> ContractResult> { +fn query_debt_shares(deps: Deps, account_id: &str) -> ContractResult> { DEBT_SHARES - .prefix(token_id) + .prefix(account_id) .range(deps.storage, None, None, Order::Ascending) .map(|res| { let (denom, shares) = res?; @@ -117,9 +116,9 @@ fn query_debt_shares(deps: Deps, token_id: NftTokenId) -> ContractResult ContractResult> { +fn query_coin_balances(deps: Deps, account_id: &str) -> ContractResult> { COIN_BALANCES - .prefix(token_id) + .prefix(account_id) .range(deps.storage, None, None, Order::Ascending) .map(|item| { let (denom, amount) = item?; @@ -135,7 +134,7 @@ pub fn query_all_debt_shares( ) -> StdResult> { let start = start_after .as_ref() - .map(|(token_id, denom)| Bound::exclusive((token_id.as_str(), denom.as_str()))); + .map(|(account_id, denom)| Bound::exclusive((account_id.as_str(), denom.as_str()))); let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; Ok(DEBT_SHARES @@ -143,8 +142,8 @@ pub fn query_all_debt_shares( .take(limit) .collect::>>()? .iter() - .map(|((token_id, denom), shares)| SharesResponseItem { - token_id: token_id.to_string(), + .map(|((account_id, denom), shares)| SharesResponseItem { + account_id: account_id.to_string(), denom: denom.to_string(), shares: *shares, }) @@ -179,12 +178,9 @@ pub fn query_allowed_vaults( .collect() } -fn get_vault_positions( - deps: Deps, - token_id: NftTokenId, -) -> ContractResult> { +fn get_vault_positions(deps: Deps, account_id: &str) -> ContractResult> { VAULT_POSITIONS - .prefix(token_id) + .prefix(account_id) .range(deps.storage, None, None, Order::Ascending) .map(|res| { let (a, p) = res?; @@ -205,9 +201,9 @@ pub fn query_all_vault_positions( limit: Option, ) -> StdResult> { let start = match &start_after { - Some((token_id, unchecked)) => { + Some((account_id, unchecked)) => { let addr = deps.api.addr_validate(unchecked)?; - Some(Bound::exclusive((token_id.as_str(), addr))) + Some(Bound::exclusive((account_id.as_str(), addr))) } None => None, }; @@ -220,8 +216,8 @@ pub fn query_all_vault_positions( .collect::>>()? .iter() .map( - |((token_id, addr), vault_position)| VaultPositionResponseItem { - token_id: token_id.clone(), + |((account_id, addr), vault_position)| VaultPositionResponseItem { + account_id: account_id.clone(), addr: addr.to_string(), vault_position: vault_position.clone(), }, @@ -248,7 +244,7 @@ pub fn query_allowed_coins( .collect::>>() } -pub fn query_total_debt_shares(deps: Deps, denom: Denom) -> StdResult { +pub fn query_total_debt_shares(deps: Deps, denom: &str) -> StdResult { let shares = TOTAL_DEBT_SHARES.load(deps.storage, denom)?; Ok(DebtShares { denom: denom.to_string(), diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index 24bc3b90f..80a30b812 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -3,12 +3,11 @@ use std::cmp::min; use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; use rover::error::{ContractError, ContractResult}; -use rover::Shares; use crate::state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; use crate::utils::{assert_coin_is_whitelisted, debt_shares_to_amount, decrement_coin_balance}; -pub fn repay(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractResult { +pub fn repay(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> ContractResult { if coin.amount.is_zero() { return Err(ContractError::NoAmount); } @@ -16,7 +15,8 @@ pub fn repay(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRes assert_coin_is_whitelisted(deps.storage, &coin.denom)?; // Ensure repayment does not exceed max debt on account - let (debt_amount, debt_shares) = current_debt_for_denom(deps.as_ref(), &env, token_id, &coin)?; + let (debt_amount, debt_shares) = + current_debt_for_denom(deps.as_ref(), &env, account_id, &coin)?; let amount_to_repay = min(debt_amount, coin.amount); let shares_to_repay = debt_amount_to_shares( deps.as_ref(), @@ -29,11 +29,11 @@ pub fn repay(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRes // Decrement token's debt position if amount_to_repay == debt_amount { - DEBT_SHARES.remove(deps.storage, (token_id, &coin.denom)); + DEBT_SHARES.remove(deps.storage, (account_id, &coin.denom)); } else { DEBT_SHARES.save( deps.storage, - (token_id, &coin.denom), + (account_id, &coin.denom), &debt_shares.checked_sub(shares_to_repay)?, )?; } @@ -48,7 +48,7 @@ pub fn repay(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRes decrement_coin_balance( deps.storage, - token_id, + account_id, &Coin { denom: coin.denom.clone(), amount: amount_to_repay, @@ -68,7 +68,7 @@ pub fn repay(deps: DepsMut, env: Env, token_id: &str, coin: Coin) -> ContractRes .add_attribute("coins_repaid", amount_to_repay)) } -fn debt_amount_to_shares(deps: Deps, env: &Env, coin: &Coin) -> ContractResult { +fn debt_amount_to_shares(deps: Deps, env: &Env, coin: &Coin) -> ContractResult { let red_bank = RED_BANK.load(deps.storage)?; let total_debt_shares = TOTAL_DEBT_SHARES.load(deps.storage, &coin.denom)?; let total_debt_amount = @@ -81,11 +81,11 @@ fn debt_amount_to_shares(deps: Deps, env: &Env, coin: &Coin) -> ContractResult ContractResult<(Uint128, Shares)> { +) -> ContractResult<(Uint128, Uint128)> { let debt_shares = DEBT_SHARES - .load(deps.storage, (token_id, &coin.denom)) + .load(deps.storage, (account_id, &coin.denom)) .map_err(|_| ContractError::NoDebt)?; let coin = debt_shares_to_amount(deps, &env.contract.address, &coin.denom, debt_shares)?; Ok((coin.amount, debt_shares)) diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 043e36547..86895891e 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -3,12 +3,11 @@ use cw_storage_plus::{Item, Map}; use rover::adapters::swap::Swapper; use rover::adapters::{Oracle, RedBank, VaultPosition}; -use rover::{Denom, NftTokenId, Shares, VaultAddr}; // Contract config pub const OWNER: Item = Item::new("owner"); pub const ACCOUNT_NFT: Item = Item::new("account_nft"); -pub const ALLOWED_COINS: Map = Map::new("allowed_coins"); +pub const ALLOWED_COINS: Map<&str, Empty> = Map::new("allowed_coins"); pub const ALLOWED_VAULTS: Map<&Addr, Empty> = Map::new("allowed_vaults"); pub const RED_BANK: Item = Item::new("red_bank"); pub const ORACLE: Item = Item::new("oracle"); @@ -17,8 +16,7 @@ pub const MAX_CLOSE_FACTOR: Item = Item::new("max_close_factor"); pub const SWAPPER: Item = Item::new("swapper"); // Positions -pub const COIN_BALANCES: Map<(NftTokenId, Denom), Uint128> = Map::new("coin_balance"); -pub const DEBT_SHARES: Map<(NftTokenId, Denom), Shares> = Map::new("debt_shares"); -pub const TOTAL_DEBT_SHARES: Map = Map::new("total_debt_shares"); -pub const VAULT_POSITIONS: Map<(NftTokenId, VaultAddr), VaultPosition> = - Map::new("vault_positions"); +pub const COIN_BALANCES: Map<(&str, &str), Uint128> = Map::new("coin_balance"); // Map<(AccountId, Denom), Amount> +pub const DEBT_SHARES: Map<(&str, &str), Uint128> = Map::new("debt_shares"); // Map<(AccountId, Denom), Shares> +pub const TOTAL_DEBT_SHARES: Map<&str, Uint128> = Map::new("total_debt_shares"); // Map +pub const VAULT_POSITIONS: Map<(&str, Addr), VaultPosition> = Map::new("vault_positions"); // Map<(AccountId, VaultAddr), VaultPosition> diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs index 9e1b6259c..771ed58ed 100644 --- a/contracts/credit-manager/src/swap.rs +++ b/contracts/credit-manager/src/swap.rs @@ -3,7 +3,6 @@ use cosmwasm_std::{to_binary, Coin, CosmosMsg, Decimal, DepsMut, Env, Response, use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg; -use rover::NftTokenId; use crate::state::SWAPPER; use crate::update_coin_balances::query_balances; @@ -12,7 +11,7 @@ use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance}; pub fn swap_exact_in( deps: DepsMut, env: Env, - token_id: NftTokenId, + account_id: &str, coin_in: Coin, denom_out: &str, slippage: Decimal, @@ -23,7 +22,7 @@ pub fn swap_exact_in( return Err(ContractError::NoAmount); } - decrement_coin_balance(deps.storage, token_id, &coin_in)?; + decrement_coin_balance(deps.storage, account_id, &coin_in)?; // Updates coin balances for account after the swap has taken place let previous_balances = query_balances(deps.as_ref(), &env.contract.address, &[denom_out])?; @@ -31,7 +30,7 @@ pub fn swap_exact_in( contract_addr: env.contract.address.to_string(), funds: vec![], msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalances { - token_id: token_id.to_string(), + account_id: account_id.to_string(), previous_balances, }))?, }); diff --git a/contracts/credit-manager/src/update_coin_balances.rs b/contracts/credit-manager/src/update_coin_balances.rs index fc4b8cb04..60de7baf3 100644 --- a/contracts/credit-manager/src/update_coin_balances.rs +++ b/contracts/credit-manager/src/update_coin_balances.rs @@ -3,7 +3,6 @@ use cosmwasm_std::{ }; use rover::error::ContractResult; -use rover::NftTokenId; use crate::utils::{decrement_coin_balance, increment_coin_balance}; @@ -28,7 +27,7 @@ pub fn query_balances(deps: Deps, addr: &Addr, denoms: &[&str]) -> StdResult ContractResult { let mut response = Response::new(); @@ -39,7 +38,7 @@ pub fn update_coin_balances( let amount_to_reduce = prev.amount.checked_sub(curr.amount)?; decrement_coin_balance( deps.storage, - token_id, + account_id, &Coin { denom: curr.denom.clone(), amount: amount_to_reduce, @@ -52,7 +51,7 @@ pub fn update_coin_balances( let amount_to_increment = curr.amount.checked_sub(prev.amount)?; increment_coin_balance( deps.storage, - token_id, + account_id, &Coin { denom: curr.denom.clone(), amount: amount_to_increment, diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index fa8bb8892..74051c273 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -24,10 +24,10 @@ pub fn assert_coins_are_whitelisted( pub fn increment_coin_balance( storage: &mut dyn Storage, - token_id: &str, + account_id: &str, coin: &Coin, ) -> ContractResult { - COIN_BALANCES.update(storage, (token_id, &coin.denom), |value_opt| { + COIN_BALANCES.update(storage, (account_id, &coin.denom), |value_opt| { value_opt .unwrap_or_else(Uint128::zero) .checked_add(coin.amount) @@ -37,10 +37,10 @@ pub fn increment_coin_balance( pub fn decrement_coin_balance( storage: &mut dyn Storage, - token_id: &str, + account_id: &str, coin: &Coin, ) -> ContractResult { - let path = COIN_BALANCES.key((token_id, &coin.denom)); + let path = COIN_BALANCES.key((account_id, &coin.denom)); let value_opt = path.may_load(storage)?; let new_value = value_opt .unwrap_or_else(Uint128::zero) diff --git a/contracts/credit-manager/src/vault/deposit.rs b/contracts/credit-manager/src/vault/deposit.rs index 39544b9c1..59658bd6a 100644 --- a/contracts/credit-manager/src/vault/deposit.rs +++ b/contracts/credit-manager/src/vault/deposit.rs @@ -7,7 +7,6 @@ use rover::error::{ContractError, ContractResult}; use rover::extensions::Stringify; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg; -use rover::NftTokenId; use crate::state::VAULT_POSITIONS; use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance}; @@ -16,7 +15,7 @@ use crate::vault::utils::assert_vault_is_whitelisted; pub fn deposit_into_vault( deps: DepsMut, rover_addr: &Addr, - token_id: NftTokenId, + account_id: &str, vault: Vault, coins: &[Coin], ) -> ContractResult { @@ -27,7 +26,7 @@ pub fn deposit_into_vault( // Decrement token's coin balance amount coins.iter().try_for_each(|coin| -> ContractResult<_> { - decrement_coin_balance(deps.storage, token_id, coin)?; + decrement_coin_balance(deps.storage, account_id, coin)?; Ok(()) })?; @@ -37,7 +36,7 @@ pub fn deposit_into_vault( funds: vec![], msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateVaultCoinBalance { vault: vault.clone(), - token_id: token_id.to_string(), + account_id: account_id.to_string(), previous_total_balance: current_balance, }))?, }); @@ -51,7 +50,7 @@ pub fn deposit_into_vault( pub fn update_vault_coin_balance( deps: DepsMut, vault: Vault, - token_id: &str, + account_id: &str, previous_total_balance: Uint128, rover_addr: &Addr, ) -> ContractResult { @@ -67,7 +66,7 @@ pub fn update_vault_coin_balance( // Increment token's vault position VAULT_POSITIONS.update( deps.storage, - (token_id, vault.address().clone()), + (account_id, vault.address().clone()), |position_opt| -> ContractResult<_> { let p = position_opt.unwrap_or_default(); match vault_info.lockup { diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 5ef1efeac..5446c1da3 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -15,12 +15,12 @@ pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> pub fn decrement_vault_position( storage: &mut dyn Storage, - token_id: &str, + account_id: &str, vault: &Vault, amount: Uint128, force: bool, ) -> ContractResult { - let path = VAULT_POSITIONS.key((token_id, vault.address().clone())); + let path = VAULT_POSITIONS.key((account_id, vault.address().clone())); let mut position = path.load(storage)?; // Force indicates that the vault is one with a required lockup that needs to be broken diff --git a/contracts/credit-manager/src/vault/withdraw.rs b/contracts/credit-manager/src/vault/withdraw.rs index 836f99731..2cd480810 100644 --- a/contracts/credit-manager/src/vault/withdraw.rs +++ b/contracts/credit-manager/src/vault/withdraw.rs @@ -4,7 +4,6 @@ use rover::adapters::Vault; use rover::error::ContractResult; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg as RoverExecuteMsg; -use rover::NftTokenId; use crate::update_coin_balances::query_balances; use crate::vault::utils::{assert_vault_is_whitelisted, decrement_vault_position}; @@ -12,14 +11,14 @@ use crate::vault::utils::{assert_vault_is_whitelisted, decrement_vault_position} pub fn withdraw_from_vault( deps: DepsMut, env: Env, - token_id: NftTokenId, + account_id: &str, vault: Vault, amount: Uint128, force: bool, ) -> ContractResult { assert_vault_is_whitelisted(deps.storage, &vault)?; - decrement_vault_position(deps.storage, token_id, &vault, amount, force)?; + decrement_vault_position(deps.storage, account_id, &vault, amount, force)?; // Sends vault coins to vault in exchange for underlying assets let withdraw_msg = vault.withdraw_msg(&deps.querier, amount, force)?; @@ -38,7 +37,7 @@ pub fn withdraw_from_vault( funds: vec![], msg: to_binary(&RoverExecuteMsg::Callback( CallbackMsg::UpdateCoinBalances { - token_id: token_id.to_string(), + account_id: account_id.to_string(), previous_balances, }, ))?, diff --git a/contracts/credit-manager/src/withdraw.rs b/contracts/credit-manager/src/withdraw.rs index c4d4f7666..f7a9f4504 100644 --- a/contracts/credit-manager/src/withdraw.rs +++ b/contracts/credit-manager/src/withdraw.rs @@ -6,7 +6,7 @@ use crate::utils::{assert_coin_is_whitelisted, decrement_coin_balance}; pub fn withdraw( deps: DepsMut, - token_id: &str, + account_id: &str, coin: Coin, recipient: Addr, ) -> ContractResult { @@ -16,7 +16,7 @@ pub fn withdraw( return Err(ContractError::NoAmount); } - decrement_coin_balance(deps.storage, token_id, &coin)?; + decrement_coin_balance(deps.storage, account_id, &coin)?; // send coin to recipient let transfer_msg = CosmosMsg::Bank(BankMsg::Send { diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index bd5f8ed0b..16c9c033f 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -81,7 +81,7 @@ impl MockEnv { pub fn update_credit_account( &mut self, - token_id: &str, + account_id: &str, sender: &Addr, actions: Vec, send_funds: &[Coin], @@ -90,7 +90,7 @@ impl MockEnv { sender.clone(), self.rover.clone(), &ExecuteMsg::UpdateCreditAccount { - token_id: token_id.to_string(), + account_id: account_id.to_string(), actions, }, send_funds, @@ -141,10 +141,10 @@ impl MockEnv { &ExecuteMsg::CreateCreditAccount {}, &[], )?; - Ok(self.get_token_id(res)) + Ok(self.get_account_id(res)) } - fn get_token_id(&mut self, res: AppResponse) -> String { + fn get_account_id(&mut self, res: AppResponse) -> String { let attr: Vec<&String> = res .events .iter() @@ -182,25 +182,25 @@ impl MockEnv { // Queries //-------------------------------------------------------------------------------------------------- - pub fn query_position(&self, token_id: &str) -> PositionsWithValueResponse { + pub fn query_position(&self, account_id: &str) -> PositionsWithValueResponse { self.app .wrap() .query_wasm_smart( self.rover.clone(), &QueryMsg::Positions { - token_id: token_id.to_string(), + account_id: account_id.to_string(), }, ) .unwrap() } - pub fn query_health(&self, token_id: &str) -> HealthResponse { + pub fn query_health(&self, account_id: &str) -> HealthResponse { self.app .wrap() .query_wasm_smart( self.rover.clone(), &QueryMsg::Health { - token_id: token_id.to_string(), + account_id: account_id.to_string(), }, ) .unwrap() @@ -573,7 +573,7 @@ impl MockEnvBuilder { amount: self .get_allowed_coins() .iter() - .map(|info| info.to_coin(DEFAULT_RED_BANK_COIN_BALANCE)) + .map(|info| info.to_coin(DEFAULT_RED_BANK_COIN_BALANCE.u128())) .collect(), })) .unwrap(); diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 2f1daff30..7d9d82923 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use cosmwasm_std::{coin, Addr, Coin, Decimal}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -24,10 +24,7 @@ pub struct VaultTestInfo { } impl CoinInfo { - pub fn to_coin(&self, amount: Uint128) -> Coin { - Coin { - denom: self.denom.clone(), - amount, - } + pub fn to_coin(&self, amount: u128) -> Coin { + coin(amount, self.denom.clone()) } } diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index 1cb08f4b1..c6c0746ef 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -1,6 +1,6 @@ use std::ops::{Mul, Sub}; -use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Decimal, Uint128}; use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use rover::error::ContractError; @@ -21,13 +21,13 @@ fn test_only_token_owner_can_borrow() { .allowed_coins(&[coin_info.clone()]) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let another_user = Addr::unchecked("another_user"); let res = mock.update_credit_account( - &token_id, + &account_id, &another_user, - vec![Borrow(coin_info.to_coin(Uint128::new(12312u128)))], + vec![Borrow(coin_info.to_coin(12312))], &[], ); @@ -35,7 +35,7 @@ fn test_only_token_owner_can_borrow() { res, ContractError::NotTokenOwner { user: another_user.into(), - token_id, + account_id, }, ) } @@ -46,15 +46,12 @@ fn test_can_only_borrow_what_is_whitelisted() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().allowed_coins(&[coin_info]).build().unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Borrow(Coin { - denom: "usomething".to_string(), - amount: Uint128::new(234), - })], + vec![Borrow(coin(234, "usomething"))], &[], ); @@ -73,18 +70,14 @@ fn test_borrowing_zero_does_nothing() { .allowed_coins(&[coin_info.clone()]) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let res = mock.update_credit_account( - &token_id, - &user, - vec![Borrow(coin_info.to_coin(Uint128::zero()))], - &[], - ); + let res = + mock.update_credit_account(&account_id, &user, vec![Borrow(coin_info.to_coin(0))], &[]); assert_err(res, ContractError::NoAmount); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 0); assert_eq!(position.debt.len(), 0); } @@ -97,30 +90,30 @@ fn test_cannot_borrow_above_max_ltv() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 0); assert_eq!(position.debt.len(), 0); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(coin_info.to_coin(Uint128::new(300))), - Borrow(coin_info.to_coin(Uint128::new(700))), + Deposit(coin_info.to_coin(300)), + Borrow(coin_info.to_coin(700)), ], - &[Coin::new(300u128, coin_info.denom)], + &[coin(300, coin_info.denom)], ); assert_err( res, ContractError::AboveMaxLTV { - token_id, + account_id, max_ltv_health_factor: "0.998573466476462196".to_string(), }, ); @@ -134,33 +127,27 @@ fn test_success_when_new_debt_asset() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 0); assert_eq!(position.debt.len(), 0); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(Coin { - denom: coin_info.denom.clone(), - amount: Uint128::new(300), - }), - Borrow(Coin { - denom: coin_info.denom.clone(), - amount: Uint128::new(42), - }), + Deposit(coin(300, coin_info.denom.clone())), + Borrow(coin(42, coin_info.denom.clone())), ], - &[Coin::new(300u128, coin_info.denom.clone())], + &[coin(300, coin_info.denom.clone())], ) .unwrap(); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 1); let asset_res = position.coins.first().unwrap(); assert_eq!( @@ -214,43 +201,43 @@ fn test_debt_shares_with_debt_amount() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user_a.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .fund_account(AccountToFund { addr: user_b.clone(), - funds: vec![Coin::new(450u128, coin_info.denom.clone())], + funds: coins(450, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id_a = mock.create_credit_account(&user_a).unwrap(); - let token_id_b = mock.create_credit_account(&user_b).unwrap(); + let account_id_a = mock.create_credit_account(&user_a).unwrap(); + let account_id_b = mock.create_credit_account(&user_b).unwrap(); mock.update_credit_account( - &token_id_a, + &account_id_a, &user_a, vec![ - Deposit(coin_info.to_coin(Uint128::new(300))), - Borrow(coin_info.to_coin(Uint128::new(50))), + Deposit(coin_info.to_coin(300)), + Borrow(coin_info.to_coin(50)), ], - &[Coin::new(300u128, coin_info.denom.clone())], + &[coin(300, coin_info.denom.clone())], ) .unwrap(); let interim_red_bank_debt = mock.query_red_bank_debt(&coin_info.denom); mock.update_credit_account( - &token_id_b, + &account_id_b, &user_b, vec![ - Deposit(coin_info.to_coin(Uint128::new(450))), - Borrow(coin_info.to_coin(Uint128::new(50))), + Deposit(coin_info.to_coin(450)), + Borrow(coin_info.to_coin(50)), ], - &[Coin::new(450u128, coin_info.denom.clone())], + &[coin(450, coin_info.denom.clone())], ) .unwrap(); let token_a_shares = Uint128::new(50).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); - let position = mock.query_position(&token_id_a); + let position = mock.query_position(&account_id_a); let debt_position_a = position.debt.first().unwrap(); assert_eq!(debt_position_a.shares, token_a_shares.clone()); assert_eq!(debt_position_a.denom, coin_info.denom); @@ -258,7 +245,7 @@ fn test_debt_shares_with_debt_amount() { let token_b_shares = Uint128::new(50) .mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) .multiply_ratio(Uint128::new(50), interim_red_bank_debt.amount); - let position = mock.query_position(&token_id_b); + let position = mock.query_position(&account_id_b); let debt_position_b = position.debt.first().unwrap(); assert_eq!(debt_position_b.shares, token_b_shares.clone()); assert_eq!(debt_position_b.denom, coin_info.denom); diff --git a/contracts/credit-manager/tests/test_coin_balances.rs b/contracts/credit-manager/tests/test_coin_balances.rs index 1bdf71313..f94c80e5c 100644 --- a/contracts/credit-manager/tests/test_coin_balances.rs +++ b/contracts/credit-manager/tests/test_coin_balances.rs @@ -1,5 +1,5 @@ use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{coin, coins, Addr, Coin, OverflowError, Uint128}; +use cosmwasm_std::{coin, coins, Addr, OverflowError}; use cw_multi_test::{BankSudo, SudoMsg}; use rover::error::ContractError; @@ -14,12 +14,12 @@ pub mod helpers; fn test_only_rover_can_call_update_coin_balances() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.invoke_callback( &user, CallbackMsg::UpdateCoinBalances { - token_id, + account_id, previous_balances: vec![], }, ); @@ -35,24 +35,24 @@ fn test_user_does_not_have_enough_to_pay_diff() { .allowed_coins(&[osmo_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, osmo_info.denom.clone())], + funds: coins(300, osmo_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Deposit(osmo_info.to_coin(Uint128::new(300)))], - &[osmo_info.to_coin(Uint128::new(300))], + vec![Deposit(osmo_info.to_coin(300))], + &[osmo_info.to_coin(300)], ) .unwrap(); let res = mock.invoke_callback( &mock.rover.clone(), CallbackMsg::UpdateCoinBalances { - token_id, + account_id, previous_balances: coins(601, osmo_info.denom), }, ); @@ -76,30 +76,30 @@ fn test_user_gets_rebalanced_down() { .allowed_coins(&[osmo_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, osmo_info.denom.clone())], + funds: coins(300, osmo_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Deposit(osmo_info.to_coin(Uint128::new(300)))], - &[osmo_info.to_coin(Uint128::new(300))], + vec![Deposit(osmo_info.to_coin(300))], + &[osmo_info.to_coin(300)], ) .unwrap(); mock.invoke_callback( &mock.rover.clone(), CallbackMsg::UpdateCoinBalances { - token_id: token_id.clone(), + account_id: account_id.clone(), previous_balances: coins(500, osmo_info.denom.clone()), }, ) .unwrap(); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); assert_eq!(position.coins.first().unwrap().amount.u128(), 100); @@ -114,37 +114,37 @@ fn test_user_gets_rebalanced_up() { .allowed_coins(&[osmo_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, osmo_info.denom.clone())], + funds: coins(300, osmo_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Deposit(osmo_info.to_coin(Uint128::new(300)))], - &[osmo_info.to_coin(Uint128::new(300))], + vec![Deposit(osmo_info.to_coin(300))], + &[osmo_info.to_coin(300)], ) .unwrap(); mock.app .sudo(SudoMsg::Bank(BankSudo::Mint { to_address: mock.rover.clone().to_string(), - amount: vec![Coin::new(200u128, osmo_info.denom.clone())], + amount: coins(200, osmo_info.denom.clone()), })) .unwrap(); mock.invoke_callback( &mock.rover.clone(), CallbackMsg::UpdateCoinBalances { - token_id: token_id.clone(), + account_id: account_id.clone(), previous_balances: coins(300, osmo_info.denom.clone()), }, ) .unwrap(); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); assert_eq!(position.coins.first().unwrap().amount.u128(), 500); @@ -160,14 +160,14 @@ fn test_works_on_multiple() { .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); mock.app .sudo(SudoMsg::Bank(BankSudo::Mint { to_address: mock.rover.clone().to_string(), amount: vec![ - Coin::new(143u128, osmo_info.denom.clone()), - Coin::new(57u128, atom_info.denom.clone()), + coin(143, osmo_info.denom.clone()), + coin(57, atom_info.denom.clone()), ], })) .unwrap(); @@ -175,13 +175,13 @@ fn test_works_on_multiple() { mock.invoke_callback( &mock.rover.clone(), CallbackMsg::UpdateCoinBalances { - token_id: token_id.clone(), + account_id: account_id.clone(), previous_balances: vec![coin(0, osmo_info.denom), coin(0, atom_info.denom)], }, ) .unwrap(); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 2); let osmo = get_coin("uosmo", &position.coins); assert_eq!(osmo.amount.u128(), 143); diff --git a/contracts/credit-manager/tests/test_create_credit_account.rs b/contracts/credit-manager/tests/test_create_credit_account.rs index 392f8ac9d..f5dce11d0 100644 --- a/contracts/credit-manager/tests/test_create_credit_account.rs +++ b/contracts/credit-manager/tests/test_create_credit_account.rs @@ -1,5 +1,5 @@ use crate::helpers::MockEnv; -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Empty}; use cw721::OwnerOfResponse; use cw721_base::QueryMsg as NftQueryMsg; @@ -33,7 +33,7 @@ fn test_create_credit_account_success() { let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); // Double checking ownership by querying NFT account-nft for correct owner let config = mock.query_config(); @@ -43,8 +43,8 @@ fn test_create_credit_account_success() { .wrap() .query_wasm_smart( config.account_nft.unwrap(), - &NftQueryMsg::OwnerOf { - token_id, + &NftQueryMsg::::OwnerOf { + token_id: account_id, include_expired: None, }, ) diff --git a/contracts/credit-manager/tests/test_deposit.rs b/contracts/credit-manager/tests/test_deposit.rs index cd71c48fc..ff265702b 100644 --- a/contracts/credit-manager/tests/test_deposit.rs +++ b/contracts/credit-manager/tests/test_deposit.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Coin, Decimal, Uint128}; use rover::coins::Coins; use rover::error::ContractError::{ @@ -17,16 +17,13 @@ pub mod helpers; fn test_only_owner_of_token_can_deposit() { let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let another_user = Addr::unchecked("another_user"); let res = mock.update_credit_account( - &token_id, + &account_id, &another_user, - vec![Action::Deposit(Coin { - denom: "uosmo".to_string(), - amount: Uint128::zero(), - })], + vec![Action::Deposit(coin(0, "uosmo"))], &[], ); @@ -34,7 +31,7 @@ fn test_only_owner_of_token_can_deposit() { res, NotTokenOwner { user: another_user.into(), - token_id, + account_id, }, ) } @@ -48,20 +45,20 @@ fn test_deposit_nothing() { .build() .unwrap(); let user = Addr::unchecked("user"); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Action::Deposit(coin_info.to_coin(Uint128::zero()))], + vec![Action::Deposit(coin_info.to_coin(0))], &[], ) .unwrap(); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); } @@ -74,13 +71,13 @@ fn test_deposit_but_no_funds() { .build() .unwrap(); let user = Addr::unchecked("user"); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let deposit_amount = Uint128::new(234); let res = mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Action::Deposit(coin_info.to_coin(deposit_amount))], + vec![Action::Deposit(coin_info.to_coin(deposit_amount.u128()))], &[], ); @@ -92,7 +89,7 @@ fn test_deposit_but_no_funds() { }, ); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); } @@ -105,17 +102,17 @@ fn test_deposit_but_not_enough_funds() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Action::Deposit(coin_info.to_coin(Uint128::new(350)))], - &[Coin::new(250u128, coin_info.denom)], + vec![Action::Deposit(coin_info.to_coin(350))], + &[coin(250, coin_info.denom)], ); assert_err( @@ -135,24 +132,24 @@ fn test_can_only_deposit_allowed_assets() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let not_allowed_coin = ujake_info().to_coin(Uint128::new(234)); + let not_allowed_coin = ujake_info().to_coin(234); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![Action::Deposit(not_allowed_coin.clone())], - &[Coin::new(250u128, coin_info.denom)], + &[coin(250, coin_info.denom)], ); assert_err(res, NotWhitelisted(not_allowed_coin.denom)); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); } @@ -167,25 +164,25 @@ fn test_extra_funds_received() { .fund_account(AccountToFund { addr: user.clone(), funds: vec![ - Coin::new(300u128, uosmo_info.denom.clone()), - Coin::new(250u128, uatom_info.denom.clone()), + coin(300, uosmo_info.denom.clone()), + coin(250, uatom_info.denom.clone()), ], }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let extra_funds = Coin::new(25u128, uatom_info.denom); + let extra_funds = coin(25, uatom_info.denom); let res = mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Action::Deposit(uosmo_info.to_coin(Uint128::new(234)))], - &[Coin::new(234u128, uosmo_info.denom), extra_funds.clone()], + vec![Action::Deposit(uosmo_info.to_coin(234))], + &[coin(234, uosmo_info.denom), extra_funds.clone()], ); assert_err(res, ExtraFundsReceived(Coins::from(vec![extra_funds]))); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); } @@ -198,22 +195,22 @@ fn test_deposit_success() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let deposit_amount = Uint128::new(234); mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Action::Deposit(coin_info.to_coin(deposit_amount))], + vec![Action::Deposit(coin_info.to_coin(deposit_amount.u128()))], &[Coin::new(deposit_amount.into(), coin_info.denom.clone())], ) .unwrap(); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); let assets_res = res.coins.first().unwrap(); assert_eq!(res.coins.len(), 1); assert_eq!(assets_res.amount, deposit_amount); @@ -239,32 +236,32 @@ fn test_multiple_deposit_actions() { .fund_account(AccountToFund { addr: user.clone(), funds: vec![ - Coin::new(300u128, uosmo_info.denom.clone()), - Coin::new(50u128, uatom_info.denom.clone()), + coin(300, uosmo_info.denom.clone()), + coin(50, uatom_info.denom.clone()), ], }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let uosmo_amount = Uint128::new(234); let uatom_amount = Uint128::new(25); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Action::Deposit(uosmo_info.to_coin(uosmo_amount)), - Action::Deposit(uatom_info.to_coin(uatom_amount)), + Action::Deposit(uosmo_info.to_coin(uosmo_amount.u128())), + Action::Deposit(uatom_info.to_coin(uatom_amount.u128())), ], &[ - Coin::new(234u128, uosmo_info.denom.clone()), - Coin::new(25u128, uatom_info.denom.clone()), + coin(234, uosmo_info.denom.clone()), + coin(25, uatom_info.denom.clone()), ], ) .unwrap(); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 2); let uosmo_value = Decimal::from_atomics(uosmo_amount, 0).unwrap() * uosmo_info.price; assert_present(&res, &uosmo_info, uosmo_amount, uosmo_value); diff --git a/contracts/credit-manager/tests/test_dispatch.rs b/contracts/credit-manager/tests/test_dispatch.rs index 81b46d895..1331e532a 100644 --- a/contracts/credit-manager/tests/test_dispatch.rs +++ b/contracts/credit-manager/tests/test_dispatch.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_std::{coin, Addr}; use helpers::assert_err; use rover::error::ContractError; @@ -13,16 +13,16 @@ pub mod helpers; fn test_dispatch_only_allowed_for_token_owner() { let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let bad_guy = Addr::unchecked("bad_guy"); - let res = mock.update_credit_account(&token_id, &bad_guy, vec![], &[]); + let res = mock.update_credit_account(&account_id, &bad_guy, vec![], &[]); assert_err( res, NotTokenOwner { user: bad_guy.into(), - token_id, + account_id, }, ) } @@ -31,15 +31,15 @@ fn test_dispatch_only_allowed_for_token_owner() { fn test_nothing_happens_if_no_actions_are_passed() { let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); - mock.update_credit_account(&token_id, &user, vec![], &[]) + mock.update_credit_account(&account_id, &user, vec![], &[]) .unwrap(); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); } @@ -51,11 +51,8 @@ fn test_only_rover_can_execute_callbacks() { let res = mock.execute_callback( &external_user, CallbackMsg::Borrow { - token_id: "1234".to_string(), - coin: Coin { - denom: "uatom".to_string(), - amount: Uint128::new(1000u128), - }, + account_id: "1234".to_string(), + coin: coin(1000, "uatom"), }, ); assert_err(res, ContractError::ExternalInvocation); diff --git a/contracts/credit-manager/tests/test_enumerate_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs index c4b448c01..0fe47021b 100644 --- a/contracts/credit-manager/tests/test_enumerate_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_std::{coin, Addr, Uint128}; use rover::msg::execute::Action; use rover::msg::query::CoinBalanceResponseItem; @@ -14,44 +14,44 @@ fn test_pagination_on_all_coin_balances_query_works() { let user_c = Addr::unchecked("user_c"); let user_a_coins = vec![ - Coin::new(1u128, "coin_1"), - Coin::new(1u128, "coin_2"), - Coin::new(1u128, "coin_3"), - Coin::new(1u128, "coin_4"), - Coin::new(1u128, "coin_5"), - Coin::new(1u128, "coin_6"), - Coin::new(1u128, "coin_7"), - Coin::new(1u128, "coin_8"), - Coin::new(1u128, "coin_9"), - Coin::new(1u128, "coin_10"), - Coin::new(1u128, "coin_11"), - Coin::new(1u128, "coin_12"), - Coin::new(1u128, "coin_13"), - Coin::new(1u128, "coin_14"), + coin(1, "coin_1"), + coin(1, "coin_2"), + coin(1, "coin_3"), + coin(1, "coin_4"), + coin(1, "coin_5"), + coin(1, "coin_6"), + coin(1, "coin_7"), + coin(1, "coin_8"), + coin(1, "coin_9"), + coin(1, "coin_10"), + coin(1, "coin_11"), + coin(1, "coin_12"), + coin(1, "coin_13"), + coin(1, "coin_14"), ]; let user_b_coins = vec![ - Coin::new(1u128, "coin_1"), - Coin::new(1u128, "coin_2"), - Coin::new(1u128, "coin_3"), - Coin::new(1u128, "coin_4"), - Coin::new(1u128, "coin_5"), - Coin::new(1u128, "coin_6"), - Coin::new(1u128, "coin_7"), - Coin::new(1u128, "coin_8"), - Coin::new(1u128, "coin_9"), - Coin::new(1u128, "coin_10"), + coin(1, "coin_1"), + coin(1, "coin_2"), + coin(1, "coin_3"), + coin(1, "coin_4"), + coin(1, "coin_5"), + coin(1, "coin_6"), + coin(1, "coin_7"), + coin(1, "coin_8"), + coin(1, "coin_9"), + coin(1, "coin_10"), ]; let user_c_coins = vec![ - Coin::new(1u128, "coin_1"), - Coin::new(1u128, "coin_2"), - Coin::new(1u128, "coin_3"), - Coin::new(1u128, "coin_4"), - Coin::new(1u128, "coin_5"), - Coin::new(1u128, "coin_6"), - Coin::new(1u128, "coin_7"), - Coin::new(1u128, "coin_8"), + coin(1, "coin_1"), + coin(1, "coin_2"), + coin(1, "coin_3"), + coin(1, "coin_4"), + coin(1, "coin_5"), + coin(1, "coin_6"), + coin(1, "coin_7"), + coin(1, "coin_8"), ]; let mut mock = MockEnv::new() @@ -71,9 +71,9 @@ fn test_pagination_on_all_coin_balances_query_works() { .build() .unwrap(); - let token_id_a = mock.create_credit_account(&user_a).unwrap(); + let account_id_a = mock.create_credit_account(&user_a).unwrap(); mock.update_credit_account( - &token_id_a, + &account_id_a, &user_a, user_a_coins .iter() @@ -83,9 +83,9 @@ fn test_pagination_on_all_coin_balances_query_works() { ) .unwrap(); - let token_id_b = mock.create_credit_account(&user_b).unwrap(); + let account_id_b = mock.create_credit_account(&user_b).unwrap(); mock.update_credit_account( - &token_id_b, + &account_id_b, &user_b, user_b_coins .iter() @@ -95,9 +95,9 @@ fn test_pagination_on_all_coin_balances_query_works() { ) .unwrap(); - let token_id_c = mock.create_credit_account(&user_c).unwrap(); + let account_id_c = mock.create_credit_account(&user_c).unwrap(); mock.update_credit_account( - &token_id_c, + &account_id_c, &user_c, user_c_coins .iter() @@ -120,19 +120,19 @@ fn test_pagination_on_all_coin_balances_query_works() { let all_assets_res_a = mock.query_all_coin_balances(None, None); let CoinBalanceResponseItem { - token_id, denom, .. + account_id, denom, .. } = all_assets_res_a.last().unwrap().clone(); - let all_assets_res_b = mock.query_all_coin_balances(Some((token_id, denom)), None); + let all_assets_res_b = mock.query_all_coin_balances(Some((account_id, denom)), None); let CoinBalanceResponseItem { - token_id, denom, .. + account_id, denom, .. } = all_assets_res_b.last().unwrap().clone(); - let all_assets_res_c = mock.query_all_coin_balances(Some((token_id, denom)), None); + let all_assets_res_c = mock.query_all_coin_balances(Some((account_id, denom)), None); let CoinBalanceResponseItem { - token_id, denom, .. + account_id, denom, .. } = all_assets_res_c.last().unwrap().clone(); - let all_assets_res_d = mock.query_all_coin_balances(Some((token_id, denom)), None); + let all_assets_res_d = mock.query_all_coin_balances(Some((account_id, denom)), None); // Assert default is observed assert_eq!(all_assets_res_a.len(), 10); @@ -152,7 +152,7 @@ fn test_pagination_on_all_coin_balances_query_works() { let user_a_response_items = user_a_coins .iter() .map(|coin| CoinBalanceResponseItem { - token_id: token_id_a.clone(), + account_id: account_id_a.clone(), denom: coin.denom.clone(), amount: Uint128::new(1), }) @@ -161,7 +161,7 @@ fn test_pagination_on_all_coin_balances_query_works() { let user_b_response_items = user_b_coins .iter() .map(|coin| CoinBalanceResponseItem { - token_id: token_id_b.clone(), + account_id: account_id_b.clone(), denom: coin.denom.clone(), amount: Uint128::new(1), }) @@ -170,7 +170,7 @@ fn test_pagination_on_all_coin_balances_query_works() { let user_c_response_items = user_c_coins .iter() .map(|coin| CoinBalanceResponseItem { - token_id: token_id_c.clone(), + account_id: account_id_c.clone(), denom: coin.denom.clone(), amount: Uint128::new(1), }) diff --git a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs index 444dcf088..576d6af3f 100644 --- a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_std::{coin, Addr}; use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use rover::msg::execute::Action; @@ -15,44 +15,44 @@ fn test_pagination_on_all_debt_shares_query_works() { let user_c = Addr::unchecked("user_c"); let user_a_coins = vec![ - Coin::new(10u128, "coin_1"), - Coin::new(10u128, "coin_2"), - Coin::new(10u128, "coin_3"), - Coin::new(10u128, "coin_4"), - Coin::new(10u128, "coin_5"), - Coin::new(10u128, "coin_6"), - Coin::new(10u128, "coin_7"), - Coin::new(10u128, "coin_8"), - Coin::new(10u128, "coin_9"), - Coin::new(10u128, "coin_10"), - Coin::new(10u128, "coin_11"), - Coin::new(10u128, "coin_12"), - Coin::new(10u128, "coin_13"), - Coin::new(10u128, "coin_14"), + coin(10, "coin_1"), + coin(10, "coin_2"), + coin(10, "coin_3"), + coin(10, "coin_4"), + coin(10, "coin_5"), + coin(10, "coin_6"), + coin(10, "coin_7"), + coin(10, "coin_8"), + coin(10, "coin_9"), + coin(10, "coin_10"), + coin(10, "coin_11"), + coin(10, "coin_12"), + coin(10, "coin_13"), + coin(10, "coin_14"), ]; let user_b_coins = vec![ - Coin::new(10u128, "coin_15"), - Coin::new(10u128, "coin_16"), - Coin::new(10u128, "coin_17"), - Coin::new(10u128, "coin_18"), - Coin::new(10u128, "coin_19"), - Coin::new(10u128, "coin_20"), - Coin::new(10u128, "coin_21"), - Coin::new(10u128, "coin_22"), - Coin::new(10u128, "coin_23"), - Coin::new(10u128, "coin_24"), + coin(10, "coin_15"), + coin(10, "coin_16"), + coin(10, "coin_17"), + coin(10, "coin_18"), + coin(10, "coin_19"), + coin(10, "coin_20"), + coin(10, "coin_21"), + coin(10, "coin_22"), + coin(10, "coin_23"), + coin(10, "coin_24"), ]; let user_c_coins = vec![ - Coin::new(10u128, "coin_25"), - Coin::new(10u128, "coin_26"), - Coin::new(10u128, "coin_27"), - Coin::new(10u128, "coin_28"), - Coin::new(10u128, "coin_29"), - Coin::new(10u128, "coin_30"), - Coin::new(10u128, "coin_31"), - Coin::new(10u128, "coin_32"), + coin(10, "coin_25"), + coin(10, "coin_26"), + coin(10, "coin_27"), + coin(10, "coin_28"), + coin(10, "coin_29"), + coin(10, "coin_30"), + coin(10, "coin_31"), + coin(10, "coin_32"), ]; let mut mock = MockEnv::new() @@ -72,19 +72,16 @@ fn test_pagination_on_all_debt_shares_query_works() { .build() .unwrap(); - let token_id_a = mock.create_credit_account(&user_a).unwrap(); + let account_id_a = mock.create_credit_account(&user_a).unwrap(); mock.update_credit_account( - &token_id_a, + &account_id_a, &user_a, user_a_coins .iter() - .flat_map(|coin| { + .flat_map(|c| { vec![ - Action::Deposit(coin.clone()), - Action::Borrow(Coin { - denom: coin.denom.clone(), - amount: Uint128::new(1), - }), + Action::Deposit(c.clone()), + Action::Borrow(coin(1, c.denom.clone())), ] }) .collect::>(), @@ -92,19 +89,16 @@ fn test_pagination_on_all_debt_shares_query_works() { ) .unwrap(); - let token_id_b = mock.create_credit_account(&user_b).unwrap(); + let account_id_b = mock.create_credit_account(&user_b).unwrap(); mock.update_credit_account( - &token_id_b, + &account_id_b, &user_b, user_b_coins .iter() - .flat_map(|coin| { + .flat_map(|c| { vec![ - Action::Deposit(coin.clone()), - Action::Borrow(Coin { - denom: coin.denom.clone(), - amount: Uint128::new(1), - }), + Action::Deposit(c.clone()), + Action::Borrow(coin(1, c.denom.clone())), ] }) .collect::>(), @@ -112,19 +106,16 @@ fn test_pagination_on_all_debt_shares_query_works() { ) .unwrap(); - let token_id_c = mock.create_credit_account(&user_c).unwrap(); + let account_id_c = mock.create_credit_account(&user_c).unwrap(); mock.update_credit_account( - &token_id_c, + &account_id_c, &user_c, user_c_coins .iter() - .flat_map(|coin| { + .flat_map(|c| { vec![ - Action::Deposit(coin.clone()), - Action::Borrow(Coin { - denom: coin.denom.clone(), - amount: Uint128::new(1), - }), + Action::Deposit(c.clone()), + Action::Borrow(coin(1, c.denom.clone())), ] }) .collect::>(), @@ -145,19 +136,19 @@ fn test_pagination_on_all_debt_shares_query_works() { let all_debt_shares_res_a = mock.query_all_debt_shares(None, None); let SharesResponseItem { - token_id, denom, .. + account_id, denom, .. } = all_debt_shares_res_a.last().unwrap().clone(); - let all_debt_shares_res_b = mock.query_all_debt_shares(Some((token_id, denom)), None); + let all_debt_shares_res_b = mock.query_all_debt_shares(Some((account_id, denom)), None); let SharesResponseItem { - token_id, denom, .. + account_id, denom, .. } = all_debt_shares_res_b.last().unwrap().clone(); - let all_debt_shares_res_c = mock.query_all_debt_shares(Some((token_id, denom)), None); + let all_debt_shares_res_c = mock.query_all_debt_shares(Some((account_id, denom)), None); let SharesResponseItem { - token_id, denom, .. + account_id, denom, .. } = all_debt_shares_res_c.last().unwrap().clone(); - let all_debt_shares_res_d = mock.query_all_debt_shares(Some((token_id, denom)), None); + let all_debt_shares_res_d = mock.query_all_debt_shares(Some((account_id, denom)), None); // Assert default is observed assert_eq!(all_debt_shares_res_a.len(), 10); @@ -177,7 +168,7 @@ fn test_pagination_on_all_debt_shares_query_works() { let user_a_response_items = user_a_coins .iter() .map(|coin| SharesResponseItem { - token_id: token_id_a.clone(), + account_id: account_id_a.clone(), denom: coin.denom.clone(), shares: DEFAULT_DEBT_SHARES_PER_COIN_BORROWED, }) @@ -186,7 +177,7 @@ fn test_pagination_on_all_debt_shares_query_works() { let user_b_response_items = user_b_coins .iter() .map(|coin| SharesResponseItem { - token_id: token_id_b.clone(), + account_id: account_id_b.clone(), denom: coin.denom.clone(), shares: DEFAULT_DEBT_SHARES_PER_COIN_BORROWED, }) @@ -195,7 +186,7 @@ fn test_pagination_on_all_debt_shares_query_works() { let user_c_response_items = user_c_coins .iter() .map(|coin| SharesResponseItem { - token_id: token_id_c.clone(), + account_id: account_id_c.clone(), denom: coin.denom.clone(), shares: DEFAULT_DEBT_SHARES_PER_COIN_BORROWED, }) diff --git a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs index 332ca647d..34f860bce 100644 --- a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_std::{coin, Addr}; use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use rover::msg::execute::Action; @@ -15,44 +15,44 @@ fn test_pagination_on_all_total_debt_shares_query_works() { let user_c = Addr::unchecked("user_c"); let user_a_coins = vec![ - Coin::new(10u128, "coin_1"), - Coin::new(10u128, "coin_2"), - Coin::new(10u128, "coin_3"), - Coin::new(10u128, "coin_4"), - Coin::new(10u128, "coin_5"), - Coin::new(10u128, "coin_6"), - Coin::new(10u128, "coin_7"), - Coin::new(10u128, "coin_8"), - Coin::new(10u128, "coin_9"), - Coin::new(10u128, "coin_10"), - Coin::new(10u128, "coin_11"), - Coin::new(10u128, "coin_12"), - Coin::new(10u128, "coin_13"), - Coin::new(10u128, "coin_14"), + coin(10, "coin_1"), + coin(10, "coin_2"), + coin(10, "coin_3"), + coin(10, "coin_4"), + coin(10, "coin_5"), + coin(10, "coin_6"), + coin(10, "coin_7"), + coin(10, "coin_8"), + coin(10, "coin_9"), + coin(10, "coin_10"), + coin(10, "coin_11"), + coin(10, "coin_12"), + coin(10, "coin_13"), + coin(10, "coin_14"), ]; let user_b_coins = vec![ - Coin::new(10u128, "coin_15"), - Coin::new(10u128, "coin_16"), - Coin::new(10u128, "coin_17"), - Coin::new(10u128, "coin_18"), - Coin::new(10u128, "coin_19"), - Coin::new(10u128, "coin_20"), - Coin::new(10u128, "coin_21"), - Coin::new(10u128, "coin_22"), - Coin::new(10u128, "coin_23"), - Coin::new(10u128, "coin_24"), + coin(10, "coin_15"), + coin(10, "coin_16"), + coin(10, "coin_17"), + coin(10, "coin_18"), + coin(10, "coin_19"), + coin(10, "coin_20"), + coin(10, "coin_21"), + coin(10, "coin_22"), + coin(10, "coin_23"), + coin(10, "coin_24"), ]; let user_c_coins = vec![ - Coin::new(10u128, "coin_25"), - Coin::new(10u128, "coin_26"), - Coin::new(10u128, "coin_27"), - Coin::new(10u128, "coin_28"), - Coin::new(10u128, "coin_29"), - Coin::new(10u128, "coin_30"), - Coin::new(10u128, "coin_31"), - Coin::new(10u128, "coin_32"), + coin(10, "coin_25"), + coin(10, "coin_26"), + coin(10, "coin_27"), + coin(10, "coin_28"), + coin(10, "coin_29"), + coin(10, "coin_30"), + coin(10, "coin_31"), + coin(10, "coin_32"), ]; let mut mock = MockEnv::new() @@ -72,19 +72,16 @@ fn test_pagination_on_all_total_debt_shares_query_works() { .build() .unwrap(); - let token_id_a = mock.create_credit_account(&user_a).unwrap(); + let account_id_a = mock.create_credit_account(&user_a).unwrap(); mock.update_credit_account( - &token_id_a, + &account_id_a, &user_a, user_a_coins .iter() - .flat_map(|coin| { + .flat_map(|c| { vec![ - Action::Deposit(coin.clone()), - Action::Borrow(Coin { - denom: coin.denom.clone(), - amount: Uint128::new(1), - }), + Action::Deposit(c.clone()), + Action::Borrow(coin(1, c.denom.clone())), ] }) .collect::>(), @@ -92,19 +89,16 @@ fn test_pagination_on_all_total_debt_shares_query_works() { ) .unwrap(); - let token_id_b = mock.create_credit_account(&user_b).unwrap(); + let account_id_b = mock.create_credit_account(&user_b).unwrap(); mock.update_credit_account( - &token_id_b, + &account_id_b, &user_b, user_b_coins .iter() - .flat_map(|coin| { + .flat_map(|c| { vec![ - Action::Deposit(coin.clone()), - Action::Borrow(Coin { - denom: coin.denom.clone(), - amount: Uint128::new(1), - }), + Action::Deposit(c.clone()), + Action::Borrow(coin(1, c.denom.clone())), ] }) .collect::>(), @@ -112,19 +106,16 @@ fn test_pagination_on_all_total_debt_shares_query_works() { ) .unwrap(); - let token_id_c = mock.create_credit_account(&user_c).unwrap(); + let account_id_c = mock.create_credit_account(&user_c).unwrap(); mock.update_credit_account( - &token_id_c, + &account_id_c, &user_c, user_c_coins .iter() - .flat_map(|coin| { + .flat_map(|c| { vec![ - Action::Deposit(coin.clone()), - Action::Borrow(Coin { - denom: coin.denom.clone(), - amount: Uint128::new(1), - }), + Action::Deposit(c.clone()), + Action::Borrow(coin(1, c.denom.clone())), ] }) .collect::>(), diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index 522e23f8a..5e2f350d3 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -1,5 +1,5 @@ use cosmwasm_std::testing::MockApi; -use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_std::{coin, Addr}; use rover::msg::execute::Action; @@ -23,22 +23,22 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { .fund_account(AccountToFund { addr: user_a.clone(), funds: vec![ - Coin::new(1000u128, uosmo.denom.clone()), - Coin::new(1000u128, uatom.denom.clone()), + coin(1000, uosmo.denom.clone()), + coin(1000, uatom.denom.clone()), ], }) .fund_account(AccountToFund { addr: user_b.clone(), funds: vec![ - Coin::new(1000u128, uosmo.denom.clone()), - Coin::new(1000u128, uatom.denom.clone()), + coin(1000, uosmo.denom.clone()), + coin(1000, uatom.denom.clone()), ], }) .fund_account(AccountToFund { addr: user_c.clone(), funds: vec![ - Coin::new(1000u128, uosmo.denom.clone()), - Coin::new(1000u128, uatom.denom.clone()), + coin(1000, uosmo.denom.clone()), + coin(1000, uatom.denom.clone()), ], }) .allowed_coins(&[uosmo.clone(), uatom.clone()]) @@ -47,53 +47,41 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { .unwrap(); let mut actions = vec![ - Action::Deposit(uatom.to_coin(Uint128::new(220))), - Action::Deposit(uosmo.to_coin(Uint128::new(220))), + Action::Deposit(uatom.to_coin(220)), + Action::Deposit(uosmo.to_coin(220)), ]; all_vaults.iter().for_each(|v| { actions.extend([Action::VaultDeposit { vault: mock.get_vault(v), - coins: vec![ - uatom.to_coin(Uint128::new(10)), - uosmo.to_coin(Uint128::new(10)), - ], + coins: vec![uatom.to_coin(10), uosmo.to_coin(10)], }]); }); - let token_id_a = mock.create_credit_account(&user_a).unwrap(); + let account_id_a = mock.create_credit_account(&user_a).unwrap(); mock.update_credit_account( - &token_id_a, + &account_id_a, &user_a, actions.clone(), - &[ - uatom.to_coin(Uint128::new(220)), - uosmo.to_coin(Uint128::new(220)), - ], + &[uatom.to_coin(220), uosmo.to_coin(220)], ) .unwrap(); - let token_id_b = mock.create_credit_account(&user_b).unwrap(); + let account_id_b = mock.create_credit_account(&user_b).unwrap(); mock.update_credit_account( - &token_id_b, + &account_id_b, &user_b, actions.clone(), - &[ - uatom.to_coin(Uint128::new(220)), - uosmo.to_coin(Uint128::new(220)), - ], + &[uatom.to_coin(220), uosmo.to_coin(220)], ) .unwrap(); - let token_id_c = mock.create_credit_account(&user_c).unwrap(); + let account_id_c = mock.create_credit_account(&user_c).unwrap(); mock.update_credit_account( - &token_id_c, + &account_id_c, &user_c, actions, - &[ - uatom.to_coin(Uint128::new(220)), - uosmo.to_coin(Uint128::new(220)), - ], + &[uatom.to_coin(220), uosmo.to_coin(220)], ) .unwrap(); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index 3524144ef..58796697d 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -1,5 +1,5 @@ use cosmwasm_std::testing::MockApi; -use cosmwasm_std::{Addr, Api, Coin, Uint128}; +use cosmwasm_std::{coin, Addr, Api}; use itertools::Itertools; use rover::adapters::VaultBase; @@ -25,22 +25,22 @@ fn test_pagination_on_all_vault_positions_query_works() { .fund_account(AccountToFund { addr: user_a.clone(), funds: vec![ - Coin::new(1000u128, uosmo.denom.clone()), - Coin::new(1000u128, uatom.denom.clone()), + coin(1000, uosmo.denom.clone()), + coin(1000, uatom.denom.clone()), ], }) .fund_account(AccountToFund { addr: user_b.clone(), funds: vec![ - Coin::new(1000u128, uosmo.denom.clone()), - Coin::new(1000u128, uatom.denom.clone()), + coin(1000, uosmo.denom.clone()), + coin(1000, uatom.denom.clone()), ], }) .fund_account(AccountToFund { addr: user_c.clone(), funds: vec![ - Coin::new(1000u128, uosmo.denom.clone()), - Coin::new(1000u128, uatom.denom.clone()), + coin(1000, uosmo.denom.clone()), + coin(1000, uatom.denom.clone()), ], }) .allowed_coins(&[uosmo.clone(), uatom.clone()]) @@ -49,53 +49,41 @@ fn test_pagination_on_all_vault_positions_query_works() { .unwrap(); let mut actions = vec![ - Action::Deposit(uatom.to_coin(Uint128::new(220))), - Action::Deposit(uosmo.to_coin(Uint128::new(220))), + Action::Deposit(uatom.to_coin(220)), + Action::Deposit(uosmo.to_coin(220)), ]; all_vaults.iter().for_each(|v| { actions.extend([Action::VaultDeposit { vault: mock.get_vault(v), - coins: vec![ - uatom.to_coin(Uint128::new(10)), - uosmo.to_coin(Uint128::new(10)), - ], + coins: vec![uatom.to_coin(10), uosmo.to_coin(10)], }]); }); - let token_id_a = mock.create_credit_account(&user_a).unwrap(); + let account_id_a = mock.create_credit_account(&user_a).unwrap(); mock.update_credit_account( - &token_id_a, + &account_id_a, &user_a, actions.clone(), - &[ - uatom.to_coin(Uint128::new(220)), - uosmo.to_coin(Uint128::new(220)), - ], + &[uatom.to_coin(220), uosmo.to_coin(220)], ) .unwrap(); - let token_id_b = mock.create_credit_account(&user_b).unwrap(); + let account_id_b = mock.create_credit_account(&user_b).unwrap(); mock.update_credit_account( - &token_id_b, + &account_id_b, &user_b, actions.clone(), - &[ - uatom.to_coin(Uint128::new(220)), - uosmo.to_coin(Uint128::new(220)), - ], + &[uatom.to_coin(220), uosmo.to_coin(220)], ) .unwrap(); - let token_id_c = mock.create_credit_account(&user_c).unwrap(); + let account_id_c = mock.create_credit_account(&user_c).unwrap(); mock.update_credit_account( - &token_id_c, + &account_id_c, &user_c, actions, - &[ - uatom.to_coin(Uint128::new(220)), - uosmo.to_coin(Uint128::new(220)), - ], + &[uatom.to_coin(220), uosmo.to_coin(220)], ) .unwrap(); @@ -109,14 +97,14 @@ fn test_pagination_on_all_vault_positions_query_works() { let vaults_res_a = mock.query_all_vault_positions(None, None); let item = vaults_res_a.last().unwrap(); - let vaults_res_b = - mock.query_all_vault_positions(Some((item.token_id.clone(), item.addr.clone())), Some(30)); + let vaults_res_b = mock + .query_all_vault_positions(Some((item.account_id.clone(), item.addr.clone())), Some(30)); let item = vaults_res_b.last().unwrap(); - let vaults_res_c = - mock.query_all_vault_positions(Some((item.token_id.clone(), item.addr.clone())), Some(30)); + let vaults_res_c = mock + .query_all_vault_positions(Some((item.account_id.clone(), item.addr.clone())), Some(30)); let item = vaults_res_c.last().unwrap(); let vaults_res_d = - mock.query_all_vault_positions(Some((item.token_id.clone(), item.addr.clone())), None); + mock.query_all_vault_positions(Some((item.account_id.clone(), item.addr.clone())), None); // Assert default is observed assert_eq!(vaults_res_a.len(), 10); diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index 52510eefe..fea81fd1d 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -1,6 +1,6 @@ use std::ops::{Add, Div, Mul}; -use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128}; use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use mock_oracle::msg::CoinPrice; @@ -26,26 +26,26 @@ fn test_only_assets_with_no_debts() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let deposit_amount = Uint128::new(300); mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Deposit(coin_info.to_coin(deposit_amount))], + vec![Deposit(coin_info.to_coin(deposit_amount.u128()))], &[Coin::new(deposit_amount.into(), coin_info.denom.clone())], ) .unwrap(); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.debt.len(), 0); - let health = mock.query_health(&token_id); + let health = mock.query_health(&account_id); let assets_value = coin_info.price * Decimal::from_atomics(deposit_amount, 0).unwrap(); assert_eq!(health.total_collateral_value, assets_value); assert_eq!(health.total_debt_value, Decimal::zero()); @@ -79,31 +79,31 @@ fn test_terra_ragnarok() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let deposit_amount = Uint128::new(12); let borrow_amount = Uint128::new(2); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(coin_info.to_coin(deposit_amount)), - Borrow(coin_info.to_coin(borrow_amount)), + Deposit(coin_info.to_coin(deposit_amount.u128())), + Borrow(coin_info.to_coin(borrow_amount.u128())), ], &[Coin::new(deposit_amount.into(), coin_info.denom.clone())], ) .unwrap(); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.debt.len(), 1); - let health = mock.query_health(&token_id); + let health = mock.query_health(&account_id); let assets_value = coin_info.price * Decimal::from_atomics(deposit_amount + borrow_amount, 0).unwrap(); assert_eq!(health.total_collateral_value, assets_value); @@ -127,11 +127,11 @@ fn test_terra_ragnarok() { price: Decimal::zero(), }); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.debt.len(), 1); - let health = mock.query_health(&token_id); + let health = mock.query_health(&account_id); assert_eq!(health.total_collateral_value, Decimal::zero()); assert_eq!(health.total_debt_value, Decimal::zero()); assert_eq!(health.liquidation_health_factor, None); @@ -153,34 +153,33 @@ fn test_debts_no_assets() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let borrowed_amount = Uint128::new(100); let res = mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Borrow(coin_info.to_coin(borrowed_amount))], + vec![Borrow(coin_info.to_coin(100))], &[], ); assert_err( res, ContractError::AboveMaxLTV { - token_id: token_id.clone(), + account_id: account_id.clone(), max_ltv_health_factor: "0.693069306930693069".to_string(), }, ); - let position = mock.query_position(&token_id); - assert_eq!(position.token_id, token_id); + let position = mock.query_position(&account_id); + assert_eq!(position.account_id, account_id); assert_eq!(position.coins.len(), 0); assert_eq!(position.debt.len(), 0); - let health = mock.query_health(&token_id); + let health = mock.query_health(&account_id); assert_eq!(health.total_collateral_value, Decimal::zero()); assert_eq!(health.total_debt_value, Decimal::zero()); assert_eq!(health.liquidation_health_factor, None); @@ -210,29 +209,29 @@ fn test_cannot_borrow_more_than_healthy() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(coin_info.to_coin(Uint128::new(300))), - Borrow(coin_info.to_coin(Uint128::new(50))), + Deposit(coin_info.to_coin(300)), + Borrow(coin_info.to_coin(50)), ], &[Coin::new(Uint128::new(300).into(), coin_info.denom.clone())], ) .unwrap(); - let position = mock.query_position(&token_id); - assert_eq!(position.token_id, token_id); + let position = mock.query_position(&account_id); + assert_eq!(position.account_id, account_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.debt.len(), 1); - let health = mock.query_health(&token_id); + let health = mock.query_health(&account_id); let assets_value = Decimal::from_atomics(82789u128, 2).unwrap(); assert_eq!(health.total_collateral_value, assets_value); let debts_value = Decimal::from_atomics(1206354u128, 4).unwrap(); @@ -249,30 +248,30 @@ fn test_cannot_borrow_more_than_healthy() { assert!(!health.above_max_ltv); mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Borrow(coin_info.to_coin(Uint128::new(100)))], + vec![Borrow(coin_info.to_coin(100))], &[], ) .unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Borrow(coin_info.to_coin(Uint128::new(150)))], + vec![Borrow(coin_info.to_coin(150))], &[], ); assert_err( res, ContractError::AboveMaxLTV { - token_id: token_id.clone(), + account_id: account_id.clone(), max_ltv_health_factor: "0.990099009900990099".to_string(), }, ); // All valid on step 2 as well (meaning step 3 did not go through) - let health = mock.query_health(&token_id); + let health = mock.query_health(&account_id); let assets_value = Decimal::from_atomics(106443u128, 2).unwrap(); assert_eq!(health.total_collateral_value, assets_value); let debts_value = Decimal::from_atomics(3595408u128, 4).unwrap(); @@ -320,24 +319,24 @@ fn test_cannot_borrow_more_but_not_liquidatable() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(uosmo_info.to_coin(Uint128::new(300))), - Borrow(uatom_info.to_coin(Uint128::new(50))), + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(50)), ], &[Coin::new(300, uosmo_info.denom)], ) .unwrap(); - let health = mock.query_health(&token_id); + let health = mock.query_health(&account_id); assert!(!health.liquidatable); assert!(!health.above_max_ltv); @@ -346,21 +345,17 @@ fn test_cannot_borrow_more_but_not_liquidatable() { price: Decimal::from_atomics(24u128, 0).unwrap(), }); - let health = mock.query_health(&token_id); + let health = mock.query_health(&account_id); assert!(!health.liquidatable); assert!(health.above_max_ltv); - let res = mock.update_credit_account( - &token_id, - &user, - vec![Borrow(uatom_info.to_coin(Uint128::new(2)))], - &[], - ); + let res = + mock.update_credit_account(&account_id, &user, vec![Borrow(uatom_info.to_coin(2))], &[]); assert_err( res, ContractError::AboveMaxLTV { - token_id: token_id.clone(), + account_id: account_id.clone(), max_ltv_health_factor: "0.947847222222222222".to_string(), }, ); @@ -370,7 +365,7 @@ fn test_cannot_borrow_more_but_not_liquidatable() { price: Decimal::from_atomics(35u128, 0).unwrap(), }); - let health = mock.query_health(&token_id); + let health = mock.query_health(&account_id); assert!(health.liquidatable); assert!(health.above_max_ltv); } @@ -401,31 +396,31 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let deposit_amount = Uint128::new(298); let borrowed_amount = Uint128::new(49); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(uosmo_info.to_coin(deposit_amount)), - Borrow(uatom_info.to_coin(borrowed_amount)), + Deposit(uosmo_info.to_coin(deposit_amount.u128())), + Borrow(uatom_info.to_coin(borrowed_amount.u128())), ], &[Coin::new(deposit_amount.into(), uosmo_info.denom.clone())], ) .unwrap(); - let position = mock.query_position(&token_id); - assert_eq!(position.token_id, token_id); + let position = mock.query_position(&account_id); + assert_eq!(position.account_id, account_id); assert_eq!(position.coins.len(), 2); assert_eq!(position.debt.len(), 1); - let health = mock.query_health(&token_id); + let health = mock.query_health(&account_id); let deposit_amount_dec = Decimal::from_atomics(deposit_amount, 0).unwrap(); let borrowed_amount_dec = Decimal::from_atomics(borrowed_amount, 0).unwrap(); assert_eq!( @@ -487,28 +482,28 @@ fn test_debt_value() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user_a.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .fund_account(AccountToFund { addr: user_b.clone(), - funds: vec![Coin::new(140u128, uosmo_info.denom.clone())], + funds: coins(140, uosmo_info.denom.clone()), }) .build() .unwrap(); - let token_id_a = mock.create_credit_account(&user_a).unwrap(); - let token_id_b = mock.create_credit_account(&user_b).unwrap(); + let account_id_a = mock.create_credit_account(&user_a).unwrap(); + let account_id_b = mock.create_credit_account(&user_b).unwrap(); let user_a_deposit_amount_osmo = Uint128::new(298); let user_a_borrowed_amount_atom = Uint128::new(49); let user_a_borrowed_amount_osmo = Uint128::new(30); mock.update_credit_account( - &token_id_a, + &account_id_a, &user_a, vec![ - Borrow(uatom_info.to_coin(user_a_borrowed_amount_atom)), - Borrow(uosmo_info.to_coin(user_a_borrowed_amount_osmo)), - Deposit(uosmo_info.to_coin(user_a_deposit_amount_osmo)), + Borrow(uatom_info.to_coin(user_a_borrowed_amount_atom.u128())), + Borrow(uosmo_info.to_coin(user_a_borrowed_amount_osmo.u128())), + Deposit(uosmo_info.to_coin(user_a_deposit_amount_osmo.u128())), ], &[Coin::new( user_a_deposit_amount_osmo.into(), @@ -523,11 +518,11 @@ fn test_debt_value() { let user_b_borrowed_amount_atom = Uint128::new(24); mock.update_credit_account( - &token_id_b, + &account_id_b, &user_b, vec![ - Borrow(uatom_info.to_coin(user_b_borrowed_amount_atom)), - Deposit(uosmo_info.to_coin(user_b_deposit_amount)), + Borrow(uatom_info.to_coin(user_b_borrowed_amount_atom.u128())), + Deposit(uosmo_info.to_coin(user_b_deposit_amount.u128())), ], &[Coin::new( user_b_deposit_amount.into(), @@ -536,12 +531,12 @@ fn test_debt_value() { ) .unwrap(); - let position_a = mock.query_position(&token_id_a); - assert_eq!(position_a.token_id, token_id_a); + let position_a = mock.query_position(&account_id_a); + assert_eq!(position_a.account_id, account_id_a); assert_eq!(position_a.coins.len(), 2); assert_eq!(position_a.debt.len(), 2); - let health = mock.query_health(&token_id_a); + let health = mock.query_health(&account_id_a); assert!(!health.above_max_ltv); assert!(!health.liquidatable); @@ -554,7 +549,7 @@ fn test_debt_value() { find_by_denom(&uatom_info.denom, &position_a.debt).shares ); - let position_b = mock.query_position(&token_id_b); + let position_b = mock.query_position(&account_id_b); let user_b_debt_shares_atom = user_a_debt_shares_atom .multiply_ratio(user_b_borrowed_amount_atom, interim_red_bank_debt.amount); assert_eq!( diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 371321af2..bacf49a7d 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Coin, Decimal, OverflowError, OverflowOperation, Uint128}; +use cosmwasm_std::{coins, Addr, Coin, Decimal, OverflowError, OverflowOperation, Uint128}; use mock_oracle::msg::CoinPrice; use rover::error::ContractError; @@ -23,35 +23,35 @@ fn test_can_only_liquidate_unhealthy_accounts() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .build() .unwrap(); - let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); mock.update_credit_account( - &liquidatee_token_id, + &liquidatee_account_id, &liquidatee, vec![ - Deposit(uosmo_info.to_coin(Uint128::from(300u128))), - Borrow(uatom_info.to_coin(Uint128::from(50u128))), + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(50)), ], &[Coin::new(300, uosmo_info.clone().denom)], ) .unwrap(); - let health = mock.query_health(&liquidatee_token_id); + let health = mock.query_health(&liquidatee_account_id); assert!(!health.liquidatable); let liquidator = Addr::unchecked("liquidator"); - let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); let res = mock.update_credit_account( - &liquidator_token_id, + &liquidator_account_id, &liquidator, vec![LiquidateCoin { - liquidatee_token_id: liquidatee_token_id.clone(), - debt_coin: uatom_info.to_coin(Uint128::from(10u128)), + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(10), request_coin_denom: uosmo_info.denom, }], &[], @@ -60,7 +60,7 @@ fn test_can_only_liquidate_unhealthy_accounts() { assert_err( res, NotLiquidatable { - token_id: liquidatee_token_id, + account_id: liquidatee_account_id, lqdt_health_factor: "2.029411764705882352".to_string(), }, ) @@ -77,24 +77,24 @@ fn test_liquidatee_does_not_have_requested_asset() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .build() .unwrap(); - let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); mock.update_credit_account( - &liquidatee_token_id, + &liquidatee_account_id, &liquidatee, vec![ - Deposit(uosmo_info.to_coin(Uint128::from(300u128))), - Borrow(uatom_info.to_coin(Uint128::from(105u128))), + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(105)), ], &[Coin::new(300, uosmo_info.denom)], ) .unwrap(); - let health = mock.query_health(&liquidatee_token_id); + let health = mock.query_health(&liquidatee_account_id); assert!(!health.liquidatable); mock.price_change(CoinPrice { @@ -103,16 +103,16 @@ fn test_liquidatee_does_not_have_requested_asset() { }); let liquidator = Addr::unchecked("liquidator"); - let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); let res = mock.update_credit_account( - &liquidator_token_id, + &liquidator_account_id, &liquidator, vec![ - Borrow(uatom_info.to_coin(Uint128::from(50u128))), + Borrow(uatom_info.to_coin(50)), LiquidateCoin { - liquidatee_token_id: liquidatee_token_id.clone(), - debt_coin: uatom_info.to_coin(Uint128::from(10u128)), + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(10), request_coin_denom: ujake_info.denom.clone(), }, ], @@ -134,28 +134,28 @@ fn test_liquidatee_does_not_have_debt_coin() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .fund_account(AccountToFund { addr: random_user.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .build() .unwrap(); - let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); mock.update_credit_account( - &liquidatee_token_id, + &liquidatee_account_id, &liquidatee, vec![ - Deposit(uosmo_info.to_coin(Uint128::from(300u128))), - Borrow(uatom_info.to_coin(Uint128::from(105u128))), + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(105)), ], &[Coin::new(300, uosmo_info.denom.clone())], ) .unwrap(); - let health = mock.query_health(&liquidatee_token_id); + let health = mock.query_health(&liquidatee_account_id); assert!(!health.liquidatable); // Seeding a jakecoin borrow @@ -164,8 +164,8 @@ fn test_liquidatee_does_not_have_debt_coin() { &random_user_token, &random_user, vec![ - Deposit(uosmo_info.to_coin(Uint128::from(300u128))), - Borrow(ujake_info.to_coin(Uint128::from(10u128))), + Deposit(uosmo_info.to_coin(300)), + Borrow(ujake_info.to_coin(10)), ], &[Coin::new(300, uosmo_info.denom)], ) @@ -177,16 +177,16 @@ fn test_liquidatee_does_not_have_debt_coin() { }); let liquidator = Addr::unchecked("liquidator"); - let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); let res = mock.update_credit_account( - &liquidator_token_id, + &liquidator_account_id, &liquidator, vec![ - Borrow(uatom_info.to_coin(Uint128::from(50u128))), + Borrow(uatom_info.to_coin(50)), LiquidateCoin { - liquidatee_token_id: liquidatee_token_id.clone(), - debt_coin: ujake_info.to_coin(Uint128::from(10u128)), + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: ujake_info.to_coin(10), request_coin_denom: uatom_info.denom, }, ], @@ -206,24 +206,24 @@ fn test_liquidator_does_not_have_enough_to_pay_debt() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .build() .unwrap(); - let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); mock.update_credit_account( - &liquidatee_token_id, + &liquidatee_account_id, &liquidatee, vec![ - Deposit(uosmo_info.to_coin(Uint128::from(300u128))), - Borrow(uatom_info.to_coin(Uint128::from(100u128))), + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(100)), ], &[Coin::new(300, uosmo_info.clone().denom)], ) .unwrap(); - let health = mock.query_health(&liquidatee_token_id); + let health = mock.query_health(&liquidatee_account_id); assert!(!health.liquidatable); mock.price_change(CoinPrice { @@ -232,14 +232,14 @@ fn test_liquidator_does_not_have_enough_to_pay_debt() { }); let liquidator = Addr::unchecked("liquidator"); - let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); let res = mock.update_credit_account( - &liquidator_token_id, + &liquidator_account_id, &liquidator, vec![LiquidateCoin { - liquidatee_token_id: liquidatee_token_id.clone(), - debt_coin: uatom_info.to_coin(Uint128::from(10u128)), + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(10), request_coin_denom: uosmo_info.denom, }], &[], @@ -265,24 +265,24 @@ fn test_liquidator_left_in_unhealthy_state() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .build() .unwrap(); - let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); mock.update_credit_account( - &liquidatee_token_id, + &liquidatee_account_id, &liquidatee, vec![ - Deposit(uosmo_info.to_coin(Uint128::from(300u128))), - Borrow(uatom_info.to_coin(Uint128::from(100u128))), + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(100)), ], &[Coin::new(300, uosmo_info.clone().denom)], ) .unwrap(); - let health = mock.query_health(&liquidatee_token_id); + let health = mock.query_health(&liquidatee_account_id); assert!(!health.liquidatable); mock.price_change(CoinPrice { @@ -291,16 +291,16 @@ fn test_liquidator_left_in_unhealthy_state() { }); let liquidator = Addr::unchecked("liquidator"); - let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); let res = mock.update_credit_account( - &liquidator_token_id, + &liquidator_account_id, &liquidator, vec![ - Borrow(uatom_info.to_coin(Uint128::from(10u128))), + Borrow(uatom_info.to_coin(10)), LiquidateCoin { - liquidatee_token_id: liquidatee_token_id.clone(), - debt_coin: uatom_info.to_coin(Uint128::from(10u128)), + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(10), request_coin_denom: uosmo_info.denom, }, ], @@ -310,7 +310,7 @@ fn test_liquidator_left_in_unhealthy_state() { assert_err( res, AboveMaxLTV { - token_id: liquidator_token_id, + account_id: liquidator_account_id, max_ltv_health_factor: "0.7945".to_string(), }, ) @@ -328,22 +328,22 @@ fn test_liquidatee_not_healthier_after_liquidation() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: vec![Coin::new(300u128, uatom_info.denom.clone())], + funds: coins(300, uatom_info.denom.clone()), }) .build() .unwrap(); - let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); mock.update_credit_account( - &liquidatee_token_id, + &liquidatee_account_id, &liquidatee, vec![ - Deposit(uosmo_info.to_coin(Uint128::from(300u128))), - Borrow(uatom_info.to_coin(Uint128::from(100u128))), + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(100)), ], &[Coin::new(300, uosmo_info.denom.clone())], ) @@ -354,20 +354,20 @@ fn test_liquidatee_not_healthier_after_liquidation() { price: Decimal::from_atomics(20u128, 0).unwrap(), }); - let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); let res = mock.update_credit_account( - &liquidator_token_id, + &liquidator_account_id, &liquidator, vec![ - Deposit(uatom_info.to_coin(Uint128::from(50u128))), + Deposit(uatom_info.to_coin(50)), LiquidateCoin { - liquidatee_token_id: liquidatee_token_id.clone(), - debt_coin: uatom_info.to_coin(Uint128::from(50u128)), + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(50), request_coin_denom: uosmo_info.denom, }, ], - &[uatom_info.to_coin(Uint128::from(50u128))], + &[uatom_info.to_coin(50)], ); assert_err( @@ -390,22 +390,22 @@ fn test_debt_amount_adjusted_to_close_factor_max() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: vec![Coin::new(300u128, uatom_info.denom.clone())], + funds: coins(300, uatom_info.denom.clone()), }) .build() .unwrap(); - let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); mock.update_credit_account( - &liquidatee_token_id, + &liquidatee_account_id, &liquidatee, vec![ - Deposit(uosmo_info.to_coin(Uint128::from(300u128))), - Borrow(uatom_info.to_coin(Uint128::from(100u128))), + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(100)), ], &[Coin::new(300, uosmo_info.denom.clone())], ) @@ -416,25 +416,25 @@ fn test_debt_amount_adjusted_to_close_factor_max() { price: Decimal::from_atomics(6u128, 0).unwrap(), }); - let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); mock.update_credit_account( - &liquidator_token_id, + &liquidator_account_id, &liquidator, vec![ - Deposit(uatom_info.to_coin(Uint128::from(50u128))), + Deposit(uatom_info.to_coin(50)), LiquidateCoin { - liquidatee_token_id: liquidatee_token_id.clone(), - debt_coin: uatom_info.to_coin(Uint128::from(50u128)), + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(50), request_coin_denom: uosmo_info.denom, }, ], - &[uatom_info.to_coin(Uint128::from(50u128))], + &[uatom_info.to_coin(50)], ) .unwrap(); // Assert liquidatee's new position - let position = mock.query_position(&liquidatee_token_id); + let position = mock.query_position(&liquidatee_account_id); assert_eq!(position.coins.len(), 2); let osmo_balance = get_coin(&position.coins, "uosmo"); assert_eq!(osmo_balance.amount, Uint128::new(48)); @@ -446,7 +446,7 @@ fn test_debt_amount_adjusted_to_close_factor_max() { assert_eq!(atom_debt.amount, Uint128::new(91)); // Assert liquidator's new position - let position = mock.query_position(&liquidator_token_id); + let position = mock.query_position(&liquidator_account_id); assert_eq!(position.coins.len(), 2); assert_eq!(position.debt.len(), 0); let atom_balance = get_coin(&position.coins, "uatom"); @@ -467,23 +467,23 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: vec![Coin::new(300u128, ujake_info.denom.clone())], + funds: coins(300, ujake_info.denom.clone()), }) .build() .unwrap(); - let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); mock.update_credit_account( - &liquidatee_token_id, + &liquidatee_account_id, &liquidatee, vec![ - Deposit(uosmo_info.to_coin(Uint128::from(300u128))), - Borrow(uatom_info.to_coin(Uint128::from(100u128))), - Borrow(ujake_info.to_coin(Uint128::from(10u128))), + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(100)), + Borrow(ujake_info.to_coin(10)), ], &[Coin::new(300, uosmo_info.denom.clone())], ) @@ -494,25 +494,25 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { price: Decimal::from_atomics(20u128, 0).unwrap(), }); - let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); mock.update_credit_account( - &liquidator_token_id, + &liquidator_account_id, &liquidator, vec![ - Deposit(ujake_info.to_coin(Uint128::from(50u128))), + Deposit(ujake_info.to_coin(50)), LiquidateCoin { - liquidatee_token_id: liquidatee_token_id.clone(), - debt_coin: ujake_info.to_coin(Uint128::from(50u128)), + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: ujake_info.to_coin(50), request_coin_denom: uosmo_info.denom, }, ], - &[ujake_info.to_coin(Uint128::from(50u128))], + &[ujake_info.to_coin(50)], ) .unwrap(); // Assert liquidatee's new position - let position = mock.query_position(&liquidatee_token_id); + let position = mock.query_position(&liquidatee_account_id); assert_eq!(position.coins.len(), 3); let osmo_balance = get_coin(&position.coins, "uosmo"); assert_eq!(osmo_balance.amount, Uint128::new(191)); @@ -526,7 +526,7 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { assert_eq!(atom_debt.amount, Uint128::new(101)); // Assert liquidator's new position - let position = mock.query_position(&liquidator_token_id); + let position = mock.query_position(&liquidator_account_id); assert_eq!(position.coins.len(), 2); assert_eq!(position.debt.len(), 0); let jake_balance = get_coin(&position.coins, "ujake"); @@ -546,22 +546,22 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: vec![Coin::new(300u128, uatom_info.denom.clone())], + funds: coins(300, uatom_info.denom.clone()), }) .build() .unwrap(); - let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); mock.update_credit_account( - &liquidatee_token_id, + &liquidatee_account_id, &liquidatee, vec![ - Deposit(uosmo_info.to_coin(Uint128::from(300u128))), - Borrow(uatom_info.to_coin(Uint128::from(100u128))), + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(100)), ], &[Coin::new(300, uosmo_info.denom.clone())], ) @@ -572,25 +572,25 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { price: Decimal::from_atomics(20u128, 0).unwrap(), }); - let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); mock.update_credit_account( - &liquidator_token_id, + &liquidator_account_id, &liquidator, vec![ - Deposit(uatom_info.to_coin(Uint128::from(50u128))), + Deposit(uatom_info.to_coin(50)), LiquidateCoin { - liquidatee_token_id: liquidatee_token_id.clone(), - debt_coin: uatom_info.to_coin(Uint128::from(50u128)), + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(50), request_coin_denom: uosmo_info.denom, }, ], - &[uatom_info.to_coin(Uint128::from(50u128))], + &[uatom_info.to_coin(50)], ) .unwrap(); // Assert liquidatee's new position - let position = mock.query_position(&liquidatee_token_id); + let position = mock.query_position(&liquidatee_account_id); assert_eq!(position.coins.len(), 2); let osmo_balance = get_coin(&position.coins, "uosmo"); assert_eq!(osmo_balance.amount, Uint128::new(48)); @@ -602,7 +602,7 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { assert_eq!(atom_debt.amount, Uint128::new(98)); // Assert liquidator's new position - let position = mock.query_position(&liquidator_token_id); + let position = mock.query_position(&liquidator_account_id); assert_eq!(position.coins.len(), 2); assert_eq!(position.debt.len(), 0); let atom_balance = get_coin(&position.coins, "uatom"); @@ -622,22 +622,22 @@ fn test_debt_amount_no_adjustment() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![Coin::new(300u128, uosmo_info.denom.clone())], + funds: coins(300, uosmo_info.denom.clone()), }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: vec![Coin::new(300u128, uatom_info.denom.clone())], + funds: coins(300, uatom_info.denom.clone()), }) .build() .unwrap(); - let liquidatee_token_id = mock.create_credit_account(&liquidatee).unwrap(); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); mock.update_credit_account( - &liquidatee_token_id, + &liquidatee_account_id, &liquidatee, vec![ - Deposit(uosmo_info.to_coin(Uint128::from(300u128))), - Borrow(uatom_info.to_coin(Uint128::from(100u128))), + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(100)), ], &[Coin::new(300, uosmo_info.denom.clone())], ) @@ -648,25 +648,25 @@ fn test_debt_amount_no_adjustment() { price: Decimal::from_atomics(55u128, 1).unwrap(), }); - let liquidator_token_id = mock.create_credit_account(&liquidator).unwrap(); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); mock.update_credit_account( - &liquidator_token_id, + &liquidator_account_id, &liquidator, vec![ - Deposit(uatom_info.to_coin(Uint128::from(10u128))), + Deposit(uatom_info.to_coin(10)), LiquidateCoin { - liquidatee_token_id: liquidatee_token_id.clone(), - debt_coin: uatom_info.to_coin(Uint128::from(10u128)), + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(10), request_coin_denom: uosmo_info.denom, }, ], - &[uatom_info.to_coin(Uint128::from(10u128))], + &[uatom_info.to_coin(10)], ) .unwrap(); // Assert liquidatee's new position - let position = mock.query_position(&liquidatee_token_id); + let position = mock.query_position(&liquidatee_account_id); assert_eq!(position.coins.len(), 2); let osmo_balance = get_coin(&position.coins, "uosmo"); assert_eq!(osmo_balance.amount, Uint128::new(69)); @@ -678,7 +678,7 @@ fn test_debt_amount_no_adjustment() { assert_eq!(atom_debt.amount, Uint128::new(91)); // Assert liquidator's new position - let position = mock.query_position(&liquidator_token_id); + let position = mock.query_position(&liquidator_account_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.debt.len(), 0); let osmo_balance = get_coin(&position.coins, "uosmo"); diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index ab9025145..d8fcfab08 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -1,6 +1,6 @@ use std::ops::{Add, Mul, Sub}; -use cosmwasm_std::{Addr, Coin, Decimal, OverflowError, OverflowOperation, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Decimal, OverflowError, OverflowOperation, Uint128}; use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use rover::error::ContractError; @@ -17,13 +17,13 @@ fn test_only_token_owner_can_repay() { let coin_info = uosmo_info(); let owner = Addr::unchecked("owner"); let mut mock = MockEnv::new().build().unwrap(); - let token_id = mock.create_credit_account(&owner).unwrap(); + let account_id = mock.create_credit_account(&owner).unwrap(); let another_user = Addr::unchecked("another_user"); let res = mock.update_credit_account( - &token_id, + &account_id, &another_user, - vec![Repay(coin_info.to_coin(Uint128::new(12312u128)))], + vec![Repay(coin_info.to_coin(12312))], &[], ); @@ -31,7 +31,7 @@ fn test_only_token_owner_can_repay() { res, ContractError::NotTokenOwner { user: another_user.into(), - token_id, + account_id, }, ) } @@ -41,15 +41,12 @@ fn test_can_only_repay_what_is_whitelisted() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new().allowed_coins(&[coin_info]).build().unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Repay(Coin { - denom: "usomething".to_string(), - amount: Uint128::new(234), - })], + vec![Repay(coin(234, "usomething"))], &[], ); @@ -67,14 +64,10 @@ fn test_repaying_zero_raises() { .allowed_coins(&[coin_info.clone()]) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let res = mock.update_credit_account( - &token_id, - &user, - vec![Repay(coin_info.to_coin(Uint128::zero()))], - &[], - ); + let res = + mock.update_credit_account(&account_id, &user, vec![Repay(coin_info.to_coin(0))], &[]); assert_err(res, ContractError::NoAmount) } @@ -97,39 +90,39 @@ fn test_raises_when_repaying_what_is_not_owed() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user_a.clone(), - funds: vec![Coin::new(300u128, uatom_info.denom.clone())], + funds: coins(300, uatom_info.denom.clone()), }) .fund_account(AccountToFund { addr: user_b.clone(), - funds: vec![Coin::new(100u128, uatom_info.denom.clone())], + funds: coins(100, uatom_info.denom.clone()), }) .build() .unwrap(); - let token_id_a = mock.create_credit_account(&user_a).unwrap(); - let token_id_b = mock.create_credit_account(&user_b).unwrap(); + let account_id_a = mock.create_credit_account(&user_a).unwrap(); + let account_id_b = mock.create_credit_account(&user_b).unwrap(); // Seeding uatom with existing total debt shares from another user mock.update_credit_account( - &token_id_b, + &account_id_b, &user_b, vec![ - Deposit(uatom_info.to_coin(Uint128::new(100))), - Borrow(uatom_info.to_coin(Uint128::new(12))), + Deposit(uatom_info.to_coin(100)), + Borrow(uatom_info.to_coin(12)), ], - &[uatom_info.to_coin(Uint128::new(100))], + &[uatom_info.to_coin(100)], ) .unwrap(); let res = mock.update_credit_account( - &token_id_a, + &account_id_a, &user_a, vec![ - Deposit(uatom_info.to_coin(Uint128::new(300))), - Borrow(uosmo_info.to_coin(Uint128::new(42))), - Repay(uatom_info.to_coin(Uint128::new(42))), + Deposit(uatom_info.to_coin(300)), + Borrow(uosmo_info.to_coin(42)), + Repay(uatom_info.to_coin(42)), ], - &[uatom_info.to_coin(Uint128::new(300))], + &[uatom_info.to_coin(300)], ); assert_err(res, ContractError::NoDebt) @@ -152,23 +145,23 @@ fn test_raises_when_not_enough_assets_to_repay() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, uatom_info.denom.clone())], + funds: coins(300, uatom_info.denom.clone()), }) .build() .unwrap(); - let token_id_a = mock.create_credit_account(&user).unwrap(); + let account_id_a = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id_a, + &account_id_a, &user, vec![ - Deposit(uatom_info.to_coin(Uint128::new(300))), - Borrow(uosmo_info.to_coin(Uint128::new(50))), - Withdraw(uosmo_info.to_coin(Uint128::new(10))), - Repay(uosmo_info.to_coin(Uint128::new(50))), + Deposit(uatom_info.to_coin(300)), + Borrow(uosmo_info.to_coin(50)), + Withdraw(uosmo_info.to_coin(10)), + Repay(uosmo_info.to_coin(50)), ], - &[uatom_info.to_coin(Uint128::new(300))], + &[uatom_info.to_coin(300)], ); assert_err( @@ -191,39 +184,34 @@ fn test_successful_repay() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 0); assert_eq!(position.debt.len(), 0); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(coin_info.to_coin(Uint128::new(300))), - Borrow(coin_info.to_coin(Uint128::new(50))), + Deposit(coin_info.to_coin(300)), + Borrow(coin_info.to_coin(50)), ], - &[Coin::new(300u128, coin_info.denom.clone())], + &[coin(300, coin_info.denom.clone())], ) .unwrap(); let interim_red_bank_debt = mock.query_red_bank_debt(&coin_info.denom); - mock.update_credit_account( - &token_id, - &user, - vec![Repay(coin_info.to_coin(Uint128::new(20)))], - &[], - ) - .unwrap(); + mock.update_credit_account(&account_id, &user, vec![Repay(coin_info.to_coin(20))], &[]) + .unwrap(); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 1); let asset_res = position.coins.first().unwrap(); let expected_net_asset_amount = Uint128::new(330); // Deposit + Borrow - Repay @@ -254,14 +242,14 @@ fn test_successful_repay() { ); mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Repay(coin_info.to_coin(Uint128::new(31)))], // Interest accrued paid back as well + vec![Repay(coin_info.to_coin(31))], // Interest accrued paid back as well &[], ) .unwrap(); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 1); let asset_res = position.coins.first().unwrap(); let expected_net_asset_amount = Uint128::new(299); // Deposit + Borrow - full repay - interest @@ -292,26 +280,26 @@ fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(coin_info.to_coin(Uint128::new(300))), - Borrow(coin_info.to_coin(Uint128::new(50))), - Repay(coin_info.to_coin(Uint128::new(75))), + Deposit(coin_info.to_coin(300)), + Borrow(coin_info.to_coin(50)), + Repay(coin_info.to_coin(75)), ], - &[Coin::new(300u128, coin_info.denom.clone())], + &[coin(300, coin_info.denom.clone())], ) .unwrap(); - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 1); let asset_res = position.coins.first().unwrap(); let expected_net_asset_amount = Uint128::new(299); // Deposit + Borrow - Repay - interest diff --git a/contracts/credit-manager/tests/test_swap.rs b/contracts/credit-manager/tests/test_swap.rs index 8c0678e3a..9778c5feb 100644 --- a/contracts/credit-manager/tests/test_swap.rs +++ b/contracts/credit-manager/tests/test_swap.rs @@ -1,5 +1,5 @@ use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{coin, Addr, Coin, Decimal, OverflowError, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Coin, Decimal, OverflowError, Uint128}; use rover::error::ContractError; use rover::msg::execute::Action::{Deposit, SwapExactIn}; @@ -13,11 +13,11 @@ pub mod helpers; fn test_only_token_owner_can_swap_for_account() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let another_user = Addr::unchecked("another_user"); let res = mock.update_credit_account( - &token_id, + &account_id, &another_user, vec![SwapExactIn { coin_in: coin(12, "mars"), @@ -31,7 +31,7 @@ fn test_only_token_owner_can_swap_for_account() { res, ContractError::NotTokenOwner { user: another_user.into(), - token_id, + account_id, }, ) } @@ -40,10 +40,10 @@ fn test_only_token_owner_can_swap_for_account() { fn test_coin_in_must_be_whitelisted() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![SwapExactIn { coin_in: coin(12, "mars"), @@ -65,13 +65,13 @@ fn test_denom_out_must_be_whitelisted() { .allowed_coins(&[osmo_info.clone()]) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![SwapExactIn { - coin_in: osmo_info.to_coin(Uint128::new(10_000)), + coin_in: osmo_info.to_coin(10_000), denom_out: "ujake".to_string(), slippage: Decimal::from_atomics(6u128, 1).unwrap(), }], @@ -91,13 +91,13 @@ fn test_no_amount_sent() { .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![SwapExactIn { - coin_in: osmo_info.to_coin(Uint128::zero()), + coin_in: osmo_info.to_coin(0), denom_out: atom_info.denom, slippage: Decimal::from_atomics(6u128, 1).unwrap(), }], @@ -117,13 +117,13 @@ fn test_user_has_zero_balance_for_swap_req() { .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![SwapExactIn { - coin_in: osmo_info.to_coin(Uint128::new(10_000)), + coin_in: osmo_info.to_coin(10_000), denom_out: atom_info.denom, slippage: Decimal::from_atomics(6u128, 1).unwrap(), }], @@ -150,24 +150,24 @@ fn test_user_does_not_have_enough_balance_for_swap_req() { .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, osmo_info.denom.clone())], + funds: coins(300, osmo_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(osmo_info.to_coin(Uint128::new(100))), + Deposit(osmo_info.to_coin(100)), SwapExactIn { - coin_in: osmo_info.to_coin(Uint128::new(10_000)), + coin_in: osmo_info.to_coin(10_000), denom_out: atom_info.denom, slippage: Decimal::from_atomics(6u128, 1).unwrap(), }, ], - &[osmo_info.to_coin(Uint128::new(100))], + &[osmo_info.to_coin(100)], ); assert_err( @@ -195,22 +195,22 @@ fn test_swap_successful() { .build() .unwrap(); - let res = mock.query_swap_estimate(&atom_info.to_coin(Uint128::new(10_000)), &osmo_info.denom); + let res = mock.query_swap_estimate(&atom_info.to_coin(10_000), &osmo_info.denom); assert_eq!(res.amount, MOCK_SWAP_RESULT); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(atom_info.to_coin(Uint128::new(10_000))), + Deposit(atom_info.to_coin(10_000)), SwapExactIn { - coin_in: atom_info.to_coin(Uint128::new(10_000)), + coin_in: atom_info.to_coin(10_000), denom_out: osmo_info.denom.clone(), slippage: Decimal::from_atomics(6u128, 1).unwrap(), }, ], - &[atom_info.to_coin(Uint128::new(10_000))], + &[atom_info.to_coin(10_000)], ) .unwrap(); @@ -221,7 +221,7 @@ fn test_swap_successful() { assert_eq!(osmo_balance, MOCK_SWAP_RESULT); // assert account position - let position = mock.query_position(&token_id); + let position = mock.query_position(&account_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); assert_eq!(position.coins.first().unwrap().amount, MOCK_SWAP_RESULT); diff --git a/contracts/credit-manager/tests/test_vault_deposit.rs b/contracts/credit-manager/tests/test_vault_deposit.rs index 7510cfcfc..260331505 100644 --- a/contracts/credit-manager/tests/test_vault_deposit.rs +++ b/contracts/credit-manager/tests/test_vault_deposit.rs @@ -1,5 +1,5 @@ use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{Addr, Coin, OverflowError, Uint128}; +use cosmwasm_std::{coin, coins, Addr, OverflowError, Uint128}; use mock_vault::contract::STARTING_VAULT_SHARES; use rover::adapters::VaultBase; @@ -15,11 +15,11 @@ fn test_only_account_owner_can_take_action() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let bad_guy = Addr::unchecked("bad_guy"); let res = mock.update_credit_account( - &token_id, + &account_id, &bad_guy, vec![VaultDeposit { vault: VaultBase::new("xyz".to_string()), @@ -32,7 +32,7 @@ fn test_only_account_owner_can_take_action() { res, ContractError::NotTokenOwner { user: bad_guy.to_string(), - token_id, + account_id, }, ); } @@ -54,14 +54,14 @@ fn test_all_deposit_coins_are_whitelisted() { .unwrap(); let vault = mock.get_vault(&leverage_vault); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![VaultDeposit { vault, - coins: vec![Coin::new(200u128, "uatom"), Coin::new(400u128, "uosmo")], + coins: vec![coin(200, "uatom"), coin(400, "uosmo")], }], &[], ); @@ -87,14 +87,14 @@ fn test_vault_is_whitelisted() { .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![VaultDeposit { vault: VaultBase::new("unknown_vault".to_string()), - coins: vec![Coin::new(200u128, "uatom")], + coins: coins(200, "uatom"), }], &[], ); @@ -123,14 +123,14 @@ fn test_deposited_coins_match_vault_requirements() { .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![VaultDeposit { vault: mock.get_vault(&leverage_vault), - coins: vec![Coin::new(200u128, "uatom"), Coin::new(200u128, "uosmo")], + coins: vec![coin(200, "uatom"), coin(200, "uosmo")], }], &[], ); @@ -160,19 +160,19 @@ fn test_fails_if_not_enough_funds_for_deposit() { .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, "uatom"), Coin::new(500u128, "uosmo")], + funds: vec![coin(300, "uatom"), coin(500, "uosmo")], }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![VaultDeposit { vault: mock.get_vault(&leverage_vault), - coins: vec![Coin::new(200u128, "uatom"), Coin::new(200u128, "uosmo")], + coins: vec![coin(200, "uatom"), coin(200, "uosmo")], }], &[], ); @@ -204,41 +204,35 @@ fn test_successful_deposit_into_locked_vault() { .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, "uatom"), Coin::new(500u128, "uosmo")], + funds: vec![coin(300, "uatom"), coin(500, "uosmo")], }) .build() .unwrap(); let vault = mock.get_vault(&leverage_vault); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let balance = mock.query_total_vault_coin_balance(&vault); assert_eq!(balance, Uint128::zero()); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(Coin { - denom: uatom.denom, - amount: Uint128::new(200), - }), - Deposit(Coin { - denom: uosmo.denom, - amount: Uint128::new(400), - }), + Deposit(coin(200, uatom.denom)), + Deposit(coin(400, uosmo.denom)), VaultDeposit { vault: vault.clone(), - coins: vec![Coin::new(23u128, "uatom"), Coin::new(120u128, "uosmo")], + coins: vec![coin(23, "uatom"), coin(120, "uosmo")], }, ], - &[Coin::new(200u128, "uatom"), Coin::new(400u128, "uosmo")], + &[coin(200, "uatom"), coin(400, "uosmo")], ) .unwrap(); let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.vault_positions.len(), 1); assert_eq!( STARTING_VAULT_SHARES, @@ -281,39 +275,33 @@ fn test_successful_deposit_into_unlocked_vault() { .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, "uatom"), Coin::new(500u128, "uosmo")], + funds: vec![coin(300, "uatom"), coin(500, "uosmo")], }) .build() .unwrap(); let vault = mock.get_vault(&leverage_vault); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(Coin { - denom: uatom.denom, - amount: Uint128::new(200), - }), - Deposit(Coin { - denom: uosmo.denom, - amount: Uint128::new(400), - }), + Deposit(coin(200, uatom.denom)), + Deposit(coin(400, uosmo.denom)), VaultDeposit { vault: vault.clone(), - coins: vec![Coin::new(23u128, "uatom"), Coin::new(120u128, "uosmo")], + coins: vec![coin(23, "uatom"), coin(120, "uosmo")], }, ], - &[Coin::new(200u128, "uatom"), Coin::new(400u128, "uosmo")], + &[coin(200, "uatom"), coin(400, "uosmo")], ) .unwrap(); let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.vault_positions.len(), 1); assert_eq!( STARTING_VAULT_SHARES, diff --git a/contracts/credit-manager/tests/test_vault_withdraw.rs b/contracts/credit-manager/tests/test_vault_withdraw.rs index 3c8e733a1..c4ad5b852 100644 --- a/contracts/credit-manager/tests/test_vault_withdraw.rs +++ b/contracts/credit-manager/tests/test_vault_withdraw.rs @@ -1,5 +1,5 @@ use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{Addr, Coin, OverflowError, Uint128}; +use cosmwasm_std::{coin, Addr, OverflowError, Uint128}; use mock_vault::contract::STARTING_VAULT_SHARES; use rover::adapters::VaultBase; @@ -18,11 +18,11 @@ fn test_only_owner_of_token_can_withdraw_from_vault() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let bad_guy = Addr::unchecked("bad_guy"); let res = mock.update_credit_account( - &token_id, + &account_id, &bad_guy, vec![VaultWithdraw { vault: VaultBase::new("some_vault".to_string()), @@ -35,7 +35,7 @@ fn test_only_owner_of_token_can_withdraw_from_vault() { res, NotTokenOwner { user: bad_guy.into(), - token_id, + account_id, }, ) } @@ -45,10 +45,10 @@ fn test_can_only_take_action_on_whitelisted_vaults() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![VaultWithdraw { vault: VaultBase::new("not_allowed_vault".to_string()), @@ -77,36 +77,30 @@ fn test_no_unlocked_vault_coins_to_withdraw() { .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, "uatom"), Coin::new(500u128, "uosmo")], + funds: vec![coin(300, "uatom"), coin(500, "uosmo")], }) .build() .unwrap(); let vault = mock.get_vault(&leverage_vault); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(Coin { - denom: uatom.denom, - amount: Uint128::from(200u128), - }), - Deposit(Coin { - denom: uosmo.denom, - amount: Uint128::from(200u128), - }), + Deposit(coin(200, uatom.denom)), + Deposit(coin(200, uosmo.denom)), VaultDeposit { vault: vault.clone(), - coins: vec![Coin::new(100u128, "uatom"), Coin::new(100u128, "uosmo")], + coins: vec![coin(100, "uatom"), coin(100, "uosmo")], }, VaultWithdraw { vault, amount: STARTING_VAULT_SHARES, }, ], - &[Coin::new(200u128, "uatom"), Coin::new(200u128, "uosmo")], + &[coin(200, "uatom"), coin(200, "uosmo")], ); assert_err( @@ -134,12 +128,12 @@ fn test_force_withdraw_can_only_be_called_by_rover() { .unwrap(); let vault = mock.get_vault(&leverage_vault); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.invoke_callback( &user.clone(), CallbackMsg::VaultForceWithdraw { - token_id, + account_id, vault: VaultBase::new(Addr::unchecked(vault.address().clone())), amount: STARTING_VAULT_SHARES, }, @@ -164,37 +158,31 @@ fn test_force_withdraw_breaks_lock() { .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, "uatom"), Coin::new(500u128, "uosmo")], + funds: vec![coin(300, "uatom"), coin(500, "uosmo")], }) .build() .unwrap(); let vault = mock.get_vault(&leverage_vault); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(Coin { - denom: uatom.denom, - amount: Uint128::from(200u128), - }), - Deposit(Coin { - denom: uosmo.denom, - amount: Uint128::from(200u128), - }), + Deposit(coin(200, uatom.denom)), + Deposit(coin(200, uosmo.denom)), VaultDeposit { vault: vault.clone(), - coins: vec![Coin::new(100u128, "uatom"), Coin::new(100u128, "uosmo")], + coins: vec![coin(100, "uatom"), coin(100, "uosmo")], }, ], - &[Coin::new(200u128, "uatom"), Coin::new(200u128, "uosmo")], + &[coin(200, "uatom"), coin(200, "uosmo")], ) .unwrap(); // Assert token's position - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.vault_positions.len(), 1); let v = res.vault_positions.first().unwrap(); assert_eq!(v.position.locked, STARTING_VAULT_SHARES); @@ -202,7 +190,7 @@ fn test_force_withdraw_breaks_lock() { mock.invoke_callback( &mock.rover.clone(), CallbackMsg::VaultForceWithdraw { - token_id: token_id.clone(), + account_id: account_id.clone(), vault: VaultBase::new(Addr::unchecked(vault.address().clone())), amount: STARTING_VAULT_SHARES, }, @@ -210,7 +198,7 @@ fn test_force_withdraw_breaks_lock() { .unwrap(); // Assert token's updated position - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.vault_positions.len(), 0); let atom = get_coin("uatom", &res.coins); assert_eq!(atom.amount, Uint128::from(200u128)); @@ -245,37 +233,31 @@ fn test_withdraw_with_unlocked_vault_coins() { .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, "uatom"), Coin::new(500u128, "uosmo")], + funds: vec![coin(300, "uatom"), coin(500, "uosmo")], }) .build() .unwrap(); let vault = mock.get_vault(&leverage_vault); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Deposit(Coin { - denom: uatom.denom, - amount: Uint128::from(200u128), - }), - Deposit(Coin { - denom: uosmo.denom, - amount: Uint128::from(200u128), - }), + Deposit(coin(200, uatom.denom)), + Deposit(coin(200, uosmo.denom)), VaultDeposit { vault: vault.clone(), - coins: vec![Coin::new(100u128, "uatom"), Coin::new(100u128, "uosmo")], + coins: vec![coin(100, "uatom"), coin(100, "uosmo")], }, ], - &[Coin::new(200u128, "uatom"), Coin::new(200u128, "uosmo")], + &[coin(200, "uatom"), coin(200, "uosmo")], ) .unwrap(); // Assert token's position - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.vault_positions.len(), 1); let v = res.vault_positions.first().unwrap(); assert_eq!(v.position.unlocked, STARTING_VAULT_SHARES); @@ -295,7 +277,7 @@ fn test_withdraw_with_unlocked_vault_coins() { assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); mock.update_credit_account( - &token_id, + &account_id, &user, vec![VaultWithdraw { vault, @@ -306,7 +288,7 @@ fn test_withdraw_with_unlocked_vault_coins() { .unwrap(); // Assert token's updated position - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.vault_positions.len(), 0); let atom = get_coin("uatom", &res.coins); assert_eq!(atom.amount, Uint128::from(200u128)); diff --git a/contracts/credit-manager/tests/test_withdraw.rs b/contracts/credit-manager/tests/test_withdraw.rs index 425a88bc3..67f999092 100644 --- a/contracts/credit-manager/tests/test_withdraw.rs +++ b/contracts/credit-manager/tests/test_withdraw.rs @@ -1,5 +1,5 @@ use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{Addr, Coin, OverflowError, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Coin, OverflowError, Uint128}; use rover::error::ContractError; use rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; @@ -14,16 +14,13 @@ fn test_only_owner_of_token_can_withdraw() { let coin_info = uosmo_info(); let owner = Addr::unchecked("owner"); let mut mock = MockEnv::new().build().unwrap(); - let token_id = mock.create_credit_account(&owner).unwrap(); + let account_id = mock.create_credit_account(&owner).unwrap(); let another_user = Addr::unchecked("another_user"); let res = mock.update_credit_account( - &token_id, + &account_id, &another_user, - vec![Action::Withdraw(Coin { - denom: coin_info.denom, - amount: Uint128::new(382), - })], + vec![Action::Withdraw(coin(382, coin_info.denom))], &[], ); @@ -31,11 +28,11 @@ fn test_only_owner_of_token_can_withdraw() { res, NotTokenOwner { user: another_user.into(), - token_id: token_id.clone(), + account_id: account_id.clone(), }, ); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); } @@ -47,21 +44,18 @@ fn test_withdraw_nothing() { .allowed_coins(&[coin_info.clone()]) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Action::Withdraw(Coin { - denom: coin_info.denom, - amount: Uint128::new(0), - })], + vec![Action::Withdraw(coin(0, coin_info.denom))], &[], ); assert_err(res, ContractError::NoAmount); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); } @@ -73,13 +67,12 @@ fn test_withdraw_but_no_funds() { .allowed_coins(&[coin_info.clone()]) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let withdraw_amount = Uint128::new(234); let res = mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Action::Withdraw(coin_info.to_coin(withdraw_amount))], + vec![Action::Withdraw(coin_info.to_coin(234))], &[], ); @@ -92,7 +85,7 @@ fn test_withdraw_but_no_funds() { }), ); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); } @@ -104,20 +97,20 @@ fn test_withdraw_but_not_enough_funds() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Action::Deposit(coin_info.to_coin(Uint128::new(300))), - Action::Withdraw(coin_info.to_coin(Uint128::new(400))), + Action::Deposit(coin_info.to_coin(300)), + Action::Withdraw(coin_info.to_coin(400)), ], - &[Coin::new(300u128, coin_info.denom)], + &[coin(300, coin_info.denom)], ); assert_err( @@ -129,7 +122,7 @@ fn test_withdraw_but_not_enough_funds() { }), ); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); } @@ -141,26 +134,26 @@ fn test_can_only_withdraw_allowed_assets() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let not_allowed_coin = ujake_info().to_coin(Uint128::new(234)); + let not_allowed_coin = ujake_info().to_coin(234); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Action::Deposit(coin_info.to_coin(Uint128::new(234))), + Action::Deposit(coin_info.to_coin(234)), Action::Withdraw(not_allowed_coin.clone()), ], - &[Coin::new(234u128, coin_info.denom)], + &[coin(234, coin_info.denom)], ); assert_err(res, NotWhitelisted(not_allowed_coin.denom)); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); } @@ -172,33 +165,32 @@ fn test_cannot_withdraw_more_than_healthy() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let deposit_amount = Uint128::new(200); let res = mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Action::Deposit(coin_info.to_coin(deposit_amount)), - Action::Borrow(coin_info.to_coin(Uint128::new(400))), - Action::Withdraw(coin_info.to_coin(Uint128::new(50))), + Action::Deposit(coin_info.to_coin(200)), + Action::Borrow(coin_info.to_coin(400)), + Action::Withdraw(coin_info.to_coin(50)), ], - &[Coin::new(200u128, coin_info.denom)], + &[coin(200, coin_info.denom)], ); assert_err( res, ContractError::AboveMaxLTV { - token_id: token_id.clone(), + account_id: account_id.clone(), max_ltv_health_factor: "0.960099750623441396".to_string(), }, ); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); } @@ -210,25 +202,25 @@ fn test_withdraw_success() { .allowed_coins(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![Coin::new(300u128, coin_info.denom.clone())], + funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let deposit_amount = Uint128::new(234); + let deposit_amount = 234; mock.update_credit_account( - &token_id, + &account_id, &user, vec![ Action::Deposit(coin_info.to_coin(deposit_amount)), Action::Withdraw(coin_info.to_coin(deposit_amount)), ], - &[Coin::new(deposit_amount.into(), coin_info.denom.clone())], + &[Coin::new(deposit_amount, coin_info.denom.clone())], ) .unwrap(); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); let coin = mock.query_balance(&mock.rover, &coin_info.denom); @@ -246,32 +238,32 @@ fn test_multiple_withdraw_actions() { .fund_account(AccountToFund { addr: user.clone(), funds: vec![ - Coin::new(234u128, uosmo_info.denom.clone()), - Coin::new(25u128, uatom_info.denom.clone()), + coin(234, uosmo_info.denom.clone()), + coin(25, uatom_info.denom.clone()), ], }) .build() .unwrap(); - let token_id = mock.create_credit_account(&user).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); let uosmo_amount = Uint128::new(234); let uatom_amount = Uint128::new(25); mock.update_credit_account( - &token_id, + &account_id, &user, vec![ - Action::Deposit(uosmo_info.to_coin(uosmo_amount)), - Action::Deposit(uatom_info.to_coin(uatom_amount)), + Action::Deposit(uosmo_info.to_coin(uosmo_amount.u128())), + Action::Deposit(uatom_info.to_coin(uatom_amount.u128())), ], &[ - Coin::new(234u128, uosmo_info.denom.clone()), - Coin::new(25u128, uatom_info.denom.clone()), + coin(234, uosmo_info.denom.clone()), + coin(25, uatom_info.denom.clone()), ], ) .unwrap(); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 2); let coin = mock.query_balance(&user, &uosmo_info.denom); @@ -281,14 +273,14 @@ fn test_multiple_withdraw_actions() { assert_eq!(coin.amount, Uint128::zero()); mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Action::Withdraw(uosmo_info.to_coin(uosmo_amount))], + vec![Action::Withdraw(uosmo_info.to_coin(uosmo_amount.u128()))], &[], ) .unwrap(); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 1); let coin = mock.query_balance(&mock.rover, &uosmo_info.denom); @@ -298,14 +290,14 @@ fn test_multiple_withdraw_actions() { assert_eq!(coin.amount, uosmo_amount); mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Action::Withdraw(uatom_info.to_coin(Uint128::new(20)))], + vec![Action::Withdraw(uatom_info.to_coin(20))], &[], ) .unwrap(); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 1); let coin = mock.query_balance(&mock.rover, &uatom_info.denom); @@ -315,14 +307,14 @@ fn test_multiple_withdraw_actions() { assert_eq!(coin.amount, Uint128::new(20)); mock.update_credit_account( - &token_id, + &account_id, &user, - vec![Action::Withdraw(uatom_info.to_coin(Uint128::new(5)))], + vec![Action::Withdraw(uatom_info.to_coin(5))], &[], ) .unwrap(); - let res = mock.query_position(&token_id); + let res = mock.query_position(&account_id); assert_eq!(res.coins.len(), 0); let coin = mock.query_balance(&mock.rover, &uatom_info.denom); diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index e0296bdb7..2e55a1da9 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -18,6 +18,6 @@ mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" -cw-storage-plus = "0.14" +cw-storage-plus = "0.15" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/contracts/mock-vault/Cargo.toml b/contracts/mock-vault/Cargo.toml index 5a7b9a6e8..62b7a470c 100644 --- a/contracts/mock-vault/Cargo.toml +++ b/contracts/mock-vault/Cargo.toml @@ -14,7 +14,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -rover = { version = "0.1", path = "../../packages/rover" } +rover = { version = "1.0", path = "../../packages/rover" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" diff --git a/contracts/mock-vault/src/state.rs b/contracts/mock-vault/src/state.rs index 7edb77fec..03a85ceb4 100644 --- a/contracts/mock-vault/src/state.rs +++ b/contracts/mock-vault/src/state.rs @@ -2,10 +2,9 @@ use cosmwasm_std::Uint128; use cw_storage_plus::{Item, Map}; use rover::adapters::Oracle; -use rover::Shares; pub const LP_TOKEN_DENOM: Item = Item::new("lp_token_denom"); -pub const TOTAL_VAULT_SHARES: Item = Item::new("total_vault_shares"); +pub const TOTAL_VAULT_SHARES: Item = Item::new("total_vault_shares"); pub const LOCKUP_TIME: Item> = Item::new("lockup_time"); pub const ASSETS: Map = Map::new("assets"); // Denom -> Amount pub const ORACLE: Item = Item::new("oracle"); diff --git a/contracts/swapper/base/Cargo.toml b/contracts/swapper/base/Cargo.toml index 65f99ba2f..5f8a10893 100644 --- a/contracts/swapper/base/Cargo.toml +++ b/contracts/swapper/base/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "swapper-base" -version = "0.1.0" +version = "1.0.0" authors = ["grod220 "] edition = "2021" license = "GPL-3.0-or-later" @@ -14,7 +14,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -rover = { version = "0.1", path = "../../../packages/rover" } +rover = { version = "1.0", path = "../../../packages/rover" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" diff --git a/contracts/swapper/mock/Cargo.toml b/contracts/swapper/mock/Cargo.toml index 51fb733b3..45689864a 100644 --- a/contracts/swapper/mock/Cargo.toml +++ b/contracts/swapper/mock/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "swapper-mock" -version = "0.1.0" +version = "1.0.0" authors = ["grod220 "] edition = "2021" license = "GPL-3.0-or-later" @@ -14,8 +14,8 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -rover = { version = "0.1", path = "../../../packages/rover" } -swapper-base = { path = "../base", version = "0.1" } +rover = { version = "1.0", path = "../../../packages/rover" } +swapper-base = { path = "../base", version = "1.0" } cosmwasm-std = "1.1" cw-storage-plus = "0.15" diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index 63f9cb4c4..966f24ac7 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "swapper-osmosis" -version = "0.1.0" +version = "1.0.0" authors = ["grod220 "] edition = "2021" license = "GPL-3.0-or-later" @@ -14,8 +14,8 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -rover = { version = "0.1", path = "../../../packages/rover" } -swapper-base = { path = "../base", version = "0.1" } +rover = { version = "1.0", path = "../../../packages/rover" } +swapper-base = { path = "../base", version = "1.0" } cosmwasm-std = "1.1" cw-storage-plus = "0.15" diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index b53dedad0..df3651053 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rover" -version = "0.1.0" +version = "1.0.0" authors = ["larry_0x ", "Gabe R. "] edition = "2021" license = "GPL-3.0-or-later" diff --git a/packages/rover/src/adapters/vault.rs b/packages/rover/src/adapters/vault.rs index a75492b1d..ab76bf808 100644 --- a/packages/rover/src/adapters/vault.rs +++ b/packages/rover/src/adapters/vault.rs @@ -9,7 +9,6 @@ use crate::adapters::Oracle; use crate::error::ContractResult; use crate::extensions::Stringify; use crate::msg::vault::{ExecuteMsg, QueryMsg, VaultInfo}; -use crate::Shares; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)] #[serde(rename_all = "snake_case")] @@ -125,7 +124,7 @@ impl Vault { pub fn query_redeem_preview( &self, querier: &QuerierWrapper, - shares: Shares, + shares: Uint128, ) -> StdResult> { let response: Vec = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.0.to_string(), diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 94d10a8e0..78994bef1 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -12,7 +12,7 @@ pub type ContractResult = Result; pub enum ContractError { #[error("Actions resulted in exceeding maximum allowed loan-to-value. Max LTV health factor: {max_ltv_health_factor:?}")] AboveMaxLTV { - token_id: String, + account_id: String, max_ltv_health_factor: String, }, @@ -52,15 +52,15 @@ pub enum ContractError { NoDebt, #[error( - "{token_id:?} is not a liquidatable credit account. Health factor: {lqdt_health_factor:?}." + "{account_id:?} is not a liquidatable credit account. Health factor: {lqdt_health_factor:?}." )] NotLiquidatable { - token_id: String, + account_id: String, lqdt_health_factor: String, }, - #[error("{user:?} is not the owner of {token_id:?}")] - NotTokenOwner { user: String, token_id: String }, + #[error("{user:?} is not the owner of {account_id:?}")] + NotTokenOwner { user: String, account_id: String }, #[error("{0} is not whitelisted")] NotWhitelisted(String), diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index ca399be4c..833a26332 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -1,12 +1,5 @@ -use cosmwasm_std::{Addr, Uint128}; - pub mod adapters; pub mod coins; pub mod error; pub mod extensions; pub mod msg; - -pub type Denom<'a> = &'a str; -pub type NftTokenId<'a> = &'a str; -pub type Shares = Uint128; -pub type VaultAddr = Addr; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 5e923d51b..1e88c7f28 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -15,7 +15,7 @@ pub enum ExecuteMsg { CreateCreditAccount {}, /// Update user's position on their credit account UpdateCreditAccount { - token_id: String, + account_id: String, actions: Vec, }, @@ -60,7 +60,7 @@ pub enum Action { /// - The value of the debt repaid exceeds the maximum close factor % LiquidateCoin { /// The credit account id of the one with a liquidation threshold health factor 1 or below - liquidatee_token_id: String, + liquidatee_account_id: String, /// The coin debt that the liquidator wishes to pay back on behalf of the liquidatee. /// The liquidator must already have these assets in their credit account. debt_coin: Coin, @@ -82,22 +82,22 @@ pub enum CallbackMsg { /// Withdraw specified amount of coin from credit account; /// Decrement the token's asset amount; Withdraw { - token_id: String, + account_id: String, coin: Coin, recipient: Addr, }, /// Borrow specified amount of coin from Red Bank; /// Increase the token's coin amount and debt shares; - Borrow { token_id: String, coin: Coin }, + Borrow { account_id: String, coin: Coin }, /// Repay coin of specified amount back to Red Bank; /// Decrement the token's coin amount and debt shares; - Repay { token_id: String, coin: Coin }, + Repay { account_id: String, coin: Coin }, /// Calculate the account's max loan-to-value health factor. If above 1, /// emits a `position_changed` event. If 1 or below, raises an error. - AssertBelowMaxLTV { token_id: String }, + AssertBelowMaxLTV { account_id: String }, /// Adds list of coins to a vault strategy VaultDeposit { - token_id: String, + account_id: String, vault: Vault, coins: Vec, }, @@ -105,38 +105,38 @@ pub enum CallbackMsg { UpdateVaultCoinBalance { vault: Vault, /// Account that needs vault coin balance adjustment - token_id: String, + account_id: String, /// Total vault coin balance in Rover previous_total_balance: Uint128, }, /// Exchanges vault LP shares for assets VaultWithdraw { - token_id: String, + account_id: String, vault: Vault, amount: Uint128, }, /// A privileged action only to be used by Rover. Same as `VaultWithdraw` except it bypasses any lockup period /// restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation. VaultForceWithdraw { - token_id: String, + account_id: String, vault: Vault, amount: Uint128, }, /// Pay back debts of a liquidatable rover account for a bonus LiquidateCoin { - liquidator_token_id: String, - liquidatee_token_id: String, + liquidator_account_id: String, + liquidatee_account_id: String, debt_coin: Coin, request_coin_denom: String, }, /// Determine health factor improved as a consequence of liquidation event AssertHealthFactorImproved { - token_id: String, + account_id: String, previous_health_factor: Decimal, }, /// Perform a swapper with an exact-in amount. Requires slippage allowance %. SwapExactIn { - token_id: String, + account_id: String, coin_in: Coin, denom_out: String, slippage: Decimal, @@ -144,7 +144,7 @@ pub enum CallbackMsg { /// Used to update the coin balance of account after an async action UpdateCoinBalances { /// Account that needs coin balance adjustment - token_id: String, + account_id: String, /// Total balances for coins in Rover prior to withdraw previous_balances: Vec, }, diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 4f974a7b1..e32850d8f 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -26,17 +26,17 @@ pub enum QueryMsg { }, /// All positions represented by token with value #[returns(PositionsWithValueResponse)] - Positions { token_id: String }, + Positions { account_id: String }, /// The health of the account represented by token #[returns(HealthResponse)] - Health { token_id: String }, - /// Enumerate coin balances for all token positions; start_after accepts (token_id, denom) + Health { account_id: String }, + /// Enumerate coin balances for all token positions; start_after accepts (account_id, denom) #[returns(Vec)] AllCoinBalances { start_after: Option<(String, String)>, limit: Option, }, - /// Enumerate debt shares for all token positions; start_after accepts (token_id, denom) + /// Enumerate debt shares for all token positions; start_after accepts (account_id, denom) #[returns(Vec)] AllDebtShares { start_after: Option<(String, String)>, @@ -51,7 +51,7 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, - /// Enumerate all vault positions; start_after accepts (token_id, addr) + /// Enumerate all vault positions; start_after accepts (account_id, addr) #[returns(Vec)] AllVaultPositions { start_after: Option<(String, String)>, @@ -71,7 +71,7 @@ pub enum QueryMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct CoinBalanceResponseItem { - pub token_id: String, + pub account_id: String, pub denom: String, pub amount: Uint128, } @@ -79,7 +79,7 @@ pub struct CoinBalanceResponseItem { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct SharesResponseItem { - pub token_id: String, + pub account_id: String, pub denom: String, pub shares: Uint128, } @@ -117,7 +117,7 @@ pub struct CoinValue { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct Positions { - pub token_id: String, + pub account_id: String, pub coins: Vec, pub debt: Vec, pub vault_positions: Vec, @@ -126,7 +126,7 @@ pub struct Positions { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct VaultPositionResponseItem { - pub token_id: String, + pub account_id: String, pub addr: String, pub vault_position: VaultPosition, } @@ -149,7 +149,7 @@ pub struct VaultPositionWithAddr { #[serde(rename_all = "snake_case")] pub struct PositionsWithValueResponse { /// Unique NFT token id that represents the cross-margin account. The owner of this NFT, owns the account. - pub token_id: String, + pub account_id: String, /// All coin balances value pub coins: Vec, /// All debt positions with value diff --git a/schemas/account-nft/account-nft.json b/schemas/account-nft/account-nft.json index 983aebbe1..85d8e7799 100644 --- a/schemas/account-nft/account-nft.json +++ b/schemas/account-nft/account-nft.json @@ -1,6 +1,6 @@ { "contract_name": "account-nft", - "contract_version": "0.1.0", + "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -24,7 +24,8 @@ "description": "Symbol of the NFT contract", "type": "string" } - } + }, + "additionalProperties": false }, "execute": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -317,7 +318,8 @@ ], "properties": { "never": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -677,6 +679,7 @@ ] } }, + "additionalProperties": false, "definitions": { "Approval": { "type": "object", @@ -697,7 +700,8 @@ "description": "Account that can transfer/send the token", "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)", @@ -742,7 +746,8 @@ ], "properties": { "never": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -770,7 +775,8 @@ "null" ] } - } + }, + "additionalProperties": false }, "OwnerOfResponse": { "type": "object", @@ -790,7 +796,8 @@ "description": "Owner of the token", "type": "string" } - } + }, + "additionalProperties": false }, "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); ```", @@ -821,6 +828,7 @@ } } }, + "additionalProperties": false, "definitions": { "Approval": { "type": "object", @@ -841,7 +849,8 @@ "description": "Account that can transfer/send the token", "type": "string" } - } + }, + "additionalProperties": false }, "Expiration": { "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", @@ -882,7 +891,8 @@ ], "properties": { "never": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -918,7 +928,8 @@ "type": "string" } } - } + }, + "additionalProperties": false }, "approval": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -932,6 +943,7 @@ "$ref": "#/definitions/Approval" } }, + "additionalProperties": false, "definitions": { "Approval": { "type": "object", @@ -952,7 +964,8 @@ "description": "Account that can transfer/send the token", "type": "string" } - } + }, + "additionalProperties": false }, "Expiration": { "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", @@ -993,7 +1006,8 @@ ], "properties": { "never": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -1029,6 +1043,7 @@ } } }, + "additionalProperties": false, "definitions": { "Approval": { "type": "object", @@ -1049,7 +1064,8 @@ "description": "Account that can transfer/send the token", "type": "string" } - } + }, + "additionalProperties": false }, "Expiration": { "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", @@ -1090,7 +1106,8 @@ ], "properties": { "never": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -1126,7 +1143,8 @@ "symbol": { "type": "string" } - } + }, + "additionalProperties": false }, "minter": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -1140,7 +1158,8 @@ "minter": { "type": "string" } - } + }, + "additionalProperties": false }, "nft_info": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -1166,6 +1185,7 @@ ] } }, + "additionalProperties": false, "definitions": { "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)", @@ -1186,7 +1206,8 @@ "format": "uint64", "minimum": 0.0 } - } + }, + "additionalProperties": false }, "owner_of": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -1209,6 +1230,7 @@ "type": "string" } }, + "additionalProperties": false, "definitions": { "Approval": { "type": "object", @@ -1229,7 +1251,8 @@ "description": "Account that can transfer/send the token", "type": "string" } - } + }, + "additionalProperties": false }, "Expiration": { "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", @@ -1270,7 +1293,8 @@ ], "properties": { "never": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -1311,7 +1335,8 @@ "type": "string" } } - } + }, + "additionalProperties": false } } } diff --git a/schemas/credit-manager/credit-manager.json b/schemas/credit-manager/credit-manager.json index b2823a800..e7024abf7 100644 --- a/schemas/credit-manager/credit-manager.json +++ b/schemas/credit-manager/credit-manager.json @@ -1,6 +1,6 @@ { "contract_name": "credit-manager", - "contract_version": "0.1.0", + "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -122,18 +122,18 @@ "update_credit_account": { "type": "object", "required": [ - "actions", - "token_id" + "account_id", + "actions" ], "properties": { + "account_id": { + "type": "string" + }, "actions": { "type": "array", "items": { "$ref": "#/definitions/Action" } - }, - "token_id": { - "type": "string" } } } @@ -295,7 +295,7 @@ "type": "object", "required": [ "debt_coin", - "liquidatee_token_id", + "liquidatee_account_id", "request_coin_denom" ], "properties": { @@ -307,7 +307,7 @@ } ] }, - "liquidatee_token_id": { + "liquidatee_account_id": { "description": "The credit account id of the one with a liquidation threshold health factor 1 or below", "type": "string" }, @@ -368,19 +368,19 @@ "withdraw": { "type": "object", "required": [ + "account_id", "coin", - "recipient", - "token_id" + "recipient" ], "properties": { + "account_id": { + "type": "string" + }, "coin": { "$ref": "#/definitions/Coin" }, "recipient": { "$ref": "#/definitions/Addr" - }, - "token_id": { - "type": "string" } } } @@ -397,15 +397,15 @@ "borrow": { "type": "object", "required": [ - "coin", - "token_id" + "account_id", + "coin" ], "properties": { + "account_id": { + "type": "string" + }, "coin": { "$ref": "#/definitions/Coin" - }, - "token_id": { - "type": "string" } } } @@ -422,15 +422,15 @@ "repay": { "type": "object", "required": [ - "coin", - "token_id" + "account_id", + "coin" ], "properties": { + "account_id": { + "type": "string" + }, "coin": { "$ref": "#/definitions/Coin" - }, - "token_id": { - "type": "string" } } } @@ -447,10 +447,10 @@ "assert_below_max_l_t_v": { "type": "object", "required": [ - "token_id" + "account_id" ], "properties": { - "token_id": { + "account_id": { "type": "string" } } @@ -468,20 +468,20 @@ "vault_deposit": { "type": "object", "required": [ + "account_id", "coins", - "token_id", "vault" ], "properties": { + "account_id": { + "type": "string" + }, "coins": { "type": "array", "items": { "$ref": "#/definitions/Coin" } }, - "token_id": { - "type": "string" - }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" } @@ -500,11 +500,15 @@ "update_vault_coin_balance": { "type": "object", "required": [ + "account_id", "previous_total_balance", - "token_id", "vault" ], "properties": { + "account_id": { + "description": "Account that needs vault coin balance adjustment", + "type": "string" + }, "previous_total_balance": { "description": "Total vault coin balance in Rover", "allOf": [ @@ -513,10 +517,6 @@ } ] }, - "token_id": { - "description": "Account that needs vault coin balance adjustment", - "type": "string" - }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" } @@ -535,17 +535,17 @@ "vault_withdraw": { "type": "object", "required": [ + "account_id", "amount", - "token_id", "vault" ], "properties": { + "account_id": { + "type": "string" + }, "amount": { "$ref": "#/definitions/Uint128" }, - "token_id": { - "type": "string" - }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" } @@ -564,17 +564,17 @@ "vault_force_withdraw": { "type": "object", "required": [ + "account_id", "amount", - "token_id", "vault" ], "properties": { + "account_id": { + "type": "string" + }, "amount": { "$ref": "#/definitions/Uint128" }, - "token_id": { - "type": "string" - }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" } @@ -594,18 +594,18 @@ "type": "object", "required": [ "debt_coin", - "liquidatee_token_id", - "liquidator_token_id", + "liquidatee_account_id", + "liquidator_account_id", "request_coin_denom" ], "properties": { "debt_coin": { "$ref": "#/definitions/Coin" }, - "liquidatee_token_id": { + "liquidatee_account_id": { "type": "string" }, - "liquidator_token_id": { + "liquidator_account_id": { "type": "string" }, "request_coin_denom": { @@ -626,15 +626,15 @@ "assert_health_factor_improved": { "type": "object", "required": [ - "previous_health_factor", - "token_id" + "account_id", + "previous_health_factor" ], "properties": { + "account_id": { + "type": "string" + }, "previous_health_factor": { "$ref": "#/definitions/Decimal" - }, - "token_id": { - "type": "string" } } } @@ -651,12 +651,15 @@ "swap_exact_in": { "type": "object", "required": [ + "account_id", "coin_in", "denom_out", - "slippage", - "token_id" + "slippage" ], "properties": { + "account_id": { + "type": "string" + }, "coin_in": { "$ref": "#/definitions/Coin" }, @@ -665,9 +668,6 @@ }, "slippage": { "$ref": "#/definitions/Decimal" - }, - "token_id": { - "type": "string" } } } @@ -684,20 +684,20 @@ "update_coin_balances": { "type": "object", "required": [ - "previous_balances", - "token_id" + "account_id", + "previous_balances" ], "properties": { + "account_id": { + "description": "Account that needs coin balance adjustment", + "type": "string" + }, "previous_balances": { "description": "Total balances for coins in Rover prior to withdraw", "type": "array", "items": { "$ref": "#/definitions/Coin" } - }, - "token_id": { - "description": "Account that needs coin balance adjustment", - "type": "string" } } } @@ -924,10 +924,10 @@ "positions": { "type": "object", "required": [ - "token_id" + "account_id" ], "properties": { - "token_id": { + "account_id": { "type": "string" } }, @@ -946,10 +946,10 @@ "health": { "type": "object", "required": [ - "token_id" + "account_id" ], "properties": { - "token_id": { + "account_id": { "type": "string" } }, @@ -959,7 +959,7 @@ "additionalProperties": false }, { - "description": "Enumerate coin balances for all token positions; start_after accepts (token_id, denom)", + "description": "Enumerate coin balances for all token positions; start_after accepts (account_id, denom)", "type": "object", "required": [ "all_coin_balances" @@ -999,7 +999,7 @@ "additionalProperties": false }, { - "description": "Enumerate debt shares for all token positions; start_after accepts (token_id, denom)", + "description": "Enumerate debt shares for all token positions; start_after accepts (account_id, denom)", "type": "object", "required": [ "all_debt_shares" @@ -1082,7 +1082,7 @@ "additionalProperties": false }, { - "description": "Enumerate all vault positions; start_after accepts (token_id, addr)", + "description": "Enumerate all vault positions; start_after accepts (account_id, addr)", "type": "object", "required": [ "all_vault_positions" @@ -1198,19 +1198,19 @@ "CoinBalanceResponseItem": { "type": "object", "required": [ + "account_id", "amount", - "denom", - "token_id" + "denom" ], "properties": { + "account_id": { + "type": "string" + }, "amount": { "$ref": "#/definitions/Uint128" }, "denom": { "type": "string" - }, - "token_id": { - "type": "string" } } }, @@ -1231,19 +1231,19 @@ "SharesResponseItem": { "type": "object", "required": [ + "account_id", "denom", - "shares", - "token_id" + "shares" ], "properties": { + "account_id": { + "type": "string" + }, "denom": { "type": "string" }, "shares": { "$ref": "#/definitions/Uint128" - }, - "token_id": { - "type": "string" } } }, @@ -1344,15 +1344,15 @@ "VaultPositionResponseItem": { "type": "object", "required": [ + "account_id", "addr", - "token_id", "vault_position" ], "properties": { - "addr": { + "account_id": { "type": "string" }, - "token_id": { + "addr": { "type": "string" }, "vault_position": { @@ -1492,12 +1492,16 @@ "title": "PositionsWithValueResponse", "type": "object", "required": [ + "account_id", "coins", "debt", - "token_id", "vault_positions" ], "properties": { + "account_id": { + "description": "Unique NFT token id that represents the cross-margin account. The owner of this NFT, owns the account.", + "type": "string" + }, "coins": { "description": "All coin balances value", "type": "array", @@ -1512,10 +1516,6 @@ "$ref": "#/definitions/DebtSharesValue" } }, - "token_id": { - "description": "Unique NFT token id that represents the cross-margin account. The owner of this NFT, owns the account.", - "type": "string" - }, "vault_positions": { "description": "All vault positions", "type": "array", diff --git a/schemas/mock-red-bank/mock-red-bank.json b/schemas/mock-red-bank/mock-red-bank.json index 4b84a3f9d..81899bdd6 100644 --- a/schemas/mock-red-bank/mock-red-bank.json +++ b/schemas/mock-red-bank/mock-red-bank.json @@ -185,6 +185,7 @@ "borrow_enabled", "borrow_index", "borrow_rate", + "collateral_total_scaled", "debt_total_scaled", "denom", "deposit_cap", @@ -195,7 +196,6 @@ "liquidation_threshold", "liquidity_index", "liquidity_rate", - "ma_token_address", "max_loan_to_value", "reserve_factor" ], @@ -220,6 +220,14 @@ } ] }, + "collateral_total_scaled": { + "description": "Total collateral scaled for the market's currency", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, "debt_total_scaled": { "description": "Total debt scaled for the market's currency", "allOf": [ @@ -290,14 +298,6 @@ } ] }, - "ma_token_address": { - "description": "maToken contract address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, "max_loan_to_value": { "description": "Max base asset that can be borrowed per \"base asset\" collateral when using the asset as collateral", "allOf": [ @@ -316,10 +316,6 @@ } }, "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" - }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" diff --git a/schemas/swapper-base/swapper-base.json b/schemas/swapper-base/swapper-base.json index f50201138..04491e88a 100644 --- a/schemas/swapper-base/swapper-base.json +++ b/schemas/swapper-base/swapper-base.json @@ -1,6 +1,6 @@ { "contract_name": "swapper-base", - "contract_version": "0.1.0", + "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 8bfee62c8..a50d632b8 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,8 +1,8 @@ { - "accountNft": "osmo1zwxt98d4w6dc7nwgh5znmqf2kglyax4gg9hr3497lmfywfcckppsgw3hkx", - "mockRedBank": "osmo1zqsgw96drq3cav4kwfecfy26zyl0wt6zpkh9xwfj08smt6acq45skrakkp", - "mockOracle": "osmo1dqfzp4vqyfcz0f6yuajvky9gwaxnpw2kmh447z0wy37k40hl8g8sdwq9qp", - "mockVault": "osmo1m7cx2r6czyqmqc7nt8zhlk8y63ewk23wfg3tvng9askeztzt7f7qq294hz", - "swapper": "osmo1dkg8rkhgtvav3aufth6ud49pz5ces6vqg5vwdgthlhgtutq74q8qrrt2wd", - "creditManager": "osmo1zf26ahe5gqjtvnedh7ems7naf2wtw3z4ll6atf3t0hptal8ss4vq2mlx6w" + "accountNft": "osmo1cs0f9yv3smthukxdag6k3z06j2vhar26l63arkxuupec6lj0e7lst9els8", + "mockRedBank": "osmo1fswlsmvw7du33gqgnp9jmthlcxfzmu66ph8wlf8xuw4lz8gtt6fqmx6tt3", + "mockOracle": "osmo1kgrrj45k4udlw7d4flaamsrq0633q4pa5rlyueffua0kcx2llsmsddrs9d", + "mockVault": "osmo1hssy7afj5ye3zt9p67wh7py2lmkyx2vy3jxpc7p6fpqtqkgyvn7sltg6sn", + "swapper": "osmo10dpyf07zpn769e9e0kfkgqllw6yc3wvvpl5efmg5lpc6l7pkl9xs0g2zem", + "creditManager": "osmo1wla04nzxlugq7v0k6fkez6625uctt3tpwfxzgh0lavgpslw4supsm4qw38" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 0bff2437f..6f7a233a5 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -55,7 +55,7 @@ export class Deployer { const { contractAddress } = await this.cwClient.instantiate( this.deployerAddr, codeId, - msg, + msg as Record, `mars-${name}`, 'auto', ) diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 2924c6335..5c5b2aa7c 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -52,7 +52,7 @@ export class Rover { [{ deposit: { amount, denom: this.config.baseDenom } }], [{ amount, denom: this.config.baseDenom }], ) - const positions = await this.query.positions({ tokenId: this.accountId! }) + const positions = await this.query.positions({ accountId: this.accountId! }) assert.equal(positions.coins.length, 1) assert.equal(positions.coins[0].amount, amount) assert.equal(positions.coins[0].denom, this.config.baseDenom) @@ -61,10 +61,10 @@ export class Rover { async withdraw() { const amount = this.config.withdrawAmount.toString() - const positionsBefore = await this.query.positions({ tokenId: this.accountId! }) + const positionsBefore = await this.query.positions({ accountId: this.accountId! }) const beforeWithdraw = parseFloat(positionsBefore.coins[0].amount) await this.updateCreditAccount([{ withdraw: { amount, denom: this.config.baseDenom } }]) - const positionsAfter = await this.query.positions({ tokenId: this.accountId! }) + const positionsAfter = await this.query.positions({ accountId: this.accountId! }) const afterWithdraw = parseFloat(positionsAfter.coins[0].amount) assert.equal(beforeWithdraw - afterWithdraw, amount) printGreen(`Withdrew: ${amount} ${this.config.baseDenom}`) @@ -73,7 +73,7 @@ export class Rover { async borrow() { const amount = this.config.borrowAmount.toString() await this.updateCreditAccount([{ borrow: { amount, denom: this.config.baseDenom } }]) - const positions = await this.query.positions({ tokenId: this.accountId! }) + const positions = await this.query.positions({ accountId: this.accountId! }) assert.equal(positions.debt.length, 1) assert.equal(positions.debt[0].denom, this.config.baseDenom) printGreen(`Borrowed from RedBank: ${amount} ${this.config.baseDenom}`) @@ -82,7 +82,7 @@ export class Rover { async repay() { const amount = this.config.repayAmount.toString() await this.updateCreditAccount([{ repay: { amount, denom: this.config.baseDenom } }]) - const positions = await this.query.positions({ tokenId: this.accountId! }) + const positions = await this.query.positions({ accountId: this.accountId! }) printGreen( `Repaid to RedBank: ${amount} ${this.config.baseDenom}. Debt remaining: ${positions.debt[0].amount} ${positions.debt[0].denom}`, ) @@ -91,7 +91,7 @@ export class Rover { async swap() { const amount = this.config.swapAmount.toString() printBlue(`Swapping ${amount} ${this.config.baseDenom} for ${this.config.secondaryDenom}`) - const prevPositions = await this.query.positions({ tokenId: this.accountId! }) + const prevPositions = await this.query.positions({ accountId: this.accountId! }) printBlue( `Previous account balance: ${prevPositions.coins[0].amount} ${prevPositions.coins[0].denom}`, ) @@ -105,7 +105,7 @@ export class Rover { }, ]) printGreen(`Swap successful`) - const newPositions = await this.query.positions({ tokenId: this.accountId! }) + const newPositions = await this.query.positions({ accountId: this.accountId! }) printGreen( `New account balance: ${newPositions.coins[0].amount} ${newPositions.coins[0].denom}, ${newPositions.coins[1].amount} ${newPositions.coins[1].denom}`, ) @@ -121,7 +121,7 @@ export class Rover { }, }, ]) - const positions = await this.query.positions({ tokenId: this.accountId! }) + const positions = await this.query.positions({ accountId: this.accountId! }) assert.equal(positions.vault_positions.length, 1) assert.equal(positions.vault_positions[0].addr, this.storage.addresses.mockVault) assert.equal(positions.vault_positions[0].position, this.config.baseDenom) @@ -134,7 +134,7 @@ export class Rover { private async updateCreditAccount(actions: Action[], funds?: Coin[]) { await this.exec.updateCreditAccount( - { actions, tokenId: this.accountId! }, + { actions, accountId: this.accountId! }, 'auto', undefined, funds, diff --git a/scripts/package.json b/scripts/package.json index 1d90983e7..2f88a3904 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -13,20 +13,19 @@ "format-check": "prettier --check ." }, "dependencies": { - "@cosmjs/cosmwasm-stargate": "^0.28.4", - "@cosmjs/stargate": "^0.28.4", - "@cosmwasm/ts-codegen": "^0.16.3", + "@cosmjs/cosmwasm-stargate": "^0.29.0", + "@cosmjs/stargate": "^0.29.0", + "@cosmwasm/ts-codegen": "^0.16.4", "chalk": "4.1.2", "cosmjs-types": "^0.5.0", "lodash": "^4.17.21", "long": "^5.2.0", - "osmojs": "^0.15.0", "prepend-file": "^2.0.1" }, "devDependencies": { "@babel/preset-env": "^7.19.1", "@babel/preset-typescript": "^7.18.6", - "@types/jest": "^29.0.2", + "@types/jest": "^29.0.3", "@typescript-eslint/eslint-plugin": "^5.37.0", "@typescript-eslint/parser": "^5.37.0", "eslint": "^8.23.1", diff --git a/scripts/types/generated/account-nft/AccountNft.client.ts b/scripts/types/generated/account-nft/AccountNft.client.ts index bd4008a1e..00c5febd5 100644 --- a/scripts/types/generated/account-nft/AccountNft.client.ts +++ b/scripts/types/generated/account-nft/AccountNft.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/account-nft/AccountNft.react-query.ts b/scripts/types/generated/account-nft/AccountNft.react-query.ts index 3c5f88e73..8bc775b9a 100644 --- a/scripts/types/generated/account-nft/AccountNft.react-query.ts +++ b/scripts/types/generated/account-nft/AccountNft.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/account-nft/AccountNft.types.ts b/scripts/types/generated/account-nft/AccountNft.types.ts index 332559b3e..066450187 100644 --- a/scripts/types/generated/account-nft/AccountNft.types.ts +++ b/scripts/types/generated/account-nft/AccountNft.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -9,7 +9,6 @@ export interface InstantiateMsg { minter: string name: string symbol: string - [k: string]: unknown } export type ExecuteMsg = | { @@ -87,9 +86,7 @@ export type Expiration = at_time: Timestamp } | { - never: { - [k: string]: unknown - } + never: {} } export type Timestamp = Uint64 export type Uint64 = string @@ -160,53 +157,42 @@ export type QueryMsg = export interface AllNftInfoResponseForEmpty { access: OwnerOfResponse info: NftInfoResponseForEmpty - [k: string]: unknown } export interface OwnerOfResponse { approvals: Approval[] owner: string - [k: string]: unknown } export interface Approval { expires: Expiration spender: string - [k: string]: unknown } export interface NftInfoResponseForEmpty { extension: Empty token_uri?: string | null - [k: string]: unknown } export interface Empty { [k: string]: unknown } export interface OperatorsResponse { operators: Approval[] - [k: string]: unknown } export interface TokensResponse { tokens: string[] - [k: string]: unknown } export interface ApprovalResponse { approval: Approval - [k: string]: unknown } export interface ApprovalsResponse { approvals: Approval[] - [k: string]: unknown } export interface ContractInfoResponse { name: string symbol: string - [k: string]: unknown } export interface MinterResponse { minter: string - [k: string]: unknown } export interface NumTokensResponse { count: number - [k: string]: unknown } export type String = string diff --git a/scripts/types/generated/account-nft/bundle.ts b/scripts/types/generated/account-nft/bundle.ts index 9622030ec..626d38f36 100644 --- a/scripts/types/generated/account-nft/bundle.ts +++ b/scripts/types/generated/account-nft/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/credit-manager/CreditManager.client.ts b/scripts/types/generated/credit-manager/CreditManager.client.ts index f542cc0c2..8f00e9d33 100644 --- a/scripts/types/generated/credit-manager/CreditManager.client.ts +++ b/scripts/types/generated/credit-manager/CreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -63,8 +63,8 @@ export interface CreditManagerReadOnlyInterface { limit?: number startAfter?: string }) => Promise - positions: ({ tokenId }: { tokenId: string }) => Promise - health: ({ tokenId }: { tokenId: string }) => Promise + positions: ({ accountId }: { accountId: string }) => Promise + health: ({ accountId }: { accountId: string }) => Promise allCoinBalances: ({ limit, startAfter, @@ -157,17 +157,17 @@ export class CreditManagerQueryClient implements CreditManagerReadOnlyInterface }, }) } - positions = async ({ tokenId }: { tokenId: string }): Promise => { + positions = async ({ accountId }: { accountId: string }): Promise => { return this.client.queryContractSmart(this.contractAddress, { positions: { - token_id: tokenId, + account_id: accountId, }, }) } - health = async ({ tokenId }: { tokenId: string }): Promise => { + health = async ({ accountId }: { accountId: string }): Promise => { return this.client.queryContractSmart(this.contractAddress, { health: { - token_id: tokenId, + account_id: accountId, }, }) } @@ -264,11 +264,11 @@ export interface CreditManagerInterface extends CreditManagerReadOnlyInterface { ) => Promise updateCreditAccount: ( { + accountId, actions, - tokenId, }: { + accountId: string actions: Action[] - tokenId: string }, fee?: number | StdFee | 'auto', memo?: string, @@ -327,11 +327,11 @@ export class CreditManagerClient } updateCreditAccount = async ( { + accountId, actions, - tokenId, }: { + accountId: string actions: Action[] - tokenId: string }, fee: number | StdFee | 'auto' = 'auto', memo?: string, @@ -342,8 +342,8 @@ export class CreditManagerClient this.contractAddress, { update_credit_account: { + account_id: accountId, actions, - token_id: tokenId, }, }, fee, diff --git a/scripts/types/generated/credit-manager/CreditManager.react-query.ts b/scripts/types/generated/credit-manager/CreditManager.react-query.ts index 3e1537155..99a427680 100644 --- a/scripts/types/generated/credit-manager/CreditManager.react-query.ts +++ b/scripts/types/generated/credit-manager/CreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -284,7 +284,7 @@ export function useCreditManagerAllCoinBalancesQuery extends CreditManagerReactQuery { args: { - tokenId: string + accountId: string } } export function useCreditManagerHealthQuery({ @@ -297,7 +297,7 @@ export function useCreditManagerHealthQuery({ () => client ? client.health({ - tokenId: args.tokenId, + accountId: args.accountId, }) : Promise.reject(new Error('Invalid client')), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, @@ -306,7 +306,7 @@ export function useCreditManagerHealthQuery({ export interface CreditManagerPositionsQuery extends CreditManagerReactQuery { args: { - tokenId: string + accountId: string } } export function useCreditManagerPositionsQuery({ @@ -319,7 +319,7 @@ export function useCreditManagerPositionsQuery client ? client.positions({ - tokenId: args.tokenId, + accountId: args.accountId, }) : Promise.reject(new Error('Invalid client')), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, @@ -431,8 +431,8 @@ export function useCreditManagerUpdateConfigMutation( export interface CreditManagerUpdateCreditAccountMutation { client: CreditManagerClient msg: { + accountId: string actions: Action[] - tokenId: string } args?: { fee?: number | StdFee | 'auto' diff --git a/scripts/types/generated/credit-manager/CreditManager.types.ts b/scripts/types/generated/credit-manager/CreditManager.types.ts index 6e7e7a35e..4c964acc1 100644 --- a/scripts/types/generated/credit-manager/CreditManager.types.ts +++ b/scripts/types/generated/credit-manager/CreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -29,8 +29,8 @@ export type ExecuteMsg = } | { update_credit_account: { + account_id: string actions: Action[] - token_id: string [k: string]: unknown } } @@ -73,7 +73,7 @@ export type Action = | { liquidate_coin: { debt_coin: Coin - liquidatee_token_id: string + liquidatee_account_id: string request_coin_denom: string [k: string]: unknown } @@ -90,60 +90,60 @@ export type Uint128 = string export type CallbackMsg = | { withdraw: { + account_id: string coin: Coin recipient: Addr - token_id: string [k: string]: unknown } } | { borrow: { + account_id: string coin: Coin - token_id: string [k: string]: unknown } } | { repay: { + account_id: string coin: Coin - token_id: string [k: string]: unknown } } | { assert_below_max_l_t_v: { - token_id: string + account_id: string [k: string]: unknown } } | { vault_deposit: { + account_id: string coins: Coin[] - token_id: string vault: VaultBaseForAddr [k: string]: unknown } } | { update_vault_coin_balance: { + account_id: string previous_total_balance: Uint128 - token_id: string vault: VaultBaseForAddr1 [k: string]: unknown } } | { vault_withdraw: { + account_id: string amount: Uint128 - token_id: string vault: VaultBaseForAddr2 [k: string]: unknown } } | { vault_force_withdraw: { + account_id: string amount: Uint128 - token_id: string vault: VaultBaseForAddr3 [k: string]: unknown } @@ -151,32 +151,32 @@ export type CallbackMsg = | { liquidate_coin: { debt_coin: Coin - liquidatee_token_id: string - liquidator_token_id: string + liquidatee_account_id: string + liquidator_account_id: string request_coin_denom: string [k: string]: unknown } } | { assert_health_factor_improved: { + account_id: string previous_health_factor: Decimal - token_id: string [k: string]: unknown } } | { swap_exact_in: { + account_id: string coin_in: Coin denom_out: string slippage: Decimal - token_id: string [k: string]: unknown } } | { update_coin_balances: { + account_id: string previous_balances: Coin[] - token_id: string [k: string]: unknown } } @@ -220,12 +220,12 @@ export type QueryMsg = } | { positions: { - token_id: string + account_id: string } } | { health: { - token_id: string + account_id: string } } | { @@ -268,16 +268,16 @@ export type QueryMsg = } export type ArrayOfCoinBalanceResponseItem = CoinBalanceResponseItem[] export interface CoinBalanceResponseItem { + account_id: string amount: Uint128 denom: string - token_id: string [k: string]: unknown } export type ArrayOfSharesResponseItem = SharesResponseItem[] export interface SharesResponseItem { + account_id: string denom: string shares: Uint128 - token_id: string [k: string]: unknown } export type ArrayOfDebtShares = DebtShares[] @@ -294,8 +294,8 @@ export interface VaultWithBalance { } export type ArrayOfVaultPositionResponseItem = VaultPositionResponseItem[] export interface VaultPositionResponseItem { + account_id: string addr: string - token_id: string vault_position: VaultPosition [k: string]: unknown } @@ -328,9 +328,9 @@ export interface HealthResponse { [k: string]: unknown } export interface PositionsWithValueResponse { + account_id: string coins: CoinValue[] debt: DebtSharesValue[] - token_id: string vault_positions: VaultPositionWithAddr[] [k: string]: unknown } diff --git a/scripts/types/generated/credit-manager/bundle.ts b/scripts/types/generated/credit-manager/bundle.ts index abc245fb8..c96367a1c 100644 --- a/scripts/types/generated/credit-manager/bundle.ts +++ b/scripts/types/generated/credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-oracle/MockOracle.client.ts b/scripts/types/generated/mock-oracle/MockOracle.client.ts index 1ac3024a9..417f1ec29 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.client.ts +++ b/scripts/types/generated/mock-oracle/MockOracle.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-oracle/MockOracle.react-query.ts b/scripts/types/generated/mock-oracle/MockOracle.react-query.ts index 740a328fc..7cfcee6f2 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.react-query.ts +++ b/scripts/types/generated/mock-oracle/MockOracle.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-oracle/MockOracle.types.ts b/scripts/types/generated/mock-oracle/MockOracle.types.ts index 7a9d5d195..9b3e67ae1 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.types.ts +++ b/scripts/types/generated/mock-oracle/MockOracle.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-oracle/bundle.ts b/scripts/types/generated/mock-oracle/bundle.ts index fa20c3b1d..7542d98d7 100644 --- a/scripts/types/generated/mock-oracle/bundle.ts +++ b/scripts/types/generated/mock-oracle/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.client.ts b/scripts/types/generated/mock-red-bank/MockRedBank.client.ts index 23b31d02d..5de4d6eb0 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.client.ts +++ b/scripts/types/generated/mock-red-bank/MockRedBank.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -15,7 +15,6 @@ import { Uint128, Coin, QueryMsg, - Addr, Market, InterestRateModel, UserAssetDebtResponse, diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts b/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts index 93a50d5f2..fbc2dc349 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts +++ b/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -16,7 +16,6 @@ import { Uint128, Coin, QueryMsg, - Addr, Market, InterestRateModel, UserAssetDebtResponse, diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.types.ts b/scripts/types/generated/mock-red-bank/MockRedBank.types.ts index f2dfbc20e..d63d6a045 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.types.ts +++ b/scripts/types/generated/mock-red-bank/MockRedBank.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -49,11 +49,11 @@ export type QueryMsg = denom: string } } -export type Addr = string export interface Market { borrow_enabled: boolean borrow_index: Decimal borrow_rate: Decimal + collateral_total_scaled: Uint128 debt_total_scaled: Uint128 denom: string deposit_cap: Uint128 @@ -64,7 +64,6 @@ export interface Market { liquidation_threshold: Decimal liquidity_index: Decimal liquidity_rate: Decimal - ma_token_address: Addr max_loan_to_value: Decimal reserve_factor: Decimal [k: string]: unknown diff --git a/scripts/types/generated/mock-red-bank/bundle.ts b/scripts/types/generated/mock-red-bank/bundle.ts index 321b4a2fc..0e3bcf403 100644 --- a/scripts/types/generated/mock-red-bank/bundle.ts +++ b/scripts/types/generated/mock-red-bank/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-vault/MockVault.client.ts b/scripts/types/generated/mock-vault/MockVault.client.ts index 947787997..4034dff1a 100644 --- a/scripts/types/generated/mock-vault/MockVault.client.ts +++ b/scripts/types/generated/mock-vault/MockVault.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-vault/MockVault.react-query.ts b/scripts/types/generated/mock-vault/MockVault.react-query.ts index 82e2ff5e9..e419297dd 100644 --- a/scripts/types/generated/mock-vault/MockVault.react-query.ts +++ b/scripts/types/generated/mock-vault/MockVault.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-vault/MockVault.types.ts b/scripts/types/generated/mock-vault/MockVault.types.ts index 0f0463939..7b01221f1 100644 --- a/scripts/types/generated/mock-vault/MockVault.types.ts +++ b/scripts/types/generated/mock-vault/MockVault.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-vault/bundle.ts b/scripts/types/generated/mock-vault/bundle.ts index e8f7a9b68..051c16f92 100644 --- a/scripts/types/generated/mock-vault/bundle.ts +++ b/scripts/types/generated/mock-vault/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/swapper-base/SwapperBase.client.ts b/scripts/types/generated/swapper-base/SwapperBase.client.ts index 5c6a9ddda..8ca9e9602 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.client.ts +++ b/scripts/types/generated/swapper-base/SwapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/swapper-base/SwapperBase.react-query.ts b/scripts/types/generated/swapper-base/SwapperBase.react-query.ts index 1619c9218..b021dbe28 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.react-query.ts +++ b/scripts/types/generated/swapper-base/SwapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/swapper-base/SwapperBase.types.ts b/scripts/types/generated/swapper-base/SwapperBase.types.ts index 93d8fdd21..c5eb30d75 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.types.ts +++ b/scripts/types/generated/swapper-base/SwapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/swapper-base/bundle.ts b/scripts/types/generated/swapper-base/bundle.ts index a625602d6..a413481f8 100644 --- a/scripts/types/generated/swapper-base/bundle.ts +++ b/scripts/types/generated/swapper-base/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.3. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 5ac045ef9..07bb4110f 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1096,7 +1096,7 @@ "@babel/helper-validator-option" "^7.18.6" "@babel/plugin-transform-typescript" "^7.18.6" -"@babel/runtime@^7.11.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.19.0", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.11.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.8.4": version "7.19.0" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz" integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== @@ -1175,143 +1175,143 @@ "@noble/hashes" "^1.0.0" protobufjs "^6.8.8" -"@cosmjs/amino@0.28.13": - version "0.28.13" - resolved "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.28.13.tgz" - integrity sha512-IHnH2zGwaY69qT4mVAavr/pfzx6YE+ud1NHJbvVePlbGiz68CXTi5LHR+K0lrKB5mQ7E+ZErWz2mw5U/x+V1wQ== - dependencies: - "@cosmjs/crypto" "0.28.13" - "@cosmjs/encoding" "0.28.13" - "@cosmjs/math" "0.28.13" - "@cosmjs/utils" "0.28.13" - -"@cosmjs/cosmwasm-stargate@^0.28.4": - version "0.28.13" - resolved "https://registry.npmjs.org/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.28.13.tgz" - integrity sha512-dVZNOiRd8btQreRUabncGhVXGCS2wToXqxi9l3KEHwCJQ2RWTshuqV+EZAdCaYHE5W6823s2Ol2W/ukA9AXJPw== - dependencies: - "@cosmjs/amino" "0.28.13" - "@cosmjs/crypto" "0.28.13" - "@cosmjs/encoding" "0.28.13" - "@cosmjs/math" "0.28.13" - "@cosmjs/proto-signing" "0.28.13" - "@cosmjs/stargate" "0.28.13" - "@cosmjs/tendermint-rpc" "0.28.13" - "@cosmjs/utils" "0.28.13" - cosmjs-types "^0.4.0" +"@cosmjs/amino@^0.29.0": + version "0.29.0" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.29.0.tgz#35873a580a6102e48415ed2b5b97477f146fb50d" + integrity sha512-/ZUVx6nRN5YE36H3SDq9+i8g2nZ8DJQnN9fVRC8rSHQKauNkoEuK4NxTNcQ2o2EBLUT0kyYAFY2550HVsPMrgw== + dependencies: + "@cosmjs/crypto" "^0.29.0" + "@cosmjs/encoding" "^0.29.0" + "@cosmjs/math" "^0.29.0" + "@cosmjs/utils" "^0.29.0" + +"@cosmjs/cosmwasm-stargate@^0.29.0": + version "0.29.0" + resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.29.0.tgz#dea1c16fe80daf14072c3796574fe8cb34a3729b" + integrity sha512-KoNc0XpK6Gh4CITpyMXIuhIdZu59lF3wO1pHabeEZ0v7w3U0tFdCbDppe2RufCkERDZZCGFxnoRmr0KL2wK6Tw== + dependencies: + "@cosmjs/amino" "^0.29.0" + "@cosmjs/crypto" "^0.29.0" + "@cosmjs/encoding" "^0.29.0" + "@cosmjs/math" "^0.29.0" + "@cosmjs/proto-signing" "^0.29.0" + "@cosmjs/stargate" "^0.29.0" + "@cosmjs/tendermint-rpc" "^0.29.0" + "@cosmjs/utils" "^0.29.0" + cosmjs-types "^0.5.0" long "^4.0.0" pako "^2.0.2" -"@cosmjs/crypto@0.28.13": - version "0.28.13" - resolved "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.28.13.tgz" - integrity sha512-ynKfM0q/tMBQMHJby6ad8lR3gkgBKaelQhIsCZTjClsnuC7oYT9y3ThSZCUWr7Pa9h0J8ahU2YV2oFWFVWJQzQ== +"@cosmjs/crypto@^0.29.0": + version "0.29.0" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.29.0.tgz#c914424a8b538f6624e505bc2015a71e3977c2fb" + integrity sha512-MPJoebRGh7AcZgbfR25ci7iV+XzJiKwVq4wL8n6M5P2QdrIv7DqqniyFXcBbn9dQjMLMHnOSgT9LRv+VXzUVCA== dependencies: - "@cosmjs/encoding" "0.28.13" - "@cosmjs/math" "0.28.13" - "@cosmjs/utils" "0.28.13" + "@cosmjs/encoding" "^0.29.0" + "@cosmjs/math" "^0.29.0" + "@cosmjs/utils" "^0.29.0" "@noble/hashes" "^1" bn.js "^5.2.0" elliptic "^6.5.3" libsodium-wrappers "^0.7.6" -"@cosmjs/encoding@0.28.13": - version "0.28.13" - resolved "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.28.13.tgz" - integrity sha512-jtXbAYtV77rLHxoIrjGFsvgGjeTKttuHRv6cvuy3toCZzY7JzTclKH5O2g36IIE4lXwD9xwuhGJ2aa6A3dhNkA== +"@cosmjs/encoding@^0.29.0": + version "0.29.0" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.29.0.tgz#75b1b41a2f31f71fcb0982cd1b210d6410739fd0" + integrity sha512-6HDBtid/YLbyXapY6PdMMIigAtGKyD1w0dUCLU1dOIkPf1q3y43kqoA7WnLkRw0g0/lZY1VGM2fX+2RWU0wxYg== dependencies: base64-js "^1.3.0" bech32 "^1.1.4" readonly-date "^1.0.0" -"@cosmjs/json-rpc@0.28.13": - version "0.28.13" - resolved "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.28.13.tgz" - integrity sha512-fInSvg7x9P6p+GWqet+TMhrMTM3OWWdLJOGS5w2ryubMjgpR1rLiAx77MdTNkArW+/6sUwku0sN4veM4ENQu6A== +"@cosmjs/json-rpc@^0.29.0": + version "0.29.0" + resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.29.0.tgz#481f282bcb3457c71f393342691e957a4fa56535" + integrity sha512-noCt91X+dSYjW1BYbp5jFaYaA/PWIQFXOgl4ZDW0ecGOAj8xh6/D/Vd8bDO97CQgJ1KVw0pyAqVhmrBOBUo1sA== dependencies: - "@cosmjs/stream" "0.28.13" + "@cosmjs/stream" "^0.29.0" xstream "^11.14.0" -"@cosmjs/math@0.28.13": - version "0.28.13" - resolved "https://registry.npmjs.org/@cosmjs/math/-/math-0.28.13.tgz" - integrity sha512-PDpL8W/kbyeWi0mQ2OruyqE8ZUAdxPs1xCbDX3WXJwy2oU+X2UTbkuweJHVpS9CIqmZulBoWQAmlf6t6zr1N/g== +"@cosmjs/math@^0.29.0": + version "0.29.0" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.29.0.tgz#2c34f96d94055fe82ca310bec7b2d8a9f1c507cb" + integrity sha512-ufRRmyDQtJUrH8r1V4N7Q6rTOk9ZX7XIXjJto7cfXP8kcxm7IJXKYk+r0EfDnNHFkxTidYvW/1YXeeNoy8xZYw== dependencies: bn.js "^5.2.0" -"@cosmjs/proto-signing@0.28.13": - version "0.28.13" - resolved "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.28.13.tgz" - integrity sha512-nSl/2ZLsUJYz3Ad0RY3ihZUgRHIow2OnYqKsESMu+3RA/jTi9bDYhiBu8mNMHI0xrEJry918B2CyI56pOUHdPQ== - dependencies: - "@cosmjs/amino" "0.28.13" - "@cosmjs/crypto" "0.28.13" - "@cosmjs/encoding" "0.28.13" - "@cosmjs/math" "0.28.13" - "@cosmjs/utils" "0.28.13" - cosmjs-types "^0.4.0" +"@cosmjs/proto-signing@^0.29.0": + version "0.29.0" + resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.0.tgz#4d9c10fc3a5c64b454bd2d9b407861fcffdfbbe0" + integrity sha512-zAdgDz5vRGAfJ5yyKYuTL7qg5UNUT7v4iV1/ZP8ZQn2fLh9QVxViAIovF4r/Y3EEI4JS5uYj/f8UeHMHQSu8hw== + dependencies: + "@cosmjs/amino" "^0.29.0" + "@cosmjs/crypto" "^0.29.0" + "@cosmjs/encoding" "^0.29.0" + "@cosmjs/math" "^0.29.0" + "@cosmjs/utils" "^0.29.0" + cosmjs-types "^0.5.0" long "^4.0.0" -"@cosmjs/socket@0.28.13": - version "0.28.13" - resolved "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.28.13.tgz" - integrity sha512-lavwGxQ5VdeltyhpFtwCRVfxeWjH5D5mmN7jgx9nuCf3XSFbTcOYxrk2pQ4usenu1Q1KZdL4Yl5RCNrJuHD9Ug== +"@cosmjs/socket@^0.29.0": + version "0.29.0" + resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.29.0.tgz#6f8f56799e69ead02f9ffe8925c782804635ac89" + integrity sha512-y7cOBp6YJ2Sn/DZne1eiJ6PVkgZlAi48d0Bz6hVuZ6CliutG0BzM/F3bSLxdw8m2fXNU+lYsi4uLPd0epf5Hig== dependencies: - "@cosmjs/stream" "0.28.13" + "@cosmjs/stream" "^0.29.0" isomorphic-ws "^4.0.1" ws "^7" xstream "^11.14.0" -"@cosmjs/stargate@0.28.13", "@cosmjs/stargate@^0.28.4": - version "0.28.13" - resolved "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.28.13.tgz" - integrity sha512-dVBMazDz8/eActHsRcZjDHHptOBMqvibj5CFgEtZBp22gP6ASzoAUXTlkSVk5FBf4sfuUHoff6st134/+PGMAg== +"@cosmjs/stargate@^0.29.0": + version "0.29.0" + resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.29.0.tgz#55263ed9d414f2c3073a451527576e4c3d6f04a6" + integrity sha512-BsV3iA3vMclMm/B1LYO0djBYCALr/UIvL6u9HGvM7QvpdtpQiAvskuS4PieVO/gtF9iCCBJLPqa0scwFIgvDyg== dependencies: "@confio/ics23" "^0.6.8" - "@cosmjs/amino" "0.28.13" - "@cosmjs/encoding" "0.28.13" - "@cosmjs/math" "0.28.13" - "@cosmjs/proto-signing" "0.28.13" - "@cosmjs/stream" "0.28.13" - "@cosmjs/tendermint-rpc" "0.28.13" - "@cosmjs/utils" "0.28.13" - cosmjs-types "^0.4.0" + "@cosmjs/amino" "^0.29.0" + "@cosmjs/encoding" "^0.29.0" + "@cosmjs/math" "^0.29.0" + "@cosmjs/proto-signing" "^0.29.0" + "@cosmjs/stream" "^0.29.0" + "@cosmjs/tendermint-rpc" "^0.29.0" + "@cosmjs/utils" "^0.29.0" + cosmjs-types "^0.5.0" long "^4.0.0" protobufjs "~6.11.3" xstream "^11.14.0" -"@cosmjs/stream@0.28.13": - version "0.28.13" - resolved "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.28.13.tgz" - integrity sha512-AnjtfwT8NwPPkd3lhZhjOlOzT0Kn9bgEu2IPOZjQ1nmG2bplsr6TJmnwn0dJxHT7UGtex17h6whKB5N4wU37Wg== +"@cosmjs/stream@^0.29.0": + version "0.29.0" + resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.29.0.tgz#df2d7ea23293170bc192e91c0fa3e9f8d993b7cc" + integrity sha512-KAJ9sNoXhF19wtkoJf3O2y4YXfklDZgmXhDotgAejLrw2ixoVfTodMHvnl6tpw3ZnmXKibTfUaNXWZD++sG6uQ== dependencies: xstream "^11.14.0" -"@cosmjs/tendermint-rpc@0.28.13", "@cosmjs/tendermint-rpc@^0.28.13": - version "0.28.13" - resolved "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.28.13.tgz" - integrity sha512-GB+ZmfuJIGQm0hsRtLYjeR3lOxF7Z6XyCBR0cX5AAYOZzSEBJjevPgUHD6tLn8zIhvzxaW3/VKnMB+WmlxdH4w== - dependencies: - "@cosmjs/crypto" "0.28.13" - "@cosmjs/encoding" "0.28.13" - "@cosmjs/json-rpc" "0.28.13" - "@cosmjs/math" "0.28.13" - "@cosmjs/socket" "0.28.13" - "@cosmjs/stream" "0.28.13" - "@cosmjs/utils" "0.28.13" +"@cosmjs/tendermint-rpc@^0.29.0": + version "0.29.0" + resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.0.tgz#db71e743d2ee8dde706c09bc92ac47cc6197f672" + integrity sha512-G+42oGh+tw8/KV0gLAGzNCTe/6mkf7VUE5noSTbsxbeliFR7Lt4i6H2aqvWzmlZFeRxunR7AsQr4wakvlVNWyg== + dependencies: + "@cosmjs/crypto" "^0.29.0" + "@cosmjs/encoding" "^0.29.0" + "@cosmjs/json-rpc" "^0.29.0" + "@cosmjs/math" "^0.29.0" + "@cosmjs/socket" "^0.29.0" + "@cosmjs/stream" "^0.29.0" + "@cosmjs/utils" "^0.29.0" axios "^0.21.2" readonly-date "^1.0.0" xstream "^11.14.0" -"@cosmjs/utils@0.28.13": - version "0.28.13" - resolved "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.13.tgz" - integrity sha512-dVeMBiyg+46x7XBZEfJK8yTihphbCFpjVYmLJVqmTsHfJwymQ65cpyW/C+V/LgWARGK8hWQ/aX9HM5Ao8QmMSg== +"@cosmjs/utils@^0.29.0": + version "0.29.0" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.0.tgz#0a61e6d608e9f6f89a278cc71f4e7cee01199657" + integrity sha512-NiJk3ISX+FU1cQcTTgmJcY84A8mV/p8L5CRewp/2jc/lUmo8j9lMGbX17U7NxVQ9RX5RmrwgdjYnBASzhRCVmA== -"@cosmwasm/ts-codegen@^0.16.3": - version "0.16.3" - resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.16.3.tgz#2eeec718657314601896b42625979ffbac6ab5e8" - integrity sha512-VKqIR8DCSWN7GtfgM5coEa+cGg0gbpSBTauAqr2tMipnfRZcJO5SVznt7z7XqAm7ctjiozh+oQifAFxnwp+8/w== +"@cosmwasm/ts-codegen@^0.16.4": + version "0.16.4" + resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.16.4.tgz#84367a886c631289a9ba0ff7696da62d37f1ac9a" + integrity sha512-50r6eU+BHAn7uRiJ9YAS8Wym7hq1d4j0VcAYpEeCl399ELdb/zS+8hDU6mczPQM0QYKflYffb40X78NuACji9w== dependencies: "@babel/core" "7.18.10" "@babel/generator" "7.18.12" @@ -1700,28 +1700,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@osmonauts/helpers@^0.4.3": - version "0.4.3" - resolved "https://registry.npmjs.org/@osmonauts/helpers/-/helpers-0.4.3.tgz" - integrity sha512-Dd6CXSKoAP4kLptE8Fx2vsEqc0Ai4kF07l4zPsfOYop9SH5ayOKkRTfAkrBjBr84FsMPyu5TEhJeqj1i//Q/jg== - dependencies: - "@babel/runtime" "^7.18.9" - "@cosmjs/amino" "0.28.13" - "@cosmjs/crypto" "0.28.13" - "@cosmjs/proto-signing" "0.28.13" - "@cosmjs/stargate" "0.28.13" - cosmjs-types "0.5.1" - long "^5.2.0" - protobufjs "^6.11.3" - -"@osmonauts/lcd@^0.4.0": - version "0.4.0" - resolved "https://registry.npmjs.org/@osmonauts/lcd/-/lcd-0.4.0.tgz" - integrity sha512-DjzU2ZqJ6DHkRpNfIfut1LIVYxdqo5IQf9sgYPiDF2hmTCxvYeAZ6uvCuiUiKJO0QdjBs3criv0/CH+Ytl5dsA== - dependencies: - "@babel/runtime" "^7.18.9" - axios "0.27.2" - "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz" @@ -1890,10 +1868,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.0.2": - version "29.0.2" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.0.2.tgz#05dcb2d78d2fcc444be89f95b7389f2c3601d336" - integrity sha512-TaklkwSEtvwJpleiKBHgEBySIQlcZ08gYP/s5wdtdLnjz9uxjnDd7U+Y0JWACebkqBc+jtbol2PEtEW0wQV2zQ== +"@types/jest@^29.0.3": + version "29.0.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.0.3.tgz#b61a5ed100850686b8d3c5e28e3a1926b2001b59" + integrity sha512-F6ukyCTwbfsEX5F2YmVYmM5TcTHy1q9P5rWlRbrk56KyMh3v9xRGUO3aa8+SkvMi0SHXtASJv1283enXimC0Og== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -2144,19 +2122,6 @@ ast-stringify@0.1.0: dependencies: "@babel/runtime" "^7.11.2" -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -axios@0.27.2: - version "0.27.2" - resolved "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== - dependencies: - follow-redirects "^1.14.9" - form-data "^4.0.0" - axios@^0.21.2: version "0.21.4" resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz" @@ -2515,13 +2480,6 @@ colors@^1.1.2: resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -2541,7 +2499,7 @@ core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.25.1: dependencies: browserslist "^4.21.3" -cosmjs-types@0.5.1, cosmjs-types@^0.5.0: +cosmjs-types@^0.5.0: version "0.5.1" resolved "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.5.1.tgz" integrity sha512-NcC58xUIVLlKdIimWWQAmSlmCjiMrJnuHf4i3LiD8PCextfHR0fT3V5/WlXZZreyMgdmh6ML1zPUfGTbbo3Z5g== @@ -2549,14 +2507,6 @@ cosmjs-types@0.5.1, cosmjs-types@^0.5.0: long "^4.0.0" protobufjs "~6.11.2" -cosmjs-types@^0.4.0: - version "0.4.1" - resolved "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.4.1.tgz" - integrity sha512-I7E/cHkIgoJzMNQdFF0YVqPlaTqrqKHrskuSTIqlEyxfB5Lf3WKCajSXVK2yHOfOFfSux/RxEdpMzw/eO4DIog== - dependencies: - long "^4.0.0" - protobufjs "~6.11.2" - cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" @@ -2609,11 +2559,6 @@ define-properties@^1.1.3, define-properties@^1.1.4: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" @@ -3014,20 +2959,11 @@ flatted@^3.1.0: resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -follow-redirects@^1.14.0, follow-redirects@^1.14.9: +follow-redirects@^1.14.0: version "1.15.1" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz" integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" @@ -4051,18 +3987,6 @@ micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz" @@ -4216,21 +4140,6 @@ os-tmpdir@~1.0.2: resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== -osmojs@^0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/osmojs/-/osmojs-0.15.0.tgz#e389b9fd22904b0979821cefc5ec94c291f3c2da" - integrity sha512-A8LxWplqsW6Kwmx1wxU6pK7EKrg+G/ganmEu/EHr4xLxQiDiGD3OfLhV0wAsJcHfaD2IUTWzlzA3RZURATWWLQ== - dependencies: - "@babel/runtime" "^7.19.0" - "@cosmjs/amino" "0.28.13" - "@cosmjs/proto-signing" "0.28.13" - "@cosmjs/stargate" "0.28.13" - "@cosmjs/tendermint-rpc" "^0.28.13" - "@osmonauts/helpers" "^0.4.3" - "@osmonauts/lcd" "^0.4.0" - long "^5.2.0" - protobufjs "^6.11.3" - p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" @@ -4372,7 +4281,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -protobufjs@^6.11.3, protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: +protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: version "6.11.3" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz" integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== From 112dea565d35815a755d6d29c734a794e0f74150 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 21 Sep 2022 15:20:25 +0200 Subject: [PATCH 058/218] Bug fix: internal counter for nft ids (#22) * Bug fix: internal counter for nft ids * New testnet deployment addrs --- contracts/account-nft/src/contract.rs | 2 + contracts/account-nft/src/execute.rs | 11 ++--- contracts/account-nft/src/state.rs | 1 + contracts/account-nft/tests/helpers.rs | 16 +++++++ contracts/account-nft/tests/test_mint.rs | 53 ++++++++++++++++++++--- scripts/deploy/addresses/osmo-test-4.json | 12 ++--- 6 files changed, 78 insertions(+), 17 deletions(-) diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index b03c03571..b92a0a489 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -10,6 +10,7 @@ use cw721_base::{ContractError, Cw721Contract, InstantiateMsg}; use crate::execute::{accept_ownership, mint, propose_new_owner}; use crate::msg::{ExecuteMsg, QueryMsg}; use crate::query::query_proposed_new_owner; +use crate::state::NEXT_ID; // Extending CW721 base contract pub type Parent<'a> = Cw721Contract<'a, Empty, Empty, Empty, Empty>; @@ -21,6 +22,7 @@ pub fn instantiate( info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { + NEXT_ID.save(deps.storage, &1)?; Parent::default().instantiate(deps, env, info, msg) } diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index f615e6a26..85dc7a5f0 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{DepsMut, Empty, Env, Event, MessageInfo, Response}; use cw721_base::{ContractError, MintMsg}; use crate::contract::Parent; -use crate::state::PENDING_OWNER; +use crate::state::{NEXT_ID, PENDING_OWNER}; pub fn mint( deps: DepsMut, @@ -10,15 +10,16 @@ pub fn mint( info: MessageInfo, user: &str, ) -> Result { - let parent = Parent::default(); - let num_tokens = parent.token_count(deps.storage)?; + let next_id = NEXT_ID.load(deps.storage)?; let mint_msg_override = MintMsg { - token_id: (num_tokens + 1).to_string(), + token_id: next_id.to_string(), owner: user.to_string(), token_uri: None, extension: Empty {}, }; - parent.mint(deps, env, info, mint_msg_override) + NEXT_ID.save(deps.storage, &(next_id + 1))?; + + Parent::default().mint(deps, env, info, mint_msg_override) } pub fn propose_new_owner( diff --git a/contracts/account-nft/src/state.rs b/contracts/account-nft/src/state.rs index 326b460e2..cb1d0877c 100644 --- a/contracts/account-nft/src/state.rs +++ b/contracts/account-nft/src/state.rs @@ -2,3 +2,4 @@ use cosmwasm_std::Addr; use cw_storage_plus::Item; pub const PENDING_OWNER: Item = Item::new("pending_owner"); +pub const NEXT_ID: Item = Item::new("next_id"); diff --git a/contracts/account-nft/tests/helpers.rs b/contracts/account-nft/tests/helpers.rs index 6c8273b87..e18dfecb6 100644 --- a/contracts/account-nft/tests/helpers.rs +++ b/contracts/account-nft/tests/helpers.rs @@ -40,3 +40,19 @@ pub fn mint_action( &[], ) } + +pub fn burn_action( + app: &mut BasicApp, + sender: &Addr, + contract_addr: &Addr, + token_id: &str, +) -> AnyResult { + app.execute_contract( + sender.clone(), + contract_addr.clone(), + &ExtendedExecuteMsg::Burn { + token_id: token_id.to_string(), + }, + &[], + ) +} diff --git a/contracts/account-nft/tests/test_mint.rs b/contracts/account-nft/tests/test_mint.rs index 4b38f7331..76357ed78 100644 --- a/contracts/account-nft/tests/test_mint.rs +++ b/contracts/account-nft/tests/test_mint.rs @@ -2,12 +2,13 @@ use std::fmt::Error; use cosmwasm_std::{Addr, Empty}; use cw721::OwnerOfResponse; -use cw721_base::QueryMsg; +use cw721_base::ContractError::Unauthorized; +use cw721_base::{ContractError, QueryMsg}; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; use account_nft::msg::ExecuteMsg as ExtendedExecuteMsg; -use crate::helpers::{instantiate_mock_nft_contract, mint_action}; +use crate::helpers::{burn_action, instantiate_mock_nft_contract, mint_action}; pub mod helpers; @@ -37,16 +38,56 @@ fn test_id_incrementer() { } #[test] -fn test_only_owner_can_mint() { +fn test_id_incrementer_works_despite_burns() { + let mut app = App::default(); + let owner = Addr::unchecked("owner"); + let contract_addr = instantiate_mock_nft_contract(&mut app, &owner); + + let user = Addr::unchecked("user"); + let res = mint_action(&mut app, &owner, &contract_addr, &user).unwrap(); + let token_id_1 = get_token_id(res); + assert_eq!(token_id_1, "1"); + let res = mint_action(&mut app, &owner, &contract_addr, &user).unwrap(); + let token_id_2 = get_token_id(res); + assert_eq!(token_id_2, "2"); + + burn_action(&mut app, &user, &contract_addr, &token_id_1).unwrap(); + burn_action(&mut app, &user, &contract_addr, &token_id_2).unwrap(); + + let res = mint_action(&mut app, &owner, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + assert_eq!(token_id, "3"); + assert_owner_is_correct(&mut app, &contract_addr, &user, &token_id); +} + +#[test] +fn test_only_contract_owner_can_mint() { let mut app = App::default(); let owner = Addr::unchecked("owner"); let contract_addr = instantiate_mock_nft_contract(&mut app, &owner); let bad_guy = Addr::unchecked("bad_guy"); let res = mint_action(&mut app, &bad_guy, &contract_addr, &bad_guy); - if res.is_ok() { - panic!("Unauthorized access to minting function"); - } + let err: ContractError = res.unwrap_err().downcast().unwrap(); + assert_eq!(err, Unauthorized {}) +} + +#[test] +fn test_only_token_owner_can_burn() { + let mut app = App::default(); + let owner = Addr::unchecked("owner"); + let contract_addr = instantiate_mock_nft_contract(&mut app, &owner); + + let user = Addr::unchecked("user"); + let res = mint_action(&mut app, &owner, &contract_addr, &user).unwrap(); + let token_id = get_token_id(res); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = burn_action(&mut app, &bad_guy, &contract_addr, &token_id); + let err: ContractError = res.unwrap_err().downcast().unwrap(); + assert_eq!(err, Unauthorized {}); + + burn_action(&mut app, &user, &contract_addr, &token_id).unwrap(); } #[test] diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index a50d632b8..28bc780fe 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,8 +1,8 @@ { - "accountNft": "osmo1cs0f9yv3smthukxdag6k3z06j2vhar26l63arkxuupec6lj0e7lst9els8", - "mockRedBank": "osmo1fswlsmvw7du33gqgnp9jmthlcxfzmu66ph8wlf8xuw4lz8gtt6fqmx6tt3", - "mockOracle": "osmo1kgrrj45k4udlw7d4flaamsrq0633q4pa5rlyueffua0kcx2llsmsddrs9d", - "mockVault": "osmo1hssy7afj5ye3zt9p67wh7py2lmkyx2vy3jxpc7p6fpqtqkgyvn7sltg6sn", - "swapper": "osmo10dpyf07zpn769e9e0kfkgqllw6yc3wvvpl5efmg5lpc6l7pkl9xs0g2zem", - "creditManager": "osmo1wla04nzxlugq7v0k6fkez6625uctt3tpwfxzgh0lavgpslw4supsm4qw38" + "accountNft": "osmo16v3mvsdnkh4c6ykc885n3x5ay9e36akdzxcl2g93698rqw007xxqesld8w", + "mockRedBank": "osmo1xrnx0q3x7kwzss53fry0dwwsc7pff6aq628l6n0rmvegkalp4y7qzl7j7z", + "mockOracle": "osmo1r9u2tfq8n5xpn2g0fq8ha0rj0cyp2fzr5w9jvcqwt3r8lxdfm6yszmtza5", + "mockVault": "osmo1gg4rpug7vwrnq0ask0k7nmw23z6wl8c8fr7jmup9pdpaal9uc5nqq7lyrm", + "swapper": "osmo1ak4x8k2h7s6pq5dnlncmgsmx2nqcaplpfxlmklx2ln7qn6dtny8q70apjv", + "creditManager": "osmo1963xgmt8agyc6q4k2vhf980kffq6ukkj9mgtwdxxnpj3dak2akdq20z9dw" } From 08c67167d6d31314392417fea7882d02ff67c0fb Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 28 Sep 2022 07:20:17 -0500 Subject: [PATCH 059/218] Oracle vault adapter contract (#23) * Oracle vault adapter contract * review updates * Build scripts update --- .github/workflows/main.yml | 2 +- Cargo.lock | 35 +- Cargo.toml | 7 +- Makefile.toml | 2 +- contracts/account-nft/Cargo.toml | 2 - contracts/account-nft/src/msg/execute.rs | 6 +- contracts/credit-manager/Cargo.toml | 2 - contracts/credit-manager/src/contract.rs | 8 +- contracts/credit-manager/src/execute.rs | 6 +- contracts/credit-manager/src/health.rs | 21 +- contracts/credit-manager/src/instantiate.rs | 2 +- contracts/credit-manager/src/liquidate.rs | 10 +- contracts/credit-manager/src/query.rs | 100 ++--- contracts/credit-manager/src/repay.rs | 1 + contracts/credit-manager/src/state.rs | 4 +- contracts/credit-manager/src/utils.rs | 28 +- contracts/credit-manager/src/vault/deposit.rs | 10 +- contracts/credit-manager/src/vault/mod.rs | 1 + contracts/credit-manager/src/vault/utils.rs | 27 +- .../credit-manager/src/vault/withdraw.rs | 13 +- .../credit-manager/tests/helpers/builders.rs | 4 +- .../credit-manager/tests/helpers/mock_env.rs | 10 +- .../credit-manager/tests/helpers/types.rs | 9 +- .../credit-manager/tests/helpers/utils.rs | 4 +- contracts/credit-manager/tests/test_borrow.rs | 70 +--- .../tests/test_coin_balances.rs | 6 +- .../credit-manager/tests/test_deposit.rs | 38 +- .../credit-manager/tests/test_dispatch.rs | 4 +- .../test_enumerate_vault_coin_balances.rs | 10 +- .../tests/test_enumerate_vault_positions.rs | 34 +- contracts/credit-manager/tests/test_health.rs | 76 ++-- .../tests/test_liquidate_coin.rs | 177 ++++++--- contracts/credit-manager/tests/test_repay.rs | 23 +- contracts/credit-manager/tests/test_swap.rs | 2 +- .../tests/test_vault_deposit.rs | 30 +- .../tests/test_vault_withdraw.rs | 33 +- .../credit-manager/tests/test_withdraw.rs | 22 +- contracts/mars-oracle-adapter/Cargo.toml | 30 ++ .../mars-oracle-adapter/examples/schema.rs | 10 + contracts/mars-oracle-adapter/src/contract.rs | 201 ++++++++++ contracts/mars-oracle-adapter/src/error.rs | 27 ++ contracts/mars-oracle-adapter/src/lib.rs | 4 + contracts/mars-oracle-adapter/src/msg.rs | 67 ++++ contracts/mars-oracle-adapter/src/state.rs | 11 + .../mars-oracle-adapter/tests/helpers.rs | 157 ++++++++ .../tests/test_query_price.rs | 62 +++ .../tests/test_update_config.rs | 147 +++++++ contracts/mock-oracle/Cargo.toml | 2 - contracts/mock-oracle/src/msg.rs | 9 +- contracts/mock-red-bank/Cargo.toml | 2 - contracts/mock-red-bank/src/msg.rs | 11 +- contracts/mock-vault/Cargo.toml | 2 - contracts/mock-vault/src/contract.rs | 7 +- contracts/mock-vault/src/msg.rs | 5 +- contracts/mock-vault/src/query.rs | 4 + contracts/swapper/mock/Cargo.toml | 2 - contracts/swapper/mock/src/lib.rs | 2 - contracts/swapper/osmosis/Cargo.toml | 2 +- contracts/swapper/osmosis/src/route.rs | 10 +- contracts/swapper/osmosis/tests/test_swap.rs | 3 +- packages/rover/Cargo.toml | 2 - packages/rover/src/adapters/oracle.rs | 15 +- packages/rover/src/adapters/red_bank.rs | 5 +- packages/rover/src/adapters/swap/msgs.rs | 11 +- packages/rover/src/adapters/swap/swapper.rs | 5 +- packages/rover/src/adapters/vault.rs | 81 ++-- packages/rover/src/coins.rs | 13 +- packages/rover/src/extensions/mod.rs | 3 - packages/rover/src/lib.rs | 2 +- packages/rover/src/msg/execute.rs | 14 +- packages/rover/src/msg/instantiate.rs | 8 +- packages/rover/src/msg/query.rs | 84 ++-- packages/rover/src/msg/vault.rs | 14 +- packages/rover/src/traits.rs | 39 ++ schemas/account-nft/account-nft.json | 30 +- schemas/credit-manager/credit-manager.json | 320 ++++++++++----- .../mars-oracle-adapter.json | 376 ++++++++++++++++++ schemas/mock-oracle/mock-oracle.json | 7 +- schemas/mock-red-bank/mock-red-bank.json | 11 +- schemas/mock-vault/mock-vault.json | 35 +- schemas/swapper-base/swapper-base.json | 23 +- scripts/deploy/addresses/osmo-test-4.json | 11 +- scripts/deploy/base/deployer.ts | 50 ++- scripts/deploy/base/index.ts | 6 +- scripts/deploy/base/rover.ts | 17 +- scripts/deploy/osmosis/config.ts | 14 +- scripts/package.json | 12 +- scripts/types/config.ts | 10 +- .../account-nft/AccountNft.client.ts | 2 +- .../account-nft/AccountNft.react-query.ts | 2 +- .../generated/account-nft/AccountNft.types.ts | 15 +- scripts/types/generated/account-nft/bundle.ts | 2 +- .../credit-manager/CreditManager.client.ts | 20 +- .../CreditManager.react-query.ts | 22 +- .../credit-manager/CreditManager.types.ts | 86 +--- .../types/generated/credit-manager/bundle.ts | 2 +- .../MarsOracleAdapter.client.ts | 138 +++++++ .../MarsOracleAdapter.react-query.ts | 165 ++++++++ .../MarsOracleAdapter.types.ts | 62 +++ .../generated/mars-oracle-adapter/bundle.ts | 13 + .../mock-oracle/MockOracle.client.ts | 2 +- .../mock-oracle/MockOracle.react-query.ts | 2 +- .../generated/mock-oracle/MockOracle.types.ts | 4 +- scripts/types/generated/mock-oracle/bundle.ts | 10 +- .../mock-red-bank/MockRedBank.client.ts | 2 +- .../mock-red-bank/MockRedBank.react-query.ts | 2 +- .../mock-red-bank/MockRedBank.types.ts | 7 +- .../types/generated/mock-red-bank/bundle.ts | 10 +- .../generated/mock-vault/MockVault.client.ts | 15 +- .../mock-vault/MockVault.react-query.ts | 26 +- .../generated/mock-vault/MockVault.types.ts | 21 +- scripts/types/generated/mock-vault/bundle.ts | 10 +- .../swapper-base/SwapperBase.client.ts | 2 +- .../swapper-base/SwapperBase.react-query.ts | 2 +- .../swapper-base/SwapperBase.types.ts | 10 +- .../types/generated/swapper-base/bundle.ts | 10 +- scripts/types/storageItems.ts | 4 +- scripts/yarn.lock | 182 +++++---- 118 files changed, 2614 insertions(+), 1070 deletions(-) create mode 100644 contracts/mars-oracle-adapter/Cargo.toml create mode 100644 contracts/mars-oracle-adapter/examples/schema.rs create mode 100644 contracts/mars-oracle-adapter/src/contract.rs create mode 100644 contracts/mars-oracle-adapter/src/error.rs create mode 100644 contracts/mars-oracle-adapter/src/lib.rs create mode 100644 contracts/mars-oracle-adapter/src/msg.rs create mode 100644 contracts/mars-oracle-adapter/src/state.rs create mode 100644 contracts/mars-oracle-adapter/tests/helpers.rs create mode 100644 contracts/mars-oracle-adapter/tests/test_query_price.rs create mode 100644 contracts/mars-oracle-adapter/tests/test_update_config.rs delete mode 100644 packages/rover/src/extensions/mod.rs create mode 100644 packages/rover/src/traits.rs create mode 100644 schemas/mars-oracle-adapter/mars-oracle-adapter.json create mode 100644 scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts create mode 100644 scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts create mode 100644 scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts create mode 100644 scripts/types/generated/mars-oracle-adapter/bundle.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d41615de1..eff757f83 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -103,7 +103,7 @@ jobs: - name: Install cargo make uses: davidB/rust-cargo-make@v1 - - name: Run tests + - name: Run formatter working-directory: rover run: cargo make fmt diff --git a/Cargo.lock b/Cargo.lock index 1826b554c..5f0f98b46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,8 +13,6 @@ dependencies = [ "cw-storage-plus 0.15.0", "cw721", "cw721-base", - "schemars", - "serde", ] [[package]] @@ -185,8 +183,6 @@ dependencies = [ "mock-red-bank", "mock-vault", "rover", - "schemars", - "serde", "swapper-mock", ] @@ -576,13 +572,28 @@ dependencies = [ "mars-outpost", ] +[[package]] +name = "mars-oracle-adapter" +version = "1.0.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test 0.15.0", + "cw-storage-plus 0.15.0", + "mars-outpost", + "mock-oracle", + "mock-vault", + "rover", + "thiserror", +] + [[package]] name = "mars-outpost" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", - "schemars", - "serde", "thiserror", ] @@ -594,8 +605,6 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 0.15.0", "mars-outpost", - "schemars", - "serde", ] [[package]] @@ -606,8 +615,6 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 0.15.0", "mars-outpost", - "schemars", - "serde", ] [[package]] @@ -618,8 +625,6 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 0.15.0", "rover", - "schemars", - "serde", "thiserror", ] @@ -748,8 +753,6 @@ dependencies = [ "mars-outpost", "mock-oracle", "mock-red-bank", - "schemars", - "serde", "thiserror", ] @@ -932,8 +935,6 @@ dependencies = [ "cw-multi-test 0.15.0", "cw-storage-plus 0.15.0", "rover", - "schemars", - "serde", "swapper-base", "thiserror", ] @@ -943,6 +944,7 @@ name = "swapper-osmosis" version = "1.0.0" dependencies = [ "anyhow", + "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 0.13.4", "cw-storage-plus 0.15.0", @@ -950,7 +952,6 @@ dependencies = [ "osmo-bindings-test", "rover", "schemars", - "serde", "swapper-base", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 0c13366c8..b67fc9903 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,11 +2,14 @@ members = [ "contracts/account-nft", "contracts/credit-manager", + "contracts/swapper/*", + "contracts/mars-oracle-adapter", + "packages/*", + +# Mock contracts "contracts/mock-oracle", "contracts/mock-red-bank", "contracts/mock-vault", - "contracts/swapper/*", - "packages/*", ] [profile.release.package.rover] diff --git a/Makefile.toml b/Makefile.toml index a91eb16c5..7a6f5fd7d 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -52,7 +52,7 @@ script = """ rm -rf schemas mkdir schemas - array=( credit-manager account-nft mock-red-bank mock-vault mock-oracle swapper-base ) + array=( credit-manager account-nft swapper-base mars-oracle-adapter mock-red-bank mock-vault mock-oracle ) for i in "${array[@]}" do : diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index b77e335dc..daa7b94b0 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -19,8 +19,6 @@ cw721 = "0.15" cw721-base = { version = "0.15", features = ["library"] } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" -schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } [dev-dependencies] anyhow = "1" diff --git a/contracts/account-nft/src/msg/execute.rs b/contracts/account-nft/src/msg/execute.rs index 9dff120fa..c95886d27 100644 --- a/contracts/account-nft/src/msg/execute.rs +++ b/contracts/account-nft/src/msg/execute.rs @@ -1,13 +1,11 @@ +use cosmwasm_schema::cw_serde; use std::convert::TryInto; use cosmwasm_std::{Binary, Empty, StdError}; use cw721::Expiration; use cw721_base::{ContractError, ExecuteMsg as ParentExecuteMsg}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { //-------------------------------------------------------------------------------------------------- // Extended and overridden messages diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 8eeb4a24f..ceecc9667 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -27,8 +27,6 @@ cw2 = "0.15" cw721 = "0.15" cw721-base = { version = "0.15", features = ["library"] } cw-storage-plus = "0.15" -schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } [dev-dependencies] swapper-mock = { version = "1.0", path = "../../contracts/swapper/mock", features = ["library"] } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index d10c03f62..6e5ae08b8 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -11,7 +11,7 @@ use crate::instantiate::store_config; use crate::query::{ query_all_coin_balances, query_all_debt_shares, query_all_total_debt_shares, query_all_total_vault_coin_balances, query_all_vault_positions, query_allowed_coins, - query_allowed_vaults, query_config, query_position_with_value, query_total_debt_shares, + query_allowed_vaults, query_config, query_positions, query_total_debt_shares, query_total_vault_coin_balance, }; @@ -27,7 +27,7 @@ pub fn instantiate( ) -> ContractResult { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; store_config(deps, &msg)?; - Ok(Response::new().add_attribute("method", "instantiate")) + Ok(Response::default()) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -58,9 +58,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { QueryMsg::AllowedCoins { start_after, limit } => { to_binary(&query_allowed_coins(deps, start_after, limit)?) } - QueryMsg::Positions { account_id } => { - to_binary(&query_position_with_value(deps, &env, &account_id)?) - } + QueryMsg::Positions { account_id } => to_binary(&query_positions(deps, &env, &account_id)?), QueryMsg::Health { account_id } => { to_binary::(&Into::into(compute_health(deps, &env, &account_id)?)) } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 8edf7b119..2b5409f7f 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -7,7 +7,6 @@ use cw721_base::QueryMsg; use crate::borrow::borrow; use crate::deposit::deposit; use crate::health::assert_below_max_ltv; -use crate::liquidate::{assert_health_factor_improved, liquidate_coin}; use crate::repay::repay; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, @@ -15,15 +14,16 @@ use crate::state::{ }; use crate::vault::{deposit_into_vault, update_vault_coin_balance, withdraw_from_vault}; +use crate::liquidate::{assert_health_factor_improved, liquidate_coin}; use crate::swap::swap_exact_in; use crate::update_coin_balances::update_coin_balances; use crate::withdraw::withdraw; use account_nft::msg::ExecuteMsg as NftExecuteMsg; use rover::coins::Coins; use rover::error::{ContractError, ContractResult}; -use rover::extensions::Stringify; use rover::msg::execute::{Action, CallbackMsg}; use rover::msg::instantiate::ConfigUpdates; +use rover::traits::Stringify; pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; @@ -95,7 +95,7 @@ pub fn update_config( if let Some(vaults) = new_config.allowed_vaults { vaults.iter().try_for_each(|unchecked| { let vault = unchecked.check(deps.api)?; - ALLOWED_VAULTS.save(deps.storage, vault.address(), &Empty {}) + ALLOWED_VAULTS.save(deps.storage, &vault.address, &Empty {}) })?; response = response .add_attribute("key", "allowed_vaults") diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index de6f51368..5ff69e940 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -2,18 +2,19 @@ use cosmwasm_std::{Decimal, Deps, Env, Event, Response}; use mars_health::health::Health; use rover::error::{ContractError, ContractResult}; +use rover::traits::Coins; -use crate::query::query_position; +use crate::query::query_positions; use crate::state::{ORACLE, RED_BANK}; -use crate::utils::debt_shares_to_amount; +use crate::vault::simulate_withdraw; pub fn compute_health(deps: Deps, env: &Env, account_id: &str) -> ContractResult { - let res = query_position(deps, account_id)?; - let debt_amounts = res - .debt - .iter() - .map(|item| debt_shares_to_amount(deps, &env.contract.address, &item.denom, item.shares)) - .collect::>>()?; + let res = query_positions(deps, env, account_id)?; + let coins_if_withdrawn = simulate_withdraw(&deps, &res.vaults)?; + + let mut collateral = Vec::with_capacity(res.coins.len() + coins_if_withdrawn.len()); + collateral.extend(res.coins); + collateral.extend(coins_if_withdrawn); let oracle = ORACLE.load(deps.storage)?; let red_bank = RED_BANK.load(deps.storage)?; @@ -21,8 +22,8 @@ pub fn compute_health(deps: Deps, env: &Env, account_id: &str) -> ContractResult &deps.querier, oracle.address(), red_bank.address(), - &res.coins, - debt_amounts.as_slice(), + &collateral, + &res.debts.to_coins(), )?; Ok(health) diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 634a7ee4c..037b26977 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -18,7 +18,7 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { msg.allowed_vaults.iter().try_for_each(|unchecked| { let vault = unchecked.check(deps.api)?; - ALLOWED_VAULTS.save(deps.storage, vault.address(), &Empty {}) + ALLOWED_VAULTS.save(deps.storage, &vault.address, &Empty {}) })?; msg.allowed_coins diff --git a/contracts/credit-manager/src/liquidate.rs b/contracts/credit-manager/src/liquidate.rs index 06f8c62d0..fce670ba4 100644 --- a/contracts/credit-manager/src/liquidate.rs +++ b/contracts/credit-manager/src/liquidate.rs @@ -5,11 +5,12 @@ use mars_health::health::Health; use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; +use rover::traits::{IntoDecimal, IntoUint128}; use crate::health::{compute_health, val_or_na}; use crate::repay::current_debt_for_denom; use crate::state::{COIN_BALANCES, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE}; -use crate::utils::{decrement_coin_balance, increment_coin_balance, IntoUint128}; +use crate::utils::{decrement_coin_balance, increment_coin_balance}; pub fn liquidate_coin( deps: DepsMut, @@ -99,8 +100,9 @@ fn calculate_liquidation_request( .load(deps.storage, (liquidatee_account_id, request_coin)) .map_err(|_| ContractError::CoinNotAvailable(request_coin.to_string()))?; let request_res = oracle.query_price(&deps.querier, request_coin)?; - let balance_dec = Decimal::from_atomics(liquidatee_balance, 0)?; - let max_request_value = request_res.price.checked_mul(balance_dec)?; + let max_request_value = request_res + .price + .checked_mul(liquidatee_balance.to_dec()?)?; let liq_bonus_rate = MAX_LIQUIDATION_BONUS.load(deps.storage)?; let request_coin_adjusted_max_debt = max_request_value .div(liq_bonus_rate.add(Decimal::one())) @@ -119,7 +121,7 @@ fn calculate_liquidation_request( // Calculate exact request coin amount to give to liquidator // FORMULA: request amount = (1 + liquidation bonus %) * debt value / request coin price - let debt_amount_dec = Decimal::from_atomics(final_debt_to_repay, 0)?; + let debt_amount_dec = final_debt_to_repay.to_dec()?; let request_amount = liq_bonus_rate .add(Decimal::one()) .checked_mul(debt_res.price.checked_mul(debt_amount_dec)?)? diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 4c5b37d55..fff39ce9c 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -4,16 +4,15 @@ use cw_storage_plus::Bound; use rover::adapters::{Vault, VaultBase, VaultPosition, VaultUnchecked}; use rover::error::ContractResult; use rover::msg::query::{ - CoinBalanceResponseItem, ConfigResponse, DebtShares, DebtSharesValue, Positions, - PositionsWithValueResponse, SharesResponseItem, VaultPositionResponseItem, - VaultPositionWithAddr, VaultWithBalance, + CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, Positions, SharesResponseItem, + VaultPositionResponseItem, VaultWithBalance, }; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_POSITIONS, }; -use crate::utils::{coin_value, debt_shares_to_amount}; +use crate::utils::debt_shares_to_amount; const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; @@ -32,53 +31,12 @@ pub fn query_config(deps: Deps) -> StdResult { }) } -pub fn query_position(deps: Deps, account_id: &str) -> ContractResult { +pub fn query_positions(deps: Deps, env: &Env, account_id: &str) -> ContractResult { Ok(Positions { account_id: account_id.to_string(), coins: query_coin_balances(deps, account_id)?, - debt: query_debt_shares(deps, account_id)?, - vault_positions: get_vault_positions(deps, account_id)?, - }) -} - -pub fn query_position_with_value( - deps: Deps, - env: &Env, - account_id: &str, -) -> ContractResult { - let Positions { - account_id, - coins, - debt, - vault_positions, - } = query_position(deps, account_id)?; - - let coin_balances_value = coins - .iter() - .map(|coin| coin_value(&deps, coin)) - .collect::>>()?; - - let debt_with_values = debt - .iter() - .map(|item| { - let coin = - debt_shares_to_amount(deps, &env.contract.address, &item.denom, item.shares)?; - let cv = coin_value(&deps, &coin)?; - Ok(DebtSharesValue { - denom: cv.denom, - shares: item.shares, - amount: cv.amount, - price: cv.price, - value: cv.value, - }) - }) - .collect::>>()?; - - Ok(PositionsWithValueResponse { - account_id, - coins: coin_balances_value, - debt: debt_with_values, - vault_positions, // TODO: add vault values here + debts: query_debt_amounts(deps, env, account_id)?, + vaults: get_vault_positions(deps, account_id)?, }) } @@ -105,13 +63,18 @@ pub fn query_all_coin_balances( .collect()) } -fn query_debt_shares(deps: Deps, account_id: &str) -> ContractResult> { +fn query_debt_amounts(deps: Deps, env: &Env, account_id: &str) -> ContractResult> { DEBT_SHARES .prefix(account_id) .range(deps.storage, None, None, Order::Ascending) .map(|res| { let (denom, shares) = res?; - Ok(DebtShares { denom, shares }) + let coin = debt_shares_to_amount(deps, &env.contract.address, &denom, shares)?; + Ok(DebtAmount { + denom, + shares, + amount: coin.amount, + }) }) .collect() } @@ -161,7 +124,7 @@ pub fn query_allowed_vaults( let start = match &start_after { Some(unchecked) => { vault = unchecked.check(deps.api)?; - Some(Bound::exclusive(vault.address())) + Some(Bound::exclusive(&vault.address)) } None => None, }; @@ -178,18 +141,15 @@ pub fn query_allowed_vaults( .collect() } -fn get_vault_positions(deps: Deps, account_id: &str) -> ContractResult> { +fn get_vault_positions(deps: Deps, account_id: &str) -> ContractResult> { VAULT_POSITIONS .prefix(account_id) .range(deps.storage, None, None, Order::Ascending) .map(|res| { - let (a, p) = res?; - Ok(VaultPositionWithAddr { - addr: a.to_string(), - position: VaultPosition { - unlocked: p.unlocked, - locked: p.locked, - }, + let (addr, position) = res?; + Ok(VaultPosition { + vault: VaultBase::new(addr), + state: position, }) }) .collect() @@ -215,13 +175,13 @@ pub fn query_all_vault_positions( .take(limit) .collect::>>()? .iter() - .map( - |((account_id, addr), vault_position)| VaultPositionResponseItem { - account_id: account_id.clone(), - addr: addr.to_string(), - vault_position: vault_position.clone(), + .map(|((account_id, addr), state)| VaultPositionResponseItem { + account_id: account_id.clone(), + position: VaultPosition { + vault: VaultBase::new(addr.clone()), + state: state.clone(), }, - ) + }) .collect()) } @@ -294,7 +254,7 @@ pub fn query_all_total_vault_coin_balances( let start = match &start_after { Some(unchecked) => { vault = unchecked.check(deps.api)?; - Some(Bound::exclusive(vault.address())) + Some(Bound::exclusive(&vault.address)) } None => None, }; @@ -306,13 +266,9 @@ pub fn query_all_total_vault_coin_balances( .take(limit) .map(|res| { let addr = res?; - let unchecked = VaultBase::new(addr.to_string()); - let vault = unchecked.check(deps.api)?; + let vault = VaultBase::new(addr); let balance = vault.query_balance(&deps.querier, rover_addr)?; - Ok(VaultWithBalance { - vault: vault.into(), - balance, - }) + Ok(VaultWithBalance { vault, balance }) }) .collect() } diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index 80a30b812..246ec8447 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -78,6 +78,7 @@ fn debt_amount_to_shares(deps: Deps, env: &Env, coin: &Coin) -> ContractResult (debt amount, debt shares) pub fn current_debt_for_denom( deps: Deps, env: &Env, diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 86895891e..fad799fef 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{Addr, Decimal, Empty, Uint128}; use cw_storage_plus::{Item, Map}; use rover::adapters::swap::Swapper; -use rover::adapters::{Oracle, RedBank, VaultPosition}; +use rover::adapters::{Oracle, RedBank, VaultPositionState}; // Contract config pub const OWNER: Item = Item::new("owner"); @@ -19,4 +19,4 @@ pub const SWAPPER: Item = Item::new("swapper"); pub const COIN_BALANCES: Map<(&str, &str), Uint128> = Map::new("coin_balance"); // Map<(AccountId, Denom), Amount> pub const DEBT_SHARES: Map<(&str, &str), Uint128> = Map::new("debt_shares"); // Map<(AccountId, Denom), Shares> pub const TOTAL_DEBT_SHARES: Map<&str, Uint128> = Map::new("total_debt_shares"); // Map -pub const VAULT_POSITIONS: Map<(&str, Addr), VaultPosition> = Map::new("vault_positions"); // Map<(AccountId, VaultAddr), VaultPosition> +pub const VAULT_POSITIONS: Map<(&str, Addr), VaultPositionState> = Map::new("vault_positions"); // Map<(AccountId, VaultAddr), VaultPositionState> diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 74051c273..b9548fd54 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -1,9 +1,8 @@ -use cosmwasm_std::{Addr, Coin, Decimal, Deps, Storage, Uint128}; +use cosmwasm_std::{Addr, Coin, Deps, Storage, Uint128}; use rover::error::{ContractError, ContractResult}; -use rover::msg::query::CoinValue; -use crate::state::{ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES}; +use crate::state::{ALLOWED_COINS, COIN_BALANCES, RED_BANK, TOTAL_DEBT_SHARES}; pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, denom: &str) -> ContractResult<()> { let is_whitelisted = ALLOWED_COINS.has(storage, denom); @@ -78,26 +77,3 @@ pub fn debt_shares_to_amount( amount, }) } - -pub fn coin_value(deps: &Deps, coin: &Coin) -> ContractResult { - let oracle = ORACLE.load(deps.storage)?; - let res = oracle.query_price(&deps.querier, &coin.denom)?; - let decimal_amount = Decimal::from_atomics(coin.amount, 0)?; - let value = res.price.checked_mul(decimal_amount)?; - Ok(CoinValue { - denom: coin.denom.clone(), - amount: coin.amount, - price: res.price, - value, - }) -} - -pub trait IntoUint128 { - fn uint128(&self) -> Uint128; -} - -impl IntoUint128 for Decimal { - fn uint128(&self) -> Uint128 { - *self * Uint128::new(1) - } -} diff --git a/contracts/credit-manager/src/vault/deposit.rs b/contracts/credit-manager/src/vault/deposit.rs index 59658bd6a..29154f089 100644 --- a/contracts/credit-manager/src/vault/deposit.rs +++ b/contracts/credit-manager/src/vault/deposit.rs @@ -2,11 +2,11 @@ use cosmwasm_std::{ to_binary, Addr, Coin, CosmosMsg, DepsMut, QuerierWrapper, Response, Uint128, WasmMsg, }; -use rover::adapters::{Vault, VaultPosition}; +use rover::adapters::{Vault, VaultPositionState}; use rover::error::{ContractError, ContractResult}; -use rover::extensions::Stringify; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg; +use rover::traits::Stringify; use crate::state::VAULT_POSITIONS; use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance}; @@ -66,15 +66,15 @@ pub fn update_vault_coin_balance( // Increment token's vault position VAULT_POSITIONS.update( deps.storage, - (account_id, vault.address().clone()), + (account_id, vault.address), |position_opt| -> ContractResult<_> { let p = position_opt.unwrap_or_default(); match vault_info.lockup { - None => Ok(VaultPosition { + None => Ok(VaultPositionState { unlocked: p.unlocked.checked_add(diff)?, locked: p.locked, }), - Some(_) => Ok(VaultPosition { + Some(_) => Ok(VaultPositionState { unlocked: p.unlocked, locked: p.locked.checked_add(diff)?, }), diff --git a/contracts/credit-manager/src/vault/mod.rs b/contracts/credit-manager/src/vault/mod.rs index 27605ee84..7eb1c36e5 100644 --- a/contracts/credit-manager/src/vault/mod.rs +++ b/contracts/credit-manager/src/vault/mod.rs @@ -1,4 +1,5 @@ pub use self::deposit::*; +pub use self::utils::*; pub use self::withdraw::*; mod deposit; diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 5446c1da3..1ed5d918e 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,14 +1,14 @@ -use cosmwasm_std::{Storage, Uint128}; +use cosmwasm_std::{Coin, Deps, Storage, Uint128}; -use rover::adapters::{Vault, VaultPosition}; +use rover::adapters::{Vault, VaultPosition, VaultPositionState}; use rover::error::{ContractError, ContractResult}; use crate::state::{ALLOWED_VAULTS, VAULT_POSITIONS}; pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> ContractResult<()> { - let is_whitelisted = ALLOWED_VAULTS.has(storage, vault.address()); + let is_whitelisted = ALLOWED_VAULTS.has(storage, &vault.address); if !is_whitelisted { - return Err(ContractError::NotWhitelisted(vault.address().to_string())); + return Err(ContractError::NotWhitelisted(vault.address.to_string())); } Ok(()) } @@ -19,8 +19,8 @@ pub fn decrement_vault_position( vault: &Vault, amount: Uint128, force: bool, -) -> ContractResult { - let path = VAULT_POSITIONS.key((account_id, vault.address().clone())); +) -> ContractResult { + let path = VAULT_POSITIONS.key((account_id, vault.address.clone())); let mut position = path.load(storage)?; // Force indicates that the vault is one with a required lockup that needs to be broken @@ -31,7 +31,7 @@ pub fn decrement_vault_position( position.unlocked = position.unlocked.checked_sub(amount)?; } - if position == VaultPosition::default() { + if position == VaultPositionState::default() { path.remove(storage); } else { path.save(storage, &position)?; @@ -39,3 +39,16 @@ pub fn decrement_vault_position( Ok(position) } + +/// Does a simulated withdraw from multiple vault positions to see what assets would be returned +pub fn simulate_withdraw(deps: &Deps, positions: &[VaultPosition]) -> ContractResult> { + let mut coins: Vec = vec![]; + for p in positions { + let total_vault_coins = p.state.total()?; + let coins_if_withdrawn = p + .vault + .query_preview_redeem(&deps.querier, total_vault_coins)?; + coins.extend(coins_if_withdrawn) + } + Ok(coins) +} diff --git a/contracts/credit-manager/src/vault/withdraw.rs b/contracts/credit-manager/src/vault/withdraw.rs index 2cd480810..8d489a97e 100644 --- a/contracts/credit-manager/src/vault/withdraw.rs +++ b/contracts/credit-manager/src/vault/withdraw.rs @@ -4,6 +4,7 @@ use rover::adapters::Vault; use rover::error::ContractResult; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg as RoverExecuteMsg; +use rover::traits::Denoms; use crate::update_coin_balances::query_balances; use crate::vault::utils::{assert_vault_is_whitelisted, decrement_vault_position}; @@ -25,13 +26,11 @@ pub fn withdraw_from_vault( // Updates coin balances for account after a vault withdraw has taken place let vault_info = vault.query_vault_info(&deps.querier)?; - let denoms = vault_info - .coins - .iter() - .map(|v| v.denom.as_str()) - .collect::>(); - let previous_balances = - query_balances(deps.as_ref(), &env.contract.address, denoms.as_slice())?; + let previous_balances = query_balances( + deps.as_ref(), + &env.contract.address, + &vault_info.coins.to_denoms(), + )?; let update_coin_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: env.contract.address.to_string(), funds: vec![], diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index 4ff5f2161..924130c66 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -1,5 +1,7 @@ use cosmwasm_std::Decimal; +use rover::traits::IntoDecimal; + use crate::helpers::{CoinInfo, VaultTestInfo}; pub fn build_mock_coin_infos(count: usize) -> Vec { @@ -9,7 +11,7 @@ pub fn build_mock_coin_infos(count: usize) -> Vec { denom: format!("coin_{}", i), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: Decimal::from_atomics(10u128, 0).unwrap(), + price: 10.to_dec().unwrap(), }) .collect() } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 16c9c033f..11e071316 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -24,8 +24,8 @@ use rover::adapters::{OracleBase, RedBankBase, Vault, VaultBase, VaultUnchecked} use rover::msg::execute::{Action, CallbackMsg}; use rover::msg::instantiate::ConfigUpdates; use rover::msg::query::{ - CoinBalanceResponseItem, ConfigResponse, DebtShares, HealthResponse, - PositionsWithValueResponse, SharesResponseItem, VaultPositionResponseItem, VaultWithBalance, + CoinBalanceResponseItem, ConfigResponse, DebtShares, HealthResponse, Positions, + SharesResponseItem, VaultPositionResponseItem, VaultWithBalance, }; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -34,7 +34,7 @@ use crate::helpers::{ mock_swapper_contract, mock_vault_contract, AccountToFund, CoinInfo, VaultTestInfo, }; -pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000u128); +pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000); pub struct MockEnv { pub app: BasicApp, @@ -182,7 +182,7 @@ impl MockEnv { // Queries //-------------------------------------------------------------------------------------------------- - pub fn query_position(&self, account_id: &str) -> PositionsWithValueResponse { + pub fn query_positions(&self, account_id: &str) -> Positions { self.app .wrap() .query_wasm_smart( @@ -330,7 +330,7 @@ impl MockEnv { vault .check(&MockApi::default()) .unwrap() - .query_redeem_preview(&self.app.wrap(), shares) + .query_preview_redeem(&self.app.wrap(), shares) .unwrap() } diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 7d9d82923..52357a307 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -1,14 +1,13 @@ +use cosmwasm_schema::cw_serde; use cosmwasm_std::{coin, Addr, Coin, Decimal}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[cw_serde] pub struct AccountToFund { pub addr: Addr, pub funds: Vec, } -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[cw_serde] pub struct CoinInfo { pub denom: String, pub price: Decimal, @@ -16,7 +15,7 @@ pub struct CoinInfo { pub liquidation_threshold: Decimal, } -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[cw_serde] pub struct VaultTestInfo { pub lp_token_denom: String, pub lockup: Option, diff --git a/contracts/credit-manager/tests/helpers/utils.rs b/contracts/credit-manager/tests/helpers/utils.rs index 1e3f5616b..36cef8c57 100644 --- a/contracts/credit-manager/tests/helpers/utils.rs +++ b/contracts/credit-manager/tests/helpers/utils.rs @@ -1,5 +1,5 @@ -use rover::msg::query::CoinValue; +use cosmwasm_std::Coin; -pub fn get_coin(denom: &str, coins: &[CoinValue]) -> CoinValue { +pub fn get_coin(denom: &str, coins: &[Coin]) -> Coin { coins.iter().find(|cv| cv.denom == denom).unwrap().clone() } diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index c6c0746ef..36944c390 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -1,6 +1,6 @@ use std::ops::{Mul, Sub}; -use cosmwasm_std::{coin, coins, Addr, Decimal, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Uint128}; use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use rover::error::ContractError; @@ -77,9 +77,9 @@ fn test_borrowing_zero_does_nothing() { assert_err(res, ContractError::NoAmount); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 0); - assert_eq!(position.debt.len(), 0); + assert_eq!(position.debts.len(), 0); } #[test] @@ -96,9 +96,9 @@ fn test_cannot_borrow_above_max_ltv() { .unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 0); - assert_eq!(position.debt.len(), 0); + assert_eq!(position.debts.len(), 0); let res = mock.update_credit_account( &account_id, @@ -133,9 +133,9 @@ fn test_success_when_new_debt_asset() { .unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 0); - assert_eq!(position.debt.len(), 0); + assert_eq!(position.debts.len(), 0); mock.update_credit_account( &account_id, &user, @@ -147,7 +147,7 @@ fn test_success_when_new_debt_asset() { ) .unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 1); let asset_res = position.coins.first().unwrap(); assert_eq!( @@ -155,25 +155,16 @@ fn test_success_when_new_debt_asset() { Uint128::new(342) // Deposit + Borrow ); assert_eq!(asset_res.denom, coin_info.denom); - assert_eq!(asset_res.price, coin_info.price); - assert_eq!( - asset_res.value, - coin_info.price * Decimal::from_atomics(342u128, 0).unwrap() - ); - let debt_shares_res = position.debt.first().unwrap(); - assert_eq!(position.debt.len(), 1); + let debt_shares_res = position.debts.first().unwrap(); + assert_eq!(position.debts.len(), 1); assert_eq!( debt_shares_res.shares, Uint128::new(42).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) ); assert_eq!(debt_shares_res.denom, coin_info.denom); - let debt_amount = Uint128::new(42u128) + Uint128::new(1); // simulated yield + let debt_amount = Uint128::new(42) + Uint128::new(1); // simulated yield assert_eq!(debt_shares_res.amount, debt_amount); - assert_eq!( - debt_shares_res.value, - coin_info.price * Decimal::from_atomics(debt_amount, 0).unwrap() - ); let coin = mock.query_balance(&mock.rover, &coin_info.denom); assert_eq!(coin.amount, Uint128::new(342)); @@ -237,16 +228,16 @@ fn test_debt_shares_with_debt_amount() { .unwrap(); let token_a_shares = Uint128::new(50).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); - let position = mock.query_position(&account_id_a); - let debt_position_a = position.debt.first().unwrap(); + let position = mock.query_positions(&account_id_a); + let debt_position_a = position.debts.first().unwrap(); assert_eq!(debt_position_a.shares, token_a_shares.clone()); assert_eq!(debt_position_a.denom, coin_info.denom); let token_b_shares = Uint128::new(50) .mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) .multiply_ratio(Uint128::new(50), interim_red_bank_debt.amount); - let position = mock.query_position(&account_id_b); - let debt_position_b = position.debt.first().unwrap(); + let position = mock.query_positions(&account_id_b); + let debt_position_b = position.debts.first().unwrap(); assert_eq!(debt_position_b.shares, token_b_shares.clone()); assert_eq!(debt_position_b.denom, coin_info.denom); @@ -256,35 +247,4 @@ fn test_debt_shares_with_debt_amount() { total.shares, debt_position_a.shares + debt_position_b.shares ); - - let red_bank_debt = mock.query_red_bank_debt(&coin_info.denom); - - let a_amount_owed = red_bank_debt - .amount - .multiply_ratio(debt_position_a.shares, total.shares); - assert_eq!( - debt_position_a.value, - coin_info.price * Decimal::from_atomics(a_amount_owed, 0).unwrap() - ); - - let b_amount_owed = red_bank_debt - .amount - .multiply_ratio(debt_position_b.shares, total.shares); - assert_eq!( - debt_position_b.value, - coin_info.price * Decimal::from_atomics(b_amount_owed, 0).unwrap() - ); - - // NOTE: There is an expected rounding error. This will not pass. - // let total_borrowed_plus_interest = Decimal::from_atomics(Uint128::new(102), 0).unwrap(); - // assert_eq!( - // total_borrowed_plus_interest * coin_info.price, - // debt_position_a.total_value + debt_position_b.total_value - // ) - // This test below asserts the rounding down that's happening - let total_owed = Decimal::from_atomics(a_amount_owed + b_amount_owed, 0).unwrap(); - assert_eq!( - total_owed * coin_info.price, - debt_position_a.value + debt_position_b.value - ) } diff --git a/contracts/credit-manager/tests/test_coin_balances.rs b/contracts/credit-manager/tests/test_coin_balances.rs index f94c80e5c..a7a17bc38 100644 --- a/contracts/credit-manager/tests/test_coin_balances.rs +++ b/contracts/credit-manager/tests/test_coin_balances.rs @@ -99,7 +99,7 @@ fn test_user_gets_rebalanced_down() { ) .unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); assert_eq!(position.coins.first().unwrap().amount.u128(), 100); @@ -144,7 +144,7 @@ fn test_user_gets_rebalanced_up() { ) .unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); assert_eq!(position.coins.first().unwrap().amount.u128(), 500); @@ -181,7 +181,7 @@ fn test_works_on_multiple() { ) .unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 2); let osmo = get_coin("uosmo", &position.coins); assert_eq!(osmo.amount.u128(), 143); diff --git a/contracts/credit-manager/tests/test_deposit.rs b/contracts/credit-manager/tests/test_deposit.rs index ff265702b..bd55cc9f4 100644 --- a/contracts/credit-manager/tests/test_deposit.rs +++ b/contracts/credit-manager/tests/test_deposit.rs @@ -1,11 +1,11 @@ -use cosmwasm_std::{coin, coins, Addr, Coin, Decimal, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Coin, Uint128}; use rover::coins::Coins; use rover::error::ContractError::{ ExtraFundsReceived, FundsMismatch, NotTokenOwner, NotWhitelisted, }; use rover::msg::execute::Action; -use rover::msg::query::PositionsWithValueResponse; +use rover::msg::query::Positions; use crate::helpers::{ assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv, @@ -47,7 +47,7 @@ fn test_deposit_nothing() { let user = Addr::unchecked("user"); let account_id = mock.create_credit_account(&user).unwrap(); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); mock.update_credit_account( @@ -58,7 +58,7 @@ fn test_deposit_nothing() { ) .unwrap(); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); } @@ -89,7 +89,7 @@ fn test_deposit_but_no_funds() { }, ); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); } @@ -149,7 +149,7 @@ fn test_can_only_deposit_allowed_assets() { assert_err(res, NotWhitelisted(not_allowed_coin.denom)); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); } @@ -182,7 +182,7 @@ fn test_extra_funds_received() { assert_err(res, ExtraFundsReceived(Coins::from(vec![extra_funds]))); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); } @@ -210,16 +210,11 @@ fn test_deposit_success() { ) .unwrap(); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); let assets_res = res.coins.first().unwrap(); assert_eq!(res.coins.len(), 1); assert_eq!(assets_res.amount, deposit_amount); assert_eq!(assets_res.denom, coin_info.denom); - assert_eq!(assets_res.price, coin_info.price); - assert_eq!( - assets_res.value, - coin_info.price * Decimal::from_atomics(deposit_amount, 0).unwrap() - ); let coin = mock.query_balance(&mock.rover, &coin_info.denom); assert_eq!(coin.amount, deposit_amount) @@ -261,12 +256,10 @@ fn test_multiple_deposit_actions() { ) .unwrap(); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 2); - let uosmo_value = Decimal::from_atomics(uosmo_amount, 0).unwrap() * uosmo_info.price; - assert_present(&res, &uosmo_info, uosmo_amount, uosmo_value); - let uatom_value = Decimal::from_atomics(uatom_amount, 0).unwrap() * uatom_info.price; - assert_present(&res, &uatom_info, uatom_amount, uatom_value); + assert_present(&res, &uosmo_info, uosmo_amount); + assert_present(&res, &uatom_info, uatom_amount); let coin = mock.query_balance(&mock.rover, &uosmo_info.denom); assert_eq!(coin.amount, uosmo_amount); @@ -275,14 +268,9 @@ fn test_multiple_deposit_actions() { assert_eq!(coin.amount, uatom_amount); } -fn assert_present( - res: &PositionsWithValueResponse, - coin: &CoinInfo, - amount: Uint128, - total_val: Decimal, -) { +fn assert_present(res: &Positions, coin: &CoinInfo, amount: Uint128) { res.coins .iter() - .find(|item| item.denom == coin.denom && item.amount == amount && item.value == total_val) + .find(|item| item.denom == coin.denom && item.amount == amount) .unwrap(); } diff --git a/contracts/credit-manager/tests/test_dispatch.rs b/contracts/credit-manager/tests/test_dispatch.rs index 1331e532a..373b6903e 100644 --- a/contracts/credit-manager/tests/test_dispatch.rs +++ b/contracts/credit-manager/tests/test_dispatch.rs @@ -33,13 +33,13 @@ fn test_nothing_happens_if_no_actions_are_passed() { let user = Addr::unchecked("user"); let account_id = mock.create_credit_account(&user).unwrap(); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); mock.update_credit_account(&account_id, &user, vec![], &[]) .unwrap(); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); } diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index 5e2f350d3..931ad6a03 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -1,4 +1,3 @@ -use cosmwasm_std::testing::MockApi; use cosmwasm_std::{coin, Addr}; use rover::msg::execute::Action; @@ -95,15 +94,15 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { let vaults_res_a = mock.query_all_total_vault_coin_balances(None, None); let vaults_res_b = mock.query_all_total_vault_coin_balances( - Some(vaults_res_a.last().unwrap().clone().vault), + Some(vaults_res_a.last().unwrap().clone().vault.into()), None, ); let vaults_res_c = mock.query_all_total_vault_coin_balances( - Some(vaults_res_b.last().unwrap().clone().vault), + Some(vaults_res_b.last().unwrap().clone().vault.into()), None, ); let vaults_res_d = mock.query_all_total_vault_coin_balances( - Some(vaults_res_c.last().unwrap().clone().vault), + Some(vaults_res_c.last().unwrap().clone().vault.into()), None, ); @@ -119,8 +118,7 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { .chain(vaults_res_b.iter().cloned()) .chain(vaults_res_c.iter().cloned()) .chain(vaults_res_d.iter().cloned()) - .map(|v| v.vault.check(&MockApi::default()).unwrap()) - .map(|v| v.query_vault_info(&mock.app.wrap()).unwrap()) + .map(|v| v.vault.query_vault_info(&mock.app.wrap()).unwrap()) .map(|info| info.token_denom) .collect::>(); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index 58796697d..b0145a775 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -1,8 +1,6 @@ -use cosmwasm_std::testing::MockApi; -use cosmwasm_std::{coin, Addr, Api}; +use cosmwasm_std::{coin, Addr}; use itertools::Itertools; -use rover::adapters::VaultBase; use rover::msg::execute::Action; use crate::helpers::{ @@ -97,14 +95,29 @@ fn test_pagination_on_all_vault_positions_query_works() { let vaults_res_a = mock.query_all_vault_positions(None, None); let item = vaults_res_a.last().unwrap(); - let vaults_res_b = mock - .query_all_vault_positions(Some((item.account_id.clone(), item.addr.clone())), Some(30)); + let vaults_res_b = mock.query_all_vault_positions( + Some(( + item.account_id.clone(), + item.position.vault.address.to_string(), + )), + Some(30), + ); let item = vaults_res_b.last().unwrap(); - let vaults_res_c = mock - .query_all_vault_positions(Some((item.account_id.clone(), item.addr.clone())), Some(30)); + let vaults_res_c = mock.query_all_vault_positions( + Some(( + item.account_id.clone(), + item.position.vault.address.to_string(), + )), + Some(30), + ); let item = vaults_res_c.last().unwrap(); - let vaults_res_d = - mock.query_all_vault_positions(Some((item.account_id.clone(), item.addr.clone())), None); + let vaults_res_d = mock.query_all_vault_positions( + Some(( + item.account_id.clone(), + item.position.vault.address.to_string(), + )), + None, + ); // Assert default is observed assert_eq!(vaults_res_a.len(), 10); @@ -118,8 +131,7 @@ fn test_pagination_on_all_vault_positions_query_works() { .chain(vaults_res_b.iter().cloned()) .chain(vaults_res_c.iter().cloned()) .chain(vaults_res_d.iter().cloned()) - .map(|v| VaultBase::new(MockApi::default().addr_validate(v.addr.as_str()).unwrap())) - .map(|v| v.query_vault_info(&mock.app.wrap()).unwrap()) + .map(|v| v.position.vault.query_vault_info(&mock.app.wrap()).unwrap()) .map(|info| info.token_denom) .collect::>(); diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index fea81fd1d..aa5dc30f9 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -6,7 +6,8 @@ use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use mock_oracle::msg::CoinPrice; use rover::error::ContractError; use rover::msg::execute::Action::{Borrow, Deposit}; -use rover::msg::query::DebtSharesValue; +use rover::msg::query::DebtAmount; +use rover::traits::IntoDecimal; use crate::helpers::{assert_err, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv}; @@ -41,12 +42,12 @@ fn test_only_assets_with_no_debts() { ) .unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 1); - assert_eq!(position.debt.len(), 0); + assert_eq!(position.debts.len(), 0); let health = mock.query_health(&account_id); - let assets_value = coin_info.price * Decimal::from_atomics(deposit_amount, 0).unwrap(); + let assets_value = coin_info.price * deposit_amount.to_dec().unwrap(); assert_eq!(health.total_collateral_value, assets_value); assert_eq!(health.total_debt_value, Decimal::zero()); assert_eq!(health.liquidation_health_factor, None); @@ -69,7 +70,7 @@ fn test_only_assets_with_no_debts() { fn test_terra_ragnarok() { let coin_info = CoinInfo { denom: "uluna".to_string(), - price: Decimal::from_atomics(100u128, 0).unwrap(), + price: 100.to_dec().unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), }; @@ -99,17 +100,15 @@ fn test_terra_ragnarok() { ) .unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 1); - assert_eq!(position.debt.len(), 1); + assert_eq!(position.debts.len(), 1); let health = mock.query_health(&account_id); - let assets_value = - coin_info.price * Decimal::from_atomics(deposit_amount + borrow_amount, 0).unwrap(); + let assets_value = coin_info.price * (deposit_amount + borrow_amount).to_dec().unwrap(); assert_eq!(health.total_collateral_value, assets_value); // Note: Simulated yield from mock_red_bank makes debt position more expensive - let debts_value = - coin_info.price * Decimal::from_atomics(borrow_amount.add(Uint128::new(1)), 0).unwrap(); + let debts_value = coin_info.price * borrow_amount.add(Uint128::new(1)).to_dec().unwrap(); assert_eq!(health.total_debt_value, debts_value); assert_eq!( health.liquidation_health_factor, @@ -127,9 +126,9 @@ fn test_terra_ragnarok() { price: Decimal::zero(), }); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 1); - assert_eq!(position.debt.len(), 1); + assert_eq!(position.debts.len(), 1); let health = mock.query_health(&account_id); assert_eq!(health.total_collateral_value, Decimal::zero()); @@ -174,10 +173,10 @@ fn test_debts_no_assets() { }, ); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.account_id, account_id); assert_eq!(position.coins.len(), 0); - assert_eq!(position.debt.len(), 0); + assert_eq!(position.debts.len(), 0); let health = mock.query_health(&account_id); assert_eq!(health.total_collateral_value, Decimal::zero()); @@ -226,10 +225,10 @@ fn test_cannot_borrow_more_than_healthy() { ) .unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.account_id, account_id); assert_eq!(position.coins.len(), 1); - assert_eq!(position.debt.len(), 1); + assert_eq!(position.debts.len(), 1); let health = mock.query_health(&account_id); let assets_value = Decimal::from_atomics(82789u128, 2).unwrap(); @@ -342,7 +341,7 @@ fn test_cannot_borrow_more_but_not_liquidatable() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(24u128, 0).unwrap(), + price: 24.to_dec().unwrap(), }); let health = mock.query_health(&account_id); @@ -362,7 +361,7 @@ fn test_cannot_borrow_more_but_not_liquidatable() { mock.price_change(CoinPrice { denom: uatom_info.denom, - price: Decimal::from_atomics(35u128, 0).unwrap(), + price: 35.to_dec().unwrap(), }); let health = mock.query_health(&account_id); @@ -415,14 +414,14 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { ) .unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.account_id, account_id); assert_eq!(position.coins.len(), 2); - assert_eq!(position.debt.len(), 1); + assert_eq!(position.debts.len(), 1); let health = mock.query_health(&account_id); - let deposit_amount_dec = Decimal::from_atomics(deposit_amount, 0).unwrap(); - let borrowed_amount_dec = Decimal::from_atomics(borrowed_amount, 0).unwrap(); + let deposit_amount_dec = deposit_amount.to_dec().unwrap(); + let borrowed_amount_dec = borrowed_amount.to_dec().unwrap(); assert_eq!( health.total_collateral_value, uosmo_info.price * deposit_amount_dec + uatom_info.price * borrowed_amount_dec @@ -531,10 +530,10 @@ fn test_debt_value() { ) .unwrap(); - let position_a = mock.query_position(&account_id_a); + let position_a = mock.query_positions(&account_id_a); assert_eq!(position_a.account_id, account_id_a); assert_eq!(position_a.coins.len(), 2); - assert_eq!(position_a.debt.len(), 2); + assert_eq!(position_a.debts.len(), 2); let health = mock.query_health(&account_id_a); assert!(!health.above_max_ltv); @@ -546,15 +545,15 @@ fn test_debt_value() { user_a_borrowed_amount_atom.mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); assert_eq!( user_a_debt_shares_atom, - find_by_denom(&uatom_info.denom, &position_a.debt).shares + find_by_denom(&uatom_info.denom, &position_a.debts).shares ); - let position_b = mock.query_position(&account_id_b); + let position_b = mock.query_positions(&account_id_b); let user_b_debt_shares_atom = user_a_debt_shares_atom .multiply_ratio(user_b_borrowed_amount_atom, interim_red_bank_debt.amount); assert_eq!( user_b_debt_shares_atom, - find_by_denom(&uatom_info.denom, &position_b.debt).shares + find_by_denom(&uatom_info.denom, &position_b.debts).shares ); let red_bank_atom_res = mock.query_total_debt_shares(&uatom_info.denom); @@ -567,22 +566,19 @@ fn test_debt_value() { let user_a_owed_atom = red_bank_atom_debt .amount .multiply_ratio(user_a_debt_shares_atom, red_bank_atom_res.shares); - let user_a_owed_atom_value = - uatom_info.price * Decimal::from_atomics(user_a_owed_atom, 0).unwrap(); + let user_a_owed_atom_value = uatom_info.price * user_a_owed_atom.to_dec().unwrap(); - let osmo_borrowed_amount_dec = - Decimal::from_atomics(user_a_borrowed_amount_osmo + Uint128::new(1u128), 0).unwrap(); + let osmo_borrowed_amount_dec = (user_a_borrowed_amount_osmo + Uint128::one()) + .to_dec() + .unwrap(); let osmo_debt_value = uosmo_info.price * osmo_borrowed_amount_dec; let total_debt_value = user_a_owed_atom_value.add(osmo_debt_value); assert_eq!(health.total_debt_value, total_debt_value); - let user_a_deposit_amount_osmo_dec = - Decimal::from_atomics(user_a_deposit_amount_osmo, 0).unwrap(); - let user_a_borrowed_amount_osmo_dec = - Decimal::from_atomics(user_a_borrowed_amount_osmo, 0).unwrap(); - let user_a_borrowed_amount_atom_dec = - Decimal::from_atomics(user_a_borrowed_amount_atom, 0).unwrap(); + let user_a_deposit_amount_osmo_dec = user_a_deposit_amount_osmo.to_dec().unwrap(); + let user_a_borrowed_amount_osmo_dec = user_a_borrowed_amount_osmo.to_dec().unwrap(); + let user_a_borrowed_amount_atom_dec = user_a_borrowed_amount_atom.to_dec().unwrap(); let lqdt_adjusted_assets_value = (uosmo_info.price * user_a_deposit_amount_osmo_dec @@ -605,6 +601,8 @@ fn test_debt_value() { ); } -fn find_by_denom<'a>(denom: &'a str, shares: &'a [DebtSharesValue]) -> &'a DebtSharesValue { +fn find_by_denom<'a>(denom: &'a str, shares: &'a [DebtAmount]) -> &'a DebtAmount { shares.iter().find(|item| item.denom == *denom).unwrap() } + +// TODO: Test vault positions taken into account diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index bacf49a7d..402cfda37 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -1,12 +1,15 @@ -use cosmwasm_std::{coins, Addr, Coin, Decimal, OverflowError, OverflowOperation, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Coin, Decimal, OverflowError, OverflowOperation, Uint128}; use mock_oracle::msg::CoinPrice; use rover::error::ContractError; use rover::error::ContractError::{AboveMaxLTV, NotLiquidatable}; -use rover::msg::execute::Action::{Borrow, Deposit, LiquidateCoin}; -use rover::msg::query::{CoinValue, DebtSharesValue}; +use rover::msg::execute::Action::{Borrow, Deposit, LiquidateCoin, VaultDeposit}; +use rover::msg::query::DebtAmount; +use rover::traits::IntoDecimal; -use crate::helpers::{assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv}; +use crate::helpers::{ + assert_err, get_coin, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, VaultTestInfo, +}; pub mod helpers; @@ -66,6 +69,73 @@ fn test_can_only_liquidate_unhealthy_accounts() { ) } +#[test] +fn test_vault_positions_contribute_to_health() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + + let leverage_vault = VaultTestInfo { + lp_token_denom: "uleverage".to_string(), + lockup: None, + asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom_info.clone(), uosmo_info.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(uatom_info.to_coin(200)), + Deposit(uosmo_info.to_coin(400)), + VaultDeposit { + vault, + coins: vec![coin(200, "uatom"), coin(400, "uosmo")], + }, + Borrow(uatom_info.to_coin(14)), + ], + &[coin(200, "uatom"), coin(400, "uosmo")], + ) + .unwrap(); + + let health = mock.query_health(&liquidatee_account_id); + assert!(!health.liquidatable); + + let liquidator = Addr::unchecked("liquidator"); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![LiquidateCoin { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(10), + request_coin_denom: uosmo_info.denom, + }], + &[], + ); + + assert_err( + res, + NotLiquidatable { + account_id: liquidatee_account_id, + lqdt_health_factor: "18.04".to_string(), + }, + ) +} + #[test] fn test_liquidatee_does_not_have_requested_asset() { let uosmo_info = uosmo_info(); @@ -99,7 +169,7 @@ fn test_liquidatee_does_not_have_requested_asset() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(20u128, 0).unwrap(), + price: 20.to_dec().unwrap(), }); let liquidator = Addr::unchecked("liquidator"); @@ -173,7 +243,7 @@ fn test_liquidatee_does_not_have_debt_coin() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(20u128, 0).unwrap(), + price: 20.to_dec().unwrap(), }); let liquidator = Addr::unchecked("liquidator"); @@ -228,7 +298,7 @@ fn test_liquidator_does_not_have_enough_to_pay_debt() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(20u128, 0).unwrap(), + price: 20.to_dec().unwrap(), }); let liquidator = Addr::unchecked("liquidator"); @@ -287,7 +357,7 @@ fn test_liquidator_left_in_unhealthy_state() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(20u128, 0).unwrap(), + price: 20.to_dec().unwrap(), }); let liquidator = Addr::unchecked("liquidator"); @@ -351,7 +421,7 @@ fn test_liquidatee_not_healthier_after_liquidation() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(20u128, 0).unwrap(), + price: 20.to_dec().unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -413,7 +483,7 @@ fn test_debt_amount_adjusted_to_close_factor_max() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(6u128, 0).unwrap(), + price: 6.to_dec().unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -434,24 +504,24 @@ fn test_debt_amount_adjusted_to_close_factor_max() { .unwrap(); // Assert liquidatee's new position - let position = mock.query_position(&liquidatee_account_id); + let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.coins.len(), 2); - let osmo_balance = get_coin(&position.coins, "uosmo"); + let osmo_balance = get_coin("uosmo", &position.coins); assert_eq!(osmo_balance.amount, Uint128::new(48)); - let atom_balance = get_coin(&position.coins, "uatom"); + let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(100)); - assert_eq!(position.debt.len(), 1); - let atom_debt = get_debt(&position.debt, "uatom"); + assert_eq!(position.debts.len(), 1); + let atom_debt = get_debt("uatom", &position.debts); assert_eq!(atom_debt.amount, Uint128::new(91)); // Assert liquidator's new position - let position = mock.query_position(&liquidator_account_id); + let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.coins.len(), 2); - assert_eq!(position.debt.len(), 0); - let atom_balance = get_coin(&position.coins, "uatom"); + assert_eq!(position.debts.len(), 0); + let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(40)); - let osmo_balance = get_coin(&position.coins, "uosmo"); + let osmo_balance = get_coin("uosmo", &position.coins); assert_eq!(osmo_balance.amount, Uint128::new(252)); } @@ -491,7 +561,7 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { mock.price_change(CoinPrice { denom: uatom_info.denom, - price: Decimal::from_atomics(20u128, 0).unwrap(), + price: 20.to_dec().unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -512,26 +582,26 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { .unwrap(); // Assert liquidatee's new position - let position = mock.query_position(&liquidatee_account_id); + let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.coins.len(), 3); - let osmo_balance = get_coin(&position.coins, "uosmo"); + let osmo_balance = get_coin("uosmo", &position.coins); assert_eq!(osmo_balance.amount, Uint128::new(191)); - let atom_balance = get_coin(&position.coins, "uatom"); + let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(100)); - let jake_balance = get_coin(&position.coins, "ujake"); + let jake_balance = get_coin("ujake", &position.coins); assert_eq!(jake_balance.amount, Uint128::new(10)); - assert_eq!(position.debt.len(), 1); - let atom_debt = get_debt(&position.debt, "uatom"); + assert_eq!(position.debts.len(), 1); + let atom_debt = get_debt("uatom", &position.debts); assert_eq!(atom_debt.amount, Uint128::new(101)); // Assert liquidator's new position - let position = mock.query_position(&liquidator_account_id); + let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.coins.len(), 2); - assert_eq!(position.debt.len(), 0); - let jake_balance = get_coin(&position.coins, "ujake"); + assert_eq!(position.debts.len(), 0); + let jake_balance = get_coin("ujake", &position.coins); assert_eq!(jake_balance.amount, Uint128::new(39)); - let osmo_balance = get_coin(&position.coins, "uosmo"); + let osmo_balance = get_coin("uosmo", &position.coins); assert_eq!(osmo_balance.amount, Uint128::new(109)); } @@ -569,7 +639,7 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(20u128, 0).unwrap(), + price: 20.to_dec().unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -590,24 +660,24 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { .unwrap(); // Assert liquidatee's new position - let position = mock.query_position(&liquidatee_account_id); + let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.coins.len(), 2); - let osmo_balance = get_coin(&position.coins, "uosmo"); + let osmo_balance = get_coin("uosmo", &position.coins); assert_eq!(osmo_balance.amount, Uint128::new(48)); - let atom_balance = get_coin(&position.coins, "uatom"); + let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(100)); - assert_eq!(position.debt.len(), 1); - let atom_debt = get_debt(&position.debt, "uatom"); + assert_eq!(position.debts.len(), 1); + let atom_debt = get_debt("uatom", &position.debts); assert_eq!(atom_debt.amount, Uint128::new(98)); // Assert liquidator's new position - let position = mock.query_position(&liquidator_account_id); + let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.coins.len(), 2); - assert_eq!(position.debt.len(), 0); - let atom_balance = get_coin(&position.coins, "uatom"); + assert_eq!(position.debts.len(), 0); + let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(47)); - let osmo_balance = get_coin(&position.coins, "uosmo"); + let osmo_balance = get_coin("uosmo", &position.coins); assert_eq!(osmo_balance.amount, Uint128::new(252)); } @@ -666,22 +736,22 @@ fn test_debt_amount_no_adjustment() { .unwrap(); // Assert liquidatee's new position - let position = mock.query_position(&liquidatee_account_id); + let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.coins.len(), 2); - let osmo_balance = get_coin(&position.coins, "uosmo"); + let osmo_balance = get_coin("uosmo", &position.coins); assert_eq!(osmo_balance.amount, Uint128::new(69)); - let atom_balance = get_coin(&position.coins, "uatom"); + let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(100)); - assert_eq!(position.debt.len(), 1); - let atom_debt = get_debt(&position.debt, "uatom"); + assert_eq!(position.debts.len(), 1); + let atom_debt = get_debt("uatom", &position.debts); assert_eq!(atom_debt.amount, Uint128::new(91)); // Assert liquidator's new position - let position = mock.query_position(&liquidator_account_id); + let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.coins.len(), 1); - assert_eq!(position.debt.len(), 0); - let osmo_balance = get_coin(&position.coins, "uosmo"); + assert_eq!(position.debts.len(), 0); + let osmo_balance = get_coin("uosmo", &position.coins); assert_eq!(osmo_balance.amount, Uint128::new(231)); } @@ -694,15 +764,10 @@ fn test_debt_amount_no_adjustment() { #[test] fn test_liquidate_with_no_deposited_funds() {} -fn get_coin(coins: &[CoinValue], denom: &str) -> CoinValue { - coins - .iter() - .find(|coin| coin.denom.as_str() == denom) - .unwrap() - .clone() -} +#[test] +fn test_liquidate_with_vault_position() {} -fn get_debt(coins: &[DebtSharesValue], denom: &str) -> DebtSharesValue { +fn get_debt(denom: &str, coins: &[DebtAmount]) -> DebtAmount { coins .iter() .find(|coin| coin.denom.as_str() == denom) diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index d8fcfab08..ce918ae9e 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -5,6 +5,7 @@ use cosmwasm_std::{coin, coins, Addr, Decimal, OverflowError, OverflowOperation, use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use rover::error::ContractError; use rover::msg::execute::Action::{Borrow, Deposit, Repay, Withdraw}; +use rover::traits::IntoDecimal; use crate::helpers::{ assert_err, uosmo_info, AccountToFund, CoinInfo, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, @@ -78,7 +79,7 @@ fn test_raises_when_repaying_what_is_not_owed() { let uatom_info = CoinInfo { denom: "atom".to_string(), - price: Decimal::from_atomics(9u128, 0).unwrap(), + price: 9.to_dec().unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), }; @@ -134,7 +135,7 @@ fn test_raises_when_not_enough_assets_to_repay() { let uatom_info = CoinInfo { denom: "atom".to_string(), - price: Decimal::from_atomics(9u128, 0).unwrap(), + price: 9.to_dec().unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), }; @@ -191,9 +192,9 @@ fn test_successful_repay() { let account_id = mock.create_credit_account(&user).unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 0); - assert_eq!(position.debt.len(), 0); + assert_eq!(position.debts.len(), 0); mock.update_credit_account( &account_id, @@ -211,14 +212,14 @@ fn test_successful_repay() { mock.update_credit_account(&account_id, &user, vec![Repay(coin_info.to_coin(20))], &[]) .unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 1); let asset_res = position.coins.first().unwrap(); let expected_net_asset_amount = Uint128::new(330); // Deposit + Borrow - Repay assert_eq!(asset_res.amount, expected_net_asset_amount); - let debt_shares_res = position.debt.first().unwrap(); - assert_eq!(position.debt.len(), 1); + let debt_shares_res = position.debts.first().unwrap(); + assert_eq!(position.debts.len(), 1); assert_eq!(debt_shares_res.denom, coin_info.denom); let former_total_debt_shares = Uint128::new(50).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); @@ -249,14 +250,14 @@ fn test_successful_repay() { ) .unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 1); let asset_res = position.coins.first().unwrap(); let expected_net_asset_amount = Uint128::new(299); // Deposit + Borrow - full repay - interest assert_eq!(asset_res.amount, expected_net_asset_amount); // Full debt repaid and purged from storage - assert_eq!(position.debt.len(), 0); + assert_eq!(position.debts.len(), 0); let res = mock.query_total_debt_shares(&coin_info.denom); assert_eq!(res.shares, Uint128::zero()); @@ -299,13 +300,13 @@ fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { ) .unwrap(); - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 1); let asset_res = position.coins.first().unwrap(); let expected_net_asset_amount = Uint128::new(299); // Deposit + Borrow - Repay - interest assert_eq!(asset_res.amount, expected_net_asset_amount); - assert_eq!(position.debt.len(), 0); + assert_eq!(position.debts.len(), 0); let res = mock.query_total_debt_shares(&coin_info.denom); assert_eq!(res.shares, Uint128::zero()); diff --git a/contracts/credit-manager/tests/test_swap.rs b/contracts/credit-manager/tests/test_swap.rs index 9778c5feb..f1d0dc015 100644 --- a/contracts/credit-manager/tests/test_swap.rs +++ b/contracts/credit-manager/tests/test_swap.rs @@ -221,7 +221,7 @@ fn test_swap_successful() { assert_eq!(osmo_balance, MOCK_SWAP_RESULT); // assert account position - let position = mock.query_position(&account_id); + let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); assert_eq!(position.coins.first().unwrap().amount, MOCK_SWAP_RESULT); diff --git a/contracts/credit-manager/tests/test_vault_deposit.rs b/contracts/credit-manager/tests/test_vault_deposit.rs index 260331505..8986eec55 100644 --- a/contracts/credit-manager/tests/test_vault_deposit.rs +++ b/contracts/credit-manager/tests/test_vault_deposit.rs @@ -232,19 +232,15 @@ fn test_successful_deposit_into_locked_vault() { let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); - let res = mock.query_position(&account_id); - assert_eq!(res.vault_positions.len(), 1); + let res = mock.query_positions(&account_id); + assert_eq!(res.vaults.len(), 1); assert_eq!( STARTING_VAULT_SHARES, - res.vault_positions.first().unwrap().position.locked - ); - assert_eq!( - Uint128::zero(), - res.vault_positions.first().unwrap().position.unlocked + res.vaults.first().unwrap().state.locked ); + assert_eq!(Uint128::zero(), res.vaults.first().unwrap().state.unlocked); - let assets = - mock.query_preview_redeem(&vault, res.vault_positions.first().unwrap().position.locked); + let assets = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().state.locked); let osmo_withdraw = assets.iter().find(|coin| coin.denom == "uosmo").unwrap(); assert_eq!(osmo_withdraw.amount, Uint128::new(120)); @@ -301,21 +297,15 @@ fn test_successful_deposit_into_unlocked_vault() { let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); - let res = mock.query_position(&account_id); - assert_eq!(res.vault_positions.len(), 1); + let res = mock.query_positions(&account_id); + assert_eq!(res.vaults.len(), 1); assert_eq!( STARTING_VAULT_SHARES, - res.vault_positions.first().unwrap().position.unlocked - ); - assert_eq!( - Uint128::zero(), - res.vault_positions.first().unwrap().position.locked + res.vaults.first().unwrap().state.unlocked ); + assert_eq!(Uint128::zero(), res.vaults.first().unwrap().state.locked); - let assets = mock.query_preview_redeem( - &vault, - res.vault_positions.first().unwrap().position.unlocked, - ); + let assets = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().state.unlocked); let osmo_withdraw = assets.iter().find(|coin| coin.denom == "uosmo").unwrap(); assert_eq!(osmo_withdraw.amount, Uint128::new(120)); diff --git a/contracts/credit-manager/tests/test_vault_withdraw.rs b/contracts/credit-manager/tests/test_vault_withdraw.rs index c4ad5b852..cd6fe42f8 100644 --- a/contracts/credit-manager/tests/test_vault_withdraw.rs +++ b/contracts/credit-manager/tests/test_vault_withdraw.rs @@ -1,5 +1,5 @@ use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{coin, Addr, OverflowError, Uint128}; +use cosmwasm_std::{coin, Addr, Coin, OverflowError, Uint128}; use mock_vault::contract::STARTING_VAULT_SHARES; use rover::adapters::VaultBase; @@ -7,7 +7,6 @@ use rover::error::ContractError; use rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; use rover::msg::execute::Action::{Deposit, VaultDeposit, VaultWithdraw}; use rover::msg::execute::CallbackMsg; -use rover::msg::query::CoinValue; use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv, VaultTestInfo}; @@ -134,7 +133,7 @@ fn test_force_withdraw_can_only_be_called_by_rover() { &user.clone(), CallbackMsg::VaultForceWithdraw { account_id, - vault: VaultBase::new(Addr::unchecked(vault.address().clone())), + vault: VaultBase::new(Addr::unchecked(vault.address)), amount: STARTING_VAULT_SHARES, }, ); @@ -182,24 +181,24 @@ fn test_force_withdraw_breaks_lock() { .unwrap(); // Assert token's position - let res = mock.query_position(&account_id); - assert_eq!(res.vault_positions.len(), 1); - let v = res.vault_positions.first().unwrap(); - assert_eq!(v.position.locked, STARTING_VAULT_SHARES); + let res = mock.query_positions(&account_id); + assert_eq!(res.vaults.len(), 1); + let v = res.vaults.first().unwrap(); + assert_eq!(v.state.locked, STARTING_VAULT_SHARES); mock.invoke_callback( &mock.rover.clone(), CallbackMsg::VaultForceWithdraw { account_id: account_id.clone(), - vault: VaultBase::new(Addr::unchecked(vault.address().clone())), + vault: VaultBase::new(Addr::unchecked(vault.address)), amount: STARTING_VAULT_SHARES, }, ) .unwrap(); // Assert token's updated position - let res = mock.query_position(&account_id); - assert_eq!(res.vault_positions.len(), 0); + let res = mock.query_positions(&account_id); + assert_eq!(res.vaults.len(), 0); let atom = get_coin("uatom", &res.coins); assert_eq!(atom.amount, Uint128::from(200u128)); let osmo = get_coin("uosmo", &res.coins); @@ -257,10 +256,10 @@ fn test_withdraw_with_unlocked_vault_coins() { .unwrap(); // Assert token's position - let res = mock.query_position(&account_id); - assert_eq!(res.vault_positions.len(), 1); - let v = res.vault_positions.first().unwrap(); - assert_eq!(v.position.unlocked, STARTING_VAULT_SHARES); + let res = mock.query_positions(&account_id); + assert_eq!(res.vaults.len(), 1); + let v = res.vaults.first().unwrap(); + assert_eq!(v.state.unlocked, STARTING_VAULT_SHARES); let atom = get_coin("uatom", &res.coins); assert_eq!(atom.amount, Uint128::from(100u128)); let osmo = get_coin("uosmo", &res.coins); @@ -288,8 +287,8 @@ fn test_withdraw_with_unlocked_vault_coins() { .unwrap(); // Assert token's updated position - let res = mock.query_position(&account_id); - assert_eq!(res.vault_positions.len(), 0); + let res = mock.query_positions(&account_id); + assert_eq!(res.vaults.len(), 0); let atom = get_coin("uatom", &res.coins); assert_eq!(atom.amount, Uint128::from(200u128)); let osmo = get_coin("uosmo", &res.coins); @@ -306,6 +305,6 @@ fn test_withdraw_with_unlocked_vault_coins() { assert_eq!(Uint128::zero(), lp_balance.amount); } -fn get_coin(denom: &str, coins: &[CoinValue]) -> CoinValue { +fn get_coin(denom: &str, coins: &[Coin]) -> Coin { coins.iter().find(|cv| cv.denom == denom).unwrap().clone() } diff --git a/contracts/credit-manager/tests/test_withdraw.rs b/contracts/credit-manager/tests/test_withdraw.rs index 67f999092..739d32012 100644 --- a/contracts/credit-manager/tests/test_withdraw.rs +++ b/contracts/credit-manager/tests/test_withdraw.rs @@ -32,7 +32,7 @@ fn test_only_owner_of_token_can_withdraw() { }, ); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); } @@ -55,7 +55,7 @@ fn test_withdraw_nothing() { assert_err(res, ContractError::NoAmount); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); } @@ -85,7 +85,7 @@ fn test_withdraw_but_no_funds() { }), ); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); } @@ -122,7 +122,7 @@ fn test_withdraw_but_not_enough_funds() { }), ); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); } @@ -153,7 +153,7 @@ fn test_can_only_withdraw_allowed_assets() { assert_err(res, NotWhitelisted(not_allowed_coin.denom)); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); } @@ -190,7 +190,7 @@ fn test_cannot_withdraw_more_than_healthy() { }, ); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); } @@ -220,7 +220,7 @@ fn test_withdraw_success() { ) .unwrap(); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); let coin = mock.query_balance(&mock.rover, &coin_info.denom); @@ -263,7 +263,7 @@ fn test_multiple_withdraw_actions() { ) .unwrap(); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 2); let coin = mock.query_balance(&user, &uosmo_info.denom); @@ -280,7 +280,7 @@ fn test_multiple_withdraw_actions() { ) .unwrap(); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 1); let coin = mock.query_balance(&mock.rover, &uosmo_info.denom); @@ -297,7 +297,7 @@ fn test_multiple_withdraw_actions() { ) .unwrap(); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 1); let coin = mock.query_balance(&mock.rover, &uatom_info.denom); @@ -314,7 +314,7 @@ fn test_multiple_withdraw_actions() { ) .unwrap(); - let res = mock.query_position(&account_id); + let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); let coin = mock.query_balance(&mock.rover, &uatom_info.denom); diff --git a/contracts/mars-oracle-adapter/Cargo.toml b/contracts/mars-oracle-adapter/Cargo.toml new file mode 100644 index 000000000..e509a446d --- /dev/null +++ b/contracts/mars-oracle-adapter/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "mars-oracle-adapter" +version = "1.0.0" +authors = ["grod220 "] +edition = "2021" +license = "GPL-3.0-or-later" +repository = "https://github.com/mars-protocol/rover" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } +rover = { version = "1.0", path = "../../packages/rover" } + +cosmwasm-schema = "1.1" +cosmwasm-std = "1.1" +cw-storage-plus = "0.15" +thiserror = "1.0" + +[dev-dependencies] +mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } +mock-vault = { version = "1.0", path = "../../contracts/mock-vault", features = ["library"] } + +anyhow = "1" +cw-multi-test = "0.15" diff --git a/contracts/mars-oracle-adapter/examples/schema.rs b/contracts/mars-oracle-adapter/examples/schema.rs new file mode 100644 index 000000000..391924528 --- /dev/null +++ b/contracts/mars-oracle-adapter/examples/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use mars_oracle_adapter::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/mars-oracle-adapter/src/contract.rs b/contracts/mars-oracle-adapter/src/contract.rs new file mode 100644 index 000000000..6134c4e55 --- /dev/null +++ b/contracts/mars-oracle-adapter/src/contract.rs @@ -0,0 +1,201 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::Order::Ascending; +use cosmwasm_std::{ + to_binary, Addr, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, + Storage, +}; +use cw_storage_plus::Bound; +use mars_outpost::oracle::PriceResponse; + +use rover::adapters::{Oracle, VaultBase}; +use rover::traits::IntoDecimal; + +use crate::error::{ContractError, ContractResult}; +use crate::msg::{ + ConfigResponse, ConfigUpdates, ExecuteMsg, InstantiateMsg, PricingMethod, QueryMsg, + VaultPricingInfo, +}; +use crate::state::{ORACLE, OWNER, VAULT_PRICING_INFO}; + +const MAX_LIMIT: u32 = 30; +const DEFAULT_LIMIT: u32 = 10; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let owner = deps.api.addr_validate(&msg.owner)?; + OWNER.save(deps.storage, &owner)?; + + let oracle = msg.oracle.check(deps.api)?; + ORACLE.save(deps.storage, &oracle)?; + + for info in msg.vault_pricing { + VAULT_PRICING_INFO.save(deps.storage, &info.denom, &info)?; + } + + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> ContractResult { + match msg { + ExecuteMsg::UpdateConfig { new_config } => update_config(deps, info, new_config), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> ContractResult { + let res = match msg { + QueryMsg::Price { denom } => to_binary(&query_price(deps, &denom)?), + QueryMsg::Config {} => to_binary(&query_config(deps)?), + QueryMsg::PricingInfo { denom } => to_binary(&query_pricing_info(deps, &denom)?), + QueryMsg::AllPricingInfo { start_after, limit } => { + to_binary(&query_all_pricing_info(deps, start_after, limit)?) + } + }; + res.map_err(Into::into) +} + +fn query_pricing_info(deps: Deps, denom: &str) -> StdResult { + VAULT_PRICING_INFO.load(deps.storage, denom) +} + +fn query_all_pricing_info( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult> { + let start = start_after + .as_ref() + .map(|denom| Bound::exclusive(denom.as_str())); + + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + VAULT_PRICING_INFO + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|res| { + let (_, info) = res?; + Ok(info) + }) + .collect::>>() +} + +fn query_price(deps: Deps, denom: &str) -> ContractResult { + let info_opt = VAULT_PRICING_INFO.may_load(deps.storage, denom)?; + let oracle = ORACLE.load(deps.storage)?; + + match info_opt { + Some(info) if info.method == PricingMethod::PreviewRedeem => { + let vault = VaultBase::new(info.addr.clone()); + calculate_preview_redeem(&deps, &oracle, &info, &vault) + } + _ => Ok(oracle.query_price(&deps.querier, denom)?), + } +} + +fn calculate_preview_redeem( + deps: &Deps, + oracle: &Oracle, + info: &VaultPricingInfo, + vault: &VaultBase, +) -> ContractResult { + let total_issued = vault.query_total_vault_coins_issued(&deps.querier)?; + let coins = vault.query_preview_redeem(&deps.querier, total_issued)?; + let total_value = + coins + .iter() + .try_fold(Decimal::zero(), |total, coin| -> ContractResult<_> { + let res = oracle.query_price(&deps.querier, &coin.denom)?; + let value = res.price.checked_mul(coin.amount.to_dec()?)?; + let new_total = total.checked_add(value)?; + Ok(new_total) + })?; + + Ok(PriceResponse { + denom: info.denom.clone(), + price: total_value.checked_div(total_issued.to_dec()?)?, + }) +} + +fn query_config(deps: Deps) -> ContractResult { + Ok(ConfigResponse { + owner: OWNER.load(deps.storage)?, + oracle: ORACLE.load(deps.storage)?, + }) +} + +pub fn update_config( + deps: DepsMut, + info: MessageInfo, + new_config: ConfigUpdates, +) -> ContractResult { + let owner = OWNER.load(deps.storage)?; + + if info.sender != owner { + return Err(ContractError::Unauthorized { + user: info.sender.into(), + action: "update config".to_string(), + }); + } + + let mut response = + Response::new().add_attribute("action", "rover/oracle-adapter/update_config"); + + if let Some(addr_str) = new_config.owner { + let validated = deps.api.addr_validate(&addr_str)?; + OWNER.save(deps.storage, &validated)?; + response = response + .add_attribute("key", "owner") + .add_attribute("value", addr_str); + } + + if let Some(unchecked) = new_config.oracle { + ORACLE.save(deps.storage, &unchecked.check(deps.api)?)?; + response = response + .add_attribute("key", "oracle") + .add_attribute("value", unchecked.address()); + } + + if let Some(vault_pricing) = new_config.vault_pricing { + clear_map(deps.storage)?; + for info in &vault_pricing { + VAULT_PRICING_INFO.save(deps.storage, &info.denom, info)?; + } + let value_str = if vault_pricing.is_empty() { + "None".to_string() + } else { + vault_pricing + .into_iter() + .map(|info| info.denom) + .collect::>() + .join(", ") + }; + response = response + .add_attribute("key", "vault_pricing") + .add_attribute("value", value_str); + } + + Ok(response) +} + +fn clear_map(storage: &mut dyn Storage) -> ContractResult<()> { + VAULT_PRICING_INFO + .range(storage, None, None, Ascending) + .collect::>>()? + .iter() + .for_each(|(denom, _)| { + VAULT_PRICING_INFO.remove(storage, denom); + }); + Ok(()) +} diff --git a/contracts/mars-oracle-adapter/src/error.rs b/contracts/mars-oracle-adapter/src/error.rs new file mode 100644 index 000000000..172f63dff --- /dev/null +++ b/contracts/mars-oracle-adapter/src/error.rs @@ -0,0 +1,27 @@ +use cosmwasm_std::{CheckedFromRatioError, DecimalRangeExceeded, OverflowError, StdError}; +use thiserror::Error; + +use rover::error::ContractError as RoverError; + +pub type ContractResult = Result; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + CheckedFromRatioError(#[from] CheckedFromRatioError), + + #[error("{0}")] + DecimalRangeExceeded(#[from] DecimalRangeExceeded), + + #[error("{0}")] + Overflow(#[from] OverflowError), + + #[error("{0}")] + RoverError(#[from] RoverError), + + #[error("{0}")] + Std(#[from] StdError), + + #[error("{user:?} is not authorized to {action:?}")] + Unauthorized { user: String, action: String }, +} diff --git a/contracts/mars-oracle-adapter/src/lib.rs b/contracts/mars-oracle-adapter/src/lib.rs new file mode 100644 index 000000000..a5abdbb0f --- /dev/null +++ b/contracts/mars-oracle-adapter/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/mars-oracle-adapter/src/msg.rs b/contracts/mars-oracle-adapter/src/msg.rs new file mode 100644 index 000000000..8ec62ed4e --- /dev/null +++ b/contracts/mars-oracle-adapter/src/msg.rs @@ -0,0 +1,67 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Decimal}; + +use rover::adapters::{Oracle, OracleUnchecked}; + +#[cw_serde] +pub struct CoinPrice { + pub denom: String, + pub price: Decimal, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub oracle: OracleUnchecked, + pub vault_pricing: Vec, + pub owner: String, +} + +#[cw_serde] +pub enum ExecuteMsg { + UpdateConfig { new_config: ConfigUpdates }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(mars_outpost::oracle::PriceResponse)] + Price { denom: String }, + + #[returns(ConfigResponse)] + Config {}, + + #[returns(VaultPricingInfo)] + PricingInfo { denom: String }, + + #[returns(Vec)] + AllPricingInfo { + start_after: Option, + limit: Option, + }, +} + +#[cw_serde] +pub struct ConfigResponse { + pub owner: Addr, + pub oracle: Oracle, +} + +#[cw_serde] +#[derive(Default)] +pub struct ConfigUpdates { + pub owner: Option, + pub oracle: Option, + pub vault_pricing: Option>, +} + +#[cw_serde] +pub struct VaultPricingInfo { + pub denom: String, + pub addr: Addr, + pub method: PricingMethod, +} + +#[cw_serde] +pub enum PricingMethod { + PreviewRedeem, +} diff --git a/contracts/mars-oracle-adapter/src/state.rs b/contracts/mars-oracle-adapter/src/state.rs new file mode 100644 index 000000000..4b1d25777 --- /dev/null +++ b/contracts/mars-oracle-adapter/src/state.rs @@ -0,0 +1,11 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; +use rover::adapters::Oracle; + +use crate::msg::VaultPricingInfo; + +pub const OWNER: Item = Item::new("owner"); +pub const ORACLE: Item = Item::new("oracle"); + +/// Map<(Vault Token Denom, Pricing Method)> +pub const VAULT_PRICING_INFO: Map<&str, VaultPricingInfo> = Map::new("vault_coin"); diff --git a/contracts/mars-oracle-adapter/tests/helpers.rs b/contracts/mars-oracle-adapter/tests/helpers.rs new file mode 100644 index 000000000..288131219 --- /dev/null +++ b/contracts/mars-oracle-adapter/tests/helpers.rs @@ -0,0 +1,157 @@ +use anyhow::Result as AnyResult; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{coin, Addr, Coin, Decimal}; +use cw_multi_test::{AppResponse, BankSudo, BasicApp, ContractWrapper, Executor, SudoMsg}; + +use mars_oracle_adapter::contract::{execute, instantiate, query}; +use mars_oracle_adapter::error::ContractError; +use mars_oracle_adapter::msg::{InstantiateMsg, PricingMethod, VaultPricingInfo}; +use mock_oracle::contract::{ + execute as oracleExecute, instantiate as oracleInstantiate, query as oracleQuery, +}; +use mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; +use mock_vault::contract::{ + execute as vaultExecute, instantiate as vaultInstantiate, query as vaultQuery, + DEFAULT_VAULT_TOKEN_PREFUND, +}; +use mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; +use rover::adapters::{OracleBase, OracleUnchecked, VaultBase}; + +pub fn mock_vault_info() -> VaultTestInfo { + VaultTestInfo { + vault_coin_denom: "yOSMO_ATOM".to_string(), + lockup: None, + underlying_assets: vec!["uosmo".to_string(), "uatom".to_string()], + pricing_method: PricingMethod::PreviewRedeem, + } +} + +pub fn instantiate_oracle_adapter(app: &mut BasicApp) -> Addr { + let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); + let code_id = app.store_code(contract); + + let oracle = deploy_oracle(app); + let vault_pricing_info = deploy_vault(app, oracle.clone().into(), mock_vault_info()); + starting_vault_deposit(app, &vault_pricing_info); + + let owner = Addr::unchecked("owner"); + app.instantiate_contract( + code_id, + owner.clone(), + &InstantiateMsg { + oracle: oracle.into(), + vault_pricing: vec![vault_pricing_info], + owner: owner.to_string(), + }, + &[], + "mars-oracle-adapter", + None, + ) + .unwrap() +} + +#[cw_serde] +pub struct VaultTestInfo { + pub vault_coin_denom: String, + pub lockup: Option, + pub underlying_assets: Vec, + pub pricing_method: PricingMethod, +} + +fn deploy_vault( + app: &mut BasicApp, + oracle: OracleUnchecked, + vault: VaultTestInfo, +) -> VaultPricingInfo { + let contract = ContractWrapper::new(vaultExecute, vaultInstantiate, vaultQuery); + let code_id = app.store_code(Box::new(contract)); + + let addr = app + .instantiate_contract( + code_id, + Addr::unchecked("vault-instantiator"), + &VaultInstantiateMsg { + lp_token_denom: vault.clone().vault_coin_denom, + lockup: vault.lockup, + asset_denoms: vault.clone().underlying_assets, + oracle, + }, + &[], + "mock-vault", + None, + ) + .unwrap(); + + let vault_pricing_info = VaultPricingInfo { + denom: vault.vault_coin_denom, + addr, + method: vault.pricing_method, + }; + fund_vault(app, &vault_pricing_info); + vault_pricing_info +} + +/// cw-multi-test does not yet have the ability to mint sdk coins. For this reason, +/// this contract expects to be pre-funded with vault tokens and it will simulate the mint. +fn fund_vault(app: &mut BasicApp, vault: &VaultPricingInfo) { + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: vault.addr.to_string(), + amount: vec![Coin { + denom: vault.denom.clone(), + amount: DEFAULT_VAULT_TOKEN_PREFUND, + }], + })) + .unwrap(); +} + +fn starting_vault_deposit(app: &mut BasicApp, vault_info: &VaultPricingInfo) { + let user = Addr::unchecked("some_user_xyz"); + let coins_to_deposit = vec![coin(120_042, "uosmo"), coin(32_343, "uatom")]; + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: user.to_string(), + amount: coins_to_deposit.clone(), + })) + .unwrap(); + + let vault = VaultBase::new(vault_info.addr.clone()); + let deposit_msg = vault.deposit_msg(&coins_to_deposit).unwrap(); + app.execute(user, deposit_msg).unwrap(); +} + +fn deploy_oracle(app: &mut BasicApp) -> OracleBase { + let contract = ContractWrapper::new(oracleExecute, oracleInstantiate, oracleQuery); + let code_id = app.store_code(Box::new(contract)); + + let addr = app + .instantiate_contract( + code_id, + Addr::unchecked("oracle_contract_owner"), + &OracleInstantiateMsg { + coins: vec![ + CoinPrice { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + }, + CoinPrice { + denom: "uatom".to_string(), + price: Decimal::from_atomics(10u128, 1).unwrap(), + }, + ], + }, + &[], + "mock-oracle", + None, + ) + .unwrap(); + OracleBase::new(addr) +} + +pub fn assert_err(res: AnyResult, err: ContractError) { + match res { + Ok(_) => panic!("Result was not an error"), + Err(generic_err) => { + let contract_err: ContractError = generic_err.downcast().unwrap(); + assert_eq!(contract_err, err); + } + } +} diff --git a/contracts/mars-oracle-adapter/tests/test_query_price.rs b/contracts/mars-oracle-adapter/tests/test_query_price.rs new file mode 100644 index 000000000..dec319b1c --- /dev/null +++ b/contracts/mars-oracle-adapter/tests/test_query_price.rs @@ -0,0 +1,62 @@ +use std::ops::{Add, Div, Mul}; + +use cosmwasm_std::{Decimal, Uint128}; +use cw_multi_test::App; +use mars_outpost::oracle::PriceResponse; + +use mars_oracle_adapter::msg::QueryMsg; +use mock_vault::contract::STARTING_VAULT_SHARES; +use rover::traits::IntoDecimal; + +use crate::helpers::{instantiate_oracle_adapter, mock_vault_info}; + +pub mod helpers; + +#[test] +fn test_non_vault_coin_priced() { + let mut app = App::default(); + let contract_addr = instantiate_oracle_adapter(&mut app); + + let res: PriceResponse = app + .wrap() + .query_wasm_smart( + contract_addr.to_string(), + &QueryMsg::Price { + denom: "uosmo".to_string(), + }, + ) + .unwrap(); + + assert_eq!(res.price, Decimal::from_atomics(25u128, 2).unwrap()) +} + +#[test] +fn test_vault_coin_preview_redeem() { + let mut app = App::default(); + let contract_addr = instantiate_oracle_adapter(&mut app); + let vault_info = mock_vault_info(); + + let res: PriceResponse = app + .wrap() + .query_wasm_smart( + contract_addr.to_string(), + &QueryMsg::Price { + denom: vault_info.vault_coin_denom, + }, + ) + .unwrap(); + + let uatom_price = Decimal::from_atomics(10u128, 1).unwrap(); + let atom_in_vault = Uint128::new(32_343); + let vaults_atom_value = atom_in_vault.to_dec().unwrap().mul(uatom_price); + + let uosmo_price = Decimal::from_atomics(25u128, 2).unwrap(); + let osmo_in_vault = Uint128::new(120_042); + let vaults_osmo_value = osmo_in_vault.to_dec().unwrap().mul(uosmo_price); + + let price_per_vault_coin = vaults_atom_value + .add(vaults_osmo_value) + .div(STARTING_VAULT_SHARES); + + assert_eq!(res.price, price_per_vault_coin) +} diff --git a/contracts/mars-oracle-adapter/tests/test_update_config.rs b/contracts/mars-oracle-adapter/tests/test_update_config.rs new file mode 100644 index 000000000..76f71623a --- /dev/null +++ b/contracts/mars-oracle-adapter/tests/test_update_config.rs @@ -0,0 +1,147 @@ +use cosmwasm_std::Addr; +use cw_multi_test::{App, Executor}; + +use mars_oracle_adapter::error::ContractError; +use mars_oracle_adapter::msg::{ + ConfigResponse, ConfigUpdates, ExecuteMsg, QueryMsg, VaultPricingInfo, +}; +use rover::adapters::{OracleBase, OracleUnchecked}; + +use crate::helpers::{assert_err, instantiate_oracle_adapter}; + +pub mod helpers; + +#[test] +fn test_only_owner_can_update_config() { + let mut app = App::default(); + let contract_addr = instantiate_oracle_adapter(&mut app); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = app.execute_contract( + bad_guy.clone(), + contract_addr, + &ExecuteMsg::UpdateConfig { + new_config: Default::default(), + }, + &[], + ); + + assert_err( + res, + ContractError::Unauthorized { + user: bad_guy.to_string(), + action: "update config".to_string(), + }, + ); +} + +#[test] +fn test_update_config_works_with_full_config() { + let mut app = App::default(); + let contract_addr = instantiate_oracle_adapter(&mut app); + let original_config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + let new_owner = Addr::unchecked("new_owner"); + let new_oracle = OracleUnchecked::new("new_oracle".to_string()); + let new_vault_pricing = vec![]; + + app.execute_contract( + original_config.owner.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateConfig { + new_config: ConfigUpdates { + owner: Some(new_owner.to_string()), + oracle: Some(new_oracle), + vault_pricing: Some(new_vault_pricing), + }, + }, + &[], + ) + .unwrap(); + + let new_config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + assert_ne!(new_config.owner, original_config.owner); + assert_eq!(new_config.owner, new_owner.to_string()); + + assert_ne!(new_config.oracle, original_config.oracle); + assert_eq!( + new_config.oracle, + OracleBase::new(Addr::unchecked("new_oracle".to_string())) + ); + + let pricing_infos: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.to_string(), + &QueryMsg::AllPricingInfo { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(pricing_infos.len(), 0); +} + +#[test] +fn test_update_config_does_nothing_when_nothing_is_passed() { + let mut app = App::default(); + let contract_addr = instantiate_oracle_adapter(&mut app); + let original_config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + let original_pricing_infos: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.to_string(), + &QueryMsg::AllPricingInfo { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + app.execute_contract( + original_config.owner.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateConfig { + new_config: ConfigUpdates { + owner: None, + oracle: None, + vault_pricing: None, + }, + }, + &[], + ) + .unwrap(); + + let new_config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + assert_eq!(new_config.owner, original_config.owner); + assert_eq!(new_config.oracle, original_config.oracle); + + let new_pricing_infos: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.to_string(), + &QueryMsg::AllPricingInfo { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(new_pricing_infos, original_pricing_infos); +} diff --git a/contracts/mock-oracle/Cargo.toml b/contracts/mock-oracle/Cargo.toml index 17fb48d4e..49d20271e 100644 --- a/contracts/mock-oracle/Cargo.toml +++ b/contracts/mock-oracle/Cargo.toml @@ -19,5 +19,3 @@ mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" cw-storage-plus = "0.15" -schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/contracts/mock-oracle/src/msg.rs b/contracts/mock-oracle/src/msg.rs index 5407ddb8a..a492d1aa5 100644 --- a/contracts/mock-oracle/src/msg.rs +++ b/contracts/mock-oracle/src/msg.rs @@ -1,21 +1,18 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Decimal; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[cw_serde] pub struct CoinPrice { pub denom: String, pub price: Decimal, } -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[cw_serde] pub struct InstantiateMsg { pub coins: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { // Meant to simulate price changes for tests. Not available in prod. ChangePrice(CoinPrice), diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index 2e55a1da9..770196411 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -19,5 +19,3 @@ mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" cw-storage-plus = "0.15" -schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs index 7b164a3c2..42aee1aea 100644 --- a/contracts/mock-red-bank/src/msg.rs +++ b/contracts/mock-red-bank/src/msg.rs @@ -1,22 +1,19 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Decimal, Uint128}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[cw_serde] pub struct InstantiateMsg { pub coins: Vec, } -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[cw_serde] pub struct CoinMarketInfo { pub denom: String, pub max_ltv: Decimal, pub liquidation_threshold: Decimal, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { Borrow { coin: Coin, @@ -37,7 +34,7 @@ pub enum QueryMsg { Market { denom: String }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct UserAssetDebtResponse { pub denom: String, pub amount: Uint128, diff --git a/contracts/mock-vault/Cargo.toml b/contracts/mock-vault/Cargo.toml index 62b7a470c..6b25c0fbd 100644 --- a/contracts/mock-vault/Cargo.toml +++ b/contracts/mock-vault/Cargo.toml @@ -19,6 +19,4 @@ rover = { version = "1.0", path = "../../packages/rover" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" cw-storage-plus = "0.15" -schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index 0bebc31ba..12ec69bab 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -10,7 +10,7 @@ use rover::msg::vault::{ExecuteMsg, QueryMsg}; use crate::deposit::deposit; use crate::error::ContractError; use crate::msg::InstantiateMsg; -use crate::query::{query_coins_for_shares, query_vault_info}; +use crate::query::{query_coins_for_shares, query_vault_coins_issued, query_vault_info}; use crate::state::{ASSETS, CHAIN_BANK, LOCKUP_TIME, LP_TOKEN_DENOM, ORACLE}; use crate::withdraw::{withdraw, withdraw_force}; @@ -55,8 +55,9 @@ pub fn execute( pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Info {} => to_binary(&query_vault_info(deps)?), - QueryMsg::PreviewRedeem { shares } => { - to_binary(&query_coins_for_shares(deps.storage, shares)?) + QueryMsg::PreviewRedeem { amount } => { + to_binary(&query_coins_for_shares(deps.storage, amount)?) } + QueryMsg::TotalVaultCoinsIssued {} => to_binary(&query_vault_coins_issued(deps.storage)?), } } diff --git a/contracts/mock-vault/src/msg.rs b/contracts/mock-vault/src/msg.rs index 97b4aa4ca..24485c76d 100644 --- a/contracts/mock-vault/src/msg.rs +++ b/contracts/mock-vault/src/msg.rs @@ -1,10 +1,9 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use cosmwasm_schema::cw_serde; use rover::adapters::OracleUnchecked; // Remaining messages in packages/rover/msg/vault -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[cw_serde] pub struct InstantiateMsg { /// Denom for vault LP share token pub lp_token_denom: String, diff --git a/contracts/mock-vault/src/query.rs b/contracts/mock-vault/src/query.rs index 2f1056743..4a02b2ab7 100644 --- a/contracts/mock-vault/src/query.rs +++ b/contracts/mock-vault/src/query.rs @@ -39,3 +39,7 @@ pub fn get_all_vault_coins(storage: &dyn Storage) -> StdResult> { }) .collect() } + +pub fn query_vault_coins_issued(storage: &dyn Storage) -> StdResult { + TOTAL_VAULT_SHARES.load(storage) +} diff --git a/contracts/swapper/mock/Cargo.toml b/contracts/swapper/mock/Cargo.toml index 45689864a..b1e8f762f 100644 --- a/contracts/swapper/mock/Cargo.toml +++ b/contracts/swapper/mock/Cargo.toml @@ -19,8 +19,6 @@ swapper-base = { path = "../base", version = "1.0" } cosmwasm-std = "1.1" cw-storage-plus = "0.15" -schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" [dev-dependencies] diff --git a/contracts/swapper/mock/src/lib.rs b/contracts/swapper/mock/src/lib.rs index cd99a86b5..2943dbb50 100644 --- a/contracts/swapper/mock/src/lib.rs +++ b/contracts/swapper/mock/src/lib.rs @@ -1,3 +1 @@ -extern crate core; - pub mod contract; diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index 966f24ac7..4cc49c8c7 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -17,11 +17,11 @@ library = [] rover = { version = "1.0", path = "../../../packages/rover" } swapper-base = { path = "../base", version = "1.0" } +cosmwasm-schema = "1.1" cosmwasm-std = "1.1" cw-storage-plus = "0.15" osmo-bindings = "0.5" schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" [dev-dependencies] diff --git a/contracts/swapper/osmosis/src/route.rs b/contracts/swapper/osmosis/src/route.rs index a2151880f..851fb8934 100644 --- a/contracts/swapper/osmosis/src/route.rs +++ b/contracts/swapper/osmosis/src/route.rs @@ -1,6 +1,7 @@ use std::fmt; use std::ops::Sub; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{ to_binary, Addr, Coin, CosmosMsg, Decimal, Deps, Env, QuerierWrapper, QueryRequest, WasmQuery, }; @@ -8,15 +9,13 @@ use osmo_bindings::{ EstimatePriceResponse as OsmoResponse, OsmosisMsg, OsmosisQuery, PoolStateResponse, Step, Swap, SwapAmount, SwapAmountWithLimit, }; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - use rover::adapters::swap::{EstimateExactInSwapResponse, QueryMsg}; +use rover::traits::IntoDecimal; use swapper_base::{ContractError, ContractResult, Route}; use crate::helpers::{hashset, GetValue, IntoUint128}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[cw_serde] pub struct OsmosisRoute { pub steps: Vec, } @@ -122,12 +121,11 @@ impl Route for OsmosisRoute { })?, }))?; - let swap_estimate_dec = Decimal::from_atomics(res.amount, 0)?; let swap_amount_with_slippage = SwapAmountWithLimit::ExactIn { input: coin_in.amount, min_output: Decimal::one() .sub(slippage) - .checked_mul(swap_estimate_dec)? + .checked_mul(res.amount.to_dec()?)? .uint128(), }; diff --git a/contracts/swapper/osmosis/tests/test_swap.rs b/contracts/swapper/osmosis/tests/test_swap.rs index 761e04b85..6cedd1c6e 100644 --- a/contracts/swapper/osmosis/tests/test_swap.rs +++ b/contracts/swapper/osmosis/tests/test_swap.rs @@ -5,6 +5,7 @@ use osmo_bindings_test::{OsmosisApp, OsmosisError, Pool}; use rover::adapters::swap::ExecuteMsg; use rover::error::ContractError as RoverError; +use rover::traits::IntoDecimal; use swapper_base::ContractError; use swapper_base::Route; use swapper_osmosis::route::OsmosisRoute; @@ -112,7 +113,7 @@ fn test_swap_exact_in_slippage_too_high() { &ExecuteMsg::::SwapExactIn { coin_in: coin(1_000_000, "mars"), denom_out: "osmo".to_string(), - slippage: Decimal::from_atomics(1u128, 0).unwrap(), + slippage: 1.to_dec().unwrap(), }, &[coin(1_000_000, "mars")], ) diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index df3651053..d2e99badb 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -18,6 +18,4 @@ mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features cosmwasm-schema = "1.1" cosmwasm-std = "1.1" cw-storage-plus = "0.15" -schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs index fbd0765cb..c16809006 100644 --- a/packages/rover/src/adapters/oracle.rs +++ b/packages/rover/src/adapters/oracle.rs @@ -1,13 +1,15 @@ +use std::ops::Add; + +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, Coin, Decimal, QuerierWrapper, StdResult}; use mars_outpost::oracle::PriceResponse; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::ops::Add; -use crate::error::ContractResult; use mock_oracle::msg::QueryMsg; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +use crate::error::ContractResult; +use crate::traits::IntoDecimal; + +#[cw_serde] pub struct OracleBase(T); impl OracleBase { @@ -54,8 +56,7 @@ impl Oracle { .iter() .map(|coin| { let res = self.query_price(querier, &coin.denom)?; - let asset_amount_dec = Decimal::from_atomics(coin.amount, 0)?; - Ok(res.price.checked_mul(asset_amount_dec)?) + Ok(res.price.checked_mul(coin.amount.to_dec()?)?) }) .collect::>>()? .iter() diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index f303da803..8d582c7aa 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -1,13 +1,12 @@ +use cosmwasm_schema::cw_serde; use cosmwasm_std::{ to_binary, Addr, Api, Coin, CosmosMsg, QuerierWrapper, QueryRequest, StdResult, Uint128, WasmMsg, WasmQuery, }; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use mock_red_bank::msg::{ExecuteMsg, QueryMsg, UserAssetDebtResponse}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct RedBankBase(T); impl RedBankBase { diff --git a/packages/rover/src/adapters/swap/msgs.rs b/packages/rover/src/adapters/swap/msgs.rs index f973fbb1a..4f9727661 100644 --- a/packages/rover/src/adapters/swap/msgs.rs +++ b/packages/rover/src/adapters/swap/msgs.rs @@ -1,9 +1,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct Config { /// The contract's owner, who can update config pub owner: T, @@ -11,8 +9,7 @@ pub struct Config { pub type InstantiateMsg = Config; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { /// Update contract config UpdateConfig { owner: Option }, @@ -60,7 +57,7 @@ pub enum QueryMsg { EstimateExactInSwap { coin_in: Coin, denom_out: String }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct RouteResponse { pub denom_in: String, pub denom_out: String, @@ -69,7 +66,7 @@ pub struct RouteResponse { pub type RoutesResponse = Vec>; -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[cw_serde] pub struct EstimateExactInSwapResponse { pub amount: Uint128, } diff --git a/packages/rover/src/adapters/swap/swapper.rs b/packages/rover/src/adapters/swap/swapper.rs index 03c7e9c60..0b77e82c9 100644 --- a/packages/rover/src/adapters/swap/swapper.rs +++ b/packages/rover/src/adapters/swap/swapper.rs @@ -1,10 +1,9 @@ +use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Api, Coin, CosmosMsg, Decimal, Empty, StdResult, WasmMsg}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use crate::adapters::swap::ExecuteMsg; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct SwapperBase(T); impl SwapperBase { diff --git a/packages/rover/src/adapters/vault.rs b/packages/rover/src/adapters/vault.rs index ab76bf808..37bc7ebba 100644 --- a/packages/rover/src/adapters/vault.rs +++ b/packages/rover/src/adapters/vault.rs @@ -1,32 +1,41 @@ +use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, Decimal, QuerierWrapper, - QueryRequest, StdResult, Uint128, WasmMsg, WasmQuery, + to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, Decimal, OverflowError, + QuerierWrapper, QueryRequest, StdResult, Uint128, WasmMsg, WasmQuery, }; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use crate::adapters::Oracle; use crate::error::ContractResult; -use crate::extensions::Stringify; use crate::msg::vault::{ExecuteMsg, QueryMsg, VaultInfo}; +use crate::traits::Stringify; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)] -#[serde(rename_all = "snake_case")] -pub struct VaultPosition { +#[cw_serde] +#[derive(Default)] +pub struct VaultPositionState { pub unlocked: Uint128, pub locked: Uint128, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct VaultBase(T); - -impl VaultBase { - pub fn new(address: T) -> VaultBase { - VaultBase(address) +impl VaultPositionState { + pub fn total(&self) -> Result { + self.locked.checked_add(self.unlocked) } +} + +#[cw_serde] +pub struct VaultPosition { + pub vault: Vault, + pub state: VaultPositionState, +} + +#[cw_serde] +pub struct VaultBase { + pub address: T, +} - pub fn address(&self) -> &T { - &self.0 +impl VaultBase { + pub fn new(address: T) -> Self { + Self { address } } } @@ -35,26 +44,30 @@ pub type Vault = VaultBase; impl From<&Vault> for VaultUnchecked { fn from(vault: &Vault) -> Self { - Self(vault.address().to_string()) + Self { + address: vault.address.to_string(), + } } } impl VaultUnchecked { pub fn check(&self, api: &dyn Api) -> StdResult { - Ok(VaultBase(api.addr_validate(&self.0)?)) + Ok(VaultBase::new(api.addr_validate(&self.address)?)) } } impl From for VaultUnchecked { fn from(v: Vault) -> Self { - Self(v.0.to_string()) + Self { + address: v.address.to_string(), + } } } impl Stringify for Vec { fn to_string(&self) -> String { self.iter() - .map(|v| v.address().clone()) + .map(|v| v.address.clone()) .collect::>() .join(", ") } @@ -63,7 +76,7 @@ impl Stringify for Vec { impl Vault { pub fn deposit_msg(&self, funds: &[Coin]) -> StdResult { let deposit_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: self.address().to_string(), + contract_addr: self.address.to_string(), funds: funds.to_vec(), msg: to_binary(&ExecuteMsg::Deposit {})?, }); @@ -78,7 +91,7 @@ impl Vault { ) -> StdResult { let vault_info = self.query_vault_info(querier)?; let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: self.address().to_string(), + contract_addr: self.address.to_string(), funds: vec![Coin { denom: vault_info.token_denom, amount, @@ -96,7 +109,7 @@ impl Vault { pub fn query_vault_info(&self, querier: &QuerierWrapper) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: self.0.to_string(), + contract_addr: self.address.to_string(), msg: to_binary(&QueryMsg::Info {})?, })) } @@ -117,19 +130,25 @@ impl Vault { addr: &Addr, ) -> ContractResult { let balance = self.query_balance(querier, addr)?; - let assets = self.query_redeem_preview(querier, balance)?; + let assets = self.query_preview_redeem(querier, balance)?; oracle.query_total_value(querier, &assets) } - pub fn query_redeem_preview( + pub fn query_preview_redeem( &self, querier: &QuerierWrapper, - shares: Uint128, + amount: Uint128, ) -> StdResult> { - let response: Vec = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: self.0.to_string(), - msg: to_binary(&QueryMsg::PreviewRedeem { shares })?, - }))?; - Ok(response) + querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.address.to_string(), + msg: to_binary(&QueryMsg::PreviewRedeem { amount })?, + })) + } + + pub fn query_total_vault_coins_issued(&self, querier: &QuerierWrapper) -> StdResult { + querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.address.to_string(), + msg: to_binary(&QueryMsg::TotalVaultCoinsIssued {})?, + })) } } diff --git a/packages/rover/src/coins.rs b/packages/rover/src/coins.rs index 7bc860236..a2beae73d 100644 --- a/packages/rover/src/coins.rs +++ b/packages/rover/src/coins.rs @@ -1,15 +1,14 @@ use std::collections::BTreeMap; use std::fmt; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, StdError, StdResult, Uint128}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use crate::extensions::Stringify; +use crate::traits::{Denoms, Stringify}; /// Pending integration into cosmwasm_std: https://github.com/CosmWasm/cosmwasm/issues/1377#issuecomment-1204232193 /// Copying from here: https://github.com/mars-protocol/cw-coins/blob/main/src/lib.rs -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct Coins(pub BTreeMap); impl From> for Coins { @@ -37,6 +36,12 @@ impl Stringify for &[Coin] { } } +impl Denoms for Vec { + fn to_denoms(&self) -> Vec<&str> { + self.iter().map(|c| c.denom.as_str()).collect() + } +} + impl Coins { pub fn is_empty(&self) -> bool { self.0.is_empty() diff --git a/packages/rover/src/extensions/mod.rs b/packages/rover/src/extensions/mod.rs deleted file mode 100644 index f424c4f61..000000000 --- a/packages/rover/src/extensions/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub trait Stringify { - fn to_string(&self) -> String; -} diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index 833a26332..a0724aedf 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -1,5 +1,5 @@ pub mod adapters; pub mod coins; pub mod error; -pub mod extensions; pub mod msg; +pub mod traits; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 1e88c7f28..7078eee88 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,12 +1,10 @@ +use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use crate::adapters::{Vault, VaultUnchecked}; use crate::msg::instantiate::ConfigUpdates; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { //-------------------------------------------------------------------------------------------------- // Public messages @@ -29,8 +27,7 @@ pub enum ExecuteMsg { } /// The list of actions that users can perform on their positions -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum Action { /// Deposit coin of specified denom and amount. Verifies if the correct amount is sent with transaction. Deposit(Coin), @@ -76,8 +73,7 @@ pub enum Action { } /// Internal actions made by the contract with pre-validated inputs -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum CallbackMsg { /// Withdraw specified amount of coin from credit account; /// Decrement the token's asset amount; @@ -101,7 +97,7 @@ pub enum CallbackMsg { vault: Vault, coins: Vec, }, - /// Used to update the account balance of vault coins after a deposit + /// Used to update the account balance of vault coins after a vault action has taken place UpdateVaultCoinBalance { vault: Vault, /// Account that needs vault coin balance adjustment diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index b3bd5c315..5c674bd15 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -1,11 +1,10 @@ +use cosmwasm_schema::cw_serde; use cosmwasm_std::Decimal; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use crate::adapters::swap::SwapperUnchecked; use crate::adapters::{OracleUnchecked, RedBankUnchecked, VaultUnchecked}; -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[cw_serde] pub struct InstantiateMsg { /// The address with privileged access to update config pub owner: String, @@ -26,7 +25,8 @@ pub struct InstantiateMsg { } /// Used when you want to update fields on Instantiate config -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)] +#[cw_serde] +#[derive(Default)] pub struct ConfigUpdates { pub account_nft: Option, pub owner: Option, diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index e32850d8f..8c74a290c 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -1,10 +1,9 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Decimal, Uint128}; use mars_health::health::Health; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use crate::adapters::{VaultPosition, VaultUnchecked}; +use crate::adapters::{Vault, VaultPosition, VaultUnchecked}; +use crate::traits::Coins; #[cw_serde] #[derive(QueryResponses)] @@ -25,7 +24,7 @@ pub enum QueryMsg { limit: Option, }, /// All positions represented by token with value - #[returns(PositionsWithValueResponse)] + #[returns(Positions)] Positions { account_id: String }, /// The health of the account represented by token #[returns(HealthResponse)] @@ -68,45 +67,47 @@ pub enum QueryMsg { }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct CoinBalanceResponseItem { pub account_id: String, pub denom: String, pub amount: Uint128, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct SharesResponseItem { pub account_id: String, pub denom: String, pub shares: Uint128, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct DebtShares { pub denom: String, pub shares: Uint128, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct DebtSharesValue { +#[cw_serde] +pub struct DebtAmount { pub denom: String, /// number of shares in debt pool pub shares: Uint128, /// amount of coins pub amount: Uint128, - /// price per coin - pub price: Decimal, - /// price * amount - pub value: Decimal, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +impl Coins for Vec { + fn to_coins(&self) -> Vec { + self.iter() + .map(|d| Coin { + denom: d.denom.to_string(), + amount: d.amount, + }) + .collect() + } +} + +#[cw_serde] pub struct CoinValue { pub denom: String, pub amount: Uint128, @@ -114,53 +115,33 @@ pub struct CoinValue { pub value: Decimal, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct Positions { pub account_id: String, pub coins: Vec, - pub debt: Vec, - pub vault_positions: Vec, + pub debts: Vec, + pub vaults: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct VaultPositionResponseItem { pub account_id: String, - pub addr: String, - pub vault_position: VaultPosition, + pub position: VaultPosition, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct VaultWithBalance { - pub vault: VaultUnchecked, + pub vault: Vault, pub balance: Uint128, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct VaultPositionWithAddr { - pub addr: String, +#[cw_serde] +pub struct VaultPositionValue { pub position: VaultPosition, + pub value: Decimal, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct PositionsWithValueResponse { - /// Unique NFT token id that represents the cross-margin account. The owner of this NFT, owns the account. - pub account_id: String, - /// All coin balances value - pub coins: Vec, - /// All debt positions with value - pub debt: Vec, - // TODO: After pricing method is complete, add to response - /// All vault positions - pub vault_positions: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct ConfigResponse { pub owner: String, pub account_nft: Option, @@ -171,8 +152,7 @@ pub struct ConfigResponse { pub swapper: String, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct HealthResponse { pub total_debt_value: Decimal, pub total_collateral_value: Decimal, diff --git a/packages/rover/src/msg/vault.rs b/packages/rover/src/msg/vault.rs index 2a4de2111..7367981e6 100644 --- a/packages/rover/src/msg/vault.rs +++ b/packages/rover/src/msg/vault.rs @@ -1,11 +1,8 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Uint128}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; /// Partial compatibility with EIP-4626 -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { /// Enters list of `Vec` into a vault strategy in exchange for vault tokens. Deposit {}, @@ -25,11 +22,14 @@ pub enum QueryMsg { /// All the coins that would be redeemed for in exchange for /// vault coins. Used by Rover to calculate vault position values. #[returns(Vec)] - PreviewRedeem { shares: Uint128 }, + PreviewRedeem { amount: Uint128 }, + /// Returns the total vault coins issued. In order to prevent Cream-attack, we cannot + /// query the bank module for this amount. + #[returns(Uint128)] + TotalVaultCoinsIssued {}, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct VaultInfo { /// Coins required to enter vault. /// Amount will be proportional to the share of which it should occupy in the group diff --git a/packages/rover/src/traits.rs b/packages/rover/src/traits.rs new file mode 100644 index 000000000..ca0b128f4 --- /dev/null +++ b/packages/rover/src/traits.rs @@ -0,0 +1,39 @@ +use cosmwasm_std::{Coin, Decimal, DecimalRangeExceeded, Uint128}; + +pub trait Stringify { + fn to_string(&self) -> String; +} + +pub trait Denoms { + fn to_denoms(&self) -> Vec<&str>; +} + +pub trait Coins { + fn to_coins(&self) -> Vec; +} + +pub trait IntoUint128 { + fn uint128(&self) -> Uint128; +} + +impl IntoUint128 for Decimal { + fn uint128(&self) -> Uint128 { + *self * Uint128::new(1) + } +} + +pub trait IntoDecimal { + fn to_dec(&self) -> Result; +} + +impl IntoDecimal for Uint128 { + fn to_dec(&self) -> Result { + Decimal::from_atomics(*self, 0) + } +} + +impl IntoDecimal for u128 { + fn to_dec(&self) -> Result { + Decimal::from_atomics(*self, 0) + } +} diff --git a/schemas/account-nft/account-nft.json b/schemas/account-nft/account-nft.json index 85d8e7799..69904c087 100644 --- a/schemas/account-nft/account-nft.json +++ b/schemas/account-nft/account-nft.json @@ -47,7 +47,8 @@ "new_owner": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -60,7 +61,8 @@ ], "properties": { "accept_ownership": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -81,7 +83,8 @@ "user": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -106,7 +109,8 @@ "token_id": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -135,7 +139,8 @@ "token_id": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -170,7 +175,8 @@ "token_id": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -195,7 +201,8 @@ "token_id": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -226,7 +233,8 @@ "operator": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -247,7 +255,8 @@ "operator": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -268,7 +277,8 @@ "token_id": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false diff --git a/schemas/credit-manager/credit-manager.json b/schemas/credit-manager/credit-manager.json index e7024abf7..305eb9d43 100644 --- a/schemas/credit-manager/credit-manager.json +++ b/schemas/credit-manager/credit-manager.json @@ -76,6 +76,7 @@ ] } }, + "additionalProperties": false, "definitions": { "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", @@ -91,7 +92,16 @@ "type": "string" }, "VaultBase_for_String": { - "type": "string" + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false } } }, @@ -107,7 +117,8 @@ ], "properties": { "create_credit_account": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -135,7 +146,8 @@ "$ref": "#/definitions/Action" } } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -156,7 +168,8 @@ "new_config": { "$ref": "#/definitions/ConfigUpdates" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -254,7 +267,8 @@ "vault": { "$ref": "#/definitions/VaultBase_for_String" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -279,7 +293,8 @@ "vault": { "$ref": "#/definitions/VaultBase_for_String" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -315,7 +330,8 @@ "description": "The coin they wish to acquire from the liquidatee (amount returned will include the bonus)", "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -344,7 +360,8 @@ "slippage": { "$ref": "#/definitions/Decimal" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -382,7 +399,8 @@ "recipient": { "$ref": "#/definitions/Addr" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -407,7 +425,8 @@ "coin": { "$ref": "#/definitions/Coin" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -432,7 +451,8 @@ "coin": { "$ref": "#/definitions/Coin" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -453,7 +473,8 @@ "account_id": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -485,13 +506,14 @@ "vault": { "$ref": "#/definitions/VaultBase_for_Addr" } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, { - "description": "Used to update the account balance of vault coins after a deposit", + "description": "Used to update the account balance of vault coins after a vault action has taken place", "type": "object", "required": [ "update_vault_coin_balance" @@ -520,7 +542,8 @@ "vault": { "$ref": "#/definitions/VaultBase_for_Addr" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -549,7 +572,8 @@ "vault": { "$ref": "#/definitions/VaultBase_for_Addr" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -578,7 +602,8 @@ "vault": { "$ref": "#/definitions/VaultBase_for_Addr" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -611,7 +636,8 @@ "request_coin_denom": { "type": "string" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -636,7 +662,8 @@ "previous_health_factor": { "$ref": "#/definitions/Decimal" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -669,7 +696,8 @@ "slippage": { "$ref": "#/definitions/Decimal" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -699,7 +727,8 @@ "$ref": "#/definitions/Coin" } } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -805,7 +834,8 @@ } ] } - } + }, + "additionalProperties": false }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", @@ -825,10 +855,28 @@ "type": "string" }, "VaultBase_for_Addr": { - "$ref": "#/definitions/Addr" + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false }, "VaultBase_for_String": { - "type": "string" + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false } } }, @@ -1180,7 +1228,16 @@ ], "definitions": { "VaultBase_for_String": { - "type": "string" + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false } } }, @@ -1212,7 +1269,8 @@ "denom": { "type": "string" } - } + }, + "additionalProperties": false }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", @@ -1245,7 +1303,8 @@ "shares": { "$ref": "#/definitions/Uint128" } - } + }, + "additionalProperties": false }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", @@ -1274,7 +1333,8 @@ "shares": { "$ref": "#/definitions/Uint128" } - } + }, + "additionalProperties": false }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", @@ -1290,12 +1350,25 @@ "$ref": "#/definitions/VaultWithBalance" }, "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" + }, "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" }, - "VaultBase_for_String": { - "type": "string" + "VaultBase_for_Addr": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false }, "VaultWithBalance": { "type": "object", @@ -1308,9 +1381,10 @@ "$ref": "#/definitions/Uint128" }, "vault": { - "$ref": "#/definitions/VaultBase_for_String" + "$ref": "#/definitions/VaultBase_for_Addr" } - } + }, + "additionalProperties": false } } }, @@ -1322,43 +1396,73 @@ "$ref": "#/definitions/VaultPositionResponseItem" }, "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" + }, "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" }, + "VaultBase_for_Addr": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + }, "VaultPosition": { "type": "object", "required": [ - "locked", - "unlocked" + "state", + "vault" ], "properties": { - "locked": { - "$ref": "#/definitions/Uint128" + "state": { + "$ref": "#/definitions/VaultPositionState" }, - "unlocked": { - "$ref": "#/definitions/Uint128" + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" } - } + }, + "additionalProperties": false }, "VaultPositionResponseItem": { "type": "object", "required": [ "account_id", - "addr", - "vault_position" + "position" ], "properties": { "account_id": { "type": "string" }, - "addr": { - "type": "string" - }, - "vault_position": { + "position": { "$ref": "#/definitions/VaultPosition" } - } + }, + "additionalProperties": false + }, + "VaultPositionState": { + "type": "object", + "required": [ + "locked", + "unlocked" + ], + "properties": { + "locked": { + "$ref": "#/definitions/Uint128" + }, + "unlocked": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false } } }, @@ -1379,7 +1483,16 @@ }, "definitions": { "VaultBase_for_String": { - "type": "string" + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false } } }, @@ -1421,6 +1534,7 @@ "type": "string" } }, + "additionalProperties": false, "definitions": { "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", @@ -1480,6 +1594,7 @@ "$ref": "#/definitions/Decimal" } }, + "additionalProperties": false, "definitions": { "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", @@ -1489,49 +1604,48 @@ }, "positions": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PositionsWithValueResponse", + "title": "Positions", "type": "object", "required": [ "account_id", "coins", - "debt", - "vault_positions" + "debts", + "vaults" ], "properties": { "account_id": { - "description": "Unique NFT token id that represents the cross-margin account. The owner of this NFT, owns the account.", "type": "string" }, "coins": { - "description": "All coin balances value", "type": "array", "items": { - "$ref": "#/definitions/CoinValue" + "$ref": "#/definitions/Coin" } }, - "debt": { - "description": "All debt positions with value", + "debts": { "type": "array", "items": { - "$ref": "#/definitions/DebtSharesValue" + "$ref": "#/definitions/DebtAmount" } }, - "vault_positions": { - "description": "All vault positions", + "vaults": { "type": "array", "items": { - "$ref": "#/definitions/VaultPositionWithAddr" + "$ref": "#/definitions/VaultPosition" } } }, + "additionalProperties": false, "definitions": { - "CoinValue": { + "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" + }, + "Coin": { "type": "object", "required": [ "amount", - "denom", - "price", - "value" + "denom" ], "properties": { "amount": { @@ -1539,23 +1653,15 @@ }, "denom": { "type": "string" - }, - "price": { - "$ref": "#/definitions/Decimal" - }, - "value": { - "$ref": "#/definitions/Decimal" } } }, - "DebtSharesValue": { + "DebtAmount": { "type": "object", "required": [ "amount", "denom", - "price", - "shares", - "value" + "shares" ], "properties": { "amount": { @@ -1569,14 +1675,6 @@ "denom": { "type": "string" }, - "price": { - "description": "price per coin", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, "shares": { "description": "number of shares in debt pool", "allOf": [ @@ -1584,54 +1682,57 @@ "$ref": "#/definitions/Uint128" } ] - }, - "value": { - "description": "price * amount", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] } - } - }, - "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" + }, + "additionalProperties": false }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" }, + "VaultBase_for_Addr": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + }, "VaultPosition": { "type": "object", "required": [ - "locked", - "unlocked" + "state", + "vault" ], "properties": { - "locked": { - "$ref": "#/definitions/Uint128" + "state": { + "$ref": "#/definitions/VaultPositionState" }, - "unlocked": { - "$ref": "#/definitions/Uint128" + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" } - } + }, + "additionalProperties": false }, - "VaultPositionWithAddr": { + "VaultPositionState": { "type": "object", "required": [ - "addr", - "position" + "locked", + "unlocked" ], "properties": { - "addr": { - "type": "string" + "locked": { + "$ref": "#/definitions/Uint128" }, - "position": { - "$ref": "#/definitions/VaultPosition" + "unlocked": { + "$ref": "#/definitions/Uint128" } - } + }, + "additionalProperties": false } } }, @@ -1651,6 +1752,7 @@ "$ref": "#/definitions/Uint128" } }, + "additionalProperties": false, "definitions": { "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", diff --git a/schemas/mars-oracle-adapter/mars-oracle-adapter.json b/schemas/mars-oracle-adapter/mars-oracle-adapter.json new file mode 100644 index 000000000..0d2e74887 --- /dev/null +++ b/schemas/mars-oracle-adapter/mars-oracle-adapter.json @@ -0,0 +1,376 @@ +{ + "contract_name": "mars-oracle-adapter", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "oracle", + "owner", + "vault_pricing" + ], + "properties": { + "oracle": { + "$ref": "#/definitions/OracleBase_for_String" + }, + "owner": { + "type": "string" + }, + "vault_pricing": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultPricingInfo" + } + } + }, + "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" + }, + "OracleBase_for_String": { + "type": "string" + }, + "PricingMethod": { + "type": "string", + "enum": [ + "preview_redeem" + ] + }, + "VaultPricingInfo": { + "type": "object", + "required": [ + "addr", + "denom", + "method" + ], + "properties": { + "addr": { + "$ref": "#/definitions/Addr" + }, + "denom": { + "type": "string" + }, + "method": { + "$ref": "#/definitions/PricingMethod" + } + }, + "additionalProperties": false + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "new_config" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/ConfigUpdates" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ConfigUpdates": { + "type": "object", + "properties": { + "oracle": { + "anyOf": [ + { + "$ref": "#/definitions/OracleBase_for_String" + }, + { + "type": "null" + } + ] + }, + "owner": { + "type": [ + "string", + "null" + ] + }, + "vault_pricing": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/VaultPricingInfo" + } + } + }, + "additionalProperties": false + }, + "OracleBase_for_String": { + "type": "string" + }, + "PricingMethod": { + "type": "string", + "enum": [ + "preview_redeem" + ] + }, + "VaultPricingInfo": { + "type": "object", + "required": [ + "addr", + "denom", + "method" + ], + "properties": { + "addr": { + "$ref": "#/definitions/Addr" + }, + "denom": { + "type": "string" + }, + "method": { + "$ref": "#/definitions/PricingMethod" + } + }, + "additionalProperties": false + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "price" + ], + "properties": { + "price": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pricing_info" + ], + "properties": { + "pricing_info": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "all_pricing_info" + ], + "properties": { + "all_pricing_info": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "all_pricing_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_VaultPricingInfo", + "type": "array", + "items": { + "$ref": "#/definitions/VaultPricingInfo" + }, + "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" + }, + "PricingMethod": { + "type": "string", + "enum": [ + "preview_redeem" + ] + }, + "VaultPricingInfo": { + "type": "object", + "required": [ + "addr", + "denom", + "method" + ], + "properties": { + "addr": { + "$ref": "#/definitions/Addr" + }, + "denom": { + "type": "string" + }, + "method": { + "$ref": "#/definitions/PricingMethod" + } + }, + "additionalProperties": false + } + } + }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "type": "object", + "required": [ + "oracle", + "owner" + ], + "properties": { + "oracle": { + "$ref": "#/definitions/OracleBase_for_Addr" + }, + "owner": { + "$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" + }, + "OracleBase_for_Addr": { + "$ref": "#/definitions/Addr" + } + } + }, + "price": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PriceResponse", + "type": "object", + "required": [ + "denom", + "price" + ], + "properties": { + "denom": { + "type": "string" + }, + "price": { + "$ref": "#/definitions/Decimal" + } + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "pricing_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VaultPricingInfo", + "type": "object", + "required": [ + "addr", + "denom", + "method" + ], + "properties": { + "addr": { + "$ref": "#/definitions/Addr" + }, + "denom": { + "type": "string" + }, + "method": { + "$ref": "#/definitions/PricingMethod" + } + }, + "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" + }, + "PricingMethod": { + "type": "string", + "enum": [ + "preview_redeem" + ] + } + } + } + } +} diff --git a/schemas/mock-oracle/mock-oracle.json b/schemas/mock-oracle/mock-oracle.json index e36af95ec..94f37c950 100644 --- a/schemas/mock-oracle/mock-oracle.json +++ b/schemas/mock-oracle/mock-oracle.json @@ -17,6 +17,7 @@ } } }, + "additionalProperties": false, "definitions": { "CoinPrice": { "type": "object", @@ -31,7 +32,8 @@ "price": { "$ref": "#/definitions/Decimal" } - } + }, + "additionalProperties": false }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", @@ -70,7 +72,8 @@ "price": { "$ref": "#/definitions/Decimal" } - } + }, + "additionalProperties": false }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", diff --git a/schemas/mock-red-bank/mock-red-bank.json b/schemas/mock-red-bank/mock-red-bank.json index 81899bdd6..c0f3ea9ea 100644 --- a/schemas/mock-red-bank/mock-red-bank.json +++ b/schemas/mock-red-bank/mock-red-bank.json @@ -17,6 +17,7 @@ } } }, + "additionalProperties": false, "definitions": { "CoinMarketInfo": { "type": "object", @@ -35,7 +36,8 @@ "max_ltv": { "$ref": "#/definitions/Decimal" } - } + }, + "additionalProperties": false }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", @@ -68,7 +70,8 @@ "null" ] } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -94,7 +97,8 @@ "null" ] } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -385,6 +389,7 @@ "type": "string" } }, + "additionalProperties": false, "definitions": { "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", diff --git a/schemas/mock-vault/mock-vault.json b/schemas/mock-vault/mock-vault.json index 9a02ff125..9ac0f12e6 100644 --- a/schemas/mock-vault/mock-vault.json +++ b/schemas/mock-vault/mock-vault.json @@ -36,6 +36,7 @@ "$ref": "#/definitions/OracleBase_for_String" } }, + "additionalProperties": false, "definitions": { "OracleBase_for_String": { "type": "string" @@ -55,7 +56,8 @@ ], "properties": { "deposit": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -68,7 +70,8 @@ ], "properties": { "withdraw": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -81,7 +84,8 @@ ], "properties": { "force_withdraw": { - "type": "object" + "type": "object", + "additionalProperties": false } }, "additionalProperties": false @@ -116,10 +120,10 @@ "preview_redeem": { "type": "object", "required": [ - "shares" + "amount" ], "properties": { - "shares": { + "amount": { "$ref": "#/definitions/Uint128" } }, @@ -127,6 +131,20 @@ } }, "additionalProperties": false + }, + { + "description": "Returns the total vault coins issued. In order to prevent Cream-attack, we cannot query the bank module for this amount.", + "type": "object", + "required": [ + "total_vault_coins_issued" + ], + "properties": { + "total_vault_coins_issued": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -169,6 +187,7 @@ "type": "string" } }, + "additionalProperties": false, "definitions": { "Coin": { "type": "object", @@ -219,6 +238,12 @@ "type": "string" } } + }, + "total_vault_coins_issued": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" } } } diff --git a/schemas/swapper-base/swapper-base.json b/schemas/swapper-base/swapper-base.json index 04491e88a..98cf1f3b8 100644 --- a/schemas/swapper-base/swapper-base.json +++ b/schemas/swapper-base/swapper-base.json @@ -14,7 +14,8 @@ "description": "The contract's owner, who can update config", "type": "string" } - } + }, + "additionalProperties": false }, "execute": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -36,7 +37,8 @@ "null" ] } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -65,7 +67,8 @@ "route": { "$ref": "#/definitions/Empty" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -94,7 +97,8 @@ "slippage": { "$ref": "#/definitions/Decimal" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -123,7 +127,8 @@ "recipient": { "$ref": "#/definitions/Addr" } - } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -311,7 +316,8 @@ "description": "The contract's owner, who can update config", "type": "string" } - } + }, + "additionalProperties": false }, "estimate_exact_in_swap": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -325,6 +331,7 @@ "$ref": "#/definitions/Uint128" } }, + "additionalProperties": false, "definitions": { "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", @@ -352,6 +359,7 @@ "$ref": "#/definitions/Empty" } }, + "additionalProperties": false, "definitions": { "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)", @@ -388,7 +396,8 @@ "route": { "$ref": "#/definitions/Empty" } - } + }, + "additionalProperties": false } } } diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 28bc780fe..5a15b73e3 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,8 +1,7 @@ { - "accountNft": "osmo16v3mvsdnkh4c6ykc885n3x5ay9e36akdzxcl2g93698rqw007xxqesld8w", - "mockRedBank": "osmo1xrnx0q3x7kwzss53fry0dwwsc7pff6aq628l6n0rmvegkalp4y7qzl7j7z", - "mockOracle": "osmo1r9u2tfq8n5xpn2g0fq8ha0rj0cyp2fzr5w9jvcqwt3r8lxdfm6yszmtza5", - "mockVault": "osmo1gg4rpug7vwrnq0ask0k7nmw23z6wl8c8fr7jmup9pdpaal9uc5nqq7lyrm", - "swapper": "osmo1ak4x8k2h7s6pq5dnlncmgsmx2nqcaplpfxlmklx2ln7qn6dtny8q70apjv", - "creditManager": "osmo1963xgmt8agyc6q4k2vhf980kffq6ukkj9mgtwdxxnpj3dak2akdq20z9dw" + "accountNft": "osmo1m62qxx06tqg39azy6jqwdvd06vaju93l6e9hpr2jnyp74ewhd3lsg7ycq2", + "mockVault": "osmo176mztwavcndw3chc34ke78pyv7kun9wf6jcqeps6wppz9ksrcdrszcqfed", + "marsOracleAdapter": "osmo10yjzjhtzqeyc02xmkzdt274huvkf3nwvqxereyhay6au9uglgt0sqhn44r", + "swapper": "osmo1h4u2ku8wl8v6zqhfmmd8pnt5ap45j3yx8whtjtkuff75l3lx6yfsx3re60", + "creditManager": "osmo1dgsfqcjyzfvau03ucvu0e38fyauuppfdlrew3ddalahxrhdme73s2dcqvm" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 6f7a233a5..ec6c8c2cb 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -5,10 +5,9 @@ import { ARTIFACTS_PATH, Storage } from './storage' import fs from 'fs' import { InstantiateMsgs } from '../../types/instantiateMsgs' import { InstantiateMsg as NftInstantiateMsg } from '../../types/generated/account-nft/AccountNft.types' -import { InstantiateMsg as RedBankInstantiateMsg } from '../../types/generated/mock-red-bank/MockRedBank.types' import { InstantiateMsg as VaultInstantiateMsg } from '../../types/generated/mock-vault/MockVault.types' import { InstantiateMsg as SwapperInstantiateMsg } from '../../types/generated/swapper-base/SwapperBase.types' -import { InstantiateMsg as OracleInstantiateMsg } from '../../types/generated/mock-oracle/MockOracle.types' +import { InstantiateMsg as OracleAdapterInstantiateMsg } from '../../types/generated/mars-oracle-adapter/MarsOracleAdapter.types' import { InstantiateMsg as RoverInstantiateMsg } from '../../types/generated/credit-manager/CreditManager.types' import { Rover } from './rover' import { AccountNftClient } from '../../types/generated/account-nft/AccountNft.client' @@ -55,7 +54,8 @@ export class Deployer { const { contractAddress } = await this.cwClient.instantiate( this.deployerAddr, codeId, - msg as Record, + // @ts-expect-error expecting generic record + msg, `mars-${name}`, 'auto', ) @@ -74,34 +74,30 @@ export class Deployer { await this.instantiate('accountNft', this.storage.codeIds.accountNft!, msg) } - async instantiateMockRedBank() { - const msg: RedBankInstantiateMsg = { - coins: this.config.mockRedbankCoins, - } - await this.instantiate('mockRedBank', this.storage.codeIds.mockRedBank!, msg) - printBlue(`Seeding funds in RedBank: ${JSON.stringify(this.config.seededFundsForMockRedBank)}`) - await this.transferFunds( - this.storage.addresses.mockRedBank!, - this.config.seededFundsForMockRedBank, - ) - } - - async instantiateMockOracle() { - const msg: OracleInstantiateMsg = { - coins: this.config.oraclePrices, - } - await this.instantiate('mockOracle', this.storage.codeIds.mockOracle!, msg) - } - async instantiateMockVault() { const msg: VaultInstantiateMsg = { asset_denoms: [this.config.baseDenom], - lp_token_denom: 'xCompounder', - oracle: this.storage.addresses.mockOracle!, + lp_token_denom: this.config.vaultTokenDenom, + oracle: this.config.oracleAddr, } await this.instantiate('mockVault', this.storage.codeIds.mockVault!, msg) } + async instantiateMarsOracleAdapter() { + const msg: OracleAdapterInstantiateMsg = { + oracle: this.config.oracleAddr, + owner: this.deployerAddr, + vault_pricing: [ + { + addr: this.storage.addresses.mockVault!, + denom: this.config.vaultTokenDenom, + method: 'preview_redeem', + }, + ], + } + await this.instantiate('marsOracleAdapter', this.storage.codeIds.marsOracleAdapter!, msg) + } + async instantiateSwapper() { const msg: SwapperInstantiateMsg = { owner: this.deployerAddr, @@ -134,10 +130,10 @@ export class Deployer { async instantiateCreditManager() { const msg: RoverInstantiateMsg = { allowed_coins: [this.config.baseDenom, this.config.secondaryDenom], - allowed_vaults: [this.storage.addresses.mockVault!], - oracle: this.storage.addresses.mockOracle!, + allowed_vaults: [{ address: this.storage.addresses.mockVault! }], + oracle: this.config.oracleAddr, owner: this.deployerAddr, - red_bank: this.storage.addresses.mockRedBank!, + red_bank: this.config.redBankAddr, max_close_factor: this.config.maxCloseFactor.toString(), max_liquidation_bonus: this.config.maxLiquidationBonus.toString(), swapper: this.storage.addresses.swapper!, diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 8f3ebbcd8..8b5a3dd0d 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -13,17 +13,15 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp try { // Upload contracts await deployer.upload('accountNft', wasmFile('account_nft')) - await deployer.upload('mockRedBank', wasmFile('mock_red_bank')) await deployer.upload('mockVault', wasmFile('mock_vault')) - await deployer.upload('mockOracle', wasmFile('mock_oracle')) + await deployer.upload('marsOracleAdapter', wasmFile('mars_oracle_adapter')) await deployer.upload('swapper', wasmFile(swapperContractName)) await deployer.upload('creditManager', wasmFile('credit_manager')) // Instantiate contracts await deployer.instantiateNftContract() - await deployer.instantiateMockRedBank() - await deployer.instantiateMockOracle() await deployer.instantiateMockVault() + await deployer.instantiateMarsOracleAdapter() await deployer.instantiateSwapper() await deployer.instantiateCreditManager() await deployer.transferNftContractOwnership() diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 5c5b2aa7c..3e2c47dbf 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -74,8 +74,8 @@ export class Rover { const amount = this.config.borrowAmount.toString() await this.updateCreditAccount([{ borrow: { amount, denom: this.config.baseDenom } }]) const positions = await this.query.positions({ accountId: this.accountId! }) - assert.equal(positions.debt.length, 1) - assert.equal(positions.debt[0].denom, this.config.baseDenom) + assert.equal(positions.debts.length, 1) + assert.equal(positions.debts[0].denom, this.config.baseDenom) printGreen(`Borrowed from RedBank: ${amount} ${this.config.baseDenom}`) } @@ -84,7 +84,7 @@ export class Rover { await this.updateCreditAccount([{ repay: { amount, denom: this.config.baseDenom } }]) const positions = await this.query.positions({ accountId: this.accountId! }) printGreen( - `Repaid to RedBank: ${amount} ${this.config.baseDenom}. Debt remaining: ${positions.debt[0].amount} ${positions.debt[0].denom}`, + `Repaid to RedBank: ${amount} ${this.config.baseDenom}. Debt remaining: ${positions.debts[0].amount} ${positions.debts[0].denom}`, ) } @@ -117,17 +117,18 @@ export class Rover { { vault_deposit: { coins: [{ amount, denom: this.config.baseDenom }], - vault: this.storage.addresses.mockVault!, + vault: { address: this.storage.addresses.mockVault! }, }, }, ]) const positions = await this.query.positions({ accountId: this.accountId! }) - assert.equal(positions.vault_positions.length, 1) - assert.equal(positions.vault_positions[0].addr, this.storage.addresses.mockVault) - assert.equal(positions.vault_positions[0].position, this.config.baseDenom) + assert.equal(positions.vaults.length, 1) + assert.equal(positions.vaults[0].vault.address, this.storage.addresses.mockVault) + assert.equal(positions.vaults[0].state.locked, 123) + assert.equal(positions.vaults[0].state.unlocked, 123) printGreen( `Deposit into vault: ${amount} ${this.config.baseDenom}, Vault Postition: ${JSON.stringify( - positions.vault_positions[0].position, + positions.vaults[0], )}`, ) } diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 6bda2a9b5..c5b081faf 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -1,9 +1,13 @@ import { DeploymentConfig } from '../../types/config' -import { coins } from '@cosmjs/stargate' + +const uatom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2' export const osmosisTestnetConfig: DeploymentConfig = { + // Get the latest addresses from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-4.json + oracleAddr: 'osmo1kgv8rr9eglkv52hwf0v96cs5s7ztw06tx3a6zrrcrwgmuuru36cqgmz2xa', + redBankAddr: 'osmo1dkn4vr75uep4gmd0gatuu7zlapahps7kdapy8wwztcygdu5wy8lqtw2yuj', baseDenom: 'uosmo', - secondaryDenom: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2', // uatom + secondaryDenom: uatom, chainId: 'osmo-test-4', chainPrefix: 'osmo', deployerMnemonic: @@ -11,9 +15,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { rpcEndpoint: 'https://rpc-test.osmosis.zone', defaultGasPrice: 0.1, startingAmountForTestUser: 1e6, - mockRedbankCoins: [{ denom: 'uosmo', max_ltv: '0.8', liquidation_threshold: '0.9' }], - seededFundsForMockRedBank: coins(100, 'uosmo'), - oraclePrices: [{ denom: 'uosmo', price: '12.1' }], + vaultTokenDenom: 'xCompounder', maxCloseFactor: 0.6, maxLiquidationBonus: 0.05, depositAmount: 100, @@ -23,7 +25,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { swapRoute: { steps: [ { - denom_out: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2', + denom_out: uatom, pool_id: 1, }, ], diff --git a/scripts/package.json b/scripts/package.json index 2f88a3904..9e1f5853c 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -15,7 +15,7 @@ "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.29.0", "@cosmjs/stargate": "^0.29.0", - "@cosmwasm/ts-codegen": "^0.16.4", + "@cosmwasm/ts-codegen": "^0.16.5", "chalk": "4.1.2", "cosmjs-types": "^0.5.0", "lodash": "^4.17.21", @@ -23,15 +23,15 @@ "prepend-file": "^2.0.1" }, "devDependencies": { - "@babel/preset-env": "^7.19.1", + "@babel/preset-env": "^7.19.3", "@babel/preset-typescript": "^7.18.6", "@types/jest": "^29.0.3", - "@typescript-eslint/eslint-plugin": "^5.37.0", - "@typescript-eslint/parser": "^5.37.0", - "eslint": "^8.23.1", + "@typescript-eslint/eslint-plugin": "^5.38.1", + "@typescript-eslint/parser": "^5.38.1", + "eslint": "^8.24.0", "eslint-config-prettier": "^8.5.0", "jest": "^29.0.3", "prettier": "^2.7.1", - "typescript": "^4.8.3" + "typescript": "^4.8.4" } } diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 3b9ca977c..10f3c1c88 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -1,13 +1,12 @@ -import { CoinMarketInfo } from './generated/mock-red-bank/MockRedBank.types' -import { Coin } from '@cosmjs/amino' -import { CoinPrice } from './generated/mock-oracle/MockOracle.types' - export interface DeploymentConfig { + oracleAddr: string + redBankAddr: string baseDenom: string secondaryDenom: string chainPrefix: string rpcEndpoint: string deployerMnemonic: string + vaultTokenDenom: string chainId: string defaultGasPrice: number startingAmountForTestUser: number @@ -18,9 +17,6 @@ export interface DeploymentConfig { slippage: number swapRoute: { steps: { denom_out: string; pool_id: number }[] } withdrawAmount: number - mockRedbankCoins: CoinMarketInfo[] - seededFundsForMockRedBank: Coin[] - oraclePrices: CoinPrice[] maxCloseFactor: number maxLiquidationBonus: number } diff --git a/scripts/types/generated/account-nft/AccountNft.client.ts b/scripts/types/generated/account-nft/AccountNft.client.ts index 00c5febd5..82c371e91 100644 --- a/scripts/types/generated/account-nft/AccountNft.client.ts +++ b/scripts/types/generated/account-nft/AccountNft.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/account-nft/AccountNft.react-query.ts b/scripts/types/generated/account-nft/AccountNft.react-query.ts index 8bc775b9a..67eba0187 100644 --- a/scripts/types/generated/account-nft/AccountNft.react-query.ts +++ b/scripts/types/generated/account-nft/AccountNft.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/account-nft/AccountNft.types.ts b/scripts/types/generated/account-nft/AccountNft.types.ts index 066450187..84408b14b 100644 --- a/scripts/types/generated/account-nft/AccountNft.types.ts +++ b/scripts/types/generated/account-nft/AccountNft.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -14,25 +14,20 @@ export type ExecuteMsg = | { propose_new_owner: { new_owner: string - [k: string]: unknown } } | { - accept_ownership: { - [k: string]: unknown - } + accept_ownership: {} } | { mint: { user: string - [k: string]: unknown } } | { transfer_nft: { recipient: string token_id: string - [k: string]: unknown } } | { @@ -40,7 +35,6 @@ export type ExecuteMsg = contract: string msg: Binary token_id: string - [k: string]: unknown } } | { @@ -48,33 +42,28 @@ export type ExecuteMsg = expires?: Expiration | null spender: string token_id: string - [k: string]: unknown } } | { revoke: { spender: string token_id: string - [k: string]: unknown } } | { approve_all: { expires?: Expiration | null operator: string - [k: string]: unknown } } | { revoke_all: { operator: string - [k: string]: unknown } } | { burn: { token_id: string - [k: string]: unknown } } export type Binary = string diff --git a/scripts/types/generated/account-nft/bundle.ts b/scripts/types/generated/account-nft/bundle.ts index 626d38f36..de99f813e 100644 --- a/scripts/types/generated/account-nft/bundle.ts +++ b/scripts/types/generated/account-nft/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/credit-manager/CreditManager.client.ts b/scripts/types/generated/credit-manager/CreditManager.client.ts index 8f00e9d33..9610248c4 100644 --- a/scripts/types/generated/credit-manager/CreditManager.client.ts +++ b/scripts/types/generated/credit-manager/CreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -8,23 +8,20 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { - VaultBaseForString, Decimal, OracleBaseForString, RedBankBaseForString, SwapperBaseForString, InstantiateMsg, + VaultBaseForString, ExecuteMsg, Action, Uint128, CallbackMsg, Addr, - VaultBaseForAddr, - VaultBaseForAddr1, - VaultBaseForAddr2, - VaultBaseForAddr3, Coin, ConfigUpdates, + VaultBaseForAddr, QueryMsg, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, @@ -37,14 +34,13 @@ import { ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, VaultPosition, + VaultPositionState, ArrayOfString, ArrayOfVaultBaseForString, ConfigResponse, HealthResponse, - PositionsWithValueResponse, - CoinValue, - DebtSharesValue, - VaultPositionWithAddr, + Positions, + DebtAmount, } from './CreditManager.types' export interface CreditManagerReadOnlyInterface { contractAddress: string @@ -63,7 +59,7 @@ export interface CreditManagerReadOnlyInterface { limit?: number startAfter?: string }) => Promise - positions: ({ accountId }: { accountId: string }) => Promise + positions: ({ accountId }: { accountId: string }) => Promise health: ({ accountId }: { accountId: string }) => Promise allCoinBalances: ({ limit, @@ -157,7 +153,7 @@ export class CreditManagerQueryClient implements CreditManagerReadOnlyInterface }, }) } - positions = async ({ accountId }: { accountId: string }): Promise => { + positions = async ({ accountId }: { accountId: string }): Promise => { return this.client.queryContractSmart(this.contractAddress, { positions: { account_id: accountId, diff --git a/scripts/types/generated/credit-manager/CreditManager.react-query.ts b/scripts/types/generated/credit-manager/CreditManager.react-query.ts index 99a427680..9d2140d2b 100644 --- a/scripts/types/generated/credit-manager/CreditManager.react-query.ts +++ b/scripts/types/generated/credit-manager/CreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -9,23 +9,20 @@ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tan import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { - VaultBaseForString, Decimal, OracleBaseForString, RedBankBaseForString, SwapperBaseForString, InstantiateMsg, + VaultBaseForString, ExecuteMsg, Action, Uint128, CallbackMsg, Addr, - VaultBaseForAddr, - VaultBaseForAddr1, - VaultBaseForAddr2, - VaultBaseForAddr3, Coin, ConfigUpdates, + VaultBaseForAddr, QueryMsg, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, @@ -38,14 +35,13 @@ import { ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, VaultPosition, + VaultPositionState, ArrayOfString, ArrayOfVaultBaseForString, ConfigResponse, HealthResponse, - PositionsWithValueResponse, - CoinValue, - DebtSharesValue, - VaultPositionWithAddr, + Positions, + DebtAmount, } from './CreditManager.types' import { CreditManagerQueryClient, CreditManagerClient } from './CreditManager.client' export const creditManagerQueryKeys = { @@ -304,17 +300,17 @@ export function useCreditManagerHealthQuery({ ) } export interface CreditManagerPositionsQuery - extends CreditManagerReactQuery { + extends CreditManagerReactQuery { args: { accountId: string } } -export function useCreditManagerPositionsQuery({ +export function useCreditManagerPositionsQuery({ client, args, options, }: CreditManagerPositionsQuery) { - return useQuery( + return useQuery( creditManagerQueryKeys.positions(client?.contractAddress, args), () => client diff --git a/scripts/types/generated/credit-manager/CreditManager.types.ts b/scripts/types/generated/credit-manager/CreditManager.types.ts index 4c964acc1..9268d76e3 100644 --- a/scripts/types/generated/credit-manager/CreditManager.types.ts +++ b/scripts/types/generated/credit-manager/CreditManager.types.ts @@ -1,11 +1,10 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -export type VaultBaseForString = string export type Decimal = string export type OracleBaseForString = string export type RedBankBaseForString = string @@ -19,25 +18,23 @@ export interface InstantiateMsg { owner: string red_bank: RedBankBaseForString swapper: SwapperBaseForString - [k: string]: unknown +} +export interface VaultBaseForString { + address: string } export type ExecuteMsg = | { - create_credit_account: { - [k: string]: unknown - } + create_credit_account: {} } | { update_credit_account: { account_id: string actions: Action[] - [k: string]: unknown } } | { update_config: { new_config: ConfigUpdates - [k: string]: unknown } } | { @@ -60,14 +57,12 @@ export type Action = vault_deposit: { coins: Coin[] vault: VaultBaseForString - [k: string]: unknown } } | { vault_withdraw: { amount: Uint128 vault: VaultBaseForString - [k: string]: unknown } } | { @@ -75,7 +70,6 @@ export type Action = debt_coin: Coin liquidatee_account_id: string request_coin_denom: string - [k: string]: unknown } } | { @@ -83,7 +77,6 @@ export type Action = coin_in: Coin denom_out: string slippage: Decimal - [k: string]: unknown } } export type Uint128 = string @@ -93,27 +86,23 @@ export type CallbackMsg = account_id: string coin: Coin recipient: Addr - [k: string]: unknown } } | { borrow: { account_id: string coin: Coin - [k: string]: unknown } } | { repay: { account_id: string coin: Coin - [k: string]: unknown } } | { assert_below_max_l_t_v: { account_id: string - [k: string]: unknown } } | { @@ -121,31 +110,27 @@ export type CallbackMsg = account_id: string coins: Coin[] vault: VaultBaseForAddr - [k: string]: unknown } } | { update_vault_coin_balance: { account_id: string previous_total_balance: Uint128 - vault: VaultBaseForAddr1 - [k: string]: unknown + vault: VaultBaseForAddr } } | { vault_withdraw: { account_id: string amount: Uint128 - vault: VaultBaseForAddr2 - [k: string]: unknown + vault: VaultBaseForAddr } } | { vault_force_withdraw: { account_id: string amount: Uint128 - vault: VaultBaseForAddr3 - [k: string]: unknown + vault: VaultBaseForAddr } } | { @@ -154,14 +139,12 @@ export type CallbackMsg = liquidatee_account_id: string liquidator_account_id: string request_coin_denom: string - [k: string]: unknown } } | { assert_health_factor_improved: { account_id: string previous_health_factor: Decimal - [k: string]: unknown } } | { @@ -170,21 +153,15 @@ export type CallbackMsg = coin_in: Coin denom_out: string slippage: Decimal - [k: string]: unknown } } | { update_coin_balances: { account_id: string previous_balances: Coin[] - [k: string]: unknown } } export type Addr = string -export type VaultBaseForAddr = string -export type VaultBaseForAddr1 = string -export type VaultBaseForAddr2 = string -export type VaultBaseForAddr3 = string export interface Coin { amount: Uint128 denom: string @@ -200,7 +177,9 @@ export interface ConfigUpdates { owner?: string | null red_bank?: RedBankBaseForString | null swapper?: SwapperBaseForString | null - [k: string]: unknown +} +export interface VaultBaseForAddr { + address: Addr } export type QueryMsg = | { @@ -271,38 +250,35 @@ export interface CoinBalanceResponseItem { account_id: string amount: Uint128 denom: string - [k: string]: unknown } export type ArrayOfSharesResponseItem = SharesResponseItem[] export interface SharesResponseItem { account_id: string denom: string shares: Uint128 - [k: string]: unknown } export type ArrayOfDebtShares = DebtShares[] export interface DebtShares { denom: string shares: Uint128 - [k: string]: unknown } export type ArrayOfVaultWithBalance = VaultWithBalance[] export interface VaultWithBalance { balance: Uint128 - vault: VaultBaseForString - [k: string]: unknown + vault: VaultBaseForAddr } export type ArrayOfVaultPositionResponseItem = VaultPositionResponseItem[] export interface VaultPositionResponseItem { account_id: string - addr: string - vault_position: VaultPosition - [k: string]: unknown + position: VaultPosition } export interface VaultPosition { + state: VaultPositionState + vault: VaultBaseForAddr +} +export interface VaultPositionState { locked: Uint128 unlocked: Uint128 - [k: string]: unknown } export type ArrayOfString = string[] export type ArrayOfVaultBaseForString = VaultBaseForString[] @@ -314,7 +290,6 @@ export interface ConfigResponse { owner: string red_bank: string swapper: string - [k: string]: unknown } export interface HealthResponse { above_max_ltv: boolean @@ -325,32 +300,15 @@ export interface HealthResponse { max_ltv_health_factor?: Decimal | null total_collateral_value: Decimal total_debt_value: Decimal - [k: string]: unknown } -export interface PositionsWithValueResponse { +export interface Positions { account_id: string - coins: CoinValue[] - debt: DebtSharesValue[] - vault_positions: VaultPositionWithAddr[] - [k: string]: unknown + coins: Coin[] + debts: DebtAmount[] + vaults: VaultPosition[] } -export interface CoinValue { +export interface DebtAmount { amount: Uint128 denom: string - price: Decimal - value: Decimal - [k: string]: unknown -} -export interface DebtSharesValue { - amount: Uint128 - denom: string - price: Decimal shares: Uint128 - value: Decimal - [k: string]: unknown -} -export interface VaultPositionWithAddr { - addr: string - position: VaultPosition - [k: string]: unknown } diff --git a/scripts/types/generated/credit-manager/bundle.ts b/scripts/types/generated/credit-manager/bundle.ts index c96367a1c..5b6328c60 100644 --- a/scripts/types/generated/credit-manager/bundle.ts +++ b/scripts/types/generated/credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts new file mode 100644 index 000000000..40e562bfd --- /dev/null +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts @@ -0,0 +1,138 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { Coin, StdFee } from '@cosmjs/amino' +import { + OracleBaseForString, + Addr, + PricingMethod, + InstantiateMsg, + VaultPricingInfo, + ExecuteMsg, + ConfigUpdates, + QueryMsg, + ArrayOfVaultPricingInfo, + OracleBaseForAddr, + ConfigResponse, + Decimal, + PriceResponse, +} from './MarsOracleAdapter.types' +export interface MarsOracleAdapterReadOnlyInterface { + contractAddress: string + price: ({ denom }: { denom: string }) => Promise + config: () => Promise + pricingInfo: ({ denom }: { denom: string }) => Promise + allPricingInfo: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }) => Promise +} +export class MarsOracleAdapterQueryClient implements MarsOracleAdapterReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.price = this.price.bind(this) + this.config = this.config.bind(this) + this.pricingInfo = this.pricingInfo.bind(this) + this.allPricingInfo = this.allPricingInfo.bind(this) + } + + price = async ({ denom }: { denom: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + price: { + denom, + }, + }) + } + config = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + config: {}, + }) + } + pricingInfo = async ({ denom }: { denom: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + pricing_info: { + denom, + }, + }) + } + allPricingInfo = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_pricing_info: { + limit, + start_after: startAfter, + }, + }) + } +} +export interface MarsOracleAdapterInterface extends MarsOracleAdapterReadOnlyInterface { + contractAddress: string + sender: string + updateConfig: ( + { + newConfig, + }: { + newConfig: ConfigUpdates + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise +} +export class MarsOracleAdapterClient + extends MarsOracleAdapterQueryClient + implements MarsOracleAdapterInterface +{ + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.updateConfig = this.updateConfig.bind(this) + } + + updateConfig = async ( + { + newConfig, + }: { + newConfig: ConfigUpdates + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_config: { + new_config: newConfig, + }, + }, + fee, + memo, + funds, + ) + } +} diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts new file mode 100644 index 000000000..8ecf8c9bd --- /dev/null +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts @@ -0,0 +1,165 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee, Coin } from '@cosmjs/amino' +import { + OracleBaseForString, + Addr, + PricingMethod, + InstantiateMsg, + VaultPricingInfo, + ExecuteMsg, + ConfigUpdates, + QueryMsg, + ArrayOfVaultPricingInfo, + OracleBaseForAddr, + ConfigResponse, + Decimal, + PriceResponse, +} from './MarsOracleAdapter.types' +import { MarsOracleAdapterQueryClient, MarsOracleAdapterClient } from './MarsOracleAdapter.client' +export const marsOracleAdapterQueryKeys = { + contract: [ + { + contract: 'marsOracleAdapter', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...marsOracleAdapterQueryKeys.contract[0], address: contractAddress }] as const, + price: (contractAddress: string | undefined, args?: Record) => + [{ ...marsOracleAdapterQueryKeys.address(contractAddress)[0], method: 'price', args }] as const, + config: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsOracleAdapterQueryKeys.address(contractAddress)[0], method: 'config', args }, + ] as const, + pricingInfo: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsOracleAdapterQueryKeys.address(contractAddress)[0], method: 'pricing_info', args }, + ] as const, + allPricingInfo: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsOracleAdapterQueryKeys.address(contractAddress)[0], + method: 'all_pricing_info', + args, + }, + ] as const, +} +export interface MarsOracleAdapterReactQuery { + client: MarsOracleAdapterQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MarsOracleAdapterAllPricingInfoQuery + extends MarsOracleAdapterReactQuery { + args: { + limit?: number + startAfter?: string + } +} +export function useMarsOracleAdapterAllPricingInfoQuery({ + client, + args, + options, +}: MarsOracleAdapterAllPricingInfoQuery) { + return useQuery( + marsOracleAdapterQueryKeys.allPricingInfo(client?.contractAddress, args), + () => + client + ? client.allPricingInfo({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsOracleAdapterPricingInfoQuery + extends MarsOracleAdapterReactQuery { + args: { + denom: string + } +} +export function useMarsOracleAdapterPricingInfoQuery({ + client, + args, + options, +}: MarsOracleAdapterPricingInfoQuery) { + return useQuery( + marsOracleAdapterQueryKeys.pricingInfo(client?.contractAddress, args), + () => + client + ? client.pricingInfo({ + denom: args.denom, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsOracleAdapterConfigQuery + extends MarsOracleAdapterReactQuery {} +export function useMarsOracleAdapterConfigQuery({ + client, + options, +}: MarsOracleAdapterConfigQuery) { + return useQuery( + marsOracleAdapterQueryKeys.config(client?.contractAddress), + () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsOracleAdapterPriceQuery + extends MarsOracleAdapterReactQuery { + args: { + denom: string + } +} +export function useMarsOracleAdapterPriceQuery({ + client, + args, + options, +}: MarsOracleAdapterPriceQuery) { + return useQuery( + marsOracleAdapterQueryKeys.price(client?.contractAddress, args), + () => + client + ? client.price({ + denom: args.denom, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsOracleAdapterUpdateConfigMutation { + client: MarsOracleAdapterClient + msg: { + newConfig: ConfigUpdates + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsOracleAdapterUpdateConfigMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateConfig(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts new file mode 100644 index 000000000..4dca1c4fa --- /dev/null +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts @@ -0,0 +1,62 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export type OracleBaseForString = string +export type Addr = string +export type PricingMethod = 'preview_redeem' +export interface InstantiateMsg { + oracle: OracleBaseForString + owner: string + vault_pricing: VaultPricingInfo[] +} +export interface VaultPricingInfo { + addr: Addr + denom: string + method: PricingMethod +} +export type ExecuteMsg = { + update_config: { + new_config: ConfigUpdates + } +} +export interface ConfigUpdates { + oracle?: OracleBaseForString | null + owner?: string | null + vault_pricing?: VaultPricingInfo[] | null +} +export type QueryMsg = + | { + price: { + denom: string + } + } + | { + config: {} + } + | { + pricing_info: { + denom: string + } + } + | { + all_pricing_info: { + limit?: number | null + start_after?: string | null + } + } +export type ArrayOfVaultPricingInfo = VaultPricingInfo[] +export type OracleBaseForAddr = string +export interface ConfigResponse { + oracle: OracleBaseForAddr + owner: Addr +} +export type Decimal = string +export interface PriceResponse { + denom: string + price: Decimal + [k: string]: unknown +} diff --git a/scripts/types/generated/mars-oracle-adapter/bundle.ts b/scripts/types/generated/mars-oracle-adapter/bundle.ts new file mode 100644 index 000000000..02375a2b8 --- /dev/null +++ b/scripts/types/generated/mars-oracle-adapter/bundle.ts @@ -0,0 +1,13 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _6 from './MarsOracleAdapter.types' +import * as _7 from './MarsOracleAdapter.client' +import * as _8 from './MarsOracleAdapter.react-query' +export namespace contracts { + export const MarsOracleAdapter = { ..._6, ..._7, ..._8 } +} diff --git a/scripts/types/generated/mock-oracle/MockOracle.client.ts b/scripts/types/generated/mock-oracle/MockOracle.client.ts index 417f1ec29..f22720f36 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.client.ts +++ b/scripts/types/generated/mock-oracle/MockOracle.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-oracle/MockOracle.react-query.ts b/scripts/types/generated/mock-oracle/MockOracle.react-query.ts index 7cfcee6f2..aca388f4d 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.react-query.ts +++ b/scripts/types/generated/mock-oracle/MockOracle.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-oracle/MockOracle.types.ts b/scripts/types/generated/mock-oracle/MockOracle.types.ts index 9b3e67ae1..833e13cb0 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.types.ts +++ b/scripts/types/generated/mock-oracle/MockOracle.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -8,12 +8,10 @@ export type Decimal = string export interface InstantiateMsg { coins: CoinPrice[] - [k: string]: unknown } export interface CoinPrice { denom: string price: Decimal - [k: string]: unknown } export type ExecuteMsg = { change_price: CoinPrice diff --git a/scripts/types/generated/mock-oracle/bundle.ts b/scripts/types/generated/mock-oracle/bundle.ts index 7542d98d7..5a59629e7 100644 --- a/scripts/types/generated/mock-oracle/bundle.ts +++ b/scripts/types/generated/mock-oracle/bundle.ts @@ -1,13 +1,13 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _6 from './MockOracle.types' -import * as _7 from './MockOracle.client' -import * as _8 from './MockOracle.react-query' +import * as _9 from './MockOracle.types' +import * as _10 from './MockOracle.client' +import * as _11 from './MockOracle.react-query' export namespace contracts { - export const MockOracle = { ..._6, ..._7, ..._8 } + export const MockOracle = { ..._9, ..._10, ..._11 } } diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.client.ts b/scripts/types/generated/mock-red-bank/MockRedBank.client.ts index 5de4d6eb0..2950d9db3 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.client.ts +++ b/scripts/types/generated/mock-red-bank/MockRedBank.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts b/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts index fbc2dc349..e059eabb2 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts +++ b/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.types.ts b/scripts/types/generated/mock-red-bank/MockRedBank.types.ts index d63d6a045..ee4ae957e 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.types.ts +++ b/scripts/types/generated/mock-red-bank/MockRedBank.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -8,27 +8,23 @@ export type Decimal = string export interface InstantiateMsg { coins: CoinMarketInfo[] - [k: string]: unknown } export interface CoinMarketInfo { denom: string liquidation_threshold: Decimal max_ltv: Decimal - [k: string]: unknown } export type ExecuteMsg = | { borrow: { coin: Coin recipient?: string | null - [k: string]: unknown } } | { repay: { denom: string on_behalf_of?: string | null - [k: string]: unknown } } export type Uint128 = string @@ -78,5 +74,4 @@ export interface InterestRateModel { export interface UserAssetDebtResponse { amount: Uint128 denom: string - [k: string]: unknown } diff --git a/scripts/types/generated/mock-red-bank/bundle.ts b/scripts/types/generated/mock-red-bank/bundle.ts index 0e3bcf403..7b5a5df82 100644 --- a/scripts/types/generated/mock-red-bank/bundle.ts +++ b/scripts/types/generated/mock-red-bank/bundle.ts @@ -1,13 +1,13 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _9 from './MockRedBank.types' -import * as _10 from './MockRedBank.client' -import * as _11 from './MockRedBank.react-query' +import * as _12 from './MockRedBank.types' +import * as _13 from './MockRedBank.client' +import * as _14 from './MockRedBank.react-query' export namespace contracts { - export const MockRedBank = { ..._9, ..._10, ..._11 } + export const MockRedBank = { ..._12, ..._13, ..._14 } } diff --git a/scripts/types/generated/mock-vault/MockVault.client.ts b/scripts/types/generated/mock-vault/MockVault.client.ts index 4034dff1a..3c40db792 100644 --- a/scripts/types/generated/mock-vault/MockVault.client.ts +++ b/scripts/types/generated/mock-vault/MockVault.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -20,7 +20,8 @@ import { export interface MockVaultReadOnlyInterface { contractAddress: string info: () => Promise - previewRedeem: ({ shares }: { shares: Uint128 }) => Promise + previewRedeem: ({ amount }: { amount: Uint128 }) => Promise + totalVaultCoinsIssued: () => Promise } export class MockVaultQueryClient implements MockVaultReadOnlyInterface { client: CosmWasmClient @@ -31,6 +32,7 @@ export class MockVaultQueryClient implements MockVaultReadOnlyInterface { this.contractAddress = contractAddress this.info = this.info.bind(this) this.previewRedeem = this.previewRedeem.bind(this) + this.totalVaultCoinsIssued = this.totalVaultCoinsIssued.bind(this) } info = async (): Promise => { @@ -38,13 +40,18 @@ export class MockVaultQueryClient implements MockVaultReadOnlyInterface { info: {}, }) } - previewRedeem = async ({ shares }: { shares: Uint128 }): Promise => { + previewRedeem = async ({ amount }: { amount: Uint128 }): Promise => { return this.client.queryContractSmart(this.contractAddress, { preview_redeem: { - shares, + amount, }, }) } + totalVaultCoinsIssued = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + total_vault_coins_issued: {}, + }) + } } export interface MockVaultInterface extends MockVaultReadOnlyInterface { contractAddress: string diff --git a/scripts/types/generated/mock-vault/MockVault.react-query.ts b/scripts/types/generated/mock-vault/MockVault.react-query.ts index e419297dd..c4ca251dc 100644 --- a/scripts/types/generated/mock-vault/MockVault.react-query.ts +++ b/scripts/types/generated/mock-vault/MockVault.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -33,6 +33,14 @@ export const mockVaultQueryKeys = { [ { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'preview_redeem', args }, ] as const, + totalVaultCoinsIssued: (contractAddress: string | undefined, args?: Record) => + [ + { + ...mockVaultQueryKeys.address(contractAddress)[0], + method: 'total_vault_coins_issued', + args, + }, + ] as const, } export interface MockVaultReactQuery { client: MockVaultQueryClient | undefined @@ -43,10 +51,22 @@ export interface MockVaultReactQuery { initialData?: undefined } } +export interface MockVaultTotalVaultCoinsIssuedQuery + extends MockVaultReactQuery {} +export function useMockVaultTotalVaultCoinsIssuedQuery({ + client, + options, +}: MockVaultTotalVaultCoinsIssuedQuery) { + return useQuery( + mockVaultQueryKeys.totalVaultCoinsIssued(client?.contractAddress), + () => (client ? client.totalVaultCoinsIssued() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MockVaultPreviewRedeemQuery extends MockVaultReactQuery { args: { - shares: Uint128 + amount: Uint128 } } export function useMockVaultPreviewRedeemQuery({ @@ -59,7 +79,7 @@ export function useMockVaultPreviewRedeemQuery({ () => client ? client.previewRedeem({ - shares: args.shares, + amount: args.amount, }) : Promise.reject(new Error('Invalid client')), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, diff --git a/scripts/types/generated/mock-vault/MockVault.types.ts b/scripts/types/generated/mock-vault/MockVault.types.ts index 7b01221f1..8114114a7 100644 --- a/scripts/types/generated/mock-vault/MockVault.types.ts +++ b/scripts/types/generated/mock-vault/MockVault.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -11,23 +11,16 @@ export interface InstantiateMsg { lockup?: number | null lp_token_denom: string oracle: OracleBaseForString - [k: string]: unknown } export type ExecuteMsg = | { - deposit: { - [k: string]: unknown - } + deposit: {} } | { - withdraw: { - [k: string]: unknown - } + withdraw: {} } | { - force_withdraw: { - [k: string]: unknown - } + force_withdraw: {} } export type QueryMsg = | { @@ -35,15 +28,17 @@ export type QueryMsg = } | { preview_redeem: { - shares: Uint128 + amount: Uint128 } } + | { + total_vault_coins_issued: {} + } export type Uint128 = string export interface VaultInfo { coins: Coin[] lockup?: number | null token_denom: string - [k: string]: unknown } export interface Coin { amount: Uint128 diff --git a/scripts/types/generated/mock-vault/bundle.ts b/scripts/types/generated/mock-vault/bundle.ts index 051c16f92..82599902c 100644 --- a/scripts/types/generated/mock-vault/bundle.ts +++ b/scripts/types/generated/mock-vault/bundle.ts @@ -1,13 +1,13 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _12 from './MockVault.types' -import * as _13 from './MockVault.client' -import * as _14 from './MockVault.react-query' +import * as _15 from './MockVault.types' +import * as _16 from './MockVault.client' +import * as _17 from './MockVault.react-query' export namespace contracts { - export const MockVault = { ..._12, ..._13, ..._14 } + export const MockVault = { ..._15, ..._16, ..._17 } } diff --git a/scripts/types/generated/swapper-base/SwapperBase.client.ts b/scripts/types/generated/swapper-base/SwapperBase.client.ts index 8ca9e9602..02a924c0c 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.client.ts +++ b/scripts/types/generated/swapper-base/SwapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/swapper-base/SwapperBase.react-query.ts b/scripts/types/generated/swapper-base/SwapperBase.react-query.ts index b021dbe28..a47fdc9e0 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.react-query.ts +++ b/scripts/types/generated/swapper-base/SwapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/swapper-base/SwapperBase.types.ts b/scripts/types/generated/swapper-base/SwapperBase.types.ts index c5eb30d75..b6863f76b 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.types.ts +++ b/scripts/types/generated/swapper-base/SwapperBase.types.ts @@ -1,19 +1,17 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ export interface InstantiateMsg { owner: string - [k: string]: unknown } export type ExecuteMsg = | { update_config: { owner?: string | null - [k: string]: unknown } } | { @@ -21,7 +19,6 @@ export type ExecuteMsg = denom_in: string denom_out: string route: Empty - [k: string]: unknown } } | { @@ -29,7 +26,6 @@ export type ExecuteMsg = coin_in: Coin denom_out: string slippage: Decimal - [k: string]: unknown } } | { @@ -37,7 +33,6 @@ export type ExecuteMsg = denom_in: string denom_out: string recipient: Addr - [k: string]: unknown } } export type Uint128 = string @@ -75,16 +70,13 @@ export type QueryMsg = } export interface ConfigForString { owner: string - [k: string]: unknown } export interface EstimateExactInSwapResponse { amount: Uint128 - [k: string]: unknown } export interface RouteResponseForEmpty { denom_in: string denom_out: string route: Empty - [k: string]: unknown } export type ArrayOfRouteResponseForEmpty = RouteResponseForEmpty[] diff --git a/scripts/types/generated/swapper-base/bundle.ts b/scripts/types/generated/swapper-base/bundle.ts index a413481f8..efff30782 100644 --- a/scripts/types/generated/swapper-base/bundle.ts +++ b/scripts/types/generated/swapper-base/bundle.ts @@ -1,13 +1,13 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.4. + * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _15 from './SwapperBase.types' -import * as _16 from './SwapperBase.client' -import * as _17 from './SwapperBase.react-query' +import * as _18 from './SwapperBase.types' +import * as _19 from './SwapperBase.client' +import * as _20 from './SwapperBase.react-query' export namespace contracts { - export const SwapperBase = { ..._15, ..._16, ..._17 } + export const SwapperBase = { ..._18, ..._19, ..._20 } } diff --git a/scripts/types/storageItems.ts b/scripts/types/storageItems.ts index 5f62605d1..8f9781620 100644 --- a/scripts/types/storageItems.ts +++ b/scripts/types/storageItems.ts @@ -4,14 +4,14 @@ export interface StorageItems { mockRedBank?: number mockVault?: number mockOracle?: number + marsOracleAdapter?: number swapper?: number creditManager?: number } addresses: { accountNft?: string - mockRedBank?: string mockVault?: string - mockOracle?: string + marsOracleAdapter?: string swapper?: string creditManager?: string } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 07bb4110f..fa3170ec9 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -22,10 +22,10 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.0.tgz" integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== -"@babel/compat-data@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.1.tgz#72d647b4ff6a4f82878d184613353af1dd0290f9" - integrity sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg== +"@babel/compat-data@^7.19.3": + version "7.19.3" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.3.tgz#707b939793f867f5a73b2666e6d9a3396eb03151" + integrity sha512-prBHMK4JYYK+wDjJF1q99KK4JLL+egWS4nmNqdlMUgCExMZ+iZW0hGhyC3VEbsPjvaN0TBhW//VIFwBrk8sEiw== "@babel/core@7.18.10": version "7.18.10" @@ -112,12 +112,12 @@ browserslist "^4.20.2" semver "^6.3.0" -"@babel/helper-compilation-targets@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz#7f630911d83b408b76fe584831c98e5395d7a17c" - integrity sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg== +"@babel/helper-compilation-targets@^7.19.3": + version "7.19.3" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz#a10a04588125675d7c7ae299af86fa1b2ee038ca" + integrity sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg== dependencies: - "@babel/compat-data" "^7.19.1" + "@babel/compat-data" "^7.19.3" "@babel/helper-validator-option" "^7.18.6" browserslist "^4.21.3" semver "^6.3.0" @@ -286,6 +286,11 @@ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz" integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== +"@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + "@babel/helper-validator-option@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz" @@ -995,13 +1000,13 @@ core-js-compat "^3.22.1" semver "^6.3.0" -"@babel/preset-env@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.1.tgz#9f04c916f9c0205a48ebe5cc1be7768eb1983f67" - integrity sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA== +"@babel/preset-env@^7.19.3": + version "7.19.3" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.3.tgz#52cd19abaecb3f176a4ff9cc5e15b7bf06bec754" + integrity sha512-ziye1OTc9dGFOAXSWKUqQblYHNlBOaDl8wzqf2iKXJAltYiR3hKHUKmkt+S9PppW7RQpq4fFCrwwpIDj/f5P4w== dependencies: - "@babel/compat-data" "^7.19.1" - "@babel/helper-compilation-targets" "^7.19.1" + "@babel/compat-data" "^7.19.3" + "@babel/helper-compilation-targets" "^7.19.3" "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-validator-option" "^7.18.6" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" @@ -1069,7 +1074,7 @@ "@babel/plugin-transform-unicode-escapes" "^7.18.10" "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.19.0" + "@babel/types" "^7.19.3" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" babel-plugin-polyfill-regenerator "^0.4.1" @@ -1162,6 +1167,15 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" +"@babel/types@^7.19.3": + version "7.19.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.3.tgz#fc420e6bbe54880bce6779ffaf315f5e43ec9624" + integrity sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw== + dependencies: + "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" @@ -1308,10 +1322,10 @@ resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.0.tgz#0a61e6d608e9f6f89a278cc71f4e7cee01199657" integrity sha512-NiJk3ISX+FU1cQcTTgmJcY84A8mV/p8L5CRewp/2jc/lUmo8j9lMGbX17U7NxVQ9RX5RmrwgdjYnBASzhRCVmA== -"@cosmwasm/ts-codegen@^0.16.4": - version "0.16.4" - resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.16.4.tgz#84367a886c631289a9ba0ff7696da62d37f1ac9a" - integrity sha512-50r6eU+BHAn7uRiJ9YAS8Wym7hq1d4j0VcAYpEeCl399ELdb/zS+8hDU6mczPQM0QYKflYffb40X78NuACji9w== +"@cosmwasm/ts-codegen@^0.16.5": + version "0.16.5" + resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.16.5.tgz#98618126000b28a4039572f501e61805ff5a5620" + integrity sha512-rGqxMsftRESspiT4CfeYP2hxFeRdpN5YPw3FezNbRT0pqO7yZVvFXl5cqGidOHbzlXhreo7/RAABRfGOW7arng== dependencies: "@babel/core" "7.18.10" "@babel/generator" "7.18.12" @@ -1356,10 +1370,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.10.4": - version "0.10.4" - resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz" - integrity sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw== +"@humanwhocodes/config-array@^0.10.5": + version "0.10.5" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.5.tgz#bb679745224745fff1e9a41961c1d45a49f81c04" + integrity sha512-XVVDtp+dVvRxMoxSiSfasYaG02VEe1qH5cKgMQJWhol6HwzbcqoCMJi8dAGoYAO57jhUyhI6cWuRiTcRaDaYug== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" @@ -1923,85 +1937,84 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.37.0.tgz" - integrity sha512-Fde6W0IafXktz1UlnhGkrrmnnGpAo1kyX7dnyHHVrmwJOn72Oqm3eYtddrpOwwel2W8PAK9F3pIL5S+lfoM0og== +"@typescript-eslint/eslint-plugin@^5.38.1": + version "5.38.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz#9f05d42fa8fb9f62304cc2f5c2805e03c01c2620" + integrity sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ== dependencies: - "@typescript-eslint/scope-manager" "5.37.0" - "@typescript-eslint/type-utils" "5.37.0" - "@typescript-eslint/utils" "5.37.0" + "@typescript-eslint/scope-manager" "5.38.1" + "@typescript-eslint/type-utils" "5.38.1" + "@typescript-eslint/utils" "5.38.1" debug "^4.3.4" - functional-red-black-tree "^1.0.1" ignore "^5.2.0" regexpp "^3.2.0" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.37.0.tgz" - integrity sha512-01VzI/ipYKuaG5PkE5+qyJ6m02fVALmMPY3Qq5BHflDx3y4VobbLdHQkSMg9VPRS4KdNt4oYTMaomFoHonBGAw== +"@typescript-eslint/parser@^5.38.1": + version "5.38.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.38.1.tgz#c577f429f2c32071b92dff4af4f5fbbbd2414bd0" + integrity sha512-LDqxZBVFFQnQRz9rUZJhLmox+Ep5kdUmLatLQnCRR6523YV+XhRjfYzStQ4MheFA8kMAfUlclHSbu+RKdRwQKw== dependencies: - "@typescript-eslint/scope-manager" "5.37.0" - "@typescript-eslint/types" "5.37.0" - "@typescript-eslint/typescript-estree" "5.37.0" + "@typescript-eslint/scope-manager" "5.38.1" + "@typescript-eslint/types" "5.38.1" + "@typescript-eslint/typescript-estree" "5.38.1" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.37.0.tgz" - integrity sha512-F67MqrmSXGd/eZnujjtkPgBQzgespu/iCZ+54Ok9X5tALb9L2v3G+QBSoWkXG0p3lcTJsL+iXz5eLUEdSiJU9Q== +"@typescript-eslint/scope-manager@5.38.1": + version "5.38.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz#f87b289ef8819b47189351814ad183e8801d5764" + integrity sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ== dependencies: - "@typescript-eslint/types" "5.37.0" - "@typescript-eslint/visitor-keys" "5.37.0" + "@typescript-eslint/types" "5.38.1" + "@typescript-eslint/visitor-keys" "5.38.1" -"@typescript-eslint/type-utils@5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.37.0.tgz" - integrity sha512-BSx/O0Z0SXOF5tY0bNTBcDEKz2Ec20GVYvq/H/XNKiUorUFilH7NPbFUuiiyzWaSdN3PA8JV0OvYx0gH/5aFAQ== +"@typescript-eslint/type-utils@5.38.1": + version "5.38.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.38.1.tgz#7f038fcfcc4ade4ea76c7c69b2aa25e6b261f4c1" + integrity sha512-UU3j43TM66gYtzo15ivK2ZFoDFKKP0k03MItzLdq0zV92CeGCXRfXlfQX5ILdd4/DSpHkSjIgLLLh1NtkOJOAw== dependencies: - "@typescript-eslint/typescript-estree" "5.37.0" - "@typescript-eslint/utils" "5.37.0" + "@typescript-eslint/typescript-estree" "5.38.1" + "@typescript-eslint/utils" "5.38.1" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.37.0.tgz" - integrity sha512-3frIJiTa5+tCb2iqR/bf7XwU20lnU05r/sgPJnRpwvfZaqCJBrl8Q/mw9vr3NrNdB/XtVyMA0eppRMMBqdJ1bA== +"@typescript-eslint/types@5.38.1": + version "5.38.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.38.1.tgz#74f9d6dcb8dc7c58c51e9fbc6653ded39e2e225c" + integrity sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg== -"@typescript-eslint/typescript-estree@5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.37.0.tgz" - integrity sha512-JkFoFIt/cx59iqEDSgIGnQpCTRv96MQnXCYvJi7QhBC24uyuzbD8wVbajMB1b9x4I0octYFJ3OwjAwNqk1AjDA== +"@typescript-eslint/typescript-estree@5.38.1": + version "5.38.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz#657d858d5d6087f96b638ee383ee1cff52605a1e" + integrity sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g== dependencies: - "@typescript-eslint/types" "5.37.0" - "@typescript-eslint/visitor-keys" "5.37.0" + "@typescript-eslint/types" "5.38.1" + "@typescript-eslint/visitor-keys" "5.38.1" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.37.0.tgz" - integrity sha512-jUEJoQrWbZhmikbcWSMDuUSxEE7ID2W/QCV/uz10WtQqfOuKZUqFGjqLJ+qhDd17rjgp+QJPqTdPIBWwoob2NQ== +"@typescript-eslint/utils@5.38.1": + version "5.38.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.38.1.tgz#e3ac37d7b33d1362bb5adf4acdbe00372fb813ef" + integrity sha512-oIuUiVxPBsndrN81oP8tXnFa/+EcZ03qLqPDfSZ5xIJVm7A9V0rlkQwwBOAGtrdN70ZKDlKv+l1BeT4eSFxwXA== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.37.0" - "@typescript-eslint/types" "5.37.0" - "@typescript-eslint/typescript-estree" "5.37.0" + "@typescript-eslint/scope-manager" "5.38.1" + "@typescript-eslint/types" "5.38.1" + "@typescript-eslint/typescript-estree" "5.38.1" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.37.0": - version "5.37.0" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.37.0.tgz" - integrity sha512-Hp7rT4cENBPIzMwrlehLW/28EVCOcE9U1Z1BQTc8EA8v5qpr7GRGuG+U58V5tTY48zvUOA3KHvw3rA8tY9fbdA== +"@typescript-eslint/visitor-keys@5.38.1": + version "5.38.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz#508071bfc6b96d194c0afe6a65ad47029059edbc" + integrity sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA== dependencies: - "@typescript-eslint/types" "5.37.0" + "@typescript-eslint/types" "5.38.1" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -2717,13 +2730,13 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.23.1: - version "8.23.1" - resolved "https://registry.npmjs.org/eslint/-/eslint-8.23.1.tgz" - integrity sha512-w7C1IXCc6fNqjpuYd0yPlcTKKmHlHHktRkzmBPZ+7cvNBQuiNjx0xaMTjAJGCafJhQkrFJooREv0CtrVzmHwqg== +eslint@^8.24.0: + version "8.24.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.24.0.tgz#489516c927a5da11b3979dbfb2679394523383c8" + integrity sha512-dWFaPhGhTAiPcCgm3f6LI2MBWbogMnTJzFBbhXVRQDJPkr9pGZvVjlVfXd+vyDcWPA2Ic9L2AXPIQM0+vk/cSQ== dependencies: "@eslint/eslintrc" "^1.3.2" - "@humanwhocodes/config-array" "^0.10.4" + "@humanwhocodes/config-array" "^0.10.5" "@humanwhocodes/gitignore-to-minimatch" "^1.0.2" "@humanwhocodes/module-importer" "^1.0.1" ajv "^6.10.0" @@ -2979,11 +2992,6 @@ function-bind@^1.1.1: resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz" - integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== - fuzzy@0.1.3: version "0.1.3" resolved "https://registry.npmjs.org/fuzzy/-/fuzzy-0.1.3.tgz" @@ -4795,10 +4803,10 @@ type@^2.7.2: resolved "https://registry.npmjs.org/type/-/type-2.7.2.tgz" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== -typescript@^4.8.3: - version "4.8.3" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz" - integrity sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig== +typescript@^4.8.4: + version "4.8.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" + integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" From ead9325eedf9aea8e03087066387519b61e6ba10 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 28 Sep 2022 08:12:33 -0500 Subject: [PATCH 060/218] Updating redbank/oracle testnet addrs (#24) --- scripts/deploy/addresses/osmo-test-4.json | 10 +++++----- scripts/deploy/osmosis/config.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 5a15b73e3..86a7ff1e2 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,7 @@ { - "accountNft": "osmo1m62qxx06tqg39azy6jqwdvd06vaju93l6e9hpr2jnyp74ewhd3lsg7ycq2", - "mockVault": "osmo176mztwavcndw3chc34ke78pyv7kun9wf6jcqeps6wppz9ksrcdrszcqfed", - "marsOracleAdapter": "osmo10yjzjhtzqeyc02xmkzdt274huvkf3nwvqxereyhay6au9uglgt0sqhn44r", - "swapper": "osmo1h4u2ku8wl8v6zqhfmmd8pnt5ap45j3yx8whtjtkuff75l3lx6yfsx3re60", - "creditManager": "osmo1dgsfqcjyzfvau03ucvu0e38fyauuppfdlrew3ddalahxrhdme73s2dcqvm" + "accountNft": "osmo1xdz3k7dyrjhne749j2ucvy8egrh0rj200r365hdlc4vm6gy0gxsqw9lje4", + "mockVault": "osmo1fak59kj9cwh6p3evcvq3q6r0v5q7tpdfxyqsfs48wne28rw9vrjq0x2l3q", + "marsOracleAdapter": "osmo108enr4hvmc5w6yum9cx6pnkuudhla4t3se8l0v695y07hfj2zxqsp9sc0j", + "swapper": "osmo13qh27g5q5du92fyc0x2cacfgddrl0pzdvd6ulws8tactjdqyxvfqcxe4f9", + "creditManager": "osmo13xj9wy673y5g035wlghwfnqdkzawd0gpfc34dm73rns4962fdt9s7yuur7" } diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index c5b081faf..63a440458 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -4,8 +4,8 @@ const uatom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5 export const osmosisTestnetConfig: DeploymentConfig = { // Get the latest addresses from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-4.json - oracleAddr: 'osmo1kgv8rr9eglkv52hwf0v96cs5s7ztw06tx3a6zrrcrwgmuuru36cqgmz2xa', - redBankAddr: 'osmo1dkn4vr75uep4gmd0gatuu7zlapahps7kdapy8wwztcygdu5wy8lqtw2yuj', + oracleAddr: 'osmo1y3y3ek83hyc4y2te8kytymg599q9sycv9dsufysapra5gglpr4ys25nh94', + redBankAddr: 'osmo1w5rqrdhut890jplmsqnr8gj3uf0wq6lj5rfdnhrtl63lpf6e7v6qalrhhn', baseDenom: 'uosmo', secondaryDenom: uatom, chainId: 'osmo-test-4', From fd41fe025b10415fdfaef5fd3bd6c4083536a34e Mon Sep 17 00:00:00 2001 From: Piotr Babel Date: Mon, 10 Oct 2022 13:30:02 +0200 Subject: [PATCH 061/218] Fix redbank schema. (#25) --- Cargo.lock | 1 + contracts/credit-manager/Cargo.toml | 1 + .../credit-manager/tests/helpers/mock_env.rs | 13 ++++---- contracts/mock-red-bank/examples/schema.rs | 3 +- contracts/mock-red-bank/src/contract.rs | 21 +++++++------ contracts/mock-red-bank/src/execute.rs | 23 ++++++++------ contracts/mock-red-bank/src/msg.rs | 31 ++----------------- contracts/mock-red-bank/src/query.rs | 20 ++++++------ packages/rover/src/adapters/red_bank.rs | 18 +++++------ 9 files changed, 56 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f0f98b46..b22688f70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,6 +179,7 @@ dependencies = [ "cw721-base", "itertools", "mars-health", + "mars-outpost", "mock-oracle", "mock-red-bank", "mock-vault", diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index ceecc9667..011e65470 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -30,6 +30,7 @@ cw-storage-plus = "0.15" [dev-dependencies] swapper-mock = { version = "1.0", path = "../../contracts/swapper/mock", features = ["library"] } +mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } anyhow = "1" cw-multi-test = "0.15" diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 11e071316..01612674a 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -7,13 +7,12 @@ use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use account_nft::msg::ExecuteMsg as NftExecuteMsg; +use mars_outpost::red_bank::QueryMsg::UserDebt; +use mars_outpost::red_bank::UserDebtResponse; use mock_oracle::msg::{ CoinPrice, ExecuteMsg as OracleExecuteMsg, InstantiateMsg as OracleInstantiateMsg, }; -use mock_red_bank::msg::QueryMsg::UserAssetDebt; -use mock_red_bank::msg::{ - CoinMarketInfo, InstantiateMsg as RedBankInstantiateMsg, UserAssetDebtResponse, -}; +use mock_red_bank::msg::{CoinMarketInfo, InstantiateMsg as RedBankInstantiateMsg}; use mock_vault::contract::DEFAULT_VAULT_TOKEN_PREFUND; use mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; use rover::adapters::swap::QueryMsg::EstimateExactInSwap; @@ -312,14 +311,14 @@ impl MockEnv { .unwrap() } - pub fn query_red_bank_debt(&self, denom: &str) -> UserAssetDebtResponse { + pub fn query_red_bank_debt(&self, denom: &str) -> UserDebtResponse { let config = self.query_config(); self.app .wrap() .query_wasm_smart( config.red_bank, - &UserAssetDebt { - user_address: self.rover.to_string(), + &UserDebt { + user: self.rover.to_string(), denom: denom.into(), }, ) diff --git a/contracts/mock-red-bank/examples/schema.rs b/contracts/mock-red-bank/examples/schema.rs index 2f4f5a8da..6312801fb 100644 --- a/contracts/mock-red-bank/examples/schema.rs +++ b/contracts/mock-red-bank/examples/schema.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::write_api; -use mock_red_bank::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_outpost::red_bank::{ExecuteMsg, QueryMsg}; +use mock_red_bank::msg::InstantiateMsg; fn main() { write_api! { diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index 9d21f41fa..d59a7f626 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -3,10 +3,12 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use crate::execute::{borrow, repay}; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::msg::InstantiateMsg; use crate::query::{query_debt, query_market}; use crate::state::COIN_MARKET_INFO; +use mars_outpost::red_bank; + #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, @@ -25,21 +27,20 @@ pub fn execute( deps: DepsMut, _env: Env, info: MessageInfo, - msg: ExecuteMsg, + msg: red_bank::ExecuteMsg, ) -> StdResult { match msg { - ExecuteMsg::Borrow { coin, .. } => borrow(deps, info, coin), - ExecuteMsg::Repay { denom, .. } => repay(deps, info, denom), + red_bank::ExecuteMsg::Borrow { denom, amount, .. } => borrow(deps, info, denom, amount), + red_bank::ExecuteMsg::Repay { .. } => repay(deps, info), + _ => unimplemented!("Msg not supported!"), } } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, _env: Env, msg: red_bank::QueryMsg) -> StdResult { match msg { - QueryMsg::UserAssetDebt { - user_address, - denom, - } => to_binary(&query_debt(deps, user_address, denom)?), - QueryMsg::Market { denom } => to_binary(&query_market(deps, denom)?), + red_bank::QueryMsg::UserDebt { user, denom } => to_binary(&query_debt(deps, user, denom)?), + red_bank::QueryMsg::Market { denom } => to_binary(&query_market(deps, denom)?), + _ => unimplemented!("Query not supported!"), } } diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs index 79277cf74..745b5b2f7 100644 --- a/contracts/mock-red-bank/src/execute.rs +++ b/contracts/mock-red-bank/src/execute.rs @@ -1,34 +1,39 @@ -use cosmwasm_std::{BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdResult, Uint128}; +use cosmwasm_std::{coin, BankMsg, CosmosMsg, DepsMut, MessageInfo, Response, StdResult, Uint128}; use crate::helpers::load_debt_amount; use crate::state::DEBT_AMOUNT; -pub fn borrow(deps: DepsMut, info: MessageInfo, coin: Coin) -> StdResult { - let debt_amount = load_debt_amount(deps.storage, &info.sender, &coin.denom)?; +pub fn borrow( + deps: DepsMut, + info: MessageInfo, + denom: String, + amount: Uint128, +) -> StdResult { + let debt_amount = load_debt_amount(deps.storage, &info.sender, &denom)?; DEBT_AMOUNT.save( deps.storage, - (info.sender.clone(), coin.denom.clone()), + (info.sender.clone(), denom.clone()), &debt_amount - .checked_add(coin.amount)? + .checked_add(amount)? .checked_add(Uint128::new(1))?, // The extra unit is simulated accrued interest )?; let transfer_msg = CosmosMsg::Bank(BankMsg::Send { to_address: info.sender.to_string(), - amount: vec![coin], + amount: vec![coin(amount.u128(), denom)], }); Ok(Response::new().add_message(transfer_msg)) } -pub fn repay(deps: DepsMut, info: MessageInfo, denom: String) -> StdResult { - let debt_amount = load_debt_amount(deps.storage, &info.sender, &denom)?; +pub fn repay(deps: DepsMut, info: MessageInfo) -> StdResult { let coin_sent = info.funds.first().unwrap(); + let debt_amount = load_debt_amount(deps.storage, &info.sender, &coin_sent.denom)?; DEBT_AMOUNT.save( deps.storage, - (info.sender.clone(), denom), + (info.sender.clone(), coin_sent.denom.clone()), &debt_amount.checked_sub(coin_sent.amount)?, )?; diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs index 42aee1aea..422370d95 100644 --- a/contracts/mock-red-bank/src/msg.rs +++ b/contracts/mock-red-bank/src/msg.rs @@ -1,5 +1,5 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Coin, Decimal, Uint128}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Decimal; #[cw_serde] pub struct InstantiateMsg { @@ -12,30 +12,3 @@ pub struct CoinMarketInfo { pub max_ltv: Decimal, pub liquidation_threshold: Decimal, } - -#[cw_serde] -pub enum ExecuteMsg { - Borrow { - coin: Coin, - recipient: Option, - }, - Repay { - denom: String, - on_behalf_of: Option, - }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(UserAssetDebtResponse)] - UserAssetDebt { user_address: String, denom: String }, - #[returns(mars_outpost::red_bank::Market)] - Market { denom: String }, -} - -#[cw_serde] -pub struct UserAssetDebtResponse { - pub denom: String, - pub amount: Uint128, -} diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index 9dc844515..04fe61fdb 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -1,18 +1,20 @@ -use cosmwasm_std::{Deps, StdResult}; +use cosmwasm_std::{Deps, StdResult, Uint128}; use mars_outpost::red_bank::Market; use crate::helpers::load_debt_amount; -use crate::msg::UserAssetDebtResponse; use crate::state::COIN_MARKET_INFO; -pub fn query_debt( - deps: Deps, - user_address: String, - denom: String, -) -> StdResult { - let user_addr = deps.api.addr_validate(&user_address)?; +use mars_outpost::red_bank::UserDebtResponse; + +pub fn query_debt(deps: Deps, user: String, denom: String) -> StdResult { + let user_addr = deps.api.addr_validate(&user)?; let amount = load_debt_amount(deps.storage, &user_addr, &denom)?; - Ok(UserAssetDebtResponse { denom, amount }) + Ok(UserDebtResponse { + denom, + amount, + amount_scaled: Uint128::zero(), + uncollateralized: false, + }) } pub fn query_market(deps: Deps, denom: String) -> StdResult { diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index 8d582c7aa..36c2b1b7c 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ WasmMsg, WasmQuery, }; -use mock_red_bank::msg::{ExecuteMsg, QueryMsg, UserAssetDebtResponse}; +use mars_outpost::red_bank; #[cw_serde] pub struct RedBankBase(T); @@ -39,8 +39,9 @@ impl RedBank { pub fn borrow_msg(&self, coin: &Coin) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address().to_string(), - msg: to_binary(&ExecuteMsg::Borrow { - coin: coin.clone(), + msg: to_binary(&red_bank::ExecuteMsg::Borrow { + denom: coin.denom.to_string(), + amount: coin.amount, recipient: None, })?, funds: vec![], @@ -51,10 +52,7 @@ impl RedBank { pub fn repay_msg(&self, coin: &Coin) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address().to_string(), - msg: to_binary(&ExecuteMsg::Repay { - denom: coin.denom.clone(), - on_behalf_of: None, - })?, + msg: to_binary(&red_bank::ExecuteMsg::Repay { on_behalf_of: None })?, funds: vec![coin.clone()], })) } @@ -65,11 +63,11 @@ impl RedBank { user_address: &Addr, denom: &str, ) -> StdResult { - let response: UserAssetDebtResponse = + let response: red_bank::UserDebtResponse = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address().to_string(), - msg: to_binary(&QueryMsg::UserAssetDebt { - user_address: user_address.to_string(), + msg: to_binary(&red_bank::QueryMsg::UserDebt { + user: user_address.to_string(), denom: denom.to_string(), })?, }))?; From cfe95da9161eed16d6a0154869322233b4730514 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 12 Oct 2022 15:02:10 +0200 Subject: [PATCH 062/218] Locked vaults: Requesting unlock + withdrawing (#21) * Vaults: Request + unlocking * Review updates pt.2 * Update deployment --- Cargo.lock | 13 +- contracts/credit-manager/src/contract.rs | 16 +- contracts/credit-manager/src/execute.rs | 29 +- contracts/credit-manager/src/state.rs | 5 + contracts/credit-manager/src/utils.rs | 39 +- contracts/credit-manager/src/vault/deposit.rs | 53 +- contracts/credit-manager/src/vault/mod.rs | 4 + .../src/vault/request_unlock.rs | 80 + contracts/credit-manager/src/vault/utils.rs | 60 +- .../credit-manager/src/vault/withdraw.rs | 29 +- .../src/vault/withdraw_unlocked.rs | 61 + .../tests/helpers/assertions.rs | 11 +- .../credit-manager/tests/helpers/builders.rs | 4 +- .../credit-manager/tests/helpers/contracts.rs | 4 +- .../credit-manager/tests/helpers/mock_env.rs | 40 +- .../credit-manager/tests/helpers/types.rs | 4 +- .../tests/test_enumerate_allowed_vaults.rs | 12 +- .../test_enumerate_vault_coin_balances.rs | 12 +- .../tests/test_enumerate_vault_positions.rs | 12 +- .../credit-manager/tests/test_instantiate.rs | 24 +- .../tests/test_liquidate_coin.rs | 4 +- .../tests/test_vault_deposit.rs | 32 +- .../tests/test_vault_request_unlock.rs | 280 +++ .../tests/test_vault_withdraw.rs | 22 +- .../tests/test_vault_withdraw_unlocked.rs | 297 +++ contracts/mock-vault/src/contract.rs | 17 +- contracts/mock-vault/src/error.rs | 6 + contracts/mock-vault/src/lib.rs | 1 + contracts/mock-vault/src/query.rs | 28 +- contracts/mock-vault/src/state.rs | 6 +- contracts/mock-vault/src/unlock.rs | 67 + packages/rover/src/adapters/vault.rs | 70 +- packages/rover/src/error.rs | 9 + packages/rover/src/extensions/mod.rs | 3 + packages/rover/src/extensions/reply.rs | 61 + packages/rover/src/lib.rs | 1 + packages/rover/src/msg/execute.rs | 19 + packages/rover/src/msg/vault.rs | 40 +- schemas/account-nft/account-nft.json | 2 +- schemas/credit-manager/credit-manager.json | 182 +- .../mars-oracle-adapter.json | 1 + schemas/mock-oracle/mock-oracle.json | 1 + schemas/mock-red-bank/mock-red-bank.json | 1673 +++++++++++++++-- schemas/mock-vault/mock-vault.json | 237 ++- scripts/deploy/addresses/osmo-test-4.json | 10 +- scripts/package.json | 18 +- .../account-nft/AccountNft.client.ts | 2 +- .../account-nft/AccountNft.react-query.ts | 2 +- .../generated/account-nft/AccountNft.types.ts | 2 +- scripts/types/generated/account-nft/bundle.ts | 2 +- .../credit-manager/CreditManager.client.ts | 3 +- .../CreditManager.react-query.ts | 3 +- .../credit-manager/CreditManager.types.ts | 33 +- .../types/generated/credit-manager/bundle.ts | 2 +- .../MarsOracleAdapter.client.ts | 2 +- .../MarsOracleAdapter.react-query.ts | 2 +- .../MarsOracleAdapter.types.ts | 3 +- .../generated/mars-oracle-adapter/bundle.ts | 2 +- .../mock-oracle/MockOracle.client.ts | 2 +- .../mock-oracle/MockOracle.react-query.ts | 2 +- .../generated/mock-oracle/MockOracle.types.ts | 3 +- scripts/types/generated/mock-oracle/bundle.ts | 2 +- .../mock-red-bank/MockRedBank.client.ts | 618 +++++- .../mock-red-bank/MockRedBank.react-query.ts | 595 +++++- .../mock-red-bank/MockRedBank.types.ts | 201 +- .../types/generated/mock-red-bank/bundle.ts | 2 +- .../generated/mock-vault/MockVault.client.ts | 88 +- .../mock-vault/MockVault.react-query.ts | 111 +- .../generated/mock-vault/MockVault.types.ts | 36 +- scripts/types/generated/mock-vault/bundle.ts | 2 +- .../swapper-base/SwapperBase.client.ts | 2 +- .../swapper-base/SwapperBase.react-query.ts | 2 +- .../swapper-base/SwapperBase.types.ts | 2 +- .../types/generated/swapper-base/bundle.ts | 2 +- scripts/yarn.lock | 974 +++++----- 75 files changed, 5383 insertions(+), 918 deletions(-) create mode 100644 contracts/credit-manager/src/vault/request_unlock.rs create mode 100644 contracts/credit-manager/src/vault/withdraw_unlocked.rs create mode 100644 contracts/credit-manager/tests/test_vault_request_unlock.rs create mode 100644 contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs create mode 100644 contracts/mock-vault/src/unlock.rs create mode 100644 packages/rover/src/extensions/mod.rs create mode 100644 packages/rover/src/extensions/reply.rs diff --git a/Cargo.lock b/Cargo.lock index b22688f70..96035f21a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,9 +83,9 @@ checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" [[package]] name = "cosmwasm-crypto" -version = "1.1.1" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cb32eb596428347033db2da3028558ef8ec506ae00605932eef4b24972baa2" +checksum = "ce1c26e5595c6a960cd3d6dc1d8629e16a2b0cb22ecd653b28bbf292d7c53a3c" dependencies = [ "digest 0.10.5", "ed25519-zebra", @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.1.1" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0faf3a02389f78d6173b7e680751205015d5406f8abbaa9aa36fd216adc9f10d" +checksum = "0a24050753f242554c558dfe7a6b3a456850f2bc6fcf8b518988720e40a5fa21" dependencies = [ "syn", ] @@ -129,15 +129,16 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.1.1" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3409b349f282924c8099b554aae6fe70e4eb97d6a64697ae13c8be25a7eb158" +checksum = "78e50ba8bfea2e449be2e7fa96fd8b196d09fb5ef34e27a18f28835ed835b199" dependencies = [ "base64", "cosmwasm-crypto", "cosmwasm-derive", "derivative", "forward_ref", + "hex", "schemars", "serde", "serde-json-wasm", diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 6e5ae08b8..3053bf017 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -1,7 +1,10 @@ -use cosmwasm_std::{entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; +use cosmwasm_std::{ + entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, +}; use cw2::set_contract_version; -use rover::error::ContractResult; +use rover::adapters::VAULT_REQUEST_REPLY_ID; +use rover::error::{ContractError, ContractResult}; use rover::msg::query::HealthResponse; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -14,6 +17,7 @@ use crate::query::{ query_allowed_vaults, query_config, query_positions, query_total_debt_shares, query_total_vault_coin_balance, }; +use crate::vault::handle_unlock_request_reply; const CONTRACT_NAME: &str = "crates.io:rover-credit-manager"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -48,6 +52,14 @@ pub fn execute( } } +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, _: Env, reply: Reply) -> ContractResult { + match reply.id { + VAULT_REQUEST_REPLY_ID => handle_unlock_request_reply(deps, reply), + id => Err(ContractError::ReplyIdError(id)), + } +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { let res = match msg { diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 2b5409f7f..dd282fdb5 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -12,7 +12,10 @@ use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, }; -use crate::vault::{deposit_into_vault, update_vault_coin_balance, withdraw_from_vault}; +use crate::vault::{ + deposit_into_vault, request_unlock_from_vault, update_vault_coin_balance, withdraw_from_vault, + withdraw_unlocked_from_vault, +}; use crate::liquidate::{assert_health_factor_improved, liquidate_coin}; use crate::swap::swap_exact_in; @@ -210,6 +213,20 @@ pub fn dispatch_actions( vault: vault.check(deps.api)?, amount: *amount, }), + Action::VaultRequestUnlock { vault, amount } => { + callbacks.push(CallbackMsg::VaultRequestUnlock { + account_id: account_id.to_string(), + vault: vault.check(deps.api)?, + amount: *amount, + }) + } + Action::VaultWithdrawUnlocked { id, vault } => { + callbacks.push(CallbackMsg::VaultWithdrawUnlocked { + account_id: account_id.to_string(), + vault: vault.check(deps.api)?, + position_id: *id, + }) + } } } @@ -307,6 +324,16 @@ pub fn execute_callback( vault, amount, } => withdraw_from_vault(deps, env, &account_id, vault, amount, true), + CallbackMsg::VaultRequestUnlock { + account_id, + vault, + amount, + } => request_unlock_from_vault(deps, &account_id, vault, amount), + CallbackMsg::VaultWithdrawUnlocked { + account_id, + vault, + position_id, + } => withdraw_unlocked_from_vault(deps, env, &account_id, vault, position_id), } } diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index fad799fef..d608f064a 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,6 +1,7 @@ use cosmwasm_std::{Addr, Decimal, Empty, Uint128}; use cw_storage_plus::{Item, Map}; +use crate::vault::RequestTempStorage; use rover::adapters::swap::Swapper; use rover::adapters::{Oracle, RedBank, VaultPositionState}; @@ -20,3 +21,7 @@ pub const COIN_BALANCES: Map<(&str, &str), Uint128> = Map::new("coin_balance"); pub const DEBT_SHARES: Map<(&str, &str), Uint128> = Map::new("debt_shares"); // Map<(AccountId, Denom), Shares> pub const TOTAL_DEBT_SHARES: Map<&str, Uint128> = Map::new("total_debt_shares"); // Map pub const VAULT_POSITIONS: Map<(&str, Addr), VaultPositionState> = Map::new("vault_positions"); // Map<(AccountId, VaultAddr), VaultPositionState> + +// Temporary state to save variables to be used on reply handling +pub const VAULT_REQUEST_TEMP_STORAGE: Item = + Item::new("vault_request_temp_var"); diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index b9548fd54..3fb84dd01 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -1,8 +1,11 @@ -use cosmwasm_std::{Addr, Coin, Deps, Storage, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, Deps, Storage, Uint128}; +use std::collections::HashSet; +use std::hash::Hash; use rover::error::{ContractError, ContractResult}; +use rover::msg::query::CoinValue; -use crate::state::{ALLOWED_COINS, COIN_BALANCES, RED_BANK, TOTAL_DEBT_SHARES}; +use crate::state::{ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES}; pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, denom: &str) -> ContractResult<()> { let is_whitelisted = ALLOWED_COINS.has(storage, denom); @@ -77,3 +80,35 @@ pub fn debt_shares_to_amount( amount, }) } + +pub fn coin_value(deps: &Deps, coin: &Coin) -> ContractResult { + let oracle = ORACLE.load(deps.storage)?; + let res = oracle.query_price(&deps.querier, &coin.denom)?; + let decimal_amount = Decimal::from_atomics(coin.amount, 0)?; + let value = res.price.checked_mul(decimal_amount)?; + Ok(CoinValue { + denom: coin.denom.clone(), + amount: coin.amount, + price: res.price, + value, + }) +} + +pub trait IntoUint128 { + fn uint128(&self) -> Uint128; +} + +impl IntoUint128 for Decimal { + fn uint128(&self) -> Uint128 { + *self * Uint128::new(1) + } +} + +pub fn contents_equal(vec_a: &[T], vec_b: &[T]) -> bool +where + T: Eq + Hash, +{ + let set_a: HashSet<_> = vec_a.iter().collect(); + let set_b: HashSet<_> = vec_b.iter().collect(); + set_a == set_b +} diff --git a/contracts/credit-manager/src/vault/deposit.rs b/contracts/credit-manager/src/vault/deposit.rs index 29154f089..2a2ee92cd 100644 --- a/contracts/credit-manager/src/vault/deposit.rs +++ b/contracts/credit-manager/src/vault/deposit.rs @@ -2,15 +2,14 @@ use cosmwasm_std::{ to_binary, Addr, Coin, CosmosMsg, DepsMut, QuerierWrapper, Response, Uint128, WasmMsg, }; -use rover::adapters::{Vault, VaultPositionState}; +use rover::adapters::{Vault, VaultPositionUpdate}; use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg; use rover::traits::Stringify; -use crate::state::VAULT_POSITIONS; -use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance}; -use crate::vault::utils::assert_vault_is_whitelisted; +use crate::utils::{assert_coins_are_whitelisted, contents_equal, decrement_coin_balance}; +use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; pub fn deposit_into_vault( deps: DepsMut, @@ -61,24 +60,15 @@ pub fn update_vault_coin_balance( } let diff = current_balance.checked_sub(previous_total_balance)?; - let vault_info = vault.query_vault_info(&deps.querier)?; + let vault_info = vault.query_info(&deps.querier)?; - // Increment token's vault position - VAULT_POSITIONS.update( + update_vault_position( deps.storage, - (account_id, vault.address), - |position_opt| -> ContractResult<_> { - let p = position_opt.unwrap_or_default(); - match vault_info.lockup { - None => Ok(VaultPositionState { - unlocked: p.unlocked.checked_add(diff)?, - locked: p.locked, - }), - Some(_) => Ok(VaultPositionState { - unlocked: p.unlocked, - locked: p.locked.checked_add(diff)?, - }), - } + account_id, + &vault.address, + match vault_info.lockup { + None => VaultPositionUpdate::IncrementUnlocked(diff), + Some(_) => VaultPositionUpdate::IncrementLocked(diff), }, )?; @@ -93,20 +83,27 @@ pub fn update_vault_coin_balance( pub fn assert_denoms_match_vault_reqs( querier: QuerierWrapper, vault: &Vault, - assets: &[Coin], + coins: &[Coin], ) -> ContractResult<()> { - let vault_info = vault.query_vault_info(&querier)?; + let vault_info = vault.query_info(&querier)?; - let all_req_coins_present = vault_info - .coins + // Check if coins match one of the accepted combinations for vault + let denoms = coins.iter().map(|c| c.denom.clone()).collect::>(); + let matched_combo = vault_info + .accepts .iter() - .all(|coin| assets.iter().any(|req_coin| req_coin.denom == coin.denom)); + .any(|combo| contents_equal(combo, &denoms)); - if !all_req_coins_present || assets.len() != vault_info.coins.len() { + if !matched_combo { return Err(ContractError::RequirementsNotMet(format!( "Required assets: {} -- do not match given assets: {}", - vault_info.coins.as_slice().to_string(), - assets.to_string() + vault_info + .accepts + .iter() + .map(|v| v.join(", ")) + .collect::>() + .join(" or "), + coins.to_string() ))); } Ok(()) diff --git a/contracts/credit-manager/src/vault/mod.rs b/contracts/credit-manager/src/vault/mod.rs index 7eb1c36e5..527c24051 100644 --- a/contracts/credit-manager/src/vault/mod.rs +++ b/contracts/credit-manager/src/vault/mod.rs @@ -1,7 +1,11 @@ pub use self::deposit::*; +pub use self::request_unlock::*; pub use self::utils::*; pub use self::withdraw::*; +pub use self::withdraw_unlocked::*; mod deposit; +mod request_unlock; mod utils; mod withdraw; +mod withdraw_unlocked; diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs new file mode 100644 index 000000000..83ad39009 --- /dev/null +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -0,0 +1,80 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Coin, DepsMut, Reply, Response, Uint128}; + +use crate::state::VAULT_REQUEST_TEMP_STORAGE; +use rover::adapters::{Vault, VaultPositionUpdate, VaultUnlockingId}; +use rover::error::{ContractError, ContractResult}; +use rover::extensions::AttrParse; + +use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; + +#[cw_serde] +pub struct RequestTempStorage { + pub account_id: String, + pub amount: Uint128, +} + +pub fn request_unlock_from_vault( + deps: DepsMut, + account_id: &str, + vault: Vault, + amount: Uint128, +) -> ContractResult { + assert_vault_is_whitelisted(deps.storage, &vault)?; + + let vault_info = vault.query_info(&deps.querier)?; + if vault_info.lockup.is_none() { + return Err(ContractError::RequirementsNotMet( + "This vault does not require lockup. Call withdraw directly.".to_string(), + )); + } + + VAULT_REQUEST_TEMP_STORAGE.save( + deps.storage, + &RequestTempStorage { + account_id: account_id.to_string(), + amount, + }, + )?; + + let request_unlock_msg = vault.request_unlock_msg(&[Coin { + denom: vault_info.vault_coin_denom, + amount, + }])?; + + Ok(Response::new() + .add_submessage(request_unlock_msg) + .add_attribute("action", "rover/credit_manager/vault/request_unlock")) +} + +pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResult { + let RequestTempStorage { account_id, amount } = + VAULT_REQUEST_TEMP_STORAGE.load(deps.storage)?; + + let unlock_event = reply.parse_unlock_event()?; + let vault_addr = deps.api.addr_validate(unlock_event.vault_addr.as_str())?; + + update_vault_position( + deps.storage, + &account_id, + &vault_addr, + VaultPositionUpdate::AddUnlocking(VaultUnlockingId { + id: unlock_event.id, + amount, + }), + )?; + + update_vault_position( + deps.storage, + &account_id, + &vault_addr, + VaultPositionUpdate::DecrementLocked(amount), + )?; + + VAULT_REQUEST_TEMP_STORAGE.remove(deps.storage); + + Ok(Response::new().add_attribute( + "action", + "rover/credit_manager/vault/unlock_request/handle_reply", + )) +} diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 1ed5d918e..8fed59762 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,9 +1,10 @@ -use cosmwasm_std::{Coin, Deps, Storage, Uint128}; +use cosmwasm_std::{Addr, Coin, Deps, StdResult, Storage}; -use rover::adapters::{Vault, VaultPosition, VaultPositionState}; +use rover::adapters::{Vault, VaultPosition, VaultPositionState, VaultPositionUpdate}; use rover::error::{ContractError, ContractResult}; use crate::state::{ALLOWED_VAULTS, VAULT_POSITIONS}; +use crate::update_coin_balances::query_balances; pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> ContractResult<()> { let is_whitelisted = ALLOWED_VAULTS.has(storage, &vault.address); @@ -13,31 +14,56 @@ pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> Ok(()) } -pub fn decrement_vault_position( +pub fn update_vault_position( storage: &mut dyn Storage, account_id: &str, - vault: &Vault, - amount: Uint128, - force: bool, + vault_addr: &Addr, + update: VaultPositionUpdate, ) -> ContractResult { - let path = VAULT_POSITIONS.key((account_id, vault.address.clone())); - let mut position = path.load(storage)?; + let path = VAULT_POSITIONS.key((account_id, vault_addr.clone())); + let mut new_position = path.may_load(storage)?.unwrap_or_default(); - // Force indicates that the vault is one with a required lockup that needs to be broken - // In this case, we'll need to withdraw from the locked bucket - if force { - position.locked = position.locked.checked_sub(amount)?; - } else { - position.unlocked = position.unlocked.checked_sub(amount)?; + match update { + VaultPositionUpdate::DecrementUnlocked(amount) => { + new_position.unlocked = new_position.unlocked.checked_sub(amount)?; + } + VaultPositionUpdate::IncrementUnlocked(amount) => { + new_position.unlocked = new_position.unlocked.checked_add(amount)?; + } + VaultPositionUpdate::DecrementLocked(amount) => { + new_position.locked = new_position.locked.checked_sub(amount)?; + } + VaultPositionUpdate::IncrementLocked(amount) => { + new_position.locked = new_position.locked.checked_add(amount)?; + } + VaultPositionUpdate::AddUnlocking(position) => { + new_position.unlocking.push(position); + } + VaultPositionUpdate::RemoveUnlocking(id) => new_position.unlocking.retain(|p| p.id != id), } - if position == VaultPositionState::default() { + if new_position == VaultPositionState::default() { path.remove(storage); } else { - path.save(storage, &position)?; + path.save(storage, &new_position)?; } + Ok(new_position) +} - Ok(position) +/// Returns the denoms you may receive on a withdraw +/// Inferred by vault entry requirements +pub fn query_withdraw_denom_balances( + deps: Deps, + rover_addr: &Addr, + vault: &Vault, +) -> StdResult> { + let vault_info = vault.query_info(&deps.querier)?; + let denoms = vault_info + .accepts + .iter() + .flat_map(|v| v.iter().map(|s| s.as_str())) + .collect::>(); + query_balances(deps, rover_addr, denoms.as_slice()) } /// Does a simulated withdraw from multiple vault positions to see what assets would be returned diff --git a/contracts/credit-manager/src/vault/withdraw.rs b/contracts/credit-manager/src/vault/withdraw.rs index 8d489a97e..235499074 100644 --- a/contracts/credit-manager/src/vault/withdraw.rs +++ b/contracts/credit-manager/src/vault/withdraw.rs @@ -1,13 +1,13 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, Uint128, WasmMsg}; -use rover::adapters::Vault; +use rover::adapters::{Vault, VaultPositionUpdate}; use rover::error::ContractResult; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg as RoverExecuteMsg; -use rover::traits::Denoms; -use crate::update_coin_balances::query_balances; -use crate::vault::utils::{assert_vault_is_whitelisted, decrement_vault_position}; +use crate::vault::utils::{ + assert_vault_is_whitelisted, query_withdraw_denom_balances, update_vault_position, +}; pub fn withdraw_from_vault( deps: DepsMut, @@ -19,18 +19,25 @@ pub fn withdraw_from_vault( ) -> ContractResult { assert_vault_is_whitelisted(deps.storage, &vault)?; - decrement_vault_position(deps.storage, account_id, &vault, amount, force)?; + // Force indicates that the vault is one with a required lockup that needs to be broken + // In this case, we'll need to withdraw from the locked bucket + update_vault_position( + deps.storage, + account_id, + &vault.address, + if force { + VaultPositionUpdate::DecrementLocked(amount) + } else { + VaultPositionUpdate::DecrementUnlocked(amount) + }, + )?; // Sends vault coins to vault in exchange for underlying assets let withdraw_msg = vault.withdraw_msg(&deps.querier, amount, force)?; // Updates coin balances for account after a vault withdraw has taken place - let vault_info = vault.query_vault_info(&deps.querier)?; - let previous_balances = query_balances( - deps.as_ref(), - &env.contract.address, - &vault_info.coins.to_denoms(), - )?; + let previous_balances = + query_withdraw_denom_balances(deps.as_ref(), &env.contract.address, &vault)?; let update_coin_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: env.contract.address.to_string(), funds: vec![], diff --git a/contracts/credit-manager/src/vault/withdraw_unlocked.rs b/contracts/credit-manager/src/vault/withdraw_unlocked.rs new file mode 100644 index 000000000..092f30883 --- /dev/null +++ b/contracts/credit-manager/src/vault/withdraw_unlocked.rs @@ -0,0 +1,61 @@ +use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, Uint128, WasmMsg}; + +use rover::adapters::{Vault, VaultPositionUpdate}; +use rover::error::{ContractError, ContractResult}; +use rover::msg::execute::CallbackMsg; +use rover::msg::ExecuteMsg; + +use crate::state::VAULT_POSITIONS; +use crate::vault::utils::{ + assert_vault_is_whitelisted, query_withdraw_denom_balances, update_vault_position, +}; + +pub fn withdraw_unlocked_from_vault( + deps: DepsMut, + env: Env, + account_id: &str, + vault: Vault, + position_id: Uint128, +) -> ContractResult { + assert_vault_is_whitelisted(deps.storage, &vault)?; + + let vault_position = VAULT_POSITIONS.load(deps.storage, (account_id, vault.address.clone()))?; + + let matching_unlock = vault_position + .unlocking + .iter() + .find(|p| p.id == position_id) + .ok_or_else(|| ContractError::NoPositionMatch(position_id.to_string()))?; + + let matching_unlock = vault.query_unlocking_position_info(&deps.querier, matching_unlock.id)?; + + if matching_unlock.unlocked_at > env.block.time { + return Err(ContractError::UnlockNotReady {}); + } + + update_vault_position( + deps.storage, + account_id, + &vault.address, + VaultPositionUpdate::RemoveUnlocking(position_id), + )?; + + // Updates coin balances for account after the withdraw has taken place + let previous_balances = + query_withdraw_denom_balances(deps.as_ref(), &env.contract.address, &vault)?; + let update_coin_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalances { + account_id: account_id.to_string(), + previous_balances, + }))?, + }); + + let withdraw_unlocked_msg = vault.withdraw_unlocked_msg(position_id)?; + + Ok(Response::new() + .add_message(withdraw_unlocked_msg) + .add_message(update_coin_balance_msg) + .add_attribute("action", "rover/credit_manager/vault/unlock")) +} diff --git a/contracts/credit-manager/tests/helpers/assertions.rs b/contracts/credit-manager/tests/helpers/assertions.rs index d1b5cdd5d..f7568383a 100644 --- a/contracts/credit-manager/tests/helpers/assertions.rs +++ b/contracts/credit-manager/tests/helpers/assertions.rs @@ -1,5 +1,7 @@ use anyhow::Result as AnyResult; +use credit_manager::utils::contents_equal; use cw_multi_test::AppResponse; +use std::hash::Hash; use rover::error::ContractError; @@ -13,8 +15,9 @@ pub fn assert_err(res: AnyResult, err: ContractError) { } } -pub fn assert_contents_equal(vec_a: Vec, vec_b: Vec) { - assert_eq!(vec_a.len(), vec_b.len()); - assert!(vec_a.iter().all(|item| vec_b.contains(item))); - assert!(vec_b.iter().all(|item| vec_a.contains(item))); +pub fn assert_contents_equal(vec_a: &[T], vec_b: &[T]) +where + T: Eq + Hash, +{ + assert!(contents_equal(vec_a, vec_b)) } diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index 924130c66..0744b0618 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -21,9 +21,9 @@ pub fn build_mock_vaults(count: usize) -> Vec { .into_iter() .map(|i| { VaultTestInfo { - lp_token_denom: format!("vault_{}", i), + denom: format!("vault_{}", i), lockup: Some(1_209_600), // 14 days - asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], } }) .collect() diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index 1d52bf604..9c87a39c3 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -4,7 +4,7 @@ use cw_multi_test::{App, Contract, ContractWrapper}; use account_nft::contract::{ execute as cw721Execute, instantiate as cw721Instantiate, query as cw721Query, }; -use credit_manager::contract::{execute, instantiate, query}; +use credit_manager::contract::{execute, instantiate, query, reply}; use mock_oracle::contract::{ execute as oracleExecute, instantiate as oracleInstantiate, query as oracleQuery, }; @@ -23,7 +23,7 @@ pub fn mock_app() -> App { } pub fn mock_rover_contract() -> Box> { - let contract = ContractWrapper::new(execute, instantiate, query); + let contract = ContractWrapper::new(execute, instantiate, query).with_reply(reply); Box::new(contract) } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 01612674a..a782cc94f 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -26,6 +26,8 @@ use rover::msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtShares, HealthResponse, Positions, SharesResponseItem, VaultPositionResponseItem, VaultWithBalance, }; +use rover::msg::vault::QueryMsg::UnlockingPositionsForAddr; +use rover::msg::vault::UnlockingPosition; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::helpers::{ @@ -237,9 +239,9 @@ impl MockEnv { let info = v .check(&MockApi::default()) .unwrap() - .query_vault_info(&self.app.wrap()) + .query_info(&self.app.wrap()) .unwrap(); - vault.lp_token_denom == info.token_denom + vault.denom == info.vault_coin_denom }) .unwrap() .clone() @@ -333,6 +335,34 @@ impl MockEnv { .unwrap() } + pub fn query_unlocking_position_info( + &self, + vault: &VaultUnchecked, + id: Uint128, + ) -> UnlockingPosition { + vault + .check(&MockApi::default()) + .unwrap() + .query_unlocking_position_info(&self.app.wrap(), id) + .unwrap() + } + + pub fn query_unlocking_positions( + &self, + vault: &VaultUnchecked, + manager_contract_addr: &Addr, + ) -> Vec { + self.app + .wrap() + .query_wasm_smart( + vault.address.to_string(), + &UnlockingPositionsForAddr { + addr: manager_contract_addr.to_string(), + }, + ) + .unwrap() + } + pub fn query_total_vault_coin_balance(&self, vault: &VaultUnchecked) -> Uint128 { self.app .wrap() @@ -590,9 +620,9 @@ impl MockEnvBuilder { code_id, Addr::unchecked("vault-instantiator"), &VaultInstantiateMsg { - lp_token_denom: vault.clone().lp_token_denom, + lp_token_denom: vault.clone().denom, lockup: vault.lockup, - asset_denoms: vault.clone().asset_denoms, + asset_denoms: vault.clone().underlying_denoms, oracle, }, &[], @@ -600,7 +630,7 @@ impl MockEnvBuilder { None, ) .unwrap(); - self.fund_vault(&addr, &vault.lp_token_denom); + self.fund_vault(&addr, &vault.denom); VaultBase::new(addr) } diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 52357a307..3ea6c1a65 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -17,9 +17,9 @@ pub struct CoinInfo { #[cw_serde] pub struct VaultTestInfo { - pub lp_token_denom: String, + pub denom: String, pub lockup: Option, - pub asset_denoms: Vec, + pub underlying_denoms: Vec, } impl CoinInfo { diff --git a/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs b/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs index 04f1dd682..a99cd7fa5 100644 --- a/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs +++ b/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs @@ -41,17 +41,17 @@ fn test_pagination_on_allowed_vaults_query_works() { .chain(vaults_res_c.iter().cloned()) .chain(vaults_res_d.iter().cloned()) .map(|v| v.check(&MockApi::default()).unwrap()) - .map(|v| v.query_vault_info(&mock.app.wrap()).unwrap()) - .map(|info| info.token_denom) + .map(|v| v.query_info(&mock.app.wrap()).unwrap()) + .map(|info| info.vault_coin_denom) .collect::>(); assert_eq!(combined.len(), allowed_vaults.len()); assert_contents_equal( - allowed_vaults + &allowed_vaults .iter() - .map(|v| v.lp_token_denom.clone()) - .collect(), - combined, + .map(|v| v.denom.clone()) + .collect::>(), + &combined, ) } diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index 931ad6a03..880f35ec8 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -118,17 +118,17 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { .chain(vaults_res_b.iter().cloned()) .chain(vaults_res_c.iter().cloned()) .chain(vaults_res_d.iter().cloned()) - .map(|v| v.vault.query_vault_info(&mock.app.wrap()).unwrap()) - .map(|info| info.token_denom) + .map(|v| v.vault.query_info(&mock.app.wrap()).unwrap()) + .map(|info| info.vault_coin_denom) .collect::>(); assert_eq!(combined.len(), all_vaults.len()); assert_contents_equal( - all_vaults + &all_vaults .iter() - .map(|v| v.lp_token_denom.clone()) - .collect(), - combined, + .map(|v| v.denom.clone()) + .collect::>(), + &combined, ) } diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index b0145a775..4ba2e950e 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -131,8 +131,8 @@ fn test_pagination_on_all_vault_positions_query_works() { .chain(vaults_res_b.iter().cloned()) .chain(vaults_res_c.iter().cloned()) .chain(vaults_res_d.iter().cloned()) - .map(|v| v.position.vault.query_vault_info(&mock.app.wrap()).unwrap()) - .map(|info| info.token_denom) + .map(|v| v.position.vault.query_info(&mock.app.wrap()).unwrap()) + .map(|info| info.vault_coin_denom) .collect::>(); let deduped = combined.iter().unique().cloned().collect::>(); @@ -140,10 +140,10 @@ fn test_pagination_on_all_vault_positions_query_works() { assert_eq!(deduped.len(), all_vaults.len()); assert_contents_equal( - all_vaults + &all_vaults .iter() - .map(|v| v.lp_token_denom.clone()) - .collect(), - deduped, + .map(|v| v.denom.clone()) + .collect::>(), + &deduped, ) } diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 3b0e42020..8a9024672 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -33,19 +33,19 @@ fn test_nft_contract_addr_not_set_on_instantiate() { fn test_allowed_vaults_set_on_instantiate() { let allowed_vaults = vec![ VaultTestInfo { - lp_token_denom: "vault_contract_1".to_string(), + denom: "vault_contract_1".to_string(), lockup: None, - asset_denoms: vec![], + underlying_denoms: vec![], }, VaultTestInfo { - lp_token_denom: "vault_contract_2".to_string(), + denom: "vault_contract_2".to_string(), lockup: None, - asset_denoms: vec![], + underlying_denoms: vec![], }, VaultTestInfo { - lp_token_denom: "vault_contract_3".to_string(), + denom: "vault_contract_3".to_string(), lockup: None, - asset_denoms: vec![], + underlying_denoms: vec![], }, ]; @@ -55,11 +55,11 @@ fn test_allowed_vaults_set_on_instantiate() { .unwrap(); let res = mock.query_allowed_vaults(None, None); assert_contents_equal( - res, - allowed_vaults + &res, + &allowed_vaults .iter() .map(|info| mock.get_vault(info)) - .collect(), + .collect::>(), ); } @@ -94,11 +94,11 @@ fn test_allowed_coins_set_on_instantiate() { let res = mock.query_allowed_coins(None, None); assert_contents_equal( - res, - allowed_coins + &res, + &allowed_coins .iter() .map(|info| info.denom.clone()) - .collect(), + .collect::>(), ) } diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 402cfda37..358735067 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -75,9 +75,9 @@ fn test_vault_positions_contribute_to_health() { let uatom_info = uatom_info(); let leverage_vault = VaultTestInfo { - lp_token_denom: "uleverage".to_string(), + denom: "uleverage".to_string(), lockup: None, - asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], }; let liquidatee = Addr::unchecked("liquidatee"); diff --git a/contracts/credit-manager/tests/test_vault_deposit.rs b/contracts/credit-manager/tests/test_vault_deposit.rs index 8986eec55..a24147605 100644 --- a/contracts/credit-manager/tests/test_vault_deposit.rs +++ b/contracts/credit-manager/tests/test_vault_deposit.rs @@ -41,9 +41,9 @@ fn test_only_account_owner_can_take_action() { fn test_all_deposit_coins_are_whitelisted() { let uatom = uatom_info(); let leverage_vault = VaultTestInfo { - lp_token_denom: "uleverage".to_string(), + denom: "uleverage".to_string(), lockup: None, - asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], }; let user = Addr::unchecked("user"); @@ -75,9 +75,9 @@ fn test_vault_is_whitelisted() { let uosmo = uosmo_info(); let leverage_vault = VaultTestInfo { - lp_token_denom: "uleverage".to_string(), + denom: "uleverage".to_string(), lockup: None, - asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], }; let user = Addr::unchecked("user"); @@ -111,9 +111,9 @@ fn test_deposited_coins_match_vault_requirements() { let uosmo = uosmo_info(); let leverage_vault = VaultTestInfo { - lp_token_denom: "uleverage".to_string(), + denom: "uleverage".to_string(), lockup: None, - asset_denoms: vec!["uatom".to_string(), "ujake".to_string()], + underlying_denoms: vec!["uatom".to_string(), "ujake".to_string()], }; let user = Addr::unchecked("user"); @@ -149,9 +149,9 @@ fn test_fails_if_not_enough_funds_for_deposit() { let uosmo = uosmo_info(); let leverage_vault = VaultTestInfo { - lp_token_denom: "uleverage".to_string(), + denom: "uleverage".to_string(), lockup: None, - asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], }; let user = Addr::unchecked("user"); @@ -193,9 +193,9 @@ fn test_successful_deposit_into_locked_vault() { let uosmo = uosmo_info(); let leverage_vault = VaultTestInfo { - lp_token_denom: "uleverage".to_string(), + denom: "uleverage".to_string(), lockup: Some(1_209_600u64), - asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], }; let user = Addr::unchecked("user"); @@ -229,7 +229,7 @@ fn test_successful_deposit_into_locked_vault() { ) .unwrap(); - let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); let res = mock.query_positions(&account_id); @@ -250,7 +250,7 @@ fn test_successful_deposit_into_locked_vault() { let balance = mock.query_total_vault_coin_balance(&vault); assert_eq!(balance, STARTING_VAULT_SHARES); - let vault_token_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + let vault_token_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); assert_eq!(vault_token_balance.amount, STARTING_VAULT_SHARES) } @@ -260,9 +260,9 @@ fn test_successful_deposit_into_unlocked_vault() { let uosmo = uosmo_info(); let leverage_vault = VaultTestInfo { - lp_token_denom: "uleverage".to_string(), + denom: "uleverage".to_string(), lockup: None, - asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], }; let user = Addr::unchecked("user"); @@ -294,7 +294,7 @@ fn test_successful_deposit_into_unlocked_vault() { ) .unwrap(); - let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); let res = mock.query_positions(&account_id); @@ -315,6 +315,6 @@ fn test_successful_deposit_into_unlocked_vault() { let balance = mock.query_total_vault_coin_balance(&vault); assert_eq!(balance, STARTING_VAULT_SHARES); - let vault_token_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + let vault_token_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); assert_eq!(vault_token_balance.amount, STARTING_VAULT_SHARES) } diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs new file mode 100644 index 000000000..b15848b55 --- /dev/null +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -0,0 +1,280 @@ +use cosmwasm_std::OverflowOperation::Sub; +use cosmwasm_std::{coin, coins, Addr, OverflowError, Uint128}; +use cw_multi_test::{BankSudo, SudoMsg}; + +use mock_vault::contract::STARTING_VAULT_SHARES; +use rover::adapters::VaultUnchecked; +use rover::error::ContractError; +use rover::msg::execute::Action::{Deposit, VaultDeposit, VaultRequestUnlock}; + +use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv, VaultTestInfo}; + +pub mod helpers; + +#[test] +fn test_only_owner_can_request_unlocked() { + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: Some(1_209_600), // 14 days + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_vaults(&[leverage_vault.clone()]) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.update_credit_account( + &account_id, + &bad_guy, + vec![VaultRequestUnlock { + vault, + amount: STARTING_VAULT_SHARES, + }], + &[], + ); + + assert_err( + res, + ContractError::NotTokenOwner { + user: bad_guy.to_string(), + account_id, + }, + ); +} + +#[test] +fn test_can_only_take_action_on_whitelisted_vaults() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().build().unwrap(); + + let vault = VaultUnchecked::new("xvault".to_string()); + let account_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![VaultRequestUnlock { + vault, + amount: STARTING_VAULT_SHARES, + }], + &[], + ); + + assert_err(res, ContractError::NotWhitelisted("xvault".to_string())); +} + +#[test] +fn test_request_when_unnecessary() { + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: None, + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_vaults(&[leverage_vault.clone()]) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![VaultRequestUnlock { + vault, + amount: STARTING_VAULT_SHARES, + }], + &[], + ); + + assert_err( + res, + ContractError::RequirementsNotMet( + "This vault does not require lockup. Call withdraw directly.".to_string(), + ), + ); +} + +#[test] +fn test_no_funds_for_request() { + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: Some(1_209_600), // 14 days + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_vaults(&[leverage_vault.clone()]) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + // Seed Rover with vault tokens + mock.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: mock.rover.clone().to_string(), + amount: coins(5_000_000, leverage_vault.denom), + })) + .unwrap(); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![VaultRequestUnlock { + vault, + amount: STARTING_VAULT_SHARES, + }], + &[], + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: Sub, + operand1: "0".to_string(), + operand2: STARTING_VAULT_SHARES.to_string(), + }), + ); +} + +#[test] +fn test_not_enough_funds_for_request() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: Some(1_209_600), // 14 days + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + // Seed Rover with vault tokens + mock.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: mock.rover.clone().to_string(), + amount: coins(5_000_000, leverage_vault.denom), + })) + .unwrap(); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(coin(200, uatom.denom)), + Deposit(coin(400, uosmo.denom)), + VaultDeposit { + vault: vault.clone(), + coins: vec![coin(23, "uatom"), coin(120, "uosmo")], + }, + VaultRequestUnlock { + vault, + amount: STARTING_VAULT_SHARES + Uint128::new(100), + }, + ], + &[coin(200, "uatom"), coin(400, "uosmo")], + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: Sub, + operand1: STARTING_VAULT_SHARES.to_string(), + operand2: (STARTING_VAULT_SHARES + Uint128::new(100)).to_string(), + }), + ); +} + +#[test] +fn test_request_unlocked() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: Some(1_209_600), // 14 days + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(coin(200, uatom.denom)), + Deposit(coin(400, uosmo.denom)), + VaultDeposit { + vault: vault.clone(), + coins: vec![coin(23, "uatom"), coin(120, "uosmo")], + }, + VaultRequestUnlock { + vault: vault.clone(), + amount: STARTING_VAULT_SHARES, + }, + ], + &[coin(200, "uatom"), coin(400, "uosmo")], + ) + .unwrap(); + + // Assert token's position with Rover + let res = mock.query_positions(&account_id); + assert_eq!(res.vaults.len(), 1); + let unlocking = res.vaults.first().unwrap().state.unlocking.clone(); + assert_eq!(unlocking.len(), 1); + let first = unlocking.first().unwrap(); + assert_eq!(first.amount, STARTING_VAULT_SHARES); + let expected_unlock_time = + mock.app.block_info().time.seconds() + leverage_vault.lockup.unwrap(); + let unlocking_position = mock.query_unlocking_position_info(&vault, first.id); + assert_eq!( + unlocking_position.unlocked_at.seconds(), + expected_unlock_time + ); + + // Assert Rover's position w/ Vault + let res = mock.query_unlocking_positions(&vault, &mock.rover); + assert_eq!(res.len(), 1); + assert_eq!(res.first().unwrap().amount, STARTING_VAULT_SHARES); + assert_eq!( + res.first().unwrap().unlocked_at.seconds(), + expected_unlock_time + ); +} diff --git a/contracts/credit-manager/tests/test_vault_withdraw.rs b/contracts/credit-manager/tests/test_vault_withdraw.rs index cd6fe42f8..206d8feb4 100644 --- a/contracts/credit-manager/tests/test_vault_withdraw.rs +++ b/contracts/credit-manager/tests/test_vault_withdraw.rs @@ -65,9 +65,9 @@ fn test_no_unlocked_vault_coins_to_withdraw() { let uosmo = uosmo_info(); let leverage_vault = VaultTestInfo { - lp_token_denom: "uleverage".to_string(), + denom: "uleverage".to_string(), lockup: Some(213231), - asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], }; let user = Addr::unchecked("user"); @@ -115,9 +115,9 @@ fn test_no_unlocked_vault_coins_to_withdraw() { #[test] fn test_force_withdraw_can_only_be_called_by_rover() { let leverage_vault = VaultTestInfo { - lp_token_denom: "uleverage".to_string(), + denom: "uleverage".to_string(), lockup: Some(213231), - asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], }; let user = Addr::unchecked("user"); @@ -146,9 +146,9 @@ fn test_force_withdraw_breaks_lock() { let uosmo = uosmo_info(); let leverage_vault = VaultTestInfo { - lp_token_denom: "uleverage".to_string(), + denom: "uleverage".to_string(), lockup: Some(213231), - asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], }; let user = Addr::unchecked("user"); @@ -211,7 +211,7 @@ fn test_force_withdraw_breaks_lock() { assert_eq!(osmo.amount, Uint128::from(200u128)); // Assert Rover does not have the vault tokens anymore - let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); assert_eq!(Uint128::zero(), lp_balance.amount); } @@ -221,9 +221,9 @@ fn test_withdraw_with_unlocked_vault_coins() { let uosmo = uosmo_info(); let leverage_vault = VaultTestInfo { - lp_token_denom: "uleverage".to_string(), + denom: "uleverage".to_string(), lockup: None, - asset_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], }; let user = Addr::unchecked("user"); @@ -272,7 +272,7 @@ fn test_withdraw_with_unlocked_vault_coins() { assert_eq!(osmo.amount, Uint128::from(100u128)); // Assert Rover has the vault tokens - let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); mock.update_credit_account( @@ -301,7 +301,7 @@ fn test_withdraw_with_unlocked_vault_coins() { assert_eq!(osmo.amount, Uint128::from(200u128)); // Assert Rover does not have the vault tokens anymore - let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.lp_token_denom); + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); assert_eq!(Uint128::zero(), lp_balance.amount); } diff --git a/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs b/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs new file mode 100644 index 000000000..9c026ee96 --- /dev/null +++ b/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs @@ -0,0 +1,297 @@ +use cosmwasm_std::StdError::NotFound; +use cosmwasm_std::{coin, Addr, Uint128}; + +use mock_vault::contract::STARTING_VAULT_SHARES; +use rover::adapters::VaultUnchecked; +use rover::error::ContractError; +use rover::msg::execute::Action::{ + Deposit, VaultDeposit, VaultRequestUnlock, VaultWithdrawUnlocked, +}; +use rover::msg::query::Positions; + +use crate::helpers::{ + assert_err, get_coin, uatom_info, uosmo_info, AccountToFund, MockEnv, VaultTestInfo, +}; + +pub mod helpers; + +#[test] +fn test_only_owner_can_withdraw_unlocked_for_account() { + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: Some(1_209_600), // 14 days + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_vaults(&[leverage_vault.clone()]) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.update_credit_account( + &account_id, + &bad_guy, + vec![VaultWithdrawUnlocked { + id: Uint128::new(423), + vault, + }], + &[], + ); + + assert_err( + res, + ContractError::NotTokenOwner { + user: bad_guy.to_string(), + account_id, + }, + ); +} + +#[test] +fn test_can_only_take_action_on_whitelisted_vaults() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().build().unwrap(); + + let vault = VaultUnchecked::new("xvault".to_string()); + let account_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![VaultWithdrawUnlocked { + id: Uint128::new(234), + vault, + }], + &[], + ); + + assert_err(res, ContractError::NotWhitelisted("xvault".to_string())); +} + +#[test] +fn test_not_owner_of_unlocking_position() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: Some(1_209_600), // 14 days + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user_a = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id_a = mock.create_credit_account(&user_a).unwrap(); + + mock.update_credit_account( + &account_id_a, + &user_a, + vec![ + Deposit(coin(200, uatom.denom)), + Deposit(coin(400, uosmo.denom)), + VaultDeposit { + vault: vault.clone(), + coins: vec![coin(23, "uatom"), coin(120, "uosmo")], + }, + VaultRequestUnlock { + vault: vault.clone(), + amount: STARTING_VAULT_SHARES, + }, + ], + &[coin(200, "uatom"), coin(400, "uosmo")], + ) + .unwrap(); + + let res = mock.query_positions(&account_id_a); + assert_eq!(res.vaults.len(), 1); + let unlocking_id = res + .vaults + .first() + .unwrap() + .state + .unlocking + .first() + .unwrap() + .id; + + let user_b = Addr::unchecked("user_b"); + let account_id_b = mock.create_credit_account(&user_b).unwrap(); + + let res = mock.update_credit_account( + &account_id_b, + &user_b, + vec![VaultWithdrawUnlocked { + id: unlocking_id, + vault, + }], + &[], + ); + + assert_err( + res, + ContractError::Std(NotFound { + kind: "rover::adapters::vault::VaultPositionState".to_string(), + }), + ); +} + +#[test] +fn test_unlocking_position_not_ready() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: Some(1_209_600), // 14 days + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(coin(200, uatom.denom)), + Deposit(coin(400, uosmo.denom)), + VaultDeposit { + vault: vault.clone(), + coins: vec![coin(23, "uatom"), coin(120, "uosmo")], + }, + VaultRequestUnlock { + vault: vault.clone(), + amount: STARTING_VAULT_SHARES, + }, + ], + &[coin(200, "uatom"), coin(400, "uosmo")], + ) + .unwrap(); + + let Positions { vaults, .. } = mock.query_positions(&account_id); + + let position_id = vaults.first().unwrap().state.unlocking.first().unwrap().id; + + let res = mock.update_credit_account( + &account_id, + &user, + vec![VaultWithdrawUnlocked { + id: position_id, + vault, + }], + &[], + ); + + assert_err(res, ContractError::UnlockNotReady); +} + +#[test] +fn test_withdraw_unlock_success() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: Some(1_209_600), // 14 days + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(coin(200, uatom.denom)), + Deposit(coin(400, uosmo.denom)), + VaultDeposit { + vault: vault.clone(), + coins: vec![coin(200, "uatom"), coin(400, "uosmo")], + }, + VaultRequestUnlock { + vault: vault.clone(), + amount: STARTING_VAULT_SHARES, + }, + ], + &[coin(200, "uatom"), coin(400, "uosmo")], + ) + .unwrap(); + + let Positions { coins, .. } = mock.query_positions(&account_id); + assert_eq!(coins.len(), 0); + + mock.app.update_block(|block| { + block.time = block.time.plus_seconds(leverage_vault.lockup.unwrap()); + block.height += 1; + }); + + let Positions { vaults, .. } = mock.query_positions(&account_id); + + let position_id = vaults.first().unwrap().state.unlocking.first().unwrap().id; + + mock.update_credit_account( + &account_id, + &user, + vec![VaultWithdrawUnlocked { + id: position_id, + vault, + }], + &[], + ) + .unwrap(); + + let Positions { vaults, coins, .. } = mock.query_positions(&account_id); + + // Users vault position decrements + assert_eq!(vaults.len(), 0); + + // Users asset position increments + let atom = get_coin("uatom", &coins); + assert_eq!(atom.amount, Uint128::from(200u128)); + let osmo = get_coin("uosmo", &coins); + assert_eq!(osmo.amount, Uint128::from(400u128)); + + // Assert Rover indeed has those on hand in the bank + let atom = mock.query_balance(&mock.rover, "uatom"); + assert_eq!(atom.amount, Uint128::from(200u128)); + let osmo = mock.query_balance(&mock.rover, "uosmo"); + assert_eq!(osmo.amount, Uint128::from(400u128)); +} diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index 12ec69bab..693d8448b 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -10,8 +10,12 @@ use rover::msg::vault::{ExecuteMsg, QueryMsg}; use crate::deposit::deposit; use crate::error::ContractError; use crate::msg::InstantiateMsg; -use crate::query::{query_coins_for_shares, query_vault_coins_issued, query_vault_info}; -use crate::state::{ASSETS, CHAIN_BANK, LOCKUP_TIME, LP_TOKEN_DENOM, ORACLE}; +use crate::query::{ + query_coins_for_shares, query_unlocking_position, query_unlocking_positions, + query_vault_coins_issued, query_vault_info, +}; +use crate::state::{ASSETS, CHAIN_BANK, LOCKUP_TIME, LP_TOKEN_DENOM, NEXT_UNLOCK_ID, ORACLE}; +use crate::unlock::{request_unlock, withdraw_unlocked}; use crate::withdraw::{withdraw, withdraw_force}; pub const STARTING_VAULT_SHARES: Uint128 = Uint128::new(1_000_000); @@ -34,13 +38,14 @@ pub fn instantiate( ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; LP_TOKEN_DENOM.save(deps.storage, &msg.lp_token_denom)?; CHAIN_BANK.save(deps.storage, &DEFAULT_VAULT_TOKEN_PREFUND)?; + NEXT_UNLOCK_ID.save(deps.storage, &Uint128::new(1))?; Ok(Response::default()) } #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, - _: Env, + env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { @@ -48,6 +53,8 @@ pub fn execute( ExecuteMsg::Deposit {} => deposit(deps, info), ExecuteMsg::Withdraw {} => withdraw(deps, info), ExecuteMsg::ForceWithdraw {} => withdraw_force(deps, info), + ExecuteMsg::RequestUnlock {} => request_unlock(deps, env, info), + ExecuteMsg::WithdrawUnlocked { id } => withdraw_unlocked(deps, env, info, id), } } @@ -59,5 +66,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { to_binary(&query_coins_for_shares(deps.storage, amount)?) } QueryMsg::TotalVaultCoinsIssued {} => to_binary(&query_vault_coins_issued(deps.storage)?), + QueryMsg::UnlockingPositionsForAddr { addr } => { + to_binary(&query_unlocking_positions(deps, addr)?) + } + QueryMsg::UnlockingPosition { id } => to_binary(&query_unlocking_position(deps, id)?), } } diff --git a/contracts/mock-vault/src/error.rs b/contracts/mock-vault/src/error.rs index eb7d08110..739638dce 100644 --- a/contracts/mock-vault/src/error.rs +++ b/contracts/mock-vault/src/error.rs @@ -13,6 +13,12 @@ pub enum ContractError { #[error("{0}")] CheckedMultiply(#[from] CheckedMultiplyRatioError), + #[error("This vault does not require a lockup, just withdraw directly")] + NoLockupTime, + + #[error("There is more time left on the lock period")] + UnlockNotReady, + #[error("You must request an unlock first")] UnlockRequired, diff --git a/contracts/mock-vault/src/lib.rs b/contracts/mock-vault/src/lib.rs index 4cf11fd34..5556ca1de 100644 --- a/contracts/mock-vault/src/lib.rs +++ b/contracts/mock-vault/src/lib.rs @@ -4,4 +4,5 @@ pub mod error; pub mod msg; pub mod query; pub mod state; +pub mod unlock; pub mod withdraw; diff --git a/contracts/mock-vault/src/query.rs b/contracts/mock-vault/src/query.rs index 4a02b2ab7..4512c78ec 100644 --- a/contracts/mock-vault/src/query.rs +++ b/contracts/mock-vault/src/query.rs @@ -1,8 +1,8 @@ -use cosmwasm_std::{Coin, Deps, Order, StdResult, Storage, Uint128}; +use cosmwasm_std::{Coin, Deps, Order, StdError, StdResult, Storage, Uint128}; -use rover::msg::vault::VaultInfo; +use rover::msg::vault::{UnlockingPosition, VaultInfo}; -use crate::state::{ASSETS, LOCKUP_TIME, LP_TOKEN_DENOM, TOTAL_VAULT_SHARES}; +use crate::state::{ASSETS, LOCKUP_TIME, LP_TOKEN_DENOM, TOTAL_VAULT_SHARES, UNLOCKING_COINS}; pub fn query_coins_for_shares(storage: &dyn Storage, shares: Uint128) -> StdResult> { let total_shares_opt = TOTAL_VAULT_SHARES.may_load(storage)?; @@ -23,10 +23,12 @@ pub fn query_coins_for_shares(storage: &dyn Storage, shares: Uint128) -> StdResu } pub fn query_vault_info(deps: Deps) -> StdResult { + let all_coins = get_all_vault_coins(deps.storage)?; + let accepted_denoms = all_coins.iter().map(|c| c.denom.clone()).collect(); Ok(VaultInfo { - coins: get_all_vault_coins(deps.storage)?, + accepts: vec![accepted_denoms], lockup: LOCKUP_TIME.load(deps.storage)?, - token_denom: LP_TOKEN_DENOM.load(deps.storage)?, + vault_coin_denom: LP_TOKEN_DENOM.load(deps.storage)?, }) } @@ -40,6 +42,22 @@ pub fn get_all_vault_coins(storage: &dyn Storage) -> StdResult> { .collect() } +pub fn query_unlocking_position(deps: Deps, id: Uint128) -> StdResult { + UNLOCKING_COINS + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()? + .into_iter() + .flat_map(|(_, positions)| positions) + .find(|p| p.id == id) + .ok_or_else(|| StdError::generic_err("Id not found")) +} + +pub fn query_unlocking_positions(deps: Deps, addr: String) -> StdResult> { + let addr = deps.api.addr_validate(addr.as_str())?; + let res = UNLOCKING_COINS.load(deps.storage, addr)?; + Ok(res) +} + pub fn query_vault_coins_issued(storage: &dyn Storage) -> StdResult { TOTAL_VAULT_SHARES.load(storage) } diff --git a/contracts/mock-vault/src/state.rs b/contracts/mock-vault/src/state.rs index 03a85ceb4..a76645473 100644 --- a/contracts/mock-vault/src/state.rs +++ b/contracts/mock-vault/src/state.rs @@ -1,7 +1,8 @@ -use cosmwasm_std::Uint128; +use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::{Item, Map}; use rover::adapters::Oracle; +use rover::msg::vault::UnlockingPosition; pub const LP_TOKEN_DENOM: Item = Item::new("lp_token_denom"); pub const TOTAL_VAULT_SHARES: Item = Item::new("total_vault_shares"); @@ -11,3 +12,6 @@ pub const ORACLE: Item = Item::new("oracle"); // Used for mock LP token minting pub const CHAIN_BANK: Item = Item::new("chain_bank"); + +pub const UNLOCKING_COINS: Map> = Map::new("unlocking_coins"); +pub const NEXT_UNLOCK_ID: Item = Item::new("next_unlock_id"); diff --git a/contracts/mock-vault/src/unlock.rs b/contracts/mock-vault/src/unlock.rs new file mode 100644 index 000000000..0d00ee104 --- /dev/null +++ b/contracts/mock-vault/src/unlock.rs @@ -0,0 +1,67 @@ +use cosmwasm_std::{DepsMut, Env, Event, MessageInfo, Response, StdResult, Uint128}; + +use rover::msg::vault::{ + UnlockingPosition, UNLOCKING_POSITION_ATTR, UNLOCKING_POSITION_CREATED_EVENT_TYPE, +}; + +use crate::error::ContractError; +use crate::state::{LOCKUP_TIME, NEXT_UNLOCK_ID, UNLOCKING_COINS}; +use crate::withdraw::{_exchange, get_vault_token}; + +pub fn request_unlock( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> Result { + let lockup_time_opt = LOCKUP_TIME.load(deps.storage)?; + let lockup_time = lockup_time_opt.ok_or(ContractError::NoLockupTime {})?; + + let vault_tokens = get_vault_token(deps.storage, info.funds)?; + + let next_unlock_id = NEXT_UNLOCK_ID.load(deps.storage)?; + let unlocked_at = env.block.time.plus_seconds(lockup_time); + UNLOCKING_COINS.update(deps.storage, info.sender, |opt| -> StdResult<_> { + let mut unlocking_positions = opt.unwrap_or_default(); + unlocking_positions.push(UnlockingPosition { + id: next_unlock_id, + amount: vault_tokens.amount, + unlocked_at, + }); + Ok(unlocking_positions) + })?; + + NEXT_UNLOCK_ID.save(deps.storage, &(next_unlock_id + Uint128::from(1u128)))?; + + let event = Event::new(UNLOCKING_POSITION_CREATED_EVENT_TYPE) + .add_attribute(UNLOCKING_POSITION_ATTR, next_unlock_id); + Ok(Response::new().add_event(event)) +} + +pub fn withdraw_unlocked( + deps: DepsMut, + env: Env, + info: MessageInfo, + id: Uint128, +) -> Result { + let unlocking_positions = UNLOCKING_COINS + .may_load(deps.storage, info.sender.clone())? + .ok_or(ContractError::UnlockRequired {})?; + + let matching_position = unlocking_positions + .iter() + .find(|p| p.id == id) + .ok_or(ContractError::UnlockRequired {})? + .clone(); + + if matching_position.unlocked_at > env.block.time { + return Err(ContractError::UnlockNotReady {}); + } + + let remaining = unlocking_positions + .into_iter() + .filter(|p| p.id != id) + .collect(); + UNLOCKING_COINS.save(deps.storage, info.sender.clone(), &remaining)?; + + _exchange(deps.storage, info.sender, matching_position.amount) +} diff --git a/packages/rover/src/adapters/vault.rs b/packages/rover/src/adapters/vault.rs index 37bc7ebba..d3ed7783b 100644 --- a/packages/rover/src/adapters/vault.rs +++ b/packages/rover/src/adapters/vault.rs @@ -1,19 +1,42 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, Decimal, OverflowError, - QuerierWrapper, QueryRequest, StdResult, Uint128, WasmMsg, WasmQuery, + QuerierWrapper, QueryRequest, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, }; use crate::adapters::Oracle; use crate::error::ContractResult; -use crate::msg::vault::{ExecuteMsg, QueryMsg, VaultInfo}; +use crate::msg::vault::{ExecuteMsg, QueryMsg, UnlockingPosition, VaultInfo}; use crate::traits::Stringify; +pub const VAULT_REQUEST_REPLY_ID: u64 = 10_001; + #[cw_serde] #[derive(Default)] pub struct VaultPositionState { pub unlocked: Uint128, pub locked: Uint128, + pub unlocking: Vec, +} + +#[cw_serde] +pub enum VaultPositionUpdate { + DecrementUnlocked(Uint128), + IncrementUnlocked(Uint128), + DecrementLocked(Uint128), + IncrementLocked(Uint128), + AddUnlocking(VaultUnlockingId), + RemoveUnlocking(UnlockingId), +} + +pub type UnlockingId = Uint128; + +#[cw_serde] +pub struct VaultUnlockingId { + /// Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call. + pub id: UnlockingId, + /// Number of vault tokens + pub amount: Uint128, } impl VaultPositionState { @@ -29,6 +52,7 @@ pub struct VaultPosition { } #[cw_serde] +#[derive(Eq, Hash)] pub struct VaultBase { pub address: T, } @@ -89,11 +113,11 @@ impl Vault { amount: Uint128, force: bool, ) -> StdResult { - let vault_info = self.query_vault_info(querier)?; + let vault_info = self.query_info(querier)?; let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![Coin { - denom: vault_info.token_denom, + denom: vault_info.vault_coin_denom, amount, }], msg: to_binary( @@ -107,18 +131,50 @@ impl Vault { Ok(withdraw_msg) } - pub fn query_vault_info(&self, querier: &QuerierWrapper) -> StdResult { + pub fn request_unlock_msg(&self, funds: &[Coin]) -> StdResult { + let request_msg = SubMsg::reply_on_success( + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address.to_string(), + funds: funds.to_vec(), + msg: to_binary(&ExecuteMsg::RequestUnlock {})?, + }), + VAULT_REQUEST_REPLY_ID, + ); + Ok(request_msg) + } + + pub fn withdraw_unlocked_msg(&self, position_id: Uint128) -> StdResult { + let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::WithdrawUnlocked { id: position_id })?, + }); + Ok(withdraw_msg) + } + + pub fn query_info(&self, querier: &QuerierWrapper) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), msg: to_binary(&QueryMsg::Info {})?, })) } + pub fn query_unlocking_position_info( + &self, + querier: &QuerierWrapper, + id: Uint128, + ) -> StdResult { + querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.address.to_string(), + msg: to_binary(&QueryMsg::UnlockingPosition { id })?, + })) + } + pub fn query_balance(&self, querier: &QuerierWrapper, addr: &Addr) -> StdResult { - let vault_info = self.query_vault_info(querier)?; + let vault_info = self.query_info(querier)?; let res: BalanceResponse = querier.query(&QueryRequest::Bank(BankQuery::Balance { address: addr.to_string(), - denom: vault_info.token_denom, + denom: vault_info.vault_coin_denom, }))?; Ok(res.amount.amount) } diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 78994bef1..1209169d7 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -51,6 +51,9 @@ pub enum ContractError { #[error("No debt to repay")] NoDebt, + #[error("Position {0} was not a valid position for this account id in this vault")] + NoPositionMatch(String), + #[error( "{account_id:?} is not a liquidatable credit account. Health factor: {lqdt_health_factor:?}." )] @@ -71,6 +74,9 @@ pub enum ContractError { #[error("{0}")] Overflow(#[from] OverflowError), + #[error("Reply id: {0} not valid")] + ReplyIdError(u64), + #[error("{0}")] RequirementsNotMet(String), @@ -79,4 +85,7 @@ pub enum ContractError { #[error("{user:?} is not authorized to {action:?}")] Unauthorized { user: String, action: String }, + + #[error("There is more time left on the lock period")] + UnlockNotReady, } diff --git a/packages/rover/src/extensions/mod.rs b/packages/rover/src/extensions/mod.rs new file mode 100644 index 000000000..0cbe190b2 --- /dev/null +++ b/packages/rover/src/extensions/mod.rs @@ -0,0 +1,3 @@ +pub use self::reply::*; + +mod reply; diff --git a/packages/rover/src/extensions/reply.rs b/packages/rover/src/extensions/reply.rs new file mode 100644 index 000000000..421b6f4d1 --- /dev/null +++ b/packages/rover/src/extensions/reply.rs @@ -0,0 +1,61 @@ +use std::str::FromStr; + +use crate::msg::vault::UNLOCKING_POSITION_CREATED_EVENT_TYPE; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Coin, Reply, StdError, StdResult, SubMsgResult, Uint128}; + +// https://github.com/CosmWasm/wasmd/blob/main/EVENTS.md#standard-events-in-xwasm +const CONTRACT_ADDR_KEY: &str = "_contract_addr"; + +#[cw_serde] +pub struct AssetTransferMsg { + pub recipient: String, + pub sender: String, + pub amount: Vec, +} + +#[cw_serde] +pub struct UnlockEvent { + pub id: Uint128, + pub vault_addr: String, +} + +pub trait AttrParse { + fn parse_unlock_event(self) -> StdResult; +} + +impl AttrParse for Reply { + fn parse_unlock_event(self) -> StdResult { + match self.result { + SubMsgResult::Err(err) => Err(StdError::generic_err(err)), + SubMsgResult::Ok(response) => { + let unlock_event = response + .events + .iter() + .find(|event| { + event.ty == format!("wasm-{}", UNLOCKING_POSITION_CREATED_EVENT_TYPE) + }) + .ok_or_else(|| StdError::generic_err("No unlock event"))?; + + let id = &unlock_event + .attributes + .iter() + .find(|x| x.key == "id") + .ok_or_else(|| StdError::generic_err("No id attribute"))? + .value; + + let contract_addr = &unlock_event + .attributes + .iter() + .find(|x| x.key == CONTRACT_ADDR_KEY) + .ok_or_else(|| StdError::generic_err("No contract attribute"))? + .value; + + Ok(UnlockEvent { + id: Uint128::from_str(id)?, + vault_addr: contract_addr.to_string(), + }) + } + } + } +} diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index a0724aedf..38ad76f8e 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -1,5 +1,6 @@ pub mod adapters; pub mod coins; pub mod error; +pub mod extensions; pub mod msg; pub mod traits; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 7078eee88..8b8ecbb73 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -47,6 +47,13 @@ pub enum Action { vault: VaultUnchecked, amount: Uint128, }, + /// Requests unlocking of shares for a vault with a required lock period + VaultRequestUnlock { + vault: VaultUnchecked, + amount: Uint128, + }, + /// Withdraws the assets for unlocking position id from vault. Required time must have elapsed. + VaultWithdrawUnlocked { id: Uint128, vault: VaultUnchecked }, /// Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt /// denom/amount of what the liquidator wants to payoff and 2) the request coin denom which the /// liquidatee should have a balance of. The amount returned to liquidator will be the request coin @@ -118,6 +125,18 @@ pub enum CallbackMsg { vault: Vault, amount: Uint128, }, + /// Requests unlocking of shares for a vault with a lock period + VaultRequestUnlock { + account_id: String, + vault: Vault, + amount: Uint128, + }, + /// Withdraws assets from vault for a locked position having a lockup period that has been fulfilled + VaultWithdrawUnlocked { + account_id: String, + vault: Vault, + position_id: Uint128, + }, /// Pay back debts of a liquidatable rover account for a bonus LiquidateCoin { liquidator_account_id: String, diff --git a/packages/rover/src/msg/vault.rs b/packages/rover/src/msg/vault.rs index 7367981e6..c5b491f9c 100644 --- a/packages/rover/src/msg/vault.rs +++ b/packages/rover/src/msg/vault.rs @@ -1,5 +1,8 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::{Timestamp, Uint128}; + +pub const UNLOCKING_POSITION_CREATED_EVENT_TYPE: &str = "unlocking_position_created"; +pub const UNLOCKING_POSITION_ATTR: &str = "id"; /// Partial compatibility with EIP-4626 #[cw_serde] @@ -11,6 +14,13 @@ pub enum ExecuteMsg { /// A privileged action only to be used by Rover. Same as `Withdraw` except it bypasses any lockup period /// restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation. ForceWithdraw {}, + /// Some vaults have lockup periods (typically between 1-14 days). This action sends vault `Coin` + /// which is locked for vault lockup period and available to `Unlock` after that time has elapsed. + /// On response, vault sends back `unlocking_position_created` event with attribute `id` representing + /// the new unlocking coins position. + RequestUnlock {}, + /// Withdraw assets in vault that have been unlocked for given unlocking position + WithdrawUnlocked { id: Uint128 }, } #[cw_serde] @@ -21,22 +31,36 @@ pub enum QueryMsg { Info {}, /// All the coins that would be redeemed for in exchange for /// vault coins. Used by Rover to calculate vault position values. - #[returns(Vec)] + #[returns(Vec)] PreviewRedeem { amount: Uint128 }, /// Returns the total vault coins issued. In order to prevent Cream-attack, we cannot /// query the bank module for this amount. #[returns(Uint128)] TotalVaultCoinsIssued {}, + /// The vault positions that this address has requested to unlock + #[returns(Vec)] + UnlockingPositionsForAddr { addr: String }, + #[returns(UnlockingPosition)] + UnlockingPosition { id: Uint128 }, } #[cw_serde] pub struct VaultInfo { - /// Coins required to enter vault. - /// Amount will be proportional to the share of which it should occupy in the group - /// (e.g. { denom: osmo, amount: 1 }, { denom: atom, amount: 1 } indicate a 50-50 split) - pub coins: Vec, + /// Denom of vault token + pub vault_coin_denom: String, + /// Coin denoms required to enter vault. + /// Multiple vectors indicate the vault accepts more than one combination to enter. + pub accepts: Vec>, /// Time in seconds for unlock period pub lockup: Option, - /// Denom of vault token - pub token_denom: String, +} + +#[cw_serde] +pub struct UnlockingPosition { + /// Unique identifier representing the unlocking position. Needed for `ExecuteMsg::Unlock {}` call. + pub id: Uint128, + /// Number of vault tokens + pub amount: Uint128, + /// Absolute time when position unlocks in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC) + pub unlocked_at: Timestamp, } diff --git a/schemas/account-nft/account-nft.json b/schemas/account-nft/account-nft.json index 69904c087..d4572bdfa 100644 --- a/schemas/account-nft/account-nft.json +++ b/schemas/account-nft/account-nft.json @@ -286,7 +286,7 @@ ], "definitions": { "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", + "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" }, "Expiration": { diff --git a/schemas/credit-manager/credit-manager.json b/schemas/credit-manager/credit-manager.json index 305eb9d43..4a521f32c 100644 --- a/schemas/credit-manager/credit-manager.json +++ b/schemas/credit-manager/credit-manager.json @@ -299,6 +299,58 @@ }, "additionalProperties": false }, + { + "description": "Requests unlocking of shares for a vault with a required lock period", + "type": "object", + "required": [ + "vault_request_unlock" + ], + "properties": { + "vault_request_unlock": { + "type": "object", + "required": [ + "amount", + "vault" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraws the assets for unlocking position id from vault. Required time must have elapsed.", + "type": "object", + "required": [ + "vault_withdraw_unlocked" + ], + "properties": { + "vault_withdraw_unlocked": { + "type": "object", + "required": [ + "id", + "vault" + ], + "properties": { + "id": { + "$ref": "#/definitions/Uint128" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt denom/amount of what the liquidator wants to payoff and 2) the request coin denom which the liquidatee should have a balance of. The amount returned to liquidator will be the request coin of the amount that precisely matches the value of the debt + a liquidation bonus. The debt amount will be adjusted down if: - Exceeds liquidatee's total debt for denom - Not enough liquidatee request coin balance to match - The value of the debt repaid exceeds the maximum close factor %", "type": "object", @@ -608,6 +660,66 @@ }, "additionalProperties": false }, + { + "description": "Requests unlocking of shares for a vault with a lock period", + "type": "object", + "required": [ + "vault_request_unlock" + ], + "properties": { + "vault_request_unlock": { + "type": "object", + "required": [ + "account_id", + "amount", + "vault" + ], + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "$ref": "#/definitions/Uint128" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraws assets from vault for a locked position having a lockup period that has been fulfilled", + "type": "object", + "required": [ + "vault_withdraw_unlocked" + ], + "properties": { + "vault_withdraw_unlocked": { + "type": "object", + "required": [ + "account_id", + "position_id", + "vault" + ], + "properties": { + "account_id": { + "type": "string" + }, + "position_id": { + "$ref": "#/definitions/Uint128" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Pay back debts of a liquidatable rover account for a bonus", "type": "object", @@ -1452,7 +1564,8 @@ "type": "object", "required": [ "locked", - "unlocked" + "unlocked", + "unlocking" ], "properties": { "locked": { @@ -1460,6 +1573,38 @@ }, "unlocked": { "$ref": "#/definitions/Uint128" + }, + "unlocking": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultUnlockingId" + } + } + }, + "additionalProperties": false + }, + "VaultUnlockingId": { + "type": "object", + "required": [ + "amount", + "id" + ], + "properties": { + "amount": { + "description": "Number of vault tokens", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "id": { + "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] } }, "additionalProperties": false @@ -1722,7 +1867,8 @@ "type": "object", "required": [ "locked", - "unlocked" + "unlocked", + "unlocking" ], "properties": { "locked": { @@ -1730,6 +1876,38 @@ }, "unlocked": { "$ref": "#/definitions/Uint128" + }, + "unlocking": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultUnlockingId" + } + } + }, + "additionalProperties": false + }, + "VaultUnlockingId": { + "type": "object", + "required": [ + "amount", + "id" + ], + "properties": { + "amount": { + "description": "Number of vault tokens", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "id": { + "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] } }, "additionalProperties": false diff --git a/schemas/mars-oracle-adapter/mars-oracle-adapter.json b/schemas/mars-oracle-adapter/mars-oracle-adapter.json index 0d2e74887..ff7d93cb5 100644 --- a/schemas/mars-oracle-adapter/mars-oracle-adapter.json +++ b/schemas/mars-oracle-adapter/mars-oracle-adapter.json @@ -331,6 +331,7 @@ "$ref": "#/definitions/Decimal" } }, + "additionalProperties": false, "definitions": { "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", diff --git a/schemas/mock-oracle/mock-oracle.json b/schemas/mock-oracle/mock-oracle.json index 94f37c950..8a85c9b34 100644 --- a/schemas/mock-oracle/mock-oracle.json +++ b/schemas/mock-oracle/mock-oracle.json @@ -127,6 +127,7 @@ "$ref": "#/definitions/Decimal" } }, + "additionalProperties": false, "definitions": { "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", diff --git a/schemas/mock-red-bank/mock-red-bank.json b/schemas/mock-red-bank/mock-red-bank.json index c0f3ea9ea..c74d8962a 100644 --- a/schemas/mock-red-bank/mock-red-bank.json +++ b/schemas/mock-red-bank/mock-red-bank.json @@ -50,6 +50,194 @@ "title": "ExecuteMsg", "oneOf": [ { + "description": "Update contract config (only owner can call)", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "$ref": "#/definitions/CreateOrUpdateConfig" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Initialize an asset on the money market (only owner can call)", + "type": "object", + "required": [ + "init_asset" + ], + "properties": { + "init_asset": { + "type": "object", + "required": [ + "denom", + "params" + ], + "properties": { + "denom": { + "description": "Asset related info", + "type": "string" + }, + "params": { + "description": "Asset parameters", + "allOf": [ + { + "$ref": "#/definitions/InitOrUpdateAssetParams" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update an asset on the money market (only owner can call)", + "type": "object", + "required": [ + "update_asset" + ], + "properties": { + "update_asset": { + "type": "object", + "required": [ + "denom", + "params" + ], + "properties": { + "denom": { + "description": "Asset related info", + "type": "string" + }, + "params": { + "description": "Asset parameters", + "allOf": [ + { + "$ref": "#/definitions/InitOrUpdateAssetParams" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update uncollateralized loan limit for a given user and asset. Overrides previous value if any. A limit of zero means no uncollateralized limit and the debt in that asset needs to be collateralized (only owner can call)", + "type": "object", + "required": [ + "update_uncollateralized_loan_limit" + ], + "properties": { + "update_uncollateralized_loan_limit": { + "type": "object", + "required": [ + "denom", + "new_limit", + "user" + ], + "properties": { + "denom": { + "description": "Asset the user receives the credit in", + "type": "string" + }, + "new_limit": { + "description": "Limit for the uncolateralize loan.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "user": { + "description": "Address that receives the credit", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Deposit native coins. Deposited coins must be sent in the transaction this call is made", + "type": "object", + "required": [ + "deposit" + ], + "properties": { + "deposit": { + "type": "object", + "properties": { + "on_behalf_of": { + "description": "Address that will receive the maTokens", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw an amount of the asset burning an equivalent amount of maTokens.", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "amount": { + "description": "Amount to be withdrawn. If None is specified, the full amount will be withdrawn.", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "denom": { + "description": "Asset to withdraw", + "type": "string" + }, + "recipient": { + "description": "The address where the withdrawn amount is sent", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Borrow native coins. If borrow allowed, amount is added to caller's debt and sent to the address.", "type": "object", "required": [ "borrow" @@ -58,13 +246,24 @@ "borrow": { "type": "object", "required": [ - "coin" + "amount", + "denom" ], "properties": { - "coin": { - "$ref": "#/definitions/Coin" + "amount": { + "description": "Amount to borrow", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "Asset to borrow", + "type": "string" }, "recipient": { + "description": "The address where the borrowed amount is sent", "type": [ "string", "null" @@ -77,25 +276,84 @@ "additionalProperties": false }, { + "description": "Repay native coins loan. Coins used to repay must be sent in the transaction this call is made.", "type": "object", "required": [ "repay" ], "properties": { "repay": { + "type": "object", + "properties": { + "on_behalf_of": { + "description": "Repay the funds for the user", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Liquidate under-collateralized native loans. Coins used to repay must be sent in the transaction this call is made.\n\nThe liquidator will receive collateral shares. To get the underlying asset, consider sending a separate `withdraw` execute message.", + "type": "object", + "required": [ + "liquidate" + ], + "properties": { + "liquidate": { "type": "object", "required": [ - "denom" + "collateral_denom", + "user" ], "properties": { - "denom": { + "collateral_denom": { + "description": "Denom of the collateral asset, which liquidator gets from the borrower", "type": "string" }, - "on_behalf_of": { + "recipient": { + "description": "The address for receiving underlying collateral", "type": [ "string", "null" ] + }, + "user": { + "description": "The address of the borrower getting liquidated", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update (enable / disable) asset as collateral for the caller", + "type": "object", + "required": [ + "update_asset_collateral_status" + ], + "properties": { + "update_asset_collateral_status": { + "type": "object", + "required": [ + "denom", + "enable" + ], + "properties": { + "denom": { + "description": "Asset to update status for", + "type": "string" + }, + "enable": { + "description": "Option to enable (true) / disable (false) asset as collateral", + "type": "boolean" } }, "additionalProperties": false @@ -105,20 +363,178 @@ } ], "definitions": { - "Coin": { + "CreateOrUpdateConfig": { + "type": "object", + "properties": { + "address_provider": { + "type": [ + "string", + "null" + ] + }, + "close_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "owner": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "InitOrUpdateAssetParams": { + "type": "object", + "properties": { + "borrow_enabled": { + "description": "If false cannot borrow", + "type": [ + "boolean", + "null" + ] + }, + "deposit_cap": { + "description": "Deposit Cap defined in terms of the asset (Unlimited by default)", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "deposit_enabled": { + "description": "If false cannot deposit", + "type": [ + "boolean", + "null" + ] + }, + "initial_borrow_rate": { + "description": "Initial borrow rate", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "interest_rate_model": { + "description": "Interest rate strategy to calculate borrow_rate and liquidity_rate", + "anyOf": [ + { + "$ref": "#/definitions/InterestRateModel" + }, + { + "type": "null" + } + ] + }, + "liquidation_bonus": { + "description": "Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral from user in an amount equal to debt repayed + bonus)", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "liquidation_threshold": { + "description": "uusd amount in debt position per uusd of asset collateral that if surpassed makes the user's position liquidatable.", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "max_loan_to_value": { + "description": "Max uusd that can be borrowed per uusd of collateral when using the asset as collateral", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "reserve_factor": { + "description": "Portion of the borrow rate that is kept as protocol rewards", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "InterestRateModel": { "type": "object", "required": [ - "amount", - "denom" + "base", + "optimal_utilization_rate", + "slope_1", + "slope_2" ], "properties": { - "amount": { - "$ref": "#/definitions/Uint128" + "base": { + "description": "Base rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] }, - "denom": { - "type": "string" + "optimal_utilization_rate": { + "description": "Optimal utilization rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope_1": { + "description": "Slope parameter for interest rate model function when utilization_rate < optimal_utilization_rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope_2": { + "description": "Slope parameter for interest rate model function when utilization_rate >= optimal_utilization_rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] } - } + }, + "additionalProperties": false }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", @@ -131,31 +547,21 @@ "title": "QueryMsg", "oneOf": [ { + "description": "Get config", "type": "object", "required": [ - "user_asset_debt" + "config" ], "properties": { - "user_asset_debt": { + "config": { "type": "object", - "required": [ - "denom", - "user_address" - ], - "properties": { - "denom": { - "type": "string" - }, - "user_address": { - "type": "string" - } - }, "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Get asset market", "type": "object", "required": [ "market" @@ -175,65 +581,901 @@ } }, "additionalProperties": false - } - ] - }, - "migrate": null, - "sudo": null, - "responses": { - "market": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Market", - "type": "object", - "required": [ - "borrow_enabled", - "borrow_index", - "borrow_rate", - "collateral_total_scaled", - "debt_total_scaled", + }, + { + "description": "Enumerate markets with pagination", + "type": "object", + "required": [ + "markets" + ], + "properties": { + "markets": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get uncollateralized limit for given user and asset", + "type": "object", + "required": [ + "uncollateralized_loan_limit" + ], + "properties": { + "uncollateralized_loan_limit": { + "type": "object", + "required": [ + "denom", + "user" + ], + "properties": { + "denom": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get all uncollateralized limits for a given user", + "type": "object", + "required": [ + "uncollateralized_loan_limits" + ], + "properties": { + "uncollateralized_loan_limits": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get user debt position for a specific asset", + "type": "object", + "required": [ + "user_debt" + ], + "properties": { + "user_debt": { + "type": "object", + "required": [ + "denom", + "user" + ], + "properties": { + "denom": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get all debt positions for a user", + "type": "object", + "required": [ + "user_debts" + ], + "properties": { + "user_debts": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get user collateral position for a specific asset", + "type": "object", + "required": [ + "user_collateral" + ], + "properties": { + "user_collateral": { + "type": "object", + "required": [ + "denom", + "user" + ], + "properties": { + "denom": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get all collateral positions for a user", + "type": "object", + "required": [ + "user_collaterals" + ], + "properties": { + "user_collaterals": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get user position", + "type": "object", + "required": [ + "user_position" + ], + "properties": { + "user_position": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get liquidity scaled amount for a given underlying asset amount. (i.e: how much scaled collateral is added if the given amount is deposited)", + "type": "object", + "required": [ + "scaled_liquidity_amount" + ], + "properties": { + "scaled_liquidity_amount": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get equivalent scaled debt for a given underlying asset amount. (i.e: how much scaled debt is added if the given amount is borrowed)", + "type": "object", + "required": [ + "scaled_debt_amount" + ], + "properties": { + "scaled_debt_amount": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get underlying asset amount for a given asset and scaled amount. (i.e. How much underlying asset will be released if withdrawing by burning a given scaled collateral amount stored in state.)", + "type": "object", + "required": [ + "underlying_liquidity_amount" + ], + "properties": { + "underlying_liquidity_amount": { + "type": "object", + "required": [ + "amount_scaled", + "denom" + ], + "properties": { + "amount_scaled": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get underlying debt amount for a given asset and scaled amounts. (i.e: How much underlying asset needs to be repaid to cancel a given scaled debt amount stored in state)", + "type": "object", + "required": [ + "underlying_debt_amount" + ], + "properties": { + "underlying_debt_amount": { + "type": "object", + "required": [ + "amount_scaled", + "denom" + ], + "properties": { + "amount_scaled": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "migrate": null, + "sudo": null, + "responses": { + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config_for_String", + "description": "Global configuration", + "type": "object", + "required": [ + "address_provider", + "close_factor", + "owner" + ], + "properties": { + "address_provider": { + "description": "Address provider returns addresses for all protocol contracts", + "type": "string" + }, + "close_factor": { + "description": "Maximum percentage of outstanding debt that can be covered by a liquidator", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "owner": { + "description": "Contract owner", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "market": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Market", + "type": "object", + "required": [ + "borrow_enabled", + "borrow_index", + "borrow_rate", + "collateral_total_scaled", + "debt_total_scaled", + "denom", + "deposit_cap", + "deposit_enabled", + "indexes_last_updated", + "interest_rate_model", + "liquidation_bonus", + "liquidation_threshold", + "liquidity_index", + "liquidity_rate", + "max_loan_to_value", + "reserve_factor" + ], + "properties": { + "borrow_enabled": { + "description": "If false cannot borrow", + "type": "boolean" + }, + "borrow_index": { + "description": "Borrow index (Used to compute borrow interest)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "borrow_rate": { + "description": "Rate charged to borrowers", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "collateral_total_scaled": { + "description": "Total collateral scaled for the market's currency", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "debt_total_scaled": { + "description": "Total debt scaled for the market's currency", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "Denom of the asset", + "type": "string" + }, + "deposit_cap": { + "description": "Deposit Cap (defined in terms of the asset)", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "deposit_enabled": { + "description": "If false cannot deposit", + "type": "boolean" + }, + "indexes_last_updated": { + "description": "Timestamp (seconds) where indexes and where last updated", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "interest_rate_model": { + "description": "model (params + internal state) that defines how interest rate behaves", + "allOf": [ + { + "$ref": "#/definitions/InterestRateModel" + } + ] + }, + "liquidation_bonus": { + "description": "Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral from user in an amount equal to debt repayed + bonus)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "liquidation_threshold": { + "description": "Base asset amount in debt position per \"base asset\" of asset collateral that if surpassed makes the user's position liquidatable.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "liquidity_index": { + "description": "Liquidity index (Used to compute deposit interest)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "liquidity_rate": { + "description": "Rate paid to depositors", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "max_loan_to_value": { + "description": "Max base asset that can be borrowed per \"base asset\" collateral when using the asset as collateral", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "reserve_factor": { + "description": "Portion of the borrow rate that is kept as protocol rewards", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "InterestRateModel": { + "type": "object", + "required": [ + "base", + "optimal_utilization_rate", + "slope_1", + "slope_2" + ], + "properties": { + "base": { + "description": "Base rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "optimal_utilization_rate": { + "description": "Optimal utilization rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope_1": { + "description": "Slope parameter for interest rate model function when utilization_rate < optimal_utilization_rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope_2": { + "description": "Slope parameter for interest rate model function when utilization_rate >= optimal_utilization_rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "markets": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Market", + "type": "array", + "items": { + "$ref": "#/definitions/Market" + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "InterestRateModel": { + "type": "object", + "required": [ + "base", + "optimal_utilization_rate", + "slope_1", + "slope_2" + ], + "properties": { + "base": { + "description": "Base rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "optimal_utilization_rate": { + "description": "Optimal utilization rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope_1": { + "description": "Slope parameter for interest rate model function when utilization_rate < optimal_utilization_rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope_2": { + "description": "Slope parameter for interest rate model function when utilization_rate >= optimal_utilization_rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "Market": { + "type": "object", + "required": [ + "borrow_enabled", + "borrow_index", + "borrow_rate", + "collateral_total_scaled", + "debt_total_scaled", + "denom", + "deposit_cap", + "deposit_enabled", + "indexes_last_updated", + "interest_rate_model", + "liquidation_bonus", + "liquidation_threshold", + "liquidity_index", + "liquidity_rate", + "max_loan_to_value", + "reserve_factor" + ], + "properties": { + "borrow_enabled": { + "description": "If false cannot borrow", + "type": "boolean" + }, + "borrow_index": { + "description": "Borrow index (Used to compute borrow interest)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "borrow_rate": { + "description": "Rate charged to borrowers", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "collateral_total_scaled": { + "description": "Total collateral scaled for the market's currency", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "debt_total_scaled": { + "description": "Total debt scaled for the market's currency", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "Denom of the asset", + "type": "string" + }, + "deposit_cap": { + "description": "Deposit Cap (defined in terms of the asset)", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "deposit_enabled": { + "description": "If false cannot deposit", + "type": "boolean" + }, + "indexes_last_updated": { + "description": "Timestamp (seconds) where indexes and where last updated", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "interest_rate_model": { + "description": "model (params + internal state) that defines how interest rate behaves", + "allOf": [ + { + "$ref": "#/definitions/InterestRateModel" + } + ] + }, + "liquidation_bonus": { + "description": "Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral from user in an amount equal to debt repayed + bonus)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "liquidation_threshold": { + "description": "Base asset amount in debt position per \"base asset\" of asset collateral that if surpassed makes the user's position liquidatable.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "liquidity_index": { + "description": "Liquidity index (Used to compute deposit interest)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "liquidity_rate": { + "description": "Rate paid to depositors", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "max_loan_to_value": { + "description": "Max base asset that can be borrowed per \"base asset\" collateral when using the asset as collateral", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "reserve_factor": { + "description": "Portion of the borrow rate that is kept as protocol rewards", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "scaled_debt_amount": { + "$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" + }, + "scaled_liquidity_amount": { + "$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" + }, + "uncollateralized_loan_limit": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UncollateralizedLoanLimitResponse", + "type": "object", + "required": [ "denom", - "deposit_cap", - "deposit_enabled", - "indexes_last_updated", - "interest_rate_model", - "liquidation_bonus", - "liquidation_threshold", - "liquidity_index", - "liquidity_rate", - "max_loan_to_value", - "reserve_factor" + "limit" ], "properties": { - "borrow_enabled": { - "description": "If false cannot borrow", - "type": "boolean" + "denom": { + "description": "Asset denom", + "type": "string" }, - "borrow_index": { - "description": "Borrow index (Used to compute borrow interest)", + "limit": { + "description": "Uncollateralized loan limit in this asset", "allOf": [ { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" } ] + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "uncollateralized_loan_limits": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_UncollateralizedLoanLimitResponse", + "type": "array", + "items": { + "$ref": "#/definitions/UncollateralizedLoanLimitResponse" + }, + "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" }, - "borrow_rate": { - "description": "Rate charged to borrowers", - "allOf": [ - { - "$ref": "#/definitions/Decimal" + "UncollateralizedLoanLimitResponse": { + "type": "object", + "required": [ + "denom", + "limit" + ], + "properties": { + "denom": { + "description": "Asset denom", + "type": "string" + }, + "limit": { + "description": "Uncollateralized loan limit in this asset", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] } - ] - }, - "collateral_total_scaled": { - "description": "Total collateral scaled for the market's currency", + }, + "additionalProperties": false + } + } + }, + "underlying_debt_amount": { + "$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" + }, + "underlying_liquidity_amount": { + "$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" + }, + "user_collateral": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UserCollateralResponse", + "type": "object", + "required": [ + "amount", + "amount_scaled", + "denom", + "enabled" + ], + "properties": { + "amount": { + "description": "Underlying asset amount that is actually deposited at the current block", "allOf": [ { "$ref": "#/definitions/Uint128" } ] }, - "debt_total_scaled": { - "description": "Total debt scaled for the market's currency", + "amount_scaled": { + "description": "Scaled collateral amount stored in contract state", "allOf": [ { "$ref": "#/definitions/Uint128" @@ -241,159 +1483,244 @@ ] }, "denom": { - "description": "Denom of the asset", + "description": "Asset denom", "type": "string" }, - "deposit_cap": { - "description": "Deposit Cap (defined in terms of the asset)", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "deposit_enabled": { - "description": "If false cannot deposit", + "enabled": { + "description": "Wether the user is using asset as collateral or not", "type": "boolean" + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "user_collaterals": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_UserCollateralResponse", + "type": "array", + "items": { + "$ref": "#/definitions/UserCollateralResponse" + }, + "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" }, - "indexes_last_updated": { - "description": "Timestamp (seconds) where indexes and where last updated", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "interest_rate_model": { - "description": "model (params + internal state) that defines how interest rate behaves", - "allOf": [ - { - "$ref": "#/definitions/InterestRateModel" - } - ] - }, - "liquidation_bonus": { - "description": "Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral from user in an amount equal to debt repayed + bonus)", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "liquidation_threshold": { - "description": "Base asset amount in debt position per \"base asset\" of asset collateral that if surpassed makes the user's position liquidatable.", - "allOf": [ - { - "$ref": "#/definitions/Decimal" + "UserCollateralResponse": { + "type": "object", + "required": [ + "amount", + "amount_scaled", + "denom", + "enabled" + ], + "properties": { + "amount": { + "description": "Underlying asset amount that is actually deposited at the current block", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "amount_scaled": { + "description": "Scaled collateral amount stored in contract state", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "Asset denom", + "type": "string" + }, + "enabled": { + "description": "Wether the user is using asset as collateral or not", + "type": "boolean" } - ] - }, - "liquidity_index": { - "description": "Liquidity index (Used to compute deposit interest)", + }, + "additionalProperties": false + } + } + }, + "user_debt": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UserDebtResponse", + "type": "object", + "required": [ + "amount", + "amount_scaled", + "denom", + "uncollateralized" + ], + "properties": { + "amount": { + "description": "Underlying asset amount that is actually owed at the current block", "allOf": [ { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" } ] }, - "liquidity_rate": { - "description": "Rate paid to depositors", + "amount_scaled": { + "description": "Scaled debt amount stored in contract state", "allOf": [ { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" } ] }, - "max_loan_to_value": { - "description": "Max base asset that can be borrowed per \"base asset\" collateral when using the asset as collateral", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] + "denom": { + "description": "Asset denom", + "type": "string" }, - "reserve_factor": { - "description": "Portion of the borrow rate that is kept as protocol rewards", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] + "uncollateralized": { + "description": "Marker for uncollateralized debt", + "type": "boolean" } }, + "additionalProperties": false, "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "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" + } + } + }, + "user_debts": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_UserDebtResponse", + "type": "array", + "items": { + "$ref": "#/definitions/UserDebtResponse" + }, + "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" }, - "InterestRateModel": { + "UserDebtResponse": { "type": "object", "required": [ - "base", - "optimal_utilization_rate", - "slope_1", - "slope_2" + "amount", + "amount_scaled", + "denom", + "uncollateralized" ], "properties": { - "base": { - "description": "Base rate", + "amount": { + "description": "Underlying asset amount that is actually owed at the current block", "allOf": [ { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" } ] }, - "optimal_utilization_rate": { - "description": "Optimal utilization rate", + "amount_scaled": { + "description": "Scaled debt amount stored in contract state", "allOf": [ { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" } ] }, - "slope_1": { - "description": "Slope parameter for interest rate model function when utilization_rate < optimal_utilization_rate", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] + "denom": { + "description": "Asset denom", + "type": "string" }, - "slope_2": { - "description": "Slope parameter for interest rate model function when utilization_rate >= optimal_utilization_rate", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] + "uncollateralized": { + "description": "Marker for uncollateralized debt", + "type": "boolean" } - } - }, - "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" + }, + "additionalProperties": false } } }, - "user_asset_debt": { + "user_position": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "UserAssetDebtResponse", + "title": "UserPositionResponse", "type": "object", "required": [ - "amount", - "denom" + "health_status", + "total_collateralized_debt", + "total_enabled_collateral", + "weighted_liquidation_threshold_collateral", + "weighted_max_ltv_collateral" ], "properties": { - "amount": { - "$ref": "#/definitions/Uint128" + "health_status": { + "$ref": "#/definitions/UserHealthStatus" }, - "denom": { - "type": "string" + "total_collateralized_debt": { + "description": "Total value of all collateralized debts. If the user has an uncollateralized loan limit in an asset, the debt in this asset will not be included in this value.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "total_enabled_collateral": { + "description": "Total value of all enabled collateral assets. If an asset is disabled as collateral, it will not be included in this value.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "weighted_liquidation_threshold_collateral": { + "$ref": "#/definitions/Decimal" + }, + "weighted_max_ltv_collateral": { + "$ref": "#/definitions/Decimal" } }, "additionalProperties": false, "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "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" + }, + "UserHealthStatus": { + "oneOf": [ + { + "type": "string", + "enum": [ + "not_borrowing" + ] + }, + { + "type": "object", + "required": [ + "borrowing" + ], + "properties": { + "borrowing": { + "type": "object", + "required": [ + "liq_threshold_hf", + "max_ltv_hf" + ], + "properties": { + "liq_threshold_hf": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv_hf": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] } } } diff --git a/schemas/mock-vault/mock-vault.json b/schemas/mock-vault/mock-vault.json index 9ac0f12e6..a55639709 100644 --- a/schemas/mock-vault/mock-vault.json +++ b/schemas/mock-vault/mock-vault.json @@ -89,8 +89,50 @@ } }, "additionalProperties": false + }, + { + "description": "Some vaults have lockup periods (typically between 1-14 days). This action sends vault `Coin` which is locked for vault lockup period and available to `Unlock` after that time has elapsed. On response, vault sends back `unlocking_position_created` event with attribute `id` representing the new unlocking coins position.", + "type": "object", + "required": [ + "request_unlock" + ], + "properties": { + "request_unlock": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw assets in vault that have been unlocked for given unlocking position", + "type": "object", + "required": [ + "withdraw_unlocked" + ], + "properties": { + "withdraw_unlocked": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" } - ] + } }, "query": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -145,6 +187,49 @@ } }, "additionalProperties": false + }, + { + "description": "The vault positions that this address has requested to unlock", + "type": "object", + "required": [ + "unlocking_positions_for_addr" + ], + "properties": { + "unlocking_positions_for_addr": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unlocking_position" + ], + "properties": { + "unlocking_position": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -162,15 +247,18 @@ "title": "VaultInfo", "type": "object", "required": [ - "coins", - "token_denom" + "accepts", + "vault_coin_denom" ], "properties": { - "coins": { - "description": "Coins required to enter vault. Amount will be proportional to the share of which it should occupy in the group (e.g. { denom: osmo, amount: 1 }, { denom: atom, amount: 1 } indicate a 50-50 split)", + "accepts": { + "description": "Coin denoms required to enter vault. Multiple vectors indicate the vault accepts more than one combination to enter.", "type": "array", "items": { - "$ref": "#/definitions/Coin" + "type": "array", + "items": { + "type": "string" + } } }, "lockup": { @@ -182,12 +270,20 @@ "format": "uint64", "minimum": 0.0 }, - "token_denom": { + "vault_coin_denom": { "description": "Denom of vault token", "type": "string" } }, - "additionalProperties": false, + "additionalProperties": false + }, + "preview_redeem": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Coin", + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + }, "definitions": { "Coin": { "type": "object", @@ -210,40 +306,127 @@ } } }, - "preview_redeem": { + "total_vault_coins_issued": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_Coin", + "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" + }, + "unlocking_position": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UnlockingPosition", + "type": "object", + "required": [ + "amount", + "id", + "unlocked_at" + ], + "properties": { + "amount": { + "description": "Number of vault tokens", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "id": { + "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::Unlock {}` call.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "unlocked_at": { + "description": "Absolute time when position unlocks in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC)", + "allOf": [ + { + "$ref": "#/definitions/Timestamp" + } + ] + } + }, + "additionalProperties": false, + "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" + } + } + }, + "unlocking_positions_for_addr": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_UnlockingPosition", "type": "array", "items": { - "$ref": "#/definitions/Coin" + "$ref": "#/definitions/UnlockingPosition" }, "definitions": { - "Coin": { + "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" + }, + "UnlockingPosition": { "type": "object", "required": [ "amount", - "denom" + "id", + "unlocked_at" ], "properties": { "amount": { - "$ref": "#/definitions/Uint128" + "description": "Number of vault tokens", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] }, - "denom": { - "type": "string" + "id": { + "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::Unlock {}` call.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "unlocked_at": { + "description": "Absolute time when position unlocks in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC)", + "allOf": [ + { + "$ref": "#/definitions/Timestamp" + } + ] } - } - }, - "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" + }, + "additionalProperties": false } } - }, - "total_vault_coins_issued": { - "$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/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 86a7ff1e2..cb83efecc 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,7 @@ { - "accountNft": "osmo1xdz3k7dyrjhne749j2ucvy8egrh0rj200r365hdlc4vm6gy0gxsqw9lje4", - "mockVault": "osmo1fak59kj9cwh6p3evcvq3q6r0v5q7tpdfxyqsfs48wne28rw9vrjq0x2l3q", - "marsOracleAdapter": "osmo108enr4hvmc5w6yum9cx6pnkuudhla4t3se8l0v695y07hfj2zxqsp9sc0j", - "swapper": "osmo13qh27g5q5du92fyc0x2cacfgddrl0pzdvd6ulws8tactjdqyxvfqcxe4f9", - "creditManager": "osmo13xj9wy673y5g035wlghwfnqdkzawd0gpfc34dm73rns4962fdt9s7yuur7" + "accountNft": "osmo1m90h4fx5enc5wagrq8enm03597wy3sxl3mpcrg254dt459z709lquekx0a", + "mockVault": "osmo1t2n7lk9699sqztg8x23rzfd8x5vvhnqddt69a8k9pw5e0pj7zgkqt3ak7c", + "marsOracleAdapter": "osmo1d6wp6u3efdrevmef9f4u6juyffzr6yhg5m9a8c8x9camxssk2vsqxpg4yc", + "swapper": "osmo1ywhrzs2uz2yyhkd8h9qjvluvklq4devjf0sx5qzd9l56yza3qhtsfk89vs", + "creditManager": "osmo1py5le8gryx3zuetvjl6xkcecrnp8r3n7t66hfxjmz3vlmj55f2yq2s6jmk" } diff --git a/scripts/package.json b/scripts/package.json index 9e1f5853c..5bb1b0b3f 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -13,9 +13,9 @@ "format-check": "prettier --check ." }, "dependencies": { - "@cosmjs/cosmwasm-stargate": "^0.29.0", - "@cosmjs/stargate": "^0.29.0", - "@cosmwasm/ts-codegen": "^0.16.5", + "@cosmjs/cosmwasm-stargate": "^0.29.1", + "@cosmjs/stargate": "^0.29.1", + "@cosmwasm/ts-codegen": "^0.19.0", "chalk": "4.1.2", "cosmjs-types": "^0.5.0", "lodash": "^4.17.21", @@ -23,14 +23,14 @@ "prepend-file": "^2.0.1" }, "devDependencies": { - "@babel/preset-env": "^7.19.3", + "@babel/preset-env": "^7.19.4", "@babel/preset-typescript": "^7.18.6", - "@types/jest": "^29.0.3", - "@typescript-eslint/eslint-plugin": "^5.38.1", - "@typescript-eslint/parser": "^5.38.1", - "eslint": "^8.24.0", + "@types/jest": "^29.1.2", + "@typescript-eslint/eslint-plugin": "^5.40.0", + "@typescript-eslint/parser": "^5.40.0", + "eslint": "^8.25.0", "eslint-config-prettier": "^8.5.0", - "jest": "^29.0.3", + "jest": "^29.1.2", "prettier": "^2.7.1", "typescript": "^4.8.4" } diff --git a/scripts/types/generated/account-nft/AccountNft.client.ts b/scripts/types/generated/account-nft/AccountNft.client.ts index 82c371e91..ebd9975e9 100644 --- a/scripts/types/generated/account-nft/AccountNft.client.ts +++ b/scripts/types/generated/account-nft/AccountNft.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/account-nft/AccountNft.react-query.ts b/scripts/types/generated/account-nft/AccountNft.react-query.ts index 67eba0187..3a326a5e7 100644 --- a/scripts/types/generated/account-nft/AccountNft.react-query.ts +++ b/scripts/types/generated/account-nft/AccountNft.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/account-nft/AccountNft.types.ts b/scripts/types/generated/account-nft/AccountNft.types.ts index 84408b14b..939c49c64 100644 --- a/scripts/types/generated/account-nft/AccountNft.types.ts +++ b/scripts/types/generated/account-nft/AccountNft.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/account-nft/bundle.ts b/scripts/types/generated/account-nft/bundle.ts index de99f813e..353537575 100644 --- a/scripts/types/generated/account-nft/bundle.ts +++ b/scripts/types/generated/account-nft/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/credit-manager/CreditManager.client.ts b/scripts/types/generated/credit-manager/CreditManager.client.ts index 9610248c4..25b2a0393 100644 --- a/scripts/types/generated/credit-manager/CreditManager.client.ts +++ b/scripts/types/generated/credit-manager/CreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -35,6 +35,7 @@ import { VaultPositionResponseItem, VaultPosition, VaultPositionState, + VaultUnlockingId, ArrayOfString, ArrayOfVaultBaseForString, ConfigResponse, diff --git a/scripts/types/generated/credit-manager/CreditManager.react-query.ts b/scripts/types/generated/credit-manager/CreditManager.react-query.ts index 9d2140d2b..0e79c956b 100644 --- a/scripts/types/generated/credit-manager/CreditManager.react-query.ts +++ b/scripts/types/generated/credit-manager/CreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -36,6 +36,7 @@ import { VaultPositionResponseItem, VaultPosition, VaultPositionState, + VaultUnlockingId, ArrayOfString, ArrayOfVaultBaseForString, ConfigResponse, diff --git a/scripts/types/generated/credit-manager/CreditManager.types.ts b/scripts/types/generated/credit-manager/CreditManager.types.ts index 9268d76e3..61f7100ca 100644 --- a/scripts/types/generated/credit-manager/CreditManager.types.ts +++ b/scripts/types/generated/credit-manager/CreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -65,6 +65,18 @@ export type Action = vault: VaultBaseForString } } + | { + vault_request_unlock: { + amount: Uint128 + vault: VaultBaseForString + } + } + | { + vault_withdraw_unlocked: { + id: Uint128 + vault: VaultBaseForString + } + } | { liquidate_coin: { debt_coin: Coin @@ -133,6 +145,20 @@ export type CallbackMsg = vault: VaultBaseForAddr } } + | { + vault_request_unlock: { + account_id: string + amount: Uint128 + vault: VaultBaseForAddr + } + } + | { + vault_withdraw_unlocked: { + account_id: string + position_id: Uint128 + vault: VaultBaseForAddr + } + } | { liquidate_coin: { debt_coin: Coin @@ -279,6 +305,11 @@ export interface VaultPosition { export interface VaultPositionState { locked: Uint128 unlocked: Uint128 + unlocking: VaultUnlockingId[] +} +export interface VaultUnlockingId { + amount: Uint128 + id: Uint128 } export type ArrayOfString = string[] export type ArrayOfVaultBaseForString = VaultBaseForString[] diff --git a/scripts/types/generated/credit-manager/bundle.ts b/scripts/types/generated/credit-manager/bundle.ts index 5b6328c60..9aa441b7b 100644 --- a/scripts/types/generated/credit-manager/bundle.ts +++ b/scripts/types/generated/credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts index 40e562bfd..af267ab4d 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts index 8ecf8c9bd..a1af7b2c5 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts index 4dca1c4fa..7457699e6 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -58,5 +58,4 @@ export type Decimal = string export interface PriceResponse { denom: string price: Decimal - [k: string]: unknown } diff --git a/scripts/types/generated/mars-oracle-adapter/bundle.ts b/scripts/types/generated/mars-oracle-adapter/bundle.ts index 02375a2b8..20637870c 100644 --- a/scripts/types/generated/mars-oracle-adapter/bundle.ts +++ b/scripts/types/generated/mars-oracle-adapter/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-oracle/MockOracle.client.ts b/scripts/types/generated/mock-oracle/MockOracle.client.ts index f22720f36..ab10b948f 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.client.ts +++ b/scripts/types/generated/mock-oracle/MockOracle.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-oracle/MockOracle.react-query.ts b/scripts/types/generated/mock-oracle/MockOracle.react-query.ts index aca388f4d..5f30f22b4 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.react-query.ts +++ b/scripts/types/generated/mock-oracle/MockOracle.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-oracle/MockOracle.types.ts b/scripts/types/generated/mock-oracle/MockOracle.types.ts index 833e13cb0..84b003a8f 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.types.ts +++ b/scripts/types/generated/mock-oracle/MockOracle.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -24,5 +24,4 @@ export type QueryMsg = { export interface PriceResponse { denom: string price: Decimal - [k: string]: unknown } diff --git a/scripts/types/generated/mock-oracle/bundle.ts b/scripts/types/generated/mock-oracle/bundle.ts index 5a59629e7..e017ba9ba 100644 --- a/scripts/types/generated/mock-oracle/bundle.ts +++ b/scripts/types/generated/mock-oracle/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.client.ts b/scripts/types/generated/mock-red-bank/MockRedBank.client.ts index 2950d9db3..67eb3d409 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.client.ts +++ b/scripts/types/generated/mock-red-bank/MockRedBank.client.ts @@ -1,34 +1,104 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee } from '@cosmjs/amino' +import { Coin, StdFee } from '@cosmjs/amino' import { Decimal, InstantiateMsg, CoinMarketInfo, ExecuteMsg, Uint128, - Coin, + CreateOrUpdateConfig, + InitOrUpdateAssetParams, + InterestRateModel, QueryMsg, + ConfigForString, Market, - InterestRateModel, - UserAssetDebtResponse, + ArrayOfMarket, + UncollateralizedLoanLimitResponse, + ArrayOfUncollateralizedLoanLimitResponse, + UserCollateralResponse, + ArrayOfUserCollateralResponse, + UserDebtResponse, + ArrayOfUserDebtResponse, + UserHealthStatus, + UserPositionResponse, } from './MockRedBank.types' export interface MockRedBankReadOnlyInterface { contractAddress: string - userAssetDebt: ({ + config: () => Promise + market: ({ denom }: { denom: string }) => Promise + markets: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }) => Promise + uncollateralizedLoanLimit: ({ denom, - userAddress, + user, }: { denom: string - userAddress: string - }) => Promise - market: ({ denom }: { denom: string }) => Promise + user: string + }) => Promise + uncollateralizedLoanLimits: ({ + limit, + startAfter, + user, + }: { + limit?: number + startAfter?: string + user: string + }) => Promise + userDebt: ({ denom, user }: { denom: string; user: string }) => Promise + userDebts: ({ + limit, + startAfter, + user, + }: { + limit?: number + startAfter?: string + user: string + }) => Promise + userCollateral: ({ + denom, + user, + }: { + denom: string + user: string + }) => Promise + userCollaterals: ({ + limit, + startAfter, + user, + }: { + limit?: number + startAfter?: string + user: string + }) => Promise + userPosition: ({ user }: { user: string }) => Promise + scaledLiquidityAmount: ({ amount, denom }: { amount: Uint128; denom: string }) => Promise + scaledDebtAmount: ({ amount, denom }: { amount: Uint128; denom: string }) => Promise + underlyingLiquidityAmount: ({ + amountScaled, + denom, + }: { + amountScaled: Uint128 + denom: string + }) => Promise + underlyingDebtAmount: ({ + amountScaled, + denom, + }: { + amountScaled: Uint128 + denom: string + }) => Promise } export class MockRedBankQueryClient implements MockRedBankReadOnlyInterface { client: CosmWasmClient @@ -37,27 +107,200 @@ export class MockRedBankQueryClient implements MockRedBankReadOnlyInterface { constructor(client: CosmWasmClient, contractAddress: string) { this.client = client this.contractAddress = contractAddress - this.userAssetDebt = this.userAssetDebt.bind(this) + this.config = this.config.bind(this) this.market = this.market.bind(this) + this.markets = this.markets.bind(this) + this.uncollateralizedLoanLimit = this.uncollateralizedLoanLimit.bind(this) + this.uncollateralizedLoanLimits = this.uncollateralizedLoanLimits.bind(this) + this.userDebt = this.userDebt.bind(this) + this.userDebts = this.userDebts.bind(this) + this.userCollateral = this.userCollateral.bind(this) + this.userCollaterals = this.userCollaterals.bind(this) + this.userPosition = this.userPosition.bind(this) + this.scaledLiquidityAmount = this.scaledLiquidityAmount.bind(this) + this.scaledDebtAmount = this.scaledDebtAmount.bind(this) + this.underlyingLiquidityAmount = this.underlyingLiquidityAmount.bind(this) + this.underlyingDebtAmount = this.underlyingDebtAmount.bind(this) } - userAssetDebt = async ({ + config = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + config: {}, + }) + } + market = async ({ denom }: { denom: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + market: { + denom, + }, + }) + } + markets = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + markets: { + limit, + start_after: startAfter, + }, + }) + } + uncollateralizedLoanLimit = async ({ denom, - userAddress, + user, }: { denom: string - userAddress: string - }): Promise => { + user: string + }): Promise => { return this.client.queryContractSmart(this.contractAddress, { - user_asset_debt: { + uncollateralized_loan_limit: { denom, - user_address: userAddress, + user, }, }) } - market = async ({ denom }: { denom: string }): Promise => { + uncollateralizedLoanLimits = async ({ + limit, + startAfter, + user, + }: { + limit?: number + startAfter?: string + user: string + }): Promise => { return this.client.queryContractSmart(this.contractAddress, { - market: { + uncollateralized_loan_limits: { + limit, + start_after: startAfter, + user, + }, + }) + } + userDebt = async ({ + denom, + user, + }: { + denom: string + user: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + user_debt: { + denom, + user, + }, + }) + } + userDebts = async ({ + limit, + startAfter, + user, + }: { + limit?: number + startAfter?: string + user: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + user_debts: { + limit, + start_after: startAfter, + user, + }, + }) + } + userCollateral = async ({ + denom, + user, + }: { + denom: string + user: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + user_collateral: { + denom, + user, + }, + }) + } + userCollaterals = async ({ + limit, + startAfter, + user, + }: { + limit?: number + startAfter?: string + user: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + user_collaterals: { + limit, + start_after: startAfter, + user, + }, + }) + } + userPosition = async ({ user }: { user: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + user_position: { + user, + }, + }) + } + scaledLiquidityAmount = async ({ + amount, + denom, + }: { + amount: Uint128 + denom: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + scaled_liquidity_amount: { + amount, + denom, + }, + }) + } + scaledDebtAmount = async ({ + amount, + denom, + }: { + amount: Uint128 + denom: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + scaled_debt_amount: { + amount, + denom, + }, + }) + } + underlyingLiquidityAmount = async ({ + amountScaled, + denom, + }: { + amountScaled: Uint128 + denom: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + underlying_liquidity_amount: { + amount_scaled: amountScaled, + denom, + }, + }) + } + underlyingDebtAmount = async ({ + amountScaled, + denom, + }: { + amountScaled: Uint128 + denom: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + underlying_debt_amount: { + amount_scaled: amountScaled, denom, }, }) @@ -66,12 +309,86 @@ export class MockRedBankQueryClient implements MockRedBankReadOnlyInterface { export interface MockRedBankInterface extends MockRedBankReadOnlyInterface { contractAddress: string sender: string + updateConfig: ( + { + config, + }: { + config: CreateOrUpdateConfig + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + initAsset: ( + { + denom, + params, + }: { + denom: string + params: InitOrUpdateAssetParams + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + updateAsset: ( + { + denom, + params, + }: { + denom: string + params: InitOrUpdateAssetParams + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + updateUncollateralizedLoanLimit: ( + { + denom, + newLimit, + user, + }: { + denom: string + newLimit: Uint128 + user: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + deposit: ( + { + onBehalfOf, + }: { + onBehalfOf?: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + withdraw: ( + { + amount, + denom, + recipient, + }: { + amount?: Uint128 + denom: string + recipient?: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise borrow: ( { - coin, + amount, + denom, recipient, }: { - coin: Coin + amount: Uint128 + denom: string recipient?: string }, fee?: number | StdFee | 'auto', @@ -80,16 +397,40 @@ export interface MockRedBankInterface extends MockRedBankReadOnlyInterface { ) => Promise repay: ( { - denom, onBehalfOf, }: { - denom: string onBehalfOf?: string }, fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], ) => Promise + liquidate: ( + { + collateralDenom, + recipient, + user, + }: { + collateralDenom: string + recipient?: string + user: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + updateAssetCollateralStatus: ( + { + denom, + enable, + }: { + denom: string + enable: boolean + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise } export class MockRedBankClient extends MockRedBankQueryClient implements MockRedBankInterface { client: SigningCosmWasmClient @@ -101,16 +442,182 @@ export class MockRedBankClient extends MockRedBankQueryClient implements MockRed this.client = client this.sender = sender this.contractAddress = contractAddress + this.updateConfig = this.updateConfig.bind(this) + this.initAsset = this.initAsset.bind(this) + this.updateAsset = this.updateAsset.bind(this) + this.updateUncollateralizedLoanLimit = this.updateUncollateralizedLoanLimit.bind(this) + this.deposit = this.deposit.bind(this) + this.withdraw = this.withdraw.bind(this) this.borrow = this.borrow.bind(this) this.repay = this.repay.bind(this) + this.liquidate = this.liquidate.bind(this) + this.updateAssetCollateralStatus = this.updateAssetCollateralStatus.bind(this) } + updateConfig = async ( + { + config, + }: { + config: CreateOrUpdateConfig + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_config: { + config, + }, + }, + fee, + memo, + funds, + ) + } + initAsset = async ( + { + denom, + params, + }: { + denom: string + params: InitOrUpdateAssetParams + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + init_asset: { + denom, + params, + }, + }, + fee, + memo, + funds, + ) + } + updateAsset = async ( + { + denom, + params, + }: { + denom: string + params: InitOrUpdateAssetParams + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_asset: { + denom, + params, + }, + }, + fee, + memo, + funds, + ) + } + updateUncollateralizedLoanLimit = async ( + { + denom, + newLimit, + user, + }: { + denom: string + newLimit: Uint128 + user: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_uncollateralized_loan_limit: { + denom, + new_limit: newLimit, + user, + }, + }, + fee, + memo, + funds, + ) + } + deposit = async ( + { + onBehalfOf, + }: { + onBehalfOf?: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + deposit: { + on_behalf_of: onBehalfOf, + }, + }, + fee, + memo, + funds, + ) + } + withdraw = async ( + { + amount, + denom, + recipient, + }: { + amount?: Uint128 + denom: string + recipient?: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + withdraw: { + amount, + denom, + recipient, + }, + }, + fee, + memo, + funds, + ) + } borrow = async ( { - coin, + amount, + denom, recipient, }: { - coin: Coin + amount: Uint128 + denom: string recipient?: string }, fee: number | StdFee | 'auto' = 'auto', @@ -122,7 +629,8 @@ export class MockRedBankClient extends MockRedBankQueryClient implements MockRed this.contractAddress, { borrow: { - coin, + amount, + denom, recipient, }, }, @@ -133,10 +641,8 @@ export class MockRedBankClient extends MockRedBankQueryClient implements MockRed } repay = async ( { - denom, onBehalfOf, }: { - denom: string onBehalfOf?: string }, fee: number | StdFee | 'auto' = 'auto', @@ -148,7 +654,6 @@ export class MockRedBankClient extends MockRedBankQueryClient implements MockRed this.contractAddress, { repay: { - denom, on_behalf_of: onBehalfOf, }, }, @@ -157,4 +662,59 @@ export class MockRedBankClient extends MockRedBankQueryClient implements MockRed funds, ) } + liquidate = async ( + { + collateralDenom, + recipient, + user, + }: { + collateralDenom: string + recipient?: string + user: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + liquidate: { + collateral_denom: collateralDenom, + recipient, + user, + }, + }, + fee, + memo, + funds, + ) + } + updateAssetCollateralStatus = async ( + { + denom, + enable, + }: { + denom: string + enable: boolean + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_asset_collateral_status: { + denom, + enable, + }, + }, + fee, + memo, + funds, + ) + } } diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts b/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts index e059eabb2..8785e7eeb 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts +++ b/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts @@ -1,24 +1,34 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee } from '@cosmjs/amino' +import { StdFee, Coin } from '@cosmjs/amino' import { Decimal, InstantiateMsg, CoinMarketInfo, ExecuteMsg, Uint128, - Coin, + CreateOrUpdateConfig, + InitOrUpdateAssetParams, + InterestRateModel, QueryMsg, + ConfigForString, Market, - InterestRateModel, - UserAssetDebtResponse, + ArrayOfMarket, + UncollateralizedLoanLimitResponse, + ArrayOfUncollateralizedLoanLimitResponse, + UserCollateralResponse, + ArrayOfUserCollateralResponse, + UserDebtResponse, + ArrayOfUserDebtResponse, + UserHealthStatus, + UserPositionResponse, } from './MockRedBank.types' import { MockRedBankQueryClient, MockRedBankClient } from './MockRedBank.client' export const mockRedBankQueryKeys = { @@ -29,12 +39,81 @@ export const mockRedBankQueryKeys = { ] as const, address: (contractAddress: string | undefined) => [{ ...mockRedBankQueryKeys.contract[0], address: contractAddress }] as const, - userAssetDebt: (contractAddress: string | undefined, args?: Record) => - [ - { ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'user_asset_debt', args }, - ] as const, + config: (contractAddress: string | undefined, args?: Record) => + [{ ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, market: (contractAddress: string | undefined, args?: Record) => [{ ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'market', args }] as const, + markets: (contractAddress: string | undefined, args?: Record) => + [{ ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'markets', args }] as const, + uncollateralizedLoanLimit: ( + contractAddress: string | undefined, + args?: Record, + ) => + [ + { + ...mockRedBankQueryKeys.address(contractAddress)[0], + method: 'uncollateralized_loan_limit', + args, + }, + ] as const, + uncollateralizedLoanLimits: ( + contractAddress: string | undefined, + args?: Record, + ) => + [ + { + ...mockRedBankQueryKeys.address(contractAddress)[0], + method: 'uncollateralized_loan_limits', + args, + }, + ] as const, + userDebt: (contractAddress: string | undefined, args?: Record) => + [{ ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'user_debt', args }] as const, + userDebts: (contractAddress: string | undefined, args?: Record) => + [{ ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'user_debts', args }] as const, + userCollateral: (contractAddress: string | undefined, args?: Record) => + [ + { ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'user_collateral', args }, + ] as const, + userCollaterals: (contractAddress: string | undefined, args?: Record) => + [ + { ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'user_collaterals', args }, + ] as const, + userPosition: (contractAddress: string | undefined, args?: Record) => + [ + { ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'user_position', args }, + ] as const, + scaledLiquidityAmount: (contractAddress: string | undefined, args?: Record) => + [ + { + ...mockRedBankQueryKeys.address(contractAddress)[0], + method: 'scaled_liquidity_amount', + args, + }, + ] as const, + scaledDebtAmount: (contractAddress: string | undefined, args?: Record) => + [ + { ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'scaled_debt_amount', args }, + ] as const, + underlyingLiquidityAmount: ( + contractAddress: string | undefined, + args?: Record, + ) => + [ + { + ...mockRedBankQueryKeys.address(contractAddress)[0], + method: 'underlying_liquidity_amount', + args, + }, + ] as const, + underlyingDebtAmount: (contractAddress: string | undefined, args?: Record) => + [ + { + ...mockRedBankQueryKeys.address(contractAddress)[0], + method: 'underlying_debt_amount', + args, + }, + ] as const, } export interface MockRedBankReactQuery { client: MockRedBankQueryClient | undefined @@ -45,55 +124,378 @@ export interface MockRedBankReactQuery { initialData?: undefined } } -export interface MockRedBankMarketQuery extends MockRedBankReactQuery { +export interface MockRedBankUnderlyingDebtAmountQuery + extends MockRedBankReactQuery { args: { + amountScaled: Uint128 denom: string } } -export function useMockRedBankMarketQuery({ +export function useMockRedBankUnderlyingDebtAmountQuery({ client, args, options, -}: MockRedBankMarketQuery) { - return useQuery( - mockRedBankQueryKeys.market(client?.contractAddress, args), +}: MockRedBankUnderlyingDebtAmountQuery) { + return useQuery( + mockRedBankQueryKeys.underlyingDebtAmount(client?.contractAddress, args), () => client - ? client.market({ + ? client.underlyingDebtAmount({ + amountScaled: args.amountScaled, denom: args.denom, }) : Promise.reject(new Error('Invalid client')), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankUserAssetDebtQuery - extends MockRedBankReactQuery { +export interface MockRedBankUnderlyingLiquidityAmountQuery + extends MockRedBankReactQuery { args: { + amountScaled: Uint128 denom: string - userAddress: string } } -export function useMockRedBankUserAssetDebtQuery({ +export function useMockRedBankUnderlyingLiquidityAmountQuery({ client, args, options, -}: MockRedBankUserAssetDebtQuery) { - return useQuery( - mockRedBankQueryKeys.userAssetDebt(client?.contractAddress, args), +}: MockRedBankUnderlyingLiquidityAmountQuery) { + return useQuery( + mockRedBankQueryKeys.underlyingLiquidityAmount(client?.contractAddress, args), () => client - ? client.userAssetDebt({ + ? client.underlyingLiquidityAmount({ + amountScaled: args.amountScaled, denom: args.denom, - userAddress: args.userAddress, }) : Promise.reject(new Error('Invalid client')), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankRepayMutation { +export interface MockRedBankScaledDebtAmountQuery + extends MockRedBankReactQuery { + args: { + amount: Uint128 + denom: string + } +} +export function useMockRedBankScaledDebtAmountQuery({ + client, + args, + options, +}: MockRedBankScaledDebtAmountQuery) { + return useQuery( + mockRedBankQueryKeys.scaledDebtAmount(client?.contractAddress, args), + () => + client + ? client.scaledDebtAmount({ + amount: args.amount, + denom: args.denom, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankScaledLiquidityAmountQuery + extends MockRedBankReactQuery { + args: { + amount: Uint128 + denom: string + } +} +export function useMockRedBankScaledLiquidityAmountQuery({ + client, + args, + options, +}: MockRedBankScaledLiquidityAmountQuery) { + return useQuery( + mockRedBankQueryKeys.scaledLiquidityAmount(client?.contractAddress, args), + () => + client + ? client.scaledLiquidityAmount({ + amount: args.amount, + denom: args.denom, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankUserPositionQuery + extends MockRedBankReactQuery { + args: { + user: string + } +} +export function useMockRedBankUserPositionQuery({ + client, + args, + options, +}: MockRedBankUserPositionQuery) { + return useQuery( + mockRedBankQueryKeys.userPosition(client?.contractAddress, args), + () => + client + ? client.userPosition({ + user: args.user, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankUserCollateralsQuery + extends MockRedBankReactQuery { + args: { + limit?: number + startAfter?: string + user: string + } +} +export function useMockRedBankUserCollateralsQuery({ + client, + args, + options, +}: MockRedBankUserCollateralsQuery) { + return useQuery( + mockRedBankQueryKeys.userCollaterals(client?.contractAddress, args), + () => + client + ? client.userCollaterals({ + limit: args.limit, + startAfter: args.startAfter, + user: args.user, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankUserCollateralQuery + extends MockRedBankReactQuery { + args: { + denom: string + user: string + } +} +export function useMockRedBankUserCollateralQuery({ + client, + args, + options, +}: MockRedBankUserCollateralQuery) { + return useQuery( + mockRedBankQueryKeys.userCollateral(client?.contractAddress, args), + () => + client + ? client.userCollateral({ + denom: args.denom, + user: args.user, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankUserDebtsQuery + extends MockRedBankReactQuery { + args: { + limit?: number + startAfter?: string + user: string + } +} +export function useMockRedBankUserDebtsQuery({ + client, + args, + options, +}: MockRedBankUserDebtsQuery) { + return useQuery( + mockRedBankQueryKeys.userDebts(client?.contractAddress, args), + () => + client + ? client.userDebts({ + limit: args.limit, + startAfter: args.startAfter, + user: args.user, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankUserDebtQuery + extends MockRedBankReactQuery { + args: { + denom: string + user: string + } +} +export function useMockRedBankUserDebtQuery({ + client, + args, + options, +}: MockRedBankUserDebtQuery) { + return useQuery( + mockRedBankQueryKeys.userDebt(client?.contractAddress, args), + () => + client + ? client.userDebt({ + denom: args.denom, + user: args.user, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankUncollateralizedLoanLimitsQuery + extends MockRedBankReactQuery { + args: { + limit?: number + startAfter?: string + user: string + } +} +export function useMockRedBankUncollateralizedLoanLimitsQuery< + TData = ArrayOfUncollateralizedLoanLimitResponse, +>({ client, args, options }: MockRedBankUncollateralizedLoanLimitsQuery) { + return useQuery( + mockRedBankQueryKeys.uncollateralizedLoanLimits(client?.contractAddress, args), + () => + client + ? client.uncollateralizedLoanLimits({ + limit: args.limit, + startAfter: args.startAfter, + user: args.user, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankUncollateralizedLoanLimitQuery + extends MockRedBankReactQuery { + args: { + denom: string + user: string + } +} +export function useMockRedBankUncollateralizedLoanLimitQuery< + TData = UncollateralizedLoanLimitResponse, +>({ client, args, options }: MockRedBankUncollateralizedLoanLimitQuery) { + return useQuery( + mockRedBankQueryKeys.uncollateralizedLoanLimit(client?.contractAddress, args), + () => + client + ? client.uncollateralizedLoanLimit({ + denom: args.denom, + user: args.user, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankMarketsQuery + extends MockRedBankReactQuery { + args: { + limit?: number + startAfter?: string + } +} +export function useMockRedBankMarketsQuery({ + client, + args, + options, +}: MockRedBankMarketsQuery) { + return useQuery( + mockRedBankQueryKeys.markets(client?.contractAddress, args), + () => + client + ? client.markets({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankMarketQuery extends MockRedBankReactQuery { + args: { + denom: string + } +} +export function useMockRedBankMarketQuery({ + client, + args, + options, +}: MockRedBankMarketQuery) { + return useQuery( + mockRedBankQueryKeys.market(client?.contractAddress, args), + () => + client + ? client.market({ + denom: args.denom, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankConfigQuery + extends MockRedBankReactQuery {} +export function useMockRedBankConfigQuery({ + client, + options, +}: MockRedBankConfigQuery) { + return useQuery( + mockRedBankQueryKeys.config(client?.contractAddress), + () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockRedBankUpdateAssetCollateralStatusMutation { client: MockRedBankClient msg: { denom: string + enable: boolean + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockRedBankUpdateAssetCollateralStatusMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateAssetCollateralStatus(msg, fee, memo, funds), + options, + ) +} +export interface MockRedBankLiquidateMutation { + client: MockRedBankClient + msg: { + collateralDenom: string + recipient?: string + user: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockRedBankLiquidateMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.liquidate(msg, fee, memo, funds), + options, + ) +} +export interface MockRedBankRepayMutation { + client: MockRedBankClient + msg: { onBehalfOf?: string } args?: { @@ -113,7 +515,8 @@ export function useMockRedBankRepayMutation( export interface MockRedBankBorrowMutation { client: MockRedBankClient msg: { - coin: Coin + amount: Uint128 + denom: string recipient?: string } args?: { @@ -130,3 +533,143 @@ export function useMockRedBankBorrowMutation( options, ) } +export interface MockRedBankWithdrawMutation { + client: MockRedBankClient + msg: { + amount?: Uint128 + denom: string + recipient?: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockRedBankWithdrawMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.withdraw(msg, fee, memo, funds), + options, + ) +} +export interface MockRedBankDepositMutation { + client: MockRedBankClient + msg: { + onBehalfOf?: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockRedBankDepositMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.deposit(msg, fee, memo, funds), + options, + ) +} +export interface MockRedBankUpdateUncollateralizedLoanLimitMutation { + client: MockRedBankClient + msg: { + denom: string + newLimit: Uint128 + user: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockRedBankUpdateUncollateralizedLoanLimitMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateUncollateralizedLoanLimit(msg, fee, memo, funds), + options, + ) +} +export interface MockRedBankUpdateAssetMutation { + client: MockRedBankClient + msg: { + denom: string + params: InitOrUpdateAssetParams + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockRedBankUpdateAssetMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateAsset(msg, fee, memo, funds), + options, + ) +} +export interface MockRedBankInitAssetMutation { + client: MockRedBankClient + msg: { + denom: string + params: InitOrUpdateAssetParams + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockRedBankInitAssetMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.initAsset(msg, fee, memo, funds), + options, + ) +} +export interface MockRedBankUpdateConfigMutation { + client: MockRedBankClient + msg: { + config: CreateOrUpdateConfig + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockRedBankUpdateConfigMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateConfig(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.types.ts b/scripts/types/generated/mock-red-bank/MockRedBank.types.ts index ee4ae957e..2035efaf5 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.types.ts +++ b/scripts/types/generated/mock-red-bank/MockRedBank.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -15,36 +15,178 @@ export interface CoinMarketInfo { max_ltv: Decimal } export type ExecuteMsg = + | { + update_config: { + config: CreateOrUpdateConfig + } + } + | { + init_asset: { + denom: string + params: InitOrUpdateAssetParams + } + } + | { + update_asset: { + denom: string + params: InitOrUpdateAssetParams + } + } + | { + update_uncollateralized_loan_limit: { + denom: string + new_limit: Uint128 + user: string + } + } + | { + deposit: { + on_behalf_of?: string | null + } + } + | { + withdraw: { + amount?: Uint128 | null + denom: string + recipient?: string | null + } + } | { borrow: { - coin: Coin + amount: Uint128 + denom: string recipient?: string | null } } | { repay: { - denom: string on_behalf_of?: string | null } } + | { + liquidate: { + collateral_denom: string + recipient?: string | null + user: string + } + } + | { + update_asset_collateral_status: { + denom: string + enable: boolean + } + } export type Uint128 = string -export interface Coin { - amount: Uint128 - denom: string - [k: string]: unknown +export interface CreateOrUpdateConfig { + address_provider?: string | null + close_factor?: Decimal | null + owner?: string | null +} +export interface InitOrUpdateAssetParams { + borrow_enabled?: boolean | null + deposit_cap?: Uint128 | null + deposit_enabled?: boolean | null + initial_borrow_rate?: Decimal | null + interest_rate_model?: InterestRateModel | null + liquidation_bonus?: Decimal | null + liquidation_threshold?: Decimal | null + max_loan_to_value?: Decimal | null + reserve_factor?: Decimal | null +} +export interface InterestRateModel { + base: Decimal + optimal_utilization_rate: Decimal + slope_1: Decimal + slope_2: Decimal } export type QueryMsg = | { - user_asset_debt: { + config: {} + } + | { + market: { denom: string - user_address: string } } | { - market: { + markets: { + limit?: number | null + start_after?: string | null + } + } + | { + uncollateralized_loan_limit: { denom: string + user: string } } + | { + uncollateralized_loan_limits: { + limit?: number | null + start_after?: string | null + user: string + } + } + | { + user_debt: { + denom: string + user: string + } + } + | { + user_debts: { + limit?: number | null + start_after?: string | null + user: string + } + } + | { + user_collateral: { + denom: string + user: string + } + } + | { + user_collaterals: { + limit?: number | null + start_after?: string | null + user: string + } + } + | { + user_position: { + user: string + } + } + | { + scaled_liquidity_amount: { + amount: Uint128 + denom: string + } + } + | { + scaled_debt_amount: { + amount: Uint128 + denom: string + } + } + | { + underlying_liquidity_amount: { + amount_scaled: Uint128 + denom: string + } + } + | { + underlying_debt_amount: { + amount_scaled: Uint128 + denom: string + } + } +export interface ConfigForString { + address_provider: string + close_factor: Decimal + owner: string +} export interface Market { borrow_enabled: boolean borrow_index: Decimal @@ -62,16 +204,39 @@ export interface Market { liquidity_rate: Decimal max_loan_to_value: Decimal reserve_factor: Decimal - [k: string]: unknown } -export interface InterestRateModel { - base: Decimal - optimal_utilization_rate: Decimal - slope_1: Decimal - slope_2: Decimal - [k: string]: unknown +export type ArrayOfMarket = Market[] +export interface UncollateralizedLoanLimitResponse { + denom: string + limit: Uint128 +} +export type ArrayOfUncollateralizedLoanLimitResponse = UncollateralizedLoanLimitResponse[] +export interface UserCollateralResponse { + amount: Uint128 + amount_scaled: Uint128 + denom: string + enabled: boolean } -export interface UserAssetDebtResponse { +export type ArrayOfUserCollateralResponse = UserCollateralResponse[] +export interface UserDebtResponse { amount: Uint128 + amount_scaled: Uint128 denom: string + uncollateralized: boolean +} +export type ArrayOfUserDebtResponse = UserDebtResponse[] +export type UserHealthStatus = + | 'not_borrowing' + | { + borrowing: { + liq_threshold_hf: Decimal + max_ltv_hf: Decimal + } + } +export interface UserPositionResponse { + health_status: UserHealthStatus + total_collateralized_debt: Decimal + total_enabled_collateral: Decimal + weighted_liquidation_threshold_collateral: Decimal + weighted_max_ltv_collateral: Decimal } diff --git a/scripts/types/generated/mock-red-bank/bundle.ts b/scripts/types/generated/mock-red-bank/bundle.ts index 7b5a5df82..9687acebc 100644 --- a/scripts/types/generated/mock-red-bank/bundle.ts +++ b/scripts/types/generated/mock-red-bank/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-vault/MockVault.client.ts b/scripts/types/generated/mock-vault/MockVault.client.ts index 3c40db792..0dfb77e1c 100644 --- a/scripts/types/generated/mock-vault/MockVault.client.ts +++ b/scripts/types/generated/mock-vault/MockVault.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -11,17 +11,23 @@ import { OracleBaseForString, InstantiateMsg, ExecuteMsg, - QueryMsg, Uint128, + QueryMsg, VaultInfo, - Coin, ArrayOfCoin, + Coin, + Timestamp, + Uint64, + UnlockingPosition, + ArrayOfUnlockingPosition, } from './MockVault.types' export interface MockVaultReadOnlyInterface { contractAddress: string info: () => Promise previewRedeem: ({ amount }: { amount: Uint128 }) => Promise totalVaultCoinsIssued: () => Promise + unlockingPositionsForAddr: ({ addr }: { addr: string }) => Promise + unlockingPosition: ({ id }: { id: Uint128 }) => Promise } export class MockVaultQueryClient implements MockVaultReadOnlyInterface { client: CosmWasmClient @@ -33,6 +39,8 @@ export class MockVaultQueryClient implements MockVaultReadOnlyInterface { this.info = this.info.bind(this) this.previewRedeem = this.previewRedeem.bind(this) this.totalVaultCoinsIssued = this.totalVaultCoinsIssued.bind(this) + this.unlockingPositionsForAddr = this.unlockingPositionsForAddr.bind(this) + this.unlockingPosition = this.unlockingPosition.bind(this) } info = async (): Promise => { @@ -52,6 +60,24 @@ export class MockVaultQueryClient implements MockVaultReadOnlyInterface { total_vault_coins_issued: {}, }) } + unlockingPositionsForAddr = async ({ + addr, + }: { + addr: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + unlocking_positions_for_addr: { + addr, + }, + }) + } + unlockingPosition = async ({ id }: { id: Uint128 }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + unlocking_position: { + id, + }, + }) + } } export interface MockVaultInterface extends MockVaultReadOnlyInterface { contractAddress: string @@ -67,6 +93,21 @@ export interface MockVaultInterface extends MockVaultReadOnlyInterface { memo?: string, funds?: Coin[], ) => Promise + requestUnlock: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + withdrawUnlocked: ( + { + id, + }: { + id: Uint128 + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise } export class MockVaultClient extends MockVaultQueryClient implements MockVaultInterface { client: SigningCosmWasmClient @@ -81,6 +122,8 @@ export class MockVaultClient extends MockVaultQueryClient implements MockVaultIn this.deposit = this.deposit.bind(this) this.withdraw = this.withdraw.bind(this) this.forceWithdraw = this.forceWithdraw.bind(this) + this.requestUnlock = this.requestUnlock.bind(this) + this.withdrawUnlocked = this.withdrawUnlocked.bind(this) } deposit = async ( @@ -131,4 +174,43 @@ export class MockVaultClient extends MockVaultQueryClient implements MockVaultIn funds, ) } + requestUnlock = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + request_unlock: {}, + }, + fee, + memo, + funds, + ) + } + withdrawUnlocked = async ( + { + id, + }: { + id: Uint128 + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + withdraw_unlocked: { + id, + }, + }, + fee, + memo, + funds, + ) + } } diff --git a/scripts/types/generated/mock-vault/MockVault.react-query.ts b/scripts/types/generated/mock-vault/MockVault.react-query.ts index c4ca251dc..ec5441751 100644 --- a/scripts/types/generated/mock-vault/MockVault.react-query.ts +++ b/scripts/types/generated/mock-vault/MockVault.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -12,11 +12,15 @@ import { OracleBaseForString, InstantiateMsg, ExecuteMsg, - QueryMsg, Uint128, + QueryMsg, VaultInfo, - Coin, ArrayOfCoin, + Coin, + Timestamp, + Uint64, + UnlockingPosition, + ArrayOfUnlockingPosition, } from './MockVault.types' import { MockVaultQueryClient, MockVaultClient } from './MockVault.client' export const mockVaultQueryKeys = { @@ -41,6 +45,21 @@ export const mockVaultQueryKeys = { args, }, ] as const, + unlockingPositionsForAddr: ( + contractAddress: string | undefined, + args?: Record, + ) => + [ + { + ...mockVaultQueryKeys.address(contractAddress)[0], + method: 'unlocking_positions_for_addr', + args, + }, + ] as const, + unlockingPosition: (contractAddress: string | undefined, args?: Record) => + [ + { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'unlocking_position', args }, + ] as const, } export interface MockVaultReactQuery { client: MockVaultQueryClient | undefined @@ -51,6 +70,50 @@ export interface MockVaultReactQuery { initialData?: undefined } } +export interface MockVaultUnlockingPositionQuery + extends MockVaultReactQuery { + args: { + id: Uint128 + } +} +export function useMockVaultUnlockingPositionQuery({ + client, + args, + options, +}: MockVaultUnlockingPositionQuery) { + return useQuery( + mockVaultQueryKeys.unlockingPosition(client?.contractAddress, args), + () => + client + ? client.unlockingPosition({ + id: args.id, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockVaultUnlockingPositionsForAddrQuery + extends MockVaultReactQuery { + args: { + addr: string + } +} +export function useMockVaultUnlockingPositionsForAddrQuery({ + client, + args, + options, +}: MockVaultUnlockingPositionsForAddrQuery) { + return useQuery( + mockVaultQueryKeys.unlockingPositionsForAddr(client?.contractAddress, args), + () => + client + ? client.unlockingPositionsForAddr({ + addr: args.addr, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MockVaultTotalVaultCoinsIssuedQuery extends MockVaultReactQuery {} export function useMockVaultTotalVaultCoinsIssuedQuery({ @@ -96,6 +159,48 @@ export function useMockVaultInfoQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MockVaultWithdrawUnlockedMutation { + client: MockVaultClient + msg: { + id: Uint128 + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockVaultWithdrawUnlockedMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.withdrawUnlocked(msg, fee, memo, funds), + options, + ) +} +export interface MockVaultRequestUnlockMutation { + client: MockVaultClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockVaultRequestUnlockMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, args: { fee, memo, funds } = {} }) => client.requestUnlock(fee, memo, funds), + options, + ) +} export interface MockVaultForceWithdrawMutation { client: MockVaultClient args?: { diff --git a/scripts/types/generated/mock-vault/MockVault.types.ts b/scripts/types/generated/mock-vault/MockVault.types.ts index 8114114a7..6234300bb 100644 --- a/scripts/types/generated/mock-vault/MockVault.types.ts +++ b/scripts/types/generated/mock-vault/MockVault.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -22,6 +22,15 @@ export type ExecuteMsg = | { force_withdraw: {} } + | { + request_unlock: {} + } + | { + withdraw_unlocked: { + id: Uint128 + } + } +export type Uint128 = string export type QueryMsg = | { info: {} @@ -34,15 +43,32 @@ export type QueryMsg = | { total_vault_coins_issued: {} } -export type Uint128 = string + | { + unlocking_positions_for_addr: { + addr: string + } + } + | { + unlocking_position: { + id: Uint128 + } + } export interface VaultInfo { - coins: Coin[] + accepts: string[][] lockup?: number | null - token_denom: string + vault_coin_denom: string } +export type ArrayOfCoin = Coin[] export interface Coin { amount: Uint128 denom: string [k: string]: unknown } -export type ArrayOfCoin = Coin[] +export type Timestamp = Uint64 +export type Uint64 = string +export interface UnlockingPosition { + amount: Uint128 + id: Uint128 + unlocked_at: Timestamp +} +export type ArrayOfUnlockingPosition = UnlockingPosition[] diff --git a/scripts/types/generated/mock-vault/bundle.ts b/scripts/types/generated/mock-vault/bundle.ts index 82599902c..8ea78d991 100644 --- a/scripts/types/generated/mock-vault/bundle.ts +++ b/scripts/types/generated/mock-vault/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/swapper-base/SwapperBase.client.ts b/scripts/types/generated/swapper-base/SwapperBase.client.ts index 02a924c0c..8db2ed9ed 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.client.ts +++ b/scripts/types/generated/swapper-base/SwapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/swapper-base/SwapperBase.react-query.ts b/scripts/types/generated/swapper-base/SwapperBase.react-query.ts index a47fdc9e0..5f9795aae 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.react-query.ts +++ b/scripts/types/generated/swapper-base/SwapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/swapper-base/SwapperBase.types.ts b/scripts/types/generated/swapper-base/SwapperBase.types.ts index b6863f76b..5b0f1a366 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.types.ts +++ b/scripts/types/generated/swapper-base/SwapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/swapper-base/bundle.ts b/scripts/types/generated/swapper-base/bundle.ts index efff30782..4835afc43 100644 --- a/scripts/types/generated/swapper-base/bundle.ts +++ b/scripts/types/generated/swapper-base/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.16.5. + * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/yarn.lock b/scripts/yarn.lock index fa3170ec9..e9350eade 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -27,6 +27,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.3.tgz#707b939793f867f5a73b2666e6d9a3396eb03151" integrity sha512-prBHMK4JYYK+wDjJF1q99KK4JLL+egWS4nmNqdlMUgCExMZ+iZW0hGhyC3VEbsPjvaN0TBhW//VIFwBrk8sEiw== +"@babel/compat-data@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.4.tgz#95c86de137bf0317f3a570e1b6e996b427299747" + integrity sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw== + "@babel/core@7.18.10": version "7.18.10" resolved "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz" @@ -281,6 +286,11 @@ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz" integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + "@babel/helper-validator-identifier@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz" @@ -454,6 +464,17 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.18.8" +"@babel/plugin-proposal-object-rest-spread@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz#a8fc86e8180ff57290c91a75d83fe658189b642d" + integrity sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q== + dependencies: + "@babel/compat-data" "^7.19.4" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-proposal-optional-catch-binding@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz" @@ -667,6 +688,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-block-scoping@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.19.4.tgz#315d70f68ce64426db379a3d830e7ac30be02e9b" + integrity sha512-934S2VLLlt2hRJwPf4MczaOr4hYF0z+VKPwqTNxyKX7NthTiPfhuKFWQZHXRM0vh/wo/VyXB3s4bZUNA08l+tQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-transform-classes@^7.18.9", "@babel/plugin-transform-classes@^7.19.0": version "7.19.0" resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz" @@ -689,13 +717,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-destructuring@^7.18.13", "@babel/plugin-transform-destructuring@^7.18.9": +"@babel/plugin-transform-destructuring@^7.18.9": version "7.18.13" resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz" integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow== dependencies: "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-destructuring@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.19.4.tgz#46890722687b9b89e1369ad0bd8dc6c5a3b4319d" + integrity sha512-t0j0Hgidqf0aM86dF8U+vXYReUgJnlv4bZLsyoPnwZNrGY+7/38o8YjaELrvHeVfTZao15kjR0PVv0nju2iduA== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.18.6" resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz" @@ -1000,12 +1035,12 @@ core-js-compat "^3.22.1" semver "^6.3.0" -"@babel/preset-env@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.3.tgz#52cd19abaecb3f176a4ff9cc5e15b7bf06bec754" - integrity sha512-ziye1OTc9dGFOAXSWKUqQblYHNlBOaDl8wzqf2iKXJAltYiR3hKHUKmkt+S9PppW7RQpq4fFCrwwpIDj/f5P4w== +"@babel/preset-env@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.4.tgz#4c91ce2e1f994f717efb4237891c3ad2d808c94b" + integrity sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg== dependencies: - "@babel/compat-data" "^7.19.3" + "@babel/compat-data" "^7.19.4" "@babel/helper-compilation-targets" "^7.19.3" "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-validator-option" "^7.18.6" @@ -1020,7 +1055,7 @@ "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" "@babel/plugin-proposal-numeric-separator" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.18.9" + "@babel/plugin-proposal-object-rest-spread" "^7.19.4" "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" "@babel/plugin-proposal-optional-chaining" "^7.18.9" "@babel/plugin-proposal-private-methods" "^7.18.6" @@ -1044,10 +1079,10 @@ "@babel/plugin-transform-arrow-functions" "^7.18.6" "@babel/plugin-transform-async-to-generator" "^7.18.6" "@babel/plugin-transform-block-scoped-functions" "^7.18.6" - "@babel/plugin-transform-block-scoping" "^7.18.9" + "@babel/plugin-transform-block-scoping" "^7.19.4" "@babel/plugin-transform-classes" "^7.19.0" "@babel/plugin-transform-computed-properties" "^7.18.9" - "@babel/plugin-transform-destructuring" "^7.18.13" + "@babel/plugin-transform-destructuring" "^7.19.4" "@babel/plugin-transform-dotall-regex" "^7.18.6" "@babel/plugin-transform-duplicate-keys" "^7.18.9" "@babel/plugin-transform-exponentiation-operator" "^7.18.6" @@ -1074,7 +1109,7 @@ "@babel/plugin-transform-unicode-escapes" "^7.18.10" "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.19.3" + "@babel/types" "^7.19.4" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" babel-plugin-polyfill-regenerator "^0.4.1" @@ -1167,12 +1202,12 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@babel/types@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.3.tgz#fc420e6bbe54880bce6779ffaf315f5e43ec9624" - integrity sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw== +"@babel/types@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" + integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== dependencies: - "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-string-parser" "^7.19.4" "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" @@ -1189,143 +1224,143 @@ "@noble/hashes" "^1.0.0" protobufjs "^6.8.8" -"@cosmjs/amino@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.29.0.tgz#35873a580a6102e48415ed2b5b97477f146fb50d" - integrity sha512-/ZUVx6nRN5YE36H3SDq9+i8g2nZ8DJQnN9fVRC8rSHQKauNkoEuK4NxTNcQ2o2EBLUT0kyYAFY2550HVsPMrgw== - dependencies: - "@cosmjs/crypto" "^0.29.0" - "@cosmjs/encoding" "^0.29.0" - "@cosmjs/math" "^0.29.0" - "@cosmjs/utils" "^0.29.0" - -"@cosmjs/cosmwasm-stargate@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.29.0.tgz#dea1c16fe80daf14072c3796574fe8cb34a3729b" - integrity sha512-KoNc0XpK6Gh4CITpyMXIuhIdZu59lF3wO1pHabeEZ0v7w3U0tFdCbDppe2RufCkERDZZCGFxnoRmr0KL2wK6Tw== - dependencies: - "@cosmjs/amino" "^0.29.0" - "@cosmjs/crypto" "^0.29.0" - "@cosmjs/encoding" "^0.29.0" - "@cosmjs/math" "^0.29.0" - "@cosmjs/proto-signing" "^0.29.0" - "@cosmjs/stargate" "^0.29.0" - "@cosmjs/tendermint-rpc" "^0.29.0" - "@cosmjs/utils" "^0.29.0" +"@cosmjs/amino@^0.29.1": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.29.1.tgz#cdd77fbca4cd4a4d99540f885ceb0666f7afd348" + integrity sha512-Obw6qMLSUg2YmHOe9cHF9LofeLoz52I+1OUuVg7WdVDWK3kvWYA6oME/21h5XHb18pU5f0Nkcgz1SXTG8/stTQ== + dependencies: + "@cosmjs/crypto" "^0.29.1" + "@cosmjs/encoding" "^0.29.1" + "@cosmjs/math" "^0.29.1" + "@cosmjs/utils" "^0.29.1" + +"@cosmjs/cosmwasm-stargate@^0.29.1": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.29.1.tgz#dbddffce2099e5cd78bb1dc956033d651a18a6f3" + integrity sha512-sUgxaM30IB3OcaW0PPO6JLvU8nSu8Pt5N5d743XKpdU32ocyyhtuUZmuhMaaFZBFpx7le9FjVViW+MfN2II69g== + dependencies: + "@cosmjs/amino" "^0.29.1" + "@cosmjs/crypto" "^0.29.1" + "@cosmjs/encoding" "^0.29.1" + "@cosmjs/math" "^0.29.1" + "@cosmjs/proto-signing" "^0.29.1" + "@cosmjs/stargate" "^0.29.1" + "@cosmjs/tendermint-rpc" "^0.29.1" + "@cosmjs/utils" "^0.29.1" cosmjs-types "^0.5.0" long "^4.0.0" pako "^2.0.2" -"@cosmjs/crypto@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.29.0.tgz#c914424a8b538f6624e505bc2015a71e3977c2fb" - integrity sha512-MPJoebRGh7AcZgbfR25ci7iV+XzJiKwVq4wL8n6M5P2QdrIv7DqqniyFXcBbn9dQjMLMHnOSgT9LRv+VXzUVCA== +"@cosmjs/crypto@^0.29.1": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.29.1.tgz#1c5b2fe6de08e43b1ca65c3b33e7a39c9e6f6cb1" + integrity sha512-H64KHUbp1nndPAJ3DoJs8OSOcpFjn2o/ptGgDaa1xhX4OiT7CF8IFoKyzzbPMLS/l/jWWkByc3uMn9R/LwW/Mg== dependencies: - "@cosmjs/encoding" "^0.29.0" - "@cosmjs/math" "^0.29.0" - "@cosmjs/utils" "^0.29.0" + "@cosmjs/encoding" "^0.29.1" + "@cosmjs/math" "^0.29.1" + "@cosmjs/utils" "^0.29.1" "@noble/hashes" "^1" bn.js "^5.2.0" elliptic "^6.5.3" libsodium-wrappers "^0.7.6" -"@cosmjs/encoding@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.29.0.tgz#75b1b41a2f31f71fcb0982cd1b210d6410739fd0" - integrity sha512-6HDBtid/YLbyXapY6PdMMIigAtGKyD1w0dUCLU1dOIkPf1q3y43kqoA7WnLkRw0g0/lZY1VGM2fX+2RWU0wxYg== +"@cosmjs/encoding@^0.29.1": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.29.1.tgz#7fc49707e71fa4ee0ec398f9f127511839045ec5" + integrity sha512-spojVtRoPBxoOZ3n7ZqNSOJceTcnIKLY1yxMC6UL5a0HEPgGsJHx6LIJfcYnT4Gpp0kE1mzqRhVX9CT7Fbdw/g== dependencies: base64-js "^1.3.0" bech32 "^1.1.4" readonly-date "^1.0.0" -"@cosmjs/json-rpc@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.29.0.tgz#481f282bcb3457c71f393342691e957a4fa56535" - integrity sha512-noCt91X+dSYjW1BYbp5jFaYaA/PWIQFXOgl4ZDW0ecGOAj8xh6/D/Vd8bDO97CQgJ1KVw0pyAqVhmrBOBUo1sA== +"@cosmjs/json-rpc@^0.29.1": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.29.1.tgz#56a1e542e5ec4594adde2349cbd78c9bbead1976" + integrity sha512-HG39w39xXExDSyIUg6JXeza82wSwcsWvkuu3WRUqTLZ73BlbEJBMwAqoJbstq7UklYNb3rJ+/qUvYYWiJcAryA== dependencies: - "@cosmjs/stream" "^0.29.0" + "@cosmjs/stream" "^0.29.1" xstream "^11.14.0" -"@cosmjs/math@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.29.0.tgz#2c34f96d94055fe82ca310bec7b2d8a9f1c507cb" - integrity sha512-ufRRmyDQtJUrH8r1V4N7Q6rTOk9ZX7XIXjJto7cfXP8kcxm7IJXKYk+r0EfDnNHFkxTidYvW/1YXeeNoy8xZYw== +"@cosmjs/math@^0.29.1": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.29.1.tgz#d71a6a2e95ac43380cd70c85c7656a6e8204e0e2" + integrity sha512-IBZHjKXReFIJY902wMFYB5mIQdppQdETbfQFrtXWNvXVOyavgOm60uLUZ2s3n9oAvZNpCiUFLnUulPT7M2LGKw== dependencies: bn.js "^5.2.0" -"@cosmjs/proto-signing@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.0.tgz#4d9c10fc3a5c64b454bd2d9b407861fcffdfbbe0" - integrity sha512-zAdgDz5vRGAfJ5yyKYuTL7qg5UNUT7v4iV1/ZP8ZQn2fLh9QVxViAIovF4r/Y3EEI4JS5uYj/f8UeHMHQSu8hw== +"@cosmjs/proto-signing@^0.29.1": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.1.tgz#925b074de560bd2ab536f4b33599efa4d5415891" + integrity sha512-k3fGmfA0IRBZrctgHHAfixZ03tXY4zKkxOGgB38pdTE3BKwr1rj1CCwrQALdqO5Xkdb2McIRisc5/eVmhJfcrA== dependencies: - "@cosmjs/amino" "^0.29.0" - "@cosmjs/crypto" "^0.29.0" - "@cosmjs/encoding" "^0.29.0" - "@cosmjs/math" "^0.29.0" - "@cosmjs/utils" "^0.29.0" + "@cosmjs/amino" "^0.29.1" + "@cosmjs/crypto" "^0.29.1" + "@cosmjs/encoding" "^0.29.1" + "@cosmjs/math" "^0.29.1" + "@cosmjs/utils" "^0.29.1" cosmjs-types "^0.5.0" long "^4.0.0" -"@cosmjs/socket@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.29.0.tgz#6f8f56799e69ead02f9ffe8925c782804635ac89" - integrity sha512-y7cOBp6YJ2Sn/DZne1eiJ6PVkgZlAi48d0Bz6hVuZ6CliutG0BzM/F3bSLxdw8m2fXNU+lYsi4uLPd0epf5Hig== +"@cosmjs/socket@^0.29.1": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.29.1.tgz#7d83e87ca5157d07290032d51c1f75ba6be252fc" + integrity sha512-seHWBHRNdnl+neu0mY2JTRS/KQsStJpLgL1DRjG8uPiC3K1ZvBgXNX7bhdht81C//KDTbrjdQjWqX9vR6E6QAw== dependencies: - "@cosmjs/stream" "^0.29.0" + "@cosmjs/stream" "^0.29.1" isomorphic-ws "^4.0.1" ws "^7" xstream "^11.14.0" -"@cosmjs/stargate@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.29.0.tgz#55263ed9d414f2c3073a451527576e4c3d6f04a6" - integrity sha512-BsV3iA3vMclMm/B1LYO0djBYCALr/UIvL6u9HGvM7QvpdtpQiAvskuS4PieVO/gtF9iCCBJLPqa0scwFIgvDyg== +"@cosmjs/stargate@^0.29.1": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.29.1.tgz#85b739516318102c7907209f9273bd32faccf39c" + integrity sha512-6LYblr8XjIPat4HbW2k0bnPefBHM/0JJUvk8c0m6Nv/vQ9QcG8EIGkB/qndsx+gmPGPNpQ2ed/IpL3670KScmg== dependencies: "@confio/ics23" "^0.6.8" - "@cosmjs/amino" "^0.29.0" - "@cosmjs/encoding" "^0.29.0" - "@cosmjs/math" "^0.29.0" - "@cosmjs/proto-signing" "^0.29.0" - "@cosmjs/stream" "^0.29.0" - "@cosmjs/tendermint-rpc" "^0.29.0" - "@cosmjs/utils" "^0.29.0" + "@cosmjs/amino" "^0.29.1" + "@cosmjs/encoding" "^0.29.1" + "@cosmjs/math" "^0.29.1" + "@cosmjs/proto-signing" "^0.29.1" + "@cosmjs/stream" "^0.29.1" + "@cosmjs/tendermint-rpc" "^0.29.1" + "@cosmjs/utils" "^0.29.1" cosmjs-types "^0.5.0" long "^4.0.0" protobufjs "~6.11.3" xstream "^11.14.0" -"@cosmjs/stream@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.29.0.tgz#df2d7ea23293170bc192e91c0fa3e9f8d993b7cc" - integrity sha512-KAJ9sNoXhF19wtkoJf3O2y4YXfklDZgmXhDotgAejLrw2ixoVfTodMHvnl6tpw3ZnmXKibTfUaNXWZD++sG6uQ== +"@cosmjs/stream@^0.29.1": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.29.1.tgz#128a8ced26b077befdb8e305a7994ac2324ff09b" + integrity sha512-QMvMzF39jDaBPavPRLjY+aiRiYDUZ/Lcd9WemvnlAs1nG4inILLQxfwnuCjY7q9BR6H4NHDr0oz4aQQFI+5xXg== dependencies: xstream "^11.14.0" -"@cosmjs/tendermint-rpc@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.0.tgz#db71e743d2ee8dde706c09bc92ac47cc6197f672" - integrity sha512-G+42oGh+tw8/KV0gLAGzNCTe/6mkf7VUE5noSTbsxbeliFR7Lt4i6H2aqvWzmlZFeRxunR7AsQr4wakvlVNWyg== - dependencies: - "@cosmjs/crypto" "^0.29.0" - "@cosmjs/encoding" "^0.29.0" - "@cosmjs/json-rpc" "^0.29.0" - "@cosmjs/math" "^0.29.0" - "@cosmjs/socket" "^0.29.0" - "@cosmjs/stream" "^0.29.0" - "@cosmjs/utils" "^0.29.0" +"@cosmjs/tendermint-rpc@^0.29.1": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.1.tgz#d548c78869b92da733301111553b82f778b8a184" + integrity sha512-BqISUkgx/1WzagRGV1wASp9/0oVZew1kS8CUBUoMG9j+uu7iyWa5GmvW3Tumd/79mfZ4sqeQdrOB6y4yekS1qQ== + dependencies: + "@cosmjs/crypto" "^0.29.1" + "@cosmjs/encoding" "^0.29.1" + "@cosmjs/json-rpc" "^0.29.1" + "@cosmjs/math" "^0.29.1" + "@cosmjs/socket" "^0.29.1" + "@cosmjs/stream" "^0.29.1" + "@cosmjs/utils" "^0.29.1" axios "^0.21.2" readonly-date "^1.0.0" xstream "^11.14.0" -"@cosmjs/utils@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.0.tgz#0a61e6d608e9f6f89a278cc71f4e7cee01199657" - integrity sha512-NiJk3ISX+FU1cQcTTgmJcY84A8mV/p8L5CRewp/2jc/lUmo8j9lMGbX17U7NxVQ9RX5RmrwgdjYnBASzhRCVmA== +"@cosmjs/utils@^0.29.1": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.1.tgz#f953bb7c213f257db4766bf30474b741348c9189" + integrity sha512-Nq0uJqzV7VoviJShtasUm41bDe7BArlkPaToEapGNPhR43pwKMowAMuZdJsKeTzEz9BtUAB/nCG+GO6/yCV1hQ== -"@cosmwasm/ts-codegen@^0.16.5": - version "0.16.5" - resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.16.5.tgz#98618126000b28a4039572f501e61805ff5a5620" - integrity sha512-rGqxMsftRESspiT4CfeYP2hxFeRdpN5YPw3FezNbRT0pqO7yZVvFXl5cqGidOHbzlXhreo7/RAABRfGOW7arng== +"@cosmwasm/ts-codegen@^0.19.0": + version "0.19.0" + resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.19.0.tgz#30663019ba283dc50778209c984f8bd22ead47af" + integrity sha512-UTQnrxuxTVhaEGLB5gLCV5ppyF0WG6nGqY/eNHeVv3WeoB3CobeGFdsONbXz+whIX6+xJFQJ9YE+rmGVWVc0lQ== dependencies: "@babel/core" "7.18.10" "@babel/generator" "7.18.12" @@ -1353,12 +1388,12 @@ parse-package-name "1.0.0" rimraf "3.0.2" shelljs "0.8.5" - wasm-ast-types "^0.11.3" + wasm-ast-types "^0.13.0" -"@eslint/eslintrc@^1.3.2": - version "1.3.2" - resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz" - integrity sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ== +"@eslint/eslintrc@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.3.tgz#2b044ab39fdfa75b4688184f9e573ce3c5b0ff95" + integrity sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -1379,11 +1414,6 @@ debug "^4.1.1" minimatch "^3.0.4" -"@humanwhocodes/gitignore-to-minimatch@^1.0.2": - version "1.0.2" - resolved "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz" - integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA== - "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" @@ -1410,28 +1440,28 @@ resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.0.3": - version "29.0.3" - resolved "https://registry.npmjs.org/@jest/console/-/console-29.0.3.tgz" - integrity sha512-cGg0r+klVHSYnfE977S9wmpuQ9L+iYuYgL+5bPXiUlUynLLYunRxswEmhBzvrSKGof5AKiHuTTmUKAqRcDY9dg== +"@jest/console@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.1.2.tgz#0ae975a70004696f8320490fcaa1a4152f7b62e4" + integrity sha512-ujEBCcYs82BTmRxqfHMQggSlkUZP63AE5YEaTPj7eFyJOzukkTorstOUC7L6nE3w5SYadGVAnTsQ/ZjTGL0qYQ== dependencies: - "@jest/types" "^29.0.3" + "@jest/types" "^29.1.2" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.0.3" - jest-util "^29.0.3" + jest-message-util "^29.1.2" + jest-util "^29.1.2" slash "^3.0.0" -"@jest/core@^29.0.3": - version "29.0.3" - resolved "https://registry.npmjs.org/@jest/core/-/core-29.0.3.tgz" - integrity sha512-1d0hLbOrM1qQE3eP3DtakeMbKTcXiXP3afWxqz103xPyddS2NhnNghS7MaXx1dcDt4/6p4nlhmeILo2ofgi8cQ== +"@jest/core@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.1.2.tgz#e5ce7a71e7da45156a96fb5eeed11d18b67bd112" + integrity sha512-sCO2Va1gikvQU2ynDN8V4+6wB7iVrD2CvT0zaRst4rglf56yLly0NQ9nuRRAWFeimRf+tCdFsb1Vk1N9LrrMPA== dependencies: - "@jest/console" "^29.0.3" - "@jest/reporters" "^29.0.3" - "@jest/test-result" "^29.0.3" - "@jest/transform" "^29.0.3" - "@jest/types" "^29.0.3" + "@jest/console" "^29.1.2" + "@jest/reporters" "^29.1.2" + "@jest/test-result" "^29.1.2" + "@jest/transform" "^29.1.2" + "@jest/types" "^29.1.2" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" @@ -1439,32 +1469,32 @@ exit "^0.1.2" graceful-fs "^4.2.9" jest-changed-files "^29.0.0" - jest-config "^29.0.3" - jest-haste-map "^29.0.3" - jest-message-util "^29.0.3" + jest-config "^29.1.2" + jest-haste-map "^29.1.2" + jest-message-util "^29.1.2" jest-regex-util "^29.0.0" - jest-resolve "^29.0.3" - jest-resolve-dependencies "^29.0.3" - jest-runner "^29.0.3" - jest-runtime "^29.0.3" - jest-snapshot "^29.0.3" - jest-util "^29.0.3" - jest-validate "^29.0.3" - jest-watcher "^29.0.3" + jest-resolve "^29.1.2" + jest-resolve-dependencies "^29.1.2" + jest-runner "^29.1.2" + jest-runtime "^29.1.2" + jest-snapshot "^29.1.2" + jest-util "^29.1.2" + jest-validate "^29.1.2" + jest-watcher "^29.1.2" micromatch "^4.0.4" - pretty-format "^29.0.3" + pretty-format "^29.1.2" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.0.3": - version "29.0.3" - resolved "https://registry.npmjs.org/@jest/environment/-/environment-29.0.3.tgz" - integrity sha512-iKl272NKxYNQNqXMQandAIwjhQaGw5uJfGXduu8dS9llHi8jV2ChWrtOAVPnMbaaoDhnI3wgUGNDvZgHeEJQCA== +"@jest/environment@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.1.2.tgz#bb51a43fce9f960ba9a48f0b5b556f30618ebc0a" + integrity sha512-rG7xZ2UeOfvOVzoLIJ0ZmvPl4tBEQ2n73CZJSlzUjPw4or1oSWC0s0Rk0ZX+pIBJ04aVr6hLWFn1DFtrnf8MhQ== dependencies: - "@jest/fake-timers" "^29.0.3" - "@jest/types" "^29.0.3" + "@jest/fake-timers" "^29.1.2" + "@jest/types" "^29.1.2" "@types/node" "*" - jest-mock "^29.0.3" + jest-mock "^29.1.2" "@jest/expect-utils@^29.0.3": version "29.0.3" @@ -1473,46 +1503,53 @@ dependencies: jest-get-type "^29.0.0" -"@jest/expect@^29.0.3": - version "29.0.3" - resolved "https://registry.npmjs.org/@jest/expect/-/expect-29.0.3.tgz" - integrity sha512-6W7K+fsI23FQ01H/BWccPyDZFrnU9QlzDcKOjrNVU5L8yUORFAJJIpmyxWPW70+X624KUNqzZwPThPMX28aXEQ== +"@jest/expect-utils@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.1.2.tgz#66dbb514d38f7d21456bc774419c9ae5cca3f88d" + integrity sha512-4a48bhKfGj/KAH39u0ppzNTABXQ8QPccWAFUFobWBaEMSMp+sB31Z2fK/l47c4a/Mu1po2ffmfAIPxXbVTXdtg== dependencies: - expect "^29.0.3" - jest-snapshot "^29.0.3" + jest-get-type "^29.0.0" -"@jest/fake-timers@^29.0.3": - version "29.0.3" - resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.3.tgz" - integrity sha512-tmbUIo03x0TdtcZCESQ0oQSakPCpo7+s6+9mU19dd71MptkP4zCwoeZqna23//pgbhtT1Wq02VmA9Z9cNtvtCQ== +"@jest/expect@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.1.2.tgz#334a86395f621f1ab63ad95b06a588b9114d7b7a" + integrity sha512-FXw/UmaZsyfRyvZw3M6POgSNqwmuOXJuzdNiMWW9LCYo0GRoRDhg+R5iq5higmRTHQY7hx32+j7WHwinRmoILQ== dependencies: - "@jest/types" "^29.0.3" + expect "^29.1.2" + jest-snapshot "^29.1.2" + +"@jest/fake-timers@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.1.2.tgz#f157cdf23b4da48ce46cb00fea28ed1b57fc271a" + integrity sha512-GppaEqS+QQYegedxVMpCe2xCXxxeYwQ7RsNx55zc8f+1q1qevkZGKequfTASI7ejmg9WwI+SJCrHe9X11bLL9Q== + dependencies: + "@jest/types" "^29.1.2" "@sinonjs/fake-timers" "^9.1.2" "@types/node" "*" - jest-message-util "^29.0.3" - jest-mock "^29.0.3" - jest-util "^29.0.3" + jest-message-util "^29.1.2" + jest-mock "^29.1.2" + jest-util "^29.1.2" -"@jest/globals@^29.0.3": - version "29.0.3" - resolved "https://registry.npmjs.org/@jest/globals/-/globals-29.0.3.tgz" - integrity sha512-YqGHT65rFY2siPIHHFjuCGUsbzRjdqkwbat+Of6DmYRg5shIXXrLdZoVE/+TJ9O1dsKsFmYhU58JvIbZRU1Z9w== +"@jest/globals@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.1.2.tgz#826ede84bc280ae7f789cb72d325c48cd048b9d3" + integrity sha512-uMgfERpJYoQmykAd0ffyMq8wignN4SvLUG6orJQRe9WAlTRc9cdpCaE/29qurXixYJVZWUqIBXhSk8v5xN1V9g== dependencies: - "@jest/environment" "^29.0.3" - "@jest/expect" "^29.0.3" - "@jest/types" "^29.0.3" - jest-mock "^29.0.3" + "@jest/environment" "^29.1.2" + "@jest/expect" "^29.1.2" + "@jest/types" "^29.1.2" + jest-mock "^29.1.2" -"@jest/reporters@^29.0.3": - version "29.0.3" - resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-29.0.3.tgz" - integrity sha512-3+QU3d4aiyOWfmk1obDerie4XNCaD5Xo1IlKNde2yGEi02WQD+ZQD0i5Hgqm1e73sMV7kw6pMlCnprtEwEVwxw== +"@jest/reporters@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.1.2.tgz#5520898ed0a4ecf69d8b671e1dc8465d0acdfa6e" + integrity sha512-X4fiwwyxy9mnfpxL0g9DD0KcTmEIqP0jUdnc2cfa9riHy+I6Gwwp5vOZiwyg0vZxfSDxrOlK9S4+340W4d+DAA== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.0.3" - "@jest/test-result" "^29.0.3" - "@jest/transform" "^29.0.3" - "@jest/types" "^29.0.3" + "@jest/console" "^29.1.2" + "@jest/test-result" "^29.1.2" + "@jest/transform" "^29.1.2" + "@jest/types" "^29.1.2" "@jridgewell/trace-mapping" "^0.3.15" "@types/node" "*" chalk "^4.0.0" @@ -1525,9 +1562,9 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.0.3" - jest-util "^29.0.3" - jest-worker "^29.0.3" + jest-message-util "^29.1.2" + jest-util "^29.1.2" + jest-worker "^29.1.2" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" @@ -1557,24 +1594,24 @@ callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.0.3": - version "29.0.3" - resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-29.0.3.tgz" - integrity sha512-vViVnQjCgTmbhDKEonKJPtcFe9G/CJO4/Np4XwYJah+lF2oI7KKeRp8t1dFvv44wN2NdbDb/qC6pi++Vpp0Dlg== +"@jest/test-result@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.1.2.tgz#6a8d006eb2b31ce0287d1fc10d12b8ff8504f3c8" + integrity sha512-jjYYjjumCJjH9hHCoMhA8PCl1OxNeGgAoZ7yuGYILRJX9NjgzTN0pCT5qAoYR4jfOP8htIByvAlz9vfNSSBoVg== dependencies: - "@jest/console" "^29.0.3" - "@jest/types" "^29.0.3" + "@jest/console" "^29.1.2" + "@jest/types" "^29.1.2" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.0.3": - version "29.0.3" - resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.0.3.tgz" - integrity sha512-Hf4+xYSWZdxTNnhDykr8JBs0yBN/nxOXyUQWfotBUqqy0LF9vzcFB0jm/EDNZCx587znLWTIgxcokW7WeZMobQ== +"@jest/test-sequencer@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.1.2.tgz#10bfd89c08bfdba382eb05cc79c1d23a01238a93" + integrity sha512-fU6dsUqqm8sA+cd85BmeF7Gu9DsXVWFdGn9taxM6xN1cKdcP/ivSgXh5QucFRFz1oZxKv3/9DYYbq0ULly3P/Q== dependencies: - "@jest/test-result" "^29.0.3" + "@jest/test-result" "^29.1.2" graceful-fs "^4.2.9" - jest-haste-map "^29.0.3" + jest-haste-map "^29.1.2" slash "^3.0.0" "@jest/transform@28.1.3": @@ -1598,22 +1635,22 @@ slash "^3.0.0" write-file-atomic "^4.0.1" -"@jest/transform@^29.0.3": - version "29.0.3" - resolved "https://registry.npmjs.org/@jest/transform/-/transform-29.0.3.tgz" - integrity sha512-C5ihFTRYaGDbi/xbRQRdbo5ddGtI4VSpmL6AIcZxdhwLbXMa7PcXxxqyI91vGOFHnn5aVM3WYnYKCHEqmLVGzg== +"@jest/transform@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.1.2.tgz#20f814696e04f090421f6d505c14bbfe0157062a" + integrity sha512-2uaUuVHTitmkx1tHF+eBjb4p7UuzBG7SXIaA/hNIkaMP6K+gXYGxP38ZcrofzqN0HeZ7A90oqsOa97WU7WZkSw== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.0.3" + "@jest/types" "^29.1.2" "@jridgewell/trace-mapping" "^0.3.15" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.0.3" + jest-haste-map "^29.1.2" jest-regex-util "^29.0.0" - jest-util "^29.0.3" + jest-util "^29.1.2" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" @@ -1643,6 +1680,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.1.2.tgz#7442d32b16bcd7592d9614173078b8c334ec730a" + integrity sha512-DcXGtoTykQB5jiwCmVr8H4vdg2OJhQex3qPkG+ISyDO7xQXbt/4R6dowcRyPemRnkH7JoHvZuxPBdlq+9JxFCg== + dependencies: + "@jest/schemas" "^29.0.0" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz" @@ -1882,10 +1931,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.0.3": - version "29.0.3" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.0.3.tgz#b61a5ed100850686b8d3c5e28e3a1926b2001b59" - integrity sha512-F6ukyCTwbfsEX5F2YmVYmM5TcTHy1q9P5rWlRbrk56KyMh3v9xRGUO3aa8+SkvMi0SHXtASJv1283enXimC0Og== +"@types/jest@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.1.2.tgz#7ad8077043ab5f6c108c8111bcc1d224e5600a87" + integrity sha512-y+nlX0h87U0R+wsGn6EBuoRWYyv3KFtwRNP3QWp9+k2tJ2/bqcGS3UxD7jgT+tiwJWWq3UsyV4Y+T6rsMT4XMg== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -1937,84 +1986,85 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz#9f05d42fa8fb9f62304cc2f5c2805e03c01c2620" - integrity sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ== +"@typescript-eslint/eslint-plugin@^5.40.0": + version "5.40.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.40.0.tgz#0159bb71410eec563968288a17bd4478cdb685bd" + integrity sha512-FIBZgS3DVJgqPwJzvZTuH4HNsZhHMa9SjxTKAZTlMsPw/UzpEjcf9f4dfgDJEHjK+HboUJo123Eshl6niwEm/Q== dependencies: - "@typescript-eslint/scope-manager" "5.38.1" - "@typescript-eslint/type-utils" "5.38.1" - "@typescript-eslint/utils" "5.38.1" + "@typescript-eslint/scope-manager" "5.40.0" + "@typescript-eslint/type-utils" "5.40.0" + "@typescript-eslint/utils" "5.40.0" debug "^4.3.4" ignore "^5.2.0" regexpp "^3.2.0" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.38.1.tgz#c577f429f2c32071b92dff4af4f5fbbbd2414bd0" - integrity sha512-LDqxZBVFFQnQRz9rUZJhLmox+Ep5kdUmLatLQnCRR6523YV+XhRjfYzStQ4MheFA8kMAfUlclHSbu+RKdRwQKw== +"@typescript-eslint/parser@^5.40.0": + version "5.40.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.40.0.tgz#432bddc1fe9154945660f67c1ba6d44de5014840" + integrity sha512-Ah5gqyX2ySkiuYeOIDg7ap51/b63QgWZA7w6AHtFrag7aH0lRQPbLzUjk0c9o5/KZ6JRkTTDKShL4AUrQa6/hw== dependencies: - "@typescript-eslint/scope-manager" "5.38.1" - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/typescript-estree" "5.38.1" + "@typescript-eslint/scope-manager" "5.40.0" + "@typescript-eslint/types" "5.40.0" + "@typescript-eslint/typescript-estree" "5.40.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz#f87b289ef8819b47189351814ad183e8801d5764" - integrity sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ== +"@typescript-eslint/scope-manager@5.40.0": + version "5.40.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.40.0.tgz#d6ea782c8e3a2371ba3ea31458dcbdc934668fc4" + integrity sha512-d3nPmjUeZtEWRvyReMI4I1MwPGC63E8pDoHy0BnrYjnJgilBD3hv7XOiETKLY/zTwI7kCnBDf2vWTRUVpYw0Uw== dependencies: - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/visitor-keys" "5.38.1" + "@typescript-eslint/types" "5.40.0" + "@typescript-eslint/visitor-keys" "5.40.0" -"@typescript-eslint/type-utils@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.38.1.tgz#7f038fcfcc4ade4ea76c7c69b2aa25e6b261f4c1" - integrity sha512-UU3j43TM66gYtzo15ivK2ZFoDFKKP0k03MItzLdq0zV92CeGCXRfXlfQX5ILdd4/DSpHkSjIgLLLh1NtkOJOAw== +"@typescript-eslint/type-utils@5.40.0": + version "5.40.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.40.0.tgz#4964099d0158355e72d67a370249d7fc03331126" + integrity sha512-nfuSdKEZY2TpnPz5covjJqav+g5qeBqwSHKBvz7Vm1SAfy93SwKk/JeSTymruDGItTwNijSsno5LhOHRS1pcfw== dependencies: - "@typescript-eslint/typescript-estree" "5.38.1" - "@typescript-eslint/utils" "5.38.1" + "@typescript-eslint/typescript-estree" "5.40.0" + "@typescript-eslint/utils" "5.40.0" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.38.1.tgz#74f9d6dcb8dc7c58c51e9fbc6653ded39e2e225c" - integrity sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg== +"@typescript-eslint/types@5.40.0": + version "5.40.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.40.0.tgz#8de07e118a10b8f63c99e174a3860f75608c822e" + integrity sha512-V1KdQRTXsYpf1Y1fXCeZ+uhjW48Niiw0VGt4V8yzuaDTU8Z1Xl7yQDyQNqyAFcVhpYXIVCEuxSIWTsLDpHgTbw== -"@typescript-eslint/typescript-estree@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz#657d858d5d6087f96b638ee383ee1cff52605a1e" - integrity sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g== +"@typescript-eslint/typescript-estree@5.40.0": + version "5.40.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.40.0.tgz#e305e6a5d65226efa5471ee0f12e0ffaab6d3075" + integrity sha512-b0GYlDj8TLTOqwX7EGbw2gL5EXS2CPEWhF9nGJiGmEcmlpNBjyHsTwbqpyIEPVpl6br4UcBOYlcI2FJVtJkYhg== dependencies: - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/visitor-keys" "5.38.1" + "@typescript-eslint/types" "5.40.0" + "@typescript-eslint/visitor-keys" "5.40.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.38.1.tgz#e3ac37d7b33d1362bb5adf4acdbe00372fb813ef" - integrity sha512-oIuUiVxPBsndrN81oP8tXnFa/+EcZ03qLqPDfSZ5xIJVm7A9V0rlkQwwBOAGtrdN70ZKDlKv+l1BeT4eSFxwXA== +"@typescript-eslint/utils@5.40.0": + version "5.40.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.40.0.tgz#647f56a875fd09d33c6abd70913c3dd50759b772" + integrity sha512-MO0y3T5BQ5+tkkuYZJBjePewsY+cQnfkYeRqS6tPh28niiIwPnQ1t59CSRcs1ZwJJNOdWw7rv9pF8aP58IMihA== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.38.1" - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/typescript-estree" "5.38.1" + "@typescript-eslint/scope-manager" "5.40.0" + "@typescript-eslint/types" "5.40.0" + "@typescript-eslint/typescript-estree" "5.40.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" + semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz#508071bfc6b96d194c0afe6a65ad47029059edbc" - integrity sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA== +"@typescript-eslint/visitor-keys@5.40.0": + version "5.40.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.40.0.tgz#dd2d38097f68e0d2e1e06cb9f73c0173aca54b68" + integrity sha512-ijJ+6yig+x9XplEpG2K6FUdJeQGGj/15U3S56W9IqXKJqleuD7zJ2AX/miLezwxpd7ZxDAqO87zWufKg+RPZyQ== dependencies: - "@typescript-eslint/types" "5.38.1" + "@typescript-eslint/types" "5.40.0" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -2142,12 +2192,12 @@ axios@^0.21.2: dependencies: follow-redirects "^1.14.0" -babel-jest@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.0.3.tgz" - integrity sha512-ApPyHSOhS/sVzwUOQIWJmdvDhBsMG01HX9z7ogtkp1TToHGGUWFlnXJUIzCgKPSfiYLn3ibipCYzsKSURHEwLg== +babel-jest@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.1.2.tgz#540d3241925c55240fb0c742e3ffc5f33a501978" + integrity sha512-IuG+F3HTHryJb7gacC7SQ59A9kO56BctUsT67uJHp1mMCHUOMXpDwOHWGifWqdWVknN2WNkCVQELPjXx0aLJ9Q== dependencies: - "@jest/transform" "^29.0.3" + "@jest/transform" "^29.1.2" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" babel-preset-jest "^29.0.2" @@ -2730,14 +2780,13 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.24.0: - version "8.24.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.24.0.tgz#489516c927a5da11b3979dbfb2679394523383c8" - integrity sha512-dWFaPhGhTAiPcCgm3f6LI2MBWbogMnTJzFBbhXVRQDJPkr9pGZvVjlVfXd+vyDcWPA2Ic9L2AXPIQM0+vk/cSQ== +eslint@^8.25.0: + version "8.25.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.25.0.tgz#00eb962f50962165d0c4ee3327708315eaa8058b" + integrity sha512-DVlJOZ4Pn50zcKW5bYH7GQK/9MsoQG2d5eDH0ebEkE8PbgzTTmtt/VTH9GGJ4BfeZCpBLqFfvsjX35UacUL83A== dependencies: - "@eslint/eslintrc" "^1.3.2" + "@eslint/eslintrc" "^1.3.3" "@humanwhocodes/config-array" "^0.10.5" - "@humanwhocodes/gitignore-to-minimatch" "^1.0.2" "@humanwhocodes/module-importer" "^1.0.1" ajv "^6.10.0" chalk "^4.0.0" @@ -2846,7 +2895,7 @@ exit@^0.1.2: resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0, expect@^29.0.3: +expect@^29.0.0: version "29.0.3" resolved "https://registry.npmjs.org/expect/-/expect-29.0.3.tgz" integrity sha512-t8l5DTws3212VbmPL+tBFXhjRHLmctHB0oQbL8eUc6S7NzZtYUhycrFO9mkxA0ZUC6FAWdNi7JchJSkODtcu1Q== @@ -2857,6 +2906,17 @@ expect@^29.0.0, expect@^29.0.3: jest-message-util "^29.0.3" jest-util "^29.0.3" +expect@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.1.2.tgz#82f8f28d7d408c7c68da3a386a490ee683e1eced" + integrity sha512-AuAGn1uxva5YBbBlXb+2JPxJRuemZsmlGcapPXWNSBNsQtAULfjioREGBWuI0EOvYUKjDnrCy8PW5Zlr1md5mw== + dependencies: + "@jest/expect-utils" "^29.1.2" + jest-get-type "^29.0.0" + jest-matcher-utils "^29.1.2" + jest-message-util "^29.1.2" + jest-util "^29.1.2" + ext@^1.1.2: version "1.7.0" resolved "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz" @@ -3410,74 +3470,74 @@ jest-changed-files@^29.0.0: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-29.0.3.tgz" - integrity sha512-QeGzagC6Hw5pP+df1+aoF8+FBSgkPmraC1UdkeunWh0jmrp7wC0Hr6umdUAOELBQmxtKAOMNC3KAdjmCds92Zg== +jest-circus@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.1.2.tgz#4551068e432f169a53167fe1aef420cf51c8a735" + integrity sha512-ajQOdxY6mT9GtnfJRZBRYS7toNIJayiiyjDyoZcnvPRUPwJ58JX0ci0PKAKUo2C1RyzlHw0jabjLGKksO42JGA== dependencies: - "@jest/environment" "^29.0.3" - "@jest/expect" "^29.0.3" - "@jest/test-result" "^29.0.3" - "@jest/types" "^29.0.3" + "@jest/environment" "^29.1.2" + "@jest/expect" "^29.1.2" + "@jest/test-result" "^29.1.2" + "@jest/types" "^29.1.2" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^29.0.3" - jest-matcher-utils "^29.0.3" - jest-message-util "^29.0.3" - jest-runtime "^29.0.3" - jest-snapshot "^29.0.3" - jest-util "^29.0.3" + jest-each "^29.1.2" + jest-matcher-utils "^29.1.2" + jest-message-util "^29.1.2" + jest-runtime "^29.1.2" + jest-snapshot "^29.1.2" + jest-util "^29.1.2" p-limit "^3.1.0" - pretty-format "^29.0.3" + pretty-format "^29.1.2" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-29.0.3.tgz" - integrity sha512-aUy9Gd/Kut1z80eBzG10jAn6BgS3BoBbXyv+uXEqBJ8wnnuZ5RpNfARoskSrTIy1GY4a8f32YGuCMwibtkl9CQ== +jest-cli@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.1.2.tgz#423b9c5d3ea20a50b1354b8bf3f2a20e72110e89" + integrity sha512-vsvBfQ7oS2o4MJdAH+4u9z76Vw5Q8WBQF5MchDbkylNknZdrPTX1Ix7YRJyTlOWqRaS7ue/cEAn+E4V1MWyMzw== dependencies: - "@jest/core" "^29.0.3" - "@jest/test-result" "^29.0.3" - "@jest/types" "^29.0.3" + "@jest/core" "^29.1.2" + "@jest/test-result" "^29.1.2" + "@jest/types" "^29.1.2" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.0.3" - jest-util "^29.0.3" - jest-validate "^29.0.3" + jest-config "^29.1.2" + jest-util "^29.1.2" + jest-validate "^29.1.2" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-config/-/jest-config-29.0.3.tgz" - integrity sha512-U5qkc82HHVYe3fNu2CRXLN4g761Na26rWKf7CjM8LlZB3In1jadEkZdMwsE37rd9RSPV0NfYaCjHdk/gu3v+Ew== +jest-config@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.1.2.tgz#7d004345ca4c09f5d8f802355f54494e90842f4d" + integrity sha512-EC3Zi86HJUOz+2YWQcJYQXlf0zuBhJoeyxLM6vb6qJsVmpP7KcCP1JnyF0iaqTaXdBP8Rlwsvs7hnKWQWWLwwA== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.0.3" - "@jest/types" "^29.0.3" - babel-jest "^29.0.3" + "@jest/test-sequencer" "^29.1.2" + "@jest/types" "^29.1.2" + babel-jest "^29.1.2" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.0.3" - jest-environment-node "^29.0.3" + jest-circus "^29.1.2" + jest-environment-node "^29.1.2" jest-get-type "^29.0.0" jest-regex-util "^29.0.0" - jest-resolve "^29.0.3" - jest-runner "^29.0.3" - jest-util "^29.0.3" - jest-validate "^29.0.3" + jest-resolve "^29.1.2" + jest-runner "^29.1.2" + jest-util "^29.1.2" + jest-validate "^29.1.2" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.0.3" + pretty-format "^29.1.2" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -3491,6 +3551,16 @@ jest-diff@^29.0.3: jest-get-type "^29.0.0" pretty-format "^29.0.3" +jest-diff@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.1.2.tgz#bb7aaf5353227d6f4f96c5e7e8713ce576a607dc" + integrity sha512-4GQts0aUopVvecIT4IwD/7xsBaMhKTYoM4/njE/aVw9wpw+pIUVp8Vab/KnSzSilr84GnLBkaP3JLDnQYCKqVQ== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.0.0" + jest-get-type "^29.0.0" + pretty-format "^29.1.2" + jest-docblock@^29.0.0: version "29.0.0" resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.0.0.tgz" @@ -3498,28 +3568,28 @@ jest-docblock@^29.0.0: dependencies: detect-newline "^3.0.0" -jest-each@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-each/-/jest-each-29.0.3.tgz" - integrity sha512-wILhZfESURHHBNvPMJ0lZlYZrvOQJxAo3wNHi+ycr90V7M+uGR9Gh4+4a/BmaZF0XTyZsk4OiYEf3GJN7Ltqzg== +jest-each@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.1.2.tgz#d4c8532c07a846e79f194f7007ce7cb1987d1cd0" + integrity sha512-AmTQp9b2etNeEwMyr4jc0Ql/LIX/dhbgP21gHAizya2X6rUspHn2gysMXaj6iwWuOJ2sYRgP8c1P4cXswgvS1A== dependencies: - "@jest/types" "^29.0.3" + "@jest/types" "^29.1.2" chalk "^4.0.0" jest-get-type "^29.0.0" - jest-util "^29.0.3" - pretty-format "^29.0.3" + jest-util "^29.1.2" + pretty-format "^29.1.2" -jest-environment-node@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.0.3.tgz" - integrity sha512-cdZqRCnmIlTXC+9vtvmfiY/40Cj6s2T0czXuq1whvQdmpzAnj4sbqVYuZ4zFHk766xTTJ+Ij3uUqkk8KCfXoyg== +jest-environment-node@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.1.2.tgz#005e05cc6ea4b9b5ba55906ab1ce53c82f6907a7" + integrity sha512-C59yVbdpY8682u6k/lh8SUMDJPbOyCHOTgLVVi1USWFxtNV+J8fyIwzkg+RJIVI30EKhKiAGNxYaFr3z6eyNhQ== dependencies: - "@jest/environment" "^29.0.3" - "@jest/fake-timers" "^29.0.3" - "@jest/types" "^29.0.3" + "@jest/environment" "^29.1.2" + "@jest/fake-timers" "^29.1.2" + "@jest/types" "^29.1.2" "@types/node" "*" - jest-mock "^29.0.3" - jest-util "^29.0.3" + jest-mock "^29.1.2" + jest-util "^29.1.2" jest-get-type@^29.0.0: version "29.0.0" @@ -3545,32 +3615,32 @@ jest-haste-map@^28.1.3: optionalDependencies: fsevents "^2.3.2" -jest-haste-map@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.0.3.tgz" - integrity sha512-uMqR99+GuBHo0RjRhOE4iA6LmsxEwRdgiIAQgMU/wdT2XebsLDz5obIwLZm/Psj+GwSEQhw9AfAVKGYbh2G55A== +jest-haste-map@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.1.2.tgz#93f3634aa921b6b654e7c94137b24e02e7ca6ac9" + integrity sha512-xSjbY8/BF11Jh3hGSPfYTa/qBFrm3TPM7WU8pU93m2gqzORVLkHFWvuZmFsTEBPRKndfewXhMOuzJNHyJIZGsw== dependencies: - "@jest/types" "^29.0.3" + "@jest/types" "^29.1.2" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" jest-regex-util "^29.0.0" - jest-util "^29.0.3" - jest-worker "^29.0.3" + jest-util "^29.1.2" + jest-worker "^29.1.2" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.0.3.tgz" - integrity sha512-YfW/G63dAuiuQ3QmQlh8hnqLDe25WFY3eQhuc/Ev1AGmkw5zREblTh7TCSKLoheyggu6G9gxO2hY8p9o6xbaRQ== +jest-leak-detector@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.1.2.tgz#4c846db14c58219430ccbc4f01a1ec52ebee4fc2" + integrity sha512-TG5gAZJpgmZtjb6oWxBLf2N6CfQ73iwCe6cofu/Uqv9iiAm6g502CAnGtxQaTfpHECBdVEMRBhomSXeLnoKjiQ== dependencies: jest-get-type "^29.0.0" - pretty-format "^29.0.3" + pretty-format "^29.1.2" jest-matcher-utils@^29.0.3: version "29.0.3" @@ -3582,6 +3652,16 @@ jest-matcher-utils@^29.0.3: jest-get-type "^29.0.0" pretty-format "^29.0.3" +jest-matcher-utils@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.1.2.tgz#e68c4bcc0266e70aa1a5c13fb7b8cd4695e318a1" + integrity sha512-MV5XrD3qYSW2zZSHRRceFzqJ39B2z11Qv0KPyZYxnzDHFeYZGJlgGi0SW+IXSJfOewgJp/Km/7lpcFT+cgZypw== + dependencies: + chalk "^4.0.0" + jest-diff "^29.1.2" + jest-get-type "^29.0.0" + pretty-format "^29.1.2" + jest-message-util@^29.0.3: version "29.0.3" resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.3.tgz" @@ -3597,13 +3677,29 @@ jest-message-util@^29.0.3: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.3.tgz" - integrity sha512-ort9pYowltbcrCVR43wdlqfAiFJXBx8l4uJDsD8U72LgBcetvEp+Qxj1W9ZYgMRoeAo+ov5cnAGF2B6+Oth+ww== +jest-message-util@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.1.2.tgz#c21a33c25f9dc1ebfcd0f921d89438847a09a501" + integrity sha512-9oJ2Os+Qh6IlxLpmvshVbGUiSkZVc2FK+uGOm6tghafnB2RyjKAxMZhtxThRMxfX1J1SOMhTn9oK3/MutRWQJQ== dependencies: - "@jest/types" "^29.0.3" + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.1.2" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.1.2" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.1.2.tgz#de47807edbb9d4abf8423f1d8d308d670105678c" + integrity sha512-PFDAdjjWbjPUtQPkQufvniXIS3N9Tv7tbibePEjIIprzjgo0qQlyUiVMrT4vL8FaSJo1QXifQUOuPH3HQC/aMA== + dependencies: + "@jest/types" "^29.1.2" "@types/node" "*" + jest-util "^29.1.2" jest-pnp-resolver@^1.2.2: version "1.2.2" @@ -3620,88 +3716,88 @@ jest-regex-util@^29.0.0: resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz" integrity sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug== -jest-resolve-dependencies@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.0.3.tgz" - integrity sha512-KzuBnXqNvbuCdoJpv8EanbIGObk7vUBNt/PwQPPx2aMhlv/jaXpUJsqWYRpP/0a50faMBY7WFFP8S3/CCzwfDw== +jest-resolve-dependencies@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.1.2.tgz#a6919e58a0c7465582cb8ec2d745b4e64ae8647f" + integrity sha512-44yYi+yHqNmH3OoWZvPgmeeiwKxhKV/0CfrzaKLSkZG9gT973PX8i+m8j6pDrTYhhHoiKfF3YUFg/6AeuHw4HQ== dependencies: jest-regex-util "^29.0.0" - jest-snapshot "^29.0.3" + jest-snapshot "^29.1.2" -jest-resolve@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.0.3.tgz" - integrity sha512-toVkia85Y/BPAjJasTC9zIPY6MmVXQPtrCk8SmiheC4MwVFE/CMFlOtMN6jrwPMC6TtNh8+sTMllasFeu1wMPg== +jest-resolve@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.1.2.tgz#9dd8c2fc83e59ee7d676b14bd45a5f89e877741d" + integrity sha512-7fcOr+k7UYSVRJYhSmJHIid3AnDBcLQX3VmT9OSbPWsWz1MfT7bcoerMhADKGvKCoMpOHUQaDHtQoNp/P9JMGg== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.0.3" + jest-haste-map "^29.1.2" jest-pnp-resolver "^1.2.2" - jest-util "^29.0.3" - jest-validate "^29.0.3" + jest-util "^29.1.2" + jest-validate "^29.1.2" resolve "^1.20.0" resolve.exports "^1.1.0" slash "^3.0.0" -jest-runner@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-29.0.3.tgz" - integrity sha512-Usu6VlTOZlCZoNuh3b2Tv/yzDpKqtiNAetG9t3kJuHfUyVMNW7ipCCJOUojzKkjPoaN7Bl1f7Buu6PE0sGpQxw== +jest-runner@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.1.2.tgz#f18b2b86101341e047de8c2f51a5fdc4e97d053a" + integrity sha512-yy3LEWw8KuBCmg7sCGDIqKwJlULBuNIQa2eFSVgVASWdXbMYZ9H/X0tnXt70XFoGf92W2sOQDOIFAA6f2BG04Q== dependencies: - "@jest/console" "^29.0.3" - "@jest/environment" "^29.0.3" - "@jest/test-result" "^29.0.3" - "@jest/transform" "^29.0.3" - "@jest/types" "^29.0.3" + "@jest/console" "^29.1.2" + "@jest/environment" "^29.1.2" + "@jest/test-result" "^29.1.2" + "@jest/transform" "^29.1.2" + "@jest/types" "^29.1.2" "@types/node" "*" chalk "^4.0.0" emittery "^0.10.2" graceful-fs "^4.2.9" jest-docblock "^29.0.0" - jest-environment-node "^29.0.3" - jest-haste-map "^29.0.3" - jest-leak-detector "^29.0.3" - jest-message-util "^29.0.3" - jest-resolve "^29.0.3" - jest-runtime "^29.0.3" - jest-util "^29.0.3" - jest-watcher "^29.0.3" - jest-worker "^29.0.3" + jest-environment-node "^29.1.2" + jest-haste-map "^29.1.2" + jest-leak-detector "^29.1.2" + jest-message-util "^29.1.2" + jest-resolve "^29.1.2" + jest-runtime "^29.1.2" + jest-util "^29.1.2" + jest-watcher "^29.1.2" + jest-worker "^29.1.2" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.0.3.tgz" - integrity sha512-12gZXRQ7ozEeEHKTY45a+YLqzNDR/x4c//X6AqwKwKJPpWM8FY4vwn4VQJOcLRS3Nd1fWwgP7LU4SoynhuUMHQ== +jest-runtime@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.1.2.tgz#dbcd57103d61115479108d5864bdcd661d9c6783" + integrity sha512-jr8VJLIf+cYc+8hbrpt412n5jX3tiXmpPSYTGnwcvNemY+EOuLNiYnHJ3Kp25rkaAcTWOEI4ZdOIQcwYcXIAZw== dependencies: - "@jest/environment" "^29.0.3" - "@jest/fake-timers" "^29.0.3" - "@jest/globals" "^29.0.3" + "@jest/environment" "^29.1.2" + "@jest/fake-timers" "^29.1.2" + "@jest/globals" "^29.1.2" "@jest/source-map" "^29.0.0" - "@jest/test-result" "^29.0.3" - "@jest/transform" "^29.0.3" - "@jest/types" "^29.0.3" + "@jest/test-result" "^29.1.2" + "@jest/transform" "^29.1.2" + "@jest/types" "^29.1.2" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.0.3" - jest-message-util "^29.0.3" - jest-mock "^29.0.3" + jest-haste-map "^29.1.2" + jest-message-util "^29.1.2" + jest-mock "^29.1.2" jest-regex-util "^29.0.0" - jest-resolve "^29.0.3" - jest-snapshot "^29.0.3" - jest-util "^29.0.3" + jest-resolve "^29.1.2" + jest-snapshot "^29.1.2" + jest-util "^29.1.2" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.0.3.tgz" - integrity sha512-52q6JChm04U3deq+mkQ7R/7uy7YyfVIrebMi6ZkBoDJ85yEjm/sJwdr1P0LOIEHmpyLlXrxy3QP0Zf5J2kj0ew== +jest-snapshot@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.1.2.tgz#7dd277e88c45f2d2ff5888de1612e63c7ceb575b" + integrity sha512-rYFomGpVMdBlfwTYxkUp3sjD6usptvZcONFYNqVlaz4EpHPnDvlWjvmOQ9OCSNKqYZqLM2aS3wq01tWujLg7gg== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" @@ -3709,23 +3805,23 @@ jest-snapshot@^29.0.3: "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.0.3" - "@jest/transform" "^29.0.3" - "@jest/types" "^29.0.3" + "@jest/expect-utils" "^29.1.2" + "@jest/transform" "^29.1.2" + "@jest/types" "^29.1.2" "@types/babel__traverse" "^7.0.6" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.0.3" + expect "^29.1.2" graceful-fs "^4.2.9" - jest-diff "^29.0.3" + jest-diff "^29.1.2" jest-get-type "^29.0.0" - jest-haste-map "^29.0.3" - jest-matcher-utils "^29.0.3" - jest-message-util "^29.0.3" - jest-util "^29.0.3" + jest-haste-map "^29.1.2" + jest-matcher-utils "^29.1.2" + jest-message-util "^29.1.2" + jest-util "^29.1.2" natural-compare "^1.4.0" - pretty-format "^29.0.3" + pretty-format "^29.1.2" semver "^7.3.5" jest-util@^28.1.3: @@ -3752,30 +3848,42 @@ jest-util@^29.0.3: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-29.0.3.tgz" - integrity sha512-OebiqqT6lK8cbMPtrSoS3aZP4juID762lZvpf1u+smZnwTEBCBInan0GAIIhv36MxGaJvmq5uJm7dl5gVt+Zrw== +jest-util@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.1.2.tgz#ac5798e93cb6a6703084e194cfa0898d66126df1" + integrity sha512-vPCk9F353i0Ymx3WQq3+a4lZ07NXu9Ca8wya6o4Fe4/aO1e1awMMprZ3woPFpKwghEOW+UXgd15vVotuNN9ONQ== dependencies: - "@jest/types" "^29.0.3" + "@jest/types" "^29.1.2" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.1.2.tgz#83a728b8f6354da2e52346878c8bc7383516ca51" + integrity sha512-k71pOslNlV8fVyI+mEySy2pq9KdXdgZtm7NHrBX8LghJayc3wWZH0Yr0mtYNGaCU4F1OLPXRkwZR0dBm/ClshA== + dependencies: + "@jest/types" "^29.1.2" camelcase "^6.2.0" chalk "^4.0.0" jest-get-type "^29.0.0" leven "^3.1.0" - pretty-format "^29.0.3" + pretty-format "^29.1.2" -jest-watcher@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.0.3.tgz" - integrity sha512-tQX9lU91A+9tyUQKUMp0Ns8xAcdhC9fo73eqA3LFxP2bSgiF49TNcc+vf3qgGYYK9qRjFpXW9+4RgF/mbxyOOw== +jest-watcher@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.1.2.tgz#de21439b7d889e2fcf62cc2a4779ef1a3f1f3c62" + integrity sha512-6JUIUKVdAvcxC6bM8/dMgqY2N4lbT+jZVsxh0hCJRbwkIEnbr/aPjMQ28fNDI5lB51Klh00MWZZeVf27KBUj5w== dependencies: - "@jest/test-result" "^29.0.3" - "@jest/types" "^29.0.3" + "@jest/test-result" "^29.1.2" + "@jest/types" "^29.1.2" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.10.2" - jest-util "^29.0.3" + jest-util "^29.1.2" string-length "^4.0.1" jest-worker@^28.1.3: @@ -3787,24 +3895,25 @@ jest-worker@^28.1.3: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-29.0.3.tgz" - integrity sha512-Tl/YWUugQOjoTYwjKdfJWkSOfhufJHO5LhXTSZC3TRoQKO+fuXnZAdoXXBlpLXKGODBL3OvdUasfDD4PcMe6ng== +jest-worker@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.1.2.tgz#a68302af61bce82b42a9a57285ca7499d29b2afc" + integrity sha512-AdTZJxKjTSPHbXT/AIOjQVmoFx0LHFcVabWu0sxI7PAy7rFf8c0upyvgBKgguVXdM4vY74JdwkyD4hSmpTW8jA== dependencies: "@types/node" "*" + jest-util "^29.1.2" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest/-/jest-29.0.3.tgz" - integrity sha512-ElgUtJBLgXM1E8L6K1RW1T96R897YY/3lRYqq9uVcPWtP2AAl/nQ16IYDh/FzQOOQ12VEuLdcPU83mbhG2C3PQ== +jest@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.1.2.tgz#f821a1695ffd6cd0efc3b59d2dfcc70a98582499" + integrity sha512-5wEIPpCezgORnqf+rCaYD1SK+mNN7NsstWzIsuvsnrhR/hSxXWd82oI7DkrbJ+XTD28/eG8SmxdGvukrGGK6Tw== dependencies: - "@jest/core" "^29.0.3" - "@jest/types" "^29.0.3" + "@jest/core" "^29.1.2" + "@jest/types" "^29.1.2" import-local "^3.0.2" - jest-cli "^29.0.3" + jest-cli "^29.1.2" js-sdsl@^4.1.4: version "4.1.4" @@ -4281,6 +4390,15 @@ pretty-format@^29.0.0, pretty-format@^29.0.3: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.1.2.tgz#b1f6b75be7d699be1a051f5da36e8ae9e76a8e6a" + integrity sha512-CGJ6VVGXVRP2o2Dorl4mAwwvDWT25luIsYhkyVQW32E4nL+TgW939J7LlKT/npq5Cpq6j3s+sy+13yk7xYpBmg== + dependencies: + "@jest/schemas" "^29.0.0" + ansi-styles "^5.0.0" + react-is "^18.0.0" + prompts@^2.0.1: version "2.4.2" resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" @@ -4874,10 +4992,10 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -wasm-ast-types@^0.11.3: - version "0.11.3" - resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.11.3.tgz#d91b6456e11b3cb23d77b34e5156d13d2ead3a22" - integrity sha512-rNJNKznUMgt8Bb01xPoyjM13VQmXzDIBG49oV09k4RusRELZu7pQWejBy08lV5BQjcIYKpWkqiUnAZJp73ZmOA== +wasm-ast-types@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.13.0.tgz#79068ca2c51c097546ed7abdc61a86f7ad43836b" + integrity sha512-Rbg+02LqSYc4v0jGt6L+edLUzoQyfml3nBU13T/4e3T/L8BG25ein5mHSfA+/0l9y29EsNloD0LYr5KQnunN0w== dependencies: "@babel/runtime" "^7.18.9" "@babel/types" "7.18.10" From 7df3f72d79bf9025fb163703f4b187976795ea9e Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 13 Oct 2022 11:41:42 +0200 Subject: [PATCH 063/218] Adding support for mock vaults in testnet (#26) --- scripts/deploy/addresses/osmo-test-4.json | 4 +- scripts/deploy/base/deployer.ts | 68 ++++++++++------- scripts/deploy/base/index.ts | 11 ++- scripts/deploy/base/rover.ts | 89 ++++++++++++++++++++--- scripts/deploy/osmosis/config.ts | 8 +- scripts/types/config.ts | 8 ++ scripts/types/storageItems.ts | 2 + 7 files changed, 146 insertions(+), 44 deletions(-) diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index cb83efecc..16f0a77c3 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,7 @@ { "accountNft": "osmo1m90h4fx5enc5wagrq8enm03597wy3sxl3mpcrg254dt459z709lquekx0a", - "mockVault": "osmo1t2n7lk9699sqztg8x23rzfd8x5vvhnqddt69a8k9pw5e0pj7zgkqt3ak7c", "marsOracleAdapter": "osmo1d6wp6u3efdrevmef9f4u6juyffzr6yhg5m9a8c8x9camxssk2vsqxpg4yc", "swapper": "osmo1ywhrzs2uz2yyhkd8h9qjvluvklq4devjf0sx5qzd9l56yza3qhtsfk89vs", - "creditManager": "osmo1py5le8gryx3zuetvjl6xkcecrnp8r3n7t66hfxjmz3vlmj55f2yq2s6jmk" + "creditManager": "osmo1py5le8gryx3zuetvjl6xkcecrnp8r3n7t66hfxjmz3vlmj55f2yq2s6jmk", + "mockVault": "osmo1xqcw53p7f7wzgfltcty4qzgux0hahm5wkh57wh6x9tx0l3y3fcqsfaex5r" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index ec6c8c2cb..7fcbca825 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -13,7 +13,7 @@ import { Rover } from './rover' import { AccountNftClient } from '../../types/generated/account-nft/AccountNft.client' import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing' import { getAddress, getWallet, setupClient } from './setupDeployer' -import { coins } from '@cosmjs/stargate' +import { coin } from '@cosmjs/stargate' import { Coin } from '@cosmjs/amino' import { writeFile } from 'fs/promises' import { join, resolve } from 'path' @@ -81,6 +81,18 @@ export class Deployer { oracle: this.config.oracleAddr, } await this.instantiate('mockVault', this.storage.codeIds.mockVault!, msg) + + // Temporary until Token Factory is integrated into Cosmwasm or Apollo Vaults are in testnet + if (!this.storage.actions.seedMockVault) { + printBlue('Seeding mock vault') + await this.transferCoin( + this.storage.addresses.mockVault!, + coin(10_000_000, this.config.vaultTokenDenom), + ) + this.storage.actions.seedMockVault = true + } else { + printGray('Mock vault already seeded') + } } async instantiateMarsOracleAdapter() { @@ -104,27 +116,31 @@ export class Deployer { } await this.instantiate('swapper', this.storage.codeIds.swapper!, msg) - await this.transferFunds(this.storage.addresses.swapper!, [ - { denom: this.config.baseDenom, amount: '100' }, - ]) + if (!this.storage.actions.setRouteAndSeedSwapper) { + printBlue(`Seeding swapper w/ ${this.config.baseDenom}`) + await this.transferCoin(this.storage.addresses.swapper!, coin(100, this.config.baseDenom)) - const swapClient = new SwapperBaseClient( - this.cwClient, - this.deployerAddr, - this.storage.addresses.swapper!, - ) - printBlue( - `Setting ${this.config.baseDenom}-${this.config.secondaryDenom} route for swap contract`, - ) - await swapClient.setRoute({ - denomIn: this.config.baseDenom, - denomOut: this.config.secondaryDenom, - route: this.config.swapRoute, - }) - - const swapQuery = new SwapperBaseQueryClient(this.cwClient, this.storage.addresses.swapper!) - const routes = await swapQuery.routes({}) - assert.equal(routes.length, 1) + const swapClient = new SwapperBaseClient( + this.cwClient, + this.deployerAddr, + this.storage.addresses.swapper!, + ) + printBlue( + `Setting ${this.config.baseDenom}-${this.config.secondaryDenom} route for swap contract`, + ) + await swapClient.setRoute({ + denomIn: this.config.baseDenom, + denomOut: this.config.secondaryDenom, + route: this.config.swapRoute, + }) + + const swapQuery = new SwapperBaseQueryClient(this.cwClient, this.storage.addresses.swapper!) + const routes = await swapQuery.routes({}) + assert.equal(routes.length, 1) + this.storage.actions.setRouteAndSeedSwapper = true + } else { + printGray('Swap contract already seeded with funds') + } } async instantiateCreditManager() { @@ -168,9 +184,9 @@ export class Deployer { async newUserRoverClient() { const { client, address } = await this.generateNewAddress() printBlue(`New user: ${address}`) - await this.transferFunds( + await this.transferCoin( address, - coins(this.config.startingAmountForTestUser, this.config.baseDenom), + coin(this.config.startingAmountForTestUser, this.config.baseDenom), ) return this.getRoverClient(address, client) } @@ -183,9 +199,9 @@ export class Deployer { ) } - private async transferFunds(recipient: string, coins: Coin[]) { - await this.cwClient.sendTokens(this.deployerAddr, recipient, coins, 'auto') - const balance = await this.cwClient.getBalance(recipient, this.config.baseDenom) + private async transferCoin(recipient: string, coin: Coin) { + await this.cwClient.sendTokens(this.deployerAddr, recipient, [coin], 'auto') + const balance = await this.cwClient.getBalance(recipient, coin.denom) printBlue(`New balance: ${balance.amount} ${balance.denom}`) } diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 8b5a3dd0d..f295eaf52 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -1,6 +1,6 @@ import { setupDeployer } from './setupDeployer' import { printRed, printYellow } from '../../utils/chalk' -import { DeploymentConfig } from '../../types/config' +import { DeploymentConfig, VaultType } from '../../types/config' import { wasmFile } from '../../utils/environment' export interface TaskRunnerProps { @@ -38,9 +38,12 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp // await rover.swap() await rover.withdraw() - // TODO: Use after token factory is launched and integrated into mock_vault - // or Apollo vaults are on testnet - // await rover.vaultDeposit() + await rover.vaultDeposit() + if (config.vaultType === VaultType.UNLOCKED) { + await rover.vaultWithdraw() + } else { + await rover.vaultRequestUnlock() + } printYellow('COMPLETE') } catch (e) { diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 3e2c47dbf..68782261d 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -25,7 +25,7 @@ export class Rover { private userAddr: string, private storage: Storage, private config: DeploymentConfig, - cwClient: SigningCosmWasmClient, + private cwClient: SigningCosmWasmClient, ) { this.exec = new CreditManagerClient(cwClient, userAddr, storage.addresses.creditManager!) this.query = new CreditManagerQueryClient(cwClient, storage.addresses.creditManager!) @@ -56,7 +56,7 @@ export class Rover { assert.equal(positions.coins.length, 1) assert.equal(positions.coins[0].amount, amount) assert.equal(positions.coins[0].denom, this.config.baseDenom) - printGreen(`Deposited: ${amount} ${this.config.baseDenom}`) + printGreen(`Deposited into credit account: ${amount} ${this.config.baseDenom}`) } async withdraw() { @@ -112,27 +112,96 @@ export class Rover { } async vaultDeposit() { - const amount = '10' + const oldRoverBalance = await this.cwClient.getBalance( + this.storage.addresses.creditManager!, + this.config.vaultTokenDenom, + ) await this.updateCreditAccount([ { vault_deposit: { - coins: [{ amount, denom: this.config.baseDenom }], + coins: [ + { amount: this.config.vaultDepositAmount.toString(), denom: this.config.baseDenom }, + ], vault: { address: this.storage.addresses.mockVault! }, }, }, ]) const positions = await this.query.positions({ accountId: this.accountId! }) assert.equal(positions.vaults.length, 1) - assert.equal(positions.vaults[0].vault.address, this.storage.addresses.mockVault) - assert.equal(positions.vaults[0].state.locked, 123) - assert.equal(positions.vaults[0].state.unlocked, 123) + const state = await this.getVaultBalance(this.storage.addresses.mockVault!) + assert(state.locked > 0 || state.unlocked > 0) + const newRoverBalance = await this.cwClient.getBalance( + this.storage.addresses.creditManager!, + this.config.vaultTokenDenom, + ) + const newAmount = parseInt(newRoverBalance.amount) - parseInt(oldRoverBalance.amount) + assert(newAmount === state.locked || newAmount === state.unlocked) + + printGreen( + `Deposited ${this.config.vaultDepositAmount} ${ + this.config.baseDenom + } in exchange for vault tokens: ${JSON.stringify(positions.vaults[0])}`, + ) + } + + async vaultWithdraw() { + const oldBalance = await this.getAccountBalance(this.config.baseDenom) + await this.updateCreditAccount([ + { + vault_withdraw: { + amount: this.config.vaultWithdrawAmount.toString(), + vault: { address: this.storage.addresses.mockVault! }, + }, + }, + ]) + const newBalance = await this.getAccountBalance(this.config.baseDenom) + assert(newBalance > oldBalance) + printGreen( + `Withdrew ${newBalance - oldBalance} ${this.config.baseDenom} in exchange for ${ + this.config.vaultWithdrawAmount + } ${this.config.vaultTokenDenom}`, + ) + } + + async vaultRequestUnlock() { + const oldBalance = await this.getVaultBalance(this.storage.addresses.mockVault!) + await this.updateCreditAccount([ + { + vault_request_unlock: { + amount: this.config.vaultWithdrawAmount.toString(), + vault: { address: this.storage.addresses.mockVault! }, + }, + }, + ]) + const newBalance = await this.getVaultBalance(this.storage.addresses.mockVault!) + assert(newBalance.locked < oldBalance.locked) + assert.equal(newBalance.unlocking.length, 1) + printGreen( - `Deposit into vault: ${amount} ${this.config.baseDenom}, Vault Postition: ${JSON.stringify( - positions.vaults[0], - )}`, + `Requested unlock: ID #${newBalance.unlocking[0].id}, amount: ${ + newBalance.unlocking[0].amount + } in exchange for: ${oldBalance.locked - newBalance.locked} ${this.config.vaultTokenDenom}`, ) } + private async getAccountBalance(denom: string) { + const positions = await this.query.positions({ accountId: this.accountId! }) + const coin = positions.coins.find((c) => c.denom === denom) + if (!coin) throw new Error(`No balance of ${denom}`) + return parseInt(coin.amount) + } + + private async getVaultBalance(vaultAddr: string) { + const positions = await this.query.positions({ accountId: this.accountId! }) + const vault = positions.vaults.find((p) => p.vault.address === vaultAddr) + if (!vault) throw new Error(`No balance for ${vaultAddr}`) + return { + locked: parseInt(vault.state.locked), + unlocked: parseInt(vault.state.unlocked), + unlocking: vault.state.unlocking, + } + } + private async updateCreditAccount(actions: Action[], funds?: Coin[]) { await this.exec.updateCreditAccount( { actions, accountId: this.accountId! }, diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 63a440458..6085365dd 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -1,6 +1,7 @@ -import { DeploymentConfig } from '../../types/config' +import { DeploymentConfig, VaultType } from '../../types/config' const uatom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2' +const udig = 'ibc/307E5C96C8F60D1CBEE269A9A86C0834E1DB06F2B3788AE4F716EDB97A48B97D' export const osmosisTestnetConfig: DeploymentConfig = { // Get the latest addresses from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-4.json @@ -15,7 +16,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { rpcEndpoint: 'https://rpc-test.osmosis.zone', defaultGasPrice: 0.1, startingAmountForTestUser: 1e6, - vaultTokenDenom: 'xCompounder', + vaultTokenDenom: udig, maxCloseFactor: 0.6, maxLiquidationBonus: 0.05, depositAmount: 100, @@ -32,4 +33,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { }, slippage: 0.4, withdrawAmount: 12, + vaultDepositAmount: 10, + vaultType: VaultType.UNLOCKED, + vaultWithdrawAmount: 1_000_000, } diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 10f3c1c88..ad9dd1248 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -1,3 +1,8 @@ +export enum VaultType { + LOCKED, + UNLOCKED, +} + export interface DeploymentConfig { oracleAddr: string redBankAddr: string @@ -19,4 +24,7 @@ export interface DeploymentConfig { withdrawAmount: number maxCloseFactor: number maxLiquidationBonus: number + vaultDepositAmount: number + vaultWithdrawAmount: number + vaultType: VaultType } diff --git a/scripts/types/storageItems.ts b/scripts/types/storageItems.ts index 8f9781620..4f929322b 100644 --- a/scripts/types/storageItems.ts +++ b/scripts/types/storageItems.ts @@ -18,5 +18,7 @@ export interface StorageItems { actions: { proposedNewOwner?: boolean acceptedOwnership?: boolean + setRouteAndSeedSwapper?: boolean + seedMockVault?: boolean } } From f95ae2268edd6a0c2ca226e831399b4d888bd7de Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 18 Oct 2022 22:11:35 +0200 Subject: [PATCH 064/218] Adding support for vault liquidations (#27) * Adding support for vault liquidations * review updates --- Cargo.lock | 1 + contracts/credit-manager/Cargo.toml | 1 + contracts/credit-manager/src/execute.rs | 29 +- contracts/credit-manager/src/health.rs | 8 +- contracts/credit-manager/src/lib.rs | 2 +- .../src/{liquidate.rs => liquidate_coin.rs} | 46 +- contracts/credit-manager/src/utils.rs | 4 +- contracts/credit-manager/src/vault/deposit.rs | 32 +- .../src/vault/liquidate_vault.rs | 164 +++++ contracts/credit-manager/src/vault/mod.rs | 2 + .../src/vault/request_unlock.rs | 14 +- contracts/credit-manager/src/vault/utils.rs | 97 ++- .../credit-manager/src/vault/withdraw.rs | 12 +- .../src/vault/withdraw_unlocked.rs | 28 +- .../credit-manager/tests/helpers/contracts.rs | 63 +- .../credit-manager/tests/helpers/mock_env.rs | 80 ++- .../credit-manager/tests/helpers/utils.rs | 9 + .../tests/test_enumerate_allowed_vaults.rs | 2 +- .../test_enumerate_vault_coin_balances.rs | 2 +- .../tests/test_enumerate_vault_positions.rs | 2 +- .../credit-manager/tests/test_instantiate.rs | 11 +- .../tests/test_liquidate_coin.rs | 21 +- .../tests/test_liquidate_vault.rs | 614 ++++++++++++++++++ .../tests/test_vault_withdraw_unlocked.rs | 10 +- contracts/mars-oracle-adapter/src/contract.rs | 30 +- contracts/mars-oracle-adapter/src/msg.rs | 8 +- .../tests/test_query_priceable_underlying.rs | 51 ++ contracts/mock-vault/src/contract.rs | 9 +- contracts/mock-vault/src/error.rs | 3 + contracts/mock-vault/src/query.rs | 6 +- contracts/mock-vault/src/state.rs | 2 +- contracts/mock-vault/src/unlock.rs | 45 +- contracts/mock-vault/src/withdraw.rs | 6 +- packages/rover/src/adapters/vault.rs | 57 +- packages/rover/src/extensions/reply.rs | 10 +- packages/rover/src/msg/execute.rs | 19 +- packages/rover/src/msg/vault.rs | 18 +- scripts/deploy/base/deployer.ts | 2 +- scripts/deploy/base/rover.ts | 3 + 39 files changed, 1302 insertions(+), 221 deletions(-) rename contracts/credit-manager/src/{liquidate.rs => liquidate_coin.rs} (90%) create mode 100644 contracts/credit-manager/src/vault/liquidate_vault.rs create mode 100644 contracts/credit-manager/tests/test_liquidate_vault.rs create mode 100644 contracts/mars-oracle-adapter/tests/test_query_priceable_underlying.rs diff --git a/Cargo.lock b/Cargo.lock index 96035f21a..8e8a4205a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,6 +180,7 @@ dependencies = [ "cw721-base", "itertools", "mars-health", + "mars-oracle-adapter", "mars-outpost", "mock-oracle", "mock-red-bank", diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 011e65470..cc7070863 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -17,6 +17,7 @@ library = [] account-nft = { version = "1.0", path = "../account-nft", features = ["library"] } mars-health = { version = "0.1", path = "../../../outposts/packages/health" } mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } +mars-oracle-adapter = { version = "1.0", path = "../../contracts/mars-oracle-adapter", features = ["library"] } mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } mock-vault = { version = "1.0", path = "../../contracts/mock-vault", features = ["library"] } rover = { version = "1.0", path = "../../packages/rover" } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index dd282fdb5..3816b9cab 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -13,11 +13,11 @@ use crate::state::{ OWNER, RED_BANK, SWAPPER, }; use crate::vault::{ - deposit_into_vault, request_unlock_from_vault, update_vault_coin_balance, withdraw_from_vault, - withdraw_unlocked_from_vault, + deposit_into_vault, liquidate_vault, request_unlock_from_vault, update_vault_coin_balance, + withdraw_from_vault, withdraw_unlocked_from_vault, }; -use crate::liquidate::{assert_health_factor_improved, liquidate_coin}; +use crate::liquidate_coin::{assert_health_factor_improved, liquidate_coin}; use crate::swap::swap_exact_in; use crate::update_coin_balances::update_coin_balances; use crate::withdraw::withdraw; @@ -198,6 +198,16 @@ pub fn dispatch_actions( debt_coin: debt_coin.clone(), request_coin_denom: request_coin_denom.clone(), }), + Action::LiquidateVault { + liquidatee_account_id, + debt_coin, + request_vault, + } => callbacks.push(CallbackMsg::LiquidateVault { + liquidator_account_id: account_id.to_string(), + liquidatee_account_id: liquidatee_account_id.to_string(), + debt_coin: debt_coin.clone(), + request_vault: request_vault.check(deps.api)?, + }), Action::SwapExactIn { coin_in, denom_out, @@ -300,6 +310,19 @@ pub fn execute_callback( debt_coin, &request_coin_denom, ), + CallbackMsg::LiquidateVault { + liquidator_account_id, + liquidatee_account_id, + debt_coin, + request_vault, + } => liquidate_vault( + deps, + env, + &liquidator_account_id, + &liquidatee_account_id, + debt_coin, + request_vault, + ), CallbackMsg::AssertHealthFactorImproved { account_id, previous_health_factor, diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 5ff69e940..1b7cc041c 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -6,15 +6,15 @@ use rover::traits::Coins; use crate::query::query_positions; use crate::state::{ORACLE, RED_BANK}; -use crate::vault::simulate_withdraw; +use crate::vault::get_priceable_coins; pub fn compute_health(deps: Deps, env: &Env, account_id: &str) -> ContractResult { let res = query_positions(deps, env, account_id)?; - let coins_if_withdrawn = simulate_withdraw(&deps, &res.vaults)?; + let priceable_coins = get_priceable_coins(&deps, &res.vaults)?; - let mut collateral = Vec::with_capacity(res.coins.len() + coins_if_withdrawn.len()); + let mut collateral = Vec::with_capacity(res.coins.len() + priceable_coins.len()); collateral.extend(res.coins); - collateral.extend(coins_if_withdrawn); + collateral.extend(priceable_coins); let oracle = ORACLE.load(deps.storage)?; let red_bank = RED_BANK.load(deps.storage)?; diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 52bbf634d..fd4a3a4c0 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -5,7 +5,7 @@ pub mod deposit; pub mod execute; pub mod health; pub mod instantiate; -pub mod liquidate; +pub mod liquidate_coin; pub mod query; pub mod repay; pub mod state; diff --git a/contracts/credit-manager/src/liquidate.rs b/contracts/credit-manager/src/liquidate_coin.rs similarity index 90% rename from contracts/credit-manager/src/liquidate.rs rename to contracts/credit-manager/src/liquidate_coin.rs index fce670ba4..f5bbd96b4 100644 --- a/contracts/credit-manager/src/liquidate.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -1,6 +1,6 @@ use std::ops::{Add, Div}; -use cosmwasm_std::{Coin, Decimal, Deps, DepsMut, Env, Response, StdError}; +use cosmwasm_std::{Coin, Decimal, Deps, DepsMut, Env, Response, StdError, Uint128}; use mars_health::health::Health; use rover::error::{ContractError, ContractResult}; @@ -20,22 +20,17 @@ pub fn liquidate_coin( debt_coin: Coin, request_coin_denom: &str, ) -> ContractResult { - // Assert the liquidatee's credit account is liquidatable - let health = compute_health(deps.as_ref(), &env, liquidatee_account_id)?; - if !health.is_liquidatable() { - return Err(ContractError::NotLiquidatable { - account_id: liquidatee_account_id.to_string(), - lqdt_health_factor: val_or_na(health.liquidation_health_factor), - }); - } + let request_coin_balance = COIN_BALANCES + .load(deps.storage, (liquidatee_account_id, request_coin_denom)) + .map_err(|_| ContractError::CoinNotAvailable(request_coin_denom.to_string()))?; - let (debt, request) = calculate_liquidation_request( + let (health, debt, request) = calculate_liquidation( &deps, &env, liquidatee_account_id, &debt_coin, request_coin_denom, - &health, + request_coin_balance, )?; // Transfer debt coin from liquidator's coin balance to liquidatee @@ -62,7 +57,7 @@ pub fn liquidate_coin( Ok(Response::new() .add_message(repay_msg) .add_message(assert_healthier_msg) - .add_attribute("action", "rover/credit_manager/liquidate") + .add_attribute("action", "rover/credit_manager/liquidate_coin") .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("debt_repaid_denom", debt.denom) .add_attribute("debt_repaid_amount", debt.amount) @@ -75,14 +70,24 @@ pub fn liquidate_coin( /// - Exceeds liquidatee's total debt for denom /// - Not enough liquidatee request coin balance to match /// - The value of the debt repaid exceeds the maximum close factor % -fn calculate_liquidation_request( +/// Returns -> (Health, Debt Coin, Request Coin) +pub fn calculate_liquidation( deps: &DepsMut, env: &Env, liquidatee_account_id: &str, debt_coin: &Coin, request_coin: &str, - health: &Health, -) -> ContractResult<(Coin, Coin)> { + request_coin_balance: Uint128, +) -> ContractResult<(Health, Coin, Coin)> { + // Assert the liquidatee's credit account is liquidatable + let health = compute_health(deps.as_ref(), env, liquidatee_account_id)?; + if !health.is_liquidatable() { + return Err(ContractError::NotLiquidatable { + account_id: liquidatee_account_id.to_string(), + lqdt_health_factor: val_or_na(health.liquidation_health_factor), + }); + } + // Ensure debt repaid does not exceed liquidatee's total debt for denom let (total_debt_amount, _) = current_debt_for_denom(deps.as_ref(), env, liquidatee_account_id, debt_coin)?; @@ -96,13 +101,10 @@ fn calculate_liquidation_request( // Calculate the maximum debt possible to repay given liquidatee's request coin balance // FORMULA: debt amount = request value / (1 + liquidation bonus %) / debt price - let liquidatee_balance = COIN_BALANCES - .load(deps.storage, (liquidatee_account_id, request_coin)) - .map_err(|_| ContractError::CoinNotAvailable(request_coin.to_string()))?; let request_res = oracle.query_price(&deps.querier, request_coin)?; let max_request_value = request_res .price - .checked_mul(liquidatee_balance.to_dec()?)?; + .checked_mul(request_coin_balance.to_dec()?)?; let liq_bonus_rate = MAX_LIQUIDATION_BONUS.load(deps.storage)?; let request_coin_adjusted_max_debt = max_request_value .div(liq_bonus_rate.add(Decimal::one())) @@ -121,15 +123,15 @@ fn calculate_liquidation_request( // Calculate exact request coin amount to give to liquidator // FORMULA: request amount = (1 + liquidation bonus %) * debt value / request coin price - let debt_amount_dec = final_debt_to_repay.to_dec()?; let request_amount = liq_bonus_rate .add(Decimal::one()) - .checked_mul(debt_res.price.checked_mul(debt_amount_dec)?)? + .checked_mul(debt_res.price.checked_mul(final_debt_to_repay.to_dec()?)?)? .div(request_res.price) .uint128(); - // (Debt Coin, Request Coin) + // (Health, Debt Coin, Request Coin) Ok(( + health, Coin { denom: debt_coin.denom.clone(), amount: final_debt_to_repay, diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 3fb84dd01..a6d395774 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -4,6 +4,7 @@ use std::hash::Hash; use rover::error::{ContractError, ContractResult}; use rover::msg::query::CoinValue; +use rover::traits::IntoDecimal; use crate::state::{ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES}; @@ -84,8 +85,7 @@ pub fn debt_shares_to_amount( pub fn coin_value(deps: &Deps, coin: &Coin) -> ContractResult { let oracle = ORACLE.load(deps.storage)?; let res = oracle.query_price(&deps.querier, &coin.denom)?; - let decimal_amount = Decimal::from_atomics(coin.amount, 0)?; - let value = res.price.checked_mul(decimal_amount)?; + let value = res.price.checked_mul(coin.amount.to_dec()?)?; Ok(CoinValue { denom: coin.denom.clone(), amount: coin.amount, diff --git a/contracts/credit-manager/src/vault/deposit.rs b/contracts/credit-manager/src/vault/deposit.rs index 2a2ee92cd..fafbbdfd0 100644 --- a/contracts/credit-manager/src/vault/deposit.rs +++ b/contracts/credit-manager/src/vault/deposit.rs @@ -2,11 +2,10 @@ use cosmwasm_std::{ to_binary, Addr, Coin, CosmosMsg, DepsMut, QuerierWrapper, Response, Uint128, WasmMsg, }; -use rover::adapters::{Vault, VaultPositionUpdate}; +use rover::adapters::{UpdateType, Vault, VaultPositionUpdate}; use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg; -use rover::traits::Stringify; use crate::utils::{assert_coins_are_whitelisted, contents_equal, decrement_coin_balance}; use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; @@ -67,8 +66,14 @@ pub fn update_vault_coin_balance( account_id, &vault.address, match vault_info.lockup { - None => VaultPositionUpdate::IncrementUnlocked(diff), - Some(_) => VaultPositionUpdate::IncrementLocked(diff), + None => VaultPositionUpdate::Unlocked { + amount: diff, + kind: UpdateType::Increment, + }, + Some(_) => VaultPositionUpdate::Locked { + amount: diff, + kind: UpdateType::Increment, + }, }, )?; @@ -87,23 +92,14 @@ pub fn assert_denoms_match_vault_reqs( ) -> ContractResult<()> { let vault_info = vault.query_info(&querier)?; - // Check if coins match one of the accepted combinations for vault - let denoms = coins.iter().map(|c| c.denom.clone()).collect::>(); - let matched_combo = vault_info - .accepts - .iter() - .any(|combo| contents_equal(combo, &denoms)); + let given_denoms = coins.iter().map(|c| c.denom.clone()).collect::>(); + let fulfills_reqs = contents_equal(&vault_info.accepts, &given_denoms); - if !matched_combo { + if !fulfills_reqs { return Err(ContractError::RequirementsNotMet(format!( "Required assets: {} -- do not match given assets: {}", - vault_info - .accepts - .iter() - .map(|v| v.join(", ")) - .collect::>() - .join(" or "), - coins.to_string() + vault_info.accepts.join(", "), + given_denoms.join(", ") ))); } Ok(()) diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs new file mode 100644 index 000000000..e52f40dfd --- /dev/null +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -0,0 +1,164 @@ +use cosmwasm_std::{ + to_binary, Coin, CosmosMsg, DepsMut, Env, QuerierWrapper, Response, Storage, Uint128, WasmMsg, +}; +use std::cmp::min; + +use rover::adapters::{UpdateType, Vault, VaultPositionState, VaultPositionUpdate}; +use rover::error::ContractResult; +use rover::msg::execute::CallbackMsg; +use rover::msg::vault::VaultInfo; +use rover::msg::ExecuteMsg; +use rover::traits::Denoms; + +use crate::liquidate_coin::calculate_liquidation; +use crate::state::VAULT_POSITIONS; +use crate::update_coin_balances::query_balances; +use crate::utils::{decrement_coin_balance, increment_coin_balance}; +use crate::vault::update_vault_position; + +pub fn liquidate_vault( + deps: DepsMut, + env: Env, + liquidator_account_id: &str, + liquidatee_account_id: &str, + debt_coin: Coin, + request_vault: Vault, +) -> ContractResult { + let vault_info = request_vault.query_info(&deps.querier)?; + let liquidatee_position = VAULT_POSITIONS.load( + deps.storage, + (liquidatee_account_id, request_vault.address.clone()), + )?; + let (_health, debt, request) = calculate_liquidation( + &deps, + &env, + liquidatee_account_id, + &debt_coin, + &vault_info.token_denom, + liquidatee_position.total()?, + )?; + + // Transfer debt coin from liquidator's coin balance to liquidatee + // Will be used to pay off the debt via CallbackMsg::Repay {} + decrement_coin_balance(deps.storage, liquidator_account_id, &debt)?; + increment_coin_balance(deps.storage, liquidatee_account_id, &debt)?; + let repay_msg = (CallbackMsg::Repay { + account_id: liquidatee_account_id.to_string(), + coin: debt.clone(), + }) + .into_cosmos_msg(&env.contract.address)?; + + let vault_withdraw_msgs = get_vault_withdraw_msgs( + deps.storage, + &deps.querier, + liquidatee_account_id, + &request_vault, + &vault_info, + &liquidatee_position, + request.amount, + )?; + + // Update coin balances of liquidator after withdraws have been made + let coins_from_withdraw = request_vault.query_preview_redeem(&deps.querier, request.amount)?; + let previous_balances = query_balances( + deps.as_ref(), + &env.contract.address, + &coins_from_withdraw.to_denoms(), + )?; + let update_coin_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalances { + account_id: liquidator_account_id.to_string(), + previous_balances, + }))?, + }); + + // TODO: Reviewing with Davide on whether this is necessary + // // Ensure health factor has improved as a consequence of liquidation event + // let assert_healthier_msg = (CallbackMsg::AssertHealthFactorImproved { + // account_id: liquidatee_account_id.to_string(), + // previous_health_factor: health.liquidation_health_factor.unwrap(), + // }) + // .into_cosmos_msg(&env.contract.address)?; + + Ok(Response::new() + .add_message(repay_msg) + .add_messages(vault_withdraw_msgs) + .add_message(update_coin_balance_msg) + // .add_message(assert_healthier_msg) + .add_attribute("action", "rover/credit_manager/liquidate_vault") + .add_attribute("liquidatee_account_id", liquidatee_account_id) + .add_attribute("debt_repaid_denom", debt.denom) + .add_attribute("debt_repaid_amount", debt.amount) + .add_attribute("vault_coin_denom", request.denom) + .add_attribute("vault_coin_liquidated", request.amount)) +} + +/// Generates Cosmos msgs for Vault withdraws & updates Rover credit account balances +fn get_vault_withdraw_msgs( + storage: &mut dyn Storage, + querier: &QuerierWrapper, + liquidatee_account_id: &str, + request_vault: &Vault, + vault_info: &VaultInfo, + liquidatee_position: &VaultPositionState, + amount: Uint128, +) -> ContractResult> { + let mut total_to_liquidate = amount; + let mut vault_withdraw_msgs = vec![]; + + // No vault lockup indicates it's an unlocked vault. Should liquidate from the UNLOCKED bucket. + if vault_info.lockup.is_none() { + update_vault_position( + storage, + liquidatee_account_id, + &request_vault.address, + VaultPositionUpdate::Unlocked { + amount: total_to_liquidate, + kind: UpdateType::Decrement, + }, + )?; + + let msg = request_vault.withdraw_msg(querier, total_to_liquidate, false)?; + vault_withdraw_msgs.push(msg); + } else { + // A locking vault can have two different positions: LOCKED & UNLOCKING + // Priority goes to force withdrawing the unlocking buckets + for u in &liquidatee_position.unlocking { + let amount = min(u.amount, total_to_liquidate); + update_vault_position( + storage, + liquidatee_account_id, + &request_vault.address, + VaultPositionUpdate::Unlocking { + id: u.id, + amount, + kind: UpdateType::Decrement, + }, + )?; + + let msg = request_vault.force_withdraw_unlocking_msg(u.id, Some(amount))?; + vault_withdraw_msgs.push(msg); + + total_to_liquidate = total_to_liquidate.checked_sub(amount)?; + } + + // If unlocking positions have been exhausted, liquidate from LOCKED bucket + if !total_to_liquidate.is_zero() { + update_vault_position( + storage, + liquidatee_account_id, + &request_vault.address, + VaultPositionUpdate::Locked { + amount: total_to_liquidate, + kind: UpdateType::Decrement, + }, + )?; + + let msg = request_vault.withdraw_msg(querier, total_to_liquidate, true)?; + vault_withdraw_msgs.push(msg); + } + } + Ok(vault_withdraw_msgs) +} diff --git a/contracts/credit-manager/src/vault/mod.rs b/contracts/credit-manager/src/vault/mod.rs index 527c24051..af63ebe2b 100644 --- a/contracts/credit-manager/src/vault/mod.rs +++ b/contracts/credit-manager/src/vault/mod.rs @@ -1,10 +1,12 @@ pub use self::deposit::*; +pub use self::liquidate_vault::*; pub use self::request_unlock::*; pub use self::utils::*; pub use self::withdraw::*; pub use self::withdraw_unlocked::*; mod deposit; +mod liquidate_vault; mod request_unlock; mod utils; mod withdraw; diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index 83ad39009..ed0f9c333 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, DepsMut, Reply, Response, Uint128}; use crate::state::VAULT_REQUEST_TEMP_STORAGE; -use rover::adapters::{Vault, VaultPositionUpdate, VaultUnlockingId}; +use rover::adapters::{UpdateType, Vault, VaultPositionUpdate}; use rover::error::{ContractError, ContractResult}; use rover::extensions::AttrParse; @@ -38,7 +38,7 @@ pub fn request_unlock_from_vault( )?; let request_unlock_msg = vault.request_unlock_msg(&[Coin { - denom: vault_info.vault_coin_denom, + denom: vault_info.token_denom, amount, }])?; @@ -58,17 +58,21 @@ pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResul deps.storage, &account_id, &vault_addr, - VaultPositionUpdate::AddUnlocking(VaultUnlockingId { + VaultPositionUpdate::Unlocking { id: unlock_event.id, amount, - }), + kind: UpdateType::Increment, + }, )?; update_vault_position( deps.storage, &account_id, &vault_addr, - VaultPositionUpdate::DecrementLocked(amount), + VaultPositionUpdate::Locked { + amount, + kind: UpdateType::Decrement, + }, )?; VAULT_REQUEST_TEMP_STORAGE.remove(deps.storage); diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 8fed59762..8c7ee638f 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,9 +1,15 @@ -use cosmwasm_std::{Addr, Coin, Deps, StdResult, Storage}; +use cosmwasm_std::{ + coin, to_binary, Addr, Coin, Deps, QueryRequest, StdResult, Storage, WasmQuery, +}; -use rover::adapters::{Vault, VaultPosition, VaultPositionState, VaultPositionUpdate}; +use mars_oracle_adapter::msg::QueryMsg::PriceableUnderlying; +use rover::adapters::{ + UpdateType, Vault, VaultPosition, VaultPositionState, VaultPositionUpdate, + VaultUnlockingPosition, +}; use rover::error::{ContractError, ContractResult}; -use crate::state::{ALLOWED_VAULTS, VAULT_POSITIONS}; +use crate::state::{ALLOWED_VAULTS, ORACLE, VAULT_POSITIONS}; use crate::update_coin_balances::query_balances; pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> ContractResult<()> { @@ -24,22 +30,38 @@ pub fn update_vault_position( let mut new_position = path.may_load(storage)?.unwrap_or_default(); match update { - VaultPositionUpdate::DecrementUnlocked(amount) => { - new_position.unlocked = new_position.unlocked.checked_sub(amount)?; - } - VaultPositionUpdate::IncrementUnlocked(amount) => { - new_position.unlocked = new_position.unlocked.checked_add(amount)?; - } - VaultPositionUpdate::DecrementLocked(amount) => { - new_position.locked = new_position.locked.checked_sub(amount)?; - } - VaultPositionUpdate::IncrementLocked(amount) => { - new_position.locked = new_position.locked.checked_add(amount)?; - } - VaultPositionUpdate::AddUnlocking(position) => { - new_position.unlocking.push(position); - } - VaultPositionUpdate::RemoveUnlocking(id) => new_position.unlocking.retain(|p| p.id != id), + VaultPositionUpdate::Unlocked { amount, kind } => match kind { + UpdateType::Increment => { + new_position.unlocked = new_position.unlocked.checked_add(amount)?; + } + UpdateType::Decrement => { + new_position.unlocked = new_position.unlocked.checked_sub(amount)?; + } + }, + VaultPositionUpdate::Locked { amount, kind } => match kind { + UpdateType::Increment => { + new_position.locked = new_position.locked.checked_add(amount)?; + } + UpdateType::Decrement => { + new_position.locked = new_position.locked.checked_sub(amount)?; + } + }, + VaultPositionUpdate::Unlocking { id, amount, kind } => match kind { + UpdateType::Increment => { + new_position + .unlocking + .push(VaultUnlockingPosition { id, amount }); + } + UpdateType::Decrement => { + let mut matching_unlock = get_unlocking_position(id, &new_position)?; + matching_unlock.amount = matching_unlock.amount.checked_sub(amount)?; + + new_position.unlocking.retain(|p| p.id != id); + if !matching_unlock.amount.is_zero() { + new_position.unlocking.push(matching_unlock); + } + } + }, } if new_position == VaultPositionState::default() { @@ -50,8 +72,7 @@ pub fn update_vault_position( Ok(new_position) } -/// Returns the denoms you may receive on a withdraw -/// Inferred by vault entry requirements +/// Returns the denoms received on a withdraw, inferred by vault entry requirements pub fn query_withdraw_denom_balances( deps: Deps, rover_addr: &Addr, @@ -61,20 +82,38 @@ pub fn query_withdraw_denom_balances( let denoms = vault_info .accepts .iter() - .flat_map(|v| v.iter().map(|s| s.as_str())) + .map(String::as_str) .collect::>(); - query_balances(deps, rover_addr, denoms.as_slice()) + query_balances(deps, rover_addr, &denoms) } -/// Does a simulated withdraw from multiple vault positions to see what assets would be returned -pub fn simulate_withdraw(deps: &Deps, positions: &[VaultPosition]) -> ContractResult> { +/// Returns the mars-oracle accepted priceable coins +pub fn get_priceable_coins(deps: &Deps, positions: &[VaultPosition]) -> ContractResult> { + let oracle = ORACLE.load(deps.storage)?; let mut coins: Vec = vec![]; for p in positions { + let vault_info = p.vault.query_info(&deps.querier)?; let total_vault_coins = p.state.total()?; - let coins_if_withdrawn = p - .vault - .query_preview_redeem(&deps.querier, total_vault_coins)?; - coins.extend(coins_if_withdrawn) + let priceable_coins: Vec = + deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: oracle.address().to_string(), + msg: to_binary(&PriceableUnderlying { + coin: coin(total_vault_coins.u128(), vault_info.token_denom), + })?, + }))?; + coins.extend(priceable_coins) } Ok(coins) } + +pub fn get_unlocking_position( + position_id: u64, + vault_position: &VaultPositionState, +) -> ContractResult { + vault_position + .unlocking + .iter() + .find(|p| p.id == position_id) + .ok_or_else(|| ContractError::NoPositionMatch(position_id.to_string())) + .cloned() +} diff --git a/contracts/credit-manager/src/vault/withdraw.rs b/contracts/credit-manager/src/vault/withdraw.rs index 235499074..7c7ea0bca 100644 --- a/contracts/credit-manager/src/vault/withdraw.rs +++ b/contracts/credit-manager/src/vault/withdraw.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, Uint128, WasmMsg}; -use rover::adapters::{Vault, VaultPositionUpdate}; +use rover::adapters::{UpdateType, Vault, VaultPositionUpdate}; use rover::error::ContractResult; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg as RoverExecuteMsg; @@ -26,9 +26,15 @@ pub fn withdraw_from_vault( account_id, &vault.address, if force { - VaultPositionUpdate::DecrementLocked(amount) + VaultPositionUpdate::Locked { + amount, + kind: UpdateType::Decrement, + } } else { - VaultPositionUpdate::DecrementUnlocked(amount) + VaultPositionUpdate::Unlocked { + amount, + kind: UpdateType::Decrement, + } }, )?; diff --git a/contracts/credit-manager/src/vault/withdraw_unlocked.rs b/contracts/credit-manager/src/vault/withdraw_unlocked.rs index 092f30883..8a5679175 100644 --- a/contracts/credit-manager/src/vault/withdraw_unlocked.rs +++ b/contracts/credit-manager/src/vault/withdraw_unlocked.rs @@ -1,11 +1,13 @@ -use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, Uint128, WasmMsg}; +use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, WasmMsg}; -use rover::adapters::{Vault, VaultPositionUpdate}; +use rover::adapters::{UpdateType, Vault, VaultPositionUpdate}; use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; +use rover::msg::vault::UnlockingPosition; use rover::msg::ExecuteMsg; use crate::state::VAULT_POSITIONS; +use crate::vault::get_unlocking_position; use crate::vault::utils::{ assert_vault_is_whitelisted, query_withdraw_denom_balances, update_vault_position, }; @@ -15,21 +17,15 @@ pub fn withdraw_unlocked_from_vault( env: Env, account_id: &str, vault: Vault, - position_id: Uint128, + position_id: u64, ) -> ContractResult { assert_vault_is_whitelisted(deps.storage, &vault)?; let vault_position = VAULT_POSITIONS.load(deps.storage, (account_id, vault.address.clone()))?; - - let matching_unlock = vault_position - .unlocking - .iter() - .find(|p| p.id == position_id) - .ok_or_else(|| ContractError::NoPositionMatch(position_id.to_string()))?; - - let matching_unlock = vault.query_unlocking_position_info(&deps.querier, matching_unlock.id)?; - - if matching_unlock.unlocked_at > env.block.time { + let matching_unlock = get_unlocking_position(position_id, &vault_position)?; + let UnlockingPosition { unlocked_at, .. } = + vault.query_unlocking_position_info(&deps.querier, matching_unlock.id)?; + if unlocked_at > env.block.time { return Err(ContractError::UnlockNotReady {}); } @@ -37,7 +33,11 @@ pub fn withdraw_unlocked_from_vault( deps.storage, account_id, &vault.address, - VaultPositionUpdate::RemoveUnlocking(position_id), + VaultPositionUpdate::Unlocking { + id: position_id, + amount: matching_unlock.amount, + kind: UpdateType::Decrement, + }, )?; // Updates coin balances for account after the withdraw has taken place diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index 9c87a39c3..ca06bde21 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -1,53 +1,70 @@ use cosmwasm_std::Empty; use cw_multi_test::{App, Contract, ContractWrapper}; -use account_nft::contract::{ - execute as cw721Execute, instantiate as cw721Instantiate, query as cw721Query, -}; -use credit_manager::contract::{execute, instantiate, query, reply}; -use mock_oracle::contract::{ - execute as oracleExecute, instantiate as oracleInstantiate, query as oracleQuery, -}; -use mock_red_bank::contract::{ - execute as redBankExecute, instantiate as redBankInstantiate, query as redBankQuery, -}; -use mock_vault::contract::{ - execute as vaultExecute, instantiate as vaultInstantiate, query as vaultQuery, -}; -use swapper_mock::contract::{ - execute as swapperExecute, instantiate as swapperInstantiate, query as swapperQuery, -}; - pub fn mock_app() -> App { App::default() } pub fn mock_rover_contract() -> Box> { - let contract = ContractWrapper::new(execute, instantiate, query).with_reply(reply); + let contract = ContractWrapper::new( + credit_manager::contract::execute, + credit_manager::contract::instantiate, + credit_manager::contract::query, + ) + .with_reply(credit_manager::contract::reply); Box::new(contract) } pub fn mock_account_nft_contract() -> Box> { - let contract = ContractWrapper::new(cw721Execute, cw721Instantiate, cw721Query); + let contract = ContractWrapper::new( + account_nft::contract::execute, + account_nft::contract::instantiate, + account_nft::contract::query, + ); Box::new(contract) } pub fn mock_red_bank_contract() -> Box> { - let contract = ContractWrapper::new(redBankExecute, redBankInstantiate, redBankQuery); + let contract = ContractWrapper::new( + mock_red_bank::contract::execute, + mock_red_bank::contract::instantiate, + mock_red_bank::contract::query, + ); Box::new(contract) } pub fn mock_oracle_contract() -> Box> { - let contract = ContractWrapper::new(oracleExecute, oracleInstantiate, oracleQuery); + let contract = ContractWrapper::new( + mock_oracle::contract::execute, + mock_oracle::contract::instantiate, + mock_oracle::contract::query, + ); + Box::new(contract) +} + +pub fn mock_oracle_adapter_contract() -> Box> { + let contract = ContractWrapper::new( + mars_oracle_adapter::contract::execute, + mars_oracle_adapter::contract::instantiate, + mars_oracle_adapter::contract::query, + ); Box::new(contract) } pub fn mock_vault_contract() -> Box> { - let contract = ContractWrapper::new(vaultExecute, vaultInstantiate, vaultQuery); + let contract = ContractWrapper::new( + mock_vault::contract::execute, + mock_vault::contract::instantiate, + mock_vault::contract::query, + ); Box::new(contract) } pub fn mock_swapper_contract() -> Box> { - let contract = ContractWrapper::new(swapperExecute, swapperInstantiate, swapperQuery); + let contract = ContractWrapper::new( + swapper_mock::contract::execute, + swapper_mock::contract::instantiate, + swapper_mock::contract::query, + ); Box::new(contract) } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index a782cc94f..6e47805c2 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -7,6 +7,9 @@ use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use account_nft::msg::ExecuteMsg as NftExecuteMsg; +use mars_oracle_adapter::msg::{ + InstantiateMsg as OracleAdapterInstantiateMsg, PricingMethod, VaultPricingInfo, +}; use mars_outpost::red_bank::QueryMsg::UserDebt; use mars_outpost::red_bank::UserDebtResponse; use mock_oracle::msg::{ @@ -26,13 +29,14 @@ use rover::msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtShares, HealthResponse, Positions, SharesResponseItem, VaultPositionResponseItem, VaultWithBalance, }; -use rover::msg::vault::QueryMsg::UnlockingPositionsForAddr; -use rover::msg::vault::UnlockingPosition; +use rover::msg::vault::QueryMsg::{Info as VaultInfoMsg, UnlockingPositionsForAddr}; +use rover::msg::vault::{UnlockingPosition, VaultInfo}; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::helpers::{ - mock_account_nft_contract, mock_oracle_contract, mock_red_bank_contract, mock_rover_contract, - mock_swapper_contract, mock_vault_contract, AccountToFund, CoinInfo, VaultTestInfo, + mock_account_nft_contract, mock_oracle_adapter_contract, mock_oracle_contract, + mock_red_bank_contract, mock_rover_contract, mock_swapper_contract, mock_vault_contract, + AccountToFund, CoinInfo, VaultTestInfo, }; pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000); @@ -40,6 +44,7 @@ pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000); pub struct MockEnv { pub app: BasicApp, pub rover: Addr, + pub mars_oracle: Addr, } pub struct MockEnvBuilder { @@ -49,6 +54,7 @@ pub struct MockEnvBuilder { pub pre_deployed_vaults: Option>, pub allowed_coins: Option>, pub oracle: Option>, + pub oracle_adapter: Option>, pub red_bank: Option>, pub deploy_nft_contract: bool, pub set_nft_contract_owner: bool, @@ -67,6 +73,7 @@ impl MockEnv { pre_deployed_vaults: None, allowed_coins: None, oracle: None, + oracle_adapter: None, red_bank: None, deploy_nft_contract: true, set_nft_contract_owner: true, @@ -159,11 +166,10 @@ impl MockEnv { } pub fn price_change(&mut self, coin: CoinPrice) { - let config = self.query_config(); self.app .execute_contract( Addr::unchecked("anyone"), - Addr::unchecked(config.oracle), + self.mars_oracle.clone(), &OracleExecuteMsg::ChangePrice(coin), &[], ) @@ -241,7 +247,7 @@ impl MockEnv { .unwrap() .query_info(&self.app.wrap()) .unwrap(); - vault.denom == info.vault_coin_denom + vault.denom == info.token_denom }) .unwrap() .clone() @@ -338,7 +344,7 @@ impl MockEnv { pub fn query_unlocking_position_info( &self, vault: &VaultUnchecked, - id: Uint128, + id: u64, ) -> UnlockingPosition { vault .check(&MockApi::default()) @@ -425,12 +431,14 @@ impl MockEnv { impl MockEnvBuilder { pub fn build(&mut self) -> AnyResult { let rover = self.get_rover()?; + let mars_oracle = self.get_oracle(); self.deploy_nft_contract(&rover); self.fund_users(); Ok(MockEnv { app: take(&mut self.app), rover, + mars_oracle: mars_oracle.address().clone(), }) } @@ -489,7 +497,6 @@ impl MockEnvBuilder { fn get_rover(&mut self) -> AnyResult { let code_id = self.app.store_code(mock_rover_contract()); - let oracle = self.get_oracle().into(); let red_bank = self.get_red_bank().into(); let swapper = self.deploy_swapper().into(); let allowed_coins = self @@ -504,6 +511,8 @@ impl MockEnvBuilder { allowed_vaults.extend(self.deploy_vaults()); allowed_vaults.extend(self.pre_deployed_vaults.clone().unwrap_or_default()); + let oracle = self.get_oracle_adapter(allowed_vaults.clone()).into(); + self.app.instantiate_contract( code_id, self.get_owner(), @@ -562,6 +571,55 @@ impl MockEnvBuilder { OracleBase::new(addr) } + fn get_oracle_adapter(&mut self, vaults: Vec) -> OracleBase { + if self.oracle_adapter.is_none() { + let addr = self.deploy_oracle_adapter(vaults); + self.oracle_adapter = Some(addr); + } + self.oracle_adapter.clone().unwrap() + } + + fn deploy_oracle_adapter(&mut self, vaults: Vec) -> OracleBase { + let owner = Addr::unchecked("oracle_adapter_contract_owner"); + let contract_code_id = self.app.store_code(mock_oracle_adapter_contract()); + let oracle = self.get_oracle().into(); + let vault_pricing = if self.pre_deployed_vaults.is_some() { + vec![] + } else { + vaults + .into_iter() + .map(|v| { + let info: VaultInfo = self + .app + .wrap() + .query_wasm_smart(v.address.clone(), &VaultInfoMsg {}) + .unwrap(); + VaultPricingInfo { + denom: info.token_denom, + addr: Addr::unchecked(v.address), + method: PricingMethod::PreviewRedeem, + } + }) + .collect() + }; + let addr = self + .app + .instantiate_contract( + contract_code_id, + owner.clone(), + &OracleAdapterInstantiateMsg { + oracle, + vault_pricing, + owner: owner.to_string(), + }, + &[], + "mars-oracle-adapter", + None, + ) + .unwrap(); + OracleBase::new(addr) + } + fn get_red_bank(&mut self) -> RedBankBase { if self.red_bank.is_none() { let addr = self.deploy_red_bank(); @@ -725,8 +783,8 @@ impl MockEnvBuilder { self } - pub fn oracle(&mut self, oracle: &str) -> &mut Self { - self.oracle = Some(OracleBase::new(Addr::unchecked(oracle))); + pub fn oracle_adapter(&mut self, addr: &str) -> &mut Self { + self.oracle_adapter = Some(OracleBase::new(Addr::unchecked(addr))); self } diff --git a/contracts/credit-manager/tests/helpers/utils.rs b/contracts/credit-manager/tests/helpers/utils.rs index 36cef8c57..adb3a1bdb 100644 --- a/contracts/credit-manager/tests/helpers/utils.rs +++ b/contracts/credit-manager/tests/helpers/utils.rs @@ -1,5 +1,14 @@ use cosmwasm_std::Coin; +use rover::msg::query::DebtAmount; pub fn get_coin(denom: &str, coins: &[Coin]) -> Coin { coins.iter().find(|cv| cv.denom == denom).unwrap().clone() } + +pub fn get_debt(denom: &str, coins: &[DebtAmount]) -> DebtAmount { + coins + .iter() + .find(|coin| coin.denom.as_str() == denom) + .unwrap() + .clone() +} diff --git a/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs b/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs index a99cd7fa5..ff3841b1a 100644 --- a/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs +++ b/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs @@ -42,7 +42,7 @@ fn test_pagination_on_allowed_vaults_query_works() { .chain(vaults_res_d.iter().cloned()) .map(|v| v.check(&MockApi::default()).unwrap()) .map(|v| v.query_info(&mock.app.wrap()).unwrap()) - .map(|info| info.vault_coin_denom) + .map(|info| info.token_denom) .collect::>(); assert_eq!(combined.len(), allowed_vaults.len()); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index 880f35ec8..2d1b29a38 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -119,7 +119,7 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { .chain(vaults_res_c.iter().cloned()) .chain(vaults_res_d.iter().cloned()) .map(|v| v.vault.query_info(&mock.app.wrap()).unwrap()) - .map(|info| info.vault_coin_denom) + .map(|info| info.token_denom) .collect::>(); assert_eq!(combined.len(), all_vaults.len()); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index 4ba2e950e..cb5d273d7 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -132,7 +132,7 @@ fn test_pagination_on_all_vault_positions_query_works() { .chain(vaults_res_c.iter().cloned()) .chain(vaults_res_d.iter().cloned()) .map(|v| v.position.vault.query_info(&mock.app.wrap()).unwrap()) - .map(|info| info.vault_coin_denom) + .map(|info| info.token_denom) .collect::>(); let deduped = combined.iter().unique().cloned().collect::>(); diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 8a9024672..945f9231d 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -120,15 +120,18 @@ fn test_raises_on_invalid_red_bank_addr() { #[test] fn test_oracle_set_on_instantiate() { - let oracle_contract = "oracle_contract_456".to_string(); - let mock = MockEnv::new().oracle(&oracle_contract).build().unwrap(); + let oracle_adapter_contract = "oracle_contract_456".to_string(); + let mock = MockEnv::new() + .oracle_adapter(&oracle_adapter_contract) + .build() + .unwrap(); let res = mock.query_config(); - assert_eq!(oracle_contract, res.oracle); + assert_eq!(oracle_adapter_contract, res.oracle); } #[test] fn test_raises_on_invalid_oracle_addr() { - let mock = MockEnv::new().oracle("%%%INVALID%%%").build(); + let mock = MockEnv::new().oracle_adapter("%%%INVALID%%%").build(); if mock.is_ok() { panic!("Should have thrown an error"); } diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 358735067..ef86ccd93 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -4,11 +4,11 @@ use mock_oracle::msg::CoinPrice; use rover::error::ContractError; use rover::error::ContractError::{AboveMaxLTV, NotLiquidatable}; use rover::msg::execute::Action::{Borrow, Deposit, LiquidateCoin, VaultDeposit}; -use rover::msg::query::DebtAmount; use rover::traits::IntoDecimal; use crate::helpers::{ - assert_err, get_coin, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, VaultTestInfo, + assert_err, get_coin, get_debt, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, + VaultTestInfo, }; pub mod helpers; @@ -99,14 +99,14 @@ fn test_vault_positions_contribute_to_health() { &liquidatee, vec![ Deposit(uatom_info.to_coin(200)), - Deposit(uosmo_info.to_coin(400)), + Deposit(uosmo_info.to_coin(401)), VaultDeposit { vault, coins: vec![coin(200, "uatom"), coin(400, "uosmo")], }, Borrow(uatom_info.to_coin(14)), ], - &[coin(200, "uatom"), coin(400, "uosmo")], + &[coin(200, "uatom"), coin(401, "uosmo")], ) .unwrap(); @@ -131,7 +131,7 @@ fn test_vault_positions_contribute_to_health() { res, NotLiquidatable { account_id: liquidatee_account_id, - lqdt_health_factor: "18.04".to_string(), + lqdt_health_factor: "18.053".to_string(), }, ) } @@ -763,14 +763,3 @@ fn test_debt_amount_no_adjustment() { // - Withdraw #[test] fn test_liquidate_with_no_deposited_funds() {} - -#[test] -fn test_liquidate_with_vault_position() {} - -fn get_debt(denom: &str, coins: &[DebtAmount]) -> DebtAmount { - coins - .iter() - .find(|coin| coin.denom.as_str() == denom) - .unwrap() - .clone() -} diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs new file mode 100644 index 000000000..f4edc2a59 --- /dev/null +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -0,0 +1,614 @@ +use cosmwasm_std::OverflowOperation::Sub; +use cosmwasm_std::StdError::NotFound; +use cosmwasm_std::{Addr, OverflowError, Uint128}; + +use mock_oracle::msg::CoinPrice; +use rover::adapters::VaultBase; +use rover::error::ContractError; +use rover::msg::execute::Action::{ + Borrow, Deposit, LiquidateVault, VaultDeposit, VaultRequestUnlock, +}; +use rover::traits::IntoDecimal; + +use crate::helpers::{ + assert_err, get_coin, get_debt, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, + VaultTestInfo, +}; + +pub mod helpers; + +// NOTE: Vault liquidation scenarios spreadsheet: +// https://docs.google.com/spreadsheets/d/1rXa_8eKbtp1wQ0Mm1Rny7QzSLsko9D7UQTtO7NrAssA/edit#gid=2127757089 + +#[test] +fn test_liquidatee_must_have_the_request_vault_position() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: None, + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], + }) + .build() + .unwrap(); + + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![Deposit(uatom.to_coin(200)), Deposit(uosmo.to_coin(400))], + &[uatom.to_coin(200), uosmo.to_coin(400)], + ) + .unwrap(); + + let liquidator = Addr::unchecked("liquidator"); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![LiquidateVault { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom.to_coin(10), + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + }], + &[], + ); + + assert_err( + res, + ContractError::Std(NotFound { + kind: "rover::adapters::vault::VaultPositionState".to_string(), + }), + ) +} + +#[test] +fn test_liquidatee_is_not_liquidatable() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: None, + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(uatom.to_coin(200)), + Deposit(uosmo.to_coin(400)), + VaultDeposit { + vault, + coins: vec![uatom.to_coin(200), uosmo.to_coin(400)], + }, + ], + &[uatom.to_coin(200), uosmo.to_coin(400)], + ) + .unwrap(); + + let liquidator = Addr::unchecked("liquidator"); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![LiquidateVault { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom.to_coin(10), + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + }], + &[], + ); + + assert_err( + res, + ContractError::NotLiquidatable { + account_id: liquidatee_account_id, + lqdt_health_factor: "n/a".to_string(), + }, + ) +} + +#[test] +fn test_liquidator_does_not_have_debt_coin_in_credit_account() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + let ujake = ujake_info(); + + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: None, + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone(), ujake.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(uatom.to_coin(200)), + Deposit(uosmo.to_coin(400)), + VaultDeposit { + vault, + coins: vec![uatom.to_coin(200), uosmo.to_coin(400)], + }, + Borrow(ujake.to_coin(175)), + ], + &[uatom.to_coin(200), uosmo.to_coin(400)], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: ujake.denom.clone(), + price: Uint128::new(20).to_dec().unwrap(), + }); + + let liquidator = Addr::unchecked("liquidator"); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![LiquidateVault { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: ujake.to_coin(10), + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + }], + &[], + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: Sub, + operand1: "0".to_string(), + operand2: "10".to_string(), + }), + ) +} + +#[test] +fn test_liquidate_unlocked_vault() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + let ujake = ujake_info(); + + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: None, + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let liquidatee = Addr::unchecked("liquidatee"); + let liquidator = Addr::unchecked("liquidator"); + + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone(), ujake.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: vec![ujake.to_coin(10)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(uatom.to_coin(200)), + Deposit(uosmo.to_coin(400)), + VaultDeposit { + vault, + coins: vec![uatom.to_coin(200), uosmo.to_coin(400)], + }, + Borrow(ujake.to_coin(175)), + ], + &[uatom.to_coin(200), uosmo.to_coin(400)], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: ujake.denom.clone(), + price: Uint128::new(20).to_dec().unwrap(), + }); + + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(ujake.to_coin(10)), + LiquidateVault { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: ujake.to_coin(10), + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + }, + ], + &[ujake.to_coin(10)], + ) + .unwrap(); + + // Assert liquidatee's new position + let position = mock.query_positions(&liquidatee_account_id); + assert_eq!(position.vaults.len(), 1); + let vault_balance = position.vaults.first().unwrap().state.unlocked; + assert_eq!(vault_balance, Uint128::new(300_000)); // Vault position liquidated by 70% + + assert_eq!(position.coins.len(), 1); + let jake_balance = get_coin("ujake", &position.coins); + assert_eq!(jake_balance.amount, Uint128::new(175)); + + assert_eq!(position.debts.len(), 1); + let atom_debt = get_debt("ujake", &position.debts); + assert_eq!(atom_debt.amount, Uint128::new(166)); + + // Assert liquidator's new position + let position = mock.query_positions(&liquidator_account_id); + assert_eq!(position.coins.len(), 2); + assert_eq!(position.debts.len(), 0); + let osmo_balance = get_coin("uosmo", &position.coins); + assert_eq!(osmo_balance.amount, Uint128::new(280)); + let atom_balance = get_coin("uatom", &position.coins); + assert_eq!(atom_balance.amount, Uint128::new(140)); +} + +#[test] +fn test_liquidate_locked_vault() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + let ujake = ujake_info(); + + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: Some(123021212), + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let liquidatee = Addr::unchecked("liquidatee"); + let liquidator = Addr::unchecked("liquidator"); + + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone(), ujake.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: vec![ujake.to_coin(10)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(uatom.to_coin(200)), + Deposit(uosmo.to_coin(400)), + VaultDeposit { + vault: vault.clone(), + coins: vec![uatom.to_coin(200), uosmo.to_coin(400)], + }, + Borrow(ujake.to_coin(175)), + VaultRequestUnlock { + vault, + amount: Uint128::new(100_000), + }, + ], + &[uatom.to_coin(200), uosmo.to_coin(400)], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: ujake.denom.clone(), + price: Uint128::new(20).to_dec().unwrap(), + }); + + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(ujake.to_coin(10)), + LiquidateVault { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: ujake.to_coin(10), + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + }, + ], + &[ujake.to_coin(10)], + ) + .unwrap(); + + // Assert liquidatee's new position + let position = mock.query_positions(&liquidatee_account_id); + assert_eq!(position.vaults.len(), 1); + let vault_state = position.vaults.first().unwrap().state.clone(); + // Vault position liquidated by 70%. Unlocking first, then locked bucket. + assert_eq!(vault_state.unlocking.len(), 0); + assert_eq!(vault_state.locked, Uint128::new(300_000)); + + assert_eq!(position.coins.len(), 1); + let jake_balance = get_coin("ujake", &position.coins); + assert_eq!(jake_balance.amount, Uint128::new(175)); + + assert_eq!(position.debts.len(), 1); + let atom_debt = get_debt("ujake", &position.debts); + assert_eq!(atom_debt.amount, Uint128::new(166)); + + // Assert liquidator's new position + let position = mock.query_positions(&liquidator_account_id); + assert_eq!(position.coins.len(), 2); + assert_eq!(position.debts.len(), 0); + let osmo_balance = get_coin("uosmo", &position.coins); + assert_eq!(osmo_balance.amount, Uint128::new(280)); + let atom_balance = get_coin("uatom", &position.coins); + assert_eq!(atom_balance.amount, Uint128::new(140)); +} + +#[test] +fn test_liquidate_unlocking_priority() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + let ujake = ujake_info(); + + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: Some(123021212), + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let liquidatee = Addr::unchecked("liquidatee"); + let liquidator = Addr::unchecked("liquidator"); + + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone(), ujake.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: vec![ujake.to_coin(10)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(uatom.to_coin(200)), + Deposit(uosmo.to_coin(400)), + VaultDeposit { + vault: vault.clone(), + coins: vec![uatom.to_coin(200), uosmo.to_coin(400)], + }, + Borrow(ujake.to_coin(175)), + VaultRequestUnlock { + vault: vault.clone(), + amount: Uint128::new(10_000), + }, + VaultRequestUnlock { + vault: vault.clone(), + amount: Uint128::new(200_000), + }, + VaultRequestUnlock { + vault, + amount: Uint128::new(700_000), + }, + ], + &[uatom.to_coin(200), uosmo.to_coin(400)], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: ujake.denom.clone(), + price: Uint128::new(20).to_dec().unwrap(), + }); + + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(ujake.to_coin(10)), + LiquidateVault { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: ujake.to_coin(10), + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + }, + ], + &[ujake.to_coin(10)], + ) + .unwrap(); + + // Assert liquidatee's new position + let position = mock.query_positions(&liquidatee_account_id); + assert_eq!(position.vaults.len(), 1); + let vault_state = position.vaults.first().unwrap().state.clone(); + assert_eq!(vault_state.unlocked, Uint128::zero()); + assert_eq!(vault_state.locked, Uint128::new(90_000)); + assert_eq!(vault_state.unlocking.len(), 1); + assert_eq!( + vault_state.unlocking.first().unwrap().amount, + Uint128::new(210_000) + ); + + assert_eq!(position.coins.len(), 1); + let jake_balance = get_coin("ujake", &position.coins); + assert_eq!(jake_balance.amount, Uint128::new(175)); + + assert_eq!(position.debts.len(), 1); + let atom_debt = get_debt("ujake", &position.debts); + assert_eq!(atom_debt.amount, Uint128::new(166)); + + // Assert liquidator's new position + let position = mock.query_positions(&liquidator_account_id); + assert_eq!(position.coins.len(), 2); + assert_eq!(position.debts.len(), 0); + let osmo_balance = get_coin("uosmo", &position.coins); + assert_eq!(osmo_balance.amount, Uint128::new(280)); + let atom_balance = get_coin("uatom", &position.coins); + assert_eq!(atom_balance.amount, Uint128::new(140)); +} + +// NOTE: liquidation calculation+adjustments are quite complex, full cases in test_liquidate_coin.rs +#[test] +fn test_liquidation_calculation_adjustment() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + let ujake = ujake_info(); + + let leverage_vault = VaultTestInfo { + denom: "uleverage".to_string(), + lockup: None, + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + }; + + let liquidatee = Addr::unchecked("liquidatee"); + let liquidator = Addr::unchecked("liquidator"); + + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone(), ujake.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: vec![ujake.to_coin(50)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(uatom.to_coin(200)), + Deposit(uosmo.to_coin(400)), + VaultDeposit { + vault, + coins: vec![uatom.to_coin(200), uosmo.to_coin(400)], + }, + Borrow(ujake.to_coin(175)), + ], + &[uatom.to_coin(200), uosmo.to_coin(400)], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: ujake.denom.clone(), + price: Uint128::new(20).to_dec().unwrap(), + }); + + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(ujake.to_coin(50)), + LiquidateVault { + liquidatee_account_id: liquidatee_account_id.clone(), + // Given the request vault balance, this debt payment is too high. + // It will be adjusted to 14, the max given the request vault value + debt_coin: ujake.to_coin(50), + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + }, + ], + &[ujake.to_coin(50)], + ) + .unwrap(); + + // Assert liquidatee's new position + let position = mock.query_positions(&liquidatee_account_id); + assert_eq!(position.vaults.len(), 1); + let vault_balance = position.vaults.first().unwrap().state.unlocked; + assert_eq!(vault_balance, Uint128::new(20_000)); // Vault position liquidated by 98% + + assert_eq!(position.coins.len(), 1); + let jake_balance = get_coin("ujake", &position.coins); + assert_eq!(jake_balance.amount, Uint128::new(175)); + + assert_eq!(position.debts.len(), 1); + let atom_debt = get_debt("ujake", &position.debts); + assert_eq!(atom_debt.amount, Uint128::new(162)); + + // Assert liquidator's new position + let position = mock.query_positions(&liquidator_account_id); + assert_eq!(position.coins.len(), 3); + let osmo_balance = get_coin("ujake", &position.coins); + assert_eq!(osmo_balance.amount, Uint128::new(36)); + let osmo_balance = get_coin("uosmo", &position.coins); + assert_eq!(osmo_balance.amount, Uint128::new(392)); + let atom_balance = get_coin("uatom", &position.coins); + assert_eq!(atom_balance.amount, Uint128::new(196)); + assert_eq!(position.debts.len(), 0); +} diff --git a/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs b/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs index 9c026ee96..692f44542 100644 --- a/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs +++ b/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs @@ -36,10 +36,7 @@ fn test_only_owner_can_withdraw_unlocked_for_account() { let res = mock.update_credit_account( &account_id, &bad_guy, - vec![VaultWithdrawUnlocked { - id: Uint128::new(423), - vault, - }], + vec![VaultWithdrawUnlocked { id: 423, vault }], &[], ); @@ -63,10 +60,7 @@ fn test_can_only_take_action_on_whitelisted_vaults() { let res = mock.update_credit_account( &account_id, &user, - vec![VaultWithdrawUnlocked { - id: Uint128::new(234), - vault, - }], + vec![VaultWithdrawUnlocked { id: 234, vault }], &[], ); diff --git a/contracts/mars-oracle-adapter/src/contract.rs b/contracts/mars-oracle-adapter/src/contract.rs index 6134c4e55..59e87b72d 100644 --- a/contracts/mars-oracle-adapter/src/contract.rs +++ b/contracts/mars-oracle-adapter/src/contract.rs @@ -2,8 +2,8 @@ use cosmwasm_std::entry_point; use cosmwasm_std::Order::Ascending; use cosmwasm_std::{ - to_binary, Addr, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, - Storage, + to_binary, Addr, Binary, Coin, Decimal, Deps, DepsMut, Env, MessageInfo, Order, Response, + StdResult, Storage, }; use cw_storage_plus::Bound; use mars_outpost::oracle::PriceResponse; @@ -57,6 +57,9 @@ pub fn execute( pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> ContractResult { let res = match msg { QueryMsg::Price { denom } => to_binary(&query_price(deps, &denom)?), + QueryMsg::PriceableUnderlying { coin } => { + to_binary(&query_priceable_underlying(deps, coin)?) + } QueryMsg::Config {} => to_binary(&query_config(deps)?), QueryMsg::PricingInfo { denom } => to_binary(&query_pricing_info(deps, &denom)?), QueryMsg::AllPricingInfo { start_after, limit } => { @@ -91,15 +94,30 @@ fn query_all_pricing_info( .collect::>>() } +fn query_priceable_underlying(deps: Deps, coin: Coin) -> ContractResult> { + let info_opt = VAULT_PRICING_INFO.may_load(deps.storage, &coin.denom)?; + match info_opt { + Some(info) => match info.method { + PricingMethod::PreviewRedeem => { + let vault = VaultBase::new(info.addr); + Ok(vault.query_preview_redeem(&deps.querier, coin.amount)?) + } + }, + _ => Ok(vec![coin]), + } +} + fn query_price(deps: Deps, denom: &str) -> ContractResult { let info_opt = VAULT_PRICING_INFO.may_load(deps.storage, denom)?; let oracle = ORACLE.load(deps.storage)?; match info_opt { - Some(info) if info.method == PricingMethod::PreviewRedeem => { - let vault = VaultBase::new(info.addr.clone()); - calculate_preview_redeem(&deps, &oracle, &info, &vault) - } + Some(info) => match info.method { + PricingMethod::PreviewRedeem => { + let vault = VaultBase::new(info.addr.clone()); + calculate_preview_redeem(&deps, &oracle, &info, &vault) + } + }, _ => Ok(oracle.query_price(&deps.querier, denom)?), } } diff --git a/contracts/mars-oracle-adapter/src/msg.rs b/contracts/mars-oracle-adapter/src/msg.rs index 8ec62ed4e..890a9c4cc 100644 --- a/contracts/mars-oracle-adapter/src/msg.rs +++ b/contracts/mars-oracle-adapter/src/msg.rs @@ -1,6 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Decimal}; - +use cosmwasm_std::{Addr, Coin, Decimal}; use rover::adapters::{Oracle, OracleUnchecked}; #[cw_serde] @@ -24,9 +23,14 @@ pub enum ExecuteMsg { #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { + /// If denom is vault coin, will retrieve priceable underlying before querying oracle #[returns(mars_outpost::oracle::PriceResponse)] Price { denom: String }, + /// Converts vault coin to the mars-oracle accepted priceable coins + #[returns(Vec)] + PriceableUnderlying { coin: Coin }, + #[returns(ConfigResponse)] Config {}, diff --git a/contracts/mars-oracle-adapter/tests/test_query_priceable_underlying.rs b/contracts/mars-oracle-adapter/tests/test_query_priceable_underlying.rs new file mode 100644 index 000000000..fc890872b --- /dev/null +++ b/contracts/mars-oracle-adapter/tests/test_query_priceable_underlying.rs @@ -0,0 +1,51 @@ +use cosmwasm_std::{coin, Coin, Uint128}; +use cw_multi_test::App; + +use mars_oracle_adapter::msg::QueryMsg; + +use crate::helpers::{instantiate_oracle_adapter, mock_vault_info}; + +pub mod helpers; + +#[test] +fn test_non_vault_coin_underlying() { + let mut app = App::default(); + let contract_addr = instantiate_oracle_adapter(&mut app); + + let coins: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.to_string(), + &QueryMsg::PriceableUnderlying { + coin: coin(100, "uosmo"), + }, + ) + .unwrap(); + + assert_eq!(coins.len(), 1); + assert_eq!(coins[0].denom, "uosmo".to_string()); + assert_eq!(coins[0].amount, Uint128::new(100)); +} + +#[test] +fn test_vault_coin_preview_redeem() { + let mut app = App::default(); + let contract_addr = instantiate_oracle_adapter(&mut app); + let vault_info = mock_vault_info(); + + let coins: Vec = app + .wrap() + .query_wasm_smart( + contract_addr.to_string(), + &QueryMsg::PriceableUnderlying { + coin: coin(1000, vault_info.vault_coin_denom), + }, + ) + .unwrap(); + + assert_eq!(coins.len(), 2); + assert_eq!(coins[0].denom, "uatom".to_string()); + assert_eq!(coins[0].amount, Uint128::new(32)); + assert_eq!(coins[1].denom, "uosmo".to_string()); + assert_eq!(coins[1].amount, Uint128::new(120)); +} diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index 693d8448b..5ae04b8cc 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -15,7 +15,7 @@ use crate::query::{ query_vault_coins_issued, query_vault_info, }; use crate::state::{ASSETS, CHAIN_BANK, LOCKUP_TIME, LP_TOKEN_DENOM, NEXT_UNLOCK_ID, ORACLE}; -use crate::unlock::{request_unlock, withdraw_unlocked}; +use crate::unlock::{request_unlock, withdraw_unlocked, withdraw_unlocking_force}; use crate::withdraw::{withdraw, withdraw_force}; pub const STARTING_VAULT_SHARES: Uint128 = Uint128::new(1_000_000); @@ -38,7 +38,7 @@ pub fn instantiate( ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; LP_TOKEN_DENOM.save(deps.storage, &msg.lp_token_denom)?; CHAIN_BANK.save(deps.storage, &DEFAULT_VAULT_TOKEN_PREFUND)?; - NEXT_UNLOCK_ID.save(deps.storage, &Uint128::new(1))?; + NEXT_UNLOCK_ID.save(deps.storage, &1)?; Ok(Response::default()) } @@ -53,8 +53,11 @@ pub fn execute( ExecuteMsg::Deposit {} => deposit(deps, info), ExecuteMsg::Withdraw {} => withdraw(deps, info), ExecuteMsg::ForceWithdraw {} => withdraw_force(deps, info), + ExecuteMsg::ForceWithdrawUnlocking { lockup_id, amount } => { + withdraw_unlocking_force(deps, &info.sender, lockup_id, amount) + } ExecuteMsg::RequestUnlock {} => request_unlock(deps, env, info), - ExecuteMsg::WithdrawUnlocked { id } => withdraw_unlocked(deps, env, info, id), + ExecuteMsg::WithdrawUnlocked { id } => withdraw_unlocked(deps, env, &info.sender, id), } } diff --git a/contracts/mock-vault/src/error.rs b/contracts/mock-vault/src/error.rs index 739638dce..a4ea33cbb 100644 --- a/contracts/mock-vault/src/error.rs +++ b/contracts/mock-vault/src/error.rs @@ -16,6 +16,9 @@ pub enum ContractError { #[error("This vault does not require a lockup, just withdraw directly")] NoLockupTime, + #[error("Lockup position {0} not found")] + LockupPositionNotFound(u64), + #[error("There is more time left on the lock period")] UnlockNotReady, diff --git a/contracts/mock-vault/src/query.rs b/contracts/mock-vault/src/query.rs index 4512c78ec..53d6637ff 100644 --- a/contracts/mock-vault/src/query.rs +++ b/contracts/mock-vault/src/query.rs @@ -26,9 +26,9 @@ pub fn query_vault_info(deps: Deps) -> StdResult { let all_coins = get_all_vault_coins(deps.storage)?; let accepted_denoms = all_coins.iter().map(|c| c.denom.clone()).collect(); Ok(VaultInfo { - accepts: vec![accepted_denoms], + accepts: accepted_denoms, lockup: LOCKUP_TIME.load(deps.storage)?, - vault_coin_denom: LP_TOKEN_DENOM.load(deps.storage)?, + token_denom: LP_TOKEN_DENOM.load(deps.storage)?, }) } @@ -42,7 +42,7 @@ pub fn get_all_vault_coins(storage: &dyn Storage) -> StdResult> { .collect() } -pub fn query_unlocking_position(deps: Deps, id: Uint128) -> StdResult { +pub fn query_unlocking_position(deps: Deps, id: u64) -> StdResult { UNLOCKING_COINS .range(deps.storage, None, None, Order::Ascending) .collect::>>()? diff --git a/contracts/mock-vault/src/state.rs b/contracts/mock-vault/src/state.rs index a76645473..c7a30bb54 100644 --- a/contracts/mock-vault/src/state.rs +++ b/contracts/mock-vault/src/state.rs @@ -14,4 +14,4 @@ pub const ORACLE: Item = Item::new("oracle"); pub const CHAIN_BANK: Item = Item::new("chain_bank"); pub const UNLOCKING_COINS: Map> = Map::new("unlocking_coins"); -pub const NEXT_UNLOCK_ID: Item = Item::new("next_unlock_id"); +pub const NEXT_UNLOCK_ID: Item = Item::new("next_unlock_id"); diff --git a/contracts/mock-vault/src/unlock.rs b/contracts/mock-vault/src/unlock.rs index 0d00ee104..f0f63c4ab 100644 --- a/contracts/mock-vault/src/unlock.rs +++ b/contracts/mock-vault/src/unlock.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{DepsMut, Env, Event, MessageInfo, Response, StdResult, Uint128}; +use cosmwasm_std::{Addr, DepsMut, Env, Event, MessageInfo, Response, StdResult, Uint128}; use rover::msg::vault::{ UnlockingPosition, UNLOCKING_POSITION_ATTR, UNLOCKING_POSITION_CREATED_EVENT_TYPE, @@ -30,21 +30,21 @@ pub fn request_unlock( Ok(unlocking_positions) })?; - NEXT_UNLOCK_ID.save(deps.storage, &(next_unlock_id + Uint128::from(1u128)))?; + NEXT_UNLOCK_ID.save(deps.storage, &(next_unlock_id + 1))?; let event = Event::new(UNLOCKING_POSITION_CREATED_EVENT_TYPE) - .add_attribute(UNLOCKING_POSITION_ATTR, next_unlock_id); + .add_attribute(UNLOCKING_POSITION_ATTR, next_unlock_id.to_string()); Ok(Response::new().add_event(event)) } pub fn withdraw_unlocked( deps: DepsMut, env: Env, - info: MessageInfo, - id: Uint128, + sender: &Addr, + id: u64, ) -> Result { let unlocking_positions = UNLOCKING_COINS - .may_load(deps.storage, info.sender.clone())? + .may_load(deps.storage, sender.clone())? .ok_or(ContractError::UnlockRequired {})?; let matching_position = unlocking_positions @@ -61,7 +61,36 @@ pub fn withdraw_unlocked( .into_iter() .filter(|p| p.id != id) .collect(); - UNLOCKING_COINS.save(deps.storage, info.sender.clone(), &remaining)?; + UNLOCKING_COINS.save(deps.storage, sender.clone(), &remaining)?; + + _exchange(deps.storage, sender, matching_position.amount) +} + +pub fn withdraw_unlocking_force( + deps: DepsMut, + sender: &Addr, + lockup_id: u64, + amount: Option, +) -> Result { + let mut unlocking_positions = UNLOCKING_COINS.load(deps.storage, sender.clone())?; + let mut unlocking_position = unlocking_positions + .iter() + .find(|p| p.id == lockup_id) + .cloned() + .ok_or(ContractError::LockupPositionNotFound(lockup_id))?; + + unlocking_positions.retain(|p| p.id != lockup_id); + + let amount_to_withdraw = match amount { + Some(a) if a != unlocking_position.amount => { + unlocking_position.amount -= a; + unlocking_positions.push(unlocking_position); + a + } + _ => unlocking_position.amount, + }; + + UNLOCKING_COINS.save(deps.storage, sender.clone(), &unlocking_positions)?; - _exchange(deps.storage, info.sender, matching_position.amount) + _exchange(deps.storage, sender, amount_to_withdraw) } diff --git a/contracts/mock-vault/src/withdraw.rs b/contracts/mock-vault/src/withdraw.rs index 2b16c8f26..375ce775a 100644 --- a/contracts/mock-vault/src/withdraw.rs +++ b/contracts/mock-vault/src/withdraw.rs @@ -12,18 +12,18 @@ pub fn withdraw(deps: DepsMut, info: MessageInfo) -> Result Result { let vault_tokens = get_vault_token(deps.storage, info.funds.clone())?; - _exchange(deps.storage, info.sender, vault_tokens.amount) + _exchange(deps.storage, &info.sender, vault_tokens.amount) } /// Swap shares for underlying assets pub fn _exchange( storage: &mut dyn Storage, - send_to: Addr, + send_to: &Addr, shares: Uint128, ) -> Result { let coins = query_coins_for_shares(storage, shares)?; diff --git a/packages/rover/src/adapters/vault.rs b/packages/rover/src/adapters/vault.rs index d3ed7783b..31bc8b49d 100644 --- a/packages/rover/src/adapters/vault.rs +++ b/packages/rover/src/adapters/vault.rs @@ -16,23 +16,36 @@ pub const VAULT_REQUEST_REPLY_ID: u64 = 10_001; pub struct VaultPositionState { pub unlocked: Uint128, pub locked: Uint128, - pub unlocking: Vec, + pub unlocking: Vec, +} + +#[cw_serde] +pub enum UpdateType { + Increment, + Decrement, } #[cw_serde] pub enum VaultPositionUpdate { - DecrementUnlocked(Uint128), - IncrementUnlocked(Uint128), - DecrementLocked(Uint128), - IncrementLocked(Uint128), - AddUnlocking(VaultUnlockingId), - RemoveUnlocking(UnlockingId), + Unlocked { + amount: Uint128, + kind: UpdateType, + }, + Locked { + amount: Uint128, + kind: UpdateType, + }, + Unlocking { + id: UnlockingId, + amount: Uint128, + kind: UpdateType, + }, } -pub type UnlockingId = Uint128; +pub type UnlockingId = u64; #[cw_serde] -pub struct VaultUnlockingId { +pub struct VaultUnlockingPosition { /// Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call. pub id: UnlockingId, /// Number of vault tokens @@ -41,7 +54,10 @@ pub struct VaultUnlockingId { impl VaultPositionState { pub fn total(&self) -> Result { - self.locked.checked_add(self.unlocked) + let total_unlocking = self.unlocking.iter().map(|u| u.amount).sum(); + self.locked + .checked_add(self.unlocked)? + .checked_add(total_unlocking) } } @@ -117,7 +133,7 @@ impl Vault { let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![Coin { - denom: vault_info.vault_coin_denom, + denom: vault_info.token_denom, amount, }], msg: to_binary( @@ -131,6 +147,19 @@ impl Vault { Ok(withdraw_msg) } + pub fn force_withdraw_unlocking_msg( + &self, + lockup_id: u64, + amount: Option, + ) -> StdResult { + let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::ForceWithdrawUnlocking { lockup_id, amount })?, + }); + Ok(withdraw_msg) + } + pub fn request_unlock_msg(&self, funds: &[Coin]) -> StdResult { let request_msg = SubMsg::reply_on_success( CosmosMsg::Wasm(WasmMsg::Execute { @@ -143,7 +172,7 @@ impl Vault { Ok(request_msg) } - pub fn withdraw_unlocked_msg(&self, position_id: Uint128) -> StdResult { + pub fn withdraw_unlocked_msg(&self, position_id: u64) -> StdResult { let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![], @@ -162,7 +191,7 @@ impl Vault { pub fn query_unlocking_position_info( &self, querier: &QuerierWrapper, - id: Uint128, + id: u64, ) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), @@ -174,7 +203,7 @@ impl Vault { let vault_info = self.query_info(querier)?; let res: BalanceResponse = querier.query(&QueryRequest::Bank(BankQuery::Balance { address: addr.to_string(), - denom: vault_info.vault_coin_denom, + denom: vault_info.token_denom, }))?; Ok(res.amount.amount) } diff --git a/packages/rover/src/extensions/reply.rs b/packages/rover/src/extensions/reply.rs index 421b6f4d1..bc3451672 100644 --- a/packages/rover/src/extensions/reply.rs +++ b/packages/rover/src/extensions/reply.rs @@ -1,8 +1,6 @@ -use std::str::FromStr; - use crate::msg::vault::UNLOCKING_POSITION_CREATED_EVENT_TYPE; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Coin, Reply, StdError, StdResult, SubMsgResult, Uint128}; +use cosmwasm_std::{Coin, Reply, StdError, StdResult, SubMsgResult}; // https://github.com/CosmWasm/wasmd/blob/main/EVENTS.md#standard-events-in-xwasm const CONTRACT_ADDR_KEY: &str = "_contract_addr"; @@ -16,7 +14,7 @@ pub struct AssetTransferMsg { #[cw_serde] pub struct UnlockEvent { - pub id: Uint128, + pub id: u64, pub vault_addr: String, } @@ -52,7 +50,9 @@ impl AttrParse for Reply { .value; Ok(UnlockEvent { - id: Uint128::from_str(id)?, + id: id + .parse::() + .map_err(|_| StdError::generic_err("Could not parse id from reply"))?, vault_addr: contract_addr.to_string(), }) } diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 8b8ecbb73..5280c7f7d 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -53,7 +53,7 @@ pub enum Action { amount: Uint128, }, /// Withdraws the assets for unlocking position id from vault. Required time must have elapsed. - VaultWithdrawUnlocked { id: Uint128, vault: VaultUnchecked }, + VaultWithdrawUnlocked { id: u64, vault: VaultUnchecked }, /// Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt /// denom/amount of what the liquidator wants to payoff and 2) the request coin denom which the /// liquidatee should have a balance of. The amount returned to liquidator will be the request coin @@ -71,6 +71,15 @@ pub enum Action { /// The coin they wish to acquire from the liquidatee (amount returned will include the bonus) request_coin_denom: String, }, + /// Pay back debt of a liquidatable rover account for a via liquidating a vault position. + /// Similar to LiquidateCoin {} msg and will make similar adjustments to the request. + /// The vault position will be withdrawn (and force withdrawn if a locked vault position) and + /// the underlying assets will transferred to the liquidator. + LiquidateVault { + liquidatee_account_id: String, + debt_coin: Coin, + request_vault: VaultUnchecked, + }, /// Perform a swapper with an exact-in amount. Requires slippage allowance %. SwapExactIn { coin_in: Coin, @@ -135,7 +144,7 @@ pub enum CallbackMsg { VaultWithdrawUnlocked { account_id: String, vault: Vault, - position_id: Uint128, + position_id: u64, }, /// Pay back debts of a liquidatable rover account for a bonus LiquidateCoin { @@ -144,6 +153,12 @@ pub enum CallbackMsg { debt_coin: Coin, request_coin_denom: String, }, + LiquidateVault { + liquidator_account_id: String, + liquidatee_account_id: String, + debt_coin: Coin, + request_vault: Vault, + }, /// Determine health factor improved as a consequence of liquidation event AssertHealthFactorImproved { account_id: String, diff --git a/packages/rover/src/msg/vault.rs b/packages/rover/src/msg/vault.rs index c5b491f9c..daad8c3e6 100644 --- a/packages/rover/src/msg/vault.rs +++ b/packages/rover/src/msg/vault.rs @@ -14,13 +14,18 @@ pub enum ExecuteMsg { /// A privileged action only to be used by Rover. Same as `Withdraw` except it bypasses any lockup period /// restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation. ForceWithdraw {}, + /// Force withdraw from a position that is already unlocking (Unlock has already been called) + ForceWithdrawUnlocking { + lockup_id: u64, + amount: Option, + }, /// Some vaults have lockup periods (typically between 1-14 days). This action sends vault `Coin` /// which is locked for vault lockup period and available to `Unlock` after that time has elapsed. /// On response, vault sends back `unlocking_position_created` event with attribute `id` representing /// the new unlocking coins position. RequestUnlock {}, /// Withdraw assets in vault that have been unlocked for given unlocking position - WithdrawUnlocked { id: Uint128 }, + WithdrawUnlocked { id: u64 }, } #[cw_serde] @@ -41,16 +46,15 @@ pub enum QueryMsg { #[returns(Vec)] UnlockingPositionsForAddr { addr: String }, #[returns(UnlockingPosition)] - UnlockingPosition { id: Uint128 }, + UnlockingPosition { id: u64 }, } #[cw_serde] pub struct VaultInfo { /// Denom of vault token - pub vault_coin_denom: String, - /// Coin denoms required to enter vault. - /// Multiple vectors indicate the vault accepts more than one combination to enter. - pub accepts: Vec>, + pub token_denom: String, + /// Coin denoms required to enter vault + pub accepts: Vec, /// Time in seconds for unlock period pub lockup: Option, } @@ -58,7 +62,7 @@ pub struct VaultInfo { #[cw_serde] pub struct UnlockingPosition { /// Unique identifier representing the unlocking position. Needed for `ExecuteMsg::Unlock {}` call. - pub id: Uint128, + pub id: u64, /// Number of vault tokens pub amount: Uint128, /// Absolute time when position unlocks in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC) diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 7fcbca825..6f5613c61 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -147,7 +147,7 @@ export class Deployer { const msg: RoverInstantiateMsg = { allowed_coins: [this.config.baseDenom, this.config.secondaryDenom], allowed_vaults: [{ address: this.storage.addresses.mockVault! }], - oracle: this.config.oracleAddr, + oracle: this.storage.addresses.marsOracleAdapter!, owner: this.deployerAddr, red_bank: this.config.redBankAddr, max_close_factor: this.config.maxCloseFactor.toString(), diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 68782261d..d28ca9dbb 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -70,6 +70,9 @@ export class Rover { printGreen(`Withdrew: ${amount} ${this.config.baseDenom}`) } + // If this fails, it's likely because Red Bank has not whitelisted uncollateralized borrows. + // Need to issue this msg from Red Bank admin: + // {"update_uncollateralized_loan_limit": {"user":"[rover addr]","denom":"uosmo","new_limit":"1000000000"} } async borrow() { const amount = this.config.borrowAmount.toString() await this.updateCreditAccount([{ borrow: { amount, denom: this.config.baseDenom } }]) From 814c34d3f35053864787c2f2eb0beb5dc4bc517a Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 21 Oct 2022 10:24:20 +0200 Subject: [PATCH 065/218] Adding deposit cap limits for risk mitigation (#29) --- contracts/credit-manager/src/contract.rs | 5 +- contracts/credit-manager/src/instantiate.rs | 9 +- contracts/credit-manager/src/query.rs | 33 ++++- contracts/credit-manager/src/state.rs | 3 +- contracts/credit-manager/src/vault/deposit.rs | 39 +++++- .../credit-manager/tests/helpers/builders.rs | 3 +- ...{mock_coin_info.rs => mock_entity_info.rs} | 20 ++- .../credit-manager/tests/helpers/mock_env.rs | 87 ++++++++----- contracts/credit-manager/tests/helpers/mod.rs | 4 +- .../credit-manager/tests/helpers/types.rs | 1 + .../tests/test_enumerate_deposit_caps.rs | 61 +++++++++ .../credit-manager/tests/test_instantiate.rs | 10 +- .../tests/test_liquidate_coin.rs | 11 +- .../tests/test_liquidate_vault.rs | 53 ++------ .../tests/test_vault_deposit.rs | 122 +++++++++++++----- .../tests/test_vault_request_unlock.rs | 35 ++--- .../tests/test_vault_withdraw.rs | 29 ++--- .../tests/test_vault_withdraw_unlocked.rs | 29 +---- contracts/mars-oracle-adapter/src/contract.rs | 8 +- .../mars-oracle-adapter/tests/helpers.rs | 2 +- contracts/mock-oracle/src/contract.rs | 2 +- contracts/mock-oracle/src/msg.rs | 2 +- contracts/mock-vault/src/contract.rs | 1 - contracts/mock-vault/src/query.rs | 5 +- packages/rover/src/adapters/vault.rs | 15 +-- packages/rover/src/error.rs | 3 + packages/rover/src/msg/instantiate.rs | 11 +- packages/rover/src/msg/query.rs | 6 + 28 files changed, 389 insertions(+), 220 deletions(-) rename contracts/credit-manager/tests/helpers/{mock_coin_info.rs => mock_entity_info.rs} (62%) create mode 100644 contracts/credit-manager/tests/test_enumerate_deposit_caps.rs diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 3053bf017..e99f223cc 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -15,7 +15,7 @@ use crate::query::{ query_all_coin_balances, query_all_debt_shares, query_all_total_debt_shares, query_all_total_vault_coin_balances, query_all_vault_positions, query_allowed_coins, query_allowed_vaults, query_config, query_positions, query_total_debt_shares, - query_total_vault_coin_balance, + query_total_vault_coin_balance, query_vault_deposit_caps, }; use crate::vault::handle_unlock_request_reply; @@ -67,6 +67,9 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { QueryMsg::AllowedVaults { start_after, limit } => { to_binary(&query_allowed_vaults(deps, start_after, limit)?) } + QueryMsg::DepositCaps { start_after, limit } => { + to_binary(&query_vault_deposit_caps(deps, start_after, limit)?) + } QueryMsg::AllowedCoins { start_after, limit } => { to_binary(&query_allowed_coins(deps, start_after, limit)?) } diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 037b26977..98fbd2eb3 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -4,7 +4,7 @@ use rover::msg::InstantiateMsg; use crate::state::{ ALLOWED_COINS, ALLOWED_VAULTS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, - RED_BANK, SWAPPER, + RED_BANK, SWAPPER, VAULT_DEPOSIT_CAPS, }; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { @@ -16,9 +16,10 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; SWAPPER.save(deps.storage, &msg.swapper.check(deps.api)?)?; - msg.allowed_vaults.iter().try_for_each(|unchecked| { - let vault = unchecked.check(deps.api)?; - ALLOWED_VAULTS.save(deps.storage, &vault.address, &Empty {}) + msg.allowed_vaults.iter().try_for_each(|config| { + let vault = config.vault.check(deps.api)?; + ALLOWED_VAULTS.save(deps.storage, &vault.address, &Empty {})?; + VAULT_DEPOSIT_CAPS.save(deps.storage, &vault.address, &config.deposit_cap) })?; msg.allowed_coins diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index fff39ce9c..a3c501692 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -3,6 +3,7 @@ use cw_storage_plus::Bound; use rover::adapters::{Vault, VaultBase, VaultPosition, VaultUnchecked}; use rover::error::ContractResult; +use rover::msg::instantiate::VaultInstantiateConfig; use rover::msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, Positions, SharesResponseItem, VaultPositionResponseItem, VaultWithBalance, @@ -10,7 +11,8 @@ use rover::msg::query::{ use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, - MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_POSITIONS, + MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_DEPOSIT_CAPS, + VAULT_POSITIONS, }; use crate::utils::debt_shares_to_amount; @@ -141,6 +143,35 @@ pub fn query_allowed_vaults( .collect() } +pub fn query_vault_deposit_caps( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult> { + let vault: Vault; + let start = match &start_after { + Some(unchecked) => { + vault = unchecked.check(deps.api)?; + Some(Bound::exclusive(&vault.address)) + } + None => None, + }; + + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + VAULT_DEPOSIT_CAPS + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|res| { + let (addr, deposit_cap) = res?; + Ok(VaultInstantiateConfig { + vault: VaultBase::new(addr.to_string()), + deposit_cap, + }) + }) + .collect() +} + fn get_vault_positions(deps: Deps, account_id: &str) -> ContractResult> { VAULT_POSITIONS .prefix(account_id) diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index d608f064a..99b0a7fbb 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Decimal, Empty, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, Empty, Uint128}; use cw_storage_plus::{Item, Map}; use crate::vault::RequestTempStorage; @@ -10,6 +10,7 @@ pub const OWNER: Item = Item::new("owner"); pub const ACCOUNT_NFT: Item = Item::new("account_nft"); pub const ALLOWED_COINS: Map<&str, Empty> = Map::new("allowed_coins"); pub const ALLOWED_VAULTS: Map<&Addr, Empty> = Map::new("allowed_vaults"); +pub const VAULT_DEPOSIT_CAPS: Map<&Addr, Coin> = Map::new("vault_deposit_caps"); pub const RED_BANK: Item = Item::new("red_bank"); pub const ORACLE: Item = Item::new("oracle"); pub const MAX_LIQUIDATION_BONUS: Item = Item::new("max_liquidation_bonus"); diff --git a/contracts/credit-manager/src/vault/deposit.rs b/contracts/credit-manager/src/vault/deposit.rs index fafbbdfd0..3ca9f7946 100644 --- a/contracts/credit-manager/src/vault/deposit.rs +++ b/contracts/credit-manager/src/vault/deposit.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{ - to_binary, Addr, Coin, CosmosMsg, DepsMut, QuerierWrapper, Response, Uint128, WasmMsg, + coin, to_binary, Addr, Coin, CosmosMsg, Deps, DepsMut, QuerierWrapper, Response, Uint128, + WasmMsg, }; use rover::adapters::{UpdateType, Vault, VaultPositionUpdate}; @@ -7,6 +8,7 @@ use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg; +use crate::state::{ORACLE, VAULT_DEPOSIT_CAPS}; use crate::utils::{assert_coins_are_whitelisted, contents_equal, decrement_coin_balance}; use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; @@ -21,6 +23,7 @@ pub fn deposit_into_vault( assert_coins_are_whitelisted(deps.storage, denoms)?; assert_vault_is_whitelisted(deps.storage, &vault)?; assert_denoms_match_vault_reqs(deps.querier, &vault, coins)?; + assert_deposit_is_under_cap(deps.as_ref(), &vault, coins, rover_addr)?; // Decrement token's coin balance amount coins.iter().try_for_each(|coin| -> ContractResult<_> { @@ -104,3 +107,37 @@ pub fn assert_denoms_match_vault_reqs( } Ok(()) } + +pub fn assert_deposit_is_under_cap( + deps: Deps, + vault: &Vault, + coins: &[Coin], + rover_addr: &Addr, +) -> ContractResult<()> { + let oracle = ORACLE.load(deps.storage)?; + let deposit_request_value = oracle.query_total_value(&deps.querier, coins)?; + + let deposit_cap = VAULT_DEPOSIT_CAPS.load(deps.storage, &vault.address)?; + let deposit_cap_value = oracle.query_total_value(&deps.querier, &[deposit_cap])?; + + let vault_info = vault.query_info(&deps.querier)?; + let rover_vault_coin_balance = vault.query_balance(&deps.querier, rover_addr)?; + let rover_vault_coins_value = oracle.query_total_value( + &deps.querier, + &[coin( + rover_vault_coin_balance.u128(), + vault_info.token_denom, + )], + )?; + + let new_total_vault_value = rover_vault_coins_value.checked_add(deposit_request_value)?; + + if new_total_vault_value > deposit_cap_value { + return Err(ContractError::AboveVaultDepositCap { + new_value: new_total_vault_value.to_string(), + maximum: deposit_cap_value.to_string(), + }); + } + + Ok(()) +} diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index 0744b0618..9f59fe6c7 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::Decimal; +use cosmwasm_std::{coin, Decimal}; use rover::traits::IntoDecimal; @@ -24,6 +24,7 @@ pub fn build_mock_vaults(count: usize) -> Vec { denom: format!("vault_{}", i), lockup: Some(1_209_600), // 14 days underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + deposit_cap: coin(10000000, "uusdc"), } }) .collect() diff --git a/contracts/credit-manager/tests/helpers/mock_coin_info.rs b/contracts/credit-manager/tests/helpers/mock_entity_info.rs similarity index 62% rename from contracts/credit-manager/tests/helpers/mock_coin_info.rs rename to contracts/credit-manager/tests/helpers/mock_entity_info.rs index ff6bb8846..46ba8ee7d 100644 --- a/contracts/credit-manager/tests/helpers/mock_coin_info.rs +++ b/contracts/credit-manager/tests/helpers/mock_entity_info.rs @@ -1,5 +1,6 @@ use crate::helpers::CoinInfo; -use cosmwasm_std::Decimal; +use crate::helpers::VaultTestInfo; +use cosmwasm_std::{coin, Decimal}; pub fn uosmo_info() -> CoinInfo { CoinInfo { @@ -26,3 +27,20 @@ pub fn ujake_info() -> CoinInfo { liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), } } + +pub fn locked_vault_info() -> VaultTestInfo { + generate_mock_vault(Some(1_209_600)) // 14 days) +} + +pub fn unlocked_vault_info() -> VaultTestInfo { + generate_mock_vault(None) +} + +fn generate_mock_vault(lockup: Option) -> VaultTestInfo { + VaultTestInfo { + denom: "uleverage".to_string(), + lockup, + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + deposit_cap: coin(10_000_000, "uusdc"), + } +} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 6e47805c2..de5a65aa2 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -5,13 +5,13 @@ use cosmwasm_std::testing::MockApi; use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128}; use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; +use mars_outpost::red_bank::QueryMsg::UserDebt; +use mars_outpost::red_bank::UserDebtResponse; use account_nft::msg::ExecuteMsg as NftExecuteMsg; use mars_oracle_adapter::msg::{ InstantiateMsg as OracleAdapterInstantiateMsg, PricingMethod, VaultPricingInfo, }; -use mars_outpost::red_bank::QueryMsg::UserDebt; -use mars_outpost::red_bank::UserDebtResponse; use mock_oracle::msg::{ CoinPrice, ExecuteMsg as OracleExecuteMsg, InstantiateMsg as OracleInstantiateMsg, }; @@ -22,9 +22,9 @@ use rover::adapters::swap::QueryMsg::EstimateExactInSwap; use rover::adapters::swap::{ EstimateExactInSwapResponse, InstantiateMsg as SwapperInstantiateMsg, Swapper, SwapperBase, }; -use rover::adapters::{OracleBase, RedBankBase, Vault, VaultBase, VaultUnchecked}; +use rover::adapters::{OracleBase, RedBankBase, VaultBase, VaultUnchecked}; use rover::msg::execute::{Action, CallbackMsg}; -use rover::msg::instantiate::ConfigUpdates; +use rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; use rover::msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtShares, HealthResponse, Positions, SharesResponseItem, VaultPositionResponseItem, VaultWithBalance, @@ -51,7 +51,7 @@ pub struct MockEnvBuilder { pub app: BasicApp, pub owner: Option, pub allowed_vaults: Option>, - pub pre_deployed_vaults: Option>, + pub pre_deployed_vaults: Option>, pub allowed_coins: Option>, pub oracle: Option>, pub oracle_adapter: Option>, @@ -238,6 +238,20 @@ impl MockEnv { .unwrap() } + pub fn query_deposit_caps( + &self, + start_after: Option, + limit: Option, + ) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::DepositCaps { start_after, limit }, + ) + .unwrap() + } + pub fn get_vault(&self, vault: &VaultTestInfo) -> VaultUnchecked { self.query_allowed_vaults(None, Some(30)) // Max limit .iter() @@ -548,21 +562,24 @@ impl MockEnvBuilder { fn deploy_oracle(&mut self) -> OracleBase { let contract_code_id = self.app.store_code(mock_oracle_contract()); + let mut prices: Vec = self + .get_allowed_coins() + .iter() + .map(|item| CoinPrice { + denom: item.denom.clone(), + price: item.price, + }) + .collect(); + prices.push(CoinPrice { + denom: "uusdc".to_string(), + price: Decimal::from_atomics(12345u128, 4).unwrap(), + }); let addr = self .app .instantiate_contract( contract_code_id, Addr::unchecked("oracle_contract_owner"), - &OracleInstantiateMsg { - coins: self - .get_allowed_coins() - .iter() - .map(|item| CoinPrice { - denom: item.denom.clone(), - price: item.price, - }) - .collect(), - }, + &OracleInstantiateMsg { prices }, &[], "mock-oracle", None, @@ -571,7 +588,7 @@ impl MockEnvBuilder { OracleBase::new(addr) } - fn get_oracle_adapter(&mut self, vaults: Vec) -> OracleBase { + fn get_oracle_adapter(&mut self, vaults: Vec) -> OracleBase { if self.oracle_adapter.is_none() { let addr = self.deploy_oracle_adapter(vaults); self.oracle_adapter = Some(addr); @@ -579,7 +596,7 @@ impl MockEnvBuilder { self.oracle_adapter.clone().unwrap() } - fn deploy_oracle_adapter(&mut self, vaults: Vec) -> OracleBase { + fn deploy_oracle_adapter(&mut self, vaults: Vec) -> OracleBase { let owner = Addr::unchecked("oracle_adapter_contract_owner"); let contract_code_id = self.app.store_code(mock_oracle_adapter_contract()); let oracle = self.get_oracle().into(); @@ -588,15 +605,15 @@ impl MockEnvBuilder { } else { vaults .into_iter() - .map(|v| { + .map(|config| { let info: VaultInfo = self .app .wrap() - .query_wasm_smart(v.address.clone(), &VaultInfoMsg {}) + .query_wasm_smart(config.vault.address.clone(), &VaultInfoMsg {}) .unwrap(); VaultPricingInfo { denom: info.token_denom, - addr: Addr::unchecked(v.address), + addr: Addr::unchecked(config.vault.address), method: PricingMethod::PreviewRedeem, } }) @@ -669,7 +686,7 @@ impl MockEnvBuilder { RedBankBase::new(addr) } - fn deploy_vault(&mut self, vault: &VaultTestInfo) -> Vault { + fn deploy_vault(&mut self, vault: &VaultTestInfo) -> VaultInstantiateConfig { let code_id = self.app.store_code(mock_vault_contract()); let oracle = self.get_oracle().into(); let addr = self @@ -689,7 +706,10 @@ impl MockEnvBuilder { ) .unwrap(); self.fund_vault(&addr, &vault.denom); - VaultBase::new(addr) + VaultInstantiateConfig { + vault: VaultBase::new(addr.to_string()), + deposit_cap: vault.deposit_cap.clone(), + } } fn deploy_swapper(&mut self) -> Swapper { @@ -731,12 +751,12 @@ impl MockEnvBuilder { .unwrap(); } - fn deploy_vaults(&mut self) -> Vec { + fn deploy_vaults(&mut self) -> Vec { self.allowed_vaults .clone() .unwrap_or_default() .iter() - .map(|v| self.deploy_vault(v).into()) + .map(|v| self.deploy_vault(v)) .collect() } @@ -798,12 +818,19 @@ impl MockEnvBuilder { self } - pub fn pre_deployed_vaults(&mut self, vaults: &[&str]) -> &mut Self { - let vaults = vaults - .iter() - .map(|v| VaultBase::new(v.to_string())) - .collect::>(); - self.pre_deployed_vaults = Some(vaults); + pub fn pre_deployed_vault(&mut self, address: &str, info: &VaultTestInfo) -> &mut Self { + let config = VaultInstantiateConfig { + vault: VaultBase::new(address.to_string()), + deposit_cap: info.deposit_cap.clone(), + }; + let new_list = match self.pre_deployed_vaults.clone() { + None => Some(vec![config]), + Some(mut curr) => { + curr.push(config); + Some(curr) + } + }; + self.pre_deployed_vaults = new_list; self } diff --git a/contracts/credit-manager/tests/helpers/mod.rs b/contracts/credit-manager/tests/helpers/mod.rs index 7a4a84ee3..6d8106982 100644 --- a/contracts/credit-manager/tests/helpers/mod.rs +++ b/contracts/credit-manager/tests/helpers/mod.rs @@ -1,7 +1,7 @@ pub use self::assertions::*; pub use self::builders::*; pub use self::contracts::*; -pub use self::mock_coin_info::*; +pub use self::mock_entity_info::*; pub use self::mock_env::*; pub use self::types::*; pub use self::utils::*; @@ -9,7 +9,7 @@ pub use self::utils::*; mod assertions; mod builders; mod contracts; -mod mock_coin_info; +mod mock_entity_info; mod mock_env; mod types; mod utils; diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 3ea6c1a65..515732ae9 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -20,6 +20,7 @@ pub struct VaultTestInfo { pub denom: String, pub lockup: Option, pub underlying_denoms: Vec, + pub deposit_cap: Coin, } impl CoinInfo { diff --git a/contracts/credit-manager/tests/test_enumerate_deposit_caps.rs b/contracts/credit-manager/tests/test_enumerate_deposit_caps.rs new file mode 100644 index 000000000..991438a63 --- /dev/null +++ b/contracts/credit-manager/tests/test_enumerate_deposit_caps.rs @@ -0,0 +1,61 @@ +use cosmwasm_std::testing::MockApi; + +use crate::helpers::{assert_contents_equal, build_mock_vaults, MockEnv}; + +pub mod helpers; + +#[test] +fn test_pagination_on_deposit_caps_query_works() { + let allowed_vaults = build_mock_vaults(32); + let mock = MockEnv::new() + .allowed_vaults(&allowed_vaults) + .build() + .unwrap(); + + let vaults_res = mock.query_deposit_caps(None, Some(58_u32)); + + // Assert maximum is observed + assert_eq!(vaults_res.len(), 30); + + let vaults_res = mock.query_deposit_caps(None, Some(2_u32)); + + // Assert limit request is observed + assert_eq!(vaults_res.len(), 2); + + let vaults_res_a = mock.query_deposit_caps(None, None); + + let vaults_res_b = + mock.query_deposit_caps(Some(vaults_res_a.last().unwrap().vault.clone()), None); + let vaults_res_c = + mock.query_deposit_caps(Some(vaults_res_b.last().unwrap().vault.clone()), None); + let vaults_res_d = + mock.query_deposit_caps(Some(vaults_res_c.last().unwrap().vault.clone()), None); + + // Assert default is observed + assert_eq!(vaults_res_a.len(), 10); + assert_eq!(vaults_res_b.len(), 10); + assert_eq!(vaults_res_c.len(), 10); + + assert_eq!(vaults_res_d.len(), 2); + + let combined = vaults_res_a + .iter() + .cloned() + .chain(vaults_res_b.iter().cloned()) + .chain(vaults_res_c.iter().cloned()) + .chain(vaults_res_d.iter().cloned()) + .map(|config| config.vault.check(&MockApi::default()).unwrap()) + .map(|v| v.query_info(&mock.app.wrap()).unwrap()) + .map(|info| info.token_denom) + .collect::>(); + + assert_eq!(combined.len(), allowed_vaults.len()); + + assert_contents_equal( + &allowed_vaults + .iter() + .map(|v| v.denom.clone()) + .collect::>(), + &combined, + ) +} diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 945f9231d..c9a340730 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -1,7 +1,8 @@ use crate::helpers::{ - assert_contents_equal, uatom_info, ujake_info, uosmo_info, CoinInfo, MockEnv, VaultTestInfo, + assert_contents_equal, uatom_info, ujake_info, unlocked_vault_info, uosmo_info, CoinInfo, + MockEnv, VaultTestInfo, }; -use cosmwasm_std::Decimal; +use cosmwasm_std::{coin, Decimal}; pub mod helpers; @@ -36,16 +37,19 @@ fn test_allowed_vaults_set_on_instantiate() { denom: "vault_contract_1".to_string(), lockup: None, underlying_denoms: vec![], + deposit_cap: coin(1_000_000, "uusdc"), }, VaultTestInfo { denom: "vault_contract_2".to_string(), lockup: None, underlying_denoms: vec![], + deposit_cap: coin(1_000_000, "uusdc"), }, VaultTestInfo { denom: "vault_contract_3".to_string(), lockup: None, underlying_denoms: vec![], + deposit_cap: coin(1_000_000, "uusdc"), }, ]; @@ -66,7 +70,7 @@ fn test_allowed_vaults_set_on_instantiate() { #[test] fn test_raises_on_invalid_vaults_addr() { let mock = MockEnv::new() - .pre_deployed_vaults(&["%%%INVALID%%%"]) + .pre_deployed_vault("%%%INVALID%%%", &unlocked_vault_info()) .build(); if mock.is_ok() { diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index ef86ccd93..2ee35f69e 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -7,8 +7,8 @@ use rover::msg::execute::Action::{Borrow, Deposit, LiquidateCoin, VaultDeposit}; use rover::traits::IntoDecimal; use crate::helpers::{ - assert_err, get_coin, get_debt, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, - VaultTestInfo, + assert_err, get_coin, get_debt, uatom_info, ujake_info, unlocked_vault_info, uosmo_info, + AccountToFund, MockEnv, }; pub mod helpers; @@ -73,12 +73,7 @@ fn test_can_only_liquidate_unhealthy_accounts() { fn test_vault_positions_contribute_to_health() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = unlocked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index f4edc2a59..98fc2d578 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -11,8 +11,8 @@ use rover::msg::execute::Action::{ use rover::traits::IntoDecimal; use crate::helpers::{ - assert_err, get_coin, get_debt, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, - VaultTestInfo, + assert_err, get_coin, get_debt, locked_vault_info, uatom_info, ujake_info, unlocked_vault_info, + uosmo_info, AccountToFund, MockEnv, }; pub mod helpers; @@ -24,12 +24,7 @@ pub mod helpers; fn test_liquidatee_must_have_the_request_vault_position() { let uatom = uatom_info(); let uosmo = uosmo_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = unlocked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() @@ -78,12 +73,7 @@ fn test_liquidatee_must_have_the_request_vault_position() { fn test_liquidatee_is_not_liquidatable() { let uatom = uatom_info(); let uosmo = uosmo_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = unlocked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() @@ -142,12 +132,7 @@ fn test_liquidator_does_not_have_debt_coin_in_credit_account() { let uatom = uatom_info(); let uosmo = uosmo_info(); let ujake = ujake_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = unlocked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() @@ -213,12 +198,7 @@ fn test_liquidate_unlocked_vault() { let uatom = uatom_info(); let uosmo = uosmo_info(); let ujake = ujake_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = unlocked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let liquidator = Addr::unchecked("liquidator"); @@ -307,12 +287,7 @@ fn test_liquidate_locked_vault() { let uatom = uatom_info(); let uosmo = uosmo_info(); let ujake = ujake_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(123021212), - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let liquidator = Addr::unchecked("liquidator"); @@ -407,12 +382,7 @@ fn test_liquidate_unlocking_priority() { let uatom = uatom_info(); let uosmo = uosmo_info(); let ujake = ujake_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(123021212), - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let liquidator = Addr::unchecked("liquidator"); @@ -520,12 +490,7 @@ fn test_liquidation_calculation_adjustment() { let uatom = uatom_info(); let uosmo = uosmo_info(); let ujake = ujake_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = unlocked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let liquidator = Addr::unchecked("liquidator"); diff --git a/contracts/credit-manager/tests/test_vault_deposit.rs b/contracts/credit-manager/tests/test_vault_deposit.rs index a24147605..fb8e01bfd 100644 --- a/contracts/credit-manager/tests/test_vault_deposit.rs +++ b/contracts/credit-manager/tests/test_vault_deposit.rs @@ -6,7 +6,10 @@ use rover::adapters::VaultBase; use rover::error::ContractError; use rover::msg::execute::Action::{Deposit, VaultDeposit}; -use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv, VaultTestInfo}; +use crate::helpers::{ + assert_err, locked_vault_info, uatom_info, unlocked_vault_info, uosmo_info, AccountToFund, + MockEnv, VaultTestInfo, +}; pub mod helpers; @@ -40,11 +43,7 @@ fn test_only_account_owner_can_take_action() { #[test] fn test_all_deposit_coins_are_whitelisted() { let uatom = uatom_info(); - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -73,12 +72,7 @@ fn test_all_deposit_coins_are_whitelisted() { fn test_vault_is_whitelisted() { let uatom = uatom_info(); let uosmo = uosmo_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -114,6 +108,7 @@ fn test_deposited_coins_match_vault_requirements() { denom: "uleverage".to_string(), lockup: None, underlying_denoms: vec!["uatom".to_string(), "ujake".to_string()], + deposit_cap: coin(1_000_000, "uusdc"), }; let user = Addr::unchecked("user"); @@ -147,12 +142,7 @@ fn test_deposited_coins_match_vault_requirements() { fn test_fails_if_not_enough_funds_for_deposit() { let uatom = uatom_info(); let uosmo = uosmo_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -191,12 +181,7 @@ fn test_fails_if_not_enough_funds_for_deposit() { fn test_successful_deposit_into_locked_vault() { let uatom = uatom_info(); let uosmo = uosmo_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(1_209_600u64), - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -258,12 +243,7 @@ fn test_successful_deposit_into_locked_vault() { fn test_successful_deposit_into_unlocked_vault() { let uatom = uatom_info(); let uosmo = uosmo_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -318,3 +298,85 @@ fn test_successful_deposit_into_unlocked_vault() { let vault_token_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); assert_eq!(vault_token_balance.amount, STARTING_VAULT_SHARES) } + +#[test] +fn test_vault_deposit_must_be_under_cap() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + let leverage_vault = unlocked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![coin(9_600_000, "uatom"), coin(18_500_000, "uosmo")], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + // Vault deposit A ✅ + // new total value = 9_500_000 + // left to deposit = 2_845_000 + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(coin(7_000_000, uatom.denom.clone())), + Deposit(coin(10_000_000, uosmo.denom.clone())), + VaultDeposit { + vault: vault.clone(), + coins: vec![coin(7_000_000, "uatom"), coin(10_000_000, "uosmo")], + }, + ], + &[coin(7_000_000, "uatom"), coin(10_000_000, "uosmo")], + ) + .unwrap(); + + // Vault deposit B ✅ + // new total value = 9_850_000 + // left to deposit = 2_495_000 + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(coin(100_000, uatom.denom.clone())), + Deposit(coin(1_000_000, uosmo.denom.clone())), + VaultDeposit { + vault: vault.clone(), + coins: vec![coin(100_000, "uatom"), coin(1_000_000, "uosmo")], + }, + ], + &[coin(100_000, "uatom"), coin(1_000_000, "uosmo")], + ) + .unwrap(); + + // Vault deposit C 🚫 + // new total value = 14_225_000 + // left to deposit = -1_880_000 + let res = mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(coin(2_500_000, uatom.denom)), + Deposit(coin(7_500_000, uosmo.denom)), + VaultDeposit { + vault, + coins: vec![coin(2_500_000, "uatom"), coin(7_500_000, "uosmo")], + }, + ], + &[coin(2_500_000, "uatom"), coin(7_500_000, "uosmo")], + ); + + assert_err( + res, + ContractError::AboveVaultDepositCap { + new_value: "14224999.999999999999394422".to_string(), + maximum: "12345000".to_string(), + }, + ); +} diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs index b15848b55..9765b0ee2 100644 --- a/contracts/credit-manager/tests/test_vault_request_unlock.rs +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -7,17 +7,16 @@ use rover::adapters::VaultUnchecked; use rover::error::ContractError; use rover::msg::execute::Action::{Deposit, VaultDeposit, VaultRequestUnlock}; -use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv, VaultTestInfo}; +use crate::helpers::{ + assert_err, locked_vault_info, uatom_info, unlocked_vault_info, uosmo_info, AccountToFund, + MockEnv, +}; pub mod helpers; #[test] fn test_only_owner_can_request_unlocked() { - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(1_209_600), // 14 days - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -71,11 +70,7 @@ fn test_can_only_take_action_on_whitelisted_vaults() { #[test] fn test_request_when_unnecessary() { - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -106,11 +101,7 @@ fn test_request_when_unnecessary() { #[test] fn test_no_funds_for_request() { - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(1_209_600), // 14 days - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -154,11 +145,7 @@ fn test_not_enough_funds_for_request() { let uatom = uatom_info(); let uosmo = uosmo_info(); - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(1_209_600), // 14 days - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -215,11 +202,7 @@ fn test_request_unlocked() { let uatom = uatom_info(); let uosmo = uosmo_info(); - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(1_209_600), // 14 days - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() diff --git a/contracts/credit-manager/tests/test_vault_withdraw.rs b/contracts/credit-manager/tests/test_vault_withdraw.rs index 206d8feb4..83a5c5fa8 100644 --- a/contracts/credit-manager/tests/test_vault_withdraw.rs +++ b/contracts/credit-manager/tests/test_vault_withdraw.rs @@ -8,7 +8,10 @@ use rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; use rover::msg::execute::Action::{Deposit, VaultDeposit, VaultWithdraw}; use rover::msg::execute::CallbackMsg; -use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv, VaultTestInfo}; +use crate::helpers::{ + assert_err, locked_vault_info, uatom_info, unlocked_vault_info, uosmo_info, AccountToFund, + MockEnv, +}; pub mod helpers; @@ -64,11 +67,7 @@ fn test_no_unlocked_vault_coins_to_withdraw() { let uatom = uatom_info(); let uosmo = uosmo_info(); - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(213231), - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -114,11 +113,7 @@ fn test_no_unlocked_vault_coins_to_withdraw() { #[test] fn test_force_withdraw_can_only_be_called_by_rover() { - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(213231), - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -145,11 +140,7 @@ fn test_force_withdraw_breaks_lock() { let uatom = uatom_info(); let uosmo = uosmo_info(); - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(213231), - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -220,11 +211,7 @@ fn test_withdraw_with_unlocked_vault_coins() { let uatom = uatom_info(); let uosmo = uosmo_info(); - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() diff --git a/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs b/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs index 692f44542..34e174e05 100644 --- a/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs +++ b/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs @@ -10,18 +10,14 @@ use rover::msg::execute::Action::{ use rover::msg::query::Positions; use crate::helpers::{ - assert_err, get_coin, uatom_info, uosmo_info, AccountToFund, MockEnv, VaultTestInfo, + assert_err, get_coin, locked_vault_info, uatom_info, uosmo_info, AccountToFund, MockEnv, }; pub mod helpers; #[test] fn test_only_owner_can_withdraw_unlocked_for_account() { - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(1_209_600), // 14 days - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -71,12 +67,7 @@ fn test_can_only_take_action_on_whitelisted_vaults() { fn test_not_owner_of_unlocking_position() { let uatom = uatom_info(); let uosmo = uosmo_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(1_209_600), // 14 days - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let user_a = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -148,12 +139,7 @@ fn test_not_owner_of_unlocking_position() { fn test_unlocking_position_not_ready() { let uatom = uatom_info(); let uosmo = uosmo_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(1_209_600), // 14 days - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -209,12 +195,7 @@ fn test_unlocking_position_not_ready() { fn test_withdraw_unlock_success() { let uatom = uatom_info(); let uosmo = uosmo_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: Some(1_209_600), // 14 days - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], - }; + let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() diff --git a/contracts/mars-oracle-adapter/src/contract.rs b/contracts/mars-oracle-adapter/src/contract.rs index 59e87b72d..eae9e1bb7 100644 --- a/contracts/mars-oracle-adapter/src/contract.rs +++ b/contracts/mars-oracle-adapter/src/contract.rs @@ -140,9 +140,15 @@ fn calculate_preview_redeem( Ok(new_total) })?; + let price = if total_value.is_zero() { + Decimal::zero() + } else { + total_value.checked_div(total_issued.to_dec()?)? + }; + Ok(PriceResponse { denom: info.denom.clone(), - price: total_value.checked_div(total_issued.to_dec()?)?, + price, }) } diff --git a/contracts/mars-oracle-adapter/tests/helpers.rs b/contracts/mars-oracle-adapter/tests/helpers.rs index 288131219..de456401b 100644 --- a/contracts/mars-oracle-adapter/tests/helpers.rs +++ b/contracts/mars-oracle-adapter/tests/helpers.rs @@ -127,7 +127,7 @@ fn deploy_oracle(app: &mut BasicApp) -> OracleBase { code_id, Addr::unchecked("oracle_contract_owner"), &OracleInstantiateMsg { - coins: vec![ + prices: vec![ CoinPrice { denom: "uosmo".to_string(), price: Decimal::from_atomics(25u128, 2).unwrap(), diff --git a/contracts/mock-oracle/src/contract.rs b/contracts/mock-oracle/src/contract.rs index 3fd2f0b63..a5ecc16de 100644 --- a/contracts/mock-oracle/src/contract.rs +++ b/contracts/mock-oracle/src/contract.rs @@ -14,7 +14,7 @@ pub fn instantiate( _info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { - for item in msg.coins { + for item in msg.prices { COIN_PRICE.save(deps.storage, item.denom, &item.price)? } Ok(Response::default()) diff --git a/contracts/mock-oracle/src/msg.rs b/contracts/mock-oracle/src/msg.rs index a492d1aa5..18789a3e7 100644 --- a/contracts/mock-oracle/src/msg.rs +++ b/contracts/mock-oracle/src/msg.rs @@ -9,7 +9,7 @@ pub struct CoinPrice { #[cw_serde] pub struct InstantiateMsg { - pub coins: Vec, + pub prices: Vec, } #[cw_serde] diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index 5ae04b8cc..d5269dfb2 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -1,6 +1,5 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; - use cosmwasm_std::{ to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, }; diff --git a/contracts/mock-vault/src/query.rs b/contracts/mock-vault/src/query.rs index 53d6637ff..81e7da03c 100644 --- a/contracts/mock-vault/src/query.rs +++ b/contracts/mock-vault/src/query.rs @@ -59,5 +59,8 @@ pub fn query_unlocking_positions(deps: Deps, addr: String) -> StdResult StdResult { - TOTAL_VAULT_SHARES.load(storage) + let amount_issued = TOTAL_VAULT_SHARES + .may_load(storage)? + .unwrap_or(Uint128::zero()); + Ok(amount_issued) } diff --git a/packages/rover/src/adapters/vault.rs b/packages/rover/src/adapters/vault.rs index 31bc8b49d..42560b6c4 100644 --- a/packages/rover/src/adapters/vault.rs +++ b/packages/rover/src/adapters/vault.rs @@ -1,11 +1,9 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, Decimal, OverflowError, + to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, OverflowError, QuerierWrapper, QueryRequest, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, }; -use crate::adapters::Oracle; -use crate::error::ContractResult; use crate::msg::vault::{ExecuteMsg, QueryMsg, UnlockingPosition, VaultInfo}; use crate::traits::Stringify; @@ -208,17 +206,6 @@ impl Vault { Ok(res.amount.amount) } - pub fn query_total_value( - &self, - querier: &QuerierWrapper, - oracle: &Oracle, - addr: &Addr, - ) -> ContractResult { - let balance = self.query_balance(querier, addr)?; - let assets = self.query_preview_redeem(querier, balance)?; - oracle.query_total_value(querier, &assets) - } - pub fn query_preview_redeem( &self, querier: &QuerierWrapper, diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 1209169d7..982fb42a8 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -16,6 +16,9 @@ pub enum ContractError { max_ltv_health_factor: String, }, + #[error("Vault deposit would result in exceeding limit. With deposit: {new_value:?}, Maximum: {maximum:?}")] + AboveVaultDepositCap { new_value: String, maximum: String }, + #[error("{0} is not an available coin to request")] CoinNotAvailable(String), diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index 5c674bd15..8d72b4833 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::Decimal; +use cosmwasm_std::{Coin, Decimal}; use crate::adapters::swap::SwapperUnchecked; use crate::adapters::{OracleUnchecked, RedBankUnchecked, VaultUnchecked}; @@ -11,7 +11,8 @@ pub struct InstantiateMsg { /// Whitelisted coin denoms approved by governance pub allowed_coins: Vec, /// Whitelisted vaults approved by governance that implement credit manager's vault interface - pub allowed_vaults: Vec, + /// Includes a deposit cap that enforces a TLV limit for risk mitigation + pub allowed_vaults: Vec, /// The Mars Protocol money market contract where we borrow assets from pub red_bank: RedBankUnchecked, /// The Mars Protocol oracle contract. We read prices of assets here. @@ -24,6 +25,12 @@ pub struct InstantiateMsg { pub swapper: SwapperUnchecked, } +#[cw_serde] +pub struct VaultInstantiateConfig { + pub vault: VaultUnchecked, + pub deposit_cap: Coin, +} + /// Used when you want to update fields on Instantiate config #[cw_serde] #[derive(Default)] diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 8c74a290c..bc50a7c4f 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -17,6 +17,12 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, + /// Deposit caps on vault deposits + #[returns(Vec)] + DepositCaps { + start_after: Option, + limit: Option, + }, /// Whitelisted coins #[returns(Vec)] AllowedCoins { From d2bd15ff54a59b1cc97bf14ad4a57975bdec98cc Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 21 Oct 2022 20:46:40 +0200 Subject: [PATCH 066/218] New data structure for vault positions (#28) * New data structure for vault positions * Implementing piotrs mutable ref method * Refactoring vault amount enum --- Cargo.lock | 84 ++++++-- contracts/account-nft/Cargo.toml | 4 +- contracts/credit-manager/Cargo.toml | 6 +- contracts/credit-manager/src/contract.rs | 2 +- contracts/credit-manager/src/execute.rs | 17 +- .../credit-manager/src/liquidate_coin.rs | 44 +--- contracts/credit-manager/src/query.rs | 8 +- contracts/credit-manager/src/state.rs | 5 +- contracts/credit-manager/src/vault/deposit.rs | 24 +-- .../src/vault/liquidate_vault.rs | 99 ++++----- .../src/vault/request_unlock.rs | 14 +- contracts/credit-manager/src/vault/utils.rs | 66 +----- .../credit-manager/src/vault/withdraw.rs | 12 +- .../src/vault/withdraw_unlocked.rs | 12 +- .../credit-manager/tests/helpers/mock_env.rs | 3 +- .../tests/test_liquidate_coin.rs | 63 ------ .../tests/test_liquidate_vault.rs | 26 ++- .../tests/test_update_config.rs | 35 +++- .../tests/test_vault_deposit.rs | 17 +- .../tests/test_vault_request_unlock.rs | 4 +- .../tests/test_vault_withdraw.rs | 10 +- .../tests/test_vault_withdraw_unlocked.rs | 28 ++- contracts/mars-oracle-adapter/Cargo.toml | 4 +- contracts/mars-oracle-adapter/src/contract.rs | 19 +- .../mars-oracle-adapter/tests/helpers.rs | 3 +- contracts/mock-oracle/Cargo.toml | 2 +- contracts/mock-red-bank/Cargo.toml | 2 +- contracts/mock-vault/Cargo.toml | 2 +- contracts/swapper/base/Cargo.toml | 2 +- contracts/swapper/mock/Cargo.toml | 4 +- contracts/swapper/osmosis/Cargo.toml | 2 +- packages/rover/Cargo.toml | 4 +- packages/rover/src/adapters/mod.rs | 3 +- packages/rover/src/adapters/vault/amount.rs | 196 ++++++++++++++++++ .../src/adapters/{vault.rs => vault/base.rs} | 49 +---- packages/rover/src/adapters/vault/mod.rs | 5 + packages/rover/src/error.rs | 3 + packages/rover/src/msg/execute.rs | 7 +- packages/rover/src/msg/instantiate.rs | 3 +- packages/rover/src/msg/query.rs | 2 +- packages/rover/src/traits.rs | 13 ++ 41 files changed, 493 insertions(+), 415 deletions(-) create mode 100644 packages/rover/src/adapters/vault/amount.rs rename packages/rover/src/adapters/{vault.rs => vault/base.rs} (85%) create mode 100644 packages/rover/src/adapters/vault/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 8e8a4205a..90f327624 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,8 +9,8 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.15.0", - "cw-storage-plus 0.15.0", + "cw-multi-test 0.16.0", + "cw-storage-plus 0.16.0", "cw721", "cw721-base", ] @@ -173,9 +173,9 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.15.0", - "cw-storage-plus 0.15.0", - "cw2", + "cw-multi-test 0.16.0", + "cw-storage-plus 0.16.0", + "cw2 0.16.0", "cw721", "cw721-base", "itertools", @@ -251,15 +251,14 @@ dependencies = [ [[package]] name = "cw-multi-test" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2519fd64b6b8c7c4e77c317e665bd19b438db3e8e4867241fa6a31cf060feda9" +checksum = "7192aec80d0c01a0e5941392eea7e2b7e212ee74ca7f430bfdc899420c055ef6" dependencies = [ "anyhow", "cosmwasm-std", - "cosmwasm-storage", - "cw-storage-plus 0.15.0", - "cw-utils 0.15.0", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", "derivative", "itertools", "prost", @@ -290,6 +289,17 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-storage-plus" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b6f91c0b94481a3e9ef1ceb183c37d00764f8751e39b45fc09f4d9b970d469" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cw-utils" version = "0.13.4" @@ -310,7 +320,22 @@ checksum = "7a67007ff056f4cd034f361c8ed69780c0180959b9c8037c84f3caa78120faf5" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2", + "cw2 0.15.0", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw-utils" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a84c6c1c0acc3616398eba50783934bd6c964bad6974241eaee3460c8f5b26" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 0.16.0", "schemars", "semver", "serde", @@ -330,6 +355,19 @@ dependencies = [ "serde", ] +[[package]] +name = "cw2" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91398113b806f4d2a8d5f8d05684704a20ffd5968bf87e3473e1973710b884ad" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "schemars", + "serde", +] + [[package]] name = "cw721" version = "0.15.0" @@ -353,7 +391,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 0.15.0", "cw-utils 0.15.0", - "cw2", + "cw2 0.15.0", "cw721", "schemars", "serde", @@ -582,8 +620,8 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.15.0", - "cw-storage-plus 0.15.0", + "cw-multi-test 0.16.0", + "cw-storage-plus 0.16.0", "mars-outpost", "mock-oracle", "mock-vault", @@ -606,7 +644,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.15.0", + "cw-storage-plus 0.16.0", "mars-outpost", ] @@ -616,7 +654,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.15.0", + "cw-storage-plus 0.16.0", "mars-outpost", ] @@ -626,7 +664,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.15.0", + "cw-storage-plus 0.16.0", "rover", "thiserror", ] @@ -751,11 +789,13 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.15.0", + "cw-storage-plus 0.16.0", "mars-health", "mars-outpost", "mock-oracle", "mock-red-bank", + "schemars", + "serde", "thiserror", ] @@ -922,7 +962,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.15.0", + "cw-storage-plus 0.16.0", "rover", "schemars", "serde", @@ -935,8 +975,8 @@ version = "1.0.0" dependencies = [ "anyhow", "cosmwasm-std", - "cw-multi-test 0.15.0", - "cw-storage-plus 0.15.0", + "cw-multi-test 0.16.0", + "cw-storage-plus 0.16.0", "rover", "swapper-base", "thiserror", @@ -950,7 +990,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 0.13.4", - "cw-storage-plus 0.15.0", + "cw-storage-plus 0.16.0", "osmo-bindings", "osmo-bindings-test", "rover", diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index daa7b94b0..9098301f2 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -14,7 +14,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cw-storage-plus = "0.15" +cw-storage-plus = "0.16" cw721 = "0.15" cw721-base = { version = "0.15", features = ["library"] } cosmwasm-schema = "1.1" @@ -22,4 +22,4 @@ cosmwasm-std = "1.1" [dev-dependencies] anyhow = "1" -cw-multi-test = "0.15" +cw-multi-test = "0.16" diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index cc7070863..0a1a12cf0 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -24,15 +24,15 @@ rover = { version = "1.0", path = "../../packages/rover" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" -cw2 = "0.15" +cw2 = "0.16" cw721 = "0.15" cw721-base = { version = "0.15", features = ["library"] } -cw-storage-plus = "0.15" +cw-storage-plus = "0.16" [dev-dependencies] swapper-mock = { version = "1.0", path = "../../contracts/swapper/mock", features = ["library"] } mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } anyhow = "1" -cw-multi-test = "0.15" +cw-multi-test = "0.16" itertools = "0.10" diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index e99f223cc..b79028560 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{ }; use cw2::set_contract_version; -use rover::adapters::VAULT_REQUEST_REPLY_ID; +use rover::adapters::vault::VAULT_REQUEST_REPLY_ID; use rover::error::{ContractError, ContractResult}; use rover::msg::query::HealthResponse; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 3816b9cab..f51d247b4 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -17,7 +17,7 @@ use crate::vault::{ withdraw_from_vault, withdraw_unlocked_from_vault, }; -use crate::liquidate_coin::{assert_health_factor_improved, liquidate_coin}; +use crate::liquidate_coin::liquidate_coin; use crate::swap::swap_exact_in; use crate::update_coin_balances::update_coin_balances; use crate::withdraw::withdraw; @@ -26,7 +26,7 @@ use rover::coins::Coins; use rover::error::{ContractError, ContractResult}; use rover::msg::execute::{Action, CallbackMsg}; use rover::msg::instantiate::ConfigUpdates; -use rover::traits::Stringify; +use rover::traits::{FallbackStr, Stringify}; pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; @@ -87,22 +87,25 @@ pub fn update_config( } if let Some(coins) = new_config.allowed_coins { + ALLOWED_COINS.clear(deps.storage); coins .iter() .try_for_each(|denom| ALLOWED_COINS.save(deps.storage, denom, &Empty {}))?; + response = response .add_attribute("key", "allowed_coins") - .add_attribute("value", coins.join(", ")); + .add_attribute("value", coins.join(", ").fallback("None")); } if let Some(vaults) = new_config.allowed_vaults { + ALLOWED_VAULTS.clear(deps.storage); vaults.iter().try_for_each(|unchecked| { let vault = unchecked.check(deps.api)?; ALLOWED_VAULTS.save(deps.storage, &vault.address, &Empty {}) })?; response = response .add_attribute("key", "allowed_vaults") - .add_attribute("value", vaults.to_string()) + .add_attribute("value", vaults.to_string().fallback("None")) } if let Some(unchecked) = new_config.red_bank { @@ -285,7 +288,7 @@ pub fn execute_callback( account_id, vault, coins, - } => deposit_into_vault(deps, &env.contract.address, &account_id, vault, &coins), + } => deposit_into_vault(deps, &env.contract.address, &account_id, vault, coins), CallbackMsg::UpdateVaultCoinBalance { vault, account_id, @@ -323,10 +326,6 @@ pub fn execute_callback( debt_coin, request_vault, ), - CallbackMsg::AssertHealthFactorImproved { - account_id, - previous_health_factor, - } => assert_health_factor_improved(deps.as_ref(), env, &account_id, previous_health_factor), CallbackMsg::SwapExactIn { account_id, coin_in, diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index f5bbd96b4..f5e7073a9 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -1,7 +1,6 @@ use std::ops::{Add, Div}; -use cosmwasm_std::{Coin, Decimal, Deps, DepsMut, Env, Response, StdError, Uint128}; -use mars_health::health::Health; +use cosmwasm_std::{Coin, Decimal, DepsMut, Env, Response, StdError, Uint128}; use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; @@ -24,7 +23,7 @@ pub fn liquidate_coin( .load(deps.storage, (liquidatee_account_id, request_coin_denom)) .map_err(|_| ContractError::CoinNotAvailable(request_coin_denom.to_string()))?; - let (health, debt, request) = calculate_liquidation( + let (debt, request) = calculate_liquidation( &deps, &env, liquidatee_account_id, @@ -47,16 +46,8 @@ pub fn liquidate_coin( decrement_coin_balance(deps.storage, liquidatee_account_id, &request)?; increment_coin_balance(deps.storage, liquidator_account_id, &request)?; - // Ensure health factor has improved as a consequence of liquidation event - let assert_healthier_msg = (CallbackMsg::AssertHealthFactorImproved { - account_id: liquidatee_account_id.to_string(), - previous_health_factor: health.liquidation_health_factor.unwrap(), // safe unwrap given it was liquidatable - }) - .into_cosmos_msg(&env.contract.address)?; - Ok(Response::new() .add_message(repay_msg) - .add_message(assert_healthier_msg) .add_attribute("action", "rover/credit_manager/liquidate_coin") .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("debt_repaid_denom", debt.denom) @@ -70,7 +61,7 @@ pub fn liquidate_coin( /// - Exceeds liquidatee's total debt for denom /// - Not enough liquidatee request coin balance to match /// - The value of the debt repaid exceeds the maximum close factor % -/// Returns -> (Health, Debt Coin, Request Coin) +/// Returns -> (Debt Coin, Request Coin) pub fn calculate_liquidation( deps: &DepsMut, env: &Env, @@ -78,7 +69,7 @@ pub fn calculate_liquidation( debt_coin: &Coin, request_coin: &str, request_coin_balance: Uint128, -) -> ContractResult<(Health, Coin, Coin)> { +) -> ContractResult<(Coin, Coin)> { // Assert the liquidatee's credit account is liquidatable let health = compute_health(deps.as_ref(), env, liquidatee_account_id)?; if !health.is_liquidatable() { @@ -129,9 +120,8 @@ pub fn calculate_liquidation( .div(request_res.price) .uint128(); - // (Health, Debt Coin, Request Coin) + // (Debt Coin, Request Coin) Ok(( - health, Coin { denom: debt_coin.denom.clone(), amount: final_debt_to_repay, @@ -142,27 +132,3 @@ pub fn calculate_liquidation( }, )) } - -pub fn assert_health_factor_improved( - deps: Deps, - env: Env, - account_id: &str, - prev_hf: Decimal, -) -> ContractResult { - let health = compute_health(deps, &env, account_id)?; - if let Some(hf) = health.liquidation_health_factor { - if prev_hf > hf { - return Err(ContractError::HealthNotImproved { - prev_hf: prev_hf.to_string(), - new_hf: hf.to_string(), - }); - } - } - Ok(Response::new() - .add_attribute( - "action", - "rover/credit_manager/assert_health_factor_improved", - ) - .add_attribute("prev_hf", prev_hf.to_string()) - .add_attribute("new_hf", val_or_na(health.liquidation_health_factor))) -} diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index a3c501692..c68e56f3c 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Addr, Coin, Deps, Env, Order, StdResult, Uint128}; use cw_storage_plus::Bound; -use rover::adapters::{Vault, VaultBase, VaultPosition, VaultUnchecked}; +use rover::adapters::vault::{Vault, VaultBase, VaultPosition, VaultUnchecked}; use rover::error::ContractResult; use rover::msg::instantiate::VaultInstantiateConfig; use rover::msg::query::{ @@ -180,7 +180,7 @@ fn get_vault_positions(deps: Deps, account_id: &str) -> ContractResult>>()? .iter() - .map(|((account_id, addr), state)| VaultPositionResponseItem { + .map(|((account_id, addr), amount)| VaultPositionResponseItem { account_id: account_id.clone(), position: VaultPosition { vault: VaultBase::new(addr.clone()), - state: state.clone(), + amount: amount.clone(), }, }) .collect()) diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 99b0a7fbb..ad916684e 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -3,7 +3,8 @@ use cw_storage_plus::{Item, Map}; use crate::vault::RequestTempStorage; use rover::adapters::swap::Swapper; -use rover::adapters::{Oracle, RedBank, VaultPositionState}; +use rover::adapters::vault::VaultPositionAmount; +use rover::adapters::{Oracle, RedBank}; // Contract config pub const OWNER: Item = Item::new("owner"); @@ -21,7 +22,7 @@ pub const SWAPPER: Item = Item::new("swapper"); pub const COIN_BALANCES: Map<(&str, &str), Uint128> = Map::new("coin_balance"); // Map<(AccountId, Denom), Amount> pub const DEBT_SHARES: Map<(&str, &str), Uint128> = Map::new("debt_shares"); // Map<(AccountId, Denom), Shares> pub const TOTAL_DEBT_SHARES: Map<&str, Uint128> = Map::new("total_debt_shares"); // Map -pub const VAULT_POSITIONS: Map<(&str, Addr), VaultPositionState> = Map::new("vault_positions"); // Map<(AccountId, VaultAddr), VaultPositionState> +pub const VAULT_POSITIONS: Map<(&str, Addr), VaultPositionAmount> = Map::new("vault_positions"); // Map<(AccountId, VaultAddr), VaultPositionAmount> // Temporary state to save variables to be used on reply handling pub const VAULT_REQUEST_TEMP_STORAGE: Item = diff --git a/contracts/credit-manager/src/vault/deposit.rs b/contracts/credit-manager/src/vault/deposit.rs index 3ca9f7946..6849d2491 100644 --- a/contracts/credit-manager/src/vault/deposit.rs +++ b/contracts/credit-manager/src/vault/deposit.rs @@ -3,10 +3,11 @@ use cosmwasm_std::{ WasmMsg, }; -use rover::adapters::{UpdateType, Vault, VaultPositionUpdate}; +use rover::adapters::vault::{UpdateType, Vault, VaultPositionUpdate}; use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg; +use rover::traits::Denoms; use crate::state::{ORACLE, VAULT_DEPOSIT_CAPS}; use crate::utils::{assert_coins_are_whitelisted, contents_equal, decrement_coin_balance}; @@ -17,13 +18,12 @@ pub fn deposit_into_vault( rover_addr: &Addr, account_id: &str, vault: Vault, - coins: &[Coin], + coins: Vec, ) -> ContractResult { - let denoms = coins.iter().map(|c| c.denom.as_str()).collect(); - assert_coins_are_whitelisted(deps.storage, denoms)?; + assert_coins_are_whitelisted(deps.storage, coins.to_denoms())?; assert_vault_is_whitelisted(deps.storage, &vault)?; - assert_denoms_match_vault_reqs(deps.querier, &vault, coins)?; - assert_deposit_is_under_cap(deps.as_ref(), &vault, coins, rover_addr)?; + assert_denoms_match_vault_reqs(deps.querier, &vault, &coins)?; + assert_deposit_is_under_cap(deps.as_ref(), &vault, &coins, rover_addr)?; // Decrement token's coin balance amount coins.iter().try_for_each(|coin| -> ContractResult<_> { @@ -43,7 +43,7 @@ pub fn deposit_into_vault( }); Ok(Response::new() - .add_message(vault.deposit_msg(coins)?) + .add_message(vault.deposit_msg(&coins)?) .add_message(update_vault_balance_msg) .add_attribute("action", "rover/credit_manager/vault/deposit")) } @@ -69,14 +69,8 @@ pub fn update_vault_coin_balance( account_id, &vault.address, match vault_info.lockup { - None => VaultPositionUpdate::Unlocked { - amount: diff, - kind: UpdateType::Increment, - }, - Some(_) => VaultPositionUpdate::Locked { - amount: diff, - kind: UpdateType::Increment, - }, + None => VaultPositionUpdate::Unlocked(UpdateType::Increment(diff)), + Some(_) => VaultPositionUpdate::Locked(UpdateType::Increment(diff)), }, )?; diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index e52f40dfd..960fe2715 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -1,12 +1,14 @@ +use std::cmp::min; + use cosmwasm_std::{ to_binary, Coin, CosmosMsg, DepsMut, Env, QuerierWrapper, Response, Storage, Uint128, WasmMsg, }; -use std::cmp::min; -use rover::adapters::{UpdateType, Vault, VaultPositionState, VaultPositionUpdate}; +use rover::adapters::vault::{ + Total, UnlockingChange, UpdateType, Vault, VaultPositionAmount, VaultPositionUpdate, +}; use rover::error::ContractResult; use rover::msg::execute::CallbackMsg; -use rover::msg::vault::VaultInfo; use rover::msg::ExecuteMsg; use rover::traits::Denoms; @@ -29,13 +31,13 @@ pub fn liquidate_vault( deps.storage, (liquidatee_account_id, request_vault.address.clone()), )?; - let (_health, debt, request) = calculate_liquidation( + let (debt, request) = calculate_liquidation( &deps, &env, liquidatee_account_id, &debt_coin, &vault_info.token_denom, - liquidatee_position.total()?, + liquidatee_position.total(), )?; // Transfer debt coin from liquidator's coin balance to liquidatee @@ -53,7 +55,6 @@ pub fn liquidate_vault( &deps.querier, liquidatee_account_id, &request_vault, - &vault_info, &liquidatee_position, request.amount, )?; @@ -74,19 +75,10 @@ pub fn liquidate_vault( }))?, }); - // TODO: Reviewing with Davide on whether this is necessary - // // Ensure health factor has improved as a consequence of liquidation event - // let assert_healthier_msg = (CallbackMsg::AssertHealthFactorImproved { - // account_id: liquidatee_account_id.to_string(), - // previous_health_factor: health.liquidation_health_factor.unwrap(), - // }) - // .into_cosmos_msg(&env.contract.address)?; - Ok(Response::new() .add_message(repay_msg) .add_messages(vault_withdraw_msgs) .add_message(update_coin_balance_msg) - // .add_message(assert_healthier_msg) .add_attribute("action", "rover/credit_manager/liquidate_vault") .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("debt_repaid_denom", debt.denom) @@ -101,64 +93,57 @@ fn get_vault_withdraw_msgs( querier: &QuerierWrapper, liquidatee_account_id: &str, request_vault: &Vault, - vault_info: &VaultInfo, - liquidatee_position: &VaultPositionState, + liquidatee_position: &VaultPositionAmount, amount: Uint128, ) -> ContractResult> { let mut total_to_liquidate = amount; let mut vault_withdraw_msgs = vec![]; - // No vault lockup indicates it's an unlocked vault. Should liquidate from the UNLOCKED bucket. - if vault_info.lockup.is_none() { - update_vault_position( - storage, - liquidatee_account_id, - &request_vault.address, - VaultPositionUpdate::Unlocked { - amount: total_to_liquidate, - kind: UpdateType::Decrement, - }, - )?; - - let msg = request_vault.withdraw_msg(querier, total_to_liquidate, false)?; - vault_withdraw_msgs.push(msg); - } else { - // A locking vault can have two different positions: LOCKED & UNLOCKING - // Priority goes to force withdrawing the unlocking buckets - for u in &liquidatee_position.unlocking { - let amount = min(u.amount, total_to_liquidate); + match liquidatee_position { + VaultPositionAmount::Unlocked(_) => { update_vault_position( storage, liquidatee_account_id, &request_vault.address, - VaultPositionUpdate::Unlocking { - id: u.id, - amount, - kind: UpdateType::Decrement, - }, + VaultPositionUpdate::Unlocked(UpdateType::Decrement(total_to_liquidate)), )?; - let msg = request_vault.force_withdraw_unlocking_msg(u.id, Some(amount))?; + let msg = request_vault.withdraw_msg(querier, total_to_liquidate, false)?; vault_withdraw_msgs.push(msg); - - total_to_liquidate = total_to_liquidate.checked_sub(amount)?; } + VaultPositionAmount::Locking(amount) => { + // A locking vault can have two different positions: LOCKED & UNLOCKING + // Priority goes to force withdrawing the unlocking buckets + for u in amount.unlocking.positions() { + let amount = min(u.amount, total_to_liquidate); - // If unlocking positions have been exhausted, liquidate from LOCKED bucket - if !total_to_liquidate.is_zero() { - update_vault_position( - storage, - liquidatee_account_id, - &request_vault.address, - VaultPositionUpdate::Locked { - amount: total_to_liquidate, - kind: UpdateType::Decrement, - }, - )?; + update_vault_position( + storage, + liquidatee_account_id, + &request_vault.address, + VaultPositionUpdate::Unlocking(UnlockingChange::Decrement { id: u.id, amount }), + )?; - let msg = request_vault.withdraw_msg(querier, total_to_liquidate, true)?; - vault_withdraw_msgs.push(msg); + let msg = request_vault.force_withdraw_unlocking_msg(u.id, Some(amount))?; + vault_withdraw_msgs.push(msg); + + total_to_liquidate = total_to_liquidate.checked_sub(amount)?; + } + + // If unlocking positions have been exhausted, liquidate from LOCKED bucket + if !total_to_liquidate.is_zero() { + update_vault_position( + storage, + liquidatee_account_id, + &request_vault.address, + VaultPositionUpdate::Locked(UpdateType::Decrement(total_to_liquidate)), + )?; + + let msg = request_vault.withdraw_msg(querier, total_to_liquidate, true)?; + vault_withdraw_msgs.push(msg); + } } } + Ok(vault_withdraw_msgs) } diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index ed0f9c333..6b086d104 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -2,7 +2,9 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, DepsMut, Reply, Response, Uint128}; use crate::state::VAULT_REQUEST_TEMP_STORAGE; -use rover::adapters::{UpdateType, Vault, VaultPositionUpdate}; +use rover::adapters::vault::{ + UnlockingChange, UpdateType, Vault, VaultPositionUpdate, VaultUnlockingPosition, +}; use rover::error::{ContractError, ContractResult}; use rover::extensions::AttrParse; @@ -58,21 +60,17 @@ pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResul deps.storage, &account_id, &vault_addr, - VaultPositionUpdate::Unlocking { + VaultPositionUpdate::Unlocking(UnlockingChange::Add(VaultUnlockingPosition { id: unlock_event.id, amount, - kind: UpdateType::Increment, - }, + })), )?; update_vault_position( deps.storage, &account_id, &vault_addr, - VaultPositionUpdate::Locked { - amount, - kind: UpdateType::Decrement, - }, + VaultPositionUpdate::Locked(UpdateType::Decrement(amount)), )?; VAULT_REQUEST_TEMP_STORAGE.remove(deps.storage); diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 8c7ee638f..50889c582 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -3,9 +3,8 @@ use cosmwasm_std::{ }; use mars_oracle_adapter::msg::QueryMsg::PriceableUnderlying; -use rover::adapters::{ - UpdateType, Vault, VaultPosition, VaultPositionState, VaultPositionUpdate, - VaultUnlockingPosition, +use rover::adapters::vault::{ + Total, Vault, VaultPosition, VaultPositionAmount, VaultPositionUpdate, }; use rover::error::{ContractError, ContractResult}; @@ -25,51 +24,20 @@ pub fn update_vault_position( account_id: &str, vault_addr: &Addr, update: VaultPositionUpdate, -) -> ContractResult { +) -> ContractResult { let path = VAULT_POSITIONS.key((account_id, vault_addr.clone())); - let mut new_position = path.may_load(storage)?.unwrap_or_default(); + let mut amount = path + .may_load(storage)? + .unwrap_or_else(|| update.default_amount()); - match update { - VaultPositionUpdate::Unlocked { amount, kind } => match kind { - UpdateType::Increment => { - new_position.unlocked = new_position.unlocked.checked_add(amount)?; - } - UpdateType::Decrement => { - new_position.unlocked = new_position.unlocked.checked_sub(amount)?; - } - }, - VaultPositionUpdate::Locked { amount, kind } => match kind { - UpdateType::Increment => { - new_position.locked = new_position.locked.checked_add(amount)?; - } - UpdateType::Decrement => { - new_position.locked = new_position.locked.checked_sub(amount)?; - } - }, - VaultPositionUpdate::Unlocking { id, amount, kind } => match kind { - UpdateType::Increment => { - new_position - .unlocking - .push(VaultUnlockingPosition { id, amount }); - } - UpdateType::Decrement => { - let mut matching_unlock = get_unlocking_position(id, &new_position)?; - matching_unlock.amount = matching_unlock.amount.checked_sub(amount)?; + amount.update(update)?; - new_position.unlocking.retain(|p| p.id != id); - if !matching_unlock.amount.is_zero() { - new_position.unlocking.push(matching_unlock); - } - } - }, - } - - if new_position == VaultPositionState::default() { + if amount.total().is_zero() { path.remove(storage); } else { - path.save(storage, &new_position)?; + path.save(storage, &amount)?; } - Ok(new_position) + Ok(amount) } /// Returns the denoms received on a withdraw, inferred by vault entry requirements @@ -93,7 +61,7 @@ pub fn get_priceable_coins(deps: &Deps, positions: &[VaultPosition]) -> Contract let mut coins: Vec = vec![]; for p in positions { let vault_info = p.vault.query_info(&deps.querier)?; - let total_vault_coins = p.state.total()?; + let total_vault_coins = p.amount.total(); let priceable_coins: Vec = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: oracle.address().to_string(), @@ -105,15 +73,3 @@ pub fn get_priceable_coins(deps: &Deps, positions: &[VaultPosition]) -> Contract } Ok(coins) } - -pub fn get_unlocking_position( - position_id: u64, - vault_position: &VaultPositionState, -) -> ContractResult { - vault_position - .unlocking - .iter() - .find(|p| p.id == position_id) - .ok_or_else(|| ContractError::NoPositionMatch(position_id.to_string())) - .cloned() -} diff --git a/contracts/credit-manager/src/vault/withdraw.rs b/contracts/credit-manager/src/vault/withdraw.rs index 7c7ea0bca..cf8d85635 100644 --- a/contracts/credit-manager/src/vault/withdraw.rs +++ b/contracts/credit-manager/src/vault/withdraw.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, Uint128, WasmMsg}; -use rover::adapters::{UpdateType, Vault, VaultPositionUpdate}; +use rover::adapters::vault::{UpdateType, Vault, VaultPositionUpdate}; use rover::error::ContractResult; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg as RoverExecuteMsg; @@ -26,15 +26,9 @@ pub fn withdraw_from_vault( account_id, &vault.address, if force { - VaultPositionUpdate::Locked { - amount, - kind: UpdateType::Decrement, - } + VaultPositionUpdate::Locked(UpdateType::Decrement(amount)) } else { - VaultPositionUpdate::Unlocked { - amount, - kind: UpdateType::Decrement, - } + VaultPositionUpdate::Unlocked(UpdateType::Decrement(amount)) }, )?; diff --git a/contracts/credit-manager/src/vault/withdraw_unlocked.rs b/contracts/credit-manager/src/vault/withdraw_unlocked.rs index 8a5679175..947496616 100644 --- a/contracts/credit-manager/src/vault/withdraw_unlocked.rs +++ b/contracts/credit-manager/src/vault/withdraw_unlocked.rs @@ -1,13 +1,12 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, WasmMsg}; -use rover::adapters::{UpdateType, Vault, VaultPositionUpdate}; +use rover::adapters::vault::{UnlockingChange, Vault, VaultPositionUpdate}; use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; use rover::msg::vault::UnlockingPosition; use rover::msg::ExecuteMsg; use crate::state::VAULT_POSITIONS; -use crate::vault::get_unlocking_position; use crate::vault::utils::{ assert_vault_is_whitelisted, query_withdraw_denom_balances, update_vault_position, }; @@ -22,7 +21,9 @@ pub fn withdraw_unlocked_from_vault( assert_vault_is_whitelisted(deps.storage, &vault)?; let vault_position = VAULT_POSITIONS.load(deps.storage, (account_id, vault.address.clone()))?; - let matching_unlock = get_unlocking_position(position_id, &vault_position)?; + let matching_unlock = vault_position + .get_unlocking_position(position_id) + .ok_or_else(|| ContractError::NoPositionMatch(position_id.to_string()))?; let UnlockingPosition { unlocked_at, .. } = vault.query_unlocking_position_info(&deps.querier, matching_unlock.id)?; if unlocked_at > env.block.time { @@ -33,11 +34,10 @@ pub fn withdraw_unlocked_from_vault( deps.storage, account_id, &vault.address, - VaultPositionUpdate::Unlocking { + VaultPositionUpdate::Unlocking(UnlockingChange::Decrement { id: position_id, amount: matching_unlock.amount, - kind: UpdateType::Decrement, - }, + }), )?; // Updates coin balances for account after the withdraw has taken place diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index de5a65aa2..68fd82245 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -22,7 +22,8 @@ use rover::adapters::swap::QueryMsg::EstimateExactInSwap; use rover::adapters::swap::{ EstimateExactInSwapResponse, InstantiateMsg as SwapperInstantiateMsg, Swapper, SwapperBase, }; -use rover::adapters::{OracleBase, RedBankBase, VaultBase, VaultUnchecked}; +use rover::adapters::vault::{VaultBase, VaultUnchecked}; +use rover::adapters::{OracleBase, RedBankBase}; use rover::msg::execute::{Action, CallbackMsg}; use rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; use rover::msg::query::{ diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 2ee35f69e..32286626c 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -381,69 +381,6 @@ fn test_liquidator_left_in_unhealthy_state() { ) } -#[test] -fn test_liquidatee_not_healthier_after_liquidation() { - let uosmo_info = uosmo_info(); - let uatom_info = uatom_info(); - let liquidator = Addr::unchecked("liquidator"); - let liquidatee = Addr::unchecked("liquidatee"); - let mut mock = MockEnv::new() - // an absurdly high liquidation bonus - .max_liquidation_bonus(Decimal::from_atomics(8u128, 1).unwrap()) - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) - .fund_account(AccountToFund { - addr: liquidatee.clone(), - funds: coins(300, uosmo_info.denom.clone()), - }) - .fund_account(AccountToFund { - addr: liquidator.clone(), - funds: coins(300, uatom_info.denom.clone()), - }) - .build() - .unwrap(); - let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); - - mock.update_credit_account( - &liquidatee_account_id, - &liquidatee, - vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(100)), - ], - &[Coin::new(300, uosmo_info.denom.clone())], - ) - .unwrap(); - - mock.price_change(CoinPrice { - denom: uatom_info.denom.clone(), - price: 20.to_dec().unwrap(), - }); - - let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); - - let res = mock.update_credit_account( - &liquidator_account_id, - &liquidator, - vec![ - Deposit(uatom_info.to_coin(50)), - LiquidateCoin { - liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: uatom_info.to_coin(50), - request_coin_denom: uosmo_info.denom, - }, - ], - &[uatom_info.to_coin(50)], - ); - - assert_err( - res, - ContractError::HealthNotImproved { - prev_hf: "0.920049504950495049".to_string(), - new_hf: "0.910272727272727272".to_string(), - }, - ) -} - #[test] fn test_debt_amount_adjusted_to_close_factor_max() { let uosmo_info = uosmo_info(); diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 98fc2d578..34dc2b460 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -3,7 +3,7 @@ use cosmwasm_std::StdError::NotFound; use cosmwasm_std::{Addr, OverflowError, Uint128}; use mock_oracle::msg::CoinPrice; -use rover::adapters::VaultBase; +use rover::adapters::vault::VaultBase; use rover::error::ContractError; use rover::msg::execute::Action::{ Borrow, Deposit, LiquidateVault, VaultDeposit, VaultRequestUnlock, @@ -63,9 +63,7 @@ fn test_liquidatee_must_have_the_request_vault_position() { assert_err( res, - ContractError::Std(NotFound { - kind: "rover::adapters::vault::VaultPositionState".to_string(), - }), + ContractError::Std(NotFound { kind: "rover::adapters::vault::amount::VaultPositionAmountBase".to_string() }), ) } @@ -261,7 +259,7 @@ fn test_liquidate_unlocked_vault() { // Assert liquidatee's new position let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); - let vault_balance = position.vaults.first().unwrap().state.unlocked; + let vault_balance = position.vaults.first().unwrap().amount.unlocked(); assert_eq!(vault_balance, Uint128::new(300_000)); // Vault position liquidated by 70% assert_eq!(position.coins.len(), 1); @@ -354,10 +352,10 @@ fn test_liquidate_locked_vault() { // Assert liquidatee's new position let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); - let vault_state = position.vaults.first().unwrap().state.clone(); + let vault_amount = position.vaults.first().unwrap().amount.clone(); // Vault position liquidated by 70%. Unlocking first, then locked bucket. - assert_eq!(vault_state.unlocking.len(), 0); - assert_eq!(vault_state.locked, Uint128::new(300_000)); + assert_eq!(vault_amount.unlocking().len(), 0); + assert_eq!(vault_amount.locked(), Uint128::new(300_000)); assert_eq!(position.coins.len(), 1); let jake_balance = get_coin("ujake", &position.coins); @@ -457,12 +455,12 @@ fn test_liquidate_unlocking_priority() { // Assert liquidatee's new position let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); - let vault_state = position.vaults.first().unwrap().state.clone(); - assert_eq!(vault_state.unlocked, Uint128::zero()); - assert_eq!(vault_state.locked, Uint128::new(90_000)); - assert_eq!(vault_state.unlocking.len(), 1); + let vault_amount = position.vaults.first().unwrap().amount.clone(); + assert_eq!(vault_amount.unlocked(), Uint128::zero()); + assert_eq!(vault_amount.locked(), Uint128::new(90_000)); + assert_eq!(vault_amount.unlocking().len(), 1); assert_eq!( - vault_state.unlocking.first().unwrap().amount, + vault_amount.unlocking().first().unwrap().amount, Uint128::new(210_000) ); @@ -555,7 +553,7 @@ fn test_liquidation_calculation_adjustment() { // Assert liquidatee's new position let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); - let vault_balance = position.vaults.first().unwrap().state.unlocked; + let vault_balance = position.vaults.first().unwrap().amount.unlocked(); assert_eq!(vault_balance, Uint128::new(20_000)); // Vault position liquidated by 98% assert_eq!(position.coins.len(), 1); diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index fae592be0..8c569b6a7 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -1,10 +1,11 @@ use cosmwasm_std::{Addr, Decimal}; use rover::adapters::swap::SwapperBase; -use rover::adapters::{OracleBase, RedBankBase, VaultBase}; +use rover::adapters::vault::VaultBase; +use rover::adapters::{OracleBase, RedBankBase}; use rover::msg::instantiate::ConfigUpdates; -use crate::helpers::MockEnv; +use crate::helpers::{locked_vault_info, uatom_info, uosmo_info, MockEnv}; pub mod helpers; @@ -141,6 +142,36 @@ fn test_update_config_works_with_some_config() { assert_eq!(new_config.red_bank, original_config.red_bank); } +#[test] +fn test_update_config_removes_properly() { + let uatom = uatom_info(); + let uosmo = uosmo_info(); + let leverage_vault = locked_vault_info(); + + let mut mock = MockEnv::new() + .allowed_coins(&[uatom, uosmo]) + .allowed_vaults(&[leverage_vault]) + .build() + .unwrap(); + + mock.update_config( + &Addr::unchecked(mock.query_config().owner), + ConfigUpdates { + allowed_coins: Some(vec![]), + allowed_vaults: Some(vec![]), + ..Default::default() + }, + ) + .unwrap(); + + let new_allowed_vaults = mock.query_allowed_vaults(None, None); + let new_allowed_coins = mock.query_allowed_coins(None, None); + + // All allowed vaults and coins removed + assert_eq!(new_allowed_vaults.len(), 0); + assert_eq!(new_allowed_coins.len(), 0); +} + #[test] fn test_update_config_does_nothing_when_nothing_is_passed() { let mut mock = MockEnv::new().build().unwrap(); diff --git a/contracts/credit-manager/tests/test_vault_deposit.rs b/contracts/credit-manager/tests/test_vault_deposit.rs index fb8e01bfd..e9ba0124e 100644 --- a/contracts/credit-manager/tests/test_vault_deposit.rs +++ b/contracts/credit-manager/tests/test_vault_deposit.rs @@ -2,7 +2,7 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::{coin, coins, Addr, OverflowError, Uint128}; use mock_vault::contract::STARTING_VAULT_SHARES; -use rover::adapters::VaultBase; +use rover::adapters::vault::VaultBase; use rover::error::ContractError; use rover::msg::execute::Action::{Deposit, VaultDeposit}; @@ -221,11 +221,14 @@ fn test_successful_deposit_into_locked_vault() { assert_eq!(res.vaults.len(), 1); assert_eq!( STARTING_VAULT_SHARES, - res.vaults.first().unwrap().state.locked + res.vaults.first().unwrap().amount.locked() + ); + assert_eq!( + Uint128::zero(), + res.vaults.first().unwrap().amount.unlocked() ); - assert_eq!(Uint128::zero(), res.vaults.first().unwrap().state.unlocked); - let assets = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().state.locked); + let assets = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().amount.locked()); let osmo_withdraw = assets.iter().find(|coin| coin.denom == "uosmo").unwrap(); assert_eq!(osmo_withdraw.amount, Uint128::new(120)); @@ -281,11 +284,11 @@ fn test_successful_deposit_into_unlocked_vault() { assert_eq!(res.vaults.len(), 1); assert_eq!( STARTING_VAULT_SHARES, - res.vaults.first().unwrap().state.unlocked + res.vaults.first().unwrap().amount.unlocked() ); - assert_eq!(Uint128::zero(), res.vaults.first().unwrap().state.locked); + assert_eq!(Uint128::zero(), res.vaults.first().unwrap().amount.locked()); - let assets = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().state.unlocked); + let assets = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().amount.unlocked()); let osmo_withdraw = assets.iter().find(|coin| coin.denom == "uosmo").unwrap(); assert_eq!(osmo_withdraw.amount, Uint128::new(120)); diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs index 9765b0ee2..e7f48bf4d 100644 --- a/contracts/credit-manager/tests/test_vault_request_unlock.rs +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{coin, coins, Addr, OverflowError, Uint128}; use cw_multi_test::{BankSudo, SudoMsg}; use mock_vault::contract::STARTING_VAULT_SHARES; -use rover::adapters::VaultUnchecked; +use rover::adapters::vault::VaultUnchecked; use rover::error::ContractError; use rover::msg::execute::Action::{Deposit, VaultDeposit, VaultRequestUnlock}; @@ -240,7 +240,7 @@ fn test_request_unlocked() { // Assert token's position with Rover let res = mock.query_positions(&account_id); assert_eq!(res.vaults.len(), 1); - let unlocking = res.vaults.first().unwrap().state.unlocking.clone(); + let unlocking = res.vaults.first().unwrap().amount.unlocking(); assert_eq!(unlocking.len(), 1); let first = unlocking.first().unwrap(); assert_eq!(first.amount, STARTING_VAULT_SHARES); diff --git a/contracts/credit-manager/tests/test_vault_withdraw.rs b/contracts/credit-manager/tests/test_vault_withdraw.rs index 83a5c5fa8..f582d0ff9 100644 --- a/contracts/credit-manager/tests/test_vault_withdraw.rs +++ b/contracts/credit-manager/tests/test_vault_withdraw.rs @@ -2,7 +2,7 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::{coin, Addr, Coin, OverflowError, Uint128}; use mock_vault::contract::STARTING_VAULT_SHARES; -use rover::adapters::VaultBase; +use rover::adapters::vault::VaultBase; use rover::error::ContractError; use rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; use rover::msg::execute::Action::{Deposit, VaultDeposit, VaultWithdraw}; @@ -89,10 +89,6 @@ fn test_no_unlocked_vault_coins_to_withdraw() { vec![ Deposit(coin(200, uatom.denom)), Deposit(coin(200, uosmo.denom)), - VaultDeposit { - vault: vault.clone(), - coins: vec![coin(100, "uatom"), coin(100, "uosmo")], - }, VaultWithdraw { vault, amount: STARTING_VAULT_SHARES, @@ -175,7 +171,7 @@ fn test_force_withdraw_breaks_lock() { let res = mock.query_positions(&account_id); assert_eq!(res.vaults.len(), 1); let v = res.vaults.first().unwrap(); - assert_eq!(v.state.locked, STARTING_VAULT_SHARES); + assert_eq!(v.amount.locked(), STARTING_VAULT_SHARES); mock.invoke_callback( &mock.rover.clone(), @@ -246,7 +242,7 @@ fn test_withdraw_with_unlocked_vault_coins() { let res = mock.query_positions(&account_id); assert_eq!(res.vaults.len(), 1); let v = res.vaults.first().unwrap(); - assert_eq!(v.state.unlocked, STARTING_VAULT_SHARES); + assert_eq!(v.amount.unlocked(), STARTING_VAULT_SHARES); let atom = get_coin("uatom", &res.coins); assert_eq!(atom.amount, Uint128::from(100u128)); let osmo = get_coin("uosmo", &res.coins); diff --git a/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs b/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs index 34e174e05..33db9a31e 100644 --- a/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs +++ b/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs @@ -2,7 +2,7 @@ use cosmwasm_std::StdError::NotFound; use cosmwasm_std::{coin, Addr, Uint128}; use mock_vault::contract::STARTING_VAULT_SHARES; -use rover::adapters::VaultUnchecked; +use rover::adapters::vault::VaultUnchecked; use rover::error::ContractError; use rover::msg::execute::Action::{ Deposit, VaultDeposit, VaultRequestUnlock, VaultWithdrawUnlocked, @@ -108,8 +108,8 @@ fn test_not_owner_of_unlocking_position() { .vaults .first() .unwrap() - .state - .unlocking + .amount + .unlocking() .first() .unwrap() .id; @@ -129,9 +129,7 @@ fn test_not_owner_of_unlocking_position() { assert_err( res, - ContractError::Std(NotFound { - kind: "rover::adapters::vault::VaultPositionState".to_string(), - }), + ContractError::Std(NotFound { kind: "rover::adapters::vault::amount::VaultPositionAmountBase".to_string() }), ); } @@ -176,7 +174,14 @@ fn test_unlocking_position_not_ready() { let Positions { vaults, .. } = mock.query_positions(&account_id); - let position_id = vaults.first().unwrap().state.unlocking.first().unwrap().id; + let position_id = vaults + .first() + .unwrap() + .amount + .unlocking() + .first() + .unwrap() + .id; let res = mock.update_credit_account( &account_id, @@ -240,7 +245,14 @@ fn test_withdraw_unlock_success() { let Positions { vaults, .. } = mock.query_positions(&account_id); - let position_id = vaults.first().unwrap().state.unlocking.first().unwrap().id; + let position_id = vaults + .first() + .unwrap() + .amount + .unlocking() + .first() + .unwrap() + .id; mock.update_credit_account( &account_id, diff --git a/contracts/mars-oracle-adapter/Cargo.toml b/contracts/mars-oracle-adapter/Cargo.toml index e509a446d..69757b71a 100644 --- a/contracts/mars-oracle-adapter/Cargo.toml +++ b/contracts/mars-oracle-adapter/Cargo.toml @@ -19,7 +19,7 @@ rover = { version = "1.0", path = "../../packages/rover" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" -cw-storage-plus = "0.15" +cw-storage-plus = "0.16" thiserror = "1.0" [dev-dependencies] @@ -27,4 +27,4 @@ mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features mock-vault = { version = "1.0", path = "../../contracts/mock-vault", features = ["library"] } anyhow = "1" -cw-multi-test = "0.15" +cw-multi-test = "0.16" diff --git a/contracts/mars-oracle-adapter/src/contract.rs b/contracts/mars-oracle-adapter/src/contract.rs index eae9e1bb7..424eca5f1 100644 --- a/contracts/mars-oracle-adapter/src/contract.rs +++ b/contracts/mars-oracle-adapter/src/contract.rs @@ -1,14 +1,14 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::Order::Ascending; use cosmwasm_std::{ to_binary, Addr, Binary, Coin, Decimal, Deps, DepsMut, Env, MessageInfo, Order, Response, - StdResult, Storage, + StdResult, }; use cw_storage_plus::Bound; use mars_outpost::oracle::PriceResponse; -use rover::adapters::{Oracle, VaultBase}; +use rover::adapters::vault::VaultBase; +use rover::adapters::Oracle; use rover::traits::IntoDecimal; use crate::error::{ContractError, ContractResult}; @@ -192,7 +192,7 @@ pub fn update_config( } if let Some(vault_pricing) = new_config.vault_pricing { - clear_map(deps.storage)?; + VAULT_PRICING_INFO.clear(deps.storage); for info in &vault_pricing { VAULT_PRICING_INFO.save(deps.storage, &info.denom, info)?; } @@ -212,14 +212,3 @@ pub fn update_config( Ok(response) } - -fn clear_map(storage: &mut dyn Storage) -> ContractResult<()> { - VAULT_PRICING_INFO - .range(storage, None, None, Ascending) - .collect::>>()? - .iter() - .for_each(|(denom, _)| { - VAULT_PRICING_INFO.remove(storage, denom); - }); - Ok(()) -} diff --git a/contracts/mars-oracle-adapter/tests/helpers.rs b/contracts/mars-oracle-adapter/tests/helpers.rs index de456401b..77a214047 100644 --- a/contracts/mars-oracle-adapter/tests/helpers.rs +++ b/contracts/mars-oracle-adapter/tests/helpers.rs @@ -15,7 +15,8 @@ use mock_vault::contract::{ DEFAULT_VAULT_TOKEN_PREFUND, }; use mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; -use rover::adapters::{OracleBase, OracleUnchecked, VaultBase}; +use rover::adapters::vault::VaultBase; +use rover::adapters::{OracleBase, OracleUnchecked}; pub fn mock_vault_info() -> VaultTestInfo { VaultTestInfo { diff --git a/contracts/mock-oracle/Cargo.toml b/contracts/mock-oracle/Cargo.toml index 49d20271e..694c4200d 100644 --- a/contracts/mock-oracle/Cargo.toml +++ b/contracts/mock-oracle/Cargo.toml @@ -18,4 +18,4 @@ mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" -cw-storage-plus = "0.15" +cw-storage-plus = "0.16" diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index 770196411..b59fc423b 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -18,4 +18,4 @@ mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" -cw-storage-plus = "0.15" +cw-storage-plus = "0.16" diff --git a/contracts/mock-vault/Cargo.toml b/contracts/mock-vault/Cargo.toml index 6b25c0fbd..9c58dfa87 100644 --- a/contracts/mock-vault/Cargo.toml +++ b/contracts/mock-vault/Cargo.toml @@ -18,5 +18,5 @@ rover = { version = "1.0", path = "../../packages/rover" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" -cw-storage-plus = "0.15" +cw-storage-plus = "0.16" thiserror = "1.0" diff --git a/contracts/swapper/base/Cargo.toml b/contracts/swapper/base/Cargo.toml index 5f8a10893..39afa2c2a 100644 --- a/contracts/swapper/base/Cargo.toml +++ b/contracts/swapper/base/Cargo.toml @@ -18,7 +18,7 @@ rover = { version = "1.0", path = "../../../packages/rover" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" -cw-storage-plus = "0.15" +cw-storage-plus = "0.16" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" diff --git a/contracts/swapper/mock/Cargo.toml b/contracts/swapper/mock/Cargo.toml index b1e8f762f..d6944983f 100644 --- a/contracts/swapper/mock/Cargo.toml +++ b/contracts/swapper/mock/Cargo.toml @@ -18,9 +18,9 @@ rover = { version = "1.0", path = "../../../packages/rover" } swapper-base = { path = "../base", version = "1.0" } cosmwasm-std = "1.1" -cw-storage-plus = "0.15" +cw-storage-plus = "0.16" thiserror = "1.0" [dev-dependencies] anyhow = "1" -cw-multi-test = "0.15" +cw-multi-test = "0.16" diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index 4cc49c8c7..c219d849e 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -19,7 +19,7 @@ swapper-base = { path = "../base", version = "1.0" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" -cw-storage-plus = "0.15" +cw-storage-plus = "0.16" osmo-bindings = "0.5" schemars = "0.8" thiserror = "1.0" diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index d2e99badb..0bb52259a 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -17,5 +17,7 @@ mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features cosmwasm-schema = "1.1" cosmwasm-std = "1.1" -cw-storage-plus = "0.15" +cw-storage-plus = "0.16" +schemars = "0.8" +serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" diff --git a/packages/rover/src/adapters/mod.rs b/packages/rover/src/adapters/mod.rs index 89c29b93b..d54da8a17 100644 --- a/packages/rover/src/adapters/mod.rs +++ b/packages/rover/src/adapters/mod.rs @@ -1,9 +1,8 @@ mod oracle; mod red_bank; -mod vault; pub mod swap; +pub mod vault; pub use self::oracle::*; pub use self::red_bank::*; -pub use self::vault::*; diff --git a/packages/rover/src/adapters/vault/amount.rs b/packages/rover/src/adapters/vault/amount.rs new file mode 100644 index 000000000..917d0880a --- /dev/null +++ b/packages/rover/src/adapters/vault/amount.rs @@ -0,0 +1,196 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Uint128; +use std::ops::Add; + +use crate::adapters::vault::VaultUnlockingPosition; +use crate::error::{ContractError, ContractResult}; + +#[cw_serde] +pub enum UpdateType { + Increment(Uint128), + Decrement(Uint128), +} + +#[cw_serde] +pub enum UnlockingChange { + Add(VaultUnlockingPosition), + Decrement { id: u64, amount: Uint128 }, +} + +#[cw_serde] +pub enum VaultPositionUpdate { + Unlocked(UpdateType), + Locked(UpdateType), + Unlocking(UnlockingChange), +} + +impl VaultPositionUpdate { + pub fn default_amount(&self) -> VaultPositionAmount { + match self { + VaultPositionUpdate::Unlocked { .. } => { + VaultPositionAmount::Unlocked(VaultAmount(Uint128::zero())) + } + _ => VaultPositionAmount::Locking(LockingVaultAmount { + locked: VaultAmount(Uint128::zero()), + unlocking: UnlockingPositions(vec![]), + }), + } + } +} + +pub type VaultPositionAmount = VaultPositionAmountBase; + +impl Total for VaultPositionAmount { + fn total(&self) -> Uint128 { + match self { + VaultPositionAmount::Unlocked(a) => a.total(), + VaultPositionAmount::Locking(a) => a.total(), + } + } +} + +impl VaultPositionAmount { + pub fn unlocked(&self) -> Uint128 { + match self { + VaultPositionAmount::Unlocked(amount) => amount.total(), + _ => Uint128::zero(), + } + } + + pub fn locked(&self) -> Uint128 { + match self { + VaultPositionAmount::Locking(amount) => amount.locked.total(), + _ => Uint128::zero(), + } + } + + pub fn unlocking(&self) -> Vec { + match self { + VaultPositionAmount::Locking(amount) => amount.unlocking.positions(), + _ => vec![], + } + } + + pub fn get_unlocking_position(&self, id: u64) -> Option { + match self { + VaultPositionAmount::Locking(amount) => amount + .unlocking + .positions() + .iter() + .find(|p| p.id == id) + .cloned(), + _ => None, + } + } + + pub fn update(&mut self, update: VaultPositionUpdate) -> ContractResult<()> { + match self { + VaultPositionAmount::Unlocked(amount) => match update { + VaultPositionUpdate::Unlocked(u) => match u { + UpdateType::Increment(a) => amount.increment(a), + UpdateType::Decrement(a) => amount.decrement(a), + }, + _ => Err(ContractError::MismatchedVaultType {}), + }, + VaultPositionAmount::Locking(amount) => match update { + VaultPositionUpdate::Locked(u) => match u { + UpdateType::Increment(a) => amount.locked.increment(a), + UpdateType::Decrement(a) => amount.locked.decrement(a), + }, + VaultPositionUpdate::Unlocking(u) => match u { + UnlockingChange::Add(p) => amount.unlocking.add(p), + UnlockingChange::Decrement { id, amount: a } => { + amount.unlocking.decrement(id, a) + } + }, + _ => Err(ContractError::MismatchedVaultType {}), + }, + } + } +} + +pub trait Total { + fn total(&self) -> Uint128; +} + +#[cw_serde] +pub enum VaultPositionAmountBase +where + U: Total, + L: Total, +{ + Unlocked(U), + Locking(L), +} + +#[cw_serde] +pub struct VaultAmount(Uint128); + +impl Total for VaultAmount { + fn total(&self) -> Uint128 { + self.0 + } +} + +impl VaultAmount { + pub fn increment(&mut self, amount: Uint128) -> ContractResult<()> { + self.0 = self.0.checked_add(amount)?; + Ok(()) + } + + pub fn decrement(&mut self, amount: Uint128) -> ContractResult<()> { + self.0 = self.0.checked_sub(amount)?; + Ok(()) + } +} + +#[cw_serde] +pub struct LockingVaultAmount { + pub locked: VaultAmount, + pub unlocking: UnlockingPositions, +} + +impl Total for LockingVaultAmount { + fn total(&self) -> Uint128 { + self.locked.total().add(self.unlocking.total()) + } +} + +#[cw_serde] +pub struct UnlockingPositions(Vec); + +impl UnlockingPositions { + pub fn positions(&self) -> Vec { + self.0.clone() + } + + pub fn total(&self) -> Uint128 { + self.0.iter().map(|u| u.amount).sum() + } + + pub fn add(&mut self, position: VaultUnlockingPosition) -> ContractResult<()> { + self.0.push(position); + Ok(()) + } + + pub fn decrement(&mut self, id: u64, amount: Uint128) -> ContractResult<()> { + let res = self.0.iter_mut().find(|p| p.id == id); + match res { + Some(p) => { + let new_amount = p.amount.checked_sub(amount)?; + if new_amount.is_zero() { + self.remove(id)?; + } else { + p.amount = new_amount; + } + } + None => return Err(ContractError::NoPositionMatch(id.to_string())), + } + Ok(()) + } + + pub fn remove(&mut self, id: u64) -> ContractResult<()> { + self.0.retain(|p| p.id != id); + Ok(()) + } +} diff --git a/packages/rover/src/adapters/vault.rs b/packages/rover/src/adapters/vault/base.rs similarity index 85% rename from packages/rover/src/adapters/vault.rs rename to packages/rover/src/adapters/vault/base.rs index 42560b6c4..e944bb395 100644 --- a/packages/rover/src/adapters/vault.rs +++ b/packages/rover/src/adapters/vault/base.rs @@ -1,45 +1,17 @@ +use std::hash::Hash; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, OverflowError, - QuerierWrapper, QueryRequest, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, + to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, QuerierWrapper, + QueryRequest, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, }; +use crate::adapters::vault::VaultPositionAmount; use crate::msg::vault::{ExecuteMsg, QueryMsg, UnlockingPosition, VaultInfo}; use crate::traits::Stringify; pub const VAULT_REQUEST_REPLY_ID: u64 = 10_001; -#[cw_serde] -#[derive(Default)] -pub struct VaultPositionState { - pub unlocked: Uint128, - pub locked: Uint128, - pub unlocking: Vec, -} - -#[cw_serde] -pub enum UpdateType { - Increment, - Decrement, -} - -#[cw_serde] -pub enum VaultPositionUpdate { - Unlocked { - amount: Uint128, - kind: UpdateType, - }, - Locked { - amount: Uint128, - kind: UpdateType, - }, - Unlocking { - id: UnlockingId, - amount: Uint128, - kind: UpdateType, - }, -} - pub type UnlockingId = u64; #[cw_serde] @@ -50,19 +22,10 @@ pub struct VaultUnlockingPosition { pub amount: Uint128, } -impl VaultPositionState { - pub fn total(&self) -> Result { - let total_unlocking = self.unlocking.iter().map(|u| u.amount).sum(); - self.locked - .checked_add(self.unlocked)? - .checked_add(total_unlocking) - } -} - #[cw_serde] pub struct VaultPosition { pub vault: Vault, - pub state: VaultPositionState, + pub amount: VaultPositionAmount, } #[cw_serde] diff --git a/packages/rover/src/adapters/vault/mod.rs b/packages/rover/src/adapters/vault/mod.rs new file mode 100644 index 000000000..9c66ae9c1 --- /dev/null +++ b/packages/rover/src/adapters/vault/mod.rs @@ -0,0 +1,5 @@ +mod amount; +mod base; + +pub use self::amount::*; +pub use self::base::*; diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 982fb42a8..ca206a9a2 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -48,6 +48,9 @@ pub enum ContractError { )] HealthNotImproved { prev_hf: String, new_hf: String }, + #[error("Issued incorrect action for vault type")] + MismatchedVaultType, + #[error("No coin amount set for action")] NoAmount, diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 5280c7f7d..a30493e35 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; -use crate::adapters::{Vault, VaultUnchecked}; +use crate::adapters::vault::{Vault, VaultUnchecked}; use crate::msg::instantiate::ConfigUpdates; #[cw_serde] @@ -159,11 +159,6 @@ pub enum CallbackMsg { debt_coin: Coin, request_vault: Vault, }, - /// Determine health factor improved as a consequence of liquidation event - AssertHealthFactorImproved { - account_id: String, - previous_health_factor: Decimal, - }, /// Perform a swapper with an exact-in amount. Requires slippage allowance %. SwapExactIn { account_id: String, diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index 8d72b4833..f30423383 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -2,7 +2,8 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Decimal}; use crate::adapters::swap::SwapperUnchecked; -use crate::adapters::{OracleUnchecked, RedBankUnchecked, VaultUnchecked}; +use crate::adapters::vault::VaultUnchecked; +use crate::adapters::{OracleUnchecked, RedBankUnchecked}; #[cw_serde] pub struct InstantiateMsg { diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index bc50a7c4f..da4564b9c 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Decimal, Uint128}; use mars_health::health::Health; -use crate::adapters::{Vault, VaultPosition, VaultUnchecked}; +use crate::adapters::vault::{Vault, VaultPosition, VaultUnchecked}; use crate::traits::Coins; #[cw_serde] diff --git a/packages/rover/src/traits.rs b/packages/rover/src/traits.rs index ca0b128f4..0cd8cb1e0 100644 --- a/packages/rover/src/traits.rs +++ b/packages/rover/src/traits.rs @@ -37,3 +37,16 @@ impl IntoDecimal for u128 { Decimal::from_atomics(*self, 0) } } + +pub trait FallbackStr { + fn fallback(&self, fallback: &str) -> String; +} + +impl FallbackStr for String { + fn fallback(&self, fallback: &str) -> String { + match self { + s if !s.is_empty() => s.clone(), + _ => fallback.to_string(), + } + } +} From b77739083c4cefe4383b6975f67b834d420fe310 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 27 Oct 2022 11:49:01 +0200 Subject: [PATCH 067/218] Vault liq threshold support (#30) * Vault liq threshold support * add vault config validation --- contracts/credit-manager/src/contract.rs | 11 +- contracts/credit-manager/src/execute.rs | 19 +-- contracts/credit-manager/src/health.rs | 75 +++++++--- contracts/credit-manager/src/instantiate.rs | 21 +-- contracts/credit-manager/src/query.rs | 42 +----- contracts/credit-manager/src/state.rs | 7 +- contracts/credit-manager/src/vault/deposit.rs | 6 +- contracts/credit-manager/src/vault/utils.rs | 8 +- .../credit-manager/tests/helpers/builders.rs | 2 + .../tests/helpers/mock_entity_info.rs | 2 + .../credit-manager/tests/helpers/mock_env.rs | 38 +++--- .../credit-manager/tests/helpers/types.rs | 2 + .../tests/test_enumerate_allowed_vaults.rs | 57 -------- ...aps.rs => test_enumerate_vault_configs.rs} | 17 ++- .../credit-manager/tests/test_instantiate.rs | 49 ++++++- .../tests/test_liquidate_coin.rs | 2 +- .../tests/test_liquidate_vault.rs | 36 +++-- .../tests/test_update_config.rs | 128 ++++++++++++++---- .../tests/test_vault_deposit.rs | 4 +- packages/rover/src/adapters/vault/base.rs | 25 +++- packages/rover/src/error.rs | 3 + packages/rover/src/msg/instantiate.rs | 29 +++- packages/rover/src/msg/query.rs | 10 +- 23 files changed, 361 insertions(+), 232 deletions(-) delete mode 100644 contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs rename contracts/credit-manager/tests/{test_enumerate_deposit_caps.rs => test_enumerate_vault_configs.rs} (68%) diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index b79028560..c432540f1 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -14,8 +14,8 @@ use crate::instantiate::store_config; use crate::query::{ query_all_coin_balances, query_all_debt_shares, query_all_total_debt_shares, query_all_total_vault_coin_balances, query_all_vault_positions, query_allowed_coins, - query_allowed_vaults, query_config, query_positions, query_total_debt_shares, - query_total_vault_coin_balance, query_vault_deposit_caps, + query_config, query_positions, query_total_debt_shares, query_total_vault_coin_balance, + query_vault_configs, }; use crate::vault::handle_unlock_request_reply; @@ -64,11 +64,8 @@ pub fn reply(deps: DepsMut, _: Env, reply: Reply) -> ContractResult { pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { let res = match msg { QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::AllowedVaults { start_after, limit } => { - to_binary(&query_allowed_vaults(deps, start_after, limit)?) - } - QueryMsg::DepositCaps { start_after, limit } => { - to_binary(&query_vault_deposit_caps(deps, start_after, limit)?) + QueryMsg::VaultConfigs { start_after, limit } => { + to_binary(&query_vault_configs(deps, start_after, limit)?) } QueryMsg::AllowedCoins { start_after, limit } => { to_binary(&query_allowed_coins(deps, start_after, limit)?) diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index f51d247b4..a1dd90a57 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -9,8 +9,8 @@ use crate::deposit::deposit; use crate::health::assert_below_max_ltv; use crate::repay::repay; use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, - OWNER, RED_BANK, SWAPPER, + ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, + SWAPPER, VAULT_CONFIGS, }; use crate::vault::{ deposit_into_vault, liquidate_vault, request_unlock_from_vault, update_vault_coin_balance, @@ -97,15 +97,16 @@ pub fn update_config( .add_attribute("value", coins.join(", ").fallback("None")); } - if let Some(vaults) = new_config.allowed_vaults { - ALLOWED_VAULTS.clear(deps.storage); - vaults.iter().try_for_each(|unchecked| { - let vault = unchecked.check(deps.api)?; - ALLOWED_VAULTS.save(deps.storage, &vault.address, &Empty {}) + if let Some(configs) = new_config.vault_configs { + VAULT_CONFIGS.clear(deps.storage); + configs.iter().try_for_each(|v| -> ContractResult<_> { + v.config.check()?; + let vault = v.vault.check(deps.api)?; + Ok(VAULT_CONFIGS.save(deps.storage, &vault.address, &v.config)?) })?; response = response - .add_attribute("key", "allowed_vaults") - .add_attribute("value", vaults.to_string().fallback("None")) + .add_attribute("key", "vault_configs") + .add_attribute("value", configs.to_string().fallback("None")) } if let Some(unchecked) = new_config.red_bank { diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 1b7cc041c..230b4e400 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -1,34 +1,71 @@ -use cosmwasm_std::{Decimal, Deps, Env, Event, Response}; -use mars_health::health::Health; +use cosmwasm_std::{Coin, Decimal, Deps, Env, Event, Response}; +use mars_health::health::{Health, Position}; +use mars_health::query::MarsQuerier; +use rover::adapters::vault::{Total, VaultPosition}; +use rover::adapters::{Oracle, RedBank}; use rover::error::{ContractError, ContractResult}; -use rover::traits::Coins; +use rover::traits::{Coins, IntoDecimal}; use crate::query::query_positions; -use crate::state::{ORACLE, RED_BANK}; -use crate::vault::get_priceable_coins; +use crate::state::{ORACLE, RED_BANK, VAULT_CONFIGS}; +// Given Red Bank and Mars-Oracle does not have knowledge of vaults, +// we cannot use Health::compute_health_from_coins() and must assemble positions manually pub fn compute_health(deps: Deps, env: &Env, account_id: &str) -> ContractResult { - let res = query_positions(deps, env, account_id)?; - let priceable_coins = get_priceable_coins(&deps, &res.vaults)?; - - let mut collateral = Vec::with_capacity(res.coins.len() + priceable_coins.len()); - collateral.extend(res.coins); - collateral.extend(priceable_coins); - let oracle = ORACLE.load(deps.storage)?; let red_bank = RED_BANK.load(deps.storage)?; - let health = Health::compute_health_from_coins( - &deps.querier, - oracle.address(), - red_bank.address(), - &collateral, - &res.debts.to_coins(), - )?; + let res = query_positions(deps, env, account_id)?; + + let mut positions: Vec = vec![]; + let coin_positions = + get_positions_for_coins(&deps, &res.coins, &res.debts.to_coins(), &oracle, &red_bank)?; + positions.extend(coin_positions); + let vault_positions = get_positions_for_vaults(&deps, &res.vaults, &oracle)?; + positions.extend(vault_positions); + + let health = Health::compute_health(&positions)?; Ok(health) } +fn get_positions_for_coins( + deps: &Deps, + collateral: &[Coin], + debt: &[Coin], + oracle: &Oracle, + red_bank: &RedBank, +) -> ContractResult> { + let querier = MarsQuerier::new(&deps.querier, oracle.address(), red_bank.address()); + let positions = Health::positions_from_coins(&querier, collateral, debt)? + .into_values() + .collect(); + Ok(positions) +} + +fn get_positions_for_vaults( + deps: &Deps, + vaults: &[VaultPosition], + oracle: &Oracle, +) -> ContractResult> { + vaults + .iter() + .map(|v| { + let info = v.vault.query_info(&deps.querier)?; + let query_res = oracle.query_price(&deps.querier, &info.token_denom)?; + let config = VAULT_CONFIGS.load(deps.storage, &v.vault.address)?; + Ok(Position { + denom: query_res.denom, + price: query_res.price, + collateral_amount: v.amount.total().to_dec()?, + debt_amount: Decimal::zero(), + max_ltv: config.max_ltv, + liquidation_threshold: config.liquidation_threshold, + }) + }) + .collect() +} + pub fn assert_below_max_ltv(deps: Deps, env: Env, account_id: &str) -> ContractResult { let health = compute_health(deps, &env, account_id)?; diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 98fbd2eb3..096ab097b 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,13 +1,14 @@ -use cosmwasm_std::{DepsMut, Empty, StdResult}; +use cosmwasm_std::{DepsMut, Empty}; +use rover::error::ContractResult; use rover::msg::InstantiateMsg; use crate::state::{ - ALLOWED_COINS, ALLOWED_VAULTS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, - RED_BANK, SWAPPER, VAULT_DEPOSIT_CAPS, + ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, + VAULT_CONFIGS, }; -pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { +pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { let owner = deps.api.addr_validate(&msg.owner)?; OWNER.save(deps.storage, &owner)?; RED_BANK.save(deps.storage, &msg.red_bank.check(deps.api)?)?; @@ -16,11 +17,13 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> StdResult<()> { MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; SWAPPER.save(deps.storage, &msg.swapper.check(deps.api)?)?; - msg.allowed_vaults.iter().try_for_each(|config| { - let vault = config.vault.check(deps.api)?; - ALLOWED_VAULTS.save(deps.storage, &vault.address, &Empty {})?; - VAULT_DEPOSIT_CAPS.save(deps.storage, &vault.address, &config.deposit_cap) - })?; + msg.allowed_vaults + .iter() + .try_for_each(|v| -> ContractResult<_> { + v.config.check()?; + let vault = v.vault.check(deps.api)?; + Ok(VAULT_CONFIGS.save(deps.storage, &vault.address, &v.config)?) + })?; msg.allowed_coins .iter() diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index c68e56f3c..7539f923c 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -10,8 +10,8 @@ use rover::msg::query::{ }; use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, ALLOWED_VAULTS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, - MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_DEPOSIT_CAPS, + ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, + MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_CONFIGS, VAULT_POSITIONS, }; use crate::utils::debt_shares_to_amount; @@ -115,35 +115,7 @@ pub fn query_all_debt_shares( .collect()) } -/// NOTE: This implementation of the query function assumes the map `ALLOWED_VAULTS` only saves `Empty`. -/// If a vault is to be removed from the whitelist, the map must remove the corresponding key. -pub fn query_allowed_vaults( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult> { - let vault: Vault; - let start = match &start_after { - Some(unchecked) => { - vault = unchecked.check(deps.api)?; - Some(Bound::exclusive(&vault.address)) - } - None => None, - }; - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - - ALLOWED_VAULTS - .keys(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|res| { - let addr = res?; - Ok(VaultBase::new(addr.to_string())) - }) - .collect() -} - -pub fn query_vault_deposit_caps( +pub fn query_vault_configs( deps: Deps, start_after: Option, limit: Option, @@ -159,14 +131,14 @@ pub fn query_vault_deposit_caps( let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - VAULT_DEPOSIT_CAPS + VAULT_CONFIGS .range(deps.storage, start, None, Order::Ascending) .take(limit) .map(|res| { - let (addr, deposit_cap) = res?; + let (addr, config) = res?; Ok(VaultInstantiateConfig { vault: VaultBase::new(addr.to_string()), - deposit_cap, + config, }) }) .collect() @@ -292,7 +264,7 @@ pub fn query_all_total_vault_coin_balances( let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - ALLOWED_VAULTS + VAULT_CONFIGS .keys(deps.storage, start, None, Order::Ascending) .take(limit) .map(|res| { diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index ad916684e..c320d2432 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,17 +1,16 @@ -use cosmwasm_std::{Addr, Coin, Decimal, Empty, Uint128}; +use cosmwasm_std::{Addr, Decimal, Empty, Uint128}; use cw_storage_plus::{Item, Map}; use crate::vault::RequestTempStorage; use rover::adapters::swap::Swapper; -use rover::adapters::vault::VaultPositionAmount; +use rover::adapters::vault::{VaultConfig, VaultPositionAmount}; use rover::adapters::{Oracle, RedBank}; // Contract config pub const OWNER: Item = Item::new("owner"); pub const ACCOUNT_NFT: Item = Item::new("account_nft"); pub const ALLOWED_COINS: Map<&str, Empty> = Map::new("allowed_coins"); -pub const ALLOWED_VAULTS: Map<&Addr, Empty> = Map::new("allowed_vaults"); -pub const VAULT_DEPOSIT_CAPS: Map<&Addr, Coin> = Map::new("vault_deposit_caps"); +pub const VAULT_CONFIGS: Map<&Addr, VaultConfig> = Map::new("vault_configs"); pub const RED_BANK: Item = Item::new("red_bank"); pub const ORACLE: Item = Item::new("oracle"); pub const MAX_LIQUIDATION_BONUS: Item = Item::new("max_liquidation_bonus"); diff --git a/contracts/credit-manager/src/vault/deposit.rs b/contracts/credit-manager/src/vault/deposit.rs index 6849d2491..b7dd2c854 100644 --- a/contracts/credit-manager/src/vault/deposit.rs +++ b/contracts/credit-manager/src/vault/deposit.rs @@ -9,7 +9,7 @@ use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg; use rover::traits::Denoms; -use crate::state::{ORACLE, VAULT_DEPOSIT_CAPS}; +use crate::state::{ORACLE, VAULT_CONFIGS}; use crate::utils::{assert_coins_are_whitelisted, contents_equal, decrement_coin_balance}; use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; @@ -111,8 +111,8 @@ pub fn assert_deposit_is_under_cap( let oracle = ORACLE.load(deps.storage)?; let deposit_request_value = oracle.query_total_value(&deps.querier, coins)?; - let deposit_cap = VAULT_DEPOSIT_CAPS.load(deps.storage, &vault.address)?; - let deposit_cap_value = oracle.query_total_value(&deps.querier, &[deposit_cap])?; + let config = VAULT_CONFIGS.load(deps.storage, &vault.address)?; + let deposit_cap_value = oracle.query_total_value(&deps.querier, &[config.deposit_cap])?; let vault_info = vault.query_info(&deps.querier)?; let rover_vault_coin_balance = vault.query_balance(&deps.querier, rover_addr)?; diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 50889c582..399621bd7 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -8,12 +8,14 @@ use rover::adapters::vault::{ }; use rover::error::{ContractError, ContractResult}; -use crate::state::{ALLOWED_VAULTS, ORACLE, VAULT_POSITIONS}; +use crate::state::{ORACLE, VAULT_CONFIGS, VAULT_POSITIONS}; use crate::update_coin_balances::query_balances; pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> ContractResult<()> { - let is_whitelisted = ALLOWED_VAULTS.has(storage, &vault.address); - if !is_whitelisted { + let config = VAULT_CONFIGS + .may_load(storage, &vault.address)? + .and_then(|config| config.whitelisted.then_some(true)); + if config.is_none() { return Err(ContractError::NotWhitelisted(vault.address.to_string())); } Ok(()) diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index 9f59fe6c7..401cd4edf 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -25,6 +25,8 @@ pub fn build_mock_vaults(count: usize) -> Vec { lockup: Some(1_209_600), // 14 days underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], deposit_cap: coin(10000000, "uusdc"), + max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), } }) .collect() diff --git a/contracts/credit-manager/tests/helpers/mock_entity_info.rs b/contracts/credit-manager/tests/helpers/mock_entity_info.rs index 46ba8ee7d..4ef942f28 100644 --- a/contracts/credit-manager/tests/helpers/mock_entity_info.rs +++ b/contracts/credit-manager/tests/helpers/mock_entity_info.rs @@ -42,5 +42,7 @@ fn generate_mock_vault(lockup: Option) -> VaultTestInfo { lockup, underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], deposit_cap: coin(10_000_000, "uusdc"), + max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), } } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 68fd82245..db1b842db 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -22,7 +22,7 @@ use rover::adapters::swap::QueryMsg::EstimateExactInSwap; use rover::adapters::swap::{ EstimateExactInSwapResponse, InstantiateMsg as SwapperInstantiateMsg, Swapper, SwapperBase, }; -use rover::adapters::vault::{VaultBase, VaultUnchecked}; +use rover::adapters::vault::{VaultBase, VaultConfig, VaultUnchecked}; use rover::adapters::{OracleBase, RedBankBase}; use rover::msg::execute::{Action, CallbackMsg}; use rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; @@ -225,21 +225,7 @@ impl MockEnv { .unwrap() } - pub fn query_allowed_vaults( - &self, - start_after: Option, - limit: Option, - ) -> Vec { - self.app - .wrap() - .query_wasm_smart( - self.rover.clone(), - &QueryMsg::AllowedVaults { start_after, limit }, - ) - .unwrap() - } - - pub fn query_deposit_caps( + pub fn query_vault_configs( &self, start_after: Option, limit: Option, @@ -248,16 +234,17 @@ impl MockEnv { .wrap() .query_wasm_smart( self.rover.clone(), - &QueryMsg::DepositCaps { start_after, limit }, + &QueryMsg::VaultConfigs { start_after, limit }, ) .unwrap() } pub fn get_vault(&self, vault: &VaultTestInfo) -> VaultUnchecked { - self.query_allowed_vaults(None, Some(30)) // Max limit + self.query_vault_configs(None, Some(30)) // Max limit .iter() .find(|v| { let info = v + .vault .check(&MockApi::default()) .unwrap() .query_info(&self.app.wrap()) @@ -265,6 +252,7 @@ impl MockEnv { vault.denom == info.token_denom }) .unwrap() + .vault .clone() } @@ -709,7 +697,12 @@ impl MockEnvBuilder { self.fund_vault(&addr, &vault.denom); VaultInstantiateConfig { vault: VaultBase::new(addr.to_string()), - deposit_cap: vault.deposit_cap.clone(), + config: VaultConfig { + deposit_cap: vault.deposit_cap.clone(), + max_ltv: vault.max_ltv, + liquidation_threshold: vault.liquidation_threshold, + whitelisted: true, + }, } } @@ -822,7 +815,12 @@ impl MockEnvBuilder { pub fn pre_deployed_vault(&mut self, address: &str, info: &VaultTestInfo) -> &mut Self { let config = VaultInstantiateConfig { vault: VaultBase::new(address.to_string()), - deposit_cap: info.deposit_cap.clone(), + config: VaultConfig { + deposit_cap: info.deposit_cap.clone(), + max_ltv: info.max_ltv, + liquidation_threshold: info.liquidation_threshold, + whitelisted: true, + }, }; let new_list = match self.pre_deployed_vaults.clone() { None => Some(vec![config]), diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 515732ae9..a17e27dab 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -21,6 +21,8 @@ pub struct VaultTestInfo { pub lockup: Option, pub underlying_denoms: Vec, pub deposit_cap: Coin, + pub max_ltv: Decimal, + pub liquidation_threshold: Decimal, } impl CoinInfo { diff --git a/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs b/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs deleted file mode 100644 index ff3841b1a..000000000 --- a/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs +++ /dev/null @@ -1,57 +0,0 @@ -use cosmwasm_std::testing::MockApi; - -use crate::helpers::{assert_contents_equal, build_mock_vaults, MockEnv}; - -pub mod helpers; - -#[test] -fn test_pagination_on_allowed_vaults_query_works() { - let allowed_vaults = build_mock_vaults(32); - let mock = MockEnv::new() - .allowed_vaults(&allowed_vaults) - .build() - .unwrap(); - - let vaults_res = mock.query_allowed_vaults(None, Some(58_u32)); - - // Assert maximum is observed - assert_eq!(vaults_res.len(), 30); - - let vaults_res = mock.query_allowed_vaults(None, Some(2_u32)); - - // Assert limit request is observed - assert_eq!(vaults_res.len(), 2); - - let vaults_res_a = mock.query_allowed_vaults(None, None); - let vaults_res_b = mock.query_allowed_vaults(Some(vaults_res_a.last().unwrap().clone()), None); - let vaults_res_c = mock.query_allowed_vaults(Some(vaults_res_b.last().unwrap().clone()), None); - let vaults_res_d = mock.query_allowed_vaults(Some(vaults_res_c.last().unwrap().clone()), None); - - // Assert default is observed - assert_eq!(vaults_res_a.len(), 10); - assert_eq!(vaults_res_b.len(), 10); - assert_eq!(vaults_res_c.len(), 10); - - assert_eq!(vaults_res_d.len(), 2); - - let combined = vaults_res_a - .iter() - .cloned() - .chain(vaults_res_b.iter().cloned()) - .chain(vaults_res_c.iter().cloned()) - .chain(vaults_res_d.iter().cloned()) - .map(|v| v.check(&MockApi::default()).unwrap()) - .map(|v| v.query_info(&mock.app.wrap()).unwrap()) - .map(|info| info.token_denom) - .collect::>(); - - assert_eq!(combined.len(), allowed_vaults.len()); - - assert_contents_equal( - &allowed_vaults - .iter() - .map(|v| v.denom.clone()) - .collect::>(), - &combined, - ) -} diff --git a/contracts/credit-manager/tests/test_enumerate_deposit_caps.rs b/contracts/credit-manager/tests/test_enumerate_vault_configs.rs similarity index 68% rename from contracts/credit-manager/tests/test_enumerate_deposit_caps.rs rename to contracts/credit-manager/tests/test_enumerate_vault_configs.rs index 991438a63..7f4969b21 100644 --- a/contracts/credit-manager/tests/test_enumerate_deposit_caps.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_configs.rs @@ -5,31 +5,30 @@ use crate::helpers::{assert_contents_equal, build_mock_vaults, MockEnv}; pub mod helpers; #[test] -fn test_pagination_on_deposit_caps_query_works() { +fn test_pagination_on_allowed_vaults_query_works() { let allowed_vaults = build_mock_vaults(32); let mock = MockEnv::new() .allowed_vaults(&allowed_vaults) .build() .unwrap(); - let vaults_res = mock.query_deposit_caps(None, Some(58_u32)); + let vaults_res = mock.query_vault_configs(None, Some(58_u32)); // Assert maximum is observed assert_eq!(vaults_res.len(), 30); - let vaults_res = mock.query_deposit_caps(None, Some(2_u32)); + let vaults_res = mock.query_vault_configs(None, Some(2_u32)); // Assert limit request is observed assert_eq!(vaults_res.len(), 2); - let vaults_res_a = mock.query_deposit_caps(None, None); - + let vaults_res_a = mock.query_vault_configs(None, None); let vaults_res_b = - mock.query_deposit_caps(Some(vaults_res_a.last().unwrap().vault.clone()), None); + mock.query_vault_configs(Some(vaults_res_a.last().unwrap().vault.clone()), None); let vaults_res_c = - mock.query_deposit_caps(Some(vaults_res_b.last().unwrap().vault.clone()), None); + mock.query_vault_configs(Some(vaults_res_b.last().unwrap().vault.clone()), None); let vaults_res_d = - mock.query_deposit_caps(Some(vaults_res_c.last().unwrap().vault.clone()), None); + mock.query_vault_configs(Some(vaults_res_c.last().unwrap().vault.clone()), None); // Assert default is observed assert_eq!(vaults_res_a.len(), 10); @@ -44,7 +43,7 @@ fn test_pagination_on_deposit_caps_query_works() { .chain(vaults_res_b.iter().cloned()) .chain(vaults_res_c.iter().cloned()) .chain(vaults_res_d.iter().cloned()) - .map(|config| config.vault.check(&MockApi::default()).unwrap()) + .map(|v| v.vault.check(&MockApi::default()).unwrap()) .map(|v| v.query_info(&mock.app.wrap()).unwrap()) .map(|info| info.token_denom) .collect::>(); diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index c9a340730..45a1fbe6c 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -38,18 +38,24 @@ fn test_allowed_vaults_set_on_instantiate() { lockup: None, underlying_denoms: vec![], deposit_cap: coin(1_000_000, "uusdc"), + max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), }, VaultTestInfo { denom: "vault_contract_2".to_string(), lockup: None, underlying_denoms: vec![], deposit_cap: coin(1_000_000, "uusdc"), + max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), }, VaultTestInfo { denom: "vault_contract_3".to_string(), lockup: None, underlying_denoms: vec![], deposit_cap: coin(1_000_000, "uusdc"), + max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), }, ]; @@ -57,9 +63,9 @@ fn test_allowed_vaults_set_on_instantiate() { .allowed_vaults(&allowed_vaults) .build() .unwrap(); - let res = mock.query_allowed_vaults(None, None); + let res = mock.query_vault_configs(None, None); assert_contents_equal( - &res, + &res.iter().map(|v| v.vault.clone()).collect::>(), &allowed_vaults .iter() .map(|info| mock.get_vault(info)) @@ -78,6 +84,45 @@ fn test_raises_on_invalid_vaults_addr() { } } +#[test] +fn test_raises_on_invalid_vaults_config() { + let mock = MockEnv::new() + .pre_deployed_vault( + "addr_123", + &VaultTestInfo { + denom: "uleverage".to_string(), + lockup: None, + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + deposit_cap: coin(10_000_000, "uusdc"), + max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + }, + ) + .build(); + + if mock.is_ok() { + panic!("Should have thrown an error: max_ltv > liquidation_threshold"); + } + + let mock = MockEnv::new() + .pre_deployed_vault( + "addr_123", + &VaultTestInfo { + denom: "uleverage".to_string(), + lockup: None, + underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + deposit_cap: coin(10_000_000, "uusdc"), + max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(9u128, 0).unwrap(), + }, + ) + .build(); + + if mock.is_ok() { + panic!("Should have thrown an error: liquidation_threshold > 1"); + } +} + #[test] fn test_allowed_coins_set_on_instantiate() { let allowed_coins = vec![ diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 32286626c..715903366 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -126,7 +126,7 @@ fn test_vault_positions_contribute_to_health() { res, NotLiquidatable { account_id: liquidatee_account_id, - lqdt_health_factor: "18.053".to_string(), + lqdt_health_factor: "14.853".to_string(), }, ) } diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 34dc2b460..3027581e6 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -150,7 +150,7 @@ fn test_liquidator_does_not_have_debt_coin_in_credit_account() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uatom.to_coin(200)), + Deposit(uatom.to_coin(300)), Deposit(uosmo.to_coin(400)), VaultDeposit { vault, @@ -158,7 +158,7 @@ fn test_liquidator_does_not_have_debt_coin_in_credit_account() { }, Borrow(ujake.to_coin(175)), ], - &[uatom.to_coin(200), uosmo.to_coin(400)], + &[uatom.to_coin(300), uosmo.to_coin(400)], ) .unwrap(); @@ -222,7 +222,7 @@ fn test_liquidate_unlocked_vault() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uatom.to_coin(200)), + Deposit(uatom.to_coin(300)), Deposit(uosmo.to_coin(400)), VaultDeposit { vault, @@ -230,7 +230,7 @@ fn test_liquidate_unlocked_vault() { }, Borrow(ujake.to_coin(175)), ], - &[uatom.to_coin(200), uosmo.to_coin(400)], + &[uatom.to_coin(300), uosmo.to_coin(400)], ) .unwrap(); @@ -262,9 +262,11 @@ fn test_liquidate_unlocked_vault() { let vault_balance = position.vaults.first().unwrap().amount.unlocked(); assert_eq!(vault_balance, Uint128::new(300_000)); // Vault position liquidated by 70% - assert_eq!(position.coins.len(), 1); + assert_eq!(position.coins.len(), 2); let jake_balance = get_coin("ujake", &position.coins); assert_eq!(jake_balance.amount, Uint128::new(175)); + let atom_balance = get_coin("uatom", &position.coins); + assert_eq!(atom_balance.amount, Uint128::new(100)); assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("ujake", &position.debts); @@ -311,7 +313,7 @@ fn test_liquidate_locked_vault() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uatom.to_coin(200)), + Deposit(uatom.to_coin(300)), Deposit(uosmo.to_coin(400)), VaultDeposit { vault: vault.clone(), @@ -323,7 +325,7 @@ fn test_liquidate_locked_vault() { amount: Uint128::new(100_000), }, ], - &[uatom.to_coin(200), uosmo.to_coin(400)], + &[uatom.to_coin(300), uosmo.to_coin(400)], ) .unwrap(); @@ -357,9 +359,11 @@ fn test_liquidate_locked_vault() { assert_eq!(vault_amount.unlocking().len(), 0); assert_eq!(vault_amount.locked(), Uint128::new(300_000)); - assert_eq!(position.coins.len(), 1); + assert_eq!(position.coins.len(), 2); let jake_balance = get_coin("ujake", &position.coins); assert_eq!(jake_balance.amount, Uint128::new(175)); + let atom_balance = get_coin("uatom", &position.coins); + assert_eq!(atom_balance.amount, Uint128::new(100)); assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("ujake", &position.debts); @@ -406,7 +410,7 @@ fn test_liquidate_unlocking_priority() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uatom.to_coin(200)), + Deposit(uatom.to_coin(300)), Deposit(uosmo.to_coin(400)), VaultDeposit { vault: vault.clone(), @@ -426,7 +430,7 @@ fn test_liquidate_unlocking_priority() { amount: Uint128::new(700_000), }, ], - &[uatom.to_coin(200), uosmo.to_coin(400)], + &[uatom.to_coin(300), uosmo.to_coin(400)], ) .unwrap(); @@ -464,9 +468,11 @@ fn test_liquidate_unlocking_priority() { Uint128::new(210_000) ); - assert_eq!(position.coins.len(), 1); + assert_eq!(position.coins.len(), 2); let jake_balance = get_coin("ujake", &position.coins); assert_eq!(jake_balance.amount, Uint128::new(175)); + let atom_balance = get_coin("uatom", &position.coins); + assert_eq!(atom_balance.amount, Uint128::new(100)); assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("ujake", &position.debts); @@ -514,7 +520,7 @@ fn test_liquidation_calculation_adjustment() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uatom.to_coin(200)), + Deposit(uatom.to_coin(300)), Deposit(uosmo.to_coin(400)), VaultDeposit { vault, @@ -522,7 +528,7 @@ fn test_liquidation_calculation_adjustment() { }, Borrow(ujake.to_coin(175)), ], - &[uatom.to_coin(200), uosmo.to_coin(400)], + &[uatom.to_coin(300), uosmo.to_coin(400)], ) .unwrap(); @@ -556,9 +562,11 @@ fn test_liquidation_calculation_adjustment() { let vault_balance = position.vaults.first().unwrap().amount.unlocked(); assert_eq!(vault_balance, Uint128::new(20_000)); // Vault position liquidated by 98% - assert_eq!(position.coins.len(), 1); + assert_eq!(position.coins.len(), 2); let jake_balance = get_coin("ujake", &position.coins); assert_eq!(jake_balance.amount, Uint128::new(175)); + let atom_balance = get_coin("uatom", &position.coins); + assert_eq!(atom_balance.amount, Uint128::new(100)); assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("ujake", &position.debts); diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 8c569b6a7..45e036c65 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -1,11 +1,12 @@ -use cosmwasm_std::{Addr, Decimal}; +use cosmwasm_std::{coin, Addr, Decimal}; use rover::adapters::swap::SwapperBase; -use rover::adapters::vault::VaultBase; +use rover::adapters::vault::{VaultBase, VaultConfig}; use rover::adapters::{OracleBase, RedBankBase}; -use rover::msg::instantiate::ConfigUpdates; +use rover::error::ContractError; +use rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; -use crate::helpers::{locked_vault_info, uatom_info, uosmo_info, MockEnv}; +use crate::helpers::{assert_err, locked_vault_info, uatom_info, uosmo_info, MockEnv}; pub mod helpers; @@ -19,13 +20,13 @@ fn test_only_owner_can_update_config() { ConfigUpdates { account_nft: None, owner: Some(new_owner.to_string()), - allowed_vaults: None, allowed_coins: None, red_bank: None, oracle: None, max_liquidation_bonus: None, max_close_factor: None, swapper: None, + vault_configs: None, }, ); @@ -34,17 +35,80 @@ fn test_only_owner_can_update_config() { } } +#[test] +fn test_raises_on_invalid_vaults_config() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_config(); + let res = mock.update_config( + &Addr::unchecked(original_config.owner.clone()), + ConfigUpdates { + account_nft: None, + owner: None, + allowed_coins: None, + red_bank: None, + oracle: None, + max_liquidation_bonus: None, + max_close_factor: None, + swapper: None, + vault_configs: Some(vec![VaultInstantiateConfig { + vault: VaultBase::new("vault_123".to_string()), + config: VaultConfig { + deposit_cap: coin(10_000_000, "uusdc"), + max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + whitelisted: true, + }, + }]), + }, + ); + + assert_err(res, ContractError::InvalidVaultConfig {}); + + let res = mock.update_config( + &Addr::unchecked(original_config.owner), + ConfigUpdates { + account_nft: None, + owner: None, + allowed_coins: None, + red_bank: None, + oracle: None, + max_liquidation_bonus: None, + max_close_factor: None, + swapper: None, + vault_configs: Some(vec![VaultInstantiateConfig { + vault: VaultBase::new("vault_123".to_string()), + config: VaultConfig { + deposit_cap: coin(10_000_000, "uusdc"), + max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(9u128, 0).unwrap(), + whitelisted: true, + }, + }]), + }, + ); + + assert_err(res, ContractError::InvalidVaultConfig {}); +} + #[test] fn test_update_config_works_with_full_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); - let original_allowed_vaults = mock.query_allowed_vaults(None, None); let original_allowed_coins = mock.query_allowed_coins(None, None); + let original_vault_configs = mock.query_vault_configs(None, None); let new_nft_contract = mock.deploy_nft_contract().unwrap(); let new_owner = Addr::unchecked("new_owner"); let new_red_bank = RedBankBase::new("new_red_bank".to_string()); - let new_allowed_vaults = vec![VaultBase::new("vault_contract_1".to_string())]; + let new_vault_configs = vec![VaultInstantiateConfig { + vault: VaultBase::new("vault_contract_3000".to_string()), + config: VaultConfig { + deposit_cap: coin(123, "usomething"), + max_ltv: Decimal::from_atomics(3u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), + whitelisted: false, + }, + }]; let new_allowed_coins = vec!["uosmo".to_string()]; let new_oracle = OracleBase::new("new_oracle".to_string()); let new_liq_bonus = Decimal::from_atomics(17u128, 2).unwrap(); @@ -56,20 +120,20 @@ fn test_update_config_works_with_full_config() { ConfigUpdates { account_nft: Some(new_nft_contract.to_string()), owner: Some(new_owner.to_string()), - allowed_vaults: Some(new_allowed_vaults.clone()), allowed_coins: Some(new_allowed_coins.clone()), red_bank: Some(new_red_bank.clone()), oracle: Some(new_oracle.clone()), max_liquidation_bonus: Some(new_liq_bonus), max_close_factor: Some(new_close_factor), swapper: Some(new_swapper.clone()), + vault_configs: Some(new_vault_configs.clone()), }, ) .unwrap(); let new_config = mock.query_config(); - let new_queried_allowed_vaults = mock.query_allowed_vaults(None, None); let new_queried_allowed_coins = mock.query_allowed_coins(None, None); + let new_queried_vault_configs = mock.query_vault_configs(None, None); assert_eq!(new_config.account_nft, Some(new_nft_contract.to_string())); assert_ne!(new_config.account_nft, original_config.account_nft); @@ -77,8 +141,8 @@ fn test_update_config_works_with_full_config() { assert_eq!(new_config.owner, new_owner.to_string()); assert_ne!(new_config.owner, original_config.owner); - assert_eq!(new_queried_allowed_vaults, new_allowed_vaults); - assert_ne!(new_queried_allowed_vaults, original_allowed_vaults); + assert_eq!(new_queried_vault_configs, new_vault_configs); + assert_ne!(new_queried_vault_configs, original_vault_configs); assert_eq!(new_queried_allowed_coins, new_allowed_coins); assert_ne!(new_queried_allowed_coins, original_allowed_coins); @@ -109,32 +173,40 @@ fn test_update_config_works_with_full_config() { fn test_update_config_works_with_some_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); - let original_allowed_vaults = mock.query_allowed_vaults(None, None); let original_allowed_coins = mock.query_allowed_coins(None, None); + let original_vault_configs = mock.query_vault_configs(None, None); let new_nft_contract = mock.deploy_nft_contract().unwrap(); - let new_allowed_vaults = vec![VaultBase::new("vault_contract_1".to_string())]; + let new_vault_configs = vec![VaultInstantiateConfig { + vault: VaultBase::new("vault_contract_1".to_string()), + config: VaultConfig { + deposit_cap: coin(1211, "uxyz"), + max_ltv: Default::default(), + liquidation_threshold: Default::default(), + whitelisted: false, + }, + }]; mock.update_config( &Addr::unchecked(original_config.owner.clone()), ConfigUpdates { account_nft: Some(new_nft_contract.to_string()), - allowed_vaults: Some(new_allowed_vaults.clone()), + vault_configs: Some(new_vault_configs.clone()), ..Default::default() }, ) .unwrap(); let new_config = mock.query_config(); - let new_queried_allowed_vaults = mock.query_allowed_vaults(None, None); let new_queried_allowed_coins = mock.query_allowed_coins(None, None); + let new_queried_vault_configs = mock.query_vault_configs(None, None); // Changed configs assert_eq!(new_config.account_nft, Some(new_nft_contract.to_string())); assert_ne!(new_config.account_nft, original_config.account_nft); - assert_eq!(new_queried_allowed_vaults, new_allowed_vaults); - assert_ne!(new_queried_allowed_vaults, original_allowed_vaults); + assert_eq!(new_queried_vault_configs, new_vault_configs); + assert_ne!(new_queried_vault_configs, original_vault_configs); // Unchanged configs assert_eq!(new_config.owner, original_config.owner); @@ -154,29 +226,35 @@ fn test_update_config_removes_properly() { .build() .unwrap(); + let allowed_coins = mock.query_allowed_coins(None, None); + let vault_configs = mock.query_vault_configs(None, None); + + assert_eq!(allowed_coins.len(), 2); + assert_eq!(vault_configs.len(), 1); + mock.update_config( &Addr::unchecked(mock.query_config().owner), ConfigUpdates { allowed_coins: Some(vec![]), - allowed_vaults: Some(vec![]), + vault_configs: Some(vec![]), ..Default::default() }, ) .unwrap(); - let new_allowed_vaults = mock.query_allowed_vaults(None, None); - let new_allowed_coins = mock.query_allowed_coins(None, None); + let allowed_coins = mock.query_allowed_coins(None, None); + let vault_configs = mock.query_vault_configs(None, None); // All allowed vaults and coins removed - assert_eq!(new_allowed_vaults.len(), 0); - assert_eq!(new_allowed_coins.len(), 0); + assert_eq!(allowed_coins.len(), 0); + assert_eq!(vault_configs.len(), 0); } #[test] fn test_update_config_does_nothing_when_nothing_is_passed() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); - let original_allowed_vaults = mock.query_allowed_vaults(None, None); + let original_vault_configs = mock.query_vault_configs(None, None); let original_allowed_coins = mock.query_allowed_coins(None, None); mock.update_config( @@ -186,12 +264,12 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { .unwrap(); let new_config = mock.query_config(); - let new_queried_allowed_vaults = mock.query_allowed_vaults(None, None); + let new_queried_vault_configs = mock.query_vault_configs(None, None); let new_queried_allowed_coins = mock.query_allowed_coins(None, None); assert_eq!(new_config.account_nft, original_config.account_nft); assert_eq!(new_config.owner, original_config.owner); - assert_eq!(new_queried_allowed_vaults, original_allowed_vaults); + assert_eq!(new_queried_vault_configs, original_vault_configs); assert_eq!(new_queried_allowed_coins, original_allowed_coins); assert_eq!(new_config.red_bank, original_config.red_bank); assert_eq!(new_config.oracle, original_config.oracle); diff --git a/contracts/credit-manager/tests/test_vault_deposit.rs b/contracts/credit-manager/tests/test_vault_deposit.rs index e9ba0124e..4aa88b43b 100644 --- a/contracts/credit-manager/tests/test_vault_deposit.rs +++ b/contracts/credit-manager/tests/test_vault_deposit.rs @@ -1,5 +1,5 @@ use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{coin, coins, Addr, OverflowError, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Decimal, OverflowError, Uint128}; use mock_vault::contract::STARTING_VAULT_SHARES; use rover::adapters::vault::VaultBase; @@ -109,6 +109,8 @@ fn test_deposited_coins_match_vault_requirements() { lockup: None, underlying_denoms: vec!["uatom".to_string(), "ujake".to_string()], deposit_cap: coin(1_000_000, "uusdc"), + max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), }; let user = Addr::unchecked("user"); diff --git a/packages/rover/src/adapters/vault/base.rs b/packages/rover/src/adapters/vault/base.rs index e944bb395..47a296a87 100644 --- a/packages/rover/src/adapters/vault/base.rs +++ b/packages/rover/src/adapters/vault/base.rs @@ -2,11 +2,13 @@ use std::hash::Hash; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, QuerierWrapper, + to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, Decimal, QuerierWrapper, QueryRequest, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, }; use crate::adapters::vault::VaultPositionAmount; +use crate::error::ContractError; +use crate::error::ContractError::InvalidVaultConfig; use crate::msg::vault::{ExecuteMsg, QueryMsg, UnlockingPosition, VaultInfo}; use crate::traits::Stringify; @@ -28,6 +30,27 @@ pub struct VaultPosition { pub amount: VaultPositionAmount, } +#[cw_serde] +pub struct VaultConfig { + pub deposit_cap: Coin, + pub max_ltv: Decimal, + pub liquidation_threshold: Decimal, + pub whitelisted: bool, +} + +impl VaultConfig { + pub fn check(&self) -> Result<(), ContractError> { + let max_ltv_too_big = self.max_ltv > Decimal::one(); + let lqt_too_big = self.liquidation_threshold > Decimal::one(); + let max_ltv_bigger_than_lqt = self.max_ltv > self.liquidation_threshold; + + if max_ltv_too_big || lqt_too_big || max_ltv_bigger_than_lqt { + return Err(InvalidVaultConfig {}); + } + Ok(()) + } +} + #[cw_serde] #[derive(Eq, Hash)] pub struct VaultBase { diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index ca206a9a2..72a167196 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -48,6 +48,9 @@ pub enum ContractError { )] HealthNotImproved { prev_hf: String, new_hf: String }, + #[error("Vault configuration has invalid values")] + InvalidVaultConfig {}, + #[error("Issued incorrect action for vault type")] MismatchedVaultType, diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index f30423383..623145651 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -1,9 +1,10 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Coin, Decimal}; - use crate::adapters::swap::SwapperUnchecked; +use crate::adapters::vault::VaultConfig; use crate::adapters::vault::VaultUnchecked; use crate::adapters::{OracleUnchecked, RedBankUnchecked}; +use crate::traits::Stringify; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Decimal; #[cw_serde] pub struct InstantiateMsg { @@ -29,7 +30,25 @@ pub struct InstantiateMsg { #[cw_serde] pub struct VaultInstantiateConfig { pub vault: VaultUnchecked, - pub deposit_cap: Coin, + pub config: VaultConfig, +} + +impl Stringify for Vec { + fn to_string(&self) -> String { + self.iter() + .map(|v| { + format!( + "addr: {}, deposit_cap: {}, max_ltv: {}, liquidation_threshold: {}, whitelisted: {}", + v.vault.address, + v.config.deposit_cap, + v.config.max_ltv, + v.config.liquidation_threshold, + v.config.whitelisted + ) + }) + .collect::>() + .join(" :: ") + } } /// Used when you want to update fields on Instantiate config @@ -39,7 +58,7 @@ pub struct ConfigUpdates { pub account_nft: Option, pub owner: Option, pub allowed_coins: Option>, - pub allowed_vaults: Option>, + pub vault_configs: Option>, pub red_bank: Option, pub oracle: Option, pub max_liquidation_bonus: Option, diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index da4564b9c..e267bb02a 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -11,15 +11,9 @@ pub enum QueryMsg { /// Owner & account nft address #[returns(ConfigResponse)] Config {}, - /// Whitelisted vaults - #[returns(Vec)] - AllowedVaults { - start_after: Option, - limit: Option, - }, - /// Deposit caps on vault deposits + /// Configs on vaults #[returns(Vec)] - DepositCaps { + VaultConfigs { start_after: Option, limit: Option, }, From 4ea5ce0c2a54234089fa080ab986363da5a1bfc9 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 28 Oct 2022 09:37:52 +0200 Subject: [PATCH 068/218] New vault api alignment (#32) * Apollo vault msg sync * Review updates --- Cargo.lock | 222 ++--- Makefile.toml | 14 +- contracts/account-nft/Cargo.toml | 4 +- contracts/credit-manager/Cargo.toml | 6 +- contracts/credit-manager/src/execute.rs | 44 +- contracts/credit-manager/src/health.rs | 2 +- .../src/vault/{deposit.rs => enter.rs} | 50 +- .../src/vault/{withdraw.rs => exit.rs} | 2 +- ...{withdraw_unlocked.rs => exit_unlocked.rs} | 9 +- .../src/vault/liquidate_vault.rs | 10 +- contracts/credit-manager/src/vault/mod.rs | 12 +- .../src/vault/request_unlock.rs | 18 +- contracts/credit-manager/src/vault/utils.rs | 39 +- .../credit-manager/tests/helpers/builders.rs | 14 +- .../tests/helpers/mock_entity_info.rs | 19 +- .../credit-manager/tests/helpers/mock_env.rs | 45 +- .../credit-manager/tests/helpers/types.rs | 7 +- .../tests/test_enumerate_allowed_vaults.rs | 1 + .../test_enumerate_vault_coin_balances.rs | 50 +- .../tests/test_enumerate_vault_configs.rs | 4 +- .../tests/test_enumerate_vault_positions.rs | 50 +- contracts/credit-manager/tests/test_health.rs | 2 - .../credit-manager/tests/test_instantiate.rs | 20 +- .../tests/test_liquidate_coin.rs | 33 +- .../tests/test_liquidate_vault.rs | 220 +++-- ...t_vault_deposit.rs => test_vault_enter.rs} | 161 ++-- ...t_vault_withdraw.rs => test_vault_exit.rs} | 92 +- .../tests/test_vault_exit_unlocked.rs | 392 +++++++++ .../tests/test_vault_request_unlock.rs | 99 +-- .../tests/test_vault_withdraw_unlocked.rs | 284 ------- contracts/mars-oracle-adapter/Cargo.toml | 1 + contracts/mars-oracle-adapter/src/contract.rs | 32 +- contracts/mars-oracle-adapter/src/msg.rs | 3 +- .../mars-oracle-adapter/tests/helpers.rs | 27 +- .../tests/test_query_price.rs | 16 +- .../tests/test_query_priceable_underlying.rs | 8 +- contracts/mock-vault/Cargo.toml | 2 + contracts/mock-vault/examples/schema.rs | 2 +- contracts/mock-vault/src/contract.rs | 74 +- contracts/mock-vault/src/deposit.rs | 36 +- contracts/mock-vault/src/error.rs | 11 +- contracts/mock-vault/src/msg.rs | 16 +- contracts/mock-vault/src/query.rs | 67 +- contracts/mock-vault/src/state.rs | 17 +- contracts/mock-vault/src/unlock.rs | 70 +- contracts/mock-vault/src/withdraw.rs | 57 +- packages/cosmos-vault-standard/Cargo.lock | 789 ++++++++++++++++++ packages/cosmos-vault-standard/Cargo.toml | 21 + packages/cosmos-vault-standard/README.md | 17 + packages/cosmos-vault-standard/src/cw4626.rs | 88 ++ .../src/extensions/keeper.rs | 38 + .../src/extensions/lockup.rs | 104 +++ .../src/extensions/mod.rs | 4 + packages/cosmos-vault-standard/src/lib.rs | 5 + packages/cosmos-vault-standard/src/msg.rs | 221 +++++ packages/rover/Cargo.toml | 2 + packages/rover/src/adapters/vault/base.rs | 91 +- packages/rover/src/extensions/reply.rs | 6 +- packages/rover/src/msg/execute.rs | 35 +- packages/rover/src/msg/mod.rs | 1 - packages/rover/src/msg/vault.rs | 70 -- 61 files changed, 2577 insertions(+), 1279 deletions(-) rename contracts/credit-manager/src/vault/{deposit.rs => enter.rs} (72%) rename contracts/credit-manager/src/vault/{withdraw.rs => exit.rs} (98%) rename contracts/credit-manager/src/vault/{withdraw_unlocked.rs => exit_unlocked.rs} (88%) create mode 100644 contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs rename contracts/credit-manager/tests/{test_vault_deposit.rs => test_vault_enter.rs} (62%) rename contracts/credit-manager/tests/{test_vault_withdraw.rs => test_vault_exit.rs} (72%) create mode 100644 contracts/credit-manager/tests/test_vault_exit_unlocked.rs delete mode 100644 contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs create mode 100644 packages/cosmos-vault-standard/Cargo.lock create mode 100644 packages/cosmos-vault-standard/Cargo.toml create mode 100644 packages/cosmos-vault-standard/README.md create mode 100644 packages/cosmos-vault-standard/src/cw4626.rs create mode 100644 packages/cosmos-vault-standard/src/extensions/keeper.rs create mode 100644 packages/cosmos-vault-standard/src/extensions/lockup.rs create mode 100644 packages/cosmos-vault-standard/src/extensions/mod.rs create mode 100644 packages/cosmos-vault-standard/src/lib.rs create mode 100644 packages/cosmos-vault-standard/src/msg.rs delete mode 100644 packages/rover/src/msg/vault.rs diff --git a/Cargo.lock b/Cargo.lock index 90f327624..f258186d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,9 +17,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" [[package]] name = "base16ct" @@ -29,15 +29,15 @@ checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64ct" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" [[package]] name = "block-buffer" @@ -81,11 +81,24 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" +[[package]] +name = "cosmos-vault-standard" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-asset", + "cw-utils 0.16.0", + "cw20 0.16.0", + "schemars", + "serde", +] + [[package]] name = "cosmwasm-crypto" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1c26e5595c6a960cd3d6dc1d8629e16a2b0cb22ecd653b28bbf292d7c53a3c" +checksum = "28376836c7677e1ea6d6656a754582e88b91e544ce22fae42956d5fe5549a958" dependencies = [ "digest 0.10.5", "ed25519-zebra", @@ -96,18 +109,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a24050753f242554c558dfe7a6b3a456850f2bc6fcf8b518988720e40a5fa21" +checksum = "8eb69f4f7a8a4bce68c8fbd3646238fede1e77056e4ea31c5b6bfc37b709eec3" dependencies = [ "syn", ] [[package]] name = "cosmwasm-schema" -version = "1.1.1" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d13e9ee950418b0ac90a2579b8769640e0b07e6d06d9d5f5b512ba64265e5a" +checksum = "a227cfeb9a7152b26a354b1c990e930e962f75fd68f57ab5ae2ef888c8524292" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -118,9 +131,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.1.1" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73d5e04d338db6af813d0633fe9ab6a5cd36ae83796ca769ae13d6910a5861df" +checksum = "3626cb42eef870de67f791e873711255325224d86f281bf628c42abd295f3a14" dependencies = [ "proc-macro2", "quote", @@ -129,9 +142,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78e50ba8bfea2e449be2e7fa96fd8b196d09fb5ef34e27a18f28835ed835b199" +checksum = "46bf9157d060abbc55152aeadcace799d03dc630575daa66604079a1206cb060" dependencies = [ "base64", "cosmwasm-crypto", @@ -148,9 +161,9 @@ dependencies = [ [[package]] name = "cosmwasm-storage" -version = "1.1.1" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f024880dd46c053f30dd31f3b3aab8197b5cfaafe86c9e302c845df0cff44a0a" +checksum = "b61fcfef87d15af0263e2e4d792af80355929674a3b4e29ffb3c898ec6e25852" dependencies = [ "cosmwasm-std", "serde", @@ -171,11 +184,13 @@ version = "1.0.0" dependencies = [ "account-nft", "anyhow", + "cosmos-vault-standard", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 0.16.0", "cw-storage-plus 0.16.0", - "cw2 0.16.0", + "cw-utils 0.16.0", + "cw2", "cw721", "cw721-base", "itertools", @@ -197,9 +212,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2b443d17d49dad5ef0ede301c3179cc923b8822f3393b4d2c28c269dd4a122" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -230,6 +245,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-asset" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7996c9c60e416aec195719137767d5cef8301237438bbabb772ee45a27f06e5e" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "cw1155", + "cw20 0.13.4", + "schemars", + "serde", +] + [[package]] name = "cw-multi-test" version = "0.13.4" @@ -278,17 +307,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cw-storage-plus" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ba3fb5fad2dce94263d070848b2befc46b5c8e4929adfb9a3595267823d6ec" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - [[package]] name = "cw-storage-plus" version = "0.16.0" @@ -314,13 +332,13 @@ dependencies = [ [[package]] name = "cw-utils" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a67007ff056f4cd034f361c8ed69780c0180959b9c8037c84f3caa78120faf5" +checksum = "d6a84c6c1c0acc3616398eba50783934bd6c964bad6974241eaee3460c8f5b26" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 0.15.0", + "cw2", "schemars", "semver", "serde", @@ -328,70 +346,79 @@ dependencies = [ ] [[package]] -name = "cw-utils" -version = "0.16.0" +name = "cw1155" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a84c6c1c0acc3616398eba50783934bd6c964bad6974241eaee3460c8f5b26" +checksum = "42197c9a0fd844653177009125f24157e486578289327a3781923e427a8f9bbc" dependencies = [ - "cosmwasm-schema", "cosmwasm-std", - "cw2 0.16.0", + "cw-utils 0.13.4", "schemars", - "semver", "serde", - "thiserror", ] [[package]] name = "cw2" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0a1924a28607bf7cb9fd6681a64feea3e5fa9a8cb71fb4d24cf33635b21065a" +checksum = "91398113b806f4d2a8d5f8d05684704a20ffd5968bf87e3473e1973710b884ad" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.15.0", + "cw-storage-plus 0.16.0", "schemars", "serde", ] [[package]] -name = "cw2" +name = "cw20" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" +dependencies = [ + "cosmwasm-std", + "cw-utils 0.13.4", + "schemars", + "serde", +] + +[[package]] +name = "cw20" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91398113b806f4d2a8d5f8d05684704a20ffd5968bf87e3473e1973710b884ad" +checksum = "a45a8794a5dd33b66af34caee52a7beceb690856adcc1682b6e3db88b2cdee62" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", "schemars", "serde", ] [[package]] name = "cw721" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20dfe04f86e5327956b559ffcc86d9a43167391f37402afd8bf40b0be16bee4d" +checksum = "94a1ea6e6277bdd6dfc043a9b1380697fe29d6e24b072597439523658d21d791" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-utils 0.15.0", + "cw-utils 0.16.0", "schemars", "serde", ] [[package]] name = "cw721-base" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62c3ee3b669fc2a8094301a73fd7be97a7454d4df2650c33599f737e8f254d24" +checksum = "77518e27431d43214cff4cdfbd788a7508f68d9b1f32389e6fce513e7eaccbef" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.15.0", - "cw-utils 0.15.0", - "cw2 0.15.0", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", + "cw2", "cw721", "schemars", "serde", @@ -447,9 +474,9 @@ checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" [[package]] name = "ecdsa" -version = "0.14.7" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85789ce7dfbd0f0624c07ef653a08bb2ebf43d3e16531361f46d36dd54334fed" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der", "elliptic-curve", @@ -537,9 +564,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", @@ -548,9 +575,9 @@ dependencies = [ [[package]] name = "group" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7391856def869c1c81063a03457c676fbcd419709c3dfb33d8d319de484b154d" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff", "rand_core 0.6.4", @@ -574,24 +601,24 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bf247779e67a9082a4790b45e71ac7cfd1321331a5c856a74a9faebdab78d0" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "k256" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3636d281d46c3b64182eb3a0a42b7b483191a2ecc3f05301fa67403f7c9bc949" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if", "ecdsa", @@ -601,9 +628,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.132" +version = "0.2.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "55edcf6c0bb319052dea84732cf99db461780fd5e8d3eb46ab6ff312ab31f197" [[package]] name = "mars-health" @@ -622,6 +649,7 @@ dependencies = [ "cosmwasm-std", "cw-multi-test 0.16.0", "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", "mars-outpost", "mock-oracle", "mock-vault", @@ -662,9 +690,11 @@ dependencies = [ name = "mock-vault" version = "1.0.0" dependencies = [ + "cosmos-vault-standard", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", "rover", "thiserror", ] @@ -715,9 +745,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] @@ -769,7 +799,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", ] [[package]] @@ -787,9 +817,11 @@ dependencies = [ name = "rover" version = "1.0.0" dependencies = [ + "cosmos-vault-standard", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", "mars-health", "mars-outpost", "mock-oracle", @@ -807,9 +839,9 @@ checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "schemars" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" +checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307" dependencies = [ "dyn-clone", "schemars_derive", @@ -819,9 +851,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" +checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9" dependencies = [ "proc-macro2", "quote", @@ -851,9 +883,9 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "serde" -version = "1.0.144" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] @@ -869,9 +901,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", @@ -891,9 +923,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ "itoa", "ryu", @@ -926,9 +958,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.3" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb766570a2825fa972bceff0d195727876a9cdf2460ab2e52d455dc2de47fd9" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ "digest 0.10.5", "rand_core 0.6.4", @@ -1001,9 +1033,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.100" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", @@ -1012,18 +1044,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -1038,9 +1070,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "uint" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" dependencies = [ "byteorder", "crunchy", @@ -1050,9 +1082,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "version_check" diff --git a/Makefile.toml b/Makefile.toml index 7a6f5fd7d..e664eef0c 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -11,17 +11,15 @@ args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked"] [tasks.rust-optimizer] script = """ -if [[ $(arch) == 'arm64' ]]; then - docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ - --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/workspace-optimizer-arm64:0.12.8 +if [[ $(arch) == "arm64" ]]; then + image="cosmwasm/workspace-optimizer-arm64" else - docker run --rm -v "$(pwd)":/code \ + image="cosmwasm/workspace-optimizer" +fi +docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/workspace-optimizer:0.12.8 -fi + ${image}:0.12.9 """ [tasks.test] diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 9098301f2..701772f07 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -15,8 +15,8 @@ library = [] [dependencies] cw-storage-plus = "0.16" -cw721 = "0.15" -cw721-base = { version = "0.15", features = ["library"] } +cw721 = "0.16" +cw721-base = { version = "0.16", features = ["library"] } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 0a1a12cf0..435e8761b 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -22,11 +22,12 @@ mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", featu mock-vault = { version = "1.0", path = "../../contracts/mock-vault", features = ["library"] } rover = { version = "1.0", path = "../../packages/rover" } +cosmos-vault-standard = { path = "../../packages/cosmos-vault-standard", features = ["lockup"] } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" cw2 = "0.16" -cw721 = "0.15" -cw721-base = { version = "0.15", features = ["library"] } +cw721 = "0.16" +cw721-base = { version = "0.16", features = ["library"] } cw-storage-plus = "0.16" [dev-dependencies] @@ -35,4 +36,5 @@ mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } anyhow = "1" cw-multi-test = "0.16" +cw-utils = "0.16" itertools = "0.10" diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index a1dd90a57..bcc884841 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -13,8 +13,8 @@ use crate::state::{ SWAPPER, VAULT_CONFIGS, }; use crate::vault::{ - deposit_into_vault, liquidate_vault, request_unlock_from_vault, update_vault_coin_balance, - withdraw_from_vault, withdraw_unlocked_from_vault, + enter_vault, exit_vault, exit_vault_unlocked, liquidate_vault, request_vault_unlock, + update_vault_coin_balance, }; use crate::liquidate_coin::liquidate_coin; @@ -184,13 +184,13 @@ pub fn dispatch_actions( account_id: account_id.to_string(), coin: coin.clone(), }), - Action::VaultDeposit { + Action::EnterVault { vault, - coins: assets, - } => callbacks.push(CallbackMsg::VaultDeposit { + coin: assets, + } => callbacks.push(CallbackMsg::EnterVault { account_id: account_id.to_string(), vault: vault.check(deps.api)?, - coins: assets.clone(), + coin: assets.clone(), }), Action::LiquidateCoin { liquidatee_account_id, @@ -222,20 +222,20 @@ pub fn dispatch_actions( denom_out: denom_out.to_string(), slippage: *slippage, }), - Action::VaultWithdraw { vault, amount } => callbacks.push(CallbackMsg::VaultWithdraw { + Action::ExitVault { vault, amount } => callbacks.push(CallbackMsg::ExitVault { account_id: account_id.to_string(), vault: vault.check(deps.api)?, amount: *amount, }), - Action::VaultRequestUnlock { vault, amount } => { - callbacks.push(CallbackMsg::VaultRequestUnlock { + Action::RequestVaultUnlock { vault, amount } => { + callbacks.push(CallbackMsg::RequestVaultUnlock { account_id: account_id.to_string(), vault: vault.check(deps.api)?, amount: *amount, }) } - Action::VaultWithdrawUnlocked { id, vault } => { - callbacks.push(CallbackMsg::VaultWithdrawUnlocked { + Action::ExitVaultUnlocked { id, vault } => { + callbacks.push(CallbackMsg::ExitVaultUnlocked { account_id: account_id.to_string(), vault: vault.check(deps.api)?, position_id: *id, @@ -285,11 +285,11 @@ pub fn execute_callback( CallbackMsg::AssertBelowMaxLTV { account_id } => { assert_below_max_ltv(deps.as_ref(), env, &account_id) } - CallbackMsg::VaultDeposit { + CallbackMsg::EnterVault { account_id, vault, - coins, - } => deposit_into_vault(deps, &env.contract.address, &account_id, vault, coins), + coin, + } => enter_vault(deps, &env.contract.address, &account_id, vault, coin), CallbackMsg::UpdateVaultCoinBalance { vault, account_id, @@ -337,26 +337,26 @@ pub fn execute_callback( account_id, previous_balances, } => update_coin_balances(deps, env, &account_id, &previous_balances), - CallbackMsg::VaultWithdraw { + CallbackMsg::ExitVault { account_id, vault, amount, - } => withdraw_from_vault(deps, env, &account_id, vault, amount, false), - CallbackMsg::VaultForceWithdraw { + } => exit_vault(deps, env, &account_id, vault, amount, false), + CallbackMsg::ForceExitVault { account_id, vault, amount, - } => withdraw_from_vault(deps, env, &account_id, vault, amount, true), - CallbackMsg::VaultRequestUnlock { + } => exit_vault(deps, env, &account_id, vault, amount, true), + CallbackMsg::RequestVaultUnlock { account_id, vault, amount, - } => request_unlock_from_vault(deps, &account_id, vault, amount), - CallbackMsg::VaultWithdrawUnlocked { + } => request_vault_unlock(deps, &account_id, vault, amount), + CallbackMsg::ExitVaultUnlocked { account_id, vault, position_id, - } => withdraw_unlocked_from_vault(deps, env, &account_id, vault, position_id), + } => exit_vault_unlocked(deps, env, &account_id, vault, position_id), } } diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 230b4e400..5cbb5d9c6 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -52,7 +52,7 @@ fn get_positions_for_vaults( .iter() .map(|v| { let info = v.vault.query_info(&deps.querier)?; - let query_res = oracle.query_price(&deps.querier, &info.token_denom)?; + let query_res = oracle.query_price(&deps.querier, &info.vault_token_denom)?; let config = VAULT_CONFIGS.load(deps.storage, &v.vault.address)?; Ok(Position { denom: query_res.denom, diff --git a/contracts/credit-manager/src/vault/deposit.rs b/contracts/credit-manager/src/vault/enter.rs similarity index 72% rename from contracts/credit-manager/src/vault/deposit.rs rename to contracts/credit-manager/src/vault/enter.rs index b7dd2c854..d0fc070d1 100644 --- a/contracts/credit-manager/src/vault/deposit.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - coin, to_binary, Addr, Coin, CosmosMsg, Deps, DepsMut, QuerierWrapper, Response, Uint128, + coin as c, to_binary, Addr, Coin, CosmosMsg, Deps, DepsMut, QuerierWrapper, Response, Uint128, WasmMsg, }; @@ -7,29 +7,24 @@ use rover::adapters::vault::{UpdateType, Vault, VaultPositionUpdate}; use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg; -use rover::traits::Denoms; use crate::state::{ORACLE, VAULT_CONFIGS}; -use crate::utils::{assert_coins_are_whitelisted, contents_equal, decrement_coin_balance}; +use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance}; use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; -pub fn deposit_into_vault( +pub fn enter_vault( deps: DepsMut, rover_addr: &Addr, account_id: &str, vault: Vault, - coins: Vec, + coin: Coin, ) -> ContractResult { - assert_coins_are_whitelisted(deps.storage, coins.to_denoms())?; + assert_coins_are_whitelisted(deps.storage, vec![coin.denom.as_str()])?; assert_vault_is_whitelisted(deps.storage, &vault)?; - assert_denoms_match_vault_reqs(deps.querier, &vault, &coins)?; - assert_deposit_is_under_cap(deps.as_ref(), &vault, &coins, rover_addr)?; + assert_denom_matches_vault_reqs(deps.querier, &vault, &coin)?; + assert_deposit_is_under_cap(deps.as_ref(), &vault, &coin, rover_addr)?; - // Decrement token's coin balance amount - coins.iter().try_for_each(|coin| -> ContractResult<_> { - decrement_coin_balance(deps.storage, account_id, coin)?; - Ok(()) - })?; + decrement_coin_balance(deps.storage, account_id, &coin)?; let current_balance = vault.query_balance(&deps.querier, rover_addr)?; let update_vault_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { @@ -43,7 +38,7 @@ pub fn deposit_into_vault( }); Ok(Response::new() - .add_message(vault.deposit_msg(&coins)?) + .add_message(vault.deposit_msg(&coin)?) .add_message(update_vault_balance_msg) .add_attribute("action", "rover/credit_manager/vault/deposit")) } @@ -62,13 +57,13 @@ pub fn update_vault_coin_balance( } let diff = current_balance.checked_sub(previous_total_balance)?; - let vault_info = vault.query_info(&deps.querier)?; + let duration = vault.query_lockup_duration(&deps.querier).ok(); update_vault_position( deps.storage, account_id, &vault.address, - match vault_info.lockup { + match duration { None => VaultPositionUpdate::Unlocked(UpdateType::Increment(diff)), Some(_) => VaultPositionUpdate::Locked(UpdateType::Increment(diff)), }, @@ -82,21 +77,16 @@ pub fn update_vault_coin_balance( )) } -pub fn assert_denoms_match_vault_reqs( +pub fn assert_denom_matches_vault_reqs( querier: QuerierWrapper, vault: &Vault, - coins: &[Coin], + coin: &Coin, ) -> ContractResult<()> { let vault_info = vault.query_info(&querier)?; - - let given_denoms = coins.iter().map(|c| c.denom.clone()).collect::>(); - let fulfills_reqs = contents_equal(&vault_info.accepts, &given_denoms); - - if !fulfills_reqs { + if vault_info.req_denom != coin.denom { return Err(ContractError::RequirementsNotMet(format!( - "Required assets: {} -- do not match given assets: {}", - vault_info.accepts.join(", "), - given_denoms.join(", ") + "Required coin: {} -- does not match given coin: {}", + vault_info.req_denom, coin.denom ))); } Ok(()) @@ -105,11 +95,11 @@ pub fn assert_denoms_match_vault_reqs( pub fn assert_deposit_is_under_cap( deps: Deps, vault: &Vault, - coins: &[Coin], + coin: &Coin, rover_addr: &Addr, ) -> ContractResult<()> { let oracle = ORACLE.load(deps.storage)?; - let deposit_request_value = oracle.query_total_value(&deps.querier, coins)?; + let deposit_request_value = oracle.query_total_value(&deps.querier, &[coin.clone()])?; let config = VAULT_CONFIGS.load(deps.storage, &vault.address)?; let deposit_cap_value = oracle.query_total_value(&deps.querier, &[config.deposit_cap])?; @@ -118,9 +108,9 @@ pub fn assert_deposit_is_under_cap( let rover_vault_coin_balance = vault.query_balance(&deps.querier, rover_addr)?; let rover_vault_coins_value = oracle.query_total_value( &deps.querier, - &[coin( + &[c( rover_vault_coin_balance.u128(), - vault_info.token_denom, + vault_info.vault_token_denom, )], )?; diff --git a/contracts/credit-manager/src/vault/withdraw.rs b/contracts/credit-manager/src/vault/exit.rs similarity index 98% rename from contracts/credit-manager/src/vault/withdraw.rs rename to contracts/credit-manager/src/vault/exit.rs index cf8d85635..ac6ace2fa 100644 --- a/contracts/credit-manager/src/vault/withdraw.rs +++ b/contracts/credit-manager/src/vault/exit.rs @@ -9,7 +9,7 @@ use crate::vault::utils::{ assert_vault_is_whitelisted, query_withdraw_denom_balances, update_vault_position, }; -pub fn withdraw_from_vault( +pub fn exit_vault( deps: DepsMut, env: Env, account_id: &str, diff --git a/contracts/credit-manager/src/vault/withdraw_unlocked.rs b/contracts/credit-manager/src/vault/exit_unlocked.rs similarity index 88% rename from contracts/credit-manager/src/vault/withdraw_unlocked.rs rename to contracts/credit-manager/src/vault/exit_unlocked.rs index 947496616..35824b04d 100644 --- a/contracts/credit-manager/src/vault/withdraw_unlocked.rs +++ b/contracts/credit-manager/src/vault/exit_unlocked.rs @@ -1,9 +1,9 @@ +use cosmos_vault_standard::extensions::lockup::Lockup; use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, WasmMsg}; use rover::adapters::vault::{UnlockingChange, Vault, VaultPositionUpdate}; use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; -use rover::msg::vault::UnlockingPosition; use rover::msg::ExecuteMsg; use crate::state::VAULT_POSITIONS; @@ -11,7 +11,7 @@ use crate::vault::utils::{ assert_vault_is_whitelisted, query_withdraw_denom_balances, update_vault_position, }; -pub fn withdraw_unlocked_from_vault( +pub fn exit_vault_unlocked( deps: DepsMut, env: Env, account_id: &str, @@ -24,9 +24,8 @@ pub fn withdraw_unlocked_from_vault( let matching_unlock = vault_position .get_unlocking_position(position_id) .ok_or_else(|| ContractError::NoPositionMatch(position_id.to_string()))?; - let UnlockingPosition { unlocked_at, .. } = - vault.query_unlocking_position_info(&deps.querier, matching_unlock.id)?; - if unlocked_at > env.block.time { + let Lockup { release_at, .. } = vault.query_lockup(&deps.querier, matching_unlock.id)?; + if !release_at.is_expired(&env.block) { return Err(ContractError::UnlockNotReady {}); } diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index 960fe2715..a81c9c35c 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -10,7 +10,6 @@ use rover::adapters::vault::{ use rover::error::ContractResult; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg; -use rover::traits::Denoms; use crate::liquidate_coin::calculate_liquidation; use crate::state::VAULT_POSITIONS; @@ -36,7 +35,7 @@ pub fn liquidate_vault( &env, liquidatee_account_id, &debt_coin, - &vault_info.token_denom, + &vault_info.vault_token_denom, liquidatee_position.total(), )?; @@ -60,11 +59,10 @@ pub fn liquidate_vault( )?; // Update coin balances of liquidator after withdraws have been made - let coins_from_withdraw = request_vault.query_preview_redeem(&deps.querier, request.amount)?; let previous_balances = query_balances( deps.as_ref(), &env.contract.address, - &coins_from_withdraw.to_denoms(), + &[vault_info.req_denom.as_str()], )?; let update_coin_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: env.contract.address.to_string(), @@ -117,6 +115,10 @@ fn get_vault_withdraw_msgs( for u in amount.unlocking.positions() { let amount = min(u.amount, total_to_liquidate); + if amount.is_zero() { + break; + } + update_vault_position( storage, liquidatee_account_id, diff --git a/contracts/credit-manager/src/vault/mod.rs b/contracts/credit-manager/src/vault/mod.rs index af63ebe2b..17fb700ff 100644 --- a/contracts/credit-manager/src/vault/mod.rs +++ b/contracts/credit-manager/src/vault/mod.rs @@ -1,13 +1,13 @@ -pub use self::deposit::*; +pub use self::enter::*; +pub use self::exit::*; +pub use self::exit_unlocked::*; pub use self::liquidate_vault::*; pub use self::request_unlock::*; pub use self::utils::*; -pub use self::withdraw::*; -pub use self::withdraw_unlocked::*; -mod deposit; +mod enter; +mod exit; +mod exit_unlocked; mod liquidate_vault; mod request_unlock; mod utils; -mod withdraw; -mod withdraw_unlocked; diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index 6b086d104..7ba5c2d3d 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -16,7 +16,7 @@ pub struct RequestTempStorage { pub amount: Uint128, } -pub fn request_unlock_from_vault( +pub fn request_vault_unlock( deps: DepsMut, account_id: &str, vault: Vault, @@ -24,12 +24,11 @@ pub fn request_unlock_from_vault( ) -> ContractResult { assert_vault_is_whitelisted(deps.storage, &vault)?; - let vault_info = vault.query_info(&deps.querier)?; - if vault_info.lockup.is_none() { - return Err(ContractError::RequirementsNotMet( + vault.query_lockup_duration(&deps.querier).map_err(|_| { + ContractError::RequirementsNotMet( "This vault does not require lockup. Call withdraw directly.".to_string(), - )); - } + ) + })?; VAULT_REQUEST_TEMP_STORAGE.save( deps.storage, @@ -39,10 +38,11 @@ pub fn request_unlock_from_vault( }, )?; - let request_unlock_msg = vault.request_unlock_msg(&[Coin { - denom: vault_info.token_denom, + let vault_info = vault.query_info(&deps.querier)?; + let request_unlock_msg = vault.request_unlock_msg(Coin { + denom: vault_info.vault_token_denom, amount, - }])?; + })?; Ok(Response::new() .add_submessage(request_unlock_msg) diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 399621bd7..2ed952c16 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,14 +1,9 @@ -use cosmwasm_std::{ - coin, to_binary, Addr, Coin, Deps, QueryRequest, StdResult, Storage, WasmQuery, -}; +use cosmwasm_std::{Addr, Coin, Deps, StdResult, Storage}; -use mars_oracle_adapter::msg::QueryMsg::PriceableUnderlying; -use rover::adapters::vault::{ - Total, Vault, VaultPosition, VaultPositionAmount, VaultPositionUpdate, -}; +use rover::adapters::vault::{Total, Vault, VaultPositionAmount, VaultPositionUpdate}; use rover::error::{ContractError, ContractResult}; -use crate::state::{ORACLE, VAULT_CONFIGS, VAULT_POSITIONS}; +use crate::state::{VAULT_CONFIGS, VAULT_POSITIONS}; use crate::update_coin_balances::query_balances; pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> ContractResult<()> { @@ -42,36 +37,12 @@ pub fn update_vault_position( Ok(amount) } -/// Returns the denoms received on a withdraw, inferred by vault entry requirements +/// Returns the total vault token balance for rover pub fn query_withdraw_denom_balances( deps: Deps, rover_addr: &Addr, vault: &Vault, ) -> StdResult> { let vault_info = vault.query_info(&deps.querier)?; - let denoms = vault_info - .accepts - .iter() - .map(String::as_str) - .collect::>(); - query_balances(deps, rover_addr, &denoms) -} - -/// Returns the mars-oracle accepted priceable coins -pub fn get_priceable_coins(deps: &Deps, positions: &[VaultPosition]) -> ContractResult> { - let oracle = ORACLE.load(deps.storage)?; - let mut coins: Vec = vec![]; - for p in positions { - let vault_info = p.vault.query_info(&deps.querier)?; - let total_vault_coins = p.amount.total(); - let priceable_coins: Vec = - deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: oracle.address().to_string(), - msg: to_binary(&PriceableUnderlying { - coin: coin(total_vault_coins.u128(), vault_info.token_denom), - })?, - }))?; - coins.extend(priceable_coins) - } - Ok(coins) + query_balances(deps, rover_addr, &[vault_info.req_denom.as_str()]) } diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index 401cd4edf..a503c2620 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -1,8 +1,9 @@ use cosmwasm_std::{coin, Decimal}; +use cw_utils::Duration; use rover::traits::IntoDecimal; -use crate::helpers::{CoinInfo, VaultTestInfo}; +use crate::helpers::{lp_token_info, CoinInfo, VaultTestInfo}; pub fn build_mock_coin_infos(count: usize) -> Vec { (1..=count) @@ -17,16 +18,17 @@ pub fn build_mock_coin_infos(count: usize) -> Vec { } pub fn build_mock_vaults(count: usize) -> Vec { + let lp_token = lp_token_info(); (1..=count) .into_iter() .map(|i| { VaultTestInfo { - denom: format!("vault_{}", i), - lockup: Some(1_209_600), // 14 days - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + vault_token_denom: format!("vault_{}", i), + lockup: Some(Duration::Time(1_209_600)), // 14 days + denom_req: lp_token.denom.clone(), deposit_cap: coin(10000000, "uusdc"), - max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + max_ltv: lp_token.max_ltv, + liquidation_threshold: lp_token.liquidation_threshold, } }) .collect() diff --git a/contracts/credit-manager/tests/helpers/mock_entity_info.rs b/contracts/credit-manager/tests/helpers/mock_entity_info.rs index 4ef942f28..5e8372d67 100644 --- a/contracts/credit-manager/tests/helpers/mock_entity_info.rs +++ b/contracts/credit-manager/tests/helpers/mock_entity_info.rs @@ -1,6 +1,7 @@ use crate::helpers::CoinInfo; use crate::helpers::VaultTestInfo; use cosmwasm_std::{coin, Decimal}; +use cw_utils::Duration; pub fn uosmo_info() -> CoinInfo { CoinInfo { @@ -28,19 +29,29 @@ pub fn ujake_info() -> CoinInfo { } } +pub fn lp_token_info() -> CoinInfo { + CoinInfo { + denom: "ugamm22".to_string(), + price: Decimal::from_atomics(9874u128, 3).unwrap(), + max_ltv: Decimal::from_atomics(63u128, 2).unwrap(), + liquidation_threshold: Decimal::from_atomics(68u128, 2).unwrap(), + } +} + pub fn locked_vault_info() -> VaultTestInfo { - generate_mock_vault(Some(1_209_600)) // 14 days) + generate_mock_vault(Some(Duration::Time(1_209_600))) // 14 days) } pub fn unlocked_vault_info() -> VaultTestInfo { generate_mock_vault(None) } -fn generate_mock_vault(lockup: Option) -> VaultTestInfo { +pub fn generate_mock_vault(lockup: Option) -> VaultTestInfo { + let lp_token = lp_token_info(); VaultTestInfo { - denom: "uleverage".to_string(), + vault_token_denom: "uleverage".to_string(), lockup, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], + denom_req: lp_token.denom, deposit_cap: coin(10_000_000, "uusdc"), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index db1b842db..5638973c2 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -2,13 +2,17 @@ use std::mem::take; use anyhow::Result as AnyResult; use cosmwasm_std::testing::MockApi; -use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128}; +use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use mars_outpost::red_bank::QueryMsg::UserDebt; use mars_outpost::red_bank::UserDebtResponse; use account_nft::msg::ExecuteMsg as NftExecuteMsg; +use cosmos_vault_standard::extensions::lockup::Lockup; +use cosmos_vault_standard::extensions::lockup::LockupQueryMsg::Lockups; +use cosmos_vault_standard::msg::QueryMsg::{Info as VaultInfoMsg, VaultExtension}; +use cosmos_vault_standard::msg::{AssetsResponse, ExtensionQueryMsg, VaultInfo}; use mars_oracle_adapter::msg::{ InstantiateMsg as OracleAdapterInstantiateMsg, PricingMethod, VaultPricingInfo, }; @@ -30,8 +34,6 @@ use rover::msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtShares, HealthResponse, Positions, SharesResponseItem, VaultPositionResponseItem, VaultWithBalance, }; -use rover::msg::vault::QueryMsg::{Info as VaultInfoMsg, UnlockingPositionsForAddr}; -use rover::msg::vault::{UnlockingPosition, VaultInfo}; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::helpers::{ @@ -249,7 +251,7 @@ impl MockEnv { .unwrap() .query_info(&self.app.wrap()) .unwrap(); - vault.denom == info.token_denom + vault.vault_token_denom == info.vault_token_denom }) .unwrap() .vault @@ -336,7 +338,7 @@ impl MockEnv { .unwrap() } - pub fn query_preview_redeem(&self, vault: &VaultUnchecked, shares: Uint128) -> Vec { + pub fn query_preview_redeem(&self, vault: &VaultUnchecked, shares: Uint128) -> AssetsResponse { vault .check(&MockApi::default()) .unwrap() @@ -344,30 +346,24 @@ impl MockEnv { .unwrap() } - pub fn query_unlocking_position_info( - &self, - vault: &VaultUnchecked, - id: u64, - ) -> UnlockingPosition { + pub fn query_lockup(&self, vault: &VaultUnchecked, id: u64) -> Lockup { vault .check(&MockApi::default()) .unwrap() - .query_unlocking_position_info(&self.app.wrap(), id) + .query_lockup(&self.app.wrap(), id) .unwrap() } - pub fn query_unlocking_positions( - &self, - vault: &VaultUnchecked, - manager_contract_addr: &Addr, - ) -> Vec { + pub fn query_lockups(&self, vault: &VaultUnchecked, addr: &Addr) -> Vec { self.app .wrap() .query_wasm_smart( vault.address.to_string(), - &UnlockingPositionsForAddr { - addr: manager_contract_addr.to_string(), - }, + &VaultExtension(ExtensionQueryMsg::Lockup(Lockups { + owner: addr.to_string(), + start_after: None, + limit: None, + })), ) .unwrap() } @@ -598,12 +594,13 @@ impl MockEnvBuilder { let info: VaultInfo = self .app .wrap() - .query_wasm_smart(config.vault.address.clone(), &VaultInfoMsg {}) + .query_wasm_smart(config.vault.address.clone(), &VaultInfoMsg:: {}) .unwrap(); VaultPricingInfo { - denom: info.token_denom, + vault_coin_denom: info.vault_token_denom, addr: Addr::unchecked(config.vault.address), method: PricingMethod::PreviewRedeem, + req_denom: info.req_denom, } }) .collect() @@ -684,9 +681,9 @@ impl MockEnvBuilder { code_id, Addr::unchecked("vault-instantiator"), &VaultInstantiateMsg { - lp_token_denom: vault.clone().denom, + vault_token_denom: vault.clone().vault_token_denom, lockup: vault.lockup, - asset_denoms: vault.clone().underlying_denoms, + req_denom: vault.clone().denom_req, oracle, }, &[], @@ -694,7 +691,7 @@ impl MockEnvBuilder { None, ) .unwrap(); - self.fund_vault(&addr, &vault.denom); + self.fund_vault(&addr, &vault.vault_token_denom); VaultInstantiateConfig { vault: VaultBase::new(addr.to_string()), config: VaultConfig { diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index a17e27dab..9d6e0e213 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{coin, Addr, Coin, Decimal}; +use cw_utils::Duration; #[cw_serde] pub struct AccountToFund { @@ -17,9 +18,9 @@ pub struct CoinInfo { #[cw_serde] pub struct VaultTestInfo { - pub denom: String, - pub lockup: Option, - pub underlying_denoms: Vec, + pub vault_token_denom: String, + pub lockup: Option, + pub denom_req: String, pub deposit_cap: Coin, pub max_ltv: Decimal, pub liquidation_threshold: Decimal, diff --git a/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs b/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs @@ -0,0 +1 @@ + diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index 2d1b29a38..ad28674c8 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -1,17 +1,16 @@ -use cosmwasm_std::{coin, Addr}; +use cosmwasm_std::Addr; use rover::msg::execute::Action; use crate::helpers::{ - assert_contents_equal, build_mock_vaults, uatom_info, uosmo_info, AccountToFund, MockEnv, + assert_contents_equal, build_mock_vaults, lp_token_info, AccountToFund, MockEnv, }; pub mod helpers; #[test] fn test_pagination_on_all_vault_coin_balances_query_works() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); + let lp_token = lp_token_info(); let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); @@ -21,39 +20,27 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { let mut mock = MockEnv::new() .fund_account(AccountToFund { addr: user_a.clone(), - funds: vec![ - coin(1000, uosmo.denom.clone()), - coin(1000, uatom.denom.clone()), - ], + funds: vec![lp_token.to_coin(1000)], }) .fund_account(AccountToFund { addr: user_b.clone(), - funds: vec![ - coin(1000, uosmo.denom.clone()), - coin(1000, uatom.denom.clone()), - ], + funds: vec![lp_token.to_coin(1000)], }) .fund_account(AccountToFund { addr: user_c.clone(), - funds: vec![ - coin(1000, uosmo.denom.clone()), - coin(1000, uatom.denom.clone()), - ], + funds: vec![lp_token.to_coin(1000)], }) - .allowed_coins(&[uosmo.clone(), uatom.clone()]) + .allowed_coins(&[lp_token.clone()]) .allowed_vaults(&all_vaults) .build() .unwrap(); - let mut actions = vec![ - Action::Deposit(uatom.to_coin(220)), - Action::Deposit(uosmo.to_coin(220)), - ]; + let mut actions = vec![Action::Deposit(lp_token.to_coin(220))]; all_vaults.iter().for_each(|v| { - actions.extend([Action::VaultDeposit { + actions.extend([Action::EnterVault { vault: mock.get_vault(v), - coins: vec![uatom.to_coin(10), uosmo.to_coin(10)], + coin: lp_token.to_coin(10), }]); }); @@ -62,7 +49,7 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { &account_id_a, &user_a, actions.clone(), - &[uatom.to_coin(220), uosmo.to_coin(220)], + &[lp_token.to_coin(220)], ) .unwrap(); @@ -71,18 +58,13 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { &account_id_b, &user_b, actions.clone(), - &[uatom.to_coin(220), uosmo.to_coin(220)], + &[lp_token.to_coin(220)], ) .unwrap(); let account_id_c = mock.create_credit_account(&user_c).unwrap(); - mock.update_credit_account( - &account_id_c, - &user_c, - actions, - &[uatom.to_coin(220), uosmo.to_coin(220)], - ) - .unwrap(); + mock.update_credit_account(&account_id_c, &user_c, actions, &[lp_token.to_coin(220)]) + .unwrap(); let vaults_res = mock.query_all_total_vault_coin_balances(None, Some(58_u32)); // Assert maximum is observed @@ -119,7 +101,7 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { .chain(vaults_res_c.iter().cloned()) .chain(vaults_res_d.iter().cloned()) .map(|v| v.vault.query_info(&mock.app.wrap()).unwrap()) - .map(|info| info.token_denom) + .map(|info| info.vault_token_denom) .collect::>(); assert_eq!(combined.len(), all_vaults.len()); @@ -127,7 +109,7 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { assert_contents_equal( &all_vaults .iter() - .map(|v| v.denom.clone()) + .map(|v| v.vault_token_denom.clone()) .collect::>(), &combined, ) diff --git a/contracts/credit-manager/tests/test_enumerate_vault_configs.rs b/contracts/credit-manager/tests/test_enumerate_vault_configs.rs index 7f4969b21..02b10f448 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_configs.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_configs.rs @@ -45,7 +45,7 @@ fn test_pagination_on_allowed_vaults_query_works() { .chain(vaults_res_d.iter().cloned()) .map(|v| v.vault.check(&MockApi::default()).unwrap()) .map(|v| v.query_info(&mock.app.wrap()).unwrap()) - .map(|info| info.token_denom) + .map(|info| info.vault_token_denom) .collect::>(); assert_eq!(combined.len(), allowed_vaults.len()); @@ -53,7 +53,7 @@ fn test_pagination_on_allowed_vaults_query_works() { assert_contents_equal( &allowed_vaults .iter() - .map(|v| v.denom.clone()) + .map(|v| v.vault_token_denom.clone()) .collect::>(), &combined, ) diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index cb5d273d7..d738e1b8c 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -1,18 +1,17 @@ -use cosmwasm_std::{coin, Addr}; +use cosmwasm_std::Addr; use itertools::Itertools; use rover::msg::execute::Action; use crate::helpers::{ - assert_contents_equal, build_mock_vaults, uatom_info, uosmo_info, AccountToFund, MockEnv, + assert_contents_equal, build_mock_vaults, lp_token_info, AccountToFund, MockEnv, }; pub mod helpers; #[test] fn test_pagination_on_all_vault_positions_query_works() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); + let lp_token = lp_token_info(); let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); @@ -22,39 +21,27 @@ fn test_pagination_on_all_vault_positions_query_works() { let mut mock = MockEnv::new() .fund_account(AccountToFund { addr: user_a.clone(), - funds: vec![ - coin(1000, uosmo.denom.clone()), - coin(1000, uatom.denom.clone()), - ], + funds: vec![lp_token.to_coin(1000)], }) .fund_account(AccountToFund { addr: user_b.clone(), - funds: vec![ - coin(1000, uosmo.denom.clone()), - coin(1000, uatom.denom.clone()), - ], + funds: vec![lp_token.to_coin(1000)], }) .fund_account(AccountToFund { addr: user_c.clone(), - funds: vec![ - coin(1000, uosmo.denom.clone()), - coin(1000, uatom.denom.clone()), - ], + funds: vec![lp_token.to_coin(1000)], }) - .allowed_coins(&[uosmo.clone(), uatom.clone()]) + .allowed_coins(&[lp_token.clone()]) .allowed_vaults(&all_vaults) .build() .unwrap(); - let mut actions = vec![ - Action::Deposit(uatom.to_coin(220)), - Action::Deposit(uosmo.to_coin(220)), - ]; + let mut actions = vec![Action::Deposit(lp_token.to_coin(220))]; all_vaults.iter().for_each(|v| { - actions.extend([Action::VaultDeposit { + actions.extend([Action::EnterVault { vault: mock.get_vault(v), - coins: vec![uatom.to_coin(10), uosmo.to_coin(10)], + coin: lp_token.to_coin(10), }]); }); @@ -63,7 +50,7 @@ fn test_pagination_on_all_vault_positions_query_works() { &account_id_a, &user_a, actions.clone(), - &[uatom.to_coin(220), uosmo.to_coin(220)], + &[lp_token.to_coin(220)], ) .unwrap(); @@ -72,18 +59,13 @@ fn test_pagination_on_all_vault_positions_query_works() { &account_id_b, &user_b, actions.clone(), - &[uatom.to_coin(220), uosmo.to_coin(220)], + &[lp_token.to_coin(220)], ) .unwrap(); let account_id_c = mock.create_credit_account(&user_c).unwrap(); - mock.update_credit_account( - &account_id_c, - &user_c, - actions, - &[uatom.to_coin(220), uosmo.to_coin(220)], - ) - .unwrap(); + mock.update_credit_account(&account_id_c, &user_c, actions, &[lp_token.to_coin(220)]) + .unwrap(); let vaults_res = mock.query_all_vault_positions(None, Some(58_u32)); // Assert maximum is observed @@ -132,7 +114,7 @@ fn test_pagination_on_all_vault_positions_query_works() { .chain(vaults_res_c.iter().cloned()) .chain(vaults_res_d.iter().cloned()) .map(|v| v.position.vault.query_info(&mock.app.wrap()).unwrap()) - .map(|info| info.token_denom) + .map(|info| info.vault_token_denom) .collect::>(); let deduped = combined.iter().unique().cloned().collect::>(); @@ -142,7 +124,7 @@ fn test_pagination_on_all_vault_positions_query_works() { assert_contents_equal( &all_vaults .iter() - .map(|v| v.denom.clone()) + .map(|v| v.vault_token_denom.clone()) .collect::>(), &deduped, ) diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index aa5dc30f9..fb0082818 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -604,5 +604,3 @@ fn test_debt_value() { fn find_by_denom<'a>(denom: &'a str, shares: &'a [DebtAmount]) -> &'a DebtAmount { shares.iter().find(|item| item.denom == *denom).unwrap() } - -// TODO: Test vault positions taken into account diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 45a1fbe6c..ed8328402 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -34,25 +34,25 @@ fn test_nft_contract_addr_not_set_on_instantiate() { fn test_allowed_vaults_set_on_instantiate() { let allowed_vaults = vec![ VaultTestInfo { - denom: "vault_contract_1".to_string(), + vault_token_denom: "vault_contract_1".to_string(), lockup: None, - underlying_denoms: vec![], + denom_req: "lp_denom_123".to_string(), deposit_cap: coin(1_000_000, "uusdc"), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), }, VaultTestInfo { - denom: "vault_contract_2".to_string(), + vault_token_denom: "vault_contract_2".to_string(), lockup: None, - underlying_denoms: vec![], + denom_req: "lp_denom_123".to_string(), deposit_cap: coin(1_000_000, "uusdc"), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), }, VaultTestInfo { - denom: "vault_contract_3".to_string(), + vault_token_denom: "vault_contract_3".to_string(), lockup: None, - underlying_denoms: vec![], + denom_req: "lp_denom_123".to_string(), deposit_cap: coin(1_000_000, "uusdc"), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), @@ -90,12 +90,12 @@ fn test_raises_on_invalid_vaults_config() { .pre_deployed_vault( "addr_123", &VaultTestInfo { - denom: "uleverage".to_string(), + vault_token_denom: "uleverage".to_string(), lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], deposit_cap: coin(10_000_000, "uusdc"), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + denom_req: "lp_denom_123".to_string(), }, ) .build(); @@ -108,12 +108,12 @@ fn test_raises_on_invalid_vaults_config() { .pre_deployed_vault( "addr_123", &VaultTestInfo { - denom: "uleverage".to_string(), + vault_token_denom: "uleverage".to_string(), lockup: None, - underlying_denoms: vec!["uatom".to_string(), "uosmo".to_string()], deposit_cap: coin(10_000_000, "uusdc"), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 0).unwrap(), + denom_req: "lp_denom_123".to_string(), }, ) .build(); diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 715903366..d1cb89bad 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -1,14 +1,14 @@ -use cosmwasm_std::{coin, coins, Addr, Coin, Decimal, OverflowError, OverflowOperation, Uint128}; +use cosmwasm_std::{coins, Addr, Coin, Decimal, OverflowError, OverflowOperation, Uint128}; use mock_oracle::msg::CoinPrice; use rover::error::ContractError; use rover::error::ContractError::{AboveMaxLTV, NotLiquidatable}; -use rover::msg::execute::Action::{Borrow, Deposit, LiquidateCoin, VaultDeposit}; +use rover::msg::execute::Action::{Borrow, Deposit, EnterVault, LiquidateCoin}; use rover::traits::IntoDecimal; use crate::helpers::{ - assert_err, get_coin, get_debt, uatom_info, ujake_info, unlocked_vault_info, uosmo_info, - AccountToFund, MockEnv, + assert_err, get_coin, get_debt, lp_token_info, uatom_info, ujake_info, unlocked_vault_info, + uosmo_info, AccountToFund, MockEnv, }; pub mod helpers; @@ -71,17 +71,17 @@ fn test_can_only_liquidate_unhealthy_accounts() { #[test] fn test_vault_positions_contribute_to_health() { - let uosmo_info = uosmo_info(); - let uatom_info = uatom_info(); + let atom_info = uatom_info(); + let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom_info.clone(), uosmo_info.clone()]) + .allowed_coins(&[lp_token.clone(), atom_info.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + funds: vec![lp_token.to_coin(500)], }) .build() .unwrap(); @@ -93,15 +93,14 @@ fn test_vault_positions_contribute_to_health() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uatom_info.to_coin(200)), - Deposit(uosmo_info.to_coin(401)), - VaultDeposit { + Deposit(lp_token.to_coin(220)), + EnterVault { vault, - coins: vec![coin(200, "uatom"), coin(400, "uosmo")], + coin: lp_token.to_coin(200), }, - Borrow(uatom_info.to_coin(14)), + Borrow(atom_info.to_coin(14)), ], - &[coin(200, "uatom"), coin(401, "uosmo")], + &[lp_token.to_coin(220)], ) .unwrap(); @@ -116,8 +115,8 @@ fn test_vault_positions_contribute_to_health() { &liquidator, vec![LiquidateCoin { liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: uatom_info.to_coin(10), - request_coin_denom: uosmo_info.denom, + debt_coin: atom_info.to_coin(10), + request_coin_denom: atom_info.denom, }], &[], ); @@ -126,7 +125,7 @@ fn test_vault_positions_contribute_to_health() { res, NotLiquidatable { account_id: liquidatee_account_id, - lqdt_health_factor: "14.853".to_string(), + lqdt_health_factor: "101.94976".to_string(), }, ) } diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 3027581e6..068f77ae0 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -1,18 +1,18 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::StdError::NotFound; -use cosmwasm_std::{Addr, OverflowError, Uint128}; +use cosmwasm_std::{Addr, Decimal, OverflowError, Uint128}; use mock_oracle::msg::CoinPrice; use rover::adapters::vault::VaultBase; use rover::error::ContractError; use rover::msg::execute::Action::{ - Borrow, Deposit, LiquidateVault, VaultDeposit, VaultRequestUnlock, + Borrow, Deposit, EnterVault, LiquidateVault, RequestVaultUnlock, }; use rover::traits::IntoDecimal; use crate::helpers::{ - assert_err, get_coin, get_debt, locked_vault_info, uatom_info, ujake_info, unlocked_vault_info, - uosmo_info, AccountToFund, MockEnv, + assert_err, get_coin, get_debt, locked_vault_info, lp_token_info, uatom_info, ujake_info, + unlocked_vault_info, uosmo_info, AccountToFund, MockEnv, }; pub mod helpers; @@ -69,17 +69,16 @@ fn test_liquidatee_must_have_the_request_vault_position() { #[test] fn test_liquidatee_is_not_liquidatable() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); + let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_coins(&[lp_token.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], + funds: vec![lp_token.to_coin(300)], }) .build() .unwrap(); @@ -91,14 +90,13 @@ fn test_liquidatee_is_not_liquidatable() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uatom.to_coin(200)), - Deposit(uosmo.to_coin(400)), - VaultDeposit { + Deposit(lp_token.to_coin(200)), + EnterVault { vault, - coins: vec![uatom.to_coin(200), uosmo.to_coin(400)], + coin: lp_token.to_coin(200), }, ], - &[uatom.to_coin(200), uosmo.to_coin(400)], + &[lp_token.to_coin(200)], ) .unwrap(); @@ -110,7 +108,7 @@ fn test_liquidatee_is_not_liquidatable() { &liquidator, vec![LiquidateVault { liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: uatom.to_coin(10), + debt_coin: lp_token.to_coin(10), request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), }], &[], @@ -127,18 +125,17 @@ fn test_liquidatee_is_not_liquidatable() { #[test] fn test_liquidator_does_not_have_debt_coin_in_credit_account() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); + let lp_token = lp_token_info(); let ujake = ujake_info(); let leverage_vault = unlocked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone(), ujake.clone()]) + .allowed_coins(&[lp_token.clone(), ujake.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], + funds: vec![lp_token.to_coin(300)], }) .build() .unwrap(); @@ -150,15 +147,14 @@ fn test_liquidator_does_not_have_debt_coin_in_credit_account() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uatom.to_coin(300)), - Deposit(uosmo.to_coin(400)), - VaultDeposit { + Deposit(lp_token.to_coin(200)), + EnterVault { vault, - coins: vec![uatom.to_coin(200), uosmo.to_coin(400)], + coin: lp_token.to_coin(200), }, Borrow(ujake.to_coin(175)), ], - &[uatom.to_coin(300), uosmo.to_coin(400)], + &[lp_token.to_coin(200)], ) .unwrap(); @@ -193,8 +189,7 @@ fn test_liquidator_does_not_have_debt_coin_in_credit_account() { #[test] fn test_liquidate_unlocked_vault() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); + let lp_token = lp_token_info(); let ujake = ujake_info(); let leverage_vault = unlocked_vault_info(); @@ -202,11 +197,11 @@ fn test_liquidate_unlocked_vault() { let liquidator = Addr::unchecked("liquidator"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone(), ujake.clone()]) + .allowed_coins(&[lp_token.clone(), ujake.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], + funds: vec![lp_token.to_coin(300)], }) .fund_account(AccountToFund { addr: liquidator.clone(), @@ -222,15 +217,14 @@ fn test_liquidate_unlocked_vault() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uatom.to_coin(300)), - Deposit(uosmo.to_coin(400)), - VaultDeposit { + Deposit(lp_token.to_coin(200)), + EnterVault { vault, - coins: vec![uatom.to_coin(200), uosmo.to_coin(400)], + coin: lp_token.to_coin(200), }, Borrow(ujake.to_coin(175)), ], - &[uatom.to_coin(300), uosmo.to_coin(400)], + &[lp_token.to_coin(200)], ) .unwrap(); @@ -260,13 +254,11 @@ fn test_liquidate_unlocked_vault() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_balance = position.vaults.first().unwrap().amount.unlocked(); - assert_eq!(vault_balance, Uint128::new(300_000)); // Vault position liquidated by 70% + assert_eq!(vault_balance, Uint128::new(893_661)); // 1M - 106_339 - assert_eq!(position.coins.len(), 2); + assert_eq!(position.coins.len(), 1); let jake_balance = get_coin("ujake", &position.coins); assert_eq!(jake_balance.amount, Uint128::new(175)); - let atom_balance = get_coin("uatom", &position.coins); - assert_eq!(atom_balance.amount, Uint128::new(100)); assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("ujake", &position.debts); @@ -274,34 +266,31 @@ fn test_liquidate_unlocked_vault() { // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.coins.len(), 2); + assert_eq!(position.coins.len(), 1); assert_eq!(position.debts.len(), 0); - let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(280)); - let atom_balance = get_coin("uatom", &position.coins); - assert_eq!(atom_balance.amount, Uint128::new(140)); + let lp = get_coin(&lp_token.denom, &position.coins); + assert_eq!(lp.amount, Uint128::new(21)); } #[test] fn test_liquidate_locked_vault() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); - let ujake = ujake_info(); + let lp_token = lp_token_info(); + let atom = uatom_info(); let leverage_vault = locked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let liquidator = Addr::unchecked("liquidator"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone(), ujake.clone()]) + .allowed_coins(&[lp_token.clone(), atom.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], + funds: vec![lp_token.to_coin(300)], }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: vec![ujake.to_coin(10)], + funds: vec![atom.to_coin(35)], }) .build() .unwrap(); @@ -313,24 +302,23 @@ fn test_liquidate_locked_vault() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uatom.to_coin(300)), - Deposit(uosmo.to_coin(400)), - VaultDeposit { + Deposit(lp_token.to_coin(80)), + EnterVault { vault: vault.clone(), - coins: vec![uatom.to_coin(200), uosmo.to_coin(400)], + coin: lp_token.to_coin(80), }, - Borrow(ujake.to_coin(175)), - VaultRequestUnlock { + Borrow(atom.to_coin(700)), + RequestVaultUnlock { vault, amount: Uint128::new(100_000), }, ], - &[uatom.to_coin(300), uosmo.to_coin(400)], + &[lp_token.to_coin(80)], ) .unwrap(); mock.price_change(CoinPrice { - denom: ujake.denom.clone(), + denom: atom.denom.clone(), price: Uint128::new(20).to_dec().unwrap(), }); @@ -340,14 +328,14 @@ fn test_liquidate_locked_vault() { &liquidator_account_id, &liquidator, vec![ - Deposit(ujake.to_coin(10)), + Deposit(atom.to_coin(35)), LiquidateVault { liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: ujake.to_coin(10), + debt_coin: atom.to_coin(35), request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), }, ], - &[ujake.to_coin(10)], + &[atom.to_coin(35)], ) .unwrap(); @@ -355,34 +343,29 @@ fn test_liquidate_locked_vault() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_amount = position.vaults.first().unwrap().amount.clone(); - // Vault position liquidated by 70%. Unlocking first, then locked bucket. - assert_eq!(vault_amount.unlocking().len(), 0); - assert_eq!(vault_amount.locked(), Uint128::new(300_000)); + // 930,473 vault tokens liquidated + assert_eq!(vault_amount.unlocking().len(), 0); // unlocking first: 100k - 100k = 0 + assert_eq!(vault_amount.locked(), Uint128::new(69_527)); // then locked bucket: 900k - 830,473 = 69_527 - assert_eq!(position.coins.len(), 2); - let jake_balance = get_coin("ujake", &position.coins); - assert_eq!(jake_balance.amount, Uint128::new(175)); + assert_eq!(position.coins.len(), 1); let atom_balance = get_coin("uatom", &position.coins); - assert_eq!(atom_balance.amount, Uint128::new(100)); + assert_eq!(atom_balance.amount, Uint128::new(700)); assert_eq!(position.debts.len(), 1); - let atom_debt = get_debt("ujake", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(166)); + let atom_debt = get_debt("uatom", &position.debts); + assert_eq!(atom_debt.amount, Uint128::new(666)); // 701 - 35 // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.coins.len(), 2); + assert_eq!(position.coins.len(), 1); assert_eq!(position.debts.len(), 0); - let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(280)); - let atom_balance = get_coin("uatom", &position.coins); - assert_eq!(atom_balance.amount, Uint128::new(140)); + let lp_balance = get_coin(&lp_token.denom, &position.coins); + assert_eq!(lp_balance.amount, Uint128::new(74)); } #[test] fn test_liquidate_unlocking_priority() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); + let lp_token = lp_token_info(); let ujake = ujake_info(); let leverage_vault = locked_vault_info(); @@ -390,11 +373,11 @@ fn test_liquidate_unlocking_priority() { let liquidator = Addr::unchecked("liquidator"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone(), ujake.clone()]) + .allowed_coins(&[lp_token.clone(), ujake.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], + funds: vec![lp_token.to_coin(300)], }) .fund_account(AccountToFund { addr: liquidator.clone(), @@ -410,27 +393,26 @@ fn test_liquidate_unlocking_priority() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uatom.to_coin(300)), - Deposit(uosmo.to_coin(400)), - VaultDeposit { + Deposit(lp_token.to_coin(200)), + EnterVault { vault: vault.clone(), - coins: vec![uatom.to_coin(200), uosmo.to_coin(400)], + coin: lp_token.to_coin(200), }, Borrow(ujake.to_coin(175)), - VaultRequestUnlock { + RequestVaultUnlock { vault: vault.clone(), amount: Uint128::new(10_000), }, - VaultRequestUnlock { + RequestVaultUnlock { vault: vault.clone(), amount: Uint128::new(200_000), }, - VaultRequestUnlock { + RequestVaultUnlock { vault, amount: Uint128::new(700_000), }, ], - &[uatom.to_coin(300), uosmo.to_coin(400)], + &[lp_token.to_coin(200)], ) .unwrap(); @@ -462,17 +444,22 @@ fn test_liquidate_unlocking_priority() { let vault_amount = position.vaults.first().unwrap().amount.clone(); assert_eq!(vault_amount.unlocked(), Uint128::zero()); assert_eq!(vault_amount.locked(), Uint128::new(90_000)); - assert_eq!(vault_amount.unlocking().len(), 1); + // Total liquidated: 106,339 + // First bucket drained + // Second bucket partially liquidated + assert_eq!(vault_amount.unlocking().len(), 2); assert_eq!( vault_amount.unlocking().first().unwrap().amount, - Uint128::new(210_000) + Uint128::new(200_000 - (106_339 - 10_000)) + ); + assert_eq!( + vault_amount.unlocking().get(1).unwrap().amount, + Uint128::new(700_000) ); - assert_eq!(position.coins.len(), 2); + assert_eq!(position.coins.len(), 1); let jake_balance = get_coin("ujake", &position.coins); assert_eq!(jake_balance.amount, Uint128::new(175)); - let atom_balance = get_coin("uatom", &position.coins); - assert_eq!(atom_balance.amount, Uint128::new(100)); assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("ujake", &position.debts); @@ -480,19 +467,16 @@ fn test_liquidate_unlocking_priority() { // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.coins.len(), 2); + assert_eq!(position.coins.len(), 1); assert_eq!(position.debts.len(), 0); - let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(280)); - let atom_balance = get_coin("uatom", &position.coins); - assert_eq!(atom_balance.amount, Uint128::new(140)); + let osmo_balance = get_coin(&lp_token.denom, &position.coins); + assert_eq!(osmo_balance.amount, Uint128::new(21)); } // NOTE: liquidation calculation+adjustments are quite complex, full cases in test_liquidate_coin.rs #[test] fn test_liquidation_calculation_adjustment() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); + let lp_token = lp_token_info(); let ujake = ujake_info(); let leverage_vault = unlocked_vault_info(); @@ -500,16 +484,17 @@ fn test_liquidation_calculation_adjustment() { let liquidator = Addr::unchecked("liquidator"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone(), ujake.clone()]) + .allowed_coins(&[lp_token.clone(), ujake.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], + funds: vec![lp_token.to_coin(300)], }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: vec![ujake.to_coin(50)], + funds: vec![ujake.to_coin(500)], }) + .max_close_factor(Decimal::from_atomics(87u128, 2).unwrap()) .build() .unwrap(); @@ -520,15 +505,14 @@ fn test_liquidation_calculation_adjustment() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uatom.to_coin(300)), - Deposit(uosmo.to_coin(400)), - VaultDeposit { + Deposit(lp_token.to_coin(200)), + EnterVault { vault, - coins: vec![uatom.to_coin(200), uosmo.to_coin(400)], + coin: lp_token.to_coin(200), }, Borrow(ujake.to_coin(175)), ], - &[uatom.to_coin(300), uosmo.to_coin(400)], + &[lp_token.to_coin(200)], ) .unwrap(); @@ -543,16 +527,16 @@ fn test_liquidation_calculation_adjustment() { &liquidator_account_id, &liquidator, vec![ - Deposit(ujake.to_coin(50)), + Deposit(ujake.to_coin(500)), LiquidateVault { liquidatee_account_id: liquidatee_account_id.clone(), // Given the request vault balance, this debt payment is too high. - // It will be adjusted to 14, the max given the request vault value - debt_coin: ujake.to_coin(50), + // It will be adjusted to 94, the max given the request vault value + debt_coin: ujake.to_coin(500), request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), }, ], - &[ujake.to_coin(50)], + &[ujake.to_coin(500)], ) .unwrap(); @@ -560,26 +544,22 @@ fn test_liquidation_calculation_adjustment() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_balance = position.vaults.first().unwrap().amount.unlocked(); - assert_eq!(vault_balance, Uint128::new(20_000)); // Vault position liquidated by 98% + assert_eq!(vault_balance, Uint128::new(406)); // Vault position liquidated by 99% - assert_eq!(position.coins.len(), 2); + assert_eq!(position.coins.len(), 1); let jake_balance = get_coin("ujake", &position.coins); assert_eq!(jake_balance.amount, Uint128::new(175)); - let atom_balance = get_coin("uatom", &position.coins); - assert_eq!(atom_balance.amount, Uint128::new(100)); assert_eq!(position.debts.len(), 1); - let atom_debt = get_debt("ujake", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(162)); + let ujake_debt = get_debt("ujake", &position.debts); + assert_eq!(ujake_debt.amount, Uint128::new(82)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.coins.len(), 3); + assert_eq!(position.coins.len(), 2); let osmo_balance = get_coin("ujake", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(36)); - let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(392)); - let atom_balance = get_coin("uatom", &position.coins); - assert_eq!(atom_balance.amount, Uint128::new(196)); + assert_eq!(osmo_balance.amount, Uint128::new(406)); + let atom_balance = get_coin(&lp_token.denom, &position.coins); + assert_eq!(atom_balance.amount, Uint128::new(199)); assert_eq!(position.debts.len(), 0); } diff --git a/contracts/credit-manager/tests/test_vault_deposit.rs b/contracts/credit-manager/tests/test_vault_enter.rs similarity index 62% rename from contracts/credit-manager/tests/test_vault_deposit.rs rename to contracts/credit-manager/tests/test_vault_enter.rs index 4aa88b43b..d415dbb99 100644 --- a/contracts/credit-manager/tests/test_vault_deposit.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -1,14 +1,14 @@ use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{coin, coins, Addr, Decimal, OverflowError, Uint128}; +use cosmwasm_std::{coin, Addr, OverflowError, Uint128}; use mock_vault::contract::STARTING_VAULT_SHARES; use rover::adapters::vault::VaultBase; use rover::error::ContractError; -use rover::msg::execute::Action::{Deposit, VaultDeposit}; +use rover::msg::execute::Action::{Deposit, EnterVault}; use crate::helpers::{ - assert_err, locked_vault_info, uatom_info, unlocked_vault_info, uosmo_info, AccountToFund, - MockEnv, VaultTestInfo, + assert_err, locked_vault_info, lp_token_info, uatom_info, unlocked_vault_info, uosmo_info, + AccountToFund, MockEnv, }; pub mod helpers; @@ -24,9 +24,9 @@ fn test_only_account_owner_can_take_action() { let res = mock.update_credit_account( &account_id, &bad_guy, - vec![VaultDeposit { + vec![EnterVault { vault: VaultBase::new("xyz".to_string()), - coins: vec![], + coin: coin(1, "uosmo"), }], &[], ); @@ -41,13 +41,12 @@ fn test_only_account_owner_can_take_action() { } #[test] -fn test_all_deposit_coins_are_whitelisted() { - let uatom = uatom_info(); +fn test_deposit_denom_is_whitelisted() { + let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom]) .allowed_vaults(&[leverage_vault.clone()]) .build() .unwrap(); @@ -58,14 +57,14 @@ fn test_all_deposit_coins_are_whitelisted() { let res = mock.update_credit_account( &account_id, &user, - vec![VaultDeposit { + vec![EnterVault { vault, - coins: vec![coin(200, "uatom"), coin(400, "uosmo")], + coin: coin(200, lp_token.denom.clone()), }], &[], ); - assert_err(res, ContractError::NotWhitelisted("uosmo".to_string())); + assert_err(res, ContractError::NotWhitelisted(lp_token.denom)); } #[test] @@ -86,9 +85,9 @@ fn test_vault_is_whitelisted() { let res = mock.update_credit_account( &account_id, &user, - vec![VaultDeposit { + vec![EnterVault { vault: VaultBase::new("unknown_vault".to_string()), - coins: coins(200, "uatom"), + coin: coin(200, "uatom"), }], &[], ); @@ -100,22 +99,13 @@ fn test_vault_is_whitelisted() { } #[test] -fn test_deposited_coins_match_vault_requirements() { +fn test_deposited_coin_matches_vault_requirements() { let uatom = uatom_info(); - let uosmo = uosmo_info(); - - let leverage_vault = VaultTestInfo { - denom: "uleverage".to_string(), - lockup: None, - underlying_denoms: vec!["uatom".to_string(), "ujake".to_string()], - deposit_cap: coin(1_000_000, "uusdc"), - max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), - }; + let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom, uosmo]) + .allowed_coins(&[uatom]) .allowed_vaults(&[leverage_vault.clone()]) .build() .unwrap(); @@ -125,9 +115,9 @@ fn test_deposited_coins_match_vault_requirements() { let res = mock.update_credit_account( &account_id, &user, - vec![VaultDeposit { + vec![EnterVault { vault: mock.get_vault(&leverage_vault), - coins: vec![coin(200, "uatom"), coin(200, "uosmo")], + coin: coin(200, "uatom"), }], &[], ); @@ -135,24 +125,23 @@ fn test_deposited_coins_match_vault_requirements() { assert_err( res, ContractError::RequirementsNotMet( - "Required assets: uatom, ujake -- do not match given assets: uatom, uosmo".to_string(), + "Required coin: ugamm22 -- does not match given coin: uatom".to_string(), ), ); } #[test] fn test_fails_if_not_enough_funds_for_deposit() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); + let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom, uosmo]) + .allowed_coins(&[lp_token.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + funds: vec![lp_token.to_coin(300)], }) .build() .unwrap(); @@ -162,9 +151,9 @@ fn test_fails_if_not_enough_funds_for_deposit() { let res = mock.update_credit_account( &account_id, &user, - vec![VaultDeposit { + vec![EnterVault { vault: mock.get_vault(&leverage_vault), - coins: vec![coin(200, "uatom"), coin(200, "uosmo")], + coin: coin(200, lp_token.denom), }], &[], ); @@ -181,17 +170,16 @@ fn test_fails_if_not_enough_funds_for_deposit() { #[test] fn test_successful_deposit_into_locked_vault() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); + let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_coins(&[lp_token.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + funds: vec![lp_token.to_coin(300)], }) .build() .unwrap(); @@ -205,18 +193,17 @@ fn test_successful_deposit_into_locked_vault() { &account_id, &user, vec![ - Deposit(coin(200, uatom.denom)), - Deposit(coin(400, uosmo.denom)), - VaultDeposit { + Deposit(lp_token.to_coin(200)), + EnterVault { vault: vault.clone(), - coins: vec![coin(23, "uatom"), coin(120, "uosmo")], + coin: lp_token.to_coin(23), }, ], - &[coin(200, "uatom"), coin(400, "uosmo")], + &[lp_token.to_coin(200)], ) .unwrap(); - let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.vault_token_denom); assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); let res = mock.query_positions(&account_id); @@ -231,32 +218,28 @@ fn test_successful_deposit_into_locked_vault() { ); let assets = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().amount.locked()); - - let osmo_withdraw = assets.iter().find(|coin| coin.denom == "uosmo").unwrap(); - assert_eq!(osmo_withdraw.amount, Uint128::new(120)); - let atom_withdraw = assets.iter().find(|coin| coin.denom == "uatom").unwrap(); - assert_eq!(atom_withdraw.amount, Uint128::new(23)); + assert_eq!(assets.coin.denom, lp_token.denom); + assert_eq!(assets.coin.amount, Uint128::new(23)); let balance = mock.query_total_vault_coin_balance(&vault); assert_eq!(balance, STARTING_VAULT_SHARES); - let vault_token_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); + let vault_token_balance = mock.query_balance(&mock.rover, &leverage_vault.vault_token_denom); assert_eq!(vault_token_balance.amount, STARTING_VAULT_SHARES) } #[test] fn test_successful_deposit_into_unlocked_vault() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); + let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_coins(&[lp_token.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + funds: vec![lp_token.to_coin(300)], }) .build() .unwrap(); @@ -268,18 +251,17 @@ fn test_successful_deposit_into_unlocked_vault() { &account_id, &user, vec![ - Deposit(coin(200, uatom.denom)), - Deposit(coin(400, uosmo.denom)), - VaultDeposit { + Deposit(lp_token.to_coin(200)), + EnterVault { vault: vault.clone(), - coins: vec![coin(23, "uatom"), coin(120, "uosmo")], + coin: lp_token.to_coin(23), }, ], - &[coin(200, "uatom"), coin(400, "uosmo")], + &[lp_token.to_coin(200)], ) .unwrap(); - let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.vault_token_denom); assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); let res = mock.query_positions(&account_id); @@ -291,32 +273,28 @@ fn test_successful_deposit_into_unlocked_vault() { assert_eq!(Uint128::zero(), res.vaults.first().unwrap().amount.locked()); let assets = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().amount.unlocked()); - - let osmo_withdraw = assets.iter().find(|coin| coin.denom == "uosmo").unwrap(); - assert_eq!(osmo_withdraw.amount, Uint128::new(120)); - let atom_withdraw = assets.iter().find(|coin| coin.denom == "uatom").unwrap(); - assert_eq!(atom_withdraw.amount, Uint128::new(23)); + assert_eq!(assets.coin.denom, lp_token.denom); + assert_eq!(assets.coin.amount, Uint128::new(23)); let balance = mock.query_total_vault_coin_balance(&vault); assert_eq!(balance, STARTING_VAULT_SHARES); - let vault_token_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); + let vault_token_balance = mock.query_balance(&mock.rover, &leverage_vault.vault_token_denom); assert_eq!(vault_token_balance.amount, STARTING_VAULT_SHARES) } #[test] fn test_vault_deposit_must_be_under_cap() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); + let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_coins(&[lp_token.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![coin(9_600_000, "uatom"), coin(18_500_000, "uosmo")], + funds: vec![lp_token.to_coin(3_300_000)], }) .build() .unwrap(); @@ -325,62 +303,59 @@ fn test_vault_deposit_must_be_under_cap() { let account_id = mock.create_credit_account(&user).unwrap(); // Vault deposit A ✅ - // new total value = 9_500_000 - // left to deposit = 2_845_000 + // new total value = 6_911_800 + // left to deposit = 5_433_200 mock.update_credit_account( &account_id, &user, vec![ - Deposit(coin(7_000_000, uatom.denom.clone())), - Deposit(coin(10_000_000, uosmo.denom.clone())), - VaultDeposit { + Deposit(lp_token.to_coin(700_000)), + EnterVault { vault: vault.clone(), - coins: vec![coin(7_000_000, "uatom"), coin(10_000_000, "uosmo")], + coin: lp_token.to_coin(700_000), }, ], - &[coin(7_000_000, "uatom"), coin(10_000_000, "uosmo")], + &[lp_token.to_coin(700_000)], ) .unwrap(); // Vault deposit B ✅ - // new total value = 9_850_000 - // left to deposit = 2_495_000 + // new total value = 7_899_200 + // left to deposit = 4_445_800 mock.update_credit_account( &account_id, &user, vec![ - Deposit(coin(100_000, uatom.denom.clone())), - Deposit(coin(1_000_000, uosmo.denom.clone())), - VaultDeposit { + Deposit(lp_token.to_coin(100_000)), + EnterVault { vault: vault.clone(), - coins: vec![coin(100_000, "uatom"), coin(1_000_000, "uosmo")], + coin: lp_token.to_coin(100_000), }, ], - &[coin(100_000, "uatom"), coin(1_000_000, "uosmo")], + &[lp_token.to_coin(100_000)], ) .unwrap(); // Vault deposit C 🚫 - // new total value = 14_225_000 - // left to deposit = -1_880_000 + // new total value = 32_584_200 + // left to deposit = -20_239_200 let res = mock.update_credit_account( &account_id, &user, vec![ - Deposit(coin(2_500_000, uatom.denom)), - Deposit(coin(7_500_000, uosmo.denom)), - VaultDeposit { + Deposit(lp_token.to_coin(2_500_000)), + EnterVault { vault, - coins: vec![coin(2_500_000, "uatom"), coin(7_500_000, "uosmo")], + coin: lp_token.to_coin(2_500_000), }, ], - &[coin(2_500_000, "uatom"), coin(7_500_000, "uosmo")], + &[lp_token.to_coin(2_500_000)], ); assert_err( res, ContractError::AboveVaultDepositCap { - new_value: "14224999.999999999999394422".to_string(), + new_value: "32584199.999999999998984572".to_string(), maximum: "12345000".to_string(), }, ); diff --git a/contracts/credit-manager/tests/test_vault_withdraw.rs b/contracts/credit-manager/tests/test_vault_exit.rs similarity index 72% rename from contracts/credit-manager/tests/test_vault_withdraw.rs rename to contracts/credit-manager/tests/test_vault_exit.rs index f582d0ff9..6e45565f8 100644 --- a/contracts/credit-manager/tests/test_vault_withdraw.rs +++ b/contracts/credit-manager/tests/test_vault_exit.rs @@ -5,12 +5,12 @@ use mock_vault::contract::STARTING_VAULT_SHARES; use rover::adapters::vault::VaultBase; use rover::error::ContractError; use rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; -use rover::msg::execute::Action::{Deposit, VaultDeposit, VaultWithdraw}; +use rover::msg::execute::Action::{Deposit, EnterVault, ExitVault}; use rover::msg::execute::CallbackMsg; use crate::helpers::{ - assert_err, locked_vault_info, uatom_info, unlocked_vault_info, uosmo_info, AccountToFund, - MockEnv, + assert_err, locked_vault_info, lp_token_info, uatom_info, unlocked_vault_info, uosmo_info, + AccountToFund, MockEnv, }; pub mod helpers; @@ -26,7 +26,7 @@ fn test_only_owner_of_token_can_withdraw_from_vault() { let res = mock.update_credit_account( &account_id, &bad_guy, - vec![VaultWithdraw { + vec![ExitVault { vault: VaultBase::new("some_vault".to_string()), amount: STARTING_VAULT_SHARES, }], @@ -52,7 +52,7 @@ fn test_can_only_take_action_on_whitelisted_vaults() { let res = mock.update_credit_account( &account_id, &user, - vec![VaultWithdraw { + vec![ExitVault { vault: VaultBase::new("not_allowed_vault".to_string()), amount: STARTING_VAULT_SHARES, }], @@ -89,7 +89,7 @@ fn test_no_unlocked_vault_coins_to_withdraw() { vec![ Deposit(coin(200, uatom.denom)), Deposit(coin(200, uosmo.denom)), - VaultWithdraw { + ExitVault { vault, amount: STARTING_VAULT_SHARES, }, @@ -122,7 +122,7 @@ fn test_force_withdraw_can_only_be_called_by_rover() { let res = mock.invoke_callback( &user.clone(), - CallbackMsg::VaultForceWithdraw { + CallbackMsg::ForceExitVault { account_id, vault: VaultBase::new(Addr::unchecked(vault.address)), amount: STARTING_VAULT_SHARES, @@ -133,18 +133,16 @@ fn test_force_withdraw_can_only_be_called_by_rover() { #[test] fn test_force_withdraw_breaks_lock() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); - + let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_coins(&[lp_token.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + funds: vec![lp_token.to_coin(300)], }) .build() .unwrap(); @@ -156,14 +154,13 @@ fn test_force_withdraw_breaks_lock() { &account_id, &user, vec![ - Deposit(coin(200, uatom.denom)), - Deposit(coin(200, uosmo.denom)), - VaultDeposit { + Deposit(lp_token.to_coin(200)), + EnterVault { vault: vault.clone(), - coins: vec![coin(100, "uatom"), coin(100, "uosmo")], + coin: lp_token.to_coin(100), }, ], - &[coin(200, "uatom"), coin(200, "uosmo")], + &[lp_token.to_coin(200)], ) .unwrap(); @@ -175,7 +172,7 @@ fn test_force_withdraw_breaks_lock() { mock.invoke_callback( &mock.rover.clone(), - CallbackMsg::VaultForceWithdraw { + CallbackMsg::ForceExitVault { account_id: account_id.clone(), vault: VaultBase::new(Addr::unchecked(vault.address)), amount: STARTING_VAULT_SHARES, @@ -186,36 +183,30 @@ fn test_force_withdraw_breaks_lock() { // Assert token's updated position let res = mock.query_positions(&account_id); assert_eq!(res.vaults.len(), 0); - let atom = get_coin("uatom", &res.coins); - assert_eq!(atom.amount, Uint128::from(200u128)); - let osmo = get_coin("uosmo", &res.coins); - assert_eq!(osmo.amount, Uint128::from(200u128)); + let lp = get_coin(&lp_token.denom, &res.coins); + assert_eq!(lp.amount, Uint128::from(200u128)); // Assert Rover indeed has those on hand in the bank - let atom = mock.query_balance(&mock.rover, "uatom"); + let atom = mock.query_balance(&mock.rover, &lp_token.denom); assert_eq!(atom.amount, Uint128::from(200u128)); - let osmo = mock.query_balance(&mock.rover, "uosmo"); - assert_eq!(osmo.amount, Uint128::from(200u128)); // Assert Rover does not have the vault tokens anymore - let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.vault_token_denom); assert_eq!(Uint128::zero(), lp_balance.amount); } #[test] fn test_withdraw_with_unlocked_vault_coins() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); - + let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_coins(&[lp_token.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + funds: vec![lp_token.to_coin(300)], }) .build() .unwrap(); @@ -227,14 +218,13 @@ fn test_withdraw_with_unlocked_vault_coins() { &account_id, &user, vec![ - Deposit(coin(200, uatom.denom)), - Deposit(coin(200, uosmo.denom)), - VaultDeposit { + Deposit(lp_token.to_coin(200)), + EnterVault { vault: vault.clone(), - coins: vec![coin(100, "uatom"), coin(100, "uosmo")], + coin: lp_token.to_coin(100), }, ], - &[coin(200, "uatom"), coin(200, "uosmo")], + &[lp_token.to_coin(200)], ) .unwrap(); @@ -243,25 +233,21 @@ fn test_withdraw_with_unlocked_vault_coins() { assert_eq!(res.vaults.len(), 1); let v = res.vaults.first().unwrap(); assert_eq!(v.amount.unlocked(), STARTING_VAULT_SHARES); - let atom = get_coin("uatom", &res.coins); - assert_eq!(atom.amount, Uint128::from(100u128)); - let osmo = get_coin("uosmo", &res.coins); - assert_eq!(osmo.amount, Uint128::from(100u128)); + let lp = get_coin(&lp_token.denom, &res.coins); + assert_eq!(lp.amount, Uint128::from(100u128)); // Assert Rover's totals - let atom = mock.query_balance(&mock.rover, "uatom"); - assert_eq!(atom.amount, Uint128::from(100u128)); - let osmo = mock.query_balance(&mock.rover, "uosmo"); - assert_eq!(osmo.amount, Uint128::from(100u128)); + let lp = mock.query_balance(&mock.rover, &lp_token.denom); + assert_eq!(lp.amount, Uint128::from(100u128)); // Assert Rover has the vault tokens - let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.vault_token_denom); assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); mock.update_credit_account( &account_id, &user, - vec![VaultWithdraw { + vec![ExitVault { vault, amount: STARTING_VAULT_SHARES, }], @@ -272,19 +258,15 @@ fn test_withdraw_with_unlocked_vault_coins() { // Assert token's updated position let res = mock.query_positions(&account_id); assert_eq!(res.vaults.len(), 0); - let atom = get_coin("uatom", &res.coins); - assert_eq!(atom.amount, Uint128::from(200u128)); - let osmo = get_coin("uosmo", &res.coins); - assert_eq!(osmo.amount, Uint128::from(200u128)); + let lp = get_coin(&lp_token.denom, &res.coins); + assert_eq!(lp.amount, Uint128::from(200u128)); // Assert Rover indeed has those on hand in the bank - let atom = mock.query_balance(&mock.rover, "uatom"); - assert_eq!(atom.amount, Uint128::from(200u128)); - let osmo = mock.query_balance(&mock.rover, "uosmo"); - assert_eq!(osmo.amount, Uint128::from(200u128)); + let lp = mock.query_balance(&mock.rover, &lp_token.denom); + assert_eq!(lp.amount, Uint128::from(200u128)); // Assert Rover does not have the vault tokens anymore - let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.denom); + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.vault_token_denom); assert_eq!(Uint128::zero(), lp_balance.amount); } diff --git a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs new file mode 100644 index 000000000..657322a95 --- /dev/null +++ b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs @@ -0,0 +1,392 @@ +use cosmwasm_std::StdError::NotFound; +use cosmwasm_std::{Addr, Uint128}; +use cw_utils::Duration; + +use mock_vault::contract::STARTING_VAULT_SHARES; +use rover::adapters::vault::VaultUnchecked; +use rover::error::ContractError; +use rover::msg::execute::Action::{Deposit, EnterVault, ExitVaultUnlocked, RequestVaultUnlock}; +use rover::msg::query::Positions; + +use crate::helpers::{ + assert_err, generate_mock_vault, get_coin, locked_vault_info, lp_token_info, AccountToFund, + MockEnv, +}; + +pub mod helpers; + +#[test] +fn test_only_owner_can_withdraw_unlocked_for_account() { + let leverage_vault = locked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_vaults(&[leverage_vault.clone()]) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.update_credit_account( + &account_id, + &bad_guy, + vec![ExitVaultUnlocked { id: 423, vault }], + &[], + ); + + assert_err( + res, + ContractError::NotTokenOwner { + user: bad_guy.to_string(), + account_id, + }, + ); +} + +#[test] +fn test_can_only_take_action_on_whitelisted_vaults() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().build().unwrap(); + + let vault = VaultUnchecked::new("xvault".to_string()); + let account_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![ExitVaultUnlocked { id: 234, vault }], + &[], + ); + + assert_err(res, ContractError::NotWhitelisted("xvault".to_string())); +} + +#[test] +fn test_not_owner_of_unlocking_position() { + let lp_token = lp_token_info(); + let leverage_vault = locked_vault_info(); + + let user_a = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id_a = mock.create_credit_account(&user_a).unwrap(); + + mock.update_credit_account( + &account_id_a, + &user_a, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault: vault.clone(), + coin: lp_token.to_coin(23), + }, + RequestVaultUnlock { + vault: vault.clone(), + amount: STARTING_VAULT_SHARES, + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + let positions = mock.query_positions(&account_id_a); + assert_eq!(positions.vaults.len(), 1); + let lockup_id = get_lockup_id(&positions); + + let user_b = Addr::unchecked("user_b"); + let account_id_b = mock.create_credit_account(&user_b).unwrap(); + + let res = mock.update_credit_account( + &account_id_b, + &user_b, + vec![ExitVaultUnlocked { + id: lockup_id, + vault, + }], + &[], + ); + + assert_err( + res, + ContractError::Std(NotFound { kind: "rover::adapters::vault::amount::VaultPositionAmountBase".to_string() }), + ); +} + +#[test] +fn test_unlocking_position_not_ready_time() { + let lp_token = lp_token_info(); + let leverage_vault = locked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault: vault.clone(), + coin: lp_token.to_coin(23), + }, + RequestVaultUnlock { + vault: vault.clone(), + amount: STARTING_VAULT_SHARES, + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + let positions = mock.query_positions(&account_id); + let lockup_id = get_lockup_id(&positions); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![ExitVaultUnlocked { + id: lockup_id, + vault, + }], + &[], + ); + + assert_err(res, ContractError::UnlockNotReady); +} + +#[test] +fn test_unlocking_position_not_ready_blocks() { + let lp_token = lp_token_info(); + let leverage_vault = generate_mock_vault(Some(Duration::Height(100_000))); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault: vault.clone(), + coin: lp_token.to_coin(23), + }, + RequestVaultUnlock { + vault: vault.clone(), + amount: STARTING_VAULT_SHARES, + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + let positions = mock.query_positions(&account_id); + let lockup_id = get_lockup_id(&positions); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![ExitVaultUnlocked { + id: lockup_id, + vault, + }], + &[], + ); + + assert_err(res, ContractError::UnlockNotReady); +} + +#[test] +fn test_withdraw_unlock_success_time_expiring() { + let lp_token = lp_token_info(); + let leverage_vault = locked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault: vault.clone(), + coin: lp_token.to_coin(200), + }, + RequestVaultUnlock { + vault: vault.clone(), + amount: STARTING_VAULT_SHARES, + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + let Positions { coins, .. } = mock.query_positions(&account_id); + assert_eq!(coins.len(), 0); + + mock.app.update_block(|block| { + if let Duration::Time(s) = leverage_vault.lockup.unwrap() { + block.time = block.time.plus_seconds(s); + block.height += 1; + } + }); + + let positions = mock.query_positions(&account_id); + let lockup_id = get_lockup_id(&positions); + + mock.update_credit_account( + &account_id, + &user, + vec![ExitVaultUnlocked { + id: lockup_id, + vault, + }], + &[], + ) + .unwrap(); + + let Positions { vaults, coins, .. } = mock.query_positions(&account_id); + + // Users vault position decrements + assert_eq!(vaults.len(), 0); + + // Users asset position increments + let lp = get_coin(&lp_token.denom, &coins); + assert_eq!(lp.amount, Uint128::from(200u128)); + + // Assert Rover indeed has those on hand in the bank + let lp = mock.query_balance(&mock.rover, &lp_token.denom); + assert_eq!(lp.amount, Uint128::from(200u128)); +} + +#[test] +fn test_withdraw_unlock_success_block_expiring() { + let lp_token = lp_token_info(); + let leverage_vault = generate_mock_vault(Some(Duration::Height(100_000))); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault: vault.clone(), + coin: lp_token.to_coin(200), + }, + RequestVaultUnlock { + vault: vault.clone(), + amount: STARTING_VAULT_SHARES, + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + let Positions { coins, .. } = mock.query_positions(&account_id); + assert_eq!(coins.len(), 0); + + mock.app.update_block(|block| { + if let Duration::Height(h) = leverage_vault.lockup.unwrap() { + block.height += h; + block.time = block.time.plus_seconds(h * 6); + } + }); + + let positions = mock.query_positions(&account_id); + let lockup_id = get_lockup_id(&positions); + + mock.update_credit_account( + &account_id, + &user, + vec![ExitVaultUnlocked { + id: lockup_id, + vault, + }], + &[], + ) + .unwrap(); + + let Positions { vaults, coins, .. } = mock.query_positions(&account_id); + + // Users vault position decrements + assert_eq!(vaults.len(), 0); + + // Users asset position increments + let lp = get_coin(&lp_token.denom, &coins); + assert_eq!(lp.amount, Uint128::from(200u128)); + + // Assert Rover indeed has those on hand in the bank + let lp = mock.query_balance(&mock.rover, &lp_token.denom); + assert_eq!(lp.amount, Uint128::from(200u128)); +} + +fn get_lockup_id(positions: &Positions) -> u64 { + positions + .vaults + .first() + .unwrap() + .amount + .unlocking() + .first() + .unwrap() + .id +} diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs index e7f48bf4d..d160a4199 100644 --- a/contracts/credit-manager/tests/test_vault_request_unlock.rs +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -1,15 +1,15 @@ use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{coin, coins, Addr, OverflowError, Uint128}; +use cosmwasm_std::{coins, Addr, OverflowError, Uint128}; use cw_multi_test::{BankSudo, SudoMsg}; +use cw_utils::{Duration, Expiration}; use mock_vault::contract::STARTING_VAULT_SHARES; use rover::adapters::vault::VaultUnchecked; use rover::error::ContractError; -use rover::msg::execute::Action::{Deposit, VaultDeposit, VaultRequestUnlock}; +use rover::msg::execute::Action::{Deposit, EnterVault, RequestVaultUnlock}; use crate::helpers::{ - assert_err, locked_vault_info, uatom_info, unlocked_vault_info, uosmo_info, AccountToFund, - MockEnv, + assert_err, locked_vault_info, lp_token_info, unlocked_vault_info, AccountToFund, MockEnv, }; pub mod helpers; @@ -31,7 +31,7 @@ fn test_only_owner_can_request_unlocked() { let res = mock.update_credit_account( &account_id, &bad_guy, - vec![VaultRequestUnlock { + vec![RequestVaultUnlock { vault, amount: STARTING_VAULT_SHARES, }], @@ -58,7 +58,7 @@ fn test_can_only_take_action_on_whitelisted_vaults() { let res = mock.update_credit_account( &account_id, &user, - vec![VaultRequestUnlock { + vec![RequestVaultUnlock { vault, amount: STARTING_VAULT_SHARES, }], @@ -84,7 +84,7 @@ fn test_request_when_unnecessary() { let res = mock.update_credit_account( &account_id, &user, - vec![VaultRequestUnlock { + vec![RequestVaultUnlock { vault, amount: STARTING_VAULT_SHARES, }], @@ -116,14 +116,14 @@ fn test_no_funds_for_request() { mock.app .sudo(SudoMsg::Bank(BankSudo::Mint { to_address: mock.rover.clone().to_string(), - amount: coins(5_000_000, leverage_vault.denom), + amount: coins(5_000_000, leverage_vault.vault_token_denom), })) .unwrap(); let res = mock.update_credit_account( &account_id, &user, - vec![VaultRequestUnlock { + vec![RequestVaultUnlock { vault, amount: STARTING_VAULT_SHARES, }], @@ -142,18 +142,16 @@ fn test_no_funds_for_request() { #[test] fn test_not_enough_funds_for_request() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); - + let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_coins(&[lp_token.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + funds: vec![lp_token.to_coin(300)], }) .build() .unwrap(); @@ -165,7 +163,7 @@ fn test_not_enough_funds_for_request() { mock.app .sudo(SudoMsg::Bank(BankSudo::Mint { to_address: mock.rover.clone().to_string(), - amount: coins(5_000_000, leverage_vault.denom), + amount: coins(5_000_000, leverage_vault.vault_token_denom), })) .unwrap(); @@ -173,18 +171,17 @@ fn test_not_enough_funds_for_request() { &account_id, &user, vec![ - Deposit(coin(200, uatom.denom)), - Deposit(coin(400, uosmo.denom)), - VaultDeposit { + Deposit(lp_token.to_coin(200)), + EnterVault { vault: vault.clone(), - coins: vec![coin(23, "uatom"), coin(120, "uosmo")], + coin: lp_token.to_coin(23), }, - VaultRequestUnlock { + RequestVaultUnlock { vault, amount: STARTING_VAULT_SHARES + Uint128::new(100), }, ], - &[coin(200, "uatom"), coin(400, "uosmo")], + &[lp_token.to_coin(200)], ); assert_err( @@ -199,18 +196,16 @@ fn test_not_enough_funds_for_request() { #[test] fn test_request_unlocked() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); - + let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .allowed_coins(&[lp_token.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![coin(300, "uatom"), coin(500, "uosmo")], + funds: vec![lp_token.to_coin(200)], }) .build() .unwrap(); @@ -222,18 +217,17 @@ fn test_request_unlocked() { &account_id, &user, vec![ - Deposit(coin(200, uatom.denom)), - Deposit(coin(400, uosmo.denom)), - VaultDeposit { + Deposit(lp_token.to_coin(200)), + EnterVault { vault: vault.clone(), - coins: vec![coin(23, "uatom"), coin(120, "uosmo")], + coin: lp_token.to_coin(23), }, - VaultRequestUnlock { + RequestVaultUnlock { vault: vault.clone(), amount: STARTING_VAULT_SHARES, }, ], - &[coin(200, "uatom"), coin(400, "uosmo")], + &[lp_token.to_coin(200)], ) .unwrap(); @@ -244,20 +238,31 @@ fn test_request_unlocked() { assert_eq!(unlocking.len(), 1); let first = unlocking.first().unwrap(); assert_eq!(first.amount, STARTING_VAULT_SHARES); - let expected_unlock_time = - mock.app.block_info().time.seconds() + leverage_vault.lockup.unwrap(); - let unlocking_position = mock.query_unlocking_position_info(&vault, first.id); - assert_eq!( - unlocking_position.unlocked_at.seconds(), - expected_unlock_time - ); - // Assert Rover's position w/ Vault - let res = mock.query_unlocking_positions(&vault, &mock.rover); - assert_eq!(res.len(), 1); - assert_eq!(res.first().unwrap().amount, STARTING_VAULT_SHARES); - assert_eq!( - res.first().unwrap().unlocked_at.seconds(), - expected_unlock_time - ); + match leverage_vault.lockup.unwrap() { + Duration::Height(_) => panic!("wrong type of duration"), + Duration::Time(s) => { + let expected_unlock_time = mock.app.block_info().time.seconds() + s; + let unlocking_position = mock.query_lockup(&vault, first.id); + + match unlocking_position.release_at { + Expiration::AtTime(t) => { + assert_eq!(t.seconds(), expected_unlock_time); + } + _ => panic!("Wrong type of expiration"), + } + + // Assert Rover's position w/ Vault + let res = mock.query_lockups(&vault, &mock.rover); + + match res.first().unwrap().release_at { + Expiration::AtTime(t) => { + assert_eq!(res.len(), 1); + assert_eq!(res.first().unwrap().coin.amount, STARTING_VAULT_SHARES); + assert_eq!(t.seconds(), expected_unlock_time); + } + _ => panic!("Wrong type of expiration"), + } + } + } } diff --git a/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs b/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs deleted file mode 100644 index 33db9a31e..000000000 --- a/contracts/credit-manager/tests/test_vault_withdraw_unlocked.rs +++ /dev/null @@ -1,284 +0,0 @@ -use cosmwasm_std::StdError::NotFound; -use cosmwasm_std::{coin, Addr, Uint128}; - -use mock_vault::contract::STARTING_VAULT_SHARES; -use rover::adapters::vault::VaultUnchecked; -use rover::error::ContractError; -use rover::msg::execute::Action::{ - Deposit, VaultDeposit, VaultRequestUnlock, VaultWithdrawUnlocked, -}; -use rover::msg::query::Positions; - -use crate::helpers::{ - assert_err, get_coin, locked_vault_info, uatom_info, uosmo_info, AccountToFund, MockEnv, -}; - -pub mod helpers; - -#[test] -fn test_only_owner_can_withdraw_unlocked_for_account() { - let leverage_vault = locked_vault_info(); - - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_vaults(&[leverage_vault.clone()]) - .build() - .unwrap(); - - let vault = mock.get_vault(&leverage_vault); - let account_id = mock.create_credit_account(&user).unwrap(); - - let bad_guy = Addr::unchecked("bad_guy"); - let res = mock.update_credit_account( - &account_id, - &bad_guy, - vec![VaultWithdrawUnlocked { id: 423, vault }], - &[], - ); - - assert_err( - res, - ContractError::NotTokenOwner { - user: bad_guy.to_string(), - account_id, - }, - ); -} - -#[test] -fn test_can_only_take_action_on_whitelisted_vaults() { - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().build().unwrap(); - - let vault = VaultUnchecked::new("xvault".to_string()); - let account_id = mock.create_credit_account(&user).unwrap(); - - let res = mock.update_credit_account( - &account_id, - &user, - vec![VaultWithdrawUnlocked { id: 234, vault }], - &[], - ); - - assert_err(res, ContractError::NotWhitelisted("xvault".to_string())); -} - -#[test] -fn test_not_owner_of_unlocking_position() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); - let leverage_vault = locked_vault_info(); - - let user_a = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) - .fund_account(AccountToFund { - addr: user_a.clone(), - funds: vec![coin(300, "uatom"), coin(500, "uosmo")], - }) - .build() - .unwrap(); - - let vault = mock.get_vault(&leverage_vault); - let account_id_a = mock.create_credit_account(&user_a).unwrap(); - - mock.update_credit_account( - &account_id_a, - &user_a, - vec![ - Deposit(coin(200, uatom.denom)), - Deposit(coin(400, uosmo.denom)), - VaultDeposit { - vault: vault.clone(), - coins: vec![coin(23, "uatom"), coin(120, "uosmo")], - }, - VaultRequestUnlock { - vault: vault.clone(), - amount: STARTING_VAULT_SHARES, - }, - ], - &[coin(200, "uatom"), coin(400, "uosmo")], - ) - .unwrap(); - - let res = mock.query_positions(&account_id_a); - assert_eq!(res.vaults.len(), 1); - let unlocking_id = res - .vaults - .first() - .unwrap() - .amount - .unlocking() - .first() - .unwrap() - .id; - - let user_b = Addr::unchecked("user_b"); - let account_id_b = mock.create_credit_account(&user_b).unwrap(); - - let res = mock.update_credit_account( - &account_id_b, - &user_b, - vec![VaultWithdrawUnlocked { - id: unlocking_id, - vault, - }], - &[], - ); - - assert_err( - res, - ContractError::Std(NotFound { kind: "rover::adapters::vault::amount::VaultPositionAmountBase".to_string() }), - ); -} - -#[test] -fn test_unlocking_position_not_ready() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); - let leverage_vault = locked_vault_info(); - - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) - .fund_account(AccountToFund { - addr: user.clone(), - funds: vec![coin(300, "uatom"), coin(500, "uosmo")], - }) - .build() - .unwrap(); - - let vault = mock.get_vault(&leverage_vault); - let account_id = mock.create_credit_account(&user).unwrap(); - - mock.update_credit_account( - &account_id, - &user, - vec![ - Deposit(coin(200, uatom.denom)), - Deposit(coin(400, uosmo.denom)), - VaultDeposit { - vault: vault.clone(), - coins: vec![coin(23, "uatom"), coin(120, "uosmo")], - }, - VaultRequestUnlock { - vault: vault.clone(), - amount: STARTING_VAULT_SHARES, - }, - ], - &[coin(200, "uatom"), coin(400, "uosmo")], - ) - .unwrap(); - - let Positions { vaults, .. } = mock.query_positions(&account_id); - - let position_id = vaults - .first() - .unwrap() - .amount - .unlocking() - .first() - .unwrap() - .id; - - let res = mock.update_credit_account( - &account_id, - &user, - vec![VaultWithdrawUnlocked { - id: position_id, - vault, - }], - &[], - ); - - assert_err(res, ContractError::UnlockNotReady); -} - -#[test] -fn test_withdraw_unlock_success() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); - let leverage_vault = locked_vault_info(); - - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) - .fund_account(AccountToFund { - addr: user.clone(), - funds: vec![coin(300, "uatom"), coin(500, "uosmo")], - }) - .build() - .unwrap(); - - let vault = mock.get_vault(&leverage_vault); - let account_id = mock.create_credit_account(&user).unwrap(); - - mock.update_credit_account( - &account_id, - &user, - vec![ - Deposit(coin(200, uatom.denom)), - Deposit(coin(400, uosmo.denom)), - VaultDeposit { - vault: vault.clone(), - coins: vec![coin(200, "uatom"), coin(400, "uosmo")], - }, - VaultRequestUnlock { - vault: vault.clone(), - amount: STARTING_VAULT_SHARES, - }, - ], - &[coin(200, "uatom"), coin(400, "uosmo")], - ) - .unwrap(); - - let Positions { coins, .. } = mock.query_positions(&account_id); - assert_eq!(coins.len(), 0); - - mock.app.update_block(|block| { - block.time = block.time.plus_seconds(leverage_vault.lockup.unwrap()); - block.height += 1; - }); - - let Positions { vaults, .. } = mock.query_positions(&account_id); - - let position_id = vaults - .first() - .unwrap() - .amount - .unlocking() - .first() - .unwrap() - .id; - - mock.update_credit_account( - &account_id, - &user, - vec![VaultWithdrawUnlocked { - id: position_id, - vault, - }], - &[], - ) - .unwrap(); - - let Positions { vaults, coins, .. } = mock.query_positions(&account_id); - - // Users vault position decrements - assert_eq!(vaults.len(), 0); - - // Users asset position increments - let atom = get_coin("uatom", &coins); - assert_eq!(atom.amount, Uint128::from(200u128)); - let osmo = get_coin("uosmo", &coins); - assert_eq!(osmo.amount, Uint128::from(400u128)); - - // Assert Rover indeed has those on hand in the bank - let atom = mock.query_balance(&mock.rover, "uatom"); - assert_eq!(atom.amount, Uint128::from(200u128)); - let osmo = mock.query_balance(&mock.rover, "uosmo"); - assert_eq!(osmo.amount, Uint128::from(400u128)); -} diff --git a/contracts/mars-oracle-adapter/Cargo.toml b/contracts/mars-oracle-adapter/Cargo.toml index 69757b71a..01661fa6b 100644 --- a/contracts/mars-oracle-adapter/Cargo.toml +++ b/contracts/mars-oracle-adapter/Cargo.toml @@ -28,3 +28,4 @@ mock-vault = { version = "1.0", path = "../../contracts/mock-vault", features = anyhow = "1" cw-multi-test = "0.16" +cw-utils = "0.16" diff --git a/contracts/mars-oracle-adapter/src/contract.rs b/contracts/mars-oracle-adapter/src/contract.rs index 424eca5f1..61e4a6167 100644 --- a/contracts/mars-oracle-adapter/src/contract.rs +++ b/contracts/mars-oracle-adapter/src/contract.rs @@ -35,7 +35,7 @@ pub fn instantiate( ORACLE.save(deps.storage, &oracle)?; for info in msg.vault_pricing { - VAULT_PRICING_INFO.save(deps.storage, &info.denom, &info)?; + VAULT_PRICING_INFO.save(deps.storage, &info.vault_coin_denom, &info)?; } Ok(Response::default()) @@ -100,7 +100,8 @@ fn query_priceable_underlying(deps: Deps, coin: Coin) -> ContractResult match info.method { PricingMethod::PreviewRedeem => { let vault = VaultBase::new(info.addr); - Ok(vault.query_preview_redeem(&deps.querier, coin.amount)?) + let res = vault.query_preview_redeem(&deps.querier, coin.amount)?; + Ok(vec![res.coin]) } }, _ => Ok(vec![coin]), @@ -129,25 +130,20 @@ fn calculate_preview_redeem( vault: &VaultBase, ) -> ContractResult { let total_issued = vault.query_total_vault_coins_issued(&deps.querier)?; - let coins = vault.query_preview_redeem(&deps.querier, total_issued)?; - let total_value = - coins - .iter() - .try_fold(Decimal::zero(), |total, coin| -> ContractResult<_> { - let res = oracle.query_price(&deps.querier, &coin.denom)?; - let value = res.price.checked_mul(coin.amount.to_dec()?)?; - let new_total = total.checked_add(value)?; - Ok(new_total) - })?; - - let price = if total_value.is_zero() { + let assets_res = vault.query_preview_redeem(&deps.querier, total_issued)?; + let price_res = oracle.query_price(&deps.querier, &assets_res.coin.denom)?; + let value = price_res + .price + .checked_mul(assets_res.coin.amount.to_dec()?)?; + + let price = if value.is_zero() || total_issued.is_zero() { Decimal::zero() } else { - total_value.checked_div(total_issued.to_dec()?)? + value.checked_div(total_issued.to_dec()?)? }; Ok(PriceResponse { - denom: info.denom.clone(), + denom: info.vault_coin_denom.clone(), price, }) } @@ -194,14 +190,14 @@ pub fn update_config( if let Some(vault_pricing) = new_config.vault_pricing { VAULT_PRICING_INFO.clear(deps.storage); for info in &vault_pricing { - VAULT_PRICING_INFO.save(deps.storage, &info.denom, info)?; + VAULT_PRICING_INFO.save(deps.storage, &info.vault_coin_denom, info)?; } let value_str = if vault_pricing.is_empty() { "None".to_string() } else { vault_pricing .into_iter() - .map(|info| info.denom) + .map(|info| info.vault_coin_denom) .collect::>() .join(", ") }; diff --git a/contracts/mars-oracle-adapter/src/msg.rs b/contracts/mars-oracle-adapter/src/msg.rs index 890a9c4cc..8ecb456e6 100644 --- a/contracts/mars-oracle-adapter/src/msg.rs +++ b/contracts/mars-oracle-adapter/src/msg.rs @@ -60,7 +60,8 @@ pub struct ConfigUpdates { #[cw_serde] pub struct VaultPricingInfo { - pub denom: String, + pub vault_coin_denom: String, + pub req_denom: String, pub addr: Addr, pub method: PricingMethod, } diff --git a/contracts/mars-oracle-adapter/tests/helpers.rs b/contracts/mars-oracle-adapter/tests/helpers.rs index 77a214047..47f9fcb79 100644 --- a/contracts/mars-oracle-adapter/tests/helpers.rs +++ b/contracts/mars-oracle-adapter/tests/helpers.rs @@ -2,7 +2,7 @@ use anyhow::Result as AnyResult; use cosmwasm_schema::cw_serde; use cosmwasm_std::{coin, Addr, Coin, Decimal}; use cw_multi_test::{AppResponse, BankSudo, BasicApp, ContractWrapper, Executor, SudoMsg}; - +use cw_utils::Duration; use mars_oracle_adapter::contract::{execute, instantiate, query}; use mars_oracle_adapter::error::ContractError; use mars_oracle_adapter::msg::{InstantiateMsg, PricingMethod, VaultPricingInfo}; @@ -22,7 +22,7 @@ pub fn mock_vault_info() -> VaultTestInfo { VaultTestInfo { vault_coin_denom: "yOSMO_ATOM".to_string(), lockup: None, - underlying_assets: vec!["uosmo".to_string(), "uatom".to_string()], + req_denom: "GAMM_LP_12352".to_string(), pricing_method: PricingMethod::PreviewRedeem, } } @@ -54,8 +54,8 @@ pub fn instantiate_oracle_adapter(app: &mut BasicApp) -> Addr { #[cw_serde] pub struct VaultTestInfo { pub vault_coin_denom: String, - pub lockup: Option, - pub underlying_assets: Vec, + pub lockup: Option, + pub req_denom: String, pub pricing_method: PricingMethod, } @@ -72,9 +72,9 @@ fn deploy_vault( code_id, Addr::unchecked("vault-instantiator"), &VaultInstantiateMsg { - lp_token_denom: vault.clone().vault_coin_denom, + vault_token_denom: vault.clone().vault_coin_denom, lockup: vault.lockup, - asset_denoms: vault.clone().underlying_assets, + req_denom: vault.clone().req_denom, oracle, }, &[], @@ -84,9 +84,10 @@ fn deploy_vault( .unwrap(); let vault_pricing_info = VaultPricingInfo { - denom: vault.vault_coin_denom, + vault_coin_denom: vault.vault_coin_denom, addr, method: vault.pricing_method, + req_denom: vault.req_denom, }; fund_vault(app, &vault_pricing_info); vault_pricing_info @@ -98,7 +99,7 @@ fn fund_vault(app: &mut BasicApp, vault: &VaultPricingInfo) { app.sudo(SudoMsg::Bank(BankSudo::Mint { to_address: vault.addr.to_string(), amount: vec![Coin { - denom: vault.denom.clone(), + denom: vault.vault_coin_denom.clone(), amount: DEFAULT_VAULT_TOKEN_PREFUND, }], })) @@ -107,15 +108,15 @@ fn fund_vault(app: &mut BasicApp, vault: &VaultPricingInfo) { fn starting_vault_deposit(app: &mut BasicApp, vault_info: &VaultPricingInfo) { let user = Addr::unchecked("some_user_xyz"); - let coins_to_deposit = vec![coin(120_042, "uosmo"), coin(32_343, "uatom")]; + let coin_to_deposit = coin(120_042, "GAMM_LP_12352"); app.sudo(SudoMsg::Bank(BankSudo::Mint { to_address: user.to_string(), - amount: coins_to_deposit.clone(), + amount: vec![coin_to_deposit.clone()], })) .unwrap(); let vault = VaultBase::new(vault_info.addr.clone()); - let deposit_msg = vault.deposit_msg(&coins_to_deposit).unwrap(); + let deposit_msg = vault.deposit_msg(&coin_to_deposit).unwrap(); app.execute(user, deposit_msg).unwrap(); } @@ -137,6 +138,10 @@ fn deploy_oracle(app: &mut BasicApp) -> OracleBase { denom: "uatom".to_string(), price: Decimal::from_atomics(10u128, 1).unwrap(), }, + CoinPrice { + denom: "GAMM_LP_12352".to_string(), + price: Decimal::from_atomics(8745u128, 2).unwrap(), + }, ], }, &[], diff --git a/contracts/mars-oracle-adapter/tests/test_query_price.rs b/contracts/mars-oracle-adapter/tests/test_query_price.rs index dec319b1c..9c7627485 100644 --- a/contracts/mars-oracle-adapter/tests/test_query_price.rs +++ b/contracts/mars-oracle-adapter/tests/test_query_price.rs @@ -1,4 +1,4 @@ -use std::ops::{Add, Div, Mul}; +use std::ops::{Div, Mul}; use cosmwasm_std::{Decimal, Uint128}; use cw_multi_test::App; @@ -46,17 +46,11 @@ fn test_vault_coin_preview_redeem() { ) .unwrap(); - let uatom_price = Decimal::from_atomics(10u128, 1).unwrap(); - let atom_in_vault = Uint128::new(32_343); - let vaults_atom_value = atom_in_vault.to_dec().unwrap().mul(uatom_price); + let lp_token_price = Decimal::from_atomics(8745u128, 2).unwrap(); + let lp_token_in_vault = Uint128::new(120_042); + let vaults_value = lp_token_in_vault.to_dec().unwrap().mul(lp_token_price); - let uosmo_price = Decimal::from_atomics(25u128, 2).unwrap(); - let osmo_in_vault = Uint128::new(120_042); - let vaults_osmo_value = osmo_in_vault.to_dec().unwrap().mul(uosmo_price); - - let price_per_vault_coin = vaults_atom_value - .add(vaults_osmo_value) - .div(STARTING_VAULT_SHARES); + let price_per_vault_coin = vaults_value.div(STARTING_VAULT_SHARES); assert_eq!(res.price, price_per_vault_coin) } diff --git a/contracts/mars-oracle-adapter/tests/test_query_priceable_underlying.rs b/contracts/mars-oracle-adapter/tests/test_query_priceable_underlying.rs index fc890872b..ca01e3872 100644 --- a/contracts/mars-oracle-adapter/tests/test_query_priceable_underlying.rs +++ b/contracts/mars-oracle-adapter/tests/test_query_priceable_underlying.rs @@ -43,9 +43,7 @@ fn test_vault_coin_preview_redeem() { ) .unwrap(); - assert_eq!(coins.len(), 2); - assert_eq!(coins[0].denom, "uatom".to_string()); - assert_eq!(coins[0].amount, Uint128::new(32)); - assert_eq!(coins[1].denom, "uosmo".to_string()); - assert_eq!(coins[1].amount, Uint128::new(120)); + assert_eq!(coins.len(), 1); + assert_eq!(coins[0].denom, "GAMM_LP_12352".to_string()); + assert_eq!(coins[0].amount, Uint128::new(120)); } diff --git a/contracts/mock-vault/Cargo.toml b/contracts/mock-vault/Cargo.toml index 9c58dfa87..26c243c9a 100644 --- a/contracts/mock-vault/Cargo.toml +++ b/contracts/mock-vault/Cargo.toml @@ -15,8 +15,10 @@ library = [] [dependencies] rover = { version = "1.0", path = "../../packages/rover" } +cosmos-vault-standard = { path = "../../packages/cosmos-vault-standard", features = ["lockup"] } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" cw-storage-plus = "0.16" +cw-utils = "0.16" thiserror = "1.0" diff --git a/contracts/mock-vault/examples/schema.rs b/contracts/mock-vault/examples/schema.rs index 5a25723b9..44d726d9e 100644 --- a/contracts/mock-vault/examples/schema.rs +++ b/contracts/mock-vault/examples/schema.rs @@ -1,6 +1,6 @@ +use cosmos_vault_standard::msg::{ExecuteMsg, QueryMsg}; use cosmwasm_schema::write_api; use mock_vault::msg::InstantiateMsg; -use rover::msg::vault::{ExecuteMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index d5269dfb2..937a999de 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -1,19 +1,20 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, -}; +use cosmwasm_std::{coin, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; -use rover::msg::vault::{ExecuteMsg, QueryMsg}; +use cosmos_vault_standard::extensions::lockup::{LockupExecuteMsg, LockupQueryMsg}; +use cosmos_vault_standard::msg::{ExecuteMsg, ExtensionExecuteMsg, ExtensionQueryMsg, QueryMsg}; use crate::deposit::deposit; -use crate::error::ContractError; +use crate::error::ContractResult; use crate::msg::InstantiateMsg; use crate::query::{ - query_coins_for_shares, query_unlocking_position, query_unlocking_positions, - query_vault_coins_issued, query_vault_info, + query_coins_for_shares, query_lockup, query_lockup_duration, query_lockups, query_vault_info, + query_vault_token_supply, +}; +use crate::state::{ + CHAIN_BANK, COIN_BALANCE, LOCKUP_TIME, NEXT_LOCKUP_ID, ORACLE, VAULT_TOKEN_DENOM, }; -use crate::state::{ASSETS, CHAIN_BANK, LOCKUP_TIME, LP_TOKEN_DENOM, NEXT_UNLOCK_ID, ORACLE}; use crate::unlock::{request_unlock, withdraw_unlocked, withdraw_unlocking_force}; use crate::withdraw::{withdraw, withdraw_force}; @@ -29,15 +30,13 @@ pub fn instantiate( _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> StdResult { - for denom in msg.asset_denoms { - ASSETS.save(deps.storage, denom, &Uint128::zero())?; - } +) -> ContractResult { + COIN_BALANCE.save(deps.storage, &coin(0, msg.req_denom))?; LOCKUP_TIME.save(deps.storage, &msg.lockup)?; ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; - LP_TOKEN_DENOM.save(deps.storage, &msg.lp_token_denom)?; + VAULT_TOKEN_DENOM.save(deps.storage, &msg.vault_token_denom)?; CHAIN_BANK.save(deps.storage, &DEFAULT_VAULT_TOKEN_PREFUND)?; - NEXT_UNLOCK_ID.save(deps.storage, &1)?; + NEXT_LOCKUP_ID.save(deps.storage, &1)?; Ok(Response::default()) } @@ -47,30 +46,43 @@ pub fn execute( env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result { +) -> ContractResult { match msg { - ExecuteMsg::Deposit {} => deposit(deps, info), - ExecuteMsg::Withdraw {} => withdraw(deps, info), - ExecuteMsg::ForceWithdraw {} => withdraw_force(deps, info), - ExecuteMsg::ForceWithdrawUnlocking { lockup_id, amount } => { - withdraw_unlocking_force(deps, &info.sender, lockup_id, amount) - } - ExecuteMsg::RequestUnlock {} => request_unlock(deps, env, info), - ExecuteMsg::WithdrawUnlocked { id } => withdraw_unlocked(deps, env, &info.sender, id), + ExecuteMsg::Deposit { .. } => deposit(deps, info), + ExecuteMsg::Redeem { .. } => withdraw(deps, info), + ExecuteMsg::VaultExtension(ext) => match ext { + ExtensionExecuteMsg::Lockup(lockup_msg) => match lockup_msg { + LockupExecuteMsg::WithdrawUnlocked { lockup_id, .. } => { + withdraw_unlocked(deps, env, &info.sender, lockup_id) + } + LockupExecuteMsg::Unlock { .. } => request_unlock(deps, env, info), + LockupExecuteMsg::ForceWithdraw { .. } => withdraw_force(deps, info), + LockupExecuteMsg::ForceWithdrawUnlocking { + lockup_id, amount, .. + } => withdraw_unlocking_force(deps, &info.sender, lockup_id, amount), + }, + }, } } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> ContractResult { + let res = match msg { + QueryMsg::TotalVaultTokenSupply {} => to_binary(&query_vault_token_supply(deps.storage)?), QueryMsg::Info {} => to_binary(&query_vault_info(deps)?), QueryMsg::PreviewRedeem { amount } => { to_binary(&query_coins_for_shares(deps.storage, amount)?) } - QueryMsg::TotalVaultCoinsIssued {} => to_binary(&query_vault_coins_issued(deps.storage)?), - QueryMsg::UnlockingPositionsForAddr { addr } => { - to_binary(&query_unlocking_positions(deps, addr)?) - } - QueryMsg::UnlockingPosition { id } => to_binary(&query_unlocking_position(deps, id)?), - } + QueryMsg::VaultExtension(ext) => match ext { + ExtensionQueryMsg::Lockup(lockup_msg) => match lockup_msg { + LockupQueryMsg::Lockups { owner, .. } => to_binary(&query_lockups(deps, owner)?), + LockupQueryMsg::Lockup { lockup_id, .. } => { + to_binary(&query_lockup(deps, lockup_id)?) + } + LockupQueryMsg::LockupDuration {} => to_binary(&query_lockup_duration(deps)?), + }, + }, + _ => unimplemented!(), + }; + res.map_err(Into::into) } diff --git a/contracts/mock-vault/src/deposit.rs b/contracts/mock-vault/src/deposit.rs index 415ec510b..0526d5c48 100644 --- a/contracts/mock-vault/src/deposit.rs +++ b/contracts/mock-vault/src/deposit.rs @@ -2,13 +2,13 @@ use cosmwasm_std::{BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, Std use crate::contract::STARTING_VAULT_SHARES; use crate::error::ContractError; -use crate::query::get_all_vault_coins; -use crate::state::{ASSETS, CHAIN_BANK, LP_TOKEN_DENOM, ORACLE, TOTAL_VAULT_SHARES}; +use crate::error::ContractError::WrongDenomSent; +use crate::state::{CHAIN_BANK, COIN_BALANCE, ORACLE, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}; pub fn deposit(deps: DepsMut, info: MessageInfo) -> Result { let total_shares_opt = TOTAL_VAULT_SHARES.may_load(deps.storage)?; let oracle = ORACLE.load(deps.storage)?; - let all_vault_assets = get_all_vault_coins(deps.storage)?; + let balance = COIN_BALANCE.load(deps.storage)?; let shares_to_add = match total_shares_opt { None => { @@ -16,7 +16,7 @@ pub fn deposit(deps: DepsMut, info: MessageInfo) -> Result { - let total_vault_value = oracle.query_total_value(&deps.querier, &all_vault_assets)?; + let total_vault_value = oracle.query_total_value(&deps.querier, &[balance])?; let assets_value = oracle.query_total_value(&deps.querier, &info.funds)?; let shares_to_add = total_shares .checked_multiply_ratio(assets_value.atomics(), total_vault_value.atomics())?; @@ -25,18 +25,20 @@ pub fn deposit(deps: DepsMut, info: MessageInfo) -> Result StdResult<()> { - ASSETS.update( - deps.storage, - asset.clone().denom, - |current_amount| -> StdResult<_> { - Ok(current_amount.unwrap_or(Uint128::zero()) + asset.amount) - }, - )?; - Ok(()) - })?; - - // Send vault tokens to + let balance = COIN_BALANCE.load(deps.storage)?; + let amount_deposited = match info.funds.first() { + Some(c) if c.denom == balance.denom => c.amount, + _ => return Err(WrongDenomSent), + }; + COIN_BALANCE.save( + deps.storage, + &Coin { + denom: balance.denom, + amount: balance.amount + amount_deposited, + }, + )?; + + // Send vault tokens to user let minted = mock_lp_token_mint(deps, shares_to_add)?; let transfer_msg = CosmosMsg::Bank(BankMsg::Send { to_address: info.sender.to_string(), @@ -47,7 +49,7 @@ pub fn deposit(deps: DepsMut, info: MessageInfo) -> Result StdResult { - let denom = LP_TOKEN_DENOM.load(deps.storage)?; + let denom = VAULT_TOKEN_DENOM.load(deps.storage)?; CHAIN_BANK.update(deps.storage, |bank_amount| -> StdResult<_> { Ok(bank_amount - amount) diff --git a/contracts/mock-vault/src/error.rs b/contracts/mock-vault/src/error.rs index a4ea33cbb..842dcd98b 100644 --- a/contracts/mock-vault/src/error.rs +++ b/contracts/mock-vault/src/error.rs @@ -2,6 +2,8 @@ use cosmwasm_std::{CheckedMultiplyRatioError, StdError}; use rover::error::ContractError as RoverError; use thiserror::Error; +pub type ContractResult = Result; + #[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] @@ -13,18 +15,21 @@ pub enum ContractError { #[error("{0}")] CheckedMultiply(#[from] CheckedMultiplyRatioError), - #[error("This vault does not require a lockup, just withdraw directly")] - NoLockupTime, - #[error("Lockup position {0} not found")] LockupPositionNotFound(u64), + #[error("This vault is not a locking vault, action not allowed")] + NotLockingVault, + #[error("There is more time left on the lock period")] UnlockNotReady, #[error("You must request an unlock first")] UnlockRequired, + #[error("Attempting to deposit incorrect denom")] + WrongDenomSent, + #[error("Vault token not sent")] VaultTokenNotSent, } diff --git a/contracts/mock-vault/src/msg.rs b/contracts/mock-vault/src/msg.rs index 24485c76d..2b516efa9 100644 --- a/contracts/mock-vault/src/msg.rs +++ b/contracts/mock-vault/src/msg.rs @@ -1,15 +1,15 @@ use cosmwasm_schema::cw_serde; - +use cw_utils::Duration; use rover::adapters::OracleUnchecked; -// Remaining messages in packages/rover/msg/vault +// Remaining messages in cosmos-vault-standard #[cw_serde] pub struct InstantiateMsg { - /// Denom for vault LP share token - pub lp_token_denom: String, - /// Denoms for assets in vault - pub asset_denoms: Vec, - /// Time in seconds for unlock period - pub lockup: Option, + /// Denom for vault token + pub vault_token_denom: String, + /// Denom required for entry. Also denom received on withdraw. + pub req_denom: String, + /// Duration of unlock period + pub lockup: Option, pub oracle: OracleUnchecked, } diff --git a/contracts/mock-vault/src/query.rs b/contracts/mock-vault/src/query.rs index 81e7da03c..49f290c8c 100644 --- a/contracts/mock-vault/src/query.rs +++ b/contracts/mock-vault/src/query.rs @@ -1,64 +1,61 @@ use cosmwasm_std::{Coin, Deps, Order, StdError, StdResult, Storage, Uint128}; +use cw_utils::Duration; -use rover::msg::vault::{UnlockingPosition, VaultInfo}; +use cosmos_vault_standard::extensions::lockup::Lockup; +use cosmos_vault_standard::msg::{AssetsResponse, VaultInfo}; -use crate::state::{ASSETS, LOCKUP_TIME, LP_TOKEN_DENOM, TOTAL_VAULT_SHARES, UNLOCKING_COINS}; +use crate::error::ContractError::NotLockingVault; +use crate::error::ContractResult; +use crate::state::{COIN_BALANCE, LOCKUPS, LOCKUP_TIME, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}; -pub fn query_coins_for_shares(storage: &dyn Storage, shares: Uint128) -> StdResult> { +pub fn query_coins_for_shares( + storage: &dyn Storage, + shares: Uint128, +) -> ContractResult { let total_shares_opt = TOTAL_VAULT_SHARES.may_load(storage)?; + let balance = COIN_BALANCE.load(storage)?; match total_shares_opt { - None => Ok(vec![]), - Some(total_vault_shares) => { - let all_vault_coins = get_all_vault_coins(storage)?; - let coins_for_shares = all_vault_coins - .iter() - .map(|asset| Coin { - denom: asset.clone().denom, - amount: asset.amount.multiply_ratio(shares, total_vault_shares), - }) - .collect::>(); - Ok(coins_for_shares) - } + Some(total_vault_shares) if !total_vault_shares.is_zero() => Ok(AssetsResponse { + coin: Coin { + denom: balance.denom, + amount: balance.amount.multiply_ratio(shares, total_vault_shares), + }, + }), + _ => Ok(AssetsResponse { coin: balance }), } } -pub fn query_vault_info(deps: Deps) -> StdResult { - let all_coins = get_all_vault_coins(deps.storage)?; - let accepted_denoms = all_coins.iter().map(|c| c.denom.clone()).collect(); +pub fn query_vault_info(deps: Deps) -> ContractResult { + let req_denom = COIN_BALANCE.load(deps.storage)?.denom; + let vault_token_denom = VAULT_TOKEN_DENOM.load(deps.storage)?; Ok(VaultInfo { - accepts: accepted_denoms, - lockup: LOCKUP_TIME.load(deps.storage)?, - token_denom: LP_TOKEN_DENOM.load(deps.storage)?, + req_denom, + vault_token_denom, }) } -pub fn get_all_vault_coins(storage: &dyn Storage) -> StdResult> { - ASSETS - .range(storage, None, None, Order::Ascending) - .map(|res| { - let (denom, amount) = res?; - Ok(Coin { denom, amount }) - }) - .collect() +pub fn query_lockup_duration(deps: Deps) -> ContractResult { + let res = LOCKUP_TIME.load(deps.storage)?.ok_or(NotLockingVault)?; + Ok(res) } -pub fn query_unlocking_position(deps: Deps, id: u64) -> StdResult { - UNLOCKING_COINS +pub fn query_lockup(deps: Deps, id: u64) -> ContractResult { + Ok(LOCKUPS .range(deps.storage, None, None, Order::Ascending) .collect::>>()? .into_iter() .flat_map(|(_, positions)| positions) .find(|p| p.id == id) - .ok_or_else(|| StdError::generic_err("Id not found")) + .ok_or_else(|| StdError::generic_err("Id not found"))?) } -pub fn query_unlocking_positions(deps: Deps, addr: String) -> StdResult> { +pub fn query_lockups(deps: Deps, addr: String) -> ContractResult> { let addr = deps.api.addr_validate(addr.as_str())?; - let res = UNLOCKING_COINS.load(deps.storage, addr)?; + let res = LOCKUPS.load(deps.storage, addr)?; Ok(res) } -pub fn query_vault_coins_issued(storage: &dyn Storage) -> StdResult { +pub fn query_vault_token_supply(storage: &dyn Storage) -> ContractResult { let amount_issued = TOTAL_VAULT_SHARES .may_load(storage)? .unwrap_or(Uint128::zero()); diff --git a/contracts/mock-vault/src/state.rs b/contracts/mock-vault/src/state.rs index c7a30bb54..1125fc009 100644 --- a/contracts/mock-vault/src/state.rs +++ b/contracts/mock-vault/src/state.rs @@ -1,17 +1,18 @@ -use cosmwasm_std::{Addr, Uint128}; +use cosmwasm_std::{Addr, Coin, Uint128}; use cw_storage_plus::{Item, Map}; +use cw_utils::Duration; +use cosmos_vault_standard::extensions::lockup::Lockup; use rover::adapters::Oracle; -use rover::msg::vault::UnlockingPosition; -pub const LP_TOKEN_DENOM: Item = Item::new("lp_token_denom"); +pub const VAULT_TOKEN_DENOM: Item = Item::new("vault_token_denom"); pub const TOTAL_VAULT_SHARES: Item = Item::new("total_vault_shares"); -pub const LOCKUP_TIME: Item> = Item::new("lockup_time"); -pub const ASSETS: Map = Map::new("assets"); // Denom -> Amount +pub const LOCKUP_TIME: Item> = Item::new("lockup_time"); pub const ORACLE: Item = Item::new("oracle"); +pub const COIN_BALANCE: Item = Item::new("underlying_coin"); +pub const LOCKUPS: Map> = Map::new("lockups"); +pub const NEXT_LOCKUP_ID: Item = Item::new("next_lockup_id"); + // Used for mock LP token minting pub const CHAIN_BANK: Item = Item::new("chain_bank"); - -pub const UNLOCKING_COINS: Map> = Map::new("unlocking_coins"); -pub const NEXT_UNLOCK_ID: Item = Item::new("next_unlock_id"); diff --git a/contracts/mock-vault/src/unlock.rs b/contracts/mock-vault/src/unlock.rs index f0f63c4ab..5f2ebf72d 100644 --- a/contracts/mock-vault/src/unlock.rs +++ b/contracts/mock-vault/src/unlock.rs @@ -1,11 +1,12 @@ use cosmwasm_std::{Addr, DepsMut, Env, Event, MessageInfo, Response, StdResult, Uint128}; +use cw_utils::{Duration, Expiration}; -use rover::msg::vault::{ - UnlockingPosition, UNLOCKING_POSITION_ATTR, UNLOCKING_POSITION_CREATED_EVENT_TYPE, +use cosmos_vault_standard::extensions::lockup::{ + Lockup, UNLOCKING_POSITION_ATTR_KEY, UNLOCKING_POSITION_CREATED_EVENT_TYPE, }; use crate::error::ContractError; -use crate::state::{LOCKUP_TIME, NEXT_UNLOCK_ID, UNLOCKING_COINS}; +use crate::state::{LOCKUPS, LOCKUP_TIME, NEXT_LOCKUP_ID}; use crate::withdraw::{_exchange, get_vault_token}; pub fn request_unlock( @@ -14,26 +15,31 @@ pub fn request_unlock( info: MessageInfo, ) -> Result { let lockup_time_opt = LOCKUP_TIME.load(deps.storage)?; - let lockup_time = lockup_time_opt.ok_or(ContractError::NoLockupTime {})?; - - let vault_tokens = get_vault_token(deps.storage, info.funds)?; - - let next_unlock_id = NEXT_UNLOCK_ID.load(deps.storage)?; - let unlocked_at = env.block.time.plus_seconds(lockup_time); - UNLOCKING_COINS.update(deps.storage, info.sender, |opt| -> StdResult<_> { - let mut unlocking_positions = opt.unwrap_or_default(); - unlocking_positions.push(UnlockingPosition { - id: next_unlock_id, - amount: vault_tokens.amount, - unlocked_at, + let lockup_duration = lockup_time_opt.ok_or(ContractError::NotLockingVault {})?; + + let vault_token = get_vault_token(deps.storage, info.funds)?; + let next_lockup_id = NEXT_LOCKUP_ID.load(deps.storage)?; + + let release_at = match lockup_duration { + Duration::Height(h) => Expiration::AtHeight(env.block.height + h), + Duration::Time(s) => Expiration::AtTime(env.block.time.plus_seconds(s)), + }; + + LOCKUPS.update(deps.storage, info.sender.clone(), |opt| -> StdResult<_> { + let mut lockups = opt.unwrap_or_default(); + lockups.push(Lockup { + owner: info.sender.clone(), + id: next_lockup_id, + release_at, + coin: vault_token, }); - Ok(unlocking_positions) + Ok(lockups) })?; - NEXT_UNLOCK_ID.save(deps.storage, &(next_unlock_id + 1))?; + NEXT_LOCKUP_ID.save(deps.storage, &(next_lockup_id + 1))?; let event = Event::new(UNLOCKING_POSITION_CREATED_EVENT_TYPE) - .add_attribute(UNLOCKING_POSITION_ATTR, next_unlock_id.to_string()); + .add_attribute(UNLOCKING_POSITION_ATTR_KEY, next_lockup_id.to_string()); Ok(Response::new().add_event(event)) } @@ -43,7 +49,7 @@ pub fn withdraw_unlocked( sender: &Addr, id: u64, ) -> Result { - let unlocking_positions = UNLOCKING_COINS + let unlocking_positions = LOCKUPS .may_load(deps.storage, sender.clone())? .ok_or(ContractError::UnlockRequired {})?; @@ -53,7 +59,7 @@ pub fn withdraw_unlocked( .ok_or(ContractError::UnlockRequired {})? .clone(); - if matching_position.unlocked_at > env.block.time { + if !matching_position.release_at.is_expired(&env.block) { return Err(ContractError::UnlockNotReady {}); } @@ -61,36 +67,36 @@ pub fn withdraw_unlocked( .into_iter() .filter(|p| p.id != id) .collect(); - UNLOCKING_COINS.save(deps.storage, sender.clone(), &remaining)?; + LOCKUPS.save(deps.storage, sender.clone(), &remaining)?; - _exchange(deps.storage, sender, matching_position.amount) + _exchange(deps.storage, sender, matching_position.coin.amount) } pub fn withdraw_unlocking_force( deps: DepsMut, sender: &Addr, lockup_id: u64, - amount: Option, + amounts: Option, ) -> Result { - let mut unlocking_positions = UNLOCKING_COINS.load(deps.storage, sender.clone())?; - let mut unlocking_position = unlocking_positions + let mut lockups = LOCKUPS.load(deps.storage, sender.clone())?; + let mut lockup = lockups .iter() .find(|p| p.id == lockup_id) .cloned() .ok_or(ContractError::LockupPositionNotFound(lockup_id))?; - unlocking_positions.retain(|p| p.id != lockup_id); + lockups.retain(|p| p.id != lockup_id); - let amount_to_withdraw = match amount { - Some(a) if a != unlocking_position.amount => { - unlocking_position.amount -= a; - unlocking_positions.push(unlocking_position); + let amount_to_withdraw = match amounts { + Some(a) => { + lockup.coin.amount -= a; + lockups.push(lockup.clone()); a } - _ => unlocking_position.amount, + None => lockup.coin.amount, }; - UNLOCKING_COINS.save(deps.storage, sender.clone(), &unlocking_positions)?; + LOCKUPS.save(deps.storage, sender.clone(), &lockups)?; _exchange(deps.storage, sender, amount_to_withdraw) } diff --git a/contracts/mock-vault/src/withdraw.rs b/contracts/mock-vault/src/withdraw.rs index 375ce775a..a5c18cb66 100644 --- a/contracts/mock-vault/src/withdraw.rs +++ b/contracts/mock-vault/src/withdraw.rs @@ -1,12 +1,14 @@ +use cosmos_vault_standard::msg::AssetsResponse; use cosmwasm_std::{ - Addr, BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdResult, Storage, Uint128, + Addr, BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, Storage, + Uint128, }; -use crate::error::ContractError; +use crate::error::{ContractError, ContractResult}; use crate::query::query_coins_for_shares; -use crate::state::{ASSETS, CHAIN_BANK, LOCKUP_TIME, LP_TOKEN_DENOM, TOTAL_VAULT_SHARES}; +use crate::state::{CHAIN_BANK, COIN_BALANCE, LOCKUP_TIME, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}; -pub fn withdraw(deps: DepsMut, info: MessageInfo) -> Result { +pub fn withdraw(deps: DepsMut, info: MessageInfo) -> ContractResult { let lockup_time = LOCKUP_TIME.load(deps.storage)?; if lockup_time.is_some() { return Err(ContractError::UnlockRequired {}); @@ -15,7 +17,7 @@ pub fn withdraw(deps: DepsMut, info: MessageInfo) -> Result Result { +pub fn withdraw_force(deps: DepsMut, info: MessageInfo) -> ContractResult { let vault_tokens = get_vault_token(deps.storage, info.funds.clone())?; _exchange(deps.storage, &info.sender, vault_tokens.amount) } @@ -25,34 +27,38 @@ pub fn _exchange( storage: &mut dyn Storage, send_to: &Addr, shares: Uint128, -) -> Result { - let coins = query_coins_for_shares(storage, shares)?; +) -> ContractResult { + let res = withdraw_state_update(storage, shares)?; + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: send_to.to_string(), + amount: vec![res.coin], + }); + Ok(Response::new().add_message(transfer_msg)) +} + +pub fn withdraw_state_update( + storage: &mut dyn Storage, + shares: Uint128, +) -> ContractResult { + let res = query_coins_for_shares(storage, shares)?; TOTAL_VAULT_SHARES.update(storage, |current_amount| -> StdResult<_> { Ok(current_amount - shares) })?; - coins.iter().try_for_each(|asset| -> StdResult<()> { - ASSETS.update( - storage, - asset.clone().denom, - |current_amount| -> StdResult<_> { Ok(current_amount.unwrap() - asset.amount) }, - )?; - Ok(()) + COIN_BALANCE.update(storage, |total| -> StdResult<_> { + Ok(Coin { + denom: total.denom, + amount: total.amount - res.coin.amount, + }) })?; mock_lp_token_burn(storage, shares)?; - - let transfer_msg = CosmosMsg::Bank(BankMsg::Send { - to_address: send_to.to_string(), - amount: coins, - }); - - Ok(Response::new().add_message(transfer_msg)) + Ok(res) } -pub fn get_vault_token(storage: &mut dyn Storage, funds: Vec) -> Result { - let vault_token_denom = LP_TOKEN_DENOM.load(storage)?; +pub fn get_vault_token(storage: &mut dyn Storage, funds: Vec) -> ContractResult { + let vault_token_denom = VAULT_TOKEN_DENOM.load(storage)?; let res = funds.iter().find(|coin| coin.denom == vault_token_denom); match res { Some(c) if !c.amount.is_zero() => Ok(Coin { @@ -63,9 +69,8 @@ pub fn get_vault_token(storage: &mut dyn Storage, funds: Vec) -> Result StdResult<()> { +fn mock_lp_token_burn(storage: &mut dyn Storage, amount: Uint128) -> Result { CHAIN_BANK.update(storage, |bank_amount| -> StdResult<_> { Ok(bank_amount + amount) - })?; - Ok(()) + }) } diff --git a/packages/cosmos-vault-standard/Cargo.lock b/packages/cosmos-vault-standard/Cargo.lock new file mode 100644 index 000000000..485daeb76 --- /dev/null +++ b/packages/cosmos-vault-standard/Cargo.lock @@ -0,0 +1,789 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "base64ct" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" + +[[package]] +name = "cosmos-vault-standard" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-asset", + "cw-utils 0.16.0", + "cw20 0.16.0", + "schemars", + "serde", +] + +[[package]] +name = "cosmwasm-crypto" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7d659bbecbdca16878322becea489c0fcc36e784f41c942ea0171e9cf74179" +dependencies = [ + "digest 0.10.5", + "ed25519-zebra", + "k256", + "rand_core 0.6.4", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81b4676a316d3e4636f658d2d3aba9b4b30c3177e311bbda721b56d4a26342f" +dependencies = [ + "syn", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88917b0e7c987fd99251f8bb7e06da7d705e7572e0732428b72f8bc1f17b1af5" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03bd1495b8bc0130529dad7e69bef8d800654b50a5d5fc150f1e795c4c24c5b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cosmwasm-std" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39d5725d2bd4b868284c127f7a904c93a9826550f30267b301484138db2eb9b" +dependencies = [ + "base64", + "cosmwasm-crypto", + "cosmwasm-derive", + "derivative", + "forward_ref", + "hex", + "schemars", + "serde", + "serde-json-wasm", + "thiserror", + "uint", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f2b443d17d49dad5ef0ede301c3179cc923b8822f3393b4d2c28c269dd4a122" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "cw-asset" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7996c9c60e416aec195719137767d5cef8301237438bbabb772ee45a27f06e5e" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "cw1155", + "cw20 0.13.4", + "schemars", + "serde", +] + +[[package]] +name = "cw-storage-plus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-storage-plus" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b6f91c0b94481a3e9ef1ceb183c37d00764f8751e39b45fc09f4d9b970d469" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-utils" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-utils" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a84c6c1c0acc3616398eba50783934bd6c964bad6974241eaee3460c8f5b26" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw1155" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42197c9a0fd844653177009125f24157e486578289327a3781923e427a8f9bbc" +dependencies = [ + "cosmwasm-std", + "cw-utils 0.13.4", + "schemars", + "serde", +] + +[[package]] +name = "cw2" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91398113b806f4d2a8d5f8d05684704a20ffd5968bf87e3473e1973710b884ad" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "schemars", + "serde", +] + +[[package]] +name = "cw20" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" +dependencies = [ + "cosmwasm-std", + "cw-utils 0.13.4", + "schemars", + "serde", +] + +[[package]] +name = "cw20" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45a8794a5dd33b66af34caee52a7beceb690856adcc1682b6e3db88b2cdee62" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.16.0", + "schemars", + "serde", +] + +[[package]] +name = "der" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +dependencies = [ + "block-buffer 0.10.3", + "crypto-common", + "subtle", +] + +[[package]] +name = "dyn-clone" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" + +[[package]] +name = "ecdsa" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85789ce7dfbd0f0624c07ef653a08bb2ebf43d3e16531361f46d36dd54334fed" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" +dependencies = [ + "curve25519-dalek", + "hex", + "rand_core 0.6.4", + "serde", + "sha2 0.9.9", + "thiserror", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "digest 0.10.5", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df689201f395c6b90dfe87127685f8dbfc083a5e779e613575d8bd7314300c3e" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "group" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7391856def869c1c81063a03457c676fbcd419709c3dfb33d8d319de484b154d" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.5", +] + +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "k256" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3636d281d46c3b64182eb3a0a42b7b483191a2ecc3f05301fa67403f7c9bc949" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2 0.10.6", +] + +[[package]] +name = "libc" +version = "0.2.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "rfc6979" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c86280f057430a52f4861551b092a01b419b8eacefc7c995eacb9dc132fe32" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "schemars" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" + +[[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.5", +] + +[[package]] +name = "signature" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb766570a2825fa972bceff0d195727876a9cdf2460ab2e52d455dc2de47fd9" +dependencies = [ + "digest 0.10.5", + "rand_core 0.6.4", +] + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "uint" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/packages/cosmos-vault-standard/Cargo.toml b/packages/cosmos-vault-standard/Cargo.toml new file mode 100644 index 000000000..9ac6f8206 --- /dev/null +++ b/packages/cosmos-vault-standard/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "cosmos-vault-standard" +version = "0.1.0" +edition = "2021" +authors = ["Sturdy "] + +[features] +default = [] +lockup = ["cw-utils"] +keeper = [] +cw20 = ["dep:cw20", "dep:cw-asset"] +cw4626 = ["cw20"] + +[dependencies] +cosmwasm-std = "1.1.0" +schemars = "0.8.8" +serde = { version = "1.0.137", default-features = false, features = ["derive"] } +cosmwasm-schema = { version = "1.1.0" } +cw-utils = { version = "0.16.0", optional = true } +cw20 = { version = "0.16.0", optional = true } +cw-asset = { version = "2.3.0", optional = true } diff --git a/packages/cosmos-vault-standard/README.md b/packages/cosmos-vault-standard/README.md new file mode 100644 index 000000000..fad5d22f3 --- /dev/null +++ b/packages/cosmos-vault-standard/README.md @@ -0,0 +1,17 @@ +# How to use Extensions + +For example, if you want to use the Keeper and Lockup extensions as well as a +custom extension: + +```rust +use custom_extension::{CustomExtension} +use vault_standard::{ExecuteMsg as VaultStandardExecuteMsg}; + +pub enum ExtensionExecuteMsg { + Keeper(KeeperExecuteMsg), + Lockup(LockupExecuteMsg), + Custom(CustomExtension) +} + +type ExecuteMsg = VaultStandardExecuteMsg; +``` diff --git a/packages/cosmos-vault-standard/src/cw4626.rs b/packages/cosmos-vault-standard/src/cw4626.rs new file mode 100644 index 000000000..11b395550 --- /dev/null +++ b/packages/cosmos-vault-standard/src/cw4626.rs @@ -0,0 +1,88 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Binary, Empty, Uint128}; +use cw20::{Cw20Coin, Expiration, Logo}; + +use crate::msg::ExtensionExecuteMsg; + +#[cw_serde] +pub enum Cw4626ExecuteMsg { + //-------------------------------------------------------------------------------------------------- + // Standard CW20 ExecuteMsgs + //-------------------------------------------------------------------------------------------------- + /// Transfer is a base message to move tokens to another account without triggering actions + Transfer { + recipient: String, + amount: Uint128, + }, + /// Send is a base message to transfer tokens to a contract and trigger an action + /// on the receiving contract. + Send { + contract: String, + amount: Uint128, + msg: Binary, + }, + /// Only with "approval" extension. Allows spender to access an additional amount tokens + /// from the owner's (env.sender) account. If expires is Some(), overwrites current allowance + /// expiration with this one. + IncreaseAllowance { + spender: String, + amount: Uint128, + expires: Option, + }, + /// Only with "approval" extension. Lowers the spender's access of tokens + /// from the owner's (env.sender) account by amount. If expires is Some(), overwrites current + /// allowance expiration with this one. + DecreaseAllowance { + spender: String, + amount: Uint128, + expires: Option, + }, + /// Only with "approval" extension. Transfers amount tokens from owner -> recipient + /// if `env.sender` has sufficient pre-approval. + TransferFrom { + owner: String, + recipient: String, + amount: Uint128, + }, + /// Only with "approval" extension. Sends amount tokens from owner -> contract + /// if `env.sender` has sufficient pre-approval. + SendFrom { + owner: String, + contract: String, + amount: Uint128, + msg: Binary, + }, + /// Only with the "marketing" extension. If authorized, updates marketing metadata. + /// Setting None/null for any of these will leave it unchanged. + /// Setting Some("") will clear this field on the contract storage + UpdateMarketing { + /// A URL pointing to the project behind this token. + project: Option, + /// A longer description of the token and it's utility. Designed for tooltips or such + description: Option, + /// The address (if any) who can update this data structure + marketing: Option, + }, + /// If set as the "marketing" role on the contract, upload a new URL, SVG, or PNG for the token + UploadLogo(Logo), + //-------------------------------------------------------------------------------------------------- + // CW4626 ExecuteMsgs + //-------------------------------------------------------------------------------------------------- + Deposit { + cw20s: Option>, + /// An optional field containing the recipient of the vault token. If not set, the + /// caller address will be used instead. + recipient: Option, + }, + + Redeem { + /// An optional field containing which address should receive the withdrawn underlying assets. + /// If not set, the caller address will be used instead. + recipient: Option, + amount: Uint128, + }, + + Callback(S), + + VaultExtension(T), +} diff --git a/packages/cosmos-vault-standard/src/extensions/keeper.rs b/packages/cosmos-vault-standard/src/extensions/keeper.rs new file mode 100644 index 000000000..f2af10414 --- /dev/null +++ b/packages/cosmos-vault-standard/src/extensions/keeper.rs @@ -0,0 +1,38 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Addr; + +#[cw_serde] +pub struct KeeperJob { + //The numeric ID of the job + pub id: u64, + /// bool whether only whitelisted keepers can execute the job + pub whitelist: bool, + /// A list of whitelisted addresses that can execute the job + pub whitelisted_keepers: Vec, +} + +#[cw_serde] +pub enum KeeperExecuteMsg { + /// Callable by vault admin to whitelist a keeper to be able to execute a job + WhitelistKeeper { job_id: u64, keeper: String }, + /// Callable by vault admin to remove a keeper from the whitelist of a job + BlacklistKeeper { job_id: u64, keeper: String }, + /// Execute a keeper job. Should only be able to be called if + /// QueryMsg::KeeperJobReady returns true, and only by whitelisted + /// keepers if the whitelist bool on the KeeperJob is set to true. + ExecuteJob { job_id: u64 }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum KeeperQueryMsg { + /// Returns Vec + #[returns(Vec)] + KeeperJobs, + /// Returns Vec + #[returns(Vec)] + WhitelistedKeepers { job_id: u64 }, + /// Returns bool, whether the keeper job can be executed or not + #[returns(bool)] + KeeperJobReady { job_id: u64 }, +} diff --git a/packages/cosmos-vault-standard/src/extensions/lockup.rs b/packages/cosmos-vault-standard/src/extensions/lockup.rs new file mode 100644 index 000000000..b9db6f0d1 --- /dev/null +++ b/packages/cosmos-vault-standard/src/extensions/lockup.rs @@ -0,0 +1,104 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Coin, Uint128}; +use cw_utils::{Duration, Expiration}; + +#[cfg(feature = "cw20")] +use cw20::Cw20Coin; + +/// Type for the unlocking position created event emitted on call to `Unlock`. +pub const UNLOCKING_POSITION_CREATED_EVENT_TYPE: &str = "unlocking_position_created"; +/// Key for the lockup id attribute in the "unlocking position created" event that +/// is emitted on call to `Unlock`. +pub const UNLOCKING_POSITION_ATTR_KEY: &str = "lockup_id"; + +#[cw_serde] +pub enum LockupExecuteMsg { + /// Unlock is called to initiate unlocking a locked position held by the + /// vault. + /// The caller must pass the native vault tokens in the funds field. + /// Emits an event with type `UNLOCK_EVENT_TYPE` with an attribute with key + /// `UNLOCKING_POSITION_ATTR_KEY` containing an u64 lockup_id. + /// Also encodes the u64 lockup ID as binary and returns it in the Response's + /// data field, so that it can be read by SubMsg replies. + /// + /// Like Redeem, this takes an amount so that the same API can be used for + /// CW4626 and native tokens. + Unlock { amount: Uint128 }, + + /// Withdraw an unlocking position that has finished unlocking. + WithdrawUnlocked { + /// An optional field containing which address should receive the + /// withdrawn underlying assets. If not set, the caller address will be + /// used instead. + recipient: Option, + /// The ID of the expired lockup to withdraw from. + /// If None is passed, the vault will attempt to withdraw all expired + /// lockup positions. Note that this can fail if there are too many + /// lockup positions and the `max_contract_gas` limit is hit. + lockup_id: u64, + }, + + /// Can be called by whitelisted addresses to bypass the lockup and + /// immediately return the underlying assets. Used in the event of + /// liquidation. The caller must pass the native vault tokens in the funds + /// field. + ForceWithdraw { + /// The address which should receive the withdrawn assets. If not set, + /// the caller address will be used instead. + recipient: Option, + /// The amount of vault tokens to force unlock. + amount: Uint128, + }, + + /// Force withdraw from a position that is already unlocking (Unlock has + /// already been called). + ForceWithdrawUnlocking { + /// The ID of the unlocking position from which to force withdraw + lockup_id: u64, + /// Optional amounts of each underlying asset to be force withdrawn. + /// If None is passed, the entire position will be force withdrawn. + /// Vaults MAY require the ratio of assets to be the same as the ratio + /// in the `deposit_assets` field returned by the `VaultInfo` query. + amount: Option, + #[cfg(feature = "cw20")] + cw20s_amounts: Option>, + /// The address which should receive the withdrawn assets. If not set, + /// the assets will be sent to the caller. + recipient: Option, + }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum LockupQueryMsg { + /// Returns a `Vec` containing all the currently unclaimed lockup + /// positions for the `owner`. + #[returns(Vec)] + Lockups { + /// The address of the owner of the lockup + owner: String, + /// Return results only after this lockup_id + start_after: Option, + /// Max amount of results to return + limit: Option, + }, + + /// Returns `Lockup` info about a specific lockup, by owner and ID. + #[returns(Lockup)] + Lockup { lockup_id: u64 }, + + /// Returns `cw_utils::Duration` duration of the lockup. + #[returns(Duration)] + LockupDuration {}, +} + +/// Info about a currently unlocking position. +#[cw_serde] +pub struct Lockup { + pub owner: Addr, + pub id: u64, + pub release_at: Expiration, + pub coin: Coin, + #[cfg(feature = "cw20")] + pub cw20s: Vec, +} diff --git a/packages/cosmos-vault-standard/src/extensions/mod.rs b/packages/cosmos-vault-standard/src/extensions/mod.rs new file mode 100644 index 000000000..10ce789e6 --- /dev/null +++ b/packages/cosmos-vault-standard/src/extensions/mod.rs @@ -0,0 +1,4 @@ +#[cfg(feature = "keeper")] +pub mod keeper; +#[cfg(feature = "lockup")] +pub mod lockup; diff --git a/packages/cosmos-vault-standard/src/lib.rs b/packages/cosmos-vault-standard/src/lib.rs new file mode 100644 index 000000000..3094eb313 --- /dev/null +++ b/packages/cosmos-vault-standard/src/lib.rs @@ -0,0 +1,5 @@ +pub mod extensions; +pub mod msg; + +#[cfg(feature = "cw4626")] +pub mod cw4626; diff --git a/packages/cosmos-vault-standard/src/msg.rs b/packages/cosmos-vault-standard/src/msg.rs new file mode 100644 index 000000000..e22513711 --- /dev/null +++ b/packages/cosmos-vault-standard/src/msg.rs @@ -0,0 +1,221 @@ +#[cfg(feature = "cw20")] +use { + cosmwasm_std::{StdError, StdResult}, + cw20::Cw20Coin, + cw_asset::{Asset, AssetInfo}, + std::convert::TryFrom, +}; + +#[cfg(feature = "lockup")] +use crate::extensions::lockup::{LockupExecuteMsg, LockupQueryMsg}; + +#[cfg(feature = "keeper")] +use crate::extensions::keeper::{KeeperExecuteMsg, KeeperQueryMsg}; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Uint128; +use cosmwasm_std::{Coin, Empty}; +use schemars::JsonSchema; + +#[cw_serde] +pub enum ExecuteMsg { + /// Called to deposit into the vault. Native assets are passed in the funds + /// parameter. + Deposit { + /// With the cw20 feature, it is allowed to deposit CW20 tokens. These + /// must be passed in with to the `cw20s` argument and have allowance + /// pre-approved. + #[cfg(feature = "cw20")] + cw20s: Option>, + /// The optional recipient of the vault token. If not set, the caller + /// address will be used instead. + recipient: Option, + }, + + /// Called to redeem vault tokens and receive assets back from the vault. + /// The native vault token must be passed in the funds parameter, unless the + /// lockup extension is called, in which case the vault token has already + /// been passed to ExecuteMsg::Unlock. + Redeem { + /// An optional field containing which address should receive the + /// withdrawn underlying assets. If not set, the caller address will be + /// used instead. + recipient: Option, + /// The amount of vault tokens sent to the contract. In the case that + /// the vault token is a Cosmos native denom, we of course have this + /// information in the info.funds, but if the vault implements the Cw4626 + /// API, then we need this argument. We figured it's better to have one + /// API for both types of vaults, so we require this argument. + amount: Uint128, + }, + + /// Support for custom extensions + VaultExtension(T), +} + +/// Contains ExecuteMsgs of all enabled extensions. To enable extensions defined +/// outside of this create, you can define your own `ExtensionExecuteMsg` type +/// in your contract crate and pass it in as the generic parameter to ExecuteMsg +#[cw_serde] +pub enum ExtensionExecuteMsg { + #[cfg(feature = "keeper")] + Keeper(KeeperExecuteMsg), + #[cfg(feature = "lockup")] + Lockup(LockupExecuteMsg), +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg +where + T: JsonSchema, +{ + /// Returns `VaultStandardInfo` with information on the version of the vault + /// standard used as well as any enabled extensions. + #[returns(VaultStandardInfo)] + VaultStandardInfo {}, + + /// Returns `VaultInfo` representing vault requirements, lockup, & vault + /// token denom. + #[returns(VaultInfo)] + Info {}, + + /// Returns `Uint128` amount of vault tokens that will be returned for the + /// passed in assets. + /// + /// Allows an on-chain or off-chain user to simulate the effects of their + /// deposit at the current block, given current on-chain conditions. + /// + /// MUST return as close to and no more than the exact amount of Vault + /// shares that would be minted in a deposit call in the same transaction. + /// I.e. deposit should return the same or more shares as previewDeposit if + /// called in the same transaction. + /// + /// MUST NOT account for deposit limits like those returned from maxDeposit + /// and should always act as though the deposit would be accepted, + /// regardless if the user has enough tokens approved, etc. + /// + /// MUST be inclusive of deposit fees. Integrators should be aware of the + /// existence of deposit fees. + #[returns(Uint128)] + PreviewDeposit { + coins: Vec, + #[cfg(feature = "cw20")] + cw20s: Vec, + }, + + /// Returns `AssetsResponse` representing all the assets that would be + /// redeemed in exchange for vault tokens. Used by Rover to calculate vault + /// position values. + #[returns(AssetsResponse)] + PreviewRedeem { amount: Uint128 }, + + /// Returns `Option` maximum amount of assets that can be + /// deposited into the Vault for the `recipient`, through a call to Deposit. + /// + /// MUST return the maximum amount of assets deposit would allow to be + /// deposited for `recipient` and not cause a revert, which MUST NOT be higher + /// than the actual maximum that would be accepted (it should underestimate + /// if necessary). This assumes that the user has infinite assets, i.e. + /// MUST NOT rely on the asset balances of `recipient`. + /// + /// MUST factor in both global and user-specific limits, like if deposits + /// are entirely disabled (even temporarily) it MUST return 0. + #[returns(Option)] + MaxDeposit { recipient: String }, + + /// Returns `Option` maximum amount of Vault shares that can be redeemed + /// from the owner balance in the Vault, through a call to Withdraw + /// + /// TODO: Keep this? Could potentially be combined with MaxWithdraw to return + /// a MaxWithdrawResponse type that includes both max assets that can be + /// withdrawn as well as max vault shares that can be withdrawn in exchange + /// for assets. + #[returns(Option)] + MaxRedeem { owner: String }, + + /// Returns `AssetsResponse` assets managed by vault. + /// Useful for display purposes, and does not have to confer the exact + /// amount of underlying assets. + #[returns(AssetsResponse)] + TotalAssets {}, + + /// Returns `Uint128` total amount of vault tokens in circulation. + #[returns(Uint128)] + TotalVaultTokenSupply {}, + + /// The amount of shares that the vault would exchange for the amount of + /// assets provided, in an ideal scenario where all the conditions are met. + /// + /// Useful for display purposes and does not have to confer the exact amount + /// of shares returned by the vault if the passed in assets were deposited. + /// This calculation may not reflect the “per-user” price-per-share, and + /// instead should reflect the “average-user’s” price-per-share, meaning + /// what the average user should expect to see when exchanging to and from. + #[returns(Uint128)] + ConvertToShares { + coins: Vec, + #[cfg(feature = "cw20")] + cw20s: Vec, + }, + + /// Returns `AssetsResponse` assets that the Vault would exchange for + /// the amount of shares provided, in an ideal scenario where all the + /// conditions are met. + /// + /// Useful for display purposes and does not have to confer the exact amount + /// of assets returned by the vault if the passed in shares were withdrawn. + /// This calculation may not reflect the “per-user” price-per-share, and + /// instead should reflect the “average-user’s” price-per-share, meaning + /// what the average user should expect to see when exchanging to and from. + #[returns(AssetsResponse)] + ConvertToAssets { shares: Uint128 }, + + /// TODO: How to handle return derive? We must supply a type here, but we + /// don't know it. + #[returns(Empty)] + VaultExtension(T), +} + +/// Contains QueryMsgs of all enabled extensions. To enable extensions defined +/// outside of this create, you can define your own `ExtensionQueryMsg` type +/// in your contract crate and pass it in as the generic parameter to QueryMsg +#[cw_serde] +pub enum ExtensionQueryMsg { + #[cfg(feature = "keeper")] + Keeper(KeeperQueryMsg), + #[cfg(feature = "lockup")] + Lockup(LockupQueryMsg), +} + +/// Struct returned from QueryMsg::VaultStandardInfo with information about the +/// used version of the vault standard and any extensions used. +/// +/// This struct should be stored as an Item under the `vault_standard_info` key, +/// so that other contracts can do a RawQuery and read it directly from storage +/// instead of needing to do a costly SmartQuery. +#[cw_serde] +pub struct VaultStandardInfo { + /// The version of the vault standard used. A number, e.g. 1, 2, etc. + pub version: u16, + /// A list of vault standard extensions used by the vault. + /// E.g. ["cw20", "lockup", "keeper"] + pub extensions: Vec, +} + +#[cw_serde] +pub struct AssetsResponse { + pub coin: Coin, + #[cfg(feature = "cw20")] + pub cw20s: Vec, +} + +/// Returned by QueryMsg::Info and contains information about this vault +#[cw_serde] +pub struct VaultInfo { + pub req_denom: String, + #[cfg(feature = "cw20")] + pub deposit_cw20s: Vec, + /// Denom of vault token + pub vault_token_denom: String, +} diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 0bb52259a..7ab2e76c8 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -15,9 +15,11 @@ mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } +cosmos-vault-standard = { path = "../cosmos-vault-standard", features = ["lockup"] } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" cw-storage-plus = "0.16" +cw-utils = "0.16" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" diff --git a/packages/rover/src/adapters/vault/base.rs b/packages/rover/src/adapters/vault/base.rs index 47a296a87..0a2db0582 100644 --- a/packages/rover/src/adapters/vault/base.rs +++ b/packages/rover/src/adapters/vault/base.rs @@ -1,25 +1,36 @@ +use cosmos_vault_standard::extensions::lockup::LockupExecuteMsg::{ + ForceWithdraw, ForceWithdrawUnlocking, Unlock, WithdrawUnlocked, +}; +use cosmos_vault_standard::extensions::lockup::LockupQueryMsg::{ + Lockup as LockupQueryMsg, LockupDuration, +}; +use cosmos_vault_standard::msg::{ + AssetsResponse, ExecuteMsg, ExtensionExecuteMsg, ExtensionQueryMsg, QueryMsg, VaultInfo, +}; use std::hash::Hash; +use cosmos_vault_standard::extensions::lockup::Lockup; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, Decimal, QuerierWrapper, QueryRequest, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, }; +use cw_utils::Duration; use crate::adapters::vault::VaultPositionAmount; use crate::error::ContractError; use crate::error::ContractError::InvalidVaultConfig; -use crate::msg::vault::{ExecuteMsg, QueryMsg, UnlockingPosition, VaultInfo}; use crate::traits::Stringify; pub const VAULT_REQUEST_REPLY_ID: u64 = 10_001; -pub type UnlockingId = u64; +type VaultExecuteMsg = ExecuteMsg; +type VaultQueryMsg = QueryMsg; #[cw_serde] pub struct VaultUnlockingPosition { /// Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call. - pub id: UnlockingId, + pub id: u64, /// Number of vault tokens pub amount: Uint128, } @@ -98,11 +109,11 @@ impl Stringify for Vec { } impl Vault { - pub fn deposit_msg(&self, funds: &[Coin]) -> StdResult { + pub fn deposit_msg(&self, coin: &Coin) -> StdResult { let deposit_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), - funds: funds.to_vec(), - msg: to_binary(&ExecuteMsg::Deposit {})?, + funds: vec![coin.clone()], + msg: to_binary(&VaultExecuteMsg::Deposit { recipient: None })?, }); Ok(deposit_msg) } @@ -117,14 +128,20 @@ impl Vault { let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![Coin { - denom: vault_info.token_denom, + denom: vault_info.vault_token_denom, amount, }], msg: to_binary( &(if force { - ExecuteMsg::ForceWithdraw {} + VaultExecuteMsg::VaultExtension(ExtensionExecuteMsg::Lockup(ForceWithdraw { + recipient: None, + amount, + })) } else { - ExecuteMsg::Withdraw {} + VaultExecuteMsg::Redeem { + recipient: None, + amount, + } }), )?, }); @@ -139,28 +156,43 @@ impl Vault { let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![], - msg: to_binary(&ExecuteMsg::ForceWithdrawUnlocking { lockup_id, amount })?, + msg: to_binary(&VaultExecuteMsg::VaultExtension( + ExtensionExecuteMsg::Lockup(ForceWithdrawUnlocking { + lockup_id, + amount, + recipient: None, + }), + ))?, }); Ok(withdraw_msg) } - pub fn request_unlock_msg(&self, funds: &[Coin]) -> StdResult { + pub fn request_unlock_msg(&self, coin: Coin) -> StdResult { let request_msg = SubMsg::reply_on_success( CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), - funds: funds.to_vec(), - msg: to_binary(&ExecuteMsg::RequestUnlock {})?, + funds: vec![coin.clone()], + msg: to_binary(&VaultExecuteMsg::VaultExtension( + ExtensionExecuteMsg::Lockup(Unlock { + amount: coin.amount, + }), + ))?, }), VAULT_REQUEST_REPLY_ID, ); Ok(request_msg) } - pub fn withdraw_unlocked_msg(&self, position_id: u64) -> StdResult { + pub fn withdraw_unlocked_msg(&self, lockup_id: u64) -> StdResult { let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![], - msg: to_binary(&ExecuteMsg::WithdrawUnlocked { id: position_id })?, + msg: to_binary(&VaultExecuteMsg::VaultExtension( + ExtensionExecuteMsg::Lockup(WithdrawUnlocked { + recipient: None, + lockup_id, + }), + ))?, }); Ok(withdraw_msg) } @@ -168,18 +200,25 @@ impl Vault { pub fn query_info(&self, querier: &QuerierWrapper) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), - msg: to_binary(&QueryMsg::Info {})?, + msg: to_binary(&VaultQueryMsg::Info {})?, })) } - pub fn query_unlocking_position_info( - &self, - querier: &QuerierWrapper, - id: u64, - ) -> StdResult { + pub fn query_lockup_duration(&self, querier: &QuerierWrapper) -> StdResult { + querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.address.to_string(), + msg: to_binary(&VaultQueryMsg::VaultExtension(ExtensionQueryMsg::Lockup( + LockupDuration {}, + )))?, + })) + } + + pub fn query_lockup(&self, querier: &QuerierWrapper, lockup_id: u64) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), - msg: to_binary(&QueryMsg::UnlockingPosition { id })?, + msg: to_binary(&VaultQueryMsg::VaultExtension(ExtensionQueryMsg::Lockup( + LockupQueryMsg { lockup_id }, + )))?, })) } @@ -187,7 +226,7 @@ impl Vault { let vault_info = self.query_info(querier)?; let res: BalanceResponse = querier.query(&QueryRequest::Bank(BankQuery::Balance { address: addr.to_string(), - denom: vault_info.token_denom, + denom: vault_info.vault_token_denom, }))?; Ok(res.amount.amount) } @@ -196,17 +235,17 @@ impl Vault { &self, querier: &QuerierWrapper, amount: Uint128, - ) -> StdResult> { + ) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), - msg: to_binary(&QueryMsg::PreviewRedeem { amount })?, + msg: to_binary(&VaultQueryMsg::PreviewRedeem { amount })?, })) } pub fn query_total_vault_coins_issued(&self, querier: &QuerierWrapper) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), - msg: to_binary(&QueryMsg::TotalVaultCoinsIssued {})?, + msg: to_binary(&VaultQueryMsg::TotalVaultTokenSupply {})?, })) } } diff --git a/packages/rover/src/extensions/reply.rs b/packages/rover/src/extensions/reply.rs index bc3451672..b77ab3bfb 100644 --- a/packages/rover/src/extensions/reply.rs +++ b/packages/rover/src/extensions/reply.rs @@ -1,4 +1,6 @@ -use crate::msg::vault::UNLOCKING_POSITION_CREATED_EVENT_TYPE; +use cosmos_vault_standard::extensions::lockup::{ + UNLOCKING_POSITION_ATTR_KEY, UNLOCKING_POSITION_CREATED_EVENT_TYPE, +}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Reply, StdError, StdResult, SubMsgResult}; @@ -38,7 +40,7 @@ impl AttrParse for Reply { let id = &unlock_event .attributes .iter() - .find(|x| x.key == "id") + .find(|x| x.key == UNLOCKING_POSITION_ATTR_KEY) .ok_or_else(|| StdError::generic_err("No id attribute"))? .value; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index a30493e35..5bdfc7cb5 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -38,22 +38,19 @@ pub enum Action { /// Repay coin of specified amount back to Red Bank Repay(Coin), /// Deposit coins into vault strategy - VaultDeposit { - vault: VaultUnchecked, - coins: Vec, - }, + EnterVault { vault: VaultUnchecked, coin: Coin }, /// Withdraw underlying coins from vault - VaultWithdraw { + ExitVault { vault: VaultUnchecked, amount: Uint128, }, /// Requests unlocking of shares for a vault with a required lock period - VaultRequestUnlock { + RequestVaultUnlock { vault: VaultUnchecked, amount: Uint128, }, /// Withdraws the assets for unlocking position id from vault. Required time must have elapsed. - VaultWithdrawUnlocked { id: u64, vault: VaultUnchecked }, + ExitVaultUnlocked { id: u64, vault: VaultUnchecked }, /// Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt /// denom/amount of what the liquidator wants to payoff and 2) the request coin denom which the /// liquidatee should have a balance of. The amount returned to liquidator will be the request coin @@ -107,11 +104,17 @@ pub enum CallbackMsg { /// Calculate the account's max loan-to-value health factor. If above 1, /// emits a `position_changed` event. If 1 or below, raises an error. AssertBelowMaxLTV { account_id: String }, - /// Adds list of coins to a vault strategy - VaultDeposit { + /// Adds coin to a vault strategy + EnterVault { account_id: String, vault: Vault, - coins: Vec, + coin: Coin, + }, + /// Exchanges vault LP shares for assets + ExitVault { + account_id: String, + vault: Vault, + amount: Uint128, }, /// Used to update the account balance of vault coins after a vault action has taken place UpdateVaultCoinBalance { @@ -121,27 +124,21 @@ pub enum CallbackMsg { /// Total vault coin balance in Rover previous_total_balance: Uint128, }, - /// Exchanges vault LP shares for assets - VaultWithdraw { - account_id: String, - vault: Vault, - amount: Uint128, - }, /// A privileged action only to be used by Rover. Same as `VaultWithdraw` except it bypasses any lockup period /// restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation. - VaultForceWithdraw { + ForceExitVault { account_id: String, vault: Vault, amount: Uint128, }, /// Requests unlocking of shares for a vault with a lock period - VaultRequestUnlock { + RequestVaultUnlock { account_id: String, vault: Vault, amount: Uint128, }, /// Withdraws assets from vault for a locked position having a lockup period that has been fulfilled - VaultWithdrawUnlocked { + ExitVaultUnlocked { account_id: String, vault: Vault, position_id: u64, diff --git a/packages/rover/src/msg/mod.rs b/packages/rover/src/msg/mod.rs index f3358abc2..50cb8a531 100644 --- a/packages/rover/src/msg/mod.rs +++ b/packages/rover/src/msg/mod.rs @@ -1,7 +1,6 @@ pub mod execute; pub mod instantiate; pub mod query; -pub mod vault; pub use execute::ExecuteMsg; pub use instantiate::InstantiateMsg; diff --git a/packages/rover/src/msg/vault.rs b/packages/rover/src/msg/vault.rs deleted file mode 100644 index daad8c3e6..000000000 --- a/packages/rover/src/msg/vault.rs +++ /dev/null @@ -1,70 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Timestamp, Uint128}; - -pub const UNLOCKING_POSITION_CREATED_EVENT_TYPE: &str = "unlocking_position_created"; -pub const UNLOCKING_POSITION_ATTR: &str = "id"; - -/// Partial compatibility with EIP-4626 -#[cw_serde] -pub enum ExecuteMsg { - /// Enters list of `Vec` into a vault strategy in exchange for vault tokens. - Deposit {}, - /// Withdraw underlying coins in vault by exchanging vault `Coin` - Withdraw {}, - /// A privileged action only to be used by Rover. Same as `Withdraw` except it bypasses any lockup period - /// restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation. - ForceWithdraw {}, - /// Force withdraw from a position that is already unlocking (Unlock has already been called) - ForceWithdrawUnlocking { - lockup_id: u64, - amount: Option, - }, - /// Some vaults have lockup periods (typically between 1-14 days). This action sends vault `Coin` - /// which is locked for vault lockup period and available to `Unlock` after that time has elapsed. - /// On response, vault sends back `unlocking_position_created` event with attribute `id` representing - /// the new unlocking coins position. - RequestUnlock {}, - /// Withdraw assets in vault that have been unlocked for given unlocking position - WithdrawUnlocked { id: u64 }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Vault requirements, lockup, & vault token denom - #[returns(VaultInfo)] - Info {}, - /// All the coins that would be redeemed for in exchange for - /// vault coins. Used by Rover to calculate vault position values. - #[returns(Vec)] - PreviewRedeem { amount: Uint128 }, - /// Returns the total vault coins issued. In order to prevent Cream-attack, we cannot - /// query the bank module for this amount. - #[returns(Uint128)] - TotalVaultCoinsIssued {}, - /// The vault positions that this address has requested to unlock - #[returns(Vec)] - UnlockingPositionsForAddr { addr: String }, - #[returns(UnlockingPosition)] - UnlockingPosition { id: u64 }, -} - -#[cw_serde] -pub struct VaultInfo { - /// Denom of vault token - pub token_denom: String, - /// Coin denoms required to enter vault - pub accepts: Vec, - /// Time in seconds for unlock period - pub lockup: Option, -} - -#[cw_serde] -pub struct UnlockingPosition { - /// Unique identifier representing the unlocking position. Needed for `ExecuteMsg::Unlock {}` call. - pub id: u64, - /// Number of vault tokens - pub amount: Uint128, - /// Absolute time when position unlocks in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC) - pub unlocked_at: Timestamp, -} From b9c6c59a120002531da6bbe94b86a585ddebde9f Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 1 Nov 2022 14:50:43 +0100 Subject: [PATCH 069/218] [TEMP] Adding outposts repo as internal dep (#34) * [TEMP] Adding outposts repo as internal dep * review updates --- .github/workflows/main.yml | 54 +- Makefile.toml | 35 +- contracts/credit-manager/Cargo.toml | 4 +- contracts/mars-oracle-adapter/Cargo.toml | 2 +- contracts/mock-oracle/Cargo.toml | 2 +- contracts/mock-red-bank/Cargo.toml | 2 +- .../src/extensions/lockup.rs | 4 +- packages/health/Cargo.toml | 29 + packages/health/src/health.rs | 181 ++++ packages/health/src/lib.rs | 2 + packages/health/src/query.rs | 42 + packages/outpost/Cargo.toml | 31 + packages/outpost/src/address_provider.rs | 160 ++++ packages/outpost/src/error.rs | 71 ++ packages/outpost/src/helpers.rs | 63 ++ packages/outpost/src/incentives.rs | 95 ++ packages/outpost/src/lib.rs | 8 + packages/outpost/src/math.rs | 267 ++++++ packages/outpost/src/oracle.rs | 91 ++ .../src/red_bank/interest_rate_model.rs | 226 +++++ packages/outpost/src/red_bank/market.rs | 128 +++ packages/outpost/src/red_bank/mod.rs | 9 + packages/outpost/src/red_bank/msg.rs | 222 +++++ packages/outpost/src/red_bank/types.rs | 117 +++ packages/outpost/src/rewards_collector.rs | 179 ++++ packages/rover/Cargo.toml | 4 +- schema.Makefile.toml | 51 ++ schemas/credit-manager/credit-manager.json | 561 ++++++++---- .../mars-oracle-adapter.json | 131 ++- schemas/mock-oracle/mock-oracle.json | 4 +- schemas/mock-vault/mock-vault.json | 823 ++++++++++++++---- 31 files changed, 3157 insertions(+), 441 deletions(-) create mode 100644 packages/health/Cargo.toml create mode 100644 packages/health/src/health.rs create mode 100644 packages/health/src/lib.rs create mode 100644 packages/health/src/query.rs create mode 100644 packages/outpost/Cargo.toml create mode 100644 packages/outpost/src/address_provider.rs create mode 100644 packages/outpost/src/error.rs create mode 100644 packages/outpost/src/helpers.rs create mode 100644 packages/outpost/src/incentives.rs create mode 100644 packages/outpost/src/lib.rs create mode 100644 packages/outpost/src/math.rs create mode 100644 packages/outpost/src/oracle.rs create mode 100644 packages/outpost/src/red_bank/interest_rate_model.rs create mode 100644 packages/outpost/src/red_bank/market.rs create mode 100644 packages/outpost/src/red_bank/mod.rs create mode 100644 packages/outpost/src/red_bank/msg.rs create mode 100644 packages/outpost/src/red_bank/types.rs create mode 100644 packages/outpost/src/rewards_collector.rs create mode 100644 schema.Makefile.toml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index eff757f83..390d3edfa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,8 +1,3 @@ -# NOTE: after Mars outposts publishes external crate, this workflow should revert to: -# https://github.com/mars-protocol/rover/blob/8824233d4106eb71ee41a9c7a84b4b94a2bad072/.github/workflows/main.yml -# Rust optimizer does not support local package dependencies from other workspaces. For this reason it is -# temporarily disabled until it can be replaced by a crates.io dep. - on: pull_request name: Main @@ -15,15 +10,6 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@v3 - with: - path: rover - - - name: Checkout mars-outposts - uses: actions/checkout@v3 - with: - repository: mars-protocol/outposts - token: ${{ secrets.TEMP_ACCESS_TOKEN }} - path: outposts - name: Install stable toolchain uses: actions-rs/toolchain@v1 @@ -36,29 +22,21 @@ jobs: - name: Install cargo make uses: davidB/rust-cargo-make@v1 - - name: Run cargo check - working-directory: rover - run: cargo make check - - - name: Compile WASM contract - working-directory: rover + - name: Compile workspace run: cargo make build + - name: Generate schemas + run: cargo make generate-all-schemas + + - name: Compile contracts to wasm + run: cargo make rust-optimizer + test: name: Test Suite runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v3 - with: - path: rover - - - name: Checkout mars-outposts - uses: actions/checkout@v3 - with: - repository: mars-protocol/outposts - token: ${{ secrets.TEMP_ACCESS_TOKEN }} - path: outposts - name: Install stable toolchain uses: actions-rs/toolchain@v1 @@ -71,10 +49,7 @@ jobs: uses: davidB/rust-cargo-make@v1 - name: Run tests - working-directory: rover run: cargo make test - env: - RUST_BACKTRACE: 1 lints: name: Lints @@ -82,15 +57,6 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@v3 - with: - path: rover - - - name: Checkout mars-outposts - uses: actions/checkout@v3 - with: - repository: mars-protocol/outposts - token: ${{ secrets.TEMP_ACCESS_TOKEN }} - path: outposts - name: Install stable toolchain uses: actions-rs/toolchain@v1 @@ -103,10 +69,8 @@ jobs: - name: Install cargo make uses: davidB/rust-cargo-make@v1 - - name: Run formatter - working-directory: rover + - name: Check formatting run: cargo make fmt - - name: Run cargo clippy - working-directory: rover + - name: Clippy check run: cargo make clippy diff --git a/Makefile.toml b/Makefile.toml index e664eef0c..becad4707 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -1,10 +1,10 @@ +extend= [ + { path = "schema.Makefile.toml" } +] + [config] default_to_workspace = false -[tasks.check] -command = "cargo" -args = ["check"] - [tasks.build] command = "cargo" args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked"] @@ -12,14 +12,14 @@ args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked"] [tasks.rust-optimizer] script = """ if [[ $(arch) == "arm64" ]]; then - image="cosmwasm/workspace-optimizer-arm64" + image="cosmwasm/workspace-optimizer-arm64:0.12.8" else - image="cosmwasm/workspace-optimizer" + image="cosmwasm/workspace-optimizer:0.12.9" fi docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - ${image}:0.12.9 + ${image} """ [tasks.test] @@ -36,29 +36,10 @@ args = ["clippy", "--tests", "--", "-D", "warnings"] [tasks.all-github-actions] dependencies = [ - "check", "build", "test", "fmt", "clippy", + "generate-all-schemas", "rust-optimizer", ] - -[tasks.generate-all-schemas] -script = """ - rm -rf schema - rm -rf schemas - mkdir schemas - - array=( credit-manager account-nft swapper-base mars-oracle-adapter mock-red-bank mock-vault mock-oracle ) - for i in "${array[@]}" - do - : - echo "$i" - cargo run --package "$i" --example schema - mkdir schemas/"$i" - mv schema/"$i".json schemas/"$i"/"$i".json - done - - rm -rf schema -""" diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 435e8761b..20a170e50 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -15,7 +15,7 @@ library = [] [dependencies] account-nft = { version = "1.0", path = "../account-nft", features = ["library"] } -mars-health = { version = "0.1", path = "../../../outposts/packages/health" } +mars-health = { version = "0.1", path = "../../packages/health" } mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } mars-oracle-adapter = { version = "1.0", path = "../../contracts/mars-oracle-adapter", features = ["library"] } mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } @@ -32,7 +32,7 @@ cw-storage-plus = "0.16" [dev-dependencies] swapper-mock = { version = "1.0", path = "../../contracts/swapper/mock", features = ["library"] } -mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } +mars-outpost = { version = "0.1", path = "../../packages/outpost" } anyhow = "1" cw-multi-test = "0.16" diff --git a/contracts/mars-oracle-adapter/Cargo.toml b/contracts/mars-oracle-adapter/Cargo.toml index 01661fa6b..fabcba86d 100644 --- a/contracts/mars-oracle-adapter/Cargo.toml +++ b/contracts/mars-oracle-adapter/Cargo.toml @@ -14,7 +14,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } +mars-outpost = { version = "0.1", path = "../../packages/outpost" } rover = { version = "1.0", path = "../../packages/rover" } cosmwasm-schema = "1.1" diff --git a/contracts/mock-oracle/Cargo.toml b/contracts/mock-oracle/Cargo.toml index 694c4200d..a4a0b78ff 100644 --- a/contracts/mock-oracle/Cargo.toml +++ b/contracts/mock-oracle/Cargo.toml @@ -14,7 +14,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } +mars-outpost = { version = "0.1", path = "../../packages/outpost" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index b59fc423b..d4ad2dcb4 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -14,7 +14,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } +mars-outpost = { version = "0.1", path = "../../packages/outpost" } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" diff --git a/packages/cosmos-vault-standard/src/extensions/lockup.rs b/packages/cosmos-vault-standard/src/extensions/lockup.rs index b9db6f0d1..76351cbd5 100644 --- a/packages/cosmos-vault-standard/src/extensions/lockup.rs +++ b/packages/cosmos-vault-standard/src/extensions/lockup.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Coin, Uint128}; -use cw_utils::{Duration, Expiration}; +use cw_utils::Expiration; #[cfg(feature = "cw20")] use cw20::Cw20Coin; @@ -88,7 +88,7 @@ pub enum LockupQueryMsg { Lockup { lockup_id: u64 }, /// Returns `cw_utils::Duration` duration of the lockup. - #[returns(Duration)] + #[returns(cw_utils::Duration)] LockupDuration {}, } diff --git a/packages/health/Cargo.toml b/packages/health/Cargo.toml new file mode 100644 index 000000000..6a1caf972 --- /dev/null +++ b/packages/health/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "mars-health" +version = "0.1.0" +authors = [ + "larry_0x ", + "Piotr Babel ", + "Gabe Rodriguez ", + "Ahmad Kaouk" +] +edition = "2021" +description = "Helper functions to compute the health factor" +license = "GPL-3.0" +repository = "https://github.com/mars-protocol/outposts" +homepage = "https://marsprotocol.io" +documentation = "https://docs.marsprotocol.io/mars-protocol/developers/protocol-overview" +readme = "README.md" +keywords = ["mars", "cosmwasm", "health"] + +[lib] +doctest = false + +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] + +[dependencies] +mars-outpost = { path = "../outpost" } +cosmwasm-std = "1.1" diff --git a/packages/health/src/health.rs b/packages/health/src/health.rs new file mode 100644 index 000000000..dd3d667d1 --- /dev/null +++ b/packages/health/src/health.rs @@ -0,0 +1,181 @@ +use crate::query::MarsQuerier; +use cosmwasm_std::{Addr, Coin, Decimal, QuerierWrapper, StdError, StdResult, Uint128}; +use mars_outpost::{math::divide_decimal_by_decimal, red_bank::Market}; +use std::{collections::HashMap, fmt}; + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct Position { + pub denom: String, + pub price: Decimal, + pub collateral_amount: Decimal, + pub debt_amount: Decimal, + pub max_ltv: Decimal, + pub liquidation_threshold: Decimal, +} + +#[derive(Default, Debug, PartialEq, Eq)] +pub struct Health { + /// The sum of the value of all debts + pub total_debt_value: Decimal, + /// The sum of the value of all collaterals + pub total_collateral_value: Decimal, + /// The sum of the value of all colletarals adjusted by their Max LTV + pub max_ltv_adjusted_collateral: Decimal, + /// The sum of the value of all colletarals adjusted by their Liquidation Threshold + pub liquidation_threshold_adjusted_collateral: Decimal, + /// The sum of the value of all collaterals multiplied by their max LTV, over the total value of debt + pub max_ltv_health_factor: Option, + /// The sum of the value of all collaterals multiplied by their liquidation threshold over the total value of debt + pub liquidation_health_factor: Option, +} + +impl fmt::Display for Health { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "(total_debt_value: {}, total_collateral_value: {}, max_ltv_adjusted_collateral: {}, lqdt_threshold_adjusted_collateral: {}, max_ltv_health_factor: {}, liquidation_health_factor: {})", + self.total_debt_value, + self.total_collateral_value, + self.max_ltv_adjusted_collateral, + self.liquidation_threshold_adjusted_collateral, + self.max_ltv_health_factor.map_or("n/a".to_string(), |x| x.to_string()), + self.liquidation_health_factor.map_or("n/a".to_string(), |x| x.to_string()) + ) + } +} + +impl Health { + /// Compute the health from coins (collateral and debt) + pub fn compute_health_from_coins( + querier: &QuerierWrapper, + oracle_addr: &Addr, + red_bank_addr: &Addr, + collateral: &[Coin], + debt: &[Coin], + ) -> StdResult { + let querier = MarsQuerier::new(querier, oracle_addr, red_bank_addr); + let positions = Self::positions_from_coins(&querier, collateral, debt)?; + + Self::compute_health(&positions.into_values().collect::>()) + } + + /// Compute the health for a Position + pub fn compute_health(positions: &[Position]) -> StdResult { + let mut health = positions.iter().try_fold::<_, _, StdResult>( + Health::default(), + |mut h, p| { + let collateral_value = p.collateral_amount.checked_mul(p.price)?; + h.total_debt_value += p.debt_amount.checked_mul(p.price)?; + h.total_collateral_value += collateral_value; + h.max_ltv_adjusted_collateral += collateral_value.checked_mul(p.max_ltv)?; + h.liquidation_threshold_adjusted_collateral += + collateral_value.checked_mul(p.liquidation_threshold)?; + Ok(h) + }, + )?; + + // If there aren't any debts a health factor can't be computed (divide by zero) + if !health.total_debt_value.is_zero() { + health.max_ltv_health_factor = Some(divide_decimal_by_decimal( + health.max_ltv_adjusted_collateral, + health.total_debt_value, + )?); + health.liquidation_health_factor = Some(divide_decimal_by_decimal( + health.liquidation_threshold_adjusted_collateral, + health.total_debt_value, + )?); + } + + Ok(health) + } + + #[inline] + pub fn is_liquidatable(&self) -> bool { + self.liquidation_health_factor + .map_or(false, |hf| hf < Decimal::one()) + } + + #[inline] + pub fn is_above_max_ltv(&self) -> bool { + self.max_ltv_health_factor + .map_or(false, |hf| hf < Decimal::one()) + } + + /// Convert a collection of coins (Collateral and debts) to a map of `Position` + pub fn positions_from_coins( + querier: &MarsQuerier, + collateral: &[Coin], + debt: &[Coin], + ) -> StdResult> { + let mut positions: HashMap = HashMap::new(); + + collateral.iter().try_for_each(|c| -> StdResult<_> { + match positions.get_mut(&c.denom) { + Some(p) => { + p.collateral_amount += to_decimal(c.amount)?; + } + None => { + let Market { + max_loan_to_value, + liquidation_threshold, + .. + } = querier.query_market(&c.denom)?; + + positions.insert( + c.denom.clone(), + Position { + denom: c.denom.clone(), + collateral_amount: to_decimal(c.amount)?, + debt_amount: Decimal::zero(), + price: querier.query_price(&c.denom)?, + max_ltv: max_loan_to_value, + liquidation_threshold, + }, + ); + } + } + Ok(()) + })?; + + debt.iter().try_for_each(|d| -> StdResult<_> { + match positions.get_mut(&d.denom) { + Some(p) => { + p.debt_amount += to_decimal(d.amount)?; + } + None => { + let Market { + max_loan_to_value, + liquidation_threshold, + .. + } = querier.query_market(&d.denom)?; + + positions.insert( + d.denom.clone(), + Position { + denom: d.denom.clone(), + collateral_amount: Decimal::zero(), + debt_amount: to_decimal(d.amount)?, + price: querier.query_price(&d.denom)?, + max_ltv: max_loan_to_value, + liquidation_threshold, + }, + ); + } + } + Ok(()) + })?; + Ok(positions) + } +} + +/// helper function to convert `Uint128` to `Decimal`. +/// Maps `CheckFromRatioError` to `StdError` +pub fn to_decimal(x: Uint128) -> StdResult { + Decimal::checked_from_ratio(x, 1u128).map_err(|_e| StdError::Overflow { + source: cosmwasm_std::OverflowError { + operation: cosmwasm_std::OverflowOperation::Mul, + operand1: x.to_string(), + operand2: "".to_string(), + }, + }) +} diff --git a/packages/health/src/lib.rs b/packages/health/src/lib.rs new file mode 100644 index 000000000..3073fc0dd --- /dev/null +++ b/packages/health/src/lib.rs @@ -0,0 +1,2 @@ +pub mod health; +pub mod query; diff --git a/packages/health/src/query.rs b/packages/health/src/query.rs new file mode 100644 index 000000000..d651ea4d2 --- /dev/null +++ b/packages/health/src/query.rs @@ -0,0 +1,42 @@ +use cosmwasm_std::{Addr, Decimal, QuerierWrapper, StdResult}; +use mars_outpost::oracle::{self, PriceResponse}; +use mars_outpost::red_bank::{self, Market}; + +pub struct MarsQuerier<'a> { + querier: &'a QuerierWrapper<'a>, + oracle_addr: &'a Addr, + red_bank_addr: &'a Addr, +} + +impl<'a> MarsQuerier<'a> { + pub fn new( + querier: &'a QuerierWrapper, + oracle_addr: &'a Addr, + red_bank_addr: &'a Addr, + ) -> Self { + MarsQuerier { + querier, + oracle_addr, + red_bank_addr, + } + } + + pub fn query_market(&self, denom: &str) -> StdResult { + self.querier.query_wasm_smart( + self.red_bank_addr, + &red_bank::QueryMsg::Market { + denom: denom.to_string(), + }, + ) + } + + pub fn query_price(&self, denom: &str) -> StdResult { + let PriceResponse { price, .. } = self.querier.query_wasm_smart( + self.oracle_addr, + &oracle::QueryMsg::Price { + denom: denom.to_string(), + }, + )?; + Ok(price) + } +} diff --git a/packages/outpost/Cargo.toml b/packages/outpost/Cargo.toml new file mode 100644 index 000000000..168656a4e --- /dev/null +++ b/packages/outpost/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "mars-outpost" +version = "0.1.0" +authors = [ + "larry_0x ", + "Piotr Babel ", + "Spike Spiegel ", + "Ahmad Kaouk", + "Harry Scholes" +] +edition = "2021" +description = "Mars is a fully automated, on-chain credit protocol governed by a decentralised community of users and developers" +license = "GPL-3.0" +repository = "https://github.com/mars-protocol/outposts" +homepage = "https://marsprotocol.io" +documentation = "https://docs.marsprotocol.io/mars-protocol/developers/protocol-overview" +readme = "README.md" +keywords = ["mars", "cosmwasm"] + +[lib] +doctest = false + +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] + +[dependencies] +cosmwasm-std = "1.1" +cosmwasm-schema = "1.1" +thiserror = "1.0" diff --git a/packages/outpost/src/address_provider.rs b/packages/outpost/src/address_provider.rs new file mode 100644 index 000000000..6c893b7d9 --- /dev/null +++ b/packages/outpost/src/address_provider.rs @@ -0,0 +1,160 @@ +use std::any::type_name; +use std::fmt; +use std::str::FromStr; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::StdError; + +#[cw_serde] +#[derive(Copy, Eq, Hash)] +pub enum MarsAddressType { + Incentives, + Oracle, + RedBank, + RewardsCollector, + /// Protocol admin is an ICS-27 interchain account controlled by Mars Hub's x/gov module. + /// This account will take the owner and admin roles of outpost contracts. + /// + /// Owner means the account who can invoke certain priviliged execute methods on a contract, + /// such as updating the config. + /// Admin means the account who can migrate a contract. + ProtocolAdmin, + /// The `fee_collector` module account controlled by Mars Hub's x/distribution module. + /// Funds sent to this account will be distributed as staking rewards. + /// + /// NOTE: This is a Mars Hub address with the `mars` bech32 prefix, which may not be recognized + /// by the `api.addr_validate` method. + FeeCollector, + /// The module account controlled by the by Mars Hub's x/safety module. + /// Funds sent to this account will be deposited into the safety fund. + /// + /// NOTE: This is a Mars Hub address with the `mars` bech32 prefix, which may not be recognized + /// by the `api.addr_validate` method. + SafetyFund, +} + +impl fmt::Display for MarsAddressType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + MarsAddressType::FeeCollector => "fee_collector", + MarsAddressType::Incentives => "incentives", + MarsAddressType::Oracle => "oracle", + MarsAddressType::ProtocolAdmin => "protocol_admin", + MarsAddressType::RedBank => "red_bank", + MarsAddressType::RewardsCollector => "rewards_collector", + MarsAddressType::SafetyFund => "safety_fund", + }; + write!(f, "{}", s) + } +} + +impl FromStr for MarsAddressType { + type Err = StdError; + + fn from_str(s: &str) -> Result { + match s { + "fee_collector" => Ok(MarsAddressType::FeeCollector), + "incentives" => Ok(MarsAddressType::Incentives), + "oracle" => Ok(MarsAddressType::Oracle), + "protocol_admin" => Ok(MarsAddressType::ProtocolAdmin), + "red_bank" => Ok(MarsAddressType::RedBank), + "rewards_collector" => Ok(MarsAddressType::RewardsCollector), + "safety_fund" => Ok(MarsAddressType::SafetyFund), + _ => Err(StdError::parse_err(type_name::(), s)), + } + } +} + +/// Essentially, mars-address-provider is a required init param for all other contracts, so it needs +/// to be initialised first (Only owner can be set on initialization). So the deployment looks like +/// this: +/// +/// 1. Init the address provider +/// 2. Init all other contracts, passing in the address provider address (not ALL contracts need this +/// but many do) +/// 3. Update the address provider, with an update config call to contain all the other contract addresses +/// from step 2, this is why we need it to be owned by an EOA (externally owned account) - so we +/// can do this update as part of the deployment +/// 4. Update the owner of the address provider contract at the end of deployment to be either a. the +/// multisig or b. the gov/council contract +#[cw_serde] +pub struct InstantiateMsg { + /// The contract's owner + pub owner: String, + /// The address prefix of the chain this contract is deployed on + pub prefix: String, +} + +#[cw_serde] +pub enum ExecuteMsg { + /// Set address + SetAddress { + address_type: MarsAddressType, + address: String, + }, + /// Propose to transfer the contract's ownership to another account + TransferOwnership { new_owner: String }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// Get config + #[returns(Config)] + Config {}, + /// Get a single address + #[returns(AddressResponseItem)] + Address(MarsAddressType), + /// Get a list of addresses + #[returns(Vec)] + Addresses(Vec), + /// Query all stored addresses with pagination + #[returns(Vec)] + AllAddresses { + start_after: Option, + limit: Option, + }, +} + +pub type Config = InstantiateMsg; + +#[cw_serde] +pub struct AddressResponseItem { + /// The type of address + pub address_type: MarsAddressType, + /// Address value + pub address: String, +} + +pub mod helpers { + use std::collections::HashMap; + + use super::{AddressResponseItem, MarsAddressType, QueryMsg}; + use cosmwasm_std::{Addr, Deps, StdResult}; + + pub fn query_address( + deps: Deps, + address_provider_addr: &Addr, + contract: MarsAddressType, + ) -> StdResult { + let res: AddressResponseItem = deps + .querier + .query_wasm_smart(address_provider_addr, &QueryMsg::Address(contract))?; + + deps.api.addr_validate(&res.address) + } + + pub fn query_addresses( + deps: Deps, + address_provider_addr: &Addr, + contracts: Vec, + ) -> StdResult> { + let res: Vec = deps + .querier + .query_wasm_smart(address_provider_addr, &QueryMsg::Addresses(contracts))?; + + res.iter() + .map(|item| Ok((item.address_type, deps.api.addr_validate(&item.address)?))) + .collect() + } +} diff --git a/packages/outpost/src/error.rs b/packages/outpost/src/error.rs new file mode 100644 index 000000000..510f1ac8e --- /dev/null +++ b/packages/outpost/src/error.rs @@ -0,0 +1,71 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum MarsError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("All params should be available during instantiation")] + InstantiateParamsUnavailable {}, + + #[error("Incorrect number of addresses, expected {expected:?}, got {actual:?}")] + AddressesQueryWrongNumber { expected: u32, actual: u32 }, + + #[error("Invalid param: {param_name} is {invalid_value}, but it should be {predicate}")] + InvalidParam { + param_name: String, + invalid_value: String, + predicate: String, + }, + + #[error("Failed to deserialize RPC query response for: {target_type}")] + Deserialize { target_type: String }, +} + +impl From for StdError { + fn from(source: MarsError) -> Self { + match source { + MarsError::Std(e) => e, + e => StdError::generic_err(format!("{}", e)), + } + } +} + +// TESTS + +#[cfg(test)] +mod tests { + use super::*; + use crate::error::MarsError; + + #[test] + fn test_mars_error_to_std_error() { + { + let mars_error = MarsError::Unauthorized {}; + + let std_error: StdError = mars_error.into(); + + assert_eq!(std_error, StdError::generic_err("Unauthorized")) + } + + { + let mars_error = MarsError::Std(StdError::generic_err("Some error")); + + let std_error: StdError = mars_error.into(); + + assert_eq!(std_error, StdError::generic_err("Some error")) + } + + { + let mars_error = MarsError::Std(StdError::invalid_data_size(1, 2)); + + let std_error: StdError = mars_error.into(); + + assert_eq!(std_error, StdError::invalid_data_size(1, 2)) + } + } +} diff --git a/packages/outpost/src/helpers.rs b/packages/outpost/src/helpers.rs new file mode 100644 index 000000000..d385906df --- /dev/null +++ b/packages/outpost/src/helpers.rs @@ -0,0 +1,63 @@ +use cosmwasm_std::{coins, Addr, Api, BankMsg, CosmosMsg, Decimal, StdResult, Uint128}; + +use crate::error::MarsError; + +pub fn build_send_asset_msg(recipient_addr: &Addr, denom: &str, amount: Uint128) -> CosmosMsg { + CosmosMsg::Bank(BankMsg::Send { + to_address: recipient_addr.into(), + amount: coins(amount.u128(), denom), + }) +} + +/// Used when unwrapping an optional address sent in a contract call by a user. +/// Validates addreess if present, otherwise uses a given default value. +pub fn option_string_to_addr( + api: &dyn Api, + option_string: Option, + default: Addr, +) -> StdResult { + match option_string { + Some(input_addr) => api.addr_validate(&input_addr), + None => Ok(default), + } +} + +pub fn decimal_param_lt_one(param_value: Decimal, param_name: &str) -> Result<(), MarsError> { + if !param_value.lt(&Decimal::one()) { + Err(MarsError::InvalidParam { + param_name: param_name.to_string(), + invalid_value: param_value.to_string(), + predicate: "< 1".to_string(), + }) + } else { + Ok(()) + } +} + +pub fn decimal_param_le_one(param_value: Decimal, param_name: &str) -> Result<(), MarsError> { + if !param_value.le(&Decimal::one()) { + Err(MarsError::InvalidParam { + param_name: param_name.to_string(), + invalid_value: param_value.to_string(), + predicate: "<= 1".to_string(), + }) + } else { + Ok(()) + } +} + +pub fn integer_param_gt_zero(param_value: u64, param_name: &str) -> Result<(), MarsError> { + if !param_value.gt(&0) { + Err(MarsError::InvalidParam { + param_name: param_name.to_string(), + invalid_value: param_value.to_string(), + predicate: "> 0".to_string(), + }) + } else { + Ok(()) + } +} + +pub fn zero_address() -> Addr { + Addr::unchecked("") +} diff --git a/packages/outpost/src/incentives.rs b/packages/outpost/src/incentives.rs new file mode 100644 index 000000000..f379e2aeb --- /dev/null +++ b/packages/outpost/src/incentives.rs @@ -0,0 +1,95 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Decimal, Uint128}; + +/// Global configuration +#[cw_serde] +pub struct Config { + /// Contract owner + pub owner: Addr, + /// Address provider + pub address_provider: Addr, + /// Mars Token Denom + pub mars_denom: String, +} + +/// Incentive Metadata for a given incentive +#[cw_serde] +pub struct AssetIncentive { + /// How much MARS per second is emitted to be then distributed to all Red Bank depositors + pub emission_per_second: Uint128, + /// Total MARS assigned for distribution since the start of the incentive + pub index: Decimal, + /// Last time (in seconds) index was updated + pub last_updated: u64, +} + +/// Response to AssetIncentive query +#[cw_serde] +pub struct AssetIncentiveResponse { + /// Existing asset incentive for a given address. Will return None if it doesn't exist + pub asset_incentive: Option, +} + +#[cw_serde] +pub struct InstantiateMsg { + /// Contract owner + pub owner: String, + /// Address provider + pub address_provider: String, + /// Mars token denom + pub mars_denom: String, +} + +#[cw_serde] +pub enum ExecuteMsg { + /// Set emission per second for an asset to its depositor at Red Bank + SetAssetIncentive { + /// Asset denom associated with the incentives + denom: String, + /// How many MARS will be assigned per second to be distributed among all Red Bank + /// depositors + emission_per_second: Uint128, + }, + + /// Handle balance change updating user and asset rewards. + /// Sent from an external contract, triggered on user balance changes. + /// Will return an empty response if no incentive is applied for the asset + BalanceChange { + /// User address. Address is trusted as it must be validated by the Red Bank + /// contract before calling this method + user_addr: Addr, + /// Denom of the asset of which deposited balance is changed + denom: String, + /// The user's scaled collateral amount up to the instant before the change + user_amount_scaled_before: Uint128, + /// The market's total scaled collateral amount up to the instant before the change + total_amount_scaled_before: Uint128, + }, + + /// Claim rewards. MARS rewards accrued by the user will be staked into xMARS before + /// being sent. + ClaimRewards {}, + + /// Update contract config (only callable by owner) + UpdateConfig { + owner: Option, + address_provider: Option, + mars_denom: Option, + }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// Query contract config + #[returns(Config)] + Config {}, + + /// Query info about asset incentive for a given denom + #[returns(AssetIncentiveResponse)] + AssetIncentive { denom: String }, + + /// Query user current unclaimed rewards + #[returns(Uint128)] + UserUnclaimedRewards { user: String }, +} diff --git a/packages/outpost/src/lib.rs b/packages/outpost/src/lib.rs new file mode 100644 index 000000000..3b29cf38f --- /dev/null +++ b/packages/outpost/src/lib.rs @@ -0,0 +1,8 @@ +pub mod address_provider; +pub mod error; +pub mod helpers; +pub mod incentives; +pub mod math; +pub mod oracle; +pub mod red_bank; +pub mod rewards_collector; diff --git a/packages/outpost/src/math.rs b/packages/outpost/src/math.rs new file mode 100644 index 000000000..40098bac8 --- /dev/null +++ b/packages/outpost/src/math.rs @@ -0,0 +1,267 @@ +use std::convert::TryInto; + +use cosmwasm_std::{ + CheckedFromRatioError, Decimal, Fraction, OverflowError, OverflowOperation, StdError, + StdResult, Uint128, Uint256, +}; + +pub fn uint128_checked_div_with_ceil( + numerator: Uint128, + denominator: Uint128, +) -> StdResult { + let mut result = numerator.checked_div(denominator)?; + + if !numerator.checked_rem(denominator)?.is_zero() { + result += Uint128::from(1_u128); + } + + Ok(result) +} + +/// Divide 'a' by 'b'. +pub fn divide_decimal_by_decimal(a: Decimal, b: Decimal) -> StdResult { + Decimal::checked_from_ratio(a.numerator(), b.numerator()).map_err(|e| match e { + CheckedFromRatioError::Overflow => StdError::Overflow { + source: OverflowError { + operation: OverflowOperation::Mul, + operand1: a.numerator().to_string(), + operand2: a.denominator().to_string(), + }, + }, + CheckedFromRatioError::DivideByZero => StdError::DivideByZero { + source: cosmwasm_std::DivideByZeroError { + operand: b.to_string(), + }, + }, + }) +} + +/// Divide Uint128 by Decimal. +/// (Uint128 / numerator / denominator) is equal to (Uint128 * denominator / numerator). +pub fn divide_uint128_by_decimal(a: Uint128, b: Decimal) -> StdResult { + // (Uint128 / numerator / denominator) is equal to (Uint128 * denominator / numerator). + let numerator_u256 = a.full_mul(b.denominator()); + let denominator_u256 = Uint256::from(b.numerator()); + + let result_u256 = numerator_u256 / denominator_u256; + + let result = result_u256.try_into()?; + Ok(result) +} + +/// Divide Uint128 by Decimal, rounding up to the nearest integer. +pub fn divide_uint128_by_decimal_and_ceil(a: Uint128, b: Decimal) -> StdResult { + // (Uint128 / numerator / denominator) is equal to (Uint128 * denominator / numerator). + let numerator_u256 = a.full_mul(b.denominator()); + let denominator_u256 = Uint256::from(b.numerator()); + + let mut result_u256 = numerator_u256 / denominator_u256; + + if numerator_u256.checked_rem(denominator_u256)? > Uint256::zero() { + result_u256 += Uint256::from(1_u32); + } + + let result = result_u256.try_into()?; + Ok(result) +} + +/// Multiply Uint128 by Decimal, rounding up to the nearest integer. +pub fn multiply_uint128_by_decimal_and_ceil(a: Uint128, b: Decimal) -> StdResult { + let numerator_u256 = a.full_mul(b.numerator()); + let denominator_u256 = Uint256::from(b.denominator()); + + let mut result_u256 = numerator_u256 / denominator_u256; + + if numerator_u256.checked_rem(denominator_u256)? > Uint256::zero() { + result_u256 += Uint256::from(1_u32); + } + + let result = result_u256.try_into()?; + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::{ConversionOverflowError, OverflowOperation}; + use std::str::FromStr; + + const DECIMAL_FRACTIONAL: Uint128 = Uint128::new(1_000_000_000_000_000_000u128); // 1*10**18 + const DECIMAL_FRACTIONAL_SQUARED: Uint128 = + Uint128::new(1_000_000_000_000_000_000_000_000_000_000_000_000u128); // (1*10**18)**2 = 1*10**36 + + #[test] + fn test_uint128_checked_div_with_ceil() { + let a = Uint128::new(120u128); + let b = Uint128::zero(); + uint128_checked_div_with_ceil(a, b).unwrap_err(); + + let a = Uint128::new(120u128); + let b = Uint128::new(60_u128); + let c = uint128_checked_div_with_ceil(a, b).unwrap(); + assert_eq!(c, Uint128::new(2u128)); + + let a = Uint128::new(120u128); + let b = Uint128::new(119_u128); + let c = uint128_checked_div_with_ceil(a, b).unwrap(); + assert_eq!(c, Uint128::new(2u128)); + + let a = Uint128::new(120u128); + let b = Uint128::new(120_u128); + let c = uint128_checked_div_with_ceil(a, b).unwrap(); + assert_eq!(c, Uint128::new(1u128)); + + let a = Uint128::new(120u128); + let b = Uint128::new(121_u128); + let c = uint128_checked_div_with_ceil(a, b).unwrap(); + assert_eq!(c, Uint128::new(1u128)); + + let a = Uint128::zero(); + let b = Uint128::new(121_u128); + let c = uint128_checked_div_with_ceil(a, b).unwrap(); + assert_eq!(c, Uint128::zero()); + } + + #[test] + fn checked_decimal_division() { + let a = Decimal::from_ratio(99988u128, 100u128); + let b = Decimal::from_ratio(24997u128, 100u128); + let c = divide_decimal_by_decimal(a, b).unwrap(); + assert_eq!(c, Decimal::from_str("4.0").unwrap()); + + let a = Decimal::from_ratio(123456789u128, 1000000u128); + let b = Decimal::from_ratio(33u128, 1u128); + let c = divide_decimal_by_decimal(a, b).unwrap(); + assert_eq!(c, Decimal::from_str("3.741114818181818181").unwrap()); + + let a = Decimal::MAX; + let b = Decimal::MAX; + let c = divide_decimal_by_decimal(a, b).unwrap(); + assert_eq!(c, Decimal::one()); + + // Note: DivideByZeroError is not public so we just check if dividing by zero returns error + let a = Decimal::one(); + let b = Decimal::zero(); + divide_decimal_by_decimal(a, b).unwrap_err(); + + let a = Decimal::MAX; + let b = Decimal::from_ratio(1u128, DECIMAL_FRACTIONAL); + let res_error = divide_decimal_by_decimal(a, b).unwrap_err(); + assert_eq!( + res_error, + OverflowError::new(OverflowOperation::Mul, Uint128::MAX, DECIMAL_FRACTIONAL).into() + ); + } + + #[test] + fn test_divide_uint128_by_decimal() { + let a = Uint128::new(120u128); + let b = Decimal::from_ratio(120u128, 15u128); + let c = divide_uint128_by_decimal(a, b).unwrap(); + assert_eq!(c, Uint128::new(15u128)); + + let a = Uint128::new(DECIMAL_FRACTIONAL.u128()); + let b = Decimal::from_ratio(DECIMAL_FRACTIONAL.u128(), 1u128); + let c = divide_uint128_by_decimal(a, b).unwrap(); + assert_eq!(c, Uint128::new(1u128)); + + let a = Uint128::new(DECIMAL_FRACTIONAL.u128()); + let b = Decimal::from_ratio(1u128, DECIMAL_FRACTIONAL.u128()); + let c = divide_uint128_by_decimal(a, b).unwrap(); + assert_eq!(c, Uint128::new(DECIMAL_FRACTIONAL_SQUARED.u128())); + + let a = Uint128::MAX; + let b = Decimal::one(); + let c = divide_uint128_by_decimal(a, b).unwrap(); + assert_eq!(c, Uint128::MAX); + + let a = Uint128::new(1_000_000_000_000_000_000); + let b = Decimal::from_ratio(1u128, DECIMAL_FRACTIONAL); + let c = divide_uint128_by_decimal(a, b).unwrap(); + assert_eq!( + c, + Uint128::new(1_000_000_000_000_000_000_000_000_000_000_000_000) + ); + + // Division is truncated + let a = Uint128::new(100); + let b = Decimal::from_ratio(3u128, 1u128); + let c = divide_uint128_by_decimal(a, b).unwrap(); + assert_eq!(c, Uint128::new(33)); + + let a = Uint128::new(75); + let b = Decimal::from_ratio(100u128, 1u128); + let c = divide_uint128_by_decimal(a, b).unwrap(); + assert_eq!(c, Uint128::new(0)); + + // Overflow + let a = Uint128::MAX; + let b = Decimal::from_ratio(1_u128, 10_u128); + let res_error = divide_uint128_by_decimal(a, b).unwrap_err(); + assert_eq!( + res_error, + ConversionOverflowError::new( + "Uint256", + "Uint128", + "3402823669209384634633746074317682114550" + ) + .into() + ); + } + + #[test] + fn test_divide_uint128_by_decimal_and_ceil() { + let a = Uint128::new(120u128); + let b = Decimal::from_ratio(120u128, 15u128); + let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); + assert_eq!(c, Uint128::new(15u128)); + + let a = Uint128::new(DECIMAL_FRACTIONAL.u128()); + let b = Decimal::from_ratio(DECIMAL_FRACTIONAL.u128(), 1u128); + let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); + assert_eq!(c, Uint128::new(1u128)); + + let a = Uint128::new(DECIMAL_FRACTIONAL.u128()); + let b = Decimal::from_ratio(1u128, DECIMAL_FRACTIONAL.u128()); + let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); + assert_eq!(c, Uint128::new(DECIMAL_FRACTIONAL_SQUARED.u128())); + + let a = Uint128::MAX; + let b = Decimal::one(); + let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); + assert_eq!(c, Uint128::MAX); + + let a = Uint128::new(1_000_000_000_000_000_000); + let b = Decimal::from_ratio(1u128, DECIMAL_FRACTIONAL); + let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); + assert_eq!( + c, + Uint128::new(1_000_000_000_000_000_000_000_000_000_000_000_000) + ); + + // Division is rounded up + let a = Uint128::new(100); + let b = Decimal::from_ratio(3u128, 1u128); + let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); + assert_eq!(c, Uint128::new(34)); + + let a = Uint128::new(75); + let b = Decimal::from_ratio(100u128, 1u128); + let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); + assert_eq!(c, Uint128::new(1)); + + // Overflow + let a = Uint128::MAX; + let b = Decimal::from_ratio(1_u128, 10_u128); + let res_error = divide_uint128_by_decimal_and_ceil(a, b).unwrap_err(); + assert_eq!( + res_error, + ConversionOverflowError::new( + "Uint256", + "Uint128", + "3402823669209384634633746074317682114550" + ) + .into() + ); + } +} diff --git a/packages/outpost/src/oracle.rs b/packages/outpost/src/oracle.rs new file mode 100644 index 000000000..c969245e3 --- /dev/null +++ b/packages/outpost/src/oracle.rs @@ -0,0 +1,91 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Decimal; + +#[cw_serde] +pub struct Config { + /// The contract's owner, who can update config and price sources + pub owner: T, + /// The asset in which prices are denominated in + pub base_denom: String, +} + +pub type InstantiateMsg = Config; + +#[cw_serde] +pub enum ExecuteMsg { + /// Update contract config + UpdateConfig { owner: String }, + /// Specify the price source to be used for a coin + /// + /// NOTE: The input parameters for method are chain-specific. + SetPriceSource { denom: String, price_source: T }, + /// Remove price source for a coin + RemovePriceSource { denom: String }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// Query contract config. + #[returns(Config)] + Config {}, + /// Query a coin's price source. + /// + /// NOTE: The response type of this query is chain-specific. + #[returns(PriceSourceResponse)] + PriceSource { denom: String }, + /// Enumerate all coins' price sources. + /// + /// NOTE: The response type of this query is chain-specific. + #[returns(Vec>)] + PriceSources { + start_after: Option, + limit: Option, + }, + /// Query a coin's price. + /// + /// NOTE: This query may be dependent on block time (e.g. if the price source is TWAP), so may not + /// work properly with time travel queries on archive nodes. + #[returns(PriceResponse)] + Price { denom: String }, + /// Enumerate all coins' prices. + /// + /// NOTE: This query may be dependent on block time (e.g. if the price source is TWAP), so may not + /// work properly with time travel queries on archive nodes. + #[returns(Vec)] + Prices { + start_after: Option, + limit: Option, + }, +} + +#[cw_serde] +pub struct PriceSourceResponse { + pub denom: String, + pub price_source: T, +} + +#[cw_serde] +pub struct PriceResponse { + pub denom: String, + pub price: Decimal, +} + +pub mod helpers { + use super::{PriceResponse, QueryMsg}; + use cosmwasm_std::{Decimal, QuerierWrapper, StdResult}; + + pub fn query_price( + querier: &QuerierWrapper, + oracle: impl Into, + denom: impl Into, + ) -> StdResult { + let res: PriceResponse = querier.query_wasm_smart( + oracle.into(), + &QueryMsg::Price { + denom: denom.into(), + }, + )?; + Ok(res.price) + } +} diff --git a/packages/outpost/src/red_bank/interest_rate_model.rs b/packages/outpost/src/red_bank/interest_rate_model.rs new file mode 100644 index 000000000..adfdd35da --- /dev/null +++ b/packages/outpost/src/red_bank/interest_rate_model.rs @@ -0,0 +1,226 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Decimal, StdError, StdResult}; + +use crate::error::MarsError; +use crate::helpers::decimal_param_le_one; +use crate::math; + +#[cw_serde] +#[derive(Eq, Default)] +pub struct InterestRateModel { + /// Optimal utilization rate + pub optimal_utilization_rate: Decimal, + /// Base rate + pub base: Decimal, + /// Slope parameter for interest rate model function when utilization_rate < optimal_utilization_rate + pub slope_1: Decimal, + /// Slope parameter for interest rate model function when utilization_rate >= optimal_utilization_rate + pub slope_2: Decimal, +} + +impl InterestRateModel { + pub fn validate(&self) -> Result<(), MarsError> { + decimal_param_le_one(self.optimal_utilization_rate, "optimal_utilization_rate")?; + Ok(()) + } + + pub fn get_borrow_rate(&self, current_utilization_rate: Decimal) -> StdResult { + let new_borrow_rate = if current_utilization_rate <= self.optimal_utilization_rate { + if current_utilization_rate.is_zero() { + // prevent division by zero when optimal_utilization_rate is zero + self.base + } else { + // The borrow interest rates increase slowly with utilization + self.base + + self.slope_1.checked_mul(math::divide_decimal_by_decimal( + current_utilization_rate, + self.optimal_utilization_rate, + )?)? + } + } else { + // The borrow interest rates increase sharply with utilization + self.base + + self.slope_1 + + math::divide_decimal_by_decimal( + self.slope_2 + .checked_mul(current_utilization_rate - self.optimal_utilization_rate)?, + Decimal::one() - self.optimal_utilization_rate, + )? + }; + Ok(new_borrow_rate) + } + + pub fn get_liquidity_rate( + &self, + borrow_rate: Decimal, + current_utilization_rate: Decimal, + reserve_factor: Decimal, + ) -> StdResult { + borrow_rate + .checked_mul(current_utilization_rate)? + // This operation should not underflow as reserve_factor is checked to be <= 1 + .checked_mul(Decimal::one() - reserve_factor) + .map_err(StdError::from) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::red_bank::Market; + + #[test] + fn test_model_lifecycle() { + let optimal_utilization_rate = Decimal::percent(80); + let reserve_factor = Decimal::percent(20); + + let model = InterestRateModel { + optimal_utilization_rate, + base: Decimal::zero(), + slope_1: Decimal::percent(7), + slope_2: Decimal::percent(45), + }; + + let mut market = Market { + borrow_rate: Decimal::percent(10), + liquidity_rate: Decimal::zero(), + reserve_factor, + interest_rate_model: model.clone(), + ..Default::default() + }; + + let diff = Decimal::percent(10); + let utilization_rate = optimal_utilization_rate - diff; + + market.update_interest_rates(utilization_rate).unwrap(); + + let expected_borrow_rate = model.base + + math::divide_decimal_by_decimal( + model.slope_1.checked_mul(utilization_rate).unwrap(), + model.optimal_utilization_rate, + ) + .unwrap(); + + assert_eq!(market.borrow_rate, expected_borrow_rate); + assert_eq!( + market.liquidity_rate, + expected_borrow_rate + .checked_mul(utilization_rate) + .unwrap() + .checked_mul(Decimal::one() - reserve_factor) + .unwrap() + ); + } + + #[test] + fn test_interest_rates_calculation() { + let model = InterestRateModel { + optimal_utilization_rate: Decimal::percent(80), + base: Decimal::zero(), + slope_1: Decimal::percent(7), + slope_2: Decimal::percent(45), + }; + + // current utilization rate < optimal utilization rate + { + let current_utilization_rate = Decimal::percent(79); + let new_borrow_rate = model.get_borrow_rate(current_utilization_rate).unwrap(); + + let expected_borrow_rate = model.base + + math::divide_decimal_by_decimal( + model.slope_1.checked_mul(current_utilization_rate).unwrap(), + model.optimal_utilization_rate, + ) + .unwrap(); + + assert_eq!(new_borrow_rate, expected_borrow_rate); + } + + // current utilization rate == optimal utilization rate + { + let current_utilization_rate = Decimal::percent(80); + let new_borrow_rate = model.get_borrow_rate(current_utilization_rate).unwrap(); + + let expected_borrow_rate = model.base + + math::divide_decimal_by_decimal( + model.slope_1.checked_mul(current_utilization_rate).unwrap(), + model.optimal_utilization_rate, + ) + .unwrap(); + + assert_eq!(new_borrow_rate, expected_borrow_rate); + } + + // current utilization rate >= optimal utilization rate + { + let current_utilization_rate = Decimal::percent(81); + let new_borrow_rate = model.get_borrow_rate(current_utilization_rate).unwrap(); + + let expected_borrow_rate = model.base + + model.slope_1 + + math::divide_decimal_by_decimal( + model + .slope_2 + .checked_mul(current_utilization_rate - model.optimal_utilization_rate) + .unwrap(), + Decimal::one() - model.optimal_utilization_rate, + ) + .unwrap(); + + assert_eq!(new_borrow_rate, expected_borrow_rate); + } + + // current utilization rate == 100% and optimal utilization rate == 100% + { + let model = InterestRateModel { + optimal_utilization_rate: Decimal::percent(100), + base: Decimal::zero(), + slope_1: Decimal::percent(7), + slope_2: Decimal::zero(), + }; + + let current_utilization_rate = Decimal::percent(100); + let new_borrow_rate = model.get_borrow_rate(current_utilization_rate).unwrap(); + + let expected_borrow_rate = Decimal::percent(7); + + assert_eq!(new_borrow_rate, expected_borrow_rate); + } + + // current utilization rate == 0% and optimal utilization rate == 0% + { + let model = InterestRateModel { + optimal_utilization_rate: Decimal::percent(0), + base: Decimal::percent(2), + slope_1: Decimal::percent(7), + slope_2: Decimal::zero(), + }; + + let current_utilization_rate = Decimal::percent(0); + let new_borrow_rate = model.get_borrow_rate(current_utilization_rate).unwrap(); + + let expected_borrow_rate = Decimal::percent(2); + + assert_eq!(new_borrow_rate, expected_borrow_rate); + } + + // current utilization rate == 20% and optimal utilization rate == 0% + { + let model = InterestRateModel { + optimal_utilization_rate: Decimal::percent(0), + base: Decimal::percent(2), + slope_1: Decimal::percent(1), + slope_2: Decimal::percent(5), + }; + + let current_utilization_rate = Decimal::percent(20); + let new_borrow_rate = model.get_borrow_rate(current_utilization_rate).unwrap(); + + let expected_borrow_rate = model.base + + model.slope_1 + + model.slope_2.checked_mul(current_utilization_rate).unwrap(); + + assert_eq!(new_borrow_rate, expected_borrow_rate); + } + } +} diff --git a/packages/outpost/src/red_bank/market.rs b/packages/outpost/src/red_bank/market.rs new file mode 100644 index 000000000..831ec02e7 --- /dev/null +++ b/packages/outpost/src/red_bank/market.rs @@ -0,0 +1,128 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Decimal, StdResult, Uint128}; + +use crate::error::MarsError; +use crate::helpers::{decimal_param_le_one, decimal_param_lt_one}; +use crate::red_bank::InterestRateModel; + +#[cw_serde] +pub struct Market { + /// Denom of the asset + pub denom: String, + + /// Max base asset that can be borrowed per "base asset" collateral when using the asset as collateral + pub max_loan_to_value: Decimal, + /// Base asset amount in debt position per "base asset" of asset collateral that if surpassed makes the user's position liquidatable. + pub liquidation_threshold: Decimal, + /// Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral + /// from user in an amount equal to debt repayed + bonus) + pub liquidation_bonus: Decimal, + /// Portion of the borrow rate that is kept as protocol rewards + pub reserve_factor: Decimal, + + /// model (params + internal state) that defines how interest rate behaves + pub interest_rate_model: InterestRateModel, + + /// Borrow index (Used to compute borrow interest) + pub borrow_index: Decimal, + /// Liquidity index (Used to compute deposit interest) + pub liquidity_index: Decimal, + /// Rate charged to borrowers + pub borrow_rate: Decimal, + /// Rate paid to depositors + pub liquidity_rate: Decimal, + /// Timestamp (seconds) where indexes and where last updated + pub indexes_last_updated: u64, + + /// Total collateral scaled for the market's currency + pub collateral_total_scaled: Uint128, + /// Total debt scaled for the market's currency + pub debt_total_scaled: Uint128, + + /// If false cannot deposit + pub deposit_enabled: bool, + /// If false cannot borrow + pub borrow_enabled: bool, + /// Deposit Cap (defined in terms of the asset) + pub deposit_cap: Uint128, +} + +impl Default for Market { + fn default() -> Self { + Market { + denom: "".to_string(), + borrow_index: Decimal::one(), + liquidity_index: Decimal::one(), + borrow_rate: Decimal::zero(), + liquidity_rate: Decimal::zero(), + max_loan_to_value: Decimal::zero(), + reserve_factor: Decimal::zero(), + indexes_last_updated: 0, + collateral_total_scaled: Uint128::zero(), + debt_total_scaled: Uint128::zero(), + liquidation_threshold: Decimal::one(), + liquidation_bonus: Decimal::zero(), + interest_rate_model: InterestRateModel::default(), + deposit_enabled: true, + borrow_enabled: true, + // By default the cap should be unlimited (no cap) + deposit_cap: Uint128::MAX, + } + } +} + +impl Market { + pub fn validate(&self) -> Result<(), MarsError> { + decimal_param_lt_one(self.reserve_factor, "reserve_factor")?; + decimal_param_le_one(self.max_loan_to_value, "max_loan_to_value")?; + decimal_param_le_one(self.liquidation_threshold, "liquidation_threshold")?; + decimal_param_le_one(self.liquidation_bonus, "liquidation_bonus")?; + + // liquidation_threshold should be greater than max_loan_to_value + if self.liquidation_threshold <= self.max_loan_to_value { + return Err(MarsError::InvalidParam { + param_name: "liquidation_threshold".to_string(), + invalid_value: self.liquidation_threshold.to_string(), + predicate: format!("> {} (max LTV)", self.max_loan_to_value), + }); + } + + self.interest_rate_model.validate()?; + + Ok(()) + } + + pub fn update_interest_rates(&mut self, current_utilization_rate: Decimal) -> StdResult<()> { + self.borrow_rate = self + .interest_rate_model + .get_borrow_rate(current_utilization_rate)?; + + self.liquidity_rate = self.interest_rate_model.get_liquidity_rate( + self.borrow_rate, + current_utilization_rate, + self.reserve_factor, + )?; + + Ok(()) + } + + pub fn increase_collateral(&mut self, amount_scaled: Uint128) -> StdResult<()> { + self.collateral_total_scaled = self.collateral_total_scaled.checked_add(amount_scaled)?; + Ok(()) + } + + pub fn increase_debt(&mut self, amount_scaled: Uint128) -> StdResult<()> { + self.debt_total_scaled = self.debt_total_scaled.checked_add(amount_scaled)?; + Ok(()) + } + + pub fn decrease_collateral(&mut self, amount_scaled: Uint128) -> StdResult<()> { + self.collateral_total_scaled = self.collateral_total_scaled.checked_sub(amount_scaled)?; + Ok(()) + } + + pub fn decrease_debt(&mut self, amount_scaled: Uint128) -> StdResult<()> { + self.debt_total_scaled = self.debt_total_scaled.checked_sub(amount_scaled)?; + Ok(()) + } +} diff --git a/packages/outpost/src/red_bank/mod.rs b/packages/outpost/src/red_bank/mod.rs new file mode 100644 index 000000000..cc1a55a51 --- /dev/null +++ b/packages/outpost/src/red_bank/mod.rs @@ -0,0 +1,9 @@ +mod interest_rate_model; +mod market; +mod msg; +mod types; + +pub use interest_rate_model::*; +pub use market::*; +pub use msg::*; +pub use types::*; diff --git a/packages/outpost/src/red_bank/msg.rs b/packages/outpost/src/red_bank/msg.rs new file mode 100644 index 000000000..0ebb917d1 --- /dev/null +++ b/packages/outpost/src/red_bank/msg.rs @@ -0,0 +1,222 @@ +use crate::red_bank::InterestRateModel; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Decimal, Uint128}; + +#[cw_serde] +pub struct InstantiateMsg { + /// Market configuration + pub config: CreateOrUpdateConfig, +} + +#[cw_serde] +#[allow(clippy::large_enum_variant)] +pub enum ExecuteMsg { + /// Update contract config (only owner can call) + UpdateConfig { config: CreateOrUpdateConfig }, + + /// Initialize an asset on the money market (only owner can call) + InitAsset { + /// Asset related info + denom: String, + /// Asset parameters + params: InitOrUpdateAssetParams, + }, + + /// Update an asset on the money market (only owner can call) + UpdateAsset { + /// Asset related info + denom: String, + /// Asset parameters + params: InitOrUpdateAssetParams, + }, + + /// Update uncollateralized loan limit for a given user and asset. + /// Overrides previous value if any. A limit of zero means no + /// uncollateralized limit and the debt in that asset needs to be + /// collateralized (only owner can call) + UpdateUncollateralizedLoanLimit { + /// Address that receives the credit + user: String, + /// Asset the user receives the credit in + denom: String, + /// Limit for the uncolateralize loan. + new_limit: Uint128, + }, + + /// Deposit native coins. Deposited coins must be sent in the transaction + /// this call is made + Deposit { + /// Address that will receive the maTokens + on_behalf_of: Option, + }, + + /// Withdraw an amount of the asset burning an equivalent amount of maTokens. + Withdraw { + /// Asset to withdraw + denom: String, + /// Amount to be withdrawn. If None is specified, the full amount will be withdrawn. + amount: Option, + /// The address where the withdrawn amount is sent + recipient: Option, + }, + + /// Borrow native coins. If borrow allowed, amount is added to caller's debt + /// and sent to the address. + Borrow { + /// Asset to borrow + denom: String, + /// Amount to borrow + amount: Uint128, + /// The address where the borrowed amount is sent + recipient: Option, + }, + + /// Repay native coins loan. Coins used to repay must be sent in the + /// transaction this call is made. + Repay { + /// Repay the funds for the user + on_behalf_of: Option, + }, + + /// Liquidate under-collateralized native loans. Coins used to repay must be sent in the + /// transaction this call is made. + /// + /// The liquidator will receive collateral shares. To get the underlying asset, consider sending + /// a separate `withdraw` execute message. + Liquidate { + /// The address of the borrower getting liquidated + user: String, + /// Denom of the collateral asset, which liquidator gets from the borrower + collateral_denom: String, + /// The address for receiving underlying collateral + recipient: Option, + }, + + /// Update (enable / disable) asset as collateral for the caller + UpdateAssetCollateralStatus { + /// Asset to update status for + denom: String, + /// Option to enable (true) / disable (false) asset as collateral + enable: bool, + }, +} + +#[cw_serde] +pub struct CreateOrUpdateConfig { + pub owner: Option, + pub address_provider: Option, + pub close_factor: Option, +} + +#[cw_serde] +pub struct InitOrUpdateAssetParams { + /// Initial borrow rate + pub initial_borrow_rate: Option, + + /// Portion of the borrow rate that is kept as protocol rewards + pub reserve_factor: Option, + /// Max uusd that can be borrowed per uusd of collateral when using the asset as collateral + pub max_loan_to_value: Option, + /// uusd amount in debt position per uusd of asset collateral that if surpassed makes the user's position liquidatable. + pub liquidation_threshold: Option, + /// Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral + /// from user in an amount equal to debt repayed + bonus) + pub liquidation_bonus: Option, + + /// Interest rate strategy to calculate borrow_rate and liquidity_rate + pub interest_rate_model: Option, + + /// If false cannot deposit + pub deposit_enabled: Option, + /// If false cannot borrow + pub borrow_enabled: Option, + /// Deposit Cap defined in terms of the asset (Unlimited by default) + pub deposit_cap: Option, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// Get config + #[returns(crate::red_bank::ConfigResponse)] + Config {}, + + /// Get asset market + #[returns(crate::red_bank::Market)] + Market { denom: String }, + + /// Enumerate markets with pagination + #[returns(Vec)] + Markets { + start_after: Option, + limit: Option, + }, + + /// Get uncollateralized limit for given user and asset + #[returns(crate::red_bank::UncollateralizedLoanLimitResponse)] + UncollateralizedLoanLimit { user: String, denom: String }, + + /// Get all uncollateralized limits for a given user + #[returns(Vec)] + UncollateralizedLoanLimits { + user: String, + start_after: Option, + limit: Option, + }, + + /// Get user debt position for a specific asset + #[returns(crate::red_bank::UserDebtResponse)] + UserDebt { user: String, denom: String }, + + /// Get all debt positions for a user + #[returns(Vec)] + UserDebts { + user: String, + start_after: Option, + limit: Option, + }, + + /// Get user collateral position for a specific asset + #[returns(crate::red_bank::UserCollateralResponse)] + UserCollateral { user: String, denom: String }, + + /// Get all collateral positions for a user + #[returns(Vec)] + UserCollaterals { + user: String, + start_after: Option, + limit: Option, + }, + + /// Get user position + #[returns(crate::red_bank::UserPositionResponse)] + UserPosition { user: String }, + + /// Get liquidity scaled amount for a given underlying asset amount. + /// (i.e: how much scaled collateral is added if the given amount is deposited) + #[returns(Uint128)] + ScaledLiquidityAmount { denom: String, amount: Uint128 }, + + /// Get equivalent scaled debt for a given underlying asset amount. + /// (i.e: how much scaled debt is added if the given amount is borrowed) + #[returns(Uint128)] + ScaledDebtAmount { denom: String, amount: Uint128 }, + + /// Get underlying asset amount for a given asset and scaled amount. + /// (i.e. How much underlying asset will be released if withdrawing by burning a given scaled + /// collateral amount stored in state.) + #[returns(Uint128)] + UnderlyingLiquidityAmount { + denom: String, + amount_scaled: Uint128, + }, + + /// Get underlying debt amount for a given asset and scaled amounts. + /// (i.e: How much underlying asset needs to be repaid to cancel a given scaled debt + /// amount stored in state) + #[returns(Uint128)] + UnderlyingDebtAmount { + denom: String, + amount_scaled: Uint128, + }, +} diff --git a/packages/outpost/src/red_bank/types.rs b/packages/outpost/src/red_bank/types.rs new file mode 100644 index 000000000..39876d0d8 --- /dev/null +++ b/packages/outpost/src/red_bank/types.rs @@ -0,0 +1,117 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Decimal, Uint128}; + +use crate::error::MarsError; +use crate::helpers::decimal_param_le_one; + +/// Global configuration +#[cw_serde] +pub struct Config { + /// Contract owner + pub owner: T, + /// Address provider returns addresses for all protocol contracts + pub address_provider: T, + /// Maximum percentage of outstanding debt that can be covered by a liquidator + pub close_factor: Decimal, +} + +impl Config { + pub fn validate(&self) -> Result<(), MarsError> { + decimal_param_le_one(self.close_factor, "close_factor")?; + Ok(()) + } +} + +#[cw_serde] +#[derive(Default)] +pub struct Collateral { + /// Scaled collateral amount + pub amount_scaled: Uint128, + /// Whether this asset is enabled as collateral + /// + /// Set to true by default, unless the user explicitly disables it by invoking the + /// `update_asset_collateral_status` execute method. + /// + /// If disabled, the asset will not be subject to liquidation, but will not be considered when + /// evaluting the user's health factor either. + pub enabled: bool, +} + +/// Debt for each asset and user +#[cw_serde] +#[derive(Default)] +pub struct Debt { + /// Scaled debt amount + pub amount_scaled: Uint128, + /// Marker for uncollateralized debt + pub uncollateralized: bool, +} + +#[cw_serde] +pub enum UserHealthStatus { + NotBorrowing, + Borrowing { + max_ltv_hf: Decimal, + liq_threshold_hf: Decimal, + }, +} + +/// User asset settlement +#[derive(Default, Debug)] +pub struct Position { + pub denom: String, + pub collateral_amount: Uint128, + pub debt_amount: Uint128, + pub uncollateralized_debt: bool, + pub max_ltv: Decimal, + pub liquidation_threshold: Decimal, + pub asset_price: Decimal, +} + +pub type ConfigResponse = Config; + +#[cw_serde] +pub struct UncollateralizedLoanLimitResponse { + /// Asset denom + pub denom: String, + /// Uncollateralized loan limit in this asset + pub limit: Uint128, +} + +#[cw_serde] +pub struct UserDebtResponse { + /// Asset denom + pub denom: String, + /// Scaled debt amount stored in contract state + pub amount_scaled: Uint128, + /// Underlying asset amount that is actually owed at the current block + pub amount: Uint128, + /// Marker for uncollateralized debt + pub uncollateralized: bool, +} + +#[cw_serde] +pub struct UserCollateralResponse { + /// Asset denom + pub denom: String, + /// Scaled collateral amount stored in contract state + pub amount_scaled: Uint128, + /// Underlying asset amount that is actually deposited at the current block + pub amount: Uint128, + /// Wether the user is using asset as collateral or not + pub enabled: bool, +} + +#[cw_serde] +pub struct UserPositionResponse { + /// Total value of all enabled collateral assets. + /// If an asset is disabled as collateral, it will not be included in this value. + pub total_enabled_collateral: Decimal, + /// Total value of all collateralized debts. + /// If the user has an uncollateralized loan limit in an asset, the debt in this asset will not + /// be included in this value. + pub total_collateralized_debt: Decimal, + pub weighted_max_ltv_collateral: Decimal, + pub weighted_liquidation_threshold_collateral: Decimal, + pub health_status: UserHealthStatus, +} diff --git a/packages/outpost/src/rewards_collector.rs b/packages/outpost/src/rewards_collector.rs new file mode 100644 index 000000000..48954414b --- /dev/null +++ b/packages/outpost/src/rewards_collector.rs @@ -0,0 +1,179 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Api, Decimal, StdResult, Uint128}; + +use crate::error::MarsError; +use crate::helpers::{decimal_param_le_one, integer_param_gt_zero}; + +const MAX_SLIPPAGE_TOLERANCE_PERCENTAGE: u64 = 50; + +/// Global configuration +#[cw_serde] +pub struct Config { + /// Contract owner + pub owner: T, + /// Address provider returns addresses for all protocol contracts + pub address_provider: T, + /// Percentage of fees that are sent to the safety fund + pub safety_tax_rate: Decimal, + /// The asset to which the safety fund share is converted + pub safety_fund_denom: String, + /// The asset to which the fee collector share is converted + pub fee_collector_denom: String, + /// The channel ID of the mars hub + pub channel_id: String, + /// Revision number of Mars Hub's IBC client + pub timeout_revision: u64, + /// Number of blocks after which an IBC transfer is to be considered failed, if no acknowledgement is received + pub timeout_blocks: u64, + /// Number of seconds after which an IBC transfer is to be considered failed, if no acknowledgement is received + pub timeout_seconds: u64, + /// Maximum percentage of price movement (minimum amount you accept to receive during swap) + pub slippage_tolerance: Decimal, +} + +impl Config { + pub fn validate(&self) -> Result<(), MarsError> { + decimal_param_le_one(self.safety_tax_rate, "safety_tax_rate")?; + + integer_param_gt_zero(self.timeout_revision, "timeout_revision")?; + integer_param_gt_zero(self.timeout_blocks, "timeout_blocks")?; + integer_param_gt_zero(self.timeout_seconds, "timeout_seconds")?; + + if self.slippage_tolerance > Decimal::percent(MAX_SLIPPAGE_TOLERANCE_PERCENTAGE) { + return Err(MarsError::InvalidParam { + param_name: "slippage_tolerance".to_string(), + invalid_value: self.slippage_tolerance.to_string(), + predicate: format!("<= {}", Decimal::percent(MAX_SLIPPAGE_TOLERANCE_PERCENTAGE)), + }); + } + + Ok(()) + } +} + +impl Config { + pub fn check(&self, api: &dyn Api) -> StdResult> { + Ok(Config { + owner: api.addr_validate(&self.owner)?, + address_provider: api.addr_validate(&self.address_provider)?, + safety_tax_rate: self.safety_tax_rate, + safety_fund_denom: self.safety_fund_denom.clone(), + fee_collector_denom: self.fee_collector_denom.clone(), + channel_id: self.channel_id.clone(), + timeout_revision: self.timeout_revision, + timeout_blocks: self.timeout_blocks, + timeout_seconds: self.timeout_seconds, + slippage_tolerance: self.slippage_tolerance, + }) + } +} + +impl From> for Config { + fn from(cfg: Config) -> Self { + Self { + owner: cfg.owner.into(), + address_provider: cfg.address_provider.into(), + safety_tax_rate: cfg.safety_tax_rate, + safety_fund_denom: cfg.safety_fund_denom.clone(), + fee_collector_denom: cfg.fee_collector_denom.clone(), + channel_id: cfg.channel_id.clone(), + timeout_revision: cfg.timeout_revision, + timeout_blocks: cfg.timeout_blocks, + timeout_seconds: cfg.timeout_seconds, + slippage_tolerance: cfg.slippage_tolerance, + } + } +} + +#[cw_serde] +#[derive(Default)] +pub struct CreateOrUpdateConfig { + /// Contract owner + pub owner: Option, + /// Address provider returns addresses for all protocol contracts + pub address_provider: Option, + /// Percentage of fees that are sent to the safety fund + pub safety_tax_rate: Option, + /// The asset to which the safety fund share is converted + pub safety_fund_denom: Option, + /// The asset to which the fee collector share is converted + pub fee_collector_denom: Option, + /// The channel id of the mars hub + pub channel_id: Option, + /// Revision number of Mars Hub's IBC light client + pub timeout_revision: Option, + /// Number of blocks after which an IBC transfer is to be considered failed, if no acknowledgement is received + pub timeout_blocks: Option, + /// Number of seconds after which an IBC transfer is to be considered failed, if no acknowledgement is received + pub timeout_seconds: Option, + /// Maximum percentage of price movement (minimum amount you accept to receive during swap) + pub slippage_tolerance: Option, +} + +pub type InstantiateMsg = Config; + +#[cw_serde] +pub enum ExecuteMsg { + /// Update contract config + UpdateConfig { new_cfg: CreateOrUpdateConfig }, + + /// Configure the route for swapping an asset + /// + /// This is chain-specific, and can include parameters such as slippage tolerance and the routes + /// for multi-step swaps + SetRoute { + denom_in: String, + denom_out: String, + route: Route, + }, + + /// Withdraw maTokens from the red bank + WithdrawFromRedBank { + denom: String, + amount: Option, + }, + + /// Distribute the accrued protocol income between the safety fund and the fee modules on mars hub, + /// according to the split set in config. + /// Callable by any address. + DistributeRewards { + denom: String, + amount: Option, + }, + + /// Swap any asset on the contract + SwapAsset { + denom: String, + amount: Option, + }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// Get config parameters + #[returns(Config)] + Config {}, + /// Get routes for swapping an input denom into an output denom. + /// + /// NOTE: The response type of this query is chain-specific. + #[returns(RouteResponse)] + Route { denom_in: String, denom_out: String }, + /// Enumerate all swap routes. + /// + /// NOTE: The response type of this query is chain-specific. + #[returns(Vec>)] + Routes { + start_after: Option<(String, String)>, + limit: Option, + }, +} + +#[cw_serde] +pub struct RouteResponse { + pub denom_in: String, + pub denom_out: String, + pub route: Route, +} + +pub type RoutesResponse = Vec>; diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 7ab2e76c8..64f1de5e6 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -10,8 +10,8 @@ repository = "https://github.com/mars-protocol/rover" doctest = false [dependencies] -mars-health = { version = "0.1", path = "../../../outposts/packages/health" } -mars-outpost = { version = "0.1", path = "../../../outposts/packages/outpost" } +mars-health = { version = "0.1", path = "../../packages/health" } +mars-outpost = { version = "0.1", path = "../../packages/outpost" } mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } diff --git a/schema.Makefile.toml b/schema.Makefile.toml new file mode 100644 index 000000000..e8b2a972a --- /dev/null +++ b/schema.Makefile.toml @@ -0,0 +1,51 @@ +[tasks.generate-all-schemas] +script_runner = "@rust" +script = ''' +use std::fs; +use std::process::Command; + +fn main() -> std::io::Result<()> { + fs::remove_dir_all("schema"); + fs::remove_dir_all("schemas"); + fs::create_dir("schemas")?; + println!("Done"); + + let contracts = vec![ + "credit-manager", + "account-nft", + "swapper-base", + "mars-oracle-adapter", + "mock-red-bank", + "mock-vault", + "mock-oracle", + ]; + + for contract in contracts { + println!("{}", contract); + + let output = Command::new("cargo") + .arg("run") + .arg("--package") + .arg(contract) + .arg("--example") + .arg("schema") + .output() + .expect("failed to execute process"); + + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + + fs::create_dir(format!("schemas/{}", contract))?; + + fs::rename( + format!("schema/{}.json", contract), + format!("schemas/{}/{}.json", contract, contract), + )?; + } + + fs::remove_dir_all("schema"); + + Ok(()) +} +''' diff --git a/schemas/credit-manager/credit-manager.json b/schemas/credit-manager/credit-manager.json index 4a521f32c..bbdf51f5c 100644 --- a/schemas/credit-manager/credit-manager.json +++ b/schemas/credit-manager/credit-manager.json @@ -25,10 +25,10 @@ } }, "allowed_vaults": { - "description": "Whitelisted vaults approved by governance that implement credit manager's vault interface", + "description": "Whitelisted vaults approved by governance that implement credit manager's vault interface Includes a deposit cap that enforces a TLV limit for risk mitigation", "type": "array", "items": { - "$ref": "#/definitions/VaultBase_for_String" + "$ref": "#/definitions/VaultInstantiateConfig" } }, "max_close_factor": { @@ -78,6 +78,21 @@ }, "additionalProperties": false, "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, "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" @@ -91,6 +106,10 @@ "SwapperBase_for_String": { "type": "string" }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, "VaultBase_for_String": { "type": "object", "required": [ @@ -102,6 +121,46 @@ } }, "additionalProperties": false + }, + "VaultConfig": { + "type": "object", + "required": [ + "deposit_cap", + "liquidation_threshold", + "max_ltv", + "whitelisted" + ], + "properties": { + "deposit_cap": { + "$ref": "#/definitions/Coin" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv": { + "$ref": "#/definitions/Decimal" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "VaultInstantiateConfig": { + "type": "object", + "required": [ + "config", + "vault" + ], + "properties": { + "config": { + "$ref": "#/definitions/VaultConfig" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false } } }, @@ -248,21 +307,18 @@ "description": "Deposit coins into vault strategy", "type": "object", "required": [ - "vault_deposit" + "enter_vault" ], "properties": { - "vault_deposit": { + "enter_vault": { "type": "object", "required": [ - "coins", + "coin", "vault" ], "properties": { - "coins": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } + "coin": { + "$ref": "#/definitions/Coin" }, "vault": { "$ref": "#/definitions/VaultBase_for_String" @@ -277,10 +333,10 @@ "description": "Withdraw underlying coins from vault", "type": "object", "required": [ - "vault_withdraw" + "exit_vault" ], "properties": { - "vault_withdraw": { + "exit_vault": { "type": "object", "required": [ "amount", @@ -303,10 +359,10 @@ "description": "Requests unlocking of shares for a vault with a required lock period", "type": "object", "required": [ - "vault_request_unlock" + "request_vault_unlock" ], "properties": { - "vault_request_unlock": { + "request_vault_unlock": { "type": "object", "required": [ "amount", @@ -329,10 +385,10 @@ "description": "Withdraws the assets for unlocking position id from vault. Required time must have elapsed.", "type": "object", "required": [ - "vault_withdraw_unlocked" + "exit_vault_unlocked" ], "properties": { - "vault_withdraw_unlocked": { + "exit_vault_unlocked": { "type": "object", "required": [ "id", @@ -340,7 +396,9 @@ ], "properties": { "id": { - "$ref": "#/definitions/Uint128" + "type": "integer", + "format": "uint64", + "minimum": 0.0 }, "vault": { "$ref": "#/definitions/VaultBase_for_String" @@ -388,6 +446,36 @@ }, "additionalProperties": false }, + { + "description": "Pay back debt of a liquidatable rover account for a via liquidating a vault position. Similar to LiquidateCoin {} msg and will make similar adjustments to the request. The vault position will be withdrawn (and force withdrawn if a locked vault position) and the underlying assets will transferred to the liquidator.", + "type": "object", + "required": [ + "liquidate_vault" + ], + "properties": { + "liquidate_vault": { + "type": "object", + "required": [ + "debt_coin", + "liquidatee_account_id", + "request_vault" + ], + "properties": { + "debt_coin": { + "$ref": "#/definitions/Coin" + }, + "liquidatee_account_id": { + "type": "string" + }, + "request_vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %.", "type": "object", @@ -532,28 +620,25 @@ "additionalProperties": false }, { - "description": "Adds list of coins to a vault strategy", + "description": "Adds coin to a vault strategy", "type": "object", "required": [ - "vault_deposit" + "enter_vault" ], "properties": { - "vault_deposit": { + "enter_vault": { "type": "object", "required": [ "account_id", - "coins", + "coin", "vault" ], "properties": { "account_id": { "type": "string" }, - "coins": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } + "coin": { + "$ref": "#/definitions/Coin" }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" @@ -565,31 +650,25 @@ "additionalProperties": false }, { - "description": "Used to update the account balance of vault coins after a vault action has taken place", + "description": "Exchanges vault LP shares for assets", "type": "object", "required": [ - "update_vault_coin_balance" + "exit_vault" ], "properties": { - "update_vault_coin_balance": { + "exit_vault": { "type": "object", "required": [ "account_id", - "previous_total_balance", + "amount", "vault" ], "properties": { "account_id": { - "description": "Account that needs vault coin balance adjustment", "type": "string" }, - "previous_total_balance": { - "description": "Total vault coin balance in Rover", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] + "amount": { + "$ref": "#/definitions/Uint128" }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" @@ -601,25 +680,31 @@ "additionalProperties": false }, { - "description": "Exchanges vault LP shares for assets", + "description": "Used to update the account balance of vault coins after a vault action has taken place", "type": "object", "required": [ - "vault_withdraw" + "update_vault_coin_balance" ], "properties": { - "vault_withdraw": { + "update_vault_coin_balance": { "type": "object", "required": [ "account_id", - "amount", + "previous_total_balance", "vault" ], "properties": { "account_id": { + "description": "Account that needs vault coin balance adjustment", "type": "string" }, - "amount": { - "$ref": "#/definitions/Uint128" + "previous_total_balance": { + "description": "Total vault coin balance in Rover", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" @@ -634,10 +719,10 @@ "description": "A privileged action only to be used by Rover. Same as `VaultWithdraw` except it bypasses any lockup period restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation.", "type": "object", "required": [ - "vault_force_withdraw" + "force_exit_vault" ], "properties": { - "vault_force_withdraw": { + "force_exit_vault": { "type": "object", "required": [ "account_id", @@ -664,10 +749,10 @@ "description": "Requests unlocking of shares for a vault with a lock period", "type": "object", "required": [ - "vault_request_unlock" + "request_vault_unlock" ], "properties": { - "vault_request_unlock": { + "request_vault_unlock": { "type": "object", "required": [ "account_id", @@ -694,10 +779,10 @@ "description": "Withdraws assets from vault for a locked position having a lockup period that has been fulfilled", "type": "object", "required": [ - "vault_withdraw_unlocked" + "exit_vault_unlocked" ], "properties": { - "vault_withdraw_unlocked": { + "exit_vault_unlocked": { "type": "object", "required": [ "account_id", @@ -709,7 +794,9 @@ "type": "string" }, "position_id": { - "$ref": "#/definitions/Uint128" + "type": "integer", + "format": "uint64", + "minimum": 0.0 }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" @@ -755,24 +842,31 @@ "additionalProperties": false }, { - "description": "Determine health factor improved as a consequence of liquidation event", "type": "object", "required": [ - "assert_health_factor_improved" + "liquidate_vault" ], "properties": { - "assert_health_factor_improved": { + "liquidate_vault": { "type": "object", "required": [ - "account_id", - "previous_health_factor" + "debt_coin", + "liquidatee_account_id", + "liquidator_account_id", + "request_vault" ], "properties": { - "account_id": { + "debt_coin": { + "$ref": "#/definitions/Coin" + }, + "liquidatee_account_id": { "type": "string" }, - "previous_health_factor": { - "$ref": "#/definitions/Decimal" + "liquidator_account_id": { + "type": "string" + }, + "request_vault": { + "$ref": "#/definitions/VaultBase_for_Addr" } }, "additionalProperties": false @@ -881,15 +975,6 @@ "type": "string" } }, - "allowed_vaults": { - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/VaultBase_for_String" - } - }, "max_close_factor": { "anyOf": [ { @@ -945,6 +1030,15 @@ "type": "null" } ] + }, + "vault_configs": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/VaultInstantiateConfig" + } } }, "additionalProperties": false @@ -989,6 +1083,46 @@ } }, "additionalProperties": false + }, + "VaultConfig": { + "type": "object", + "required": [ + "deposit_cap", + "liquidation_threshold", + "max_ltv", + "whitelisted" + ], + "properties": { + "deposit_cap": { + "$ref": "#/definitions/Coin" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv": { + "$ref": "#/definitions/Decimal" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "VaultInstantiateConfig": { + "type": "object", + "required": [ + "config", + "vault" + ], + "properties": { + "config": { + "$ref": "#/definitions/VaultConfig" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false } } }, @@ -1011,13 +1145,13 @@ "additionalProperties": false }, { - "description": "Whitelisted vaults", + "description": "Configs on vaults", "type": "object", "required": [ - "allowed_vaults" + "vault_configs" ], "properties": { - "allowed_vaults": { + "vault_configs": { "type": "object", "properties": { "limit": { @@ -1512,10 +1646,35 @@ "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" }, + "LockingVaultAmount": { + "type": "object", + "required": [ + "locked", + "unlocking" + ], + "properties": { + "locked": { + "$ref": "#/definitions/VaultAmount" + }, + "unlocking": { + "$ref": "#/definitions/UnlockingPositions" + } + }, + "additionalProperties": false + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" }, + "UnlockingPositions": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultUnlockingPosition" + } + }, + "VaultAmount": { + "$ref": "#/definitions/Uint128" + }, "VaultBase_for_Addr": { "type": "object", "required": [ @@ -1531,12 +1690,12 @@ "VaultPosition": { "type": "object", "required": [ - "state", + "amount", "vault" ], "properties": { - "state": { - "$ref": "#/definitions/VaultPositionState" + "amount": { + "$ref": "#/definitions/VaultPositionAmountBase_for_VaultAmount_and_LockingVaultAmount" }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" @@ -1544,6 +1703,34 @@ }, "additionalProperties": false }, + "VaultPositionAmountBase_for_VaultAmount_and_LockingVaultAmount": { + "oneOf": [ + { + "type": "object", + "required": [ + "unlocked" + ], + "properties": { + "unlocked": { + "$ref": "#/definitions/VaultAmount" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "locking" + ], + "properties": { + "locking": { + "$ref": "#/definitions/LockingVaultAmount" + } + }, + "additionalProperties": false + } + ] + }, "VaultPositionResponseItem": { "type": "object", "required": [ @@ -1560,30 +1747,7 @@ }, "additionalProperties": false }, - "VaultPositionState": { - "type": "object", - "required": [ - "locked", - "unlocked", - "unlocking" - ], - "properties": { - "locked": { - "$ref": "#/definitions/Uint128" - }, - "unlocked": { - "$ref": "#/definitions/Uint128" - }, - "unlocking": { - "type": "array", - "items": { - "$ref": "#/definitions/VaultUnlockingId" - } - } - }, - "additionalProperties": false - }, - "VaultUnlockingId": { + "VaultUnlockingPosition": { "type": "object", "required": [ "amount", @@ -1600,11 +1764,9 @@ }, "id": { "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] + "type": "integer", + "format": "uint64", + "minimum": 0.0 } }, "additionalProperties": false @@ -1619,28 +1781,6 @@ "type": "string" } }, - "allowed_vaults": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_VaultBase_for_String", - "type": "array", - "items": { - "$ref": "#/definitions/VaultBase_for_String" - }, - "definitions": { - "VaultBase_for_String": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - } - }, "config": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ConfigResponse", @@ -1831,10 +1971,35 @@ }, "additionalProperties": false }, + "LockingVaultAmount": { + "type": "object", + "required": [ + "locked", + "unlocking" + ], + "properties": { + "locked": { + "$ref": "#/definitions/VaultAmount" + }, + "unlocking": { + "$ref": "#/definitions/UnlockingPositions" + } + }, + "additionalProperties": false + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" }, + "UnlockingPositions": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultUnlockingPosition" + } + }, + "VaultAmount": { + "$ref": "#/definitions/Uint128" + }, "VaultBase_for_Addr": { "type": "object", "required": [ @@ -1850,12 +2015,12 @@ "VaultPosition": { "type": "object", "required": [ - "state", + "amount", "vault" ], "properties": { - "state": { - "$ref": "#/definitions/VaultPositionState" + "amount": { + "$ref": "#/definitions/VaultPositionAmountBase_for_VaultAmount_and_LockingVaultAmount" }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" @@ -1863,30 +2028,35 @@ }, "additionalProperties": false }, - "VaultPositionState": { - "type": "object", - "required": [ - "locked", - "unlocked", - "unlocking" - ], - "properties": { - "locked": { - "$ref": "#/definitions/Uint128" - }, - "unlocked": { - "$ref": "#/definitions/Uint128" + "VaultPositionAmountBase_for_VaultAmount_and_LockingVaultAmount": { + "oneOf": [ + { + "type": "object", + "required": [ + "unlocked" + ], + "properties": { + "unlocked": { + "$ref": "#/definitions/VaultAmount" + } + }, + "additionalProperties": false }, - "unlocking": { - "type": "array", - "items": { - "$ref": "#/definitions/VaultUnlockingId" - } + { + "type": "object", + "required": [ + "locking" + ], + "properties": { + "locking": { + "$ref": "#/definitions/LockingVaultAmount" + } + }, + "additionalProperties": false } - }, - "additionalProperties": false + ] }, - "VaultUnlockingId": { + "VaultUnlockingPosition": { "type": "object", "required": [ "amount", @@ -1903,11 +2073,9 @@ }, "id": { "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] + "type": "integer", + "format": "uint64", + "minimum": 0.0 } }, "additionalProperties": false @@ -1943,6 +2111,91 @@ "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" + }, + "vault_configs": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_VaultInstantiateConfig", + "type": "array", + "items": { + "$ref": "#/definitions/VaultInstantiateConfig" + }, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VaultBase_for_String": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + }, + "VaultConfig": { + "type": "object", + "required": [ + "deposit_cap", + "liquidation_threshold", + "max_ltv", + "whitelisted" + ], + "properties": { + "deposit_cap": { + "$ref": "#/definitions/Coin" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv": { + "$ref": "#/definitions/Decimal" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "VaultInstantiateConfig": { + "type": "object", + "required": [ + "config", + "vault" + ], + "properties": { + "config": { + "$ref": "#/definitions/VaultConfig" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + } + } } } } diff --git a/schemas/mars-oracle-adapter/mars-oracle-adapter.json b/schemas/mars-oracle-adapter/mars-oracle-adapter.json index ff7d93cb5..7cd08574d 100644 --- a/schemas/mars-oracle-adapter/mars-oracle-adapter.json +++ b/schemas/mars-oracle-adapter/mars-oracle-adapter.json @@ -44,18 +44,22 @@ "type": "object", "required": [ "addr", - "denom", - "method" + "method", + "req_denom", + "vault_coin_denom" ], "properties": { "addr": { "$ref": "#/definitions/Addr" }, - "denom": { - "type": "string" - }, "method": { "$ref": "#/definitions/PricingMethod" + }, + "req_denom": { + "type": "string" + }, + "vault_coin_denom": { + "type": "string" } }, "additionalProperties": false @@ -137,18 +141,22 @@ "type": "object", "required": [ "addr", - "denom", - "method" + "method", + "req_denom", + "vault_coin_denom" ], "properties": { "addr": { "$ref": "#/definitions/Addr" }, - "denom": { - "type": "string" - }, "method": { "$ref": "#/definitions/PricingMethod" + }, + "req_denom": { + "type": "string" + }, + "vault_coin_denom": { + "type": "string" } }, "additionalProperties": false @@ -160,6 +168,7 @@ "title": "QueryMsg", "oneOf": [ { + "description": "If denom is vault coin, will retrieve priceable underlying before querying oracle", "type": "object", "required": [ "price" @@ -180,6 +189,28 @@ }, "additionalProperties": false }, + { + "description": "Converts vault coin to the mars-oracle accepted priceable coins", + "type": "object", + "required": [ + "priceable_underlying" + ], + "properties": { + "priceable_underlying": { + "type": "object", + "required": [ + "coin" + ], + "properties": { + "coin": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -243,7 +274,28 @@ }, "additionalProperties": false } - ] + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } }, "migrate": null, "sudo": null, @@ -270,18 +322,22 @@ "type": "object", "required": [ "addr", - "denom", - "method" + "method", + "req_denom", + "vault_coin_denom" ], "properties": { "addr": { "$ref": "#/definitions/Addr" }, - "denom": { - "type": "string" - }, "method": { "$ref": "#/definitions/PricingMethod" + }, + "req_denom": { + "type": "string" + }, + "vault_coin_denom": { + "type": "string" } }, "additionalProperties": false @@ -339,24 +395,57 @@ } } }, + "priceable_underlying": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Coin", + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + }, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "pricing_info": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "VaultPricingInfo", "type": "object", "required": [ "addr", - "denom", - "method" + "method", + "req_denom", + "vault_coin_denom" ], "properties": { "addr": { "$ref": "#/definitions/Addr" }, - "denom": { - "type": "string" - }, "method": { "$ref": "#/definitions/PricingMethod" + }, + "req_denom": { + "type": "string" + }, + "vault_coin_denom": { + "type": "string" } }, "additionalProperties": false, diff --git a/schemas/mock-oracle/mock-oracle.json b/schemas/mock-oracle/mock-oracle.json index 8a85c9b34..5339b4d84 100644 --- a/schemas/mock-oracle/mock-oracle.json +++ b/schemas/mock-oracle/mock-oracle.json @@ -7,10 +7,10 @@ "title": "InstantiateMsg", "type": "object", "required": [ - "coins" + "prices" ], "properties": { - "coins": { + "prices": { "type": "array", "items": { "$ref": "#/definitions/CoinPrice" diff --git a/schemas/mock-vault/mock-vault.json b/schemas/mock-vault/mock-vault.json index a55639709..182b23b85 100644 --- a/schemas/mock-vault/mock-vault.json +++ b/schemas/mock-vault/mock-vault.json @@ -7,37 +7,70 @@ "title": "InstantiateMsg", "type": "object", "required": [ - "asset_denoms", - "lp_token_denom", - "oracle" + "oracle", + "req_denom", + "vault_token_denom" ], "properties": { - "asset_denoms": { - "description": "Denoms for assets in vault", - "type": "array", - "items": { - "type": "string" - } - }, "lockup": { - "description": "Time in seconds for unlock period", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "lp_token_denom": { - "description": "Denom for vault LP share token", - "type": "string" + "description": "Duration of unlock period", + "anyOf": [ + { + "$ref": "#/definitions/Duration" + }, + { + "type": "null" + } + ] }, "oracle": { "$ref": "#/definitions/OracleBase_for_String" + }, + "req_denom": { + "description": "Denom required for entry. Also denom received on withdraw.", + "type": "string" + }, + "vault_token_denom": { + "description": "Denom for vault token", + "type": "string" } }, "additionalProperties": false, "definitions": { + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "OracleBase_for_String": { "type": "string" } @@ -46,10 +79,9 @@ "execute": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", - "description": "Partial compatibility with EIP-4626", "oneOf": [ { - "description": "Enters list of `Vec` into a vault strategy in exchange for vault tokens.", + "description": "Called to deposit into the vault. Native assets are passed in the funds parameter.", "type": "object", "required": [ "deposit" @@ -57,77 +89,221 @@ "properties": { "deposit": { "type": "object", + "properties": { + "recipient": { + "description": "The optional recipient of the vault token. If not set, the caller address will be used instead.", + "type": [ + "string", + "null" + ] + } + }, "additionalProperties": false } }, "additionalProperties": false }, { - "description": "Withdraw underlying coins in vault by exchanging vault `Coin`", + "description": "Called to redeem vault tokens and receive assets back from the vault. The native vault token must be passed in the funds parameter, unless the lockup extension is called, in which case the vault token has already been passed to ExecuteMsg::Unlock.", "type": "object", "required": [ - "withdraw" + "redeem" ], "properties": { - "withdraw": { + "redeem": { "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "description": "The amount of vault tokens sent to the contract. In the case that the vault token is a Cosmos native denom, we of course have this information in the info.funds, but if the vault implements the Cw4626 API, then we need this argument. We figured it's better to have one API for both types of vaults, so we require this argument.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "recipient": { + "description": "An optional field containing which address should receive the withdrawn underlying assets. If not set, the caller address will be used instead.", + "type": [ + "string", + "null" + ] + } + }, "additionalProperties": false } }, "additionalProperties": false }, { - "description": "A privileged action only to be used by Rover. Same as `Withdraw` except it bypasses any lockup period restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation.", + "description": "Support for custom extensions", "type": "object", "required": [ - "force_withdraw" + "vault_extension" ], "properties": { - "force_withdraw": { - "type": "object", - "additionalProperties": false + "vault_extension": { + "$ref": "#/definitions/ExtensionExecuteMsg" } }, "additionalProperties": false - }, - { - "description": "Some vaults have lockup periods (typically between 1-14 days). This action sends vault `Coin` which is locked for vault lockup period and available to `Unlock` after that time has elapsed. On response, vault sends back `unlocking_position_created` event with attribute `id` representing the new unlocking coins position.", - "type": "object", - "required": [ - "request_unlock" - ], - "properties": { - "request_unlock": { + } + ], + "definitions": { + "ExtensionExecuteMsg": { + "description": "Contains ExecuteMsgs of all enabled extensions. To enable extensions defined outside of this create, you can define your own `ExtensionExecuteMsg` type in your contract crate and pass it in as the generic parameter to ExecuteMsg", + "oneOf": [ + { "type": "object", + "required": [ + "lockup" + ], + "properties": { + "lockup": { + "$ref": "#/definitions/LockupExecuteMsg" + } + }, "additionalProperties": false } - }, - "additionalProperties": false + ] }, - { - "description": "Withdraw assets in vault that have been unlocked for given unlocking position", - "type": "object", - "required": [ - "withdraw_unlocked" - ], - "properties": { - "withdraw_unlocked": { + "LockupExecuteMsg": { + "oneOf": [ + { + "description": "Unlock is called to initiate unlocking a locked position held by the vault. The caller must pass the native vault tokens in the funds field. Emits an event with type `UNLOCK_EVENT_TYPE` with an attribute with key `UNLOCKING_POSITION_ATTR_KEY` containing an u64 lockup_id. Also encodes the u64 lockup ID as binary and returns it in the Response's data field, so that it can be read by SubMsg replies.\n\nLike Redeem, this takes an amount so that the same API can be used for CW4626 and native tokens.", "type": "object", "required": [ - "id" + "unlock" ], "properties": { - "id": { - "$ref": "#/definitions/Uint128" + "unlock": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw an unlocking position that has finished unlocking.", + "type": "object", + "required": [ + "withdraw_unlocked" + ], + "properties": { + "withdraw_unlocked": { + "type": "object", + "required": [ + "lockup_id" + ], + "properties": { + "lockup_id": { + "description": "The ID of the expired lockup to withdraw from. If None is passed, the vault will attempt to withdraw all expired lockup positions. Note that this can fail if there are too many lockup positions and the `max_contract_gas` limit is hit.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "recipient": { + "description": "An optional field containing which address should receive the withdrawn underlying assets. If not set, the caller address will be used instead.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Can be called by whitelisted addresses to bypass the lockup and immediately return the underlying assets. Used in the event of liquidation. The caller must pass the native vault tokens in the funds field.", + "type": "object", + "required": [ + "force_withdraw" + ], + "properties": { + "force_withdraw": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "description": "The amount of vault tokens to force unlock.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "recipient": { + "description": "The address which should receive the withdrawn assets. If not set, the caller address will be used instead.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Force withdraw from a position that is already unlocking (Unlock has already been called).", + "type": "object", + "required": [ + "force_withdraw_unlocking" + ], + "properties": { + "force_withdraw_unlocking": { + "type": "object", + "required": [ + "lockup_id" + ], + "properties": { + "amount": { + "description": "Optional amounts of each underlying asset to be force withdrawn. If None is passed, the entire position will be force withdrawn. Vaults MAY require the ratio of assets to be the same as the ratio in the `deposit_assets` field returned by the `VaultInfo` query.", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "lockup_id": { + "description": "The ID of the unlocking position from which to force withdraw", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "recipient": { + "description": "The address which should receive the withdrawn assets. If not set, the assets will be sent to the caller.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false } }, "additionalProperties": false } - }, - "additionalProperties": false - } - ], - "definitions": { + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -139,7 +315,21 @@ "title": "QueryMsg", "oneOf": [ { - "description": "Vault requirements, lockup, & vault token denom", + "description": "Returns `VaultStandardInfo` with information on the version of the vault standard used as well as any enabled extensions.", + "type": "object", + "required": [ + "vault_standard_info" + ], + "properties": { + "vault_standard_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns `VaultInfo` representing vault requirements, lockup, & vault token denom.", "type": "object", "required": [ "info" @@ -153,7 +343,32 @@ "additionalProperties": false }, { - "description": "All the coins that would be redeemed for in exchange for vault coins. Used by Rover to calculate vault position values.", + "description": "Returns `Uint128` amount of vault tokens that will be returned for the passed in assets.\n\nAllows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions.\n\nMUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called in the same transaction.\n\nMUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the deposit would be accepted, regardless if the user has enough tokens approved, etc.\n\nMUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.", + "type": "object", + "required": [ + "preview_deposit" + ], + "properties": { + "preview_deposit": { + "type": "object", + "required": [ + "coins" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns `AssetsResponse` representing all the assets that would be redeemed in exchange for vault tokens. Used by Rover to calculate vault position values.", "type": "object", "required": [ "preview_redeem" @@ -175,33 +390,41 @@ "additionalProperties": false }, { - "description": "Returns the total vault coins issued. In order to prevent Cream-attack, we cannot query the bank module for this amount.", + "description": "Returns `Option` maximum amount of assets that can be deposited into the Vault for the `recipient`, through a call to Deposit.\n\nMUST return the maximum amount of assets deposit would allow to be deposited for `recipient` and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary). This assumes that the user has infinite assets, i.e. MUST NOT rely on the asset balances of `recipient`.\n\nMUST factor in both global and user-specific limits, like if deposits are entirely disabled (even temporarily) it MUST return 0.", "type": "object", "required": [ - "total_vault_coins_issued" + "max_deposit" ], "properties": { - "total_vault_coins_issued": { + "max_deposit": { "type": "object", + "required": [ + "recipient" + ], + "properties": { + "recipient": { + "type": "string" + } + }, "additionalProperties": false } }, "additionalProperties": false }, { - "description": "The vault positions that this address has requested to unlock", + "description": "Returns `Option` maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, through a call to Withdraw\n\nTODO: Keep this? Could potentially be combined with MaxWithdraw to return a MaxWithdrawResponse type that includes both max assets that can be withdrawn as well as max vault shares that can be withdrawn in exchange for assets.", "type": "object", "required": [ - "unlocking_positions_for_addr" + "max_redeem" ], "properties": { - "unlocking_positions_for_addr": { + "max_redeem": { "type": "object", "required": [ - "addr" + "owner" ], "properties": { - "addr": { + "owner": { "type": "string" } }, @@ -211,18 +434,72 @@ "additionalProperties": false }, { + "description": "Returns `AssetsResponse` assets managed by vault. Useful for display purposes, and does not have to confer the exact amount of underlying assets.", "type": "object", "required": [ - "unlocking_position" + "total_assets" ], "properties": { - "unlocking_position": { + "total_assets": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns `Uint128` total amount of vault tokens in circulation.", + "type": "object", + "required": [ + "total_vault_token_supply" + ], + "properties": { + "total_vault_token_supply": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The amount of shares that the vault would exchange for the amount of assets provided, in an ideal scenario where all the conditions are met.\n\nUseful for display purposes and does not have to confer the exact amount of shares returned by the vault if the passed in assets were deposited. This calculation may not reflect the “per-user” price-per-share, and instead should reflect the “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and from.", + "type": "object", + "required": [ + "convert_to_shares" + ], + "properties": { + "convert_to_shares": { + "type": "object", + "required": [ + "coins" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns `AssetsResponse` assets that the Vault would exchange for the amount of shares provided, in an ideal scenario where all the conditions are met.\n\nUseful for display purposes and does not have to confer the exact amount of assets returned by the vault if the passed in shares were withdrawn. This calculation may not reflect the “per-user” price-per-share, and instead should reflect the “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and from.", + "type": "object", + "required": [ + "convert_to_assets" + ], + "properties": { + "convert_to_assets": { "type": "object", "required": [ - "id" + "shares" ], "properties": { - "id": { + "shares": { "$ref": "#/definitions/Uint128" } }, @@ -230,9 +507,137 @@ } }, "additionalProperties": false + }, + { + "description": "TODO: How to handle return derive? We must supply a type here, but we don't know it.", + "type": "object", + "required": [ + "vault_extension" + ], + "properties": { + "vault_extension": { + "$ref": "#/definitions/ExtensionQueryMsg" + } + }, + "additionalProperties": false } ], "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "ExtensionQueryMsg": { + "description": "Contains QueryMsgs of all enabled extensions. To enable extensions defined outside of this create, you can define your own `ExtensionQueryMsg` type in your contract crate and pass it in as the generic parameter to QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "lockup" + ], + "properties": { + "lockup": { + "$ref": "#/definitions/LockupQueryMsg" + } + }, + "additionalProperties": false + } + ] + }, + "LockupQueryMsg": { + "oneOf": [ + { + "description": "Returns a `Vec` containing all the currently unclaimed lockup positions for the `owner`.", + "type": "object", + "required": [ + "lockups" + ], + "properties": { + "lockups": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "description": "Max amount of results to return", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "description": "The address of the owner of the lockup", + "type": "string" + }, + "start_after": { + "description": "Return results only after this lockup_id", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns `Lockup` info about a specific lockup, by owner and ID.", + "type": "object", + "required": [ + "lockup" + ], + "properties": { + "lockup": { + "type": "object", + "required": [ + "lockup_id" + ], + "properties": { + "lockup_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns `cw_utils::Duration` duration of the lockup.", + "type": "object", + "required": [ + "lockup_duration" + ], + "properties": { + "lockup_duration": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -242,49 +647,91 @@ "migrate": null, "sudo": null, "responses": { - "info": { + "convert_to_assets": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VaultInfo", + "title": "AssetsResponse", "type": "object", "required": [ - "accepts", - "vault_coin_denom" + "coin" ], "properties": { - "accepts": { - "description": "Coin denoms required to enter vault. Multiple vectors indicate the vault accepts more than one combination to enter.", - "type": "array", - "items": { - "type": "array", - "items": { + "coin": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { "type": "string" } } }, - "lockup": { - "description": "Time in seconds for unlock period", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 + "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" + } + } + }, + "convert_to_shares": { + "$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" + }, + "info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VaultInfo", + "description": "Returned by QueryMsg::Info and contains information about this vault", + "type": "object", + "required": [ + "req_denom", + "vault_token_denom" + ], + "properties": { + "req_denom": { + "type": "string" }, - "vault_coin_denom": { + "vault_token_denom": { "description": "Denom of vault token", "type": "string" } }, "additionalProperties": false }, - "preview_redeem": { + "max_deposit": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_Coin", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - }, + "title": "Nullable_AssetsResponse", + "anyOf": [ + { + "$ref": "#/definitions/AssetsResponse" + }, + { + "type": "null" + } + ], "definitions": { + "AssetsResponse": { + "type": "object", + "required": [ + "coin" + ], + "properties": { + "coin": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + }, "Coin": { "type": "object", "required": [ @@ -306,127 +753,137 @@ } } }, - "total_vault_coins_issued": { + "max_redeem": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Uint128", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ], + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "preview_deposit": { "$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" }, - "unlocking_position": { + "preview_redeem": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "UnlockingPosition", + "title": "AssetsResponse", "type": "object", "required": [ - "amount", - "id", - "unlocked_at" + "coin" ], "properties": { - "amount": { - "description": "Number of vault tokens", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "id": { - "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::Unlock {}` call.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "unlocked_at": { - "description": "Absolute time when position unlocks in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC)", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] + "coin": { + "$ref": "#/definitions/Coin" } }, "additionalProperties": false, "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" + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" } - ] + } }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" - }, - "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" } } }, - "unlocking_positions_for_addr": { + "total_assets": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_UnlockingPosition", - "type": "array", - "items": { - "$ref": "#/definitions/UnlockingPosition" + "title": "AssetsResponse", + "type": "object", + "required": [ + "coin" + ], + "properties": { + "coin": { + "$ref": "#/definitions/Coin" + } }, + "additionalProperties": false, "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" - }, - "UnlockingPosition": { + "Coin": { "type": "object", "required": [ "amount", - "id", - "unlocked_at" + "denom" ], "properties": { "amount": { - "description": "Number of vault tokens", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "id": { - "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::Unlock {}` call.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] + "$ref": "#/definitions/Uint128" }, - "unlocked_at": { - "description": "Absolute time when position unlocks in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC)", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] + "denom": { + "type": "string" } - }, - "additionalProperties": false + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" } } + }, + "total_vault_token_supply": { + "$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" + }, + "vault_extension": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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" + }, + "vault_standard_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VaultStandardInfo", + "description": "Struct returned from QueryMsg::VaultStandardInfo with information about the used version of the vault standard and any extensions used.\n\nThis struct should be stored as an Item under the `vault_standard_info` key, so that other contracts can do a RawQuery and read it directly from storage instead of needing to do a costly SmartQuery.", + "type": "object", + "required": [ + "extensions", + "version" + ], + "properties": { + "extensions": { + "description": "A list of vault standard extensions used by the vault. E.g. [\"cw20\", \"lockup\", \"keeper\"]", + "type": "array", + "items": { + "type": "string" + } + }, + "version": { + "description": "The version of the vault standard used. A number, e.g. 1, 2, etc.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + }, + "additionalProperties": false } } } From 43993ff4739e7e91ce3892b88c7e390482927af6 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 1 Nov 2022 15:04:23 +0100 Subject: [PATCH 070/218] integrate cw-set for allowed coins (#33) * integrate cw-set for allowed coins * review updates --- Cargo.lock | 11 +++++++++++ contracts/credit-manager/Cargo.toml | 1 + contracts/credit-manager/src/execute.rs | 2 +- contracts/credit-manager/src/instantiate.rs | 4 ++-- contracts/credit-manager/src/query.rs | 2 +- contracts/credit-manager/src/state.rs | 8 +++++--- contracts/credit-manager/src/utils.rs | 2 +- 7 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f258186d0..17bf45410 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -187,6 +187,7 @@ dependencies = [ "cosmos-vault-standard", "cosmwasm-schema", "cosmwasm-std", + "cw-item-set", "cw-multi-test 0.16.0", "cw-storage-plus 0.16.0", "cw-utils 0.16.0", @@ -259,6 +260,16 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-item-set" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05dad9dc2e6e9ab784bb5598d8528f4afe014b7b0ec05e1f466fe2e11aca368c" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.16.0", +] + [[package]] name = "cw-multi-test" version = "0.13.4" diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 20a170e50..66fd61905 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -28,6 +28,7 @@ cosmwasm-std = "1.1" cw2 = "0.16" cw721 = "0.16" cw721-base = { version = "0.16", features = ["library"] } +cw-item-set = { version = "0.6", default-features = false, features = ["iterator"] } cw-storage-plus = "0.16" [dev-dependencies] diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index bcc884841..b6aa861c3 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -90,7 +90,7 @@ pub fn update_config( ALLOWED_COINS.clear(deps.storage); coins .iter() - .try_for_each(|denom| ALLOWED_COINS.save(deps.storage, denom, &Empty {}))?; + .try_for_each(|denom| ALLOWED_COINS.insert(deps.storage, denom).map(|_| ()))?; response = response .add_attribute("key", "allowed_coins") diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 096ab097b..9c99e003e 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{DepsMut, Empty}; +use cosmwasm_std::DepsMut; use rover::error::ContractResult; use rover::msg::InstantiateMsg; @@ -27,7 +27,7 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { msg.allowed_coins .iter() - .try_for_each(|denom| ALLOWED_COINS.save(deps.storage, denom, &Empty {}))?; + .try_for_each(|denom| ALLOWED_COINS.insert(deps.storage, denom).map(|_| ()))?; Ok(()) } diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 7539f923c..27812e1c1 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -202,7 +202,7 @@ pub fn query_allowed_coins( let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; ALLOWED_COINS - .keys(deps.storage, start, None, Order::Ascending) + .items(deps.storage, start, None, Order::Ascending) .take(limit) .collect::>>() } diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index c320d2432..403dbda23 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,15 +1,17 @@ -use cosmwasm_std::{Addr, Decimal, Empty, Uint128}; +use cosmwasm_std::{Addr, Decimal, Uint128}; +use cw_item_set::Set; use cw_storage_plus::{Item, Map}; -use crate::vault::RequestTempStorage; use rover::adapters::swap::Swapper; use rover::adapters::vault::{VaultConfig, VaultPositionAmount}; use rover::adapters::{Oracle, RedBank}; +use crate::vault::RequestTempStorage; + // Contract config pub const OWNER: Item = Item::new("owner"); pub const ACCOUNT_NFT: Item = Item::new("account_nft"); -pub const ALLOWED_COINS: Map<&str, Empty> = Map::new("allowed_coins"); +pub const ALLOWED_COINS: Set<&str> = Set::new("allowed_coins"); pub const VAULT_CONFIGS: Map<&Addr, VaultConfig> = Map::new("vault_configs"); pub const RED_BANK: Item = Item::new("red_bank"); pub const ORACLE: Item = Item::new("oracle"); diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index a6d395774..54b5cdab3 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -9,7 +9,7 @@ use rover::traits::IntoDecimal; use crate::state::{ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES}; pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, denom: &str) -> ContractResult<()> { - let is_whitelisted = ALLOWED_COINS.has(storage, denom); + let is_whitelisted = ALLOWED_COINS.contains(storage, denom); if !is_whitelisted { return Err(ContractError::NotWhitelisted(denom.to_string())); } From b479a28d8f908e9c893cbb3d28dc3997ba170fd1 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 2 Nov 2022 09:55:37 +0100 Subject: [PATCH 071/218] Unlocking vault positions to hold underlying token (#35) * Unlocking position data structure update * Update cosmos-vault-standard dep * review updates --- Cargo.lock | 54 +- contracts/credit-manager/Cargo.toml | 4 +- contracts/credit-manager/src/health.rs | 49 +- .../credit-manager/src/liquidate_coin.rs | 43 +- contracts/credit-manager/src/vault/enter.rs | 9 +- .../credit-manager/src/vault/exit_unlocked.rs | 2 +- .../src/vault/liquidate_vault.rs | 284 +++++-- .../src/vault/request_unlock.rs | 34 +- contracts/credit-manager/src/vault/utils.rs | 6 +- .../credit-manager/tests/helpers/builders.rs | 2 +- .../tests/helpers/mock_entity_info.rs | 2 +- .../credit-manager/tests/helpers/mock_env.rs | 12 +- .../credit-manager/tests/helpers/types.rs | 2 +- .../test_enumerate_vault_coin_balances.rs | 2 +- .../tests/test_enumerate_vault_configs.rs | 2 +- .../tests/test_enumerate_vault_positions.rs | 2 +- .../credit-manager/tests/test_instantiate.rs | 10 +- .../tests/test_liquidate_coin.rs | 18 +- .../tests/test_liquidate_vault.rs | 160 +++- .../credit-manager/tests/test_vault_enter.rs | 10 +- .../tests/test_vault_exit_unlocked.rs | 36 +- .../tests/test_vault_request_unlock.rs | 13 +- contracts/mars-oracle-adapter/src/contract.rs | 15 +- contracts/mars-oracle-adapter/src/msg.rs | 2 +- .../mars-oracle-adapter/tests/helpers.rs | 4 +- contracts/mock-vault/Cargo.toml | 2 +- contracts/mock-vault/src/contract.rs | 21 +- contracts/mock-vault/src/deposit.rs | 25 +- contracts/mock-vault/src/error.rs | 3 + contracts/mock-vault/src/msg.rs | 2 +- contracts/mock-vault/src/query.rs | 39 +- contracts/mock-vault/src/unlock.rs | 52 +- contracts/mock-vault/src/withdraw.rs | 29 +- packages/cosmos-vault-standard/Cargo.lock | 789 ------------------ packages/cosmos-vault-standard/Cargo.toml | 21 - packages/cosmos-vault-standard/README.md | 17 - packages/cosmos-vault-standard/src/cw4626.rs | 88 -- .../src/extensions/keeper.rs | 38 - .../src/extensions/lockup.rs | 104 --- .../src/extensions/mod.rs | 4 - packages/cosmos-vault-standard/src/lib.rs | 5 - packages/cosmos-vault-standard/src/msg.rs | 221 ----- packages/rover/Cargo.toml | 2 +- packages/rover/src/adapters/oracle.rs | 6 +- packages/rover/src/adapters/red_bank.rs | 10 + packages/rover/src/adapters/vault/amount.rs | 101 +-- packages/rover/src/adapters/vault/base.rs | 75 +- packages/rover/src/adapters/vault/config.rs | 26 + packages/rover/src/adapters/vault/mod.rs | 6 + packages/rover/src/adapters/vault/position.rs | 19 + packages/rover/src/adapters/vault/update.rs | 40 + schemas/credit-manager/credit-manager.json | 39 +- .../mars-oracle-adapter.json | 32 +- schemas/mock-vault/mock-vault.json | 391 +++------ 54 files changed, 899 insertions(+), 2085 deletions(-) delete mode 100644 packages/cosmos-vault-standard/Cargo.lock delete mode 100644 packages/cosmos-vault-standard/Cargo.toml delete mode 100644 packages/cosmos-vault-standard/README.md delete mode 100644 packages/cosmos-vault-standard/src/cw4626.rs delete mode 100644 packages/cosmos-vault-standard/src/extensions/keeper.rs delete mode 100644 packages/cosmos-vault-standard/src/extensions/lockup.rs delete mode 100644 packages/cosmos-vault-standard/src/extensions/mod.rs delete mode 100644 packages/cosmos-vault-standard/src/lib.rs delete mode 100644 packages/cosmos-vault-standard/src/msg.rs create mode 100644 packages/rover/src/adapters/vault/config.rs create mode 100644 packages/rover/src/adapters/vault/position.rs create mode 100644 packages/rover/src/adapters/vault/update.rs diff --git a/Cargo.lock b/Cargo.lock index 17bf45410..b57192b49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,12 +84,11 @@ checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" [[package]] name = "cosmos-vault-standard" version = "0.1.0" +source = "git+https://github.com/apollodao/cosmos-vault-standard#f9857326d0eebfd7edca855be9bd06b2d27e34e3" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-asset", "cw-utils 0.16.0", - "cw20 0.16.0", "schemars", "serde", ] @@ -246,20 +245,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "cw-asset" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7996c9c60e416aec195719137767d5cef8301237438bbabb772ee45a27f06e5e" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus 0.13.4", - "cw1155", - "cw20 0.13.4", - "schemars", - "serde", -] - [[package]] name = "cw-item-set" version = "0.6.0" @@ -356,18 +341,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw1155" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42197c9a0fd844653177009125f24157e486578289327a3781923e427a8f9bbc" -dependencies = [ - "cosmwasm-std", - "cw-utils 0.13.4", - "schemars", - "serde", -] - [[package]] name = "cw2" version = "0.16.0" @@ -381,31 +354,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cw20" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" -dependencies = [ - "cosmwasm-std", - "cw-utils 0.13.4", - "schemars", - "serde", -] - -[[package]] -name = "cw20" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45a8794a5dd33b66af34caee52a7beceb690856adcc1682b6e3db88b2cdee62" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils 0.16.0", - "schemars", - "serde", -] - [[package]] name = "cw721" version = "0.16.0" diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 66fd61905..c3883753c 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -18,11 +18,12 @@ account-nft = { version = "1.0", path = "../account-nft", features = ["library"] mars-health = { version = "0.1", path = "../../packages/health" } mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } mars-oracle-adapter = { version = "1.0", path = "../../contracts/mars-oracle-adapter", features = ["library"] } +mars-outpost = { version = "0.1", path = "../../packages/outpost" } mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } mock-vault = { version = "1.0", path = "../../contracts/mock-vault", features = ["library"] } rover = { version = "1.0", path = "../../packages/rover" } -cosmos-vault-standard = { path = "../../packages/cosmos-vault-standard", features = ["lockup"] } +cosmos-vault-standard = { git = "https://github.com/apollodao/cosmos-vault-standard", features = ["lockup", "force-unlock"] } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" cw2 = "0.16" @@ -33,7 +34,6 @@ cw-storage-plus = "0.16" [dev-dependencies] swapper-mock = { version = "1.0", path = "../../contracts/swapper/mock", features = ["library"] } -mars-outpost = { version = "0.1", path = "../../packages/outpost" } anyhow = "1" cw-multi-test = "0.16" diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 5cbb5d9c6..ede1df3eb 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -1,8 +1,9 @@ use cosmwasm_std::{Coin, Decimal, Deps, Env, Event, Response}; use mars_health::health::{Health, Position}; use mars_health::query::MarsQuerier; +use mars_outpost::red_bank::Market; -use rover::adapters::vault::{Total, VaultPosition}; +use rover::adapters::vault::VaultPosition; use rover::adapters::{Oracle, RedBank}; use rover::error::{ContractError, ContractResult}; use rover::traits::{Coins, IntoDecimal}; @@ -48,22 +49,52 @@ fn get_positions_for_vaults( vaults: &[VaultPosition], oracle: &Oracle, ) -> ContractResult> { - vaults + let positions = vaults .iter() .map(|v| { let info = v.vault.query_info(&deps.querier)?; - let query_res = oracle.query_price(&deps.querier, &info.vault_token_denom)?; + let price_res = oracle.query_price(&deps.querier, &info.vault_token)?; let config = VAULT_CONFIGS.load(deps.storage, &v.vault.address)?; - Ok(Position { - denom: query_res.denom, - price: query_res.price, - collateral_amount: v.amount.total().to_dec()?, + let mut positions = vec![]; + + positions.push(Position { + denom: price_res.denom, + price: price_res.price, + collateral_amount: v + .amount + .unlocked() + .checked_add(v.amount.locked())? + .to_dec()?, debt_amount: Decimal::zero(), max_ltv: config.max_ltv, liquidation_threshold: config.liquidation_threshold, - }) + }); + + let red_bank = RED_BANK.load(deps.storage)?; + for u in v.amount.unlocking().positions() { + let price_res = oracle.query_price(&deps.querier, &u.coin.denom)?; + let Market { + max_loan_to_value, + liquidation_threshold, + .. + } = red_bank.query_market(&deps.querier, &u.coin.denom)?; + positions.push(Position { + denom: price_res.denom, + price: price_res.price, + collateral_amount: u.coin.amount.to_dec()?, + debt_amount: Decimal::zero(), + max_ltv: max_loan_to_value, + liquidation_threshold, + }) + } + + Ok(positions) }) - .collect() + .collect::>>()? + .into_iter() + .flatten() + .collect::>(); + Ok(positions) } pub fn assert_below_max_ltv(deps: Deps, env: Env, account_id: &str) -> ContractResult { diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index f5e7073a9..35370ff02 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -1,6 +1,6 @@ use std::ops::{Add, Div}; -use cosmwasm_std::{Coin, Decimal, DepsMut, Env, Response, StdError, Uint128}; +use cosmwasm_std::{Coin, CosmosMsg, Decimal, DepsMut, Env, Response, StdError, Storage, Uint128}; use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; @@ -32,15 +32,13 @@ pub fn liquidate_coin( request_coin_balance, )?; - // Transfer debt coin from liquidator's coin balance to liquidatee - // Will be used to pay off the debt via CallbackMsg::Repay {} - decrement_coin_balance(deps.storage, liquidator_account_id, &debt)?; - increment_coin_balance(deps.storage, liquidatee_account_id, &debt)?; - let repay_msg = (CallbackMsg::Repay { - account_id: liquidatee_account_id.to_string(), - coin: debt.clone(), - }) - .into_cosmos_msg(&env.contract.address)?; + let repay_msg = repay_debt( + deps.storage, + &env, + liquidator_account_id, + liquidatee_account_id, + &debt, + )?; // Transfer requested coin from liquidatee to liquidator decrement_coin_balance(deps.storage, liquidatee_account_id, &request)?; @@ -118,7 +116,11 @@ pub fn calculate_liquidation( .add(Decimal::one()) .checked_mul(debt_res.price.checked_mul(final_debt_to_repay.to_dec()?)?)? .div(request_res.price) - .uint128(); + .uint128() + // Given the nature of integers, these operations will round down. This means the liquidation balance will get + // closer and closer to 0, but never actually get there and stay as a single denom unit. + // The remediation for this is to round up at the very end of the calculation. Which adding 1 effectively does. + .checked_add(Uint128::new(1))?; // (Debt Coin, Request Coin) Ok(( @@ -132,3 +134,22 @@ pub fn calculate_liquidation( }, )) } + +pub fn repay_debt( + storage: &mut dyn Storage, + env: &Env, + liquidator_account_id: &str, + liquidatee_account_id: &str, + debt: &Coin, +) -> ContractResult { + // Transfer debt coin from liquidator's coin balance to liquidatee + // Will be used to pay off the debt via CallbackMsg::Repay {} + decrement_coin_balance(storage, liquidator_account_id, debt)?; + increment_coin_balance(storage, liquidatee_account_id, debt)?; + let msg = (CallbackMsg::Repay { + account_id: liquidatee_account_id.to_string(), + coin: debt.clone(), + }) + .into_cosmos_msg(&env.contract.address)?; + Ok(msg) +} diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index d0fc070d1..bf19d9224 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -83,10 +83,10 @@ pub fn assert_denom_matches_vault_reqs( coin: &Coin, ) -> ContractResult<()> { let vault_info = vault.query_info(&querier)?; - if vault_info.req_denom != coin.denom { + if vault_info.base_token != coin.denom { return Err(ContractError::RequirementsNotMet(format!( "Required coin: {} -- does not match given coin: {}", - vault_info.req_denom, coin.denom + vault_info.base_token, coin.denom ))); } Ok(()) @@ -108,10 +108,7 @@ pub fn assert_deposit_is_under_cap( let rover_vault_coin_balance = vault.query_balance(&deps.querier, rover_addr)?; let rover_vault_coins_value = oracle.query_total_value( &deps.querier, - &[c( - rover_vault_coin_balance.u128(), - vault_info.vault_token_denom, - )], + &[c(rover_vault_coin_balance.u128(), vault_info.vault_token)], )?; let new_total_vault_value = rover_vault_coins_value.checked_add(deposit_request_value)?; diff --git a/contracts/credit-manager/src/vault/exit_unlocked.rs b/contracts/credit-manager/src/vault/exit_unlocked.rs index 35824b04d..780fa2d31 100644 --- a/contracts/credit-manager/src/vault/exit_unlocked.rs +++ b/contracts/credit-manager/src/vault/exit_unlocked.rs @@ -35,7 +35,7 @@ pub fn exit_vault_unlocked( &vault.address, VaultPositionUpdate::Unlocking(UnlockingChange::Decrement { id: position_id, - amount: matching_unlock.amount, + amount: matching_unlock.coin.amount, }), )?; diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index a81c9c35c..a2acd617e 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -1,20 +1,20 @@ use std::cmp::min; use cosmwasm_std::{ - to_binary, Coin, CosmosMsg, DepsMut, Env, QuerierWrapper, Response, Storage, Uint128, WasmMsg, + to_binary, Coin, CosmosMsg, DepsMut, Env, Response, StdResult, Uint128, WasmMsg, }; use rover::adapters::vault::{ - Total, UnlockingChange, UpdateType, Vault, VaultPositionAmount, VaultPositionUpdate, + UnlockingChange, UnlockingPositions, UpdateType, Vault, VaultPositionAmount, + VaultPositionUpdate, }; use rover::error::ContractResult; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg; -use crate::liquidate_coin::calculate_liquidation; +use crate::liquidate_coin::{calculate_liquidation, repay_debt}; use crate::state::VAULT_POSITIONS; use crate::update_coin_balances::query_balances; -use crate::utils::{decrement_coin_balance, increment_coin_balance}; use crate::vault::update_vault_position; pub fn liquidate_vault( @@ -25,59 +25,94 @@ pub fn liquidate_vault( debt_coin: Coin, request_vault: Vault, ) -> ContractResult { - let vault_info = request_vault.query_info(&deps.querier)?; let liquidatee_position = VAULT_POSITIONS.load( deps.storage, (liquidatee_account_id, request_vault.address.clone()), )?; + + match liquidatee_position { + VaultPositionAmount::Unlocked(a) => liquidate_unlocked( + deps, + env, + liquidator_account_id, + liquidatee_account_id, + debt_coin, + request_vault, + a.total(), + ), + VaultPositionAmount::Locking(ref a) => { + // A locking vault can have two different positions: LOCKED & UNLOCKING + // Priority goes to force withdrawing the unlocking buckets + if !a.unlocking.positions().is_empty() { + liquidate_unlocking( + deps, + env, + liquidator_account_id, + liquidatee_account_id, + debt_coin, + request_vault, + liquidatee_position.unlocking(), + ) + } else { + liquidate_locked( + deps, + env, + liquidator_account_id, + liquidatee_account_id, + debt_coin, + request_vault, + a.locked.total(), + ) + } + } + } +} + +fn liquidate_unlocked( + deps: DepsMut, + env: Env, + liquidator_account_id: &str, + liquidatee_account_id: &str, + debt_coin: Coin, + request_vault: Vault, + amount: Uint128, +) -> ContractResult { + let vault_info = request_vault.query_info(&deps.querier)?; + let (debt, request) = calculate_liquidation( &deps, &env, liquidatee_account_id, &debt_coin, - &vault_info.vault_token_denom, - liquidatee_position.total(), + &vault_info.vault_token, + amount, )?; - // Transfer debt coin from liquidator's coin balance to liquidatee - // Will be used to pay off the debt via CallbackMsg::Repay {} - decrement_coin_balance(deps.storage, liquidator_account_id, &debt)?; - increment_coin_balance(deps.storage, liquidatee_account_id, &debt)?; - let repay_msg = (CallbackMsg::Repay { - account_id: liquidatee_account_id.to_string(), - coin: debt.clone(), - }) - .into_cosmos_msg(&env.contract.address)?; - - let vault_withdraw_msgs = get_vault_withdraw_msgs( + let repay_msg = repay_debt( deps.storage, - &deps.querier, + &env, + liquidator_account_id, liquidatee_account_id, - &request_vault, - &liquidatee_position, - request.amount, + &debt, )?; - // Update coin balances of liquidator after withdraws have been made - let previous_balances = query_balances( - deps.as_ref(), - &env.contract.address, - &[vault_info.req_denom.as_str()], + update_vault_position( + deps.storage, + liquidatee_account_id, + &request_vault.address, + VaultPositionUpdate::Unlocked(UpdateType::Decrement(request.amount)), )?; - let update_coin_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: env.contract.address.to_string(), - funds: vec![], - msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalances { - account_id: liquidator_account_id.to_string(), - previous_balances, - }))?, - }); + + let vault_withdraw_msg = request_vault.withdraw_msg(&deps.querier, request.amount, false)?; + + let update_coin_balance_msg = + update_balances(deps, &env, &liquidator_account_id, &vault_info.base_token)?; Ok(Response::new() .add_message(repay_msg) - .add_messages(vault_withdraw_msgs) + .add_message(vault_withdraw_msg) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit_manager/liquidate_vault") + .add_attribute("action", "rover/credit_manager/liquidate_vault/unlocked") .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("debt_repaid_denom", debt.denom) .add_attribute("debt_repaid_amount", debt.amount) @@ -85,67 +120,138 @@ pub fn liquidate_vault( .add_attribute("vault_coin_liquidated", request.amount)) } -/// Generates Cosmos msgs for Vault withdraws & updates Rover credit account balances -fn get_vault_withdraw_msgs( - storage: &mut dyn Storage, - querier: &QuerierWrapper, +fn liquidate_unlocking( + deps: DepsMut, + env: Env, + liquidator_account_id: &str, liquidatee_account_id: &str, - request_vault: &Vault, - liquidatee_position: &VaultPositionAmount, - amount: Uint128, -) -> ContractResult> { - let mut total_to_liquidate = amount; + debt_coin: Coin, + request_vault: Vault, + unlocking_positions: UnlockingPositions, +) -> ContractResult { + let vault_info = request_vault.query_info(&deps.querier)?; + + let (debt, request) = calculate_liquidation( + &deps, + &env, + liquidatee_account_id, + &debt_coin, + &vault_info.base_token, + unlocking_positions.total(), + )?; + + let repay_msg = repay_debt( + deps.storage, + &env, + liquidator_account_id, + liquidatee_account_id, + &debt, + )?; + + let mut total_to_liquidate = request.amount; let mut vault_withdraw_msgs = vec![]; - match liquidatee_position { - VaultPositionAmount::Unlocked(_) => { - update_vault_position( - storage, - liquidatee_account_id, - &request_vault.address, - VaultPositionUpdate::Unlocked(UpdateType::Decrement(total_to_liquidate)), - )?; - - let msg = request_vault.withdraw_msg(querier, total_to_liquidate, false)?; - vault_withdraw_msgs.push(msg); + for u in unlocking_positions.positions() { + let amount = min(u.coin.amount, total_to_liquidate); + + if amount.is_zero() { + break; } - VaultPositionAmount::Locking(amount) => { - // A locking vault can have two different positions: LOCKED & UNLOCKING - // Priority goes to force withdrawing the unlocking buckets - for u in amount.unlocking.positions() { - let amount = min(u.amount, total_to_liquidate); - if amount.is_zero() { - break; - } + update_vault_position( + deps.storage, + liquidatee_account_id, + &request_vault.address, + VaultPositionUpdate::Unlocking(UnlockingChange::Decrement { id: u.id, amount }), + )?; - update_vault_position( - storage, - liquidatee_account_id, - &request_vault.address, - VaultPositionUpdate::Unlocking(UnlockingChange::Decrement { id: u.id, amount }), - )?; + let msg = request_vault.force_withdraw_unlocking_msg(u.id, Some(amount))?; + vault_withdraw_msgs.push(msg); - let msg = request_vault.force_withdraw_unlocking_msg(u.id, Some(amount))?; - vault_withdraw_msgs.push(msg); + total_to_liquidate = total_to_liquidate.checked_sub(amount)?; + } - total_to_liquidate = total_to_liquidate.checked_sub(amount)?; - } + let update_coin_balance_msg = + update_balances(deps, &env, &liquidator_account_id, &vault_info.base_token)?; - // If unlocking positions have been exhausted, liquidate from LOCKED bucket - if !total_to_liquidate.is_zero() { - update_vault_position( - storage, - liquidatee_account_id, - &request_vault.address, - VaultPositionUpdate::Locked(UpdateType::Decrement(total_to_liquidate)), - )?; + Ok(Response::new() + .add_message(repay_msg) + .add_messages(vault_withdraw_msgs) + .add_message(update_coin_balance_msg) + .add_attribute("action", "rover/credit_manager/liquidate_vault/unlocking") + .add_attribute("liquidatee_account_id", liquidatee_account_id) + .add_attribute("debt_repaid_denom", debt.denom) + .add_attribute("debt_repaid_amount", debt.amount) + .add_attribute("vault_coin_denom", request.denom) + .add_attribute("vault_coin_liquidated", request.amount)) +} - let msg = request_vault.withdraw_msg(querier, total_to_liquidate, true)?; - vault_withdraw_msgs.push(msg); - } - } - } +fn liquidate_locked( + deps: DepsMut, + env: Env, + liquidator_account_id: &str, + liquidatee_account_id: &str, + debt_coin: Coin, + request_vault: Vault, + amount: Uint128, +) -> ContractResult { + let vault_info = request_vault.query_info(&deps.querier)?; + + let (debt, request) = calculate_liquidation( + &deps, + &env, + liquidatee_account_id, + &debt_coin, + &vault_info.vault_token, + amount, + )?; + + let repay_msg = repay_debt( + deps.storage, + &env, + liquidator_account_id, + liquidatee_account_id, + &debt, + )?; + + update_vault_position( + deps.storage, + liquidatee_account_id, + &request_vault.address, + VaultPositionUpdate::Locked(UpdateType::Decrement(request.amount)), + )?; + + let vault_withdraw_msg = request_vault.withdraw_msg(&deps.querier, request.amount, true)?; + + let update_coin_balance_msg = + update_balances(deps, &env, &liquidator_account_id, &vault_info.base_token)?; + + Ok(Response::new() + .add_message(repay_msg) + .add_message(vault_withdraw_msg) + .add_message(update_coin_balance_msg) + .add_attribute("action", "rover/credit_manager/liquidate_vault/locked") + .add_attribute("liquidatee_account_id", liquidatee_account_id) + .add_attribute("debt_repaid_denom", debt.denom) + .add_attribute("debt_repaid_amount", debt.amount) + .add_attribute("vault_coin_denom", request.denom) + .add_attribute("vault_coin_liquidated", request.amount)) +} - Ok(vault_withdraw_msgs) +/// Updates coin balances of liquidator after withdraws have been made +fn update_balances( + deps: DepsMut, + env: &Env, + liquidator_account_id: &&str, + denom: &str, +) -> StdResult { + let previous_balances = query_balances(deps.as_ref(), &env.contract.address, &[denom])?; + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalances { + account_id: liquidator_account_id.to_string(), + previous_balances, + }))?, + })) } diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index 7ba5c2d3d..3bf296698 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{Coin, DepsMut, Reply, Response, Uint128}; use crate::state::VAULT_REQUEST_TEMP_STORAGE; use rover::adapters::vault::{ - UnlockingChange, UpdateType, Vault, VaultPositionUpdate, VaultUnlockingPosition, + UnlockingChange, UpdateType, Vault, VaultBase, VaultPositionUpdate, VaultUnlockingPosition, }; use rover::error::{ContractError, ContractResult}; use rover::extensions::AttrParse; @@ -30,6 +30,13 @@ pub fn request_vault_unlock( ) })?; + update_vault_position( + deps.storage, + account_id, + &vault.address, + VaultPositionUpdate::Locked(UpdateType::Decrement(amount)), + )?; + VAULT_REQUEST_TEMP_STORAGE.save( deps.storage, &RequestTempStorage { @@ -40,7 +47,7 @@ pub fn request_vault_unlock( let vault_info = vault.query_info(&deps.querier)?; let request_unlock_msg = vault.request_unlock_msg(Coin { - denom: vault_info.vault_token_denom, + denom: vault_info.vault_token, amount, })?; @@ -50,29 +57,26 @@ pub fn request_vault_unlock( } pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResult { - let RequestTempStorage { account_id, amount } = - VAULT_REQUEST_TEMP_STORAGE.load(deps.storage)?; - + let storage = VAULT_REQUEST_TEMP_STORAGE.load(deps.storage)?; let unlock_event = reply.parse_unlock_event()?; let vault_addr = deps.api.addr_validate(unlock_event.vault_addr.as_str())?; + let vault = VaultBase::new(vault_addr.clone()); + let lockup = vault.query_lockup(&deps.querier, unlock_event.id)?; + let info = vault.query_info(&deps.querier)?; update_vault_position( deps.storage, - &account_id, + &storage.account_id, &vault_addr, VaultPositionUpdate::Unlocking(UnlockingChange::Add(VaultUnlockingPosition { - id: unlock_event.id, - amount, + id: lockup.id, + coin: Coin { + denom: info.base_token, + amount: lockup.base_token_amount, + }, })), )?; - update_vault_position( - deps.storage, - &account_id, - &vault_addr, - VaultPositionUpdate::Locked(UpdateType::Decrement(amount)), - )?; - VAULT_REQUEST_TEMP_STORAGE.remove(deps.storage); Ok(Response::new().add_attribute( diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 2ed952c16..9622d9d76 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Addr, Coin, Deps, StdResult, Storage}; -use rover::adapters::vault::{Total, Vault, VaultPositionAmount, VaultPositionUpdate}; +use rover::adapters::vault::{Vault, VaultPositionAmount, VaultPositionUpdate}; use rover::error::{ContractError, ContractResult}; use crate::state::{VAULT_CONFIGS, VAULT_POSITIONS}; @@ -29,7 +29,7 @@ pub fn update_vault_position( amount.update(update)?; - if amount.total().is_zero() { + if amount.is_empty() { path.remove(storage); } else { path.save(storage, &amount)?; @@ -44,5 +44,5 @@ pub fn query_withdraw_denom_balances( vault: &Vault, ) -> StdResult> { let vault_info = vault.query_info(&deps.querier)?; - query_balances(deps, rover_addr, &[vault_info.req_denom.as_str()]) + query_balances(deps, rover_addr, &[vault_info.base_token.as_str()]) } diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index a503c2620..429bbd247 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -25,7 +25,7 @@ pub fn build_mock_vaults(count: usize) -> Vec { VaultTestInfo { vault_token_denom: format!("vault_{}", i), lockup: Some(Duration::Time(1_209_600)), // 14 days - denom_req: lp_token.denom.clone(), + base_token_denom: lp_token.denom.clone(), deposit_cap: coin(10000000, "uusdc"), max_ltv: lp_token.max_ltv, liquidation_threshold: lp_token.liquidation_threshold, diff --git a/contracts/credit-manager/tests/helpers/mock_entity_info.rs b/contracts/credit-manager/tests/helpers/mock_entity_info.rs index 5e8372d67..2c25e5b9c 100644 --- a/contracts/credit-manager/tests/helpers/mock_entity_info.rs +++ b/contracts/credit-manager/tests/helpers/mock_entity_info.rs @@ -51,7 +51,7 @@ pub fn generate_mock_vault(lockup: Option) -> VaultTestInfo { VaultTestInfo { vault_token_denom: "uleverage".to_string(), lockup, - denom_req: lp_token.denom, + base_token_denom: lp_token.denom, deposit_cap: coin(10_000_000, "uusdc"), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 5638973c2..44cf8801f 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -12,7 +12,7 @@ use account_nft::msg::ExecuteMsg as NftExecuteMsg; use cosmos_vault_standard::extensions::lockup::Lockup; use cosmos_vault_standard::extensions::lockup::LockupQueryMsg::Lockups; use cosmos_vault_standard::msg::QueryMsg::{Info as VaultInfoMsg, VaultExtension}; -use cosmos_vault_standard::msg::{AssetsResponse, ExtensionQueryMsg, VaultInfo}; +use cosmos_vault_standard::msg::{ExtensionQueryMsg, VaultInfo}; use mars_oracle_adapter::msg::{ InstantiateMsg as OracleAdapterInstantiateMsg, PricingMethod, VaultPricingInfo, }; @@ -251,7 +251,7 @@ impl MockEnv { .unwrap() .query_info(&self.app.wrap()) .unwrap(); - vault.vault_token_denom == info.vault_token_denom + vault.vault_token_denom == info.vault_token }) .unwrap() .vault @@ -338,7 +338,7 @@ impl MockEnv { .unwrap() } - pub fn query_preview_redeem(&self, vault: &VaultUnchecked, shares: Uint128) -> AssetsResponse { + pub fn query_preview_redeem(&self, vault: &VaultUnchecked, shares: Uint128) -> Uint128 { vault .check(&MockApi::default()) .unwrap() @@ -597,10 +597,10 @@ impl MockEnvBuilder { .query_wasm_smart(config.vault.address.clone(), &VaultInfoMsg:: {}) .unwrap(); VaultPricingInfo { - vault_coin_denom: info.vault_token_denom, + vault_coin_denom: info.vault_token, addr: Addr::unchecked(config.vault.address), method: PricingMethod::PreviewRedeem, - req_denom: info.req_denom, + base_denom: info.base_token, } }) .collect() @@ -683,7 +683,7 @@ impl MockEnvBuilder { &VaultInstantiateMsg { vault_token_denom: vault.clone().vault_token_denom, lockup: vault.lockup, - req_denom: vault.clone().denom_req, + base_token_denom: vault.clone().base_token_denom, oracle, }, &[], diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 9d6e0e213..867f3dc07 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -19,8 +19,8 @@ pub struct CoinInfo { #[cw_serde] pub struct VaultTestInfo { pub vault_token_denom: String, + pub base_token_denom: String, pub lockup: Option, - pub denom_req: String, pub deposit_cap: Coin, pub max_ltv: Decimal, pub liquidation_threshold: Decimal, diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index ad28674c8..a4e657adc 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -101,7 +101,7 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { .chain(vaults_res_c.iter().cloned()) .chain(vaults_res_d.iter().cloned()) .map(|v| v.vault.query_info(&mock.app.wrap()).unwrap()) - .map(|info| info.vault_token_denom) + .map(|info| info.vault_token) .collect::>(); assert_eq!(combined.len(), all_vaults.len()); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_configs.rs b/contracts/credit-manager/tests/test_enumerate_vault_configs.rs index 02b10f448..f52759ee9 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_configs.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_configs.rs @@ -45,7 +45,7 @@ fn test_pagination_on_allowed_vaults_query_works() { .chain(vaults_res_d.iter().cloned()) .map(|v| v.vault.check(&MockApi::default()).unwrap()) .map(|v| v.query_info(&mock.app.wrap()).unwrap()) - .map(|info| info.vault_token_denom) + .map(|info| info.vault_token) .collect::>(); assert_eq!(combined.len(), allowed_vaults.len()); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index d738e1b8c..6003fc0e1 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -114,7 +114,7 @@ fn test_pagination_on_all_vault_positions_query_works() { .chain(vaults_res_c.iter().cloned()) .chain(vaults_res_d.iter().cloned()) .map(|v| v.position.vault.query_info(&mock.app.wrap()).unwrap()) - .map(|info| info.vault_token_denom) + .map(|info| info.vault_token) .collect::>(); let deduped = combined.iter().unique().cloned().collect::>(); diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index ed8328402..d5f4a0214 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -36,7 +36,7 @@ fn test_allowed_vaults_set_on_instantiate() { VaultTestInfo { vault_token_denom: "vault_contract_1".to_string(), lockup: None, - denom_req: "lp_denom_123".to_string(), + base_token_denom: "lp_denom_123".to_string(), deposit_cap: coin(1_000_000, "uusdc"), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), @@ -44,7 +44,7 @@ fn test_allowed_vaults_set_on_instantiate() { VaultTestInfo { vault_token_denom: "vault_contract_2".to_string(), lockup: None, - denom_req: "lp_denom_123".to_string(), + base_token_denom: "lp_denom_123".to_string(), deposit_cap: coin(1_000_000, "uusdc"), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), @@ -52,7 +52,7 @@ fn test_allowed_vaults_set_on_instantiate() { VaultTestInfo { vault_token_denom: "vault_contract_3".to_string(), lockup: None, - denom_req: "lp_denom_123".to_string(), + base_token_denom: "lp_denom_123".to_string(), deposit_cap: coin(1_000_000, "uusdc"), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), @@ -95,7 +95,7 @@ fn test_raises_on_invalid_vaults_config() { deposit_cap: coin(10_000_000, "uusdc"), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), - denom_req: "lp_denom_123".to_string(), + base_token_denom: "lp_denom_123".to_string(), }, ) .build(); @@ -113,7 +113,7 @@ fn test_raises_on_invalid_vaults_config() { deposit_cap: coin(10_000_000, "uusdc"), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 0).unwrap(), - denom_req: "lp_denom_123".to_string(), + base_token_denom: "lp_denom_123".to_string(), }, ) .build(); diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index d1cb89bad..efab57739 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -375,7 +375,7 @@ fn test_liquidator_left_in_unhealthy_state() { res, AboveMaxLTV { account_id: liquidator_account_id, - max_ltv_health_factor: "0.7945".to_string(), + max_ltv_health_factor: "0.795375".to_string(), }, ) } @@ -438,7 +438,7 @@ fn test_debt_amount_adjusted_to_close_factor_max() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.coins.len(), 2); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(48)); + assert_eq!(osmo_balance.amount, Uint128::new(47)); let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(100)); @@ -453,7 +453,7 @@ fn test_debt_amount_adjusted_to_close_factor_max() { let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(40)); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(252)); + assert_eq!(osmo_balance.amount, Uint128::new(253)); } #[test] @@ -516,7 +516,7 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.coins.len(), 3); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(191)); + assert_eq!(osmo_balance.amount, Uint128::new(190)); let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(100)); let jake_balance = get_coin("ujake", &position.coins); @@ -533,7 +533,7 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { let jake_balance = get_coin("ujake", &position.coins); assert_eq!(jake_balance.amount, Uint128::new(39)); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(109)); + assert_eq!(osmo_balance.amount, Uint128::new(110)); } #[test] @@ -594,7 +594,7 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.coins.len(), 2); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(48)); + assert_eq!(osmo_balance.amount, Uint128::new(47)); let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(100)); @@ -609,7 +609,7 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(47)); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(252)); + assert_eq!(osmo_balance.amount, Uint128::new(253)); } #[test] @@ -670,7 +670,7 @@ fn test_debt_amount_no_adjustment() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.coins.len(), 2); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(69)); + assert_eq!(osmo_balance.amount, Uint128::new(68)); let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(100)); @@ -683,7 +683,7 @@ fn test_debt_amount_no_adjustment() { assert_eq!(position.coins.len(), 1); assert_eq!(position.debts.len(), 0); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(231)); + assert_eq!(osmo_balance.amount, Uint128::new(232)); } // TODO: After swap is implemented, attempt to liquidate with no deposited funds: diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 068f77ae0..35c157e37 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -63,7 +63,9 @@ fn test_liquidatee_must_have_the_request_vault_position() { assert_err( res, - ContractError::Std(NotFound { kind: "rover::adapters::vault::amount::VaultPositionAmountBase".to_string() }), + ContractError::Std(NotFound { + kind: "rover::adapters::vault::amount::VaultPositionAmount".to_string(), + }), ) } @@ -254,7 +256,7 @@ fn test_liquidate_unlocked_vault() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_balance = position.vaults.first().unwrap().amount.unlocked(); - assert_eq!(vault_balance, Uint128::new(893_661)); // 1M - 106_339 + assert_eq!(vault_balance, Uint128::new(893_660)); // 1M - 106_340 assert_eq!(position.coins.len(), 1); let jake_balance = get_coin("ujake", &position.coins); @@ -304,14 +306,10 @@ fn test_liquidate_locked_vault() { vec![ Deposit(lp_token.to_coin(80)), EnterVault { - vault: vault.clone(), + vault, coin: lp_token.to_coin(80), }, Borrow(atom.to_coin(700)), - RequestVaultUnlock { - vault, - amount: Uint128::new(100_000), - }, ], &[lp_token.to_coin(80)], ) @@ -343,9 +341,10 @@ fn test_liquidate_locked_vault() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_amount = position.vaults.first().unwrap().amount.clone(); - // 930,473 vault tokens liquidated - assert_eq!(vault_amount.unlocking().len(), 0); // unlocking first: 100k - 100k = 0 - assert_eq!(vault_amount.locked(), Uint128::new(69_527)); // then locked bucket: 900k - 830,473 = 69_527 + // 1M - 930,474 vault tokens liquidated = 69,526 + assert_eq!(vault_amount.locked(), Uint128::new(69_526)); + assert_eq!(vault_amount.unlocking().positions().len(), 0); + assert_eq!(vault_amount.unlocked(), Uint128::zero()); assert_eq!(position.coins.len(), 1); let atom_balance = get_coin("uatom", &position.coins); @@ -372,6 +371,102 @@ fn test_liquidate_unlocking_priority() { let liquidatee = Addr::unchecked("liquidatee"); let liquidator = Addr::unchecked("liquidator"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), ujake.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: vec![ujake.to_coin(100)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault: vault.clone(), + coin: lp_token.to_coin(200), + }, + Borrow(ujake.to_coin(175)), + RequestVaultUnlock { + vault, + amount: Uint128::new(100_000), + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: ujake.denom.clone(), + price: Uint128::new(20).to_dec().unwrap(), + }); + + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(ujake.to_coin(10)), + LiquidateVault { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: ujake.to_coin(10), + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + }, + ], + &[ujake.to_coin(10)], + ) + .unwrap(); + + // Assert only unlocking position liquidated + let position = mock.query_positions(&liquidatee_account_id); + assert_eq!(position.vaults.len(), 1); + let vault_amount = position.vaults.first().unwrap().amount.clone(); + assert_eq!(vault_amount.unlocked(), Uint128::zero()); + assert_eq!(vault_amount.unlocking().positions().len(), 0); + assert_eq!(vault_amount.locked(), Uint128::new(900_000)); + + mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(ujake.to_coin(10)), + LiquidateVault { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: ujake.to_coin(10), + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + }, + ], + &[ujake.to_coin(10)], + ) + .unwrap(); + + // Assert locked positions can now be liquidated + let position = mock.query_positions(&liquidatee_account_id); + let vault_amount = position.vaults.first().unwrap().amount.clone(); + assert!(vault_amount.locked() < Uint128::new(900_000)); +} + +#[test] +fn test_liquidate_unlocking_liquidation_order() { + let lp_token = lp_token_info(); + let ujake = ujake_info(); + let leverage_vault = locked_vault_info(); + + let liquidatee = Addr::unchecked("liquidatee"); + let liquidator = Addr::unchecked("liquidator"); + let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone(), ujake.clone()]) .allowed_vaults(&[leverage_vault.clone()]) @@ -405,11 +500,15 @@ fn test_liquidate_unlocking_priority() { }, RequestVaultUnlock { vault: vault.clone(), - amount: Uint128::new(200_000), + amount: Uint128::new(50_000), + }, + RequestVaultUnlock { + vault: vault.clone(), + amount: Uint128::new(100_000), }, RequestVaultUnlock { vault, - amount: Uint128::new(700_000), + amount: Uint128::new(840_000), }, ], &[lp_token.to_coin(200)], @@ -443,18 +542,33 @@ fn test_liquidate_unlocking_priority() { assert_eq!(position.vaults.len(), 1); let vault_amount = position.vaults.first().unwrap().amount.clone(); assert_eq!(vault_amount.unlocked(), Uint128::zero()); - assert_eq!(vault_amount.locked(), Uint128::new(90_000)); - // Total liquidated: 106,339 - // First bucket drained - // Second bucket partially liquidated - assert_eq!(vault_amount.unlocking().len(), 2); + assert_eq!(vault_amount.locked(), Uint128::zero()); + + // Total liquidated: 21 LP tokens + // First bucket drained: -2 (all) + // Second bucket drained: -10 (all) + // Third bucket partially liquidated: -10 (out of 20) + // Fourth bucket retained: -0 (out of 168) + assert_eq!(vault_amount.unlocking().positions().len(), 2); assert_eq!( - vault_amount.unlocking().first().unwrap().amount, - Uint128::new(200_000 - (106_339 - 10_000)) + vault_amount + .unlocking() + .positions() + .first() + .unwrap() + .coin + .amount, + Uint128::new(10) ); assert_eq!( - vault_amount.unlocking().get(1).unwrap().amount, - Uint128::new(700_000) + vault_amount + .unlocking() + .positions() + .get(1) + .unwrap() + .coin + .amount, + Uint128::new(168) ); assert_eq!(position.coins.len(), 1); @@ -470,7 +584,7 @@ fn test_liquidate_unlocking_priority() { assert_eq!(position.coins.len(), 1); assert_eq!(position.debts.len(), 0); let osmo_balance = get_coin(&lp_token.denom, &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(21)); + assert_eq!(osmo_balance.amount, Uint128::new(22)); } // NOTE: liquidation calculation+adjustments are quite complex, full cases in test_liquidate_coin.rs @@ -544,7 +658,7 @@ fn test_liquidation_calculation_adjustment() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_balance = position.vaults.first().unwrap().amount.unlocked(); - assert_eq!(vault_balance, Uint128::new(406)); // Vault position liquidated by 99% + assert_eq!(vault_balance, Uint128::new(405)); // Vault position liquidated by 99% assert_eq!(position.coins.len(), 1); let jake_balance = get_coin("ujake", &position.coins); diff --git a/contracts/credit-manager/tests/test_vault_enter.rs b/contracts/credit-manager/tests/test_vault_enter.rs index d415dbb99..7161e3a21 100644 --- a/contracts/credit-manager/tests/test_vault_enter.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -217,9 +217,8 @@ fn test_successful_deposit_into_locked_vault() { res.vaults.first().unwrap().amount.unlocked() ); - let assets = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().amount.locked()); - assert_eq!(assets.coin.denom, lp_token.denom); - assert_eq!(assets.coin.amount, Uint128::new(23)); + let amount = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().amount.locked()); + assert_eq!(amount, Uint128::new(23)); let balance = mock.query_total_vault_coin_balance(&vault); assert_eq!(balance, STARTING_VAULT_SHARES); @@ -272,9 +271,8 @@ fn test_successful_deposit_into_unlocked_vault() { ); assert_eq!(Uint128::zero(), res.vaults.first().unwrap().amount.locked()); - let assets = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().amount.unlocked()); - assert_eq!(assets.coin.denom, lp_token.denom); - assert_eq!(assets.coin.amount, Uint128::new(23)); + let amount = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().amount.unlocked()); + assert_eq!(amount, Uint128::new(23)); let balance = mock.query_total_vault_coin_balance(&vault); assert_eq!(balance, STARTING_VAULT_SHARES); diff --git a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs index 657322a95..6a56f7beb 100644 --- a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs +++ b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs @@ -1,4 +1,3 @@ -use cosmwasm_std::StdError::NotFound; use cosmwasm_std::{Addr, Uint128}; use cw_utils::Duration; @@ -68,7 +67,8 @@ fn test_not_owner_of_unlocking_position() { let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); - let user_a = Addr::unchecked("user"); + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) .allowed_vaults(&[leverage_vault.clone()]) @@ -76,6 +76,10 @@ fn test_not_owner_of_unlocking_position() { addr: user_a.clone(), funds: vec![lp_token.to_coin(300)], }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: vec![lp_token.to_coin(2)], + }) .build() .unwrap(); @@ -104,23 +108,30 @@ fn test_not_owner_of_unlocking_position() { assert_eq!(positions.vaults.len(), 1); let lockup_id = get_lockup_id(&positions); - let user_b = Addr::unchecked("user_b"); let account_id_b = mock.create_credit_account(&user_b).unwrap(); let res = mock.update_credit_account( &account_id_b, &user_b, - vec![ExitVaultUnlocked { - id: lockup_id, - vault, - }], - &[], + vec![ + Deposit(lp_token.to_coin(2)), + EnterVault { + vault: vault.clone(), + coin: lp_token.to_coin(2), + }, + RequestVaultUnlock { + vault: vault.clone(), + amount: STARTING_VAULT_SHARES, + }, + ExitVaultUnlocked { + id: lockup_id, // ID from user_a not from user_b + vault, + }, + ], + &[lp_token.to_coin(2)], ); - assert_err( - res, - ContractError::Std(NotFound { kind: "rover::adapters::vault::amount::VaultPositionAmountBase".to_string() }), - ); + assert_err(res, ContractError::NoPositionMatch(lockup_id.to_string())); } #[test] @@ -386,6 +397,7 @@ fn get_lockup_id(positions: &Positions) -> u64 { .unwrap() .amount .unlocking() + .positions() .first() .unwrap() .id diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs index d160a4199..1b3c801f1 100644 --- a/contracts/credit-manager/tests/test_vault_request_unlock.rs +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -100,7 +100,7 @@ fn test_request_when_unnecessary() { } #[test] -fn test_no_funds_for_request() { +fn test_no_vault_tokens_for_request() { let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); @@ -141,7 +141,7 @@ fn test_no_funds_for_request() { } #[test] -fn test_not_enough_funds_for_request() { +fn test_not_enough_vault_tokens_for_request() { let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); @@ -235,9 +235,10 @@ fn test_request_unlocked() { let res = mock.query_positions(&account_id); assert_eq!(res.vaults.len(), 1); let unlocking = res.vaults.first().unwrap().amount.unlocking(); - assert_eq!(unlocking.len(), 1); - let first = unlocking.first().unwrap(); - assert_eq!(first.amount, STARTING_VAULT_SHARES); + assert_eq!(unlocking.positions().len(), 1); + let positions = unlocking.positions(); + let first = positions.first().unwrap(); + assert_eq!(first.coin.amount, Uint128::new(23)); match leverage_vault.lockup.unwrap() { Duration::Height(_) => panic!("wrong type of duration"), @@ -258,7 +259,7 @@ fn test_request_unlocked() { match res.first().unwrap().release_at { Expiration::AtTime(t) => { assert_eq!(res.len(), 1); - assert_eq!(res.first().unwrap().coin.amount, STARTING_VAULT_SHARES); + assert_eq!(res.first().unwrap().base_token_amount, Uint128::new(23)); assert_eq!(t.seconds(), expected_unlock_time); } _ => panic!("Wrong type of expiration"), diff --git a/contracts/mars-oracle-adapter/src/contract.rs b/contracts/mars-oracle-adapter/src/contract.rs index 61e4a6167..17f992d5b 100644 --- a/contracts/mars-oracle-adapter/src/contract.rs +++ b/contracts/mars-oracle-adapter/src/contract.rs @@ -100,8 +100,11 @@ fn query_priceable_underlying(deps: Deps, coin: Coin) -> ContractResult match info.method { PricingMethod::PreviewRedeem => { let vault = VaultBase::new(info.addr); - let res = vault.query_preview_redeem(&deps.querier, coin.amount)?; - Ok(vec![res.coin]) + let amount = vault.query_preview_redeem(&deps.querier, coin.amount)?; + Ok(vec![Coin { + denom: info.base_denom, + amount, + }]) } }, _ => Ok(vec![coin]), @@ -130,11 +133,9 @@ fn calculate_preview_redeem( vault: &VaultBase, ) -> ContractResult { let total_issued = vault.query_total_vault_coins_issued(&deps.querier)?; - let assets_res = vault.query_preview_redeem(&deps.querier, total_issued)?; - let price_res = oracle.query_price(&deps.querier, &assets_res.coin.denom)?; - let value = price_res - .price - .checked_mul(assets_res.coin.amount.to_dec()?)?; + let amount = vault.query_preview_redeem(&deps.querier, total_issued)?; + let price_res = oracle.query_price(&deps.querier, &info.base_denom)?; + let value = price_res.price.checked_mul(amount.to_dec()?)?; let price = if value.is_zero() || total_issued.is_zero() { Decimal::zero() diff --git a/contracts/mars-oracle-adapter/src/msg.rs b/contracts/mars-oracle-adapter/src/msg.rs index 8ecb456e6..1721a947a 100644 --- a/contracts/mars-oracle-adapter/src/msg.rs +++ b/contracts/mars-oracle-adapter/src/msg.rs @@ -61,7 +61,7 @@ pub struct ConfigUpdates { #[cw_serde] pub struct VaultPricingInfo { pub vault_coin_denom: String, - pub req_denom: String, + pub base_denom: String, pub addr: Addr, pub method: PricingMethod, } diff --git a/contracts/mars-oracle-adapter/tests/helpers.rs b/contracts/mars-oracle-adapter/tests/helpers.rs index 47f9fcb79..32aa7dc3c 100644 --- a/contracts/mars-oracle-adapter/tests/helpers.rs +++ b/contracts/mars-oracle-adapter/tests/helpers.rs @@ -74,7 +74,7 @@ fn deploy_vault( &VaultInstantiateMsg { vault_token_denom: vault.clone().vault_coin_denom, lockup: vault.lockup, - req_denom: vault.clone().req_denom, + base_token_denom: vault.clone().req_denom, oracle, }, &[], @@ -87,7 +87,7 @@ fn deploy_vault( vault_coin_denom: vault.vault_coin_denom, addr, method: vault.pricing_method, - req_denom: vault.req_denom, + base_denom: vault.req_denom, }; fund_vault(app, &vault_pricing_info); vault_pricing_info diff --git a/contracts/mock-vault/Cargo.toml b/contracts/mock-vault/Cargo.toml index 26c243c9a..eb37d361d 100644 --- a/contracts/mock-vault/Cargo.toml +++ b/contracts/mock-vault/Cargo.toml @@ -15,8 +15,8 @@ library = [] [dependencies] rover = { version = "1.0", path = "../../packages/rover" } -cosmos-vault-standard = { path = "../../packages/cosmos-vault-standard", features = ["lockup"] } +cosmos-vault-standard = { git = "https://github.com/apollodao/cosmos-vault-standard", features = ["lockup", "force-unlock"] } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" cw-storage-plus = "0.16" diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index 937a999de..151bb78f1 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -1,3 +1,4 @@ +use cosmos_vault_standard::extensions::force_unlock::ForceUnlockExecuteMsg; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{coin, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; @@ -9,14 +10,15 @@ use crate::deposit::deposit; use crate::error::ContractResult; use crate::msg::InstantiateMsg; use crate::query::{ - query_coins_for_shares, query_lockup, query_lockup_duration, query_lockups, query_vault_info, - query_vault_token_supply, + query_lockup, query_lockup_duration, query_lockups, query_vault_info, query_vault_token_supply, + shares_to_base_denom_amount, }; use crate::state::{ - CHAIN_BANK, COIN_BALANCE, LOCKUP_TIME, NEXT_LOCKUP_ID, ORACLE, VAULT_TOKEN_DENOM, + CHAIN_BANK, COIN_BALANCE, LOCKUP_TIME, NEXT_LOCKUP_ID, ORACLE, TOTAL_VAULT_SHARES, + VAULT_TOKEN_DENOM, }; use crate::unlock::{request_unlock, withdraw_unlocked, withdraw_unlocking_force}; -use crate::withdraw::{withdraw, withdraw_force}; +use crate::withdraw::{redeem_force, withdraw}; pub const STARTING_VAULT_SHARES: Uint128 = Uint128::new(1_000_000); @@ -31,12 +33,13 @@ pub fn instantiate( _info: MessageInfo, msg: InstantiateMsg, ) -> ContractResult { - COIN_BALANCE.save(deps.storage, &coin(0, msg.req_denom))?; + COIN_BALANCE.save(deps.storage, &coin(0, msg.base_token_denom))?; LOCKUP_TIME.save(deps.storage, &msg.lockup)?; ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; VAULT_TOKEN_DENOM.save(deps.storage, &msg.vault_token_denom)?; CHAIN_BANK.save(deps.storage, &DEFAULT_VAULT_TOKEN_PREFUND)?; NEXT_LOCKUP_ID.save(deps.storage, &1)?; + TOTAL_VAULT_SHARES.save(deps.storage, &Uint128::zero())?; Ok(Response::default()) } @@ -56,8 +59,10 @@ pub fn execute( withdraw_unlocked(deps, env, &info.sender, lockup_id) } LockupExecuteMsg::Unlock { .. } => request_unlock(deps, env, info), - LockupExecuteMsg::ForceWithdraw { .. } => withdraw_force(deps, info), - LockupExecuteMsg::ForceWithdrawUnlocking { + }, + ExtensionExecuteMsg::ForceUnlock(force_msg) => match force_msg { + ForceUnlockExecuteMsg::ForceRedeem { .. } => redeem_force(deps, info), + ForceUnlockExecuteMsg::ForceWithdrawUnlocking { lockup_id, amount, .. } => withdraw_unlocking_force(deps, &info.sender, lockup_id, amount), }, @@ -71,7 +76,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> ContractResult { QueryMsg::TotalVaultTokenSupply {} => to_binary(&query_vault_token_supply(deps.storage)?), QueryMsg::Info {} => to_binary(&query_vault_info(deps)?), QueryMsg::PreviewRedeem { amount } => { - to_binary(&query_coins_for_shares(deps.storage, amount)?) + to_binary(&shares_to_base_denom_amount(deps.storage, amount)?) } QueryMsg::VaultExtension(ext) => match ext { ExtensionQueryMsg::Lockup(lockup_msg) => match lockup_msg { diff --git a/contracts/mock-vault/src/deposit.rs b/contracts/mock-vault/src/deposit.rs index 0526d5c48..6bd805e89 100644 --- a/contracts/mock-vault/src/deposit.rs +++ b/contracts/mock-vault/src/deposit.rs @@ -6,23 +6,20 @@ use crate::error::ContractError::WrongDenomSent; use crate::state::{CHAIN_BANK, COIN_BALANCE, ORACLE, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}; pub fn deposit(deps: DepsMut, info: MessageInfo) -> Result { - let total_shares_opt = TOTAL_VAULT_SHARES.may_load(deps.storage)?; + let total_shares = TOTAL_VAULT_SHARES.load(deps.storage)?; let oracle = ORACLE.load(deps.storage)?; let balance = COIN_BALANCE.load(deps.storage)?; - let shares_to_add = match total_shares_opt { - None => { - TOTAL_VAULT_SHARES.save(deps.storage, &STARTING_VAULT_SHARES)?; - STARTING_VAULT_SHARES - } - Some(total_shares) => { - let total_vault_value = oracle.query_total_value(&deps.querier, &[balance])?; - let assets_value = oracle.query_total_value(&deps.querier, &info.funds)?; - let shares_to_add = total_shares - .checked_multiply_ratio(assets_value.atomics(), total_vault_value.atomics())?; - TOTAL_VAULT_SHARES.save(deps.storage, &(total_shares + shares_to_add))?; - shares_to_add - } + let shares_to_add = if total_shares.is_zero() { + TOTAL_VAULT_SHARES.save(deps.storage, &STARTING_VAULT_SHARES)?; + STARTING_VAULT_SHARES + } else { + let total_vault_value = oracle.query_total_value(&deps.querier, &[balance])?; + let assets_value = oracle.query_total_value(&deps.querier, &info.funds)?; + let shares_to_add = total_shares + .checked_multiply_ratio(assets_value.atomics(), total_vault_value.atomics())?; + TOTAL_VAULT_SHARES.save(deps.storage, &(total_shares + shares_to_add))?; + shares_to_add }; let balance = COIN_BALANCE.load(deps.storage)?; diff --git a/contracts/mock-vault/src/error.rs b/contracts/mock-vault/src/error.rs index 842dcd98b..b67b90201 100644 --- a/contracts/mock-vault/src/error.rs +++ b/contracts/mock-vault/src/error.rs @@ -21,6 +21,9 @@ pub enum ContractError { #[error("This vault is not a locking vault, action not allowed")] NotLockingVault, + #[error("Not allowed to perform action")] + Unauthorized, + #[error("There is more time left on the lock period")] UnlockNotReady, diff --git a/contracts/mock-vault/src/msg.rs b/contracts/mock-vault/src/msg.rs index 2b516efa9..4ec3153fd 100644 --- a/contracts/mock-vault/src/msg.rs +++ b/contracts/mock-vault/src/msg.rs @@ -8,7 +8,7 @@ pub struct InstantiateMsg { /// Denom for vault token pub vault_token_denom: String, /// Denom required for entry. Also denom received on withdraw. - pub req_denom: String, + pub base_token_denom: String, /// Duration of unlock period pub lockup: Option, pub oracle: OracleUnchecked, diff --git a/contracts/mock-vault/src/query.rs b/contracts/mock-vault/src/query.rs index 49f290c8c..a164ff8ed 100644 --- a/contracts/mock-vault/src/query.rs +++ b/contracts/mock-vault/src/query.rs @@ -1,36 +1,32 @@ -use cosmwasm_std::{Coin, Deps, Order, StdError, StdResult, Storage, Uint128}; -use cw_utils::Duration; - use cosmos_vault_standard::extensions::lockup::Lockup; -use cosmos_vault_standard::msg::{AssetsResponse, VaultInfo}; +use cosmos_vault_standard::msg::VaultInfo; +use cosmwasm_std::{Deps, Order, StdError, StdResult, Storage, Uint128}; +use cw_utils::Duration; use crate::error::ContractError::NotLockingVault; use crate::error::ContractResult; use crate::state::{COIN_BALANCE, LOCKUPS, LOCKUP_TIME, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}; -pub fn query_coins_for_shares( +pub fn shares_to_base_denom_amount( storage: &dyn Storage, shares: Uint128, -) -> ContractResult { - let total_shares_opt = TOTAL_VAULT_SHARES.may_load(storage)?; +) -> ContractResult { + let total_shares = TOTAL_VAULT_SHARES.load(storage)?; let balance = COIN_BALANCE.load(storage)?; - match total_shares_opt { - Some(total_vault_shares) if !total_vault_shares.is_zero() => Ok(AssetsResponse { - coin: Coin { - denom: balance.denom, - amount: balance.amount.multiply_ratio(shares, total_vault_shares), - }, - }), - _ => Ok(AssetsResponse { coin: balance }), + + if total_shares.is_zero() { + Ok(balance.amount) + } else { + Ok(balance.amount.multiply_ratio(shares, total_shares)) } } pub fn query_vault_info(deps: Deps) -> ContractResult { - let req_denom = COIN_BALANCE.load(deps.storage)?.denom; - let vault_token_denom = VAULT_TOKEN_DENOM.load(deps.storage)?; + let base_token = COIN_BALANCE.load(deps.storage)?.denom; + let vault_token = VAULT_TOKEN_DENOM.load(deps.storage)?; Ok(VaultInfo { - req_denom, - vault_token_denom, + base_token, + vault_token, }) } @@ -56,8 +52,5 @@ pub fn query_lockups(deps: Deps, addr: String) -> ContractResult> { } pub fn query_vault_token_supply(storage: &dyn Storage) -> ContractResult { - let amount_issued = TOTAL_VAULT_SHARES - .may_load(storage)? - .unwrap_or(Uint128::zero()); - Ok(amount_issued) + Ok(TOTAL_VAULT_SHARES.load(storage)?) } diff --git a/contracts/mock-vault/src/unlock.rs b/contracts/mock-vault/src/unlock.rs index 5f2ebf72d..73f6561c6 100644 --- a/contracts/mock-vault/src/unlock.rs +++ b/contracts/mock-vault/src/unlock.rs @@ -1,13 +1,14 @@ -use cosmwasm_std::{Addr, DepsMut, Env, Event, MessageInfo, Response, StdResult, Uint128}; -use cw_utils::{Duration, Expiration}; - use cosmos_vault_standard::extensions::lockup::{ Lockup, UNLOCKING_POSITION_ATTR_KEY, UNLOCKING_POSITION_CREATED_EVENT_TYPE, }; +use cosmwasm_std::{ + Addr, BankMsg, Coin, CosmosMsg, DepsMut, Env, Event, MessageInfo, Response, StdResult, Uint128, +}; +use cw_utils::{Duration, Expiration}; use crate::error::ContractError; -use crate::state::{LOCKUPS, LOCKUP_TIME, NEXT_LOCKUP_ID}; -use crate::withdraw::{_exchange, get_vault_token}; +use crate::state::{COIN_BALANCE, LOCKUPS, LOCKUP_TIME, NEXT_LOCKUP_ID}; +use crate::withdraw::{get_vault_token, withdraw_state_update}; pub fn request_unlock( deps: DepsMut, @@ -18,6 +19,8 @@ pub fn request_unlock( let lockup_duration = lockup_time_opt.ok_or(ContractError::NotLockingVault {})?; let vault_token = get_vault_token(deps.storage, info.funds)?; + let lock_amount = withdraw_state_update(deps.storage, vault_token.amount)?; + let next_lockup_id = NEXT_LOCKUP_ID.load(deps.storage)?; let release_at = match lockup_duration { @@ -31,7 +34,7 @@ pub fn request_unlock( owner: info.sender.clone(), id: next_lockup_id, release_at, - coin: vault_token, + base_token_amount: lock_amount, }); Ok(lockups) })?; @@ -49,27 +52,36 @@ pub fn withdraw_unlocked( sender: &Addr, id: u64, ) -> Result { - let unlocking_positions = LOCKUPS + let lockups = LOCKUPS .may_load(deps.storage, sender.clone())? .ok_or(ContractError::UnlockRequired {})?; - let matching_position = unlocking_positions + let matching_position = lockups .iter() .find(|p| p.id == id) .ok_or(ContractError::UnlockRequired {})? .clone(); + if &matching_position.owner != sender { + return Err(ContractError::Unauthorized {}); + } + if !matching_position.release_at.is_expired(&env.block) { return Err(ContractError::UnlockNotReady {}); } - let remaining = unlocking_positions - .into_iter() - .filter(|p| p.id != id) - .collect(); + let remaining = lockups.into_iter().filter(|p| p.id != id).collect(); LOCKUPS.save(deps.storage, sender.clone(), &remaining)?; - _exchange(deps.storage, sender, matching_position.coin.amount) + let underlying_coin = COIN_BALANCE.load(deps.storage)?; + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: sender.to_string(), + amount: vec![Coin { + denom: underlying_coin.denom, + amount: matching_position.base_token_amount, + }], + }); + Ok(Response::new().add_message(transfer_msg)) } pub fn withdraw_unlocking_force( @@ -89,14 +101,22 @@ pub fn withdraw_unlocking_force( let amount_to_withdraw = match amounts { Some(a) => { - lockup.coin.amount -= a; + lockup.base_token_amount -= a; lockups.push(lockup.clone()); a } - None => lockup.coin.amount, + None => lockup.base_token_amount, }; LOCKUPS.save(deps.storage, sender.clone(), &lockups)?; - _exchange(deps.storage, sender, amount_to_withdraw) + let base_token = COIN_BALANCE.load(deps.storage)?; + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: sender.to_string(), + amount: vec![Coin { + denom: base_token.denom, + amount: amount_to_withdraw, + }], + }); + Ok(Response::new().add_message(transfer_msg)) } diff --git a/contracts/mock-vault/src/withdraw.rs b/contracts/mock-vault/src/withdraw.rs index a5c18cb66..3ae2d3d96 100644 --- a/contracts/mock-vault/src/withdraw.rs +++ b/contracts/mock-vault/src/withdraw.rs @@ -1,11 +1,10 @@ -use cosmos_vault_standard::msg::AssetsResponse; use cosmwasm_std::{ Addr, BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, Storage, Uint128, }; use crate::error::{ContractError, ContractResult}; -use crate::query::query_coins_for_shares; +use crate::query::shares_to_base_denom_amount; use crate::state::{CHAIN_BANK, COIN_BALANCE, LOCKUP_TIME, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}; pub fn withdraw(deps: DepsMut, info: MessageInfo) -> ContractResult { @@ -17,7 +16,7 @@ pub fn withdraw(deps: DepsMut, info: MessageInfo) -> ContractResult { _exchange(deps.storage, &info.sender, vault_tokens.amount) } -pub fn withdraw_force(deps: DepsMut, info: MessageInfo) -> ContractResult { +pub fn redeem_force(deps: DepsMut, info: MessageInfo) -> ContractResult { let vault_tokens = get_vault_token(deps.storage, info.funds.clone())?; _exchange(deps.storage, &info.sender, vault_tokens.amount) } @@ -28,10 +27,14 @@ pub fn _exchange( send_to: &Addr, shares: Uint128, ) -> ContractResult { - let res = withdraw_state_update(storage, shares)?; + let amount = withdraw_state_update(storage, shares)?; + let base_token = COIN_BALANCE.load(storage)?; let transfer_msg = CosmosMsg::Bank(BankMsg::Send { to_address: send_to.to_string(), - amount: vec![res.coin], + amount: vec![Coin { + denom: base_token.denom, + amount, + }], }); Ok(Response::new().add_message(transfer_msg)) } @@ -39,22 +42,20 @@ pub fn _exchange( pub fn withdraw_state_update( storage: &mut dyn Storage, shares: Uint128, -) -> ContractResult { - let res = query_coins_for_shares(storage, shares)?; - - TOTAL_VAULT_SHARES.update(storage, |current_amount| -> StdResult<_> { - Ok(current_amount - shares) - })?; - +) -> ContractResult { + let base_amount = shares_to_base_denom_amount(storage, shares)?; COIN_BALANCE.update(storage, |total| -> StdResult<_> { Ok(Coin { denom: total.denom, - amount: total.amount - res.coin.amount, + amount: total.amount - base_amount, }) })?; + let current_amount = TOTAL_VAULT_SHARES.load(storage)?; + TOTAL_VAULT_SHARES.save(storage, &(current_amount - shares))?; + mock_lp_token_burn(storage, shares)?; - Ok(res) + Ok(base_amount) } pub fn get_vault_token(storage: &mut dyn Storage, funds: Vec) -> ContractResult { diff --git a/packages/cosmos-vault-standard/Cargo.lock b/packages/cosmos-vault-standard/Cargo.lock deleted file mode 100644 index 485daeb76..000000000 --- a/packages/cosmos-vault-standard/Cargo.lock +++ /dev/null @@ -1,789 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "base64ct" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-buffer" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" -dependencies = [ - "generic-array", -] - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "const-oid" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" - -[[package]] -name = "cosmos-vault-standard" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-asset", - "cw-utils 0.16.0", - "cw20 0.16.0", - "schemars", - "serde", -] - -[[package]] -name = "cosmwasm-crypto" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7d659bbecbdca16878322becea489c0fcc36e784f41c942ea0171e9cf74179" -dependencies = [ - "digest 0.10.5", - "ed25519-zebra", - "k256", - "rand_core 0.6.4", - "thiserror", -] - -[[package]] -name = "cosmwasm-derive" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81b4676a316d3e4636f658d2d3aba9b4b30c3177e311bbda721b56d4a26342f" -dependencies = [ - "syn", -] - -[[package]] -name = "cosmwasm-schema" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88917b0e7c987fd99251f8bb7e06da7d705e7572e0732428b72f8bc1f17b1af5" -dependencies = [ - "cosmwasm-schema-derive", - "schemars", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "cosmwasm-schema-derive" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03bd1495b8bc0130529dad7e69bef8d800654b50a5d5fc150f1e795c4c24c5b4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "cosmwasm-std" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39d5725d2bd4b868284c127f7a904c93a9826550f30267b301484138db2eb9b" -dependencies = [ - "base64", - "cosmwasm-crypto", - "cosmwasm-derive", - "derivative", - "forward_ref", - "hex", - "schemars", - "serde", - "serde-json-wasm", - "thiserror", - "uint", -] - -[[package]] -name = "cpufeatures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" -dependencies = [ - "libc", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2b443d17d49dad5ef0ede301c3179cc923b8822f3393b4d2c28c269dd4a122" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "curve25519-dalek" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle", - "zeroize", -] - -[[package]] -name = "cw-asset" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7996c9c60e416aec195719137767d5cef8301237438bbabb772ee45a27f06e5e" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus 0.13.4", - "cw1155", - "cw20 0.13.4", - "schemars", - "serde", -] - -[[package]] -name = "cw-storage-plus" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw-storage-plus" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b6f91c0b94481a3e9ef1ceb183c37d00764f8751e39b45fc09f4d9b970d469" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw-utils" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw-utils" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a84c6c1c0acc3616398eba50783934bd6c964bad6974241eaee3460c8f5b26" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw2", - "schemars", - "semver", - "serde", - "thiserror", -] - -[[package]] -name = "cw1155" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42197c9a0fd844653177009125f24157e486578289327a3781923e427a8f9bbc" -dependencies = [ - "cosmwasm-std", - "cw-utils 0.13.4", - "schemars", - "serde", -] - -[[package]] -name = "cw2" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91398113b806f4d2a8d5f8d05684704a20ffd5968bf87e3473e1973710b884ad" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.16.0", - "schemars", - "serde", -] - -[[package]] -name = "cw20" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" -dependencies = [ - "cosmwasm-std", - "cw-utils 0.13.4", - "schemars", - "serde", -] - -[[package]] -name = "cw20" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45a8794a5dd33b66af34caee52a7beceb690856adcc1682b6e3db88b2cdee62" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils 0.16.0", - "schemars", - "serde", -] - -[[package]] -name = "der" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f" -dependencies = [ - "const-oid", - "zeroize", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] -name = "digest" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" -dependencies = [ - "block-buffer 0.10.3", - "crypto-common", - "subtle", -] - -[[package]] -name = "dyn-clone" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" - -[[package]] -name = "ecdsa" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85789ce7dfbd0f0624c07ef653a08bb2ebf43d3e16531361f46d36dd54334fed" -dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", -] - -[[package]] -name = "ed25519-zebra" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" -dependencies = [ - "curve25519-dalek", - "hex", - "rand_core 0.6.4", - "serde", - "sha2 0.9.9", - "thiserror", - "zeroize", -] - -[[package]] -name = "elliptic-curve" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" -dependencies = [ - "base16ct", - "crypto-bigint", - "der", - "digest 0.10.5", - "ff", - "generic-array", - "group", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "ff" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df689201f395c6b90dfe87127685f8dbfc083a5e779e613575d8bd7314300c3e" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "forward_ref" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" - -[[package]] -name = "generic-array" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "group" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7391856def869c1c81063a03457c676fbcd419709c3dfb33d8d319de484b154d" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.5", -] - -[[package]] -name = "itoa" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" - -[[package]] -name = "k256" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3636d281d46c3b64182eb3a0a42b7b483191a2ecc3f05301fa67403f7c9bc949" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "sha2 0.10.6", -] - -[[package]] -name = "libc" -version = "0.2.133" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "pkcs8" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "proc-macro2" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.7", -] - -[[package]] -name = "rfc6979" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c86280f057430a52f4861551b092a01b419b8eacefc7c995eacb9dc132fe32" -dependencies = [ - "crypto-bigint", - "hmac", - "zeroize", -] - -[[package]] -name = "ryu" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "schemars" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn", -] - -[[package]] -name = "sec1" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "semver" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" - -[[package]] -name = "serde" -version = "1.0.144" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-json-wasm" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.144" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_derive_internals" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "sha2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.5", -] - -[[package]] -name = "signature" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb766570a2825fa972bceff0d195727876a9cdf2460ab2e52d455dc2de47fd9" -dependencies = [ - "digest 0.10.5", - "rand_core 0.6.4", -] - -[[package]] -name = "spki" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "syn" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "uint" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unicode-ident" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "zeroize" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/packages/cosmos-vault-standard/Cargo.toml b/packages/cosmos-vault-standard/Cargo.toml deleted file mode 100644 index 9ac6f8206..000000000 --- a/packages/cosmos-vault-standard/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "cosmos-vault-standard" -version = "0.1.0" -edition = "2021" -authors = ["Sturdy "] - -[features] -default = [] -lockup = ["cw-utils"] -keeper = [] -cw20 = ["dep:cw20", "dep:cw-asset"] -cw4626 = ["cw20"] - -[dependencies] -cosmwasm-std = "1.1.0" -schemars = "0.8.8" -serde = { version = "1.0.137", default-features = false, features = ["derive"] } -cosmwasm-schema = { version = "1.1.0" } -cw-utils = { version = "0.16.0", optional = true } -cw20 = { version = "0.16.0", optional = true } -cw-asset = { version = "2.3.0", optional = true } diff --git a/packages/cosmos-vault-standard/README.md b/packages/cosmos-vault-standard/README.md deleted file mode 100644 index fad5d22f3..000000000 --- a/packages/cosmos-vault-standard/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# How to use Extensions - -For example, if you want to use the Keeper and Lockup extensions as well as a -custom extension: - -```rust -use custom_extension::{CustomExtension} -use vault_standard::{ExecuteMsg as VaultStandardExecuteMsg}; - -pub enum ExtensionExecuteMsg { - Keeper(KeeperExecuteMsg), - Lockup(LockupExecuteMsg), - Custom(CustomExtension) -} - -type ExecuteMsg = VaultStandardExecuteMsg; -``` diff --git a/packages/cosmos-vault-standard/src/cw4626.rs b/packages/cosmos-vault-standard/src/cw4626.rs deleted file mode 100644 index 11b395550..000000000 --- a/packages/cosmos-vault-standard/src/cw4626.rs +++ /dev/null @@ -1,88 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Binary, Empty, Uint128}; -use cw20::{Cw20Coin, Expiration, Logo}; - -use crate::msg::ExtensionExecuteMsg; - -#[cw_serde] -pub enum Cw4626ExecuteMsg { - //-------------------------------------------------------------------------------------------------- - // Standard CW20 ExecuteMsgs - //-------------------------------------------------------------------------------------------------- - /// Transfer is a base message to move tokens to another account without triggering actions - Transfer { - recipient: String, - amount: Uint128, - }, - /// Send is a base message to transfer tokens to a contract and trigger an action - /// on the receiving contract. - Send { - contract: String, - amount: Uint128, - msg: Binary, - }, - /// Only with "approval" extension. Allows spender to access an additional amount tokens - /// from the owner's (env.sender) account. If expires is Some(), overwrites current allowance - /// expiration with this one. - IncreaseAllowance { - spender: String, - amount: Uint128, - expires: Option, - }, - /// Only with "approval" extension. Lowers the spender's access of tokens - /// from the owner's (env.sender) account by amount. If expires is Some(), overwrites current - /// allowance expiration with this one. - DecreaseAllowance { - spender: String, - amount: Uint128, - expires: Option, - }, - /// Only with "approval" extension. Transfers amount tokens from owner -> recipient - /// if `env.sender` has sufficient pre-approval. - TransferFrom { - owner: String, - recipient: String, - amount: Uint128, - }, - /// Only with "approval" extension. Sends amount tokens from owner -> contract - /// if `env.sender` has sufficient pre-approval. - SendFrom { - owner: String, - contract: String, - amount: Uint128, - msg: Binary, - }, - /// Only with the "marketing" extension. If authorized, updates marketing metadata. - /// Setting None/null for any of these will leave it unchanged. - /// Setting Some("") will clear this field on the contract storage - UpdateMarketing { - /// A URL pointing to the project behind this token. - project: Option, - /// A longer description of the token and it's utility. Designed for tooltips or such - description: Option, - /// The address (if any) who can update this data structure - marketing: Option, - }, - /// If set as the "marketing" role on the contract, upload a new URL, SVG, or PNG for the token - UploadLogo(Logo), - //-------------------------------------------------------------------------------------------------- - // CW4626 ExecuteMsgs - //-------------------------------------------------------------------------------------------------- - Deposit { - cw20s: Option>, - /// An optional field containing the recipient of the vault token. If not set, the - /// caller address will be used instead. - recipient: Option, - }, - - Redeem { - /// An optional field containing which address should receive the withdrawn underlying assets. - /// If not set, the caller address will be used instead. - recipient: Option, - amount: Uint128, - }, - - Callback(S), - - VaultExtension(T), -} diff --git a/packages/cosmos-vault-standard/src/extensions/keeper.rs b/packages/cosmos-vault-standard/src/extensions/keeper.rs deleted file mode 100644 index f2af10414..000000000 --- a/packages/cosmos-vault-standard/src/extensions/keeper.rs +++ /dev/null @@ -1,38 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Addr; - -#[cw_serde] -pub struct KeeperJob { - //The numeric ID of the job - pub id: u64, - /// bool whether only whitelisted keepers can execute the job - pub whitelist: bool, - /// A list of whitelisted addresses that can execute the job - pub whitelisted_keepers: Vec, -} - -#[cw_serde] -pub enum KeeperExecuteMsg { - /// Callable by vault admin to whitelist a keeper to be able to execute a job - WhitelistKeeper { job_id: u64, keeper: String }, - /// Callable by vault admin to remove a keeper from the whitelist of a job - BlacklistKeeper { job_id: u64, keeper: String }, - /// Execute a keeper job. Should only be able to be called if - /// QueryMsg::KeeperJobReady returns true, and only by whitelisted - /// keepers if the whitelist bool on the KeeperJob is set to true. - ExecuteJob { job_id: u64 }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum KeeperQueryMsg { - /// Returns Vec - #[returns(Vec)] - KeeperJobs, - /// Returns Vec - #[returns(Vec)] - WhitelistedKeepers { job_id: u64 }, - /// Returns bool, whether the keeper job can be executed or not - #[returns(bool)] - KeeperJobReady { job_id: u64 }, -} diff --git a/packages/cosmos-vault-standard/src/extensions/lockup.rs b/packages/cosmos-vault-standard/src/extensions/lockup.rs deleted file mode 100644 index 76351cbd5..000000000 --- a/packages/cosmos-vault-standard/src/extensions/lockup.rs +++ /dev/null @@ -1,104 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Coin, Uint128}; -use cw_utils::Expiration; - -#[cfg(feature = "cw20")] -use cw20::Cw20Coin; - -/// Type for the unlocking position created event emitted on call to `Unlock`. -pub const UNLOCKING_POSITION_CREATED_EVENT_TYPE: &str = "unlocking_position_created"; -/// Key for the lockup id attribute in the "unlocking position created" event that -/// is emitted on call to `Unlock`. -pub const UNLOCKING_POSITION_ATTR_KEY: &str = "lockup_id"; - -#[cw_serde] -pub enum LockupExecuteMsg { - /// Unlock is called to initiate unlocking a locked position held by the - /// vault. - /// The caller must pass the native vault tokens in the funds field. - /// Emits an event with type `UNLOCK_EVENT_TYPE` with an attribute with key - /// `UNLOCKING_POSITION_ATTR_KEY` containing an u64 lockup_id. - /// Also encodes the u64 lockup ID as binary and returns it in the Response's - /// data field, so that it can be read by SubMsg replies. - /// - /// Like Redeem, this takes an amount so that the same API can be used for - /// CW4626 and native tokens. - Unlock { amount: Uint128 }, - - /// Withdraw an unlocking position that has finished unlocking. - WithdrawUnlocked { - /// An optional field containing which address should receive the - /// withdrawn underlying assets. If not set, the caller address will be - /// used instead. - recipient: Option, - /// The ID of the expired lockup to withdraw from. - /// If None is passed, the vault will attempt to withdraw all expired - /// lockup positions. Note that this can fail if there are too many - /// lockup positions and the `max_contract_gas` limit is hit. - lockup_id: u64, - }, - - /// Can be called by whitelisted addresses to bypass the lockup and - /// immediately return the underlying assets. Used in the event of - /// liquidation. The caller must pass the native vault tokens in the funds - /// field. - ForceWithdraw { - /// The address which should receive the withdrawn assets. If not set, - /// the caller address will be used instead. - recipient: Option, - /// The amount of vault tokens to force unlock. - amount: Uint128, - }, - - /// Force withdraw from a position that is already unlocking (Unlock has - /// already been called). - ForceWithdrawUnlocking { - /// The ID of the unlocking position from which to force withdraw - lockup_id: u64, - /// Optional amounts of each underlying asset to be force withdrawn. - /// If None is passed, the entire position will be force withdrawn. - /// Vaults MAY require the ratio of assets to be the same as the ratio - /// in the `deposit_assets` field returned by the `VaultInfo` query. - amount: Option, - #[cfg(feature = "cw20")] - cw20s_amounts: Option>, - /// The address which should receive the withdrawn assets. If not set, - /// the assets will be sent to the caller. - recipient: Option, - }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum LockupQueryMsg { - /// Returns a `Vec` containing all the currently unclaimed lockup - /// positions for the `owner`. - #[returns(Vec)] - Lockups { - /// The address of the owner of the lockup - owner: String, - /// Return results only after this lockup_id - start_after: Option, - /// Max amount of results to return - limit: Option, - }, - - /// Returns `Lockup` info about a specific lockup, by owner and ID. - #[returns(Lockup)] - Lockup { lockup_id: u64 }, - - /// Returns `cw_utils::Duration` duration of the lockup. - #[returns(cw_utils::Duration)] - LockupDuration {}, -} - -/// Info about a currently unlocking position. -#[cw_serde] -pub struct Lockup { - pub owner: Addr, - pub id: u64, - pub release_at: Expiration, - pub coin: Coin, - #[cfg(feature = "cw20")] - pub cw20s: Vec, -} diff --git a/packages/cosmos-vault-standard/src/extensions/mod.rs b/packages/cosmos-vault-standard/src/extensions/mod.rs deleted file mode 100644 index 10ce789e6..000000000 --- a/packages/cosmos-vault-standard/src/extensions/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[cfg(feature = "keeper")] -pub mod keeper; -#[cfg(feature = "lockup")] -pub mod lockup; diff --git a/packages/cosmos-vault-standard/src/lib.rs b/packages/cosmos-vault-standard/src/lib.rs deleted file mode 100644 index 3094eb313..000000000 --- a/packages/cosmos-vault-standard/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod extensions; -pub mod msg; - -#[cfg(feature = "cw4626")] -pub mod cw4626; diff --git a/packages/cosmos-vault-standard/src/msg.rs b/packages/cosmos-vault-standard/src/msg.rs deleted file mode 100644 index e22513711..000000000 --- a/packages/cosmos-vault-standard/src/msg.rs +++ /dev/null @@ -1,221 +0,0 @@ -#[cfg(feature = "cw20")] -use { - cosmwasm_std::{StdError, StdResult}, - cw20::Cw20Coin, - cw_asset::{Asset, AssetInfo}, - std::convert::TryFrom, -}; - -#[cfg(feature = "lockup")] -use crate::extensions::lockup::{LockupExecuteMsg, LockupQueryMsg}; - -#[cfg(feature = "keeper")] -use crate::extensions::keeper::{KeeperExecuteMsg, KeeperQueryMsg}; - -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Uint128; -use cosmwasm_std::{Coin, Empty}; -use schemars::JsonSchema; - -#[cw_serde] -pub enum ExecuteMsg { - /// Called to deposit into the vault. Native assets are passed in the funds - /// parameter. - Deposit { - /// With the cw20 feature, it is allowed to deposit CW20 tokens. These - /// must be passed in with to the `cw20s` argument and have allowance - /// pre-approved. - #[cfg(feature = "cw20")] - cw20s: Option>, - /// The optional recipient of the vault token. If not set, the caller - /// address will be used instead. - recipient: Option, - }, - - /// Called to redeem vault tokens and receive assets back from the vault. - /// The native vault token must be passed in the funds parameter, unless the - /// lockup extension is called, in which case the vault token has already - /// been passed to ExecuteMsg::Unlock. - Redeem { - /// An optional field containing which address should receive the - /// withdrawn underlying assets. If not set, the caller address will be - /// used instead. - recipient: Option, - /// The amount of vault tokens sent to the contract. In the case that - /// the vault token is a Cosmos native denom, we of course have this - /// information in the info.funds, but if the vault implements the Cw4626 - /// API, then we need this argument. We figured it's better to have one - /// API for both types of vaults, so we require this argument. - amount: Uint128, - }, - - /// Support for custom extensions - VaultExtension(T), -} - -/// Contains ExecuteMsgs of all enabled extensions. To enable extensions defined -/// outside of this create, you can define your own `ExtensionExecuteMsg` type -/// in your contract crate and pass it in as the generic parameter to ExecuteMsg -#[cw_serde] -pub enum ExtensionExecuteMsg { - #[cfg(feature = "keeper")] - Keeper(KeeperExecuteMsg), - #[cfg(feature = "lockup")] - Lockup(LockupExecuteMsg), -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg -where - T: JsonSchema, -{ - /// Returns `VaultStandardInfo` with information on the version of the vault - /// standard used as well as any enabled extensions. - #[returns(VaultStandardInfo)] - VaultStandardInfo {}, - - /// Returns `VaultInfo` representing vault requirements, lockup, & vault - /// token denom. - #[returns(VaultInfo)] - Info {}, - - /// Returns `Uint128` amount of vault tokens that will be returned for the - /// passed in assets. - /// - /// Allows an on-chain or off-chain user to simulate the effects of their - /// deposit at the current block, given current on-chain conditions. - /// - /// MUST return as close to and no more than the exact amount of Vault - /// shares that would be minted in a deposit call in the same transaction. - /// I.e. deposit should return the same or more shares as previewDeposit if - /// called in the same transaction. - /// - /// MUST NOT account for deposit limits like those returned from maxDeposit - /// and should always act as though the deposit would be accepted, - /// regardless if the user has enough tokens approved, etc. - /// - /// MUST be inclusive of deposit fees. Integrators should be aware of the - /// existence of deposit fees. - #[returns(Uint128)] - PreviewDeposit { - coins: Vec, - #[cfg(feature = "cw20")] - cw20s: Vec, - }, - - /// Returns `AssetsResponse` representing all the assets that would be - /// redeemed in exchange for vault tokens. Used by Rover to calculate vault - /// position values. - #[returns(AssetsResponse)] - PreviewRedeem { amount: Uint128 }, - - /// Returns `Option` maximum amount of assets that can be - /// deposited into the Vault for the `recipient`, through a call to Deposit. - /// - /// MUST return the maximum amount of assets deposit would allow to be - /// deposited for `recipient` and not cause a revert, which MUST NOT be higher - /// than the actual maximum that would be accepted (it should underestimate - /// if necessary). This assumes that the user has infinite assets, i.e. - /// MUST NOT rely on the asset balances of `recipient`. - /// - /// MUST factor in both global and user-specific limits, like if deposits - /// are entirely disabled (even temporarily) it MUST return 0. - #[returns(Option)] - MaxDeposit { recipient: String }, - - /// Returns `Option` maximum amount of Vault shares that can be redeemed - /// from the owner balance in the Vault, through a call to Withdraw - /// - /// TODO: Keep this? Could potentially be combined with MaxWithdraw to return - /// a MaxWithdrawResponse type that includes both max assets that can be - /// withdrawn as well as max vault shares that can be withdrawn in exchange - /// for assets. - #[returns(Option)] - MaxRedeem { owner: String }, - - /// Returns `AssetsResponse` assets managed by vault. - /// Useful for display purposes, and does not have to confer the exact - /// amount of underlying assets. - #[returns(AssetsResponse)] - TotalAssets {}, - - /// Returns `Uint128` total amount of vault tokens in circulation. - #[returns(Uint128)] - TotalVaultTokenSupply {}, - - /// The amount of shares that the vault would exchange for the amount of - /// assets provided, in an ideal scenario where all the conditions are met. - /// - /// Useful for display purposes and does not have to confer the exact amount - /// of shares returned by the vault if the passed in assets were deposited. - /// This calculation may not reflect the “per-user” price-per-share, and - /// instead should reflect the “average-user’s” price-per-share, meaning - /// what the average user should expect to see when exchanging to and from. - #[returns(Uint128)] - ConvertToShares { - coins: Vec, - #[cfg(feature = "cw20")] - cw20s: Vec, - }, - - /// Returns `AssetsResponse` assets that the Vault would exchange for - /// the amount of shares provided, in an ideal scenario where all the - /// conditions are met. - /// - /// Useful for display purposes and does not have to confer the exact amount - /// of assets returned by the vault if the passed in shares were withdrawn. - /// This calculation may not reflect the “per-user” price-per-share, and - /// instead should reflect the “average-user’s” price-per-share, meaning - /// what the average user should expect to see when exchanging to and from. - #[returns(AssetsResponse)] - ConvertToAssets { shares: Uint128 }, - - /// TODO: How to handle return derive? We must supply a type here, but we - /// don't know it. - #[returns(Empty)] - VaultExtension(T), -} - -/// Contains QueryMsgs of all enabled extensions. To enable extensions defined -/// outside of this create, you can define your own `ExtensionQueryMsg` type -/// in your contract crate and pass it in as the generic parameter to QueryMsg -#[cw_serde] -pub enum ExtensionQueryMsg { - #[cfg(feature = "keeper")] - Keeper(KeeperQueryMsg), - #[cfg(feature = "lockup")] - Lockup(LockupQueryMsg), -} - -/// Struct returned from QueryMsg::VaultStandardInfo with information about the -/// used version of the vault standard and any extensions used. -/// -/// This struct should be stored as an Item under the `vault_standard_info` key, -/// so that other contracts can do a RawQuery and read it directly from storage -/// instead of needing to do a costly SmartQuery. -#[cw_serde] -pub struct VaultStandardInfo { - /// The version of the vault standard used. A number, e.g. 1, 2, etc. - pub version: u16, - /// A list of vault standard extensions used by the vault. - /// E.g. ["cw20", "lockup", "keeper"] - pub extensions: Vec, -} - -#[cw_serde] -pub struct AssetsResponse { - pub coin: Coin, - #[cfg(feature = "cw20")] - pub cw20s: Vec, -} - -/// Returned by QueryMsg::Info and contains information about this vault -#[cw_serde] -pub struct VaultInfo { - pub req_denom: String, - #[cfg(feature = "cw20")] - pub deposit_cw20s: Vec, - /// Denom of vault token - pub vault_token_denom: String, -} diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 64f1de5e6..497bdbe62 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -15,7 +15,7 @@ mars-outpost = { version = "0.1", path = "../../packages/outpost" } mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } -cosmos-vault-standard = { path = "../cosmos-vault-standard", features = ["lockup"] } +cosmos-vault-standard = { git = "https://github.com/apollodao/cosmos-vault-standard", features = ["lockup", "force-unlock"] } cosmwasm-schema = "1.1" cosmwasm-std = "1.1" cw-storage-plus = "0.16" diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs index c16809006..e354e99f4 100644 --- a/packages/rover/src/adapters/oracle.rs +++ b/packages/rover/src/adapters/oracle.rs @@ -1,5 +1,3 @@ -use std::ops::Add; - use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, Coin, Decimal, QuerierWrapper, StdResult}; use mars_outpost::oracle::PriceResponse; @@ -60,8 +58,6 @@ impl Oracle { }) .collect::>>()? .iter() - .fold(Decimal::zero(), |total_value, amount| { - total_value.add(amount) - })) + .sum()) } } diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index 36c2b1b7c..36632090b 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -5,6 +5,7 @@ use cosmwasm_std::{ }; use mars_outpost::red_bank; +use mars_outpost::red_bank::Market; #[cw_serde] pub struct RedBankBase(T); @@ -73,4 +74,13 @@ impl RedBank { }))?; Ok(response.amount) } + + pub fn query_market(&self, querier: &QuerierWrapper, denom: &str) -> StdResult { + querier.query_wasm_smart( + self.address(), + &red_bank::QueryMsg::Market { + denom: denom.to_string(), + }, + ) + } } diff --git a/packages/rover/src/adapters/vault/amount.rs b/packages/rover/src/adapters/vault/amount.rs index 917d0880a..498f40e98 100644 --- a/packages/rover/src/adapters/vault/amount.rs +++ b/packages/rover/src/adapters/vault/amount.rs @@ -1,55 +1,22 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Uint128; -use std::ops::Add; -use crate::adapters::vault::VaultUnlockingPosition; +use crate::adapters::vault::{ + UnlockingChange, UpdateType, VaultPositionUpdate, VaultUnlockingPosition, +}; use crate::error::{ContractError, ContractResult}; #[cw_serde] -pub enum UpdateType { - Increment(Uint128), - Decrement(Uint128), +pub enum VaultPositionAmount { + Unlocked(VaultAmount), + Locking(LockingVaultAmount), } -#[cw_serde] -pub enum UnlockingChange { - Add(VaultUnlockingPosition), - Decrement { id: u64, amount: Uint128 }, -} - -#[cw_serde] -pub enum VaultPositionUpdate { - Unlocked(UpdateType), - Locked(UpdateType), - Unlocking(UnlockingChange), -} - -impl VaultPositionUpdate { - pub fn default_amount(&self) -> VaultPositionAmount { - match self { - VaultPositionUpdate::Unlocked { .. } => { - VaultPositionAmount::Unlocked(VaultAmount(Uint128::zero())) - } - _ => VaultPositionAmount::Locking(LockingVaultAmount { - locked: VaultAmount(Uint128::zero()), - unlocking: UnlockingPositions(vec![]), - }), - } - } -} - -pub type VaultPositionAmount = VaultPositionAmountBase; - -impl Total for VaultPositionAmount { - fn total(&self) -> Uint128 { - match self { - VaultPositionAmount::Unlocked(a) => a.total(), - VaultPositionAmount::Locking(a) => a.total(), - } +impl VaultPositionAmount { + pub fn is_empty(&self) -> bool { + self.unlocked().is_zero() && self.locked().is_zero() && self.unlocking().is_empty() } -} -impl VaultPositionAmount { pub fn unlocked(&self) -> Uint128 { match self { VaultPositionAmount::Unlocked(amount) => amount.total(), @@ -64,10 +31,10 @@ impl VaultPositionAmount { } } - pub fn unlocking(&self) -> Vec { + pub fn unlocking(&self) -> UnlockingPositions { match self { - VaultPositionAmount::Locking(amount) => amount.unlocking.positions(), - _ => vec![], + VaultPositionAmount::Locking(amount) => amount.unlocking.clone(), + _ => UnlockingPositions(vec![]), } } @@ -109,30 +76,18 @@ impl VaultPositionAmount { } } -pub trait Total { - fn total(&self) -> Uint128; -} - -#[cw_serde] -pub enum VaultPositionAmountBase -where - U: Total, - L: Total, -{ - Unlocked(U), - Locking(L), -} - #[cw_serde] pub struct VaultAmount(Uint128); -impl Total for VaultAmount { - fn total(&self) -> Uint128 { +impl VaultAmount { + pub fn new(amount: Uint128) -> VaultAmount { + VaultAmount(amount) + } + + pub fn total(&self) -> Uint128 { self.0 } -} -impl VaultAmount { pub fn increment(&mut self, amount: Uint128) -> ContractResult<()> { self.0 = self.0.checked_add(amount)?; Ok(()) @@ -150,22 +105,24 @@ pub struct LockingVaultAmount { pub unlocking: UnlockingPositions, } -impl Total for LockingVaultAmount { - fn total(&self) -> Uint128 { - self.locked.total().add(self.unlocking.total()) - } -} - #[cw_serde] pub struct UnlockingPositions(Vec); impl UnlockingPositions { + pub fn new(positions: Vec) -> UnlockingPositions { + UnlockingPositions(positions) + } + pub fn positions(&self) -> Vec { self.0.clone() } pub fn total(&self) -> Uint128 { - self.0.iter().map(|u| u.amount).sum() + self.0.iter().map(|u| u.coin.amount).sum() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() } pub fn add(&mut self, position: VaultUnlockingPosition) -> ContractResult<()> { @@ -177,11 +134,11 @@ impl UnlockingPositions { let res = self.0.iter_mut().find(|p| p.id == id); match res { Some(p) => { - let new_amount = p.amount.checked_sub(amount)?; + let new_amount = p.coin.amount.checked_sub(amount)?; if new_amount.is_zero() { self.remove(id)?; } else { - p.amount = new_amount; + p.coin.amount = new_amount; } } None => return Err(ContractError::NoPositionMatch(id.to_string())), diff --git a/packages/rover/src/adapters/vault/base.rs b/packages/rover/src/adapters/vault/base.rs index 0a2db0582..19fad6a6f 100644 --- a/packages/rover/src/adapters/vault/base.rs +++ b/packages/rover/src/adapters/vault/base.rs @@ -1,25 +1,24 @@ -use cosmos_vault_standard::extensions::lockup::LockupExecuteMsg::{ - ForceWithdraw, ForceWithdrawUnlocking, Unlock, WithdrawUnlocked, -}; -use cosmos_vault_standard::extensions::lockup::LockupQueryMsg::{ - Lockup as LockupQueryMsg, LockupDuration, -}; -use cosmos_vault_standard::msg::{ - AssetsResponse, ExecuteMsg, ExtensionExecuteMsg, ExtensionQueryMsg, QueryMsg, VaultInfo, +use cosmos_vault_standard::extensions::force_unlock::ForceUnlockExecuteMsg::{ + ForceRedeem, ForceWithdrawUnlocking, }; use std::hash::Hash; -use cosmos_vault_standard::extensions::lockup::Lockup; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, Decimal, QuerierWrapper, + to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, QuerierWrapper, QueryRequest, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, }; use cw_utils::Duration; -use crate::adapters::vault::VaultPositionAmount; -use crate::error::ContractError; -use crate::error::ContractError::InvalidVaultConfig; +use cosmos_vault_standard::extensions::lockup::Lockup; +use cosmos_vault_standard::extensions::lockup::LockupExecuteMsg::{Unlock, WithdrawUnlocked}; +use cosmos_vault_standard::extensions::lockup::LockupQueryMsg::{ + Lockup as LockupQueryMsg, LockupDuration, +}; +use cosmos_vault_standard::msg::{ + ExecuteMsg, ExtensionExecuteMsg, ExtensionQueryMsg, QueryMsg, VaultInfo, +}; + use crate::traits::Stringify; pub const VAULT_REQUEST_REPLY_ID: u64 = 10_001; @@ -27,41 +26,6 @@ pub const VAULT_REQUEST_REPLY_ID: u64 = 10_001; type VaultExecuteMsg = ExecuteMsg; type VaultQueryMsg = QueryMsg; -#[cw_serde] -pub struct VaultUnlockingPosition { - /// Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call. - pub id: u64, - /// Number of vault tokens - pub amount: Uint128, -} - -#[cw_serde] -pub struct VaultPosition { - pub vault: Vault, - pub amount: VaultPositionAmount, -} - -#[cw_serde] -pub struct VaultConfig { - pub deposit_cap: Coin, - pub max_ltv: Decimal, - pub liquidation_threshold: Decimal, - pub whitelisted: bool, -} - -impl VaultConfig { - pub fn check(&self) -> Result<(), ContractError> { - let max_ltv_too_big = self.max_ltv > Decimal::one(); - let lqt_too_big = self.liquidation_threshold > Decimal::one(); - let max_ltv_bigger_than_lqt = self.max_ltv > self.liquidation_threshold; - - if max_ltv_too_big || lqt_too_big || max_ltv_bigger_than_lqt { - return Err(InvalidVaultConfig {}); - } - Ok(()) - } -} - #[cw_serde] #[derive(Eq, Hash)] pub struct VaultBase { @@ -113,7 +77,10 @@ impl Vault { let deposit_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![coin.clone()], - msg: to_binary(&VaultExecuteMsg::Deposit { recipient: None })?, + msg: to_binary(&VaultExecuteMsg::Deposit { + amount: coin.amount, + recipient: None, + })?, }); Ok(deposit_msg) } @@ -128,12 +95,12 @@ impl Vault { let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![Coin { - denom: vault_info.vault_token_denom, + denom: vault_info.vault_token, amount, }], msg: to_binary( &(if force { - VaultExecuteMsg::VaultExtension(ExtensionExecuteMsg::Lockup(ForceWithdraw { + VaultExecuteMsg::VaultExtension(ExtensionExecuteMsg::ForceUnlock(ForceRedeem { recipient: None, amount, })) @@ -157,7 +124,7 @@ impl Vault { contract_addr: self.address.to_string(), funds: vec![], msg: to_binary(&VaultExecuteMsg::VaultExtension( - ExtensionExecuteMsg::Lockup(ForceWithdrawUnlocking { + ExtensionExecuteMsg::ForceUnlock(ForceWithdrawUnlocking { lockup_id, amount, recipient: None, @@ -226,7 +193,7 @@ impl Vault { let vault_info = self.query_info(querier)?; let res: BalanceResponse = querier.query(&QueryRequest::Bank(BankQuery::Balance { address: addr.to_string(), - denom: vault_info.vault_token_denom, + denom: vault_info.vault_token, }))?; Ok(res.amount.amount) } @@ -235,7 +202,7 @@ impl Vault { &self, querier: &QuerierWrapper, amount: Uint128, - ) -> StdResult { + ) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), msg: to_binary(&VaultQueryMsg::PreviewRedeem { amount })?, diff --git a/packages/rover/src/adapters/vault/config.rs b/packages/rover/src/adapters/vault/config.rs new file mode 100644 index 000000000..a7047e650 --- /dev/null +++ b/packages/rover/src/adapters/vault/config.rs @@ -0,0 +1,26 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Coin, Decimal}; + +use crate::error::ContractError; +use crate::error::ContractError::InvalidVaultConfig; + +#[cw_serde] +pub struct VaultConfig { + pub deposit_cap: Coin, + pub max_ltv: Decimal, + pub liquidation_threshold: Decimal, + pub whitelisted: bool, +} + +impl VaultConfig { + pub fn check(&self) -> Result<(), ContractError> { + let max_ltv_too_big = self.max_ltv > Decimal::one(); + let lqt_too_big = self.liquidation_threshold > Decimal::one(); + let max_ltv_bigger_than_lqt = self.max_ltv > self.liquidation_threshold; + + if max_ltv_too_big || lqt_too_big || max_ltv_bigger_than_lqt { + return Err(InvalidVaultConfig {}); + } + Ok(()) + } +} diff --git a/packages/rover/src/adapters/vault/mod.rs b/packages/rover/src/adapters/vault/mod.rs index 9c66ae9c1..c9aaf16db 100644 --- a/packages/rover/src/adapters/vault/mod.rs +++ b/packages/rover/src/adapters/vault/mod.rs @@ -1,5 +1,11 @@ mod amount; mod base; +mod config; +mod position; +mod update; pub use self::amount::*; pub use self::base::*; +pub use self::config::*; +pub use self::position::*; +pub use self::update::*; diff --git a/packages/rover/src/adapters/vault/position.rs b/packages/rover/src/adapters/vault/position.rs new file mode 100644 index 000000000..e3fd070ad --- /dev/null +++ b/packages/rover/src/adapters/vault/position.rs @@ -0,0 +1,19 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Coin; + +use crate::adapters::vault::Vault; +use crate::adapters::vault::VaultPositionAmount; + +#[cw_serde] +pub struct VaultUnlockingPosition { + /// Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call. + pub id: u64, + /// Coins that are awaiting to be unlocked (underlying, not vault tokens) + pub coin: Coin, +} + +#[cw_serde] +pub struct VaultPosition { + pub vault: Vault, + pub amount: VaultPositionAmount, +} diff --git a/packages/rover/src/adapters/vault/update.rs b/packages/rover/src/adapters/vault/update.rs new file mode 100644 index 000000000..51e8ec120 --- /dev/null +++ b/packages/rover/src/adapters/vault/update.rs @@ -0,0 +1,40 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Uint128; + +use crate::adapters::vault::{ + LockingVaultAmount, UnlockingPositions, VaultAmount, VaultPositionAmount, + VaultUnlockingPosition, +}; + +#[cw_serde] +pub enum UpdateType { + Increment(Uint128), + Decrement(Uint128), +} + +#[cw_serde] +pub enum UnlockingChange { + Add(VaultUnlockingPosition), + Decrement { id: u64, amount: Uint128 }, +} + +#[cw_serde] +pub enum VaultPositionUpdate { + Unlocked(UpdateType), + Locked(UpdateType), + Unlocking(UnlockingChange), +} + +impl VaultPositionUpdate { + pub fn default_amount(&self) -> VaultPositionAmount { + match self { + VaultPositionUpdate::Unlocked { .. } => { + VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::zero())) + } + _ => VaultPositionAmount::Locking(LockingVaultAmount { + locked: VaultAmount::new(Uint128::zero()), + unlocking: UnlockingPositions::new(vec![]), + }), + } + } +} diff --git a/schemas/credit-manager/credit-manager.json b/schemas/credit-manager/credit-manager.json index bbdf51f5c..f41bc89b0 100644 --- a/schemas/credit-manager/credit-manager.json +++ b/schemas/credit-manager/credit-manager.json @@ -1646,6 +1646,21 @@ "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" }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, "LockingVaultAmount": { "type": "object", "required": [ @@ -1695,7 +1710,7 @@ ], "properties": { "amount": { - "$ref": "#/definitions/VaultPositionAmountBase_for_VaultAmount_and_LockingVaultAmount" + "$ref": "#/definitions/VaultPositionAmount" }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" @@ -1703,7 +1718,7 @@ }, "additionalProperties": false }, - "VaultPositionAmountBase_for_VaultAmount_and_LockingVaultAmount": { + "VaultPositionAmount": { "oneOf": [ { "type": "object", @@ -1750,15 +1765,15 @@ "VaultUnlockingPosition": { "type": "object", "required": [ - "amount", + "coin", "id" ], "properties": { - "amount": { - "description": "Number of vault tokens", + "coin": { + "description": "Coins that are awaiting to be unlocked (underlying, not vault tokens)", "allOf": [ { - "$ref": "#/definitions/Uint128" + "$ref": "#/definitions/Coin" } ] }, @@ -2020,7 +2035,7 @@ ], "properties": { "amount": { - "$ref": "#/definitions/VaultPositionAmountBase_for_VaultAmount_and_LockingVaultAmount" + "$ref": "#/definitions/VaultPositionAmount" }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" @@ -2028,7 +2043,7 @@ }, "additionalProperties": false }, - "VaultPositionAmountBase_for_VaultAmount_and_LockingVaultAmount": { + "VaultPositionAmount": { "oneOf": [ { "type": "object", @@ -2059,15 +2074,15 @@ "VaultUnlockingPosition": { "type": "object", "required": [ - "amount", + "coin", "id" ], "properties": { - "amount": { - "description": "Number of vault tokens", + "coin": { + "description": "Coins that are awaiting to be unlocked (underlying, not vault tokens)", "allOf": [ { - "$ref": "#/definitions/Uint128" + "$ref": "#/definitions/Coin" } ] }, diff --git a/schemas/mars-oracle-adapter/mars-oracle-adapter.json b/schemas/mars-oracle-adapter/mars-oracle-adapter.json index 7cd08574d..9786cba55 100644 --- a/schemas/mars-oracle-adapter/mars-oracle-adapter.json +++ b/schemas/mars-oracle-adapter/mars-oracle-adapter.json @@ -44,20 +44,20 @@ "type": "object", "required": [ "addr", + "base_denom", "method", - "req_denom", "vault_coin_denom" ], "properties": { "addr": { "$ref": "#/definitions/Addr" }, + "base_denom": { + "type": "string" + }, "method": { "$ref": "#/definitions/PricingMethod" }, - "req_denom": { - "type": "string" - }, "vault_coin_denom": { "type": "string" } @@ -141,20 +141,20 @@ "type": "object", "required": [ "addr", + "base_denom", "method", - "req_denom", "vault_coin_denom" ], "properties": { "addr": { "$ref": "#/definitions/Addr" }, + "base_denom": { + "type": "string" + }, "method": { "$ref": "#/definitions/PricingMethod" }, - "req_denom": { - "type": "string" - }, "vault_coin_denom": { "type": "string" } @@ -322,20 +322,20 @@ "type": "object", "required": [ "addr", + "base_denom", "method", - "req_denom", "vault_coin_denom" ], "properties": { "addr": { "$ref": "#/definitions/Addr" }, + "base_denom": { + "type": "string" + }, "method": { "$ref": "#/definitions/PricingMethod" }, - "req_denom": { - "type": "string" - }, "vault_coin_denom": { "type": "string" } @@ -430,20 +430,20 @@ "type": "object", "required": [ "addr", + "base_denom", "method", - "req_denom", "vault_coin_denom" ], "properties": { "addr": { "$ref": "#/definitions/Addr" }, + "base_denom": { + "type": "string" + }, "method": { "$ref": "#/definitions/PricingMethod" }, - "req_denom": { - "type": "string" - }, "vault_coin_denom": { "type": "string" } diff --git a/schemas/mock-vault/mock-vault.json b/schemas/mock-vault/mock-vault.json index 182b23b85..0e6775621 100644 --- a/schemas/mock-vault/mock-vault.json +++ b/schemas/mock-vault/mock-vault.json @@ -7,11 +7,15 @@ "title": "InstantiateMsg", "type": "object", "required": [ + "base_token_denom", "oracle", - "req_denom", "vault_token_denom" ], "properties": { + "base_token_denom": { + "description": "Denom required for entry. Also denom received on withdraw.", + "type": "string" + }, "lockup": { "description": "Duration of unlock period", "anyOf": [ @@ -26,10 +30,6 @@ "oracle": { "$ref": "#/definitions/OracleBase_for_String" }, - "req_denom": { - "description": "Denom required for entry. Also denom received on withdraw.", - "type": "string" - }, "vault_token_denom": { "description": "Denom for vault token", "type": "string" @@ -89,7 +89,18 @@ "properties": { "deposit": { "type": "object", + "required": [ + "amount" + ], "properties": { + "amount": { + "description": "The amount of base tokens to deposit.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, "recipient": { "description": "The optional recipient of the vault token. If not set, the caller address will be used instead.", "type": [ @@ -125,7 +136,7 @@ ] }, "recipient": { - "description": "An optional field containing which address should receive the withdrawn underlying assets. If not set, the caller address will be used instead.", + "description": "An optional field containing which address should receive the withdrawn base tokens. If not set, the caller address will be used instead.", "type": [ "string", "null" @@ -166,26 +177,50 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "force_unlock" + ], + "properties": { + "force_unlock": { + "$ref": "#/definitions/ForceUnlockExecuteMsg" + } + }, + "additionalProperties": false } ] }, - "LockupExecuteMsg": { + "ForceUnlockExecuteMsg": { "oneOf": [ { - "description": "Unlock is called to initiate unlocking a locked position held by the vault. The caller must pass the native vault tokens in the funds field. Emits an event with type `UNLOCK_EVENT_TYPE` with an attribute with key `UNLOCKING_POSITION_ATTR_KEY` containing an u64 lockup_id. Also encodes the u64 lockup ID as binary and returns it in the Response's data field, so that it can be read by SubMsg replies.\n\nLike Redeem, this takes an amount so that the same API can be used for CW4626 and native tokens.", + "description": "Can be called by whitelisted addresses to bypass the lockup and immediately return the base tokens. Used in the event of liquidation. The caller must pass the native vault tokens in the funds field.", "type": "object", "required": [ - "unlock" + "force_redeem" ], "properties": { - "unlock": { + "force_redeem": { "type": "object", "required": [ "amount" ], "properties": { "amount": { - "$ref": "#/definitions/Uint128" + "description": "The amount of vault tokens to force redeem.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "recipient": { + "description": "The address which should receive the withdrawn assets. If not set, the caller address will be used instead.", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false @@ -194,26 +229,37 @@ "additionalProperties": false }, { - "description": "Withdraw an unlocking position that has finished unlocking.", + "description": "Force withdraw from a position that is already unlocking (Unlock has already been called).", "type": "object", "required": [ - "withdraw_unlocked" + "force_withdraw_unlocking" ], "properties": { - "withdraw_unlocked": { + "force_withdraw_unlocking": { "type": "object", "required": [ "lockup_id" ], "properties": { + "amount": { + "description": "Optional amount of base tokens to be force withdrawn. If None is passed, the entire position will be force withdrawn.", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, "lockup_id": { - "description": "The ID of the expired lockup to withdraw from. If None is passed, the vault will attempt to withdraw all expired lockup positions. Note that this can fail if there are too many lockup positions and the `max_contract_gas` limit is hit.", + "description": "The ID of the unlocking position from which to force withdraw", "type": "integer", "format": "uint64", "minimum": 0.0 }, "recipient": { - "description": "An optional field containing which address should receive the withdrawn underlying assets. If not set, the caller address will be used instead.", + "description": "The address which should receive the withdrawn assets. If not set, the assets will be sent to the caller.", "type": [ "string", "null" @@ -224,34 +270,26 @@ } }, "additionalProperties": false - }, + } + ] + }, + "LockupExecuteMsg": { + "oneOf": [ { - "description": "Can be called by whitelisted addresses to bypass the lockup and immediately return the underlying assets. Used in the event of liquidation. The caller must pass the native vault tokens in the funds field.", + "description": "Unlock is called to initiate unlocking a locked position held by the vault. The caller must pass the native vault tokens in the funds field. Emits an event with type `UNLOCKING_POSITION_CREATED_EVENT_TYPE` with an attribute with key `UNLOCKING_POSITION_ATTR_KEY` containing an u64 lockup_id. Also encodes the u64 lockup ID as binary and returns it in the Response's data field, so that it can be read by SubMsg replies.\n\nLike Redeem, this takes an amount so that the same API can be used for CW4626 and native tokens.", "type": "object", "required": [ - "force_withdraw" + "unlock" ], "properties": { - "force_withdraw": { + "unlock": { "type": "object", "required": [ "amount" ], "properties": { "amount": { - "description": "The amount of vault tokens to force unlock.", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "recipient": { - "description": "The address which should receive the withdrawn assets. If not set, the caller address will be used instead.", - "type": [ - "string", - "null" - ] + "$ref": "#/definitions/Uint128" } }, "additionalProperties": false @@ -260,37 +298,26 @@ "additionalProperties": false }, { - "description": "Force withdraw from a position that is already unlocking (Unlock has already been called).", + "description": "Withdraw an unlocking position that has finished unlocking.", "type": "object", "required": [ - "force_withdraw_unlocking" + "withdraw_unlocked" ], "properties": { - "force_withdraw_unlocking": { + "withdraw_unlocked": { "type": "object", "required": [ "lockup_id" ], "properties": { - "amount": { - "description": "Optional amounts of each underlying asset to be force withdrawn. If None is passed, the entire position will be force withdrawn. Vaults MAY require the ratio of assets to be the same as the ratio in the `deposit_assets` field returned by the `VaultInfo` query.", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, "lockup_id": { - "description": "The ID of the unlocking position from which to force withdraw", + "description": "The ID of the expired lockup to withdraw from.", "type": "integer", "format": "uint64", "minimum": 0.0 }, "recipient": { - "description": "The address which should receive the withdrawn assets. If not set, the assets will be sent to the caller.", + "description": "An optional field containing which address should receive the withdrawn base tokens. If not set, the caller address will be used instead.", "type": [ "string", "null" @@ -352,14 +379,11 @@ "preview_deposit": { "type": "object", "required": [ - "coins" + "amount" ], "properties": { - "coins": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } + "amount": { + "$ref": "#/definitions/Uint128" } }, "additionalProperties": false @@ -368,7 +392,7 @@ "additionalProperties": false }, { - "description": "Returns `AssetsResponse` representing all the assets that would be redeemed in exchange for vault tokens. Used by Rover to calculate vault position values.", + "description": "Returns the number of base tokens that would be redeemed in exchange `amount` for vault tokens. Used by Rover to calculate vault position values.", "type": "object", "required": [ "preview_redeem" @@ -390,51 +414,7 @@ "additionalProperties": false }, { - "description": "Returns `Option` maximum amount of assets that can be deposited into the Vault for the `recipient`, through a call to Deposit.\n\nMUST return the maximum amount of assets deposit would allow to be deposited for `recipient` and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary). This assumes that the user has infinite assets, i.e. MUST NOT rely on the asset balances of `recipient`.\n\nMUST factor in both global and user-specific limits, like if deposits are entirely disabled (even temporarily) it MUST return 0.", - "type": "object", - "required": [ - "max_deposit" - ], - "properties": { - "max_deposit": { - "type": "object", - "required": [ - "recipient" - ], - "properties": { - "recipient": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns `Option` maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, through a call to Withdraw\n\nTODO: Keep this? Could potentially be combined with MaxWithdraw to return a MaxWithdrawResponse type that includes both max assets that can be withdrawn as well as max vault shares that can be withdrawn in exchange for assets.", - "type": "object", - "required": [ - "max_redeem" - ], - "properties": { - "max_redeem": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "owner": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns `AssetsResponse` assets managed by vault. Useful for display purposes, and does not have to confer the exact amount of underlying assets.", + "description": "Returns the amount of assets managed by the vault denominated in base tokens. Useful for display purposes, and does not have to confer the exact amount of base tokens.", "type": "object", "required": [ "total_assets" @@ -471,14 +451,11 @@ "convert_to_shares": { "type": "object", "required": [ - "coins" + "amount" ], "properties": { - "coins": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } + "amount": { + "$ref": "#/definitions/Uint128" } }, "additionalProperties": false @@ -487,7 +464,7 @@ "additionalProperties": false }, { - "description": "Returns `AssetsResponse` assets that the Vault would exchange for the amount of shares provided, in an ideal scenario where all the conditions are met.\n\nUseful for display purposes and does not have to confer the exact amount of assets returned by the vault if the passed in shares were withdrawn. This calculation may not reflect the “per-user” price-per-share, and instead should reflect the “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and from.", + "description": "Returns the amount of base tokens that the Vault would exchange for the `amount` of shares provided, in an ideal scenario where all the conditions are met.\n\nUseful for display purposes and does not have to confer the exact amount of assets returned by the vault if the passed in shares were withdrawn. This calculation may not reflect the “per-user” price-per-share, and instead should reflect the “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and from.", "type": "object", "required": [ "convert_to_assets" @@ -496,10 +473,10 @@ "convert_to_assets": { "type": "object", "required": [ - "shares" + "amount" ], "properties": { - "shares": { + "amount": { "$ref": "#/definitions/Uint128" } }, @@ -523,21 +500,6 @@ } ], "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, "ExtensionQueryMsg": { "description": "Contains QueryMsgs of all enabled extensions. To enable extensions defined outside of this create, you can define your own `ExtensionQueryMsg` type in your contract crate and pass it in as the generic parameter to QueryMsg", "oneOf": [ @@ -649,38 +611,9 @@ "responses": { "convert_to_assets": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AssetsResponse", - "type": "object", - "required": [ - "coin" - ], - "properties": { - "coin": { - "$ref": "#/definitions/Coin" - } - }, - "additionalProperties": false, - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } + "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" }, "convert_to_shares": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -694,83 +627,21 @@ "description": "Returned by QueryMsg::Info and contains information about this vault", "type": "object", "required": [ - "req_denom", - "vault_token_denom" + "base_token", + "vault_token" ], "properties": { - "req_denom": { + "base_token": { + "description": "The token that is accepted for deposits, withdrawals and used for accounting in the vault. The denom if it is a native token and the contract address if it is a cw20 token.", "type": "string" }, - "vault_token_denom": { - "description": "Denom of vault token", + "vault_token": { + "description": "Vault token. The denom if it is a native token and the contract address if it is a cw20 token.", "type": "string" } }, "additionalProperties": false }, - "max_deposit": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Nullable_AssetsResponse", - "anyOf": [ - { - "$ref": "#/definitions/AssetsResponse" - }, - { - "type": "null" - } - ], - "definitions": { - "AssetsResponse": { - "type": "object", - "required": [ - "coin" - ], - "properties": { - "coin": { - "$ref": "#/definitions/Coin" - } - }, - "additionalProperties": false - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "max_redeem": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Nullable_Uint128", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ], - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, "preview_deposit": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Uint128", @@ -779,73 +650,15 @@ }, "preview_redeem": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AssetsResponse", - "type": "object", - "required": [ - "coin" - ], - "properties": { - "coin": { - "$ref": "#/definitions/Coin" - } - }, - "additionalProperties": false, - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } + "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" }, "total_assets": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AssetsResponse", - "type": "object", - "required": [ - "coin" - ], - "properties": { - "coin": { - "$ref": "#/definitions/Coin" - } - }, - "additionalProperties": false, - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } + "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" }, "total_vault_token_supply": { "$schema": "http://json-schema.org/draft-07/schema#", From 0e0358ef13b4093e603629ff41ba2806ce77392f Mon Sep 17 00:00:00 2001 From: piobab Date: Fri, 4 Nov 2022 12:44:11 +0100 Subject: [PATCH 072/218] Use cargo workspace feature. (#37) * Use cargo workspace feature. * Fixes after review. * Update Cargo with correct lib params. --- Cargo.lock | 104 ++++++++++------------- Cargo.toml | 59 +++++++++++-- contracts/account-nft/Cargo.toml | 32 ++++--- contracts/credit-manager/Cargo.toml | 62 +++++++------- contracts/mars-oracle-adapter/Cargo.toml | 42 ++++----- contracts/mock-oracle/Cargo.toml | 27 +++--- contracts/mock-red-bank/Cargo.toml | 27 +++--- contracts/mock-vault/Cargo.toml | 33 +++---- contracts/mock-vault/src/contract.rs | 1 + contracts/swapper/base/Cargo.toml | 33 +++---- contracts/swapper/mock/Cargo.toml | 33 +++---- contracts/swapper/osmosis/Cargo.toml | 39 +++++---- packages/health/Cargo.toml | 28 +++--- packages/outpost/Cargo.toml | 31 +++---- packages/rover/Cargo.toml | 44 +++++----- 15 files changed, 322 insertions(+), 273 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b57192b49..7efa2f493 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,12 +9,23 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.16.0", + "cw-multi-test", "cw-storage-plus 0.16.0", "cw721", "cw721-base", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "anyhow" version = "1.0.66" @@ -84,7 +95,7 @@ checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" [[package]] name = "cosmos-vault-standard" version = "0.1.0" -source = "git+https://github.com/apollodao/cosmos-vault-standard#f9857326d0eebfd7edca855be9bd06b2d27e34e3" +source = "git+https://github.com/apollodao/cosmos-vault-standard#5713cb127f92b26a6b6b6ecd85bc10ed49d94066" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -187,7 +198,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-item-set", - "cw-multi-test 0.16.0", + "cw-multi-test", "cw-storage-plus 0.16.0", "cw-utils 0.16.0", "cw2", @@ -274,24 +285,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw-multi-test" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7192aec80d0c01a0e5941392eea7e2b7e212ee74ca7f430bfdc899420c055ef6" -dependencies = [ - "anyhow", - "cosmwasm-std", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", - "derivative", - "itertools", - "prost", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw-storage-plus" version = "0.13.4" @@ -445,16 +438,16 @@ dependencies = [ [[package]] name = "ed25519-zebra" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ "curve25519-dalek", + "hashbrown", "hex", "rand_core 0.6.4", "serde", "sha2 0.9.9", - "thiserror", "zeroize", ] @@ -486,9 +479,9 @@ dependencies = [ [[package]] name = "ff" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df689201f395c6b90dfe87127685f8dbfc083a5e779e613575d8bd7314300c3e" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "rand_core 0.6.4", "subtle", @@ -510,17 +503,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.8" @@ -529,7 +511,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -543,6 +525,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + [[package]] name = "hex" version = "0.4.3" @@ -587,13 +578,13 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.136" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55edcf6c0bb319052dea84732cf99db461780fd5e8d3eb46ab6ff312ab31f197" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "mars-health" -version = "0.1.0" +version = "1.0.0" dependencies = [ "cosmwasm-std", "mars-outpost", @@ -606,7 +597,7 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.16.0", + "cw-multi-test", "cw-storage-plus 0.16.0", "cw-utils 0.16.0", "mars-outpost", @@ -618,7 +609,7 @@ dependencies = [ [[package]] name = "mars-outpost" -version = "0.1.0" +version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -658,6 +649,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -683,7 +680,7 @@ checksum = "f96719fd1b214bb8aecbe066bb9965052cae11df4f540978122ab243d9f0a15e" dependencies = [ "anyhow", "cosmwasm-std", - "cw-multi-test 0.13.4", + "cw-multi-test", "cw-storage-plus 0.13.4", "itertools", "osmo-bindings", @@ -748,9 +745,6 @@ name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] [[package]] name = "rand_core" @@ -758,14 +752,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom", ] [[package]] name = "rfc6979" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c86280f057430a52f4861551b092a01b419b8eacefc7c995eacb9dc132fe32" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint", "hmac", @@ -966,7 +960,7 @@ version = "1.0.0" dependencies = [ "anyhow", "cosmwasm-std", - "cw-multi-test 0.16.0", + "cw-multi-test", "cw-storage-plus 0.16.0", "rover", "swapper-base", @@ -980,7 +974,7 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.13.4", + "cw-multi-test", "cw-storage-plus 0.16.0", "osmo-bindings", "osmo-bindings-test", @@ -1051,12 +1045,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index b67fc9903..7a8c420ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,20 +6,65 @@ members = [ "contracts/mars-oracle-adapter", "packages/*", -# Mock contracts + # Mock contracts "contracts/mock-oracle", "contracts/mock-red-bank", "contracts/mock-vault", ] -[profile.release.package.rover] -codegen-units = 1 -incremental = false +[workspace.package] +version = "1.0.0" +authors = [ + "grod220 ", + "larry_0x ", + "Piotr Babel ", +] +edition = "2021" +repository = "https://github.com/mars-protocol/rover" +homepage = "https://marsprotocol.io" +documentation = "https://docs.marsprotocol.io/" +keywords = ["mars", "cosmos", "cosmwasm"] + +[workspace.dependencies] +anyhow = "1" +cosmwasm-schema = "1.1" +cosmwasm-std = "1.1" +cw2 = "0.16" +cw721 = "0.16" +cw721-base = { version = "0.16", features = ["library"] } +cw-item-set = { version = "0.6", default-features = false, features = ["iterator"] } +cw-multi-test = "0.13" +cw-utils = "0.16" +cw-storage-plus = "0.16" +osmosis-std = "0.12" +schemars = "0.8" +serde = { version = "1.0", default-features = false, features = ["derive"] } +thiserror = "1.0" + +# packages +cosmos-vault-standard = { git = "https://github.com/apollodao/cosmos-vault-standard", features = ["lockup", "force-unlock"] } +mars-health = { version = "1.0.0", path = "./packages/health" } +mars-outpost = { version = "1.0.0", path = "./packages/outpost" } +rover = { version = "1.0.0", path = "./packages/rover" } + +# contracts +account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } +mars-oracle-adapter = { version = "1.0.0", path = "./contracts/mars-oracle-adapter", features = ["library"] } +swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } + +# mocks +mock-oracle = { version = "1.0.0", path = "./contracts/mock-oracle", features = ["library"] } +mock-red-bank = { version = "1.0.0", path = "./contracts/mock-red-bank", features = ["library"] } +mock-vault = { version = "1.0.0", path = "./contracts/mock-vault", features = ["library"] } +swapper-mock = { version = "1.0.0", path = "./contracts/swapper/mock", features = ["library"] } [profile.release] -rpath = false -lto = true -overflow-checks = true opt-level = 3 debug = false +rpath = false +lto = true debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 701772f07..f4ca1ac21 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -1,25 +1,29 @@ [package] -name = "account-nft" -version = "1.0.0" -authors = ["larry_0x , grod220 "] -edition = "2021" -license = "GPL-3.0-or-later" -repository = "https://github.com/mars-protocol/rover" +name = "account-nft" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } [lib] crate-type = ["cdylib", "rlib"] [features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -library = [] +library = [] [dependencies] -cw-storage-plus = "0.16" -cw721 = "0.16" -cw721-base = { version = "0.16", features = ["library"] } -cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw721 = { workspace = true } +cw721-base = { workspace = true } +cw-storage-plus = { workspace = true } [dev-dependencies] -anyhow = "1" -cw-multi-test = "0.16" +anyhow = { workspace = true } +cw-multi-test = { workspace = true } diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index c3883753c..b1c30e126 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -1,41 +1,43 @@ [package] -name = "credit-manager" -version = "1.0.0" -authors = ["grod220 , larry_0x "] -edition = "2021" -license = "GPL-3.0-or-later" -repository = "https://github.com/mars-protocol/rover" +name = "credit-manager" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } [lib] crate-type = ["cdylib", "rlib"] [features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -library = [] +library = [] [dependencies] -account-nft = { version = "1.0", path = "../account-nft", features = ["library"] } -mars-health = { version = "0.1", path = "../../packages/health" } -mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } -mars-oracle-adapter = { version = "1.0", path = "../../contracts/mars-oracle-adapter", features = ["library"] } -mars-outpost = { version = "0.1", path = "../../packages/outpost" } -mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } -mock-vault = { version = "1.0", path = "../../contracts/mock-vault", features = ["library"] } -rover = { version = "1.0", path = "../../packages/rover" } - -cosmos-vault-standard = { git = "https://github.com/apollodao/cosmos-vault-standard", features = ["lockup", "force-unlock"] } -cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" -cw2 = "0.16" -cw721 = "0.16" -cw721-base = { version = "0.16", features = ["library"] } -cw-item-set = { version = "0.6", default-features = false, features = ["iterator"] } -cw-storage-plus = "0.16" +account-nft = { workspace = true } +cosmos-vault-standard = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw721 = { workspace = true } +cw721-base = { workspace = true } +cw-item-set = { workspace = true } +cw-storage-plus = { workspace = true } +mars-health = { workspace = true } +mars-outpost = { workspace = true } +mars-oracle-adapter = { workspace = true } +rover = { workspace = true } [dev-dependencies] -swapper-mock = { version = "1.0", path = "../../contracts/swapper/mock", features = ["library"] } - -anyhow = "1" -cw-multi-test = "0.16" -cw-utils = "0.16" -itertools = "0.10" +anyhow = { workspace = true } +cw-multi-test = { workspace = true } +cw-utils = { workspace = true } +itertools = "0.10" +mock-oracle = { workspace = true } +mock-red-bank = { workspace = true } +mock-vault = { workspace = true } +swapper-mock = { workspace = true } diff --git a/contracts/mars-oracle-adapter/Cargo.toml b/contracts/mars-oracle-adapter/Cargo.toml index fabcba86d..80c04310a 100644 --- a/contracts/mars-oracle-adapter/Cargo.toml +++ b/contracts/mars-oracle-adapter/Cargo.toml @@ -1,31 +1,33 @@ [package] -name = "mars-oracle-adapter" -version = "1.0.0" -authors = ["grod220 "] -edition = "2021" -license = "GPL-3.0-or-later" -repository = "https://github.com/mars-protocol/rover" +name = "mars-oracle-adapter" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } [lib] crate-type = ["cdylib", "rlib"] [features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -library = [] +library = [] [dependencies] -mars-outpost = { version = "0.1", path = "../../packages/outpost" } -rover = { version = "1.0", path = "../../packages/rover" } - -cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" -cw-storage-plus = "0.16" -thiserror = "1.0" +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +mars-outpost = { workspace = true } +rover = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] -mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } -mock-vault = { version = "1.0", path = "../../contracts/mock-vault", features = ["library"] } - -anyhow = "1" -cw-multi-test = "0.16" -cw-utils = "0.16" +anyhow = { workspace = true } +cw-multi-test = { workspace = true } +cw-utils = { workspace = true } +mock-oracle = { workspace = true } +mock-vault = { workspace = true } diff --git a/contracts/mock-oracle/Cargo.toml b/contracts/mock-oracle/Cargo.toml index a4a0b78ff..b44ba3630 100644 --- a/contracts/mock-oracle/Cargo.toml +++ b/contracts/mock-oracle/Cargo.toml @@ -1,21 +1,24 @@ [package] -name = "mock-oracle" -version = "1.0.0" -authors = ["larry_0x ", "grod220 "] -edition = "2021" -license = "GPL-3.0-or-later" -repository = "https://github.com/mars-protocol/rover" +name = "mock-oracle" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } [lib] crate-type = ["cdylib", "rlib"] [features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -library = [] +library = [] [dependencies] -mars-outpost = { version = "0.1", path = "../../packages/outpost" } - -cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" -cw-storage-plus = "0.16" +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +mars-outpost = { workspace = true } diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index d4ad2dcb4..46e0deba2 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -1,21 +1,24 @@ [package] -name = "mock-red-bank" -version = "1.0.0" -authors = ["larry_0x , grod220 "] -edition = "2021" -license = "GPL-3.0-or-later" -repository = "https://github.com/mars-protocol/rover" +name = "mock-red-bank" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } [lib] crate-type = ["cdylib", "rlib"] [features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -library = [] +library = [] [dependencies] -mars-outpost = { version = "0.1", path = "../../packages/outpost" } - -cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" -cw-storage-plus = "0.16" +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +mars-outpost = { workspace = true } diff --git a/contracts/mock-vault/Cargo.toml b/contracts/mock-vault/Cargo.toml index eb37d361d..a2928c588 100644 --- a/contracts/mock-vault/Cargo.toml +++ b/contracts/mock-vault/Cargo.toml @@ -1,24 +1,27 @@ [package] -name = "mock-vault" -version = "1.0.0" -authors = ["larry_0x ", "grod220 "] -edition = "2021" -license = "GPL-3.0-or-later" -repository = "https://github.com/mars-protocol/rover" +name = "mock-vault" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } [lib] crate-type = ["cdylib", "rlib"] [features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -library = [] +library = [] [dependencies] -rover = { version = "1.0", path = "../../packages/rover" } - -cosmos-vault-standard = { git = "https://github.com/apollodao/cosmos-vault-standard", features = ["lockup", "force-unlock"] } -cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" -cw-storage-plus = "0.16" -cw-utils = "0.16" -thiserror = "1.0" +cosmos-vault-standard = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +thiserror = { workspace = true } +rover = { workspace = true } diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index 151bb78f1..948e21693 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -65,6 +65,7 @@ pub fn execute( ForceUnlockExecuteMsg::ForceWithdrawUnlocking { lockup_id, amount, .. } => withdraw_unlocking_force(deps, &info.sender, lockup_id, amount), + _ => unimplemented!(), }, }, } diff --git a/contracts/swapper/base/Cargo.toml b/contracts/swapper/base/Cargo.toml index 39afa2c2a..be28cc993 100644 --- a/contracts/swapper/base/Cargo.toml +++ b/contracts/swapper/base/Cargo.toml @@ -1,24 +1,27 @@ [package] -name = "swapper-base" -version = "1.0.0" -authors = ["grod220 "] -edition = "2021" -license = "GPL-3.0-or-later" -repository = "https://github.com/mars-protocol/rover" +name = "swapper-base" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } [lib] crate-type = ["cdylib", "rlib"] [features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -library = [] +library = [] [dependencies] -rover = { version = "1.0", path = "../../../packages/rover" } - -cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" -cw-storage-plus = "0.16" -schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } -thiserror = "1.0" +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +rover = { workspace = true } diff --git a/contracts/swapper/mock/Cargo.toml b/contracts/swapper/mock/Cargo.toml index d6944983f..6cc1b5746 100644 --- a/contracts/swapper/mock/Cargo.toml +++ b/contracts/swapper/mock/Cargo.toml @@ -1,26 +1,29 @@ [package] -name = "swapper-mock" -version = "1.0.0" -authors = ["grod220 "] -edition = "2021" -license = "GPL-3.0-or-later" -repository = "https://github.com/mars-protocol/rover" +name = "swapper-mock" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } [lib] crate-type = ["cdylib", "rlib"] [features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -library = [] +library = [] [dependencies] -rover = { version = "1.0", path = "../../../packages/rover" } -swapper-base = { path = "../base", version = "1.0" } - -cosmwasm-std = "1.1" -cw-storage-plus = "0.16" -thiserror = "1.0" +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +rover = { workspace = true } +swapper-base = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] -anyhow = "1" -cw-multi-test = "0.16" +anyhow = { workspace = true } +cw-multi-test = { workspace = true } diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index c219d849e..31aa1c794 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -1,30 +1,33 @@ [package] -name = "swapper-osmosis" -version = "1.0.0" -authors = ["grod220 "] -edition = "2021" -license = "GPL-3.0-or-later" -repository = "https://github.com/mars-protocol/rover" +name = "swapper-osmosis" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } [lib] crate-type = ["cdylib", "rlib"] [features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -library = [] +library = [] [dependencies] -rover = { version = "1.0", path = "../../../packages/rover" } -swapper-base = { path = "../base", version = "1.0" } - -cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" -cw-storage-plus = "0.16" -osmo-bindings = "0.5" -schemars = "0.8" -thiserror = "1.0" +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +osmo-bindings = "0.5" +schemars = { workspace = true } +swapper-base = { workspace = true } +rover = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] -anyhow = "1" -cw-multi-test = "0.13" +anyhow = { workspace = true } +cw-multi-test = { workspace = true } osmo-bindings-test = "0.5" diff --git a/packages/health/Cargo.toml b/packages/health/Cargo.toml index 6a1caf972..53271100d 100644 --- a/packages/health/Cargo.toml +++ b/packages/health/Cargo.toml @@ -1,20 +1,12 @@ [package] -name = "mars-health" -version = "0.1.0" -authors = [ - "larry_0x ", - "Piotr Babel ", - "Gabe Rodriguez ", - "Ahmad Kaouk" -] -edition = "2021" -description = "Helper functions to compute the health factor" -license = "GPL-3.0" -repository = "https://github.com/mars-protocol/outposts" -homepage = "https://marsprotocol.io" -documentation = "https://docs.marsprotocol.io/mars-protocol/developers/protocol-overview" -readme = "README.md" -keywords = ["mars", "cosmwasm", "health"] +name = "mars-health" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } [lib] doctest = false @@ -25,5 +17,5 @@ doctest = false backtraces = ["cosmwasm-std/backtraces"] [dependencies] -mars-outpost = { path = "../outpost" } -cosmwasm-std = "1.1" +mars-outpost = { workspace = true } +cosmwasm-std = { workspace = true } diff --git a/packages/outpost/Cargo.toml b/packages/outpost/Cargo.toml index 168656a4e..e674b91ed 100644 --- a/packages/outpost/Cargo.toml +++ b/packages/outpost/Cargo.toml @@ -1,21 +1,12 @@ [package] -name = "mars-outpost" -version = "0.1.0" -authors = [ - "larry_0x ", - "Piotr Babel ", - "Spike Spiegel ", - "Ahmad Kaouk", - "Harry Scholes" -] -edition = "2021" -description = "Mars is a fully automated, on-chain credit protocol governed by a decentralised community of users and developers" -license = "GPL-3.0" -repository = "https://github.com/mars-protocol/outposts" -homepage = "https://marsprotocol.io" -documentation = "https://docs.marsprotocol.io/mars-protocol/developers/protocol-overview" -readme = "README.md" -keywords = ["mars", "cosmwasm"] +name = "mars-outpost" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } [lib] doctest = false @@ -26,6 +17,6 @@ doctest = false backtraces = ["cosmwasm-std/backtraces"] [dependencies] -cosmwasm-std = "1.1" -cosmwasm-schema = "1.1" -thiserror = "1.0" +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +thiserror = { workspace = true } diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 497bdbe62..a811c9070 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -1,25 +1,31 @@ [package] -name = "rover" -version = "1.0.0" -authors = ["larry_0x ", "Gabe R. "] -edition = "2021" -license = "GPL-3.0-or-later" -repository = "https://github.com/mars-protocol/rover" +name = "rover" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } [lib] doctest = false -[dependencies] -mars-health = { version = "0.1", path = "../../packages/health" } -mars-outpost = { version = "0.1", path = "../../packages/outpost" } -mock-red-bank = { version = "1.0", path = "../../contracts/mock-red-bank", features = ["library"] } -mock-oracle = { version = "1.0", path = "../../contracts/mock-oracle", features = ["library"] } +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] -cosmos-vault-standard = { git = "https://github.com/apollodao/cosmos-vault-standard", features = ["lockup", "force-unlock"] } -cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" -cw-storage-plus = "0.16" -cw-utils = "0.16" -schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } -thiserror = "1.0" +[dependencies] +cosmos-vault-standard = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +mars-health = { workspace = true } +mock-oracle = { workspace = true } +mars-outpost = { workspace = true } +mock-red-bank = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } From 95c069fa261f2d9a0365a568e0f2466e64d1f9dc Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 4 Nov 2022 15:23:51 +0100 Subject: [PATCH 073/218] Integrating zapper contract (#36) * integrating zapper contract * review updates --- Cargo.lock | 13 + Cargo.toml | 2 + contracts/credit-manager/Cargo.toml | 1 + contracts/credit-manager/src/borrow.rs | 2 +- contracts/credit-manager/src/contract.rs | 8 + contracts/credit-manager/src/deposit.rs | 2 +- contracts/credit-manager/src/execute.rs | 73 ++++- contracts/credit-manager/src/health.rs | 2 +- contracts/credit-manager/src/instantiate.rs | 3 +- contracts/credit-manager/src/lib.rs | 1 + .../credit-manager/src/liquidate_coin.rs | 2 +- contracts/credit-manager/src/query.rs | 3 +- contracts/credit-manager/src/repay.rs | 2 +- contracts/credit-manager/src/state.rs | 3 +- contracts/credit-manager/src/swap.rs | 20 +- .../src/update_coin_balances.rs | 80 ++--- contracts/credit-manager/src/utils.rs | 38 ++- contracts/credit-manager/src/vault/enter.rs | 42 ++- contracts/credit-manager/src/vault/exit.rs | 18 +- .../credit-manager/src/vault/exit_unlocked.rs | 12 +- .../src/vault/liquidate_vault.rs | 56 ++-- .../src/vault/request_unlock.rs | 4 +- contracts/credit-manager/src/vault/utils.rs | 8 +- contracts/credit-manager/src/withdraw.rs | 2 +- contracts/credit-manager/src/zap.rs | 87 +++++ .../credit-manager/tests/helpers/contracts.rs | 9 + .../credit-manager/tests/helpers/mock_env.rs | 63 +++- .../credit-manager/tests/helpers/types.rs | 9 + .../tests/test_coin_balances.rs | 57 +--- .../test_enumerate_vault_coin_balances.rs | 5 +- .../tests/test_enumerate_vault_positions.rs | 5 +- .../tests/test_liquidate_coin.rs | 3 +- .../tests/test_liquidate_vault.rs | 21 +- .../tests/test_update_config.rs | 11 +- .../credit-manager/tests/test_vault_enter.rs | 124 ++++++- .../credit-manager/tests/test_vault_exit.rs | 6 +- .../tests/test_vault_exit_unlocked.rs | 18 +- .../tests/test_vault_request_unlock.rs | 6 +- .../credit-manager/tests/test_zap_provide.rs | 302 ++++++++++++++++++ .../credit-manager/tests/test_zap_withdraw.rs | 224 +++++++++++++ contracts/mock-zapper/Cargo.toml | 23 ++ contracts/mock-zapper/examples/schema.rs | 10 + contracts/mock-zapper/src/contract.rs | 65 ++++ contracts/mock-zapper/src/error.rs | 30 ++ contracts/mock-zapper/src/execute.rs | 100 ++++++ contracts/mock-zapper/src/lib.rs | 5 + contracts/mock-zapper/src/query.rs | 50 +++ contracts/mock-zapper/src/state.rs | 8 + packages/rover/src/adapters/mod.rs | 2 + packages/rover/src/adapters/zapper.rs | 89 ++++++ packages/rover/src/msg/execute.rs | 33 +- packages/rover/src/msg/instantiate.rs | 4 + packages/rover/src/msg/mod.rs | 1 + packages/rover/src/msg/query.rs | 10 + packages/rover/src/msg/zapper.rs | 41 +++ schema.Makefile.toml | 1 + schemas/credit-manager/credit-manager.json | 301 ++++++++++++++++- schemas/mock-vault/mock-vault.json | 32 ++ schemas/mock-zapper/mock-zapper.json | 236 ++++++++++++++ 59 files changed, 2128 insertions(+), 260 deletions(-) create mode 100644 contracts/credit-manager/src/zap.rs create mode 100644 contracts/credit-manager/tests/test_zap_provide.rs create mode 100644 contracts/credit-manager/tests/test_zap_withdraw.rs create mode 100644 contracts/mock-zapper/Cargo.toml create mode 100644 contracts/mock-zapper/examples/schema.rs create mode 100644 contracts/mock-zapper/src/contract.rs create mode 100644 contracts/mock-zapper/src/error.rs create mode 100644 contracts/mock-zapper/src/execute.rs create mode 100644 contracts/mock-zapper/src/lib.rs create mode 100644 contracts/mock-zapper/src/query.rs create mode 100644 contracts/mock-zapper/src/state.rs create mode 100644 packages/rover/src/adapters/zapper.rs create mode 100644 packages/rover/src/msg/zapper.rs create mode 100644 schemas/mock-zapper/mock-zapper.json diff --git a/Cargo.lock b/Cargo.lock index 7efa2f493..a622f3630 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -211,6 +211,7 @@ dependencies = [ "mock-oracle", "mock-red-bank", "mock-vault", + "mock-zapper", "rover", "swapper-mock", ] @@ -649,6 +650,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "mock-zapper" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", + "rover", + "thiserror", +] + [[package]] name = "once_cell" version = "1.16.0" diff --git a/Cargo.toml b/Cargo.toml index 7a8c420ec..d4cb19eda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "contracts/mock-oracle", "contracts/mock-red-bank", "contracts/mock-vault", + "contracts/mock-zapper", ] [workspace.package] @@ -56,6 +57,7 @@ swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } mock-oracle = { version = "1.0.0", path = "./contracts/mock-oracle", features = ["library"] } mock-red-bank = { version = "1.0.0", path = "./contracts/mock-red-bank", features = ["library"] } mock-vault = { version = "1.0.0", path = "./contracts/mock-vault", features = ["library"] } +mock-zapper = { version = "1.0.0", path = "./contracts/mock-zapper", features = ["library"] } swapper-mock = { version = "1.0.0", path = "./contracts/swapper/mock", features = ["library"] } [profile.release] diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index b1c30e126..cda9575ac 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -40,4 +40,5 @@ itertools = "0.10" mock-oracle = { workspace = true } mock-red-bank = { workspace = true } mock-vault = { workspace = true } +mock-zapper = { workspace = true } swapper-mock = { workspace = true } diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index 4a8e4b148..7b80d3ac1 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -50,7 +50,7 @@ pub fn borrow(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> Contract Ok(Response::new() .add_message(red_bank.borrow_msg(&coin)?) - .add_attribute("action", "rover/credit_manager/borrow") + .add_attribute("action", "rover/credit-manager/borrow") .add_attribute("debt_shares_added", debt_shares_to_add) .add_attribute("coins_borrowed", coin.amount)) } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index c432540f1..2aa686e9a 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -18,6 +18,7 @@ use crate::query::{ query_vault_configs, }; use crate::vault::handle_unlock_request_reply; +use crate::zap::{estimate_provide_liquidity, estimate_withdraw_liquidity}; const CONTRACT_NAME: &str = "crates.io:rover-credit-manager"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -95,6 +96,13 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { QueryMsg::AllVaultPositions { start_after, limit } => { to_binary(&query_all_vault_positions(deps, start_after, limit)?) } + QueryMsg::EstimateProvideLiquidity { + lp_token_out, + coins_in, + } => to_binary(&estimate_provide_liquidity(deps, &lp_token_out, coins_in)?), + QueryMsg::EstimateWithdrawLiquidity { lp_token } => { + to_binary(&estimate_withdraw_liquidity(deps, lp_token)?) + } }; res.map_err(Into::into) } diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index a9cfe7981..27af27125 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -25,7 +25,7 @@ pub fn deposit( increment_coin_balance(storage, account_id, coin)?; Ok(response - .add_attribute("action", "rover/credit_manager/callback/deposit") + .add_attribute("action", "rover/credit-manager/callback/deposit") .add_attribute("deposit_received", coin.to_string())) } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index b6aa861c3..7210565d0 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -10,7 +10,7 @@ use crate::health::assert_below_max_ltv; use crate::repay::repay; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, - SWAPPER, VAULT_CONFIGS, + SWAPPER, VAULT_CONFIGS, ZAPPER, }; use crate::vault::{ enter_vault, exit_vault, exit_vault_unlocked, liquidate_vault, request_vault_unlock, @@ -19,8 +19,9 @@ use crate::vault::{ use crate::liquidate_coin::liquidate_coin; use crate::swap::swap_exact_in; -use crate::update_coin_balances::update_coin_balances; +use crate::update_coin_balances::update_coin_balance; use crate::withdraw::withdraw; +use crate::zap::{provide_liquidity, withdraw_liquidity}; use account_nft::msg::ExecuteMsg as NftExecuteMsg; use rover::coins::Coins; use rover::error::{ContractError, ContractResult}; @@ -41,7 +42,7 @@ pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult callbacks.push(CallbackMsg::EnterVault { account_id: account_id.to_string(), vault: vault.check(deps.api)?, - coin: assets.clone(), + denom: denom.to_string(), + amount: *amount, }), Action::LiquidateCoin { liquidatee_account_id, @@ -241,6 +251,22 @@ pub fn dispatch_actions( position_id: *id, }) } + Action::ProvideLiquidity { + coins_in, + lp_token_out, + minimum_receive, + } => callbacks.push(CallbackMsg::ProvideLiquidity { + account_id: account_id.to_string(), + lp_token_out: lp_token_out.clone(), + coins_in: coins_in.clone(), + minimum_receive: *minimum_receive, + }), + Action::WithdrawLiquidity { lp_token } => { + callbacks.push(CallbackMsg::WithdrawLiquidity { + account_id: account_id.to_string(), + lp_token: lp_token.clone(), + }) + } } } @@ -288,8 +314,16 @@ pub fn execute_callback( CallbackMsg::EnterVault { account_id, vault, - coin, - } => enter_vault(deps, &env.contract.address, &account_id, vault, coin), + denom, + amount, + } => enter_vault( + deps, + &env.contract.address, + &account_id, + vault, + &denom, + amount, + ), CallbackMsg::UpdateVaultCoinBalance { vault, account_id, @@ -333,10 +367,10 @@ pub fn execute_callback( denom_out, slippage, } => swap_exact_in(deps, env, &account_id, coin_in, &denom_out, slippage), - CallbackMsg::UpdateCoinBalances { + CallbackMsg::UpdateCoinBalance { account_id, - previous_balances, - } => update_coin_balances(deps, env, &account_id, &previous_balances), + previous_balance, + } => update_coin_balance(deps, env, &account_id, &previous_balance), CallbackMsg::ExitVault { account_id, vault, @@ -357,6 +391,23 @@ pub fn execute_callback( vault, position_id, } => exit_vault_unlocked(deps, env, &account_id, vault, position_id), + CallbackMsg::ProvideLiquidity { + account_id, + coins_in, + lp_token_out, + minimum_receive, + } => provide_liquidity( + deps, + env, + &account_id, + coins_in, + &lp_token_out, + minimum_receive, + ), + CallbackMsg::WithdrawLiquidity { + account_id, + lp_token, + } => withdraw_liquidity(deps, env, &account_id, lp_token), } } diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index ede1df3eb..68b846eb1 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -125,7 +125,7 @@ pub fn assert_below_max_ltv(deps: Deps, env: Env, account_id: &str) -> ContractR .add_attribute("above_max_ltv", health.is_above_max_ltv().to_string()); Ok(Response::new() - .add_attribute("action", "rover/credit_manager/callback/assert_health") + .add_attribute("action", "rover/credit-manager/callback/assert_health") .add_event(event)) } diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 9c99e003e..5b910e5be 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -5,7 +5,7 @@ use rover::msg::InstantiateMsg; use crate::state::{ ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, - VAULT_CONFIGS, + VAULT_CONFIGS, ZAPPER, }; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { @@ -16,6 +16,7 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { MAX_LIQUIDATION_BONUS.save(deps.storage, &msg.max_liquidation_bonus)?; MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; SWAPPER.save(deps.storage, &msg.swapper.check(deps.api)?)?; + ZAPPER.save(deps.storage, &msg.zapper.check(deps.api)?)?; msg.allowed_vaults .iter() diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index fd4a3a4c0..ad405eb8e 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -14,3 +14,4 @@ pub mod update_coin_balances; pub mod utils; pub mod vault; pub mod withdraw; +pub mod zap; diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index 35370ff02..3566bde7c 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -46,7 +46,7 @@ pub fn liquidate_coin( Ok(Response::new() .add_message(repay_msg) - .add_attribute("action", "rover/credit_manager/liquidate_coin") + .add_attribute("action", "rover/credit-manager/liquidate_coin") .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("debt_repaid_denom", debt.denom) .add_attribute("debt_repaid_amount", debt.amount) diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 27812e1c1..3865c1e0e 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -12,7 +12,7 @@ use rover::msg::query::{ use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_CONFIGS, - VAULT_POSITIONS, + VAULT_POSITIONS, ZAPPER, }; use crate::utils::debt_shares_to_amount; @@ -30,6 +30,7 @@ pub fn query_config(deps: Deps) -> StdResult { max_liquidation_bonus: MAX_LIQUIDATION_BONUS.load(deps.storage)?, max_close_factor: MAX_CLOSE_FACTOR.load(deps.storage)?, swapper: SWAPPER.load(deps.storage)?.address().into(), + zapper: ZAPPER.load(deps.storage)?.address().into(), }) } diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index 246ec8447..c469d7908 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -63,7 +63,7 @@ pub fn repay(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> ContractR Ok(Response::new() .add_message(red_bank_repay_msg) - .add_attribute("action", "rover/credit_manager/repay") + .add_attribute("action", "rover/credit-manager/repay") .add_attribute("debt_shares_repaid", shares_to_repay) .add_attribute("coins_repaid", amount_to_repay)) } diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 403dbda23..7a851698c 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -4,7 +4,7 @@ use cw_storage_plus::{Item, Map}; use rover::adapters::swap::Swapper; use rover::adapters::vault::{VaultConfig, VaultPositionAmount}; -use rover::adapters::{Oracle, RedBank}; +use rover::adapters::{Oracle, RedBank, Zapper}; use crate::vault::RequestTempStorage; @@ -18,6 +18,7 @@ pub const ORACLE: Item = Item::new("oracle"); pub const MAX_LIQUIDATION_BONUS: Item = Item::new("max_liquidation_bonus"); pub const MAX_CLOSE_FACTOR: Item = Item::new("max_close_factor"); pub const SWAPPER: Item = Item::new("swapper"); +pub const ZAPPER: Item = Item::new("zapper"); // Positions pub const COIN_BALANCES: Map<(&str, &str), Uint128> = Map::new("coin_balance"); // Map<(AccountId, Denom), Amount> diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs index 771ed58ed..cf2cb0d7b 100644 --- a/contracts/credit-manager/src/swap.rs +++ b/contracts/credit-manager/src/swap.rs @@ -1,12 +1,9 @@ -use cosmwasm_std::{to_binary, Coin, CosmosMsg, Decimal, DepsMut, Env, Response, WasmMsg}; +use cosmwasm_std::{Coin, Decimal, DepsMut, Env, Response}; use rover::error::{ContractError, ContractResult}; -use rover::msg::execute::CallbackMsg; -use rover::msg::ExecuteMsg; use crate::state::SWAPPER; -use crate::update_coin_balances::query_balances; -use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance}; +use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance, update_balance_msg}; pub fn swap_exact_in( deps: DepsMut, @@ -25,20 +22,13 @@ pub fn swap_exact_in( decrement_coin_balance(deps.storage, account_id, &coin_in)?; // Updates coin balances for account after the swap has taken place - let previous_balances = query_balances(deps.as_ref(), &env.contract.address, &[denom_out])?; - let update_coin_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: env.contract.address.to_string(), - funds: vec![], - msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalances { - account_id: account_id.to_string(), - previous_balances, - }))?, - }); + let update_coin_balance_msg = + update_balance_msg(&deps.querier, &env.contract.address, account_id, denom_out)?; let swapper = SWAPPER.load(deps.storage)?; Ok(Response::new() .add_message(swapper.swap_exact_in_msg(&coin_in, denom_out, slippage)?) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit_manager/swapper")) + .add_attribute("action", "rover/credit-manager/swapper")) } diff --git a/contracts/credit-manager/src/update_coin_balances.rs b/contracts/credit-manager/src/update_coin_balances.rs index 60de7baf3..dda7cf162 100644 --- a/contracts/credit-manager/src/update_coin_balances.rs +++ b/contracts/credit-manager/src/update_coin_balances.rs @@ -1,13 +1,14 @@ use cosmwasm_std::{ - Addr, BalanceResponse, BankQuery, Coin, Deps, DepsMut, Env, QueryRequest, Response, StdResult, + Addr, BalanceResponse, BankQuery, Coin, DepsMut, Env, QuerierWrapper, QueryRequest, Response, + StdResult, }; use rover::error::ContractResult; use crate::utils::{decrement_coin_balance, increment_coin_balance}; -pub fn query_balance(deps: Deps, addr: &Addr, denom: &str) -> StdResult { - let res: BalanceResponse = deps.querier.query(&QueryRequest::Bank(BankQuery::Balance { +pub fn query_balance(querier: &QuerierWrapper, addr: &Addr, denom: &str) -> StdResult { + let res: BalanceResponse = querier.query(&QueryRequest::Bank(BankQuery::Balance { address: addr.to_string(), denom: denom.to_string(), }))?; @@ -17,51 +18,40 @@ pub fn query_balance(deps: Deps, addr: &Addr, denom: &str) -> StdResult { }) } -pub fn query_balances(deps: Deps, addr: &Addr, denoms: &[&str]) -> StdResult> { - denoms - .iter() - .map(|denom| query_balance(deps, addr, denom)) - .collect() -} - -pub fn update_coin_balances( +pub fn update_coin_balance( deps: DepsMut, env: Env, account_id: &str, - previous_balances: &[Coin], + prev: &Coin, ) -> ContractResult { - let mut response = Response::new(); - - for prev in previous_balances { - let curr = query_balance(deps.as_ref(), &env.contract.address, &prev.denom)?; - if prev.amount > curr.amount { - let amount_to_reduce = prev.amount.checked_sub(curr.amount)?; - decrement_coin_balance( - deps.storage, - account_id, - &Coin { - denom: curr.denom.clone(), - amount: amount_to_reduce, - }, - )?; - response = response - .add_attribute("denom", curr.denom.clone()) - .add_attribute("decremented", amount_to_reduce); - } else { - let amount_to_increment = curr.amount.checked_sub(prev.amount)?; - increment_coin_balance( - deps.storage, - account_id, - &Coin { - denom: curr.denom.clone(), - amount: amount_to_increment, - }, - )?; - response = response - .add_attribute("denom", curr.denom.clone()) - .add_attribute("incremented", amount_to_increment); - } + let curr = query_balance(&deps.querier, &env.contract.address, &prev.denom)?; + if prev.amount > curr.amount { + let amount_to_reduce = prev.amount.checked_sub(curr.amount)?; + decrement_coin_balance( + deps.storage, + account_id, + &Coin { + denom: curr.denom.clone(), + amount: amount_to_reduce, + }, + )?; + Ok(Response::new() + .add_attribute("action", "rover/credit-manager/update_coin_balance") + .add_attribute("denom", curr.denom) + .add_attribute("decremented", amount_to_reduce)) + } else { + let amount_to_increment = curr.amount.checked_sub(prev.amount)?; + increment_coin_balance( + deps.storage, + account_id, + &Coin { + denom: curr.denom.clone(), + amount: amount_to_increment, + }, + )?; + Ok(Response::new() + .add_attribute("action", "rover/credit-manager/update_coin_balance") + .add_attribute("denom", curr.denom) + .add_attribute("incremented", amount_to_increment)) } - - Ok(response.add_attribute("action", "rover/credit_manager/update_coin_balance")) } diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 54b5cdab3..8817027c0 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -1,12 +1,19 @@ -use cosmwasm_std::{Addr, Coin, Decimal, Deps, Storage, Uint128}; use std::collections::HashSet; use std::hash::Hash; +use cosmwasm_std::{ + to_binary, Addr, Coin, CosmosMsg, Decimal, Deps, QuerierWrapper, StdResult, Storage, Uint128, + WasmMsg, +}; + use rover::error::{ContractError, ContractResult}; +use rover::msg::execute::CallbackMsg; use rover::msg::query::CoinValue; +use rover::msg::ExecuteMsg; use rover::traits::IntoDecimal; use crate::state::{ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES}; +use crate::update_coin_balances::query_balance; pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, denom: &str) -> ContractResult<()> { let is_whitelisted = ALLOWED_COINS.contains(storage, denom); @@ -56,6 +63,35 @@ pub fn decrement_coin_balance( Ok(new_value) } +pub fn update_balance_msg( + querier: &QuerierWrapper, + rover_addr: &Addr, + account_id: &str, + denom: &str, +) -> StdResult { + let previous_balance = query_balance(querier, rover_addr, denom)?; + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: rover_addr.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalance { + account_id: account_id.to_string(), + previous_balance, + }))?, + })) +} + +pub fn update_balances_msgs( + querier: &QuerierWrapper, + rover_addr: &Addr, + account_id: &str, + denoms: Vec<&str>, +) -> StdResult> { + denoms + .iter() + .map(|denom| update_balance_msg(querier, rover_addr, account_id, denom)) + .collect() +} + pub fn debt_shares_to_amount( deps: Deps, rover_addr: &Addr, diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index bf19d9224..893bf25e9 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - coin as c, to_binary, Addr, Coin, CosmosMsg, Deps, DepsMut, QuerierWrapper, Response, Uint128, - WasmMsg, + coin as c, to_binary, Addr, Coin, CosmosMsg, Deps, DepsMut, QuerierWrapper, Response, Storage, + Uint128, WasmMsg, }; use rover::adapters::vault::{UpdateType, Vault, VaultPositionUpdate}; @@ -8,7 +8,7 @@ use rover::error::{ContractError, ContractResult}; use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg; -use crate::state::{ORACLE, VAULT_CONFIGS}; +use crate::state::{COIN_BALANCES, ORACLE, VAULT_CONFIGS}; use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance}; use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; @@ -17,14 +17,21 @@ pub fn enter_vault( rover_addr: &Addr, account_id: &str, vault: Vault, - coin: Coin, + denom: &str, + amount_opt: Option, ) -> ContractResult { - assert_coins_are_whitelisted(deps.storage, vec![coin.denom.as_str()])?; + let amount = or_full_balance_default(deps.storage, amount_opt, account_id, denom)?; + let coin_to_enter = Coin { + denom: denom.to_string(), + amount, + }; + + assert_coins_are_whitelisted(deps.storage, vec![denom])?; assert_vault_is_whitelisted(deps.storage, &vault)?; - assert_denom_matches_vault_reqs(deps.querier, &vault, &coin)?; - assert_deposit_is_under_cap(deps.as_ref(), &vault, &coin, rover_addr)?; + assert_denom_matches_vault_reqs(deps.querier, &vault, &coin_to_enter)?; + assert_deposit_is_under_cap(deps.as_ref(), &vault, &coin_to_enter, rover_addr)?; - decrement_coin_balance(deps.storage, account_id, &coin)?; + decrement_coin_balance(deps.storage, account_id, &coin_to_enter)?; let current_balance = vault.query_balance(&deps.querier, rover_addr)?; let update_vault_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { @@ -38,9 +45,22 @@ pub fn enter_vault( }); Ok(Response::new() - .add_message(vault.deposit_msg(&coin)?) + .add_message(vault.deposit_msg(&coin_to_enter)?) .add_message(update_vault_balance_msg) - .add_attribute("action", "rover/credit_manager/vault/deposit")) + .add_attribute("action", "rover/credit-manager/vault/deposit")) +} + +fn or_full_balance_default( + storage: &dyn Storage, + amount_opt: Option, + account_id: &str, + denom: &str, +) -> ContractResult { + if let Some(a) = amount_opt { + Ok(a) + } else { + Ok(COIN_BALANCES.load(storage, (account_id, denom))?) + } } pub fn update_vault_coin_balance( @@ -70,7 +90,7 @@ pub fn update_vault_coin_balance( )?; Ok(Response::new() - .add_attribute("action", "rover/credit_manager/vault/update_balance") + .add_attribute("action", "rover/credit-manager/vault/update_balance") .add_attribute( "amount_incremented", current_balance.checked_sub(previous_total_balance)?, diff --git a/contracts/credit-manager/src/vault/exit.rs b/contracts/credit-manager/src/vault/exit.rs index ac6ace2fa..04199d7c9 100644 --- a/contracts/credit-manager/src/vault/exit.rs +++ b/contracts/credit-manager/src/vault/exit.rs @@ -6,7 +6,7 @@ use rover::msg::execute::CallbackMsg; use rover::msg::ExecuteMsg as RoverExecuteMsg; use crate::vault::utils::{ - assert_vault_is_whitelisted, query_withdraw_denom_balances, update_vault_position, + assert_vault_is_whitelisted, query_withdraw_denom_balance, update_vault_position, }; pub fn exit_vault( @@ -36,21 +36,19 @@ pub fn exit_vault( let withdraw_msg = vault.withdraw_msg(&deps.querier, amount, force)?; // Updates coin balances for account after a vault withdraw has taken place - let previous_balances = - query_withdraw_denom_balances(deps.as_ref(), &env.contract.address, &vault)?; + let previous_balance = + query_withdraw_denom_balance(deps.as_ref(), &env.contract.address, &vault)?; let update_coin_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: env.contract.address.to_string(), funds: vec![], - msg: to_binary(&RoverExecuteMsg::Callback( - CallbackMsg::UpdateCoinBalances { - account_id: account_id.to_string(), - previous_balances, - }, - ))?, + msg: to_binary(&RoverExecuteMsg::Callback(CallbackMsg::UpdateCoinBalance { + account_id: account_id.to_string(), + previous_balance, + }))?, }); Ok(Response::new() .add_message(withdraw_msg) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit_manager/vault/withdraw")) + .add_attribute("action", "rover/credit-manager/vault/withdraw")) } diff --git a/contracts/credit-manager/src/vault/exit_unlocked.rs b/contracts/credit-manager/src/vault/exit_unlocked.rs index 780fa2d31..7fb8cdb62 100644 --- a/contracts/credit-manager/src/vault/exit_unlocked.rs +++ b/contracts/credit-manager/src/vault/exit_unlocked.rs @@ -8,7 +8,7 @@ use rover::msg::ExecuteMsg; use crate::state::VAULT_POSITIONS; use crate::vault::utils::{ - assert_vault_is_whitelisted, query_withdraw_denom_balances, update_vault_position, + assert_vault_is_whitelisted, query_withdraw_denom_balance, update_vault_position, }; pub fn exit_vault_unlocked( @@ -40,14 +40,14 @@ pub fn exit_vault_unlocked( )?; // Updates coin balances for account after the withdraw has taken place - let previous_balances = - query_withdraw_denom_balances(deps.as_ref(), &env.contract.address, &vault)?; + let previous_balance = + query_withdraw_denom_balance(deps.as_ref(), &env.contract.address, &vault)?; let update_coin_balance_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: env.contract.address.to_string(), funds: vec![], - msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalances { + msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalance { account_id: account_id.to_string(), - previous_balances, + previous_balance, }))?, }); @@ -56,5 +56,5 @@ pub fn exit_vault_unlocked( Ok(Response::new() .add_message(withdraw_unlocked_msg) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit_manager/vault/unlock")) + .add_attribute("action", "rover/credit-manager/vault/unlock")) } diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index a2acd617e..d3b666c23 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -1,20 +1,16 @@ use std::cmp::min; -use cosmwasm_std::{ - to_binary, Coin, CosmosMsg, DepsMut, Env, Response, StdResult, Uint128, WasmMsg, -}; +use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; use rover::adapters::vault::{ UnlockingChange, UnlockingPositions, UpdateType, Vault, VaultPositionAmount, VaultPositionUpdate, }; use rover::error::ContractResult; -use rover::msg::execute::CallbackMsg; -use rover::msg::ExecuteMsg; use crate::liquidate_coin::{calculate_liquidation, repay_debt}; use crate::state::VAULT_POSITIONS; -use crate::update_coin_balances::query_balances; +use crate::utils::update_balance_msg; use crate::vault::update_vault_position; pub fn liquidate_vault( @@ -105,14 +101,18 @@ fn liquidate_unlocked( let vault_withdraw_msg = request_vault.withdraw_msg(&deps.querier, request.amount, false)?; - let update_coin_balance_msg = - update_balances(deps, &env, &liquidator_account_id, &vault_info.base_token)?; + let update_coin_balance_msg = update_balance_msg( + &deps.querier, + &env.contract.address, + liquidator_account_id, + &vault_info.base_token, + )?; Ok(Response::new() .add_message(repay_msg) .add_message(vault_withdraw_msg) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit_manager/liquidate_vault/unlocked") + .add_attribute("action", "rover/credit-manager/liquidate_vault/unlocked") .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("debt_repaid_denom", debt.denom) .add_attribute("debt_repaid_amount", debt.amount) @@ -171,14 +171,18 @@ fn liquidate_unlocking( total_to_liquidate = total_to_liquidate.checked_sub(amount)?; } - let update_coin_balance_msg = - update_balances(deps, &env, &liquidator_account_id, &vault_info.base_token)?; + let update_coin_balance_msg = update_balance_msg( + &deps.querier, + &env.contract.address, + liquidator_account_id, + &vault_info.base_token, + )?; Ok(Response::new() .add_message(repay_msg) .add_messages(vault_withdraw_msgs) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit_manager/liquidate_vault/unlocking") + .add_attribute("action", "rover/credit-manager/liquidate_vault/unlocking") .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("debt_repaid_denom", debt.denom) .add_attribute("debt_repaid_amount", debt.amount) @@ -223,35 +227,21 @@ fn liquidate_locked( let vault_withdraw_msg = request_vault.withdraw_msg(&deps.querier, request.amount, true)?; - let update_coin_balance_msg = - update_balances(deps, &env, &liquidator_account_id, &vault_info.base_token)?; + let update_coin_balance_msg = update_balance_msg( + &deps.querier, + &env.contract.address, + liquidator_account_id, + &vault_info.base_token, + )?; Ok(Response::new() .add_message(repay_msg) .add_message(vault_withdraw_msg) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit_manager/liquidate_vault/locked") + .add_attribute("action", "rover/credit-manager/liquidate_vault/locked") .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("debt_repaid_denom", debt.denom) .add_attribute("debt_repaid_amount", debt.amount) .add_attribute("vault_coin_denom", request.denom) .add_attribute("vault_coin_liquidated", request.amount)) } - -/// Updates coin balances of liquidator after withdraws have been made -fn update_balances( - deps: DepsMut, - env: &Env, - liquidator_account_id: &&str, - denom: &str, -) -> StdResult { - let previous_balances = query_balances(deps.as_ref(), &env.contract.address, &[denom])?; - Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: env.contract.address.to_string(), - funds: vec![], - msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalances { - account_id: liquidator_account_id.to_string(), - previous_balances, - }))?, - })) -} diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index 3bf296698..5ce34d858 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -53,7 +53,7 @@ pub fn request_vault_unlock( Ok(Response::new() .add_submessage(request_unlock_msg) - .add_attribute("action", "rover/credit_manager/vault/request_unlock")) + .add_attribute("action", "rover/credit-manager/vault/request_unlock")) } pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResult { @@ -81,6 +81,6 @@ pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResul Ok(Response::new().add_attribute( "action", - "rover/credit_manager/vault/unlock_request/handle_reply", + "rover/credit-manager/vault/unlock_request/handle_reply", )) } diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 9622d9d76..9a786e68b 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -4,7 +4,7 @@ use rover::adapters::vault::{Vault, VaultPositionAmount, VaultPositionUpdate}; use rover::error::{ContractError, ContractResult}; use crate::state::{VAULT_CONFIGS, VAULT_POSITIONS}; -use crate::update_coin_balances::query_balances; +use crate::update_coin_balances::query_balance; pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> ContractResult<()> { let config = VAULT_CONFIGS @@ -38,11 +38,11 @@ pub fn update_vault_position( } /// Returns the total vault token balance for rover -pub fn query_withdraw_denom_balances( +pub fn query_withdraw_denom_balance( deps: Deps, rover_addr: &Addr, vault: &Vault, -) -> StdResult> { +) -> StdResult { let vault_info = vault.query_info(&deps.querier)?; - query_balances(deps, rover_addr, &[vault_info.base_token.as_str()]) + query_balance(&deps.querier, rover_addr, vault_info.base_token.as_str()) } diff --git a/contracts/credit-manager/src/withdraw.rs b/contracts/credit-manager/src/withdraw.rs index f7a9f4504..329ad17ea 100644 --- a/contracts/credit-manager/src/withdraw.rs +++ b/contracts/credit-manager/src/withdraw.rs @@ -26,6 +26,6 @@ pub fn withdraw( Ok(Response::new() .add_message(transfer_msg) - .add_attribute("action", "rover/credit_manager/callback/withdraw") + .add_attribute("action", "rover/credit-manager/callback/withdraw") .add_attribute("withdrawn", coin.to_string())) } diff --git a/contracts/credit-manager/src/zap.rs b/contracts/credit-manager/src/zap.rs new file mode 100644 index 000000000..212902574 --- /dev/null +++ b/contracts/credit-manager/src/zap.rs @@ -0,0 +1,87 @@ +use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; + +use rover::error::ContractResult; +use rover::traits::Denoms; + +use crate::state::ZAPPER; +use crate::utils::{ + assert_coin_is_whitelisted, assert_coins_are_whitelisted, decrement_coin_balance, + update_balance_msg, update_balances_msgs, +}; + +pub fn provide_liquidity( + deps: DepsMut, + env: Env, + account_id: &str, + coins_in: Vec, + lp_token_out: &str, + minimum_receive: Uint128, +) -> ContractResult { + assert_coin_is_whitelisted(deps.storage, lp_token_out)?; + assert_coins_are_whitelisted(deps.storage, coins_in.to_denoms())?; + + // Decrement coin amounts in account for those sent to pool + for coin_in in &coins_in { + decrement_coin_balance(deps.storage, account_id, coin_in)?; + } + + // After zap is complete, update account's LP token balance + let zapper = ZAPPER.load(deps.storage)?; + let zap_msg = zapper.provide_liquidity_msg(&coins_in, lp_token_out, minimum_receive)?; + let update_balance_msg = update_balance_msg( + &deps.querier, + &env.contract.address, + account_id, + lp_token_out, + )?; + + Ok(Response::new() + .add_message(zap_msg) + .add_message(update_balance_msg) + .add_attribute("action", "rover/credit-manager/provide_liquidity")) +} + +pub fn withdraw_liquidity( + deps: DepsMut, + env: Env, + account_id: &str, + lp_token: Coin, +) -> ContractResult { + assert_coin_is_whitelisted(deps.storage, &lp_token.denom)?; + + let zapper = ZAPPER.load(deps.storage)?; + let coins_out = zapper.estimate_withdraw_liquidity(&deps.querier, &lp_token)?; + assert_coins_are_whitelisted(deps.storage, coins_out.to_denoms())?; + + decrement_coin_balance(deps.storage, account_id, &lp_token)?; + + // After unzap is complete, update account's coin balances + let zap_msg = zapper.withdraw_liquidity_msg(&lp_token)?; + let update_balances_msgs = update_balances_msgs( + &deps.querier, + &env.contract.address, + account_id, + coins_out.to_denoms(), + )?; + + Ok(Response::new() + .add_message(zap_msg) + .add_messages(update_balances_msgs) + .add_attribute("action", "rover/credit-manager/withdraw_liquidity")) +} + +pub fn estimate_provide_liquidity( + deps: Deps, + lp_token_out: &str, + coins_in: Vec, +) -> ContractResult { + let zapper = ZAPPER.load(deps.storage)?; + let estimate = zapper.estimate_provide_liquidity(&deps.querier, lp_token_out, &coins_in)?; + Ok(estimate) +} + +pub fn estimate_withdraw_liquidity(deps: Deps, lp_token: Coin) -> ContractResult> { + let zapper = ZAPPER.load(deps.storage)?; + let estimate = zapper.estimate_withdraw_liquidity(&deps.querier, &lp_token)?; + Ok(estimate) +} diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index ca06bde21..6554997f7 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -68,3 +68,12 @@ pub fn mock_swapper_contract() -> Box> { ); Box::new(contract) } + +pub fn mock_zapper_contract() -> Box> { + let contract = ContractWrapper::new( + mock_zapper::contract::execute, + mock_zapper::contract::instantiate, + mock_zapper::contract::query, + ); + Box::new(contract) +} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 44cf8801f..3603cd310 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -1,21 +1,21 @@ use std::mem::take; use anyhow::Result as AnyResult; +use cosmos_vault_standard::extensions::lockup::Lockup; +use cosmos_vault_standard::extensions::lockup::LockupQueryMsg::Lockups; +use cosmos_vault_standard::msg::QueryMsg::{Info as VaultInfoMsg, VaultExtension}; +use cosmos_vault_standard::msg::{ExtensionQueryMsg, VaultInfo}; use cosmwasm_std::testing::MockApi; use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; -use mars_outpost::red_bank::QueryMsg::UserDebt; -use mars_outpost::red_bank::UserDebtResponse; use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use cosmos_vault_standard::extensions::lockup::Lockup; -use cosmos_vault_standard::extensions::lockup::LockupQueryMsg::Lockups; -use cosmos_vault_standard::msg::QueryMsg::{Info as VaultInfoMsg, VaultExtension}; -use cosmos_vault_standard::msg::{ExtensionQueryMsg, VaultInfo}; use mars_oracle_adapter::msg::{ InstantiateMsg as OracleAdapterInstantiateMsg, PricingMethod, VaultPricingInfo, }; +use mars_outpost::red_bank::QueryMsg::UserDebt; +use mars_outpost::red_bank::UserDebtResponse; use mock_oracle::msg::{ CoinPrice, ExecuteMsg as OracleExecuteMsg, InstantiateMsg as OracleInstantiateMsg, }; @@ -27,19 +27,21 @@ use rover::adapters::swap::{ EstimateExactInSwapResponse, InstantiateMsg as SwapperInstantiateMsg, Swapper, SwapperBase, }; use rover::adapters::vault::{VaultBase, VaultConfig, VaultUnchecked}; -use rover::adapters::{OracleBase, RedBankBase}; +use rover::adapters::{OracleBase, OracleUnchecked, RedBankBase, Zapper, ZapperBase}; use rover::msg::execute::{Action, CallbackMsg}; use rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; use rover::msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtShares, HealthResponse, Positions, SharesResponseItem, VaultPositionResponseItem, VaultWithBalance, }; +use rover::msg::zapper::QueryMsg::EstimateProvideLiquidity; +use rover::msg::zapper::{InstantiateMsg as ZapperInstantiateMsg, LpConfig}; use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::helpers::{ - mock_account_nft_contract, mock_oracle_adapter_contract, mock_oracle_contract, + lp_token_info, mock_account_nft_contract, mock_oracle_adapter_contract, mock_oracle_contract, mock_red_bank_contract, mock_rover_contract, mock_swapper_contract, mock_vault_contract, - AccountToFund, CoinInfo, VaultTestInfo, + mock_zapper_contract, AccountToFund, CoinInfo, VaultTestInfo, }; pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000); @@ -425,6 +427,20 @@ impl MockEnv { ) .unwrap() } + + pub fn estimate_provide_liquidity(&self, lp_token_out: &str, coins_in: &[Coin]) -> Uint128 { + let config = self.query_config(); + self.app + .wrap() + .query_wasm_smart( + config.zapper, + &EstimateProvideLiquidity { + lp_token_out: lp_token_out.to_string(), + coins_in: coins_in.to_vec(), + }, + ) + .unwrap() + } } impl MockEnvBuilder { @@ -511,6 +527,7 @@ impl MockEnvBuilder { allowed_vaults.extend(self.pre_deployed_vaults.clone().unwrap_or_default()); let oracle = self.get_oracle_adapter(allowed_vaults.clone()).into(); + let zapper = self.deploy_zapper(&oracle)?.into(); self.app.instantiate_contract( code_id, @@ -524,6 +541,7 @@ impl MockEnvBuilder { max_liquidation_bonus, max_close_factor, swapper, + zapper, }, &[], "mock-rover-contract", @@ -728,6 +746,33 @@ impl MockEnvBuilder { SwapperBase::new(addr) } + fn deploy_zapper(&mut self, oracle: &OracleUnchecked) -> AnyResult { + let code_id = self.app.store_code(mock_zapper_contract()); + let lp_token = lp_token_info(); + let addr = self.app.instantiate_contract( + code_id, + Addr::unchecked("zapper-instantiator"), + &ZapperInstantiateMsg { + oracle: oracle.clone(), + lp_configs: vec![LpConfig { + lp_token_denom: lp_token.denom.to_string(), + lp_pair_denoms: ("uatom".to_string(), "uosmo".to_string()), + }], + }, + &[], + "mock-vault", + None, + )?; + // Fund with lp tokens to simulate mints + self.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: addr.to_string(), + amount: coins(10_000_000, lp_token.denom), + })) + .unwrap(); + Ok(ZapperBase::new(addr)) + } + /// cw-multi-test does not yet have the ability to mint sdk coins. For this reason, /// this contract expects to be pre-funded with vault tokens and it will simulate the mint. fn fund_vault(&mut self, vault_addr: &Addr, denom: &str) { diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 867f3dc07..177793fff 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -16,6 +16,15 @@ pub struct CoinInfo { pub liquidation_threshold: Decimal, } +#[cw_serde] +pub struct LpCoinInfo { + pub denom: String, + pub price: Decimal, + pub max_ltv: Decimal, + pub liquidation_threshold: Decimal, + pub underlying_pair: (String, String), +} + #[cw_serde] pub struct VaultTestInfo { pub vault_token_denom: String, diff --git a/contracts/credit-manager/tests/test_coin_balances.rs b/contracts/credit-manager/tests/test_coin_balances.rs index a7a17bc38..9c802b5dc 100644 --- a/contracts/credit-manager/tests/test_coin_balances.rs +++ b/contracts/credit-manager/tests/test_coin_balances.rs @@ -6,7 +6,7 @@ use rover::error::ContractError; use rover::msg::execute::Action::Deposit; use rover::msg::execute::CallbackMsg; -use crate::helpers::{assert_err, get_coin, uatom_info, uosmo_info, AccountToFund, MockEnv}; +use crate::helpers::{assert_err, uosmo_info, AccountToFund, MockEnv}; pub mod helpers; @@ -18,9 +18,9 @@ fn test_only_rover_can_call_update_coin_balances() { let res = mock.invoke_callback( &user, - CallbackMsg::UpdateCoinBalances { + CallbackMsg::UpdateCoinBalance { account_id, - previous_balances: vec![], + previous_balance: coin(1, "utest"), }, ); assert_err(res, ContractError::ExternalInvocation) @@ -51,9 +51,9 @@ fn test_user_does_not_have_enough_to_pay_diff() { let res = mock.invoke_callback( &mock.rover.clone(), - CallbackMsg::UpdateCoinBalances { + CallbackMsg::UpdateCoinBalance { account_id, - previous_balances: coins(601, osmo_info.denom), + previous_balance: coin(601, osmo_info.denom), }, ); @@ -92,9 +92,9 @@ fn test_user_gets_rebalanced_down() { mock.invoke_callback( &mock.rover.clone(), - CallbackMsg::UpdateCoinBalances { + CallbackMsg::UpdateCoinBalance { account_id: account_id.clone(), - previous_balances: coins(500, osmo_info.denom.clone()), + previous_balance: coin(500, osmo_info.denom.clone()), }, ) .unwrap(); @@ -137,9 +137,9 @@ fn test_user_gets_rebalanced_up() { mock.invoke_callback( &mock.rover.clone(), - CallbackMsg::UpdateCoinBalances { + CallbackMsg::UpdateCoinBalance { account_id: account_id.clone(), - previous_balances: coins(300, osmo_info.denom.clone()), + previous_balance: coin(300, osmo_info.denom.clone()), }, ) .unwrap(); @@ -149,42 +149,3 @@ fn test_user_gets_rebalanced_up() { assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); assert_eq!(position.coins.first().unwrap().amount.u128(), 500); } - -#[test] -fn test_works_on_multiple() { - let osmo_info = uosmo_info(); - let atom_info = uatom_info(); - - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) - .build() - .unwrap(); - let account_id = mock.create_credit_account(&user).unwrap(); - - mock.app - .sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: mock.rover.clone().to_string(), - amount: vec![ - coin(143, osmo_info.denom.clone()), - coin(57, atom_info.denom.clone()), - ], - })) - .unwrap(); - - mock.invoke_callback( - &mock.rover.clone(), - CallbackMsg::UpdateCoinBalances { - account_id: account_id.clone(), - previous_balances: vec![coin(0, osmo_info.denom), coin(0, atom_info.denom)], - }, - ) - .unwrap(); - - let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 2); - let osmo = get_coin("uosmo", &position.coins); - assert_eq!(osmo.amount.u128(), 143); - let atom = get_coin("uatom", &position.coins); - assert_eq!(atom.amount.u128(), 57); -} diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index a4e657adc..69d2292eb 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Uint128}; use rover::msg::execute::Action; @@ -40,7 +40,8 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { all_vaults.iter().for_each(|v| { actions.extend([Action::EnterVault { vault: mock.get_vault(v), - coin: lp_token.to_coin(10), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(10)), }]); }); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index 6003fc0e1..9eef05024 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Uint128}; use itertools::Itertools; use rover::msg::execute::Action; @@ -41,7 +41,8 @@ fn test_pagination_on_all_vault_positions_query_works() { all_vaults.iter().for_each(|v| { actions.extend([Action::EnterVault { vault: mock.get_vault(v), - coin: lp_token.to_coin(10), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(10)), }]); }); diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index efab57739..8947c18a5 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -96,7 +96,8 @@ fn test_vault_positions_contribute_to_health() { Deposit(lp_token.to_coin(220)), EnterVault { vault, - coin: lp_token.to_coin(200), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(200)), }, Borrow(atom_info.to_coin(14)), ], diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 35c157e37..3d7acfad2 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -95,7 +95,8 @@ fn test_liquidatee_is_not_liquidatable() { Deposit(lp_token.to_coin(200)), EnterVault { vault, - coin: lp_token.to_coin(200), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(200)), }, ], &[lp_token.to_coin(200)], @@ -152,7 +153,8 @@ fn test_liquidator_does_not_have_debt_coin_in_credit_account() { Deposit(lp_token.to_coin(200)), EnterVault { vault, - coin: lp_token.to_coin(200), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(200)), }, Borrow(ujake.to_coin(175)), ], @@ -222,7 +224,8 @@ fn test_liquidate_unlocked_vault() { Deposit(lp_token.to_coin(200)), EnterVault { vault, - coin: lp_token.to_coin(200), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(200)), }, Borrow(ujake.to_coin(175)), ], @@ -307,7 +310,8 @@ fn test_liquidate_locked_vault() { Deposit(lp_token.to_coin(80)), EnterVault { vault, - coin: lp_token.to_coin(80), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(80)), }, Borrow(atom.to_coin(700)), ], @@ -395,7 +399,8 @@ fn test_liquidate_unlocking_priority() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(200), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(200)), }, Borrow(ujake.to_coin(175)), RequestVaultUnlock { @@ -491,7 +496,8 @@ fn test_liquidate_unlocking_liquidation_order() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(200), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(200)), }, Borrow(ujake.to_coin(175)), RequestVaultUnlock { @@ -622,7 +628,8 @@ fn test_liquidation_calculation_adjustment() { Deposit(lp_token.to_coin(200)), EnterVault { vault, - coin: lp_token.to_coin(200), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(200)), }, Borrow(ujake.to_coin(175)), ], diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 45e036c65..266928f39 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{coin, Addr, Decimal}; use rover::adapters::swap::SwapperBase; use rover::adapters::vault::{VaultBase, VaultConfig}; -use rover::adapters::{OracleBase, RedBankBase}; +use rover::adapters::{OracleBase, RedBankBase, ZapperBase}; use rover::error::ContractError; use rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; @@ -27,6 +27,7 @@ fn test_only_owner_can_update_config() { max_close_factor: None, swapper: None, vault_configs: None, + zapper: None, }, ); @@ -59,6 +60,7 @@ fn test_raises_on_invalid_vaults_config() { whitelisted: true, }, }]), + zapper: None, }, ); @@ -84,6 +86,7 @@ fn test_raises_on_invalid_vaults_config() { whitelisted: true, }, }]), + zapper: None, }, ); @@ -111,6 +114,7 @@ fn test_update_config_works_with_full_config() { }]; let new_allowed_coins = vec!["uosmo".to_string()]; let new_oracle = OracleBase::new("new_oracle".to_string()); + let new_zapper = ZapperBase::new("new_zapper".to_string()); let new_liq_bonus = Decimal::from_atomics(17u128, 2).unwrap(); let new_close_factor = Decimal::from_atomics(32u128, 2).unwrap(); let new_swapper = SwapperBase::new("new_swapper".to_string()); @@ -127,6 +131,7 @@ fn test_update_config_works_with_full_config() { max_close_factor: Some(new_close_factor), swapper: Some(new_swapper.clone()), vault_configs: Some(new_vault_configs.clone()), + zapper: Some(new_zapper.clone()), }, ) .unwrap(); @@ -153,6 +158,9 @@ fn test_update_config_works_with_full_config() { assert_eq!(&new_config.oracle, new_oracle.address()); assert_ne!(new_config.oracle, original_config.oracle); + assert_eq!(&new_config.zapper, new_zapper.address()); + assert_ne!(new_config.zapper, original_config.zapper); + assert_eq!(new_config.max_liquidation_bonus, new_liq_bonus); assert_ne!( new_config.max_liquidation_bonus, @@ -273,6 +281,7 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { assert_eq!(new_queried_allowed_coins, original_allowed_coins); assert_eq!(new_config.red_bank, original_config.red_bank); assert_eq!(new_config.oracle, original_config.oracle); + assert_eq!(new_config.zapper, original_config.zapper); assert_eq!( new_config.max_liquidation_bonus, original_config.max_liquidation_bonus diff --git a/contracts/credit-manager/tests/test_vault_enter.rs b/contracts/credit-manager/tests/test_vault_enter.rs index 7161e3a21..4aeb25c8b 100644 --- a/contracts/credit-manager/tests/test_vault_enter.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -1,5 +1,6 @@ use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{coin, Addr, OverflowError, Uint128}; +use cosmwasm_std::StdError::NotFound; +use cosmwasm_std::{Addr, OverflowError, Uint128}; use mock_vault::contract::STARTING_VAULT_SHARES; use rover::adapters::vault::VaultBase; @@ -26,7 +27,8 @@ fn test_only_account_owner_can_take_action() { &bad_guy, vec![EnterVault { vault: VaultBase::new("xyz".to_string()), - coin: coin(1, "uosmo"), + denom: "uosmo".to_string(), + amount: Some(Uint128::new(1)), }], &[], ); @@ -59,7 +61,8 @@ fn test_deposit_denom_is_whitelisted() { &user, vec![EnterVault { vault, - coin: coin(200, lp_token.denom.clone()), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(200)), }], &[], ); @@ -75,7 +78,7 @@ fn test_vault_is_whitelisted() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom, uosmo]) + .allowed_coins(&[uatom.clone(), uosmo]) .allowed_vaults(&[leverage_vault]) .build() .unwrap(); @@ -87,7 +90,8 @@ fn test_vault_is_whitelisted() { &user, vec![EnterVault { vault: VaultBase::new("unknown_vault".to_string()), - coin: coin(200, "uatom"), + denom: uatom.denom, + amount: Some(Uint128::new(200)), }], &[], ); @@ -105,7 +109,7 @@ fn test_deposited_coin_matches_vault_requirements() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom]) + .allowed_coins(&[uatom.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .build() .unwrap(); @@ -117,7 +121,8 @@ fn test_deposited_coin_matches_vault_requirements() { &user, vec![EnterVault { vault: mock.get_vault(&leverage_vault), - coin: coin(200, "uatom"), + denom: uatom.denom, + amount: Some(Uint128::new(200)), }], &[], ); @@ -131,7 +136,7 @@ fn test_deposited_coin_matches_vault_requirements() { } #[test] -fn test_fails_if_not_enough_funds_for_deposit() { +fn test_fails_if_not_enough_funds_for_implied_deposit() { let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); @@ -153,7 +158,45 @@ fn test_fails_if_not_enough_funds_for_deposit() { &user, vec![EnterVault { vault: mock.get_vault(&leverage_vault), - coin: coin(200, lp_token.denom), + denom: lp_token.denom, + amount: None, + }], + &[], + ); + + assert_err( + res, + ContractError::Std(NotFound { + kind: "cosmwasm_std::math::uint128::Uint128".to_string(), + }), + ); +} + +#[test] +fn test_fails_if_not_enough_funds_for_enumerated_deposit() { + let lp_token = lp_token_info(); + let leverage_vault = unlocked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![EnterVault { + vault: mock.get_vault(&leverage_vault), + denom: lp_token.denom, + amount: Some(Uint128::new(200)), }], &[], ); @@ -196,7 +239,8 @@ fn test_successful_deposit_into_locked_vault() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(23), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(23)), }, ], &[lp_token.to_coin(200)], @@ -253,7 +297,8 @@ fn test_successful_deposit_into_unlocked_vault() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(23), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(23)), }, ], &[lp_token.to_coin(200)], @@ -310,7 +355,8 @@ fn test_vault_deposit_must_be_under_cap() { Deposit(lp_token.to_coin(700_000)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(700_000), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(700_000)), }, ], &[lp_token.to_coin(700_000)], @@ -327,7 +373,8 @@ fn test_vault_deposit_must_be_under_cap() { Deposit(lp_token.to_coin(100_000)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(100_000), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(100_000)), }, ], &[lp_token.to_coin(100_000)], @@ -344,7 +391,8 @@ fn test_vault_deposit_must_be_under_cap() { Deposit(lp_token.to_coin(2_500_000)), EnterVault { vault, - coin: lp_token.to_coin(2_500_000), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(2_500_000)), }, ], &[lp_token.to_coin(2_500_000)], @@ -358,3 +406,51 @@ fn test_vault_deposit_must_be_under_cap() { }, ); } + +#[test] +fn test_successful_deposit_with_implied_full_balance_amount() { + let lp_token = lp_token_info(); + let leverage_vault = unlocked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault: vault.clone(), + denom: lp_token.denom.clone(), + amount: None, + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + // Assert credit account has full balance of LP token + let res = mock.query_positions(&account_id); + let amount = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().amount.unlocked()); + assert_eq!(amount, Uint128::new(200)); + assert_eq!(res.coins.len(), 0); + + // Assert vault indeed has those tokens + let base_denom = mock.query_balance( + &Addr::unchecked(vault.address), + &leverage_vault.base_token_denom, + ); + assert_eq!(base_denom.amount, Uint128::new(200)) +} diff --git a/contracts/credit-manager/tests/test_vault_exit.rs b/contracts/credit-manager/tests/test_vault_exit.rs index 6e45565f8..7a03d1b0e 100644 --- a/contracts/credit-manager/tests/test_vault_exit.rs +++ b/contracts/credit-manager/tests/test_vault_exit.rs @@ -157,7 +157,8 @@ fn test_force_withdraw_breaks_lock() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(100), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(100)), }, ], &[lp_token.to_coin(200)], @@ -221,7 +222,8 @@ fn test_withdraw_with_unlocked_vault_coins() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(100), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(100)), }, ], &[lp_token.to_coin(200)], diff --git a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs index 6a56f7beb..e98565140 100644 --- a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs +++ b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs @@ -93,7 +93,8 @@ fn test_not_owner_of_unlocking_position() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(23), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(23)), }, RequestVaultUnlock { vault: vault.clone(), @@ -117,7 +118,8 @@ fn test_not_owner_of_unlocking_position() { Deposit(lp_token.to_coin(2)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(2), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(2)), }, RequestVaultUnlock { vault: vault.clone(), @@ -160,7 +162,8 @@ fn test_unlocking_position_not_ready_time() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(23), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(23)), }, RequestVaultUnlock { vault: vault.clone(), @@ -213,7 +216,8 @@ fn test_unlocking_position_not_ready_blocks() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(23), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(23)), }, RequestVaultUnlock { vault: vault.clone(), @@ -266,7 +270,8 @@ fn test_withdraw_unlock_success_time_expiring() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(200), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(200)), }, RequestVaultUnlock { vault: vault.clone(), @@ -341,7 +346,8 @@ fn test_withdraw_unlock_success_block_expiring() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(200), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(200)), }, RequestVaultUnlock { vault: vault.clone(), diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs index 1b3c801f1..32eca0b10 100644 --- a/contracts/credit-manager/tests/test_vault_request_unlock.rs +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -174,7 +174,8 @@ fn test_not_enough_vault_tokens_for_request() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(23), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(23)), }, RequestVaultUnlock { vault, @@ -220,7 +221,8 @@ fn test_request_unlocked() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - coin: lp_token.to_coin(23), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(23)), }, RequestVaultUnlock { vault: vault.clone(), diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs new file mode 100644 index 000000000..bf2fda504 --- /dev/null +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -0,0 +1,302 @@ +use cosmwasm_std::OverflowOperation::Sub; +use cosmwasm_std::{Addr, OverflowError, Uint128}; +use mock_zapper::contract::STARTING_LP_POOL_TOKENS; + +use mock_zapper::error::ContractError; +use rover::error::ContractError as RoverError; +use rover::msg::execute::Action::{Deposit, ProvideLiquidity}; + +use crate::helpers::{ + assert_err, get_coin, lp_token_info, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, +}; + +pub mod helpers; + +#[test] +fn test_only_token_owner_can_zap_for_account() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().build().unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); + + let another_user = Addr::unchecked("another_user"); + let res = mock.update_credit_account( + &account_id, + &another_user, + vec![ProvideLiquidity { + coins_in: vec![], + lp_token_out: "".to_string(), + minimum_receive: Default::default(), + }], + &[], + ); + + assert_err( + res, + RoverError::NotTokenOwner { + user: another_user.clone().into(), + account_id: account_id.clone(), + }, + ); +} + +#[test] +fn test_does_not_have_enough_tokens_to_provide_liq() { + let atom = uatom_info(); + let osmo = uosmo_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), osmo.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + let res = mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + Deposit(osmo.to_coin(50)), + ProvideLiquidity { + coins_in: vec![atom.to_coin(100), osmo.to_coin(200)], + lp_token_out: lp_token.denom, + minimum_receive: Uint128::zero(), + }, + ], + &[atom.to_coin(100), osmo.to_coin(50)], + ); + + assert_err( + res, + RoverError::Overflow(OverflowError { + operation: Sub, + operand1: "50".to_string(), + operand2: "200".to_string(), + }), + ) +} + +#[test] +fn test_lp_token_out_must_be_whitelisted() { + let atom = uatom_info(); + let osmo = uosmo_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[atom.clone(), osmo.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), osmo.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + let res = mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + Deposit(osmo.to_coin(50)), + ProvideLiquidity { + coins_in: vec![atom.to_coin(100), osmo.to_coin(200)], + lp_token_out: lp_token.denom.clone(), + minimum_receive: Uint128::zero(), + }, + ], + &[atom.to_coin(100), osmo.to_coin(50)], + ); + + assert_err(res, RoverError::NotWhitelisted(lp_token.denom)) +} + +#[test] +fn test_coins_in_must_be_whitelisted() { + let atom = uatom_info(); + let osmo = uosmo_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), osmo.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), osmo.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + let res = mock.update_credit_account( + &account_id, + &user, + vec![ProvideLiquidity { + coins_in: vec![atom.to_coin(100), osmo.to_coin(200)], + lp_token_out: lp_token.denom, + minimum_receive: Uint128::zero(), + }], + &[], + ); + + assert_err(res, RoverError::NotWhitelisted(atom.denom)) +} + +#[test] +fn test_min_received_too_high() { + let atom = uatom_info(); + let osmo = uosmo_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), osmo.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + let err = mock + .update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + Deposit(osmo.to_coin(50)), + ProvideLiquidity { + coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + lp_token_out: lp_token.denom, + minimum_receive: Uint128::new(100_000_000_000), + }, + ], + &[atom.to_coin(100), osmo.to_coin(50)], + ) + .unwrap_err(); + + let contract_err: ContractError = err.downcast().unwrap(); + assert_eq!(contract_err, ContractError::ReceivedBelowMinimum); +} + +#[test] +fn test_wrong_denom_provided() { + let atom = uatom_info(); + let jake = ujake_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone(), jake.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), jake.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + let err = mock + .update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + Deposit(jake.to_coin(50)), + ProvideLiquidity { + coins_in: vec![atom.to_coin(100), jake.to_coin(50)], + lp_token_out: lp_token.denom.clone(), + minimum_receive: Uint128::zero(), + }, + ], + &[atom.to_coin(100), jake.to_coin(50)], + ) + .unwrap_err(); + + let contract_err: ContractError = err.downcast().unwrap(); + assert_eq!( + contract_err, + ContractError::RequirementsNotMet { + lp_token: lp_token.denom, + coin0: atom.denom, + coin1: "uosmo".to_string() + } + ); +} + +#[test] +fn test_successful_zap() { + let atom = uatom_info(); + let osmo = uosmo_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), osmo.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + let estimate = + mock.estimate_provide_liquidity(&lp_token.denom, &[atom.to_coin(100), osmo.to_coin(50)]); + let slippage_adjusted = estimate.multiply_ratio(Uint128::new(95), Uint128::new(100)); + assert_eq!(slippage_adjusted, Uint128::new(950_000)); // 1_000_000 * .95 + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + Deposit(osmo.to_coin(50)), + ProvideLiquidity { + coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + lp_token_out: lp_token.denom.clone(), + minimum_receive: slippage_adjusted, + }, + ], + &[atom.to_coin(100), osmo.to_coin(50)], + ) + .unwrap(); + + // assert follow up estimate can be made (calculates ratio from first deposit) + let estimate = + mock.estimate_provide_liquidity(&lp_token.denom, &[atom.to_coin(300), osmo.to_coin(150)]); + assert_eq!(estimate, STARTING_LP_POOL_TOKENS * Uint128::new(3)); // 3x the size as first deposit + + // assert user's new position + let positions = mock.query_positions(&account_id); + assert_eq!(positions.coins.len(), 1); + let lp_balance = get_coin(&lp_token.denom, &positions.coins); + assert_eq!(lp_balance.amount, STARTING_LP_POOL_TOKENS); + + // assert rover actually has the tokens + let lp_balance = mock.query_balance(&mock.rover, &lp_token.denom); + assert_eq!(lp_balance.amount, STARTING_LP_POOL_TOKENS); + let atom_balance = mock.query_balance(&mock.rover, &atom.denom); + assert_eq!(atom_balance.amount, Uint128::zero()); + let osmo_balance = mock.query_balance(&mock.rover, &osmo.denom); + assert_eq!(osmo_balance.amount, Uint128::zero()); + + // assert coin balance of zapper contract + let config = mock.query_config(); + let lp_balance = mock.query_balance(&Addr::unchecked(config.zapper.clone()), &lp_token.denom); + // prefunded minus minted + assert_eq!( + lp_balance.amount, + Uint128::new(10_000_000) - STARTING_LP_POOL_TOKENS + ); + let atom_balance = mock.query_balance(&Addr::unchecked(config.zapper.clone()), &atom.denom); + assert_eq!(atom_balance.amount, Uint128::new(100)); + let osmo_balance = mock.query_balance(&Addr::unchecked(config.zapper), &osmo.denom); + assert_eq!(osmo_balance.amount, Uint128::new(50)); +} diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs new file mode 100644 index 000000000..2be7e0e33 --- /dev/null +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -0,0 +1,224 @@ +use cosmwasm_std::OverflowOperation::Sub; +use cosmwasm_std::{Addr, OverflowError, Uint128}; + +use mock_zapper::contract::STARTING_LP_POOL_TOKENS; +use rover::error::ContractError as RoverError; +use rover::msg::execute::Action::{Deposit, ProvideLiquidity, WithdrawLiquidity}; +use rover::msg::instantiate::ConfigUpdates; + +use crate::helpers::{ + assert_err, get_coin, lp_token_info, uatom_info, uosmo_info, AccountToFund, MockEnv, +}; + +pub mod helpers; + +#[test] +fn test_only_token_owner_can_unzap_for_account() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().build().unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); + + let another_user = Addr::unchecked("another_user"); + let res = mock.update_credit_account( + &account_id, + &another_user, + vec![WithdrawLiquidity { + lp_token: Default::default(), + }], + &[], + ); + + assert_err( + res, + RoverError::NotTokenOwner { + user: another_user.into(), + account_id, + }, + ) +} + +#[test] +fn test_lp_token_in_must_be_whitelisted() { + let lp_token = lp_token_info(); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().build().unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + let res = mock.update_credit_account( + &account_id, + &user, + vec![WithdrawLiquidity { + lp_token: lp_token.to_coin(100), + }], + &[], + ); + + assert_err(res, RoverError::NotWhitelisted(lp_token.denom)) +} + +#[test] +fn test_coins_out_must_be_whitelisted() { + let atom = uatom_info(); + let osmo = uosmo_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), osmo.to_coin(300)], + }) + .build() + .unwrap(); + + // Seed zapper with denoms so test can estimate withdraws + let account_id = mock.create_credit_account(&user).unwrap(); + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + Deposit(osmo.to_coin(50)), + ProvideLiquidity { + coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + lp_token_out: lp_token.denom.clone(), + minimum_receive: Uint128::zero(), + }, + ], + &[atom.to_coin(100), osmo.to_coin(50)], + ) + .unwrap(); + + // update config to disallow denoms out + let config = mock.query_config(); + mock.update_config( + &Addr::unchecked(config.owner), + ConfigUpdates { + allowed_coins: Some(vec![lp_token.denom.clone(), atom.denom]), + ..Default::default() + }, + ) + .unwrap(); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![WithdrawLiquidity { + lp_token: lp_token.to_coin(10), + }], + &[], + ); + + assert_err(res, RoverError::NotWhitelisted(osmo.denom)) +} + +#[test] +fn test_does_not_have_the_tokens_to_withdraw_liq() { + let atom = uatom_info(); + let osmo = uosmo_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), osmo.to_coin(300)], + }) + .build() + .unwrap(); + + // Seed zapper with denoms so test can estimate withdraws + let account_id = mock.create_credit_account(&user).unwrap(); + let attempted_unzap_amount = 100_000_000_000; + let res = mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + Deposit(osmo.to_coin(50)), + ProvideLiquidity { + coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + lp_token_out: lp_token.denom.clone(), + minimum_receive: Uint128::zero(), + }, + WithdrawLiquidity { + lp_token: lp_token.to_coin(attempted_unzap_amount), + }, + ], + &[atom.to_coin(100), osmo.to_coin(50)], + ); + + assert_err( + res, + RoverError::Overflow(OverflowError { + operation: Sub, + operand1: STARTING_LP_POOL_TOKENS.to_string(), + operand2: attempted_unzap_amount.to_string(), + }), + ) +} + +#[test] +fn test_successful_unzap() { + let atom = uatom_info(); + let osmo = uosmo_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), osmo.to_coin(300)], + }) + .build() + .unwrap(); + + // Seed zapper with denoms so test can estimate withdraws + let account_id = mock.create_credit_account(&user).unwrap(); + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + Deposit(osmo.to_coin(50)), + ProvideLiquidity { + coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + lp_token_out: lp_token.denom.clone(), + minimum_receive: Uint128::zero(), + }, + WithdrawLiquidity { + lp_token: lp_token.to_coin(STARTING_LP_POOL_TOKENS.u128()), + }, + ], + &[atom.to_coin(100), osmo.to_coin(50)], + ) + .unwrap(); + + // Assert user's new position + let positions = mock.query_positions(&account_id); + assert_eq!(positions.coins.len(), 2); + let atom_balance = get_coin(&atom.denom, &positions.coins); + assert_eq!(atom_balance.amount, Uint128::new(100)); + let osmo_balance = get_coin(&osmo.denom, &positions.coins); + assert_eq!(osmo_balance.amount, Uint128::new(50)); + + // assert rover actually has the tokens + let lp_balance = mock.query_balance(&mock.rover, &lp_token.denom); + assert_eq!(lp_balance.amount, Uint128::zero()); + let atom_balance = mock.query_balance(&mock.rover, &atom.denom); + assert_eq!(atom_balance.amount, Uint128::new(100)); + let osmo_balance = mock.query_balance(&mock.rover, &osmo.denom); + assert_eq!(osmo_balance.amount, Uint128::new(50)); + + // assert coin balance of zapper contract + let config = mock.query_config(); + let lp_balance = mock.query_balance(&Addr::unchecked(config.zapper.clone()), &lp_token.denom); + assert_eq!(lp_balance.amount, Uint128::new(10_000_000)); // prefunded original amount + let atom_balance = mock.query_balance(&Addr::unchecked(config.zapper.clone()), &atom.denom); + assert_eq!(atom_balance.amount, Uint128::zero()); + let osmo_balance = mock.query_balance(&Addr::unchecked(config.zapper), &osmo.denom); + assert_eq!(osmo_balance.amount, Uint128::zero()); +} diff --git a/contracts/mock-zapper/Cargo.toml b/contracts/mock-zapper/Cargo.toml new file mode 100644 index 000000000..28dea6515 --- /dev/null +++ b/contracts/mock-zapper/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "mock-zapper" +version = "1.0.0" +authors = ["grod220 "] +edition = "2021" +license = "GPL-3.0-or-later" +repository = "https://github.com/mars-protocol/rover" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +rover = { version = "1.0", path = "../../packages/rover" } + +cosmwasm-schema = "1.1" +cosmwasm-std = "1.1" +cw-storage-plus = "0.16" +cw-utils = "0.16" +thiserror = "1.0" diff --git a/contracts/mock-zapper/examples/schema.rs b/contracts/mock-zapper/examples/schema.rs new file mode 100644 index 000000000..59c9ec477 --- /dev/null +++ b/contracts/mock-zapper/examples/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use rover::msg::zapper::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/mock-zapper/src/contract.rs b/contracts/mock-zapper/src/contract.rs new file mode 100644 index 000000000..dfee12207 --- /dev/null +++ b/contracts/mock-zapper/src/contract.rs @@ -0,0 +1,65 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{coin, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; + +use crate::error::ContractResult; +use crate::execute::{provide_liquidity, withdraw_liquidity}; +use crate::query::{estimate_provide_liquidity, estimate_withdraw_liquidity}; +use crate::state::{COIN_BALANCES, ORACLE}; +use rover::msg::zapper::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +pub const STARTING_LP_POOL_TOKENS: Uint128 = Uint128::new(1_000_000); + +/// cw-multi-test does not yet have the ability to mint sdk coins. For this reason, +/// this contract expects to be pre-funded with LP tokens and it will simulate the mint. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> ContractResult { + ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; + for config in msg.lp_configs { + COIN_BALANCES.save( + deps.storage, + &config.lp_token_denom, + &( + coin(0, config.lp_pair_denoms.0), + coin(0, config.lp_pair_denoms.1), + ), + )?; + } + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> ContractResult { + match msg { + ExecuteMsg::ProvideLiquidity { + lp_token_out, + minimum_receive, + .. + } => provide_liquidity(deps, info, lp_token_out, minimum_receive), + ExecuteMsg::WithdrawLiquidity { .. } => withdraw_liquidity(deps, info), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> ContractResult { + let res = match msg { + QueryMsg::EstimateProvideLiquidity { + lp_token_out, + coins_in, + } => to_binary(&estimate_provide_liquidity(&deps, &lp_token_out, coins_in)?), + QueryMsg::EstimateWithdrawLiquidity { coin_in } => { + to_binary(&estimate_withdraw_liquidity(deps.storage, &coin_in)?) + } + }; + res.map_err(Into::into) +} diff --git a/contracts/mock-zapper/src/error.rs b/contracts/mock-zapper/src/error.rs new file mode 100644 index 000000000..c21aeec6a --- /dev/null +++ b/contracts/mock-zapper/src/error.rs @@ -0,0 +1,30 @@ +use cosmwasm_std::{CheckedMultiplyRatioError, StdError}; +use rover::error::ContractError as RoverError; +use thiserror::Error; + +pub type ContractResult = Result; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + RoverError(#[from] RoverError), + + #[error("{0}")] + CheckedMultiply(#[from] CheckedMultiplyRatioError), + + #[error("Required minimum received was not met")] + ReceivedBelowMinimum, + + #[error("Could not find coin trying to access")] + CoinNotFound, + + #[error("{lp_token:?} requires {coin0:?} and {coin1:?}")] + RequirementsNotMet { + lp_token: String, + coin0: String, + coin1: String, + }, +} diff --git a/contracts/mock-zapper/src/execute.rs b/contracts/mock-zapper/src/execute.rs new file mode 100644 index 000000000..7f40019e7 --- /dev/null +++ b/contracts/mock-zapper/src/execute.rs @@ -0,0 +1,100 @@ +use crate::error::ContractError; +use crate::query::{estimate_provide_liquidity, estimate_withdraw_liquidity}; +use crate::state::{COIN_BALANCES, LP_TOKEN_SUPPLY}; +use cosmwasm_std::{ + BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, Storage, Uint128, +}; + +pub fn provide_liquidity( + deps: DepsMut, + info: MessageInfo, + lp_token_out_denom: String, + minimum_receive: Uint128, +) -> Result { + let sent_coin_a = info.funds.get(0).ok_or(ContractError::CoinNotFound)?; + let sent_coin_b = info.funds.get(1).ok_or(ContractError::CoinNotFound)?; + let (mut coin0, mut coin1) = COIN_BALANCES.load(deps.storage, &lp_token_out_denom)?; + + if (sent_coin_a.denom != coin0.denom && sent_coin_a.denom != coin1.denom) + || (sent_coin_b.denom != coin0.denom && sent_coin_b.denom != coin1.denom) + { + return Err(ContractError::RequirementsNotMet { + lp_token: lp_token_out_denom, + coin0: coin0.denom, + coin1: coin1.denom, + }); + } + + let lp_token_amount = + estimate_provide_liquidity(&deps.as_ref(), &lp_token_out_denom, info.funds.clone())?; + + if minimum_receive > lp_token_amount { + return Err(ContractError::ReceivedBelowMinimum); + } + + // Update internal balances + if coin0.denom == sent_coin_a.denom { + coin0.amount += sent_coin_a.amount; + coin1.amount += sent_coin_b.amount; + } else { + coin0.amount += sent_coin_b.amount; + coin1.amount += sent_coin_a.amount; + } + COIN_BALANCES.save(deps.storage, &lp_token_out_denom, &(coin0, coin1))?; + + // Send LP tokens to user (assumes mock zapper has been pre-funded with this token) + mock_lp_token_mint(deps.storage, lp_token_amount, &lp_token_out_denom)?; + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.to_string(), + amount: vec![Coin { + denom: lp_token_out_denom, + amount: lp_token_amount, + }], + }); + + Ok(Response::new().add_message(transfer_msg)) +} + +pub fn withdraw_liquidity(deps: DepsMut, info: MessageInfo) -> Result { + let lp_token_sent = info.funds.get(0).ok_or(ContractError::CoinNotFound)?; + mock_lp_token_burn(deps.storage, lp_token_sent)?; + + let underlying_coins = estimate_withdraw_liquidity(deps.storage, lp_token_sent)?; + + // Update internal balances + let (mut coin0, mut coin1) = COIN_BALANCES.load(deps.storage, &lp_token_sent.denom)?; + let coin_a = underlying_coins.get(0).ok_or(ContractError::CoinNotFound)?; + let coin_b = underlying_coins.get(1).ok_or(ContractError::CoinNotFound)?; + if coin0.denom == coin_a.denom { + coin0.amount -= coin_a.amount; + coin1.amount -= coin_b.amount; + } else { + coin0.amount -= coin_b.amount; + coin1.amount -= coin_a.amount; + }; + COIN_BALANCES.save(deps.storage, &lp_token_sent.denom, &(coin0, coin1))?; + + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.to_string(), + amount: underlying_coins, + }); + Ok(Response::new().add_message(transfer_msg)) +} + +fn mock_lp_token_mint( + storage: &mut dyn Storage, + amount: Uint128, + lp_token_out_denom: &str, +) -> Result<(), StdError> { + let total_supply = LP_TOKEN_SUPPLY + .load(storage, lp_token_out_denom) + .unwrap_or(Uint128::zero()); + LP_TOKEN_SUPPLY.save(storage, lp_token_out_denom, &(total_supply + amount))?; + Ok(()) +} + +fn mock_lp_token_burn(storage: &mut dyn Storage, lp_token: &Coin) -> Result { + LP_TOKEN_SUPPLY.update(storage, &lp_token.denom, |total_supply| -> StdResult<_> { + Ok(total_supply.unwrap_or_else(Uint128::zero) - lp_token.amount) + }) +} diff --git a/contracts/mock-zapper/src/lib.rs b/contracts/mock-zapper/src/lib.rs new file mode 100644 index 000000000..512e6c212 --- /dev/null +++ b/contracts/mock-zapper/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; +pub mod error; +pub mod execute; +pub mod query; +pub mod state; diff --git a/contracts/mock-zapper/src/query.rs b/contracts/mock-zapper/src/query.rs new file mode 100644 index 000000000..25e85009a --- /dev/null +++ b/contracts/mock-zapper/src/query.rs @@ -0,0 +1,50 @@ +use cosmwasm_std::{Coin, Deps, Storage, Uint128}; + +use crate::contract::STARTING_LP_POOL_TOKENS; +use crate::error::ContractError; +use crate::state::{COIN_BALANCES, LP_TOKEN_SUPPLY, ORACLE}; + +pub fn estimate_provide_liquidity( + deps: &Deps, + lp_token_out: &str, + coins_in: Vec, +) -> Result { + let total_supply = LP_TOKEN_SUPPLY + .load(deps.storage, lp_token_out) + .unwrap_or(Uint128::zero()); + + let lp_tokens_estimate = if total_supply.is_zero() { + STARTING_LP_POOL_TOKENS + } else { + let (coin0, coin1) = COIN_BALANCES.load(deps.storage, lp_token_out)?; + let oracle = ORACLE.load(deps.storage)?; + let total_underlying_value = oracle.query_total_value(&deps.querier, &[coin0, coin1])?; + let given_value = oracle.query_total_value(&deps.querier, &coins_in)?; + total_supply + .checked_multiply_ratio(given_value.atomics(), total_underlying_value.atomics())? + }; + Ok(lp_tokens_estimate) +} + +pub fn estimate_withdraw_liquidity( + storage: &dyn Storage, + lp_token: &Coin, +) -> Result, ContractError> { + let total_supply = LP_TOKEN_SUPPLY.load(storage, &lp_token.denom)?; + let (coin0, coin1) = COIN_BALANCES.load(storage, &lp_token.denom)?; + + if total_supply.is_zero() { + Ok(vec![coin0, coin1]) + } else { + Ok(vec![ + Coin { + denom: coin0.denom, + amount: coin0.amount.multiply_ratio(lp_token.amount, total_supply), + }, + Coin { + denom: coin1.denom, + amount: coin1.amount.multiply_ratio(lp_token.amount, total_supply), + }, + ]) + } +} diff --git a/contracts/mock-zapper/src/state.rs b/contracts/mock-zapper/src/state.rs new file mode 100644 index 000000000..73c4ad5de --- /dev/null +++ b/contracts/mock-zapper/src/state.rs @@ -0,0 +1,8 @@ +use cosmwasm_std::{Coin, Uint128}; +use cw_storage_plus::{Item, Map}; +use rover::adapters::Oracle; + +pub const ORACLE: Item = Item::new("oracle"); + +pub const LP_TOKEN_SUPPLY: Map<&str, Uint128> = Map::new("lp_token_supply"); // LP token denom -> Total LP token supply +pub const COIN_BALANCES: Map<&str, (Coin, Coin)> = Map::new("coin_balances"); // LP token denom -> Underlying tokens diff --git a/packages/rover/src/adapters/mod.rs b/packages/rover/src/adapters/mod.rs index d54da8a17..ea04f1044 100644 --- a/packages/rover/src/adapters/mod.rs +++ b/packages/rover/src/adapters/mod.rs @@ -1,8 +1,10 @@ mod oracle; mod red_bank; +mod zapper; pub mod swap; pub mod vault; pub use self::oracle::*; pub use self::red_bank::*; +pub use self::zapper::*; diff --git a/packages/rover/src/adapters/zapper.rs b/packages/rover/src/adapters/zapper.rs new file mode 100644 index 000000000..c4e49b4cc --- /dev/null +++ b/packages/rover/src/adapters/zapper.rs @@ -0,0 +1,89 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + to_binary, Addr, Api, Coin, CosmosMsg, QuerierWrapper, StdResult, Uint128, WasmMsg, +}; + +use crate::msg::zapper::{ExecuteMsg, QueryMsg}; + +#[cw_serde] +pub struct ZapperBase(T); + +impl ZapperBase { + pub fn new(address: T) -> ZapperBase { + ZapperBase(address) + } + + pub fn address(&self) -> &T { + &self.0 + } +} + +pub type ZapperUnchecked = ZapperBase; +pub type Zapper = ZapperBase; + +impl From for ZapperUnchecked { + fn from(zapper: Zapper) -> Self { + Self(zapper.address().to_string()) + } +} + +impl ZapperUnchecked { + pub fn check(&self, api: &dyn Api) -> StdResult { + Ok(ZapperBase::new(api.addr_validate(self.address())?)) + } +} + +impl Zapper { + pub fn estimate_provide_liquidity( + &self, + querier: &QuerierWrapper, + lp_token_out: &str, + coins_in: &[Coin], + ) -> StdResult { + querier.query_wasm_smart( + self.address().to_string(), + &QueryMsg::EstimateProvideLiquidity { + lp_token_out: lp_token_out.to_string(), + coins_in: coins_in.to_vec(), + }, + ) + } + + pub fn estimate_withdraw_liquidity( + &self, + querier: &QuerierWrapper, + lp_token: &Coin, + ) -> StdResult> { + querier.query_wasm_smart( + self.address().to_string(), + &QueryMsg::EstimateWithdrawLiquidity { + coin_in: lp_token.clone(), + }, + ) + } + + pub fn provide_liquidity_msg( + &self, + coins_in: &[Coin], + lp_token_out: &str, + minimum_receive: Uint128, + ) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address().to_string(), + msg: to_binary(&ExecuteMsg::ProvideLiquidity { + lp_token_out: lp_token_out.to_string(), + minimum_receive, + recipient: None, + })?, + funds: coins_in.to_vec(), + })) + } + + pub fn withdraw_liquidity_msg(&self, lp_token: &Coin) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address().to_string(), + msg: to_binary(&ExecuteMsg::WithdrawLiquidity { recipient: None })?, + funds: vec![lp_token.clone()], + })) + } +} diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 5bdfc7cb5..b3f6cef89 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -38,7 +38,12 @@ pub enum Action { /// Repay coin of specified amount back to Red Bank Repay(Coin), /// Deposit coins into vault strategy - EnterVault { vault: VaultUnchecked, coin: Coin }, + /// If amount sent is None, Rover attempts to deposit the account's entire balance into the vault + EnterVault { + vault: VaultUnchecked, + denom: String, + amount: Option, + }, /// Withdraw underlying coins from vault ExitVault { vault: VaultUnchecked, @@ -83,6 +88,14 @@ pub enum Action { denom_out: String, slippage: Decimal, }, + /// Add Vec to liquidity pool in exchange for LP tokens + ProvideLiquidity { + coins_in: Vec, + lp_token_out: String, + minimum_receive: Uint128, + }, + /// Send LP token and withdraw corresponding reserve assets from pool + WithdrawLiquidity { lp_token: Coin }, } /// Internal actions made by the contract with pre-validated inputs @@ -108,7 +121,8 @@ pub enum CallbackMsg { EnterVault { account_id: String, vault: Vault, - coin: Coin, + denom: String, + amount: Option, }, /// Exchanges vault LP shares for assets ExitVault { @@ -164,12 +178,21 @@ pub enum CallbackMsg { slippage: Decimal, }, /// Used to update the coin balance of account after an async action - UpdateCoinBalances { + UpdateCoinBalance { /// Account that needs coin balance adjustment account_id: String, - /// Total balances for coins in Rover prior to withdraw - previous_balances: Vec, + /// Total balance for coin in Rover prior to withdraw + previous_balance: Coin, + }, + /// Add Vec to liquidity pool in exchange for LP tokens + ProvideLiquidity { + account_id: String, + coins_in: Vec, + lp_token_out: String, + minimum_receive: Uint128, }, + /// Send LP token and withdraw corresponding reserve assets from pool + WithdrawLiquidity { account_id: String, lp_token: Coin }, } impl CallbackMsg { diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index 623145651..fa4e3bb49 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -1,6 +1,7 @@ use crate::adapters::swap::SwapperUnchecked; use crate::adapters::vault::VaultConfig; use crate::adapters::vault::VaultUnchecked; +use crate::adapters::ZapperUnchecked; use crate::adapters::{OracleUnchecked, RedBankUnchecked}; use crate::traits::Stringify; use cosmwasm_schema::cw_serde; @@ -25,6 +26,8 @@ pub struct InstantiateMsg { pub max_close_factor: Decimal, /// Helper contract for making swaps pub swapper: SwapperUnchecked, + /// Helper contract for adding/removing liquidity + pub zapper: ZapperUnchecked, } #[cw_serde] @@ -64,4 +67,5 @@ pub struct ConfigUpdates { pub max_liquidation_bonus: Option, pub max_close_factor: Option, pub swapper: Option, + pub zapper: Option, } diff --git a/packages/rover/src/msg/mod.rs b/packages/rover/src/msg/mod.rs index 50cb8a531..38a6d2030 100644 --- a/packages/rover/src/msg/mod.rs +++ b/packages/rover/src/msg/mod.rs @@ -1,6 +1,7 @@ pub mod execute; pub mod instantiate; pub mod query; +pub mod zapper; pub use execute::ExecuteMsg; pub use instantiate::InstantiateMsg; diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index e267bb02a..f8f96aef6 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -65,6 +65,15 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, + /// Estimate how many LP tokens received in exchange for coins provided for liquidity + #[returns(Uint128)] + EstimateProvideLiquidity { + lp_token_out: String, + coins_in: Vec, + }, + /// Estimate coins withdrawn if exchanged for LP tokens + #[returns(Vec)] + EstimateWithdrawLiquidity { lp_token: Coin }, } #[cw_serde] @@ -150,6 +159,7 @@ pub struct ConfigResponse { pub max_liquidation_bonus: Decimal, pub max_close_factor: Decimal, pub swapper: String, + pub zapper: String, } #[cw_serde] diff --git a/packages/rover/src/msg/zapper.rs b/packages/rover/src/msg/zapper.rs new file mode 100644 index 000000000..7e3d418d7 --- /dev/null +++ b/packages/rover/src/msg/zapper.rs @@ -0,0 +1,41 @@ +// TODO: should be removed when liquidity-helper is finalized and published to crates.io + +use crate::adapters::OracleUnchecked; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Coin, Uint128}; + +#[cw_serde] +pub struct LpConfig { + pub lp_token_denom: String, + pub lp_pair_denoms: (String, String), +} + +#[cw_serde] +pub struct InstantiateMsg { + pub oracle: OracleUnchecked, + pub lp_configs: Vec, +} + +#[cw_serde] +pub enum ExecuteMsg { + ProvideLiquidity { + lp_token_out: String, + recipient: Option, + minimum_receive: Uint128, + }, + WithdrawLiquidity { + recipient: Option, + }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(Uint128)] + EstimateProvideLiquidity { + lp_token_out: String, + coins_in: Vec, + }, + #[returns(Vec)] + EstimateWithdrawLiquidity { coin_in: Coin }, +} diff --git a/schema.Makefile.toml b/schema.Makefile.toml index e8b2a972a..ab3ec7b9b 100644 --- a/schema.Makefile.toml +++ b/schema.Makefile.toml @@ -18,6 +18,7 @@ fn main() -> std::io::Result<()> { "mock-red-bank", "mock-vault", "mock-oracle", + "mock-zapper", ]; for contract in contracts { diff --git a/schemas/credit-manager/credit-manager.json b/schemas/credit-manager/credit-manager.json index f41bc89b0..4cf34c4a2 100644 --- a/schemas/credit-manager/credit-manager.json +++ b/schemas/credit-manager/credit-manager.json @@ -14,7 +14,8 @@ "oracle", "owner", "red_bank", - "swapper" + "swapper", + "zapper" ], "properties": { "allowed_coins": { @@ -74,6 +75,14 @@ "$ref": "#/definitions/SwapperBase_for_String" } ] + }, + "zapper": { + "description": "Helper contract for adding/removing liquidity", + "allOf": [ + { + "$ref": "#/definitions/ZapperBase_for_String" + } + ] } }, "additionalProperties": false, @@ -161,6 +170,9 @@ } }, "additionalProperties": false + }, + "ZapperBase_for_String": { + "type": "string" } } }, @@ -304,7 +316,7 @@ "additionalProperties": false }, { - "description": "Deposit coins into vault strategy", + "description": "Deposit coins into vault strategy If amount sent is None, Rover attempts to deposit the account's entire balance into the vault", "type": "object", "required": [ "enter_vault" @@ -313,12 +325,22 @@ "enter_vault": { "type": "object", "required": [ - "coin", + "denom", "vault" ], "properties": { - "coin": { - "$ref": "#/definitions/Coin" + "amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "denom": { + "type": "string" }, "vault": { "$ref": "#/definitions/VaultBase_for_String" @@ -505,6 +527,61 @@ } }, "additionalProperties": false + }, + { + "description": "Add Vec to liquidity pool in exchange for LP tokens", + "type": "object", + "required": [ + "provide_liquidity" + ], + "properties": { + "provide_liquidity": { + "type": "object", + "required": [ + "coins_in", + "lp_token_out", + "minimum_receive" + ], + "properties": { + "coins_in": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "lp_token_out": { + "type": "string" + }, + "minimum_receive": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Send LP token and withdraw corresponding reserve assets from pool", + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "required": [ + "lp_token" + ], + "properties": { + "lp_token": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -630,15 +707,25 @@ "type": "object", "required": [ "account_id", - "coin", + "denom", "vault" ], "properties": { "account_id": { "type": "string" }, - "coin": { - "$ref": "#/definitions/Coin" + "amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "denom": { + "type": "string" }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" @@ -912,26 +999,90 @@ "description": "Used to update the coin balance of account after an async action", "type": "object", "required": [ - "update_coin_balances" + "update_coin_balance" ], "properties": { - "update_coin_balances": { + "update_coin_balance": { "type": "object", "required": [ "account_id", - "previous_balances" + "previous_balance" ], "properties": { "account_id": { "description": "Account that needs coin balance adjustment", "type": "string" }, - "previous_balances": { - "description": "Total balances for coins in Rover prior to withdraw", + "previous_balance": { + "description": "Total balance for coin in Rover prior to withdraw", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Add Vec to liquidity pool in exchange for LP tokens", + "type": "object", + "required": [ + "provide_liquidity" + ], + "properties": { + "provide_liquidity": { + "type": "object", + "required": [ + "account_id", + "coins_in", + "lp_token_out", + "minimum_receive" + ], + "properties": { + "account_id": { + "type": "string" + }, + "coins_in": { "type": "array", "items": { "$ref": "#/definitions/Coin" } + }, + "lp_token_out": { + "type": "string" + }, + "minimum_receive": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Send LP token and withdraw corresponding reserve assets from pool", + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "required": [ + "account_id", + "lp_token" + ], + "properties": { + "account_id": { + "type": "string" + }, + "lp_token": { + "$ref": "#/definitions/Coin" } }, "additionalProperties": false @@ -1039,6 +1190,16 @@ "items": { "$ref": "#/definitions/VaultInstantiateConfig" } + }, + "zapper": { + "anyOf": [ + { + "$ref": "#/definitions/ZapperBase_for_String" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -1123,6 +1284,9 @@ } }, "additionalProperties": false + }, + "ZapperBase_for_String": { + "type": "string" } } }, @@ -1470,9 +1634,79 @@ } }, "additionalProperties": false + }, + { + "description": "Estimate how many LP tokens received in exchange for coins provided for liquidity", + "type": "object", + "required": [ + "estimate_provide_liquidity" + ], + "properties": { + "estimate_provide_liquidity": { + "type": "object", + "required": [ + "coins_in", + "lp_token_out" + ], + "properties": { + "coins_in": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "lp_token_out": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Estimate coins withdrawn if exchanged for LP tokens", + "type": "object", + "required": [ + "estimate_withdraw_liquidity" + ], + "properties": { + "estimate_withdraw_liquidity": { + "type": "object", + "required": [ + "lp_token" + ], + "properties": { + "lp_token": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, "VaultBase_for_String": { "type": "object", "required": [ @@ -1806,7 +2040,8 @@ "oracle", "owner", "red_bank", - "swapper" + "swapper", + "zapper" ], "properties": { "account_nft": { @@ -1832,6 +2067,9 @@ }, "swapper": { "type": "string" + }, + "zapper": { + "type": "string" } }, "additionalProperties": false, @@ -1842,6 +2080,41 @@ } } }, + "estimate_provide_liquidity": { + "$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" + }, + "estimate_withdraw_liquidity": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Coin", + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + }, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "health": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "HealthResponse", diff --git a/schemas/mock-vault/mock-vault.json b/schemas/mock-vault/mock-vault.json index 0e6775621..73c6afb4b 100644 --- a/schemas/mock-vault/mock-vault.json +++ b/schemas/mock-vault/mock-vault.json @@ -270,6 +270,38 @@ } }, "additionalProperties": false + }, + { + "description": "Update the whitelist of addresses that can call ForceRedeem and ForceWithdrawUnlocking.", + "type": "object", + "required": [ + "update_force_withdraw_whitelist" + ], + "properties": { + "update_force_withdraw_whitelist": { + "type": "object", + "required": [ + "add_addresses", + "remove_addresses" + ], + "properties": { + "add_addresses": { + "type": "array", + "items": { + "type": "string" + } + }, + "remove_addresses": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, diff --git a/schemas/mock-zapper/mock-zapper.json b/schemas/mock-zapper/mock-zapper.json new file mode 100644 index 000000000..98e93db7a --- /dev/null +++ b/schemas/mock-zapper/mock-zapper.json @@ -0,0 +1,236 @@ +{ + "contract_name": "mock-zapper", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "lp_configs", + "oracle" + ], + "properties": { + "lp_configs": { + "type": "array", + "items": { + "$ref": "#/definitions/LpConfig" + } + }, + "oracle": { + "$ref": "#/definitions/OracleBase_for_String" + } + }, + "additionalProperties": false, + "definitions": { + "LpConfig": { + "type": "object", + "required": [ + "lp_pair_denoms", + "lp_token_denom" + ], + "properties": { + "lp_pair_denoms": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "lp_token_denom": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OracleBase_for_String": { + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "provide_liquidity" + ], + "properties": { + "provide_liquidity": { + "type": "object", + "required": [ + "lp_token_out", + "minimum_receive" + ], + "properties": { + "lp_token_out": { + "type": "string" + }, + "minimum_receive": { + "$ref": "#/definitions/Uint128" + }, + "recipient": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "properties": { + "recipient": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "estimate_provide_liquidity" + ], + "properties": { + "estimate_provide_liquidity": { + "type": "object", + "required": [ + "coins_in", + "lp_token_out" + ], + "properties": { + "coins_in": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "lp_token_out": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "estimate_withdraw_liquidity" + ], + "properties": { + "estimate_withdraw_liquidity": { + "type": "object", + "required": [ + "coin_in" + ], + "properties": { + "coin_in": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "migrate": null, + "sudo": null, + "responses": { + "estimate_provide_liquidity": { + "$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" + }, + "estimate_withdraw_liquidity": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Coin", + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + }, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + } + } +} From 9023d9f532d1d07f76ee2af83871adf88a6014c3 Mon Sep 17 00:00:00 2001 From: piobab Date: Fri, 4 Nov 2022 16:44:58 +0100 Subject: [PATCH 074/218] Mp 1447 osmosis swap new ver (#31) * MP-1447. Migrate to osmosis-rust. * MP-1447. Tests - update to new osmosis-rust. * MP-1447. Fix tests. * MP-1447. More tests fixed. * Fix for denoms and price calc. * Use TWAP for estimate. * Use deps from github with more queries. * Fixes after review. * Use 1.18 go version. * Include chain package for ci/cd. * Bump go. * Fix clippy. * MP-1702. Ignore TWAP related tests. * Use metadata and env for osmosis-testing. * Update github ci/cd to use wasm files for testing. * Fix clippy. * Use wasm from cargo build outputs. * Remove env for tests. * Update imported package to workspace. * Use wasm files from cargo build. --- .github/workflows/main.yml | 29 +- Cargo.lock | 916 +++++++++++++++++- Cargo.toml | 5 +- contracts/swapper/base/src/contract.rs | 9 +- contracts/swapper/base/src/error.rs | 5 +- contracts/swapper/base/src/traits.rs | 12 +- contracts/swapper/osmosis/Cargo.toml | 8 +- contracts/swapper/osmosis/src/contract.rs | 15 +- contracts/swapper/osmosis/src/helpers.rs | 14 - contracts/swapper/osmosis/src/route.rs | 174 ++-- contracts/swapper/osmosis/tests/helpers.rs | 99 +- .../osmosis/tests/test_enumerate_routes.rs | 170 ++-- .../swapper/osmosis/tests/test_estimate.rs | 256 +++-- .../swapper/osmosis/tests/test_instantiate.rs | 57 +- .../swapper/osmosis/tests/test_set_route.rs | 549 ++++++----- contracts/swapper/osmosis/tests/test_swap.rs | 284 +++--- .../osmosis/tests/test_update_config.rs | 97 +- packages/chains/osmosis/Cargo.toml | 22 + packages/chains/osmosis/README.md | 2 + packages/chains/osmosis/src/helpers.rs | 85 ++ packages/chains/osmosis/src/lib.rs | 1 + 21 files changed, 1936 insertions(+), 873 deletions(-) create mode 100755 packages/chains/osmosis/Cargo.toml create mode 100644 packages/chains/osmosis/README.md create mode 100644 packages/chains/osmosis/src/helpers.rs create mode 100644 packages/chains/osmosis/src/lib.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 390d3edfa..c1f961c15 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,12 +2,21 @@ on: pull_request name: Main +env: + # Directory with wasm files used by `tests` + ARTIFACTS_DIR_PATH: 'target/wasm32-unknown-unknown/release' + jobs: check: name: Check runs-on: ubuntu-latest steps: + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: '>=1.18.0' + - name: Checkout sources uses: actions/checkout@v3 @@ -25,16 +34,21 @@ jobs: - name: Compile workspace run: cargo make build - - name: Generate schemas - run: cargo make generate-all-schemas - - name: Compile contracts to wasm run: cargo make rust-optimizer + - name: Generate schemas + run: cargo make generate-all-schemas + test: name: Test Suite runs-on: ubuntu-latest steps: + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: '>=1.18.0' + - name: Checkout sources uses: actions/checkout@v3 @@ -44,10 +58,14 @@ jobs: profile: minimal toolchain: stable override: true + target: wasm32-unknown-unknown - name: Install cargo make uses: davidB/rust-cargo-make@v1 + - name: Compile workspace + run: cargo make build + - name: Run tests run: cargo make test @@ -55,6 +73,11 @@ jobs: name: Lints runs-on: ubuntu-latest steps: + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: '>=1.18.0' + - name: Checkout sources uses: actions/checkout@v3 diff --git a/Cargo.lock b/Cargo.lock index a622f3630..e47b3eb06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,12 +26,49 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +[[package]] +name = "async-trait" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "base16ct" version = "0.1.1" @@ -50,6 +87,53 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" +[[package]] +name = "bindgen" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + +[[package]] +name = "bip32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30ed1d6f8437a487a266c8293aeb95b61a23261273e3e02912cdb8b68bf798b" +dependencies = [ + "bs58", + "hmac", + "k256", + "once_cell", + "pbkdf2", + "rand_core 0.6.4", + "ripemd", + "sha2 0.10.6", + "subtle", + "zeroize", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "block-buffer" version = "0.9.0" @@ -68,6 +152,21 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +dependencies = [ + "sha2 0.9.9", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + [[package]] name = "byteorder" version = "1.4.3" @@ -80,18 +179,83 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "clang-sys" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "3.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +dependencies = [ + "atty", + "bitflags", + "clap_lex", + "indexmap", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "const-oid" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" +[[package]] +name = "cosmos-sdk-proto" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b42021d8488665b1a0d9748f1f81df7235362d194f44481e2e61bf376b77b4" +dependencies = [ + "prost 0.11.0", + "prost-types", + "tendermint-proto", +] + [[package]] name = "cosmos-vault-standard" version = "0.1.0" @@ -104,6 +268,26 @@ dependencies = [ "serde", ] +[[package]] +name = "cosmrs" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3903590099dcf1ea580d9353034c9ba1dbf55d1389a5bd2ade98535c3445d1f9" +dependencies = [ + "bip32", + "cosmos-sdk-proto", + "ecdsa", + "eyre", + "getrandom", + "k256", + "rand_core 0.6.4", + "serde", + "serde_json", + "subtle-encoding", + "tendermint", + "thiserror", +] + [[package]] name = "cosmwasm-crypto" version = "1.1.5" @@ -280,7 +464,7 @@ dependencies = [ "cw-utils 0.13.4", "derivative", "itertools", - "prost", + "prost 0.9.0", "schemars", "serde", "thiserror", @@ -437,6 +621,27 @@ dependencies = [ "signature", ] +[[package]] +name = "ed25519" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "sha2 0.9.9", + "zeroize", +] + [[package]] name = "ed25519-zebra" version = "3.1.0" @@ -478,6 +683,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "env_logger" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "ff" version = "0.12.1" @@ -488,12 +716,83 @@ dependencies = [ "subtle", ] +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "eyre", + "paste", +] + [[package]] name = "forward_ref" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" +[[package]] +name = "futures" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -511,10 +810,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "group" version = "0.12.1" @@ -535,6 +842,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -550,6 +866,28 @@ dependencies = [ "digest 0.10.5", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "itertools" version = "0.10.5" @@ -565,6 +903,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "k256" version = "0.11.6" @@ -575,14 +922,52 @@ dependencies = [ "ecdsa", "elliptic-curve", "sha2 0.10.6", + "sha3", ] +[[package]] +name = "keccak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + [[package]] name = "mars-health" version = "1.0.0" @@ -608,6 +993,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "mars-osmosis" +version = "1.0.0" +dependencies = [ + "cosmwasm-std", + "osmosis-std 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", +] + [[package]] name = "mars-outpost" version = "1.0.0" @@ -617,6 +1011,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mock-oracle" version = "1.0.0" @@ -662,6 +1068,55 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.16.0" @@ -675,33 +1130,114 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "osmo-bindings" -version = "0.5.1" +name = "os_str_bytes" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29a49bf5df140714e63e3a13b6d6c7b7903620c4e3451476bb59cf50c1c7f88" +checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" + +[[package]] +name = "osmosis-std" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b3792977036dc49cfc9af9fd7a6c021fd48dfffc8ebf09324201506c65a47a" dependencies = [ + "chrono", "cosmwasm-std", + "osmosis-std-derive 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.11.0", + "prost-types", "schemars", "serde", + "serde-cw-value", ] [[package]] -name = "osmo-bindings-test" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f96719fd1b214bb8aecbe066bb9965052cae11df4f540978122ab243d9f0a15e" +name = "osmosis-std" +version = "0.12.0" +source = "git+https://github.com/osmosis-labs/osmosis-rust#28ffd12efce20ee36bb78505e5af136b26344b70" dependencies = [ - "anyhow", + "chrono", "cosmwasm-std", - "cw-multi-test", - "cw-storage-plus 0.13.4", - "itertools", - "osmo-bindings", + "osmosis-std-derive 0.12.0 (git+https://github.com/osmosis-labs/osmosis-rust)", + "prost 0.11.0", + "prost-types", "schemars", "serde", + "serde-cw-value", +] + +[[package]] +name = "osmosis-std-derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c501f2b8ff88b1c60ab671d7b808e947f384fa2524fe4ec8c06f63ef4be29979" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "osmosis-std-derive" +version = "0.12.0" +source = "git+https://github.com/osmosis-labs/osmosis-rust#28ffd12efce20ee36bb78505e5af136b26344b70" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "osmosis-testing" +version = "0.12.0" +source = "git+https://github.com/osmosis-labs/osmosis-rust#28ffd12efce20ee36bb78505e5af136b26344b70" +dependencies = [ + "base64", + "bindgen", + "cosmrs", + "cosmwasm-std", + "osmosis-std 0.12.0 (git+https://github.com/osmosis-labs/osmosis-rust)", + "prost 0.11.0", + "serde", + "serde_json", "thiserror", ] +[[package]] +name = "paste" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.5", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkcs8" version = "0.9.0" @@ -728,7 +1264,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" +dependencies = [ + "bytes", + "prost-derive 0.11.0", ] [[package]] @@ -744,6 +1290,29 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dfaa718ad76a44b3415e6c4d53b17c8f99160dcb3a99b10470fce8ad43f6e3e" +dependencies = [ + "bytes", + "prost 0.11.0", +] + [[package]] name = "quote" version = "1.0.21" @@ -768,6 +1337,23 @@ dependencies = [ "getrandom", ] +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + [[package]] name = "rfc6979" version = "0.3.1" @@ -779,6 +1365,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.5", +] + +[[package]] +name = "ripemd160" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "rover" version = "1.0.0" @@ -797,6 +1403,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "ryu" version = "1.0.11" @@ -856,6 +1468,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-cw-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75d32da6b8ed758b7d850b6c3c08f1d7df51a4df3cb201296e63e34a78e99d4" +dependencies = [ + "serde", +] + [[package]] name = "serde-json-wasm" version = "0.4.1" @@ -865,6 +1486,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_bytes" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.147" @@ -898,6 +1528,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha2" version = "0.9.9" @@ -922,6 +1563,22 @@ dependencies = [ "digest 0.10.5", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + [[package]] name = "signature" version = "1.6.4" @@ -948,12 +1605,27 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "subtle" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + [[package]] name = "swapper-base" version = "1.0.0" @@ -987,10 +1659,10 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test", "cw-storage-plus 0.16.0", - "osmo-bindings", - "osmo-bindings-test", + "mars-osmosis", + "osmosis-std 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "osmosis-testing", "rover", "schemars", "swapper-base", @@ -1008,6 +1680,82 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tendermint" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467f82178deeebcd357e1273a0c0b77b9a8a0313ef7c07074baebe99d87851f4" +dependencies = [ + "async-trait", + "bytes", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures", + "k256", + "num-traits", + "once_cell", + "prost 0.11.0", + "prost-types", + "ripemd160", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto", + "time", + "zeroize", +] + +[[package]] +name = "tendermint-proto" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ce80bf536476db81ecc9ebab834dc329c9c1509a694f211a73858814bfe023" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost 0.11.0", + "prost-types", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + [[package]] name = "thiserror" version = "1.0.37" @@ -1028,6 +1776,23 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + [[package]] name = "typenum" version = "1.15.0" @@ -1052,6 +1817,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "version_check" version = "0.9.4" @@ -1064,8 +1835,119 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "which" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "zeroize" version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/Cargo.toml b/Cargo.toml index d4cb19eda..c947dfee2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,10 @@ members = [ "contracts/credit-manager", "contracts/swapper/*", "contracts/mars-oracle-adapter", - "packages/*", + "packages/chains/*", + "packages/health", + "packages/outpost", + "packages/rover", # Mock contracts "contracts/mock-oracle", diff --git a/contracts/swapper/base/src/contract.rs b/contracts/swapper/base/src/contract.rs index 8a6b94d99..2e1e78752 100644 --- a/contracts/swapper/base/src/contract.rs +++ b/contracts/swapper/base/src/contract.rs @@ -166,7 +166,7 @@ where let route = self .routes .load(deps.storage, (coin_in.denom.clone(), denom_out))?; - route.estimate_exact_in_swap(deps, env, coin_in) + route.estimate_exact_in_swap(&deps.querier, &env, &coin_in) } fn swap_exact_in( @@ -181,12 +181,7 @@ where let swap_msg = self .routes .load(deps.storage, (coin_in.denom.clone(), denom_out.clone()))? - .build_exact_in_swap_msg( - &deps.querier, - env.contract.address.clone(), - &coin_in, - slippage, - )?; + .build_exact_in_swap_msg(&deps.querier, &env, &coin_in, slippage)?; // Check balance of result of swapper and send back result to sender let transfer_msg = CosmosMsg::Wasm(WasmMsg::Execute { diff --git a/contracts/swapper/base/src/error.rs b/contracts/swapper/base/src/error.rs index 99e7f22c8..bd12cc4e3 100644 --- a/contracts/swapper/base/src/error.rs +++ b/contracts/swapper/base/src/error.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{DecimalRangeExceeded, OverflowError, StdError}; +use cosmwasm_std::{CheckedMultiplyRatioError, DecimalRangeExceeded, OverflowError, StdError}; use rover::error::ContractError as RoverError; use thiserror::Error; @@ -13,6 +13,9 @@ pub enum ContractError { #[error("{0}")] Overflow(#[from] OverflowError), + #[error("{0}")] + CheckedMultiplyRatio(#[from] CheckedMultiplyRatioError), + #[error("{denom_a:?}-{denom_b:?} is not an available pool")] PoolNotFound { denom_a: String, denom_b: String }, diff --git a/contracts/swapper/base/src/traits.rs b/contracts/swapper/base/src/traits.rs index 4bc1ca0bb..936fc8f65 100644 --- a/contracts/swapper/base/src/traits.rs +++ b/contracts/swapper/base/src/traits.rs @@ -1,8 +1,6 @@ use std::fmt::{Debug, Display}; -use cosmwasm_std::{ - Addr, Coin, CosmosMsg, CustomMsg, CustomQuery, Decimal, Deps, Env, QuerierWrapper, -}; +use cosmwasm_std::{Coin, CosmosMsg, CustomMsg, CustomQuery, Decimal, Env, QuerierWrapper}; use schemars::JsonSchema; use serde::{de::DeserializeOwned, Serialize}; @@ -28,7 +26,7 @@ where fn build_exact_in_swap_msg( &self, querier: &QuerierWrapper, - contract_addr: Addr, + env: &Env, coin_in: &Coin, slippage: Decimal, ) -> ContractResult>; @@ -36,8 +34,8 @@ where /// Query to get the estimate result of a swap fn estimate_exact_in_swap( &self, - deps: Deps, - env: Env, - coin_in: Coin, + querier: &QuerierWrapper, + env: &Env, + coin_in: &Coin, ) -> ContractResult; } diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index 31aa1c794..eb9558fff 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -21,13 +21,13 @@ library = [] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } -osmo-bindings = "0.5" +mars-osmosis = { version = "1.0.0", path = "../../../packages/chains/osmosis" } +osmosis-std = { workspace = true } schemars = { workspace = true } swapper-base = { workspace = true } rover = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -anyhow = { workspace = true } -cw-multi-test = { workspace = true } -osmo-bindings-test = "0.5" +anyhow = { workspace = true } +osmosis-testing = { version = "0.12", git = "https://github.com/osmosis-labs/osmosis-rust" } diff --git a/contracts/swapper/osmosis/src/contract.rs b/contracts/swapper/osmosis/src/contract.rs index 1de0020e2..cba24a4f3 100644 --- a/contracts/swapper/osmosis/src/contract.rs +++ b/contracts/swapper/osmosis/src/contract.rs @@ -1,5 +1,4 @@ -use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response}; -use osmo_bindings::{OsmosisMsg, OsmosisQuery}; +use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response}; use rover::adapters::swap::{ExecuteMsg, InstantiateMsg, QueryMsg}; use swapper_base::{ContractResult, SwapBase}; @@ -7,29 +6,29 @@ use swapper_base::{ContractResult, SwapBase}; use crate::route::OsmosisRoute; /// The Osmosis swapper contract inherits logic from the base swapper contract -pub type OsmosisSwap<'a> = SwapBase<'a, OsmosisQuery, OsmosisMsg, OsmosisRoute>; +pub type OsmosisSwap<'a> = SwapBase<'a, Empty, Empty, OsmosisRoute>; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - deps: DepsMut, + deps: DepsMut, _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> ContractResult> { +) -> ContractResult { OsmosisSwap::default().instantiate(deps, msg) } #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> ContractResult> { +) -> ContractResult { OsmosisSwap::default().execute(deps, env, info, msg) } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { OsmosisSwap::default().query(deps, env, msg) } diff --git a/contracts/swapper/osmosis/src/helpers.rs b/contracts/swapper/osmosis/src/helpers.rs index 3f461bdb8..25ec7394b 100644 --- a/contracts/swapper/osmosis/src/helpers.rs +++ b/contracts/swapper/osmosis/src/helpers.rs @@ -1,5 +1,4 @@ use cosmwasm_std::{Decimal, Uint128}; -use osmo_bindings::SwapAmount; use std::collections::HashSet; use std::hash::Hash; @@ -17,16 +16,3 @@ impl IntoUint128 for Decimal { *self * Uint128::new(1) } } - -pub trait GetValue { - fn value(&self) -> Uint128; -} - -impl GetValue for SwapAmount { - fn value(&self) -> Uint128 { - match self { - Self::In(amount) => *amount, - Self::Out(amount) => *amount, - } - } -} diff --git a/contracts/swapper/osmosis/src/route.rs b/contracts/swapper/osmosis/src/route.rs index 851fb8934..e4b359f8f 100644 --- a/contracts/swapper/osmosis/src/route.rs +++ b/contracts/swapper/osmosis/src/route.rs @@ -1,47 +1,46 @@ use std::fmt; -use std::ops::Sub; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_binary, Addr, Coin, CosmosMsg, Decimal, Deps, Env, QuerierWrapper, QueryRequest, WasmQuery, + BlockInfo, Coin, CosmosMsg, Decimal, Empty, Env, Fraction, QuerierWrapper, Uint128, }; -use osmo_bindings::{ - EstimatePriceResponse as OsmoResponse, OsmosisMsg, OsmosisQuery, PoolStateResponse, Step, Swap, - SwapAmount, SwapAmountWithLimit, -}; -use rover::adapters::swap::{EstimateExactInSwapResponse, QueryMsg}; -use rover::traits::IntoDecimal; +use mars_osmosis::helpers::{has_denom, query_pool, query_twap_price}; +use osmosis_std::types::osmosis::gamm::v1beta1::{MsgSwapExactAmountIn, SwapAmountInRoute}; +use rover::adapters::swap::EstimateExactInSwapResponse; use swapper_base::{ContractError, ContractResult, Route}; -use crate::helpers::{hashset, GetValue, IntoUint128}; +use crate::helpers::hashset; + +/// 10 min in seconds (Risk Team recommendation) +const TWAP_WINDOW_SIZE_SECONDS: u64 = 600u64; #[cw_serde] -pub struct OsmosisRoute { - pub steps: Vec, -} +pub struct OsmosisRoute(pub Vec); impl fmt::Display for OsmosisRoute { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let s = self - .steps + .0 .iter() - .map(|step| format!("{}:{}", step.pool_id, step.denom_out)) + .map(|step| format!("{}:{}", step.pool_id, step.token_out_denom)) .collect::>() .join("|"); write!(f, "{}", s) } } -impl Route for OsmosisRoute { - // Perform basic validation of the swapper steps +impl Route for OsmosisRoute { + // Perform basic validation of the swap steps fn validate( &self, - querier: &QuerierWrapper, + querier: &QuerierWrapper, denom_in: &str, denom_out: &str, ) -> ContractResult<()> { + let steps = &self.0; + // there must be at least one step - if self.steps.is_empty() { + if steps.is_empty() { return Err(ContractError::InvalidRoute { reason: "the route must contain at least one step".to_string(), }); @@ -52,13 +51,10 @@ impl Route for OsmosisRoute { // - the output denom must not be the same as the input denom of a previous step (i.e. the route must not contain a loop) let mut prev_denom_out = denom_in; let mut seen_denoms = hashset(&[denom_in]); - for (i, step) in self.steps.iter().enumerate() { - let pool_state: PoolStateResponse = - querier.query(&QueryRequest::Custom(OsmosisQuery::PoolState { - id: step.pool_id, - }))?; + for (i, step) in steps.iter().enumerate() { + let pool = query_pool(querier, step.pool_id)?; - if !pool_state.has_denom(prev_denom_out) { + if !has_denom(prev_denom_out, &pool.pool_assets) { return Err(ContractError::InvalidRoute { reason: format!( "step {}: pool {} does not contain input denom {}", @@ -69,25 +65,28 @@ impl Route for OsmosisRoute { }); } - if !pool_state.has_denom(&step.denom_out) { + if !has_denom(&step.token_out_denom, &pool.pool_assets) { return Err(ContractError::InvalidRoute { reason: format!( "step {}: pool {} does not contain output denom {}", i + 1, step.pool_id, - &step.denom_out + &step.token_out_denom ), }); } - if seen_denoms.contains(step.denom_out.as_str()) { + if seen_denoms.contains(step.token_out_denom.as_str()) { return Err(ContractError::InvalidRoute { - reason: format!("route contains a loop: denom {} seen twice", step.denom_out), + reason: format!( + "route contains a loop: denom {} seen twice", + step.token_out_denom + ), }); } - prev_denom_out = &step.denom_out; - seen_denoms.insert(&step.denom_out); + prev_denom_out = &step.token_out_denom; + seen_denoms.insert(&step.token_out_denom); } // the route's final output denom must match the desired output denom @@ -106,63 +105,76 @@ impl Route for OsmosisRoute { /// Build a CosmosMsg that swaps given an input denom and amount fn build_exact_in_swap_msg( &self, - querier: &QuerierWrapper, - contract_addr: Addr, + querier: &QuerierWrapper, + env: &Env, coin_in: &Coin, slippage: Decimal, - ) -> ContractResult> { - let last_step = self.steps.last().unwrap(); // Safe as contract guarantees at least one step - let res: EstimateExactInSwapResponse = - querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: contract_addr.to_string(), - msg: to_binary(&QueryMsg::EstimateExactInSwap { - coin_in: coin_in.clone(), - denom_out: last_step.denom_out.clone(), - })?, - }))?; - - let swap_amount_with_slippage = SwapAmountWithLimit::ExactIn { - input: coin_in.amount, - min_output: Decimal::one() - .sub(slippage) - .checked_mul(res.amount.to_dec()?)? - .uint128(), - }; - - let first_swap = self - .steps - .first() - .map(|step| Swap::new(step.pool_id, coin_in.denom.clone(), &step.denom_out)) - .unwrap(); // Safe as contract guarantees at least one step - - Ok(CosmosMsg::Custom(OsmosisMsg::Swap { - first: first_swap, - route: self.steps[1..].to_vec(), - amount: swap_amount_with_slippage, - })) + ) -> ContractResult { + let steps = &self.0; + + steps.first().ok_or(ContractError::InvalidRoute { + reason: "the route must contain at least one step".to_string(), + })?; + + let out_amount = query_out_amount(querier, &env.block, coin_in, steps)?; + let min_out_amount = (Decimal::one() - slippage) * out_amount; + + let swap_msg: CosmosMsg = MsgSwapExactAmountIn { + sender: env.contract.address.to_string(), + routes: steps.to_vec(), + token_in: Some(osmosis_std::types::cosmos::base::v1beta1::Coin { + denom: coin_in.denom.clone(), + amount: coin_in.amount.to_string(), + }), + token_out_min_amount: min_out_amount.to_string(), + } + .into(); + Ok(swap_msg) } fn estimate_exact_in_swap( &self, - deps: Deps, - env: Env, - coin_in: Coin, + querier: &QuerierWrapper, + env: &Env, + coin_in: &Coin, ) -> ContractResult { - let first_step = self.steps.first().unwrap(); // Safe as contract guarantees at least one step - let query = OsmosisQuery::EstimateSwap { - sender: env.contract.address.to_string(), - first: Swap { - pool_id: first_step.pool_id, - denom_in: coin_in.denom, - denom_out: first_step.denom_out.clone(), - }, - route: self.steps[1..].to_vec(), - amount: SwapAmount::In(coin_in.amount), - }; - - let res: OsmoResponse = deps.querier.query(&QueryRequest::Custom(query))?; - Ok(EstimateExactInSwapResponse { - amount: res.amount.value(), - }) + let out_amount = query_out_amount(querier, &env.block, coin_in, &self.0)?; + Ok(EstimateExactInSwapResponse { amount: out_amount }) } } + +/// Query how much amount of denom_out we get for denom_in. +/// +/// Example calculation: +/// If we want to swap atom to usdc and configured routes are [pool_1 (atom/osmo), pool_69 (osmo/usdc)] (no direct pool of atom/usdc): +/// 1) query pool_1 to get price for atom/osmo +/// 2) query pool_69 to get price for osmo/usdc +/// 3) atom/usdc = (price for atom/osmo) * (price for osmo/usdc) +/// 4) usdc_out_amount = (atom amount) * (price for atom/usdc) +fn query_out_amount( + querier: &QuerierWrapper, + block: &BlockInfo, + coin_in: &Coin, + steps: &[SwapAmountInRoute], +) -> ContractResult { + let start_time = block.time.seconds() - TWAP_WINDOW_SIZE_SECONDS; + + let mut price = Decimal::one(); + let mut denom_in = coin_in.denom.clone(); + for step in steps { + let step_price = query_twap_price( + querier, + step.pool_id, + &denom_in, + &step.token_out_denom, + start_time, + )?; + price = price.checked_mul(step_price)?; + denom_in = step.token_out_denom.clone(); + } + + let out_amount = coin_in + .amount + .checked_multiply_ratio(price.numerator(), price.denominator())?; + Ok(out_amount) +} diff --git a/contracts/swapper/osmosis/tests/helpers.rs b/contracts/swapper/osmosis/tests/helpers.rs index 4ddd79037..26d372c08 100644 --- a/contracts/swapper/osmosis/tests/helpers.rs +++ b/contracts/swapper/osmosis/tests/helpers.rs @@ -1,65 +1,62 @@ -use std::fmt::Debug; +use std::fmt::Display; +use std::str::FromStr; -use anyhow::Result as AnyResult; -use cosmwasm_std::Addr; -use cosmwasm_std::CustomQuery; -use cw_multi_test::{AppResponse, Executor}; -use cw_multi_test::{Contract, ContractWrapper}; -use osmo_bindings::{OsmosisMsg, OsmosisQuery}; -use osmo_bindings_test::OsmosisApp; -use schemars::JsonSchema; +use osmosis_testing::cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest; +use osmosis_testing::{Account, Bank, OsmosisTestApp, RunnerError, SigningAccount, Wasm}; -use rover::adapters::swap::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}; -use swapper_base::ContractError; -use swapper_osmosis::contract::{execute, instantiate, query}; -use swapper_osmosis::route::OsmosisRoute; +use rover::adapters::swap::InstantiateMsg; -pub fn mock_osmosis_app() -> OsmosisApp { - OsmosisApp::default() -} +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -pub fn mock_osmosis_contract() -> Box> -where - C: Clone + Debug + PartialEq + JsonSchema, - Q: CustomQuery, - ContractWrapper< - ExecuteMsg, - Config, - QueryMsg, - ContractError, - ContractError, - ContractError, - OsmosisMsg, - OsmosisQuery, - >: Contract, -{ - let contract = ContractWrapper::new(execute, instantiate, query); //.with_reply(reply); - Box::new(contract) +pub fn wasm_file() -> String { + let artifacts_dir = + std::env::var("ARTIFACTS_DIR_PATH").unwrap_or_else(|_| "artifacts".to_string()); + let snaked_name = CONTRACT_NAME.replace('-', "_"); + format!("../../../{}/{}.wasm", artifacts_dir, snaked_name) } -pub fn assert_err(res: AnyResult, err: ContractError) { - match res { - Ok(_) => panic!("Result was not an error"), - Err(generic_err) => { - let contract_err: ContractError = generic_err.downcast().unwrap(); - assert_eq!(contract_err, err); - } - } -} +pub fn instantiate_contract(wasm: &Wasm, owner: &SigningAccount) -> String { + let wasm_byte_code = std::fs::read(wasm_file()).unwrap(); + let code_id = wasm + .store_code(&wasm_byte_code, None, owner) + .unwrap() + .data + .code_id; -pub fn instantiate_contract(app: &mut OsmosisApp) -> Addr { - let owner = Addr::unchecked("owner"); - let contract = mock_osmosis_contract(); - let code_id = app.store_code(contract); - app.instantiate_contract( + wasm.instantiate( code_id, - owner.clone(), &InstantiateMsg { - owner: owner.to_string(), + owner: owner.address(), }, - &[], - "mock-swapper-osmosis-contract", None, + Some("swapper-osmosis-contract"), + &[], + owner, ) .unwrap() + .data + .address +} + +pub fn query_balance(bank: &Bank, addr: &str, denom: &str) -> u128 { + bank.query_balance(&QueryBalanceRequest { + address: addr.to_string(), + denom: denom.to_string(), + }) + .unwrap() + .balance + .map(|c| u128::from_str(&c.amount).unwrap()) + .unwrap_or(0) +} + +pub fn assert_err(actual: RunnerError, expected: impl Display) { + match actual { + RunnerError::ExecuteError { msg } => { + assert!(msg.contains(&format!("{}", expected))) + } + RunnerError::QueryError { msg } => { + assert!(msg.contains(&format!("{}", expected))) + } + _ => panic!("Unhandled error"), + } } diff --git a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs index 2063da17e..986612cd9 100644 --- a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs +++ b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs @@ -1,8 +1,7 @@ -use crate::helpers::{instantiate_contract, mock_osmosis_app}; -use cosmwasm_std::{coin, Addr}; -use cw_multi_test::Executor; -use osmo_bindings::Step; -use osmo_bindings_test::Pool; +use crate::helpers::instantiate_contract; +use cosmwasm_std::coin; +use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; +use osmosis_testing::{Gamm, Module, OsmosisTestApp, SigningAccount, Wasm}; use rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; use std::collections::HashMap; use swapper_osmosis::route::OsmosisRoute; @@ -11,66 +10,54 @@ pub mod helpers; #[test] fn test_enumerating_routes() { - let owner = Addr::unchecked("owner"); - let mut app = mock_osmosis_app(); - let contract_addr = instantiate_contract(&mut app); - - let coin_a = coin(6_000_000, "uatom"); - let coin_b = coin(1_500_000, "uosmo"); - let pool_id_x = 1; - let pool_x = Pool::new(coin_a, coin_b); - - let coin_c = coin(100_000, "uosmo"); - let coin_d = coin(1_000_000, "umars"); - let pool_id_y = 420; - let pool_y = Pool::new(coin_c, coin_d); - - let coin_e = coin(100_000, "uosmo"); - let coin_f = coin(1_000_000, "uusdc"); - let pool_id_z = 69; - let pool_z = Pool::new(coin_e, coin_f); - - app.init_modules(|router, _, storage| { - router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); - router.custom.set_pool(storage, pool_id_y, &pool_y).unwrap(); - router.custom.set_pool(storage, pool_id_z, &pool_z).unwrap(); - }); - - let routes = mock_routes(); - - app.execute_contract( - owner.clone(), - contract_addr.clone(), + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "uosmo"), + coin(1_000_000_000_000, "umars"), + coin(1_000_000_000_000, "uusdc"), + ]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let routes = create_pools_and_routes(&app, &signer); + + wasm.execute( + &contract_addr, &ExecuteMsg::SetRoute { denom_in: "uatom".to_string(), denom_out: "umars".to_string(), route: routes.get(&("uatom", "umars")).unwrap().clone(), }, &[], + &signer, ) .unwrap(); - app.execute_contract( - owner.clone(), - contract_addr.clone(), + wasm.execute( + &contract_addr, &ExecuteMsg::SetRoute { denom_in: "uatom".to_string(), denom_out: "uusdc".to_string(), route: routes.get(&("uatom", "uusdc")).unwrap().clone(), }, &[], + &signer, ) .unwrap(); - app.execute_contract( - owner, - contract_addr.clone(), + wasm.execute( + &contract_addr, &ExecuteMsg::SetRoute { denom_in: "uosmo".to_string(), denom_out: "umars".to_string(), route: routes.get(&("uosmo", "umars")).unwrap().clone(), }, &[], + &signer, ) .unwrap(); @@ -93,10 +80,9 @@ fn test_enumerating_routes() { }, ]; - let res: Vec> = app - .wrap() - .query_wasm_smart( - contract_addr.to_string(), + let res: Vec> = wasm + .query( + &contract_addr, &QueryMsg::Routes { start_after: None, limit: None, @@ -105,10 +91,9 @@ fn test_enumerating_routes() { .unwrap(); assert_eq!(res, expected); - let res: Vec> = app - .wrap() - .query_wasm_smart( - contract_addr.to_string(), + let res: Vec> = wasm + .query( + &contract_addr, &QueryMsg::Routes { start_after: None, limit: Some(1), @@ -117,10 +102,9 @@ fn test_enumerating_routes() { .unwrap(); assert_eq!(res, expected[..1]); - let res: Vec> = app - .wrap() - .query_wasm_smart( - contract_addr.to_string(), + let res: Vec> = wasm + .query( + &contract_addr, &QueryMsg::Routes { start_after: Some(("uatom".to_string(), "uosmo".to_string())), limit: None, @@ -130,52 +114,70 @@ fn test_enumerating_routes() { assert_eq!(res, expected[1..]); } -fn mock_routes() -> HashMap<(&'static str, &'static str), OsmosisRoute> { +fn create_pools_and_routes( + app: &OsmosisTestApp, + signer: &SigningAccount, +) -> HashMap<(&'static str, &'static str), OsmosisRoute> { + let gamm = Gamm::new(app); + + let pool_atom_osmo = gamm + .create_basic_pool( + &[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], + signer, + ) + .unwrap() + .data + .pool_id; + let pool_osmo_mars = gamm + .create_basic_pool(&[coin(100_000, "uosmo"), coin(1_000_000, "umars")], signer) + .unwrap() + .data + .pool_id; + let pool_osmo_usdc = gamm + .create_basic_pool(&[coin(100_000, "uosmo"), coin(1_000_000, "uusdc")], signer) + .unwrap() + .data + .pool_id; + let mut map = HashMap::new(); // uosmo -> umars map.insert( ("uosmo", "umars"), - OsmosisRoute { - steps: vec![Step { - pool_id: 420, - denom_out: "umars".to_string(), - }], - }, + OsmosisRoute(vec![SwapAmountInRoute { + pool_id: pool_osmo_mars, + token_out_denom: "umars".to_string(), + }]), ); // uatom -> uosmo -> umars map.insert( ("uatom", "umars"), - OsmosisRoute { - steps: vec![ - Step { - pool_id: 1, - denom_out: "uosmo".to_string(), - }, - Step { - pool_id: 420, - denom_out: "umars".to_string(), - }, - ], - }, + OsmosisRoute(vec![ + SwapAmountInRoute { + pool_id: pool_atom_osmo, + token_out_denom: "uosmo".to_string(), + }, + SwapAmountInRoute { + pool_id: pool_osmo_mars, + token_out_denom: "umars".to_string(), + }, + ]), ); // uatom -> uosmo -> uusdc map.insert( ("uatom", "uusdc"), - OsmosisRoute { - steps: vec![ - Step { - pool_id: 1, - denom_out: "uosmo".to_string(), - }, - Step { - pool_id: 69, - denom_out: "uusdc".to_string(), - }, - ], - }, + OsmosisRoute(vec![ + SwapAmountInRoute { + pool_id: pool_atom_osmo, + token_out_denom: "uosmo".to_string(), + }, + SwapAmountInRoute { + pool_id: pool_osmo_usdc, + token_out_denom: "uusdc".to_string(), + }, + ]), ); map diff --git a/contracts/swapper/osmosis/tests/test_estimate.rs b/contracts/swapper/osmosis/tests/test_estimate.rs index 08511c86f..6ec000627 100644 --- a/contracts/swapper/osmosis/tests/test_estimate.rs +++ b/contracts/swapper/osmosis/tests/test_estimate.rs @@ -1,187 +1,185 @@ -use cosmwasm_std::{coin, Addr, StdError, StdResult, Uint128}; -use cw_multi_test::Executor; -use osmo_bindings::Step; -use osmo_bindings_test::{Pool as OsmoPool, Pool}; +use cosmwasm_std::{coin, Uint128}; +use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; +use osmosis_testing::{Gamm, Module, OsmosisTestApp, RunnerResult, Wasm}; use rover::adapters::swap::{EstimateExactInSwapResponse, ExecuteMsg, QueryMsg}; use swapper_osmosis::route::OsmosisRoute; -use crate::helpers::instantiate_contract; -use crate::helpers::mock_osmosis_app; +use crate::helpers::{assert_err, instantiate_contract}; pub mod helpers; #[test] fn test_error_on_route_not_found() { - let mut app = mock_osmosis_app(); - let contract_addr = instantiate_contract(&mut app); - let res: StdResult = app.wrap().query_wasm_smart( - contract_addr, + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + let owner = app + .init_account(&[coin(1_000_000_000_000, "uosmo")]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &owner); + + let res: RunnerResult = wasm.query( + &contract_addr, &QueryMsg::EstimateExactInSwap { coin_in: coin(1000, "jake"), denom_out: "mars".to_string(), }, ); - match res { - Ok(_) => panic!("should have thrown an error"), - Err(err) => assert_eq!( - err, - StdError::generic_err( - "Querier contract error: swapper_osmosis::route::OsmosisRoute not found" - ) - ), - } + assert_err( + res.unwrap_err(), + "swapper_osmosis::route::OsmosisRoute not found", + ); } #[test] +#[ignore] // FIXME: TWAP doesn't work on osmosis-testing - fix in progress fn test_estimate_swap_one_step() { - let mut app = mock_osmosis_app(); - - let coin_a = coin(6_000_000, "osmo"); - let coin_b = coin(1_500_000, "atom"); - let pool_id = 43; - let pool = OsmoPool::new(coin_a.clone(), coin_b.clone()); + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); - app.init_modules(|router, _, storage| { - router.custom.set_pool(storage, pool_id, &pool).unwrap(); - }); + let contract_addr = instantiate_contract(&wasm, &signer); - let owner = Addr::unchecked("owner"); - let contract_addr = instantiate_contract(&mut app); + let gamm = Gamm::new(&app); + let pool_atom_osmo = gamm + .create_basic_pool( + &[coin(1_500_000, "uatom"), coin(6_000_000, "uosmo")], + &signer, + ) + .unwrap() + .data + .pool_id; - app.execute_contract( - owner, - contract_addr.clone(), + wasm.execute( + &contract_addr, &ExecuteMsg::SetRoute { - denom_in: "osmo".to_string(), - denom_out: "atom".to_string(), - route: OsmosisRoute { - steps: vec![Step { - pool_id, - denom_out: "atom".to_string(), - }], - }, + denom_in: "uosmo".to_string(), + denom_out: "uatom".to_string(), + route: OsmosisRoute(vec![SwapAmountInRoute { + pool_id: pool_atom_osmo, + token_out_denom: "uatom".to_string(), + }]), }, &[], + &signer, ) .unwrap(); - let res: EstimateExactInSwapResponse = app - .wrap() - .query_wasm_smart( - contract_addr, + let res: EstimateExactInSwapResponse = wasm + .query( + &contract_addr, &QueryMsg::EstimateExactInSwap { - coin_in: coin(1000, coin_a.denom), - denom_out: coin_b.denom, + coin_in: coin(1000, "uosmo"), + denom_out: "uatom".to_string(), }, ) .unwrap(); - assert_eq!(res.amount, Uint128::new(250)); } #[test] +#[ignore] // FIXME: TWAP doesn't work on osmosis-testing - fix in progress fn test_estimate_swap_multi_step() { - let mut app = mock_osmosis_app(); - - let coin_a = coin(6_000_000, "uatom"); - let coin_b = coin(1_500_000, "uosmo"); - let pool_id_x = 1; - let pool_x = Pool::new(coin_a.clone(), coin_b); - - let coin_c = coin(100_000, "uosmo"); - let coin_d = coin(1_000_000, "umars"); - let pool_id_y = 420; - let pool_y = Pool::new(coin_c, coin_d); - - let coin_e = coin(100_000, "uosmo"); - let coin_f = coin(1_000_000, "uusdc"); - let pool_id_z = 69; - let pool_z = Pool::new(coin_e, coin_f.clone()); - - app.init_modules(|router, _, storage| { - router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); - router.custom.set_pool(storage, pool_id_y, &pool_y).unwrap(); - router.custom.set_pool(storage, pool_id_z, &pool_z).unwrap(); - }); - - let owner = Addr::unchecked("owner"); - let contract_addr = instantiate_contract(&mut app); - - app.execute_contract( - owner.clone(), - contract_addr.clone(), + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "uosmo"), + coin(1_000_000_000_000, "umars"), + coin(1_000_000_000_000, "uusdc"), + ]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let gamm = Gamm::new(&app); + let pool_atom_osmo = gamm + .create_basic_pool( + &[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], + &signer, + ) + .unwrap() + .data + .pool_id; + let pool_osmo_mars = gamm + .create_basic_pool(&[coin(100_000, "uosmo"), coin(1_000_000, "umars")], &signer) + .unwrap() + .data + .pool_id; + let pool_osmo_usdc = gamm + .create_basic_pool(&[coin(100_000, "uosmo"), coin(1_000_000, "uusdc")], &signer) + .unwrap() + .data + .pool_id; + + wasm.execute( + &contract_addr, &ExecuteMsg::SetRoute { denom_in: "uatom".to_string(), denom_out: "umars".to_string(), - route: OsmosisRoute { - steps: vec![ - Step { - pool_id: 1, - denom_out: "uosmo".to_string(), - }, - Step { - pool_id: 420, - denom_out: "umars".to_string(), - }, - ], - }, + route: OsmosisRoute(vec![ + SwapAmountInRoute { + pool_id: pool_atom_osmo, + token_out_denom: "uosmo".to_string(), + }, + SwapAmountInRoute { + pool_id: pool_osmo_mars, + token_out_denom: "umars".to_string(), + }, + ]), }, &[], + &signer, ) .unwrap(); - app.execute_contract( - owner.clone(), - contract_addr.clone(), + wasm.execute( + &contract_addr, &ExecuteMsg::SetRoute { denom_in: "uatom".to_string(), denom_out: "uusdc".to_string(), - route: OsmosisRoute { - steps: vec![ - Step { - pool_id: 1, - denom_out: "uosmo".to_string(), - }, - Step { - pool_id: 69, - denom_out: "uusdc".to_string(), - }, - ], - }, + route: OsmosisRoute(vec![ + SwapAmountInRoute { + pool_id: pool_atom_osmo, + token_out_denom: "uosmo".to_string(), + }, + SwapAmountInRoute { + pool_id: pool_osmo_usdc, + token_out_denom: "uusdc".to_string(), + }, + ]), }, &[], + &signer, ) .unwrap(); - app.execute_contract( - owner, - contract_addr.clone(), - &ExecuteMsg::SetRoute { - denom_in: "uosmo".to_string(), - denom_out: "umars".to_string(), - route: OsmosisRoute { - steps: vec![Step { - pool_id: 420, - denom_out: "umars".to_string(), - }], - }, - }, - &[], - ) - .unwrap(); - - let res: EstimateExactInSwapResponse = app - .wrap() - .query_wasm_smart( - contract_addr, + // atom/usdc = (price for atom/osmo) * (price for osmo/usdc) + // usdc_out_amount = (atom amount) * (price for atom/usdc) + // + // 1 osmo = 4 atom => atom/osmo = 0.25 + // 1 osmo = 10 usdc => osmo/usdc = 10 + // + // atom/usdc = 0.25 * 10 = 2.5 + // usdc_out_amount = 1000 * 2.5 = 2500 + let res: EstimateExactInSwapResponse = wasm + .query( + &contract_addr, &QueryMsg::EstimateExactInSwap { - coin_in: coin(1000, coin_a.denom), - denom_out: coin_f.denom, + coin_in: coin(1000, "uatom"), + denom_out: "uusdc".to_string(), }, ) .unwrap(); - - assert_eq!(res.amount, Uint128::new(2484)); + assert_eq!(res.amount, Uint128::new(2500)); } diff --git a/contracts/swapper/osmosis/tests/test_instantiate.rs b/contracts/swapper/osmosis/tests/test_instantiate.rs index ad486186b..573dbfb66 100644 --- a/contracts/swapper/osmosis/tests/test_instantiate.rs +++ b/contracts/swapper/osmosis/tests/test_instantiate.rs @@ -1,54 +1,51 @@ -use cosmwasm_std::Addr; -use cw_multi_test::Executor; +use cosmwasm_std::coin; +use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; use rover::adapters::swap::{Config, InstantiateMsg, QueryMsg}; -use crate::helpers::{mock_osmosis_app, mock_osmosis_contract}; +use crate::helpers::{instantiate_contract, wasm_file}; pub mod helpers; #[test] fn test_owner_set_on_instantiate() { - let owner = Addr::unchecked("owner"); - let mut app = mock_osmosis_app(); - let contract = mock_osmosis_contract(); - let code_id = app.store_code(contract); - let contract_addr = app - .instantiate_contract( - code_id, - owner.clone(), - &InstantiateMsg { - owner: owner.to_string(), - }, - &[], - "mock-swapper-contract", - None, - ) + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + let signer = app + .init_account(&[coin(1_000_000_000_000, "uosmo")]) .unwrap(); - let config: Config = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); + let contract_addr = instantiate_contract(&wasm, &signer); - assert_eq!(config.owner, owner); + let config: Config = wasm.query(&contract_addr, &QueryMsg::Config {}).unwrap(); + assert_eq!(config.owner, signer.address()); } #[test] fn test_raises_on_invalid_owner_addr() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + let signer = app + .init_account(&[coin(1_000_000_000_000, "uosmo")]) + .unwrap(); + + let wasm_byte_code = std::fs::read(wasm_file()).unwrap(); + let code_id = wasm + .store_code(&wasm_byte_code, None, &signer) + .unwrap() + .data + .code_id; + let owner = "%%%INVALID%%%"; - let mut app = mock_osmosis_app(); - let contract = mock_osmosis_contract(); - let code_id = app.store_code(contract); - let res = app.instantiate_contract( + let res = wasm.instantiate( code_id, - Addr::unchecked(owner), &InstantiateMsg { owner: owner.to_string(), }, - &[], - "mock-swapper-contract", None, + Some("swapper-osmosis-contract"), + &[], + &signer, ); if res.is_ok() { diff --git a/contracts/swapper/osmosis/tests/test_set_route.rs b/contracts/swapper/osmosis/tests/test_set_route.rs index 0a5b902c5..096616d68 100644 --- a/contracts/swapper/osmosis/tests/test_set_route.rs +++ b/contracts/swapper/osmosis/tests/test_set_route.rs @@ -1,51 +1,56 @@ +use cosmwasm_std::coin; use cosmwasm_std::StdError::GenericErr; -use cosmwasm_std::{coin, Addr}; -use cw_multi_test::Executor; -use osmo_bindings::Step; -use osmo_bindings_test::Pool; +use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; +use osmosis_testing::{Account, Gamm, Module, OsmosisTestApp, Wasm}; use rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; use rover::error::ContractError as RoverError; use swapper_base::ContractError; use swapper_osmosis::route::OsmosisRoute; -use crate::helpers::mock_osmosis_app; use crate::helpers::{assert_err, instantiate_contract}; pub mod helpers; #[test] fn test_only_owner_can_set_routes() { - let mut app = mock_osmosis_app(); - let contract_addr = instantiate_contract(&mut app); + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); - let bad_guy = Addr::unchecked("bad_guy"); - let res = app.execute_contract( - bad_guy.clone(), - contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "mars".to_string(), - denom_out: "weth".to_string(), - route: OsmosisRoute { - steps: vec![ - Step { + let accs = app + .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) + .unwrap(); + let owner = &accs[0]; + let bad_guy = &accs[1]; + + let contract_addr = instantiate_contract(&wasm, owner); + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "mars".to_string(), + denom_out: "weth".to_string(), + route: OsmosisRoute(vec![ + SwapAmountInRoute { pool_id: 1, - denom_out: "osmo".to_string(), + token_out_denom: "osmo".to_string(), }, - Step { + SwapAmountInRoute { pool_id: 2, - denom_out: "weth".to_string(), + token_out_denom: "weth".to_string(), }, - ], + ]), }, - }, - &[], - ); + &[], + bad_guy, + ) + .unwrap_err(); assert_err( - res, + res_err, ContractError::Rover(RoverError::Unauthorized { - user: bad_guy.to_string(), + user: bad_guy.address(), action: "set route".to_string(), }), ); @@ -53,23 +58,30 @@ fn test_only_owner_can_set_routes() { #[test] fn test_must_pass_at_least_one_step() { - let owner = Addr::unchecked("owner"); - let mut app = mock_osmosis_app(); - let contract_addr = instantiate_contract(&mut app); + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); - let res = app.execute_contract( - owner, - contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "mars".to_string(), - denom_out: "weth".to_string(), - route: OsmosisRoute { steps: vec![] }, - }, - &[], - ); + let signer = app + .init_account(&[coin(1_000_000_000_000, "uosmo")]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "mars".to_string(), + denom_out: "weth".to_string(), + route: OsmosisRoute(vec![]), + }, + &[], + &signer, + ) + .unwrap_err(); assert_err( - res, + res_err, ContractError::InvalidRoute { reason: "the route must contain at least one step".to_string(), }, @@ -78,219 +90,267 @@ fn test_must_pass_at_least_one_step() { #[test] fn test_must_be_available_in_osmosis() { - let owner = Addr::unchecked("owner"); - let mut app = mock_osmosis_app(); - let contract_addr = instantiate_contract(&mut app); + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); - let res = app.execute_contract( - owner, - contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "mars".to_string(), - denom_out: "weth".to_string(), - route: OsmosisRoute { - steps: vec![Step { + let signer = app + .init_account(&[coin(1_000_000_000_000, "uosmo")]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "mars".to_string(), + denom_out: "weth".to_string(), + route: OsmosisRoute(vec![SwapAmountInRoute { pool_id: 1, - denom_out: "osmo".to_string(), - }], + token_out_denom: "osmo".to_string(), + }]), }, - }, - &[], - ); + &[], + &signer, + ) + .unwrap_err(); assert_err( - res, + res_err, ContractError::Std(GenericErr { - msg: "Querier contract error: osmo_bindings_test::multitest::Pool not found" - .to_string(), + msg: "Querier contract error".to_string(), }), ); } #[test] fn test_step_does_not_contain_input_denom() { - let owner = Addr::unchecked("owner"); - let mut app = mock_osmosis_app(); - - let coin_a = coin(6_000_000, "atom"); - let coin_b = coin(1_500_000, "osmo"); - let pool_id_x = 43; - let pool_x = Pool::new(coin_a, coin_b); - - app.init_modules(|router, _, storage| { - router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); - }); + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); - let contract_addr = instantiate_contract(&mut app); + let contract_addr = instantiate_contract(&wasm, &signer); - let res = app.execute_contract( - owner, - contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "mars".to_string(), - denom_out: "weth".to_string(), - route: OsmosisRoute { - steps: vec![Step { - pool_id: pool_id_x, - denom_out: "osmo".to_string(), - }], + let gamm = Gamm::new(&app); + let pool_atom_osmo = gamm + .create_basic_pool( + &[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], + &signer, + ) + .unwrap() + .data + .pool_id; + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "umars".to_string(), + denom_out: "uweth".to_string(), + route: OsmosisRoute(vec![SwapAmountInRoute { + pool_id: pool_atom_osmo, + token_out_denom: "uosmo".to_string(), + }]), }, - }, - &[], - ); + &[], + &signer, + ) + .unwrap_err(); assert_err( - res, + res_err, ContractError::InvalidRoute { - reason: "step 1: pool 43 does not contain input denom mars".to_string(), + reason: format!( + "step 1: pool {} does not contain input denom umars", + pool_atom_osmo + ), }, ); } #[test] fn test_step_does_not_contain_output_denom() { - let owner = Addr::unchecked("owner"); - let mut app = mock_osmosis_app(); - - let coin_a = coin(6_000_000, "mars"); - let coin_b = coin(1_500_000, "osmo"); - let pool_id_x = 43; - let pool_x = Pool::new(coin_a, coin_b); - - app.init_modules(|router, _, storage| { - router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); - }); + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "umars"), + coin(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); - let contract_addr = instantiate_contract(&mut app); + let contract_addr = instantiate_contract(&wasm, &signer); - let res = app.execute_contract( - owner, - contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "mars".to_string(), - denom_out: "weth".to_string(), - route: OsmosisRoute { - steps: vec![Step { - pool_id: pool_id_x, - denom_out: "weth".to_string(), - }], + let gamm = Gamm::new(&app); + let pool_mars_osmo = gamm + .create_basic_pool( + &[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], + &signer, + ) + .unwrap() + .data + .pool_id; + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "umars".to_string(), + denom_out: "uweth".to_string(), + route: OsmosisRoute(vec![SwapAmountInRoute { + pool_id: pool_mars_osmo, + token_out_denom: "uweth".to_string(), + }]), }, - }, - &[], - ); + &[], + &signer, + ) + .unwrap_err(); assert_err( - res, + res_err, ContractError::InvalidRoute { - reason: "step 1: pool 43 does not contain output denom weth".to_string(), + reason: format!( + "step 1: pool {} does not contain output denom uweth", + pool_mars_osmo + ), }, ); } #[test] fn test_steps_do_not_loop() { - let owner = Addr::unchecked("owner"); - let mut app = mock_osmosis_app(); - - let coin_a = coin(6_000_000, "atom"); - let coin_b = coin(1_500_000, "osmo"); - let pool_id_x = 43; - let pool_x = Pool::new(coin_a, coin_b); - - let coin_c = coin(6_000_000, "osmo"); - let coin_d = coin(1_500_000, "usdc"); - let pool_id_y = 101; - let pool_y = Pool::new(coin_c, coin_d); - - let coin_e = coin(6_000_000, "osmo"); - let coin_f = coin(1_500_000, "mars"); - let pool_id_z = 2; - let pool_z = Pool::new(coin_e, coin_f); - - app.init_modules(|router, _, storage| { - router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); - router.custom.set_pool(storage, pool_id_y, &pool_y).unwrap(); - router.custom.set_pool(storage, pool_id_z, &pool_z).unwrap(); - }); - - let contract_addr = instantiate_contract(&mut app); - - let res = app.execute_contract( - owner, - contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "atom".to_string(), - denom_out: "mars".to_string(), - route: OsmosisRoute { - steps: vec![ - Step { - pool_id: pool_id_x, - denom_out: "osmo".to_string(), + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "uosmo"), + coin(1_000_000_000_000, "umars"), + coin(1_000_000_000_000, "uusdc"), + ]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let gamm = Gamm::new(&app); + let pool_atom_osmo = gamm + .create_basic_pool( + &[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], + &signer, + ) + .unwrap() + .data + .pool_id; + let pool_osmo_usdc = gamm + .create_basic_pool( + &[coin(6_000_000, "uosmo"), coin(1_500_000, "uusdc")], + &signer, + ) + .unwrap() + .data + .pool_id; + let pool_osmo_mars = gamm + .create_basic_pool( + &[coin(6_000_000, "uosmo"), coin(1_500_000, "umars")], + &signer, + ) + .unwrap() + .data + .pool_id; + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "uatom".to_string(), + denom_out: "umars".to_string(), + route: OsmosisRoute(vec![ + SwapAmountInRoute { + pool_id: pool_atom_osmo, + token_out_denom: "uosmo".to_string(), }, - Step { - pool_id: pool_id_y, - denom_out: "usdc".to_string(), + SwapAmountInRoute { + pool_id: pool_osmo_usdc, + token_out_denom: "uusdc".to_string(), }, - Step { - pool_id: pool_id_y, - denom_out: "osmo".to_string(), + SwapAmountInRoute { + pool_id: pool_osmo_usdc, + token_out_denom: "uosmo".to_string(), }, - Step { - pool_id: pool_id_z, - denom_out: "mars".to_string(), + SwapAmountInRoute { + pool_id: pool_osmo_mars, + token_out_denom: "umars".to_string(), }, - ], + ]), }, - }, - &[], - ); + &[], + &signer, + ) + .unwrap_err(); // invalid - route contains a loop // this example: ATOM -> OSMO -> USDC -> OSMO -> MARS assert_err( - res, + res_err, ContractError::InvalidRoute { - reason: "route contains a loop: denom osmo seen twice".to_string(), + reason: "route contains a loop: denom uosmo seen twice".to_string(), }, ); } #[test] fn test_step_output_does_not_match() { - let owner = Addr::unchecked("owner"); - let mut app = mock_osmosis_app(); - - let coin_a = coin(6_000_000, "atom"); - let coin_b = coin(1_500_000, "osmo"); - let pool_id_x = 43; - let pool_x = Pool::new(coin_a, coin_b); - - app.init_modules(|router, _, storage| { - router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); - }); + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); - let contract_addr = instantiate_contract(&mut app); + let contract_addr = instantiate_contract(&wasm, &signer); - let res = app.execute_contract( - owner, - contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "atom".to_string(), - denom_out: "mars".to_string(), - route: OsmosisRoute { - steps: vec![Step { - pool_id: pool_id_x, - denom_out: "osmo".to_string(), - }], + let gamm = Gamm::new(&app); + let pool_atom_osmo = gamm + .create_basic_pool( + &[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], + &signer, + ) + .unwrap() + .data + .pool_id; + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "uatom".to_string(), + denom_out: "umars".to_string(), + route: OsmosisRoute(vec![SwapAmountInRoute { + pool_id: pool_atom_osmo, + token_out_denom: "uosmo".to_string(), + }]), }, - }, - &[], - ); + &[], + &signer, + ) + .unwrap_err(); assert_err( - res, + res_err, ContractError::InvalidRoute { - reason: "the route's output denom osmo does not match the desired output mars" + reason: "the route's output denom uosmo does not match the desired output umars" .to_string(), }, ); @@ -298,60 +358,69 @@ fn test_step_output_does_not_match() { #[test] fn test_set_route_success() { - let owner = Addr::unchecked("owner"); - let mut app = mock_osmosis_app(); - let contract_addr = instantiate_contract(&mut app); - - let coin_a = coin(6_000_000, "mars"); - let coin_b = coin(1_500_000, "osmo"); - let pool_id_x = 43; - let pool_x = Pool::new(coin_a, coin_b); - - let coin_c = coin(100_000, "weth"); - let coin_d = coin(1_000_000, "osmo"); - let pool_id_y = 15; - let pool_y = Pool::new(coin_c, coin_d); - - app.init_modules(|router, _, storage| { - router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); - router.custom.set_pool(storage, pool_id_y, &pool_y).unwrap(); - }); - - app.execute_contract( - owner, - contract_addr.clone(), + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uosmo"), + coin(1_000_000_000_000, "umars"), + coin(1_000_000_000_000, "uweth"), + ]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let gamm = Gamm::new(&app); + let pool_mars_osmo = gamm + .create_basic_pool( + &[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], + &signer, + ) + .unwrap() + .data + .pool_id; + let pool_weth_osmo = gamm + .create_basic_pool(&[coin(100_000, "uweth"), coin(1_000_000, "uosmo")], &signer) + .unwrap() + .data + .pool_id; + + wasm.execute( + &contract_addr, &ExecuteMsg::SetRoute { - denom_in: "mars".to_string(), - denom_out: "weth".to_string(), - route: OsmosisRoute { - steps: vec![ - Step { - pool_id: pool_id_x, - denom_out: "osmo".to_string(), - }, - Step { - pool_id: pool_id_y, - denom_out: "weth".to_string(), - }, - ], - }, + denom_in: "umars".to_string(), + denom_out: "uweth".to_string(), + route: OsmosisRoute(vec![ + SwapAmountInRoute { + pool_id: pool_mars_osmo, + token_out_denom: "uosmo".to_string(), + }, + SwapAmountInRoute { + pool_id: pool_weth_osmo, + token_out_denom: "uweth".to_string(), + }, + ]), }, &[], + &signer, ) .unwrap(); - let res: RouteResponse = app - .wrap() - .query_wasm_smart( - contract_addr.to_string(), + let res: RouteResponse = wasm + .query( + &contract_addr, &QueryMsg::Route { - denom_in: "mars".to_string(), - denom_out: "weth".to_string(), + denom_in: "umars".to_string(), + denom_out: "uweth".to_string(), }, ) .unwrap(); - assert_eq!(res.denom_in, "mars".to_string()); - assert_eq!(res.denom_out, "weth".to_string()); - assert_eq!(res.route.to_string(), "43:osmo|15:weth".to_string()); + assert_eq!(res.denom_in, "umars".to_string()); + assert_eq!(res.denom_out, "uweth".to_string()); + assert_eq!( + res.route.to_string(), + format!("{}:uosmo|{}:uweth", pool_mars_osmo, pool_weth_osmo) + ); } diff --git a/contracts/swapper/osmosis/tests/test_swap.rs b/contracts/swapper/osmosis/tests/test_swap.rs index 6cedd1c6e..543acdeea 100644 --- a/contracts/swapper/osmosis/tests/test_swap.rs +++ b/contracts/swapper/osmosis/tests/test_swap.rs @@ -1,202 +1,182 @@ -use cosmwasm_std::{coin, Addr, Decimal, Querier, QuerierResult, QuerierWrapper, Uint128}; -use cw_multi_test::Executor; -use osmo_bindings::Step; -use osmo_bindings_test::{OsmosisApp, OsmosisError, Pool}; +use cosmwasm_std::{coin, Addr, Decimal}; +use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; +use osmosis_testing::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; use rover::adapters::swap::ExecuteMsg; use rover::error::ContractError as RoverError; -use rover::traits::IntoDecimal; use swapper_base::ContractError; -use swapper_base::Route; use swapper_osmosis::route::OsmosisRoute; -use crate::helpers::mock_osmosis_app; -use crate::helpers::{assert_err, instantiate_contract}; +use crate::helpers::{assert_err, instantiate_contract, query_balance}; pub mod helpers; #[test] fn test_transfer_callback_only_internal() { - let mut app = mock_osmosis_app(); - let contract_addr = instantiate_contract(&mut app); - - let bad_guy = Addr::unchecked("bad_guy"); - let res = app.execute_contract( - bad_guy.clone(), - contract_addr, - &ExecuteMsg::::TransferResult { - recipient: bad_guy.clone(), - denom_in: "mars".to_string(), - denom_out: "osmo".to_string(), - }, - &[], - ); + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let accs = app + .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) + .unwrap(); + let owner = &accs[0]; + let bad_guy = &accs[1]; + + let contract_addr = instantiate_contract(&wasm, owner); + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::::TransferResult { + recipient: Addr::unchecked(bad_guy.address()), + denom_in: "mars".to_string(), + denom_out: "osmo".to_string(), + }, + &[], + bad_guy, + ) + .unwrap_err(); assert_err( - res, + res_err, ContractError::Rover(RoverError::Unauthorized { - user: bad_guy.to_string(), + user: bad_guy.address(), action: "transfer result".to_string(), }), ); } #[test] +#[ignore] // FIXME: TWAP doesn't work on osmosis-testing - fix in progress fn test_swap_exact_in_slippage_too_high() { - pub struct MockQuerier<'a> { - app: &'a OsmosisApp, - } - impl Querier for MockQuerier<'_> { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - self.app.raw_query(bin_request) - } - } - - let owner = Addr::unchecked("owner"); - let whale = Addr::unchecked("whale"); - let mut app = mock_osmosis_app(); - let contract_addr = instantiate_contract(&mut app); - - let coin_a = coin(6_000_000, "mars"); - let coin_b = coin(1_500_000, "osmo"); - let pool_id_x = 43; - let pool_x = Pool::new(coin_a, coin_b.clone()); - - app.init_modules(|router, _, storage| { - router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); - router - .bank - .init_balance(storage, &owner, vec![coin(10_000, "mars")]) - .unwrap(); - router - .bank - .init_balance(storage, &whale, vec![coin(1_000_000, "mars")]) - .unwrap(); - }); - - let route = OsmosisRoute { - steps: vec![Step { - pool_id: pool_id_x, - denom_out: coin_b.denom, - }], - }; - - app.execute_contract( - owner.clone(), - contract_addr.clone(), - &ExecuteMsg::SetRoute { - denom_in: "mars".to_string(), - denom_out: "osmo".to_string(), - route: route.clone(), - }, - &[], - ) - .unwrap(); + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uosmo"), + coin(1_000_000_000_000, "umars"), + ]) + .unwrap(); + let whale = app.init_account(&[coin(1_000_000, "umars")]).unwrap(); - let querier = MockQuerier { app: &app }; - let mock_querier = QuerierWrapper::new(&querier); + let contract_addr = instantiate_contract(&wasm, &signer); - // Simulate real-time slippage by generating swapper message first, changing pool ratio, and then swapping with that message - let msg = route - .build_exact_in_swap_msg( - &mock_querier, - contract_addr.clone(), - &coin(10_000, "mars"), - Decimal::from_atomics(6u128, 2).unwrap(), + let gamm = Gamm::new(&app); + let pool_mars_osmo = gamm + .create_basic_pool( + &[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], + &signer, ) - .unwrap(); + .unwrap() + .data + .pool_id; - // whale does a huge trade to make mars less valuable - app.execute_contract( - whale, - contract_addr, - &ExecuteMsg::::SwapExactIn { - coin_in: coin(1_000_000, "mars"), - denom_out: "osmo".to_string(), - slippage: 1.to_dec().unwrap(), + let route = OsmosisRoute(vec![SwapAmountInRoute { + pool_id: pool_mars_osmo, + token_out_denom: "uosmo".to_string(), + }]); + + wasm.execute( + &contract_addr, + &ExecuteMsg::SetRoute { + denom_in: "umars".to_string(), + denom_out: "uosmo".to_string(), + route, }, - &[coin(1_000_000, "mars")], + &[], + &signer, ) .unwrap(); - // Resume initial user's trade but the output is less than slippage allowance - let res = app.execute(owner, msg); + // whale does a huge trade + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::::SwapExactIn { + coin_in: coin(1_000_000, "umars"), + denom_out: "uosmo".to_string(), + slippage: Decimal::percent(5), + }, + &[coin(1_000_000, "umars")], + &whale, + ) + .unwrap_err(); - let error: OsmosisError = res.unwrap_err().downcast().unwrap(); - assert_eq!(error, OsmosisError::PriceTooLow); + assert_err( + res_err, + "uosmo token is lesser than min amount: calculated amount is lesser than min amount", + ) } #[test] +#[ignore] // FIXME: TWAP doesn't work on osmosis-testing - fix in progress fn test_swap_exact_in_success() { - let owner = Addr::unchecked("owner"); - let mut app = mock_osmosis_app(); - let contract_addr = instantiate_contract(&mut app); - - let coin_a = coin(6_000_000, "mars"); - let coin_b = coin(1_500_000, "osmo"); - let pool_id_x = 43; - let pool_x = Pool::new(coin_a, coin_b); - - app.init_modules(|router, _, storage| { - router.custom.set_pool(storage, pool_id_x, &pool_x).unwrap(); - router - .bank - .init_balance(storage, &owner, vec![coin(10_000, "mars")]) - .unwrap(); - }); - - app.execute_contract( - owner.clone(), - contract_addr.clone(), + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uosmo"), + coin(1_000_000_000_000, "umars"), + ]) + .unwrap(); + let user = app.init_account(&[coin(10_000, "umars")]).unwrap(); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let gamm = Gamm::new(&app); + let pool_mars_osmo = gamm + .create_basic_pool( + &[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], + &signer, + ) + .unwrap() + .data + .pool_id; + + wasm.execute( + &contract_addr, &ExecuteMsg::SetRoute { - denom_in: "mars".to_string(), - denom_out: "osmo".to_string(), - route: OsmosisRoute { - steps: vec![Step { - pool_id: pool_id_x, - denom_out: "osmo".to_string(), - }], - }, + denom_in: "umars".to_string(), + denom_out: "uosmo".to_string(), + route: OsmosisRoute(vec![SwapAmountInRoute { + pool_id: pool_mars_osmo, + token_out_denom: "uosmo".to_string(), + }]), }, &[], + &signer, ) .unwrap(); - let mars_balance = app.wrap().query_balance(owner.to_string(), "mars").unwrap(); - let osmo_balance = app.wrap().query_balance(owner.to_string(), "osmo").unwrap(); - - assert_eq!(mars_balance.amount, Uint128::new(10_000)); - assert_eq!(osmo_balance.amount, Uint128::zero()); + let bank = Bank::new(&app); + let osmo_balance = query_balance(&bank, &user.address(), "uosmo"); + let mars_balance = query_balance(&bank, &user.address(), "umars"); + assert_eq!(osmo_balance, 0); + assert_eq!(mars_balance, 10_000); - app.execute_contract( - owner.clone(), - contract_addr.clone(), + wasm.execute( + &contract_addr, &ExecuteMsg::::SwapExactIn { - coin_in: coin(10_000, "mars"), - denom_out: "osmo".to_string(), - slippage: Decimal::from_atomics(6u128, 2).unwrap(), + coin_in: coin(10_000, "umars"), + denom_out: "uosmo".to_string(), + slippage: Decimal::percent(6), }, - &[coin(10_000, "mars")], + &[coin(10_000, "umars")], + &user, ) .unwrap(); // Assert user receives their new tokens - let mars_balance = app.wrap().query_balance(owner.to_string(), "mars").unwrap(); - let osmo_balance = app.wrap().query_balance(owner.to_string(), "osmo").unwrap(); - - assert_eq!(mars_balance.amount, Uint128::zero()); - assert_eq!(osmo_balance.amount, Uint128::new(2489)); + let osmo_balance = query_balance(&bank, &user.address(), "uosmo"); + let mars_balance = query_balance(&bank, &user.address(), "umars"); + assert_eq!(osmo_balance, 2470); + assert_eq!(mars_balance, 0); // Assert no tokens in contract left over - let mars_balance = app - .wrap() - .query_balance(contract_addr.to_string(), "mars") - .unwrap(); - let osmo_balance = app - .wrap() - .query_balance(contract_addr.to_string(), "osmo") - .unwrap(); - - assert_eq!(mars_balance.amount, Uint128::zero()); - assert_eq!(osmo_balance.amount, Uint128::zero()); + let osmo_balance = query_balance(&bank, &contract_addr, "uosmo"); + let mars_balance = query_balance(&bank, &contract_addr, "umars"); + assert_eq!(osmo_balance, 0); + assert_eq!(mars_balance, 0); } diff --git a/contracts/swapper/osmosis/tests/test_update_config.rs b/contracts/swapper/osmosis/tests/test_update_config.rs index 8f53760f2..861d855df 100644 --- a/contracts/swapper/osmosis/tests/test_update_config.rs +++ b/contracts/swapper/osmosis/tests/test_update_config.rs @@ -1,35 +1,43 @@ -use cosmwasm_std::Addr; -use cw_multi_test::Executor; +use cosmwasm_std::coin; +use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; use rover::adapters::swap::{Config, ExecuteMsg, QueryMsg}; use rover::error::ContractError as RoverError; use swapper_base::ContractError; use swapper_osmosis::route::OsmosisRoute; -use crate::helpers::mock_osmosis_app; use crate::helpers::{assert_err, instantiate_contract}; pub mod helpers; #[test] fn test_only_owner_can_update_config() { - let mut app = mock_osmosis_app(); - let contract_addr = instantiate_contract(&mut app); + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); - let bad_guy = Addr::unchecked("bad_guy"); - let res = app.execute_contract( - bad_guy.clone(), - contract_addr, - &ExecuteMsg::::UpdateConfig { - owner: Some(bad_guy.to_string()), - }, - &[], - ); + let accs = app + .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) + .unwrap(); + let owner = &accs[0]; + let bad_guy = &accs[1]; + + let contract_addr = instantiate_contract(&wasm, owner); + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::::UpdateConfig { + owner: Some(bad_guy.address()), + }, + &[], + bad_guy, + ) + .unwrap_err(); assert_err( - res, + res_err, ContractError::Rover(RoverError::Unauthorized { - user: bad_guy.to_string(), + user: bad_guy.address(), action: "update owner".to_string(), }), ); @@ -37,48 +45,49 @@ fn test_only_owner_can_update_config() { #[test] fn test_update_config_works_with_full_config() { - let owner = Addr::unchecked("owner"); - let mut app = mock_osmosis_app(); - let contract_addr = instantiate_contract(&mut app); - - let new_owner = Addr::unchecked("new_owner"); - app.execute_contract( - owner.clone(), - contract_addr.clone(), + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let accs = app + .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) + .unwrap(); + let owner = &accs[0]; + let new_owner = &accs[1]; + + let contract_addr = instantiate_contract(&wasm, owner); + + wasm.execute( + &contract_addr, &ExecuteMsg::::UpdateConfig { - owner: Some(new_owner.to_string()), + owner: Some(new_owner.address()), }, &[], + owner, ) .unwrap(); - let new_config: Config = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - assert_ne!(new_config.owner, owner.to_string()); - assert_eq!(new_config.owner, new_owner.to_string()); + let config: Config = wasm.query(&contract_addr, &QueryMsg::Config {}).unwrap(); + assert_eq!(config.owner, new_owner.address()); } #[test] fn test_update_config_does_nothing_when_nothing_is_passed() { - let owner = Addr::unchecked("owner"); - let mut app = mock_osmosis_app(); - let contract_addr = instantiate_contract(&mut app); + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + let owner = app + .init_account(&[coin(1_000_000_000_000, "uosmo")]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &owner); - app.execute_contract( - owner.clone(), - contract_addr.clone(), + wasm.execute( + &contract_addr, &ExecuteMsg::::UpdateConfig { owner: None }, &[], + &owner, ) .unwrap(); - let new_config: Config = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(new_config.owner, owner.to_string()); + let config: Config = wasm.query(&contract_addr, &QueryMsg::Config {}).unwrap(); + assert_eq!(config.owner, owner.address()); } diff --git a/packages/chains/osmosis/Cargo.toml b/packages/chains/osmosis/Cargo.toml new file mode 100755 index 000000000..68f32f32b --- /dev/null +++ b/packages/chains/osmosis/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "mars-osmosis" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } + +[lib] +doctest = false + +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] + +[dependencies] +cosmwasm-std = { workspace = true } +osmosis-std = { workspace = true } +serde = { workspace = true } \ No newline at end of file diff --git a/packages/chains/osmosis/README.md b/packages/chains/osmosis/README.md new file mode 100644 index 000000000..c5d6a62f7 --- /dev/null +++ b/packages/chains/osmosis/README.md @@ -0,0 +1,2 @@ +# Mars Osmosis +Contains helpers for Osmosis chain diff --git a/packages/chains/osmosis/src/helpers.rs b/packages/chains/osmosis/src/helpers.rs new file mode 100644 index 000000000..c74dd5421 --- /dev/null +++ b/packages/chains/osmosis/src/helpers.rs @@ -0,0 +1,85 @@ +use std::str::FromStr; + +use cosmwasm_std::{Decimal, Empty, QuerierWrapper, QueryRequest, StdResult}; + +use osmosis_std::shim::Timestamp; +use osmosis_std::types::cosmos::base::v1beta1::Coin; +use osmosis_std::types::osmosis::gamm::v1beta1::{ + GammQuerier, PoolAsset, PoolParams, QueryPoolRequest, +}; +use osmosis_std::types::osmosis::twap::v1beta1::TwapQuerier; + +use serde::{Deserialize, Serialize}; + +// NOTE: Use custom Pool (`id` type as String) due to problem with json (de)serialization discrepancy between go and rust side. +// https://github.com/osmosis-labs/osmosis-rust/issues/42 +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct Pool { + pub id: String, + pub address: String, + pub pool_params: Option, + pub future_pool_governor: String, + pub pool_assets: Vec, + pub total_shares: Option, + pub total_weight: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct QueryPoolResponse { + pub pool: Pool, +} + +/// Query an Osmosis pool's coin depths and the supply of of liquidity token +pub fn query_pool(querier: &QuerierWrapper, pool_id: u64) -> StdResult { + let req: QueryRequest = QueryPoolRequest { pool_id }.into(); + let res: QueryPoolResponse = querier.query(&req)?; + Ok(res.pool) +} + +pub fn has_denom(denom: &str, pool_assets: &[PoolAsset]) -> bool { + pool_assets + .iter() + .flat_map(|asset| &asset.token) + .any(|coin| coin.denom == denom) +} + +/// Query the spot price of a coin, denominated in OSMO +pub fn query_spot_price( + querier: &QuerierWrapper, + pool_id: u64, + base_denom: &str, + quote_denom: &str, +) -> StdResult { + // NOTE: Currency pair consists of base and quote asset (base/quote). Spot query has it swapped. + // For example: + // if we want to check the price ATOM/OSMO then we pass base_asset = OSMO, quote_asset = ATOM + let spot_price_res = GammQuerier::new(querier).spot_price( + pool_id, + quote_denom.to_string(), + base_denom.to_string(), + )?; + let price = Decimal::from_str(&spot_price_res.spot_price)?; + Ok(price) +} + +/// Query the twap price of a coin, denominated in OSMO. +/// `start_time` must be within 48 hours of current block time. +pub fn query_twap_price( + querier: &QuerierWrapper, + pool_id: u64, + base_denom: &str, + quote_denom: &str, + start_time: u64, +) -> StdResult { + let arithmetic_twap_res = TwapQuerier::new(querier).arithmetic_twap_to_now( + pool_id, + base_denom.to_string(), + quote_denom.to_string(), + Some(Timestamp { + seconds: start_time as i64, + nanos: 0, + }), + )?; + let price = Decimal::from_str(&arithmetic_twap_res.arithmetic_twap)?; + Ok(price) +} diff --git a/packages/chains/osmosis/src/lib.rs b/packages/chains/osmosis/src/lib.rs new file mode 100644 index 000000000..1630fabcd --- /dev/null +++ b/packages/chains/osmosis/src/lib.rs @@ -0,0 +1 @@ +pub mod helpers; From 92b4311cdf382702c551a974729980b5dd6f02a9 Mon Sep 17 00:00:00 2001 From: piobab Date: Fri, 4 Nov 2022 19:59:40 +0100 Subject: [PATCH 075/218] Compliant with cw2 migration specification. (#38) * Compliant with cw2 migration specification. * Make package / contracts consistent. * Update schema. --- Cargo.lock | 287 +++++++++--------- Cargo.toml | 20 +- contracts/account-nft/Cargo.toml | 3 +- contracts/account-nft/examples/schema.rs | 2 +- contracts/account-nft/src/contract.rs | 9 + contracts/account-nft/tests/helpers.rs | 4 +- contracts/account-nft/tests/test_mint.rs | 2 +- contracts/account-nft/tests/test_ownership.rs | 2 +- contracts/credit-manager/Cargo.toml | 24 +- contracts/credit-manager/examples/schema.rs | 2 +- contracts/credit-manager/src/borrow.rs | 2 +- contracts/credit-manager/src/contract.rs | 16 +- contracts/credit-manager/src/deposit.rs | 4 +- contracts/credit-manager/src/execute.rs | 12 +- contracts/credit-manager/src/health.rs | 8 +- contracts/credit-manager/src/instantiate.rs | 4 +- .../credit-manager/src/liquidate_coin.rs | 6 +- contracts/credit-manager/src/query.rs | 8 +- contracts/credit-manager/src/repay.rs | 2 +- contracts/credit-manager/src/state.rs | 6 +- contracts/credit-manager/src/swap.rs | 2 +- .../src/update_coin_balances.rs | 2 +- contracts/credit-manager/src/utils.rs | 10 +- contracts/credit-manager/src/vault/enter.rs | 8 +- contracts/credit-manager/src/vault/exit.rs | 8 +- .../credit-manager/src/vault/exit_unlocked.rs | 8 +- .../src/vault/liquidate_vault.rs | 4 +- .../src/vault/request_unlock.rs | 6 +- contracts/credit-manager/src/vault/utils.rs | 4 +- contracts/credit-manager/src/withdraw.rs | 2 +- contracts/credit-manager/src/zap.rs | 4 +- .../tests/helpers/assertions.rs | 4 +- .../credit-manager/tests/helpers/builders.rs | 2 +- .../credit-manager/tests/helpers/contracts.rs | 44 +-- .../credit-manager/tests/helpers/mock_env.rs | 34 +-- .../credit-manager/tests/helpers/utils.rs | 2 +- contracts/credit-manager/tests/test_borrow.rs | 6 +- .../tests/test_coin_balances.rs | 6 +- .../credit-manager/tests/test_deposit.rs | 8 +- .../credit-manager/tests/test_dispatch.rs | 6 +- .../tests/test_enumerate_coin_balances.rs | 4 +- .../tests/test_enumerate_debt_shares.rs | 6 +- .../tests/test_enumerate_total_debt_shares.rs | 6 +- .../test_enumerate_vault_coin_balances.rs | 2 +- .../tests/test_enumerate_vault_positions.rs | 2 +- contracts/credit-manager/tests/test_health.rs | 12 +- .../tests/test_liquidate_coin.rs | 10 +- .../tests/test_liquidate_vault.rs | 12 +- contracts/credit-manager/tests/test_repay.rs | 8 +- contracts/credit-manager/tests/test_swap.rs | 6 +- .../tests/test_update_config.rs | 10 +- .../credit-manager/tests/test_vault_enter.rs | 8 +- .../credit-manager/tests/test_vault_exit.rs | 12 +- .../tests/test_vault_exit_unlocked.rs | 12 +- .../tests/test_vault_request_unlock.rs | 8 +- .../credit-manager/tests/test_withdraw.rs | 6 +- .../credit-manager/tests/test_zap_provide.rs | 8 +- .../credit-manager/tests/test_zap_withdraw.rs | 8 +- contracts/mock-oracle/Cargo.toml | 2 +- contracts/mock-oracle/examples/schema.rs | 2 +- contracts/mock-red-bank/Cargo.toml | 2 +- contracts/mock-red-bank/examples/schema.rs | 2 +- contracts/mock-vault/Cargo.toml | 4 +- contracts/mock-vault/examples/schema.rs | 2 +- contracts/mock-vault/src/error.rs | 2 +- contracts/mock-vault/src/msg.rs | 2 +- contracts/mock-vault/src/state.rs | 2 +- contracts/mock-zapper/Cargo.toml | 31 +- contracts/mock-zapper/examples/schema.rs | 2 +- contracts/mock-zapper/src/contract.rs | 2 +- contracts/mock-zapper/src/error.rs | 2 +- contracts/mock-zapper/src/state.rs | 2 +- .../Cargo.toml | 13 +- .../examples/schema.rs | 0 .../src/contract.rs | 16 +- .../src/error.rs | 2 +- .../src/lib.rs | 0 .../src/msg.rs | 2 +- .../src/state.rs | 2 +- .../tests/helpers.rs | 18 +- .../tests/test_query_price.rs | 4 +- .../tests/test_query_priceable_underlying.rs | 0 .../tests/test_update_config.rs | 2 +- contracts/swapper/base/Cargo.toml | 4 +- contracts/swapper/base/examples/schema.rs | 2 +- contracts/swapper/base/src/contract.rs | 4 +- contracts/swapper/base/src/error.rs | 2 +- contracts/swapper/base/src/traits.rs | 2 +- contracts/swapper/mock/Cargo.toml | 12 +- contracts/swapper/mock/src/contract.rs | 4 +- contracts/swapper/osmosis/Cargo.toml | 21 +- contracts/swapper/osmosis/src/contract.rs | 14 +- contracts/swapper/osmosis/src/route.rs | 4 +- contracts/swapper/osmosis/tests/helpers.rs | 2 +- .../osmosis/tests/test_enumerate_routes.rs | 4 +- .../swapper/osmosis/tests/test_estimate.rs | 4 +- .../swapper/osmosis/tests/test_instantiate.rs | 2 +- .../swapper/osmosis/tests/test_set_route.rs | 8 +- contracts/swapper/osmosis/tests/test_swap.rs | 8 +- .../osmosis/tests/test_update_config.rs | 8 +- packages/rover/Cargo.toml | 6 +- packages/rover/src/adapters/oracle.rs | 2 +- schema.Makefile.toml | 14 +- .../mars-account-nft.json} | 2 +- .../mars-credit-manager.json} | 2 +- .../mars-mock-oracle.json} | 2 +- .../mars-mock-red-bank.json} | 2 +- .../mars-mock-vault.json} | 2 +- .../mars-mock-zapper.json} | 2 +- .../mars-swapper-base.json} | 2 +- 110 files changed, 518 insertions(+), 474 deletions(-) rename contracts/{mars-oracle-adapter => oracle-adapter}/Cargo.toml (72%) rename contracts/{mars-oracle-adapter => oracle-adapter}/examples/schema.rs (100%) rename contracts/{mars-oracle-adapter => oracle-adapter}/src/contract.rs (94%) rename contracts/{mars-oracle-adapter => oracle-adapter}/src/error.rs (92%) rename contracts/{mars-oracle-adapter => oracle-adapter}/src/lib.rs (100%) rename contracts/{mars-oracle-adapter => oracle-adapter}/src/msg.rs (96%) rename contracts/{mars-oracle-adapter => oracle-adapter}/src/state.rs (90%) rename contracts/{mars-oracle-adapter => oracle-adapter}/tests/helpers.rs (94%) rename contracts/{mars-oracle-adapter => oracle-adapter}/tests/test_query_price.rs (94%) rename contracts/{mars-oracle-adapter => oracle-adapter}/tests/test_query_priceable_underlying.rs (100%) rename contracts/{mars-oracle-adapter => oracle-adapter}/tests/test_update_config.rs (98%) rename schemas/{account-nft/account-nft.json => mars-account-nft/mars-account-nft.json} (99%) rename schemas/{credit-manager/credit-manager.json => mars-credit-manager/mars-credit-manager.json} (99%) rename schemas/{mock-oracle/mock-oracle.json => mars-mock-oracle/mars-mock-oracle.json} (98%) rename schemas/{mock-red-bank/mock-red-bank.json => mars-mock-red-bank/mars-mock-red-bank.json} (99%) rename schemas/{mock-vault/mock-vault.json => mars-mock-vault/mars-mock-vault.json} (99%) rename schemas/{mock-zapper/mock-zapper.json => mars-mock-zapper/mars-mock-zapper.json} (99%) rename schemas/{swapper-base/swapper-base.json => mars-swapper-base/mars-swapper-base.json} (99%) diff --git a/Cargo.lock b/Cargo.lock index e47b3eb06..175371a6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,19 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "account-nft" -version = "1.0.0" -dependencies = [ - "anyhow", - "cosmwasm-schema", - "cosmwasm-std", - "cw-multi-test", - "cw-storage-plus 0.16.0", - "cw721", - "cw721-base", -] - [[package]] name = "ahash" version = "0.7.6" @@ -251,7 +238,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20b42021d8488665b1a0d9748f1f81df7235362d194f44481e2e61bf376b77b4" dependencies = [ - "prost 0.11.0", + "prost 0.11.2", "prost-types", "tendermint-proto", ] @@ -372,34 +359,6 @@ dependencies = [ "libc", ] -[[package]] -name = "credit-manager" -version = "1.0.0" -dependencies = [ - "account-nft", - "anyhow", - "cosmos-vault-standard", - "cosmwasm-schema", - "cosmwasm-std", - "cw-item-set", - "cw-multi-test", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", - "cw2", - "cw721", - "cw721-base", - "itertools", - "mars-health", - "mars-oracle-adapter", - "mars-outpost", - "mock-oracle", - "mock-red-bank", - "mock-vault", - "mock-zapper", - "rover", - "swapper-mock", -] - [[package]] name = "crunchy" version = "0.2.2" @@ -968,6 +927,48 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mars-account-nft" +version = "1.0.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus 0.16.0", + "cw2", + "cw721", + "cw721-base", +] + +[[package]] +name = "mars-credit-manager" +version = "1.0.0" +dependencies = [ + "anyhow", + "cosmos-vault-standard", + "cosmwasm-schema", + "cosmwasm-std", + "cw-item-set", + "cw-multi-test", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", + "cw2", + "cw721", + "cw721-base", + "itertools", + "mars-account-nft", + "mars-health", + "mars-mock-oracle", + "mars-mock-red-bank", + "mars-mock-vault", + "mars-mock-zapper", + "mars-oracle-adapter", + "mars-outpost", + "mars-rover", + "mars-swapper-mock", +] + [[package]] name = "mars-health" version = "1.0.0" @@ -976,6 +977,51 @@ dependencies = [ "mars-outpost", ] +[[package]] +name = "mars-mock-oracle" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "mars-outpost", +] + +[[package]] +name = "mars-mock-red-bank" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "mars-outpost", +] + +[[package]] +name = "mars-mock-vault" +version = "1.0.0" +dependencies = [ + "cosmos-vault-standard", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", + "mars-rover", + "thiserror", +] + +[[package]] +name = "mars-mock-zapper" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", + "mars-rover", + "thiserror", +] + [[package]] name = "mars-oracle-adapter" version = "1.0.0" @@ -986,10 +1032,11 @@ dependencies = [ "cw-multi-test", "cw-storage-plus 0.16.0", "cw-utils 0.16.0", + "cw2", + "mars-mock-oracle", + "mars-mock-vault", "mars-outpost", - "mock-oracle", - "mock-vault", - "rover", + "mars-rover", "thiserror", ] @@ -1012,62 +1059,79 @@ dependencies = [ ] [[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "mock-oracle" +name = "mars-rover" version = "1.0.0" dependencies = [ + "cosmos-vault-standard", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", + "mars-health", + "mars-mock-oracle", + "mars-mock-red-bank", "mars-outpost", + "schemars", + "serde", + "thiserror", ] [[package]] -name = "mock-red-bank" +name = "mars-swapper-base" version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.16.0", - "mars-outpost", + "mars-rover", + "schemars", + "serde", + "thiserror", ] [[package]] -name = "mock-vault" +name = "mars-swapper-mock" version = "1.0.0" dependencies = [ - "cosmos-vault-standard", - "cosmwasm-schema", + "anyhow", "cosmwasm-std", + "cw-multi-test", "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", - "rover", + "mars-rover", + "mars-swapper-base", "thiserror", ] [[package]] -name = "mock-zapper" +name = "mars-swapper-osmosis" version = "1.0.0" dependencies = [ + "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", - "rover", + "cw2", + "mars-osmosis", + "mars-rover", + "mars-swapper-base", + "osmosis-std 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "osmosis-testing", + "schemars", "thiserror", ] +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "nom" version = "7.1.1" @@ -1144,7 +1208,7 @@ dependencies = [ "chrono", "cosmwasm-std", "osmosis-std-derive 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "prost 0.11.0", + "prost 0.11.2", "prost-types", "schemars", "serde", @@ -1159,7 +1223,7 @@ dependencies = [ "chrono", "cosmwasm-std", "osmosis-std-derive 0.12.0 (git+https://github.com/osmosis-labs/osmosis-rust)", - "prost 0.11.0", + "prost 0.11.2", "prost-types", "schemars", "serde", @@ -1199,7 +1263,7 @@ dependencies = [ "cosmrs", "cosmwasm-std", "osmosis-std 0.12.0 (git+https://github.com/osmosis-labs/osmosis-rust)", - "prost 0.11.0", + "prost 0.11.2", "serde", "serde_json", "thiserror", @@ -1269,12 +1333,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" +checksum = "a0841812012b2d4a6145fae9a6af1534873c32aa67fff26bd09f8fa42c83f95a" dependencies = [ "bytes", - "prost-derive 0.11.0", + "prost-derive 0.11.2", ] [[package]] @@ -1292,9 +1356,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" +checksum = "164ae68b6587001ca506d3bf7f1000bfa248d0e1217b618108fba4ec1d0cc306" dependencies = [ "anyhow", "itertools", @@ -1305,12 +1369,12 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dfaa718ad76a44b3415e6c4d53b17c8f99160dcb3a99b10470fce8ad43f6e3e" +checksum = "747761bc3dc48f9a34553bf65605cf6cb6288ba219f3450b4275dbd81539551a" dependencies = [ "bytes", - "prost 0.11.0", + "prost 0.11.2", ] [[package]] @@ -1385,24 +1449,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "rover" -version = "1.0.0" -dependencies = [ - "cosmos-vault-standard", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", - "mars-health", - "mars-outpost", - "mock-oracle", - "mock-red-bank", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "rustc-hash" version = "1.1.0" @@ -1626,49 +1672,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "swapper-base" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.16.0", - "rover", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "swapper-mock" -version = "1.0.0" -dependencies = [ - "anyhow", - "cosmwasm-std", - "cw-multi-test", - "cw-storage-plus 0.16.0", - "rover", - "swapper-base", - "thiserror", -] - -[[package]] -name = "swapper-osmosis" -version = "1.0.0" -dependencies = [ - "anyhow", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.16.0", - "mars-osmosis", - "osmosis-std 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "osmosis-testing", - "rover", - "schemars", - "swapper-base", - "thiserror", -] - [[package]] name = "syn" version = "1.0.103" @@ -1707,7 +1710,7 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost 0.11.0", + "prost 0.11.2", "prost-types", "ripemd160", "serde", @@ -1733,7 +1736,7 @@ dependencies = [ "flex-error", "num-derive", "num-traits", - "prost 0.11.0", + "prost 0.11.2", "prost-types", "serde", "serde_bytes", diff --git a/Cargo.toml b/Cargo.toml index c947dfee2..2ae07846a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [ "contracts/account-nft", "contracts/credit-manager", "contracts/swapper/*", - "contracts/mars-oracle-adapter", + "contracts/oracle-adapter", "packages/chains/*", "packages/health", "packages/outpost", @@ -49,19 +49,19 @@ thiserror = "1.0" cosmos-vault-standard = { git = "https://github.com/apollodao/cosmos-vault-standard", features = ["lockup", "force-unlock"] } mars-health = { version = "1.0.0", path = "./packages/health" } mars-outpost = { version = "1.0.0", path = "./packages/outpost" } -rover = { version = "1.0.0", path = "./packages/rover" } +mars-rover = { version = "1.0.0", path = "./packages/rover" } # contracts -account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-oracle-adapter = { version = "1.0.0", path = "./contracts/mars-oracle-adapter", features = ["library"] } -swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } +mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } +mars-oracle-adapter = { version = "1.0.0", path = "contracts/oracle-adapter", features = ["library"] } +mars-swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } # mocks -mock-oracle = { version = "1.0.0", path = "./contracts/mock-oracle", features = ["library"] } -mock-red-bank = { version = "1.0.0", path = "./contracts/mock-red-bank", features = ["library"] } -mock-vault = { version = "1.0.0", path = "./contracts/mock-vault", features = ["library"] } -mock-zapper = { version = "1.0.0", path = "./contracts/mock-zapper", features = ["library"] } -swapper-mock = { version = "1.0.0", path = "./contracts/swapper/mock", features = ["library"] } +mars-mock-oracle = { version = "1.0.0", path = "./contracts/mock-oracle", features = ["library"] } +mars-mock-red-bank = { version = "1.0.0", path = "./contracts/mock-red-bank", features = ["library"] } +mars-mock-vault = { version = "1.0.0", path = "./contracts/mock-vault", features = ["library"] } +mars-mock-zapper = { version = "1.0.0", path = "./contracts/mock-zapper", features = ["library"] } +mars-swapper-mock = { version = "1.0.0", path = "./contracts/swapper/mock", features = ["library"] } [profile.release] opt-level = 3 diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index f4ca1ac21..c97a68917 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "account-nft" +name = "mars-account-nft" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } @@ -20,6 +20,7 @@ library = [] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw2 = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true } cw-storage-plus = { workspace = true } diff --git a/contracts/account-nft/examples/schema.rs b/contracts/account-nft/examples/schema.rs index 9f18105f8..08ad55c8a 100644 --- a/contracts/account-nft/examples/schema.rs +++ b/contracts/account-nft/examples/schema.rs @@ -1,6 +1,6 @@ -use account_nft::msg::{ExecuteMsg, QueryMsg}; use cosmwasm_schema::write_api; use cw721_base::msg::InstantiateMsg; +use mars_account_nft::msg::{ExecuteMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index b92a0a489..79b113c34 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -5,6 +5,7 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{ to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; +use cw2::set_contract_version; use cw721_base::{ContractError, Cw721Contract, InstantiateMsg}; use crate::execute::{accept_ownership, mint, propose_new_owner}; @@ -12,6 +13,9 @@ use crate::msg::{ExecuteMsg, QueryMsg}; use crate::query::query_proposed_new_owner; use crate::state::NEXT_ID; +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + // Extending CW721 base contract pub type Parent<'a> = Cw721Contract<'a, Empty, Empty, Empty, Empty>; @@ -22,6 +26,11 @@ pub fn instantiate( info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { + set_contract_version( + deps.storage, + &format!("crates.io:{}", CONTRACT_NAME), + CONTRACT_VERSION, + )?; NEXT_ID.save(deps.storage, &1)?; Parent::default().instantiate(deps, env, info, msg) } diff --git a/contracts/account-nft/tests/helpers.rs b/contracts/account-nft/tests/helpers.rs index e18dfecb6..c098766fd 100644 --- a/contracts/account-nft/tests/helpers.rs +++ b/contracts/account-nft/tests/helpers.rs @@ -3,8 +3,8 @@ use cosmwasm_std::Addr; use cw721_base::InstantiateMsg; use cw_multi_test::{AppResponse, BasicApp, ContractWrapper, Executor}; -use account_nft::contract::{execute, instantiate, query}; -use account_nft::msg::ExecuteMsg as ExtendedExecuteMsg; +use mars_account_nft::contract::{execute, instantiate, query}; +use mars_account_nft::msg::ExecuteMsg as ExtendedExecuteMsg; pub fn instantiate_mock_nft_contract(app: &mut BasicApp, owner: &Addr) -> Addr { let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); diff --git a/contracts/account-nft/tests/test_mint.rs b/contracts/account-nft/tests/test_mint.rs index 76357ed78..f952973c7 100644 --- a/contracts/account-nft/tests/test_mint.rs +++ b/contracts/account-nft/tests/test_mint.rs @@ -6,7 +6,7 @@ use cw721_base::ContractError::Unauthorized; use cw721_base::{ContractError, QueryMsg}; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; -use account_nft::msg::ExecuteMsg as ExtendedExecuteMsg; +use mars_account_nft::msg::ExecuteMsg as ExtendedExecuteMsg; use crate::helpers::{burn_action, instantiate_mock_nft_contract, mint_action}; diff --git a/contracts/account-nft/tests/test_ownership.rs b/contracts/account-nft/tests/test_ownership.rs index 4309542a3..9311465f0 100644 --- a/contracts/account-nft/tests/test_ownership.rs +++ b/contracts/account-nft/tests/test_ownership.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{Addr, StdResult}; use cw721_base::MinterResponse; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; -use account_nft::msg::{ExecuteMsg as ExtendedExecuteMsg, QueryMsg}; +use mars_account_nft::msg::{ExecuteMsg as ExtendedExecuteMsg, QueryMsg}; use crate::helpers::instantiate_mock_nft_contract; diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index cda9575ac..6da3c1ca7 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "credit-manager" +name = "mars-credit-manager" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } @@ -18,7 +18,6 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -account-nft = { workspace = true } cosmos-vault-standard = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } @@ -27,18 +26,19 @@ cw721 = { workspace = true } cw721-base = { workspace = true } cw-item-set = { workspace = true } cw-storage-plus = { workspace = true } +mars-account-nft = { workspace = true } mars-health = { workspace = true } mars-outpost = { workspace = true } mars-oracle-adapter = { workspace = true } -rover = { workspace = true } +mars-rover = { workspace = true } [dev-dependencies] -anyhow = { workspace = true } -cw-multi-test = { workspace = true } -cw-utils = { workspace = true } -itertools = "0.10" -mock-oracle = { workspace = true } -mock-red-bank = { workspace = true } -mock-vault = { workspace = true } -mock-zapper = { workspace = true } -swapper-mock = { workspace = true } +anyhow = { workspace = true } +cw-multi-test = { workspace = true } +cw-utils = { workspace = true } +itertools = "0.10" +mars-mock-oracle = { workspace = true } +mars-mock-red-bank = { workspace = true } +mars-mock-vault = { workspace = true } +mars-mock-zapper = { workspace = true } +mars-swapper-mock = { workspace = true } diff --git a/contracts/credit-manager/examples/schema.rs b/contracts/credit-manager/examples/schema.rs index 3b992ec27..01b4a4eb0 100644 --- a/contracts/credit-manager/examples/schema.rs +++ b/contracts/credit-manager/examples/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index 7b80d3ac1..bf9712179 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; -use rover::error::{ContractError, ContractResult}; +use mars_rover::error::{ContractError, ContractResult}; use crate::state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; use crate::utils::{assert_coin_is_whitelisted, increment_coin_balance}; diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 2aa686e9a..18d4c9c9b 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -3,10 +3,10 @@ use cosmwasm_std::{ }; use cw2::set_contract_version; -use rover::adapters::vault::VAULT_REQUEST_REPLY_ID; -use rover::error::{ContractError, ContractResult}; -use rover::msg::query::HealthResponse; -use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_rover::adapters::vault::VAULT_REQUEST_REPLY_ID; +use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::msg::query::HealthResponse; +use mars_rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::execute::{create_credit_account, dispatch_actions, execute_callback, update_config}; use crate::health::compute_health; @@ -20,7 +20,7 @@ use crate::query::{ use crate::vault::handle_unlock_request_reply; use crate::zap::{estimate_provide_liquidity, estimate_withdraw_liquidity}; -const CONTRACT_NAME: &str = "crates.io:rover-credit-manager"; +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] @@ -30,7 +30,11 @@ pub fn instantiate( _info: MessageInfo, msg: InstantiateMsg, ) -> ContractResult { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + set_contract_version( + deps.storage, + &format!("crates.io:{}", CONTRACT_NAME), + CONTRACT_VERSION, + )?; store_config(deps, &msg)?; Ok(Response::default()) } diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index 27af27125..6f759e22a 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Coin, Response, Storage, Uint128}; -use rover::coins::Coins; -use rover::error::{ContractError, ContractResult}; +use mars_rover::coins::Coins; +use mars_rover::error::{ContractError, ContractResult}; use crate::utils::{assert_coin_is_whitelisted, increment_coin_balance}; diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 7210565d0..01a2bfa29 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -22,12 +22,12 @@ use crate::swap::swap_exact_in; use crate::update_coin_balances::update_coin_balance; use crate::withdraw::withdraw; use crate::zap::{provide_liquidity, withdraw_liquidity}; -use account_nft::msg::ExecuteMsg as NftExecuteMsg; -use rover::coins::Coins; -use rover::error::{ContractError, ContractResult}; -use rover::msg::execute::{Action, CallbackMsg}; -use rover::msg::instantiate::ConfigUpdates; -use rover::traits::{FallbackStr, Stringify}; +use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; +use mars_rover::coins::Coins; +use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::msg::execute::{Action, CallbackMsg}; +use mars_rover::msg::instantiate::ConfigUpdates; +use mars_rover::traits::{FallbackStr, Stringify}; pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 68b846eb1..ead9a9b3d 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -3,10 +3,10 @@ use mars_health::health::{Health, Position}; use mars_health::query::MarsQuerier; use mars_outpost::red_bank::Market; -use rover::adapters::vault::VaultPosition; -use rover::adapters::{Oracle, RedBank}; -use rover::error::{ContractError, ContractResult}; -use rover::traits::{Coins, IntoDecimal}; +use mars_rover::adapters::vault::VaultPosition; +use mars_rover::adapters::{Oracle, RedBank}; +use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::traits::{Coins, IntoDecimal}; use crate::query::query_positions; use crate::state::{ORACLE, RED_BANK, VAULT_CONFIGS}; diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 5b910e5be..6c99cccbd 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,7 +1,7 @@ use cosmwasm_std::DepsMut; -use rover::error::ContractResult; -use rover::msg::InstantiateMsg; +use mars_rover::error::ContractResult; +use mars_rover::msg::InstantiateMsg; use crate::state::{ ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index 3566bde7c..9c909bc93 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -2,9 +2,9 @@ use std::ops::{Add, Div}; use cosmwasm_std::{Coin, CosmosMsg, Decimal, DepsMut, Env, Response, StdError, Storage, Uint128}; -use rover::error::{ContractError, ContractResult}; -use rover::msg::execute::CallbackMsg; -use rover::traits::{IntoDecimal, IntoUint128}; +use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::msg::execute::CallbackMsg; +use mars_rover::traits::{IntoDecimal, IntoUint128}; use crate::health::{compute_health, val_or_na}; use crate::repay::current_debt_for_denom; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 3865c1e0e..5e8713c58 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,10 +1,10 @@ use cosmwasm_std::{Addr, Coin, Deps, Env, Order, StdResult, Uint128}; use cw_storage_plus::Bound; -use rover::adapters::vault::{Vault, VaultBase, VaultPosition, VaultUnchecked}; -use rover::error::ContractResult; -use rover::msg::instantiate::VaultInstantiateConfig; -use rover::msg::query::{ +use mars_rover::adapters::vault::{Vault, VaultBase, VaultPosition, VaultUnchecked}; +use mars_rover::error::ContractResult; +use mars_rover::msg::instantiate::VaultInstantiateConfig; +use mars_rover::msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, Positions, SharesResponseItem, VaultPositionResponseItem, VaultWithBalance, }; diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index c469d7908..1955a0972 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -2,7 +2,7 @@ use std::cmp::min; use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; -use rover::error::{ContractError, ContractResult}; +use mars_rover::error::{ContractError, ContractResult}; use crate::state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; use crate::utils::{assert_coin_is_whitelisted, debt_shares_to_amount, decrement_coin_balance}; diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 7a851698c..f4730a9b6 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -2,9 +2,9 @@ use cosmwasm_std::{Addr, Decimal, Uint128}; use cw_item_set::Set; use cw_storage_plus::{Item, Map}; -use rover::adapters::swap::Swapper; -use rover::adapters::vault::{VaultConfig, VaultPositionAmount}; -use rover::adapters::{Oracle, RedBank, Zapper}; +use mars_rover::adapters::swap::Swapper; +use mars_rover::adapters::vault::{VaultConfig, VaultPositionAmount}; +use mars_rover::adapters::{Oracle, RedBank, Zapper}; use crate::vault::RequestTempStorage; diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs index cf2cb0d7b..f27cc8934 100644 --- a/contracts/credit-manager/src/swap.rs +++ b/contracts/credit-manager/src/swap.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Coin, Decimal, DepsMut, Env, Response}; -use rover::error::{ContractError, ContractResult}; +use mars_rover::error::{ContractError, ContractResult}; use crate::state::SWAPPER; use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance, update_balance_msg}; diff --git a/contracts/credit-manager/src/update_coin_balances.rs b/contracts/credit-manager/src/update_coin_balances.rs index dda7cf162..12fecb856 100644 --- a/contracts/credit-manager/src/update_coin_balances.rs +++ b/contracts/credit-manager/src/update_coin_balances.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{ StdResult, }; -use rover::error::ContractResult; +use mars_rover::error::ContractResult; use crate::utils::{decrement_coin_balance, increment_coin_balance}; diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 8817027c0..13c685913 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -6,11 +6,11 @@ use cosmwasm_std::{ WasmMsg, }; -use rover::error::{ContractError, ContractResult}; -use rover::msg::execute::CallbackMsg; -use rover::msg::query::CoinValue; -use rover::msg::ExecuteMsg; -use rover::traits::IntoDecimal; +use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::msg::execute::CallbackMsg; +use mars_rover::msg::query::CoinValue; +use mars_rover::msg::ExecuteMsg; +use mars_rover::traits::IntoDecimal; use crate::state::{ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES}; use crate::update_coin_balances::query_balance; diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index 893bf25e9..4c549ed84 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -3,10 +3,10 @@ use cosmwasm_std::{ Uint128, WasmMsg, }; -use rover::adapters::vault::{UpdateType, Vault, VaultPositionUpdate}; -use rover::error::{ContractError, ContractResult}; -use rover::msg::execute::CallbackMsg; -use rover::msg::ExecuteMsg; +use mars_rover::adapters::vault::{UpdateType, Vault, VaultPositionUpdate}; +use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::msg::execute::CallbackMsg; +use mars_rover::msg::ExecuteMsg; use crate::state::{COIN_BALANCES, ORACLE, VAULT_CONFIGS}; use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance}; diff --git a/contracts/credit-manager/src/vault/exit.rs b/contracts/credit-manager/src/vault/exit.rs index 04199d7c9..356b733f3 100644 --- a/contracts/credit-manager/src/vault/exit.rs +++ b/contracts/credit-manager/src/vault/exit.rs @@ -1,9 +1,9 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, Uint128, WasmMsg}; -use rover::adapters::vault::{UpdateType, Vault, VaultPositionUpdate}; -use rover::error::ContractResult; -use rover::msg::execute::CallbackMsg; -use rover::msg::ExecuteMsg as RoverExecuteMsg; +use mars_rover::adapters::vault::{UpdateType, Vault, VaultPositionUpdate}; +use mars_rover::error::ContractResult; +use mars_rover::msg::execute::CallbackMsg; +use mars_rover::msg::ExecuteMsg as RoverExecuteMsg; use crate::vault::utils::{ assert_vault_is_whitelisted, query_withdraw_denom_balance, update_vault_position, diff --git a/contracts/credit-manager/src/vault/exit_unlocked.rs b/contracts/credit-manager/src/vault/exit_unlocked.rs index 7fb8cdb62..4db7ca143 100644 --- a/contracts/credit-manager/src/vault/exit_unlocked.rs +++ b/contracts/credit-manager/src/vault/exit_unlocked.rs @@ -1,10 +1,10 @@ use cosmos_vault_standard::extensions::lockup::Lockup; use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, WasmMsg}; -use rover::adapters::vault::{UnlockingChange, Vault, VaultPositionUpdate}; -use rover::error::{ContractError, ContractResult}; -use rover::msg::execute::CallbackMsg; -use rover::msg::ExecuteMsg; +use mars_rover::adapters::vault::{UnlockingChange, Vault, VaultPositionUpdate}; +use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::msg::execute::CallbackMsg; +use mars_rover::msg::ExecuteMsg; use crate::state::VAULT_POSITIONS; use crate::vault::utils::{ diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index d3b666c23..25599f9ed 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -2,11 +2,11 @@ use std::cmp::min; use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; -use rover::adapters::vault::{ +use mars_rover::adapters::vault::{ UnlockingChange, UnlockingPositions, UpdateType, Vault, VaultPositionAmount, VaultPositionUpdate, }; -use rover::error::ContractResult; +use mars_rover::error::ContractResult; use crate::liquidate_coin::{calculate_liquidation, repay_debt}; use crate::state::VAULT_POSITIONS; diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index 5ce34d858..999ea2487 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -2,11 +2,11 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, DepsMut, Reply, Response, Uint128}; use crate::state::VAULT_REQUEST_TEMP_STORAGE; -use rover::adapters::vault::{ +use mars_rover::adapters::vault::{ UnlockingChange, UpdateType, Vault, VaultBase, VaultPositionUpdate, VaultUnlockingPosition, }; -use rover::error::{ContractError, ContractResult}; -use rover::extensions::AttrParse; +use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::extensions::AttrParse; use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 9a786e68b..359bfb7cc 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Addr, Coin, Deps, StdResult, Storage}; -use rover::adapters::vault::{Vault, VaultPositionAmount, VaultPositionUpdate}; -use rover::error::{ContractError, ContractResult}; +use mars_rover::adapters::vault::{Vault, VaultPositionAmount, VaultPositionUpdate}; +use mars_rover::error::{ContractError, ContractResult}; use crate::state::{VAULT_CONFIGS, VAULT_POSITIONS}; use crate::update_coin_balances::query_balance; diff --git a/contracts/credit-manager/src/withdraw.rs b/contracts/credit-manager/src/withdraw.rs index 329ad17ea..f1368b8b1 100644 --- a/contracts/credit-manager/src/withdraw.rs +++ b/contracts/credit-manager/src/withdraw.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, DepsMut, Response}; -use rover::error::{ContractError, ContractResult}; +use mars_rover::error::{ContractError, ContractResult}; use crate::utils::{assert_coin_is_whitelisted, decrement_coin_balance}; diff --git a/contracts/credit-manager/src/zap.rs b/contracts/credit-manager/src/zap.rs index 212902574..c6b9c8d78 100644 --- a/contracts/credit-manager/src/zap.rs +++ b/contracts/credit-manager/src/zap.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; -use rover::error::ContractResult; -use rover::traits::Denoms; +use mars_rover::error::ContractResult; +use mars_rover::traits::Denoms; use crate::state::ZAPPER; use crate::utils::{ diff --git a/contracts/credit-manager/tests/helpers/assertions.rs b/contracts/credit-manager/tests/helpers/assertions.rs index f7568383a..2d2a1a424 100644 --- a/contracts/credit-manager/tests/helpers/assertions.rs +++ b/contracts/credit-manager/tests/helpers/assertions.rs @@ -1,9 +1,9 @@ use anyhow::Result as AnyResult; -use credit_manager::utils::contents_equal; use cw_multi_test::AppResponse; +use mars_credit_manager::utils::contents_equal; use std::hash::Hash; -use rover::error::ContractError; +use mars_rover::error::ContractError; pub fn assert_err(res: AnyResult, err: ContractError) { match res { diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index 429bbd247..8c4e33e0d 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{coin, Decimal}; use cw_utils::Duration; -use rover::traits::IntoDecimal; +use mars_rover::traits::IntoDecimal; use crate::helpers::{lp_token_info, CoinInfo, VaultTestInfo}; diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index 6554997f7..cdce025e7 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -7,37 +7,37 @@ pub fn mock_app() -> App { pub fn mock_rover_contract() -> Box> { let contract = ContractWrapper::new( - credit_manager::contract::execute, - credit_manager::contract::instantiate, - credit_manager::contract::query, + mars_credit_manager::contract::execute, + mars_credit_manager::contract::instantiate, + mars_credit_manager::contract::query, ) - .with_reply(credit_manager::contract::reply); + .with_reply(mars_credit_manager::contract::reply); Box::new(contract) } pub fn mock_account_nft_contract() -> Box> { let contract = ContractWrapper::new( - account_nft::contract::execute, - account_nft::contract::instantiate, - account_nft::contract::query, + mars_account_nft::contract::execute, + mars_account_nft::contract::instantiate, + mars_account_nft::contract::query, ); Box::new(contract) } pub fn mock_red_bank_contract() -> Box> { let contract = ContractWrapper::new( - mock_red_bank::contract::execute, - mock_red_bank::contract::instantiate, - mock_red_bank::contract::query, + mars_mock_red_bank::contract::execute, + mars_mock_red_bank::contract::instantiate, + mars_mock_red_bank::contract::query, ); Box::new(contract) } pub fn mock_oracle_contract() -> Box> { let contract = ContractWrapper::new( - mock_oracle::contract::execute, - mock_oracle::contract::instantiate, - mock_oracle::contract::query, + mars_mock_oracle::contract::execute, + mars_mock_oracle::contract::instantiate, + mars_mock_oracle::contract::query, ); Box::new(contract) } @@ -53,27 +53,27 @@ pub fn mock_oracle_adapter_contract() -> Box> { pub fn mock_vault_contract() -> Box> { let contract = ContractWrapper::new( - mock_vault::contract::execute, - mock_vault::contract::instantiate, - mock_vault::contract::query, + mars_mock_vault::contract::execute, + mars_mock_vault::contract::instantiate, + mars_mock_vault::contract::query, ); Box::new(contract) } pub fn mock_swapper_contract() -> Box> { let contract = ContractWrapper::new( - swapper_mock::contract::execute, - swapper_mock::contract::instantiate, - swapper_mock::contract::query, + mars_swapper_mock::contract::execute, + mars_swapper_mock::contract::instantiate, + mars_swapper_mock::contract::query, ); Box::new(contract) } pub fn mock_zapper_contract() -> Box> { let contract = ContractWrapper::new( - mock_zapper::contract::execute, - mock_zapper::contract::instantiate, - mock_zapper::contract::query, + mars_mock_zapper::contract::execute, + mars_mock_zapper::contract::instantiate, + mars_mock_zapper::contract::query, ); Box::new(contract) } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 3603cd310..25480fd05 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -10,33 +10,33 @@ use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; -use account_nft::msg::ExecuteMsg as NftExecuteMsg; +use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; +use mars_mock_oracle::msg::{ + CoinPrice, ExecuteMsg as OracleExecuteMsg, InstantiateMsg as OracleInstantiateMsg, +}; +use mars_mock_red_bank::msg::{CoinMarketInfo, InstantiateMsg as RedBankInstantiateMsg}; +use mars_mock_vault::contract::DEFAULT_VAULT_TOKEN_PREFUND; +use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; use mars_oracle_adapter::msg::{ InstantiateMsg as OracleAdapterInstantiateMsg, PricingMethod, VaultPricingInfo, }; use mars_outpost::red_bank::QueryMsg::UserDebt; use mars_outpost::red_bank::UserDebtResponse; -use mock_oracle::msg::{ - CoinPrice, ExecuteMsg as OracleExecuteMsg, InstantiateMsg as OracleInstantiateMsg, -}; -use mock_red_bank::msg::{CoinMarketInfo, InstantiateMsg as RedBankInstantiateMsg}; -use mock_vault::contract::DEFAULT_VAULT_TOKEN_PREFUND; -use mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; -use rover::adapters::swap::QueryMsg::EstimateExactInSwap; -use rover::adapters::swap::{ +use mars_rover::adapters::swap::QueryMsg::EstimateExactInSwap; +use mars_rover::adapters::swap::{ EstimateExactInSwapResponse, InstantiateMsg as SwapperInstantiateMsg, Swapper, SwapperBase, }; -use rover::adapters::vault::{VaultBase, VaultConfig, VaultUnchecked}; -use rover::adapters::{OracleBase, OracleUnchecked, RedBankBase, Zapper, ZapperBase}; -use rover::msg::execute::{Action, CallbackMsg}; -use rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; -use rover::msg::query::{ +use mars_rover::adapters::vault::{VaultBase, VaultConfig, VaultUnchecked}; +use mars_rover::adapters::{OracleBase, OracleUnchecked, RedBankBase, Zapper, ZapperBase}; +use mars_rover::msg::execute::{Action, CallbackMsg}; +use mars_rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; +use mars_rover::msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtShares, HealthResponse, Positions, SharesResponseItem, VaultPositionResponseItem, VaultWithBalance, }; -use rover::msg::zapper::QueryMsg::EstimateProvideLiquidity; -use rover::msg::zapper::{InstantiateMsg as ZapperInstantiateMsg, LpConfig}; -use rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_rover::msg::zapper::QueryMsg::EstimateProvideLiquidity; +use mars_rover::msg::zapper::{InstantiateMsg as ZapperInstantiateMsg, LpConfig}; +use mars_rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::helpers::{ lp_token_info, mock_account_nft_contract, mock_oracle_adapter_contract, mock_oracle_contract, diff --git a/contracts/credit-manager/tests/helpers/utils.rs b/contracts/credit-manager/tests/helpers/utils.rs index adb3a1bdb..b704ccd6f 100644 --- a/contracts/credit-manager/tests/helpers/utils.rs +++ b/contracts/credit-manager/tests/helpers/utils.rs @@ -1,5 +1,5 @@ use cosmwasm_std::Coin; -use rover::msg::query::DebtAmount; +use mars_rover::msg::query::DebtAmount; pub fn get_coin(denom: &str, coins: &[Coin]) -> Coin { coins.iter().find(|cv| cv.denom == denom).unwrap().clone() diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index 36944c390..780e0978e 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -2,9 +2,9 @@ use std::ops::{Mul, Sub}; use cosmwasm_std::{coin, coins, Addr, Uint128}; -use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; -use rover::error::ContractError; -use rover::msg::execute::Action::{Borrow, Deposit}; +use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; +use mars_rover::error::ContractError; +use mars_rover::msg::execute::Action::{Borrow, Deposit}; use crate::helpers::{ assert_err, uosmo_info, AccountToFund, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, diff --git a/contracts/credit-manager/tests/test_coin_balances.rs b/contracts/credit-manager/tests/test_coin_balances.rs index 9c802b5dc..eab54fac9 100644 --- a/contracts/credit-manager/tests/test_coin_balances.rs +++ b/contracts/credit-manager/tests/test_coin_balances.rs @@ -2,9 +2,9 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::{coin, coins, Addr, OverflowError}; use cw_multi_test::{BankSudo, SudoMsg}; -use rover::error::ContractError; -use rover::msg::execute::Action::Deposit; -use rover::msg::execute::CallbackMsg; +use mars_rover::error::ContractError; +use mars_rover::msg::execute::Action::Deposit; +use mars_rover::msg::execute::CallbackMsg; use crate::helpers::{assert_err, uosmo_info, AccountToFund, MockEnv}; diff --git a/contracts/credit-manager/tests/test_deposit.rs b/contracts/credit-manager/tests/test_deposit.rs index bd55cc9f4..d01084c4a 100644 --- a/contracts/credit-manager/tests/test_deposit.rs +++ b/contracts/credit-manager/tests/test_deposit.rs @@ -1,11 +1,11 @@ use cosmwasm_std::{coin, coins, Addr, Coin, Uint128}; -use rover::coins::Coins; -use rover::error::ContractError::{ +use mars_rover::coins::Coins; +use mars_rover::error::ContractError::{ ExtraFundsReceived, FundsMismatch, NotTokenOwner, NotWhitelisted, }; -use rover::msg::execute::Action; -use rover::msg::query::Positions; +use mars_rover::msg::execute::Action; +use mars_rover::msg::query::Positions; use crate::helpers::{ assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv, diff --git a/contracts/credit-manager/tests/test_dispatch.rs b/contracts/credit-manager/tests/test_dispatch.rs index 373b6903e..088187db4 100644 --- a/contracts/credit-manager/tests/test_dispatch.rs +++ b/contracts/credit-manager/tests/test_dispatch.rs @@ -1,9 +1,9 @@ use cosmwasm_std::{coin, Addr}; use helpers::assert_err; -use rover::error::ContractError; -use rover::error::ContractError::NotTokenOwner; -use rover::msg::execute::CallbackMsg; +use mars_rover::error::ContractError; +use mars_rover::error::ContractError::NotTokenOwner; +use mars_rover::msg::execute::CallbackMsg; use crate::helpers::MockEnv; diff --git a/contracts/credit-manager/tests/test_enumerate_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs index 0fe47021b..4fac0a75a 100644 --- a/contracts/credit-manager/tests/test_enumerate_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{coin, Addr, Uint128}; -use rover::msg::execute::Action; -use rover::msg::query::CoinBalanceResponseItem; +use mars_rover::msg::execute::Action; +use mars_rover::msg::query::CoinBalanceResponseItem; use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; diff --git a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs index 576d6af3f..b458c15db 100644 --- a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs @@ -1,8 +1,8 @@ use cosmwasm_std::{coin, Addr}; -use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; -use rover::msg::execute::Action; -use rover::msg::query::SharesResponseItem; +use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; +use mars_rover::msg::execute::Action; +use mars_rover::msg::query::SharesResponseItem; use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; diff --git a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs index 34f860bce..74adfa163 100644 --- a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs @@ -1,8 +1,8 @@ use cosmwasm_std::{coin, Addr}; -use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; -use rover::msg::execute::Action; -use rover::msg::query::DebtShares; +use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; +use mars_rover::msg::execute::Action; +use mars_rover::msg::query::DebtShares; use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index 69d2292eb..ca33ab95f 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Addr, Uint128}; -use rover::msg::execute::Action; +use mars_rover::msg::execute::Action; use crate::helpers::{ assert_contents_equal, build_mock_vaults, lp_token_info, AccountToFund, MockEnv, diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index 9eef05024..79ac44fb6 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Addr, Uint128}; use itertools::Itertools; -use rover::msg::execute::Action; +use mars_rover::msg::execute::Action; use crate::helpers::{ assert_contents_equal, build_mock_vaults, lp_token_info, AccountToFund, MockEnv, diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index fb0082818..bd75c1bad 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -2,12 +2,12 @@ use std::ops::{Add, Div, Mul}; use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128}; -use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; -use mock_oracle::msg::CoinPrice; -use rover::error::ContractError; -use rover::msg::execute::Action::{Borrow, Deposit}; -use rover::msg::query::DebtAmount; -use rover::traits::IntoDecimal; +use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; +use mars_mock_oracle::msg::CoinPrice; +use mars_rover::error::ContractError; +use mars_rover::msg::execute::Action::{Borrow, Deposit}; +use mars_rover::msg::query::DebtAmount; +use mars_rover::traits::IntoDecimal; use crate::helpers::{assert_err, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv}; diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 8947c18a5..8722a3a04 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -1,10 +1,10 @@ use cosmwasm_std::{coins, Addr, Coin, Decimal, OverflowError, OverflowOperation, Uint128}; -use mock_oracle::msg::CoinPrice; -use rover::error::ContractError; -use rover::error::ContractError::{AboveMaxLTV, NotLiquidatable}; -use rover::msg::execute::Action::{Borrow, Deposit, EnterVault, LiquidateCoin}; -use rover::traits::IntoDecimal; +use mars_mock_oracle::msg::CoinPrice; +use mars_rover::error::ContractError; +use mars_rover::error::ContractError::{AboveMaxLTV, NotLiquidatable}; +use mars_rover::msg::execute::Action::{Borrow, Deposit, EnterVault, LiquidateCoin}; +use mars_rover::traits::IntoDecimal; use crate::helpers::{ assert_err, get_coin, get_debt, lp_token_info, uatom_info, ujake_info, unlocked_vault_info, diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 3d7acfad2..ff0c11e13 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -2,13 +2,13 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::StdError::NotFound; use cosmwasm_std::{Addr, Decimal, OverflowError, Uint128}; -use mock_oracle::msg::CoinPrice; -use rover::adapters::vault::VaultBase; -use rover::error::ContractError; -use rover::msg::execute::Action::{ +use mars_mock_oracle::msg::CoinPrice; +use mars_rover::adapters::vault::VaultBase; +use mars_rover::error::ContractError; +use mars_rover::msg::execute::Action::{ Borrow, Deposit, EnterVault, LiquidateVault, RequestVaultUnlock, }; -use rover::traits::IntoDecimal; +use mars_rover::traits::IntoDecimal; use crate::helpers::{ assert_err, get_coin, get_debt, locked_vault_info, lp_token_info, uatom_info, ujake_info, @@ -64,7 +64,7 @@ fn test_liquidatee_must_have_the_request_vault_position() { assert_err( res, ContractError::Std(NotFound { - kind: "rover::adapters::vault::amount::VaultPositionAmount".to_string(), + kind: "mars_rover::adapters::vault::amount::VaultPositionAmount".to_string(), }), ) } diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index ce918ae9e..e81bfee2e 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -2,10 +2,10 @@ use std::ops::{Add, Mul, Sub}; use cosmwasm_std::{coin, coins, Addr, Decimal, OverflowError, OverflowOperation, Uint128}; -use credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; -use rover::error::ContractError; -use rover::msg::execute::Action::{Borrow, Deposit, Repay, Withdraw}; -use rover::traits::IntoDecimal; +use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; +use mars_rover::error::ContractError; +use mars_rover::msg::execute::Action::{Borrow, Deposit, Repay, Withdraw}; +use mars_rover::traits::IntoDecimal; use crate::helpers::{ assert_err, uosmo_info, AccountToFund, CoinInfo, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, diff --git a/contracts/credit-manager/tests/test_swap.rs b/contracts/credit-manager/tests/test_swap.rs index f1d0dc015..9b9df9cff 100644 --- a/contracts/credit-manager/tests/test_swap.rs +++ b/contracts/credit-manager/tests/test_swap.rs @@ -1,9 +1,9 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::{coin, coins, Addr, Coin, Decimal, OverflowError, Uint128}; -use rover::error::ContractError; -use rover::msg::execute::Action::{Deposit, SwapExactIn}; -use swapper_mock::contract::MOCK_SWAP_RESULT; +use mars_rover::error::ContractError; +use mars_rover::msg::execute::Action::{Deposit, SwapExactIn}; +use mars_swapper_mock::contract::MOCK_SWAP_RESULT; use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv}; diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 266928f39..cd59d8d98 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -1,10 +1,10 @@ use cosmwasm_std::{coin, Addr, Decimal}; -use rover::adapters::swap::SwapperBase; -use rover::adapters::vault::{VaultBase, VaultConfig}; -use rover::adapters::{OracleBase, RedBankBase, ZapperBase}; -use rover::error::ContractError; -use rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; +use mars_rover::adapters::swap::SwapperBase; +use mars_rover::adapters::vault::{VaultBase, VaultConfig}; +use mars_rover::adapters::{OracleBase, RedBankBase, ZapperBase}; +use mars_rover::error::ContractError; +use mars_rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; use crate::helpers::{assert_err, locked_vault_info, uatom_info, uosmo_info, MockEnv}; diff --git a/contracts/credit-manager/tests/test_vault_enter.rs b/contracts/credit-manager/tests/test_vault_enter.rs index 4aeb25c8b..b541ceda8 100644 --- a/contracts/credit-manager/tests/test_vault_enter.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -2,10 +2,10 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::StdError::NotFound; use cosmwasm_std::{Addr, OverflowError, Uint128}; -use mock_vault::contract::STARTING_VAULT_SHARES; -use rover::adapters::vault::VaultBase; -use rover::error::ContractError; -use rover::msg::execute::Action::{Deposit, EnterVault}; +use mars_mock_vault::contract::STARTING_VAULT_SHARES; +use mars_rover::adapters::vault::VaultBase; +use mars_rover::error::ContractError; +use mars_rover::msg::execute::Action::{Deposit, EnterVault}; use crate::helpers::{ assert_err, locked_vault_info, lp_token_info, uatom_info, unlocked_vault_info, uosmo_info, diff --git a/contracts/credit-manager/tests/test_vault_exit.rs b/contracts/credit-manager/tests/test_vault_exit.rs index 7a03d1b0e..8966db3e5 100644 --- a/contracts/credit-manager/tests/test_vault_exit.rs +++ b/contracts/credit-manager/tests/test_vault_exit.rs @@ -1,12 +1,12 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::{coin, Addr, Coin, OverflowError, Uint128}; -use mock_vault::contract::STARTING_VAULT_SHARES; -use rover::adapters::vault::VaultBase; -use rover::error::ContractError; -use rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; -use rover::msg::execute::Action::{Deposit, EnterVault, ExitVault}; -use rover::msg::execute::CallbackMsg; +use mars_mock_vault::contract::STARTING_VAULT_SHARES; +use mars_rover::adapters::vault::VaultBase; +use mars_rover::error::ContractError; +use mars_rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; +use mars_rover::msg::execute::Action::{Deposit, EnterVault, ExitVault}; +use mars_rover::msg::execute::CallbackMsg; use crate::helpers::{ assert_err, locked_vault_info, lp_token_info, uatom_info, unlocked_vault_info, uosmo_info, diff --git a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs index e98565140..b89b0a9b4 100644 --- a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs +++ b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs @@ -1,11 +1,13 @@ use cosmwasm_std::{Addr, Uint128}; use cw_utils::Duration; -use mock_vault::contract::STARTING_VAULT_SHARES; -use rover::adapters::vault::VaultUnchecked; -use rover::error::ContractError; -use rover::msg::execute::Action::{Deposit, EnterVault, ExitVaultUnlocked, RequestVaultUnlock}; -use rover::msg::query::Positions; +use mars_mock_vault::contract::STARTING_VAULT_SHARES; +use mars_rover::adapters::vault::VaultUnchecked; +use mars_rover::error::ContractError; +use mars_rover::msg::execute::Action::{ + Deposit, EnterVault, ExitVaultUnlocked, RequestVaultUnlock, +}; +use mars_rover::msg::query::Positions; use crate::helpers::{ assert_err, generate_mock_vault, get_coin, locked_vault_info, lp_token_info, AccountToFund, diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs index 32eca0b10..444530ddd 100644 --- a/contracts/credit-manager/tests/test_vault_request_unlock.rs +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -3,10 +3,10 @@ use cosmwasm_std::{coins, Addr, OverflowError, Uint128}; use cw_multi_test::{BankSudo, SudoMsg}; use cw_utils::{Duration, Expiration}; -use mock_vault::contract::STARTING_VAULT_SHARES; -use rover::adapters::vault::VaultUnchecked; -use rover::error::ContractError; -use rover::msg::execute::Action::{Deposit, EnterVault, RequestVaultUnlock}; +use mars_mock_vault::contract::STARTING_VAULT_SHARES; +use mars_rover::adapters::vault::VaultUnchecked; +use mars_rover::error::ContractError; +use mars_rover::msg::execute::Action::{Deposit, EnterVault, RequestVaultUnlock}; use crate::helpers::{ assert_err, locked_vault_info, lp_token_info, unlocked_vault_info, AccountToFund, MockEnv, diff --git a/contracts/credit-manager/tests/test_withdraw.rs b/contracts/credit-manager/tests/test_withdraw.rs index 739d32012..a674c0a62 100644 --- a/contracts/credit-manager/tests/test_withdraw.rs +++ b/contracts/credit-manager/tests/test_withdraw.rs @@ -1,9 +1,9 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::{coin, coins, Addr, Coin, OverflowError, Uint128}; -use rover::error::ContractError; -use rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; -use rover::msg::execute::Action; +use mars_rover::error::ContractError; +use mars_rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; +use mars_rover::msg::execute::Action; use crate::helpers::{assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv}; diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs index bf2fda504..63e8d60b5 100644 --- a/contracts/credit-manager/tests/test_zap_provide.rs +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -1,10 +1,10 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::{Addr, OverflowError, Uint128}; -use mock_zapper::contract::STARTING_LP_POOL_TOKENS; +use mars_mock_zapper::contract::STARTING_LP_POOL_TOKENS; -use mock_zapper::error::ContractError; -use rover::error::ContractError as RoverError; -use rover::msg::execute::Action::{Deposit, ProvideLiquidity}; +use mars_mock_zapper::error::ContractError; +use mars_rover::error::ContractError as RoverError; +use mars_rover::msg::execute::Action::{Deposit, ProvideLiquidity}; use crate::helpers::{ assert_err, get_coin, lp_token_info, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index 2be7e0e33..1a3e45187 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -1,10 +1,10 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::{Addr, OverflowError, Uint128}; -use mock_zapper::contract::STARTING_LP_POOL_TOKENS; -use rover::error::ContractError as RoverError; -use rover::msg::execute::Action::{Deposit, ProvideLiquidity, WithdrawLiquidity}; -use rover::msg::instantiate::ConfigUpdates; +use mars_mock_zapper::contract::STARTING_LP_POOL_TOKENS; +use mars_rover::error::ContractError as RoverError; +use mars_rover::msg::execute::Action::{Deposit, ProvideLiquidity, WithdrawLiquidity}; +use mars_rover::msg::instantiate::ConfigUpdates; use crate::helpers::{ assert_err, get_coin, lp_token_info, uatom_info, uosmo_info, AccountToFund, MockEnv, diff --git a/contracts/mock-oracle/Cargo.toml b/contracts/mock-oracle/Cargo.toml index b44ba3630..8364044a4 100644 --- a/contracts/mock-oracle/Cargo.toml +++ b/contracts/mock-oracle/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mock-oracle" +name = "mars-mock-oracle" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } diff --git a/contracts/mock-oracle/examples/schema.rs b/contracts/mock-oracle/examples/schema.rs index 75fb53b14..ff9ba2b24 100644 --- a/contracts/mock-oracle/examples/schema.rs +++ b/contracts/mock-oracle/examples/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use mock_oracle::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_mock_oracle::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index 46e0deba2..3ad43dc94 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mock-red-bank" +name = "mars-mock-red-bank" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } diff --git a/contracts/mock-red-bank/examples/schema.rs b/contracts/mock-red-bank/examples/schema.rs index 6312801fb..dbd1c8fb4 100644 --- a/contracts/mock-red-bank/examples/schema.rs +++ b/contracts/mock-red-bank/examples/schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; +use mars_mock_red_bank::msg::InstantiateMsg; use mars_outpost::red_bank::{ExecuteMsg, QueryMsg}; -use mock_red_bank::msg::InstantiateMsg; fn main() { write_api! { diff --git a/contracts/mock-vault/Cargo.toml b/contracts/mock-vault/Cargo.toml index a2928c588..bb802cbf3 100644 --- a/contracts/mock-vault/Cargo.toml +++ b/contracts/mock-vault/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mock-vault" +name = "mars-mock-vault" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } @@ -23,5 +23,5 @@ cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } +mars-rover = { workspace = true } thiserror = { workspace = true } -rover = { workspace = true } diff --git a/contracts/mock-vault/examples/schema.rs b/contracts/mock-vault/examples/schema.rs index 44d726d9e..f2526f54d 100644 --- a/contracts/mock-vault/examples/schema.rs +++ b/contracts/mock-vault/examples/schema.rs @@ -1,6 +1,6 @@ use cosmos_vault_standard::msg::{ExecuteMsg, QueryMsg}; use cosmwasm_schema::write_api; -use mock_vault::msg::InstantiateMsg; +use mars_mock_vault::msg::InstantiateMsg; fn main() { write_api! { diff --git a/contracts/mock-vault/src/error.rs b/contracts/mock-vault/src/error.rs index b67b90201..f8808ae97 100644 --- a/contracts/mock-vault/src/error.rs +++ b/contracts/mock-vault/src/error.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{CheckedMultiplyRatioError, StdError}; -use rover::error::ContractError as RoverError; +use mars_rover::error::ContractError as RoverError; use thiserror::Error; pub type ContractResult = Result; diff --git a/contracts/mock-vault/src/msg.rs b/contracts/mock-vault/src/msg.rs index 4ec3153fd..f0ae16005 100644 --- a/contracts/mock-vault/src/msg.rs +++ b/contracts/mock-vault/src/msg.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::cw_serde; use cw_utils::Duration; -use rover::adapters::OracleUnchecked; +use mars_rover::adapters::OracleUnchecked; // Remaining messages in cosmos-vault-standard #[cw_serde] diff --git a/contracts/mock-vault/src/state.rs b/contracts/mock-vault/src/state.rs index 1125fc009..6b777f23b 100644 --- a/contracts/mock-vault/src/state.rs +++ b/contracts/mock-vault/src/state.rs @@ -3,7 +3,7 @@ use cw_storage_plus::{Item, Map}; use cw_utils::Duration; use cosmos_vault_standard::extensions::lockup::Lockup; -use rover::adapters::Oracle; +use mars_rover::adapters::Oracle; pub const VAULT_TOKEN_DENOM: Item = Item::new("vault_token_denom"); pub const TOTAL_VAULT_SHARES: Item = Item::new("total_vault_shares"); diff --git a/contracts/mock-zapper/Cargo.toml b/contracts/mock-zapper/Cargo.toml index 28dea6515..b68e4d74b 100644 --- a/contracts/mock-zapper/Cargo.toml +++ b/contracts/mock-zapper/Cargo.toml @@ -1,23 +1,26 @@ [package] -name = "mock-zapper" -version = "1.0.0" -authors = ["grod220 "] -edition = "2021" -license = "GPL-3.0-or-later" -repository = "https://github.com/mars-protocol/rover" +name = "mars-mock-zapper" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } [lib] crate-type = ["cdylib", "rlib"] [features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -library = [] +library = [] [dependencies] -rover = { version = "1.0", path = "../../packages/rover" } - -cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" -cw-storage-plus = "0.16" -cw-utils = "0.16" -thiserror = "1.0" +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +mars-rover = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/mock-zapper/examples/schema.rs b/contracts/mock-zapper/examples/schema.rs index 59c9ec477..51f782654 100644 --- a/contracts/mock-zapper/examples/schema.rs +++ b/contracts/mock-zapper/examples/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use rover::msg::zapper::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_rover::msg::zapper::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/mock-zapper/src/contract.rs b/contracts/mock-zapper/src/contract.rs index dfee12207..92fc7d88d 100644 --- a/contracts/mock-zapper/src/contract.rs +++ b/contracts/mock-zapper/src/contract.rs @@ -6,7 +6,7 @@ use crate::error::ContractResult; use crate::execute::{provide_liquidity, withdraw_liquidity}; use crate::query::{estimate_provide_liquidity, estimate_withdraw_liquidity}; use crate::state::{COIN_BALANCES, ORACLE}; -use rover::msg::zapper::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_rover::msg::zapper::{ExecuteMsg, InstantiateMsg, QueryMsg}; pub const STARTING_LP_POOL_TOKENS: Uint128 = Uint128::new(1_000_000); diff --git a/contracts/mock-zapper/src/error.rs b/contracts/mock-zapper/src/error.rs index c21aeec6a..f329fc1e7 100644 --- a/contracts/mock-zapper/src/error.rs +++ b/contracts/mock-zapper/src/error.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{CheckedMultiplyRatioError, StdError}; -use rover::error::ContractError as RoverError; +use mars_rover::error::ContractError as RoverError; use thiserror::Error; pub type ContractResult = Result; diff --git a/contracts/mock-zapper/src/state.rs b/contracts/mock-zapper/src/state.rs index 73c4ad5de..706ef6e5a 100644 --- a/contracts/mock-zapper/src/state.rs +++ b/contracts/mock-zapper/src/state.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Coin, Uint128}; use cw_storage_plus::{Item, Map}; -use rover::adapters::Oracle; +use mars_rover::adapters::Oracle; pub const ORACLE: Item = Item::new("oracle"); diff --git a/contracts/mars-oracle-adapter/Cargo.toml b/contracts/oracle-adapter/Cargo.toml similarity index 72% rename from contracts/mars-oracle-adapter/Cargo.toml rename to contracts/oracle-adapter/Cargo.toml index 80c04310a..e9bd37ffa 100644 --- a/contracts/mars-oracle-adapter/Cargo.toml +++ b/contracts/oracle-adapter/Cargo.toml @@ -20,14 +20,15 @@ library = [] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw2 = { workspace = true } cw-storage-plus = { workspace = true } mars-outpost = { workspace = true } -rover = { workspace = true } +mars-rover = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -anyhow = { workspace = true } -cw-multi-test = { workspace = true } -cw-utils = { workspace = true } -mock-oracle = { workspace = true } -mock-vault = { workspace = true } +anyhow = { workspace = true } +cw-multi-test = { workspace = true } +cw-utils = { workspace = true } +mars-mock-oracle = { workspace = true } +mars-mock-vault = { workspace = true } diff --git a/contracts/mars-oracle-adapter/examples/schema.rs b/contracts/oracle-adapter/examples/schema.rs similarity index 100% rename from contracts/mars-oracle-adapter/examples/schema.rs rename to contracts/oracle-adapter/examples/schema.rs diff --git a/contracts/mars-oracle-adapter/src/contract.rs b/contracts/oracle-adapter/src/contract.rs similarity index 94% rename from contracts/mars-oracle-adapter/src/contract.rs rename to contracts/oracle-adapter/src/contract.rs index 17f992d5b..697c2397b 100644 --- a/contracts/mars-oracle-adapter/src/contract.rs +++ b/contracts/oracle-adapter/src/contract.rs @@ -4,12 +4,13 @@ use cosmwasm_std::{ to_binary, Addr, Binary, Coin, Decimal, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, }; +use cw2::set_contract_version; use cw_storage_plus::Bound; use mars_outpost::oracle::PriceResponse; -use rover::adapters::vault::VaultBase; -use rover::adapters::Oracle; -use rover::traits::IntoDecimal; +use mars_rover::adapters::vault::VaultBase; +use mars_rover::adapters::Oracle; +use mars_rover::traits::IntoDecimal; use crate::error::{ContractError, ContractResult}; use crate::msg::{ @@ -18,6 +19,9 @@ use crate::msg::{ }; use crate::state::{ORACLE, OWNER, VAULT_PRICING_INFO}; +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; @@ -28,6 +32,12 @@ pub fn instantiate( _info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { + set_contract_version( + deps.storage, + &format!("crates.io:{}", CONTRACT_NAME), + CONTRACT_VERSION, + )?; + let owner = deps.api.addr_validate(&msg.owner)?; OWNER.save(deps.storage, &owner)?; diff --git a/contracts/mars-oracle-adapter/src/error.rs b/contracts/oracle-adapter/src/error.rs similarity index 92% rename from contracts/mars-oracle-adapter/src/error.rs rename to contracts/oracle-adapter/src/error.rs index 172f63dff..9ae38f531 100644 --- a/contracts/mars-oracle-adapter/src/error.rs +++ b/contracts/oracle-adapter/src/error.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{CheckedFromRatioError, DecimalRangeExceeded, OverflowError, StdError}; use thiserror::Error; -use rover::error::ContractError as RoverError; +use mars_rover::error::ContractError as RoverError; pub type ContractResult = Result; diff --git a/contracts/mars-oracle-adapter/src/lib.rs b/contracts/oracle-adapter/src/lib.rs similarity index 100% rename from contracts/mars-oracle-adapter/src/lib.rs rename to contracts/oracle-adapter/src/lib.rs diff --git a/contracts/mars-oracle-adapter/src/msg.rs b/contracts/oracle-adapter/src/msg.rs similarity index 96% rename from contracts/mars-oracle-adapter/src/msg.rs rename to contracts/oracle-adapter/src/msg.rs index 1721a947a..b5cd38af9 100644 --- a/contracts/mars-oracle-adapter/src/msg.rs +++ b/contracts/oracle-adapter/src/msg.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Coin, Decimal}; -use rover::adapters::{Oracle, OracleUnchecked}; +use mars_rover::adapters::{Oracle, OracleUnchecked}; #[cw_serde] pub struct CoinPrice { diff --git a/contracts/mars-oracle-adapter/src/state.rs b/contracts/oracle-adapter/src/state.rs similarity index 90% rename from contracts/mars-oracle-adapter/src/state.rs rename to contracts/oracle-adapter/src/state.rs index 4b1d25777..4508a0e40 100644 --- a/contracts/mars-oracle-adapter/src/state.rs +++ b/contracts/oracle-adapter/src/state.rs @@ -1,6 +1,6 @@ use cosmwasm_std::Addr; use cw_storage_plus::{Item, Map}; -use rover::adapters::Oracle; +use mars_rover::adapters::Oracle; use crate::msg::VaultPricingInfo; diff --git a/contracts/mars-oracle-adapter/tests/helpers.rs b/contracts/oracle-adapter/tests/helpers.rs similarity index 94% rename from contracts/mars-oracle-adapter/tests/helpers.rs rename to contracts/oracle-adapter/tests/helpers.rs index 32aa7dc3c..39273ba92 100644 --- a/contracts/mars-oracle-adapter/tests/helpers.rs +++ b/contracts/oracle-adapter/tests/helpers.rs @@ -3,20 +3,20 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{coin, Addr, Coin, Decimal}; use cw_multi_test::{AppResponse, BankSudo, BasicApp, ContractWrapper, Executor, SudoMsg}; use cw_utils::Duration; -use mars_oracle_adapter::contract::{execute, instantiate, query}; -use mars_oracle_adapter::error::ContractError; -use mars_oracle_adapter::msg::{InstantiateMsg, PricingMethod, VaultPricingInfo}; -use mock_oracle::contract::{ +use mars_mock_oracle::contract::{ execute as oracleExecute, instantiate as oracleInstantiate, query as oracleQuery, }; -use mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; -use mock_vault::contract::{ +use mars_mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; +use mars_mock_vault::contract::{ execute as vaultExecute, instantiate as vaultInstantiate, query as vaultQuery, DEFAULT_VAULT_TOKEN_PREFUND, }; -use mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; -use rover::adapters::vault::VaultBase; -use rover::adapters::{OracleBase, OracleUnchecked}; +use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; +use mars_oracle_adapter::contract::{execute, instantiate, query}; +use mars_oracle_adapter::error::ContractError; +use mars_oracle_adapter::msg::{InstantiateMsg, PricingMethod, VaultPricingInfo}; +use mars_rover::adapters::vault::VaultBase; +use mars_rover::adapters::{OracleBase, OracleUnchecked}; pub fn mock_vault_info() -> VaultTestInfo { VaultTestInfo { diff --git a/contracts/mars-oracle-adapter/tests/test_query_price.rs b/contracts/oracle-adapter/tests/test_query_price.rs similarity index 94% rename from contracts/mars-oracle-adapter/tests/test_query_price.rs rename to contracts/oracle-adapter/tests/test_query_price.rs index 9c7627485..dc6a32720 100644 --- a/contracts/mars-oracle-adapter/tests/test_query_price.rs +++ b/contracts/oracle-adapter/tests/test_query_price.rs @@ -4,9 +4,9 @@ use cosmwasm_std::{Decimal, Uint128}; use cw_multi_test::App; use mars_outpost::oracle::PriceResponse; +use mars_mock_vault::contract::STARTING_VAULT_SHARES; use mars_oracle_adapter::msg::QueryMsg; -use mock_vault::contract::STARTING_VAULT_SHARES; -use rover::traits::IntoDecimal; +use mars_rover::traits::IntoDecimal; use crate::helpers::{instantiate_oracle_adapter, mock_vault_info}; diff --git a/contracts/mars-oracle-adapter/tests/test_query_priceable_underlying.rs b/contracts/oracle-adapter/tests/test_query_priceable_underlying.rs similarity index 100% rename from contracts/mars-oracle-adapter/tests/test_query_priceable_underlying.rs rename to contracts/oracle-adapter/tests/test_query_priceable_underlying.rs diff --git a/contracts/mars-oracle-adapter/tests/test_update_config.rs b/contracts/oracle-adapter/tests/test_update_config.rs similarity index 98% rename from contracts/mars-oracle-adapter/tests/test_update_config.rs rename to contracts/oracle-adapter/tests/test_update_config.rs index 76f71623a..c24fd5023 100644 --- a/contracts/mars-oracle-adapter/tests/test_update_config.rs +++ b/contracts/oracle-adapter/tests/test_update_config.rs @@ -5,7 +5,7 @@ use mars_oracle_adapter::error::ContractError; use mars_oracle_adapter::msg::{ ConfigResponse, ConfigUpdates, ExecuteMsg, QueryMsg, VaultPricingInfo, }; -use rover::adapters::{OracleBase, OracleUnchecked}; +use mars_rover::adapters::{OracleBase, OracleUnchecked}; use crate::helpers::{assert_err, instantiate_oracle_adapter}; diff --git a/contracts/swapper/base/Cargo.toml b/contracts/swapper/base/Cargo.toml index be28cc993..b9cf528ae 100644 --- a/contracts/swapper/base/Cargo.toml +++ b/contracts/swapper/base/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "swapper-base" +name = "mars-swapper-base" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } @@ -21,7 +21,7 @@ library = [] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } +mars-rover = { workspace = true } schemars = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } -rover = { workspace = true } diff --git a/contracts/swapper/base/examples/schema.rs b/contracts/swapper/base/examples/schema.rs index 27d3aca05..2984d9d30 100644 --- a/contracts/swapper/base/examples/schema.rs +++ b/contracts/swapper/base/examples/schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; use cosmwasm_std::Empty; -use rover::adapters::swap::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_rover::adapters::swap::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/swapper/base/src/contract.rs b/contracts/swapper/base/src/contract.rs index 2e1e78752..6e2329a7f 100644 --- a/contracts/swapper/base/src/contract.rs +++ b/contracts/swapper/base/src/contract.rs @@ -6,11 +6,11 @@ use cosmwasm_std::{ }; use cw_storage_plus::{Bound, Item, Map}; -use rover::adapters::swap::{ +use mars_rover::adapters::swap::{ Config, EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg, RouteResponse, RoutesResponse, }; -use rover::error::ContractError as RoverError; +use mars_rover::error::ContractError as RoverError; use crate::{ContractResult, Route}; diff --git a/contracts/swapper/base/src/error.rs b/contracts/swapper/base/src/error.rs index bd12cc4e3..38bafe87f 100644 --- a/contracts/swapper/base/src/error.rs +++ b/contracts/swapper/base/src/error.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{CheckedMultiplyRatioError, DecimalRangeExceeded, OverflowError, StdError}; -use rover::error::ContractError as RoverError; +use mars_rover::error::ContractError as RoverError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] diff --git a/contracts/swapper/base/src/traits.rs b/contracts/swapper/base/src/traits.rs index 936fc8f65..80f7e4eb3 100644 --- a/contracts/swapper/base/src/traits.rs +++ b/contracts/swapper/base/src/traits.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{Coin, CosmosMsg, CustomMsg, CustomQuery, Decimal, Env, Querie use schemars::JsonSchema; use serde::{de::DeserializeOwned, Serialize}; -use rover::adapters::swap::EstimateExactInSwapResponse; +use mars_rover::adapters::swap::EstimateExactInSwapResponse; use crate::ContractResult; diff --git a/contracts/swapper/mock/Cargo.toml b/contracts/swapper/mock/Cargo.toml index 6cc1b5746..2abac3c99 100644 --- a/contracts/swapper/mock/Cargo.toml +++ b/contracts/swapper/mock/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "swapper-mock" +name = "mars-swapper-mock" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } @@ -18,11 +18,11 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -rover = { workspace = true } -swapper-base = { workspace = true } -thiserror = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +mars-swapper-base = { workspace = true } +mars-rover = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/swapper/mock/src/contract.rs b/contracts/swapper/mock/src/contract.rs index 9d4dc1dd5..f5ff04668 100644 --- a/contracts/swapper/mock/src/contract.rs +++ b/contracts/swapper/mock/src/contract.rs @@ -3,7 +3,9 @@ use cosmwasm_std::{ MessageInfo, Response, StdError, StdResult, Uint128, }; -use rover::adapters::swap::{EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_rover::adapters::swap::{ + EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg, +}; pub const MOCK_SWAP_RESULT: Uint128 = Uint128::new(1337); diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index eb9558fff..c851004b5 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "swapper-osmosis" +name = "mars-swapper-osmosis" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } @@ -18,15 +18,16 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -mars-osmosis = { version = "1.0.0", path = "../../../packages/chains/osmosis" } -osmosis-std = { workspace = true } -schemars = { workspace = true } -swapper-base = { workspace = true } -rover = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw-storage-plus = { workspace = true } +mars-osmosis = { version = "1.0.0", path = "../../../packages/chains/osmosis" } +mars-swapper-base = { workspace = true } +mars-rover = { workspace = true } +osmosis-std = { workspace = true } +schemars = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/swapper/osmosis/src/contract.rs b/contracts/swapper/osmosis/src/contract.rs index cba24a4f3..bb4e781f7 100644 --- a/contracts/swapper/osmosis/src/contract.rs +++ b/contracts/swapper/osmosis/src/contract.rs @@ -1,13 +1,16 @@ use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response}; - -use rover::adapters::swap::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use swapper_base::{ContractResult, SwapBase}; +use cw2::set_contract_version; +use mars_rover::adapters::swap::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_swapper_base::{ContractResult, SwapBase}; use crate::route::OsmosisRoute; /// The Osmosis swapper contract inherits logic from the base swapper contract pub type OsmosisSwap<'a> = SwapBase<'a, Empty, Empty, OsmosisRoute>; +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, @@ -15,6 +18,11 @@ pub fn instantiate( _info: MessageInfo, msg: InstantiateMsg, ) -> ContractResult { + set_contract_version( + deps.storage, + &format!("crates.io:{}", CONTRACT_NAME), + CONTRACT_VERSION, + )?; OsmosisSwap::default().instantiate(deps, msg) } diff --git a/contracts/swapper/osmosis/src/route.rs b/contracts/swapper/osmosis/src/route.rs index e4b359f8f..4810df2a1 100644 --- a/contracts/swapper/osmosis/src/route.rs +++ b/contracts/swapper/osmosis/src/route.rs @@ -5,9 +5,9 @@ use cosmwasm_std::{ BlockInfo, Coin, CosmosMsg, Decimal, Empty, Env, Fraction, QuerierWrapper, Uint128, }; use mars_osmosis::helpers::{has_denom, query_pool, query_twap_price}; +use mars_rover::adapters::swap::EstimateExactInSwapResponse; +use mars_swapper_base::{ContractError, ContractResult, Route}; use osmosis_std::types::osmosis::gamm::v1beta1::{MsgSwapExactAmountIn, SwapAmountInRoute}; -use rover::adapters::swap::EstimateExactInSwapResponse; -use swapper_base::{ContractError, ContractResult, Route}; use crate::helpers::hashset; diff --git a/contracts/swapper/osmosis/tests/helpers.rs b/contracts/swapper/osmosis/tests/helpers.rs index 26d372c08..6ebe01472 100644 --- a/contracts/swapper/osmosis/tests/helpers.rs +++ b/contracts/swapper/osmosis/tests/helpers.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use osmosis_testing::cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest; use osmosis_testing::{Account, Bank, OsmosisTestApp, RunnerError, SigningAccount, Wasm}; -use rover::adapters::swap::InstantiateMsg; +use mars_rover::adapters::swap::InstantiateMsg; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); diff --git a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs index 986612cd9..723cc679b 100644 --- a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs +++ b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs @@ -1,10 +1,10 @@ use crate::helpers::instantiate_contract; use cosmwasm_std::coin; +use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; +use mars_swapper_osmosis::route::OsmosisRoute; use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; use osmosis_testing::{Gamm, Module, OsmosisTestApp, SigningAccount, Wasm}; -use rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; use std::collections::HashMap; -use swapper_osmosis::route::OsmosisRoute; pub mod helpers; diff --git a/contracts/swapper/osmosis/tests/test_estimate.rs b/contracts/swapper/osmosis/tests/test_estimate.rs index 6ec000627..61e6baf37 100644 --- a/contracts/swapper/osmosis/tests/test_estimate.rs +++ b/contracts/swapper/osmosis/tests/test_estimate.rs @@ -2,8 +2,8 @@ use cosmwasm_std::{coin, Uint128}; use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; use osmosis_testing::{Gamm, Module, OsmosisTestApp, RunnerResult, Wasm}; -use rover::adapters::swap::{EstimateExactInSwapResponse, ExecuteMsg, QueryMsg}; -use swapper_osmosis::route::OsmosisRoute; +use mars_rover::adapters::swap::{EstimateExactInSwapResponse, ExecuteMsg, QueryMsg}; +use mars_swapper_osmosis::route::OsmosisRoute; use crate::helpers::{assert_err, instantiate_contract}; diff --git a/contracts/swapper/osmosis/tests/test_instantiate.rs b/contracts/swapper/osmosis/tests/test_instantiate.rs index 573dbfb66..28c32d8fc 100644 --- a/contracts/swapper/osmosis/tests/test_instantiate.rs +++ b/contracts/swapper/osmosis/tests/test_instantiate.rs @@ -1,7 +1,7 @@ use cosmwasm_std::coin; use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; -use rover::adapters::swap::{Config, InstantiateMsg, QueryMsg}; +use mars_rover::adapters::swap::{Config, InstantiateMsg, QueryMsg}; use crate::helpers::{instantiate_contract, wasm_file}; diff --git a/contracts/swapper/osmosis/tests/test_set_route.rs b/contracts/swapper/osmosis/tests/test_set_route.rs index 096616d68..9b5b23244 100644 --- a/contracts/swapper/osmosis/tests/test_set_route.rs +++ b/contracts/swapper/osmosis/tests/test_set_route.rs @@ -3,10 +3,10 @@ use cosmwasm_std::StdError::GenericErr; use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; use osmosis_testing::{Account, Gamm, Module, OsmosisTestApp, Wasm}; -use rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; -use rover::error::ContractError as RoverError; -use swapper_base::ContractError; -use swapper_osmosis::route::OsmosisRoute; +use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; +use mars_rover::error::ContractError as RoverError; +use mars_swapper_base::ContractError; +use mars_swapper_osmosis::route::OsmosisRoute; use crate::helpers::{assert_err, instantiate_contract}; diff --git a/contracts/swapper/osmosis/tests/test_swap.rs b/contracts/swapper/osmosis/tests/test_swap.rs index 543acdeea..2747c074f 100644 --- a/contracts/swapper/osmosis/tests/test_swap.rs +++ b/contracts/swapper/osmosis/tests/test_swap.rs @@ -2,10 +2,10 @@ use cosmwasm_std::{coin, Addr, Decimal}; use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; use osmosis_testing::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; -use rover::adapters::swap::ExecuteMsg; -use rover::error::ContractError as RoverError; -use swapper_base::ContractError; -use swapper_osmosis::route::OsmosisRoute; +use mars_rover::adapters::swap::ExecuteMsg; +use mars_rover::error::ContractError as RoverError; +use mars_swapper_base::ContractError; +use mars_swapper_osmosis::route::OsmosisRoute; use crate::helpers::{assert_err, instantiate_contract, query_balance}; diff --git a/contracts/swapper/osmosis/tests/test_update_config.rs b/contracts/swapper/osmosis/tests/test_update_config.rs index 861d855df..4ddcf96ca 100644 --- a/contracts/swapper/osmosis/tests/test_update_config.rs +++ b/contracts/swapper/osmosis/tests/test_update_config.rs @@ -1,10 +1,10 @@ use cosmwasm_std::coin; use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; -use rover::adapters::swap::{Config, ExecuteMsg, QueryMsg}; -use rover::error::ContractError as RoverError; -use swapper_base::ContractError; -use swapper_osmosis::route::OsmosisRoute; +use mars_rover::adapters::swap::{Config, ExecuteMsg, QueryMsg}; +use mars_rover::error::ContractError as RoverError; +use mars_swapper_base::ContractError; +use mars_swapper_osmosis::route::OsmosisRoute; use crate::helpers::{assert_err, instantiate_contract}; diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index a811c9070..c9021827c 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rover" +name = "mars-rover" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } @@ -23,9 +23,9 @@ cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } mars-health = { workspace = true } -mock-oracle = { workspace = true } +mars-mock-oracle = { workspace = true } +mars-mock-red-bank = { workspace = true } mars-outpost = { workspace = true } -mock-red-bank = { workspace = true } schemars = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs index e354e99f4..d84861d23 100644 --- a/packages/rover/src/adapters/oracle.rs +++ b/packages/rover/src/adapters/oracle.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, Coin, Decimal, QuerierWrapper, StdResult}; use mars_outpost::oracle::PriceResponse; -use mock_oracle::msg::QueryMsg; +use mars_mock_oracle::msg::QueryMsg; use crate::error::ContractResult; use crate::traits::IntoDecimal; diff --git a/schema.Makefile.toml b/schema.Makefile.toml index ab3ec7b9b..3a2ee6c84 100644 --- a/schema.Makefile.toml +++ b/schema.Makefile.toml @@ -11,14 +11,14 @@ fn main() -> std::io::Result<()> { println!("Done"); let contracts = vec![ - "credit-manager", - "account-nft", - "swapper-base", + "mars-credit-manager", + "mars-account-nft", + "mars-swapper-base", "mars-oracle-adapter", - "mock-red-bank", - "mock-vault", - "mock-oracle", - "mock-zapper", + "mars-mock-red-bank", + "mars-mock-vault", + "mars-mock-oracle", + "mars-mock-zapper", ]; for contract in contracts { diff --git a/schemas/account-nft/account-nft.json b/schemas/mars-account-nft/mars-account-nft.json similarity index 99% rename from schemas/account-nft/account-nft.json rename to schemas/mars-account-nft/mars-account-nft.json index d4572bdfa..337b5b31e 100644 --- a/schemas/account-nft/account-nft.json +++ b/schemas/mars-account-nft/mars-account-nft.json @@ -1,5 +1,5 @@ { - "contract_name": "account-nft", + "contract_name": "mars-account-nft", "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { diff --git a/schemas/credit-manager/credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json similarity index 99% rename from schemas/credit-manager/credit-manager.json rename to schemas/mars-credit-manager/mars-credit-manager.json index 4cf34c4a2..beabddf8e 100644 --- a/schemas/credit-manager/credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -1,5 +1,5 @@ { - "contract_name": "credit-manager", + "contract_name": "mars-credit-manager", "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { diff --git a/schemas/mock-oracle/mock-oracle.json b/schemas/mars-mock-oracle/mars-mock-oracle.json similarity index 98% rename from schemas/mock-oracle/mock-oracle.json rename to schemas/mars-mock-oracle/mars-mock-oracle.json index 5339b4d84..8ed327af9 100644 --- a/schemas/mock-oracle/mock-oracle.json +++ b/schemas/mars-mock-oracle/mars-mock-oracle.json @@ -1,5 +1,5 @@ { - "contract_name": "mock-oracle", + "contract_name": "mars-mock-oracle", "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { diff --git a/schemas/mock-red-bank/mock-red-bank.json b/schemas/mars-mock-red-bank/mars-mock-red-bank.json similarity index 99% rename from schemas/mock-red-bank/mock-red-bank.json rename to schemas/mars-mock-red-bank/mars-mock-red-bank.json index c74d8962a..0acc5e00a 100644 --- a/schemas/mock-red-bank/mock-red-bank.json +++ b/schemas/mars-mock-red-bank/mars-mock-red-bank.json @@ -1,5 +1,5 @@ { - "contract_name": "mock-red-bank", + "contract_name": "mars-mock-red-bank", "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { diff --git a/schemas/mock-vault/mock-vault.json b/schemas/mars-mock-vault/mars-mock-vault.json similarity index 99% rename from schemas/mock-vault/mock-vault.json rename to schemas/mars-mock-vault/mars-mock-vault.json index 73c6afb4b..394b006af 100644 --- a/schemas/mock-vault/mock-vault.json +++ b/schemas/mars-mock-vault/mars-mock-vault.json @@ -1,5 +1,5 @@ { - "contract_name": "mock-vault", + "contract_name": "mars-mock-vault", "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { diff --git a/schemas/mock-zapper/mock-zapper.json b/schemas/mars-mock-zapper/mars-mock-zapper.json similarity index 99% rename from schemas/mock-zapper/mock-zapper.json rename to schemas/mars-mock-zapper/mars-mock-zapper.json index 98e93db7a..d49ec71c3 100644 --- a/schemas/mock-zapper/mock-zapper.json +++ b/schemas/mars-mock-zapper/mars-mock-zapper.json @@ -1,5 +1,5 @@ { - "contract_name": "mock-zapper", + "contract_name": "mars-mock-zapper", "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { diff --git a/schemas/swapper-base/swapper-base.json b/schemas/mars-swapper-base/mars-swapper-base.json similarity index 99% rename from schemas/swapper-base/swapper-base.json rename to schemas/mars-swapper-base/mars-swapper-base.json index 98cf1f3b8..b2a0aa215 100644 --- a/schemas/swapper-base/swapper-base.json +++ b/schemas/mars-swapper-base/mars-swapper-base.json @@ -1,5 +1,5 @@ { - "contract_name": "swapper-base", + "contract_name": "mars-swapper-base", "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { From 8a46360069be944f29c96653c4d86b3848765fc9 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Sat, 5 Nov 2022 07:46:35 +0100 Subject: [PATCH 076/218] Vault build scripts update (#39) * integrating zapper contract * Vault build scripts update --- scripts/deploy/addresses/osmo-test-4.json | 11 +- scripts/deploy/base/deployer.ts | 160 +- scripts/deploy/base/index.ts | 8 + scripts/deploy/base/rover.ts | 92 +- scripts/deploy/osmosis/config.ts | 22 +- scripts/package.json | 10 +- scripts/types/config.ts | 12 +- .../account-nft/AccountNft.client.ts | 2 +- .../account-nft/AccountNft.react-query.ts | 2 +- .../generated/account-nft/AccountNft.types.ts | 2 +- scripts/types/generated/account-nft/bundle.ts | 2 +- .../credit-manager/CreditManager.client.ts | 63 +- .../CreditManager.react-query.ts | 103 +- .../credit-manager/CreditManager.types.ts | 152 +- .../types/generated/credit-manager/bundle.ts | 2 +- .../MarsOracleAdapter.client.ts | 16 +- .../MarsOracleAdapter.react-query.ts | 37 +- .../MarsOracleAdapter.types.ts | 17 +- .../generated/mars-oracle-adapter/bundle.ts | 2 +- .../mock-oracle/MockOracle.client.ts | 2 +- .../mock-oracle/MockOracle.react-query.ts | 2 +- .../generated/mock-oracle/MockOracle.types.ts | 4 +- scripts/types/generated/mock-oracle/bundle.ts | 2 +- .../mock-red-bank/MockRedBank.client.ts | 2 +- .../mock-red-bank/MockRedBank.react-query.ts | 2 +- .../mock-red-bank/MockRedBank.types.ts | 2 +- .../types/generated/mock-red-bank/bundle.ts | 2 +- .../generated/mock-vault/MockVault.client.ts | 190 +- .../mock-vault/MockVault.react-query.ts | 225 +- .../generated/mock-vault/MockVault.types.ts | 127 +- scripts/types/generated/mock-vault/bundle.ts | 2 +- .../mock-zapper/MockZapper.client.ts | 158 + .../mock-zapper/MockZapper.react-query.ts | 152 + .../generated/mock-zapper/MockZapper.types.ts | 48 + scripts/types/generated/mock-zapper/bundle.ts | 13 + .../swapper-base/SwapperBase.client.ts | 2 +- .../swapper-base/SwapperBase.react-query.ts | 2 +- .../swapper-base/SwapperBase.types.ts | 2 +- .../types/generated/swapper-base/bundle.ts | 10 +- scripts/types/instantiateMsgs.ts | 2 + scripts/types/storageItems.ts | 6 + scripts/yarn.lock | 2752 ++++++++--------- 42 files changed, 2585 insertions(+), 1839 deletions(-) create mode 100644 scripts/types/generated/mock-zapper/MockZapper.client.ts create mode 100644 scripts/types/generated/mock-zapper/MockZapper.react-query.ts create mode 100644 scripts/types/generated/mock-zapper/MockZapper.types.ts create mode 100644 scripts/types/generated/mock-zapper/bundle.ts diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 16f0a77c3..7dbc56003 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,8 @@ { - "accountNft": "osmo1m90h4fx5enc5wagrq8enm03597wy3sxl3mpcrg254dt459z709lquekx0a", - "marsOracleAdapter": "osmo1d6wp6u3efdrevmef9f4u6juyffzr6yhg5m9a8c8x9camxssk2vsqxpg4yc", - "swapper": "osmo1ywhrzs2uz2yyhkd8h9qjvluvklq4devjf0sx5qzd9l56yza3qhtsfk89vs", - "creditManager": "osmo1py5le8gryx3zuetvjl6xkcecrnp8r3n7t66hfxjmz3vlmj55f2yq2s6jmk", - "mockVault": "osmo1xqcw53p7f7wzgfltcty4qzgux0hahm5wkh57wh6x9tx0l3y3fcqsfaex5r" + "accountNft": "osmo1s2wausalfxldwprvd6ryzawrre5k5slvpf7dqkusyp0ea68c9m4szazrzr", + "mockVault": "osmo1qlpkaqhymdtmggx2qj9jvz0asdu2e2l6mm9eypshftjed5f8p8ks2za86s", + "marsOracleAdapter": "osmo17hzreqvt79vltz7ryk0l3x4tn9lakhkr8pnjzg5n5lug6q7urnzsznq4uw", + "swapper": "osmo15czttrmmz0ecd5gxd6l5p8ew4kk86s805vzx9u9gamgzwc0na0rqsfy0j9", + "mockZapper": "osmo14rha7vczhdhfkhlh4ypptf94vshvc74fr39v8vtj938v5aue9u8svfvhj9", + "creditManager": "osmo1xxetx647k4ujfdkklkl0tvt7qsvwl9ce7wtkq0j2yum865esh6yqfust02" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 6f5613c61..89ddcd0ee 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -7,6 +7,7 @@ import { InstantiateMsgs } from '../../types/instantiateMsgs' import { InstantiateMsg as NftInstantiateMsg } from '../../types/generated/account-nft/AccountNft.types' import { InstantiateMsg as VaultInstantiateMsg } from '../../types/generated/mock-vault/MockVault.types' import { InstantiateMsg as SwapperInstantiateMsg } from '../../types/generated/swapper-base/SwapperBase.types' +import { InstantiateMsg as ZapperInstantiateMsg } from '../../types/generated/mock-zapper/MockZapper.types' import { InstantiateMsg as OracleAdapterInstantiateMsg } from '../../types/generated/mars-oracle-adapter/MarsOracleAdapter.types' import { InstantiateMsg as RoverInstantiateMsg } from '../../types/generated/credit-manager/CreditManager.types' import { Rover } from './rover' @@ -54,7 +55,6 @@ export class Deployer { const { contractAddress } = await this.cwClient.instantiate( this.deployerAddr, codeId, - // @ts-expect-error expecting generic record msg, `mars-${name}`, 'auto', @@ -76,9 +76,9 @@ export class Deployer { async instantiateMockVault() { const msg: VaultInstantiateMsg = { - asset_denoms: [this.config.baseDenom], - lp_token_denom: this.config.vaultTokenDenom, + base_token_denom: this.config.baseDenom, oracle: this.config.oracleAddr, + vault_token_denom: this.config.vaultTokenDenom, } await this.instantiate('mockVault', this.storage.codeIds.mockVault!, msg) @@ -102,8 +102,9 @@ export class Deployer { vault_pricing: [ { addr: this.storage.addresses.mockVault!, - denom: this.config.vaultTokenDenom, method: 'preview_redeem', + base_denom: this.config.baseDenom, + vault_coin_denom: this.config.vaultTokenDenom, }, ], } @@ -143,16 +144,52 @@ export class Deployer { } } + async instantiateZapper() { + const msg: ZapperInstantiateMsg = { + oracle: this.storage.addresses.marsOracleAdapter!, + lp_configs: [ + { + lp_token_denom: this.config.lpToken.denom, + lp_pair_denoms: [this.config.zap[0].denom, this.config.zap[1].denom], + }, + ], + } + await this.instantiate('mockZapper', this.storage.codeIds.mockZapper!, msg) + + // Temporary until Token Factory is integrated into Cosmwasm or Apollo Vaults are in testnet + if (!this.storage.actions.seedMockZapper) { + printBlue('Seeding mock zapper') + await this.transferCoin( + this.storage.addresses.mockZapper!, + coin(10_000_000, this.config.lpToken.denom), + ) + this.storage.actions.seedMockZapper = true + } else { + printGray('Mock zapper already seeded') + } + } + async instantiateCreditManager() { const msg: RoverInstantiateMsg = { - allowed_coins: [this.config.baseDenom, this.config.secondaryDenom], - allowed_vaults: [{ address: this.storage.addresses.mockVault! }], + allowed_coins: [this.config.baseDenom, this.config.secondaryDenom, this.config.lpToken.denom], + allowed_vaults: [ + { + config: { + deposit_cap: this.config.vaultDepositCap, + liquidation_threshold: this.config.vaultLiquidationThreshold.toString(), + max_ltv: this.config.vaultMaxLTV.toString(), + whitelisted: true, + }, + vault: { address: this.storage.addresses.mockVault! }, + }, + ], oracle: this.storage.addresses.marsOracleAdapter!, owner: this.deployerAddr, red_bank: this.config.redBankAddr, max_close_factor: this.config.maxCloseFactor.toString(), max_liquidation_bonus: this.config.maxLiquidationBonus.toString(), swapper: this.storage.addresses.swapper!, + zapper: this.storage.addresses.mockZapper!, } await this.instantiate('creditManager', this.storage.codeIds.creditManager!, msg) } @@ -199,6 +236,117 @@ export class Deployer { ) } + async grantCreditLines() { + if (this.storage.actions.grantedCreditLines) { + printGray('Credit lines already granted') + return + } + + const wallet = await getWallet(this.config.redBankDeployerMnemonic, this.config.chainPrefix) + const client = await setupClient(this.config, wallet) + const addr = await getAddress(wallet) + + for (const coin of this.config.toGrantCreditLines) { + const msg = { + update_uncollateralized_loan_limit: { + user: this.storage.addresses.creditManager, + denom: coin.denom, + new_limit: coin.amount.toString(), + }, + } + + printBlue(`Granting credit line to Rover for: ${coin.amount} ${coin.denom}`) + await client.execute(addr, this.config.redBankAddr, msg, 'auto') + } + + this.storage.actions.grantedCreditLines = true + } + + async setupOraclePricesForZapDenoms() { + if (this.storage.actions.oraclePricesSet) { + printGray('Oracle prices already set') + return + } + + const { client, addr } = await this.getOutpostsDeployer() + + for (const coin of this.config.zap + .map((c) => ({ denom: c.denom, price: c.price })) + .concat(this.config.lpToken)) { + try { + await client.queryContractSmart(this.config.oracleAddr, { + price: { + denom: coin.denom, + }, + }) + printGray(`Price for ${coin.denom} already set`) + } catch { + const msg = { + set_price_source: { + denom: coin.denom, + price_source: { + fixed: { price: coin.price.toString() }, + }, + }, + } + console.log(JSON.stringify(msg)) + printBlue(`Setting price for ${coin.denom}: ${coin.price}`) + await client.execute(addr, this.config.oracleAddr, msg, 'auto') + } + } + this.storage.actions.oraclePricesSet = true + } + + async setupRedBankMarketsForZapDenoms() { + if (this.storage.actions.redBankMarketsSet) { + printGray('Red bank markets already set') + return + } + const { client, addr } = await this.getOutpostsDeployer() + + for (const denom of this.config.zap.map((c) => c.denom).concat(this.config.lpToken.denom)) { + try { + await client.queryContractSmart(this.config.redBankAddr, { + market: { + denom, + }, + }) + printGray(`Market for ${denom} already set`) + } catch { + const msg = { + init_asset: { + denom, + initial_borrow_rate: '0.1', + max_loan_to_value: '0.65', + reserve_factor: '0.2', + liquidation_threshold: '0.7', + liquidation_bonus: '0.1', + interest_rate_model: { + optimal_utilization_rate: '0.1', + base: '0.3', + slope_1: '0.25', + slope_2: '0.3', + }, + deposit_cap: '1000000000', + deposit_enabled: true, + borrow_enabled: true, + symbol: denom, + }, + } + printBlue(`Setting market for ${denom}`) + await client.execute(addr, this.config.redBankAddr, msg, 'auto') + } + } + this.storage.actions.redBankMarketsSet = true + } + + private async getOutpostsDeployer() { + const wallet = await getWallet(this.config.redBankDeployerMnemonic, this.config.chainPrefix) + const client = await setupClient(this.config, wallet) + const addr = await getAddress(wallet) + return { client, addr } + } + private async transferCoin(recipient: string, coin: Coin) { await this.cwClient.sendTokens(this.deployerAddr, recipient, [coin], 'auto') const balance = await this.cwClient.getBalance(recipient, coin.denom) diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index f295eaf52..3f9106d45 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -16,6 +16,7 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp await deployer.upload('mockVault', wasmFile('mock_vault')) await deployer.upload('marsOracleAdapter', wasmFile('mars_oracle_adapter')) await deployer.upload('swapper', wasmFile(swapperContractName)) + await deployer.upload('mockZapper', wasmFile('mock_zapper')) await deployer.upload('creditManager', wasmFile('credit_manager')) // Instantiate contracts @@ -23,8 +24,12 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp await deployer.instantiateMockVault() await deployer.instantiateMarsOracleAdapter() await deployer.instantiateSwapper() + await deployer.instantiateZapper() await deployer.instantiateCreditManager() await deployer.transferNftContractOwnership() + await deployer.grantCreditLines() + await deployer.setupOraclePricesForZapDenoms() + await deployer.setupRedBankMarketsForZapDenoms() await deployer.saveDeploymentAddrsToFile() const rover = await deployer.newUserRoverClient() @@ -38,6 +43,9 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp // await rover.swap() await rover.withdraw() + await rover.zap() + await rover.unzap() + await rover.vaultDeposit() if (config.vaultType === VaultType.UNLOCKED) { await rover.vaultWithdraw() diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index d28ca9dbb..b0dd9d3d5 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -62,32 +62,33 @@ export class Rover { async withdraw() { const amount = this.config.withdrawAmount.toString() const positionsBefore = await this.query.positions({ accountId: this.accountId! }) - const beforeWithdraw = parseFloat(positionsBefore.coins[0].amount) + const beforeWithdraw = parseFloat( + positionsBefore.coins.find((c) => c.denom === this.config.baseDenom)!.amount, + ) await this.updateCreditAccount([{ withdraw: { amount, denom: this.config.baseDenom } }]) const positionsAfter = await this.query.positions({ accountId: this.accountId! }) - const afterWithdraw = parseFloat(positionsAfter.coins[0].amount) + const afterWithdraw = parseFloat( + positionsAfter.coins.find((c) => c.denom === this.config.baseDenom)!.amount, + ) assert.equal(beforeWithdraw - afterWithdraw, amount) printGreen(`Withdrew: ${amount} ${this.config.baseDenom}`) } - // If this fails, it's likely because Red Bank has not whitelisted uncollateralized borrows. - // Need to issue this msg from Red Bank admin: - // {"update_uncollateralized_loan_limit": {"user":"[rover addr]","denom":"uosmo","new_limit":"1000000000"} } async borrow() { const amount = this.config.borrowAmount.toString() - await this.updateCreditAccount([{ borrow: { amount, denom: this.config.baseDenom } }]) + await this.updateCreditAccount([{ borrow: { amount, denom: this.config.secondaryDenom } }]) const positions = await this.query.positions({ accountId: this.accountId! }) assert.equal(positions.debts.length, 1) - assert.equal(positions.debts[0].denom, this.config.baseDenom) - printGreen(`Borrowed from RedBank: ${amount} ${this.config.baseDenom}`) + assert.equal(positions.debts[0].denom, this.config.secondaryDenom) + printGreen(`Borrowed from RedBank: ${amount} ${this.config.secondaryDenom}`) } async repay() { const amount = this.config.repayAmount.toString() - await this.updateCreditAccount([{ repay: { amount, denom: this.config.baseDenom } }]) + await this.updateCreditAccount([{ repay: { amount, denom: this.config.secondaryDenom } }]) const positions = await this.query.positions({ accountId: this.accountId! }) printGreen( - `Repaid to RedBank: ${amount} ${this.config.baseDenom}. Debt remaining: ${positions.debts[0].amount} ${positions.debts[0].denom}`, + `Repaid to RedBank: ${amount} ${this.config.secondaryDenom}. Debt remaining: ${positions.debts[0].amount} ${positions.debts[0].denom}`, ) } @@ -114,6 +115,42 @@ export class Rover { ) } + async zap() { + await this.updateCreditAccount([ + { + provide_liquidity: { + coins_in: this.config.zap.map((c) => ({ denom: c.denom, amount: c.amount.toString() })), + lp_token_out: this.config.lpToken.denom, + minimum_receive: '1', + }, + }, + ]) + const positions = await this.query.positions({ accountId: this.accountId! }) + const lp_balance = positions.coins.find((c) => c.denom === this.config.lpToken.denom)!.amount + printGreen( + `Zapped ${this.config.zap.map((c) => c.denom).join(', ')} for LP token: ${lp_balance} ${ + this.config.lpToken.denom + }`, + ) + } + + async unzap() { + const lpToken = { denom: this.config.lpToken.denom, amount: this.config.unzapAmount.toString() } + await this.updateCreditAccount([ + { + withdraw_liquidity: { + lp_token: lpToken, + }, + }, + ]) + const underlying = await this.query.estimateWithdrawLiquidity({ lpToken }) + printGreen( + `Unzapped ${this.config.lpToken.denom} ${this.config.unzapAmount} for underlying: ${underlying + .map((c) => `${c.amount} ${c.denom}`) + .join(', ')}`, + ) + } + async vaultDeposit() { const oldRoverBalance = await this.cwClient.getBalance( this.storage.addresses.creditManager!, @@ -121,10 +158,9 @@ export class Rover { ) await this.updateCreditAccount([ { - vault_deposit: { - coins: [ - { amount: this.config.vaultDepositAmount.toString(), denom: this.config.baseDenom }, - ], + enter_vault: { + amount: this.config.vaultDepositAmount.toString(), + denom: this.config.baseDenom, vault: { address: this.storage.addresses.mockVault! }, }, }, @@ -151,7 +187,7 @@ export class Rover { const oldBalance = await this.getAccountBalance(this.config.baseDenom) await this.updateCreditAccount([ { - vault_withdraw: { + exit_vault: { amount: this.config.vaultWithdrawAmount.toString(), vault: { address: this.storage.addresses.mockVault! }, }, @@ -170,7 +206,7 @@ export class Rover { const oldBalance = await this.getVaultBalance(this.storage.addresses.mockVault!) await this.updateCreditAccount([ { - vault_request_unlock: { + request_vault_unlock: { amount: this.config.vaultWithdrawAmount.toString(), vault: { address: this.storage.addresses.mockVault! }, }, @@ -182,7 +218,7 @@ export class Rover { printGreen( `Requested unlock: ID #${newBalance.unlocking[0].id}, amount: ${ - newBalance.unlocking[0].amount + newBalance.unlocking[0].coin.amount } in exchange for: ${oldBalance.locked - newBalance.locked} ${this.config.vaultTokenDenom}`, ) } @@ -198,15 +234,27 @@ export class Rover { const positions = await this.query.positions({ accountId: this.accountId! }) const vault = positions.vaults.find((p) => p.vault.address === vaultAddr) if (!vault) throw new Error(`No balance for ${vaultAddr}`) - return { - locked: parseInt(vault.state.locked), - unlocked: parseInt(vault.state.unlocked), - unlocking: vault.state.unlocking, + + if ('unlocked' in vault.amount) { + return { + unlocked: parseInt(vault.amount.unlocked), + locked: 0, + unlocking: [], + } + } else { + return { + unlocked: 0, + locked: parseInt(vault.amount.locking.locked), + unlocking: vault.amount.locking.unlocking.map((lockup) => ({ + id: lockup.id, + coin: { denom: lockup.coin.denom, amount: parseInt(lockup.coin.amount) }, + })), + } } } private async updateCreditAccount(actions: Action[], funds?: Coin[]) { - await this.exec.updateCreditAccount( + return await this.exec.updateCreditAccount( { actions, accountId: this.accountId! }, 'auto', undefined, diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 6085365dd..524f61a4c 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -2,24 +2,31 @@ import { DeploymentConfig, VaultType } from '../../types/config' const uatom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2' const udig = 'ibc/307E5C96C8F60D1CBEE269A9A86C0834E1DB06F2B3788AE4F716EDB97A48B97D' +const ucro = 'ibc/E6931F78057F7CC5DA0FD6CEF82FF39373A6E0452BF1FD76910B93292CF356C1' export const osmosisTestnetConfig: DeploymentConfig = { // Get the latest addresses from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-4.json - oracleAddr: 'osmo1y3y3ek83hyc4y2te8kytymg599q9sycv9dsufysapra5gglpr4ys25nh94', - redBankAddr: 'osmo1w5rqrdhut890jplmsqnr8gj3uf0wq6lj5rfdnhrtl63lpf6e7v6qalrhhn', + oracleAddr: 'osmo1hkkx42777dyfz7wc8acjjhfdh9x2ugcjvdt7shtft6ha9cn420cquz3u3j', + redBankAddr: 'osmo1g30recyv8pfy3qd4qn3dn7plc0rn5z68y5gn32j39e96tjhthzxsw3uvvu', baseDenom: 'uosmo', secondaryDenom: uatom, chainId: 'osmo-test-4', chainPrefix: 'osmo', deployerMnemonic: 'rely wonder join knock during sudden slow plate segment state agree also arrest mandate grief ordinary lonely lawsuit hurt super banana rule velvet cart', + redBankDeployerMnemonic: + 'elevator august inherit simple buddy giggle zone despair marine rich swim danger blur people hundred faint ladder wet toe strong blade utility trial process', rpcEndpoint: 'https://rpc-test.osmosis.zone', defaultGasPrice: 0.1, - startingAmountForTestUser: 1e6, + startingAmountForTestUser: 2e6, vaultTokenDenom: udig, maxCloseFactor: 0.6, maxLiquidationBonus: 0.05, depositAmount: 100, + toGrantCreditLines: [ + { denom: 'uosmo', amount: '100000000000' }, + { denom: uatom, amount: '100000000000' }, + ], borrowAmount: 10, repayAmount: 3, swapAmount: 12, @@ -34,6 +41,15 @@ export const osmosisTestnetConfig: DeploymentConfig = { slippage: 0.4, withdrawAmount: 12, vaultDepositAmount: 10, + vaultDepositCap: { denom: 'uosmo', amount: '100000000000' }, + vaultMaxLTV: 0.65, + vaultLiquidationThreshold: 0.75, vaultType: VaultType.UNLOCKED, vaultWithdrawAmount: 1_000_000, + lpToken: { denom: ucro, price: 3 }, + zap: [ + { denom: uatom, amount: 3, price: 2.135 }, + { denom: 'uosmo', amount: 3, price: 1 }, + ], + unzapAmount: 1000000, } diff --git a/scripts/package.json b/scripts/package.json index 5bb1b0b3f..5a254e7d2 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -13,13 +13,13 @@ "format-check": "prettier --check ." }, "dependencies": { - "@cosmjs/cosmwasm-stargate": "^0.29.1", - "@cosmjs/stargate": "^0.29.1", - "@cosmwasm/ts-codegen": "^0.19.0", + "@cosmjs/cosmwasm-stargate": "^0.29.3", + "@cosmjs/stargate": "^0.29.3", + "@cosmwasm/ts-codegen": "^0.20.0", "chalk": "4.1.2", - "cosmjs-types": "^0.5.0", + "cosmjs-types": "^0.5.2", "lodash": "^4.17.21", - "long": "^5.2.0", + "long": "^5.2.1", "prepend-file": "^2.0.1" }, "devDependencies": { diff --git a/scripts/types/config.ts b/scripts/types/config.ts index ad9dd1248..7445a9827 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -1,3 +1,5 @@ +import { Coin } from './generated/credit-manager/CreditManager.types' + export enum VaultType { LOCKED, UNLOCKED, @@ -11,11 +13,13 @@ export interface DeploymentConfig { chainPrefix: string rpcEndpoint: string deployerMnemonic: string + redBankDeployerMnemonic: string vaultTokenDenom: string chainId: string defaultGasPrice: number startingAmountForTestUser: number depositAmount: number + toGrantCreditLines: Coin[] borrowAmount: number repayAmount: number swapAmount: number @@ -24,7 +28,13 @@ export interface DeploymentConfig { withdrawAmount: number maxCloseFactor: number maxLiquidationBonus: number + vaultType: VaultType vaultDepositAmount: number + vaultDepositCap: Coin + vaultLiquidationThreshold: number + vaultMaxLTV: number vaultWithdrawAmount: number - vaultType: VaultType + lpToken: { denom: string; price: number } + zap: { amount: number; denom: string; price: number }[] + unzapAmount: number } diff --git a/scripts/types/generated/account-nft/AccountNft.client.ts b/scripts/types/generated/account-nft/AccountNft.client.ts index ebd9975e9..1eb349654 100644 --- a/scripts/types/generated/account-nft/AccountNft.client.ts +++ b/scripts/types/generated/account-nft/AccountNft.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/account-nft/AccountNft.react-query.ts b/scripts/types/generated/account-nft/AccountNft.react-query.ts index 3a326a5e7..dc4dd8b85 100644 --- a/scripts/types/generated/account-nft/AccountNft.react-query.ts +++ b/scripts/types/generated/account-nft/AccountNft.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/account-nft/AccountNft.types.ts b/scripts/types/generated/account-nft/AccountNft.types.ts index 939c49c64..062be6203 100644 --- a/scripts/types/generated/account-nft/AccountNft.types.ts +++ b/scripts/types/generated/account-nft/AccountNft.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/account-nft/bundle.ts b/scripts/types/generated/account-nft/bundle.ts index 353537575..df166d953 100644 --- a/scripts/types/generated/account-nft/bundle.ts +++ b/scripts/types/generated/account-nft/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/credit-manager/CreditManager.client.ts b/scripts/types/generated/credit-manager/CreditManager.client.ts index 25b2a0393..f568d188e 100644 --- a/scripts/types/generated/credit-manager/CreditManager.client.ts +++ b/scripts/types/generated/credit-manager/CreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -8,18 +8,21 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { + Uint128, Decimal, OracleBaseForString, RedBankBaseForString, SwapperBaseForString, + ZapperBaseForString, InstantiateMsg, + VaultInstantiateConfig, + VaultConfig, + Coin, VaultBaseForString, ExecuteMsg, Action, - Uint128, CallbackMsg, Addr, - Coin, ConfigUpdates, VaultBaseForAddr, QueryMsg, @@ -31,28 +34,33 @@ import { DebtShares, ArrayOfVaultWithBalance, VaultWithBalance, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, VaultPosition, - VaultPositionState, - VaultUnlockingId, + LockingVaultAmount, + VaultUnlockingPosition, ArrayOfString, - ArrayOfVaultBaseForString, ConfigResponse, + ArrayOfCoin, HealthResponse, Positions, DebtAmount, + ArrayOfVaultInstantiateConfig, } from './CreditManager.types' export interface CreditManagerReadOnlyInterface { contractAddress: string config: () => Promise - allowedVaults: ({ + vaultConfigs: ({ limit, startAfter, }: { limit?: number startAfter?: VaultBaseForString - }) => Promise + }) => Promise allowedCoins: ({ limit, startAfter, @@ -99,6 +107,14 @@ export interface CreditManagerReadOnlyInterface { limit?: number startAfter?: VaultBaseForString }) => Promise + estimateProvideLiquidity: ({ + coinsIn, + lpTokenOut, + }: { + coinsIn: Coin[] + lpTokenOut: string + }) => Promise + estimateWithdrawLiquidity: ({ lpToken }: { lpToken: Coin }) => Promise } export class CreditManagerQueryClient implements CreditManagerReadOnlyInterface { client: CosmWasmClient @@ -108,7 +124,7 @@ export class CreditManagerQueryClient implements CreditManagerReadOnlyInterface this.client = client this.contractAddress = contractAddress this.config = this.config.bind(this) - this.allowedVaults = this.allowedVaults.bind(this) + this.vaultConfigs = this.vaultConfigs.bind(this) this.allowedCoins = this.allowedCoins.bind(this) this.positions = this.positions.bind(this) this.health = this.health.bind(this) @@ -119,6 +135,8 @@ export class CreditManagerQueryClient implements CreditManagerReadOnlyInterface this.allVaultPositions = this.allVaultPositions.bind(this) this.totalVaultCoinBalance = this.totalVaultCoinBalance.bind(this) this.allTotalVaultCoinBalances = this.allTotalVaultCoinBalances.bind(this) + this.estimateProvideLiquidity = this.estimateProvideLiquidity.bind(this) + this.estimateWithdrawLiquidity = this.estimateWithdrawLiquidity.bind(this) } config = async (): Promise => { @@ -126,15 +144,15 @@ export class CreditManagerQueryClient implements CreditManagerReadOnlyInterface config: {}, }) } - allowedVaults = async ({ + vaultConfigs = async ({ limit, startAfter, }: { limit?: number startAfter?: VaultBaseForString - }): Promise => { + }): Promise => { return this.client.queryContractSmart(this.contractAddress, { - allowed_vaults: { + vault_configs: { limit, start_after: startAfter, }, @@ -250,6 +268,27 @@ export class CreditManagerQueryClient implements CreditManagerReadOnlyInterface }, }) } + estimateProvideLiquidity = async ({ + coinsIn, + lpTokenOut, + }: { + coinsIn: Coin[] + lpTokenOut: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + estimate_provide_liquidity: { + coins_in: coinsIn, + lp_token_out: lpTokenOut, + }, + }) + } + estimateWithdrawLiquidity = async ({ lpToken }: { lpToken: Coin }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + estimate_withdraw_liquidity: { + lp_token: lpToken, + }, + }) + } } export interface CreditManagerInterface extends CreditManagerReadOnlyInterface { contractAddress: string diff --git a/scripts/types/generated/credit-manager/CreditManager.react-query.ts b/scripts/types/generated/credit-manager/CreditManager.react-query.ts index 0e79c956b..dcdf64930 100644 --- a/scripts/types/generated/credit-manager/CreditManager.react-query.ts +++ b/scripts/types/generated/credit-manager/CreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -9,18 +9,21 @@ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tan import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { + Uint128, Decimal, OracleBaseForString, RedBankBaseForString, SwapperBaseForString, + ZapperBaseForString, InstantiateMsg, + VaultInstantiateConfig, + VaultConfig, + Coin, VaultBaseForString, ExecuteMsg, Action, - Uint128, CallbackMsg, Addr, - Coin, ConfigUpdates, VaultBaseForAddr, QueryMsg, @@ -32,17 +35,22 @@ import { DebtShares, ArrayOfVaultWithBalance, VaultWithBalance, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, VaultPosition, - VaultPositionState, - VaultUnlockingId, + LockingVaultAmount, + VaultUnlockingPosition, ArrayOfString, - ArrayOfVaultBaseForString, ConfigResponse, + ArrayOfCoin, HealthResponse, Positions, DebtAmount, + ArrayOfVaultInstantiateConfig, } from './CreditManager.types' import { CreditManagerQueryClient, CreditManagerClient } from './CreditManager.client' export const creditManagerQueryKeys = { @@ -55,9 +63,9 @@ export const creditManagerQueryKeys = { [{ ...creditManagerQueryKeys.contract[0], address: contractAddress }] as const, config: (contractAddress: string | undefined, args?: Record) => [{ ...creditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, - allowedVaults: (contractAddress: string | undefined, args?: Record) => + vaultConfigs: (contractAddress: string | undefined, args?: Record) => [ - { ...creditManagerQueryKeys.address(contractAddress)[0], method: 'allowed_vaults', args }, + { ...creditManagerQueryKeys.address(contractAddress)[0], method: 'vault_configs', args }, ] as const, allowedCoins: (contractAddress: string | undefined, args?: Record) => [ @@ -114,6 +122,25 @@ export const creditManagerQueryKeys = { args, }, ] as const, + estimateProvideLiquidity: (contractAddress: string | undefined, args?: Record) => + [ + { + ...creditManagerQueryKeys.address(contractAddress)[0], + method: 'estimate_provide_liquidity', + args, + }, + ] as const, + estimateWithdrawLiquidity: ( + contractAddress: string | undefined, + args?: Record, + ) => + [ + { + ...creditManagerQueryKeys.address(contractAddress)[0], + method: 'estimate_withdraw_liquidity', + args, + }, + ] as const, } export interface CreditManagerReactQuery { client: CreditManagerQueryClient | undefined @@ -124,6 +151,52 @@ export interface CreditManagerReactQuery { initialData?: undefined } } +export interface CreditManagerEstimateWithdrawLiquidityQuery + extends CreditManagerReactQuery { + args: { + lpToken: Coin + } +} +export function useCreditManagerEstimateWithdrawLiquidityQuery({ + client, + args, + options, +}: CreditManagerEstimateWithdrawLiquidityQuery) { + return useQuery( + creditManagerQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), + () => + client + ? client.estimateWithdrawLiquidity({ + lpToken: args.lpToken, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface CreditManagerEstimateProvideLiquidityQuery + extends CreditManagerReactQuery { + args: { + coinsIn: Coin[] + lpTokenOut: string + } +} +export function useCreditManagerEstimateProvideLiquidityQuery({ + client, + args, + options, +}: CreditManagerEstimateProvideLiquidityQuery) { + return useQuery( + creditManagerQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), + () => + client + ? client.estimateProvideLiquidity({ + coinsIn: args.coinsIn, + lpTokenOut: args.lpTokenOut, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface CreditManagerAllTotalVaultCoinBalancesQuery extends CreditManagerReactQuery { args: { @@ -346,23 +419,23 @@ export function useCreditManagerAllowedCoinsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface CreditManagerAllowedVaultsQuery - extends CreditManagerReactQuery { +export interface CreditManagerVaultConfigsQuery + extends CreditManagerReactQuery { args: { limit?: number startAfter?: VaultBaseForString } } -export function useCreditManagerAllowedVaultsQuery({ +export function useCreditManagerVaultConfigsQuery({ client, args, options, -}: CreditManagerAllowedVaultsQuery) { - return useQuery( - creditManagerQueryKeys.allowedVaults(client?.contractAddress, args), +}: CreditManagerVaultConfigsQuery) { + return useQuery( + creditManagerQueryKeys.vaultConfigs(client?.contractAddress, args), () => client - ? client.allowedVaults({ + ? client.vaultConfigs({ limit: args.limit, startAfter: args.startAfter, }) diff --git a/scripts/types/generated/credit-manager/CreditManager.types.ts b/scripts/types/generated/credit-manager/CreditManager.types.ts index 61f7100ca..c271ba41f 100644 --- a/scripts/types/generated/credit-manager/CreditManager.types.ts +++ b/scripts/types/generated/credit-manager/CreditManager.types.ts @@ -1,23 +1,41 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ +export type Uint128 = string export type Decimal = string export type OracleBaseForString = string export type RedBankBaseForString = string export type SwapperBaseForString = string +export type ZapperBaseForString = string export interface InstantiateMsg { allowed_coins: string[] - allowed_vaults: VaultBaseForString[] + allowed_vaults: VaultInstantiateConfig[] max_close_factor: Decimal max_liquidation_bonus: Decimal oracle: OracleBaseForString owner: string red_bank: RedBankBaseForString swapper: SwapperBaseForString + zapper: ZapperBaseForString +} +export interface VaultInstantiateConfig { + config: VaultConfig + vault: VaultBaseForString +} +export interface VaultConfig { + deposit_cap: Coin + liquidation_threshold: Decimal + max_ltv: Decimal + whitelisted: boolean +} +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown } export interface VaultBaseForString { address: string @@ -54,26 +72,27 @@ export type Action = repay: Coin } | { - vault_deposit: { - coins: Coin[] + enter_vault: { + amount?: Uint128 | null + denom: string vault: VaultBaseForString } } | { - vault_withdraw: { + exit_vault: { amount: Uint128 vault: VaultBaseForString } } | { - vault_request_unlock: { + request_vault_unlock: { amount: Uint128 vault: VaultBaseForString } } | { - vault_withdraw_unlocked: { - id: Uint128 + exit_vault_unlocked: { + id: number vault: VaultBaseForString } } @@ -84,6 +103,13 @@ export type Action = request_coin_denom: string } } + | { + liquidate_vault: { + debt_coin: Coin + liquidatee_account_id: string + request_vault: VaultBaseForString + } + } | { swap_exact_in: { coin_in: Coin @@ -91,7 +117,18 @@ export type Action = slippage: Decimal } } -export type Uint128 = string + | { + provide_liquidity: { + coins_in: Coin[] + lp_token_out: string + minimum_receive: Uint128 + } + } + | { + withdraw_liquidity: { + lp_token: Coin + } + } export type CallbackMsg = | { withdraw: { @@ -118,44 +155,45 @@ export type CallbackMsg = } } | { - vault_deposit: { + enter_vault: { account_id: string - coins: Coin[] + amount?: Uint128 | null + denom: string vault: VaultBaseForAddr } } | { - update_vault_coin_balance: { + exit_vault: { account_id: string - previous_total_balance: Uint128 + amount: Uint128 vault: VaultBaseForAddr } } | { - vault_withdraw: { + update_vault_coin_balance: { account_id: string - amount: Uint128 + previous_total_balance: Uint128 vault: VaultBaseForAddr } } | { - vault_force_withdraw: { + force_exit_vault: { account_id: string amount: Uint128 vault: VaultBaseForAddr } } | { - vault_request_unlock: { + request_vault_unlock: { account_id: string amount: Uint128 vault: VaultBaseForAddr } } | { - vault_withdraw_unlocked: { + exit_vault_unlocked: { account_id: string - position_id: Uint128 + position_id: number vault: VaultBaseForAddr } } @@ -168,9 +206,11 @@ export type CallbackMsg = } } | { - assert_health_factor_improved: { - account_id: string - previous_health_factor: Decimal + liquidate_vault: { + debt_coin: Coin + liquidatee_account_id: string + liquidator_account_id: string + request_vault: VaultBaseForAddr } } | { @@ -182,27 +222,37 @@ export type CallbackMsg = } } | { - update_coin_balances: { + update_coin_balance: { + account_id: string + previous_balance: Coin + } + } + | { + provide_liquidity: { account_id: string - previous_balances: Coin[] + coins_in: Coin[] + lp_token_out: string + minimum_receive: Uint128 + } + } + | { + withdraw_liquidity: { + account_id: string + lp_token: Coin } } export type Addr = string -export interface Coin { - amount: Uint128 - denom: string - [k: string]: unknown -} export interface ConfigUpdates { account_nft?: string | null allowed_coins?: string[] | null - allowed_vaults?: VaultBaseForString[] | null max_close_factor?: Decimal | null max_liquidation_bonus?: Decimal | null oracle?: OracleBaseForString | null owner?: string | null red_bank?: RedBankBaseForString | null swapper?: SwapperBaseForString | null + vault_configs?: VaultInstantiateConfig[] | null + zapper?: ZapperBaseForString | null } export interface VaultBaseForAddr { address: Addr @@ -212,7 +262,7 @@ export type QueryMsg = config: {} } | { - allowed_vaults: { + vault_configs: { limit?: number | null start_after?: VaultBaseForString | null } @@ -271,6 +321,17 @@ export type QueryMsg = start_after?: VaultBaseForString | null } } + | { + estimate_provide_liquidity: { + coins_in: Coin[] + lp_token_out: string + } + } + | { + estimate_withdraw_liquidity: { + lp_token: Coin + } + } export type ArrayOfCoinBalanceResponseItem = CoinBalanceResponseItem[] export interface CoinBalanceResponseItem { account_id: string @@ -293,26 +354,34 @@ export interface VaultWithBalance { balance: Uint128 vault: VaultBaseForAddr } +export type VaultPositionAmount = + | { + unlocked: VaultAmount + } + | { + locking: LockingVaultAmount + } +export type VaultAmount = string +export type VaultAmount1 = string +export type UnlockingPositions = VaultUnlockingPosition[] export type ArrayOfVaultPositionResponseItem = VaultPositionResponseItem[] export interface VaultPositionResponseItem { account_id: string position: VaultPosition } export interface VaultPosition { - state: VaultPositionState + amount: VaultPositionAmount vault: VaultBaseForAddr } -export interface VaultPositionState { - locked: Uint128 - unlocked: Uint128 - unlocking: VaultUnlockingId[] +export interface LockingVaultAmount { + locked: VaultAmount1 + unlocking: UnlockingPositions } -export interface VaultUnlockingId { - amount: Uint128 - id: Uint128 +export interface VaultUnlockingPosition { + coin: Coin + id: number } export type ArrayOfString = string[] -export type ArrayOfVaultBaseForString = VaultBaseForString[] export interface ConfigResponse { account_nft?: string | null max_close_factor: Decimal @@ -321,7 +390,9 @@ export interface ConfigResponse { owner: string red_bank: string swapper: string + zapper: string } +export type ArrayOfCoin = Coin[] export interface HealthResponse { above_max_ltv: boolean liquidatable: boolean @@ -343,3 +414,4 @@ export interface DebtAmount { denom: string shares: Uint128 } +export type ArrayOfVaultInstantiateConfig = VaultInstantiateConfig[] diff --git a/scripts/types/generated/credit-manager/bundle.ts b/scripts/types/generated/credit-manager/bundle.ts index 9aa441b7b..c2f710c3f 100644 --- a/scripts/types/generated/credit-manager/bundle.ts +++ b/scripts/types/generated/credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts index af267ab4d..3dd7dd657 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts @@ -1,12 +1,12 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { Coin, StdFee } from '@cosmjs/amino' +import { StdFee } from '@cosmjs/amino' import { OracleBaseForString, Addr, @@ -16,15 +16,19 @@ import { ExecuteMsg, ConfigUpdates, QueryMsg, + Uint128, + Coin, ArrayOfVaultPricingInfo, OracleBaseForAddr, ConfigResponse, Decimal, PriceResponse, + ArrayOfCoin, } from './MarsOracleAdapter.types' export interface MarsOracleAdapterReadOnlyInterface { contractAddress: string price: ({ denom }: { denom: string }) => Promise + priceableUnderlying: ({ coin }: { coin: Coin }) => Promise config: () => Promise pricingInfo: ({ denom }: { denom: string }) => Promise allPricingInfo: ({ @@ -43,6 +47,7 @@ export class MarsOracleAdapterQueryClient implements MarsOracleAdapterReadOnlyIn this.client = client this.contractAddress = contractAddress this.price = this.price.bind(this) + this.priceableUnderlying = this.priceableUnderlying.bind(this) this.config = this.config.bind(this) this.pricingInfo = this.pricingInfo.bind(this) this.allPricingInfo = this.allPricingInfo.bind(this) @@ -55,6 +60,13 @@ export class MarsOracleAdapterQueryClient implements MarsOracleAdapterReadOnlyIn }, }) } + priceableUnderlying = async ({ coin }: { coin: Coin }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + priceable_underlying: { + coin, + }, + }) + } config = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { config: {}, diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts index a1af7b2c5..8637ecdbb 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts @@ -1,13 +1,13 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee, Coin } from '@cosmjs/amino' +import { StdFee } from '@cosmjs/amino' import { OracleBaseForString, Addr, @@ -17,11 +17,14 @@ import { ExecuteMsg, ConfigUpdates, QueryMsg, + Uint128, + Coin, ArrayOfVaultPricingInfo, OracleBaseForAddr, ConfigResponse, Decimal, PriceResponse, + ArrayOfCoin, } from './MarsOracleAdapter.types' import { MarsOracleAdapterQueryClient, MarsOracleAdapterClient } from './MarsOracleAdapter.client' export const marsOracleAdapterQueryKeys = { @@ -34,6 +37,14 @@ export const marsOracleAdapterQueryKeys = { [{ ...marsOracleAdapterQueryKeys.contract[0], address: contractAddress }] as const, price: (contractAddress: string | undefined, args?: Record) => [{ ...marsOracleAdapterQueryKeys.address(contractAddress)[0], method: 'price', args }] as const, + priceableUnderlying: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsOracleAdapterQueryKeys.address(contractAddress)[0], + method: 'priceable_underlying', + args, + }, + ] as const, config: (contractAddress: string | undefined, args?: Record) => [ { ...marsOracleAdapterQueryKeys.address(contractAddress)[0], method: 'config', args }, @@ -118,6 +129,28 @@ export function useMarsOracleAdapterConfigQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsOracleAdapterPriceableUnderlyingQuery + extends MarsOracleAdapterReactQuery { + args: { + coin: Coin + } +} +export function useMarsOracleAdapterPriceableUnderlyingQuery({ + client, + args, + options, +}: MarsOracleAdapterPriceableUnderlyingQuery) { + return useQuery( + marsOracleAdapterQueryKeys.priceableUnderlying(client?.contractAddress, args), + () => + client + ? client.priceableUnderlying({ + coin: args.coin, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsOracleAdapterPriceQuery extends MarsOracleAdapterReactQuery { args: { diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts index 7457699e6..3072f9a66 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -15,8 +15,9 @@ export interface InstantiateMsg { } export interface VaultPricingInfo { addr: Addr - denom: string + base_denom: string method: PricingMethod + vault_coin_denom: string } export type ExecuteMsg = { update_config: { @@ -34,6 +35,11 @@ export type QueryMsg = denom: string } } + | { + priceable_underlying: { + coin: Coin + } + } | { config: {} } @@ -48,6 +54,12 @@ export type QueryMsg = start_after?: string | null } } +export type Uint128 = string +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} export type ArrayOfVaultPricingInfo = VaultPricingInfo[] export type OracleBaseForAddr = string export interface ConfigResponse { @@ -59,3 +71,4 @@ export interface PriceResponse { denom: string price: Decimal } +export type ArrayOfCoin = Coin[] diff --git a/scripts/types/generated/mars-oracle-adapter/bundle.ts b/scripts/types/generated/mars-oracle-adapter/bundle.ts index 20637870c..e1620a2fe 100644 --- a/scripts/types/generated/mars-oracle-adapter/bundle.ts +++ b/scripts/types/generated/mars-oracle-adapter/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-oracle/MockOracle.client.ts b/scripts/types/generated/mock-oracle/MockOracle.client.ts index ab10b948f..57e102e6c 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.client.ts +++ b/scripts/types/generated/mock-oracle/MockOracle.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-oracle/MockOracle.react-query.ts b/scripts/types/generated/mock-oracle/MockOracle.react-query.ts index 5f30f22b4..125acbdb6 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.react-query.ts +++ b/scripts/types/generated/mock-oracle/MockOracle.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-oracle/MockOracle.types.ts b/scripts/types/generated/mock-oracle/MockOracle.types.ts index 84b003a8f..56d42aa73 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.types.ts +++ b/scripts/types/generated/mock-oracle/MockOracle.types.ts @@ -1,13 +1,13 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ export type Decimal = string export interface InstantiateMsg { - coins: CoinPrice[] + prices: CoinPrice[] } export interface CoinPrice { denom: string diff --git a/scripts/types/generated/mock-oracle/bundle.ts b/scripts/types/generated/mock-oracle/bundle.ts index e017ba9ba..a654c2b8e 100644 --- a/scripts/types/generated/mock-oracle/bundle.ts +++ b/scripts/types/generated/mock-oracle/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.client.ts b/scripts/types/generated/mock-red-bank/MockRedBank.client.ts index 67eb3d409..344fa75c5 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.client.ts +++ b/scripts/types/generated/mock-red-bank/MockRedBank.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts b/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts index 8785e7eeb..d0b16d767 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts +++ b/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.types.ts b/scripts/types/generated/mock-red-bank/MockRedBank.types.ts index 2035efaf5..d3227a844 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.types.ts +++ b/scripts/types/generated/mock-red-bank/MockRedBank.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-red-bank/bundle.ts b/scripts/types/generated/mock-red-bank/bundle.ts index 9687acebc..c40f381d0 100644 --- a/scripts/types/generated/mock-red-bank/bundle.ts +++ b/scripts/types/generated/mock-red-bank/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-vault/MockVault.client.ts b/scripts/types/generated/mock-vault/MockVault.client.ts index 0dfb77e1c..d0e6858ac 100644 --- a/scripts/types/generated/mock-vault/MockVault.client.ts +++ b/scripts/types/generated/mock-vault/MockVault.client.ts @@ -1,33 +1,39 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee } from '@cosmjs/amino' +import { Coin, StdFee } from '@cosmjs/amino' import { + Duration, OracleBaseForString, InstantiateMsg, ExecuteMsg, Uint128, + ExtensionExecuteMsg, + LockupExecuteMsg, + ForceUnlockExecuteMsg, QueryMsg, + ExtensionQueryMsg, + LockupQueryMsg, VaultInfo, - ArrayOfCoin, - Coin, - Timestamp, - Uint64, - UnlockingPosition, - ArrayOfUnlockingPosition, + Empty, + VaultStandardInfo, } from './MockVault.types' export interface MockVaultReadOnlyInterface { contractAddress: string + vaultStandardInfo: () => Promise info: () => Promise - previewRedeem: ({ amount }: { amount: Uint128 }) => Promise - totalVaultCoinsIssued: () => Promise - unlockingPositionsForAddr: ({ addr }: { addr: string }) => Promise - unlockingPosition: ({ id }: { id: Uint128 }) => Promise + previewDeposit: ({ amount }: { amount: Uint128 }) => Promise + previewRedeem: ({ amount }: { amount: Uint128 }) => Promise + totalAssets: () => Promise + totalVaultTokenSupply: () => Promise + convertToShares: ({ amount }: { amount: Uint128 }) => Promise + convertToAssets: ({ amount }: { amount: Uint128 }) => Promise + vaultExtension: () => Promise } export class MockVaultQueryClient implements MockVaultReadOnlyInterface { client: CosmWasmClient @@ -36,74 +42,99 @@ export class MockVaultQueryClient implements MockVaultReadOnlyInterface { constructor(client: CosmWasmClient, contractAddress: string) { this.client = client this.contractAddress = contractAddress + this.vaultStandardInfo = this.vaultStandardInfo.bind(this) this.info = this.info.bind(this) + this.previewDeposit = this.previewDeposit.bind(this) this.previewRedeem = this.previewRedeem.bind(this) - this.totalVaultCoinsIssued = this.totalVaultCoinsIssued.bind(this) - this.unlockingPositionsForAddr = this.unlockingPositionsForAddr.bind(this) - this.unlockingPosition = this.unlockingPosition.bind(this) + this.totalAssets = this.totalAssets.bind(this) + this.totalVaultTokenSupply = this.totalVaultTokenSupply.bind(this) + this.convertToShares = this.convertToShares.bind(this) + this.convertToAssets = this.convertToAssets.bind(this) + this.vaultExtension = this.vaultExtension.bind(this) } + vaultStandardInfo = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + vault_standard_info: {}, + }) + } info = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { info: {}, }) } - previewRedeem = async ({ amount }: { amount: Uint128 }): Promise => { + previewDeposit = async ({ amount }: { amount: Uint128 }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + preview_deposit: { + amount, + }, + }) + } + previewRedeem = async ({ amount }: { amount: Uint128 }): Promise => { return this.client.queryContractSmart(this.contractAddress, { preview_redeem: { amount, }, }) } - totalVaultCoinsIssued = async (): Promise => { + totalAssets = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + total_assets: {}, + }) + } + totalVaultTokenSupply = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { - total_vault_coins_issued: {}, + total_vault_token_supply: {}, }) } - unlockingPositionsForAddr = async ({ - addr, - }: { - addr: string - }): Promise => { + convertToShares = async ({ amount }: { amount: Uint128 }): Promise => { return this.client.queryContractSmart(this.contractAddress, { - unlocking_positions_for_addr: { - addr, + convert_to_shares: { + amount, }, }) } - unlockingPosition = async ({ id }: { id: Uint128 }): Promise => { + convertToAssets = async ({ amount }: { amount: Uint128 }): Promise => { return this.client.queryContractSmart(this.contractAddress, { - unlocking_position: { - id, + convert_to_assets: { + amount, }, }) } + vaultExtension = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + vault_extension: {}, + }) + } } export interface MockVaultInterface extends MockVaultReadOnlyInterface { contractAddress: string sender: string - deposit: (fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[]) => Promise - withdraw: ( - fee?: number | StdFee | 'auto', - memo?: string, - funds?: Coin[], - ) => Promise - forceWithdraw: ( + deposit: ( + { + amount, + recipient, + }: { + amount: Uint128 + recipient?: string + }, fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], ) => Promise - requestUnlock: ( + redeem: ( + { + amount, + recipient, + }: { + amount: Uint128 + recipient?: string + }, fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], ) => Promise - withdrawUnlocked: ( - { - id, - }: { - id: Uint128 - }, + vaultExtension: ( fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], @@ -120,13 +151,18 @@ export class MockVaultClient extends MockVaultQueryClient implements MockVaultIn this.sender = sender this.contractAddress = contractAddress this.deposit = this.deposit.bind(this) - this.withdraw = this.withdraw.bind(this) - this.forceWithdraw = this.forceWithdraw.bind(this) - this.requestUnlock = this.requestUnlock.bind(this) - this.withdrawUnlocked = this.withdrawUnlocked.bind(this) + this.redeem = this.redeem.bind(this) + this.vaultExtension = this.vaultExtension.bind(this) } deposit = async ( + { + amount, + recipient, + }: { + amount: Uint128 + recipient?: string + }, fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -135,46 +171,24 @@ export class MockVaultClient extends MockVaultQueryClient implements MockVaultIn this.sender, this.contractAddress, { - deposit: {}, - }, - fee, - memo, - funds, - ) - } - withdraw = async ( - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - withdraw: {}, - }, - fee, - memo, - funds, - ) - } - forceWithdraw = async ( - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - force_withdraw: {}, + deposit: { + amount, + recipient, + }, }, fee, memo, funds, ) } - requestUnlock = async ( + redeem = async ( + { + amount, + recipient, + }: { + amount: Uint128 + recipient?: string + }, fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -183,19 +197,17 @@ export class MockVaultClient extends MockVaultQueryClient implements MockVaultIn this.sender, this.contractAddress, { - request_unlock: {}, + redeem: { + amount, + recipient, + }, }, fee, memo, funds, ) } - withdrawUnlocked = async ( - { - id, - }: { - id: Uint128 - }, + vaultExtension = async ( fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -204,9 +216,7 @@ export class MockVaultClient extends MockVaultQueryClient implements MockVaultIn this.sender, this.contractAddress, { - withdraw_unlocked: { - id, - }, + vault_extension: {}, }, fee, memo, diff --git a/scripts/types/generated/mock-vault/MockVault.react-query.ts b/scripts/types/generated/mock-vault/MockVault.react-query.ts index ec5441751..db84a2a5c 100644 --- a/scripts/types/generated/mock-vault/MockVault.react-query.ts +++ b/scripts/types/generated/mock-vault/MockVault.react-query.ts @@ -1,26 +1,28 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee } from '@cosmjs/amino' +import { StdFee, Coin } from '@cosmjs/amino' import { + Duration, OracleBaseForString, InstantiateMsg, ExecuteMsg, Uint128, + ExtensionExecuteMsg, + LockupExecuteMsg, + ForceUnlockExecuteMsg, QueryMsg, + ExtensionQueryMsg, + LockupQueryMsg, VaultInfo, - ArrayOfCoin, - Coin, - Timestamp, - Uint64, - UnlockingPosition, - ArrayOfUnlockingPosition, + Empty, + VaultStandardInfo, } from './MockVault.types' import { MockVaultQueryClient, MockVaultClient } from './MockVault.client' export const mockVaultQueryKeys = { @@ -31,34 +33,41 @@ export const mockVaultQueryKeys = { ] as const, address: (contractAddress: string | undefined) => [{ ...mockVaultQueryKeys.contract[0], address: contractAddress }] as const, + vaultStandardInfo: (contractAddress: string | undefined, args?: Record) => + [ + { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'vault_standard_info', args }, + ] as const, info: (contractAddress: string | undefined, args?: Record) => [{ ...mockVaultQueryKeys.address(contractAddress)[0], method: 'info', args }] as const, + previewDeposit: (contractAddress: string | undefined, args?: Record) => + [ + { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'preview_deposit', args }, + ] as const, previewRedeem: (contractAddress: string | undefined, args?: Record) => [ { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'preview_redeem', args }, ] as const, - totalVaultCoinsIssued: (contractAddress: string | undefined, args?: Record) => + totalAssets: (contractAddress: string | undefined, args?: Record) => + [{ ...mockVaultQueryKeys.address(contractAddress)[0], method: 'total_assets', args }] as const, + totalVaultTokenSupply: (contractAddress: string | undefined, args?: Record) => [ { ...mockVaultQueryKeys.address(contractAddress)[0], - method: 'total_vault_coins_issued', + method: 'total_vault_token_supply', args, }, ] as const, - unlockingPositionsForAddr: ( - contractAddress: string | undefined, - args?: Record, - ) => + convertToShares: (contractAddress: string | undefined, args?: Record) => [ - { - ...mockVaultQueryKeys.address(contractAddress)[0], - method: 'unlocking_positions_for_addr', - args, - }, + { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'convert_to_shares', args }, ] as const, - unlockingPosition: (contractAddress: string | undefined, args?: Record) => + convertToAssets: (contractAddress: string | undefined, args?: Record) => [ - { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'unlocking_position', args }, + { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'convert_to_assets', args }, + ] as const, + vaultExtension: (contractAddress: string | undefined, args?: Record) => + [ + { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'vault_extension', args }, ] as const, } export interface MockVaultReactQuery { @@ -70,74 +79,93 @@ export interface MockVaultReactQuery { initialData?: undefined } } -export interface MockVaultUnlockingPositionQuery - extends MockVaultReactQuery { +export interface MockVaultVaultExtensionQuery extends MockVaultReactQuery {} +export function useMockVaultVaultExtensionQuery({ + client, + options, +}: MockVaultVaultExtensionQuery) { + return useQuery( + mockVaultQueryKeys.vaultExtension(client?.contractAddress), + () => (client ? client.vaultExtension() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockVaultConvertToAssetsQuery extends MockVaultReactQuery { args: { - id: Uint128 + amount: Uint128 } } -export function useMockVaultUnlockingPositionQuery({ +export function useMockVaultConvertToAssetsQuery({ client, args, options, -}: MockVaultUnlockingPositionQuery) { - return useQuery( - mockVaultQueryKeys.unlockingPosition(client?.contractAddress, args), +}: MockVaultConvertToAssetsQuery) { + return useQuery( + mockVaultQueryKeys.convertToAssets(client?.contractAddress, args), () => client - ? client.unlockingPosition({ - id: args.id, + ? client.convertToAssets({ + amount: args.amount, }) : Promise.reject(new Error('Invalid client')), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultUnlockingPositionsForAddrQuery - extends MockVaultReactQuery { +export interface MockVaultConvertToSharesQuery extends MockVaultReactQuery { args: { - addr: string + amount: Uint128 } } -export function useMockVaultUnlockingPositionsForAddrQuery({ +export function useMockVaultConvertToSharesQuery({ client, args, options, -}: MockVaultUnlockingPositionsForAddrQuery) { - return useQuery( - mockVaultQueryKeys.unlockingPositionsForAddr(client?.contractAddress, args), +}: MockVaultConvertToSharesQuery) { + return useQuery( + mockVaultQueryKeys.convertToShares(client?.contractAddress, args), () => client - ? client.unlockingPositionsForAddr({ - addr: args.addr, + ? client.convertToShares({ + amount: args.amount, }) : Promise.reject(new Error('Invalid client')), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultTotalVaultCoinsIssuedQuery +export interface MockVaultTotalVaultTokenSupplyQuery extends MockVaultReactQuery {} -export function useMockVaultTotalVaultCoinsIssuedQuery({ +export function useMockVaultTotalVaultTokenSupplyQuery({ client, options, -}: MockVaultTotalVaultCoinsIssuedQuery) { +}: MockVaultTotalVaultTokenSupplyQuery) { return useQuery( - mockVaultQueryKeys.totalVaultCoinsIssued(client?.contractAddress), - () => (client ? client.totalVaultCoinsIssued() : Promise.reject(new Error('Invalid client'))), + mockVaultQueryKeys.totalVaultTokenSupply(client?.contractAddress), + () => (client ? client.totalVaultTokenSupply() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultPreviewRedeemQuery - extends MockVaultReactQuery { +export interface MockVaultTotalAssetsQuery extends MockVaultReactQuery {} +export function useMockVaultTotalAssetsQuery({ + client, + options, +}: MockVaultTotalAssetsQuery) { + return useQuery( + mockVaultQueryKeys.totalAssets(client?.contractAddress), + () => (client ? client.totalAssets() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockVaultPreviewRedeemQuery extends MockVaultReactQuery { args: { amount: Uint128 } } -export function useMockVaultPreviewRedeemQuery({ +export function useMockVaultPreviewRedeemQuery({ client, args, options, }: MockVaultPreviewRedeemQuery) { - return useQuery( + return useQuery( mockVaultQueryKeys.previewRedeem(client?.contractAddress, args), () => client @@ -148,6 +176,27 @@ export function useMockVaultPreviewRedeemQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MockVaultPreviewDepositQuery extends MockVaultReactQuery { + args: { + amount: Uint128 + } +} +export function useMockVaultPreviewDepositQuery({ + client, + args, + options, +}: MockVaultPreviewDepositQuery) { + return useQuery( + mockVaultQueryKeys.previewDeposit(client?.contractAddress, args), + () => + client + ? client.previewDeposit({ + amount: args.amount, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MockVaultInfoQuery extends MockVaultReactQuery {} export function useMockVaultInfoQuery({ client, @@ -159,85 +208,65 @@ export function useMockVaultInfoQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultWithdrawUnlockedMutation { - client: MockVaultClient - msg: { - id: Uint128 - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMockVaultWithdrawUnlockedMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => - client.withdrawUnlocked(msg, fee, memo, funds), - options, +export interface MockVaultVaultStandardInfoQuery + extends MockVaultReactQuery {} +export function useMockVaultVaultStandardInfoQuery({ + client, + options, +}: MockVaultVaultStandardInfoQuery) { + return useQuery( + mockVaultQueryKeys.vaultStandardInfo(client?.contractAddress), + () => (client ? client.vaultStandardInfo() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultRequestUnlockMutation { +export interface MockVaultVaultExtensionMutation { client: MockVaultClient + msg: ExtensionExecuteMsg args?: { fee?: number | StdFee | 'auto' memo?: string funds?: Coin[] } } -export function useMockVaultRequestUnlockMutation( +export function useMockVaultVaultExtensionMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( - ({ client, args: { fee, memo, funds } = {} }) => client.requestUnlock(fee, memo, funds), + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.vaultExtension(msg, fee, memo, funds), options, ) } -export interface MockVaultForceWithdrawMutation { +export interface MockVaultRedeemMutation { client: MockVaultClient - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] + msg: { + amount: Uint128 + recipient?: string } -} -export function useMockVaultForceWithdrawMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, args: { fee, memo, funds } = {} }) => client.forceWithdraw(fee, memo, funds), - options, - ) -} -export interface MockVaultWithdrawMutation { - client: MockVaultClient args?: { fee?: number | StdFee | 'auto' memo?: string funds?: Coin[] } } -export function useMockVaultWithdrawMutation( - options?: Omit, 'mutationFn'>, +export function useMockVaultRedeemMutation( + options?: Omit, 'mutationFn'>, ) { - return useMutation( - ({ client, args: { fee, memo, funds } = {} }) => client.withdraw(fee, memo, funds), + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.redeem(msg, fee, memo, funds), options, ) } export interface MockVaultDepositMutation { client: MockVaultClient + msg: { + amount: Uint128 + recipient?: string + } args?: { fee?: number | StdFee | 'auto' memo?: string @@ -248,7 +277,7 @@ export function useMockVaultDepositMutation( options?: Omit, 'mutationFn'>, ) { return useMutation( - ({ client, args: { fee, memo, funds } = {} }) => client.deposit(fee, memo, funds), + ({ client, msg, args: { fee, memo, funds } = {} }) => client.deposit(msg, fee, memo, funds), options, ) } diff --git a/scripts/types/generated/mock-vault/MockVault.types.ts b/scripts/types/generated/mock-vault/MockVault.types.ts index 6234300bb..59ab66608 100644 --- a/scripts/types/generated/mock-vault/MockVault.types.ts +++ b/scripts/types/generated/mock-vault/MockVault.types.ts @@ -1,74 +1,143 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ +export type Duration = + | { + height: number + } + | { + time: number + } export type OracleBaseForString = string export interface InstantiateMsg { - asset_denoms: string[] - lockup?: number | null - lp_token_denom: string + base_token_denom: string + lockup?: Duration | null oracle: OracleBaseForString + vault_token_denom: string } export type ExecuteMsg = | { - deposit: {} + deposit: { + amount: Uint128 + recipient?: string | null + } + } + | { + redeem: { + amount: Uint128 + recipient?: string | null + } + } + | { + vault_extension: ExtensionExecuteMsg } +export type Uint128 = string +export type ExtensionExecuteMsg = | { - withdraw: {} + lockup: LockupExecuteMsg } | { - force_withdraw: {} + force_unlock: ForceUnlockExecuteMsg } +export type LockupExecuteMsg = | { - request_unlock: {} + unlock: { + amount: Uint128 + } } | { withdraw_unlocked: { - id: Uint128 + lockup_id: number + recipient?: string | null + } + } +export type ForceUnlockExecuteMsg = + | { + force_redeem: { + amount: Uint128 + recipient?: string | null + } + } + | { + force_withdraw_unlocking: { + amount?: Uint128 | null + lockup_id: number + recipient?: string | null + } + } + | { + update_force_withdraw_whitelist: { + add_addresses: string[] + remove_addresses: string[] } } -export type Uint128 = string export type QueryMsg = + | { + vault_standard_info: {} + } | { info: {} } + | { + preview_deposit: { + amount: Uint128 + } + } | { preview_redeem: { amount: Uint128 } } | { - total_vault_coins_issued: {} + total_assets: {} + } + | { + total_vault_token_supply: {} } | { - unlocking_positions_for_addr: { - addr: string + convert_to_shares: { + amount: Uint128 } } | { - unlocking_position: { - id: Uint128 + convert_to_assets: { + amount: Uint128 } } + | { + vault_extension: ExtensionQueryMsg + } +export type ExtensionQueryMsg = { + lockup: LockupQueryMsg +} +export type LockupQueryMsg = + | { + lockups: { + limit?: number | null + owner: string + start_after?: number | null + } + } + | { + lockup: { + lockup_id: number + } + } + | { + lockup_duration: {} + } export interface VaultInfo { - accepts: string[][] - lockup?: number | null - vault_coin_denom: string + base_token: string + vault_token: string } -export type ArrayOfCoin = Coin[] -export interface Coin { - amount: Uint128 - denom: string +export interface Empty { [k: string]: unknown } -export type Timestamp = Uint64 -export type Uint64 = string -export interface UnlockingPosition { - amount: Uint128 - id: Uint128 - unlocked_at: Timestamp +export interface VaultStandardInfo { + extensions: string[] + version: number } -export type ArrayOfUnlockingPosition = UnlockingPosition[] diff --git a/scripts/types/generated/mock-vault/bundle.ts b/scripts/types/generated/mock-vault/bundle.ts index 8ea78d991..34a4b0df7 100644 --- a/scripts/types/generated/mock-vault/bundle.ts +++ b/scripts/types/generated/mock-vault/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mock-zapper/MockZapper.client.ts b/scripts/types/generated/mock-zapper/MockZapper.client.ts new file mode 100644 index 000000000..bc3eda946 --- /dev/null +++ b/scripts/types/generated/mock-zapper/MockZapper.client.ts @@ -0,0 +1,158 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + OracleBaseForString, + InstantiateMsg, + LpConfig, + ExecuteMsg, + Uint128, + QueryMsg, + Coin, + ArrayOfCoin, +} from './MockZapper.types' +export interface MockZapperReadOnlyInterface { + contractAddress: string + estimateProvideLiquidity: ({ + coinsIn, + lpTokenOut, + }: { + coinsIn: Coin[] + lpTokenOut: string + }) => Promise + estimateWithdrawLiquidity: ({ coinIn }: { coinIn: Coin }) => Promise +} +export class MockZapperQueryClient implements MockZapperReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.estimateProvideLiquidity = this.estimateProvideLiquidity.bind(this) + this.estimateWithdrawLiquidity = this.estimateWithdrawLiquidity.bind(this) + } + + estimateProvideLiquidity = async ({ + coinsIn, + lpTokenOut, + }: { + coinsIn: Coin[] + lpTokenOut: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + estimate_provide_liquidity: { + coins_in: coinsIn, + lp_token_out: lpTokenOut, + }, + }) + } + estimateWithdrawLiquidity = async ({ coinIn }: { coinIn: Coin }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + estimate_withdraw_liquidity: { + coin_in: coinIn, + }, + }) + } +} +export interface MockZapperInterface extends MockZapperReadOnlyInterface { + contractAddress: string + sender: string + provideLiquidity: ( + { + lpTokenOut, + minimumReceive, + recipient, + }: { + lpTokenOut: string + minimumReceive: Uint128 + recipient?: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + withdrawLiquidity: ( + { + recipient, + }: { + recipient?: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise +} +export class MockZapperClient extends MockZapperQueryClient implements MockZapperInterface { + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.provideLiquidity = this.provideLiquidity.bind(this) + this.withdrawLiquidity = this.withdrawLiquidity.bind(this) + } + + provideLiquidity = async ( + { + lpTokenOut, + minimumReceive, + recipient, + }: { + lpTokenOut: string + minimumReceive: Uint128 + recipient?: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + provide_liquidity: { + lp_token_out: lpTokenOut, + minimum_receive: minimumReceive, + recipient, + }, + }, + fee, + memo, + funds, + ) + } + withdrawLiquidity = async ( + { + recipient, + }: { + recipient?: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + withdraw_liquidity: { + recipient, + }, + }, + fee, + memo, + funds, + ) + } +} diff --git a/scripts/types/generated/mock-zapper/MockZapper.react-query.ts b/scripts/types/generated/mock-zapper/MockZapper.react-query.ts new file mode 100644 index 000000000..a2997d8d4 --- /dev/null +++ b/scripts/types/generated/mock-zapper/MockZapper.react-query.ts @@ -0,0 +1,152 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + OracleBaseForString, + InstantiateMsg, + LpConfig, + ExecuteMsg, + Uint128, + QueryMsg, + Coin, + ArrayOfCoin, +} from './MockZapper.types' +import { MockZapperQueryClient, MockZapperClient } from './MockZapper.client' +export const mockZapperQueryKeys = { + contract: [ + { + contract: 'mockZapper', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...mockZapperQueryKeys.contract[0], address: contractAddress }] as const, + estimateProvideLiquidity: (contractAddress: string | undefined, args?: Record) => + [ + { + ...mockZapperQueryKeys.address(contractAddress)[0], + method: 'estimate_provide_liquidity', + args, + }, + ] as const, + estimateWithdrawLiquidity: ( + contractAddress: string | undefined, + args?: Record, + ) => + [ + { + ...mockZapperQueryKeys.address(contractAddress)[0], + method: 'estimate_withdraw_liquidity', + args, + }, + ] as const, +} +export interface MockZapperReactQuery { + client: MockZapperQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MockZapperEstimateWithdrawLiquidityQuery + extends MockZapperReactQuery { + args: { + coinIn: Coin + } +} +export function useMockZapperEstimateWithdrawLiquidityQuery({ + client, + args, + options, +}: MockZapperEstimateWithdrawLiquidityQuery) { + return useQuery( + mockZapperQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), + () => + client + ? client.estimateWithdrawLiquidity({ + coinIn: args.coinIn, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockZapperEstimateProvideLiquidityQuery + extends MockZapperReactQuery { + args: { + coinsIn: Coin[] + lpTokenOut: string + } +} +export function useMockZapperEstimateProvideLiquidityQuery({ + client, + args, + options, +}: MockZapperEstimateProvideLiquidityQuery) { + return useQuery( + mockZapperQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), + () => + client + ? client.estimateProvideLiquidity({ + coinsIn: args.coinsIn, + lpTokenOut: args.lpTokenOut, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MockZapperWithdrawLiquidityMutation { + client: MockZapperClient + msg: { + recipient?: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockZapperWithdrawLiquidityMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.withdrawLiquidity(msg, fee, memo, funds), + options, + ) +} +export interface MockZapperProvideLiquidityMutation { + client: MockZapperClient + msg: { + lpTokenOut: string + minimumReceive: Uint128 + recipient?: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMockZapperProvideLiquidityMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.provideLiquidity(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mock-zapper/MockZapper.types.ts b/scripts/types/generated/mock-zapper/MockZapper.types.ts new file mode 100644 index 000000000..64b086509 --- /dev/null +++ b/scripts/types/generated/mock-zapper/MockZapper.types.ts @@ -0,0 +1,48 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export type OracleBaseForString = string +export interface InstantiateMsg { + lp_configs: LpConfig[] + oracle: OracleBaseForString +} +export interface LpConfig { + lp_pair_denoms: [string, string] + lp_token_denom: string +} +export type ExecuteMsg = + | { + provide_liquidity: { + lp_token_out: string + minimum_receive: Uint128 + recipient?: string | null + } + } + | { + withdraw_liquidity: { + recipient?: string | null + } + } +export type Uint128 = string +export type QueryMsg = + | { + estimate_provide_liquidity: { + coins_in: Coin[] + lp_token_out: string + } + } + | { + estimate_withdraw_liquidity: { + coin_in: Coin + } + } +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} +export type ArrayOfCoin = Coin[] diff --git a/scripts/types/generated/mock-zapper/bundle.ts b/scripts/types/generated/mock-zapper/bundle.ts new file mode 100644 index 000000000..6e12af719 --- /dev/null +++ b/scripts/types/generated/mock-zapper/bundle.ts @@ -0,0 +1,13 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _18 from './MockZapper.types' +import * as _19 from './MockZapper.client' +import * as _20 from './MockZapper.react-query' +export namespace contracts { + export const MockZapper = { ..._18, ..._19, ..._20 } +} diff --git a/scripts/types/generated/swapper-base/SwapperBase.client.ts b/scripts/types/generated/swapper-base/SwapperBase.client.ts index 8db2ed9ed..b056c255e 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.client.ts +++ b/scripts/types/generated/swapper-base/SwapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/swapper-base/SwapperBase.react-query.ts b/scripts/types/generated/swapper-base/SwapperBase.react-query.ts index 5f9795aae..554aeaceb 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.react-query.ts +++ b/scripts/types/generated/swapper-base/SwapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/swapper-base/SwapperBase.types.ts b/scripts/types/generated/swapper-base/SwapperBase.types.ts index 5b0f1a366..1d2483afa 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.types.ts +++ b/scripts/types/generated/swapper-base/SwapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/swapper-base/bundle.ts b/scripts/types/generated/swapper-base/bundle.ts index 4835afc43..f302a10e6 100644 --- a/scripts/types/generated/swapper-base/bundle.ts +++ b/scripts/types/generated/swapper-base/bundle.ts @@ -1,13 +1,13 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.19.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _18 from './SwapperBase.types' -import * as _19 from './SwapperBase.client' -import * as _20 from './SwapperBase.react-query' +import * as _21 from './SwapperBase.types' +import * as _22 from './SwapperBase.client' +import * as _23 from './SwapperBase.react-query' export namespace contracts { - export const SwapperBase = { ..._18, ..._19, ..._20 } + export const SwapperBase = { ..._21, ..._22, ..._23 } } diff --git a/scripts/types/instantiateMsgs.ts b/scripts/types/instantiateMsgs.ts index f52477d16..1a65447a9 100644 --- a/scripts/types/instantiateMsgs.ts +++ b/scripts/types/instantiateMsgs.ts @@ -4,6 +4,7 @@ import { InstantiateMsg as VaultInstantiateMsg } from './generated/mock-vault/Mo import { InstantiateMsg as OracleInstantiateMsg } from './generated/mock-oracle/MockOracle.types' import { InstantiateMsg as RoverInstantiateMsg } from './generated/credit-manager/CreditManager.types' import { InstantiateMsg as SwapperInstantiateMsg } from './generated/swapper-base/SwapperBase.types' +import { InstantiateMsg as ZapperInstantiateMsg } from './generated/mock-zapper/MockZapper.types' export type InstantiateMsgs = | NftInstantiateMsg @@ -12,3 +13,4 @@ export type InstantiateMsgs = | OracleInstantiateMsg | RoverInstantiateMsg | SwapperInstantiateMsg + | ZapperInstantiateMsg diff --git a/scripts/types/storageItems.ts b/scripts/types/storageItems.ts index 4f929322b..a53cff574 100644 --- a/scripts/types/storageItems.ts +++ b/scripts/types/storageItems.ts @@ -6,6 +6,7 @@ export interface StorageItems { mockOracle?: number marsOracleAdapter?: number swapper?: number + mockZapper?: number creditManager?: number } addresses: { @@ -13,6 +14,7 @@ export interface StorageItems { mockVault?: string marsOracleAdapter?: string swapper?: string + mockZapper?: string creditManager?: string } actions: { @@ -20,5 +22,9 @@ export interface StorageItems { acceptedOwnership?: boolean setRouteAndSeedSwapper?: boolean seedMockVault?: boolean + seedMockZapper?: boolean + grantedCreditLines?: boolean + oraclePricesSet?: boolean + redBankMarketsSet?: boolean } } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index e9350eade..750305e6a 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -4,7 +4,7 @@ "@ampproject/remapping@^2.1.0": version "2.2.0" - resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== dependencies: "@jridgewell/gen-mapping" "^0.1.0" @@ -12,29 +12,19 @@ "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.0.tgz" - integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== - -"@babel/compat-data@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.3.tgz#707b939793f867f5a73b2666e6d9a3396eb03151" - integrity sha512-prBHMK4JYYK+wDjJF1q99KK4JLL+egWS4nmNqdlMUgCExMZ+iZW0hGhyC3VEbsPjvaN0TBhW//VIFwBrk8sEiw== - -"@babel/compat-data@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.4.tgz#95c86de137bf0317f3a570e1b6e996b427299747" - integrity sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.4", "@babel/compat-data@^7.20.0": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30" + integrity sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ== "@babel/core@7.18.10": version "7.18.10" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.10.tgz#39ad504991d77f1f3da91be0b8b949a5bc466fb8" integrity sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw== dependencies: "@ampproject/remapping" "^2.1.0" @@ -54,20 +44,20 @@ semver "^6.3.0" "@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz" - integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.6.tgz#7122ae4f5c5a37c0946c066149abd8e75f81540f" + integrity sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.0" - "@babel/helper-compilation-targets" "^7.19.0" - "@babel/helper-module-transforms" "^7.19.0" - "@babel/helpers" "^7.19.0" - "@babel/parser" "^7.19.0" + "@babel/generator" "^7.19.6" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helpers" "^7.19.4" + "@babel/parser" "^7.19.6" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -76,60 +66,50 @@ "@babel/generator@7.18.12": version "7.18.12" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.12.tgz#fa58daa303757bd6f5e4bbca91b342040463d9f4" integrity sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg== dependencies: "@babel/types" "^7.18.10" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/generator@^7.18.10", "@babel/generator@^7.19.0", "@babel/generator@^7.7.2": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz" - integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== +"@babel/generator@^7.18.10", "@babel/generator@^7.19.6", "@babel/generator@^7.20.1", "@babel/generator@^7.7.2": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.1.tgz#ef32ecd426222624cbd94871a7024639cf61a9fa" + integrity sha512-u1dMdBUmA7Z0rBB97xh8pIhviK7oItYOkjbsCxTWMknyvbQRBwX7/gn4JXurRdirWMFh+ZtYARqkA6ydogVZpg== dependencies: - "@babel/types" "^7.19.0" + "@babel/types" "^7.20.0" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" "@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== dependencies: "@babel/types" "^7.18.6" "@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== dependencies: "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz" - integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.3": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a" + integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ== dependencies: - "@babel/compat-data" "^7.19.0" - "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.20.2" - semver "^6.3.0" - -"@babel/helper-compilation-targets@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz#a10a04588125675d7c7ae299af86fa1b2ee038ca" - integrity sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg== - dependencies: - "@babel/compat-data" "^7.19.3" + "@babel/compat-data" "^7.20.0" "@babel/helper-validator-option" "^7.18.6" browserslist "^4.21.3" semver "^6.3.0" "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0": version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b" integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" @@ -142,25 +122,13 @@ "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.19.0": version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.1.0" -"@babel/helper-define-polyfill-provider@^0.3.2": - version "0.3.2" - resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz" - integrity sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg== - dependencies: - "@babel/helper-compilation-targets" "^7.17.7" - "@babel/helper-plugin-utils" "^7.16.7" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - semver "^6.1.2" - -"@babel/helper-define-polyfill-provider@^0.3.3": +"@babel/helper-define-polyfill-provider@^0.3.2", "@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== @@ -174,19 +142,19 @@ "@babel/helper-environment-visitor@^7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== "@babel/helper-explode-assignable-expression@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== dependencies: "@babel/types" "^7.18.6" "@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== dependencies: "@babel/template" "^7.18.10" @@ -194,54 +162,54 @@ "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== dependencies: "@babel/types" "^7.18.6" "@babel/helper-member-expression-to-functions@^7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== dependencies: "@babel/types" "^7.18.9" "@babel/helper-module-imports@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz" - integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz#6c52cc3ac63b70952d33ee987cbee1c9368b533f" + integrity sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-simple-access" "^7.19.4" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== dependencies: "@babel/types" "^7.18.6" "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== "@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" @@ -250,65 +218,55 @@ "@babel/types" "^7.18.9" "@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz" - integrity sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" + integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-member-expression-to-functions" "^7.18.9" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/traverse" "^7.19.1" + "@babel/types" "^7.19.0" -"@babel/helper-simple-access@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz" - integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== +"@babel/helper-simple-access@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz#be553f4951ac6352df2567f7daa19a0ee15668e7" + integrity sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.19.4" "@babel/helper-skip-transparent-expression-wrappers@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz" - integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw== + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" + integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== dependencies: - "@babel/types" "^7.18.9" + "@babel/types" "^7.20.0" "@babel/helper-split-export-declaration@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== dependencies: "@babel/types" "^7.18.6" -"@babel/helper-string-parser@^7.18.10": - version "7.18.10" - resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz" - integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== - -"@babel/helper-string-parser@^7.19.4": +"@babel/helper-string-parser@^7.18.10", "@babel/helper-string-parser@^7.19.4": version "7.19.4" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== -"@babel/helper-validator-identifier@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz" - integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== - -"@babel/helper-validator-identifier@^7.19.1": +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== "@babel/helper-validator-option@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== "@babel/helper-wrap-function@^7.18.9": version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz#89f18335cff1152373222f76a4b37799636ae8b1" integrity sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg== dependencies: "@babel/helper-function-name" "^7.19.0" @@ -316,18 +274,18 @@ "@babel/traverse" "^7.19.0" "@babel/types" "^7.19.0" -"@babel/helpers@^7.18.9", "@babel/helpers@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz" - integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== +"@babel/helpers@^7.18.9", "@babel/helpers@^7.19.4": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.1.tgz#2ab7a0fcb0a03b5bf76629196ed63c2d7311f4c9" + integrity sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg== dependencies: "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" + "@babel/traverse" "^7.20.1" + "@babel/types" "^7.20.0" "@babel/highlight@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== dependencies: "@babel/helper-validator-identifier" "^7.18.6" @@ -336,44 +294,34 @@ "@babel/parser@7.18.11": version "7.18.11" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz" - integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.19.6", "@babel/parser@^7.20.1": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.1.tgz#3e045a92f7b4623cafc2425eddcb8cf2e54f9cc5" + integrity sha512-hp0AYxaZJhxULfM1zyp7Wgr+pSUKBcP3M+PHnSzWGdXOzg/kHWIgiUWARvubhUKGOEw3xqY4x+lyZ9ytBVcELw== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== dependencies: "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz#a11af19aa373d68d561f08e0a57242350ed0ec50" integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== dependencies: "@babel/helper-plugin-utils" "^7.18.9" "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-proposal-optional-chaining" "^7.18.9" -"@babel/plugin-proposal-async-generator-functions@^7.18.10": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.0.tgz" - integrity sha512-nhEByMUTx3uZueJ/QkJuSlCfN4FGg+xy+vRsfGQGzSauq5ks2Deid2+05Q3KhfaUjvec1IGhw/Zm3cFm8JigTQ== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-remap-async-to-generator" "^7.18.9" - "@babel/plugin-syntax-async-generators" "^7.8.4" - -"@babel/plugin-proposal-async-generator-functions@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz#34f6f5174b688529342288cd264f80c9ea9fb4a7" - integrity sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q== +"@babel/plugin-proposal-async-generator-functions@^7.18.10", "@babel/plugin-proposal-async-generator-functions@^7.19.1": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz#352f02baa5d69f4e7529bdac39aaa02d41146af9" + integrity sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-plugin-utils" "^7.19.0" @@ -382,7 +330,7 @@ "@babel/plugin-proposal-class-properties@7.18.6", "@babel/plugin-proposal-class-properties@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== dependencies: "@babel/helper-create-class-features-plugin" "^7.18.6" @@ -390,7 +338,7 @@ "@babel/plugin-proposal-class-static-block@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz#8aa81d403ab72d3962fc06c26e222dacfc9b9020" integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== dependencies: "@babel/helper-create-class-features-plugin" "^7.18.6" @@ -399,7 +347,7 @@ "@babel/plugin-proposal-dynamic-import@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94" integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== dependencies: "@babel/helper-plugin-utils" "^7.18.6" @@ -407,7 +355,7 @@ "@babel/plugin-proposal-export-default-from@7.18.10": version "7.18.10" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz#091f4794dbce4027c03cf4ebc64d3fb96b75c206" integrity sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow== dependencies: "@babel/helper-plugin-utils" "^7.18.9" @@ -415,7 +363,7 @@ "@babel/plugin-proposal-export-namespace-from@^7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203" integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== dependencies: "@babel/helper-plugin-utils" "^7.18.9" @@ -423,7 +371,7 @@ "@babel/plugin-proposal-json-strings@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b" integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== dependencies: "@babel/helper-plugin-utils" "^7.18.6" @@ -431,7 +379,7 @@ "@babel/plugin-proposal-logical-assignment-operators@^7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz#8148cbb350483bf6220af06fa6db3690e14b2e23" integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== dependencies: "@babel/helper-plugin-utils" "^7.18.9" @@ -439,7 +387,7 @@ "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== dependencies: "@babel/helper-plugin-utils" "^7.18.6" @@ -447,15 +395,15 @@ "@babel/plugin-proposal-numeric-separator@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75" integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== dependencies: "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@7.18.9", "@babel/plugin-proposal-object-rest-spread@^7.18.9": +"@babel/plugin-proposal-object-rest-spread@7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz#f9434f6beb2c8cae9dfcf97d2a5941bbbf9ad4e7" integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q== dependencies: "@babel/compat-data" "^7.18.8" @@ -464,7 +412,7 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.18.8" -"@babel/plugin-proposal-object-rest-spread@^7.19.4": +"@babel/plugin-proposal-object-rest-spread@^7.18.9", "@babel/plugin-proposal-object-rest-spread@^7.19.4": version "7.19.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz#a8fc86e8180ff57290c91a75d83fe658189b642d" integrity sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q== @@ -477,7 +425,7 @@ "@babel/plugin-proposal-optional-catch-binding@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== dependencies: "@babel/helper-plugin-utils" "^7.18.6" @@ -485,7 +433,7 @@ "@babel/plugin-proposal-optional-chaining@^7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993" integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== dependencies: "@babel/helper-plugin-utils" "^7.18.9" @@ -494,7 +442,7 @@ "@babel/plugin-proposal-private-methods@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== dependencies: "@babel/helper-create-class-features-plugin" "^7.18.6" @@ -502,7 +450,7 @@ "@babel/plugin-proposal-private-property-in-object@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz#a64137b232f0aca3733a67eb1a144c192389c503" integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" @@ -512,7 +460,7 @@ "@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.18.6" @@ -520,154 +468,154 @@ "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-bigint@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": version "7.12.13" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" "@babel/plugin-syntax-class-static-block@^7.14.5": version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-export-default-from@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz#8df076711a4818c4ce4f23e61d622b0ba2ff84bc" integrity sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew== dependencies: "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-export-namespace-from@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== dependencies: "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-import-assertions@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz" - integrity sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ== + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" + integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-jsx@^7.7.2": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== dependencies: "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-private-property-in-object@^7.14.5": version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.18.6", "@babel/plugin-syntax-typescript@^7.7.2": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz" - integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== +"@babel/plugin-syntax-typescript@^7.20.0", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz#4e9a0cfc769c85689b77a2e642d24e9f697fc8c7" + integrity sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-arrow-functions@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== dependencies: "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-async-to-generator@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz#ccda3d1ab9d5ced5265fdb13f1882d5476c71615" integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== dependencies: "@babel/helper-module-imports" "^7.18.6" @@ -676,28 +624,21 @@ "@babel/plugin-transform-block-scoped-functions@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-block-scoping@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz" - integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-block-scoping@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.19.4.tgz#315d70f68ce64426db379a3d830e7ac30be02e9b" - integrity sha512-934S2VLLlt2hRJwPf4MczaOr4hYF0z+VKPwqTNxyKX7NthTiPfhuKFWQZHXRM0vh/wo/VyXB3s4bZUNA08l+tQ== +"@babel/plugin-transform-block-scoping@^7.18.9", "@babel/plugin-transform-block-scoping@^7.19.4": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.0.tgz#91fe5e6ffc9ba13cb6c95ed7f0b1204f68c988c5" + integrity sha512-sXOohbpHZSk7GjxK9b3dKB7CfqUD5DwOH+DggKzOQ7TXYP+RCSbRykfjQmn/zq+rBjycVRtLf9pYhAaEJA786w== dependencies: "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-classes@^7.18.9", "@babel/plugin-transform-classes@^7.19.0": version "7.19.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz#0e61ec257fba409c41372175e7c1e606dc79bb20" integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" @@ -712,28 +653,21 @@ "@babel/plugin-transform-computed-properties@^7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-destructuring@^7.18.9": - version "7.18.13" - resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz" - integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-destructuring@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.19.4.tgz#46890722687b9b89e1369ad0bd8dc6c5a3b4319d" - integrity sha512-t0j0Hgidqf0aM86dF8U+vXYReUgJnlv4bZLsyoPnwZNrGY+7/38o8YjaELrvHeVfTZao15kjR0PVv0nju2iduA== +"@babel/plugin-transform-destructuring@^7.18.9", "@babel/plugin-transform-destructuring@^7.19.4": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.0.tgz#712829ef4825d9cc04bb379de316f981e9a6f648" + integrity sha512-1dIhvZfkDVx/zn2S1aFwlruspTt4189j7fEkH0Y0VyuDM6bQt7bD6kLcz3l4IlLG+e5OReaBz9ROAbttRtUHqA== dependencies: "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.18.6" @@ -741,14 +675,14 @@ "@babel/plugin-transform-duplicate-keys@^7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== dependencies: "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-exponentiation-operator@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== dependencies: "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" @@ -756,14 +690,14 @@ "@babel/plugin-transform-for-of@^7.18.8": version "7.18.8" - resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== dependencies: "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-function-name@^7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== dependencies: "@babel/helper-compilation-targets" "^7.18.9" @@ -772,65 +706,54 @@ "@babel/plugin-transform-literals@^7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== dependencies: "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-member-expression-literals@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== dependencies: "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-modules-amd@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz" - integrity sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg== + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz#aca391801ae55d19c4d8d2ebfeaa33df5f2a2cbd" + integrity sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg== dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-modules-commonjs@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz" - integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q== + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz#25b32feef24df8038fc1ec56038917eacb0b730c" + integrity sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ== dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-simple-access" "^7.18.6" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-simple-access" "^7.19.4" "@babel/plugin-transform-modules-systemjs@^7.18.9", "@babel/plugin-transform-modules-systemjs@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz" - integrity sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A== + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz#59e2a84064b5736a4471b1aa7b13d4431d327e0d" + integrity sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ== dependencies: "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.19.0" + "@babel/helper-module-transforms" "^7.19.6" "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-validator-identifier" "^7.18.6" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-validator-identifier" "^7.19.1" "@babel/plugin-transform-modules-umd@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== dependencies: "@babel/helper-module-transforms" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.0.tgz" - integrity sha512-HDSuqOQzkU//kfGdiHBt71/hkDTApw4U/cMVgKgX7PqfB3LOaK+2GtCEsBu1dL9CkswDm0Gwehht1dCr421ULQ== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": +"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6", "@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz#ec7455bab6cd8fb05c525a94876f435a48128888" integrity sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw== @@ -840,36 +763,36 @@ "@babel/plugin-transform-new-target@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8" integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== dependencies: "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-object-super@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== dependencies: "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-replace-supers" "^7.18.6" "@babel/plugin-transform-parameters@^7.18.8": - version "7.18.8" - resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz" - integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg== + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.1.tgz#9a5aa370fdcce36f110455e9369db7afca0f9eeb" + integrity sha512-nDvKLrAvl+kf6BOy1UJ3MGwzzfTMgppxwiD2Jb4LO3xjYyZq30oQzDNJbCQpMdG9+j2IXHoiMrw5Cm/L6ZoxXQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-property-literals@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== dependencies: "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-regenerator@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73" integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== dependencies: "@babel/helper-plugin-utils" "^7.18.6" @@ -877,14 +800,14 @@ "@babel/plugin-transform-reserved-words@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== dependencies: "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-runtime@7.18.10": version "7.18.10" - resolved "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz#37d14d1fa810a368fd635d4d1476c0154144a96f" integrity sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ== dependencies: "@babel/helper-module-imports" "^7.18.6" @@ -896,14 +819,14 @@ "@babel/plugin-transform-shorthand-properties@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== dependencies: "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-spread@^7.18.9", "@babel/plugin-transform-spread@^7.19.0": version "7.19.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== dependencies: "@babel/helper-plugin-utils" "^7.19.0" @@ -911,44 +834,44 @@ "@babel/plugin-transform-sticky-regex@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== dependencies: "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-template-literals@^7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== dependencies: "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-typeof-symbol@^7.18.9": version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== dependencies: "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-typescript@^7.18.6": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.0.tgz" - integrity sha512-DOOIywxPpkQHXijXv+s9MDAyZcLp12oYRl3CMWZ6u7TjSoCBq/KqHR/nNFR3+i2xqheZxoF0H2XyL7B6xeSRuA== + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.0.tgz#2c7ec62b8bfc21482f3748789ba294a46a375169" + integrity sha512-xOAsAFaun3t9hCwZ13Qe7gq423UgMZ6zAgmLxeGGapFqlT/X3L5qT2btjiVLlFn7gWtMaVyceS5VxGAuKbgizw== dependencies: "@babel/helper-create-class-features-plugin" "^7.19.0" "@babel/helper-plugin-utils" "^7.19.0" - "@babel/plugin-syntax-typescript" "^7.18.6" + "@babel/plugin-syntax-typescript" "^7.20.0" "@babel/plugin-transform-unicode-escapes@^7.18.10": version "7.18.10" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== dependencies: "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-unicode-regex@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.18.6" @@ -956,7 +879,7 @@ "@babel/preset-env@7.18.10": version "7.18.10" - resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.10.tgz#83b8dfe70d7eea1aae5a10635ab0a5fe60dfc0f4" integrity sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA== dependencies: "@babel/compat-data" "^7.18.8" @@ -1118,7 +1041,7 @@ "@babel/preset-modules@^0.1.5": version "0.1.5" - resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -1129,7 +1052,7 @@ "@babel/preset-typescript@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399" integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== dependencies: "@babel/helper-plugin-utils" "^7.18.6" @@ -1137,15 +1060,15 @@ "@babel/plugin-transform-typescript" "^7.18.6" "@babel/runtime@^7.11.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.8.4": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz" - integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9" + integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg== dependencies: - regenerator-runtime "^0.13.4" + regenerator-runtime "^0.13.10" "@babel/template@^7.18.10", "@babel/template@^7.3.3": version "7.18.10" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== dependencies: "@babel/code-frame" "^7.18.6" @@ -1154,7 +1077,7 @@ "@babel/traverse@7.18.11": version "7.18.11" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.11.tgz#3d51f2afbd83ecf9912bcbb5c4d94e3d2ddaa16f" integrity sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ== dependencies: "@babel/code-frame" "^7.18.6" @@ -1168,44 +1091,35 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.18.10", "@babel/traverse@^7.18.9", "@babel/traverse@^7.19.0", "@babel/traverse@^7.7.2": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.0.tgz" - integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== +"@babel/traverse@^7.18.10", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.19.6", "@babel/traverse@^7.20.1", "@babel/traverse@^7.7.2": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.1.tgz#9b15ccbf882f6d107eeeecf263fbcdd208777ec8" + integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.0" + "@babel/generator" "^7.20.1" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.0" - "@babel/types" "^7.19.0" + "@babel/parser" "^7.20.1" + "@babel/types" "^7.20.0" debug "^4.1.0" globals "^11.1.0" "@babel/types@7.18.10": version "7.18.10" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.10.tgz#4908e81b6b339ca7c6b7a555a5fc29446f26dde6" integrity sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ== dependencies: "@babel/helper-string-parser" "^7.18.10" "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz" - integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== - dependencies: - "@babel/helper-string-parser" "^7.18.10" - "@babel/helper-validator-identifier" "^7.18.6" - to-fast-properties "^2.0.0" - -"@babel/types@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" - integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.19.4", "@babel/types@^7.20.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.0.tgz#52c94cf8a7e24e89d2a194c25c35b17a64871479" + integrity sha512-Jlgt3H0TajCW164wkTOTzHkZb075tMQMULzrLUoUeKmO7eFL96GgDxf7/Axhc5CAuKE3KFyVW1p6ysKsi2oXAg== dependencies: "@babel/helper-string-parser" "^7.19.4" "@babel/helper-validator-identifier" "^7.19.1" @@ -1213,154 +1127,154 @@ "@bcoe/v8-coverage@^0.2.3": version "0.2.3" - resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== "@confio/ics23@^0.6.8": version "0.6.8" - resolved "https://registry.npmjs.org/@confio/ics23/-/ics23-0.6.8.tgz" + resolved "https://registry.yarnpkg.com/@confio/ics23/-/ics23-0.6.8.tgz#2a6b4f1f2b7b20a35d9a0745bb5a446e72930b3d" integrity sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w== dependencies: "@noble/hashes" "^1.0.0" protobufjs "^6.8.8" -"@cosmjs/amino@^0.29.1": - version "0.29.1" - resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.29.1.tgz#cdd77fbca4cd4a4d99540f885ceb0666f7afd348" - integrity sha512-Obw6qMLSUg2YmHOe9cHF9LofeLoz52I+1OUuVg7WdVDWK3kvWYA6oME/21h5XHb18pU5f0Nkcgz1SXTG8/stTQ== - dependencies: - "@cosmjs/crypto" "^0.29.1" - "@cosmjs/encoding" "^0.29.1" - "@cosmjs/math" "^0.29.1" - "@cosmjs/utils" "^0.29.1" - -"@cosmjs/cosmwasm-stargate@^0.29.1": - version "0.29.1" - resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.29.1.tgz#dbddffce2099e5cd78bb1dc956033d651a18a6f3" - integrity sha512-sUgxaM30IB3OcaW0PPO6JLvU8nSu8Pt5N5d743XKpdU32ocyyhtuUZmuhMaaFZBFpx7le9FjVViW+MfN2II69g== - dependencies: - "@cosmjs/amino" "^0.29.1" - "@cosmjs/crypto" "^0.29.1" - "@cosmjs/encoding" "^0.29.1" - "@cosmjs/math" "^0.29.1" - "@cosmjs/proto-signing" "^0.29.1" - "@cosmjs/stargate" "^0.29.1" - "@cosmjs/tendermint-rpc" "^0.29.1" - "@cosmjs/utils" "^0.29.1" - cosmjs-types "^0.5.0" +"@cosmjs/amino@^0.29.3": + version "0.29.3" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.29.3.tgz#5aa338a301ea970a93e15522706615efea507c10" + integrity sha512-BFz1++ERerIggiFc7iGHhGe1CeV3rCv8BvkoBQTBN/ZwzHOaKvqQj8smDlRGlQxX3HWlTwgiLN2A+OB5yX4ZRw== + dependencies: + "@cosmjs/crypto" "^0.29.3" + "@cosmjs/encoding" "^0.29.3" + "@cosmjs/math" "^0.29.3" + "@cosmjs/utils" "^0.29.3" + +"@cosmjs/cosmwasm-stargate@^0.29.3": + version "0.29.3" + resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.29.3.tgz#f6279fc6d590db01d6cb0f5cfae43bb2c88c279b" + integrity sha512-S13VlyWj2EGZ7hePT+kQkybksfjjYs/YffuZNVaD5P4CADiOcF52LGJcUCywyfYvC4RQYRVLOaq5hbAlLTUuew== + dependencies: + "@cosmjs/amino" "^0.29.3" + "@cosmjs/crypto" "^0.29.3" + "@cosmjs/encoding" "^0.29.3" + "@cosmjs/math" "^0.29.3" + "@cosmjs/proto-signing" "^0.29.3" + "@cosmjs/stargate" "^0.29.3" + "@cosmjs/tendermint-rpc" "^0.29.3" + "@cosmjs/utils" "^0.29.3" + cosmjs-types "^0.5.2" long "^4.0.0" pako "^2.0.2" -"@cosmjs/crypto@^0.29.1": - version "0.29.1" - resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.29.1.tgz#1c5b2fe6de08e43b1ca65c3b33e7a39c9e6f6cb1" - integrity sha512-H64KHUbp1nndPAJ3DoJs8OSOcpFjn2o/ptGgDaa1xhX4OiT7CF8IFoKyzzbPMLS/l/jWWkByc3uMn9R/LwW/Mg== +"@cosmjs/crypto@^0.29.3": + version "0.29.3" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.29.3.tgz#4af0ac1264f1638c31d03cfcbedf2fca36e17890" + integrity sha512-dOCYLLEOnn5idNgoAcdQnuvFXZx/BmLnb2Mh8ZZtw6peFNvRePfaMX12HerngkLVAcLBc/V6pZHWoj9DBrsvng== dependencies: - "@cosmjs/encoding" "^0.29.1" - "@cosmjs/math" "^0.29.1" - "@cosmjs/utils" "^0.29.1" + "@cosmjs/encoding" "^0.29.3" + "@cosmjs/math" "^0.29.3" + "@cosmjs/utils" "^0.29.3" "@noble/hashes" "^1" bn.js "^5.2.0" elliptic "^6.5.3" libsodium-wrappers "^0.7.6" -"@cosmjs/encoding@^0.29.1": - version "0.29.1" - resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.29.1.tgz#7fc49707e71fa4ee0ec398f9f127511839045ec5" - integrity sha512-spojVtRoPBxoOZ3n7ZqNSOJceTcnIKLY1yxMC6UL5a0HEPgGsJHx6LIJfcYnT4Gpp0kE1mzqRhVX9CT7Fbdw/g== +"@cosmjs/encoding@^0.29.3": + version "0.29.3" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.29.3.tgz#191fe1192d78ac0f9eb01b6e0aa4ba976cfb2c7a" + integrity sha512-K6CTcDGovwzF3QOmLm9mWwjcu4Md64zCOBYgVK3boGbsaExP/6YAjT22e+yDsReXWlEUtSVCjqCC/9EEcwmYmg== dependencies: base64-js "^1.3.0" bech32 "^1.1.4" readonly-date "^1.0.0" -"@cosmjs/json-rpc@^0.29.1": - version "0.29.1" - resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.29.1.tgz#56a1e542e5ec4594adde2349cbd78c9bbead1976" - integrity sha512-HG39w39xXExDSyIUg6JXeza82wSwcsWvkuu3WRUqTLZ73BlbEJBMwAqoJbstq7UklYNb3rJ+/qUvYYWiJcAryA== +"@cosmjs/json-rpc@^0.29.3": + version "0.29.3" + resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.29.3.tgz#17d99b71410c24e082d492d307ad25463d0a72d1" + integrity sha512-GP3qSMxVcoTQFI1/tWQDou843ZO0s51LaT+oaSr7F6C4XNCBv9BnSiVteijeZOaIPmhSBMnZs+7QDORlDHpS7A== dependencies: - "@cosmjs/stream" "^0.29.1" + "@cosmjs/stream" "^0.29.3" xstream "^11.14.0" -"@cosmjs/math@^0.29.1": - version "0.29.1" - resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.29.1.tgz#d71a6a2e95ac43380cd70c85c7656a6e8204e0e2" - integrity sha512-IBZHjKXReFIJY902wMFYB5mIQdppQdETbfQFrtXWNvXVOyavgOm60uLUZ2s3n9oAvZNpCiUFLnUulPT7M2LGKw== +"@cosmjs/math@^0.29.3": + version "0.29.3" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.29.3.tgz#29f98f6529f3d7654f1af85b329b48482eabbecb" + integrity sha512-4HGHqS+Yn81dZLOAYcDSQbROBD1a7ETW3ur5hziCTXMjZFILRJ3w71PlFUVppVb2u3kRDBBXuYHvZ6/V0M0nrg== dependencies: bn.js "^5.2.0" -"@cosmjs/proto-signing@^0.29.1": - version "0.29.1" - resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.1.tgz#925b074de560bd2ab536f4b33599efa4d5415891" - integrity sha512-k3fGmfA0IRBZrctgHHAfixZ03tXY4zKkxOGgB38pdTE3BKwr1rj1CCwrQALdqO5Xkdb2McIRisc5/eVmhJfcrA== - dependencies: - "@cosmjs/amino" "^0.29.1" - "@cosmjs/crypto" "^0.29.1" - "@cosmjs/encoding" "^0.29.1" - "@cosmjs/math" "^0.29.1" - "@cosmjs/utils" "^0.29.1" - cosmjs-types "^0.5.0" +"@cosmjs/proto-signing@^0.29.3": + version "0.29.3" + resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.3.tgz#fa5ed609ed2a0007d8d5eacbeb1f5a89ba1b77ff" + integrity sha512-Ai3l9THjMOrLJ4Ebn1Dgptwg6W5ZIRJqtnJjijHhGwTVC1WT0WdYU3aMZ7+PwubcA/cA1rH4ZTK7jrfYbra63g== + dependencies: + "@cosmjs/amino" "^0.29.3" + "@cosmjs/crypto" "^0.29.3" + "@cosmjs/encoding" "^0.29.3" + "@cosmjs/math" "^0.29.3" + "@cosmjs/utils" "^0.29.3" + cosmjs-types "^0.5.2" long "^4.0.0" -"@cosmjs/socket@^0.29.1": - version "0.29.1" - resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.29.1.tgz#7d83e87ca5157d07290032d51c1f75ba6be252fc" - integrity sha512-seHWBHRNdnl+neu0mY2JTRS/KQsStJpLgL1DRjG8uPiC3K1ZvBgXNX7bhdht81C//KDTbrjdQjWqX9vR6E6QAw== +"@cosmjs/socket@^0.29.3": + version "0.29.3" + resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.29.3.tgz#0c3fcf16066946c43a7666516ee0edc096ff977c" + integrity sha512-yP35avUsBId/HUBVPRg8z1KmW2iTjMNzflBcFVuTbVoDZrK9DHIlAsB8lV+XKIKPqqECvEq2Dtb1Z+XDy1WBEA== dependencies: - "@cosmjs/stream" "^0.29.1" + "@cosmjs/stream" "^0.29.3" isomorphic-ws "^4.0.1" ws "^7" xstream "^11.14.0" -"@cosmjs/stargate@^0.29.1": - version "0.29.1" - resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.29.1.tgz#85b739516318102c7907209f9273bd32faccf39c" - integrity sha512-6LYblr8XjIPat4HbW2k0bnPefBHM/0JJUvk8c0m6Nv/vQ9QcG8EIGkB/qndsx+gmPGPNpQ2ed/IpL3670KScmg== +"@cosmjs/stargate@^0.29.3": + version "0.29.3" + resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.29.3.tgz#9bd303bfd32a7399a233e662864e7cc32e2607af" + integrity sha512-455TgXStCi6E8KDjnhDAM8wt6aLSjobH4Dixvd7Up1DfCH6UB9NkC/G0fMJANNcNXMaM4wSX14niTXwD1d31BA== dependencies: "@confio/ics23" "^0.6.8" - "@cosmjs/amino" "^0.29.1" - "@cosmjs/encoding" "^0.29.1" - "@cosmjs/math" "^0.29.1" - "@cosmjs/proto-signing" "^0.29.1" - "@cosmjs/stream" "^0.29.1" - "@cosmjs/tendermint-rpc" "^0.29.1" - "@cosmjs/utils" "^0.29.1" - cosmjs-types "^0.5.0" + "@cosmjs/amino" "^0.29.3" + "@cosmjs/encoding" "^0.29.3" + "@cosmjs/math" "^0.29.3" + "@cosmjs/proto-signing" "^0.29.3" + "@cosmjs/stream" "^0.29.3" + "@cosmjs/tendermint-rpc" "^0.29.3" + "@cosmjs/utils" "^0.29.3" + cosmjs-types "^0.5.2" long "^4.0.0" protobufjs "~6.11.3" xstream "^11.14.0" -"@cosmjs/stream@^0.29.1": - version "0.29.1" - resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.29.1.tgz#128a8ced26b077befdb8e305a7994ac2324ff09b" - integrity sha512-QMvMzF39jDaBPavPRLjY+aiRiYDUZ/Lcd9WemvnlAs1nG4inILLQxfwnuCjY7q9BR6H4NHDr0oz4aQQFI+5xXg== +"@cosmjs/stream@^0.29.3": + version "0.29.3" + resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.29.3.tgz#9d9a9ec952cbc96f2e524204c4833980e314e6cd" + integrity sha512-0fbKvslZjNyuVe43cB9NDSqlBUXOHG84wGry4HmYfwayRtHr1CDWH5nR3v04eG0/prmZht8J3TgPsfWozIP+cw== dependencies: xstream "^11.14.0" -"@cosmjs/tendermint-rpc@^0.29.1": - version "0.29.1" - resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.1.tgz#d548c78869b92da733301111553b82f778b8a184" - integrity sha512-BqISUkgx/1WzagRGV1wASp9/0oVZew1kS8CUBUoMG9j+uu7iyWa5GmvW3Tumd/79mfZ4sqeQdrOB6y4yekS1qQ== - dependencies: - "@cosmjs/crypto" "^0.29.1" - "@cosmjs/encoding" "^0.29.1" - "@cosmjs/json-rpc" "^0.29.1" - "@cosmjs/math" "^0.29.1" - "@cosmjs/socket" "^0.29.1" - "@cosmjs/stream" "^0.29.1" - "@cosmjs/utils" "^0.29.1" +"@cosmjs/tendermint-rpc@^0.29.3": + version "0.29.3" + resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.3.tgz#054f80e5095cdf328d98fa7bcf23cd785435d247" + integrity sha512-4l3VacUMQdyGGqfzbZ02kEwlVdMVOdAeWJt2euoVdfUR/HT+TTzQrrL+ORj9PEooLLtwtMl9dqms8uEiblYBDg== + dependencies: + "@cosmjs/crypto" "^0.29.3" + "@cosmjs/encoding" "^0.29.3" + "@cosmjs/json-rpc" "^0.29.3" + "@cosmjs/math" "^0.29.3" + "@cosmjs/socket" "^0.29.3" + "@cosmjs/stream" "^0.29.3" + "@cosmjs/utils" "^0.29.3" axios "^0.21.2" readonly-date "^1.0.0" xstream "^11.14.0" -"@cosmjs/utils@^0.29.1": - version "0.29.1" - resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.1.tgz#f953bb7c213f257db4766bf30474b741348c9189" - integrity sha512-Nq0uJqzV7VoviJShtasUm41bDe7BArlkPaToEapGNPhR43pwKMowAMuZdJsKeTzEz9BtUAB/nCG+GO6/yCV1hQ== +"@cosmjs/utils@^0.29.3": + version "0.29.3" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.3.tgz#d7e1f381267e61b7d3219ebd75d46defc397cd43" + integrity sha512-UuKoBN2xiRXcBpz7jzCwagKhOnLOsRmR8mu3IzY+Yx38i8rW52FSXMbxC/yE83X0vLea+zgMQFPwv0gy4QWUJw== -"@cosmwasm/ts-codegen@^0.19.0": - version "0.19.0" - resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.19.0.tgz#30663019ba283dc50778209c984f8bd22ead47af" - integrity sha512-UTQnrxuxTVhaEGLB5gLCV5ppyF0WG6nGqY/eNHeVv3WeoB3CobeGFdsONbXz+whIX6+xJFQJ9YE+rmGVWVc0lQ== +"@cosmwasm/ts-codegen@^0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.20.0.tgz#786afd4c2c172040547134c1c3076fb47e823721" + integrity sha512-lrp61YkjONbuStNWQEKdD/MYQwREnQ2fNnI59dSa7VOqBxyhlBoOhj/6xjYFv4P3cm2Pd374kzfayhTkuFwhLw== dependencies: "@babel/core" "7.18.10" "@babel/generator" "7.18.12" @@ -1388,7 +1302,7 @@ parse-package-name "1.0.0" rimraf "3.0.2" shelljs "0.8.5" - wasm-ast-types "^0.13.0" + wasm-ast-types "^0.14.0" "@eslint/eslintrc@^1.3.3": version "1.3.3" @@ -1405,28 +1319,28 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.10.5": - version "0.10.5" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.5.tgz#bb679745224745fff1e9a41961c1d45a49f81c04" - integrity sha512-XVVDtp+dVvRxMoxSiSfasYaG02VEe1qH5cKgMQJWhol6HwzbcqoCMJi8dAGoYAO57jhUyhI6cWuRiTcRaDaYug== +"@humanwhocodes/config-array@^0.11.6": + version "0.11.7" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f" + integrity sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" - minimatch "^3.0.4" + minimatch "^3.0.5" "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== "@humanwhocodes/object-schema@^1.2.1": version "1.2.1" - resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" - resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== dependencies: camelcase "^5.3.1" @@ -1437,119 +1351,112 @@ "@istanbuljs/schema@^0.1.2": version "0.1.3" - resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.1.2.tgz#0ae975a70004696f8320490fcaa1a4152f7b62e4" - integrity sha512-ujEBCcYs82BTmRxqfHMQggSlkUZP63AE5YEaTPj7eFyJOzukkTorstOUC7L6nE3w5SYadGVAnTsQ/ZjTGL0qYQ== +"@jest/console@^29.2.1": + version "29.2.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.2.1.tgz#5f2c62dcdd5ce66e94b6d6729e021758bceea090" + integrity sha512-MF8Adcw+WPLZGBiNxn76DOuczG3BhODTcMlDCA4+cFi41OkaY/lyI0XUUhi73F88Y+7IHoGmD80pN5CtxQUdSw== dependencies: - "@jest/types" "^29.1.2" + "@jest/types" "^29.2.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.1.2" - jest-util "^29.1.2" + jest-message-util "^29.2.1" + jest-util "^29.2.1" slash "^3.0.0" -"@jest/core@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.1.2.tgz#e5ce7a71e7da45156a96fb5eeed11d18b67bd112" - integrity sha512-sCO2Va1gikvQU2ynDN8V4+6wB7iVrD2CvT0zaRst4rglf56yLly0NQ9nuRRAWFeimRf+tCdFsb1Vk1N9LrrMPA== +"@jest/core@^29.2.2": + version "29.2.2" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.2.2.tgz#207aa8973d9de8769f9518732bc5f781efc3ffa7" + integrity sha512-susVl8o2KYLcZhhkvSB+b7xX575CX3TmSvxfeDjpRko7KmT89rHkXj6XkDkNpSeFMBzIENw5qIchO9HC9Sem+A== dependencies: - "@jest/console" "^29.1.2" - "@jest/reporters" "^29.1.2" - "@jest/test-result" "^29.1.2" - "@jest/transform" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/console" "^29.2.1" + "@jest/reporters" "^29.2.2" + "@jest/test-result" "^29.2.1" + "@jest/transform" "^29.2.2" + "@jest/types" "^29.2.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^29.0.0" - jest-config "^29.1.2" - jest-haste-map "^29.1.2" - jest-message-util "^29.1.2" - jest-regex-util "^29.0.0" - jest-resolve "^29.1.2" - jest-resolve-dependencies "^29.1.2" - jest-runner "^29.1.2" - jest-runtime "^29.1.2" - jest-snapshot "^29.1.2" - jest-util "^29.1.2" - jest-validate "^29.1.2" - jest-watcher "^29.1.2" + jest-changed-files "^29.2.0" + jest-config "^29.2.2" + jest-haste-map "^29.2.1" + jest-message-util "^29.2.1" + jest-regex-util "^29.2.0" + jest-resolve "^29.2.2" + jest-resolve-dependencies "^29.2.2" + jest-runner "^29.2.2" + jest-runtime "^29.2.2" + jest-snapshot "^29.2.2" + jest-util "^29.2.1" + jest-validate "^29.2.2" + jest-watcher "^29.2.2" micromatch "^4.0.4" - pretty-format "^29.1.2" + pretty-format "^29.2.1" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.1.2.tgz#bb51a43fce9f960ba9a48f0b5b556f30618ebc0a" - integrity sha512-rG7xZ2UeOfvOVzoLIJ0ZmvPl4tBEQ2n73CZJSlzUjPw4or1oSWC0s0Rk0ZX+pIBJ04aVr6hLWFn1DFtrnf8MhQ== +"@jest/environment@^29.2.2": + version "29.2.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.2.2.tgz#481e729048d42e87d04842c38aa4d09c507f53b0" + integrity sha512-OWn+Vhu0I1yxuGBJEFFekMYc8aGBGrY4rt47SOh/IFaI+D7ZHCk7pKRiSoZ2/Ml7b0Ony3ydmEHRx/tEOC7H1A== dependencies: - "@jest/fake-timers" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/fake-timers" "^29.2.2" + "@jest/types" "^29.2.1" "@types/node" "*" - jest-mock "^29.1.2" - -"@jest/expect-utils@^29.0.3": - version "29.0.3" - resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.0.3.tgz" - integrity sha512-i1xUkau7K/63MpdwiRqaxgZOjxYs4f0WMTGJnYwUKubsNRZSeQbLorS7+I4uXVF9KQ5r61BUPAUMZ7Lf66l64Q== - dependencies: - jest-get-type "^29.0.0" + jest-mock "^29.2.2" -"@jest/expect-utils@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.1.2.tgz#66dbb514d38f7d21456bc774419c9ae5cca3f88d" - integrity sha512-4a48bhKfGj/KAH39u0ppzNTABXQ8QPccWAFUFobWBaEMSMp+sB31Z2fK/l47c4a/Mu1po2ffmfAIPxXbVTXdtg== +"@jest/expect-utils@^29.2.2": + version "29.2.2" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.2.2.tgz#460a5b5a3caf84d4feb2668677393dd66ff98665" + integrity sha512-vwnVmrVhTmGgQzyvcpze08br91OL61t9O0lJMDyb6Y/D8EKQ9V7rGUb/p7PDt0GPzK0zFYqXWFo4EO2legXmkg== dependencies: - jest-get-type "^29.0.0" + jest-get-type "^29.2.0" -"@jest/expect@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.1.2.tgz#334a86395f621f1ab63ad95b06a588b9114d7b7a" - integrity sha512-FXw/UmaZsyfRyvZw3M6POgSNqwmuOXJuzdNiMWW9LCYo0GRoRDhg+R5iq5higmRTHQY7hx32+j7WHwinRmoILQ== +"@jest/expect@^29.2.2": + version "29.2.2" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.2.2.tgz#81edbd33afbde7795ca07ff6b4753d15205032e4" + integrity sha512-zwblIZnrIVt8z/SiEeJ7Q9wKKuB+/GS4yZe9zw7gMqfGf4C5hBLGrVyxu1SzDbVSqyMSlprKl3WL1r80cBNkgg== dependencies: - expect "^29.1.2" - jest-snapshot "^29.1.2" + expect "^29.2.2" + jest-snapshot "^29.2.2" -"@jest/fake-timers@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.1.2.tgz#f157cdf23b4da48ce46cb00fea28ed1b57fc271a" - integrity sha512-GppaEqS+QQYegedxVMpCe2xCXxxeYwQ7RsNx55zc8f+1q1qevkZGKequfTASI7ejmg9WwI+SJCrHe9X11bLL9Q== +"@jest/fake-timers@^29.2.2": + version "29.2.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.2.2.tgz#d8332e6e3cfa99cde4bc87d04a17d6b699deb340" + integrity sha512-nqaW3y2aSyZDl7zQ7t1XogsxeavNpH6kkdq+EpXncIDvAkjvFD7hmhcIs1nWloengEWUoWqkqSA6MSbf9w6DgA== dependencies: - "@jest/types" "^29.1.2" + "@jest/types" "^29.2.1" "@sinonjs/fake-timers" "^9.1.2" "@types/node" "*" - jest-message-util "^29.1.2" - jest-mock "^29.1.2" - jest-util "^29.1.2" + jest-message-util "^29.2.1" + jest-mock "^29.2.2" + jest-util "^29.2.1" -"@jest/globals@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.1.2.tgz#826ede84bc280ae7f789cb72d325c48cd048b9d3" - integrity sha512-uMgfERpJYoQmykAd0ffyMq8wignN4SvLUG6orJQRe9WAlTRc9cdpCaE/29qurXixYJVZWUqIBXhSk8v5xN1V9g== +"@jest/globals@^29.2.2": + version "29.2.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.2.2.tgz#205ff1e795aa774301c2c0ba0be182558471b845" + integrity sha512-/nt+5YMh65kYcfBhj38B3Hm0Trk4IsuMXNDGKE/swp36yydBWfz3OXkLqkSvoAtPW8IJMSJDFCbTM2oj5SNprw== dependencies: - "@jest/environment" "^29.1.2" - "@jest/expect" "^29.1.2" - "@jest/types" "^29.1.2" - jest-mock "^29.1.2" + "@jest/environment" "^29.2.2" + "@jest/expect" "^29.2.2" + "@jest/types" "^29.2.1" + jest-mock "^29.2.2" -"@jest/reporters@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.1.2.tgz#5520898ed0a4ecf69d8b671e1dc8465d0acdfa6e" - integrity sha512-X4fiwwyxy9mnfpxL0g9DD0KcTmEIqP0jUdnc2cfa9riHy+I6Gwwp5vOZiwyg0vZxfSDxrOlK9S4+340W4d+DAA== +"@jest/reporters@^29.2.2": + version "29.2.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.2.2.tgz#69b395f79c3a97ce969ce05ccf1a482e5d6de290" + integrity sha512-AzjL2rl2zJC0njIzcooBvjA4sJjvdoq98sDuuNs4aNugtLPSQ+91nysGKRF0uY1to5k0MdGMdOBggUsPqvBcpA== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.1.2" - "@jest/test-result" "^29.1.2" - "@jest/transform" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/console" "^29.2.1" + "@jest/test-result" "^29.2.1" + "@jest/transform" "^29.2.2" + "@jest/types" "^29.2.1" "@jridgewell/trace-mapping" "^0.3.15" "@types/node" "*" chalk "^4.0.0" @@ -1562,61 +1469,60 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.1.2" - jest-util "^29.1.2" - jest-worker "^29.1.2" + jest-message-util "^29.2.1" + jest-util "^29.2.1" + jest-worker "^29.2.1" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" - terminal-link "^2.0.0" v8-to-istanbul "^9.0.1" "@jest/schemas@^28.1.3": version "28.1.3" - resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-28.1.3.tgz#ad8b86a66f11f33619e3d7e1dcddd7f2d40ff905" integrity sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg== dependencies: "@sinclair/typebox" "^0.24.1" "@jest/schemas@^29.0.0": version "29.0.0" - resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.0.0.tgz#5f47f5994dd4ef067fb7b4188ceac45f77fe952a" integrity sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA== dependencies: "@sinclair/typebox" "^0.24.1" -"@jest/source-map@^29.0.0": - version "29.0.0" - resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-29.0.0.tgz" - integrity sha512-nOr+0EM8GiHf34mq2GcJyz/gYFyLQ2INDhAylrZJ9mMWoW21mLBfZa0BUVPPMxVYrLjeiRe2Z7kWXOGnS0TFhQ== +"@jest/source-map@^29.2.0": + version "29.2.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.2.0.tgz#ab3420c46d42508dcc3dc1c6deee0b613c235744" + integrity sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ== dependencies: "@jridgewell/trace-mapping" "^0.3.15" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.1.2.tgz#6a8d006eb2b31ce0287d1fc10d12b8ff8504f3c8" - integrity sha512-jjYYjjumCJjH9hHCoMhA8PCl1OxNeGgAoZ7yuGYILRJX9NjgzTN0pCT5qAoYR4jfOP8htIByvAlz9vfNSSBoVg== +"@jest/test-result@^29.2.1": + version "29.2.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.2.1.tgz#f42dbf7b9ae465d0a93eee6131473b8bb3bd2edb" + integrity sha512-lS4+H+VkhbX6z64tZP7PAUwPqhwj3kbuEHcaLuaBuB+riyaX7oa1txe0tXgrFj5hRWvZKvqO7LZDlNWeJ7VTPA== dependencies: - "@jest/console" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/console" "^29.2.1" + "@jest/types" "^29.2.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.1.2.tgz#10bfd89c08bfdba382eb05cc79c1d23a01238a93" - integrity sha512-fU6dsUqqm8sA+cd85BmeF7Gu9DsXVWFdGn9taxM6xN1cKdcP/ivSgXh5QucFRFz1oZxKv3/9DYYbq0ULly3P/Q== +"@jest/test-sequencer@^29.2.2": + version "29.2.2" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.2.2.tgz#4ac7487b237e517a1f55e7866fb5553f6e0168b9" + integrity sha512-Cuc1znc1pl4v9REgmmLf0jBd3Y65UXJpioGYtMr/JNpQEIGEzkmHhy6W6DLbSsXeUA13TDzymPv0ZGZ9jH3eIw== dependencies: - "@jest/test-result" "^29.1.2" + "@jest/test-result" "^29.2.1" graceful-fs "^4.2.9" - jest-haste-map "^29.1.2" + jest-haste-map "^29.2.1" slash "^3.0.0" "@jest/transform@28.1.3": version "28.1.3" - resolved "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.3.tgz#59d8098e50ab07950e0f2fc0fc7ec462371281b0" integrity sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA== dependencies: "@babel/core" "^7.11.6" @@ -1635,22 +1541,22 @@ slash "^3.0.0" write-file-atomic "^4.0.1" -"@jest/transform@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.1.2.tgz#20f814696e04f090421f6d505c14bbfe0157062a" - integrity sha512-2uaUuVHTitmkx1tHF+eBjb4p7UuzBG7SXIaA/hNIkaMP6K+gXYGxP38ZcrofzqN0HeZ7A90oqsOa97WU7WZkSw== +"@jest/transform@^29.2.2": + version "29.2.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.2.2.tgz#dfc03fc092b31ffea0c55917728e75bfcf8b5de6" + integrity sha512-aPe6rrletyuEIt2axxgdtxljmzH8O/nrov4byy6pDw9S8inIrTV+2PnjyP/oFHMSynzGxJ2s6OHowBNMXp/Jzg== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.1.2" + "@jest/types" "^29.2.1" "@jridgewell/trace-mapping" "^0.3.15" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.1.2" - jest-regex-util "^29.0.0" - jest-util "^29.1.2" + jest-haste-map "^29.2.1" + jest-regex-util "^29.2.0" + jest-util "^29.2.1" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" @@ -1658,7 +1564,7 @@ "@jest/types@^28.1.3": version "28.1.3" - resolved "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.3.tgz#b05de80996ff12512bc5ceb1d208285a7d11748b" integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ== dependencies: "@jest/schemas" "^28.1.3" @@ -1668,22 +1574,10 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jest/types@^29.0.3": - version "29.0.3" - resolved "https://registry.npmjs.org/@jest/types/-/types-29.0.3.tgz" - integrity sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A== - dependencies: - "@jest/schemas" "^29.0.0" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jest/types@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.1.2.tgz#7442d32b16bcd7592d9614173078b8c334ec730a" - integrity sha512-DcXGtoTykQB5jiwCmVr8H4vdg2OJhQex3qPkG+ISyDO7xQXbt/4R6dowcRyPemRnkH7JoHvZuxPBdlq+9JxFCg== +"@jest/types@^29.2.1": + version "29.2.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.2.1.tgz#ec9c683094d4eb754e41e2119d8bdaef01cf6da0" + integrity sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw== dependencies: "@jest/schemas" "^29.0.0" "@types/istanbul-lib-coverage" "^2.0.0" @@ -1694,7 +1588,7 @@ "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== dependencies: "@jridgewell/set-array" "^1.0.0" @@ -1702,49 +1596,49 @@ "@jridgewell/gen-mapping@^0.3.2": version "0.3.2" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== dependencies: "@jridgewell/set-array" "^1.0.1" "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@^3.0.3": +"@jridgewell/resolve-uri@3.1.0": version "3.1.0" - resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== "@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": version "1.1.2" - resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.15" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz" - integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" "@jsdevtools/ono@^7.1.3": version "7.1.3" - resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz" + resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== "@noble/hashes@^1", "@noble/hashes@^1.0.0": - version "1.1.2" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz" - integrity sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA== + version "1.1.3" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.3.tgz#360afc77610e0a61f3417e497dcf36862e4f8111" + integrity sha512-CE0FCR57H2acVI5UOzIGSSIYxZ6v/HOhDR0Ro9VLyhnzLwx0o8W1mmgaqlEUx4049qJDlIBRztv5k+MM8vbO3A== "@nodelib/fs.scandir@2.1.5": version "2.1.5" - resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: "@nodelib/fs.stat" "2.0.5" @@ -1752,12 +1646,12 @@ "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" - resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" - resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" @@ -1765,27 +1659,27 @@ "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" - resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== "@protobufjs/base64@^1.1.2": version "1.1.2" - resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== "@protobufjs/codegen@^2.0.4": version "2.0.4" - resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== "@protobufjs/eventemitter@^1.1.0": version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== "@protobufjs/fetch@^1.1.0": version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== dependencies: "@protobufjs/aspromise" "^1.1.1" @@ -1793,32 +1687,32 @@ "@protobufjs/float@^1.0.2": version "1.0.2" - resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== "@protobufjs/inquire@^1.1.0": version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== "@protobufjs/path@^1.1.2": version "1.1.2" - resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== "@protobufjs/pool@^1.1.0": version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== "@protobufjs/utf8@^1.1.0": version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== "@pyramation/json-schema-ref-parser@9.0.6": version "9.0.6" - resolved "https://registry.npmjs.org/@pyramation/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz" + resolved "https://registry.yarnpkg.com/@pyramation/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#556e416ce7dcc15a3c1afd04d6a059e03ed09aeb" integrity sha512-L5kToHAEc1Q87R8ZwWFaNa4tPHr8Hnm+U+DRdUVq3tUtk+EX4pCqSd34Z6EMxNi/bjTzt1syAG9J2Oo1YFlqSg== dependencies: "@jsdevtools/ono" "^7.1.3" @@ -1827,7 +1721,7 @@ "@pyramation/json-schema-to-typescript@ 11.0.4": version "11.0.4" - resolved "https://registry.npmjs.org/@pyramation/json-schema-to-typescript/-/json-schema-to-typescript-11.0.4.tgz" + resolved "https://registry.yarnpkg.com/@pyramation/json-schema-to-typescript/-/json-schema-to-typescript-11.0.4.tgz#959bdb631dad336e1fdbf608a9b5908ab0da1d6b" integrity sha512-+aSzXDLhMHOEdV2cJ7Tjg/9YenjHU5BCmClVygzwxJZ1R16NOfEn7lTAwVzb/2jivOSnhjHzMJbnSf8b6rd1zg== dependencies: "@pyramation/json-schema-ref-parser" "9.0.6" @@ -1846,27 +1740,27 @@ prettier "^2.6.2" "@sinclair/typebox@^0.24.1": - version "0.24.40" - resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.40.tgz" - integrity sha512-Xint60L8rF0+nRy+6fCjW9jQMmu7fTpbwTBrXZiK6eq/RHDJS7LvWX/0oXC8O7fCePmrY/XdfaTv2HiUDeCq4g== + version "0.24.51" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" + integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== "@sinonjs/commons@^1.7.0": - version "1.8.3" - resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + version "1.8.4" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.4.tgz#d1f2d80f1bd0f2520873f161588bd9b7f8567120" + integrity sha512-RpmQdHVo8hCEHDVpO39zToS9jOhR6nw+/lQAzRNq9ErrGV9IeHM71XCn68svVl/euFeVW6BWX4p35gkhbOcSIQ== dependencies: type-detect "4.0.8" "@sinonjs/fake-timers@^9.1.2": version "9.1.2" - resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== dependencies: "@sinonjs/commons" "^1.7.0" "@types/babel__core@^7.1.14": version "7.1.19" - resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== dependencies: "@babel/parser" "^7.1.0" @@ -1877,29 +1771,29 @@ "@types/babel__generator@*": version "7.6.4" - resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": version "7.4.1" - resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.18.1" - resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz" - integrity sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA== + version "7.18.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.2.tgz#235bf339d17185bdec25e024ca19cce257cc7309" + integrity sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg== dependencies: "@babel/types" "^7.3.0" "@types/glob@^7.1.3": version "7.2.0" - resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== dependencies: "@types/minimatch" "*" @@ -1907,179 +1801,186 @@ "@types/graceful-fs@^4.1.3": version "4.1.5" - resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== dependencies: "@types/node" "*" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" - resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== "@types/istanbul-lib-report@*": version "3.0.0" - resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== dependencies: "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^3.0.0": version "3.0.1" - resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== dependencies: "@types/istanbul-lib-report" "*" "@types/jest@^29.1.2": - version "29.1.2" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.1.2.tgz#7ad8077043ab5f6c108c8111bcc1d224e5600a87" - integrity sha512-y+nlX0h87U0R+wsGn6EBuoRWYyv3KFtwRNP3QWp9+k2tJ2/bqcGS3UxD7jgT+tiwJWWq3UsyV4Y+T6rsMT4XMg== + version "29.2.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.1.tgz#31fda30bdf2861706abc5f1730be78bed54f83ee" + integrity sha512-nKixEdnGDqFOZkMTF74avFNr3yRqB1ZJ6sRZv5/28D5x2oLN14KApv7F9mfDT/vUic0L3tRCsh3XWpWjtJisUQ== dependencies: expect "^29.0.0" pretty-format "^29.0.0" "@types/json-schema@^7.0.11", "@types/json-schema@^7.0.9": version "7.0.11" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/lodash@^4.14.182": - version "4.14.185" - resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.185.tgz" - integrity sha512-evMDG1bC4rgQg4ku9tKpuMh5iBNEwNa3tf9zRHdP1qlv+1WUg44xat4IxCE14gIpZRGUUWAx2VhItCZc25NfMA== + version "4.14.187" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.187.tgz#122ff0a7192115b4c1a19444ab4482caa77e2c9d" + integrity sha512-MrO/xLXCaUgZy3y96C/iOsaIqZSeupyTImKClHunL5GrmaiII2VwvWmLBu2hwa0Kp0sV19CsyjtrTc/Fx8rg/A== "@types/long@^4.0.1": version "4.0.2" - resolved "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== "@types/minimatch@*": version "5.1.2" - resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/node@*", "@types/node@>=13.7.0": - version "18.7.16" - resolved "https://registry.npmjs.org/@types/node/-/node-18.7.16.tgz" - integrity sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg== + version "18.11.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" + integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== "@types/prettier@^2.1.5", "@types/prettier@^2.6.1": - version "2.7.0" - resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz" - integrity sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A== + version "2.7.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e" + integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow== + +"@types/semver@^7.3.12": + version "7.3.13" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" + integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== "@types/stack-utils@^2.0.0": version "2.0.1" - resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== "@types/yargs-parser@*": version "21.0.0" - resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== "@types/yargs@^17.0.8": - version "17.0.12" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz" - integrity sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ== + version "17.0.13" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76" + integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg== dependencies: "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^5.40.0": - version "5.40.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.40.0.tgz#0159bb71410eec563968288a17bd4478cdb685bd" - integrity sha512-FIBZgS3DVJgqPwJzvZTuH4HNsZhHMa9SjxTKAZTlMsPw/UzpEjcf9f4dfgDJEHjK+HboUJo123Eshl6niwEm/Q== + version "5.42.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.42.0.tgz#36a8c0c379870127059889a9cc7e05c260d2aaa5" + integrity sha512-5TJh2AgL6+wpL8H/GTSjNb4WrjKoR2rqvFxR/DDTqYNk6uXn8BJMEcncLSpMbf/XV1aS0jAjYwn98uvVCiAywQ== dependencies: - "@typescript-eslint/scope-manager" "5.40.0" - "@typescript-eslint/type-utils" "5.40.0" - "@typescript-eslint/utils" "5.40.0" + "@typescript-eslint/scope-manager" "5.42.0" + "@typescript-eslint/type-utils" "5.42.0" + "@typescript-eslint/utils" "5.42.0" debug "^4.3.4" ignore "^5.2.0" + natural-compare-lite "^1.4.0" regexpp "^3.2.0" semver "^7.3.7" tsutils "^3.21.0" "@typescript-eslint/parser@^5.40.0": - version "5.40.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.40.0.tgz#432bddc1fe9154945660f67c1ba6d44de5014840" - integrity sha512-Ah5gqyX2ySkiuYeOIDg7ap51/b63QgWZA7w6AHtFrag7aH0lRQPbLzUjk0c9o5/KZ6JRkTTDKShL4AUrQa6/hw== + version "5.42.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.42.0.tgz#be0ffbe279e1320e3d15e2ef0ad19262f59e9240" + integrity sha512-Ixh9qrOTDRctFg3yIwrLkgf33AHyEIn6lhyf5cCfwwiGtkWhNpVKlEZApi3inGQR/barWnY7qY8FbGKBO7p3JA== dependencies: - "@typescript-eslint/scope-manager" "5.40.0" - "@typescript-eslint/types" "5.40.0" - "@typescript-eslint/typescript-estree" "5.40.0" + "@typescript-eslint/scope-manager" "5.42.0" + "@typescript-eslint/types" "5.42.0" + "@typescript-eslint/typescript-estree" "5.42.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.40.0": - version "5.40.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.40.0.tgz#d6ea782c8e3a2371ba3ea31458dcbdc934668fc4" - integrity sha512-d3nPmjUeZtEWRvyReMI4I1MwPGC63E8pDoHy0BnrYjnJgilBD3hv7XOiETKLY/zTwI7kCnBDf2vWTRUVpYw0Uw== +"@typescript-eslint/scope-manager@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.42.0.tgz#e1f2bb26d3b2a508421ee2e3ceea5396b192f5ef" + integrity sha512-l5/3IBHLH0Bv04y+H+zlcLiEMEMjWGaCX6WyHE5Uk2YkSGAMlgdUPsT/ywTSKgu9D1dmmKMYgYZijObfA39Wow== dependencies: - "@typescript-eslint/types" "5.40.0" - "@typescript-eslint/visitor-keys" "5.40.0" + "@typescript-eslint/types" "5.42.0" + "@typescript-eslint/visitor-keys" "5.42.0" -"@typescript-eslint/type-utils@5.40.0": - version "5.40.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.40.0.tgz#4964099d0158355e72d67a370249d7fc03331126" - integrity sha512-nfuSdKEZY2TpnPz5covjJqav+g5qeBqwSHKBvz7Vm1SAfy93SwKk/JeSTymruDGItTwNijSsno5LhOHRS1pcfw== +"@typescript-eslint/type-utils@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.42.0.tgz#4206d7192d4fe903ddf99d09b41d4ac31b0b7dca" + integrity sha512-HW14TXC45dFVZxnVW8rnUGnvYyRC0E/vxXShFCthcC9VhVTmjqOmtqj6H5rm9Zxv+ORxKA/1aLGD7vmlLsdlOg== dependencies: - "@typescript-eslint/typescript-estree" "5.40.0" - "@typescript-eslint/utils" "5.40.0" + "@typescript-eslint/typescript-estree" "5.42.0" + "@typescript-eslint/utils" "5.42.0" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.40.0": - version "5.40.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.40.0.tgz#8de07e118a10b8f63c99e174a3860f75608c822e" - integrity sha512-V1KdQRTXsYpf1Y1fXCeZ+uhjW48Niiw0VGt4V8yzuaDTU8Z1Xl7yQDyQNqyAFcVhpYXIVCEuxSIWTsLDpHgTbw== +"@typescript-eslint/types@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.42.0.tgz#5aeff9b5eced48f27d5b8139339bf1ef805bad7a" + integrity sha512-t4lzO9ZOAUcHY6bXQYRuu+3SSYdD9TS8ooApZft4WARt4/f2Cj/YpvbTe8A4GuhT4bNW72goDMOy7SW71mZwGw== -"@typescript-eslint/typescript-estree@5.40.0": - version "5.40.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.40.0.tgz#e305e6a5d65226efa5471ee0f12e0ffaab6d3075" - integrity sha512-b0GYlDj8TLTOqwX7EGbw2gL5EXS2CPEWhF9nGJiGmEcmlpNBjyHsTwbqpyIEPVpl6br4UcBOYlcI2FJVtJkYhg== +"@typescript-eslint/typescript-estree@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.0.tgz#2592d24bb5f89bf54a63384ff3494870f95b3fd8" + integrity sha512-2O3vSq794x3kZGtV7i4SCWZWCwjEtkWfVqX4m5fbUBomOsEOyd6OAD1qU2lbvV5S8tgy/luJnOYluNyYVeOTTg== dependencies: - "@typescript-eslint/types" "5.40.0" - "@typescript-eslint/visitor-keys" "5.40.0" + "@typescript-eslint/types" "5.42.0" + "@typescript-eslint/visitor-keys" "5.42.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.40.0": - version "5.40.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.40.0.tgz#647f56a875fd09d33c6abd70913c3dd50759b772" - integrity sha512-MO0y3T5BQ5+tkkuYZJBjePewsY+cQnfkYeRqS6tPh28niiIwPnQ1t59CSRcs1ZwJJNOdWw7rv9pF8aP58IMihA== +"@typescript-eslint/utils@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.42.0.tgz#f06bd43b9a9a06ed8f29600273240e84a53f2f15" + integrity sha512-JZ++3+h1vbeG1NUECXQZE3hg0kias9kOtcQr3+JVQ3whnjvKuMyktJAAIj6743OeNPnGBmjj7KEmiDL7qsdnCQ== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.40.0" - "@typescript-eslint/types" "5.40.0" - "@typescript-eslint/typescript-estree" "5.40.0" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.42.0" + "@typescript-eslint/types" "5.42.0" + "@typescript-eslint/typescript-estree" "5.42.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.40.0": - version "5.40.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.40.0.tgz#dd2d38097f68e0d2e1e06cb9f73c0173aca54b68" - integrity sha512-ijJ+6yig+x9XplEpG2K6FUdJeQGGj/15U3S56W9IqXKJqleuD7zJ2AX/miLezwxpd7ZxDAqO87zWufKg+RPZyQ== +"@typescript-eslint/visitor-keys@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.0.tgz#ee8d62d486f41cfe646632fab790fbf0c1db5bb0" + integrity sha512-QHbu5Hf/2lOEOwy+IUw0GoSCuAzByTAWWrOTKzTzsotiUnWFpuKnXcAhC9YztAf2EElQ0VvIK+pHJUPkM0q7jg== dependencies: - "@typescript-eslint/types" "5.40.0" + "@typescript-eslint/types" "5.42.0" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: version "5.3.2" - resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^8.8.0: - version "8.8.0" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz" - integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + version "8.8.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" + integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -2089,73 +1990,73 @@ ajv@^6.10.0, ajv@^6.12.4: ansi-escapes@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b" integrity sha512-tH/fSoQp4DrEodDK3QpdiWiZTSe7sBJ9eOqcQBZ0o9HTM+5M/viSEn+sPMoTuPjQQ8n++w3QJoPEjt8LVPcrCg== ansi-escapes@^3.2.0: version "3.2.0" - resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== ansi-escapes@^4.2.1: version "4.3.2" - resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: type-fest "^0.21.3" ansi-regex@^2.0.0: version "2.1.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== ansi-regex@^3.0.0: version "3.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== ansi-regex@^4.1.0: version "4.1.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-styles@^2.2.1: version "2.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== ansi-styles@^3.2.1: version "3.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" ansi-styles@^5.0.0: version "5.2.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== any-promise@^1.0.0: version "1.3.0" - resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== anymatch@^3.0.3: version "3.1.2" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== dependencies: normalize-path "^3.0.0" @@ -2163,58 +2064,51 @@ anymatch@^3.0.3: argparse@^1.0.7: version "1.0.10" - resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" argparse@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== array-union@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== ast-stringify@0.1.0: version "0.1.0" - resolved "https://registry.npmjs.org/ast-stringify/-/ast-stringify-0.1.0.tgz" + resolved "https://registry.yarnpkg.com/ast-stringify/-/ast-stringify-0.1.0.tgz#5c6439fbfb4513dcc26c7d34464ccd084ed91cb7" integrity sha512-J1PgFYV3RG6r37+M6ySZJH406hR82okwGvFM9hLXpOvdx4WC4GEW8/qiw6pi1hKTrqcRvoHP8a7mp87egYr6iA== dependencies: "@babel/runtime" "^7.11.2" axios@^0.21.2: version "0.21.4" - resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: follow-redirects "^1.14.0" -babel-jest@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.1.2.tgz#540d3241925c55240fb0c742e3ffc5f33a501978" - integrity sha512-IuG+F3HTHryJb7gacC7SQ59A9kO56BctUsT67uJHp1mMCHUOMXpDwOHWGifWqdWVknN2WNkCVQELPjXx0aLJ9Q== +babel-jest@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.2.2.tgz#2c15abd8c2081293c9c3f4f80a4ed1d51542fee5" + integrity sha512-kkq2QSDIuvpgfoac3WZ1OOcHsQQDU5xYk2Ql7tLdJ8BVAYbefEXal+NfS45Y5LVZA7cxC8KYcQMObpCt1J025w== dependencies: - "@jest/transform" "^29.1.2" + "@jest/transform" "^29.2.2" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.0.2" + babel-preset-jest "^29.2.0" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== - dependencies: - object.assign "^4.1.0" - babel-plugin-istanbul@^6.1.1: version "6.1.1" - resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -2223,26 +2117,17 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^29.0.2: - version "29.0.2" - resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.0.2.tgz" - integrity sha512-eBr2ynAEFjcebVvu8Ktx580BD1QKCrBG1XwEUTXJe285p9HA/4hOhfWCFRQhTKSyBV0VzjhG7H91Eifz9s29hg== +babel-plugin-jest-hoist@^29.2.0: + version "29.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz#23ee99c37390a98cfddf3ef4a78674180d823094" + integrity sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" -babel-plugin-polyfill-corejs2@^0.3.2: - version "0.3.2" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz" - integrity sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q== - dependencies: - "@babel/compat-data" "^7.17.7" - "@babel/helper-define-polyfill-provider" "^0.3.2" - semver "^6.1.1" - -babel-plugin-polyfill-corejs2@^0.3.3: +babel-plugin-polyfill-corejs2@^0.3.2, babel-plugin-polyfill-corejs2@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== @@ -2253,7 +2138,7 @@ babel-plugin-polyfill-corejs2@^0.3.3: babel-plugin-polyfill-corejs3@^0.5.3: version "0.5.3" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7" integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw== dependencies: "@babel/helper-define-polyfill-provider" "^0.3.2" @@ -2267,14 +2152,7 @@ babel-plugin-polyfill-corejs3@^0.6.0: "@babel/helper-define-polyfill-provider" "^0.3.3" core-js-compat "^3.25.1" -babel-plugin-polyfill-regenerator@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz" - integrity sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.2" - -babel-plugin-polyfill-regenerator@^0.4.1: +babel-plugin-polyfill-regenerator@^0.4.0, babel-plugin-polyfill-regenerator@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== @@ -2283,7 +2161,7 @@ babel-plugin-polyfill-regenerator@^0.4.1: babel-preset-current-node-syntax@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" @@ -2299,42 +2177,42 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^29.0.2: - version "29.0.2" - resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.0.2.tgz" - integrity sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA== +babel-preset-jest@^29.2.0: + version "29.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz#3048bea3a1af222e3505e4a767a974c95a7620dc" + integrity sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA== dependencies: - babel-plugin-jest-hoist "^29.0.2" + babel-plugin-jest-hoist "^29.2.0" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.3.0: version "1.5.1" - resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== bech32@^1.1.4: version "1.1.4" - resolved "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== bn.js@^4.11.9: version "4.12.0" - resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== bn.js@^5.2.0: version "5.2.1" - resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== brace-expansion@^1.1.7: version "1.1.11" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" @@ -2342,86 +2220,78 @@ brace-expansion@^1.1.7: brace-expansion@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" braces@^3.0.2: version "3.0.2" - resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" brorand@^1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== -browserslist@^4.20.2, browserslist@^4.21.3: - version "4.21.3" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz" - integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== +browserslist@^4.21.3, browserslist@^4.21.4: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== dependencies: - caniuse-lite "^1.0.30001370" - electron-to-chromium "^1.4.202" + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" node-releases "^2.0.6" - update-browserslist-db "^1.0.5" + update-browserslist-db "^1.0.9" bser@2.1.1: version "2.1.1" - resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== dependencies: node-int64 "^0.4.0" buffer-from@^1.0.0: version "1.1.2" - resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - call-me-maybe@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz" - integrity sha512-wCyFsDQkKPwwF8BDwOiWNx/9K45L/hvggQiDbve+viMNMQnWhrlYIuBk09offfwCRtCO9P6XwUttufzU11WCVw== + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa" + integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== callsites@^3.0.0: version "3.1.0" - resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase@^5.3.1: version "5.3.1" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== camelcase@^6.2.0: version "6.3.0" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001370: - version "1.0.30001397" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001397.tgz" - integrity sha512-SW9N2TbCdLf0eiNDRrrQXx2sOkaakNZbCjgNpPyMJJbiOrU5QzMIrXOVMRM1myBXTD5iTkdrtU/EguCrBocHlA== +caniuse-lite@^1.0.30001400: + version "1.0.30001429" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz#70cdae959096756a85713b36dd9cb82e62325639" + integrity sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg== case@1.6.3: version "1.6.3" - resolved "https://registry.npmjs.org/case/-/case-1.6.3.tgz" + resolved "https://registry.yarnpkg.com/case/-/case-1.6.3.tgz#0a4386e3e9825351ca2e6216c60467ff5f1ea1c9" integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== chalk@4.1.2, chalk@^4.0.0: version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -2429,7 +2299,7 @@ chalk@4.1.2, chalk@^4.0.0: chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" - resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== dependencies: ansi-styles "^2.2.1" @@ -2440,7 +2310,7 @@ chalk@^1.0.0, chalk@^1.1.3: chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" @@ -2449,32 +2319,32 @@ chalk@^2.0.0, chalk@^2.4.2: char-regex@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== chardet@^0.4.0: version "0.4.2" - resolved "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" integrity sha512-j/Toj7f1z98Hh2cYo2BVr85EpIRWqUi7rtRSGxh/cqUjqrnJe9l9UE7IUGd2vQ2p+kSHLkSzObQPZPLUC6TQwg== chardet@^0.7.0: version "0.7.0" - resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== ci-info@^3.2.0: - version "3.4.0" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz" - integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== + version "3.5.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.5.0.tgz#bfac2a29263de4c829d806b1ab478e35091e171f" + integrity sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw== cjs-module-lexer@^1.0.0: version "1.2.2" - resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== cli-color@^2.0.2: version "2.0.3" - resolved "https://registry.npmjs.org/cli-color/-/cli-color-2.0.3.tgz" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.3.tgz#73769ba969080629670f3f2ef69a4bf4e7cc1879" integrity sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ== dependencies: d "^1.0.1" @@ -2485,94 +2355,92 @@ cli-color@^2.0.2: cli-cursor@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== dependencies: restore-cursor "^2.0.0" cli-width@^2.0.0: version "2.2.1" - resolved "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: string-width "^4.2.0" - strip-ansi "^6.0.0" + strip-ansi "^6.0.1" wrap-ansi "^7.0.0" co@^4.6.0: version "4.6.0" - resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== collect-v8-coverage@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== color-convert@^1.9.0: version "1.9.3" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: color-name "1.1.3" color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@1.1.3: version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== color-name@~1.1.4: version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== colors@^1.1.2: version "1.4.0" - resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== concat-map@0.0.1: version "0.0.1" - resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.25.1: - version "3.25.1" - resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.25.1.tgz" - integrity sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw== + version "3.26.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.26.0.tgz#94e2cf8ba3e63800c4956ea298a6473bc9d62b44" + integrity sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A== dependencies: - browserslist "^4.21.3" + browserslist "^4.21.4" -cosmjs-types@^0.5.0: - version "0.5.1" - resolved "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.5.1.tgz" - integrity sha512-NcC58xUIVLlKdIimWWQAmSlmCjiMrJnuHf4i3LiD8PCextfHR0fT3V5/WlXZZreyMgdmh6ML1zPUfGTbbo3Z5g== +cosmjs-types@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.5.2.tgz#2d42b354946f330dfb5c90a87fdc2a36f97b965d" + integrity sha512-zxCtIJj8v3Di7s39uN4LNcN3HIE1z0B9Z0SPE8ZNQR0oSzsuSe1ACgxoFkvhkS7WBasCAFcglS11G2hyfd5tPg== dependencies: long "^4.0.0" protobufjs "~6.11.2" cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: path-key "^3.1.0" @@ -2581,7 +2449,7 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: d@1, d@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== dependencies: es5-ext "^0.10.50" @@ -2589,34 +2457,34 @@ d@1, d@^1.0.1: dargs@7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz" + resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" dedent@^0.7.0: version "0.7.0" - resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== deep-is@^0.1.3: version "0.1.4" - resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== deepmerge@4.2.2, deepmerge@^4.2.2: version "4.2.2" - resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== -define-properties@^1.1.3, define-properties@^1.1.4: +define-properties@^1.1.3: version "1.1.4" - resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== dependencies: has-property-descriptors "^1.0.0" @@ -2624,41 +2492,41 @@ define-properties@^1.1.3, define-properties@^1.1.4: detect-newline@^3.0.0: version "3.1.0" - resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -diff-sequences@^29.0.0: - version "29.0.0" - resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.0.0.tgz" - integrity sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA== +diff-sequences@^29.2.0: + version "29.2.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.2.0.tgz#4c55b5b40706c7b5d2c5c75999a50c56d214e8f6" + integrity sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw== dir-glob@^3.0.1: version "3.0.1" - resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: path-type "^4.0.0" doctrine@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" dotty@0.1.2: version "0.1.2" - resolved "https://registry.npmjs.org/dotty/-/dotty-0.1.2.tgz" + resolved "https://registry.yarnpkg.com/dotty/-/dotty-0.1.2.tgz#512d44cc4111a724931226259297f235e8484f6f" integrity sha512-V0EWmKeH3DEhMwAZ+8ZB2Ao4OK6p++Z0hsDtZq3N0+0ZMVqkzrcEGROvOnZpLnvBg5PTNG23JEDLAm64gPaotQ== -electron-to-chromium@^1.4.202: - version "1.4.247" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.247.tgz" - integrity sha512-FLs6R4FQE+1JHM0hh3sfdxnYjKvJpHZyhQDjc2qFq/xFvmmRt/TATNToZhrcGUFzpF2XjeiuozrA8lI0PZmYYw== +electron-to-chromium@^1.4.251: + version "1.4.284" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" + integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== elliptic@^6.5.3: version "6.5.4" - resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: bn.js "^4.11.9" @@ -2669,26 +2537,26 @@ elliptic@^6.5.3: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -emittery@^0.10.2: - version "0.10.2" - resolved "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz" - integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^8.0.0: version "8.0.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== error-ex@^1.3.1: version "1.3.2" - resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.61, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: version "0.10.62" - resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== dependencies: es6-iterator "^2.0.3" @@ -2697,7 +2565,7 @@ es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@ es6-iterator@^2.0.3: version "2.0.3" - resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== dependencies: d "1" @@ -2706,7 +2574,7 @@ es6-iterator@^2.0.3: es6-symbol@^3.1.1, es6-symbol@^3.1.3: version "3.1.3" - resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== dependencies: d "^1.0.1" @@ -2714,7 +2582,7 @@ es6-symbol@^3.1.1, es6-symbol@^3.1.3: es6-weak-map@^2.0.3: version "2.0.3" - resolved "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== dependencies: d "1" @@ -2724,32 +2592,32 @@ es6-weak-map@^2.0.3: escalade@^3.1.1: version "3.1.1" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== escape-string-regexp@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== escape-string-regexp@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== eslint-config-prettier@^8.5.0: version "8.5.0" - resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== eslint-scope@^5.1.1: version "5.1.1" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== dependencies: esrecurse "^4.3.0" @@ -2757,7 +2625,7 @@ eslint-scope@^5.1.1: eslint-scope@^7.1.1: version "7.1.1" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== dependencies: esrecurse "^4.3.0" @@ -2765,29 +2633,30 @@ eslint-scope@^7.1.1: eslint-utils@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== dependencies: eslint-visitor-keys "^2.0.0" eslint-visitor-keys@^2.0.0: version "2.1.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== eslint-visitor-keys@^3.3.0: version "3.3.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== eslint@^8.25.0: - version "8.25.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.25.0.tgz#00eb962f50962165d0c4ee3327708315eaa8058b" - integrity sha512-DVlJOZ4Pn50zcKW5bYH7GQK/9MsoQG2d5eDH0ebEkE8PbgzTTmtt/VTH9GGJ4BfeZCpBLqFfvsjX35UacUL83A== + version "8.26.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.26.0.tgz#2bcc8836e6c424c4ac26a5674a70d44d84f2181d" + integrity sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg== dependencies: "@eslint/eslintrc" "^1.3.3" - "@humanwhocodes/config-array" "^0.10.5" + "@humanwhocodes/config-array" "^0.11.6" "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -2803,14 +2672,14 @@ eslint@^8.25.0: fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" find-up "^5.0.0" - glob-parent "^6.0.1" + glob-parent "^6.0.2" globals "^13.15.0" - globby "^11.1.0" grapheme-splitter "^1.0.4" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" + is-path-inside "^3.0.3" js-sdsl "^4.1.4" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" @@ -2826,7 +2695,7 @@ eslint@^8.25.0: espree@^9.4.0: version "9.4.0" - resolved "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.0.tgz#cd4bc3d6e9336c433265fc0aa016fc1aaf182f8a" integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw== dependencies: acorn "^8.8.0" @@ -2835,41 +2704,41 @@ espree@^9.4.0: esprima@^4.0.0: version "4.0.1" - resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.0: version "1.4.0" - resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" esrecurse@^4.3.0: version "4.3.0" - resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: estraverse "^5.2.0" estraverse@^4.1.1: version "4.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== esutils@^2.0.2: version "2.0.3" - resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== event-emitter@^0.3.5: version "0.3.5" - resolved "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== dependencies: d "1" @@ -2877,7 +2746,7 @@ event-emitter@^0.3.5: execa@^5.0.0: version "5.1.1" - resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: cross-spawn "^7.0.3" @@ -2892,41 +2761,30 @@ execa@^5.0.0: exit@^0.1.2: version "0.1.2" - resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0: - version "29.0.3" - resolved "https://registry.npmjs.org/expect/-/expect-29.0.3.tgz" - integrity sha512-t8l5DTws3212VbmPL+tBFXhjRHLmctHB0oQbL8eUc6S7NzZtYUhycrFO9mkxA0ZUC6FAWdNi7JchJSkODtcu1Q== +expect@^29.0.0, expect@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.2.2.tgz#ba2dd0d7e818727710324a6e7f13dd0e6d086106" + integrity sha512-hE09QerxZ5wXiOhqkXy5d2G9ar+EqOyifnCXCpMNu+vZ6DG9TJ6CO2c2kPDSLqERTTWrO7OZj8EkYHQqSd78Yw== dependencies: - "@jest/expect-utils" "^29.0.3" - jest-get-type "^29.0.0" - jest-matcher-utils "^29.0.3" - jest-message-util "^29.0.3" - jest-util "^29.0.3" - -expect@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.1.2.tgz#82f8f28d7d408c7c68da3a386a490ee683e1eced" - integrity sha512-AuAGn1uxva5YBbBlXb+2JPxJRuemZsmlGcapPXWNSBNsQtAULfjioREGBWuI0EOvYUKjDnrCy8PW5Zlr1md5mw== - dependencies: - "@jest/expect-utils" "^29.1.2" - jest-get-type "^29.0.0" - jest-matcher-utils "^29.1.2" - jest-message-util "^29.1.2" - jest-util "^29.1.2" + "@jest/expect-utils" "^29.2.2" + jest-get-type "^29.2.0" + jest-matcher-utils "^29.2.2" + jest-message-util "^29.2.1" + jest-util "^29.2.1" ext@^1.1.2: version "1.7.0" - resolved "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== dependencies: type "^2.7.2" external-editor@^2.0.4: version "2.2.0" - resolved "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== dependencies: chardet "^0.4.0" @@ -2935,7 +2793,7 @@ external-editor@^2.0.4: external-editor@^3.0.3: version "3.1.0" - resolved "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== dependencies: chardet "^0.7.0" @@ -2944,12 +2802,12 @@ external-editor@^3.0.3: fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" - resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.2.9: version "3.2.12" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== dependencies: "@nodelib/fs.stat" "^2.0.2" @@ -2960,52 +2818,52 @@ fast-glob@^3.2.9: fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-levenshtein@^2.0.6: version "2.0.6" - resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: version "1.13.0" - resolved "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== dependencies: reusify "^1.0.4" fb-watchman@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz" - integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== dependencies: bser "2.1.1" figures@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" integrity sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA== dependencies: escape-string-regexp "^1.0.5" file-entry-cache@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" fill-range@^7.0.1: version "7.0.1" - resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: to-regex-range "^5.0.1" find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: locate-path "^5.0.0" @@ -3013,7 +2871,7 @@ find-up@^4.0.0, find-up@^4.1.0: find-up@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" @@ -3021,7 +2879,7 @@ find-up@^5.0.0: flat-cache@^3.0.4: version "3.0.4" - resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: flatted "^3.1.0" @@ -3029,48 +2887,48 @@ flat-cache@^3.0.4: flatted@^3.1.0: version "3.2.7" - resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== follow-redirects@^1.14.0: - version "1.15.1" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz" - integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@^2.3.2: version "2.3.2" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: version "1.1.1" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== fuzzy@0.1.3: version "0.1.3" - resolved "https://registry.npmjs.org/fuzzy/-/fuzzy-0.1.3.tgz" + resolved "https://registry.yarnpkg.com/fuzzy/-/fuzzy-0.1.3.tgz#4c76ec2ff0ac1a36a9dccf9a00df8623078d4ed8" integrity sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w== gensync@^1.0.0-beta.2: version "1.0.0-beta.2" - resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== get-caller-file@^2.0.5: version "2.0.5" - resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: - version "1.1.2" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz" - integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== +get-intrinsic@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== dependencies: function-bind "^1.1.1" has "^1.0.3" @@ -3078,43 +2936,43 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: get-package-type@^0.1.0: version "0.1.0" - resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== get-stdin@^8.0.0: version "8.0.0" - resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== get-stream@^6.0.0: version "6.0.1" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== glob-parent@^5.1.2: version "5.1.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" -glob-parent@^6.0.1: +glob-parent@^6.0.2: version "6.0.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: is-glob "^4.0.3" glob-promise@^4.2.2: version "4.2.2" - resolved "https://registry.npmjs.org/glob-promise/-/glob-promise-4.2.2.tgz" + resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-4.2.2.tgz#15f44bcba0e14219cd93af36da6bb905ff007877" integrity sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw== dependencies: "@types/glob" "^7.1.3" glob@8.0.3: version "8.0.3" - resolved "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== dependencies: fs.realpath "^1.0.0" @@ -3125,7 +2983,7 @@ glob@8.0.3: glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" @@ -3137,26 +2995,26 @@ glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: globals@^11.1.0: version "11.12.0" - resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.15.0: version "13.17.0" - resolved "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== dependencies: type-fest "^0.20.2" globalthis@^1.0.1: version "1.0.3" - resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== dependencies: define-properties "^1.1.3" globby@^11.1.0: version "11.1.0" - resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" @@ -3168,53 +3026,53 @@ globby@^11.1.0: graceful-fs@^4.1.15, graceful-fs@^4.2.9: version "4.2.10" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== grapheme-splitter@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== has-ansi@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== dependencies: ansi-regex "^2.0.0" has-flag@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-property-descriptors@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== dependencies: get-intrinsic "^1.1.1" has-symbols@^1.0.3: version "1.0.3" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== has@^1.0.3: version "1.0.3" - resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: function-bind "^1.1.1" hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" - resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== dependencies: inherits "^2.0.3" @@ -3222,7 +3080,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: hmac-drbg@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== dependencies: hash.js "^1.0.3" @@ -3231,29 +3089,29 @@ hmac-drbg@^1.0.1: html-escaper@^2.0.0: version "2.0.2" - resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== human-signals@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== iconv-lite@^0.4.17, iconv-lite@^0.4.24: version "0.4.24" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" ignore@^5.2.0: version "5.2.0" - resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" - resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" @@ -3261,7 +3119,7 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: import-local@^3.0.2: version "3.1.0" - resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== dependencies: pkg-dir "^4.2.0" @@ -3269,12 +3127,12 @@ import-local@^3.0.2: imurmurhash@^0.1.4: version "0.1.4" - resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== inflight@^1.0.4: version "1.0.6" - resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" @@ -3282,17 +3140,17 @@ inflight@^1.0.4: inherits@2, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== inherits@2.0.3: version "2.0.3" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== inquirer-autocomplete-prompt@^0.11.1: version "0.11.1" - resolved "https://registry.npmjs.org/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-0.11.1.tgz" + resolved "https://registry.yarnpkg.com/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-0.11.1.tgz#f90ca9510a4c489882e9be294934bd8c2e575e09" integrity sha512-VM4eNiyRD4CeUc2cyKni+F8qgHwL9WC4LdOr+mEC85qP/QNsDV+ysVqUrJYhw1TmDQu1QVhc8hbaL7wfk8SJxw== dependencies: ansi-escapes "^2.0.0" @@ -3305,7 +3163,7 @@ inquirer-autocomplete-prompt@^0.11.1: inquirer@3.1.1: version "3.1.1" - resolved "https://registry.npmjs.org/inquirer/-/inquirer-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.1.1.tgz#87621c4fba4072f48a8dd71c9f9df6f100b2d534" integrity sha512-H50sHQwgvvaTBd3HpKMVtL/u6LoHDvYym51gd7bGQe/+9HkCE+J0/3N5FJLfd6O6oz44hHewC2Pc2LodzWVafQ== dependencies: ansi-escapes "^2.0.0" @@ -3325,7 +3183,7 @@ inquirer@3.1.1: inquirer@^6.0.0: version "6.5.2" - resolved "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== dependencies: ansi-escapes "^3.2.0" @@ -3344,7 +3202,7 @@ inquirer@^6.0.0: inquirerer@0.1.3: version "0.1.3" - resolved "https://registry.npmjs.org/inquirerer/-/inquirerer-0.1.3.tgz" + resolved "https://registry.yarnpkg.com/inquirerer/-/inquirerer-0.1.3.tgz#ecf91dc672b3bf45211d7f64bf5e8d5e171fd2ad" integrity sha512-yGgLUOqPxTsINBjZNZeLi3cv2zgxXtw9feaAOSJf2j6AqIT5Uxs5ZOqOrfAf+xP65Sicla1FD3iDxa3D6TsCAQ== dependencies: colors "^1.1.2" @@ -3353,82 +3211,87 @@ inquirerer@0.1.3: interpret@^1.0.0: version "1.4.0" - resolved "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-core-module@^2.9.0: - version "2.10.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz" - integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== dependencies: has "^1.0.3" is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== is-fullwidth-code-point@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-generator-fn@^2.0.0: version "2.1.0" - resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" - resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" is-number@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-promise@^2.2.2: version "2.2.2" - resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== is-stream@^2.0.0: version "2.0.1" - resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== isexe@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== isomorphic-ws@^4.0.1: version "4.0.1" - resolved "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" - resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: - version "5.2.0" - resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz" - integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== dependencies: "@babel/core" "^7.12.3" "@babel/parser" "^7.14.7" @@ -3438,7 +3301,7 @@ istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: istanbul-lib-report@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== dependencies: istanbul-lib-coverage "^3.0.0" @@ -3447,7 +3310,7 @@ istanbul-lib-report@^3.0.0: istanbul-lib-source-maps@^4.0.0: version "4.0.1" - resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== dependencies: debug "^4.1.1" @@ -3456,149 +3319,139 @@ istanbul-lib-source-maps@^4.0.0: istanbul-reports@^3.1.3: version "3.1.5" - resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^29.0.0: - version "29.0.0" - resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.0.0.tgz" - integrity sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ== +jest-changed-files@^29.2.0: + version "29.2.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.2.0.tgz#b6598daa9803ea6a4dce7968e20ab380ddbee289" + integrity sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA== dependencies: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.1.2.tgz#4551068e432f169a53167fe1aef420cf51c8a735" - integrity sha512-ajQOdxY6mT9GtnfJRZBRYS7toNIJayiiyjDyoZcnvPRUPwJ58JX0ci0PKAKUo2C1RyzlHw0jabjLGKksO42JGA== +jest-circus@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.2.2.tgz#1dc4d35fd49bf5e64d3cc505fb2db396237a6dfa" + integrity sha512-upSdWxx+Mh4DV7oueuZndJ1NVdgtTsqM4YgywHEx05UMH5nxxA2Qu9T9T9XVuR021XxqSoaKvSmmpAbjwwwxMw== dependencies: - "@jest/environment" "^29.1.2" - "@jest/expect" "^29.1.2" - "@jest/test-result" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/environment" "^29.2.2" + "@jest/expect" "^29.2.2" + "@jest/test-result" "^29.2.1" + "@jest/types" "^29.2.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^29.1.2" - jest-matcher-utils "^29.1.2" - jest-message-util "^29.1.2" - jest-runtime "^29.1.2" - jest-snapshot "^29.1.2" - jest-util "^29.1.2" + jest-each "^29.2.1" + jest-matcher-utils "^29.2.2" + jest-message-util "^29.2.1" + jest-runtime "^29.2.2" + jest-snapshot "^29.2.2" + jest-util "^29.2.1" p-limit "^3.1.0" - pretty-format "^29.1.2" + pretty-format "^29.2.1" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.1.2.tgz#423b9c5d3ea20a50b1354b8bf3f2a20e72110e89" - integrity sha512-vsvBfQ7oS2o4MJdAH+4u9z76Vw5Q8WBQF5MchDbkylNknZdrPTX1Ix7YRJyTlOWqRaS7ue/cEAn+E4V1MWyMzw== +jest-cli@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.2.2.tgz#feaf0aa57d327e80d4f2f18d5f8cd2e77cac5371" + integrity sha512-R45ygnnb2CQOfd8rTPFR+/fls0d+1zXS6JPYTBBrnLPrhr58SSuPTiA5Tplv8/PXpz4zXR/AYNxmwIj6J6nrvg== dependencies: - "@jest/core" "^29.1.2" - "@jest/test-result" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/core" "^29.2.2" + "@jest/test-result" "^29.2.1" + "@jest/types" "^29.2.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.1.2" - jest-util "^29.1.2" - jest-validate "^29.1.2" + jest-config "^29.2.2" + jest-util "^29.2.1" + jest-validate "^29.2.2" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.1.2.tgz#7d004345ca4c09f5d8f802355f54494e90842f4d" - integrity sha512-EC3Zi86HJUOz+2YWQcJYQXlf0zuBhJoeyxLM6vb6qJsVmpP7KcCP1JnyF0iaqTaXdBP8Rlwsvs7hnKWQWWLwwA== +jest-config@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.2.2.tgz#bf98623a46454d644630c1f0de8bba3f495c2d59" + integrity sha512-Q0JX54a5g1lP63keRfKR8EuC7n7wwny2HoTRDb8cx78IwQOiaYUVZAdjViY3WcTxpR02rPUpvNVmZ1fkIlZPcw== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.1.2" - "@jest/types" "^29.1.2" - babel-jest "^29.1.2" + "@jest/test-sequencer" "^29.2.2" + "@jest/types" "^29.2.1" + babel-jest "^29.2.2" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.1.2" - jest-environment-node "^29.1.2" - jest-get-type "^29.0.0" - jest-regex-util "^29.0.0" - jest-resolve "^29.1.2" - jest-runner "^29.1.2" - jest-util "^29.1.2" - jest-validate "^29.1.2" + jest-circus "^29.2.2" + jest-environment-node "^29.2.2" + jest-get-type "^29.2.0" + jest-regex-util "^29.2.0" + jest-resolve "^29.2.2" + jest-runner "^29.2.2" + jest-util "^29.2.1" + jest-validate "^29.2.2" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.1.2" + pretty-format "^29.2.1" slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-29.0.3.tgz" - integrity sha512-+X/AIF5G/vX9fWK+Db9bi9BQas7M9oBME7egU7psbn4jlszLFCu0dW63UgeE6cs/GANq4fLaT+8sGHQQ0eCUfg== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.0.0" - jest-get-type "^29.0.0" - pretty-format "^29.0.3" - -jest-diff@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.1.2.tgz#bb7aaf5353227d6f4f96c5e7e8713ce576a607dc" - integrity sha512-4GQts0aUopVvecIT4IwD/7xsBaMhKTYoM4/njE/aVw9wpw+pIUVp8Vab/KnSzSilr84GnLBkaP3JLDnQYCKqVQ== +jest-diff@^29.2.1: + version "29.2.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.2.1.tgz#027e42f5a18b693fb2e88f81b0ccab533c08faee" + integrity sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA== dependencies: chalk "^4.0.0" - diff-sequences "^29.0.0" - jest-get-type "^29.0.0" - pretty-format "^29.1.2" + diff-sequences "^29.2.0" + jest-get-type "^29.2.0" + pretty-format "^29.2.1" -jest-docblock@^29.0.0: - version "29.0.0" - resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.0.0.tgz" - integrity sha512-s5Kpra/kLzbqu9dEjov30kj1n4tfu3e7Pl8v+f8jOkeWNqM6Ds8jRaJfZow3ducoQUrf2Z4rs2N5S3zXnb83gw== +jest-docblock@^29.2.0: + version "29.2.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.2.0.tgz#307203e20b637d97cee04809efc1d43afc641e82" + integrity sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A== dependencies: detect-newline "^3.0.0" -jest-each@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.1.2.tgz#d4c8532c07a846e79f194f7007ce7cb1987d1cd0" - integrity sha512-AmTQp9b2etNeEwMyr4jc0Ql/LIX/dhbgP21gHAizya2X6rUspHn2gysMXaj6iwWuOJ2sYRgP8c1P4cXswgvS1A== +jest-each@^29.2.1: + version "29.2.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.2.1.tgz#6b0a88ee85c2ba27b571a6010c2e0c674f5c9b29" + integrity sha512-sGP86H/CpWHMyK3qGIGFCgP6mt+o5tu9qG4+tobl0LNdgny0aitLXs9/EBacLy3Bwqy+v4uXClqJgASJWcruYw== dependencies: - "@jest/types" "^29.1.2" + "@jest/types" "^29.2.1" chalk "^4.0.0" - jest-get-type "^29.0.0" - jest-util "^29.1.2" - pretty-format "^29.1.2" - -jest-environment-node@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.1.2.tgz#005e05cc6ea4b9b5ba55906ab1ce53c82f6907a7" - integrity sha512-C59yVbdpY8682u6k/lh8SUMDJPbOyCHOTgLVVi1USWFxtNV+J8fyIwzkg+RJIVI30EKhKiAGNxYaFr3z6eyNhQ== - dependencies: - "@jest/environment" "^29.1.2" - "@jest/fake-timers" "^29.1.2" - "@jest/types" "^29.1.2" + jest-get-type "^29.2.0" + jest-util "^29.2.1" + pretty-format "^29.2.1" + +jest-environment-node@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.2.2.tgz#a64b272773870c3a947cd338c25fd34938390bc2" + integrity sha512-B7qDxQjkIakQf+YyrqV5dICNs7tlCO55WJ4OMSXsqz1lpI/0PmeuXdx2F7eU8rnPbRkUR/fItSSUh0jvE2y/tw== + dependencies: + "@jest/environment" "^29.2.2" + "@jest/fake-timers" "^29.2.2" + "@jest/types" "^29.2.1" "@types/node" "*" - jest-mock "^29.1.2" - jest-util "^29.1.2" + jest-mock "^29.2.2" + jest-util "^29.2.1" -jest-get-type@^29.0.0: - version "29.0.0" - resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.0.0.tgz" - integrity sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw== +jest-get-type@^29.2.0: + version "29.2.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.2.0.tgz#726646f927ef61d583a3b3adb1ab13f3a5036408" + integrity sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA== jest-haste-map@^28.1.3: version "28.1.3" - resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.3.tgz#abd5451129a38d9841049644f34b034308944e2b" integrity sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA== dependencies: "@jest/types" "^28.1.3" @@ -3615,189 +3468,164 @@ jest-haste-map@^28.1.3: optionalDependencies: fsevents "^2.3.2" -jest-haste-map@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.1.2.tgz#93f3634aa921b6b654e7c94137b24e02e7ca6ac9" - integrity sha512-xSjbY8/BF11Jh3hGSPfYTa/qBFrm3TPM7WU8pU93m2gqzORVLkHFWvuZmFsTEBPRKndfewXhMOuzJNHyJIZGsw== +jest-haste-map@^29.2.1: + version "29.2.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.2.1.tgz#f803fec57f8075e6c55fb5cd551f99a72471c699" + integrity sha512-wF460rAFmYc6ARcCFNw4MbGYQjYkvjovb9GBT+W10Um8q5nHq98jD6fHZMDMO3tA56S8XnmNkM8GcA8diSZfnA== dependencies: - "@jest/types" "^29.1.2" + "@jest/types" "^29.2.1" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^29.0.0" - jest-util "^29.1.2" - jest-worker "^29.1.2" + jest-regex-util "^29.2.0" + jest-util "^29.2.1" + jest-worker "^29.2.1" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.1.2.tgz#4c846db14c58219430ccbc4f01a1ec52ebee4fc2" - integrity sha512-TG5gAZJpgmZtjb6oWxBLf2N6CfQ73iwCe6cofu/Uqv9iiAm6g502CAnGtxQaTfpHECBdVEMRBhomSXeLnoKjiQ== +jest-leak-detector@^29.2.1: + version "29.2.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.2.1.tgz#ec551686b7d512ec875616c2c3534298b1ffe2fc" + integrity sha512-1YvSqYoiurxKOJtySc+CGVmw/e1v4yNY27BjWTVzp0aTduQeA7pdieLiW05wTYG/twlKOp2xS/pWuikQEmklug== dependencies: - jest-get-type "^29.0.0" - pretty-format "^29.1.2" + jest-get-type "^29.2.0" + pretty-format "^29.2.1" -jest-matcher-utils@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.0.3.tgz" - integrity sha512-RsR1+cZ6p1hDV4GSCQTg+9qjeotQCgkaleIKLK7dm+U4V/H2bWedU3RAtLm8+mANzZ7eDV33dMar4pejd7047w== +jest-matcher-utils@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz#9202f8e8d3a54733266784ce7763e9a08688269c" + integrity sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw== dependencies: chalk "^4.0.0" - jest-diff "^29.0.3" - jest-get-type "^29.0.0" - pretty-format "^29.0.3" + jest-diff "^29.2.1" + jest-get-type "^29.2.0" + pretty-format "^29.2.1" -jest-matcher-utils@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.1.2.tgz#e68c4bcc0266e70aa1a5c13fb7b8cd4695e318a1" - integrity sha512-MV5XrD3qYSW2zZSHRRceFzqJ39B2z11Qv0KPyZYxnzDHFeYZGJlgGi0SW+IXSJfOewgJp/Km/7lpcFT+cgZypw== - dependencies: - chalk "^4.0.0" - jest-diff "^29.1.2" - jest-get-type "^29.0.0" - pretty-format "^29.1.2" - -jest-message-util@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.3.tgz" - integrity sha512-7T8JiUTtDfppojosORAflABfLsLKMLkBHSWkjNQrjIltGoDzNGn7wEPOSfjqYAGTYME65esQzMJxGDjuLBKdOg== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.0.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.0.3" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-message-util@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.1.2.tgz#c21a33c25f9dc1ebfcd0f921d89438847a09a501" - integrity sha512-9oJ2Os+Qh6IlxLpmvshVbGUiSkZVc2FK+uGOm6tghafnB2RyjKAxMZhtxThRMxfX1J1SOMhTn9oK3/MutRWQJQ== +jest-message-util@^29.2.1: + version "29.2.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.2.1.tgz#3a51357fbbe0cc34236f17a90d772746cf8d9193" + integrity sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.1.2" + "@jest/types" "^29.2.1" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^29.1.2" + pretty-format "^29.2.1" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.1.2.tgz#de47807edbb9d4abf8423f1d8d308d670105678c" - integrity sha512-PFDAdjjWbjPUtQPkQufvniXIS3N9Tv7tbibePEjIIprzjgo0qQlyUiVMrT4vL8FaSJo1QXifQUOuPH3HQC/aMA== +jest-mock@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.2.2.tgz#9045618b3f9d27074bbcf2d55bdca6a5e2e8bca7" + integrity sha512-1leySQxNAnivvbcx0sCB37itu8f4OX2S/+gxLAV4Z62shT4r4dTG9tACDywUAEZoLSr36aYUTsVp3WKwWt4PMQ== dependencies: - "@jest/types" "^29.1.2" + "@jest/types" "^29.2.1" "@types/node" "*" - jest-util "^29.1.2" + jest-util "^29.2.1" jest-pnp-resolver@^1.2.2: version "1.2.2" - resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== jest-regex-util@^28.0.2: version "28.0.2" - resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== -jest-regex-util@^29.0.0: - version "29.0.0" - resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.0.0.tgz" - integrity sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug== +jest-regex-util@^29.2.0: + version "29.2.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.2.0.tgz#82ef3b587e8c303357728d0322d48bbfd2971f7b" + integrity sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA== -jest-resolve-dependencies@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.1.2.tgz#a6919e58a0c7465582cb8ec2d745b4e64ae8647f" - integrity sha512-44yYi+yHqNmH3OoWZvPgmeeiwKxhKV/0CfrzaKLSkZG9gT973PX8i+m8j6pDrTYhhHoiKfF3YUFg/6AeuHw4HQ== +jest-resolve-dependencies@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.2.2.tgz#1f444766f37a25f1490b5137408b6ff746a05d64" + integrity sha512-wWOmgbkbIC2NmFsq8Lb+3EkHuW5oZfctffTGvwsA4JcJ1IRk8b2tg+hz44f0lngvRTeHvp3Kyix9ACgudHH9aQ== dependencies: - jest-regex-util "^29.0.0" - jest-snapshot "^29.1.2" + jest-regex-util "^29.2.0" + jest-snapshot "^29.2.2" -jest-resolve@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.1.2.tgz#9dd8c2fc83e59ee7d676b14bd45a5f89e877741d" - integrity sha512-7fcOr+k7UYSVRJYhSmJHIid3AnDBcLQX3VmT9OSbPWsWz1MfT7bcoerMhADKGvKCoMpOHUQaDHtQoNp/P9JMGg== +jest-resolve@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.2.2.tgz#ad6436053b0638b41e12bbddde2b66e1397b35b5" + integrity sha512-3gaLpiC3kr14rJR3w7vWh0CBX2QAhfpfiQTwrFPvVrcHe5VUBtIXaR004aWE/X9B2CFrITOQAp5gxLONGrk6GA== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.1.2" + jest-haste-map "^29.2.1" jest-pnp-resolver "^1.2.2" - jest-util "^29.1.2" - jest-validate "^29.1.2" + jest-util "^29.2.1" + jest-validate "^29.2.2" resolve "^1.20.0" resolve.exports "^1.1.0" slash "^3.0.0" -jest-runner@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.1.2.tgz#f18b2b86101341e047de8c2f51a5fdc4e97d053a" - integrity sha512-yy3LEWw8KuBCmg7sCGDIqKwJlULBuNIQa2eFSVgVASWdXbMYZ9H/X0tnXt70XFoGf92W2sOQDOIFAA6f2BG04Q== +jest-runner@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.2.2.tgz#6b5302ed15eba8bf05e6b14d40f1e8d469564da3" + integrity sha512-1CpUxXDrbsfy9Hr9/1zCUUhT813kGGK//58HeIw/t8fa/DmkecEwZSWlb1N/xDKXg3uCFHQp1GCvlSClfImMxg== dependencies: - "@jest/console" "^29.1.2" - "@jest/environment" "^29.1.2" - "@jest/test-result" "^29.1.2" - "@jest/transform" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/console" "^29.2.1" + "@jest/environment" "^29.2.2" + "@jest/test-result" "^29.2.1" + "@jest/transform" "^29.2.2" + "@jest/types" "^29.2.1" "@types/node" "*" chalk "^4.0.0" - emittery "^0.10.2" + emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^29.0.0" - jest-environment-node "^29.1.2" - jest-haste-map "^29.1.2" - jest-leak-detector "^29.1.2" - jest-message-util "^29.1.2" - jest-resolve "^29.1.2" - jest-runtime "^29.1.2" - jest-util "^29.1.2" - jest-watcher "^29.1.2" - jest-worker "^29.1.2" + jest-docblock "^29.2.0" + jest-environment-node "^29.2.2" + jest-haste-map "^29.2.1" + jest-leak-detector "^29.2.1" + jest-message-util "^29.2.1" + jest-resolve "^29.2.2" + jest-runtime "^29.2.2" + jest-util "^29.2.1" + jest-watcher "^29.2.2" + jest-worker "^29.2.1" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.1.2.tgz#dbcd57103d61115479108d5864bdcd661d9c6783" - integrity sha512-jr8VJLIf+cYc+8hbrpt412n5jX3tiXmpPSYTGnwcvNemY+EOuLNiYnHJ3Kp25rkaAcTWOEI4ZdOIQcwYcXIAZw== - dependencies: - "@jest/environment" "^29.1.2" - "@jest/fake-timers" "^29.1.2" - "@jest/globals" "^29.1.2" - "@jest/source-map" "^29.0.0" - "@jest/test-result" "^29.1.2" - "@jest/transform" "^29.1.2" - "@jest/types" "^29.1.2" +jest-runtime@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.2.2.tgz#4068ee82423769a481460efd21d45a8efaa5c179" + integrity sha512-TpR1V6zRdLynckKDIQaY41od4o0xWL+KOPUCZvJK2bu5P1UXhjobt5nJ2ICNeIxgyj9NGkO0aWgDqYPVhDNKjA== + dependencies: + "@jest/environment" "^29.2.2" + "@jest/fake-timers" "^29.2.2" + "@jest/globals" "^29.2.2" + "@jest/source-map" "^29.2.0" + "@jest/test-result" "^29.2.1" + "@jest/transform" "^29.2.2" + "@jest/types" "^29.2.1" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.1.2" - jest-message-util "^29.1.2" - jest-mock "^29.1.2" - jest-regex-util "^29.0.0" - jest-resolve "^29.1.2" - jest-snapshot "^29.1.2" - jest-util "^29.1.2" + jest-haste-map "^29.2.1" + jest-message-util "^29.2.1" + jest-mock "^29.2.2" + jest-regex-util "^29.2.0" + jest-resolve "^29.2.2" + jest-snapshot "^29.2.2" + jest-util "^29.2.1" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.1.2.tgz#7dd277e88c45f2d2ff5888de1612e63c7ceb575b" - integrity sha512-rYFomGpVMdBlfwTYxkUp3sjD6usptvZcONFYNqVlaz4EpHPnDvlWjvmOQ9OCSNKqYZqLM2aS3wq01tWujLg7gg== +jest-snapshot@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.2.2.tgz#1016ce60297b77382386bad561107174604690c2" + integrity sha512-GfKJrpZ5SMqhli3NJ+mOspDqtZfJBryGA8RIBxF+G+WbDoC7HCqKaeAss4Z/Sab6bAW11ffasx8/vGsj83jyjA== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" @@ -3805,28 +3633,28 @@ jest-snapshot@^29.1.2: "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.1.2" - "@jest/transform" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/expect-utils" "^29.2.2" + "@jest/transform" "^29.2.2" + "@jest/types" "^29.2.1" "@types/babel__traverse" "^7.0.6" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.1.2" + expect "^29.2.2" graceful-fs "^4.2.9" - jest-diff "^29.1.2" - jest-get-type "^29.0.0" - jest-haste-map "^29.1.2" - jest-matcher-utils "^29.1.2" - jest-message-util "^29.1.2" - jest-util "^29.1.2" + jest-diff "^29.2.1" + jest-get-type "^29.2.0" + jest-haste-map "^29.2.1" + jest-matcher-utils "^29.2.2" + jest-message-util "^29.2.1" + jest-util "^29.2.1" natural-compare "^1.4.0" - pretty-format "^29.1.2" + pretty-format "^29.2.1" semver "^7.3.5" jest-util@^28.1.3: version "28.1.3" - resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.3.tgz#f4f932aa0074f0679943220ff9cbba7e497028b0" integrity sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ== dependencies: "@jest/types" "^28.1.3" @@ -3836,98 +3664,86 @@ jest-util@^28.1.3: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.0.3.tgz" - integrity sha512-Q0xaG3YRG8QiTC4R6fHjHQPaPpz9pJBEi0AeOE4mQh/FuWOijFjGXMMOfQEaU9i3z76cNR7FobZZUQnL6IyfdQ== +jest-util@^29.2.1: + version "29.2.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.2.1.tgz#f26872ba0dc8cbefaba32c34f98935f6cf5fc747" + integrity sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g== dependencies: - "@jest/types" "^29.0.3" + "@jest/types" "^29.2.1" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.1.2.tgz#ac5798e93cb6a6703084e194cfa0898d66126df1" - integrity sha512-vPCk9F353i0Ymx3WQq3+a4lZ07NXu9Ca8wya6o4Fe4/aO1e1awMMprZ3woPFpKwghEOW+UXgd15vVotuNN9ONQ== +jest-validate@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.2.2.tgz#e43ce1931292dfc052562a11bc681af3805eadce" + integrity sha512-eJXATaKaSnOuxNfs8CLHgdABFgUrd0TtWS8QckiJ4L/QVDF4KVbZFBBOwCBZHOS0Rc5fOxqngXeGXE3nGQkpQA== dependencies: - "@jest/types" "^29.1.2" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.1.2.tgz#83a728b8f6354da2e52346878c8bc7383516ca51" - integrity sha512-k71pOslNlV8fVyI+mEySy2pq9KdXdgZtm7NHrBX8LghJayc3wWZH0Yr0mtYNGaCU4F1OLPXRkwZR0dBm/ClshA== - dependencies: - "@jest/types" "^29.1.2" + "@jest/types" "^29.2.1" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^29.0.0" + jest-get-type "^29.2.0" leven "^3.1.0" - pretty-format "^29.1.2" + pretty-format "^29.2.1" -jest-watcher@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.1.2.tgz#de21439b7d889e2fcf62cc2a4779ef1a3f1f3c62" - integrity sha512-6JUIUKVdAvcxC6bM8/dMgqY2N4lbT+jZVsxh0hCJRbwkIEnbr/aPjMQ28fNDI5lB51Klh00MWZZeVf27KBUj5w== +jest-watcher@^29.2.2: + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.2.2.tgz#7093d4ea8177e0a0da87681a9e7b09a258b9daf7" + integrity sha512-j2otfqh7mOvMgN2WlJ0n7gIx9XCMWntheYGlBK7+5g3b1Su13/UAK7pdKGyd4kDlrLwtH2QPvRv5oNIxWvsJ1w== dependencies: - "@jest/test-result" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/test-result" "^29.2.1" + "@jest/types" "^29.2.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - emittery "^0.10.2" - jest-util "^29.1.2" + emittery "^0.13.1" + jest-util "^29.2.1" string-length "^4.0.1" jest-worker@^28.1.3: version "28.1.3" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.3.tgz#7e3c4ce3fa23d1bb6accb169e7f396f98ed4bb98" integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.1.2.tgz#a68302af61bce82b42a9a57285ca7499d29b2afc" - integrity sha512-AdTZJxKjTSPHbXT/AIOjQVmoFx0LHFcVabWu0sxI7PAy7rFf8c0upyvgBKgguVXdM4vY74JdwkyD4hSmpTW8jA== +jest-worker@^29.2.1: + version "29.2.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.2.1.tgz#8ba68255438252e1674f990f0180c54dfa26a3b1" + integrity sha512-ROHTZ+oj7sBrgtv46zZ84uWky71AoYi0vEV9CdEtc1FQunsoAGe5HbQmW76nI5QWdvECVPrSi1MCVUmizSavMg== dependencies: "@types/node" "*" - jest-util "^29.1.2" + jest-util "^29.2.1" merge-stream "^2.0.0" supports-color "^8.0.0" jest@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.1.2.tgz#f821a1695ffd6cd0efc3b59d2dfcc70a98582499" - integrity sha512-5wEIPpCezgORnqf+rCaYD1SK+mNN7NsstWzIsuvsnrhR/hSxXWd82oI7DkrbJ+XTD28/eG8SmxdGvukrGGK6Tw== + version "29.2.2" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.2.2.tgz#24da83cbbce514718acd698926b7679109630476" + integrity sha512-r+0zCN9kUqoON6IjDdjbrsWobXM/09Nd45kIPRD8kloaRh1z5ZCMdVsgLXGxmlL7UpAJsvCYOQNO+NjvG/gqiQ== dependencies: - "@jest/core" "^29.1.2" - "@jest/types" "^29.1.2" + "@jest/core" "^29.2.2" + "@jest/types" "^29.2.1" import-local "^3.0.2" - jest-cli "^29.1.2" + jest-cli "^29.2.2" js-sdsl@^4.1.4: - version "4.1.4" - resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz" - integrity sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw== + version "4.1.5" + resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.5.tgz#1ff1645e6b4d1b028cd3f862db88c9d887f26e2a" + integrity sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q== js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: version "3.14.1" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" @@ -3935,54 +3751,54 @@ js-yaml@^3.13.1: js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" jsesc@^2.5.1: version "2.5.2" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== jsesc@~0.5.0: version "0.5.0" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== json-parse-even-better-errors@^2.3.0: version "2.3.1" - resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^0.4.1: version "0.4.1" - resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json5@^2.2.1: version "2.2.1" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== kleur@^3.0.3: version "3.0.3" - resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== leven@^3.1.0: version "3.1.0" - resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== levn@^0.4.1: version "0.4.1" - resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" @@ -3990,91 +3806,91 @@ levn@^0.4.1: libsodium-wrappers@^0.7.6: version "0.7.10" - resolved "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz" + resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz#13ced44cacb0fc44d6ac9ce67d725956089ce733" integrity sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg== dependencies: libsodium "^0.7.0" libsodium@^0.7.0: version "0.7.10" - resolved "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz" + resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.10.tgz#c2429a7e4c0836f879d701fec2c8a208af024159" integrity sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ== lines-and-columns@^1.1.6: version "1.2.4" - resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== locate-path@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: p-locate "^4.1.0" locate-path@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" lodash.debounce@^4.0.8: version "4.0.8" - resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== lodash.merge@^4.6.2: version "4.6.2" - resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash@^4.17.12, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0: version "4.17.21" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== long@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -long@^5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/long/-/long-5.2.0.tgz" - integrity sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w== +long@^5.2.0, long@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.1.tgz#e27595d0083d103d2fa2c20c7699f8e0c92b897f" + integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A== lru-cache@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: yallist "^4.0.0" lru-queue@^0.1.0: version "0.1.0" - resolved "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz" + resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" integrity sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ== dependencies: es5-ext "~0.10.2" make-dir@^3.0.0: version "3.1.0" - resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" makeerror@1.0.12: version "1.0.12" - resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== dependencies: tmpl "1.0.5" memoizee@^0.4.15: version "0.4.15" - resolved "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz" + resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72" integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== dependencies: d "^1.0.1" @@ -4088,17 +3904,17 @@ memoizee@^0.4.15: merge-stream@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" - resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: version "4.0.5" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: braces "^3.0.2" @@ -4106,143 +3922,143 @@ micromatch@^4.0.4: mimic-fn@^1.0.0: version "1.2.0" - resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== mimic-fn@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== minimalistic-crypto-utils@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" minimatch@^5.0.1: version "5.1.0" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== dependencies: brace-expansion "^2.0.1" -minimist@1.2.6, minimist@^1.2.6: +minimist@1.2.6: version "1.2.6" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== +minimist@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + mkdirp@1.0.4, mkdirp@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== ms@2.1.2: version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== mute-stream@0.0.7: version "0.0.7" - resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ== mz@^2.7.0: version "2.7.0" - resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== dependencies: any-promise "^1.0.0" object-assign "^4.0.1" thenify-all "^1.0.0" +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== next-tick@1, next-tick@^1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== node-int64@^0.4.0: version "0.4.0" - resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-releases@^2.0.6: version "2.0.6" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== normalize-path@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== npm-run-path@^4.0.1: version "4.0.1" - resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" object-assign@^4.0.1: version "4.1.1" - resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== object-keys@^1.1.1: version "1.1.1" - resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.0: - version "4.1.4" - resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - has-symbols "^1.0.3" - object-keys "^1.1.1" - once@^1.3.0: version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" onetime@^2.0.0: version "2.0.1" - resolved "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== dependencies: mimic-fn "^1.0.0" onetime@^5.1.2: version "5.1.2" - resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" optionator@^0.9.1: version "0.9.1" - resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: deep-is "^0.1.3" @@ -4254,57 +4070,57 @@ optionator@^0.9.1: os-tmpdir@~1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== p-limit@^2.2.0: version "2.3.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" p-locate@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: p-limit "^2.2.0" p-locate@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" p-try@^2.0.0: version "2.2.0" - resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== pako@^2.0.2: version "2.0.4" - resolved "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.4.tgz#6cebc4bbb0b6c73b0d5b8d7e8476e2b2fbea576d" integrity sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg== parent-module@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" parse-json@^5.2.0: version "5.2.0" - resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" @@ -4314,86 +4130,77 @@ parse-json@^5.2.0: parse-package-name@1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/parse-package-name/-/parse-package-name-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/parse-package-name/-/parse-package-name-1.0.0.tgz#1a108757e4ffc6889d5e78bcc4932a97c097a5a7" integrity sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg== path-exists@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" - resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.7: version "1.0.7" - resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== picocolors@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pirates@^4.0.4: version "4.0.5" - resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== pkg-dir@^4.2.0: version "4.2.0" - resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: find-up "^4.0.0" prelude-ls@^1.2.1: version "1.2.1" - resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prepend-file@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/prepend-file/-/prepend-file-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/prepend-file/-/prepend-file-2.0.1.tgz#6a624b474a65ab1f87dc24d1757d5a6d989eb2db" integrity sha512-0hXWjmOpz5YBIk6xujS0lYtCw6IAA0wCR3fw49UGTLc3E9BIhcxgqdMa8rzGvrtt2F8wFiGP42oEpQ8fo9zhRw== dependencies: temp-write "^4.0.0" prettier@^2.6.2, prettier@^2.7.1: version "2.7.1" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== -pretty-format@^29.0.0, pretty-format@^29.0.3: - version "29.0.3" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.3.tgz" - integrity sha512-cHudsvQr1K5vNVLbvYF/nv3Qy/F/BcEKxGuIeMiVMRHxPOO1RxXooP8g/ZrwAp7Dx+KdMZoOc7NxLHhMrP2f9Q== - dependencies: - "@jest/schemas" "^29.0.0" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -pretty-format@^29.1.2: - version "29.1.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.1.2.tgz#b1f6b75be7d699be1a051f5da36e8ae9e76a8e6a" - integrity sha512-CGJ6VVGXVRP2o2Dorl4mAwwvDWT25luIsYhkyVQW32E4nL+TgW939J7LlKT/npq5Cpq6j3s+sy+13yk7xYpBmg== +pretty-format@^29.0.0, pretty-format@^29.2.1: + version "29.2.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.2.1.tgz#86e7748fe8bbc96a6a4e04fa99172630907a9611" + integrity sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA== dependencies: "@jest/schemas" "^29.0.0" ansi-styles "^5.0.0" @@ -4401,7 +4208,7 @@ pretty-format@^29.1.2: prompts@^2.0.1: version "2.4.2" - resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== dependencies: kleur "^3.0.3" @@ -4409,7 +4216,7 @@ prompts@^2.0.1: protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: version "6.11.3" - resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== dependencies: "@protobufjs/aspromise" "^1.1.2" @@ -4428,114 +4235,114 @@ protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: punycode@^2.1.0: version "2.1.1" - resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== queue-microtask@^1.2.2: version "1.2.3" - resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== react-is@^18.0.0: version "18.2.0" - resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== readonly-date@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/readonly-date/-/readonly-date-1.0.0.tgz#5af785464d8c7d7c40b9d738cbde8c646f97dcd9" integrity sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ== rechoir@^0.6.2: version "0.6.2" - resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== dependencies: resolve "^1.1.6" -regenerate-unicode-properties@^10.0.1: - version "10.0.1" - resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz" - integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== +regenerate-unicode-properties@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" + integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== dependencies: regenerate "^1.4.2" regenerate@^1.4.2: version "1.4.2" - resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.13.4: - version "0.13.9" - resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" - integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +regenerator-runtime@^0.13.10: + version "0.13.10" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee" + integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw== regenerator-transform@^0.15.0: version "0.15.0" - resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== dependencies: "@babel/runtime" "^7.8.4" regexpp@^3.2.0: version "3.2.0" - resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== regexpu-core@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz" - integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA== + version "5.2.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.1.tgz#a69c26f324c1e962e9ffd0b88b055caba8089139" + integrity sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ== dependencies: regenerate "^1.4.2" - regenerate-unicode-properties "^10.0.1" - regjsgen "^0.6.0" - regjsparser "^0.8.2" + regenerate-unicode-properties "^10.1.0" + regjsgen "^0.7.1" + regjsparser "^0.9.1" unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" -regjsgen@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz" - integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== +regjsgen@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.7.1.tgz#ee5ef30e18d3f09b7c369b76e7c2373ed25546f6" + integrity sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA== -regjsparser@^0.8.2: - version "0.8.4" - resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz" - integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== dependencies: jsesc "~0.5.0" require-directory@^2.1.1: version "2.1.1" - resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== resolve-cwd@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== dependencies: resolve-from "^5.0.0" resolve-from@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve-from@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve.exports@^1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== resolve@^1.1.6, resolve@^1.14.2, resolve@^1.20.0: version "1.22.1" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== dependencies: is-core-module "^2.9.0" @@ -4544,7 +4351,7 @@ resolve@^1.1.6, resolve@^1.14.2, resolve@^1.20.0: restore-cursor@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== dependencies: onetime "^2.0.0" @@ -4552,84 +4359,79 @@ restore-cursor@^2.0.0: reusify@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rimraf@3.0.2, rimraf@^3.0.2: version "3.0.2" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" run-async@^2.2.0, run-async@^2.3.0: version "2.4.1" - resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== run-parallel@^1.1.9: version "1.2.0" - resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" rx-lite-aggregates@^4.0.8: version "4.0.8" - resolved "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" integrity sha512-3xPNZGW93oCjiO7PtKxRK6iOVYBWBvtf9QHDfU23Oc+dLIQmAV//UnyXV/yihv81VS/UqoQPk4NegS8EFi55Hg== dependencies: rx-lite "*" rx-lite@*, rx-lite@^4.0.8: version "4.0.8" - resolved "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" integrity sha512-Cun9QucwK6MIrp3mry/Y7hqD1oFqTYLQ4pGxaHTjIdaFDWRGGLikqp6u8LcWJnzpoALg9hap+JGk8sFIUuEGNA== rxjs@^6.4.0: version "6.6.7" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: tslib "^1.9.0" -safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - "safer-buffer@>= 2.1.2 < 3": version "2.1.2" - resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== semver@^7.3.5, semver@^7.3.7: - version "7.3.7" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== dependencies: lru-cache "^6.0.0" shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== shelljs@0.8.5: version "0.8.5" - resolved "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== dependencies: glob "^7.0.0" @@ -4638,22 +4440,22 @@ shelljs@0.8.5: signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== sisteransi@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== slash@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== source-map-support@0.5.13: version "0.5.13" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== dependencies: buffer-from "^1.0.0" @@ -4661,24 +4463,24 @@ source-map-support@0.5.13: source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== sprintf-js@~1.0.2: version "1.0.3" - resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== stack-utils@^2.0.3: version "2.0.5" - resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== dependencies: escape-string-regexp "^2.0.0" string-length@^4.0.1: version "4.0.2" - resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== dependencies: char-regex "^1.0.2" @@ -4686,7 +4488,7 @@ string-length@^4.0.1: string-width@^2.0.0, string-width@^2.1.0: version "2.1.1" - resolved "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== dependencies: is-fullwidth-code-point "^2.0.0" @@ -4694,7 +4496,7 @@ string-width@^2.0.0, string-width@^2.1.0: string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" @@ -4703,99 +4505,91 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: strip-ansi@^3.0.0: version "3.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== dependencies: ansi-regex "^2.0.0" strip-ansi@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow== dependencies: ansi-regex "^3.0.0" strip-ansi@^5.1.0: version "5.2.0" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== dependencies: ansi-regex "^4.1.0" strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-bom@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== strip-final-newline@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== supports-color@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== supports-color@^5.3.0: version "5.5.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.2.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" supports-color@^8.0.0: version "8.1.1" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" -supports-hyperlinks@^2.0.0: - version "2.3.0" - resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz" - integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== symbol-observable@^2.0.3: version "2.0.3" - resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-2.0.3.tgz#5b521d3d07a43c351055fa43b8355b62d33fd16a" integrity sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA== temp-dir@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== temp-write@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/temp-write/-/temp-write-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-4.0.0.tgz#cd2e0825fc826ae72d201dc26eef3bf7e6fc9320" integrity sha512-HIeWmj77uOOHb0QX7siN3OtwV3CTntquin6TNVg6SHOqCP3hYKmox90eeFOGaY1MqJ9WYDDjkyZrW6qS5AWpbw== dependencies: graceful-fs "^4.1.15" @@ -4804,17 +4598,9 @@ temp-write@^4.0.0: temp-dir "^1.0.0" uuid "^3.3.2" -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== - dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" - test-exclude@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== dependencies: "@istanbuljs/schema" "^0.1.2" @@ -4823,31 +4609,31 @@ test-exclude@^6.0.0: text-table@^0.2.0: version "0.2.0" - resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== thenify-all@^1.0.0: version "1.6.0" - resolved "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== dependencies: thenify ">= 3.1.0 < 4" "thenify@>= 3.1.0 < 4": version "3.3.1" - resolved "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== dependencies: any-promise "^1.0.0" through@^2.3.6: version "2.3.8" - resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== timers-ext@^0.1.7: version "0.1.7" - resolved "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz" + resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== dependencies: es5-ext "~0.10.46" @@ -4855,70 +4641,70 @@ timers-ext@^0.1.7: tmp@^0.0.33: version "0.0.33" - resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== dependencies: os-tmpdir "~1.0.2" tmpl@1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-fast-properties@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tsutils@^3.21.0: version "3.21.0" - resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" - resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" type-detect@4.0.8: version "4.0.8" - resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== type-fest@^0.20.2: version "0.20.2" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== type-fest@^0.21.3: version "0.21.3" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== type@^1.0.1: version "1.2.0" - resolved "https://registry.npmjs.org/type/-/type-1.2.0.tgz" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== type@^2.7.2: version "2.7.2" - resolved "https://registry.npmjs.org/type/-/type-2.7.2.tgz" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== typescript@^4.8.4: @@ -4928,12 +4714,12 @@ typescript@^4.8.4: unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== unicode-match-property-ecmascript@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== dependencies: unicode-canonical-property-names-ecmascript "^2.0.0" @@ -4941,44 +4727,44 @@ unicode-match-property-ecmascript@^2.0.0: unicode-match-property-value-ecmascript@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== unicode-property-aliases-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz" - integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== -update-browserslist-db@^1.0.5: - version "1.0.7" - resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz" - integrity sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg== +update-browserslist-db@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== dependencies: escalade "^3.1.1" picocolors "^1.0.0" uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" util@^0.10.3: version "0.10.4" - resolved "https://registry.npmjs.org/util/-/util-0.10.4.tgz" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== dependencies: inherits "2.0.3" uuid@^3.3.2: version "3.4.0" - resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== v8-to-istanbul@^9.0.1: version "9.0.1" - resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz#b6f994b0b5d4ef255e17a0d17dc444a9f5132fa4" integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w== dependencies: "@jridgewell/trace-mapping" "^0.3.12" @@ -4987,15 +4773,15 @@ v8-to-istanbul@^9.0.1: walker@^1.0.8: version "1.0.8" - resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== dependencies: makeerror "1.0.12" -wasm-ast-types@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.13.0.tgz#79068ca2c51c097546ed7abdc61a86f7ad43836b" - integrity sha512-Rbg+02LqSYc4v0jGt6L+edLUzoQyfml3nBU13T/4e3T/L8BG25ein5mHSfA+/0l9y29EsNloD0LYr5KQnunN0w== +wasm-ast-types@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.14.0.tgz#c6e135df2f029280a4be1dbdbf01d4de488a244b" + integrity sha512-1DAiGmLexIA2nizkLHQdyTN3GWb1bdox2/iroyrBmeazTwCdAsPhud/5Pro9JBSmoWjnBGSdlsUYTaY5n25PcA== dependencies: "@babel/runtime" "^7.18.9" "@babel/types" "7.18.10" @@ -5006,19 +4792,19 @@ wasm-ast-types@^0.13.0: which@^2.0.1: version "2.0.2" - resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" word-wrap@^1.2.3: version "1.2.3" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== wrap-ansi@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -5027,12 +4813,12 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== write-file-atomic@^4.0.1: version "4.0.2" - resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== dependencies: imurmurhash "^0.1.4" @@ -5040,12 +4826,12 @@ write-file-atomic@^4.0.1: ws@^7: version "7.5.9" - resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== xstream@^11.14.0: version "11.14.0" - resolved "https://registry.npmjs.org/xstream/-/xstream-11.14.0.tgz" + resolved "https://registry.yarnpkg.com/xstream/-/xstream-11.14.0.tgz#2c071d26b18310523b6877e86b4e54df068a9ae5" integrity sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw== dependencies: globalthis "^1.0.1" @@ -5053,25 +4839,25 @@ xstream@^11.14.0: y18n@^5.0.5: version "5.0.8" - resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yargs-parser@^21.0.0: version "21.1.1" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@^17.3.1: - version "17.5.1" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz" - integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== + version "17.6.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.1.tgz#712508771045019cda059bc1ba3ae091aaa1402e" + integrity sha512-leBuCGrL4dAd6ispNOGsJlhd0uZ6Qehkbu/B9KCR+Pxa/NVdNwi+i31lo0buCm6XxhJQFshXCD0/evfV4xfoUg== dependencies: - cliui "^7.0.2" + cliui "^8.0.1" escalade "^3.1.1" get-caller-file "^2.0.5" require-directory "^2.1.1" @@ -5081,5 +4867,5 @@ yargs@^17.3.1: yocto-queue@^0.1.0: version "0.1.0" - resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From eab7637fc51767cc5c43669061932221dbb92d7d Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 7 Nov 2022 12:45:09 +0100 Subject: [PATCH 077/218] Fields of Mars limits (#40) * Fields of mars only limits * Adding message composer to ts-codedgen generated types * inter_iter consumption --- contracts/credit-manager/src/execute.rs | 75 ++- contracts/credit-manager/src/lib.rs | 1 + contracts/credit-manager/src/query.rs | 6 +- contracts/credit-manager/src/refund.rs | 30 ++ contracts/credit-manager/src/utils.rs | 33 +- contracts/credit-manager/src/vault/enter.rs | 13 + .../test_enumerate_vault_coin_balances.rs | 1 + .../tests/test_enumerate_vault_positions.rs | 1 + .../tests/test_fields_vault_limit.rs | 66 +++ .../tests/test_refund_balances.rs | 98 ++++ packages/rover/src/error.rs | 3 + packages/rover/src/msg/execute.rs | 6 + .../mars-credit-manager.json | 58 +++ scripts/codegen/index.ts | 2 +- scripts/deploy/addresses/osmo-test-4.json | 12 +- scripts/deploy/base/deployer.ts | 29 +- scripts/deploy/base/rover.ts | 24 +- scripts/package.json | 8 +- scripts/types/config.ts | 12 +- .../MarsAccountNft.client.ts} | 13 +- .../MarsAccountNft.message-composer.ts | 396 ++++++++++++++++ .../MarsAccountNft.react-query.ts} | 283 +++++++----- .../MarsAccountNft.types.ts} | 0 .../bundle.ts | 9 +- .../MarsCreditManager.client.ts} | 14 +- .../MarsCreditManager.message-composer.ts | 173 +++++++ .../MarsCreditManager.react-query.ts} | 248 +++++----- .../MarsCreditManager.types.ts} | 13 + .../generated/mars-credit-manager/bundle.ts | 14 + .../MarsMockOracle.client.ts} | 13 +- .../MarsMockOracle.message-composer.ts | 71 +++ .../MarsMockOracle.react-query.ts} | 35 +- .../MarsMockOracle.types.ts} | 0 .../bundle.ts | 9 +- .../MarsMockRedBank.client.ts} | 13 +- .../MarsMockRedBank.message-composer.ts | 432 ++++++++++++++++++ .../MarsMockRedBank.react-query.ts} | 299 ++++++------ .../MarsMockRedBank.types.ts} | 0 .../generated/mars-mock-red-bank/bundle.ts | 14 + .../MarsMockVault.client.ts} | 13 +- .../MarsMockVault.message-composer.ts | 134 ++++++ .../MarsMockVault.react-query.ts} | 156 ++++--- .../MarsMockVault.types.ts} | 0 .../bundle.ts | 9 +- .../MarsMockZapper.client.ts} | 13 +- .../MarsMockZapper.message-composer.ts | 110 +++++ .../MarsMockZapper.react-query.ts} | 58 +-- .../MarsMockZapper.types.ts} | 0 .../bundle.ts | 9 +- .../MarsOracleAdapter.message-composer.ts | 75 +++ .../generated/mars-oracle-adapter/bundle.ts | 9 +- .../MarsSwapperBase.client.ts} | 13 +- .../MarsSwapperBase.message-composer.ts | 200 ++++++++ .../MarsSwapperBase.react-query.ts} | 102 ++--- .../MarsSwapperBase.types.ts} | 0 .../generated/mars-swapper-base/bundle.ts | 14 + scripts/types/generated/mock-vault/bundle.ts | 13 - scripts/types/generated/mock-zapper/bundle.ts | 13 - .../types/generated/swapper-base/bundle.ts | 13 - scripts/types/instantiateMsgs.ts | 14 +- scripts/yarn.lock | 132 ++++-- 61 files changed, 2850 insertions(+), 777 deletions(-) create mode 100644 contracts/credit-manager/src/refund.rs create mode 100644 contracts/credit-manager/tests/test_fields_vault_limit.rs create mode 100644 contracts/credit-manager/tests/test_refund_balances.rs rename scripts/types/generated/{account-nft/AccountNft.client.ts => mars-account-nft/MarsAccountNft.client.ts} (97%) create mode 100644 scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts rename scripts/types/generated/{account-nft/AccountNft.react-query.ts => mars-account-nft/MarsAccountNft.react-query.ts} (53%) rename scripts/types/generated/{account-nft/AccountNft.types.ts => mars-account-nft/MarsAccountNft.types.ts} (100%) rename scripts/types/generated/{credit-manager => mars-account-nft}/bundle.ts (51%) rename scripts/types/generated/{credit-manager/CreditManager.client.ts => mars-credit-manager/MarsCreditManager.client.ts} (96%) create mode 100644 scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts rename scripts/types/generated/{credit-manager/CreditManager.react-query.ts => mars-credit-manager/MarsCreditManager.react-query.ts} (56%) rename scripts/types/generated/{credit-manager/CreditManager.types.ts => mars-credit-manager/MarsCreditManager.types.ts} (97%) create mode 100644 scripts/types/generated/mars-credit-manager/bundle.ts rename scripts/types/generated/{mock-oracle/MockOracle.client.ts => mars-mock-oracle/MarsMockOracle.client.ts} (85%) create mode 100644 scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts rename scripts/types/generated/{mock-oracle/MockOracle.react-query.ts => mars-mock-oracle/MarsMockOracle.react-query.ts} (58%) rename scripts/types/generated/{mock-oracle/MockOracle.types.ts => mars-mock-oracle/MarsMockOracle.types.ts} (100%) rename scripts/types/generated/{account-nft => mars-mock-oracle}/bundle.ts (51%) rename scripts/types/generated/{mock-red-bank/MockRedBank.client.ts => mars-mock-red-bank/MarsMockRedBank.client.ts} (97%) create mode 100644 scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts rename scripts/types/generated/{mock-red-bank/MockRedBank.react-query.ts => mars-mock-red-bank/MarsMockRedBank.react-query.ts} (56%) rename scripts/types/generated/{mock-red-bank/MockRedBank.types.ts => mars-mock-red-bank/MarsMockRedBank.types.ts} (100%) create mode 100644 scripts/types/generated/mars-mock-red-bank/bundle.ts rename scripts/types/generated/{mock-vault/MockVault.client.ts => mars-mock-vault/MarsMockVault.client.ts} (94%) create mode 100644 scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts rename scripts/types/generated/{mock-vault/MockVault.react-query.ts => mars-mock-vault/MarsMockVault.react-query.ts} (53%) rename scripts/types/generated/{mock-vault/MockVault.types.ts => mars-mock-vault/MarsMockVault.types.ts} (100%) rename scripts/types/generated/{mock-oracle => mars-mock-vault}/bundle.ts (51%) rename scripts/types/generated/{mock-zapper/MockZapper.client.ts => mars-mock-zapper/MarsMockZapper.client.ts} (90%) create mode 100644 scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts rename scripts/types/generated/{mock-zapper/MockZapper.react-query.ts => mars-mock-zapper/MarsMockZapper.react-query.ts} (59%) rename scripts/types/generated/{mock-zapper/MockZapper.types.ts => mars-mock-zapper/MarsMockZapper.types.ts} (100%) rename scripts/types/generated/{mock-red-bank => mars-mock-zapper}/bundle.ts (50%) create mode 100644 scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts rename scripts/types/generated/{swapper-base/SwapperBase.client.ts => mars-swapper-base/MarsSwapperBase.client.ts} (94%) create mode 100644 scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts rename scripts/types/generated/{swapper-base/SwapperBase.react-query.ts => mars-swapper-base/MarsSwapperBase.react-query.ts} (56%) rename scripts/types/generated/{swapper-base/SwapperBase.types.ts => mars-swapper-base/MarsSwapperBase.types.ts} (100%) create mode 100644 scripts/types/generated/mars-swapper-base/bundle.ts delete mode 100644 scripts/types/generated/mock-vault/bundle.ts delete mode 100644 scripts/types/generated/mock-zapper/bundle.ts delete mode 100644 scripts/types/generated/swapper-base/bundle.ts diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 01a2bfa29..fed8b244f 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -1,33 +1,33 @@ use cosmwasm_std::{ - to_binary, Addr, CosmosMsg, DepsMut, Empty, Env, MessageInfo, Response, StdResult, WasmMsg, + to_binary, Addr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdResult, WasmMsg, }; -use cw721::OwnerOfResponse; -use cw721_base::QueryMsg; + +use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; +use mars_rover::coins::Coins; +use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::msg::execute::{Action, CallbackMsg}; +use mars_rover::msg::instantiate::ConfigUpdates; +use mars_rover::traits::{FallbackStr, Stringify}; use crate::borrow::borrow; use crate::deposit::deposit; use crate::health::assert_below_max_ltv; +use crate::liquidate_coin::liquidate_coin; +use crate::refund::refund_coin_balances; use crate::repay::repay; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, VAULT_CONFIGS, ZAPPER, }; -use crate::vault::{ - enter_vault, exit_vault, exit_vault_unlocked, liquidate_vault, request_vault_unlock, - update_vault_coin_balance, -}; - -use crate::liquidate_coin::liquidate_coin; use crate::swap::swap_exact_in; use crate::update_coin_balances::update_coin_balance; +use crate::utils::assert_is_token_owner; +use crate::vault::{ + assert_only_one_vault_position, enter_vault, exit_vault, exit_vault_unlocked, liquidate_vault, + request_vault_unlock, update_vault_coin_balance, +}; use crate::withdraw::withdraw; use crate::zap::{provide_liquidity, withdraw_liquidity}; -use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; -use mars_rover::coins::Coins; -use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::msg::execute::{Action, CallbackMsg}; -use mars_rover::msg::instantiate::ConfigUpdates; -use mars_rover::traits::{FallbackStr, Stringify}; pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; @@ -267,6 +267,11 @@ pub fn dispatch_actions( lp_token: lp_token.clone(), }) } + Action::RefundAllCoinBalances {} => { + callbacks.push(CallbackMsg::RefundAllCoinBalances { + account_id: account_id.to_string(), + }) + } } } @@ -276,10 +281,16 @@ pub fn dispatch_actions( return Err(ContractError::ExtraFundsReceived(received_coins)); } - // after user selected actions, we assert LTV is healthy; if not, throw error and revert all actions - callbacks.extend([CallbackMsg::AssertBelowMaxLTV { - account_id: account_id.to_string(), - }]); + callbacks.extend([ + // Fields of Mars ONLY assertion. Only one vault position per credit account + CallbackMsg::AssertOneVaultPositionOnly { + account_id: account_id.to_string(), + }, + // after user selected actions, we assert LTV is healthy; if not, throw error and revert all actions + CallbackMsg::AssertBelowMaxLTV { + account_id: account_id.to_string(), + }, + ]); let callback_msgs = callbacks .iter() @@ -408,25 +419,11 @@ pub fn execute_callback( account_id, lp_token, } => withdraw_liquidity(deps, env, &account_id, lp_token), + CallbackMsg::AssertOneVaultPositionOnly { account_id } => { + assert_only_one_vault_position(deps, &account_id) + } + CallbackMsg::RefundAllCoinBalances { account_id } => { + refund_coin_balances(deps, env, &account_id) + } } } - -pub fn assert_is_token_owner(deps: &DepsMut, user: &Addr, account_id: &str) -> ContractResult<()> { - let contract_addr = ACCOUNT_NFT.load(deps.storage)?; - let owner_res: OwnerOfResponse = deps.querier.query_wasm_smart( - contract_addr, - &QueryMsg::::OwnerOf { - token_id: account_id.to_string(), - include_expired: None, - }, - )?; - - if user != &owner_res.owner { - return Err(ContractError::NotTokenOwner { - user: user.to_string(), - account_id: account_id.to_string(), - }); - } - - Ok(()) -} diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index ad405eb8e..27a76c029 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -7,6 +7,7 @@ pub mod health; pub mod instantiate; pub mod liquidate_coin; pub mod query; +pub mod refund; pub mod repay; pub mod state; pub mod swap; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 5e8713c58..de2f631ae 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -39,7 +39,7 @@ pub fn query_positions(deps: Deps, env: &Env, account_id: &str) -> ContractResul account_id: account_id.to_string(), coins: query_coin_balances(deps, account_id)?, debts: query_debt_amounts(deps, env, account_id)?, - vaults: get_vault_positions(deps, account_id)?, + vaults: query_vault_positions(deps, account_id)?, }) } @@ -82,7 +82,7 @@ fn query_debt_amounts(deps: Deps, env: &Env, account_id: &str) -> ContractResult .collect() } -fn query_coin_balances(deps: Deps, account_id: &str) -> ContractResult> { +pub fn query_coin_balances(deps: Deps, account_id: &str) -> ContractResult> { COIN_BALANCES .prefix(account_id) .range(deps.storage, None, None, Order::Ascending) @@ -145,7 +145,7 @@ pub fn query_vault_configs( .collect() } -fn get_vault_positions(deps: Deps, account_id: &str) -> ContractResult> { +pub fn query_vault_positions(deps: Deps, account_id: &str) -> ContractResult> { VAULT_POSITIONS .prefix(account_id) .range(deps.storage, None, None, Order::Ascending) diff --git a/contracts/credit-manager/src/refund.rs b/contracts/credit-manager/src/refund.rs new file mode 100644 index 000000000..fb814bb87 --- /dev/null +++ b/contracts/credit-manager/src/refund.rs @@ -0,0 +1,30 @@ +use cosmwasm_std::{to_binary, Addr, CosmosMsg, DepsMut, Env, Response, WasmMsg}; +use mars_rover::error::ContractResult; +use mars_rover::msg::execute::CallbackMsg; +use mars_rover::msg::ExecuteMsg; + +use crate::query::query_coin_balances; +use crate::utils::query_nft_token_owner; + +pub fn refund_coin_balances(deps: DepsMut, env: Env, account_id: &str) -> ContractResult { + let coins = query_coin_balances(deps.as_ref(), account_id)?; + let account_nft_owner = query_nft_token_owner(deps.as_ref(), account_id)?; + let withdraw_msgs = coins + .into_iter() + .map(|coin| { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::Withdraw { + account_id: account_id.to_string(), + coin, + recipient: Addr::unchecked(account_nft_owner.clone()), + }))?, + })) + }) + .collect::>>()?; + Ok(Response::new().add_messages(withdraw_msgs).add_attribute( + "action", + "rover/credit-manager/callback/refund_coin_balances", + )) +} diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 13c685913..08bfb227b 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -2,9 +2,11 @@ use std::collections::HashSet; use std::hash::Hash; use cosmwasm_std::{ - to_binary, Addr, Coin, CosmosMsg, Decimal, Deps, QuerierWrapper, StdResult, Storage, Uint128, - WasmMsg, + to_binary, Addr, Coin, CosmosMsg, Decimal, Deps, DepsMut, Empty, QuerierWrapper, StdResult, + Storage, Uint128, WasmMsg, }; +use cw721::OwnerOfResponse; +use cw721_base::QueryMsg; use mars_rover::error::{ContractError, ContractResult}; use mars_rover::msg::execute::CallbackMsg; @@ -12,9 +14,34 @@ use mars_rover::msg::query::CoinValue; use mars_rover::msg::ExecuteMsg; use mars_rover::traits::IntoDecimal; -use crate::state::{ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES}; +use crate::state::{ + ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES, +}; use crate::update_coin_balances::query_balance; +pub fn assert_is_token_owner(deps: &DepsMut, user: &Addr, account_id: &str) -> ContractResult<()> { + let owner = query_nft_token_owner(deps.as_ref(), account_id)?; + if user != &owner { + return Err(ContractError::NotTokenOwner { + user: user.to_string(), + account_id: account_id.to_string(), + }); + } + Ok(()) +} + +pub fn query_nft_token_owner(deps: Deps, account_id: &str) -> ContractResult { + let contract_addr = ACCOUNT_NFT.load(deps.storage)?; + let res: OwnerOfResponse = deps.querier.query_wasm_smart( + contract_addr, + &QueryMsg::::OwnerOf { + token_id: account_id.to_string(), + include_expired: None, + }, + )?; + Ok(res.owner) +} + pub fn assert_coin_is_whitelisted(storage: &mut dyn Storage, denom: &str) -> ContractResult<()> { let is_whitelisted = ALLOWED_COINS.contains(storage, denom); if !is_whitelisted { diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index 4c549ed84..7793cf41a 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -8,6 +8,7 @@ use mars_rover::error::{ContractError, ContractResult}; use mars_rover::msg::execute::CallbackMsg; use mars_rover::msg::ExecuteMsg; +use crate::query::query_vault_positions; use crate::state::{COIN_BALANCES, ORACLE, VAULT_CONFIGS}; use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance}; use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; @@ -142,3 +143,15 @@ pub fn assert_deposit_is_under_cap( Ok(()) } + +pub fn assert_only_one_vault_position(deps: DepsMut, account_id: &str) -> ContractResult { + let vaults = query_vault_positions(deps.as_ref(), account_id)?; + if vaults.len() > 1 { + return Err(ContractError::OnlyOneVaultPositionAllowed); + } + + Ok(Response::new().add_attribute( + "action", + "rover/credit-manager/callback/assert_only_one_vault_position", + )) +} diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index ca33ab95f..e2c7c876d 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -9,6 +9,7 @@ use crate::helpers::{ pub mod helpers; #[test] +#[ignore] // Test ignored due to Fields limitation on vault position amounts fn test_pagination_on_all_vault_coin_balances_query_works() { let lp_token = lp_token_info(); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index 79ac44fb6..1680afd8d 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -10,6 +10,7 @@ use crate::helpers::{ pub mod helpers; #[test] +#[ignore] // Test ignored due to Fields limitation on vault position amounts fn test_pagination_on_all_vault_positions_query_works() { let lp_token = lp_token_info(); diff --git a/contracts/credit-manager/tests/test_fields_vault_limit.rs b/contracts/credit-manager/tests/test_fields_vault_limit.rs new file mode 100644 index 000000000..fd7b271fc --- /dev/null +++ b/contracts/credit-manager/tests/test_fields_vault_limit.rs @@ -0,0 +1,66 @@ +use cosmwasm_std::{coin, Addr, Decimal, Uint128}; +use mars_rover::error::ContractError; +use mars_rover::msg::execute::Action::{Deposit, EnterVault}; + +use crate::helpers::{ + assert_err, lp_token_info, unlocked_vault_info, AccountToFund, CoinInfo, MockEnv, VaultTestInfo, +}; + +pub mod helpers; + +#[test] +fn test_can_only_have_a_single_vault_position() { + let lp_token = lp_token_info(); + let leverage_vault = unlocked_vault_info(); + + let degen_vault_token = CoinInfo { + denom: "udegen452".to_string(), + price: Decimal::from_atomics(121u128, 3).unwrap(), + max_ltv: Decimal::from_atomics(4u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), + }; + let degen_vault = VaultTestInfo { + vault_token_denom: "udegen".to_string(), + lockup: None, + base_token_denom: degen_vault_token.denom.clone(), + deposit_cap: coin(10_000_000, "uusdc"), + max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), degen_vault_token.clone()]) + .allowed_vaults(&[leverage_vault.clone(), degen_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300), degen_vault_token.to_coin(300)], + }) + .build() + .unwrap(); + + let lev_vault = mock.get_vault(&leverage_vault); + let degen_vault = mock.get_vault(°en_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault: lev_vault, + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(200)), + }, + Deposit(degen_vault_token.to_coin(200)), + EnterVault { + vault: degen_vault, + denom: degen_vault_token.denom.clone(), + amount: Some(Uint128::new(200)), + }, + ], + &[lp_token.to_coin(200), degen_vault_token.to_coin(200)], + ); + assert_err(res, ContractError::OnlyOneVaultPositionAllowed); +} diff --git a/contracts/credit-manager/tests/test_refund_balances.rs b/contracts/credit-manager/tests/test_refund_balances.rs new file mode 100644 index 000000000..a5fd2bd5e --- /dev/null +++ b/contracts/credit-manager/tests/test_refund_balances.rs @@ -0,0 +1,98 @@ +use cosmwasm_std::{coin, Addr, Uint128}; + +use mars_rover::msg::execute::Action::{Deposit, EnterVault, RefundAllCoinBalances}; + +use crate::helpers::{ + locked_vault_info, lp_token_info, uatom_info, uosmo_info, AccountToFund, MockEnv, +}; + +pub mod helpers; + +#[test] +fn test_refund_coin_balances_when_balances() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![ + coin(234, uosmo_info.denom.clone()), + coin(25, uatom_info.denom.clone()), + ], + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(uosmo_info.to_coin(234)), + Deposit(uatom_info.to_coin(25)), + RefundAllCoinBalances {}, + ], + &[uosmo_info.to_coin(234), uatom_info.to_coin(25)], + ) + .unwrap(); + + // Assert refunds have been issued + let res = mock.query_positions(&account_id); + assert_eq!(res.coins.len(), 0); + + let osmo_balance = mock.query_balance(&user, &uosmo_info.denom); + assert_eq!(osmo_balance.amount, Uint128::new(234)); + let atom_balance = mock.query_balance(&user, &uatom_info.denom); + assert_eq!(atom_balance.amount, Uint128::new(25)); +} + +#[test] +fn test_refund_coin_balances_when_no_balances() { + let lp_token = lp_token_info(); + let leverage_vault = locked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(200)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + let balance = mock.query_total_vault_coin_balance(&vault); + assert_eq!(balance, Uint128::zero()); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault, + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(200)), + }, + RefundAllCoinBalances {}, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + // Assert no error is thrown and nothing happens to coin balances + let res = mock.query_positions(&account_id); + assert_eq!(res.coins.len(), 0); + // Assert vault positions have not been effected + assert_eq!(res.vaults.len(), 1); + + // Assert nothing has been refunded to wallet + let lp_balance = mock.query_balance(&user, &lp_token.denom); + assert_eq!(lp_balance.amount, Uint128::zero()); +} diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 72a167196..b57745cfb 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -80,6 +80,9 @@ pub enum ContractError { #[error("Expected vault coins in exchange for deposit, but none were sent")] NoVaultCoinsReceived, + #[error("No more than one vault positions is allowed")] + OnlyOneVaultPositionAllowed, + #[error("{0}")] Overflow(#[from] OverflowError), diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index b3f6cef89..da4b8d5c3 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -96,6 +96,8 @@ pub enum Action { }, /// Send LP token and withdraw corresponding reserve assets from pool WithdrawLiquidity { lp_token: Coin }, + /// Refunds all coin balances back to user wallet + RefundAllCoinBalances {}, } /// Internal actions made by the contract with pre-validated inputs @@ -193,6 +195,10 @@ pub enum CallbackMsg { }, /// Send LP token and withdraw corresponding reserve assets from pool WithdrawLiquidity { account_id: String, lp_token: Coin }, + /// Checks to ensure only one vault position is taken per credit account + AssertOneVaultPositionOnly { account_id: String }, + /// Refunds all coin balances back to user wallet + RefundAllCoinBalances { account_id: String }, } impl CallbackMsg { diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index beabddf8e..5eb5a843c 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -582,6 +582,20 @@ } }, "additionalProperties": false + }, + { + "description": "Refunds all coin balances back to user wallet", + "type": "object", + "required": [ + "refund_all_coin_balances" + ], + "properties": { + "refund_all_coin_balances": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -1089,6 +1103,50 @@ } }, "additionalProperties": false + }, + { + "description": "Checks to ensure only one vault position is taken per credit account", + "type": "object", + "required": [ + "assert_one_vault_position_only" + ], + "properties": { + "assert_one_vault_position_only": { + "type": "object", + "required": [ + "account_id" + ], + "properties": { + "account_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Refunds all coin balances back to user wallet", + "type": "object", + "required": [ + "refund_all_coin_balances" + ], + "properties": { + "refund_all_coin_balances": { + "type": "object", + "required": [ + "account_id" + ], + "properties": { + "account_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, diff --git a/scripts/codegen/index.ts b/scripts/codegen/index.ts index e8a73f7ee..1a51b578d 100644 --- a/scripts/codegen/index.ts +++ b/scripts/codegen/index.ts @@ -27,7 +27,7 @@ void (async function () { queryKeys: true, }, messageComposer: { - enabled: false, + enabled: true, }, }, }) diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 7dbc56003..0c8bd4768 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,8 +1,8 @@ { - "accountNft": "osmo1s2wausalfxldwprvd6ryzawrre5k5slvpf7dqkusyp0ea68c9m4szazrzr", - "mockVault": "osmo1qlpkaqhymdtmggx2qj9jvz0asdu2e2l6mm9eypshftjed5f8p8ks2za86s", - "marsOracleAdapter": "osmo17hzreqvt79vltz7ryk0l3x4tn9lakhkr8pnjzg5n5lug6q7urnzsznq4uw", - "swapper": "osmo15czttrmmz0ecd5gxd6l5p8ew4kk86s805vzx9u9gamgzwc0na0rqsfy0j9", - "mockZapper": "osmo14rha7vczhdhfkhlh4ypptf94vshvc74fr39v8vtj938v5aue9u8svfvhj9", - "creditManager": "osmo1xxetx647k4ujfdkklkl0tvt7qsvwl9ce7wtkq0j2yum865esh6yqfust02" + "accountNft": "osmo1jjxjzahlpnsrl96wsejepqmpqpz5ugr379kzmpfkdeff3s2r7wnqxxncmq", + "mockVault": "osmo1gyjqum3fc6jqxhn8m5tcdg3j8yhjqq34764f65yjn496zzazt4hq2gccz5", + "marsOracleAdapter": "osmo1s568lnjldxrvpr420qp70plfyvl4c7xw0s4plxkff6607qmd6rgsng7ux9", + "swapper": "osmo1p2lnkpgezme7xduz4f76l2t0rtnn68dpxvkf7g4l68nmna2ljh8qe3qfuh", + "mockZapper": "osmo1dthjy47aqa3nn67dwa8pqkpuqxucxs8aw437x4ckvgthe4dp67dsje57ag", + "creditManager": "osmo1pln365zkklx3vzyts25fqc7ypmn6qfx2fspvgvlpyaefxgtr96vqyj4apl" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 89ddcd0ee..11efa8b26 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -4,25 +4,25 @@ import { printBlue, printGray, printGreen } from '../../utils/chalk' import { ARTIFACTS_PATH, Storage } from './storage' import fs from 'fs' import { InstantiateMsgs } from '../../types/instantiateMsgs' -import { InstantiateMsg as NftInstantiateMsg } from '../../types/generated/account-nft/AccountNft.types' -import { InstantiateMsg as VaultInstantiateMsg } from '../../types/generated/mock-vault/MockVault.types' -import { InstantiateMsg as SwapperInstantiateMsg } from '../../types/generated/swapper-base/SwapperBase.types' -import { InstantiateMsg as ZapperInstantiateMsg } from '../../types/generated/mock-zapper/MockZapper.types' +import { InstantiateMsg as NftInstantiateMsg } from '../../types/generated/mars-account-nft/MarsAccountNft.types' +import { InstantiateMsg as VaultInstantiateMsg } from '../../types/generated/mars-mock-vault/MarsMockVault.types' +import { InstantiateMsg as SwapperInstantiateMsg } from '../../types/generated/mars-swapper-base/MarsSwapperBase.types' +import { InstantiateMsg as ZapperInstantiateMsg } from '../../types/generated/mars-mock-zapper/MarsMockZapper.types' +import { InstantiateMsg as RoverInstantiateMsg } from '../../types/generated/mars-credit-manager/MarsCreditManager.types' import { InstantiateMsg as OracleAdapterInstantiateMsg } from '../../types/generated/mars-oracle-adapter/MarsOracleAdapter.types' -import { InstantiateMsg as RoverInstantiateMsg } from '../../types/generated/credit-manager/CreditManager.types' import { Rover } from './rover' -import { AccountNftClient } from '../../types/generated/account-nft/AccountNft.client' import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing' import { getAddress, getWallet, setupClient } from './setupDeployer' import { coin } from '@cosmjs/stargate' import { Coin } from '@cosmjs/amino' import { writeFile } from 'fs/promises' import { join, resolve } from 'path' -import { - SwapperBaseClient, - SwapperBaseQueryClient, -} from '../../types/generated/swapper-base/SwapperBase.client' import assert from 'assert' +import { + MarsSwapperBaseClient, + MarsSwapperBaseQueryClient, +} from '../../types/generated/mars-swapper-base/MarsSwapperBase.client' +import { MarsAccountNftClient } from '../../types/generated/mars-account-nft/MarsAccountNft.client' export class Deployer { constructor( @@ -121,7 +121,7 @@ export class Deployer { printBlue(`Seeding swapper w/ ${this.config.baseDenom}`) await this.transferCoin(this.storage.addresses.swapper!, coin(100, this.config.baseDenom)) - const swapClient = new SwapperBaseClient( + const swapClient = new MarsSwapperBaseClient( this.cwClient, this.deployerAddr, this.storage.addresses.swapper!, @@ -135,7 +135,10 @@ export class Deployer { route: this.config.swapRoute, }) - const swapQuery = new SwapperBaseQueryClient(this.cwClient, this.storage.addresses.swapper!) + const swapQuery = new MarsSwapperBaseQueryClient( + this.cwClient, + this.storage.addresses.swapper!, + ) const routes = await swapQuery.routes({}) assert.equal(routes.length, 1) this.storage.actions.setRouteAndSeedSwapper = true @@ -196,7 +199,7 @@ export class Deployer { async transferNftContractOwnership() { if (!this.storage.actions.proposedNewOwner) { - const nftClient = new AccountNftClient( + const nftClient = new MarsAccountNftClient( this.cwClient, this.deployerAddr, this.storage.addresses.accountNft!, diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index b0dd9d3d5..abe8cbe72 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -1,24 +1,24 @@ -import { - CreditManagerClient, - CreditManagerQueryClient, -} from '../../types/generated/credit-manager/CreditManager.client' -import { AccountNftQueryClient } from '../../types/generated/account-nft/AccountNft.client' import { Storage } from './storage' import { DeploymentConfig } from '../../types/config' import { difference } from 'lodash' import assert from 'assert' import { printBlue, printGreen } from '../../utils/chalk' import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' +import { + MarsCreditManagerClient, + MarsCreditManagerQueryClient, +} from '../../types/generated/mars-credit-manager/MarsCreditManager.client' +import { MarsAccountNftQueryClient } from '../../types/generated/mars-account-nft/MarsAccountNft.client' import { Action, Coin, ConfigUpdates, -} from '../../types/generated/credit-manager/CreditManager.types' +} from '../../types/generated/mars-credit-manager/MarsCreditManager.types' export class Rover { - private exec: CreditManagerClient - private query: CreditManagerQueryClient - private nft: AccountNftQueryClient + private exec: MarsCreditManagerClient + private query: MarsCreditManagerQueryClient + private nft: MarsAccountNftQueryClient private accountId?: string constructor( @@ -27,9 +27,9 @@ export class Rover { private config: DeploymentConfig, private cwClient: SigningCosmWasmClient, ) { - this.exec = new CreditManagerClient(cwClient, userAddr, storage.addresses.creditManager!) - this.query = new CreditManagerQueryClient(cwClient, storage.addresses.creditManager!) - this.nft = new AccountNftQueryClient(cwClient, storage.addresses.accountNft!) + this.exec = new MarsCreditManagerClient(cwClient, userAddr, storage.addresses.creditManager!) + this.query = new MarsCreditManagerQueryClient(cwClient, storage.addresses.creditManager!) + this.nft = new MarsAccountNftQueryClient(cwClient, storage.addresses.accountNft!) } async updateConfig(newConfig: ConfigUpdates) { diff --git a/scripts/package.json b/scripts/package.json index 5a254e7d2..60b71814a 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -4,7 +4,7 @@ "license": "GPL-3.0-or-later", "scripts": { "deploy:osmosis": "yarn build && node build/deploy/osmosis", - "generate-types": "yarn rust-schema && tsc --project codegen-tsconfig.json && rm -rf types/generated && node build/codegen && node build/codegen/insertIgnores.js", + "generate-types": "yarn rust-schema && tsc --project codegen-tsconfig.json && rm -rf types/generated && node build/codegen && node build/codegen/insertIgnores.js && yarn format", "rust-schema": "cd ../ && cargo make generate-all-schemas && cd scripts", "compile-wasm": "cd ../ && cargo make rust-optimizer && cd scripts", "build": "tsc", @@ -23,12 +23,12 @@ "prepend-file": "^2.0.1" }, "devDependencies": { - "@babel/preset-env": "^7.19.4", + "@babel/preset-env": "^7.20.2", "@babel/preset-typescript": "^7.18.6", - "@types/jest": "^29.1.2", + "@types/jest": "^29.2.2", "@typescript-eslint/eslint-plugin": "^5.40.0", "@typescript-eslint/parser": "^5.40.0", - "eslint": "^8.25.0", + "eslint": "^8.27.0", "eslint-config-prettier": "^8.5.0", "jest": "^29.1.2", "prettier": "^2.7.1", diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 7445a9827..1f9e5e6d2 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -1,5 +1,3 @@ -import { Coin } from './generated/credit-manager/CreditManager.types' - export enum VaultType { LOCKED, UNLOCKED, @@ -19,7 +17,10 @@ export interface DeploymentConfig { defaultGasPrice: number startingAmountForTestUser: number depositAmount: number - toGrantCreditLines: Coin[] + toGrantCreditLines: { + amount: string + denom: string + }[] borrowAmount: number repayAmount: number swapAmount: number @@ -30,7 +31,10 @@ export interface DeploymentConfig { maxLiquidationBonus: number vaultType: VaultType vaultDepositAmount: number - vaultDepositCap: Coin + vaultDepositCap: { + amount: string + denom: string + } vaultLiquidationThreshold: number vaultMaxLTV: number vaultWithdrawAmount: number diff --git a/scripts/types/generated/account-nft/AccountNft.client.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts similarity index 97% rename from scripts/types/generated/account-nft/AccountNft.client.ts rename to scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts index 1eb349654..643927d15 100644 --- a/scripts/types/generated/account-nft/AccountNft.client.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts @@ -28,8 +28,8 @@ import { MinterResponse, NumTokensResponse, String, -} from './AccountNft.types' -export interface AccountNftReadOnlyInterface { +} from './MarsAccountNft.types' +export interface MarsAccountNftReadOnlyInterface { contractAddress: string proposedNewOwner: () => Promise ownerOf: ({ @@ -94,7 +94,7 @@ export interface AccountNftReadOnlyInterface { }) => Promise minter: () => Promise } -export class AccountNftQueryClient implements AccountNftReadOnlyInterface { +export class MarsAccountNftQueryClient implements MarsAccountNftReadOnlyInterface { client: CosmWasmClient contractAddress: string @@ -253,7 +253,7 @@ export class AccountNftQueryClient implements AccountNftReadOnlyInterface { }) } } -export interface AccountNftInterface extends AccountNftReadOnlyInterface { +export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface { contractAddress: string sender: string proposeNewOwner: ( @@ -366,7 +366,10 @@ export interface AccountNftInterface extends AccountNftReadOnlyInterface { funds?: Coin[], ) => Promise } -export class AccountNftClient extends AccountNftQueryClient implements AccountNftInterface { +export class MarsAccountNftClient + extends MarsAccountNftQueryClient + implements MarsAccountNftInterface +{ client: SigningCosmWasmClient sender: string contractAddress: string diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts new file mode 100644 index 000000000..24761e47a --- /dev/null +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts @@ -0,0 +1,396 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { Coin } from '@cosmjs/amino' +import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + InstantiateMsg, + ExecuteMsg, + Binary, + Expiration, + Timestamp, + Uint64, + QueryMsg, + AllNftInfoResponseForEmpty, + OwnerOfResponse, + Approval, + NftInfoResponseForEmpty, + Empty, + OperatorsResponse, + TokensResponse, + ApprovalResponse, + ApprovalsResponse, + ContractInfoResponse, + MinterResponse, + NumTokensResponse, + String, +} from './MarsAccountNft.types' +export interface MarsAccountNftMessage { + contractAddress: string + sender: string + proposeNewOwner: ( + { + newOwner, + }: { + newOwner: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + acceptOwnership: (funds?: Coin[]) => MsgExecuteContractEncodeObject + mint: ( + { + user, + }: { + user: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + transferNft: ( + { + recipient, + tokenId, + }: { + recipient: string + tokenId: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + sendNft: ( + { + contract, + msg, + tokenId, + }: { + contract: string + msg: Binary + tokenId: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + approve: ( + { + expires, + spender, + tokenId, + }: { + expires?: Expiration + spender: string + tokenId: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + revoke: ( + { + spender, + tokenId, + }: { + spender: string + tokenId: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + approveAll: ( + { + expires, + operator, + }: { + expires?: Expiration + operator: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + revokeAll: ( + { + operator, + }: { + operator: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + burn: ( + { + tokenId, + }: { + tokenId: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject +} +export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.proposeNewOwner = this.proposeNewOwner.bind(this) + this.acceptOwnership = this.acceptOwnership.bind(this) + this.mint = this.mint.bind(this) + this.transferNft = this.transferNft.bind(this) + this.sendNft = this.sendNft.bind(this) + this.approve = this.approve.bind(this) + this.revoke = this.revoke.bind(this) + this.approveAll = this.approveAll.bind(this) + this.revokeAll = this.revokeAll.bind(this) + this.burn = this.burn.bind(this) + } + + proposeNewOwner = ( + { + newOwner, + }: { + newOwner: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + propose_new_owner: { + new_owner: newOwner, + }, + }), + ), + funds, + }), + } + } + acceptOwnership = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + accept_ownership: {}, + }), + ), + funds, + }), + } + } + mint = ( + { + user, + }: { + user: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + mint: { + user, + }, + }), + ), + funds, + }), + } + } + transferNft = ( + { + recipient, + tokenId, + }: { + recipient: string + tokenId: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + transfer_nft: { + recipient, + token_id: tokenId, + }, + }), + ), + funds, + }), + } + } + sendNft = ( + { + contract, + msg, + tokenId, + }: { + contract: string + msg: Binary + tokenId: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + send_nft: { + contract, + msg, + token_id: tokenId, + }, + }), + ), + funds, + }), + } + } + approve = ( + { + expires, + spender, + tokenId, + }: { + expires?: Expiration + spender: string + tokenId: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + approve: { + expires, + spender, + token_id: tokenId, + }, + }), + ), + funds, + }), + } + } + revoke = ( + { + spender, + tokenId, + }: { + spender: string + tokenId: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + revoke: { + spender, + token_id: tokenId, + }, + }), + ), + funds, + }), + } + } + approveAll = ( + { + expires, + operator, + }: { + expires?: Expiration + operator: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + approve_all: { + expires, + operator, + }, + }), + ), + funds, + }), + } + } + revokeAll = ( + { + operator, + }: { + operator: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + revoke_all: { + operator, + }, + }), + ), + funds, + }), + } + } + burn = ( + { + tokenId, + }: { + tokenId: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + burn: { + token_id: tokenId, + }, + }), + ), + funds, + }), + } + } +} diff --git a/scripts/types/generated/account-nft/AccountNft.react-query.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts similarity index 53% rename from scripts/types/generated/account-nft/AccountNft.react-query.ts rename to scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts index dc4dd8b85..44f64ae51 100644 --- a/scripts/types/generated/account-nft/AccountNft.react-query.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts @@ -29,49 +29,61 @@ import { MinterResponse, NumTokensResponse, String, -} from './AccountNft.types' -import { AccountNftQueryClient, AccountNftClient } from './AccountNft.client' -export const accountNftQueryKeys = { +} from './MarsAccountNft.types' +import { MarsAccountNftQueryClient, MarsAccountNftClient } from './MarsAccountNft.client' +export const marsAccountNftQueryKeys = { contract: [ { - contract: 'accountNft', + contract: 'marsAccountNft', }, ] as const, address: (contractAddress: string | undefined) => - [{ ...accountNftQueryKeys.contract[0], address: contractAddress }] as const, + [{ ...marsAccountNftQueryKeys.contract[0], address: contractAddress }] as const, proposedNewOwner: (contractAddress: string | undefined, args?: Record) => [ - { ...accountNftQueryKeys.address(contractAddress)[0], method: 'proposed_new_owner', args }, + { + ...marsAccountNftQueryKeys.address(contractAddress)[0], + method: 'proposed_new_owner', + args, + }, ] as const, ownerOf: (contractAddress: string | undefined, args?: Record) => - [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'owner_of', args }] as const, + [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'owner_of', args }] as const, approval: (contractAddress: string | undefined, args?: Record) => - [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'approval', args }] as const, + [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'approval', args }] as const, approvals: (contractAddress: string | undefined, args?: Record) => - [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'approvals', args }] as const, + [ + { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'approvals', args }, + ] as const, allOperators: (contractAddress: string | undefined, args?: Record) => [ - { ...accountNftQueryKeys.address(contractAddress)[0], method: 'all_operators', args }, + { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'all_operators', args }, ] as const, numTokens: (contractAddress: string | undefined, args?: Record) => - [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'num_tokens', args }] as const, + [ + { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'num_tokens', args }, + ] as const, contractInfo: (contractAddress: string | undefined, args?: Record) => [ - { ...accountNftQueryKeys.address(contractAddress)[0], method: 'contract_info', args }, + { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'contract_info', args }, ] as const, nftInfo: (contractAddress: string | undefined, args?: Record) => - [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'nft_info', args }] as const, + [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'nft_info', args }] as const, allNftInfo: (contractAddress: string | undefined, args?: Record) => - [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'all_nft_info', args }] as const, + [ + { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'all_nft_info', args }, + ] as const, tokens: (contractAddress: string | undefined, args?: Record) => - [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'tokens', args }] as const, + [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'tokens', args }] as const, allTokens: (contractAddress: string | undefined, args?: Record) => - [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'all_tokens', args }] as const, + [ + { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'all_tokens', args }, + ] as const, minter: (contractAddress: string | undefined, args?: Record) => - [{ ...accountNftQueryKeys.address(contractAddress)[0], method: 'minter', args }] as const, + [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'minter', args }] as const, } -export interface AccountNftReactQuery { - client: AccountNftQueryClient | undefined +export interface MarsAccountNftReactQuery { + client: MarsAccountNftQueryClient | undefined options?: Omit< UseQueryOptions, "'queryKey' | 'queryFn' | 'initialData'" @@ -79,31 +91,32 @@ export interface AccountNftReactQuery { initialData?: undefined } } -export interface AccountNftMinterQuery extends AccountNftReactQuery {} -export function useAccountNftMinterQuery({ +export interface MarsAccountNftMinterQuery + extends MarsAccountNftReactQuery {} +export function useMarsAccountNftMinterQuery({ client, options, -}: AccountNftMinterQuery) { +}: MarsAccountNftMinterQuery) { return useQuery( - accountNftQueryKeys.minter(client?.contractAddress), + marsAccountNftQueryKeys.minter(client?.contractAddress), () => (client ? client.minter() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface AccountNftAllTokensQuery - extends AccountNftReactQuery { +export interface MarsAccountNftAllTokensQuery + extends MarsAccountNftReactQuery { args: { limit?: number startAfter?: string } } -export function useAccountNftAllTokensQuery({ +export function useMarsAccountNftAllTokensQuery({ client, args, options, -}: AccountNftAllTokensQuery) { +}: MarsAccountNftAllTokensQuery) { return useQuery( - accountNftQueryKeys.allTokens(client?.contractAddress, args), + marsAccountNftQueryKeys.allTokens(client?.contractAddress, args), () => client ? client.allTokens({ @@ -114,20 +127,21 @@ export function useAccountNftAllTokensQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface AccountNftTokensQuery extends AccountNftReactQuery { +export interface MarsAccountNftTokensQuery + extends MarsAccountNftReactQuery { args: { limit?: number owner: string startAfter?: string } } -export function useAccountNftTokensQuery({ +export function useMarsAccountNftTokensQuery({ client, args, options, -}: AccountNftTokensQuery) { +}: MarsAccountNftTokensQuery) { return useQuery( - accountNftQueryKeys.tokens(client?.contractAddress, args), + marsAccountNftQueryKeys.tokens(client?.contractAddress, args), () => client ? client.tokens({ @@ -139,20 +153,20 @@ export function useAccountNftTokensQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface AccountNftAllNftInfoQuery - extends AccountNftReactQuery { +export interface MarsAccountNftAllNftInfoQuery + extends MarsAccountNftReactQuery { args: { includeExpired?: boolean tokenId: string } } -export function useAccountNftAllNftInfoQuery({ +export function useMarsAccountNftAllNftInfoQuery({ client, args, options, -}: AccountNftAllNftInfoQuery) { +}: MarsAccountNftAllNftInfoQuery) { return useQuery( - accountNftQueryKeys.allNftInfo(client?.contractAddress, args), + marsAccountNftQueryKeys.allNftInfo(client?.contractAddress, args), () => client ? client.allNftInfo({ @@ -163,19 +177,19 @@ export function useAccountNftAllNftInfoQuery { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface AccountNftNftInfoQuery - extends AccountNftReactQuery { +export interface MarsAccountNftNftInfoQuery + extends MarsAccountNftReactQuery { args: { tokenId: string } } -export function useAccountNftNftInfoQuery({ +export function useMarsAccountNftNftInfoQuery({ client, args, options, -}: AccountNftNftInfoQuery) { +}: MarsAccountNftNftInfoQuery) { return useQuery( - accountNftQueryKeys.nftInfo(client?.contractAddress, args), + marsAccountNftQueryKeys.nftInfo(client?.contractAddress, args), () => client ? client.nftInfo({ @@ -185,32 +199,32 @@ export function useAccountNftNftInfoQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface AccountNftContractInfoQuery - extends AccountNftReactQuery {} -export function useAccountNftContractInfoQuery({ +export interface MarsAccountNftContractInfoQuery + extends MarsAccountNftReactQuery {} +export function useMarsAccountNftContractInfoQuery({ client, options, -}: AccountNftContractInfoQuery) { +}: MarsAccountNftContractInfoQuery) { return useQuery( - accountNftQueryKeys.contractInfo(client?.contractAddress), + marsAccountNftQueryKeys.contractInfo(client?.contractAddress), () => (client ? client.contractInfo() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface AccountNftNumTokensQuery - extends AccountNftReactQuery {} -export function useAccountNftNumTokensQuery({ +export interface MarsAccountNftNumTokensQuery + extends MarsAccountNftReactQuery {} +export function useMarsAccountNftNumTokensQuery({ client, options, -}: AccountNftNumTokensQuery) { +}: MarsAccountNftNumTokensQuery) { return useQuery( - accountNftQueryKeys.numTokens(client?.contractAddress), + marsAccountNftQueryKeys.numTokens(client?.contractAddress), () => (client ? client.numTokens() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface AccountNftAllOperatorsQuery - extends AccountNftReactQuery { +export interface MarsAccountNftAllOperatorsQuery + extends MarsAccountNftReactQuery { args: { includeExpired?: boolean limit?: number @@ -218,13 +232,13 @@ export interface AccountNftAllOperatorsQuery startAfter?: string } } -export function useAccountNftAllOperatorsQuery({ +export function useMarsAccountNftAllOperatorsQuery({ client, args, options, -}: AccountNftAllOperatorsQuery) { +}: MarsAccountNftAllOperatorsQuery) { return useQuery( - accountNftQueryKeys.allOperators(client?.contractAddress, args), + marsAccountNftQueryKeys.allOperators(client?.contractAddress, args), () => client ? client.allOperators({ @@ -237,20 +251,20 @@ export function useAccountNftAllOperatorsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface AccountNftApprovalsQuery - extends AccountNftReactQuery { +export interface MarsAccountNftApprovalsQuery + extends MarsAccountNftReactQuery { args: { includeExpired?: boolean tokenId: string } } -export function useAccountNftApprovalsQuery({ +export function useMarsAccountNftApprovalsQuery({ client, args, options, -}: AccountNftApprovalsQuery) { +}: MarsAccountNftApprovalsQuery) { return useQuery( - accountNftQueryKeys.approvals(client?.contractAddress, args), + marsAccountNftQueryKeys.approvals(client?.contractAddress, args), () => client ? client.approvals({ @@ -261,21 +275,21 @@ export function useAccountNftApprovalsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface AccountNftApprovalQuery - extends AccountNftReactQuery { +export interface MarsAccountNftApprovalQuery + extends MarsAccountNftReactQuery { args: { includeExpired?: boolean spender: string tokenId: string } } -export function useAccountNftApprovalQuery({ +export function useMarsAccountNftApprovalQuery({ client, args, options, -}: AccountNftApprovalQuery) { +}: MarsAccountNftApprovalQuery) { return useQuery( - accountNftQueryKeys.approval(client?.contractAddress, args), + marsAccountNftQueryKeys.approval(client?.contractAddress, args), () => client ? client.approval({ @@ -287,20 +301,20 @@ export function useAccountNftApprovalQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface AccountNftOwnerOfQuery - extends AccountNftReactQuery { +export interface MarsAccountNftOwnerOfQuery + extends MarsAccountNftReactQuery { args: { includeExpired?: boolean tokenId: string } } -export function useAccountNftOwnerOfQuery({ +export function useMarsAccountNftOwnerOfQuery({ client, args, options, -}: AccountNftOwnerOfQuery) { +}: MarsAccountNftOwnerOfQuery) { return useQuery( - accountNftQueryKeys.ownerOf(client?.contractAddress, args), + marsAccountNftQueryKeys.ownerOf(client?.contractAddress, args), () => client ? client.ownerOf({ @@ -311,20 +325,20 @@ export function useAccountNftOwnerOfQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface AccountNftProposedNewOwnerQuery - extends AccountNftReactQuery {} -export function useAccountNftProposedNewOwnerQuery({ +export interface MarsAccountNftProposedNewOwnerQuery + extends MarsAccountNftReactQuery {} +export function useMarsAccountNftProposedNewOwnerQuery({ client, options, -}: AccountNftProposedNewOwnerQuery) { +}: MarsAccountNftProposedNewOwnerQuery) { return useQuery( - accountNftQueryKeys.proposedNewOwner(client?.contractAddress), + marsAccountNftQueryKeys.proposedNewOwner(client?.contractAddress), () => (client ? client.proposedNewOwner() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface AccountNftBurnMutation { - client: AccountNftClient +export interface MarsAccountNftBurnMutation { + client: MarsAccountNftClient msg: { tokenId: string } @@ -334,16 +348,19 @@ export interface AccountNftBurnMutation { funds?: Coin[] } } -export function useAccountNftBurnMutation( - options?: Omit, 'mutationFn'>, +export function useMarsAccountNftBurnMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.burn(msg, fee, memo, funds), options, ) } -export interface AccountNftRevokeAllMutation { - client: AccountNftClient +export interface MarsAccountNftRevokeAllMutation { + client: MarsAccountNftClient msg: { operator: string } @@ -353,19 +370,19 @@ export interface AccountNftRevokeAllMutation { funds?: Coin[] } } -export function useAccountNftRevokeAllMutation( +export function useMarsAccountNftRevokeAllMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.revokeAll(msg, fee, memo, funds), options, ) } -export interface AccountNftApproveAllMutation { - client: AccountNftClient +export interface MarsAccountNftApproveAllMutation { + client: MarsAccountNftClient msg: { expires?: Expiration operator: string @@ -376,19 +393,19 @@ export interface AccountNftApproveAllMutation { funds?: Coin[] } } -export function useAccountNftApproveAllMutation( +export function useMarsAccountNftApproveAllMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.approveAll(msg, fee, memo, funds), options, ) } -export interface AccountNftRevokeMutation { - client: AccountNftClient +export interface MarsAccountNftRevokeMutation { + client: MarsAccountNftClient msg: { spender: string tokenId: string @@ -399,16 +416,19 @@ export interface AccountNftRevokeMutation { funds?: Coin[] } } -export function useAccountNftRevokeMutation( - options?: Omit, 'mutationFn'>, +export function useMarsAccountNftRevokeMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.revoke(msg, fee, memo, funds), options, ) } -export interface AccountNftApproveMutation { - client: AccountNftClient +export interface MarsAccountNftApproveMutation { + client: MarsAccountNftClient msg: { expires?: Expiration spender: string @@ -420,16 +440,19 @@ export interface AccountNftApproveMutation { funds?: Coin[] } } -export function useAccountNftApproveMutation( - options?: Omit, 'mutationFn'>, +export function useMarsAccountNftApproveMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.approve(msg, fee, memo, funds), options, ) } -export interface AccountNftSendNftMutation { - client: AccountNftClient +export interface MarsAccountNftSendNftMutation { + client: MarsAccountNftClient msg: { contract: string msg: Binary @@ -441,16 +464,19 @@ export interface AccountNftSendNftMutation { funds?: Coin[] } } -export function useAccountNftSendNftMutation( - options?: Omit, 'mutationFn'>, +export function useMarsAccountNftSendNftMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.sendNft(msg, fee, memo, funds), options, ) } -export interface AccountNftTransferNftMutation { - client: AccountNftClient +export interface MarsAccountNftTransferNftMutation { + client: MarsAccountNftClient msg: { recipient: string tokenId: string @@ -461,19 +487,19 @@ export interface AccountNftTransferNftMutation { funds?: Coin[] } } -export function useAccountNftTransferNftMutation( +export function useMarsAccountNftTransferNftMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.transferNft(msg, fee, memo, funds), options, ) } -export interface AccountNftMintMutation { - client: AccountNftClient +export interface MarsAccountNftMintMutation { + client: MarsAccountNftClient msg: { user: string } @@ -483,35 +509,38 @@ export interface AccountNftMintMutation { funds?: Coin[] } } -export function useAccountNftMintMutation( - options?: Omit, 'mutationFn'>, +export function useMarsAccountNftMintMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.mint(msg, fee, memo, funds), options, ) } -export interface AccountNftAcceptOwnershipMutation { - client: AccountNftClient +export interface MarsAccountNftAcceptOwnershipMutation { + client: MarsAccountNftClient args?: { fee?: number | StdFee | 'auto' memo?: string funds?: Coin[] } } -export function useAccountNftAcceptOwnershipMutation( +export function useMarsAccountNftAcceptOwnershipMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, args: { fee, memo, funds } = {} }) => client.acceptOwnership(fee, memo, funds), options, ) } -export interface AccountNftProposeNewOwnerMutation { - client: AccountNftClient +export interface MarsAccountNftProposeNewOwnerMutation { + client: MarsAccountNftClient msg: { newOwner: string } @@ -521,13 +550,13 @@ export interface AccountNftProposeNewOwnerMutation { funds?: Coin[] } } -export function useAccountNftProposeNewOwnerMutation( +export function useMarsAccountNftProposeNewOwnerMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.proposeNewOwner(msg, fee, memo, funds), options, diff --git a/scripts/types/generated/account-nft/AccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts similarity index 100% rename from scripts/types/generated/account-nft/AccountNft.types.ts rename to scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts diff --git a/scripts/types/generated/credit-manager/bundle.ts b/scripts/types/generated/mars-account-nft/bundle.ts similarity index 51% rename from scripts/types/generated/credit-manager/bundle.ts rename to scripts/types/generated/mars-account-nft/bundle.ts index c2f710c3f..b6237ebad 100644 --- a/scripts/types/generated/credit-manager/bundle.ts +++ b/scripts/types/generated/mars-account-nft/bundle.ts @@ -5,9 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _3 from './CreditManager.types' -import * as _4 from './CreditManager.client' -import * as _5 from './CreditManager.react-query' +import * as _0 from './MarsAccountNft.types' +import * as _1 from './MarsAccountNft.client' +import * as _2 from './MarsAccountNft.message-composer' +import * as _3 from './MarsAccountNft.react-query' export namespace contracts { - export const CreditManager = { ..._3, ..._4, ..._5 } + export const MarsAccountNft = { ..._0, ..._1, ..._2, ..._3 } } diff --git a/scripts/types/generated/credit-manager/CreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts similarity index 96% rename from scripts/types/generated/credit-manager/CreditManager.client.ts rename to scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index f568d188e..2599bb9a1 100644 --- a/scripts/types/generated/credit-manager/CreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -50,8 +50,8 @@ import { Positions, DebtAmount, ArrayOfVaultInstantiateConfig, -} from './CreditManager.types' -export interface CreditManagerReadOnlyInterface { +} from './MarsCreditManager.types' +export interface MarsCreditManagerReadOnlyInterface { contractAddress: string config: () => Promise vaultConfigs: ({ @@ -116,7 +116,7 @@ export interface CreditManagerReadOnlyInterface { }) => Promise estimateWithdrawLiquidity: ({ lpToken }: { lpToken: Coin }) => Promise } -export class CreditManagerQueryClient implements CreditManagerReadOnlyInterface { +export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyInterface { client: CosmWasmClient contractAddress: string @@ -290,7 +290,7 @@ export class CreditManagerQueryClient implements CreditManagerReadOnlyInterface }) } } -export interface CreditManagerInterface extends CreditManagerReadOnlyInterface { +export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInterface { contractAddress: string sender: string createCreditAccount: ( @@ -326,9 +326,9 @@ export interface CreditManagerInterface extends CreditManagerReadOnlyInterface { funds?: Coin[], ) => Promise } -export class CreditManagerClient - extends CreditManagerQueryClient - implements CreditManagerInterface +export class MarsCreditManagerClient + extends MarsCreditManagerQueryClient + implements MarsCreditManagerInterface { client: SigningCosmWasmClient sender: string diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts new file mode 100644 index 000000000..b628fa4e7 --- /dev/null +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -0,0 +1,173 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + Uint128, + Decimal, + OracleBaseForString, + RedBankBaseForString, + SwapperBaseForString, + ZapperBaseForString, + InstantiateMsg, + VaultInstantiateConfig, + VaultConfig, + Coin, + VaultBaseForString, + ExecuteMsg, + Action, + CallbackMsg, + Addr, + ConfigUpdates, + VaultBaseForAddr, + QueryMsg, + ArrayOfCoinBalanceResponseItem, + CoinBalanceResponseItem, + ArrayOfSharesResponseItem, + SharesResponseItem, + ArrayOfDebtShares, + DebtShares, + ArrayOfVaultWithBalance, + VaultWithBalance, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, + ArrayOfVaultPositionResponseItem, + VaultPositionResponseItem, + VaultPosition, + LockingVaultAmount, + VaultUnlockingPosition, + ArrayOfString, + ConfigResponse, + ArrayOfCoin, + HealthResponse, + Positions, + DebtAmount, + ArrayOfVaultInstantiateConfig, +} from './MarsCreditManager.types' +export interface MarsCreditManagerMessage { + contractAddress: string + sender: string + createCreditAccount: (funds?: Coin[]) => MsgExecuteContractEncodeObject + updateCreditAccount: ( + { + accountId, + actions, + }: { + accountId: string + actions: Action[] + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + updateConfig: ( + { + newConfig, + }: { + newConfig: ConfigUpdates + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + callback: (funds?: Coin[]) => MsgExecuteContractEncodeObject +} +export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.createCreditAccount = this.createCreditAccount.bind(this) + this.updateCreditAccount = this.updateCreditAccount.bind(this) + this.updateConfig = this.updateConfig.bind(this) + this.callback = this.callback.bind(this) + } + + createCreditAccount = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + create_credit_account: {}, + }), + ), + funds, + }), + } + } + updateCreditAccount = ( + { + accountId, + actions, + }: { + accountId: string + actions: Action[] + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_credit_account: { + account_id: accountId, + actions, + }, + }), + ), + funds, + }), + } + } + updateConfig = ( + { + newConfig, + }: { + newConfig: ConfigUpdates + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_config: { + new_config: newConfig, + }, + }), + ), + funds, + }), + } + } + callback = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + callback: {}, + }), + ), + funds, + }), + } + } +} diff --git a/scripts/types/generated/credit-manager/CreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts similarity index 56% rename from scripts/types/generated/credit-manager/CreditManager.react-query.ts rename to scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index dcdf64930..b0f029cef 100644 --- a/scripts/types/generated/credit-manager/CreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -51,46 +51,64 @@ import { Positions, DebtAmount, ArrayOfVaultInstantiateConfig, -} from './CreditManager.types' -import { CreditManagerQueryClient, CreditManagerClient } from './CreditManager.client' -export const creditManagerQueryKeys = { +} from './MarsCreditManager.types' +import { MarsCreditManagerQueryClient, MarsCreditManagerClient } from './MarsCreditManager.client' +export const marsCreditManagerQueryKeys = { contract: [ { - contract: 'creditManager', + contract: 'marsCreditManager', }, ] as const, address: (contractAddress: string | undefined) => - [{ ...creditManagerQueryKeys.contract[0], address: contractAddress }] as const, + [{ ...marsCreditManagerQueryKeys.contract[0], address: contractAddress }] as const, config: (contractAddress: string | undefined, args?: Record) => - [{ ...creditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, + [ + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, + ] as const, vaultConfigs: (contractAddress: string | undefined, args?: Record) => [ - { ...creditManagerQueryKeys.address(contractAddress)[0], method: 'vault_configs', args }, + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'vault_configs', args }, ] as const, allowedCoins: (contractAddress: string | undefined, args?: Record) => [ - { ...creditManagerQueryKeys.address(contractAddress)[0], method: 'allowed_coins', args }, + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'allowed_coins', args }, ] as const, positions: (contractAddress: string | undefined, args?: Record) => - [{ ...creditManagerQueryKeys.address(contractAddress)[0], method: 'positions', args }] as const, + [ + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'positions', args }, + ] as const, health: (contractAddress: string | undefined, args?: Record) => - [{ ...creditManagerQueryKeys.address(contractAddress)[0], method: 'health', args }] as const, + [ + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'health', args }, + ] as const, allCoinBalances: (contractAddress: string | undefined, args?: Record) => [ - { ...creditManagerQueryKeys.address(contractAddress)[0], method: 'all_coin_balances', args }, + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'all_coin_balances', + args, + }, ] as const, allDebtShares: (contractAddress: string | undefined, args?: Record) => [ - { ...creditManagerQueryKeys.address(contractAddress)[0], method: 'all_debt_shares', args }, + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'all_debt_shares', + args, + }, ] as const, totalDebtShares: (contractAddress: string | undefined, args?: Record) => [ - { ...creditManagerQueryKeys.address(contractAddress)[0], method: 'total_debt_shares', args }, + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'total_debt_shares', + args, + }, ] as const, allTotalDebtShares: (contractAddress: string | undefined, args?: Record) => [ { - ...creditManagerQueryKeys.address(contractAddress)[0], + ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'all_total_debt_shares', args, }, @@ -98,7 +116,7 @@ export const creditManagerQueryKeys = { allVaultPositions: (contractAddress: string | undefined, args?: Record) => [ { - ...creditManagerQueryKeys.address(contractAddress)[0], + ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'all_vault_positions', args, }, @@ -106,7 +124,7 @@ export const creditManagerQueryKeys = { totalVaultCoinBalance: (contractAddress: string | undefined, args?: Record) => [ { - ...creditManagerQueryKeys.address(contractAddress)[0], + ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'total_vault_coin_balance', args, }, @@ -117,7 +135,7 @@ export const creditManagerQueryKeys = { ) => [ { - ...creditManagerQueryKeys.address(contractAddress)[0], + ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'all_total_vault_coin_balances', args, }, @@ -125,7 +143,7 @@ export const creditManagerQueryKeys = { estimateProvideLiquidity: (contractAddress: string | undefined, args?: Record) => [ { - ...creditManagerQueryKeys.address(contractAddress)[0], + ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'estimate_provide_liquidity', args, }, @@ -136,14 +154,14 @@ export const creditManagerQueryKeys = { ) => [ { - ...creditManagerQueryKeys.address(contractAddress)[0], + ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'estimate_withdraw_liquidity', args, }, ] as const, } -export interface CreditManagerReactQuery { - client: CreditManagerQueryClient | undefined +export interface MarsCreditManagerReactQuery { + client: MarsCreditManagerQueryClient | undefined options?: Omit< UseQueryOptions, "'queryKey' | 'queryFn' | 'initialData'" @@ -151,19 +169,19 @@ export interface CreditManagerReactQuery { initialData?: undefined } } -export interface CreditManagerEstimateWithdrawLiquidityQuery - extends CreditManagerReactQuery { +export interface MarsCreditManagerEstimateWithdrawLiquidityQuery + extends MarsCreditManagerReactQuery { args: { lpToken: Coin } } -export function useCreditManagerEstimateWithdrawLiquidityQuery({ +export function useMarsCreditManagerEstimateWithdrawLiquidityQuery({ client, args, options, -}: CreditManagerEstimateWithdrawLiquidityQuery) { +}: MarsCreditManagerEstimateWithdrawLiquidityQuery) { return useQuery( - creditManagerQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), + marsCreditManagerQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), () => client ? client.estimateWithdrawLiquidity({ @@ -173,20 +191,20 @@ export function useCreditManagerEstimateWithdrawLiquidityQuery - extends CreditManagerReactQuery { +export interface MarsCreditManagerEstimateProvideLiquidityQuery + extends MarsCreditManagerReactQuery { args: { coinsIn: Coin[] lpTokenOut: string } } -export function useCreditManagerEstimateProvideLiquidityQuery({ +export function useMarsCreditManagerEstimateProvideLiquidityQuery({ client, args, options, -}: CreditManagerEstimateProvideLiquidityQuery) { +}: MarsCreditManagerEstimateProvideLiquidityQuery) { return useQuery( - creditManagerQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), + marsCreditManagerQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), () => client ? client.estimateProvideLiquidity({ @@ -197,20 +215,18 @@ export function useCreditManagerEstimateProvideLiquidityQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface CreditManagerAllTotalVaultCoinBalancesQuery - extends CreditManagerReactQuery { +export interface MarsCreditManagerAllTotalVaultCoinBalancesQuery + extends MarsCreditManagerReactQuery { args: { limit?: number startAfter?: VaultBaseForString } } -export function useCreditManagerAllTotalVaultCoinBalancesQuery({ - client, - args, - options, -}: CreditManagerAllTotalVaultCoinBalancesQuery) { +export function useMarsCreditManagerAllTotalVaultCoinBalancesQuery< + TData = ArrayOfVaultWithBalance, +>({ client, args, options }: MarsCreditManagerAllTotalVaultCoinBalancesQuery) { return useQuery( - creditManagerQueryKeys.allTotalVaultCoinBalances(client?.contractAddress, args), + marsCreditManagerQueryKeys.allTotalVaultCoinBalances(client?.contractAddress, args), () => client ? client.allTotalVaultCoinBalances({ @@ -221,19 +237,19 @@ export function useCreditManagerAllTotalVaultCoinBalancesQuery - extends CreditManagerReactQuery { +export interface MarsCreditManagerTotalVaultCoinBalanceQuery + extends MarsCreditManagerReactQuery { args: { vault: VaultBaseForString } } -export function useCreditManagerTotalVaultCoinBalanceQuery({ +export function useMarsCreditManagerTotalVaultCoinBalanceQuery({ client, args, options, -}: CreditManagerTotalVaultCoinBalanceQuery) { +}: MarsCreditManagerTotalVaultCoinBalanceQuery) { return useQuery( - creditManagerQueryKeys.totalVaultCoinBalance(client?.contractAddress, args), + marsCreditManagerQueryKeys.totalVaultCoinBalance(client?.contractAddress, args), () => client ? client.totalVaultCoinBalance({ @@ -243,20 +259,18 @@ export function useCreditManagerTotalVaultCoinBalanceQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface CreditManagerAllVaultPositionsQuery - extends CreditManagerReactQuery { +export interface MarsCreditManagerAllVaultPositionsQuery + extends MarsCreditManagerReactQuery { args: { limit?: number startAfter?: string[][] } } -export function useCreditManagerAllVaultPositionsQuery({ - client, - args, - options, -}: CreditManagerAllVaultPositionsQuery) { +export function useMarsCreditManagerAllVaultPositionsQuery< + TData = ArrayOfVaultPositionResponseItem, +>({ client, args, options }: MarsCreditManagerAllVaultPositionsQuery) { return useQuery( - creditManagerQueryKeys.allVaultPositions(client?.contractAddress, args), + marsCreditManagerQueryKeys.allVaultPositions(client?.contractAddress, args), () => client ? client.allVaultPositions({ @@ -267,20 +281,20 @@ export function useCreditManagerAllVaultPositionsQuery - extends CreditManagerReactQuery { +export interface MarsCreditManagerAllTotalDebtSharesQuery + extends MarsCreditManagerReactQuery { args: { limit?: number startAfter?: string } } -export function useCreditManagerAllTotalDebtSharesQuery({ +export function useMarsCreditManagerAllTotalDebtSharesQuery({ client, args, options, -}: CreditManagerAllTotalDebtSharesQuery) { +}: MarsCreditManagerAllTotalDebtSharesQuery) { return useQuery( - creditManagerQueryKeys.allTotalDebtShares(client?.contractAddress, args), + marsCreditManagerQueryKeys.allTotalDebtShares(client?.contractAddress, args), () => client ? client.allTotalDebtShares({ @@ -291,32 +305,32 @@ export function useCreditManagerAllTotalDebtSharesQuery - extends CreditManagerReactQuery {} -export function useCreditManagerTotalDebtSharesQuery({ +export interface MarsCreditManagerTotalDebtSharesQuery + extends MarsCreditManagerReactQuery {} +export function useMarsCreditManagerTotalDebtSharesQuery({ client, options, -}: CreditManagerTotalDebtSharesQuery) { +}: MarsCreditManagerTotalDebtSharesQuery) { return useQuery( - creditManagerQueryKeys.totalDebtShares(client?.contractAddress), + marsCreditManagerQueryKeys.totalDebtShares(client?.contractAddress), () => (client ? client.totalDebtShares() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface CreditManagerAllDebtSharesQuery - extends CreditManagerReactQuery { +export interface MarsCreditManagerAllDebtSharesQuery + extends MarsCreditManagerReactQuery { args: { limit?: number startAfter?: string[][] } } -export function useCreditManagerAllDebtSharesQuery({ +export function useMarsCreditManagerAllDebtSharesQuery({ client, args, options, -}: CreditManagerAllDebtSharesQuery) { +}: MarsCreditManagerAllDebtSharesQuery) { return useQuery( - creditManagerQueryKeys.allDebtShares(client?.contractAddress, args), + marsCreditManagerQueryKeys.allDebtShares(client?.contractAddress, args), () => client ? client.allDebtShares({ @@ -327,20 +341,20 @@ export function useCreditManagerAllDebtSharesQuery - extends CreditManagerReactQuery { +export interface MarsCreditManagerAllCoinBalancesQuery + extends MarsCreditManagerReactQuery { args: { limit?: number startAfter?: string[][] } } -export function useCreditManagerAllCoinBalancesQuery({ +export function useMarsCreditManagerAllCoinBalancesQuery({ client, args, options, -}: CreditManagerAllCoinBalancesQuery) { +}: MarsCreditManagerAllCoinBalancesQuery) { return useQuery( - creditManagerQueryKeys.allCoinBalances(client?.contractAddress, args), + marsCreditManagerQueryKeys.allCoinBalances(client?.contractAddress, args), () => client ? client.allCoinBalances({ @@ -351,19 +365,19 @@ export function useCreditManagerAllCoinBalancesQuery - extends CreditManagerReactQuery { +export interface MarsCreditManagerHealthQuery + extends MarsCreditManagerReactQuery { args: { accountId: string } } -export function useCreditManagerHealthQuery({ +export function useMarsCreditManagerHealthQuery({ client, args, options, -}: CreditManagerHealthQuery) { +}: MarsCreditManagerHealthQuery) { return useQuery( - creditManagerQueryKeys.health(client?.contractAddress, args), + marsCreditManagerQueryKeys.health(client?.contractAddress, args), () => client ? client.health({ @@ -373,19 +387,19 @@ export function useCreditManagerHealthQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface CreditManagerPositionsQuery - extends CreditManagerReactQuery { +export interface MarsCreditManagerPositionsQuery + extends MarsCreditManagerReactQuery { args: { accountId: string } } -export function useCreditManagerPositionsQuery({ +export function useMarsCreditManagerPositionsQuery({ client, args, options, -}: CreditManagerPositionsQuery) { +}: MarsCreditManagerPositionsQuery) { return useQuery( - creditManagerQueryKeys.positions(client?.contractAddress, args), + marsCreditManagerQueryKeys.positions(client?.contractAddress, args), () => client ? client.positions({ @@ -395,20 +409,20 @@ export function useCreditManagerPositionsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface CreditManagerAllowedCoinsQuery - extends CreditManagerReactQuery { +export interface MarsCreditManagerAllowedCoinsQuery + extends MarsCreditManagerReactQuery { args: { limit?: number startAfter?: string } } -export function useCreditManagerAllowedCoinsQuery({ +export function useMarsCreditManagerAllowedCoinsQuery({ client, args, options, -}: CreditManagerAllowedCoinsQuery) { +}: MarsCreditManagerAllowedCoinsQuery) { return useQuery( - creditManagerQueryKeys.allowedCoins(client?.contractAddress, args), + marsCreditManagerQueryKeys.allowedCoins(client?.contractAddress, args), () => client ? client.allowedCoins({ @@ -419,20 +433,20 @@ export function useCreditManagerAllowedCoinsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface CreditManagerVaultConfigsQuery - extends CreditManagerReactQuery { +export interface MarsCreditManagerVaultConfigsQuery + extends MarsCreditManagerReactQuery { args: { limit?: number startAfter?: VaultBaseForString } } -export function useCreditManagerVaultConfigsQuery({ +export function useMarsCreditManagerVaultConfigsQuery({ client, args, options, -}: CreditManagerVaultConfigsQuery) { +}: MarsCreditManagerVaultConfigsQuery) { return useQuery( - creditManagerQueryKeys.vaultConfigs(client?.contractAddress, args), + marsCreditManagerQueryKeys.vaultConfigs(client?.contractAddress, args), () => client ? client.vaultConfigs({ @@ -443,20 +457,20 @@ export function useCreditManagerVaultConfigsQuery - extends CreditManagerReactQuery {} -export function useCreditManagerConfigQuery({ +export interface MarsCreditManagerConfigQuery + extends MarsCreditManagerReactQuery {} +export function useMarsCreditManagerConfigQuery({ client, options, -}: CreditManagerConfigQuery) { +}: MarsCreditManagerConfigQuery) { return useQuery( - creditManagerQueryKeys.config(client?.contractAddress), + marsCreditManagerQueryKeys.config(client?.contractAddress), () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface CreditManagerCallbackMutation { - client: CreditManagerClient +export interface MarsCreditManagerCallbackMutation { + client: MarsCreditManagerClient msg: CallbackMsg args?: { fee?: number | StdFee | 'auto' @@ -464,19 +478,19 @@ export interface CreditManagerCallbackMutation { funds?: Coin[] } } -export function useCreditManagerCallbackMutation( +export function useMarsCreditManagerCallbackMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.callback(msg, fee, memo, funds), options, ) } -export interface CreditManagerUpdateConfigMutation { - client: CreditManagerClient +export interface MarsCreditManagerUpdateConfigMutation { + client: MarsCreditManagerClient msg: { newConfig: ConfigUpdates } @@ -486,20 +500,20 @@ export interface CreditManagerUpdateConfigMutation { funds?: Coin[] } } -export function useCreditManagerUpdateConfigMutation( +export function useMarsCreditManagerUpdateConfigMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateConfig(msg, fee, memo, funds), options, ) } -export interface CreditManagerUpdateCreditAccountMutation { - client: CreditManagerClient +export interface MarsCreditManagerUpdateCreditAccountMutation { + client: MarsCreditManagerClient msg: { accountId: string actions: Action[] @@ -510,33 +524,33 @@ export interface CreditManagerUpdateCreditAccountMutation { funds?: Coin[] } } -export function useCreditManagerUpdateCreditAccountMutation( +export function useMarsCreditManagerUpdateCreditAccountMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateCreditAccount(msg, fee, memo, funds), options, ) } -export interface CreditManagerCreateCreditAccountMutation { - client: CreditManagerClient +export interface MarsCreditManagerCreateCreditAccountMutation { + client: MarsCreditManagerClient args?: { fee?: number | StdFee | 'auto' memo?: string funds?: Coin[] } } -export function useCreditManagerCreateCreditAccountMutation( +export function useMarsCreditManagerCreateCreditAccountMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, args: { fee, memo, funds } = {} }) => client.createCreditAccount(fee, memo, funds), options, ) diff --git a/scripts/types/generated/credit-manager/CreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts similarity index 97% rename from scripts/types/generated/credit-manager/CreditManager.types.ts rename to scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index c271ba41f..5cefad83f 100644 --- a/scripts/types/generated/credit-manager/CreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -129,6 +129,9 @@ export type Action = lp_token: Coin } } + | { + refund_all_coin_balances: {} + } export type CallbackMsg = | { withdraw: { @@ -241,6 +244,16 @@ export type CallbackMsg = lp_token: Coin } } + | { + assert_one_vault_position_only: { + account_id: string + } + } + | { + refund_all_coin_balances: { + account_id: string + } + } export type Addr = string export interface ConfigUpdates { account_nft?: string | null diff --git a/scripts/types/generated/mars-credit-manager/bundle.ts b/scripts/types/generated/mars-credit-manager/bundle.ts new file mode 100644 index 000000000..5878326c1 --- /dev/null +++ b/scripts/types/generated/mars-credit-manager/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _4 from './MarsCreditManager.types' +import * as _5 from './MarsCreditManager.client' +import * as _6 from './MarsCreditManager.message-composer' +import * as _7 from './MarsCreditManager.react-query' +export namespace contracts { + export const MarsCreditManager = { ..._4, ..._5, ..._6, ..._7 } +} diff --git a/scripts/types/generated/mock-oracle/MockOracle.client.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts similarity index 85% rename from scripts/types/generated/mock-oracle/MockOracle.client.ts rename to scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts index 57e102e6c..2a4748b54 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.client.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts @@ -14,12 +14,12 @@ import { ExecuteMsg, QueryMsg, PriceResponse, -} from './MockOracle.types' -export interface MockOracleReadOnlyInterface { +} from './MarsMockOracle.types' +export interface MarsMockOracleReadOnlyInterface { contractAddress: string price: ({ denom }: { denom: string }) => Promise } -export class MockOracleQueryClient implements MockOracleReadOnlyInterface { +export class MarsMockOracleQueryClient implements MarsMockOracleReadOnlyInterface { client: CosmWasmClient contractAddress: string @@ -37,7 +37,7 @@ export class MockOracleQueryClient implements MockOracleReadOnlyInterface { }) } } -export interface MockOracleInterface extends MockOracleReadOnlyInterface { +export interface MarsMockOracleInterface extends MarsMockOracleReadOnlyInterface { contractAddress: string sender: string changePrice: ( @@ -53,7 +53,10 @@ export interface MockOracleInterface extends MockOracleReadOnlyInterface { funds?: Coin[], ) => Promise } -export class MockOracleClient extends MockOracleQueryClient implements MockOracleInterface { +export class MarsMockOracleClient + extends MarsMockOracleQueryClient + implements MarsMockOracleInterface +{ client: SigningCosmWasmClient sender: string contractAddress: string diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts new file mode 100644 index 000000000..6db180841 --- /dev/null +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts @@ -0,0 +1,71 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { Coin } from '@cosmjs/amino' +import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + Decimal, + InstantiateMsg, + CoinPrice, + ExecuteMsg, + QueryMsg, + PriceResponse, +} from './MarsMockOracle.types' +export interface MarsMockOracleMessage { + contractAddress: string + sender: string + changePrice: ( + { + denom, + price, + }: { + denom: string + price: Decimal + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject +} +export class MarsMockOracleMessageComposer implements MarsMockOracleMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.changePrice = this.changePrice.bind(this) + } + + changePrice = ( + { + denom, + price, + }: { + denom: string + price: Decimal + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + change_price: { + denom, + price, + }, + }), + ), + funds, + }), + } + } +} diff --git a/scripts/types/generated/mock-oracle/MockOracle.react-query.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts similarity index 58% rename from scripts/types/generated/mock-oracle/MockOracle.react-query.ts rename to scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts index 125acbdb6..a255e74ad 100644 --- a/scripts/types/generated/mock-oracle/MockOracle.react-query.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts @@ -15,21 +15,21 @@ import { ExecuteMsg, QueryMsg, PriceResponse, -} from './MockOracle.types' -import { MockOracleQueryClient, MockOracleClient } from './MockOracle.client' -export const mockOracleQueryKeys = { +} from './MarsMockOracle.types' +import { MarsMockOracleQueryClient, MarsMockOracleClient } from './MarsMockOracle.client' +export const marsMockOracleQueryKeys = { contract: [ { - contract: 'mockOracle', + contract: 'marsMockOracle', }, ] as const, address: (contractAddress: string | undefined) => - [{ ...mockOracleQueryKeys.contract[0], address: contractAddress }] as const, + [{ ...marsMockOracleQueryKeys.contract[0], address: contractAddress }] as const, price: (contractAddress: string | undefined, args?: Record) => - [{ ...mockOracleQueryKeys.address(contractAddress)[0], method: 'price', args }] as const, + [{ ...marsMockOracleQueryKeys.address(contractAddress)[0], method: 'price', args }] as const, } -export interface MockOracleReactQuery { - client: MockOracleQueryClient | undefined +export interface MarsMockOracleReactQuery { + client: MarsMockOracleQueryClient | undefined options?: Omit< UseQueryOptions, "'queryKey' | 'queryFn' | 'initialData'" @@ -37,18 +37,19 @@ export interface MockOracleReactQuery { initialData?: undefined } } -export interface MockOraclePriceQuery extends MockOracleReactQuery { +export interface MarsMockOraclePriceQuery + extends MarsMockOracleReactQuery { args: { denom: string } } -export function useMockOraclePriceQuery({ +export function useMarsMockOraclePriceQuery({ client, args, options, -}: MockOraclePriceQuery) { +}: MarsMockOraclePriceQuery) { return useQuery( - mockOracleQueryKeys.price(client?.contractAddress, args), + marsMockOracleQueryKeys.price(client?.contractAddress, args), () => client ? client.price({ @@ -58,8 +59,8 @@ export function useMockOraclePriceQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockOracleChangePriceMutation { - client: MockOracleClient +export interface MarsMockOracleChangePriceMutation { + client: MarsMockOracleClient msg: CoinPrice args?: { fee?: number | StdFee | 'auto' @@ -67,13 +68,13 @@ export interface MockOracleChangePriceMutation { funds?: Coin[] } } -export function useMockOracleChangePriceMutation( +export function useMarsMockOracleChangePriceMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.changePrice(msg, fee, memo, funds), options, ) diff --git a/scripts/types/generated/mock-oracle/MockOracle.types.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts similarity index 100% rename from scripts/types/generated/mock-oracle/MockOracle.types.ts rename to scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts diff --git a/scripts/types/generated/account-nft/bundle.ts b/scripts/types/generated/mars-mock-oracle/bundle.ts similarity index 51% rename from scripts/types/generated/account-nft/bundle.ts rename to scripts/types/generated/mars-mock-oracle/bundle.ts index df166d953..9077d952b 100644 --- a/scripts/types/generated/account-nft/bundle.ts +++ b/scripts/types/generated/mars-mock-oracle/bundle.ts @@ -5,9 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _0 from './AccountNft.types' -import * as _1 from './AccountNft.client' -import * as _2 from './AccountNft.react-query' +import * as _8 from './MarsMockOracle.types' +import * as _9 from './MarsMockOracle.client' +import * as _10 from './MarsMockOracle.message-composer' +import * as _11 from './MarsMockOracle.react-query' export namespace contracts { - export const AccountNft = { ..._0, ..._1, ..._2 } + export const MarsMockOracle = { ..._8, ..._9, ..._10, ..._11 } } diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts similarity index 97% rename from scripts/types/generated/mock-red-bank/MockRedBank.client.ts rename to scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index 344fa75c5..2eda55c06 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -28,8 +28,8 @@ import { ArrayOfUserDebtResponse, UserHealthStatus, UserPositionResponse, -} from './MockRedBank.types' -export interface MockRedBankReadOnlyInterface { +} from './MarsMockRedBank.types' +export interface MarsMockRedBankReadOnlyInterface { contractAddress: string config: () => Promise market: ({ denom }: { denom: string }) => Promise @@ -100,7 +100,7 @@ export interface MockRedBankReadOnlyInterface { denom: string }) => Promise } -export class MockRedBankQueryClient implements MockRedBankReadOnlyInterface { +export class MarsMockRedBankQueryClient implements MarsMockRedBankReadOnlyInterface { client: CosmWasmClient contractAddress: string @@ -306,7 +306,7 @@ export class MockRedBankQueryClient implements MockRedBankReadOnlyInterface { }) } } -export interface MockRedBankInterface extends MockRedBankReadOnlyInterface { +export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterface { contractAddress: string sender: string updateConfig: ( @@ -432,7 +432,10 @@ export interface MockRedBankInterface extends MockRedBankReadOnlyInterface { funds?: Coin[], ) => Promise } -export class MockRedBankClient extends MockRedBankQueryClient implements MockRedBankInterface { +export class MarsMockRedBankClient + extends MarsMockRedBankQueryClient + implements MarsMockRedBankInterface +{ client: SigningCosmWasmClient sender: string contractAddress: string diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts new file mode 100644 index 000000000..5f75dbe11 --- /dev/null +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts @@ -0,0 +1,432 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { Coin } from '@cosmjs/amino' +import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + Decimal, + InstantiateMsg, + CoinMarketInfo, + ExecuteMsg, + Uint128, + CreateOrUpdateConfig, + InitOrUpdateAssetParams, + InterestRateModel, + QueryMsg, + ConfigForString, + Market, + ArrayOfMarket, + UncollateralizedLoanLimitResponse, + ArrayOfUncollateralizedLoanLimitResponse, + UserCollateralResponse, + ArrayOfUserCollateralResponse, + UserDebtResponse, + ArrayOfUserDebtResponse, + UserHealthStatus, + UserPositionResponse, +} from './MarsMockRedBank.types' +export interface MarsMockRedBankMessage { + contractAddress: string + sender: string + updateConfig: ( + { + config, + }: { + config: CreateOrUpdateConfig + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + initAsset: ( + { + denom, + params, + }: { + denom: string + params: InitOrUpdateAssetParams + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + updateAsset: ( + { + denom, + params, + }: { + denom: string + params: InitOrUpdateAssetParams + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + updateUncollateralizedLoanLimit: ( + { + denom, + newLimit, + user, + }: { + denom: string + newLimit: Uint128 + user: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + deposit: ( + { + onBehalfOf, + }: { + onBehalfOf?: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + withdraw: ( + { + amount, + denom, + recipient, + }: { + amount?: Uint128 + denom: string + recipient?: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + borrow: ( + { + amount, + denom, + recipient, + }: { + amount: Uint128 + denom: string + recipient?: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + repay: ( + { + onBehalfOf, + }: { + onBehalfOf?: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + liquidate: ( + { + collateralDenom, + recipient, + user, + }: { + collateralDenom: string + recipient?: string + user: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + updateAssetCollateralStatus: ( + { + denom, + enable, + }: { + denom: string + enable: boolean + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject +} +export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.updateConfig = this.updateConfig.bind(this) + this.initAsset = this.initAsset.bind(this) + this.updateAsset = this.updateAsset.bind(this) + this.updateUncollateralizedLoanLimit = this.updateUncollateralizedLoanLimit.bind(this) + this.deposit = this.deposit.bind(this) + this.withdraw = this.withdraw.bind(this) + this.borrow = this.borrow.bind(this) + this.repay = this.repay.bind(this) + this.liquidate = this.liquidate.bind(this) + this.updateAssetCollateralStatus = this.updateAssetCollateralStatus.bind(this) + } + + updateConfig = ( + { + config, + }: { + config: CreateOrUpdateConfig + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_config: { + config, + }, + }), + ), + funds, + }), + } + } + initAsset = ( + { + denom, + params, + }: { + denom: string + params: InitOrUpdateAssetParams + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + init_asset: { + denom, + params, + }, + }), + ), + funds, + }), + } + } + updateAsset = ( + { + denom, + params, + }: { + denom: string + params: InitOrUpdateAssetParams + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_asset: { + denom, + params, + }, + }), + ), + funds, + }), + } + } + updateUncollateralizedLoanLimit = ( + { + denom, + newLimit, + user, + }: { + denom: string + newLimit: Uint128 + user: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_uncollateralized_loan_limit: { + denom, + new_limit: newLimit, + user, + }, + }), + ), + funds, + }), + } + } + deposit = ( + { + onBehalfOf, + }: { + onBehalfOf?: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + deposit: { + on_behalf_of: onBehalfOf, + }, + }), + ), + funds, + }), + } + } + withdraw = ( + { + amount, + denom, + recipient, + }: { + amount?: Uint128 + denom: string + recipient?: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + withdraw: { + amount, + denom, + recipient, + }, + }), + ), + funds, + }), + } + } + borrow = ( + { + amount, + denom, + recipient, + }: { + amount: Uint128 + denom: string + recipient?: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + borrow: { + amount, + denom, + recipient, + }, + }), + ), + funds, + }), + } + } + repay = ( + { + onBehalfOf, + }: { + onBehalfOf?: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + repay: { + on_behalf_of: onBehalfOf, + }, + }), + ), + funds, + }), + } + } + liquidate = ( + { + collateralDenom, + recipient, + user, + }: { + collateralDenom: string + recipient?: string + user: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + liquidate: { + collateral_denom: collateralDenom, + recipient, + user, + }, + }), + ), + funds, + }), + } + } + updateAssetCollateralStatus = ( + { + denom, + enable, + }: { + denom: string + enable: boolean + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_asset_collateral_status: { + denom, + enable, + }, + }), + ), + funds, + }), + } + } +} diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts similarity index 56% rename from scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts rename to scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index d0b16d767..55f99f412 100644 --- a/scripts/types/generated/mock-red-bank/MockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -29,29 +29,29 @@ import { ArrayOfUserDebtResponse, UserHealthStatus, UserPositionResponse, -} from './MockRedBank.types' -import { MockRedBankQueryClient, MockRedBankClient } from './MockRedBank.client' -export const mockRedBankQueryKeys = { +} from './MarsMockRedBank.types' +import { MarsMockRedBankQueryClient, MarsMockRedBankClient } from './MarsMockRedBank.client' +export const marsMockRedBankQueryKeys = { contract: [ { - contract: 'mockRedBank', + contract: 'marsMockRedBank', }, ] as const, address: (contractAddress: string | undefined) => - [{ ...mockRedBankQueryKeys.contract[0], address: contractAddress }] as const, + [{ ...marsMockRedBankQueryKeys.contract[0], address: contractAddress }] as const, config: (contractAddress: string | undefined, args?: Record) => - [{ ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, + [{ ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, market: (contractAddress: string | undefined, args?: Record) => - [{ ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'market', args }] as const, + [{ ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'market', args }] as const, markets: (contractAddress: string | undefined, args?: Record) => - [{ ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'markets', args }] as const, + [{ ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'markets', args }] as const, uncollateralizedLoanLimit: ( contractAddress: string | undefined, args?: Record, ) => [ { - ...mockRedBankQueryKeys.address(contractAddress)[0], + ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'uncollateralized_loan_limit', args, }, @@ -62,38 +62,46 @@ export const mockRedBankQueryKeys = { ) => [ { - ...mockRedBankQueryKeys.address(contractAddress)[0], + ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'uncollateralized_loan_limits', args, }, ] as const, userDebt: (contractAddress: string | undefined, args?: Record) => - [{ ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'user_debt', args }] as const, + [ + { ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'user_debt', args }, + ] as const, userDebts: (contractAddress: string | undefined, args?: Record) => - [{ ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'user_debts', args }] as const, + [ + { ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'user_debts', args }, + ] as const, userCollateral: (contractAddress: string | undefined, args?: Record) => [ - { ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'user_collateral', args }, + { ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'user_collateral', args }, ] as const, userCollaterals: (contractAddress: string | undefined, args?: Record) => [ - { ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'user_collaterals', args }, + { ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'user_collaterals', args }, ] as const, userPosition: (contractAddress: string | undefined, args?: Record) => [ - { ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'user_position', args }, + { ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'user_position', args }, ] as const, scaledLiquidityAmount: (contractAddress: string | undefined, args?: Record) => [ { - ...mockRedBankQueryKeys.address(contractAddress)[0], + ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'scaled_liquidity_amount', args, }, ] as const, scaledDebtAmount: (contractAddress: string | undefined, args?: Record) => [ - { ...mockRedBankQueryKeys.address(contractAddress)[0], method: 'scaled_debt_amount', args }, + { + ...marsMockRedBankQueryKeys.address(contractAddress)[0], + method: 'scaled_debt_amount', + args, + }, ] as const, underlyingLiquidityAmount: ( contractAddress: string | undefined, @@ -101,7 +109,7 @@ export const mockRedBankQueryKeys = { ) => [ { - ...mockRedBankQueryKeys.address(contractAddress)[0], + ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'underlying_liquidity_amount', args, }, @@ -109,14 +117,14 @@ export const mockRedBankQueryKeys = { underlyingDebtAmount: (contractAddress: string | undefined, args?: Record) => [ { - ...mockRedBankQueryKeys.address(contractAddress)[0], + ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'underlying_debt_amount', args, }, ] as const, } -export interface MockRedBankReactQuery { - client: MockRedBankQueryClient | undefined +export interface MarsMockRedBankReactQuery { + client: MarsMockRedBankQueryClient | undefined options?: Omit< UseQueryOptions, "'queryKey' | 'queryFn' | 'initialData'" @@ -124,20 +132,20 @@ export interface MockRedBankReactQuery { initialData?: undefined } } -export interface MockRedBankUnderlyingDebtAmountQuery - extends MockRedBankReactQuery { +export interface MarsMockRedBankUnderlyingDebtAmountQuery + extends MarsMockRedBankReactQuery { args: { amountScaled: Uint128 denom: string } } -export function useMockRedBankUnderlyingDebtAmountQuery({ +export function useMarsMockRedBankUnderlyingDebtAmountQuery({ client, args, options, -}: MockRedBankUnderlyingDebtAmountQuery) { +}: MarsMockRedBankUnderlyingDebtAmountQuery) { return useQuery( - mockRedBankQueryKeys.underlyingDebtAmount(client?.contractAddress, args), + marsMockRedBankQueryKeys.underlyingDebtAmount(client?.contractAddress, args), () => client ? client.underlyingDebtAmount({ @@ -148,20 +156,20 @@ export function useMockRedBankUnderlyingDebtAmountQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankUnderlyingLiquidityAmountQuery - extends MockRedBankReactQuery { +export interface MarsMockRedBankUnderlyingLiquidityAmountQuery + extends MarsMockRedBankReactQuery { args: { amountScaled: Uint128 denom: string } } -export function useMockRedBankUnderlyingLiquidityAmountQuery({ +export function useMarsMockRedBankUnderlyingLiquidityAmountQuery({ client, args, options, -}: MockRedBankUnderlyingLiquidityAmountQuery) { +}: MarsMockRedBankUnderlyingLiquidityAmountQuery) { return useQuery( - mockRedBankQueryKeys.underlyingLiquidityAmount(client?.contractAddress, args), + marsMockRedBankQueryKeys.underlyingLiquidityAmount(client?.contractAddress, args), () => client ? client.underlyingLiquidityAmount({ @@ -172,20 +180,20 @@ export function useMockRedBankUnderlyingLiquidityAmountQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankScaledDebtAmountQuery - extends MockRedBankReactQuery { +export interface MarsMockRedBankScaledDebtAmountQuery + extends MarsMockRedBankReactQuery { args: { amount: Uint128 denom: string } } -export function useMockRedBankScaledDebtAmountQuery({ +export function useMarsMockRedBankScaledDebtAmountQuery({ client, args, options, -}: MockRedBankScaledDebtAmountQuery) { +}: MarsMockRedBankScaledDebtAmountQuery) { return useQuery( - mockRedBankQueryKeys.scaledDebtAmount(client?.contractAddress, args), + marsMockRedBankQueryKeys.scaledDebtAmount(client?.contractAddress, args), () => client ? client.scaledDebtAmount({ @@ -196,20 +204,20 @@ export function useMockRedBankScaledDebtAmountQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankScaledLiquidityAmountQuery - extends MockRedBankReactQuery { +export interface MarsMockRedBankScaledLiquidityAmountQuery + extends MarsMockRedBankReactQuery { args: { amount: Uint128 denom: string } } -export function useMockRedBankScaledLiquidityAmountQuery({ +export function useMarsMockRedBankScaledLiquidityAmountQuery({ client, args, options, -}: MockRedBankScaledLiquidityAmountQuery) { +}: MarsMockRedBankScaledLiquidityAmountQuery) { return useQuery( - mockRedBankQueryKeys.scaledLiquidityAmount(client?.contractAddress, args), + marsMockRedBankQueryKeys.scaledLiquidityAmount(client?.contractAddress, args), () => client ? client.scaledLiquidityAmount({ @@ -220,19 +228,19 @@ export function useMockRedBankScaledLiquidityAmountQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankUserPositionQuery - extends MockRedBankReactQuery { +export interface MarsMockRedBankUserPositionQuery + extends MarsMockRedBankReactQuery { args: { user: string } } -export function useMockRedBankUserPositionQuery({ +export function useMarsMockRedBankUserPositionQuery({ client, args, options, -}: MockRedBankUserPositionQuery) { +}: MarsMockRedBankUserPositionQuery) { return useQuery( - mockRedBankQueryKeys.userPosition(client?.contractAddress, args), + marsMockRedBankQueryKeys.userPosition(client?.contractAddress, args), () => client ? client.userPosition({ @@ -242,21 +250,21 @@ export function useMockRedBankUserPositionQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankUserCollateralsQuery - extends MockRedBankReactQuery { +export interface MarsMockRedBankUserCollateralsQuery + extends MarsMockRedBankReactQuery { args: { limit?: number startAfter?: string user: string } } -export function useMockRedBankUserCollateralsQuery({ +export function useMarsMockRedBankUserCollateralsQuery({ client, args, options, -}: MockRedBankUserCollateralsQuery) { +}: MarsMockRedBankUserCollateralsQuery) { return useQuery( - mockRedBankQueryKeys.userCollaterals(client?.contractAddress, args), + marsMockRedBankQueryKeys.userCollaterals(client?.contractAddress, args), () => client ? client.userCollaterals({ @@ -268,20 +276,20 @@ export function useMockRedBankUserCollateralsQuery - extends MockRedBankReactQuery { +export interface MarsMockRedBankUserCollateralQuery + extends MarsMockRedBankReactQuery { args: { denom: string user: string } } -export function useMockRedBankUserCollateralQuery({ +export function useMarsMockRedBankUserCollateralQuery({ client, args, options, -}: MockRedBankUserCollateralQuery) { +}: MarsMockRedBankUserCollateralQuery) { return useQuery( - mockRedBankQueryKeys.userCollateral(client?.contractAddress, args), + marsMockRedBankQueryKeys.userCollateral(client?.contractAddress, args), () => client ? client.userCollateral({ @@ -292,21 +300,21 @@ export function useMockRedBankUserCollateralQuery - extends MockRedBankReactQuery { +export interface MarsMockRedBankUserDebtsQuery + extends MarsMockRedBankReactQuery { args: { limit?: number startAfter?: string user: string } } -export function useMockRedBankUserDebtsQuery({ +export function useMarsMockRedBankUserDebtsQuery({ client, args, options, -}: MockRedBankUserDebtsQuery) { +}: MarsMockRedBankUserDebtsQuery) { return useQuery( - mockRedBankQueryKeys.userDebts(client?.contractAddress, args), + marsMockRedBankQueryKeys.userDebts(client?.contractAddress, args), () => client ? client.userDebts({ @@ -318,20 +326,20 @@ export function useMockRedBankUserDebtsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankUserDebtQuery - extends MockRedBankReactQuery { +export interface MarsMockRedBankUserDebtQuery + extends MarsMockRedBankReactQuery { args: { denom: string user: string } } -export function useMockRedBankUserDebtQuery({ +export function useMarsMockRedBankUserDebtQuery({ client, args, options, -}: MockRedBankUserDebtQuery) { +}: MarsMockRedBankUserDebtQuery) { return useQuery( - mockRedBankQueryKeys.userDebt(client?.contractAddress, args), + marsMockRedBankQueryKeys.userDebt(client?.contractAddress, args), () => client ? client.userDebt({ @@ -342,19 +350,19 @@ export function useMockRedBankUserDebtQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankUncollateralizedLoanLimitsQuery - extends MockRedBankReactQuery { +export interface MarsMockRedBankUncollateralizedLoanLimitsQuery + extends MarsMockRedBankReactQuery { args: { limit?: number startAfter?: string user: string } } -export function useMockRedBankUncollateralizedLoanLimitsQuery< +export function useMarsMockRedBankUncollateralizedLoanLimitsQuery< TData = ArrayOfUncollateralizedLoanLimitResponse, ->({ client, args, options }: MockRedBankUncollateralizedLoanLimitsQuery) { +>({ client, args, options }: MarsMockRedBankUncollateralizedLoanLimitsQuery) { return useQuery( - mockRedBankQueryKeys.uncollateralizedLoanLimits(client?.contractAddress, args), + marsMockRedBankQueryKeys.uncollateralizedLoanLimits(client?.contractAddress, args), () => client ? client.uncollateralizedLoanLimits({ @@ -366,18 +374,18 @@ export function useMockRedBankUncollateralizedLoanLimitsQuery< { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankUncollateralizedLoanLimitQuery - extends MockRedBankReactQuery { +export interface MarsMockRedBankUncollateralizedLoanLimitQuery + extends MarsMockRedBankReactQuery { args: { denom: string user: string } } -export function useMockRedBankUncollateralizedLoanLimitQuery< +export function useMarsMockRedBankUncollateralizedLoanLimitQuery< TData = UncollateralizedLoanLimitResponse, ->({ client, args, options }: MockRedBankUncollateralizedLoanLimitQuery) { +>({ client, args, options }: MarsMockRedBankUncollateralizedLoanLimitQuery) { return useQuery( - mockRedBankQueryKeys.uncollateralizedLoanLimit(client?.contractAddress, args), + marsMockRedBankQueryKeys.uncollateralizedLoanLimit(client?.contractAddress, args), () => client ? client.uncollateralizedLoanLimit({ @@ -388,20 +396,20 @@ export function useMockRedBankUncollateralizedLoanLimitQuery< { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankMarketsQuery - extends MockRedBankReactQuery { +export interface MarsMockRedBankMarketsQuery + extends MarsMockRedBankReactQuery { args: { limit?: number startAfter?: string } } -export function useMockRedBankMarketsQuery({ +export function useMarsMockRedBankMarketsQuery({ client, args, options, -}: MockRedBankMarketsQuery) { +}: MarsMockRedBankMarketsQuery) { return useQuery( - mockRedBankQueryKeys.markets(client?.contractAddress, args), + marsMockRedBankQueryKeys.markets(client?.contractAddress, args), () => client ? client.markets({ @@ -412,18 +420,19 @@ export function useMockRedBankMarketsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankMarketQuery extends MockRedBankReactQuery { +export interface MarsMockRedBankMarketQuery + extends MarsMockRedBankReactQuery { args: { denom: string } } -export function useMockRedBankMarketQuery({ +export function useMarsMockRedBankMarketQuery({ client, args, options, -}: MockRedBankMarketQuery) { +}: MarsMockRedBankMarketQuery) { return useQuery( - mockRedBankQueryKeys.market(client?.contractAddress, args), + marsMockRedBankQueryKeys.market(client?.contractAddress, args), () => client ? client.market({ @@ -433,20 +442,20 @@ export function useMockRedBankMarketQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankConfigQuery - extends MockRedBankReactQuery {} -export function useMockRedBankConfigQuery({ +export interface MarsMockRedBankConfigQuery + extends MarsMockRedBankReactQuery {} +export function useMarsMockRedBankConfigQuery({ client, options, -}: MockRedBankConfigQuery) { +}: MarsMockRedBankConfigQuery) { return useQuery( - mockRedBankQueryKeys.config(client?.contractAddress), + marsMockRedBankQueryKeys.config(client?.contractAddress), () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockRedBankUpdateAssetCollateralStatusMutation { - client: MockRedBankClient +export interface MarsMockRedBankUpdateAssetCollateralStatusMutation { + client: MarsMockRedBankClient msg: { denom: string enable: boolean @@ -457,20 +466,20 @@ export interface MockRedBankUpdateAssetCollateralStatusMutation { funds?: Coin[] } } -export function useMockRedBankUpdateAssetCollateralStatusMutation( +export function useMarsMockRedBankUpdateAssetCollateralStatusMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateAssetCollateralStatus(msg, fee, memo, funds), options, ) } -export interface MockRedBankLiquidateMutation { - client: MockRedBankClient +export interface MarsMockRedBankLiquidateMutation { + client: MarsMockRedBankClient msg: { collateralDenom: string recipient?: string @@ -482,19 +491,19 @@ export interface MockRedBankLiquidateMutation { funds?: Coin[] } } -export function useMockRedBankLiquidateMutation( +export function useMarsMockRedBankLiquidateMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.liquidate(msg, fee, memo, funds), options, ) } -export interface MockRedBankRepayMutation { - client: MockRedBankClient +export interface MarsMockRedBankRepayMutation { + client: MarsMockRedBankClient msg: { onBehalfOf?: string } @@ -504,16 +513,19 @@ export interface MockRedBankRepayMutation { funds?: Coin[] } } -export function useMockRedBankRepayMutation( - options?: Omit, 'mutationFn'>, +export function useMarsMockRedBankRepayMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.repay(msg, fee, memo, funds), options, ) } -export interface MockRedBankBorrowMutation { - client: MockRedBankClient +export interface MarsMockRedBankBorrowMutation { + client: MarsMockRedBankClient msg: { amount: Uint128 denom: string @@ -525,16 +537,19 @@ export interface MockRedBankBorrowMutation { funds?: Coin[] } } -export function useMockRedBankBorrowMutation( - options?: Omit, 'mutationFn'>, +export function useMarsMockRedBankBorrowMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.borrow(msg, fee, memo, funds), options, ) } -export interface MockRedBankWithdrawMutation { - client: MockRedBankClient +export interface MarsMockRedBankWithdrawMutation { + client: MarsMockRedBankClient msg: { amount?: Uint128 denom: string @@ -546,19 +561,19 @@ export interface MockRedBankWithdrawMutation { funds?: Coin[] } } -export function useMockRedBankWithdrawMutation( +export function useMarsMockRedBankWithdrawMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.withdraw(msg, fee, memo, funds), options, ) } -export interface MockRedBankDepositMutation { - client: MockRedBankClient +export interface MarsMockRedBankDepositMutation { + client: MarsMockRedBankClient msg: { onBehalfOf?: string } @@ -568,19 +583,19 @@ export interface MockRedBankDepositMutation { funds?: Coin[] } } -export function useMockRedBankDepositMutation( +export function useMarsMockRedBankDepositMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.deposit(msg, fee, memo, funds), options, ) } -export interface MockRedBankUpdateUncollateralizedLoanLimitMutation { - client: MockRedBankClient +export interface MarsMockRedBankUpdateUncollateralizedLoanLimitMutation { + client: MarsMockRedBankClient msg: { denom: string newLimit: Uint128 @@ -592,20 +607,24 @@ export interface MockRedBankUpdateUncollateralizedLoanLimitMutation { funds?: Coin[] } } -export function useMockRedBankUpdateUncollateralizedLoanLimitMutation( +export function useMarsMockRedBankUpdateUncollateralizedLoanLimitMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions< + ExecuteResult, + Error, + MarsMockRedBankUpdateUncollateralizedLoanLimitMutation + >, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateUncollateralizedLoanLimit(msg, fee, memo, funds), options, ) } -export interface MockRedBankUpdateAssetMutation { - client: MockRedBankClient +export interface MarsMockRedBankUpdateAssetMutation { + client: MarsMockRedBankClient msg: { denom: string params: InitOrUpdateAssetParams @@ -616,19 +635,19 @@ export interface MockRedBankUpdateAssetMutation { funds?: Coin[] } } -export function useMockRedBankUpdateAssetMutation( +export function useMarsMockRedBankUpdateAssetMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateAsset(msg, fee, memo, funds), options, ) } -export interface MockRedBankInitAssetMutation { - client: MockRedBankClient +export interface MarsMockRedBankInitAssetMutation { + client: MarsMockRedBankClient msg: { denom: string params: InitOrUpdateAssetParams @@ -639,19 +658,19 @@ export interface MockRedBankInitAssetMutation { funds?: Coin[] } } -export function useMockRedBankInitAssetMutation( +export function useMarsMockRedBankInitAssetMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.initAsset(msg, fee, memo, funds), options, ) } -export interface MockRedBankUpdateConfigMutation { - client: MockRedBankClient +export interface MarsMockRedBankUpdateConfigMutation { + client: MarsMockRedBankClient msg: { config: CreateOrUpdateConfig } @@ -661,13 +680,13 @@ export interface MockRedBankUpdateConfigMutation { funds?: Coin[] } } -export function useMockRedBankUpdateConfigMutation( +export function useMarsMockRedBankUpdateConfigMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateConfig(msg, fee, memo, funds), options, diff --git a/scripts/types/generated/mock-red-bank/MockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts similarity index 100% rename from scripts/types/generated/mock-red-bank/MockRedBank.types.ts rename to scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts diff --git a/scripts/types/generated/mars-mock-red-bank/bundle.ts b/scripts/types/generated/mars-mock-red-bank/bundle.ts new file mode 100644 index 000000000..2fa1eefe2 --- /dev/null +++ b/scripts/types/generated/mars-mock-red-bank/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _12 from './MarsMockRedBank.types' +import * as _13 from './MarsMockRedBank.client' +import * as _14 from './MarsMockRedBank.message-composer' +import * as _15 from './MarsMockRedBank.react-query' +export namespace contracts { + export const MarsMockRedBank = { ..._12, ..._13, ..._14, ..._15 } +} diff --git a/scripts/types/generated/mock-vault/MockVault.client.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts similarity index 94% rename from scripts/types/generated/mock-vault/MockVault.client.ts rename to scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts index d0e6858ac..e10d9622d 100644 --- a/scripts/types/generated/mock-vault/MockVault.client.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts @@ -22,8 +22,8 @@ import { VaultInfo, Empty, VaultStandardInfo, -} from './MockVault.types' -export interface MockVaultReadOnlyInterface { +} from './MarsMockVault.types' +export interface MarsMockVaultReadOnlyInterface { contractAddress: string vaultStandardInfo: () => Promise info: () => Promise @@ -35,7 +35,7 @@ export interface MockVaultReadOnlyInterface { convertToAssets: ({ amount }: { amount: Uint128 }) => Promise vaultExtension: () => Promise } -export class MockVaultQueryClient implements MockVaultReadOnlyInterface { +export class MarsMockVaultQueryClient implements MarsMockVaultReadOnlyInterface { client: CosmWasmClient contractAddress: string @@ -107,7 +107,7 @@ export class MockVaultQueryClient implements MockVaultReadOnlyInterface { }) } } -export interface MockVaultInterface extends MockVaultReadOnlyInterface { +export interface MarsMockVaultInterface extends MarsMockVaultReadOnlyInterface { contractAddress: string sender: string deposit: ( @@ -140,7 +140,10 @@ export interface MockVaultInterface extends MockVaultReadOnlyInterface { funds?: Coin[], ) => Promise } -export class MockVaultClient extends MockVaultQueryClient implements MockVaultInterface { +export class MarsMockVaultClient + extends MarsMockVaultQueryClient + implements MarsMockVaultInterface +{ client: SigningCosmWasmClient sender: string contractAddress: string diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts new file mode 100644 index 000000000..3e119026a --- /dev/null +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts @@ -0,0 +1,134 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { Coin } from '@cosmjs/amino' +import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + Duration, + OracleBaseForString, + InstantiateMsg, + ExecuteMsg, + Uint128, + ExtensionExecuteMsg, + LockupExecuteMsg, + ForceUnlockExecuteMsg, + QueryMsg, + ExtensionQueryMsg, + LockupQueryMsg, + VaultInfo, + Empty, + VaultStandardInfo, +} from './MarsMockVault.types' +export interface MarsMockVaultMessage { + contractAddress: string + sender: string + deposit: ( + { + amount, + recipient, + }: { + amount: Uint128 + recipient?: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + redeem: ( + { + amount, + recipient, + }: { + amount: Uint128 + recipient?: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + vaultExtension: (funds?: Coin[]) => MsgExecuteContractEncodeObject +} +export class MarsMockVaultMessageComposer implements MarsMockVaultMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.deposit = this.deposit.bind(this) + this.redeem = this.redeem.bind(this) + this.vaultExtension = this.vaultExtension.bind(this) + } + + deposit = ( + { + amount, + recipient, + }: { + amount: Uint128 + recipient?: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + deposit: { + amount, + recipient, + }, + }), + ), + funds, + }), + } + } + redeem = ( + { + amount, + recipient, + }: { + amount: Uint128 + recipient?: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + redeem: { + amount, + recipient, + }, + }), + ), + funds, + }), + } + } + vaultExtension = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + vault_extension: {}, + }), + ), + funds, + }), + } + } +} diff --git a/scripts/types/generated/mock-vault/MockVault.react-query.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts similarity index 53% rename from scripts/types/generated/mock-vault/MockVault.react-query.ts rename to scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts index db84a2a5c..2cfe68ea5 100644 --- a/scripts/types/generated/mock-vault/MockVault.react-query.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts @@ -23,55 +23,61 @@ import { VaultInfo, Empty, VaultStandardInfo, -} from './MockVault.types' -import { MockVaultQueryClient, MockVaultClient } from './MockVault.client' -export const mockVaultQueryKeys = { +} from './MarsMockVault.types' +import { MarsMockVaultQueryClient, MarsMockVaultClient } from './MarsMockVault.client' +export const marsMockVaultQueryKeys = { contract: [ { - contract: 'mockVault', + contract: 'marsMockVault', }, ] as const, address: (contractAddress: string | undefined) => - [{ ...mockVaultQueryKeys.contract[0], address: contractAddress }] as const, + [{ ...marsMockVaultQueryKeys.contract[0], address: contractAddress }] as const, vaultStandardInfo: (contractAddress: string | undefined, args?: Record) => [ - { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'vault_standard_info', args }, + { + ...marsMockVaultQueryKeys.address(contractAddress)[0], + method: 'vault_standard_info', + args, + }, ] as const, info: (contractAddress: string | undefined, args?: Record) => - [{ ...mockVaultQueryKeys.address(contractAddress)[0], method: 'info', args }] as const, + [{ ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'info', args }] as const, previewDeposit: (contractAddress: string | undefined, args?: Record) => [ - { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'preview_deposit', args }, + { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'preview_deposit', args }, ] as const, previewRedeem: (contractAddress: string | undefined, args?: Record) => [ - { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'preview_redeem', args }, + { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'preview_redeem', args }, ] as const, totalAssets: (contractAddress: string | undefined, args?: Record) => - [{ ...mockVaultQueryKeys.address(contractAddress)[0], method: 'total_assets', args }] as const, + [ + { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'total_assets', args }, + ] as const, totalVaultTokenSupply: (contractAddress: string | undefined, args?: Record) => [ { - ...mockVaultQueryKeys.address(contractAddress)[0], + ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'total_vault_token_supply', args, }, ] as const, convertToShares: (contractAddress: string | undefined, args?: Record) => [ - { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'convert_to_shares', args }, + { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'convert_to_shares', args }, ] as const, convertToAssets: (contractAddress: string | undefined, args?: Record) => [ - { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'convert_to_assets', args }, + { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'convert_to_assets', args }, ] as const, vaultExtension: (contractAddress: string | undefined, args?: Record) => [ - { ...mockVaultQueryKeys.address(contractAddress)[0], method: 'vault_extension', args }, + { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'vault_extension', args }, ] as const, } -export interface MockVaultReactQuery { - client: MockVaultQueryClient | undefined +export interface MarsMockVaultReactQuery { + client: MarsMockVaultQueryClient | undefined options?: Omit< UseQueryOptions, "'queryKey' | 'queryFn' | 'initialData'" @@ -79,29 +85,31 @@ export interface MockVaultReactQuery { initialData?: undefined } } -export interface MockVaultVaultExtensionQuery extends MockVaultReactQuery {} -export function useMockVaultVaultExtensionQuery({ +export interface MarsMockVaultVaultExtensionQuery + extends MarsMockVaultReactQuery {} +export function useMarsMockVaultVaultExtensionQuery({ client, options, -}: MockVaultVaultExtensionQuery) { +}: MarsMockVaultVaultExtensionQuery) { return useQuery( - mockVaultQueryKeys.vaultExtension(client?.contractAddress), + marsMockVaultQueryKeys.vaultExtension(client?.contractAddress), () => (client ? client.vaultExtension() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultConvertToAssetsQuery extends MockVaultReactQuery { +export interface MarsMockVaultConvertToAssetsQuery + extends MarsMockVaultReactQuery { args: { amount: Uint128 } } -export function useMockVaultConvertToAssetsQuery({ +export function useMarsMockVaultConvertToAssetsQuery({ client, args, options, -}: MockVaultConvertToAssetsQuery) { +}: MarsMockVaultConvertToAssetsQuery) { return useQuery( - mockVaultQueryKeys.convertToAssets(client?.contractAddress, args), + marsMockVaultQueryKeys.convertToAssets(client?.contractAddress, args), () => client ? client.convertToAssets({ @@ -111,18 +119,19 @@ export function useMockVaultConvertToAssetsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultConvertToSharesQuery extends MockVaultReactQuery { +export interface MarsMockVaultConvertToSharesQuery + extends MarsMockVaultReactQuery { args: { amount: Uint128 } } -export function useMockVaultConvertToSharesQuery({ +export function useMarsMockVaultConvertToSharesQuery({ client, args, options, -}: MockVaultConvertToSharesQuery) { +}: MarsMockVaultConvertToSharesQuery) { return useQuery( - mockVaultQueryKeys.convertToShares(client?.contractAddress, args), + marsMockVaultQueryKeys.convertToShares(client?.contractAddress, args), () => client ? client.convertToShares({ @@ -132,41 +141,43 @@ export function useMockVaultConvertToSharesQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultTotalVaultTokenSupplyQuery - extends MockVaultReactQuery {} -export function useMockVaultTotalVaultTokenSupplyQuery({ +export interface MarsMockVaultTotalVaultTokenSupplyQuery + extends MarsMockVaultReactQuery {} +export function useMarsMockVaultTotalVaultTokenSupplyQuery({ client, options, -}: MockVaultTotalVaultTokenSupplyQuery) { +}: MarsMockVaultTotalVaultTokenSupplyQuery) { return useQuery( - mockVaultQueryKeys.totalVaultTokenSupply(client?.contractAddress), + marsMockVaultQueryKeys.totalVaultTokenSupply(client?.contractAddress), () => (client ? client.totalVaultTokenSupply() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultTotalAssetsQuery extends MockVaultReactQuery {} -export function useMockVaultTotalAssetsQuery({ +export interface MarsMockVaultTotalAssetsQuery + extends MarsMockVaultReactQuery {} +export function useMarsMockVaultTotalAssetsQuery({ client, options, -}: MockVaultTotalAssetsQuery) { +}: MarsMockVaultTotalAssetsQuery) { return useQuery( - mockVaultQueryKeys.totalAssets(client?.contractAddress), + marsMockVaultQueryKeys.totalAssets(client?.contractAddress), () => (client ? client.totalAssets() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultPreviewRedeemQuery extends MockVaultReactQuery { +export interface MarsMockVaultPreviewRedeemQuery + extends MarsMockVaultReactQuery { args: { amount: Uint128 } } -export function useMockVaultPreviewRedeemQuery({ +export function useMarsMockVaultPreviewRedeemQuery({ client, args, options, -}: MockVaultPreviewRedeemQuery) { +}: MarsMockVaultPreviewRedeemQuery) { return useQuery( - mockVaultQueryKeys.previewRedeem(client?.contractAddress, args), + marsMockVaultQueryKeys.previewRedeem(client?.contractAddress, args), () => client ? client.previewRedeem({ @@ -176,18 +187,19 @@ export function useMockVaultPreviewRedeemQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultPreviewDepositQuery extends MockVaultReactQuery { +export interface MarsMockVaultPreviewDepositQuery + extends MarsMockVaultReactQuery { args: { amount: Uint128 } } -export function useMockVaultPreviewDepositQuery({ +export function useMarsMockVaultPreviewDepositQuery({ client, args, options, -}: MockVaultPreviewDepositQuery) { +}: MarsMockVaultPreviewDepositQuery) { return useQuery( - mockVaultQueryKeys.previewDeposit(client?.contractAddress, args), + marsMockVaultQueryKeys.previewDeposit(client?.contractAddress, args), () => client ? client.previewDeposit({ @@ -197,31 +209,31 @@ export function useMockVaultPreviewDepositQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultInfoQuery extends MockVaultReactQuery {} -export function useMockVaultInfoQuery({ +export interface MarsMockVaultInfoQuery extends MarsMockVaultReactQuery {} +export function useMarsMockVaultInfoQuery({ client, options, -}: MockVaultInfoQuery) { +}: MarsMockVaultInfoQuery) { return useQuery( - mockVaultQueryKeys.info(client?.contractAddress), + marsMockVaultQueryKeys.info(client?.contractAddress), () => (client ? client.info() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultVaultStandardInfoQuery - extends MockVaultReactQuery {} -export function useMockVaultVaultStandardInfoQuery({ +export interface MarsMockVaultVaultStandardInfoQuery + extends MarsMockVaultReactQuery {} +export function useMarsMockVaultVaultStandardInfoQuery({ client, options, -}: MockVaultVaultStandardInfoQuery) { +}: MarsMockVaultVaultStandardInfoQuery) { return useQuery( - mockVaultQueryKeys.vaultStandardInfo(client?.contractAddress), + marsMockVaultQueryKeys.vaultStandardInfo(client?.contractAddress), () => (client ? client.vaultStandardInfo() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockVaultVaultExtensionMutation { - client: MockVaultClient +export interface MarsMockVaultVaultExtensionMutation { + client: MarsMockVaultClient msg: ExtensionExecuteMsg args?: { fee?: number | StdFee | 'auto' @@ -229,20 +241,20 @@ export interface MockVaultVaultExtensionMutation { funds?: Coin[] } } -export function useMockVaultVaultExtensionMutation( +export function useMarsMockVaultVaultExtensionMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.vaultExtension(msg, fee, memo, funds), options, ) } -export interface MockVaultRedeemMutation { - client: MockVaultClient +export interface MarsMockVaultRedeemMutation { + client: MarsMockVaultClient msg: { amount: Uint128 recipient?: string @@ -253,16 +265,19 @@ export interface MockVaultRedeemMutation { funds?: Coin[] } } -export function useMockVaultRedeemMutation( - options?: Omit, 'mutationFn'>, +export function useMarsMockVaultRedeemMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.redeem(msg, fee, memo, funds), options, ) } -export interface MockVaultDepositMutation { - client: MockVaultClient +export interface MarsMockVaultDepositMutation { + client: MarsMockVaultClient msg: { amount: Uint128 recipient?: string @@ -273,10 +288,13 @@ export interface MockVaultDepositMutation { funds?: Coin[] } } -export function useMockVaultDepositMutation( - options?: Omit, 'mutationFn'>, +export function useMarsMockVaultDepositMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.deposit(msg, fee, memo, funds), options, ) diff --git a/scripts/types/generated/mock-vault/MockVault.types.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts similarity index 100% rename from scripts/types/generated/mock-vault/MockVault.types.ts rename to scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts diff --git a/scripts/types/generated/mock-oracle/bundle.ts b/scripts/types/generated/mars-mock-vault/bundle.ts similarity index 51% rename from scripts/types/generated/mock-oracle/bundle.ts rename to scripts/types/generated/mars-mock-vault/bundle.ts index a654c2b8e..cc8347955 100644 --- a/scripts/types/generated/mock-oracle/bundle.ts +++ b/scripts/types/generated/mars-mock-vault/bundle.ts @@ -5,9 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _9 from './MockOracle.types' -import * as _10 from './MockOracle.client' -import * as _11 from './MockOracle.react-query' +import * as _16 from './MarsMockVault.types' +import * as _17 from './MarsMockVault.client' +import * as _18 from './MarsMockVault.message-composer' +import * as _19 from './MarsMockVault.react-query' export namespace contracts { - export const MockOracle = { ..._9, ..._10, ..._11 } + export const MarsMockVault = { ..._16, ..._17, ..._18, ..._19 } } diff --git a/scripts/types/generated/mock-zapper/MockZapper.client.ts b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts similarity index 90% rename from scripts/types/generated/mock-zapper/MockZapper.client.ts rename to scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts index bc3eda946..c1d176a49 100644 --- a/scripts/types/generated/mock-zapper/MockZapper.client.ts +++ b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts @@ -16,8 +16,8 @@ import { QueryMsg, Coin, ArrayOfCoin, -} from './MockZapper.types' -export interface MockZapperReadOnlyInterface { +} from './MarsMockZapper.types' +export interface MarsMockZapperReadOnlyInterface { contractAddress: string estimateProvideLiquidity: ({ coinsIn, @@ -28,7 +28,7 @@ export interface MockZapperReadOnlyInterface { }) => Promise estimateWithdrawLiquidity: ({ coinIn }: { coinIn: Coin }) => Promise } -export class MockZapperQueryClient implements MockZapperReadOnlyInterface { +export class MarsMockZapperQueryClient implements MarsMockZapperReadOnlyInterface { client: CosmWasmClient contractAddress: string @@ -61,7 +61,7 @@ export class MockZapperQueryClient implements MockZapperReadOnlyInterface { }) } } -export interface MockZapperInterface extends MockZapperReadOnlyInterface { +export interface MarsMockZapperInterface extends MarsMockZapperReadOnlyInterface { contractAddress: string sender: string provideLiquidity: ( @@ -89,7 +89,10 @@ export interface MockZapperInterface extends MockZapperReadOnlyInterface { funds?: Coin[], ) => Promise } -export class MockZapperClient extends MockZapperQueryClient implements MockZapperInterface { +export class MarsMockZapperClient + extends MarsMockZapperQueryClient + implements MarsMockZapperInterface +{ client: SigningCosmWasmClient sender: string contractAddress: string diff --git a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts new file mode 100644 index 000000000..822a00b22 --- /dev/null +++ b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts @@ -0,0 +1,110 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + OracleBaseForString, + InstantiateMsg, + LpConfig, + ExecuteMsg, + Uint128, + QueryMsg, + Coin, + ArrayOfCoin, +} from './MarsMockZapper.types' +export interface MarsMockZapperMessage { + contractAddress: string + sender: string + provideLiquidity: ( + { + lpTokenOut, + minimumReceive, + recipient, + }: { + lpTokenOut: string + minimumReceive: Uint128 + recipient?: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + withdrawLiquidity: ( + { + recipient, + }: { + recipient?: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject +} +export class MarsMockZapperMessageComposer implements MarsMockZapperMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.provideLiquidity = this.provideLiquidity.bind(this) + this.withdrawLiquidity = this.withdrawLiquidity.bind(this) + } + + provideLiquidity = ( + { + lpTokenOut, + minimumReceive, + recipient, + }: { + lpTokenOut: string + minimumReceive: Uint128 + recipient?: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + provide_liquidity: { + lp_token_out: lpTokenOut, + minimum_receive: minimumReceive, + recipient, + }, + }), + ), + funds, + }), + } + } + withdrawLiquidity = ( + { + recipient, + }: { + recipient?: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + withdraw_liquidity: { + recipient, + }, + }), + ), + funds, + }), + } + } +} diff --git a/scripts/types/generated/mock-zapper/MockZapper.react-query.ts b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts similarity index 59% rename from scripts/types/generated/mock-zapper/MockZapper.react-query.ts rename to scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts index a2997d8d4..6f9fff7d1 100644 --- a/scripts/types/generated/mock-zapper/MockZapper.react-query.ts +++ b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts @@ -17,20 +17,20 @@ import { QueryMsg, Coin, ArrayOfCoin, -} from './MockZapper.types' -import { MockZapperQueryClient, MockZapperClient } from './MockZapper.client' -export const mockZapperQueryKeys = { +} from './MarsMockZapper.types' +import { MarsMockZapperQueryClient, MarsMockZapperClient } from './MarsMockZapper.client' +export const marsMockZapperQueryKeys = { contract: [ { - contract: 'mockZapper', + contract: 'marsMockZapper', }, ] as const, address: (contractAddress: string | undefined) => - [{ ...mockZapperQueryKeys.contract[0], address: contractAddress }] as const, + [{ ...marsMockZapperQueryKeys.contract[0], address: contractAddress }] as const, estimateProvideLiquidity: (contractAddress: string | undefined, args?: Record) => [ { - ...mockZapperQueryKeys.address(contractAddress)[0], + ...marsMockZapperQueryKeys.address(contractAddress)[0], method: 'estimate_provide_liquidity', args, }, @@ -41,14 +41,14 @@ export const mockZapperQueryKeys = { ) => [ { - ...mockZapperQueryKeys.address(contractAddress)[0], + ...marsMockZapperQueryKeys.address(contractAddress)[0], method: 'estimate_withdraw_liquidity', args, }, ] as const, } -export interface MockZapperReactQuery { - client: MockZapperQueryClient | undefined +export interface MarsMockZapperReactQuery { + client: MarsMockZapperQueryClient | undefined options?: Omit< UseQueryOptions, "'queryKey' | 'queryFn' | 'initialData'" @@ -56,19 +56,19 @@ export interface MockZapperReactQuery { initialData?: undefined } } -export interface MockZapperEstimateWithdrawLiquidityQuery - extends MockZapperReactQuery { +export interface MarsMockZapperEstimateWithdrawLiquidityQuery + extends MarsMockZapperReactQuery { args: { coinIn: Coin } } -export function useMockZapperEstimateWithdrawLiquidityQuery({ +export function useMarsMockZapperEstimateWithdrawLiquidityQuery({ client, args, options, -}: MockZapperEstimateWithdrawLiquidityQuery) { +}: MarsMockZapperEstimateWithdrawLiquidityQuery) { return useQuery( - mockZapperQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), + marsMockZapperQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), () => client ? client.estimateWithdrawLiquidity({ @@ -78,20 +78,20 @@ export function useMockZapperEstimateWithdrawLiquidityQuery { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockZapperEstimateProvideLiquidityQuery - extends MockZapperReactQuery { +export interface MarsMockZapperEstimateProvideLiquidityQuery + extends MarsMockZapperReactQuery { args: { coinsIn: Coin[] lpTokenOut: string } } -export function useMockZapperEstimateProvideLiquidityQuery({ +export function useMarsMockZapperEstimateProvideLiquidityQuery({ client, args, options, -}: MockZapperEstimateProvideLiquidityQuery) { +}: MarsMockZapperEstimateProvideLiquidityQuery) { return useQuery( - mockZapperQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), + marsMockZapperQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), () => client ? client.estimateProvideLiquidity({ @@ -102,8 +102,8 @@ export function useMockZapperEstimateProvideLiquidityQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MockZapperWithdrawLiquidityMutation { - client: MockZapperClient +export interface MarsMockZapperWithdrawLiquidityMutation { + client: MarsMockZapperClient msg: { recipient?: string } @@ -113,20 +113,20 @@ export interface MockZapperWithdrawLiquidityMutation { funds?: Coin[] } } -export function useMockZapperWithdrawLiquidityMutation( +export function useMarsMockZapperWithdrawLiquidityMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.withdrawLiquidity(msg, fee, memo, funds), options, ) } -export interface MockZapperProvideLiquidityMutation { - client: MockZapperClient +export interface MarsMockZapperProvideLiquidityMutation { + client: MarsMockZapperClient msg: { lpTokenOut: string minimumReceive: Uint128 @@ -138,13 +138,13 @@ export interface MockZapperProvideLiquidityMutation { funds?: Coin[] } } -export function useMockZapperProvideLiquidityMutation( +export function useMarsMockZapperProvideLiquidityMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.provideLiquidity(msg, fee, memo, funds), options, diff --git a/scripts/types/generated/mock-zapper/MockZapper.types.ts b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.types.ts similarity index 100% rename from scripts/types/generated/mock-zapper/MockZapper.types.ts rename to scripts/types/generated/mars-mock-zapper/MarsMockZapper.types.ts diff --git a/scripts/types/generated/mock-red-bank/bundle.ts b/scripts/types/generated/mars-mock-zapper/bundle.ts similarity index 50% rename from scripts/types/generated/mock-red-bank/bundle.ts rename to scripts/types/generated/mars-mock-zapper/bundle.ts index c40f381d0..6bffb33f5 100644 --- a/scripts/types/generated/mock-red-bank/bundle.ts +++ b/scripts/types/generated/mars-mock-zapper/bundle.ts @@ -5,9 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _12 from './MockRedBank.types' -import * as _13 from './MockRedBank.client' -import * as _14 from './MockRedBank.react-query' +import * as _20 from './MarsMockZapper.types' +import * as _21 from './MarsMockZapper.client' +import * as _22 from './MarsMockZapper.message-composer' +import * as _23 from './MarsMockZapper.react-query' export namespace contracts { - export const MockRedBank = { ..._12, ..._13, ..._14 } + export const MarsMockZapper = { ..._20, ..._21, ..._22, ..._23 } } diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts new file mode 100644 index 000000000..0ce7b8ca9 --- /dev/null +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts @@ -0,0 +1,75 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + OracleBaseForString, + Addr, + PricingMethod, + InstantiateMsg, + VaultPricingInfo, + ExecuteMsg, + ConfigUpdates, + QueryMsg, + Uint128, + Coin, + ArrayOfVaultPricingInfo, + OracleBaseForAddr, + ConfigResponse, + Decimal, + PriceResponse, + ArrayOfCoin, +} from './MarsOracleAdapter.types' +export interface MarsOracleAdapterMessage { + contractAddress: string + sender: string + updateConfig: ( + { + newConfig, + }: { + newConfig: ConfigUpdates + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject +} +export class MarsOracleAdapterMessageComposer implements MarsOracleAdapterMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.updateConfig = this.updateConfig.bind(this) + } + + updateConfig = ( + { + newConfig, + }: { + newConfig: ConfigUpdates + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_config: { + new_config: newConfig, + }, + }), + ), + funds, + }), + } + } +} diff --git a/scripts/types/generated/mars-oracle-adapter/bundle.ts b/scripts/types/generated/mars-oracle-adapter/bundle.ts index e1620a2fe..63503f7f2 100644 --- a/scripts/types/generated/mars-oracle-adapter/bundle.ts +++ b/scripts/types/generated/mars-oracle-adapter/bundle.ts @@ -5,9 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _6 from './MarsOracleAdapter.types' -import * as _7 from './MarsOracleAdapter.client' -import * as _8 from './MarsOracleAdapter.react-query' +import * as _24 from './MarsOracleAdapter.types' +import * as _25 from './MarsOracleAdapter.client' +import * as _26 from './MarsOracleAdapter.message-composer' +import * as _27 from './MarsOracleAdapter.react-query' export namespace contracts { - export const MarsOracleAdapter = { ..._6, ..._7, ..._8 } + export const MarsOracleAdapter = { ..._24, ..._25, ..._26, ..._27 } } diff --git a/scripts/types/generated/swapper-base/SwapperBase.client.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts similarity index 94% rename from scripts/types/generated/swapper-base/SwapperBase.client.ts rename to scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts index b056c255e..f297c7f0c 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.client.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts @@ -20,8 +20,8 @@ import { EstimateExactInSwapResponse, RouteResponseForEmpty, ArrayOfRouteResponseForEmpty, -} from './SwapperBase.types' -export interface SwapperBaseReadOnlyInterface { +} from './MarsSwapperBase.types' +export interface MarsSwapperBaseReadOnlyInterface { contractAddress: string config: () => Promise route: ({ @@ -46,7 +46,7 @@ export interface SwapperBaseReadOnlyInterface { denomOut: string }) => Promise } -export class SwapperBaseQueryClient implements SwapperBaseReadOnlyInterface { +export class MarsSwapperBaseQueryClient implements MarsSwapperBaseReadOnlyInterface { client: CosmWasmClient contractAddress: string @@ -107,7 +107,7 @@ export class SwapperBaseQueryClient implements SwapperBaseReadOnlyInterface { }) } } -export interface SwapperBaseInterface extends SwapperBaseReadOnlyInterface { +export interface MarsSwapperBaseInterface extends MarsSwapperBaseReadOnlyInterface { contractAddress: string sender: string updateConfig: ( @@ -163,7 +163,10 @@ export interface SwapperBaseInterface extends SwapperBaseReadOnlyInterface { funds?: Coin[], ) => Promise } -export class SwapperBaseClient extends SwapperBaseQueryClient implements SwapperBaseInterface { +export class MarsSwapperBaseClient + extends MarsSwapperBaseQueryClient + implements MarsSwapperBaseInterface +{ client: SigningCosmWasmClient sender: string contractAddress: string diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts new file mode 100644 index 000000000..ac2e3c46c --- /dev/null +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts @@ -0,0 +1,200 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + InstantiateMsg, + ExecuteMsg, + Uint128, + Decimal, + Addr, + Empty, + Coin, + QueryMsg, + ConfigForString, + EstimateExactInSwapResponse, + RouteResponseForEmpty, + ArrayOfRouteResponseForEmpty, +} from './MarsSwapperBase.types' +export interface MarsSwapperBaseMessage { + contractAddress: string + sender: string + updateConfig: ( + { + owner, + }: { + owner?: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + setRoute: ( + { + denomIn, + denomOut, + route, + }: { + denomIn: string + denomOut: string + route: Empty + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + swapExactIn: ( + { + coinIn, + denomOut, + slippage, + }: { + coinIn: Coin + denomOut: string + slippage: Decimal + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + transferResult: ( + { + denomIn, + denomOut, + recipient, + }: { + denomIn: string + denomOut: string + recipient: Addr + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject +} +export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.updateConfig = this.updateConfig.bind(this) + this.setRoute = this.setRoute.bind(this) + this.swapExactIn = this.swapExactIn.bind(this) + this.transferResult = this.transferResult.bind(this) + } + + updateConfig = ( + { + owner, + }: { + owner?: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_config: { + owner, + }, + }), + ), + funds, + }), + } + } + setRoute = ( + { + denomIn, + denomOut, + route, + }: { + denomIn: string + denomOut: string + route: Empty + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + set_route: { + denom_in: denomIn, + denom_out: denomOut, + route, + }, + }), + ), + funds, + }), + } + } + swapExactIn = ( + { + coinIn, + denomOut, + slippage, + }: { + coinIn: Coin + denomOut: string + slippage: Decimal + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + swap_exact_in: { + coin_in: coinIn, + denom_out: denomOut, + slippage, + }, + }), + ), + funds, + }), + } + } + transferResult = ( + { + denomIn, + denomOut, + recipient, + }: { + denomIn: string + denomOut: string + recipient: Addr + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + transfer_result: { + denom_in: denomIn, + denom_out: denomOut, + recipient, + }, + }), + ), + funds, + }), + } + } +} diff --git a/scripts/types/generated/swapper-base/SwapperBase.react-query.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts similarity index 56% rename from scripts/types/generated/swapper-base/SwapperBase.react-query.ts rename to scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts index 554aeaceb..1c41e5831 100644 --- a/scripts/types/generated/swapper-base/SwapperBase.react-query.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts @@ -21,33 +21,33 @@ import { EstimateExactInSwapResponse, RouteResponseForEmpty, ArrayOfRouteResponseForEmpty, -} from './SwapperBase.types' -import { SwapperBaseQueryClient, SwapperBaseClient } from './SwapperBase.client' -export const swapperBaseQueryKeys = { +} from './MarsSwapperBase.types' +import { MarsSwapperBaseQueryClient, MarsSwapperBaseClient } from './MarsSwapperBase.client' +export const marsSwapperBaseQueryKeys = { contract: [ { - contract: 'swapperBase', + contract: 'marsSwapperBase', }, ] as const, address: (contractAddress: string | undefined) => - [{ ...swapperBaseQueryKeys.contract[0], address: contractAddress }] as const, + [{ ...marsSwapperBaseQueryKeys.contract[0], address: contractAddress }] as const, config: (contractAddress: string | undefined, args?: Record) => - [{ ...swapperBaseQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, + [{ ...marsSwapperBaseQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, route: (contractAddress: string | undefined, args?: Record) => - [{ ...swapperBaseQueryKeys.address(contractAddress)[0], method: 'route', args }] as const, + [{ ...marsSwapperBaseQueryKeys.address(contractAddress)[0], method: 'route', args }] as const, routes: (contractAddress: string | undefined, args?: Record) => - [{ ...swapperBaseQueryKeys.address(contractAddress)[0], method: 'routes', args }] as const, + [{ ...marsSwapperBaseQueryKeys.address(contractAddress)[0], method: 'routes', args }] as const, estimateExactInSwap: (contractAddress: string | undefined, args?: Record) => [ { - ...swapperBaseQueryKeys.address(contractAddress)[0], + ...marsSwapperBaseQueryKeys.address(contractAddress)[0], method: 'estimate_exact_in_swap', args, }, ] as const, } -export interface SwapperBaseReactQuery { - client: SwapperBaseQueryClient | undefined +export interface MarsSwapperBaseReactQuery { + client: MarsSwapperBaseQueryClient | undefined options?: Omit< UseQueryOptions, "'queryKey' | 'queryFn' | 'initialData'" @@ -55,20 +55,20 @@ export interface SwapperBaseReactQuery { initialData?: undefined } } -export interface SwapperBaseEstimateExactInSwapQuery - extends SwapperBaseReactQuery { +export interface MarsSwapperBaseEstimateExactInSwapQuery + extends MarsSwapperBaseReactQuery { args: { coinIn: Coin denomOut: string } } -export function useSwapperBaseEstimateExactInSwapQuery({ +export function useMarsSwapperBaseEstimateExactInSwapQuery({ client, args, options, -}: SwapperBaseEstimateExactInSwapQuery) { +}: MarsSwapperBaseEstimateExactInSwapQuery) { return useQuery( - swapperBaseQueryKeys.estimateExactInSwap(client?.contractAddress, args), + marsSwapperBaseQueryKeys.estimateExactInSwap(client?.contractAddress, args), () => client ? client.estimateExactInSwap({ @@ -79,20 +79,20 @@ export function useSwapperBaseEstimateExactInSwapQuery - extends SwapperBaseReactQuery { +export interface MarsSwapperBaseRoutesQuery + extends MarsSwapperBaseReactQuery { args: { limit?: number startAfter?: string[][] } } -export function useSwapperBaseRoutesQuery({ +export function useMarsSwapperBaseRoutesQuery({ client, args, options, -}: SwapperBaseRoutesQuery) { +}: MarsSwapperBaseRoutesQuery) { return useQuery( - swapperBaseQueryKeys.routes(client?.contractAddress, args), + marsSwapperBaseQueryKeys.routes(client?.contractAddress, args), () => client ? client.routes({ @@ -103,20 +103,20 @@ export function useSwapperBaseRoutesQuery( { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface SwapperBaseRouteQuery - extends SwapperBaseReactQuery { +export interface MarsSwapperBaseRouteQuery + extends MarsSwapperBaseReactQuery { args: { denomIn: string denomOut: string } } -export function useSwapperBaseRouteQuery({ +export function useMarsSwapperBaseRouteQuery({ client, args, options, -}: SwapperBaseRouteQuery) { +}: MarsSwapperBaseRouteQuery) { return useQuery( - swapperBaseQueryKeys.route(client?.contractAddress, args), + marsSwapperBaseQueryKeys.route(client?.contractAddress, args), () => client ? client.route({ @@ -127,20 +127,20 @@ export function useSwapperBaseRouteQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface SwapperBaseConfigQuery - extends SwapperBaseReactQuery {} -export function useSwapperBaseConfigQuery({ +export interface MarsSwapperBaseConfigQuery + extends MarsSwapperBaseReactQuery {} +export function useMarsSwapperBaseConfigQuery({ client, options, -}: SwapperBaseConfigQuery) { +}: MarsSwapperBaseConfigQuery) { return useQuery( - swapperBaseQueryKeys.config(client?.contractAddress), + marsSwapperBaseQueryKeys.config(client?.contractAddress), () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface SwapperBaseTransferResultMutation { - client: SwapperBaseClient +export interface MarsSwapperBaseTransferResultMutation { + client: MarsSwapperBaseClient msg: { denomIn: string denomOut: string @@ -152,20 +152,20 @@ export interface SwapperBaseTransferResultMutation { funds?: Coin[] } } -export function useSwapperBaseTransferResultMutation( +export function useMarsSwapperBaseTransferResultMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.transferResult(msg, fee, memo, funds), options, ) } -export interface SwapperBaseSwapExactInMutation { - client: SwapperBaseClient +export interface MarsSwapperBaseSwapExactInMutation { + client: MarsSwapperBaseClient msg: { coinIn: Coin denomOut: string @@ -177,19 +177,19 @@ export interface SwapperBaseSwapExactInMutation { funds?: Coin[] } } -export function useSwapperBaseSwapExactInMutation( +export function useMarsSwapperBaseSwapExactInMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.swapExactIn(msg, fee, memo, funds), options, ) } -export interface SwapperBaseSetRouteMutation { - client: SwapperBaseClient +export interface MarsSwapperBaseSetRouteMutation { + client: MarsSwapperBaseClient msg: { denomIn: string denomOut: string @@ -201,19 +201,19 @@ export interface SwapperBaseSetRouteMutation { funds?: Coin[] } } -export function useSwapperBaseSetRouteMutation( +export function useMarsSwapperBaseSetRouteMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.setRoute(msg, fee, memo, funds), options, ) } -export interface SwapperBaseUpdateConfigMutation { - client: SwapperBaseClient +export interface MarsSwapperBaseUpdateConfigMutation { + client: MarsSwapperBaseClient msg: { owner?: string } @@ -223,13 +223,13 @@ export interface SwapperBaseUpdateConfigMutation { funds?: Coin[] } } -export function useSwapperBaseUpdateConfigMutation( +export function useMarsSwapperBaseUpdateConfigMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateConfig(msg, fee, memo, funds), options, diff --git a/scripts/types/generated/swapper-base/SwapperBase.types.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts similarity index 100% rename from scripts/types/generated/swapper-base/SwapperBase.types.ts rename to scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts diff --git a/scripts/types/generated/mars-swapper-base/bundle.ts b/scripts/types/generated/mars-swapper-base/bundle.ts new file mode 100644 index 000000000..b36640116 --- /dev/null +++ b/scripts/types/generated/mars-swapper-base/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _28 from './MarsSwapperBase.types' +import * as _29 from './MarsSwapperBase.client' +import * as _30 from './MarsSwapperBase.message-composer' +import * as _31 from './MarsSwapperBase.react-query' +export namespace contracts { + export const MarsSwapperBase = { ..._28, ..._29, ..._30, ..._31 } +} diff --git a/scripts/types/generated/mock-vault/bundle.ts b/scripts/types/generated/mock-vault/bundle.ts deleted file mode 100644 index 34a4b0df7..000000000 --- a/scripts/types/generated/mock-vault/bundle.ts +++ /dev/null @@ -1,13 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import * as _15 from './MockVault.types' -import * as _16 from './MockVault.client' -import * as _17 from './MockVault.react-query' -export namespace contracts { - export const MockVault = { ..._15, ..._16, ..._17 } -} diff --git a/scripts/types/generated/mock-zapper/bundle.ts b/scripts/types/generated/mock-zapper/bundle.ts deleted file mode 100644 index 6e12af719..000000000 --- a/scripts/types/generated/mock-zapper/bundle.ts +++ /dev/null @@ -1,13 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import * as _18 from './MockZapper.types' -import * as _19 from './MockZapper.client' -import * as _20 from './MockZapper.react-query' -export namespace contracts { - export const MockZapper = { ..._18, ..._19, ..._20 } -} diff --git a/scripts/types/generated/swapper-base/bundle.ts b/scripts/types/generated/swapper-base/bundle.ts deleted file mode 100644 index f302a10e6..000000000 --- a/scripts/types/generated/swapper-base/bundle.ts +++ /dev/null @@ -1,13 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import * as _21 from './SwapperBase.types' -import * as _22 from './SwapperBase.client' -import * as _23 from './SwapperBase.react-query' -export namespace contracts { - export const SwapperBase = { ..._21, ..._22, ..._23 } -} diff --git a/scripts/types/instantiateMsgs.ts b/scripts/types/instantiateMsgs.ts index 1a65447a9..e1b147cb1 100644 --- a/scripts/types/instantiateMsgs.ts +++ b/scripts/types/instantiateMsgs.ts @@ -1,10 +1,10 @@ -import { InstantiateMsg as NftInstantiateMsg } from './generated/account-nft/AccountNft.types' -import { InstantiateMsg as RedBankInstantiateMsg } from './generated/mock-red-bank/MockRedBank.types' -import { InstantiateMsg as VaultInstantiateMsg } from './generated/mock-vault/MockVault.types' -import { InstantiateMsg as OracleInstantiateMsg } from './generated/mock-oracle/MockOracle.types' -import { InstantiateMsg as RoverInstantiateMsg } from './generated/credit-manager/CreditManager.types' -import { InstantiateMsg as SwapperInstantiateMsg } from './generated/swapper-base/SwapperBase.types' -import { InstantiateMsg as ZapperInstantiateMsg } from './generated/mock-zapper/MockZapper.types' +import { InstantiateMsg as NftInstantiateMsg } from './generated/mars-account-nft/MarsAccountNft.types' +import { InstantiateMsg as RedBankInstantiateMsg } from './generated/mars-mock-red-bank/MarsMockRedBank.types' +import { InstantiateMsg as VaultInstantiateMsg } from './generated/mars-mock-vault/MarsMockVault.types' +import { InstantiateMsg as OracleInstantiateMsg } from './generated/mars-mock-oracle/MarsMockOracle.types' +import { InstantiateMsg as RoverInstantiateMsg } from './generated/mars-credit-manager/MarsCreditManager.types' +import { InstantiateMsg as SwapperInstantiateMsg } from './generated/mars-swapper-base/MarsSwapperBase.types' +import { InstantiateMsg as ZapperInstantiateMsg } from './generated/mars-mock-zapper/MarsMockZapper.types' export type InstantiateMsgs = | NftInstantiateMsg diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 750305e6a..f22530959 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -17,7 +17,7 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.4", "@babel/compat-data@^7.20.0": +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.4", "@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1": version "7.20.1" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30" integrity sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ== @@ -97,7 +97,7 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.3": +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.3", "@babel/helper-compilation-targets@^7.20.0": version "7.20.0" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a" integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ== @@ -207,6 +207,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== +"@babel/helper-plugin-utils@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" + integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== + "@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" @@ -217,7 +222,7 @@ "@babel/helper-wrap-function" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9", "@babel/helper-replace-supers@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== @@ -318,7 +323,7 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-proposal-optional-chaining" "^7.18.9" -"@babel/plugin-proposal-async-generator-functions@^7.18.10", "@babel/plugin-proposal-async-generator-functions@^7.19.1": +"@babel/plugin-proposal-async-generator-functions@^7.18.10", "@babel/plugin-proposal-async-generator-functions@^7.20.1": version "7.20.1" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz#352f02baa5d69f4e7529bdac39aaa02d41146af9" integrity sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g== @@ -412,7 +417,7 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.18.8" -"@babel/plugin-proposal-object-rest-spread@^7.18.9", "@babel/plugin-proposal-object-rest-spread@^7.19.4": +"@babel/plugin-proposal-object-rest-spread@^7.18.9": version "7.19.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz#a8fc86e8180ff57290c91a75d83fe658189b642d" integrity sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q== @@ -423,6 +428,17 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.18.8" +"@babel/plugin-proposal-object-rest-spread@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz#a556f59d555f06961df1e572bb5eca864c84022d" + integrity sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ== + dependencies: + "@babel/compat-data" "^7.20.1" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.20.1" + "@babel/plugin-proposal-optional-catch-binding@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" @@ -515,7 +531,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-import-assertions@^7.18.6": +"@babel/plugin-syntax-import-assertions@^7.18.6", "@babel/plugin-syntax-import-assertions@^7.20.0": version "7.20.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== @@ -629,14 +645,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-block-scoping@^7.18.9", "@babel/plugin-transform-block-scoping@^7.19.4": +"@babel/plugin-transform-block-scoping@^7.18.9": version "7.20.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.0.tgz#91fe5e6ffc9ba13cb6c95ed7f0b1204f68c988c5" integrity sha512-sXOohbpHZSk7GjxK9b3dKB7CfqUD5DwOH+DggKzOQ7TXYP+RCSbRykfjQmn/zq+rBjycVRtLf9pYhAaEJA786w== dependencies: "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-transform-classes@^7.18.9", "@babel/plugin-transform-classes@^7.19.0": +"@babel/plugin-transform-block-scoping@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz#f59b1767e6385c663fd0bce655db6ca9c8b236ed" + integrity sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-classes@^7.18.9": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz#0e61ec257fba409c41372175e7c1e606dc79bb20" integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== @@ -651,6 +674,21 @@ "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" +"@babel/plugin-transform-classes@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz#c0033cf1916ccf78202d04be4281d161f6709bb2" + integrity sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-replace-supers" "^7.19.1" + "@babel/helper-split-export-declaration" "^7.18.6" + globals "^11.1.0" + "@babel/plugin-transform-computed-properties@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" @@ -658,13 +696,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-destructuring@^7.18.9", "@babel/plugin-transform-destructuring@^7.19.4": +"@babel/plugin-transform-destructuring@^7.18.9": version "7.20.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.0.tgz#712829ef4825d9cc04bb379de316f981e9a6f648" integrity sha512-1dIhvZfkDVx/zn2S1aFwlruspTt4189j7fEkH0Y0VyuDM6bQt7bD6kLcz3l4IlLG+e5OReaBz9ROAbttRtUHqA== dependencies: "@babel/helper-plugin-utils" "^7.19.0" +"@babel/plugin-transform-destructuring@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz#c23741cfa44ddd35f5e53896e88c75331b8b2792" + integrity sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" @@ -718,7 +763,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-modules-amd@^7.18.6": +"@babel/plugin-transform-modules-amd@^7.18.6", "@babel/plugin-transform-modules-amd@^7.19.6": version "7.19.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz#aca391801ae55d19c4d8d2ebfeaa33df5f2a2cbd" integrity sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg== @@ -726,7 +771,7 @@ "@babel/helper-module-transforms" "^7.19.6" "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-transform-modules-commonjs@^7.18.6": +"@babel/plugin-transform-modules-commonjs@^7.18.6", "@babel/plugin-transform-modules-commonjs@^7.19.6": version "7.19.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz#25b32feef24df8038fc1ec56038917eacb0b730c" integrity sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ== @@ -735,7 +780,7 @@ "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-simple-access" "^7.19.4" -"@babel/plugin-transform-modules-systemjs@^7.18.9", "@babel/plugin-transform-modules-systemjs@^7.19.0": +"@babel/plugin-transform-modules-systemjs@^7.18.9", "@babel/plugin-transform-modules-systemjs@^7.19.6": version "7.19.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz#59e2a84064b5736a4471b1aa7b13d4431d327e0d" integrity sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ== @@ -776,7 +821,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-replace-supers" "^7.18.6" -"@babel/plugin-transform-parameters@^7.18.8": +"@babel/plugin-transform-parameters@^7.18.8", "@babel/plugin-transform-parameters@^7.20.1": version "7.20.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.1.tgz#9a5aa370fdcce36f110455e9369db7afca0f9eeb" integrity sha512-nDvKLrAvl+kf6BOy1UJ3MGwzzfTMgppxwiD2Jb4LO3xjYyZq30oQzDNJbCQpMdG9+j2IXHoiMrw5Cm/L6ZoxXQ== @@ -958,18 +1003,18 @@ core-js-compat "^3.22.1" semver "^6.3.0" -"@babel/preset-env@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.4.tgz#4c91ce2e1f994f717efb4237891c3ad2d808c94b" - integrity sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg== +"@babel/preset-env@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.20.2.tgz#9b1642aa47bb9f43a86f9630011780dab7f86506" + integrity sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg== dependencies: - "@babel/compat-data" "^7.19.4" - "@babel/helper-compilation-targets" "^7.19.3" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/compat-data" "^7.20.1" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-validator-option" "^7.18.6" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-async-generator-functions" "^7.19.1" + "@babel/plugin-proposal-async-generator-functions" "^7.20.1" "@babel/plugin-proposal-class-properties" "^7.18.6" "@babel/plugin-proposal-class-static-block" "^7.18.6" "@babel/plugin-proposal-dynamic-import" "^7.18.6" @@ -978,7 +1023,7 @@ "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" "@babel/plugin-proposal-numeric-separator" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.19.4" + "@babel/plugin-proposal-object-rest-spread" "^7.20.2" "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" "@babel/plugin-proposal-optional-chaining" "^7.18.9" "@babel/plugin-proposal-private-methods" "^7.18.6" @@ -989,7 +1034,7 @@ "@babel/plugin-syntax-class-static-block" "^7.14.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.18.6" + "@babel/plugin-syntax-import-assertions" "^7.20.0" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" @@ -1002,10 +1047,10 @@ "@babel/plugin-transform-arrow-functions" "^7.18.6" "@babel/plugin-transform-async-to-generator" "^7.18.6" "@babel/plugin-transform-block-scoped-functions" "^7.18.6" - "@babel/plugin-transform-block-scoping" "^7.19.4" - "@babel/plugin-transform-classes" "^7.19.0" + "@babel/plugin-transform-block-scoping" "^7.20.2" + "@babel/plugin-transform-classes" "^7.20.2" "@babel/plugin-transform-computed-properties" "^7.18.9" - "@babel/plugin-transform-destructuring" "^7.19.4" + "@babel/plugin-transform-destructuring" "^7.20.2" "@babel/plugin-transform-dotall-regex" "^7.18.6" "@babel/plugin-transform-duplicate-keys" "^7.18.9" "@babel/plugin-transform-exponentiation-operator" "^7.18.6" @@ -1013,14 +1058,14 @@ "@babel/plugin-transform-function-name" "^7.18.9" "@babel/plugin-transform-literals" "^7.18.9" "@babel/plugin-transform-member-expression-literals" "^7.18.6" - "@babel/plugin-transform-modules-amd" "^7.18.6" - "@babel/plugin-transform-modules-commonjs" "^7.18.6" - "@babel/plugin-transform-modules-systemjs" "^7.19.0" + "@babel/plugin-transform-modules-amd" "^7.19.6" + "@babel/plugin-transform-modules-commonjs" "^7.19.6" + "@babel/plugin-transform-modules-systemjs" "^7.19.6" "@babel/plugin-transform-modules-umd" "^7.18.6" "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" "@babel/plugin-transform-new-target" "^7.18.6" "@babel/plugin-transform-object-super" "^7.18.6" - "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-transform-parameters" "^7.20.1" "@babel/plugin-transform-property-literals" "^7.18.6" "@babel/plugin-transform-regenerator" "^7.18.6" "@babel/plugin-transform-reserved-words" "^7.18.6" @@ -1032,7 +1077,7 @@ "@babel/plugin-transform-unicode-escapes" "^7.18.10" "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.19.4" + "@babel/types" "^7.20.2" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" babel-plugin-polyfill-regenerator "^0.4.1" @@ -1125,6 +1170,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.2.tgz#67ac09266606190f496322dbaff360fdaa5e7842" + integrity sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1825,10 +1879,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.1.2": - version "29.2.1" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.1.tgz#31fda30bdf2861706abc5f1730be78bed54f83ee" - integrity sha512-nKixEdnGDqFOZkMTF74avFNr3yRqB1ZJ6sRZv5/28D5x2oLN14KApv7F9mfDT/vUic0L3tRCsh3XWpWjtJisUQ== +"@types/jest@^29.2.2": + version "29.2.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.2.tgz#874e7dc6702fa6a3fe6107792aa98636dcc480b4" + integrity sha512-og1wAmdxKoS71K2ZwSVqWPX6OVn3ihZ6ZT2qvZvZQm90lJVDyXIjYcu4Khx2CNIeaFv12rOU/YObOsI3VOkzog== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -2648,10 +2702,10 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.25.0: - version "8.26.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.26.0.tgz#2bcc8836e6c424c4ac26a5674a70d44d84f2181d" - integrity sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg== +eslint@^8.27.0: + version "8.27.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.27.0.tgz#d547e2f7239994ad1faa4bb5d84e5d809db7cf64" + integrity sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ== dependencies: "@eslint/eslintrc" "^1.3.3" "@humanwhocodes/config-array" "^0.11.6" From b817eeb2bbee8b5e12b10b0f115b20aa2cb060bd Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 9 Nov 2022 12:17:56 +0100 Subject: [PATCH 078/218] Fixing build scripts (#41) --- Cargo.lock | 22 +- contracts/credit-manager/src/execute.rs | 2 +- .../src/vault/request_unlock.rs | 9 +- .../credit-manager/tests/test_deposit.rs | 5 +- .../tests/test_liquidate_coin.rs | 6 - packages/rover/src/coins.rs | 291 +++++++++++++++--- packages/rover/src/extensions/reply.rs | 12 - scripts/deploy/addresses/osmo-test-4.json | 12 +- scripts/deploy/base/deployer.ts | 11 +- scripts/deploy/base/index.ts | 16 +- scripts/deploy/base/rover.ts | 17 +- scripts/deploy/osmosis/config.ts | 19 +- scripts/deploy/osmosis/index.ts | 2 +- scripts/types/config.ts | 5 +- scripts/types/storageItems.ts | 2 +- 15 files changed, 319 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 175371a6f..1d95cca0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -644,9 +644,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", @@ -910,9 +910,9 @@ checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", @@ -1218,7 +1218,7 @@ dependencies = [ [[package]] name = "osmosis-std" version = "0.12.0" -source = "git+https://github.com/osmosis-labs/osmosis-rust#28ffd12efce20ee36bb78505e5af136b26344b70" +source = "git+https://github.com/osmosis-labs/osmosis-rust#9b010acf59d631cf099f2b45dbc011a4d99b37f8" dependencies = [ "chrono", "cosmwasm-std", @@ -1245,7 +1245,7 @@ dependencies = [ [[package]] name = "osmosis-std-derive" version = "0.12.0" -source = "git+https://github.com/osmosis-labs/osmosis-rust#28ffd12efce20ee36bb78505e5af136b26344b70" +source = "git+https://github.com/osmosis-labs/osmosis-rust#9b010acf59d631cf099f2b45dbc011a4d99b37f8" dependencies = [ "itertools", "proc-macro2", @@ -1256,7 +1256,7 @@ dependencies = [ [[package]] name = "osmosis-testing" version = "0.12.0" -source = "git+https://github.com/osmosis-labs/osmosis-rust#28ffd12efce20ee36bb78505e5af136b26344b70" +source = "git+https://github.com/osmosis-labs/osmosis-rust#9b010acf59d631cf099f2b45dbc011a4d99b37f8" dependencies = [ "base64", "bindgen", @@ -1403,9 +1403,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -1414,9 +1414,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "rfc6979" diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index fed8b244f..6b94b918c 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -166,7 +166,7 @@ pub fn dispatch_actions( let mut response = Response::new(); let mut callbacks: Vec = vec![]; - let mut received_coins = Coins::from(info.funds.as_slice()); + let mut received_coins = Coins::try_from(info.funds)?; for action in actions { match action { diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index 999ea2487..53a30930a 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Coin, DepsMut, Reply, Response, Uint128}; +use cosmwasm_std::{Addr, Coin, DepsMut, Reply, Response, Uint128}; use crate::state::VAULT_REQUEST_TEMP_STORAGE; use mars_rover::adapters::vault::{ @@ -14,6 +14,7 @@ use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; pub struct RequestTempStorage { pub account_id: String, pub amount: Uint128, + pub vault_addr: Addr, } pub fn request_vault_unlock( @@ -42,6 +43,7 @@ pub fn request_vault_unlock( &RequestTempStorage { account_id: account_id.to_string(), amount, + vault_addr: vault.address.clone(), }, )?; @@ -59,15 +61,14 @@ pub fn request_vault_unlock( pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResult { let storage = VAULT_REQUEST_TEMP_STORAGE.load(deps.storage)?; let unlock_event = reply.parse_unlock_event()?; - let vault_addr = deps.api.addr_validate(unlock_event.vault_addr.as_str())?; - let vault = VaultBase::new(vault_addr.clone()); + let vault = VaultBase::new(storage.vault_addr.clone()); let lockup = vault.query_lockup(&deps.querier, unlock_event.id)?; let info = vault.query_info(&deps.querier)?; update_vault_position( deps.storage, &storage.account_id, - &vault_addr, + &storage.vault_addr, VaultPositionUpdate::Unlocking(UnlockingChange::Add(VaultUnlockingPosition { id: lockup.id, coin: Coin { diff --git a/contracts/credit-manager/tests/test_deposit.rs b/contracts/credit-manager/tests/test_deposit.rs index d01084c4a..e9aa1a531 100644 --- a/contracts/credit-manager/tests/test_deposit.rs +++ b/contracts/credit-manager/tests/test_deposit.rs @@ -180,7 +180,10 @@ fn test_extra_funds_received() { &[coin(234, uosmo_info.denom), extra_funds.clone()], ); - assert_err(res, ExtraFundsReceived(Coins::from(vec![extra_funds]))); + assert_err( + res, + ExtraFundsReceived(Coins::try_from(vec![extra_funds]).unwrap()), + ); let res = mock.query_positions(&account_id); assert_eq!(res.coins.len(), 0); diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 8722a3a04..66b4cbe9d 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -687,11 +687,5 @@ fn test_debt_amount_no_adjustment() { assert_eq!(osmo_balance.amount, Uint128::new(232)); } -// TODO: After swap is implemented, attempt to liquidate with no deposited funds: -// - Borrow atom -// - Liquidate and collect osmo -// - Swap osmo for atom -// - Repay debt -// - Withdraw #[test] fn test_liquidate_with_no_deposited_funds() {} diff --git a/packages/rover/src/coins.rs b/packages/rover/src/coins.rs index a2beae73d..b206b83e5 100644 --- a/packages/rover/src/coins.rs +++ b/packages/rover/src/coins.rs @@ -1,57 +1,271 @@ -use std::collections::BTreeMap; +use std::any::type_name; +use std::collections::{BTreeMap, HashSet}; use std::fmt; +use std::str::FromStr; -use cosmwasm_schema::cw_serde; +use crate::traits::{Denoms, Stringify}; use cosmwasm_std::{Coin, StdError, StdResult, Uint128}; +use schemars::JsonSchema; +use serde::{de, Serialize}; -use crate::traits::{Denoms, Stringify}; +/// A collection of coins, similar to Cosmos SDK's `sdk.Coins` struct. +/// +/// Differently from `sdk.Coins`, which is a vector of `sdk.Coin`, here we implement Coins as a BTreeMap +/// that maps from coin denoms to amounts. This has a number of advantages: +/// +/// * coins are naturally sorted alphabetically by denom +/// * duplicate denoms are automatically removed +/// * cheaper for searching/inserting/deleting: O(log(n)) compared to O(n) +/// * compared to `Vec`, the map data structure stringifies to a compact JSON representation, +/// therefore is cheaper when writing to contract storage +/// +/// ## On the string representation of coins +/// +/// Two approaches are implemented for stringifing Coins: the JSON representation, and the plain text +/// representation. +/// +/// **The JSON representation** comes in the format below. This is used for contract storage or message +/// passing between contracts: +/// +/// ```json +/// {"uatom":"12345","umars":"42069","uosmo":"88888"} +/// ``` +/// +/// Use the `serde_json` library to convert Coins to/from JSON strings: +/// +/// ```rust +/// use cw_coins::Coins; +/// +/// let coins: Coins = serde_json::from_str(r#"{"uatom":"12345","uosmo":"42069"}"#).unwrap(); +/// let json = serde_json::to_string(&coins).unwrap(); +/// ``` +/// +/// The plain text representation is the same format as the `sdk.Coins.String` method uses. It is used +/// in event logging: +/// +/// ```plain +/// 12345uatom,42069umars,88888uosmo +/// ``` +/// +/// Use `{from,to}_string` methods to convert Coin to/from plain strings: +/// +/// ```rust +/// use std::str::FromStr; +/// use cw_coins::Coins; +/// +/// let coins = Coins::from_str("12345uatom,42069umars,88888uosmo").unwrap(); +/// let plain = coins.to_string(); +/// ``` +#[derive(Serialize, Clone, Default, Debug, PartialEq, Eq, JsonSchema)] +pub struct Coins(BTreeMap); + +// We implement a custom serde::de::Deserialize trait to handle the case where the JSON string contains +// duplicate keys, i.e. duplicate coin denoms. +// +// If we derive the trait, by default, it will not throw an error in such a case. Instead, it takes +// the amount that is seen the last. E.g. the following JSON string +// +// ```json +// { +// "uatom": "12345", +// "uatom", "23456", +// "uatom": "67890" +// } +// ``` +// +// will be deserialized into a Coins object with only one element, with denom `uatom` and amount 67890. +// The amount 67890 is seen the last and overwrites the two amounts seen earlier. +// +// This is NOT a desirable property. We want an error to be thown if the JSON string contain dups. +impl<'de> de::Deserialize<'de> for Coins { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = Coins; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a map with non-duplicating string keys and stringified 128-bit unsigned integer values") + } + + #[inline] + fn visit_map(self, mut access: M) -> Result + where + M: de::MapAccess<'de>, + { + let mut seen_denoms = HashSet::::new(); + let mut coins = BTreeMap::::new(); + + while let Some((denom, amount_str)) = access.next_entry::()? { + if seen_denoms.contains(&denom) { + return Err(de::Error::custom(format!( + "failed to parse into Coins! duplicate denom: {}", + denom + ))); + } + + let amount = Uint128::from_str(&amount_str).map_err(|_| { + de::Error::custom(format!( + "failed to parse into Coins! invalid amount: {}", + amount_str + )) + })?; + + if amount.is_zero() { + return Err(de::Error::custom(format!( + "amount for denom {} is zero", + denom + ))); + } -/// Pending integration into cosmwasm_std: https://github.com/CosmWasm/cosmwasm/issues/1377#issuecomment-1204232193 -/// Copying from here: https://github.com/mars-protocol/cw-coins/blob/main/src/lib.rs -#[cw_serde] -pub struct Coins(pub BTreeMap); + seen_denoms.insert(denom.clone()); + coins.insert(denom, amount); + } -impl From> for Coins { - fn from(coins: Vec) -> Self { - let map = coins + Ok(Coins(coins)) + } + } + + deserializer.deserialize_map(Visitor) + } +} + +impl TryFrom> for Coins { + type Error = StdError; + + fn try_from(vec: Vec) -> StdResult { + let vec_len = vec.len(); + let map = vec .into_iter() + .filter(|coin| !coin.amount.is_zero()) .map(|coin| (coin.denom, coin.amount)) - .collect(); - Self(map) + .collect::>(); + + // the map having a different length from the vec means the vec must either 1) contain + // duplicate denoms, or 2) contain zero amounts + if map.len() != vec_len { + return Err(StdError::parse_err( + type_name::(), + "duplicate denoms or zero amount", + )); + } + + Ok(Self(map)) } } -impl From<&[Coin]> for Coins { - fn from(coins: &[Coin]) -> Self { - coins.to_vec().into() +impl TryFrom<&[Coin]> for Coins { + type Error = StdError; + + fn try_from(slice: &[Coin]) -> StdResult { + slice.to_vec().try_into() } } -impl Stringify for &[Coin] { - fn to_string(&self) -> String { - self.iter() - .map(|coin| coin.clone().denom) - .collect::>() - .join(", ") +impl FromStr for Coins { + type Err = StdError; + + fn from_str(s: &str) -> StdResult { + // `cosmwasm_std::Coin` does not implement `FromStr`, so we have do it ourselves + // + // Parsing the string with regex doesn't work, because the resulting wasm binary would be + // too big from including the `regex` library. + // + // If the binary size is not a concern, here's an example: + // https://github.com/PFC-Validator/terra-rust/blob/v1.1.8/terra-rust-api/src/client/core_types.rs#L34-L55 + // + // We opt for the following solution: enumerate characters in the string, and break before + // the first non-number character. Split the string at that index. + // + // This assumes the denom never starts with a number, which is the case: + // https://github.com/cosmos/cosmos-sdk/blob/v0.46.0/types/coin.go#L854-L856 + let parse_coin_str = |s: &str| -> StdResult { + for (i, c) in s.chars().enumerate() { + if c.is_alphabetic() { + let amount = Uint128::from_str(&s[..i])?; + let denom = String::from(&s[i..]); + return Ok(Coin { amount, denom }); + } + } + + Err(StdError::parse_err( + type_name::(), + format!("invalid coin string: {s}"), + )) + }; + + s.split(',') + .into_iter() + .map(parse_coin_str) + .collect::>>()? + .try_into() } } -impl Denoms for Vec { - fn to_denoms(&self) -> Vec<&str> { - self.iter().map(|c| c.denom.as_str()).collect() +impl fmt::Display for Coins { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // NOTE: The `iter` method for BTreeMap returns an Iterator where entries are already sorted + // by key, so we don't need to sort the coins manually + let s = self + .0 + .iter() + .map(|(denom, amount)| format!("{amount}{denom}")) + .collect::>() + .join(","); + write!(f, "{s}") } } impl Coins { + /// Cast to Vec, while NOT consuming the original object + pub fn to_vec(&self) -> Vec { + self.0 + .iter() + .map(|(denom, amount)| Coin { + denom: denom.clone(), + amount: *amount, + }) + .collect() + } + + /// Cast to Vec, consuming the original object + pub fn into_vec(self) -> Vec { + self.0 + .into_iter() + .map(|(denom, amount)| Coin { denom, amount }) + .collect() + } + + pub fn len(&self) -> usize { + self.0.len() + } + pub fn is_empty(&self) -> bool { self.0.is_empty() } + pub fn denoms(&self) -> Vec { + self.0.keys().cloned().collect() + } + pub fn amount(&self, denom: &str) -> Option { self.0.get(denom).map(Clone::clone) } - pub fn deduct(&mut self, to_deduct: &Coin) -> StdResult<&mut Self> { + /// NOTE: the syntax can be simpler if Uint128 has an inplace add method... + pub fn add(&mut self, coin: &Coin) -> StdResult<()> { + let amount = self + .0 + .entry(coin.denom.clone()) + .or_insert_with(Uint128::zero); + *amount = amount.checked_add(coin.amount)?; + Ok(()) + } + + pub fn deduct(&mut self, to_deduct: &Coin) -> StdResult<()> { if let Some(amount) = self.amount(&to_deduct.denom) { let new_amount = amount.checked_sub(to_deduct.amount)?; if new_amount.is_zero() { @@ -59,7 +273,7 @@ impl Coins { } else { self.0.insert(to_deduct.denom.clone(), new_amount); } - Ok(self) + Ok(()) } else { Err(StdError::generic_err(format!( "not found in coin list: {}", @@ -69,18 +283,17 @@ impl Coins { } } -impl fmt::Display for Coins { - // TODO: For empty coins, this stringifies to am empty string, which may cause confusions. - // Should it stringify to a more informative string, such as `[]`? - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // NOTE: The `iter` method for BTreeMap returns an Iterator where entries are already sorted - // by key, so we don't need sort the coins manually - let s = self - .0 - .iter() - .map(|(denom, amount)| format!("{}{}", amount, denom)) - .collect::>() - .join(","); - write!(f, "{}", s) +impl Stringify for &[Coin] { + fn to_string(&self) -> String { + self.iter() + .map(|coin| coin.clone().denom) + .collect::>() + .join(", ") + } +} + +impl Denoms for Vec { + fn to_denoms(&self) -> Vec<&str> { + self.iter().map(|c| c.denom.as_str()).collect() } } diff --git a/packages/rover/src/extensions/reply.rs b/packages/rover/src/extensions/reply.rs index b77ab3bfb..031daf469 100644 --- a/packages/rover/src/extensions/reply.rs +++ b/packages/rover/src/extensions/reply.rs @@ -4,9 +4,6 @@ use cosmos_vault_standard::extensions::lockup::{ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Reply, StdError, StdResult, SubMsgResult}; -// https://github.com/CosmWasm/wasmd/blob/main/EVENTS.md#standard-events-in-xwasm -const CONTRACT_ADDR_KEY: &str = "_contract_addr"; - #[cw_serde] pub struct AssetTransferMsg { pub recipient: String, @@ -17,7 +14,6 @@ pub struct AssetTransferMsg { #[cw_serde] pub struct UnlockEvent { pub id: u64, - pub vault_addr: String, } pub trait AttrParse { @@ -44,18 +40,10 @@ impl AttrParse for Reply { .ok_or_else(|| StdError::generic_err("No id attribute"))? .value; - let contract_addr = &unlock_event - .attributes - .iter() - .find(|x| x.key == CONTRACT_ADDR_KEY) - .ok_or_else(|| StdError::generic_err("No contract attribute"))? - .value; - Ok(UnlockEvent { id: id .parse::() .map_err(|_| StdError::generic_err("Could not parse id from reply"))?, - vault_addr: contract_addr.to_string(), }) } } diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 0c8bd4768..ef290e765 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,8 +1,8 @@ { - "accountNft": "osmo1jjxjzahlpnsrl96wsejepqmpqpz5ugr379kzmpfkdeff3s2r7wnqxxncmq", - "mockVault": "osmo1gyjqum3fc6jqxhn8m5tcdg3j8yhjqq34764f65yjn496zzazt4hq2gccz5", - "marsOracleAdapter": "osmo1s568lnjldxrvpr420qp70plfyvl4c7xw0s4plxkff6607qmd6rgsng7ux9", - "swapper": "osmo1p2lnkpgezme7xduz4f76l2t0rtnn68dpxvkf7g4l68nmna2ljh8qe3qfuh", - "mockZapper": "osmo1dthjy47aqa3nn67dwa8pqkpuqxucxs8aw437x4ckvgthe4dp67dsje57ag", - "creditManager": "osmo1pln365zkklx3vzyts25fqc7ypmn6qfx2fspvgvlpyaefxgtr96vqyj4apl" + "accountNft": "osmo1xvne7u9svgy9vtqtqnaet4nvn8zcpp984zzrlezfzgk4798tps8srkf5wa", + "mockVault": "osmo1yqgjaehalz0pv5j22fdnaaekuprlggd7hth8m66jmdxe58ztqs4sjqtrlk", + "marsOracleAdapter": "osmo1tlad2hj9rm7az7atx2qq8pdpl2007hrhpzua42j8wgxr0kc0ct4sahuyh7", + "swapper": "osmo15kxcpvjaqlrj8ezecnghf2qs2x87veqx0fcemye0jpdr8jq7qkvsnyvuuf", + "mockZapper": "osmo1axad429tgnvzvfax08s4ytmf7ndg0f9z4jy355zyh4m6nasgtnzs5aw8u7", + "creditManager": "osmo1krz37p6xkkyu0f240enyt4ccxk7ds69kfgc5pnldsmpmmuvn3vpsnmpjaf" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 11efa8b26..717d2fc88 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -76,9 +76,10 @@ export class Deployer { async instantiateMockVault() { const msg: VaultInstantiateMsg = { - base_token_denom: this.config.baseDenom, + base_token_denom: this.config.lpToken.denom, oracle: this.config.oracleAddr, vault_token_denom: this.config.vaultTokenDenom, + lockup: this.config.vaultLockup, } await this.instantiate('mockVault', this.storage.codeIds.mockVault!, msg) @@ -117,10 +118,7 @@ export class Deployer { } await this.instantiate('swapper', this.storage.codeIds.swapper!, msg) - if (!this.storage.actions.setRouteAndSeedSwapper) { - printBlue(`Seeding swapper w/ ${this.config.baseDenom}`) - await this.transferCoin(this.storage.addresses.swapper!, coin(100, this.config.baseDenom)) - + if (!this.storage.actions.setRoute) { const swapClient = new MarsSwapperBaseClient( this.cwClient, this.deployerAddr, @@ -132,6 +130,7 @@ export class Deployer { await swapClient.setRoute({ denomIn: this.config.baseDenom, denomOut: this.config.secondaryDenom, + // @ts-expect-error ts-codegen incorrectly parses an array as an object route: this.config.swapRoute, }) @@ -141,7 +140,7 @@ export class Deployer { ) const routes = await swapQuery.routes({}) assert.equal(routes.length, 1) - this.storage.actions.setRouteAndSeedSwapper = true + this.storage.actions.setRoute = true } else { printGray('Swap contract already seeded with funds') } diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 3f9106d45..7312705b6 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -12,12 +12,12 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp const deployer = await setupDeployer(config) try { // Upload contracts - await deployer.upload('accountNft', wasmFile('account_nft')) - await deployer.upload('mockVault', wasmFile('mock_vault')) + await deployer.upload('accountNft', wasmFile('mars_account_nft')) + await deployer.upload('mockVault', wasmFile('mars_mock_vault')) await deployer.upload('marsOracleAdapter', wasmFile('mars_oracle_adapter')) await deployer.upload('swapper', wasmFile(swapperContractName)) - await deployer.upload('mockZapper', wasmFile('mock_zapper')) - await deployer.upload('creditManager', wasmFile('credit_manager')) + await deployer.upload('mockZapper', wasmFile('mars_mock_zapper')) + await deployer.upload('creditManager', wasmFile('mars_credit_manager')) // Instantiate contracts await deployer.instantiateNftContract() @@ -39,13 +39,9 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp await rover.deposit() await rover.borrow() await rover.repay() - // TODO: Osmosis-bindings need updating - // await rover.swap() + await rover.swap() await rover.withdraw() - await rover.zap() - await rover.unzap() - await rover.vaultDeposit() if (config.vaultType === VaultType.UNLOCKED) { await rover.vaultWithdraw() @@ -53,6 +49,8 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp await rover.vaultRequestUnlock() } + await rover.refundAllBalances() + printYellow('COMPLETE') } catch (e) { printRed(e) diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index abe8cbe72..6fe5416eb 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -160,7 +160,7 @@ export class Rover { { enter_vault: { amount: this.config.vaultDepositAmount.toString(), - denom: this.config.baseDenom, + denom: this.config.lpToken.denom, vault: { address: this.storage.addresses.mockVault! }, }, }, @@ -178,8 +178,8 @@ export class Rover { printGreen( `Deposited ${this.config.vaultDepositAmount} ${ - this.config.baseDenom - } in exchange for vault tokens: ${JSON.stringify(positions.vaults[0])}`, + this.config.lpToken.denom + } in exchange for vault tokens: ${JSON.stringify(positions.vaults[0].amount)}`, ) } @@ -219,10 +219,19 @@ export class Rover { printGreen( `Requested unlock: ID #${newBalance.unlocking[0].id}, amount: ${ newBalance.unlocking[0].coin.amount - } in exchange for: ${oldBalance.locked - newBalance.locked} ${this.config.vaultTokenDenom}`, + } ${newBalance.unlocking[0].coin.denom} in exchange for: ${ + oldBalance.locked - newBalance.locked + } ${this.config.vaultTokenDenom}`, ) } + async refundAllBalances() { + await this.updateCreditAccount([{ refund_all_coin_balances: {} }]) + const positions = await this.query.positions({ accountId: this.accountId! }) + assert.equal(positions.coins.length, 0) + printGreen(`Withdrew all balances back to wallet`) + } + private async getAccountBalance(denom: string) { const positions = await this.query.positions({ accountId: this.accountId! }) const coin = positions.coins.find((c) => c.denom === denom) diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 524f61a4c..8ca3a4c63 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -20,6 +20,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { defaultGasPrice: 0.1, startingAmountForTestUser: 2e6, vaultTokenDenom: udig, + vaultLockup: { time: 86400 }, // 1 day maxCloseFactor: 0.6, maxLiquidationBonus: 0.05, depositAmount: 100, @@ -28,23 +29,21 @@ export const osmosisTestnetConfig: DeploymentConfig = { { denom: uatom, amount: '100000000000' }, ], borrowAmount: 10, - repayAmount: 3, + repayAmount: 8, swapAmount: 12, - swapRoute: { - steps: [ - { - denom_out: uatom, - pool_id: 1, - }, - ], - }, + swapRoute: [ + { + token_out_denom: uatom, + pool_id: '1', + }, + ], slippage: 0.4, withdrawAmount: 12, vaultDepositAmount: 10, vaultDepositCap: { denom: 'uosmo', amount: '100000000000' }, vaultMaxLTV: 0.65, vaultLiquidationThreshold: 0.75, - vaultType: VaultType.UNLOCKED, + vaultType: VaultType.LOCKED, vaultWithdrawAmount: 1_000_000, lpToken: { denom: ucro, price: 3 }, zap: [ diff --git a/scripts/deploy/osmosis/index.ts b/scripts/deploy/osmosis/index.ts index d7943516b..4946bdec0 100644 --- a/scripts/deploy/osmosis/index.ts +++ b/scripts/deploy/osmosis/index.ts @@ -2,5 +2,5 @@ import { taskRunner } from '../base' import { osmosisTestnetConfig } from './config' void (async function () { - await taskRunner({ config: osmosisTestnetConfig, swapperContractName: 'swapper_osmosis' }) + await taskRunner({ config: osmosisTestnetConfig, swapperContractName: 'mars_swapper_osmosis' }) })() diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 1f9e5e6d2..ca741ca05 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -1,3 +1,5 @@ +import { Duration } from './generated/mars-mock-vault/MarsMockVault.types' + export enum VaultType { LOCKED, UNLOCKED, @@ -13,6 +15,7 @@ export interface DeploymentConfig { deployerMnemonic: string redBankDeployerMnemonic: string vaultTokenDenom: string + vaultLockup?: Duration chainId: string defaultGasPrice: number startingAmountForTestUser: number @@ -25,7 +28,7 @@ export interface DeploymentConfig { repayAmount: number swapAmount: number slippage: number - swapRoute: { steps: { denom_out: string; pool_id: number }[] } + swapRoute: { token_out_denom: string; pool_id: string }[] withdrawAmount: number maxCloseFactor: number maxLiquidationBonus: number diff --git a/scripts/types/storageItems.ts b/scripts/types/storageItems.ts index a53cff574..6201c1934 100644 --- a/scripts/types/storageItems.ts +++ b/scripts/types/storageItems.ts @@ -20,7 +20,7 @@ export interface StorageItems { actions: { proposedNewOwner?: boolean acceptedOwnership?: boolean - setRouteAndSeedSwapper?: boolean + setRoute?: boolean seedMockVault?: boolean seedMockZapper?: boolean grantedCreditLines?: boolean From e093b275916aacd1bbafc7713da65a9c7093c7f5 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 11 Nov 2022 10:02:32 +0100 Subject: [PATCH 079/218] Pre-audit tweaks (#42) * pre-audit updates * review updates --- Cargo.lock | 35 +- Cargo.toml | 8 +- README.md | 318 +++++------------- contracts/credit-manager/Cargo.toml | 26 +- .../credit-manager/src/vault/exit_unlocked.rs | 6 +- .../src/vault/request_unlock.rs | 6 +- .../credit-manager/tests/helpers/mock_env.rs | 31 +- .../tests/test_vault_request_unlock.rs | 4 +- contracts/mock-vault/Cargo.toml | 14 +- contracts/mock-vault/examples/schema.rs | 2 +- contracts/mock-vault/src/contract.rs | 19 +- contracts/mock-vault/src/msg.rs | 2 +- contracts/mock-vault/src/query.rs | 23 +- contracts/mock-vault/src/state.rs | 4 +- contracts/mock-vault/src/unlock.rs | 24 +- packages/chains/osmosis/README.md | 2 - packages/rover/Cargo.toml | 24 +- packages/rover/README.md | 5 - packages/rover/src/adapters/vault/base.rs | 63 ++-- packages/rover/src/extensions/reply.rs | 6 +- schemas/mars-mock-vault/mars-mock-vault.json | 85 +++-- .../mars-mock-vault/MarsMockVault.client.ts | 12 +- .../MarsMockVault.message-composer.ts | 4 +- .../MarsMockVault.react-query.ts | 17 +- .../mars-mock-vault/MarsMockVault.types.ts | 8 +- 25 files changed, 319 insertions(+), 429 deletions(-) delete mode 100644 packages/chains/osmosis/README.md delete mode 100644 packages/rover/README.md diff --git a/Cargo.lock b/Cargo.lock index 1d95cca0b..f50cff4a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,18 +243,6 @@ dependencies = [ "tendermint-proto", ] -[[package]] -name = "cosmos-vault-standard" -version = "0.1.0" -source = "git+https://github.com/apollodao/cosmos-vault-standard#5713cb127f92b26a6b6b6ecd85bc10ed49d94066" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils 0.16.0", - "schemars", - "serde", -] - [[package]] name = "cosmrs" version = "0.9.0" @@ -350,6 +338,19 @@ dependencies = [ "serde", ] +[[package]] +name = "cosmwasm-vault-standard" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48847f2c22b96d9659ff9d0b67403ed1d5cbe5470e65d293c4c455de05b237b" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.16.0", + "schemars", + "serde", +] + [[package]] name = "cpufeatures" version = "0.2.5" @@ -946,9 +947,9 @@ name = "mars-credit-manager" version = "1.0.0" dependencies = [ "anyhow", - "cosmos-vault-standard", "cosmwasm-schema", "cosmwasm-std", + "cosmwasm-vault-standard", "cw-item-set", "cw-multi-test", "cw-storage-plus 0.16.0", @@ -1001,9 +1002,9 @@ dependencies = [ name = "mars-mock-vault" version = "1.0.0" dependencies = [ - "cosmos-vault-standard", "cosmwasm-schema", "cosmwasm-std", + "cosmwasm-vault-standard", "cw-storage-plus 0.16.0", "cw-utils 0.16.0", "mars-rover", @@ -1062,9 +1063,9 @@ dependencies = [ name = "mars-rover" version = "1.0.0" dependencies = [ - "cosmos-vault-standard", "cosmwasm-schema", "cosmwasm-std", + "cosmwasm-vault-standard", "cw-storage-plus 0.16.0", "cw-utils 0.16.0", "mars-health", @@ -1195,9 +1196,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "os_str_bytes" -version = "6.3.1" +version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" +checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e" [[package]] name = "osmosis-std" diff --git a/Cargo.toml b/Cargo.toml index 2ae07846a..96f71be2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,10 +46,10 @@ serde = { version = "1.0", default-features = false, features = ["deri thiserror = "1.0" # packages -cosmos-vault-standard = { git = "https://github.com/apollodao/cosmos-vault-standard", features = ["lockup", "force-unlock"] } -mars-health = { version = "1.0.0", path = "./packages/health" } -mars-outpost = { version = "1.0.0", path = "./packages/outpost" } -mars-rover = { version = "1.0.0", path = "./packages/rover" } +cosmwasm-vault-standard = { version = "0.1.0", features = ["lockup", "force-unlock"] } +mars-health = { version = "1.0.0", path = "./packages/health" } +mars-outpost = { version = "1.0.0", path = "./packages/outpost" } +mars-rover = { version = "1.0.0", path = "./packages/rover" } # contracts mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } diff --git a/README.md b/README.md index 99b07a91a..c5889a2f4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # Rover A generalized credit protocol built on Mars lending market -## Bug bounty - ## Overview DeFi lending protocols, such as Aave and Compound, typically require users to first deposit some collateral assets before they can borrow. Once deposited, this collateral is locked inside the lending market smart contracts; the users are not allowed to put them into productive use as they see fit. @@ -16,218 +14,74 @@ The target audience of the credit manager is risk-seeking investors who wish to To start, a user first needs to access the Mars credit manager contract and request the opening of a credit account. The credit account is analogous to a "sub-account" on centralized trading platforms such as FTX, and is represented by a non-fungible token (NFT). -Users interact with their credit accounts by executing actions. In Rust/[CosmWasm](https://cosmwasm.com/) code, this can be expressed as (using the [cw-asset](https://github.com/mars-protocol/cw-asset) library): - ```rust -use cosmwasm_std::Uint128; -use cw_asset::{Asset, AssetList, AssetInfo}; - -enum Action { - /// deposit the specified asset into the credit account - Deposit(Asset), - /// withdraw the specified asset from the credit account - Withdraw(Asset), - /// borrow the specified asset from Red Bank - Borrow(Asset), - /// repay the specified asset to Red Bank - Repay(Asset), - /// swap the asset in the credit account - Swap { - offer: Asset, - ask: AssetInfo, - minimum_receive: Option, - }, - /// deposit assets into a vault (e.g. an automated - /// yield farming strategy) - EnterVault { - vault_addr: String, - deposits: AssetList, - }, - /// withdraw assets from a vault - ExitVault { - vault_addr: String, - shares: Uint128, - }, -} - -enum ExecuteMsg { - /// mint a new credit account NFT - CreateCreditAccount { - initial_deposits: AssetList, - }, - /// update a credit account specified by the token id - /// by executing an array of actions +pub enum ExecuteMsg { + /// Create a new account + CreateCreditAccount {}, + /// Take actions on account UpdateCreditAccount { - token_id: String, + account_id: String, actions: Vec, }, } - ``` -The credit manager contract executes the list of actions specified by `ExecuteMsg::UpdateCreditAccount { actions }` in order. *After all actions have been executed, it calculates the overall health factor of the credit account. If the account is unhealthy as a result of the actions, an error is thrown and all actions reverted.* - -Some readers might have noticed that this design resembles [the Fields of Mars contract](https://github.com/mars-protocol/fields-of-mars/blob/v1.0.0/packages/fields-of-mars/src/martian_field.rs#L264-L318) in Mars V1. Indeed, the credit account can be considered a direct extension of Fields, of which many code components can be reused. - -### Deposit and borrowing - - -In the example below, the user funds a freshly-opened credit account with 50 USDC, and borrows 100 USDC from Mars liquidity pool. To do this, provide the following execute message: - -```json -{ - "update_credit_account": { - "token_id": "...", - "actions": [ - { - "deposit": { - "info": { - "native": "uusdc" - }, - "amount": "50000000" - } - }, - { - "borrow": { - "info": { - "native": "uusdc" - }, - "amount": "100000000" - } - } - ] - } -} - -``` - -The actions results in the following credit account position: - -[![Screen Shot 2022-06-24 at 12.43.09 AM](https://aws1.discourse-cdn.com/standard17/uploads/mars/optimized/1X/c12fca3fae645092b9deba67c9398fe0795b0c38_2_690x280.png) - -This example highlights a few characteristics of the credit account: - -1. The credit manager does not need to explicitly deposit collateral into Red Bank before borrowing from it. In other words, the loan is extended to the credit manager in the form of uncollateralized debt. (*NOTE: Martian Council must have approved an uncollateralized limit*) -2. The user borrows more assets ($100) than their deposit ($50). To our knowledge, this is not possible with other lending protocols. Despite this, the credit account remains over-collateralized ($150 in assets vs $100 in liabilities). -3. A production-ready credit manager contract will probably need to use a more sophisticated algorithm to assess the health of credit accounts, which takes into consideration various risk factors such as the volatility and market liquidity of each supported asset. In this article, we use LTV for simplicity sake. - -### Leveraged trading - -Credit accounts support trading of assets. Continuing from the previous example, the user may provide the following execute message, which swaps 50 USDC for OSMO (assuming OSMO price is ~$2): - -```json -{ - "update_credit_account": { - "token_id": "...", - "actions": [ - { - "swap": { - "offer": { - "info": { - "native": "uusdc" - }, - "amount": "50000000" - }, - "ask": { - "native": "uosmo" - }, - "minimum_receive": "25000000" - } - } - ] - } -} - -``` - -Which results in the following credit account position: - -[![Screen Shot 2022-06-24 at 12.43.17 AM](https://aws1.discourse-cdn.com/standard17/uploads/mars/optimized/1X/fee50699046039aa809df51489d5c0d4b5000636_2_690x285.png) - -Although the trade takes place on a spot exchange, since it is (partially) funded by borrowed assets from Red Bank, the user here effectively takes a leveraged long position on OSMO. - -### Vaults - -The credit manager may support vaults created by third party protocols, which provide automated trading or yield farming strategies, as long as these vaults are approved by Martian Council (governance), and implement a standard API. - -The API includes execute functions for entering or exiting, and query functions for assessing the asset value locked in the vault. The execute functions also must emit events in a specific format (out of scope for this article) so that they can be parsed by the credit manager. +Users interact with their credit accounts by executing the following actions: ```rust -/// each vault contract must implement this -enum VaultExecuteMsg { - Enter { - deposits: AssetList, +pub enum Action { + Deposit(Coin), + Withdraw(Coin), + Borrow(Coin), + Repay(Coin), + EnterVault { + vault: VaultUnchecked, + denom: String, + amount: Option, }, - Exit { - shares: Uint128, + ExitVault { + vault: VaultUnchecked, + amount: Uint128, }, -} - -/// each vault contract must implement this -enum VaultQueryMsg { - /// the amount of assets under management of the vault that - /// belongs to a specific user; response type: `cw_asset::AssetList` - Deposit { - user: String, + RequestVaultUnlock { + vault: VaultUnchecked, + amount: Uint128, }, + ExitVaultUnlocked { id: u64, vault: VaultUnchecked }, + LiquidateCoin { + liquidatee_account_id: String, + debt_coin: Coin, + request_coin_denom: String, + }, + LiquidateVault { + liquidatee_account_id: String, + debt_coin: Coin, + request_vault: VaultUnchecked, + }, + SwapExactIn { + coin_in: Coin, + denom_out: String, + slippage: Decimal, + }, + ProvideLiquidity { + coins_in: Vec, + lp_token_out: String, + minimum_receive: Uint128, + }, + WithdrawLiquidity { lp_token: Coin }, + RefundAllCoinBalances {}, } - -``` - -For example, assume a vault that takes OSMO and USDC deposits, provides them to an Osmosis DEX pool, stakes the LP share tokens, and auto-compounds staking rewards. To enter this vault, the user executes: - -```json -{ - "update_credit_account": { - "token_id": "...", - "actions": [ - { - "enter_vault": { - "vault_addr": "...", - "deposits": [ - { - "info": { - "native": "uosmo" - }, - "amount": "25000000" - }, - { - "info": { - "native": "uusdc" - }, - "amount": "50000000" - } - ] - } - } - ] - } -} - ``` -Which results in: - -[![Screen Shot 2022-06-24 at 12.43.24 AM](https://aws1.discourse-cdn.com/standard17/uploads/mars/optimized/1X/30e208b40a0812560bb2606260efb94035517f42_2_690x286.png) - -*NOTE: It is possible to execute the two previous steps (swap and enter vault) in one transaction, improving user experience.* - -### Liquidations - -To ensure solvency (i.e. that the protocol always has more assets than liabilities), the credit manager must monitor the health factor of each credit account, and execute liquidations when necessary. - -A credit account's health factor may drop due to: - -- the value of assets going down (e.g. prices dropping) -- the value of liabilities going up (e.g. borrow interest accrues, or prices of the debt assets going up) - -Once the health factor drops below a preset threshold (liquidation threshold, a governance-decided parameter), any person can trigger a liquidation of the credit account. The liquidator must pay back some amounts of debts on behalf of the credit account; they will in return be rewarded some of the account's assets as the liquidation bonus: +The credit manager contract executes the list of actions specified by `ExecuteMsg::UpdateCreditAccount { actions }` in order. *After all actions have been executed, it calculates the overall health factor of the credit account. If the account is unhealthy as a result of the actions, an error is thrown and all actions reverted.* -[![Screen Shot 2022-06-24 at 12.46.36 AM](https://aws1.discourse-cdn.com/standard17/uploads/mars/optimized/1X/a5c0da751deceb28652bbd82127c41ee2c01d507_2_493x500.png) +You may have noticed that this design resembles [the Fields of Mars contract](https://github.com/mars-protocol/fields-of-mars/blob/v1.0.0/packages/fields-of-mars/src/martian_field.rs#L264-L318) in Mars V1. Indeed, Credit Manager can be considered a direct extension of Fields. -The liquidator may then sell the vault shares in the secondary market. +### Vault API -In this example, the user account's net value falls from $20 ($120 in assets minus $100 in liabilities) to $15 (losing $5), while the liquidator's account net value increases by $5. The transfer of this $5 is the liquidation bonus, rewarded to the liquidator for deploying capital to ensure the protocol's solvency. The maximum allowed amount of bonus for each liquidation event, the bonus rate, can either be set by governance (i.e. the approach used by Aave) or by [free market mechanisms 2](https://twitter.com/larry0x/status/1538515908049747971) ([Euler](https://twitter.com/euler_mab/status/1537091423748517889)). +Vault writers interested in integrating with Rover must write their vaults +to abide by the [Cosmos Vault Standard](https://github.com/apollodao/cosmos-vault-standard) and +make a governance proposal. ### Additional thoughts @@ -237,68 +91,60 @@ A generalized credit protocol would enable leveraged trading or yield farming ca ### Environment Setup -#### ==== For building/testing contracts in Rust === - -[Install rustup](https://rustup.rs/). Once installed, make sure you have the wasm32 target: +- [Install rustup](https://rustup.rs/). Once installed, make sure you have the wasm32 target: ```shell rustup default stable -cargo version -# If this is lower than 1.55.0+, update rustup update stable -rustup target list --installed rustup target add wasm32-unknown-unknown ``` -Run `cargo build` and you're good to go! +- Install [cargo make](https://github.com/sagiegurari/cargo-make) -#### ==== For end-to-end tests and deployment scripts === +```shell +cargo install --force cargo-make +``` - - Dependencies - - [Docker](https://docs.docker.com/get-docker/) - - [Node.js v16](https://github.com/nvm-sh/nvm) - - [LocalOsmosis](https://docs.osmosis.zone/developing/dapps/get_started/cosmwasm-localosmosis.html#initial-setup) - - Intel/Amd 64-bit processor - - while there is experimental Arm support for CosmWasm/rust-optimizer, it's discouraged to use in production and LocalOsmosis is likely to have issues. +- Install [Docker](https://docs.docker.com/get-docker/) -### Test +- Install [Node.js v16](https://github.com/nvm-sh/nvm) -For contract tests in rust -```shell -cargo test -``` +- Install [Yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable) -For end-to-end tests via Typescript. Start LocalOsmosis: +- Also note that Intel/Amd 64-bit processor is required. While there is experimental ARM support for CosmWasm/rust-optimizer, it's discouraged to use in production and LocalOsmosis is likely to have issues. + +### Build + +Pull down Rover repo locally ```shell -cd LocalOsmosis -make start +git clone https://github.com/mars-protocol/rover +cd rover ``` -In another shell, compile contracts +Run `cargo build` to ensure it compiles fine. + + +### Test +Requires building the wasm binaries for the contracts: ```shell -# In rover directory at the top level docker run --rm -v "$(pwd)":/code \ ---mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ ---mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ -cosmwasm/workspace-optimizer:0.12.6 + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.12.9 ``` -Run test scripts +For Rust cw-multi tests + osmosis-testing suite (requires mars_swapper_osmosis.wasm from previous step): +```shell +cargo test +``` +For Typescript end-to-end testnet deployment & tests against that deployment: ```shell cd scripts -npm install -npm run test +yarn install +yarn deploy:osmosis ``` -### Deploy - -### Notes - -## Deployment - -### Mainnet - -### Testnet +### Deployment -## License +Addresses published in [/scripts/deploy/addresses](https://github.com/mars-protocol/rover/tree/master/scripts/deploy/addresses) diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 6da3c1ca7..d81bf381c 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -18,19 +18,19 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmos-vault-standard = { workspace = true } -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -cw721 = { workspace = true } -cw721-base = { workspace = true } -cw-item-set = { workspace = true } -cw-storage-plus = { workspace = true } -mars-account-nft = { workspace = true } -mars-health = { workspace = true } -mars-outpost = { workspace = true } -mars-oracle-adapter = { workspace = true } -mars-rover = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cosmwasm-vault-standard = { workspace = true } +cw2 = { workspace = true } +cw721 = { workspace = true } +cw721-base = { workspace = true } +cw-item-set = { workspace = true } +cw-storage-plus = { workspace = true } +mars-account-nft = { workspace = true } +mars-health = { workspace = true } +mars-outpost = { workspace = true } +mars-oracle-adapter = { workspace = true } +mars-rover = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/credit-manager/src/vault/exit_unlocked.rs b/contracts/credit-manager/src/vault/exit_unlocked.rs index 4db7ca143..3bbb3d912 100644 --- a/contracts/credit-manager/src/vault/exit_unlocked.rs +++ b/contracts/credit-manager/src/vault/exit_unlocked.rs @@ -1,6 +1,5 @@ -use cosmos_vault_standard::extensions::lockup::Lockup; use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, WasmMsg}; - +use cosmwasm_vault_standard::extensions::lockup::UnlockingPosition; use mars_rover::adapters::vault::{UnlockingChange, Vault, VaultPositionUpdate}; use mars_rover::error::{ContractError, ContractResult}; use mars_rover::msg::execute::CallbackMsg; @@ -24,7 +23,8 @@ pub fn exit_vault_unlocked( let matching_unlock = vault_position .get_unlocking_position(position_id) .ok_or_else(|| ContractError::NoPositionMatch(position_id.to_string()))?; - let Lockup { release_at, .. } = vault.query_lockup(&deps.querier, matching_unlock.id)?; + let UnlockingPosition { release_at, .. } = + vault.query_unlocking_position(&deps.querier, matching_unlock.id)?; if !release_at.is_expired(&env.block) { return Err(ContractError::UnlockNotReady {}); } diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index 53a30930a..0b4e4db67 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -62,7 +62,7 @@ pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResul let storage = VAULT_REQUEST_TEMP_STORAGE.load(deps.storage)?; let unlock_event = reply.parse_unlock_event()?; let vault = VaultBase::new(storage.vault_addr.clone()); - let lockup = vault.query_lockup(&deps.querier, unlock_event.id)?; + let unlocking_position = vault.query_unlocking_position(&deps.querier, unlock_event.id)?; let info = vault.query_info(&deps.querier)?; update_vault_position( @@ -70,10 +70,10 @@ pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResul &storage.account_id, &storage.vault_addr, VaultPositionUpdate::Unlocking(UnlockingChange::Add(VaultUnlockingPosition { - id: lockup.id, + id: unlocking_position.id, coin: Coin { denom: info.base_token, - amount: lockup.base_token_amount, + amount: unlocking_position.base_token_amount, }, })), )?; diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 25480fd05..b90f9f59d 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -1,12 +1,11 @@ use std::mem::take; use anyhow::Result as AnyResult; -use cosmos_vault_standard::extensions::lockup::Lockup; -use cosmos_vault_standard::extensions::lockup::LockupQueryMsg::Lockups; -use cosmos_vault_standard::msg::QueryMsg::{Info as VaultInfoMsg, VaultExtension}; -use cosmos_vault_standard::msg::{ExtensionQueryMsg, VaultInfo}; use cosmwasm_std::testing::MockApi; use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; +use cosmwasm_vault_standard::extensions::lockup::{LockupQueryMsg, UnlockingPosition}; +use cosmwasm_vault_standard::msg::VaultStandardQueryMsg::{Info as VaultInfoMsg, VaultExtension}; +use cosmwasm_vault_standard::msg::{ExtensionQueryMsg, VaultInfoResponse}; use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; @@ -348,24 +347,30 @@ impl MockEnv { .unwrap() } - pub fn query_lockup(&self, vault: &VaultUnchecked, id: u64) -> Lockup { + pub fn query_unlocking_position(&self, vault: &VaultUnchecked, id: u64) -> UnlockingPosition { vault .check(&MockApi::default()) .unwrap() - .query_lockup(&self.app.wrap(), id) + .query_unlocking_position(&self.app.wrap(), id) .unwrap() } - pub fn query_lockups(&self, vault: &VaultUnchecked, addr: &Addr) -> Vec { + pub fn query_unlocking_positions( + &self, + vault: &VaultUnchecked, + addr: &Addr, + ) -> Vec { self.app .wrap() .query_wasm_smart( vault.address.to_string(), - &VaultExtension(ExtensionQueryMsg::Lockup(Lockups { - owner: addr.to_string(), - start_after: None, - limit: None, - })), + &VaultExtension(ExtensionQueryMsg::Lockup( + LockupQueryMsg::UnlockingPositions { + owner: addr.to_string(), + start_after: None, + limit: None, + }, + )), ) .unwrap() } @@ -609,7 +614,7 @@ impl MockEnvBuilder { vaults .into_iter() .map(|config| { - let info: VaultInfo = self + let info: VaultInfoResponse = self .app .wrap() .query_wasm_smart(config.vault.address.clone(), &VaultInfoMsg:: {}) diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs index 444530ddd..0d22ab871 100644 --- a/contracts/credit-manager/tests/test_vault_request_unlock.rs +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -246,7 +246,7 @@ fn test_request_unlocked() { Duration::Height(_) => panic!("wrong type of duration"), Duration::Time(s) => { let expected_unlock_time = mock.app.block_info().time.seconds() + s; - let unlocking_position = mock.query_lockup(&vault, first.id); + let unlocking_position = mock.query_unlocking_position(&vault, first.id); match unlocking_position.release_at { Expiration::AtTime(t) => { @@ -256,7 +256,7 @@ fn test_request_unlocked() { } // Assert Rover's position w/ Vault - let res = mock.query_lockups(&vault, &mock.rover); + let res = mock.query_unlocking_positions(&vault, &mock.rover); match res.first().unwrap().release_at { Expiration::AtTime(t) => { diff --git a/contracts/mock-vault/Cargo.toml b/contracts/mock-vault/Cargo.toml index bb802cbf3..0d6b07c67 100644 --- a/contracts/mock-vault/Cargo.toml +++ b/contracts/mock-vault/Cargo.toml @@ -18,10 +18,10 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmos-vault-standard = { workspace = true } -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -mars-rover = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cosmwasm-vault-standard = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +mars-rover = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/mock-vault/examples/schema.rs b/contracts/mock-vault/examples/schema.rs index f2526f54d..4a0e8f245 100644 --- a/contracts/mock-vault/examples/schema.rs +++ b/contracts/mock-vault/examples/schema.rs @@ -1,6 +1,6 @@ -use cosmos_vault_standard::msg::{ExecuteMsg, QueryMsg}; use cosmwasm_schema::write_api; use mars_mock_vault::msg::InstantiateMsg; +use mars_rover::adapters::vault::{ExecuteMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index 948e21693..c9239eea1 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -1,17 +1,18 @@ -use cosmos_vault_standard::extensions::force_unlock::ForceUnlockExecuteMsg; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{coin, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; +use cosmwasm_vault_standard::extensions::force_unlock::ForceUnlockExecuteMsg; +use cosmwasm_vault_standard::extensions::lockup::{LockupExecuteMsg, LockupQueryMsg}; +use cosmwasm_vault_standard::msg::{ExtensionExecuteMsg, ExtensionQueryMsg}; -use cosmos_vault_standard::extensions::lockup::{LockupExecuteMsg, LockupQueryMsg}; -use cosmos_vault_standard::msg::{ExecuteMsg, ExtensionExecuteMsg, ExtensionQueryMsg, QueryMsg}; +use mars_rover::adapters::vault::{ExecuteMsg, QueryMsg}; use crate::deposit::deposit; use crate::error::ContractResult; use crate::msg::InstantiateMsg; use crate::query::{ - query_lockup, query_lockup_duration, query_lockups, query_vault_info, query_vault_token_supply, - shares_to_base_denom_amount, + query_lockup_duration, query_unlocking_position, query_unlocking_positions, query_vault_info, + query_vault_token_supply, shares_to_base_denom_amount, }; use crate::state::{ CHAIN_BANK, COIN_BALANCE, LOCKUP_TIME, NEXT_LOCKUP_ID, ORACLE, TOTAL_VAULT_SHARES, @@ -81,9 +82,11 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> ContractResult { } QueryMsg::VaultExtension(ext) => match ext { ExtensionQueryMsg::Lockup(lockup_msg) => match lockup_msg { - LockupQueryMsg::Lockups { owner, .. } => to_binary(&query_lockups(deps, owner)?), - LockupQueryMsg::Lockup { lockup_id, .. } => { - to_binary(&query_lockup(deps, lockup_id)?) + LockupQueryMsg::UnlockingPositions { owner, .. } => { + to_binary(&query_unlocking_positions(deps, owner)?) + } + LockupQueryMsg::UnlockingPosition { lockup_id, .. } => { + to_binary(&query_unlocking_position(deps, lockup_id)?) } LockupQueryMsg::LockupDuration {} => to_binary(&query_lockup_duration(deps)?), }, diff --git a/contracts/mock-vault/src/msg.rs b/contracts/mock-vault/src/msg.rs index f0ae16005..0ee5eb4cb 100644 --- a/contracts/mock-vault/src/msg.rs +++ b/contracts/mock-vault/src/msg.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::cw_serde; use cw_utils::Duration; use mars_rover::adapters::OracleUnchecked; -// Remaining messages in cosmos-vault-standard +// Remaining messages in cosmwasm-vault-standard #[cw_serde] pub struct InstantiateMsg { /// Denom for vault token diff --git a/contracts/mock-vault/src/query.rs b/contracts/mock-vault/src/query.rs index a164ff8ed..66f4787ef 100644 --- a/contracts/mock-vault/src/query.rs +++ b/contracts/mock-vault/src/query.rs @@ -1,11 +1,13 @@ -use cosmos_vault_standard::extensions::lockup::Lockup; -use cosmos_vault_standard::msg::VaultInfo; use cosmwasm_std::{Deps, Order, StdError, StdResult, Storage, Uint128}; +use cosmwasm_vault_standard::extensions::lockup::UnlockingPosition; +use cosmwasm_vault_standard::msg::VaultInfoResponse; use cw_utils::Duration; use crate::error::ContractError::NotLockingVault; use crate::error::ContractResult; -use crate::state::{COIN_BALANCE, LOCKUPS, LOCKUP_TIME, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}; +use crate::state::{ + COIN_BALANCE, LOCKUP_TIME, TOTAL_VAULT_SHARES, UNLOCKING_POSITIONS, VAULT_TOKEN_DENOM, +}; pub fn shares_to_base_denom_amount( storage: &dyn Storage, @@ -21,10 +23,10 @@ pub fn shares_to_base_denom_amount( } } -pub fn query_vault_info(deps: Deps) -> ContractResult { +pub fn query_vault_info(deps: Deps) -> ContractResult { let base_token = COIN_BALANCE.load(deps.storage)?.denom; let vault_token = VAULT_TOKEN_DENOM.load(deps.storage)?; - Ok(VaultInfo { + Ok(VaultInfoResponse { base_token, vault_token, }) @@ -35,8 +37,8 @@ pub fn query_lockup_duration(deps: Deps) -> ContractResult { Ok(res) } -pub fn query_lockup(deps: Deps, id: u64) -> ContractResult { - Ok(LOCKUPS +pub fn query_unlocking_position(deps: Deps, id: u64) -> ContractResult { + Ok(UNLOCKING_POSITIONS .range(deps.storage, None, None, Order::Ascending) .collect::>>()? .into_iter() @@ -45,9 +47,12 @@ pub fn query_lockup(deps: Deps, id: u64) -> ContractResult { .ok_or_else(|| StdError::generic_err("Id not found"))?) } -pub fn query_lockups(deps: Deps, addr: String) -> ContractResult> { +pub fn query_unlocking_positions( + deps: Deps, + addr: String, +) -> ContractResult> { let addr = deps.api.addr_validate(addr.as_str())?; - let res = LOCKUPS.load(deps.storage, addr)?; + let res = UNLOCKING_POSITIONS.load(deps.storage, addr)?; Ok(res) } diff --git a/contracts/mock-vault/src/state.rs b/contracts/mock-vault/src/state.rs index 6b777f23b..1c72eee56 100644 --- a/contracts/mock-vault/src/state.rs +++ b/contracts/mock-vault/src/state.rs @@ -1,8 +1,8 @@ use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_vault_standard::extensions::lockup::UnlockingPosition; use cw_storage_plus::{Item, Map}; use cw_utils::Duration; -use cosmos_vault_standard::extensions::lockup::Lockup; use mars_rover::adapters::Oracle; pub const VAULT_TOKEN_DENOM: Item = Item::new("vault_token_denom"); @@ -11,7 +11,7 @@ pub const LOCKUP_TIME: Item> = Item::new("lockup_time"); pub const ORACLE: Item = Item::new("oracle"); pub const COIN_BALANCE: Item = Item::new("underlying_coin"); -pub const LOCKUPS: Map> = Map::new("lockups"); +pub const UNLOCKING_POSITIONS: Map> = Map::new("unlocking_positions"); pub const NEXT_LOCKUP_ID: Item = Item::new("next_lockup_id"); // Used for mock LP token minting diff --git a/contracts/mock-vault/src/unlock.rs b/contracts/mock-vault/src/unlock.rs index 73f6561c6..d3ff64206 100644 --- a/contracts/mock-vault/src/unlock.rs +++ b/contracts/mock-vault/src/unlock.rs @@ -1,13 +1,13 @@ -use cosmos_vault_standard::extensions::lockup::{ - Lockup, UNLOCKING_POSITION_ATTR_KEY, UNLOCKING_POSITION_CREATED_EVENT_TYPE, -}; use cosmwasm_std::{ Addr, BankMsg, Coin, CosmosMsg, DepsMut, Env, Event, MessageInfo, Response, StdResult, Uint128, }; +use cosmwasm_vault_standard::extensions::lockup::{ + UnlockingPosition, UNLOCKING_POSITION_ATTR_KEY, UNLOCKING_POSITION_CREATED_EVENT_TYPE, +}; use cw_utils::{Duration, Expiration}; use crate::error::ContractError; -use crate::state::{COIN_BALANCE, LOCKUPS, LOCKUP_TIME, NEXT_LOCKUP_ID}; +use crate::state::{COIN_BALANCE, LOCKUP_TIME, NEXT_LOCKUP_ID, UNLOCKING_POSITIONS}; use crate::withdraw::{get_vault_token, withdraw_state_update}; pub fn request_unlock( @@ -28,15 +28,15 @@ pub fn request_unlock( Duration::Time(s) => Expiration::AtTime(env.block.time.plus_seconds(s)), }; - LOCKUPS.update(deps.storage, info.sender.clone(), |opt| -> StdResult<_> { - let mut lockups = opt.unwrap_or_default(); - lockups.push(Lockup { + UNLOCKING_POSITIONS.update(deps.storage, info.sender.clone(), |opt| -> StdResult<_> { + let mut unlocking_positions = opt.unwrap_or_default(); + unlocking_positions.push(UnlockingPosition { owner: info.sender.clone(), id: next_lockup_id, release_at, base_token_amount: lock_amount, }); - Ok(lockups) + Ok(unlocking_positions) })?; NEXT_LOCKUP_ID.save(deps.storage, &(next_lockup_id + 1))?; @@ -52,7 +52,7 @@ pub fn withdraw_unlocked( sender: &Addr, id: u64, ) -> Result { - let lockups = LOCKUPS + let lockups = UNLOCKING_POSITIONS .may_load(deps.storage, sender.clone())? .ok_or(ContractError::UnlockRequired {})?; @@ -71,7 +71,7 @@ pub fn withdraw_unlocked( } let remaining = lockups.into_iter().filter(|p| p.id != id).collect(); - LOCKUPS.save(deps.storage, sender.clone(), &remaining)?; + UNLOCKING_POSITIONS.save(deps.storage, sender.clone(), &remaining)?; let underlying_coin = COIN_BALANCE.load(deps.storage)?; let transfer_msg = CosmosMsg::Bank(BankMsg::Send { @@ -90,7 +90,7 @@ pub fn withdraw_unlocking_force( lockup_id: u64, amounts: Option, ) -> Result { - let mut lockups = LOCKUPS.load(deps.storage, sender.clone())?; + let mut lockups = UNLOCKING_POSITIONS.load(deps.storage, sender.clone())?; let mut lockup = lockups .iter() .find(|p| p.id == lockup_id) @@ -108,7 +108,7 @@ pub fn withdraw_unlocking_force( None => lockup.base_token_amount, }; - LOCKUPS.save(deps.storage, sender.clone(), &lockups)?; + UNLOCKING_POSITIONS.save(deps.storage, sender.clone(), &lockups)?; let base_token = COIN_BALANCE.load(deps.storage)?; let transfer_msg = CosmosMsg::Bank(BankMsg::Send { diff --git a/packages/chains/osmosis/README.md b/packages/chains/osmosis/README.md deleted file mode 100644 index c5d6a62f7..000000000 --- a/packages/chains/osmosis/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Mars Osmosis -Contains helpers for Osmosis chain diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index c9021827c..0b9ea87d5 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -17,15 +17,15 @@ doctest = false backtraces = ["cosmwasm-std/backtraces"] [dependencies] -cosmos-vault-standard = { workspace = true } -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -mars-health = { workspace = true } -mars-mock-oracle = { workspace = true } -mars-mock-red-bank = { workspace = true } -mars-outpost = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cosmwasm-vault-standard = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +mars-health = { workspace = true } +mars-mock-oracle = { workspace = true } +mars-mock-red-bank = { workspace = true } +mars-outpost = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } diff --git a/packages/rover/README.md b/packages/rover/README.md deleted file mode 100644 index 9bd42bb50..000000000 --- a/packages/rover/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Fields of Mars: Common Types - -## Adapters - -## License diff --git a/packages/rover/src/adapters/vault/base.rs b/packages/rover/src/adapters/vault/base.rs index 19fad6a6f..a8a1ac68d 100644 --- a/packages/rover/src/adapters/vault/base.rs +++ b/packages/rover/src/adapters/vault/base.rs @@ -1,4 +1,4 @@ -use cosmos_vault_standard::extensions::force_unlock::ForceUnlockExecuteMsg::{ +use cosmwasm_vault_standard::extensions::force_unlock::ForceUnlockExecuteMsg::{ ForceRedeem, ForceWithdrawUnlocking, }; use std::hash::Hash; @@ -10,21 +10,20 @@ use cosmwasm_std::{ }; use cw_utils::Duration; -use cosmos_vault_standard::extensions::lockup::Lockup; -use cosmos_vault_standard::extensions::lockup::LockupExecuteMsg::{Unlock, WithdrawUnlocked}; -use cosmos_vault_standard::extensions::lockup::LockupQueryMsg::{ - Lockup as LockupQueryMsg, LockupDuration, -}; -use cosmos_vault_standard::msg::{ - ExecuteMsg, ExtensionExecuteMsg, ExtensionQueryMsg, QueryMsg, VaultInfo, +use cosmwasm_vault_standard::extensions::lockup::LockupExecuteMsg::{Unlock, WithdrawUnlocked}; +use cosmwasm_vault_standard::extensions::lockup::LockupQueryMsg::LockupDuration; +use cosmwasm_vault_standard::extensions::lockup::{LockupQueryMsg, UnlockingPosition}; +use cosmwasm_vault_standard::msg::{ + ExtensionExecuteMsg, ExtensionQueryMsg, VaultStandardExecuteMsg, VaultStandardQueryMsg, }; +use cosmwasm_vault_standard::VaultInfoResponse; use crate::traits::Stringify; pub const VAULT_REQUEST_REPLY_ID: u64 = 10_001; -type VaultExecuteMsg = ExecuteMsg; -type VaultQueryMsg = QueryMsg; +pub type ExecuteMsg = VaultStandardExecuteMsg; +pub type QueryMsg = VaultStandardQueryMsg; #[cw_serde] #[derive(Eq, Hash)] @@ -77,7 +76,7 @@ impl Vault { let deposit_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![coin.clone()], - msg: to_binary(&VaultExecuteMsg::Deposit { + msg: to_binary(&ExecuteMsg::Deposit { amount: coin.amount, recipient: None, })?, @@ -100,12 +99,12 @@ impl Vault { }], msg: to_binary( &(if force { - VaultExecuteMsg::VaultExtension(ExtensionExecuteMsg::ForceUnlock(ForceRedeem { + ExecuteMsg::VaultExtension(ExtensionExecuteMsg::ForceUnlock(ForceRedeem { recipient: None, amount, })) } else { - VaultExecuteMsg::Redeem { + ExecuteMsg::Redeem { recipient: None, amount, } @@ -123,7 +122,7 @@ impl Vault { let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![], - msg: to_binary(&VaultExecuteMsg::VaultExtension( + msg: to_binary(&ExecuteMsg::VaultExtension( ExtensionExecuteMsg::ForceUnlock(ForceWithdrawUnlocking { lockup_id, amount, @@ -139,11 +138,11 @@ impl Vault { CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![coin.clone()], - msg: to_binary(&VaultExecuteMsg::VaultExtension( - ExtensionExecuteMsg::Lockup(Unlock { + msg: to_binary(&ExecuteMsg::VaultExtension(ExtensionExecuteMsg::Lockup( + Unlock { amount: coin.amount, - }), - ))?, + }, + )))?, }), VAULT_REQUEST_REPLY_ID, ); @@ -154,37 +153,41 @@ impl Vault { let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![], - msg: to_binary(&VaultExecuteMsg::VaultExtension( - ExtensionExecuteMsg::Lockup(WithdrawUnlocked { + msg: to_binary(&ExecuteMsg::VaultExtension(ExtensionExecuteMsg::Lockup( + WithdrawUnlocked { recipient: None, lockup_id, - }), - ))?, + }, + )))?, }); Ok(withdraw_msg) } - pub fn query_info(&self, querier: &QuerierWrapper) -> StdResult { + pub fn query_info(&self, querier: &QuerierWrapper) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), - msg: to_binary(&VaultQueryMsg::Info {})?, + msg: to_binary(&QueryMsg::Info {})?, })) } pub fn query_lockup_duration(&self, querier: &QuerierWrapper) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), - msg: to_binary(&VaultQueryMsg::VaultExtension(ExtensionQueryMsg::Lockup( + msg: to_binary(&QueryMsg::VaultExtension(ExtensionQueryMsg::Lockup( LockupDuration {}, )))?, })) } - pub fn query_lockup(&self, querier: &QuerierWrapper, lockup_id: u64) -> StdResult { + pub fn query_unlocking_position( + &self, + querier: &QuerierWrapper, + lockup_id: u64, + ) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), - msg: to_binary(&VaultQueryMsg::VaultExtension(ExtensionQueryMsg::Lockup( - LockupQueryMsg { lockup_id }, + msg: to_binary(&QueryMsg::VaultExtension(ExtensionQueryMsg::Lockup( + LockupQueryMsg::UnlockingPosition { lockup_id }, )))?, })) } @@ -205,14 +208,14 @@ impl Vault { ) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), - msg: to_binary(&VaultQueryMsg::PreviewRedeem { amount })?, + msg: to_binary(&QueryMsg::PreviewRedeem { amount })?, })) } pub fn query_total_vault_coins_issued(&self, querier: &QuerierWrapper) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), - msg: to_binary(&VaultQueryMsg::TotalVaultTokenSupply {})?, + msg: to_binary(&QueryMsg::TotalVaultTokenSupply {})?, })) } } diff --git a/packages/rover/src/extensions/reply.rs b/packages/rover/src/extensions/reply.rs index 031daf469..2f52a60d3 100644 --- a/packages/rover/src/extensions/reply.rs +++ b/packages/rover/src/extensions/reply.rs @@ -1,8 +1,8 @@ -use cosmos_vault_standard::extensions::lockup::{ - UNLOCKING_POSITION_ATTR_KEY, UNLOCKING_POSITION_CREATED_EVENT_TYPE, -}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Reply, StdError, StdResult, SubMsgResult}; +use cosmwasm_vault_standard::extensions::lockup::{ + UNLOCKING_POSITION_ATTR_KEY, UNLOCKING_POSITION_CREATED_EVENT_TYPE, +}; #[cw_serde] pub struct AssetTransferMsg { diff --git a/schemas/mars-mock-vault/mars-mock-vault.json b/schemas/mars-mock-vault/mars-mock-vault.json index 394b006af..0d4e4056c 100644 --- a/schemas/mars-mock-vault/mars-mock-vault.json +++ b/schemas/mars-mock-vault/mars-mock-vault.json @@ -79,6 +79,7 @@ "execute": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", + "description": "The default ExecuteMsg variants that all vaults must implement. This enum can be extended with additional variants by defining an extension enum and then passing it as the generic argument `T` to this enum.", "oneOf": [ { "description": "Called to deposit into the vault. Native assets are passed in the funds parameter.", @@ -128,7 +129,7 @@ ], "properties": { "amount": { - "description": "The amount of vault tokens sent to the contract. In the case that the vault token is a Cosmos native denom, we of course have this information in the info.funds, but if the vault implements the Cw4626 API, then we need this argument. We figured it's better to have one API for both types of vaults, so we require this argument.", + "description": "The amount of vault tokens sent to the contract. In the case that the vault token is a Cosmos native denom, we of course have this information in info.funds, but if the vault implements the Cw4626 API, then we need this argument. We figured it's better to have one API for both types of vaults, so we require this argument.", "allOf": [ { "$ref": "#/definitions/Uint128" @@ -149,7 +150,7 @@ "additionalProperties": false }, { - "description": "Support for custom extensions", + "description": "Called to execute functionality of any enabled extensions.", "type": "object", "required": [ "vault_extension" @@ -164,7 +165,7 @@ ], "definitions": { "ExtensionExecuteMsg": { - "description": "Contains ExecuteMsgs of all enabled extensions. To enable extensions defined outside of this create, you can define your own `ExtensionExecuteMsg` type in your contract crate and pass it in as the generic parameter to ExecuteMsg", + "description": "Contains ExecuteMsgs of all enabled extensions. To enable extensions defined outside of this crate, you can define your own `ExtensionExecuteMsg` type in your contract crate and pass it in as the generic parameter to ExecuteMsg", "oneOf": [ { "type": "object", @@ -193,6 +194,7 @@ ] }, "ForceUnlockExecuteMsg": { + "description": "Additional ExecuteMsg variants for vaults that enable the ForceUnlock extension.", "oneOf": [ { "description": "Can be called by whitelisted addresses to bypass the lockup and immediately return the base tokens. Used in the event of liquidation. The caller must pass the native vault tokens in the funds field.", @@ -286,12 +288,14 @@ ], "properties": { "add_addresses": { + "description": "Addresses to add to the whitelist.", "type": "array", "items": { "type": "string" } }, "remove_addresses": { + "description": "Addresses to remove from the whitelist.", "type": "array", "items": { "type": "string" @@ -306,6 +310,7 @@ ] }, "LockupExecuteMsg": { + "description": "Additional ExecuteMsg variants for vaults that enable the Lockup extension.", "oneOf": [ { "description": "Unlock is called to initiate unlocking a locked position held by the vault. The caller must pass the native vault tokens in the funds field. Emits an event with type `UNLOCKING_POSITION_CREATED_EVENT_TYPE` with an attribute with key `UNLOCKING_POSITION_ATTR_KEY` containing an u64 lockup_id. Also encodes the u64 lockup ID as binary and returns it in the Response's data field, so that it can be read by SubMsg replies.\n\nLike Redeem, this takes an amount so that the same API can be used for CW4626 and native tokens.", @@ -321,7 +326,12 @@ ], "properties": { "amount": { - "$ref": "#/definitions/Uint128" + "description": "The amount of vault tokens to unlock.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] } }, "additionalProperties": false @@ -372,9 +382,10 @@ "query": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "QueryMsg", + "description": "The default QueryMsg variants that all vaults must implement. This enum can be extended with additional variants by defining an extension enum and then passing it as the generic argument `T` to this enum.", "oneOf": [ { - "description": "Returns `VaultStandardInfo` with information on the version of the vault standard used as well as any enabled extensions.", + "description": "Returns `VaultStandardInfoResponse` with information on the version of the vault standard used as well as any enabled extensions.", "type": "object", "required": [ "vault_standard_info" @@ -388,7 +399,7 @@ "additionalProperties": false }, { - "description": "Returns `VaultInfo` representing vault requirements, lockup, & vault token denom.", + "description": "Returns `VaultInfoResponse` representing vault requirements, lockup, & vault token denom.", "type": "object", "required": [ "info" @@ -402,7 +413,7 @@ "additionalProperties": false }, { - "description": "Returns `Uint128` amount of vault tokens that will be returned for the passed in assets.\n\nAllows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions.\n\nMUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called in the same transaction.\n\nMUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the deposit would be accepted, regardless if the user has enough tokens approved, etc.\n\nMUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.", + "description": "Returns `Uint128` amount of vault tokens that will be returned for the passed in `amount` of base tokens.\n\nAllows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions.\n\nMust return as close to and no more than the exact amount of vault tokens that would be minted in a deposit call in the same transaction. I.e. Deposit should return the same or more vault tokens as PreviewDeposit if called in the same transaction.", "type": "object", "required": [ "preview_deposit" @@ -415,7 +426,12 @@ ], "properties": { "amount": { - "$ref": "#/definitions/Uint128" + "description": "The amount of base tokens to preview depositing.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] } }, "additionalProperties": false @@ -424,7 +440,7 @@ "additionalProperties": false }, { - "description": "Returns the number of base tokens that would be redeemed in exchange `amount` for vault tokens. Used by Rover to calculate vault position values.", + "description": "Returns `Uint128` amount of base tokens that would be withdrawn in exchange for redeeming `amount` of vault tokens.\n\nAllows an on-chain or off-chain user to simulate the effects of their redeem at the current block, given current on-chain conditions.\n\nMust return as close to and no more than the exact amount of base tokens that would be withdrawn in a redeem call in the same transaction.", "type": "object", "required": [ "preview_redeem" @@ -437,7 +453,12 @@ ], "properties": { "amount": { - "$ref": "#/definitions/Uint128" + "description": "The amount of vault tokens to preview redeeming.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] } }, "additionalProperties": false @@ -474,7 +495,7 @@ "additionalProperties": false }, { - "description": "The amount of shares that the vault would exchange for the amount of assets provided, in an ideal scenario where all the conditions are met.\n\nUseful for display purposes and does not have to confer the exact amount of shares returned by the vault if the passed in assets were deposited. This calculation may not reflect the “per-user” price-per-share, and instead should reflect the “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and from.", + "description": "The amount of vault tokens that the vault would exchange for the amount of assets provided, in an ideal scenario where all the conditions are met.\n\nUseful for display purposes and does not have to confer the exact amount of vault tokens returned by the vault if the passed in assets were deposited. This calculation should not reflect the \"per-user\" price-per-share, and instead should reflect the \"average-user’s\" price-per-share, meaning what the average user should expect to see when exchanging to and from.", "type": "object", "required": [ "convert_to_shares" @@ -487,7 +508,12 @@ ], "properties": { "amount": { - "$ref": "#/definitions/Uint128" + "description": "The amount of base tokens to convert to vault tokens.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] } }, "additionalProperties": false @@ -496,7 +522,7 @@ "additionalProperties": false }, { - "description": "Returns the amount of base tokens that the Vault would exchange for the `amount` of shares provided, in an ideal scenario where all the conditions are met.\n\nUseful for display purposes and does not have to confer the exact amount of assets returned by the vault if the passed in shares were withdrawn. This calculation may not reflect the “per-user” price-per-share, and instead should reflect the “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and from.", + "description": "Returns the amount of base tokens that the Vault would exchange for the `amount` of vault tokens provided, in an ideal scenario where all the conditions are met.\n\nUseful for display purposes and does not have to confer the exact amount of assets returned by the vault if the passed in vault tokens were redeemed. This calculation should not reflect the \"per-user\" price-per-share, and instead should reflect the \"average-user’s\" price-per-share, meaning what the average user should expect to see when exchanging to and from.", "type": "object", "required": [ "convert_to_assets" @@ -509,7 +535,12 @@ ], "properties": { "amount": { - "$ref": "#/definitions/Uint128" + "description": "The amount of vault tokens to convert to base tokens.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] } }, "additionalProperties": false @@ -518,7 +549,7 @@ "additionalProperties": false }, { - "description": "TODO: How to handle return derive? We must supply a type here, but we don't know it.", + "description": "Handle queries of any enabled extensions.", "type": "object", "required": [ "vault_extension" @@ -533,7 +564,7 @@ ], "definitions": { "ExtensionQueryMsg": { - "description": "Contains QueryMsgs of all enabled extensions. To enable extensions defined outside of this create, you can define your own `ExtensionQueryMsg` type in your contract crate and pass it in as the generic parameter to QueryMsg", + "description": "Contains QueryMsgs of all enabled extensions. To enable extensions defined outside of this crate, you can define your own `ExtensionQueryMsg` type in your contract crate and pass it in as the generic parameter to QueryMsg", "oneOf": [ { "type": "object", @@ -550,15 +581,16 @@ ] }, "LockupQueryMsg": { + "description": "Additional QueryMsg variants for vaults that enable the Lockup extension.", "oneOf": [ { - "description": "Returns a `Vec` containing all the currently unclaimed lockup positions for the `owner`.", + "description": "Returns a `Vec` containing all the currently unclaimed lockup positions for the `owner`.", "type": "object", "required": [ - "lockups" + "unlocking_positions" ], "properties": { - "lockups": { + "unlocking_positions": { "type": "object", "required": [ "owner" @@ -593,19 +625,20 @@ "additionalProperties": false }, { - "description": "Returns `Lockup` info about a specific lockup, by owner and ID.", + "description": "Returns an `UnlockingPosition` info about a specific lockup, by owner and ID.", "type": "object", "required": [ - "lockup" + "unlocking_position" ], "properties": { - "lockup": { + "unlocking_position": { "type": "object", "required": [ "lockup_id" ], "properties": { "lockup_id": { + "description": "The ID of the lockup to query", "type": "integer", "format": "uint64", "minimum": 0.0 @@ -617,7 +650,7 @@ "additionalProperties": false }, { - "description": "Returns `cw_utils::Duration` duration of the lockup.", + "description": "Returns `cw_utils::Duration` duration of the lockup of the vault.", "type": "object", "required": [ "lockup_duration" @@ -655,7 +688,7 @@ }, "info": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VaultInfo", + "title": "VaultInfoResponse", "description": "Returned by QueryMsg::Info and contains information about this vault", "type": "object", "required": [ @@ -706,7 +739,7 @@ }, "vault_standard_info": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VaultStandardInfo", + "title": "VaultStandardInfoResponse", "description": "Struct returned from QueryMsg::VaultStandardInfo with information about the used version of the vault standard and any extensions used.\n\nThis struct should be stored as an Item under the `vault_standard_info` key, so that other contracts can do a RawQuery and read it directly from storage instead of needing to do a costly SmartQuery.", "type": "object", "required": [ @@ -715,7 +748,7 @@ ], "properties": { "extensions": { - "description": "A list of vault standard extensions used by the vault. E.g. [\"cw20\", \"lockup\", \"keeper\"]", + "description": "A list of vault standard extensions used by the vault. E.g. [\"lockup\", \"keeper\"]", "type": "array", "items": { "type": "string" diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts index e10d9622d..b162b4d32 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts @@ -19,14 +19,14 @@ import { QueryMsg, ExtensionQueryMsg, LockupQueryMsg, - VaultInfo, + VaultInfoResponse, Empty, - VaultStandardInfo, + VaultStandardInfoResponse, } from './MarsMockVault.types' export interface MarsMockVaultReadOnlyInterface { contractAddress: string - vaultStandardInfo: () => Promise - info: () => Promise + vaultStandardInfo: () => Promise + info: () => Promise previewDeposit: ({ amount }: { amount: Uint128 }) => Promise previewRedeem: ({ amount }: { amount: Uint128 }) => Promise totalAssets: () => Promise @@ -53,12 +53,12 @@ export class MarsMockVaultQueryClient implements MarsMockVaultReadOnlyInterface this.vaultExtension = this.vaultExtension.bind(this) } - vaultStandardInfo = async (): Promise => { + vaultStandardInfo = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { vault_standard_info: {}, }) } - info = async (): Promise => { + info = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { info: {}, }) diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts index 3e119026a..40d94fe26 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts @@ -21,9 +21,9 @@ import { QueryMsg, ExtensionQueryMsg, LockupQueryMsg, - VaultInfo, + VaultInfoResponse, Empty, - VaultStandardInfo, + VaultStandardInfoResponse, } from './MarsMockVault.types' export interface MarsMockVaultMessage { contractAddress: string diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts index 2cfe68ea5..5efc557c5 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts @@ -20,9 +20,9 @@ import { QueryMsg, ExtensionQueryMsg, LockupQueryMsg, - VaultInfo, + VaultInfoResponse, Empty, - VaultStandardInfo, + VaultStandardInfoResponse, } from './MarsMockVault.types' import { MarsMockVaultQueryClient, MarsMockVaultClient } from './MarsMockVault.client' export const marsMockVaultQueryKeys = { @@ -209,24 +209,25 @@ export function useMarsMockVaultPreviewDepositQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsMockVaultInfoQuery extends MarsMockVaultReactQuery {} -export function useMarsMockVaultInfoQuery({ +export interface MarsMockVaultInfoQuery + extends MarsMockVaultReactQuery {} +export function useMarsMockVaultInfoQuery({ client, options, }: MarsMockVaultInfoQuery) { - return useQuery( + return useQuery( marsMockVaultQueryKeys.info(client?.contractAddress), () => (client ? client.info() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } export interface MarsMockVaultVaultStandardInfoQuery - extends MarsMockVaultReactQuery {} -export function useMarsMockVaultVaultStandardInfoQuery({ + extends MarsMockVaultReactQuery {} +export function useMarsMockVaultVaultStandardInfoQuery({ client, options, }: MarsMockVaultVaultStandardInfoQuery) { - return useQuery( + return useQuery( marsMockVaultQueryKeys.vaultStandardInfo(client?.contractAddress), () => (client ? client.vaultStandardInfo() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts index 59ab66608..fbfd98047 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts @@ -116,28 +116,28 @@ export type ExtensionQueryMsg = { } export type LockupQueryMsg = | { - lockups: { + unlocking_positions: { limit?: number | null owner: string start_after?: number | null } } | { - lockup: { + unlocking_position: { lockup_id: number } } | { lockup_duration: {} } -export interface VaultInfo { +export interface VaultInfoResponse { base_token: string vault_token: string } export interface Empty { [k: string]: unknown } -export interface VaultStandardInfo { +export interface VaultStandardInfoResponse { extensions: string[] version: number } From 1c10aa538eaa1c2dc93f10faba6223b2eaed3ec6 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 16 Nov 2022 15:59:20 +0100 Subject: [PATCH 080/218] Adjusting liq bonus setting (#44) * Liquidation bonus tweaks * review updates * updating types --- Cargo.lock | 13 +- contracts/credit-manager/src/execute.rs | 15 +- contracts/credit-manager/src/instantiate.rs | 4 +- .../credit-manager/src/liquidate_coin.rs | 16 +- contracts/credit-manager/src/query.rs | 6 +- contracts/credit-manager/src/repay.rs | 8 +- contracts/credit-manager/src/state.rs | 1 - .../src/vault/liquidate_vault.rs | 72 ++--- .../credit-manager/tests/helpers/builders.rs | 1 + .../tests/helpers/mock_entity_info.rs | 4 + .../credit-manager/tests/helpers/mock_env.rs | 15 +- .../credit-manager/tests/helpers/types.rs | 1 + .../tests/test_fields_vault_limit.rs | 1 + contracts/credit-manager/tests/test_health.rs | 7 + .../credit-manager/tests/test_instantiate.rs | 9 +- .../tests/test_liquidate_coin.rs | 19 +- .../tests/test_liquidate_vault.rs | 264 ++++++++++-------- contracts/credit-manager/tests/test_repay.rs | 2 + .../tests/test_update_config.rs | 15 - contracts/mock-red-bank/src/msg.rs | 1 + contracts/mock-red-bank/src/query.rs | 1 + packages/rover/src/adapters/vault/position.rs | 7 + packages/rover/src/msg/execute.rs | 5 +- packages/rover/src/msg/instantiate.rs | 3 - packages/rover/src/msg/query.rs | 1 - .../mars-credit-manager.json | 41 ++- .../mars-mock-red-bank.json | 4 + scripts/deploy/base/deployer.ts | 1 - scripts/deploy/osmosis/config.ts | 1 - scripts/types/config.ts | 1 - .../MarsCreditManager.client.ts | 1 + .../MarsCreditManager.message-composer.ts | 1 + .../MarsCreditManager.react-query.ts | 1 + .../MarsCreditManager.types.ts | 6 +- .../MarsMockRedBank.types.ts | 1 + 35 files changed, 281 insertions(+), 268 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f50cff4a9..40077d1f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -887,9 +887,12 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +dependencies = [ + "cpufeatures", +] [[package]] name = "lazy_static" @@ -1219,7 +1222,7 @@ dependencies = [ [[package]] name = "osmosis-std" version = "0.12.0" -source = "git+https://github.com/osmosis-labs/osmosis-rust#9b010acf59d631cf099f2b45dbc011a4d99b37f8" +source = "git+https://github.com/osmosis-labs/osmosis-rust#b3df85158eb52ad5d99f3580f4a128bbdaeffa17" dependencies = [ "chrono", "cosmwasm-std", @@ -1246,7 +1249,7 @@ dependencies = [ [[package]] name = "osmosis-std-derive" version = "0.12.0" -source = "git+https://github.com/osmosis-labs/osmosis-rust#9b010acf59d631cf099f2b45dbc011a4d99b37f8" +source = "git+https://github.com/osmosis-labs/osmosis-rust#b3df85158eb52ad5d99f3580f4a128bbdaeffa17" dependencies = [ "itertools", "proc-macro2", @@ -1257,7 +1260,7 @@ dependencies = [ [[package]] name = "osmosis-testing" version = "0.12.0" -source = "git+https://github.com/osmosis-labs/osmosis-rust#9b010acf59d631cf099f2b45dbc011a4d99b37f8" +source = "git+https://github.com/osmosis-labs/osmosis-rust#b3df85158eb52ad5d99f3580f4a128bbdaeffa17" dependencies = [ "base64", "bindgen", diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 6b94b918c..b90ebe627 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -16,8 +16,8 @@ use crate::liquidate_coin::liquidate_coin; use crate::refund::refund_coin_balances; use crate::repay::repay; use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, - SWAPPER, VAULT_CONFIGS, ZAPPER, + ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, ORACLE, OWNER, RED_BANK, SWAPPER, VAULT_CONFIGS, + ZAPPER, }; use crate::swap::swap_exact_in; use crate::update_coin_balances::update_coin_balance; @@ -138,13 +138,6 @@ pub fn update_config( .add_attribute("value", unchecked.address()); } - if let Some(bonus) = new_config.max_liquidation_bonus { - MAX_LIQUIDATION_BONUS.save(deps.storage, &bonus)?; - response = response - .add_attribute("key", "max_liquidation_bonus") - .add_attribute("value", bonus.to_string()); - } - if let Some(cf) = new_config.max_close_factor { MAX_CLOSE_FACTOR.save(deps.storage, &cf)?; response = response @@ -216,11 +209,13 @@ pub fn dispatch_actions( liquidatee_account_id, debt_coin, request_vault, + position_type, } => callbacks.push(CallbackMsg::LiquidateVault { liquidator_account_id: account_id.to_string(), liquidatee_account_id: liquidatee_account_id.to_string(), debt_coin: debt_coin.clone(), request_vault: request_vault.check(deps.api)?, + position_type: position_type.clone(), }), Action::SwapExactIn { coin_in, @@ -364,6 +359,7 @@ pub fn execute_callback( liquidatee_account_id, debt_coin, request_vault, + position_type, } => liquidate_vault( deps, env, @@ -371,6 +367,7 @@ pub fn execute_callback( &liquidatee_account_id, debt_coin, request_vault, + position_type, ), CallbackMsg::SwapExactIn { account_id, diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 6c99cccbd..da3b53a85 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -4,8 +4,7 @@ use mars_rover::error::ContractResult; use mars_rover::msg::InstantiateMsg; use crate::state::{ - ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, - VAULT_CONFIGS, ZAPPER, + ALLOWED_COINS, MAX_CLOSE_FACTOR, ORACLE, OWNER, RED_BANK, SWAPPER, VAULT_CONFIGS, ZAPPER, }; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { @@ -13,7 +12,6 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { OWNER.save(deps.storage, &owner)?; RED_BANK.save(deps.storage, &msg.red_bank.check(deps.api)?)?; ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; - MAX_LIQUIDATION_BONUS.save(deps.storage, &msg.max_liquidation_bonus)?; MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; SWAPPER.save(deps.storage, &msg.swapper.check(deps.api)?)?; ZAPPER.save(deps.storage, &msg.zapper.check(deps.api)?)?; diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index 9c909bc93..cb644e35e 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -8,7 +8,7 @@ use mars_rover::traits::{IntoDecimal, IntoUint128}; use crate::health::{compute_health, val_or_na}; use crate::repay::current_debt_for_denom; -use crate::state::{COIN_BALANCES, MAX_CLOSE_FACTOR, MAX_LIQUIDATION_BONUS, ORACLE}; +use crate::state::{COIN_BALANCES, MAX_CLOSE_FACTOR, ORACLE, RED_BANK}; use crate::utils::{decrement_coin_balance, increment_coin_balance}; pub fn liquidate_coin( @@ -79,7 +79,7 @@ pub fn calculate_liquidation( // Ensure debt repaid does not exceed liquidatee's total debt for denom let (total_debt_amount, _) = - current_debt_for_denom(deps.as_ref(), env, liquidatee_account_id, debt_coin)?; + current_debt_for_denom(deps.as_ref(), env, liquidatee_account_id, &debt_coin.denom)?; // Ensure debt amount does not exceed close factor % of the liquidatee's total debt value let close_factor = MAX_CLOSE_FACTOR.load(deps.storage)?; @@ -94,7 +94,11 @@ pub fn calculate_liquidation( let max_request_value = request_res .price .checked_mul(request_coin_balance.to_dec()?)?; - let liq_bonus_rate = MAX_LIQUIDATION_BONUS.load(deps.storage)?; + + let liq_bonus_rate = RED_BANK + .load(deps.storage)? + .query_market(&deps.querier, &debt_coin.denom)? + .liquidation_bonus; let request_coin_adjusted_max_debt = max_request_value .div(liq_bonus_rate.add(Decimal::one())) .div(debt_res.price) @@ -116,11 +120,11 @@ pub fn calculate_liquidation( .add(Decimal::one()) .checked_mul(debt_res.price.checked_mul(final_debt_to_repay.to_dec()?)?)? .div(request_res.price) - .uint128() // Given the nature of integers, these operations will round down. This means the liquidation balance will get // closer and closer to 0, but never actually get there and stay as a single denom unit. - // The remediation for this is to round up at the very end of the calculation. Which adding 1 effectively does. - .checked_add(Uint128::new(1))?; + // The remediation for this is to round up at the very end of the calculation. + .ceil() + .uint128(); // (Debt Coin, Request Coin) Ok(( diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index de2f631ae..cbce73790 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -10,9 +10,8 @@ use mars_rover::msg::query::{ }; use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, - MAX_LIQUIDATION_BONUS, ORACLE, OWNER, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_CONFIGS, - VAULT_POSITIONS, ZAPPER, + ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, ORACLE, OWNER, + RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_CONFIGS, VAULT_POSITIONS, ZAPPER, }; use crate::utils::debt_shares_to_amount; @@ -27,7 +26,6 @@ pub fn query_config(deps: Deps) -> StdResult { .map(|addr| addr.to_string()), red_bank: RED_BANK.load(deps.storage)?.address().into(), oracle: ORACLE.load(deps.storage)?.address().into(), - max_liquidation_bonus: MAX_LIQUIDATION_BONUS.load(deps.storage)?, max_close_factor: MAX_CLOSE_FACTOR.load(deps.storage)?, swapper: SWAPPER.load(deps.storage)?.address().into(), zapper: ZAPPER.load(deps.storage)?.address().into(), diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index 1955a0972..868ffa4e7 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -16,7 +16,7 @@ pub fn repay(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> ContractR // Ensure repayment does not exceed max debt on account let (debt_amount, debt_shares) = - current_debt_for_denom(deps.as_ref(), &env, account_id, &coin)?; + current_debt_for_denom(deps.as_ref(), &env, account_id, &coin.denom)?; let amount_to_repay = min(debt_amount, coin.amount); let shares_to_repay = debt_amount_to_shares( deps.as_ref(), @@ -83,11 +83,11 @@ pub fn current_debt_for_denom( deps: Deps, env: &Env, account_id: &str, - coin: &Coin, + denom: &str, ) -> ContractResult<(Uint128, Uint128)> { let debt_shares = DEBT_SHARES - .load(deps.storage, (account_id, &coin.denom)) + .load(deps.storage, (account_id, denom)) .map_err(|_| ContractError::NoDebt)?; - let coin = debt_shares_to_amount(deps, &env.contract.address, &coin.denom, debt_shares)?; + let coin = debt_shares_to_amount(deps, &env.contract.address, denom, debt_shares)?; Ok((coin.amount, debt_shares)) } diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index f4730a9b6..ca23e5ac3 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -15,7 +15,6 @@ pub const ALLOWED_COINS: Set<&str> = Set::new("allowed_coins"); pub const VAULT_CONFIGS: Map<&Addr, VaultConfig> = Map::new("vault_configs"); pub const RED_BANK: Item = Item::new("red_bank"); pub const ORACLE: Item = Item::new("oracle"); -pub const MAX_LIQUIDATION_BONUS: Item = Item::new("max_liquidation_bonus"); pub const MAX_CLOSE_FACTOR: Item = Item::new("max_close_factor"); pub const SWAPPER: Item = Item::new("swapper"); pub const ZAPPER: Item = Item::new("zapper"); diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index 25599f9ed..061162f17 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -3,10 +3,10 @@ use std::cmp::min; use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; use mars_rover::adapters::vault::{ - UnlockingChange, UnlockingPositions, UpdateType, Vault, VaultPositionAmount, + UnlockingChange, UnlockingPositions, UpdateType, Vault, VaultPositionAmount, VaultPositionType, VaultPositionUpdate, }; -use mars_rover::error::ContractResult; +use mars_rover::error::{ContractError, ContractResult}; use crate::liquidate_coin::{calculate_liquidation, repay_debt}; use crate::state::VAULT_POSITIONS; @@ -20,6 +20,7 @@ pub fn liquidate_vault( liquidatee_account_id: &str, debt_coin: Coin, request_vault: Vault, + position_type: VaultPositionType, ) -> ContractResult { let liquidatee_position = VAULT_POSITIONS.load( deps.storage, @@ -27,40 +28,39 @@ pub fn liquidate_vault( )?; match liquidatee_position { - VaultPositionAmount::Unlocked(a) => liquidate_unlocked( - deps, - env, - liquidator_account_id, - liquidatee_account_id, - debt_coin, - request_vault, - a.total(), - ), - VaultPositionAmount::Locking(ref a) => { - // A locking vault can have two different positions: LOCKED & UNLOCKING - // Priority goes to force withdrawing the unlocking buckets - if !a.unlocking.positions().is_empty() { - liquidate_unlocking( - deps, - env, - liquidator_account_id, - liquidatee_account_id, - debt_coin, - request_vault, - liquidatee_position.unlocking(), - ) - } else { - liquidate_locked( - deps, - env, - liquidator_account_id, - liquidatee_account_id, - debt_coin, - request_vault, - a.locked.total(), - ) - } - } + VaultPositionAmount::Unlocked(a) => match position_type { + VaultPositionType::UNLOCKED => liquidate_unlocked( + deps, + env, + liquidator_account_id, + liquidatee_account_id, + debt_coin, + request_vault, + a.total(), + ), + _ => Err(ContractError::MismatchedVaultType), + }, + VaultPositionAmount::Locking(ref a) => match position_type { + VaultPositionType::LOCKED => liquidate_locked( + deps, + env, + liquidator_account_id, + liquidatee_account_id, + debt_coin, + request_vault, + a.locked.total(), + ), + VaultPositionType::UNLOCKING => liquidate_unlocking( + deps, + env, + liquidator_account_id, + liquidatee_account_id, + debt_coin, + request_vault, + liquidatee_position.unlocking(), + ), + _ => Err(ContractError::MismatchedVaultType), + }, } } diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index 8c4e33e0d..4201a92b0 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -13,6 +13,7 @@ pub fn build_mock_coin_infos(count: usize) -> Vec { max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: 10.to_dec().unwrap(), + liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), }) .collect() } diff --git a/contracts/credit-manager/tests/helpers/mock_entity_info.rs b/contracts/credit-manager/tests/helpers/mock_entity_info.rs index 2c25e5b9c..4fc36ca45 100644 --- a/contracts/credit-manager/tests/helpers/mock_entity_info.rs +++ b/contracts/credit-manager/tests/helpers/mock_entity_info.rs @@ -9,6 +9,7 @@ pub fn uosmo_info() -> CoinInfo { price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), } } pub fn uatom_info() -> CoinInfo { @@ -17,6 +18,7 @@ pub fn uatom_info() -> CoinInfo { price: Decimal::from_atomics(10u128, 1).unwrap(), max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), + liquidation_bonus: Decimal::from_atomics(10u128, 2).unwrap(), } } @@ -26,6 +28,7 @@ pub fn ujake_info() -> CoinInfo { price: Decimal::from_atomics(23654u128, 4).unwrap(), max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), + liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), } } @@ -35,6 +38,7 @@ pub fn lp_token_info() -> CoinInfo { price: Decimal::from_atomics(9874u128, 3).unwrap(), max_ltv: Decimal::from_atomics(63u128, 2).unwrap(), liquidation_threshold: Decimal::from_atomics(68u128, 2).unwrap(), + liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), } } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index b90f9f59d..499b1887e 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -63,7 +63,6 @@ pub struct MockEnvBuilder { pub deploy_nft_contract: bool, pub set_nft_contract_owner: bool, pub accounts_to_fund: Vec, - pub max_liquidation_bonus: Option, pub max_close_factor: Option, } @@ -82,7 +81,6 @@ impl MockEnv { deploy_nft_contract: true, set_nft_contract_owner: true, accounts_to_fund: vec![], - max_liquidation_bonus: None, max_close_factor: None, } } @@ -524,7 +522,6 @@ impl MockEnvBuilder { .iter() .map(|info| info.denom.clone()) .collect(); - let max_liquidation_bonus = self.get_max_liquidation_bonus(); let max_close_factor = self.get_max_close_factor(); let mut allowed_vaults = vec![]; @@ -543,7 +540,6 @@ impl MockEnvBuilder { allowed_vaults, red_bank, oracle, - max_liquidation_bonus, max_close_factor, swapper, zapper, @@ -669,6 +665,7 @@ impl MockEnvBuilder { denom: item.denom.to_string(), max_ltv: item.max_ltv, liquidation_threshold: item.liquidation_threshold, + liquidation_bonus: item.liquidation_bonus, }) .collect(), }, @@ -805,11 +802,6 @@ impl MockEnvBuilder { self.allowed_coins.clone().unwrap_or_default() } - fn get_max_liquidation_bonus(&self) -> Decimal { - self.max_liquidation_bonus - .unwrap_or_else(|| Decimal::from_atomics(5u128, 2).unwrap()) // 5% - } - fn get_max_close_factor(&self) -> Decimal { self.max_close_factor .unwrap_or_else(|| Decimal::from_atomics(5u128, 1).unwrap()) // 50% @@ -880,11 +872,6 @@ impl MockEnvBuilder { self } - pub fn max_liquidation_bonus(&mut self, bonus: Decimal) -> &mut Self { - self.max_liquidation_bonus = Some(bonus); - self - } - pub fn max_close_factor(&mut self, cf: Decimal) -> &mut Self { self.max_close_factor = Some(cf); self diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 177793fff..2e4e162e9 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -14,6 +14,7 @@ pub struct CoinInfo { pub price: Decimal, pub max_ltv: Decimal, pub liquidation_threshold: Decimal, + pub liquidation_bonus: Decimal, } #[cw_serde] diff --git a/contracts/credit-manager/tests/test_fields_vault_limit.rs b/contracts/credit-manager/tests/test_fields_vault_limit.rs index fd7b271fc..aa7cf95e0 100644 --- a/contracts/credit-manager/tests/test_fields_vault_limit.rs +++ b/contracts/credit-manager/tests/test_fields_vault_limit.rs @@ -18,6 +18,7 @@ fn test_can_only_have_a_single_vault_position() { price: Decimal::from_atomics(121u128, 3).unwrap(), max_ltv: Decimal::from_atomics(4u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), + liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), }; let degen_vault = VaultTestInfo { vault_token_denom: "udegen".to_string(), diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index bd75c1bad..4d5c6dd86 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -73,6 +73,7 @@ fn test_terra_ragnarok() { price: 100.to_dec().unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), }; let user = Addr::unchecked("user"); @@ -305,12 +306,14 @@ fn test_cannot_borrow_more_but_not_liquidatable() { price: Decimal::from_atomics(23654u128, 4).unwrap(), max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), + liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), }; let uatom_info = CoinInfo { denom: "uatom".to_string(), price: Decimal::from_atomics(102u128, 1).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(75u128, 2).unwrap(), + liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), }; let user = Addr::unchecked("user"); @@ -382,12 +385,14 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), }; let uatom_info = CoinInfo { denom: "uatom".to_string(), price: Decimal::from_atomics(7012302005u128, 3).unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), + liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), }; let user = Addr::unchecked("user"); @@ -467,12 +472,14 @@ fn test_debt_value() { price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), max_ltv: Decimal::from_atomics(3u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), + liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), }; let uatom_info = CoinInfo { denom: "uatom".to_string(), price: Decimal::from_atomics(7012302005u128, 3).unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), + liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), }; let user_a = Addr::unchecked("user_a"); diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index d5f4a0214..0d99dcefe 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -134,6 +134,7 @@ fn test_allowed_coins_set_on_instantiate() { price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), }, ]; let mock = MockEnv::new() @@ -186,14 +187,6 @@ fn test_raises_on_invalid_oracle_addr() { } } -#[test] -fn test_max_liq_bonus_set_on_instantiate() { - let mock = MockEnv::new().build().unwrap(); - let res = mock.query_config(); - let mock_default = Decimal::from_atomics(5u128, 2).unwrap(); - assert_eq!(mock_default, res.max_liquidation_bonus); -} - #[test] fn test_max_close_factor_set_on_instantiate() { let mock = MockEnv::new().build().unwrap(); diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 66b4cbe9d..4f92aa7fe 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -376,7 +376,7 @@ fn test_liquidator_left_in_unhealthy_state() { res, AboveMaxLTV { account_id: liquidator_account_id, - max_ltv_health_factor: "0.795375".to_string(), + max_ltv_health_factor: "0.805".to_string(), }, ) } @@ -439,7 +439,7 @@ fn test_debt_amount_adjusted_to_close_factor_max() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.coins.len(), 2); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(47)); + assert_eq!(osmo_balance.amount, Uint128::new(36)); let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(100)); @@ -454,7 +454,7 @@ fn test_debt_amount_adjusted_to_close_factor_max() { let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(40)); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(253)); + assert_eq!(osmo_balance.amount, Uint128::new(264)); } #[test] @@ -517,7 +517,7 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.coins.len(), 3); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(190)); + assert_eq!(osmo_balance.amount, Uint128::new(180)); let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(100)); let jake_balance = get_coin("ujake", &position.coins); @@ -534,7 +534,7 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { let jake_balance = get_coin("ujake", &position.coins); assert_eq!(jake_balance.amount, Uint128::new(39)); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(110)); + assert_eq!(osmo_balance.amount, Uint128::new(120)); } #[test] @@ -544,7 +544,6 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { let liquidator = Addr::unchecked("liquidator"); let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .max_close_factor(Decimal::from_atomics(1u128, 1).unwrap()) .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), @@ -595,7 +594,7 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.coins.len(), 2); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(47)); + assert_eq!(osmo_balance.amount, Uint128::new(36)); let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(100)); @@ -610,7 +609,7 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(47)); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(253)); + assert_eq!(osmo_balance.amount, Uint128::new(264)); } #[test] @@ -671,7 +670,7 @@ fn test_debt_amount_no_adjustment() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.coins.len(), 2); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(68)); + assert_eq!(osmo_balance.amount, Uint128::new(58)); let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(100)); @@ -684,7 +683,7 @@ fn test_debt_amount_no_adjustment() { assert_eq!(position.coins.len(), 1); assert_eq!(position.debts.len(), 0); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(232)); + assert_eq!(osmo_balance.amount, Uint128::new(242)); } #[test] diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index ff0c11e13..2945c32f3 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -3,7 +3,7 @@ use cosmwasm_std::StdError::NotFound; use cosmwasm_std::{Addr, Decimal, OverflowError, Uint128}; use mars_mock_oracle::msg::CoinPrice; -use mars_rover::adapters::vault::VaultBase; +use mars_rover::adapters::vault::{VaultBase, VaultPositionType}; use mars_rover::error::ContractError; use mars_rover::msg::execute::Action::{ Borrow, Deposit, EnterVault, LiquidateVault, RequestVaultUnlock, @@ -57,6 +57,7 @@ fn test_liquidatee_must_have_the_request_vault_position() { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: uatom.to_coin(10), request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKED, }], &[], ); @@ -113,6 +114,7 @@ fn test_liquidatee_is_not_liquidatable() { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: lp_token.to_coin(10), request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKED, }], &[], ); @@ -177,6 +179,7 @@ fn test_liquidator_does_not_have_debt_coin_in_credit_account() { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: ujake.to_coin(10), request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKED, }], &[], ); @@ -192,25 +195,18 @@ fn test_liquidator_does_not_have_debt_coin_in_credit_account() { } #[test] -fn test_liquidate_unlocked_vault() { +fn test_wrong_position_type_sent_for_unlocked_vault() { let lp_token = lp_token_info(); - let ujake = ujake_info(); let leverage_vault = unlocked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); - let liquidator = Addr::unchecked("liquidator"); - let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), ujake.clone()]) + .allowed_coins(&[lp_token.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: vec![lp_token.to_coin(300)], }) - .fund_account(AccountToFund { - addr: liquidator.clone(), - funds: vec![ujake.to_coin(10)], - }) .build() .unwrap(); @@ -227,67 +223,106 @@ fn test_liquidate_unlocked_vault() { denom: lp_token.denom.clone(), amount: Some(Uint128::new(200)), }, - Borrow(ujake.to_coin(175)), ], &[lp_token.to_coin(200)], ) .unwrap(); - mock.price_change(CoinPrice { - denom: ujake.denom.clone(), - price: Uint128::new(20).to_dec().unwrap(), - }); - + let liquidator = Addr::unchecked("liquidator"); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); - mock.update_credit_account( + let res = mock.update_credit_account( &liquidator_account_id, &liquidator, + vec![LiquidateVault { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: lp_token.to_coin(10), + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::LOCKED, + }], + &[], + ); + + assert_err(res, ContractError::MismatchedVaultType); + + let res = mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![LiquidateVault { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: lp_token.to_coin(10), + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKING, + }], + &[], + ); + + assert_err(res, ContractError::MismatchedVaultType) +} + +#[test] +fn test_wrong_position_type_sent_for_locked_vault() { + let lp_token = lp_token_info(); + let leverage_vault = locked_vault_info(); + + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, vec![ - Deposit(ujake.to_coin(10)), - LiquidateVault { - liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: ujake.to_coin(10), - request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + Deposit(lp_token.to_coin(200)), + EnterVault { + vault, + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(200)), }, ], - &[ujake.to_coin(10)], + &[lp_token.to_coin(200)], ) .unwrap(); - // Assert liquidatee's new position - let position = mock.query_positions(&liquidatee_account_id); - assert_eq!(position.vaults.len(), 1); - let vault_balance = position.vaults.first().unwrap().amount.unlocked(); - assert_eq!(vault_balance, Uint128::new(893_660)); // 1M - 106_340 - - assert_eq!(position.coins.len(), 1); - let jake_balance = get_coin("ujake", &position.coins); - assert_eq!(jake_balance.amount, Uint128::new(175)); + let liquidator = Addr::unchecked("liquidator"); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); - assert_eq!(position.debts.len(), 1); - let atom_debt = get_debt("ujake", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(166)); + let res = mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![LiquidateVault { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: lp_token.to_coin(10), + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKED, + }], + &[], + ); - // Assert liquidator's new position - let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.coins.len(), 1); - assert_eq!(position.debts.len(), 0); - let lp = get_coin(&lp_token.denom, &position.coins); - assert_eq!(lp.amount, Uint128::new(21)); + assert_err(res, ContractError::MismatchedVaultType) } #[test] -fn test_liquidate_locked_vault() { +fn test_liquidate_unlocked_vault() { let lp_token = lp_token_info(); - let atom = uatom_info(); - let leverage_vault = locked_vault_info(); + let ujake = ujake_info(); + let leverage_vault = unlocked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let liquidator = Addr::unchecked("liquidator"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone()]) + .allowed_coins(&[lp_token.clone(), ujake.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), @@ -295,7 +330,7 @@ fn test_liquidate_locked_vault() { }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: vec![atom.to_coin(35)], + funds: vec![ujake.to_coin(10)], }) .build() .unwrap(); @@ -307,20 +342,20 @@ fn test_liquidate_locked_vault() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(lp_token.to_coin(80)), + Deposit(lp_token.to_coin(200)), EnterVault { vault, denom: lp_token.denom.clone(), - amount: Some(Uint128::new(80)), + amount: Some(Uint128::new(200)), }, - Borrow(atom.to_coin(700)), + Borrow(ujake.to_coin(175)), ], - &[lp_token.to_coin(80)], + &[lp_token.to_coin(200)], ) .unwrap(); mock.price_change(CoinPrice { - denom: atom.denom.clone(), + denom: ujake.denom.clone(), price: Uint128::new(20).to_dec().unwrap(), }); @@ -330,53 +365,51 @@ fn test_liquidate_locked_vault() { &liquidator_account_id, &liquidator, vec![ - Deposit(atom.to_coin(35)), + Deposit(ujake.to_coin(10)), LiquidateVault { liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: atom.to_coin(35), + debt_coin: ujake.to_coin(10), request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKED, }, ], - &[atom.to_coin(35)], + &[ujake.to_coin(10)], ) .unwrap(); // Assert liquidatee's new position let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); - let vault_amount = position.vaults.first().unwrap().amount.clone(); - // 1M - 930,474 vault tokens liquidated = 69,526 - assert_eq!(vault_amount.locked(), Uint128::new(69_526)); - assert_eq!(vault_amount.unlocking().positions().len(), 0); - assert_eq!(vault_amount.unlocked(), Uint128::zero()); + let vault_balance = position.vaults.first().unwrap().amount.unlocked(); + assert_eq!(vault_balance, Uint128::new(883_532)); // 1M - 116_468 assert_eq!(position.coins.len(), 1); - let atom_balance = get_coin("uatom", &position.coins); - assert_eq!(atom_balance.amount, Uint128::new(700)); + let jake_balance = get_coin("ujake", &position.coins); + assert_eq!(jake_balance.amount, Uint128::new(175)); assert_eq!(position.debts.len(), 1); - let atom_debt = get_debt("uatom", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(666)); // 701 - 35 + let atom_debt = get_debt("ujake", &position.debts); + assert_eq!(atom_debt.amount, Uint128::new(166)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.debts.len(), 0); - let lp_balance = get_coin(&lp_token.denom, &position.coins); - assert_eq!(lp_balance.amount, Uint128::new(74)); + let lp = get_coin(&lp_token.denom, &position.coins); + assert_eq!(lp.amount, Uint128::new(23)); } #[test] -fn test_liquidate_unlocking_priority() { +fn test_liquidate_locked_vault() { let lp_token = lp_token_info(); - let ujake = ujake_info(); + let atom = uatom_info(); let leverage_vault = locked_vault_info(); let liquidatee = Addr::unchecked("liquidatee"); let liquidator = Addr::unchecked("liquidator"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), ujake.clone()]) + .allowed_coins(&[lp_token.clone(), atom.clone()]) .allowed_vaults(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), @@ -384,7 +417,7 @@ fn test_liquidate_unlocking_priority() { }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: vec![ujake.to_coin(100)], + funds: vec![atom.to_coin(35)], }) .build() .unwrap(); @@ -396,24 +429,20 @@ fn test_liquidate_unlocking_priority() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(lp_token.to_coin(200)), + Deposit(lp_token.to_coin(80)), EnterVault { - vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), - }, - Borrow(ujake.to_coin(175)), - RequestVaultUnlock { vault, - amount: Uint128::new(100_000), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(80)), }, + Borrow(atom.to_coin(700)), ], - &[lp_token.to_coin(200)], + &[lp_token.to_coin(80)], ) .unwrap(); mock.price_change(CoinPrice { - denom: ujake.denom.clone(), + denom: atom.denom.clone(), price: Uint128::new(20).to_dec().unwrap(), }); @@ -423,44 +452,41 @@ fn test_liquidate_unlocking_priority() { &liquidator_account_id, &liquidator, vec![ - Deposit(ujake.to_coin(10)), + Deposit(atom.to_coin(30)), LiquidateVault { liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: ujake.to_coin(10), + debt_coin: atom.to_coin(30), request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::LOCKED, }, ], - &[ujake.to_coin(10)], + &[atom.to_coin(30)], ) .unwrap(); - // Assert only unlocking position liquidated + // Assert liquidatee's new position let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_amount = position.vaults.first().unwrap().amount.clone(); - assert_eq!(vault_amount.unlocked(), Uint128::zero()); + // 1M - 835_528 vault tokens liquidated = 164,472 + assert_eq!(vault_amount.locked(), Uint128::new(164_472)); assert_eq!(vault_amount.unlocking().positions().len(), 0); - assert_eq!(vault_amount.locked(), Uint128::new(900_000)); + assert_eq!(vault_amount.unlocked(), Uint128::zero()); - mock.update_credit_account( - &liquidator_account_id, - &liquidator, - vec![ - Deposit(ujake.to_coin(10)), - LiquidateVault { - liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: ujake.to_coin(10), - request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), - }, - ], - &[ujake.to_coin(10)], - ) - .unwrap(); + assert_eq!(position.coins.len(), 1); + let atom_balance = get_coin("uatom", &position.coins); + assert_eq!(atom_balance.amount, Uint128::new(700)); - // Assert locked positions can now be liquidated - let position = mock.query_positions(&liquidatee_account_id); - let vault_amount = position.vaults.first().unwrap().amount.clone(); - assert!(vault_amount.locked() < Uint128::new(900_000)); + assert_eq!(position.debts.len(), 1); + let atom_debt = get_debt("uatom", &position.debts); + assert_eq!(atom_debt.amount, Uint128::new(671)); // 701 - 30 + + // Assert liquidator's new position + let position = mock.query_positions(&liquidator_account_id); + assert_eq!(position.coins.len(), 1); + assert_eq!(position.debts.len(), 0); + let lp_balance = get_coin(&lp_token.denom, &position.coins); + assert_eq!(lp_balance.amount, Uint128::new(66)); } #[test] @@ -537,6 +563,7 @@ fn test_liquidate_unlocking_liquidation_order() { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: ujake.to_coin(10), request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKING, }, ], &[ujake.to_coin(10)], @@ -550,11 +577,11 @@ fn test_liquidate_unlocking_liquidation_order() { assert_eq!(vault_amount.unlocked(), Uint128::zero()); assert_eq!(vault_amount.locked(), Uint128::zero()); - // Total liquidated: 21 LP tokens - // First bucket drained: -2 (all) - // Second bucket drained: -10 (all) - // Third bucket partially liquidated: -10 (out of 20) - // Fourth bucket retained: -0 (out of 168) + // Total liquidated: 24 LP tokens + // First bucket drained: 2 of 2 + // Second bucket drained: 10 of 10 + // Third bucket partially liquidated: 12 of 20 + // Fourth bucket retained: 0 of 168 assert_eq!(vault_amount.unlocking().positions().len(), 2); assert_eq!( vault_amount @@ -564,7 +591,7 @@ fn test_liquidate_unlocking_liquidation_order() { .unwrap() .coin .amount, - Uint128::new(10) + Uint128::new(8) ); assert_eq!( vault_amount @@ -589,8 +616,8 @@ fn test_liquidate_unlocking_liquidation_order() { let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.coins.len(), 1); assert_eq!(position.debts.len(), 0); - let osmo_balance = get_coin(&lp_token.denom, &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(22)); + let lp_balance = get_coin(&lp_token.denom, &position.coins); + assert_eq!(lp_balance.amount, Uint128::new(24)); } // NOTE: liquidation calculation+adjustments are quite complex, full cases in test_liquidate_coin.rs @@ -652,9 +679,10 @@ fn test_liquidation_calculation_adjustment() { LiquidateVault { liquidatee_account_id: liquidatee_account_id.clone(), // Given the request vault balance, this debt payment is too high. - // It will be adjusted to 94, the max given the request vault value + // It will be adjusted to 85, the max given the request vault value debt_coin: ujake.to_coin(500), request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKED, }, ], &[ujake.to_coin(500)], @@ -665,22 +693,22 @@ fn test_liquidation_calculation_adjustment() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_balance = position.vaults.first().unwrap().amount.unlocked(); - assert_eq!(vault_balance, Uint128::new(405)); // Vault position liquidated by 99% + assert_eq!(vault_balance, Uint128::new(10_026)); // Vault position liquidated by 99% assert_eq!(position.coins.len(), 1); let jake_balance = get_coin("ujake", &position.coins); assert_eq!(jake_balance.amount, Uint128::new(175)); assert_eq!(position.debts.len(), 1); - let ujake_debt = get_debt("ujake", &position.debts); - assert_eq!(ujake_debt.amount, Uint128::new(82)); + let jake_debt = get_debt("ujake", &position.debts); + assert_eq!(jake_debt.amount, Uint128::new(91)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.coins.len(), 2); - let osmo_balance = get_coin("ujake", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(406)); + let jake_balance = get_coin("ujake", &position.coins); + assert_eq!(jake_balance.amount, Uint128::new(415)); let atom_balance = get_coin(&lp_token.denom, &position.coins); - assert_eq!(atom_balance.amount, Uint128::new(199)); + assert_eq!(atom_balance.amount, Uint128::new(197)); assert_eq!(position.debts.len(), 0); } diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index e81bfee2e..7e08e3b0d 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -82,6 +82,7 @@ fn test_raises_when_repaying_what_is_not_owed() { price: 9.to_dec().unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), + liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), }; let user_a = Addr::unchecked("user_a"); @@ -138,6 +139,7 @@ fn test_raises_when_not_enough_assets_to_repay() { price: 9.to_dec().unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), + liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), }; let user = Addr::unchecked("user"); diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index cd59d8d98..310f4a156 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -23,7 +23,6 @@ fn test_only_owner_can_update_config() { allowed_coins: None, red_bank: None, oracle: None, - max_liquidation_bonus: None, max_close_factor: None, swapper: None, vault_configs: None, @@ -48,7 +47,6 @@ fn test_raises_on_invalid_vaults_config() { allowed_coins: None, red_bank: None, oracle: None, - max_liquidation_bonus: None, max_close_factor: None, swapper: None, vault_configs: Some(vec![VaultInstantiateConfig { @@ -74,7 +72,6 @@ fn test_raises_on_invalid_vaults_config() { allowed_coins: None, red_bank: None, oracle: None, - max_liquidation_bonus: None, max_close_factor: None, swapper: None, vault_configs: Some(vec![VaultInstantiateConfig { @@ -115,7 +112,6 @@ fn test_update_config_works_with_full_config() { let new_allowed_coins = vec!["uosmo".to_string()]; let new_oracle = OracleBase::new("new_oracle".to_string()); let new_zapper = ZapperBase::new("new_zapper".to_string()); - let new_liq_bonus = Decimal::from_atomics(17u128, 2).unwrap(); let new_close_factor = Decimal::from_atomics(32u128, 2).unwrap(); let new_swapper = SwapperBase::new("new_swapper".to_string()); @@ -127,7 +123,6 @@ fn test_update_config_works_with_full_config() { allowed_coins: Some(new_allowed_coins.clone()), red_bank: Some(new_red_bank.clone()), oracle: Some(new_oracle.clone()), - max_liquidation_bonus: Some(new_liq_bonus), max_close_factor: Some(new_close_factor), swapper: Some(new_swapper.clone()), vault_configs: Some(new_vault_configs.clone()), @@ -161,12 +156,6 @@ fn test_update_config_works_with_full_config() { assert_eq!(&new_config.zapper, new_zapper.address()); assert_ne!(new_config.zapper, original_config.zapper); - assert_eq!(new_config.max_liquidation_bonus, new_liq_bonus); - assert_ne!( - new_config.max_liquidation_bonus, - original_config.max_liquidation_bonus - ); - assert_eq!(new_config.max_close_factor, new_close_factor); assert_ne!( new_config.max_close_factor, @@ -282,10 +271,6 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { assert_eq!(new_config.red_bank, original_config.red_bank); assert_eq!(new_config.oracle, original_config.oracle); assert_eq!(new_config.zapper, original_config.zapper); - assert_eq!( - new_config.max_liquidation_bonus, - original_config.max_liquidation_bonus - ); assert_eq!( new_config.max_close_factor, original_config.max_close_factor diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs index 422370d95..e024cdada 100644 --- a/contracts/mock-red-bank/src/msg.rs +++ b/contracts/mock-red-bank/src/msg.rs @@ -11,4 +11,5 @@ pub struct CoinMarketInfo { pub denom: String, pub max_ltv: Decimal, pub liquidation_threshold: Decimal, + pub liquidation_bonus: Decimal, } diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index 04fe61fdb..35c1e5791 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -22,6 +22,7 @@ pub fn query_market(deps: Deps, denom: String) -> StdResult { Ok(Market { max_loan_to_value: market_info.max_ltv, liquidation_threshold: market_info.liquidation_threshold, + liquidation_bonus: market_info.liquidation_bonus, ..Default::default() }) } diff --git a/packages/rover/src/adapters/vault/position.rs b/packages/rover/src/adapters/vault/position.rs index e3fd070ad..49ca2a79d 100644 --- a/packages/rover/src/adapters/vault/position.rs +++ b/packages/rover/src/adapters/vault/position.rs @@ -17,3 +17,10 @@ pub struct VaultPosition { pub vault: Vault, pub amount: VaultPositionAmount, } + +#[cw_serde] +pub enum VaultPositionType { + UNLOCKED, + LOCKED, + UNLOCKING, +} diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index da4b8d5c3..7492d46d2 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; -use crate::adapters::vault::{Vault, VaultUnchecked}; +use crate::adapters::vault::{Vault, VaultPositionType, VaultUnchecked}; use crate::msg::instantiate::ConfigUpdates; #[cw_serde] @@ -77,10 +77,12 @@ pub enum Action { /// Similar to LiquidateCoin {} msg and will make similar adjustments to the request. /// The vault position will be withdrawn (and force withdrawn if a locked vault position) and /// the underlying assets will transferred to the liquidator. + /// The `VaultPositionType` will determine which bucket to liquidate from. LiquidateVault { liquidatee_account_id: String, debt_coin: Coin, request_vault: VaultUnchecked, + position_type: VaultPositionType, }, /// Perform a swapper with an exact-in amount. Requires slippage allowance %. SwapExactIn { @@ -171,6 +173,7 @@ pub enum CallbackMsg { liquidatee_account_id: String, debt_coin: Coin, request_vault: Vault, + position_type: VaultPositionType, }, /// Perform a swapper with an exact-in amount. Requires slippage allowance %. SwapExactIn { diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index fa4e3bb49..ec1e47fb5 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -20,8 +20,6 @@ pub struct InstantiateMsg { pub red_bank: RedBankUnchecked, /// The Mars Protocol oracle contract. We read prices of assets here. pub oracle: OracleUnchecked, - /// The maximum percent a liquidator can profit from a liquidation action - pub max_liquidation_bonus: Decimal, /// The maximum percent a liquidator can decrease the debt amount of the liquidatee pub max_close_factor: Decimal, /// Helper contract for making swaps @@ -64,7 +62,6 @@ pub struct ConfigUpdates { pub vault_configs: Option>, pub red_bank: Option, pub oracle: Option, - pub max_liquidation_bonus: Option, pub max_close_factor: Option, pub swapper: Option, pub zapper: Option, diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index f8f96aef6..a142e50e2 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -156,7 +156,6 @@ pub struct ConfigResponse { pub account_nft: Option, pub red_bank: String, pub oracle: String, - pub max_liquidation_bonus: Decimal, pub max_close_factor: Decimal, pub swapper: String, pub zapper: String, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 5eb5a843c..b64d9bcd4 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -10,7 +10,6 @@ "allowed_coins", "allowed_vaults", "max_close_factor", - "max_liquidation_bonus", "oracle", "owner", "red_bank", @@ -40,14 +39,6 @@ } ] }, - "max_liquidation_bonus": { - "description": "The maximum percent a liquidator can profit from a liquidation action", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, "oracle": { "description": "The Mars Protocol oracle contract. We read prices of assets here.", "allOf": [ @@ -469,7 +460,7 @@ "additionalProperties": false }, { - "description": "Pay back debt of a liquidatable rover account for a via liquidating a vault position. Similar to LiquidateCoin {} msg and will make similar adjustments to the request. The vault position will be withdrawn (and force withdrawn if a locked vault position) and the underlying assets will transferred to the liquidator.", + "description": "Pay back debt of a liquidatable rover account for a via liquidating a vault position. Similar to LiquidateCoin {} msg and will make similar adjustments to the request. The vault position will be withdrawn (and force withdrawn if a locked vault position) and the underlying assets will transferred to the liquidator. The `VaultPositionType` will determine which bucket to liquidate from.", "type": "object", "required": [ "liquidate_vault" @@ -480,6 +471,7 @@ "required": [ "debt_coin", "liquidatee_account_id", + "position_type", "request_vault" ], "properties": { @@ -489,6 +481,9 @@ "liquidatee_account_id": { "type": "string" }, + "position_type": { + "$ref": "#/definitions/VaultPositionType" + }, "request_vault": { "$ref": "#/definitions/VaultBase_for_String" } @@ -954,6 +949,7 @@ "debt_coin", "liquidatee_account_id", "liquidator_account_id", + "position_type", "request_vault" ], "properties": { @@ -966,6 +962,9 @@ "liquidator_account_id": { "type": "string" }, + "position_type": { + "$ref": "#/definitions/VaultPositionType" + }, "request_vault": { "$ref": "#/definitions/VaultBase_for_Addr" } @@ -1194,16 +1193,6 @@ } ] }, - "max_liquidation_bonus": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, "oracle": { "anyOf": [ { @@ -1343,6 +1332,14 @@ }, "additionalProperties": false }, + "VaultPositionType": { + "type": "string", + "enum": [ + "u_n_l_o_c_k_e_d", + "l_o_c_k_e_d", + "u_n_l_o_c_k_i_n_g" + ] + }, "ZapperBase_for_String": { "type": "string" } @@ -2094,7 +2091,6 @@ "type": "object", "required": [ "max_close_factor", - "max_liquidation_bonus", "oracle", "owner", "red_bank", @@ -2111,9 +2107,6 @@ "max_close_factor": { "$ref": "#/definitions/Decimal" }, - "max_liquidation_bonus": { - "$ref": "#/definitions/Decimal" - }, "oracle": { "type": "string" }, diff --git a/schemas/mars-mock-red-bank/mars-mock-red-bank.json b/schemas/mars-mock-red-bank/mars-mock-red-bank.json index 0acc5e00a..71e2d6539 100644 --- a/schemas/mars-mock-red-bank/mars-mock-red-bank.json +++ b/schemas/mars-mock-red-bank/mars-mock-red-bank.json @@ -23,6 +23,7 @@ "type": "object", "required": [ "denom", + "liquidation_bonus", "liquidation_threshold", "max_ltv" ], @@ -30,6 +31,9 @@ "denom": { "type": "string" }, + "liquidation_bonus": { + "$ref": "#/definitions/Decimal" + }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" }, diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 717d2fc88..bec027d00 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -189,7 +189,6 @@ export class Deployer { owner: this.deployerAddr, red_bank: this.config.redBankAddr, max_close_factor: this.config.maxCloseFactor.toString(), - max_liquidation_bonus: this.config.maxLiquidationBonus.toString(), swapper: this.storage.addresses.swapper!, zapper: this.storage.addresses.mockZapper!, } diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 8ca3a4c63..cb429d427 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -22,7 +22,6 @@ export const osmosisTestnetConfig: DeploymentConfig = { vaultTokenDenom: udig, vaultLockup: { time: 86400 }, // 1 day maxCloseFactor: 0.6, - maxLiquidationBonus: 0.05, depositAmount: 100, toGrantCreditLines: [ { denom: 'uosmo', amount: '100000000000' }, diff --git a/scripts/types/config.ts b/scripts/types/config.ts index ca741ca05..1e38fab4c 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -31,7 +31,6 @@ export interface DeploymentConfig { swapRoute: { token_out_denom: string; pool_id: string }[] withdrawAmount: number maxCloseFactor: number - maxLiquidationBonus: number vaultType: VaultType vaultDepositAmount: number vaultDepositCap: { diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 2599bb9a1..ea8e4a033 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -21,6 +21,7 @@ import { VaultBaseForString, ExecuteMsg, Action, + VaultPositionType, CallbackMsg, Addr, ConfigUpdates, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index b628fa4e7..456b6206e 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -22,6 +22,7 @@ import { VaultBaseForString, ExecuteMsg, Action, + VaultPositionType, CallbackMsg, Addr, ConfigUpdates, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index b0f029cef..8d46ac266 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -22,6 +22,7 @@ import { VaultBaseForString, ExecuteMsg, Action, + VaultPositionType, CallbackMsg, Addr, ConfigUpdates, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 5cefad83f..f0c19ab9e 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -15,7 +15,6 @@ export interface InstantiateMsg { allowed_coins: string[] allowed_vaults: VaultInstantiateConfig[] max_close_factor: Decimal - max_liquidation_bonus: Decimal oracle: OracleBaseForString owner: string red_bank: RedBankBaseForString @@ -107,6 +106,7 @@ export type Action = liquidate_vault: { debt_coin: Coin liquidatee_account_id: string + position_type: VaultPositionType request_vault: VaultBaseForString } } @@ -132,6 +132,7 @@ export type Action = | { refund_all_coin_balances: {} } +export type VaultPositionType = 'u_n_l_o_c_k_e_d' | 'l_o_c_k_e_d' | 'u_n_l_o_c_k_i_n_g' export type CallbackMsg = | { withdraw: { @@ -213,6 +214,7 @@ export type CallbackMsg = debt_coin: Coin liquidatee_account_id: string liquidator_account_id: string + position_type: VaultPositionType request_vault: VaultBaseForAddr } } @@ -259,7 +261,6 @@ export interface ConfigUpdates { account_nft?: string | null allowed_coins?: string[] | null max_close_factor?: Decimal | null - max_liquidation_bonus?: Decimal | null oracle?: OracleBaseForString | null owner?: string | null red_bank?: RedBankBaseForString | null @@ -398,7 +399,6 @@ export type ArrayOfString = string[] export interface ConfigResponse { account_nft?: string | null max_close_factor: Decimal - max_liquidation_bonus: Decimal oracle: string owner: string red_bank: string diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index d3227a844..3364364a7 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -11,6 +11,7 @@ export interface InstantiateMsg { } export interface CoinMarketInfo { denom: string + liquidation_bonus: Decimal liquidation_threshold: Decimal max_ltv: Decimal } From 3d6573d9b532eb5b719e2fd787ad586f11bf360a Mon Sep 17 00:00:00 2001 From: piobab Date: Fri, 18 Nov 2022 09:28:22 +0100 Subject: [PATCH 081/218] Mp 1702 fix twap osmosis (#43) * MP-1702. Add helpers for TWAP. * MP-1702. Fix tests with TWAP. * MP-1702. Add Twap module. * Fix estimate. Remove TWAP custom module. --- contracts/swapper/osmosis/tests/helpers.rs | 81 ++++++++++++++++++- .../osmosis/tests/test_enumerate_routes.rs | 2 + .../swapper/osmosis/tests/test_estimate.rs | 33 ++++++-- contracts/swapper/osmosis/tests/test_swap.rs | 22 ++++- 4 files changed, 127 insertions(+), 11 deletions(-) diff --git a/contracts/swapper/osmosis/tests/helpers.rs b/contracts/swapper/osmosis/tests/helpers.rs index 6ebe01472..150a95b9e 100644 --- a/contracts/swapper/osmosis/tests/helpers.rs +++ b/contracts/swapper/osmosis/tests/helpers.rs @@ -1,8 +1,14 @@ +use cosmwasm_std::{Coin, Decimal, Uint128}; +use osmosis_std::types::osmosis::gamm::v1beta1::{ + MsgSwapExactAmountIn, MsgSwapExactAmountInResponse, SwapAmountInRoute, +}; use std::fmt::Display; use std::str::FromStr; use osmosis_testing::cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest; -use osmosis_testing::{Account, Bank, OsmosisTestApp, RunnerError, SigningAccount, Wasm}; +use osmosis_testing::{ + Account, Bank, ExecuteResponse, Gamm, OsmosisTestApp, Runner, RunnerError, SigningAccount, Wasm, +}; use mars_rover::adapters::swap::InstantiateMsg; @@ -38,6 +44,79 @@ pub fn instantiate_contract(wasm: &Wasm, owner: &SigningAccount) .address } +/// Every execution creates new block and block timestamp will +5 secs from last block +/// (see https://github.com/osmosis-labs/osmosis-rust/issues/53#issuecomment-1311451418). +/// +/// We need to swap n times to pass TWAP_WINDOW_SIZE_SECONDS (10 min). Every swap moves block 5 sec so +/// n = TWAP_WINDOW_SIZE_SECONDS / 5 sec = 600 sec / 5 sec = 120. +/// We need to swap at least 120 times to create historical index for TWAP. +pub fn swap_to_create_twap_records( + app: &OsmosisTestApp, + signer: &SigningAccount, + pool_id: u64, + coin_in: Coin, + denom_out: &str, +) { + swap_n_times(app, signer, pool_id, coin_in, denom_out, 120u64); +} + +pub fn swap_n_times( + app: &OsmosisTestApp, + signer: &SigningAccount, + pool_id: u64, + coin_in: Coin, + denom_out: &str, + n: u64, +) { + for _ in 0..n { + swap(app, signer, pool_id, coin_in.clone(), denom_out); + } +} + +fn swap( + app: &OsmosisTestApp, + signer: &SigningAccount, + pool_id: u64, + coin_in: Coin, + denom_out: &str, +) -> ExecuteResponse { + app.execute::<_, MsgSwapExactAmountInResponse>( + MsgSwapExactAmountIn { + sender: signer.address(), + routes: vec![SwapAmountInRoute { + pool_id, + token_out_denom: denom_out.to_string(), + }], + token_in: Some(coin_in.into()), + token_out_min_amount: "1".to_string(), + }, + MsgSwapExactAmountIn::TYPE_URL, + signer, + ) + .unwrap() +} + +/// Query price for 1 denom from pool_id (quoted in second denom from the pool). +/// +/// Example: +/// pool consists of: 250 uosmo and 100 uatom +/// query price for uatom so 1 uatom = 2.5 uosmo +pub fn query_price_from_pool(gamm: &Gamm, pool_id: u64, denom: &str) -> Decimal { + let pool_assets = &gamm.query_pool(pool_id).unwrap().pool_assets; + let coin_1 = pool_assets[0].token.as_ref().unwrap(); + let coin_2 = &pool_assets[1].token.as_ref().unwrap(); + let coin_1_amt = Uint128::from_str(&coin_1.amount).unwrap(); + let coin_2_amt = Uint128::from_str(&coin_2.amount).unwrap(); + + if coin_1.denom == denom { + Decimal::from_ratio(coin_2_amt, coin_1_amt) + } else if coin_2.denom == denom { + Decimal::from_ratio(coin_1_amt, coin_2_amt) + } else { + panic!("{} not found in the pool {}", denom, pool_id) + } +} + pub fn query_balance(bank: &Bank, addr: &str, denom: &str) -> u128 { bank.query_balance(&QueryBalanceRequest { address: addr.to_string(), diff --git a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs index 723cc679b..bf154bc54 100644 --- a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs +++ b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs @@ -1,3 +1,5 @@ +extern crate core; + use crate::helpers::instantiate_contract; use cosmwasm_std::coin; use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; diff --git a/contracts/swapper/osmosis/tests/test_estimate.rs b/contracts/swapper/osmosis/tests/test_estimate.rs index 61e6baf37..8d65af0a2 100644 --- a/contracts/swapper/osmosis/tests/test_estimate.rs +++ b/contracts/swapper/osmosis/tests/test_estimate.rs @@ -5,7 +5,9 @@ use osmosis_testing::{Gamm, Module, OsmosisTestApp, RunnerResult, Wasm}; use mars_rover::adapters::swap::{EstimateExactInSwapResponse, ExecuteMsg, QueryMsg}; use mars_swapper_osmosis::route::OsmosisRoute; -use crate::helpers::{assert_err, instantiate_contract}; +use crate::helpers::{ + assert_err, instantiate_contract, query_price_from_pool, swap_to_create_twap_records, +}; pub mod helpers; @@ -34,7 +36,6 @@ fn test_error_on_route_not_found() { } #[test] -#[ignore] // FIXME: TWAP doesn't work on osmosis-testing - fix in progress fn test_estimate_swap_one_step() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -58,6 +59,14 @@ fn test_estimate_swap_one_step() { .data .pool_id; + swap_to_create_twap_records( + &app, + &signer, + pool_atom_osmo, + coin(10u128, "uatom"), + "uosmo", + ); + wasm.execute( &contract_addr, &ExecuteMsg::SetRoute { @@ -73,20 +82,23 @@ fn test_estimate_swap_one_step() { ) .unwrap(); + let coin_in_amount = Uint128::from(1000u128); + let uosmo_price = query_price_from_pool(&gamm, pool_atom_osmo, "uosmo"); + let expected_output = coin_in_amount * uosmo_price; + let res: EstimateExactInSwapResponse = wasm .query( &contract_addr, &QueryMsg::EstimateExactInSwap { - coin_in: coin(1000, "uosmo"), + coin_in: coin(coin_in_amount.u128(), "uosmo"), denom_out: "uatom".to_string(), }, ) .unwrap(); - assert_eq!(res.amount, Uint128::new(250)); + assert_eq!(res.amount, expected_output); } #[test] -#[ignore] // FIXME: TWAP doesn't work on osmosis-testing - fix in progress fn test_estimate_swap_multi_step() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -122,6 +134,8 @@ fn test_estimate_swap_multi_step() { .data .pool_id; + swap_to_create_twap_records(&app, &signer, pool_atom_osmo, coin(4u128, "uosmo"), "uatom"); + wasm.execute( &contract_addr, &ExecuteMsg::SetRoute { @@ -164,6 +178,11 @@ fn test_estimate_swap_multi_step() { ) .unwrap(); + let coin_in_amount = Uint128::from(1000u128); + let uatom_price = query_price_from_pool(&gamm, pool_atom_osmo, "uatom"); + let uosmo_price = query_price_from_pool(&gamm, pool_osmo_usdc, "uosmo"); + let expected_output = coin_in_amount * uatom_price * uosmo_price; + // atom/usdc = (price for atom/osmo) * (price for osmo/usdc) // usdc_out_amount = (atom amount) * (price for atom/usdc) // @@ -176,10 +195,10 @@ fn test_estimate_swap_multi_step() { .query( &contract_addr, &QueryMsg::EstimateExactInSwap { - coin_in: coin(1000, "uatom"), + coin_in: coin(coin_in_amount.u128(), "uatom"), denom_out: "uusdc".to_string(), }, ) .unwrap(); - assert_eq!(res.amount, Uint128::new(2500)); + assert_eq!(res.amount, expected_output); } diff --git a/contracts/swapper/osmosis/tests/test_swap.rs b/contracts/swapper/osmosis/tests/test_swap.rs index 2747c074f..7bc49aa0e 100644 --- a/contracts/swapper/osmosis/tests/test_swap.rs +++ b/contracts/swapper/osmosis/tests/test_swap.rs @@ -7,7 +7,9 @@ use mars_rover::error::ContractError as RoverError; use mars_swapper_base::ContractError; use mars_swapper_osmosis::route::OsmosisRoute; -use crate::helpers::{assert_err, instantiate_contract, query_balance}; +use crate::helpers::{ + assert_err, instantiate_contract, query_balance, swap_to_create_twap_records, +}; pub mod helpers; @@ -47,7 +49,6 @@ fn test_transfer_callback_only_internal() { } #[test] -#[ignore] // FIXME: TWAP doesn't work on osmosis-testing - fix in progress fn test_swap_exact_in_slippage_too_high() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -72,6 +73,14 @@ fn test_swap_exact_in_slippage_too_high() { .data .pool_id; + swap_to_create_twap_records( + &app, + &signer, + pool_mars_osmo, + coin(10u128, "umars"), + "uosmo", + ); + let route = OsmosisRoute(vec![SwapAmountInRoute { pool_id: pool_mars_osmo, token_out_denom: "uosmo".to_string(), @@ -110,7 +119,6 @@ fn test_swap_exact_in_slippage_too_high() { } #[test] -#[ignore] // FIXME: TWAP doesn't work on osmosis-testing - fix in progress fn test_swap_exact_in_success() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -135,6 +143,14 @@ fn test_swap_exact_in_success() { .data .pool_id; + swap_to_create_twap_records( + &app, + &signer, + pool_mars_osmo, + coin(10u128, "umars"), + "uosmo", + ); + wasm.execute( &contract_addr, &ExecuteMsg::SetRoute { From 4618bc566ad7a5003a2d8dd8743176b215e48e3a Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 1 Dec 2022 01:16:03 +0100 Subject: [PATCH 082/218] Burn guard (#47) * Burn guard for NFTs * old tests fixed * finishing tests * mock env instantiation fix * update build scripts * Config update + test improvements * lint fixes --- Cargo.lock | 113 +- Cargo.toml | 14 +- contracts/account-nft/Cargo.toml | 7 +- contracts/account-nft/examples/schema.rs | 3 +- contracts/account-nft/src/config.rs | 26 + contracts/account-nft/src/contract.rs | 37 +- contracts/account-nft/src/error.rs | 23 + contracts/account-nft/src/execute.rs | 109 +- contracts/account-nft/src/lib.rs | 2 + contracts/account-nft/src/msg/execute.rs | 21 +- contracts/account-nft/src/msg/instantiate.rs | 36 + contracts/account-nft/src/msg/mod.rs | 2 + contracts/account-nft/src/msg/query.rs | 4 +- contracts/account-nft/src/query.rs | 7 +- contracts/account-nft/src/state.rs | 5 +- contracts/account-nft/tests/helpers.rs | 58 - .../tests/helpers/health_responses.rs | 33 + .../tests/helpers/mock_contracts.rs | 20 + .../account-nft/tests/helpers/mock_env.rs | 144 ++ .../tests/helpers/mock_env_builder.rs | 115 ++ contracts/account-nft/tests/helpers/mod.rs | 9 + .../account-nft/tests/test_burn_allowance.rs | 108 ++ contracts/account-nft/tests/test_mint.rs | 134 +- contracts/account-nft/tests/test_ownership.rs | 111 -- .../account-nft/tests/test_proposed_minter.rs | 72 + .../account-nft/tests/test_update_config.rs | 44 + contracts/credit-manager/src/execute.rs | 8 +- .../credit-manager/tests/helpers/mock_env.rs | 46 +- .../tests/test_update_config.rs | 4 +- contracts/mock-credit-manager/Cargo.toml | 26 + .../mock-credit-manager/examples/schema.rs | 12 + contracts/mock-credit-manager/src/contract.rs | 43 + contracts/mock-credit-manager/src/execute.rs | 12 + contracts/mock-credit-manager/src/lib.rs | 5 + contracts/mock-credit-manager/src/msg.rs | 10 + contracts/mock-credit-manager/src/query.rs | 7 + contracts/mock-credit-manager/src/state.rs | 5 + schema.Makefile.toml | 1 + .../mars-account-nft/mars-account-nft.json | 138 +- .../mars-mock-credit-manager.json | 1292 +++++++++++++++++ scripts/deploy/base/deployer.ts | 2 + scripts/deploy/base/index.ts | 2 +- scripts/deploy/osmosis/config.ts | 1 + scripts/package.json | 20 +- scripts/types/config.ts | 1 + .../mars-account-nft/MarsAccountNft.client.ts | 108 +- .../MarsAccountNft.message-composer.ts | 98 +- .../MarsAccountNft.react-query.ts | 564 ------- .../mars-account-nft/MarsAccountNft.types.ts | 31 +- .../generated/mars-account-nft/bundle.ts | 14 - .../MarsCreditManager.client.ts | 2 +- .../MarsCreditManager.message-composer.ts | 2 +- .../MarsCreditManager.types.ts | 2 +- .../generated/mars-credit-manager/bundle.ts | 14 - .../MarsMockCreditManager.client.ts | 344 +++++ .../MarsMockCreditManager.message-composer.ts | 99 ++ .../MarsMockCreditManager.react-query.ts} | 302 ++-- .../MarsMockCreditManager.types.ts | 197 +++ .../mars-mock-credit-manager/bundle.ts | 14 + .../mars-mock-oracle/MarsMockOracle.client.ts | 2 +- .../MarsMockOracle.message-composer.ts | 2 +- .../MarsMockOracle.react-query.ts | 8 +- .../mars-mock-oracle/MarsMockOracle.types.ts | 2 +- .../generated/mars-mock-oracle/bundle.ts | 12 +- .../MarsMockRedBank.client.ts | 2 +- .../MarsMockRedBank.message-composer.ts | 2 +- .../MarsMockRedBank.react-query.ts | 3 +- .../MarsMockRedBank.types.ts | 2 +- .../generated/mars-mock-red-bank/bundle.ts | 12 +- .../mars-mock-vault/MarsMockVault.client.ts | 2 +- .../MarsMockVault.message-composer.ts | 2 +- .../MarsMockVault.react-query.ts | 302 ---- .../mars-mock-vault/MarsMockVault.types.ts | 2 +- .../types/generated/mars-mock-vault/bundle.ts | 14 - .../mars-mock-zapper/MarsMockZapper.client.ts | 2 +- .../MarsMockZapper.message-composer.ts | 2 +- .../MarsMockZapper.react-query.ts | 3 +- .../mars-mock-zapper/MarsMockZapper.types.ts | 2 +- .../generated/mars-mock-zapper/bundle.ts | 12 +- .../MarsOracleAdapter.client.ts | 2 +- .../MarsOracleAdapter.message-composer.ts | 2 +- .../MarsOracleAdapter.react-query.ts | 3 +- .../MarsOracleAdapter.types.ts | 2 +- .../generated/mars-oracle-adapter/bundle.ts | 12 +- .../MarsSwapperBase.client.ts | 2 +- .../MarsSwapperBase.message-composer.ts | 2 +- .../MarsSwapperBase.react-query.ts | 3 +- .../MarsSwapperBase.types.ts | 2 +- .../generated/mars-swapper-base/bundle.ts | 12 +- scripts/yarn.lock | 925 ++++++------ 90 files changed, 3909 insertions(+), 2131 deletions(-) create mode 100644 contracts/account-nft/src/config.rs create mode 100644 contracts/account-nft/src/error.rs create mode 100644 contracts/account-nft/src/msg/instantiate.rs delete mode 100644 contracts/account-nft/tests/helpers.rs create mode 100644 contracts/account-nft/tests/helpers/health_responses.rs create mode 100644 contracts/account-nft/tests/helpers/mock_contracts.rs create mode 100644 contracts/account-nft/tests/helpers/mock_env.rs create mode 100644 contracts/account-nft/tests/helpers/mock_env_builder.rs create mode 100644 contracts/account-nft/tests/helpers/mod.rs create mode 100644 contracts/account-nft/tests/test_burn_allowance.rs delete mode 100644 contracts/account-nft/tests/test_ownership.rs create mode 100644 contracts/account-nft/tests/test_proposed_minter.rs create mode 100644 contracts/account-nft/tests/test_update_config.rs create mode 100644 contracts/mock-credit-manager/Cargo.toml create mode 100644 contracts/mock-credit-manager/examples/schema.rs create mode 100644 contracts/mock-credit-manager/src/contract.rs create mode 100644 contracts/mock-credit-manager/src/execute.rs create mode 100644 contracts/mock-credit-manager/src/lib.rs create mode 100644 contracts/mock-credit-manager/src/msg.rs create mode 100644 contracts/mock-credit-manager/src/query.rs create mode 100644 contracts/mock-credit-manager/src/state.rs create mode 100644 schemas/mars-mock-credit-manager/mars-mock-credit-manager.json delete mode 100644 scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts delete mode 100644 scripts/types/generated/mars-account-nft/bundle.ts delete mode 100644 scripts/types/generated/mars-credit-manager/bundle.ts create mode 100644 scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts create mode 100644 scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts rename scripts/types/generated/{mars-credit-manager/MarsCreditManager.react-query.ts => mars-mock-credit-manager/MarsMockCreditManager.react-query.ts} (52%) create mode 100644 scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts create mode 100644 scripts/types/generated/mars-mock-credit-manager/bundle.ts delete mode 100644 scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts delete mode 100644 scripts/types/generated/mars-mock-vault/bundle.ts diff --git a/Cargo.lock b/Cargo.lock index 40077d1f9..590ac8014 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -328,16 +328,6 @@ dependencies = [ "uint", ] -[[package]] -name = "cosmwasm-storage" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b61fcfef87d15af0263e2e4d792af80355929674a3b4e29ffb3c898ec6e25852" -dependencies = [ - "cosmwasm-std", - "serde", -] - [[package]] name = "cosmwasm-vault-standard" version = "0.1.0" @@ -346,7 +336,7 @@ checksum = "d48847f2c22b96d9659ff9d0b67403ed1d5cbe5470e65d293c4c455de05b237b" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-utils 0.16.0", + "cw-utils", "schemars", "serde", ] @@ -408,20 +398,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05dad9dc2e6e9ab784bb5598d8528f4afe014b7b0ec05e1f466fe2e11aca368c" dependencies = [ "cosmwasm-std", - "cw-storage-plus 0.16.0", + "cw-storage-plus", ] [[package]] name = "cw-multi-test" -version = "0.13.4" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f9a8ab7c3c29ec93cb7a39ce4b14a05e053153b4a17ef7cf2246af1b7c087e" +checksum = "7192aec80d0c01a0e5941392eea7e2b7e212ee74ca7f430bfdc899420c055ef6" dependencies = [ "anyhow", "cosmwasm-std", - "cosmwasm-storage", - "cw-storage-plus 0.13.4", - "cw-utils 0.13.4", + "cw-storage-plus", + "cw-utils", "derivative", "itertools", "prost 0.9.0", @@ -430,17 +419,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw-storage-plus" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - [[package]] name = "cw-storage-plus" version = "0.16.0" @@ -452,18 +430,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cw-utils" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw-utils" version = "0.16.0" @@ -487,7 +453,7 @@ checksum = "91398113b806f4d2a8d5f8d05684704a20ffd5968bf87e3473e1973710b884ad" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.16.0", + "cw-storage-plus", "schemars", "serde", ] @@ -500,7 +466,7 @@ checksum = "94a1ea6e6277bdd6dfc043a9b1380697fe29d6e24b072597439523658d21d791" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-utils 0.16.0", + "cw-utils", "schemars", "serde", ] @@ -513,8 +479,8 @@ checksum = "77518e27431d43214cff4cdfbd788a7508f68d9b1f32389e6fce513e7eaccbef" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", + "cw-storage-plus", + "cw-utils", "cw2", "cw721", "schemars", @@ -840,9 +806,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -939,10 +905,13 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus 0.16.0", + "cw-storage-plus", "cw2", "cw721", "cw721-base", + "mars-mock-credit-manager", + "mars-rover", + "thiserror", ] [[package]] @@ -955,8 +924,8 @@ dependencies = [ "cosmwasm-vault-standard", "cw-item-set", "cw-multi-test", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", + "cw-storage-plus", + "cw-utils", "cw2", "cw721", "cw721-base", @@ -981,13 +950,25 @@ dependencies = [ "mars-outpost", ] +[[package]] +name = "mars-mock-credit-manager" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "mars-rover", + "thiserror", +] + [[package]] name = "mars-mock-oracle" version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.16.0", + "cw-storage-plus", "mars-outpost", ] @@ -997,7 +978,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.16.0", + "cw-storage-plus", "mars-outpost", ] @@ -1008,8 +989,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vault-standard", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", + "cw-storage-plus", + "cw-utils", "mars-rover", "thiserror", ] @@ -1020,8 +1001,8 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", + "cw-storage-plus", + "cw-utils", "mars-rover", "thiserror", ] @@ -1034,8 +1015,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", + "cw-storage-plus", + "cw-utils", "cw2", "mars-mock-oracle", "mars-mock-vault", @@ -1069,8 +1050,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vault-standard", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", + "cw-storage-plus", + "cw-utils", "mars-health", "mars-mock-oracle", "mars-mock-red-bank", @@ -1086,7 +1067,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.16.0", + "cw-storage-plus", "mars-rover", "schemars", "serde", @@ -1100,7 +1081,7 @@ dependencies = [ "anyhow", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus 0.16.0", + "cw-storage-plus", "mars-rover", "mars-swapper-base", "thiserror", @@ -1113,7 +1094,7 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.16.0", + "cw-storage-plus", "cw2", "mars-osmosis", "mars-rover", @@ -1199,9 +1180,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "os_str_bytes" -version = "6.4.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "osmosis-std" diff --git a/Cargo.toml b/Cargo.toml index 96f71be2e..b1f5095ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "contracts/mock-red-bank", "contracts/mock-vault", "contracts/mock-zapper", + "contracts/mock-credit-manager", ] [workspace.package] @@ -37,7 +38,7 @@ cw2 = "0.16" cw721 = "0.16" cw721-base = { version = "0.16", features = ["library"] } cw-item-set = { version = "0.6", default-features = false, features = ["iterator"] } -cw-multi-test = "0.13" +cw-multi-test = "0.16" cw-utils = "0.16" cw-storage-plus = "0.16" osmosis-std = "0.12" @@ -57,11 +58,12 @@ mars-oracle-adapter = { version = "1.0.0", path = "contracts/oracle-adapter", fe mars-swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } # mocks -mars-mock-oracle = { version = "1.0.0", path = "./contracts/mock-oracle", features = ["library"] } -mars-mock-red-bank = { version = "1.0.0", path = "./contracts/mock-red-bank", features = ["library"] } -mars-mock-vault = { version = "1.0.0", path = "./contracts/mock-vault", features = ["library"] } -mars-mock-zapper = { version = "1.0.0", path = "./contracts/mock-zapper", features = ["library"] } -mars-swapper-mock = { version = "1.0.0", path = "./contracts/swapper/mock", features = ["library"] } +mars-mock-credit-manager = { version = "1.0.0", path = "./contracts/mock-credit-manager", features = ["library"] } +mars-mock-oracle = { version = "1.0.0", path = "./contracts/mock-oracle", features = ["library"] } +mars-mock-red-bank = { version = "1.0.0", path = "./contracts/mock-red-bank", features = ["library"] } +mars-mock-vault = { version = "1.0.0", path = "./contracts/mock-vault", features = ["library"] } +mars-mock-zapper = { version = "1.0.0", path = "./contracts/mock-zapper", features = ["library"] } +mars-swapper-mock = { version = "1.0.0", path = "./contracts/swapper/mock", features = ["library"] } [profile.release] opt-level = 3 diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index c97a68917..626e2f700 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -24,7 +24,10 @@ cw2 = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true } cw-storage-plus = { workspace = true } +mars-rover = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] -anyhow = { workspace = true } -cw-multi-test = { workspace = true } +anyhow = { workspace = true } +cw-multi-test = { workspace = true } +mars-mock-credit-manager = { workspace = true } diff --git a/contracts/account-nft/examples/schema.rs b/contracts/account-nft/examples/schema.rs index 08ad55c8a..b41c869ad 100644 --- a/contracts/account-nft/examples/schema.rs +++ b/contracts/account-nft/examples/schema.rs @@ -1,6 +1,5 @@ use cosmwasm_schema::write_api; -use cw721_base::msg::InstantiateMsg; -use mars_account_nft::msg::{ExecuteMsg, QueryMsg}; +use mars_account_nft::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/account-nft/src/config.rs b/contracts/account-nft/src/config.rs new file mode 100644 index 000000000..facffe1dc --- /dev/null +++ b/contracts/account-nft/src/config.rs @@ -0,0 +1,26 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Decimal}; + +#[cw_serde] +pub struct ConfigBase { + pub max_value_for_burn: Decimal, + pub proposed_new_minter: Option, +} + +pub type Config = ConfigBase; +pub type UncheckedConfig = ConfigBase; + +impl From for UncheckedConfig { + fn from(config: Config) -> Self { + Self { + max_value_for_burn: config.max_value_for_burn, + proposed_new_minter: config.proposed_new_minter.map(Into::into), + } + } +} + +#[cw_serde] +pub struct ConfigUpdates { + pub max_value_for_burn: Option, + pub proposed_new_minter: Option, +} diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 79b113c34..906563255 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -1,17 +1,18 @@ -use std::convert::TryInto; - #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; use cw2::set_contract_version; -use cw721_base::{ContractError, Cw721Contract, InstantiateMsg}; +use cw721_base::Cw721Contract; +use std::convert::TryInto; -use crate::execute::{accept_ownership, mint, propose_new_owner}; -use crate::msg::{ExecuteMsg, QueryMsg}; -use crate::query::query_proposed_new_owner; -use crate::state::NEXT_ID; +use crate::config::Config; +use crate::error::ContractError; +use crate::execute::{accept_ownership, burn, mint, update_config}; +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::query::query_config; +use crate::state::{CONFIG, NEXT_ID}; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -32,7 +33,16 @@ pub fn instantiate( CONTRACT_VERSION, )?; NEXT_ID.save(deps.storage, &1)?; - Parent::default().instantiate(deps, env, info, msg) + + CONFIG.save( + deps.storage, + &Config { + max_value_for_burn: msg.max_value_for_burn, + proposed_new_minter: None, + }, + )?; + + Parent::default().instantiate(deps, env, info, msg.into()) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -44,16 +54,19 @@ pub fn execute( ) -> Result { match msg { ExecuteMsg::Mint { user } => mint(deps, env, info, &user), - ExecuteMsg::ProposeNewOwner { new_owner } => propose_new_owner(deps, info, &new_owner), - ExecuteMsg::AcceptOwnership {} => accept_ownership(deps, info), - _ => Parent::default().execute(deps, env, info, msg.try_into()?), + ExecuteMsg::UpdateConfig { updates } => update_config(deps, info, updates), + ExecuteMsg::AcceptMinterRole {} => accept_ownership(deps, info), + ExecuteMsg::Burn { token_id } => burn(deps, env, info, token_id), + _ => Parent::default() + .execute(deps, env, info, msg.try_into()?) + .map_err(Into::into), } } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::ProposedNewOwner {} => to_binary(&query_proposed_new_owner(deps)?), + QueryMsg::Config {} => to_binary(&query_config(deps)?), _ => Parent::default().query(deps, env, msg.try_into()?), } } diff --git a/contracts/account-nft/src/error.rs b/contracts/account-nft/src/error.rs new file mode 100644 index 000000000..90c9a3827 --- /dev/null +++ b/contracts/account-nft/src/error.rs @@ -0,0 +1,23 @@ +use cosmwasm_std::{Decimal, OverflowError, StdError}; +use cw721_base::ContractError as Base721Error; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + BaseError(#[from] Base721Error), + + #[error("{0}")] + Overflow(#[from] OverflowError), + + #[error( + "Account balances too high. Collateral + Debts = {current_balances:?}. Max allowed is {max_value_allowed:?}" + )] + BurnNotAllowed { + current_balances: Decimal, + max_value_allowed: Decimal, + }, +} diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index 85dc7a5f0..357a59a8a 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -1,8 +1,17 @@ -use cosmwasm_std::{DepsMut, Empty, Env, Event, MessageInfo, Response}; -use cw721_base::{ContractError, MintMsg}; +use cosmwasm_std::{ + to_binary, DepsMut, Empty, Env, MessageInfo, QueryRequest, Response, WasmQuery, +}; +use cw721::Cw721Execute; +use cw721_base::MintMsg; +use mars_rover::msg::query::HealthResponse; +use mars_rover::msg::QueryMsg::Health; + +use crate::config::ConfigUpdates; use crate::contract::Parent; -use crate::state::{NEXT_ID, PENDING_OWNER}; +use crate::error::ContractError; +use crate::error::ContractError::{BaseError, BurnNotAllowed}; +use crate::state::{CONFIG, NEXT_ID}; pub fn mint( deps: DepsMut, @@ -19,42 +28,90 @@ pub fn mint( }; NEXT_ID.save(deps.storage, &(next_id + 1))?; - Parent::default().mint(deps, env, info, mint_msg_override) + Parent::default() + .mint(deps, env, info, mint_msg_override) + .map_err(Into::into) } -pub fn propose_new_owner( +/// Checks first to ensure the balance of debts and collateral does not exceed the config +/// set amount. This is to ensure accounts are not accidentally deleted. +pub fn burn( deps: DepsMut, + env: Env, info: MessageInfo, - new_owner: &str, + token_id: String, ) -> Result { - let proposed_owner_addr = deps.api.addr_validate(new_owner)?; - let current_owner = Parent::default().minter.load(deps.storage)?; + let response: HealthResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + // Expects the minter to be the credit manager + contract_addr: Parent::default().minter.load(deps.storage)?.into(), + msg: to_binary(&Health { + account_id: token_id.clone(), + })?, + }))?; - if info.sender != current_owner { - return Err(ContractError::Unauthorized {}); + let max_value_allowed = CONFIG.load(deps.storage)?.max_value_for_burn; + let current_balances = response + .total_debt_value + .checked_add(response.total_collateral_value)?; + if current_balances > max_value_allowed { + return Err(BurnNotAllowed { + current_balances, + max_value_allowed, + }); } - PENDING_OWNER.save(deps.storage, &proposed_owner_addr)?; - - Ok(Response::new().add_attribute("action", "rover/account_nft/propose_new_owner")) + Parent::default() + .burn(deps, env, info, token_id) + .map_err(Into::into) } -pub fn accept_ownership(deps: DepsMut, info: MessageInfo) -> Result { - let pending_owner = PENDING_OWNER.load(deps.storage)?; - let previous_owner = Parent::default().minter.load(deps.storage)?; +pub fn update_config( + deps: DepsMut, + info: MessageInfo, + updates: ConfigUpdates, +) -> Result { + let current_minter = Parent::default().minter.load(deps.storage)?; + if info.sender != current_minter { + return Err(BaseError(cw721_base::ContractError::Unauthorized {})); + } - if info.sender != pending_owner { - return Err(ContractError::Unauthorized {}); + let mut response = Response::new().add_attribute("action", "rover/account_nft/update_config"); + let mut config = CONFIG.load(deps.storage)?; + + if let Some(max) = updates.max_value_for_burn { + config.max_value_for_burn = max; + response = response + .add_attribute("key", "max_value_for_burn") + .add_attribute("value", max.to_string()); } - Parent::default() - .minter - .save(deps.storage, &pending_owner)?; + if let Some(addr) = updates.proposed_new_minter { + let validated = deps.api.addr_validate(&addr)?; + config.proposed_new_minter = Some(validated); + response = response + .add_attribute("key", "pending_minter") + .add_attribute("value", addr); + } - PENDING_OWNER.remove(deps.storage); + CONFIG.save(deps.storage, &config)?; + + Ok(response) +} - let event = Event::new("rover/account_nft/accept_ownership") - .add_attribute("previous_owner", previous_owner) - .add_attribute("new_owner", pending_owner); - Ok(Response::new().add_event(event)) +pub fn accept_ownership(deps: DepsMut, info: MessageInfo) -> Result { + let mut config = CONFIG.load(deps.storage)?; + let previous_minter = Parent::default().minter.load(deps.storage)?; + + match config.proposed_new_minter { + Some(addr) if addr == info.sender => { + Parent::default().minter.save(deps.storage, &addr)?; + config.proposed_new_minter = None; + CONFIG.save(deps.storage, &config)?; + + Ok(Response::new() + .add_attribute("previous_minter", previous_minter) + .add_attribute("new_minter", addr)) + } + _ => Err(BaseError(cw721_base::ContractError::Unauthorized {})), + } } diff --git a/contracts/account-nft/src/lib.rs b/contracts/account-nft/src/lib.rs index 735a7a3ea..3d18ee96c 100644 --- a/contracts/account-nft/src/lib.rs +++ b/contracts/account-nft/src/lib.rs @@ -1,4 +1,6 @@ +pub mod config; pub mod contract; +pub mod error; pub mod execute; pub mod msg; pub mod query; diff --git a/contracts/account-nft/src/msg/execute.rs b/contracts/account-nft/src/msg/execute.rs index c95886d27..822a4318e 100644 --- a/contracts/account-nft/src/msg/execute.rs +++ b/contracts/account-nft/src/msg/execute.rs @@ -1,25 +1,30 @@ use cosmwasm_schema::cw_serde; use std::convert::TryInto; +use crate::config::ConfigUpdates; +use crate::error::ContractError; use cosmwasm_std::{Binary, Empty, StdError}; use cw721::Expiration; -use cw721_base::{ContractError, ExecuteMsg as ParentExecuteMsg}; +use cw721_base::ExecuteMsg as ParentExecuteMsg; #[cw_serde] pub enum ExecuteMsg { //-------------------------------------------------------------------------------------------------- // Extended and overridden messages //-------------------------------------------------------------------------------------------------- - /// Due to some chains being permissioned via governance, we must instantiate this contract first - /// and give ownership access to Rover contract with this action after both are independently deployed. - ProposeNewOwner { new_owner: String }, + /// Update config in storage. Only minter can execute. + UpdateConfig { updates: ConfigUpdates }, - /// Accept the proposed ownership transfer - AcceptOwnership {}, + /// Accept the proposed minter role. Only the proposed new minter can execute. + AcceptMinterRole {}, /// Mint a new NFT to the specified user; can only be called by the contract minter Mint { user: String }, + /// Burn an NFT the sender has access to. Will attempt to query the Credit Manager first + /// to ensure the balance is below the config set threshold. + Burn { token_id: String }, + //-------------------------------------------------------------------------------------------------- // Base cw721 messages //-------------------------------------------------------------------------------------------------- @@ -49,9 +54,6 @@ pub enum ExecuteMsg { }, /// Remove previously granted ApproveAll permission RevokeAll { operator: String }, - - /// Burn an NFT the sender has access to - Burn { token_id: String }, } impl TryInto> for ExecuteMsg { @@ -91,7 +93,6 @@ impl TryInto> for ExecuteMsg { Ok(ParentExecuteMsg::ApproveAll { operator, expires }) } ExecuteMsg::RevokeAll { operator } => Ok(ParentExecuteMsg::RevokeAll { operator }), - ExecuteMsg::Burn { token_id } => Ok(ParentExecuteMsg::Burn { token_id }), _ => Err(StdError::generic_err( "Attempting to convert to a non-cw721 compatible message", ) diff --git a/contracts/account-nft/src/msg/instantiate.rs b/contracts/account-nft/src/msg/instantiate.rs new file mode 100644 index 000000000..81f726134 --- /dev/null +++ b/contracts/account-nft/src/msg/instantiate.rs @@ -0,0 +1,36 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Decimal; +use cw721_base::InstantiateMsg as ParentInstantiateMsg; + +#[cw_serde] +pub struct InstantiateMsg { + //-------------------------------------------------------------------------------------------------- + // Extended and overridden messages + //-------------------------------------------------------------------------------------------------- + /// The maximum amount of Debts + Collaterals for an account before burns are disallowed + /// for the NFT. Meant to prevent accidental account deletions. + pub max_value_for_burn: Decimal, + + //-------------------------------------------------------------------------------------------------- + // Base cw721 messages + //-------------------------------------------------------------------------------------------------- + /// Name of the NFT contract + pub name: String, + /// Symbol of the NFT contract + pub symbol: String, + /// The minter is the only one who can create new NFTs. + /// Initially this likely will be the contract deployer. However, this role should be transferred + /// through a config update to the Credit Manager. It is separate because some blockchains + /// are permissioned and contracts go through governance and are instantiated separately. + pub minter: String, +} + +impl From for ParentInstantiateMsg { + fn from(msg: InstantiateMsg) -> Self { + Self { + name: msg.name, + symbol: msg.symbol, + minter: msg.minter, + } + } +} diff --git a/contracts/account-nft/src/msg/mod.rs b/contracts/account-nft/src/msg/mod.rs index 3b2787b8a..f82a48d3b 100644 --- a/contracts/account-nft/src/msg/mod.rs +++ b/contracts/account-nft/src/msg/mod.rs @@ -1,5 +1,7 @@ mod execute; +mod instantiate; mod query; pub use execute::ExecuteMsg; +pub use instantiate::InstantiateMsg; pub use query::QueryMsg; diff --git a/contracts/account-nft/src/msg/query.rs b/contracts/account-nft/src/msg/query.rs index 8e593bbb1..c74f61993 100644 --- a/contracts/account-nft/src/msg/query.rs +++ b/contracts/account-nft/src/msg/query.rs @@ -10,8 +10,8 @@ pub enum QueryMsg { //-------------------------------------------------------------------------------------------------- // Extended messages //-------------------------------------------------------------------------------------------------- - #[returns(String)] - ProposedNewOwner {}, + #[returns(crate::config::UncheckedConfig)] + Config {}, //-------------------------------------------------------------------------------------------------- // Base cw721 messages diff --git a/contracts/account-nft/src/query.rs b/contracts/account-nft/src/query.rs index 580934835..e2c22befe 100644 --- a/contracts/account-nft/src/query.rs +++ b/contracts/account-nft/src/query.rs @@ -1,6 +1,7 @@ -use crate::state::PENDING_OWNER; +use crate::config::UncheckedConfig; +use crate::state::CONFIG; use cosmwasm_std::{Deps, StdResult}; -pub fn query_proposed_new_owner(deps: Deps) -> StdResult { - Ok(PENDING_OWNER.load(deps.storage)?.into()) +pub fn query_config(deps: Deps) -> StdResult { + Ok(CONFIG.load(deps.storage)?.into()) } diff --git a/contracts/account-nft/src/state.rs b/contracts/account-nft/src/state.rs index cb1d0877c..a911fc64d 100644 --- a/contracts/account-nft/src/state.rs +++ b/contracts/account-nft/src/state.rs @@ -1,5 +1,6 @@ -use cosmwasm_std::Addr; use cw_storage_plus::Item; -pub const PENDING_OWNER: Item = Item::new("pending_owner"); +use crate::config::Config; + +pub const CONFIG: Item = Item::new("config"); pub const NEXT_ID: Item = Item::new("next_id"); diff --git a/contracts/account-nft/tests/helpers.rs b/contracts/account-nft/tests/helpers.rs deleted file mode 100644 index c098766fd..000000000 --- a/contracts/account-nft/tests/helpers.rs +++ /dev/null @@ -1,58 +0,0 @@ -use anyhow::Result as AnyResult; -use cosmwasm_std::Addr; -use cw721_base::InstantiateMsg; -use cw_multi_test::{AppResponse, BasicApp, ContractWrapper, Executor}; - -use mars_account_nft::contract::{execute, instantiate, query}; -use mars_account_nft::msg::ExecuteMsg as ExtendedExecuteMsg; - -pub fn instantiate_mock_nft_contract(app: &mut BasicApp, owner: &Addr) -> Addr { - let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); - let code_id = app.store_code(contract); - - app.instantiate_contract( - code_id, - owner.clone(), - &InstantiateMsg { - name: "mock_nft".to_string(), - symbol: "MOCK".to_string(), - minter: owner.to_string(), - }, - &[], - "mock-account-nft", - None, - ) - .unwrap() -} - -pub fn mint_action( - app: &mut BasicApp, - sender: &Addr, - contract_addr: &Addr, - token_owner: &Addr, -) -> AnyResult { - app.execute_contract( - sender.clone(), - contract_addr.clone(), - &ExtendedExecuteMsg::Mint { - user: token_owner.into(), - }, - &[], - ) -} - -pub fn burn_action( - app: &mut BasicApp, - sender: &Addr, - contract_addr: &Addr, - token_id: &str, -) -> AnyResult { - app.execute_contract( - sender.clone(), - contract_addr.clone(), - &ExtendedExecuteMsg::Burn { - token_id: token_id.to_string(), - }, - &[], - ) -} diff --git a/contracts/account-nft/tests/helpers/health_responses.rs b/contracts/account-nft/tests/helpers/health_responses.rs new file mode 100644 index 000000000..3a48546c0 --- /dev/null +++ b/contracts/account-nft/tests/helpers/health_responses.rs @@ -0,0 +1,33 @@ +use std::ops::Sub; + +use cosmwasm_std::Decimal; + +use mars_rover::msg::query::HealthResponse; + +pub const MAX_VALUE_FOR_BURN: u128 = 1000u128; + +pub fn generate_health_response(debt_value: u128, collateral_value: u128) -> HealthResponse { + HealthResponse { + total_debt_value: Decimal::from_atomics(debt_value, 0).unwrap(), + total_collateral_value: Decimal::from_atomics(collateral_value, 0).unwrap(), + max_ltv_adjusted_collateral: Default::default(), + liquidation_threshold_adjusted_collateral: Default::default(), + max_ltv_health_factor: None, + liquidation_health_factor: None, + liquidatable: false, + above_max_ltv: false, + } +} + +pub fn below_max_for_burn() -> HealthResponse { + HealthResponse { + total_debt_value: Decimal::from_atomics(MAX_VALUE_FOR_BURN.sub(1), 0).unwrap(), + total_collateral_value: Default::default(), + max_ltv_adjusted_collateral: Default::default(), + liquidation_threshold_adjusted_collateral: Default::default(), + max_ltv_health_factor: None, + liquidation_health_factor: None, + liquidatable: false, + above_max_ltv: false, + } +} diff --git a/contracts/account-nft/tests/helpers/mock_contracts.rs b/contracts/account-nft/tests/helpers/mock_contracts.rs new file mode 100644 index 000000000..6e4241982 --- /dev/null +++ b/contracts/account-nft/tests/helpers/mock_contracts.rs @@ -0,0 +1,20 @@ +use cosmwasm_std::Empty; +use cw_multi_test::{Contract, ContractWrapper}; + +pub fn mock_nft_contract() -> Box> { + let contract = ContractWrapper::new( + mars_account_nft::contract::execute, + mars_account_nft::contract::instantiate, + mars_account_nft::contract::query, + ); + Box::new(contract) +} + +pub fn mock_credit_manager_contract() -> Box> { + let contract = ContractWrapper::new( + mars_mock_credit_manager::contract::execute, + mars_mock_credit_manager::contract::instantiate, + mars_mock_credit_manager::contract::query, + ); + Box::new(contract) +} diff --git a/contracts/account-nft/tests/helpers/mock_env.rs b/contracts/account-nft/tests/helpers/mock_env.rs new file mode 100644 index 000000000..10f582aff --- /dev/null +++ b/contracts/account-nft/tests/helpers/mock_env.rs @@ -0,0 +1,144 @@ +use anyhow::Result as AnyResult; +use cosmwasm_std::Addr; +use cw721::OwnerOfResponse; +use cw_multi_test::{App, AppResponse, BasicApp, Executor}; + +use mars_account_nft::config::{ConfigUpdates, UncheckedConfig}; +use mars_account_nft::msg::ExecuteMsg::{AcceptMinterRole, UpdateConfig}; +use mars_account_nft::msg::{ExecuteMsg as ExtendedExecuteMsg, QueryMsg}; +use mars_mock_credit_manager::msg::ExecuteMsg::SetHealthResponse; +use mars_rover::msg::query::HealthResponse; + +use crate::helpers::MockEnvBuilder; + +pub struct MockEnv { + pub app: BasicApp, + pub minter: Addr, + pub nft_contract: Addr, + pub deployer: Addr, +} + +#[allow(clippy::new_ret_no_self)] +impl MockEnv { + pub fn new() -> MockEnvBuilder { + MockEnvBuilder { + app: App::default(), + minter: None, + deployer: Addr::unchecked("deployer"), + nft_contract: None, + } + } + + pub fn query_config(&mut self) -> UncheckedConfig { + self.app + .wrap() + .query_wasm_smart(self.nft_contract.clone(), &QueryMsg::Config {}) + .unwrap() + } + + // Double checking ownership by querying NFT account-nft for correct owner + pub fn assert_owner_is_correct(&mut self, user: &Addr, token_id: &str) { + let owner_res: OwnerOfResponse = self + .app + .wrap() + .query_wasm_smart( + self.nft_contract.clone(), + &QueryMsg::OwnerOf { + token_id: token_id.to_string(), + include_expired: None, + }, + ) + .unwrap(); + assert_eq!(user.to_string(), owner_res.owner) + } + + pub fn set_health_response( + &mut self, + sender: &Addr, + account_id: &str, + response: &HealthResponse, + ) -> AppResponse { + self.app + .execute_contract( + sender.clone(), + self.minter.clone(), + &SetHealthResponse { + account_id: account_id.to_string(), + response: response.clone(), + }, + &[], + ) + .unwrap() + } + + pub fn mint(&mut self, token_owner: &Addr) -> AnyResult { + let res = self.app.execute_contract( + self.minter.clone(), + self.nft_contract.clone(), + &ExtendedExecuteMsg::Mint { + user: token_owner.into(), + }, + &[], + )?; + + let attr: Vec<&str> = res + .events + .iter() + .flat_map(|event| &event.attributes) + .filter(|attr| attr.key == "token_id") + .map(|attr| attr.value.as_str()) + .collect(); + + assert_eq!(attr.len(), 1); + Ok(attr.first().unwrap().to_string()) + } + + pub fn burn(&mut self, sender: &Addr, token_id: &str) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.nft_contract.clone(), + &ExtendedExecuteMsg::Burn { + token_id: token_id.to_string(), + }, + &[], + ) + } + + pub fn propose_new_minter( + &mut self, + sender: &Addr, + proposed_new_minter: &Addr, + ) -> AnyResult { + self.update_config( + sender, + &ConfigUpdates { + max_value_for_burn: None, + proposed_new_minter: Some(proposed_new_minter.to_string()), + }, + ) + } + + pub fn accept_proposed_minter(&mut self, sender: &Addr) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.nft_contract.clone(), + &AcceptMinterRole {}, + &[], + ) + } + + pub fn update_config( + &mut self, + sender: &Addr, + updates: &ConfigUpdates, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.nft_contract.clone(), + &UpdateConfig { + updates: updates.clone(), + }, + &[], + ) + } +} diff --git a/contracts/account-nft/tests/helpers/mock_env_builder.rs b/contracts/account-nft/tests/helpers/mock_env_builder.rs new file mode 100644 index 000000000..646101b7d --- /dev/null +++ b/contracts/account-nft/tests/helpers/mock_env_builder.rs @@ -0,0 +1,115 @@ +use std::mem::take; + +use anyhow::Result as AnyResult; +use cosmwasm_std::{Addr, Decimal, Empty}; +use cw_multi_test::{BasicApp, Executor}; +use mars_account_nft::config::ConfigUpdates; +use mars_account_nft::msg::ExecuteMsg::{AcceptMinterRole, UpdateConfig}; + +use mars_account_nft::msg::InstantiateMsg; + +use crate::helpers::{ + mock_credit_manager_contract, mock_nft_contract, MockEnv, MAX_VALUE_FOR_BURN, +}; + +pub struct MockEnvBuilder { + pub app: BasicApp, + pub minter: Option, + pub deployer: Addr, + pub nft_contract: Option, +} + +impl MockEnvBuilder { + pub fn build(&mut self) -> AnyResult { + Ok(MockEnv { + minter: self.get_minter(), + nft_contract: self.get_nft_contract(), + deployer: self.deployer.clone(), + app: take(&mut self.app), + }) + } + + pub fn set_minter(&mut self, minter: &str) -> &mut Self { + self.minter = Some(Addr::unchecked(minter.to_string())); + self + } + + fn get_minter(&mut self) -> Addr { + self.minter.clone().unwrap_or_else(|| self.deployer.clone()) + } + + fn get_nft_contract(&mut self) -> Addr { + if self.nft_contract.is_none() { + self.deploy_nft_contract() + } + self.nft_contract.clone().unwrap() + } + + fn deploy_nft_contract(&mut self) { + let contract = mock_nft_contract(); + let code_id = self.app.store_code(contract); + let minter = self.get_minter().into(); + + let addr = self + .app + .instantiate_contract( + code_id, + self.deployer.clone(), + &InstantiateMsg { + max_value_for_burn: Decimal::from_atomics(MAX_VALUE_FOR_BURN, 0).unwrap(), + name: "mock_nft".to_string(), + symbol: "MOCK".to_string(), + minter, + }, + &[], + "mock-account-nft", + None, + ) + .unwrap(); + self.nft_contract = Some(addr); + } + + pub fn assign_minter_to_cm(&mut self) -> &mut Self { + let contract = mock_credit_manager_contract(); + let code_id = self.app.store_code(contract); + + let cm_addr = self + .app + .instantiate_contract( + code_id, + self.deployer.clone(), + &Empty {}, + &[], + "mock-credit-manager", + None, + ) + .unwrap(); + + let nft_contract = self.get_nft_contract(); + + let minter = self.get_minter(); + + // Propose new minter + self.app + .execute_contract( + minter, + nft_contract.clone(), + &UpdateConfig { + updates: ConfigUpdates { + max_value_for_burn: None, + proposed_new_minter: Some(cm_addr.clone().into()), + }, + }, + &[], + ) + .unwrap(); + + // Accept new role + self.app + .execute_contract(cm_addr.clone(), nft_contract, &AcceptMinterRole {}, &[]) + .unwrap(); + + self.minter = Some(cm_addr); + self + } +} diff --git a/contracts/account-nft/tests/helpers/mod.rs b/contracts/account-nft/tests/helpers/mod.rs new file mode 100644 index 000000000..66d8482c9 --- /dev/null +++ b/contracts/account-nft/tests/helpers/mod.rs @@ -0,0 +1,9 @@ +pub use self::health_responses::*; +pub use self::mock_contracts::*; +pub use self::mock_env::*; +pub use self::mock_env_builder::*; + +mod health_responses; +mod mock_contracts; +mod mock_env; +mod mock_env_builder; diff --git a/contracts/account-nft/tests/test_burn_allowance.rs b/contracts/account-nft/tests/test_burn_allowance.rs new file mode 100644 index 000000000..4a54ee355 --- /dev/null +++ b/contracts/account-nft/tests/test_burn_allowance.rs @@ -0,0 +1,108 @@ +use cosmwasm_std::{Addr, Decimal, Empty, StdResult}; +use cw721::NftInfoResponse; + +use mars_account_nft::error::ContractError; +use mars_account_nft::error::ContractError::BurnNotAllowed; +use mars_account_nft::msg::QueryMsg::NftInfo; + +use crate::helpers::{below_max_for_burn, generate_health_response, MockEnv, MAX_VALUE_FOR_BURN}; + +pub mod helpers; + +#[test] +fn test_burn_not_allowed_if_too_many_debts() { + let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); + + let user = Addr::unchecked("user"); + let token_id = mock.mint(&user).unwrap(); + mock.set_health_response(&user, &token_id, &generate_health_response(10_000, 0)); + + let res = mock.burn(&user, &token_id); + let error: ContractError = res.unwrap_err().downcast().unwrap(); + assert_eq!( + error, + BurnNotAllowed { + current_balances: Decimal::from_atomics(10_000u128, 0).unwrap(), + max_value_allowed: Decimal::from_atomics(MAX_VALUE_FOR_BURN, 0).unwrap() + } + ) +} + +#[test] +fn test_burn_not_allowed_if_too_much_collateral() { + let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); + + let user = Addr::unchecked("user"); + let token_id = mock.mint(&user).unwrap(); + mock.set_health_response(&user, &token_id, &generate_health_response(0, 10_000)); + + let res = mock.burn(&user, &token_id); + let error: ContractError = res.unwrap_err().downcast().unwrap(); + assert_eq!( + error, + BurnNotAllowed { + current_balances: Decimal::from_atomics(10_000u128, 0).unwrap(), + max_value_allowed: Decimal::from_atomics(MAX_VALUE_FOR_BURN, 0).unwrap() + } + ) +} + +#[test] +fn test_burn_allowance_works_with_both_debt_and_collateral() { + let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); + + let user = Addr::unchecked("user"); + let token_id = mock.mint(&user).unwrap(); + mock.set_health_response(&user, &token_id, &generate_health_response(501, 500)); + + let res = mock.burn(&user, &token_id); + let error: ContractError = res.unwrap_err().downcast().unwrap(); + assert_eq!( + error, + BurnNotAllowed { + current_balances: Decimal::from_atomics(1_001u128, 0).unwrap(), + max_value_allowed: Decimal::from_atomics(MAX_VALUE_FOR_BURN, 0).unwrap() + } + ) +} + +#[test] +fn test_burn_allowance_at_exactly_max() { + let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); + + let user = Addr::unchecked("user"); + let token_id = mock.mint(&user).unwrap(); + mock.set_health_response(&user, &token_id, &generate_health_response(500, 500)); + + mock.burn(&user, &token_id).unwrap(); +} + +#[test] +fn test_burn_allowance_when_under_max() { + let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); + + let user = Addr::unchecked("user"); + let token_id = mock.mint(&user).unwrap(); + mock.set_health_response(&user, &token_id, &generate_health_response(500, 500)); + + // Assert no errors on calling for NftInfo + let _: NftInfoResponse = mock + .app + .wrap() + .query_wasm_smart( + mock.nft_contract.clone(), + &NftInfo { + token_id: token_id.clone(), + }, + ) + .unwrap(); + + mock.set_health_response(&user, &token_id, &below_max_for_burn()); + mock.burn(&user, &token_id).unwrap(); + + let res: StdResult> = mock + .app + .wrap() + .query_wasm_smart(mock.nft_contract, &NftInfo { token_id }); + res.unwrap_err(); +} diff --git a/contracts/account-nft/tests/test_mint.rs b/contracts/account-nft/tests/test_mint.rs index f952973c7..ef6d4290d 100644 --- a/contracts/account-nft/tests/test_mint.rs +++ b/contracts/account-nft/tests/test_mint.rs @@ -1,136 +1,120 @@ use std::fmt::Error; -use cosmwasm_std::{Addr, Empty}; +use cosmwasm_std::Addr; use cw721::OwnerOfResponse; use cw721_base::ContractError::Unauthorized; -use cw721_base::{ContractError, QueryMsg}; -use cw_multi_test::{App, AppResponse, BasicApp, Executor}; +use cw_multi_test::Executor; +use mars_account_nft::error::ContractError; +use mars_account_nft::error::ContractError::BaseError; use mars_account_nft::msg::ExecuteMsg as ExtendedExecuteMsg; +use mars_account_nft::msg::QueryMsg::OwnerOf; -use crate::helpers::{burn_action, instantiate_mock_nft_contract, mint_action}; +use crate::helpers::{below_max_for_burn, MockEnv}; pub mod helpers; #[test] fn test_id_incrementer() { - let mut app = App::default(); - let owner = Addr::unchecked("owner"); - let contract_addr = instantiate_mock_nft_contract(&mut app, &owner); + let mut mock = MockEnv::new().build().unwrap(); let user_1 = Addr::unchecked("user_1"); - let res = mint_action(&mut app, &owner, &contract_addr, &user_1).unwrap(); - let token_id = get_token_id(res); + let token_id = mock.mint(&user_1).unwrap(); assert_eq!(token_id, "1"); - assert_owner_is_correct(&mut app, &contract_addr, &user_1, &token_id); + mock.assert_owner_is_correct(&user_1, &token_id); let user_2 = Addr::unchecked("user_2"); - let res = mint_action(&mut app, &owner, &contract_addr, &user_2).unwrap(); - let token_id = get_token_id(res); + let token_id = mock.mint(&user_2).unwrap(); assert_eq!(token_id, "2"); - assert_owner_is_correct(&mut app, &contract_addr, &user_2, &token_id); + mock.assert_owner_is_correct(&user_2, &token_id); let user_3 = Addr::unchecked("user_3"); - let res = mint_action(&mut app, &owner, &contract_addr, &user_3).unwrap(); - let token_id = get_token_id(res); + let token_id = mock.mint(&user_3).unwrap(); assert_eq!(token_id, "3"); - assert_owner_is_correct(&mut app, &contract_addr, &user_3, &token_id); + mock.assert_owner_is_correct(&user_3, &token_id); } #[test] fn test_id_incrementer_works_despite_burns() { - let mut app = App::default(); - let owner = Addr::unchecked("owner"); - let contract_addr = instantiate_mock_nft_contract(&mut app, &owner); + let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); let user = Addr::unchecked("user"); - let res = mint_action(&mut app, &owner, &contract_addr, &user).unwrap(); - let token_id_1 = get_token_id(res); + let token_id_1 = mock.mint(&user).unwrap(); assert_eq!(token_id_1, "1"); - let res = mint_action(&mut app, &owner, &contract_addr, &user).unwrap(); - let token_id_2 = get_token_id(res); + + let token_id_2 = mock.mint(&user).unwrap(); assert_eq!(token_id_2, "2"); - burn_action(&mut app, &user, &contract_addr, &token_id_1).unwrap(); - burn_action(&mut app, &user, &contract_addr, &token_id_2).unwrap(); + mock.set_health_response(&user, &token_id_1, &below_max_for_burn()); + mock.burn(&user, &token_id_1).unwrap(); + mock.set_health_response(&user, &token_id_2, &below_max_for_burn()); + mock.burn(&user, &token_id_2).unwrap(); - let res = mint_action(&mut app, &owner, &contract_addr, &user).unwrap(); - let token_id = get_token_id(res); - assert_eq!(token_id, "3"); - assert_owner_is_correct(&mut app, &contract_addr, &user, &token_id); + let token_id_3 = mock.mint(&user).unwrap(); + assert_eq!(token_id_3, "3"); + mock.assert_owner_is_correct(&user, &token_id_3); } #[test] -fn test_only_contract_owner_can_mint() { - let mut app = App::default(); - let owner = Addr::unchecked("owner"); - let contract_addr = instantiate_mock_nft_contract(&mut app, &owner); +fn test_only_minter_can_mint() { + let mut mock = MockEnv::new().set_minter("mr_minter").build().unwrap(); let bad_guy = Addr::unchecked("bad_guy"); - let res = mint_action(&mut app, &bad_guy, &contract_addr, &bad_guy); + let res = mock.app.execute_contract( + bad_guy.clone(), + mock.nft_contract.clone(), + &ExtendedExecuteMsg::Mint { + user: bad_guy.into(), + }, + &[], + ); let err: ContractError = res.unwrap_err().downcast().unwrap(); - assert_eq!(err, Unauthorized {}) + assert_eq!(err, BaseError(Unauthorized {})) } #[test] fn test_only_token_owner_can_burn() { - let mut app = App::default(); - let owner = Addr::unchecked("owner"); - let contract_addr = instantiate_mock_nft_contract(&mut app, &owner); + let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); let user = Addr::unchecked("user"); - let res = mint_action(&mut app, &owner, &contract_addr, &user).unwrap(); - let token_id = get_token_id(res); + let token_id = mock.mint(&user).unwrap(); + mock.set_health_response(&user, &token_id, &below_max_for_burn()); let bad_guy = Addr::unchecked("bad_guy"); - let res = burn_action(&mut app, &bad_guy, &contract_addr, &token_id); + let res = mock.burn(&bad_guy, &token_id); let err: ContractError = res.unwrap_err().downcast().unwrap(); - assert_eq!(err, Unauthorized {}); + assert_eq!(err, BaseError(Unauthorized {})); - burn_action(&mut app, &user, &contract_addr, &token_id).unwrap(); + mock.burn(&user, &token_id).unwrap(); } #[test] fn test_normal_base_cw721_actions_can_still_be_taken() { - let mut app = App::default(); - let owner = Addr::unchecked("owner"); - let contract_addr = instantiate_mock_nft_contract(&mut app, &owner); - - let rover_user = Addr::unchecked("rover_user"); - let res = mint_action(&mut app, &owner, &contract_addr, &rover_user).unwrap(); - let token_id = get_token_id(res); - - let burn_msg: ExtendedExecuteMsg = ExtendedExecuteMsg::Burn { token_id }; - app.execute_contract(rover_user, contract_addr.clone(), &burn_msg, &[]) + let mut mock = MockEnv::new().build().unwrap(); + + let rover_user_a = Addr::unchecked("rover_user_a"); + let token_id = mock.mint(&rover_user_a).unwrap(); + + let rover_user_b = Addr::unchecked("rover_user_b"); + let transfer_msg: ExtendedExecuteMsg = ExtendedExecuteMsg::TransferNft { + token_id: token_id.clone(), + recipient: rover_user_b.clone().into(), + }; + mock.app + .execute_contract(rover_user_a, mock.nft_contract.clone(), &transfer_msg, &[]) .map_err(|_| Error::default()) .unwrap(); -} -// Double checking ownership by querying NFT account-nft for correct owner -fn assert_owner_is_correct(app: &mut BasicApp, contract_addr: &Addr, user: &Addr, token_id: &str) { - let owner_res: OwnerOfResponse = app + let res: OwnerOfResponse = mock + .app .wrap() .query_wasm_smart( - contract_addr, - &QueryMsg::::OwnerOf { - token_id: token_id.to_string(), + mock.nft_contract, + &OwnerOf { + token_id, include_expired: None, }, ) .unwrap(); - - assert_eq!(user.to_string(), owner_res.owner) -} - -fn get_token_id(res: AppResponse) -> String { - let attr: Vec<&str> = res - .events - .iter() - .flat_map(|event| &event.attributes) - .filter(|attr| attr.key == "token_id") - .map(|attr| attr.value.as_str()) - .collect(); - - assert_eq!(attr.len(), 1); - attr.first().unwrap().to_string() + assert_eq!(res.owner, rover_user_b.to_string()) } diff --git a/contracts/account-nft/tests/test_ownership.rs b/contracts/account-nft/tests/test_ownership.rs deleted file mode 100644 index 9311465f0..000000000 --- a/contracts/account-nft/tests/test_ownership.rs +++ /dev/null @@ -1,111 +0,0 @@ -use anyhow::Result as AnyResult; -use cosmwasm_std::{Addr, StdResult}; -use cw721_base::MinterResponse; -use cw_multi_test::{App, AppResponse, BasicApp, Executor}; - -use mars_account_nft::msg::{ExecuteMsg as ExtendedExecuteMsg, QueryMsg}; - -use crate::helpers::instantiate_mock_nft_contract; - -pub mod helpers; - -#[test] -fn test_only_owner_can_propose_ownership_transfer() { - let mut app = App::default(); - let owner = Addr::unchecked("owner"); - let contract_addr = instantiate_mock_nft_contract(&mut app, &owner); - - let bad_guy = Addr::unchecked("bad_guy"); - let res = propose_new_owner(&mut app, &contract_addr, &bad_guy, &bad_guy); - - if res.is_ok() { - panic!("Non-owner should not be able to propose ownership transfer"); - } -} - -#[test] -fn test_propose_ownership_stores() { - let mut app = App::default(); - let original_owner = Addr::unchecked("owner"); - let contract_addr = instantiate_mock_nft_contract(&mut app, &original_owner); - - let new_owner = Addr::unchecked("new_owner"); - propose_new_owner(&mut app, &contract_addr, &original_owner, &new_owner).unwrap(); - - let pending_owner_in_storage = query_pending_owner(&app, &contract_addr).unwrap(); - assert_eq!(pending_owner_in_storage, new_owner); -} - -#[test] -fn test_proposed_owner_can_accept_ownership() { - let mut app = App::default(); - let original_owner = Addr::unchecked("owner"); - let contract_addr = instantiate_mock_nft_contract(&mut app, &original_owner); - - let new_owner = Addr::unchecked("new_owner"); - propose_new_owner(&mut app, &contract_addr, &original_owner, &new_owner).unwrap(); - - accept_proposed_owner(&mut app, &contract_addr, &new_owner).unwrap(); - - let res = query_pending_owner(&app, &contract_addr); - if res.is_ok() { - panic!("Proposed owner should have been removed from storage"); - } - - let res: MinterResponse = app - .wrap() - .query_wasm_smart(contract_addr, &QueryMsg::Minter {}) - .unwrap(); - - assert_eq!(res.minter, new_owner) -} - -#[test] -fn test_only_proposed_owner_can_accept() { - let mut app = App::default(); - let original_owner = Addr::unchecked("owner"); - let contract_addr = instantiate_mock_nft_contract(&mut app, &original_owner); - - let new_owner = Addr::unchecked("new_owner"); - propose_new_owner(&mut app, &contract_addr, &original_owner, &new_owner).unwrap(); - - let bad_guy = Addr::unchecked("bad_guy"); - let res = accept_proposed_owner(&mut app, &contract_addr, &bad_guy); - if res.is_ok() { - panic!("Only proposed owner can accept ownership"); - } -} - -fn query_pending_owner(app: &BasicApp, contract_addr: &Addr) -> StdResult { - app.wrap() - .query_wasm_smart(contract_addr, &QueryMsg::ProposedNewOwner {}) -} - -fn propose_new_owner( - app: &mut BasicApp, - contract_addr: &Addr, - sender: &Addr, - proposed_new_owner: &Addr, -) -> AnyResult { - app.execute_contract( - sender.clone(), - contract_addr.clone(), - &ExtendedExecuteMsg::ProposeNewOwner { - new_owner: proposed_new_owner.into(), - }, - &[], - ) -} - -fn accept_proposed_owner( - app: &mut BasicApp, - contract_addr: &Addr, - sender: &Addr, -) -> AnyResult { - app.execute_contract( - sender.clone(), - contract_addr.clone(), - &ExtendedExecuteMsg::AcceptOwnership {}, - &[], - ) -} diff --git a/contracts/account-nft/tests/test_proposed_minter.rs b/contracts/account-nft/tests/test_proposed_minter.rs new file mode 100644 index 000000000..c59dbc2be --- /dev/null +++ b/contracts/account-nft/tests/test_proposed_minter.rs @@ -0,0 +1,72 @@ +use cosmwasm_std::Addr; +use cw721_base::MinterResponse; + +use mars_account_nft::msg::QueryMsg; + +use crate::helpers::MockEnv; + +pub mod helpers; + +#[test] +fn test_only_minter_can_propose_new_minter() { + let mut mock = MockEnv::new().build().unwrap(); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.propose_new_minter(&bad_guy, &bad_guy); + + if res.is_ok() { + panic!("Non-minter should not be able to propose new minter"); + } +} + +#[test] +fn test_propose_minter_stores() { + let mut mock = MockEnv::new().build().unwrap(); + + let new_minter = Addr::unchecked("new_minter"); + mock.propose_new_minter(&mock.minter.clone(), &new_minter) + .unwrap(); + + let config = mock.query_config(); + assert_eq!(config.proposed_new_minter.unwrap(), new_minter); +} + +#[test] +fn test_proposed_minter_can_accept_role() { + let mut mock = MockEnv::new().build().unwrap(); + + let new_minter = Addr::unchecked("new_minter"); + mock.propose_new_minter(&mock.minter.clone(), &new_minter) + .unwrap(); + + mock.accept_proposed_minter(&new_minter).unwrap(); + + let config = mock.query_config(); + if config.proposed_new_minter.is_some() { + panic!("Proposed minter should have been removed from storage"); + } + + let res: MinterResponse = mock + .app + .wrap() + .query_wasm_smart(mock.nft_contract, &QueryMsg::Minter {}) + .unwrap(); + + assert_eq!(res.minter, new_minter) +} + +#[test] +fn test_only_proposed_minter_can_accept() { + let mut mock = MockEnv::new().build().unwrap(); + + let new_minter = Addr::unchecked("new_minter"); + mock.propose_new_minter(&mock.minter.clone(), &new_minter) + .unwrap(); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.accept_proposed_minter(&bad_guy); + + if res.is_ok() { + panic!("Only proposed minter can accept role"); + } +} diff --git a/contracts/account-nft/tests/test_update_config.rs b/contracts/account-nft/tests/test_update_config.rs new file mode 100644 index 000000000..1d5be4d9a --- /dev/null +++ b/contracts/account-nft/tests/test_update_config.rs @@ -0,0 +1,44 @@ +use cosmwasm_std::{Addr, Decimal}; + +use mars_account_nft::config::ConfigUpdates; + +use crate::helpers::MockEnv; + +pub mod helpers; + +#[test] +fn test_only_minter_can_update_config() { + let mut mock = MockEnv::new().build().unwrap(); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.update_config( + &bad_guy, + &ConfigUpdates { + max_value_for_burn: None, + proposed_new_minter: None, + }, + ); + + if res.is_ok() { + panic!("Non-owner should not be able to propose ownership transfer"); + } +} + +#[test] +fn test_minter_can_update_config() { + let mut mock = MockEnv::new().build().unwrap(); + + let new_max_burn_val = Decimal::from_atomics(4918453u128, 2).unwrap(); + let new_proposed_minter = "new_proposed_minter".to_string(); + + let updates = ConfigUpdates { + max_value_for_burn: Some(new_max_burn_val), + proposed_new_minter: Some(new_proposed_minter.clone()), + }; + + mock.update_config(&mock.minter.clone(), &updates).unwrap(); + + let config = mock.query_config(); + assert_eq!(config.max_value_for_burn, new_max_burn_val); + assert_eq!(config.proposed_new_minter.unwrap(), new_proposed_minter); +} diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index b90ebe627..91f8272c7 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -66,15 +66,15 @@ pub fn update_config( let validated = deps.api.addr_validate(&addr_str)?; ACCOUNT_NFT.save(deps.storage, &validated)?; - // Accept ownership. NFT contract owner must have proposed Rover as a new owner first. - let accept_ownership_msg = CosmosMsg::Wasm(WasmMsg::Execute { + // Accept minter role. NFT contract minter must have proposed Rover as a new minter first. + let accept_minter_role_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: addr_str.clone(), funds: vec![], - msg: to_binary(&NftExecuteMsg::AcceptOwnership {})?, + msg: to_binary(&NftExecuteMsg::AcceptMinterRole {})?, }); response = response - .add_message(accept_ownership_msg) + .add_message(accept_minter_role_msg) .add_attribute("key", "account_nft") .add_attribute("value", addr_str); } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 499b1887e..0d8cbb6a8 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -6,9 +6,10 @@ use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; use cosmwasm_vault_standard::extensions::lockup::{LockupQueryMsg, UnlockingPosition}; use cosmwasm_vault_standard::msg::VaultStandardQueryMsg::{Info as VaultInfoMsg, VaultExtension}; use cosmwasm_vault_standard::msg::{ExtensionQueryMsg, VaultInfoResponse}; -use cw721_base::InstantiateMsg as NftInstantiateMsg; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; +use mars_account_nft::msg::InstantiateMsg as NftInstantiateMsg; +use mars_account_nft::config::ConfigUpdates as NftConfigUpdates; use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; use mars_mock_oracle::msg::{ CoinPrice, ExecuteMsg as OracleExecuteMsg, InstantiateMsg as OracleInstantiateMsg, @@ -133,12 +134,13 @@ impl MockEnv { ) } - pub fn deploy_nft_contract(&mut self) -> AnyResult { - let nft_contract = deploy_nft_contract(&mut self.app, &self.rover.clone()); - propose_new_nft_contract_owner( + pub fn deploy_new_nft_contract(&mut self) -> AnyResult { + let nft_minter = Addr::unchecked("original_nft_minter"); + let nft_contract = deploy_nft_contract(&mut self.app, &nft_minter); + propose_new_nft_minter( &mut self.app, nft_contract.clone(), - &self.rover.clone(), + &nft_minter.clone(), &self.rover.clone(), ); Ok(nft_contract) @@ -476,17 +478,12 @@ impl MockEnvBuilder { } fn deploy_nft_contract(&mut self, rover: &Addr) { - let nft_contract_owner = Addr::unchecked("original_nft_contract_owner"); + let nft_minter = Addr::unchecked("original_nft_minter"); if self.deploy_nft_contract { - let nft_contract = deploy_nft_contract(&mut self.app, &nft_contract_owner); + let nft_contract = deploy_nft_contract(&mut self.app, &nft_minter); if self.set_nft_contract_owner { - propose_new_nft_contract_owner( - &mut self.app, - nft_contract.clone(), - &nft_contract_owner, - rover, - ); + propose_new_nft_minter(&mut self.app, nft_contract.clone(), &nft_minter, rover); self.update_config( rover, ConfigUpdates { @@ -882,15 +879,16 @@ impl MockEnvBuilder { // Shared utils between MockBuilder & MockEnv //-------------------------------------------------------------------------------------------------- -fn deploy_nft_contract(app: &mut App, owner: &Addr) -> Addr { +fn deploy_nft_contract(app: &mut App, minter: &Addr) -> Addr { let nft_contract_code_id = app.store_code(mock_account_nft_contract()); app.instantiate_contract( nft_contract_code_id, - owner.clone(), + minter.clone(), &NftInstantiateMsg { + max_value_for_burn: Default::default(), name: "Rover Credit Account".to_string(), symbol: "RCA".to_string(), - minter: owner.to_string(), + minter: minter.to_string(), }, &[], "manager-mock-account-nft", @@ -899,15 +897,13 @@ fn deploy_nft_contract(app: &mut App, owner: &Addr) -> Addr { .unwrap() } -fn propose_new_nft_contract_owner( - app: &mut App, - nft_contract: Addr, - nft_contract_owner: &Addr, - rover: &Addr, -) { - let proposal_msg: NftExecuteMsg = NftExecuteMsg::ProposeNewOwner { - new_owner: rover.to_string(), +fn propose_new_nft_minter(app: &mut App, nft_contract: Addr, old_minter: &Addr, new_minter: &Addr) { + let proposal_msg: NftExecuteMsg = NftExecuteMsg::UpdateConfig { + updates: NftConfigUpdates { + max_value_for_burn: None, + proposed_new_minter: Some(new_minter.into()), + }, }; - app.execute_contract(nft_contract_owner.clone(), nft_contract, &proposal_msg, &[]) + app.execute_contract(old_minter.clone(), nft_contract, &proposal_msg, &[]) .unwrap(); } diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 310f4a156..1612437ed 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -97,7 +97,7 @@ fn test_update_config_works_with_full_config() { let original_allowed_coins = mock.query_allowed_coins(None, None); let original_vault_configs = mock.query_vault_configs(None, None); - let new_nft_contract = mock.deploy_nft_contract().unwrap(); + let new_nft_contract = mock.deploy_new_nft_contract().unwrap(); let new_owner = Addr::unchecked("new_owner"); let new_red_bank = RedBankBase::new("new_red_bank".to_string()); let new_vault_configs = vec![VaultInstantiateConfig { @@ -173,7 +173,7 @@ fn test_update_config_works_with_some_config() { let original_allowed_coins = mock.query_allowed_coins(None, None); let original_vault_configs = mock.query_vault_configs(None, None); - let new_nft_contract = mock.deploy_nft_contract().unwrap(); + let new_nft_contract = mock.deploy_new_nft_contract().unwrap(); let new_vault_configs = vec![VaultInstantiateConfig { vault: VaultBase::new("vault_contract_1".to_string()), config: VaultConfig { diff --git a/contracts/mock-credit-manager/Cargo.toml b/contracts/mock-credit-manager/Cargo.toml new file mode 100644 index 000000000..e59fb3e44 --- /dev/null +++ b/contracts/mock-credit-manager/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "mars-mock-credit-manager" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +mars-rover = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/mock-credit-manager/examples/schema.rs b/contracts/mock-credit-manager/examples/schema.rs new file mode 100644 index 000000000..c620e6d74 --- /dev/null +++ b/contracts/mock-credit-manager/examples/schema.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::write_api; +use cosmwasm_std::Empty; +use mars_mock_credit_manager::msg::ExecuteMsg; +use mars_rover::msg::QueryMsg; + +fn main() { + write_api! { + instantiate: Empty, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/mock-credit-manager/src/contract.rs b/contracts/mock-credit-manager/src/contract.rs new file mode 100644 index 000000000..1fa13f923 --- /dev/null +++ b/contracts/mock-credit-manager/src/contract.rs @@ -0,0 +1,43 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +}; +use mars_rover::msg::QueryMsg; + +use crate::execute::set_health_response; +use crate::msg::ExecuteMsg; +use crate::query::query_health; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: Empty, +) -> StdResult { + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + match msg { + ExecuteMsg::SetHealthResponse { + account_id, + response, + } => set_health_response(deps, account_id, response), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Health { account_id } => to_binary(&query_health(deps, account_id)?), + _ => unimplemented!("query msg not supported"), + } +} diff --git a/contracts/mock-credit-manager/src/execute.rs b/contracts/mock-credit-manager/src/execute.rs new file mode 100644 index 000000000..c00947fb0 --- /dev/null +++ b/contracts/mock-credit-manager/src/execute.rs @@ -0,0 +1,12 @@ +use crate::state::HEALTH_RESPONSES; +use cosmwasm_std::{DepsMut, Response, StdResult}; +use mars_rover::msg::query::HealthResponse; + +pub fn set_health_response( + deps: DepsMut, + account_id: String, + response: HealthResponse, +) -> StdResult { + HEALTH_RESPONSES.save(deps.storage, &account_id, &response)?; + Ok(Response::new()) +} diff --git a/contracts/mock-credit-manager/src/lib.rs b/contracts/mock-credit-manager/src/lib.rs new file mode 100644 index 000000000..735a7a3ea --- /dev/null +++ b/contracts/mock-credit-manager/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; +pub mod execute; +pub mod msg; +pub mod query; +pub mod state; diff --git a/contracts/mock-credit-manager/src/msg.rs b/contracts/mock-credit-manager/src/msg.rs new file mode 100644 index 000000000..52fdab325 --- /dev/null +++ b/contracts/mock-credit-manager/src/msg.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::cw_serde; +use mars_rover::msg::query::HealthResponse; + +#[cw_serde] +pub enum ExecuteMsg { + SetHealthResponse { + account_id: String, + response: HealthResponse, + }, +} diff --git a/contracts/mock-credit-manager/src/query.rs b/contracts/mock-credit-manager/src/query.rs new file mode 100644 index 000000000..070ded0d5 --- /dev/null +++ b/contracts/mock-credit-manager/src/query.rs @@ -0,0 +1,7 @@ +use crate::state::HEALTH_RESPONSES; +use cosmwasm_std::{Deps, StdResult}; +use mars_rover::msg::query::HealthResponse; + +pub fn query_health(deps: Deps, account_id: String) -> StdResult { + HEALTH_RESPONSES.load(deps.storage, &account_id) +} diff --git a/contracts/mock-credit-manager/src/state.rs b/contracts/mock-credit-manager/src/state.rs new file mode 100644 index 000000000..5ee394e6e --- /dev/null +++ b/contracts/mock-credit-manager/src/state.rs @@ -0,0 +1,5 @@ +use cw_storage_plus::Map; + +use mars_rover::msg::query::HealthResponse; + +pub const HEALTH_RESPONSES: Map<&str, HealthResponse> = Map::new("health_responses"); // Map diff --git a/schema.Makefile.toml b/schema.Makefile.toml index 3a2ee6c84..307173012 100644 --- a/schema.Makefile.toml +++ b/schema.Makefile.toml @@ -19,6 +19,7 @@ fn main() -> std::io::Result<()> { "mars-mock-vault", "mars-mock-oracle", "mars-mock-zapper", + "mars-mock-credit-manager", ]; for contract in contracts { diff --git a/schemas/mars-account-nft/mars-account-nft.json b/schemas/mars-account-nft/mars-account-nft.json index 337b5b31e..4a173c2e4 100644 --- a/schemas/mars-account-nft/mars-account-nft.json +++ b/schemas/mars-account-nft/mars-account-nft.json @@ -7,13 +7,22 @@ "title": "InstantiateMsg", "type": "object", "required": [ + "max_value_for_burn", "minter", "name", "symbol" ], "properties": { + "max_value_for_burn": { + "description": "The maximum amount of Debts + Collaterals for an account before burns are disallowed for the NFT. Meant to prevent accidental account deletions.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, "minter": { - "description": "The minter is the only one who can create new NFTs. This is designed for a base NFT that is controlled by an external program or contract. You will likely replace this with custom logic in custom NFTs", + "description": "The minter is the only one who can create new NFTs. Initially this likely will be the contract deployer. However, this role should be transferred through a config update to the Credit Manager. It is separate because some blockchains are permissioned and contracts go through governance and are instantiated separately.", "type": "string" }, "name": { @@ -25,27 +34,33 @@ "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } }, "execute": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", "oneOf": [ { - "description": "Due to some chains being permissioned via governance, we must instantiate this contract first and give ownership access to Rover contract with this action after both are independently deployed.", + "description": "Update config in storage. Only minter can execute.", "type": "object", "required": [ - "propose_new_owner" + "update_config" ], "properties": { - "propose_new_owner": { + "update_config": { "type": "object", "required": [ - "new_owner" + "updates" ], "properties": { - "new_owner": { - "type": "string" + "updates": { + "$ref": "#/definitions/ConfigUpdates" } }, "additionalProperties": false @@ -54,13 +69,13 @@ "additionalProperties": false }, { - "description": "Accept the proposed ownership transfer", + "description": "Accept the proposed minter role. Only the proposed new minter can execute.", "type": "object", "required": [ - "accept_ownership" + "accept_minter_role" ], "properties": { - "accept_ownership": { + "accept_minter_role": { "type": "object", "additionalProperties": false } @@ -89,6 +104,28 @@ }, "additionalProperties": false }, + { + "description": "Burn an NFT the sender has access to. Will attempt to query the Credit Manager first to ensure the balance is below the config set threshold.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Transfer is a base message to move a token to another account without triggering actions", "type": "object", @@ -260,33 +297,37 @@ } }, "additionalProperties": false + } + ], + "definitions": { + "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" }, - { - "description": "Burn an NFT the sender has access to", + "ConfigUpdates": { "type": "object", - "required": [ - "burn" - ], "properties": { - "burn": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" + "max_value_for_burn": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" } - }, - "additionalProperties": false + ] + }, + "proposed_new_minter": { + "type": [ + "string", + "null" + ] } }, "additionalProperties": false - } - ], - "definitions": { - "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 .", + }, + "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" }, "Expiration": { @@ -357,10 +398,10 @@ { "type": "object", "required": [ - "proposed_new_owner" + "config" ], "properties": { - "proposed_new_owner": { + "config": { "type": "object", "additionalProperties": false } @@ -1138,6 +1179,32 @@ } } }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigBase_for_String", + "type": "object", + "required": [ + "max_value_for_burn" + ], + "properties": { + "max_value_for_burn": { + "$ref": "#/definitions/Decimal" + }, + "proposed_new_minter": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, "contract_info": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ContractInfoResponse", @@ -1325,11 +1392,6 @@ } } }, - "proposed_new_owner": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "String", - "type": "string" - }, "tokens": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "TokensResponse", diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json new file mode 100644 index 000000000..ed50897cc --- /dev/null +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -0,0 +1,1292 @@ +{ + "contract_name": "mars-mock-credit-manager", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "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" + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "set_health_response" + ], + "properties": { + "set_health_response": { + "type": "object", + "required": [ + "account_id", + "response" + ], + "properties": { + "account_id": { + "type": "string" + }, + "response": { + "$ref": "#/definitions/HealthResponse" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "HealthResponse": { + "type": "object", + "required": [ + "above_max_ltv", + "liquidatable", + "liquidation_threshold_adjusted_collateral", + "max_ltv_adjusted_collateral", + "total_collateral_value", + "total_debt_value" + ], + "properties": { + "above_max_ltv": { + "type": "boolean" + }, + "liquidatable": { + "type": "boolean" + }, + "liquidation_health_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "liquidation_threshold_adjusted_collateral": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv_adjusted_collateral": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv_health_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "total_collateral_value": { + "$ref": "#/definitions/Decimal" + }, + "total_debt_value": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Owner & account nft address", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Configs on vaults", + "type": "object", + "required": [ + "vault_configs" + ], + "properties": { + "vault_configs": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/VaultBase_for_String" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Whitelisted coins", + "type": "object", + "required": [ + "allowed_coins" + ], + "properties": { + "allowed_coins": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "All positions represented by token with value", + "type": "object", + "required": [ + "positions" + ], + "properties": { + "positions": { + "type": "object", + "required": [ + "account_id" + ], + "properties": { + "account_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The health of the account represented by token", + "type": "object", + "required": [ + "health" + ], + "properties": { + "health": { + "type": "object", + "required": [ + "account_id" + ], + "properties": { + "account_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate coin balances for all token positions; start_after accepts (account_id, denom)", + "type": "object", + "required": [ + "all_coin_balances" + ], + "properties": { + "all_coin_balances": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate debt shares for all token positions; start_after accepts (account_id, denom)", + "type": "object", + "required": [ + "all_debt_shares" + ], + "properties": { + "all_debt_shares": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Total debt shares issued for Coin", + "type": "object", + "required": [ + "total_debt_shares" + ], + "properties": { + "total_debt_shares": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate total debt shares for all supported coins; start_after accepts denom string", + "type": "object", + "required": [ + "all_total_debt_shares" + ], + "properties": { + "all_total_debt_shares": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate all vault positions; start_after accepts (account_id, addr)", + "type": "object", + "required": [ + "all_vault_positions" + ], + "properties": { + "all_vault_positions": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get total vault coin balance in Rover for vault", + "type": "object", + "required": [ + "total_vault_coin_balance" + ], + "properties": { + "total_vault_coin_balance": { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate all total vault coin balances; start_after accepts vault addr", + "type": "object", + "required": [ + "all_total_vault_coin_balances" + ], + "properties": { + "all_total_vault_coin_balances": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/VaultBase_for_String" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Estimate how many LP tokens received in exchange for coins provided for liquidity", + "type": "object", + "required": [ + "estimate_provide_liquidity" + ], + "properties": { + "estimate_provide_liquidity": { + "type": "object", + "required": [ + "coins_in", + "lp_token_out" + ], + "properties": { + "coins_in": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "lp_token_out": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Estimate coins withdrawn if exchanged for LP tokens", + "type": "object", + "required": [ + "estimate_withdraw_liquidity" + ], + "properties": { + "estimate_withdraw_liquidity": { + "type": "object", + "required": [ + "lp_token" + ], + "properties": { + "lp_token": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VaultBase_for_String": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "migrate": null, + "sudo": null, + "responses": { + "all_coin_balances": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_CoinBalanceResponseItem", + "type": "array", + "items": { + "$ref": "#/definitions/CoinBalanceResponseItem" + }, + "definitions": { + "CoinBalanceResponseItem": { + "type": "object", + "required": [ + "account_id", + "amount", + "denom" + ], + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "all_debt_shares": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_SharesResponseItem", + "type": "array", + "items": { + "$ref": "#/definitions/SharesResponseItem" + }, + "definitions": { + "SharesResponseItem": { + "type": "object", + "required": [ + "account_id", + "denom", + "shares" + ], + "properties": { + "account_id": { + "type": "string" + }, + "denom": { + "type": "string" + }, + "shares": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "all_total_debt_shares": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_DebtShares", + "type": "array", + "items": { + "$ref": "#/definitions/DebtShares" + }, + "definitions": { + "DebtShares": { + "type": "object", + "required": [ + "denom", + "shares" + ], + "properties": { + "denom": { + "type": "string" + }, + "shares": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "all_total_vault_coin_balances": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_VaultWithBalance", + "type": "array", + "items": { + "$ref": "#/definitions/VaultWithBalance" + }, + "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" + }, + "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" + }, + "VaultBase_for_Addr": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + }, + "VaultWithBalance": { + "type": "object", + "required": [ + "balance", + "vault" + ], + "properties": { + "balance": { + "$ref": "#/definitions/Uint128" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + }, + "additionalProperties": false + } + } + }, + "all_vault_positions": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_VaultPositionResponseItem", + "type": "array", + "items": { + "$ref": "#/definitions/VaultPositionResponseItem" + }, + "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" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "LockingVaultAmount": { + "type": "object", + "required": [ + "locked", + "unlocking" + ], + "properties": { + "locked": { + "$ref": "#/definitions/VaultAmount" + }, + "unlocking": { + "$ref": "#/definitions/UnlockingPositions" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "UnlockingPositions": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultUnlockingPosition" + } + }, + "VaultAmount": { + "$ref": "#/definitions/Uint128" + }, + "VaultBase_for_Addr": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + }, + "VaultPosition": { + "type": "object", + "required": [ + "amount", + "vault" + ], + "properties": { + "amount": { + "$ref": "#/definitions/VaultPositionAmount" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + }, + "additionalProperties": false + }, + "VaultPositionAmount": { + "oneOf": [ + { + "type": "object", + "required": [ + "unlocked" + ], + "properties": { + "unlocked": { + "$ref": "#/definitions/VaultAmount" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "locking" + ], + "properties": { + "locking": { + "$ref": "#/definitions/LockingVaultAmount" + } + }, + "additionalProperties": false + } + ] + }, + "VaultPositionResponseItem": { + "type": "object", + "required": [ + "account_id", + "position" + ], + "properties": { + "account_id": { + "type": "string" + }, + "position": { + "$ref": "#/definitions/VaultPosition" + } + }, + "additionalProperties": false + }, + "VaultUnlockingPosition": { + "type": "object", + "required": [ + "coin", + "id" + ], + "properties": { + "coin": { + "description": "Coins that are awaiting to be unlocked (underlying, not vault tokens)", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "allowed_coins": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_String", + "type": "array", + "items": { + "type": "string" + } + }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "type": "object", + "required": [ + "max_close_factor", + "oracle", + "owner", + "red_bank", + "swapper", + "zapper" + ], + "properties": { + "account_nft": { + "type": [ + "string", + "null" + ] + }, + "max_close_factor": { + "$ref": "#/definitions/Decimal" + }, + "oracle": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "red_bank": { + "type": "string" + }, + "swapper": { + "type": "string" + }, + "zapper": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "estimate_provide_liquidity": { + "$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" + }, + "estimate_withdraw_liquidity": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Coin", + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + }, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "health": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HealthResponse", + "type": "object", + "required": [ + "above_max_ltv", + "liquidatable", + "liquidation_threshold_adjusted_collateral", + "max_ltv_adjusted_collateral", + "total_collateral_value", + "total_debt_value" + ], + "properties": { + "above_max_ltv": { + "type": "boolean" + }, + "liquidatable": { + "type": "boolean" + }, + "liquidation_health_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "liquidation_threshold_adjusted_collateral": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv_adjusted_collateral": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv_health_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "total_collateral_value": { + "$ref": "#/definitions/Decimal" + }, + "total_debt_value": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "positions": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Positions", + "type": "object", + "required": [ + "account_id", + "coins", + "debts", + "vaults" + ], + "properties": { + "account_id": { + "type": "string" + }, + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "debts": { + "type": "array", + "items": { + "$ref": "#/definitions/DebtAmount" + } + }, + "vaults": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultPosition" + } + } + }, + "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" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "DebtAmount": { + "type": "object", + "required": [ + "amount", + "denom", + "shares" + ], + "properties": { + "amount": { + "description": "amount of coins", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "type": "string" + }, + "shares": { + "description": "number of shares in debt pool", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, + "LockingVaultAmount": { + "type": "object", + "required": [ + "locked", + "unlocking" + ], + "properties": { + "locked": { + "$ref": "#/definitions/VaultAmount" + }, + "unlocking": { + "$ref": "#/definitions/UnlockingPositions" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "UnlockingPositions": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultUnlockingPosition" + } + }, + "VaultAmount": { + "$ref": "#/definitions/Uint128" + }, + "VaultBase_for_Addr": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + }, + "VaultPosition": { + "type": "object", + "required": [ + "amount", + "vault" + ], + "properties": { + "amount": { + "$ref": "#/definitions/VaultPositionAmount" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + }, + "additionalProperties": false + }, + "VaultPositionAmount": { + "oneOf": [ + { + "type": "object", + "required": [ + "unlocked" + ], + "properties": { + "unlocked": { + "$ref": "#/definitions/VaultAmount" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "locking" + ], + "properties": { + "locking": { + "$ref": "#/definitions/LockingVaultAmount" + } + }, + "additionalProperties": false + } + ] + }, + "VaultUnlockingPosition": { + "type": "object", + "required": [ + "coin", + "id" + ], + "properties": { + "coin": { + "description": "Coins that are awaiting to be unlocked (underlying, not vault tokens)", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "total_debt_shares": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DebtShares", + "type": "object", + "required": [ + "denom", + "shares" + ], + "properties": { + "denom": { + "type": "string" + }, + "shares": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "total_vault_coin_balance": { + "$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" + }, + "vault_configs": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_VaultInstantiateConfig", + "type": "array", + "items": { + "$ref": "#/definitions/VaultInstantiateConfig" + }, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VaultBase_for_String": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + }, + "VaultConfig": { + "type": "object", + "required": [ + "deposit_cap", + "liquidation_threshold", + "max_ltv", + "whitelisted" + ], + "properties": { + "deposit_cap": { + "$ref": "#/definitions/Coin" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv": { + "$ref": "#/definitions/Decimal" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "VaultInstantiateConfig": { + "type": "object", + "required": [ + "config", + "vault" + ], + "properties": { + "config": { + "$ref": "#/definitions/VaultConfig" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + } + } + } + } +} diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index bec027d00..aca6ba637 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -67,6 +67,8 @@ export class Deployer { async instantiateNftContract() { const msg: NftInstantiateMsg = { + credit_manager: this.storage.addresses.creditManager!, + max_value_for_burn: this.config.maxValueForBurn.toString(), minter: this.deployerAddr, name: 'credit-manger-accounts', symbol: 'rover-nft', diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 7312705b6..f47730a68 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -20,12 +20,12 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp await deployer.upload('creditManager', wasmFile('mars_credit_manager')) // Instantiate contracts - await deployer.instantiateNftContract() await deployer.instantiateMockVault() await deployer.instantiateMarsOracleAdapter() await deployer.instantiateSwapper() await deployer.instantiateZapper() await deployer.instantiateCreditManager() + await deployer.instantiateNftContract() await deployer.transferNftContractOwnership() await deployer.grantCreditLines() await deployer.setupOraclePricesForZapDenoms() diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index cb429d427..a27e23d2d 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -50,4 +50,5 @@ export const osmosisTestnetConfig: DeploymentConfig = { { denom: 'uosmo', amount: 3, price: 1 }, ], unzapAmount: 1000000, + maxValueForBurn: 1000000, } diff --git a/scripts/package.json b/scripts/package.json index 60b71814a..675d7432e 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -13,9 +13,9 @@ "format-check": "prettier --check ." }, "dependencies": { - "@cosmjs/cosmwasm-stargate": "^0.29.3", - "@cosmjs/stargate": "^0.29.3", - "@cosmwasm/ts-codegen": "^0.20.0", + "@cosmjs/cosmwasm-stargate": "^0.29.4", + "@cosmjs/stargate": "^0.29.4", + "@cosmwasm/ts-codegen": "^0.23.0", "chalk": "4.1.2", "cosmjs-types": "^0.5.2", "lodash": "^4.17.21", @@ -25,13 +25,13 @@ "devDependencies": { "@babel/preset-env": "^7.20.2", "@babel/preset-typescript": "^7.18.6", - "@types/jest": "^29.2.2", - "@typescript-eslint/eslint-plugin": "^5.40.0", - "@typescript-eslint/parser": "^5.40.0", - "eslint": "^8.27.0", + "@types/jest": "^29.2.3", + "@typescript-eslint/eslint-plugin": "^5.45.0", + "@typescript-eslint/parser": "^5.45.0", + "eslint": "^8.28.0", "eslint-config-prettier": "^8.5.0", - "jest": "^29.1.2", - "prettier": "^2.7.1", - "typescript": "^4.8.4" + "jest": "^29.3.1", + "prettier": "^2.8.0", + "typescript": "^4.9.3" } } diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 1e38fab4c..76831cc56 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -43,4 +43,5 @@ export interface DeploymentConfig { lpToken: { denom: string; price: number } zap: { amount: number; denom: string; price: number }[] unzapAmount: number + maxValueForBurn: number } diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts index 643927d15..0efa255f6 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -8,12 +8,14 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { Coin, StdFee } from '@cosmjs/amino' import { + Decimal, InstantiateMsg, ExecuteMsg, Binary, Expiration, Timestamp, Uint64, + ConfigUpdates, QueryMsg, AllNftInfoResponseForEmpty, OwnerOfResponse, @@ -24,14 +26,14 @@ import { TokensResponse, ApprovalResponse, ApprovalsResponse, + ConfigBaseForString, ContractInfoResponse, MinterResponse, NumTokensResponse, - String, } from './MarsAccountNft.types' export interface MarsAccountNftReadOnlyInterface { contractAddress: string - proposedNewOwner: () => Promise + config: () => Promise ownerOf: ({ includeExpired, tokenId, @@ -101,7 +103,7 @@ export class MarsAccountNftQueryClient implements MarsAccountNftReadOnlyInterfac constructor(client: CosmWasmClient, contractAddress: string) { this.client = client this.contractAddress = contractAddress - this.proposedNewOwner = this.proposedNewOwner.bind(this) + this.config = this.config.bind(this) this.ownerOf = this.ownerOf.bind(this) this.approval = this.approval.bind(this) this.approvals = this.approvals.bind(this) @@ -115,9 +117,9 @@ export class MarsAccountNftQueryClient implements MarsAccountNftReadOnlyInterfac this.minter = this.minter.bind(this) } - proposedNewOwner = async (): Promise => { + config = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { - proposed_new_owner: {}, + config: {}, }) } ownerOf = async ({ @@ -256,17 +258,17 @@ export class MarsAccountNftQueryClient implements MarsAccountNftReadOnlyInterfac export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface { contractAddress: string sender: string - proposeNewOwner: ( + updateConfig: ( { - newOwner, + updates, }: { - newOwner: string + updates: ConfigUpdates }, fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], ) => Promise - acceptOwnership: ( + acceptMinterRole: ( fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], @@ -281,6 +283,16 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface memo?: string, funds?: Coin[], ) => Promise + burn: ( + { + tokenId, + }: { + tokenId: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise transferNft: ( { recipient, @@ -355,16 +367,6 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface memo?: string, funds?: Coin[], ) => Promise - burn: ( - { - tokenId, - }: { - tokenId: string - }, - fee?: number | StdFee | 'auto', - memo?: string, - funds?: Coin[], - ) => Promise } export class MarsAccountNftClient extends MarsAccountNftQueryClient @@ -379,23 +381,23 @@ export class MarsAccountNftClient this.client = client this.sender = sender this.contractAddress = contractAddress - this.proposeNewOwner = this.proposeNewOwner.bind(this) - this.acceptOwnership = this.acceptOwnership.bind(this) + this.updateConfig = this.updateConfig.bind(this) + this.acceptMinterRole = this.acceptMinterRole.bind(this) this.mint = this.mint.bind(this) + this.burn = this.burn.bind(this) this.transferNft = this.transferNft.bind(this) this.sendNft = this.sendNft.bind(this) this.approve = this.approve.bind(this) this.revoke = this.revoke.bind(this) this.approveAll = this.approveAll.bind(this) this.revokeAll = this.revokeAll.bind(this) - this.burn = this.burn.bind(this) } - proposeNewOwner = async ( + updateConfig = async ( { - newOwner, + updates, }: { - newOwner: string + updates: ConfigUpdates }, fee: number | StdFee | 'auto' = 'auto', memo?: string, @@ -405,8 +407,8 @@ export class MarsAccountNftClient this.sender, this.contractAddress, { - propose_new_owner: { - new_owner: newOwner, + update_config: { + updates, }, }, fee, @@ -414,7 +416,7 @@ export class MarsAccountNftClient funds, ) } - acceptOwnership = async ( + acceptMinterRole = async ( fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -423,7 +425,7 @@ export class MarsAccountNftClient this.sender, this.contractAddress, { - accept_ownership: {}, + accept_minter_role: {}, }, fee, memo, @@ -453,6 +455,29 @@ export class MarsAccountNftClient funds, ) } + burn = async ( + { + tokenId, + }: { + tokenId: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + burn: { + token_id: tokenId, + }, + }, + fee, + memo, + funds, + ) + } transferNft = async ( { recipient, @@ -612,27 +637,4 @@ export class MarsAccountNftClient funds, ) } - burn = async ( - { - tokenId, - }: { - tokenId: string - }, - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - burn: { - token_id: tokenId, - }, - }, - fee, - memo, - funds, - ) - } } diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts index 24761e47a..50616545c 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -10,12 +10,14 @@ import { MsgExecuteContractEncodeObject } from 'cosmwasm' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { + Decimal, InstantiateMsg, ExecuteMsg, Binary, Expiration, Timestamp, Uint64, + ConfigUpdates, QueryMsg, AllNftInfoResponseForEmpty, OwnerOfResponse, @@ -26,23 +28,23 @@ import { TokensResponse, ApprovalResponse, ApprovalsResponse, + ConfigBaseForString, ContractInfoResponse, MinterResponse, NumTokensResponse, - String, } from './MarsAccountNft.types' export interface MarsAccountNftMessage { contractAddress: string sender: string - proposeNewOwner: ( + updateConfig: ( { - newOwner, + updates, }: { - newOwner: string + updates: ConfigUpdates }, funds?: Coin[], ) => MsgExecuteContractEncodeObject - acceptOwnership: (funds?: Coin[]) => MsgExecuteContractEncodeObject + acceptMinterRole: (funds?: Coin[]) => MsgExecuteContractEncodeObject mint: ( { user, @@ -51,6 +53,14 @@ export interface MarsAccountNftMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject + burn: ( + { + tokenId, + }: { + tokenId: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject transferNft: ( { recipient, @@ -113,14 +123,6 @@ export interface MarsAccountNftMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject - burn: ( - { - tokenId, - }: { - tokenId: string - }, - funds?: Coin[], - ) => MsgExecuteContractEncodeObject } export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { sender: string @@ -129,23 +131,23 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { constructor(sender: string, contractAddress: string) { this.sender = sender this.contractAddress = contractAddress - this.proposeNewOwner = this.proposeNewOwner.bind(this) - this.acceptOwnership = this.acceptOwnership.bind(this) + this.updateConfig = this.updateConfig.bind(this) + this.acceptMinterRole = this.acceptMinterRole.bind(this) this.mint = this.mint.bind(this) + this.burn = this.burn.bind(this) this.transferNft = this.transferNft.bind(this) this.sendNft = this.sendNft.bind(this) this.approve = this.approve.bind(this) this.revoke = this.revoke.bind(this) this.approveAll = this.approveAll.bind(this) this.revokeAll = this.revokeAll.bind(this) - this.burn = this.burn.bind(this) } - proposeNewOwner = ( + updateConfig = ( { - newOwner, + updates, }: { - newOwner: string + updates: ConfigUpdates }, funds?: Coin[], ): MsgExecuteContractEncodeObject => { @@ -156,8 +158,8 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - propose_new_owner: { - new_owner: newOwner, + update_config: { + updates, }, }), ), @@ -165,7 +167,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }), } } - acceptOwnership = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + acceptMinterRole = (funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -173,7 +175,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - accept_ownership: {}, + accept_minter_role: {}, }), ), funds, @@ -204,6 +206,30 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }), } } + burn = ( + { + tokenId, + }: { + tokenId: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + burn: { + token_id: tokenId, + }, + }), + ), + funds, + }), + } + } transferNft = ( { recipient, @@ -369,28 +395,4 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }), } } - burn = ( - { - tokenId, - }: { - tokenId: string - }, - funds?: Coin[], - ): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - burn: { - token_id: tokenId, - }, - }), - ), - funds, - }), - } - } } diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts deleted file mode 100644 index 44f64ae51..000000000 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts +++ /dev/null @@ -1,564 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' -import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee, Coin } from '@cosmjs/amino' -import { - InstantiateMsg, - ExecuteMsg, - Binary, - Expiration, - Timestamp, - Uint64, - QueryMsg, - AllNftInfoResponseForEmpty, - OwnerOfResponse, - Approval, - NftInfoResponseForEmpty, - Empty, - OperatorsResponse, - TokensResponse, - ApprovalResponse, - ApprovalsResponse, - ContractInfoResponse, - MinterResponse, - NumTokensResponse, - String, -} from './MarsAccountNft.types' -import { MarsAccountNftQueryClient, MarsAccountNftClient } from './MarsAccountNft.client' -export const marsAccountNftQueryKeys = { - contract: [ - { - contract: 'marsAccountNft', - }, - ] as const, - address: (contractAddress: string | undefined) => - [{ ...marsAccountNftQueryKeys.contract[0], address: contractAddress }] as const, - proposedNewOwner: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsAccountNftQueryKeys.address(contractAddress)[0], - method: 'proposed_new_owner', - args, - }, - ] as const, - ownerOf: (contractAddress: string | undefined, args?: Record) => - [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'owner_of', args }] as const, - approval: (contractAddress: string | undefined, args?: Record) => - [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'approval', args }] as const, - approvals: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'approvals', args }, - ] as const, - allOperators: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'all_operators', args }, - ] as const, - numTokens: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'num_tokens', args }, - ] as const, - contractInfo: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'contract_info', args }, - ] as const, - nftInfo: (contractAddress: string | undefined, args?: Record) => - [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'nft_info', args }] as const, - allNftInfo: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'all_nft_info', args }, - ] as const, - tokens: (contractAddress: string | undefined, args?: Record) => - [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'tokens', args }] as const, - allTokens: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'all_tokens', args }, - ] as const, - minter: (contractAddress: string | undefined, args?: Record) => - [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'minter', args }] as const, -} -export interface MarsAccountNftReactQuery { - client: MarsAccountNftQueryClient | undefined - options?: Omit< - UseQueryOptions, - "'queryKey' | 'queryFn' | 'initialData'" - > & { - initialData?: undefined - } -} -export interface MarsAccountNftMinterQuery - extends MarsAccountNftReactQuery {} -export function useMarsAccountNftMinterQuery({ - client, - options, -}: MarsAccountNftMinterQuery) { - return useQuery( - marsAccountNftQueryKeys.minter(client?.contractAddress), - () => (client ? client.minter() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsAccountNftAllTokensQuery - extends MarsAccountNftReactQuery { - args: { - limit?: number - startAfter?: string - } -} -export function useMarsAccountNftAllTokensQuery({ - client, - args, - options, -}: MarsAccountNftAllTokensQuery) { - return useQuery( - marsAccountNftQueryKeys.allTokens(client?.contractAddress, args), - () => - client - ? client.allTokens({ - limit: args.limit, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsAccountNftTokensQuery - extends MarsAccountNftReactQuery { - args: { - limit?: number - owner: string - startAfter?: string - } -} -export function useMarsAccountNftTokensQuery({ - client, - args, - options, -}: MarsAccountNftTokensQuery) { - return useQuery( - marsAccountNftQueryKeys.tokens(client?.contractAddress, args), - () => - client - ? client.tokens({ - limit: args.limit, - owner: args.owner, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsAccountNftAllNftInfoQuery - extends MarsAccountNftReactQuery { - args: { - includeExpired?: boolean - tokenId: string - } -} -export function useMarsAccountNftAllNftInfoQuery({ - client, - args, - options, -}: MarsAccountNftAllNftInfoQuery) { - return useQuery( - marsAccountNftQueryKeys.allNftInfo(client?.contractAddress, args), - () => - client - ? client.allNftInfo({ - includeExpired: args.includeExpired, - tokenId: args.tokenId, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsAccountNftNftInfoQuery - extends MarsAccountNftReactQuery { - args: { - tokenId: string - } -} -export function useMarsAccountNftNftInfoQuery({ - client, - args, - options, -}: MarsAccountNftNftInfoQuery) { - return useQuery( - marsAccountNftQueryKeys.nftInfo(client?.contractAddress, args), - () => - client - ? client.nftInfo({ - tokenId: args.tokenId, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsAccountNftContractInfoQuery - extends MarsAccountNftReactQuery {} -export function useMarsAccountNftContractInfoQuery({ - client, - options, -}: MarsAccountNftContractInfoQuery) { - return useQuery( - marsAccountNftQueryKeys.contractInfo(client?.contractAddress), - () => (client ? client.contractInfo() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsAccountNftNumTokensQuery - extends MarsAccountNftReactQuery {} -export function useMarsAccountNftNumTokensQuery({ - client, - options, -}: MarsAccountNftNumTokensQuery) { - return useQuery( - marsAccountNftQueryKeys.numTokens(client?.contractAddress), - () => (client ? client.numTokens() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsAccountNftAllOperatorsQuery - extends MarsAccountNftReactQuery { - args: { - includeExpired?: boolean - limit?: number - owner: string - startAfter?: string - } -} -export function useMarsAccountNftAllOperatorsQuery({ - client, - args, - options, -}: MarsAccountNftAllOperatorsQuery) { - return useQuery( - marsAccountNftQueryKeys.allOperators(client?.contractAddress, args), - () => - client - ? client.allOperators({ - includeExpired: args.includeExpired, - limit: args.limit, - owner: args.owner, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsAccountNftApprovalsQuery - extends MarsAccountNftReactQuery { - args: { - includeExpired?: boolean - tokenId: string - } -} -export function useMarsAccountNftApprovalsQuery({ - client, - args, - options, -}: MarsAccountNftApprovalsQuery) { - return useQuery( - marsAccountNftQueryKeys.approvals(client?.contractAddress, args), - () => - client - ? client.approvals({ - includeExpired: args.includeExpired, - tokenId: args.tokenId, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsAccountNftApprovalQuery - extends MarsAccountNftReactQuery { - args: { - includeExpired?: boolean - spender: string - tokenId: string - } -} -export function useMarsAccountNftApprovalQuery({ - client, - args, - options, -}: MarsAccountNftApprovalQuery) { - return useQuery( - marsAccountNftQueryKeys.approval(client?.contractAddress, args), - () => - client - ? client.approval({ - includeExpired: args.includeExpired, - spender: args.spender, - tokenId: args.tokenId, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsAccountNftOwnerOfQuery - extends MarsAccountNftReactQuery { - args: { - includeExpired?: boolean - tokenId: string - } -} -export function useMarsAccountNftOwnerOfQuery({ - client, - args, - options, -}: MarsAccountNftOwnerOfQuery) { - return useQuery( - marsAccountNftQueryKeys.ownerOf(client?.contractAddress, args), - () => - client - ? client.ownerOf({ - includeExpired: args.includeExpired, - tokenId: args.tokenId, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsAccountNftProposedNewOwnerQuery - extends MarsAccountNftReactQuery {} -export function useMarsAccountNftProposedNewOwnerQuery({ - client, - options, -}: MarsAccountNftProposedNewOwnerQuery) { - return useQuery( - marsAccountNftQueryKeys.proposedNewOwner(client?.contractAddress), - () => (client ? client.proposedNewOwner() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsAccountNftBurnMutation { - client: MarsAccountNftClient - msg: { - tokenId: string - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsAccountNftBurnMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.burn(msg, fee, memo, funds), - options, - ) -} -export interface MarsAccountNftRevokeAllMutation { - client: MarsAccountNftClient - msg: { - operator: string - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsAccountNftRevokeAllMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.revokeAll(msg, fee, memo, funds), - options, - ) -} -export interface MarsAccountNftApproveAllMutation { - client: MarsAccountNftClient - msg: { - expires?: Expiration - operator: string - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsAccountNftApproveAllMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.approveAll(msg, fee, memo, funds), - options, - ) -} -export interface MarsAccountNftRevokeMutation { - client: MarsAccountNftClient - msg: { - spender: string - tokenId: string - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsAccountNftRevokeMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.revoke(msg, fee, memo, funds), - options, - ) -} -export interface MarsAccountNftApproveMutation { - client: MarsAccountNftClient - msg: { - expires?: Expiration - spender: string - tokenId: string - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsAccountNftApproveMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.approve(msg, fee, memo, funds), - options, - ) -} -export interface MarsAccountNftSendNftMutation { - client: MarsAccountNftClient - msg: { - contract: string - msg: Binary - tokenId: string - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsAccountNftSendNftMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.sendNft(msg, fee, memo, funds), - options, - ) -} -export interface MarsAccountNftTransferNftMutation { - client: MarsAccountNftClient - msg: { - recipient: string - tokenId: string - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsAccountNftTransferNftMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.transferNft(msg, fee, memo, funds), - options, - ) -} -export interface MarsAccountNftMintMutation { - client: MarsAccountNftClient - msg: { - user: string - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsAccountNftMintMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.mint(msg, fee, memo, funds), - options, - ) -} -export interface MarsAccountNftAcceptOwnershipMutation { - client: MarsAccountNftClient - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsAccountNftAcceptOwnershipMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, args: { fee, memo, funds } = {} }) => client.acceptOwnership(fee, memo, funds), - options, - ) -} -export interface MarsAccountNftProposeNewOwnerMutation { - client: MarsAccountNftClient - msg: { - newOwner: string - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsAccountNftProposeNewOwnerMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => - client.proposeNewOwner(msg, fee, memo, funds), - options, - ) -} diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index 062be6203..319764ff9 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -1,29 +1,36 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ +export type Decimal = string export interface InstantiateMsg { + max_value_for_burn: Decimal minter: string name: string symbol: string } export type ExecuteMsg = | { - propose_new_owner: { - new_owner: string + update_config: { + updates: ConfigUpdates } } | { - accept_ownership: {} + accept_minter_role: {} } | { mint: { user: string } } + | { + burn: { + token_id: string + } + } | { transfer_nft: { recipient: string @@ -61,11 +68,6 @@ export type ExecuteMsg = operator: string } } - | { - burn: { - token_id: string - } - } export type Binary = string export type Expiration = | { @@ -79,9 +81,13 @@ export type Expiration = } export type Timestamp = Uint64 export type Uint64 = string +export interface ConfigUpdates { + max_value_for_burn?: Decimal | null + proposed_new_minter?: string | null +} export type QueryMsg = | { - proposed_new_owner: {} + config: {} } | { owner_of: { @@ -174,6 +180,10 @@ export interface ApprovalResponse { export interface ApprovalsResponse { approvals: Approval[] } +export interface ConfigBaseForString { + max_value_for_burn: Decimal + proposed_new_minter?: string | null +} export interface ContractInfoResponse { name: string symbol: string @@ -184,4 +194,3 @@ export interface MinterResponse { export interface NumTokensResponse { count: number } -export type String = string diff --git a/scripts/types/generated/mars-account-nft/bundle.ts b/scripts/types/generated/mars-account-nft/bundle.ts deleted file mode 100644 index b6237ebad..000000000 --- a/scripts/types/generated/mars-account-nft/bundle.ts +++ /dev/null @@ -1,14 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import * as _0 from './MarsAccountNft.types' -import * as _1 from './MarsAccountNft.client' -import * as _2 from './MarsAccountNft.message-composer' -import * as _3 from './MarsAccountNft.react-query' -export namespace contracts { - export const MarsAccountNft = { ..._0, ..._1, ..._2, ..._3 } -} diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index ea8e4a033..5d67f5429 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 456b6206e..59abab5f8 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index f0c19ab9e..a62010205 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-credit-manager/bundle.ts b/scripts/types/generated/mars-credit-manager/bundle.ts deleted file mode 100644 index 5878326c1..000000000 --- a/scripts/types/generated/mars-credit-manager/bundle.ts +++ /dev/null @@ -1,14 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import * as _4 from './MarsCreditManager.types' -import * as _5 from './MarsCreditManager.client' -import * as _6 from './MarsCreditManager.message-composer' -import * as _7 from './MarsCreditManager.react-query' -export namespace contracts { - export const MarsCreditManager = { ..._4, ..._5, ..._6, ..._7 } -} diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts new file mode 100644 index 000000000..95069a4f0 --- /dev/null +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -0,0 +1,344 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + Decimal, + HealthResponse, + QueryMsg, + Uint128, + VaultBaseForString, + Coin, + ArrayOfCoinBalanceResponseItem, + CoinBalanceResponseItem, + ArrayOfSharesResponseItem, + SharesResponseItem, + ArrayOfDebtShares, + DebtShares, + Addr, + ArrayOfVaultWithBalance, + VaultWithBalance, + VaultBaseForAddr, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, + ArrayOfVaultPositionResponseItem, + VaultPositionResponseItem, + VaultPosition, + LockingVaultAmount, + VaultUnlockingPosition, + ArrayOfString, + ConfigResponse, + ArrayOfCoin, + Positions, + DebtAmount, + ArrayOfVaultInstantiateConfig, + VaultInstantiateConfig, + VaultConfig, +} from './MarsMockCreditManager.types' +export interface MarsMockCreditManagerReadOnlyInterface { + contractAddress: string + config: () => Promise + vaultConfigs: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: VaultBaseForString + }) => Promise + allowedCoins: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }) => Promise + positions: ({ accountId }: { accountId: string }) => Promise + health: ({ accountId }: { accountId: string }) => Promise + allCoinBalances: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }) => Promise + allDebtShares: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }) => Promise + totalDebtShares: () => Promise + allTotalDebtShares: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }) => Promise + allVaultPositions: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }) => Promise + totalVaultCoinBalance: ({ vault }: { vault: VaultBaseForString }) => Promise + allTotalVaultCoinBalances: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: VaultBaseForString + }) => Promise + estimateProvideLiquidity: ({ + coinsIn, + lpTokenOut, + }: { + coinsIn: Coin[] + lpTokenOut: string + }) => Promise + estimateWithdrawLiquidity: ({ lpToken }: { lpToken: Coin }) => Promise +} +export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.config = this.config.bind(this) + this.vaultConfigs = this.vaultConfigs.bind(this) + this.allowedCoins = this.allowedCoins.bind(this) + this.positions = this.positions.bind(this) + this.health = this.health.bind(this) + this.allCoinBalances = this.allCoinBalances.bind(this) + this.allDebtShares = this.allDebtShares.bind(this) + this.totalDebtShares = this.totalDebtShares.bind(this) + this.allTotalDebtShares = this.allTotalDebtShares.bind(this) + this.allVaultPositions = this.allVaultPositions.bind(this) + this.totalVaultCoinBalance = this.totalVaultCoinBalance.bind(this) + this.allTotalVaultCoinBalances = this.allTotalVaultCoinBalances.bind(this) + this.estimateProvideLiquidity = this.estimateProvideLiquidity.bind(this) + this.estimateWithdrawLiquidity = this.estimateWithdrawLiquidity.bind(this) + } + + config = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + config: {}, + }) + } + vaultConfigs = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: VaultBaseForString + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + vault_configs: { + limit, + start_after: startAfter, + }, + }) + } + allowedCoins = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + allowed_coins: { + limit, + start_after: startAfter, + }, + }) + } + positions = async ({ accountId }: { accountId: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + positions: { + account_id: accountId, + }, + }) + } + health = async ({ accountId }: { accountId: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + health: { + account_id: accountId, + }, + }) + } + allCoinBalances = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_coin_balances: { + limit, + start_after: startAfter, + }, + }) + } + allDebtShares = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_debt_shares: { + limit, + start_after: startAfter, + }, + }) + } + totalDebtShares = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + total_debt_shares: {}, + }) + } + allTotalDebtShares = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_total_debt_shares: { + limit, + start_after: startAfter, + }, + }) + } + allVaultPositions = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_vault_positions: { + limit, + start_after: startAfter, + }, + }) + } + totalVaultCoinBalance = async ({ vault }: { vault: VaultBaseForString }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + total_vault_coin_balance: { + vault, + }, + }) + } + allTotalVaultCoinBalances = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: VaultBaseForString + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_total_vault_coin_balances: { + limit, + start_after: startAfter, + }, + }) + } + estimateProvideLiquidity = async ({ + coinsIn, + lpTokenOut, + }: { + coinsIn: Coin[] + lpTokenOut: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + estimate_provide_liquidity: { + coins_in: coinsIn, + lp_token_out: lpTokenOut, + }, + }) + } + estimateWithdrawLiquidity = async ({ lpToken }: { lpToken: Coin }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + estimate_withdraw_liquidity: { + lp_token: lpToken, + }, + }) + } +} +export interface MarsMockCreditManagerInterface extends MarsMockCreditManagerReadOnlyInterface { + contractAddress: string + sender: string + setHealthResponse: ( + { + accountId, + response, + }: { + accountId: string + response: HealthResponse + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise +} +export class MarsMockCreditManagerClient + extends MarsMockCreditManagerQueryClient + implements MarsMockCreditManagerInterface +{ + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.setHealthResponse = this.setHealthResponse.bind(this) + } + + setHealthResponse = async ( + { + accountId, + response, + }: { + accountId: string + response: HealthResponse + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + set_health_response: { + account_id: accountId, + response, + }, + }, + fee, + memo, + funds, + ) + } +} diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts new file mode 100644 index 000000000..3d03cc874 --- /dev/null +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -0,0 +1,99 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + InstantiateMsg, + ExecuteMsg, + Decimal, + HealthResponse, + QueryMsg, + Uint128, + VaultBaseForString, + Coin, + ArrayOfCoinBalanceResponseItem, + CoinBalanceResponseItem, + ArrayOfSharesResponseItem, + SharesResponseItem, + ArrayOfDebtShares, + DebtShares, + Addr, + ArrayOfVaultWithBalance, + VaultWithBalance, + VaultBaseForAddr, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, + ArrayOfVaultPositionResponseItem, + VaultPositionResponseItem, + VaultPosition, + LockingVaultAmount, + VaultUnlockingPosition, + ArrayOfString, + ConfigResponse, + ArrayOfCoin, + Positions, + DebtAmount, + ArrayOfVaultInstantiateConfig, + VaultInstantiateConfig, + VaultConfig, +} from './MarsMockCreditManager.types' +export interface MarsMockCreditManagerMessage { + contractAddress: string + sender: string + setHealthResponse: ( + { + accountId, + response, + }: { + accountId: string + response: HealthResponse + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject +} +export class MarsMockCreditManagerMessageComposer implements MarsMockCreditManagerMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.setHealthResponse = this.setHealthResponse.bind(this) + } + + setHealthResponse = ( + { + accountId, + response, + }: { + accountId: string + response: HealthResponse + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + set_health_response: { + account_id: accountId, + response, + }, + }), + ), + funds, + }), + } + } +} diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts similarity index 52% rename from scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts rename to scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index 8d46ac266..754bc4101 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -1,41 +1,31 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee } from '@cosmjs/amino' import { - Uint128, - Decimal, - OracleBaseForString, - RedBankBaseForString, - SwapperBaseForString, - ZapperBaseForString, InstantiateMsg, - VaultInstantiateConfig, - VaultConfig, - Coin, - VaultBaseForString, ExecuteMsg, - Action, - VaultPositionType, - CallbackMsg, - Addr, - ConfigUpdates, - VaultBaseForAddr, + Decimal, + HealthResponse, QueryMsg, + Uint128, + VaultBaseForString, + Coin, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, SharesResponseItem, ArrayOfDebtShares, DebtShares, + Addr, ArrayOfVaultWithBalance, VaultWithBalance, + VaultBaseForAddr, VaultPositionAmount, VaultAmount, VaultAmount1, @@ -48,44 +38,56 @@ import { ArrayOfString, ConfigResponse, ArrayOfCoin, - HealthResponse, Positions, DebtAmount, ArrayOfVaultInstantiateConfig, -} from './MarsCreditManager.types' -import { MarsCreditManagerQueryClient, MarsCreditManagerClient } from './MarsCreditManager.client' -export const marsCreditManagerQueryKeys = { + VaultInstantiateConfig, + VaultConfig, +} from './MarsMockCreditManager.types' +import { + MarsMockCreditManagerQueryClient, + MarsMockCreditManagerClient, +} from './MarsMockCreditManager.client' +export const marsMockCreditManagerQueryKeys = { contract: [ { - contract: 'marsCreditManager', + contract: 'marsMockCreditManager', }, ] as const, address: (contractAddress: string | undefined) => - [{ ...marsCreditManagerQueryKeys.contract[0], address: contractAddress }] as const, + [{ ...marsMockCreditManagerQueryKeys.contract[0], address: contractAddress }] as const, config: (contractAddress: string | undefined, args?: Record) => [ - { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, + { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, ] as const, vaultConfigs: (contractAddress: string | undefined, args?: Record) => [ - { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'vault_configs', args }, + { + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], + method: 'vault_configs', + args, + }, ] as const, allowedCoins: (contractAddress: string | undefined, args?: Record) => [ - { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'allowed_coins', args }, + { + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], + method: 'allowed_coins', + args, + }, ] as const, positions: (contractAddress: string | undefined, args?: Record) => [ - { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'positions', args }, + { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'positions', args }, ] as const, health: (contractAddress: string | undefined, args?: Record) => [ - { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'health', args }, + { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'health', args }, ] as const, allCoinBalances: (contractAddress: string | undefined, args?: Record) => [ { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'all_coin_balances', args, }, @@ -93,7 +95,7 @@ export const marsCreditManagerQueryKeys = { allDebtShares: (contractAddress: string | undefined, args?: Record) => [ { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'all_debt_shares', args, }, @@ -101,7 +103,7 @@ export const marsCreditManagerQueryKeys = { totalDebtShares: (contractAddress: string | undefined, args?: Record) => [ { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'total_debt_shares', args, }, @@ -109,7 +111,7 @@ export const marsCreditManagerQueryKeys = { allTotalDebtShares: (contractAddress: string | undefined, args?: Record) => [ { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'all_total_debt_shares', args, }, @@ -117,7 +119,7 @@ export const marsCreditManagerQueryKeys = { allVaultPositions: (contractAddress: string | undefined, args?: Record) => [ { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'all_vault_positions', args, }, @@ -125,7 +127,7 @@ export const marsCreditManagerQueryKeys = { totalVaultCoinBalance: (contractAddress: string | undefined, args?: Record) => [ { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'total_vault_coin_balance', args, }, @@ -136,7 +138,7 @@ export const marsCreditManagerQueryKeys = { ) => [ { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'all_total_vault_coin_balances', args, }, @@ -144,7 +146,7 @@ export const marsCreditManagerQueryKeys = { estimateProvideLiquidity: (contractAddress: string | undefined, args?: Record) => [ { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'estimate_provide_liquidity', args, }, @@ -155,14 +157,14 @@ export const marsCreditManagerQueryKeys = { ) => [ { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'estimate_withdraw_liquidity', args, }, ] as const, } -export interface MarsCreditManagerReactQuery { - client: MarsCreditManagerQueryClient | undefined +export interface MarsMockCreditManagerReactQuery { + client: MarsMockCreditManagerQueryClient | undefined options?: Omit< UseQueryOptions, "'queryKey' | 'queryFn' | 'initialData'" @@ -170,19 +172,19 @@ export interface MarsCreditManagerReactQuery { initialData?: undefined } } -export interface MarsCreditManagerEstimateWithdrawLiquidityQuery - extends MarsCreditManagerReactQuery { +export interface MarsMockCreditManagerEstimateWithdrawLiquidityQuery + extends MarsMockCreditManagerReactQuery { args: { lpToken: Coin } } -export function useMarsCreditManagerEstimateWithdrawLiquidityQuery({ +export function useMarsMockCreditManagerEstimateWithdrawLiquidityQuery({ client, args, options, -}: MarsCreditManagerEstimateWithdrawLiquidityQuery) { +}: MarsMockCreditManagerEstimateWithdrawLiquidityQuery) { return useQuery( - marsCreditManagerQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), + marsMockCreditManagerQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), () => client ? client.estimateWithdrawLiquidity({ @@ -192,20 +194,20 @@ export function useMarsCreditManagerEstimateWithdrawLiquidityQuery - extends MarsCreditManagerReactQuery { +export interface MarsMockCreditManagerEstimateProvideLiquidityQuery + extends MarsMockCreditManagerReactQuery { args: { coinsIn: Coin[] lpTokenOut: string } } -export function useMarsCreditManagerEstimateProvideLiquidityQuery({ +export function useMarsMockCreditManagerEstimateProvideLiquidityQuery({ client, args, options, -}: MarsCreditManagerEstimateProvideLiquidityQuery) { +}: MarsMockCreditManagerEstimateProvideLiquidityQuery) { return useQuery( - marsCreditManagerQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), + marsMockCreditManagerQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), () => client ? client.estimateProvideLiquidity({ @@ -216,18 +218,18 @@ export function useMarsCreditManagerEstimateProvideLiquidityQuery - extends MarsCreditManagerReactQuery { +export interface MarsMockCreditManagerAllTotalVaultCoinBalancesQuery + extends MarsMockCreditManagerReactQuery { args: { limit?: number startAfter?: VaultBaseForString } } -export function useMarsCreditManagerAllTotalVaultCoinBalancesQuery< +export function useMarsMockCreditManagerAllTotalVaultCoinBalancesQuery< TData = ArrayOfVaultWithBalance, ->({ client, args, options }: MarsCreditManagerAllTotalVaultCoinBalancesQuery) { +>({ client, args, options }: MarsMockCreditManagerAllTotalVaultCoinBalancesQuery) { return useQuery( - marsCreditManagerQueryKeys.allTotalVaultCoinBalances(client?.contractAddress, args), + marsMockCreditManagerQueryKeys.allTotalVaultCoinBalances(client?.contractAddress, args), () => client ? client.allTotalVaultCoinBalances({ @@ -238,19 +240,19 @@ export function useMarsCreditManagerAllTotalVaultCoinBalancesQuery< { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsCreditManagerTotalVaultCoinBalanceQuery - extends MarsCreditManagerReactQuery { +export interface MarsMockCreditManagerTotalVaultCoinBalanceQuery + extends MarsMockCreditManagerReactQuery { args: { vault: VaultBaseForString } } -export function useMarsCreditManagerTotalVaultCoinBalanceQuery({ +export function useMarsMockCreditManagerTotalVaultCoinBalanceQuery({ client, args, options, -}: MarsCreditManagerTotalVaultCoinBalanceQuery) { +}: MarsMockCreditManagerTotalVaultCoinBalanceQuery) { return useQuery( - marsCreditManagerQueryKeys.totalVaultCoinBalance(client?.contractAddress, args), + marsMockCreditManagerQueryKeys.totalVaultCoinBalance(client?.contractAddress, args), () => client ? client.totalVaultCoinBalance({ @@ -260,18 +262,18 @@ export function useMarsCreditManagerTotalVaultCoinBalanceQuery( { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsCreditManagerAllVaultPositionsQuery - extends MarsCreditManagerReactQuery { +export interface MarsMockCreditManagerAllVaultPositionsQuery + extends MarsMockCreditManagerReactQuery { args: { limit?: number startAfter?: string[][] } } -export function useMarsCreditManagerAllVaultPositionsQuery< +export function useMarsMockCreditManagerAllVaultPositionsQuery< TData = ArrayOfVaultPositionResponseItem, ->({ client, args, options }: MarsCreditManagerAllVaultPositionsQuery) { +>({ client, args, options }: MarsMockCreditManagerAllVaultPositionsQuery) { return useQuery( - marsCreditManagerQueryKeys.allVaultPositions(client?.contractAddress, args), + marsMockCreditManagerQueryKeys.allVaultPositions(client?.contractAddress, args), () => client ? client.allVaultPositions({ @@ -282,20 +284,20 @@ export function useMarsCreditManagerAllVaultPositionsQuery< { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsCreditManagerAllTotalDebtSharesQuery - extends MarsCreditManagerReactQuery { +export interface MarsMockCreditManagerAllTotalDebtSharesQuery + extends MarsMockCreditManagerReactQuery { args: { limit?: number startAfter?: string } } -export function useMarsCreditManagerAllTotalDebtSharesQuery({ +export function useMarsMockCreditManagerAllTotalDebtSharesQuery({ client, args, options, -}: MarsCreditManagerAllTotalDebtSharesQuery) { +}: MarsMockCreditManagerAllTotalDebtSharesQuery) { return useQuery( - marsCreditManagerQueryKeys.allTotalDebtShares(client?.contractAddress, args), + marsMockCreditManagerQueryKeys.allTotalDebtShares(client?.contractAddress, args), () => client ? client.allTotalDebtShares({ @@ -306,32 +308,32 @@ export function useMarsCreditManagerAllTotalDebtSharesQuery - extends MarsCreditManagerReactQuery {} -export function useMarsCreditManagerTotalDebtSharesQuery({ +export interface MarsMockCreditManagerTotalDebtSharesQuery + extends MarsMockCreditManagerReactQuery {} +export function useMarsMockCreditManagerTotalDebtSharesQuery({ client, options, -}: MarsCreditManagerTotalDebtSharesQuery) { +}: MarsMockCreditManagerTotalDebtSharesQuery) { return useQuery( - marsCreditManagerQueryKeys.totalDebtShares(client?.contractAddress), + marsMockCreditManagerQueryKeys.totalDebtShares(client?.contractAddress), () => (client ? client.totalDebtShares() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsCreditManagerAllDebtSharesQuery - extends MarsCreditManagerReactQuery { +export interface MarsMockCreditManagerAllDebtSharesQuery + extends MarsMockCreditManagerReactQuery { args: { limit?: number startAfter?: string[][] } } -export function useMarsCreditManagerAllDebtSharesQuery({ +export function useMarsMockCreditManagerAllDebtSharesQuery({ client, args, options, -}: MarsCreditManagerAllDebtSharesQuery) { +}: MarsMockCreditManagerAllDebtSharesQuery) { return useQuery( - marsCreditManagerQueryKeys.allDebtShares(client?.contractAddress, args), + marsMockCreditManagerQueryKeys.allDebtShares(client?.contractAddress, args), () => client ? client.allDebtShares({ @@ -342,20 +344,18 @@ export function useMarsCreditManagerAllDebtSharesQuery - extends MarsCreditManagerReactQuery { +export interface MarsMockCreditManagerAllCoinBalancesQuery + extends MarsMockCreditManagerReactQuery { args: { limit?: number startAfter?: string[][] } } -export function useMarsCreditManagerAllCoinBalancesQuery({ - client, - args, - options, -}: MarsCreditManagerAllCoinBalancesQuery) { +export function useMarsMockCreditManagerAllCoinBalancesQuery< + TData = ArrayOfCoinBalanceResponseItem, +>({ client, args, options }: MarsMockCreditManagerAllCoinBalancesQuery) { return useQuery( - marsCreditManagerQueryKeys.allCoinBalances(client?.contractAddress, args), + marsMockCreditManagerQueryKeys.allCoinBalances(client?.contractAddress, args), () => client ? client.allCoinBalances({ @@ -366,19 +366,19 @@ export function useMarsCreditManagerAllCoinBalancesQuery - extends MarsCreditManagerReactQuery { +export interface MarsMockCreditManagerHealthQuery + extends MarsMockCreditManagerReactQuery { args: { accountId: string } } -export function useMarsCreditManagerHealthQuery({ +export function useMarsMockCreditManagerHealthQuery({ client, args, options, -}: MarsCreditManagerHealthQuery) { +}: MarsMockCreditManagerHealthQuery) { return useQuery( - marsCreditManagerQueryKeys.health(client?.contractAddress, args), + marsMockCreditManagerQueryKeys.health(client?.contractAddress, args), () => client ? client.health({ @@ -388,19 +388,19 @@ export function useMarsCreditManagerHealthQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsCreditManagerPositionsQuery - extends MarsCreditManagerReactQuery { +export interface MarsMockCreditManagerPositionsQuery + extends MarsMockCreditManagerReactQuery { args: { accountId: string } } -export function useMarsCreditManagerPositionsQuery({ +export function useMarsMockCreditManagerPositionsQuery({ client, args, options, -}: MarsCreditManagerPositionsQuery) { +}: MarsMockCreditManagerPositionsQuery) { return useQuery( - marsCreditManagerQueryKeys.positions(client?.contractAddress, args), + marsMockCreditManagerQueryKeys.positions(client?.contractAddress, args), () => client ? client.positions({ @@ -410,20 +410,20 @@ export function useMarsCreditManagerPositionsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsCreditManagerAllowedCoinsQuery - extends MarsCreditManagerReactQuery { +export interface MarsMockCreditManagerAllowedCoinsQuery + extends MarsMockCreditManagerReactQuery { args: { limit?: number startAfter?: string } } -export function useMarsCreditManagerAllowedCoinsQuery({ +export function useMarsMockCreditManagerAllowedCoinsQuery({ client, args, options, -}: MarsCreditManagerAllowedCoinsQuery) { +}: MarsMockCreditManagerAllowedCoinsQuery) { return useQuery( - marsCreditManagerQueryKeys.allowedCoins(client?.contractAddress, args), + marsMockCreditManagerQueryKeys.allowedCoins(client?.contractAddress, args), () => client ? client.allowedCoins({ @@ -434,20 +434,20 @@ export function useMarsCreditManagerAllowedCoinsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsCreditManagerVaultConfigsQuery - extends MarsCreditManagerReactQuery { +export interface MarsMockCreditManagerVaultConfigsQuery + extends MarsMockCreditManagerReactQuery { args: { limit?: number startAfter?: VaultBaseForString } } -export function useMarsCreditManagerVaultConfigsQuery({ +export function useMarsMockCreditManagerVaultConfigsQuery({ client, args, options, -}: MarsCreditManagerVaultConfigsQuery) { +}: MarsMockCreditManagerVaultConfigsQuery) { return useQuery( - marsCreditManagerQueryKeys.vaultConfigs(client?.contractAddress, args), + marsMockCreditManagerQueryKeys.vaultConfigs(client?.contractAddress, args), () => client ? client.vaultConfigs({ @@ -458,66 +458,23 @@ export function useMarsCreditManagerVaultConfigsQuery - extends MarsCreditManagerReactQuery {} -export function useMarsCreditManagerConfigQuery({ +export interface MarsMockCreditManagerConfigQuery + extends MarsMockCreditManagerReactQuery {} +export function useMarsMockCreditManagerConfigQuery({ client, options, -}: MarsCreditManagerConfigQuery) { +}: MarsMockCreditManagerConfigQuery) { return useQuery( - marsCreditManagerQueryKeys.config(client?.contractAddress), + marsMockCreditManagerQueryKeys.config(client?.contractAddress), () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsCreditManagerCallbackMutation { - client: MarsCreditManagerClient - msg: CallbackMsg - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsCreditManagerCallbackMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.callback(msg, fee, memo, funds), - options, - ) -} -export interface MarsCreditManagerUpdateConfigMutation { - client: MarsCreditManagerClient - msg: { - newConfig: ConfigUpdates - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsCreditManagerUpdateConfigMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => - client.updateConfig(msg, fee, memo, funds), - options, - ) -} -export interface MarsCreditManagerUpdateCreditAccountMutation { - client: MarsCreditManagerClient +export interface MarsMockCreditManagerSetHealthResponseMutation { + client: MarsMockCreditManagerClient msg: { accountId: string - actions: Action[] + response: HealthResponse } args?: { fee?: number | StdFee | 'auto' @@ -525,34 +482,15 @@ export interface MarsCreditManagerUpdateCreditAccountMutation { funds?: Coin[] } } -export function useMarsCreditManagerUpdateCreditAccountMutation( +export function useMarsMockCreditManagerSetHealthResponseMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => - client.updateCreditAccount(msg, fee, memo, funds), - options, - ) -} -export interface MarsCreditManagerCreateCreditAccountMutation { - client: MarsCreditManagerClient - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsCreditManagerCreateCreditAccountMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, args: { fee, memo, funds } = {} }) => client.createCreditAccount(fee, memo, funds), + client.setHealthResponse(msg, fee, memo, funds), options, ) } diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts new file mode 100644 index 000000000..7c710362a --- /dev/null +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -0,0 +1,197 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + [k: string]: unknown +} +export type ExecuteMsg = { + set_health_response: { + account_id: string + response: HealthResponse + } +} +export type Decimal = string +export interface HealthResponse { + above_max_ltv: boolean + liquidatable: boolean + liquidation_health_factor?: Decimal | null + liquidation_threshold_adjusted_collateral: Decimal + max_ltv_adjusted_collateral: Decimal + max_ltv_health_factor?: Decimal | null + total_collateral_value: Decimal + total_debt_value: Decimal +} +export type QueryMsg = + | { + config: {} + } + | { + vault_configs: { + limit?: number | null + start_after?: VaultBaseForString | null + } + } + | { + allowed_coins: { + limit?: number | null + start_after?: string | null + } + } + | { + positions: { + account_id: string + } + } + | { + health: { + account_id: string + } + } + | { + all_coin_balances: { + limit?: number | null + start_after?: [string, string] | null + } + } + | { + all_debt_shares: { + limit?: number | null + start_after?: [string, string] | null + } + } + | { + total_debt_shares: string + } + | { + all_total_debt_shares: { + limit?: number | null + start_after?: string | null + } + } + | { + all_vault_positions: { + limit?: number | null + start_after?: [string, string] | null + } + } + | { + total_vault_coin_balance: { + vault: VaultBaseForString + } + } + | { + all_total_vault_coin_balances: { + limit?: number | null + start_after?: VaultBaseForString | null + } + } + | { + estimate_provide_liquidity: { + coins_in: Coin[] + lp_token_out: string + } + } + | { + estimate_withdraw_liquidity: { + lp_token: Coin + } + } +export type Uint128 = string +export interface VaultBaseForString { + address: string +} +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} +export type ArrayOfCoinBalanceResponseItem = CoinBalanceResponseItem[] +export interface CoinBalanceResponseItem { + account_id: string + amount: Uint128 + denom: string +} +export type ArrayOfSharesResponseItem = SharesResponseItem[] +export interface SharesResponseItem { + account_id: string + denom: string + shares: Uint128 +} +export type ArrayOfDebtShares = DebtShares[] +export interface DebtShares { + denom: string + shares: Uint128 +} +export type Addr = string +export type ArrayOfVaultWithBalance = VaultWithBalance[] +export interface VaultWithBalance { + balance: Uint128 + vault: VaultBaseForAddr +} +export interface VaultBaseForAddr { + address: Addr +} +export type VaultPositionAmount = + | { + unlocked: VaultAmount + } + | { + locking: LockingVaultAmount + } +export type VaultAmount = string +export type VaultAmount1 = string +export type UnlockingPositions = VaultUnlockingPosition[] +export type ArrayOfVaultPositionResponseItem = VaultPositionResponseItem[] +export interface VaultPositionResponseItem { + account_id: string + position: VaultPosition +} +export interface VaultPosition { + amount: VaultPositionAmount + vault: VaultBaseForAddr +} +export interface LockingVaultAmount { + locked: VaultAmount1 + unlocking: UnlockingPositions +} +export interface VaultUnlockingPosition { + coin: Coin + id: number +} +export type ArrayOfString = string[] +export interface ConfigResponse { + account_nft?: string | null + max_close_factor: Decimal + oracle: string + owner: string + red_bank: string + swapper: string + zapper: string +} +export type ArrayOfCoin = Coin[] +export interface Positions { + account_id: string + coins: Coin[] + debts: DebtAmount[] + vaults: VaultPosition[] +} +export interface DebtAmount { + amount: Uint128 + denom: string + shares: Uint128 +} +export type ArrayOfVaultInstantiateConfig = VaultInstantiateConfig[] +export interface VaultInstantiateConfig { + config: VaultConfig + vault: VaultBaseForString +} +export interface VaultConfig { + deposit_cap: Coin + liquidation_threshold: Decimal + max_ltv: Decimal + whitelisted: boolean +} diff --git a/scripts/types/generated/mars-mock-credit-manager/bundle.ts b/scripts/types/generated/mars-mock-credit-manager/bundle.ts new file mode 100644 index 000000000..a30bc8d56 --- /dev/null +++ b/scripts/types/generated/mars-mock-credit-manager/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _0 from './MarsMockCreditManager.types' +import * as _1 from './MarsMockCreditManager.client' +import * as _2 from './MarsMockCreditManager.message-composer' +import * as _3 from './MarsMockCreditManager.react-query' +export namespace contracts { + export const MarsMockCreditManager = { ..._0, ..._1, ..._2, ..._3 } +} diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts index 2a4748b54..df9bd7f8f 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts index 6db180841..c28913138 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts index a255e74ad..7d043667c 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts @@ -1,13 +1,12 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee, Coin } from '@cosmjs/amino' import { Decimal, InstantiateMsg, @@ -61,7 +60,10 @@ export function useMarsMockOraclePriceQuery({ } export interface MarsMockOracleChangePriceMutation { client: MarsMockOracleClient - msg: CoinPrice + msg: { + denom: string + price: Decimal + } args?: { fee?: number | StdFee | 'auto' memo?: string diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts index 56d42aa73..20bc906db 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/bundle.ts b/scripts/types/generated/mars-mock-oracle/bundle.ts index 9077d952b..9399f171c 100644 --- a/scripts/types/generated/mars-mock-oracle/bundle.ts +++ b/scripts/types/generated/mars-mock-oracle/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _8 from './MarsMockOracle.types' -import * as _9 from './MarsMockOracle.client' -import * as _10 from './MarsMockOracle.message-composer' -import * as _11 from './MarsMockOracle.react-query' +import * as _4 from './MarsMockOracle.types' +import * as _5 from './MarsMockOracle.client' +import * as _6 from './MarsMockOracle.message-composer' +import * as _7 from './MarsMockOracle.react-query' export namespace contracts { - export const MarsMockOracle = { ..._8, ..._9, ..._10, ..._11 } + export const MarsMockOracle = { ..._4, ..._5, ..._6, ..._7 } } diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index 2eda55c06..f423caa7b 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts index 5f75dbe11..0b05b5e41 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index 55f99f412..6a7b5aa45 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -1,13 +1,12 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee, Coin } from '@cosmjs/amino' import { Decimal, InstantiateMsg, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index 3364364a7..26130dae2 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/bundle.ts b/scripts/types/generated/mars-mock-red-bank/bundle.ts index 2fa1eefe2..b55e5be77 100644 --- a/scripts/types/generated/mars-mock-red-bank/bundle.ts +++ b/scripts/types/generated/mars-mock-red-bank/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _12 from './MarsMockRedBank.types' -import * as _13 from './MarsMockRedBank.client' -import * as _14 from './MarsMockRedBank.message-composer' -import * as _15 from './MarsMockRedBank.react-query' +import * as _8 from './MarsMockRedBank.types' +import * as _9 from './MarsMockRedBank.client' +import * as _10 from './MarsMockRedBank.message-composer' +import * as _11 from './MarsMockRedBank.react-query' export namespace contracts { - export const MarsMockRedBank = { ..._12, ..._13, ..._14, ..._15 } + export const MarsMockRedBank = { ..._8, ..._9, ..._10, ..._11 } } diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts index b162b4d32..494efa813 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts index 40d94fe26..42a1441d6 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts deleted file mode 100644 index 5efc557c5..000000000 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts +++ /dev/null @@ -1,302 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' -import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee, Coin } from '@cosmjs/amino' -import { - Duration, - OracleBaseForString, - InstantiateMsg, - ExecuteMsg, - Uint128, - ExtensionExecuteMsg, - LockupExecuteMsg, - ForceUnlockExecuteMsg, - QueryMsg, - ExtensionQueryMsg, - LockupQueryMsg, - VaultInfoResponse, - Empty, - VaultStandardInfoResponse, -} from './MarsMockVault.types' -import { MarsMockVaultQueryClient, MarsMockVaultClient } from './MarsMockVault.client' -export const marsMockVaultQueryKeys = { - contract: [ - { - contract: 'marsMockVault', - }, - ] as const, - address: (contractAddress: string | undefined) => - [{ ...marsMockVaultQueryKeys.contract[0], address: contractAddress }] as const, - vaultStandardInfo: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsMockVaultQueryKeys.address(contractAddress)[0], - method: 'vault_standard_info', - args, - }, - ] as const, - info: (contractAddress: string | undefined, args?: Record) => - [{ ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'info', args }] as const, - previewDeposit: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'preview_deposit', args }, - ] as const, - previewRedeem: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'preview_redeem', args }, - ] as const, - totalAssets: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'total_assets', args }, - ] as const, - totalVaultTokenSupply: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsMockVaultQueryKeys.address(contractAddress)[0], - method: 'total_vault_token_supply', - args, - }, - ] as const, - convertToShares: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'convert_to_shares', args }, - ] as const, - convertToAssets: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'convert_to_assets', args }, - ] as const, - vaultExtension: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'vault_extension', args }, - ] as const, -} -export interface MarsMockVaultReactQuery { - client: MarsMockVaultQueryClient | undefined - options?: Omit< - UseQueryOptions, - "'queryKey' | 'queryFn' | 'initialData'" - > & { - initialData?: undefined - } -} -export interface MarsMockVaultVaultExtensionQuery - extends MarsMockVaultReactQuery {} -export function useMarsMockVaultVaultExtensionQuery({ - client, - options, -}: MarsMockVaultVaultExtensionQuery) { - return useQuery( - marsMockVaultQueryKeys.vaultExtension(client?.contractAddress), - () => (client ? client.vaultExtension() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsMockVaultConvertToAssetsQuery - extends MarsMockVaultReactQuery { - args: { - amount: Uint128 - } -} -export function useMarsMockVaultConvertToAssetsQuery({ - client, - args, - options, -}: MarsMockVaultConvertToAssetsQuery) { - return useQuery( - marsMockVaultQueryKeys.convertToAssets(client?.contractAddress, args), - () => - client - ? client.convertToAssets({ - amount: args.amount, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsMockVaultConvertToSharesQuery - extends MarsMockVaultReactQuery { - args: { - amount: Uint128 - } -} -export function useMarsMockVaultConvertToSharesQuery({ - client, - args, - options, -}: MarsMockVaultConvertToSharesQuery) { - return useQuery( - marsMockVaultQueryKeys.convertToShares(client?.contractAddress, args), - () => - client - ? client.convertToShares({ - amount: args.amount, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsMockVaultTotalVaultTokenSupplyQuery - extends MarsMockVaultReactQuery {} -export function useMarsMockVaultTotalVaultTokenSupplyQuery({ - client, - options, -}: MarsMockVaultTotalVaultTokenSupplyQuery) { - return useQuery( - marsMockVaultQueryKeys.totalVaultTokenSupply(client?.contractAddress), - () => (client ? client.totalVaultTokenSupply() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsMockVaultTotalAssetsQuery - extends MarsMockVaultReactQuery {} -export function useMarsMockVaultTotalAssetsQuery({ - client, - options, -}: MarsMockVaultTotalAssetsQuery) { - return useQuery( - marsMockVaultQueryKeys.totalAssets(client?.contractAddress), - () => (client ? client.totalAssets() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsMockVaultPreviewRedeemQuery - extends MarsMockVaultReactQuery { - args: { - amount: Uint128 - } -} -export function useMarsMockVaultPreviewRedeemQuery({ - client, - args, - options, -}: MarsMockVaultPreviewRedeemQuery) { - return useQuery( - marsMockVaultQueryKeys.previewRedeem(client?.contractAddress, args), - () => - client - ? client.previewRedeem({ - amount: args.amount, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsMockVaultPreviewDepositQuery - extends MarsMockVaultReactQuery { - args: { - amount: Uint128 - } -} -export function useMarsMockVaultPreviewDepositQuery({ - client, - args, - options, -}: MarsMockVaultPreviewDepositQuery) { - return useQuery( - marsMockVaultQueryKeys.previewDeposit(client?.contractAddress, args), - () => - client - ? client.previewDeposit({ - amount: args.amount, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsMockVaultInfoQuery - extends MarsMockVaultReactQuery {} -export function useMarsMockVaultInfoQuery({ - client, - options, -}: MarsMockVaultInfoQuery) { - return useQuery( - marsMockVaultQueryKeys.info(client?.contractAddress), - () => (client ? client.info() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsMockVaultVaultStandardInfoQuery - extends MarsMockVaultReactQuery {} -export function useMarsMockVaultVaultStandardInfoQuery({ - client, - options, -}: MarsMockVaultVaultStandardInfoQuery) { - return useQuery( - marsMockVaultQueryKeys.vaultStandardInfo(client?.contractAddress), - () => (client ? client.vaultStandardInfo() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsMockVaultVaultExtensionMutation { - client: MarsMockVaultClient - msg: ExtensionExecuteMsg - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsMockVaultVaultExtensionMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => - client.vaultExtension(msg, fee, memo, funds), - options, - ) -} -export interface MarsMockVaultRedeemMutation { - client: MarsMockVaultClient - msg: { - amount: Uint128 - recipient?: string - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsMockVaultRedeemMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.redeem(msg, fee, memo, funds), - options, - ) -} -export interface MarsMockVaultDepositMutation { - client: MarsMockVaultClient - msg: { - amount: Uint128 - recipient?: string - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsMockVaultDepositMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.deposit(msg, fee, memo, funds), - options, - ) -} diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts index fbfd98047..49fbd9803 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/bundle.ts b/scripts/types/generated/mars-mock-vault/bundle.ts deleted file mode 100644 index cc8347955..000000000 --- a/scripts/types/generated/mars-mock-vault/bundle.ts +++ /dev/null @@ -1,14 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import * as _16 from './MarsMockVault.types' -import * as _17 from './MarsMockVault.client' -import * as _18 from './MarsMockVault.message-composer' -import * as _19 from './MarsMockVault.react-query' -export namespace contracts { - export const MarsMockVault = { ..._16, ..._17, ..._18, ..._19 } -} diff --git a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts index c1d176a49..7ee488a61 100644 --- a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts +++ b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts index 822a00b22..fbd07c267 100644 --- a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts +++ b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts index 6f9fff7d1..8dfc93e07 100644 --- a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts +++ b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts @@ -1,13 +1,12 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee } from '@cosmjs/amino' import { OracleBaseForString, InstantiateMsg, diff --git a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.types.ts b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.types.ts index 64b086509..6b16d2d9c 100644 --- a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.types.ts +++ b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-zapper/bundle.ts b/scripts/types/generated/mars-mock-zapper/bundle.ts index 6bffb33f5..a2a6a7cc4 100644 --- a/scripts/types/generated/mars-mock-zapper/bundle.ts +++ b/scripts/types/generated/mars-mock-zapper/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _20 from './MarsMockZapper.types' -import * as _21 from './MarsMockZapper.client' -import * as _22 from './MarsMockZapper.message-composer' -import * as _23 from './MarsMockZapper.react-query' +import * as _12 from './MarsMockZapper.types' +import * as _13 from './MarsMockZapper.client' +import * as _14 from './MarsMockZapper.message-composer' +import * as _15 from './MarsMockZapper.react-query' export namespace contracts { - export const MarsMockZapper = { ..._20, ..._21, ..._22, ..._23 } + export const MarsMockZapper = { ..._12, ..._13, ..._14, ..._15 } } diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts index 3dd7dd657..5d43ba256 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts index 0ce7b8ca9..c11790ce7 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts index 8637ecdbb..7587f33ff 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts @@ -1,13 +1,12 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee } from '@cosmjs/amino' import { OracleBaseForString, Addr, diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts index 3072f9a66..78cad5964 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-oracle-adapter/bundle.ts b/scripts/types/generated/mars-oracle-adapter/bundle.ts index 63503f7f2..f7f58cd5d 100644 --- a/scripts/types/generated/mars-oracle-adapter/bundle.ts +++ b/scripts/types/generated/mars-oracle-adapter/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _24 from './MarsOracleAdapter.types' -import * as _25 from './MarsOracleAdapter.client' -import * as _26 from './MarsOracleAdapter.message-composer' -import * as _27 from './MarsOracleAdapter.react-query' +import * as _16 from './MarsOracleAdapter.types' +import * as _17 from './MarsOracleAdapter.client' +import * as _18 from './MarsOracleAdapter.message-composer' +import * as _19 from './MarsOracleAdapter.react-query' export namespace contracts { - export const MarsOracleAdapter = { ..._24, ..._25, ..._26, ..._27 } + export const MarsOracleAdapter = { ..._16, ..._17, ..._18, ..._19 } } diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts index f297c7f0c..a1d1e1222 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts index ac2e3c46c..eb64d6c9f 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts index 1c41e5831..89545a281 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts @@ -1,13 +1,12 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee } from '@cosmjs/amino' import { InstantiateMsg, ExecuteMsg, diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts index 1d2483afa..01bb7133f 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/bundle.ts b/scripts/types/generated/mars-swapper-base/bundle.ts index b36640116..e75a5bf26 100644 --- a/scripts/types/generated/mars-swapper-base/bundle.ts +++ b/scripts/types/generated/mars-swapper-base/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.20.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _28 from './MarsSwapperBase.types' -import * as _29 from './MarsSwapperBase.client' -import * as _30 from './MarsSwapperBase.message-composer' -import * as _31 from './MarsSwapperBase.react-query' +import * as _20 from './MarsSwapperBase.types' +import * as _21 from './MarsSwapperBase.client' +import * as _22 from './MarsSwapperBase.message-composer' +import * as _23 from './MarsSwapperBase.react-query' export namespace contracts { - export const MarsSwapperBase = { ..._28, ..._29, ..._30, ..._31 } + export const MarsSwapperBase = { ..._20, ..._21, ..._22, ..._23 } } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index f22530959..f8a153a5a 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1192,143 +1192,143 @@ "@noble/hashes" "^1.0.0" protobufjs "^6.8.8" -"@cosmjs/amino@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.29.3.tgz#5aa338a301ea970a93e15522706615efea507c10" - integrity sha512-BFz1++ERerIggiFc7iGHhGe1CeV3rCv8BvkoBQTBN/ZwzHOaKvqQj8smDlRGlQxX3HWlTwgiLN2A+OB5yX4ZRw== - dependencies: - "@cosmjs/crypto" "^0.29.3" - "@cosmjs/encoding" "^0.29.3" - "@cosmjs/math" "^0.29.3" - "@cosmjs/utils" "^0.29.3" - -"@cosmjs/cosmwasm-stargate@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.29.3.tgz#f6279fc6d590db01d6cb0f5cfae43bb2c88c279b" - integrity sha512-S13VlyWj2EGZ7hePT+kQkybksfjjYs/YffuZNVaD5P4CADiOcF52LGJcUCywyfYvC4RQYRVLOaq5hbAlLTUuew== - dependencies: - "@cosmjs/amino" "^0.29.3" - "@cosmjs/crypto" "^0.29.3" - "@cosmjs/encoding" "^0.29.3" - "@cosmjs/math" "^0.29.3" - "@cosmjs/proto-signing" "^0.29.3" - "@cosmjs/stargate" "^0.29.3" - "@cosmjs/tendermint-rpc" "^0.29.3" - "@cosmjs/utils" "^0.29.3" +"@cosmjs/amino@^0.29.4": + version "0.29.4" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.29.4.tgz#93d5f90033cb2af1573627582cd2cf8a515c3ef4" + integrity sha512-FBjaJ4oUKFtH34O7XjUk370x8sF7EbXD29miXrm0Rl5GEtEORJgQwutXQllHo5gBkpOxC+ZQ40CibXhPzH7G7A== + dependencies: + "@cosmjs/crypto" "^0.29.4" + "@cosmjs/encoding" "^0.29.4" + "@cosmjs/math" "^0.29.4" + "@cosmjs/utils" "^0.29.4" + +"@cosmjs/cosmwasm-stargate@^0.29.4": + version "0.29.4" + resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.29.4.tgz#877ce28b071381e3b2586f71fad20f53634237f5" + integrity sha512-/47QmB+fLuzhcoeTTwoGtyz73yH22txfj5r+iZmHbpQ2vwCoeyG1WVNuylDGOELOej74ngCC1LCPr2/6gGHIMQ== + dependencies: + "@cosmjs/amino" "^0.29.4" + "@cosmjs/crypto" "^0.29.4" + "@cosmjs/encoding" "^0.29.4" + "@cosmjs/math" "^0.29.4" + "@cosmjs/proto-signing" "^0.29.4" + "@cosmjs/stargate" "^0.29.4" + "@cosmjs/tendermint-rpc" "^0.29.4" + "@cosmjs/utils" "^0.29.4" cosmjs-types "^0.5.2" long "^4.0.0" pako "^2.0.2" -"@cosmjs/crypto@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.29.3.tgz#4af0ac1264f1638c31d03cfcbedf2fca36e17890" - integrity sha512-dOCYLLEOnn5idNgoAcdQnuvFXZx/BmLnb2Mh8ZZtw6peFNvRePfaMX12HerngkLVAcLBc/V6pZHWoj9DBrsvng== +"@cosmjs/crypto@^0.29.4": + version "0.29.4" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.29.4.tgz#2198e1d2da9eb310df9ed8b8609dbf1a370e900b" + integrity sha512-PmSxoFl/Won7kHZv3PQUUgdmEiAMqdY7XnEnVh9PbU7Hht6uo7PQ+M0eIGW3NIXYKmn6oVExER+xOfLfq4YNGw== dependencies: - "@cosmjs/encoding" "^0.29.3" - "@cosmjs/math" "^0.29.3" - "@cosmjs/utils" "^0.29.3" + "@cosmjs/encoding" "^0.29.4" + "@cosmjs/math" "^0.29.4" + "@cosmjs/utils" "^0.29.4" "@noble/hashes" "^1" bn.js "^5.2.0" - elliptic "^6.5.3" + elliptic "^6.5.4" libsodium-wrappers "^0.7.6" -"@cosmjs/encoding@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.29.3.tgz#191fe1192d78ac0f9eb01b6e0aa4ba976cfb2c7a" - integrity sha512-K6CTcDGovwzF3QOmLm9mWwjcu4Md64zCOBYgVK3boGbsaExP/6YAjT22e+yDsReXWlEUtSVCjqCC/9EEcwmYmg== +"@cosmjs/encoding@^0.29.4": + version "0.29.4" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.29.4.tgz#0ae1b78c064dacda7c35eeb7c35ed7b55951d94f" + integrity sha512-nlwCh4j+kIqEcwNu8AFSmqXGj0bvF4nLC3J1X0eJyJenlgJBiiAGjYp3nxMf/ZjKkZP65Fq7MXVtAYs3K8xvvQ== dependencies: base64-js "^1.3.0" bech32 "^1.1.4" readonly-date "^1.0.0" -"@cosmjs/json-rpc@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.29.3.tgz#17d99b71410c24e082d492d307ad25463d0a72d1" - integrity sha512-GP3qSMxVcoTQFI1/tWQDou843ZO0s51LaT+oaSr7F6C4XNCBv9BnSiVteijeZOaIPmhSBMnZs+7QDORlDHpS7A== +"@cosmjs/json-rpc@^0.29.4": + version "0.29.4" + resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.29.4.tgz#9d71f4277181925ca41e5ccaf9b084606899b08e" + integrity sha512-pmb918u7QlLUOX7twJCBC7bK/L87uhknnP2yh9f+n7zmmslBbwENxLdM6KBAb1Yc0tbzSaLLLRoaN8kvfaiwUA== dependencies: - "@cosmjs/stream" "^0.29.3" + "@cosmjs/stream" "^0.29.4" xstream "^11.14.0" -"@cosmjs/math@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.29.3.tgz#29f98f6529f3d7654f1af85b329b48482eabbecb" - integrity sha512-4HGHqS+Yn81dZLOAYcDSQbROBD1a7ETW3ur5hziCTXMjZFILRJ3w71PlFUVppVb2u3kRDBBXuYHvZ6/V0M0nrg== +"@cosmjs/math@^0.29.4": + version "0.29.4" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.29.4.tgz#9e9079826090718de75ff239a63314b0baf63999" + integrity sha512-IvT1Cj3qOMGqz7v5FxdDCBEIDL2k9m5rufrkuD4oL9kS79ebnhA0lquX6ApPubUohTXl+5PnLo02W8HEH6Stkg== dependencies: bn.js "^5.2.0" -"@cosmjs/proto-signing@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.3.tgz#fa5ed609ed2a0007d8d5eacbeb1f5a89ba1b77ff" - integrity sha512-Ai3l9THjMOrLJ4Ebn1Dgptwg6W5ZIRJqtnJjijHhGwTVC1WT0WdYU3aMZ7+PwubcA/cA1rH4ZTK7jrfYbra63g== +"@cosmjs/proto-signing@^0.29.4": + version "0.29.4" + resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.4.tgz#8f314a936f07d15f5414280bec8aabb6de4078db" + integrity sha512-GdLOhMd54LZgG+kHf7uAWGYDT628yVhXPMWaG/1i3f3Kq4VsZgFBwJhhziM5kWblmFjBOhooGRwLrBnOxMusCg== dependencies: - "@cosmjs/amino" "^0.29.3" - "@cosmjs/crypto" "^0.29.3" - "@cosmjs/encoding" "^0.29.3" - "@cosmjs/math" "^0.29.3" - "@cosmjs/utils" "^0.29.3" + "@cosmjs/amino" "^0.29.4" + "@cosmjs/crypto" "^0.29.4" + "@cosmjs/encoding" "^0.29.4" + "@cosmjs/math" "^0.29.4" + "@cosmjs/utils" "^0.29.4" cosmjs-types "^0.5.2" long "^4.0.0" -"@cosmjs/socket@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.29.3.tgz#0c3fcf16066946c43a7666516ee0edc096ff977c" - integrity sha512-yP35avUsBId/HUBVPRg8z1KmW2iTjMNzflBcFVuTbVoDZrK9DHIlAsB8lV+XKIKPqqECvEq2Dtb1Z+XDy1WBEA== +"@cosmjs/socket@^0.29.4": + version "0.29.4" + resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.29.4.tgz#e025ea5d455647529bc7cc9794390e4af3a282fe" + integrity sha512-+J79SRVD6VSnGqKmUwLFAxXTiLYw/AmAu+075RenQxzkCxehGrGpaty+T3jJGE2p1458AjgkrmTQnfp6f+HIWw== dependencies: - "@cosmjs/stream" "^0.29.3" + "@cosmjs/stream" "^0.29.4" isomorphic-ws "^4.0.1" ws "^7" xstream "^11.14.0" -"@cosmjs/stargate@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.29.3.tgz#9bd303bfd32a7399a233e662864e7cc32e2607af" - integrity sha512-455TgXStCi6E8KDjnhDAM8wt6aLSjobH4Dixvd7Up1DfCH6UB9NkC/G0fMJANNcNXMaM4wSX14niTXwD1d31BA== +"@cosmjs/stargate@^0.29.4": + version "0.29.4" + resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.29.4.tgz#d66494ab8de8a886aedd46706a7560482cf18ad5" + integrity sha512-MEzBkOhYX0tdGgJg4mxFDjf7NMJYKNtXHX0Fu4HVACdBrxLlStf630KodmzPFlLOHfiz3CB/mqj3TobZZz8mTw== dependencies: "@confio/ics23" "^0.6.8" - "@cosmjs/amino" "^0.29.3" - "@cosmjs/encoding" "^0.29.3" - "@cosmjs/math" "^0.29.3" - "@cosmjs/proto-signing" "^0.29.3" - "@cosmjs/stream" "^0.29.3" - "@cosmjs/tendermint-rpc" "^0.29.3" - "@cosmjs/utils" "^0.29.3" + "@cosmjs/amino" "^0.29.4" + "@cosmjs/encoding" "^0.29.4" + "@cosmjs/math" "^0.29.4" + "@cosmjs/proto-signing" "^0.29.4" + "@cosmjs/stream" "^0.29.4" + "@cosmjs/tendermint-rpc" "^0.29.4" + "@cosmjs/utils" "^0.29.4" cosmjs-types "^0.5.2" long "^4.0.0" protobufjs "~6.11.3" xstream "^11.14.0" -"@cosmjs/stream@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.29.3.tgz#9d9a9ec952cbc96f2e524204c4833980e314e6cd" - integrity sha512-0fbKvslZjNyuVe43cB9NDSqlBUXOHG84wGry4HmYfwayRtHr1CDWH5nR3v04eG0/prmZht8J3TgPsfWozIP+cw== +"@cosmjs/stream@^0.29.4": + version "0.29.4" + resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.29.4.tgz#6f882c0200ba3f5b4e8004eaad9f3c41471cd288" + integrity sha512-bCcPIxxyrvtIusmZEOVZT5YS3t+dfB6wfLO6tadumYyPcCBJkXLRK6LSYcsiPjnTNr2UAlCSZX24djM2mFvlWQ== dependencies: xstream "^11.14.0" -"@cosmjs/tendermint-rpc@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.3.tgz#054f80e5095cdf328d98fa7bcf23cd785435d247" - integrity sha512-4l3VacUMQdyGGqfzbZ02kEwlVdMVOdAeWJt2euoVdfUR/HT+TTzQrrL+ORj9PEooLLtwtMl9dqms8uEiblYBDg== - dependencies: - "@cosmjs/crypto" "^0.29.3" - "@cosmjs/encoding" "^0.29.3" - "@cosmjs/json-rpc" "^0.29.3" - "@cosmjs/math" "^0.29.3" - "@cosmjs/socket" "^0.29.3" - "@cosmjs/stream" "^0.29.3" - "@cosmjs/utils" "^0.29.3" +"@cosmjs/tendermint-rpc@^0.29.4": + version "0.29.4" + resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.4.tgz#1c763f726f26618538b99add5c707392aeac85e0" + integrity sha512-ByW5tFK8epc7fx82DogUaLkWSyr9scAY9UmOC9Zn4uFfISOwRdN5c0DvYZ1pI49elMT3/MroIdORotdtYirm1g== + dependencies: + "@cosmjs/crypto" "^0.29.4" + "@cosmjs/encoding" "^0.29.4" + "@cosmjs/json-rpc" "^0.29.4" + "@cosmjs/math" "^0.29.4" + "@cosmjs/socket" "^0.29.4" + "@cosmjs/stream" "^0.29.4" + "@cosmjs/utils" "^0.29.4" axios "^0.21.2" readonly-date "^1.0.0" xstream "^11.14.0" -"@cosmjs/utils@^0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.3.tgz#d7e1f381267e61b7d3219ebd75d46defc397cd43" - integrity sha512-UuKoBN2xiRXcBpz7jzCwagKhOnLOsRmR8mu3IzY+Yx38i8rW52FSXMbxC/yE83X0vLea+zgMQFPwv0gy4QWUJw== +"@cosmjs/utils@^0.29.4": + version "0.29.4" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.4.tgz#8a80da006fe2b544a3c36f557e4b782810e532fd" + integrity sha512-X1pZWRHDbTPLa6cYW0NHvtig+lSxOdLAX7K/xp67ywBy2knnDOyzz1utGTOowmiM98XuV9quK/BWePKkJOaHpQ== -"@cosmwasm/ts-codegen@^0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.20.0.tgz#786afd4c2c172040547134c1c3076fb47e823721" - integrity sha512-lrp61YkjONbuStNWQEKdD/MYQwREnQ2fNnI59dSa7VOqBxyhlBoOhj/6xjYFv4P3cm2Pd374kzfayhTkuFwhLw== +"@cosmwasm/ts-codegen@^0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.23.0.tgz#42f4de325f9e20a2248c6a6cd74a862bd0fac22d" + integrity sha512-vQRhvmS7YoR6EFommHlL/espo8f9boc9eqDbX6ifPNENID4uu23eTPq5KGtRyDfYNe+Zld5zE7RP2hnxmx55JQ== dependencies: "@babel/core" "7.18.10" "@babel/generator" "7.18.12" @@ -1356,7 +1356,7 @@ parse-package-name "1.0.0" rimraf "3.0.2" shelljs "0.8.5" - wasm-ast-types "^0.14.0" + wasm-ast-types "^0.16.0" "@eslint/eslintrc@^1.3.3": version "1.3.3" @@ -1408,28 +1408,28 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.2.1": - version "29.2.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.2.1.tgz#5f2c62dcdd5ce66e94b6d6729e021758bceea090" - integrity sha512-MF8Adcw+WPLZGBiNxn76DOuczG3BhODTcMlDCA4+cFi41OkaY/lyI0XUUhi73F88Y+7IHoGmD80pN5CtxQUdSw== +"@jest/console@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.3.1.tgz#3e3f876e4e47616ea3b1464b9fbda981872e9583" + integrity sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.2.1" - jest-util "^29.2.1" + jest-message-util "^29.3.1" + jest-util "^29.3.1" slash "^3.0.0" -"@jest/core@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.2.2.tgz#207aa8973d9de8769f9518732bc5f781efc3ffa7" - integrity sha512-susVl8o2KYLcZhhkvSB+b7xX575CX3TmSvxfeDjpRko7KmT89rHkXj6XkDkNpSeFMBzIENw5qIchO9HC9Sem+A== +"@jest/core@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.3.1.tgz#bff00f413ff0128f4debec1099ba7dcd649774a1" + integrity sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw== dependencies: - "@jest/console" "^29.2.1" - "@jest/reporters" "^29.2.2" - "@jest/test-result" "^29.2.1" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/console" "^29.3.1" + "@jest/reporters" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" @@ -1437,32 +1437,32 @@ exit "^0.1.2" graceful-fs "^4.2.9" jest-changed-files "^29.2.0" - jest-config "^29.2.2" - jest-haste-map "^29.2.1" - jest-message-util "^29.2.1" + jest-config "^29.3.1" + jest-haste-map "^29.3.1" + jest-message-util "^29.3.1" jest-regex-util "^29.2.0" - jest-resolve "^29.2.2" - jest-resolve-dependencies "^29.2.2" - jest-runner "^29.2.2" - jest-runtime "^29.2.2" - jest-snapshot "^29.2.2" - jest-util "^29.2.1" - jest-validate "^29.2.2" - jest-watcher "^29.2.2" + jest-resolve "^29.3.1" + jest-resolve-dependencies "^29.3.1" + jest-runner "^29.3.1" + jest-runtime "^29.3.1" + jest-snapshot "^29.3.1" + jest-util "^29.3.1" + jest-validate "^29.3.1" + jest-watcher "^29.3.1" micromatch "^4.0.4" - pretty-format "^29.2.1" + pretty-format "^29.3.1" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.2.2.tgz#481e729048d42e87d04842c38aa4d09c507f53b0" - integrity sha512-OWn+Vhu0I1yxuGBJEFFekMYc8aGBGrY4rt47SOh/IFaI+D7ZHCk7pKRiSoZ2/Ml7b0Ony3ydmEHRx/tEOC7H1A== +"@jest/environment@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.3.1.tgz#eb039f726d5fcd14698acd072ac6576d41cfcaa6" + integrity sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag== dependencies: - "@jest/fake-timers" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/fake-timers" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" - jest-mock "^29.2.2" + jest-mock "^29.3.1" "@jest/expect-utils@^29.2.2": version "29.2.2" @@ -1471,46 +1471,53 @@ dependencies: jest-get-type "^29.2.0" -"@jest/expect@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.2.2.tgz#81edbd33afbde7795ca07ff6b4753d15205032e4" - integrity sha512-zwblIZnrIVt8z/SiEeJ7Q9wKKuB+/GS4yZe9zw7gMqfGf4C5hBLGrVyxu1SzDbVSqyMSlprKl3WL1r80cBNkgg== +"@jest/expect-utils@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.3.1.tgz#531f737039e9b9e27c42449798acb5bba01935b6" + integrity sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g== + dependencies: + jest-get-type "^29.2.0" + +"@jest/expect@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.3.1.tgz#456385b62894349c1d196f2d183e3716d4c6a6cd" + integrity sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg== dependencies: - expect "^29.2.2" - jest-snapshot "^29.2.2" + expect "^29.3.1" + jest-snapshot "^29.3.1" -"@jest/fake-timers@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.2.2.tgz#d8332e6e3cfa99cde4bc87d04a17d6b699deb340" - integrity sha512-nqaW3y2aSyZDl7zQ7t1XogsxeavNpH6kkdq+EpXncIDvAkjvFD7hmhcIs1nWloengEWUoWqkqSA6MSbf9w6DgA== +"@jest/fake-timers@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.3.1.tgz#b140625095b60a44de820876d4c14da1aa963f67" + integrity sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" "@sinonjs/fake-timers" "^9.1.2" "@types/node" "*" - jest-message-util "^29.2.1" - jest-mock "^29.2.2" - jest-util "^29.2.1" + jest-message-util "^29.3.1" + jest-mock "^29.3.1" + jest-util "^29.3.1" -"@jest/globals@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.2.2.tgz#205ff1e795aa774301c2c0ba0be182558471b845" - integrity sha512-/nt+5YMh65kYcfBhj38B3Hm0Trk4IsuMXNDGKE/swp36yydBWfz3OXkLqkSvoAtPW8IJMSJDFCbTM2oj5SNprw== +"@jest/globals@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.3.1.tgz#92be078228e82d629df40c3656d45328f134a0c6" + integrity sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q== dependencies: - "@jest/environment" "^29.2.2" - "@jest/expect" "^29.2.2" - "@jest/types" "^29.2.1" - jest-mock "^29.2.2" + "@jest/environment" "^29.3.1" + "@jest/expect" "^29.3.1" + "@jest/types" "^29.3.1" + jest-mock "^29.3.1" -"@jest/reporters@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.2.2.tgz#69b395f79c3a97ce969ce05ccf1a482e5d6de290" - integrity sha512-AzjL2rl2zJC0njIzcooBvjA4sJjvdoq98sDuuNs4aNugtLPSQ+91nysGKRF0uY1to5k0MdGMdOBggUsPqvBcpA== +"@jest/reporters@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.3.1.tgz#9a6d78c109608e677c25ddb34f907b90e07b4310" + integrity sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.2.1" - "@jest/test-result" "^29.2.1" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/console" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@jridgewell/trace-mapping" "^0.3.15" "@types/node" "*" chalk "^4.0.0" @@ -1523,9 +1530,9 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.2.1" - jest-util "^29.2.1" - jest-worker "^29.2.1" + jest-message-util "^29.3.1" + jest-util "^29.3.1" + jest-worker "^29.3.1" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" @@ -1554,24 +1561,24 @@ callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.2.1": - version "29.2.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.2.1.tgz#f42dbf7b9ae465d0a93eee6131473b8bb3bd2edb" - integrity sha512-lS4+H+VkhbX6z64tZP7PAUwPqhwj3kbuEHcaLuaBuB+riyaX7oa1txe0tXgrFj5hRWvZKvqO7LZDlNWeJ7VTPA== +"@jest/test-result@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.3.1.tgz#92cd5099aa94be947560a24610aa76606de78f50" + integrity sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw== dependencies: - "@jest/console" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/console" "^29.3.1" + "@jest/types" "^29.3.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.2.2.tgz#4ac7487b237e517a1f55e7866fb5553f6e0168b9" - integrity sha512-Cuc1znc1pl4v9REgmmLf0jBd3Y65UXJpioGYtMr/JNpQEIGEzkmHhy6W6DLbSsXeUA13TDzymPv0ZGZ9jH3eIw== +"@jest/test-sequencer@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz#fa24b3b050f7a59d48f7ef9e0b782ab65123090d" + integrity sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA== dependencies: - "@jest/test-result" "^29.2.1" + "@jest/test-result" "^29.3.1" graceful-fs "^4.2.9" - jest-haste-map "^29.2.1" + jest-haste-map "^29.3.1" slash "^3.0.0" "@jest/transform@28.1.3": @@ -1595,22 +1602,22 @@ slash "^3.0.0" write-file-atomic "^4.0.1" -"@jest/transform@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.2.2.tgz#dfc03fc092b31ffea0c55917728e75bfcf8b5de6" - integrity sha512-aPe6rrletyuEIt2axxgdtxljmzH8O/nrov4byy6pDw9S8inIrTV+2PnjyP/oFHMSynzGxJ2s6OHowBNMXp/Jzg== +"@jest/transform@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.3.1.tgz#1e6bd3da4af50b5c82a539b7b1f3770568d6e36d" + integrity sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" "@jridgewell/trace-mapping" "^0.3.15" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" - convert-source-map "^1.4.0" + convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.2.1" + jest-haste-map "^29.3.1" jest-regex-util "^29.2.0" - jest-util "^29.2.1" + jest-util "^29.3.1" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" @@ -1640,6 +1647,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.3.1.tgz#7c5a80777cb13e703aeec6788d044150341147e3" + integrity sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA== + dependencies: + "@jest/schemas" "^29.0.0" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -1879,10 +1898,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.2.tgz#874e7dc6702fa6a3fe6107792aa98636dcc480b4" - integrity sha512-og1wAmdxKoS71K2ZwSVqWPX6OVn3ihZ6ZT2qvZvZQm90lJVDyXIjYcu4Khx2CNIeaFv12rOU/YObOsI3VOkzog== +"@types/jest@^29.2.3": + version "29.2.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.3.tgz#f5fd88e43e5a9e4221ca361e23790d48fcf0a211" + integrity sha512-6XwoEbmatfyoCjWRX7z0fKMmgYKe9+/HrviJ5k0X/tjJWHGAezZOfYaxqQKuzG/TvQyr+ktjm4jgbk0s4/oF2w== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -1939,14 +1958,14 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.40.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.42.0.tgz#36a8c0c379870127059889a9cc7e05c260d2aaa5" - integrity sha512-5TJh2AgL6+wpL8H/GTSjNb4WrjKoR2rqvFxR/DDTqYNk6uXn8BJMEcncLSpMbf/XV1aS0jAjYwn98uvVCiAywQ== +"@typescript-eslint/eslint-plugin@^5.45.0": + version "5.45.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.0.tgz#ffa505cf961d4844d38cfa19dcec4973a6039e41" + integrity sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA== dependencies: - "@typescript-eslint/scope-manager" "5.42.0" - "@typescript-eslint/type-utils" "5.42.0" - "@typescript-eslint/utils" "5.42.0" + "@typescript-eslint/scope-manager" "5.45.0" + "@typescript-eslint/type-utils" "5.45.0" + "@typescript-eslint/utils" "5.45.0" debug "^4.3.4" ignore "^5.2.0" natural-compare-lite "^1.4.0" @@ -1954,72 +1973,72 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.40.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.42.0.tgz#be0ffbe279e1320e3d15e2ef0ad19262f59e9240" - integrity sha512-Ixh9qrOTDRctFg3yIwrLkgf33AHyEIn6lhyf5cCfwwiGtkWhNpVKlEZApi3inGQR/barWnY7qY8FbGKBO7p3JA== +"@typescript-eslint/parser@^5.45.0": + version "5.45.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.45.0.tgz#b18a5f6b3cf1c2b3e399e9d2df4be40d6b0ddd0e" + integrity sha512-brvs/WSM4fKUmF5Ot/gEve6qYiCMjm6w4HkHPfS6ZNmxTS0m0iNN4yOChImaCkqc1hRwFGqUyanMXuGal6oyyQ== dependencies: - "@typescript-eslint/scope-manager" "5.42.0" - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/typescript-estree" "5.42.0" + "@typescript-eslint/scope-manager" "5.45.0" + "@typescript-eslint/types" "5.45.0" + "@typescript-eslint/typescript-estree" "5.45.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.42.0.tgz#e1f2bb26d3b2a508421ee2e3ceea5396b192f5ef" - integrity sha512-l5/3IBHLH0Bv04y+H+zlcLiEMEMjWGaCX6WyHE5Uk2YkSGAMlgdUPsT/ywTSKgu9D1dmmKMYgYZijObfA39Wow== +"@typescript-eslint/scope-manager@5.45.0": + version "5.45.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.45.0.tgz#7a4ac1bfa9544bff3f620ab85947945938319a96" + integrity sha512-noDMjr87Arp/PuVrtvN3dXiJstQR1+XlQ4R1EvzG+NMgXi8CuMCXpb8JqNtFHKceVSQ985BZhfRdowJzbv4yKw== dependencies: - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/visitor-keys" "5.42.0" + "@typescript-eslint/types" "5.45.0" + "@typescript-eslint/visitor-keys" "5.45.0" -"@typescript-eslint/type-utils@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.42.0.tgz#4206d7192d4fe903ddf99d09b41d4ac31b0b7dca" - integrity sha512-HW14TXC45dFVZxnVW8rnUGnvYyRC0E/vxXShFCthcC9VhVTmjqOmtqj6H5rm9Zxv+ORxKA/1aLGD7vmlLsdlOg== +"@typescript-eslint/type-utils@5.45.0": + version "5.45.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.45.0.tgz#aefbc954c40878fcebeabfb77d20d84a3da3a8b2" + integrity sha512-DY7BXVFSIGRGFZ574hTEyLPRiQIvI/9oGcN8t1A7f6zIs6ftbrU0nhyV26ZW//6f85avkwrLag424n+fkuoJ1Q== dependencies: - "@typescript-eslint/typescript-estree" "5.42.0" - "@typescript-eslint/utils" "5.42.0" + "@typescript-eslint/typescript-estree" "5.45.0" + "@typescript-eslint/utils" "5.45.0" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.42.0.tgz#5aeff9b5eced48f27d5b8139339bf1ef805bad7a" - integrity sha512-t4lzO9ZOAUcHY6bXQYRuu+3SSYdD9TS8ooApZft4WARt4/f2Cj/YpvbTe8A4GuhT4bNW72goDMOy7SW71mZwGw== +"@typescript-eslint/types@5.45.0": + version "5.45.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.45.0.tgz#794760b9037ee4154c09549ef5a96599621109c5" + integrity sha512-QQij+u/vgskA66azc9dCmx+rev79PzX8uDHpsqSjEFtfF2gBUTRCpvYMh2gw2ghkJabNkPlSUCimsyBEQZd1DA== -"@typescript-eslint/typescript-estree@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.0.tgz#2592d24bb5f89bf54a63384ff3494870f95b3fd8" - integrity sha512-2O3vSq794x3kZGtV7i4SCWZWCwjEtkWfVqX4m5fbUBomOsEOyd6OAD1qU2lbvV5S8tgy/luJnOYluNyYVeOTTg== +"@typescript-eslint/typescript-estree@5.45.0": + version "5.45.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.45.0.tgz#f70a0d646d7f38c0dfd6936a5e171a77f1e5291d" + integrity sha512-maRhLGSzqUpFcZgXxg1qc/+H0bT36lHK4APhp0AEUVrpSwXiRAomm/JGjSG+kNUio5kAa3uekCYu/47cnGn5EQ== dependencies: - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/visitor-keys" "5.42.0" + "@typescript-eslint/types" "5.45.0" + "@typescript-eslint/visitor-keys" "5.45.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.42.0.tgz#f06bd43b9a9a06ed8f29600273240e84a53f2f15" - integrity sha512-JZ++3+h1vbeG1NUECXQZE3hg0kias9kOtcQr3+JVQ3whnjvKuMyktJAAIj6743OeNPnGBmjj7KEmiDL7qsdnCQ== +"@typescript-eslint/utils@5.45.0": + version "5.45.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.45.0.tgz#9cca2996eee1b8615485a6918a5c763629c7acf5" + integrity sha512-OUg2JvsVI1oIee/SwiejTot2OxwU8a7UfTFMOdlhD2y+Hl6memUSL4s98bpUTo8EpVEr0lmwlU7JSu/p2QpSvA== dependencies: "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.42.0" - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/typescript-estree" "5.42.0" + "@typescript-eslint/scope-manager" "5.45.0" + "@typescript-eslint/types" "5.45.0" + "@typescript-eslint/typescript-estree" "5.45.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.0.tgz#ee8d62d486f41cfe646632fab790fbf0c1db5bb0" - integrity sha512-QHbu5Hf/2lOEOwy+IUw0GoSCuAzByTAWWrOTKzTzsotiUnWFpuKnXcAhC9YztAf2EElQ0VvIK+pHJUPkM0q7jg== +"@typescript-eslint/visitor-keys@5.45.0": + version "5.45.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.0.tgz#e0d160e9e7fdb7f8da697a5b78e7a14a22a70528" + integrity sha512-jc6Eccbn2RtQPr1s7th6jJWQHBHI6GBVQkCHoJFQ5UreaKm59Vxw+ynQUPPY2u2Amquc+7tmEoC2G52ApsGNNg== dependencies: - "@typescript-eslint/types" "5.42.0" + "@typescript-eslint/types" "5.45.0" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -2147,12 +2166,12 @@ axios@^0.21.2: dependencies: follow-redirects "^1.14.0" -babel-jest@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.2.2.tgz#2c15abd8c2081293c9c3f4f80a4ed1d51542fee5" - integrity sha512-kkq2QSDIuvpgfoac3WZ1OOcHsQQDU5xYk2Ql7tLdJ8BVAYbefEXal+NfS45Y5LVZA7cxC8KYcQMObpCt1J025w== +babel-jest@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.3.1.tgz#05c83e0d128cd48c453eea851482a38782249f44" + integrity sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA== dependencies: - "@jest/transform" "^29.2.2" + "@jest/transform" "^29.3.1" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" babel-preset-jest "^29.2.0" @@ -2477,6 +2496,11 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.25.1: version "3.26.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.26.0.tgz#94e2cf8ba3e63800c4956ea298a6473bc9d62b44" @@ -2554,6 +2578,11 @@ diff-sequences@^29.2.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.2.0.tgz#4c55b5b40706c7b5d2c5c75999a50c56d214e8f6" integrity sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw== +diff-sequences@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.3.1.tgz#104b5b95fe725932421a9c6e5b4bef84c3f2249e" + integrity sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -2578,7 +2607,7 @@ electron-to-chromium@^1.4.251: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== -elliptic@^6.5.3: +elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -2702,10 +2731,10 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.27.0: - version "8.27.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.27.0.tgz#d547e2f7239994ad1faa4bb5d84e5d809db7cf64" - integrity sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ== +eslint@^8.28.0: + version "8.28.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.28.0.tgz#81a680732634677cc890134bcdd9fdfea8e63d6e" + integrity sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ== dependencies: "@eslint/eslintrc" "^1.3.3" "@humanwhocodes/config-array" "^0.11.6" @@ -2818,7 +2847,7 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0, expect@^29.2.2: +expect@^29.0.0: version "29.2.2" resolved "https://registry.yarnpkg.com/expect/-/expect-29.2.2.tgz#ba2dd0d7e818727710324a6e7f13dd0e6d086106" integrity sha512-hE09QerxZ5wXiOhqkXy5d2G9ar+EqOyifnCXCpMNu+vZ6DG9TJ6CO2c2kPDSLqERTTWrO7OZj8EkYHQqSd78Yw== @@ -2829,6 +2858,17 @@ expect@^29.0.0, expect@^29.2.2: jest-message-util "^29.2.1" jest-util "^29.2.1" +expect@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.3.1.tgz#92877aad3f7deefc2e3f6430dd195b92295554a6" + integrity sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA== + dependencies: + "@jest/expect-utils" "^29.3.1" + jest-get-type "^29.2.0" + jest-matcher-utils "^29.3.1" + jest-message-util "^29.3.1" + jest-util "^29.3.1" + ext@^1.1.2: version "1.7.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" @@ -3387,74 +3427,74 @@ jest-changed-files@^29.2.0: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.2.2.tgz#1dc4d35fd49bf5e64d3cc505fb2db396237a6dfa" - integrity sha512-upSdWxx+Mh4DV7oueuZndJ1NVdgtTsqM4YgywHEx05UMH5nxxA2Qu9T9T9XVuR021XxqSoaKvSmmpAbjwwwxMw== +jest-circus@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.3.1.tgz#177d07c5c0beae8ef2937a67de68f1e17bbf1b4a" + integrity sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg== dependencies: - "@jest/environment" "^29.2.2" - "@jest/expect" "^29.2.2" - "@jest/test-result" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/environment" "^29.3.1" + "@jest/expect" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^29.2.1" - jest-matcher-utils "^29.2.2" - jest-message-util "^29.2.1" - jest-runtime "^29.2.2" - jest-snapshot "^29.2.2" - jest-util "^29.2.1" + jest-each "^29.3.1" + jest-matcher-utils "^29.3.1" + jest-message-util "^29.3.1" + jest-runtime "^29.3.1" + jest-snapshot "^29.3.1" + jest-util "^29.3.1" p-limit "^3.1.0" - pretty-format "^29.2.1" + pretty-format "^29.3.1" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.2.2.tgz#feaf0aa57d327e80d4f2f18d5f8cd2e77cac5371" - integrity sha512-R45ygnnb2CQOfd8rTPFR+/fls0d+1zXS6JPYTBBrnLPrhr58SSuPTiA5Tplv8/PXpz4zXR/AYNxmwIj6J6nrvg== +jest-cli@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.3.1.tgz#e89dff427db3b1df50cea9a393ebd8640790416d" + integrity sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ== dependencies: - "@jest/core" "^29.2.2" - "@jest/test-result" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/core" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/types" "^29.3.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.2.2" - jest-util "^29.2.1" - jest-validate "^29.2.2" + jest-config "^29.3.1" + jest-util "^29.3.1" + jest-validate "^29.3.1" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.2.2.tgz#bf98623a46454d644630c1f0de8bba3f495c2d59" - integrity sha512-Q0JX54a5g1lP63keRfKR8EuC7n7wwny2HoTRDb8cx78IwQOiaYUVZAdjViY3WcTxpR02rPUpvNVmZ1fkIlZPcw== +jest-config@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.3.1.tgz#0bc3dcb0959ff8662957f1259947aedaefb7f3c6" + integrity sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.2.2" - "@jest/types" "^29.2.1" - babel-jest "^29.2.2" + "@jest/test-sequencer" "^29.3.1" + "@jest/types" "^29.3.1" + babel-jest "^29.3.1" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.2.2" - jest-environment-node "^29.2.2" + jest-circus "^29.3.1" + jest-environment-node "^29.3.1" jest-get-type "^29.2.0" jest-regex-util "^29.2.0" - jest-resolve "^29.2.2" - jest-runner "^29.2.2" - jest-util "^29.2.1" - jest-validate "^29.2.2" + jest-resolve "^29.3.1" + jest-runner "^29.3.1" + jest-util "^29.3.1" + jest-validate "^29.3.1" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.2.1" + pretty-format "^29.3.1" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -3468,6 +3508,16 @@ jest-diff@^29.2.1: jest-get-type "^29.2.0" pretty-format "^29.2.1" +jest-diff@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.3.1.tgz#d8215b72fed8f1e647aed2cae6c752a89e757527" + integrity sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.3.1" + jest-get-type "^29.2.0" + pretty-format "^29.3.1" + jest-docblock@^29.2.0: version "29.2.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.2.0.tgz#307203e20b637d97cee04809efc1d43afc641e82" @@ -3475,28 +3525,28 @@ jest-docblock@^29.2.0: dependencies: detect-newline "^3.0.0" -jest-each@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.2.1.tgz#6b0a88ee85c2ba27b571a6010c2e0c674f5c9b29" - integrity sha512-sGP86H/CpWHMyK3qGIGFCgP6mt+o5tu9qG4+tobl0LNdgny0aitLXs9/EBacLy3Bwqy+v4uXClqJgASJWcruYw== +jest-each@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.3.1.tgz#bc375c8734f1bb96625d83d1ca03ef508379e132" + integrity sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" chalk "^4.0.0" jest-get-type "^29.2.0" - jest-util "^29.2.1" - pretty-format "^29.2.1" + jest-util "^29.3.1" + pretty-format "^29.3.1" -jest-environment-node@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.2.2.tgz#a64b272773870c3a947cd338c25fd34938390bc2" - integrity sha512-B7qDxQjkIakQf+YyrqV5dICNs7tlCO55WJ4OMSXsqz1lpI/0PmeuXdx2F7eU8rnPbRkUR/fItSSUh0jvE2y/tw== +jest-environment-node@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.3.1.tgz#5023b32472b3fba91db5c799a0d5624ad4803e74" + integrity sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag== dependencies: - "@jest/environment" "^29.2.2" - "@jest/fake-timers" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/environment" "^29.3.1" + "@jest/fake-timers" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" - jest-mock "^29.2.2" - jest-util "^29.2.1" + jest-mock "^29.3.1" + jest-util "^29.3.1" jest-get-type@^29.2.0: version "29.2.0" @@ -3522,32 +3572,32 @@ jest-haste-map@^28.1.3: optionalDependencies: fsevents "^2.3.2" -jest-haste-map@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.2.1.tgz#f803fec57f8075e6c55fb5cd551f99a72471c699" - integrity sha512-wF460rAFmYc6ARcCFNw4MbGYQjYkvjovb9GBT+W10Um8q5nHq98jD6fHZMDMO3tA56S8XnmNkM8GcA8diSZfnA== +jest-haste-map@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.3.1.tgz#af83b4347f1dae5ee8c2fb57368dc0bb3e5af843" + integrity sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" jest-regex-util "^29.2.0" - jest-util "^29.2.1" - jest-worker "^29.2.1" + jest-util "^29.3.1" + jest-worker "^29.3.1" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.2.1.tgz#ec551686b7d512ec875616c2c3534298b1ffe2fc" - integrity sha512-1YvSqYoiurxKOJtySc+CGVmw/e1v4yNY27BjWTVzp0aTduQeA7pdieLiW05wTYG/twlKOp2xS/pWuikQEmklug== +jest-leak-detector@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz#95336d020170671db0ee166b75cd8ef647265518" + integrity sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA== dependencies: jest-get-type "^29.2.0" - pretty-format "^29.2.1" + pretty-format "^29.3.1" jest-matcher-utils@^29.2.2: version "29.2.2" @@ -3559,6 +3609,16 @@ jest-matcher-utils@^29.2.2: jest-get-type "^29.2.0" pretty-format "^29.2.1" +jest-matcher-utils@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz#6e7f53512f80e817dfa148672bd2d5d04914a572" + integrity sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ== + dependencies: + chalk "^4.0.0" + jest-diff "^29.3.1" + jest-get-type "^29.2.0" + pretty-format "^29.3.1" + jest-message-util@^29.2.1: version "29.2.1" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.2.1.tgz#3a51357fbbe0cc34236f17a90d772746cf8d9193" @@ -3574,14 +3634,29 @@ jest-message-util@^29.2.1: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.2.2.tgz#9045618b3f9d27074bbcf2d55bdca6a5e2e8bca7" - integrity sha512-1leySQxNAnivvbcx0sCB37itu8f4OX2S/+gxLAV4Z62shT4r4dTG9tACDywUAEZoLSr36aYUTsVp3WKwWt4PMQ== +jest-message-util@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.3.1.tgz#37bc5c468dfe5120712053dd03faf0f053bd6adb" + integrity sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA== dependencies: - "@jest/types" "^29.2.1" + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.3.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.3.1" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.3.1.tgz#60287d92e5010979d01f218c6b215b688e0f313e" + integrity sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA== + dependencies: + "@jest/types" "^29.3.1" "@types/node" "*" - jest-util "^29.2.1" + jest-util "^29.3.1" jest-pnp-resolver@^1.2.2: version "1.2.2" @@ -3598,88 +3673,88 @@ jest-regex-util@^29.2.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.2.0.tgz#82ef3b587e8c303357728d0322d48bbfd2971f7b" integrity sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA== -jest-resolve-dependencies@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.2.2.tgz#1f444766f37a25f1490b5137408b6ff746a05d64" - integrity sha512-wWOmgbkbIC2NmFsq8Lb+3EkHuW5oZfctffTGvwsA4JcJ1IRk8b2tg+hz44f0lngvRTeHvp3Kyix9ACgudHH9aQ== +jest-resolve-dependencies@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz#a6a329708a128e68d67c49f38678a4a4a914c3bf" + integrity sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA== dependencies: jest-regex-util "^29.2.0" - jest-snapshot "^29.2.2" + jest-snapshot "^29.3.1" -jest-resolve@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.2.2.tgz#ad6436053b0638b41e12bbddde2b66e1397b35b5" - integrity sha512-3gaLpiC3kr14rJR3w7vWh0CBX2QAhfpfiQTwrFPvVrcHe5VUBtIXaR004aWE/X9B2CFrITOQAp5gxLONGrk6GA== +jest-resolve@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.3.1.tgz#9a4b6b65387a3141e4a40815535c7f196f1a68a7" + integrity sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.2.1" + jest-haste-map "^29.3.1" jest-pnp-resolver "^1.2.2" - jest-util "^29.2.1" - jest-validate "^29.2.2" + jest-util "^29.3.1" + jest-validate "^29.3.1" resolve "^1.20.0" resolve.exports "^1.1.0" slash "^3.0.0" -jest-runner@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.2.2.tgz#6b5302ed15eba8bf05e6b14d40f1e8d469564da3" - integrity sha512-1CpUxXDrbsfy9Hr9/1zCUUhT813kGGK//58HeIw/t8fa/DmkecEwZSWlb1N/xDKXg3uCFHQp1GCvlSClfImMxg== +jest-runner@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.3.1.tgz#a92a879a47dd096fea46bb1517b0a99418ee9e2d" + integrity sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA== dependencies: - "@jest/console" "^29.2.1" - "@jest/environment" "^29.2.2" - "@jest/test-result" "^29.2.1" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/console" "^29.3.1" + "@jest/environment" "^29.3.1" + "@jest/test-result" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" jest-docblock "^29.2.0" - jest-environment-node "^29.2.2" - jest-haste-map "^29.2.1" - jest-leak-detector "^29.2.1" - jest-message-util "^29.2.1" - jest-resolve "^29.2.2" - jest-runtime "^29.2.2" - jest-util "^29.2.1" - jest-watcher "^29.2.2" - jest-worker "^29.2.1" + jest-environment-node "^29.3.1" + jest-haste-map "^29.3.1" + jest-leak-detector "^29.3.1" + jest-message-util "^29.3.1" + jest-resolve "^29.3.1" + jest-runtime "^29.3.1" + jest-util "^29.3.1" + jest-watcher "^29.3.1" + jest-worker "^29.3.1" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.2.2.tgz#4068ee82423769a481460efd21d45a8efaa5c179" - integrity sha512-TpR1V6zRdLynckKDIQaY41od4o0xWL+KOPUCZvJK2bu5P1UXhjobt5nJ2ICNeIxgyj9NGkO0aWgDqYPVhDNKjA== +jest-runtime@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.3.1.tgz#21efccb1a66911d6d8591276a6182f520b86737a" + integrity sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A== dependencies: - "@jest/environment" "^29.2.2" - "@jest/fake-timers" "^29.2.2" - "@jest/globals" "^29.2.2" + "@jest/environment" "^29.3.1" + "@jest/fake-timers" "^29.3.1" + "@jest/globals" "^29.3.1" "@jest/source-map" "^29.2.0" - "@jest/test-result" "^29.2.1" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/test-result" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.2.1" - jest-message-util "^29.2.1" - jest-mock "^29.2.2" + jest-haste-map "^29.3.1" + jest-message-util "^29.3.1" + jest-mock "^29.3.1" jest-regex-util "^29.2.0" - jest-resolve "^29.2.2" - jest-snapshot "^29.2.2" - jest-util "^29.2.1" + jest-resolve "^29.3.1" + jest-snapshot "^29.3.1" + jest-util "^29.3.1" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.2.2.tgz#1016ce60297b77382386bad561107174604690c2" - integrity sha512-GfKJrpZ5SMqhli3NJ+mOspDqtZfJBryGA8RIBxF+G+WbDoC7HCqKaeAss4Z/Sab6bAW11ffasx8/vGsj83jyjA== +jest-snapshot@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.3.1.tgz#17bcef71a453adc059a18a32ccbd594b8cc4e45e" + integrity sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" @@ -3687,23 +3762,23 @@ jest-snapshot@^29.2.2: "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.2.2" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/expect-utils" "^29.3.1" + "@jest/transform" "^29.3.1" + "@jest/types" "^29.3.1" "@types/babel__traverse" "^7.0.6" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.2.2" + expect "^29.3.1" graceful-fs "^4.2.9" - jest-diff "^29.2.1" + jest-diff "^29.3.1" jest-get-type "^29.2.0" - jest-haste-map "^29.2.1" - jest-matcher-utils "^29.2.2" - jest-message-util "^29.2.1" - jest-util "^29.2.1" + jest-haste-map "^29.3.1" + jest-matcher-utils "^29.3.1" + jest-message-util "^29.3.1" + jest-util "^29.3.1" natural-compare "^1.4.0" - pretty-format "^29.2.1" + pretty-format "^29.3.1" semver "^7.3.5" jest-util@^28.1.3: @@ -3730,30 +3805,42 @@ jest-util@^29.2.1: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.2.2.tgz#e43ce1931292dfc052562a11bc681af3805eadce" - integrity sha512-eJXATaKaSnOuxNfs8CLHgdABFgUrd0TtWS8QckiJ4L/QVDF4KVbZFBBOwCBZHOS0Rc5fOxqngXeGXE3nGQkpQA== +jest-util@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.3.1.tgz#1dda51e378bbcb7e3bc9d8ab651445591ed373e1" + integrity sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.3.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.3.1.tgz#d56fefaa2e7d1fde3ecdc973c7f7f8f25eea704a" + integrity sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g== + dependencies: + "@jest/types" "^29.3.1" camelcase "^6.2.0" chalk "^4.0.0" jest-get-type "^29.2.0" leven "^3.1.0" - pretty-format "^29.2.1" + pretty-format "^29.3.1" -jest-watcher@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.2.2.tgz#7093d4ea8177e0a0da87681a9e7b09a258b9daf7" - integrity sha512-j2otfqh7mOvMgN2WlJ0n7gIx9XCMWntheYGlBK7+5g3b1Su13/UAK7pdKGyd4kDlrLwtH2QPvRv5oNIxWvsJ1w== +jest-watcher@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.3.1.tgz#3341547e14fe3c0f79f9c3a4c62dbc3fc977fd4a" + integrity sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg== dependencies: - "@jest/test-result" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/test-result" "^29.3.1" + "@jest/types" "^29.3.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.2.1" + jest-util "^29.3.1" string-length "^4.0.1" jest-worker@^28.1.3: @@ -3765,25 +3852,25 @@ jest-worker@^28.1.3: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.2.1.tgz#8ba68255438252e1674f990f0180c54dfa26a3b1" - integrity sha512-ROHTZ+oj7sBrgtv46zZ84uWky71AoYi0vEV9CdEtc1FQunsoAGe5HbQmW76nI5QWdvECVPrSi1MCVUmizSavMg== +jest-worker@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.3.1.tgz#e9462161017a9bb176380d721cab022661da3d6b" + integrity sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw== dependencies: "@types/node" "*" - jest-util "^29.2.1" + jest-util "^29.3.1" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.1.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.2.2.tgz#24da83cbbce514718acd698926b7679109630476" - integrity sha512-r+0zCN9kUqoON6IjDdjbrsWobXM/09Nd45kIPRD8kloaRh1z5ZCMdVsgLXGxmlL7UpAJsvCYOQNO+NjvG/gqiQ== +jest@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.3.1.tgz#c130c0d551ae6b5459b8963747fed392ddbde122" + integrity sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA== dependencies: - "@jest/core" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/core" "^29.3.1" + "@jest/types" "^29.3.1" import-local "^3.0.2" - jest-cli "^29.2.2" + jest-cli "^29.3.1" js-sdsl@^4.1.4: version "4.1.5" @@ -4246,11 +4333,16 @@ prepend-file@^2.0.1: dependencies: temp-write "^4.0.0" -prettier@^2.6.2, prettier@^2.7.1: +prettier@^2.6.2: version "2.7.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== +prettier@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.0.tgz#c7df58393c9ba77d6fba3921ae01faf994fb9dc9" + integrity sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA== + pretty-format@^29.0.0, pretty-format@^29.2.1: version "29.2.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.2.1.tgz#86e7748fe8bbc96a6a4e04fa99172630907a9611" @@ -4260,6 +4352,15 @@ pretty-format@^29.0.0, pretty-format@^29.2.1: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.3.1: + version "29.3.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.3.1.tgz#1841cac822b02b4da8971dacb03e8a871b4722da" + integrity sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg== + dependencies: + "@jest/schemas" "^29.0.0" + ansi-styles "^5.0.0" + react-is "^18.0.0" + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -4761,10 +4862,10 @@ type@^2.7.2: resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== -typescript@^4.8.4: - version "4.8.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" - integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== +typescript@^4.9.3: + version "4.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db" + integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" @@ -4832,10 +4933,10 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -wasm-ast-types@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.14.0.tgz#c6e135df2f029280a4be1dbdbf01d4de488a244b" - integrity sha512-1DAiGmLexIA2nizkLHQdyTN3GWb1bdox2/iroyrBmeazTwCdAsPhud/5Pro9JBSmoWjnBGSdlsUYTaY5n25PcA== +wasm-ast-types@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.16.0.tgz#2d2af5a2e78b86b284587513375f27b99dca903d" + integrity sha512-QFh/ubf+s72VyJQm4TPEyv9QXJvr3iTV+vU60qnc3802i+yXAmNBDZPdYVf0sKKr4bz5dtsuuiknXpF+gwlpJg== dependencies: "@babel/runtime" "^7.18.9" "@babel/types" "7.18.10" From 7d88f2115fa29b018759ef993a62fec9c8e2b993 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 1 Dec 2022 01:44:47 +0100 Subject: [PATCH 083/218] Reentrancy mitigation (#50) * Prevent reentrancy * add notice --- contracts/credit-manager/src/execute.rs | 3 +- contracts/credit-manager/src/state.rs | 16 +++--- contracts/credit-manager/src/utils.rs | 35 ++++++++++++- .../tests/test_flagged_contract.rs | 49 +++++++++++++++++++ 4 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 contracts/credit-manager/tests/test_flagged_contract.rs diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 91f8272c7..ca4311aed 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -21,7 +21,7 @@ use crate::state::{ }; use crate::swap::swap_exact_in; use crate::update_coin_balances::update_coin_balance; -use crate::utils::assert_is_token_owner; +use crate::utils::{assert_is_token_owner, assert_not_contract_in_config}; use crate::vault::{ assert_only_one_vault_position, enter_vault, exit_vault, exit_vault_unlocked, liquidate_vault, request_vault_unlock, update_vault_coin_balance, @@ -156,6 +156,7 @@ pub fn dispatch_actions( actions: &[Action], ) -> ContractResult { assert_is_token_owner(&deps, &info.sender, account_id)?; + assert_not_contract_in_config(&deps.as_ref(), &info.sender)?; let mut response = Response::new(); let mut callbacks: Vec = vec![]; diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index ca23e5ac3..ae8ae7b96 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -8,17 +8,21 @@ use mars_rover::adapters::{Oracle, RedBank, Zapper}; use crate::vault::RequestTempStorage; -// Contract config -pub const OWNER: Item = Item::new("owner"); +// Contract dependencies +// NOTE: Ensure assert_not_contract_in_config() is updated when an external contract is added here pub const ACCOUNT_NFT: Item = Item::new("account_nft"); -pub const ALLOWED_COINS: Set<&str> = Set::new("allowed_coins"); -pub const VAULT_CONFIGS: Map<&Addr, VaultConfig> = Map::new("vault_configs"); -pub const RED_BANK: Item = Item::new("red_bank"); pub const ORACLE: Item = Item::new("oracle"); -pub const MAX_CLOSE_FACTOR: Item = Item::new("max_close_factor"); +pub const RED_BANK: Item = Item::new("red_bank"); pub const SWAPPER: Item = Item::new("swapper"); +pub const VAULT_CONFIGS: Map<&Addr, VaultConfig> = Map::new("vault_configs"); pub const ZAPPER: Item = Item::new("zapper"); +// Config +pub const OWNER: Item = Item::new("owner"); +pub const ALLOWED_COINS: Set<&str> = Set::new("allowed_coins"); +pub const MAX_CLOSE_FACTOR: Item = Item::new("max_close_factor"); +pub const MAX_UNLOCKING_POSITIONS: Item = Item::new("max_unlocking_positions"); + // Positions pub const COIN_BALANCES: Map<(&str, &str), Uint128> = Map::new("coin_balance"); // Map<(AccountId, Denom), Amount> pub const DEBT_SHARES: Map<(&str, &str), Uint128> = Map::new("debt_shares"); // Map<(AccountId, Denom), Shares> diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 08bfb227b..ce3afea60 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -1,6 +1,7 @@ use std::collections::HashSet; use std::hash::Hash; +use cosmwasm_std::Order::Ascending; use cosmwasm_std::{ to_binary, Addr, Coin, CosmosMsg, Decimal, Deps, DepsMut, Empty, QuerierWrapper, StdResult, Storage, Uint128, WasmMsg, @@ -15,7 +16,8 @@ use mars_rover::msg::ExecuteMsg; use mars_rover::traits::IntoDecimal; use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, TOTAL_DEBT_SHARES, + ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, + VAULT_CONFIGS, ZAPPER, }; use crate::update_coin_balances::query_balance; @@ -157,6 +159,37 @@ pub fn coin_value(deps: &Deps, coin: &Coin) -> ContractResult { }) } +/// Contracts we call from Rover should not be attempting to execute actions. +/// This assertion prevents a kind of reentrancy attack where a contract we call (that turned evil) +/// can deposit into their own credit account and trick our state updates like update_coin_balances.rs +/// which rely on pre-post querying of bank balances of Rover. +/// NOTE: https://twitter.com/larry0x/status/1595919149381079041 +pub fn assert_not_contract_in_config(deps: &Deps, addr_to_flag: &Addr) -> ContractResult<()> { + let vault_addrs = VAULT_CONFIGS + .keys(deps.storage, None, None, Ascending) + .collect::>>()?; + let config_contracts = vec![ + ACCOUNT_NFT.load(deps.storage)?, + RED_BANK.load(deps.storage)?.address().clone(), + ORACLE.load(deps.storage)?.address().clone(), + SWAPPER.load(deps.storage)?.address().clone(), + ZAPPER.load(deps.storage)?.address().clone(), + ]; + + let flagged_addr_in_config = config_contracts + .into_iter() + .chain(vault_addrs) + .any(|addr| addr == *addr_to_flag); + + if flagged_addr_in_config { + return Err(ContractError::Unauthorized { + user: addr_to_flag.to_string(), + action: "execute actions on rover".to_string(), + }); + } + Ok(()) +} + pub trait IntoUint128 { fn uint128(&self) -> Uint128; } diff --git a/contracts/credit-manager/tests/test_flagged_contract.rs b/contracts/credit-manager/tests/test_flagged_contract.rs new file mode 100644 index 000000000..b08a3bc51 --- /dev/null +++ b/contracts/credit-manager/tests/test_flagged_contract.rs @@ -0,0 +1,49 @@ +use cosmwasm_std::{coin, Addr}; + +use helpers::assert_err; +use mars_rover::error::ContractError::Unauthorized; +use mars_rover::msg::execute::Action; + +use crate::helpers::MockEnv; + +pub mod helpers; + +#[test] +fn test_addresses_in_config_cannot_execute_msgs() { + let mut mock = MockEnv::new().build().unwrap(); + let config = mock.query_config(); + let vault_addrs = mock + .query_vault_configs(None, None) + .iter() + .map(|v| v.vault.address.clone()) + .collect::>(); + + let banned = vec![ + config.account_nft.unwrap(), + config.red_bank, + config.oracle, + config.swapper, + config.zapper, + ] + .into_iter() + .chain(vault_addrs) + .collect::>(); + + for addr_str in banned { + let user = Addr::unchecked(addr_str); + let account_id = mock.create_credit_account(&user).unwrap(); + let res = mock.update_credit_account( + &account_id, + &user, + vec![Action::Deposit(coin(0, "uosmo"))], + &[], + ); + assert_err( + res, + Unauthorized { + user: user.into(), + action: "execute actions on rover".to_string(), + }, + ) + } +} From 4c0eec873006d31065e84283bc58549176c0786a Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 1 Dec 2022 01:45:55 +0100 Subject: [PATCH 084/218] Disallow redbank config update [oak audit] (#56) Disallow redbank config update --- contracts/credit-manager/src/execute.rs | 10 +--------- contracts/credit-manager/tests/test_update_config.rs | 10 +--------- packages/rover/src/msg/instantiate.rs | 1 - 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index ca4311aed..533c22955 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -16,8 +16,7 @@ use crate::liquidate_coin::liquidate_coin; use crate::refund::refund_coin_balances; use crate::repay::repay; use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, ORACLE, OWNER, RED_BANK, SWAPPER, VAULT_CONFIGS, - ZAPPER, + ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, ORACLE, OWNER, SWAPPER, VAULT_CONFIGS, ZAPPER, }; use crate::swap::swap_exact_in; use crate::update_coin_balances::update_coin_balance; @@ -110,13 +109,6 @@ pub fn update_config( .add_attribute("value", configs.to_string().fallback("None")) } - if let Some(unchecked) = new_config.red_bank { - RED_BANK.save(deps.storage, &unchecked.check(deps.api)?)?; - response = response - .add_attribute("key", "red_bank") - .add_attribute("value", unchecked.address()); - } - if let Some(unchecked) = new_config.oracle { ORACLE.save(deps.storage, &unchecked.check(deps.api)?)?; response = response diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 1612437ed..e67cccabf 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{coin, Addr, Decimal}; use mars_rover::adapters::swap::SwapperBase; use mars_rover::adapters::vault::{VaultBase, VaultConfig}; -use mars_rover::adapters::{OracleBase, RedBankBase, ZapperBase}; +use mars_rover::adapters::{OracleBase, ZapperBase}; use mars_rover::error::ContractError; use mars_rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; @@ -21,7 +21,6 @@ fn test_only_owner_can_update_config() { account_nft: None, owner: Some(new_owner.to_string()), allowed_coins: None, - red_bank: None, oracle: None, max_close_factor: None, swapper: None, @@ -45,7 +44,6 @@ fn test_raises_on_invalid_vaults_config() { account_nft: None, owner: None, allowed_coins: None, - red_bank: None, oracle: None, max_close_factor: None, swapper: None, @@ -70,7 +68,6 @@ fn test_raises_on_invalid_vaults_config() { account_nft: None, owner: None, allowed_coins: None, - red_bank: None, oracle: None, max_close_factor: None, swapper: None, @@ -99,7 +96,6 @@ fn test_update_config_works_with_full_config() { let new_nft_contract = mock.deploy_new_nft_contract().unwrap(); let new_owner = Addr::unchecked("new_owner"); - let new_red_bank = RedBankBase::new("new_red_bank".to_string()); let new_vault_configs = vec![VaultInstantiateConfig { vault: VaultBase::new("vault_contract_3000".to_string()), config: VaultConfig { @@ -121,7 +117,6 @@ fn test_update_config_works_with_full_config() { account_nft: Some(new_nft_contract.to_string()), owner: Some(new_owner.to_string()), allowed_coins: Some(new_allowed_coins.clone()), - red_bank: Some(new_red_bank.clone()), oracle: Some(new_oracle.clone()), max_close_factor: Some(new_close_factor), swapper: Some(new_swapper.clone()), @@ -147,9 +142,6 @@ fn test_update_config_works_with_full_config() { assert_eq!(new_queried_allowed_coins, new_allowed_coins); assert_ne!(new_queried_allowed_coins, original_allowed_coins); - assert_eq!(&new_config.red_bank, new_red_bank.address()); - assert_ne!(new_config.red_bank, original_config.red_bank); - assert_eq!(&new_config.oracle, new_oracle.address()); assert_ne!(new_config.oracle, original_config.oracle); diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index ec1e47fb5..110d61518 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -60,7 +60,6 @@ pub struct ConfigUpdates { pub owner: Option, pub allowed_coins: Option>, pub vault_configs: Option>, - pub red_bank: Option, pub oracle: Option, pub max_close_factor: Option, pub swapper: Option, From fcaf0d39d8247579f3a454d7c09cd835f425e8c7 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 1 Dec 2022 02:02:52 +0100 Subject: [PATCH 085/218] Updating de-listing logic (#51) --- contracts/credit-manager/src/health.rs | 9 ++- contracts/credit-manager/src/repay.rs | 4 +- contracts/credit-manager/src/swap.rs | 4 +- contracts/credit-manager/src/vault/enter.rs | 4 +- contracts/credit-manager/src/withdraw.rs | 4 +- contracts/credit-manager/tests/test_health.rs | 80 ++++++++++++++++++- contracts/credit-manager/tests/test_repay.rs | 20 ----- contracts/credit-manager/tests/test_swap.rs | 20 ----- .../credit-manager/tests/test_withdraw.rs | 35 +------- 9 files changed, 95 insertions(+), 85 deletions(-) diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index ead9a9b3d..88313fbbc 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -9,7 +9,7 @@ use mars_rover::error::{ContractError, ContractResult}; use mars_rover::traits::{Coins, IntoDecimal}; use crate::query::query_positions; -use crate::state::{ORACLE, RED_BANK, VAULT_CONFIGS}; +use crate::state::{ALLOWED_COINS, ORACLE, RED_BANK, VAULT_CONFIGS}; // Given Red Bank and Mars-Oracle does not have knowledge of vaults, // we cannot use Health::compute_health_from_coins() and must assemble positions manually @@ -40,6 +40,13 @@ fn get_positions_for_coins( let querier = MarsQuerier::new(&deps.querier, oracle.address(), red_bank.address()); let positions = Health::positions_from_coins(&querier, collateral, debt)? .into_values() + // If coin has been de-listed, drop MaxLTV to zero + .map(|mut p| { + if !ALLOWED_COINS.contains(deps.storage, &p.denom) { + p.max_ltv = Decimal::zero(); + } + p + }) .collect(); Ok(positions) } diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index 868ffa4e7..2c186cf07 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -5,15 +5,13 @@ use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; use mars_rover::error::{ContractError, ContractResult}; use crate::state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; -use crate::utils::{assert_coin_is_whitelisted, debt_shares_to_amount, decrement_coin_balance}; +use crate::utils::{debt_shares_to_amount, decrement_coin_balance}; pub fn repay(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> ContractResult { if coin.amount.is_zero() { return Err(ContractError::NoAmount); } - assert_coin_is_whitelisted(deps.storage, &coin.denom)?; - // Ensure repayment does not exceed max debt on account let (debt_amount, debt_shares) = current_debt_for_denom(deps.as_ref(), &env, account_id, &coin.denom)?; diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs index f27cc8934..9ea2ef62c 100644 --- a/contracts/credit-manager/src/swap.rs +++ b/contracts/credit-manager/src/swap.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{Coin, Decimal, DepsMut, Env, Response}; use mars_rover::error::{ContractError, ContractResult}; use crate::state::SWAPPER; -use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance, update_balance_msg}; +use crate::utils::{assert_coin_is_whitelisted, decrement_coin_balance, update_balance_msg}; pub fn swap_exact_in( deps: DepsMut, @@ -13,7 +13,7 @@ pub fn swap_exact_in( denom_out: &str, slippage: Decimal, ) -> ContractResult { - assert_coins_are_whitelisted(deps.storage, vec![coin_in.denom.as_str(), denom_out])?; + assert_coin_is_whitelisted(deps.storage, denom_out)?; if coin_in.amount.is_zero() { return Err(ContractError::NoAmount); diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index 7793cf41a..2143d4f50 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -10,7 +10,7 @@ use mars_rover::msg::ExecuteMsg; use crate::query::query_vault_positions; use crate::state::{COIN_BALANCES, ORACLE, VAULT_CONFIGS}; -use crate::utils::{assert_coins_are_whitelisted, decrement_coin_balance}; +use crate::utils::{assert_coin_is_whitelisted, decrement_coin_balance}; use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; pub fn enter_vault( @@ -27,7 +27,7 @@ pub fn enter_vault( amount, }; - assert_coins_are_whitelisted(deps.storage, vec![denom])?; + assert_coin_is_whitelisted(deps.storage, denom)?; assert_vault_is_whitelisted(deps.storage, &vault)?; assert_denom_matches_vault_reqs(deps.querier, &vault, &coin_to_enter)?; assert_deposit_is_under_cap(deps.as_ref(), &vault, &coin_to_enter, rover_addr)?; diff --git a/contracts/credit-manager/src/withdraw.rs b/contracts/credit-manager/src/withdraw.rs index f1368b8b1..b727b6e26 100644 --- a/contracts/credit-manager/src/withdraw.rs +++ b/contracts/credit-manager/src/withdraw.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, DepsMut, Response}; use mars_rover::error::{ContractError, ContractResult}; -use crate::utils::{assert_coin_is_whitelisted, decrement_coin_balance}; +use crate::utils::decrement_coin_balance; pub fn withdraw( deps: DepsMut, @@ -10,8 +10,6 @@ pub fn withdraw( coin: Coin, recipient: Addr, ) -> ContractResult { - assert_coin_is_whitelisted(deps.storage, &coin.denom)?; - if coin.amount.is_zero() { return Err(ContractError::NoAmount); } diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index 4d5c6dd86..fadc25584 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -6,10 +6,13 @@ use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use mars_mock_oracle::msg::CoinPrice; use mars_rover::error::ContractError; use mars_rover::msg::execute::Action::{Borrow, Deposit}; +use mars_rover::msg::instantiate::ConfigUpdates; use mars_rover::msg::query::DebtAmount; use mars_rover::traits::IntoDecimal; -use crate::helpers::{assert_err, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv}; +use crate::helpers::{ + assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv, +}; pub mod helpers; @@ -608,6 +611,81 @@ fn test_debt_value() { ); } +#[test] +fn test_delisted_assets_drop_max_ltv() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, uosmo_info.denom.clone()), + }) + .build() + .unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(100)), + ], + &[uosmo_info.to_coin(300)], + ) + .unwrap(); + + let prev_health = mock.query_health(&account_id); + + // Remove uosmo from the coin whitelist + let res = mock.query_config(); + mock.update_config( + &Addr::unchecked(res.owner), + ConfigUpdates { + allowed_coins: Some(vec![uatom_info.denom]), + ..Default::default() + }, + ) + .unwrap(); + + let curr_health = mock.query_health(&account_id); + + // Values should be the same + assert_eq!(prev_health.total_debt_value, curr_health.total_debt_value); + assert_eq!( + prev_health.total_collateral_value, + curr_health.total_collateral_value + ); + + assert_eq!( + prev_health.liquidation_health_factor, + curr_health.liquidation_health_factor + ); + assert_eq!( + prev_health.liquidation_threshold_adjusted_collateral, + curr_health.liquidation_threshold_adjusted_collateral + ); + assert_eq!(prev_health.liquidatable, curr_health.liquidatable); + + // Should have been changed due to de-listing + assert_ne!(prev_health.above_max_ltv, curr_health.above_max_ltv); + assert_ne!( + prev_health.max_ltv_adjusted_collateral, + curr_health.max_ltv_adjusted_collateral + ); + assert_ne!( + prev_health.max_ltv_health_factor, + curr_health.max_ltv_health_factor + ); + assert_eq!( + curr_health.max_ltv_health_factor, + Some(Decimal::raw(811881188118811881u128)) + ); +} + fn find_by_denom<'a>(denom: &'a str, shares: &'a [DebtAmount]) -> &'a DebtAmount { shares.iter().find(|item| item.denom == *denom).unwrap() } diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index 7e08e3b0d..d2410f051 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -37,26 +37,6 @@ fn test_only_token_owner_can_repay() { ) } -#[test] -fn test_can_only_repay_what_is_whitelisted() { - let coin_info = uosmo_info(); - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().allowed_coins(&[coin_info]).build().unwrap(); - let account_id = mock.create_credit_account(&user).unwrap(); - - let res = mock.update_credit_account( - &account_id, - &user, - vec![Repay(coin(234, "usomething"))], - &[], - ); - - assert_err( - res, - ContractError::NotWhitelisted(String::from("usomething")), - ) -} - #[test] fn test_repaying_zero_raises() { let coin_info = uosmo_info(); diff --git a/contracts/credit-manager/tests/test_swap.rs b/contracts/credit-manager/tests/test_swap.rs index 9b9df9cff..27723908a 100644 --- a/contracts/credit-manager/tests/test_swap.rs +++ b/contracts/credit-manager/tests/test_swap.rs @@ -36,26 +36,6 @@ fn test_only_token_owner_can_swap_for_account() { ) } -#[test] -fn test_coin_in_must_be_whitelisted() { - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().build().unwrap(); - let account_id = mock.create_credit_account(&user).unwrap(); - - let res = mock.update_credit_account( - &account_id, - &user, - vec![SwapExactIn { - coin_in: coin(12, "mars"), - denom_out: "osmo".to_string(), - slippage: Decimal::from_atomics(6u128, 1).unwrap(), - }], - &[], - ); - - assert_err(res, ContractError::NotWhitelisted("mars".to_string())) -} - #[test] fn test_denom_out_must_be_whitelisted() { let osmo_info = uosmo_info(); diff --git a/contracts/credit-manager/tests/test_withdraw.rs b/contracts/credit-manager/tests/test_withdraw.rs index a674c0a62..d454b6bdb 100644 --- a/contracts/credit-manager/tests/test_withdraw.rs +++ b/contracts/credit-manager/tests/test_withdraw.rs @@ -2,10 +2,10 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::{coin, coins, Addr, Coin, OverflowError, Uint128}; use mars_rover::error::ContractError; -use mars_rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; +use mars_rover::error::ContractError::NotTokenOwner; use mars_rover::msg::execute::Action; -use crate::helpers::{assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv}; +use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv}; pub mod helpers; @@ -126,37 +126,6 @@ fn test_withdraw_but_not_enough_funds() { assert_eq!(res.coins.len(), 0); } -#[test] -fn test_can_only_withdraw_allowed_assets() { - let coin_info = uosmo_info(); - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) - .fund_account(AccountToFund { - addr: user.clone(), - funds: coins(300, coin_info.denom.clone()), - }) - .build() - .unwrap(); - let account_id = mock.create_credit_account(&user).unwrap(); - - let not_allowed_coin = ujake_info().to_coin(234); - let res = mock.update_credit_account( - &account_id, - &user, - vec![ - Action::Deposit(coin_info.to_coin(234)), - Action::Withdraw(not_allowed_coin.clone()), - ], - &[coin(234, coin_info.denom)], - ); - - assert_err(res, NotWhitelisted(not_allowed_coin.denom)); - - let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); -} - #[test] fn test_cannot_withdraw_more_than_healthy() { let coin_info = uosmo_info(); From 6bb90bb1c7225f1717c1e2f66f6734e78df8bc99 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 1 Dec 2022 02:04:28 +0100 Subject: [PATCH 086/218] Remove unused priceable underlying query [oak audit] (#58) Remove unused priceable underlying query --- contracts/oracle-adapter/src/contract.rs | 25 +--------- contracts/oracle-adapter/src/msg.rs | 7 +-- .../tests/test_query_priceable_underlying.rs | 49 ------------------- 3 files changed, 4 insertions(+), 77 deletions(-) delete mode 100644 contracts/oracle-adapter/tests/test_query_priceable_underlying.rs diff --git a/contracts/oracle-adapter/src/contract.rs b/contracts/oracle-adapter/src/contract.rs index 697c2397b..779a7cb74 100644 --- a/contracts/oracle-adapter/src/contract.rs +++ b/contracts/oracle-adapter/src/contract.rs @@ -1,13 +1,12 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_binary, Addr, Binary, Coin, Decimal, Deps, DepsMut, Env, MessageInfo, Order, Response, - StdResult, + to_binary, Addr, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, }; use cw2::set_contract_version; use cw_storage_plus::Bound; -use mars_outpost::oracle::PriceResponse; +use mars_outpost::oracle::PriceResponse; use mars_rover::adapters::vault::VaultBase; use mars_rover::adapters::Oracle; use mars_rover::traits::IntoDecimal; @@ -67,9 +66,6 @@ pub fn execute( pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> ContractResult { let res = match msg { QueryMsg::Price { denom } => to_binary(&query_price(deps, &denom)?), - QueryMsg::PriceableUnderlying { coin } => { - to_binary(&query_priceable_underlying(deps, coin)?) - } QueryMsg::Config {} => to_binary(&query_config(deps)?), QueryMsg::PricingInfo { denom } => to_binary(&query_pricing_info(deps, &denom)?), QueryMsg::AllPricingInfo { start_after, limit } => { @@ -104,23 +100,6 @@ fn query_all_pricing_info( .collect::>>() } -fn query_priceable_underlying(deps: Deps, coin: Coin) -> ContractResult> { - let info_opt = VAULT_PRICING_INFO.may_load(deps.storage, &coin.denom)?; - match info_opt { - Some(info) => match info.method { - PricingMethod::PreviewRedeem => { - let vault = VaultBase::new(info.addr); - let amount = vault.query_preview_redeem(&deps.querier, coin.amount)?; - Ok(vec![Coin { - denom: info.base_denom, - amount, - }]) - } - }, - _ => Ok(vec![coin]), - } -} - fn query_price(deps: Deps, denom: &str) -> ContractResult { let info_opt = VAULT_PRICING_INFO.may_load(deps.storage, denom)?; let oracle = ORACLE.load(deps.storage)?; diff --git a/contracts/oracle-adapter/src/msg.rs b/contracts/oracle-adapter/src/msg.rs index b5cd38af9..8117c1704 100644 --- a/contracts/oracle-adapter/src/msg.rs +++ b/contracts/oracle-adapter/src/msg.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Coin, Decimal}; +use cosmwasm_std::{Addr, Decimal}; + use mars_rover::adapters::{Oracle, OracleUnchecked}; #[cw_serde] @@ -27,10 +28,6 @@ pub enum QueryMsg { #[returns(mars_outpost::oracle::PriceResponse)] Price { denom: String }, - /// Converts vault coin to the mars-oracle accepted priceable coins - #[returns(Vec)] - PriceableUnderlying { coin: Coin }, - #[returns(ConfigResponse)] Config {}, diff --git a/contracts/oracle-adapter/tests/test_query_priceable_underlying.rs b/contracts/oracle-adapter/tests/test_query_priceable_underlying.rs deleted file mode 100644 index ca01e3872..000000000 --- a/contracts/oracle-adapter/tests/test_query_priceable_underlying.rs +++ /dev/null @@ -1,49 +0,0 @@ -use cosmwasm_std::{coin, Coin, Uint128}; -use cw_multi_test::App; - -use mars_oracle_adapter::msg::QueryMsg; - -use crate::helpers::{instantiate_oracle_adapter, mock_vault_info}; - -pub mod helpers; - -#[test] -fn test_non_vault_coin_underlying() { - let mut app = App::default(); - let contract_addr = instantiate_oracle_adapter(&mut app); - - let coins: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.to_string(), - &QueryMsg::PriceableUnderlying { - coin: coin(100, "uosmo"), - }, - ) - .unwrap(); - - assert_eq!(coins.len(), 1); - assert_eq!(coins[0].denom, "uosmo".to_string()); - assert_eq!(coins[0].amount, Uint128::new(100)); -} - -#[test] -fn test_vault_coin_preview_redeem() { - let mut app = App::default(); - let contract_addr = instantiate_oracle_adapter(&mut app); - let vault_info = mock_vault_info(); - - let coins: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.to_string(), - &QueryMsg::PriceableUnderlying { - coin: coin(1000, vault_info.vault_coin_denom), - }, - ) - .unwrap(); - - assert_eq!(coins.len(), 1); - assert_eq!(coins[0].denom, "GAMM_LP_12352".to_string()); - assert_eq!(coins[0].amount, Uint128::new(120)); -} From 0c3db0cad52bf9bf17602f52f4e3a026ce6fd212 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 1 Dec 2022 02:05:26 +0100 Subject: [PATCH 087/218] Remove unused callback [oak audit] (#59) Remove unused callback --- contracts/credit-manager/src/execute.rs | 7 +- contracts/credit-manager/src/vault/exit.rs | 9 +- .../src/vault/liquidate_vault.rs | 5 +- .../credit-manager/tests/test_vault_exit.rs | 90 ------------------- packages/rover/src/adapters/vault/base.rs | 36 +++++--- packages/rover/src/msg/execute.rs | 7 -- 6 files changed, 28 insertions(+), 126 deletions(-) diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 533c22955..72f9d6763 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -376,12 +376,7 @@ pub fn execute_callback( account_id, vault, amount, - } => exit_vault(deps, env, &account_id, vault, amount, false), - CallbackMsg::ForceExitVault { - account_id, - vault, - amount, - } => exit_vault(deps, env, &account_id, vault, amount, true), + } => exit_vault(deps, env, &account_id, vault, amount), CallbackMsg::RequestVaultUnlock { account_id, vault, diff --git a/contracts/credit-manager/src/vault/exit.rs b/contracts/credit-manager/src/vault/exit.rs index 356b733f3..7041cda6b 100644 --- a/contracts/credit-manager/src/vault/exit.rs +++ b/contracts/credit-manager/src/vault/exit.rs @@ -15,7 +15,6 @@ pub fn exit_vault( account_id: &str, vault: Vault, amount: Uint128, - force: bool, ) -> ContractResult { assert_vault_is_whitelisted(deps.storage, &vault)?; @@ -25,15 +24,11 @@ pub fn exit_vault( deps.storage, account_id, &vault.address, - if force { - VaultPositionUpdate::Locked(UpdateType::Decrement(amount)) - } else { - VaultPositionUpdate::Unlocked(UpdateType::Decrement(amount)) - }, + VaultPositionUpdate::Unlocked(UpdateType::Decrement(amount)), )?; // Sends vault coins to vault in exchange for underlying assets - let withdraw_msg = vault.withdraw_msg(&deps.querier, amount, force)?; + let withdraw_msg = vault.withdraw_msg(&deps.querier, amount)?; // Updates coin balances for account after a vault withdraw has taken place let previous_balance = diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index 061162f17..7d946bbd8 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -99,7 +99,7 @@ fn liquidate_unlocked( VaultPositionUpdate::Unlocked(UpdateType::Decrement(request.amount)), )?; - let vault_withdraw_msg = request_vault.withdraw_msg(&deps.querier, request.amount, false)?; + let vault_withdraw_msg = request_vault.withdraw_msg(&deps.querier, request.amount)?; let update_coin_balance_msg = update_balance_msg( &deps.querier, @@ -225,7 +225,8 @@ fn liquidate_locked( VaultPositionUpdate::Locked(UpdateType::Decrement(request.amount)), )?; - let vault_withdraw_msg = request_vault.withdraw_msg(&deps.querier, request.amount, true)?; + let vault_withdraw_msg = + request_vault.force_withdraw_locked_msg(&deps.querier, request.amount)?; let update_coin_balance_msg = update_balance_msg( &deps.querier, diff --git a/contracts/credit-manager/tests/test_vault_exit.rs b/contracts/credit-manager/tests/test_vault_exit.rs index 8966db3e5..67741f661 100644 --- a/contracts/credit-manager/tests/test_vault_exit.rs +++ b/contracts/credit-manager/tests/test_vault_exit.rs @@ -6,7 +6,6 @@ use mars_rover::adapters::vault::VaultBase; use mars_rover::error::ContractError; use mars_rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; use mars_rover::msg::execute::Action::{Deposit, EnterVault, ExitVault}; -use mars_rover::msg::execute::CallbackMsg; use crate::helpers::{ assert_err, locked_vault_info, lp_token_info, uatom_info, unlocked_vault_info, uosmo_info, @@ -107,95 +106,6 @@ fn test_no_unlocked_vault_coins_to_withdraw() { ) } -#[test] -fn test_force_withdraw_can_only_be_called_by_rover() { - let leverage_vault = locked_vault_info(); - - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_vaults(&[leverage_vault.clone()]) - .build() - .unwrap(); - - let vault = mock.get_vault(&leverage_vault); - let account_id = mock.create_credit_account(&user).unwrap(); - - let res = mock.invoke_callback( - &user.clone(), - CallbackMsg::ForceExitVault { - account_id, - vault: VaultBase::new(Addr::unchecked(vault.address)), - amount: STARTING_VAULT_SHARES, - }, - ); - assert_err(res, ContractError::ExternalInvocation) -} - -#[test] -fn test_force_withdraw_breaks_lock() { - let lp_token = lp_token_info(); - let leverage_vault = locked_vault_info(); - - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) - .fund_account(AccountToFund { - addr: user.clone(), - funds: vec![lp_token.to_coin(300)], - }) - .build() - .unwrap(); - - let vault = mock.get_vault(&leverage_vault); - let account_id = mock.create_credit_account(&user).unwrap(); - - mock.update_credit_account( - &account_id, - &user, - vec![ - Deposit(lp_token.to_coin(200)), - EnterVault { - vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(100)), - }, - ], - &[lp_token.to_coin(200)], - ) - .unwrap(); - - // Assert token's position - let res = mock.query_positions(&account_id); - assert_eq!(res.vaults.len(), 1); - let v = res.vaults.first().unwrap(); - assert_eq!(v.amount.locked(), STARTING_VAULT_SHARES); - - mock.invoke_callback( - &mock.rover.clone(), - CallbackMsg::ForceExitVault { - account_id: account_id.clone(), - vault: VaultBase::new(Addr::unchecked(vault.address)), - amount: STARTING_VAULT_SHARES, - }, - ) - .unwrap(); - - // Assert token's updated position - let res = mock.query_positions(&account_id); - assert_eq!(res.vaults.len(), 0); - let lp = get_coin(&lp_token.denom, &res.coins); - assert_eq!(lp.amount, Uint128::from(200u128)); - - // Assert Rover indeed has those on hand in the bank - let atom = mock.query_balance(&mock.rover, &lp_token.denom); - assert_eq!(atom.amount, Uint128::from(200u128)); - - // Assert Rover does not have the vault tokens anymore - let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.vault_token_denom); - assert_eq!(Uint128::zero(), lp_balance.amount); -} - #[test] fn test_withdraw_with_unlocked_vault_coins() { let lp_token = lp_token_info(); diff --git a/packages/rover/src/adapters/vault/base.rs b/packages/rover/src/adapters/vault/base.rs index a8a1ac68d..fc633f7ec 100644 --- a/packages/rover/src/adapters/vault/base.rs +++ b/packages/rover/src/adapters/vault/base.rs @@ -84,11 +84,26 @@ impl Vault { Ok(deposit_msg) } - pub fn withdraw_msg( + pub fn withdraw_msg(&self, querier: &QuerierWrapper, amount: Uint128) -> StdResult { + let vault_info = self.query_info(querier)?; + let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address.to_string(), + funds: vec![Coin { + denom: vault_info.vault_token, + amount, + }], + msg: to_binary(&ExecuteMsg::Redeem { + recipient: None, + amount, + })?, + }); + Ok(withdraw_msg) + } + + pub fn force_withdraw_locked_msg( &self, querier: &QuerierWrapper, amount: Uint128, - force: bool, ) -> StdResult { let vault_info = self.query_info(querier)?; let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { @@ -97,19 +112,12 @@ impl Vault { denom: vault_info.vault_token, amount, }], - msg: to_binary( - &(if force { - ExecuteMsg::VaultExtension(ExtensionExecuteMsg::ForceUnlock(ForceRedeem { - recipient: None, - amount, - })) - } else { - ExecuteMsg::Redeem { - recipient: None, - amount, - } + msg: to_binary(&ExecuteMsg::VaultExtension( + ExtensionExecuteMsg::ForceUnlock(ForceRedeem { + recipient: None, + amount, }), - )?, + ))?, }); Ok(withdraw_msg) } diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 7492d46d2..5716d6088 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -142,13 +142,6 @@ pub enum CallbackMsg { /// Total vault coin balance in Rover previous_total_balance: Uint128, }, - /// A privileged action only to be used by Rover. Same as `VaultWithdraw` except it bypasses any lockup period - /// restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation. - ForceExitVault { - account_id: String, - vault: Vault, - amount: Uint128, - }, /// Requests unlocking of shares for a vault with a lock period RequestVaultUnlock { account_id: String, From 4589cf367f513ebf380d0ba4ad08888695f50c02 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 1 Dec 2022 02:06:36 +0100 Subject: [PATCH 088/218] Max close factor validated [oak audit] (#57) Max close factor validated --- contracts/credit-manager/src/execute.rs | 2 ++ contracts/credit-manager/src/instantiate.rs | 17 ++++++++++++--- .../credit-manager/tests/test_instantiate.rs | 11 ++++++++++ .../tests/test_update_config.rs | 21 +++++++++++++++++++ packages/rover/src/error.rs | 3 +++ 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 72f9d6763..876cd93a4 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -12,6 +12,7 @@ use mars_rover::traits::{FallbackStr, Stringify}; use crate::borrow::borrow; use crate::deposit::deposit; use crate::health::assert_below_max_ltv; +use crate::instantiate::assert_lte_to_one; use crate::liquidate_coin::liquidate_coin; use crate::refund::refund_coin_balances; use crate::repay::repay; @@ -131,6 +132,7 @@ pub fn update_config( } if let Some(cf) = new_config.max_close_factor { + assert_lte_to_one(&cf)?; MAX_CLOSE_FACTOR.save(deps.storage, &cf)?; response = response .add_attribute("key", "max_close_factor") diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index da3b53a85..acbb3a135 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,6 +1,6 @@ -use cosmwasm_std::DepsMut; +use cosmwasm_std::{Decimal, DepsMut}; -use mars_rover::error::ContractResult; +use mars_rover::error::{ContractError, ContractResult}; use mars_rover::msg::InstantiateMsg; use crate::state::{ @@ -12,10 +12,12 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { OWNER.save(deps.storage, &owner)?; RED_BANK.save(deps.storage, &msg.red_bank.check(deps.api)?)?; ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; - MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; SWAPPER.save(deps.storage, &msg.swapper.check(deps.api)?)?; ZAPPER.save(deps.storage, &msg.zapper.check(deps.api)?)?; + assert_lte_to_one(&msg.max_close_factor)?; + MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; + msg.allowed_vaults .iter() .try_for_each(|v| -> ContractResult<_> { @@ -30,3 +32,12 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { Ok(()) } + +pub fn assert_lte_to_one(dec: &Decimal) -> ContractResult<()> { + if dec > &Decimal::one() { + return Err(ContractError::InvalidConfig { + reason: "value greater than one".to_string(), + }); + } + Ok(()) +} diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 0d99dcefe..13d89768f 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -194,3 +194,14 @@ fn test_max_close_factor_set_on_instantiate() { let mock_default = Decimal::from_atomics(5u128, 1).unwrap(); assert_eq!(mock_default, res.max_close_factor); } + +#[test] +fn test_max_close_factor_validated() { + let mock = MockEnv::new() + .max_close_factor(Decimal::from_atomics(1244u128, 3).unwrap()) + .build(); + + if mock.is_ok() { + panic!("Should have thrown an error: Max close factor should be below 1"); + } +} diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index e67cccabf..922aaf7d2 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -4,6 +4,7 @@ use mars_rover::adapters::swap::SwapperBase; use mars_rover::adapters::vault::{VaultBase, VaultConfig}; use mars_rover::adapters::{OracleBase, ZapperBase}; use mars_rover::error::ContractError; +use mars_rover::error::ContractError::InvalidConfig; use mars_rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; use crate::helpers::{assert_err, locked_vault_info, uatom_info, uosmo_info, MockEnv}; @@ -269,3 +270,23 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { ); assert_eq!(new_config.swapper, original_config.swapper); } + +#[test] +fn test_max_close_factor_validated_on_update() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_config(); + let res = mock.update_config( + &Addr::unchecked(original_config.owner), + ConfigUpdates { + max_close_factor: Some(Decimal::from_atomics(42u128, 1).unwrap()), + ..Default::default() + }, + ); + + assert_err( + res, + InvalidConfig { + reason: "value greater than one".to_string(), + }, + ); +} diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index b57745cfb..729e98eb6 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -48,6 +48,9 @@ pub enum ContractError { )] HealthNotImproved { prev_hf: String, new_hf: String }, + #[error("{reason:?}")] + InvalidConfig { reason: String }, + #[error("Vault configuration has invalid values")] InvalidVaultConfig {}, From 83c5e87e54712dec950833a271aa85a2fb03b40d Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 1 Dec 2022 02:21:30 +0100 Subject: [PATCH 089/218] Validate for duplicate keys [oak audit] (#55) Validate to check duplicates --- contracts/credit-manager/src/execute.rs | 6 +- contracts/credit-manager/src/instantiate.rs | 30 ++++++- .../credit-manager/tests/test_instantiate.rs | 27 +++++- .../tests/test_update_config.rs | 90 ++++++++++++++++++- packages/rover/src/adapters/vault/config.rs | 6 +- packages/rover/src/error.rs | 3 - 6 files changed, 148 insertions(+), 14 deletions(-) diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 876cd93a4..b41c6dbea 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -12,7 +12,9 @@ use mars_rover::traits::{FallbackStr, Stringify}; use crate::borrow::borrow; use crate::deposit::deposit; use crate::health::assert_below_max_ltv; -use crate::instantiate::assert_lte_to_one; +use crate::instantiate::{ + assert_lte_to_one, assert_no_duplicate_coins, assert_no_duplicate_vaults, +}; use crate::liquidate_coin::liquidate_coin; use crate::refund::refund_coin_balances; use crate::repay::repay; @@ -88,6 +90,7 @@ pub fn update_config( } if let Some(coins) = new_config.allowed_coins { + assert_no_duplicate_coins(&coins)?; ALLOWED_COINS.clear(deps.storage); coins .iter() @@ -99,6 +102,7 @@ pub fn update_config( } if let Some(configs) = new_config.vault_configs { + assert_no_duplicate_vaults(&configs)?; VAULT_CONFIGS.clear(deps.storage); configs.iter().try_for_each(|v| -> ContractResult<_> { v.config.check()?; diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index acbb3a135..e4ef36eab 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,6 +1,10 @@ +use std::collections::HashSet; + use cosmwasm_std::{Decimal, DepsMut}; -use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::error::ContractError::InvalidConfig; +use mars_rover::error::ContractResult; +use mars_rover::msg::instantiate::VaultInstantiateConfig; use mars_rover::msg::InstantiateMsg; use crate::state::{ @@ -18,6 +22,7 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { assert_lte_to_one(&msg.max_close_factor)?; MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; + assert_no_duplicate_vaults(&msg.allowed_vaults)?; msg.allowed_vaults .iter() .try_for_each(|v| -> ContractResult<_> { @@ -26,6 +31,7 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { Ok(VAULT_CONFIGS.save(deps.storage, &vault.address, &v.config)?) })?; + assert_no_duplicate_coins(&msg.allowed_coins)?; msg.allowed_coins .iter() .try_for_each(|denom| ALLOWED_COINS.insert(deps.storage, denom).map(|_| ()))?; @@ -33,9 +39,29 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { Ok(()) } +pub fn assert_no_duplicate_vaults(vaults: &[VaultInstantiateConfig]) -> ContractResult<()> { + let set: HashSet<_> = vaults.iter().map(|v| v.vault.address.clone()).collect(); + if set.len() != vaults.len() { + return Err(InvalidConfig { + reason: "Duplicate vault configs present".to_string(), + }); + } + Ok(()) +} + +pub fn assert_no_duplicate_coins(denoms: &[String]) -> ContractResult<()> { + let set: HashSet<_> = denoms.iter().collect(); + if set.len() != denoms.len() { + return Err(InvalidConfig { + reason: "Duplicate coin configs present".to_string(), + }); + } + Ok(()) +} + pub fn assert_lte_to_one(dec: &Decimal) -> ContractResult<()> { if dec > &Decimal::one() { - return Err(ContractError::InvalidConfig { + return Err(InvalidConfig { reason: "value greater than one".to_string(), }); } diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 13d89768f..9fd4e92f4 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -1,8 +1,9 @@ +use cosmwasm_std::{coin, Decimal}; + use crate::helpers::{ - assert_contents_equal, uatom_info, ujake_info, unlocked_vault_info, uosmo_info, CoinInfo, - MockEnv, VaultTestInfo, + assert_contents_equal, locked_vault_info, uatom_info, ujake_info, unlocked_vault_info, + uosmo_info, CoinInfo, MockEnv, VaultTestInfo, }; -use cosmwasm_std::{coin, Decimal}; pub mod helpers; @@ -123,6 +124,17 @@ fn test_raises_on_invalid_vaults_config() { } } +#[test] +fn test_duplicate_vaults_raises() { + let mock = MockEnv::new() + .pre_deployed_vault("addr_123", &locked_vault_info()) + .pre_deployed_vault("addr_123", &unlocked_vault_info()) + .build(); + if mock.is_ok() { + panic!("Should have thrown an error"); + } +} + #[test] fn test_allowed_coins_set_on_instantiate() { let allowed_coins = vec![ @@ -152,6 +164,15 @@ fn test_allowed_coins_set_on_instantiate() { ) } +#[test] +fn test_duplicate_coins_raises() { + let allowed_coins = vec![uosmo_info(), uosmo_info(), uatom_info()]; + let mock = MockEnv::new().allowed_coins(&allowed_coins).build(); + if mock.is_ok() { + panic!("Should have thrown an error"); + } +} + #[test] fn test_red_bank_set_on_instantiate() { let red_bank_addr = "mars_red_bank_contract_123".to_string(); diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 922aaf7d2..2f44244e2 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -3,7 +3,6 @@ use cosmwasm_std::{coin, Addr, Decimal}; use mars_rover::adapters::swap::SwapperBase; use mars_rover::adapters::vault::{VaultBase, VaultConfig}; use mars_rover::adapters::{OracleBase, ZapperBase}; -use mars_rover::error::ContractError; use mars_rover::error::ContractError::InvalidConfig; use mars_rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; @@ -61,7 +60,12 @@ fn test_raises_on_invalid_vaults_config() { }, ); - assert_err(res, ContractError::InvalidVaultConfig {}); + assert_err( + res, + InvalidConfig { + reason: "max ltv or liquidation threshold are invalid".to_string(), + }, + ); let res = mock.update_config( &Addr::unchecked(original_config.owner), @@ -85,7 +89,12 @@ fn test_raises_on_invalid_vaults_config() { }, ); - assert_err(res, ContractError::InvalidVaultConfig {}); + assert_err( + res, + InvalidConfig { + reason: "max ltv or liquidation threshold are invalid".to_string(), + }, + ); } #[test] @@ -290,3 +299,78 @@ fn test_max_close_factor_validated_on_update() { }, ); } + +#[test] +fn test_raises_on_duplicate_vault_configs() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_config(); + let res = mock.update_config( + &Addr::unchecked(original_config.owner), + ConfigUpdates { + account_nft: None, + owner: None, + allowed_coins: None, + oracle: None, + max_close_factor: None, + swapper: None, + vault_configs: Some(vec![ + VaultInstantiateConfig { + vault: VaultBase::new("vault_123".to_string()), + config: VaultConfig { + deposit_cap: Default::default(), + max_ltv: Default::default(), + liquidation_threshold: Default::default(), + whitelisted: true, + }, + }, + VaultInstantiateConfig { + vault: VaultBase::new("vault_123".to_string()), + config: VaultConfig { + deposit_cap: Default::default(), + max_ltv: Default::default(), + liquidation_threshold: Default::default(), + whitelisted: false, + }, + }, + ]), + zapper: None, + }, + ); + + assert_err( + res, + InvalidConfig { + reason: "Duplicate vault configs present".to_string(), + }, + ); +} + +#[test] +fn test_raises_on_duplicate_coin_configs() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_config(); + let res = mock.update_config( + &Addr::unchecked(original_config.owner), + ConfigUpdates { + account_nft: None, + owner: None, + allowed_coins: Some(vec![ + "uosmo".to_string(), + "uatom".to_string(), + "uosmo".to_string(), + ]), + oracle: None, + max_close_factor: None, + swapper: None, + vault_configs: None, + zapper: None, + }, + ); + + assert_err( + res, + InvalidConfig { + reason: "Duplicate coin configs present".to_string(), + }, + ); +} diff --git a/packages/rover/src/adapters/vault/config.rs b/packages/rover/src/adapters/vault/config.rs index a7047e650..ad0d1a100 100644 --- a/packages/rover/src/adapters/vault/config.rs +++ b/packages/rover/src/adapters/vault/config.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Decimal}; use crate::error::ContractError; -use crate::error::ContractError::InvalidVaultConfig; +use crate::error::ContractError::InvalidConfig; #[cw_serde] pub struct VaultConfig { @@ -19,7 +19,9 @@ impl VaultConfig { let max_ltv_bigger_than_lqt = self.max_ltv > self.liquidation_threshold; if max_ltv_too_big || lqt_too_big || max_ltv_bigger_than_lqt { - return Err(InvalidVaultConfig {}); + return Err(InvalidConfig { + reason: "max ltv or liquidation threshold are invalid".to_string(), + }); } Ok(()) } diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 729e98eb6..f076347cb 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -51,9 +51,6 @@ pub enum ContractError { #[error("{reason:?}")] InvalidConfig { reason: String }, - #[error("Vault configuration has invalid values")] - InvalidVaultConfig {}, - #[error("Issued incorrect action for vault type")] MismatchedVaultType, From 6f75e506c572c989c449eaecb4082434dc69d040 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 1 Dec 2022 10:39:13 +0100 Subject: [PATCH 090/218] Enforce max unlocking positions [oak audit] (#52) * enforce max unlocking positions * Update to Uint128::from --- contracts/credit-manager/src/execute.rs | 10 ++- contracts/credit-manager/src/instantiate.rs | 4 +- contracts/credit-manager/src/query.rs | 6 +- .../src/vault/request_unlock.rs | 3 +- contracts/credit-manager/src/vault/utils.rs | 26 ++++++- .../credit-manager/tests/helpers/mock_env.rs | 14 ++++ .../tests/test_update_config.rs | 15 +++- .../tests/test_vault_request_unlock.rs | 77 +++++++++++++++++++ packages/rover/src/error.rs | 6 ++ packages/rover/src/msg/instantiate.rs | 7 +- packages/rover/src/msg/query.rs | 1 + 11 files changed, 160 insertions(+), 9 deletions(-) diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index b41c6dbea..4392d16dd 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -19,7 +19,8 @@ use crate::liquidate_coin::liquidate_coin; use crate::refund::refund_coin_balances; use crate::repay::repay; use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, ORACLE, OWNER, SWAPPER, VAULT_CONFIGS, ZAPPER, + ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, SWAPPER, + VAULT_CONFIGS, ZAPPER, }; use crate::swap::swap_exact_in; use crate::update_coin_balances::update_coin_balance; @@ -143,6 +144,13 @@ pub fn update_config( .add_attribute("value", cf.to_string()); } + if let Some(num) = new_config.max_unlocking_positions { + MAX_UNLOCKING_POSITIONS.save(deps.storage, &num)?; + response = response + .add_attribute("key", "max_unlocking_positions") + .add_attribute("value", num.to_string()); + } + Ok(response) } diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index e4ef36eab..3ff3c8e7c 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -8,7 +8,8 @@ use mars_rover::msg::instantiate::VaultInstantiateConfig; use mars_rover::msg::InstantiateMsg; use crate::state::{ - ALLOWED_COINS, MAX_CLOSE_FACTOR, ORACLE, OWNER, RED_BANK, SWAPPER, VAULT_CONFIGS, ZAPPER, + ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, SWAPPER, + VAULT_CONFIGS, ZAPPER, }; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { @@ -18,6 +19,7 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; SWAPPER.save(deps.storage, &msg.swapper.check(deps.api)?)?; ZAPPER.save(deps.storage, &msg.zapper.check(deps.api)?)?; + MAX_UNLOCKING_POSITIONS.save(deps.storage, &msg.max_unlocking_positions)?; assert_lte_to_one(&msg.max_close_factor)?; MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index cbce73790..ebd7109e6 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -10,8 +10,9 @@ use mars_rover::msg::query::{ }; use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, ORACLE, OWNER, - RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_CONFIGS, VAULT_POSITIONS, ZAPPER, + ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, + MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_CONFIGS, + VAULT_POSITIONS, ZAPPER, }; use crate::utils::debt_shares_to_amount; @@ -27,6 +28,7 @@ pub fn query_config(deps: Deps) -> StdResult { red_bank: RED_BANK.load(deps.storage)?.address().into(), oracle: ORACLE.load(deps.storage)?.address().into(), max_close_factor: MAX_CLOSE_FACTOR.load(deps.storage)?, + max_unlocking_positions: MAX_UNLOCKING_POSITIONS.load(deps.storage)?, swapper: SWAPPER.load(deps.storage)?.address().into(), zapper: ZAPPER.load(deps.storage)?.address().into(), }) diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index 0b4e4db67..2eb0f7c02 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -2,6 +2,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin, DepsMut, Reply, Response, Uint128}; use crate::state::VAULT_REQUEST_TEMP_STORAGE; +use crate::vault::assert_under_max_unlocking_limit; use mars_rover::adapters::vault::{ UnlockingChange, UpdateType, Vault, VaultBase, VaultPositionUpdate, VaultUnlockingPosition, }; @@ -24,12 +25,12 @@ pub fn request_vault_unlock( amount: Uint128, ) -> ContractResult { assert_vault_is_whitelisted(deps.storage, &vault)?; - vault.query_lockup_duration(&deps.querier).map_err(|_| { ContractError::RequirementsNotMet( "This vault does not require lockup. Call withdraw directly.".to_string(), ) })?; + assert_under_max_unlocking_limit(deps.storage, account_id, &vault)?; update_vault_position( deps.storage, diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 359bfb7cc..9df4b8ecd 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,9 +1,9 @@ -use cosmwasm_std::{Addr, Coin, Deps, StdResult, Storage}; +use cosmwasm_std::{Addr, Coin, Deps, StdResult, Storage, Uint128}; use mars_rover::adapters::vault::{Vault, VaultPositionAmount, VaultPositionUpdate}; use mars_rover::error::{ContractError, ContractResult}; -use crate::state::{VAULT_CONFIGS, VAULT_POSITIONS}; +use crate::state::{MAX_UNLOCKING_POSITIONS, VAULT_CONFIGS, VAULT_POSITIONS}; use crate::update_coin_balances::query_balance; pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> ContractResult<()> { @@ -16,6 +16,28 @@ pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> Ok(()) } +pub fn assert_under_max_unlocking_limit( + storage: &mut dyn Storage, + account_id: &str, + vault: &Vault, +) -> ContractResult<()> { + let maximum = MAX_UNLOCKING_POSITIONS.load(storage)?; + let new_amount = VAULT_POSITIONS + .may_load(storage, (account_id, vault.address.clone()))? + .map(|p| p.unlocking().positions().len()) + .map(|len| Uint128::from(len as u128)) + .unwrap_or(Uint128::zero()) + .checked_add(Uint128::one())?; + + if new_amount > maximum { + return Err(ContractError::ExceedsMaxUnlockingPositions { + new_amount, + maximum, + }); + } + Ok(()) +} + pub fn update_vault_position( storage: &mut dyn Storage, account_id: &str, diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 0d8cbb6a8..2b1d79e76 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -65,6 +65,7 @@ pub struct MockEnvBuilder { pub set_nft_contract_owner: bool, pub accounts_to_fund: Vec, pub max_close_factor: Option, + pub max_unlocking_positions: Option, } #[allow(clippy::new_ret_no_self)] @@ -83,6 +84,7 @@ impl MockEnv { set_nft_contract_owner: true, accounts_to_fund: vec![], max_close_factor: None, + max_unlocking_positions: None, } } @@ -520,6 +522,7 @@ impl MockEnvBuilder { .map(|info| info.denom.clone()) .collect(); let max_close_factor = self.get_max_close_factor(); + let max_unlocking_positions = self.get_max_unlocking_positions(); let mut allowed_vaults = vec![]; allowed_vaults.extend(self.deploy_vaults()); @@ -538,6 +541,7 @@ impl MockEnvBuilder { red_bank, oracle, max_close_factor, + max_unlocking_positions, swapper, zapper, }, @@ -804,6 +808,11 @@ impl MockEnvBuilder { .unwrap_or_else(|| Decimal::from_atomics(5u128, 1).unwrap()) // 50% } + fn get_max_unlocking_positions(&self) -> Uint128 { + self.max_unlocking_positions + .unwrap_or_else(|| Uint128::new(100)) + } + //-------------------------------------------------------------------------------------------------- // Setter functions //-------------------------------------------------------------------------------------------------- @@ -873,6 +882,11 @@ impl MockEnvBuilder { self.max_close_factor = Some(cf); self } + + pub fn max_unlocking_positions(&mut self, max: u128) -> &mut Self { + self.max_unlocking_positions = Some(Uint128::new(max)); + self + } } //-------------------------------------------------------------------------------------------------- diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 2f44244e2..8b154afef 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{coin, Addr, Decimal}; +use cosmwasm_std::{coin, Addr, Decimal, Uint128}; use mars_rover::adapters::swap::SwapperBase; use mars_rover::adapters::vault::{VaultBase, VaultConfig}; @@ -23,6 +23,7 @@ fn test_only_owner_can_update_config() { allowed_coins: None, oracle: None, max_close_factor: None, + max_unlocking_positions: None, swapper: None, vault_configs: None, zapper: None, @@ -46,6 +47,7 @@ fn test_raises_on_invalid_vaults_config() { allowed_coins: None, oracle: None, max_close_factor: None, + max_unlocking_positions: None, swapper: None, vault_configs: Some(vec![VaultInstantiateConfig { vault: VaultBase::new("vault_123".to_string()), @@ -75,6 +77,7 @@ fn test_raises_on_invalid_vaults_config() { allowed_coins: None, oracle: None, max_close_factor: None, + max_unlocking_positions: None, swapper: None, vault_configs: Some(vec![VaultInstantiateConfig { vault: VaultBase::new("vault_123".to_string()), @@ -119,6 +122,7 @@ fn test_update_config_works_with_full_config() { let new_oracle = OracleBase::new("new_oracle".to_string()); let new_zapper = ZapperBase::new("new_zapper".to_string()); let new_close_factor = Decimal::from_atomics(32u128, 2).unwrap(); + let new_unlocking_max = Uint128::new(321); let new_swapper = SwapperBase::new("new_swapper".to_string()); mock.update_config( @@ -129,6 +133,7 @@ fn test_update_config_works_with_full_config() { allowed_coins: Some(new_allowed_coins.clone()), oracle: Some(new_oracle.clone()), max_close_factor: Some(new_close_factor), + max_unlocking_positions: Some(new_unlocking_max), swapper: Some(new_swapper.clone()), vault_configs: Some(new_vault_configs.clone()), zapper: Some(new_zapper.clone()), @@ -164,6 +169,12 @@ fn test_update_config_works_with_full_config() { original_config.max_close_factor ); + assert_eq!(new_config.max_unlocking_positions, new_unlocking_max); + assert_ne!( + new_config.max_unlocking_positions, + original_config.max_unlocking_positions + ); + assert_eq!(&new_config.swapper, new_swapper.address()); assert_ne!(new_config.swapper, original_config.swapper); } @@ -312,6 +323,7 @@ fn test_raises_on_duplicate_vault_configs() { allowed_coins: None, oracle: None, max_close_factor: None, + max_unlocking_positions: None, swapper: None, vault_configs: Some(vec![ VaultInstantiateConfig { @@ -361,6 +373,7 @@ fn test_raises_on_duplicate_coin_configs() { ]), oracle: None, max_close_factor: None, + max_unlocking_positions: None, swapper: None, vault_configs: None, zapper: None, diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs index 0d22ab871..760b9bf51 100644 --- a/contracts/credit-manager/tests/test_vault_request_unlock.rs +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -269,3 +269,80 @@ fn test_request_unlocked() { } } } + +#[test] +fn test_cannot_request_more_than_max() { + let lp_token = lp_token_info(); + let leverage_vault = locked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone()]) + .allowed_vaults(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(200)], + }) + .max_unlocking_positions(3) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault: vault.clone(), + denom: lp_token.denom.clone(), + amount: Some(Uint128::new(23)), + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + // First three positions are allowed (at max) + mock.update_credit_account( + &account_id, + &user, + vec![ + RequestVaultUnlock { + vault: vault.clone(), + amount: Uint128::new(100), + }, + RequestVaultUnlock { + vault: vault.clone(), + amount: Uint128::new(100), + }, + RequestVaultUnlock { + vault: vault.clone(), + amount: Uint128::new(100), + }, + ], + &[], + ) + .unwrap(); + + // next one goes over max + let res = mock.update_credit_account( + &account_id, + &user, + vec![RequestVaultUnlock { + vault, + amount: Uint128::new(100), + }], + &[], + ); + + assert_err( + res, + ContractError::ExceedsMaxUnlockingPositions { + new_amount: Uint128::new(4), + maximum: Uint128::new(3), + }, + ) +} diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index f076347cb..3a222563a 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -31,6 +31,12 @@ pub enum ContractError { #[error("{0}")] DecimalRangeExceeded(#[from] DecimalRangeExceeded), + #[error("New unlocking positions: {new_amount:?}. Maximum: {maximum:?}.")] + ExceedsMaxUnlockingPositions { + new_amount: Uint128, + maximum: Uint128, + }, + #[error("Callbacks cannot be invoked externally")] ExternalInvocation, diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index 110d61518..775c62f3a 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -5,7 +5,7 @@ use crate::adapters::ZapperUnchecked; use crate::adapters::{OracleUnchecked, RedBankUnchecked}; use crate::traits::Stringify; use cosmwasm_schema::cw_serde; -use cosmwasm_std::Decimal; +use cosmwasm_std::{Decimal, Uint128}; #[cw_serde] pub struct InstantiateMsg { @@ -22,6 +22,10 @@ pub struct InstantiateMsg { pub oracle: OracleUnchecked, /// The maximum percent a liquidator can decrease the debt amount of the liquidatee pub max_close_factor: Decimal, + /// The maximum number of unlocking positions an account can have simultaneously + /// Note: As health checking requires looping through each, this number must not be too large. + /// If so, having too many could prevent the account from being liquidated due to gas constraints. + pub max_unlocking_positions: Uint128, /// Helper contract for making swaps pub swapper: SwapperUnchecked, /// Helper contract for adding/removing liquidity @@ -62,6 +66,7 @@ pub struct ConfigUpdates { pub vault_configs: Option>, pub oracle: Option, pub max_close_factor: Option, + pub max_unlocking_positions: Option, pub swapper: Option, pub zapper: Option, } diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index a142e50e2..f499f0769 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -157,6 +157,7 @@ pub struct ConfigResponse { pub red_bank: String, pub oracle: String, pub max_close_factor: Decimal, + pub max_unlocking_positions: Uint128, pub swapper: String, pub zapper: String, } From 79cd70b5f4581e2aecc6a1ee500eb84c1da5a193 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 1 Dec 2022 11:17:17 +0100 Subject: [PATCH 091/218] Set contract override fix [oak audit] (#54) Set contract override fix --- contracts/account-nft/src/contract.rs | 21 +++++++++++++++---- contracts/account-nft/src/msg/query.rs | 3 +++ contracts/account-nft/src/query.rs | 6 +++++- .../account-nft/tests/helpers/mock_env.rs | 7 +++++++ .../account-nft/tests/test_instantiate.rs | 19 +++++++++++++++++ 5 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 contracts/account-nft/tests/test_instantiate.rs diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 906563255..dbeb563a3 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -4,6 +4,7 @@ use cosmwasm_std::{ to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; use cw2::set_contract_version; +use cw721::ContractInfoResponse; use cw721_base::Cw721Contract; use std::convert::TryInto; @@ -11,7 +12,7 @@ use crate::config::Config; use crate::error::ContractError; use crate::execute::{accept_ownership, burn, mint, update_config}; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::query::query_config; +use crate::query::{query_config, query_next_id}; use crate::state::{CONFIG, NEXT_ID}; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); @@ -23,8 +24,8 @@ pub type Parent<'a> = Cw721Contract<'a, Empty, Empty, Empty, Empty>; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, - env: Env, - info: MessageInfo, + _: Env, + _: MessageInfo, msg: InstantiateMsg, ) -> StdResult { set_contract_version( @@ -32,6 +33,7 @@ pub fn instantiate( &format!("crates.io:{}", CONTRACT_NAME), CONTRACT_VERSION, )?; + NEXT_ID.save(deps.storage, &1)?; CONFIG.save( @@ -42,7 +44,17 @@ pub fn instantiate( }, )?; - Parent::default().instantiate(deps, env, info, msg.into()) + // Parent::default().instantiate() copied below + // Cannot use given it overrides contract version + let info = ContractInfoResponse { + name: msg.name, + symbol: msg.symbol, + }; + Parent::default().contract_info.save(deps.storage, &info)?; + let minter = deps.api.addr_validate(&msg.minter)?; + Parent::default().minter.save(deps.storage, &minter)?; + + Ok(Response::default()) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -67,6 +79,7 @@ pub fn execute( pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Config {} => to_binary(&query_config(deps)?), + QueryMsg::NextId {} => to_binary(&query_next_id(deps)?), _ => Parent::default().query(deps, env, msg.try_into()?), } } diff --git a/contracts/account-nft/src/msg/query.rs b/contracts/account-nft/src/msg/query.rs index c74f61993..3dbc1725c 100644 --- a/contracts/account-nft/src/msg/query.rs +++ b/contracts/account-nft/src/msg/query.rs @@ -13,6 +13,9 @@ pub enum QueryMsg { #[returns(crate::config::UncheckedConfig)] Config {}, + #[returns(u64)] + NextId {}, + //-------------------------------------------------------------------------------------------------- // Base cw721 messages //-------------------------------------------------------------------------------------------------- diff --git a/contracts/account-nft/src/query.rs b/contracts/account-nft/src/query.rs index e2c22befe..fcbaf0ecf 100644 --- a/contracts/account-nft/src/query.rs +++ b/contracts/account-nft/src/query.rs @@ -1,7 +1,11 @@ use crate::config::UncheckedConfig; -use crate::state::CONFIG; +use crate::state::{CONFIG, NEXT_ID}; use cosmwasm_std::{Deps, StdResult}; pub fn query_config(deps: Deps) -> StdResult { Ok(CONFIG.load(deps.storage)?.into()) } + +pub fn query_next_id(deps: Deps) -> StdResult { + NEXT_ID.load(deps.storage) +} diff --git a/contracts/account-nft/tests/helpers/mock_env.rs b/contracts/account-nft/tests/helpers/mock_env.rs index 10f582aff..c3ae21aca 100644 --- a/contracts/account-nft/tests/helpers/mock_env.rs +++ b/contracts/account-nft/tests/helpers/mock_env.rs @@ -36,6 +36,13 @@ impl MockEnv { .unwrap() } + pub fn query_next_id(&mut self) -> u64 { + self.app + .wrap() + .query_wasm_smart(self.nft_contract.clone(), &QueryMsg::NextId {}) + .unwrap() + } + // Double checking ownership by querying NFT account-nft for correct owner pub fn assert_owner_is_correct(&mut self, user: &Addr, token_id: &str) { let owner_res: OwnerOfResponse = self diff --git a/contracts/account-nft/tests/test_instantiate.rs b/contracts/account-nft/tests/test_instantiate.rs new file mode 100644 index 000000000..0e35cbeaa --- /dev/null +++ b/contracts/account-nft/tests/test_instantiate.rs @@ -0,0 +1,19 @@ +use crate::helpers::{MockEnv, MAX_VALUE_FOR_BURN}; +use cosmwasm_std::Decimal; + +pub mod helpers; + +#[test] +fn test_storage_vars_set_on_instantiate() { + let mut mock = MockEnv::new().build().unwrap(); + + let config = mock.query_config(); + assert_eq!(config.proposed_new_minter, None); + assert_eq!( + config.max_value_for_burn, + Decimal::from_atomics(MAX_VALUE_FOR_BURN, 0).unwrap() + ); + + let next_id = mock.query_next_id(); + assert_eq!(next_id, 1); +} From e37312290be21b87b67a7724127753cceddd17bf Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 1 Dec 2022 18:01:50 +0100 Subject: [PATCH 092/218] Admin pkg access controls [oak audit] (#60) * admin pkg access control * two * test fixes * more test fixes * rebasing --- Cargo.lock | 20 ++++ Cargo.toml | 1 + contracts/account-nft/src/contract.rs | 4 +- contracts/account-nft/src/execute.rs | 2 +- contracts/account-nft/tests/test_ownership.rs | 1 + .../account-nft/tests/test_update_config.rs | 2 +- contracts/credit-manager/Cargo.toml | 1 + contracts/credit-manager/src/execute.rs | 28 ++--- contracts/credit-manager/src/instantiate.rs | 8 +- contracts/credit-manager/src/query.rs | 5 +- contracts/credit-manager/src/state.rs | 3 +- .../credit-manager/tests/helpers/mock_env.rs | 42 +++---- .../tests/test_create_credit_account.rs | 2 +- contracts/credit-manager/tests/test_health.rs | 2 +- .../credit-manager/tests/test_instantiate.rs | 14 +-- .../tests/test_update_config.rs | 48 ++++---- .../credit-manager/tests/test_zap_withdraw.rs | 2 +- contracts/oracle-adapter/Cargo.toml | 1 + contracts/oracle-adapter/src/contract.rs | 37 +++--- contracts/oracle-adapter/src/error.rs | 4 + contracts/oracle-adapter/src/msg.rs | 6 +- contracts/oracle-adapter/src/state.rs | 4 +- contracts/oracle-adapter/tests/helpers.rs | 2 +- .../tests/test_update_config.rs | 29 ++--- contracts/swapper/base/Cargo.toml | 1 + contracts/swapper/base/src/contract.rs | 73 ++++-------- contracts/swapper/base/src/error.rs | 4 + contracts/swapper/mock/src/contract.rs | 4 +- contracts/swapper/osmosis/Cargo.toml | 1 + contracts/swapper/osmosis/tests/helpers.rs | 8 +- .../swapper/osmosis/tests/test_estimate.rs | 4 +- .../swapper/osmosis/tests/test_instantiate.rs | 15 +-- .../swapper/osmosis/tests/test_set_route.rs | 18 +-- contracts/swapper/osmosis/tests/test_swap.rs | 4 +- .../osmosis/tests/test_update_admin.rs | 64 ++++++++++ .../osmosis/tests/test_update_config.rs | 93 --------------- packages/rover/Cargo.toml | 1 + packages/rover/src/adapters/swap/msgs.rs | 16 ++- packages/rover/src/error.rs | 4 + packages/rover/src/msg/instantiate.rs | 4 +- packages/rover/src/msg/query.rs | 5 +- .../mars-account-nft/mars-account-nft.json | 20 ++++ .../mars-credit-manager.json | 92 +++++++-------- .../mars-mock-credit-manager.json | 20 +++- .../mars-oracle-adapter.json | 110 ++++-------------- .../mars-swapper-base/mars-swapper-base.json | 42 +++---- .../mars-account-nft/MarsAccountNft.client.ts | 7 ++ .../mars-account-nft/MarsAccountNft.types.ts | 3 + .../MarsCreditManager.types.ts | 17 +-- .../MarsMockCreditManager.types.ts | 3 +- .../MarsOracleAdapter.client.ts | 14 +-- .../MarsOracleAdapter.message-composer.ts | 4 +- .../MarsOracleAdapter.react-query.ts | 33 ------ .../MarsOracleAdapter.types.ts | 18 +-- .../MarsSwapperBase.client.ts | 28 ++--- .../MarsSwapperBase.message-composer.ts | 20 ++-- .../MarsSwapperBase.react-query.ts | 33 +++--- .../MarsSwapperBase.types.ts | 12 +- 58 files changed, 460 insertions(+), 603 deletions(-) create mode 100644 contracts/account-nft/tests/test_ownership.rs create mode 100644 contracts/swapper/osmosis/tests/test_update_admin.rs delete mode 100644 contracts/swapper/osmosis/tests/test_update_config.rs diff --git a/Cargo.lock b/Cargo.lock index 590ac8014..309de7fa6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -391,6 +391,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-controllers" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343ee0fb235c63a37694b030b48461581d89e119bc37fb4cb456fad90766123b" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw-item-set" version = "0.6.0" @@ -922,6 +937,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vault-standard", + "cw-controllers", "cw-item-set", "cw-multi-test", "cw-storage-plus", @@ -1014,6 +1030,7 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", + "cw-controllers", "cw-multi-test", "cw-storage-plus", "cw-utils", @@ -1050,6 +1067,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vault-standard", + "cw-controllers", "cw-storage-plus", "cw-utils", "mars-health", @@ -1067,6 +1085,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "cw-controllers", "cw-storage-plus", "mars-rover", "schemars", @@ -1094,6 +1113,7 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", + "cw-controllers", "cw-storage-plus", "cw2", "mars-osmosis", diff --git a/Cargo.toml b/Cargo.toml index b1f5095ea..e269f6860 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ cosmwasm-std = "1.1" cw2 = "0.16" cw721 = "0.16" cw721-base = { version = "0.16", features = ["library"] } +cw-controllers = "1.0" cw-item-set = { version = "0.6", default-features = false, features = ["iterator"] } cw-multi-test = "0.16" cw-utils = "0.16" diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index dbeb563a3..01c431fdc 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -10,7 +10,7 @@ use std::convert::TryInto; use crate::config::Config; use crate::error::ContractError; -use crate::execute::{accept_ownership, burn, mint, update_config}; +use crate::execute::{accept_minter_role, burn, mint, update_config}; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::query::{query_config, query_next_id}; use crate::state::{CONFIG, NEXT_ID}; @@ -67,7 +67,7 @@ pub fn execute( match msg { ExecuteMsg::Mint { user } => mint(deps, env, info, &user), ExecuteMsg::UpdateConfig { updates } => update_config(deps, info, updates), - ExecuteMsg::AcceptMinterRole {} => accept_ownership(deps, info), + ExecuteMsg::AcceptMinterRole {} => accept_minter_role(deps, info), ExecuteMsg::Burn { token_id } => burn(deps, env, info, token_id), _ => Parent::default() .execute(deps, env, info, msg.try_into()?) diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index 357a59a8a..c9fc746d2 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -98,7 +98,7 @@ pub fn update_config( Ok(response) } -pub fn accept_ownership(deps: DepsMut, info: MessageInfo) -> Result { +pub fn accept_minter_role(deps: DepsMut, info: MessageInfo) -> Result { let mut config = CONFIG.load(deps.storage)?; let previous_minter = Parent::default().minter.load(deps.storage)?; diff --git a/contracts/account-nft/tests/test_ownership.rs b/contracts/account-nft/tests/test_ownership.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/contracts/account-nft/tests/test_ownership.rs @@ -0,0 +1 @@ + diff --git a/contracts/account-nft/tests/test_update_config.rs b/contracts/account-nft/tests/test_update_config.rs index 1d5be4d9a..b8757cc44 100644 --- a/contracts/account-nft/tests/test_update_config.rs +++ b/contracts/account-nft/tests/test_update_config.rs @@ -20,7 +20,7 @@ fn test_only_minter_can_update_config() { ); if res.is_ok() { - panic!("Non-owner should not be able to propose ownership transfer"); + panic!("Non-minter should not be able to propose new minter"); } } diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index d81bf381c..f5c19039c 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -24,6 +24,7 @@ cosmwasm-vault-standard = { workspace = true } cw2 = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true } +cw-controllers = { workspace = true } cw-item-set = { workspace = true } cw-storage-plus = { workspace = true } mars-account-nft = { workspace = true } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 4392d16dd..f8dbde1d9 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -18,8 +18,9 @@ use crate::instantiate::{ use crate::liquidate_coin::liquidate_coin; use crate::refund::refund_coin_balances; use crate::repay::repay; +use crate::state::ADMIN; use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, SWAPPER, + ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, SWAPPER, VAULT_CONFIGS, ZAPPER, }; use crate::swap::swap_exact_in; @@ -53,14 +54,7 @@ pub fn update_config( info: MessageInfo, new_config: ConfigUpdates, ) -> ContractResult { - let owner = OWNER.load(deps.storage)?; - - if info.sender != owner { - return Err(ContractError::Unauthorized { - user: info.sender.into(), - action: "update config".to_string(), - }); - } + ADMIN.assert_admin(deps.as_ref(), &info.sender)?; let mut response = Response::new().add_attribute("action", "rover/credit-manager/update_config"); @@ -82,14 +76,6 @@ pub fn update_config( .add_attribute("value", addr_str); } - if let Some(addr_str) = new_config.owner { - let validated = deps.api.addr_validate(&addr_str)?; - OWNER.save(deps.storage, &validated)?; - response = response - .add_attribute("key", "owner") - .add_attribute("value", addr_str); - } - if let Some(coins) = new_config.allowed_coins { assert_no_duplicate_coins(&coins)?; ALLOWED_COINS.clear(deps.storage); @@ -151,6 +137,14 @@ pub fn update_config( .add_attribute("value", num.to_string()); } + if let Some(addr_str) = new_config.admin { + let validated = deps.api.addr_validate(&addr_str)?; + ADMIN.set(deps, Some(validated))?; + response = response + .add_attribute("key", "admin") + .add_attribute("value", addr_str); + } + Ok(response) } diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 3ff3c8e7c..16b0b575c 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -7,14 +7,13 @@ use mars_rover::error::ContractResult; use mars_rover::msg::instantiate::VaultInstantiateConfig; use mars_rover::msg::InstantiateMsg; +use crate::state::ADMIN; use crate::state::{ - ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, SWAPPER, + ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, RED_BANK, SWAPPER, VAULT_CONFIGS, ZAPPER, }; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { - let owner = deps.api.addr_validate(&msg.owner)?; - OWNER.save(deps.storage, &owner)?; RED_BANK.save(deps.storage, &msg.red_bank.check(deps.api)?)?; ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; SWAPPER.save(deps.storage, &msg.swapper.check(deps.api)?)?; @@ -38,6 +37,9 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { .iter() .try_for_each(|denom| ALLOWED_COINS.insert(deps.storage, denom).map(|_| ()))?; + let admin = deps.api.addr_validate(&msg.admin)?; + ADMIN.set(deps, Some(admin))?; + Ok(()) } diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index ebd7109e6..7a0bd6bef 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -9,9 +9,10 @@ use mars_rover::msg::query::{ VaultPositionResponseItem, VaultWithBalance, }; +use crate::state::ADMIN; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, - MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_CONFIGS, + MAX_UNLOCKING_POSITIONS, ORACLE, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_CONFIGS, VAULT_POSITIONS, ZAPPER, }; use crate::utils::debt_shares_to_amount; @@ -21,7 +22,7 @@ const DEFAULT_LIMIT: u32 = 10; pub fn query_config(deps: Deps) -> StdResult { Ok(ConfigResponse { - owner: OWNER.load(deps.storage)?.into(), + admin: ADMIN.get(deps)?.map(Into::into), account_nft: ACCOUNT_NFT .may_load(deps.storage)? .map(|addr| addr.to_string()), diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index ae8ae7b96..5d5b42e1c 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,4 +1,5 @@ use cosmwasm_std::{Addr, Decimal, Uint128}; +use cw_controllers::Admin; use cw_item_set::Set; use cw_storage_plus::{Item, Map}; @@ -18,7 +19,7 @@ pub const VAULT_CONFIGS: Map<&Addr, VaultConfig> = Map::new("vault_configs"); pub const ZAPPER: Item = Item::new("zapper"); // Config -pub const OWNER: Item = Item::new("owner"); +pub const ADMIN: Admin = Admin::new("admin"); pub const ALLOWED_COINS: Set<&str> = Set::new("allowed_coins"); pub const MAX_CLOSE_FACTOR: Item = Item::new("max_close_factor"); pub const MAX_UNLOCKING_POSITIONS: Item = Item::new("max_unlocking_positions"); diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 2b1d79e76..1a4167446 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -54,7 +54,7 @@ pub struct MockEnv { pub struct MockEnvBuilder { pub app: BasicApp, - pub owner: Option, + pub admin: Option, pub allowed_vaults: Option>, pub pre_deployed_vaults: Option>, pub allowed_coins: Option>, @@ -62,7 +62,7 @@ pub struct MockEnvBuilder { pub oracle_adapter: Option>, pub red_bank: Option>, pub deploy_nft_contract: bool, - pub set_nft_contract_owner: bool, + pub set_nft_contract_minter: bool, pub accounts_to_fund: Vec, pub max_close_factor: Option, pub max_unlocking_positions: Option, @@ -73,7 +73,7 @@ impl MockEnv { pub fn new() -> MockEnvBuilder { MockEnvBuilder { app: App::default(), - owner: None, + admin: None, allowed_vaults: None, pre_deployed_vaults: None, allowed_coins: None, @@ -81,7 +81,7 @@ impl MockEnv { oracle_adapter: None, red_bank: None, deploy_nft_contract: true, - set_nft_contract_owner: true, + set_nft_contract_minter: true, accounts_to_fund: vec![], max_close_factor: None, max_unlocking_positions: None, @@ -484,7 +484,7 @@ impl MockEnvBuilder { if self.deploy_nft_contract { let nft_contract = deploy_nft_contract(&mut self.app, &nft_minter); - if self.set_nft_contract_owner { + if self.set_nft_contract_minter { propose_new_nft_minter(&mut self.app, nft_contract.clone(), &nft_minter, rover); self.update_config( rover, @@ -500,7 +500,7 @@ impl MockEnvBuilder { pub fn update_config(&mut self, rover: &Addr, new_config: ConfigUpdates) { self.app .execute_contract( - self.get_owner(), + self.get_admin(), rover.clone(), &ExecuteMsg::UpdateConfig { new_config }, &[], @@ -533,9 +533,9 @@ impl MockEnvBuilder { self.app.instantiate_contract( code_id, - self.get_owner(), + self.get_admin(), &InstantiateMsg { - owner: self.get_owner().to_string(), + admin: self.get_admin().to_string(), allowed_coins, allowed_vaults, red_bank, @@ -551,10 +551,10 @@ impl MockEnvBuilder { ) } - fn get_owner(&self) -> Addr { - self.owner + fn get_admin(&self) -> Addr { + self.admin .clone() - .unwrap_or_else(|| Addr::unchecked("owner")) + .unwrap_or_else(|| Addr::unchecked("admin")) } fn get_oracle(&mut self) -> OracleBase { @@ -583,7 +583,7 @@ impl MockEnvBuilder { .app .instantiate_contract( contract_code_id, - Addr::unchecked("oracle_contract_owner"), + Addr::unchecked("oracle_contract_admin"), &OracleInstantiateMsg { prices }, &[], "mock-oracle", @@ -602,7 +602,7 @@ impl MockEnvBuilder { } fn deploy_oracle_adapter(&mut self, vaults: Vec) -> OracleBase { - let owner = Addr::unchecked("oracle_adapter_contract_owner"); + let admin = Addr::unchecked("oracle_adapter_contract_admin"); let contract_code_id = self.app.store_code(mock_oracle_adapter_contract()); let oracle = self.get_oracle().into(); let vault_pricing = if self.pre_deployed_vaults.is_some() { @@ -629,11 +629,11 @@ impl MockEnvBuilder { .app .instantiate_contract( contract_code_id, - owner.clone(), + admin.clone(), &OracleAdapterInstantiateMsg { oracle, vault_pricing, - owner: owner.to_string(), + admin: admin.to_string(), }, &[], "mars-oracle-adapter", @@ -657,7 +657,7 @@ impl MockEnvBuilder { .app .instantiate_contract( contract_code_id, - Addr::unchecked("red_bank_contract_owner"), + Addr::unchecked("red_bank_contract_admin"), &RedBankInstantiateMsg { coins: self .get_allowed_coins() @@ -732,7 +732,7 @@ impl MockEnvBuilder { code_id, Addr::unchecked("swapper-instantiator"), &SwapperInstantiateMsg { - owner: self.get_owner().to_string(), + admin: self.get_admin().to_string(), }, &[], "mock-vault", @@ -822,8 +822,8 @@ impl MockEnvBuilder { self } - pub fn owner(&mut self, owner: &str) -> &mut Self { - self.owner = Some(Addr::unchecked(owner)); + pub fn admin(&mut self, admin: &str) -> &mut Self { + self.admin = Some(Addr::unchecked(admin)); self } @@ -852,8 +852,8 @@ impl MockEnvBuilder { self } - pub fn no_nft_contract_owner(&mut self) -> &mut Self { - self.set_nft_contract_owner = false; + pub fn no_nft_contract_minter(&mut self) -> &mut Self { + self.set_nft_contract_minter = false; self } diff --git a/contracts/credit-manager/tests/test_create_credit_account.rs b/contracts/credit-manager/tests/test_create_credit_account.rs index f5dce11d0..258df22a5 100644 --- a/contracts/credit-manager/tests/test_create_credit_account.rs +++ b/contracts/credit-manager/tests/test_create_credit_account.rs @@ -18,7 +18,7 @@ fn test_create_credit_account_fails_without_nft_contract_set() { #[test] fn test_create_credit_account_fails_without_nft_contract_owner() { - let mut mock = MockEnv::new().no_nft_contract_owner().build().unwrap(); + let mut mock = MockEnv::new().no_nft_contract_minter().build().unwrap(); let user = Addr::unchecked("user"); let res = mock.create_credit_account(&user); diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index fadc25584..e4e51a4e6 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -643,7 +643,7 @@ fn test_delisted_assets_drop_max_ltv() { // Remove uosmo from the coin whitelist let res = mock.query_config(); mock.update_config( - &Addr::unchecked(res.owner), + &Addr::unchecked(res.admin.unwrap()), ConfigUpdates { allowed_coins: Some(vec![uatom_info.denom]), ..Default::default() diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 9fd4e92f4..d9f483640 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -8,17 +8,17 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_owner_set_on_instantiate() { - let owner = "owner_addr"; - let mock = MockEnv::new().owner(owner).build().unwrap(); +fn test_admin_set_on_instantiate() { + let admin = "admin_addr"; + let mock = MockEnv::new().admin(admin).build().unwrap(); let res = mock.query_config(); - assert_eq!(owner, res.owner); + assert_eq!(admin, res.admin.unwrap()); } #[test] -fn test_raises_on_invalid_owner_addr() { - let owner = "%%%INVALID%%%"; - let res = MockEnv::new().owner(owner).build(); +fn test_raises_on_invalid_admin_addr() { + let admin = "%%%INVALID%%%"; + let res = MockEnv::new().admin(admin).build(); if res.is_ok() { panic!("Should have thrown an error"); } diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 8b154afef..e73a6dcc1 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -11,15 +11,15 @@ use crate::helpers::{assert_err, locked_vault_info, uatom_info, uosmo_info, Mock pub mod helpers; #[test] -fn test_only_owner_can_update_config() { +fn test_only_admin_can_update_config() { let mut mock = MockEnv::new().build().unwrap(); - let new_owner = Addr::unchecked("bad_guy"); + let new_admin = Addr::unchecked("bad_guy"); let res = mock.update_config( - &new_owner, + &new_admin, ConfigUpdates { account_nft: None, - owner: Some(new_owner.to_string()), + admin: Some(new_admin.to_string()), allowed_coins: None, oracle: None, max_close_factor: None, @@ -31,7 +31,7 @@ fn test_only_owner_can_update_config() { ); if res.is_ok() { - panic!("only owner should be able to update config"); + panic!("only admin should be able to update config"); } } @@ -40,10 +40,10 @@ fn test_raises_on_invalid_vaults_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( - &Addr::unchecked(original_config.owner.clone()), + &Addr::unchecked(original_config.admin.clone().unwrap()), ConfigUpdates { account_nft: None, - owner: None, + admin: None, allowed_coins: None, oracle: None, max_close_factor: None, @@ -70,10 +70,10 @@ fn test_raises_on_invalid_vaults_config() { ); let res = mock.update_config( - &Addr::unchecked(original_config.owner), + &Addr::unchecked(original_config.admin.unwrap()), ConfigUpdates { account_nft: None, - owner: None, + admin: None, allowed_coins: None, oracle: None, max_close_factor: None, @@ -108,7 +108,7 @@ fn test_update_config_works_with_full_config() { let original_vault_configs = mock.query_vault_configs(None, None); let new_nft_contract = mock.deploy_new_nft_contract().unwrap(); - let new_owner = Addr::unchecked("new_owner"); + let new_admin = Addr::unchecked("new_admin"); let new_vault_configs = vec![VaultInstantiateConfig { vault: VaultBase::new("vault_contract_3000".to_string()), config: VaultConfig { @@ -126,10 +126,10 @@ fn test_update_config_works_with_full_config() { let new_swapper = SwapperBase::new("new_swapper".to_string()); mock.update_config( - &Addr::unchecked(original_config.owner.clone()), + &Addr::unchecked(original_config.admin.clone().unwrap()), ConfigUpdates { account_nft: Some(new_nft_contract.to_string()), - owner: Some(new_owner.to_string()), + admin: Some(new_admin.to_string()), allowed_coins: Some(new_allowed_coins.clone()), oracle: Some(new_oracle.clone()), max_close_factor: Some(new_close_factor), @@ -148,8 +148,8 @@ fn test_update_config_works_with_full_config() { assert_eq!(new_config.account_nft, Some(new_nft_contract.to_string())); assert_ne!(new_config.account_nft, original_config.account_nft); - assert_eq!(new_config.owner, new_owner.to_string()); - assert_ne!(new_config.owner, original_config.owner); + assert_eq!(new_config.admin.clone().unwrap(), new_admin); + assert_ne!(new_config.admin, original_config.admin); assert_eq!(new_queried_vault_configs, new_vault_configs); assert_ne!(new_queried_vault_configs, original_vault_configs); @@ -198,7 +198,7 @@ fn test_update_config_works_with_some_config() { }]; mock.update_config( - &Addr::unchecked(original_config.owner.clone()), + &Addr::unchecked(original_config.admin.clone().unwrap()), ConfigUpdates { account_nft: Some(new_nft_contract.to_string()), vault_configs: Some(new_vault_configs.clone()), @@ -219,7 +219,7 @@ fn test_update_config_works_with_some_config() { assert_ne!(new_queried_vault_configs, original_vault_configs); // Unchanged configs - assert_eq!(new_config.owner, original_config.owner); + assert_eq!(new_config.admin, original_config.admin); assert_eq!(original_allowed_coins, new_queried_allowed_coins); assert_eq!(new_config.red_bank, original_config.red_bank); } @@ -243,7 +243,7 @@ fn test_update_config_removes_properly() { assert_eq!(vault_configs.len(), 1); mock.update_config( - &Addr::unchecked(mock.query_config().owner), + &Addr::unchecked(mock.query_config().admin.unwrap()), ConfigUpdates { allowed_coins: Some(vec![]), vault_configs: Some(vec![]), @@ -268,7 +268,7 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { let original_allowed_coins = mock.query_allowed_coins(None, None); mock.update_config( - &Addr::unchecked(original_config.owner.clone()), + &Addr::unchecked(original_config.admin.clone().unwrap()), Default::default(), ) .unwrap(); @@ -278,7 +278,7 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { let new_queried_allowed_coins = mock.query_allowed_coins(None, None); assert_eq!(new_config.account_nft, original_config.account_nft); - assert_eq!(new_config.owner, original_config.owner); + assert_eq!(new_config.admin, original_config.admin); assert_eq!(new_queried_vault_configs, original_vault_configs); assert_eq!(new_queried_allowed_coins, original_allowed_coins); assert_eq!(new_config.red_bank, original_config.red_bank); @@ -296,7 +296,7 @@ fn test_max_close_factor_validated_on_update() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( - &Addr::unchecked(original_config.owner), + &Addr::unchecked(original_config.admin.unwrap()), ConfigUpdates { max_close_factor: Some(Decimal::from_atomics(42u128, 1).unwrap()), ..Default::default() @@ -316,10 +316,10 @@ fn test_raises_on_duplicate_vault_configs() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( - &Addr::unchecked(original_config.owner), + &Addr::unchecked(original_config.admin.unwrap()), ConfigUpdates { account_nft: None, - owner: None, + admin: None, allowed_coins: None, oracle: None, max_close_factor: None, @@ -362,10 +362,10 @@ fn test_raises_on_duplicate_coin_configs() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( - &Addr::unchecked(original_config.owner), + &Addr::unchecked(original_config.admin.unwrap()), ConfigUpdates { account_nft: None, - owner: None, + admin: None, allowed_coins: Some(vec![ "uosmo".to_string(), "uatom".to_string(), diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index 1a3e45187..44eb33bf5 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -93,7 +93,7 @@ fn test_coins_out_must_be_whitelisted() { // update config to disallow denoms out let config = mock.query_config(); mock.update_config( - &Addr::unchecked(config.owner), + &Addr::unchecked(config.admin.unwrap()), ConfigUpdates { allowed_coins: Some(vec![lp_token.denom.clone(), atom.denom]), ..Default::default() diff --git a/contracts/oracle-adapter/Cargo.toml b/contracts/oracle-adapter/Cargo.toml index e9bd37ffa..31340808e 100644 --- a/contracts/oracle-adapter/Cargo.toml +++ b/contracts/oracle-adapter/Cargo.toml @@ -21,6 +21,7 @@ library = [] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw2 = { workspace = true } +cw-controllers = { workspace = true } cw-storage-plus = { workspace = true } mars-outpost = { workspace = true } mars-rover = { workspace = true } diff --git a/contracts/oracle-adapter/src/contract.rs b/contracts/oracle-adapter/src/contract.rs index 779a7cb74..9cb62cf81 100644 --- a/contracts/oracle-adapter/src/contract.rs +++ b/contracts/oracle-adapter/src/contract.rs @@ -11,12 +11,12 @@ use mars_rover::adapters::vault::VaultBase; use mars_rover::adapters::Oracle; use mars_rover::traits::IntoDecimal; -use crate::error::{ContractError, ContractResult}; +use crate::error::ContractResult; use crate::msg::{ ConfigResponse, ConfigUpdates, ExecuteMsg, InstantiateMsg, PricingMethod, QueryMsg, VaultPricingInfo, }; -use crate::state::{ORACLE, OWNER, VAULT_PRICING_INFO}; +use crate::state::{ADMIN, ORACLE, VAULT_PRICING_INFO}; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -37,9 +37,6 @@ pub fn instantiate( CONTRACT_VERSION, )?; - let owner = deps.api.addr_validate(&msg.owner)?; - OWNER.save(deps.storage, &owner)?; - let oracle = msg.oracle.check(deps.api)?; ORACLE.save(deps.storage, &oracle)?; @@ -47,6 +44,9 @@ pub fn instantiate( VAULT_PRICING_INFO.save(deps.storage, &info.vault_coin_denom, &info)?; } + let admin = deps.api.addr_validate(&msg.admin)?; + ADMIN.set(deps, Some(admin))?; + Ok(Response::default()) } @@ -140,7 +140,7 @@ fn calculate_preview_redeem( fn query_config(deps: Deps) -> ContractResult { Ok(ConfigResponse { - owner: OWNER.load(deps.storage)?, + admin: ADMIN.get(deps)?, oracle: ORACLE.load(deps.storage)?, }) } @@ -150,26 +150,11 @@ pub fn update_config( info: MessageInfo, new_config: ConfigUpdates, ) -> ContractResult { - let owner = OWNER.load(deps.storage)?; - - if info.sender != owner { - return Err(ContractError::Unauthorized { - user: info.sender.into(), - action: "update config".to_string(), - }); - } + ADMIN.assert_admin(deps.as_ref(), &info.sender)?; let mut response = Response::new().add_attribute("action", "rover/oracle-adapter/update_config"); - if let Some(addr_str) = new_config.owner { - let validated = deps.api.addr_validate(&addr_str)?; - OWNER.save(deps.storage, &validated)?; - response = response - .add_attribute("key", "owner") - .add_attribute("value", addr_str); - } - if let Some(unchecked) = new_config.oracle { ORACLE.save(deps.storage, &unchecked.check(deps.api)?)?; response = response @@ -196,5 +181,13 @@ pub fn update_config( .add_attribute("value", value_str); } + if let Some(addr_str) = new_config.admin { + let validated = deps.api.addr_validate(&addr_str)?; + ADMIN.set(deps, Some(validated))?; + response = response + .add_attribute("key", "owner") + .add_attribute("value", addr_str); + } + Ok(response) } diff --git a/contracts/oracle-adapter/src/error.rs b/contracts/oracle-adapter/src/error.rs index 9ae38f531..c123289ba 100644 --- a/contracts/oracle-adapter/src/error.rs +++ b/contracts/oracle-adapter/src/error.rs @@ -1,4 +1,5 @@ use cosmwasm_std::{CheckedFromRatioError, DecimalRangeExceeded, OverflowError, StdError}; +use cw_controllers::AdminError; use thiserror::Error; use mars_rover::error::ContractError as RoverError; @@ -7,6 +8,9 @@ pub type ContractResult = Result; #[derive(Error, Debug, PartialEq)] pub enum ContractError { + #[error("{0}")] + AdminError(#[from] AdminError), + #[error("{0}")] CheckedFromRatioError(#[from] CheckedFromRatioError), diff --git a/contracts/oracle-adapter/src/msg.rs b/contracts/oracle-adapter/src/msg.rs index 8117c1704..eabd360fe 100644 --- a/contracts/oracle-adapter/src/msg.rs +++ b/contracts/oracle-adapter/src/msg.rs @@ -13,7 +13,7 @@ pub struct CoinPrice { pub struct InstantiateMsg { pub oracle: OracleUnchecked, pub vault_pricing: Vec, - pub owner: String, + pub admin: String, } #[cw_serde] @@ -43,14 +43,14 @@ pub enum QueryMsg { #[cw_serde] pub struct ConfigResponse { - pub owner: Addr, + pub admin: Option, pub oracle: Oracle, } #[cw_serde] #[derive(Default)] pub struct ConfigUpdates { - pub owner: Option, + pub admin: Option, pub oracle: Option, pub vault_pricing: Option>, } diff --git a/contracts/oracle-adapter/src/state.rs b/contracts/oracle-adapter/src/state.rs index 4508a0e40..28f1dedea 100644 --- a/contracts/oracle-adapter/src/state.rs +++ b/contracts/oracle-adapter/src/state.rs @@ -1,10 +1,10 @@ -use cosmwasm_std::Addr; +use cw_controllers::Admin; use cw_storage_plus::{Item, Map}; use mars_rover::adapters::Oracle; use crate::msg::VaultPricingInfo; -pub const OWNER: Item = Item::new("owner"); +pub const ADMIN: Admin = Admin::new("admin"); pub const ORACLE: Item = Item::new("oracle"); /// Map<(Vault Token Denom, Pricing Method)> diff --git a/contracts/oracle-adapter/tests/helpers.rs b/contracts/oracle-adapter/tests/helpers.rs index 39273ba92..fc8c145f3 100644 --- a/contracts/oracle-adapter/tests/helpers.rs +++ b/contracts/oracle-adapter/tests/helpers.rs @@ -42,7 +42,7 @@ pub fn instantiate_oracle_adapter(app: &mut BasicApp) -> Addr { &InstantiateMsg { oracle: oracle.into(), vault_pricing: vec![vault_pricing_info], - owner: owner.to_string(), + admin: owner.to_string(), }, &[], "mars-oracle-adapter", diff --git a/contracts/oracle-adapter/tests/test_update_config.rs b/contracts/oracle-adapter/tests/test_update_config.rs index c24fd5023..ac19e8150 100644 --- a/contracts/oracle-adapter/tests/test_update_config.rs +++ b/contracts/oracle-adapter/tests/test_update_config.rs @@ -1,7 +1,8 @@ use cosmwasm_std::Addr; +use cw_controllers::AdminError::NotAdmin; use cw_multi_test::{App, Executor}; -use mars_oracle_adapter::error::ContractError; +use mars_oracle_adapter::error::ContractError::AdminError; use mars_oracle_adapter::msg::{ ConfigResponse, ConfigUpdates, ExecuteMsg, QueryMsg, VaultPricingInfo, }; @@ -12,13 +13,13 @@ use crate::helpers::{assert_err, instantiate_oracle_adapter}; pub mod helpers; #[test] -fn test_only_owner_can_update_config() { +fn test_only_admin_can_update_config() { let mut app = App::default(); let contract_addr = instantiate_oracle_adapter(&mut app); let bad_guy = Addr::unchecked("bad_guy"); let res = app.execute_contract( - bad_guy.clone(), + bad_guy, contract_addr, &ExecuteMsg::UpdateConfig { new_config: Default::default(), @@ -26,13 +27,7 @@ fn test_only_owner_can_update_config() { &[], ); - assert_err( - res, - ContractError::Unauthorized { - user: bad_guy.to_string(), - action: "update config".to_string(), - }, - ); + assert_err(res, AdminError(NotAdmin {})); } #[test] @@ -49,11 +44,11 @@ fn test_update_config_works_with_full_config() { let new_vault_pricing = vec![]; app.execute_contract( - original_config.owner.clone(), + original_config.admin.clone().unwrap(), contract_addr.clone(), &ExecuteMsg::UpdateConfig { new_config: ConfigUpdates { - owner: Some(new_owner.to_string()), + admin: Some(new_owner.to_string()), oracle: Some(new_oracle), vault_pricing: Some(new_vault_pricing), }, @@ -67,8 +62,8 @@ fn test_update_config_works_with_full_config() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - assert_ne!(new_config.owner, original_config.owner); - assert_eq!(new_config.owner, new_owner.to_string()); + assert_ne!(new_config.admin, original_config.admin); + assert_eq!(new_config.admin, Some(new_owner)); assert_ne!(new_config.oracle, original_config.oracle); assert_eq!( @@ -111,11 +106,11 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { .unwrap(); app.execute_contract( - original_config.owner.clone(), + original_config.admin.clone().unwrap(), contract_addr.clone(), &ExecuteMsg::UpdateConfig { new_config: ConfigUpdates { - owner: None, + admin: None, oracle: None, vault_pricing: None, }, @@ -129,7 +124,7 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - assert_eq!(new_config.owner, original_config.owner); + assert_eq!(new_config.admin, original_config.admin); assert_eq!(new_config.oracle, original_config.oracle); let new_pricing_infos: Vec = app diff --git a/contracts/swapper/base/Cargo.toml b/contracts/swapper/base/Cargo.toml index b9cf528ae..78941c051 100644 --- a/contracts/swapper/base/Cargo.toml +++ b/contracts/swapper/base/Cargo.toml @@ -20,6 +20,7 @@ library = [] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw-controllers = { workspace = true } cw-storage-plus = { workspace = true } mars-rover = { workspace = true } schemars = { workspace = true } diff --git a/contracts/swapper/base/src/contract.rs b/contracts/swapper/base/src/contract.rs index 6e2329a7f..de0facc3a 100644 --- a/contracts/swapper/base/src/contract.rs +++ b/contracts/swapper/base/src/contract.rs @@ -4,10 +4,11 @@ use cosmwasm_std::{ to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, CustomMsg, CustomQuery, Decimal, Deps, DepsMut, Env, MessageInfo, Order, Response, WasmMsg, }; -use cw_storage_plus::{Bound, Item, Map}; +use cw_controllers::{Admin, AdminResponse}; +use cw_storage_plus::{Bound, Map}; use mars_rover::adapters::swap::{ - Config, EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg, RouteResponse, + EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg, RouteResponse, RoutesResponse, }; use mars_rover::error::ContractError as RoverError; @@ -23,8 +24,8 @@ where M: CustomMsg, R: Route, { - /// The contract's config - pub config: Item<'a, Config>, + /// The contract's admin who has special rights to update contract + pub admin: Admin<'a>, /// The trade route for each pair of input/output assets pub routes: Map<'a, (String, String), R>, /// Phantom data holds generics @@ -40,7 +41,7 @@ where { fn default() -> Self { Self { - config: Item::new("config"), + admin: Admin::new("admin"), routes: Map::new("routes"), custom_query: PhantomData, custom_message: PhantomData, @@ -59,13 +60,8 @@ where deps: DepsMut, msg: InstantiateMsg, ) -> ContractResult> { - self.config.save( - deps.storage, - &Config { - owner: deps.api.addr_validate(&msg.owner)?, - }, - )?; - + let validated = deps.api.addr_validate(&msg.admin)?; + self.admin.set(deps, Some(validated))?; Ok(Response::default()) } @@ -77,7 +73,7 @@ where msg: ExecuteMsg, ) -> ContractResult> { match msg { - ExecuteMsg::UpdateConfig { owner } => self.update_config(deps, info.sender, owner), + ExecuteMsg::UpdateAdmin { admin } => self.update_admin(deps, info.sender, &admin), ExecuteMsg::SetRoute { denom_in, denom_out, @@ -98,7 +94,7 @@ where pub fn query(&self, deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { let res = match msg { - QueryMsg::Config {} => to_binary(&self.query_config(deps)?), + QueryMsg::Admin {} => to_binary(&self.query_admin(deps)?), QueryMsg::EstimateExactInSwap { coin_in, denom_out } => { to_binary(&self.estimate_exact_in_swap(deps, env, coin_in, denom_out)?) } @@ -113,11 +109,8 @@ where res.map_err(Into::into) } - fn query_config(&self, deps: Deps) -> ContractResult> { - let cfg = self.config.load(deps.storage)?; - Ok(Config { - owner: cfg.owner.to_string(), - }) + fn query_admin(&self, deps: Deps) -> ContractResult { + Ok(self.admin.query_admin(deps)?) } fn query_route( @@ -251,15 +244,7 @@ where denom_out: String, route: R, ) -> ContractResult> { - let cfg = self.config.load(deps.storage)?; - - if sender != cfg.owner { - return Err(RoverError::Unauthorized { - user: sender.to_string(), - action: "set route".to_string(), - } - .into()); - }; + self.admin.assert_admin(deps.as_ref(), &sender)?; route.validate(&deps.querier, &denom_in, &denom_out)?; @@ -273,33 +258,19 @@ where .add_attribute("route", route.to_string())) } - fn update_config( + fn update_admin( &self, deps: DepsMut, sender: Addr, - owner: Option, + admin: &str, ) -> ContractResult> { - let mut cfg = self.config.load(deps.storage)?; - if sender != cfg.owner { - return Err(RoverError::Unauthorized { - user: sender.to_string(), - action: "update owner".to_string(), - } - .into()); - }; - - let mut response = - Response::new().add_attribute("action", "rover/swapper-base/update_config"); + self.admin.assert_admin(deps.as_ref(), &sender)?; + let validated = deps.api.addr_validate(admin)?; + self.admin.set(deps, Some(validated))?; - if let Some(addr_str) = owner { - cfg.owner = deps.api.addr_validate(&addr_str)?; - response = response - .add_attribute("key", "owner") - .add_attribute("value", addr_str); - } - - self.config.save(deps.storage, &cfg)?; - - Ok(response) + Ok(Response::new() + .add_attribute("action", "rover/swapper-base/update_admin") + .add_attribute("key", "owner") + .add_attribute("value", admin)) } } diff --git a/contracts/swapper/base/src/error.rs b/contracts/swapper/base/src/error.rs index 38bafe87f..c2a79cec7 100644 --- a/contracts/swapper/base/src/error.rs +++ b/contracts/swapper/base/src/error.rs @@ -1,9 +1,13 @@ use cosmwasm_std::{CheckedMultiplyRatioError, DecimalRangeExceeded, OverflowError, StdError}; +use cw_controllers::AdminError; use mars_rover::error::ContractError as RoverError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] pub enum ContractError { + #[error("{0}")] + AdminError(#[from] AdminError), + #[error("{0}")] DecimalRangeExceeded(#[from] DecimalRangeExceeded), diff --git a/contracts/swapper/mock/src/contract.rs b/contracts/swapper/mock/src/contract.rs index f5ff04668..75e49ab94 100644 --- a/contracts/swapper/mock/src/contract.rs +++ b/contracts/swapper/mock/src/contract.rs @@ -27,7 +27,7 @@ pub fn execute( msg: ExecuteMsg, ) -> StdResult { match msg { - ExecuteMsg::UpdateConfig { .. } => unimplemented!("not implemented"), + ExecuteMsg::UpdateAdmin { .. } => unimplemented!("not implemented"), ExecuteMsg::SetRoute { .. } => unimplemented!("not implemented"), ExecuteMsg::TransferResult { .. } => unimplemented!("not implemented"), ExecuteMsg::SwapExactIn { @@ -41,7 +41,7 @@ pub fn execute( #[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::Config { .. } => unimplemented!("not implemented"), + QueryMsg::Admin { .. } => unimplemented!("not implemented"), QueryMsg::Route { .. } => unimplemented!("not implemented"), QueryMsg::Routes { .. } => unimplemented!("not implemented"), QueryMsg::EstimateExactInSwap { .. } => to_binary(&estimate_exact_in_swap()), diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index c851004b5..0f2750b35 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -21,6 +21,7 @@ library = [] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw2 = { workspace = true } +cw-controllers = { workspace = true } cw-storage-plus = { workspace = true } mars-osmosis = { version = "1.0.0", path = "../../../packages/chains/osmosis" } mars-swapper-base = { workspace = true } diff --git a/contracts/swapper/osmosis/tests/helpers.rs b/contracts/swapper/osmosis/tests/helpers.rs index 150a95b9e..3f8b999d8 100644 --- a/contracts/swapper/osmosis/tests/helpers.rs +++ b/contracts/swapper/osmosis/tests/helpers.rs @@ -21,10 +21,10 @@ pub fn wasm_file() -> String { format!("../../../{}/{}.wasm", artifacts_dir, snaked_name) } -pub fn instantiate_contract(wasm: &Wasm, owner: &SigningAccount) -> String { +pub fn instantiate_contract(wasm: &Wasm, admin: &SigningAccount) -> String { let wasm_byte_code = std::fs::read(wasm_file()).unwrap(); let code_id = wasm - .store_code(&wasm_byte_code, None, owner) + .store_code(&wasm_byte_code, None, admin) .unwrap() .data .code_id; @@ -32,12 +32,12 @@ pub fn instantiate_contract(wasm: &Wasm, owner: &SigningAccount) wasm.instantiate( code_id, &InstantiateMsg { - owner: owner.address(), + admin: admin.address(), }, None, Some("swapper-osmosis-contract"), &[], - owner, + admin, ) .unwrap() .data diff --git a/contracts/swapper/osmosis/tests/test_estimate.rs b/contracts/swapper/osmosis/tests/test_estimate.rs index 8d65af0a2..25509245d 100644 --- a/contracts/swapper/osmosis/tests/test_estimate.rs +++ b/contracts/swapper/osmosis/tests/test_estimate.rs @@ -15,11 +15,11 @@ pub mod helpers; fn test_error_on_route_not_found() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let owner = app + let admin = app .init_account(&[coin(1_000_000_000_000, "uosmo")]) .unwrap(); - let contract_addr = instantiate_contract(&wasm, &owner); + let contract_addr = instantiate_contract(&wasm, &admin); let res: RunnerResult = wasm.query( &contract_addr, diff --git a/contracts/swapper/osmosis/tests/test_instantiate.rs b/contracts/swapper/osmosis/tests/test_instantiate.rs index 28c32d8fc..954c33378 100644 --- a/contracts/swapper/osmosis/tests/test_instantiate.rs +++ b/contracts/swapper/osmosis/tests/test_instantiate.rs @@ -1,14 +1,15 @@ use cosmwasm_std::coin; +use cw_controllers::AdminResponse; use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; -use mars_rover::adapters::swap::{Config, InstantiateMsg, QueryMsg}; +use mars_rover::adapters::swap::{InstantiateMsg, QueryMsg}; use crate::helpers::{instantiate_contract, wasm_file}; pub mod helpers; #[test] -fn test_owner_set_on_instantiate() { +fn test_admin_set_on_instantiate() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let signer = app @@ -17,12 +18,12 @@ fn test_owner_set_on_instantiate() { let contract_addr = instantiate_contract(&wasm, &signer); - let config: Config = wasm.query(&contract_addr, &QueryMsg::Config {}).unwrap(); - assert_eq!(config.owner, signer.address()); + let res: AdminResponse = wasm.query(&contract_addr, &QueryMsg::Admin {}).unwrap(); + assert_eq!(res.admin, Some(signer.address())); } #[test] -fn test_raises_on_invalid_owner_addr() { +fn test_raises_on_invalid_admin_addr() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let signer = app @@ -36,11 +37,11 @@ fn test_raises_on_invalid_owner_addr() { .data .code_id; - let owner = "%%%INVALID%%%"; + let admin = "%%%INVALID%%%"; let res = wasm.instantiate( code_id, &InstantiateMsg { - owner: owner.to_string(), + admin: admin.to_string(), }, None, Some("swapper-osmosis-contract"), diff --git a/contracts/swapper/osmosis/tests/test_set_route.rs b/contracts/swapper/osmosis/tests/test_set_route.rs index 9b5b23244..93eeabe06 100644 --- a/contracts/swapper/osmosis/tests/test_set_route.rs +++ b/contracts/swapper/osmosis/tests/test_set_route.rs @@ -1,10 +1,10 @@ use cosmwasm_std::coin; use cosmwasm_std::StdError::GenericErr; +use cw_controllers::AdminError; use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; -use osmosis_testing::{Account, Gamm, Module, OsmosisTestApp, Wasm}; +use osmosis_testing::{Gamm, Module, OsmosisTestApp, Wasm}; use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; -use mars_rover::error::ContractError as RoverError; use mars_swapper_base::ContractError; use mars_swapper_osmosis::route::OsmosisRoute; @@ -13,17 +13,17 @@ use crate::helpers::{assert_err, instantiate_contract}; pub mod helpers; #[test] -fn test_only_owner_can_set_routes() { +fn test_only_admin_can_set_routes() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let accs = app .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) .unwrap(); - let owner = &accs[0]; + let admin = &accs[0]; let bad_guy = &accs[1]; - let contract_addr = instantiate_contract(&wasm, owner); + let contract_addr = instantiate_contract(&wasm, admin); let res_err = wasm .execute( @@ -47,13 +47,7 @@ fn test_only_owner_can_set_routes() { ) .unwrap_err(); - assert_err( - res_err, - ContractError::Rover(RoverError::Unauthorized { - user: bad_guy.address(), - action: "set route".to_string(), - }), - ); + assert_err(res_err, AdminError::NotAdmin {}); } #[test] diff --git a/contracts/swapper/osmosis/tests/test_swap.rs b/contracts/swapper/osmosis/tests/test_swap.rs index 7bc49aa0e..c7396ca42 100644 --- a/contracts/swapper/osmosis/tests/test_swap.rs +++ b/contracts/swapper/osmosis/tests/test_swap.rs @@ -21,10 +21,10 @@ fn test_transfer_callback_only_internal() { let accs = app .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) .unwrap(); - let owner = &accs[0]; + let admin = &accs[0]; let bad_guy = &accs[1]; - let contract_addr = instantiate_contract(&wasm, owner); + let contract_addr = instantiate_contract(&wasm, admin); let res_err = wasm .execute( diff --git a/contracts/swapper/osmosis/tests/test_update_admin.rs b/contracts/swapper/osmosis/tests/test_update_admin.rs new file mode 100644 index 000000000..c70c2e99c --- /dev/null +++ b/contracts/swapper/osmosis/tests/test_update_admin.rs @@ -0,0 +1,64 @@ +use cosmwasm_std::coin; +use cw_controllers::{AdminError, AdminResponse}; +use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; + +use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg}; +use mars_swapper_osmosis::route::OsmosisRoute; + +use crate::helpers::{assert_err, instantiate_contract}; + +pub mod helpers; + +#[test] +fn test_only_admin_can_update_admin() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let accs = app + .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) + .unwrap(); + let admin = &accs[0]; + let bad_guy = &accs[1]; + + let contract_addr = instantiate_contract(&wasm, admin); + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::::UpdateAdmin { + admin: bad_guy.address(), + }, + &[], + bad_guy, + ) + .unwrap_err(); + + assert_err(res_err, AdminError::NotAdmin {}); +} + +#[test] +fn test_update_admin_works_with_full_config() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let accs = app + .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) + .unwrap(); + let admin = &accs[0]; + let new_admin = &accs[1]; + + let contract_addr = instantiate_contract(&wasm, admin); + + wasm.execute( + &contract_addr, + &ExecuteMsg::::UpdateAdmin { + admin: new_admin.address(), + }, + &[], + admin, + ) + .unwrap(); + + let res: AdminResponse = wasm.query(&contract_addr, &QueryMsg::Admin {}).unwrap(); + assert_eq!(res.admin, Some(new_admin.address())); +} diff --git a/contracts/swapper/osmosis/tests/test_update_config.rs b/contracts/swapper/osmosis/tests/test_update_config.rs deleted file mode 100644 index 4ddcf96ca..000000000 --- a/contracts/swapper/osmosis/tests/test_update_config.rs +++ /dev/null @@ -1,93 +0,0 @@ -use cosmwasm_std::coin; -use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; - -use mars_rover::adapters::swap::{Config, ExecuteMsg, QueryMsg}; -use mars_rover::error::ContractError as RoverError; -use mars_swapper_base::ContractError; -use mars_swapper_osmosis::route::OsmosisRoute; - -use crate::helpers::{assert_err, instantiate_contract}; - -pub mod helpers; - -#[test] -fn test_only_owner_can_update_config() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let accs = app - .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) - .unwrap(); - let owner = &accs[0]; - let bad_guy = &accs[1]; - - let contract_addr = instantiate_contract(&wasm, owner); - - let res_err = wasm - .execute( - &contract_addr, - &ExecuteMsg::::UpdateConfig { - owner: Some(bad_guy.address()), - }, - &[], - bad_guy, - ) - .unwrap_err(); - - assert_err( - res_err, - ContractError::Rover(RoverError::Unauthorized { - user: bad_guy.address(), - action: "update owner".to_string(), - }), - ); -} - -#[test] -fn test_update_config_works_with_full_config() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let accs = app - .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) - .unwrap(); - let owner = &accs[0]; - let new_owner = &accs[1]; - - let contract_addr = instantiate_contract(&wasm, owner); - - wasm.execute( - &contract_addr, - &ExecuteMsg::::UpdateConfig { - owner: Some(new_owner.address()), - }, - &[], - owner, - ) - .unwrap(); - - let config: Config = wasm.query(&contract_addr, &QueryMsg::Config {}).unwrap(); - assert_eq!(config.owner, new_owner.address()); -} - -#[test] -fn test_update_config_does_nothing_when_nothing_is_passed() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - let owner = app - .init_account(&[coin(1_000_000_000_000, "uosmo")]) - .unwrap(); - - let contract_addr = instantiate_contract(&wasm, &owner); - - wasm.execute( - &contract_addr, - &ExecuteMsg::::UpdateConfig { owner: None }, - &[], - &owner, - ) - .unwrap(); - - let config: Config = wasm.query(&contract_addr, &QueryMsg::Config {}).unwrap(); - assert_eq!(config.owner, owner.address()); -} diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 0b9ea87d5..7a0686be7 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -20,6 +20,7 @@ backtraces = ["cosmwasm-std/backtraces"] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cosmwasm-vault-standard = { workspace = true } +cw-controllers = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } mars-health = { workspace = true } diff --git a/packages/rover/src/adapters/swap/msgs.rs b/packages/rover/src/adapters/swap/msgs.rs index 4f9727661..6d0bfac94 100644 --- a/packages/rover/src/adapters/swap/msgs.rs +++ b/packages/rover/src/adapters/swap/msgs.rs @@ -2,17 +2,15 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; #[cw_serde] -pub struct Config { - /// The contract's owner, who can update config - pub owner: T, +pub struct InstantiateMsg { + /// The contract's admin, who can update config + pub admin: String, } -pub type InstantiateMsg = Config; - #[cw_serde] pub enum ExecuteMsg { - /// Update contract config - UpdateConfig { owner: Option }, + /// Update contract admin + UpdateAdmin { admin: String }, /// Configure the route for swapping an asset /// /// This is chain-specific, and can include parameters such as slippage tolerance and the routes @@ -40,8 +38,8 @@ pub enum ExecuteMsg { #[derive(QueryResponses)] pub enum QueryMsg { /// Query contract config - #[returns(Config)] - Config {}, + #[returns(cw_controllers::AdminResponse)] + Admin {}, /// Get route for swapping an input denom into an output denom #[returns(RouteResponse)] Route { denom_in: String, denom_out: String }, diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 3a222563a..43cfe0f0a 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -2,6 +2,7 @@ use cosmwasm_std::{ CheckedFromRatioError, CheckedMultiplyRatioError, DecimalRangeExceeded, OverflowError, StdError, Uint128, }; +use cw_controllers::AdminError; use thiserror::Error; use crate::coins::Coins; @@ -19,6 +20,9 @@ pub enum ContractError { #[error("Vault deposit would result in exceeding limit. With deposit: {new_value:?}, Maximum: {maximum:?}")] AboveVaultDepositCap { new_value: String, maximum: String }, + #[error("{0}")] + AdminError(#[from] AdminError), + #[error("{0} is not an available coin to request")] CoinNotAvailable(String), diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index 775c62f3a..3f82071c8 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -10,7 +10,7 @@ use cosmwasm_std::{Decimal, Uint128}; #[cw_serde] pub struct InstantiateMsg { /// The address with privileged access to update config - pub owner: String, + pub admin: String, /// Whitelisted coin denoms approved by governance pub allowed_coins: Vec, /// Whitelisted vaults approved by governance that implement credit manager's vault interface @@ -61,7 +61,7 @@ impl Stringify for Vec { #[derive(Default)] pub struct ConfigUpdates { pub account_nft: Option, - pub owner: Option, + pub admin: Option, pub allowed_coins: Option>, pub vault_configs: Option>, pub oracle: Option, diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index f499f0769..c809391b7 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Decimal, Uint128}; + use mars_health::health::Health; use crate::adapters::vault::{Vault, VaultPosition, VaultUnchecked}; @@ -8,7 +9,7 @@ use crate::traits::Coins; #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - /// Owner & account nft address + /// Rover contract-level config #[returns(ConfigResponse)] Config {}, /// Configs on vaults @@ -152,7 +153,7 @@ pub struct VaultPositionValue { #[cw_serde] pub struct ConfigResponse { - pub owner: String, + pub admin: Option, pub account_nft: Option, pub red_bank: String, pub oracle: String, diff --git a/schemas/mars-account-nft/mars-account-nft.json b/schemas/mars-account-nft/mars-account-nft.json index 4a173c2e4..f28fdfaa5 100644 --- a/schemas/mars-account-nft/mars-account-nft.json +++ b/schemas/mars-account-nft/mars-account-nft.json @@ -408,6 +408,19 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "next_id" + ], + "properties": { + "next_id": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Return the owner of the given token, error if token does not exist", "type": "object", @@ -1238,6 +1251,13 @@ }, "additionalProperties": false }, + "next_id": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint64", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, "nft_info": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "NftInfoResponse_for_Empty", diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index b64d9bcd4..0fda3ac6f 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -7,16 +7,21 @@ "title": "InstantiateMsg", "type": "object", "required": [ + "admin", "allowed_coins", "allowed_vaults", "max_close_factor", + "max_unlocking_positions", "oracle", - "owner", "red_bank", "swapper", "zapper" ], "properties": { + "admin": { + "description": "The address with privileged access to update config", + "type": "string" + }, "allowed_coins": { "description": "Whitelisted coin denoms approved by governance", "type": "array", @@ -39,6 +44,14 @@ } ] }, + "max_unlocking_positions": { + "description": "The maximum number of unlocking positions an account can have simultaneously Note: As health checking requires looping through each, this number must not be too large. If so, having too many could prevent the account from being liquidated due to gas constraints.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, "oracle": { "description": "The Mars Protocol oracle contract. We read prices of assets here.", "allOf": [ @@ -47,10 +60,6 @@ } ] }, - "owner": { - "description": "The address with privileged access to update config", - "type": "string" - }, "red_bank": { "description": "The Mars Protocol money market contract where we borrow assets from", "allOf": [ @@ -811,36 +820,6 @@ }, "additionalProperties": false }, - { - "description": "A privileged action only to be used by Rover. Same as `VaultWithdraw` except it bypasses any lockup period restrictions on the vault. Used only in the case position is unhealthy and requires immediate liquidation.", - "type": "object", - "required": [ - "force_exit_vault" - ], - "properties": { - "force_exit_vault": { - "type": "object", - "required": [ - "account_id", - "amount", - "vault" - ], - "properties": { - "account_id": { - "type": "string" - }, - "amount": { - "$ref": "#/definitions/Uint128" - }, - "vault": { - "$ref": "#/definitions/VaultBase_for_Addr" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "Requests unlocking of shares for a vault with a lock period", "type": "object", @@ -1174,6 +1153,12 @@ "null" ] }, + "admin": { + "type": [ + "string", + "null" + ] + }, "allowed_coins": { "type": [ "array", @@ -1193,26 +1178,20 @@ } ] }, - "oracle": { + "max_unlocking_positions": { "anyOf": [ { - "$ref": "#/definitions/OracleBase_for_String" + "$ref": "#/definitions/Uint128" }, { "type": "null" } ] }, - "owner": { - "type": [ - "string", - "null" - ] - }, - "red_bank": { + "oracle": { "anyOf": [ { - "$ref": "#/definitions/RedBankBase_for_String" + "$ref": "#/definitions/OracleBase_for_String" }, { "type": "null" @@ -1258,9 +1237,6 @@ "OracleBase_for_String": { "type": "string" }, - "RedBankBase_for_String": { - "type": "string" - }, "SwapperBase_for_String": { "type": "string" }, @@ -1350,7 +1326,7 @@ "title": "QueryMsg", "oneOf": [ { - "description": "Owner & account nft address", + "description": "Rover contract-level config", "type": "object", "required": [ "config" @@ -2091,8 +2067,8 @@ "type": "object", "required": [ "max_close_factor", + "max_unlocking_positions", "oracle", - "owner", "red_bank", "swapper", "zapper" @@ -2104,13 +2080,19 @@ "null" ] }, + "admin": { + "type": [ + "string", + "null" + ] + }, "max_close_factor": { "$ref": "#/definitions/Decimal" }, - "oracle": { - "type": "string" + "max_unlocking_positions": { + "$ref": "#/definitions/Uint128" }, - "owner": { + "oracle": { "type": "string" }, "red_bank": { @@ -2128,6 +2110,10 @@ "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" } } }, diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index ed50897cc..609438669 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -102,7 +102,7 @@ "title": "QueryMsg", "oneOf": [ { - "description": "Owner & account nft address", + "description": "Rover contract-level config", "type": "object", "required": [ "config" @@ -843,8 +843,8 @@ "type": "object", "required": [ "max_close_factor", + "max_unlocking_positions", "oracle", - "owner", "red_bank", "swapper", "zapper" @@ -856,13 +856,19 @@ "null" ] }, + "admin": { + "type": [ + "string", + "null" + ] + }, "max_close_factor": { "$ref": "#/definitions/Decimal" }, - "oracle": { - "type": "string" + "max_unlocking_positions": { + "$ref": "#/definitions/Uint128" }, - "owner": { + "oracle": { "type": "string" }, "red_bank": { @@ -880,6 +886,10 @@ "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" } } }, diff --git a/schemas/mars-oracle-adapter/mars-oracle-adapter.json b/schemas/mars-oracle-adapter/mars-oracle-adapter.json index 9786cba55..a0cf2c0b2 100644 --- a/schemas/mars-oracle-adapter/mars-oracle-adapter.json +++ b/schemas/mars-oracle-adapter/mars-oracle-adapter.json @@ -7,17 +7,17 @@ "title": "InstantiateMsg", "type": "object", "required": [ + "admin", "oracle", - "owner", "vault_pricing" ], "properties": { + "admin": { + "type": "string" + }, "oracle": { "$ref": "#/definitions/OracleBase_for_String" }, - "owner": { - "type": "string" - }, "vault_pricing": { "type": "array", "items": { @@ -100,6 +100,12 @@ "ConfigUpdates": { "type": "object", "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, "oracle": { "anyOf": [ { @@ -110,12 +116,6 @@ } ] }, - "owner": { - "type": [ - "string", - "null" - ] - }, "vault_pricing": { "type": [ "array", @@ -189,28 +189,6 @@ }, "additionalProperties": false }, - { - "description": "Converts vault coin to the mars-oracle accepted priceable coins", - "type": "object", - "required": [ - "priceable_underlying" - ], - "properties": { - "priceable_underlying": { - "type": "object", - "required": [ - "coin" - ], - "properties": { - "coin": { - "$ref": "#/definitions/Coin" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "type": "object", "required": [ @@ -274,28 +252,7 @@ }, "additionalProperties": false } - ], - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } + ] }, "migrate": null, "sudo": null, @@ -349,15 +306,21 @@ "title": "ConfigResponse", "type": "object", "required": [ - "oracle", - "owner" + "oracle" ], "properties": { + "admin": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, "oracle": { "$ref": "#/definitions/OracleBase_for_Addr" - }, - "owner": { - "$ref": "#/definitions/Addr" } }, "additionalProperties": false, @@ -395,35 +358,6 @@ } } }, - "priceable_underlying": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_Coin", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - }, - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, "pricing_info": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "VaultPricingInfo", diff --git a/schemas/mars-swapper-base/mars-swapper-base.json b/schemas/mars-swapper-base/mars-swapper-base.json index b2a0aa215..de25579d3 100644 --- a/schemas/mars-swapper-base/mars-swapper-base.json +++ b/schemas/mars-swapper-base/mars-swapper-base.json @@ -7,11 +7,11 @@ "title": "InstantiateMsg", "type": "object", "required": [ - "owner" + "admin" ], "properties": { - "owner": { - "description": "The contract's owner, who can update config", + "admin": { + "description": "The contract's admin, who can update config", "type": "string" } }, @@ -22,20 +22,20 @@ "title": "ExecuteMsg", "oneOf": [ { - "description": "Update contract config", + "description": "Update contract admin", "type": "object", "required": [ - "update_config" + "update_admin" ], "properties": { - "update_config": { + "update_admin": { "type": "object", + "required": [ + "admin" + ], "properties": { - "owner": { - "type": [ - "string", - "null" - ] + "admin": { + "type": "string" } }, "additionalProperties": false @@ -176,10 +176,10 @@ "description": "Query contract config", "type": "object", "required": [ - "config" + "admin" ], "properties": { - "config": { + "admin": { "type": "object", "additionalProperties": false } @@ -304,17 +304,17 @@ "migrate": null, "sudo": null, "responses": { - "config": { + "admin": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config_for_String", + "title": "AdminResponse", + "description": "Returned from Admin.query_admin()", "type": "object", - "required": [ - "owner" - ], "properties": { - "owner": { - "description": "The contract's owner, who can update config", - "type": "string" + "admin": { + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts index 0efa255f6..bce559018 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts @@ -34,6 +34,7 @@ import { export interface MarsAccountNftReadOnlyInterface { contractAddress: string config: () => Promise + nextId: () => Promise ownerOf: ({ includeExpired, tokenId, @@ -104,6 +105,7 @@ export class MarsAccountNftQueryClient implements MarsAccountNftReadOnlyInterfac this.client = client this.contractAddress = contractAddress this.config = this.config.bind(this) + this.nextId = this.nextId.bind(this) this.ownerOf = this.ownerOf.bind(this) this.approval = this.approval.bind(this) this.approvals = this.approvals.bind(this) @@ -122,6 +124,11 @@ export class MarsAccountNftQueryClient implements MarsAccountNftReadOnlyInterfac config: {}, }) } + nextId = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + next_id: {}, + }) + } ownerOf = async ({ includeExpired, tokenId, diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index 319764ff9..b2de98454 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -89,6 +89,9 @@ export type QueryMsg = | { config: {} } + | { + next_id: {} + } | { owner_of: { include_expired?: boolean | null diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index a62010205..314b1b6b0 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -12,11 +12,12 @@ export type RedBankBaseForString = string export type SwapperBaseForString = string export type ZapperBaseForString = string export interface InstantiateMsg { + admin: string allowed_coins: string[] allowed_vaults: VaultInstantiateConfig[] max_close_factor: Decimal + max_unlocking_positions: Uint128 oracle: OracleBaseForString - owner: string red_bank: RedBankBaseForString swapper: SwapperBaseForString zapper: ZapperBaseForString @@ -180,13 +181,6 @@ export type CallbackMsg = vault: VaultBaseForAddr } } - | { - force_exit_vault: { - account_id: string - amount: Uint128 - vault: VaultBaseForAddr - } - } | { request_vault_unlock: { account_id: string @@ -259,11 +253,11 @@ export type CallbackMsg = export type Addr = string export interface ConfigUpdates { account_nft?: string | null + admin?: string | null allowed_coins?: string[] | null max_close_factor?: Decimal | null + max_unlocking_positions?: Uint128 | null oracle?: OracleBaseForString | null - owner?: string | null - red_bank?: RedBankBaseForString | null swapper?: SwapperBaseForString | null vault_configs?: VaultInstantiateConfig[] | null zapper?: ZapperBaseForString | null @@ -398,9 +392,10 @@ export interface VaultUnlockingPosition { export type ArrayOfString = string[] export interface ConfigResponse { account_nft?: string | null + admin?: string | null max_close_factor: Decimal + max_unlocking_positions: Uint128 oracle: string - owner: string red_bank: string swapper: string zapper: string diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 7c710362a..16ccb496d 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -165,9 +165,10 @@ export interface VaultUnlockingPosition { export type ArrayOfString = string[] export interface ConfigResponse { account_nft?: string | null + admin?: string | null max_close_factor: Decimal + max_unlocking_positions: Uint128 oracle: string - owner: string red_bank: string swapper: string zapper: string diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts index 5d43ba256..09652b831 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts @@ -6,7 +6,7 @@ */ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee } from '@cosmjs/amino' +import { Coin, StdFee } from '@cosmjs/amino' import { OracleBaseForString, Addr, @@ -16,19 +16,15 @@ import { ExecuteMsg, ConfigUpdates, QueryMsg, - Uint128, - Coin, ArrayOfVaultPricingInfo, OracleBaseForAddr, ConfigResponse, Decimal, PriceResponse, - ArrayOfCoin, } from './MarsOracleAdapter.types' export interface MarsOracleAdapterReadOnlyInterface { contractAddress: string price: ({ denom }: { denom: string }) => Promise - priceableUnderlying: ({ coin }: { coin: Coin }) => Promise config: () => Promise pricingInfo: ({ denom }: { denom: string }) => Promise allPricingInfo: ({ @@ -47,7 +43,6 @@ export class MarsOracleAdapterQueryClient implements MarsOracleAdapterReadOnlyIn this.client = client this.contractAddress = contractAddress this.price = this.price.bind(this) - this.priceableUnderlying = this.priceableUnderlying.bind(this) this.config = this.config.bind(this) this.pricingInfo = this.pricingInfo.bind(this) this.allPricingInfo = this.allPricingInfo.bind(this) @@ -60,13 +55,6 @@ export class MarsOracleAdapterQueryClient implements MarsOracleAdapterReadOnlyIn }, }) } - priceableUnderlying = async ({ coin }: { coin: Coin }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - priceable_underlying: { - coin, - }, - }) - } config = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { config: {}, diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts index c11790ce7..5b4533bee 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts @@ -5,6 +5,7 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ +import { Coin } from '@cosmjs/amino' import { MsgExecuteContractEncodeObject } from 'cosmwasm' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' @@ -17,14 +18,11 @@ import { ExecuteMsg, ConfigUpdates, QueryMsg, - Uint128, - Coin, ArrayOfVaultPricingInfo, OracleBaseForAddr, ConfigResponse, Decimal, PriceResponse, - ArrayOfCoin, } from './MarsOracleAdapter.types' export interface MarsOracleAdapterMessage { contractAddress: string diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts index 7587f33ff..806626be5 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts @@ -16,14 +16,11 @@ import { ExecuteMsg, ConfigUpdates, QueryMsg, - Uint128, - Coin, ArrayOfVaultPricingInfo, OracleBaseForAddr, ConfigResponse, Decimal, PriceResponse, - ArrayOfCoin, } from './MarsOracleAdapter.types' import { MarsOracleAdapterQueryClient, MarsOracleAdapterClient } from './MarsOracleAdapter.client' export const marsOracleAdapterQueryKeys = { @@ -36,14 +33,6 @@ export const marsOracleAdapterQueryKeys = { [{ ...marsOracleAdapterQueryKeys.contract[0], address: contractAddress }] as const, price: (contractAddress: string | undefined, args?: Record) => [{ ...marsOracleAdapterQueryKeys.address(contractAddress)[0], method: 'price', args }] as const, - priceableUnderlying: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsOracleAdapterQueryKeys.address(contractAddress)[0], - method: 'priceable_underlying', - args, - }, - ] as const, config: (contractAddress: string | undefined, args?: Record) => [ { ...marsOracleAdapterQueryKeys.address(contractAddress)[0], method: 'config', args }, @@ -128,28 +117,6 @@ export function useMarsOracleAdapterConfigQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsOracleAdapterPriceableUnderlyingQuery - extends MarsOracleAdapterReactQuery { - args: { - coin: Coin - } -} -export function useMarsOracleAdapterPriceableUnderlyingQuery({ - client, - args, - options, -}: MarsOracleAdapterPriceableUnderlyingQuery) { - return useQuery( - marsOracleAdapterQueryKeys.priceableUnderlying(client?.contractAddress, args), - () => - client - ? client.priceableUnderlying({ - coin: args.coin, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} export interface MarsOracleAdapterPriceQuery extends MarsOracleAdapterReactQuery { args: { diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts index 78cad5964..4aa74f917 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts @@ -9,8 +9,8 @@ export type OracleBaseForString = string export type Addr = string export type PricingMethod = 'preview_redeem' export interface InstantiateMsg { + admin: string oracle: OracleBaseForString - owner: string vault_pricing: VaultPricingInfo[] } export interface VaultPricingInfo { @@ -25,8 +25,8 @@ export type ExecuteMsg = { } } export interface ConfigUpdates { + admin?: string | null oracle?: OracleBaseForString | null - owner?: string | null vault_pricing?: VaultPricingInfo[] | null } export type QueryMsg = @@ -35,11 +35,6 @@ export type QueryMsg = denom: string } } - | { - priceable_underlying: { - coin: Coin - } - } | { config: {} } @@ -54,21 +49,14 @@ export type QueryMsg = start_after?: string | null } } -export type Uint128 = string -export interface Coin { - amount: Uint128 - denom: string - [k: string]: unknown -} export type ArrayOfVaultPricingInfo = VaultPricingInfo[] export type OracleBaseForAddr = string export interface ConfigResponse { + admin?: Addr | null oracle: OracleBaseForAddr - owner: Addr } export type Decimal = string export interface PriceResponse { denom: string price: Decimal } -export type ArrayOfCoin = Coin[] diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts index a1d1e1222..8fd404c17 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts @@ -16,14 +16,14 @@ import { Empty, Coin, QueryMsg, - ConfigForString, + AdminResponse, EstimateExactInSwapResponse, RouteResponseForEmpty, ArrayOfRouteResponseForEmpty, } from './MarsSwapperBase.types' export interface MarsSwapperBaseReadOnlyInterface { contractAddress: string - config: () => Promise + admin: () => Promise route: ({ denomIn, denomOut, @@ -53,15 +53,15 @@ export class MarsSwapperBaseQueryClient implements MarsSwapperBaseReadOnlyInterf constructor(client: CosmWasmClient, contractAddress: string) { this.client = client this.contractAddress = contractAddress - this.config = this.config.bind(this) + this.admin = this.admin.bind(this) this.route = this.route.bind(this) this.routes = this.routes.bind(this) this.estimateExactInSwap = this.estimateExactInSwap.bind(this) } - config = async (): Promise => { + admin = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { - config: {}, + admin: {}, }) } route = async ({ @@ -110,11 +110,11 @@ export class MarsSwapperBaseQueryClient implements MarsSwapperBaseReadOnlyInterf export interface MarsSwapperBaseInterface extends MarsSwapperBaseReadOnlyInterface { contractAddress: string sender: string - updateConfig: ( + updateAdmin: ( { - owner, + admin, }: { - owner?: string + admin: string }, fee?: number | StdFee | 'auto', memo?: string, @@ -176,17 +176,17 @@ export class MarsSwapperBaseClient this.client = client this.sender = sender this.contractAddress = contractAddress - this.updateConfig = this.updateConfig.bind(this) + this.updateAdmin = this.updateAdmin.bind(this) this.setRoute = this.setRoute.bind(this) this.swapExactIn = this.swapExactIn.bind(this) this.transferResult = this.transferResult.bind(this) } - updateConfig = async ( + updateAdmin = async ( { - owner, + admin, }: { - owner?: string + admin: string }, fee: number | StdFee | 'auto' = 'auto', memo?: string, @@ -196,8 +196,8 @@ export class MarsSwapperBaseClient this.sender, this.contractAddress, { - update_config: { - owner, + update_admin: { + admin, }, }, fee, diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts index eb64d6c9f..ca0682c1d 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts @@ -17,7 +17,7 @@ import { Empty, Coin, QueryMsg, - ConfigForString, + AdminResponse, EstimateExactInSwapResponse, RouteResponseForEmpty, ArrayOfRouteResponseForEmpty, @@ -25,11 +25,11 @@ import { export interface MarsSwapperBaseMessage { contractAddress: string sender: string - updateConfig: ( + updateAdmin: ( { - owner, + admin, }: { - owner?: string + admin: string }, funds?: Coin[], ) => MsgExecuteContractEncodeObject @@ -77,17 +77,17 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { constructor(sender: string, contractAddress: string) { this.sender = sender this.contractAddress = contractAddress - this.updateConfig = this.updateConfig.bind(this) + this.updateAdmin = this.updateAdmin.bind(this) this.setRoute = this.setRoute.bind(this) this.swapExactIn = this.swapExactIn.bind(this) this.transferResult = this.transferResult.bind(this) } - updateConfig = ( + updateAdmin = ( { - owner, + admin, }: { - owner?: string + admin: string }, funds?: Coin[], ): MsgExecuteContractEncodeObject => { @@ -98,8 +98,8 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - update_config: { - owner, + update_admin: { + admin, }, }), ), diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts index 89545a281..0c069698b 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts @@ -16,7 +16,7 @@ import { Empty, Coin, QueryMsg, - ConfigForString, + AdminResponse, EstimateExactInSwapResponse, RouteResponseForEmpty, ArrayOfRouteResponseForEmpty, @@ -30,8 +30,8 @@ export const marsSwapperBaseQueryKeys = { ] as const, address: (contractAddress: string | undefined) => [{ ...marsSwapperBaseQueryKeys.contract[0], address: contractAddress }] as const, - config: (contractAddress: string | undefined, args?: Record) => - [{ ...marsSwapperBaseQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, + admin: (contractAddress: string | undefined, args?: Record) => + [{ ...marsSwapperBaseQueryKeys.address(contractAddress)[0], method: 'admin', args }] as const, route: (contractAddress: string | undefined, args?: Record) => [{ ...marsSwapperBaseQueryKeys.address(contractAddress)[0], method: 'route', args }] as const, routes: (contractAddress: string | undefined, args?: Record) => @@ -126,15 +126,15 @@ export function useMarsSwapperBaseRouteQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsSwapperBaseConfigQuery - extends MarsSwapperBaseReactQuery {} -export function useMarsSwapperBaseConfigQuery({ +export interface MarsSwapperBaseAdminQuery + extends MarsSwapperBaseReactQuery {} +export function useMarsSwapperBaseAdminQuery({ client, options, -}: MarsSwapperBaseConfigQuery) { - return useQuery( - marsSwapperBaseQueryKeys.config(client?.contractAddress), - () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), +}: MarsSwapperBaseAdminQuery) { + return useQuery( + marsSwapperBaseQueryKeys.admin(client?.contractAddress), + () => (client ? client.admin() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } @@ -211,10 +211,10 @@ export function useMarsSwapperBaseSetRouteMutation( options, ) } -export interface MarsSwapperBaseUpdateConfigMutation { +export interface MarsSwapperBaseUpdateAdminMutation { client: MarsSwapperBaseClient msg: { - owner?: string + admin: string } args?: { fee?: number | StdFee | 'auto' @@ -222,15 +222,14 @@ export interface MarsSwapperBaseUpdateConfigMutation { funds?: Coin[] } } -export function useMarsSwapperBaseUpdateConfigMutation( +export function useMarsSwapperBaseUpdateAdminMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => - client.updateConfig(msg, fee, memo, funds), + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateAdmin(msg, fee, memo, funds), options, ) } diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts index 01bb7133f..89a54edec 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts @@ -6,12 +6,12 @@ */ export interface InstantiateMsg { - owner: string + admin: string } export type ExecuteMsg = | { - update_config: { - owner?: string | null + update_admin: { + admin: string } } | { @@ -48,7 +48,7 @@ export interface Coin { } export type QueryMsg = | { - config: {} + admin: {} } | { route: { @@ -68,8 +68,8 @@ export type QueryMsg = denom_out: string } } -export interface ConfigForString { - owner: string +export interface AdminResponse { + admin?: string | null } export interface EstimateExactInSwapResponse { amount: Uint128 From bdc541cf8c9529eecd55bbccf1bc8736a9417c39 Mon Sep 17 00:00:00 2001 From: piobab Date: Thu, 1 Dec 2022 19:21:19 +0100 Subject: [PATCH 093/218] Use v2 queries. (#49) * Use v2 queries. * Bump osmosis deps. * Use v1 TWAP query. --- Cargo.lock | 42 +++------------ Cargo.toml | 3 +- contracts/swapper/osmosis/Cargo.toml | 3 +- packages/chains/osmosis/src/helpers.rs | 71 +++++++++++++++++++++++--- 4 files changed, 74 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 309de7fa6..cf4a52a7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1047,7 +1047,7 @@ name = "mars-osmosis" version = "1.0.0" dependencies = [ "cosmwasm-std", - "osmosis-std 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "osmosis-std", "serde", ] @@ -1119,7 +1119,7 @@ dependencies = [ "mars-osmosis", "mars-rover", "mars-swapper-base", - "osmosis-std 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "osmosis-std", "osmosis-testing", "schemars", "thiserror", @@ -1207,12 +1207,11 @@ checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "osmosis-std" version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b3792977036dc49cfc9af9fd7a6c021fd48dfffc8ebf09324201506c65a47a" +source = "git+https://github.com/osmosis-labs/osmosis-rust?rev=fb79d48810cfa80f1f74dffc1e04f0775fe1e152#fb79d48810cfa80f1f74dffc1e04f0775fe1e152" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "osmosis-std-derive", "prost 0.11.2", "prost-types", "schemars", @@ -1220,37 +1219,10 @@ dependencies = [ "serde-cw-value", ] -[[package]] -name = "osmosis-std" -version = "0.12.0" -source = "git+https://github.com/osmosis-labs/osmosis-rust#b3df85158eb52ad5d99f3580f4a128bbdaeffa17" -dependencies = [ - "chrono", - "cosmwasm-std", - "osmosis-std-derive 0.12.0 (git+https://github.com/osmosis-labs/osmosis-rust)", - "prost 0.11.2", - "prost-types", - "schemars", - "serde", - "serde-cw-value", -] - -[[package]] -name = "osmosis-std-derive" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c501f2b8ff88b1c60ab671d7b808e947f384fa2524fe4ec8c06f63ef4be29979" -dependencies = [ - "itertools", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "osmosis-std-derive" version = "0.12.0" -source = "git+https://github.com/osmosis-labs/osmosis-rust#b3df85158eb52ad5d99f3580f4a128bbdaeffa17" +source = "git+https://github.com/osmosis-labs/osmosis-rust?rev=fb79d48810cfa80f1f74dffc1e04f0775fe1e152#fb79d48810cfa80f1f74dffc1e04f0775fe1e152" dependencies = [ "itertools", "proc-macro2", @@ -1261,13 +1233,13 @@ dependencies = [ [[package]] name = "osmosis-testing" version = "0.12.0" -source = "git+https://github.com/osmosis-labs/osmosis-rust#b3df85158eb52ad5d99f3580f4a128bbdaeffa17" +source = "git+https://github.com/osmosis-labs/osmosis-rust?rev=fb79d48810cfa80f1f74dffc1e04f0775fe1e152#fb79d48810cfa80f1f74dffc1e04f0775fe1e152" dependencies = [ "base64", "bindgen", "cosmrs", "cosmwasm-std", - "osmosis-std 0.12.0 (git+https://github.com/osmosis-labs/osmosis-rust)", + "osmosis-std", "prost 0.11.2", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index e269f6860..01d9295d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,8 @@ cw-item-set = { version = "0.6", default-features = false, features = ["iter cw-multi-test = "0.16" cw-utils = "0.16" cw-storage-plus = "0.16" -osmosis-std = "0.12" +# FIXME: update with new version when osmosis-v13 branch merged to main branch +osmosis-std = { rev = "fb79d48810cfa80f1f74dffc1e04f0775fe1e152", git = "https://github.com/osmosis-labs/osmosis-rust" } schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index 0f2750b35..5abba1647 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -32,4 +32,5 @@ thiserror = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -osmosis-testing = { version = "0.12", git = "https://github.com/osmosis-labs/osmosis-rust" } +# FIXME: update with new version when osmosis-v13 branch merged to main branch +osmosis-testing = { rev = "fb79d48810cfa80f1f74dffc1e04f0775fe1e152", git = "https://github.com/osmosis-labs/osmosis-rust" } diff --git a/packages/chains/osmosis/src/helpers.rs b/packages/chains/osmosis/src/helpers.rs index c74dd5421..4a5a35aae 100644 --- a/packages/chains/osmosis/src/helpers.rs +++ b/packages/chains/osmosis/src/helpers.rs @@ -1,12 +1,13 @@ use std::str::FromStr; -use cosmwasm_std::{Decimal, Empty, QuerierWrapper, QueryRequest, StdResult}; +use cosmwasm_std::{ + coin, Decimal, Empty, QuerierWrapper, QueryRequest, StdError, StdResult, Uint128, +}; use osmosis_std::shim::Timestamp; use osmosis_std::types::cosmos::base::v1beta1::Coin; -use osmosis_std::types::osmosis::gamm::v1beta1::{ - GammQuerier, PoolAsset, PoolParams, QueryPoolRequest, -}; +use osmosis_std::types::osmosis::gamm::v1beta1::{PoolAsset, PoolParams, QueryPoolRequest}; +use osmosis_std::types::osmosis::gamm::v2::GammQuerier; use osmosis_std::types::osmosis::twap::v1beta1::TwapQuerier; use serde::{Deserialize, Serialize}; @@ -24,6 +25,21 @@ pub struct Pool { pub total_weight: String, } +impl Pool { + /// Unwraps Osmosis coin into Cosmwasm coin + pub fn unwrap_coin(osmosis_coin: &Option) -> StdResult { + let osmosis_coin = match osmosis_coin { + None => return Err(StdError::generic_err("missing coin")), // just in case, it shouldn't happen + Some(osmosis_coin) => osmosis_coin, + }; + let cosmwasm_coin = coin( + Uint128::from_str(&osmosis_coin.amount)?.u128(), + &osmosis_coin.denom, + ); + Ok(cosmwasm_coin) + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct QueryPoolResponse { pub pool: Pool, @@ -50,13 +66,10 @@ pub fn query_spot_price( base_denom: &str, quote_denom: &str, ) -> StdResult { - // NOTE: Currency pair consists of base and quote asset (base/quote). Spot query has it swapped. - // For example: - // if we want to check the price ATOM/OSMO then we pass base_asset = OSMO, quote_asset = ATOM let spot_price_res = GammQuerier::new(querier).spot_price( pool_id, - quote_denom.to_string(), base_denom.to_string(), + quote_denom.to_string(), )?; let price = Decimal::from_str(&spot_price_res.spot_price)?; Ok(price) @@ -64,6 +77,7 @@ pub fn query_spot_price( /// Query the twap price of a coin, denominated in OSMO. /// `start_time` must be within 48 hours of current block time. +#[allow(deprecated)] // FIXME: arithmetic_twap_to_now shouldn't be deprecated, make clippy happy for now pub fn query_twap_price( querier: &QuerierWrapper, pool_id: u64, @@ -83,3 +97,44 @@ pub fn query_twap_price( let price = Decimal::from_str(&arithmetic_twap_res.arithmetic_twap)?; Ok(price) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_unwrapping_coin() { + let pool = Pool { + id: "1111".to_string(), + address: "".to_string(), + pool_params: None, + future_pool_governor: "".to_string(), + pool_assets: vec![ + PoolAsset { + token: Some(Coin { + denom: "denom_1".to_string(), + amount: "123".to_string(), + }), + weight: "500".to_string(), + }, + PoolAsset { + token: Some(Coin { + denom: "denom_2".to_string(), + amount: "430".to_string(), + }), + weight: "500".to_string(), + }, + ], + total_shares: None, + total_weight: "".to_string(), + }; + + let res_err = Pool::unwrap_coin(&pool.total_shares).unwrap_err(); + assert_eq!(res_err, StdError::generic_err("missing coin")); + + let res = Pool::unwrap_coin(&pool.pool_assets[0].token).unwrap(); + assert_eq!(res, coin(123, "denom_1")); + let res = Pool::unwrap_coin(&pool.pool_assets[1].token).unwrap(); + assert_eq!(res, coin(430, "denom_2")); + } +} From 1f8bbdc78586a9831c1658e1daf02c7f690ccbe8 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 6 Dec 2022 20:54:01 +0100 Subject: [PATCH 094/218] Admin state machine pkg [oak audit] (#61) Admin package upgrade --- Cargo.lock | 18 +- Cargo.toml | 10 +- Makefile.toml | 2 +- README.md | 2 +- contracts/credit-manager/Cargo.toml | 28 +- contracts/credit-manager/src/contract.rs | 4 +- contracts/credit-manager/src/execute.rs | 110 +- contracts/credit-manager/src/instantiate.rs | 7 +- contracts/credit-manager/src/lib.rs | 1 + contracts/credit-manager/src/query.rs | 6 +- contracts/credit-manager/src/state.rs | 2 +- contracts/credit-manager/src/update_config.rs | 115 ++ .../credit-manager/tests/helpers/mock_env.rs | 14 + .../credit-manager/tests/test_update_admin.rs | 157 +++ .../tests/test_update_config.rs | 13 +- contracts/oracle-adapter/Cargo.toml | 16 +- contracts/oracle-adapter/src/contract.rs | 33 +- contracts/oracle-adapter/src/error.rs | 2 +- contracts/oracle-adapter/src/msg.rs | 6 +- contracts/oracle-adapter/src/state.rs | 2 +- contracts/oracle-adapter/tests/helpers.rs | 8 +- .../oracle-adapter/tests/test_update_admin.rs | 174 ++++ .../tests/test_update_config.rs | 20 +- packages/controllers/Cargo.toml | 18 + packages/controllers/src/admin.rs | 984 ++++++++++++++++++ packages/controllers/src/lib.rs | 3 + packages/rover/Cargo.toml | 27 +- packages/rover/src/error.rs | 2 +- packages/rover/src/msg/execute.rs | 3 + packages/rover/src/msg/instantiate.rs | 1 - packages/rover/src/msg/query.rs | 1 + .../mars-credit-manager.json | 80 +- .../mars-mock-credit-manager.json | 6 + .../mars-oracle-adapter.json | 89 +- scripts/package.json | 2 +- .../mars-account-nft/MarsAccountNft.client.ts | 2 +- .../MarsAccountNft.message-composer.ts | 2 +- .../MarsAccountNft.react-query.ts | 573 ++++++++++ .../mars-account-nft/MarsAccountNft.types.ts | 2 +- .../generated/mars-account-nft/bundle.ts | 14 + .../MarsCreditManager.client.ts | 25 +- .../MarsCreditManager.message-composer.ts | 20 +- .../MarsCreditManager.react-query.ts | 577 ++++++++++ .../MarsCreditManager.types.ts | 19 +- .../generated/mars-credit-manager/bundle.ts | 14 + .../MarsMockCreditManager.client.ts | 2 +- .../MarsMockCreditManager.message-composer.ts | 2 +- .../MarsMockCreditManager.react-query.ts | 3 +- .../MarsMockCreditManager.types.ts | 3 +- .../mars-mock-credit-manager/bundle.ts | 12 +- .../mars-mock-oracle/MarsMockOracle.client.ts | 2 +- .../MarsMockOracle.message-composer.ts | 2 +- .../MarsMockOracle.react-query.ts | 3 +- .../mars-mock-oracle/MarsMockOracle.types.ts | 2 +- .../generated/mars-mock-oracle/bundle.ts | 12 +- .../MarsMockRedBank.client.ts | 2 +- .../MarsMockRedBank.message-composer.ts | 2 +- .../MarsMockRedBank.react-query.ts | 3 +- .../MarsMockRedBank.types.ts | 2 +- .../generated/mars-mock-red-bank/bundle.ts | 12 +- .../mars-mock-vault/MarsMockVault.client.ts | 2 +- .../MarsMockVault.message-composer.ts | 2 +- .../MarsMockVault.react-query.ts | 301 ++++++ .../mars-mock-vault/MarsMockVault.types.ts | 2 +- .../types/generated/mars-mock-vault/bundle.ts | 14 + .../mars-mock-zapper/MarsMockZapper.client.ts | 2 +- .../MarsMockZapper.message-composer.ts | 2 +- .../MarsMockZapper.react-query.ts | 3 +- .../mars-mock-zapper/MarsMockZapper.types.ts | 2 +- .../generated/mars-mock-zapper/bundle.ts | 12 +- .../MarsOracleAdapter.client.ts | 25 +- .../MarsOracleAdapter.message-composer.ts | 20 +- .../MarsOracleAdapter.react-query.ts | 23 +- .../MarsOracleAdapter.types.ts | 32 +- .../generated/mars-oracle-adapter/bundle.ts | 12 +- .../MarsSwapperBase.client.ts | 2 +- .../MarsSwapperBase.message-composer.ts | 2 +- .../MarsSwapperBase.react-query.ts | 3 +- .../MarsSwapperBase.types.ts | 2 +- .../generated/mars-swapper-base/bundle.ts | 12 +- scripts/yarn.lock | 18 +- 81 files changed, 3465 insertions(+), 306 deletions(-) create mode 100644 contracts/credit-manager/src/update_config.rs create mode 100644 contracts/credit-manager/tests/test_update_admin.rs create mode 100644 contracts/oracle-adapter/tests/test_update_admin.rs create mode 100644 packages/controllers/Cargo.toml create mode 100644 packages/controllers/src/admin.rs create mode 100644 packages/controllers/src/lib.rs create mode 100644 scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts create mode 100644 scripts/types/generated/mars-account-nft/bundle.ts create mode 100644 scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts create mode 100644 scripts/types/generated/mars-credit-manager/bundle.ts create mode 100644 scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts create mode 100644 scripts/types/generated/mars-mock-vault/bundle.ts diff --git a/Cargo.lock b/Cargo.lock index cf4a52a7b..98c790569 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -406,6 +406,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-controllers-admin-fork" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw-item-set" version = "0.6.0" @@ -937,7 +950,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vault-standard", - "cw-controllers", + "cw-controllers-admin-fork", "cw-item-set", "cw-multi-test", "cw-storage-plus", @@ -1030,7 +1043,7 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-controllers", + "cw-controllers-admin-fork", "cw-multi-test", "cw-storage-plus", "cw-utils", @@ -1068,6 +1081,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-vault-standard", "cw-controllers", + "cw-controllers-admin-fork", "cw-storage-plus", "cw-utils", "mars-health", diff --git a/Cargo.toml b/Cargo.toml index 01d9295d4..a3edddfc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "packages/health", "packages/outpost", "packages/rover", + "packages/controllers", # Mock contracts "contracts/mock-oracle", @@ -49,10 +50,11 @@ serde = { version = "1.0", default-features = false, features = ["deri thiserror = "1.0" # packages -cosmwasm-vault-standard = { version = "0.1.0", features = ["lockup", "force-unlock"] } -mars-health = { version = "1.0.0", path = "./packages/health" } -mars-outpost = { version = "1.0.0", path = "./packages/outpost" } -mars-rover = { version = "1.0.0", path = "./packages/rover" } +cosmwasm-vault-standard = { version = "0.1.0", features = ["lockup", "force-unlock"] } +cw-controllers-admin-fork = { version = "1.0.0", path = "./packages/controllers" } +mars-health = { version = "1.0.0", path = "./packages/health" } +mars-outpost = { version = "1.0.0", path = "./packages/outpost" } +mars-rover = { version = "1.0.0", path = "./packages/rover" } # contracts mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } diff --git a/Makefile.toml b/Makefile.toml index becad4707..63df086a9 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -14,7 +14,7 @@ script = """ if [[ $(arch) == "arm64" ]]; then image="cosmwasm/workspace-optimizer-arm64:0.12.8" else - image="cosmwasm/workspace-optimizer:0.12.9" + image="cosmwasm/workspace-optimizer:0.12.10" fi docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ diff --git a/README.md b/README.md index c5889a2f4..02c434bc9 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ Requires building the wasm binaries for the contracts: docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.12.9 + cosmwasm/rust-optimizer:0.12.10 ``` For Rust cw-multi tests + osmosis-testing suite (requires mars_swapper_osmosis.wasm from previous step): diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index f5c19039c..264cde48c 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -18,20 +18,20 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cosmwasm-vault-standard = { workspace = true } -cw2 = { workspace = true } -cw721 = { workspace = true } -cw721-base = { workspace = true } -cw-controllers = { workspace = true } -cw-item-set = { workspace = true } -cw-storage-plus = { workspace = true } -mars-account-nft = { workspace = true } -mars-health = { workspace = true } -mars-outpost = { workspace = true } -mars-oracle-adapter = { workspace = true } -mars-rover = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cosmwasm-vault-standard = { workspace = true } +cw2 = { workspace = true } +cw721 = { workspace = true } +cw721-base = { workspace = true } +cw-controllers-admin-fork = { workspace = true } +cw-item-set = { workspace = true } +cw-storage-plus = { workspace = true } +mars-account-nft = { workspace = true } +mars-health = { workspace = true } +mars-outpost = { workspace = true } +mars-oracle-adapter = { workspace = true } +mars-rover = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 18d4c9c9b..97691b85e 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -8,7 +8,7 @@ use mars_rover::error::{ContractError, ContractResult}; use mars_rover::msg::query::HealthResponse; use mars_rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::execute::{create_credit_account, dispatch_actions, execute_callback, update_config}; +use crate::execute::{create_credit_account, dispatch_actions, execute_callback}; use crate::health::compute_health; use crate::instantiate::store_config; use crate::query::{ @@ -17,6 +17,7 @@ use crate::query::{ query_config, query_positions, query_total_debt_shares, query_total_vault_coin_balance, query_vault_configs, }; +use crate::update_config::{update_admin, update_config}; use crate::vault::handle_unlock_request_reply; use crate::zap::{estimate_provide_liquidity, estimate_withdraw_liquidity}; @@ -49,6 +50,7 @@ pub fn execute( match msg { ExecuteMsg::CreateCreditAccount {} => create_credit_account(deps, info.sender), ExecuteMsg::UpdateConfig { new_config } => update_config(deps, info, new_config), + ExecuteMsg::UpdateAdmin(update) => update_admin(deps, info, update), ExecuteMsg::Callback(callback) => execute_callback(deps, info, env, callback), ExecuteMsg::UpdateCreditAccount { account_id, diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index f8dbde1d9..c412b0554 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -6,23 +6,14 @@ use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; use mars_rover::coins::Coins; use mars_rover::error::{ContractError, ContractResult}; use mars_rover::msg::execute::{Action, CallbackMsg}; -use mars_rover::msg::instantiate::ConfigUpdates; -use mars_rover::traits::{FallbackStr, Stringify}; use crate::borrow::borrow; use crate::deposit::deposit; use crate::health::assert_below_max_ltv; -use crate::instantiate::{ - assert_lte_to_one, assert_no_duplicate_coins, assert_no_duplicate_vaults, -}; use crate::liquidate_coin::liquidate_coin; use crate::refund::refund_coin_balances; use crate::repay::repay; -use crate::state::ADMIN; -use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, SWAPPER, - VAULT_CONFIGS, ZAPPER, -}; +use crate::state::ACCOUNT_NFT; use crate::swap::swap_exact_in; use crate::update_coin_balances::update_coin_balance; use crate::utils::{assert_is_token_owner, assert_not_contract_in_config}; @@ -49,105 +40,6 @@ pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult ContractResult { - ADMIN.assert_admin(deps.as_ref(), &info.sender)?; - - let mut response = - Response::new().add_attribute("action", "rover/credit-manager/update_config"); - - if let Some(addr_str) = new_config.account_nft { - let validated = deps.api.addr_validate(&addr_str)?; - ACCOUNT_NFT.save(deps.storage, &validated)?; - - // Accept minter role. NFT contract minter must have proposed Rover as a new minter first. - let accept_minter_role_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: addr_str.clone(), - funds: vec![], - msg: to_binary(&NftExecuteMsg::AcceptMinterRole {})?, - }); - - response = response - .add_message(accept_minter_role_msg) - .add_attribute("key", "account_nft") - .add_attribute("value", addr_str); - } - - if let Some(coins) = new_config.allowed_coins { - assert_no_duplicate_coins(&coins)?; - ALLOWED_COINS.clear(deps.storage); - coins - .iter() - .try_for_each(|denom| ALLOWED_COINS.insert(deps.storage, denom).map(|_| ()))?; - - response = response - .add_attribute("key", "allowed_coins") - .add_attribute("value", coins.join(", ").fallback("None")); - } - - if let Some(configs) = new_config.vault_configs { - assert_no_duplicate_vaults(&configs)?; - VAULT_CONFIGS.clear(deps.storage); - configs.iter().try_for_each(|v| -> ContractResult<_> { - v.config.check()?; - let vault = v.vault.check(deps.api)?; - Ok(VAULT_CONFIGS.save(deps.storage, &vault.address, &v.config)?) - })?; - response = response - .add_attribute("key", "vault_configs") - .add_attribute("value", configs.to_string().fallback("None")) - } - - if let Some(unchecked) = new_config.oracle { - ORACLE.save(deps.storage, &unchecked.check(deps.api)?)?; - response = response - .add_attribute("key", "oracle") - .add_attribute("value", unchecked.address()); - } - - if let Some(unchecked) = new_config.swapper { - SWAPPER.save(deps.storage, &unchecked.check(deps.api)?)?; - response = response - .add_attribute("key", "swapper") - .add_attribute("value", unchecked.address()); - } - - if let Some(unchecked) = new_config.zapper { - ZAPPER.save(deps.storage, &unchecked.check(deps.api)?)?; - response = response - .add_attribute("key", "zapper") - .add_attribute("value", unchecked.address()); - } - - if let Some(cf) = new_config.max_close_factor { - assert_lte_to_one(&cf)?; - MAX_CLOSE_FACTOR.save(deps.storage, &cf)?; - response = response - .add_attribute("key", "max_close_factor") - .add_attribute("value", cf.to_string()); - } - - if let Some(num) = new_config.max_unlocking_positions { - MAX_UNLOCKING_POSITIONS.save(deps.storage, &num)?; - response = response - .add_attribute("key", "max_unlocking_positions") - .add_attribute("value", num.to_string()); - } - - if let Some(addr_str) = new_config.admin { - let validated = deps.api.addr_validate(&addr_str)?; - ADMIN.set(deps, Some(validated))?; - response = response - .add_attribute("key", "admin") - .add_attribute("value", addr_str); - } - - Ok(response) -} - pub fn dispatch_actions( deps: DepsMut, env: Env, diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 16b0b575c..1edde5f26 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use cosmwasm_std::{Decimal, DepsMut}; +use cw_controllers_admin_fork::AdminUpdate::InitializeAdmin; use mars_rover::error::ContractError::InvalidConfig; use mars_rover::error::ContractResult; use mars_rover::msg::instantiate::VaultInstantiateConfig; @@ -14,6 +15,9 @@ use crate::state::{ }; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { + let admin = deps.api.addr_validate(&msg.admin)?; + ADMIN.update(deps.storage, InitializeAdmin { admin })?; + RED_BANK.save(deps.storage, &msg.red_bank.check(deps.api)?)?; ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; SWAPPER.save(deps.storage, &msg.swapper.check(deps.api)?)?; @@ -37,9 +41,6 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { .iter() .try_for_each(|denom| ALLOWED_COINS.insert(deps.storage, denom).map(|_| ()))?; - let admin = deps.api.addr_validate(&msg.admin)?; - ADMIN.set(deps, Some(admin))?; - Ok(()) } diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 27a76c029..ae6f9d3cd 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -12,6 +12,7 @@ pub mod repay; pub mod state; pub mod swap; pub mod update_coin_balances; +pub mod update_config; pub mod utils; pub mod vault; pub mod withdraw; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 7a0bd6bef..0fcc4cd11 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -20,9 +20,11 @@ use crate::utils::debt_shares_to_amount; const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; -pub fn query_config(deps: Deps) -> StdResult { +pub fn query_config(deps: Deps) -> ContractResult { + let admin_state = ADMIN.query(deps.storage)?; Ok(ConfigResponse { - admin: ADMIN.get(deps)?.map(Into::into), + admin: admin_state.admin, + proposed_new_admin: admin_state.proposed, account_nft: ACCOUNT_NFT .may_load(deps.storage)? .map(|addr| addr.to_string()), diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 5d5b42e1c..de1b704b3 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{Addr, Decimal, Uint128}; -use cw_controllers::Admin; +use cw_controllers_admin_fork::Admin; use cw_item_set::Set; use cw_storage_plus::{Item, Map}; diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs new file mode 100644 index 000000000..1763fe96d --- /dev/null +++ b/contracts/credit-manager/src/update_config.rs @@ -0,0 +1,115 @@ +use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, MessageInfo, Response, WasmMsg}; + +use cw_controllers_admin_fork::AdminExecuteUpdate; +use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; +use mars_rover::error::ContractResult; +use mars_rover::msg::instantiate::ConfigUpdates; +use mars_rover::traits::{FallbackStr, Stringify}; + +use crate::instantiate::{ + assert_lte_to_one, assert_no_duplicate_coins, assert_no_duplicate_vaults, +}; +use crate::state::ADMIN; +use crate::state::{ + ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, SWAPPER, + VAULT_CONFIGS, ZAPPER, +}; + +pub fn update_config( + deps: DepsMut, + info: MessageInfo, + new_config: ConfigUpdates, +) -> ContractResult { + ADMIN.assert_admin(deps.storage, &info.sender)?; + + let mut response = + Response::new().add_attribute("action", "rover/credit-manager/update_config"); + + if let Some(addr_str) = new_config.account_nft { + let validated = deps.api.addr_validate(&addr_str)?; + ACCOUNT_NFT.save(deps.storage, &validated)?; + + // Accept minter role. NFT contract minter must have proposed Rover as a new minter first. + let accept_minter_role_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: addr_str.clone(), + funds: vec![], + msg: to_binary(&NftExecuteMsg::AcceptMinterRole {})?, + }); + + response = response + .add_message(accept_minter_role_msg) + .add_attribute("key", "account_nft") + .add_attribute("value", addr_str); + } + + if let Some(coins) = new_config.allowed_coins { + assert_no_duplicate_coins(&coins)?; + ALLOWED_COINS.clear(deps.storage); + coins + .iter() + .try_for_each(|denom| ALLOWED_COINS.insert(deps.storage, denom).map(|_| ()))?; + + response = response + .add_attribute("key", "allowed_coins") + .add_attribute("value", coins.join(", ").fallback("None")); + } + + if let Some(configs) = new_config.vault_configs { + assert_no_duplicate_vaults(&configs)?; + VAULT_CONFIGS.clear(deps.storage); + configs.iter().try_for_each(|v| -> ContractResult<_> { + v.config.check()?; + let vault = v.vault.check(deps.api)?; + Ok(VAULT_CONFIGS.save(deps.storage, &vault.address, &v.config)?) + })?; + response = response + .add_attribute("key", "vault_configs") + .add_attribute("value", configs.to_string().fallback("None")) + } + + if let Some(unchecked) = new_config.oracle { + ORACLE.save(deps.storage, &unchecked.check(deps.api)?)?; + response = response + .add_attribute("key", "oracle") + .add_attribute("value", unchecked.address()); + } + + if let Some(unchecked) = new_config.swapper { + SWAPPER.save(deps.storage, &unchecked.check(deps.api)?)?; + response = response + .add_attribute("key", "swapper") + .add_attribute("value", unchecked.address()); + } + + if let Some(unchecked) = new_config.zapper { + ZAPPER.save(deps.storage, &unchecked.check(deps.api)?)?; + response = response + .add_attribute("key", "zapper") + .add_attribute("value", unchecked.address()); + } + + if let Some(cf) = new_config.max_close_factor { + assert_lte_to_one(&cf)?; + MAX_CLOSE_FACTOR.save(deps.storage, &cf)?; + response = response + .add_attribute("key", "max_close_factor") + .add_attribute("value", cf.to_string()); + } + + if let Some(num) = new_config.max_unlocking_positions { + MAX_UNLOCKING_POSITIONS.save(deps.storage, &num)?; + response = response + .add_attribute("key", "max_unlocking_positions") + .add_attribute("value", num.to_string()); + } + + Ok(response) +} + +pub fn update_admin( + deps: DepsMut, + info: MessageInfo, + update: AdminExecuteUpdate, +) -> ContractResult { + Ok(ADMIN.execute_update(deps, info, update)?) +} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 1a4167446..89e8b8c2c 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -6,6 +6,7 @@ use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; use cosmwasm_vault_standard::extensions::lockup::{LockupQueryMsg, UnlockingPosition}; use cosmwasm_vault_standard::msg::VaultStandardQueryMsg::{Info as VaultInfoMsg, VaultExtension}; use cosmwasm_vault_standard::msg::{ExtensionQueryMsg, VaultInfoResponse}; +use cw_controllers_admin_fork::AdminExecuteUpdate; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use mars_account_nft::msg::InstantiateMsg as NftInstantiateMsg; @@ -191,6 +192,19 @@ impl MockEnv { ) } + pub fn update_admin( + &mut self, + sender: &Addr, + update: AdminExecuteUpdate, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.rover.clone(), + &ExecuteMsg::UpdateAdmin(update), + &[], + ) + } + //-------------------------------------------------------------------------------------------------- // Queries //-------------------------------------------------------------------------------------------------- diff --git a/contracts/credit-manager/tests/test_update_admin.rs b/contracts/credit-manager/tests/test_update_admin.rs new file mode 100644 index 000000000..0ee3f8e33 --- /dev/null +++ b/contracts/credit-manager/tests/test_update_admin.rs @@ -0,0 +1,157 @@ +use cosmwasm_std::Addr; +use cw_controllers_admin_fork::AdminError::{NotAdmin, NotProposedAdmin, StateTransitionError}; +use cw_controllers_admin_fork::AdminExecuteUpdate; +use mars_rover::error::ContractError::AdminError; + +use crate::helpers::{assert_err, MockEnv}; + +pub mod helpers; + +#[test] +fn test_initialized_state() { + let mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_config(); + + assert!(original_config.admin.is_some()); + assert!(original_config.proposed_new_admin.is_none()); +} + +#[test] +fn test_propose_new_admin() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_config(); + + let new_admin = "new_admin".to_string(); + + // only admin can propose new admins + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.update_admin( + &bad_guy, + AdminExecuteUpdate::ProposeNewAdmin { + proposed: bad_guy.to_string(), + }, + ); + assert_err(res, AdminError(NotAdmin {})); + + mock.update_admin( + &Addr::unchecked(original_config.admin.clone().unwrap()), + AdminExecuteUpdate::ProposeNewAdmin { + proposed: new_admin.clone(), + }, + ) + .unwrap(); + + let new_config = mock.query_config(); + + assert_eq!(new_config.admin, original_config.admin); + assert_ne!( + new_config.proposed_new_admin, + original_config.proposed_new_admin + ); + assert_eq!(new_config.proposed_new_admin, Some(new_admin)); +} + +#[test] +fn test_clear_proposed() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_config(); + + let new_admin = "new_admin".to_string(); + + mock.update_admin( + &Addr::unchecked(original_config.admin.clone().unwrap()), + AdminExecuteUpdate::ProposeNewAdmin { + proposed: new_admin.clone(), + }, + ) + .unwrap(); + + let interim_config = mock.query_config(); + + assert_eq!(interim_config.proposed_new_admin, Some(new_admin)); + + // only admin can clear + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.update_admin(&bad_guy, AdminExecuteUpdate::ClearProposed); + assert_err(res, AdminError(NotAdmin {})); + + mock.update_admin( + &Addr::unchecked(original_config.admin.clone().unwrap()), + AdminExecuteUpdate::ClearProposed, + ) + .unwrap(); + + let latest_config = mock.query_config(); + + assert_eq!(latest_config.admin, original_config.admin); + assert_ne!( + latest_config.proposed_new_admin, + interim_config.proposed_new_admin + ); + assert_eq!(latest_config.proposed_new_admin, None); +} + +#[test] +fn test_accept_admin_role() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_config(); + + let new_admin = "new_admin".to_string(); + + mock.update_admin( + &Addr::unchecked(original_config.admin.clone().unwrap()), + AdminExecuteUpdate::ProposeNewAdmin { + proposed: new_admin.clone(), + }, + ) + .unwrap(); + + // Only proposed admin can accept + let res = mock.update_admin( + &Addr::unchecked(original_config.admin.unwrap()), + AdminExecuteUpdate::AcceptProposed, + ); + assert_err(res, AdminError(NotProposedAdmin {})); + + mock.update_admin( + &Addr::unchecked(new_admin.clone()), + AdminExecuteUpdate::AcceptProposed, + ) + .unwrap(); + + let new_config = mock.query_config(); + + assert_eq!(new_config.admin.unwrap(), new_admin); + assert_eq!(new_config.proposed_new_admin, None); +} + +#[test] +fn test_abolish_admin_role() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_config(); + + // Only admin can abolish role + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.update_admin(&bad_guy, AdminExecuteUpdate::AbolishAdminRole); + assert_err(res, AdminError(NotAdmin {})); + + mock.update_admin( + &Addr::unchecked(original_config.admin.clone().unwrap()), + AdminExecuteUpdate::AbolishAdminRole, + ) + .unwrap(); + + let new_config = mock.query_config(); + + assert_eq!(new_config.admin, None); + assert_eq!(new_config.proposed_new_admin, None); + + // No new updates can occur + let res = mock.update_admin( + &Addr::unchecked(original_config.admin.clone().unwrap()), + AdminExecuteUpdate::InitializeAdmin { + admin: original_config.admin.unwrap(), + }, + ); + assert_err(res, AdminError(StateTransitionError {})); +} diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index e73a6dcc1..e212bf36d 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -19,7 +19,6 @@ fn test_only_admin_can_update_config() { &new_admin, ConfigUpdates { account_nft: None, - admin: Some(new_admin.to_string()), allowed_coins: None, oracle: None, max_close_factor: None, @@ -43,7 +42,6 @@ fn test_raises_on_invalid_vaults_config() { &Addr::unchecked(original_config.admin.clone().unwrap()), ConfigUpdates { account_nft: None, - admin: None, allowed_coins: None, oracle: None, max_close_factor: None, @@ -73,7 +71,6 @@ fn test_raises_on_invalid_vaults_config() { &Addr::unchecked(original_config.admin.unwrap()), ConfigUpdates { account_nft: None, - admin: None, allowed_coins: None, oracle: None, max_close_factor: None, @@ -108,7 +105,6 @@ fn test_update_config_works_with_full_config() { let original_vault_configs = mock.query_vault_configs(None, None); let new_nft_contract = mock.deploy_new_nft_contract().unwrap(); - let new_admin = Addr::unchecked("new_admin"); let new_vault_configs = vec![VaultInstantiateConfig { vault: VaultBase::new("vault_contract_3000".to_string()), config: VaultConfig { @@ -129,7 +125,6 @@ fn test_update_config_works_with_full_config() { &Addr::unchecked(original_config.admin.clone().unwrap()), ConfigUpdates { account_nft: Some(new_nft_contract.to_string()), - admin: Some(new_admin.to_string()), allowed_coins: Some(new_allowed_coins.clone()), oracle: Some(new_oracle.clone()), max_close_factor: Some(new_close_factor), @@ -148,8 +143,10 @@ fn test_update_config_works_with_full_config() { assert_eq!(new_config.account_nft, Some(new_nft_contract.to_string())); assert_ne!(new_config.account_nft, original_config.account_nft); - assert_eq!(new_config.admin.clone().unwrap(), new_admin); - assert_ne!(new_config.admin, original_config.admin); + assert_eq!( + new_config.admin.unwrap(), + original_config.admin.clone().unwrap() + ); assert_eq!(new_queried_vault_configs, new_vault_configs); assert_ne!(new_queried_vault_configs, original_vault_configs); @@ -319,7 +316,6 @@ fn test_raises_on_duplicate_vault_configs() { &Addr::unchecked(original_config.admin.unwrap()), ConfigUpdates { account_nft: None, - admin: None, allowed_coins: None, oracle: None, max_close_factor: None, @@ -365,7 +361,6 @@ fn test_raises_on_duplicate_coin_configs() { &Addr::unchecked(original_config.admin.unwrap()), ConfigUpdates { account_nft: None, - admin: None, allowed_coins: Some(vec![ "uosmo".to_string(), "uatom".to_string(), diff --git a/contracts/oracle-adapter/Cargo.toml b/contracts/oracle-adapter/Cargo.toml index 31340808e..4ca00e499 100644 --- a/contracts/oracle-adapter/Cargo.toml +++ b/contracts/oracle-adapter/Cargo.toml @@ -18,14 +18,14 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -cw-controllers = { workspace = true } -cw-storage-plus = { workspace = true } -mars-outpost = { workspace = true } -mars-rover = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw-controllers-admin-fork = { workspace = true } +cw-storage-plus = { workspace = true } +mars-outpost = { workspace = true } +mars-rover = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/oracle-adapter/src/contract.rs b/contracts/oracle-adapter/src/contract.rs index 9cb62cf81..7c4e9fd22 100644 --- a/contracts/oracle-adapter/src/contract.rs +++ b/contracts/oracle-adapter/src/contract.rs @@ -6,6 +6,8 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw_storage_plus::Bound; +use cw_controllers_admin_fork::AdminExecuteUpdate; +use cw_controllers_admin_fork::AdminUpdate::InitializeAdmin; use mars_outpost::oracle::PriceResponse; use mars_rover::adapters::vault::VaultBase; use mars_rover::adapters::Oracle; @@ -30,13 +32,16 @@ pub fn instantiate( _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> StdResult { +) -> ContractResult { set_contract_version( deps.storage, &format!("crates.io:{}", CONTRACT_NAME), CONTRACT_VERSION, )?; + let admin = deps.api.addr_validate(&msg.admin)?; + ADMIN.update(deps.storage, InitializeAdmin { admin })?; + let oracle = msg.oracle.check(deps.api)?; ORACLE.save(deps.storage, &oracle)?; @@ -44,9 +49,6 @@ pub fn instantiate( VAULT_PRICING_INFO.save(deps.storage, &info.vault_coin_denom, &info)?; } - let admin = deps.api.addr_validate(&msg.admin)?; - ADMIN.set(deps, Some(admin))?; - Ok(Response::default()) } @@ -59,6 +61,7 @@ pub fn execute( ) -> ContractResult { match msg { ExecuteMsg::UpdateConfig { new_config } => update_config(deps, info, new_config), + ExecuteMsg::UpdateAdmin(update) => update_admin(deps, info, update), } } @@ -139,8 +142,10 @@ fn calculate_preview_redeem( } fn query_config(deps: Deps) -> ContractResult { + let res = ADMIN.query(deps.storage)?; Ok(ConfigResponse { - admin: ADMIN.get(deps)?, + admin: res.admin, + proposed_new_admin: res.proposed, oracle: ORACLE.load(deps.storage)?, }) } @@ -150,7 +155,7 @@ pub fn update_config( info: MessageInfo, new_config: ConfigUpdates, ) -> ContractResult { - ADMIN.assert_admin(deps.as_ref(), &info.sender)?; + ADMIN.assert_admin(deps.storage, &info.sender)?; let mut response = Response::new().add_attribute("action", "rover/oracle-adapter/update_config"); @@ -181,13 +186,13 @@ pub fn update_config( .add_attribute("value", value_str); } - if let Some(addr_str) = new_config.admin { - let validated = deps.api.addr_validate(&addr_str)?; - ADMIN.set(deps, Some(validated))?; - response = response - .add_attribute("key", "owner") - .add_attribute("value", addr_str); - } - Ok(response) } + +pub fn update_admin( + deps: DepsMut, + info: MessageInfo, + update: AdminExecuteUpdate, +) -> ContractResult { + Ok(ADMIN.execute_update(deps, info, update)?) +} diff --git a/contracts/oracle-adapter/src/error.rs b/contracts/oracle-adapter/src/error.rs index c123289ba..145f06d73 100644 --- a/contracts/oracle-adapter/src/error.rs +++ b/contracts/oracle-adapter/src/error.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{CheckedFromRatioError, DecimalRangeExceeded, OverflowError, StdError}; -use cw_controllers::AdminError; +use cw_controllers_admin_fork::AdminError; use thiserror::Error; use mars_rover::error::ContractError as RoverError; diff --git a/contracts/oracle-adapter/src/msg.rs b/contracts/oracle-adapter/src/msg.rs index eabd360fe..d0f700f13 100644 --- a/contracts/oracle-adapter/src/msg.rs +++ b/contracts/oracle-adapter/src/msg.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Decimal}; +use cw_controllers_admin_fork::AdminExecuteUpdate; use mars_rover::adapters::{Oracle, OracleUnchecked}; @@ -19,6 +20,7 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { UpdateConfig { new_config: ConfigUpdates }, + UpdateAdmin(AdminExecuteUpdate), } #[cw_serde] @@ -43,14 +45,14 @@ pub enum QueryMsg { #[cw_serde] pub struct ConfigResponse { - pub admin: Option, + pub admin: Option, + pub proposed_new_admin: Option, pub oracle: Oracle, } #[cw_serde] #[derive(Default)] pub struct ConfigUpdates { - pub admin: Option, pub oracle: Option, pub vault_pricing: Option>, } diff --git a/contracts/oracle-adapter/src/state.rs b/contracts/oracle-adapter/src/state.rs index 28f1dedea..42ef50d08 100644 --- a/contracts/oracle-adapter/src/state.rs +++ b/contracts/oracle-adapter/src/state.rs @@ -1,4 +1,4 @@ -use cw_controllers::Admin; +use cw_controllers_admin_fork::Admin; use cw_storage_plus::{Item, Map}; use mars_rover::adapters::Oracle; diff --git a/contracts/oracle-adapter/tests/helpers.rs b/contracts/oracle-adapter/tests/helpers.rs index fc8c145f3..adc4778db 100644 --- a/contracts/oracle-adapter/tests/helpers.rs +++ b/contracts/oracle-adapter/tests/helpers.rs @@ -35,14 +35,14 @@ pub fn instantiate_oracle_adapter(app: &mut BasicApp) -> Addr { let vault_pricing_info = deploy_vault(app, oracle.clone().into(), mock_vault_info()); starting_vault_deposit(app, &vault_pricing_info); - let owner = Addr::unchecked("owner"); + let admin = Addr::unchecked("admin"); app.instantiate_contract( code_id, - owner.clone(), + admin.clone(), &InstantiateMsg { oracle: oracle.into(), vault_pricing: vec![vault_pricing_info], - admin: owner.to_string(), + admin: admin.to_string(), }, &[], "mars-oracle-adapter", @@ -127,7 +127,7 @@ fn deploy_oracle(app: &mut BasicApp) -> OracleBase { let addr = app .instantiate_contract( code_id, - Addr::unchecked("oracle_contract_owner"), + Addr::unchecked("oracle_contract_admin"), &OracleInstantiateMsg { prices: vec![ CoinPrice { diff --git a/contracts/oracle-adapter/tests/test_update_admin.rs b/contracts/oracle-adapter/tests/test_update_admin.rs new file mode 100644 index 000000000..b26204720 --- /dev/null +++ b/contracts/oracle-adapter/tests/test_update_admin.rs @@ -0,0 +1,174 @@ +use cosmwasm_std::Addr; +use cw_controllers_admin_fork::AdminExecuteUpdate; +use cw_multi_test::{App, Executor}; + +use mars_oracle_adapter::msg::{ConfigResponse, ExecuteMsg, QueryMsg}; + +use crate::helpers::instantiate_oracle_adapter; + +pub mod helpers; + +#[test] +fn test_initialized_state() { + let mut app = App::default(); + let contract_addr = instantiate_oracle_adapter(&mut app); + let original_config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + assert!(original_config.admin.is_some()); + assert!(original_config.proposed_new_admin.is_none()); +} + +#[test] +fn test_propose_new_admin() { + let mut app = App::default(); + let contract_addr = instantiate_oracle_adapter(&mut app); + let original_config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + let new_admin = "new_admin".to_string(); + + // only admin can propose new admins + let bad_guy = Addr::unchecked("bad_guy"); + app.execute_contract( + bad_guy.clone(), + contract_addr.clone(), + &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::ProposeNewAdmin { + proposed: bad_guy.to_string(), + }), + &[], + ) + .unwrap_err(); + + app.execute_contract( + Addr::unchecked(original_config.admin.clone().unwrap()), + contract_addr.clone(), + &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::ProposeNewAdmin { + proposed: new_admin.clone(), + }), + &[], + ) + .unwrap(); + + let new_config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + assert_eq!(new_config.admin, original_config.admin); + assert_ne!( + new_config.proposed_new_admin, + original_config.proposed_new_admin + ); + assert_eq!(new_config.proposed_new_admin, Some(new_admin)); +} + +#[test] +fn test_clear_proposed() { + let mut app = App::default(); + let contract_addr = instantiate_oracle_adapter(&mut app); + let original_config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + let new_admin = "new_admin".to_string(); + + app.execute_contract( + Addr::unchecked(original_config.admin.clone().unwrap()), + contract_addr.clone(), + &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::ProposeNewAdmin { + proposed: new_admin.clone(), + }), + &[], + ) + .unwrap(); + + let interim_config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + assert_eq!(interim_config.proposed_new_admin, Some(new_admin)); + + // only admin can clear + let bad_guy = Addr::unchecked("bad_guy"); + app.execute_contract( + bad_guy, + contract_addr.clone(), + &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::ClearProposed), + &[], + ) + .unwrap_err(); + + app.execute_contract( + Addr::unchecked(original_config.admin.clone().unwrap()), + contract_addr.clone(), + &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::ClearProposed), + &[], + ) + .unwrap(); + + let latest_config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + assert_eq!(latest_config.admin, original_config.admin); + assert_ne!( + latest_config.proposed_new_admin, + interim_config.proposed_new_admin + ); + assert_eq!(latest_config.proposed_new_admin, None); +} + +#[test] +fn test_accept_admin_role() { + let mut app = App::default(); + let contract_addr = instantiate_oracle_adapter(&mut app); + let original_config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + let new_admin = "new_admin".to_string(); + + app.execute_contract( + Addr::unchecked(original_config.admin.clone().unwrap()), + contract_addr.clone(), + &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::ProposeNewAdmin { + proposed: new_admin.clone(), + }), + &[], + ) + .unwrap(); + + // Only proposed admin can accept + app.execute_contract( + Addr::unchecked(original_config.admin.unwrap()), + contract_addr.clone(), + &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::AcceptProposed), + &[], + ) + .unwrap_err(); + + app.execute_contract( + Addr::unchecked(new_admin.clone()), + contract_addr.clone(), + &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::AcceptProposed), + &[], + ) + .unwrap(); + + let new_config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + assert_eq!(new_config.admin.unwrap(), new_admin); + assert_eq!(new_config.proposed_new_admin, None); +} diff --git a/contracts/oracle-adapter/tests/test_update_config.rs b/contracts/oracle-adapter/tests/test_update_config.rs index ac19e8150..dd2f82e35 100644 --- a/contracts/oracle-adapter/tests/test_update_config.rs +++ b/contracts/oracle-adapter/tests/test_update_config.rs @@ -1,5 +1,5 @@ use cosmwasm_std::Addr; -use cw_controllers::AdminError::NotAdmin; +use cw_controllers_admin_fork::AdminError::NotAdmin; use cw_multi_test::{App, Executor}; use mars_oracle_adapter::error::ContractError::AdminError; @@ -39,16 +39,14 @@ fn test_update_config_works_with_full_config() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - let new_owner = Addr::unchecked("new_owner"); let new_oracle = OracleUnchecked::new("new_oracle".to_string()); let new_vault_pricing = vec![]; app.execute_contract( - original_config.admin.clone().unwrap(), + Addr::unchecked(original_config.admin.clone().unwrap()), contract_addr.clone(), &ExecuteMsg::UpdateConfig { new_config: ConfigUpdates { - admin: Some(new_owner.to_string()), oracle: Some(new_oracle), vault_pricing: Some(new_vault_pricing), }, @@ -62,8 +60,11 @@ fn test_update_config_works_with_full_config() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - assert_ne!(new_config.admin, original_config.admin); - assert_eq!(new_config.admin, Some(new_owner)); + assert_eq!(new_config.admin, original_config.admin); + assert_eq!( + new_config.proposed_new_admin, + original_config.proposed_new_admin + ); assert_ne!(new_config.oracle, original_config.oracle); assert_eq!( @@ -106,11 +107,10 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { .unwrap(); app.execute_contract( - original_config.admin.clone().unwrap(), + Addr::unchecked(original_config.admin.clone().unwrap()), contract_addr.clone(), &ExecuteMsg::UpdateConfig { new_config: ConfigUpdates { - admin: None, oracle: None, vault_pricing: None, }, @@ -125,6 +125,10 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { .unwrap(); assert_eq!(new_config.admin, original_config.admin); + assert_eq!( + new_config.proposed_new_admin, + original_config.proposed_new_admin + ); assert_eq!(new_config.oracle, original_config.oracle); let new_pricing_infos: Vec = app diff --git a/packages/controllers/Cargo.toml b/packages/controllers/Cargo.toml new file mode 100644 index 000000000..01b68fde1 --- /dev/null +++ b/packages/controllers/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "cw-controllers-admin-fork" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-utils = { workspace = true } +cw-storage-plus = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } diff --git a/packages/controllers/src/admin.rs b/packages/controllers/src/admin.rs new file mode 100644 index 000000000..a47a83a04 --- /dev/null +++ b/packages/controllers/src/admin.rs @@ -0,0 +1,984 @@ +use std::fmt::Debug; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + Addr, Api, CustomQuery, DepsMut, MessageInfo, Response, StdError, StdResult, Storage, +}; +use cw_storage_plus::Item; +use schemars::JsonSchema; +use thiserror::Error; + +/// Returned from Admin.query() +#[cw_serde] +pub struct AdminResponse { + pub admin: Option, + pub proposed: Option, +} + +/// Errors returned from Admin state transitions +#[derive(Error, Debug, PartialEq)] +pub enum AdminError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Caller is not admin")] + NotAdmin {}, + + #[error("Caller is not the proposed admin")] + NotProposedAdmin {}, + + #[error("Admin state transition was not valid")] + StateTransitionError {}, +} + +type AdminResult = Result; + +/// The finite states that are possible +#[cw_serde] +enum AdminState { + A(AdminUninitialized), + B(AdminSetNoneProposed), + C(AdminSetWithProposed), + D(AdminRoleAbolished), +} + +#[cw_serde] +struct AdminUninitialized; + +impl AdminUninitialized { + pub fn initialize(&self, admin: &Addr) -> AdminState { + AdminState::B(AdminSetNoneProposed { + admin: admin.clone(), + }) + } + + pub fn abolish_admin_role(&self) -> AdminState { + AdminState::D(AdminRoleAbolished) + } +} + +#[cw_serde] +struct AdminSetNoneProposed { + admin: Addr, +} + +impl AdminSetNoneProposed { + pub fn propose(self, proposed: &Addr) -> AdminState { + AdminState::C(AdminSetWithProposed { + admin: self.admin, + proposed: proposed.clone(), + }) + } + + pub fn abolish_admin_role(self) -> AdminState { + AdminState::D(AdminRoleAbolished) + } +} + +#[cw_serde] +struct AdminSetWithProposed { + admin: Addr, + proposed: Addr, +} + +impl AdminSetWithProposed { + pub fn clear_proposed(self) -> AdminState { + AdminState::B(AdminSetNoneProposed { admin: self.admin }) + } + + pub fn accept_proposed(self) -> AdminState { + AdminState::B(AdminSetNoneProposed { + admin: self.proposed, + }) + } + + pub fn abolish_admin_role(self) -> AdminState { + AdminState::D(AdminRoleAbolished) + } +} + +#[cw_serde] +struct AdminRoleAbolished; + +#[cw_serde] +pub enum AdminUpdate { + /// Sets the initial admin when none. No restrictions permissions to modify. + InitializeAdmin { admin: Addr }, + /// Proposes a new admin to take role. Only current admin can execute. + ProposeNewAdmin { sender: Addr, proposed: Addr }, + /// Clears the currently proposed admin. Only current admin can execute. + ClearProposed { sender: Addr }, + /// Promotes the proposed admin to be the current one. Only the proposed admin can execute. + AcceptProposed { sender: Addr }, + /// Throws away the keys to the Admin role forever. Once done, no admin can ever be set later. + /// Requires Admin permission except if event is dispatched from AdminUninitialized state. + AbolishAdminRole { sender: Option }, +} + +impl<'a> AdminUpdate { + fn from(api: &'a dyn Api, sender: &Addr, update: AdminExecuteUpdate) -> StdResult { + Ok(match update { + AdminExecuteUpdate::InitializeAdmin { admin } => { + let validated = api.addr_validate(&admin)?; + AdminUpdate::InitializeAdmin { admin: validated } + } + AdminExecuteUpdate::ProposeNewAdmin { proposed } => { + let validated = api.addr_validate(&proposed)?; + AdminUpdate::ProposeNewAdmin { + sender: sender.clone(), + proposed: validated, + } + } + AdminExecuteUpdate::ClearProposed => AdminUpdate::ClearProposed { + sender: sender.clone(), + }, + AdminExecuteUpdate::AcceptProposed => AdminUpdate::AcceptProposed { + sender: sender.clone(), + }, + AdminExecuteUpdate::AbolishAdminRole => AdminUpdate::AbolishAdminRole { + sender: Some(sender.clone()), + }, + }) + } +} + +/// Same as above, but used for execute helpers. Sender and inputs are validated. +#[cw_serde] +pub enum AdminExecuteUpdate { + InitializeAdmin { admin: String }, + ProposeNewAdmin { proposed: String }, + ClearProposed, + AcceptProposed, + AbolishAdminRole, +} + +/// A struct designed to help facilitate a two-step transition between contract admins safely. +/// It implements a finite state machine with dispatched events to manage state transitions. +/// State A: AdminUninitialized +/// - No restrictions on who can initialize the admin role +/// State B: AdminSetNoneProposed +/// - Once admin is set. Only they can execute the following updates: +/// - ProposeNewAdmin +/// - ClearProposed +/// State C: AdminSetWithProposed +/// - Only the proposed new admin can accept the new role via AcceptProposed {} +/// - The current admin can also clear the proposed new admin via ClearProposed {} +/// +///```text +/// Clear Proposed +/// +-------------------------------------^ +/// | | +/// v | +/// +----------------+ +----------------+ +-------+--------+ +/// | Admin: None | Initialize Admin | Admin: Gabe | Propose New Admin | Admin: Gabe | +/// | Proposed: None +--------------------->| Proposed: None +---------------------->| Proposed: Joy | +/// +-----+----------+ ++---------------+ +-------+----+---+ +/// | | Admin: Joy | | +/// | | Proposed: None | | +/// Abolish Role | ^ | | +/// | *immutable | | Accept Proposed | | +/// | +----------------+ | <----------------------------------------+ | +/// +----------->| Admin: None | | | +/// | Proposed: None +----+------------------ Abolish Role --------------------+ +/// +----------------+ +/// ``` +pub struct Admin<'a>(Item<'a, AdminState>); + +impl<'a> Admin<'a> { + pub const fn new(namespace: &'a str) -> Self { + Self(Item::new(namespace)) + } + + fn state(&self, storage: &'a dyn Storage) -> StdResult { + Ok(self + .0 + .may_load(storage)? + .unwrap_or(AdminState::A(AdminUninitialized))) + } + + //-------------------------------------------------------------------------------------------------- + // Queries + //-------------------------------------------------------------------------------------------------- + pub fn current(&self, storage: &'a dyn Storage) -> StdResult> { + Ok(match self.state(storage)? { + AdminState::B(b) => Some(b.admin), + AdminState::C(c) => Some(c.admin), + _ => None, + }) + } + + pub fn is_admin(&self, storage: &'a dyn Storage, addr: &Addr) -> StdResult { + match self.current(storage)? { + Some(admin) if &admin == addr => Ok(true), + _ => Ok(false), + } + } + + pub fn proposed(&self, storage: &'a dyn Storage) -> StdResult> { + Ok(match self.state(storage)? { + AdminState::C(c) => Some(c.proposed), + _ => None, + }) + } + + pub fn is_proposed(&self, storage: &'a dyn Storage, addr: &Addr) -> StdResult { + match self.proposed(storage)? { + Some(proposed) if &proposed == addr => Ok(true), + _ => Ok(false), + } + } + + pub fn query(&self, storage: &'a dyn Storage) -> StdResult { + Ok(AdminResponse { + admin: self.current(storage)?.map(Into::into), + proposed: self.proposed(storage)?.map(Into::into), + }) + } + + //-------------------------------------------------------------------------------------------------- + // Mutations + //-------------------------------------------------------------------------------------------------- + /// Executes admin state transitions + pub fn update(&self, storage: &'a mut dyn Storage, event: AdminUpdate) -> AdminResult<()> { + let state = self.state(storage)?; + + let new_state = match (state, event) { + (AdminState::A(a), AdminUpdate::InitializeAdmin { admin }) => a.initialize(&admin), + (AdminState::A(a), AdminUpdate::AbolishAdminRole { .. }) => a.abolish_admin_role(), + (AdminState::B(b), AdminUpdate::ProposeNewAdmin { sender, proposed }) => { + self.assert_admin(storage, &sender)?; + b.propose(&proposed) + } + (AdminState::B(b), AdminUpdate::AbolishAdminRole { sender }) => { + let addr = sender.ok_or(AdminError::NotAdmin {})?; + self.assert_admin(storage, &addr)?; + b.abolish_admin_role() + } + (AdminState::C(c), AdminUpdate::AcceptProposed { sender }) => { + self.assert_proposed(storage, &sender)?; + c.accept_proposed() + } + (AdminState::C(c), AdminUpdate::ClearProposed { sender }) => { + self.assert_admin(storage, &sender)?; + c.clear_proposed() + } + (AdminState::C(c), AdminUpdate::AbolishAdminRole { sender }) => { + let addr = sender.ok_or(AdminError::NotAdmin {})?; + self.assert_admin(storage, &addr)?; + c.abolish_admin_role() + } + (_, _) => return Err(AdminError::StateTransitionError {}), + }; + self.0.save(storage, &new_state)?; + Ok(()) + } + + /// Helper for composing execute responses + pub fn execute_update( + &self, + deps: DepsMut, + info: MessageInfo, + update: AdminExecuteUpdate, + ) -> AdminResult> + where + C: Clone + Debug + PartialEq + JsonSchema, + { + let validated_update = AdminUpdate::from(deps.api, &info.sender, update)?; + self.update(deps.storage, validated_update)?; + let res = self.query(deps.storage)?; + Ok(Response::new() + .add_attribute("action", "update_admin") + .add_attribute("admin", res.admin.unwrap_or_else(|| "None".to_string())) + .add_attribute( + "proposed", + res.proposed.unwrap_or_else(|| "None".to_string()), + ) + .add_attribute("sender", info.sender)) + } + + //-------------------------------------------------------------------------------------------------- + // Assertions + //-------------------------------------------------------------------------------------------------- + /// Similar to is_admin() except it raises an exception if caller is not current admin + pub fn assert_admin(&self, storage: &'a dyn Storage, caller: &Addr) -> AdminResult<()> { + if !self.is_admin(storage, caller)? { + Err(AdminError::NotAdmin {}) + } else { + Ok(()) + } + } + + /// Similar to is_proposed() except it raises an exception if caller is not currently proposed new admin + pub fn assert_proposed(&self, storage: &'a dyn Storage, caller: &Addr) -> AdminResult<()> { + if !self.is_proposed(storage, caller)? { + Err(AdminError::NotProposedAdmin {}) + } else { + Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::{mock_dependencies, mock_info}; + use cosmwasm_std::Empty; + + use crate::AdminUpdate::{ + AbolishAdminRole, AcceptProposed, ClearProposed, InitializeAdmin, ProposeNewAdmin, + }; + + use super::*; + + //-------------------------------------------------------------------------------------------------- + // Test invalid state transitions + //-------------------------------------------------------------------------------------------------- + + #[test] + fn invalid_uninitialized_state_transitions() { + let mut deps = mock_dependencies(); + let admin = Admin::new("xyz"); + let storage = deps.as_mut().storage; + let new_admin = Addr::unchecked("peter_parker"); + + let err = admin + .update( + storage, + ProposeNewAdmin { + sender: new_admin.clone(), + proposed: new_admin.clone(), + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + + let err = admin + .update( + storage, + ClearProposed { + sender: new_admin.clone(), + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + + let err = admin + .update(storage, AcceptProposed { sender: new_admin }) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + } + + #[test] + fn invalid_admin_set_no_proposed_state_transitions() { + let mut deps = mock_dependencies(); + let admin = Admin::new("xyz"); + let storage = deps.as_mut().storage; + let original_admin = Addr::unchecked("peter_parker"); + admin + .update( + storage, + InitializeAdmin { + admin: original_admin.clone(), + }, + ) + .unwrap(); + + let err = admin + .update( + storage, + InitializeAdmin { + admin: original_admin.clone(), + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + + let err = admin + .update( + storage, + ClearProposed { + sender: original_admin.clone(), + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + + let err = admin + .update( + storage, + AcceptProposed { + sender: original_admin, + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + } + + #[test] + fn invalid_admin_set_with_proposed_state_transitions() { + let mut deps = mock_dependencies(); + let admin = Admin::new("xyz"); + let storage = deps.as_mut().storage; + let original_admin = Addr::unchecked("peter_parker"); + let proposed_admin = Addr::unchecked("miles_morales"); + admin + .update( + storage, + InitializeAdmin { + admin: original_admin.clone(), + }, + ) + .unwrap(); + admin + .update( + storage, + ProposeNewAdmin { + sender: original_admin.clone(), + proposed: proposed_admin.clone(), + }, + ) + .unwrap(); + + let err = admin + .update( + storage, + InitializeAdmin { + admin: original_admin.clone(), + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + + let err = admin + .update( + storage, + ProposeNewAdmin { + sender: original_admin, + proposed: proposed_admin, + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + } + + #[test] + fn invalid_admin_role_abolished_state_transitions() { + let mut deps = mock_dependencies(); + let admin = Admin::new("xyz"); + let storage = deps.as_mut().storage; + let original_admin = Addr::unchecked("peter_parker"); + let proposed_admin = Addr::unchecked("miles_morales"); + admin + .update( + storage, + AbolishAdminRole { + sender: Some(original_admin.clone()), + }, + ) + .unwrap(); + + let err = admin + .update( + storage, + InitializeAdmin { + admin: original_admin.clone(), + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + + let err = admin + .update( + storage, + ProposeNewAdmin { + sender: original_admin.clone(), + proposed: proposed_admin.clone(), + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + + let err = admin + .update( + storage, + ClearProposed { + sender: original_admin.clone(), + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + + let err = admin + .update( + storage, + AcceptProposed { + sender: proposed_admin, + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + + let err = admin + .update( + storage, + AbolishAdminRole { + sender: Some(original_admin), + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + } + + //-------------------------------------------------------------------------------------------------- + // Test permissions + //-------------------------------------------------------------------------------------------------- + + #[test] + fn initialize_admin_permissions() { + let mut deps = mock_dependencies(); + let admin = Admin::new("xyz"); + + // Anyone can initialize the first admin + let user_a = Addr::unchecked("peter_parker"); + admin + .update(deps.as_mut().storage, InitializeAdmin { admin: user_a }) + .unwrap(); + + let mut deps = mock_dependencies(); + let user_b = Addr::unchecked("miles_morales"); + admin + .update(deps.as_mut().storage, InitializeAdmin { admin: user_b }) + .unwrap(); + } + + #[test] + fn propose_new_admin_permissions() { + let mut deps = mock_dependencies(); + let storage = deps.as_mut().storage; + let admin = Admin::new("xyz"); + let original_admin = Addr::unchecked("peter_parker"); + admin + .update( + storage, + InitializeAdmin { + admin: original_admin, + }, + ) + .unwrap(); + + let bad_guy = Addr::unchecked("doc_oc"); + let err = admin + .update( + storage, + ProposeNewAdmin { + sender: bad_guy.clone(), + proposed: bad_guy, + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::NotAdmin {}) + } + + #[test] + fn clear_proposed_permissions() { + let mut deps = mock_dependencies(); + let storage = deps.as_mut().storage; + let admin = Admin::new("xyz"); + let original_admin = Addr::unchecked("peter_parker"); + let proposed_admin = Addr::unchecked("miles_morales"); + admin + .update( + storage, + InitializeAdmin { + admin: original_admin.clone(), + }, + ) + .unwrap(); + admin + .update( + storage, + ProposeNewAdmin { + sender: original_admin, + proposed: proposed_admin, + }, + ) + .unwrap(); + + let bad_guy = Addr::unchecked("doc_oc"); + let err = admin + .update(storage, ClearProposed { sender: bad_guy }) + .unwrap_err(); + assert_eq!(err, AdminError::NotAdmin {}) + } + + #[test] + fn accept_proposed_permissions() { + let mut deps = mock_dependencies(); + let storage = deps.as_mut().storage; + let admin = Admin::new("xyz"); + let original_admin = Addr::unchecked("peter_parker"); + let proposed_admin = Addr::unchecked("miles_morales"); + admin + .update( + storage, + InitializeAdmin { + admin: original_admin.clone(), + }, + ) + .unwrap(); + admin + .update( + storage, + ProposeNewAdmin { + sender: original_admin.clone(), + proposed: proposed_admin, + }, + ) + .unwrap(); + + let err = admin + .update( + storage, + AcceptProposed { + sender: original_admin, + }, + ) + .unwrap_err(); + assert_eq!(err, AdminError::NotProposedAdmin {}) + } + + #[test] + fn abolish_admin_role_permissions() { + let mut deps = mock_dependencies(); + let admin = Admin::new("xyz"); + let user = Addr::unchecked("peter_parker"); + + // As no admin is set, no restrictions on abolishing from uninitialized state + admin + .update(deps.as_mut().storage, AbolishAdminRole { sender: None }) + .unwrap(); + + let mut deps = mock_dependencies(); + admin + .update( + deps.as_mut().storage, + AbolishAdminRole { sender: Some(user) }, + ) + .unwrap(); + } + + //-------------------------------------------------------------------------------------------------- + // Test success cases + //-------------------------------------------------------------------------------------------------- + + fn assert_uninitialized(storage: &dyn Storage, admin: &Admin) { + let state = admin.state(storage).unwrap(); + match state { + AdminState::A(_) => {} + _ => panic!("Should be in the AdminUninitialized state"), + } + + let current = admin.current(storage).unwrap(); + assert_eq!(current, None); + + let proposed = admin.proposed(storage).unwrap(); + assert_eq!(proposed, None); + + let res = admin.query(storage).unwrap(); + assert_eq!( + res, + AdminResponse { + admin: None, + proposed: None + } + ); + } + + #[test] + fn uninitialized_state() { + let deps = mock_dependencies(); + let admin = Admin::new("xyz"); + assert_uninitialized(deps.as_ref().storage, &admin); + } + + #[test] + fn initialize_admin() { + let mut deps = mock_dependencies(); + let admin = Admin::new("xyz"); + let storage = deps.as_mut().storage; + let original_admin = Addr::unchecked("peter_parker"); + admin + .update( + storage, + InitializeAdmin { + admin: original_admin.clone(), + }, + ) + .unwrap(); + + let state = admin.state(storage).unwrap(); + match state { + AdminState::B(_) => {} + _ => panic!("Should be in the AdminSetNoneProposed state"), + } + + let current = admin.current(storage).unwrap(); + assert_eq!(current, Some(original_admin.clone())); + assert!(admin.is_admin(storage, &original_admin).unwrap()); + + let proposed = admin.proposed(storage).unwrap(); + assert_eq!(proposed, None); + + let res = admin.query(storage).unwrap(); + assert_eq!( + res, + AdminResponse { + admin: Some(original_admin.to_string()), + proposed: None + } + ); + } + + #[test] + fn propose_new_admin() { + let mut deps = mock_dependencies(); + let admin = Admin::new("xyz"); + let storage = deps.as_mut().storage; + let original_admin = Addr::unchecked("peter_parker"); + let proposed_admin = Addr::unchecked("miles_morales"); + admin + .update( + storage, + InitializeAdmin { + admin: original_admin.clone(), + }, + ) + .unwrap(); + admin + .update( + storage, + ProposeNewAdmin { + sender: original_admin.clone(), + proposed: proposed_admin.clone(), + }, + ) + .unwrap(); + + let state = admin.state(storage).unwrap(); + match state { + AdminState::C(_) => {} + _ => panic!("Should be in the AdminSetWithProposed state"), + } + + let current = admin.current(storage).unwrap(); + assert_eq!(current, Some(original_admin.clone())); + assert!(admin.is_admin(storage, &original_admin).unwrap()); + + let proposed = admin.proposed(storage).unwrap(); + assert_eq!(proposed, Some(proposed_admin.clone())); + assert!(admin.is_proposed(storage, &proposed_admin).unwrap()); + + let res = admin.query(storage).unwrap(); + assert_eq!( + res, + AdminResponse { + admin: Some(original_admin.to_string()), + proposed: Some(proposed_admin.to_string()) + } + ); + } + + #[test] + fn clear_proposed() { + let mut deps = mock_dependencies(); + let admin = Admin::new("xyz"); + let storage = deps.as_mut().storage; + let original_admin = Addr::unchecked("peter_parker"); + let proposed_admin = Addr::unchecked("miles_morales"); + admin + .update( + storage, + InitializeAdmin { + admin: original_admin.clone(), + }, + ) + .unwrap(); + admin + .update( + storage, + ProposeNewAdmin { + sender: original_admin.clone(), + proposed: proposed_admin.clone(), + }, + ) + .unwrap(); + + admin + .update( + storage, + ClearProposed { + sender: original_admin.clone(), + }, + ) + .unwrap(); + + let state = admin.state(storage).unwrap(); + match state { + AdminState::B(_) => {} + _ => panic!("Should be in the AdminSetNoneProposed state"), + } + + let current = admin.current(storage).unwrap(); + assert_eq!(current, Some(original_admin.clone())); + assert!(admin.is_admin(storage, &original_admin).unwrap()); + + let proposed = admin.proposed(storage).unwrap(); + assert_eq!(proposed, None); + assert!(!admin.is_proposed(storage, &proposed_admin).unwrap()); + + let res = admin.query(storage).unwrap(); + assert_eq!( + res, + AdminResponse { + admin: Some(original_admin.to_string()), + proposed: None + } + ); + } + + #[test] + fn accept_proposed() { + let mut deps = mock_dependencies(); + let admin = Admin::new("xyz"); + let storage = deps.as_mut().storage; + let original_admin = Addr::unchecked("peter_parker"); + let proposed_admin = Addr::unchecked("miles_morales"); + admin + .update( + storage, + InitializeAdmin { + admin: original_admin.clone(), + }, + ) + .unwrap(); + admin + .update( + storage, + ProposeNewAdmin { + sender: original_admin, + proposed: proposed_admin.clone(), + }, + ) + .unwrap(); + admin + .update( + storage, + AcceptProposed { + sender: proposed_admin.clone(), + }, + ) + .unwrap(); + + let state = admin.state(storage).unwrap(); + match state { + AdminState::B(_) => {} + _ => panic!("Should be in the AdminSetNoneProposed state"), + } + + let current = admin.current(storage).unwrap(); + assert_eq!(current, Some(proposed_admin.clone())); + assert!(admin.is_admin(storage, &proposed_admin).unwrap()); + + let proposed = admin.proposed(storage).unwrap(); + assert_eq!(proposed, None); + assert!(!admin.is_proposed(storage, &proposed_admin).unwrap()); + + let res = admin.query(storage).unwrap(); + assert_eq!( + res, + AdminResponse { + admin: Some(proposed_admin.to_string()), + proposed: None + } + ); + } + + #[test] + fn abolish_admin_role() { + let mut deps = mock_dependencies(); + let admin = Admin::new("xyz"); + let storage = deps.as_mut().storage; + let original_admin = Addr::unchecked("peter_parker"); + + admin + .update( + storage, + AbolishAdminRole { + sender: Some(original_admin.clone()), + }, + ) + .unwrap(); + + let state = admin.state(storage).unwrap(); + match state { + AdminState::D(_) => {} + _ => panic!("Should be in the AdminRoleAbolished state"), + } + + let current = admin.current(storage).unwrap(); + assert_eq!(current, None); + assert!(!admin.is_admin(storage, &original_admin).unwrap()); + + let proposed = admin.proposed(storage).unwrap(); + assert_eq!(proposed, None); + assert!(!admin.is_proposed(storage, &original_admin).unwrap()); + + let res = admin.query(storage).unwrap(); + assert_eq!( + res, + AdminResponse { + admin: None, + proposed: None + } + ); + } + + #[test] + fn execute_helper() { + let mut deps = mock_dependencies(); + let sender = Addr::unchecked("peter_parker"); + let info = mock_info(sender.as_ref(), &[]); + let admin = Admin::new("xyz"); + admin + .execute_update::( + deps.as_mut(), + info, + AdminExecuteUpdate::InitializeAdmin { + admin: sender.clone().into(), + }, + ) + .unwrap(); + + let storage = deps.as_ref().storage; + let state = admin.state(storage).unwrap(); + match state { + AdminState::B(_) => {} + _ => panic!("Should be in the AdminSetNoneProposed state"), + } + + let current = admin.current(storage).unwrap(); + assert_eq!(current, Some(sender.clone())); + assert!(admin.is_admin(storage, &sender).unwrap()); + + let proposed = admin.proposed(storage).unwrap(); + assert_eq!(proposed, None); + + let res = admin.query(storage).unwrap(); + assert_eq!( + res, + AdminResponse { + admin: Some(sender.to_string()), + proposed: None + } + ); + } +} diff --git a/packages/controllers/src/lib.rs b/packages/controllers/src/lib.rs new file mode 100644 index 000000000..3bef77fbb --- /dev/null +++ b/packages/controllers/src/lib.rs @@ -0,0 +1,3 @@ +mod admin; + +pub use admin::{Admin, AdminError, AdminExecuteUpdate, AdminResponse, AdminUpdate}; diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 7a0686be7..87929fa70 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -17,16 +17,17 @@ doctest = false backtraces = ["cosmwasm-std/backtraces"] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cosmwasm-vault-standard = { workspace = true } -cw-controllers = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -mars-health = { workspace = true } -mars-mock-oracle = { workspace = true } -mars-mock-red-bank = { workspace = true } -mars-outpost = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cosmwasm-vault-standard = { workspace = true } +cw-controllers = { workspace = true } +cw-controllers-admin-fork = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +mars-health = { workspace = true } +mars-mock-oracle = { workspace = true } +mars-mock-red-bank = { workspace = true } +mars-outpost = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 43cfe0f0a..44ae054f9 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{ CheckedFromRatioError, CheckedMultiplyRatioError, DecimalRangeExceeded, OverflowError, StdError, Uint128, }; -use cw_controllers::AdminError; +use cw_controllers_admin_fork::AdminError; use thiserror::Error; use crate::coins::Coins; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 5716d6088..a8ee66952 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; +use cw_controllers_admin_fork::AdminExecuteUpdate; use crate::adapters::vault::{Vault, VaultPositionType, VaultUnchecked}; use crate::msg::instantiate::ConfigUpdates; @@ -22,6 +23,8 @@ pub enum ExecuteMsg { //-------------------------------------------------------------------------------------------------- /// Update contract config constants UpdateConfig { new_config: ConfigUpdates }, + /// Manages admin role state + UpdateAdmin(AdminExecuteUpdate), /// Internal actions only callable by the contract itself Callback(CallbackMsg), } diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index 3f82071c8..efaaa0111 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -61,7 +61,6 @@ impl Stringify for Vec { #[derive(Default)] pub struct ConfigUpdates { pub account_nft: Option, - pub admin: Option, pub allowed_coins: Option>, pub vault_configs: Option>, pub oracle: Option, diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index c809391b7..b737a2a36 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -154,6 +154,7 @@ pub struct VaultPositionValue { #[cw_serde] pub struct ConfigResponse { pub admin: Option, + pub proposed_new_admin: Option, pub account_nft: Option, pub red_bank: String, pub oracle: String, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 0fda3ac6f..d5878126c 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -245,6 +245,19 @@ }, "additionalProperties": false }, + { + "description": "Manages admin role state", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "$ref": "#/definitions/AdminExecuteUpdate" + } + }, + "additionalProperties": false + }, { "description": "Internal actions only callable by the contract itself", "type": "object", @@ -607,6 +620,61 @@ "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" }, + "AdminExecuteUpdate": { + "description": "Same as above, but used for execute helpers. Sender and inputs are validated.", + "oneOf": [ + { + "type": "string", + "enum": [ + "clear_proposed", + "accept_proposed", + "abolish_admin_role" + ] + }, + { + "type": "object", + "required": [ + "initialize_admin" + ], + "properties": { + "initialize_admin": { + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "propose_new_admin" + ], + "properties": { + "propose_new_admin": { + "type": "object", + "required": [ + "proposed" + ], + "properties": { + "proposed": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "CallbackMsg": { "description": "Internal actions made by the contract with pre-validated inputs", "oneOf": [ @@ -1153,12 +1221,6 @@ "null" ] }, - "admin": { - "type": [ - "string", - "null" - ] - }, "allowed_coins": { "type": [ "array", @@ -2095,6 +2157,12 @@ "oracle": { "type": "string" }, + "proposed_new_admin": { + "type": [ + "string", + "null" + ] + }, "red_bank": { "type": "string" }, diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 609438669..6a15616f7 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -871,6 +871,12 @@ "oracle": { "type": "string" }, + "proposed_new_admin": { + "type": [ + "string", + "null" + ] + }, "red_bank": { "type": "string" }, diff --git a/schemas/mars-oracle-adapter/mars-oracle-adapter.json b/schemas/mars-oracle-adapter/mars-oracle-adapter.json index a0cf2c0b2..24e867f7b 100644 --- a/schemas/mars-oracle-adapter/mars-oracle-adapter.json +++ b/schemas/mars-oracle-adapter/mars-oracle-adapter.json @@ -90,6 +90,18 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "$ref": "#/definitions/AdminExecuteUpdate" + } + }, + "additionalProperties": false } ], "definitions": { @@ -97,15 +109,64 @@ "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" }, + "AdminExecuteUpdate": { + "description": "Same as above, but used for execute helpers. Sender and inputs are validated.", + "oneOf": [ + { + "type": "string", + "enum": [ + "clear_proposed", + "accept_proposed", + "abolish_admin_role" + ] + }, + { + "type": "object", + "required": [ + "initialize_admin" + ], + "properties": { + "initialize_admin": { + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "propose_new_admin" + ], + "properties": { + "propose_new_admin": { + "type": "object", + "required": [ + "proposed" + ], + "properties": { + "proposed": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "ConfigUpdates": { "type": "object", "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, "oracle": { "anyOf": [ { @@ -310,17 +371,19 @@ ], "properties": { "admin": { - "anyOf": [ - { - "$ref": "#/definitions/Addr" - }, - { - "type": "null" - } + "type": [ + "string", + "null" ] }, "oracle": { "$ref": "#/definitions/OracleBase_for_Addr" + }, + "proposed_new_admin": { + "type": [ + "string", + "null" + ] } }, "additionalProperties": false, diff --git a/scripts/package.json b/scripts/package.json index 675d7432e..db16fcf80 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -15,7 +15,7 @@ "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.29.4", "@cosmjs/stargate": "^0.29.4", - "@cosmwasm/ts-codegen": "^0.23.0", + "@cosmwasm/ts-codegen": "^0.24.0", "chalk": "4.1.2", "cosmjs-types": "^0.5.2", "lodash": "^4.17.21", diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts index bce559018..3887a19dd 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts index 50616545c..5267bda1e 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts new file mode 100644 index 000000000..a9c43d92b --- /dev/null +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts @@ -0,0 +1,573 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee, Coin } from '@cosmjs/amino' +import { + Decimal, + InstantiateMsg, + ExecuteMsg, + Binary, + Expiration, + Timestamp, + Uint64, + ConfigUpdates, + QueryMsg, + AllNftInfoResponseForEmpty, + OwnerOfResponse, + Approval, + NftInfoResponseForEmpty, + Empty, + OperatorsResponse, + TokensResponse, + ApprovalResponse, + ApprovalsResponse, + ConfigBaseForString, + ContractInfoResponse, + MinterResponse, + NumTokensResponse, +} from './MarsAccountNft.types' +import { MarsAccountNftQueryClient, MarsAccountNftClient } from './MarsAccountNft.client' +export const marsAccountNftQueryKeys = { + contract: [ + { + contract: 'marsAccountNft', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...marsAccountNftQueryKeys.contract[0], address: contractAddress }] as const, + config: (contractAddress: string | undefined, args?: Record) => + [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, + nextId: (contractAddress: string | undefined, args?: Record) => + [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'next_id', args }] as const, + ownerOf: (contractAddress: string | undefined, args?: Record) => + [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'owner_of', args }] as const, + approval: (contractAddress: string | undefined, args?: Record) => + [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'approval', args }] as const, + approvals: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'approvals', args }, + ] as const, + allOperators: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'all_operators', args }, + ] as const, + numTokens: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'num_tokens', args }, + ] as const, + contractInfo: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'contract_info', args }, + ] as const, + nftInfo: (contractAddress: string | undefined, args?: Record) => + [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'nft_info', args }] as const, + allNftInfo: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'all_nft_info', args }, + ] as const, + tokens: (contractAddress: string | undefined, args?: Record) => + [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'tokens', args }] as const, + allTokens: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'all_tokens', args }, + ] as const, + minter: (contractAddress: string | undefined, args?: Record) => + [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'minter', args }] as const, +} +export interface MarsAccountNftReactQuery { + client: MarsAccountNftQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MarsAccountNftMinterQuery + extends MarsAccountNftReactQuery {} +export function useMarsAccountNftMinterQuery({ + client, + options, +}: MarsAccountNftMinterQuery) { + return useQuery( + marsAccountNftQueryKeys.minter(client?.contractAddress), + () => (client ? client.minter() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsAccountNftAllTokensQuery + extends MarsAccountNftReactQuery { + args: { + limit?: number + startAfter?: string + } +} +export function useMarsAccountNftAllTokensQuery({ + client, + args, + options, +}: MarsAccountNftAllTokensQuery) { + return useQuery( + marsAccountNftQueryKeys.allTokens(client?.contractAddress, args), + () => + client + ? client.allTokens({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsAccountNftTokensQuery + extends MarsAccountNftReactQuery { + args: { + limit?: number + owner: string + startAfter?: string + } +} +export function useMarsAccountNftTokensQuery({ + client, + args, + options, +}: MarsAccountNftTokensQuery) { + return useQuery( + marsAccountNftQueryKeys.tokens(client?.contractAddress, args), + () => + client + ? client.tokens({ + limit: args.limit, + owner: args.owner, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsAccountNftAllNftInfoQuery + extends MarsAccountNftReactQuery { + args: { + includeExpired?: boolean + tokenId: string + } +} +export function useMarsAccountNftAllNftInfoQuery({ + client, + args, + options, +}: MarsAccountNftAllNftInfoQuery) { + return useQuery( + marsAccountNftQueryKeys.allNftInfo(client?.contractAddress, args), + () => + client + ? client.allNftInfo({ + includeExpired: args.includeExpired, + tokenId: args.tokenId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsAccountNftNftInfoQuery + extends MarsAccountNftReactQuery { + args: { + tokenId: string + } +} +export function useMarsAccountNftNftInfoQuery({ + client, + args, + options, +}: MarsAccountNftNftInfoQuery) { + return useQuery( + marsAccountNftQueryKeys.nftInfo(client?.contractAddress, args), + () => + client + ? client.nftInfo({ + tokenId: args.tokenId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsAccountNftContractInfoQuery + extends MarsAccountNftReactQuery {} +export function useMarsAccountNftContractInfoQuery({ + client, + options, +}: MarsAccountNftContractInfoQuery) { + return useQuery( + marsAccountNftQueryKeys.contractInfo(client?.contractAddress), + () => (client ? client.contractInfo() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsAccountNftNumTokensQuery + extends MarsAccountNftReactQuery {} +export function useMarsAccountNftNumTokensQuery({ + client, + options, +}: MarsAccountNftNumTokensQuery) { + return useQuery( + marsAccountNftQueryKeys.numTokens(client?.contractAddress), + () => (client ? client.numTokens() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsAccountNftAllOperatorsQuery + extends MarsAccountNftReactQuery { + args: { + includeExpired?: boolean + limit?: number + owner: string + startAfter?: string + } +} +export function useMarsAccountNftAllOperatorsQuery({ + client, + args, + options, +}: MarsAccountNftAllOperatorsQuery) { + return useQuery( + marsAccountNftQueryKeys.allOperators(client?.contractAddress, args), + () => + client + ? client.allOperators({ + includeExpired: args.includeExpired, + limit: args.limit, + owner: args.owner, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsAccountNftApprovalsQuery + extends MarsAccountNftReactQuery { + args: { + includeExpired?: boolean + tokenId: string + } +} +export function useMarsAccountNftApprovalsQuery({ + client, + args, + options, +}: MarsAccountNftApprovalsQuery) { + return useQuery( + marsAccountNftQueryKeys.approvals(client?.contractAddress, args), + () => + client + ? client.approvals({ + includeExpired: args.includeExpired, + tokenId: args.tokenId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsAccountNftApprovalQuery + extends MarsAccountNftReactQuery { + args: { + includeExpired?: boolean + spender: string + tokenId: string + } +} +export function useMarsAccountNftApprovalQuery({ + client, + args, + options, +}: MarsAccountNftApprovalQuery) { + return useQuery( + marsAccountNftQueryKeys.approval(client?.contractAddress, args), + () => + client + ? client.approval({ + includeExpired: args.includeExpired, + spender: args.spender, + tokenId: args.tokenId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsAccountNftOwnerOfQuery + extends MarsAccountNftReactQuery { + args: { + includeExpired?: boolean + tokenId: string + } +} +export function useMarsAccountNftOwnerOfQuery({ + client, + args, + options, +}: MarsAccountNftOwnerOfQuery) { + return useQuery( + marsAccountNftQueryKeys.ownerOf(client?.contractAddress, args), + () => + client + ? client.ownerOf({ + includeExpired: args.includeExpired, + tokenId: args.tokenId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsAccountNftNextIdQuery extends MarsAccountNftReactQuery {} +export function useMarsAccountNftNextIdQuery({ + client, + options, +}: MarsAccountNftNextIdQuery) { + return useQuery( + marsAccountNftQueryKeys.nextId(client?.contractAddress), + () => (client ? client.nextId() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsAccountNftConfigQuery + extends MarsAccountNftReactQuery {} +export function useMarsAccountNftConfigQuery({ + client, + options, +}: MarsAccountNftConfigQuery) { + return useQuery( + marsAccountNftQueryKeys.config(client?.contractAddress), + () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsAccountNftRevokeAllMutation { + client: MarsAccountNftClient + msg: { + operator: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsAccountNftRevokeAllMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.revokeAll(msg, fee, memo, funds), + options, + ) +} +export interface MarsAccountNftApproveAllMutation { + client: MarsAccountNftClient + msg: { + expires?: Expiration + operator: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsAccountNftApproveAllMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.approveAll(msg, fee, memo, funds), + options, + ) +} +export interface MarsAccountNftRevokeMutation { + client: MarsAccountNftClient + msg: { + spender: string + tokenId: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsAccountNftRevokeMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.revoke(msg, fee, memo, funds), + options, + ) +} +export interface MarsAccountNftApproveMutation { + client: MarsAccountNftClient + msg: { + expires?: Expiration + spender: string + tokenId: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsAccountNftApproveMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.approve(msg, fee, memo, funds), + options, + ) +} +export interface MarsAccountNftSendNftMutation { + client: MarsAccountNftClient + msg: { + contract: string + msg: Binary + tokenId: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsAccountNftSendNftMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.sendNft(msg, fee, memo, funds), + options, + ) +} +export interface MarsAccountNftTransferNftMutation { + client: MarsAccountNftClient + msg: { + recipient: string + tokenId: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsAccountNftTransferNftMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.transferNft(msg, fee, memo, funds), + options, + ) +} +export interface MarsAccountNftBurnMutation { + client: MarsAccountNftClient + msg: { + tokenId: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsAccountNftBurnMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.burn(msg, fee, memo, funds), + options, + ) +} +export interface MarsAccountNftMintMutation { + client: MarsAccountNftClient + msg: { + user: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsAccountNftMintMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.mint(msg, fee, memo, funds), + options, + ) +} +export interface MarsAccountNftAcceptMinterRoleMutation { + client: MarsAccountNftClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsAccountNftAcceptMinterRoleMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, args: { fee, memo, funds } = {} }) => client.acceptMinterRole(fee, memo, funds), + options, + ) +} +export interface MarsAccountNftUpdateConfigMutation { + client: MarsAccountNftClient + msg: { + updates: ConfigUpdates + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsAccountNftUpdateConfigMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateConfig(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index b2de98454..9a320ddc4 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/bundle.ts b/scripts/types/generated/mars-account-nft/bundle.ts new file mode 100644 index 000000000..3fef9c899 --- /dev/null +++ b/scripts/types/generated/mars-account-nft/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _0 from './MarsAccountNft.types' +import * as _1 from './MarsAccountNft.client' +import * as _2 from './MarsAccountNft.message-composer' +import * as _3 from './MarsAccountNft.react-query' +export namespace contracts { + export const MarsAccountNft = { ..._0, ..._1, ..._2, ..._3 } +} diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 5d67f5429..5ef25dcf1 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -22,6 +22,7 @@ import { ExecuteMsg, Action, VaultPositionType, + AdminExecuteUpdate, CallbackMsg, Addr, ConfigUpdates, @@ -321,6 +322,11 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt memo?: string, funds?: Coin[], ) => Promise + updateAdmin: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise callback: ( fee?: number | StdFee | 'auto', memo?: string, @@ -343,6 +349,7 @@ export class MarsCreditManagerClient this.createCreditAccount = this.createCreditAccount.bind(this) this.updateCreditAccount = this.updateCreditAccount.bind(this) this.updateConfig = this.updateConfig.bind(this) + this.updateAdmin = this.updateAdmin.bind(this) this.callback = this.callback.bind(this) } @@ -411,6 +418,22 @@ export class MarsCreditManagerClient funds, ) } + updateAdmin = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_admin: {}, + }, + fee, + memo, + funds, + ) + } callback = async ( fee: number | StdFee | 'auto' = 'auto', memo?: string, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 59abab5f8..74341653d 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -23,6 +23,7 @@ import { ExecuteMsg, Action, VaultPositionType, + AdminExecuteUpdate, CallbackMsg, Addr, ConfigUpdates, @@ -75,6 +76,7 @@ export interface MarsCreditManagerMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject + updateAdmin: (funds?: Coin[]) => MsgExecuteContractEncodeObject callback: (funds?: Coin[]) => MsgExecuteContractEncodeObject } export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessage { @@ -87,6 +89,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag this.createCreditAccount = this.createCreditAccount.bind(this) this.updateCreditAccount = this.updateCreditAccount.bind(this) this.updateConfig = this.updateConfig.bind(this) + this.updateAdmin = this.updateAdmin.bind(this) this.callback = this.callback.bind(this) } @@ -156,6 +159,21 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag }), } } + updateAdmin = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_admin: {}, + }), + ), + funds, + }), + } + } callback = (funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts new file mode 100644 index 000000000..3f4e0a46d --- /dev/null +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -0,0 +1,577 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + Uint128, + Decimal, + OracleBaseForString, + RedBankBaseForString, + SwapperBaseForString, + ZapperBaseForString, + InstantiateMsg, + VaultInstantiateConfig, + VaultConfig, + Coin, + VaultBaseForString, + ExecuteMsg, + Action, + VaultPositionType, + AdminExecuteUpdate, + CallbackMsg, + Addr, + ConfigUpdates, + VaultBaseForAddr, + QueryMsg, + ArrayOfCoinBalanceResponseItem, + CoinBalanceResponseItem, + ArrayOfSharesResponseItem, + SharesResponseItem, + ArrayOfDebtShares, + DebtShares, + ArrayOfVaultWithBalance, + VaultWithBalance, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, + ArrayOfVaultPositionResponseItem, + VaultPositionResponseItem, + VaultPosition, + LockingVaultAmount, + VaultUnlockingPosition, + ArrayOfString, + ConfigResponse, + ArrayOfCoin, + HealthResponse, + Positions, + DebtAmount, + ArrayOfVaultInstantiateConfig, +} from './MarsCreditManager.types' +import { MarsCreditManagerQueryClient, MarsCreditManagerClient } from './MarsCreditManager.client' +export const marsCreditManagerQueryKeys = { + contract: [ + { + contract: 'marsCreditManager', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...marsCreditManagerQueryKeys.contract[0], address: contractAddress }] as const, + config: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, + ] as const, + vaultConfigs: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'vault_configs', args }, + ] as const, + allowedCoins: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'allowed_coins', args }, + ] as const, + positions: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'positions', args }, + ] as const, + health: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'health', args }, + ] as const, + allCoinBalances: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'all_coin_balances', + args, + }, + ] as const, + allDebtShares: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'all_debt_shares', + args, + }, + ] as const, + totalDebtShares: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'total_debt_shares', + args, + }, + ] as const, + allTotalDebtShares: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'all_total_debt_shares', + args, + }, + ] as const, + allVaultPositions: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'all_vault_positions', + args, + }, + ] as const, + totalVaultCoinBalance: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'total_vault_coin_balance', + args, + }, + ] as const, + allTotalVaultCoinBalances: ( + contractAddress: string | undefined, + args?: Record, + ) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'all_total_vault_coin_balances', + args, + }, + ] as const, + estimateProvideLiquidity: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'estimate_provide_liquidity', + args, + }, + ] as const, + estimateWithdrawLiquidity: ( + contractAddress: string | undefined, + args?: Record, + ) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'estimate_withdraw_liquidity', + args, + }, + ] as const, +} +export interface MarsCreditManagerReactQuery { + client: MarsCreditManagerQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MarsCreditManagerEstimateWithdrawLiquidityQuery + extends MarsCreditManagerReactQuery { + args: { + lpToken: Coin + } +} +export function useMarsCreditManagerEstimateWithdrawLiquidityQuery({ + client, + args, + options, +}: MarsCreditManagerEstimateWithdrawLiquidityQuery) { + return useQuery( + marsCreditManagerQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), + () => + client + ? client.estimateWithdrawLiquidity({ + lpToken: args.lpToken, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerEstimateProvideLiquidityQuery + extends MarsCreditManagerReactQuery { + args: { + coinsIn: Coin[] + lpTokenOut: string + } +} +export function useMarsCreditManagerEstimateProvideLiquidityQuery({ + client, + args, + options, +}: MarsCreditManagerEstimateProvideLiquidityQuery) { + return useQuery( + marsCreditManagerQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), + () => + client + ? client.estimateProvideLiquidity({ + coinsIn: args.coinsIn, + lpTokenOut: args.lpTokenOut, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerAllTotalVaultCoinBalancesQuery + extends MarsCreditManagerReactQuery { + args: { + limit?: number + startAfter?: VaultBaseForString + } +} +export function useMarsCreditManagerAllTotalVaultCoinBalancesQuery< + TData = ArrayOfVaultWithBalance, +>({ client, args, options }: MarsCreditManagerAllTotalVaultCoinBalancesQuery) { + return useQuery( + marsCreditManagerQueryKeys.allTotalVaultCoinBalances(client?.contractAddress, args), + () => + client + ? client.allTotalVaultCoinBalances({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerTotalVaultCoinBalanceQuery + extends MarsCreditManagerReactQuery { + args: { + vault: VaultBaseForString + } +} +export function useMarsCreditManagerTotalVaultCoinBalanceQuery({ + client, + args, + options, +}: MarsCreditManagerTotalVaultCoinBalanceQuery) { + return useQuery( + marsCreditManagerQueryKeys.totalVaultCoinBalance(client?.contractAddress, args), + () => + client + ? client.totalVaultCoinBalance({ + vault: args.vault, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerAllVaultPositionsQuery + extends MarsCreditManagerReactQuery { + args: { + limit?: number + startAfter?: string[][] + } +} +export function useMarsCreditManagerAllVaultPositionsQuery< + TData = ArrayOfVaultPositionResponseItem, +>({ client, args, options }: MarsCreditManagerAllVaultPositionsQuery) { + return useQuery( + marsCreditManagerQueryKeys.allVaultPositions(client?.contractAddress, args), + () => + client + ? client.allVaultPositions({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerAllTotalDebtSharesQuery + extends MarsCreditManagerReactQuery { + args: { + limit?: number + startAfter?: string + } +} +export function useMarsCreditManagerAllTotalDebtSharesQuery({ + client, + args, + options, +}: MarsCreditManagerAllTotalDebtSharesQuery) { + return useQuery( + marsCreditManagerQueryKeys.allTotalDebtShares(client?.contractAddress, args), + () => + client + ? client.allTotalDebtShares({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerTotalDebtSharesQuery + extends MarsCreditManagerReactQuery {} +export function useMarsCreditManagerTotalDebtSharesQuery({ + client, + options, +}: MarsCreditManagerTotalDebtSharesQuery) { + return useQuery( + marsCreditManagerQueryKeys.totalDebtShares(client?.contractAddress), + () => (client ? client.totalDebtShares() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerAllDebtSharesQuery + extends MarsCreditManagerReactQuery { + args: { + limit?: number + startAfter?: string[][] + } +} +export function useMarsCreditManagerAllDebtSharesQuery({ + client, + args, + options, +}: MarsCreditManagerAllDebtSharesQuery) { + return useQuery( + marsCreditManagerQueryKeys.allDebtShares(client?.contractAddress, args), + () => + client + ? client.allDebtShares({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerAllCoinBalancesQuery + extends MarsCreditManagerReactQuery { + args: { + limit?: number + startAfter?: string[][] + } +} +export function useMarsCreditManagerAllCoinBalancesQuery({ + client, + args, + options, +}: MarsCreditManagerAllCoinBalancesQuery) { + return useQuery( + marsCreditManagerQueryKeys.allCoinBalances(client?.contractAddress, args), + () => + client + ? client.allCoinBalances({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerHealthQuery + extends MarsCreditManagerReactQuery { + args: { + accountId: string + } +} +export function useMarsCreditManagerHealthQuery({ + client, + args, + options, +}: MarsCreditManagerHealthQuery) { + return useQuery( + marsCreditManagerQueryKeys.health(client?.contractAddress, args), + () => + client + ? client.health({ + accountId: args.accountId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerPositionsQuery + extends MarsCreditManagerReactQuery { + args: { + accountId: string + } +} +export function useMarsCreditManagerPositionsQuery({ + client, + args, + options, +}: MarsCreditManagerPositionsQuery) { + return useQuery( + marsCreditManagerQueryKeys.positions(client?.contractAddress, args), + () => + client + ? client.positions({ + accountId: args.accountId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerAllowedCoinsQuery + extends MarsCreditManagerReactQuery { + args: { + limit?: number + startAfter?: string + } +} +export function useMarsCreditManagerAllowedCoinsQuery({ + client, + args, + options, +}: MarsCreditManagerAllowedCoinsQuery) { + return useQuery( + marsCreditManagerQueryKeys.allowedCoins(client?.contractAddress, args), + () => + client + ? client.allowedCoins({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerVaultConfigsQuery + extends MarsCreditManagerReactQuery { + args: { + limit?: number + startAfter?: VaultBaseForString + } +} +export function useMarsCreditManagerVaultConfigsQuery({ + client, + args, + options, +}: MarsCreditManagerVaultConfigsQuery) { + return useQuery( + marsCreditManagerQueryKeys.vaultConfigs(client?.contractAddress, args), + () => + client + ? client.vaultConfigs({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerConfigQuery + extends MarsCreditManagerReactQuery {} +export function useMarsCreditManagerConfigQuery({ + client, + options, +}: MarsCreditManagerConfigQuery) { + return useQuery( + marsCreditManagerQueryKeys.config(client?.contractAddress), + () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerCallbackMutation { + client: MarsCreditManagerClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsCreditManagerCallbackMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.callback(msg, fee, memo, funds), + options, + ) +} +export interface MarsCreditManagerUpdateAdminMutation { + client: MarsCreditManagerClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsCreditManagerUpdateAdminMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateAdmin(msg, fee, memo, funds), + options, + ) +} +export interface MarsCreditManagerUpdateConfigMutation { + client: MarsCreditManagerClient + msg: { + newConfig: ConfigUpdates + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsCreditManagerUpdateConfigMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateConfig(msg, fee, memo, funds), + options, + ) +} +export interface MarsCreditManagerUpdateCreditAccountMutation { + client: MarsCreditManagerClient + msg: { + accountId: string + actions: Action[] + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsCreditManagerUpdateCreditAccountMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateCreditAccount(msg, fee, memo, funds), + options, + ) +} +export interface MarsCreditManagerCreateCreditAccountMutation { + client: MarsCreditManagerClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsCreditManagerCreateCreditAccountMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, args: { fee, memo, funds } = {} }) => client.createCreditAccount(fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 314b1b6b0..2d8f6638a 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -55,6 +55,9 @@ export type ExecuteMsg = new_config: ConfigUpdates } } + | { + update_admin: AdminExecuteUpdate + } | { callback: CallbackMsg } @@ -134,6 +137,18 @@ export type Action = refund_all_coin_balances: {} } export type VaultPositionType = 'u_n_l_o_c_k_e_d' | 'l_o_c_k_e_d' | 'u_n_l_o_c_k_i_n_g' +export type AdminExecuteUpdate = + | ('clear_proposed' | 'accept_proposed' | 'abolish_admin_role') + | { + initialize_admin: { + admin: string + } + } + | { + propose_new_admin: { + proposed: string + } + } export type CallbackMsg = | { withdraw: { @@ -253,7 +268,6 @@ export type CallbackMsg = export type Addr = string export interface ConfigUpdates { account_nft?: string | null - admin?: string | null allowed_coins?: string[] | null max_close_factor?: Decimal | null max_unlocking_positions?: Uint128 | null @@ -396,6 +410,7 @@ export interface ConfigResponse { max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: string + proposed_new_admin?: string | null red_bank: string swapper: string zapper: string diff --git a/scripts/types/generated/mars-credit-manager/bundle.ts b/scripts/types/generated/mars-credit-manager/bundle.ts new file mode 100644 index 000000000..fbcf26e0d --- /dev/null +++ b/scripts/types/generated/mars-credit-manager/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _4 from './MarsCreditManager.types' +import * as _5 from './MarsCreditManager.client' +import * as _6 from './MarsCreditManager.message-composer' +import * as _7 from './MarsCreditManager.react-query' +export namespace contracts { + export const MarsCreditManager = { ..._4, ..._5, ..._6, ..._7 } +} diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index 95069a4f0..20b38d3e6 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index 3d03cc874..1ec6cc983 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index 754bc4101..aa05468a2 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -1,12 +1,13 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' import { InstantiateMsg, ExecuteMsg, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 16ccb496d..6c18dae58 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -169,6 +169,7 @@ export interface ConfigResponse { max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: string + proposed_new_admin?: string | null red_bank: string swapper: string zapper: string diff --git a/scripts/types/generated/mars-mock-credit-manager/bundle.ts b/scripts/types/generated/mars-mock-credit-manager/bundle.ts index a30bc8d56..e6069a3ac 100644 --- a/scripts/types/generated/mars-mock-credit-manager/bundle.ts +++ b/scripts/types/generated/mars-mock-credit-manager/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _0 from './MarsMockCreditManager.types' -import * as _1 from './MarsMockCreditManager.client' -import * as _2 from './MarsMockCreditManager.message-composer' -import * as _3 from './MarsMockCreditManager.react-query' +import * as _8 from './MarsMockCreditManager.types' +import * as _9 from './MarsMockCreditManager.client' +import * as _10 from './MarsMockCreditManager.message-composer' +import * as _11 from './MarsMockCreditManager.react-query' export namespace contracts { - export const MarsMockCreditManager = { ..._0, ..._1, ..._2, ..._3 } + export const MarsMockCreditManager = { ..._8, ..._9, ..._10, ..._11 } } diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts index df9bd7f8f..c276f83c2 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts index c28913138..fb6c923b9 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts index 7d043667c..d279c8513 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts @@ -1,12 +1,13 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee, Coin } from '@cosmjs/amino' import { Decimal, InstantiateMsg, diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts index 20bc906db..8e19a4f82 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/bundle.ts b/scripts/types/generated/mars-mock-oracle/bundle.ts index 9399f171c..c42be8bae 100644 --- a/scripts/types/generated/mars-mock-oracle/bundle.ts +++ b/scripts/types/generated/mars-mock-oracle/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _4 from './MarsMockOracle.types' -import * as _5 from './MarsMockOracle.client' -import * as _6 from './MarsMockOracle.message-composer' -import * as _7 from './MarsMockOracle.react-query' +import * as _12 from './MarsMockOracle.types' +import * as _13 from './MarsMockOracle.client' +import * as _14 from './MarsMockOracle.message-composer' +import * as _15 from './MarsMockOracle.react-query' export namespace contracts { - export const MarsMockOracle = { ..._4, ..._5, ..._6, ..._7 } + export const MarsMockOracle = { ..._12, ..._13, ..._14, ..._15 } } diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index f423caa7b..35007a833 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts index 0b05b5e41..4b2c8b3ec 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index 6a7b5aa45..0648c3b4b 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -1,12 +1,13 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee, Coin } from '@cosmjs/amino' import { Decimal, InstantiateMsg, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index 26130dae2..6b6cfb3d6 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/bundle.ts b/scripts/types/generated/mars-mock-red-bank/bundle.ts index b55e5be77..97c046386 100644 --- a/scripts/types/generated/mars-mock-red-bank/bundle.ts +++ b/scripts/types/generated/mars-mock-red-bank/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _8 from './MarsMockRedBank.types' -import * as _9 from './MarsMockRedBank.client' -import * as _10 from './MarsMockRedBank.message-composer' -import * as _11 from './MarsMockRedBank.react-query' +import * as _16 from './MarsMockRedBank.types' +import * as _17 from './MarsMockRedBank.client' +import * as _18 from './MarsMockRedBank.message-composer' +import * as _19 from './MarsMockRedBank.react-query' export namespace contracts { - export const MarsMockRedBank = { ..._8, ..._9, ..._10, ..._11 } + export const MarsMockRedBank = { ..._16, ..._17, ..._18, ..._19 } } diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts index 494efa813..eac26ad70 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts index 42a1441d6..08add0925 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts new file mode 100644 index 000000000..2d900f88e --- /dev/null +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts @@ -0,0 +1,301 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee, Coin } from '@cosmjs/amino' +import { + Duration, + OracleBaseForString, + InstantiateMsg, + ExecuteMsg, + Uint128, + ExtensionExecuteMsg, + LockupExecuteMsg, + ForceUnlockExecuteMsg, + QueryMsg, + ExtensionQueryMsg, + LockupQueryMsg, + VaultInfoResponse, + Empty, + VaultStandardInfoResponse, +} from './MarsMockVault.types' +import { MarsMockVaultQueryClient, MarsMockVaultClient } from './MarsMockVault.client' +export const marsMockVaultQueryKeys = { + contract: [ + { + contract: 'marsMockVault', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...marsMockVaultQueryKeys.contract[0], address: contractAddress }] as const, + vaultStandardInfo: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsMockVaultQueryKeys.address(contractAddress)[0], + method: 'vault_standard_info', + args, + }, + ] as const, + info: (contractAddress: string | undefined, args?: Record) => + [{ ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'info', args }] as const, + previewDeposit: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'preview_deposit', args }, + ] as const, + previewRedeem: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'preview_redeem', args }, + ] as const, + totalAssets: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'total_assets', args }, + ] as const, + totalVaultTokenSupply: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsMockVaultQueryKeys.address(contractAddress)[0], + method: 'total_vault_token_supply', + args, + }, + ] as const, + convertToShares: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'convert_to_shares', args }, + ] as const, + convertToAssets: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'convert_to_assets', args }, + ] as const, + vaultExtension: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsMockVaultQueryKeys.address(contractAddress)[0], method: 'vault_extension', args }, + ] as const, +} +export interface MarsMockVaultReactQuery { + client: MarsMockVaultQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MarsMockVaultVaultExtensionQuery + extends MarsMockVaultReactQuery {} +export function useMarsMockVaultVaultExtensionQuery({ + client, + options, +}: MarsMockVaultVaultExtensionQuery) { + return useQuery( + marsMockVaultQueryKeys.vaultExtension(client?.contractAddress), + () => (client ? client.vaultExtension() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsMockVaultConvertToAssetsQuery + extends MarsMockVaultReactQuery { + args: { + amount: Uint128 + } +} +export function useMarsMockVaultConvertToAssetsQuery({ + client, + args, + options, +}: MarsMockVaultConvertToAssetsQuery) { + return useQuery( + marsMockVaultQueryKeys.convertToAssets(client?.contractAddress, args), + () => + client + ? client.convertToAssets({ + amount: args.amount, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsMockVaultConvertToSharesQuery + extends MarsMockVaultReactQuery { + args: { + amount: Uint128 + } +} +export function useMarsMockVaultConvertToSharesQuery({ + client, + args, + options, +}: MarsMockVaultConvertToSharesQuery) { + return useQuery( + marsMockVaultQueryKeys.convertToShares(client?.contractAddress, args), + () => + client + ? client.convertToShares({ + amount: args.amount, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsMockVaultTotalVaultTokenSupplyQuery + extends MarsMockVaultReactQuery {} +export function useMarsMockVaultTotalVaultTokenSupplyQuery({ + client, + options, +}: MarsMockVaultTotalVaultTokenSupplyQuery) { + return useQuery( + marsMockVaultQueryKeys.totalVaultTokenSupply(client?.contractAddress), + () => (client ? client.totalVaultTokenSupply() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsMockVaultTotalAssetsQuery + extends MarsMockVaultReactQuery {} +export function useMarsMockVaultTotalAssetsQuery({ + client, + options, +}: MarsMockVaultTotalAssetsQuery) { + return useQuery( + marsMockVaultQueryKeys.totalAssets(client?.contractAddress), + () => (client ? client.totalAssets() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsMockVaultPreviewRedeemQuery + extends MarsMockVaultReactQuery { + args: { + amount: Uint128 + } +} +export function useMarsMockVaultPreviewRedeemQuery({ + client, + args, + options, +}: MarsMockVaultPreviewRedeemQuery) { + return useQuery( + marsMockVaultQueryKeys.previewRedeem(client?.contractAddress, args), + () => + client + ? client.previewRedeem({ + amount: args.amount, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsMockVaultPreviewDepositQuery + extends MarsMockVaultReactQuery { + args: { + amount: Uint128 + } +} +export function useMarsMockVaultPreviewDepositQuery({ + client, + args, + options, +}: MarsMockVaultPreviewDepositQuery) { + return useQuery( + marsMockVaultQueryKeys.previewDeposit(client?.contractAddress, args), + () => + client + ? client.previewDeposit({ + amount: args.amount, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsMockVaultInfoQuery + extends MarsMockVaultReactQuery {} +export function useMarsMockVaultInfoQuery({ + client, + options, +}: MarsMockVaultInfoQuery) { + return useQuery( + marsMockVaultQueryKeys.info(client?.contractAddress), + () => (client ? client.info() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsMockVaultVaultStandardInfoQuery + extends MarsMockVaultReactQuery {} +export function useMarsMockVaultVaultStandardInfoQuery({ + client, + options, +}: MarsMockVaultVaultStandardInfoQuery) { + return useQuery( + marsMockVaultQueryKeys.vaultStandardInfo(client?.contractAddress), + () => (client ? client.vaultStandardInfo() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsMockVaultVaultExtensionMutation { + client: MarsMockVaultClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsMockVaultVaultExtensionMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.vaultExtension(msg, fee, memo, funds), + options, + ) +} +export interface MarsMockVaultRedeemMutation { + client: MarsMockVaultClient + msg: { + amount: Uint128 + recipient?: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsMockVaultRedeemMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.redeem(msg, fee, memo, funds), + options, + ) +} +export interface MarsMockVaultDepositMutation { + client: MarsMockVaultClient + msg: { + amount: Uint128 + recipient?: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsMockVaultDepositMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.deposit(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts index 49fbd9803..a66861b5b 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/bundle.ts b/scripts/types/generated/mars-mock-vault/bundle.ts new file mode 100644 index 000000000..2b9660d97 --- /dev/null +++ b/scripts/types/generated/mars-mock-vault/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _20 from './MarsMockVault.types' +import * as _21 from './MarsMockVault.client' +import * as _22 from './MarsMockVault.message-composer' +import * as _23 from './MarsMockVault.react-query' +export namespace contracts { + export const MarsMockVault = { ..._20, ..._21, ..._22, ..._23 } +} diff --git a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts index 7ee488a61..06364e005 100644 --- a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts +++ b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts index fbd07c267..02af784d5 100644 --- a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts +++ b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts index 8dfc93e07..fc1230be0 100644 --- a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts +++ b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts @@ -1,12 +1,13 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' import { OracleBaseForString, InstantiateMsg, diff --git a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.types.ts b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.types.ts index 6b16d2d9c..b32009995 100644 --- a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.types.ts +++ b/scripts/types/generated/mars-mock-zapper/MarsMockZapper.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-zapper/bundle.ts b/scripts/types/generated/mars-mock-zapper/bundle.ts index a2a6a7cc4..a586bd4fc 100644 --- a/scripts/types/generated/mars-mock-zapper/bundle.ts +++ b/scripts/types/generated/mars-mock-zapper/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _12 from './MarsMockZapper.types' -import * as _13 from './MarsMockZapper.client' -import * as _14 from './MarsMockZapper.message-composer' -import * as _15 from './MarsMockZapper.react-query' +import * as _24 from './MarsMockZapper.types' +import * as _25 from './MarsMockZapper.client' +import * as _26 from './MarsMockZapper.message-composer' +import * as _27 from './MarsMockZapper.react-query' export namespace contracts { - export const MarsMockZapper = { ..._12, ..._13, ..._14, ..._15 } + export const MarsMockZapper = { ..._24, ..._25, ..._26, ..._27 } } diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts index 09652b831..c60091931 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -14,6 +14,7 @@ import { InstantiateMsg, VaultPricingInfo, ExecuteMsg, + AdminExecuteUpdate, ConfigUpdates, QueryMsg, ArrayOfVaultPricingInfo, @@ -95,6 +96,11 @@ export interface MarsOracleAdapterInterface extends MarsOracleAdapterReadOnlyInt memo?: string, funds?: Coin[], ) => Promise + updateAdmin: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise } export class MarsOracleAdapterClient extends MarsOracleAdapterQueryClient @@ -110,6 +116,7 @@ export class MarsOracleAdapterClient this.sender = sender this.contractAddress = contractAddress this.updateConfig = this.updateConfig.bind(this) + this.updateAdmin = this.updateAdmin.bind(this) } updateConfig = async ( @@ -135,4 +142,20 @@ export class MarsOracleAdapterClient funds, ) } + updateAdmin = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_admin: {}, + }, + fee, + memo, + funds, + ) + } } diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts index 5b4533bee..8f6fc1448 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -16,6 +16,7 @@ import { InstantiateMsg, VaultPricingInfo, ExecuteMsg, + AdminExecuteUpdate, ConfigUpdates, QueryMsg, ArrayOfVaultPricingInfo, @@ -35,6 +36,7 @@ export interface MarsOracleAdapterMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject + updateAdmin: (funds?: Coin[]) => MsgExecuteContractEncodeObject } export class MarsOracleAdapterMessageComposer implements MarsOracleAdapterMessage { sender: string @@ -44,6 +46,7 @@ export class MarsOracleAdapterMessageComposer implements MarsOracleAdapterMessag this.sender = sender this.contractAddress = contractAddress this.updateConfig = this.updateConfig.bind(this) + this.updateAdmin = this.updateAdmin.bind(this) } updateConfig = ( @@ -70,4 +73,19 @@ export class MarsOracleAdapterMessageComposer implements MarsOracleAdapterMessag }), } } + updateAdmin = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_admin: {}, + }), + ), + funds, + }), + } + } } diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts index 806626be5..64f9f711c 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts @@ -1,12 +1,13 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee, Coin } from '@cosmjs/amino' import { OracleBaseForString, Addr, @@ -14,6 +15,7 @@ import { InstantiateMsg, VaultPricingInfo, ExecuteMsg, + AdminExecuteUpdate, ConfigUpdates, QueryMsg, ArrayOfVaultPricingInfo, @@ -139,6 +141,25 @@ export function useMarsOracleAdapterPriceQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsOracleAdapterUpdateAdminMutation { + client: MarsOracleAdapterClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsOracleAdapterUpdateAdminMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateAdmin(msg, fee, memo, funds), + options, + ) +} export interface MarsOracleAdapterUpdateConfigMutation { client: MarsOracleAdapterClient msg: { diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts index 4aa74f917..46d8a208b 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -19,13 +19,28 @@ export interface VaultPricingInfo { method: PricingMethod vault_coin_denom: string } -export type ExecuteMsg = { - update_config: { - new_config: ConfigUpdates - } -} +export type ExecuteMsg = + | { + update_config: { + new_config: ConfigUpdates + } + } + | { + update_admin: AdminExecuteUpdate + } +export type AdminExecuteUpdate = + | ('clear_proposed' | 'accept_proposed' | 'abolish_admin_role') + | { + initialize_admin: { + admin: string + } + } + | { + propose_new_admin: { + proposed: string + } + } export interface ConfigUpdates { - admin?: string | null oracle?: OracleBaseForString | null vault_pricing?: VaultPricingInfo[] | null } @@ -52,8 +67,9 @@ export type QueryMsg = export type ArrayOfVaultPricingInfo = VaultPricingInfo[] export type OracleBaseForAddr = string export interface ConfigResponse { - admin?: Addr | null + admin?: string | null oracle: OracleBaseForAddr + proposed_new_admin?: string | null } export type Decimal = string export interface PriceResponse { diff --git a/scripts/types/generated/mars-oracle-adapter/bundle.ts b/scripts/types/generated/mars-oracle-adapter/bundle.ts index f7f58cd5d..bc697aee4 100644 --- a/scripts/types/generated/mars-oracle-adapter/bundle.ts +++ b/scripts/types/generated/mars-oracle-adapter/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _16 from './MarsOracleAdapter.types' -import * as _17 from './MarsOracleAdapter.client' -import * as _18 from './MarsOracleAdapter.message-composer' -import * as _19 from './MarsOracleAdapter.react-query' +import * as _28 from './MarsOracleAdapter.types' +import * as _29 from './MarsOracleAdapter.client' +import * as _30 from './MarsOracleAdapter.message-composer' +import * as _31 from './MarsOracleAdapter.react-query' export namespace contracts { - export const MarsOracleAdapter = { ..._16, ..._17, ..._18, ..._19 } + export const MarsOracleAdapter = { ..._28, ..._29, ..._30, ..._31 } } diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts index 8fd404c17..aa0ac8a1f 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts index ca0682c1d..73d0e0510 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts index 0c069698b..33e6625d7 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts @@ -1,12 +1,13 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' import { InstantiateMsg, ExecuteMsg, diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts index 89a54edec..9cc73da9c 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/bundle.ts b/scripts/types/generated/mars-swapper-base/bundle.ts index e75a5bf26..0e9e9246a 100644 --- a/scripts/types/generated/mars-swapper-base/bundle.ts +++ b/scripts/types/generated/mars-swapper-base/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.23.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _20 from './MarsSwapperBase.types' -import * as _21 from './MarsSwapperBase.client' -import * as _22 from './MarsSwapperBase.message-composer' -import * as _23 from './MarsSwapperBase.react-query' +import * as _32 from './MarsSwapperBase.types' +import * as _33 from './MarsSwapperBase.client' +import * as _34 from './MarsSwapperBase.message-composer' +import * as _35 from './MarsSwapperBase.react-query' export namespace contracts { - export const MarsSwapperBase = { ..._20, ..._21, ..._22, ..._23 } + export const MarsSwapperBase = { ..._32, ..._33, ..._34, ..._35 } } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index f8a153a5a..608c0014c 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1325,10 +1325,10 @@ resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.4.tgz#8a80da006fe2b544a3c36f557e4b782810e532fd" integrity sha512-X1pZWRHDbTPLa6cYW0NHvtig+lSxOdLAX7K/xp67ywBy2knnDOyzz1utGTOowmiM98XuV9quK/BWePKkJOaHpQ== -"@cosmwasm/ts-codegen@^0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.23.0.tgz#42f4de325f9e20a2248c6a6cd74a862bd0fac22d" - integrity sha512-vQRhvmS7YoR6EFommHlL/espo8f9boc9eqDbX6ifPNENID4uu23eTPq5KGtRyDfYNe+Zld5zE7RP2hnxmx55JQ== +"@cosmwasm/ts-codegen@^0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.24.0.tgz#61015220a9dccfd35dec46bcb67786441111f096" + integrity sha512-g5ufDroLzOzsMvnKyAz7i5M3Z3k8XYetRv9PnGW0Zbgj4sziw6jDimPJ3Ubnput+i380day7LnCwVT7zLIEzCQ== dependencies: "@babel/core" "7.18.10" "@babel/generator" "7.18.12" @@ -1356,7 +1356,7 @@ parse-package-name "1.0.0" rimraf "3.0.2" shelljs "0.8.5" - wasm-ast-types "^0.16.0" + wasm-ast-types "^0.17.0" "@eslint/eslintrc@^1.3.3": version "1.3.3" @@ -4933,10 +4933,10 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -wasm-ast-types@^0.16.0: - version "0.16.0" - resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.16.0.tgz#2d2af5a2e78b86b284587513375f27b99dca903d" - integrity sha512-QFh/ubf+s72VyJQm4TPEyv9QXJvr3iTV+vU60qnc3802i+yXAmNBDZPdYVf0sKKr4bz5dtsuuiknXpF+gwlpJg== +wasm-ast-types@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.17.0.tgz#417280a61d60ea9964667cf2edb8f5281dc295d7" + integrity sha512-WeriXPbG67iI51Mf/5qRR0xcpEaTO/Wyjpl+vsmjZ5K6q/0W6iO03zHsESNIH/hpc5FPTpb0Y0L9xAtnnNe9Ow== dependencies: "@babel/runtime" "^7.18.9" "@babel/types" "7.18.10" From a07a55a8b78a4617d4fde71e868a4e780258490f Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 7 Dec 2022 09:40:53 +0100 Subject: [PATCH 095/218] Update swapper to new admin package (#64) * Update swapper to new admin package * Update api --- Cargo.lock | 20 +- Cargo.toml | 1 - contracts/credit-manager/src/instantiate.rs | 11 +- contracts/credit-manager/src/update_config.rs | 6 +- .../credit-manager/tests/helpers/mock_env.rs | 8 +- .../credit-manager/tests/test_update_admin.rs | 26 +- contracts/oracle-adapter/src/contract.rs | 11 +- contracts/oracle-adapter/src/msg.rs | 4 +- .../oracle-adapter/tests/test_update_admin.rs | 18 +- contracts/swapper/base/Cargo.toml | 16 +- contracts/swapper/base/src/contract.rs | 30 +- contracts/swapper/base/src/error.rs | 2 +- contracts/swapper/mock/src/contract.rs | 2 +- contracts/swapper/osmosis/Cargo.toml | 22 +- .../swapper/osmosis/tests/test_instantiate.rs | 2 +- .../swapper/osmosis/tests/test_set_route.rs | 2 +- .../osmosis/tests/test_update_admin.rs | 192 +++++- packages/controllers/src/admin.rs | 632 +++++++++--------- packages/controllers/src/lib.rs | 2 +- packages/rover/Cargo.toml | 1 - packages/rover/src/adapters/swap/msgs.rs | 9 +- packages/rover/src/msg/execute.rs | 5 +- .../mars-credit-manager.json | 56 +- .../mars-oracle-adapter.json | 56 +- .../mars-swapper-base/mars-swapper-base.json | 70 +- .../MarsCreditManager.client.ts | 2 +- .../MarsCreditManager.message-composer.ts | 2 +- .../MarsCreditManager.react-query.ts | 2 +- .../MarsCreditManager.types.ts | 13 +- .../MarsOracleAdapter.client.ts | 2 +- .../MarsOracleAdapter.message-composer.ts | 2 +- .../MarsOracleAdapter.react-query.ts | 2 +- .../MarsOracleAdapter.types.ts | 13 +- .../MarsSwapperBase.client.ts | 15 +- .../MarsSwapperBase.message-composer.ts | 23 +- .../MarsSwapperBase.react-query.ts | 4 +- .../MarsSwapperBase.types.ts | 14 +- 37 files changed, 707 insertions(+), 591 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98c790569..979dd51b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -391,21 +391,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "cw-controllers" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343ee0fb235c63a37694b030b48461581d89e119bc37fb4cb456fad90766123b" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw-controllers-admin-fork" version = "1.0.0" @@ -1080,7 +1065,6 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vault-standard", - "cw-controllers", "cw-controllers-admin-fork", "cw-storage-plus", "cw-utils", @@ -1099,7 +1083,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-controllers", + "cw-controllers-admin-fork", "cw-storage-plus", "mars-rover", "schemars", @@ -1127,7 +1111,7 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-controllers", + "cw-controllers-admin-fork", "cw-storage-plus", "cw2", "mars-osmosis", diff --git a/Cargo.toml b/Cargo.toml index a3edddfc8..26123ddc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,6 @@ cosmwasm-std = "1.1" cw2 = "0.16" cw721 = "0.16" cw721-base = { version = "0.16", features = ["library"] } -cw-controllers = "1.0" cw-item-set = { version = "0.6", default-features = false, features = ["iterator"] } cw-multi-test = "0.16" cw-utils = "0.16" diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 1edde5f26..e6102772a 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,8 +1,8 @@ use std::collections::HashSet; use cosmwasm_std::{Decimal, DepsMut}; +use cw_controllers_admin_fork::AdminInit::SetInitialAdmin; -use cw_controllers_admin_fork::AdminUpdate::InitializeAdmin; use mars_rover::error::ContractError::InvalidConfig; use mars_rover::error::ContractResult; use mars_rover::msg::instantiate::VaultInstantiateConfig; @@ -15,8 +15,13 @@ use crate::state::{ }; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { - let admin = deps.api.addr_validate(&msg.admin)?; - ADMIN.update(deps.storage, InitializeAdmin { admin })?; + ADMIN.initialize( + deps.storage, + deps.api, + SetInitialAdmin { + admin: msg.admin.clone(), + }, + )?; RED_BANK.save(deps.storage, &msg.red_bank.check(deps.api)?)?; ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index 1763fe96d..2697e1fb7 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, MessageInfo, Response, WasmMsg}; -use cw_controllers_admin_fork::AdminExecuteUpdate; +use cw_controllers_admin_fork::AdminUpdate; use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; use mars_rover::error::ContractResult; use mars_rover::msg::instantiate::ConfigUpdates; @@ -109,7 +109,7 @@ pub fn update_config( pub fn update_admin( deps: DepsMut, info: MessageInfo, - update: AdminExecuteUpdate, + update: AdminUpdate, ) -> ContractResult { - Ok(ADMIN.execute_update(deps, info, update)?) + Ok(ADMIN.update(deps, info, update)?) } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 89e8b8c2c..f75022bc7 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -6,7 +6,7 @@ use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; use cosmwasm_vault_standard::extensions::lockup::{LockupQueryMsg, UnlockingPosition}; use cosmwasm_vault_standard::msg::VaultStandardQueryMsg::{Info as VaultInfoMsg, VaultExtension}; use cosmwasm_vault_standard::msg::{ExtensionQueryMsg, VaultInfoResponse}; -use cw_controllers_admin_fork::AdminExecuteUpdate; +use cw_controllers_admin_fork::AdminUpdate; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use mars_account_nft::msg::InstantiateMsg as NftInstantiateMsg; @@ -192,11 +192,7 @@ impl MockEnv { ) } - pub fn update_admin( - &mut self, - sender: &Addr, - update: AdminExecuteUpdate, - ) -> AnyResult { + pub fn update_admin(&mut self, sender: &Addr, update: AdminUpdate) -> AnyResult { self.app.execute_contract( sender.clone(), self.rover.clone(), diff --git a/contracts/credit-manager/tests/test_update_admin.rs b/contracts/credit-manager/tests/test_update_admin.rs index 0ee3f8e33..0ec5ddf20 100644 --- a/contracts/credit-manager/tests/test_update_admin.rs +++ b/contracts/credit-manager/tests/test_update_admin.rs @@ -1,6 +1,6 @@ use cosmwasm_std::Addr; use cw_controllers_admin_fork::AdminError::{NotAdmin, NotProposedAdmin, StateTransitionError}; -use cw_controllers_admin_fork::AdminExecuteUpdate; +use cw_controllers_admin_fork::AdminUpdate; use mars_rover::error::ContractError::AdminError; use crate::helpers::{assert_err, MockEnv}; @@ -27,7 +27,7 @@ fn test_propose_new_admin() { let bad_guy = Addr::unchecked("bad_guy"); let res = mock.update_admin( &bad_guy, - AdminExecuteUpdate::ProposeNewAdmin { + AdminUpdate::ProposeNewAdmin { proposed: bad_guy.to_string(), }, ); @@ -35,7 +35,7 @@ fn test_propose_new_admin() { mock.update_admin( &Addr::unchecked(original_config.admin.clone().unwrap()), - AdminExecuteUpdate::ProposeNewAdmin { + AdminUpdate::ProposeNewAdmin { proposed: new_admin.clone(), }, ) @@ -60,7 +60,7 @@ fn test_clear_proposed() { mock.update_admin( &Addr::unchecked(original_config.admin.clone().unwrap()), - AdminExecuteUpdate::ProposeNewAdmin { + AdminUpdate::ProposeNewAdmin { proposed: new_admin.clone(), }, ) @@ -72,12 +72,12 @@ fn test_clear_proposed() { // only admin can clear let bad_guy = Addr::unchecked("bad_guy"); - let res = mock.update_admin(&bad_guy, AdminExecuteUpdate::ClearProposed); + let res = mock.update_admin(&bad_guy, AdminUpdate::ClearProposed); assert_err(res, AdminError(NotAdmin {})); mock.update_admin( &Addr::unchecked(original_config.admin.clone().unwrap()), - AdminExecuteUpdate::ClearProposed, + AdminUpdate::ClearProposed, ) .unwrap(); @@ -100,7 +100,7 @@ fn test_accept_admin_role() { mock.update_admin( &Addr::unchecked(original_config.admin.clone().unwrap()), - AdminExecuteUpdate::ProposeNewAdmin { + AdminUpdate::ProposeNewAdmin { proposed: new_admin.clone(), }, ) @@ -109,13 +109,13 @@ fn test_accept_admin_role() { // Only proposed admin can accept let res = mock.update_admin( &Addr::unchecked(original_config.admin.unwrap()), - AdminExecuteUpdate::AcceptProposed, + AdminUpdate::AcceptProposed, ); assert_err(res, AdminError(NotProposedAdmin {})); mock.update_admin( &Addr::unchecked(new_admin.clone()), - AdminExecuteUpdate::AcceptProposed, + AdminUpdate::AcceptProposed, ) .unwrap(); @@ -132,12 +132,12 @@ fn test_abolish_admin_role() { // Only admin can abolish role let bad_guy = Addr::unchecked("bad_guy"); - let res = mock.update_admin(&bad_guy, AdminExecuteUpdate::AbolishAdminRole); + let res = mock.update_admin(&bad_guy, AdminUpdate::AbolishAdminRole); assert_err(res, AdminError(NotAdmin {})); mock.update_admin( &Addr::unchecked(original_config.admin.clone().unwrap()), - AdminExecuteUpdate::AbolishAdminRole, + AdminUpdate::AbolishAdminRole, ) .unwrap(); @@ -149,8 +149,8 @@ fn test_abolish_admin_role() { // No new updates can occur let res = mock.update_admin( &Addr::unchecked(original_config.admin.clone().unwrap()), - AdminExecuteUpdate::InitializeAdmin { - admin: original_config.admin.unwrap(), + AdminUpdate::ProposeNewAdmin { + proposed: original_config.admin.unwrap(), }, ); assert_err(res, AdminError(StateTransitionError {})); diff --git a/contracts/oracle-adapter/src/contract.rs b/contracts/oracle-adapter/src/contract.rs index 7c4e9fd22..a3f16b354 100644 --- a/contracts/oracle-adapter/src/contract.rs +++ b/contracts/oracle-adapter/src/contract.rs @@ -4,10 +4,10 @@ use cosmwasm_std::{ to_binary, Addr, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, }; use cw2::set_contract_version; +use cw_controllers_admin_fork::AdminInit::SetInitialAdmin; use cw_storage_plus::Bound; -use cw_controllers_admin_fork::AdminExecuteUpdate; -use cw_controllers_admin_fork::AdminUpdate::InitializeAdmin; +use cw_controllers_admin_fork::AdminUpdate; use mars_outpost::oracle::PriceResponse; use mars_rover::adapters::vault::VaultBase; use mars_rover::adapters::Oracle; @@ -39,8 +39,7 @@ pub fn instantiate( CONTRACT_VERSION, )?; - let admin = deps.api.addr_validate(&msg.admin)?; - ADMIN.update(deps.storage, InitializeAdmin { admin })?; + ADMIN.initialize(deps.storage, deps.api, SetInitialAdmin { admin: msg.admin })?; let oracle = msg.oracle.check(deps.api)?; ORACLE.save(deps.storage, &oracle)?; @@ -192,7 +191,7 @@ pub fn update_config( pub fn update_admin( deps: DepsMut, info: MessageInfo, - update: AdminExecuteUpdate, + update: AdminUpdate, ) -> ContractResult { - Ok(ADMIN.execute_update(deps, info, update)?) + Ok(ADMIN.update(deps, info, update)?) } diff --git a/contracts/oracle-adapter/src/msg.rs b/contracts/oracle-adapter/src/msg.rs index d0f700f13..c3de5d459 100644 --- a/contracts/oracle-adapter/src/msg.rs +++ b/contracts/oracle-adapter/src/msg.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Decimal}; -use cw_controllers_admin_fork::AdminExecuteUpdate; +use cw_controllers_admin_fork::AdminUpdate; use mars_rover::adapters::{Oracle, OracleUnchecked}; @@ -20,7 +20,7 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { UpdateConfig { new_config: ConfigUpdates }, - UpdateAdmin(AdminExecuteUpdate), + UpdateAdmin(AdminUpdate), } #[cw_serde] diff --git a/contracts/oracle-adapter/tests/test_update_admin.rs b/contracts/oracle-adapter/tests/test_update_admin.rs index b26204720..92c264c89 100644 --- a/contracts/oracle-adapter/tests/test_update_admin.rs +++ b/contracts/oracle-adapter/tests/test_update_admin.rs @@ -1,5 +1,5 @@ use cosmwasm_std::Addr; -use cw_controllers_admin_fork::AdminExecuteUpdate; +use cw_controllers_admin_fork::AdminUpdate; use cw_multi_test::{App, Executor}; use mars_oracle_adapter::msg::{ConfigResponse, ExecuteMsg, QueryMsg}; @@ -37,7 +37,7 @@ fn test_propose_new_admin() { app.execute_contract( bad_guy.clone(), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::ProposeNewAdmin { + &ExecuteMsg::UpdateAdmin(AdminUpdate::ProposeNewAdmin { proposed: bad_guy.to_string(), }), &[], @@ -47,7 +47,7 @@ fn test_propose_new_admin() { app.execute_contract( Addr::unchecked(original_config.admin.clone().unwrap()), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::ProposeNewAdmin { + &ExecuteMsg::UpdateAdmin(AdminUpdate::ProposeNewAdmin { proposed: new_admin.clone(), }), &[], @@ -81,7 +81,7 @@ fn test_clear_proposed() { app.execute_contract( Addr::unchecked(original_config.admin.clone().unwrap()), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::ProposeNewAdmin { + &ExecuteMsg::UpdateAdmin(AdminUpdate::ProposeNewAdmin { proposed: new_admin.clone(), }), &[], @@ -100,7 +100,7 @@ fn test_clear_proposed() { app.execute_contract( bad_guy, contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::ClearProposed), + &ExecuteMsg::UpdateAdmin(AdminUpdate::ClearProposed), &[], ) .unwrap_err(); @@ -108,7 +108,7 @@ fn test_clear_proposed() { app.execute_contract( Addr::unchecked(original_config.admin.clone().unwrap()), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::ClearProposed), + &ExecuteMsg::UpdateAdmin(AdminUpdate::ClearProposed), &[], ) .unwrap(); @@ -140,7 +140,7 @@ fn test_accept_admin_role() { app.execute_contract( Addr::unchecked(original_config.admin.clone().unwrap()), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::ProposeNewAdmin { + &ExecuteMsg::UpdateAdmin(AdminUpdate::ProposeNewAdmin { proposed: new_admin.clone(), }), &[], @@ -151,7 +151,7 @@ fn test_accept_admin_role() { app.execute_contract( Addr::unchecked(original_config.admin.unwrap()), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::AcceptProposed), + &ExecuteMsg::UpdateAdmin(AdminUpdate::AcceptProposed), &[], ) .unwrap_err(); @@ -159,7 +159,7 @@ fn test_accept_admin_role() { app.execute_contract( Addr::unchecked(new_admin.clone()), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminExecuteUpdate::AcceptProposed), + &ExecuteMsg::UpdateAdmin(AdminUpdate::AcceptProposed), &[], ) .unwrap(); diff --git a/contracts/swapper/base/Cargo.toml b/contracts/swapper/base/Cargo.toml index 78941c051..f4756dbbe 100644 --- a/contracts/swapper/base/Cargo.toml +++ b/contracts/swapper/base/Cargo.toml @@ -18,11 +18,11 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-controllers = { workspace = true } -cw-storage-plus = { workspace = true } -mars-rover = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-controllers-admin-fork = { workspace = true } +cw-storage-plus = { workspace = true } +mars-rover = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/swapper/base/src/contract.rs b/contracts/swapper/base/src/contract.rs index de0facc3a..d13ac5870 100644 --- a/contracts/swapper/base/src/contract.rs +++ b/contracts/swapper/base/src/contract.rs @@ -4,9 +4,10 @@ use cosmwasm_std::{ to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, CustomMsg, CustomQuery, Decimal, Deps, DepsMut, Env, MessageInfo, Order, Response, WasmMsg, }; -use cw_controllers::{Admin, AdminResponse}; use cw_storage_plus::{Bound, Map}; +use cw_controllers_admin_fork::AdminInit::SetInitialAdmin; +use cw_controllers_admin_fork::{Admin, AdminUpdate}; use mars_rover::adapters::swap::{ EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg, RouteResponse, RoutesResponse, @@ -60,8 +61,8 @@ where deps: DepsMut, msg: InstantiateMsg, ) -> ContractResult> { - let validated = deps.api.addr_validate(&msg.admin)?; - self.admin.set(deps, Some(validated))?; + self.admin + .initialize(deps.storage, deps.api, SetInitialAdmin { admin: msg.admin })?; Ok(Response::default()) } @@ -73,7 +74,7 @@ where msg: ExecuteMsg, ) -> ContractResult> { match msg { - ExecuteMsg::UpdateAdmin { admin } => self.update_admin(deps, info.sender, &admin), + ExecuteMsg::UpdateAdmin(update) => self.update_admin(deps, info, update), ExecuteMsg::SetRoute { denom_in, denom_out, @@ -94,7 +95,7 @@ where pub fn query(&self, deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { let res = match msg { - QueryMsg::Admin {} => to_binary(&self.query_admin(deps)?), + QueryMsg::Admin {} => to_binary(&self.admin.query(deps.storage)?), QueryMsg::EstimateExactInSwap { coin_in, denom_out } => { to_binary(&self.estimate_exact_in_swap(deps, env, coin_in, denom_out)?) } @@ -109,10 +110,6 @@ where res.map_err(Into::into) } - fn query_admin(&self, deps: Deps) -> ContractResult { - Ok(self.admin.query_admin(deps)?) - } - fn query_route( &self, deps: Deps, @@ -244,7 +241,7 @@ where denom_out: String, route: R, ) -> ContractResult> { - self.admin.assert_admin(deps.as_ref(), &sender)?; + self.admin.assert_admin(deps.storage, &sender)?; route.validate(&deps.querier, &denom_in, &denom_out)?; @@ -261,16 +258,9 @@ where fn update_admin( &self, deps: DepsMut, - sender: Addr, - admin: &str, + info: MessageInfo, + update: AdminUpdate, ) -> ContractResult> { - self.admin.assert_admin(deps.as_ref(), &sender)?; - let validated = deps.api.addr_validate(admin)?; - self.admin.set(deps, Some(validated))?; - - Ok(Response::new() - .add_attribute("action", "rover/swapper-base/update_admin") - .add_attribute("key", "owner") - .add_attribute("value", admin)) + Ok(self.admin.update(deps, info, update)?) } } diff --git a/contracts/swapper/base/src/error.rs b/contracts/swapper/base/src/error.rs index c2a79cec7..5c7ad584c 100644 --- a/contracts/swapper/base/src/error.rs +++ b/contracts/swapper/base/src/error.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{CheckedMultiplyRatioError, DecimalRangeExceeded, OverflowError, StdError}; -use cw_controllers::AdminError; +use cw_controllers_admin_fork::AdminError; use mars_rover::error::ContractError as RoverError; use thiserror::Error; diff --git a/contracts/swapper/mock/src/contract.rs b/contracts/swapper/mock/src/contract.rs index 75e49ab94..3b93f3c59 100644 --- a/contracts/swapper/mock/src/contract.rs +++ b/contracts/swapper/mock/src/contract.rs @@ -27,7 +27,7 @@ pub fn execute( msg: ExecuteMsg, ) -> StdResult { match msg { - ExecuteMsg::UpdateAdmin { .. } => unimplemented!("not implemented"), + ExecuteMsg::UpdateAdmin(_) => unimplemented!("not implemented"), ExecuteMsg::SetRoute { .. } => unimplemented!("not implemented"), ExecuteMsg::TransferResult { .. } => unimplemented!("not implemented"), ExecuteMsg::SwapExactIn { diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index 5abba1647..3c5e28f0d 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -18,17 +18,17 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -cw-controllers = { workspace = true } -cw-storage-plus = { workspace = true } -mars-osmosis = { version = "1.0.0", path = "../../../packages/chains/osmosis" } -mars-swapper-base = { workspace = true } -mars-rover = { workspace = true } -osmosis-std = { workspace = true } -schemars = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw-controllers-admin-fork = { workspace = true } +cw-storage-plus = { workspace = true } +mars-osmosis = { version = "1.0.0", path = "../../../packages/chains/osmosis" } +mars-swapper-base = { workspace = true } +mars-rover = { workspace = true } +osmosis-std = { workspace = true } +schemars = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/swapper/osmosis/tests/test_instantiate.rs b/contracts/swapper/osmosis/tests/test_instantiate.rs index 954c33378..6f059c0b5 100644 --- a/contracts/swapper/osmosis/tests/test_instantiate.rs +++ b/contracts/swapper/osmosis/tests/test_instantiate.rs @@ -1,5 +1,5 @@ use cosmwasm_std::coin; -use cw_controllers::AdminResponse; +use cw_controllers_admin_fork::AdminResponse; use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; use mars_rover::adapters::swap::{InstantiateMsg, QueryMsg}; diff --git a/contracts/swapper/osmosis/tests/test_set_route.rs b/contracts/swapper/osmosis/tests/test_set_route.rs index 93eeabe06..ba55eaf18 100644 --- a/contracts/swapper/osmosis/tests/test_set_route.rs +++ b/contracts/swapper/osmosis/tests/test_set_route.rs @@ -1,6 +1,6 @@ use cosmwasm_std::coin; use cosmwasm_std::StdError::GenericErr; -use cw_controllers::AdminError; +use cw_controllers_admin_fork::AdminError; use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; use osmosis_testing::{Gamm, Module, OsmosisTestApp, Wasm}; diff --git a/contracts/swapper/osmosis/tests/test_update_admin.rs b/contracts/swapper/osmosis/tests/test_update_admin.rs index c70c2e99c..356a5e354 100644 --- a/contracts/swapper/osmosis/tests/test_update_admin.rs +++ b/contracts/swapper/osmosis/tests/test_update_admin.rs @@ -1,16 +1,16 @@ use cosmwasm_std::coin; -use cw_controllers::{AdminError, AdminResponse}; use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; +use cw_controllers_admin_fork::{AdminResponse, AdminUpdate}; use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg}; use mars_swapper_osmosis::route::OsmosisRoute; -use crate::helpers::{assert_err, instantiate_contract}; +use crate::helpers::instantiate_contract; pub mod helpers; #[test] -fn test_only_admin_can_update_admin() { +fn test_initial_state() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -18,26 +18,169 @@ fn test_only_admin_can_update_admin() { .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) .unwrap(); let admin = &accs[0]; + + let contract_addr = instantiate_contract(&wasm, admin); + + let res: AdminResponse = wasm.query(&contract_addr, &QueryMsg::Admin {}).unwrap(); + assert_eq!(res.admin.unwrap(), admin.address()); + assert_eq!(res.proposed, None); +} + +#[test] +fn test_only_admin_can_propose() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let accs = app + .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 3) + .unwrap(); + let admin = &accs[0]; + let bad_guy = &accs[1]; + + let contract_addr = instantiate_contract(&wasm, admin); + + wasm.execute( + &contract_addr, + &ExecuteMsg::::UpdateAdmin(AdminUpdate::ProposeNewAdmin { + proposed: bad_guy.address(), + }), + &[], + bad_guy, + ) + .unwrap_err(); +} + +#[test] +fn test_propose_new_admin() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let accs = app + .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) + .unwrap(); + let admin = &accs[0]; + let new_admin = &accs[1]; + + let contract_addr = instantiate_contract(&wasm, admin); + + wasm.execute( + &contract_addr, + &ExecuteMsg::::UpdateAdmin(AdminUpdate::ProposeNewAdmin { + proposed: new_admin.address(), + }), + &[], + admin, + ) + .unwrap(); + + let res: AdminResponse = wasm.query(&contract_addr, &QueryMsg::Admin {}).unwrap(); + assert_eq!(res.admin.unwrap(), admin.address()); + assert_eq!(res.proposed.unwrap(), new_admin.address()); +} + +#[test] +fn test_only_admin_can_clear_proposed() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let accs = app + .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 3) + .unwrap(); + let admin = &accs[0]; let bad_guy = &accs[1]; + let new_admin = &accs[2]; + + let contract_addr = instantiate_contract(&wasm, admin); + + wasm.execute( + &contract_addr, + &ExecuteMsg::::UpdateAdmin(AdminUpdate::ProposeNewAdmin { + proposed: new_admin.address(), + }), + &[], + admin, + ) + .unwrap(); + + wasm.execute( + &contract_addr, + &ExecuteMsg::::UpdateAdmin(AdminUpdate::ClearProposed), + &[], + bad_guy, + ) + .unwrap_err(); +} + +#[test] +fn test_clear_proposed() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let accs = app + .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) + .unwrap(); + let admin = &accs[0]; + let new_admin = &accs[1]; + + let contract_addr = instantiate_contract(&wasm, admin); + + wasm.execute( + &contract_addr, + &ExecuteMsg::::UpdateAdmin(AdminUpdate::ProposeNewAdmin { + proposed: new_admin.address(), + }), + &[], + admin, + ) + .unwrap(); + + wasm.execute( + &contract_addr, + &ExecuteMsg::::UpdateAdmin(AdminUpdate::ClearProposed), + &[], + admin, + ) + .unwrap(); + + let res: AdminResponse = wasm.query(&contract_addr, &QueryMsg::Admin {}).unwrap(); + assert_eq!(res.admin.unwrap(), admin.address()); + assert_eq!(res.proposed, None); +} + +#[test] +fn test_only_proposed_admin_can_accept_role() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let accs = app + .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) + .unwrap(); + let admin = &accs[0]; + let new_admin = &accs[1]; let contract_addr = instantiate_contract(&wasm, admin); - let res_err = wasm - .execute( - &contract_addr, - &ExecuteMsg::::UpdateAdmin { - admin: bad_guy.address(), - }, - &[], - bad_guy, - ) - .unwrap_err(); - - assert_err(res_err, AdminError::NotAdmin {}); + wasm.execute( + &contract_addr, + &ExecuteMsg::::UpdateAdmin(AdminUpdate::ProposeNewAdmin { + proposed: new_admin.address(), + }), + &[], + admin, + ) + .unwrap(); + + wasm.execute( + &contract_addr, + &ExecuteMsg::::UpdateAdmin(AdminUpdate::AcceptProposed), + &[], + admin, + ) + .unwrap_err(); } #[test] -fn test_update_admin_works_with_full_config() { +fn test_accept_admin_role() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -51,14 +194,23 @@ fn test_update_admin_works_with_full_config() { wasm.execute( &contract_addr, - &ExecuteMsg::::UpdateAdmin { - admin: new_admin.address(), - }, + &ExecuteMsg::::UpdateAdmin(AdminUpdate::ProposeNewAdmin { + proposed: new_admin.address(), + }), &[], admin, ) .unwrap(); + wasm.execute( + &contract_addr, + &ExecuteMsg::::UpdateAdmin(AdminUpdate::AcceptProposed), + &[], + new_admin, + ) + .unwrap(); + let res: AdminResponse = wasm.query(&contract_addr, &QueryMsg::Admin {}).unwrap(); - assert_eq!(res.admin, Some(new_admin.address())); + assert_eq!(res.admin.unwrap(), new_admin.address()); + assert_eq!(res.proposed, None); } diff --git a/packages/controllers/src/admin.rs b/packages/controllers/src/admin.rs index a47a83a04..01efdbb0b 100644 --- a/packages/controllers/src/admin.rs +++ b/packages/controllers/src/admin.rs @@ -102,53 +102,21 @@ struct AdminRoleAbolished; #[cw_serde] pub enum AdminUpdate { - /// Sets the initial admin when none. No restrictions permissions to modify. - InitializeAdmin { admin: Addr }, /// Proposes a new admin to take role. Only current admin can execute. - ProposeNewAdmin { sender: Addr, proposed: Addr }, + ProposeNewAdmin { proposed: String }, /// Clears the currently proposed admin. Only current admin can execute. - ClearProposed { sender: Addr }, + ClearProposed, /// Promotes the proposed admin to be the current one. Only the proposed admin can execute. - AcceptProposed { sender: Addr }, + AcceptProposed, /// Throws away the keys to the Admin role forever. Once done, no admin can ever be set later. - /// Requires Admin permission except if event is dispatched from AdminUninitialized state. - AbolishAdminRole { sender: Option }, -} - -impl<'a> AdminUpdate { - fn from(api: &'a dyn Api, sender: &Addr, update: AdminExecuteUpdate) -> StdResult { - Ok(match update { - AdminExecuteUpdate::InitializeAdmin { admin } => { - let validated = api.addr_validate(&admin)?; - AdminUpdate::InitializeAdmin { admin: validated } - } - AdminExecuteUpdate::ProposeNewAdmin { proposed } => { - let validated = api.addr_validate(&proposed)?; - AdminUpdate::ProposeNewAdmin { - sender: sender.clone(), - proposed: validated, - } - } - AdminExecuteUpdate::ClearProposed => AdminUpdate::ClearProposed { - sender: sender.clone(), - }, - AdminExecuteUpdate::AcceptProposed => AdminUpdate::AcceptProposed { - sender: sender.clone(), - }, - AdminExecuteUpdate::AbolishAdminRole => AdminUpdate::AbolishAdminRole { - sender: Some(sender.clone()), - }, - }) - } + AbolishAdminRole, } -/// Same as above, but used for execute helpers. Sender and inputs are validated. #[cw_serde] -pub enum AdminExecuteUpdate { - InitializeAdmin { admin: String }, - ProposeNewAdmin { proposed: String }, - ClearProposed, - AcceptProposed, +pub enum AdminInit { + /// Sets the initial admin when none. No restrictions permissions to modify. + SetInitialAdmin { admin: String }, + /// Throws away the keys to the Admin role forever. Once done, no admin can ever be set later. AbolishAdminRole, } @@ -164,6 +132,9 @@ pub enum AdminExecuteUpdate { /// - Only the proposed new admin can accept the new role via AcceptProposed {} /// - The current admin can also clear the proposed new admin via ClearProposed {} /// +/// In every state, the admin (or on init, the initializer) can choose to abandon the role +/// and make the config immutable. +/// ///```text /// Clear Proposed /// +-------------------------------------^ @@ -238,53 +209,44 @@ impl<'a> Admin<'a> { //-------------------------------------------------------------------------------------------------- // Mutations //-------------------------------------------------------------------------------------------------- - /// Executes admin state transitions - pub fn update(&self, storage: &'a mut dyn Storage, event: AdminUpdate) -> AdminResult<()> { - let state = self.state(storage)?; - - let new_state = match (state, event) { - (AdminState::A(a), AdminUpdate::InitializeAdmin { admin }) => a.initialize(&admin), - (AdminState::A(a), AdminUpdate::AbolishAdminRole { .. }) => a.abolish_admin_role(), - (AdminState::B(b), AdminUpdate::ProposeNewAdmin { sender, proposed }) => { - self.assert_admin(storage, &sender)?; - b.propose(&proposed) - } - (AdminState::B(b), AdminUpdate::AbolishAdminRole { sender }) => { - let addr = sender.ok_or(AdminError::NotAdmin {})?; - self.assert_admin(storage, &addr)?; - b.abolish_admin_role() - } - (AdminState::C(c), AdminUpdate::AcceptProposed { sender }) => { - self.assert_proposed(storage, &sender)?; - c.accept_proposed() - } - (AdminState::C(c), AdminUpdate::ClearProposed { sender }) => { - self.assert_admin(storage, &sender)?; - c.clear_proposed() - } - (AdminState::C(c), AdminUpdate::AbolishAdminRole { sender }) => { - let addr = sender.ok_or(AdminError::NotAdmin {})?; - self.assert_admin(storage, &addr)?; - c.abolish_admin_role() + /// Execute inside instantiate fn + pub fn initialize( + &self, + storage: &'a mut dyn Storage, + api: &'a dyn Api, + init_action: AdminInit, + ) -> AdminResult<()> { + let initial_state = self.state(storage)?; + match initial_state { + AdminState::A(a) => { + let new_state = match init_action { + AdminInit::SetInitialAdmin { admin } => { + let validated = api.addr_validate(&admin)?; + a.initialize(&validated) + } + AdminInit::AbolishAdminRole => a.abolish_admin_role(), + }; + self.0.save(storage, &new_state)?; + Ok(()) } - (_, _) => return Err(AdminError::StateTransitionError {}), - }; - self.0.save(storage, &new_state)?; - Ok(()) + // Can only be in uninitialized state to call this fn + _ => Err(AdminError::StateTransitionError {}), + } } - /// Helper for composing execute responses - pub fn execute_update( + /// Composes execute responses for admin state updates + pub fn update( &self, deps: DepsMut, info: MessageInfo, - update: AdminExecuteUpdate, + update: AdminUpdate, ) -> AdminResult> where C: Clone + Debug + PartialEq + JsonSchema, { - let validated_update = AdminUpdate::from(deps.api, &info.sender, update)?; - self.update(deps.storage, validated_update)?; + let new_state = self.transition_state(deps.storage, deps.api, &info.sender, update)?; + self.0.save(deps.storage, &new_state)?; + let res = self.query(deps.storage)?; Ok(Response::new() .add_attribute("action", "update_admin") @@ -296,6 +258,43 @@ impl<'a> Admin<'a> { .add_attribute("sender", info.sender)) } + /// Executes admin state transitions + fn transition_state( + &self, + storage: &'a mut dyn Storage, + api: &'a dyn Api, + sender: &Addr, + event: AdminUpdate, + ) -> AdminResult { + let state = self.state(storage)?; + + let new_state = match (state, event) { + (AdminState::B(b), AdminUpdate::ProposeNewAdmin { proposed }) => { + let validated = api.addr_validate(&proposed)?; + self.assert_admin(storage, sender)?; + b.propose(&validated) + } + (AdminState::B(b), AdminUpdate::AbolishAdminRole) => { + self.assert_admin(storage, sender)?; + b.abolish_admin_role() + } + (AdminState::C(c), AdminUpdate::AcceptProposed) => { + self.assert_proposed(storage, sender)?; + c.accept_proposed() + } + (AdminState::C(c), AdminUpdate::ClearProposed) => { + self.assert_admin(storage, sender)?; + c.clear_proposed() + } + (AdminState::C(c), AdminUpdate::AbolishAdminRole) => { + self.assert_admin(storage, sender)?; + c.abolish_admin_role() + } + (_, _) => return Err(AdminError::StateTransitionError {}), + }; + Ok(new_state) + } + //-------------------------------------------------------------------------------------------------- // Assertions //-------------------------------------------------------------------------------------------------- @@ -323,9 +322,7 @@ mod tests { use cosmwasm_std::testing::{mock_dependencies, mock_info}; use cosmwasm_std::Empty; - use crate::AdminUpdate::{ - AbolishAdminRole, AcceptProposed, ClearProposed, InitializeAdmin, ProposeNewAdmin, - }; + use crate::AdminUpdate::{AbolishAdminRole, AcceptProposed, ClearProposed, ProposeNewAdmin}; use super::*; @@ -336,33 +333,33 @@ mod tests { #[test] fn invalid_uninitialized_state_transitions() { let mut deps = mock_dependencies(); + let sender = Addr::unchecked("peter_parker"); + let info = mock_info(sender.as_ref(), &[]); let admin = Admin::new("xyz"); - let storage = deps.as_mut().storage; - let new_admin = Addr::unchecked("peter_parker"); let err = admin - .update( - storage, + .update::( + deps.as_mut(), + info.clone(), ProposeNewAdmin { - sender: new_admin.clone(), - proposed: new_admin.clone(), + proposed: "abc".to_string(), }, ) .unwrap_err(); assert_eq!(err, AdminError::StateTransitionError {}); let err = admin - .update( - storage, - ClearProposed { - sender: new_admin.clone(), - }, - ) + .update::(deps.as_mut(), info.clone(), ClearProposed) + .unwrap_err(); + assert_eq!(err, AdminError::StateTransitionError {}); + + let err = admin + .update::(deps.as_mut(), info.clone(), AcceptProposed) .unwrap_err(); assert_eq!(err, AdminError::StateTransitionError {}); let err = admin - .update(storage, AcceptProposed { sender: new_admin }) + .update::(deps.as_mut(), info, AbolishAdminRole) .unwrap_err(); assert_eq!(err, AdminError::StateTransitionError {}); } @@ -370,45 +367,40 @@ mod tests { #[test] fn invalid_admin_set_no_proposed_state_transitions() { let mut deps = mock_dependencies(); + let sender = Addr::unchecked("peter_parker"); + let info = mock_info(sender.as_ref(), &[]); let admin = Admin::new("xyz"); - let storage = deps.as_mut().storage; - let original_admin = Addr::unchecked("peter_parker"); + + let mut_deps = deps.as_mut(); + admin - .update( - storage, - InitializeAdmin { - admin: original_admin.clone(), + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: sender.to_string(), }, ) .unwrap(); let err = admin - .update( - storage, - InitializeAdmin { - admin: original_admin.clone(), + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: "abc".to_string(), }, ) .unwrap_err(); assert_eq!(err, AdminError::StateTransitionError {}); let err = admin - .update( - storage, - ClearProposed { - sender: original_admin.clone(), - }, - ) + .update::(deps.as_mut(), info.clone(), ClearProposed) .unwrap_err(); assert_eq!(err, AdminError::StateTransitionError {}); let err = admin - .update( - storage, - AcceptProposed { - sender: original_admin, - }, - ) + .update::(deps.as_mut(), info, AcceptProposed) .unwrap_err(); assert_eq!(err, AdminError::StateTransitionError {}); } @@ -416,44 +408,51 @@ mod tests { #[test] fn invalid_admin_set_with_proposed_state_transitions() { let mut deps = mock_dependencies(); + let sender = Addr::unchecked("peter_parker"); + let info = mock_info(sender.as_ref(), &[]); let admin = Admin::new("xyz"); - let storage = deps.as_mut().storage; - let original_admin = Addr::unchecked("peter_parker"); - let proposed_admin = Addr::unchecked("miles_morales"); + + let mut_deps = deps.as_mut(); + admin - .update( - storage, - InitializeAdmin { - admin: original_admin.clone(), + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: sender.to_string(), }, ) .unwrap(); + admin - .update( - storage, + .update::( + mut_deps, + info.clone(), ProposeNewAdmin { - sender: original_admin.clone(), - proposed: proposed_admin.clone(), + proposed: "abc".to_string(), }, ) .unwrap(); + let mut_deps = deps.as_mut(); + let err = admin - .update( - storage, - InitializeAdmin { - admin: original_admin.clone(), + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: "abc".to_string(), }, ) .unwrap_err(); assert_eq!(err, AdminError::StateTransitionError {}); let err = admin - .update( - storage, + .update::( + deps.as_mut(), + info, ProposeNewAdmin { - sender: original_admin, - proposed: proposed_admin, + proposed: "efg".to_string(), }, ) .unwrap_err(); @@ -463,67 +462,50 @@ mod tests { #[test] fn invalid_admin_role_abolished_state_transitions() { let mut deps = mock_dependencies(); + let sender = Addr::unchecked("peter_parker"); + let info = mock_info(sender.as_ref(), &[]); let admin = Admin::new("xyz"); - let storage = deps.as_mut().storage; - let original_admin = Addr::unchecked("peter_parker"); - let proposed_admin = Addr::unchecked("miles_morales"); + + let mut_deps = deps.as_mut(); + admin - .update( - storage, - AbolishAdminRole { - sender: Some(original_admin.clone()), - }, - ) + .initialize(mut_deps.storage, mut_deps.api, AdminInit::AbolishAdminRole) .unwrap(); let err = admin - .update( - storage, - InitializeAdmin { - admin: original_admin.clone(), + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: "abc".to_string(), }, ) .unwrap_err(); assert_eq!(err, AdminError::StateTransitionError {}); let err = admin - .update( - storage, + .update::( + deps.as_mut(), + info.clone(), ProposeNewAdmin { - sender: original_admin.clone(), - proposed: proposed_admin.clone(), + proposed: "efg".to_string(), }, ) .unwrap_err(); assert_eq!(err, AdminError::StateTransitionError {}); let err = admin - .update( - storage, - ClearProposed { - sender: original_admin.clone(), - }, - ) + .update::(deps.as_mut(), info.clone(), ClearProposed) .unwrap_err(); assert_eq!(err, AdminError::StateTransitionError {}); let err = admin - .update( - storage, - AcceptProposed { - sender: proposed_admin, - }, - ) + .update::(deps.as_mut(), info.clone(), AcceptProposed) .unwrap_err(); assert_eq!(err, AdminError::StateTransitionError {}); let err = admin - .update( - storage, - AbolishAdminRole { - sender: Some(original_admin), - }, - ) + .update::(deps.as_mut(), info, AbolishAdminRole) .unwrap_err(); assert_eq!(err, AdminError::StateTransitionError {}); } @@ -535,135 +517,156 @@ mod tests { #[test] fn initialize_admin_permissions() { let mut deps = mock_dependencies(); + let mut_deps = deps.as_mut(); let admin = Admin::new("xyz"); - // Anyone can initialize the first admin - let user_a = Addr::unchecked("peter_parker"); + // Anyone can initialize admin - .update(deps.as_mut().storage, InitializeAdmin { admin: user_a }) + .initialize(mut_deps.storage, mut_deps.api, AdminInit::AbolishAdminRole) .unwrap(); let mut deps = mock_dependencies(); - let user_b = Addr::unchecked("miles_morales"); + let mut_deps = deps.as_mut(); + admin - .update(deps.as_mut().storage, InitializeAdmin { admin: user_b }) + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: "xyz".to_string(), + }, + ) .unwrap(); } #[test] fn propose_new_admin_permissions() { let mut deps = mock_dependencies(); - let storage = deps.as_mut().storage; + let sender = Addr::unchecked("peter_parker"); let admin = Admin::new("xyz"); - let original_admin = Addr::unchecked("peter_parker"); + + let mut_deps = deps.as_mut(); admin - .update( - storage, - InitializeAdmin { - admin: original_admin, + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: sender.to_string(), }, ) .unwrap(); let bad_guy = Addr::unchecked("doc_oc"); + let info = mock_info(bad_guy.as_ref(), &[]); let err = admin - .update( - storage, + .update::( + mut_deps, + info, ProposeNewAdmin { - sender: bad_guy.clone(), - proposed: bad_guy, + proposed: bad_guy.to_string(), }, ) .unwrap_err(); + assert_eq!(err, AdminError::NotAdmin {}) } #[test] fn clear_proposed_permissions() { let mut deps = mock_dependencies(); - let storage = deps.as_mut().storage; + let sender = Addr::unchecked("peter_parker"); + let info = mock_info(sender.as_ref(), &[]); let admin = Admin::new("xyz"); - let original_admin = Addr::unchecked("peter_parker"); - let proposed_admin = Addr::unchecked("miles_morales"); + + let mut_deps = deps.as_mut(); admin - .update( - storage, - InitializeAdmin { - admin: original_admin.clone(), + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: sender.to_string(), }, ) .unwrap(); admin - .update( - storage, + .update::( + mut_deps, + info, ProposeNewAdmin { - sender: original_admin, - proposed: proposed_admin, + proposed: "miles_morales".to_string(), }, ) .unwrap(); let bad_guy = Addr::unchecked("doc_oc"); + let info = mock_info(bad_guy.as_ref(), &[]); let err = admin - .update(storage, ClearProposed { sender: bad_guy }) + .update::(deps.as_mut(), info, ClearProposed) .unwrap_err(); + assert_eq!(err, AdminError::NotAdmin {}) } #[test] fn accept_proposed_permissions() { let mut deps = mock_dependencies(); - let storage = deps.as_mut().storage; + let sender = Addr::unchecked("peter_parker"); + let info = mock_info(sender.as_ref(), &[]); let admin = Admin::new("xyz"); - let original_admin = Addr::unchecked("peter_parker"); - let proposed_admin = Addr::unchecked("miles_morales"); + + let mut_deps = deps.as_mut(); admin - .update( - storage, - InitializeAdmin { - admin: original_admin.clone(), + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: sender.to_string(), }, ) .unwrap(); admin - .update( - storage, + .update::( + mut_deps, + info, ProposeNewAdmin { - sender: original_admin.clone(), - proposed: proposed_admin, + proposed: "miles_morales".to_string(), }, ) .unwrap(); + let bad_guy = Addr::unchecked("doc_oc"); + let info = mock_info(bad_guy.as_ref(), &[]); let err = admin - .update( - storage, - AcceptProposed { - sender: original_admin, - }, - ) + .update::(deps.as_mut(), info, AcceptProposed) .unwrap_err(); + assert_eq!(err, AdminError::NotProposedAdmin {}) } #[test] fn abolish_admin_role_permissions() { let mut deps = mock_dependencies(); + let sender = Addr::unchecked("peter_parker"); let admin = Admin::new("xyz"); - let user = Addr::unchecked("peter_parker"); - // As no admin is set, no restrictions on abolishing from uninitialized state + let mut_deps = deps.as_mut(); admin - .update(deps.as_mut().storage, AbolishAdminRole { sender: None }) - .unwrap(); - - let mut deps = mock_dependencies(); - admin - .update( - deps.as_mut().storage, - AbolishAdminRole { sender: Some(user) }, + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: sender.to_string(), + }, ) .unwrap(); + + let bad_guy = Addr::unchecked("doc_oc"); + let info = mock_info(bad_guy.as_ref(), &[]); + let err = admin + .update::(deps.as_mut(), info, AbolishAdminRole) + .unwrap_err(); + + assert_eq!(err, AdminError::NotAdmin {}) } //-------------------------------------------------------------------------------------------------- @@ -703,32 +706,34 @@ mod tests { #[test] fn initialize_admin() { let mut deps = mock_dependencies(); - let admin = Admin::new("xyz"); - let storage = deps.as_mut().storage; let original_admin = Addr::unchecked("peter_parker"); + let admin = Admin::new("xyz"); + + let mut_deps = deps.as_mut(); admin - .update( - storage, - InitializeAdmin { - admin: original_admin.clone(), + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: original_admin.to_string(), }, ) .unwrap(); - let state = admin.state(storage).unwrap(); + let state = admin.state(mut_deps.storage).unwrap(); match state { AdminState::B(_) => {} _ => panic!("Should be in the AdminSetNoneProposed state"), } - let current = admin.current(storage).unwrap(); + let current = admin.current(mut_deps.storage).unwrap(); assert_eq!(current, Some(original_admin.clone())); - assert!(admin.is_admin(storage, &original_admin).unwrap()); + assert!(admin.is_admin(mut_deps.storage, &original_admin).unwrap()); - let proposed = admin.proposed(storage).unwrap(); + let proposed = admin.proposed(mut_deps.storage).unwrap(); assert_eq!(proposed, None); - let res = admin.query(storage).unwrap(); + let res = admin.query(mut_deps.storage).unwrap(); assert_eq!( res, AdminResponse { @@ -741,28 +746,34 @@ mod tests { #[test] fn propose_new_admin() { let mut deps = mock_dependencies(); - let admin = Admin::new("xyz"); - let storage = deps.as_mut().storage; let original_admin = Addr::unchecked("peter_parker"); let proposed_admin = Addr::unchecked("miles_morales"); + let info = mock_info(original_admin.as_ref(), &[]); + let admin = Admin::new("xyz"); + + let mut_deps = deps.as_mut(); admin - .update( - storage, - InitializeAdmin { - admin: original_admin.clone(), + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: original_admin.to_string(), }, ) .unwrap(); + admin - .update( - storage, + .update::( + mut_deps, + info, ProposeNewAdmin { - sender: original_admin.clone(), - proposed: proposed_admin.clone(), + proposed: "miles_morales".to_string(), }, ) .unwrap(); + let storage = deps.as_mut().storage; + let state = admin.state(storage).unwrap(); match state { AdminState::C(_) => {} @@ -790,37 +801,40 @@ mod tests { #[test] fn clear_proposed() { let mut deps = mock_dependencies(); - let admin = Admin::new("xyz"); - let storage = deps.as_mut().storage; let original_admin = Addr::unchecked("peter_parker"); let proposed_admin = Addr::unchecked("miles_morales"); + let info = mock_info(original_admin.as_ref(), &[]); + let admin = Admin::new("xyz"); + + let mut_deps = deps.as_mut(); admin - .update( - storage, - InitializeAdmin { - admin: original_admin.clone(), + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: original_admin.to_string(), }, ) .unwrap(); + + let mut_deps = deps.as_mut(); admin - .update( - storage, + .update::( + mut_deps, + info.clone(), ProposeNewAdmin { - sender: original_admin.clone(), - proposed: proposed_admin.clone(), + proposed: "miles_morales".to_string(), }, ) .unwrap(); + let mut_deps = deps.as_mut(); admin - .update( - storage, - ClearProposed { - sender: original_admin.clone(), - }, - ) + .update::(mut_deps, info, ClearProposed) .unwrap(); + let storage = deps.as_mut().storage; + let state = admin.state(storage).unwrap(); match state { AdminState::B(_) => {} @@ -848,36 +862,41 @@ mod tests { #[test] fn accept_proposed() { let mut deps = mock_dependencies(); - let admin = Admin::new("xyz"); - let storage = deps.as_mut().storage; let original_admin = Addr::unchecked("peter_parker"); let proposed_admin = Addr::unchecked("miles_morales"); + let info = mock_info(original_admin.as_ref(), &[]); + let admin = Admin::new("xyz"); + + let mut_deps = deps.as_mut(); admin - .update( - storage, - InitializeAdmin { - admin: original_admin.clone(), + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: original_admin.to_string(), }, ) .unwrap(); + + let mut_deps = deps.as_mut(); admin - .update( - storage, + .update::( + mut_deps, + info, ProposeNewAdmin { - sender: original_admin, - proposed: proposed_admin.clone(), + proposed: "miles_morales".to_string(), }, ) .unwrap(); + + let info = mock_info(proposed_admin.as_ref(), &[]); + let mut_deps = deps.as_mut(); admin - .update( - storage, - AcceptProposed { - sender: proposed_admin.clone(), - }, - ) + .update::(mut_deps, info, AcceptProposed) .unwrap(); + let storage = deps.as_mut().storage; + let state = admin.state(storage).unwrap(); match state { AdminState::B(_) => {} @@ -905,19 +924,28 @@ mod tests { #[test] fn abolish_admin_role() { let mut deps = mock_dependencies(); - let admin = Admin::new("xyz"); - let storage = deps.as_mut().storage; let original_admin = Addr::unchecked("peter_parker"); + let info = mock_info(original_admin.as_ref(), &[]); + let admin = Admin::new("xyz"); + let mut_deps = deps.as_mut(); admin - .update( - storage, - AbolishAdminRole { - sender: Some(original_admin.clone()), + .initialize( + mut_deps.storage, + mut_deps.api, + AdminInit::SetInitialAdmin { + admin: original_admin.to_string(), }, ) .unwrap(); + let mut_deps = deps.as_mut(); + admin + .update::(mut_deps, info, AbolishAdminRole) + .unwrap(); + + let storage = deps.as_mut().storage; + let state = admin.state(storage).unwrap(); match state { AdminState::D(_) => {} @@ -941,44 +969,4 @@ mod tests { } ); } - - #[test] - fn execute_helper() { - let mut deps = mock_dependencies(); - let sender = Addr::unchecked("peter_parker"); - let info = mock_info(sender.as_ref(), &[]); - let admin = Admin::new("xyz"); - admin - .execute_update::( - deps.as_mut(), - info, - AdminExecuteUpdate::InitializeAdmin { - admin: sender.clone().into(), - }, - ) - .unwrap(); - - let storage = deps.as_ref().storage; - let state = admin.state(storage).unwrap(); - match state { - AdminState::B(_) => {} - _ => panic!("Should be in the AdminSetNoneProposed state"), - } - - let current = admin.current(storage).unwrap(); - assert_eq!(current, Some(sender.clone())); - assert!(admin.is_admin(storage, &sender).unwrap()); - - let proposed = admin.proposed(storage).unwrap(); - assert_eq!(proposed, None); - - let res = admin.query(storage).unwrap(); - assert_eq!( - res, - AdminResponse { - admin: Some(sender.to_string()), - proposed: None - } - ); - } } diff --git a/packages/controllers/src/lib.rs b/packages/controllers/src/lib.rs index 3bef77fbb..66b6d9aa8 100644 --- a/packages/controllers/src/lib.rs +++ b/packages/controllers/src/lib.rs @@ -1,3 +1,3 @@ mod admin; -pub use admin::{Admin, AdminError, AdminExecuteUpdate, AdminResponse, AdminUpdate}; +pub use admin::{Admin, AdminError, AdminInit, AdminResponse, AdminUpdate}; diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 87929fa70..33286295b 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -20,7 +20,6 @@ backtraces = ["cosmwasm-std/backtraces"] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cosmwasm-vault-standard = { workspace = true } -cw-controllers = { workspace = true } cw-controllers-admin-fork = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } diff --git a/packages/rover/src/adapters/swap/msgs.rs b/packages/rover/src/adapters/swap/msgs.rs index 6d0bfac94..a24ba12a6 100644 --- a/packages/rover/src/adapters/swap/msgs.rs +++ b/packages/rover/src/adapters/swap/msgs.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use cw_controllers_admin_fork::AdminUpdate; #[cw_serde] pub struct InstantiateMsg { @@ -9,8 +10,8 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { - /// Update contract admin - UpdateAdmin { admin: String }, + /// Manges admin role state + UpdateAdmin(AdminUpdate), /// Configure the route for swapping an asset /// /// This is chain-specific, and can include parameters such as slippage tolerance and the routes @@ -37,8 +38,8 @@ pub enum ExecuteMsg { #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - /// Query contract config - #[returns(cw_controllers::AdminResponse)] + /// Query contract admin config + #[returns(cw_controllers_admin_fork::AdminResponse)] Admin {}, /// Get route for swapping an input denom into an output denom #[returns(RouteResponse)] diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index a8ee66952..4afa94ba5 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,6 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; -use cw_controllers_admin_fork::AdminExecuteUpdate; + +use cw_controllers_admin_fork::AdminUpdate; use crate::adapters::vault::{Vault, VaultPositionType, VaultUnchecked}; use crate::msg::instantiate::ConfigUpdates; @@ -24,7 +25,7 @@ pub enum ExecuteMsg { /// Update contract config constants UpdateConfig { new_config: ConfigUpdates }, /// Manages admin role state - UpdateAdmin(AdminExecuteUpdate), + UpdateAdmin(AdminUpdate), /// Internal actions only callable by the contract itself Callback(CallbackMsg), } diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index d5878126c..911fdf9e7 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -253,7 +253,7 @@ ], "properties": { "update_admin": { - "$ref": "#/definitions/AdminExecuteUpdate" + "$ref": "#/definitions/AdminUpdate" } }, "additionalProperties": false @@ -620,39 +620,10 @@ "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" }, - "AdminExecuteUpdate": { - "description": "Same as above, but used for execute helpers. Sender and inputs are validated.", + "AdminUpdate": { "oneOf": [ { - "type": "string", - "enum": [ - "clear_proposed", - "accept_proposed", - "abolish_admin_role" - ] - }, - { - "type": "object", - "required": [ - "initialize_admin" - ], - "properties": { - "initialize_admin": { - "type": "object", - "required": [ - "admin" - ], - "properties": { - "admin": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { + "description": "Proposes a new admin to take role. Only current admin can execute.", "type": "object", "required": [ "propose_new_admin" @@ -672,6 +643,27 @@ } }, "additionalProperties": false + }, + { + "description": "Clears the currently proposed admin. Only current admin can execute.", + "type": "string", + "enum": [ + "clear_proposed" + ] + }, + { + "description": "Promotes the proposed admin to be the current one. Only the proposed admin can execute.", + "type": "string", + "enum": [ + "accept_proposed" + ] + }, + { + "description": "Throws away the keys to the Admin role forever. Once done, no admin can ever be set later.", + "type": "string", + "enum": [ + "abolish_admin_role" + ] } ] }, diff --git a/schemas/mars-oracle-adapter/mars-oracle-adapter.json b/schemas/mars-oracle-adapter/mars-oracle-adapter.json index 24e867f7b..f0c720d28 100644 --- a/schemas/mars-oracle-adapter/mars-oracle-adapter.json +++ b/schemas/mars-oracle-adapter/mars-oracle-adapter.json @@ -98,7 +98,7 @@ ], "properties": { "update_admin": { - "$ref": "#/definitions/AdminExecuteUpdate" + "$ref": "#/definitions/AdminUpdate" } }, "additionalProperties": false @@ -109,39 +109,10 @@ "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" }, - "AdminExecuteUpdate": { - "description": "Same as above, but used for execute helpers. Sender and inputs are validated.", + "AdminUpdate": { "oneOf": [ { - "type": "string", - "enum": [ - "clear_proposed", - "accept_proposed", - "abolish_admin_role" - ] - }, - { - "type": "object", - "required": [ - "initialize_admin" - ], - "properties": { - "initialize_admin": { - "type": "object", - "required": [ - "admin" - ], - "properties": { - "admin": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { + "description": "Proposes a new admin to take role. Only current admin can execute.", "type": "object", "required": [ "propose_new_admin" @@ -161,6 +132,27 @@ } }, "additionalProperties": false + }, + { + "description": "Clears the currently proposed admin. Only current admin can execute.", + "type": "string", + "enum": [ + "clear_proposed" + ] + }, + { + "description": "Promotes the proposed admin to be the current one. Only the proposed admin can execute.", + "type": "string", + "enum": [ + "accept_proposed" + ] + }, + { + "description": "Throws away the keys to the Admin role forever. Once done, no admin can ever be set later.", + "type": "string", + "enum": [ + "abolish_admin_role" + ] } ] }, diff --git a/schemas/mars-swapper-base/mars-swapper-base.json b/schemas/mars-swapper-base/mars-swapper-base.json index de25579d3..519a48c43 100644 --- a/schemas/mars-swapper-base/mars-swapper-base.json +++ b/schemas/mars-swapper-base/mars-swapper-base.json @@ -22,23 +22,14 @@ "title": "ExecuteMsg", "oneOf": [ { - "description": "Update contract admin", + "description": "Manges admin role state", "type": "object", "required": [ "update_admin" ], "properties": { "update_admin": { - "type": "object", - "required": [ - "admin" - ], - "properties": { - "admin": { - "type": "string" - } - }, - "additionalProperties": false + "$ref": "#/definitions/AdminUpdate" } }, "additionalProperties": false @@ -139,6 +130,53 @@ "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" }, + "AdminUpdate": { + "oneOf": [ + { + "description": "Proposes a new admin to take role. Only current admin can execute.", + "type": "object", + "required": [ + "propose_new_admin" + ], + "properties": { + "propose_new_admin": { + "type": "object", + "required": [ + "proposed" + ], + "properties": { + "proposed": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Clears the currently proposed admin. Only current admin can execute.", + "type": "string", + "enum": [ + "clear_proposed" + ] + }, + { + "description": "Promotes the proposed admin to be the current one. Only the proposed admin can execute.", + "type": "string", + "enum": [ + "accept_proposed" + ] + }, + { + "description": "Throws away the keys to the Admin role forever. Once done, no admin can ever be set later.", + "type": "string", + "enum": [ + "abolish_admin_role" + ] + } + ] + }, "Coin": { "type": "object", "required": [ @@ -173,7 +211,7 @@ "title": "QueryMsg", "oneOf": [ { - "description": "Query contract config", + "description": "Query contract admin config", "type": "object", "required": [ "admin" @@ -307,7 +345,7 @@ "admin": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AdminResponse", - "description": "Returned from Admin.query_admin()", + "description": "Returned from Admin.query()", "type": "object", "properties": { "admin": { @@ -315,6 +353,12 @@ "string", "null" ] + }, + "proposed": { + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 5ef25dcf1..6e01fa0c2 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -22,7 +22,7 @@ import { ExecuteMsg, Action, VaultPositionType, - AdminExecuteUpdate, + AdminUpdate, CallbackMsg, Addr, ConfigUpdates, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 74341653d..fb6120621 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -23,7 +23,7 @@ import { ExecuteMsg, Action, VaultPositionType, - AdminExecuteUpdate, + AdminUpdate, CallbackMsg, Addr, ConfigUpdates, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 3f4e0a46d..e4963e17b 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -23,7 +23,7 @@ import { ExecuteMsg, Action, VaultPositionType, - AdminExecuteUpdate, + AdminUpdate, CallbackMsg, Addr, ConfigUpdates, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 2d8f6638a..8a152dd16 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -56,7 +56,7 @@ export type ExecuteMsg = } } | { - update_admin: AdminExecuteUpdate + update_admin: AdminUpdate } | { callback: CallbackMsg @@ -137,18 +137,15 @@ export type Action = refund_all_coin_balances: {} } export type VaultPositionType = 'u_n_l_o_c_k_e_d' | 'l_o_c_k_e_d' | 'u_n_l_o_c_k_i_n_g' -export type AdminExecuteUpdate = - | ('clear_proposed' | 'accept_proposed' | 'abolish_admin_role') - | { - initialize_admin: { - admin: string - } - } +export type AdminUpdate = | { propose_new_admin: { proposed: string } } + | 'clear_proposed' + | 'accept_proposed' + | 'abolish_admin_role' export type CallbackMsg = | { withdraw: { diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts index c60091931..cc38ba06d 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts @@ -14,7 +14,7 @@ import { InstantiateMsg, VaultPricingInfo, ExecuteMsg, - AdminExecuteUpdate, + AdminUpdate, ConfigUpdates, QueryMsg, ArrayOfVaultPricingInfo, diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts index 8f6fc1448..9ee76a6c7 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts @@ -16,7 +16,7 @@ import { InstantiateMsg, VaultPricingInfo, ExecuteMsg, - AdminExecuteUpdate, + AdminUpdate, ConfigUpdates, QueryMsg, ArrayOfVaultPricingInfo, diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts index 64f9f711c..3512468da 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts @@ -15,7 +15,7 @@ import { InstantiateMsg, VaultPricingInfo, ExecuteMsg, - AdminExecuteUpdate, + AdminUpdate, ConfigUpdates, QueryMsg, ArrayOfVaultPricingInfo, diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts index 46d8a208b..a292ad435 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts @@ -26,20 +26,17 @@ export type ExecuteMsg = } } | { - update_admin: AdminExecuteUpdate - } -export type AdminExecuteUpdate = - | ('clear_proposed' | 'accept_proposed' | 'abolish_admin_role') - | { - initialize_admin: { - admin: string - } + update_admin: AdminUpdate } +export type AdminUpdate = | { propose_new_admin: { proposed: string } } + | 'clear_proposed' + | 'accept_proposed' + | 'abolish_admin_role' export interface ConfigUpdates { oracle?: OracleBaseForString | null vault_pricing?: VaultPricingInfo[] | null diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts index aa0ac8a1f..c59f852cd 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts @@ -10,6 +10,7 @@ import { StdFee } from '@cosmjs/amino' import { InstantiateMsg, ExecuteMsg, + AdminUpdate, Uint128, Decimal, Addr, @@ -111,11 +112,6 @@ export interface MarsSwapperBaseInterface extends MarsSwapperBaseReadOnlyInterfa contractAddress: string sender: string updateAdmin: ( - { - admin, - }: { - admin: string - }, fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], @@ -183,11 +179,6 @@ export class MarsSwapperBaseClient } updateAdmin = async ( - { - admin, - }: { - admin: string - }, fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -196,9 +187,7 @@ export class MarsSwapperBaseClient this.sender, this.contractAddress, { - update_admin: { - admin, - }, + update_admin: {}, }, fee, memo, diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts index 73d0e0510..8de5ee530 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts @@ -11,6 +11,7 @@ import { toUtf8 } from '@cosmjs/encoding' import { InstantiateMsg, ExecuteMsg, + AdminUpdate, Uint128, Decimal, Addr, @@ -25,14 +26,7 @@ import { export interface MarsSwapperBaseMessage { contractAddress: string sender: string - updateAdmin: ( - { - admin, - }: { - admin: string - }, - funds?: Coin[], - ) => MsgExecuteContractEncodeObject + updateAdmin: (funds?: Coin[]) => MsgExecuteContractEncodeObject setRoute: ( { denomIn, @@ -83,14 +77,7 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { this.transferResult = this.transferResult.bind(this) } - updateAdmin = ( - { - admin, - }: { - admin: string - }, - funds?: Coin[], - ): MsgExecuteContractEncodeObject => { + updateAdmin = (funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -98,9 +85,7 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - update_admin: { - admin, - }, + update_admin: {}, }), ), funds, diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts index 33e6625d7..791fd26c1 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts @@ -11,6 +11,7 @@ import { StdFee } from '@cosmjs/amino' import { InstantiateMsg, ExecuteMsg, + AdminUpdate, Uint128, Decimal, Addr, @@ -214,9 +215,6 @@ export function useMarsSwapperBaseSetRouteMutation( } export interface MarsSwapperBaseUpdateAdminMutation { client: MarsSwapperBaseClient - msg: { - admin: string - } args?: { fee?: number | StdFee | 'auto' memo?: string diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts index 9cc73da9c..44fbc492f 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts @@ -10,9 +10,7 @@ export interface InstantiateMsg { } export type ExecuteMsg = | { - update_admin: { - admin: string - } + update_admin: AdminUpdate } | { set_route: { @@ -35,6 +33,15 @@ export type ExecuteMsg = recipient: Addr } } +export type AdminUpdate = + | { + propose_new_admin: { + proposed: string + } + } + | 'clear_proposed' + | 'accept_proposed' + | 'abolish_admin_role' export type Uint128 = string export type Decimal = string export type Addr = string @@ -70,6 +77,7 @@ export type QueryMsg = } export interface AdminResponse { admin?: string | null + proposed?: string | null } export interface EstimateExactInSwapResponse { amount: Uint128 From 55f21b371d235af59d638dbbdeb7ffb5a263115d Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 7 Dec 2022 12:59:53 +0100 Subject: [PATCH 096/218] Mock zapper update (#65) --- .../credit-manager/tests/test_zap_provide.rs | 149 +++++++++++++++++- .../credit-manager/tests/test_zap_withdraw.rs | 2 +- contracts/mock-zapper/src/contract.rs | 30 +++- contracts/mock-zapper/src/error.rs | 15 +- contracts/mock-zapper/src/execute.rs | 83 +++++----- contracts/mock-zapper/src/query.rs | 49 +++--- contracts/mock-zapper/src/state.rs | 8 +- scripts/deploy/addresses/osmo-test-4.json | 12 +- scripts/deploy/base/deployer.ts | 12 +- scripts/deploy/osmosis/config.ts | 1 + scripts/types/config.ts | 1 + 11 files changed, 266 insertions(+), 96 deletions(-) diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs index 63e8d60b5..515a95de1 100644 --- a/contracts/credit-manager/tests/test_zap_provide.rs +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -1,10 +1,11 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::{Addr, OverflowError, Uint128}; use mars_mock_zapper::contract::STARTING_LP_POOL_TOKENS; +use std::ops::Mul; use mars_mock_zapper::error::ContractError; use mars_rover::error::ContractError as RoverError; -use mars_rover::msg::execute::Action::{Deposit, ProvideLiquidity}; +use mars_rover::msg::execute::Action::{Deposit, ProvideLiquidity, WithdrawLiquidity}; use crate::helpers::{ assert_err, get_coin, lp_token_info, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, @@ -211,7 +212,7 @@ fn test_wrong_denom_provided() { Deposit(jake.to_coin(50)), ProvideLiquidity { coins_in: vec![atom.to_coin(100), jake.to_coin(50)], - lp_token_out: lp_token.denom.clone(), + lp_token_out: lp_token.denom, minimum_receive: Uint128::zero(), }, ], @@ -222,11 +223,7 @@ fn test_wrong_denom_provided() { let contract_err: ContractError = err.downcast().unwrap(); assert_eq!( contract_err, - ContractError::RequirementsNotMet { - lp_token: lp_token.denom, - coin0: atom.denom, - coin1: "uosmo".to_string() - } + ContractError::RequirementsNotMet("ujake is unexpected for lp_token_out_denom".to_string()) ); } @@ -300,3 +297,141 @@ fn test_successful_zap() { let osmo_balance = mock.query_balance(&Addr::unchecked(config.zapper), &osmo.denom); assert_eq!(osmo_balance.amount, Uint128::new(50)); } + +#[test] +fn test_can_provide_unbalanced() { + let atom = uatom_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + let estimate = mock.estimate_provide_liquidity(&lp_token.denom, &[atom.to_coin(100)]); + let slippage_adjusted = estimate.multiply_ratio(Uint128::new(95), Uint128::new(100)); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + ProvideLiquidity { + coins_in: vec![atom.to_coin(100)], + lp_token_out: lp_token.denom.clone(), + minimum_receive: slippage_adjusted, + }, + ], + &[atom.to_coin(100)], + ) + .unwrap(); + + let config = mock.query_config(); + + // assert user's new position + let positions = mock.query_positions(&account_id); + assert_eq!(positions.coins.len(), 1); + let lp_balance = get_coin(&lp_token.denom, &positions.coins); + assert_eq!(lp_balance.amount, STARTING_LP_POOL_TOKENS); + + // assert coin balance of zapper contract + let atom_balance = mock.query_balance(&Addr::unchecked(config.zapper.clone()), &atom.denom); + assert_eq!(atom_balance.amount, Uint128::new(100)); + + mock.update_credit_account( + &account_id, + &user, + vec![WithdrawLiquidity { + lp_token: lp_token.to_coin(STARTING_LP_POOL_TOKENS.multiply_ratio(1u128, 2u128).u128()), + }], + &[], + ) + .unwrap(); + + // assert user's new position (withdrew half) + let positions = mock.query_positions(&account_id); + assert_eq!(positions.coins.len(), 2); + let lp_balance = get_coin(&lp_token.denom, &positions.coins); + assert_eq!( + lp_balance.amount, + STARTING_LP_POOL_TOKENS.multiply_ratio(1u128, 2u128) + ); + let atom_balance = get_coin(&atom.denom, &positions.coins); + assert_eq!(atom_balance.amount, Uint128::new(50)); + + // assert coin balance of zapper contract + let atom_balance = mock.query_balance(&Addr::unchecked(config.zapper), &atom.denom); + assert_eq!(atom_balance.amount, Uint128::new(50)); +} + +#[test] +fn test_order_does_not_matter() { + let atom = uatom_info(); + let osmo = uosmo_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), osmo.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + let estimate = + mock.estimate_provide_liquidity(&lp_token.denom, &[atom.to_coin(100), osmo.to_coin(50)]); + let slippage_adjusted = estimate.multiply_ratio(Uint128::new(95), Uint128::new(100)); + assert_eq!(slippage_adjusted, Uint128::new(950_000)); // 1_000_000 * .95 + + // order A + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + Deposit(osmo.to_coin(50)), + ProvideLiquidity { + coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + lp_token_out: lp_token.denom.clone(), + minimum_receive: slippage_adjusted, + }, + ], + &[atom.to_coin(100), osmo.to_coin(50)], + ) + .unwrap(); + + // order B + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + Deposit(osmo.to_coin(50)), + ProvideLiquidity { + coins_in: vec![osmo.to_coin(50), atom.to_coin(100)], + lp_token_out: lp_token.denom.clone(), + minimum_receive: slippage_adjusted, + }, + ], + &[atom.to_coin(100), osmo.to_coin(50)], + ) + .unwrap(); + + // assert user's new position + let positions = mock.query_positions(&account_id); + assert_eq!(positions.coins.len(), 1); + let lp_balance = get_coin(&lp_token.denom, &positions.coins); + assert_eq!( + lp_balance.amount, + STARTING_LP_POOL_TOKENS.mul(Uint128::new(2)) + ); +} diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index 44eb33bf5..df29486e7 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -105,7 +105,7 @@ fn test_coins_out_must_be_whitelisted() { &account_id, &user, vec![WithdrawLiquidity { - lp_token: lp_token.to_coin(10), + lp_token: lp_token.to_coin(100_000), }], &[], ); diff --git a/contracts/mock-zapper/src/contract.rs b/contracts/mock-zapper/src/contract.rs index 92fc7d88d..4acae12f4 100644 --- a/contracts/mock-zapper/src/contract.rs +++ b/contracts/mock-zapper/src/contract.rs @@ -1,12 +1,13 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{coin, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; +use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; + +use mars_rover::msg::zapper::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::error::ContractResult; use crate::execute::{provide_liquidity, withdraw_liquidity}; use crate::query::{estimate_provide_liquidity, estimate_withdraw_liquidity}; -use crate::state::{COIN_BALANCES, ORACLE}; -use mars_rover::msg::zapper::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::state::{COIN_BALANCES, COIN_CONFIG, ORACLE}; pub const STARTING_LP_POOL_TOKENS: Uint128 = Uint128::new(1_000_000); @@ -21,13 +22,26 @@ pub fn instantiate( ) -> ContractResult { ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; for config in msg.lp_configs { - COIN_BALANCES.save( + // Store map from lp token to the underlying tokens + COIN_CONFIG.save( deps.storage, &config.lp_token_denom, - &( - coin(0, config.lp_pair_denoms.0), - coin(0, config.lp_pair_denoms.1), - ), + &vec![ + config.lp_pair_denoms.0.to_string(), + config.lp_pair_denoms.1.to_string(), + ], + )?; + + // Store balances of each of the underlying + COIN_BALANCES.save( + deps.storage, + (&config.lp_token_denom, &config.lp_pair_denoms.0), + &Uint128::zero(), + )?; + COIN_BALANCES.save( + deps.storage, + (&config.lp_token_denom, &config.lp_pair_denoms.1), + &Uint128::zero(), )?; } Ok(Response::default()) diff --git a/contracts/mock-zapper/src/error.rs b/contracts/mock-zapper/src/error.rs index f329fc1e7..750f2f945 100644 --- a/contracts/mock-zapper/src/error.rs +++ b/contracts/mock-zapper/src/error.rs @@ -1,7 +1,9 @@ use cosmwasm_std::{CheckedMultiplyRatioError, StdError}; -use mars_rover::error::ContractError as RoverError; +use cw_utils::PaymentError; use thiserror::Error; +use mars_rover::error::ContractError as RoverError; + pub type ContractResult = Result; #[derive(Error, Debug, PartialEq)] @@ -21,10 +23,9 @@ pub enum ContractError { #[error("Could not find coin trying to access")] CoinNotFound, - #[error("{lp_token:?} requires {coin0:?} and {coin1:?}")] - RequirementsNotMet { - lp_token: String, - coin0: String, - coin1: String, - }, + #[error("{0}")] + RequirementsNotMet(String), + + #[error("{0}")] + PaymentError(#[from] PaymentError), } diff --git a/contracts/mock-zapper/src/execute.rs b/contracts/mock-zapper/src/execute.rs index 7f40019e7..56bd0c097 100644 --- a/contracts/mock-zapper/src/execute.rs +++ b/contracts/mock-zapper/src/execute.rs @@ -1,28 +1,27 @@ -use crate::error::ContractError; -use crate::query::{estimate_provide_liquidity, estimate_withdraw_liquidity}; -use crate::state::{COIN_BALANCES, LP_TOKEN_SUPPLY}; use cosmwasm_std::{ BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, Storage, Uint128, }; +use cw_utils::one_coin; + +use crate::error::{ContractError, ContractResult}; +use crate::query::{estimate_provide_liquidity, estimate_withdraw_liquidity}; +use crate::state::{COIN_BALANCES, COIN_CONFIG, LP_TOKEN_SUPPLY}; pub fn provide_liquidity( deps: DepsMut, info: MessageInfo, lp_token_out_denom: String, minimum_receive: Uint128, -) -> Result { - let sent_coin_a = info.funds.get(0).ok_or(ContractError::CoinNotFound)?; - let sent_coin_b = info.funds.get(1).ok_or(ContractError::CoinNotFound)?; - let (mut coin0, mut coin1) = COIN_BALANCES.load(deps.storage, &lp_token_out_denom)?; - - if (sent_coin_a.denom != coin0.denom && sent_coin_a.denom != coin1.denom) - || (sent_coin_b.denom != coin0.denom && sent_coin_b.denom != coin1.denom) - { - return Err(ContractError::RequirementsNotMet { - lp_token: lp_token_out_denom, - coin0: coin0.denom, - coin1: coin1.denom, - }); +) -> ContractResult { + let underlying = COIN_CONFIG.load(deps.storage, &lp_token_out_denom)?; + // Ensure no incorrect denoms sent for expected LP token underlying + for coin in &info.funds { + if !underlying.contains(&coin.denom) { + return Err(ContractError::RequirementsNotMet(format!( + "{} is unexpected for lp_token_out_denom", + coin.denom + ))); + } } let lp_token_amount = @@ -32,15 +31,17 @@ pub fn provide_liquidity( return Err(ContractError::ReceivedBelowMinimum); } - // Update internal balances - if coin0.denom == sent_coin_a.denom { - coin0.amount += sent_coin_a.amount; - coin1.amount += sent_coin_b.amount; - } else { - coin0.amount += sent_coin_b.amount; - coin1.amount += sent_coin_a.amount; + for coin in info.funds { + COIN_BALANCES.update( + deps.storage, + (&lp_token_out_denom, &coin.denom), + |amount_opt| -> StdResult<_> { + Ok(amount_opt + .unwrap_or(Uint128::zero()) + .checked_add(coin.amount)?) + }, + )?; } - COIN_BALANCES.save(deps.storage, &lp_token_out_denom, &(coin0, coin1))?; // Send LP tokens to user (assumes mock zapper has been pre-funded with this token) mock_lp_token_mint(deps.storage, lp_token_amount, &lp_token_out_denom)?; @@ -55,29 +56,29 @@ pub fn provide_liquidity( Ok(Response::new().add_message(transfer_msg)) } -pub fn withdraw_liquidity(deps: DepsMut, info: MessageInfo) -> Result { - let lp_token_sent = info.funds.get(0).ok_or(ContractError::CoinNotFound)?; - mock_lp_token_burn(deps.storage, lp_token_sent)?; +pub fn withdraw_liquidity(deps: DepsMut, info: MessageInfo) -> ContractResult { + let lp_token_sent = one_coin(&info)?; + let underlying_coins = estimate_withdraw_liquidity(deps.storage, &lp_token_sent)?; - let underlying_coins = estimate_withdraw_liquidity(deps.storage, lp_token_sent)?; - - // Update internal balances - let (mut coin0, mut coin1) = COIN_BALANCES.load(deps.storage, &lp_token_sent.denom)?; - let coin_a = underlying_coins.get(0).ok_or(ContractError::CoinNotFound)?; - let coin_b = underlying_coins.get(1).ok_or(ContractError::CoinNotFound)?; - if coin0.denom == coin_a.denom { - coin0.amount -= coin_a.amount; - coin1.amount -= coin_b.amount; - } else { - coin0.amount -= coin_b.amount; - coin1.amount -= coin_a.amount; - }; - COIN_BALANCES.save(deps.storage, &lp_token_sent.denom, &(coin0, coin1))?; + for coin in &underlying_coins { + COIN_BALANCES.update( + deps.storage, + (&lp_token_sent.denom, &coin.denom), + |amount_opt| -> StdResult<_> { + Ok(amount_opt + .unwrap_or(Uint128::zero()) + .checked_sub(coin.amount)?) + }, + )?; + } let transfer_msg = CosmosMsg::Bank(BankMsg::Send { to_address: info.sender.to_string(), amount: underlying_coins, }); + + mock_lp_token_burn(deps.storage, &lp_token_sent)?; + Ok(Response::new().add_message(transfer_msg)) } diff --git a/contracts/mock-zapper/src/query.rs b/contracts/mock-zapper/src/query.rs index 25e85009a..8d6d62967 100644 --- a/contracts/mock-zapper/src/query.rs +++ b/contracts/mock-zapper/src/query.rs @@ -1,8 +1,8 @@ -use cosmwasm_std::{Coin, Deps, Storage, Uint128}; +use cosmwasm_std::{Coin, Deps, StdResult, Storage, Uint128}; use crate::contract::STARTING_LP_POOL_TOKENS; use crate::error::ContractError; -use crate::state::{COIN_BALANCES, LP_TOKEN_SUPPLY, ORACLE}; +use crate::state::{COIN_BALANCES, COIN_CONFIG, LP_TOKEN_SUPPLY, ORACLE}; pub fn estimate_provide_liquidity( deps: &Deps, @@ -16,9 +16,18 @@ pub fn estimate_provide_liquidity( let lp_tokens_estimate = if total_supply.is_zero() { STARTING_LP_POOL_TOKENS } else { - let (coin0, coin1) = COIN_BALANCES.load(deps.storage, lp_token_out)?; + let coins = coins_in + .iter() + .map(|c| { + let balance = COIN_BALANCES.load(deps.storage, (lp_token_out, &c.denom))?; + Ok(Coin { + denom: c.denom.clone(), + amount: balance, + }) + }) + .collect::>>()?; let oracle = ORACLE.load(deps.storage)?; - let total_underlying_value = oracle.query_total_value(&deps.querier, &[coin0, coin1])?; + let total_underlying_value = oracle.query_total_value(&deps.querier, &coins)?; let given_value = oracle.query_total_value(&deps.querier, &coins_in)?; total_supply .checked_multiply_ratio(given_value.atomics(), total_underlying_value.atomics())? @@ -31,20 +40,24 @@ pub fn estimate_withdraw_liquidity( lp_token: &Coin, ) -> Result, ContractError> { let total_supply = LP_TOKEN_SUPPLY.load(storage, &lp_token.denom)?; - let (coin0, coin1) = COIN_BALANCES.load(storage, &lp_token.denom)?; - if total_supply.is_zero() { - Ok(vec![coin0, coin1]) - } else { - Ok(vec![ - Coin { - denom: coin0.denom, - amount: coin0.amount.multiply_ratio(lp_token.amount, total_supply), - }, - Coin { - denom: coin1.denom, - amount: coin1.amount.multiply_ratio(lp_token.amount, total_supply), - }, - ]) + return Ok(vec![]); } + + let underlying = COIN_CONFIG.load(storage, &lp_token.denom)?; + let estimate = underlying + .into_iter() + .map(|denom| { + let balance = COIN_BALANCES.load(storage, (&lp_token.denom, &denom))?; + Ok(Coin { + denom, + amount: balance.multiply_ratio(lp_token.amount, total_supply), + }) + }) + .collect::>>()? + .into_iter() + .filter(|c| !c.amount.is_zero()) + .collect::>(); + + Ok(estimate) } diff --git a/contracts/mock-zapper/src/state.rs b/contracts/mock-zapper/src/state.rs index 706ef6e5a..abee1a7cb 100644 --- a/contracts/mock-zapper/src/state.rs +++ b/contracts/mock-zapper/src/state.rs @@ -1,8 +1,10 @@ -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::Uint128; use cw_storage_plus::{Item, Map}; + use mars_rover::adapters::Oracle; pub const ORACLE: Item = Item::new("oracle"); -pub const LP_TOKEN_SUPPLY: Map<&str, Uint128> = Map::new("lp_token_supply"); // LP token denom -> Total LP token supply -pub const COIN_BALANCES: Map<&str, (Coin, Coin)> = Map::new("coin_balances"); // LP token denom -> Underlying tokens +pub const LP_TOKEN_SUPPLY: Map<&str, Uint128> = Map::new("lp_token_supply"); // lp token denom -> total lp token supply +pub const COIN_CONFIG: Map<&str, Vec> = Map::new("coin_config"); // lp token denom -> Vec +pub const COIN_BALANCES: Map<(&str, &str), Uint128> = Map::new("coin_balances"); // (lp token denom, underlying) -> amount diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index ef290e765..20db60a7c 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,8 +1,8 @@ { - "accountNft": "osmo1xvne7u9svgy9vtqtqnaet4nvn8zcpp984zzrlezfzgk4798tps8srkf5wa", - "mockVault": "osmo1yqgjaehalz0pv5j22fdnaaekuprlggd7hth8m66jmdxe58ztqs4sjqtrlk", - "marsOracleAdapter": "osmo1tlad2hj9rm7az7atx2qq8pdpl2007hrhpzua42j8wgxr0kc0ct4sahuyh7", - "swapper": "osmo15kxcpvjaqlrj8ezecnghf2qs2x87veqx0fcemye0jpdr8jq7qkvsnyvuuf", - "mockZapper": "osmo1axad429tgnvzvfax08s4ytmf7ndg0f9z4jy355zyh4m6nasgtnzs5aw8u7", - "creditManager": "osmo1krz37p6xkkyu0f240enyt4ccxk7ds69kfgc5pnldsmpmmuvn3vpsnmpjaf" + "mockVault": "osmo147jn7kz3d6xtlj2xnmetkqdnahe5h4dywghdlqq5hjqgexqdlm9q3nkkle", + "marsOracleAdapter": "osmo1ws5lnak9s98e762jnklrttaff48j8f28yanmpweuulg56n6755ts6030fa", + "swapper": "osmo14v9urd7gdhw0pnl3v08wa88a5txzg7dccnyl4m4jcmjaq84dxc8q22axue", + "mockZapper": "osmo1e5j9e9nq7dyyts2e7s3hygy5cxjhpkfv4t6rdu8u237r9ajwdksqdx7lth", + "creditManager": "osmo1en0p6fyrffd74wm70xlxhhmme8nmlxsyxtqcrfgc6stcty2xurmsk0rn3m", + "accountNft": "osmo1asr4ynu0hs2hsezd8w8tjnr8szjhvaheg57zyf8up4l6alg9tj8qhg6zfx" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index aca6ba637..d69103636 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -67,7 +67,6 @@ export class Deployer { async instantiateNftContract() { const msg: NftInstantiateMsg = { - credit_manager: this.storage.addresses.creditManager!, max_value_for_burn: this.config.maxValueForBurn.toString(), minter: this.deployerAddr, name: 'credit-manger-accounts', @@ -101,7 +100,7 @@ export class Deployer { async instantiateMarsOracleAdapter() { const msg: OracleAdapterInstantiateMsg = { oracle: this.config.oracleAddr, - owner: this.deployerAddr, + admin: this.deployerAddr, vault_pricing: [ { addr: this.storage.addresses.mockVault!, @@ -116,7 +115,7 @@ export class Deployer { async instantiateSwapper() { const msg: SwapperInstantiateMsg = { - owner: this.deployerAddr, + admin: this.deployerAddr, } await this.instantiate('swapper', this.storage.codeIds.swapper!, msg) @@ -175,6 +174,7 @@ export class Deployer { async instantiateCreditManager() { const msg: RoverInstantiateMsg = { + max_unlocking_positions: this.config.maxUnlockingPositions.toString(), allowed_coins: [this.config.baseDenom, this.config.secondaryDenom, this.config.lpToken.denom], allowed_vaults: [ { @@ -188,7 +188,7 @@ export class Deployer { }, ], oracle: this.storage.addresses.marsOracleAdapter!, - owner: this.deployerAddr, + admin: this.deployerAddr, red_bank: this.config.redBankAddr, max_close_factor: this.config.maxCloseFactor.toString(), swapper: this.storage.addresses.swapper!, @@ -204,7 +204,9 @@ export class Deployer { this.deployerAddr, this.storage.addresses.accountNft!, ) - await nftClient.proposeNewOwner({ newOwner: this.storage.addresses.creditManager! }) + await nftClient.updateConfig({ + updates: { proposed_new_minter: this.storage.addresses.creditManager! }, + }) this.storage.actions.proposedNewOwner = true printBlue('Nft contract owner proposes Rover as new owner') } else { diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index a27e23d2d..9d6340261 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -51,4 +51,5 @@ export const osmosisTestnetConfig: DeploymentConfig = { ], unzapAmount: 1000000, maxValueForBurn: 1000000, + maxUnlockingPositions: 10, } diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 76831cc56..a69809d05 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -44,4 +44,5 @@ export interface DeploymentConfig { zap: { amount: number; denom: string; price: number }[] unzapAmount: number maxValueForBurn: number + maxUnlockingPositions: number } From 275efbedea00eec488594b36cf43195746f0b438 Mon Sep 17 00:00:00 2001 From: piobab Date: Wed, 7 Dec 2022 18:22:25 +0100 Subject: [PATCH 097/218] MP-1712. Improve ci check. (#48) * MP-1712. Improve ci check. * Remove go installation. * MP-1712. Add coverage. * MP-1712. Add grcov coverage. * MP-1712. Add LLVM components. * Remove build. Use tests instead. * Speedup coverage pipeline. * HTML and lcov coverage. Makefile cleanup. * Comment out cache - doesn't work for osmosis-testing. * Fix cargo make coverage. * Add manual cache. * Comment out broken caching. * Apply review comments. * Add rust-optimizer job. * Add missing make install. * Update schema. * Update schema. --- .github/workflows/artifacts.yml | 38 ++++++++++++ .github/workflows/coverage.yml | 51 ++++++++++++++++ .github/workflows/main.yml | 102 +++++++++----------------------- Makefile.toml | 22 +++++-- codecov.yml | 13 ++++ coverage_grcov.Makefile.toml | 53 +++++++++++++++++ 6 files changed, 200 insertions(+), 79 deletions(-) create mode 100644 .github/workflows/artifacts.yml create mode 100644 .github/workflows/coverage.yml create mode 100644 codecov.yml create mode 100644 coverage_grcov.Makefile.toml diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml new file mode 100644 index 000000000..5f978a912 --- /dev/null +++ b/.github/workflows/artifacts.yml @@ -0,0 +1,38 @@ +name: Artifacts + +on: + push: + branches: + - master + - main + pull_request: + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + +jobs: + artifacts: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + profile: minimal + override: true + + # FIXME: use cache when osmosis-rust dependency available in crates.io + # selecting a toolchain should happen before the plugin, as the cache uses the current rustc version as its cache key + #- name: Cache dependencies + # uses: Swatinem/rust-cache@v2 + + - name: Install cargo make + uses: davidB/rust-cargo-make@v1 + + - name: Compile contracts to wasm + run: cargo make rust-optimizer diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 000000000..d71f0bbcc --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,51 @@ +name: Coverage + +on: + push: + branches: + - master + - main + pull_request: + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + # Directory with wasm files used by `tests` + ARTIFACTS_DIR_PATH: 'target/wasm32-unknown-unknown/release' + +jobs: + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + profile: minimal + override: true + components: llvm-tools-preview + + # FIXME: use cache when osmosis-rust dependency available in crates.io + # selecting a toolchain should happen before the plugin, as the cache uses the current rustc version as its cache key + #- name: Cache dependencies + # uses: Swatinem/rust-cache@v2 + + - name: Install cargo make + uses: davidB/rust-cargo-make@v1 + + # artifacts used by tests + - name: Compile workspace + run: cargo make build + + - name: Run test coverage + run: cargo make coverage-lcov + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: target/coverage/lcov.info diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c1f961c15..5656f414d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,99 +1,53 @@ -on: pull_request - name: Main +on: + push: + branches: + - master + - main + pull_request: + env: - # Directory with wasm files used by `tests` - ARTIFACTS_DIR_PATH: 'target/wasm32-unknown-unknown/release' + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always jobs: - check: - name: Check runs-on: ubuntu-latest steps: - - name: Install Go - uses: actions/setup-go@v3 - with: - go-version: '>=1.18.0' - - name: Checkout sources uses: actions/checkout@v3 - - name: Install stable toolchain + - name: Install Rust uses: actions-rs/toolchain@v1 with: - profile: minimal toolchain: stable - override: true target: wasm32-unknown-unknown - - - name: Install cargo make - uses: davidB/rust-cargo-make@v1 - - - name: Compile workspace - run: cargo make build - - - name: Compile contracts to wasm - run: cargo make rust-optimizer - - - name: Generate schemas - run: cargo make generate-all-schemas - - test: - name: Test Suite - runs-on: ubuntu-latest - steps: - - name: Install Go - uses: actions/setup-go@v3 - with: - go-version: '>=1.18.0' - - - name: Checkout sources - uses: actions/checkout@v3 - - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: + components: rustfmt, clippy profile: minimal - toolchain: stable override: true - target: wasm32-unknown-unknown - - - name: Install cargo make - uses: davidB/rust-cargo-make@v1 - - name: Compile workspace - run: cargo make build - - - name: Run tests - run: cargo make test - - lints: - name: Lints - runs-on: ubuntu-latest - steps: - - name: Install Go - uses: actions/setup-go@v3 - with: - go-version: '>=1.18.0' - - - name: Checkout sources - uses: actions/checkout@v3 - - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - components: rustfmt, clippy - override: true + # FIXME: use cache when osmosis-rust dependency available in crates.io + # selecting a toolchain should happen before the plugin, as the cache uses the current rustc version as its cache key + #- name: Cache dependencies + # uses: Swatinem/rust-cache@v2 - name: Install cargo make uses: davidB/rust-cargo-make@v1 - - name: Check formatting + - name: Format run: cargo make fmt - - name: Clippy check + - name: Clippy run: cargo make clippy + + # fails if schemas changes not committed + - name: Check schemas up-to-date + run: | + cargo make generate-all-schemas + git diff --exit-code schemas + + - name: Audit dependencies + run: | + cargo install --locked cargo-audit + cargo make audit diff --git a/Makefile.toml b/Makefile.toml index 63df086a9..a523a2454 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -1,5 +1,6 @@ -extend= [ - { path = "schema.Makefile.toml" } +extend = [ + { path = "schema.Makefile.toml" }, + { path = "coverage_grcov.Makefile.toml" } ] [config] @@ -34,12 +35,23 @@ args = ["fmt", "--all", "--check"] command = "cargo" args = ["clippy", "--tests", "--", "-D", "warnings"] -[tasks.all-github-actions] +[tasks.audit] +command = "cargo" +args = ["audit"] + +[tasks.coverage-html] +alias = "coverage_grcov_html" + +[tasks.coverage-lcov] +alias = "coverage_grcov_lcov" + +[tasks.all-actions] dependencies = [ - "build", - "test", "fmt", "clippy", + "build", + "test", "generate-all-schemas", + "audit", "rust-optimizer", ] diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..f94deb5d7 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,13 @@ +comment: false + +coverage: + status: + project: + default: + threshold: 0.05% + patch: + default: + threshold: 0.05% + +ignore: + - "**/mock-*" diff --git a/coverage_grcov.Makefile.toml b/coverage_grcov.Makefile.toml new file mode 100644 index 000000000..de640092a --- /dev/null +++ b/coverage_grcov.Makefile.toml @@ -0,0 +1,53 @@ +# https://crates.io/crates/grcov + +[env] +COVERAGE_TARGET_DIRECTORY = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/coverage" +COVERAGE_BINARIES = "${COVERAGE_TARGET_DIRECTORY}/debug/deps" +COVERAGE_PROF_OUTPUT = "${COVERAGE_TARGET_DIRECTORY}/profraw" + +[tasks.coverage_grcov_prepare_outdir] +private = true +script=''' +#!/usr/bin/env bash +set -eux + +rm -rf ${COVERAGE_PROF_OUTPUT} +mkdir -p ${COVERAGE_PROF_OUTPUT} +''' + +[tasks.coverage_grcov_run_test] +condition = { rust_version = { min = "1.60.0" } } +private = true +run_task = "test" + +[tasks.coverage_grcov_run_test.env] +CARGO_BUILD_TARGET_DIR = "${COVERAGE_TARGET_DIRECTORY}" +CARGO_INCREMENTAL = "0" +RUSTFLAGS = "-Cinstrument-coverage" +LLVM_PROFILE_FILE = "${COVERAGE_PROF_OUTPUT}/coverage-%p-%m.profraw" + +[tasks.install_grcov] +condition = { env_not_set = ["SKIP_INSTALL_GRCOV"] } +private = true +install_crate = { crate_name = "grcov" } + +[tasks.coverage_grcov] +condition = { rust_version = { min = "1.60.0" } } +private = true +script = ''' +#!/usr/bin/env bash +set -eux + +grcov ${COVERAGE_PROF_OUTPUT} \ + -b ${COVERAGE_BINARIES} -s ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY} \ + -t ${GRCOV_OUTPUT_TYPE} --llvm --branch --ignore-not-existing --ignore "/*" --ignore "*/tests/*" -o ${GRCOV_OUTPUT_PATH} +''' +dependencies = ["install_grcov", "coverage_grcov_prepare_outdir", "coverage_grcov_run_test"] + +[tasks.coverage_grcov_html] +env = { GRCOV_OUTPUT_TYPE = "html", GRCOV_OUTPUT_PATH = "${COVERAGE_TARGET_DIRECTORY}/html" } +run_task = "coverage_grcov" + +[tasks.coverage_grcov_lcov] +env = { GRCOV_OUTPUT_TYPE = "lcov", GRCOV_OUTPUT_PATH = "${COVERAGE_TARGET_DIRECTORY}/lcov.info" } +run_task = "coverage_grcov" From d6091c52362c687cc37b552904dba0b490df9adc Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 8 Dec 2022 13:35:55 +0100 Subject: [PATCH 098/218] Support optional repay amount (#66) --- contracts/credit-manager/src/execute.rs | 11 +- .../credit-manager/src/liquidate_coin.rs | 3 +- contracts/credit-manager/src/repay.rs | 30 ++--- contracts/credit-manager/tests/test_repay.rs | 117 ++++++++++++++++-- packages/rover/src/msg/execute.rs | 16 ++- .../mars-credit-manager.json | 41 +++++- .../MarsCreditManager.types.ts | 8 +- 7 files changed, 185 insertions(+), 41 deletions(-) diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index c412b0554..0a0554bdd 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -74,9 +74,10 @@ pub fn dispatch_actions( account_id: account_id.to_string(), coin: coin.clone(), }), - Action::Repay(coin) => callbacks.push(CallbackMsg::Repay { + Action::Repay { denom, amount } => callbacks.push(CallbackMsg::Repay { account_id: account_id.to_string(), - coin: coin.clone(), + denom: denom.clone(), + amount: *amount, }), Action::EnterVault { vault, @@ -206,7 +207,11 @@ pub fn execute_callback( recipient, } => withdraw(deps, &account_id, coin, recipient), CallbackMsg::Borrow { coin, account_id } => borrow(deps, env, &account_id, coin), - CallbackMsg::Repay { account_id, coin } => repay(deps, env, &account_id, coin), + CallbackMsg::Repay { + account_id, + denom, + amount, + } => repay(deps, env, &account_id, &denom, amount), CallbackMsg::AssertBelowMaxLTV { account_id } => { assert_below_max_ltv(deps.as_ref(), env, &account_id) } diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index cb644e35e..356049cbb 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -152,7 +152,8 @@ pub fn repay_debt( increment_coin_balance(storage, liquidatee_account_id, debt)?; let msg = (CallbackMsg::Repay { account_id: liquidatee_account_id.to_string(), - coin: debt.clone(), + denom: debt.denom.clone(), + amount: Some(debt.amount), }) .into_cosmos_msg(&env.contract.address)?; Ok(msg) diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index 2c186cf07..5ff2f854d 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -7,40 +7,42 @@ use mars_rover::error::{ContractError, ContractResult}; use crate::state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; use crate::utils::{debt_shares_to_amount, decrement_coin_balance}; -pub fn repay(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> ContractResult { - if coin.amount.is_zero() { - return Err(ContractError::NoAmount); - } - +pub fn repay( + deps: DepsMut, + env: Env, + account_id: &str, + denom: &str, + amount: Option, +) -> ContractResult { // Ensure repayment does not exceed max debt on account let (debt_amount, debt_shares) = - current_debt_for_denom(deps.as_ref(), &env, account_id, &coin.denom)?; - let amount_to_repay = min(debt_amount, coin.amount); + current_debt_for_denom(deps.as_ref(), &env, account_id, denom)?; + let amount_to_repay = min(debt_amount, amount.unwrap_or(Uint128::MAX)); let shares_to_repay = debt_amount_to_shares( deps.as_ref(), &env, &Coin { - denom: coin.denom.clone(), + denom: denom.to_string(), amount: amount_to_repay, }, )?; // Decrement token's debt position if amount_to_repay == debt_amount { - DEBT_SHARES.remove(deps.storage, (account_id, &coin.denom)); + DEBT_SHARES.remove(deps.storage, (account_id, denom)); } else { DEBT_SHARES.save( deps.storage, - (account_id, &coin.denom), + (account_id, denom), &debt_shares.checked_sub(shares_to_repay)?, )?; } // Decrement total debt shares for coin - let total_debt_shares = TOTAL_DEBT_SHARES.load(deps.storage, &coin.denom)?; + let total_debt_shares = TOTAL_DEBT_SHARES.load(deps.storage, denom)?; TOTAL_DEBT_SHARES.save( deps.storage, - &coin.denom, + denom, &total_debt_shares.checked_sub(shares_to_repay)?, )?; @@ -48,14 +50,14 @@ pub fn repay(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> ContractR deps.storage, account_id, &Coin { - denom: coin.denom.clone(), + denom: denom.to_string(), amount: amount_to_repay, }, )?; let red_bank = RED_BANK.load(deps.storage)?; let red_bank_repay_msg = red_bank.repay_msg(&Coin { - denom: coin.denom, + denom: denom.to_string(), amount: amount_to_repay, })?; diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index d2410f051..53efe684a 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -24,7 +24,10 @@ fn test_only_token_owner_can_repay() { let res = mock.update_credit_account( &account_id, &another_user, - vec![Repay(coin_info.to_coin(12312))], + vec![Repay { + denom: coin_info.denom, + amount: Some(Uint128::new(12312)), + }], &[], ); @@ -38,7 +41,7 @@ fn test_only_token_owner_can_repay() { } #[test] -fn test_repaying_zero_raises() { +fn test_repaying_with_zero_debt_raises() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -47,10 +50,31 @@ fn test_repaying_zero_raises() { .unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); - let res = - mock.update_credit_account(&account_id, &user, vec![Repay(coin_info.to_coin(0))], &[]); + // When passing some amount + let res = mock.update_credit_account( + &account_id, + &user, + vec![Repay { + denom: coin_info.denom.clone(), + amount: Some(Uint128::new(0)), + }], + &[], + ); + + assert_err(res, ContractError::NoDebt); + + // When passing no amount + let res = mock.update_credit_account( + &account_id, + &user, + vec![Repay { + denom: coin_info.denom, + amount: None, + }], + &[], + ); - assert_err(res, ContractError::NoAmount) + assert_err(res, ContractError::NoDebt); } #[test] @@ -102,7 +126,10 @@ fn test_raises_when_repaying_what_is_not_owed() { vec![ Deposit(uatom_info.to_coin(300)), Borrow(uosmo_info.to_coin(42)), - Repay(uatom_info.to_coin(42)), + Repay { + denom: uatom_info.denom.clone(), + amount: Some(Uint128::new(42)), + }, ], &[uatom_info.to_coin(300)], ); @@ -142,7 +169,10 @@ fn test_raises_when_not_enough_assets_to_repay() { Deposit(uatom_info.to_coin(300)), Borrow(uosmo_info.to_coin(50)), Withdraw(uosmo_info.to_coin(10)), - Repay(uosmo_info.to_coin(50)), + Repay { + denom: uosmo_info.denom, + amount: Some(Uint128::new(50)), + }, ], &[uatom_info.to_coin(300)], ); @@ -158,7 +188,7 @@ fn test_raises_when_not_enough_assets_to_repay() { } #[test] -fn test_successful_repay() { +fn test_repay_less_than_total_debt() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -191,8 +221,16 @@ fn test_successful_repay() { let interim_red_bank_debt = mock.query_red_bank_debt(&coin_info.denom); - mock.update_credit_account(&account_id, &user, vec![Repay(coin_info.to_coin(20))], &[]) - .unwrap(); + mock.update_credit_account( + &account_id, + &user, + vec![Repay { + denom: coin_info.denom.clone(), + amount: Some(Uint128::new(20)), + }], + &[], + ) + .unwrap(); let position = mock.query_positions(&account_id); assert_eq!(position.coins.len(), 1); @@ -227,7 +265,10 @@ fn test_successful_repay() { mock.update_credit_account( &account_id, &user, - vec![Repay(coin_info.to_coin(31))], // Interest accrued paid back as well + vec![Repay { + denom: coin_info.denom.clone(), + amount: Some(Uint128::new(31)), + }], // Interest accrued paid back as well &[], ) .unwrap(); @@ -276,7 +317,10 @@ fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { vec![ Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50)), - Repay(coin_info.to_coin(75)), + Repay { + denom: coin_info.denom.clone(), + amount: Some(Uint128::new(75)), + }, ], &[coin(300, coin_info.denom.clone())], ) @@ -303,3 +347,52 @@ fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1)) ); } + +#[test] +fn test_amount_none_repays_total_debt() { + let coin_info = uosmo_info(); + + let user = Addr::unchecked("user"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(coin_info.to_coin(300)), + Borrow(coin_info.to_coin(50)), + Repay { + denom: coin_info.denom.clone(), + amount: None, + }, + ], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + let position = mock.query_positions(&account_id); + assert_eq!(position.debts.len(), 0); + + let res = mock.query_total_debt_shares(&coin_info.denom); + assert_eq!(res.shares, Uint128::zero()); + + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, Uint128::new(299)); + + let config = mock.query_config(); + let coin = mock.query_balance(&Addr::unchecked(config.red_bank), &coin_info.denom); + assert_eq!( + coin.amount, + DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1)) + ); +} diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 4afa94ba5..9f8bbd5c2 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -39,8 +39,12 @@ pub enum Action { Withdraw(Coin), /// Borrow coin of specified amount from Red Bank Borrow(Coin), - /// Repay coin of specified amount back to Red Bank - Repay(Coin), + /// Repay coin of specified amount back to Red Bank. If `amount: None` is passed, + /// the repaid amount will be the minimum between account balance for denom and total owed. + Repay { + denom: String, + amount: Option, + }, /// Deposit coins into vault strategy /// If amount sent is None, Rover attempts to deposit the account's entire balance into the vault EnterVault { @@ -121,7 +125,13 @@ pub enum CallbackMsg { Borrow { account_id: String, coin: Coin }, /// Repay coin of specified amount back to Red Bank; /// Decrement the token's coin amount and debt shares; - Repay { account_id: String, coin: Coin }, + /// If `amount: None` is passed, the repaid amount will be the minimum + /// between account balance for denom and total owed; + Repay { + account_id: String, + denom: String, + amount: Option, + }, /// Calculate the account's max loan-to-value health factor. If above 1, /// emits a `position_changed` event. If 1 or below, raises an error. AssertBelowMaxLTV { account_id: String }, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 911fdf9e7..26eca6806 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -316,14 +316,33 @@ "additionalProperties": false }, { - "description": "Repay coin of specified amount back to Red Bank", + "description": "Repay coin of specified amount back to Red Bank. If `amount: None` is passed, the repaid amount will be the minimum between account balance for denom and total owed.", "type": "object", "required": [ "repay" ], "properties": { "repay": { - "$ref": "#/definitions/Coin" + "type": "object", + "required": [ + "denom" + ], + "properties": { + "amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "denom": { + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -727,7 +746,7 @@ "additionalProperties": false }, { - "description": "Repay coin of specified amount back to Red Bank; Decrement the token's coin amount and debt shares;", + "description": "Repay coin of specified amount back to Red Bank; Decrement the token's coin amount and debt shares; If `amount: None` is passed, the repaid amount will be the minimum between account balance for denom and total owed;", "type": "object", "required": [ "repay" @@ -737,14 +756,24 @@ "type": "object", "required": [ "account_id", - "coin" + "denom" ], "properties": { "account_id": { "type": "string" }, - "coin": { - "$ref": "#/definitions/Coin" + "amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "denom": { + "type": "string" } }, "additionalProperties": false diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 8a152dd16..606fa5bce 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -72,7 +72,10 @@ export type Action = borrow: Coin } | { - repay: Coin + repay: { + amount?: Uint128 | null + denom: string + } } | { enter_vault: { @@ -163,7 +166,8 @@ export type CallbackMsg = | { repay: { account_id: string - coin: Coin + amount?: Uint128 | null + denom: string } } | { From edfc42e12981d5b46ea4dcde12e2f07417ffe63f Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 8 Dec 2022 13:36:12 +0100 Subject: [PATCH 099/218] Support optional amounts for swapping (#67) --- contracts/credit-manager/src/execute.rs | 21 ++++-- contracts/credit-manager/src/swap.rs | 23 ++++-- contracts/credit-manager/tests/test_swap.rs | 70 ++++++++++++++++--- packages/rover/src/msg/execute.rs | 8 ++- .../mars-credit-manager.json | 36 +++++++--- .../MarsCreditManager.types.ts | 6 +- 6 files changed, 133 insertions(+), 31 deletions(-) diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 0a0554bdd..b903f9b4d 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -112,13 +112,15 @@ pub fn dispatch_actions( position_type: position_type.clone(), }), Action::SwapExactIn { - coin_in, + coin_in_denom, + coin_in_amount, denom_out, slippage, } => callbacks.push(CallbackMsg::SwapExactIn { account_id: account_id.to_string(), - coin_in: coin_in.clone(), - denom_out: denom_out.to_string(), + coin_in_denom: coin_in_denom.clone(), + coin_in_amount: *coin_in_amount, + denom_out: denom_out.clone(), slippage: *slippage, }), Action::ExitVault { vault, amount } => callbacks.push(CallbackMsg::ExitVault { @@ -269,10 +271,19 @@ pub fn execute_callback( ), CallbackMsg::SwapExactIn { account_id, - coin_in, + coin_in_denom, + coin_in_amount, denom_out, slippage, - } => swap_exact_in(deps, env, &account_id, coin_in, &denom_out, slippage), + } => swap_exact_in( + deps, + env, + &account_id, + &coin_in_denom, + coin_in_amount, + &denom_out, + slippage, + ), CallbackMsg::UpdateCoinBalance { account_id, previous_balance, diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs index 9ea2ef62c..88b0fb53b 100644 --- a/contracts/credit-manager/src/swap.rs +++ b/contracts/credit-manager/src/swap.rs @@ -1,25 +1,36 @@ -use cosmwasm_std::{Coin, Decimal, DepsMut, Env, Response}; +use cosmwasm_std::{Coin, Decimal, DepsMut, Env, Response, Uint128}; use mars_rover::error::{ContractError, ContractResult}; -use crate::state::SWAPPER; +use crate::state::{COIN_BALANCES, SWAPPER}; use crate::utils::{assert_coin_is_whitelisted, decrement_coin_balance, update_balance_msg}; pub fn swap_exact_in( deps: DepsMut, env: Env, account_id: &str, - coin_in: Coin, + coin_in_denom: &str, + coin_in_amount: Option, denom_out: &str, slippage: Decimal, ) -> ContractResult { assert_coin_is_whitelisted(deps.storage, denom_out)?; - if coin_in.amount.is_zero() { + // Passing None for coin_in_amount defaults to account's balance for `coin_in_denom` + let coin_in_to_trade = Coin { + denom: coin_in_denom.to_string(), + amount: coin_in_amount.unwrap_or( + COIN_BALANCES + .may_load(deps.storage, (account_id, coin_in_denom))? + .unwrap_or(Uint128::zero()), + ), + }; + + if coin_in_to_trade.amount.is_zero() { return Err(ContractError::NoAmount); } - decrement_coin_balance(deps.storage, account_id, &coin_in)?; + decrement_coin_balance(deps.storage, account_id, &coin_in_to_trade)?; // Updates coin balances for account after the swap has taken place let update_coin_balance_msg = @@ -28,7 +39,7 @@ pub fn swap_exact_in( let swapper = SWAPPER.load(deps.storage)?; Ok(Response::new() - .add_message(swapper.swap_exact_in_msg(&coin_in, denom_out, slippage)?) + .add_message(swapper.swap_exact_in_msg(&coin_in_to_trade, denom_out, slippage)?) .add_message(update_coin_balance_msg) .add_attribute("action", "rover/credit-manager/swapper")) } diff --git a/contracts/credit-manager/tests/test_swap.rs b/contracts/credit-manager/tests/test_swap.rs index 27723908a..c3f10eb19 100644 --- a/contracts/credit-manager/tests/test_swap.rs +++ b/contracts/credit-manager/tests/test_swap.rs @@ -1,5 +1,5 @@ use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{coin, coins, Addr, Coin, Decimal, OverflowError, Uint128}; +use cosmwasm_std::{coins, Addr, Coin, Decimal, OverflowError, Uint128}; use mars_rover::error::ContractError; use mars_rover::msg::execute::Action::{Deposit, SwapExactIn}; @@ -20,7 +20,8 @@ fn test_only_token_owner_can_swap_for_account() { &account_id, &another_user, vec![SwapExactIn { - coin_in: coin(12, "mars"), + coin_in_denom: "mars".to_string(), + coin_in_amount: Some(Uint128::new(12)), denom_out: "osmo".to_string(), slippage: Decimal::from_atomics(6u128, 1).unwrap(), }], @@ -51,7 +52,8 @@ fn test_denom_out_must_be_whitelisted() { &account_id, &user, vec![SwapExactIn { - coin_in: osmo_info.to_coin(10_000), + coin_in_denom: osmo_info.denom, + coin_in_amount: Some(Uint128::new(10_000)), denom_out: "ujake".to_string(), slippage: Decimal::from_atomics(6u128, 1).unwrap(), }], @@ -77,7 +79,8 @@ fn test_no_amount_sent() { &account_id, &user, vec![SwapExactIn { - coin_in: osmo_info.to_coin(0), + coin_in_denom: osmo_info.denom, + coin_in_amount: Some(Uint128::new(0)), denom_out: atom_info.denom, slippage: Decimal::from_atomics(6u128, 1).unwrap(), }], @@ -103,7 +106,8 @@ fn test_user_has_zero_balance_for_swap_req() { &account_id, &user, vec![SwapExactIn { - coin_in: osmo_info.to_coin(10_000), + coin_in_denom: osmo_info.denom, + coin_in_amount: Some(Uint128::new(10_000)), denom_out: atom_info.denom, slippage: Decimal::from_atomics(6u128, 1).unwrap(), }], @@ -142,7 +146,8 @@ fn test_user_does_not_have_enough_balance_for_swap_req() { vec![ Deposit(osmo_info.to_coin(100)), SwapExactIn { - coin_in: osmo_info.to_coin(10_000), + coin_in_denom: osmo_info.denom.clone(), + coin_in_amount: Some(Uint128::new(10_000)), denom_out: atom_info.denom, slippage: Decimal::from_atomics(6u128, 1).unwrap(), }, @@ -161,7 +166,7 @@ fn test_user_does_not_have_enough_balance_for_swap_req() { } #[test] -fn test_swap_successful() { +fn test_swap_success_with_specified_amount() { let atom_info = uatom_info(); let osmo_info = uosmo_info(); @@ -185,7 +190,56 @@ fn test_swap_successful() { vec![ Deposit(atom_info.to_coin(10_000)), SwapExactIn { - coin_in: atom_info.to_coin(10_000), + coin_in_denom: atom_info.denom.clone(), + coin_in_amount: Some(Uint128::new(10_000)), + denom_out: osmo_info.denom.clone(), + slippage: Decimal::from_atomics(6u128, 1).unwrap(), + }, + ], + &[atom_info.to_coin(10_000)], + ) + .unwrap(); + + // assert rover balance + let atom_balance = mock.query_balance(&mock.rover, &atom_info.denom).amount; + let osmo_balance = mock.query_balance(&mock.rover, &osmo_info.denom).amount; + assert_eq!(atom_balance, Uint128::zero()); + assert_eq!(osmo_balance, MOCK_SWAP_RESULT); + + // assert account position + let position = mock.query_positions(&account_id); + assert_eq!(position.coins.len(), 1); + assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); + assert_eq!(position.coins.first().unwrap().amount, MOCK_SWAP_RESULT); +} + +#[test] +fn test_swap_success_with_amount_none() { + let atom_info = uatom_info(); + let osmo_info = uosmo_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![Coin::new(10_000u128, atom_info.denom.clone())], + }) + .build() + .unwrap(); + + let res = mock.query_swap_estimate(&atom_info.to_coin(10_000), &osmo_info.denom); + assert_eq!(res.amount, MOCK_SWAP_RESULT); + + let account_id = mock.create_credit_account(&user).unwrap(); + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom_info.to_coin(10_000)), + SwapExactIn { + coin_in_denom: atom_info.denom.clone(), + coin_in_amount: None, denom_out: osmo_info.denom.clone(), slippage: Decimal::from_atomics(6u128, 1).unwrap(), }, diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 9f8bbd5c2..418a65365 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -93,8 +93,10 @@ pub enum Action { position_type: VaultPositionType, }, /// Perform a swapper with an exact-in amount. Requires slippage allowance %. + /// If `coin_in_amount: None`, the accounts entire balance of `coin_in_denom` will be used. SwapExactIn { - coin_in: Coin, + coin_in_denom: String, + coin_in_amount: Option, denom_out: String, slippage: Decimal, }, @@ -183,9 +185,11 @@ pub enum CallbackMsg { position_type: VaultPositionType, }, /// Perform a swapper with an exact-in amount. Requires slippage allowance %. + /// If `coin_in_amount: None`, the accounts entire balance of `coin_in_denom` will be used. SwapExactIn { account_id: String, - coin_in: Coin, + coin_in_denom: String, + coin_in_amount: Option, denom_out: String, slippage: Decimal, }, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 26eca6806..23c8bc880 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -535,7 +535,7 @@ "additionalProperties": false }, { - "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %.", + "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %. If `coin_in_amount: None`, the accounts entire balance of `coin_in_denom` will be used.", "type": "object", "required": [ "swap_exact_in" @@ -544,13 +544,23 @@ "swap_exact_in": { "type": "object", "required": [ - "coin_in", + "coin_in_denom", "denom_out", "slippage" ], "properties": { - "coin_in": { - "$ref": "#/definitions/Coin" + "coin_in_amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "coin_in_denom": { + "type": "string" }, "denom_out": { "type": "string" @@ -1043,7 +1053,7 @@ "additionalProperties": false }, { - "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %.", + "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %. If `coin_in_amount: None`, the accounts entire balance of `coin_in_denom` will be used.", "type": "object", "required": [ "swap_exact_in" @@ -1053,7 +1063,7 @@ "type": "object", "required": [ "account_id", - "coin_in", + "coin_in_denom", "denom_out", "slippage" ], @@ -1061,8 +1071,18 @@ "account_id": { "type": "string" }, - "coin_in": { - "$ref": "#/definitions/Coin" + "coin_in_amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "coin_in_denom": { + "type": "string" }, "denom_out": { "type": "string" diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 606fa5bce..4a2db0911 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -119,7 +119,8 @@ export type Action = } | { swap_exact_in: { - coin_in: Coin + coin_in_amount?: Uint128 | null + coin_in_denom: string denom_out: string slippage: Decimal } @@ -231,7 +232,8 @@ export type CallbackMsg = | { swap_exact_in: { account_id: string - coin_in: Coin + coin_in_amount?: Uint128 | null + coin_in_denom: string denom_out: string slippage: Decimal } From f7eb3a2167480dd13307e011df289724a6a4eab8 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 12 Dec 2022 09:27:40 +0100 Subject: [PATCH 100/218] Withdraw liquidity message takes optional amount (#68) * Optional amount on liq withdraw * add ci/cd check for latest frontend types --- .github/workflows/main.yml | 6 - .github/workflows/scripts.yml | 53 ++++++ contracts/credit-manager/src/execute.rs | 19 ++- contracts/credit-manager/src/zap.rs | 22 ++- .../credit-manager/tests/test_zap_provide.rs | 3 +- .../credit-manager/tests/test_zap_withdraw.rs | 158 +++++++++++++++++- packages/rover/src/msg/execute.rs | 17 +- .../mars-credit-manager.json | 36 +++- scripts/deploy/addresses/osmo-test-4.json | 12 +- scripts/deploy/base/index.ts | 1 + scripts/deploy/base/rover.ts | 6 +- .../MarsCreditManager.types.ts | 6 +- 12 files changed, 292 insertions(+), 47 deletions(-) create mode 100644 .github/workflows/scripts.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5656f414d..00ecaf2fe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -41,12 +41,6 @@ jobs: - name: Clippy run: cargo make clippy - # fails if schemas changes not committed - - name: Check schemas up-to-date - run: | - cargo make generate-all-schemas - git diff --exit-code schemas - - name: Audit dependencies run: | cargo install --locked cargo-audit diff --git a/.github/workflows/scripts.yml b/.github/workflows/scripts.yml new file mode 100644 index 000000000..4f90ebe15 --- /dev/null +++ b/.github/workflows/scripts.yml @@ -0,0 +1,53 @@ +name: Scripts + +on: + push: + branches: + - master + - main + pull_request: + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + +jobs: + scripts: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./scripts + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + components: rustfmt, clippy + profile: minimal + override: true + + - name: Install cargo make + uses: davidB/rust-cargo-make@v1 + + - uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Install yarn + run: npm install --global yarn + + - name: Install dependencies + run: yarn install + + # fails if schema changes not committed + - name: Generate latest schemas + run: | + yarn generate-types + git diff --exit-code + + - name: Check build + run: yarn build diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index b903f9b4d..73ae8c314 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -152,12 +152,14 @@ pub fn dispatch_actions( coins_in: coins_in.clone(), minimum_receive: *minimum_receive, }), - Action::WithdrawLiquidity { lp_token } => { - callbacks.push(CallbackMsg::WithdrawLiquidity { - account_id: account_id.to_string(), - lp_token: lp_token.clone(), - }) - } + Action::WithdrawLiquidity { + lp_token_denom, + lp_token_amount, + } => callbacks.push(CallbackMsg::WithdrawLiquidity { + account_id: account_id.to_string(), + lp_token_denom: lp_token_denom.clone(), + lp_token_amount: *lp_token_amount, + }), Action::RefundAllCoinBalances {} => { callbacks.push(CallbackMsg::RefundAllCoinBalances { account_id: account_id.to_string(), @@ -318,8 +320,9 @@ pub fn execute_callback( ), CallbackMsg::WithdrawLiquidity { account_id, - lp_token, - } => withdraw_liquidity(deps, env, &account_id, lp_token), + lp_token_denom, + lp_token_amount, + } => withdraw_liquidity(deps, env, &account_id, &lp_token_denom, lp_token_amount), CallbackMsg::AssertOneVaultPositionOnly { account_id } => { assert_only_one_vault_position(deps, &account_id) } diff --git a/contracts/credit-manager/src/zap.rs b/contracts/credit-manager/src/zap.rs index c6b9c8d78..0b712ca81 100644 --- a/contracts/credit-manager/src/zap.rs +++ b/contracts/credit-manager/src/zap.rs @@ -1,9 +1,9 @@ use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; -use mars_rover::error::ContractResult; +use mars_rover::error::{ContractError, ContractResult}; use mars_rover::traits::Denoms; -use crate::state::ZAPPER; +use crate::state::{COIN_BALANCES, ZAPPER}; use crate::utils::{ assert_coin_is_whitelisted, assert_coins_are_whitelisted, decrement_coin_balance, update_balance_msg, update_balances_msgs, @@ -45,9 +45,23 @@ pub fn withdraw_liquidity( deps: DepsMut, env: Env, account_id: &str, - lp_token: Coin, + lp_token_denom: &str, + lp_token_amount: Option, ) -> ContractResult { - assert_coin_is_whitelisted(deps.storage, &lp_token.denom)?; + assert_coin_is_whitelisted(deps.storage, lp_token_denom)?; + + let lp_token = Coin { + denom: lp_token_denom.to_string(), + amount: lp_token_amount.unwrap_or( + COIN_BALANCES + .may_load(deps.storage, (account_id, lp_token_denom))? + .unwrap_or(Uint128::zero()), + ), + }; + + if lp_token.amount.is_zero() { + return Err(ContractError::NoAmount); + } let zapper = ZAPPER.load(deps.storage)?; let coins_out = zapper.estimate_withdraw_liquidity(&deps.querier, &lp_token)?; diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs index 515a95de1..13f3c8fd3 100644 --- a/contracts/credit-manager/tests/test_zap_provide.rs +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -348,7 +348,8 @@ fn test_can_provide_unbalanced() { &account_id, &user, vec![WithdrawLiquidity { - lp_token: lp_token.to_coin(STARTING_LP_POOL_TOKENS.multiply_ratio(1u128, 2u128).u128()), + lp_token_denom: lp_token.denom.clone(), + lp_token_amount: Some(STARTING_LP_POOL_TOKENS.multiply_ratio(1u128, 2u128)), }], &[], ) diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index df29486e7..ad03bec8e 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -23,7 +23,8 @@ fn test_only_token_owner_can_unzap_for_account() { &account_id, &another_user, vec![WithdrawLiquidity { - lp_token: Default::default(), + lp_token_denom: "xyz".to_string(), + lp_token_amount: None, }], &[], ); @@ -48,7 +49,8 @@ fn test_lp_token_in_must_be_whitelisted() { &account_id, &user, vec![WithdrawLiquidity { - lp_token: lp_token.to_coin(100), + lp_token_denom: lp_token.denom.clone(), + lp_token_amount: Some(Uint128::new(100)), }], &[], ); @@ -105,7 +107,8 @@ fn test_coins_out_must_be_whitelisted() { &account_id, &user, vec![WithdrawLiquidity { - lp_token: lp_token.to_coin(100_000), + lp_token_denom: lp_token.denom, + lp_token_amount: Some(Uint128::new(100_000)), }], &[], ); @@ -144,7 +147,8 @@ fn test_does_not_have_the_tokens_to_withdraw_liq() { minimum_receive: Uint128::zero(), }, WithdrawLiquidity { - lp_token: lp_token.to_coin(attempted_unzap_amount), + lp_token_denom: lp_token.denom, + lp_token_amount: Some(Uint128::new(attempted_unzap_amount)), }, ], &[atom.to_coin(100), osmo.to_coin(50)], @@ -161,7 +165,148 @@ fn test_does_not_have_the_tokens_to_withdraw_liq() { } #[test] -fn test_successful_unzap() { +fn test_amount_zero_passed() { + let atom = uatom_info(); + let osmo = uosmo_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), osmo.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + Deposit(osmo.to_coin(50)), + ProvideLiquidity { + coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + lp_token_out: lp_token.denom.clone(), + minimum_receive: Uint128::zero(), + }, + ], + &[atom.to_coin(100), osmo.to_coin(50)], + ) + .unwrap(); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![WithdrawLiquidity { + lp_token_denom: lp_token.denom, + lp_token_amount: Some(Uint128::zero()), + }], + &[], + ); + + assert_err(res, RoverError::NoAmount) +} + +#[test] +fn test_amount_none_passed_with_no_balance() { + let atom = uatom_info(); + let osmo = uosmo_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), osmo.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![WithdrawLiquidity { + lp_token_denom: lp_token.denom, + lp_token_amount: None, + }], + &[], + ); + + assert_err(res, RoverError::NoAmount) +} + +#[test] +fn test_successful_unzap_specified_amount() { + let atom = uatom_info(); + let osmo = uosmo_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), osmo.to_coin(300)], + }) + .build() + .unwrap(); + + // Seed zapper with denoms so test can estimate withdraws + let account_id = mock.create_credit_account(&user).unwrap(); + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + Deposit(osmo.to_coin(50)), + ProvideLiquidity { + coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + lp_token_out: lp_token.denom.clone(), + minimum_receive: Uint128::zero(), + }, + WithdrawLiquidity { + lp_token_denom: lp_token.denom.clone(), + lp_token_amount: Some(STARTING_LP_POOL_TOKENS), + }, + ], + &[atom.to_coin(100), osmo.to_coin(50)], + ) + .unwrap(); + + // Assert user's new position + let positions = mock.query_positions(&account_id); + assert_eq!(positions.coins.len(), 2); + let atom_balance = get_coin(&atom.denom, &positions.coins); + assert_eq!(atom_balance.amount, Uint128::new(100)); + let osmo_balance = get_coin(&osmo.denom, &positions.coins); + assert_eq!(osmo_balance.amount, Uint128::new(50)); + + // assert rover actually has the tokens + let lp_balance = mock.query_balance(&mock.rover, &lp_token.denom); + assert_eq!(lp_balance.amount, Uint128::zero()); + let atom_balance = mock.query_balance(&mock.rover, &atom.denom); + assert_eq!(atom_balance.amount, Uint128::new(100)); + let osmo_balance = mock.query_balance(&mock.rover, &osmo.denom); + assert_eq!(osmo_balance.amount, Uint128::new(50)); + + // assert coin balance of zapper contract + let config = mock.query_config(); + let lp_balance = mock.query_balance(&Addr::unchecked(config.zapper.clone()), &lp_token.denom); + assert_eq!(lp_balance.amount, Uint128::new(10_000_000)); // prefunded original amount + let atom_balance = mock.query_balance(&Addr::unchecked(config.zapper.clone()), &atom.denom); + assert_eq!(atom_balance.amount, Uint128::zero()); + let osmo_balance = mock.query_balance(&Addr::unchecked(config.zapper), &osmo.denom); + assert_eq!(osmo_balance.amount, Uint128::zero()); +} + +#[test] +fn test_successful_unzap_unspecified_amount() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); @@ -190,7 +335,8 @@ fn test_successful_unzap() { minimum_receive: Uint128::zero(), }, WithdrawLiquidity { - lp_token: lp_token.to_coin(STARTING_LP_POOL_TOKENS.u128()), + lp_token_denom: lp_token.denom.clone(), + lp_token_amount: None, }, ], &[atom.to_coin(100), osmo.to_coin(50)], diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 418a65365..90957adcd 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -106,8 +106,12 @@ pub enum Action { lp_token_out: String, minimum_receive: Uint128, }, - /// Send LP token and withdraw corresponding reserve assets from pool - WithdrawLiquidity { lp_token: Coin }, + /// Send LP token and withdraw corresponding reserve assets from pool. + /// If `lp_token_amount: None`, the account balance of `lp_token_denom` will be used. + WithdrawLiquidity { + lp_token_denom: String, + lp_token_amount: Option, + }, /// Refunds all coin balances back to user wallet RefundAllCoinBalances {}, } @@ -207,8 +211,13 @@ pub enum CallbackMsg { lp_token_out: String, minimum_receive: Uint128, }, - /// Send LP token and withdraw corresponding reserve assets from pool - WithdrawLiquidity { account_id: String, lp_token: Coin }, + /// Send LP token and withdraw corresponding reserve assets from pool. + /// If `lp_token_amount: None`, the account balance of `lp_token_denom` will be used. + WithdrawLiquidity { + account_id: String, + lp_token_denom: String, + lp_token_amount: Option, + }, /// Checks to ensure only one vault position is taken per credit account AssertOneVaultPositionOnly { account_id: String }, /// Refunds all coin balances back to user wallet diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 23c8bc880..9a1c034cd 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -608,7 +608,7 @@ "additionalProperties": false }, { - "description": "Send LP token and withdraw corresponding reserve assets from pool", + "description": "Send LP token and withdraw corresponding reserve assets from pool. If `lp_token_amount: None`, the account balance of `lp_token_denom` will be used.", "type": "object", "required": [ "withdraw_liquidity" @@ -617,11 +617,21 @@ "withdraw_liquidity": { "type": "object", "required": [ - "lp_token" + "lp_token_denom" ], "properties": { - "lp_token": { - "$ref": "#/definitions/Coin" + "lp_token_amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "lp_token_denom": { + "type": "string" } }, "additionalProperties": false @@ -1166,7 +1176,7 @@ "additionalProperties": false }, { - "description": "Send LP token and withdraw corresponding reserve assets from pool", + "description": "Send LP token and withdraw corresponding reserve assets from pool. If `lp_token_amount: None`, the account balance of `lp_token_denom` will be used.", "type": "object", "required": [ "withdraw_liquidity" @@ -1176,14 +1186,24 @@ "type": "object", "required": [ "account_id", - "lp_token" + "lp_token_denom" ], "properties": { "account_id": { "type": "string" }, - "lp_token": { - "$ref": "#/definitions/Coin" + "lp_token_amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "lp_token_denom": { + "type": "string" } }, "additionalProperties": false diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 20db60a7c..29aee8eea 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,8 +1,8 @@ { - "mockVault": "osmo147jn7kz3d6xtlj2xnmetkqdnahe5h4dywghdlqq5hjqgexqdlm9q3nkkle", - "marsOracleAdapter": "osmo1ws5lnak9s98e762jnklrttaff48j8f28yanmpweuulg56n6755ts6030fa", - "swapper": "osmo14v9urd7gdhw0pnl3v08wa88a5txzg7dccnyl4m4jcmjaq84dxc8q22axue", - "mockZapper": "osmo1e5j9e9nq7dyyts2e7s3hygy5cxjhpkfv4t6rdu8u237r9ajwdksqdx7lth", - "creditManager": "osmo1en0p6fyrffd74wm70xlxhhmme8nmlxsyxtqcrfgc6stcty2xurmsk0rn3m", - "accountNft": "osmo1asr4ynu0hs2hsezd8w8tjnr8szjhvaheg57zyf8up4l6alg9tj8qhg6zfx" + "mockVault": "osmo1ajyq2aq6jk4eh9pvktehslh0c4s0rsl2qkzcc0sd25uqh0pkt32qd4eh6v", + "marsOracleAdapter": "osmo1gsnz74uf84amt94zwkuh2y3p6puy9haequa2ll0cms0a2z9wcxts8wt9jd", + "swapper": "osmo1lt5a96ykq4vcnwxphnsyvcq5cpugmzdpkgjd75j3l9ypjrhd2yjsnpel22", + "mockZapper": "osmo1fqphdkfsgz7qwadggj6995ygf5u0cqkqj6qu3n9w4dmyheau6w7s77h6vy", + "creditManager": "osmo17rnphu9xst39ew4ef0hr6qx4gtn5du694qsdtlzjza280pa8f0vqwsr58k", + "accountNft": "osmo1eu0stlgxz0ynykfsksqgqydglzgxauk3cy6h52j09p8rklca3pcqr25pl4" } diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index f47730a68..330e21c6d 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -45,6 +45,7 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp await rover.vaultDeposit() if (config.vaultType === VaultType.UNLOCKED) { await rover.vaultWithdraw() + await rover.unzap() } else { await rover.vaultRequestUnlock() } diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 6fe5416eb..49a96d561 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -102,7 +102,8 @@ export class Rover { await this.updateCreditAccount([ { swap_exact_in: { - coin_in: { amount, denom: this.config.baseDenom }, + coin_in_amount: amount, + coin_in_denom: this.config.baseDenom, denom_out: this.config.secondaryDenom, slippage: this.config.slippage.toString(), }, @@ -139,7 +140,8 @@ export class Rover { await this.updateCreditAccount([ { withdraw_liquidity: { - lp_token: lpToken, + lp_token_denom: lpToken.denom, + lp_token_amount: lpToken.amount, }, }, ]) diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 4a2db0911..87665e33c 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -134,7 +134,8 @@ export type Action = } | { withdraw_liquidity: { - lp_token: Coin + lp_token_amount?: Uint128 | null + lp_token_denom: string } } | { @@ -255,7 +256,8 @@ export type CallbackMsg = | { withdraw_liquidity: { account_id: string - lp_token: Coin + lp_token_amount?: Uint128 | null + lp_token_denom: string } } | { From af63f65eae9d6b1cb932da1c020a824ead40f058 Mon Sep 17 00:00:00 2001 From: piobab Date: Mon, 12 Dec 2022 16:12:31 +0100 Subject: [PATCH 101/218] Deps and cicd (#69) * Bump osmosis deps. * Codecov fixes. * Use caches. --- .github/workflows/artifacts.yml | 5 ++--- .github/workflows/coverage.yml | 5 ++--- .github/workflows/main.yml | 5 ++--- Cargo.lock | 15 +++++++++------ Cargo.toml | 3 +-- Makefile.toml | 4 ++-- codecov.yml | 3 --- contracts/swapper/osmosis/Cargo.toml | 3 +-- coverage_grcov.Makefile.toml | 22 +++++++++++----------- 9 files changed, 30 insertions(+), 35 deletions(-) diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml index 5f978a912..a732d4d45 100644 --- a/.github/workflows/artifacts.yml +++ b/.github/workflows/artifacts.yml @@ -26,10 +26,9 @@ jobs: profile: minimal override: true - # FIXME: use cache when osmosis-rust dependency available in crates.io # selecting a toolchain should happen before the plugin, as the cache uses the current rustc version as its cache key - #- name: Cache dependencies - # uses: Swatinem/rust-cache@v2 + - name: Cache dependencies + uses: Swatinem/rust-cache@v2 - name: Install cargo make uses: davidB/rust-cargo-make@v1 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d71f0bbcc..9619ea26c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -29,10 +29,9 @@ jobs: override: true components: llvm-tools-preview - # FIXME: use cache when osmosis-rust dependency available in crates.io # selecting a toolchain should happen before the plugin, as the cache uses the current rustc version as its cache key - #- name: Cache dependencies - # uses: Swatinem/rust-cache@v2 + - name: Cache dependencies + uses: Swatinem/rust-cache@v2 - name: Install cargo make uses: davidB/rust-cargo-make@v1 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 00ecaf2fe..f694b6772 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,10 +27,9 @@ jobs: profile: minimal override: true - # FIXME: use cache when osmosis-rust dependency available in crates.io # selecting a toolchain should happen before the plugin, as the cache uses the current rustc version as its cache key - #- name: Cache dependencies - # uses: Swatinem/rust-cache@v2 + - name: Cache dependencies + uses: Swatinem/rust-cache@v2 - name: Install cargo make uses: davidB/rust-cargo-make@v1 diff --git a/Cargo.lock b/Cargo.lock index 979dd51b3..ba525c272 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1204,8 +1204,9 @@ checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "osmosis-std" -version = "0.12.0" -source = "git+https://github.com/osmosis-labs/osmosis-rust?rev=fb79d48810cfa80f1f74dffc1e04f0775fe1e152#fb79d48810cfa80f1f74dffc1e04f0775fe1e152" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d6fe6ac7fcba45ed61d738091d33c838c4cabbcf4892dc7aa56d19d39cc976" dependencies = [ "chrono", "cosmwasm-std", @@ -1219,8 +1220,9 @@ dependencies = [ [[package]] name = "osmosis-std-derive" -version = "0.12.0" -source = "git+https://github.com/osmosis-labs/osmosis-rust?rev=fb79d48810cfa80f1f74dffc1e04f0775fe1e152#fb79d48810cfa80f1f74dffc1e04f0775fe1e152" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a455e262a6fdfd3914f3a4e11e6bc0ce491901cb9d507d7856d7ef6e129e90c6" dependencies = [ "itertools", "proc-macro2", @@ -1230,8 +1232,9 @@ dependencies = [ [[package]] name = "osmosis-testing" -version = "0.12.0" -source = "git+https://github.com/osmosis-labs/osmosis-rust?rev=fb79d48810cfa80f1f74dffc1e04f0775fe1e152#fb79d48810cfa80f1f74dffc1e04f0775fe1e152" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42636a83a181b7c4f94658eb3bb219caa51ff55bcc69750cd7ae4bf50b12cb2f" dependencies = [ "base64", "bindgen", diff --git a/Cargo.toml b/Cargo.toml index 26123ddc7..fc0017d74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,8 +42,7 @@ cw-item-set = { version = "0.6", default-features = false, features = ["iter cw-multi-test = "0.16" cw-utils = "0.16" cw-storage-plus = "0.16" -# FIXME: update with new version when osmosis-v13 branch merged to main branch -osmosis-std = { rev = "fb79d48810cfa80f1f74dffc1e04f0775fe1e152", git = "https://github.com/osmosis-labs/osmosis-rust" } +osmosis-std = "0.13.2" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = "1.0" diff --git a/Makefile.toml b/Makefile.toml index a523a2454..0c788db49 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -40,10 +40,10 @@ command = "cargo" args = ["audit"] [tasks.coverage-html] -alias = "coverage_grcov_html" +alias = "coverage-grcov-html" [tasks.coverage-lcov] -alias = "coverage_grcov_lcov" +alias = "coverage-grcov-lcov" [tasks.all-actions] dependencies = [ diff --git a/codecov.yml b/codecov.yml index f94deb5d7..8555bf2c4 100644 --- a/codecov.yml +++ b/codecov.yml @@ -8,6 +8,3 @@ coverage: patch: default: threshold: 0.05% - -ignore: - - "**/mock-*" diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index 3c5e28f0d..02780999d 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -32,5 +32,4 @@ thiserror = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -# FIXME: update with new version when osmosis-v13 branch merged to main branch -osmosis-testing = { rev = "fb79d48810cfa80f1f74dffc1e04f0775fe1e152", git = "https://github.com/osmosis-labs/osmosis-rust" } +osmosis-testing = "0.13.2" diff --git a/coverage_grcov.Makefile.toml b/coverage_grcov.Makefile.toml index de640092a..2e7213f81 100644 --- a/coverage_grcov.Makefile.toml +++ b/coverage_grcov.Makefile.toml @@ -5,7 +5,7 @@ COVERAGE_TARGET_DIRECTORY = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/co COVERAGE_BINARIES = "${COVERAGE_TARGET_DIRECTORY}/debug/deps" COVERAGE_PROF_OUTPUT = "${COVERAGE_TARGET_DIRECTORY}/profraw" -[tasks.coverage_grcov_prepare_outdir] +[tasks.coverage-grcov-prepare-outdir] private = true script=''' #!/usr/bin/env bash @@ -15,23 +15,23 @@ rm -rf ${COVERAGE_PROF_OUTPUT} mkdir -p ${COVERAGE_PROF_OUTPUT} ''' -[tasks.coverage_grcov_run_test] +[tasks.coverage-grcov-run-test] condition = { rust_version = { min = "1.60.0" } } private = true run_task = "test" -[tasks.coverage_grcov_run_test.env] +[tasks.coverage-grcov-run-test.env] CARGO_BUILD_TARGET_DIR = "${COVERAGE_TARGET_DIRECTORY}" CARGO_INCREMENTAL = "0" RUSTFLAGS = "-Cinstrument-coverage" LLVM_PROFILE_FILE = "${COVERAGE_PROF_OUTPUT}/coverage-%p-%m.profraw" -[tasks.install_grcov] +[tasks.install-grcov] condition = { env_not_set = ["SKIP_INSTALL_GRCOV"] } private = true install_crate = { crate_name = "grcov" } -[tasks.coverage_grcov] +[tasks.coverage-grcov] condition = { rust_version = { min = "1.60.0" } } private = true script = ''' @@ -40,14 +40,14 @@ set -eux grcov ${COVERAGE_PROF_OUTPUT} \ -b ${COVERAGE_BINARIES} -s ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY} \ - -t ${GRCOV_OUTPUT_TYPE} --llvm --branch --ignore-not-existing --ignore "/*" --ignore "*/tests/*" -o ${GRCOV_OUTPUT_PATH} + -t ${GRCOV_OUTPUT_TYPE} --llvm --branch --ignore-not-existing --ignore "/*" --ignore "*/tests/*" --ignore "*/mock*" --ignore "target/*" -o ${GRCOV_OUTPUT_PATH} ''' -dependencies = ["install_grcov", "coverage_grcov_prepare_outdir", "coverage_grcov_run_test"] +dependencies = ["install-grcov", "coverage-grcov-prepare-outdir", "coverage-grcov-run-test"] -[tasks.coverage_grcov_html] +[tasks.coverage-grcov-html] env = { GRCOV_OUTPUT_TYPE = "html", GRCOV_OUTPUT_PATH = "${COVERAGE_TARGET_DIRECTORY}/html" } -run_task = "coverage_grcov" +run_task = "coverage-grcov" -[tasks.coverage_grcov_lcov] +[tasks.coverage-grcov-lcov] env = { GRCOV_OUTPUT_TYPE = "lcov", GRCOV_OUTPUT_PATH = "${COVERAGE_TARGET_DIRECTORY}/lcov.info" } -run_task = "coverage_grcov" +run_task = "coverage-grcov" From 7506d1ccbe082cd66154e4cf97e400ebdd284baa Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 13 Dec 2022 10:53:10 +0100 Subject: [PATCH 102/218] Borrow & round up division (#70) ceil ratio --- Cargo.lock | 12 ++--- contracts/credit-manager/src/utils.rs | 7 ++- contracts/credit-manager/tests/test_health.rs | 4 +- .../tests/test_liquidate_coin.rs | 2 +- packages/rover/src/lib.rs | 1 + packages/rover/src/math/ceil_ratio.rs | 43 +++++++++++++++ packages/rover/src/math/mod.rs | 3 ++ packages/rover/tests/test_ceil_ratio.rs | 54 +++++++++++++++++++ 8 files changed, 114 insertions(+), 12 deletions(-) create mode 100644 packages/rover/src/math/ceil_ratio.rs create mode 100644 packages/rover/src/math/mod.rs create mode 100644 packages/rover/tests/test_ceil_ratio.rs diff --git a/Cargo.lock b/Cargo.lock index ba525c272..83506b9cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,9 +265,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28376836c7677e1ea6d6656a754582e88b91e544ce22fae42956d5fe5549a958" +checksum = "227315dc11f0bb22a273d0c43d3ba8ef52041c42cf959f09045388a89c57e661" dependencies = [ "digest 0.10.5", "ed25519-zebra", @@ -278,9 +278,9 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb69f4f7a8a4bce68c8fbd3646238fede1e77056e4ea31c5b6bfc37b709eec3" +checksum = "6fca30d51f7e5fbfa6440d8b10d7df0231bdf77e97fd3fe5d0cb79cc4822e50c" dependencies = [ "syn", ] @@ -311,9 +311,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bf9157d060abbc55152aeadcace799d03dc630575daa66604079a1206cb060" +checksum = "b13d5a84d15cf7be17dc249a21588cdb0f7ef308907c50ce2723316a7d79c3dc" dependencies = [ "base64", "cosmwasm-crypto", diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index ce3afea60..5de186a95 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -10,6 +10,7 @@ use cw721::OwnerOfResponse; use cw721_base::QueryMsg; use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::math::CeilRatio; use mars_rover::msg::execute::CallbackMsg; use mars_rover::msg::query::CoinValue; use mars_rover::msg::ExecuteMsg; @@ -136,10 +137,8 @@ pub fn debt_shares_to_amount( let red_bank = RED_BANK.load(deps.storage)?; let total_debt_amount = red_bank.query_debt(&deps.querier, rover_addr, denom)?; - // amount of debt for token's position - // NOTE: Given the nature of integers, the debt is rounded down. This means that the - // remaining share owners will take a small hit of the remainder. - let amount = total_debt_amount.checked_multiply_ratio(shares, total_debt_shares)?; + // Amount of debt for token's position. Rounded up to favor participants in the debt pool. + let amount = total_debt_amount.multiply_ratio_ceil(shares, total_debt_shares)?; Ok(Coin { denom: denom.to_string(), diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index e4e51a4e6..b6cbfa537 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -5,6 +5,7 @@ use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128}; use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use mars_mock_oracle::msg::CoinPrice; use mars_rover::error::ContractError; +use mars_rover::math::CeilRatio; use mars_rover::msg::execute::Action::{Borrow, Deposit}; use mars_rover::msg::instantiate::ConfigUpdates; use mars_rover::msg::query::DebtAmount; @@ -575,7 +576,8 @@ fn test_debt_value() { let user_a_owed_atom = red_bank_atom_debt .amount - .multiply_ratio(user_a_debt_shares_atom, red_bank_atom_res.shares); + .multiply_ratio_ceil(user_a_debt_shares_atom, red_bank_atom_res.shares) + .unwrap(); let user_a_owed_atom_value = uatom_info.price * user_a_owed_atom.to_dec().unwrap(); let osmo_borrowed_amount_dec = (user_a_borrowed_amount_osmo + Uint128::one()) diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 4f92aa7fe..bbca640df 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -376,7 +376,7 @@ fn test_liquidator_left_in_unhealthy_state() { res, AboveMaxLTV { account_id: liquidator_account_id, - max_ltv_health_factor: "0.805".to_string(), + max_ltv_health_factor: "0.731818181818181818".to_string(), }, ) } diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index 38ad76f8e..9c9897943 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -2,5 +2,6 @@ pub mod adapters; pub mod coins; pub mod error; pub mod extensions; +pub mod math; pub mod msg; pub mod traits; diff --git a/packages/rover/src/math/ceil_ratio.rs b/packages/rover/src/math/ceil_ratio.rs new file mode 100644 index 000000000..88b353d0d --- /dev/null +++ b/packages/rover/src/math/ceil_ratio.rs @@ -0,0 +1,43 @@ +use cosmwasm_std::{CheckedMultiplyRatioError, Uint128, Uint256}; + +pub trait CeilRatio { + fn multiply_ratio_ceil( + &self, + numerator: Uint128, + denominator: Uint128, + ) -> Result; +} + +impl CeilRatio for Uint128 { + /// Using `checked_multiply_ratio()` results in a rounding down due to the nature of integer math. + /// This function performs the same math, but rounds up. The is particularly useful in ensuring + /// safety in certain situations (e.g. calculating what an account owes) + fn multiply_ratio_ceil( + &self, + numerator: Uint128, + denominator: Uint128, + ) -> Result { + // Perform the normal multiply ratio. + // Converts to Uint256 to reduce likeliness of overflow errors + let new_numerator = self.full_mul(numerator); + let denom_256 = Uint256::from(denominator); + let mut result = new_numerator + .checked_div(denom_256) + .map_err(|_| CheckedMultiplyRatioError::DivideByZero)?; + + // Check if there's a remainder with that same division. + // If so, round up (by adding one). + if !new_numerator + .checked_rem(denom_256) + .map_err(|_| CheckedMultiplyRatioError::DivideByZero)? + .is_zero() + { + result += Uint256::one(); + } + + match result.try_into() { + Ok(ratio) => Ok(ratio), + Err(_) => Err(CheckedMultiplyRatioError::Overflow), + } + } +} diff --git a/packages/rover/src/math/mod.rs b/packages/rover/src/math/mod.rs new file mode 100644 index 000000000..958f02d57 --- /dev/null +++ b/packages/rover/src/math/mod.rs @@ -0,0 +1,3 @@ +mod ceil_ratio; + +pub use ceil_ratio::*; diff --git a/packages/rover/tests/test_ceil_ratio.rs b/packages/rover/tests/test_ceil_ratio.rs new file mode 100644 index 000000000..64abd8b37 --- /dev/null +++ b/packages/rover/tests/test_ceil_ratio.rs @@ -0,0 +1,54 @@ +use cosmwasm_std::{CheckedMultiplyRatioError, Uint128}; +use mars_rover::math::CeilRatio; + +const MAX_UINT128_SIZE: u128 = 340_282_366_920_938_463_463_374_607_431_768_211_455; + +#[test] +fn test_divide_by_zero() { + let err = Uint128::new(123) + .multiply_ratio_ceil(Uint128::new(12), Uint128::zero()) + .unwrap_err(); + assert_eq!(err, CheckedMultiplyRatioError::DivideByZero) +} + +#[test] +fn test_result_exceeds_128_bit_capacity() { + let err = Uint128::new(MAX_UINT128_SIZE) + .multiply_ratio_ceil(Uint128::new(2), Uint128::new(1)) + .unwrap_err(); + assert_eq!(err, CheckedMultiplyRatioError::Overflow) +} + +#[test] +fn test_works_with_zero() { + let res = Uint128::zero() + .multiply_ratio_ceil(Uint128::new(1), Uint128::new(10)) + .unwrap(); + assert_eq!(res, Uint128::zero()) +} + +#[test] +fn test_works_with_one() { + let res = Uint128::one() + .multiply_ratio_ceil(Uint128::new(1), Uint128::new(10)) + .unwrap(); + assert_eq!(res, Uint128::one()) +} + +#[test] +fn test_not_increment_if_divides_cleanly() { + // 56088 / 123 = 456 + let res = Uint128::new(56088) + .multiply_ratio_ceil(Uint128::new(1), Uint128::new(123)) + .unwrap(); + assert_eq!(res, Uint128::new(456)) +} + +#[test] +fn test_rounds_up() { + // 56000 / 123 = 455.28455284 + let res = Uint128::new(56000) + .multiply_ratio_ceil(Uint128::new(1), Uint128::new(123)) + .unwrap(); + assert_eq!(res, Uint128::new(456)) +} From 2620d9444bfec45583cf20dec07b151326410cb9 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 13 Dec 2022 10:53:26 +0100 Subject: [PATCH 103/218] Changing liquidation bonus rounding (#71) Changing liquidation bonus rounding to floor --- .../credit-manager/src/liquidate_coin.rs | 37 ++++++++-- .../tests/test_liquidate_coin.rs | 74 ++++++++++++++++++- .../tests/test_liquidate_vault.rs | 14 ++-- packages/rover/src/error.rs | 5 +- 4 files changed, 112 insertions(+), 18 deletions(-) diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index 356049cbb..f2fffc283 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -1,6 +1,9 @@ use std::ops::{Add, Div}; -use cosmwasm_std::{Coin, CosmosMsg, Decimal, DepsMut, Env, Response, StdError, Storage, Uint128}; +use cosmwasm_std::{ + Coin, CosmosMsg, Decimal, DepsMut, Env, QuerierWrapper, Response, StdError, Storage, Uint128, +}; +use mars_rover::adapters::Oracle; use mars_rover::error::{ContractError, ContractResult}; use mars_rover::msg::execute::CallbackMsg; @@ -120,14 +123,10 @@ pub fn calculate_liquidation( .add(Decimal::one()) .checked_mul(debt_res.price.checked_mul(final_debt_to_repay.to_dec()?)?)? .div(request_res.price) - // Given the nature of integers, these operations will round down. This means the liquidation balance will get - // closer and closer to 0, but never actually get there and stay as a single denom unit. - // The remediation for this is to round up at the very end of the calculation. - .ceil() .uint128(); // (Debt Coin, Request Coin) - Ok(( + let result = ( Coin { denom: debt_coin.denom.clone(), amount: final_debt_to_repay, @@ -136,7 +135,11 @@ pub fn calculate_liquidation( denom: request_coin.to_string(), amount: request_amount, }, - )) + ); + + assert_liquidation_profitable(&deps.querier, &oracle, result.clone())?; + + Ok(result) } pub fn repay_debt( @@ -158,3 +161,23 @@ pub fn repay_debt( .into_cosmos_msg(&env.contract.address)?; Ok(msg) } + +/// In scenarios with small amounts or large gap between coin prices, there is a possibility +/// that the liquidation will result in loss for the liquidator. This assertion prevents this. +fn assert_liquidation_profitable( + querier: &QuerierWrapper, + oracle: &Oracle, + (debt_coin, request_coin): (Coin, Coin), +) -> ContractResult<()> { + let debt_value = oracle.query_total_value(querier, &[debt_coin.clone()])?; + let request_value = oracle.query_total_value(querier, &[request_coin.clone()])?; + + if debt_value >= request_value { + return Err(ContractError::LiquidationNotProfitable { + debt_coin, + request_coin, + }); + } + + Ok(()) +} diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index bbca640df..022bc5332 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{coins, Addr, Coin, Decimal, OverflowError, OverflowOperation, use mars_mock_oracle::msg::CoinPrice; use mars_rover::error::ContractError; -use mars_rover::error::ContractError::{AboveMaxLTV, NotLiquidatable}; +use mars_rover::error::ContractError::{AboveMaxLTV, LiquidationNotProfitable, NotLiquidatable}; use mars_rover::msg::execute::Action::{Borrow, Deposit, EnterVault, LiquidateCoin}; use mars_rover::traits::IntoDecimal; @@ -381,6 +381,74 @@ fn test_liquidator_left_in_unhealthy_state() { ) } +#[test] +fn test_liquidation_not_profitable_after_calculations() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + let ujake_info = ujake_info(); + let liquidator = Addr::unchecked("liquidator"); + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: coins(300, uosmo_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: coins(300, uatom_info.denom.clone()), + }) + .build() + .unwrap(); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(100)), + Borrow(ujake_info.to_coin(25)), + ], + &[Coin::new(300, uosmo_info.denom.clone())], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: ujake_info.denom, + price: Decimal::from_atomics(100u128, 0).unwrap(), + }); + + mock.price_change(CoinPrice { + denom: uosmo_info.denom.clone(), + price: Decimal::from_atomics(2u128, 0).unwrap(), + }); + + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(uatom_info.to_coin(10)), + LiquidateCoin { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(5), + request_coin_denom: uosmo_info.denom.clone(), + }, + ], + &[uatom_info.to_coin(10)], + ); + + assert_err( + res, + LiquidationNotProfitable { + debt_coin: uatom_info.to_coin(5), + request_coin: uosmo_info.to_coin(2), + }, + ) +} + #[test] fn test_debt_amount_adjusted_to_close_factor_max() { let uosmo_info = uosmo_info(); @@ -517,7 +585,7 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.coins.len(), 3); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(180)); + assert_eq!(osmo_balance.amount, Uint128::new(181)); let atom_balance = get_coin("uatom", &position.coins); assert_eq!(atom_balance.amount, Uint128::new(100)); let jake_balance = get_coin("ujake", &position.coins); @@ -534,7 +602,7 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { let jake_balance = get_coin("ujake", &position.coins); assert_eq!(jake_balance.amount, Uint128::new(39)); let osmo_balance = get_coin("uosmo", &position.coins); - assert_eq!(osmo_balance.amount, Uint128::new(120)); + assert_eq!(osmo_balance.amount, Uint128::new(119)); } #[test] diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 2945c32f3..23b8e6774 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -381,7 +381,7 @@ fn test_liquidate_unlocked_vault() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_balance = position.vaults.first().unwrap().amount.unlocked(); - assert_eq!(vault_balance, Uint128::new(883_532)); // 1M - 116_468 + assert_eq!(vault_balance, Uint128::new(883_533)); // 1M - 116_467 assert_eq!(position.coins.len(), 1); let jake_balance = get_coin("ujake", &position.coins); @@ -468,8 +468,8 @@ fn test_liquidate_locked_vault() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_amount = position.vaults.first().unwrap().amount.clone(); - // 1M - 835_528 vault tokens liquidated = 164,472 - assert_eq!(vault_amount.locked(), Uint128::new(164_472)); + // 1M - 835,527 vault tokens liquidated = 164,473 + assert_eq!(vault_amount.locked(), Uint128::new(164_473)); assert_eq!(vault_amount.unlocking().positions().len(), 0); assert_eq!(vault_amount.unlocked(), Uint128::zero()); @@ -580,7 +580,7 @@ fn test_liquidate_unlocking_liquidation_order() { // Total liquidated: 24 LP tokens // First bucket drained: 2 of 2 // Second bucket drained: 10 of 10 - // Third bucket partially liquidated: 12 of 20 + // Third bucket partially liquidated: 11 of 20 // Fourth bucket retained: 0 of 168 assert_eq!(vault_amount.unlocking().positions().len(), 2); assert_eq!( @@ -591,7 +591,7 @@ fn test_liquidate_unlocking_liquidation_order() { .unwrap() .coin .amount, - Uint128::new(8) + Uint128::new(9) ); assert_eq!( vault_amount @@ -617,7 +617,7 @@ fn test_liquidate_unlocking_liquidation_order() { assert_eq!(position.coins.len(), 1); assert_eq!(position.debts.len(), 0); let lp_balance = get_coin(&lp_token.denom, &position.coins); - assert_eq!(lp_balance.amount, Uint128::new(24)); + assert_eq!(lp_balance.amount, Uint128::new(23)); } // NOTE: liquidation calculation+adjustments are quite complex, full cases in test_liquidate_coin.rs @@ -693,7 +693,7 @@ fn test_liquidation_calculation_adjustment() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_balance = position.vaults.first().unwrap().amount.unlocked(); - assert_eq!(vault_balance, Uint128::new(10_026)); // Vault position liquidated by 99% + assert_eq!(vault_balance, Uint128::new(10_027)); // Vault position liquidated by 99% assert_eq!(position.coins.len(), 1); let jake_balance = get_coin("ujake", &position.coins); diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 44ae054f9..638c73f75 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - CheckedFromRatioError, CheckedMultiplyRatioError, DecimalRangeExceeded, OverflowError, + CheckedFromRatioError, CheckedMultiplyRatioError, Coin, DecimalRangeExceeded, OverflowError, StdError, Uint128, }; use cw_controllers_admin_fork::AdminError; @@ -61,6 +61,9 @@ pub enum ContractError { #[error("{reason:?}")] InvalidConfig { reason: String }, + #[error("Paying down {debt_coin:?} for {request_coin:?} does not result in a profit for the liquidator")] + LiquidationNotProfitable { debt_coin: Coin, request_coin: Coin }, + #[error("Issued incorrect action for vault type")] MismatchedVaultType, From 20b51cbbefe3d3fb54aae69ed4a224cfadc6310d Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 13 Dec 2022 21:41:06 +0100 Subject: [PATCH 104/218] Add fully-qualified versions of deps (#72) --- .github/workflows/scripts.yml | 4 + Cargo.lock | 179 +++++++++++++++------------ Cargo.toml | 29 +++-- contracts/credit-manager/Cargo.toml | 2 +- contracts/swapper/osmosis/Cargo.toml | 4 +- 5 files changed, 125 insertions(+), 93 deletions(-) diff --git a/.github/workflows/scripts.yml b/.github/workflows/scripts.yml index 4f90ebe15..f01588bb3 100644 --- a/.github/workflows/scripts.yml +++ b/.github/workflows/scripts.yml @@ -30,6 +30,10 @@ jobs: profile: minimal override: true + # selecting a toolchain should happen before the plugin, as the cache uses the current rustc version as its cache key + - name: Cache dependencies + uses: Swatinem/rust-cache@v2 + - name: Install cargo make uses: davidB/rust-cargo-make@v1 diff --git a/Cargo.lock b/Cargo.lock index 83506b9cd..0cdd720db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" dependencies = [ "proc-macro2", "quote", @@ -162,9 +162,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cexpr" @@ -183,9 +183,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "num-integer", "num-traits", @@ -228,9 +228,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" +checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" [[package]] name = "cosmos-sdk-proto" @@ -238,7 +238,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20b42021d8488665b1a0d9748f1f81df7235362d194f44481e2e61bf376b77b4" dependencies = [ - "prost 0.11.2", + "prost 0.11.3", "prost-types", "tendermint-proto", ] @@ -269,7 +269,7 @@ version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "227315dc11f0bb22a273d0c43d3ba8ef52041c42cf959f09045388a89c57e661" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", "ed25519-zebra", "k256", "rand_core 0.6.4", @@ -287,9 +287,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a227cfeb9a7152b26a354b1c990e930e962f75fd68f57ab5ae2ef888c8524292" +checksum = "04135971e2c3b867eb793ca4e832543c077dbf72edaef7672699190f8fcdb619" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -300,9 +300,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3626cb42eef870de67f791e873711255325224d86f281bf628c42abd295f3a14" +checksum = "a06c8f516a13ae481016aa35f0b5c4652459e8aee65b15b6fb51547a07cea5a0" dependencies = [ "proc-macro2", "quote", @@ -397,7 +397,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 1.0.1", "cw-utils", "schemars", "serde", @@ -406,26 +406,27 @@ dependencies = [ [[package]] name = "cw-item-set" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05dad9dc2e6e9ab784bb5598d8528f4afe014b7b0ec05e1f466fe2e11aca368c" +checksum = "e2fe22a9086c61c6b41ee46669c6f61ca4142111d38551ca156be4fa9d9fa39d" dependencies = [ "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 1.0.1", ] [[package]] name = "cw-multi-test" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7192aec80d0c01a0e5941392eea7e2b7e212ee74ca7f430bfdc899420c055ef6" +checksum = "2dc50fde3ad87ef4e3a3e57c73d11326333318761c7655cc8cae67c40382ac91" dependencies = [ "anyhow", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 0.16.0", "cw-utils", "derivative", "itertools", + "k256", "prost 0.9.0", "schemars", "serde", @@ -443,6 +444,17 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-storage-plus" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cw-utils" version = "0.16.0" @@ -451,7 +463,7 @@ checksum = "d6a84c6c1c0acc3616398eba50783934bd6c964bad6974241eaee3460c8f5b26" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2", + "cw2 0.16.0", "schemars", "semver", "serde", @@ -466,7 +478,20 @@ checksum = "91398113b806f4d2a8d5f8d05684704a20ffd5968bf87e3473e1973710b884ad" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 0.16.0", + "schemars", + "serde", +] + +[[package]] +name = "cw2" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03bdf3747540b47bc1bdaf50ba3aa5e4276ab0c2ce73e8b367ebe260cc37ff9c" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", "schemars", "serde", ] @@ -492,9 +517,9 @@ checksum = "77518e27431d43214cff4cdfbd788a7508f68d9b1f32389e6fce513e7eaccbef" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 0.16.0", "cw-utils", - "cw2", + "cw2 0.16.0", "cw721", "schemars", "serde", @@ -503,9 +528,9 @@ dependencies = [ [[package]] name = "der" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ "const-oid", "zeroize", @@ -533,9 +558,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -611,7 +636,7 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.5", + "digest 0.10.6", "ff", "generic-array", "group", @@ -802,7 +827,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -887,9 +912,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "libloading" @@ -918,8 +943,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", - "cw2", + "cw-storage-plus 1.0.1", + "cw2 1.0.0", "cw721", "cw721-base", "mars-mock-credit-manager", @@ -938,9 +963,9 @@ dependencies = [ "cw-controllers-admin-fork", "cw-item-set", "cw-multi-test", - "cw-storage-plus", + "cw-storage-plus 1.0.1", "cw-utils", - "cw2", + "cw2 1.0.0", "cw721", "cw721-base", "itertools", @@ -970,7 +995,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 1.0.1", "cw-utils", "mars-rover", "thiserror", @@ -982,7 +1007,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 1.0.1", "mars-outpost", ] @@ -992,7 +1017,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 1.0.1", "mars-outpost", ] @@ -1003,7 +1028,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vault-standard", - "cw-storage-plus", + "cw-storage-plus 1.0.1", "cw-utils", "mars-rover", "thiserror", @@ -1015,7 +1040,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 1.0.1", "cw-utils", "mars-rover", "thiserror", @@ -1030,9 +1055,9 @@ dependencies = [ "cosmwasm-std", "cw-controllers-admin-fork", "cw-multi-test", - "cw-storage-plus", + "cw-storage-plus 1.0.1", "cw-utils", - "cw2", + "cw2 1.0.0", "mars-mock-oracle", "mars-mock-vault", "mars-outpost", @@ -1066,7 +1091,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-vault-standard", "cw-controllers-admin-fork", - "cw-storage-plus", + "cw-storage-plus 1.0.1", "cw-utils", "mars-health", "mars-mock-oracle", @@ -1084,7 +1109,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-controllers-admin-fork", - "cw-storage-plus", + "cw-storage-plus 1.0.1", "mars-rover", "schemars", "serde", @@ -1098,7 +1123,7 @@ dependencies = [ "anyhow", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus", + "cw-storage-plus 1.0.1", "mars-rover", "mars-swapper-base", "thiserror", @@ -1112,8 +1137,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-controllers-admin-fork", - "cw-storage-plus", - "cw2", + "cw-storage-plus 1.0.1", + "cw2 1.0.0", "mars-osmosis", "mars-rover", "mars-swapper-base", @@ -1211,7 +1236,7 @@ dependencies = [ "chrono", "cosmwasm-std", "osmosis-std-derive", - "prost 0.11.2", + "prost 0.11.3", "prost-types", "schemars", "serde", @@ -1241,7 +1266,7 @@ dependencies = [ "cosmrs", "cosmwasm-std", "osmosis-std", - "prost 0.11.2", + "prost 0.11.3", "serde", "serde_json", "thiserror", @@ -1249,9 +1274,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "cf1c2c742266c2f1041c914ba65355a83ae8747b05f208319784083583494b4b" [[package]] name = "pbkdf2" @@ -1259,7 +1284,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -1311,9 +1336,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0841812012b2d4a6145fae9a6af1534873c32aa67fff26bd09f8fa42c83f95a" +checksum = "c0b18e655c21ff5ac2084a5ad0611e827b3f92badf79f4910b5a5c58f4d87ff0" dependencies = [ "bytes", "prost-derive 0.11.2", @@ -1352,7 +1377,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "747761bc3dc48f9a34553bf65605cf6cb6288ba219f3450b4275dbd81539551a" dependencies = [ "bytes", - "prost 0.11.2", + "prost 0.11.3", ] [[package]] @@ -1413,7 +1438,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -1485,9 +1510,9 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "serde" -version = "1.0.147" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" dependencies = [ "serde_derive", ] @@ -1521,9 +1546,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" dependencies = [ "proc-macro2", "quote", @@ -1543,9 +1568,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa", "ryu", @@ -1584,7 +1609,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -1593,7 +1618,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", "keccak", ] @@ -1609,7 +1634,7 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", "rand_core 0.6.4", ] @@ -1652,9 +1677,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" dependencies = [ "proc-macro2", "quote", @@ -1688,7 +1713,7 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost 0.11.2", + "prost 0.11.3", "prost-types", "ripemd160", "serde", @@ -1714,7 +1739,7 @@ dependencies = [ "flex-error", "num-derive", "num-traits", - "prost 0.11.2", + "prost 0.11.3", "prost-types", "serde", "serde_bytes", @@ -1776,15 +1801,15 @@ checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "uint" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", "crunchy", @@ -1923,9 +1948,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index fc0017d74..bf472b46e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,25 +32,28 @@ documentation = "https://docs.marsprotocol.io/" keywords = ["mars", "cosmos", "cosmwasm"] [workspace.dependencies] -anyhow = "1" -cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" -cw2 = "0.16" -cw721 = "0.16" -cw721-base = { version = "0.16", features = ["library"] } -cw-item-set = { version = "0.6", default-features = false, features = ["iterator"] } -cw-multi-test = "0.16" -cw-utils = "0.16" -cw-storage-plus = "0.16" +anyhow = "1.0.66" +cosmwasm-schema = "1.1.9" +cosmwasm-std = "1.1.9" +cw2 = "1.0.0" +cw721 = "0.16.0" +cw721-base = { version = "0.16.0", features = ["library"] } +cw-item-set = { version = "0.7.0", default-features = false, features = ["iterator"] } +cw-multi-test = "0.16.1" +cw-utils = "0.16.0" +cw-storage-plus = "1.0.1" +itertools = "0.10.5" osmosis-std = "0.13.2" -schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } -thiserror = "1.0" +osmosis-testing = "0.13.2" +schemars = "0.8.11" +serde = { version = "1.0.150", default-features = false, features = ["derive"] } +thiserror = "1.0.37" # packages cosmwasm-vault-standard = { version = "0.1.0", features = ["lockup", "force-unlock"] } cw-controllers-admin-fork = { version = "1.0.0", path = "./packages/controllers" } mars-health = { version = "1.0.0", path = "./packages/health" } +mars-osmosis = { version = "1.0.0", path = "./packages/chains/osmosis" } mars-outpost = { version = "1.0.0", path = "./packages/outpost" } mars-rover = { version = "1.0.0", path = "./packages/rover" } diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 264cde48c..89847442f 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -37,7 +37,7 @@ mars-rover = { workspace = true } anyhow = { workspace = true } cw-multi-test = { workspace = true } cw-utils = { workspace = true } -itertools = "0.10" +itertools = { workspace = true } mars-mock-oracle = { workspace = true } mars-mock-red-bank = { workspace = true } mars-mock-vault = { workspace = true } diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index 02780999d..7b2bdb875 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -23,7 +23,7 @@ cosmwasm-std = { workspace = true } cw2 = { workspace = true } cw-controllers-admin-fork = { workspace = true } cw-storage-plus = { workspace = true } -mars-osmosis = { version = "1.0.0", path = "../../../packages/chains/osmosis" } +mars-osmosis = { workspace = true } mars-swapper-base = { workspace = true } mars-rover = { workspace = true } osmosis-std = { workspace = true } @@ -32,4 +32,4 @@ thiserror = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -osmosis-testing = "0.13.2" +osmosis-testing = { workspace = true } From ecdcdebdcff0dddc06083de7b4866c9b6f3a7c25 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 13 Dec 2022 21:46:41 +0100 Subject: [PATCH 105/218] Renaming allowed_vaults to vault_configs (#73) renaming allowed_vaults to vault_configs --- contracts/credit-manager/src/instantiate.rs | 4 ++-- .../credit-manager/tests/helpers/mock_env.rs | 20 +++++++++---------- .../test_enumerate_vault_coin_balances.rs | 2 +- .../tests/test_enumerate_vault_configs.rs | 10 +++++----- .../tests/test_enumerate_vault_positions.rs | 2 +- .../tests/test_fields_vault_limit.rs | 2 +- .../credit-manager/tests/test_instantiate.rs | 8 ++++---- .../tests/test_liquidate_coin.rs | 2 +- .../tests/test_liquidate_vault.rs | 18 ++++++++--------- .../tests/test_refund_balances.rs | 2 +- .../tests/test_update_config.rs | 2 +- .../credit-manager/tests/test_vault_enter.rs | 18 ++++++++--------- .../credit-manager/tests/test_vault_exit.rs | 4 ++-- .../tests/test_vault_exit_unlocked.rs | 12 +++++------ .../tests/test_vault_request_unlock.rs | 12 +++++------ packages/rover/src/msg/instantiate.rs | 4 ++-- .../mars-credit-manager.json | 16 +++++++-------- scripts/deploy/base/deployer.ts | 2 +- .../MarsCreditManager.client.ts | 2 +- .../MarsCreditManager.message-composer.ts | 2 +- .../MarsCreditManager.react-query.ts | 2 +- .../MarsCreditManager.types.ts | 4 ++-- 22 files changed, 75 insertions(+), 75 deletions(-) diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index e6102772a..9639a766b 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -32,8 +32,8 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { assert_lte_to_one(&msg.max_close_factor)?; MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; - assert_no_duplicate_vaults(&msg.allowed_vaults)?; - msg.allowed_vaults + assert_no_duplicate_vaults(&msg.vault_configs)?; + msg.vault_configs .iter() .try_for_each(|v| -> ContractResult<_> { v.config.check()?; diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index f75022bc7..5e2870ce7 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -56,7 +56,7 @@ pub struct MockEnv { pub struct MockEnvBuilder { pub app: BasicApp, pub admin: Option, - pub allowed_vaults: Option>, + pub vault_configs: Option>, pub pre_deployed_vaults: Option>, pub allowed_coins: Option>, pub oracle: Option>, @@ -75,7 +75,7 @@ impl MockEnv { MockEnvBuilder { app: App::default(), admin: None, - allowed_vaults: None, + vault_configs: None, pre_deployed_vaults: None, allowed_coins: None, oracle: None, @@ -534,11 +534,11 @@ impl MockEnvBuilder { let max_close_factor = self.get_max_close_factor(); let max_unlocking_positions = self.get_max_unlocking_positions(); - let mut allowed_vaults = vec![]; - allowed_vaults.extend(self.deploy_vaults()); - allowed_vaults.extend(self.pre_deployed_vaults.clone().unwrap_or_default()); + let mut vault_configs = vec![]; + vault_configs.extend(self.deploy_vaults()); + vault_configs.extend(self.pre_deployed_vaults.clone().unwrap_or_default()); - let oracle = self.get_oracle_adapter(allowed_vaults.clone()).into(); + let oracle = self.get_oracle_adapter(vault_configs.clone()).into(); let zapper = self.deploy_zapper(&oracle)?.into(); self.app.instantiate_contract( @@ -547,7 +547,7 @@ impl MockEnvBuilder { &InstantiateMsg { admin: self.get_admin().to_string(), allowed_coins, - allowed_vaults, + vault_configs, red_bank, oracle, max_close_factor, @@ -801,7 +801,7 @@ impl MockEnvBuilder { } fn deploy_vaults(&mut self) -> Vec { - self.allowed_vaults + self.vault_configs .clone() .unwrap_or_default() .iter() @@ -837,8 +837,8 @@ impl MockEnvBuilder { self } - pub fn allowed_vaults(&mut self, allowed_vaults: &[VaultTestInfo]) -> &mut Self { - self.allowed_vaults = Some(allowed_vaults.to_vec()); + pub fn vault_configs(&mut self, vault_configs: &[VaultTestInfo]) -> &mut Self { + self.vault_configs = Some(vault_configs.to_vec()); self } diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index e2c7c876d..3e32734a1 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -32,7 +32,7 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { funds: vec![lp_token.to_coin(1000)], }) .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&all_vaults) + .vault_configs(&all_vaults) .build() .unwrap(); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_configs.rs b/contracts/credit-manager/tests/test_enumerate_vault_configs.rs index f52759ee9..fc7c02291 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_configs.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_configs.rs @@ -5,10 +5,10 @@ use crate::helpers::{assert_contents_equal, build_mock_vaults, MockEnv}; pub mod helpers; #[test] -fn test_pagination_on_allowed_vaults_query_works() { - let allowed_vaults = build_mock_vaults(32); +fn test_pagination_on_vault_configs_query_works() { + let vault_configs = build_mock_vaults(32); let mock = MockEnv::new() - .allowed_vaults(&allowed_vaults) + .vault_configs(&vault_configs) .build() .unwrap(); @@ -48,10 +48,10 @@ fn test_pagination_on_allowed_vaults_query_works() { .map(|info| info.vault_token) .collect::>(); - assert_eq!(combined.len(), allowed_vaults.len()); + assert_eq!(combined.len(), vault_configs.len()); assert_contents_equal( - &allowed_vaults + &vault_configs .iter() .map(|v| v.vault_token_denom.clone()) .collect::>(), diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index 1680afd8d..ee3a73279 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -33,7 +33,7 @@ fn test_pagination_on_all_vault_positions_query_works() { funds: vec![lp_token.to_coin(1000)], }) .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&all_vaults) + .vault_configs(&all_vaults) .build() .unwrap(); diff --git a/contracts/credit-manager/tests/test_fields_vault_limit.rs b/contracts/credit-manager/tests/test_fields_vault_limit.rs index aa7cf95e0..df08a8155 100644 --- a/contracts/credit-manager/tests/test_fields_vault_limit.rs +++ b/contracts/credit-manager/tests/test_fields_vault_limit.rs @@ -32,7 +32,7 @@ fn test_can_only_have_a_single_vault_position() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone(), degen_vault_token.clone()]) - .allowed_vaults(&[leverage_vault.clone(), degen_vault.clone()]) + .vault_configs(&[leverage_vault.clone(), degen_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(300), degen_vault_token.to_coin(300)], diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index d9f483640..8f3f7613f 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -32,8 +32,8 @@ fn test_nft_contract_addr_not_set_on_instantiate() { } #[test] -fn test_allowed_vaults_set_on_instantiate() { - let allowed_vaults = vec![ +fn test_vault_configs_set_on_instantiate() { + let vault_configs = vec![ VaultTestInfo { vault_token_denom: "vault_contract_1".to_string(), lockup: None, @@ -61,13 +61,13 @@ fn test_allowed_vaults_set_on_instantiate() { ]; let mock = MockEnv::new() - .allowed_vaults(&allowed_vaults) + .vault_configs(&vault_configs) .build() .unwrap(); let res = mock.query_vault_configs(None, None); assert_contents_equal( &res.iter().map(|v| v.vault.clone()).collect::>(), - &allowed_vaults + &vault_configs .iter() .map(|info| mock.get_vault(info)) .collect::>(), diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 022bc5332..60c6530a2 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -78,7 +78,7 @@ fn test_vault_positions_contribute_to_health() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone(), atom_info.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: vec![lp_token.to_coin(500)], diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 23b8e6774..dc5c70ecb 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -29,7 +29,7 @@ fn test_liquidatee_must_have_the_request_vault_position() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() .allowed_coins(&[uatom.clone(), uosmo.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: vec![uatom.to_coin(300), uosmo.to_coin(500)], @@ -78,7 +78,7 @@ fn test_liquidatee_is_not_liquidatable() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: vec![lp_token.to_coin(300)], @@ -137,7 +137,7 @@ fn test_liquidator_does_not_have_debt_coin_in_credit_account() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone(), ujake.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: vec![lp_token.to_coin(300)], @@ -202,7 +202,7 @@ fn test_wrong_position_type_sent_for_unlocked_vault() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: vec![lp_token.to_coin(300)], @@ -268,7 +268,7 @@ fn test_wrong_position_type_sent_for_locked_vault() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: vec![lp_token.to_coin(300)], @@ -323,7 +323,7 @@ fn test_liquidate_unlocked_vault() { let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone(), ujake.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: vec![lp_token.to_coin(300)], @@ -410,7 +410,7 @@ fn test_liquidate_locked_vault() { let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone(), atom.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: vec![lp_token.to_coin(300)], @@ -500,7 +500,7 @@ fn test_liquidate_unlocking_liquidation_order() { let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone(), ujake.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: vec![lp_token.to_coin(300)], @@ -632,7 +632,7 @@ fn test_liquidation_calculation_adjustment() { let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone(), ujake.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: vec![lp_token.to_coin(300)], diff --git a/contracts/credit-manager/tests/test_refund_balances.rs b/contracts/credit-manager/tests/test_refund_balances.rs index a5fd2bd5e..1d94d4d9a 100644 --- a/contracts/credit-manager/tests/test_refund_balances.rs +++ b/contracts/credit-manager/tests/test_refund_balances.rs @@ -57,7 +57,7 @@ fn test_refund_coin_balances_when_no_balances() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(200)], diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index e212bf36d..6ba7984c2 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -229,7 +229,7 @@ fn test_update_config_removes_properly() { let mut mock = MockEnv::new() .allowed_coins(&[uatom, uosmo]) - .allowed_vaults(&[leverage_vault]) + .vault_configs(&[leverage_vault]) .build() .unwrap(); diff --git a/contracts/credit-manager/tests/test_vault_enter.rs b/contracts/credit-manager/tests/test_vault_enter.rs index b541ceda8..d0416c7cf 100644 --- a/contracts/credit-manager/tests/test_vault_enter.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -49,7 +49,7 @@ fn test_deposit_denom_is_whitelisted() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .build() .unwrap(); @@ -79,7 +79,7 @@ fn test_vault_is_whitelisted() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[uatom.clone(), uosmo]) - .allowed_vaults(&[leverage_vault]) + .vault_configs(&[leverage_vault]) .build() .unwrap(); @@ -110,7 +110,7 @@ fn test_deposited_coin_matches_vault_requirements() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[uatom.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .build() .unwrap(); @@ -143,7 +143,7 @@ fn test_fails_if_not_enough_funds_for_implied_deposit() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(300)], @@ -180,7 +180,7 @@ fn test_fails_if_not_enough_funds_for_enumerated_deposit() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(300)], @@ -219,7 +219,7 @@ fn test_successful_deposit_into_locked_vault() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(300)], @@ -279,7 +279,7 @@ fn test_successful_deposit_into_unlocked_vault() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(300)], @@ -334,7 +334,7 @@ fn test_vault_deposit_must_be_under_cap() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(3_300_000)], @@ -415,7 +415,7 @@ fn test_successful_deposit_with_implied_full_balance_amount() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(300)], diff --git a/contracts/credit-manager/tests/test_vault_exit.rs b/contracts/credit-manager/tests/test_vault_exit.rs index 67741f661..ab51b8a31 100644 --- a/contracts/credit-manager/tests/test_vault_exit.rs +++ b/contracts/credit-manager/tests/test_vault_exit.rs @@ -71,7 +71,7 @@ fn test_no_unlocked_vault_coins_to_withdraw() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[uatom.clone(), uosmo.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![coin(300, "uatom"), coin(500, "uosmo")], @@ -114,7 +114,7 @@ fn test_withdraw_with_unlocked_vault_coins() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(300)], diff --git a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs index b89b0a9b4..61201aa17 100644 --- a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs +++ b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs @@ -22,7 +22,7 @@ fn test_only_owner_can_withdraw_unlocked_for_account() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .build() .unwrap(); @@ -73,7 +73,7 @@ fn test_not_owner_of_unlocking_position() { let user_b = Addr::unchecked("user_b"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user_a.clone(), funds: vec![lp_token.to_coin(300)], @@ -146,7 +146,7 @@ fn test_unlocking_position_not_ready_time() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(300)], @@ -200,7 +200,7 @@ fn test_unlocking_position_not_ready_blocks() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(300)], @@ -254,7 +254,7 @@ fn test_withdraw_unlock_success_time_expiring() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(300)], @@ -330,7 +330,7 @@ fn test_withdraw_unlock_success_block_expiring() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(300)], diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs index 760b9bf51..701f4e981 100644 --- a/contracts/credit-manager/tests/test_vault_request_unlock.rs +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -20,7 +20,7 @@ fn test_only_owner_can_request_unlocked() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .build() .unwrap(); @@ -74,7 +74,7 @@ fn test_request_when_unnecessary() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .build() .unwrap(); @@ -105,7 +105,7 @@ fn test_no_vault_tokens_for_request() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .build() .unwrap(); @@ -148,7 +148,7 @@ fn test_not_enough_vault_tokens_for_request() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(300)], @@ -203,7 +203,7 @@ fn test_request_unlocked() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(200)], @@ -278,7 +278,7 @@ fn test_cannot_request_more_than_max() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[lp_token.clone()]) - .allowed_vaults(&[leverage_vault.clone()]) + .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![lp_token.to_coin(200)], diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index efaaa0111..a9477d412 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -13,9 +13,9 @@ pub struct InstantiateMsg { pub admin: String, /// Whitelisted coin denoms approved by governance pub allowed_coins: Vec, - /// Whitelisted vaults approved by governance that implement credit manager's vault interface + /// Vaults approved by governance that implement credit manager's vault interface /// Includes a deposit cap that enforces a TLV limit for risk mitigation - pub allowed_vaults: Vec, + pub vault_configs: Vec, /// The Mars Protocol money market contract where we borrow assets from pub red_bank: RedBankUnchecked, /// The Mars Protocol oracle contract. We read prices of assets here. diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 9a1c034cd..22b6d9934 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -9,12 +9,12 @@ "required": [ "admin", "allowed_coins", - "allowed_vaults", "max_close_factor", "max_unlocking_positions", "oracle", "red_bank", "swapper", + "vault_configs", "zapper" ], "properties": { @@ -29,13 +29,6 @@ "type": "string" } }, - "allowed_vaults": { - "description": "Whitelisted vaults approved by governance that implement credit manager's vault interface Includes a deposit cap that enforces a TLV limit for risk mitigation", - "type": "array", - "items": { - "$ref": "#/definitions/VaultInstantiateConfig" - } - }, "max_close_factor": { "description": "The maximum percent a liquidator can decrease the debt amount of the liquidatee", "allOf": [ @@ -76,6 +69,13 @@ } ] }, + "vault_configs": { + "description": "Vaults approved by governance that implement credit manager's vault interface Includes a deposit cap that enforces a TLV limit for risk mitigation", + "type": "array", + "items": { + "$ref": "#/definitions/VaultInstantiateConfig" + } + }, "zapper": { "description": "Helper contract for adding/removing liquidity", "allOf": [ diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index d69103636..d12c1b57e 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -176,7 +176,7 @@ export class Deployer { const msg: RoverInstantiateMsg = { max_unlocking_positions: this.config.maxUnlockingPositions.toString(), allowed_coins: [this.config.baseDenom, this.config.secondaryDenom, this.config.lpToken.denom], - allowed_vaults: [ + vault_configs: [ { config: { deposit_cap: this.config.vaultDepositCap, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 6e01fa0c2..cc390507f 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -8,8 +8,8 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { - Uint128, Decimal, + Uint128, OracleBaseForString, RedBankBaseForString, SwapperBaseForString, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index fb6120621..583c7e316 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -9,8 +9,8 @@ import { MsgExecuteContractEncodeObject } from 'cosmwasm' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { - Uint128, Decimal, + Uint128, OracleBaseForString, RedBankBaseForString, SwapperBaseForString, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index e4963e17b..fc3e70e0e 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -9,8 +9,8 @@ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tan import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { - Uint128, Decimal, + Uint128, OracleBaseForString, RedBankBaseForString, SwapperBaseForString, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 87665e33c..0e415c44d 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -5,8 +5,8 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -export type Uint128 = string export type Decimal = string +export type Uint128 = string export type OracleBaseForString = string export type RedBankBaseForString = string export type SwapperBaseForString = string @@ -14,12 +14,12 @@ export type ZapperBaseForString = string export interface InstantiateMsg { admin: string allowed_coins: string[] - allowed_vaults: VaultInstantiateConfig[] max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: OracleBaseForString red_bank: RedBankBaseForString swapper: SwapperBaseForString + vault_configs: VaultInstantiateConfig[] zapper: ZapperBaseForString } export interface VaultInstantiateConfig { From 95b0f05d31010c281f584c19f99776157303d3c7 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 15 Dec 2022 10:14:55 +0100 Subject: [PATCH 106/218] Updating build scripts (#76) updating build scripts --- scripts/deploy/addresses/osmo-test-4.json | 11 +- scripts/deploy/base/deployer.ts | 241 +++++++++++----------- scripts/deploy/base/index.ts | 47 ++--- scripts/deploy/base/rover.ts | 154 ++++++++------ scripts/deploy/base/setupDeployer.ts | 14 +- scripts/deploy/osmosis/config.ts | 124 +++++++---- scripts/types/config.ts | 95 +++++---- scripts/types/instantiateMsgs.ts | 2 - scripts/types/storageItems.ts | 5 +- 9 files changed, 392 insertions(+), 301 deletions(-) diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 29aee8eea..d84bb475a 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,8 +1,7 @@ { - "mockVault": "osmo1ajyq2aq6jk4eh9pvktehslh0c4s0rsl2qkzcc0sd25uqh0pkt32qd4eh6v", - "marsOracleAdapter": "osmo1gsnz74uf84amt94zwkuh2y3p6puy9haequa2ll0cms0a2z9wcxts8wt9jd", - "swapper": "osmo1lt5a96ykq4vcnwxphnsyvcq5cpugmzdpkgjd75j3l9ypjrhd2yjsnpel22", - "mockZapper": "osmo1fqphdkfsgz7qwadggj6995ygf5u0cqkqj6qu3n9w4dmyheau6w7s77h6vy", - "creditManager": "osmo17rnphu9xst39ew4ef0hr6qx4gtn5du694qsdtlzjza280pa8f0vqwsr58k", - "accountNft": "osmo1eu0stlgxz0ynykfsksqgqydglzgxauk3cy6h52j09p8rklca3pcqr25pl4" + "mockVault": "osmo1206a57paps5vy2qz38mdywn8shwjlugwtgk0zfmgjv3uy28pkm4q5qegay", + "marsOracleAdapter": "osmo1rlm6c73ymcnutmfe5dhvnu2ppc3dwnaxx7mcexlerm8ew5je23hqtgyscz", + "swapper": "osmo13spujdjfep3xrreaz6ka09nk0929w7dye82lwxda0rq3yl2cp3dsc756m0", + "creditManager": "osmo1ggar740wj9qmsml05s8uq3pv79jxtxplzpuz0l9mmpr027683fqsnmlwvj", + "accountNft": "osmo19d6japwr5rvfcn8nwgfkzhmjyk42pd6m473mtxlqukcyn8zzt3ds06jycg" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index d12c1b57e..dc917fd64 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -1,5 +1,5 @@ import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' -import { DeploymentConfig } from '../../types/config' +import { DeploymentConfig, TestActions } from '../../types/config' import { printBlue, printGray, printGreen } from '../../utils/chalk' import { ARTIFACTS_PATH, Storage } from './storage' import fs from 'fs' @@ -7,7 +7,6 @@ import { InstantiateMsgs } from '../../types/instantiateMsgs' import { InstantiateMsg as NftInstantiateMsg } from '../../types/generated/mars-account-nft/MarsAccountNft.types' import { InstantiateMsg as VaultInstantiateMsg } from '../../types/generated/mars-mock-vault/MarsMockVault.types' import { InstantiateMsg as SwapperInstantiateMsg } from '../../types/generated/mars-swapper-base/MarsSwapperBase.types' -import { InstantiateMsg as ZapperInstantiateMsg } from '../../types/generated/mars-mock-zapper/MarsMockZapper.types' import { InstantiateMsg as RoverInstantiateMsg } from '../../types/generated/mars-credit-manager/MarsCreditManager.types' import { InstantiateMsg as OracleAdapterInstantiateMsg } from '../../types/generated/mars-oracle-adapter/MarsOracleAdapter.types' import { Rover } from './rover' @@ -23,13 +22,15 @@ import { MarsSwapperBaseQueryClient, } from '../../types/generated/mars-swapper-base/MarsSwapperBase.client' import { MarsAccountNftClient } from '../../types/generated/mars-account-nft/MarsAccountNft.client' +import { MarsCreditManagerClient } from '../../types/generated/mars-credit-manager/MarsCreditManager.client' +import { InitOrUpdateAssetParams } from '../../types/generated/mars-mock-red-bank/MarsMockRedBank.types' export class Deployer { constructor( private config: DeploymentConfig, - private cwClient: SigningCosmWasmClient, - private deployerAddr: string, - private storage: Storage, + public cwClient: SigningCosmWasmClient, + public deployerAddr: string, + public storage: Storage, ) {} async saveStorage() { @@ -44,7 +45,7 @@ export class Deployer { const wasm = fs.readFileSync(ARTIFACTS_PATH + file) const uploadResult = await this.cwClient.upload(this.deployerAddr, wasm, 'auto') this.storage.codeIds[name] = uploadResult.codeId - printGreen(`${this.config.chainId} :: ${name} : ${this.storage.codeIds[name]}`) + printGreen(`${this.config.chain.id} :: ${name} : ${this.storage.codeIds[name]}`) } async instantiate(name: keyof Storage['addresses'], codeId: number, msg: InstantiateMsgs) { @@ -61,26 +62,31 @@ export class Deployer { ) this.storage.addresses[name] = contractAddress printGreen( - `${this.config.chainId} :: ${name} Contract Address : ${this.storage.addresses[name]}`, + `${this.config.chain.id} :: ${name} Contract Address : ${this.storage.addresses[name]}`, ) } async instantiateNftContract() { const msg: NftInstantiateMsg = { - max_value_for_burn: this.config.maxValueForBurn.toString(), + max_value_for_burn: this.config.maxValueForBurn, minter: this.deployerAddr, - name: 'credit-manger-accounts', - symbol: 'rover-nft', + name: 'credit-manager-accounts', + symbol: 'rNFT', } await this.instantiate('accountNft', this.storage.codeIds.accountNft!, msg) } async instantiateMockVault() { + if (!this.config.testActions) { + printGray('No test actions, mock vault not needed') + return + } + const msg: VaultInstantiateMsg = { - base_token_denom: this.config.lpToken.denom, - oracle: this.config.oracleAddr, - vault_token_denom: this.config.vaultTokenDenom, - lockup: this.config.vaultLockup, + base_token_denom: this.config.testActions.vault.mock.baseToken.denom, + oracle: this.config.oracle.addr, + vault_token_denom: this.config.testActions.vault.mock.vaultTokenDenom, + lockup: this.config.testActions.vault.mock.lockup, } await this.instantiate('mockVault', this.storage.codeIds.mockVault!, msg) @@ -89,7 +95,7 @@ export class Deployer { printBlue('Seeding mock vault') await this.transferCoin( this.storage.addresses.mockVault!, - coin(10_000_000, this.config.vaultTokenDenom), + coin(10_000_000, this.config.testActions.vault.mock.vaultTokenDenom), ) this.storage.actions.seedMockVault = true } else { @@ -99,17 +105,20 @@ export class Deployer { async instantiateMarsOracleAdapter() { const msg: OracleAdapterInstantiateMsg = { - oracle: this.config.oracleAddr, + oracle: this.config.oracle.addr, admin: this.deployerAddr, - vault_pricing: [ - { - addr: this.storage.addresses.mockVault!, - method: 'preview_redeem', - base_denom: this.config.baseDenom, - vault_coin_denom: this.config.vaultTokenDenom, - }, - ], + vault_pricing: this.config.oracle.vaultPricing, } + + if (this.config.testActions) { + msg.vault_pricing.push({ + addr: this.storage.addresses.mockVault!, + method: 'preview_redeem', + base_denom: this.config.chain.baseDenom, + vault_coin_denom: this.config.testActions.vault.mock.vaultTokenDenom, + }) + } + await this.instantiate('marsOracleAdapter', this.storage.codeIds.marsOracleAdapter!, msg) } @@ -119,81 +128,53 @@ export class Deployer { } await this.instantiate('swapper', this.storage.codeIds.swapper!, msg) - if (!this.storage.actions.setRoute) { + if (!this.storage.actions.setRoutes) { const swapClient = new MarsSwapperBaseClient( this.cwClient, this.deployerAddr, this.storage.addresses.swapper!, ) - printBlue( - `Setting ${this.config.baseDenom}-${this.config.secondaryDenom} route for swap contract`, - ) - await swapClient.setRoute({ - denomIn: this.config.baseDenom, - denomOut: this.config.secondaryDenom, - // @ts-expect-error ts-codegen incorrectly parses an array as an object - route: this.config.swapRoute, - }) + + for (const route of this.config.swapRoutes) { + printBlue(`Setting ${route.denomIn}-${route.denomOut} route for swapper contract`) + // @ts-expect-error ts-codegen cannot parse the generic + await swapClient.setRoute(route) + } const swapQuery = new MarsSwapperBaseQueryClient( this.cwClient, this.storage.addresses.swapper!, ) const routes = await swapQuery.routes({}) - assert.equal(routes.length, 1) - this.storage.actions.setRoute = true + assert.equal(routes.length, this.config.swapRoutes.length) + this.storage.actions.setRoutes = true } else { - printGray('Swap contract already seeded with funds') - } - } - - async instantiateZapper() { - const msg: ZapperInstantiateMsg = { - oracle: this.storage.addresses.marsOracleAdapter!, - lp_configs: [ - { - lp_token_denom: this.config.lpToken.denom, - lp_pair_denoms: [this.config.zap[0].denom, this.config.zap[1].denom], - }, - ], - } - await this.instantiate('mockZapper', this.storage.codeIds.mockZapper!, msg) - - // Temporary until Token Factory is integrated into Cosmwasm or Apollo Vaults are in testnet - if (!this.storage.actions.seedMockZapper) { - printBlue('Seeding mock zapper') - await this.transferCoin( - this.storage.addresses.mockZapper!, - coin(10_000_000, this.config.lpToken.denom), - ) - this.storage.actions.seedMockZapper = true - } else { - printGray('Mock zapper already seeded') + printGray("Swap contract's routes already set") } } async instantiateCreditManager() { const msg: RoverInstantiateMsg = { - max_unlocking_positions: this.config.maxUnlockingPositions.toString(), - allowed_coins: [this.config.baseDenom, this.config.secondaryDenom, this.config.lpToken.denom], - vault_configs: [ - { - config: { - deposit_cap: this.config.vaultDepositCap, - liquidation_threshold: this.config.vaultLiquidationThreshold.toString(), - max_ltv: this.config.vaultMaxLTV.toString(), - whitelisted: true, - }, - vault: { address: this.storage.addresses.mockVault! }, - }, - ], + max_unlocking_positions: this.config.maxUnlockingPositions, + allowed_coins: this.config.allowedCoins, + vault_configs: this.config.vaults, oracle: this.storage.addresses.marsOracleAdapter!, admin: this.deployerAddr, - red_bank: this.config.redBankAddr, - max_close_factor: this.config.maxCloseFactor.toString(), + red_bank: this.config.redBank.addr, + max_close_factor: this.config.maxCloseFactor, swapper: this.storage.addresses.swapper!, - zapper: this.storage.addresses.mockZapper!, + zapper: this.config.zapper.addr, } + + if (this.config.testActions) { + msg.vault_configs.push({ + vault: { + address: this.storage.addresses.mockVault!, + }, + config: this.config.testActions.vault.mock.config, + }) + } + await this.instantiate('creditManager', this.storage.codeIds.creditManager!, msg) } @@ -214,8 +195,12 @@ export class Deployer { } if (!this.storage.actions.acceptedOwnership) { - const rover = this.getRoverClient(this.deployerAddr, this.cwClient) - await rover.updateConfig({ account_nft: this.storage.addresses.accountNft }) + const client = new MarsCreditManagerClient( + this.cwClient, + this.deployerAddr, + this.storage.addresses.creditManager!, + ) + await client.updateConfig({ newConfig: { account_nft: this.storage.addresses.accountNft } }) this.storage.actions.acceptedOwnership = true printGreen(`Rover accepts ownership of Nft contract`) } else { @@ -223,20 +208,20 @@ export class Deployer { } } - async newUserRoverClient() { + async newUserRoverClient(testActions: TestActions) { const { client, address } = await this.generateNewAddress() printBlue(`New user: ${address}`) await this.transferCoin( address, - coin(this.config.startingAmountForTestUser, this.config.baseDenom), + coin(testActions.startingAmountForTestUser, this.config.chain.baseDenom), ) - return this.getRoverClient(address, client) + return this.getRoverClient(address, client, testActions) } async saveDeploymentAddrsToFile() { const addressesDir = resolve(join(__dirname, '../../../deploy/addresses')) await writeFile( - `${addressesDir}/${this.config.chainId}.json`, + `${addressesDir}/${this.config.chain.id}.json`, JSON.stringify(this.storage.addresses), ) } @@ -247,27 +232,31 @@ export class Deployer { return } - const wallet = await getWallet(this.config.redBankDeployerMnemonic, this.config.chainPrefix) + const wallet = await getWallet( + this.config.testActions!.outpostsDeployerMnemonic, + this.config.chain.prefix, + ) const client = await setupClient(this.config, wallet) const addr = await getAddress(wallet) - for (const coin of this.config.toGrantCreditLines) { + for (const denom of [this.config.chain.baseDenom, this.config.testActions!.secondaryDenom]) { const msg = { update_uncollateralized_loan_limit: { user: this.storage.addresses.creditManager, - denom: coin.denom, - new_limit: coin.amount.toString(), + denom, + new_limit: this.config.testActions!.defaultCreditLine, }, } - - printBlue(`Granting credit line to Rover for: ${coin.amount} ${coin.denom}`) - await client.execute(addr, this.config.redBankAddr, msg, 'auto') + printBlue( + `Granting credit line to Rover for: ${this.config.testActions!.defaultCreditLine} ${denom}`, + ) + await client.execute(addr, this.config.redBank.addr, msg, 'auto') } this.storage.actions.grantedCreditLines = true } - async setupOraclePricesForZapDenoms() { + async setupOraclePrices() { if (this.storage.actions.oraclePricesSet) { printGray('Oracle prices already set') return @@ -275,11 +264,11 @@ export class Deployer { const { client, addr } = await this.getOutpostsDeployer() - for (const coin of this.config.zap - .map((c) => ({ denom: c.denom, price: c.price })) - .concat(this.config.lpToken)) { + for (const coin of this.config + .testActions!.zap.map((c) => ({ denom: c.denom, price: c.price })) + .concat(this.config.testActions!.vault.mock.baseToken)) { try { - await client.queryContractSmart(this.config.oracleAddr, { + await client.queryContractSmart(this.config.oracle.addr, { price: { denom: coin.denom, }, @@ -290,13 +279,12 @@ export class Deployer { set_price_source: { denom: coin.denom, price_source: { - fixed: { price: coin.price.toString() }, + fixed: { price: coin.price }, }, }, } - console.log(JSON.stringify(msg)) printBlue(`Setting price for ${coin.denom}: ${coin.price}`) - await client.execute(addr, this.config.oracleAddr, msg, 'auto') + await client.execute(addr, this.config.oracle.addr, msg, 'auto') } } this.storage.actions.oraclePricesSet = true @@ -309,44 +297,55 @@ export class Deployer { } const { client, addr } = await this.getOutpostsDeployer() - for (const denom of this.config.zap.map((c) => c.denom).concat(this.config.lpToken.denom)) { + for (const denom of this.config + .testActions!.zap.map((c) => c.denom) + .concat(this.config.testActions!.vault.mock.baseToken.denom)) { try { - await client.queryContractSmart(this.config.redBankAddr, { + await client.queryContractSmart(this.config.redBank.addr, { market: { denom, }, }) printGray(`Market for ${denom} already set`) } catch { - const msg = { + const msg: { + init_asset: { + denom: string + params: InitOrUpdateAssetParams + } + } = { init_asset: { denom, - initial_borrow_rate: '0.1', - max_loan_to_value: '0.65', - reserve_factor: '0.2', - liquidation_threshold: '0.7', - liquidation_bonus: '0.1', - interest_rate_model: { - optimal_utilization_rate: '0.1', - base: '0.3', - slope_1: '0.25', - slope_2: '0.3', + params: { + initial_borrow_rate: '0.1', + max_loan_to_value: '0.65', + reserve_factor: '0.2', + liquidation_threshold: '0.7', + liquidation_bonus: '0.1', + interest_rate_model: { + optimal_utilization_rate: '0.1', + base: '0.3', + slope_1: '0.25', + slope_2: '0.3', + }, + deposit_cap: '1000000000', + deposit_enabled: true, + borrow_enabled: true, }, - deposit_cap: '1000000000', - deposit_enabled: true, - borrow_enabled: true, - symbol: denom, }, } printBlue(`Setting market for ${denom}`) - await client.execute(addr, this.config.redBankAddr, msg, 'auto') + await client.execute(addr, this.config.redBank.addr, msg, 'auto') } } this.storage.actions.redBankMarketsSet = true } private async getOutpostsDeployer() { - const wallet = await getWallet(this.config.redBankDeployerMnemonic, this.config.chainPrefix) + const wallet = await getWallet( + this.config.testActions!.outpostsDeployerMnemonic, + this.config.chain.prefix, + ) const client = await setupClient(this.config, wallet) const addr = await getAddress(wallet) return { client, addr } @@ -360,13 +359,13 @@ export class Deployer { private async generateNewAddress() { const { mnemonic } = await DirectSecp256k1HdWallet.generate(24) - const wallet = await getWallet(mnemonic, this.config.chainPrefix) + const wallet = await getWallet(mnemonic, this.config.chain.prefix) const client = await setupClient(this.config, wallet) const address = await getAddress(wallet) return { client, address } } - private getRoverClient(address: string, client: SigningCosmWasmClient) { - return new Rover(address, this.storage, this.config, client) + private getRoverClient(address: string, client: SigningCosmWasmClient, testActions: TestActions) { + return new Rover(address, this.storage, this.config, client, testActions) } } diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 330e21c6d..4a43d388c 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -1,6 +1,6 @@ import { setupDeployer } from './setupDeployer' import { printRed, printYellow } from '../../utils/chalk' -import { DeploymentConfig, VaultType } from '../../types/config' +import { DeploymentConfig } from '../../types/config' import { wasmFile } from '../../utils/environment' export interface TaskRunnerProps { @@ -16,41 +16,42 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp await deployer.upload('mockVault', wasmFile('mars_mock_vault')) await deployer.upload('marsOracleAdapter', wasmFile('mars_oracle_adapter')) await deployer.upload('swapper', wasmFile(swapperContractName)) - await deployer.upload('mockZapper', wasmFile('mars_mock_zapper')) await deployer.upload('creditManager', wasmFile('mars_credit_manager')) // Instantiate contracts await deployer.instantiateMockVault() await deployer.instantiateMarsOracleAdapter() await deployer.instantiateSwapper() - await deployer.instantiateZapper() await deployer.instantiateCreditManager() await deployer.instantiateNftContract() await deployer.transferNftContractOwnership() - await deployer.grantCreditLines() - await deployer.setupOraclePricesForZapDenoms() - await deployer.setupRedBankMarketsForZapDenoms() await deployer.saveDeploymentAddrsToFile() - const rover = await deployer.newUserRoverClient() - // Test basic user flows - await rover.createCreditAccount() - await rover.deposit() - await rover.borrow() - await rover.repay() - await rover.swap() - await rover.withdraw() - await rover.zap() - await rover.vaultDeposit() - if (config.vaultType === VaultType.UNLOCKED) { - await rover.vaultWithdraw() - await rover.unzap() - } else { - await rover.vaultRequestUnlock() - } + if (config.testActions) { + await deployer.grantCreditLines() + await deployer.setupOraclePrices() + await deployer.setupRedBankMarketsForZapDenoms() + const rover = await deployer.newUserRoverClient(config.testActions) + await rover.createCreditAccount() + await rover.deposit() + await rover.borrow() + await rover.swap() + await rover.repay() + await rover.withdraw() - await rover.refundAllBalances() + const vaultConfig = config.vaults[0] + const info = await rover.getVaultInfo(vaultConfig) + await rover.zap(info.tokens.base_token) + await rover.vaultDeposit(vaultConfig, info) + if (info.lockup) { + await rover.vaultRequestUnlock(vaultConfig, info) + } else { + await rover.vaultWithdraw(vaultConfig, info) + await rover.unzap(info.tokens.base_token) + } + await rover.refundAllBalances() + } printYellow('COMPLETE') } catch (e) { diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 49a96d561..f8cd8e955 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -1,5 +1,5 @@ import { Storage } from './storage' -import { DeploymentConfig } from '../../types/config' +import { DeploymentConfig, TestActions, VaultInfo } from '../../types/config' import { difference } from 'lodash' import assert from 'assert' import { printBlue, printGreen } from '../../utils/chalk' @@ -13,7 +13,9 @@ import { Action, Coin, ConfigUpdates, + VaultInstantiateConfig, } from '../../types/generated/mars-credit-manager/MarsCreditManager.types' +import { MarsMockVaultQueryClient } from '../../types/generated/mars-mock-vault/MarsMockVault.client' export class Rover { private exec: MarsCreditManagerClient @@ -26,6 +28,7 @@ export class Rover { private storage: Storage, private config: DeploymentConfig, private cwClient: SigningCosmWasmClient, + private actions: TestActions, ) { this.exec = new MarsCreditManagerClient(cwClient, userAddr, storage.addresses.creditManager!) this.query = new MarsCreditManagerQueryClient(cwClient, storage.addresses.creditManager!) @@ -47,96 +50,99 @@ export class Rover { } async deposit() { - const amount = this.config.depositAmount.toString() + const amount = this.actions.depositAmount await this.updateCreditAccount( - [{ deposit: { amount, denom: this.config.baseDenom } }], - [{ amount, denom: this.config.baseDenom }], + [{ deposit: { amount, denom: this.config.chain.baseDenom } }], + [{ amount, denom: this.config.chain.baseDenom }], ) const positions = await this.query.positions({ accountId: this.accountId! }) assert.equal(positions.coins.length, 1) assert.equal(positions.coins[0].amount, amount) - assert.equal(positions.coins[0].denom, this.config.baseDenom) - printGreen(`Deposited into credit account: ${amount} ${this.config.baseDenom}`) + assert.equal(positions.coins[0].denom, this.config.chain.baseDenom) + printGreen(`Deposited into credit account: ${amount} ${this.config.chain.baseDenom}`) } async withdraw() { - const amount = this.config.withdrawAmount.toString() + const amount = this.actions.withdrawAmount const positionsBefore = await this.query.positions({ accountId: this.accountId! }) const beforeWithdraw = parseFloat( - positionsBefore.coins.find((c) => c.denom === this.config.baseDenom)!.amount, + positionsBefore.coins.find((c) => c.denom === this.config.chain.baseDenom)!.amount, ) - await this.updateCreditAccount([{ withdraw: { amount, denom: this.config.baseDenom } }]) + await this.updateCreditAccount([{ withdraw: { amount, denom: this.config.chain.baseDenom } }]) const positionsAfter = await this.query.positions({ accountId: this.accountId! }) const afterWithdraw = parseFloat( - positionsAfter.coins.find((c) => c.denom === this.config.baseDenom)!.amount, + positionsAfter.coins.find((c) => c.denom === this.config.chain.baseDenom)!.amount, ) assert.equal(beforeWithdraw - afterWithdraw, amount) - printGreen(`Withdrew: ${amount} ${this.config.baseDenom}`) + printGreen(`Withdrew: ${amount} ${this.config.chain.baseDenom}`) } async borrow() { - const amount = this.config.borrowAmount.toString() - await this.updateCreditAccount([{ borrow: { amount, denom: this.config.secondaryDenom } }]) + const amount = this.actions.borrowAmount + await this.updateCreditAccount([{ borrow: { amount, denom: this.actions.secondaryDenom } }]) const positions = await this.query.positions({ accountId: this.accountId! }) assert.equal(positions.debts.length, 1) - assert.equal(positions.debts[0].denom, this.config.secondaryDenom) - printGreen(`Borrowed from RedBank: ${amount} ${this.config.secondaryDenom}`) + assert.equal(positions.debts[0].denom, this.actions.secondaryDenom) + printGreen(`Borrowed from RedBank: ${amount} ${this.actions.secondaryDenom}`) } async repay() { - const amount = this.config.repayAmount.toString() - await this.updateCreditAccount([{ repay: { amount, denom: this.config.secondaryDenom } }]) + const amount = this.actions.repayAmount + await this.updateCreditAccount([{ repay: { amount, denom: this.actions.secondaryDenom } }]) const positions = await this.query.positions({ accountId: this.accountId! }) printGreen( - `Repaid to RedBank: ${amount} ${this.config.secondaryDenom}. Debt remaining: ${positions.debts[0].amount} ${positions.debts[0].denom}`, + `Repaid to RedBank: ${amount} ${ + this.actions.secondaryDenom + }. Debt remaining: ${JSON.stringify(positions.debts)}`, ) } async swap() { - const amount = this.config.swapAmount.toString() - printBlue(`Swapping ${amount} ${this.config.baseDenom} for ${this.config.secondaryDenom}`) - const prevPositions = await this.query.positions({ accountId: this.accountId! }) + const amount = this.actions.swap.amount printBlue( - `Previous account balance: ${prevPositions.coins[0].amount} ${prevPositions.coins[0].denom}`, + `Swapping ${amount} ${this.config.chain.baseDenom} for ${this.actions.secondaryDenom}`, ) + const prevPositions = await this.query.positions({ accountId: this.accountId! }) + printBlue(`Previous account balance: ${JSON.stringify(prevPositions.coins)}`) await this.updateCreditAccount([ { swap_exact_in: { coin_in_amount: amount, - coin_in_denom: this.config.baseDenom, - denom_out: this.config.secondaryDenom, - slippage: this.config.slippage.toString(), + coin_in_denom: this.config.chain.baseDenom, + denom_out: this.actions.secondaryDenom, + slippage: this.actions.swap.slippage, }, }, ]) printGreen(`Swap successful`) const newPositions = await this.query.positions({ accountId: this.accountId! }) - printGreen( - `New account balance: ${newPositions.coins[0].amount} ${newPositions.coins[0].denom}, ${newPositions.coins[1].amount} ${newPositions.coins[1].denom}`, - ) + printGreen(`New account balance: ${JSON.stringify(newPositions.coins)}`) } - async zap() { + async zap(lp_token_out: string) { await this.updateCreditAccount([ { provide_liquidity: { - coins_in: this.config.zap.map((c) => ({ denom: c.denom, amount: c.amount.toString() })), - lp_token_out: this.config.lpToken.denom, + coins_in: this.actions.zap.map((c) => ({ denom: c.denom, amount: c.amount })), + lp_token_out, minimum_receive: '1', }, }, ]) const positions = await this.query.positions({ accountId: this.accountId! }) - const lp_balance = positions.coins.find((c) => c.denom === this.config.lpToken.denom)!.amount + const lp_balance = positions.coins.find((c) => c.denom === lp_token_out)!.amount printGreen( - `Zapped ${this.config.zap.map((c) => c.denom).join(', ')} for LP token: ${lp_balance} ${ - this.config.lpToken.denom - }`, + `Zapped ${this.actions.zap + .map((c) => c.denom) + .join(', ')} for LP token: ${lp_balance} ${lp_token_out}`, ) } - async unzap() { - const lpToken = { denom: this.config.lpToken.denom, amount: this.config.unzapAmount.toString() } + async unzap(lp_token_in: string) { + const lpToken = { + denom: lp_token_in, + amount: this.actions.unzapAmount, + } await this.updateCreditAccount([ { withdraw_liquidity: { @@ -147,74 +153,76 @@ export class Rover { ]) const underlying = await this.query.estimateWithdrawLiquidity({ lpToken }) printGreen( - `Unzapped ${this.config.lpToken.denom} ${this.config.unzapAmount} for underlying: ${underlying + `Unzapped ${lp_token_in} ${this.actions.unzapAmount} for underlying: ${underlying .map((c) => `${c.amount} ${c.denom}`) .join(', ')}`, ) } - async vaultDeposit() { + async vaultDeposit(v: VaultInstantiateConfig, info: VaultInfo) { const oldRoverBalance = await this.cwClient.getBalance( this.storage.addresses.creditManager!, - this.config.vaultTokenDenom, + info.tokens.vault_token, ) await this.updateCreditAccount([ { enter_vault: { - amount: this.config.vaultDepositAmount.toString(), - denom: this.config.lpToken.denom, - vault: { address: this.storage.addresses.mockVault! }, + amount: this.actions.vault.depositAmount, + denom: info.tokens.base_token, + vault: { address: v.vault.address }, }, }, ]) const positions = await this.query.positions({ accountId: this.accountId! }) assert.equal(positions.vaults.length, 1) - const state = await this.getVaultBalance(this.storage.addresses.mockVault!) + const state = await this.getVaultBalance(v.vault.address) assert(state.locked > 0 || state.unlocked > 0) const newRoverBalance = await this.cwClient.getBalance( this.storage.addresses.creditManager!, - this.config.vaultTokenDenom, + info.tokens.vault_token, ) const newAmount = parseInt(newRoverBalance.amount) - parseInt(oldRoverBalance.amount) assert(newAmount === state.locked || newAmount === state.unlocked) printGreen( - `Deposited ${this.config.vaultDepositAmount} ${ - this.config.lpToken.denom - } in exchange for vault tokens: ${JSON.stringify(positions.vaults[0].amount)}`, + `Deposited ${this.actions.vault.depositAmount} ${ + info.tokens.base_token + } in exchange for ${JSON.stringify(positions.vaults[0].amount)} vault tokens (${ + info.tokens.vault_token + })`, ) } - async vaultWithdraw() { - const oldBalance = await this.getAccountBalance(this.config.baseDenom) + async vaultWithdraw(v: VaultInstantiateConfig, info: VaultInfo) { + const oldBalance = await this.getAccountBalance(info.tokens.base_token) await this.updateCreditAccount([ { exit_vault: { - amount: this.config.vaultWithdrawAmount.toString(), - vault: { address: this.storage.addresses.mockVault! }, + amount: this.actions.vault.withdrawAmount, + vault: { address: v.vault.address }, }, }, ]) - const newBalance = await this.getAccountBalance(this.config.baseDenom) + const newBalance = await this.getAccountBalance(info.tokens.base_token) assert(newBalance > oldBalance) printGreen( - `Withdrew ${newBalance - oldBalance} ${this.config.baseDenom} in exchange for ${ - this.config.vaultWithdrawAmount - } ${this.config.vaultTokenDenom}`, + `Withdrew ${newBalance - oldBalance} ${info.tokens.base_token} in exchange for ${ + this.actions.vault.withdrawAmount + } ${info.tokens.vault_token} vault tokens`, ) } - async vaultRequestUnlock() { - const oldBalance = await this.getVaultBalance(this.storage.addresses.mockVault!) + async vaultRequestUnlock(v: VaultInstantiateConfig, info: VaultInfo) { + const oldBalance = await this.getVaultBalance(v.vault.address) await this.updateCreditAccount([ { request_vault_unlock: { - amount: this.config.vaultWithdrawAmount.toString(), - vault: { address: this.storage.addresses.mockVault! }, + amount: this.actions.vault.withdrawAmount, + vault: { address: v.vault.address }, }, }, ]) - const newBalance = await this.getVaultBalance(this.storage.addresses.mockVault!) + const newBalance = await this.getVaultBalance(v.vault.address) assert(newBalance.locked < oldBalance.locked) assert.equal(newBalance.unlocking.length, 1) @@ -223,7 +231,7 @@ export class Rover { newBalance.unlocking[0].coin.amount } ${newBalance.unlocking[0].coin.denom} in exchange for: ${ oldBalance.locked - newBalance.locked - } ${this.config.vaultTokenDenom}`, + } ${info.tokens.vault_token}`, ) } @@ -234,6 +242,28 @@ export class Rover { printGreen(`Withdrew all balances back to wallet`) } + async getVaultInfo(v: VaultInstantiateConfig): Promise { + const client = new MarsMockVaultQueryClient(this.cwClient, v.vault.address) + return { + tokens: await client.info(), + lockup: await this.getLockup(v), + } + } + + async getLockup(v: VaultInstantiateConfig): Promise { + try { + return await this.cwClient.queryContractSmart(v.vault.address, { + vault_extension: { + lockup: { + lockup_duration: {}, + }, + }, + }) + } catch (e) { + return undefined + } + } + private async getAccountBalance(denom: string) { const positions = await this.query.positions({ accountId: this.accountId! }) const coin = positions.coins.find((c) => c.denom === denom) diff --git a/scripts/deploy/base/setupDeployer.ts b/scripts/deploy/base/setupDeployer.ts index 0b2c1447d..83cef77f6 100644 --- a/scripts/deploy/base/setupDeployer.ts +++ b/scripts/deploy/base/setupDeployer.ts @@ -19,18 +19,22 @@ export const getAddress = async (wallet: DirectSecp256k1HdWallet) => { export const setupClient = async (config: DeploymentConfig, wallet: DirectSecp256k1HdWallet) => { const clientOption: SigningCosmWasmClientOptions = { - gasPrice: GasPrice.fromString(`${config.defaultGasPrice}${config.baseDenom}`), + gasPrice: GasPrice.fromString(`${config.chain.defaultGasPrice}${config.chain.baseDenom}`), } - return await SigningCosmWasmClient.connectWithSigner(config.rpcEndpoint, wallet, clientOption) + return await SigningCosmWasmClient.connectWithSigner( + config.chain.rpcEndpoint, + wallet, + clientOption, + ) } export const setupDeployer = async (config: DeploymentConfig) => { - const wallet = await getWallet(config.deployerMnemonic, config.chainPrefix) + const wallet = await getWallet(config.deployerMnemonic, config.chain.prefix) const client = await setupClient(config, wallet) const addr = await getAddress(wallet) - const balance = await client.getBalance(addr, config.baseDenom) + const balance = await client.getBalance(addr, config.chain.baseDenom) printGray(`Deployer addr: ${addr}, balance: ${parseInt(balance.amount) / 1e6} ${balance.denom}`) - const storage = await Storage.load(config.chainId) + const storage = await Storage.load(config.chain.id) return new Deployer(config, client, addr, storage) } diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 9d6340261..55819843b 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -1,55 +1,97 @@ import { DeploymentConfig, VaultType } from '../../types/config' +const uosmo = 'uosmo' const uatom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2' const udig = 'ibc/307E5C96C8F60D1CBEE269A9A86C0834E1DB06F2B3788AE4F716EDB97A48B97D' const ucro = 'ibc/E6931F78057F7CC5DA0FD6CEF82FF39373A6E0452BF1FD76910B93292CF356C1' +const gammPool1 = 'gamm/pool/1' + +const autoCompoundingVault = 'osmo1lcnpd5000ru7qpd0tz8wnl00rlfvlxvqlw04md9cxsudapd0flvsqke5t5' export const osmosisTestnetConfig: DeploymentConfig = { - // Get the latest addresses from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-4.json - oracleAddr: 'osmo1hkkx42777dyfz7wc8acjjhfdh9x2ugcjvdt7shtft6ha9cn420cquz3u3j', - redBankAddr: 'osmo1g30recyv8pfy3qd4qn3dn7plc0rn5z68y5gn32j39e96tjhthzxsw3uvvu', - baseDenom: 'uosmo', - secondaryDenom: uatom, - chainId: 'osmo-test-4', - chainPrefix: 'osmo', + allowedCoins: [uosmo, uatom, ucro, gammPool1], + chain: { + baseDenom: uosmo, + defaultGasPrice: 0.1, + id: 'osmo-test-4', + prefix: 'osmo', + rpcEndpoint: 'https://rpc-test.osmosis.zone', + }, deployerMnemonic: 'rely wonder join knock during sudden slow plate segment state agree also arrest mandate grief ordinary lonely lawsuit hurt super banana rule velvet cart', - redBankDeployerMnemonic: - 'elevator august inherit simple buddy giggle zone despair marine rich swim danger blur people hundred faint ladder wet toe strong blade utility trial process', - rpcEndpoint: 'https://rpc-test.osmosis.zone', - defaultGasPrice: 0.1, - startingAmountForTestUser: 2e6, - vaultTokenDenom: udig, - vaultLockup: { time: 86400 }, // 1 day - maxCloseFactor: 0.6, - depositAmount: 100, - toGrantCreditLines: [ - { denom: 'uosmo', amount: '100000000000' }, - { denom: uatom, amount: '100000000000' }, + maxCloseFactor: '0.6', + maxUnlockingPositions: '10', + maxValueForBurn: '1000000', + // Get the latest addresses from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-4.json + oracle: { + addr: 'osmo1hkkx42777dyfz7wc8acjjhfdh9x2ugcjvdt7shtft6ha9cn420cquz3u3j', + vaultPricing: [ + { + addr: autoCompoundingVault, + base_denom: gammPool1, + method: 'preview_redeem', + vault_coin_denom: + 'factory/osmo1lcnpd5000ru7qpd0tz8wnl00rlfvlxvqlw04md9cxsudapd0flvsqke5t5/cwVTT', + }, + ], + }, + redBank: { addr: 'osmo1g30recyv8pfy3qd4qn3dn7plc0rn5z68y5gn32j39e96tjhthzxsw3uvvu' }, + swapRoutes: [ + { denomIn: uosmo, denomOut: uatom, route: [{ token_out_denom: uatom, pool_id: '1' }] }, ], - borrowAmount: 10, - repayAmount: 8, - swapAmount: 12, - swapRoute: [ + zapper: { addr: 'osmo150dpk65f6deunksn94xtvu249hnr2hwqe335ukucltlwh3uz87hq898s7q' }, + vaults: [ { - token_out_denom: uatom, - pool_id: '1', + // https://github.com/apollodao/apollo-config/blob/master/config.json#L114 + vault: { address: autoCompoundingVault }, + config: { + deposit_cap: { denom: uosmo, amount: '1000000000' }, + liquidation_threshold: '0.75', + max_ltv: '0.65', + whitelisted: true, + }, }, ], - slippage: 0.4, - withdrawAmount: 12, - vaultDepositAmount: 10, - vaultDepositCap: { denom: 'uosmo', amount: '100000000000' }, - vaultMaxLTV: 0.65, - vaultLiquidationThreshold: 0.75, - vaultType: VaultType.LOCKED, - vaultWithdrawAmount: 1_000_000, - lpToken: { denom: ucro, price: 3 }, - zap: [ - { denom: uatom, amount: 3, price: 2.135 }, - { denom: 'uosmo', amount: 3, price: 1 }, - ], - unzapAmount: 1000000, - maxValueForBurn: 1000000, - maxUnlockingPositions: 10, + testActions: { + vault: { + depositAmount: '1000000', + withdrawAmount: '1', + mock: { + config: { + deposit_cap: { denom: uosmo, amount: '100000000000' }, + liquidation_threshold: '0.75', + max_ltv: '0.65', + whitelisted: true, + }, + vaultTokenDenom: udig, + type: VaultType.LOCKED, + lockup: { time: 3600 }, // 1 hour + baseToken: { denom: ucro, price: '3' }, + }, + }, + outpostsDeployerMnemonic: + 'elevator august inherit simple buddy giggle zone despair marine rich swim danger blur people hundred faint ladder wet toe strong blade utility trial process', + borrowAmount: '10', + repayAmount: '11', + defaultCreditLine: '100000000000', + depositAmount: '100', + secondaryDenom: uatom, + startingAmountForTestUser: '2000000', + swap: { + slippage: '0.4', + amount: '40', + route: [ + { + token_out_denom: uatom, + pool_id: '1', + }, + ], + }, + unzapAmount: '1000000', + withdrawAmount: '12', + zap: [ + { denom: uatom, amount: '1', price: '2.135' }, + { denom: uosmo, amount: '3', price: '1' }, + ], + }, } diff --git a/scripts/types/config.ts b/scripts/types/config.ts index a69809d05..2a93dec1f 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -1,48 +1,69 @@ -import { Duration } from './generated/mars-mock-vault/MarsMockVault.types' +import { Duration, VaultInfoResponse } from './generated/mars-mock-vault/MarsMockVault.types' +import { + VaultConfig, + VaultInstantiateConfig, +} from './generated/mars-credit-manager/MarsCreditManager.types' +import { VaultPricingInfo } from './generated/mars-oracle-adapter/MarsOracleAdapter.types' export enum VaultType { LOCKED, UNLOCKED, } +export type VaultInfo = { lockup: { time: number } | undefined; tokens: VaultInfoResponse } + export interface DeploymentConfig { - oracleAddr: string - redBankAddr: string - baseDenom: string - secondaryDenom: string - chainPrefix: string - rpcEndpoint: string + chain: { + prefix: string + id: string + rpcEndpoint: string + defaultGasPrice: number + baseDenom: string + } deployerMnemonic: string - redBankDeployerMnemonic: string - vaultTokenDenom: string - vaultLockup?: Duration - chainId: string - defaultGasPrice: number - startingAmountForTestUser: number - depositAmount: number - toGrantCreditLines: { - amount: string - denom: string - }[] - borrowAmount: number - repayAmount: number - swapAmount: number - slippage: number - swapRoute: { token_out_denom: string; pool_id: string }[] - withdrawAmount: number - maxCloseFactor: number - vaultType: VaultType - vaultDepositAmount: number - vaultDepositCap: { + oracle: { addr: string; vaultPricing: VaultPricingInfo[] } + redBank: { addr: string } + zapper: { addr: string } + vaults: VaultInstantiateConfig[] + allowedCoins: string[] + maxCloseFactor: string + maxValueForBurn: string + maxUnlockingPositions: string + swapRoutes: SwapRoute[] + testActions?: TestActions +} + +export interface SwapRoute { + denomIn: string + denomOut: string + route: { token_out_denom: string; pool_id: string }[] +} + +export interface TestActions { + vault: { + depositAmount: string + withdrawAmount: string + mock: { + type: VaultType + config: VaultConfig + vaultTokenDenom: string + lockup?: Duration + baseToken: { denom: string; price: string } + } + } + outpostsDeployerMnemonic: string + secondaryDenom: string + defaultCreditLine: string + startingAmountForTestUser: string + depositAmount: string + borrowAmount: string + repayAmount: string + swap: { amount: string - denom: string + slippage: string + route: { token_out_denom: string; pool_id: string }[] } - vaultLiquidationThreshold: number - vaultMaxLTV: number - vaultWithdrawAmount: number - lpToken: { denom: string; price: number } - zap: { amount: number; denom: string; price: number }[] - unzapAmount: number - maxValueForBurn: number - maxUnlockingPositions: number + withdrawAmount: string + zap: { amount: string; denom: string; price: string }[] + unzapAmount: string } diff --git a/scripts/types/instantiateMsgs.ts b/scripts/types/instantiateMsgs.ts index e1b147cb1..b8d67abd6 100644 --- a/scripts/types/instantiateMsgs.ts +++ b/scripts/types/instantiateMsgs.ts @@ -4,7 +4,6 @@ import { InstantiateMsg as VaultInstantiateMsg } from './generated/mars-mock-vau import { InstantiateMsg as OracleInstantiateMsg } from './generated/mars-mock-oracle/MarsMockOracle.types' import { InstantiateMsg as RoverInstantiateMsg } from './generated/mars-credit-manager/MarsCreditManager.types' import { InstantiateMsg as SwapperInstantiateMsg } from './generated/mars-swapper-base/MarsSwapperBase.types' -import { InstantiateMsg as ZapperInstantiateMsg } from './generated/mars-mock-zapper/MarsMockZapper.types' export type InstantiateMsgs = | NftInstantiateMsg @@ -13,4 +12,3 @@ export type InstantiateMsgs = | OracleInstantiateMsg | RoverInstantiateMsg | SwapperInstantiateMsg - | ZapperInstantiateMsg diff --git a/scripts/types/storageItems.ts b/scripts/types/storageItems.ts index 6201c1934..9db81f250 100644 --- a/scripts/types/storageItems.ts +++ b/scripts/types/storageItems.ts @@ -6,7 +6,6 @@ export interface StorageItems { mockOracle?: number marsOracleAdapter?: number swapper?: number - mockZapper?: number creditManager?: number } addresses: { @@ -14,15 +13,13 @@ export interface StorageItems { mockVault?: string marsOracleAdapter?: string swapper?: string - mockZapper?: string creditManager?: string } actions: { proposedNewOwner?: boolean acceptedOwnership?: boolean - setRoute?: boolean + setRoutes?: boolean seedMockVault?: boolean - seedMockZapper?: boolean grantedCreditLines?: boolean oraclePricesSet?: boolean redBankMarketsSet?: boolean From 77e3e427b04f08dcdb3226037d0fc2b791887006 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 15 Dec 2022 16:26:15 +0100 Subject: [PATCH 107/218] Update vault token pricing method (#75) * update vault token pricing method * alternative * review updates --- Cargo.lock | 1 + .../credit-manager/tests/test_vault_enter.rs | 2 +- contracts/oracle-adapter/Cargo.toml | 11 +-- contracts/oracle-adapter/src/contract.rs | 16 ++-- .../oracle-adapter/tests/test_query_price.rs | 86 ++++++++++++++++--- scripts/deploy/addresses/osmo-test-4.json | 10 +-- 6 files changed, 94 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0cdd720db..4da40f2dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1053,6 +1053,7 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", + "cosmwasm-vault-standard", "cw-controllers-admin-fork", "cw-multi-test", "cw-storage-plus 1.0.1", diff --git a/contracts/credit-manager/tests/test_vault_enter.rs b/contracts/credit-manager/tests/test_vault_enter.rs index d0416c7cf..03d25e936 100644 --- a/contracts/credit-manager/tests/test_vault_enter.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -401,7 +401,7 @@ fn test_vault_deposit_must_be_under_cap() { assert_err( res, ContractError::AboveVaultDepositCap { - new_value: "32584199.999999999998984572".to_string(), + new_value: "32584199.999999999993270287".to_string(), maximum: "12345000".to_string(), }, ); diff --git a/contracts/oracle-adapter/Cargo.toml b/contracts/oracle-adapter/Cargo.toml index 4ca00e499..43527bb5b 100644 --- a/contracts/oracle-adapter/Cargo.toml +++ b/contracts/oracle-adapter/Cargo.toml @@ -28,8 +28,9 @@ mars-rover = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -anyhow = { workspace = true } -cw-multi-test = { workspace = true } -cw-utils = { workspace = true } -mars-mock-oracle = { workspace = true } -mars-mock-vault = { workspace = true } +anyhow = { workspace = true } +cw-multi-test = { workspace = true } +cw-utils = { workspace = true } +mars-mock-oracle = { workspace = true } +mars-mock-vault = { workspace = true } +cosmwasm-vault-standard = { workspace = true } diff --git a/contracts/oracle-adapter/src/contract.rs b/contracts/oracle-adapter/src/contract.rs index a3f16b354..4958a39a1 100644 --- a/contracts/oracle-adapter/src/contract.rs +++ b/contracts/oracle-adapter/src/contract.rs @@ -4,14 +4,13 @@ use cosmwasm_std::{ to_binary, Addr, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, }; use cw2::set_contract_version; -use cw_controllers_admin_fork::AdminInit::SetInitialAdmin; use cw_storage_plus::Bound; +use cw_controllers_admin_fork::AdminInit::SetInitialAdmin; use cw_controllers_admin_fork::AdminUpdate; use mars_outpost::oracle::PriceResponse; use mars_rover::adapters::vault::VaultBase; use mars_rover::adapters::Oracle; -use mars_rover::traits::IntoDecimal; use crate::error::ContractResult; use crate::msg::{ @@ -123,15 +122,14 @@ fn calculate_preview_redeem( info: &VaultPricingInfo, vault: &VaultBase, ) -> ContractResult { - let total_issued = vault.query_total_vault_coins_issued(&deps.querier)?; - let amount = vault.query_preview_redeem(&deps.querier, total_issued)?; - let price_res = oracle.query_price(&deps.querier, &info.base_denom)?; - let value = price_res.price.checked_mul(amount.to_dec()?)?; - - let price = if value.is_zero() || total_issued.is_zero() { + let vault_coin_supply = vault.query_total_vault_coins_issued(&deps.querier)?; + let price = if vault_coin_supply.is_zero() { Decimal::zero() } else { - value.checked_div(total_issued.to_dec()?)? + let total_underlying = vault.query_preview_redeem(&deps.querier, vault_coin_supply)?; + let underlying_per_vault_coin = Decimal::from_ratio(total_underlying, vault_coin_supply); + let price_res = oracle.query_price(&deps.querier, &info.base_denom)?; + price_res.price.checked_mul(underlying_per_vault_coin)? }; Ok(PriceResponse { diff --git a/contracts/oracle-adapter/tests/test_query_price.rs b/contracts/oracle-adapter/tests/test_query_price.rs index dc6a32720..ce9c784c6 100644 --- a/contracts/oracle-adapter/tests/test_query_price.rs +++ b/contracts/oracle-adapter/tests/test_query_price.rs @@ -1,11 +1,11 @@ use std::ops::{Div, Mul}; -use cosmwasm_std::{Decimal, Uint128}; +use cosmwasm_std::{Empty, Uint128}; +use cosmwasm_vault_standard::VaultStandardQueryMsg::{PreviewRedeem, TotalVaultTokenSupply}; use cw_multi_test::App; -use mars_outpost::oracle::PriceResponse; -use mars_mock_vault::contract::STARTING_VAULT_SHARES; -use mars_oracle_adapter::msg::QueryMsg; +use mars_oracle_adapter::msg::{ConfigResponse, QueryMsg, VaultPricingInfo}; +use mars_outpost::oracle::PriceResponse; use mars_rover::traits::IntoDecimal; use crate::helpers::{instantiate_oracle_adapter, mock_vault_info}; @@ -17,6 +17,21 @@ fn test_non_vault_coin_priced() { let mut app = App::default(); let contract_addr = instantiate_oracle_adapter(&mut app); + let config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + let uosmo_oracle_res: PriceResponse = app + .wrap() + .query_wasm_smart( + config.oracle.address().to_string(), + &QueryMsg::Price { + denom: "uosmo".to_string(), + }, + ) + .unwrap(); + let res: PriceResponse = app .wrap() .query_wasm_smart( @@ -27,7 +42,7 @@ fn test_non_vault_coin_priced() { ) .unwrap(); - assert_eq!(res.price, Decimal::from_atomics(25u128, 2).unwrap()) + assert_eq!(res.price, uosmo_oracle_res.price); } #[test] @@ -36,21 +51,68 @@ fn test_vault_coin_preview_redeem() { let contract_addr = instantiate_oracle_adapter(&mut app); let vault_info = mock_vault_info(); - let res: PriceResponse = app + let vault_info: VaultPricingInfo = app .wrap() .query_wasm_smart( contract_addr.to_string(), - &QueryMsg::Price { + &QueryMsg::PricingInfo { denom: vault_info.vault_coin_denom, }, ) .unwrap(); - let lp_token_price = Decimal::from_atomics(8745u128, 2).unwrap(); - let lp_token_in_vault = Uint128::new(120_042); - let vaults_value = lp_token_in_vault.to_dec().unwrap().mul(lp_token_price); + let vault_token_supply: Uint128 = app + .wrap() + .query_wasm_smart(vault_info.addr.clone(), &TotalVaultTokenSupply:: {}) + .unwrap(); - let price_per_vault_coin = vaults_value.div(STARTING_VAULT_SHARES); + let total_lp_tokens: Uint128 = app + .wrap() + .query_wasm_smart( + vault_info.addr, + &PreviewRedeem:: { + amount: vault_token_supply, + }, + ) + .unwrap(); + + let config: ConfigResponse = app + .wrap() + .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) + .unwrap(); + + let lp_token_oracle_res: PriceResponse = app + .wrap() + .query_wasm_smart( + config.oracle.address().to_string(), + &QueryMsg::Price { + denom: vault_info.base_denom.clone(), + }, + ) + .unwrap(); + + let total_value_of_vault = total_lp_tokens + .to_dec() + .unwrap() + .mul(lp_token_oracle_res.price); + + let price_per_vault_coin = total_value_of_vault.div(vault_token_supply); + + let oracle_adapter_res: PriceResponse = app + .wrap() + .query_wasm_smart( + contract_addr.to_string(), + &QueryMsg::Price { + denom: vault_info.vault_coin_denom, + }, + ) + .unwrap(); - assert_eq!(res.price, price_per_vault_coin) + // vault token price = total lp tokens in vault * price of lp token / total vault tokens issued + // This formula can't be used in production because the first multiplication results in an + // integer that exceeds the memory allocated to u128's. But for this test it's a good check + // on our current formula where we use: + // Decimal::from_ratio(total_underlying, vault_coin_supply) * price of lp token + // This method does not cause an overflow given Decimal::from_ratio casts to u256 + assert_eq!(oracle_adapter_res.price, price_per_vault_coin) } diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index d84bb475a..73dbef039 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,7 @@ { - "mockVault": "osmo1206a57paps5vy2qz38mdywn8shwjlugwtgk0zfmgjv3uy28pkm4q5qegay", - "marsOracleAdapter": "osmo1rlm6c73ymcnutmfe5dhvnu2ppc3dwnaxx7mcexlerm8ew5je23hqtgyscz", - "swapper": "osmo13spujdjfep3xrreaz6ka09nk0929w7dye82lwxda0rq3yl2cp3dsc756m0", - "creditManager": "osmo1ggar740wj9qmsml05s8uq3pv79jxtxplzpuz0l9mmpr027683fqsnmlwvj", - "accountNft": "osmo19d6japwr5rvfcn8nwgfkzhmjyk42pd6m473mtxlqukcyn8zzt3ds06jycg" + "mockVault": "osmo1ka5k5pq5qjachtwpj46m7p287zr7dezvhu8wgrln6c44wkly27yq7dp0fs", + "marsOracleAdapter": "osmo13l9k608jatsd7f3kmqpdcc9003740pz8selzjclskl232qr49asqfh84cx", + "swapper": "osmo1f6gs2t5yv3k4hyazlg2g9zsmyw7ryug7g8eg349v3f2fak2s564q5nwq6p", + "creditManager": "osmo1q0s3pzhlm84uhe4akwq9yw8qqj26vea4pxmjffrpkuqumk34k39s2dxhy6", + "accountNft": "osmo1wws76kv7x9sxcmqeww3246t7mppqrjhyvcxkyyuawfgtvekne4cqxtkva7" } From 022665132b02b4e2d0eb95af70831d6f54cbea28 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 15 Dec 2022 19:47:10 +0100 Subject: [PATCH 108/218] Updating outposts package (#77) Updating outpost package --- packages/outpost/src/red_bank/msg.rs | 3 --- .../mars-mock-red-bank/mars-mock-red-bank.json | 11 ----------- scripts/deploy/addresses/osmo-test-4.json | 10 +++++----- scripts/deploy/base/deployer.ts | 7 ++++--- scripts/deploy/base/rover.ts | 4 ++-- scripts/deploy/osmosis/config.ts | 15 +++++++++------ scripts/types/config.ts | 5 ++++- .../mars-mock-red-bank/MarsMockRedBank.types.ts | 1 - 8 files changed, 24 insertions(+), 32 deletions(-) diff --git a/packages/outpost/src/red_bank/msg.rs b/packages/outpost/src/red_bank/msg.rs index 0ebb917d1..61d4e0aec 100644 --- a/packages/outpost/src/red_bank/msg.rs +++ b/packages/outpost/src/red_bank/msg.rs @@ -110,9 +110,6 @@ pub struct CreateOrUpdateConfig { #[cw_serde] pub struct InitOrUpdateAssetParams { - /// Initial borrow rate - pub initial_borrow_rate: Option, - /// Portion of the borrow rate that is kept as protocol rewards pub reserve_factor: Option, /// Max uusd that can be borrowed per uusd of collateral when using the asset as collateral diff --git a/schemas/mars-mock-red-bank/mars-mock-red-bank.json b/schemas/mars-mock-red-bank/mars-mock-red-bank.json index 71e2d6539..1b5de8439 100644 --- a/schemas/mars-mock-red-bank/mars-mock-red-bank.json +++ b/schemas/mars-mock-red-bank/mars-mock-red-bank.json @@ -427,17 +427,6 @@ "null" ] }, - "initial_borrow_rate": { - "description": "Initial borrow rate", - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, "interest_rate_model": { "description": "Interest rate strategy to calculate borrow_rate and liquidity_rate", "anyOf": [ diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 73dbef039..12bc899a9 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,7 @@ { - "mockVault": "osmo1ka5k5pq5qjachtwpj46m7p287zr7dezvhu8wgrln6c44wkly27yq7dp0fs", - "marsOracleAdapter": "osmo13l9k608jatsd7f3kmqpdcc9003740pz8selzjclskl232qr49asqfh84cx", - "swapper": "osmo1f6gs2t5yv3k4hyazlg2g9zsmyw7ryug7g8eg349v3f2fak2s564q5nwq6p", - "creditManager": "osmo1q0s3pzhlm84uhe4akwq9yw8qqj26vea4pxmjffrpkuqumk34k39s2dxhy6", - "accountNft": "osmo1wws76kv7x9sxcmqeww3246t7mppqrjhyvcxkyyuawfgtvekne4cqxtkva7" + "mockVault": "osmo1ufuvsfpe9ftgds88xer2wjdfmp09qp0sxn0wt7welymz96dyg2pqwjf5mn", + "marsOracleAdapter": "osmo1x56vjr09ujzacqs2wkhx7ut5ujt9lcuznmfp9cw7kx8hsxur78yqk0dz5x", + "swapper": "osmo1srymstravhmwh2mne2h67zz9ppy7ffa5rwljuh3q2mf82eflpgus9skwqg", + "creditManager": "osmo13pxyw9uhcvt85jv0w84aynnjnv6ucdatx9h52tslp2mgh9r2rw7qulpg54", + "accountNft": "osmo1e822j5pjmycdhzt4c7m0juuknmkv723gzaggzts90svr8tgzvywqeegxam" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index dc917fd64..75a8c8b9d 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -265,7 +265,8 @@ export class Deployer { const { client, addr } = await this.getOutpostsDeployer() for (const coin of this.config - .testActions!.zap.map((c) => ({ denom: c.denom, price: c.price })) + .testActions!.zap.coinsIn.map((c) => ({ denom: c.denom, price: c.price })) + .concat(this.config.testActions!.zap.denomOut) .concat(this.config.testActions!.vault.mock.baseToken)) { try { await client.queryContractSmart(this.config.oracle.addr, { @@ -298,7 +299,8 @@ export class Deployer { const { client, addr } = await this.getOutpostsDeployer() for (const denom of this.config - .testActions!.zap.map((c) => c.denom) + .testActions!.zap.coinsIn.map((c) => c.denom) + .concat(this.config.testActions!.zap.denomOut.denom) .concat(this.config.testActions!.vault.mock.baseToken.denom)) { try { await client.queryContractSmart(this.config.redBank.addr, { @@ -317,7 +319,6 @@ export class Deployer { init_asset: { denom, params: { - initial_borrow_rate: '0.1', max_loan_to_value: '0.65', reserve_factor: '0.2', liquidation_threshold: '0.7', diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index f8cd8e955..50fcc1187 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -123,7 +123,7 @@ export class Rover { await this.updateCreditAccount([ { provide_liquidity: { - coins_in: this.actions.zap.map((c) => ({ denom: c.denom, amount: c.amount })), + coins_in: this.actions.zap.coinsIn.map((c) => ({ denom: c.denom, amount: c.amount })), lp_token_out, minimum_receive: '1', }, @@ -132,7 +132,7 @@ export class Rover { const positions = await this.query.positions({ accountId: this.accountId! }) const lp_balance = positions.coins.find((c) => c.denom === lp_token_out)!.amount printGreen( - `Zapped ${this.actions.zap + `Zapped ${this.actions.zap.coinsIn .map((c) => c.denom) .join(', ')} for LP token: ${lp_balance} ${lp_token_out}`, ) diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 55819843b..05d6a048e 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -24,7 +24,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { maxValueForBurn: '1000000', // Get the latest addresses from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-4.json oracle: { - addr: 'osmo1hkkx42777dyfz7wc8acjjhfdh9x2ugcjvdt7shtft6ha9cn420cquz3u3j', + addr: 'osmo1jnkun9gcajn96a4yh7atzkq98c9sm0xrsqk7xtes07ujyn7xh5rqjymxxv', vaultPricing: [ { addr: autoCompoundingVault, @@ -35,7 +35,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { }, ], }, - redBank: { addr: 'osmo1g30recyv8pfy3qd4qn3dn7plc0rn5z68y5gn32j39e96tjhthzxsw3uvvu' }, + redBank: { addr: 'osmo18w58j2dlpre6kslls9w88aur5ud8000wvg8pw4fp80p6q97g6qtqvhztpv' }, swapRoutes: [ { denomIn: uosmo, denomOut: uatom, route: [{ token_out_denom: uatom, pool_id: '1' }] }, ], @@ -89,9 +89,12 @@ export const osmosisTestnetConfig: DeploymentConfig = { }, unzapAmount: '1000000', withdrawAmount: '12', - zap: [ - { denom: uatom, amount: '1', price: '2.135' }, - { denom: uosmo, amount: '3', price: '1' }, - ], + zap: { + coinsIn: [ + { denom: uatom, amount: '1', price: '2.135' }, + { denom: uosmo, amount: '3', price: '1' }, + ], + denomOut: { denom: gammPool1, price: '1.75' }, + }, }, } diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 2a93dec1f..af884e8f8 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -64,6 +64,9 @@ export interface TestActions { route: { token_out_denom: string; pool_id: string }[] } withdrawAmount: string - zap: { amount: string; denom: string; price: string }[] + zap: { + coinsIn: { amount: string; denom: string; price: string }[] + denomOut: { denom: string; price: string } + } unzapAmount: string } diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index 6b6cfb3d6..cb4cf4c09 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -87,7 +87,6 @@ export interface InitOrUpdateAssetParams { borrow_enabled?: boolean | null deposit_cap?: Uint128 | null deposit_enabled?: boolean | null - initial_borrow_rate?: Decimal | null interest_rate_model?: InterestRateModel | null liquidation_bonus?: Decimal | null liquidation_threshold?: Decimal | null From a9d1d2e8cb6093225f04372de6e5f8643dc4bcc9 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 15 Dec 2022 21:55:55 +0100 Subject: [PATCH 109/218] shorter lockup + redeploy (#78) --- scripts/deploy/addresses/osmo-test-4.json | 10 +++++----- scripts/deploy/osmosis/config.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 12bc899a9..29de20aae 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,7 @@ { - "mockVault": "osmo1ufuvsfpe9ftgds88xer2wjdfmp09qp0sxn0wt7welymz96dyg2pqwjf5mn", - "marsOracleAdapter": "osmo1x56vjr09ujzacqs2wkhx7ut5ujt9lcuznmfp9cw7kx8hsxur78yqk0dz5x", - "swapper": "osmo1srymstravhmwh2mne2h67zz9ppy7ffa5rwljuh3q2mf82eflpgus9skwqg", - "creditManager": "osmo13pxyw9uhcvt85jv0w84aynnjnv6ucdatx9h52tslp2mgh9r2rw7qulpg54", - "accountNft": "osmo1e822j5pjmycdhzt4c7m0juuknmkv723gzaggzts90svr8tgzvywqeegxam" + "mockVault": "osmo15zg6kyfcuwfx6zrc22xnqj5sg4qtvp5rfr5mnxek5fg83e9mp4yszzlw93", + "marsOracleAdapter": "osmo123dg9q7vcrqmffc9lz4nqrg9ewzqqdggqf0pgufjqpay4mv4mljqvx0xwv", + "swapper": "osmo1qh78u9yhvad7ljmgm898lq238a5emt7xkjd4znjmq0zxv5e6f79qfgqedx", + "creditManager": "osmo1ecdduxnyuqw45hyhf6raam73ehl7zkkmxxne3qsxk4nxgm0tzwsq2433xp", + "accountNft": "osmo174unkahyj4tzt7z54h25pdjfgg9kyyfqagyf2jyh9z9v5524s7tqh5rptc" } diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 05d6a048e..9cd5179c4 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -65,7 +65,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { }, vaultTokenDenom: udig, type: VaultType.LOCKED, - lockup: { time: 3600 }, // 1 hour + lockup: { time: 900 }, // 15 mins baseToken: { denom: ucro, price: '3' }, }, }, From 6b9d0ad9c4d1df3086590291ab3b088fc621b8ac Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 16 Dec 2022 12:58:42 +0100 Subject: [PATCH 110/218] New mock lp token (#79) --- scripts/deploy/addresses/osmo-test-4.json | 10 +++++----- scripts/deploy/osmosis/config.ts | 5 ++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 29de20aae..1ea87aeb3 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,7 @@ { - "mockVault": "osmo15zg6kyfcuwfx6zrc22xnqj5sg4qtvp5rfr5mnxek5fg83e9mp4yszzlw93", - "marsOracleAdapter": "osmo123dg9q7vcrqmffc9lz4nqrg9ewzqqdggqf0pgufjqpay4mv4mljqvx0xwv", - "swapper": "osmo1qh78u9yhvad7ljmgm898lq238a5emt7xkjd4znjmq0zxv5e6f79qfgqedx", - "creditManager": "osmo1ecdduxnyuqw45hyhf6raam73ehl7zkkmxxne3qsxk4nxgm0tzwsq2433xp", - "accountNft": "osmo174unkahyj4tzt7z54h25pdjfgg9kyyfqagyf2jyh9z9v5524s7tqh5rptc" + "mockVault": "osmo1487e354fslk684alf5d2aewucaxts6cjp7sxah085qcvyla2w9ns376wgm", + "marsOracleAdapter": "osmo14mmr2teztgs9w3vp3q87ycaaulf78x5k7re85zj9qy93ays0m7vsapuykz", + "swapper": "osmo1jl3sh8mhtgfwjea5plvxe3suu2ywq5k8qmzx8u3w85gkkqynjmpq65e2sj", + "creditManager": "osmo160hkkaddp2j2ph5gw35mecnrntr0tcdml9he38dw22f3e5alne4quu7vjy", + "accountNft": "osmo1t86ljlx9s70jucxntdhyg829hvqw2d3l9dkcsewsp05llmgmk4fs3jsr8v" } diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 9cd5179c4..68331d4c1 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -3,13 +3,12 @@ import { DeploymentConfig, VaultType } from '../../types/config' const uosmo = 'uosmo' const uatom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2' const udig = 'ibc/307E5C96C8F60D1CBEE269A9A86C0834E1DB06F2B3788AE4F716EDB97A48B97D' -const ucro = 'ibc/E6931F78057F7CC5DA0FD6CEF82FF39373A6E0452BF1FD76910B93292CF356C1' const gammPool1 = 'gamm/pool/1' const autoCompoundingVault = 'osmo1lcnpd5000ru7qpd0tz8wnl00rlfvlxvqlw04md9cxsudapd0flvsqke5t5' export const osmosisTestnetConfig: DeploymentConfig = { - allowedCoins: [uosmo, uatom, ucro, gammPool1], + allowedCoins: [uosmo, uatom, gammPool1], chain: { baseDenom: uosmo, defaultGasPrice: 0.1, @@ -66,7 +65,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { vaultTokenDenom: udig, type: VaultType.LOCKED, lockup: { time: 900 }, // 15 mins - baseToken: { denom: ucro, price: '3' }, + baseToken: { denom: gammPool1, price: '1.75' }, }, }, outpostsDeployerMnemonic: From 0458e95e451a312e76e452a46dc2049ea809a8ee Mon Sep 17 00:00:00 2001 From: piobab Date: Tue, 20 Dec 2022 10:10:40 +0100 Subject: [PATCH 111/218] Init contracts with owner / admin. (#80) --- scripts/deploy/base/deployer.ts | 5 +++++ scripts/deploy/base/index.ts | 3 +++ scripts/deploy/base/storage.ts | 2 ++ scripts/types/storageItems.ts | 1 + 4 files changed, 11 insertions(+) diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 75a8c8b9d..cd462caff 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -48,6 +48,10 @@ export class Deployer { printGreen(`${this.config.chain.id} :: ${name} : ${this.storage.codeIds[name]}`) } + setOwnerAddr() { + this.storage.owner = this.deployerAddr + } + async instantiate(name: keyof Storage['addresses'], codeId: number, msg: InstantiateMsgs) { if (this.storage.addresses[name]) { printGray(`Contract already instantiated :: ${name} :: ${this.storage.addresses[name]}`) @@ -59,6 +63,7 @@ export class Deployer { msg, `mars-${name}`, 'auto', + { admin: this.storage.owner }, ) this.storage.addresses[name] = contractAddress printGreen( diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 4a43d388c..e2261ad97 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -18,6 +18,9 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp await deployer.upload('swapper', wasmFile(swapperContractName)) await deployer.upload('creditManager', wasmFile('mars_credit_manager')) + // Set contracts owner + deployer.setOwnerAddr() + // Instantiate contracts await deployer.instantiateMockVault() await deployer.instantiateMarsOracleAdapter() diff --git a/scripts/deploy/base/storage.ts b/scripts/deploy/base/storage.ts index fb0c52c3f..3a045c355 100644 --- a/scripts/deploy/base/storage.ts +++ b/scripts/deploy/base/storage.ts @@ -8,11 +8,13 @@ export class Storage implements StorageItems { public addresses: StorageItems['addresses'] public codeIds: StorageItems['codeIds'] public actions: StorageItems['actions'] + public owner: StorageItems['owner'] constructor(private chainId: string, items: StorageItems) { this.addresses = items.addresses this.codeIds = items.codeIds this.actions = items.actions + this.owner = items.owner } static async load(chainId: string): Promise { diff --git a/scripts/types/storageItems.ts b/scripts/types/storageItems.ts index 9db81f250..d176864b8 100644 --- a/scripts/types/storageItems.ts +++ b/scripts/types/storageItems.ts @@ -24,4 +24,5 @@ export interface StorageItems { oraclePricesSet?: boolean redBankMarketsSet?: boolean } + owner?: string } From a11794df2a626824865b8739c4796aeb163fb2dd Mon Sep 17 00:00:00 2001 From: piobab Date: Wed, 28 Dec 2022 14:10:24 +0100 Subject: [PATCH 112/218] Mp 1814 migrate zapper (#74) * Init liquidity-helper (zapper) with code from https://github.com/apollodao/liquidity-helper * Use zapper naming. * Test cases for estimate. * Provide liquidity tests. * Withdraw liquidity tests. * Bump deps for cw-dex and cw-asset. * Improve tests. * Cleanup Cargo.toml. * Remove comment. * Extract generic structure. * Review comments. * Provide unbalanced coins. * Refactor. * Remove cw dep. * Update schema. * Update zapper scripts. * New deploy. * Ignore osmosis-testing tests for swapper and zapper. * Move zapper mock. * Fix builds. * Mp 1814 simplify zapper (#81) * Use ActionCoin for Repay, EnterVault, SwapExactIn, ProvideLiquidity and WithdrawLiquidity. * Update schema. * Use only balanced coins and return remaining coins to recipient. * Update types. * Update addresses. * Rename to "Exact" and update callback params Co-authored-by: Gabe Rodriguez --- Cargo.lock | 193 ++++++- Cargo.toml | 5 +- contracts/credit-manager/Cargo.toml | 2 +- contracts/credit-manager/src/execute.rs | 70 +-- .../credit-manager/src/liquidate_coin.rs | 3 +- contracts/credit-manager/src/repay.rs | 22 +- contracts/credit-manager/src/swap.rs | 16 +- contracts/credit-manager/src/vault/enter.rs | 33 +- contracts/credit-manager/src/zap.rs | 36 +- .../credit-manager/tests/helpers/contracts.rs | 6 +- .../credit-manager/tests/helpers/types.rs | 17 +- .../test_enumerate_vault_coin_balances.rs | 5 +- .../tests/test_enumerate_vault_positions.rs | 5 +- .../tests/test_fields_vault_limit.rs | 8 +- .../tests/test_liquidate_coin.rs | 3 +- .../tests/test_liquidate_vault.rs | 24 +- .../tests/test_refund_balances.rs | 3 +- contracts/credit-manager/tests/test_repay.rs | 45 +- contracts/credit-manager/tests/test_swap.rs | 25 +- .../credit-manager/tests/test_vault_enter.rs | 40 +- .../credit-manager/tests/test_vault_exit.rs | 3 +- .../tests/test_vault_exit_unlocked.rs | 18 +- .../tests/test_vault_request_unlock.rs | 9 +- .../credit-manager/tests/test_zap_provide.rs | 29 +- .../credit-manager/tests/test_zap_withdraw.rs | 42 +- contracts/zapper/base/Cargo.toml | 30 + contracts/zapper/base/examples/schema.rs | 10 + contracts/zapper/base/src/contract.rs | 268 +++++++++ contracts/zapper/base/src/error.rs | 22 + contracts/zapper/base/src/helpers.rs | 68 +++ contracts/zapper/base/src/lib.rs | 9 + contracts/zapper/base/src/msg.rs | 48 ++ contracts/zapper/base/src/traits.rs | 11 + .../{mock-zapper => zapper/mock}/Cargo.toml | 2 +- .../mock}/examples/schema.rs | 0 .../mock}/src/contract.rs | 0 .../{mock-zapper => zapper/mock}/src/error.rs | 0 .../mock}/src/execute.rs | 0 .../{mock-zapper => zapper/mock}/src/lib.rs | 0 .../{mock-zapper => zapper/mock}/src/query.rs | 0 .../{mock-zapper => zapper/mock}/src/state.rs | 0 contracts/zapper/osmosis/Cargo.toml | 30 + contracts/zapper/osmosis/src/contract.rs | 40 ++ contracts/zapper/osmosis/src/lib.rs | 2 + contracts/zapper/osmosis/src/lp_pool.rs | 43 ++ contracts/zapper/osmosis/tests/helpers.rs | 63 +++ .../zapper/osmosis/tests/test_callback.rs | 41 ++ .../osmosis/tests/test_provide_liquidity.rs | 528 ++++++++++++++++++ .../zapper/osmosis/tests/test_queries.rs | 178 ++++++ .../osmosis/tests/test_withdraw_liquidity.rs | 325 +++++++++++ coverage_grcov.Makefile.toml | 3 +- packages/rover/src/coins.rs | 7 + packages/rover/src/msg/execute.rs | 76 ++- schema.Makefile.toml | 2 +- .../mars-credit-manager.json | 189 +++---- .../mars-zapper-base.json} | 110 ++-- scripts/deploy/addresses/osmo-test-4.json | 11 +- scripts/deploy/base/deployer.ts | 6 + scripts/deploy/base/index.ts | 10 +- scripts/deploy/base/rover.ts | 21 +- scripts/deploy/osmosis/config.ts | 8 +- scripts/deploy/osmosis/index.ts | 6 +- .../MarsCreditManager.client.ts | 2 + .../MarsCreditManager.message-composer.ts | 2 + .../MarsCreditManager.react-query.ts | 2 + .../MarsCreditManager.types.ts | 39 +- .../generated/mars-oracle-adapter/bundle.ts | 10 +- .../generated/mars-swapper-base/bundle.ts | 10 +- .../MarsZapperBase.client.ts} | 42 +- .../MarsZapperBase.message-composer.ts} | 29 +- .../MarsZapperBase.react-query.ts} | 83 +-- .../MarsZapperBase.types.ts} | 30 +- .../bundle.ts | 10 +- scripts/types/instantiateMsgs.ts | 2 + scripts/types/storageItems.ts | 2 + 75 files changed, 2473 insertions(+), 619 deletions(-) create mode 100644 contracts/zapper/base/Cargo.toml create mode 100644 contracts/zapper/base/examples/schema.rs create mode 100644 contracts/zapper/base/src/contract.rs create mode 100644 contracts/zapper/base/src/error.rs create mode 100644 contracts/zapper/base/src/helpers.rs create mode 100644 contracts/zapper/base/src/lib.rs create mode 100644 contracts/zapper/base/src/msg.rs create mode 100644 contracts/zapper/base/src/traits.rs rename contracts/{mock-zapper => zapper/mock}/Cargo.toml (95%) rename contracts/{mock-zapper => zapper/mock}/examples/schema.rs (100%) rename contracts/{mock-zapper => zapper/mock}/src/contract.rs (100%) rename contracts/{mock-zapper => zapper/mock}/src/error.rs (100%) rename contracts/{mock-zapper => zapper/mock}/src/execute.rs (100%) rename contracts/{mock-zapper => zapper/mock}/src/lib.rs (100%) rename contracts/{mock-zapper => zapper/mock}/src/query.rs (100%) rename contracts/{mock-zapper => zapper/mock}/src/state.rs (100%) create mode 100644 contracts/zapper/osmosis/Cargo.toml create mode 100644 contracts/zapper/osmosis/src/contract.rs create mode 100644 contracts/zapper/osmosis/src/lib.rs create mode 100644 contracts/zapper/osmosis/src/lp_pool.rs create mode 100644 contracts/zapper/osmosis/tests/helpers.rs create mode 100644 contracts/zapper/osmosis/tests/test_callback.rs create mode 100644 contracts/zapper/osmosis/tests/test_provide_liquidity.rs create mode 100644 contracts/zapper/osmosis/tests/test_queries.rs create mode 100644 contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs rename schemas/{mars-mock-zapper/mars-mock-zapper.json => mars-zapper-base/mars-zapper-base.json} (76%) rename scripts/types/generated/{mars-mock-zapper/MarsMockZapper.client.ts => mars-zapper-base/MarsZapperBase.client.ts} (81%) rename scripts/types/generated/{mars-mock-zapper/MarsMockZapper.message-composer.ts => mars-zapper-base/MarsZapperBase.message-composer.ts} (78%) rename scripts/types/generated/{mars-mock-zapper/MarsMockZapper.react-query.ts => mars-zapper-base/MarsZapperBase.react-query.ts} (56%) rename scripts/types/generated/{mars-mock-zapper/MarsMockZapper.types.ts => mars-zapper-base/MarsZapperBase.types.ts} (79%) rename scripts/types/generated/{mars-mock-zapper => mars-zapper-base}/bundle.ts (51%) diff --git a/Cargo.lock b/Cargo.lock index 4da40f2dc..f12a7649c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,17 @@ version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +[[package]] +name = "apollo-utils" +version = "0.1.0" +source = "git+https://github.com/apollodao/apollo-utils.git?rev=bfd1abd8cd9716dccad3e74aeb3704cad9f1f41a#bfd1abd8cd9716dccad3e74aeb3704cad9f1f41a" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-asset", + "cw20", +] + [[package]] name = "async-trait" version = "0.1.59" @@ -336,7 +347,7 @@ checksum = "d48847f2c22b96d9659ff9d0b67403ed1d5cbe5470e65d293c4c455de05b237b" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-utils", + "cw-utils 0.16.0", "schemars", "serde", ] @@ -391,6 +402,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-asset" +version = "0.3.0" +source = "git+https://github.com/apollodao/cw-asset.git?rev=057fb193013ad4adfc25063b99960972d1d208bc#057fb193013ad4adfc25063b99960972d1d208bc" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "cw20", + "schemars", + "serde", +] + [[package]] name = "cw-controllers-admin-fork" version = "1.0.0" @@ -398,12 +422,29 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.0.1", - "cw-utils", + "cw-utils 0.16.0", "schemars", "serde", "thiserror", ] +[[package]] +name = "cw-dex" +version = "0.0.1" +source = "git+https://github.com/apollodao/cw-dex?rev=b9d18cfbba8aa09ae4ee9459d308cda87ae5ffb3#b9d18cfbba8aa09ae4ee9459d308cda87ae5ffb3" +dependencies = [ + "apollo-utils", + "cosmwasm-schema", + "cosmwasm-std", + "cw-asset", + "cw-storage-plus 0.16.0", + "cw-utils 0.11.1", + "cw-utils 0.16.0", + "cw20", + "osmosis-std 0.12.0", + "thiserror", +] + [[package]] name = "cw-item-set" version = "0.7.0" @@ -416,14 +457,14 @@ dependencies = [ [[package]] name = "cw-multi-test" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc50fde3ad87ef4e3a3e57c73d11326333318761c7655cc8cae67c40382ac91" +checksum = "c2eb84554bbfa6b66736abcd6a9bfdf237ee0ecb83910f746dff7f799093c80a" dependencies = [ "anyhow", "cosmwasm-std", - "cw-storage-plus 0.16.0", - "cw-utils", + "cw-storage-plus 1.0.1", + "cw-utils 1.0.1", "derivative", "itertools", "k256", @@ -455,6 +496,18 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-utils" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef842a1792e4285beff7b3b518705f760fa4111dc1e296e53f3e92d1ef7f6220" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw-utils" version = "0.16.0" @@ -470,6 +523,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 1.0.0", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "cw2" version = "0.16.0" @@ -496,6 +564,19 @@ dependencies = [ "serde", ] +[[package]] +name = "cw20" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45a8794a5dd33b66af34caee52a7beceb690856adcc1682b6e3db88b2cdee62" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.16.0", + "schemars", + "serde", +] + [[package]] name = "cw721" version = "0.16.0" @@ -504,7 +585,7 @@ checksum = "94a1ea6e6277bdd6dfc043a9b1380697fe29d6e24b072597439523658d21d791" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-utils", + "cw-utils 0.16.0", "schemars", "serde", ] @@ -518,7 +599,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.16.0", - "cw-utils", + "cw-utils 0.16.0", "cw2 0.16.0", "cw721", "schemars", @@ -964,7 +1045,7 @@ dependencies = [ "cw-item-set", "cw-multi-test", "cw-storage-plus 1.0.1", - "cw-utils", + "cw-utils 0.16.0", "cw2 1.0.0", "cw721", "cw721-base", @@ -974,11 +1055,11 @@ dependencies = [ "mars-mock-oracle", "mars-mock-red-bank", "mars-mock-vault", - "mars-mock-zapper", "mars-oracle-adapter", "mars-outpost", "mars-rover", "mars-swapper-mock", + "mars-zapper-mock", ] [[package]] @@ -996,7 +1077,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.0.1", - "cw-utils", + "cw-utils 0.16.0", "mars-rover", "thiserror", ] @@ -1029,19 +1110,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-vault-standard", "cw-storage-plus 1.0.1", - "cw-utils", - "mars-rover", - "thiserror", -] - -[[package]] -name = "mars-mock-zapper" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.0.1", - "cw-utils", + "cw-utils 0.16.0", "mars-rover", "thiserror", ] @@ -1057,7 +1126,7 @@ dependencies = [ "cw-controllers-admin-fork", "cw-multi-test", "cw-storage-plus 1.0.1", - "cw-utils", + "cw-utils 0.16.0", "cw2 1.0.0", "mars-mock-oracle", "mars-mock-vault", @@ -1071,7 +1140,7 @@ name = "mars-osmosis" version = "1.0.0" dependencies = [ "cosmwasm-std", - "osmosis-std", + "osmosis-std 0.13.2", "serde", ] @@ -1093,7 +1162,7 @@ dependencies = [ "cosmwasm-vault-standard", "cw-controllers-admin-fork", "cw-storage-plus 1.0.1", - "cw-utils", + "cw-utils 0.16.0", "mars-health", "mars-mock-oracle", "mars-mock-red-bank", @@ -1143,12 +1212,50 @@ dependencies = [ "mars-osmosis", "mars-rover", "mars-swapper-base", - "osmosis-std", + "osmosis-std 0.13.2", "osmosis-testing", "schemars", "thiserror", ] +[[package]] +name = "mars-zapper-base" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-dex", + "cw-storage-plus 1.0.1", + "cw-utils 0.16.0", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "mars-zapper-mock" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "cw-utils 0.16.0", + "mars-rover", + "thiserror", +] + +[[package]] +name = "mars-zapper-osmosis" +version = "1.0.0" +dependencies = [ + "cosmwasm-std", + "cw-dex", + "cw-utils 0.16.0", + "cw2 1.0.0", + "mars-zapper-base", + "osmosis-testing", +] + [[package]] name = "memchr" version = "2.5.0" @@ -1228,6 +1335,21 @@ version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +[[package]] +name = "osmosis-std" +version = "0.12.0" +source = "git+https://github.com/apollodao/osmosis-rust?rev=52ca8eaa4b2926fea01a68f4d7e7253ad29d88b3#52ca8eaa4b2926fea01a68f4d7e7253ad29d88b3" +dependencies = [ + "chrono", + "cosmwasm-std", + "osmosis-std-derive 0.12.0", + "prost 0.11.3", + "prost-types", + "schemars", + "serde", + "serde-cw-value", +] + [[package]] name = "osmosis-std" version = "0.13.2" @@ -1236,7 +1358,7 @@ checksum = "10d6fe6ac7fcba45ed61d738091d33c838c4cabbcf4892dc7aa56d19d39cc976" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive", + "osmosis-std-derive 0.13.2", "prost 0.11.3", "prost-types", "schemars", @@ -1244,6 +1366,17 @@ dependencies = [ "serde-cw-value", ] +[[package]] +name = "osmosis-std-derive" +version = "0.12.0" +source = "git+https://github.com/apollodao/osmosis-rust?rev=52ca8eaa4b2926fea01a68f4d7e7253ad29d88b3#52ca8eaa4b2926fea01a68f4d7e7253ad29d88b3" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "osmosis-std-derive" version = "0.13.2" @@ -1266,7 +1399,7 @@ dependencies = [ "bindgen", "cosmrs", "cosmwasm-std", - "osmosis-std", + "osmosis-std 0.13.2", "prost 0.11.3", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index bf472b46e..916cbf76a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "contracts/credit-manager", "contracts/swapper/*", "contracts/oracle-adapter", + "contracts/zapper/*", "packages/chains/*", "packages/health", "packages/outpost", @@ -14,7 +15,6 @@ members = [ "contracts/mock-oracle", "contracts/mock-red-bank", "contracts/mock-vault", - "contracts/mock-zapper", "contracts/mock-credit-manager", ] @@ -61,14 +61,15 @@ mars-rover = { version = "1.0.0", path = "./packages/rover" } mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } mars-oracle-adapter = { version = "1.0.0", path = "contracts/oracle-adapter", features = ["library"] } mars-swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } +mars-zapper-base = { version = "1.0.0", path = "./contracts/zapper/base" } # mocks mars-mock-credit-manager = { version = "1.0.0", path = "./contracts/mock-credit-manager", features = ["library"] } mars-mock-oracle = { version = "1.0.0", path = "./contracts/mock-oracle", features = ["library"] } mars-mock-red-bank = { version = "1.0.0", path = "./contracts/mock-red-bank", features = ["library"] } mars-mock-vault = { version = "1.0.0", path = "./contracts/mock-vault", features = ["library"] } -mars-mock-zapper = { version = "1.0.0", path = "./contracts/mock-zapper", features = ["library"] } mars-swapper-mock = { version = "1.0.0", path = "./contracts/swapper/mock", features = ["library"] } +mars-zapper-mock = { version = "1.0.0", path = "./contracts/zapper/mock", features = ["library"] } [profile.release] opt-level = 3 diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 89847442f..183c22614 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -41,5 +41,5 @@ itertools = { workspace = true } mars-mock-oracle = { workspace = true } mars-mock-red-bank = { workspace = true } mars-mock-vault = { workspace = true } -mars-mock-zapper = { workspace = true } mars-swapper-mock = { workspace = true } +mars-zapper-mock = { workspace = true } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 73ae8c314..e6a0a84ac 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -74,20 +74,14 @@ pub fn dispatch_actions( account_id: account_id.to_string(), coin: coin.clone(), }), - Action::Repay { denom, amount } => callbacks.push(CallbackMsg::Repay { + Action::Repay(coin) => callbacks.push(CallbackMsg::Repay { account_id: account_id.to_string(), - denom: denom.clone(), - amount: *amount, + coin: coin.clone(), }), - Action::EnterVault { - vault, - denom, - amount, - } => callbacks.push(CallbackMsg::EnterVault { + Action::EnterVault { vault, coin } => callbacks.push(CallbackMsg::EnterVault { account_id: account_id.to_string(), vault: vault.check(deps.api)?, - denom: denom.to_string(), - amount: *amount, + coin: coin.clone(), }), Action::LiquidateCoin { liquidatee_account_id, @@ -112,14 +106,12 @@ pub fn dispatch_actions( position_type: position_type.clone(), }), Action::SwapExactIn { - coin_in_denom, - coin_in_amount, + coin_in, denom_out, slippage, } => callbacks.push(CallbackMsg::SwapExactIn { account_id: account_id.to_string(), - coin_in_denom: coin_in_denom.clone(), - coin_in_amount: *coin_in_amount, + coin_in: coin_in.clone(), denom_out: denom_out.clone(), slippage: *slippage, }), @@ -152,14 +144,12 @@ pub fn dispatch_actions( coins_in: coins_in.clone(), minimum_receive: *minimum_receive, }), - Action::WithdrawLiquidity { - lp_token_denom, - lp_token_amount, - } => callbacks.push(CallbackMsg::WithdrawLiquidity { - account_id: account_id.to_string(), - lp_token_denom: lp_token_denom.clone(), - lp_token_amount: *lp_token_amount, - }), + Action::WithdrawLiquidity { lp_token } => { + callbacks.push(CallbackMsg::WithdrawLiquidity { + account_id: account_id.to_string(), + lp_token: lp_token.clone(), + }) + } Action::RefundAllCoinBalances {} => { callbacks.push(CallbackMsg::RefundAllCoinBalances { account_id: account_id.to_string(), @@ -211,27 +201,15 @@ pub fn execute_callback( recipient, } => withdraw(deps, &account_id, coin, recipient), CallbackMsg::Borrow { coin, account_id } => borrow(deps, env, &account_id, coin), - CallbackMsg::Repay { - account_id, - denom, - amount, - } => repay(deps, env, &account_id, &denom, amount), + CallbackMsg::Repay { account_id, coin } => repay(deps, env, &account_id, &coin), CallbackMsg::AssertBelowMaxLTV { account_id } => { assert_below_max_ltv(deps.as_ref(), env, &account_id) } CallbackMsg::EnterVault { account_id, vault, - denom, - amount, - } => enter_vault( - deps, - &env.contract.address, - &account_id, - vault, - &denom, - amount, - ), + coin, + } => enter_vault(deps, &env.contract.address, &account_id, vault, &coin), CallbackMsg::UpdateVaultCoinBalance { vault, account_id, @@ -273,19 +251,10 @@ pub fn execute_callback( ), CallbackMsg::SwapExactIn { account_id, - coin_in_denom, - coin_in_amount, + coin_in, denom_out, slippage, - } => swap_exact_in( - deps, - env, - &account_id, - &coin_in_denom, - coin_in_amount, - &denom_out, - slippage, - ), + } => swap_exact_in(deps, env, &account_id, &coin_in, &denom_out, slippage), CallbackMsg::UpdateCoinBalance { account_id, previous_balance, @@ -320,9 +289,8 @@ pub fn execute_callback( ), CallbackMsg::WithdrawLiquidity { account_id, - lp_token_denom, - lp_token_amount, - } => withdraw_liquidity(deps, env, &account_id, &lp_token_denom, lp_token_amount), + lp_token, + } => withdraw_liquidity(deps, env, &account_id, &lp_token), CallbackMsg::AssertOneVaultPositionOnly { account_id } => { assert_only_one_vault_position(deps, &account_id) } diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index f2fffc283..b085a0a2c 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -155,8 +155,7 @@ pub fn repay_debt( increment_coin_balance(storage, liquidatee_account_id, debt)?; let msg = (CallbackMsg::Repay { account_id: liquidatee_account_id.to_string(), - denom: debt.denom.clone(), - amount: Some(debt.amount), + coin: debt.into(), }) .into_cosmos_msg(&env.contract.address)?; Ok(msg) diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index 5ff2f854d..e1364abb3 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -3,6 +3,7 @@ use std::cmp::min; use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::msg::execute::ActionCoin; use crate::state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; use crate::utils::{debt_shares_to_amount, decrement_coin_balance}; @@ -11,38 +12,37 @@ pub fn repay( deps: DepsMut, env: Env, account_id: &str, - denom: &str, - amount: Option, + coin: &ActionCoin, ) -> ContractResult { // Ensure repayment does not exceed max debt on account let (debt_amount, debt_shares) = - current_debt_for_denom(deps.as_ref(), &env, account_id, denom)?; - let amount_to_repay = min(debt_amount, amount.unwrap_or(Uint128::MAX)); + current_debt_for_denom(deps.as_ref(), &env, account_id, &coin.denom)?; + let amount_to_repay = min(debt_amount, coin.amount.value().unwrap_or(Uint128::MAX)); let shares_to_repay = debt_amount_to_shares( deps.as_ref(), &env, &Coin { - denom: denom.to_string(), + denom: coin.denom.to_string(), amount: amount_to_repay, }, )?; // Decrement token's debt position if amount_to_repay == debt_amount { - DEBT_SHARES.remove(deps.storage, (account_id, denom)); + DEBT_SHARES.remove(deps.storage, (account_id, &coin.denom)); } else { DEBT_SHARES.save( deps.storage, - (account_id, denom), + (account_id, &coin.denom), &debt_shares.checked_sub(shares_to_repay)?, )?; } // Decrement total debt shares for coin - let total_debt_shares = TOTAL_DEBT_SHARES.load(deps.storage, denom)?; + let total_debt_shares = TOTAL_DEBT_SHARES.load(deps.storage, &coin.denom)?; TOTAL_DEBT_SHARES.save( deps.storage, - denom, + &coin.denom, &total_debt_shares.checked_sub(shares_to_repay)?, )?; @@ -50,14 +50,14 @@ pub fn repay( deps.storage, account_id, &Coin { - denom: denom.to_string(), + denom: coin.denom.to_string(), amount: amount_to_repay, }, )?; let red_bank = RED_BANK.load(deps.storage)?; let red_bank_repay_msg = red_bank.repay_msg(&Coin { - denom: denom.to_string(), + denom: coin.denom.to_string(), amount: amount_to_repay, })?; diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs index 88b0fb53b..592b0162d 100644 --- a/contracts/credit-manager/src/swap.rs +++ b/contracts/credit-manager/src/swap.rs @@ -1,6 +1,7 @@ use cosmwasm_std::{Coin, Decimal, DepsMut, Env, Response, Uint128}; use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::msg::execute::{ActionAmount, ActionCoin}; use crate::state::{COIN_BALANCES, SWAPPER}; use crate::utils::{assert_coin_is_whitelisted, decrement_coin_balance, update_balance_msg}; @@ -9,21 +10,20 @@ pub fn swap_exact_in( deps: DepsMut, env: Env, account_id: &str, - coin_in_denom: &str, - coin_in_amount: Option, + coin_in: &ActionCoin, denom_out: &str, slippage: Decimal, ) -> ContractResult { assert_coin_is_whitelisted(deps.storage, denom_out)?; - // Passing None for coin_in_amount defaults to account's balance for `coin_in_denom` let coin_in_to_trade = Coin { - denom: coin_in_denom.to_string(), - amount: coin_in_amount.unwrap_or( - COIN_BALANCES - .may_load(deps.storage, (account_id, coin_in_denom))? + denom: coin_in.denom.clone(), + amount: match coin_in.amount { + ActionAmount::Exact(a) => a, + ActionAmount::AccountBalance => COIN_BALANCES + .may_load(deps.storage, (account_id, &coin_in.denom))? .unwrap_or(Uint128::zero()), - ), + }, }; if coin_in_to_trade.amount.is_zero() { diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index 2143d4f50..949811aad 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -1,11 +1,11 @@ use cosmwasm_std::{ - coin as c, to_binary, Addr, Coin, CosmosMsg, Deps, DepsMut, QuerierWrapper, Response, Storage, - Uint128, WasmMsg, + coin as c, to_binary, Addr, Coin, CosmosMsg, Deps, DepsMut, QuerierWrapper, Response, Uint128, + WasmMsg, }; use mars_rover::adapters::vault::{UpdateType, Vault, VaultPositionUpdate}; use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::msg::execute::CallbackMsg; +use mars_rover::msg::execute::{ActionAmount, ActionCoin, CallbackMsg}; use mars_rover::msg::ExecuteMsg; use crate::query::query_vault_positions; @@ -18,16 +18,20 @@ pub fn enter_vault( rover_addr: &Addr, account_id: &str, vault: Vault, - denom: &str, - amount_opt: Option, + coin: &ActionCoin, ) -> ContractResult { - let amount = or_full_balance_default(deps.storage, amount_opt, account_id, denom)?; + let amount = match coin.amount { + ActionAmount::Exact(a) => a, + ActionAmount::AccountBalance => { + COIN_BALANCES.load(deps.storage, (account_id, &coin.denom))? + } + }; let coin_to_enter = Coin { - denom: denom.to_string(), + denom: coin.denom.clone(), amount, }; - assert_coin_is_whitelisted(deps.storage, denom)?; + assert_coin_is_whitelisted(deps.storage, &coin.denom)?; assert_vault_is_whitelisted(deps.storage, &vault)?; assert_denom_matches_vault_reqs(deps.querier, &vault, &coin_to_enter)?; assert_deposit_is_under_cap(deps.as_ref(), &vault, &coin_to_enter, rover_addr)?; @@ -51,19 +55,6 @@ pub fn enter_vault( .add_attribute("action", "rover/credit-manager/vault/deposit")) } -fn or_full_balance_default( - storage: &dyn Storage, - amount_opt: Option, - account_id: &str, - denom: &str, -) -> ContractResult { - if let Some(a) = amount_opt { - Ok(a) - } else { - Ok(COIN_BALANCES.load(storage, (account_id, denom))?) - } -} - pub fn update_vault_coin_balance( deps: DepsMut, vault: Vault, diff --git a/contracts/credit-manager/src/zap.rs b/contracts/credit-manager/src/zap.rs index 0b712ca81..b5fe06c87 100644 --- a/contracts/credit-manager/src/zap.rs +++ b/contracts/credit-manager/src/zap.rs @@ -1,6 +1,7 @@ use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::msg::execute::{ActionAmount, ActionCoin}; use mars_rover::traits::Denoms; use crate::state::{COIN_BALANCES, ZAPPER}; @@ -13,7 +14,7 @@ pub fn provide_liquidity( deps: DepsMut, env: Env, account_id: &str, - coins_in: Vec, + coins_in: Vec, lp_token_out: &str, minimum_receive: Uint128, ) -> ContractResult { @@ -21,13 +22,24 @@ pub fn provide_liquidity( assert_coins_are_whitelisted(deps.storage, coins_in.to_denoms())?; // Decrement coin amounts in account for those sent to pool - for coin_in in &coins_in { - decrement_coin_balance(deps.storage, account_id, coin_in)?; + let mut updated_coins_in: Vec = Vec::with_capacity(coins_in.len()); + for coin_in in coins_in { + let coin_balance = COIN_BALANCES.load(deps.storage, (account_id, &coin_in.denom))?; + let new_amount = match coin_in.amount { + ActionAmount::Exact(amt) => amt, + ActionAmount::AccountBalance => coin_balance, + }; + let updated_coin = Coin { + denom: coin_in.denom, + amount: new_amount, + }; + decrement_coin_balance(deps.storage, account_id, &updated_coin)?; + updated_coins_in.push(updated_coin); } // After zap is complete, update account's LP token balance let zapper = ZAPPER.load(deps.storage)?; - let zap_msg = zapper.provide_liquidity_msg(&coins_in, lp_token_out, minimum_receive)?; + let zap_msg = zapper.provide_liquidity_msg(&updated_coins_in, lp_token_out, minimum_receive)?; let update_balance_msg = update_balance_msg( &deps.querier, &env.contract.address, @@ -45,18 +57,18 @@ pub fn withdraw_liquidity( deps: DepsMut, env: Env, account_id: &str, - lp_token_denom: &str, - lp_token_amount: Option, + lp_token_action: &ActionCoin, ) -> ContractResult { - assert_coin_is_whitelisted(deps.storage, lp_token_denom)?; + assert_coin_is_whitelisted(deps.storage, &lp_token_action.denom)?; let lp_token = Coin { - denom: lp_token_denom.to_string(), - amount: lp_token_amount.unwrap_or( - COIN_BALANCES - .may_load(deps.storage, (account_id, lp_token_denom))? + denom: lp_token_action.denom.clone(), + amount: match lp_token_action.amount { + ActionAmount::Exact(a) => a, + ActionAmount::AccountBalance => COIN_BALANCES + .may_load(deps.storage, (account_id, &lp_token_action.denom))? .unwrap_or(Uint128::zero()), - ), + }, }; if lp_token.amount.is_zero() { diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index cdce025e7..438af109e 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -71,9 +71,9 @@ pub fn mock_swapper_contract() -> Box> { pub fn mock_zapper_contract() -> Box> { let contract = ContractWrapper::new( - mars_mock_zapper::contract::execute, - mars_mock_zapper::contract::instantiate, - mars_mock_zapper::contract::query, + mars_zapper_mock::contract::execute, + mars_zapper_mock::contract::instantiate, + mars_zapper_mock::contract::query, ); Box::new(contract) } diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 2e4e162e9..3049943a8 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -1,6 +1,7 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{coin, Addr, Coin, Decimal}; +use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; use cw_utils::Duration; +use mars_rover::msg::execute::{ActionAmount, ActionCoin}; #[cw_serde] pub struct AccountToFund { @@ -40,4 +41,18 @@ impl CoinInfo { pub fn to_coin(&self, amount: u128) -> Coin { coin(amount, self.denom.clone()) } + + pub fn to_action_coin(&self, amount: u128) -> ActionCoin { + ActionCoin { + denom: self.denom.clone(), + amount: ActionAmount::Exact(Uint128::new(amount)), + } + } + + pub fn to_action_coin_full_balance(&self) -> ActionCoin { + ActionCoin { + denom: self.denom.clone(), + amount: ActionAmount::AccountBalance, + } + } } diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index 3e32734a1..eec986b8c 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Uint128}; +use cosmwasm_std::Addr; use mars_rover::msg::execute::Action; @@ -41,8 +41,7 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { all_vaults.iter().for_each(|v| { actions.extend([Action::EnterVault { vault: mock.get_vault(v), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(10)), + coin: lp_token.to_action_coin(10), }]); }); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index ee3a73279..fcfaba032 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Uint128}; +use cosmwasm_std::Addr; use itertools::Itertools; use mars_rover::msg::execute::Action; @@ -42,8 +42,7 @@ fn test_pagination_on_all_vault_positions_query_works() { all_vaults.iter().for_each(|v| { actions.extend([Action::EnterVault { vault: mock.get_vault(v), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(10)), + coin: lp_token.to_action_coin(10), }]); }); diff --git a/contracts/credit-manager/tests/test_fields_vault_limit.rs b/contracts/credit-manager/tests/test_fields_vault_limit.rs index df08a8155..6877d7b38 100644 --- a/contracts/credit-manager/tests/test_fields_vault_limit.rs +++ b/contracts/credit-manager/tests/test_fields_vault_limit.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{coin, Addr, Decimal, Uint128}; +use cosmwasm_std::{coin, Addr, Decimal}; use mars_rover::error::ContractError; use mars_rover::msg::execute::Action::{Deposit, EnterVault}; @@ -51,14 +51,12 @@ fn test_can_only_have_a_single_vault_position() { Deposit(lp_token.to_coin(200)), EnterVault { vault: lev_vault, - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }, Deposit(degen_vault_token.to_coin(200)), EnterVault { vault: degen_vault, - denom: degen_vault_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: degen_vault_token.to_action_coin(200), }, ], &[lp_token.to_coin(200), degen_vault_token.to_coin(200)], diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 60c6530a2..eaa888fbc 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -96,8 +96,7 @@ fn test_vault_positions_contribute_to_health() { Deposit(lp_token.to_coin(220)), EnterVault { vault, - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }, Borrow(atom_info.to_coin(14)), ], diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index dc5c70ecb..1f1eff677 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -96,8 +96,7 @@ fn test_liquidatee_is_not_liquidatable() { Deposit(lp_token.to_coin(200)), EnterVault { vault, - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }, ], &[lp_token.to_coin(200)], @@ -155,8 +154,7 @@ fn test_liquidator_does_not_have_debt_coin_in_credit_account() { Deposit(lp_token.to_coin(200)), EnterVault { vault, - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }, Borrow(ujake.to_coin(175)), ], @@ -220,8 +218,7 @@ fn test_wrong_position_type_sent_for_unlocked_vault() { Deposit(lp_token.to_coin(200)), EnterVault { vault, - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }, ], &[lp_token.to_coin(200)], @@ -286,8 +283,7 @@ fn test_wrong_position_type_sent_for_locked_vault() { Deposit(lp_token.to_coin(200)), EnterVault { vault, - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }, ], &[lp_token.to_coin(200)], @@ -345,8 +341,7 @@ fn test_liquidate_unlocked_vault() { Deposit(lp_token.to_coin(200)), EnterVault { vault, - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }, Borrow(ujake.to_coin(175)), ], @@ -432,8 +427,7 @@ fn test_liquidate_locked_vault() { Deposit(lp_token.to_coin(80)), EnterVault { vault, - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(80)), + coin: lp_token.to_action_coin(80), }, Borrow(atom.to_coin(700)), ], @@ -522,8 +516,7 @@ fn test_liquidate_unlocking_liquidation_order() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }, Borrow(ujake.to_coin(175)), RequestVaultUnlock { @@ -655,8 +648,7 @@ fn test_liquidation_calculation_adjustment() { Deposit(lp_token.to_coin(200)), EnterVault { vault, - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }, Borrow(ujake.to_coin(175)), ], diff --git a/contracts/credit-manager/tests/test_refund_balances.rs b/contracts/credit-manager/tests/test_refund_balances.rs index 1d94d4d9a..ef172dcd2 100644 --- a/contracts/credit-manager/tests/test_refund_balances.rs +++ b/contracts/credit-manager/tests/test_refund_balances.rs @@ -77,8 +77,7 @@ fn test_refund_coin_balances_when_no_balances() { Deposit(lp_token.to_coin(200)), EnterVault { vault, - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }, RefundAllCoinBalances {}, ], diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index 53efe684a..bb26036ab 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -24,10 +24,7 @@ fn test_only_token_owner_can_repay() { let res = mock.update_credit_account( &account_id, &another_user, - vec![Repay { - denom: coin_info.denom, - amount: Some(Uint128::new(12312)), - }], + vec![Repay(coin_info.to_action_coin(12312))], &[], ); @@ -54,10 +51,7 @@ fn test_repaying_with_zero_debt_raises() { let res = mock.update_credit_account( &account_id, &user, - vec![Repay { - denom: coin_info.denom.clone(), - amount: Some(Uint128::new(0)), - }], + vec![Repay(coin_info.to_action_coin(0))], &[], ); @@ -67,10 +61,7 @@ fn test_repaying_with_zero_debt_raises() { let res = mock.update_credit_account( &account_id, &user, - vec![Repay { - denom: coin_info.denom, - amount: None, - }], + vec![Repay(coin_info.to_action_coin_full_balance())], &[], ); @@ -126,10 +117,7 @@ fn test_raises_when_repaying_what_is_not_owed() { vec![ Deposit(uatom_info.to_coin(300)), Borrow(uosmo_info.to_coin(42)), - Repay { - denom: uatom_info.denom.clone(), - amount: Some(Uint128::new(42)), - }, + Repay(uatom_info.to_action_coin(42)), ], &[uatom_info.to_coin(300)], ); @@ -169,10 +157,7 @@ fn test_raises_when_not_enough_assets_to_repay() { Deposit(uatom_info.to_coin(300)), Borrow(uosmo_info.to_coin(50)), Withdraw(uosmo_info.to_coin(10)), - Repay { - denom: uosmo_info.denom, - amount: Some(Uint128::new(50)), - }, + Repay(uosmo_info.to_action_coin(50)), ], &[uatom_info.to_coin(300)], ); @@ -224,10 +209,7 @@ fn test_repay_less_than_total_debt() { mock.update_credit_account( &account_id, &user, - vec![Repay { - denom: coin_info.denom.clone(), - amount: Some(Uint128::new(20)), - }], + vec![Repay(coin_info.to_action_coin(20))], &[], ) .unwrap(); @@ -265,10 +247,7 @@ fn test_repay_less_than_total_debt() { mock.update_credit_account( &account_id, &user, - vec![Repay { - denom: coin_info.denom.clone(), - amount: Some(Uint128::new(31)), - }], // Interest accrued paid back as well + vec![Repay(coin_info.to_action_coin(31))], // Interest accrued paid back as well &[], ) .unwrap(); @@ -317,10 +296,7 @@ fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { vec![ Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50)), - Repay { - denom: coin_info.denom.clone(), - amount: Some(Uint128::new(75)), - }, + Repay(coin_info.to_action_coin(75)), ], &[coin(300, coin_info.denom.clone())], ) @@ -371,10 +347,7 @@ fn test_amount_none_repays_total_debt() { vec![ Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50)), - Repay { - denom: coin_info.denom.clone(), - amount: None, - }, + Repay(coin_info.to_action_coin_full_balance()), ], &[coin(300, coin_info.denom.clone())], ) diff --git a/contracts/credit-manager/tests/test_swap.rs b/contracts/credit-manager/tests/test_swap.rs index c3f10eb19..79dedb349 100644 --- a/contracts/credit-manager/tests/test_swap.rs +++ b/contracts/credit-manager/tests/test_swap.rs @@ -3,6 +3,7 @@ use cosmwasm_std::{coins, Addr, Coin, Decimal, OverflowError, Uint128}; use mars_rover::error::ContractError; use mars_rover::msg::execute::Action::{Deposit, SwapExactIn}; +use mars_rover::msg::execute::{ActionAmount, ActionCoin}; use mars_swapper_mock::contract::MOCK_SWAP_RESULT; use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv}; @@ -20,8 +21,10 @@ fn test_only_token_owner_can_swap_for_account() { &account_id, &another_user, vec![SwapExactIn { - coin_in_denom: "mars".to_string(), - coin_in_amount: Some(Uint128::new(12)), + coin_in: ActionCoin { + denom: "mars".to_string(), + amount: ActionAmount::Exact(Uint128::new(12)), + }, denom_out: "osmo".to_string(), slippage: Decimal::from_atomics(6u128, 1).unwrap(), }], @@ -52,8 +55,7 @@ fn test_denom_out_must_be_whitelisted() { &account_id, &user, vec![SwapExactIn { - coin_in_denom: osmo_info.denom, - coin_in_amount: Some(Uint128::new(10_000)), + coin_in: osmo_info.to_action_coin(10_000), denom_out: "ujake".to_string(), slippage: Decimal::from_atomics(6u128, 1).unwrap(), }], @@ -79,8 +81,7 @@ fn test_no_amount_sent() { &account_id, &user, vec![SwapExactIn { - coin_in_denom: osmo_info.denom, - coin_in_amount: Some(Uint128::new(0)), + coin_in: osmo_info.to_action_coin(0), denom_out: atom_info.denom, slippage: Decimal::from_atomics(6u128, 1).unwrap(), }], @@ -106,8 +107,7 @@ fn test_user_has_zero_balance_for_swap_req() { &account_id, &user, vec![SwapExactIn { - coin_in_denom: osmo_info.denom, - coin_in_amount: Some(Uint128::new(10_000)), + coin_in: osmo_info.to_action_coin(10_000), denom_out: atom_info.denom, slippage: Decimal::from_atomics(6u128, 1).unwrap(), }], @@ -146,8 +146,7 @@ fn test_user_does_not_have_enough_balance_for_swap_req() { vec![ Deposit(osmo_info.to_coin(100)), SwapExactIn { - coin_in_denom: osmo_info.denom.clone(), - coin_in_amount: Some(Uint128::new(10_000)), + coin_in: osmo_info.to_action_coin(10_000), denom_out: atom_info.denom, slippage: Decimal::from_atomics(6u128, 1).unwrap(), }, @@ -190,8 +189,7 @@ fn test_swap_success_with_specified_amount() { vec![ Deposit(atom_info.to_coin(10_000)), SwapExactIn { - coin_in_denom: atom_info.denom.clone(), - coin_in_amount: Some(Uint128::new(10_000)), + coin_in: atom_info.to_action_coin(10_000), denom_out: osmo_info.denom.clone(), slippage: Decimal::from_atomics(6u128, 1).unwrap(), }, @@ -238,8 +236,7 @@ fn test_swap_success_with_amount_none() { vec![ Deposit(atom_info.to_coin(10_000)), SwapExactIn { - coin_in_denom: atom_info.denom.clone(), - coin_in_amount: None, + coin_in: atom_info.to_action_coin_full_balance(), denom_out: osmo_info.denom.clone(), slippage: Decimal::from_atomics(6u128, 1).unwrap(), }, diff --git a/contracts/credit-manager/tests/test_vault_enter.rs b/contracts/credit-manager/tests/test_vault_enter.rs index 03d25e936..46cb1ee67 100644 --- a/contracts/credit-manager/tests/test_vault_enter.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -6,6 +6,7 @@ use mars_mock_vault::contract::STARTING_VAULT_SHARES; use mars_rover::adapters::vault::VaultBase; use mars_rover::error::ContractError; use mars_rover::msg::execute::Action::{Deposit, EnterVault}; +use mars_rover::msg::execute::{ActionAmount, ActionCoin}; use crate::helpers::{ assert_err, locked_vault_info, lp_token_info, uatom_info, unlocked_vault_info, uosmo_info, @@ -27,8 +28,10 @@ fn test_only_account_owner_can_take_action() { &bad_guy, vec![EnterVault { vault: VaultBase::new("xyz".to_string()), - denom: "uosmo".to_string(), - amount: Some(Uint128::new(1)), + coin: ActionCoin { + denom: "uosmo".to_string(), + amount: ActionAmount::Exact(Uint128::new(1)), + }, }], &[], ); @@ -61,8 +64,7 @@ fn test_deposit_denom_is_whitelisted() { &user, vec![EnterVault { vault, - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }], &[], ); @@ -90,8 +92,7 @@ fn test_vault_is_whitelisted() { &user, vec![EnterVault { vault: VaultBase::new("unknown_vault".to_string()), - denom: uatom.denom, - amount: Some(Uint128::new(200)), + coin: uatom.to_action_coin(200), }], &[], ); @@ -121,8 +122,7 @@ fn test_deposited_coin_matches_vault_requirements() { &user, vec![EnterVault { vault: mock.get_vault(&leverage_vault), - denom: uatom.denom, - amount: Some(Uint128::new(200)), + coin: uatom.to_action_coin(200), }], &[], ); @@ -158,8 +158,7 @@ fn test_fails_if_not_enough_funds_for_implied_deposit() { &user, vec![EnterVault { vault: mock.get_vault(&leverage_vault), - denom: lp_token.denom, - amount: None, + coin: lp_token.to_action_coin_full_balance(), }], &[], ); @@ -195,8 +194,7 @@ fn test_fails_if_not_enough_funds_for_enumerated_deposit() { &user, vec![EnterVault { vault: mock.get_vault(&leverage_vault), - denom: lp_token.denom, - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }], &[], ); @@ -239,8 +237,7 @@ fn test_successful_deposit_into_locked_vault() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(23)), + coin: lp_token.to_action_coin(23), }, ], &[lp_token.to_coin(200)], @@ -297,8 +294,7 @@ fn test_successful_deposit_into_unlocked_vault() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(23)), + coin: lp_token.to_action_coin(23), }, ], &[lp_token.to_coin(200)], @@ -355,8 +351,7 @@ fn test_vault_deposit_must_be_under_cap() { Deposit(lp_token.to_coin(700_000)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(700_000)), + coin: lp_token.to_action_coin(700_000), }, ], &[lp_token.to_coin(700_000)], @@ -373,8 +368,7 @@ fn test_vault_deposit_must_be_under_cap() { Deposit(lp_token.to_coin(100_000)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(100_000)), + coin: lp_token.to_action_coin(100_000), }, ], &[lp_token.to_coin(100_000)], @@ -391,8 +385,7 @@ fn test_vault_deposit_must_be_under_cap() { Deposit(lp_token.to_coin(2_500_000)), EnterVault { vault, - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(2_500_000)), + coin: lp_token.to_action_coin(2_500_000), }, ], &[lp_token.to_coin(2_500_000)], @@ -433,8 +426,7 @@ fn test_successful_deposit_with_implied_full_balance_amount() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: None, + coin: lp_token.to_action_coin_full_balance(), }, ], &[lp_token.to_coin(200)], diff --git a/contracts/credit-manager/tests/test_vault_exit.rs b/contracts/credit-manager/tests/test_vault_exit.rs index ab51b8a31..d23fc87ed 100644 --- a/contracts/credit-manager/tests/test_vault_exit.rs +++ b/contracts/credit-manager/tests/test_vault_exit.rs @@ -132,8 +132,7 @@ fn test_withdraw_with_unlocked_vault_coins() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(100)), + coin: lp_token.to_action_coin(100), }, ], &[lp_token.to_coin(200)], diff --git a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs index 61201aa17..5de120b63 100644 --- a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs +++ b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs @@ -95,8 +95,7 @@ fn test_not_owner_of_unlocking_position() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(23)), + coin: lp_token.to_action_coin(23), }, RequestVaultUnlock { vault: vault.clone(), @@ -120,8 +119,7 @@ fn test_not_owner_of_unlocking_position() { Deposit(lp_token.to_coin(2)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(2)), + coin: lp_token.to_action_coin(2), }, RequestVaultUnlock { vault: vault.clone(), @@ -164,8 +162,7 @@ fn test_unlocking_position_not_ready_time() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(23)), + coin: lp_token.to_action_coin(23), }, RequestVaultUnlock { vault: vault.clone(), @@ -218,8 +215,7 @@ fn test_unlocking_position_not_ready_blocks() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(23)), + coin: lp_token.to_action_coin(23), }, RequestVaultUnlock { vault: vault.clone(), @@ -272,8 +268,7 @@ fn test_withdraw_unlock_success_time_expiring() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }, RequestVaultUnlock { vault: vault.clone(), @@ -348,8 +343,7 @@ fn test_withdraw_unlock_success_block_expiring() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(200)), + coin: lp_token.to_action_coin(200), }, RequestVaultUnlock { vault: vault.clone(), diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs index 701f4e981..64b370700 100644 --- a/contracts/credit-manager/tests/test_vault_request_unlock.rs +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -174,8 +174,7 @@ fn test_not_enough_vault_tokens_for_request() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(23)), + coin: lp_token.to_action_coin(23), }, RequestVaultUnlock { vault, @@ -221,8 +220,7 @@ fn test_request_unlocked() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(23)), + coin: lp_token.to_action_coin(23), }, RequestVaultUnlock { vault: vault.clone(), @@ -297,8 +295,7 @@ fn test_cannot_request_more_than_max() { Deposit(lp_token.to_coin(200)), EnterVault { vault: vault.clone(), - denom: lp_token.denom.clone(), - amount: Some(Uint128::new(23)), + coin: lp_token.to_action_coin(23), }, ], &[lp_token.to_coin(200)], diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs index 13f3c8fd3..1ce2f564f 100644 --- a/contracts/credit-manager/tests/test_zap_provide.rs +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -1,11 +1,12 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::{Addr, OverflowError, Uint128}; -use mars_mock_zapper::contract::STARTING_LP_POOL_TOKENS; +use mars_zapper_mock::contract::STARTING_LP_POOL_TOKENS; use std::ops::Mul; -use mars_mock_zapper::error::ContractError; use mars_rover::error::ContractError as RoverError; use mars_rover::msg::execute::Action::{Deposit, ProvideLiquidity, WithdrawLiquidity}; +use mars_rover::msg::execute::{ActionAmount, ActionCoin}; +use mars_zapper_mock::error::ContractError; use crate::helpers::{ assert_err, get_coin, lp_token_info, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, @@ -64,7 +65,7 @@ fn test_does_not_have_enough_tokens_to_provide_liq() { Deposit(atom.to_coin(100)), Deposit(osmo.to_coin(50)), ProvideLiquidity { - coins_in: vec![atom.to_coin(100), osmo.to_coin(200)], + coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(200)], lp_token_out: lp_token.denom, minimum_receive: Uint128::zero(), }, @@ -106,7 +107,7 @@ fn test_lp_token_out_must_be_whitelisted() { Deposit(atom.to_coin(100)), Deposit(osmo.to_coin(50)), ProvideLiquidity { - coins_in: vec![atom.to_coin(100), osmo.to_coin(200)], + coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(200)], lp_token_out: lp_token.denom.clone(), minimum_receive: Uint128::zero(), }, @@ -138,7 +139,7 @@ fn test_coins_in_must_be_whitelisted() { &account_id, &user, vec![ProvideLiquidity { - coins_in: vec![atom.to_coin(100), osmo.to_coin(200)], + coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(200)], lp_token_out: lp_token.denom, minimum_receive: Uint128::zero(), }], @@ -173,7 +174,7 @@ fn test_min_received_too_high() { Deposit(atom.to_coin(100)), Deposit(osmo.to_coin(50)), ProvideLiquidity { - coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom, minimum_receive: Uint128::new(100_000_000_000), }, @@ -211,7 +212,7 @@ fn test_wrong_denom_provided() { Deposit(atom.to_coin(100)), Deposit(jake.to_coin(50)), ProvideLiquidity { - coins_in: vec![atom.to_coin(100), jake.to_coin(50)], + coins_in: vec![atom.to_action_coin(100), jake.to_action_coin(50)], lp_token_out: lp_token.denom, minimum_receive: Uint128::zero(), }, @@ -256,7 +257,7 @@ fn test_successful_zap() { Deposit(atom.to_coin(100)), Deposit(osmo.to_coin(50)), ProvideLiquidity { - coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), minimum_receive: slippage_adjusted, }, @@ -323,7 +324,7 @@ fn test_can_provide_unbalanced() { vec![ Deposit(atom.to_coin(100)), ProvideLiquidity { - coins_in: vec![atom.to_coin(100)], + coins_in: vec![atom.to_action_coin(100)], lp_token_out: lp_token.denom.clone(), minimum_receive: slippage_adjusted, }, @@ -348,8 +349,10 @@ fn test_can_provide_unbalanced() { &account_id, &user, vec![WithdrawLiquidity { - lp_token_denom: lp_token.denom.clone(), - lp_token_amount: Some(STARTING_LP_POOL_TOKENS.multiply_ratio(1u128, 2u128)), + lp_token: ActionCoin { + denom: lp_token.denom.clone(), + amount: ActionAmount::Exact(STARTING_LP_POOL_TOKENS.multiply_ratio(1u128, 2u128)), + }, }], &[], ) @@ -401,7 +404,7 @@ fn test_order_does_not_matter() { Deposit(atom.to_coin(100)), Deposit(osmo.to_coin(50)), ProvideLiquidity { - coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), minimum_receive: slippage_adjusted, }, @@ -418,7 +421,7 @@ fn test_order_does_not_matter() { Deposit(atom.to_coin(100)), Deposit(osmo.to_coin(50)), ProvideLiquidity { - coins_in: vec![osmo.to_coin(50), atom.to_coin(100)], + coins_in: vec![osmo.to_action_coin(50), atom.to_action_coin(100)], lp_token_out: lp_token.denom.clone(), minimum_receive: slippage_adjusted, }, diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index ad03bec8e..42e0865ab 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -1,10 +1,11 @@ use cosmwasm_std::OverflowOperation::Sub; use cosmwasm_std::{Addr, OverflowError, Uint128}; -use mars_mock_zapper::contract::STARTING_LP_POOL_TOKENS; use mars_rover::error::ContractError as RoverError; use mars_rover::msg::execute::Action::{Deposit, ProvideLiquidity, WithdrawLiquidity}; +use mars_rover::msg::execute::{ActionAmount, ActionCoin}; use mars_rover::msg::instantiate::ConfigUpdates; +use mars_zapper_mock::contract::STARTING_LP_POOL_TOKENS; use crate::helpers::{ assert_err, get_coin, lp_token_info, uatom_info, uosmo_info, AccountToFund, MockEnv, @@ -23,8 +24,10 @@ fn test_only_token_owner_can_unzap_for_account() { &account_id, &another_user, vec![WithdrawLiquidity { - lp_token_denom: "xyz".to_string(), - lp_token_amount: None, + lp_token: ActionCoin { + denom: "xyz".to_string(), + amount: ActionAmount::AccountBalance, + }, }], &[], ); @@ -49,8 +52,7 @@ fn test_lp_token_in_must_be_whitelisted() { &account_id, &user, vec![WithdrawLiquidity { - lp_token_denom: lp_token.denom.clone(), - lp_token_amount: Some(Uint128::new(100)), + lp_token: lp_token.to_action_coin(100), }], &[], ); @@ -83,7 +85,7 @@ fn test_coins_out_must_be_whitelisted() { Deposit(atom.to_coin(100)), Deposit(osmo.to_coin(50)), ProvideLiquidity { - coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), minimum_receive: Uint128::zero(), }, @@ -107,8 +109,7 @@ fn test_coins_out_must_be_whitelisted() { &account_id, &user, vec![WithdrawLiquidity { - lp_token_denom: lp_token.denom, - lp_token_amount: Some(Uint128::new(100_000)), + lp_token: lp_token.to_action_coin(100_000), }], &[], ); @@ -134,7 +135,7 @@ fn test_does_not_have_the_tokens_to_withdraw_liq() { // Seed zapper with denoms so test can estimate withdraws let account_id = mock.create_credit_account(&user).unwrap(); - let attempted_unzap_amount = 100_000_000_000; + let attempted_unzap_amount = 100_000_000_000u128; let res = mock.update_credit_account( &account_id, &user, @@ -142,13 +143,12 @@ fn test_does_not_have_the_tokens_to_withdraw_liq() { Deposit(atom.to_coin(100)), Deposit(osmo.to_coin(50)), ProvideLiquidity { - coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), minimum_receive: Uint128::zero(), }, WithdrawLiquidity { - lp_token_denom: lp_token.denom, - lp_token_amount: Some(Uint128::new(attempted_unzap_amount)), + lp_token: lp_token.to_action_coin(attempted_unzap_amount), }, ], &[atom.to_coin(100), osmo.to_coin(50)], @@ -188,7 +188,7 @@ fn test_amount_zero_passed() { Deposit(atom.to_coin(100)), Deposit(osmo.to_coin(50)), ProvideLiquidity { - coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), minimum_receive: Uint128::zero(), }, @@ -201,8 +201,7 @@ fn test_amount_zero_passed() { &account_id, &user, vec![WithdrawLiquidity { - lp_token_denom: lp_token.denom, - lp_token_amount: Some(Uint128::zero()), + lp_token: lp_token.to_action_coin(0), }], &[], ); @@ -232,8 +231,7 @@ fn test_amount_none_passed_with_no_balance() { &account_id, &user, vec![WithdrawLiquidity { - lp_token_denom: lp_token.denom, - lp_token_amount: None, + lp_token: lp_token.to_action_coin_full_balance(), }], &[], ); @@ -266,13 +264,12 @@ fn test_successful_unzap_specified_amount() { Deposit(atom.to_coin(100)), Deposit(osmo.to_coin(50)), ProvideLiquidity { - coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), minimum_receive: Uint128::zero(), }, WithdrawLiquidity { - lp_token_denom: lp_token.denom.clone(), - lp_token_amount: Some(STARTING_LP_POOL_TOKENS), + lp_token: lp_token.to_action_coin(STARTING_LP_POOL_TOKENS.u128()), }, ], &[atom.to_coin(100), osmo.to_coin(50)], @@ -330,13 +327,12 @@ fn test_successful_unzap_unspecified_amount() { Deposit(atom.to_coin(100)), Deposit(osmo.to_coin(50)), ProvideLiquidity { - coins_in: vec![atom.to_coin(100), osmo.to_coin(50)], + coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), minimum_receive: Uint128::zero(), }, WithdrawLiquidity { - lp_token_denom: lp_token.denom.clone(), - lp_token_amount: None, + lp_token: lp_token.to_action_coin_full_balance(), }, ], &[atom.to_coin(100), osmo.to_coin(50)], diff --git a/contracts/zapper/base/Cargo.toml b/contracts/zapper/base/Cargo.toml new file mode 100644 index 000000000..3f0929c65 --- /dev/null +++ b/contracts/zapper/base/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "mars-zapper-base" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-utils = { workspace = true } +cw-storage-plus = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } + +# FIXME: develop branch dependency till Apollo finish audit +cw-dex = { git = "https://github.com/apollodao/cw-dex", rev = "b9d18cfbba8aa09ae4ee9459d308cda87ae5ffb3" } diff --git a/contracts/zapper/base/examples/schema.rs b/contracts/zapper/base/examples/schema.rs new file mode 100644 index 000000000..b38f8cc09 --- /dev/null +++ b/contracts/zapper/base/examples/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use mars_zapper_base::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/zapper/base/src/contract.rs b/contracts/zapper/base/src/contract.rs new file mode 100644 index 000000000..fcbfece3b --- /dev/null +++ b/contracts/zapper/base/src/contract.rs @@ -0,0 +1,268 @@ +use std::marker::PhantomData; + +use cosmwasm_std::{ + to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, Event, MessageInfo, + Response, StdResult, Uint128, +}; +use cw_utils::one_coin; + +use crate::{CallbackMsg, ContractError, ExecuteMsg, InstantiateMsg, LpPool, QueryMsg}; + +pub struct ZapperBase

+where + P: LpPool, +{ + /// Phantom data holds generics + pub custom_pool: PhantomData

, +} + +impl

Default for ZapperBase

+where + P: LpPool, +{ + fn default() -> Self { + Self { + custom_pool: PhantomData, + } + } +} + +impl

ZapperBase

+where + P: LpPool, +{ + pub fn instantiate( + &self, + _deps: DepsMut, + _msg: InstantiateMsg, + ) -> Result { + Ok(Response::default()) + } + + pub fn execute( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, + ) -> Result { + match msg { + ExecuteMsg::ProvideLiquidity { + lp_token_out, + recipient, + minimum_receive, + } => Self::execute_provide_liquidity( + deps, + env, + info, + lp_token_out, + recipient, + minimum_receive, + ), + ExecuteMsg::WithdrawLiquidity { recipient } => { + Self::execute_withdraw_liquidity(deps, env, info, recipient) + } + ExecuteMsg::Callback(msg) => { + // Can only be called by the contract itself + if info.sender != env.contract.address { + return Err(ContractError::Unauthorized {}); + } + match msg { + CallbackMsg::ReturnCoin { + balance_before, + recipient, + } => Self::execute_return_tokens(deps, env, info, balance_before, recipient), + } + } + } + } + + pub fn query(&self, deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::EstimateProvideLiquidity { + lp_token_out, + coins_in, + } => Self::query_estimate_provide_liquidity(deps, env, lp_token_out, coins_in), + QueryMsg::EstimateWithdrawLiquidity { coin_in } => { + Self::query_estimate_withdraw_liquidity(deps, env, coin_in) + } + } + } + + fn execute_provide_liquidity( + deps: DepsMut, + env: Env, + info: MessageInfo, + lp_token_out: String, + recipient: Option, + minimum_receive: Uint128, + ) -> Result { + let pool = P::get_pool_for_lp_token(deps.as_ref(), &lp_token_out)?; + + // Unwrap recipient or use caller's address + let recipient = recipient.map_or(Ok(info.sender), |x| deps.api.addr_validate(&x))?; + + let response = pool.provide_liquidity( + deps.as_ref(), + &env, + info.funds.clone().into(), + minimum_receive, + )?; + + // Query current contract coin balances + let mut coin_balances: Vec = Vec::with_capacity(info.funds.len() + 1); // funds + lp token + for funded_coin in info.funds { + let mut coin_balance = deps + .querier + .query_balance(&env.contract.address, &funded_coin.denom)?; + coin_balance.amount = coin_balance.amount.checked_sub(funded_coin.amount)?; + coin_balances.push(coin_balance); + } + + // Query current contract LP token balance + let lp_token_balance = deps + .querier + .query_balance(&env.contract.address, &lp_token_out)?; + coin_balances.push(lp_token_balance); + + // Callbacks to return remaining coins and LP tokens + let callback_msgs = prepare_return_coin_callbacks(&env, recipient.clone(), coin_balances)?; + + let event = Event::new("rover/zapper/execute_provide_liquidity") + .add_attribute("lp_token_out", lp_token_out) + .add_attribute("minimum_receive", minimum_receive) + .add_attribute("recipient", recipient); + + Ok(response.add_messages(callback_msgs).add_event(event)) + } + + fn execute_withdraw_liquidity( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Option, + ) -> Result { + // Make sure only one coin is sent + one_coin(&info)?; + + let lp_token = info.funds[0].clone(); + let pool = P::get_pool_for_lp_token(deps.as_ref(), &lp_token.denom)?; + + // Unwrap recipient or use caller + let recipient = recipient.map_or(Ok(info.sender), |x| deps.api.addr_validate(&x))?; + + // Use returned coins to check what denoms should be received + let coins_returned = + pool.simulate_withdraw_liquidity(deps.as_ref(), &lp_token.clone().into())?; + let coins_returned_str = coins_returned.to_string(); + + let response = pool.withdraw_liquidity(deps.as_ref(), &env, lp_token.clone().into())?; + + // Query current contract coin balances + let mut coin_balances: Vec = Vec::with_capacity(coins_returned.len() + 1); // coins returned + lp token + for coin_returned in coins_returned.to_vec() { + let coin_returned: Coin = coin_returned.try_into()?; + let coin_balance = deps + .querier + .query_balance(&env.contract.address, coin_returned.denom)?; + coin_balances.push(coin_balance); + } + + // Query current contract LP token balance + let mut lp_token_balance = deps + .querier + .query_balance(&env.contract.address, &lp_token.denom)?; + lp_token_balance.amount = lp_token_balance.amount.checked_sub(lp_token.amount)?; + coin_balances.push(lp_token_balance); + + // Callbacks to return remaining coins and LP tokens + let callback_msgs = prepare_return_coin_callbacks(&env, recipient.clone(), coin_balances)?; + + let event = Event::new("rover/zapper/execute_withdraw_liquidity") + .add_attribute("lp_token", lp_token.denom) + .add_attribute("coins_returned", coins_returned_str) + .add_attribute("recipient", recipient); + + Ok(response.add_messages(callback_msgs).add_event(event)) + } + + fn execute_return_tokens( + deps: DepsMut, + env: Env, + _info: MessageInfo, + balance_before: Coin, + recipient: Addr, + ) -> Result { + let balance_after = deps + .querier + .query_balance(env.contract.address, &balance_before.denom)?; + let return_amount = balance_after.amount.checked_sub(balance_before.amount)?; + + if return_amount.is_zero() { + return Ok(Response::new()); + } + + let return_coin = Coin { + denom: balance_before.denom, + amount: return_amount, + }; + let send_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: recipient.to_string(), + amount: vec![return_coin.clone()], + }); + + let event = Event::new("rover/zapper/execute_callback_return_lp_tokens") + .add_attribute("coin_returned", return_coin.to_string()) + .add_attribute("recipient", recipient); + + Ok(Response::new().add_message(send_msg).add_event(event)) + } + + fn query_estimate_provide_liquidity( + deps: Deps, + env: Env, + lp_token_out: String, + coins_in: Vec, + ) -> StdResult { + let pool = P::get_pool_for_lp_token(deps, &lp_token_out)?; + + let lp_tokens_returned = pool.simulate_provide_liquidity(deps, &env, coins_in.into())?; + + to_binary(&lp_tokens_returned.amount) + } + + fn query_estimate_withdraw_liquidity( + deps: Deps, + _env: Env, + coin_in: Coin, + ) -> StdResult { + let pool = P::get_pool_for_lp_token(deps, &coin_in.denom)?; + + let coins_returned = pool.simulate_withdraw_liquidity(deps, &coin_in.into())?; + + let native_coins_returned: Vec = coins_returned + .to_vec() + .into_iter() + .filter_map(|x| x.try_into().ok()) // filter out non native coins + .collect(); + + to_binary(&native_coins_returned) + } +} + +fn prepare_return_coin_callbacks( + env: &Env, + recipient: Addr, + coin_balances: Vec, +) -> StdResult> { + coin_balances + .into_iter() + .map(|coin_balance| { + CallbackMsg::ReturnCoin { + balance_before: coin_balance, + recipient: recipient.clone(), + } + .into_cosmos_msg(env) + }) + .collect() +} diff --git a/contracts/zapper/base/src/error.rs b/contracts/zapper/base/src/error.rs new file mode 100644 index 000000000..7913e3d1b --- /dev/null +++ b/contracts/zapper/base/src/error.rs @@ -0,0 +1,22 @@ +use cosmwasm_std::{OverflowError, StdError}; +use cw_dex::CwDexError; +use cw_utils::PaymentError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + Overflow(#[from] OverflowError), + + #[error("{0}")] + PaymentError(#[from] PaymentError), + + #[error("{0}")] + CwDexError(#[from] CwDexError), + + #[error("Unauthorized")] + Unauthorized {}, +} diff --git a/contracts/zapper/base/src/helpers.rs b/contracts/zapper/base/src/helpers.rs new file mode 100644 index 000000000..3965a2a87 --- /dev/null +++ b/contracts/zapper/base/src/helpers.rs @@ -0,0 +1,68 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, QuerierWrapper, StdResult, Uint128, WasmMsg}; + +use crate::msg::{ExecuteMsg, QueryMsg}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Zapper(pub Addr); + +impl Zapper { + pub fn addr(&self) -> Addr { + self.0.clone() + } + + pub fn call>(&self, msg: T) -> StdResult { + let msg = to_binary(&msg.into())?; + Ok(WasmMsg::Execute { + contract_addr: self.addr().into(), + msg, + funds: vec![], + } + .into()) + } + + pub fn provide_liquidity( + &self, + lp_token_out: String, + recipient: Option, + minimum_receive: Uint128, + ) -> StdResult { + self.call(ExecuteMsg::ProvideLiquidity { + lp_token_out, + recipient, + minimum_receive, + }) + } + + pub fn withdraw_liquidity(&self, recipient: Option) -> StdResult { + self.call(ExecuteMsg::WithdrawLiquidity { recipient }) + } + + pub fn estimate_provide_liquidity( + &self, + querier: &QuerierWrapper, + lp_token_out: String, + coins_in: Vec, + ) -> StdResult { + querier.query_wasm_smart( + self.0.to_string(), + &QueryMsg::EstimateProvideLiquidity { + lp_token_out, + coins_in, + }, + ) + } + + pub fn estimate_withdraw_liquidity( + &self, + querier: &QuerierWrapper, + coin_in: Coin, + ) -> StdResult> { + querier.query_wasm_smart( + self.0.to_string(), + &QueryMsg::EstimateWithdrawLiquidity { coin_in }, + ) + } +} diff --git a/contracts/zapper/base/src/lib.rs b/contracts/zapper/base/src/lib.rs new file mode 100644 index 000000000..84b31e55a --- /dev/null +++ b/contracts/zapper/base/src/lib.rs @@ -0,0 +1,9 @@ +mod contract; +mod error; +mod msg; +mod traits; + +pub use contract::*; +pub use error::*; +pub use msg::*; +pub use traits::*; diff --git a/contracts/zapper/base/src/msg.rs b/contracts/zapper/base/src/msg.rs new file mode 100644 index 000000000..20609aa67 --- /dev/null +++ b/contracts/zapper/base/src/msg.rs @@ -0,0 +1,48 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Env, StdResult, Uint128, WasmMsg}; + +#[cw_serde] +pub struct InstantiateMsg {} + +#[cw_serde] +pub enum ExecuteMsg { + ProvideLiquidity { + lp_token_out: String, + recipient: Option, + minimum_receive: Uint128, + }, + WithdrawLiquidity { + recipient: Option, + }, + Callback(CallbackMsg), +} + +#[cw_serde] +pub enum CallbackMsg { + ReturnCoin { + balance_before: Coin, + recipient: Addr, + }, +} + +impl CallbackMsg { + pub fn into_cosmos_msg(self, env: &Env) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_binary(&ExecuteMsg::Callback(self))?, + funds: vec![], + })) + } +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(Uint128)] + EstimateProvideLiquidity { + lp_token_out: String, + coins_in: Vec, + }, + #[returns(Vec)] + EstimateWithdrawLiquidity { coin_in: Coin }, +} diff --git a/contracts/zapper/base/src/traits.rs b/contracts/zapper/base/src/traits.rs new file mode 100644 index 000000000..77630bbd4 --- /dev/null +++ b/contracts/zapper/base/src/traits.rs @@ -0,0 +1,11 @@ +use cosmwasm_std::Deps; +use cw_dex::traits::Pool; +use cw_dex::CwDexError; + +pub trait LpPool { + /// Returns the matching pool given a LP token. + /// + /// https://github.com/apollodao/cw-dex uses cargo feature flags for chain specific implementation. + fn get_pool_for_lp_token(deps: Deps, lp_token_denom: &str) + -> Result, CwDexError>; +} diff --git a/contracts/mock-zapper/Cargo.toml b/contracts/zapper/mock/Cargo.toml similarity index 95% rename from contracts/mock-zapper/Cargo.toml rename to contracts/zapper/mock/Cargo.toml index b68e4d74b..46708a5ae 100644 --- a/contracts/mock-zapper/Cargo.toml +++ b/contracts/zapper/mock/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mars-mock-zapper" +name = "mars-zapper-mock" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } diff --git a/contracts/mock-zapper/examples/schema.rs b/contracts/zapper/mock/examples/schema.rs similarity index 100% rename from contracts/mock-zapper/examples/schema.rs rename to contracts/zapper/mock/examples/schema.rs diff --git a/contracts/mock-zapper/src/contract.rs b/contracts/zapper/mock/src/contract.rs similarity index 100% rename from contracts/mock-zapper/src/contract.rs rename to contracts/zapper/mock/src/contract.rs diff --git a/contracts/mock-zapper/src/error.rs b/contracts/zapper/mock/src/error.rs similarity index 100% rename from contracts/mock-zapper/src/error.rs rename to contracts/zapper/mock/src/error.rs diff --git a/contracts/mock-zapper/src/execute.rs b/contracts/zapper/mock/src/execute.rs similarity index 100% rename from contracts/mock-zapper/src/execute.rs rename to contracts/zapper/mock/src/execute.rs diff --git a/contracts/mock-zapper/src/lib.rs b/contracts/zapper/mock/src/lib.rs similarity index 100% rename from contracts/mock-zapper/src/lib.rs rename to contracts/zapper/mock/src/lib.rs diff --git a/contracts/mock-zapper/src/query.rs b/contracts/zapper/mock/src/query.rs similarity index 100% rename from contracts/mock-zapper/src/query.rs rename to contracts/zapper/mock/src/query.rs diff --git a/contracts/mock-zapper/src/state.rs b/contracts/zapper/mock/src/state.rs similarity index 100% rename from contracts/mock-zapper/src/state.rs rename to contracts/zapper/mock/src/state.rs diff --git a/contracts/zapper/osmosis/Cargo.toml b/contracts/zapper/osmosis/Cargo.toml new file mode 100644 index 000000000..cd2cf7006 --- /dev/null +++ b/contracts/zapper/osmosis/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "mars-zapper-osmosis" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +mars-zapper-base = { workspace = true } + +# FIXME: develop branch dependency till Apollo finish audit +cw-dex = { git = "https://github.com/apollodao/cw-dex", rev = "b9d18cfbba8aa09ae4ee9459d308cda87ae5ffb3", features = ["osmosis"] } + +[dev-dependencies] +cw-utils = { workspace = true } +osmosis-testing = { workspace = true } diff --git a/contracts/zapper/osmosis/src/contract.rs b/contracts/zapper/osmosis/src/contract.rs new file mode 100644 index 000000000..b3f78c160 --- /dev/null +++ b/contracts/zapper/osmosis/src/contract.rs @@ -0,0 +1,40 @@ +use crate::lp_pool::OsmosisLpPool; +use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cw2::set_contract_version; +use mars_zapper_base::{ContractError, ExecuteMsg, InstantiateMsg, QueryMsg, ZapperBase}; + +/// The Osmosis zapper contract inherits logic from the base zapper contract +pub type OsmosisZapper = ZapperBase; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version( + deps.storage, + &format!("crates.io:{}", CONTRACT_NAME), + CONTRACT_VERSION, + )?; + OsmosisZapper::default().instantiate(deps, msg) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + OsmosisZapper::default().execute(deps, env, info, msg) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + OsmosisZapper::default().query(deps, env, msg) +} diff --git a/contracts/zapper/osmosis/src/lib.rs b/contracts/zapper/osmosis/src/lib.rs new file mode 100644 index 000000000..d2b15554e --- /dev/null +++ b/contracts/zapper/osmosis/src/lib.rs @@ -0,0 +1,2 @@ +pub mod contract; +pub mod lp_pool; diff --git a/contracts/zapper/osmosis/src/lp_pool.rs b/contracts/zapper/osmosis/src/lp_pool.rs new file mode 100644 index 000000000..b28b008d2 --- /dev/null +++ b/contracts/zapper/osmosis/src/lp_pool.rs @@ -0,0 +1,43 @@ +use cosmwasm_std::Deps; +use cw_dex::osmosis::OsmosisPool; +use cw_dex::traits::Pool; +use cw_dex::CwDexError; +use mars_zapper_base::LpPool; +use std::str::FromStr; + +pub struct OsmosisLpPool {} + +impl OsmosisLpPool { + /// Returns the matching pool given a LP token. + /// + /// Based on impl from https://github.com/apollodao/cw-dex/blob/develop/src/implementations/pool.rs#L60 + pub fn get_pool_for_lp_token( + deps: Deps, + lp_token_denom: &str, + ) -> Result { + // The only Pool implementation that uses native denoms right now is Osmosis + if !lp_token_denom.starts_with("gamm/pool/") { + return Err(CwDexError::NotLpToken {}); + } + + let pool_id_str = lp_token_denom + .strip_prefix("gamm/pool/") + .ok_or(CwDexError::NotLpToken {})?; + + let pool_id = u64::from_str(pool_id_str).map_err(|_| CwDexError::NotLpToken {})?; + + Ok(OsmosisPool::new(pool_id, deps)?) + } +} + +impl LpPool for OsmosisLpPool { + fn get_pool_for_lp_token( + deps: Deps, + lp_token_denom: &str, + ) -> Result, CwDexError> { + Self::get_pool_for_lp_token(deps, lp_token_denom).map(|p| { + let as_trait: Box = Box::new(p); + as_trait + }) + } +} diff --git a/contracts/zapper/osmosis/tests/helpers.rs b/contracts/zapper/osmosis/tests/helpers.rs new file mode 100644 index 000000000..9c905fd2a --- /dev/null +++ b/contracts/zapper/osmosis/tests/helpers.rs @@ -0,0 +1,63 @@ +use std::fmt::Display; +use std::str::FromStr; + +use osmosis_testing::cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest; +use osmosis_testing::{Bank, OsmosisTestApp, RunnerError, SigningAccount, Wasm}; + +use mars_zapper_base::InstantiateMsg; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); + +pub fn wasm_file() -> String { + let artifacts_dir = + std::env::var("ARTIFACTS_DIR_PATH").unwrap_or_else(|_| "artifacts".to_string()); + let snaked_name = CONTRACT_NAME.replace('-', "_"); + format!("../../../{}/{}.wasm", artifacts_dir, snaked_name) +} + +pub fn instantiate_contract(wasm: &Wasm, admin: &SigningAccount) -> String { + println!("WASM name: {}", wasm_file()); + let wasm_byte_code = std::fs::read(wasm_file()).unwrap(); + let code_id = wasm + .store_code(&wasm_byte_code, None, admin) + .unwrap() + .data + .code_id; + + wasm.instantiate( + code_id, + &InstantiateMsg {}, + None, + Some("zapper-osmosis-contract"), + &[], + admin, + ) + .unwrap() + .data + .address +} + +pub fn query_balance(bank: &Bank, addr: &str, denom: &str) -> u128 { + bank.query_balance(&QueryBalanceRequest { + address: addr.to_string(), + denom: denom.to_string(), + }) + .unwrap() + .balance + .map(|c| u128::from_str(&c.amount).unwrap()) + .unwrap_or(0) +} + +pub fn assert_err(actual: RunnerError, expected: impl Display) { + match actual { + RunnerError::ExecuteError { msg } => { + println!("ExecuteError, msg: {}", msg); + assert!(msg.contains(&format!("{}", expected))) + } + RunnerError::QueryError { msg } => { + println!("QueryError, msg: {}", msg); + assert!(msg.contains(&format!("{}", expected))) + } + _ => panic!("Unhandled error"), + } +} diff --git a/contracts/zapper/osmosis/tests/test_callback.rs b/contracts/zapper/osmosis/tests/test_callback.rs new file mode 100644 index 000000000..45c3c3ea4 --- /dev/null +++ b/contracts/zapper/osmosis/tests/test_callback.rs @@ -0,0 +1,41 @@ +use cosmwasm_std::{coin, Addr, Coin}; +use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; + +use mars_zapper_base::{CallbackMsg, ContractError, ExecuteMsg}; + +use crate::helpers::{assert_err, instantiate_contract}; + +pub mod helpers; + +#[test] +fn test_only_contract_itself_can_callback() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let accs = app + .init_accounts( + &[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "uosmo"), + ], + 2, + ) + .unwrap(); + let admin = &accs[0]; + let user = &accs[1]; + + let contract_addr = instantiate_contract(&wasm, admin); + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::Callback(CallbackMsg::ReturnCoin { + balance_before: Coin::new(1u128, "gamm/pool/1"), + recipient: Addr::unchecked(user.address()), + }), + &[], + user, + ) + .unwrap_err(); + assert_err(res_err, ContractError::Unauthorized {}); +} diff --git a/contracts/zapper/osmosis/tests/test_provide_liquidity.rs b/contracts/zapper/osmosis/tests/test_provide_liquidity.rs new file mode 100644 index 000000000..d61a06477 --- /dev/null +++ b/contracts/zapper/osmosis/tests/test_provide_liquidity.rs @@ -0,0 +1,528 @@ +use cosmwasm_std::{coin, Coin, Uint128}; +use cw_dex::CwDexError; +use osmosis_testing::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; + +use mars_zapper_base::{ExecuteMsg, QueryMsg}; + +use crate::helpers::{assert_err, instantiate_contract, query_balance}; + +pub mod helpers; + +#[test] +fn test_provide_liquidity_with_invalid_lp_token() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::ProvideLiquidity { + lp_token_out: "INVALID_POOL".to_string(), + recipient: None, + minimum_receive: Uint128::one(), + }, + &[coin(1_000_000, "uatom"), coin(2_000_000, "uosmo")], + &signer, + ) + .unwrap_err(); + assert_err(res_err, CwDexError::NotLpToken {}); +} + +#[test] +fn test_provide_liquidity_with_invalid_coins() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let gamm = Gamm::new(&app); + let pool_id = gamm + .create_basic_pool( + &[coin(2_000_000, "uatom"), coin(4_000_000, "uosmo")], + &signer, + ) + .unwrap() + .data + .pool_id; + + let contract_addr = instantiate_contract(&wasm, &signer); + + // Generic error: Querier contract error: codespace: undefined, code: 1: execute wasm contract failed + wasm.execute( + &contract_addr, + &ExecuteMsg::ProvideLiquidity { + lp_token_out: format!("gamm/pool/{}", pool_id), + recipient: None, + minimum_receive: Uint128::one(), + }, + &[], + &signer, + ) + .unwrap_err(); +} + +#[test] +fn test_provide_liquidity_with_min_not_received() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let accs = app + .init_accounts( + &[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "uosmo"), + ], + 2, + ) + .unwrap(); + let admin = &accs[0]; + let user = &accs[1]; + + let gamm = Gamm::new(&app); + let pool_id = gamm + .create_basic_pool( + &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], + admin, + ) + .unwrap() + .data + .pool_id; + let pool_denom = format!("gamm/pool/{}", pool_id); + + let contract_addr = instantiate_contract(&wasm, admin); + + let bank = Bank::new(&app); + + let coins_in = vec![coin(5_000_000, "uatom"), coin(10_000_000, "uosmo")]; + + let estimate_amount: Uint128 = wasm + .query( + &contract_addr, + &QueryMsg::EstimateProvideLiquidity { + lp_token_out: pool_denom.clone(), + coins_in: coins_in.clone(), + }, + ) + .unwrap(); + + let min_receive = estimate_amount + Uint128::one(); + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::ProvideLiquidity { + lp_token_out: pool_denom.clone(), + recipient: None, + minimum_receive: min_receive, + }, + &coins_in, + user, + ) + .unwrap_err(); + assert_err( + res_err, + CwDexError::MinOutNotReceived { + min_out: min_receive, + received: Uint128::from(25000000000000000000u128), + }, + ); + + let contract_balance = query_balance(&bank, &contract_addr, &pool_denom); + assert_eq!(contract_balance, 0u128); + let user_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_balance, 0u128); +} + +#[test] +fn test_provide_liquidity_with_one_coin() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let uatom_acc_balance = 1_000_000_000_000u128; + let uosmo_acc_balance = 1_000_000_000_000u128; + let accs = app + .init_accounts( + &[ + coin(uatom_acc_balance, "uatom"), + coin(uosmo_acc_balance, "uosmo"), + ], + 2, + ) + .unwrap(); + let admin = &accs[0]; + let user = &accs[1]; + + let gamm = Gamm::new(&app); + let pool_id = gamm + .create_basic_pool( + &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], + admin, + ) + .unwrap() + .data + .pool_id; + let pool_denom = format!("gamm/pool/{}", pool_id); + + let contract_addr = instantiate_contract(&wasm, admin); + + let bank = Bank::new(&app); + + let contract_balance = query_balance(&bank, &contract_addr, &pool_denom); + assert_eq!(contract_balance, 0u128); + let user_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_balance, 0u128); + + let uatom_liquidity_amount = 1_000_000u128; + let coins_in = vec![coin(uatom_liquidity_amount, "uatom")]; + + let estimate_amount: Uint128 = wasm + .query( + &contract_addr, + &QueryMsg::EstimateProvideLiquidity { + lp_token_out: pool_denom.clone(), + coins_in: coins_in.clone(), + }, + ) + .unwrap(); + assert_eq!(estimate_amount.u128(), 2457308182481546200u128); + + wasm.execute( + &contract_addr, + &ExecuteMsg::ProvideLiquidity { + lp_token_out: pool_denom.clone(), + recipient: None, + minimum_receive: estimate_amount, + }, + &coins_in, + user, + ) + .unwrap(); + + let contract_pool_balance = query_balance(&bank, &contract_addr, &pool_denom); + assert_eq!(contract_pool_balance, 0u128); + let contract_uatom_balance = query_balance(&bank, &contract_addr, "uatom"); + assert_eq!(contract_uatom_balance, 0u128); + let contract_uosmo_balance = query_balance(&bank, &contract_addr, "uosmo"); + assert_eq!(contract_uosmo_balance, 0u128); + + let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_pool_balance, estimate_amount.u128()); + let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); + assert_eq!( + user_uatom_balance, + uatom_acc_balance - uatom_liquidity_amount + ); + let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); + assert_eq!(user_uosmo_balance, uosmo_acc_balance); +} + +#[test] +fn test_provide_liquidity_with_two_balanced_coins() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let uatom_acc_balance = 1_000_000_000_000u128; + let uosmo_acc_balance = 1_000_000_000_000u128; + let accs = app + .init_accounts( + &[ + coin(uatom_acc_balance, "uatom"), + coin(uosmo_acc_balance, "uosmo"), + ], + 2, + ) + .unwrap(); + let admin = &accs[0]; + let user = &accs[1]; + + let gamm = Gamm::new(&app); + let pool_id = gamm + .create_basic_pool( + &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], + admin, + ) + .unwrap() + .data + .pool_id; + let pool_denom = format!("gamm/pool/{}", pool_id); + + let contract_addr = instantiate_contract(&wasm, admin); + + let bank = Bank::new(&app); + + let contract_balance = query_balance(&bank, &contract_addr, &pool_denom); + assert_eq!(contract_balance, 0u128); + let user_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_balance, 0u128); + + let uatom_liquidity_amount = 5_000_000u128; + let uosmo_liquidity_amount = 10_000_000u128; + let coins_in = vec![ + coin(uatom_liquidity_amount, "uatom"), + coin(uosmo_liquidity_amount, "uosmo"), + ]; + + let estimate_amount: Uint128 = wasm + .query( + &contract_addr, + &QueryMsg::EstimateProvideLiquidity { + lp_token_out: pool_denom.clone(), + coins_in: coins_in.clone(), + }, + ) + .unwrap(); + assert_eq!(estimate_amount.u128(), 25000000000000000000u128); + + wasm.execute( + &contract_addr, + &ExecuteMsg::ProvideLiquidity { + lp_token_out: pool_denom.clone(), + recipient: None, + minimum_receive: estimate_amount, + }, + &coins_in, + user, + ) + .unwrap(); + + let contract_pool_balance = query_balance(&bank, &contract_addr, &pool_denom); + assert_eq!(contract_pool_balance, 0u128); + let contract_uatom_balance = query_balance(&bank, &contract_addr, "uatom"); + assert_eq!(contract_uatom_balance, 0u128); + let contract_uosmo_balance = query_balance(&bank, &contract_addr, "uosmo"); + assert_eq!(contract_uosmo_balance, 0u128); + + let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_pool_balance, estimate_amount.u128()); + let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); + assert_eq!( + user_uatom_balance, + uatom_acc_balance - uatom_liquidity_amount + ); + let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); + assert_eq!( + user_uosmo_balance, + uosmo_acc_balance - uosmo_liquidity_amount + ); +} + +#[test] +fn test_provide_liquidity_with_two_unbalanced_coins() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let uatom_acc_balance = 1_000_000_000_000u128; + let uosmo_acc_balance = 1_000_000_000_000u128; + let accs = app + .init_accounts( + &[ + coin(uatom_acc_balance, "uatom"), + coin(uosmo_acc_balance, "uosmo"), + ], + 2, + ) + .unwrap(); + let admin = &accs[0]; + let user = &accs[1]; + + let gamm = Gamm::new(&app); + let pool_id = gamm + .create_basic_pool( + &[coin(20_000_000_000, "uatom"), coin(40_000_000_000, "uosmo")], + admin, + ) + .unwrap() + .data + .pool_id; + let pool_denom = format!("gamm/pool/{}", pool_id); + + let contract_addr = instantiate_contract(&wasm, admin); + + let bank = Bank::new(&app); + + let contract_balance = query_balance(&bank, &contract_addr, &pool_denom); + assert_eq!(contract_balance, 0u128); + let user_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_balance, 0u128); + + let uatom_liquidity_amount = 5_000_000u128; + let uosmo_liquidity_amount = 22_000_000u128; + let coins_in = vec![ + coin(uatom_liquidity_amount, "uatom"), + coin(uosmo_liquidity_amount, "uosmo"), + ]; + + let estimate_amount: Uint128 = wasm + .query( + &contract_addr, + &QueryMsg::EstimateProvideLiquidity { + lp_token_out: pool_denom.clone(), + coins_in: coins_in.clone(), + }, + ) + .unwrap(); + assert_eq!(estimate_amount.u128(), 25000000000000000u128); + + // how much coins are taken from account to create LP + let estimate_coins: Vec = wasm + .query( + &contract_addr, + &QueryMsg::EstimateWithdrawLiquidity { + coin_in: coin(estimate_amount.u128(), pool_denom.clone()), + }, + ) + .unwrap(); + let uatom_estimate_amount = estimate_coins + .iter() + .find(|c| c.denom == "uatom") + .unwrap() + .amount + .u128(); + let uosmo_estimate_amount = estimate_coins + .iter() + .find(|c| c.denom == "uosmo") + .unwrap() + .amount + .u128(); + assert_eq!(uatom_estimate_amount, 4950000u128); + assert_eq!(uosmo_estimate_amount, 9900000u128); + + wasm.execute( + &contract_addr, + &ExecuteMsg::ProvideLiquidity { + lp_token_out: pool_denom.clone(), + recipient: None, + minimum_receive: estimate_amount, + }, + &coins_in, + user, + ) + .unwrap(); + + let contract_pool_balance = query_balance(&bank, &contract_addr, &pool_denom); + assert_eq!(contract_pool_balance, 0u128); + let contract_uatom_balance = query_balance(&bank, &contract_addr, "uatom"); + assert_eq!(contract_uatom_balance, 0u128); + let contract_uosmo_balance = query_balance(&bank, &contract_addr, "uosmo"); + assert_eq!(contract_uosmo_balance, 0u128); + + let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_pool_balance, estimate_amount.u128()); + let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); + assert_eq!(user_uatom_balance, 999995000000u128); + let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); + assert_eq!(user_uosmo_balance, 999990000000u128); +} + +#[test] +fn test_provide_liquidity_with_different_recipient() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let uatom_acc_balance = 1_000_000_000_000u128; + let uosmo_acc_balance = 1_000_000_000_000u128; + let accs = app + .init_accounts( + &[ + coin(uatom_acc_balance, "uatom"), + coin(uosmo_acc_balance, "uosmo"), + ], + 3, + ) + .unwrap(); + let admin = &accs[0]; + let user = &accs[1]; + let recipient = &accs[2]; + + let gamm = Gamm::new(&app); + let pool_id = gamm + .create_basic_pool( + &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], + admin, + ) + .unwrap() + .data + .pool_id; + let pool_denom = format!("gamm/pool/{}", pool_id); + + let contract_addr = instantiate_contract(&wasm, admin); + + let bank = Bank::new(&app); + + let contract_balance = query_balance(&bank, &contract_addr, &pool_denom); + assert_eq!(contract_balance, 0u128); + let user_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_balance, 0u128); + let recipient_balance = query_balance(&bank, &recipient.address(), &pool_denom); + assert_eq!(recipient_balance, 0u128); + + let uatom_liquidity_amount = 5_000_000u128; + let uosmo_liquidity_amount = 10_000_000u128; + let coins_in = vec![ + coin(uatom_liquidity_amount, "uatom"), + coin(uosmo_liquidity_amount, "uosmo"), + ]; + + let estimate_amount: Uint128 = wasm + .query( + &contract_addr, + &QueryMsg::EstimateProvideLiquidity { + lp_token_out: pool_denom.clone(), + coins_in: coins_in.clone(), + }, + ) + .unwrap(); + assert_eq!(estimate_amount.u128(), 25000000000000000000u128); + + wasm.execute( + &contract_addr, + &ExecuteMsg::ProvideLiquidity { + lp_token_out: pool_denom.clone(), + recipient: Some(recipient.address()), + minimum_receive: estimate_amount, + }, + &coins_in, + user, + ) + .unwrap(); + + let contract_pool_balance = query_balance(&bank, &contract_addr, &pool_denom); + assert_eq!(contract_pool_balance, 0u128); + let contract_uatom_balance = query_balance(&bank, &contract_addr, "uatom"); + assert_eq!(contract_uatom_balance, 0u128); + let contract_uosmo_balance = query_balance(&bank, &contract_addr, "uosmo"); + assert_eq!(contract_uosmo_balance, 0u128); + + let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_pool_balance, 0u128); + let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); + assert_eq!( + user_uatom_balance, + uatom_acc_balance - uatom_liquidity_amount + ); + let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); + assert_eq!( + user_uosmo_balance, + uosmo_acc_balance - uosmo_liquidity_amount + ); + + let recipient_pool_balance = query_balance(&bank, &recipient.address(), &pool_denom); + assert_eq!(recipient_pool_balance, estimate_amount.u128()); + let recipient_uatom_balance = query_balance(&bank, &recipient.address(), "uatom"); + assert_eq!(recipient_uatom_balance, uatom_acc_balance); + let recipient_uosmo_balance = query_balance(&bank, &recipient.address(), "uosmo"); + assert_eq!(recipient_uosmo_balance, uosmo_acc_balance); +} diff --git a/contracts/zapper/osmosis/tests/test_queries.rs b/contracts/zapper/osmosis/tests/test_queries.rs new file mode 100644 index 000000000..f6292f9b3 --- /dev/null +++ b/contracts/zapper/osmosis/tests/test_queries.rs @@ -0,0 +1,178 @@ +use cosmwasm_std::{coin, Coin, Uint128}; +use cw_dex::CwDexError; +use osmosis_testing::{Gamm, Module, OsmosisTestApp, Wasm}; +use std::ops::Div; +use std::str::FromStr; + +use mars_zapper_base::QueryMsg; + +use crate::helpers::{assert_err, instantiate_contract}; + +pub mod helpers; + +#[test] +fn test_estimate_provide_liquidity_with_invalid_lp_token() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[coin(1_000_000_000_000, "uosmo")]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let res_err = wasm + .query::( + &contract_addr, + &QueryMsg::EstimateProvideLiquidity { + lp_token_out: "INVALID_POOL".to_string(), + coins_in: vec![coin(500_000, "uatom"), coin(2_000_000, "uosmo")], + }, + ) + .unwrap_err(); + assert_err(res_err, CwDexError::NotLpToken {}); +} + +#[test] +fn test_estimate_provide_liquidity_with_invalid_coins() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let gamm = Gamm::new(&app); + let pool_id = gamm + .create_basic_pool( + &[coin(2_000_000, "uatom"), coin(4_000_000, "uosmo")], + &signer, + ) + .unwrap() + .data + .pool_id; + + let pool = gamm.query_pool(pool_id).unwrap(); + let lp_token = pool.total_shares.unwrap().denom; + assert_eq!(lp_token, "gamm/pool/1".to_string()); + + let contract_addr = instantiate_contract(&wasm, &signer); + + // Generic error: Querier contract error: codespace: undefined, code: 1: execute wasm contract failed + wasm.query::( + &contract_addr, + &QueryMsg::EstimateProvideLiquidity { + lp_token_out: lp_token, + coins_in: vec![], + }, + ) + .unwrap_err(); +} + +#[test] +fn test_estimate_provide_liquidity_successfully() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let gamm = Gamm::new(&app); + let pool_id = gamm + .create_basic_pool( + &[coin(2_000_000, "uatom"), coin(4_000_000, "uosmo")], + &signer, + ) + .unwrap() + .data + .pool_id; + + let pool = gamm.query_pool(pool_id).unwrap(); + let total_shares = pool.total_shares.unwrap(); + let total_amount = Uint128::from_str(&total_shares.amount).unwrap(); + assert_eq!(total_amount, Uint128::from(100000000000000000000u128)); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let amount: Uint128 = wasm + .query( + &contract_addr, + &QueryMsg::EstimateProvideLiquidity { + lp_token_out: total_shares.denom, + coins_in: vec![coin(1_000_000, "uatom"), coin(2_000_000, "uosmo")], + }, + ) + .unwrap(); + let expected_amount = total_amount.div(Uint128::from(2u8)); + assert_eq!(amount, expected_amount); +} + +#[test] +fn test_estimate_withdraw_liquidity_with_invalid_lp_token() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[coin(1_000_000_000_000, "uosmo")]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let res_err = wasm + .query::( + &contract_addr, + &QueryMsg::EstimateWithdrawLiquidity { + coin_in: coin(500_000, "INVALID_POOL"), + }, + ) + .unwrap_err(); + assert_err(res_err, CwDexError::NotLpToken {}); +} + +#[test] +fn test_estimate_withdraw_liquidity_successfully() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let gamm = Gamm::new(&app); + let pool_id = gamm + .create_basic_pool( + &[coin(2_000_000, "uatom"), coin(4_000_000, "uosmo")], + &signer, + ) + .unwrap() + .data + .pool_id; + + let pool = gamm.query_pool(pool_id).unwrap(); + let total_shares = pool.total_shares.unwrap(); + let total_amount = Uint128::from_str(&total_shares.amount).unwrap(); + assert_eq!(total_amount, Uint128::from(100000000000000000000u128)); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let withdraw_amount = total_amount.div(Uint128::from(2u8)); + let coins: Vec = wasm + .query( + &contract_addr, + &QueryMsg::EstimateWithdrawLiquidity { + coin_in: coin(withdraw_amount.u128(), total_shares.denom), + }, + ) + .unwrap(); + assert_eq!(coins, vec![coin(990000, "uatom"), coin(1980000, "uosmo")]) +} diff --git a/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs b/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs new file mode 100644 index 000000000..d6702138a --- /dev/null +++ b/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs @@ -0,0 +1,325 @@ +use cosmwasm_std::{coin, Coin, Uint128}; +use cw_dex::CwDexError; +use cw_utils::PaymentError; +use osmosis_testing::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; + +use mars_zapper_base::{ContractError, ExecuteMsg, QueryMsg}; + +use crate::helpers::{assert_err, instantiate_contract, query_balance}; + +pub mod helpers; + +#[test] +fn test_withdraw_liquidity_without_funds() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "gamm/pool/1"), + coin(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::WithdrawLiquidity { recipient: None }, + &[], + &signer, + ) + .unwrap_err(); + assert_err( + res_err, + ContractError::PaymentError(PaymentError::NoFunds {}), + ); +} + +#[test] +fn test_withdraw_liquidity_with_more_than_one_coin_sent() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "gamm/pool/1"), + coin(1_000_000_000_000, "uosmo"), + ]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::WithdrawLiquidity { recipient: None }, + &[coin(1_000_000, "gamm/pool/1"), coin(2_000_000, "uosmo")], + &signer, + ) + .unwrap_err(); + assert_err( + res_err, + ContractError::PaymentError(PaymentError::MultipleDenoms {}), + ); +} + +#[test] +fn test_withdraw_liquidity_with_invalid_lp_token() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let signer = app + .init_account(&[coin(1_000_000_000_000, "uosmo")]) + .unwrap(); + + let contract_addr = instantiate_contract(&wasm, &signer); + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::WithdrawLiquidity { recipient: None }, + &[coin(1_000_000, "uosmo")], + &signer, + ) + .unwrap_err(); + assert_err(res_err, CwDexError::NotLpToken {}); +} + +#[test] +fn test_withdraw_liquidity_successfully() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let uatom_acc_balance = 1_000_000_000_000u128; + let uosmo_acc_balance = 1_000_000_000_000u128; + let accs = app + .init_accounts( + &[ + coin(uatom_acc_balance, "uatom"), + coin(uosmo_acc_balance, "uosmo"), + ], + 2, + ) + .unwrap(); + let admin = &accs[0]; + let user = &accs[1]; + + let gamm = Gamm::new(&app); + let pool_id = gamm + .create_basic_pool( + &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], + admin, + ) + .unwrap() + .data + .pool_id; + let pool_denom = format!("gamm/pool/{}", pool_id); + + let contract_addr = instantiate_contract(&wasm, admin); + + let bank = Bank::new(&app); + + let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_pool_balance, 0u128); + + let uatom_liquidity_amount = 5_000_000u128; + let uosmo_liquidity_amount = 10_000_000u128; + wasm.execute( + &contract_addr, + &ExecuteMsg::ProvideLiquidity { + lp_token_out: pool_denom.clone(), + recipient: None, + minimum_receive: Uint128::one(), + }, + &[ + coin(uatom_liquidity_amount, "uatom"), + coin(uosmo_liquidity_amount, "uosmo"), + ], + user, + ) + .unwrap(); + + let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_pool_balance, 25000000000000000000u128); + let user_uatom_balance_before = query_balance(&bank, &user.address(), "uatom"); + let user_uosmo_balance_before = query_balance(&bank, &user.address(), "uosmo"); + + let estimate_coins: Vec = wasm + .query( + &contract_addr, + &QueryMsg::EstimateWithdrawLiquidity { + coin_in: coin(user_pool_balance, &pool_denom), + }, + ) + .unwrap(); + let uatom_estimate_amount = estimate_coins + .iter() + .find(|c| c.denom == "uatom") + .unwrap() + .amount + .u128(); + let uosmo_estimate_amount = estimate_coins + .iter() + .find(|c| c.denom == "uosmo") + .unwrap() + .amount + .u128(); + assert_eq!(uatom_estimate_amount, 4950000u128); + assert_eq!(uosmo_estimate_amount, 9900000u128); + + wasm.execute( + &contract_addr, + &ExecuteMsg::WithdrawLiquidity { recipient: None }, + &[coin(user_pool_balance, &pool_denom)], + user, + ) + .unwrap(); + + let contract_pool_balance = query_balance(&bank, &contract_addr, &pool_denom); + assert_eq!(contract_pool_balance, 0u128); + let contract_uatom_balance = query_balance(&bank, &contract_addr, "uatom"); + assert_eq!(contract_uatom_balance, 0u128); + let contract_uosmo_balance = query_balance(&bank, &contract_addr, "uosmo"); + assert_eq!(contract_uosmo_balance, 0u128); + + let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_pool_balance, 0u128); + let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); + assert_eq!( + user_uatom_balance, + user_uatom_balance_before + uatom_estimate_amount + ); + let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); + assert_eq!( + user_uosmo_balance, + user_uosmo_balance_before + uosmo_estimate_amount + ); +} + +#[test] +fn test_withdraw_liquidity_with_different_recipient_successfully() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let uatom_acc_balance = 1_000_000_000_000u128; + let uosmo_acc_balance = 1_000_000_000_000u128; + let accs = app + .init_accounts( + &[ + coin(uatom_acc_balance, "uatom"), + coin(uosmo_acc_balance, "uosmo"), + ], + 3, + ) + .unwrap(); + let admin = &accs[0]; + let user = &accs[1]; + let recipient = &accs[2]; + + let gamm = Gamm::new(&app); + let pool_id = gamm + .create_basic_pool( + &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], + admin, + ) + .unwrap() + .data + .pool_id; + let pool_denom = format!("gamm/pool/{}", pool_id); + + let contract_addr = instantiate_contract(&wasm, admin); + + let bank = Bank::new(&app); + + let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_pool_balance, 0u128); + + let uatom_liquidity_amount = 5_000_000u128; + let uosmo_liquidity_amount = 10_000_000u128; + wasm.execute( + &contract_addr, + &ExecuteMsg::ProvideLiquidity { + lp_token_out: pool_denom.clone(), + recipient: None, + minimum_receive: Uint128::one(), + }, + &[ + coin(uatom_liquidity_amount, "uatom"), + coin(uosmo_liquidity_amount, "uosmo"), + ], + user, + ) + .unwrap(); + + let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_pool_balance, 25000000000000000000u128); + let user_uatom_balance_before = query_balance(&bank, &user.address(), "uatom"); + let user_uosmo_balance_before = query_balance(&bank, &user.address(), "uosmo"); + + let recipient_pool_balance = query_balance(&bank, &recipient.address(), &pool_denom); + assert_eq!(recipient_pool_balance, 0u128); + let recipient_uatom_balance_before = query_balance(&bank, &recipient.address(), "uatom"); + let recipient_uosmo_balance_before = query_balance(&bank, &recipient.address(), "uosmo"); + + let estimate_coins: Vec = wasm + .query( + &contract_addr, + &QueryMsg::EstimateWithdrawLiquidity { + coin_in: coin(user_pool_balance, &pool_denom), + }, + ) + .unwrap(); + let uatom_estimate_amount = estimate_coins + .iter() + .find(|c| c.denom == "uatom") + .unwrap() + .amount + .u128(); + let uosmo_estimate_amount = estimate_coins + .iter() + .find(|c| c.denom == "uosmo") + .unwrap() + .amount + .u128(); + assert_eq!(uatom_estimate_amount, 4950000u128); + assert_eq!(uosmo_estimate_amount, 9900000u128); + + wasm.execute( + &contract_addr, + &ExecuteMsg::WithdrawLiquidity { + recipient: Some(recipient.address()), + }, + &[coin(user_pool_balance, &pool_denom)], + user, + ) + .unwrap(); + + let contract_pool_balance = query_balance(&bank, &contract_addr, &pool_denom); + assert_eq!(contract_pool_balance, 0u128); + let contract_uatom_balance = query_balance(&bank, &contract_addr, "uatom"); + assert_eq!(contract_uatom_balance, 0u128); + let contract_uosmo_balance = query_balance(&bank, &contract_addr, "uosmo"); + assert_eq!(contract_uosmo_balance, 0u128); + + let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); + assert_eq!(user_pool_balance, 0u128); + let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); + assert_eq!(user_uatom_balance, user_uatom_balance_before); + let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); + assert_eq!(user_uosmo_balance, user_uosmo_balance_before); + + let recipient_pool_balance = query_balance(&bank, &recipient.address(), &pool_denom); + assert_eq!(recipient_pool_balance, 0u128); + let recipient_uatom_balance = query_balance(&bank, &recipient.address(), "uatom"); + assert_eq!( + recipient_uatom_balance, + recipient_uatom_balance_before + uatom_estimate_amount + ); + let recipient_uosmo_balance = query_balance(&bank, &recipient.address(), "uosmo"); + assert_eq!( + recipient_uosmo_balance, + recipient_uosmo_balance_before + uosmo_estimate_amount + ); +} diff --git a/coverage_grcov.Makefile.toml b/coverage_grcov.Makefile.toml index 2e7213f81..d77fbc7bb 100644 --- a/coverage_grcov.Makefile.toml +++ b/coverage_grcov.Makefile.toml @@ -31,6 +31,7 @@ condition = { env_not_set = ["SKIP_INSTALL_GRCOV"] } private = true install_crate = { crate_name = "grcov" } +# NOTE: ignore coverage for swapper and zapper contracts because their tests are based on `osmosis-testing` which don't work for grcov [tasks.coverage-grcov] condition = { rust_version = { min = "1.60.0" } } private = true @@ -40,7 +41,7 @@ set -eux grcov ${COVERAGE_PROF_OUTPUT} \ -b ${COVERAGE_BINARIES} -s ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY} \ - -t ${GRCOV_OUTPUT_TYPE} --llvm --branch --ignore-not-existing --ignore "/*" --ignore "*/tests/*" --ignore "*/mock*" --ignore "target/*" -o ${GRCOV_OUTPUT_PATH} + -t ${GRCOV_OUTPUT_TYPE} --llvm --branch --ignore-not-existing --ignore "/*" --ignore "*/tests/*" --ignore "*/mock*" --ignore "target/*" --ignore "*/swapper/*" --ignore "*/zapper/*" -o ${GRCOV_OUTPUT_PATH} ''' dependencies = ["install-grcov", "coverage-grcov-prepare-outdir", "coverage-grcov-run-test"] diff --git a/packages/rover/src/coins.rs b/packages/rover/src/coins.rs index b206b83e5..4305e8bb2 100644 --- a/packages/rover/src/coins.rs +++ b/packages/rover/src/coins.rs @@ -3,6 +3,7 @@ use std::collections::{BTreeMap, HashSet}; use std::fmt; use std::str::FromStr; +use crate::msg::execute::ActionCoin; use crate::traits::{Denoms, Stringify}; use cosmwasm_std::{Coin, StdError, StdResult, Uint128}; use schemars::JsonSchema; @@ -297,3 +298,9 @@ impl Denoms for Vec { self.iter().map(|c| c.denom.as_str()).collect() } } + +impl Denoms for Vec { + fn to_denoms(&self) -> Vec<&str> { + self.iter().map(|c| c.denom.as_str()).collect() + } +} diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 90957adcd..38b9299e4 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -30,6 +30,36 @@ pub enum ExecuteMsg { Callback(CallbackMsg), } +#[cw_serde] +pub enum ActionAmount { + Exact(Uint128), + AccountBalance, +} + +impl ActionAmount { + pub fn value(&self) -> Option { + match self { + ActionAmount::Exact(amt) => Some(*amt), + ActionAmount::AccountBalance => None, + } + } +} + +#[cw_serde] +pub struct ActionCoin { + pub denom: String, + pub amount: ActionAmount, +} + +impl From<&Coin> for ActionCoin { + fn from(value: &Coin) -> Self { + Self { + denom: value.denom.to_string(), + amount: ActionAmount::Exact(value.amount), + } + } +} + /// The list of actions that users can perform on their positions #[cw_serde] pub enum Action { @@ -39,18 +69,14 @@ pub enum Action { Withdraw(Coin), /// Borrow coin of specified amount from Red Bank Borrow(Coin), - /// Repay coin of specified amount back to Red Bank. If `amount: None` is passed, + /// Repay coin of specified amount back to Red Bank. If `amount: AccountBalance` is passed, /// the repaid amount will be the minimum between account balance for denom and total owed. - Repay { - denom: String, - amount: Option, - }, + Repay(ActionCoin), /// Deposit coins into vault strategy - /// If amount sent is None, Rover attempts to deposit the account's entire balance into the vault + /// If `coin.amount: AccountBalance`, Rover attempts to deposit the account's entire balance into the vault EnterVault { vault: VaultUnchecked, - denom: String, - amount: Option, + coin: ActionCoin, }, /// Withdraw underlying coins from vault ExitVault { @@ -93,25 +119,21 @@ pub enum Action { position_type: VaultPositionType, }, /// Perform a swapper with an exact-in amount. Requires slippage allowance %. - /// If `coin_in_amount: None`, the accounts entire balance of `coin_in_denom` will be used. + /// If `coin_in.amount: AccountBalance`, the accounts entire balance of `coin_in.denom` will be used. SwapExactIn { - coin_in_denom: String, - coin_in_amount: Option, + coin_in: ActionCoin, denom_out: String, slippage: Decimal, }, /// Add Vec to liquidity pool in exchange for LP tokens ProvideLiquidity { - coins_in: Vec, + coins_in: Vec, lp_token_out: String, minimum_receive: Uint128, }, /// Send LP token and withdraw corresponding reserve assets from pool. - /// If `lp_token_amount: None`, the account balance of `lp_token_denom` will be used. - WithdrawLiquidity { - lp_token_denom: String, - lp_token_amount: Option, - }, + /// If `lp_token.amount: AccountBalance`, the account balance of `lp_token.denom` will be used. + WithdrawLiquidity { lp_token: ActionCoin }, /// Refunds all coin balances back to user wallet RefundAllCoinBalances {}, } @@ -131,12 +153,11 @@ pub enum CallbackMsg { Borrow { account_id: String, coin: Coin }, /// Repay coin of specified amount back to Red Bank; /// Decrement the token's coin amount and debt shares; - /// If `amount: None` is passed, the repaid amount will be the minimum + /// If `coin.amount: AccountBalance` is passed, the repaid amount will be the minimum /// between account balance for denom and total owed; Repay { account_id: String, - denom: String, - amount: Option, + coin: ActionCoin, }, /// Calculate the account's max loan-to-value health factor. If above 1, /// emits a `position_changed` event. If 1 or below, raises an error. @@ -145,8 +166,7 @@ pub enum CallbackMsg { EnterVault { account_id: String, vault: Vault, - denom: String, - amount: Option, + coin: ActionCoin, }, /// Exchanges vault LP shares for assets ExitVault { @@ -189,11 +209,10 @@ pub enum CallbackMsg { position_type: VaultPositionType, }, /// Perform a swapper with an exact-in amount. Requires slippage allowance %. - /// If `coin_in_amount: None`, the accounts entire balance of `coin_in_denom` will be used. + /// If `coin_in.amount: AccountBalance`, the accounts entire balance of `coin_in.denom` will be used. SwapExactIn { account_id: String, - coin_in_denom: String, - coin_in_amount: Option, + coin_in: ActionCoin, denom_out: String, slippage: Decimal, }, @@ -207,16 +226,15 @@ pub enum CallbackMsg { /// Add Vec to liquidity pool in exchange for LP tokens ProvideLiquidity { account_id: String, - coins_in: Vec, + coins_in: Vec, lp_token_out: String, minimum_receive: Uint128, }, /// Send LP token and withdraw corresponding reserve assets from pool. - /// If `lp_token_amount: None`, the account balance of `lp_token_denom` will be used. + /// If `lp_token.amount: AccountBalance`, the account balance of `lp_token.denom` will be used. WithdrawLiquidity { account_id: String, - lp_token_denom: String, - lp_token_amount: Option, + lp_token: ActionCoin, }, /// Checks to ensure only one vault position is taken per credit account AssertOneVaultPositionOnly { account_id: String }, diff --git a/schema.Makefile.toml b/schema.Makefile.toml index 307173012..ed5994f9c 100644 --- a/schema.Makefile.toml +++ b/schema.Makefile.toml @@ -15,10 +15,10 @@ fn main() -> std::io::Result<()> { "mars-account-nft", "mars-swapper-base", "mars-oracle-adapter", + "mars-zapper-base", "mars-mock-red-bank", "mars-mock-vault", "mars-mock-oracle", - "mars-mock-zapper", "mars-mock-credit-manager", ]; diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 22b6d9934..794ad3ffe 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -316,39 +316,20 @@ "additionalProperties": false }, { - "description": "Repay coin of specified amount back to Red Bank. If `amount: None` is passed, the repaid amount will be the minimum between account balance for denom and total owed.", + "description": "Repay coin of specified amount back to Red Bank. If `amount: AccountBalance` is passed, the repaid amount will be the minimum between account balance for denom and total owed.", "type": "object", "required": [ "repay" ], "properties": { "repay": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "amount": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "denom": { - "type": "string" - } - }, - "additionalProperties": false + "$ref": "#/definitions/ActionCoin" } }, "additionalProperties": false }, { - "description": "Deposit coins into vault strategy If amount sent is None, Rover attempts to deposit the account's entire balance into the vault", + "description": "Deposit coins into vault strategy If `coin.amount: AccountBalance`, Rover attempts to deposit the account's entire balance into the vault", "type": "object", "required": [ "enter_vault" @@ -357,22 +338,12 @@ "enter_vault": { "type": "object", "required": [ - "denom", + "coin", "vault" ], "properties": { - "amount": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "denom": { - "type": "string" + "coin": { + "$ref": "#/definitions/ActionCoin" }, "vault": { "$ref": "#/definitions/VaultBase_for_String" @@ -535,7 +506,7 @@ "additionalProperties": false }, { - "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %. If `coin_in_amount: None`, the accounts entire balance of `coin_in_denom` will be used.", + "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %. If `coin_in.amount: AccountBalance`, the accounts entire balance of `coin_in.denom` will be used.", "type": "object", "required": [ "swap_exact_in" @@ -544,23 +515,13 @@ "swap_exact_in": { "type": "object", "required": [ - "coin_in_denom", + "coin_in", "denom_out", "slippage" ], "properties": { - "coin_in_amount": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "coin_in_denom": { - "type": "string" + "coin_in": { + "$ref": "#/definitions/ActionCoin" }, "denom_out": { "type": "string" @@ -592,7 +553,7 @@ "coins_in": { "type": "array", "items": { - "$ref": "#/definitions/Coin" + "$ref": "#/definitions/ActionCoin" } }, "lp_token_out": { @@ -608,7 +569,7 @@ "additionalProperties": false }, { - "description": "Send LP token and withdraw corresponding reserve assets from pool. If `lp_token_amount: None`, the account balance of `lp_token_denom` will be used.", + "description": "Send LP token and withdraw corresponding reserve assets from pool. If `lp_token.amount: AccountBalance`, the account balance of `lp_token.denom` will be used.", "type": "object", "required": [ "withdraw_liquidity" @@ -617,21 +578,11 @@ "withdraw_liquidity": { "type": "object", "required": [ - "lp_token_denom" + "lp_token" ], "properties": { - "lp_token_amount": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "lp_token_denom": { - "type": "string" + "lp_token": { + "$ref": "#/definitions/ActionCoin" } }, "additionalProperties": false @@ -655,6 +606,44 @@ } ] }, + "ActionAmount": { + "oneOf": [ + { + "type": "string", + "enum": [ + "account_balance" + ] + }, + { + "type": "object", + "required": [ + "exact" + ], + "properties": { + "exact": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + ] + }, + "ActionCoin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/ActionAmount" + }, + "denom": { + "type": "string" + } + }, + "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" @@ -766,7 +755,7 @@ "additionalProperties": false }, { - "description": "Repay coin of specified amount back to Red Bank; Decrement the token's coin amount and debt shares; If `amount: None` is passed, the repaid amount will be the minimum between account balance for denom and total owed;", + "description": "Repay coin of specified amount back to Red Bank; Decrement the token's coin amount and debt shares; If `coin.amount: AccountBalance` is passed, the repaid amount will be the minimum between account balance for denom and total owed;", "type": "object", "required": [ "repay" @@ -776,24 +765,14 @@ "type": "object", "required": [ "account_id", - "denom" + "coin" ], "properties": { "account_id": { "type": "string" }, - "amount": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "denom": { - "type": "string" + "coin": { + "$ref": "#/definitions/ActionCoin" } }, "additionalProperties": false @@ -834,25 +813,15 @@ "type": "object", "required": [ "account_id", - "denom", + "coin", "vault" ], "properties": { "account_id": { "type": "string" }, - "amount": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "denom": { - "type": "string" + "coin": { + "$ref": "#/definitions/ActionCoin" }, "vault": { "$ref": "#/definitions/VaultBase_for_Addr" @@ -1063,7 +1032,7 @@ "additionalProperties": false }, { - "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %. If `coin_in_amount: None`, the accounts entire balance of `coin_in_denom` will be used.", + "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %. If `coin_in.amount: AccountBalance`, the accounts entire balance of `coin_in.denom` will be used.", "type": "object", "required": [ "swap_exact_in" @@ -1073,7 +1042,7 @@ "type": "object", "required": [ "account_id", - "coin_in_denom", + "coin_in", "denom_out", "slippage" ], @@ -1081,18 +1050,8 @@ "account_id": { "type": "string" }, - "coin_in_amount": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "coin_in_denom": { - "type": "string" + "coin_in": { + "$ref": "#/definitions/ActionCoin" }, "denom_out": { "type": "string" @@ -1160,7 +1119,7 @@ "coins_in": { "type": "array", "items": { - "$ref": "#/definitions/Coin" + "$ref": "#/definitions/ActionCoin" } }, "lp_token_out": { @@ -1176,7 +1135,7 @@ "additionalProperties": false }, { - "description": "Send LP token and withdraw corresponding reserve assets from pool. If `lp_token_amount: None`, the account balance of `lp_token_denom` will be used.", + "description": "Send LP token and withdraw corresponding reserve assets from pool. If `lp_token.amount: AccountBalance`, the account balance of `lp_token.denom` will be used.", "type": "object", "required": [ "withdraw_liquidity" @@ -1186,24 +1145,14 @@ "type": "object", "required": [ "account_id", - "lp_token_denom" + "lp_token" ], "properties": { "account_id": { "type": "string" }, - "lp_token_amount": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "lp_token_denom": { - "type": "string" + "lp_token": { + "$ref": "#/definitions/ActionCoin" } }, "additionalProperties": false diff --git a/schemas/mars-mock-zapper/mars-mock-zapper.json b/schemas/mars-zapper-base/mars-zapper-base.json similarity index 76% rename from schemas/mars-mock-zapper/mars-mock-zapper.json rename to schemas/mars-zapper-base/mars-zapper-base.json index d49ec71c3..a4b2c27e5 100644 --- a/schemas/mars-mock-zapper/mars-mock-zapper.json +++ b/schemas/mars-zapper-base/mars-zapper-base.json @@ -1,58 +1,12 @@ { - "contract_name": "mars-mock-zapper", + "contract_name": "mars-zapper-base", "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "InstantiateMsg", "type": "object", - "required": [ - "lp_configs", - "oracle" - ], - "properties": { - "lp_configs": { - "type": "array", - "items": { - "$ref": "#/definitions/LpConfig" - } - }, - "oracle": { - "$ref": "#/definitions/OracleBase_for_String" - } - }, - "additionalProperties": false, - "definitions": { - "LpConfig": { - "type": "object", - "required": [ - "lp_pair_denoms", - "lp_token_denom" - ], - "properties": { - "lp_pair_denoms": { - "type": "array", - "items": [ - { - "type": "string" - }, - { - "type": "string" - } - ], - "maxItems": 2, - "minItems": 2 - }, - "lp_token_denom": { - "type": "string" - } - }, - "additionalProperties": false - }, - "OracleBase_for_String": { - "type": "string" - } - } + "additionalProperties": false }, "execute": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -109,9 +63,69 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "callback" + ], + "properties": { + "callback": { + "$ref": "#/definitions/CallbackMsg" + } + }, + "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" + }, + "CallbackMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "return_coin" + ], + "properties": { + "return_coin": { + "type": "object", + "required": [ + "balance_before", + "recipient" + ], + "properties": { + "balance_before": { + "$ref": "#/definitions/Coin" + }, + "recipient": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 1ea87aeb3..1e06b9405 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,8 @@ { - "mockVault": "osmo1487e354fslk684alf5d2aewucaxts6cjp7sxah085qcvyla2w9ns376wgm", - "marsOracleAdapter": "osmo14mmr2teztgs9w3vp3q87ycaaulf78x5k7re85zj9qy93ays0m7vsapuykz", - "swapper": "osmo1jl3sh8mhtgfwjea5plvxe3suu2ywq5k8qmzx8u3w85gkkqynjmpq65e2sj", - "creditManager": "osmo160hkkaddp2j2ph5gw35mecnrntr0tcdml9he38dw22f3e5alne4quu7vjy", - "accountNft": "osmo1t86ljlx9s70jucxntdhyg829hvqw2d3l9dkcsewsp05llmgmk4fs3jsr8v" + "mockVault": "osmo1r5p5n3ds36sl99l73znhr2u74a7tlqdxp648c2n0wmkprlufdadqgwnw9u", + "marsOracleAdapter": "osmo14vpwv95jnwxcdcqpv27gqey0p6r48canax9er3vdq8cddg79jv8s40kf33", + "swapper": "osmo1940km39gjdxxprg5q0220d2upz9ff3eju5jmcrfpp3dcehct4a9spm8yxl", + "zapper": "osmo1c90nreaa93p4g6hs2zhyaxtc5fjpwa7snzecrgfc3kr8t24cpwyshllpa5", + "creditManager": "osmo1jjle0v4vgeusqg3h80uxnvwwdd078u5a4nhzukqjhzj3nf88pnkqrpecum", + "accountNft": "osmo1cxexgfldv506h4ctmyp3zc7v2zc0tsjqhyy9qe3cvncljl507egqcyy35w" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index cd462caff..2accb744f 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -7,6 +7,7 @@ import { InstantiateMsgs } from '../../types/instantiateMsgs' import { InstantiateMsg as NftInstantiateMsg } from '../../types/generated/mars-account-nft/MarsAccountNft.types' import { InstantiateMsg as VaultInstantiateMsg } from '../../types/generated/mars-mock-vault/MarsMockVault.types' import { InstantiateMsg as SwapperInstantiateMsg } from '../../types/generated/mars-swapper-base/MarsSwapperBase.types' +import { InstantiateMsg as ZapperInstantiateMsg } from '../../types/generated/mars-zapper-base/MarsZapperBase.types' import { InstantiateMsg as RoverInstantiateMsg } from '../../types/generated/mars-credit-manager/MarsCreditManager.types' import { InstantiateMsg as OracleAdapterInstantiateMsg } from '../../types/generated/mars-oracle-adapter/MarsOracleAdapter.types' import { Rover } from './rover' @@ -158,6 +159,11 @@ export class Deployer { } } + async instantiateZapper() { + const msg: ZapperInstantiateMsg = {} + await this.instantiate('zapper', this.storage.codeIds.zapper!, msg) + } + async instantiateCreditManager() { const msg: RoverInstantiateMsg = { max_unlocking_positions: this.config.maxUnlockingPositions, diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index e2261ad97..82d073f2d 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -6,9 +6,14 @@ import { wasmFile } from '../../utils/environment' export interface TaskRunnerProps { config: DeploymentConfig swapperContractName: string + zapperContractName: string } -export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProps) => { +export const taskRunner = async ({ + config, + swapperContractName, + zapperContractName, +}: TaskRunnerProps) => { const deployer = await setupDeployer(config) try { // Upload contracts @@ -16,6 +21,7 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp await deployer.upload('mockVault', wasmFile('mars_mock_vault')) await deployer.upload('marsOracleAdapter', wasmFile('mars_oracle_adapter')) await deployer.upload('swapper', wasmFile(swapperContractName)) + await deployer.upload('zapper', wasmFile(zapperContractName)) await deployer.upload('creditManager', wasmFile('mars_credit_manager')) // Set contracts owner @@ -25,6 +31,7 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp await deployer.instantiateMockVault() await deployer.instantiateMarsOracleAdapter() await deployer.instantiateSwapper() + await deployer.instantiateZapper() await deployer.instantiateCreditManager() await deployer.instantiateNftContract() await deployer.transferNftContractOwnership() @@ -35,6 +42,7 @@ export const taskRunner = async ({ config, swapperContractName }: TaskRunnerProp await deployer.grantCreditLines() await deployer.setupOraclePrices() await deployer.setupRedBankMarketsForZapDenoms() + const rover = await deployer.newUserRoverClient(config.testActions) await rover.createCreditAccount() await rover.deposit() diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 50fcc1187..5f52ab4c0 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -88,7 +88,9 @@ export class Rover { async repay() { const amount = this.actions.repayAmount - await this.updateCreditAccount([{ repay: { amount, denom: this.actions.secondaryDenom } }]) + await this.updateCreditAccount([ + { repay: { amount: { exact: amount }, denom: this.actions.secondaryDenom } }, + ]) const positions = await this.query.positions({ accountId: this.accountId! }) printGreen( `Repaid to RedBank: ${amount} ${ @@ -107,8 +109,7 @@ export class Rover { await this.updateCreditAccount([ { swap_exact_in: { - coin_in_amount: amount, - coin_in_denom: this.config.chain.baseDenom, + coin_in: { amount: { exact: amount }, denom: this.config.chain.baseDenom }, denom_out: this.actions.secondaryDenom, slippage: this.actions.swap.slippage, }, @@ -123,7 +124,10 @@ export class Rover { await this.updateCreditAccount([ { provide_liquidity: { - coins_in: this.actions.zap.coinsIn.map((c) => ({ denom: c.denom, amount: c.amount })), + coins_in: this.actions.zap.coinsIn.map((c) => ({ + denom: c.denom, + amount: { exact: c.amount }, + })), lp_token_out, minimum_receive: '1', }, @@ -146,8 +150,7 @@ export class Rover { await this.updateCreditAccount([ { withdraw_liquidity: { - lp_token_denom: lpToken.denom, - lp_token_amount: lpToken.amount, + lp_token: { amount: { exact: lpToken.amount }, denom: lpToken.denom }, }, }, ]) @@ -167,8 +170,10 @@ export class Rover { await this.updateCreditAccount([ { enter_vault: { - amount: this.actions.vault.depositAmount, - denom: info.tokens.base_token, + coin: { + amount: { exact: this.actions.vault.depositAmount }, + denom: info.tokens.base_token, + }, vault: { address: v.vault.address }, }, }, diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 68331d4c1..fbc1c5289 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -5,7 +5,7 @@ const uatom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5 const udig = 'ibc/307E5C96C8F60D1CBEE269A9A86C0834E1DB06F2B3788AE4F716EDB97A48B97D' const gammPool1 = 'gamm/pool/1' -const autoCompoundingVault = 'osmo1lcnpd5000ru7qpd0tz8wnl00rlfvlxvqlw04md9cxsudapd0flvsqke5t5' +const autoCompoundingVault = 'osmo1v40lnedgvake8p7f49gvqu0q3vc9sx3qpc0jqtyfdyw25d4vg8us38an37' export const osmosisTestnetConfig: DeploymentConfig = { allowedCoins: [uosmo, uatom, gammPool1], @@ -30,7 +30,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { base_denom: gammPool1, method: 'preview_redeem', vault_coin_denom: - 'factory/osmo1lcnpd5000ru7qpd0tz8wnl00rlfvlxvqlw04md9cxsudapd0flvsqke5t5/cwVTT', + 'factory/osmo1v40lnedgvake8p7f49gvqu0q3vc9sx3qpc0jqtyfdyw25d4vg8us38an37/cwVTT', }, ], }, @@ -44,7 +44,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { // https://github.com/apollodao/apollo-config/blob/master/config.json#L114 vault: { address: autoCompoundingVault }, config: { - deposit_cap: { denom: uosmo, amount: '1000000000' }, + deposit_cap: { denom: uosmo, amount: '100000000000000000000000000' }, // 100 osmo liquidation_threshold: '0.75', max_ltv: '0.65', whitelisted: true, @@ -57,7 +57,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { withdrawAmount: '1', mock: { config: { - deposit_cap: { denom: uosmo, amount: '100000000000' }, + deposit_cap: { denom: uosmo, amount: '100000000000000000000000000' }, // 100 osmo liquidation_threshold: '0.75', max_ltv: '0.65', whitelisted: true, diff --git a/scripts/deploy/osmosis/index.ts b/scripts/deploy/osmosis/index.ts index 4946bdec0..62854fae0 100644 --- a/scripts/deploy/osmosis/index.ts +++ b/scripts/deploy/osmosis/index.ts @@ -2,5 +2,9 @@ import { taskRunner } from '../base' import { osmosisTestnetConfig } from './config' void (async function () { - await taskRunner({ config: osmosisTestnetConfig, swapperContractName: 'mars_swapper_osmosis' }) + await taskRunner({ + config: osmosisTestnetConfig, + swapperContractName: 'mars_swapper_osmosis', + zapperContractName: 'mars_zapper_osmosis', + }) })() diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index cc390507f..d7c7363ec 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -21,10 +21,12 @@ import { VaultBaseForString, ExecuteMsg, Action, + ActionAmount, VaultPositionType, AdminUpdate, CallbackMsg, Addr, + ActionCoin, ConfigUpdates, VaultBaseForAddr, QueryMsg, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 583c7e316..02caddb47 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -22,10 +22,12 @@ import { VaultBaseForString, ExecuteMsg, Action, + ActionAmount, VaultPositionType, AdminUpdate, CallbackMsg, Addr, + ActionCoin, ConfigUpdates, VaultBaseForAddr, QueryMsg, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index fc3e70e0e..65a179928 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -22,10 +22,12 @@ import { VaultBaseForString, ExecuteMsg, Action, + ActionAmount, VaultPositionType, AdminUpdate, CallbackMsg, Addr, + ActionCoin, ConfigUpdates, VaultBaseForAddr, QueryMsg, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 0e415c44d..0ba0260d6 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -72,15 +72,11 @@ export type Action = borrow: Coin } | { - repay: { - amount?: Uint128 | null - denom: string - } + repay: ActionCoin } | { enter_vault: { - amount?: Uint128 | null - denom: string + coin: ActionCoin vault: VaultBaseForString } } @@ -119,28 +115,31 @@ export type Action = } | { swap_exact_in: { - coin_in_amount?: Uint128 | null - coin_in_denom: string + coin_in: ActionCoin denom_out: string slippage: Decimal } } | { provide_liquidity: { - coins_in: Coin[] + coins_in: ActionCoin[] lp_token_out: string minimum_receive: Uint128 } } | { withdraw_liquidity: { - lp_token_amount?: Uint128 | null - lp_token_denom: string + lp_token: ActionCoin } } | { refund_all_coin_balances: {} } +export type ActionAmount = + | 'account_balance' + | { + exact: Uint128 + } export type VaultPositionType = 'u_n_l_o_c_k_e_d' | 'l_o_c_k_e_d' | 'u_n_l_o_c_k_i_n_g' export type AdminUpdate = | { @@ -168,8 +167,7 @@ export type CallbackMsg = | { repay: { account_id: string - amount?: Uint128 | null - denom: string + coin: ActionCoin } } | { @@ -180,8 +178,7 @@ export type CallbackMsg = | { enter_vault: { account_id: string - amount?: Uint128 | null - denom: string + coin: ActionCoin vault: VaultBaseForAddr } } @@ -233,8 +230,7 @@ export type CallbackMsg = | { swap_exact_in: { account_id: string - coin_in_amount?: Uint128 | null - coin_in_denom: string + coin_in: ActionCoin denom_out: string slippage: Decimal } @@ -248,7 +244,7 @@ export type CallbackMsg = | { provide_liquidity: { account_id: string - coins_in: Coin[] + coins_in: ActionCoin[] lp_token_out: string minimum_receive: Uint128 } @@ -256,8 +252,7 @@ export type CallbackMsg = | { withdraw_liquidity: { account_id: string - lp_token_amount?: Uint128 | null - lp_token_denom: string + lp_token: ActionCoin } } | { @@ -271,6 +266,10 @@ export type CallbackMsg = } } export type Addr = string +export interface ActionCoin { + amount: ActionAmount + denom: string +} export interface ConfigUpdates { account_nft?: string | null allowed_coins?: string[] | null diff --git a/scripts/types/generated/mars-oracle-adapter/bundle.ts b/scripts/types/generated/mars-oracle-adapter/bundle.ts index bc697aee4..119c39ef3 100644 --- a/scripts/types/generated/mars-oracle-adapter/bundle.ts +++ b/scripts/types/generated/mars-oracle-adapter/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _28 from './MarsOracleAdapter.types' -import * as _29 from './MarsOracleAdapter.client' -import * as _30 from './MarsOracleAdapter.message-composer' -import * as _31 from './MarsOracleAdapter.react-query' +import * as _24 from './MarsOracleAdapter.types' +import * as _25 from './MarsOracleAdapter.client' +import * as _26 from './MarsOracleAdapter.message-composer' +import * as _27 from './MarsOracleAdapter.react-query' export namespace contracts { - export const MarsOracleAdapter = { ..._28, ..._29, ..._30, ..._31 } + export const MarsOracleAdapter = { ..._24, ..._25, ..._26, ..._27 } } diff --git a/scripts/types/generated/mars-swapper-base/bundle.ts b/scripts/types/generated/mars-swapper-base/bundle.ts index 0e9e9246a..69a659ed4 100644 --- a/scripts/types/generated/mars-swapper-base/bundle.ts +++ b/scripts/types/generated/mars-swapper-base/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _32 from './MarsSwapperBase.types' -import * as _33 from './MarsSwapperBase.client' -import * as _34 from './MarsSwapperBase.message-composer' -import * as _35 from './MarsSwapperBase.react-query' +import * as _28 from './MarsSwapperBase.types' +import * as _29 from './MarsSwapperBase.client' +import * as _30 from './MarsSwapperBase.message-composer' +import * as _31 from './MarsSwapperBase.react-query' export namespace contracts { - export const MarsSwapperBase = { ..._32, ..._33, ..._34, ..._35 } + export const MarsSwapperBase = { ..._28, ..._29, ..._30, ..._31 } } diff --git a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts b/scripts/types/generated/mars-zapper-base/MarsZapperBase.client.ts similarity index 81% rename from scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts rename to scripts/types/generated/mars-zapper-base/MarsZapperBase.client.ts index 06364e005..f00619dea 100644 --- a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.client.ts +++ b/scripts/types/generated/mars-zapper-base/MarsZapperBase.client.ts @@ -8,16 +8,16 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { - OracleBaseForString, InstantiateMsg, - LpConfig, ExecuteMsg, Uint128, - QueryMsg, + CallbackMsg, + Addr, Coin, + QueryMsg, ArrayOfCoin, -} from './MarsMockZapper.types' -export interface MarsMockZapperReadOnlyInterface { +} from './MarsZapperBase.types' +export interface MarsZapperBaseReadOnlyInterface { contractAddress: string estimateProvideLiquidity: ({ coinsIn, @@ -28,7 +28,7 @@ export interface MarsMockZapperReadOnlyInterface { }) => Promise estimateWithdrawLiquidity: ({ coinIn }: { coinIn: Coin }) => Promise } -export class MarsMockZapperQueryClient implements MarsMockZapperReadOnlyInterface { +export class MarsZapperBaseQueryClient implements MarsZapperBaseReadOnlyInterface { client: CosmWasmClient contractAddress: string @@ -61,7 +61,7 @@ export class MarsMockZapperQueryClient implements MarsMockZapperReadOnlyInterfac }) } } -export interface MarsMockZapperInterface extends MarsMockZapperReadOnlyInterface { +export interface MarsZapperBaseInterface extends MarsZapperBaseReadOnlyInterface { contractAddress: string sender: string provideLiquidity: ( @@ -88,10 +88,15 @@ export interface MarsMockZapperInterface extends MarsMockZapperReadOnlyInterface memo?: string, funds?: Coin[], ) => Promise + callback: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise } -export class MarsMockZapperClient - extends MarsMockZapperQueryClient - implements MarsMockZapperInterface +export class MarsZapperBaseClient + extends MarsZapperBaseQueryClient + implements MarsZapperBaseInterface { client: SigningCosmWasmClient sender: string @@ -104,6 +109,7 @@ export class MarsMockZapperClient this.contractAddress = contractAddress this.provideLiquidity = this.provideLiquidity.bind(this) this.withdrawLiquidity = this.withdrawLiquidity.bind(this) + this.callback = this.callback.bind(this) } provideLiquidity = async ( @@ -158,4 +164,20 @@ export class MarsMockZapperClient funds, ) } + callback = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + callback: {}, + }, + fee, + memo, + funds, + ) + } } diff --git a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts b/scripts/types/generated/mars-zapper-base/MarsZapperBase.message-composer.ts similarity index 78% rename from scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts rename to scripts/types/generated/mars-zapper-base/MarsZapperBase.message-composer.ts index 02af784d5..9276335a1 100644 --- a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.message-composer.ts +++ b/scripts/types/generated/mars-zapper-base/MarsZapperBase.message-composer.ts @@ -9,16 +9,16 @@ import { MsgExecuteContractEncodeObject } from 'cosmwasm' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { - OracleBaseForString, InstantiateMsg, - LpConfig, ExecuteMsg, Uint128, - QueryMsg, + CallbackMsg, + Addr, Coin, + QueryMsg, ArrayOfCoin, -} from './MarsMockZapper.types' -export interface MarsMockZapperMessage { +} from './MarsZapperBase.types' +export interface MarsZapperBaseMessage { contractAddress: string sender: string provideLiquidity: ( @@ -41,8 +41,9 @@ export interface MarsMockZapperMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject + callback: (funds?: Coin[]) => MsgExecuteContractEncodeObject } -export class MarsMockZapperMessageComposer implements MarsMockZapperMessage { +export class MarsZapperBaseMessageComposer implements MarsZapperBaseMessage { sender: string contractAddress: string @@ -51,6 +52,7 @@ export class MarsMockZapperMessageComposer implements MarsMockZapperMessage { this.contractAddress = contractAddress this.provideLiquidity = this.provideLiquidity.bind(this) this.withdrawLiquidity = this.withdrawLiquidity.bind(this) + this.callback = this.callback.bind(this) } provideLiquidity = ( @@ -107,4 +109,19 @@ export class MarsMockZapperMessageComposer implements MarsMockZapperMessage { }), } } + callback = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + callback: {}, + }), + ), + funds, + }), + } + } } diff --git a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts b/scripts/types/generated/mars-zapper-base/MarsZapperBase.react-query.ts similarity index 56% rename from scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts rename to scripts/types/generated/mars-zapper-base/MarsZapperBase.react-query.ts index fc1230be0..853d9806d 100644 --- a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.react-query.ts +++ b/scripts/types/generated/mars-zapper-base/MarsZapperBase.react-query.ts @@ -9,28 +9,28 @@ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tan import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { - OracleBaseForString, InstantiateMsg, - LpConfig, ExecuteMsg, Uint128, - QueryMsg, + CallbackMsg, + Addr, Coin, + QueryMsg, ArrayOfCoin, -} from './MarsMockZapper.types' -import { MarsMockZapperQueryClient, MarsMockZapperClient } from './MarsMockZapper.client' -export const marsMockZapperQueryKeys = { +} from './MarsZapperBase.types' +import { MarsZapperBaseQueryClient, MarsZapperBaseClient } from './MarsZapperBase.client' +export const marsZapperBaseQueryKeys = { contract: [ { - contract: 'marsMockZapper', + contract: 'marsZapperBase', }, ] as const, address: (contractAddress: string | undefined) => - [{ ...marsMockZapperQueryKeys.contract[0], address: contractAddress }] as const, + [{ ...marsZapperBaseQueryKeys.contract[0], address: contractAddress }] as const, estimateProvideLiquidity: (contractAddress: string | undefined, args?: Record) => [ { - ...marsMockZapperQueryKeys.address(contractAddress)[0], + ...marsZapperBaseQueryKeys.address(contractAddress)[0], method: 'estimate_provide_liquidity', args, }, @@ -41,14 +41,14 @@ export const marsMockZapperQueryKeys = { ) => [ { - ...marsMockZapperQueryKeys.address(contractAddress)[0], + ...marsZapperBaseQueryKeys.address(contractAddress)[0], method: 'estimate_withdraw_liquidity', args, }, ] as const, } -export interface MarsMockZapperReactQuery { - client: MarsMockZapperQueryClient | undefined +export interface MarsZapperBaseReactQuery { + client: MarsZapperBaseQueryClient | undefined options?: Omit< UseQueryOptions, "'queryKey' | 'queryFn' | 'initialData'" @@ -56,19 +56,19 @@ export interface MarsMockZapperReactQuery { initialData?: undefined } } -export interface MarsMockZapperEstimateWithdrawLiquidityQuery - extends MarsMockZapperReactQuery { +export interface MarsZapperBaseEstimateWithdrawLiquidityQuery + extends MarsZapperBaseReactQuery { args: { coinIn: Coin } } -export function useMarsMockZapperEstimateWithdrawLiquidityQuery({ +export function useMarsZapperBaseEstimateWithdrawLiquidityQuery({ client, args, options, -}: MarsMockZapperEstimateWithdrawLiquidityQuery) { +}: MarsZapperBaseEstimateWithdrawLiquidityQuery) { return useQuery( - marsMockZapperQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), + marsZapperBaseQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), () => client ? client.estimateWithdrawLiquidity({ @@ -78,20 +78,20 @@ export function useMarsMockZapperEstimateWithdrawLiquidityQuery - extends MarsMockZapperReactQuery { +export interface MarsZapperBaseEstimateProvideLiquidityQuery + extends MarsZapperBaseReactQuery { args: { coinsIn: Coin[] lpTokenOut: string } } -export function useMarsMockZapperEstimateProvideLiquidityQuery({ +export function useMarsZapperBaseEstimateProvideLiquidityQuery({ client, args, options, -}: MarsMockZapperEstimateProvideLiquidityQuery) { +}: MarsZapperBaseEstimateProvideLiquidityQuery) { return useQuery( - marsMockZapperQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), + marsZapperBaseQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), () => client ? client.estimateProvideLiquidity({ @@ -102,8 +102,27 @@ export function useMarsMockZapperEstimateProvideLiquidityQuery( { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsMockZapperWithdrawLiquidityMutation { - client: MarsMockZapperClient +export interface MarsZapperBaseCallbackMutation { + client: MarsZapperBaseClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsZapperBaseCallbackMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.callback(msg, fee, memo, funds), + options, + ) +} +export interface MarsZapperBaseWithdrawLiquidityMutation { + client: MarsZapperBaseClient msg: { recipient?: string } @@ -113,20 +132,20 @@ export interface MarsMockZapperWithdrawLiquidityMutation { funds?: Coin[] } } -export function useMarsMockZapperWithdrawLiquidityMutation( +export function useMarsZapperBaseWithdrawLiquidityMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.withdrawLiquidity(msg, fee, memo, funds), options, ) } -export interface MarsMockZapperProvideLiquidityMutation { - client: MarsMockZapperClient +export interface MarsZapperBaseProvideLiquidityMutation { + client: MarsZapperBaseClient msg: { lpTokenOut: string minimumReceive: Uint128 @@ -138,13 +157,13 @@ export interface MarsMockZapperProvideLiquidityMutation { funds?: Coin[] } } -export function useMarsMockZapperProvideLiquidityMutation( +export function useMarsZapperBaseProvideLiquidityMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.provideLiquidity(msg, fee, memo, funds), options, diff --git a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.types.ts b/scripts/types/generated/mars-zapper-base/MarsZapperBase.types.ts similarity index 79% rename from scripts/types/generated/mars-mock-zapper/MarsMockZapper.types.ts rename to scripts/types/generated/mars-zapper-base/MarsZapperBase.types.ts index b32009995..478ca253f 100644 --- a/scripts/types/generated/mars-mock-zapper/MarsMockZapper.types.ts +++ b/scripts/types/generated/mars-zapper-base/MarsZapperBase.types.ts @@ -5,15 +5,7 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -export type OracleBaseForString = string -export interface InstantiateMsg { - lp_configs: LpConfig[] - oracle: OracleBaseForString -} -export interface LpConfig { - lp_pair_denoms: [string, string] - lp_token_denom: string -} +export interface InstantiateMsg {} export type ExecuteMsg = | { provide_liquidity: { @@ -27,7 +19,22 @@ export type ExecuteMsg = recipient?: string | null } } + | { + callback: CallbackMsg + } export type Uint128 = string +export type CallbackMsg = { + return_coin: { + balance_before: Coin + recipient: Addr + } +} +export type Addr = string +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} export type QueryMsg = | { estimate_provide_liquidity: { @@ -40,9 +47,4 @@ export type QueryMsg = coin_in: Coin } } -export interface Coin { - amount: Uint128 - denom: string - [k: string]: unknown -} export type ArrayOfCoin = Coin[] diff --git a/scripts/types/generated/mars-mock-zapper/bundle.ts b/scripts/types/generated/mars-zapper-base/bundle.ts similarity index 51% rename from scripts/types/generated/mars-mock-zapper/bundle.ts rename to scripts/types/generated/mars-zapper-base/bundle.ts index a586bd4fc..26a4a169c 100644 --- a/scripts/types/generated/mars-mock-zapper/bundle.ts +++ b/scripts/types/generated/mars-zapper-base/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _24 from './MarsMockZapper.types' -import * as _25 from './MarsMockZapper.client' -import * as _26 from './MarsMockZapper.message-composer' -import * as _27 from './MarsMockZapper.react-query' +import * as _32 from './MarsZapperBase.types' +import * as _33 from './MarsZapperBase.client' +import * as _34 from './MarsZapperBase.message-composer' +import * as _35 from './MarsZapperBase.react-query' export namespace contracts { - export const MarsMockZapper = { ..._24, ..._25, ..._26, ..._27 } + export const MarsZapperBase = { ..._32, ..._33, ..._34, ..._35 } } diff --git a/scripts/types/instantiateMsgs.ts b/scripts/types/instantiateMsgs.ts index b8d67abd6..2ded837db 100644 --- a/scripts/types/instantiateMsgs.ts +++ b/scripts/types/instantiateMsgs.ts @@ -4,6 +4,7 @@ import { InstantiateMsg as VaultInstantiateMsg } from './generated/mars-mock-vau import { InstantiateMsg as OracleInstantiateMsg } from './generated/mars-mock-oracle/MarsMockOracle.types' import { InstantiateMsg as RoverInstantiateMsg } from './generated/mars-credit-manager/MarsCreditManager.types' import { InstantiateMsg as SwapperInstantiateMsg } from './generated/mars-swapper-base/MarsSwapperBase.types' +import { InstantiateMsg as ZapperInstantiateMsg } from './generated/mars-zapper-base/MarsZapperBase.types' export type InstantiateMsgs = | NftInstantiateMsg @@ -12,3 +13,4 @@ export type InstantiateMsgs = | OracleInstantiateMsg | RoverInstantiateMsg | SwapperInstantiateMsg + | ZapperInstantiateMsg diff --git a/scripts/types/storageItems.ts b/scripts/types/storageItems.ts index d176864b8..704ee48ae 100644 --- a/scripts/types/storageItems.ts +++ b/scripts/types/storageItems.ts @@ -6,6 +6,7 @@ export interface StorageItems { mockOracle?: number marsOracleAdapter?: number swapper?: number + zapper?: number creditManager?: number } addresses: { @@ -13,6 +14,7 @@ export interface StorageItems { mockVault?: string marsOracleAdapter?: string swapper?: string + zapper?: string creditManager?: string } actions: { From 498edafed2a0ba867d13cfc99a5622cb50243074 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 29 Dec 2022 16:38:23 +0100 Subject: [PATCH 113/218] rename .coins to .deposits (#83) --- contracts/credit-manager/src/health.rs | 9 +++- contracts/credit-manager/src/query.rs | 2 +- contracts/credit-manager/tests/test_borrow.rs | 10 ++-- .../tests/test_coin_balances.rs | 12 ++--- .../credit-manager/tests/test_deposit.rs | 18 +++---- .../credit-manager/tests/test_dispatch.rs | 4 +- contracts/credit-manager/tests/test_health.rs | 14 +++--- .../tests/test_liquidate_coin.rs | 48 +++++++++---------- .../tests/test_liquidate_vault.rs | 34 ++++++------- .../tests/test_refund_balances.rs | 4 +- contracts/credit-manager/tests/test_repay.rs | 14 +++--- contracts/credit-manager/tests/test_swap.rs | 12 ++--- .../credit-manager/tests/test_vault_enter.rs | 2 +- .../credit-manager/tests/test_vault_exit.rs | 4 +- .../tests/test_vault_exit_unlocked.rs | 20 ++++---- .../credit-manager/tests/test_withdraw.rs | 20 ++++---- .../credit-manager/tests/test_zap_provide.rs | 18 +++---- .../credit-manager/tests/test_zap_withdraw.rs | 12 ++--- packages/rover/src/msg/query.rs | 2 +- .../mars-credit-manager.json | 10 ++-- .../mars-mock-credit-manager.json | 10 ++-- scripts/deploy/base/rover.ts | 20 ++++---- .../MarsCreditManager.types.ts | 2 +- .../MarsMockCreditManager.types.ts | 2 +- 24 files changed, 156 insertions(+), 147 deletions(-) diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 88313fbbc..6587baa7f 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -20,8 +20,13 @@ pub fn compute_health(deps: Deps, env: &Env, account_id: &str) -> ContractResult let res = query_positions(deps, env, account_id)?; let mut positions: Vec = vec![]; - let coin_positions = - get_positions_for_coins(&deps, &res.coins, &res.debts.to_coins(), &oracle, &red_bank)?; + let coin_positions = get_positions_for_coins( + &deps, + &res.deposits, + &res.debts.to_coins(), + &oracle, + &red_bank, + )?; positions.extend(coin_positions); let vault_positions = get_positions_for_vaults(&deps, &res.vaults, &oracle)?; positions.extend(vault_positions); diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 0fcc4cd11..336200515 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -40,7 +40,7 @@ pub fn query_config(deps: Deps) -> ContractResult { pub fn query_positions(deps: Deps, env: &Env, account_id: &str) -> ContractResult { Ok(Positions { account_id: account_id.to_string(), - coins: query_coin_balances(deps, account_id)?, + deposits: query_coin_balances(deps, account_id)?, debts: query_debt_amounts(deps, env, account_id)?, vaults: query_vault_positions(deps, account_id)?, }) diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index 780e0978e..18042f75f 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -78,7 +78,7 @@ fn test_borrowing_zero_does_nothing() { assert_err(res, ContractError::NoAmount); let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 0); + assert_eq!(position.deposits.len(), 0); assert_eq!(position.debts.len(), 0); } @@ -97,7 +97,7 @@ fn test_cannot_borrow_above_max_ltv() { let account_id = mock.create_credit_account(&user).unwrap(); let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 0); + assert_eq!(position.deposits.len(), 0); assert_eq!(position.debts.len(), 0); let res = mock.update_credit_account( @@ -134,7 +134,7 @@ fn test_success_when_new_debt_asset() { let account_id = mock.create_credit_account(&user).unwrap(); let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 0); + assert_eq!(position.deposits.len(), 0); assert_eq!(position.debts.len(), 0); mock.update_credit_account( &account_id, @@ -148,8 +148,8 @@ fn test_success_when_new_debt_asset() { .unwrap(); let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 1); - let asset_res = position.coins.first().unwrap(); + assert_eq!(position.deposits.len(), 1); + let asset_res = position.deposits.first().unwrap(); assert_eq!( asset_res.amount, Uint128::new(342) // Deposit + Borrow diff --git a/contracts/credit-manager/tests/test_coin_balances.rs b/contracts/credit-manager/tests/test_coin_balances.rs index eab54fac9..3c9d8ceb1 100644 --- a/contracts/credit-manager/tests/test_coin_balances.rs +++ b/contracts/credit-manager/tests/test_coin_balances.rs @@ -100,9 +100,9 @@ fn test_user_gets_rebalanced_down() { .unwrap(); let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 1); - assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); - assert_eq!(position.coins.first().unwrap().amount.u128(), 100); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.deposits.first().unwrap().denom, osmo_info.denom); + assert_eq!(position.deposits.first().unwrap().amount.u128(), 100); } #[test] @@ -145,7 +145,7 @@ fn test_user_gets_rebalanced_up() { .unwrap(); let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 1); - assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); - assert_eq!(position.coins.first().unwrap().amount.u128(), 500); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.deposits.first().unwrap().denom, osmo_info.denom); + assert_eq!(position.deposits.first().unwrap().amount.u128(), 500); } diff --git a/contracts/credit-manager/tests/test_deposit.rs b/contracts/credit-manager/tests/test_deposit.rs index e9aa1a531..1ae5627f7 100644 --- a/contracts/credit-manager/tests/test_deposit.rs +++ b/contracts/credit-manager/tests/test_deposit.rs @@ -48,7 +48,7 @@ fn test_deposit_nothing() { let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); mock.update_credit_account( &account_id, @@ -59,7 +59,7 @@ fn test_deposit_nothing() { .unwrap(); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); } #[test] @@ -90,7 +90,7 @@ fn test_deposit_but_no_funds() { ); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); } #[test] @@ -150,7 +150,7 @@ fn test_can_only_deposit_allowed_assets() { assert_err(res, NotWhitelisted(not_allowed_coin.denom)); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); } #[test] @@ -186,7 +186,7 @@ fn test_extra_funds_received() { ); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); } #[test] @@ -214,8 +214,8 @@ fn test_deposit_success() { .unwrap(); let res = mock.query_positions(&account_id); - let assets_res = res.coins.first().unwrap(); - assert_eq!(res.coins.len(), 1); + let assets_res = res.deposits.first().unwrap(); + assert_eq!(res.deposits.len(), 1); assert_eq!(assets_res.amount, deposit_amount); assert_eq!(assets_res.denom, coin_info.denom); @@ -260,7 +260,7 @@ fn test_multiple_deposit_actions() { .unwrap(); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 2); + assert_eq!(res.deposits.len(), 2); assert_present(&res, &uosmo_info, uosmo_amount); assert_present(&res, &uatom_info, uatom_amount); @@ -272,7 +272,7 @@ fn test_multiple_deposit_actions() { } fn assert_present(res: &Positions, coin: &CoinInfo, amount: Uint128) { - res.coins + res.deposits .iter() .find(|item| item.denom == coin.denom && item.amount == amount) .unwrap(); diff --git a/contracts/credit-manager/tests/test_dispatch.rs b/contracts/credit-manager/tests/test_dispatch.rs index 088187db4..dfd99f981 100644 --- a/contracts/credit-manager/tests/test_dispatch.rs +++ b/contracts/credit-manager/tests/test_dispatch.rs @@ -34,13 +34,13 @@ fn test_nothing_happens_if_no_actions_are_passed() { let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); mock.update_credit_account(&account_id, &user, vec![], &[]) .unwrap(); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); } #[test] diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index b6cbfa537..e39f0a980 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -47,7 +47,7 @@ fn test_only_assets_with_no_debts() { .unwrap(); let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 1); + assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let health = mock.query_health(&account_id); @@ -106,7 +106,7 @@ fn test_terra_ragnarok() { .unwrap(); let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 1); + assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 1); let health = mock.query_health(&account_id); @@ -132,7 +132,7 @@ fn test_terra_ragnarok() { }); let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 1); + assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 1); let health = mock.query_health(&account_id); @@ -180,7 +180,7 @@ fn test_debts_no_assets() { let position = mock.query_positions(&account_id); assert_eq!(position.account_id, account_id); - assert_eq!(position.coins.len(), 0); + assert_eq!(position.deposits.len(), 0); assert_eq!(position.debts.len(), 0); let health = mock.query_health(&account_id); @@ -232,7 +232,7 @@ fn test_cannot_borrow_more_than_healthy() { let position = mock.query_positions(&account_id); assert_eq!(position.account_id, account_id); - assert_eq!(position.coins.len(), 1); + assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 1); let health = mock.query_health(&account_id); @@ -425,7 +425,7 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { let position = mock.query_positions(&account_id); assert_eq!(position.account_id, account_id); - assert_eq!(position.coins.len(), 2); + assert_eq!(position.deposits.len(), 2); assert_eq!(position.debts.len(), 1); let health = mock.query_health(&account_id); @@ -543,7 +543,7 @@ fn test_debt_value() { let position_a = mock.query_positions(&account_id_a); assert_eq!(position_a.account_id, account_id_a); - assert_eq!(position_a.coins.len(), 2); + assert_eq!(position_a.deposits.len(), 2); assert_eq!(position_a.debts.len(), 2); let health = mock.query_health(&account_id_a); diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index eaa888fbc..7f86caaed 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -504,10 +504,10 @@ fn test_debt_amount_adjusted_to_close_factor_max() { // Assert liquidatee's new position let position = mock.query_positions(&liquidatee_account_id); - assert_eq!(position.coins.len(), 2); - let osmo_balance = get_coin("uosmo", &position.coins); + assert_eq!(position.deposits.len(), 2); + let osmo_balance = get_coin("uosmo", &position.deposits); assert_eq!(osmo_balance.amount, Uint128::new(36)); - let atom_balance = get_coin("uatom", &position.coins); + let atom_balance = get_coin("uatom", &position.deposits); assert_eq!(atom_balance.amount, Uint128::new(100)); assert_eq!(position.debts.len(), 1); @@ -516,11 +516,11 @@ fn test_debt_amount_adjusted_to_close_factor_max() { // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.coins.len(), 2); + assert_eq!(position.deposits.len(), 2); assert_eq!(position.debts.len(), 0); - let atom_balance = get_coin("uatom", &position.coins); + let atom_balance = get_coin("uatom", &position.deposits); assert_eq!(atom_balance.amount, Uint128::new(40)); - let osmo_balance = get_coin("uosmo", &position.coins); + let osmo_balance = get_coin("uosmo", &position.deposits); assert_eq!(osmo_balance.amount, Uint128::new(264)); } @@ -582,12 +582,12 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { // Assert liquidatee's new position let position = mock.query_positions(&liquidatee_account_id); - assert_eq!(position.coins.len(), 3); - let osmo_balance = get_coin("uosmo", &position.coins); + assert_eq!(position.deposits.len(), 3); + let osmo_balance = get_coin("uosmo", &position.deposits); assert_eq!(osmo_balance.amount, Uint128::new(181)); - let atom_balance = get_coin("uatom", &position.coins); + let atom_balance = get_coin("uatom", &position.deposits); assert_eq!(atom_balance.amount, Uint128::new(100)); - let jake_balance = get_coin("ujake", &position.coins); + let jake_balance = get_coin("ujake", &position.deposits); assert_eq!(jake_balance.amount, Uint128::new(10)); assert_eq!(position.debts.len(), 1); @@ -596,11 +596,11 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.coins.len(), 2); + assert_eq!(position.deposits.len(), 2); assert_eq!(position.debts.len(), 0); - let jake_balance = get_coin("ujake", &position.coins); + let jake_balance = get_coin("ujake", &position.deposits); assert_eq!(jake_balance.amount, Uint128::new(39)); - let osmo_balance = get_coin("uosmo", &position.coins); + let osmo_balance = get_coin("uosmo", &position.deposits); assert_eq!(osmo_balance.amount, Uint128::new(119)); } @@ -659,10 +659,10 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { // Assert liquidatee's new position let position = mock.query_positions(&liquidatee_account_id); - assert_eq!(position.coins.len(), 2); - let osmo_balance = get_coin("uosmo", &position.coins); + assert_eq!(position.deposits.len(), 2); + let osmo_balance = get_coin("uosmo", &position.deposits); assert_eq!(osmo_balance.amount, Uint128::new(36)); - let atom_balance = get_coin("uatom", &position.coins); + let atom_balance = get_coin("uatom", &position.deposits); assert_eq!(atom_balance.amount, Uint128::new(100)); assert_eq!(position.debts.len(), 1); @@ -671,11 +671,11 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.coins.len(), 2); + assert_eq!(position.deposits.len(), 2); assert_eq!(position.debts.len(), 0); - let atom_balance = get_coin("uatom", &position.coins); + let atom_balance = get_coin("uatom", &position.deposits); assert_eq!(atom_balance.amount, Uint128::new(47)); - let osmo_balance = get_coin("uosmo", &position.coins); + let osmo_balance = get_coin("uosmo", &position.deposits); assert_eq!(osmo_balance.amount, Uint128::new(264)); } @@ -735,10 +735,10 @@ fn test_debt_amount_no_adjustment() { // Assert liquidatee's new position let position = mock.query_positions(&liquidatee_account_id); - assert_eq!(position.coins.len(), 2); - let osmo_balance = get_coin("uosmo", &position.coins); + assert_eq!(position.deposits.len(), 2); + let osmo_balance = get_coin("uosmo", &position.deposits); assert_eq!(osmo_balance.amount, Uint128::new(58)); - let atom_balance = get_coin("uatom", &position.coins); + let atom_balance = get_coin("uatom", &position.deposits); assert_eq!(atom_balance.amount, Uint128::new(100)); assert_eq!(position.debts.len(), 1); @@ -747,9 +747,9 @@ fn test_debt_amount_no_adjustment() { // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.coins.len(), 1); + assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); - let osmo_balance = get_coin("uosmo", &position.coins); + let osmo_balance = get_coin("uosmo", &position.deposits); assert_eq!(osmo_balance.amount, Uint128::new(242)); } diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 1f1eff677..82d8cdd44 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -378,8 +378,8 @@ fn test_liquidate_unlocked_vault() { let vault_balance = position.vaults.first().unwrap().amount.unlocked(); assert_eq!(vault_balance, Uint128::new(883_533)); // 1M - 116_467 - assert_eq!(position.coins.len(), 1); - let jake_balance = get_coin("ujake", &position.coins); + assert_eq!(position.deposits.len(), 1); + let jake_balance = get_coin("ujake", &position.deposits); assert_eq!(jake_balance.amount, Uint128::new(175)); assert_eq!(position.debts.len(), 1); @@ -388,9 +388,9 @@ fn test_liquidate_unlocked_vault() { // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.coins.len(), 1); + assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); - let lp = get_coin(&lp_token.denom, &position.coins); + let lp = get_coin(&lp_token.denom, &position.deposits); assert_eq!(lp.amount, Uint128::new(23)); } @@ -467,8 +467,8 @@ fn test_liquidate_locked_vault() { assert_eq!(vault_amount.unlocking().positions().len(), 0); assert_eq!(vault_amount.unlocked(), Uint128::zero()); - assert_eq!(position.coins.len(), 1); - let atom_balance = get_coin("uatom", &position.coins); + assert_eq!(position.deposits.len(), 1); + let atom_balance = get_coin("uatom", &position.deposits); assert_eq!(atom_balance.amount, Uint128::new(700)); assert_eq!(position.debts.len(), 1); @@ -477,9 +477,9 @@ fn test_liquidate_locked_vault() { // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.coins.len(), 1); + assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); - let lp_balance = get_coin(&lp_token.denom, &position.coins); + let lp_balance = get_coin(&lp_token.denom, &position.deposits); assert_eq!(lp_balance.amount, Uint128::new(66)); } @@ -597,8 +597,8 @@ fn test_liquidate_unlocking_liquidation_order() { Uint128::new(168) ); - assert_eq!(position.coins.len(), 1); - let jake_balance = get_coin("ujake", &position.coins); + assert_eq!(position.deposits.len(), 1); + let jake_balance = get_coin("ujake", &position.deposits); assert_eq!(jake_balance.amount, Uint128::new(175)); assert_eq!(position.debts.len(), 1); @@ -607,9 +607,9 @@ fn test_liquidate_unlocking_liquidation_order() { // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.coins.len(), 1); + assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); - let lp_balance = get_coin(&lp_token.denom, &position.coins); + let lp_balance = get_coin(&lp_token.denom, &position.deposits); assert_eq!(lp_balance.amount, Uint128::new(23)); } @@ -687,8 +687,8 @@ fn test_liquidation_calculation_adjustment() { let vault_balance = position.vaults.first().unwrap().amount.unlocked(); assert_eq!(vault_balance, Uint128::new(10_027)); // Vault position liquidated by 99% - assert_eq!(position.coins.len(), 1); - let jake_balance = get_coin("ujake", &position.coins); + assert_eq!(position.deposits.len(), 1); + let jake_balance = get_coin("ujake", &position.deposits); assert_eq!(jake_balance.amount, Uint128::new(175)); assert_eq!(position.debts.len(), 1); @@ -697,10 +697,10 @@ fn test_liquidation_calculation_adjustment() { // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.coins.len(), 2); - let jake_balance = get_coin("ujake", &position.coins); + assert_eq!(position.deposits.len(), 2); + let jake_balance = get_coin("ujake", &position.deposits); assert_eq!(jake_balance.amount, Uint128::new(415)); - let atom_balance = get_coin(&lp_token.denom, &position.coins); + let atom_balance = get_coin(&lp_token.denom, &position.deposits); assert_eq!(atom_balance.amount, Uint128::new(197)); assert_eq!(position.debts.len(), 0); } diff --git a/contracts/credit-manager/tests/test_refund_balances.rs b/contracts/credit-manager/tests/test_refund_balances.rs index ef172dcd2..e67f0d728 100644 --- a/contracts/credit-manager/tests/test_refund_balances.rs +++ b/contracts/credit-manager/tests/test_refund_balances.rs @@ -41,7 +41,7 @@ fn test_refund_coin_balances_when_balances() { // Assert refunds have been issued let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); let osmo_balance = mock.query_balance(&user, &uosmo_info.denom); assert_eq!(osmo_balance.amount, Uint128::new(234)); @@ -87,7 +87,7 @@ fn test_refund_coin_balances_when_no_balances() { // Assert no error is thrown and nothing happens to coin balances let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); // Assert vault positions have not been effected assert_eq!(res.vaults.len(), 1); diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index bb26036ab..e6ad1349f 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -190,7 +190,7 @@ fn test_repay_less_than_total_debt() { let account_id = mock.create_credit_account(&user).unwrap(); let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 0); + assert_eq!(position.deposits.len(), 0); assert_eq!(position.debts.len(), 0); mock.update_credit_account( @@ -215,8 +215,8 @@ fn test_repay_less_than_total_debt() { .unwrap(); let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 1); - let asset_res = position.coins.first().unwrap(); + assert_eq!(position.deposits.len(), 1); + let asset_res = position.deposits.first().unwrap(); let expected_net_asset_amount = Uint128::new(330); // Deposit + Borrow - Repay assert_eq!(asset_res.amount, expected_net_asset_amount); @@ -253,8 +253,8 @@ fn test_repay_less_than_total_debt() { .unwrap(); let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 1); - let asset_res = position.coins.first().unwrap(); + assert_eq!(position.deposits.len(), 1); + let asset_res = position.deposits.first().unwrap(); let expected_net_asset_amount = Uint128::new(299); // Deposit + Borrow - full repay - interest assert_eq!(asset_res.amount, expected_net_asset_amount); @@ -303,8 +303,8 @@ fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { .unwrap(); let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 1); - let asset_res = position.coins.first().unwrap(); + assert_eq!(position.deposits.len(), 1); + let asset_res = position.deposits.first().unwrap(); let expected_net_asset_amount = Uint128::new(299); // Deposit + Borrow - Repay - interest assert_eq!(asset_res.amount, expected_net_asset_amount); diff --git a/contracts/credit-manager/tests/test_swap.rs b/contracts/credit-manager/tests/test_swap.rs index 79dedb349..822bb5a34 100644 --- a/contracts/credit-manager/tests/test_swap.rs +++ b/contracts/credit-manager/tests/test_swap.rs @@ -206,9 +206,9 @@ fn test_swap_success_with_specified_amount() { // assert account position let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 1); - assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); - assert_eq!(position.coins.first().unwrap().amount, MOCK_SWAP_RESULT); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.deposits.first().unwrap().denom, osmo_info.denom); + assert_eq!(position.deposits.first().unwrap().amount, MOCK_SWAP_RESULT); } #[test] @@ -253,7 +253,7 @@ fn test_swap_success_with_amount_none() { // assert account position let position = mock.query_positions(&account_id); - assert_eq!(position.coins.len(), 1); - assert_eq!(position.coins.first().unwrap().denom, osmo_info.denom); - assert_eq!(position.coins.first().unwrap().amount, MOCK_SWAP_RESULT); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.deposits.first().unwrap().denom, osmo_info.denom); + assert_eq!(position.deposits.first().unwrap().amount, MOCK_SWAP_RESULT); } diff --git a/contracts/credit-manager/tests/test_vault_enter.rs b/contracts/credit-manager/tests/test_vault_enter.rs index 46cb1ee67..81c677c38 100644 --- a/contracts/credit-manager/tests/test_vault_enter.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -437,7 +437,7 @@ fn test_successful_deposit_with_implied_full_balance_amount() { let res = mock.query_positions(&account_id); let amount = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().amount.unlocked()); assert_eq!(amount, Uint128::new(200)); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); // Assert vault indeed has those tokens let base_denom = mock.query_balance( diff --git a/contracts/credit-manager/tests/test_vault_exit.rs b/contracts/credit-manager/tests/test_vault_exit.rs index d23fc87ed..20181ee3b 100644 --- a/contracts/credit-manager/tests/test_vault_exit.rs +++ b/contracts/credit-manager/tests/test_vault_exit.rs @@ -144,7 +144,7 @@ fn test_withdraw_with_unlocked_vault_coins() { assert_eq!(res.vaults.len(), 1); let v = res.vaults.first().unwrap(); assert_eq!(v.amount.unlocked(), STARTING_VAULT_SHARES); - let lp = get_coin(&lp_token.denom, &res.coins); + let lp = get_coin(&lp_token.denom, &res.deposits); assert_eq!(lp.amount, Uint128::from(100u128)); // Assert Rover's totals @@ -169,7 +169,7 @@ fn test_withdraw_with_unlocked_vault_coins() { // Assert token's updated position let res = mock.query_positions(&account_id); assert_eq!(res.vaults.len(), 0); - let lp = get_coin(&lp_token.denom, &res.coins); + let lp = get_coin(&lp_token.denom, &res.deposits); assert_eq!(lp.amount, Uint128::from(200u128)); // Assert Rover indeed has those on hand in the bank diff --git a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs index 5de120b63..cb445637c 100644 --- a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs +++ b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs @@ -279,8 +279,8 @@ fn test_withdraw_unlock_success_time_expiring() { ) .unwrap(); - let Positions { coins, .. } = mock.query_positions(&account_id); - assert_eq!(coins.len(), 0); + let Positions { deposits, .. } = mock.query_positions(&account_id); + assert_eq!(deposits.len(), 0); mock.app.update_block(|block| { if let Duration::Time(s) = leverage_vault.lockup.unwrap() { @@ -303,13 +303,15 @@ fn test_withdraw_unlock_success_time_expiring() { ) .unwrap(); - let Positions { vaults, coins, .. } = mock.query_positions(&account_id); + let Positions { + vaults, deposits, .. + } = mock.query_positions(&account_id); // Users vault position decrements assert_eq!(vaults.len(), 0); // Users asset position increments - let lp = get_coin(&lp_token.denom, &coins); + let lp = get_coin(&lp_token.denom, &deposits); assert_eq!(lp.amount, Uint128::from(200u128)); // Assert Rover indeed has those on hand in the bank @@ -354,8 +356,8 @@ fn test_withdraw_unlock_success_block_expiring() { ) .unwrap(); - let Positions { coins, .. } = mock.query_positions(&account_id); - assert_eq!(coins.len(), 0); + let Positions { deposits, .. } = mock.query_positions(&account_id); + assert_eq!(deposits.len(), 0); mock.app.update_block(|block| { if let Duration::Height(h) = leverage_vault.lockup.unwrap() { @@ -378,13 +380,15 @@ fn test_withdraw_unlock_success_block_expiring() { ) .unwrap(); - let Positions { vaults, coins, .. } = mock.query_positions(&account_id); + let Positions { + vaults, deposits, .. + } = mock.query_positions(&account_id); // Users vault position decrements assert_eq!(vaults.len(), 0); // Users asset position increments - let lp = get_coin(&lp_token.denom, &coins); + let lp = get_coin(&lp_token.denom, &deposits); assert_eq!(lp.amount, Uint128::from(200u128)); // Assert Rover indeed has those on hand in the bank diff --git a/contracts/credit-manager/tests/test_withdraw.rs b/contracts/credit-manager/tests/test_withdraw.rs index d454b6bdb..033ba2e92 100644 --- a/contracts/credit-manager/tests/test_withdraw.rs +++ b/contracts/credit-manager/tests/test_withdraw.rs @@ -33,7 +33,7 @@ fn test_only_owner_of_token_can_withdraw() { ); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); } #[test] @@ -56,7 +56,7 @@ fn test_withdraw_nothing() { assert_err(res, ContractError::NoAmount); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); } #[test] @@ -86,7 +86,7 @@ fn test_withdraw_but_no_funds() { ); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); } #[test] @@ -123,7 +123,7 @@ fn test_withdraw_but_not_enough_funds() { ); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); } #[test] @@ -160,7 +160,7 @@ fn test_cannot_withdraw_more_than_healthy() { ); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); } #[test] @@ -190,7 +190,7 @@ fn test_withdraw_success() { .unwrap(); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); let coin = mock.query_balance(&mock.rover, &coin_info.denom); assert_eq!(coin.amount, Uint128::zero()) @@ -233,7 +233,7 @@ fn test_multiple_withdraw_actions() { .unwrap(); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 2); + assert_eq!(res.deposits.len(), 2); let coin = mock.query_balance(&user, &uosmo_info.denom); assert_eq!(coin.amount, Uint128::zero()); @@ -250,7 +250,7 @@ fn test_multiple_withdraw_actions() { .unwrap(); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 1); + assert_eq!(res.deposits.len(), 1); let coin = mock.query_balance(&mock.rover, &uosmo_info.denom); assert_eq!(coin.amount, Uint128::zero()); @@ -267,7 +267,7 @@ fn test_multiple_withdraw_actions() { .unwrap(); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 1); + assert_eq!(res.deposits.len(), 1); let coin = mock.query_balance(&mock.rover, &uatom_info.denom); assert_eq!(coin.amount, Uint128::new(5)); @@ -284,7 +284,7 @@ fn test_multiple_withdraw_actions() { .unwrap(); let res = mock.query_positions(&account_id); - assert_eq!(res.coins.len(), 0); + assert_eq!(res.deposits.len(), 0); let coin = mock.query_balance(&mock.rover, &uatom_info.denom); assert_eq!(coin.amount, Uint128::zero()); diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs index 1ce2f564f..5fe7a1601 100644 --- a/contracts/credit-manager/tests/test_zap_provide.rs +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -273,8 +273,8 @@ fn test_successful_zap() { // assert user's new position let positions = mock.query_positions(&account_id); - assert_eq!(positions.coins.len(), 1); - let lp_balance = get_coin(&lp_token.denom, &positions.coins); + assert_eq!(positions.deposits.len(), 1); + let lp_balance = get_coin(&lp_token.denom, &positions.deposits); assert_eq!(lp_balance.amount, STARTING_LP_POOL_TOKENS); // assert rover actually has the tokens @@ -337,8 +337,8 @@ fn test_can_provide_unbalanced() { // assert user's new position let positions = mock.query_positions(&account_id); - assert_eq!(positions.coins.len(), 1); - let lp_balance = get_coin(&lp_token.denom, &positions.coins); + assert_eq!(positions.deposits.len(), 1); + let lp_balance = get_coin(&lp_token.denom, &positions.deposits); assert_eq!(lp_balance.amount, STARTING_LP_POOL_TOKENS); // assert coin balance of zapper contract @@ -360,13 +360,13 @@ fn test_can_provide_unbalanced() { // assert user's new position (withdrew half) let positions = mock.query_positions(&account_id); - assert_eq!(positions.coins.len(), 2); - let lp_balance = get_coin(&lp_token.denom, &positions.coins); + assert_eq!(positions.deposits.len(), 2); + let lp_balance = get_coin(&lp_token.denom, &positions.deposits); assert_eq!( lp_balance.amount, STARTING_LP_POOL_TOKENS.multiply_ratio(1u128, 2u128) ); - let atom_balance = get_coin(&atom.denom, &positions.coins); + let atom_balance = get_coin(&atom.denom, &positions.deposits); assert_eq!(atom_balance.amount, Uint128::new(50)); // assert coin balance of zapper contract @@ -432,8 +432,8 @@ fn test_order_does_not_matter() { // assert user's new position let positions = mock.query_positions(&account_id); - assert_eq!(positions.coins.len(), 1); - let lp_balance = get_coin(&lp_token.denom, &positions.coins); + assert_eq!(positions.deposits.len(), 1); + let lp_balance = get_coin(&lp_token.denom, &positions.deposits); assert_eq!( lp_balance.amount, STARTING_LP_POOL_TOKENS.mul(Uint128::new(2)) diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index 42e0865ab..03339af55 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -278,10 +278,10 @@ fn test_successful_unzap_specified_amount() { // Assert user's new position let positions = mock.query_positions(&account_id); - assert_eq!(positions.coins.len(), 2); - let atom_balance = get_coin(&atom.denom, &positions.coins); + assert_eq!(positions.deposits.len(), 2); + let atom_balance = get_coin(&atom.denom, &positions.deposits); assert_eq!(atom_balance.amount, Uint128::new(100)); - let osmo_balance = get_coin(&osmo.denom, &positions.coins); + let osmo_balance = get_coin(&osmo.denom, &positions.deposits); assert_eq!(osmo_balance.amount, Uint128::new(50)); // assert rover actually has the tokens @@ -341,10 +341,10 @@ fn test_successful_unzap_unspecified_amount() { // Assert user's new position let positions = mock.query_positions(&account_id); - assert_eq!(positions.coins.len(), 2); - let atom_balance = get_coin(&atom.denom, &positions.coins); + assert_eq!(positions.deposits.len(), 2); + let atom_balance = get_coin(&atom.denom, &positions.deposits); assert_eq!(atom_balance.amount, Uint128::new(100)); - let osmo_balance = get_coin(&osmo.denom, &positions.coins); + let osmo_balance = get_coin(&osmo.denom, &positions.deposits); assert_eq!(osmo_balance.amount, Uint128::new(50)); // assert rover actually has the tokens diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index b737a2a36..333f99470 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -128,7 +128,7 @@ pub struct CoinValue { #[cw_serde] pub struct Positions { pub account_id: String, - pub coins: Vec, + pub deposits: Vec, pub debts: Vec, pub vaults: Vec, } diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 794ad3ffe..709171248 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -2296,24 +2296,24 @@ "type": "object", "required": [ "account_id", - "coins", "debts", + "deposits", "vaults" ], "properties": { "account_id": { "type": "string" }, - "coins": { + "debts": { "type": "array", "items": { - "$ref": "#/definitions/Coin" + "$ref": "#/definitions/DebtAmount" } }, - "debts": { + "deposits": { "type": "array", "items": { - "$ref": "#/definitions/DebtAmount" + "$ref": "#/definitions/Coin" } }, "vaults": { diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 6a15616f7..e70021ca2 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -1000,24 +1000,24 @@ "type": "object", "required": [ "account_id", - "coins", "debts", + "deposits", "vaults" ], "properties": { "account_id": { "type": "string" }, - "coins": { + "debts": { "type": "array", "items": { - "$ref": "#/definitions/Coin" + "$ref": "#/definitions/DebtAmount" } }, - "debts": { + "deposits": { "type": "array", "items": { - "$ref": "#/definitions/DebtAmount" + "$ref": "#/definitions/Coin" } }, "vaults": { diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 5f52ab4c0..9637a5edc 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -56,9 +56,9 @@ export class Rover { [{ amount, denom: this.config.chain.baseDenom }], ) const positions = await this.query.positions({ accountId: this.accountId! }) - assert.equal(positions.coins.length, 1) - assert.equal(positions.coins[0].amount, amount) - assert.equal(positions.coins[0].denom, this.config.chain.baseDenom) + assert.equal(positions.deposits.length, 1) + assert.equal(positions.deposits[0].amount, amount) + assert.equal(positions.deposits[0].denom, this.config.chain.baseDenom) printGreen(`Deposited into credit account: ${amount} ${this.config.chain.baseDenom}`) } @@ -66,12 +66,12 @@ export class Rover { const amount = this.actions.withdrawAmount const positionsBefore = await this.query.positions({ accountId: this.accountId! }) const beforeWithdraw = parseFloat( - positionsBefore.coins.find((c) => c.denom === this.config.chain.baseDenom)!.amount, + positionsBefore.deposits.find((c) => c.denom === this.config.chain.baseDenom)!.amount, ) await this.updateCreditAccount([{ withdraw: { amount, denom: this.config.chain.baseDenom } }]) const positionsAfter = await this.query.positions({ accountId: this.accountId! }) const afterWithdraw = parseFloat( - positionsAfter.coins.find((c) => c.denom === this.config.chain.baseDenom)!.amount, + positionsAfter.deposits.find((c) => c.denom === this.config.chain.baseDenom)!.amount, ) assert.equal(beforeWithdraw - afterWithdraw, amount) printGreen(`Withdrew: ${amount} ${this.config.chain.baseDenom}`) @@ -105,7 +105,7 @@ export class Rover { `Swapping ${amount} ${this.config.chain.baseDenom} for ${this.actions.secondaryDenom}`, ) const prevPositions = await this.query.positions({ accountId: this.accountId! }) - printBlue(`Previous account balance: ${JSON.stringify(prevPositions.coins)}`) + printBlue(`Previous account balance: ${JSON.stringify(prevPositions.deposits)}`) await this.updateCreditAccount([ { swap_exact_in: { @@ -117,7 +117,7 @@ export class Rover { ]) printGreen(`Swap successful`) const newPositions = await this.query.positions({ accountId: this.accountId! }) - printGreen(`New account balance: ${JSON.stringify(newPositions.coins)}`) + printGreen(`New account balance: ${JSON.stringify(newPositions.deposits)}`) } async zap(lp_token_out: string) { @@ -134,7 +134,7 @@ export class Rover { }, ]) const positions = await this.query.positions({ accountId: this.accountId! }) - const lp_balance = positions.coins.find((c) => c.denom === lp_token_out)!.amount + const lp_balance = positions.deposits.find((c) => c.denom === lp_token_out)!.amount printGreen( `Zapped ${this.actions.zap.coinsIn .map((c) => c.denom) @@ -243,7 +243,7 @@ export class Rover { async refundAllBalances() { await this.updateCreditAccount([{ refund_all_coin_balances: {} }]) const positions = await this.query.positions({ accountId: this.accountId! }) - assert.equal(positions.coins.length, 0) + assert.equal(positions.deposits.length, 0) printGreen(`Withdrew all balances back to wallet`) } @@ -271,7 +271,7 @@ export class Rover { private async getAccountBalance(denom: string) { const positions = await this.query.positions({ accountId: this.accountId! }) - const coin = positions.coins.find((c) => c.denom === denom) + const coin = positions.deposits.find((c) => c.denom === denom) if (!coin) throw new Error(`No balance of ${denom}`) return parseInt(coin.amount) } diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 0ba0260d6..a7a5430ba 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -432,8 +432,8 @@ export interface HealthResponse { } export interface Positions { account_id: string - coins: Coin[] debts: DebtAmount[] + deposits: Coin[] vaults: VaultPosition[] } export interface DebtAmount { diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 6c18dae58..0e08c6460 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -177,8 +177,8 @@ export interface ConfigResponse { export type ArrayOfCoin = Coin[] export interface Positions { account_id: string - coins: Coin[] debts: DebtAmount[] + deposits: Coin[] vaults: VaultPosition[] } export interface DebtAmount { From 781c5cc2d309aada7310b56f7fcc5637f163501a Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 29 Dec 2022 17:38:10 +0100 Subject: [PATCH 114/218] Vault utilization query (#82) * Add new utilization query * tests * update frontend types * simplify admin setting * new deployment --- Makefile.toml | 4 +- contracts/credit-manager/src/contract.rs | 6 +- contracts/credit-manager/src/query.rs | 19 ++- contracts/credit-manager/src/vault/enter.rs | 20 +-- contracts/credit-manager/src/vault/utils.rs | 44 +++++- .../credit-manager/tests/helpers/mock_env.rs | 7 +- .../tests/test_update_config.rs | 120 +++++++++++---- .../tests/test_utilization_query.rs | 138 ++++++++++++++++++ packages/rover/src/msg/query.rs | 17 ++- .../mars-credit-manager.json | 23 ++- .../mars-mock-credit-manager.json | 23 ++- scripts/deploy/addresses/osmo-test-4.json | 12 +- scripts/deploy/base/deployer.ts | 6 +- scripts/deploy/base/index.ts | 3 - scripts/deploy/base/storage.ts | 2 - scripts/deploy/osmosis/config.ts | 1 + .../MarsCreditManager.client.ts | 15 +- .../MarsCreditManager.message-composer.ts | 3 +- .../MarsCreditManager.react-query.ts | 21 +-- .../MarsCreditManager.types.ts | 9 +- .../MarsMockCreditManager.client.ts | 16 +- .../MarsMockCreditManager.message-composer.ts | 4 +- .../MarsMockCreditManager.react-query.ts | 22 +-- .../MarsMockCreditManager.types.ts | 7 +- 24 files changed, 410 insertions(+), 132 deletions(-) create mode 100644 contracts/credit-manager/tests/test_utilization_query.rs diff --git a/Makefile.toml b/Makefile.toml index 0c788db49..12a4c1537 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -13,9 +13,9 @@ args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked"] [tasks.rust-optimizer] script = """ if [[ $(arch) == "arm64" ]]; then - image="cosmwasm/workspace-optimizer-arm64:0.12.8" + image="cosmwasm/workspace-optimizer-arm64:0.12.11" else - image="cosmwasm/workspace-optimizer:0.12.10" + image="cosmwasm/workspace-optimizer:0.12.11" fi docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 97691b85e..5dd59153a 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -15,7 +15,7 @@ use crate::query::{ query_all_coin_balances, query_all_debt_shares, query_all_total_debt_shares, query_all_total_vault_coin_balances, query_all_vault_positions, query_allowed_coins, query_config, query_positions, query_total_debt_shares, query_total_vault_coin_balance, - query_vault_configs, + query_vaults_info, }; use crate::update_config::{update_admin, update_config}; use crate::vault::handle_unlock_request_reply; @@ -71,8 +71,8 @@ pub fn reply(deps: DepsMut, _: Env, reply: Reply) -> ContractResult { pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { let res = match msg { QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::VaultConfigs { start_after, limit } => { - to_binary(&query_vault_configs(deps, start_after, limit)?) + QueryMsg::VaultsInfo { start_after, limit } => { + to_binary(&query_vaults_info(deps, env, start_after, limit)?) } QueryMsg::AllowedCoins { start_after, limit } => { to_binary(&query_allowed_coins(deps, start_after, limit)?) diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 336200515..f87178953 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -3,10 +3,9 @@ use cw_storage_plus::Bound; use mars_rover::adapters::vault::{Vault, VaultBase, VaultPosition, VaultUnchecked}; use mars_rover::error::ContractResult; -use mars_rover::msg::instantiate::VaultInstantiateConfig; use mars_rover::msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, Positions, SharesResponseItem, - VaultPositionResponseItem, VaultWithBalance, + VaultInfoResponse, VaultPositionResponseItem, VaultWithBalance, }; use crate::state::ADMIN; @@ -16,6 +15,7 @@ use crate::state::{ VAULT_POSITIONS, ZAPPER, }; use crate::utils::debt_shares_to_amount; +use crate::vault::vault_utilization_in_deposit_cap_denom; const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; @@ -119,11 +119,12 @@ pub fn query_all_debt_shares( .collect()) } -pub fn query_vault_configs( +pub fn query_vaults_info( deps: Deps, + env: Env, start_after: Option, limit: Option, -) -> StdResult> { +) -> ContractResult> { let vault: Vault; let start = match &start_after { Some(unchecked) => { @@ -140,9 +141,15 @@ pub fn query_vault_configs( .take(limit) .map(|res| { let (addr, config) = res?; - Ok(VaultInstantiateConfig { - vault: VaultBase::new(addr.to_string()), + let vault = VaultBase::new(addr); + Ok(VaultInfoResponse { + vault: vault.clone().into(), config, + utilization: vault_utilization_in_deposit_cap_denom( + &deps, + &vault, + &env.contract.address, + )?, }) }) .collect() diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index 949811aad..f445dc33f 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -1,6 +1,5 @@ use cosmwasm_std::{ - coin as c, to_binary, Addr, Coin, CosmosMsg, Deps, DepsMut, QuerierWrapper, Response, Uint128, - WasmMsg, + to_binary, Addr, Coin, CosmosMsg, Deps, DepsMut, QuerierWrapper, Response, Uint128, WasmMsg, }; use mars_rover::adapters::vault::{UpdateType, Vault, VaultPositionUpdate}; @@ -11,6 +10,7 @@ use mars_rover::msg::ExecuteMsg; use crate::query::query_vault_positions; use crate::state::{COIN_BALANCES, ORACLE, VAULT_CONFIGS}; use crate::utils::{assert_coin_is_whitelisted, decrement_coin_balance}; +use crate::vault::rover_vault_balance_value; use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; pub fn enter_vault( @@ -107,24 +107,18 @@ pub fn assert_denom_matches_vault_reqs( pub fn assert_deposit_is_under_cap( deps: Deps, vault: &Vault, - coin: &Coin, + coin_to_add: &Coin, rover_addr: &Addr, ) -> ContractResult<()> { let oracle = ORACLE.load(deps.storage)?; - let deposit_request_value = oracle.query_total_value(&deps.querier, &[coin.clone()])?; + let deposit_request_value = oracle.query_total_value(&deps.querier, &[coin_to_add.clone()])?; + let rover_vault_balance_value = rover_vault_balance_value(&deps, vault, rover_addr)?; + + let new_total_vault_value = rover_vault_balance_value.checked_add(deposit_request_value)?; let config = VAULT_CONFIGS.load(deps.storage, &vault.address)?; let deposit_cap_value = oracle.query_total_value(&deps.querier, &[config.deposit_cap])?; - let vault_info = vault.query_info(&deps.querier)?; - let rover_vault_coin_balance = vault.query_balance(&deps.querier, rover_addr)?; - let rover_vault_coins_value = oracle.query_total_value( - &deps.querier, - &[c(rover_vault_coin_balance.u128(), vault_info.vault_token)], - )?; - - let new_total_vault_value = rover_vault_coins_value.checked_add(deposit_request_value)?; - if new_total_vault_value > deposit_cap_value { return Err(ContractError::AboveVaultDepositCap { new_value: new_total_vault_value.to_string(), diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 9df4b8ecd..148561d1a 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,9 +1,10 @@ -use cosmwasm_std::{Addr, Coin, Deps, StdResult, Storage, Uint128}; +use cosmwasm_std::{coin, Addr, Coin, Decimal, Deps, StdResult, Storage, Uint128}; use mars_rover::adapters::vault::{Vault, VaultPositionAmount, VaultPositionUpdate}; use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::traits::IntoUint128; -use crate::state::{MAX_UNLOCKING_POSITIONS, VAULT_CONFIGS, VAULT_POSITIONS}; +use crate::state::{MAX_UNLOCKING_POSITIONS, ORACLE, VAULT_CONFIGS, VAULT_POSITIONS}; use crate::update_coin_balances::query_balance; pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> ContractResult<()> { @@ -68,3 +69,42 @@ pub fn query_withdraw_denom_balance( let vault_info = vault.query_info(&deps.querier)?; query_balance(&deps.querier, rover_addr, vault_info.base_token.as_str()) } + +pub fn vault_utilization_in_deposit_cap_denom( + deps: &Deps, + vault: &Vault, + rover_addr: &Addr, +) -> ContractResult { + let rover_vault_balance_value = rover_vault_balance_value(deps, vault, rover_addr)?; + let config = VAULT_CONFIGS.load(deps.storage, &vault.address)?; + let oracle = ORACLE.load(deps.storage)?; + let deposit_cap_denom_price = oracle + .query_price(&deps.querier, &config.deposit_cap.denom)? + .price; + + Ok(Coin { + denom: config.deposit_cap.denom, + amount: rover_vault_balance_value + .checked_div(deposit_cap_denom_price)? + .uint128(), + }) +} + +/// Total value of vault coins under Rover's management for vault +pub fn rover_vault_balance_value( + deps: &Deps, + vault: &Vault, + rover_addr: &Addr, +) -> ContractResult { + let oracle = ORACLE.load(deps.storage)?; + let vault_info = vault.query_info(&deps.querier)?; + let rover_vault_coin_balance = vault.query_balance(&deps.querier, rover_addr)?; + let balance_value = oracle.query_total_value( + &deps.querier, + &[coin( + rover_vault_coin_balance.u128(), + vault_info.vault_token, + )], + )?; + Ok(balance_value) +} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 5e2870ce7..a3c7857a1 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -33,7 +33,8 @@ use mars_rover::msg::execute::{Action, CallbackMsg}; use mars_rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; use mars_rover::msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtShares, HealthResponse, Positions, - SharesResponseItem, VaultPositionResponseItem, VaultWithBalance, + SharesResponseItem, VaultInfoResponse as RoverVaultInfoResponse, VaultPositionResponseItem, + VaultWithBalance, }; use mars_rover::msg::zapper::QueryMsg::EstimateProvideLiquidity; use mars_rover::msg::zapper::{InstantiateMsg as ZapperInstantiateMsg, LpConfig}; @@ -244,12 +245,12 @@ impl MockEnv { &self, start_after: Option, limit: Option, - ) -> Vec { + ) -> Vec { self.app .wrap() .query_wasm_smart( self.rover.clone(), - &QueryMsg::VaultConfigs { start_after, limit }, + &QueryMsg::VaultsInfo { start_after, limit }, ) .unwrap() } diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 6ba7984c2..f17475966 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -1,12 +1,19 @@ use cosmwasm_std::{coin, Addr, Decimal, Uint128}; +use cw_multi_test::{BasicApp, Executor}; +use mars_mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; +use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; use mars_rover::adapters::swap::SwapperBase; use mars_rover::adapters::vault::{VaultBase, VaultConfig}; use mars_rover::adapters::{OracleBase, ZapperBase}; use mars_rover::error::ContractError::InvalidConfig; use mars_rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; +use mars_rover::msg::query::VaultInfoResponse; -use crate::helpers::{assert_err, locked_vault_info, uatom_info, uosmo_info, MockEnv}; +use crate::helpers::{ + assert_err, locked_vault_info, mock_oracle_contract, mock_vault_contract, uatom_info, + uosmo_info, MockEnv, +}; pub mod helpers; @@ -105,17 +112,9 @@ fn test_update_config_works_with_full_config() { let original_vault_configs = mock.query_vault_configs(None, None); let new_nft_contract = mock.deploy_new_nft_contract().unwrap(); - let new_vault_configs = vec![VaultInstantiateConfig { - vault: VaultBase::new("vault_contract_3000".to_string()), - config: VaultConfig { - deposit_cap: coin(123, "usomething"), - max_ltv: Decimal::from_atomics(3u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), - whitelisted: false, - }, - }]; + let new_vault_configs = vec![deploy_vault(&mut mock.app)]; let new_allowed_coins = vec!["uosmo".to_string()]; - let new_oracle = OracleBase::new("new_oracle".to_string()); + let new_oracle = deploy_new_oracle(&mut mock.app); let new_zapper = ZapperBase::new("new_zapper".to_string()); let new_close_factor = Decimal::from_atomics(32u128, 2).unwrap(); let new_unlocking_max = Uint128::new(321); @@ -148,7 +147,17 @@ fn test_update_config_works_with_full_config() { original_config.admin.clone().unwrap() ); - assert_eq!(new_queried_vault_configs, new_vault_configs); + assert_eq!( + new_queried_vault_configs, + new_vault_configs + .iter() + .map(|v| VaultInfoResponse { + vault: v.vault.clone(), + config: v.config.clone(), + utilization: coin(0, "uusdc"), + }) + .collect::>() + ); assert_ne!(new_queried_vault_configs, original_vault_configs); assert_eq!(new_queried_allowed_coins, new_allowed_coins); @@ -184,21 +193,13 @@ fn test_update_config_works_with_some_config() { let original_vault_configs = mock.query_vault_configs(None, None); let new_nft_contract = mock.deploy_new_nft_contract().unwrap(); - let new_vault_configs = vec![VaultInstantiateConfig { - vault: VaultBase::new("vault_contract_1".to_string()), - config: VaultConfig { - deposit_cap: coin(1211, "uxyz"), - max_ltv: Default::default(), - liquidation_threshold: Default::default(), - whitelisted: false, - }, - }]; + let new_max_unlocking = Uint128::new(42); mock.update_config( &Addr::unchecked(original_config.admin.clone().unwrap()), ConfigUpdates { account_nft: Some(new_nft_contract.to_string()), - vault_configs: Some(new_vault_configs.clone()), + max_unlocking_positions: Some(new_max_unlocking), ..Default::default() }, ) @@ -212,13 +213,28 @@ fn test_update_config_works_with_some_config() { assert_eq!(new_config.account_nft, Some(new_nft_contract.to_string())); assert_ne!(new_config.account_nft, original_config.account_nft); - assert_eq!(new_queried_vault_configs, new_vault_configs); - assert_ne!(new_queried_vault_configs, original_vault_configs); + assert_eq!(new_config.max_unlocking_positions, new_max_unlocking); + assert_ne!( + new_config.max_unlocking_positions, + original_config.max_unlocking_positions + ); // Unchanged configs assert_eq!(new_config.admin, original_config.admin); - assert_eq!(original_allowed_coins, new_queried_allowed_coins); + assert_eq!( + new_config.proposed_new_admin, + original_config.proposed_new_admin + ); assert_eq!(new_config.red_bank, original_config.red_bank); + assert_eq!(new_config.oracle, original_config.oracle); + assert_eq!( + new_config.max_close_factor, + original_config.max_close_factor + ); + assert_eq!(new_config.swapper, original_config.swapper); + assert_eq!(new_config.zapper, original_config.zapper); + assert_eq!(original_allowed_coins, new_queried_allowed_coins); + assert_eq!(new_queried_vault_configs, original_vault_configs); } #[test] @@ -382,3 +398,57 @@ fn test_raises_on_duplicate_coin_configs() { }, ); } + +fn deploy_new_oracle(app: &mut BasicApp) -> OracleBase { + let contract_code_id = app.store_code(mock_oracle_contract()); + let addr = app + .instantiate_contract( + contract_code_id, + Addr::unchecked("oracle_contract_admin"), + &OracleInstantiateMsg { + prices: vec![ + CoinPrice { + denom: "uusdc".to_string(), + price: Decimal::from_atomics(12345u128, 4).unwrap(), + }, + CoinPrice { + denom: "vault_xyz".to_string(), + price: Decimal::from_atomics(989685877u128, 8).unwrap(), + }, + ], + }, + &[], + "mock-oracle", + None, + ) + .unwrap(); + OracleBase::new(addr.to_string()) +} + +fn deploy_vault(app: &mut BasicApp) -> VaultInstantiateConfig { + let code_id = app.store_code(mock_vault_contract()); + let addr = app + .instantiate_contract( + code_id, + Addr::unchecked("vault-instantiator"), + &VaultInstantiateMsg { + vault_token_denom: "vault_xyz".to_string(), + lockup: None, + base_token_denom: "base_token".to_string(), + oracle: OracleBase::new("oracle".to_string()), + }, + &[], + "mock-vault", + None, + ) + .unwrap(); + VaultInstantiateConfig { + vault: VaultBase::new(addr.to_string()), + config: VaultConfig { + deposit_cap: coin(123, "uusdc"), + max_ltv: Decimal::from_atomics(3u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), + whitelisted: false, + }, + } +} diff --git a/contracts/credit-manager/tests/test_utilization_query.rs b/contracts/credit-manager/tests/test_utilization_query.rs new file mode 100644 index 000000000..eb772f88d --- /dev/null +++ b/contracts/credit-manager/tests/test_utilization_query.rs @@ -0,0 +1,138 @@ +use cosmwasm_std::{Addr, Decimal, Uint128}; + +use mars_rover::msg::execute::Action::{Deposit, EnterVault}; +use mars_rover::msg::execute::ActionAmount::Exact; +use mars_rover::msg::execute::ActionCoin; + +use crate::helpers::{ + ujake_info, unlocked_vault_info, uosmo_info, AccountToFund, CoinInfo, MockEnv, VaultTestInfo, +}; + +pub mod helpers; + +#[test] +fn test_utilization_is_zero() { + let mock = MockEnv::new() + .vault_configs(&[unlocked_vault_info()]) + .build() + .unwrap(); + let vault_infos = mock.query_vault_configs(None, None); + assert_eq!(1, vault_infos.len()); + let vault = vault_infos.first().unwrap(); + assert_eq!(Uint128::zero(), vault.utilization.amount); + assert_eq!(vault.config.deposit_cap.denom, vault.utilization.denom); +} + +#[test] +fn test_utilization_if_cap_is_base_denom() { + let user = Addr::unchecked("user"); + let base_info = CoinInfo { + denom: "base_denom".to_string(), + price: Decimal::from_atomics(1u128, 0).unwrap(), + max_ltv: Default::default(), + liquidation_threshold: Default::default(), + liquidation_bonus: Default::default(), + }; + + let leverage_vault = VaultTestInfo { + vault_token_denom: "uleverage".to_string(), + base_token_denom: base_info.denom.clone(), + lockup: None, + deposit_cap: base_info.to_coin(100), + max_ltv: Default::default(), + liquidation_threshold: Default::default(), + }; + + let mut mock = MockEnv::new() + .allowed_coins(&[base_info.clone()]) + .vault_configs(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![base_info.to_coin(50)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(base_info.to_coin(50)), + EnterVault { + vault, + coin: ActionCoin { + denom: base_info.denom.clone(), + amount: Exact(Uint128::new(50)), + }, + }, + ], + &[base_info.to_coin(50)], + ) + .unwrap(); + + let vault_infos = mock.query_vault_configs(None, None); + let vault = vault_infos.first().unwrap(); + assert_eq!(vault.config.deposit_cap.denom, vault.utilization.denom); + assert_eq!(Uint128::new(50), vault.utilization.amount); +} + +/* + Vault deposit cap: 100 uosmo + price: .25 + Current vault deposits: 1_000_000 uleverage (vault tokens) // 1_000_000 ujake underlying + price: 2.3654 (underlying / # vault tokens * price of underlying) + Utilization denominated in uosmo = 1_000_000 * 2.3654 / .25 ---> 9461600 +*/ +#[test] +fn test_utilization_in_other_denom() { + let osmo_info = uosmo_info(); + let jake_info = ujake_info(); + + let leverage_vault = VaultTestInfo { + vault_token_denom: "uleverage".to_string(), + base_token_denom: jake_info.denom.clone(), + lockup: None, + deposit_cap: osmo_info.to_coin(50_000_000), + max_ltv: Default::default(), + liquidation_threshold: Default::default(), + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[jake_info.clone(), osmo_info]) + .vault_configs(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![jake_info.to_coin(1_000_000)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(jake_info.to_coin(1_000_000)), + EnterVault { + vault, + coin: ActionCoin { + denom: jake_info.denom.clone(), + amount: Exact(Uint128::new(1_000_000)), + }, + }, + ], + &[jake_info.to_coin(1_000_000)], + ) + .unwrap(); + + let vault_infos = mock.query_vault_configs(None, None); + let vault = vault_infos.first().unwrap(); + assert_eq!(vault.config.deposit_cap.denom, vault.utilization.denom); + assert_eq!(Uint128::new(9461600), vault.utilization.amount); +} diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 333f99470..880297886 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{Coin, Decimal, Uint128}; use mars_health::health::Health; -use crate::adapters::vault::{Vault, VaultPosition, VaultUnchecked}; +use crate::adapters::vault::{Vault, VaultConfig, VaultPosition, VaultUnchecked}; use crate::traits::Coins; #[cw_serde] @@ -12,9 +12,9 @@ pub enum QueryMsg { /// Rover contract-level config #[returns(ConfigResponse)] Config {}, - /// Configs on vaults - #[returns(Vec)] - VaultConfigs { + /// Configs & deposit caps on vaults + #[returns(Vec)] + VaultsInfo { start_after: Option, limit: Option, }, @@ -77,6 +77,15 @@ pub enum QueryMsg { EstimateWithdrawLiquidity { lp_token: Coin }, } +#[cw_serde] +pub struct VaultInfoResponse { + pub vault: VaultUnchecked, + pub config: VaultConfig, + /// The amount the vault has been utilized, + /// denominated in the same denom set in the vault config's deposit cap + pub utilization: Coin, +} + #[cw_serde] pub struct CoinBalanceResponseItem { pub account_id: String, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 709171248..078ac4c44 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -1412,13 +1412,13 @@ "additionalProperties": false }, { - "description": "Configs on vaults", + "description": "Configs & deposit caps on vaults", "type": "object", "required": [ - "vault_configs" + "vaults_info" ], "properties": { - "vault_configs": { + "vaults_info": { "type": "object", "properties": { "limit": { @@ -2515,12 +2515,12 @@ "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" }, - "vault_configs": { + "vaults_info": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_VaultInstantiateConfig", + "title": "Array_of_VaultInfoResponse", "type": "array", "items": { - "$ref": "#/definitions/VaultInstantiateConfig" + "$ref": "#/definitions/VaultInfoResponse" }, "definitions": { "Coin": { @@ -2582,16 +2582,25 @@ }, "additionalProperties": false }, - "VaultInstantiateConfig": { + "VaultInfoResponse": { "type": "object", "required": [ "config", + "utilization", "vault" ], "properties": { "config": { "$ref": "#/definitions/VaultConfig" }, + "utilization": { + "description": "The amount the vault has been utilized, denominated in the same denom set in the vault config's deposit cap", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, "vault": { "$ref": "#/definitions/VaultBase_for_String" } diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index e70021ca2..696b70964 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -116,13 +116,13 @@ "additionalProperties": false }, { - "description": "Configs on vaults", + "description": "Configs & deposit caps on vaults", "type": "object", "required": [ - "vault_configs" + "vaults_info" ], "properties": { - "vault_configs": { + "vaults_info": { "type": "object", "properties": { "limit": { @@ -1219,12 +1219,12 @@ "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" }, - "vault_configs": { + "vaults_info": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_VaultInstantiateConfig", + "title": "Array_of_VaultInfoResponse", "type": "array", "items": { - "$ref": "#/definitions/VaultInstantiateConfig" + "$ref": "#/definitions/VaultInfoResponse" }, "definitions": { "Coin": { @@ -1286,16 +1286,25 @@ }, "additionalProperties": false }, - "VaultInstantiateConfig": { + "VaultInfoResponse": { "type": "object", "required": [ "config", + "utilization", "vault" ], "properties": { "config": { "$ref": "#/definitions/VaultConfig" }, + "utilization": { + "description": "The amount the vault has been utilized, denominated in the same denom set in the vault config's deposit cap", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, "vault": { "$ref": "#/definitions/VaultBase_for_String" } diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 1e06b9405..1b32f3a31 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,8 +1,8 @@ { - "mockVault": "osmo1r5p5n3ds36sl99l73znhr2u74a7tlqdxp648c2n0wmkprlufdadqgwnw9u", - "marsOracleAdapter": "osmo14vpwv95jnwxcdcqpv27gqey0p6r48canax9er3vdq8cddg79jv8s40kf33", - "swapper": "osmo1940km39gjdxxprg5q0220d2upz9ff3eju5jmcrfpp3dcehct4a9spm8yxl", - "zapper": "osmo1c90nreaa93p4g6hs2zhyaxtc5fjpwa7snzecrgfc3kr8t24cpwyshllpa5", - "creditManager": "osmo1jjle0v4vgeusqg3h80uxnvwwdd078u5a4nhzukqjhzj3nf88pnkqrpecum", - "accountNft": "osmo1cxexgfldv506h4ctmyp3zc7v2zc0tsjqhyy9qe3cvncljl507egqcyy35w" + "mockVault": "osmo13vg6se4stfp5hdgykwy2wj5r2r3qjjd230nvmnqjzvel9dcacdfqskjx4f", + "marsOracleAdapter": "osmo15ym2679968p5kkl4xy55uue0x6cddtgc8y9skkwy8xwahrrue0tqnjmsve", + "swapper": "osmo12k5cua86nenxw6jrt4dyu5t29s6td4nr45tqjnsj8hzd7prfss9qllz5ay", + "zapper": "osmo14gzl0fh8mstmp9l2zxaxmf0dqeraq0e9wx5cruy4ehhnzud4ph2qjhpfxj", + "creditManager": "osmo1qnun3tw0kgx563e8tz296uc3dz264p88fgj2e69dgvm9vyye2ryqhjyumn", + "accountNft": "osmo17tlnpmz4ujj0aza44atfvf8w7w2u7tg3a8zqkm7h9sq42lc4fr3s9lzqwp" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 2accb744f..ffbcf2ae4 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -49,10 +49,6 @@ export class Deployer { printGreen(`${this.config.chain.id} :: ${name} : ${this.storage.codeIds[name]}`) } - setOwnerAddr() { - this.storage.owner = this.deployerAddr - } - async instantiate(name: keyof Storage['addresses'], codeId: number, msg: InstantiateMsgs) { if (this.storage.addresses[name]) { printGray(`Contract already instantiated :: ${name} :: ${this.storage.addresses[name]}`) @@ -64,7 +60,7 @@ export class Deployer { msg, `mars-${name}`, 'auto', - { admin: this.storage.owner }, + { admin: this.deployerAddr }, ) this.storage.addresses[name] = contractAddress printGreen( diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 82d073f2d..52458cd4a 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -24,9 +24,6 @@ export const taskRunner = async ({ await deployer.upload('zapper', wasmFile(zapperContractName)) await deployer.upload('creditManager', wasmFile('mars_credit_manager')) - // Set contracts owner - deployer.setOwnerAddr() - // Instantiate contracts await deployer.instantiateMockVault() await deployer.instantiateMarsOracleAdapter() diff --git a/scripts/deploy/base/storage.ts b/scripts/deploy/base/storage.ts index 3a045c355..fb0c52c3f 100644 --- a/scripts/deploy/base/storage.ts +++ b/scripts/deploy/base/storage.ts @@ -8,13 +8,11 @@ export class Storage implements StorageItems { public addresses: StorageItems['addresses'] public codeIds: StorageItems['codeIds'] public actions: StorageItems['actions'] - public owner: StorageItems['owner'] constructor(private chainId: string, items: StorageItems) { this.addresses = items.addresses this.codeIds = items.codeIds this.actions = items.actions - this.owner = items.owner } static async load(chainId: string): Promise { diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index fbc1c5289..55440f1bd 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -37,6 +37,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { redBank: { addr: 'osmo18w58j2dlpre6kslls9w88aur5ud8000wvg8pw4fp80p6q97g6qtqvhztpv' }, swapRoutes: [ { denomIn: uosmo, denomOut: uatom, route: [{ token_out_denom: uatom, pool_id: '1' }] }, + { denomIn: uatom, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '1' }] }, ], zapper: { addr: 'osmo150dpk65f6deunksn94xtvu249hnr2hwqe335ukucltlwh3uz87hq898s7q' }, vaults: [ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index d7c7363ec..58842a652 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -53,18 +53,19 @@ import { HealthResponse, Positions, DebtAmount, - ArrayOfVaultInstantiateConfig, + ArrayOfVaultInfoResponse, + VaultInfoResponse, } from './MarsCreditManager.types' export interface MarsCreditManagerReadOnlyInterface { contractAddress: string config: () => Promise - vaultConfigs: ({ + vaultsInfo: ({ limit, startAfter, }: { limit?: number startAfter?: VaultBaseForString - }) => Promise + }) => Promise allowedCoins: ({ limit, startAfter, @@ -128,7 +129,7 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn this.client = client this.contractAddress = contractAddress this.config = this.config.bind(this) - this.vaultConfigs = this.vaultConfigs.bind(this) + this.vaultsInfo = this.vaultsInfo.bind(this) this.allowedCoins = this.allowedCoins.bind(this) this.positions = this.positions.bind(this) this.health = this.health.bind(this) @@ -148,15 +149,15 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn config: {}, }) } - vaultConfigs = async ({ + vaultsInfo = async ({ limit, startAfter, }: { limit?: number startAfter?: VaultBaseForString - }): Promise => { + }): Promise => { return this.client.queryContractSmart(this.contractAddress, { - vault_configs: { + vaults_info: { limit, start_after: startAfter, }, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 02caddb47..ed0956b03 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -54,7 +54,8 @@ import { HealthResponse, Positions, DebtAmount, - ArrayOfVaultInstantiateConfig, + ArrayOfVaultInfoResponse, + VaultInfoResponse, } from './MarsCreditManager.types' export interface MarsCreditManagerMessage { contractAddress: string diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 65a179928..ef41ac4be 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -54,7 +54,8 @@ import { HealthResponse, Positions, DebtAmount, - ArrayOfVaultInstantiateConfig, + ArrayOfVaultInfoResponse, + VaultInfoResponse, } from './MarsCreditManager.types' import { MarsCreditManagerQueryClient, MarsCreditManagerClient } from './MarsCreditManager.client' export const marsCreditManagerQueryKeys = { @@ -69,9 +70,9 @@ export const marsCreditManagerQueryKeys = { [ { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, ] as const, - vaultConfigs: (contractAddress: string | undefined, args?: Record) => + vaultsInfo: (contractAddress: string | undefined, args?: Record) => [ - { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'vault_configs', args }, + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'vaults_info', args }, ] as const, allowedCoins: (contractAddress: string | undefined, args?: Record) => [ @@ -437,23 +438,23 @@ export function useMarsCreditManagerAllowedCoinsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsCreditManagerVaultConfigsQuery - extends MarsCreditManagerReactQuery { +export interface MarsCreditManagerVaultsInfoQuery + extends MarsCreditManagerReactQuery { args: { limit?: number startAfter?: VaultBaseForString } } -export function useMarsCreditManagerVaultConfigsQuery({ +export function useMarsCreditManagerVaultsInfoQuery({ client, args, options, -}: MarsCreditManagerVaultConfigsQuery) { - return useQuery( - marsCreditManagerQueryKeys.vaultConfigs(client?.contractAddress, args), +}: MarsCreditManagerVaultsInfoQuery) { + return useQuery( + marsCreditManagerQueryKeys.vaultsInfo(client?.contractAddress, args), () => client - ? client.vaultConfigs({ + ? client.vaultsInfo({ limit: args.limit, startAfter: args.startAfter, }) diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index a7a5430ba..e13489d17 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -288,7 +288,7 @@ export type QueryMsg = config: {} } | { - vault_configs: { + vaults_info: { limit?: number | null start_after?: VaultBaseForString | null } @@ -441,4 +441,9 @@ export interface DebtAmount { denom: string shares: Uint128 } -export type ArrayOfVaultInstantiateConfig = VaultInstantiateConfig[] +export type ArrayOfVaultInfoResponse = VaultInfoResponse[] +export interface VaultInfoResponse { + config: VaultConfig + utilization: Coin + vault: VaultBaseForString +} diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index 20b38d3e6..71df78b11 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -40,20 +40,20 @@ import { ArrayOfCoin, Positions, DebtAmount, - ArrayOfVaultInstantiateConfig, - VaultInstantiateConfig, + ArrayOfVaultInfoResponse, + VaultInfoResponse, VaultConfig, } from './MarsMockCreditManager.types' export interface MarsMockCreditManagerReadOnlyInterface { contractAddress: string config: () => Promise - vaultConfigs: ({ + vaultsInfo: ({ limit, startAfter, }: { limit?: number startAfter?: VaultBaseForString - }) => Promise + }) => Promise allowedCoins: ({ limit, startAfter, @@ -117,7 +117,7 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe this.client = client this.contractAddress = contractAddress this.config = this.config.bind(this) - this.vaultConfigs = this.vaultConfigs.bind(this) + this.vaultsInfo = this.vaultsInfo.bind(this) this.allowedCoins = this.allowedCoins.bind(this) this.positions = this.positions.bind(this) this.health = this.health.bind(this) @@ -137,15 +137,15 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe config: {}, }) } - vaultConfigs = async ({ + vaultsInfo = async ({ limit, startAfter, }: { limit?: number startAfter?: VaultBaseForString - }): Promise => { + }): Promise => { return this.client.queryContractSmart(this.contractAddress, { - vault_configs: { + vaults_info: { limit, start_after: startAfter, }, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index 1ec6cc983..0e9b856f5 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -41,8 +41,8 @@ import { ArrayOfCoin, Positions, DebtAmount, - ArrayOfVaultInstantiateConfig, - VaultInstantiateConfig, + ArrayOfVaultInfoResponse, + VaultInfoResponse, VaultConfig, } from './MarsMockCreditManager.types' export interface MarsMockCreditManagerMessage { diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index aa05468a2..071b09d79 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -41,8 +41,8 @@ import { ArrayOfCoin, Positions, DebtAmount, - ArrayOfVaultInstantiateConfig, - VaultInstantiateConfig, + ArrayOfVaultInfoResponse, + VaultInfoResponse, VaultConfig, } from './MarsMockCreditManager.types' import { @@ -61,11 +61,11 @@ export const marsMockCreditManagerQueryKeys = { [ { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, ] as const, - vaultConfigs: (contractAddress: string | undefined, args?: Record) => + vaultsInfo: (contractAddress: string | undefined, args?: Record) => [ { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], - method: 'vault_configs', + method: 'vaults_info', args, }, ] as const, @@ -435,23 +435,23 @@ export function useMarsMockCreditManagerAllowedCoinsQuery { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsMockCreditManagerVaultConfigsQuery - extends MarsMockCreditManagerReactQuery { +export interface MarsMockCreditManagerVaultsInfoQuery + extends MarsMockCreditManagerReactQuery { args: { limit?: number startAfter?: VaultBaseForString } } -export function useMarsMockCreditManagerVaultConfigsQuery({ +export function useMarsMockCreditManagerVaultsInfoQuery({ client, args, options, -}: MarsMockCreditManagerVaultConfigsQuery) { - return useQuery( - marsMockCreditManagerQueryKeys.vaultConfigs(client?.contractAddress, args), +}: MarsMockCreditManagerVaultsInfoQuery) { + return useQuery( + marsMockCreditManagerQueryKeys.vaultsInfo(client?.contractAddress, args), () => client - ? client.vaultConfigs({ + ? client.vaultsInfo({ limit: args.limit, startAfter: args.startAfter, }) diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 0e08c6460..e10b5a9e2 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -30,7 +30,7 @@ export type QueryMsg = config: {} } | { - vault_configs: { + vaults_info: { limit?: number | null start_after?: VaultBaseForString | null } @@ -186,9 +186,10 @@ export interface DebtAmount { denom: string shares: Uint128 } -export type ArrayOfVaultInstantiateConfig = VaultInstantiateConfig[] -export interface VaultInstantiateConfig { +export type ArrayOfVaultInfoResponse = VaultInfoResponse[] +export interface VaultInfoResponse { config: VaultConfig + utilization: Coin vault: VaultBaseForString } export interface VaultConfig { From 98f27a3448e9284a8736af8408322f14e81f5ed5 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 29 Dec 2022 21:35:01 +0100 Subject: [PATCH 115/218] Adjust deposit cap params (#84) adjust deposit cap params --- scripts/deploy/addresses/osmo-test-4.json | 12 ++++++------ scripts/deploy/osmosis/config.ts | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 1b32f3a31..9f2078219 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,8 +1,8 @@ { - "mockVault": "osmo13vg6se4stfp5hdgykwy2wj5r2r3qjjd230nvmnqjzvel9dcacdfqskjx4f", - "marsOracleAdapter": "osmo15ym2679968p5kkl4xy55uue0x6cddtgc8y9skkwy8xwahrrue0tqnjmsve", - "swapper": "osmo12k5cua86nenxw6jrt4dyu5t29s6td4nr45tqjnsj8hzd7prfss9qllz5ay", - "zapper": "osmo14gzl0fh8mstmp9l2zxaxmf0dqeraq0e9wx5cruy4ehhnzud4ph2qjhpfxj", - "creditManager": "osmo1qnun3tw0kgx563e8tz296uc3dz264p88fgj2e69dgvm9vyye2ryqhjyumn", - "accountNft": "osmo17tlnpmz4ujj0aza44atfvf8w7w2u7tg3a8zqkm7h9sq42lc4fr3s9lzqwp" + "mockVault": "osmo1j6xne7vfsk40u9ksjy66twmjtex8su3yjldmkjrjtfpt5sjuu6zs9tqmv3", + "marsOracleAdapter": "osmo1a43s9x6v643weudyqx4yry4tulucqhecsntu0fr7sjjnua0salnss0qfwe", + "swapper": "osmo1c6e5g6ys5vwl6rtd3chkawtgxyev8sxm4nvc9rf2wttjc29scaeqr3wtuz", + "zapper": "osmo1mvxutyanyelkc428pe3dyz96dqa56gg20wt0yugjurvvfexz5tdsgr2d0x", + "creditManager": "osmo1yxw9nnkvexayyfsqrh6yss6vwvtuknnyza2ezyq3tkv5j479mudqnk40z4", + "accountNft": "osmo1smhhaqmqeu9f6p7x6rfw9lk3n8y8520hyx23dqfpl6qklwd0q8jsu4ws2x" } diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 55440f1bd..7f4b24074 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -45,7 +45,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { // https://github.com/apollodao/apollo-config/blob/master/config.json#L114 vault: { address: autoCompoundingVault }, config: { - deposit_cap: { denom: uosmo, amount: '100000000000000000000000000' }, // 100 osmo + deposit_cap: { denom: uosmo, amount: '100000000' }, // 100 osmo liquidation_threshold: '0.75', max_ltv: '0.65', whitelisted: true, @@ -55,10 +55,10 @@ export const osmosisTestnetConfig: DeploymentConfig = { testActions: { vault: { depositAmount: '1000000', - withdrawAmount: '1', + withdrawAmount: '1000000', mock: { config: { - deposit_cap: { denom: uosmo, amount: '100000000000000000000000000' }, // 100 osmo + deposit_cap: { denom: uosmo, amount: '100000000' }, // 100 osmo liquidation_threshold: '0.75', max_ltv: '0.65', whitelisted: true, From 6e318e9fdcb749ad634b2b196401484c135d93e4 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 2 Jan 2023 10:02:48 +0100 Subject: [PATCH 116/218] Updating build script price sources (#85) * Updating build script price sources * adjusting base token deploy config --- scripts/deploy/addresses/osmo-test-4.json | 12 ++++----- scripts/deploy/base/deployer.ts | 30 ++++++++--------------- scripts/deploy/osmosis/config.ts | 8 +++--- scripts/types/config.ts | 7 +++--- scripts/types/priceSource.ts | 22 +++++++++++++++++ 5 files changed, 46 insertions(+), 33 deletions(-) create mode 100644 scripts/types/priceSource.ts diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 9f2078219..3351bddd6 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,8 +1,8 @@ { - "mockVault": "osmo1j6xne7vfsk40u9ksjy66twmjtex8su3yjldmkjrjtfpt5sjuu6zs9tqmv3", - "marsOracleAdapter": "osmo1a43s9x6v643weudyqx4yry4tulucqhecsntu0fr7sjjnua0salnss0qfwe", - "swapper": "osmo1c6e5g6ys5vwl6rtd3chkawtgxyev8sxm4nvc9rf2wttjc29scaeqr3wtuz", - "zapper": "osmo1mvxutyanyelkc428pe3dyz96dqa56gg20wt0yugjurvvfexz5tdsgr2d0x", - "creditManager": "osmo1yxw9nnkvexayyfsqrh6yss6vwvtuknnyza2ezyq3tkv5j479mudqnk40z4", - "accountNft": "osmo1smhhaqmqeu9f6p7x6rfw9lk3n8y8520hyx23dqfpl6qklwd0q8jsu4ws2x" + "mockVault": "osmo1666zn7mz558793eka3ll2dsll34w3nsls3etcpgyka88mxepqmss0eyjl0", + "marsOracleAdapter": "osmo18lu2s7jzrga3zf0e30exd60dvfkw90krzddt9zhqm9zpap80jmfs2gckz6", + "swapper": "osmo1sgme3vlj96dyapq4a5s6paf4huw0eflvgrkhs5xruntlavn9g4aswe2ejk", + "zapper": "osmo1hzn6judff44stlnmyuu8vaxjkrc2nurc2xvu00u0hs6gxrjx54esla3lxv", + "creditManager": "osmo1vzx4gy7rphrzkjyym4vwml995rydaqy8jqx5mcx7elmxuyypcvtqhd7huh", + "accountNft": "osmo1prkk67duuvzvp2v96y4t0mcln4slfvcq9p4v6jy28p5gyt64nq5ss8p6j5" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index ffbcf2ae4..03fd7625e 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -25,6 +25,7 @@ import { import { MarsAccountNftClient } from '../../types/generated/mars-account-nft/MarsAccountNft.client' import { MarsCreditManagerClient } from '../../types/generated/mars-credit-manager/MarsCreditManager.client' import { InitOrUpdateAssetParams } from '../../types/generated/mars-mock-red-bank/MarsMockRedBank.types' +import { PriceSource } from '../../types/priceSource' export class Deployer { constructor( @@ -116,7 +117,7 @@ export class Deployer { msg.vault_pricing.push({ addr: this.storage.addresses.mockVault!, method: 'preview_redeem', - base_denom: this.config.chain.baseDenom, + base_denom: this.config.testActions.vault.mock.baseToken.denom, vault_coin_denom: this.config.testActions.vault.mock.vaultTokenDenom, }) } @@ -272,28 +273,17 @@ export class Deployer { const { client, addr } = await this.getOutpostsDeployer() for (const coin of this.config - .testActions!.zap.coinsIn.map((c) => ({ denom: c.denom, price: c.price })) + .testActions!.zap.coinsIn.map((c) => ({ denom: c.denom, priceSource: c.priceSource })) .concat(this.config.testActions!.zap.denomOut) .concat(this.config.testActions!.vault.mock.baseToken)) { - try { - await client.queryContractSmart(this.config.oracle.addr, { - price: { - denom: coin.denom, - }, - }) - printGray(`Price for ${coin.denom} already set`) - } catch { - const msg = { - set_price_source: { - denom: coin.denom, - price_source: { - fixed: { price: coin.price }, - }, - }, - } - printBlue(`Setting price for ${coin.denom}: ${coin.price}`) - await client.execute(addr, this.config.oracle.addr, msg, 'auto') + const msg = { + set_price_source: { + denom: coin.denom, + price_source: coin.priceSource as PriceSource, + }, } + printBlue(`Setting price source for ${coin.denom}: ${JSON.stringify(coin.priceSource)}`) + await client.execute(addr, this.config.oracle.addr, msg, 'auto') } this.storage.actions.oraclePricesSet = true } diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 7f4b24074..c7901a5f2 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -66,7 +66,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { vaultTokenDenom: udig, type: VaultType.LOCKED, lockup: { time: 900 }, // 15 mins - baseToken: { denom: gammPool1, price: '1.75' }, + baseToken: { denom: gammPool1, priceSource: { xyk_liquidity_token: { pool_id: 1 } } }, }, }, outpostsDeployerMnemonic: @@ -91,10 +91,10 @@ export const osmosisTestnetConfig: DeploymentConfig = { withdrawAmount: '12', zap: { coinsIn: [ - { denom: uatom, amount: '1', price: '2.135' }, - { denom: uosmo, amount: '3', price: '1' }, + { denom: uatom, amount: '1', priceSource: { twap: { pool_id: 1, window_size: 1800 } } }, + { denom: uosmo, amount: '3', priceSource: { fixed: { price: '1' } } }, ], - denomOut: { denom: gammPool1, price: '1.75' }, + denomOut: { denom: gammPool1, priceSource: { xyk_liquidity_token: { pool_id: 1 } } }, }, }, } diff --git a/scripts/types/config.ts b/scripts/types/config.ts index af884e8f8..8817beb0c 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -4,6 +4,7 @@ import { VaultInstantiateConfig, } from './generated/mars-credit-manager/MarsCreditManager.types' import { VaultPricingInfo } from './generated/mars-oracle-adapter/MarsOracleAdapter.types' +import { PriceSource } from './priceSource' export enum VaultType { LOCKED, @@ -48,7 +49,7 @@ export interface TestActions { config: VaultConfig vaultTokenDenom: string lockup?: Duration - baseToken: { denom: string; price: string } + baseToken: { denom: string; priceSource: PriceSource } } } outpostsDeployerMnemonic: string @@ -65,8 +66,8 @@ export interface TestActions { } withdrawAmount: string zap: { - coinsIn: { amount: string; denom: string; price: string }[] - denomOut: { denom: string; price: string } + coinsIn: { amount: string; denom: string; priceSource: PriceSource }[] + denomOut: { denom: string; priceSource: PriceSource } } unzapAmount: string } diff --git a/scripts/types/priceSource.ts b/scripts/types/priceSource.ts new file mode 100644 index 000000000..aae29eb77 --- /dev/null +++ b/scripts/types/priceSource.ts @@ -0,0 +1,22 @@ +export type PriceSource = + | { + fixed: { + price: string + } + } + | { + spot: { + pool_id: number + } + } + | { + twap: { + pool_id: number + window_size: number + } + } + | { + xyk_liquidity_token: { + pool_id: number + } + } From 75c80b7366007f13eb5bced4abbe8187dbe91f95 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 4 Jan 2023 15:11:56 +0100 Subject: [PATCH 117/218] Use crates.io owner pkg (#87) * Updating to crates.io owner package * update types * additional renames --- Cargo.lock | 166 +-- Cargo.toml | 13 +- contracts/credit-manager/Cargo.toml | 28 +- contracts/credit-manager/src/contract.rs | 4 +- contracts/credit-manager/src/instantiate.rs | 10 +- contracts/credit-manager/src/query.rs | 8 +- contracts/credit-manager/src/state.rs | 4 +- contracts/credit-manager/src/update_config.rs | 12 +- .../credit-manager/tests/helpers/mock_env.rs | 38 +- contracts/credit-manager/tests/test_health.rs | 2 +- .../credit-manager/tests/test_instantiate.rs | 14 +- .../credit-manager/tests/test_update_admin.rs | 130 +-- .../tests/test_update_config.rs | 40 +- .../credit-manager/tests/test_zap_withdraw.rs | 2 +- contracts/oracle-adapter/Cargo.toml | 16 +- contracts/oracle-adapter/src/contract.rs | 24 +- contracts/oracle-adapter/src/error.rs | 4 +- contracts/oracle-adapter/src/msg.rs | 10 +- contracts/oracle-adapter/src/state.rs | 4 +- contracts/oracle-adapter/tests/helpers.rs | 8 +- .../oracle-adapter/tests/test_update_admin.rs | 78 +- .../tests/test_update_config.rs | 24 +- contracts/swapper/base/Cargo.toml | 16 +- contracts/swapper/base/src/contract.rs | 26 +- contracts/swapper/base/src/error.rs | 4 +- contracts/swapper/mock/src/contract.rs | 4 +- contracts/swapper/osmosis/Cargo.toml | 22 +- contracts/swapper/osmosis/tests/helpers.rs | 8 +- .../swapper/osmosis/tests/test_estimate.rs | 4 +- .../swapper/osmosis/tests/test_instantiate.rs | 14 +- .../swapper/osmosis/tests/test_set_route.rs | 10 +- contracts/swapper/osmosis/tests/test_swap.rs | 4 +- .../osmosis/tests/test_update_admin.rs | 114 +- contracts/zapper/osmosis/tests/helpers.rs | 6 +- .../zapper/osmosis/tests/test_callback.rs | 4 +- .../osmosis/tests/test_provide_liquidity.rs | 30 +- .../osmosis/tests/test_withdraw_liquidity.rs | 12 +- packages/controllers/Cargo.toml | 18 - packages/controllers/src/admin.rs | 972 ------------------ packages/controllers/src/lib.rs | 3 - packages/rover/Cargo.toml | 26 +- packages/rover/src/adapters/swap/msgs.rs | 16 +- packages/rover/src/error.rs | 4 +- packages/rover/src/msg/execute.rs | 6 +- packages/rover/src/msg/instantiate.rs | 2 +- packages/rover/src/msg/query.rs | 4 +- .../mars-credit-manager.json | 126 +-- .../mars-mock-credit-manager.json | 14 +- .../mars-oracle-adapter.json | 96 +- .../mars-swapper-base/mars-swapper-base.json | 134 +-- scripts/deploy/base/deployer.ts | 6 +- .../MarsCreditManager.client.ts | 10 +- .../MarsCreditManager.message-composer.ts | 10 +- .../MarsCreditManager.react-query.ts | 12 +- .../MarsCreditManager.types.ts | 14 +- .../MarsMockCreditManager.types.ts | 4 +- .../MarsOracleAdapter.client.ts | 10 +- .../MarsOracleAdapter.message-composer.ts | 10 +- .../MarsOracleAdapter.react-query.ts | 12 +- .../MarsOracleAdapter.types.ts | 14 +- .../MarsSwapperBase.client.ts | 20 +- .../MarsSwapperBase.message-composer.ts | 12 +- .../MarsSwapperBase.react-query.ts | 32 +- .../MarsSwapperBase.types.ts | 22 +- 64 files changed, 772 insertions(+), 1754 deletions(-) delete mode 100644 packages/controllers/Cargo.toml delete mode 100644 packages/controllers/src/admin.rs delete mode 100644 packages/controllers/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f12a7649c..b661a3cc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,9 +24,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "apollo-utils" @@ -41,9 +41,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.59" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" dependencies = [ "proc-macro2", "quote", @@ -249,7 +249,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20b42021d8488665b1a0d9748f1f81df7235362d194f44481e2e61bf376b77b4" dependencies = [ - "prost 0.11.3", + "prost 0.11.5", "prost-types", "tendermint-proto", ] @@ -415,19 +415,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cw-controllers-admin-fork" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.0.1", - "cw-utils 0.16.0", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw-dex" version = "0.0.1" @@ -531,7 +518,7 @@ checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 1.0.0", + "cw2 1.0.1", "schemars", "semver", "serde", @@ -553,13 +540,13 @@ dependencies = [ [[package]] name = "cw2" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03bdf3747540b47bc1bdaf50ba3aa5e4276ab0c2ce73e8b367ebe260cc37ff9c" +checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.16.0", + "cw-storage-plus 1.0.1", "schemars", "serde", ] @@ -650,9 +637,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" +checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" [[package]] name = "ecdsa" @@ -944,9 +931,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" @@ -993,9 +980,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.138" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libloading" @@ -1025,7 +1012,7 @@ dependencies = [ "cosmwasm-std", "cw-multi-test", "cw-storage-plus 1.0.1", - "cw2 1.0.0", + "cw2 1.0.1", "cw721", "cw721-base", "mars-mock-credit-manager", @@ -1041,12 +1028,11 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vault-standard", - "cw-controllers-admin-fork", "cw-item-set", "cw-multi-test", "cw-storage-plus 1.0.1", "cw-utils 0.16.0", - "cw2 1.0.0", + "cw2 1.0.1", "cw721", "cw721-base", "itertools", @@ -1057,6 +1043,7 @@ dependencies = [ "mars-mock-vault", "mars-oracle-adapter", "mars-outpost", + "mars-owner", "mars-rover", "mars-swapper-mock", "mars-zapper-mock", @@ -1123,14 +1110,14 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vault-standard", - "cw-controllers-admin-fork", "cw-multi-test", "cw-storage-plus 1.0.1", "cw-utils 0.16.0", - "cw2 1.0.0", + "cw2 1.0.1", "mars-mock-oracle", "mars-mock-vault", "mars-outpost", + "mars-owner", "mars-rover", "thiserror", ] @@ -1153,6 +1140,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "mars-owner" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5644a8b047a0d64d04706414805872f35439755e057431152dd36e6f369be24" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "schemars", + "thiserror", +] + [[package]] name = "mars-rover" version = "1.0.0" @@ -1160,13 +1160,13 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vault-standard", - "cw-controllers-admin-fork", "cw-storage-plus 1.0.1", "cw-utils 0.16.0", "mars-health", "mars-mock-oracle", "mars-mock-red-bank", "mars-outpost", + "mars-owner", "schemars", "serde", "thiserror", @@ -1178,8 +1178,8 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-controllers-admin-fork", "cw-storage-plus 1.0.1", + "mars-owner", "mars-rover", "schemars", "serde", @@ -1206,10 +1206,10 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-controllers-admin-fork", "cw-storage-plus 1.0.1", - "cw2 1.0.0", + "cw2 1.0.1", "mars-osmosis", + "mars-owner", "mars-rover", "mars-swapper-base", "osmosis-std 0.13.2", @@ -1251,7 +1251,7 @@ dependencies = [ "cosmwasm-std", "cw-dex", "cw-utils 0.16.0", - "cw2 1.0.0", + "cw2 1.0.1", "mars-zapper-base", "osmosis-testing", ] @@ -1270,9 +1270,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nom" -version = "7.1.1" +version = "7.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" dependencies = [ "memchr", "minimal-lexical", @@ -1319,9 +1319,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "opaque-debug" @@ -1343,7 +1343,7 @@ dependencies = [ "chrono", "cosmwasm-std", "osmosis-std-derive 0.12.0", - "prost 0.11.3", + "prost 0.11.5", "prost-types", "schemars", "serde", @@ -1359,7 +1359,7 @@ dependencies = [ "chrono", "cosmwasm-std", "osmosis-std-derive 0.13.2", - "prost 0.11.3", + "prost 0.11.5", "prost-types", "schemars", "serde", @@ -1400,7 +1400,7 @@ dependencies = [ "cosmrs", "cosmwasm-std", "osmosis-std 0.13.2", - "prost 0.11.3", + "prost 0.11.5", "serde", "serde_json", "thiserror", @@ -1408,9 +1408,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1c2c742266c2f1041c914ba65355a83ae8747b05f208319784083583494b4b" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "pbkdf2" @@ -1451,9 +1451,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] @@ -1470,12 +1470,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b18e655c21ff5ac2084a5ad0611e827b3f92badf79f4910b5a5c58f4d87ff0" +checksum = "c01db6702aa05baa3f57dec92b8eeeeb4cb19e894e73996b32a4093289e54592" dependencies = [ "bytes", - "prost-derive 0.11.2", + "prost-derive 0.11.5", ] [[package]] @@ -1493,9 +1493,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164ae68b6587001ca506d3bf7f1000bfa248d0e1217b618108fba4ec1d0cc306" +checksum = "c8842bad1a5419bca14eac663ba798f6bc19c413c2fdceb5f3ba3b0932d96720" dependencies = [ "anyhow", "itertools", @@ -1506,19 +1506,19 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747761bc3dc48f9a34553bf65605cf6cb6288ba219f3450b4275dbd81539551a" +checksum = "017f79637768cde62820bc2d4fe0e45daaa027755c323ad077767c6c5f173091" dependencies = [ "bytes", - "prost 0.11.3", + "prost 0.11.5", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -1594,9 +1594,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "schemars" @@ -1638,15 +1638,15 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" [[package]] name = "serde" -version = "1.0.150" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] @@ -1671,18 +1671,18 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +checksum = "718dc5fff5b36f99093fc49b280cfc96ce6fc824317783bff5a1fed0c7a64819" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.150" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -1702,9 +1702,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "itoa", "ryu", @@ -1713,9 +1713,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" dependencies = [ "proc-macro2", "quote", @@ -1811,9 +1811,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -1847,7 +1847,7 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost 0.11.3", + "prost 0.11.5", "prost-types", "ripemd160", "serde", @@ -1873,7 +1873,7 @@ dependencies = [ "flex-error", "num-derive", "num-traits", - "prost 0.11.3", + "prost 0.11.5", "prost-types", "serde", "serde_bytes", @@ -1898,18 +1898,18 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -1953,9 +1953,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-xid" diff --git a/Cargo.toml b/Cargo.toml index 916cbf76a..6c05d8466 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ members = [ "packages/health", "packages/outpost", "packages/rover", - "packages/controllers", # Mock contracts "contracts/mock-oracle", @@ -50,12 +49,12 @@ serde = { version = "1.0.150", default-features = false, features = [" thiserror = "1.0.37" # packages -cosmwasm-vault-standard = { version = "0.1.0", features = ["lockup", "force-unlock"] } -cw-controllers-admin-fork = { version = "1.0.0", path = "./packages/controllers" } -mars-health = { version = "1.0.0", path = "./packages/health" } -mars-osmosis = { version = "1.0.0", path = "./packages/chains/osmosis" } -mars-outpost = { version = "1.0.0", path = "./packages/outpost" } -mars-rover = { version = "1.0.0", path = "./packages/rover" } +cosmwasm-vault-standard = { version = "0.1.0", features = ["lockup", "force-unlock"] } +mars-health = { version = "1.0.0", path = "./packages/health" } +mars-osmosis = { version = "1.0.0", path = "./packages/chains/osmosis" } +mars-outpost = { version = "1.0.0", path = "./packages/outpost" } +mars-owner = "1.0.0" +mars-rover = { version = "1.0.0", path = "./packages/rover" } # contracts mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 183c22614..63d389b1c 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -18,20 +18,20 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cosmwasm-vault-standard = { workspace = true } -cw2 = { workspace = true } -cw721 = { workspace = true } -cw721-base = { workspace = true } -cw-controllers-admin-fork = { workspace = true } -cw-item-set = { workspace = true } -cw-storage-plus = { workspace = true } -mars-account-nft = { workspace = true } -mars-health = { workspace = true } -mars-outpost = { workspace = true } -mars-oracle-adapter = { workspace = true } -mars-rover = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cosmwasm-vault-standard = { workspace = true } +cw2 = { workspace = true } +cw721 = { workspace = true } +cw721-base = { workspace = true } +cw-item-set = { workspace = true } +cw-storage-plus = { workspace = true } +mars-account-nft = { workspace = true } +mars-health = { workspace = true } +mars-oracle-adapter = { workspace = true } +mars-outpost = { workspace = true } +mars-owner = { workspace = true } +mars-rover = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 5dd59153a..f3008883b 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -17,7 +17,7 @@ use crate::query::{ query_config, query_positions, query_total_debt_shares, query_total_vault_coin_balance, query_vaults_info, }; -use crate::update_config::{update_admin, update_config}; +use crate::update_config::{update_config, update_owner}; use crate::vault::handle_unlock_request_reply; use crate::zap::{estimate_provide_liquidity, estimate_withdraw_liquidity}; @@ -50,7 +50,7 @@ pub fn execute( match msg { ExecuteMsg::CreateCreditAccount {} => create_credit_account(deps, info.sender), ExecuteMsg::UpdateConfig { new_config } => update_config(deps, info, new_config), - ExecuteMsg::UpdateAdmin(update) => update_admin(deps, info, update), + ExecuteMsg::UpdateOwner(update) => update_owner(deps, info, update), ExecuteMsg::Callback(callback) => execute_callback(deps, info, env, callback), ExecuteMsg::UpdateCreditAccount { account_id, diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 9639a766b..c8e4ff570 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,25 +1,25 @@ use std::collections::HashSet; use cosmwasm_std::{Decimal, DepsMut}; -use cw_controllers_admin_fork::AdminInit::SetInitialAdmin; +use mars_owner::OwnerInit::SetInitialOwner; use mars_rover::error::ContractError::InvalidConfig; use mars_rover::error::ContractResult; use mars_rover::msg::instantiate::VaultInstantiateConfig; use mars_rover::msg::InstantiateMsg; -use crate::state::ADMIN; +use crate::state::OWNER; use crate::state::{ ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, RED_BANK, SWAPPER, VAULT_CONFIGS, ZAPPER, }; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { - ADMIN.initialize( + OWNER.initialize( deps.storage, deps.api, - SetInitialAdmin { - admin: msg.admin.clone(), + SetInitialOwner { + owner: msg.owner.clone(), }, )?; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index f87178953..509cc0864 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -8,7 +8,7 @@ use mars_rover::msg::query::{ VaultInfoResponse, VaultPositionResponseItem, VaultWithBalance, }; -use crate::state::ADMIN; +use crate::state::OWNER; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_CONFIGS, @@ -21,10 +21,10 @@ const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; pub fn query_config(deps: Deps) -> ContractResult { - let admin_state = ADMIN.query(deps.storage)?; + let owner_res = OWNER.query(deps.storage)?; Ok(ConfigResponse { - admin: admin_state.admin, - proposed_new_admin: admin_state.proposed, + owner: owner_res.owner, + proposed_new_owner: owner_res.proposed, account_nft: ACCOUNT_NFT .may_load(deps.storage)? .map(|addr| addr.to_string()), diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index de1b704b3..9c4e085a5 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Addr, Decimal, Uint128}; -use cw_controllers_admin_fork::Admin; use cw_item_set::Set; use cw_storage_plus::{Item, Map}; +use mars_owner::Owner; use mars_rover::adapters::swap::Swapper; use mars_rover::adapters::vault::{VaultConfig, VaultPositionAmount}; @@ -19,7 +19,7 @@ pub const VAULT_CONFIGS: Map<&Addr, VaultConfig> = Map::new("vault_configs"); pub const ZAPPER: Item = Item::new("zapper"); // Config -pub const ADMIN: Admin = Admin::new("admin"); +pub const OWNER: Owner = Owner::new("owner"); pub const ALLOWED_COINS: Set<&str> = Set::new("allowed_coins"); pub const MAX_CLOSE_FACTOR: Item = Item::new("max_close_factor"); pub const MAX_UNLOCKING_POSITIONS: Item = Item::new("max_unlocking_positions"); diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index 2697e1fb7..c973b73bf 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, MessageInfo, Response, WasmMsg}; -use cw_controllers_admin_fork::AdminUpdate; use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; +use mars_owner::OwnerUpdate; use mars_rover::error::ContractResult; use mars_rover::msg::instantiate::ConfigUpdates; use mars_rover::traits::{FallbackStr, Stringify}; @@ -9,7 +9,7 @@ use mars_rover::traits::{FallbackStr, Stringify}; use crate::instantiate::{ assert_lte_to_one, assert_no_duplicate_coins, assert_no_duplicate_vaults, }; -use crate::state::ADMIN; +use crate::state::OWNER; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, SWAPPER, VAULT_CONFIGS, ZAPPER, @@ -20,7 +20,7 @@ pub fn update_config( info: MessageInfo, new_config: ConfigUpdates, ) -> ContractResult { - ADMIN.assert_admin(deps.storage, &info.sender)?; + OWNER.assert_owner(deps.storage, &info.sender)?; let mut response = Response::new().add_attribute("action", "rover/credit-manager/update_config"); @@ -106,10 +106,10 @@ pub fn update_config( Ok(response) } -pub fn update_admin( +pub fn update_owner( deps: DepsMut, info: MessageInfo, - update: AdminUpdate, + update: OwnerUpdate, ) -> ContractResult { - Ok(ADMIN.update(deps, info, update)?) + Ok(OWNER.update(deps, info, update)?) } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index a3c7857a1..de91d2e01 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -6,9 +6,9 @@ use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; use cosmwasm_vault_standard::extensions::lockup::{LockupQueryMsg, UnlockingPosition}; use cosmwasm_vault_standard::msg::VaultStandardQueryMsg::{Info as VaultInfoMsg, VaultExtension}; use cosmwasm_vault_standard::msg::{ExtensionQueryMsg, VaultInfoResponse}; -use cw_controllers_admin_fork::AdminUpdate; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use mars_account_nft::msg::InstantiateMsg as NftInstantiateMsg; +use mars_owner::OwnerUpdate; use mars_account_nft::config::ConfigUpdates as NftConfigUpdates; use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; @@ -56,7 +56,7 @@ pub struct MockEnv { pub struct MockEnvBuilder { pub app: BasicApp, - pub admin: Option, + pub owner: Option, pub vault_configs: Option>, pub pre_deployed_vaults: Option>, pub allowed_coins: Option>, @@ -75,7 +75,7 @@ impl MockEnv { pub fn new() -> MockEnvBuilder { MockEnvBuilder { app: App::default(), - admin: None, + owner: None, vault_configs: None, pre_deployed_vaults: None, allowed_coins: None, @@ -193,11 +193,11 @@ impl MockEnv { ) } - pub fn update_admin(&mut self, sender: &Addr, update: AdminUpdate) -> AnyResult { + pub fn update_owner(&mut self, sender: &Addr, update: OwnerUpdate) -> AnyResult { self.app.execute_contract( sender.clone(), self.rover.clone(), - &ExecuteMsg::UpdateAdmin(update), + &ExecuteMsg::UpdateOwner(update), &[], ) } @@ -511,7 +511,7 @@ impl MockEnvBuilder { pub fn update_config(&mut self, rover: &Addr, new_config: ConfigUpdates) { self.app .execute_contract( - self.get_admin(), + self.get_owner(), rover.clone(), &ExecuteMsg::UpdateConfig { new_config }, &[], @@ -544,9 +544,9 @@ impl MockEnvBuilder { self.app.instantiate_contract( code_id, - self.get_admin(), + self.get_owner(), &InstantiateMsg { - admin: self.get_admin().to_string(), + owner: self.get_owner().to_string(), allowed_coins, vault_configs, red_bank, @@ -562,10 +562,10 @@ impl MockEnvBuilder { ) } - fn get_admin(&self) -> Addr { - self.admin + fn get_owner(&self) -> Addr { + self.owner .clone() - .unwrap_or_else(|| Addr::unchecked("admin")) + .unwrap_or_else(|| Addr::unchecked("owner")) } fn get_oracle(&mut self) -> OracleBase { @@ -594,7 +594,7 @@ impl MockEnvBuilder { .app .instantiate_contract( contract_code_id, - Addr::unchecked("oracle_contract_admin"), + Addr::unchecked("oracle_contract_owner"), &OracleInstantiateMsg { prices }, &[], "mock-oracle", @@ -613,7 +613,7 @@ impl MockEnvBuilder { } fn deploy_oracle_adapter(&mut self, vaults: Vec) -> OracleBase { - let admin = Addr::unchecked("oracle_adapter_contract_admin"); + let owner = Addr::unchecked("oracle_adapter_contract_owner"); let contract_code_id = self.app.store_code(mock_oracle_adapter_contract()); let oracle = self.get_oracle().into(); let vault_pricing = if self.pre_deployed_vaults.is_some() { @@ -640,11 +640,11 @@ impl MockEnvBuilder { .app .instantiate_contract( contract_code_id, - admin.clone(), + owner.clone(), &OracleAdapterInstantiateMsg { oracle, vault_pricing, - admin: admin.to_string(), + owner: owner.to_string(), }, &[], "mars-oracle-adapter", @@ -668,7 +668,7 @@ impl MockEnvBuilder { .app .instantiate_contract( contract_code_id, - Addr::unchecked("red_bank_contract_admin"), + Addr::unchecked("red_bank_contract_owner"), &RedBankInstantiateMsg { coins: self .get_allowed_coins() @@ -743,7 +743,7 @@ impl MockEnvBuilder { code_id, Addr::unchecked("swapper-instantiator"), &SwapperInstantiateMsg { - admin: self.get_admin().to_string(), + owner: self.get_owner().to_string(), }, &[], "mock-vault", @@ -833,8 +833,8 @@ impl MockEnvBuilder { self } - pub fn admin(&mut self, admin: &str) -> &mut Self { - self.admin = Some(Addr::unchecked(admin)); + pub fn owner(&mut self, owner: &str) -> &mut Self { + self.owner = Some(Addr::unchecked(owner)); self } diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index e39f0a980..d42e5ab50 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -645,7 +645,7 @@ fn test_delisted_assets_drop_max_ltv() { // Remove uosmo from the coin whitelist let res = mock.query_config(); mock.update_config( - &Addr::unchecked(res.admin.unwrap()), + &Addr::unchecked(res.owner.unwrap()), ConfigUpdates { allowed_coins: Some(vec![uatom_info.denom]), ..Default::default() diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 8f3f7613f..f38cfea46 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -8,17 +8,17 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_admin_set_on_instantiate() { - let admin = "admin_addr"; - let mock = MockEnv::new().admin(admin).build().unwrap(); +fn test_owner_set_on_instantiate() { + let owner = "owner_addr"; + let mock = MockEnv::new().owner(owner).build().unwrap(); let res = mock.query_config(); - assert_eq!(admin, res.admin.unwrap()); + assert_eq!(owner, res.owner.unwrap()); } #[test] -fn test_raises_on_invalid_admin_addr() { - let admin = "%%%INVALID%%%"; - let res = MockEnv::new().admin(admin).build(); +fn test_raises_on_invalid_owner_addr() { + let owner = "%%%INVALID%%%"; + let res = MockEnv::new().owner(owner).build(); if res.is_ok() { panic!("Should have thrown an error"); } diff --git a/contracts/credit-manager/tests/test_update_admin.rs b/contracts/credit-manager/tests/test_update_admin.rs index 0ec5ddf20..05579bc27 100644 --- a/contracts/credit-manager/tests/test_update_admin.rs +++ b/contracts/credit-manager/tests/test_update_admin.rs @@ -1,7 +1,7 @@ use cosmwasm_std::Addr; -use cw_controllers_admin_fork::AdminError::{NotAdmin, NotProposedAdmin, StateTransitionError}; -use cw_controllers_admin_fork::AdminUpdate; -use mars_rover::error::ContractError::AdminError; +use mars_owner::OwnerError::{NotOwner, NotProposedOwner, StateTransitionError}; +use mars_owner::OwnerUpdate; +use mars_rover::error::ContractError::OwnerError; use crate::helpers::{assert_err, MockEnv}; @@ -12,43 +12,43 @@ fn test_initialized_state() { let mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); - assert!(original_config.admin.is_some()); - assert!(original_config.proposed_new_admin.is_none()); + assert!(original_config.owner.is_some()); + assert!(original_config.proposed_new_owner.is_none()); } #[test] -fn test_propose_new_admin() { +fn test_propose_new_owner() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); - let new_admin = "new_admin".to_string(); + let new_owner = "new_owner".to_string(); - // only admin can propose new admins + // only owner can propose new owners let bad_guy = Addr::unchecked("bad_guy"); - let res = mock.update_admin( + let res = mock.update_owner( &bad_guy, - AdminUpdate::ProposeNewAdmin { + OwnerUpdate::ProposeNewOwner { proposed: bad_guy.to_string(), }, ); - assert_err(res, AdminError(NotAdmin {})); + assert_err(res, OwnerError(NotOwner {})); - mock.update_admin( - &Addr::unchecked(original_config.admin.clone().unwrap()), - AdminUpdate::ProposeNewAdmin { - proposed: new_admin.clone(), + mock.update_owner( + &Addr::unchecked(original_config.owner.clone().unwrap()), + OwnerUpdate::ProposeNewOwner { + proposed: new_owner.clone(), }, ) .unwrap(); let new_config = mock.query_config(); - assert_eq!(new_config.admin, original_config.admin); + assert_eq!(new_config.owner, original_config.owner); assert_ne!( - new_config.proposed_new_admin, - original_config.proposed_new_admin + new_config.proposed_new_owner, + original_config.proposed_new_owner ); - assert_eq!(new_config.proposed_new_admin, Some(new_admin)); + assert_eq!(new_config.proposed_new_owner, Some(new_owner)); } #[test] @@ -56,102 +56,102 @@ fn test_clear_proposed() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); - let new_admin = "new_admin".to_string(); + let new_owner = "new_owner".to_string(); - mock.update_admin( - &Addr::unchecked(original_config.admin.clone().unwrap()), - AdminUpdate::ProposeNewAdmin { - proposed: new_admin.clone(), + mock.update_owner( + &Addr::unchecked(original_config.owner.clone().unwrap()), + OwnerUpdate::ProposeNewOwner { + proposed: new_owner.clone(), }, ) .unwrap(); let interim_config = mock.query_config(); - assert_eq!(interim_config.proposed_new_admin, Some(new_admin)); + assert_eq!(interim_config.proposed_new_owner, Some(new_owner)); - // only admin can clear + // only owner can clear let bad_guy = Addr::unchecked("bad_guy"); - let res = mock.update_admin(&bad_guy, AdminUpdate::ClearProposed); - assert_err(res, AdminError(NotAdmin {})); + let res = mock.update_owner(&bad_guy, OwnerUpdate::ClearProposed); + assert_err(res, OwnerError(NotOwner {})); - mock.update_admin( - &Addr::unchecked(original_config.admin.clone().unwrap()), - AdminUpdate::ClearProposed, + mock.update_owner( + &Addr::unchecked(original_config.owner.clone().unwrap()), + OwnerUpdate::ClearProposed, ) .unwrap(); let latest_config = mock.query_config(); - assert_eq!(latest_config.admin, original_config.admin); + assert_eq!(latest_config.owner, original_config.owner); assert_ne!( - latest_config.proposed_new_admin, - interim_config.proposed_new_admin + latest_config.proposed_new_owner, + interim_config.proposed_new_owner ); - assert_eq!(latest_config.proposed_new_admin, None); + assert_eq!(latest_config.proposed_new_owner, None); } #[test] -fn test_accept_admin_role() { +fn test_accept_owner_role() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); - let new_admin = "new_admin".to_string(); + let new_owner = "new_owner".to_string(); - mock.update_admin( - &Addr::unchecked(original_config.admin.clone().unwrap()), - AdminUpdate::ProposeNewAdmin { - proposed: new_admin.clone(), + mock.update_owner( + &Addr::unchecked(original_config.owner.clone().unwrap()), + OwnerUpdate::ProposeNewOwner { + proposed: new_owner.clone(), }, ) .unwrap(); - // Only proposed admin can accept - let res = mock.update_admin( - &Addr::unchecked(original_config.admin.unwrap()), - AdminUpdate::AcceptProposed, + // Only proposed owner can accept + let res = mock.update_owner( + &Addr::unchecked(original_config.owner.unwrap()), + OwnerUpdate::AcceptProposed, ); - assert_err(res, AdminError(NotProposedAdmin {})); + assert_err(res, OwnerError(NotProposedOwner {})); - mock.update_admin( - &Addr::unchecked(new_admin.clone()), - AdminUpdate::AcceptProposed, + mock.update_owner( + &Addr::unchecked(new_owner.clone()), + OwnerUpdate::AcceptProposed, ) .unwrap(); let new_config = mock.query_config(); - assert_eq!(new_config.admin.unwrap(), new_admin); - assert_eq!(new_config.proposed_new_admin, None); + assert_eq!(new_config.owner.unwrap(), new_owner); + assert_eq!(new_config.proposed_new_owner, None); } #[test] -fn test_abolish_admin_role() { +fn test_abolish_owner_role() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); - // Only admin can abolish role + // Only owner can abolish role let bad_guy = Addr::unchecked("bad_guy"); - let res = mock.update_admin(&bad_guy, AdminUpdate::AbolishAdminRole); - assert_err(res, AdminError(NotAdmin {})); + let res = mock.update_owner(&bad_guy, OwnerUpdate::AbolishOwnerRole); + assert_err(res, OwnerError(NotOwner {})); - mock.update_admin( - &Addr::unchecked(original_config.admin.clone().unwrap()), - AdminUpdate::AbolishAdminRole, + mock.update_owner( + &Addr::unchecked(original_config.owner.clone().unwrap()), + OwnerUpdate::AbolishOwnerRole, ) .unwrap(); let new_config = mock.query_config(); - assert_eq!(new_config.admin, None); - assert_eq!(new_config.proposed_new_admin, None); + assert_eq!(new_config.owner, None); + assert_eq!(new_config.proposed_new_owner, None); // No new updates can occur - let res = mock.update_admin( - &Addr::unchecked(original_config.admin.clone().unwrap()), - AdminUpdate::ProposeNewAdmin { - proposed: original_config.admin.unwrap(), + let res = mock.update_owner( + &Addr::unchecked(original_config.owner.clone().unwrap()), + OwnerUpdate::ProposeNewOwner { + proposed: original_config.owner.unwrap(), }, ); - assert_err(res, AdminError(StateTransitionError {})); + assert_err(res, OwnerError(StateTransitionError {})); } diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index f17475966..eca366c94 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -18,12 +18,12 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_only_admin_can_update_config() { +fn test_only_owner_can_update_config() { let mut mock = MockEnv::new().build().unwrap(); - let new_admin = Addr::unchecked("bad_guy"); + let new_owner = Addr::unchecked("bad_guy"); let res = mock.update_config( - &new_admin, + &new_owner, ConfigUpdates { account_nft: None, allowed_coins: None, @@ -37,7 +37,7 @@ fn test_only_admin_can_update_config() { ); if res.is_ok() { - panic!("only admin should be able to update config"); + panic!("only owner should be able to update config"); } } @@ -46,7 +46,7 @@ fn test_raises_on_invalid_vaults_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( - &Addr::unchecked(original_config.admin.clone().unwrap()), + &Addr::unchecked(original_config.owner.clone().unwrap()), ConfigUpdates { account_nft: None, allowed_coins: None, @@ -75,7 +75,7 @@ fn test_raises_on_invalid_vaults_config() { ); let res = mock.update_config( - &Addr::unchecked(original_config.admin.unwrap()), + &Addr::unchecked(original_config.owner.unwrap()), ConfigUpdates { account_nft: None, allowed_coins: None, @@ -121,7 +121,7 @@ fn test_update_config_works_with_full_config() { let new_swapper = SwapperBase::new("new_swapper".to_string()); mock.update_config( - &Addr::unchecked(original_config.admin.clone().unwrap()), + &Addr::unchecked(original_config.owner.clone().unwrap()), ConfigUpdates { account_nft: Some(new_nft_contract.to_string()), allowed_coins: Some(new_allowed_coins.clone()), @@ -143,8 +143,8 @@ fn test_update_config_works_with_full_config() { assert_ne!(new_config.account_nft, original_config.account_nft); assert_eq!( - new_config.admin.unwrap(), - original_config.admin.clone().unwrap() + new_config.owner.unwrap(), + original_config.owner.clone().unwrap() ); assert_eq!( @@ -196,7 +196,7 @@ fn test_update_config_works_with_some_config() { let new_max_unlocking = Uint128::new(42); mock.update_config( - &Addr::unchecked(original_config.admin.clone().unwrap()), + &Addr::unchecked(original_config.owner.clone().unwrap()), ConfigUpdates { account_nft: Some(new_nft_contract.to_string()), max_unlocking_positions: Some(new_max_unlocking), @@ -220,10 +220,10 @@ fn test_update_config_works_with_some_config() { ); // Unchanged configs - assert_eq!(new_config.admin, original_config.admin); + assert_eq!(new_config.owner, original_config.owner); assert_eq!( - new_config.proposed_new_admin, - original_config.proposed_new_admin + new_config.proposed_new_owner, + original_config.proposed_new_owner ); assert_eq!(new_config.red_bank, original_config.red_bank); assert_eq!(new_config.oracle, original_config.oracle); @@ -256,7 +256,7 @@ fn test_update_config_removes_properly() { assert_eq!(vault_configs.len(), 1); mock.update_config( - &Addr::unchecked(mock.query_config().admin.unwrap()), + &Addr::unchecked(mock.query_config().owner.unwrap()), ConfigUpdates { allowed_coins: Some(vec![]), vault_configs: Some(vec![]), @@ -281,7 +281,7 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { let original_allowed_coins = mock.query_allowed_coins(None, None); mock.update_config( - &Addr::unchecked(original_config.admin.clone().unwrap()), + &Addr::unchecked(original_config.owner.clone().unwrap()), Default::default(), ) .unwrap(); @@ -291,7 +291,7 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { let new_queried_allowed_coins = mock.query_allowed_coins(None, None); assert_eq!(new_config.account_nft, original_config.account_nft); - assert_eq!(new_config.admin, original_config.admin); + assert_eq!(new_config.owner, original_config.owner); assert_eq!(new_queried_vault_configs, original_vault_configs); assert_eq!(new_queried_allowed_coins, original_allowed_coins); assert_eq!(new_config.red_bank, original_config.red_bank); @@ -309,7 +309,7 @@ fn test_max_close_factor_validated_on_update() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( - &Addr::unchecked(original_config.admin.unwrap()), + &Addr::unchecked(original_config.owner.unwrap()), ConfigUpdates { max_close_factor: Some(Decimal::from_atomics(42u128, 1).unwrap()), ..Default::default() @@ -329,7 +329,7 @@ fn test_raises_on_duplicate_vault_configs() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( - &Addr::unchecked(original_config.admin.unwrap()), + &Addr::unchecked(original_config.owner.unwrap()), ConfigUpdates { account_nft: None, allowed_coins: None, @@ -374,7 +374,7 @@ fn test_raises_on_duplicate_coin_configs() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( - &Addr::unchecked(original_config.admin.unwrap()), + &Addr::unchecked(original_config.owner.unwrap()), ConfigUpdates { account_nft: None, allowed_coins: Some(vec![ @@ -404,7 +404,7 @@ fn deploy_new_oracle(app: &mut BasicApp) -> OracleBase { let addr = app .instantiate_contract( contract_code_id, - Addr::unchecked("oracle_contract_admin"), + Addr::unchecked("oracle_contract_owner"), &OracleInstantiateMsg { prices: vec![ CoinPrice { diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index 03339af55..c260715a0 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -97,7 +97,7 @@ fn test_coins_out_must_be_whitelisted() { // update config to disallow denoms out let config = mock.query_config(); mock.update_config( - &Addr::unchecked(config.admin.unwrap()), + &Addr::unchecked(config.owner.unwrap()), ConfigUpdates { allowed_coins: Some(vec![lp_token.denom.clone(), atom.denom]), ..Default::default() diff --git a/contracts/oracle-adapter/Cargo.toml b/contracts/oracle-adapter/Cargo.toml index 43527bb5b..2483d8059 100644 --- a/contracts/oracle-adapter/Cargo.toml +++ b/contracts/oracle-adapter/Cargo.toml @@ -18,14 +18,14 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -cw-controllers-admin-fork = { workspace = true } -cw-storage-plus = { workspace = true } -mars-outpost = { workspace = true } -mars-rover = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw-storage-plus = { workspace = true } +mars-outpost = { workspace = true } +mars-owner = { workspace = true } +mars-rover = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/oracle-adapter/src/contract.rs b/contracts/oracle-adapter/src/contract.rs index 4958a39a1..e8ce6bb38 100644 --- a/contracts/oracle-adapter/src/contract.rs +++ b/contracts/oracle-adapter/src/contract.rs @@ -6,9 +6,9 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw_storage_plus::Bound; -use cw_controllers_admin_fork::AdminInit::SetInitialAdmin; -use cw_controllers_admin_fork::AdminUpdate; use mars_outpost::oracle::PriceResponse; +use mars_owner::OwnerInit::SetInitialOwner; +use mars_owner::OwnerUpdate; use mars_rover::adapters::vault::VaultBase; use mars_rover::adapters::Oracle; @@ -17,7 +17,7 @@ use crate::msg::{ ConfigResponse, ConfigUpdates, ExecuteMsg, InstantiateMsg, PricingMethod, QueryMsg, VaultPricingInfo, }; -use crate::state::{ADMIN, ORACLE, VAULT_PRICING_INFO}; +use crate::state::{ORACLE, OWNER, VAULT_PRICING_INFO}; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -38,7 +38,7 @@ pub fn instantiate( CONTRACT_VERSION, )?; - ADMIN.initialize(deps.storage, deps.api, SetInitialAdmin { admin: msg.admin })?; + OWNER.initialize(deps.storage, deps.api, SetInitialOwner { owner: msg.owner })?; let oracle = msg.oracle.check(deps.api)?; ORACLE.save(deps.storage, &oracle)?; @@ -59,7 +59,7 @@ pub fn execute( ) -> ContractResult { match msg { ExecuteMsg::UpdateConfig { new_config } => update_config(deps, info, new_config), - ExecuteMsg::UpdateAdmin(update) => update_admin(deps, info, update), + ExecuteMsg::UpdateOwner(update) => update_owner(deps, info, update), } } @@ -139,10 +139,10 @@ fn calculate_preview_redeem( } fn query_config(deps: Deps) -> ContractResult { - let res = ADMIN.query(deps.storage)?; + let res = OWNER.query(deps.storage)?; Ok(ConfigResponse { - admin: res.admin, - proposed_new_admin: res.proposed, + owner: res.owner, + proposed_new_owner: res.proposed, oracle: ORACLE.load(deps.storage)?, }) } @@ -152,7 +152,7 @@ pub fn update_config( info: MessageInfo, new_config: ConfigUpdates, ) -> ContractResult { - ADMIN.assert_admin(deps.storage, &info.sender)?; + OWNER.assert_owner(deps.storage, &info.sender)?; let mut response = Response::new().add_attribute("action", "rover/oracle-adapter/update_config"); @@ -186,10 +186,10 @@ pub fn update_config( Ok(response) } -pub fn update_admin( +pub fn update_owner( deps: DepsMut, info: MessageInfo, - update: AdminUpdate, + update: OwnerUpdate, ) -> ContractResult { - Ok(ADMIN.update(deps, info, update)?) + Ok(OWNER.update(deps, info, update)?) } diff --git a/contracts/oracle-adapter/src/error.rs b/contracts/oracle-adapter/src/error.rs index 145f06d73..8bc292861 100644 --- a/contracts/oracle-adapter/src/error.rs +++ b/contracts/oracle-adapter/src/error.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{CheckedFromRatioError, DecimalRangeExceeded, OverflowError, StdError}; -use cw_controllers_admin_fork::AdminError; +use mars_owner::OwnerError; use thiserror::Error; use mars_rover::error::ContractError as RoverError; @@ -9,7 +9,7 @@ pub type ContractResult = Result; #[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] - AdminError(#[from] AdminError), + OwnerError(#[from] OwnerError), #[error("{0}")] CheckedFromRatioError(#[from] CheckedFromRatioError), diff --git a/contracts/oracle-adapter/src/msg.rs b/contracts/oracle-adapter/src/msg.rs index c3de5d459..198162e27 100644 --- a/contracts/oracle-adapter/src/msg.rs +++ b/contracts/oracle-adapter/src/msg.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Decimal}; -use cw_controllers_admin_fork::AdminUpdate; +use mars_owner::OwnerUpdate; use mars_rover::adapters::{Oracle, OracleUnchecked}; @@ -14,13 +14,13 @@ pub struct CoinPrice { pub struct InstantiateMsg { pub oracle: OracleUnchecked, pub vault_pricing: Vec, - pub admin: String, + pub owner: String, } #[cw_serde] pub enum ExecuteMsg { UpdateConfig { new_config: ConfigUpdates }, - UpdateAdmin(AdminUpdate), + UpdateOwner(OwnerUpdate), } #[cw_serde] @@ -45,8 +45,8 @@ pub enum QueryMsg { #[cw_serde] pub struct ConfigResponse { - pub admin: Option, - pub proposed_new_admin: Option, + pub owner: Option, + pub proposed_new_owner: Option, pub oracle: Oracle, } diff --git a/contracts/oracle-adapter/src/state.rs b/contracts/oracle-adapter/src/state.rs index 42ef50d08..1901a99b8 100644 --- a/contracts/oracle-adapter/src/state.rs +++ b/contracts/oracle-adapter/src/state.rs @@ -1,10 +1,10 @@ -use cw_controllers_admin_fork::Admin; use cw_storage_plus::{Item, Map}; +use mars_owner::Owner; use mars_rover::adapters::Oracle; use crate::msg::VaultPricingInfo; -pub const ADMIN: Admin = Admin::new("admin"); +pub const OWNER: Owner = Owner::new("owner"); pub const ORACLE: Item = Item::new("oracle"); /// Map<(Vault Token Denom, Pricing Method)> diff --git a/contracts/oracle-adapter/tests/helpers.rs b/contracts/oracle-adapter/tests/helpers.rs index adc4778db..39273ba92 100644 --- a/contracts/oracle-adapter/tests/helpers.rs +++ b/contracts/oracle-adapter/tests/helpers.rs @@ -35,14 +35,14 @@ pub fn instantiate_oracle_adapter(app: &mut BasicApp) -> Addr { let vault_pricing_info = deploy_vault(app, oracle.clone().into(), mock_vault_info()); starting_vault_deposit(app, &vault_pricing_info); - let admin = Addr::unchecked("admin"); + let owner = Addr::unchecked("owner"); app.instantiate_contract( code_id, - admin.clone(), + owner.clone(), &InstantiateMsg { oracle: oracle.into(), vault_pricing: vec![vault_pricing_info], - admin: admin.to_string(), + owner: owner.to_string(), }, &[], "mars-oracle-adapter", @@ -127,7 +127,7 @@ fn deploy_oracle(app: &mut BasicApp) -> OracleBase { let addr = app .instantiate_contract( code_id, - Addr::unchecked("oracle_contract_admin"), + Addr::unchecked("oracle_contract_owner"), &OracleInstantiateMsg { prices: vec![ CoinPrice { diff --git a/contracts/oracle-adapter/tests/test_update_admin.rs b/contracts/oracle-adapter/tests/test_update_admin.rs index 92c264c89..1f19710a8 100644 --- a/contracts/oracle-adapter/tests/test_update_admin.rs +++ b/contracts/oracle-adapter/tests/test_update_admin.rs @@ -1,6 +1,6 @@ use cosmwasm_std::Addr; -use cw_controllers_admin_fork::AdminUpdate; use cw_multi_test::{App, Executor}; +use mars_owner::OwnerUpdate; use mars_oracle_adapter::msg::{ConfigResponse, ExecuteMsg, QueryMsg}; @@ -17,12 +17,12 @@ fn test_initialized_state() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - assert!(original_config.admin.is_some()); - assert!(original_config.proposed_new_admin.is_none()); + assert!(original_config.owner.is_some()); + assert!(original_config.proposed_new_owner.is_none()); } #[test] -fn test_propose_new_admin() { +fn test_propose_new_owner() { let mut app = App::default(); let contract_addr = instantiate_oracle_adapter(&mut app); let original_config: ConfigResponse = app @@ -30,14 +30,14 @@ fn test_propose_new_admin() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - let new_admin = "new_admin".to_string(); + let new_owner = "new_owner".to_string(); - // only admin can propose new admins + // only owner can propose new owners let bad_guy = Addr::unchecked("bad_guy"); app.execute_contract( bad_guy.clone(), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminUpdate::ProposeNewAdmin { + &ExecuteMsg::UpdateOwner(OwnerUpdate::ProposeNewOwner { proposed: bad_guy.to_string(), }), &[], @@ -45,10 +45,10 @@ fn test_propose_new_admin() { .unwrap_err(); app.execute_contract( - Addr::unchecked(original_config.admin.clone().unwrap()), + Addr::unchecked(original_config.owner.clone().unwrap()), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminUpdate::ProposeNewAdmin { - proposed: new_admin.clone(), + &ExecuteMsg::UpdateOwner(OwnerUpdate::ProposeNewOwner { + proposed: new_owner.clone(), }), &[], ) @@ -59,12 +59,12 @@ fn test_propose_new_admin() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - assert_eq!(new_config.admin, original_config.admin); + assert_eq!(new_config.owner, original_config.owner); assert_ne!( - new_config.proposed_new_admin, - original_config.proposed_new_admin + new_config.proposed_new_owner, + original_config.proposed_new_owner ); - assert_eq!(new_config.proposed_new_admin, Some(new_admin)); + assert_eq!(new_config.proposed_new_owner, Some(new_owner)); } #[test] @@ -76,13 +76,13 @@ fn test_clear_proposed() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - let new_admin = "new_admin".to_string(); + let new_owner = "new_owner".to_string(); app.execute_contract( - Addr::unchecked(original_config.admin.clone().unwrap()), + Addr::unchecked(original_config.owner.clone().unwrap()), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminUpdate::ProposeNewAdmin { - proposed: new_admin.clone(), + &ExecuteMsg::UpdateOwner(OwnerUpdate::ProposeNewOwner { + proposed: new_owner.clone(), }), &[], ) @@ -93,22 +93,22 @@ fn test_clear_proposed() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - assert_eq!(interim_config.proposed_new_admin, Some(new_admin)); + assert_eq!(interim_config.proposed_new_owner, Some(new_owner)); - // only admin can clear + // only owner can clear let bad_guy = Addr::unchecked("bad_guy"); app.execute_contract( bad_guy, contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminUpdate::ClearProposed), + &ExecuteMsg::UpdateOwner(OwnerUpdate::ClearProposed), &[], ) .unwrap_err(); app.execute_contract( - Addr::unchecked(original_config.admin.clone().unwrap()), + Addr::unchecked(original_config.owner.clone().unwrap()), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminUpdate::ClearProposed), + &ExecuteMsg::UpdateOwner(OwnerUpdate::ClearProposed), &[], ) .unwrap(); @@ -118,16 +118,16 @@ fn test_clear_proposed() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - assert_eq!(latest_config.admin, original_config.admin); + assert_eq!(latest_config.owner, original_config.owner); assert_ne!( - latest_config.proposed_new_admin, - interim_config.proposed_new_admin + latest_config.proposed_new_owner, + interim_config.proposed_new_owner ); - assert_eq!(latest_config.proposed_new_admin, None); + assert_eq!(latest_config.proposed_new_owner, None); } #[test] -fn test_accept_admin_role() { +fn test_accept_owner_role() { let mut app = App::default(); let contract_addr = instantiate_oracle_adapter(&mut app); let original_config: ConfigResponse = app @@ -135,31 +135,31 @@ fn test_accept_admin_role() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - let new_admin = "new_admin".to_string(); + let new_owner = "new_owner".to_string(); app.execute_contract( - Addr::unchecked(original_config.admin.clone().unwrap()), + Addr::unchecked(original_config.owner.clone().unwrap()), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminUpdate::ProposeNewAdmin { - proposed: new_admin.clone(), + &ExecuteMsg::UpdateOwner(OwnerUpdate::ProposeNewOwner { + proposed: new_owner.clone(), }), &[], ) .unwrap(); - // Only proposed admin can accept + // Only proposed owner can accept app.execute_contract( - Addr::unchecked(original_config.admin.unwrap()), + Addr::unchecked(original_config.owner.unwrap()), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminUpdate::AcceptProposed), + &ExecuteMsg::UpdateOwner(OwnerUpdate::AcceptProposed), &[], ) .unwrap_err(); app.execute_contract( - Addr::unchecked(new_admin.clone()), + Addr::unchecked(new_owner.clone()), contract_addr.clone(), - &ExecuteMsg::UpdateAdmin(AdminUpdate::AcceptProposed), + &ExecuteMsg::UpdateOwner(OwnerUpdate::AcceptProposed), &[], ) .unwrap(); @@ -169,6 +169,6 @@ fn test_accept_admin_role() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - assert_eq!(new_config.admin.unwrap(), new_admin); - assert_eq!(new_config.proposed_new_admin, None); + assert_eq!(new_config.owner.unwrap(), new_owner); + assert_eq!(new_config.proposed_new_owner, None); } diff --git a/contracts/oracle-adapter/tests/test_update_config.rs b/contracts/oracle-adapter/tests/test_update_config.rs index dd2f82e35..1c91b54cb 100644 --- a/contracts/oracle-adapter/tests/test_update_config.rs +++ b/contracts/oracle-adapter/tests/test_update_config.rs @@ -1,8 +1,8 @@ use cosmwasm_std::Addr; -use cw_controllers_admin_fork::AdminError::NotAdmin; use cw_multi_test::{App, Executor}; +use mars_owner::OwnerError::NotOwner; -use mars_oracle_adapter::error::ContractError::AdminError; +use mars_oracle_adapter::error::ContractError::OwnerError; use mars_oracle_adapter::msg::{ ConfigResponse, ConfigUpdates, ExecuteMsg, QueryMsg, VaultPricingInfo, }; @@ -13,7 +13,7 @@ use crate::helpers::{assert_err, instantiate_oracle_adapter}; pub mod helpers; #[test] -fn test_only_admin_can_update_config() { +fn test_only_owner_can_update_config() { let mut app = App::default(); let contract_addr = instantiate_oracle_adapter(&mut app); @@ -27,7 +27,7 @@ fn test_only_admin_can_update_config() { &[], ); - assert_err(res, AdminError(NotAdmin {})); + assert_err(res, OwnerError(NotOwner {})); } #[test] @@ -43,7 +43,7 @@ fn test_update_config_works_with_full_config() { let new_vault_pricing = vec![]; app.execute_contract( - Addr::unchecked(original_config.admin.clone().unwrap()), + Addr::unchecked(original_config.owner.clone().unwrap()), contract_addr.clone(), &ExecuteMsg::UpdateConfig { new_config: ConfigUpdates { @@ -60,10 +60,10 @@ fn test_update_config_works_with_full_config() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - assert_eq!(new_config.admin, original_config.admin); + assert_eq!(new_config.owner, original_config.owner); assert_eq!( - new_config.proposed_new_admin, - original_config.proposed_new_admin + new_config.proposed_new_owner, + original_config.proposed_new_owner ); assert_ne!(new_config.oracle, original_config.oracle); @@ -107,7 +107,7 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { .unwrap(); app.execute_contract( - Addr::unchecked(original_config.admin.clone().unwrap()), + Addr::unchecked(original_config.owner.clone().unwrap()), contract_addr.clone(), &ExecuteMsg::UpdateConfig { new_config: ConfigUpdates { @@ -124,10 +124,10 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) .unwrap(); - assert_eq!(new_config.admin, original_config.admin); + assert_eq!(new_config.owner, original_config.owner); assert_eq!( - new_config.proposed_new_admin, - original_config.proposed_new_admin + new_config.proposed_new_owner, + original_config.proposed_new_owner ); assert_eq!(new_config.oracle, original_config.oracle); diff --git a/contracts/swapper/base/Cargo.toml b/contracts/swapper/base/Cargo.toml index f4756dbbe..4e4b587f9 100644 --- a/contracts/swapper/base/Cargo.toml +++ b/contracts/swapper/base/Cargo.toml @@ -18,11 +18,11 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-controllers-admin-fork = { workspace = true } -cw-storage-plus = { workspace = true } -mars-rover = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +mars-rover = { workspace = true } +mars-owner = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/swapper/base/src/contract.rs b/contracts/swapper/base/src/contract.rs index d13ac5870..780e80fa9 100644 --- a/contracts/swapper/base/src/contract.rs +++ b/contracts/swapper/base/src/contract.rs @@ -5,9 +5,9 @@ use cosmwasm_std::{ DepsMut, Env, MessageInfo, Order, Response, WasmMsg, }; use cw_storage_plus::{Bound, Map}; +use mars_owner::OwnerInit::SetInitialOwner; +use mars_owner::{Owner, OwnerUpdate}; -use cw_controllers_admin_fork::AdminInit::SetInitialAdmin; -use cw_controllers_admin_fork::{Admin, AdminUpdate}; use mars_rover::adapters::swap::{ EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg, RouteResponse, RoutesResponse, @@ -25,8 +25,8 @@ where M: CustomMsg, R: Route, { - /// The contract's admin who has special rights to update contract - pub admin: Admin<'a>, + /// The contract's owner who has special rights to update contract + pub owner: Owner<'a>, /// The trade route for each pair of input/output assets pub routes: Map<'a, (String, String), R>, /// Phantom data holds generics @@ -42,7 +42,7 @@ where { fn default() -> Self { Self { - admin: Admin::new("admin"), + owner: Owner::new("owner"), routes: Map::new("routes"), custom_query: PhantomData, custom_message: PhantomData, @@ -61,8 +61,8 @@ where deps: DepsMut, msg: InstantiateMsg, ) -> ContractResult> { - self.admin - .initialize(deps.storage, deps.api, SetInitialAdmin { admin: msg.admin })?; + self.owner + .initialize(deps.storage, deps.api, SetInitialOwner { owner: msg.owner })?; Ok(Response::default()) } @@ -74,7 +74,7 @@ where msg: ExecuteMsg, ) -> ContractResult> { match msg { - ExecuteMsg::UpdateAdmin(update) => self.update_admin(deps, info, update), + ExecuteMsg::UpdateOwner(update) => self.update_owner(deps, info, update), ExecuteMsg::SetRoute { denom_in, denom_out, @@ -95,7 +95,7 @@ where pub fn query(&self, deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { let res = match msg { - QueryMsg::Admin {} => to_binary(&self.admin.query(deps.storage)?), + QueryMsg::Owner {} => to_binary(&self.owner.query(deps.storage)?), QueryMsg::EstimateExactInSwap { coin_in, denom_out } => { to_binary(&self.estimate_exact_in_swap(deps, env, coin_in, denom_out)?) } @@ -241,7 +241,7 @@ where denom_out: String, route: R, ) -> ContractResult> { - self.admin.assert_admin(deps.storage, &sender)?; + self.owner.assert_owner(deps.storage, &sender)?; route.validate(&deps.querier, &denom_in, &denom_out)?; @@ -255,12 +255,12 @@ where .add_attribute("route", route.to_string())) } - fn update_admin( + fn update_owner( &self, deps: DepsMut, info: MessageInfo, - update: AdminUpdate, + update: OwnerUpdate, ) -> ContractResult> { - Ok(self.admin.update(deps, info, update)?) + Ok(self.owner.update(deps, info, update)?) } } diff --git a/contracts/swapper/base/src/error.rs b/contracts/swapper/base/src/error.rs index 5c7ad584c..a9e911820 100644 --- a/contracts/swapper/base/src/error.rs +++ b/contracts/swapper/base/src/error.rs @@ -1,12 +1,12 @@ use cosmwasm_std::{CheckedMultiplyRatioError, DecimalRangeExceeded, OverflowError, StdError}; -use cw_controllers_admin_fork::AdminError; +use mars_owner::OwnerError; use mars_rover::error::ContractError as RoverError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] - AdminError(#[from] AdminError), + OwnerError(#[from] OwnerError), #[error("{0}")] DecimalRangeExceeded(#[from] DecimalRangeExceeded), diff --git a/contracts/swapper/mock/src/contract.rs b/contracts/swapper/mock/src/contract.rs index 3b93f3c59..d0adc1619 100644 --- a/contracts/swapper/mock/src/contract.rs +++ b/contracts/swapper/mock/src/contract.rs @@ -27,7 +27,7 @@ pub fn execute( msg: ExecuteMsg, ) -> StdResult { match msg { - ExecuteMsg::UpdateAdmin(_) => unimplemented!("not implemented"), + ExecuteMsg::UpdateOwner(_) => unimplemented!("not implemented"), ExecuteMsg::SetRoute { .. } => unimplemented!("not implemented"), ExecuteMsg::TransferResult { .. } => unimplemented!("not implemented"), ExecuteMsg::SwapExactIn { @@ -41,7 +41,7 @@ pub fn execute( #[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::Admin { .. } => unimplemented!("not implemented"), + QueryMsg::Owner { .. } => unimplemented!("not implemented"), QueryMsg::Route { .. } => unimplemented!("not implemented"), QueryMsg::Routes { .. } => unimplemented!("not implemented"), QueryMsg::EstimateExactInSwap { .. } => to_binary(&estimate_exact_in_swap()), diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index 7b2bdb875..e114cb83c 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -18,17 +18,17 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -cw-controllers-admin-fork = { workspace = true } -cw-storage-plus = { workspace = true } -mars-osmosis = { workspace = true } -mars-swapper-base = { workspace = true } -mars-rover = { workspace = true } -osmosis-std = { workspace = true } -schemars = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw-storage-plus = { workspace = true } +mars-osmosis = { workspace = true } +mars-owner = { workspace = true } +mars-swapper-base = { workspace = true } +mars-rover = { workspace = true } +osmosis-std = { workspace = true } +schemars = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/swapper/osmosis/tests/helpers.rs b/contracts/swapper/osmosis/tests/helpers.rs index 3f8b999d8..150a95b9e 100644 --- a/contracts/swapper/osmosis/tests/helpers.rs +++ b/contracts/swapper/osmosis/tests/helpers.rs @@ -21,10 +21,10 @@ pub fn wasm_file() -> String { format!("../../../{}/{}.wasm", artifacts_dir, snaked_name) } -pub fn instantiate_contract(wasm: &Wasm, admin: &SigningAccount) -> String { +pub fn instantiate_contract(wasm: &Wasm, owner: &SigningAccount) -> String { let wasm_byte_code = std::fs::read(wasm_file()).unwrap(); let code_id = wasm - .store_code(&wasm_byte_code, None, admin) + .store_code(&wasm_byte_code, None, owner) .unwrap() .data .code_id; @@ -32,12 +32,12 @@ pub fn instantiate_contract(wasm: &Wasm, admin: &SigningAccount) wasm.instantiate( code_id, &InstantiateMsg { - admin: admin.address(), + owner: owner.address(), }, None, Some("swapper-osmosis-contract"), &[], - admin, + owner, ) .unwrap() .data diff --git a/contracts/swapper/osmosis/tests/test_estimate.rs b/contracts/swapper/osmosis/tests/test_estimate.rs index 25509245d..8d65af0a2 100644 --- a/contracts/swapper/osmosis/tests/test_estimate.rs +++ b/contracts/swapper/osmosis/tests/test_estimate.rs @@ -15,11 +15,11 @@ pub mod helpers; fn test_error_on_route_not_found() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let admin = app + let owner = app .init_account(&[coin(1_000_000_000_000, "uosmo")]) .unwrap(); - let contract_addr = instantiate_contract(&wasm, &admin); + let contract_addr = instantiate_contract(&wasm, &owner); let res: RunnerResult = wasm.query( &contract_addr, diff --git a/contracts/swapper/osmosis/tests/test_instantiate.rs b/contracts/swapper/osmosis/tests/test_instantiate.rs index 6f059c0b5..b01f34341 100644 --- a/contracts/swapper/osmosis/tests/test_instantiate.rs +++ b/contracts/swapper/osmosis/tests/test_instantiate.rs @@ -1,5 +1,5 @@ use cosmwasm_std::coin; -use cw_controllers_admin_fork::AdminResponse; +use mars_owner::OwnerResponse; use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; use mars_rover::adapters::swap::{InstantiateMsg, QueryMsg}; @@ -9,7 +9,7 @@ use crate::helpers::{instantiate_contract, wasm_file}; pub mod helpers; #[test] -fn test_admin_set_on_instantiate() { +fn test_owner_set_on_instantiate() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let signer = app @@ -18,12 +18,12 @@ fn test_admin_set_on_instantiate() { let contract_addr = instantiate_contract(&wasm, &signer); - let res: AdminResponse = wasm.query(&contract_addr, &QueryMsg::Admin {}).unwrap(); - assert_eq!(res.admin, Some(signer.address())); + let res: OwnerResponse = wasm.query(&contract_addr, &QueryMsg::Owner {}).unwrap(); + assert_eq!(res.owner, Some(signer.address())); } #[test] -fn test_raises_on_invalid_admin_addr() { +fn test_raises_on_invalid_owner_addr() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let signer = app @@ -37,11 +37,11 @@ fn test_raises_on_invalid_admin_addr() { .data .code_id; - let admin = "%%%INVALID%%%"; + let owner = "%%%INVALID%%%"; let res = wasm.instantiate( code_id, &InstantiateMsg { - admin: admin.to_string(), + owner: owner.to_string(), }, None, Some("swapper-osmosis-contract"), diff --git a/contracts/swapper/osmosis/tests/test_set_route.rs b/contracts/swapper/osmosis/tests/test_set_route.rs index ba55eaf18..231f211af 100644 --- a/contracts/swapper/osmosis/tests/test_set_route.rs +++ b/contracts/swapper/osmosis/tests/test_set_route.rs @@ -1,6 +1,6 @@ use cosmwasm_std::coin; use cosmwasm_std::StdError::GenericErr; -use cw_controllers_admin_fork::AdminError; +use mars_owner::OwnerError; use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; use osmosis_testing::{Gamm, Module, OsmosisTestApp, Wasm}; @@ -13,17 +13,17 @@ use crate::helpers::{assert_err, instantiate_contract}; pub mod helpers; #[test] -fn test_only_admin_can_set_routes() { +fn test_only_owner_can_set_routes() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let accs = app .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) .unwrap(); - let admin = &accs[0]; + let owner = &accs[0]; let bad_guy = &accs[1]; - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); let res_err = wasm .execute( @@ -47,7 +47,7 @@ fn test_only_admin_can_set_routes() { ) .unwrap_err(); - assert_err(res_err, AdminError::NotAdmin {}); + assert_err(res_err, OwnerError::NotOwner {}); } #[test] diff --git a/contracts/swapper/osmosis/tests/test_swap.rs b/contracts/swapper/osmosis/tests/test_swap.rs index c7396ca42..7bc49aa0e 100644 --- a/contracts/swapper/osmosis/tests/test_swap.rs +++ b/contracts/swapper/osmosis/tests/test_swap.rs @@ -21,10 +21,10 @@ fn test_transfer_callback_only_internal() { let accs = app .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) .unwrap(); - let admin = &accs[0]; + let owner = &accs[0]; let bad_guy = &accs[1]; - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); let res_err = wasm .execute( diff --git a/contracts/swapper/osmosis/tests/test_update_admin.rs b/contracts/swapper/osmosis/tests/test_update_admin.rs index 356a5e354..04b314837 100644 --- a/contracts/swapper/osmosis/tests/test_update_admin.rs +++ b/contracts/swapper/osmosis/tests/test_update_admin.rs @@ -1,7 +1,7 @@ use cosmwasm_std::coin; use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; -use cw_controllers_admin_fork::{AdminResponse, AdminUpdate}; +use mars_owner::{OwnerResponse, OwnerUpdate}; use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg}; use mars_swapper_osmosis::route::OsmosisRoute; @@ -17,31 +17,31 @@ fn test_initial_state() { let accs = app .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) .unwrap(); - let admin = &accs[0]; + let owner = &accs[0]; - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); - let res: AdminResponse = wasm.query(&contract_addr, &QueryMsg::Admin {}).unwrap(); - assert_eq!(res.admin.unwrap(), admin.address()); + let res: OwnerResponse = wasm.query(&contract_addr, &QueryMsg::Owner {}).unwrap(); + assert_eq!(res.owner.unwrap(), owner.address()); assert_eq!(res.proposed, None); } #[test] -fn test_only_admin_can_propose() { +fn test_only_owner_can_propose() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let accs = app .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 3) .unwrap(); - let admin = &accs[0]; + let owner = &accs[0]; let bad_guy = &accs[1]; - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); wasm.execute( &contract_addr, - &ExecuteMsg::::UpdateAdmin(AdminUpdate::ProposeNewAdmin { + &ExecuteMsg::::UpdateOwner(OwnerUpdate::ProposeNewOwner { proposed: bad_guy.address(), }), &[], @@ -51,60 +51,60 @@ fn test_only_admin_can_propose() { } #[test] -fn test_propose_new_admin() { +fn test_propose_new_owner() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let accs = app .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) .unwrap(); - let admin = &accs[0]; - let new_admin = &accs[1]; + let owner = &accs[0]; + let new_owner = &accs[1]; - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); wasm.execute( &contract_addr, - &ExecuteMsg::::UpdateAdmin(AdminUpdate::ProposeNewAdmin { - proposed: new_admin.address(), + &ExecuteMsg::::UpdateOwner(OwnerUpdate::ProposeNewOwner { + proposed: new_owner.address(), }), &[], - admin, + owner, ) .unwrap(); - let res: AdminResponse = wasm.query(&contract_addr, &QueryMsg::Admin {}).unwrap(); - assert_eq!(res.admin.unwrap(), admin.address()); - assert_eq!(res.proposed.unwrap(), new_admin.address()); + let res: OwnerResponse = wasm.query(&contract_addr, &QueryMsg::Owner {}).unwrap(); + assert_eq!(res.owner.unwrap(), owner.address()); + assert_eq!(res.proposed.unwrap(), new_owner.address()); } #[test] -fn test_only_admin_can_clear_proposed() { +fn test_only_owner_can_clear_proposed() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let accs = app .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 3) .unwrap(); - let admin = &accs[0]; + let owner = &accs[0]; let bad_guy = &accs[1]; - let new_admin = &accs[2]; + let new_owner = &accs[2]; - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); wasm.execute( &contract_addr, - &ExecuteMsg::::UpdateAdmin(AdminUpdate::ProposeNewAdmin { - proposed: new_admin.address(), + &ExecuteMsg::::UpdateOwner(OwnerUpdate::ProposeNewOwner { + proposed: new_owner.address(), }), &[], - admin, + owner, ) .unwrap(); wasm.execute( &contract_addr, - &ExecuteMsg::::UpdateAdmin(AdminUpdate::ClearProposed), + &ExecuteMsg::::UpdateOwner(OwnerUpdate::ClearProposed), &[], bad_guy, ) @@ -119,98 +119,98 @@ fn test_clear_proposed() { let accs = app .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) .unwrap(); - let admin = &accs[0]; - let new_admin = &accs[1]; + let owner = &accs[0]; + let new_owner = &accs[1]; - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); wasm.execute( &contract_addr, - &ExecuteMsg::::UpdateAdmin(AdminUpdate::ProposeNewAdmin { - proposed: new_admin.address(), + &ExecuteMsg::::UpdateOwner(OwnerUpdate::ProposeNewOwner { + proposed: new_owner.address(), }), &[], - admin, + owner, ) .unwrap(); wasm.execute( &contract_addr, - &ExecuteMsg::::UpdateAdmin(AdminUpdate::ClearProposed), + &ExecuteMsg::::UpdateOwner(OwnerUpdate::ClearProposed), &[], - admin, + owner, ) .unwrap(); - let res: AdminResponse = wasm.query(&contract_addr, &QueryMsg::Admin {}).unwrap(); - assert_eq!(res.admin.unwrap(), admin.address()); + let res: OwnerResponse = wasm.query(&contract_addr, &QueryMsg::Owner {}).unwrap(); + assert_eq!(res.owner.unwrap(), owner.address()); assert_eq!(res.proposed, None); } #[test] -fn test_only_proposed_admin_can_accept_role() { +fn test_only_proposed_owner_can_accept_role() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let accs = app .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) .unwrap(); - let admin = &accs[0]; - let new_admin = &accs[1]; + let owner = &accs[0]; + let new_owner = &accs[1]; - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); wasm.execute( &contract_addr, - &ExecuteMsg::::UpdateAdmin(AdminUpdate::ProposeNewAdmin { - proposed: new_admin.address(), + &ExecuteMsg::::UpdateOwner(OwnerUpdate::ProposeNewOwner { + proposed: new_owner.address(), }), &[], - admin, + owner, ) .unwrap(); wasm.execute( &contract_addr, - &ExecuteMsg::::UpdateAdmin(AdminUpdate::AcceptProposed), + &ExecuteMsg::::UpdateOwner(OwnerUpdate::AcceptProposed), &[], - admin, + owner, ) .unwrap_err(); } #[test] -fn test_accept_admin_role() { +fn test_accept_owner_role() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let accs = app .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) .unwrap(); - let admin = &accs[0]; - let new_admin = &accs[1]; + let owner = &accs[0]; + let new_owner = &accs[1]; - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); wasm.execute( &contract_addr, - &ExecuteMsg::::UpdateAdmin(AdminUpdate::ProposeNewAdmin { - proposed: new_admin.address(), + &ExecuteMsg::::UpdateOwner(OwnerUpdate::ProposeNewOwner { + proposed: new_owner.address(), }), &[], - admin, + owner, ) .unwrap(); wasm.execute( &contract_addr, - &ExecuteMsg::::UpdateAdmin(AdminUpdate::AcceptProposed), + &ExecuteMsg::::UpdateOwner(OwnerUpdate::AcceptProposed), &[], - new_admin, + new_owner, ) .unwrap(); - let res: AdminResponse = wasm.query(&contract_addr, &QueryMsg::Admin {}).unwrap(); - assert_eq!(res.admin.unwrap(), new_admin.address()); + let res: OwnerResponse = wasm.query(&contract_addr, &QueryMsg::Owner {}).unwrap(); + assert_eq!(res.owner.unwrap(), new_owner.address()); assert_eq!(res.proposed, None); } diff --git a/contracts/zapper/osmosis/tests/helpers.rs b/contracts/zapper/osmosis/tests/helpers.rs index 9c905fd2a..ae077f1bd 100644 --- a/contracts/zapper/osmosis/tests/helpers.rs +++ b/contracts/zapper/osmosis/tests/helpers.rs @@ -15,11 +15,11 @@ pub fn wasm_file() -> String { format!("../../../{}/{}.wasm", artifacts_dir, snaked_name) } -pub fn instantiate_contract(wasm: &Wasm, admin: &SigningAccount) -> String { +pub fn instantiate_contract(wasm: &Wasm, owner: &SigningAccount) -> String { println!("WASM name: {}", wasm_file()); let wasm_byte_code = std::fs::read(wasm_file()).unwrap(); let code_id = wasm - .store_code(&wasm_byte_code, None, admin) + .store_code(&wasm_byte_code, None, owner) .unwrap() .data .code_id; @@ -30,7 +30,7 @@ pub fn instantiate_contract(wasm: &Wasm, admin: &SigningAccount) None, Some("zapper-osmosis-contract"), &[], - admin, + owner, ) .unwrap() .data diff --git a/contracts/zapper/osmosis/tests/test_callback.rs b/contracts/zapper/osmosis/tests/test_callback.rs index 45c3c3ea4..5fc8fe283 100644 --- a/contracts/zapper/osmosis/tests/test_callback.rs +++ b/contracts/zapper/osmosis/tests/test_callback.rs @@ -21,10 +21,10 @@ fn test_only_contract_itself_can_callback() { 2, ) .unwrap(); - let admin = &accs[0]; + let owner = &accs[0]; let user = &accs[1]; - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); let res_err = wasm .execute( diff --git a/contracts/zapper/osmosis/tests/test_provide_liquidity.rs b/contracts/zapper/osmosis/tests/test_provide_liquidity.rs index d61a06477..328328858 100644 --- a/contracts/zapper/osmosis/tests/test_provide_liquidity.rs +++ b/contracts/zapper/osmosis/tests/test_provide_liquidity.rs @@ -89,21 +89,21 @@ fn test_provide_liquidity_with_min_not_received() { 2, ) .unwrap(); - let admin = &accs[0]; + let owner = &accs[0]; let user = &accs[1]; let gamm = Gamm::new(&app); let pool_id = gamm .create_basic_pool( &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], - admin, + owner, ) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{}", pool_id); - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); let bank = Bank::new(&app); @@ -162,21 +162,21 @@ fn test_provide_liquidity_with_one_coin() { 2, ) .unwrap(); - let admin = &accs[0]; + let owner = &accs[0]; let user = &accs[1]; let gamm = Gamm::new(&app); let pool_id = gamm .create_basic_pool( &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], - admin, + owner, ) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{}", pool_id); - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); let bank = Bank::new(&app); @@ -245,21 +245,21 @@ fn test_provide_liquidity_with_two_balanced_coins() { 2, ) .unwrap(); - let admin = &accs[0]; + let owner = &accs[0]; let user = &accs[1]; let gamm = Gamm::new(&app); let pool_id = gamm .create_basic_pool( &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], - admin, + owner, ) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{}", pool_id); - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); let bank = Bank::new(&app); @@ -335,21 +335,21 @@ fn test_provide_liquidity_with_two_unbalanced_coins() { 2, ) .unwrap(); - let admin = &accs[0]; + let owner = &accs[0]; let user = &accs[1]; let gamm = Gamm::new(&app); let pool_id = gamm .create_basic_pool( &[coin(20_000_000_000, "uatom"), coin(40_000_000_000, "uosmo")], - admin, + owner, ) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{}", pool_id); - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); let bank = Bank::new(&app); @@ -443,7 +443,7 @@ fn test_provide_liquidity_with_different_recipient() { 3, ) .unwrap(); - let admin = &accs[0]; + let owner = &accs[0]; let user = &accs[1]; let recipient = &accs[2]; @@ -451,14 +451,14 @@ fn test_provide_liquidity_with_different_recipient() { let pool_id = gamm .create_basic_pool( &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], - admin, + owner, ) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{}", pool_id); - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); let bank = Bank::new(&app); diff --git a/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs b/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs index d6702138a..f94017a7e 100644 --- a/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs +++ b/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs @@ -103,21 +103,21 @@ fn test_withdraw_liquidity_successfully() { 2, ) .unwrap(); - let admin = &accs[0]; + let owner = &accs[0]; let user = &accs[1]; let gamm = Gamm::new(&app); let pool_id = gamm .create_basic_pool( &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], - admin, + owner, ) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{}", pool_id); - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); let bank = Bank::new(&app); @@ -214,7 +214,7 @@ fn test_withdraw_liquidity_with_different_recipient_successfully() { 3, ) .unwrap(); - let admin = &accs[0]; + let owner = &accs[0]; let user = &accs[1]; let recipient = &accs[2]; @@ -222,14 +222,14 @@ fn test_withdraw_liquidity_with_different_recipient_successfully() { let pool_id = gamm .create_basic_pool( &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], - admin, + owner, ) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{}", pool_id); - let contract_addr = instantiate_contract(&wasm, admin); + let contract_addr = instantiate_contract(&wasm, owner); let bank = Bank::new(&app); diff --git a/packages/controllers/Cargo.toml b/packages/controllers/Cargo.toml deleted file mode 100644 index 01b68fde1..000000000 --- a/packages/controllers/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "cw-controllers-admin-fork" -version = { workspace = true } -authors = { workspace = true } -edition = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -documentation = { workspace = true } -keywords = { workspace = true } - -[dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-utils = { workspace = true } -cw-storage-plus = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } diff --git a/packages/controllers/src/admin.rs b/packages/controllers/src/admin.rs deleted file mode 100644 index 01efdbb0b..000000000 --- a/packages/controllers/src/admin.rs +++ /dev/null @@ -1,972 +0,0 @@ -use std::fmt::Debug; - -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{ - Addr, Api, CustomQuery, DepsMut, MessageInfo, Response, StdError, StdResult, Storage, -}; -use cw_storage_plus::Item; -use schemars::JsonSchema; -use thiserror::Error; - -/// Returned from Admin.query() -#[cw_serde] -pub struct AdminResponse { - pub admin: Option, - pub proposed: Option, -} - -/// Errors returned from Admin state transitions -#[derive(Error, Debug, PartialEq)] -pub enum AdminError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Caller is not admin")] - NotAdmin {}, - - #[error("Caller is not the proposed admin")] - NotProposedAdmin {}, - - #[error("Admin state transition was not valid")] - StateTransitionError {}, -} - -type AdminResult = Result; - -/// The finite states that are possible -#[cw_serde] -enum AdminState { - A(AdminUninitialized), - B(AdminSetNoneProposed), - C(AdminSetWithProposed), - D(AdminRoleAbolished), -} - -#[cw_serde] -struct AdminUninitialized; - -impl AdminUninitialized { - pub fn initialize(&self, admin: &Addr) -> AdminState { - AdminState::B(AdminSetNoneProposed { - admin: admin.clone(), - }) - } - - pub fn abolish_admin_role(&self) -> AdminState { - AdminState::D(AdminRoleAbolished) - } -} - -#[cw_serde] -struct AdminSetNoneProposed { - admin: Addr, -} - -impl AdminSetNoneProposed { - pub fn propose(self, proposed: &Addr) -> AdminState { - AdminState::C(AdminSetWithProposed { - admin: self.admin, - proposed: proposed.clone(), - }) - } - - pub fn abolish_admin_role(self) -> AdminState { - AdminState::D(AdminRoleAbolished) - } -} - -#[cw_serde] -struct AdminSetWithProposed { - admin: Addr, - proposed: Addr, -} - -impl AdminSetWithProposed { - pub fn clear_proposed(self) -> AdminState { - AdminState::B(AdminSetNoneProposed { admin: self.admin }) - } - - pub fn accept_proposed(self) -> AdminState { - AdminState::B(AdminSetNoneProposed { - admin: self.proposed, - }) - } - - pub fn abolish_admin_role(self) -> AdminState { - AdminState::D(AdminRoleAbolished) - } -} - -#[cw_serde] -struct AdminRoleAbolished; - -#[cw_serde] -pub enum AdminUpdate { - /// Proposes a new admin to take role. Only current admin can execute. - ProposeNewAdmin { proposed: String }, - /// Clears the currently proposed admin. Only current admin can execute. - ClearProposed, - /// Promotes the proposed admin to be the current one. Only the proposed admin can execute. - AcceptProposed, - /// Throws away the keys to the Admin role forever. Once done, no admin can ever be set later. - AbolishAdminRole, -} - -#[cw_serde] -pub enum AdminInit { - /// Sets the initial admin when none. No restrictions permissions to modify. - SetInitialAdmin { admin: String }, - /// Throws away the keys to the Admin role forever. Once done, no admin can ever be set later. - AbolishAdminRole, -} - -/// A struct designed to help facilitate a two-step transition between contract admins safely. -/// It implements a finite state machine with dispatched events to manage state transitions. -/// State A: AdminUninitialized -/// - No restrictions on who can initialize the admin role -/// State B: AdminSetNoneProposed -/// - Once admin is set. Only they can execute the following updates: -/// - ProposeNewAdmin -/// - ClearProposed -/// State C: AdminSetWithProposed -/// - Only the proposed new admin can accept the new role via AcceptProposed {} -/// - The current admin can also clear the proposed new admin via ClearProposed {} -/// -/// In every state, the admin (or on init, the initializer) can choose to abandon the role -/// and make the config immutable. -/// -///```text -/// Clear Proposed -/// +-------------------------------------^ -/// | | -/// v | -/// +----------------+ +----------------+ +-------+--------+ -/// | Admin: None | Initialize Admin | Admin: Gabe | Propose New Admin | Admin: Gabe | -/// | Proposed: None +--------------------->| Proposed: None +---------------------->| Proposed: Joy | -/// +-----+----------+ ++---------------+ +-------+----+---+ -/// | | Admin: Joy | | -/// | | Proposed: None | | -/// Abolish Role | ^ | | -/// | *immutable | | Accept Proposed | | -/// | +----------------+ | <----------------------------------------+ | -/// +----------->| Admin: None | | | -/// | Proposed: None +----+------------------ Abolish Role --------------------+ -/// +----------------+ -/// ``` -pub struct Admin<'a>(Item<'a, AdminState>); - -impl<'a> Admin<'a> { - pub const fn new(namespace: &'a str) -> Self { - Self(Item::new(namespace)) - } - - fn state(&self, storage: &'a dyn Storage) -> StdResult { - Ok(self - .0 - .may_load(storage)? - .unwrap_or(AdminState::A(AdminUninitialized))) - } - - //-------------------------------------------------------------------------------------------------- - // Queries - //-------------------------------------------------------------------------------------------------- - pub fn current(&self, storage: &'a dyn Storage) -> StdResult> { - Ok(match self.state(storage)? { - AdminState::B(b) => Some(b.admin), - AdminState::C(c) => Some(c.admin), - _ => None, - }) - } - - pub fn is_admin(&self, storage: &'a dyn Storage, addr: &Addr) -> StdResult { - match self.current(storage)? { - Some(admin) if &admin == addr => Ok(true), - _ => Ok(false), - } - } - - pub fn proposed(&self, storage: &'a dyn Storage) -> StdResult> { - Ok(match self.state(storage)? { - AdminState::C(c) => Some(c.proposed), - _ => None, - }) - } - - pub fn is_proposed(&self, storage: &'a dyn Storage, addr: &Addr) -> StdResult { - match self.proposed(storage)? { - Some(proposed) if &proposed == addr => Ok(true), - _ => Ok(false), - } - } - - pub fn query(&self, storage: &'a dyn Storage) -> StdResult { - Ok(AdminResponse { - admin: self.current(storage)?.map(Into::into), - proposed: self.proposed(storage)?.map(Into::into), - }) - } - - //-------------------------------------------------------------------------------------------------- - // Mutations - //-------------------------------------------------------------------------------------------------- - /// Execute inside instantiate fn - pub fn initialize( - &self, - storage: &'a mut dyn Storage, - api: &'a dyn Api, - init_action: AdminInit, - ) -> AdminResult<()> { - let initial_state = self.state(storage)?; - match initial_state { - AdminState::A(a) => { - let new_state = match init_action { - AdminInit::SetInitialAdmin { admin } => { - let validated = api.addr_validate(&admin)?; - a.initialize(&validated) - } - AdminInit::AbolishAdminRole => a.abolish_admin_role(), - }; - self.0.save(storage, &new_state)?; - Ok(()) - } - // Can only be in uninitialized state to call this fn - _ => Err(AdminError::StateTransitionError {}), - } - } - - /// Composes execute responses for admin state updates - pub fn update( - &self, - deps: DepsMut, - info: MessageInfo, - update: AdminUpdate, - ) -> AdminResult> - where - C: Clone + Debug + PartialEq + JsonSchema, - { - let new_state = self.transition_state(deps.storage, deps.api, &info.sender, update)?; - self.0.save(deps.storage, &new_state)?; - - let res = self.query(deps.storage)?; - Ok(Response::new() - .add_attribute("action", "update_admin") - .add_attribute("admin", res.admin.unwrap_or_else(|| "None".to_string())) - .add_attribute( - "proposed", - res.proposed.unwrap_or_else(|| "None".to_string()), - ) - .add_attribute("sender", info.sender)) - } - - /// Executes admin state transitions - fn transition_state( - &self, - storage: &'a mut dyn Storage, - api: &'a dyn Api, - sender: &Addr, - event: AdminUpdate, - ) -> AdminResult { - let state = self.state(storage)?; - - let new_state = match (state, event) { - (AdminState::B(b), AdminUpdate::ProposeNewAdmin { proposed }) => { - let validated = api.addr_validate(&proposed)?; - self.assert_admin(storage, sender)?; - b.propose(&validated) - } - (AdminState::B(b), AdminUpdate::AbolishAdminRole) => { - self.assert_admin(storage, sender)?; - b.abolish_admin_role() - } - (AdminState::C(c), AdminUpdate::AcceptProposed) => { - self.assert_proposed(storage, sender)?; - c.accept_proposed() - } - (AdminState::C(c), AdminUpdate::ClearProposed) => { - self.assert_admin(storage, sender)?; - c.clear_proposed() - } - (AdminState::C(c), AdminUpdate::AbolishAdminRole) => { - self.assert_admin(storage, sender)?; - c.abolish_admin_role() - } - (_, _) => return Err(AdminError::StateTransitionError {}), - }; - Ok(new_state) - } - - //-------------------------------------------------------------------------------------------------- - // Assertions - //-------------------------------------------------------------------------------------------------- - /// Similar to is_admin() except it raises an exception if caller is not current admin - pub fn assert_admin(&self, storage: &'a dyn Storage, caller: &Addr) -> AdminResult<()> { - if !self.is_admin(storage, caller)? { - Err(AdminError::NotAdmin {}) - } else { - Ok(()) - } - } - - /// Similar to is_proposed() except it raises an exception if caller is not currently proposed new admin - pub fn assert_proposed(&self, storage: &'a dyn Storage, caller: &Addr) -> AdminResult<()> { - if !self.is_proposed(storage, caller)? { - Err(AdminError::NotProposedAdmin {}) - } else { - Ok(()) - } - } -} - -#[cfg(test)] -mod tests { - use cosmwasm_std::testing::{mock_dependencies, mock_info}; - use cosmwasm_std::Empty; - - use crate::AdminUpdate::{AbolishAdminRole, AcceptProposed, ClearProposed, ProposeNewAdmin}; - - use super::*; - - //-------------------------------------------------------------------------------------------------- - // Test invalid state transitions - //-------------------------------------------------------------------------------------------------- - - #[test] - fn invalid_uninitialized_state_transitions() { - let mut deps = mock_dependencies(); - let sender = Addr::unchecked("peter_parker"); - let info = mock_info(sender.as_ref(), &[]); - let admin = Admin::new("xyz"); - - let err = admin - .update::( - deps.as_mut(), - info.clone(), - ProposeNewAdmin { - proposed: "abc".to_string(), - }, - ) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - - let err = admin - .update::(deps.as_mut(), info.clone(), ClearProposed) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - - let err = admin - .update::(deps.as_mut(), info.clone(), AcceptProposed) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - - let err = admin - .update::(deps.as_mut(), info, AbolishAdminRole) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - } - - #[test] - fn invalid_admin_set_no_proposed_state_transitions() { - let mut deps = mock_dependencies(); - let sender = Addr::unchecked("peter_parker"); - let info = mock_info(sender.as_ref(), &[]); - let admin = Admin::new("xyz"); - - let mut_deps = deps.as_mut(); - - admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: sender.to_string(), - }, - ) - .unwrap(); - - let err = admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: "abc".to_string(), - }, - ) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - - let err = admin - .update::(deps.as_mut(), info.clone(), ClearProposed) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - - let err = admin - .update::(deps.as_mut(), info, AcceptProposed) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - } - - #[test] - fn invalid_admin_set_with_proposed_state_transitions() { - let mut deps = mock_dependencies(); - let sender = Addr::unchecked("peter_parker"); - let info = mock_info(sender.as_ref(), &[]); - let admin = Admin::new("xyz"); - - let mut_deps = deps.as_mut(); - - admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: sender.to_string(), - }, - ) - .unwrap(); - - admin - .update::( - mut_deps, - info.clone(), - ProposeNewAdmin { - proposed: "abc".to_string(), - }, - ) - .unwrap(); - - let mut_deps = deps.as_mut(); - - let err = admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: "abc".to_string(), - }, - ) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - - let err = admin - .update::( - deps.as_mut(), - info, - ProposeNewAdmin { - proposed: "efg".to_string(), - }, - ) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - } - - #[test] - fn invalid_admin_role_abolished_state_transitions() { - let mut deps = mock_dependencies(); - let sender = Addr::unchecked("peter_parker"); - let info = mock_info(sender.as_ref(), &[]); - let admin = Admin::new("xyz"); - - let mut_deps = deps.as_mut(); - - admin - .initialize(mut_deps.storage, mut_deps.api, AdminInit::AbolishAdminRole) - .unwrap(); - - let err = admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: "abc".to_string(), - }, - ) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - - let err = admin - .update::( - deps.as_mut(), - info.clone(), - ProposeNewAdmin { - proposed: "efg".to_string(), - }, - ) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - - let err = admin - .update::(deps.as_mut(), info.clone(), ClearProposed) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - - let err = admin - .update::(deps.as_mut(), info.clone(), AcceptProposed) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - - let err = admin - .update::(deps.as_mut(), info, AbolishAdminRole) - .unwrap_err(); - assert_eq!(err, AdminError::StateTransitionError {}); - } - - //-------------------------------------------------------------------------------------------------- - // Test permissions - //-------------------------------------------------------------------------------------------------- - - #[test] - fn initialize_admin_permissions() { - let mut deps = mock_dependencies(); - let mut_deps = deps.as_mut(); - let admin = Admin::new("xyz"); - - // Anyone can initialize - admin - .initialize(mut_deps.storage, mut_deps.api, AdminInit::AbolishAdminRole) - .unwrap(); - - let mut deps = mock_dependencies(); - let mut_deps = deps.as_mut(); - - admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: "xyz".to_string(), - }, - ) - .unwrap(); - } - - #[test] - fn propose_new_admin_permissions() { - let mut deps = mock_dependencies(); - let sender = Addr::unchecked("peter_parker"); - let admin = Admin::new("xyz"); - - let mut_deps = deps.as_mut(); - admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: sender.to_string(), - }, - ) - .unwrap(); - - let bad_guy = Addr::unchecked("doc_oc"); - let info = mock_info(bad_guy.as_ref(), &[]); - let err = admin - .update::( - mut_deps, - info, - ProposeNewAdmin { - proposed: bad_guy.to_string(), - }, - ) - .unwrap_err(); - - assert_eq!(err, AdminError::NotAdmin {}) - } - - #[test] - fn clear_proposed_permissions() { - let mut deps = mock_dependencies(); - let sender = Addr::unchecked("peter_parker"); - let info = mock_info(sender.as_ref(), &[]); - let admin = Admin::new("xyz"); - - let mut_deps = deps.as_mut(); - admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: sender.to_string(), - }, - ) - .unwrap(); - admin - .update::( - mut_deps, - info, - ProposeNewAdmin { - proposed: "miles_morales".to_string(), - }, - ) - .unwrap(); - - let bad_guy = Addr::unchecked("doc_oc"); - let info = mock_info(bad_guy.as_ref(), &[]); - let err = admin - .update::(deps.as_mut(), info, ClearProposed) - .unwrap_err(); - - assert_eq!(err, AdminError::NotAdmin {}) - } - - #[test] - fn accept_proposed_permissions() { - let mut deps = mock_dependencies(); - let sender = Addr::unchecked("peter_parker"); - let info = mock_info(sender.as_ref(), &[]); - let admin = Admin::new("xyz"); - - let mut_deps = deps.as_mut(); - admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: sender.to_string(), - }, - ) - .unwrap(); - admin - .update::( - mut_deps, - info, - ProposeNewAdmin { - proposed: "miles_morales".to_string(), - }, - ) - .unwrap(); - - let bad_guy = Addr::unchecked("doc_oc"); - let info = mock_info(bad_guy.as_ref(), &[]); - let err = admin - .update::(deps.as_mut(), info, AcceptProposed) - .unwrap_err(); - - assert_eq!(err, AdminError::NotProposedAdmin {}) - } - - #[test] - fn abolish_admin_role_permissions() { - let mut deps = mock_dependencies(); - let sender = Addr::unchecked("peter_parker"); - let admin = Admin::new("xyz"); - - let mut_deps = deps.as_mut(); - admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: sender.to_string(), - }, - ) - .unwrap(); - - let bad_guy = Addr::unchecked("doc_oc"); - let info = mock_info(bad_guy.as_ref(), &[]); - let err = admin - .update::(deps.as_mut(), info, AbolishAdminRole) - .unwrap_err(); - - assert_eq!(err, AdminError::NotAdmin {}) - } - - //-------------------------------------------------------------------------------------------------- - // Test success cases - //-------------------------------------------------------------------------------------------------- - - fn assert_uninitialized(storage: &dyn Storage, admin: &Admin) { - let state = admin.state(storage).unwrap(); - match state { - AdminState::A(_) => {} - _ => panic!("Should be in the AdminUninitialized state"), - } - - let current = admin.current(storage).unwrap(); - assert_eq!(current, None); - - let proposed = admin.proposed(storage).unwrap(); - assert_eq!(proposed, None); - - let res = admin.query(storage).unwrap(); - assert_eq!( - res, - AdminResponse { - admin: None, - proposed: None - } - ); - } - - #[test] - fn uninitialized_state() { - let deps = mock_dependencies(); - let admin = Admin::new("xyz"); - assert_uninitialized(deps.as_ref().storage, &admin); - } - - #[test] - fn initialize_admin() { - let mut deps = mock_dependencies(); - let original_admin = Addr::unchecked("peter_parker"); - let admin = Admin::new("xyz"); - - let mut_deps = deps.as_mut(); - admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: original_admin.to_string(), - }, - ) - .unwrap(); - - let state = admin.state(mut_deps.storage).unwrap(); - match state { - AdminState::B(_) => {} - _ => panic!("Should be in the AdminSetNoneProposed state"), - } - - let current = admin.current(mut_deps.storage).unwrap(); - assert_eq!(current, Some(original_admin.clone())); - assert!(admin.is_admin(mut_deps.storage, &original_admin).unwrap()); - - let proposed = admin.proposed(mut_deps.storage).unwrap(); - assert_eq!(proposed, None); - - let res = admin.query(mut_deps.storage).unwrap(); - assert_eq!( - res, - AdminResponse { - admin: Some(original_admin.to_string()), - proposed: None - } - ); - } - - #[test] - fn propose_new_admin() { - let mut deps = mock_dependencies(); - let original_admin = Addr::unchecked("peter_parker"); - let proposed_admin = Addr::unchecked("miles_morales"); - let info = mock_info(original_admin.as_ref(), &[]); - let admin = Admin::new("xyz"); - - let mut_deps = deps.as_mut(); - admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: original_admin.to_string(), - }, - ) - .unwrap(); - - admin - .update::( - mut_deps, - info, - ProposeNewAdmin { - proposed: "miles_morales".to_string(), - }, - ) - .unwrap(); - - let storage = deps.as_mut().storage; - - let state = admin.state(storage).unwrap(); - match state { - AdminState::C(_) => {} - _ => panic!("Should be in the AdminSetWithProposed state"), - } - - let current = admin.current(storage).unwrap(); - assert_eq!(current, Some(original_admin.clone())); - assert!(admin.is_admin(storage, &original_admin).unwrap()); - - let proposed = admin.proposed(storage).unwrap(); - assert_eq!(proposed, Some(proposed_admin.clone())); - assert!(admin.is_proposed(storage, &proposed_admin).unwrap()); - - let res = admin.query(storage).unwrap(); - assert_eq!( - res, - AdminResponse { - admin: Some(original_admin.to_string()), - proposed: Some(proposed_admin.to_string()) - } - ); - } - - #[test] - fn clear_proposed() { - let mut deps = mock_dependencies(); - let original_admin = Addr::unchecked("peter_parker"); - let proposed_admin = Addr::unchecked("miles_morales"); - let info = mock_info(original_admin.as_ref(), &[]); - let admin = Admin::new("xyz"); - - let mut_deps = deps.as_mut(); - admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: original_admin.to_string(), - }, - ) - .unwrap(); - - let mut_deps = deps.as_mut(); - admin - .update::( - mut_deps, - info.clone(), - ProposeNewAdmin { - proposed: "miles_morales".to_string(), - }, - ) - .unwrap(); - - let mut_deps = deps.as_mut(); - admin - .update::(mut_deps, info, ClearProposed) - .unwrap(); - - let storage = deps.as_mut().storage; - - let state = admin.state(storage).unwrap(); - match state { - AdminState::B(_) => {} - _ => panic!("Should be in the AdminSetNoneProposed state"), - } - - let current = admin.current(storage).unwrap(); - assert_eq!(current, Some(original_admin.clone())); - assert!(admin.is_admin(storage, &original_admin).unwrap()); - - let proposed = admin.proposed(storage).unwrap(); - assert_eq!(proposed, None); - assert!(!admin.is_proposed(storage, &proposed_admin).unwrap()); - - let res = admin.query(storage).unwrap(); - assert_eq!( - res, - AdminResponse { - admin: Some(original_admin.to_string()), - proposed: None - } - ); - } - - #[test] - fn accept_proposed() { - let mut deps = mock_dependencies(); - let original_admin = Addr::unchecked("peter_parker"); - let proposed_admin = Addr::unchecked("miles_morales"); - let info = mock_info(original_admin.as_ref(), &[]); - let admin = Admin::new("xyz"); - - let mut_deps = deps.as_mut(); - admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: original_admin.to_string(), - }, - ) - .unwrap(); - - let mut_deps = deps.as_mut(); - admin - .update::( - mut_deps, - info, - ProposeNewAdmin { - proposed: "miles_morales".to_string(), - }, - ) - .unwrap(); - - let info = mock_info(proposed_admin.as_ref(), &[]); - let mut_deps = deps.as_mut(); - admin - .update::(mut_deps, info, AcceptProposed) - .unwrap(); - - let storage = deps.as_mut().storage; - - let state = admin.state(storage).unwrap(); - match state { - AdminState::B(_) => {} - _ => panic!("Should be in the AdminSetNoneProposed state"), - } - - let current = admin.current(storage).unwrap(); - assert_eq!(current, Some(proposed_admin.clone())); - assert!(admin.is_admin(storage, &proposed_admin).unwrap()); - - let proposed = admin.proposed(storage).unwrap(); - assert_eq!(proposed, None); - assert!(!admin.is_proposed(storage, &proposed_admin).unwrap()); - - let res = admin.query(storage).unwrap(); - assert_eq!( - res, - AdminResponse { - admin: Some(proposed_admin.to_string()), - proposed: None - } - ); - } - - #[test] - fn abolish_admin_role() { - let mut deps = mock_dependencies(); - let original_admin = Addr::unchecked("peter_parker"); - let info = mock_info(original_admin.as_ref(), &[]); - let admin = Admin::new("xyz"); - - let mut_deps = deps.as_mut(); - admin - .initialize( - mut_deps.storage, - mut_deps.api, - AdminInit::SetInitialAdmin { - admin: original_admin.to_string(), - }, - ) - .unwrap(); - - let mut_deps = deps.as_mut(); - admin - .update::(mut_deps, info, AbolishAdminRole) - .unwrap(); - - let storage = deps.as_mut().storage; - - let state = admin.state(storage).unwrap(); - match state { - AdminState::D(_) => {} - _ => panic!("Should be in the AdminRoleAbolished state"), - } - - let current = admin.current(storage).unwrap(); - assert_eq!(current, None); - assert!(!admin.is_admin(storage, &original_admin).unwrap()); - - let proposed = admin.proposed(storage).unwrap(); - assert_eq!(proposed, None); - assert!(!admin.is_proposed(storage, &original_admin).unwrap()); - - let res = admin.query(storage).unwrap(); - assert_eq!( - res, - AdminResponse { - admin: None, - proposed: None - } - ); - } -} diff --git a/packages/controllers/src/lib.rs b/packages/controllers/src/lib.rs deleted file mode 100644 index 66b6d9aa8..000000000 --- a/packages/controllers/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod admin; - -pub use admin::{Admin, AdminError, AdminInit, AdminResponse, AdminUpdate}; diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 33286295b..da3d558b9 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -17,16 +17,16 @@ doctest = false backtraces = ["cosmwasm-std/backtraces"] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cosmwasm-vault-standard = { workspace = true } -cw-controllers-admin-fork = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -mars-health = { workspace = true } -mars-mock-oracle = { workspace = true } -mars-mock-red-bank = { workspace = true } -mars-outpost = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cosmwasm-vault-standard = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +mars-health = { workspace = true } +mars-mock-oracle = { workspace = true } +mars-mock-red-bank = { workspace = true } +mars-outpost = { workspace = true } +mars-owner = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } diff --git a/packages/rover/src/adapters/swap/msgs.rs b/packages/rover/src/adapters/swap/msgs.rs index a24ba12a6..2134db856 100644 --- a/packages/rover/src/adapters/swap/msgs.rs +++ b/packages/rover/src/adapters/swap/msgs.rs @@ -1,17 +1,17 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; -use cw_controllers_admin_fork::AdminUpdate; +use mars_owner::OwnerUpdate; #[cw_serde] pub struct InstantiateMsg { - /// The contract's admin, who can update config - pub admin: String, + /// The contract's owner, who can update config + pub owner: String, } #[cw_serde] pub enum ExecuteMsg { - /// Manges admin role state - UpdateAdmin(AdminUpdate), + /// Manges owner role state + UpdateOwner(OwnerUpdate), /// Configure the route for swapping an asset /// /// This is chain-specific, and can include parameters such as slippage tolerance and the routes @@ -38,9 +38,9 @@ pub enum ExecuteMsg { #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - /// Query contract admin config - #[returns(cw_controllers_admin_fork::AdminResponse)] - Admin {}, + /// Query contract owner config + #[returns(mars_owner::OwnerResponse)] + Owner {}, /// Get route for swapping an input denom into an output denom #[returns(RouteResponse)] Route { denom_in: String, denom_out: String }, diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 638c73f75..9143ac590 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{ CheckedFromRatioError, CheckedMultiplyRatioError, Coin, DecimalRangeExceeded, OverflowError, StdError, Uint128, }; -use cw_controllers_admin_fork::AdminError; +use mars_owner::OwnerError; use thiserror::Error; use crate::coins::Coins; @@ -21,7 +21,7 @@ pub enum ContractError { AboveVaultDepositCap { new_value: String, maximum: String }, #[error("{0}")] - AdminError(#[from] AdminError), + OwnerError(#[from] OwnerError), #[error("{0} is not an available coin to request")] CoinNotAvailable(String), diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 38b9299e4..6d347893c 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; -use cw_controllers_admin_fork::AdminUpdate; +use mars_owner::OwnerUpdate; use crate::adapters::vault::{Vault, VaultPositionType, VaultUnchecked}; use crate::msg::instantiate::ConfigUpdates; @@ -24,8 +24,8 @@ pub enum ExecuteMsg { //-------------------------------------------------------------------------------------------------- /// Update contract config constants UpdateConfig { new_config: ConfigUpdates }, - /// Manages admin role state - UpdateAdmin(AdminUpdate), + /// Manages owner role state + UpdateOwner(OwnerUpdate), /// Internal actions only callable by the contract itself Callback(CallbackMsg), } diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index a9477d412..885167243 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -10,7 +10,7 @@ use cosmwasm_std::{Decimal, Uint128}; #[cw_serde] pub struct InstantiateMsg { /// The address with privileged access to update config - pub admin: String, + pub owner: String, /// Whitelisted coin denoms approved by governance pub allowed_coins: Vec, /// Vaults approved by governance that implement credit manager's vault interface diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 880297886..3571c6c29 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -162,8 +162,8 @@ pub struct VaultPositionValue { #[cw_serde] pub struct ConfigResponse { - pub admin: Option, - pub proposed_new_admin: Option, + pub owner: Option, + pub proposed_new_owner: Option, pub account_nft: Option, pub red_bank: String, pub oracle: String, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 078ac4c44..eed7a29d1 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -7,21 +7,17 @@ "title": "InstantiateMsg", "type": "object", "required": [ - "admin", "allowed_coins", "max_close_factor", "max_unlocking_positions", "oracle", + "owner", "red_bank", "swapper", "vault_configs", "zapper" ], "properties": { - "admin": { - "description": "The address with privileged access to update config", - "type": "string" - }, "allowed_coins": { "description": "Whitelisted coin denoms approved by governance", "type": "array", @@ -53,6 +49,10 @@ } ] }, + "owner": { + "description": "The address with privileged access to update config", + "type": "string" + }, "red_bank": { "description": "The Mars Protocol money market contract where we borrow assets from", "allOf": [ @@ -246,14 +246,14 @@ "additionalProperties": false }, { - "description": "Manages admin role state", + "description": "Manages owner role state", "type": "object", "required": [ - "update_admin" + "update_owner" ], "properties": { - "update_admin": { - "$ref": "#/definitions/AdminUpdate" + "update_owner": { + "$ref": "#/definitions/OwnerUpdate" } }, "additionalProperties": false @@ -648,53 +648,6 @@ "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" }, - "AdminUpdate": { - "oneOf": [ - { - "description": "Proposes a new admin to take role. Only current admin can execute.", - "type": "object", - "required": [ - "propose_new_admin" - ], - "properties": { - "propose_new_admin": { - "type": "object", - "required": [ - "proposed" - ], - "properties": { - "proposed": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Clears the currently proposed admin. Only current admin can execute.", - "type": "string", - "enum": [ - "clear_proposed" - ] - }, - { - "description": "Promotes the proposed admin to be the current one. Only the proposed admin can execute.", - "type": "string", - "enum": [ - "accept_proposed" - ] - }, - { - "description": "Throws away the keys to the Admin role forever. Once done, no admin can ever be set later.", - "type": "string", - "enum": [ - "abolish_admin_role" - ] - } - ] - }, "CallbackMsg": { "description": "Internal actions made by the contract with pre-validated inputs", "oneOf": [ @@ -1309,6 +1262,53 @@ "OracleBase_for_String": { "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" + ] + } + ] + }, "SwapperBase_for_String": { "type": "string" }, @@ -2152,12 +2152,6 @@ "null" ] }, - "admin": { - "type": [ - "string", - "null" - ] - }, "max_close_factor": { "$ref": "#/definitions/Decimal" }, @@ -2167,7 +2161,13 @@ "oracle": { "type": "string" }, - "proposed_new_admin": { + "owner": { + "type": [ + "string", + "null" + ] + }, + "proposed_new_owner": { "type": [ "string", "null" diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 696b70964..ba2ddbae6 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -856,12 +856,6 @@ "null" ] }, - "admin": { - "type": [ - "string", - "null" - ] - }, "max_close_factor": { "$ref": "#/definitions/Decimal" }, @@ -871,7 +865,13 @@ "oracle": { "type": "string" }, - "proposed_new_admin": { + "owner": { + "type": [ + "string", + "null" + ] + }, + "proposed_new_owner": { "type": [ "string", "null" diff --git a/schemas/mars-oracle-adapter/mars-oracle-adapter.json b/schemas/mars-oracle-adapter/mars-oracle-adapter.json index f0c720d28..a6d45da3f 100644 --- a/schemas/mars-oracle-adapter/mars-oracle-adapter.json +++ b/schemas/mars-oracle-adapter/mars-oracle-adapter.json @@ -7,17 +7,17 @@ "title": "InstantiateMsg", "type": "object", "required": [ - "admin", "oracle", + "owner", "vault_pricing" ], "properties": { - "admin": { - "type": "string" - }, "oracle": { "$ref": "#/definitions/OracleBase_for_String" }, + "owner": { + "type": "string" + }, "vault_pricing": { "type": "array", "items": { @@ -94,11 +94,11 @@ { "type": "object", "required": [ - "update_admin" + "update_owner" ], "properties": { - "update_admin": { - "$ref": "#/definitions/AdminUpdate" + "update_owner": { + "$ref": "#/definitions/OwnerUpdate" } }, "additionalProperties": false @@ -109,16 +109,44 @@ "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" }, - "AdminUpdate": { + "ConfigUpdates": { + "type": "object", + "properties": { + "oracle": { + "anyOf": [ + { + "$ref": "#/definitions/OracleBase_for_String" + }, + { + "type": "null" + } + ] + }, + "vault_pricing": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/VaultPricingInfo" + } + } + }, + "additionalProperties": false + }, + "OracleBase_for_String": { + "type": "string" + }, + "OwnerUpdate": { "oneOf": [ { - "description": "Proposes a new admin to take role. Only current admin can execute.", + "description": "Proposes a new owner to take role. Only current owner can execute.", "type": "object", "required": [ - "propose_new_admin" + "propose_new_owner" ], "properties": { - "propose_new_admin": { + "propose_new_owner": { "type": "object", "required": [ "proposed" @@ -134,56 +162,28 @@ "additionalProperties": false }, { - "description": "Clears the currently proposed admin. Only current admin can execute.", + "description": "Clears the currently proposed owner. Only current owner can execute.", "type": "string", "enum": [ "clear_proposed" ] }, { - "description": "Promotes the proposed admin to be the current one. Only the proposed admin can execute.", + "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 Admin role forever. Once done, no admin can ever be set later.", + "description": "Throws away the keys to the Owner role forever. Once done, no owner can ever be set later.", "type": "string", "enum": [ - "abolish_admin_role" + "abolish_owner_role" ] } ] }, - "ConfigUpdates": { - "type": "object", - "properties": { - "oracle": { - "anyOf": [ - { - "$ref": "#/definitions/OracleBase_for_String" - }, - { - "type": "null" - } - ] - }, - "vault_pricing": { - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/VaultPricingInfo" - } - } - }, - "additionalProperties": false - }, - "OracleBase_for_String": { - "type": "string" - }, "PricingMethod": { "type": "string", "enum": [ @@ -362,16 +362,16 @@ "oracle" ], "properties": { - "admin": { + "oracle": { + "$ref": "#/definitions/OracleBase_for_Addr" + }, + "owner": { "type": [ "string", "null" ] }, - "oracle": { - "$ref": "#/definitions/OracleBase_for_Addr" - }, - "proposed_new_admin": { + "proposed_new_owner": { "type": [ "string", "null" diff --git a/schemas/mars-swapper-base/mars-swapper-base.json b/schemas/mars-swapper-base/mars-swapper-base.json index 519a48c43..2a3a79534 100644 --- a/schemas/mars-swapper-base/mars-swapper-base.json +++ b/schemas/mars-swapper-base/mars-swapper-base.json @@ -7,11 +7,11 @@ "title": "InstantiateMsg", "type": "object", "required": [ - "admin" + "owner" ], "properties": { - "admin": { - "description": "The contract's admin, who can update config", + "owner": { + "description": "The contract's owner, who can update config", "type": "string" } }, @@ -22,14 +22,14 @@ "title": "ExecuteMsg", "oneOf": [ { - "description": "Manges admin role state", + "description": "Manges owner role state", "type": "object", "required": [ - "update_admin" + "update_owner" ], "properties": { - "update_admin": { - "$ref": "#/definitions/AdminUpdate" + "update_owner": { + "$ref": "#/definitions/OwnerUpdate" } }, "additionalProperties": false @@ -130,16 +130,39 @@ "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" }, - "AdminUpdate": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "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" + }, + "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" + }, + "OwnerUpdate": { "oneOf": [ { - "description": "Proposes a new admin to take role. Only current admin can execute.", + "description": "Proposes a new owner to take role. Only current owner can execute.", "type": "object", "required": [ - "propose_new_admin" + "propose_new_owner" ], "properties": { - "propose_new_admin": { + "propose_new_owner": { "type": "object", "required": [ "proposed" @@ -155,51 +178,28 @@ "additionalProperties": false }, { - "description": "Clears the currently proposed admin. Only current admin can execute.", + "description": "Clears the currently proposed owner. Only current owner can execute.", "type": "string", "enum": [ "clear_proposed" ] }, { - "description": "Promotes the proposed admin to be the current one. Only the proposed admin can execute.", + "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 Admin role forever. Once done, no admin can ever be set later.", + "description": "Throws away the keys to the Owner role forever. Once done, no owner can ever be set later.", "type": "string", "enum": [ - "abolish_admin_role" + "abolish_owner_role" ] } ] }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "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" - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -211,13 +211,13 @@ "title": "QueryMsg", "oneOf": [ { - "description": "Query contract admin config", + "description": "Query contract owner config", "type": "object", "required": [ - "admin" + "owner" ], "properties": { - "admin": { + "owner": { "type": "object", "additionalProperties": false } @@ -342,27 +342,6 @@ "migrate": null, "sudo": null, "responses": { - "admin": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AdminResponse", - "description": "Returned from Admin.query()", - "type": "object", - "properties": { - "admin": { - "type": [ - "string", - "null" - ] - }, - "proposed": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, "estimate_exact_in_swap": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "EstimateExactInSwapResponse", @@ -383,6 +362,37 @@ } } }, + "owner": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OwnerResponse", + "description": "Returned from Owner.query()", + "type": "object", + "required": [ + "abolished", + "initialized" + ], + "properties": { + "abolished": { + "type": "boolean" + }, + "initialized": { + "type": "boolean" + }, + "owner": { + "type": [ + "string", + "null" + ] + }, + "proposed": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, "route": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "RouteResponse_for_Empty", diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 03fd7625e..591e6d4d6 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -109,7 +109,7 @@ export class Deployer { async instantiateMarsOracleAdapter() { const msg: OracleAdapterInstantiateMsg = { oracle: this.config.oracle.addr, - admin: this.deployerAddr, + owner: this.deployerAddr, vault_pricing: this.config.oracle.vaultPricing, } @@ -127,7 +127,7 @@ export class Deployer { async instantiateSwapper() { const msg: SwapperInstantiateMsg = { - admin: this.deployerAddr, + owner: this.deployerAddr, } await this.instantiate('swapper', this.storage.codeIds.swapper!, msg) @@ -167,7 +167,7 @@ export class Deployer { allowed_coins: this.config.allowedCoins, vault_configs: this.config.vaults, oracle: this.storage.addresses.marsOracleAdapter!, - admin: this.deployerAddr, + owner: this.deployerAddr, red_bank: this.config.redBank.addr, max_close_factor: this.config.maxCloseFactor, swapper: this.storage.addresses.swapper!, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 58842a652..c9847d101 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -23,7 +23,7 @@ import { Action, ActionAmount, VaultPositionType, - AdminUpdate, + OwnerUpdate, CallbackMsg, Addr, ActionCoin, @@ -325,7 +325,7 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt memo?: string, funds?: Coin[], ) => Promise - updateAdmin: ( + updateOwner: ( fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], @@ -352,7 +352,7 @@ export class MarsCreditManagerClient this.createCreditAccount = this.createCreditAccount.bind(this) this.updateCreditAccount = this.updateCreditAccount.bind(this) this.updateConfig = this.updateConfig.bind(this) - this.updateAdmin = this.updateAdmin.bind(this) + this.updateOwner = this.updateOwner.bind(this) this.callback = this.callback.bind(this) } @@ -421,7 +421,7 @@ export class MarsCreditManagerClient funds, ) } - updateAdmin = async ( + updateOwner = async ( fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -430,7 +430,7 @@ export class MarsCreditManagerClient this.sender, this.contractAddress, { - update_admin: {}, + update_owner: {}, }, fee, memo, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index ed0956b03..92a600f33 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -24,7 +24,7 @@ import { Action, ActionAmount, VaultPositionType, - AdminUpdate, + OwnerUpdate, CallbackMsg, Addr, ActionCoin, @@ -79,7 +79,7 @@ export interface MarsCreditManagerMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject - updateAdmin: (funds?: Coin[]) => MsgExecuteContractEncodeObject + updateOwner: (funds?: Coin[]) => MsgExecuteContractEncodeObject callback: (funds?: Coin[]) => MsgExecuteContractEncodeObject } export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessage { @@ -92,7 +92,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag this.createCreditAccount = this.createCreditAccount.bind(this) this.updateCreditAccount = this.updateCreditAccount.bind(this) this.updateConfig = this.updateConfig.bind(this) - this.updateAdmin = this.updateAdmin.bind(this) + this.updateOwner = this.updateOwner.bind(this) this.callback = this.callback.bind(this) } @@ -162,7 +162,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag }), } } - updateAdmin = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateOwner = (funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -170,7 +170,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - update_admin: {}, + update_owner: {}, }), ), funds, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index ef41ac4be..54c1d4f0a 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -24,7 +24,7 @@ import { Action, ActionAmount, VaultPositionType, - AdminUpdate, + OwnerUpdate, CallbackMsg, Addr, ActionCoin, @@ -493,7 +493,7 @@ export function useMarsCreditManagerCallbackMutation( options, ) } -export interface MarsCreditManagerUpdateAdminMutation { +export interface MarsCreditManagerUpdateOwnerMutation { client: MarsCreditManagerClient args?: { fee?: number | StdFee | 'auto' @@ -501,14 +501,14 @@ export interface MarsCreditManagerUpdateAdminMutation { funds?: Coin[] } } -export function useMarsCreditManagerUpdateAdminMutation( +export function useMarsCreditManagerUpdateOwnerMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateAdmin(msg, fee, memo, funds), + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateOwner(msg, fee, memo, funds), options, ) } diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index e13489d17..d28c8ee14 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -12,11 +12,11 @@ export type RedBankBaseForString = string export type SwapperBaseForString = string export type ZapperBaseForString = string export interface InstantiateMsg { - admin: string allowed_coins: string[] max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: OracleBaseForString + owner: string red_bank: RedBankBaseForString swapper: SwapperBaseForString vault_configs: VaultInstantiateConfig[] @@ -56,7 +56,7 @@ export type ExecuteMsg = } } | { - update_admin: AdminUpdate + update_owner: OwnerUpdate } | { callback: CallbackMsg @@ -141,15 +141,15 @@ export type ActionAmount = exact: Uint128 } export type VaultPositionType = 'u_n_l_o_c_k_e_d' | 'l_o_c_k_e_d' | 'u_n_l_o_c_k_i_n_g' -export type AdminUpdate = +export type OwnerUpdate = | { - propose_new_admin: { + propose_new_owner: { proposed: string } } | 'clear_proposed' | 'accept_proposed' - | 'abolish_admin_role' + | 'abolish_owner_role' export type CallbackMsg = | { withdraw: { @@ -410,11 +410,11 @@ export interface VaultUnlockingPosition { export type ArrayOfString = string[] export interface ConfigResponse { account_nft?: string | null - admin?: string | null max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: string - proposed_new_admin?: string | null + owner?: string | null + proposed_new_owner?: string | null red_bank: string swapper: string zapper: string diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index e10b5a9e2..64663cdc7 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -165,11 +165,11 @@ export interface VaultUnlockingPosition { export type ArrayOfString = string[] export interface ConfigResponse { account_nft?: string | null - admin?: string | null max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: string - proposed_new_admin?: string | null + owner?: string | null + proposed_new_owner?: string | null red_bank: string swapper: string zapper: string diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts index cc38ba06d..dd523fd36 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts @@ -14,7 +14,7 @@ import { InstantiateMsg, VaultPricingInfo, ExecuteMsg, - AdminUpdate, + OwnerUpdate, ConfigUpdates, QueryMsg, ArrayOfVaultPricingInfo, @@ -96,7 +96,7 @@ export interface MarsOracleAdapterInterface extends MarsOracleAdapterReadOnlyInt memo?: string, funds?: Coin[], ) => Promise - updateAdmin: ( + updateOwner: ( fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], @@ -116,7 +116,7 @@ export class MarsOracleAdapterClient this.sender = sender this.contractAddress = contractAddress this.updateConfig = this.updateConfig.bind(this) - this.updateAdmin = this.updateAdmin.bind(this) + this.updateOwner = this.updateOwner.bind(this) } updateConfig = async ( @@ -142,7 +142,7 @@ export class MarsOracleAdapterClient funds, ) } - updateAdmin = async ( + updateOwner = async ( fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -151,7 +151,7 @@ export class MarsOracleAdapterClient this.sender, this.contractAddress, { - update_admin: {}, + update_owner: {}, }, fee, memo, diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts index 9ee76a6c7..b0e9a13a9 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts @@ -16,7 +16,7 @@ import { InstantiateMsg, VaultPricingInfo, ExecuteMsg, - AdminUpdate, + OwnerUpdate, ConfigUpdates, QueryMsg, ArrayOfVaultPricingInfo, @@ -36,7 +36,7 @@ export interface MarsOracleAdapterMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject - updateAdmin: (funds?: Coin[]) => MsgExecuteContractEncodeObject + updateOwner: (funds?: Coin[]) => MsgExecuteContractEncodeObject } export class MarsOracleAdapterMessageComposer implements MarsOracleAdapterMessage { sender: string @@ -46,7 +46,7 @@ export class MarsOracleAdapterMessageComposer implements MarsOracleAdapterMessag this.sender = sender this.contractAddress = contractAddress this.updateConfig = this.updateConfig.bind(this) - this.updateAdmin = this.updateAdmin.bind(this) + this.updateOwner = this.updateOwner.bind(this) } updateConfig = ( @@ -73,7 +73,7 @@ export class MarsOracleAdapterMessageComposer implements MarsOracleAdapterMessag }), } } - updateAdmin = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateOwner = (funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -81,7 +81,7 @@ export class MarsOracleAdapterMessageComposer implements MarsOracleAdapterMessag contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - update_admin: {}, + update_owner: {}, }), ), funds, diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts index 3512468da..756736628 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts @@ -15,7 +15,7 @@ import { InstantiateMsg, VaultPricingInfo, ExecuteMsg, - AdminUpdate, + OwnerUpdate, ConfigUpdates, QueryMsg, ArrayOfVaultPricingInfo, @@ -141,7 +141,7 @@ export function useMarsOracleAdapterPriceQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsOracleAdapterUpdateAdminMutation { +export interface MarsOracleAdapterUpdateOwnerMutation { client: MarsOracleAdapterClient args?: { fee?: number | StdFee | 'auto' @@ -149,14 +149,14 @@ export interface MarsOracleAdapterUpdateAdminMutation { funds?: Coin[] } } -export function useMarsOracleAdapterUpdateAdminMutation( +export function useMarsOracleAdapterUpdateOwnerMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateAdmin(msg, fee, memo, funds), + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateOwner(msg, fee, memo, funds), options, ) } diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts index a292ad435..768efa54c 100644 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts +++ b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts @@ -9,8 +9,8 @@ export type OracleBaseForString = string export type Addr = string export type PricingMethod = 'preview_redeem' export interface InstantiateMsg { - admin: string oracle: OracleBaseForString + owner: string vault_pricing: VaultPricingInfo[] } export interface VaultPricingInfo { @@ -26,17 +26,17 @@ export type ExecuteMsg = } } | { - update_admin: AdminUpdate + update_owner: OwnerUpdate } -export type AdminUpdate = +export type OwnerUpdate = | { - propose_new_admin: { + propose_new_owner: { proposed: string } } | 'clear_proposed' | 'accept_proposed' - | 'abolish_admin_role' + | 'abolish_owner_role' export interface ConfigUpdates { oracle?: OracleBaseForString | null vault_pricing?: VaultPricingInfo[] | null @@ -64,9 +64,9 @@ export type QueryMsg = export type ArrayOfVaultPricingInfo = VaultPricingInfo[] export type OracleBaseForAddr = string export interface ConfigResponse { - admin?: string | null oracle: OracleBaseForAddr - proposed_new_admin?: string | null + owner?: string | null + proposed_new_owner?: string | null } export type Decimal = string export interface PriceResponse { diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts index c59f852cd..00e8bc426 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts @@ -10,21 +10,21 @@ import { StdFee } from '@cosmjs/amino' import { InstantiateMsg, ExecuteMsg, - AdminUpdate, + OwnerUpdate, Uint128, Decimal, Addr, Empty, Coin, QueryMsg, - AdminResponse, EstimateExactInSwapResponse, + OwnerResponse, RouteResponseForEmpty, ArrayOfRouteResponseForEmpty, } from './MarsSwapperBase.types' export interface MarsSwapperBaseReadOnlyInterface { contractAddress: string - admin: () => Promise + owner: () => Promise route: ({ denomIn, denomOut, @@ -54,15 +54,15 @@ export class MarsSwapperBaseQueryClient implements MarsSwapperBaseReadOnlyInterf constructor(client: CosmWasmClient, contractAddress: string) { this.client = client this.contractAddress = contractAddress - this.admin = this.admin.bind(this) + this.owner = this.owner.bind(this) this.route = this.route.bind(this) this.routes = this.routes.bind(this) this.estimateExactInSwap = this.estimateExactInSwap.bind(this) } - admin = async (): Promise => { + owner = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { - admin: {}, + owner: {}, }) } route = async ({ @@ -111,7 +111,7 @@ export class MarsSwapperBaseQueryClient implements MarsSwapperBaseReadOnlyInterf export interface MarsSwapperBaseInterface extends MarsSwapperBaseReadOnlyInterface { contractAddress: string sender: string - updateAdmin: ( + updateOwner: ( fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], @@ -172,13 +172,13 @@ export class MarsSwapperBaseClient this.client = client this.sender = sender this.contractAddress = contractAddress - this.updateAdmin = this.updateAdmin.bind(this) + this.updateOwner = this.updateOwner.bind(this) this.setRoute = this.setRoute.bind(this) this.swapExactIn = this.swapExactIn.bind(this) this.transferResult = this.transferResult.bind(this) } - updateAdmin = async ( + updateOwner = async ( fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -187,7 +187,7 @@ export class MarsSwapperBaseClient this.sender, this.contractAddress, { - update_admin: {}, + update_owner: {}, }, fee, memo, diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts index 8de5ee530..86c7378c4 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts @@ -11,22 +11,22 @@ import { toUtf8 } from '@cosmjs/encoding' import { InstantiateMsg, ExecuteMsg, - AdminUpdate, + OwnerUpdate, Uint128, Decimal, Addr, Empty, Coin, QueryMsg, - AdminResponse, EstimateExactInSwapResponse, + OwnerResponse, RouteResponseForEmpty, ArrayOfRouteResponseForEmpty, } from './MarsSwapperBase.types' export interface MarsSwapperBaseMessage { contractAddress: string sender: string - updateAdmin: (funds?: Coin[]) => MsgExecuteContractEncodeObject + updateOwner: (funds?: Coin[]) => MsgExecuteContractEncodeObject setRoute: ( { denomIn, @@ -71,13 +71,13 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { constructor(sender: string, contractAddress: string) { this.sender = sender this.contractAddress = contractAddress - this.updateAdmin = this.updateAdmin.bind(this) + this.updateOwner = this.updateOwner.bind(this) this.setRoute = this.setRoute.bind(this) this.swapExactIn = this.swapExactIn.bind(this) this.transferResult = this.transferResult.bind(this) } - updateAdmin = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateOwner = (funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -85,7 +85,7 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - update_admin: {}, + update_owner: {}, }), ), funds, diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts index 791fd26c1..f9597c3af 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts @@ -11,15 +11,15 @@ import { StdFee } from '@cosmjs/amino' import { InstantiateMsg, ExecuteMsg, - AdminUpdate, + OwnerUpdate, Uint128, Decimal, Addr, Empty, Coin, QueryMsg, - AdminResponse, EstimateExactInSwapResponse, + OwnerResponse, RouteResponseForEmpty, ArrayOfRouteResponseForEmpty, } from './MarsSwapperBase.types' @@ -32,8 +32,8 @@ export const marsSwapperBaseQueryKeys = { ] as const, address: (contractAddress: string | undefined) => [{ ...marsSwapperBaseQueryKeys.contract[0], address: contractAddress }] as const, - admin: (contractAddress: string | undefined, args?: Record) => - [{ ...marsSwapperBaseQueryKeys.address(contractAddress)[0], method: 'admin', args }] as const, + owner: (contractAddress: string | undefined, args?: Record) => + [{ ...marsSwapperBaseQueryKeys.address(contractAddress)[0], method: 'owner', args }] as const, route: (contractAddress: string | undefined, args?: Record) => [{ ...marsSwapperBaseQueryKeys.address(contractAddress)[0], method: 'route', args }] as const, routes: (contractAddress: string | undefined, args?: Record) => @@ -128,15 +128,15 @@ export function useMarsSwapperBaseRouteQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsSwapperBaseAdminQuery - extends MarsSwapperBaseReactQuery {} -export function useMarsSwapperBaseAdminQuery({ +export interface MarsSwapperBaseOwnerQuery + extends MarsSwapperBaseReactQuery {} +export function useMarsSwapperBaseOwnerQuery({ client, options, -}: MarsSwapperBaseAdminQuery) { - return useQuery( - marsSwapperBaseQueryKeys.admin(client?.contractAddress), - () => (client ? client.admin() : Promise.reject(new Error('Invalid client'))), +}: MarsSwapperBaseOwnerQuery) { + return useQuery( + marsSwapperBaseQueryKeys.owner(client?.contractAddress), + () => (client ? client.owner() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } @@ -213,7 +213,7 @@ export function useMarsSwapperBaseSetRouteMutation( options, ) } -export interface MarsSwapperBaseUpdateAdminMutation { +export interface MarsSwapperBaseUpdateOwnerMutation { client: MarsSwapperBaseClient args?: { fee?: number | StdFee | 'auto' @@ -221,14 +221,14 @@ export interface MarsSwapperBaseUpdateAdminMutation { funds?: Coin[] } } -export function useMarsSwapperBaseUpdateAdminMutation( +export function useMarsSwapperBaseUpdateOwnerMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateAdmin(msg, fee, memo, funds), + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateOwner(msg, fee, memo, funds), options, ) } diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts index 44fbc492f..2524e475e 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts @@ -6,11 +6,11 @@ */ export interface InstantiateMsg { - admin: string + owner: string } export type ExecuteMsg = | { - update_admin: AdminUpdate + update_owner: OwnerUpdate } | { set_route: { @@ -33,15 +33,15 @@ export type ExecuteMsg = recipient: Addr } } -export type AdminUpdate = +export type OwnerUpdate = | { - propose_new_admin: { + propose_new_owner: { proposed: string } } | 'clear_proposed' | 'accept_proposed' - | 'abolish_admin_role' + | 'abolish_owner_role' export type Uint128 = string export type Decimal = string export type Addr = string @@ -55,7 +55,7 @@ export interface Coin { } export type QueryMsg = | { - admin: {} + owner: {} } | { route: { @@ -75,13 +75,15 @@ export type QueryMsg = denom_out: string } } -export interface AdminResponse { - admin?: string | null - proposed?: string | null -} export interface EstimateExactInSwapResponse { amount: Uint128 } +export interface OwnerResponse { + abolished: boolean + initialized: boolean + owner?: string | null + proposed?: string | null +} export interface RouteResponseForEmpty { denom_in: string denom_out: string From 50a86bd08415a5bc29b0be9f8e677b2d6edecbd6 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 10 Jan 2023 13:34:30 +0100 Subject: [PATCH 118/218] Health check + Vault pricing updates (#90) * Extract adapter msg to package * test imports * Remove unused deps * add deps back * update decimal math * builds * fix tests * Adding flexible oracle struct * tests compile * simplify types * removing old math dep * Convert to fraction math * Test fixing in progress * health tests fixed * health tests fixed * Update liquidate_coin tests. * removing oracle adatper * refactor health package * build tests * guard for zero total supply * fix liquidation tests * build scripts update * adjustments * review updates Co-authored-by: Piotr Babel --- Cargo.lock | 39 +- Cargo.toml | 4 +- contracts/account-nft/Cargo.toml | 1 + contracts/account-nft/src/config.rs | 6 +- contracts/account-nft/src/error.rs | 6 +- contracts/account-nft/src/execute.rs | 2 +- contracts/account-nft/src/msg/instantiate.rs | 8 +- .../tests/helpers/health_responses.rs | 13 +- .../account-nft/tests/helpers/mock_env.rs | 2 +- .../tests/helpers/mock_env_builder.rs | 6 +- .../account-nft/tests/test_burn_allowance.rs | 14 +- .../account-nft/tests/test_instantiate.rs | 6 +- .../account-nft/tests/test_update_config.rs | 4 +- contracts/credit-manager/Cargo.toml | 2 +- contracts/credit-manager/src/contract.rs | 2 +- contracts/credit-manager/src/health.rs | 266 ++++++---- .../credit-manager/src/liquidate_coin.rs | 31 +- contracts/credit-manager/src/state.rs | 4 +- contracts/credit-manager/src/utils.rs | 18 +- .../src/vault/liquidate_vault.rs | 36 +- contracts/credit-manager/src/vault/utils.rs | 19 +- .../credit-manager/tests/helpers/builders.rs | 4 +- .../credit-manager/tests/helpers/contracts.rs | 9 - .../credit-manager/tests/helpers/mock_env.rs | 91 +--- contracts/credit-manager/tests/test_borrow.rs | 4 +- contracts/credit-manager/tests/test_health.rs | 192 +++++--- .../credit-manager/tests/test_instantiate.rs | 11 +- .../tests/test_liquidate_coin.rs | 29 +- .../tests/test_liquidate_vault.rs | 19 +- contracts/credit-manager/tests/test_repay.rs | 5 +- .../tests/test_update_config.rs | 7 +- .../credit-manager/tests/test_vault_enter.rs | 2 +- .../credit-manager/tests/test_withdraw.rs | 2 +- .../credit-manager/tests/test_zap_provide.rs | 2 +- contracts/mock-credit-manager/Cargo.toml | 1 + contracts/mock-credit-manager/src/execute.rs | 2 +- contracts/mock-credit-manager/src/msg.rs | 2 +- contracts/mock-credit-manager/src/query.rs | 2 +- contracts/mock-credit-manager/src/state.rs | 2 +- contracts/mock-oracle/src/contract.rs | 1 - contracts/mock-vault/src/deposit.rs | 3 +- contracts/mock-vault/src/msg.rs | 2 +- contracts/mock-vault/src/state.rs | 2 +- contracts/oracle-adapter/Cargo.toml | 36 -- contracts/oracle-adapter/examples/schema.rs | 10 - contracts/oracle-adapter/src/contract.rs | 195 -------- contracts/oracle-adapter/src/error.rs | 31 -- contracts/oracle-adapter/src/lib.rs | 4 - contracts/oracle-adapter/src/msg.rs | 71 --- contracts/oracle-adapter/src/state.rs | 11 - contracts/oracle-adapter/tests/helpers.rs | 163 ------- .../oracle-adapter/tests/test_query_price.rs | 118 ----- .../oracle-adapter/tests/test_update_admin.rs | 174 ------- .../tests/test_update_config.rs | 146 ------ contracts/swapper/mock/Cargo.toml | 1 - contracts/zapper/base/Cargo.toml | 1 - contracts/zapper/mock/src/query.rs | 3 +- contracts/zapper/mock/src/state.rs | 2 +- packages/health/Cargo.toml | 4 +- packages/health/src/health.rs | 169 ++----- packages/health/src/lib.rs | 5 +- packages/health/src/query.rs | 42 -- packages/math/Cargo.toml | 21 + packages/math/src/fraction.rs | 100 ++++ packages/math/src/lib.rs | 3 + packages/math/tests/test_div_floor.rs | 75 +++ packages/math/tests/test_mul_ceil.rs | 86 ++++ packages/math/tests/test_mul_floor.rs | 74 +++ packages/rover/Cargo.toml | 3 +- packages/rover/src/adapters/mod.rs | 11 +- packages/rover/src/adapters/oracle.rs | 15 +- packages/rover/src/adapters/vault/base.rs | 37 +- packages/rover/src/error.rs | 4 + packages/rover/src/lib.rs | 1 - packages/rover/src/math/ceil_ratio.rs | 43 -- packages/rover/src/math/mod.rs | 3 - packages/rover/src/msg/instantiate.rs | 13 +- packages/rover/src/msg/query.rs | 31 +- packages/rover/src/msg/zapper.rs | 2 +- packages/rover/src/traits.rs | 28 +- packages/rover/tests/test_ceil_ratio.rs | 54 --- schema.Makefile.toml | 1 - .../mars-account-nft/mars-account-nft.json | 24 +- .../mars-credit-manager.json | 12 +- .../mars-mock-credit-manager.json | 24 +- .../mars-oracle-adapter.json | 455 ------------------ scripts/deploy/base/deployer.ts | 22 +- scripts/deploy/base/index.ts | 2 - scripts/deploy/osmosis/config.ts | 9 - scripts/types/config.ts | 3 +- .../mars-account-nft/MarsAccountNft.client.ts | 2 +- .../MarsAccountNft.message-composer.ts | 2 +- .../MarsAccountNft.react-query.ts | 2 +- .../mars-account-nft/MarsAccountNft.types.ts | 8 +- .../MarsCreditManager.types.ts | 8 +- .../MarsMockCreditManager.client.ts | 2 +- .../MarsMockCreditManager.message-composer.ts | 2 +- .../MarsMockCreditManager.react-query.ts | 2 +- .../MarsMockCreditManager.types.ts | 10 +- .../MarsOracleAdapter.client.ts | 161 ------- .../MarsOracleAdapter.message-composer.ts | 91 ---- .../MarsOracleAdapter.react-query.ts | 185 ------- .../MarsOracleAdapter.types.ts | 75 --- .../generated/mars-oracle-adapter/bundle.ts | 14 - .../generated/mars-swapper-base/bundle.ts | 10 +- .../generated/mars-zapper-base/bundle.ts | 10 +- scripts/types/storageItems.ts | 2 - 107 files changed, 993 insertions(+), 2818 deletions(-) delete mode 100644 contracts/oracle-adapter/Cargo.toml delete mode 100644 contracts/oracle-adapter/examples/schema.rs delete mode 100644 contracts/oracle-adapter/src/contract.rs delete mode 100644 contracts/oracle-adapter/src/error.rs delete mode 100644 contracts/oracle-adapter/src/lib.rs delete mode 100644 contracts/oracle-adapter/src/msg.rs delete mode 100644 contracts/oracle-adapter/src/state.rs delete mode 100644 contracts/oracle-adapter/tests/helpers.rs delete mode 100644 contracts/oracle-adapter/tests/test_query_price.rs delete mode 100644 contracts/oracle-adapter/tests/test_update_admin.rs delete mode 100644 contracts/oracle-adapter/tests/test_update_config.rs delete mode 100644 packages/health/src/query.rs create mode 100644 packages/math/Cargo.toml create mode 100644 packages/math/src/fraction.rs create mode 100644 packages/math/src/lib.rs create mode 100644 packages/math/tests/test_div_floor.rs create mode 100644 packages/math/tests/test_mul_ceil.rs create mode 100644 packages/math/tests/test_mul_floor.rs delete mode 100644 packages/rover/src/math/ceil_ratio.rs delete mode 100644 packages/rover/src/math/mod.rs delete mode 100644 packages/rover/tests/test_ceil_ratio.rs delete mode 100644 schemas/mars-oracle-adapter/mars-oracle-adapter.json delete mode 100644 scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts delete mode 100644 scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts delete mode 100644 scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts delete mode 100644 scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts delete mode 100644 scripts/types/generated/mars-oracle-adapter/bundle.ts diff --git a/Cargo.lock b/Cargo.lock index b661a3cc1..ab191d20a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1015,6 +1015,7 @@ dependencies = [ "cw2 1.0.1", "cw721", "cw721-base", + "mars-health", "mars-mock-credit-manager", "mars-rover", "thiserror", @@ -1038,10 +1039,10 @@ dependencies = [ "itertools", "mars-account-nft", "mars-health", + "mars-math", "mars-mock-oracle", "mars-mock-red-bank", "mars-mock-vault", - "mars-oracle-adapter", "mars-outpost", "mars-owner", "mars-rover", @@ -1053,8 +1054,16 @@ dependencies = [ name = "mars-health" version = "1.0.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", - "mars-outpost", +] + +[[package]] +name = "mars-math" +version = "1.0.0" +dependencies = [ + "cosmwasm-std", + "thiserror", ] [[package]] @@ -1065,6 +1074,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.0.1", "cw-utils 0.16.0", + "mars-health", "mars-rover", "thiserror", ] @@ -1102,26 +1112,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "mars-oracle-adapter" -version = "1.0.0" -dependencies = [ - "anyhow", - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-vault-standard", - "cw-multi-test", - "cw-storage-plus 1.0.1", - "cw-utils 0.16.0", - "cw2 1.0.1", - "mars-mock-oracle", - "mars-mock-vault", - "mars-outpost", - "mars-owner", - "mars-rover", - "thiserror", -] - [[package]] name = "mars-osmosis" version = "1.0.0" @@ -1163,8 +1153,7 @@ dependencies = [ "cw-storage-plus 1.0.1", "cw-utils 0.16.0", "mars-health", - "mars-mock-oracle", - "mars-mock-red-bank", + "mars-math", "mars-outpost", "mars-owner", "schemars", @@ -1195,7 +1184,6 @@ dependencies = [ "cw-multi-test", "cw-storage-plus 1.0.1", "mars-rover", - "mars-swapper-base", "thiserror", ] @@ -1225,7 +1213,6 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-dex", - "cw-storage-plus 1.0.1", "cw-utils 0.16.0", "schemars", "serde", diff --git a/Cargo.toml b/Cargo.toml index 6c05d8466..c13dd9842 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,12 +3,12 @@ members = [ "contracts/account-nft", "contracts/credit-manager", "contracts/swapper/*", - "contracts/oracle-adapter", "contracts/zapper/*", "packages/chains/*", "packages/health", "packages/outpost", "packages/rover", + "packages/math", # Mock contracts "contracts/mock-oracle", @@ -51,6 +51,7 @@ thiserror = "1.0.37" # packages cosmwasm-vault-standard = { version = "0.1.0", features = ["lockup", "force-unlock"] } mars-health = { version = "1.0.0", path = "./packages/health" } +mars-math = { version = "1.0.0", path = "./packages/math" } mars-osmosis = { version = "1.0.0", path = "./packages/chains/osmosis" } mars-outpost = { version = "1.0.0", path = "./packages/outpost" } mars-owner = "1.0.0" @@ -58,7 +59,6 @@ mars-rover = { version = "1.0.0", path = "./packages/rover" } # contracts mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-oracle-adapter = { version = "1.0.0", path = "contracts/oracle-adapter", features = ["library"] } mars-swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } mars-zapper-base = { version = "1.0.0", path = "./contracts/zapper/base" } diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 626e2f700..088ce447e 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -24,6 +24,7 @@ cw2 = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true } cw-storage-plus = { workspace = true } +mars-health = { workspace = true } mars-rover = { workspace = true } thiserror = { workspace = true } diff --git a/contracts/account-nft/src/config.rs b/contracts/account-nft/src/config.rs index facffe1dc..bc8412920 100644 --- a/contracts/account-nft/src/config.rs +++ b/contracts/account-nft/src/config.rs @@ -1,9 +1,9 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Decimal}; +use cosmwasm_std::{Addr, Uint128}; #[cw_serde] pub struct ConfigBase { - pub max_value_for_burn: Decimal, + pub max_value_for_burn: Uint128, pub proposed_new_minter: Option, } @@ -21,6 +21,6 @@ impl From for UncheckedConfig { #[cw_serde] pub struct ConfigUpdates { - pub max_value_for_burn: Option, + pub max_value_for_burn: Option, pub proposed_new_minter: Option, } diff --git a/contracts/account-nft/src/error.rs b/contracts/account-nft/src/error.rs index 90c9a3827..779d64378 100644 --- a/contracts/account-nft/src/error.rs +++ b/contracts/account-nft/src/error.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Decimal, OverflowError, StdError}; +use cosmwasm_std::{OverflowError, StdError, Uint128}; use cw721_base::ContractError as Base721Error; use thiserror::Error; @@ -17,7 +17,7 @@ pub enum ContractError { "Account balances too high. Collateral + Debts = {current_balances:?}. Max allowed is {max_value_allowed:?}" )] BurnNotAllowed { - current_balances: Decimal, - max_value_allowed: Decimal, + current_balances: Uint128, + max_value_allowed: Uint128, }, } diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index c9fc746d2..c41a8beec 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ use cw721::Cw721Execute; use cw721_base::MintMsg; -use mars_rover::msg::query::HealthResponse; +use mars_health::HealthResponse; use mars_rover::msg::QueryMsg::Health; use crate::config::ConfigUpdates; diff --git a/contracts/account-nft/src/msg/instantiate.rs b/contracts/account-nft/src/msg/instantiate.rs index 81f726134..f5329167b 100644 --- a/contracts/account-nft/src/msg/instantiate.rs +++ b/contracts/account-nft/src/msg/instantiate.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::Decimal; +use cosmwasm_std::Uint128; use cw721_base::InstantiateMsg as ParentInstantiateMsg; #[cw_serde] @@ -7,9 +7,9 @@ pub struct InstantiateMsg { //-------------------------------------------------------------------------------------------------- // Extended and overridden messages //-------------------------------------------------------------------------------------------------- - /// The maximum amount of Debts + Collaterals for an account before burns are disallowed - /// for the NFT. Meant to prevent accidental account deletions. - pub max_value_for_burn: Decimal, + /// The maximum value of Debts + Collaterals (denominated in base token) for an account + /// before burns are disallowed for the NFT. Meant to prevent accidental account deletions + pub max_value_for_burn: Uint128, //-------------------------------------------------------------------------------------------------- // Base cw721 messages diff --git a/contracts/account-nft/tests/helpers/health_responses.rs b/contracts/account-nft/tests/helpers/health_responses.rs index 3a48546c0..c7824c1f7 100644 --- a/contracts/account-nft/tests/helpers/health_responses.rs +++ b/contracts/account-nft/tests/helpers/health_responses.rs @@ -1,15 +1,14 @@ use std::ops::Sub; -use cosmwasm_std::Decimal; +use cosmwasm_std::Uint128; +use mars_health::HealthResponse; -use mars_rover::msg::query::HealthResponse; - -pub const MAX_VALUE_FOR_BURN: u128 = 1000u128; +pub const MAX_VALUE_FOR_BURN: Uint128 = Uint128::new(1000); pub fn generate_health_response(debt_value: u128, collateral_value: u128) -> HealthResponse { HealthResponse { - total_debt_value: Decimal::from_atomics(debt_value, 0).unwrap(), - total_collateral_value: Decimal::from_atomics(collateral_value, 0).unwrap(), + total_debt_value: debt_value.into(), + total_collateral_value: collateral_value.into(), max_ltv_adjusted_collateral: Default::default(), liquidation_threshold_adjusted_collateral: Default::default(), max_ltv_health_factor: None, @@ -21,7 +20,7 @@ pub fn generate_health_response(debt_value: u128, collateral_value: u128) -> Hea pub fn below_max_for_burn() -> HealthResponse { HealthResponse { - total_debt_value: Decimal::from_atomics(MAX_VALUE_FOR_BURN.sub(1), 0).unwrap(), + total_debt_value: MAX_VALUE_FOR_BURN.sub(Uint128::one()), total_collateral_value: Default::default(), max_ltv_adjusted_collateral: Default::default(), liquidation_threshold_adjusted_collateral: Default::default(), diff --git a/contracts/account-nft/tests/helpers/mock_env.rs b/contracts/account-nft/tests/helpers/mock_env.rs index c3ae21aca..0b222a0f1 100644 --- a/contracts/account-nft/tests/helpers/mock_env.rs +++ b/contracts/account-nft/tests/helpers/mock_env.rs @@ -6,8 +6,8 @@ use cw_multi_test::{App, AppResponse, BasicApp, Executor}; use mars_account_nft::config::{ConfigUpdates, UncheckedConfig}; use mars_account_nft::msg::ExecuteMsg::{AcceptMinterRole, UpdateConfig}; use mars_account_nft::msg::{ExecuteMsg as ExtendedExecuteMsg, QueryMsg}; +use mars_health::HealthResponse; use mars_mock_credit_manager::msg::ExecuteMsg::SetHealthResponse; -use mars_rover::msg::query::HealthResponse; use crate::helpers::MockEnvBuilder; diff --git a/contracts/account-nft/tests/helpers/mock_env_builder.rs b/contracts/account-nft/tests/helpers/mock_env_builder.rs index 646101b7d..10e3e7584 100644 --- a/contracts/account-nft/tests/helpers/mock_env_builder.rs +++ b/contracts/account-nft/tests/helpers/mock_env_builder.rs @@ -1,11 +1,11 @@ use std::mem::take; use anyhow::Result as AnyResult; -use cosmwasm_std::{Addr, Decimal, Empty}; +use cosmwasm_std::{Addr, Empty}; use cw_multi_test::{BasicApp, Executor}; + use mars_account_nft::config::ConfigUpdates; use mars_account_nft::msg::ExecuteMsg::{AcceptMinterRole, UpdateConfig}; - use mars_account_nft::msg::InstantiateMsg; use crate::helpers::{ @@ -56,7 +56,7 @@ impl MockEnvBuilder { code_id, self.deployer.clone(), &InstantiateMsg { - max_value_for_burn: Decimal::from_atomics(MAX_VALUE_FOR_BURN, 0).unwrap(), + max_value_for_burn: MAX_VALUE_FOR_BURN, name: "mock_nft".to_string(), symbol: "MOCK".to_string(), minter, diff --git a/contracts/account-nft/tests/test_burn_allowance.rs b/contracts/account-nft/tests/test_burn_allowance.rs index 4a54ee355..4b78f4b3c 100644 --- a/contracts/account-nft/tests/test_burn_allowance.rs +++ b/contracts/account-nft/tests/test_burn_allowance.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Decimal, Empty, StdResult}; +use cosmwasm_std::{Addr, Empty, StdResult, Uint128}; use cw721::NftInfoResponse; use mars_account_nft::error::ContractError; @@ -22,8 +22,8 @@ fn test_burn_not_allowed_if_too_many_debts() { assert_eq!( error, BurnNotAllowed { - current_balances: Decimal::from_atomics(10_000u128, 0).unwrap(), - max_value_allowed: Decimal::from_atomics(MAX_VALUE_FOR_BURN, 0).unwrap() + current_balances: Uint128::new(10_000), + max_value_allowed: MAX_VALUE_FOR_BURN } ) } @@ -41,8 +41,8 @@ fn test_burn_not_allowed_if_too_much_collateral() { assert_eq!( error, BurnNotAllowed { - current_balances: Decimal::from_atomics(10_000u128, 0).unwrap(), - max_value_allowed: Decimal::from_atomics(MAX_VALUE_FOR_BURN, 0).unwrap() + current_balances: Uint128::new(10_000), + max_value_allowed: MAX_VALUE_FOR_BURN } ) } @@ -60,8 +60,8 @@ fn test_burn_allowance_works_with_both_debt_and_collateral() { assert_eq!( error, BurnNotAllowed { - current_balances: Decimal::from_atomics(1_001u128, 0).unwrap(), - max_value_allowed: Decimal::from_atomics(MAX_VALUE_FOR_BURN, 0).unwrap() + current_balances: Uint128::new(1_001), + max_value_allowed: MAX_VALUE_FOR_BURN } ) } diff --git a/contracts/account-nft/tests/test_instantiate.rs b/contracts/account-nft/tests/test_instantiate.rs index 0e35cbeaa..248cb1315 100644 --- a/contracts/account-nft/tests/test_instantiate.rs +++ b/contracts/account-nft/tests/test_instantiate.rs @@ -1,5 +1,4 @@ use crate::helpers::{MockEnv, MAX_VALUE_FOR_BURN}; -use cosmwasm_std::Decimal; pub mod helpers; @@ -9,10 +8,7 @@ fn test_storage_vars_set_on_instantiate() { let config = mock.query_config(); assert_eq!(config.proposed_new_minter, None); - assert_eq!( - config.max_value_for_burn, - Decimal::from_atomics(MAX_VALUE_FOR_BURN, 0).unwrap() - ); + assert_eq!(config.max_value_for_burn, MAX_VALUE_FOR_BURN); let next_id = mock.query_next_id(); assert_eq!(next_id, 1); diff --git a/contracts/account-nft/tests/test_update_config.rs b/contracts/account-nft/tests/test_update_config.rs index b8757cc44..8eb3a6344 100644 --- a/contracts/account-nft/tests/test_update_config.rs +++ b/contracts/account-nft/tests/test_update_config.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Decimal}; +use cosmwasm_std::{Addr, Uint128}; use mars_account_nft::config::ConfigUpdates; @@ -28,7 +28,7 @@ fn test_only_minter_can_update_config() { fn test_minter_can_update_config() { let mut mock = MockEnv::new().build().unwrap(); - let new_max_burn_val = Decimal::from_atomics(4918453u128, 2).unwrap(); + let new_max_burn_val = Uint128::new(4918453); let new_proposed_minter = "new_proposed_minter".to_string(); let updates = ConfigUpdates { diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 63d389b1c..30095de88 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -27,8 +27,8 @@ cw721-base = { workspace = true } cw-item-set = { workspace = true } cw-storage-plus = { workspace = true } mars-account-nft = { workspace = true } +mars-math = { workspace = true } mars-health = { workspace = true } -mars-oracle-adapter = { workspace = true } mars-outpost = { workspace = true } mars-owner = { workspace = true } mars-rover = { workspace = true } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index f3008883b..d9c239eca 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -3,9 +3,9 @@ use cosmwasm_std::{ }; use cw2::set_contract_version; +use mars_health::HealthResponse; use mars_rover::adapters::vault::VAULT_REQUEST_REPLY_ID; use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::msg::query::HealthResponse; use mars_rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::execute::{create_credit_account, dispatch_actions, execute_callback}; diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 6587baa7f..a6ba134a7 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -1,112 +1,192 @@ -use cosmwasm_std::{Coin, Decimal, Deps, Env, Event, Response}; -use mars_health::health::{Health, Position}; -use mars_health::query::MarsQuerier; -use mars_outpost::red_bank::Market; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Coin, Decimal, Deps, Env, Event, Response, Uint128}; +use mars_health::Health; +use mars_math::FractionMath; +use mars_outpost::oracle::PriceResponse; +use mars_outpost::red_bank::Market; use mars_rover::adapters::vault::VaultPosition; -use mars_rover::adapters::{Oracle, RedBank}; use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::traits::{Coins, IntoDecimal}; +use mars_rover::msg::query::{DebtAmount, Positions}; use crate::query::query_positions; use crate::state::{ALLOWED_COINS, ORACLE, RED_BANK, VAULT_CONFIGS}; -// Given Red Bank and Mars-Oracle does not have knowledge of vaults, -// we cannot use Health::compute_health_from_coins() and must assemble positions manually +/// Used as storage when trying to compute Health +#[cw_serde] +struct CollateralValue { + pub total_collateral_value: Uint128, + pub max_ltv_adjusted_collateral: Uint128, + pub liquidation_threshold_adjusted_collateral: Uint128, +} + +/// The mars-health package, red bank, and oracle do not have knowledge of vault config or pricing. +/// Cannot use the health package so forking and adjusting for rover internally here. pub fn compute_health(deps: Deps, env: &Env, account_id: &str) -> ContractResult { + let positions = query_positions(deps, env, account_id)?; + + let CollateralValue { + total_collateral_value, + max_ltv_adjusted_collateral, + liquidation_threshold_adjusted_collateral, + } = calculate_collateral_value(&deps, &positions)?; + + let total_debt_value = calculate_total_debt_value(&deps, &positions.debts)?; + + let max_ltv_health_factor = if total_debt_value.is_zero() { + None + } else { + Some(Decimal::checked_from_ratio( + max_ltv_adjusted_collateral, + total_debt_value, + )?) + }; + + let liquidation_health_factor = if total_debt_value.is_zero() { + None + } else { + Some(Decimal::checked_from_ratio( + liquidation_threshold_adjusted_collateral, + total_debt_value, + )?) + }; + + Ok(Health { + total_debt_value, + total_collateral_value, + max_ltv_adjusted_collateral, + liquidation_threshold_adjusted_collateral, + max_ltv_health_factor, + liquidation_health_factor, + }) +} + +fn calculate_collateral_value( + deps: &Deps, + positions: &Positions, +) -> ContractResult { + let deposits = calculate_deposits_value(deps, &positions.deposits)?; + let vaults = calculate_vaults_value(deps, &positions.vaults)?; + + Ok(CollateralValue { + total_collateral_value: deposits + .total_collateral_value + .checked_add(vaults.total_collateral_value)?, + max_ltv_adjusted_collateral: deposits + .max_ltv_adjusted_collateral + .checked_add(vaults.max_ltv_adjusted_collateral)?, + liquidation_threshold_adjusted_collateral: deposits + .liquidation_threshold_adjusted_collateral + .checked_add(vaults.liquidation_threshold_adjusted_collateral)?, + }) +} + +fn calculate_vaults_value( + deps: &Deps, + vaults: &[VaultPosition], +) -> ContractResult { let oracle = ORACLE.load(deps.storage)?; let red_bank = RED_BANK.load(deps.storage)?; - let res = query_positions(deps, env, account_id)?; - - let mut positions: Vec = vec![]; - let coin_positions = get_positions_for_coins( - &deps, - &res.deposits, - &res.debts.to_coins(), - &oracle, - &red_bank, - )?; - positions.extend(coin_positions); - let vault_positions = get_positions_for_vaults(&deps, &res.vaults, &oracle)?; - positions.extend(vault_positions); - - let health = Health::compute_health(&positions)?; - Ok(health) + let mut total_collateral_value = Uint128::zero(); + let mut max_ltv_adjusted_collateral = Uint128::zero(); + let mut liquidation_threshold_adjusted_collateral = Uint128::zero(); + + for v in vaults { + // Unlocked & locked denominated in vault coins + let vault_coin_amount = v.amount.unlocked().checked_add(v.amount.locked())?; + let vault_coin_value = v + .vault + .query_value(&deps.querier, &oracle, vault_coin_amount)?; + total_collateral_value = total_collateral_value.checked_add(vault_coin_value)?; + + let config = VAULT_CONFIGS.load(deps.storage, &v.vault.address)?; + max_ltv_adjusted_collateral = vault_coin_value + .checked_mul_floor(config.max_ltv)? + .checked_add(max_ltv_adjusted_collateral)?; + liquidation_threshold_adjusted_collateral = vault_coin_value + .checked_mul_floor(config.liquidation_threshold)? + .checked_add(liquidation_threshold_adjusted_collateral)?; + + // Unlocking positions denominated in underlying token + let info = v.vault.query_info(&deps.querier)?; + let PriceResponse { price, .. } = oracle.query_price(&deps.querier, &info.base_token)?; + let Market { + max_loan_to_value, + liquidation_threshold, + .. + } = red_bank.query_market(&deps.querier, &info.base_token)?; + for u in v.amount.unlocking().positions() { + let underlying_value = u.coin.amount.checked_mul_floor(price)?; + total_collateral_value = total_collateral_value.checked_add(underlying_value)?; + max_ltv_adjusted_collateral = underlying_value + .checked_mul_floor(max_loan_to_value)? + .checked_add(max_ltv_adjusted_collateral)?; + liquidation_threshold_adjusted_collateral = underlying_value + .checked_mul_floor(liquidation_threshold)? + .checked_add(liquidation_threshold_adjusted_collateral)?; + } + } + + Ok(CollateralValue { + total_collateral_value, + max_ltv_adjusted_collateral, + liquidation_threshold_adjusted_collateral, + }) } -fn get_positions_for_coins( - deps: &Deps, - collateral: &[Coin], - debt: &[Coin], - oracle: &Oracle, - red_bank: &RedBank, -) -> ContractResult> { - let querier = MarsQuerier::new(&deps.querier, oracle.address(), red_bank.address()); - let positions = Health::positions_from_coins(&querier, collateral, debt)? - .into_values() +fn calculate_deposits_value(deps: &Deps, deposits: &[Coin]) -> ContractResult { + let oracle = ORACLE.load(deps.storage)?; + let red_bank = RED_BANK.load(deps.storage)?; + + let mut total_collateral_value = Uint128::zero(); + let mut max_ltv_adjusted_collateral = Uint128::zero(); + let mut liquidation_threshold_adjusted_collateral = Uint128::zero(); + + for c in deposits { + let value = oracle.query_value(&deps.querier, c)?; + total_collateral_value = total_collateral_value.checked_add(value)?; + + let Market { + max_loan_to_value, + liquidation_threshold, + .. + } = red_bank.query_market(&deps.querier, &c.denom)?; + // If coin has been de-listed, drop MaxLTV to zero - .map(|mut p| { - if !ALLOWED_COINS.contains(deps.storage, &p.denom) { - p.max_ltv = Decimal::zero(); - } - p - }) - .collect(); - Ok(positions) + let max_ltv = if ALLOWED_COINS.contains(deps.storage, &c.denom) { + max_loan_to_value + } else { + Decimal::zero() + }; + let max_ltv_adjusted = value.checked_mul_floor(max_ltv)?; + max_ltv_adjusted_collateral = max_ltv_adjusted_collateral.checked_add(max_ltv_adjusted)?; + + let liq_adjusted = value.checked_mul_floor(liquidation_threshold)?; + liquidation_threshold_adjusted_collateral = + liquidation_threshold_adjusted_collateral.checked_add(liq_adjusted)?; + } + Ok(CollateralValue { + total_collateral_value, + max_ltv_adjusted_collateral, + liquidation_threshold_adjusted_collateral, + }) } -fn get_positions_for_vaults( - deps: &Deps, - vaults: &[VaultPosition], - oracle: &Oracle, -) -> ContractResult> { - let positions = vaults - .iter() - .map(|v| { - let info = v.vault.query_info(&deps.querier)?; - let price_res = oracle.query_price(&deps.querier, &info.vault_token)?; - let config = VAULT_CONFIGS.load(deps.storage, &v.vault.address)?; - let mut positions = vec![]; - - positions.push(Position { - denom: price_res.denom, - price: price_res.price, - collateral_amount: v - .amount - .unlocked() - .checked_add(v.amount.locked())? - .to_dec()?, - debt_amount: Decimal::zero(), - max_ltv: config.max_ltv, - liquidation_threshold: config.liquidation_threshold, - }); - - let red_bank = RED_BANK.load(deps.storage)?; - for u in v.amount.unlocking().positions() { - let price_res = oracle.query_price(&deps.querier, &u.coin.denom)?; - let Market { - max_loan_to_value, - liquidation_threshold, - .. - } = red_bank.query_market(&deps.querier, &u.coin.denom)?; - positions.push(Position { - denom: price_res.denom, - price: price_res.price, - collateral_amount: u.coin.amount.to_dec()?, - debt_amount: Decimal::zero(), - max_ltv: max_loan_to_value, - liquidation_threshold, - }) - } - - Ok(positions) - }) - .collect::>>()? - .into_iter() - .flatten() - .collect::>(); - Ok(positions) +fn calculate_total_debt_value(deps: &Deps, debts: &[DebtAmount]) -> ContractResult { + let oracle = ORACLE.load(deps.storage)?; + let mut total = Uint128::zero(); + for debt in debts { + let debt_value = oracle.query_value( + &deps.querier, + &Coin { + denom: debt.denom.clone(), + amount: debt.amount, + }, + )?; + total = total.checked_add(debt_value)?; + } + Ok(total) } pub fn assert_below_max_ltv(deps: Deps, env: Env, account_id: &str) -> ContractResult { diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index b085a0a2c..1b713e15e 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -1,13 +1,13 @@ -use std::ops::{Add, Div}; +use std::ops::Add; use cosmwasm_std::{ Coin, CosmosMsg, Decimal, DepsMut, Env, QuerierWrapper, Response, StdError, Storage, Uint128, }; -use mars_rover::adapters::Oracle; +use mars_math::FractionMath; +use mars_rover::adapters::oracle::Oracle; use mars_rover::error::{ContractError, ContractResult}; use mars_rover::msg::execute::CallbackMsg; -use mars_rover::traits::{IntoDecimal, IntoUint128}; use crate::health::{compute_health, val_or_na}; use crate::repay::current_debt_for_denom; @@ -86,26 +86,22 @@ pub fn calculate_liquidation( // Ensure debt amount does not exceed close factor % of the liquidatee's total debt value let close_factor = MAX_CLOSE_FACTOR.load(deps.storage)?; - let max_close_value = close_factor.checked_mul(health.total_debt_value)?; + let max_close_value = health.total_debt_value.checked_mul_floor(close_factor)?; let oracle = ORACLE.load(deps.storage)?; let debt_res = oracle.query_price(&deps.querier, &debt_coin.denom)?; - let max_close_amount = max_close_value.div(debt_res.price).uint128(); + let max_close_amount = max_close_value.checked_div_floor(debt_res.price)?; // Calculate the maximum debt possible to repay given liquidatee's request coin balance // FORMULA: debt amount = request value / (1 + liquidation bonus %) / debt price let request_res = oracle.query_price(&deps.querier, request_coin)?; - let max_request_value = request_res - .price - .checked_mul(request_coin_balance.to_dec()?)?; - + let max_request_value = request_coin_balance.checked_mul_floor(request_res.price)?; let liq_bonus_rate = RED_BANK .load(deps.storage)? .query_market(&deps.querier, &debt_coin.denom)? .liquidation_bonus; let request_coin_adjusted_max_debt = max_request_value - .div(liq_bonus_rate.add(Decimal::one())) - .div(debt_res.price) - .uint128(); + .checked_div_floor(Decimal::one().add(liq_bonus_rate))? + .checked_div_floor(debt_res.price)?; let final_debt_to_repay = *vec![ debt_coin.amount, @@ -118,12 +114,11 @@ pub fn calculate_liquidation( .ok_or_else(|| StdError::generic_err("Minimum not found"))?; // Calculate exact request coin amount to give to liquidator - // FORMULA: request amount = (1 + liquidation bonus %) * debt value / request coin price - let request_amount = liq_bonus_rate - .add(Decimal::one()) - .checked_mul(debt_res.price.checked_mul(final_debt_to_repay.to_dec()?)?)? - .div(request_res.price) - .uint128(); + // FORMULA: request amount = debt value * (1 + liquidation bonus %) / request coin price + let request_amount = final_debt_to_repay + .checked_mul_floor(debt_res.price)? + .checked_mul_floor(liq_bonus_rate.add(Decimal::one()))? + .checked_div_floor(request_res.price)?; // (Debt Coin, Request Coin) let result = ( diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 9c4e085a5..e792c0af8 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -3,9 +3,11 @@ use cw_item_set::Set; use cw_storage_plus::{Item, Map}; use mars_owner::Owner; +use mars_rover::adapters::oracle::Oracle; +use mars_rover::adapters::red_bank::RedBank; use mars_rover::adapters::swap::Swapper; use mars_rover::adapters::vault::{VaultConfig, VaultPositionAmount}; -use mars_rover::adapters::{Oracle, RedBank, Zapper}; +use mars_rover::adapters::zapper::Zapper; use crate::vault::RequestTempStorage; diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 5de186a95..7b3a90e08 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -9,12 +9,10 @@ use cosmwasm_std::{ use cw721::OwnerOfResponse; use cw721_base::QueryMsg; +use mars_math::{FractionMath, Fractional}; use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::math::CeilRatio; use mars_rover::msg::execute::CallbackMsg; -use mars_rover::msg::query::CoinValue; use mars_rover::msg::ExecuteMsg; -use mars_rover::traits::IntoDecimal; use crate::state::{ ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, @@ -138,7 +136,7 @@ pub fn debt_shares_to_amount( let total_debt_amount = red_bank.query_debt(&deps.querier, rover_addr, denom)?; // Amount of debt for token's position. Rounded up to favor participants in the debt pool. - let amount = total_debt_amount.multiply_ratio_ceil(shares, total_debt_shares)?; + let amount = total_debt_amount.checked_mul_ceil(Fractional(shares, total_debt_shares))?; Ok(Coin { denom: denom.to_string(), @@ -146,18 +144,6 @@ pub fn debt_shares_to_amount( }) } -pub fn coin_value(deps: &Deps, coin: &Coin) -> ContractResult { - let oracle = ORACLE.load(deps.storage)?; - let res = oracle.query_price(&deps.querier, &coin.denom)?; - let value = res.price.checked_mul(coin.amount.to_dec()?)?; - Ok(CoinValue { - denom: coin.denom.clone(), - amount: coin.amount, - price: res.price, - value, - }) -} - /// Contracts we call from Rover should not be attempting to execute actions. /// This assertion prevents a kind of reentrancy attack where a contract we call (that turned evil) /// can deposit into their own credit account and trick our state updates like update_coin_balances.rs diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index 7d946bbd8..b9f2eeaf9 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -1,6 +1,7 @@ use std::cmp::min; use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; +use cosmwasm_vault_standard::VaultInfoResponse; use mars_rover::adapters::vault::{ UnlockingChange, UnlockingPositions, UpdateType, Vault, VaultPositionAmount, VaultPositionType, @@ -75,13 +76,14 @@ fn liquidate_unlocked( ) -> ContractResult { let vault_info = request_vault.query_info(&deps.querier)?; - let (debt, request) = calculate_liquidation( + let (debt, request) = calculate_vault_liquidation( &deps, &env, liquidatee_account_id, &debt_coin, - &vault_info.vault_token, + &request_vault, amount, + &vault_info, )?; let repay_msg = repay_debt( @@ -120,6 +122,31 @@ fn liquidate_unlocked( .add_attribute("vault_coin_liquidated", request.amount)) } +/// Converts vault coins to their underlying value. This allows for pricing and liquidation +/// values to be determined. Afterward, the final amount is converted back into vault coins. +fn calculate_vault_liquidation( + deps: &DepsMut, + env: &Env, + liquidatee_account_id: &str, + debt_coin: &Coin, + request_vault: &Vault, + amount: Uint128, + vault_info: &VaultInfoResponse, +) -> ContractResult<(Coin, Coin)> { + let total_underlying = request_vault.query_preview_redeem(&deps.querier, amount)?; + let (debt, mut request) = calculate_liquidation( + deps, + env, + liquidatee_account_id, + debt_coin, + &vault_info.base_token, + total_underlying, + )?; + request.denom = vault_info.vault_token.clone(); + request.amount = amount.checked_multiply_ratio(request.amount, total_underlying)?; + Ok((debt, request)) +} + fn liquidate_unlocking( deps: DepsMut, env: Env, @@ -201,13 +228,14 @@ fn liquidate_locked( ) -> ContractResult { let vault_info = request_vault.query_info(&deps.querier)?; - let (debt, request) = calculate_liquidation( + let (debt, request) = calculate_vault_liquidation( &deps, &env, liquidatee_account_id, &debt_coin, - &vault_info.vault_token, + &request_vault, amount, + &vault_info, )?; let repay_msg = repay_debt( diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 148561d1a..3548b1e63 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,8 +1,8 @@ -use cosmwasm_std::{coin, Addr, Coin, Decimal, Deps, StdResult, Storage, Uint128}; +use cosmwasm_std::{Addr, Coin, Deps, StdResult, Storage, Uint128}; +use mars_math::FractionMath; use mars_rover::adapters::vault::{Vault, VaultPositionAmount, VaultPositionUpdate}; use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::traits::IntoUint128; use crate::state::{MAX_UNLOCKING_POSITIONS, ORACLE, VAULT_CONFIGS, VAULT_POSITIONS}; use crate::update_coin_balances::query_balance; @@ -84,9 +84,7 @@ pub fn vault_utilization_in_deposit_cap_denom( Ok(Coin { denom: config.deposit_cap.denom, - amount: rover_vault_balance_value - .checked_div(deposit_cap_denom_price)? - .uint128(), + amount: rover_vault_balance_value.checked_div_floor(deposit_cap_denom_price)?, }) } @@ -95,16 +93,9 @@ pub fn rover_vault_balance_value( deps: &Deps, vault: &Vault, rover_addr: &Addr, -) -> ContractResult { +) -> ContractResult { let oracle = ORACLE.load(deps.storage)?; - let vault_info = vault.query_info(&deps.querier)?; let rover_vault_coin_balance = vault.query_balance(&deps.querier, rover_addr)?; - let balance_value = oracle.query_total_value( - &deps.querier, - &[coin( - rover_vault_coin_balance.u128(), - vault_info.vault_token, - )], - )?; + let balance_value = vault.query_value(&deps.querier, &oracle, rover_vault_coin_balance)?; Ok(balance_value) } diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index 4201a92b0..6fdfcd555 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -1,8 +1,6 @@ use cosmwasm_std::{coin, Decimal}; use cw_utils::Duration; -use mars_rover::traits::IntoDecimal; - use crate::helpers::{lp_token_info, CoinInfo, VaultTestInfo}; pub fn build_mock_coin_infos(count: usize) -> Vec { @@ -12,7 +10,7 @@ pub fn build_mock_coin_infos(count: usize) -> Vec { denom: format!("coin_{}", i), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - price: 10.to_dec().unwrap(), + price: Decimal::from_atomics(10u128, 0).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), }) .collect() diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index 438af109e..aa3017e80 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -42,15 +42,6 @@ pub fn mock_oracle_contract() -> Box> { Box::new(contract) } -pub fn mock_oracle_adapter_contract() -> Box> { - let contract = ContractWrapper::new( - mars_oracle_adapter::contract::execute, - mars_oracle_adapter::contract::instantiate, - mars_oracle_adapter::contract::query, - ); - Box::new(contract) -} - pub fn mock_vault_contract() -> Box> { let contract = ContractWrapper::new( mars_mock_vault::contract::execute, diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index de91d2e01..abe10a796 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -2,48 +2,47 @@ use std::mem::take; use anyhow::Result as AnyResult; use cosmwasm_std::testing::MockApi; -use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128}; +use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128}; use cosmwasm_vault_standard::extensions::lockup::{LockupQueryMsg, UnlockingPosition}; -use cosmwasm_vault_standard::msg::VaultStandardQueryMsg::{Info as VaultInfoMsg, VaultExtension}; -use cosmwasm_vault_standard::msg::{ExtensionQueryMsg, VaultInfoResponse}; +use cosmwasm_vault_standard::msg::ExtensionQueryMsg; +use cosmwasm_vault_standard::msg::VaultStandardQueryMsg::VaultExtension; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; -use mars_account_nft::msg::InstantiateMsg as NftInstantiateMsg; use mars_owner::OwnerUpdate; use mars_account_nft::config::ConfigUpdates as NftConfigUpdates; use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; +use mars_account_nft::msg::InstantiateMsg as NftInstantiateMsg; +use mars_health::HealthResponse; use mars_mock_oracle::msg::{ CoinPrice, ExecuteMsg as OracleExecuteMsg, InstantiateMsg as OracleInstantiateMsg, }; use mars_mock_red_bank::msg::{CoinMarketInfo, InstantiateMsg as RedBankInstantiateMsg}; use mars_mock_vault::contract::DEFAULT_VAULT_TOKEN_PREFUND; use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; -use mars_oracle_adapter::msg::{ - InstantiateMsg as OracleAdapterInstantiateMsg, PricingMethod, VaultPricingInfo, -}; use mars_outpost::red_bank::QueryMsg::UserDebt; use mars_outpost::red_bank::UserDebtResponse; +use mars_rover::adapters::oracle::{Oracle, OracleBase, OracleUnchecked}; +use mars_rover::adapters::red_bank::RedBankBase; use mars_rover::adapters::swap::QueryMsg::EstimateExactInSwap; use mars_rover::adapters::swap::{ EstimateExactInSwapResponse, InstantiateMsg as SwapperInstantiateMsg, Swapper, SwapperBase, }; use mars_rover::adapters::vault::{VaultBase, VaultConfig, VaultUnchecked}; -use mars_rover::adapters::{OracleBase, OracleUnchecked, RedBankBase, Zapper, ZapperBase}; +use mars_rover::adapters::zapper::{Zapper, ZapperBase}; use mars_rover::msg::execute::{Action, CallbackMsg}; use mars_rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; use mars_rover::msg::query::{ - CoinBalanceResponseItem, ConfigResponse, DebtShares, HealthResponse, Positions, - SharesResponseItem, VaultInfoResponse as RoverVaultInfoResponse, VaultPositionResponseItem, - VaultWithBalance, + CoinBalanceResponseItem, ConfigResponse, DebtShares, Positions, SharesResponseItem, + VaultInfoResponse as RoverVaultInfoResponse, VaultPositionResponseItem, VaultWithBalance, }; use mars_rover::msg::zapper::QueryMsg::EstimateProvideLiquidity; use mars_rover::msg::zapper::{InstantiateMsg as ZapperInstantiateMsg, LpConfig}; use mars_rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::helpers::{ - lp_token_info, mock_account_nft_contract, mock_oracle_adapter_contract, mock_oracle_contract, - mock_red_bank_contract, mock_rover_contract, mock_swapper_contract, mock_vault_contract, - mock_zapper_contract, AccountToFund, CoinInfo, VaultTestInfo, + lp_token_info, mock_account_nft_contract, mock_oracle_contract, mock_red_bank_contract, + mock_rover_contract, mock_swapper_contract, mock_vault_contract, mock_zapper_contract, + AccountToFund, CoinInfo, VaultTestInfo, }; pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000); @@ -60,8 +59,7 @@ pub struct MockEnvBuilder { pub vault_configs: Option>, pub pre_deployed_vaults: Option>, pub allowed_coins: Option>, - pub oracle: Option>, - pub oracle_adapter: Option>, + pub oracle: Option, pub red_bank: Option>, pub deploy_nft_contract: bool, pub set_nft_contract_minter: bool, @@ -80,7 +78,6 @@ impl MockEnv { pre_deployed_vaults: None, allowed_coins: None, oracle: None, - oracle_adapter: None, red_bank: None, deploy_nft_contract: true, set_nft_contract_minter: true, @@ -539,7 +536,7 @@ impl MockEnvBuilder { vault_configs.extend(self.deploy_vaults()); vault_configs.extend(self.pre_deployed_vaults.clone().unwrap_or_default()); - let oracle = self.get_oracle_adapter(vault_configs.clone()).into(); + let oracle = self.get_oracle().into(); let zapper = self.deploy_zapper(&oracle)?.into(); self.app.instantiate_contract( @@ -568,7 +565,7 @@ impl MockEnvBuilder { .unwrap_or_else(|| Addr::unchecked("owner")) } - fn get_oracle(&mut self) -> OracleBase { + fn get_oracle(&mut self) -> Oracle { if self.oracle.is_none() { let addr = self.deploy_oracle(); self.oracle = Some(addr); @@ -576,7 +573,7 @@ impl MockEnvBuilder { self.oracle.clone().unwrap() } - fn deploy_oracle(&mut self) -> OracleBase { + fn deploy_oracle(&mut self) -> Oracle { let contract_code_id = self.app.store_code(mock_oracle_contract()); let mut prices: Vec = self .get_allowed_coins() @@ -604,56 +601,6 @@ impl MockEnvBuilder { OracleBase::new(addr) } - fn get_oracle_adapter(&mut self, vaults: Vec) -> OracleBase { - if self.oracle_adapter.is_none() { - let addr = self.deploy_oracle_adapter(vaults); - self.oracle_adapter = Some(addr); - } - self.oracle_adapter.clone().unwrap() - } - - fn deploy_oracle_adapter(&mut self, vaults: Vec) -> OracleBase { - let owner = Addr::unchecked("oracle_adapter_contract_owner"); - let contract_code_id = self.app.store_code(mock_oracle_adapter_contract()); - let oracle = self.get_oracle().into(); - let vault_pricing = if self.pre_deployed_vaults.is_some() { - vec![] - } else { - vaults - .into_iter() - .map(|config| { - let info: VaultInfoResponse = self - .app - .wrap() - .query_wasm_smart(config.vault.address.clone(), &VaultInfoMsg:: {}) - .unwrap(); - VaultPricingInfo { - vault_coin_denom: info.vault_token, - addr: Addr::unchecked(config.vault.address), - method: PricingMethod::PreviewRedeem, - base_denom: info.base_token, - } - }) - .collect() - }; - let addr = self - .app - .instantiate_contract( - contract_code_id, - owner.clone(), - &OracleAdapterInstantiateMsg { - oracle, - vault_pricing, - owner: owner.to_string(), - }, - &[], - "mars-oracle-adapter", - None, - ) - .unwrap(); - OracleBase::new(addr) - } - fn get_red_bank(&mut self) -> RedBankBase { if self.red_bank.is_none() { let addr = self.deploy_red_bank(); @@ -853,8 +800,8 @@ impl MockEnvBuilder { self } - pub fn oracle_adapter(&mut self, addr: &str) -> &mut Self { - self.oracle_adapter = Some(OracleBase::new(Addr::unchecked(addr))); + pub fn oracle(&mut self, addr: &str) -> &mut Self { + self.oracle = Some(OracleBase::new(Addr::unchecked(addr))); self } diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index 18042f75f..2f133c8da 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -105,7 +105,7 @@ fn test_cannot_borrow_above_max_ltv() { &user, vec![ Deposit(coin_info.to_coin(300)), - Borrow(coin_info.to_coin(700)), + Borrow(coin_info.to_coin(800)), ], &[coin(300, coin_info.denom)], ); @@ -114,7 +114,7 @@ fn test_cannot_borrow_above_max_ltv() { res, ContractError::AboveMaxLTV { account_id, - max_ltv_health_factor: "0.998573466476462196".to_string(), + max_ltv_health_factor: "0.96".to_string(), }, ); } diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index d42e5ab50..bea6284b8 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -1,15 +1,14 @@ -use std::ops::{Add, Div, Mul}; +use std::ops::{Add, Mul}; use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128}; use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; +use mars_math::{FractionMath, Fractional}; use mars_mock_oracle::msg::CoinPrice; use mars_rover::error::ContractError; -use mars_rover::math::CeilRatio; use mars_rover::msg::execute::Action::{Borrow, Deposit}; use mars_rover::msg::instantiate::ConfigUpdates; use mars_rover::msg::query::DebtAmount; -use mars_rover::traits::IntoDecimal; use crate::helpers::{ assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv, @@ -17,6 +16,9 @@ use crate::helpers::{ pub mod helpers; +// Health scenarios: +// https://docs.google.com/spreadsheets/d/1YhydvetAkLywgyFjpzLIdRe-_z-KbjFOigiPuQBh-ac/edit#gid=1394903922 + /// Action: User deposits 300 osmo (.25 price) /// Health: assets_value: 75 /// debt value 0 @@ -51,9 +53,9 @@ fn test_only_assets_with_no_debts() { assert_eq!(position.debts.len(), 0); let health = mock.query_health(&account_id); - let assets_value = coin_info.price * deposit_amount.to_dec().unwrap(); + let assets_value = deposit_amount.checked_mul_floor(coin_info.price).unwrap(); assert_eq!(health.total_collateral_value, assets_value); - assert_eq!(health.total_debt_value, Decimal::zero()); + assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.liquidation_health_factor, None); assert_eq!(health.max_ltv_health_factor, None); assert!(!health.liquidatable); @@ -74,7 +76,7 @@ fn test_only_assets_with_no_debts() { fn test_terra_ragnarok() { let coin_info = CoinInfo { denom: "uluna".to_string(), - price: 100.to_dec().unwrap(), + price: Decimal::from_atomics(100u128, 1).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), @@ -110,18 +112,32 @@ fn test_terra_ragnarok() { assert_eq!(position.debts.len(), 1); let health = mock.query_health(&account_id); - let assets_value = coin_info.price * (deposit_amount + borrow_amount).to_dec().unwrap(); + let assets_value = (deposit_amount + borrow_amount) + .checked_mul_floor(coin_info.price) + .unwrap(); assert_eq!(health.total_collateral_value, assets_value); // Note: Simulated yield from mock_red_bank makes debt position more expensive - let debts_value = coin_info.price * borrow_amount.add(Uint128::new(1)).to_dec().unwrap(); + let debts_value = borrow_amount + .add(Uint128::new(1)) + .checked_mul_floor(coin_info.price) + .unwrap(); assert_eq!(health.total_debt_value, debts_value); + assert_eq!( health.liquidation_health_factor, - Some(assets_value * coin_info.liquidation_threshold / debts_value) + Some(Decimal::from_ratio( + assets_value + .checked_mul_floor(coin_info.liquidation_threshold) + .unwrap(), + debts_value + )) ); assert_eq!( health.max_ltv_health_factor, - Some(assets_value * coin_info.max_ltv / debts_value) + Some(Decimal::from_ratio( + assets_value.checked_mul_floor(coin_info.max_ltv).unwrap(), + debts_value, + )) ); assert!(!health.liquidatable); assert!(!health.above_max_ltv); @@ -136,8 +152,8 @@ fn test_terra_ragnarok() { assert_eq!(position.debts.len(), 1); let health = mock.query_health(&account_id); - assert_eq!(health.total_collateral_value, Decimal::zero()); - assert_eq!(health.total_debt_value, Decimal::zero()); + assert_eq!(health.total_collateral_value, Uint128::zero()); + assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.liquidation_health_factor, None); assert_eq!(health.max_ltv_health_factor, None); assert!(!health.liquidatable); @@ -174,7 +190,7 @@ fn test_debts_no_assets() { res, ContractError::AboveMaxLTV { account_id: account_id.clone(), - max_ltv_health_factor: "0.693069306930693069".to_string(), + max_ltv_health_factor: "0.68".to_string(), }, ); @@ -184,8 +200,8 @@ fn test_debts_no_assets() { assert_eq!(position.debts.len(), 0); let health = mock.query_health(&account_id); - assert_eq!(health.total_collateral_value, Decimal::zero()); - assert_eq!(health.total_debt_value, Decimal::zero()); + assert_eq!(health.total_collateral_value, Uint128::zero()); + assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.liquidation_health_factor, None); assert_eq!(health.max_ltv_health_factor, None); assert!(!health.liquidatable); @@ -236,17 +252,17 @@ fn test_cannot_borrow_more_than_healthy() { assert_eq!(position.debts.len(), 1); let health = mock.query_health(&account_id); - let assets_value = Decimal::from_atomics(82789u128, 2).unwrap(); + let assets_value = Uint128::new(827); assert_eq!(health.total_collateral_value, assets_value); - let debts_value = Decimal::from_atomics(1206354u128, 4).unwrap(); + let debts_value = Uint128::new(120); assert_eq!(health.total_debt_value, debts_value); assert_eq!( health.liquidation_health_factor, - Some(assets_value * coin_info.liquidation_threshold / debts_value) + Some(Decimal::from_ratio(454u128, 120u128)) ); assert_eq!( health.max_ltv_health_factor, - Some(assets_value * coin_info.max_ltv / debts_value) + Some(Decimal::from_ratio(413u128, 120u128)) ); assert!(!health.liquidatable); assert!(!health.above_max_ltv); @@ -270,23 +286,23 @@ fn test_cannot_borrow_more_than_healthy() { res, ContractError::AboveMaxLTV { account_id: account_id.clone(), - max_ltv_health_factor: "0.990099009900990099".to_string(), + max_ltv_health_factor: "0.990223463687150837".to_string(), }, ); // All valid on step 2 as well (meaning step 3 did not go through) let health = mock.query_health(&account_id); - let assets_value = Decimal::from_atomics(106443u128, 2).unwrap(); + let assets_value = Uint128::new(1064); assert_eq!(health.total_collateral_value, assets_value); - let debts_value = Decimal::from_atomics(3595408u128, 4).unwrap(); + let debts_value = Uint128::new(359); assert_eq!(health.total_debt_value, debts_value); assert_eq!( health.liquidation_health_factor, - Some(assets_value * coin_info.liquidation_threshold / debts_value) + Some(Decimal::from_ratio(585u128, 359u128)) ); assert_eq!( health.max_ltv_health_factor, - Some(assets_value * coin_info.max_ltv / debts_value) + Some(Decimal::from_ratio(532u128, 359u128)) ); assert!(!health.liquidatable); assert!(!health.above_max_ltv); @@ -348,7 +364,7 @@ fn test_cannot_borrow_more_but_not_liquidatable() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: 24.to_dec().unwrap(), + price: Decimal::from_atomics(24u128, 0).unwrap(), }); let health = mock.query_health(&account_id); @@ -362,13 +378,13 @@ fn test_cannot_borrow_more_but_not_liquidatable() { res, ContractError::AboveMaxLTV { account_id: account_id.clone(), - max_ltv_health_factor: "0.947847222222222222".to_string(), + max_ltv_health_factor: "0.946759259259259259".to_string(), }, ); mock.price_change(CoinPrice { denom: uatom_info.denom, - price: 35.to_dec().unwrap(), + price: Decimal::from_atomics(35u128, 0).unwrap(), }); let health = mock.query_health(&account_id); @@ -429,35 +445,58 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { assert_eq!(position.debts.len(), 1); let health = mock.query_health(&account_id); - let deposit_amount_dec = deposit_amount.to_dec().unwrap(); - let borrowed_amount_dec = borrowed_amount.to_dec().unwrap(); assert_eq!( health.total_collateral_value, - uosmo_info.price * deposit_amount_dec + uatom_info.price * borrowed_amount_dec + deposit_amount + .checked_mul_floor(uosmo_info.price) + .unwrap() + .add(borrowed_amount.checked_mul_floor(uatom_info.price).unwrap()) ); assert_eq!( health.total_debt_value, - uatom_info.price * (borrowed_amount_dec + Decimal::one()) // simulated interest + Uint128::new(350_615_100) // with simulated interest ); - - let lqdt_adjusted_assets_value = - uosmo_info.price * deposit_amount_dec * uosmo_info.liquidation_threshold - + uatom_info.price * borrowed_amount_dec * uatom_info.liquidation_threshold; + let lqdt_adjusted_assets_value = deposit_amount + .checked_mul_floor(uosmo_info.price) + .unwrap() + .checked_mul_floor(uosmo_info.liquidation_threshold) + .unwrap() + .add( + borrowed_amount + .checked_mul_floor(uatom_info.price) + .unwrap() + .checked_mul_floor(uatom_info.liquidation_threshold) + .unwrap(), + ); assert_eq!( health.liquidation_health_factor, - Some( - lqdt_adjusted_assets_value - .div(uatom_info.price.mul(borrowed_amount_dec + Decimal::one())) - ) + Some(Decimal::from_ratio( + lqdt_adjusted_assets_value, + (borrowed_amount + Uint128::one()) + .checked_mul_floor(uatom_info.price) + .unwrap() + )) ); - let ltv_adjusted_assets_value = uosmo_info.price * deposit_amount_dec * uosmo_info.max_ltv - + uatom_info.price * borrowed_amount_dec * uatom_info.max_ltv; + let ltv_adjusted_assets_value = deposit_amount + .checked_mul_floor(uosmo_info.price) + .unwrap() + .checked_mul_floor(uosmo_info.max_ltv) + .unwrap() + .add( + borrowed_amount + .checked_mul_floor(uatom_info.price) + .unwrap() + .checked_mul_floor(uatom_info.max_ltv) + .unwrap(), + ); assert_eq!( health.max_ltv_health_factor, - Some( - ltv_adjusted_assets_value - .div(uatom_info.price.mul(borrowed_amount_dec + Decimal::one())) - ) + Some(Decimal::from_ratio( + ltv_adjusted_assets_value, + (borrowed_amount + Uint128::one()) + .checked_mul_floor(uatom_info.price) + .unwrap() + )) ); assert!(!health.liquidatable); assert!(!health.above_max_ltv); @@ -576,40 +615,63 @@ fn test_debt_value() { let user_a_owed_atom = red_bank_atom_debt .amount - .multiply_ratio_ceil(user_a_debt_shares_atom, red_bank_atom_res.shares) + .checked_mul_ceil(Fractional( + user_a_debt_shares_atom, + red_bank_atom_res.shares, + )) + .unwrap(); + let user_a_owed_atom_value = user_a_owed_atom + .checked_mul_floor(uatom_info.price) .unwrap(); - let user_a_owed_atom_value = uatom_info.price * user_a_owed_atom.to_dec().unwrap(); - let osmo_borrowed_amount_dec = (user_a_borrowed_amount_osmo + Uint128::one()) - .to_dec() + let osmo_debt_value = (user_a_borrowed_amount_osmo + Uint128::one()) + .checked_mul_floor(uosmo_info.price) .unwrap(); - let osmo_debt_value = uosmo_info.price * osmo_borrowed_amount_dec; let total_debt_value = user_a_owed_atom_value.add(osmo_debt_value); assert_eq!(health.total_debt_value, total_debt_value); - let user_a_deposit_amount_osmo_dec = user_a_deposit_amount_osmo.to_dec().unwrap(); - let user_a_borrowed_amount_osmo_dec = user_a_borrowed_amount_osmo.to_dec().unwrap(); - let user_a_borrowed_amount_atom_dec = user_a_borrowed_amount_atom.to_dec().unwrap(); + let lqdt_adjusted_assets_value = user_a_deposit_amount_osmo + .add(user_a_borrowed_amount_osmo) + .checked_mul_floor(uosmo_info.price) + .unwrap() + .checked_mul_floor(uosmo_info.liquidation_threshold) + .unwrap() + .add( + user_a_borrowed_amount_atom + .checked_mul_floor(uatom_info.price) + .unwrap() + .checked_mul_floor(uatom_info.liquidation_threshold) + .unwrap(), + ); - let lqdt_adjusted_assets_value = (uosmo_info.price - * user_a_deposit_amount_osmo_dec - * uosmo_info.liquidation_threshold) - + (uatom_info.price * user_a_borrowed_amount_atom_dec * uatom_info.liquidation_threshold) - + (uosmo_info.price * user_a_borrowed_amount_osmo_dec * uosmo_info.liquidation_threshold); assert_eq!( health.liquidation_health_factor, - Some(lqdt_adjusted_assets_value.div(total_debt_value)) + Some(Decimal::from_ratio( + lqdt_adjusted_assets_value, + total_debt_value + )) ); - let ltv_adjusted_assets_value = - (uosmo_info.price * user_a_deposit_amount_osmo_dec * uosmo_info.max_ltv) - + (uatom_info.price * user_a_borrowed_amount_atom_dec * uatom_info.max_ltv) - + (uosmo_info.price * user_a_borrowed_amount_osmo_dec * uosmo_info.max_ltv); - + let ltv_adjusted_assets_value = user_a_deposit_amount_osmo + .add(user_a_borrowed_amount_osmo) + .checked_mul_floor(uosmo_info.price) + .unwrap() + .checked_mul_floor(uosmo_info.max_ltv) + .unwrap() + .add( + user_a_borrowed_amount_atom + .checked_mul_floor(uatom_info.price) + .unwrap() + .checked_mul_floor(uatom_info.max_ltv) + .unwrap(), + ); assert_eq!( health.max_ltv_health_factor, - Some(ltv_adjusted_assets_value.div(total_debt_value)) + Some(Decimal::from_ratio( + ltv_adjusted_assets_value, + total_debt_value + )) ); } diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index f38cfea46..fcfaa4045 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -191,18 +191,15 @@ fn test_raises_on_invalid_red_bank_addr() { #[test] fn test_oracle_set_on_instantiate() { - let oracle_adapter_contract = "oracle_contract_456".to_string(); - let mock = MockEnv::new() - .oracle_adapter(&oracle_adapter_contract) - .build() - .unwrap(); + let oracle_contract = "oracle_contract_456".to_string(); + let mock = MockEnv::new().oracle(&oracle_contract).build().unwrap(); let res = mock.query_config(); - assert_eq!(oracle_adapter_contract, res.oracle); + assert_eq!(oracle_contract, res.oracle); } #[test] fn test_raises_on_invalid_oracle_addr() { - let mock = MockEnv::new().oracle_adapter("%%%INVALID%%%").build(); + let mock = MockEnv::new().oracle("%%%INVALID%%%").build(); if mock.is_ok() { panic!("Should have thrown an error"); } diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 7f86caaed..6f8c2bf0f 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -4,7 +4,6 @@ use mars_mock_oracle::msg::CoinPrice; use mars_rover::error::ContractError; use mars_rover::error::ContractError::{AboveMaxLTV, LiquidationNotProfitable, NotLiquidatable}; use mars_rover::msg::execute::Action::{Borrow, Deposit, EnterVault, LiquidateCoin}; -use mars_rover::traits::IntoDecimal; use crate::helpers::{ assert_err, get_coin, get_debt, lp_token_info, uatom_info, ujake_info, unlocked_vault_info, @@ -64,7 +63,7 @@ fn test_can_only_liquidate_unhealthy_accounts() { res, NotLiquidatable { account_id: liquidatee_account_id, - lqdt_health_factor: "2.029411764705882352".to_string(), + lqdt_health_factor: "2.019607843137254901".to_string(), }, ) } @@ -125,7 +124,7 @@ fn test_vault_positions_contribute_to_health() { res, NotLiquidatable { account_id: liquidatee_account_id, - lqdt_health_factor: "101.94976".to_string(), + lqdt_health_factor: "101.733333333333333333".to_string(), }, ) } @@ -163,7 +162,7 @@ fn test_liquidatee_does_not_have_requested_asset() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: 20.to_dec().unwrap(), + price: Decimal::from_atomics(20u128, 0).unwrap(), }); let liquidator = Addr::unchecked("liquidator"); @@ -237,7 +236,7 @@ fn test_liquidatee_does_not_have_debt_coin() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: 20.to_dec().unwrap(), + price: Decimal::from_atomics(20u128, 0).unwrap(), }); let liquidator = Addr::unchecked("liquidator"); @@ -292,7 +291,7 @@ fn test_liquidator_does_not_have_enough_to_pay_debt() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: 20.to_dec().unwrap(), + price: Decimal::from_atomics(20u128, 0).unwrap(), }); let liquidator = Addr::unchecked("liquidator"); @@ -351,7 +350,7 @@ fn test_liquidator_left_in_unhealthy_state() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: 20.to_dec().unwrap(), + price: Decimal::from_atomics(20u128, 0).unwrap(), }); let liquidator = Addr::unchecked("liquidator"); @@ -375,7 +374,7 @@ fn test_liquidator_left_in_unhealthy_state() { res, AboveMaxLTV { account_id: liquidator_account_id, - max_ltv_health_factor: "0.731818181818181818".to_string(), + max_ltv_health_factor: "0.727272727272727272".to_string(), }, ) } @@ -482,7 +481,7 @@ fn test_debt_amount_adjusted_to_close_factor_max() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: 6.to_dec().unwrap(), + price: Decimal::from_atomics(6u128, 0).unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -560,7 +559,7 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { mock.price_change(CoinPrice { denom: uatom_info.denom, - price: 20.to_dec().unwrap(), + price: Decimal::from_atomics(20u128, 0).unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -584,7 +583,7 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.deposits.len(), 3); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(181)); + assert_eq!(osmo_balance.amount, Uint128::new(184)); let atom_balance = get_coin("uatom", &position.deposits); assert_eq!(atom_balance.amount, Uint128::new(100)); let jake_balance = get_coin("ujake", &position.deposits); @@ -601,7 +600,7 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { let jake_balance = get_coin("ujake", &position.deposits); assert_eq!(jake_balance.amount, Uint128::new(39)); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(119)); + assert_eq!(osmo_balance.amount, Uint128::new(116)); } #[test] @@ -637,7 +636,7 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: 20.to_dec().unwrap(), + price: Decimal::from_atomics(20u128, 0).unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -737,7 +736,7 @@ fn test_debt_amount_no_adjustment() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.deposits.len(), 2); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(58)); + assert_eq!(osmo_balance.amount, Uint128::new(60)); let atom_balance = get_coin("uatom", &position.deposits); assert_eq!(atom_balance.amount, Uint128::new(100)); @@ -750,7 +749,7 @@ fn test_debt_amount_no_adjustment() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(242)); + assert_eq!(osmo_balance.amount, Uint128::new(240)); } #[test] diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 82d8cdd44..15fc6d9bd 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -8,7 +8,6 @@ use mars_rover::error::ContractError; use mars_rover::msg::execute::Action::{ Borrow, Deposit, EnterVault, LiquidateVault, RequestVaultUnlock, }; -use mars_rover::traits::IntoDecimal; use crate::helpers::{ assert_err, get_coin, get_debt, locked_vault_info, lp_token_info, uatom_info, ujake_info, @@ -164,7 +163,7 @@ fn test_liquidator_does_not_have_debt_coin_in_credit_account() { mock.price_change(CoinPrice { denom: ujake.denom.clone(), - price: Uint128::new(20).to_dec().unwrap(), + price: Decimal::from_atomics(20u128, 0).unwrap(), }); let liquidator = Addr::unchecked("liquidator"); @@ -351,7 +350,7 @@ fn test_liquidate_unlocked_vault() { mock.price_change(CoinPrice { denom: ujake.denom.clone(), - price: Uint128::new(20).to_dec().unwrap(), + price: Decimal::from_atomics(20u128, 0).unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -376,7 +375,7 @@ fn test_liquidate_unlocked_vault() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_balance = position.vaults.first().unwrap().amount.unlocked(); - assert_eq!(vault_balance, Uint128::new(883_533)); // 1M - 116_467 + assert_eq!(vault_balance, Uint128::new(885_000)); // 1M - 115_000 assert_eq!(position.deposits.len(), 1); let jake_balance = get_coin("ujake", &position.deposits); @@ -437,7 +436,7 @@ fn test_liquidate_locked_vault() { mock.price_change(CoinPrice { denom: atom.denom.clone(), - price: Uint128::new(20).to_dec().unwrap(), + price: Decimal::from_atomics(20u128, 0).unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -462,8 +461,8 @@ fn test_liquidate_locked_vault() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_amount = position.vaults.first().unwrap().amount.clone(); - // 1M - 835,527 vault tokens liquidated = 164,473 - assert_eq!(vault_amount.locked(), Uint128::new(164_473)); + // 1M - 825,000 vault tokens liquidated = 175,000 + assert_eq!(vault_amount.locked(), Uint128::new(175_000)); assert_eq!(vault_amount.unlocking().positions().len(), 0); assert_eq!(vault_amount.unlocked(), Uint128::zero()); @@ -542,7 +541,7 @@ fn test_liquidate_unlocking_liquidation_order() { mock.price_change(CoinPrice { denom: ujake.denom.clone(), - price: Uint128::new(20).to_dec().unwrap(), + price: Decimal::from_atomics(20u128, 0).unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -658,7 +657,7 @@ fn test_liquidation_calculation_adjustment() { mock.price_change(CoinPrice { denom: ujake.denom.clone(), - price: Uint128::new(20).to_dec().unwrap(), + price: Decimal::from_atomics(20u128, 0).unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -685,7 +684,7 @@ fn test_liquidation_calculation_adjustment() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_balance = position.vaults.first().unwrap().amount.unlocked(); - assert_eq!(vault_balance, Uint128::new(10_027)); // Vault position liquidated by 99% + assert_eq!(vault_balance, Uint128::new(15_000)); // Vault position liquidated by 99% assert_eq!(position.deposits.len(), 1); let jake_balance = get_coin("ujake", &position.deposits); diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index e6ad1349f..fc7b0f94c 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -5,7 +5,6 @@ use cosmwasm_std::{coin, coins, Addr, Decimal, OverflowError, OverflowOperation, use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use mars_rover::error::ContractError; use mars_rover::msg::execute::Action::{Borrow, Deposit, Repay, Withdraw}; -use mars_rover::traits::IntoDecimal; use crate::helpers::{ assert_err, uosmo_info, AccountToFund, CoinInfo, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, @@ -74,7 +73,7 @@ fn test_raises_when_repaying_what_is_not_owed() { let uatom_info = CoinInfo { denom: "atom".to_string(), - price: 9.to_dec().unwrap(), + price: Decimal::from_atomics(9u128, 0).unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), @@ -131,7 +130,7 @@ fn test_raises_when_not_enough_assets_to_repay() { let uatom_info = CoinInfo { denom: "atom".to_string(), - price: 9.to_dec().unwrap(), + price: Decimal::from_atomics(9u128, 0).unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index eca366c94..a8650453e 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -3,9 +3,10 @@ use cw_multi_test::{BasicApp, Executor}; use mars_mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; +use mars_rover::adapters::oracle::{OracleBase, OracleUnchecked}; use mars_rover::adapters::swap::SwapperBase; use mars_rover::adapters::vault::{VaultBase, VaultConfig}; -use mars_rover::adapters::{OracleBase, ZapperBase}; +use mars_rover::adapters::zapper::ZapperBase; use mars_rover::error::ContractError::InvalidConfig; use mars_rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; use mars_rover::msg::query::VaultInfoResponse; @@ -399,7 +400,7 @@ fn test_raises_on_duplicate_coin_configs() { ); } -fn deploy_new_oracle(app: &mut BasicApp) -> OracleBase { +fn deploy_new_oracle(app: &mut BasicApp) -> OracleUnchecked { let contract_code_id = app.store_code(mock_oracle_contract()); let addr = app .instantiate_contract( @@ -422,7 +423,7 @@ fn deploy_new_oracle(app: &mut BasicApp) -> OracleBase { None, ) .unwrap(); - OracleBase::new(addr.to_string()) + OracleUnchecked::new(addr.to_string()) } fn deploy_vault(app: &mut BasicApp) -> VaultInstantiateConfig { diff --git a/contracts/credit-manager/tests/test_vault_enter.rs b/contracts/credit-manager/tests/test_vault_enter.rs index 81c677c38..84c075953 100644 --- a/contracts/credit-manager/tests/test_vault_enter.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -394,7 +394,7 @@ fn test_vault_deposit_must_be_under_cap() { assert_err( res, ContractError::AboveVaultDepositCap { - new_value: "32584199.999999999993270287".to_string(), + new_value: "32584200".to_string(), maximum: "12345000".to_string(), }, ); diff --git a/contracts/credit-manager/tests/test_withdraw.rs b/contracts/credit-manager/tests/test_withdraw.rs index 033ba2e92..4629c9ca4 100644 --- a/contracts/credit-manager/tests/test_withdraw.rs +++ b/contracts/credit-manager/tests/test_withdraw.rs @@ -155,7 +155,7 @@ fn test_cannot_withdraw_more_than_healthy() { res, ContractError::AboveMaxLTV { account_id: account_id.clone(), - max_ltv_health_factor: "0.960099750623441396".to_string(), + max_ltv_health_factor: "0.95".to_string(), }, ); diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs index 5fe7a1601..c6d61e9e2 100644 --- a/contracts/credit-manager/tests/test_zap_provide.rs +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -269,7 +269,7 @@ fn test_successful_zap() { // assert follow up estimate can be made (calculates ratio from first deposit) let estimate = mock.estimate_provide_liquidity(&lp_token.denom, &[atom.to_coin(300), osmo.to_coin(150)]); - assert_eq!(estimate, STARTING_LP_POOL_TOKENS * Uint128::new(3)); // 3x the size as first deposit + assert_eq!(estimate, Uint128::new(3008928)); // ~3x the size as first deposit // assert user's new position let positions = mock.query_positions(&account_id); diff --git a/contracts/mock-credit-manager/Cargo.toml b/contracts/mock-credit-manager/Cargo.toml index e59fb3e44..7b5b29bb0 100644 --- a/contracts/mock-credit-manager/Cargo.toml +++ b/contracts/mock-credit-manager/Cargo.toml @@ -22,5 +22,6 @@ cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } +mars-health = { workspace = true } mars-rover = { workspace = true } thiserror = { workspace = true } diff --git a/contracts/mock-credit-manager/src/execute.rs b/contracts/mock-credit-manager/src/execute.rs index c00947fb0..b08f652b2 100644 --- a/contracts/mock-credit-manager/src/execute.rs +++ b/contracts/mock-credit-manager/src/execute.rs @@ -1,6 +1,6 @@ use crate::state::HEALTH_RESPONSES; use cosmwasm_std::{DepsMut, Response, StdResult}; -use mars_rover::msg::query::HealthResponse; +use mars_health::HealthResponse; pub fn set_health_response( deps: DepsMut, diff --git a/contracts/mock-credit-manager/src/msg.rs b/contracts/mock-credit-manager/src/msg.rs index 52fdab325..0c5d4ed4c 100644 --- a/contracts/mock-credit-manager/src/msg.rs +++ b/contracts/mock-credit-manager/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use mars_rover::msg::query::HealthResponse; +use mars_health::HealthResponse; #[cw_serde] pub enum ExecuteMsg { diff --git a/contracts/mock-credit-manager/src/query.rs b/contracts/mock-credit-manager/src/query.rs index 070ded0d5..1a79f40d1 100644 --- a/contracts/mock-credit-manager/src/query.rs +++ b/contracts/mock-credit-manager/src/query.rs @@ -1,6 +1,6 @@ use crate::state::HEALTH_RESPONSES; use cosmwasm_std::{Deps, StdResult}; -use mars_rover::msg::query::HealthResponse; +use mars_health::HealthResponse; pub fn query_health(deps: Deps, account_id: String) -> StdResult { HEALTH_RESPONSES.load(deps.storage, &account_id) diff --git a/contracts/mock-credit-manager/src/state.rs b/contracts/mock-credit-manager/src/state.rs index 5ee394e6e..9a5dc087f 100644 --- a/contracts/mock-credit-manager/src/state.rs +++ b/contracts/mock-credit-manager/src/state.rs @@ -1,5 +1,5 @@ use cw_storage_plus::Map; -use mars_rover::msg::query::HealthResponse; +use mars_health::HealthResponse; pub const HEALTH_RESPONSES: Map<&str, HealthResponse> = Map::new("health_responses"); // Map diff --git a/contracts/mock-oracle/src/contract.rs b/contracts/mock-oracle/src/contract.rs index a5ecc16de..f752070b8 100644 --- a/contracts/mock-oracle/src/contract.rs +++ b/contracts/mock-oracle/src/contract.rs @@ -1,7 +1,6 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; - use mars_outpost::oracle::PriceResponse; use crate::msg::{CoinPrice, ExecuteMsg, InstantiateMsg, QueryMsg}; diff --git a/contracts/mock-vault/src/deposit.rs b/contracts/mock-vault/src/deposit.rs index 6bd805e89..12f6fc1c9 100644 --- a/contracts/mock-vault/src/deposit.rs +++ b/contracts/mock-vault/src/deposit.rs @@ -16,8 +16,7 @@ pub fn deposit(deps: DepsMut, info: MessageInfo) -> Result = Item::new("vault_token_denom"); pub const TOTAL_VAULT_SHARES: Item = Item::new("total_vault_shares"); diff --git a/contracts/oracle-adapter/Cargo.toml b/contracts/oracle-adapter/Cargo.toml deleted file mode 100644 index 2483d8059..000000000 --- a/contracts/oracle-adapter/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "mars-oracle-adapter" -version = { workspace = true } -authors = { workspace = true } -edition = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -documentation = { workspace = true } -keywords = { workspace = true } - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -library = [] - -[dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -cw-storage-plus = { workspace = true } -mars-outpost = { workspace = true } -mars-owner = { workspace = true } -mars-rover = { workspace = true } -thiserror = { workspace = true } - -[dev-dependencies] -anyhow = { workspace = true } -cw-multi-test = { workspace = true } -cw-utils = { workspace = true } -mars-mock-oracle = { workspace = true } -mars-mock-vault = { workspace = true } -cosmwasm-vault-standard = { workspace = true } diff --git a/contracts/oracle-adapter/examples/schema.rs b/contracts/oracle-adapter/examples/schema.rs deleted file mode 100644 index 391924528..000000000 --- a/contracts/oracle-adapter/examples/schema.rs +++ /dev/null @@ -1,10 +0,0 @@ -use cosmwasm_schema::write_api; -use mars_oracle_adapter::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - query: QueryMsg, - execute: ExecuteMsg, - } -} diff --git a/contracts/oracle-adapter/src/contract.rs b/contracts/oracle-adapter/src/contract.rs deleted file mode 100644 index e8ce6bb38..000000000 --- a/contracts/oracle-adapter/src/contract.rs +++ /dev/null @@ -1,195 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_binary, Addr, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, -}; -use cw2::set_contract_version; -use cw_storage_plus::Bound; - -use mars_outpost::oracle::PriceResponse; -use mars_owner::OwnerInit::SetInitialOwner; -use mars_owner::OwnerUpdate; -use mars_rover::adapters::vault::VaultBase; -use mars_rover::adapters::Oracle; - -use crate::error::ContractResult; -use crate::msg::{ - ConfigResponse, ConfigUpdates, ExecuteMsg, InstantiateMsg, PricingMethod, QueryMsg, - VaultPricingInfo, -}; -use crate::state::{ORACLE, OWNER, VAULT_PRICING_INFO}; - -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -const MAX_LIMIT: u32 = 30; -const DEFAULT_LIMIT: u32 = 10; - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> ContractResult { - set_contract_version( - deps.storage, - &format!("crates.io:{}", CONTRACT_NAME), - CONTRACT_VERSION, - )?; - - OWNER.initialize(deps.storage, deps.api, SetInitialOwner { owner: msg.owner })?; - - let oracle = msg.oracle.check(deps.api)?; - ORACLE.save(deps.storage, &oracle)?; - - for info in msg.vault_pricing { - VAULT_PRICING_INFO.save(deps.storage, &info.vault_coin_denom, &info)?; - } - - Ok(Response::default()) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - _: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> ContractResult { - match msg { - ExecuteMsg::UpdateConfig { new_config } => update_config(deps, info, new_config), - ExecuteMsg::UpdateOwner(update) => update_owner(deps, info, update), - } -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> ContractResult { - let res = match msg { - QueryMsg::Price { denom } => to_binary(&query_price(deps, &denom)?), - QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::PricingInfo { denom } => to_binary(&query_pricing_info(deps, &denom)?), - QueryMsg::AllPricingInfo { start_after, limit } => { - to_binary(&query_all_pricing_info(deps, start_after, limit)?) - } - }; - res.map_err(Into::into) -} - -fn query_pricing_info(deps: Deps, denom: &str) -> StdResult { - VAULT_PRICING_INFO.load(deps.storage, denom) -} - -fn query_all_pricing_info( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult> { - let start = start_after - .as_ref() - .map(|denom| Bound::exclusive(denom.as_str())); - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - - VAULT_PRICING_INFO - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|res| { - let (_, info) = res?; - Ok(info) - }) - .collect::>>() -} - -fn query_price(deps: Deps, denom: &str) -> ContractResult { - let info_opt = VAULT_PRICING_INFO.may_load(deps.storage, denom)?; - let oracle = ORACLE.load(deps.storage)?; - - match info_opt { - Some(info) => match info.method { - PricingMethod::PreviewRedeem => { - let vault = VaultBase::new(info.addr.clone()); - calculate_preview_redeem(&deps, &oracle, &info, &vault) - } - }, - _ => Ok(oracle.query_price(&deps.querier, denom)?), - } -} - -fn calculate_preview_redeem( - deps: &Deps, - oracle: &Oracle, - info: &VaultPricingInfo, - vault: &VaultBase, -) -> ContractResult { - let vault_coin_supply = vault.query_total_vault_coins_issued(&deps.querier)?; - let price = if vault_coin_supply.is_zero() { - Decimal::zero() - } else { - let total_underlying = vault.query_preview_redeem(&deps.querier, vault_coin_supply)?; - let underlying_per_vault_coin = Decimal::from_ratio(total_underlying, vault_coin_supply); - let price_res = oracle.query_price(&deps.querier, &info.base_denom)?; - price_res.price.checked_mul(underlying_per_vault_coin)? - }; - - Ok(PriceResponse { - denom: info.vault_coin_denom.clone(), - price, - }) -} - -fn query_config(deps: Deps) -> ContractResult { - let res = OWNER.query(deps.storage)?; - Ok(ConfigResponse { - owner: res.owner, - proposed_new_owner: res.proposed, - oracle: ORACLE.load(deps.storage)?, - }) -} - -pub fn update_config( - deps: DepsMut, - info: MessageInfo, - new_config: ConfigUpdates, -) -> ContractResult { - OWNER.assert_owner(deps.storage, &info.sender)?; - - let mut response = - Response::new().add_attribute("action", "rover/oracle-adapter/update_config"); - - if let Some(unchecked) = new_config.oracle { - ORACLE.save(deps.storage, &unchecked.check(deps.api)?)?; - response = response - .add_attribute("key", "oracle") - .add_attribute("value", unchecked.address()); - } - - if let Some(vault_pricing) = new_config.vault_pricing { - VAULT_PRICING_INFO.clear(deps.storage); - for info in &vault_pricing { - VAULT_PRICING_INFO.save(deps.storage, &info.vault_coin_denom, info)?; - } - let value_str = if vault_pricing.is_empty() { - "None".to_string() - } else { - vault_pricing - .into_iter() - .map(|info| info.vault_coin_denom) - .collect::>() - .join(", ") - }; - response = response - .add_attribute("key", "vault_pricing") - .add_attribute("value", value_str); - } - - Ok(response) -} - -pub fn update_owner( - deps: DepsMut, - info: MessageInfo, - update: OwnerUpdate, -) -> ContractResult { - Ok(OWNER.update(deps, info, update)?) -} diff --git a/contracts/oracle-adapter/src/error.rs b/contracts/oracle-adapter/src/error.rs deleted file mode 100644 index 8bc292861..000000000 --- a/contracts/oracle-adapter/src/error.rs +++ /dev/null @@ -1,31 +0,0 @@ -use cosmwasm_std::{CheckedFromRatioError, DecimalRangeExceeded, OverflowError, StdError}; -use mars_owner::OwnerError; -use thiserror::Error; - -use mars_rover::error::ContractError as RoverError; - -pub type ContractResult = Result; - -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - OwnerError(#[from] OwnerError), - - #[error("{0}")] - CheckedFromRatioError(#[from] CheckedFromRatioError), - - #[error("{0}")] - DecimalRangeExceeded(#[from] DecimalRangeExceeded), - - #[error("{0}")] - Overflow(#[from] OverflowError), - - #[error("{0}")] - RoverError(#[from] RoverError), - - #[error("{0}")] - Std(#[from] StdError), - - #[error("{user:?} is not authorized to {action:?}")] - Unauthorized { user: String, action: String }, -} diff --git a/contracts/oracle-adapter/src/lib.rs b/contracts/oracle-adapter/src/lib.rs deleted file mode 100644 index a5abdbb0f..000000000 --- a/contracts/oracle-adapter/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod contract; -pub mod error; -pub mod msg; -pub mod state; diff --git a/contracts/oracle-adapter/src/msg.rs b/contracts/oracle-adapter/src/msg.rs deleted file mode 100644 index 198162e27..000000000 --- a/contracts/oracle-adapter/src/msg.rs +++ /dev/null @@ -1,71 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Decimal}; -use mars_owner::OwnerUpdate; - -use mars_rover::adapters::{Oracle, OracleUnchecked}; - -#[cw_serde] -pub struct CoinPrice { - pub denom: String, - pub price: Decimal, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub oracle: OracleUnchecked, - pub vault_pricing: Vec, - pub owner: String, -} - -#[cw_serde] -pub enum ExecuteMsg { - UpdateConfig { new_config: ConfigUpdates }, - UpdateOwner(OwnerUpdate), -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// If denom is vault coin, will retrieve priceable underlying before querying oracle - #[returns(mars_outpost::oracle::PriceResponse)] - Price { denom: String }, - - #[returns(ConfigResponse)] - Config {}, - - #[returns(VaultPricingInfo)] - PricingInfo { denom: String }, - - #[returns(Vec)] - AllPricingInfo { - start_after: Option, - limit: Option, - }, -} - -#[cw_serde] -pub struct ConfigResponse { - pub owner: Option, - pub proposed_new_owner: Option, - pub oracle: Oracle, -} - -#[cw_serde] -#[derive(Default)] -pub struct ConfigUpdates { - pub oracle: Option, - pub vault_pricing: Option>, -} - -#[cw_serde] -pub struct VaultPricingInfo { - pub vault_coin_denom: String, - pub base_denom: String, - pub addr: Addr, - pub method: PricingMethod, -} - -#[cw_serde] -pub enum PricingMethod { - PreviewRedeem, -} diff --git a/contracts/oracle-adapter/src/state.rs b/contracts/oracle-adapter/src/state.rs deleted file mode 100644 index 1901a99b8..000000000 --- a/contracts/oracle-adapter/src/state.rs +++ /dev/null @@ -1,11 +0,0 @@ -use cw_storage_plus::{Item, Map}; -use mars_owner::Owner; -use mars_rover::adapters::Oracle; - -use crate::msg::VaultPricingInfo; - -pub const OWNER: Owner = Owner::new("owner"); -pub const ORACLE: Item = Item::new("oracle"); - -/// Map<(Vault Token Denom, Pricing Method)> -pub const VAULT_PRICING_INFO: Map<&str, VaultPricingInfo> = Map::new("vault_coin"); diff --git a/contracts/oracle-adapter/tests/helpers.rs b/contracts/oracle-adapter/tests/helpers.rs deleted file mode 100644 index 39273ba92..000000000 --- a/contracts/oracle-adapter/tests/helpers.rs +++ /dev/null @@ -1,163 +0,0 @@ -use anyhow::Result as AnyResult; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{coin, Addr, Coin, Decimal}; -use cw_multi_test::{AppResponse, BankSudo, BasicApp, ContractWrapper, Executor, SudoMsg}; -use cw_utils::Duration; -use mars_mock_oracle::contract::{ - execute as oracleExecute, instantiate as oracleInstantiate, query as oracleQuery, -}; -use mars_mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; -use mars_mock_vault::contract::{ - execute as vaultExecute, instantiate as vaultInstantiate, query as vaultQuery, - DEFAULT_VAULT_TOKEN_PREFUND, -}; -use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; -use mars_oracle_adapter::contract::{execute, instantiate, query}; -use mars_oracle_adapter::error::ContractError; -use mars_oracle_adapter::msg::{InstantiateMsg, PricingMethod, VaultPricingInfo}; -use mars_rover::adapters::vault::VaultBase; -use mars_rover::adapters::{OracleBase, OracleUnchecked}; - -pub fn mock_vault_info() -> VaultTestInfo { - VaultTestInfo { - vault_coin_denom: "yOSMO_ATOM".to_string(), - lockup: None, - req_denom: "GAMM_LP_12352".to_string(), - pricing_method: PricingMethod::PreviewRedeem, - } -} - -pub fn instantiate_oracle_adapter(app: &mut BasicApp) -> Addr { - let contract = Box::new(ContractWrapper::new(execute, instantiate, query)); - let code_id = app.store_code(contract); - - let oracle = deploy_oracle(app); - let vault_pricing_info = deploy_vault(app, oracle.clone().into(), mock_vault_info()); - starting_vault_deposit(app, &vault_pricing_info); - - let owner = Addr::unchecked("owner"); - app.instantiate_contract( - code_id, - owner.clone(), - &InstantiateMsg { - oracle: oracle.into(), - vault_pricing: vec![vault_pricing_info], - owner: owner.to_string(), - }, - &[], - "mars-oracle-adapter", - None, - ) - .unwrap() -} - -#[cw_serde] -pub struct VaultTestInfo { - pub vault_coin_denom: String, - pub lockup: Option, - pub req_denom: String, - pub pricing_method: PricingMethod, -} - -fn deploy_vault( - app: &mut BasicApp, - oracle: OracleUnchecked, - vault: VaultTestInfo, -) -> VaultPricingInfo { - let contract = ContractWrapper::new(vaultExecute, vaultInstantiate, vaultQuery); - let code_id = app.store_code(Box::new(contract)); - - let addr = app - .instantiate_contract( - code_id, - Addr::unchecked("vault-instantiator"), - &VaultInstantiateMsg { - vault_token_denom: vault.clone().vault_coin_denom, - lockup: vault.lockup, - base_token_denom: vault.clone().req_denom, - oracle, - }, - &[], - "mock-vault", - None, - ) - .unwrap(); - - let vault_pricing_info = VaultPricingInfo { - vault_coin_denom: vault.vault_coin_denom, - addr, - method: vault.pricing_method, - base_denom: vault.req_denom, - }; - fund_vault(app, &vault_pricing_info); - vault_pricing_info -} - -/// cw-multi-test does not yet have the ability to mint sdk coins. For this reason, -/// this contract expects to be pre-funded with vault tokens and it will simulate the mint. -fn fund_vault(app: &mut BasicApp, vault: &VaultPricingInfo) { - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: vault.addr.to_string(), - amount: vec![Coin { - denom: vault.vault_coin_denom.clone(), - amount: DEFAULT_VAULT_TOKEN_PREFUND, - }], - })) - .unwrap(); -} - -fn starting_vault_deposit(app: &mut BasicApp, vault_info: &VaultPricingInfo) { - let user = Addr::unchecked("some_user_xyz"); - let coin_to_deposit = coin(120_042, "GAMM_LP_12352"); - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: user.to_string(), - amount: vec![coin_to_deposit.clone()], - })) - .unwrap(); - - let vault = VaultBase::new(vault_info.addr.clone()); - let deposit_msg = vault.deposit_msg(&coin_to_deposit).unwrap(); - app.execute(user, deposit_msg).unwrap(); -} - -fn deploy_oracle(app: &mut BasicApp) -> OracleBase { - let contract = ContractWrapper::new(oracleExecute, oracleInstantiate, oracleQuery); - let code_id = app.store_code(Box::new(contract)); - - let addr = app - .instantiate_contract( - code_id, - Addr::unchecked("oracle_contract_owner"), - &OracleInstantiateMsg { - prices: vec![ - CoinPrice { - denom: "uosmo".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - }, - CoinPrice { - denom: "uatom".to_string(), - price: Decimal::from_atomics(10u128, 1).unwrap(), - }, - CoinPrice { - denom: "GAMM_LP_12352".to_string(), - price: Decimal::from_atomics(8745u128, 2).unwrap(), - }, - ], - }, - &[], - "mock-oracle", - None, - ) - .unwrap(); - OracleBase::new(addr) -} - -pub fn assert_err(res: AnyResult, err: ContractError) { - match res { - Ok(_) => panic!("Result was not an error"), - Err(generic_err) => { - let contract_err: ContractError = generic_err.downcast().unwrap(); - assert_eq!(contract_err, err); - } - } -} diff --git a/contracts/oracle-adapter/tests/test_query_price.rs b/contracts/oracle-adapter/tests/test_query_price.rs deleted file mode 100644 index ce9c784c6..000000000 --- a/contracts/oracle-adapter/tests/test_query_price.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::ops::{Div, Mul}; - -use cosmwasm_std::{Empty, Uint128}; -use cosmwasm_vault_standard::VaultStandardQueryMsg::{PreviewRedeem, TotalVaultTokenSupply}; -use cw_multi_test::App; - -use mars_oracle_adapter::msg::{ConfigResponse, QueryMsg, VaultPricingInfo}; -use mars_outpost::oracle::PriceResponse; -use mars_rover::traits::IntoDecimal; - -use crate::helpers::{instantiate_oracle_adapter, mock_vault_info}; - -pub mod helpers; - -#[test] -fn test_non_vault_coin_priced() { - let mut app = App::default(); - let contract_addr = instantiate_oracle_adapter(&mut app); - - let config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - let uosmo_oracle_res: PriceResponse = app - .wrap() - .query_wasm_smart( - config.oracle.address().to_string(), - &QueryMsg::Price { - denom: "uosmo".to_string(), - }, - ) - .unwrap(); - - let res: PriceResponse = app - .wrap() - .query_wasm_smart( - contract_addr.to_string(), - &QueryMsg::Price { - denom: "uosmo".to_string(), - }, - ) - .unwrap(); - - assert_eq!(res.price, uosmo_oracle_res.price); -} - -#[test] -fn test_vault_coin_preview_redeem() { - let mut app = App::default(); - let contract_addr = instantiate_oracle_adapter(&mut app); - let vault_info = mock_vault_info(); - - let vault_info: VaultPricingInfo = app - .wrap() - .query_wasm_smart( - contract_addr.to_string(), - &QueryMsg::PricingInfo { - denom: vault_info.vault_coin_denom, - }, - ) - .unwrap(); - - let vault_token_supply: Uint128 = app - .wrap() - .query_wasm_smart(vault_info.addr.clone(), &TotalVaultTokenSupply:: {}) - .unwrap(); - - let total_lp_tokens: Uint128 = app - .wrap() - .query_wasm_smart( - vault_info.addr, - &PreviewRedeem:: { - amount: vault_token_supply, - }, - ) - .unwrap(); - - let config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - let lp_token_oracle_res: PriceResponse = app - .wrap() - .query_wasm_smart( - config.oracle.address().to_string(), - &QueryMsg::Price { - denom: vault_info.base_denom.clone(), - }, - ) - .unwrap(); - - let total_value_of_vault = total_lp_tokens - .to_dec() - .unwrap() - .mul(lp_token_oracle_res.price); - - let price_per_vault_coin = total_value_of_vault.div(vault_token_supply); - - let oracle_adapter_res: PriceResponse = app - .wrap() - .query_wasm_smart( - contract_addr.to_string(), - &QueryMsg::Price { - denom: vault_info.vault_coin_denom, - }, - ) - .unwrap(); - - // vault token price = total lp tokens in vault * price of lp token / total vault tokens issued - // This formula can't be used in production because the first multiplication results in an - // integer that exceeds the memory allocated to u128's. But for this test it's a good check - // on our current formula where we use: - // Decimal::from_ratio(total_underlying, vault_coin_supply) * price of lp token - // This method does not cause an overflow given Decimal::from_ratio casts to u256 - assert_eq!(oracle_adapter_res.price, price_per_vault_coin) -} diff --git a/contracts/oracle-adapter/tests/test_update_admin.rs b/contracts/oracle-adapter/tests/test_update_admin.rs deleted file mode 100644 index 1f19710a8..000000000 --- a/contracts/oracle-adapter/tests/test_update_admin.rs +++ /dev/null @@ -1,174 +0,0 @@ -use cosmwasm_std::Addr; -use cw_multi_test::{App, Executor}; -use mars_owner::OwnerUpdate; - -use mars_oracle_adapter::msg::{ConfigResponse, ExecuteMsg, QueryMsg}; - -use crate::helpers::instantiate_oracle_adapter; - -pub mod helpers; - -#[test] -fn test_initialized_state() { - let mut app = App::default(); - let contract_addr = instantiate_oracle_adapter(&mut app); - let original_config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - assert!(original_config.owner.is_some()); - assert!(original_config.proposed_new_owner.is_none()); -} - -#[test] -fn test_propose_new_owner() { - let mut app = App::default(); - let contract_addr = instantiate_oracle_adapter(&mut app); - let original_config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - let new_owner = "new_owner".to_string(); - - // only owner can propose new owners - let bad_guy = Addr::unchecked("bad_guy"); - app.execute_contract( - bad_guy.clone(), - contract_addr.clone(), - &ExecuteMsg::UpdateOwner(OwnerUpdate::ProposeNewOwner { - proposed: bad_guy.to_string(), - }), - &[], - ) - .unwrap_err(); - - app.execute_contract( - Addr::unchecked(original_config.owner.clone().unwrap()), - contract_addr.clone(), - &ExecuteMsg::UpdateOwner(OwnerUpdate::ProposeNewOwner { - proposed: new_owner.clone(), - }), - &[], - ) - .unwrap(); - - let new_config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(new_config.owner, original_config.owner); - assert_ne!( - new_config.proposed_new_owner, - original_config.proposed_new_owner - ); - assert_eq!(new_config.proposed_new_owner, Some(new_owner)); -} - -#[test] -fn test_clear_proposed() { - let mut app = App::default(); - let contract_addr = instantiate_oracle_adapter(&mut app); - let original_config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - let new_owner = "new_owner".to_string(); - - app.execute_contract( - Addr::unchecked(original_config.owner.clone().unwrap()), - contract_addr.clone(), - &ExecuteMsg::UpdateOwner(OwnerUpdate::ProposeNewOwner { - proposed: new_owner.clone(), - }), - &[], - ) - .unwrap(); - - let interim_config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(interim_config.proposed_new_owner, Some(new_owner)); - - // only owner can clear - let bad_guy = Addr::unchecked("bad_guy"); - app.execute_contract( - bad_guy, - contract_addr.clone(), - &ExecuteMsg::UpdateOwner(OwnerUpdate::ClearProposed), - &[], - ) - .unwrap_err(); - - app.execute_contract( - Addr::unchecked(original_config.owner.clone().unwrap()), - contract_addr.clone(), - &ExecuteMsg::UpdateOwner(OwnerUpdate::ClearProposed), - &[], - ) - .unwrap(); - - let latest_config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(latest_config.owner, original_config.owner); - assert_ne!( - latest_config.proposed_new_owner, - interim_config.proposed_new_owner - ); - assert_eq!(latest_config.proposed_new_owner, None); -} - -#[test] -fn test_accept_owner_role() { - let mut app = App::default(); - let contract_addr = instantiate_oracle_adapter(&mut app); - let original_config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - let new_owner = "new_owner".to_string(); - - app.execute_contract( - Addr::unchecked(original_config.owner.clone().unwrap()), - contract_addr.clone(), - &ExecuteMsg::UpdateOwner(OwnerUpdate::ProposeNewOwner { - proposed: new_owner.clone(), - }), - &[], - ) - .unwrap(); - - // Only proposed owner can accept - app.execute_contract( - Addr::unchecked(original_config.owner.unwrap()), - contract_addr.clone(), - &ExecuteMsg::UpdateOwner(OwnerUpdate::AcceptProposed), - &[], - ) - .unwrap_err(); - - app.execute_contract( - Addr::unchecked(new_owner.clone()), - contract_addr.clone(), - &ExecuteMsg::UpdateOwner(OwnerUpdate::AcceptProposed), - &[], - ) - .unwrap(); - - let new_config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(new_config.owner.unwrap(), new_owner); - assert_eq!(new_config.proposed_new_owner, None); -} diff --git a/contracts/oracle-adapter/tests/test_update_config.rs b/contracts/oracle-adapter/tests/test_update_config.rs deleted file mode 100644 index 1c91b54cb..000000000 --- a/contracts/oracle-adapter/tests/test_update_config.rs +++ /dev/null @@ -1,146 +0,0 @@ -use cosmwasm_std::Addr; -use cw_multi_test::{App, Executor}; -use mars_owner::OwnerError::NotOwner; - -use mars_oracle_adapter::error::ContractError::OwnerError; -use mars_oracle_adapter::msg::{ - ConfigResponse, ConfigUpdates, ExecuteMsg, QueryMsg, VaultPricingInfo, -}; -use mars_rover::adapters::{OracleBase, OracleUnchecked}; - -use crate::helpers::{assert_err, instantiate_oracle_adapter}; - -pub mod helpers; - -#[test] -fn test_only_owner_can_update_config() { - let mut app = App::default(); - let contract_addr = instantiate_oracle_adapter(&mut app); - - let bad_guy = Addr::unchecked("bad_guy"); - let res = app.execute_contract( - bad_guy, - contract_addr, - &ExecuteMsg::UpdateConfig { - new_config: Default::default(), - }, - &[], - ); - - assert_err(res, OwnerError(NotOwner {})); -} - -#[test] -fn test_update_config_works_with_full_config() { - let mut app = App::default(); - let contract_addr = instantiate_oracle_adapter(&mut app); - let original_config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - let new_oracle = OracleUnchecked::new("new_oracle".to_string()); - let new_vault_pricing = vec![]; - - app.execute_contract( - Addr::unchecked(original_config.owner.clone().unwrap()), - contract_addr.clone(), - &ExecuteMsg::UpdateConfig { - new_config: ConfigUpdates { - oracle: Some(new_oracle), - vault_pricing: Some(new_vault_pricing), - }, - }, - &[], - ) - .unwrap(); - - let new_config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(new_config.owner, original_config.owner); - assert_eq!( - new_config.proposed_new_owner, - original_config.proposed_new_owner - ); - - assert_ne!(new_config.oracle, original_config.oracle); - assert_eq!( - new_config.oracle, - OracleBase::new(Addr::unchecked("new_oracle".to_string())) - ); - - let pricing_infos: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.to_string(), - &QueryMsg::AllPricingInfo { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - assert_eq!(pricing_infos.len(), 0); -} - -#[test] -fn test_update_config_does_nothing_when_nothing_is_passed() { - let mut app = App::default(); - let contract_addr = instantiate_oracle_adapter(&mut app); - let original_config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - let original_pricing_infos: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.to_string(), - &QueryMsg::AllPricingInfo { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - app.execute_contract( - Addr::unchecked(original_config.owner.clone().unwrap()), - contract_addr.clone(), - &ExecuteMsg::UpdateConfig { - new_config: ConfigUpdates { - oracle: None, - vault_pricing: None, - }, - }, - &[], - ) - .unwrap(); - - let new_config: ConfigResponse = app - .wrap() - .query_wasm_smart(contract_addr.to_string(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(new_config.owner, original_config.owner); - assert_eq!( - new_config.proposed_new_owner, - original_config.proposed_new_owner - ); - assert_eq!(new_config.oracle, original_config.oracle); - - let new_pricing_infos: Vec = app - .wrap() - .query_wasm_smart( - contract_addr.to_string(), - &QueryMsg::AllPricingInfo { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - assert_eq!(new_pricing_infos, original_pricing_infos); -} diff --git a/contracts/swapper/mock/Cargo.toml b/contracts/swapper/mock/Cargo.toml index 2abac3c99..316414a14 100644 --- a/contracts/swapper/mock/Cargo.toml +++ b/contracts/swapper/mock/Cargo.toml @@ -20,7 +20,6 @@ library = [] [dependencies] cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } -mars-swapper-base = { workspace = true } mars-rover = { workspace = true } thiserror = { workspace = true } diff --git a/contracts/zapper/base/Cargo.toml b/contracts/zapper/base/Cargo.toml index 3f0929c65..44e558d63 100644 --- a/contracts/zapper/base/Cargo.toml +++ b/contracts/zapper/base/Cargo.toml @@ -21,7 +21,6 @@ library = [] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-utils = { workspace = true } -cw-storage-plus = { workspace = true } schemars = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } diff --git a/contracts/zapper/mock/src/query.rs b/contracts/zapper/mock/src/query.rs index 8d6d62967..fcbf6a377 100644 --- a/contracts/zapper/mock/src/query.rs +++ b/contracts/zapper/mock/src/query.rs @@ -29,8 +29,7 @@ pub fn estimate_provide_liquidity( let oracle = ORACLE.load(deps.storage)?; let total_underlying_value = oracle.query_total_value(&deps.querier, &coins)?; let given_value = oracle.query_total_value(&deps.querier, &coins_in)?; - total_supply - .checked_multiply_ratio(given_value.atomics(), total_underlying_value.atomics())? + total_supply.checked_multiply_ratio(given_value, total_underlying_value)? }; Ok(lp_tokens_estimate) } diff --git a/contracts/zapper/mock/src/state.rs b/contracts/zapper/mock/src/state.rs index abee1a7cb..522b3359a 100644 --- a/contracts/zapper/mock/src/state.rs +++ b/contracts/zapper/mock/src/state.rs @@ -1,7 +1,7 @@ use cosmwasm_std::Uint128; use cw_storage_plus::{Item, Map}; -use mars_rover::adapters::Oracle; +use mars_rover::adapters::oracle::Oracle; pub const ORACLE: Item = Item::new("oracle"); diff --git a/packages/health/Cargo.toml b/packages/health/Cargo.toml index 53271100d..359fbde6e 100644 --- a/packages/health/Cargo.toml +++ b/packages/health/Cargo.toml @@ -17,5 +17,5 @@ doctest = false backtraces = ["cosmwasm-std/backtraces"] [dependencies] -mars-outpost = { workspace = true } -cosmwasm-std = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } diff --git a/packages/health/src/health.rs b/packages/health/src/health.rs index dd3d667d1..1f70f985b 100644 --- a/packages/health/src/health.rs +++ b/packages/health/src/health.rs @@ -1,28 +1,18 @@ -use crate::query::MarsQuerier; -use cosmwasm_std::{Addr, Coin, Decimal, QuerierWrapper, StdError, StdResult, Uint128}; -use mars_outpost::{math::divide_decimal_by_decimal, red_bank::Market}; -use std::{collections::HashMap, fmt}; +use cosmwasm_schema::cw_serde; +use std::fmt; -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct Position { - pub denom: String, - pub price: Decimal, - pub collateral_amount: Decimal, - pub debt_amount: Decimal, - pub max_ltv: Decimal, - pub liquidation_threshold: Decimal, -} +use cosmwasm_std::{Decimal, Uint128}; -#[derive(Default, Debug, PartialEq, Eq)] +#[cw_serde] pub struct Health { /// The sum of the value of all debts - pub total_debt_value: Decimal, + pub total_debt_value: Uint128, /// The sum of the value of all collaterals - pub total_collateral_value: Decimal, + pub total_collateral_value: Uint128, /// The sum of the value of all colletarals adjusted by their Max LTV - pub max_ltv_adjusted_collateral: Decimal, + pub max_ltv_adjusted_collateral: Uint128, /// The sum of the value of all colletarals adjusted by their Liquidation Threshold - pub liquidation_threshold_adjusted_collateral: Decimal, + pub liquidation_threshold_adjusted_collateral: Uint128, /// The sum of the value of all collaterals multiplied by their max LTV, over the total value of debt pub max_ltv_health_factor: Option, /// The sum of the value of all collaterals multiplied by their liquidation threshold over the total value of debt @@ -45,50 +35,6 @@ impl fmt::Display for Health { } impl Health { - /// Compute the health from coins (collateral and debt) - pub fn compute_health_from_coins( - querier: &QuerierWrapper, - oracle_addr: &Addr, - red_bank_addr: &Addr, - collateral: &[Coin], - debt: &[Coin], - ) -> StdResult { - let querier = MarsQuerier::new(querier, oracle_addr, red_bank_addr); - let positions = Self::positions_from_coins(&querier, collateral, debt)?; - - Self::compute_health(&positions.into_values().collect::>()) - } - - /// Compute the health for a Position - pub fn compute_health(positions: &[Position]) -> StdResult { - let mut health = positions.iter().try_fold::<_, _, StdResult>( - Health::default(), - |mut h, p| { - let collateral_value = p.collateral_amount.checked_mul(p.price)?; - h.total_debt_value += p.debt_amount.checked_mul(p.price)?; - h.total_collateral_value += collateral_value; - h.max_ltv_adjusted_collateral += collateral_value.checked_mul(p.max_ltv)?; - h.liquidation_threshold_adjusted_collateral += - collateral_value.checked_mul(p.liquidation_threshold)?; - Ok(h) - }, - )?; - - // If there aren't any debts a health factor can't be computed (divide by zero) - if !health.total_debt_value.is_zero() { - health.max_ltv_health_factor = Some(divide_decimal_by_decimal( - health.max_ltv_adjusted_collateral, - health.total_debt_value, - )?); - health.liquidation_health_factor = Some(divide_decimal_by_decimal( - health.liquidation_threshold_adjusted_collateral, - health.total_debt_value, - )?); - } - - Ok(health) - } - #[inline] pub fn is_liquidatable(&self) -> bool { self.liquidation_health_factor @@ -100,82 +46,31 @@ impl Health { self.max_ltv_health_factor .map_or(false, |hf| hf < Decimal::one()) } +} - /// Convert a collection of coins (Collateral and debts) to a map of `Position` - pub fn positions_from_coins( - querier: &MarsQuerier, - collateral: &[Coin], - debt: &[Coin], - ) -> StdResult> { - let mut positions: HashMap = HashMap::new(); - - collateral.iter().try_for_each(|c| -> StdResult<_> { - match positions.get_mut(&c.denom) { - Some(p) => { - p.collateral_amount += to_decimal(c.amount)?; - } - None => { - let Market { - max_loan_to_value, - liquidation_threshold, - .. - } = querier.query_market(&c.denom)?; - - positions.insert( - c.denom.clone(), - Position { - denom: c.denom.clone(), - collateral_amount: to_decimal(c.amount)?, - debt_amount: Decimal::zero(), - price: querier.query_price(&c.denom)?, - max_ltv: max_loan_to_value, - liquidation_threshold, - }, - ); - } - } - Ok(()) - })?; - - debt.iter().try_for_each(|d| -> StdResult<_> { - match positions.get_mut(&d.denom) { - Some(p) => { - p.debt_amount += to_decimal(d.amount)?; - } - None => { - let Market { - max_loan_to_value, - liquidation_threshold, - .. - } = querier.query_market(&d.denom)?; - - positions.insert( - d.denom.clone(), - Position { - denom: d.denom.clone(), - collateral_amount: Decimal::zero(), - debt_amount: to_decimal(d.amount)?, - price: querier.query_price(&d.denom)?, - max_ltv: max_loan_to_value, - liquidation_threshold, - }, - ); - } - } - Ok(()) - })?; - Ok(positions) - } +#[cw_serde] +pub struct HealthResponse { + pub total_debt_value: Uint128, + pub total_collateral_value: Uint128, + pub max_ltv_adjusted_collateral: Uint128, + pub liquidation_threshold_adjusted_collateral: Uint128, + pub max_ltv_health_factor: Option, + pub liquidation_health_factor: Option, + pub liquidatable: bool, + pub above_max_ltv: bool, } -/// helper function to convert `Uint128` to `Decimal`. -/// Maps `CheckFromRatioError` to `StdError` -pub fn to_decimal(x: Uint128) -> StdResult { - Decimal::checked_from_ratio(x, 1u128).map_err(|_e| StdError::Overflow { - source: cosmwasm_std::OverflowError { - operation: cosmwasm_std::OverflowOperation::Mul, - operand1: x.to_string(), - operand2: "".to_string(), - }, - }) +impl From for HealthResponse { + fn from(h: Health) -> Self { + Self { + total_debt_value: h.total_debt_value, + total_collateral_value: h.total_collateral_value, + max_ltv_adjusted_collateral: h.max_ltv_adjusted_collateral, + liquidation_threshold_adjusted_collateral: h.liquidation_threshold_adjusted_collateral, + max_ltv_health_factor: h.max_ltv_health_factor, + liquidation_health_factor: h.liquidation_health_factor, + liquidatable: h.is_liquidatable(), + above_max_ltv: h.is_above_max_ltv(), + } + } } diff --git a/packages/health/src/lib.rs b/packages/health/src/lib.rs index 3073fc0dd..120fd3dda 100644 --- a/packages/health/src/lib.rs +++ b/packages/health/src/lib.rs @@ -1,2 +1,3 @@ -pub mod health; -pub mod query; +mod health; + +pub use self::health::*; diff --git a/packages/health/src/query.rs b/packages/health/src/query.rs deleted file mode 100644 index d651ea4d2..000000000 --- a/packages/health/src/query.rs +++ /dev/null @@ -1,42 +0,0 @@ -use cosmwasm_std::{Addr, Decimal, QuerierWrapper, StdResult}; -use mars_outpost::oracle::{self, PriceResponse}; -use mars_outpost::red_bank::{self, Market}; - -pub struct MarsQuerier<'a> { - querier: &'a QuerierWrapper<'a>, - oracle_addr: &'a Addr, - red_bank_addr: &'a Addr, -} - -impl<'a> MarsQuerier<'a> { - pub fn new( - querier: &'a QuerierWrapper, - oracle_addr: &'a Addr, - red_bank_addr: &'a Addr, - ) -> Self { - MarsQuerier { - querier, - oracle_addr, - red_bank_addr, - } - } - - pub fn query_market(&self, denom: &str) -> StdResult { - self.querier.query_wasm_smart( - self.red_bank_addr, - &red_bank::QueryMsg::Market { - denom: denom.to_string(), - }, - ) - } - - pub fn query_price(&self, denom: &str) -> StdResult { - let PriceResponse { price, .. } = self.querier.query_wasm_smart( - self.oracle_addr, - &oracle::QueryMsg::Price { - denom: denom.to_string(), - }, - )?; - Ok(price) - } -} diff --git a/packages/math/Cargo.toml b/packages/math/Cargo.toml new file mode 100644 index 000000000..1a21c21ba --- /dev/null +++ b/packages/math/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "mars-math" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } + +[lib] +doctest = false + +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] + +[dependencies] +cosmwasm-std = { workspace = true } +thiserror = { workspace = true } diff --git a/packages/math/src/fraction.rs b/packages/math/src/fraction.rs new file mode 100644 index 000000000..62694adb1 --- /dev/null +++ b/packages/math/src/fraction.rs @@ -0,0 +1,100 @@ +use cosmwasm_std::{ConversionOverflowError, DivideByZeroError, Fraction, OverflowError, Uint128}; +use thiserror::Error; + +// TODO: Delete when merged: https://github.com/CosmWasm/cosmwasm/pull/1566 + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum CheckedMultiplyFractionError { + #[error("{0}")] + DivideByZero(#[from] DivideByZeroError), + + #[error("{0}")] + ConversionOverflow(#[from] ConversionOverflowError), + + #[error("{0}")] + Overflow(#[from] OverflowError), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Fractional(pub T, pub T); + +impl + PartialEq> Fraction for Fractional { + fn numerator(&self) -> T { + self.0 + } + fn denominator(&self) -> T { + self.1 + } + fn inv(&self) -> Option { + if self.numerator() == 0u8.into() { + None + } else { + Some(Fractional(self.1, self.0)) + } + } +} + +pub trait FractionMath { + fn checked_mul_floor, T: Into>( + self, + rhs: F, + ) -> Result + where + Self: Sized; + + fn checked_mul_ceil + Clone, T: Into>( + self, + rhs: F, + ) -> Result + where + Self: Sized; + + fn checked_div_floor, T: Into>( + self, + rhs: F, + ) -> Result + where + Self: Sized; +} + +impl FractionMath for Uint128 { + fn checked_mul_floor, T: Into>( + self, + rhs: F, + ) -> Result { + let divisor = rhs.denominator().into(); + let res = self + .full_mul(rhs.numerator().into()) + .checked_div(divisor.into())?; + Ok(res.try_into()?) + } + + fn checked_mul_ceil + Clone, T: Into>( + self, + rhs: F, + ) -> Result { + let floor_result = self.checked_mul_floor(rhs.clone())?; + let divisor = rhs.denominator().into(); + let remainder = self + .full_mul(rhs.numerator().into()) + .checked_rem(divisor.into())?; + if !remainder.is_zero() { + Ok(Uint128::one().checked_add(floor_result)?) + } else { + Ok(floor_result) + } + } + + // Note: Should not use .inv() on decimal and then just use self.checked_mul_floor(inverted). + // Inverting a decimal does large number math and a loss of precision causing off by one bugs. + fn checked_div_floor, T: Into>( + self, + rhs: F, + ) -> Result { + let divisor = rhs.numerator().into(); + let res = self + .full_mul(rhs.denominator().into()) + .checked_div(divisor.into())?; + Ok(res.try_into()?) + } +} diff --git a/packages/math/src/lib.rs b/packages/math/src/lib.rs new file mode 100644 index 000000000..5108f1928 --- /dev/null +++ b/packages/math/src/lib.rs @@ -0,0 +1,3 @@ +mod fraction; + +pub use self::fraction::*; diff --git a/packages/math/tests/test_div_floor.rs b/packages/math/tests/test_div_floor.rs new file mode 100644 index 000000000..b24153617 --- /dev/null +++ b/packages/math/tests/test_div_floor.rs @@ -0,0 +1,75 @@ +use cosmwasm_std::{ConversionOverflowError, Decimal, DivideByZeroError, Uint128}; + +use mars_math::CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}; +use mars_math::{FractionMath, Fractional}; + +#[test] +fn div_floor_raises_with_zero() { + let fraction = Fractional(Uint128::zero(), Uint128::new(21)); + assert_eq!( + Uint128::new(123456).checked_div_floor(fraction), + Err(DivideByZero(DivideByZeroError { + operand: "2592576".to_string() + })), + ); +} + +#[test] +fn div_floor_does_nothing_with_one() { + let fraction = Fractional(Uint128::one(), Uint128::one()); + let res = Uint128::new(123456).checked_div_floor(fraction).unwrap(); + assert_eq!(Uint128::new(123456), res) +} + +#[test] +fn div_floor_rounds_down_with_normal_case() { + let fraction = Fractional(5u128, 21u128); + let res = Uint128::new(123456).checked_div_floor(fraction).unwrap(); // 518515.2 + assert_eq!(Uint128::new(518515), res) +} + +#[test] +fn div_floor_does_not_round_on_even_divide() { + let fraction = Fractional(5u128, 2u128); + let res = Uint128::new(25).checked_div_floor(fraction).unwrap(); + + assert_eq!(Uint128::new(10), res) +} + +#[test] +fn div_floor_works_when_operation_temporarily_takes_above_max() { + let fraction = Fractional(21u128, 8u128); + let res = Uint128::MAX.checked_div_floor(fraction).unwrap(); // 129_631_377_874_643_224_176_523_659_974_006_937_697.1428 + assert_eq!( + Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_697), + res + ) +} + +#[test] +fn div_floor_works_with_decimal() { + let decimal = Decimal::from_ratio(21u128, 8u128); + let res = Uint128::new(123456).checked_div_floor(decimal).unwrap(); // 47030.857 + assert_eq!(Uint128::new(47030), res) +} + +#[test] +fn div_floor_works_with_decimal_evenly() { + let res = Uint128::new(60) + .checked_div_floor(Decimal::from_atomics(6u128, 0).unwrap()) + .unwrap(); + assert_eq!(res, Uint128::new(10)); +} + +#[test] +fn checked_div_floor_does_not_panic_on_overflow() { + let fraction = Fractional(8u128, 21u128); + assert_eq!( + Uint128::MAX.checked_div_floor(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint256", + target_type: "Uint128", + value: "893241213167463466591358344508391555069".to_string() + })), + ); +} diff --git a/packages/math/tests/test_mul_ceil.rs b/packages/math/tests/test_mul_ceil.rs new file mode 100644 index 000000000..032cc3e6e --- /dev/null +++ b/packages/math/tests/test_mul_ceil.rs @@ -0,0 +1,86 @@ +use cosmwasm_std::{ConversionOverflowError, Decimal, DivideByZeroError, Uint128}; +use mars_math::CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}; +use mars_math::{FractionMath, Fractional}; + +#[test] +fn mul_ceil_works_with_zero() { + let fraction = Fractional(Uint128::zero(), Uint128::new(21)); + let res = Uint128::new(123456).checked_mul_ceil(fraction).unwrap(); + assert_eq!(Uint128::zero(), res) +} + +#[test] +fn mul_ceil_does_nothing_with_one() { + let fraction = Fractional(Uint128::one(), Uint128::one()); + let res = Uint128::new(123456).checked_mul_ceil(fraction).unwrap(); + assert_eq!(Uint128::new(123456), res) +} + +#[test] +fn mul_ceil_rounds_up_with_normal_case() { + let fraction = Fractional(8u128, 21u128); + let res = Uint128::new(123456).checked_mul_ceil(fraction).unwrap(); // 47030.857 + assert_eq!(Uint128::new(47031), res) +} + +#[test] +fn mul_ceil_does_not_round_on_even_divide() { + let fraction = Fractional(2u128, 5u128); + let res = Uint128::new(25).checked_mul_ceil(fraction).unwrap(); + assert_eq!(Uint128::new(10), res) +} + +#[test] +fn mul_ceil_works_when_operation_temporarily_takes_above_max() { + let fraction = Fractional(8u128, 21u128); + let res = Uint128::MAX.checked_mul_ceil(fraction).unwrap(); // 129_631_377_874_643_224_176_523_659_974_006_937_697.1428 + assert_eq!( + Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_698), + res + ) +} + +#[test] +fn mul_ceil_works_with_decimal() { + let decimal = Decimal::from_ratio(8u128, 21u128); + let res = Uint128::new(123456).checked_mul_ceil(decimal).unwrap(); // 47030.857 + assert_eq!(Uint128::new(47031), res) +} + +#[test] +#[should_panic(expected = "ConversionOverflowError")] +fn mul_ceil_panics_on_overflow() { + let fraction = Fractional(21u128, 8u128); + Uint128::MAX.checked_mul_ceil(fraction).unwrap(); +} + +#[test] +fn checked_mul_ceil_does_not_panic_on_overflow() { + let fraction = Fractional(21u128, 8u128); + assert_eq!( + Uint128::MAX.checked_mul_ceil(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint256", + target_type: "Uint128", + value: "893241213167463466591358344508391555069".to_string() // raises prior to rounding up + })), + ); +} + +#[test] +#[should_panic(expected = "DivideByZeroError")] +fn mul_ceil_panics_on_zero_div() { + let fraction = Fractional(21u128, 0u128); + Uint128::new(123456).checked_mul_ceil(fraction).unwrap(); +} + +#[test] +fn checked_mul_ceil_does_not_panic_on_zero_div() { + let fraction = Fractional(21u128, 0u128); + assert_eq!( + Uint128::new(123456).checked_mul_ceil(fraction), + Err(DivideByZero(DivideByZeroError { + operand: "2592576".to_string() + })), + ); +} diff --git a/packages/math/tests/test_mul_floor.rs b/packages/math/tests/test_mul_floor.rs new file mode 100644 index 000000000..cf861866b --- /dev/null +++ b/packages/math/tests/test_mul_floor.rs @@ -0,0 +1,74 @@ +use cosmwasm_std::{ConversionOverflowError, Decimal, DivideByZeroError, Uint128}; + +use mars_math::CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}; +use mars_math::{FractionMath, Fractional}; + +#[test] +fn mul_floor_works_with_zero() { + let fraction = Fractional(Uint128::zero(), Uint128::new(21)); + let res = Uint128::new(123456).checked_mul_floor(fraction).unwrap(); + assert_eq!(Uint128::zero(), res) +} + +#[test] +fn mul_floor_does_nothing_with_one() { + let fraction = Fractional(Uint128::one(), Uint128::one()); + let res = Uint128::new(123456).checked_mul_floor(fraction).unwrap(); + assert_eq!(Uint128::new(123456), res) +} + +#[test] +fn mul_floor_rounds_down_with_normal_case() { + let fraction = Fractional(8u128, 21u128); + let res = Uint128::new(123456).checked_mul_floor(fraction).unwrap(); // 47030.857 + assert_eq!(Uint128::new(47030), res) +} + +#[test] +fn mul_floor_does_not_round_on_even_divide() { + let fraction = Fractional(2u128, 5u128); + let res = Uint128::new(25).checked_mul_floor(fraction).unwrap(); + + assert_eq!(Uint128::new(10), res) +} + +#[test] +fn mul_floor_works_when_operation_temporarily_takes_above_max() { + let fraction = Fractional(8u128, 21u128); + let res = Uint128::MAX.checked_mul_floor(fraction).unwrap(); // 129_631_377_874_643_224_176_523_659_974_006_937_697.1428 + assert_eq!( + Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_697), + res + ) +} + +#[test] +fn mul_floor_works_with_decimal() { + let decimal = Decimal::from_ratio(8u128, 21u128); + let res = Uint128::new(123456).checked_mul_floor(decimal).unwrap(); // 47030.857 + assert_eq!(Uint128::new(47030), res) +} + +#[test] +fn checked_mul_floor_does_not_panic_on_overflow() { + let fraction = Fractional(21u128, 8u128); + assert_eq!( + Uint128::MAX.checked_mul_floor(fraction), + Err(ConversionOverflow(ConversionOverflowError { + source_type: "Uint256", + target_type: "Uint128", + value: "893241213167463466591358344508391555069".to_string() + })), + ); +} + +#[test] +fn checked_mul_floor_does_not_panic_on_zero_div() { + let fraction = Fractional(21u128, 0u128); + assert_eq!( + Uint128::new(123456).checked_mul_floor(fraction), + Err(DivideByZero(DivideByZeroError { + operand: "2592576".to_string() + })), + ); +} diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index da3d558b9..5b1422cbf 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -23,8 +23,7 @@ cosmwasm-vault-standard = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } mars-health = { workspace = true } -mars-mock-oracle = { workspace = true } -mars-mock-red-bank = { workspace = true } +mars-math = { workspace = true } mars-outpost = { workspace = true } mars-owner = { workspace = true } schemars = { workspace = true } diff --git a/packages/rover/src/adapters/mod.rs b/packages/rover/src/adapters/mod.rs index ea04f1044..1008d92c5 100644 --- a/packages/rover/src/adapters/mod.rs +++ b/packages/rover/src/adapters/mod.rs @@ -1,10 +1,5 @@ -mod oracle; -mod red_bank; -mod zapper; - +pub mod oracle; +pub mod red_bank; pub mod swap; pub mod vault; - -pub use self::oracle::*; -pub use self::red_bank::*; -pub use self::zapper::*; +pub mod zapper; diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs index d84861d23..4be3d6b84 100644 --- a/packages/rover/src/adapters/oracle.rs +++ b/packages/rover/src/adapters/oracle.rs @@ -1,11 +1,10 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Api, Coin, Decimal, QuerierWrapper, StdResult}; -use mars_outpost::oracle::PriceResponse; +use cosmwasm_std::{Addr, Api, Coin, QuerierWrapper, StdResult, Uint128}; -use mars_mock_oracle::msg::QueryMsg; +use mars_math::FractionMath; +use mars_outpost::oracle::{PriceResponse, QueryMsg}; use crate::error::ContractResult; -use crate::traits::IntoDecimal; #[cw_serde] pub struct OracleBase(T); @@ -45,16 +44,20 @@ impl Oracle { ) } + pub fn query_value(&self, querier: &QuerierWrapper, coin: &Coin) -> ContractResult { + self.query_total_value(querier, &[coin.clone()]) + } + pub fn query_total_value( &self, querier: &QuerierWrapper, coins: &[Coin], - ) -> ContractResult { + ) -> ContractResult { Ok(coins .iter() .map(|coin| { let res = self.query_price(querier, &coin.denom)?; - Ok(res.price.checked_mul(coin.amount.to_dec()?)?) + Ok(coin.amount.checked_mul_floor(res.price)?) }) .collect::>>()? .iter() diff --git a/packages/rover/src/adapters/vault/base.rs b/packages/rover/src/adapters/vault/base.rs index fc633f7ec..4744227c9 100644 --- a/packages/rover/src/adapters/vault/base.rs +++ b/packages/rover/src/adapters/vault/base.rs @@ -1,15 +1,13 @@ -use cosmwasm_vault_standard::extensions::force_unlock::ForceUnlockExecuteMsg::{ - ForceRedeem, ForceWithdrawUnlocking, -}; use std::hash::Hash; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, QuerierWrapper, - QueryRequest, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, + QueryRequest, StdError, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, +}; +use cosmwasm_vault_standard::extensions::force_unlock::ForceUnlockExecuteMsg::{ + ForceRedeem, ForceWithdrawUnlocking, }; -use cw_utils::Duration; - use cosmwasm_vault_standard::extensions::lockup::LockupExecuteMsg::{Unlock, WithdrawUnlocked}; use cosmwasm_vault_standard::extensions::lockup::LockupQueryMsg::LockupDuration; use cosmwasm_vault_standard::extensions::lockup::{LockupQueryMsg, UnlockingPosition}; @@ -17,7 +15,11 @@ use cosmwasm_vault_standard::msg::{ ExtensionExecuteMsg, ExtensionQueryMsg, VaultStandardExecuteMsg, VaultStandardQueryMsg, }; use cosmwasm_vault_standard::VaultInfoResponse; +use cw_utils::Duration; +use mars_math::FractionMath; + +use crate::adapters::oracle::Oracle; use crate::traits::Stringify; pub const VAULT_REQUEST_REPLY_ID: u64 = 10_001; @@ -226,4 +228,27 @@ impl Vault { msg: to_binary(&QueryMsg::TotalVaultTokenSupply {})?, })) } + + pub fn query_value( + &self, + querier: &QuerierWrapper, + oracle: &Oracle, + amount: Uint128, + ) -> StdResult { + let total_supply = self.query_total_vault_coins_issued(querier)?; + if total_supply.is_zero() { + return Ok(Uint128::zero()); + }; + + let total_underlying = self.query_preview_redeem(querier, total_supply)?; + let amount_in_underlying = amount + .checked_multiply_ratio(total_underlying, total_supply) + .map_err(|_| StdError::generic_err("CheckedMultiplyRatioError"))?; + let vault_info = self.query_info(querier)?; + let price_res = oracle.query_price(querier, &vault_info.base_token)?; + let amount_value = amount_in_underlying + .checked_mul_floor(price_res.price) + .map_err(|_| StdError::generic_err("CheckedMultiplyFractionError"))?; + Ok(amount_value) + } } diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 9143ac590..4588826e9 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -2,6 +2,7 @@ use cosmwasm_std::{ CheckedFromRatioError, CheckedMultiplyRatioError, Coin, DecimalRangeExceeded, OverflowError, StdError, Uint128, }; +use mars_math::CheckedMultiplyFractionError; use mars_owner::OwnerError; use thiserror::Error; @@ -32,6 +33,9 @@ pub enum ContractError { #[error("{0}")] CheckedMultiply(#[from] CheckedMultiplyRatioError), + #[error("{0}")] + CheckedMultiplyFraction(#[from] CheckedMultiplyFractionError), + #[error("{0}")] DecimalRangeExceeded(#[from] DecimalRangeExceeded), diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index 9c9897943..38ad76f8e 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -2,6 +2,5 @@ pub mod adapters; pub mod coins; pub mod error; pub mod extensions; -pub mod math; pub mod msg; pub mod traits; diff --git a/packages/rover/src/math/ceil_ratio.rs b/packages/rover/src/math/ceil_ratio.rs deleted file mode 100644 index 88b353d0d..000000000 --- a/packages/rover/src/math/ceil_ratio.rs +++ /dev/null @@ -1,43 +0,0 @@ -use cosmwasm_std::{CheckedMultiplyRatioError, Uint128, Uint256}; - -pub trait CeilRatio { - fn multiply_ratio_ceil( - &self, - numerator: Uint128, - denominator: Uint128, - ) -> Result; -} - -impl CeilRatio for Uint128 { - /// Using `checked_multiply_ratio()` results in a rounding down due to the nature of integer math. - /// This function performs the same math, but rounds up. The is particularly useful in ensuring - /// safety in certain situations (e.g. calculating what an account owes) - fn multiply_ratio_ceil( - &self, - numerator: Uint128, - denominator: Uint128, - ) -> Result { - // Perform the normal multiply ratio. - // Converts to Uint256 to reduce likeliness of overflow errors - let new_numerator = self.full_mul(numerator); - let denom_256 = Uint256::from(denominator); - let mut result = new_numerator - .checked_div(denom_256) - .map_err(|_| CheckedMultiplyRatioError::DivideByZero)?; - - // Check if there's a remainder with that same division. - // If so, round up (by adding one). - if !new_numerator - .checked_rem(denom_256) - .map_err(|_| CheckedMultiplyRatioError::DivideByZero)? - .is_zero() - { - result += Uint256::one(); - } - - match result.try_into() { - Ok(ratio) => Ok(ratio), - Err(_) => Err(CheckedMultiplyRatioError::Overflow), - } - } -} diff --git a/packages/rover/src/math/mod.rs b/packages/rover/src/math/mod.rs deleted file mode 100644 index 958f02d57..000000000 --- a/packages/rover/src/math/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod ceil_ratio; - -pub use ceil_ratio::*; diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index 885167243..b8342c245 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -1,12 +1,13 @@ -use crate::adapters::swap::SwapperUnchecked; -use crate::adapters::vault::VaultConfig; -use crate::adapters::vault::VaultUnchecked; -use crate::adapters::ZapperUnchecked; -use crate::adapters::{OracleUnchecked, RedBankUnchecked}; -use crate::traits::Stringify; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Decimal, Uint128}; +use crate::adapters::oracle::OracleUnchecked; +use crate::adapters::red_bank::RedBankUnchecked; +use crate::adapters::swap::SwapperUnchecked; +use crate::adapters::vault::{VaultConfig, VaultUnchecked}; +use crate::adapters::zapper::ZapperUnchecked; +use crate::traits::Stringify; + #[cw_serde] pub struct InstantiateMsg { /// The address with privileged access to update config diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 3571c6c29..ebbee4c10 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -1,8 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Decimal, Uint128}; -use mars_health::health::Health; - use crate::adapters::vault::{Vault, VaultConfig, VaultPosition, VaultUnchecked}; use crate::traits::Coins; @@ -28,7 +26,7 @@ pub enum QueryMsg { #[returns(Positions)] Positions { account_id: String }, /// The health of the account represented by token - #[returns(HealthResponse)] + #[returns(mars_health::HealthResponse)] Health { account_id: String }, /// Enumerate coin balances for all token positions; start_after accepts (account_id, denom) #[returns(Vec)] @@ -172,30 +170,3 @@ pub struct ConfigResponse { pub swapper: String, pub zapper: String, } - -#[cw_serde] -pub struct HealthResponse { - pub total_debt_value: Decimal, - pub total_collateral_value: Decimal, - pub max_ltv_adjusted_collateral: Decimal, - pub liquidation_threshold_adjusted_collateral: Decimal, - pub max_ltv_health_factor: Option, - pub liquidation_health_factor: Option, - pub liquidatable: bool, - pub above_max_ltv: bool, -} - -impl From for HealthResponse { - fn from(h: Health) -> Self { - Self { - total_debt_value: h.total_debt_value, - total_collateral_value: h.total_collateral_value, - max_ltv_adjusted_collateral: h.max_ltv_adjusted_collateral, - liquidation_threshold_adjusted_collateral: h.liquidation_threshold_adjusted_collateral, - max_ltv_health_factor: h.max_ltv_health_factor, - liquidation_health_factor: h.liquidation_health_factor, - liquidatable: h.is_liquidatable(), - above_max_ltv: h.is_above_max_ltv(), - } - } -} diff --git a/packages/rover/src/msg/zapper.rs b/packages/rover/src/msg/zapper.rs index 7e3d418d7..dbaadcb42 100644 --- a/packages/rover/src/msg/zapper.rs +++ b/packages/rover/src/msg/zapper.rs @@ -1,6 +1,6 @@ // TODO: should be removed when liquidity-helper is finalized and published to crates.io -use crate::adapters::OracleUnchecked; +use crate::adapters::oracle::OracleUnchecked; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Uint128}; diff --git a/packages/rover/src/traits.rs b/packages/rover/src/traits.rs index 0cd8cb1e0..a1af9c7bd 100644 --- a/packages/rover/src/traits.rs +++ b/packages/rover/src/traits.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, Decimal, DecimalRangeExceeded, Uint128}; +use cosmwasm_std::Coin; pub trait Stringify { fn to_string(&self) -> String; @@ -12,32 +12,6 @@ pub trait Coins { fn to_coins(&self) -> Vec; } -pub trait IntoUint128 { - fn uint128(&self) -> Uint128; -} - -impl IntoUint128 for Decimal { - fn uint128(&self) -> Uint128 { - *self * Uint128::new(1) - } -} - -pub trait IntoDecimal { - fn to_dec(&self) -> Result; -} - -impl IntoDecimal for Uint128 { - fn to_dec(&self) -> Result { - Decimal::from_atomics(*self, 0) - } -} - -impl IntoDecimal for u128 { - fn to_dec(&self) -> Result { - Decimal::from_atomics(*self, 0) - } -} - pub trait FallbackStr { fn fallback(&self, fallback: &str) -> String; } diff --git a/packages/rover/tests/test_ceil_ratio.rs b/packages/rover/tests/test_ceil_ratio.rs deleted file mode 100644 index 64abd8b37..000000000 --- a/packages/rover/tests/test_ceil_ratio.rs +++ /dev/null @@ -1,54 +0,0 @@ -use cosmwasm_std::{CheckedMultiplyRatioError, Uint128}; -use mars_rover::math::CeilRatio; - -const MAX_UINT128_SIZE: u128 = 340_282_366_920_938_463_463_374_607_431_768_211_455; - -#[test] -fn test_divide_by_zero() { - let err = Uint128::new(123) - .multiply_ratio_ceil(Uint128::new(12), Uint128::zero()) - .unwrap_err(); - assert_eq!(err, CheckedMultiplyRatioError::DivideByZero) -} - -#[test] -fn test_result_exceeds_128_bit_capacity() { - let err = Uint128::new(MAX_UINT128_SIZE) - .multiply_ratio_ceil(Uint128::new(2), Uint128::new(1)) - .unwrap_err(); - assert_eq!(err, CheckedMultiplyRatioError::Overflow) -} - -#[test] -fn test_works_with_zero() { - let res = Uint128::zero() - .multiply_ratio_ceil(Uint128::new(1), Uint128::new(10)) - .unwrap(); - assert_eq!(res, Uint128::zero()) -} - -#[test] -fn test_works_with_one() { - let res = Uint128::one() - .multiply_ratio_ceil(Uint128::new(1), Uint128::new(10)) - .unwrap(); - assert_eq!(res, Uint128::one()) -} - -#[test] -fn test_not_increment_if_divides_cleanly() { - // 56088 / 123 = 456 - let res = Uint128::new(56088) - .multiply_ratio_ceil(Uint128::new(1), Uint128::new(123)) - .unwrap(); - assert_eq!(res, Uint128::new(456)) -} - -#[test] -fn test_rounds_up() { - // 56000 / 123 = 455.28455284 - let res = Uint128::new(56000) - .multiply_ratio_ceil(Uint128::new(1), Uint128::new(123)) - .unwrap(); - assert_eq!(res, Uint128::new(456)) -} diff --git a/schema.Makefile.toml b/schema.Makefile.toml index ed5994f9c..81e88dbe1 100644 --- a/schema.Makefile.toml +++ b/schema.Makefile.toml @@ -14,7 +14,6 @@ fn main() -> std::io::Result<()> { "mars-credit-manager", "mars-account-nft", "mars-swapper-base", - "mars-oracle-adapter", "mars-zapper-base", "mars-mock-red-bank", "mars-mock-vault", diff --git a/schemas/mars-account-nft/mars-account-nft.json b/schemas/mars-account-nft/mars-account-nft.json index f28fdfaa5..f19db989a 100644 --- a/schemas/mars-account-nft/mars-account-nft.json +++ b/schemas/mars-account-nft/mars-account-nft.json @@ -14,10 +14,10 @@ ], "properties": { "max_value_for_burn": { - "description": "The maximum amount of Debts + Collaterals for an account before burns are disallowed for the NFT. Meant to prevent accidental account deletions.", + "description": "The maximum value of Debts + Collaterals (denominated in base token) for an account before burns are disallowed for the NFT. Meant to prevent accidental account deletions", "allOf": [ { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" } ] }, @@ -36,8 +36,8 @@ }, "additionalProperties": false, "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "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" } } @@ -310,7 +310,7 @@ "max_value_for_burn": { "anyOf": [ { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" }, { "type": "null" @@ -326,10 +326,6 @@ }, "additionalProperties": false }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, "Expiration": { "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", "oneOf": [ @@ -385,6 +381,10 @@ } ] }, + "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" @@ -1201,7 +1201,7 @@ ], "properties": { "max_value_for_burn": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" }, "proposed_new_minter": { "type": [ @@ -1212,8 +1212,8 @@ }, "additionalProperties": false, "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" } } diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index eed7a29d1..a32842cb8 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -2260,10 +2260,10 @@ ] }, "liquidation_threshold_adjusted_collateral": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" }, "max_ltv_adjusted_collateral": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" }, "max_ltv_health_factor": { "anyOf": [ @@ -2276,10 +2276,10 @@ ] }, "total_collateral_value": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" }, "total_debt_value": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" } }, "additionalProperties": false, @@ -2287,6 +2287,10 @@ "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" } } }, diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index ba2ddbae6..98be73633 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -71,10 +71,10 @@ ] }, "liquidation_threshold_adjusted_collateral": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" }, "max_ltv_adjusted_collateral": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" }, "max_ltv_health_factor": { "anyOf": [ @@ -87,13 +87,17 @@ ] }, "total_collateral_value": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" }, "total_debt_value": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" } }, "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" } } }, @@ -964,10 +968,10 @@ ] }, "liquidation_threshold_adjusted_collateral": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" }, "max_ltv_adjusted_collateral": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" }, "max_ltv_health_factor": { "anyOf": [ @@ -980,10 +984,10 @@ ] }, "total_collateral_value": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" }, "total_debt_value": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" } }, "additionalProperties": false, @@ -991,6 +995,10 @@ "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" } } }, diff --git a/schemas/mars-oracle-adapter/mars-oracle-adapter.json b/schemas/mars-oracle-adapter/mars-oracle-adapter.json deleted file mode 100644 index a6d45da3f..000000000 --- a/schemas/mars-oracle-adapter/mars-oracle-adapter.json +++ /dev/null @@ -1,455 +0,0 @@ -{ - "contract_name": "mars-oracle-adapter", - "contract_version": "1.0.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "oracle", - "owner", - "vault_pricing" - ], - "properties": { - "oracle": { - "$ref": "#/definitions/OracleBase_for_String" - }, - "owner": { - "type": "string" - }, - "vault_pricing": { - "type": "array", - "items": { - "$ref": "#/definitions/VaultPricingInfo" - } - } - }, - "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" - }, - "OracleBase_for_String": { - "type": "string" - }, - "PricingMethod": { - "type": "string", - "enum": [ - "preview_redeem" - ] - }, - "VaultPricingInfo": { - "type": "object", - "required": [ - "addr", - "base_denom", - "method", - "vault_coin_denom" - ], - "properties": { - "addr": { - "$ref": "#/definitions/Addr" - }, - "base_denom": { - "type": "string" - }, - "method": { - "$ref": "#/definitions/PricingMethod" - }, - "vault_coin_denom": { - "type": "string" - } - }, - "additionalProperties": false - } - } - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "required": [ - "new_config" - ], - "properties": { - "new_config": { - "$ref": "#/definitions/ConfigUpdates" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "update_owner" - ], - "properties": { - "update_owner": { - "$ref": "#/definitions/OwnerUpdate" - } - }, - "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" - }, - "ConfigUpdates": { - "type": "object", - "properties": { - "oracle": { - "anyOf": [ - { - "$ref": "#/definitions/OracleBase_for_String" - }, - { - "type": "null" - } - ] - }, - "vault_pricing": { - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/VaultPricingInfo" - } - } - }, - "additionalProperties": false - }, - "OracleBase_for_String": { - "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" - ] - } - ] - }, - "PricingMethod": { - "type": "string", - "enum": [ - "preview_redeem" - ] - }, - "VaultPricingInfo": { - "type": "object", - "required": [ - "addr", - "base_denom", - "method", - "vault_coin_denom" - ], - "properties": { - "addr": { - "$ref": "#/definitions/Addr" - }, - "base_denom": { - "type": "string" - }, - "method": { - "$ref": "#/definitions/PricingMethod" - }, - "vault_coin_denom": { - "type": "string" - } - }, - "additionalProperties": false - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "description": "If denom is vault coin, will retrieve priceable underlying before querying oracle", - "type": "object", - "required": [ - "price" - ], - "properties": { - "price": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "pricing_info" - ], - "properties": { - "pricing_info": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "all_pricing_info" - ], - "properties": { - "all_pricing_info": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "migrate": null, - "sudo": null, - "responses": { - "all_pricing_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_VaultPricingInfo", - "type": "array", - "items": { - "$ref": "#/definitions/VaultPricingInfo" - }, - "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" - }, - "PricingMethod": { - "type": "string", - "enum": [ - "preview_redeem" - ] - }, - "VaultPricingInfo": { - "type": "object", - "required": [ - "addr", - "base_denom", - "method", - "vault_coin_denom" - ], - "properties": { - "addr": { - "$ref": "#/definitions/Addr" - }, - "base_denom": { - "type": "string" - }, - "method": { - "$ref": "#/definitions/PricingMethod" - }, - "vault_coin_denom": { - "type": "string" - } - }, - "additionalProperties": false - } - } - }, - "config": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ConfigResponse", - "type": "object", - "required": [ - "oracle" - ], - "properties": { - "oracle": { - "$ref": "#/definitions/OracleBase_for_Addr" - }, - "owner": { - "type": [ - "string", - "null" - ] - }, - "proposed_new_owner": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "OracleBase_for_Addr": { - "$ref": "#/definitions/Addr" - } - } - }, - "price": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PriceResponse", - "type": "object", - "required": [ - "denom", - "price" - ], - "properties": { - "denom": { - "type": "string" - }, - "price": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - } - } - }, - "pricing_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VaultPricingInfo", - "type": "object", - "required": [ - "addr", - "base_denom", - "method", - "vault_coin_denom" - ], - "properties": { - "addr": { - "$ref": "#/definitions/Addr" - }, - "base_denom": { - "type": "string" - }, - "method": { - "$ref": "#/definitions/PricingMethod" - }, - "vault_coin_denom": { - "type": "string" - } - }, - "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" - }, - "PricingMethod": { - "type": "string", - "enum": [ - "preview_redeem" - ] - } - } - } - } -} diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 591e6d4d6..3fbe954b2 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -9,7 +9,6 @@ import { InstantiateMsg as VaultInstantiateMsg } from '../../types/generated/mar import { InstantiateMsg as SwapperInstantiateMsg } from '../../types/generated/mars-swapper-base/MarsSwapperBase.types' import { InstantiateMsg as ZapperInstantiateMsg } from '../../types/generated/mars-zapper-base/MarsZapperBase.types' import { InstantiateMsg as RoverInstantiateMsg } from '../../types/generated/mars-credit-manager/MarsCreditManager.types' -import { InstantiateMsg as OracleAdapterInstantiateMsg } from '../../types/generated/mars-oracle-adapter/MarsOracleAdapter.types' import { Rover } from './rover' import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing' import { getAddress, getWallet, setupClient } from './setupDeployer' @@ -106,25 +105,6 @@ export class Deployer { } } - async instantiateMarsOracleAdapter() { - const msg: OracleAdapterInstantiateMsg = { - oracle: this.config.oracle.addr, - owner: this.deployerAddr, - vault_pricing: this.config.oracle.vaultPricing, - } - - if (this.config.testActions) { - msg.vault_pricing.push({ - addr: this.storage.addresses.mockVault!, - method: 'preview_redeem', - base_denom: this.config.testActions.vault.mock.baseToken.denom, - vault_coin_denom: this.config.testActions.vault.mock.vaultTokenDenom, - }) - } - - await this.instantiate('marsOracleAdapter', this.storage.codeIds.marsOracleAdapter!, msg) - } - async instantiateSwapper() { const msg: SwapperInstantiateMsg = { owner: this.deployerAddr, @@ -166,7 +146,7 @@ export class Deployer { max_unlocking_positions: this.config.maxUnlockingPositions, allowed_coins: this.config.allowedCoins, vault_configs: this.config.vaults, - oracle: this.storage.addresses.marsOracleAdapter!, + oracle: this.config.oracle.addr, owner: this.deployerAddr, red_bank: this.config.redBank.addr, max_close_factor: this.config.maxCloseFactor, diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 52458cd4a..1c686d12d 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -19,14 +19,12 @@ export const taskRunner = async ({ // Upload contracts await deployer.upload('accountNft', wasmFile('mars_account_nft')) await deployer.upload('mockVault', wasmFile('mars_mock_vault')) - await deployer.upload('marsOracleAdapter', wasmFile('mars_oracle_adapter')) await deployer.upload('swapper', wasmFile(swapperContractName)) await deployer.upload('zapper', wasmFile(zapperContractName)) await deployer.upload('creditManager', wasmFile('mars_credit_manager')) // Instantiate contracts await deployer.instantiateMockVault() - await deployer.instantiateMarsOracleAdapter() await deployer.instantiateSwapper() await deployer.instantiateZapper() await deployer.instantiateCreditManager() diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index c7901a5f2..9472c1e7d 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -24,15 +24,6 @@ export const osmosisTestnetConfig: DeploymentConfig = { // Get the latest addresses from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-4.json oracle: { addr: 'osmo1jnkun9gcajn96a4yh7atzkq98c9sm0xrsqk7xtes07ujyn7xh5rqjymxxv', - vaultPricing: [ - { - addr: autoCompoundingVault, - base_denom: gammPool1, - method: 'preview_redeem', - vault_coin_denom: - 'factory/osmo1v40lnedgvake8p7f49gvqu0q3vc9sx3qpc0jqtyfdyw25d4vg8us38an37/cwVTT', - }, - ], }, redBank: { addr: 'osmo18w58j2dlpre6kslls9w88aur5ud8000wvg8pw4fp80p6q97g6qtqvhztpv' }, swapRoutes: [ diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 8817beb0c..b741025e2 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -3,7 +3,6 @@ import { VaultConfig, VaultInstantiateConfig, } from './generated/mars-credit-manager/MarsCreditManager.types' -import { VaultPricingInfo } from './generated/mars-oracle-adapter/MarsOracleAdapter.types' import { PriceSource } from './priceSource' export enum VaultType { @@ -22,7 +21,7 @@ export interface DeploymentConfig { baseDenom: string } deployerMnemonic: string - oracle: { addr: string; vaultPricing: VaultPricingInfo[] } + oracle: { addr: string } redBank: { addr: string } zapper: { addr: string } vaults: VaultInstantiateConfig[] diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts index 3887a19dd..de80142cb 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts @@ -8,7 +8,7 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { Coin, StdFee } from '@cosmjs/amino' import { - Decimal, + Uint128, InstantiateMsg, ExecuteMsg, Binary, diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts index 5267bda1e..5df812250 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts @@ -10,7 +10,7 @@ import { MsgExecuteContractEncodeObject } from 'cosmwasm' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { - Decimal, + Uint128, InstantiateMsg, ExecuteMsg, Binary, diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts index a9c43d92b..e3c215a32 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts @@ -9,7 +9,7 @@ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tan import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee, Coin } from '@cosmjs/amino' import { - Decimal, + Uint128, InstantiateMsg, ExecuteMsg, Binary, diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index 9a320ddc4..0fa4760f5 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -5,9 +5,9 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -export type Decimal = string +export type Uint128 = string export interface InstantiateMsg { - max_value_for_burn: Decimal + max_value_for_burn: Uint128 minter: string name: string symbol: string @@ -82,7 +82,7 @@ export type Expiration = export type Timestamp = Uint64 export type Uint64 = string export interface ConfigUpdates { - max_value_for_burn?: Decimal | null + max_value_for_burn?: Uint128 | null proposed_new_minter?: string | null } export type QueryMsg = @@ -184,7 +184,7 @@ export interface ApprovalsResponse { approvals: Approval[] } export interface ConfigBaseForString { - max_value_for_burn: Decimal + max_value_for_burn: Uint128 proposed_new_minter?: string | null } export interface ContractInfoResponse { diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index d28c8ee14..a63ce8749 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -424,11 +424,11 @@ export interface HealthResponse { above_max_ltv: boolean liquidatable: boolean liquidation_health_factor?: Decimal | null - liquidation_threshold_adjusted_collateral: Decimal - max_ltv_adjusted_collateral: Decimal + liquidation_threshold_adjusted_collateral: Uint128 + max_ltv_adjusted_collateral: Uint128 max_ltv_health_factor?: Decimal | null - total_collateral_value: Decimal - total_debt_value: Decimal + total_collateral_value: Uint128 + total_debt_value: Uint128 } export interface Positions { account_id: string diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index 71df78b11..f4dfb7155 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -11,9 +11,9 @@ import { InstantiateMsg, ExecuteMsg, Decimal, + Uint128, HealthResponse, QueryMsg, - Uint128, VaultBaseForString, Coin, ArrayOfCoinBalanceResponseItem, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index 0e9b856f5..039413d6d 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -12,9 +12,9 @@ import { InstantiateMsg, ExecuteMsg, Decimal, + Uint128, HealthResponse, QueryMsg, - Uint128, VaultBaseForString, Coin, ArrayOfCoinBalanceResponseItem, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index 071b09d79..ff95c833e 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -12,9 +12,9 @@ import { InstantiateMsg, ExecuteMsg, Decimal, + Uint128, HealthResponse, QueryMsg, - Uint128, VaultBaseForString, Coin, ArrayOfCoinBalanceResponseItem, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 64663cdc7..978a599d5 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -15,15 +15,16 @@ export type ExecuteMsg = { } } export type Decimal = string +export type Uint128 = string export interface HealthResponse { above_max_ltv: boolean liquidatable: boolean liquidation_health_factor?: Decimal | null - liquidation_threshold_adjusted_collateral: Decimal - max_ltv_adjusted_collateral: Decimal + liquidation_threshold_adjusted_collateral: Uint128 + max_ltv_adjusted_collateral: Uint128 max_ltv_health_factor?: Decimal | null - total_collateral_value: Decimal - total_debt_value: Decimal + total_collateral_value: Uint128 + total_debt_value: Uint128 } export type QueryMsg = | { @@ -100,7 +101,6 @@ export type QueryMsg = lp_token: Coin } } -export type Uint128 = string export interface VaultBaseForString { address: string } diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts deleted file mode 100644 index dd523fd36..000000000 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.client.ts +++ /dev/null @@ -1,161 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { Coin, StdFee } from '@cosmjs/amino' -import { - OracleBaseForString, - Addr, - PricingMethod, - InstantiateMsg, - VaultPricingInfo, - ExecuteMsg, - OwnerUpdate, - ConfigUpdates, - QueryMsg, - ArrayOfVaultPricingInfo, - OracleBaseForAddr, - ConfigResponse, - Decimal, - PriceResponse, -} from './MarsOracleAdapter.types' -export interface MarsOracleAdapterReadOnlyInterface { - contractAddress: string - price: ({ denom }: { denom: string }) => Promise - config: () => Promise - pricingInfo: ({ denom }: { denom: string }) => Promise - allPricingInfo: ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string - }) => Promise -} -export class MarsOracleAdapterQueryClient implements MarsOracleAdapterReadOnlyInterface { - client: CosmWasmClient - contractAddress: string - - constructor(client: CosmWasmClient, contractAddress: string) { - this.client = client - this.contractAddress = contractAddress - this.price = this.price.bind(this) - this.config = this.config.bind(this) - this.pricingInfo = this.pricingInfo.bind(this) - this.allPricingInfo = this.allPricingInfo.bind(this) - } - - price = async ({ denom }: { denom: string }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - price: { - denom, - }, - }) - } - config = async (): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - config: {}, - }) - } - pricingInfo = async ({ denom }: { denom: string }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - pricing_info: { - denom, - }, - }) - } - allPricingInfo = async ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - all_pricing_info: { - limit, - start_after: startAfter, - }, - }) - } -} -export interface MarsOracleAdapterInterface extends MarsOracleAdapterReadOnlyInterface { - contractAddress: string - sender: string - updateConfig: ( - { - newConfig, - }: { - newConfig: ConfigUpdates - }, - fee?: number | StdFee | 'auto', - memo?: string, - funds?: Coin[], - ) => Promise - updateOwner: ( - fee?: number | StdFee | 'auto', - memo?: string, - funds?: Coin[], - ) => Promise -} -export class MarsOracleAdapterClient - extends MarsOracleAdapterQueryClient - implements MarsOracleAdapterInterface -{ - client: SigningCosmWasmClient - sender: string - contractAddress: string - - constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { - super(client, contractAddress) - this.client = client - this.sender = sender - this.contractAddress = contractAddress - this.updateConfig = this.updateConfig.bind(this) - this.updateOwner = this.updateOwner.bind(this) - } - - updateConfig = async ( - { - newConfig, - }: { - newConfig: ConfigUpdates - }, - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - update_config: { - new_config: newConfig, - }, - }, - fee, - memo, - funds, - ) - } - updateOwner = async ( - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - update_owner: {}, - }, - fee, - memo, - funds, - ) - } -} diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts deleted file mode 100644 index b0e9a13a9..000000000 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.message-composer.ts +++ /dev/null @@ -1,91 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import { Coin } from '@cosmjs/amino' -import { MsgExecuteContractEncodeObject } from 'cosmwasm' -import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' -import { toUtf8 } from '@cosmjs/encoding' -import { - OracleBaseForString, - Addr, - PricingMethod, - InstantiateMsg, - VaultPricingInfo, - ExecuteMsg, - OwnerUpdate, - ConfigUpdates, - QueryMsg, - ArrayOfVaultPricingInfo, - OracleBaseForAddr, - ConfigResponse, - Decimal, - PriceResponse, -} from './MarsOracleAdapter.types' -export interface MarsOracleAdapterMessage { - contractAddress: string - sender: string - updateConfig: ( - { - newConfig, - }: { - newConfig: ConfigUpdates - }, - funds?: Coin[], - ) => MsgExecuteContractEncodeObject - updateOwner: (funds?: Coin[]) => MsgExecuteContractEncodeObject -} -export class MarsOracleAdapterMessageComposer implements MarsOracleAdapterMessage { - sender: string - contractAddress: string - - constructor(sender: string, contractAddress: string) { - this.sender = sender - this.contractAddress = contractAddress - this.updateConfig = this.updateConfig.bind(this) - this.updateOwner = this.updateOwner.bind(this) - } - - updateConfig = ( - { - newConfig, - }: { - newConfig: ConfigUpdates - }, - funds?: Coin[], - ): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - update_config: { - new_config: newConfig, - }, - }), - ), - funds, - }), - } - } - updateOwner = (funds?: Coin[]): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - update_owner: {}, - }), - ), - funds, - }), - } - } -} diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts deleted file mode 100644 index 756736628..000000000 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.react-query.ts +++ /dev/null @@ -1,185 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' -import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee, Coin } from '@cosmjs/amino' -import { - OracleBaseForString, - Addr, - PricingMethod, - InstantiateMsg, - VaultPricingInfo, - ExecuteMsg, - OwnerUpdate, - ConfigUpdates, - QueryMsg, - ArrayOfVaultPricingInfo, - OracleBaseForAddr, - ConfigResponse, - Decimal, - PriceResponse, -} from './MarsOracleAdapter.types' -import { MarsOracleAdapterQueryClient, MarsOracleAdapterClient } from './MarsOracleAdapter.client' -export const marsOracleAdapterQueryKeys = { - contract: [ - { - contract: 'marsOracleAdapter', - }, - ] as const, - address: (contractAddress: string | undefined) => - [{ ...marsOracleAdapterQueryKeys.contract[0], address: contractAddress }] as const, - price: (contractAddress: string | undefined, args?: Record) => - [{ ...marsOracleAdapterQueryKeys.address(contractAddress)[0], method: 'price', args }] as const, - config: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsOracleAdapterQueryKeys.address(contractAddress)[0], method: 'config', args }, - ] as const, - pricingInfo: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsOracleAdapterQueryKeys.address(contractAddress)[0], method: 'pricing_info', args }, - ] as const, - allPricingInfo: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsOracleAdapterQueryKeys.address(contractAddress)[0], - method: 'all_pricing_info', - args, - }, - ] as const, -} -export interface MarsOracleAdapterReactQuery { - client: MarsOracleAdapterQueryClient | undefined - options?: Omit< - UseQueryOptions, - "'queryKey' | 'queryFn' | 'initialData'" - > & { - initialData?: undefined - } -} -export interface MarsOracleAdapterAllPricingInfoQuery - extends MarsOracleAdapterReactQuery { - args: { - limit?: number - startAfter?: string - } -} -export function useMarsOracleAdapterAllPricingInfoQuery({ - client, - args, - options, -}: MarsOracleAdapterAllPricingInfoQuery) { - return useQuery( - marsOracleAdapterQueryKeys.allPricingInfo(client?.contractAddress, args), - () => - client - ? client.allPricingInfo({ - limit: args.limit, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsOracleAdapterPricingInfoQuery - extends MarsOracleAdapterReactQuery { - args: { - denom: string - } -} -export function useMarsOracleAdapterPricingInfoQuery({ - client, - args, - options, -}: MarsOracleAdapterPricingInfoQuery) { - return useQuery( - marsOracleAdapterQueryKeys.pricingInfo(client?.contractAddress, args), - () => - client - ? client.pricingInfo({ - denom: args.denom, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsOracleAdapterConfigQuery - extends MarsOracleAdapterReactQuery {} -export function useMarsOracleAdapterConfigQuery({ - client, - options, -}: MarsOracleAdapterConfigQuery) { - return useQuery( - marsOracleAdapterQueryKeys.config(client?.contractAddress), - () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsOracleAdapterPriceQuery - extends MarsOracleAdapterReactQuery { - args: { - denom: string - } -} -export function useMarsOracleAdapterPriceQuery({ - client, - args, - options, -}: MarsOracleAdapterPriceQuery) { - return useQuery( - marsOracleAdapterQueryKeys.price(client?.contractAddress, args), - () => - client - ? client.price({ - denom: args.denom, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsOracleAdapterUpdateOwnerMutation { - client: MarsOracleAdapterClient - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsOracleAdapterUpdateOwnerMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateOwner(msg, fee, memo, funds), - options, - ) -} -export interface MarsOracleAdapterUpdateConfigMutation { - client: MarsOracleAdapterClient - msg: { - newConfig: ConfigUpdates - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsOracleAdapterUpdateConfigMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => - client.updateConfig(msg, fee, memo, funds), - options, - ) -} diff --git a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts b/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts deleted file mode 100644 index 768efa54c..000000000 --- a/scripts/types/generated/mars-oracle-adapter/MarsOracleAdapter.types.ts +++ /dev/null @@ -1,75 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -export type OracleBaseForString = string -export type Addr = string -export type PricingMethod = 'preview_redeem' -export interface InstantiateMsg { - oracle: OracleBaseForString - owner: string - vault_pricing: VaultPricingInfo[] -} -export interface VaultPricingInfo { - addr: Addr - base_denom: string - method: PricingMethod - vault_coin_denom: string -} -export type ExecuteMsg = - | { - update_config: { - new_config: ConfigUpdates - } - } - | { - update_owner: OwnerUpdate - } -export type OwnerUpdate = - | { - propose_new_owner: { - proposed: string - } - } - | 'clear_proposed' - | 'accept_proposed' - | 'abolish_owner_role' -export interface ConfigUpdates { - oracle?: OracleBaseForString | null - vault_pricing?: VaultPricingInfo[] | null -} -export type QueryMsg = - | { - price: { - denom: string - } - } - | { - config: {} - } - | { - pricing_info: { - denom: string - } - } - | { - all_pricing_info: { - limit?: number | null - start_after?: string | null - } - } -export type ArrayOfVaultPricingInfo = VaultPricingInfo[] -export type OracleBaseForAddr = string -export interface ConfigResponse { - oracle: OracleBaseForAddr - owner?: string | null - proposed_new_owner?: string | null -} -export type Decimal = string -export interface PriceResponse { - denom: string - price: Decimal -} diff --git a/scripts/types/generated/mars-oracle-adapter/bundle.ts b/scripts/types/generated/mars-oracle-adapter/bundle.ts deleted file mode 100644 index 119c39ef3..000000000 --- a/scripts/types/generated/mars-oracle-adapter/bundle.ts +++ /dev/null @@ -1,14 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import * as _24 from './MarsOracleAdapter.types' -import * as _25 from './MarsOracleAdapter.client' -import * as _26 from './MarsOracleAdapter.message-composer' -import * as _27 from './MarsOracleAdapter.react-query' -export namespace contracts { - export const MarsOracleAdapter = { ..._24, ..._25, ..._26, ..._27 } -} diff --git a/scripts/types/generated/mars-swapper-base/bundle.ts b/scripts/types/generated/mars-swapper-base/bundle.ts index 69a659ed4..7793f5f39 100644 --- a/scripts/types/generated/mars-swapper-base/bundle.ts +++ b/scripts/types/generated/mars-swapper-base/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _28 from './MarsSwapperBase.types' -import * as _29 from './MarsSwapperBase.client' -import * as _30 from './MarsSwapperBase.message-composer' -import * as _31 from './MarsSwapperBase.react-query' +import * as _24 from './MarsSwapperBase.types' +import * as _25 from './MarsSwapperBase.client' +import * as _26 from './MarsSwapperBase.message-composer' +import * as _27 from './MarsSwapperBase.react-query' export namespace contracts { - export const MarsSwapperBase = { ..._28, ..._29, ..._30, ..._31 } + export const MarsSwapperBase = { ..._24, ..._25, ..._26, ..._27 } } diff --git a/scripts/types/generated/mars-zapper-base/bundle.ts b/scripts/types/generated/mars-zapper-base/bundle.ts index 26a4a169c..0254e9b04 100644 --- a/scripts/types/generated/mars-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-zapper-base/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _32 from './MarsZapperBase.types' -import * as _33 from './MarsZapperBase.client' -import * as _34 from './MarsZapperBase.message-composer' -import * as _35 from './MarsZapperBase.react-query' +import * as _28 from './MarsZapperBase.types' +import * as _29 from './MarsZapperBase.client' +import * as _30 from './MarsZapperBase.message-composer' +import * as _31 from './MarsZapperBase.react-query' export namespace contracts { - export const MarsZapperBase = { ..._32, ..._33, ..._34, ..._35 } + export const MarsZapperBase = { ..._28, ..._29, ..._30, ..._31 } } diff --git a/scripts/types/storageItems.ts b/scripts/types/storageItems.ts index 704ee48ae..7edfa61fe 100644 --- a/scripts/types/storageItems.ts +++ b/scripts/types/storageItems.ts @@ -4,7 +4,6 @@ export interface StorageItems { mockRedBank?: number mockVault?: number mockOracle?: number - marsOracleAdapter?: number swapper?: number zapper?: number creditManager?: number @@ -12,7 +11,6 @@ export interface StorageItems { addresses: { accountNft?: string mockVault?: string - marsOracleAdapter?: string swapper?: string zapper?: string creditManager?: string From d58f03cbdeeacc87226526b5e7c202ab989883d9 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 10 Jan 2023 14:03:13 +0100 Subject: [PATCH 119/218] Updating formatting rules (#91) --- .github/workflows/main.yml | 2 +- Makefile.toml | 1 + contracts/account-nft/src/contract.rs | 39 ++-- contracts/account-nft/src/execute.rs | 32 ++-- contracts/account-nft/src/msg/execute.rs | 58 ++++-- contracts/account-nft/src/msg/query.rs | 20 ++- contracts/account-nft/src/query.rs | 7 +- .../account-nft/tests/helpers/mock_env.rs | 22 ++- .../tests/helpers/mock_env_builder.rs | 11 +- contracts/account-nft/tests/helpers/mod.rs | 5 +- .../account-nft/tests/test_burn_allowance.rs | 18 +- contracts/account-nft/tests/test_mint.rs | 9 +- .../account-nft/tests/test_proposed_minter.rs | 17 +- .../account-nft/tests/test_update_config.rs | 1 - contracts/credit-manager/src/borrow.rs | 10 +- contracts/credit-manager/src/contract.rs | 115 ++++++------ contracts/credit-manager/src/deposit.rs | 11 +- contracts/credit-manager/src/execute.rs | 138 +++++++------- contracts/credit-manager/src/health.rs | 44 ++--- contracts/credit-manager/src/instantiate.rs | 24 ++- .../credit-manager/src/liquidate_coin.rs | 28 ++- contracts/credit-manager/src/query.rs | 50 +++--- contracts/credit-manager/src/refund.rs | 17 +- contracts/credit-manager/src/repay.rs | 18 +- contracts/credit-manager/src/state.rs | 13 +- contracts/credit-manager/src/swap.rs | 15 +- .../src/update_coin_balances.rs | 1 - contracts/credit-manager/src/update_config.rs | 41 ++--- contracts/credit-manager/src/utils.rs | 54 +++--- contracts/credit-manager/src/vault/enter.rs | 38 ++-- contracts/credit-manager/src/vault/exit.rs | 10 +- .../credit-manager/src/vault/exit_unlocked.rs | 23 ++- .../src/vault/liquidate_vault.rs | 59 +++--- contracts/credit-manager/src/vault/mod.rs | 9 +- .../src/vault/request_unlock.rs | 27 +-- contracts/credit-manager/src/vault/utils.rs | 22 +-- contracts/credit-manager/src/withdraw.rs | 1 - contracts/credit-manager/src/zap.rs | 27 ++- .../tests/helpers/assertions.rs | 4 +- .../credit-manager/tests/helpers/builders.rs | 6 +- .../tests/helpers/mock_entity_info.rs | 4 +- .../credit-manager/tests/helpers/mock_env.rs | 156 +++++++++------- contracts/credit-manager/tests/helpers/mod.rs | 10 +- .../credit-manager/tests/helpers/utils.rs | 6 +- contracts/credit-manager/tests/test_borrow.rs | 65 ++----- .../tests/test_coin_balances.rs | 11 +- .../tests/test_create_credit_account.rs | 3 +- .../credit-manager/tests/test_deposit.rs | 45 ++--- .../credit-manager/tests/test_dispatch.rs | 11 +- .../tests/test_enumerate_allowed_coins.rs | 9 +- .../tests/test_enumerate_coin_balances.rs | 35 ++-- .../tests/test_enumerate_debt_shares.rs | 35 ++-- .../tests/test_enumerate_total_debt_shares.rs | 38 ++-- .../test_enumerate_vault_coin_balances.rs | 27 +-- .../tests/test_enumerate_vault_configs.rs | 10 +- .../tests/test_enumerate_vault_positions.rs | 42 +---- .../tests/test_fields_vault_limit.rs | 6 +- .../tests/test_flagged_contract.rs | 4 +- contracts/credit-manager/tests/test_health.rs | 169 +++++------------- .../credit-manager/tests/test_instantiate.rs | 28 +-- .../tests/test_liquidate_coin.rs | 56 ++---- .../tests/test_liquidate_vault.rs | 37 +--- .../tests/test_refund_balances.rs | 6 +- contracts/credit-manager/tests/test_repay.rs | 51 ++---- contracts/credit-manager/tests/test_swap.rs | 31 ++-- .../credit-manager/tests/test_update_admin.rs | 22 +-- .../tests/test_update_config.rs | 56 +++--- .../tests/test_utilization_query.rs | 14 +- .../credit-manager/tests/test_vault_enter.rs | 48 ++--- .../credit-manager/tests/test_vault_exit.rs | 16 +- .../tests/test_vault_exit_unlocked.rs | 57 +++--- .../tests/test_vault_request_unlock.rs | 27 +-- .../credit-manager/tests/test_withdraw.rs | 36 ++-- .../credit-manager/tests/test_zap_provide.rs | 31 ++-- .../credit-manager/tests/test_zap_withdraw.rs | 18 +- contracts/mock-credit-manager/src/contract.rs | 8 +- contracts/mock-credit-manager/src/execute.rs | 3 +- contracts/mock-credit-manager/src/query.rs | 3 +- contracts/mock-credit-manager/src/state.rs | 1 - contracts/mock-oracle/src/contract.rs | 15 +- contracts/mock-oracle/src/msg.rs | 4 +- contracts/mock-red-bank/src/contract.rs | 32 ++-- contracts/mock-red-bank/src/execute.rs | 7 +- contracts/mock-red-bank/src/query.rs | 7 +- contracts/mock-vault/src/contract.rs | 84 +++++---- contracts/mock-vault/src/deposit.rs | 18 +- contracts/mock-vault/src/query.rs | 12 +- contracts/mock-vault/src/state.rs | 1 - contracts/mock-vault/src/unlock.rs | 15 +- contracts/mock-vault/src/withdraw.rs | 12 +- contracts/swapper/base/src/contract.rs | 53 +++--- contracts/swapper/base/src/error.rs | 9 +- contracts/swapper/base/src/traits.rs | 3 +- contracts/swapper/mock/src/contract.rs | 33 ++-- contracts/swapper/osmosis/src/contract.rs | 6 +- contracts/swapper/osmosis/src/helpers.rs | 4 +- contracts/swapper/osmosis/src/route.rs | 23 +-- contracts/swapper/osmosis/tests/helpers.rs | 34 ++-- .../osmosis/tests/test_enumerate_routes.rs | 11 +- .../swapper/osmosis/tests/test_estimate.rs | 37 +--- .../swapper/osmosis/tests/test_instantiate.rs | 17 +- .../swapper/osmosis/tests/test_set_route.rs | 85 ++------- contracts/swapper/osmosis/tests/test_swap.rs | 48 ++--- .../osmosis/tests/test_update_admin.rs | 31 +--- contracts/zapper/base/src/contract.rs | 36 ++-- contracts/zapper/base/src/msg.rs | 4 +- contracts/zapper/base/src/traits.rs | 3 +- contracts/zapper/mock/src/contract.rs | 26 +-- contracts/zapper/mock/src/error.rs | 3 +- contracts/zapper/mock/src/execute.rs | 20 +-- contracts/zapper/mock/src/query.rs | 12 +- contracts/zapper/mock/src/state.rs | 1 - contracts/zapper/osmosis/src/contract.rs | 9 +- contracts/zapper/osmosis/src/lp_pool.rs | 12 +- contracts/zapper/osmosis/tests/helpers.rs | 47 +++-- .../zapper/osmosis/tests/test_callback.rs | 11 +- .../osmosis/tests/test_provide_liquidity.rs | 154 ++++------------ .../zapper/osmosis/tests/test_queries.rs | 45 ++--- .../osmosis/tests/test_withdraw_liquidity.rs | 135 ++++---------- packages/chains/osmosis/src/helpers.rs | 36 ++-- packages/health/src/health.rs | 8 +- packages/math/src/fraction.rs | 12 +- packages/math/tests/test_div_floor.rs | 16 +- packages/math/tests/test_mul_ceil.rs | 11 +- packages/math/tests/test_mul_floor.rs | 12 +- packages/outpost/src/address_provider.rs | 18 +- packages/outpost/src/error.rs | 11 +- packages/outpost/src/incentives.rs | 8 +- packages/outpost/src/math.rs | 16 +- packages/outpost/src/oracle.rs | 24 ++- .../src/red_bank/interest_rate_model.rs | 4 +- packages/outpost/src/red_bank/market.rs | 12 +- packages/outpost/src/red_bank/msg.rs | 40 ++++- packages/outpost/src/red_bank/types.rs | 3 +- packages/outpost/src/rewards_collector.rs | 15 +- packages/rover/src/adapters/oracle.rs | 1 - packages/rover/src/adapters/red_bank.rs | 8 +- packages/rover/src/adapters/swap/mod.rs | 3 +- packages/rover/src/adapters/swap/msgs.rs | 10 +- packages/rover/src/adapters/vault/amount.rs | 22 ++- packages/rover/src/adapters/vault/base.rs | 66 +++---- packages/rover/src/adapters/vault/config.rs | 3 +- packages/rover/src/adapters/vault/mod.rs | 6 +- packages/rover/src/adapters/vault/position.rs | 3 +- packages/rover/src/adapters/vault/update.rs | 11 +- packages/rover/src/adapters/zapper.rs | 4 +- packages/rover/src/coins.rs | 64 +++---- packages/rover/src/error.rs | 29 ++- packages/rover/src/extensions/reply.rs | 2 +- packages/rover/src/msg/execute.rs | 37 ++-- packages/rover/src/msg/instantiate.rs | 16 +- packages/rover/src/msg/query.rs | 22 ++- packages/rover/src/msg/zapper.rs | 7 +- rustfmt.toml | 4 + 154 files changed, 1713 insertions(+), 2252 deletions(-) create mode 100644 rustfmt.toml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f694b6772..7def88a60 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,7 +21,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: nightly target: wasm32-unknown-unknown components: rustfmt, clippy profile: minimal diff --git a/Makefile.toml b/Makefile.toml index 12a4c1537..1d53d3f4f 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -28,6 +28,7 @@ command = "cargo" args = ["test", "--locked"] [tasks.fmt] +toolchain = "nightly" command = "cargo" args = ["fmt", "--all", "--check"] diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 01c431fdc..1e70b379f 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -1,3 +1,5 @@ +use std::convert::TryInto; + #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -6,14 +8,15 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw721::ContractInfoResponse; use cw721_base::Cw721Contract; -use std::convert::TryInto; -use crate::config::Config; -use crate::error::ContractError; -use crate::execute::{accept_minter_role, burn, mint, update_config}; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::query::{query_config, query_next_id}; -use crate::state::{CONFIG, NEXT_ID}; +use crate::{ + config::Config, + error::ContractError, + execute::{accept_minter_role, burn, mint, update_config}, + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + query::{query_config, query_next_id}, + state::{CONFIG, NEXT_ID}, +}; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -28,11 +31,7 @@ pub fn instantiate( _: MessageInfo, msg: InstantiateMsg, ) -> StdResult { - set_contract_version( - deps.storage, - &format!("crates.io:{}", CONTRACT_NAME), - CONTRACT_VERSION, - )?; + set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; NEXT_ID.save(deps.storage, &1)?; @@ -65,13 +64,17 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Mint { user } => mint(deps, env, info, &user), - ExecuteMsg::UpdateConfig { updates } => update_config(deps, info, updates), + ExecuteMsg::Mint { + user, + } => mint(deps, env, info, &user), + ExecuteMsg::UpdateConfig { + updates, + } => update_config(deps, info, updates), ExecuteMsg::AcceptMinterRole {} => accept_minter_role(deps, info), - ExecuteMsg::Burn { token_id } => burn(deps, env, info, token_id), - _ => Parent::default() - .execute(deps, env, info, msg.try_into()?) - .map_err(Into::into), + ExecuteMsg::Burn { + token_id, + } => burn(deps, env, info, token_id), + _ => Parent::default().execute(deps, env, info, msg.try_into()?).map_err(Into::into), } } diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index c41a8beec..526e51705 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -3,15 +3,18 @@ use cosmwasm_std::{ }; use cw721::Cw721Execute; use cw721_base::MintMsg; - use mars_health::HealthResponse; use mars_rover::msg::QueryMsg::Health; -use crate::config::ConfigUpdates; -use crate::contract::Parent; -use crate::error::ContractError; -use crate::error::ContractError::{BaseError, BurnNotAllowed}; -use crate::state::{CONFIG, NEXT_ID}; +use crate::{ + config::ConfigUpdates, + contract::Parent, + error::{ + ContractError, + ContractError::{BaseError, BurnNotAllowed}, + }, + state::{CONFIG, NEXT_ID}, +}; pub fn mint( deps: DepsMut, @@ -28,9 +31,7 @@ pub fn mint( }; NEXT_ID.save(deps.storage, &(next_id + 1))?; - Parent::default() - .mint(deps, env, info, mint_msg_override) - .map_err(Into::into) + Parent::default().mint(deps, env, info, mint_msg_override).map_err(Into::into) } /// Checks first to ensure the balance of debts and collateral does not exceed the config @@ -50,9 +51,8 @@ pub fn burn( }))?; let max_value_allowed = CONFIG.load(deps.storage)?.max_value_for_burn; - let current_balances = response - .total_debt_value - .checked_add(response.total_collateral_value)?; + let current_balances = + response.total_debt_value.checked_add(response.total_collateral_value)?; if current_balances > max_value_allowed { return Err(BurnNotAllowed { current_balances, @@ -60,9 +60,7 @@ pub fn burn( }); } - Parent::default() - .burn(deps, env, info, token_id) - .map_err(Into::into) + Parent::default().burn(deps, env, info, token_id).map_err(Into::into) } pub fn update_config( @@ -88,9 +86,7 @@ pub fn update_config( if let Some(addr) = updates.proposed_new_minter { let validated = deps.api.addr_validate(&addr)?; config.proposed_new_minter = Some(validated); - response = response - .add_attribute("key", "pending_minter") - .add_attribute("value", addr); + response = response.add_attribute("key", "pending_minter").add_attribute("value", addr); } CONFIG.save(deps.storage, &config)?; diff --git a/contracts/account-nft/src/msg/execute.rs b/contracts/account-nft/src/msg/execute.rs index 822a4318e..14bb91e9b 100644 --- a/contracts/account-nft/src/msg/execute.rs +++ b/contracts/account-nft/src/msg/execute.rs @@ -1,35 +1,44 @@ -use cosmwasm_schema::cw_serde; use std::convert::TryInto; -use crate::config::ConfigUpdates; -use crate::error::ContractError; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Binary, Empty, StdError}; use cw721::Expiration; use cw721_base::ExecuteMsg as ParentExecuteMsg; +use crate::{config::ConfigUpdates, error::ContractError}; + #[cw_serde] pub enum ExecuteMsg { //-------------------------------------------------------------------------------------------------- // Extended and overridden messages //-------------------------------------------------------------------------------------------------- /// Update config in storage. Only minter can execute. - UpdateConfig { updates: ConfigUpdates }, + UpdateConfig { + updates: ConfigUpdates, + }, /// Accept the proposed minter role. Only the proposed new minter can execute. AcceptMinterRole {}, /// Mint a new NFT to the specified user; can only be called by the contract minter - Mint { user: String }, + Mint { + user: String, + }, /// Burn an NFT the sender has access to. Will attempt to query the Credit Manager first /// to ensure the balance is below the config set threshold. - Burn { token_id: String }, + Burn { + token_id: String, + }, //-------------------------------------------------------------------------------------------------- // Base cw721 messages //-------------------------------------------------------------------------------------------------- /// Transfer is a base message to move a token to another account without triggering actions - TransferNft { recipient: String, token_id: String }, + TransferNft { + recipient: String, + token_id: String, + }, /// Send is a base message to transfer a token to a contract and trigger an action /// on the receiving contract. SendNft { @@ -45,7 +54,10 @@ pub enum ExecuteMsg { expires: Option, }, /// Remove previously granted Approval - Revoke { spender: String, token_id: String }, + Revoke { + spender: String, + token_id: String, + }, /// Allows operator to transfer / send any token from the owner's account. /// If expiration is set, then this allowance has a time/height limit ApproveAll { @@ -53,7 +65,9 @@ pub enum ExecuteMsg { expires: Option, }, /// Remove previously granted ApproveAll permission - RevokeAll { operator: String }, + RevokeAll { + operator: String, + }, } impl TryInto> for ExecuteMsg { @@ -86,13 +100,25 @@ impl TryInto> for ExecuteMsg { token_id, expires, }), - ExecuteMsg::Revoke { spender, token_id } => { - Ok(ParentExecuteMsg::Revoke { spender, token_id }) - } - ExecuteMsg::ApproveAll { operator, expires } => { - Ok(ParentExecuteMsg::ApproveAll { operator, expires }) - } - ExecuteMsg::RevokeAll { operator } => Ok(ParentExecuteMsg::RevokeAll { operator }), + ExecuteMsg::Revoke { + spender, + token_id, + } => Ok(ParentExecuteMsg::Revoke { + spender, + token_id, + }), + ExecuteMsg::ApproveAll { + operator, + expires, + } => Ok(ParentExecuteMsg::ApproveAll { + operator, + expires, + }), + ExecuteMsg::RevokeAll { + operator, + } => Ok(ParentExecuteMsg::RevokeAll { + operator, + }), _ => Err(StdError::generic_err( "Attempting to convert to a non-cw721 compatible message", ) diff --git a/contracts/account-nft/src/msg/query.rs b/contracts/account-nft/src/msg/query.rs index 3dbc1725c..7387be867 100644 --- a/contracts/account-nft/src/msg/query.rs +++ b/contracts/account-nft/src/msg/query.rs @@ -63,7 +63,9 @@ pub enum QueryMsg { /// Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* /// but directly from the contract #[returns(cw721::NftInfoResponse)] - NftInfo { token_id: String }, + NftInfo { + token_id: String, + }, /// With MetaData Extension. /// Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization for clients #[returns(cw721::AllNftInfoResponse)] @@ -136,7 +138,11 @@ impl TryInto> for QueryMsg { }), QueryMsg::NumTokens {} => Ok(ParentQueryMsg::NumTokens {}), QueryMsg::ContractInfo {} => Ok(ParentQueryMsg::ContractInfo {}), - QueryMsg::NftInfo { token_id } => Ok(ParentQueryMsg::NftInfo { token_id }), + QueryMsg::NftInfo { + token_id, + } => Ok(ParentQueryMsg::NftInfo { + token_id, + }), QueryMsg::AllNftInfo { token_id, include_expired, @@ -153,9 +159,13 @@ impl TryInto> for QueryMsg { start_after, limit, }), - QueryMsg::AllTokens { start_after, limit } => { - Ok(ParentQueryMsg::AllTokens { start_after, limit }) - } + QueryMsg::AllTokens { + start_after, + limit, + } => Ok(ParentQueryMsg::AllTokens { + start_after, + limit, + }), QueryMsg::Minter {} => Ok(ParentQueryMsg::Minter {}), _ => Err(StdError::generic_err( "Attempting to convert to a non-cw721 compatible message", diff --git a/contracts/account-nft/src/query.rs b/contracts/account-nft/src/query.rs index fcbaf0ecf..11696799f 100644 --- a/contracts/account-nft/src/query.rs +++ b/contracts/account-nft/src/query.rs @@ -1,7 +1,10 @@ -use crate::config::UncheckedConfig; -use crate::state::{CONFIG, NEXT_ID}; use cosmwasm_std::{Deps, StdResult}; +use crate::{ + config::UncheckedConfig, + state::{CONFIG, NEXT_ID}, +}; + pub fn query_config(deps: Deps) -> StdResult { Ok(CONFIG.load(deps.storage)?.into()) } diff --git a/contracts/account-nft/tests/helpers/mock_env.rs b/contracts/account-nft/tests/helpers/mock_env.rs index 0b222a0f1..d2a2fe58a 100644 --- a/contracts/account-nft/tests/helpers/mock_env.rs +++ b/contracts/account-nft/tests/helpers/mock_env.rs @@ -2,10 +2,14 @@ use anyhow::Result as AnyResult; use cosmwasm_std::Addr; use cw721::OwnerOfResponse; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; - -use mars_account_nft::config::{ConfigUpdates, UncheckedConfig}; -use mars_account_nft::msg::ExecuteMsg::{AcceptMinterRole, UpdateConfig}; -use mars_account_nft::msg::{ExecuteMsg as ExtendedExecuteMsg, QueryMsg}; +use mars_account_nft::{ + config::{ConfigUpdates, UncheckedConfig}, + msg::{ + ExecuteMsg as ExtendedExecuteMsg, + ExecuteMsg::{AcceptMinterRole, UpdateConfig}, + QueryMsg, + }, +}; use mars_health::HealthResponse; use mars_mock_credit_manager::msg::ExecuteMsg::SetHealthResponse; @@ -30,17 +34,11 @@ impl MockEnv { } pub fn query_config(&mut self) -> UncheckedConfig { - self.app - .wrap() - .query_wasm_smart(self.nft_contract.clone(), &QueryMsg::Config {}) - .unwrap() + self.app.wrap().query_wasm_smart(self.nft_contract.clone(), &QueryMsg::Config {}).unwrap() } pub fn query_next_id(&mut self) -> u64 { - self.app - .wrap() - .query_wasm_smart(self.nft_contract.clone(), &QueryMsg::NextId {}) - .unwrap() + self.app.wrap().query_wasm_smart(self.nft_contract.clone(), &QueryMsg::NextId {}).unwrap() } // Double checking ownership by querying NFT account-nft for correct owner diff --git a/contracts/account-nft/tests/helpers/mock_env_builder.rs b/contracts/account-nft/tests/helpers/mock_env_builder.rs index 10e3e7584..a7607e2d9 100644 --- a/contracts/account-nft/tests/helpers/mock_env_builder.rs +++ b/contracts/account-nft/tests/helpers/mock_env_builder.rs @@ -3,10 +3,13 @@ use std::mem::take; use anyhow::Result as AnyResult; use cosmwasm_std::{Addr, Empty}; use cw_multi_test::{BasicApp, Executor}; - -use mars_account_nft::config::ConfigUpdates; -use mars_account_nft::msg::ExecuteMsg::{AcceptMinterRole, UpdateConfig}; -use mars_account_nft::msg::InstantiateMsg; +use mars_account_nft::{ + config::ConfigUpdates, + msg::{ + ExecuteMsg::{AcceptMinterRole, UpdateConfig}, + InstantiateMsg, + }, +}; use crate::helpers::{ mock_credit_manager_contract, mock_nft_contract, MockEnv, MAX_VALUE_FOR_BURN, diff --git a/contracts/account-nft/tests/helpers/mod.rs b/contracts/account-nft/tests/helpers/mod.rs index 66d8482c9..8ed2d450b 100644 --- a/contracts/account-nft/tests/helpers/mod.rs +++ b/contracts/account-nft/tests/helpers/mod.rs @@ -1,7 +1,4 @@ -pub use self::health_responses::*; -pub use self::mock_contracts::*; -pub use self::mock_env::*; -pub use self::mock_env_builder::*; +pub use self::{health_responses::*, mock_contracts::*, mock_env::*, mock_env_builder::*}; mod health_responses; mod mock_contracts; diff --git a/contracts/account-nft/tests/test_burn_allowance.rs b/contracts/account-nft/tests/test_burn_allowance.rs index 4b78f4b3c..f557dcc7c 100644 --- a/contracts/account-nft/tests/test_burn_allowance.rs +++ b/contracts/account-nft/tests/test_burn_allowance.rs @@ -1,9 +1,9 @@ use cosmwasm_std::{Addr, Empty, StdResult, Uint128}; use cw721::NftInfoResponse; - -use mars_account_nft::error::ContractError; -use mars_account_nft::error::ContractError::BurnNotAllowed; -use mars_account_nft::msg::QueryMsg::NftInfo; +use mars_account_nft::{ + error::{ContractError, ContractError::BurnNotAllowed}, + msg::QueryMsg::NftInfo, +}; use crate::helpers::{below_max_for_burn, generate_health_response, MockEnv, MAX_VALUE_FOR_BURN}; @@ -100,9 +100,11 @@ fn test_burn_allowance_when_under_max() { mock.set_health_response(&user, &token_id, &below_max_for_burn()); mock.burn(&user, &token_id).unwrap(); - let res: StdResult> = mock - .app - .wrap() - .query_wasm_smart(mock.nft_contract, &NftInfo { token_id }); + let res: StdResult> = mock.app.wrap().query_wasm_smart( + mock.nft_contract, + &NftInfo { + token_id, + }, + ); res.unwrap_err(); } diff --git a/contracts/account-nft/tests/test_mint.rs b/contracts/account-nft/tests/test_mint.rs index ef6d4290d..ba50f7fc0 100644 --- a/contracts/account-nft/tests/test_mint.rs +++ b/contracts/account-nft/tests/test_mint.rs @@ -4,11 +4,10 @@ use cosmwasm_std::Addr; use cw721::OwnerOfResponse; use cw721_base::ContractError::Unauthorized; use cw_multi_test::Executor; - -use mars_account_nft::error::ContractError; -use mars_account_nft::error::ContractError::BaseError; -use mars_account_nft::msg::ExecuteMsg as ExtendedExecuteMsg; -use mars_account_nft::msg::QueryMsg::OwnerOf; +use mars_account_nft::{ + error::{ContractError, ContractError::BaseError}, + msg::{ExecuteMsg as ExtendedExecuteMsg, QueryMsg::OwnerOf}, +}; use crate::helpers::{below_max_for_burn, MockEnv}; diff --git a/contracts/account-nft/tests/test_proposed_minter.rs b/contracts/account-nft/tests/test_proposed_minter.rs index c59dbc2be..808d07ae4 100644 --- a/contracts/account-nft/tests/test_proposed_minter.rs +++ b/contracts/account-nft/tests/test_proposed_minter.rs @@ -1,6 +1,5 @@ use cosmwasm_std::Addr; use cw721_base::MinterResponse; - use mars_account_nft::msg::QueryMsg; use crate::helpers::MockEnv; @@ -24,8 +23,7 @@ fn test_propose_minter_stores() { let mut mock = MockEnv::new().build().unwrap(); let new_minter = Addr::unchecked("new_minter"); - mock.propose_new_minter(&mock.minter.clone(), &new_minter) - .unwrap(); + mock.propose_new_minter(&mock.minter.clone(), &new_minter).unwrap(); let config = mock.query_config(); assert_eq!(config.proposed_new_minter.unwrap(), new_minter); @@ -36,8 +34,7 @@ fn test_proposed_minter_can_accept_role() { let mut mock = MockEnv::new().build().unwrap(); let new_minter = Addr::unchecked("new_minter"); - mock.propose_new_minter(&mock.minter.clone(), &new_minter) - .unwrap(); + mock.propose_new_minter(&mock.minter.clone(), &new_minter).unwrap(); mock.accept_proposed_minter(&new_minter).unwrap(); @@ -46,11 +43,8 @@ fn test_proposed_minter_can_accept_role() { panic!("Proposed minter should have been removed from storage"); } - let res: MinterResponse = mock - .app - .wrap() - .query_wasm_smart(mock.nft_contract, &QueryMsg::Minter {}) - .unwrap(); + let res: MinterResponse = + mock.app.wrap().query_wasm_smart(mock.nft_contract, &QueryMsg::Minter {}).unwrap(); assert_eq!(res.minter, new_minter) } @@ -60,8 +54,7 @@ fn test_only_proposed_minter_can_accept() { let mut mock = MockEnv::new().build().unwrap(); let new_minter = Addr::unchecked("new_minter"); - mock.propose_new_minter(&mock.minter.clone(), &new_minter) - .unwrap(); + mock.propose_new_minter(&mock.minter.clone(), &new_minter).unwrap(); let bad_guy = Addr::unchecked("bad_guy"); let res = mock.accept_proposed_minter(&bad_guy); diff --git a/contracts/account-nft/tests/test_update_config.rs b/contracts/account-nft/tests/test_update_config.rs index 8eb3a6344..79176ae0b 100644 --- a/contracts/account-nft/tests/test_update_config.rs +++ b/contracts/account-nft/tests/test_update_config.rs @@ -1,5 +1,4 @@ use cosmwasm_std::{Addr, Uint128}; - use mars_account_nft::config::ConfigUpdates; use crate::helpers::MockEnv; diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index bf9712179..9e715ea2c 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -1,9 +1,10 @@ use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; - use mars_rover::error::{ContractError, ContractResult}; -use crate::state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; -use crate::utils::{assert_coin_is_whitelisted, increment_coin_balance}; +use crate::{ + state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}, + utils::{assert_coin_is_whitelisted, increment_coin_balance}, +}; pub static DEFAULT_DEBT_SHARES_PER_COIN_BORROWED: Uint128 = Uint128::new(1_000_000); @@ -24,8 +25,7 @@ pub fn borrow(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> Contract red_bank.query_debt(&deps.querier, &env.contract.address, &coin.denom)?; let debt_shares_to_add = if total_debt_amount.is_zero() { - coin.amount - .checked_mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED)? + coin.amount.checked_mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED)? } else { TOTAL_DEBT_SHARES .load(deps.storage, &coin.denom)? diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index d9c239eca..a876e9a4c 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -2,24 +2,27 @@ use cosmwasm_std::{ entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, }; use cw2::set_contract_version; - use mars_health::HealthResponse; -use mars_rover::adapters::vault::VAULT_REQUEST_REPLY_ID; -use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_rover::{ + adapters::vault::VAULT_REQUEST_REPLY_ID, + error::{ContractError, ContractResult}, + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, +}; -use crate::execute::{create_credit_account, dispatch_actions, execute_callback}; -use crate::health::compute_health; -use crate::instantiate::store_config; -use crate::query::{ - query_all_coin_balances, query_all_debt_shares, query_all_total_debt_shares, - query_all_total_vault_coin_balances, query_all_vault_positions, query_allowed_coins, - query_config, query_positions, query_total_debt_shares, query_total_vault_coin_balance, - query_vaults_info, +use crate::{ + execute::{create_credit_account, dispatch_actions, execute_callback}, + health::compute_health, + instantiate::store_config, + query::{ + query_all_coin_balances, query_all_debt_shares, query_all_total_debt_shares, + query_all_total_vault_coin_balances, query_all_vault_positions, query_allowed_coins, + query_config, query_positions, query_total_debt_shares, query_total_vault_coin_balance, + query_vaults_info, + }, + update_config::{update_config, update_owner}, + vault::handle_unlock_request_reply, + zap::{estimate_provide_liquidity, estimate_withdraw_liquidity}, }; -use crate::update_config::{update_config, update_owner}; -use crate::vault::handle_unlock_request_reply; -use crate::zap::{estimate_provide_liquidity, estimate_withdraw_liquidity}; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -31,11 +34,7 @@ pub fn instantiate( _info: MessageInfo, msg: InstantiateMsg, ) -> ContractResult { - set_contract_version( - deps.storage, - &format!("crates.io:{}", CONTRACT_NAME), - CONTRACT_VERSION, - )?; + set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; store_config(deps, &msg)?; Ok(Response::default()) } @@ -49,7 +48,9 @@ pub fn execute( ) -> ContractResult { match msg { ExecuteMsg::CreateCreditAccount {} => create_credit_account(deps, info.sender), - ExecuteMsg::UpdateConfig { new_config } => update_config(deps, info, new_config), + ExecuteMsg::UpdateConfig { + new_config, + } => update_config(deps, info, new_config), ExecuteMsg::UpdateOwner(update) => update_owner(deps, info, update), ExecuteMsg::Callback(callback) => execute_callback(deps, info, env, callback), ExecuteMsg::UpdateCreditAccount { @@ -71,44 +72,56 @@ pub fn reply(deps: DepsMut, _: Env, reply: Reply) -> ContractResult { pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { let res = match msg { QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::VaultsInfo { start_after, limit } => { - to_binary(&query_vaults_info(deps, env, start_after, limit)?) - } - QueryMsg::AllowedCoins { start_after, limit } => { - to_binary(&query_allowed_coins(deps, start_after, limit)?) - } - QueryMsg::Positions { account_id } => to_binary(&query_positions(deps, &env, &account_id)?), - QueryMsg::Health { account_id } => { - to_binary::(&Into::into(compute_health(deps, &env, &account_id)?)) - } - QueryMsg::AllCoinBalances { start_after, limit } => { - to_binary(&query_all_coin_balances(deps, start_after, limit)?) - } - QueryMsg::AllDebtShares { start_after, limit } => { - to_binary(&query_all_debt_shares(deps, start_after, limit)?) - } + QueryMsg::VaultsInfo { + start_after, + limit, + } => to_binary(&query_vaults_info(deps, env, start_after, limit)?), + QueryMsg::AllowedCoins { + start_after, + limit, + } => to_binary(&query_allowed_coins(deps, start_after, limit)?), + QueryMsg::Positions { + account_id, + } => to_binary(&query_positions(deps, &env, &account_id)?), + QueryMsg::Health { + account_id, + } => to_binary::(&Into::into(compute_health(deps, &env, &account_id)?)), + QueryMsg::AllCoinBalances { + start_after, + limit, + } => to_binary(&query_all_coin_balances(deps, start_after, limit)?), + QueryMsg::AllDebtShares { + start_after, + limit, + } => to_binary(&query_all_debt_shares(deps, start_after, limit)?), QueryMsg::TotalDebtShares(denom) => to_binary(&query_total_debt_shares(deps, &denom)?), - QueryMsg::AllTotalDebtShares { start_after, limit } => { - to_binary(&query_all_total_debt_shares(deps, start_after, limit)?) - } - QueryMsg::TotalVaultCoinBalance { vault } => to_binary(&query_total_vault_coin_balance( + QueryMsg::AllTotalDebtShares { + start_after, + limit, + } => to_binary(&query_all_total_debt_shares(deps, start_after, limit)?), + QueryMsg::TotalVaultCoinBalance { + vault, + } => to_binary(&query_total_vault_coin_balance(deps, &vault, &env.contract.address)?), + QueryMsg::AllTotalVaultCoinBalances { + start_after, + limit, + } => to_binary(&query_all_total_vault_coin_balances( deps, - &vault, &env.contract.address, + start_after, + limit, )?), - QueryMsg::AllTotalVaultCoinBalances { start_after, limit } => to_binary( - &query_all_total_vault_coin_balances(deps, &env.contract.address, start_after, limit)?, - ), - QueryMsg::AllVaultPositions { start_after, limit } => { - to_binary(&query_all_vault_positions(deps, start_after, limit)?) - } + QueryMsg::AllVaultPositions { + start_after, + limit, + } => to_binary(&query_all_vault_positions(deps, start_after, limit)?), QueryMsg::EstimateProvideLiquidity { lp_token_out, coins_in, } => to_binary(&estimate_provide_liquidity(deps, &lp_token_out, coins_in)?), - QueryMsg::EstimateWithdrawLiquidity { lp_token } => { - to_binary(&estimate_withdraw_liquidity(deps, lp_token)?) - } + QueryMsg::EstimateWithdrawLiquidity { + lp_token, + } => to_binary(&estimate_withdraw_liquidity(deps, lp_token)?), }; res.map_err(Into::into) } diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index 6f759e22a..3220509eb 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -1,7 +1,8 @@ use cosmwasm_std::{Coin, Response, Storage, Uint128}; - -use mars_rover::coins::Coins; -use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::{ + coins::Coins, + error::{ContractError, ContractResult}, +}; use crate::utils::{assert_coin_is_whitelisted, increment_coin_balance}; @@ -31,9 +32,7 @@ pub fn deposit( /// Assert that fund of exactly the same type and amount was sent along with a message fn assert_sent_fund(expected: &Coin, received_coins: &Coins) -> ContractResult<()> { - let received = received_coins - .amount(&expected.denom) - .unwrap_or_else(Uint128::zero); + let received = received_coins.amount(&expected.denom).unwrap_or_else(Uint128::zero); if received != expected.amount { return Err(ContractError::FundsMismatch { diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index e6a0a84ac..288abd28c 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -1,28 +1,31 @@ use cosmwasm_std::{ to_binary, Addr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdResult, WasmMsg, }; - use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; -use mars_rover::coins::Coins; -use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::msg::execute::{Action, CallbackMsg}; +use mars_rover::{ + coins::Coins, + error::{ContractError, ContractResult}, + msg::execute::{Action, CallbackMsg}, +}; -use crate::borrow::borrow; -use crate::deposit::deposit; -use crate::health::assert_below_max_ltv; -use crate::liquidate_coin::liquidate_coin; -use crate::refund::refund_coin_balances; -use crate::repay::repay; -use crate::state::ACCOUNT_NFT; -use crate::swap::swap_exact_in; -use crate::update_coin_balances::update_coin_balance; -use crate::utils::{assert_is_token_owner, assert_not_contract_in_config}; -use crate::vault::{ - assert_only_one_vault_position, enter_vault, exit_vault, exit_vault_unlocked, liquidate_vault, - request_vault_unlock, update_vault_coin_balance, +use crate::{ + borrow::borrow, + deposit::deposit, + health::assert_below_max_ltv, + liquidate_coin::liquidate_coin, + refund::refund_coin_balances, + repay::repay, + state::ACCOUNT_NFT, + swap::swap_exact_in, + update_coin_balances::update_coin_balance, + utils::{assert_is_token_owner, assert_not_contract_in_config}, + vault::{ + assert_only_one_vault_position, enter_vault, exit_vault, exit_vault_unlocked, + liquidate_vault, request_vault_unlock, update_vault_coin_balance, + }, + withdraw::withdraw, + zap::{provide_liquidity, withdraw_liquidity}, }; -use crate::withdraw::withdraw; -use crate::zap::{provide_liquidity, withdraw_liquidity}; pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; @@ -57,13 +60,7 @@ pub fn dispatch_actions( for action in actions { match action { Action::Deposit(coin) => { - response = deposit( - deps.storage, - response, - account_id, - coin, - &mut received_coins, - )?; + response = deposit(deps.storage, response, account_id, coin, &mut received_coins)?; } Action::Withdraw(coin) => callbacks.push(CallbackMsg::Withdraw { account_id: account_id.to_string(), @@ -78,7 +75,10 @@ pub fn dispatch_actions( account_id: account_id.to_string(), coin: coin.clone(), }), - Action::EnterVault { vault, coin } => callbacks.push(CallbackMsg::EnterVault { + Action::EnterVault { + vault, + coin, + } => callbacks.push(CallbackMsg::EnterVault { account_id: account_id.to_string(), vault: vault.check(deps.api)?, coin: coin.clone(), @@ -115,25 +115,30 @@ pub fn dispatch_actions( denom_out: denom_out.clone(), slippage: *slippage, }), - Action::ExitVault { vault, amount } => callbacks.push(CallbackMsg::ExitVault { + Action::ExitVault { + vault, + amount, + } => callbacks.push(CallbackMsg::ExitVault { account_id: account_id.to_string(), vault: vault.check(deps.api)?, amount: *amount, }), - Action::RequestVaultUnlock { vault, amount } => { - callbacks.push(CallbackMsg::RequestVaultUnlock { - account_id: account_id.to_string(), - vault: vault.check(deps.api)?, - amount: *amount, - }) - } - Action::ExitVaultUnlocked { id, vault } => { - callbacks.push(CallbackMsg::ExitVaultUnlocked { - account_id: account_id.to_string(), - vault: vault.check(deps.api)?, - position_id: *id, - }) - } + Action::RequestVaultUnlock { + vault, + amount, + } => callbacks.push(CallbackMsg::RequestVaultUnlock { + account_id: account_id.to_string(), + vault: vault.check(deps.api)?, + amount: *amount, + }), + Action::ExitVaultUnlocked { + id, + vault, + } => callbacks.push(CallbackMsg::ExitVaultUnlocked { + account_id: account_id.to_string(), + vault: vault.check(deps.api)?, + position_id: *id, + }), Action::ProvideLiquidity { coins_in, lp_token_out, @@ -144,12 +149,12 @@ pub fn dispatch_actions( coins_in: coins_in.clone(), minimum_receive: *minimum_receive, }), - Action::WithdrawLiquidity { lp_token } => { - callbacks.push(CallbackMsg::WithdrawLiquidity { - account_id: account_id.to_string(), - lp_token: lp_token.clone(), - }) - } + Action::WithdrawLiquidity { + lp_token, + } => callbacks.push(CallbackMsg::WithdrawLiquidity { + account_id: account_id.to_string(), + lp_token: lp_token.clone(), + }), Action::RefundAllCoinBalances {} => { callbacks.push(CallbackMsg::RefundAllCoinBalances { account_id: account_id.to_string(), @@ -200,11 +205,17 @@ pub fn execute_callback( coin, recipient, } => withdraw(deps, &account_id, coin, recipient), - CallbackMsg::Borrow { coin, account_id } => borrow(deps, env, &account_id, coin), - CallbackMsg::Repay { account_id, coin } => repay(deps, env, &account_id, &coin), - CallbackMsg::AssertBelowMaxLTV { account_id } => { - assert_below_max_ltv(deps.as_ref(), env, &account_id) - } + CallbackMsg::Borrow { + coin, + account_id, + } => borrow(deps, env, &account_id, coin), + CallbackMsg::Repay { + account_id, + coin, + } => repay(deps, env, &account_id, &coin), + CallbackMsg::AssertBelowMaxLTV { + account_id, + } => assert_below_max_ltv(deps.as_ref(), env, &account_id), CallbackMsg::EnterVault { account_id, vault, @@ -279,23 +290,16 @@ pub fn execute_callback( coins_in, lp_token_out, minimum_receive, - } => provide_liquidity( - deps, - env, - &account_id, - coins_in, - &lp_token_out, - minimum_receive, - ), + } => provide_liquidity(deps, env, &account_id, coins_in, &lp_token_out, minimum_receive), CallbackMsg::WithdrawLiquidity { account_id, lp_token, } => withdraw_liquidity(deps, env, &account_id, &lp_token), - CallbackMsg::AssertOneVaultPositionOnly { account_id } => { - assert_only_one_vault_position(deps, &account_id) - } - CallbackMsg::RefundAllCoinBalances { account_id } => { - refund_coin_balances(deps, env, &account_id) - } + CallbackMsg::AssertOneVaultPositionOnly { + account_id, + } => assert_only_one_vault_position(deps, &account_id), + CallbackMsg::RefundAllCoinBalances { + account_id, + } => refund_coin_balances(deps, env, &account_id), } } diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index a6ba134a7..84680ba74 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -1,16 +1,18 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Decimal, Deps, Env, Event, Response, Uint128}; use mars_health::Health; - use mars_math::FractionMath; -use mars_outpost::oracle::PriceResponse; -use mars_outpost::red_bank::Market; -use mars_rover::adapters::vault::VaultPosition; -use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::msg::query::{DebtAmount, Positions}; - -use crate::query::query_positions; -use crate::state::{ALLOWED_COINS, ORACLE, RED_BANK, VAULT_CONFIGS}; +use mars_outpost::{oracle::PriceResponse, red_bank::Market}; +use mars_rover::{ + adapters::vault::VaultPosition, + error::{ContractError, ContractResult}, + msg::query::{DebtAmount, Positions}, +}; + +use crate::{ + query::query_positions, + state::{ALLOWED_COINS, ORACLE, RED_BANK, VAULT_CONFIGS}, +}; /// Used as storage when trying to compute Health #[cw_serde] @@ -36,10 +38,7 @@ pub fn compute_health(deps: Deps, env: &Env, account_id: &str) -> ContractResult let max_ltv_health_factor = if total_debt_value.is_zero() { None } else { - Some(Decimal::checked_from_ratio( - max_ltv_adjusted_collateral, - total_debt_value, - )?) + Some(Decimal::checked_from_ratio(max_ltv_adjusted_collateral, total_debt_value)?) }; let liquidation_health_factor = if total_debt_value.is_zero() { @@ -95,9 +94,7 @@ fn calculate_vaults_value( for v in vaults { // Unlocked & locked denominated in vault coins let vault_coin_amount = v.amount.unlocked().checked_add(v.amount.locked())?; - let vault_coin_value = v - .vault - .query_value(&deps.querier, &oracle, vault_coin_amount)?; + let vault_coin_value = v.vault.query_value(&deps.querier, &oracle, vault_coin_amount)?; total_collateral_value = total_collateral_value.checked_add(vault_coin_value)?; let config = VAULT_CONFIGS.load(deps.storage, &v.vault.address)?; @@ -110,7 +107,10 @@ fn calculate_vaults_value( // Unlocking positions denominated in underlying token let info = v.vault.query_info(&deps.querier)?; - let PriceResponse { price, .. } = oracle.query_price(&deps.querier, &info.base_token)?; + let PriceResponse { + price, + .. + } = oracle.query_price(&deps.querier, &info.base_token)?; let Market { max_loan_to_value, liquidation_threshold, @@ -205,15 +205,9 @@ pub fn assert_below_max_ltv(deps: Deps, env: Env, account_id: &str) -> ContractR .add_attribute("account_id", account_id) .add_attribute("assets_value", health.total_collateral_value.to_string()) .add_attribute("debts_value", health.total_debt_value.to_string()) - .add_attribute( - "lqdt_health_factor", - val_or_na(health.liquidation_health_factor), - ) + .add_attribute("lqdt_health_factor", val_or_na(health.liquidation_health_factor)) .add_attribute("liquidatable", health.is_liquidatable().to_string()) - .add_attribute( - "max_ltv_health_factor", - val_or_na(health.max_ltv_health_factor), - ) + .add_attribute("max_ltv_health_factor", val_or_na(health.max_ltv_health_factor)) .add_attribute("above_max_ltv", health.is_above_max_ltv().to_string()); Ok(Response::new() diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index c8e4ff570..e0eac027d 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -2,15 +2,13 @@ use std::collections::HashSet; use cosmwasm_std::{Decimal, DepsMut}; use mars_owner::OwnerInit::SetInitialOwner; +use mars_rover::{ + error::{ContractError::InvalidConfig, ContractResult}, + msg::{instantiate::VaultInstantiateConfig, InstantiateMsg}, +}; -use mars_rover::error::ContractError::InvalidConfig; -use mars_rover::error::ContractResult; -use mars_rover::msg::instantiate::VaultInstantiateConfig; -use mars_rover::msg::InstantiateMsg; - -use crate::state::OWNER; use crate::state::{ - ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, RED_BANK, SWAPPER, + ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, SWAPPER, VAULT_CONFIGS, ZAPPER, }; @@ -33,13 +31,11 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; assert_no_duplicate_vaults(&msg.vault_configs)?; - msg.vault_configs - .iter() - .try_for_each(|v| -> ContractResult<_> { - v.config.check()?; - let vault = v.vault.check(deps.api)?; - Ok(VAULT_CONFIGS.save(deps.storage, &vault.address, &v.config)?) - })?; + msg.vault_configs.iter().try_for_each(|v| -> ContractResult<_> { + v.config.check()?; + let vault = v.vault.check(deps.api)?; + Ok(VAULT_CONFIGS.save(deps.storage, &vault.address, &v.config)?) + })?; assert_no_duplicate_coins(&msg.allowed_coins)?; msg.allowed_coins diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index 1b713e15e..19288047b 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -3,16 +3,19 @@ use std::ops::Add; use cosmwasm_std::{ Coin, CosmosMsg, Decimal, DepsMut, Env, QuerierWrapper, Response, StdError, Storage, Uint128, }; - use mars_math::FractionMath; -use mars_rover::adapters::oracle::Oracle; -use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::msg::execute::CallbackMsg; +use mars_rover::{ + adapters::oracle::Oracle, + error::{ContractError, ContractResult}, + msg::execute::CallbackMsg, +}; -use crate::health::{compute_health, val_or_na}; -use crate::repay::current_debt_for_denom; -use crate::state::{COIN_BALANCES, MAX_CLOSE_FACTOR, ORACLE, RED_BANK}; -use crate::utils::{decrement_coin_balance, increment_coin_balance}; +use crate::{ + health::{compute_health, val_or_na}, + repay::current_debt_for_denom, + state::{COIN_BALANCES, MAX_CLOSE_FACTOR, ORACLE, RED_BANK}, + utils::{decrement_coin_balance, increment_coin_balance}, +}; pub fn liquidate_coin( deps: DepsMut, @@ -35,13 +38,8 @@ pub fn liquidate_coin( request_coin_balance, )?; - let repay_msg = repay_debt( - deps.storage, - &env, - liquidator_account_id, - liquidatee_account_id, - &debt, - )?; + let repay_msg = + repay_debt(deps.storage, &env, liquidator_account_id, liquidatee_account_id, &debt)?; // Transfer requested coin from liquidatee to liquidator decrement_coin_balance(deps.storage, liquidatee_account_id, &request)?; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 509cc0864..84bd68fa8 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,21 +1,23 @@ use cosmwasm_std::{Addr, Coin, Deps, Env, Order, StdResult, Uint128}; use cw_storage_plus::Bound; - -use mars_rover::adapters::vault::{Vault, VaultBase, VaultPosition, VaultUnchecked}; -use mars_rover::error::ContractResult; -use mars_rover::msg::query::{ - CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, Positions, SharesResponseItem, - VaultInfoResponse, VaultPositionResponseItem, VaultWithBalance, +use mars_rover::{ + adapters::vault::{Vault, VaultBase, VaultPosition, VaultUnchecked}, + error::ContractResult, + msg::query::{ + CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, Positions, + SharesResponseItem, VaultInfoResponse, VaultPositionResponseItem, VaultWithBalance, + }, }; -use crate::state::OWNER; -use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, - MAX_UNLOCKING_POSITIONS, ORACLE, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, VAULT_CONFIGS, - VAULT_POSITIONS, ZAPPER, +use crate::{ + state::{ + ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, + MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, + VAULT_CONFIGS, VAULT_POSITIONS, ZAPPER, + }, + utils::debt_shares_to_amount, + vault::vault_utilization_in_deposit_cap_denom, }; -use crate::utils::debt_shares_to_amount; -use crate::vault::vault_utilization_in_deposit_cap_denom; const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; @@ -25,9 +27,7 @@ pub fn query_config(deps: Deps) -> ContractResult { Ok(ConfigResponse { owner: owner_res.owner, proposed_new_owner: owner_res.proposed, - account_nft: ACCOUNT_NFT - .may_load(deps.storage)? - .map(|addr| addr.to_string()), + account_nft: ACCOUNT_NFT.may_load(deps.storage)?.map(|addr| addr.to_string()), red_bank: RED_BANK.load(deps.storage)?.address().into(), oracle: ORACLE.load(deps.storage)?.address().into(), max_close_factor: MAX_CLOSE_FACTOR.load(deps.storage)?, @@ -91,7 +91,10 @@ pub fn query_coin_balances(deps: Deps, account_id: &str) -> ContractResult, limit: Option, ) -> StdResult> { - let start = start_after - .as_ref() - .map(|denom| Bound::exclusive(denom.as_str())); + let start = start_after.as_ref().map(|denom| Bound::exclusive(denom.as_str())); let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; @@ -231,9 +232,7 @@ pub fn query_all_total_debt_shares( start_after: Option, limit: Option, ) -> StdResult> { - let start = start_after - .as_ref() - .map(|denom| Bound::exclusive(denom.as_str())); + let start = start_after.as_ref().map(|denom| Bound::exclusive(denom.as_str())); let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; @@ -282,7 +281,10 @@ pub fn query_all_total_vault_coin_balances( let addr = res?; let vault = VaultBase::new(addr); let balance = vault.query_balance(&deps.querier, rover_addr)?; - Ok(VaultWithBalance { vault, balance }) + Ok(VaultWithBalance { + vault, + balance, + }) }) .collect() } diff --git a/contracts/credit-manager/src/refund.rs b/contracts/credit-manager/src/refund.rs index fb814bb87..b3a6280bf 100644 --- a/contracts/credit-manager/src/refund.rs +++ b/contracts/credit-manager/src/refund.rs @@ -1,10 +1,10 @@ use cosmwasm_std::{to_binary, Addr, CosmosMsg, DepsMut, Env, Response, WasmMsg}; -use mars_rover::error::ContractResult; -use mars_rover::msg::execute::CallbackMsg; -use mars_rover::msg::ExecuteMsg; +use mars_rover::{ + error::ContractResult, + msg::{execute::CallbackMsg, ExecuteMsg}, +}; -use crate::query::query_coin_balances; -use crate::utils::query_nft_token_owner; +use crate::{query::query_coin_balances, utils::query_nft_token_owner}; pub fn refund_coin_balances(deps: DepsMut, env: Env, account_id: &str) -> ContractResult { let coins = query_coin_balances(deps.as_ref(), account_id)?; @@ -23,8 +23,7 @@ pub fn refund_coin_balances(deps: DepsMut, env: Env, account_id: &str) -> Contra })) }) .collect::>>()?; - Ok(Response::new().add_messages(withdraw_msgs).add_attribute( - "action", - "rover/credit-manager/callback/refund_coin_balances", - )) + Ok(Response::new() + .add_messages(withdraw_msgs) + .add_attribute("action", "rover/credit-manager/callback/refund_coin_balances")) } diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index e1364abb3..6fd902a6d 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -1,12 +1,15 @@ use std::cmp::min; use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; +use mars_rover::{ + error::{ContractError, ContractResult}, + msg::execute::ActionCoin, +}; -use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::msg::execute::ActionCoin; - -use crate::state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}; -use crate::utils::{debt_shares_to_amount, decrement_coin_balance}; +use crate::{ + state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}, + utils::{debt_shares_to_amount, decrement_coin_balance}, +}; pub fn repay( deps: DepsMut, @@ -85,9 +88,8 @@ pub fn current_debt_for_denom( account_id: &str, denom: &str, ) -> ContractResult<(Uint128, Uint128)> { - let debt_shares = DEBT_SHARES - .load(deps.storage, (account_id, denom)) - .map_err(|_| ContractError::NoDebt)?; + let debt_shares = + DEBT_SHARES.load(deps.storage, (account_id, denom)).map_err(|_| ContractError::NoDebt)?; let coin = debt_shares_to_amount(deps, &env.contract.address, denom, debt_shares)?; Ok((coin.amount, debt_shares)) } diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index e792c0af8..20ba2c70d 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -2,12 +2,13 @@ use cosmwasm_std::{Addr, Decimal, Uint128}; use cw_item_set::Set; use cw_storage_plus::{Item, Map}; use mars_owner::Owner; - -use mars_rover::adapters::oracle::Oracle; -use mars_rover::adapters::red_bank::RedBank; -use mars_rover::adapters::swap::Swapper; -use mars_rover::adapters::vault::{VaultConfig, VaultPositionAmount}; -use mars_rover::adapters::zapper::Zapper; +use mars_rover::adapters::{ + oracle::Oracle, + red_bank::RedBank, + swap::Swapper, + vault::{VaultConfig, VaultPositionAmount}, + zapper::Zapper, +}; use crate::vault::RequestTempStorage; diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs index 592b0162d..91d9c6c1d 100644 --- a/contracts/credit-manager/src/swap.rs +++ b/contracts/credit-manager/src/swap.rs @@ -1,10 +1,13 @@ use cosmwasm_std::{Coin, Decimal, DepsMut, Env, Response, Uint128}; - -use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::msg::execute::{ActionAmount, ActionCoin}; - -use crate::state::{COIN_BALANCES, SWAPPER}; -use crate::utils::{assert_coin_is_whitelisted, decrement_coin_balance, update_balance_msg}; +use mars_rover::{ + error::{ContractError, ContractResult}, + msg::execute::{ActionAmount, ActionCoin}, +}; + +use crate::{ + state::{COIN_BALANCES, SWAPPER}, + utils::{assert_coin_is_whitelisted, decrement_coin_balance, update_balance_msg}, +}; pub fn swap_exact_in( deps: DepsMut, diff --git a/contracts/credit-manager/src/update_coin_balances.rs b/contracts/credit-manager/src/update_coin_balances.rs index 12fecb856..0a6ce8899 100644 --- a/contracts/credit-manager/src/update_coin_balances.rs +++ b/contracts/credit-manager/src/update_coin_balances.rs @@ -2,7 +2,6 @@ use cosmwasm_std::{ Addr, BalanceResponse, BankQuery, Coin, DepsMut, Env, QuerierWrapper, QueryRequest, Response, StdResult, }; - use mars_rover::error::ContractResult; use crate::utils::{decrement_coin_balance, increment_coin_balance}; diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index c973b73bf..6d725a52d 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -1,18 +1,18 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, MessageInfo, Response, WasmMsg}; - use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; use mars_owner::OwnerUpdate; -use mars_rover::error::ContractResult; -use mars_rover::msg::instantiate::ConfigUpdates; -use mars_rover::traits::{FallbackStr, Stringify}; - -use crate::instantiate::{ - assert_lte_to_one, assert_no_duplicate_coins, assert_no_duplicate_vaults, +use mars_rover::{ + error::ContractResult, + msg::instantiate::ConfigUpdates, + traits::{FallbackStr, Stringify}, }; -use crate::state::OWNER; -use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, SWAPPER, - VAULT_CONFIGS, ZAPPER, + +use crate::{ + instantiate::{assert_lte_to_one, assert_no_duplicate_coins, assert_no_duplicate_vaults}, + state::{ + ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, + SWAPPER, VAULT_CONFIGS, ZAPPER, + }, }; pub fn update_config( @@ -45,9 +45,7 @@ pub fn update_config( if let Some(coins) = new_config.allowed_coins { assert_no_duplicate_coins(&coins)?; ALLOWED_COINS.clear(deps.storage); - coins - .iter() - .try_for_each(|denom| ALLOWED_COINS.insert(deps.storage, denom).map(|_| ()))?; + coins.iter().try_for_each(|denom| ALLOWED_COINS.insert(deps.storage, denom).map(|_| ()))?; response = response .add_attribute("key", "allowed_coins") @@ -69,23 +67,20 @@ pub fn update_config( if let Some(unchecked) = new_config.oracle { ORACLE.save(deps.storage, &unchecked.check(deps.api)?)?; - response = response - .add_attribute("key", "oracle") - .add_attribute("value", unchecked.address()); + response = + response.add_attribute("key", "oracle").add_attribute("value", unchecked.address()); } if let Some(unchecked) = new_config.swapper { SWAPPER.save(deps.storage, &unchecked.check(deps.api)?)?; - response = response - .add_attribute("key", "swapper") - .add_attribute("value", unchecked.address()); + response = + response.add_attribute("key", "swapper").add_attribute("value", unchecked.address()); } if let Some(unchecked) = new_config.zapper { ZAPPER.save(deps.storage, &unchecked.check(deps.api)?)?; - response = response - .add_attribute("key", "zapper") - .add_attribute("value", unchecked.address()); + response = + response.add_attribute("key", "zapper").add_attribute("value", unchecked.address()); } if let Some(cf) = new_config.max_close_factor { diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 7b3a90e08..b534b395f 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -1,24 +1,24 @@ -use std::collections::HashSet; -use std::hash::Hash; +use std::{collections::HashSet, hash::Hash}; -use cosmwasm_std::Order::Ascending; use cosmwasm_std::{ - to_binary, Addr, Coin, CosmosMsg, Decimal, Deps, DepsMut, Empty, QuerierWrapper, StdResult, - Storage, Uint128, WasmMsg, + to_binary, Addr, Coin, CosmosMsg, Decimal, Deps, DepsMut, Empty, Order::Ascending, + QuerierWrapper, StdResult, Storage, Uint128, WasmMsg, }; use cw721::OwnerOfResponse; use cw721_base::QueryMsg; - use mars_math::{FractionMath, Fractional}; -use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::msg::execute::CallbackMsg; -use mars_rover::msg::ExecuteMsg; +use mars_rover::{ + error::{ContractError, ContractResult}, + msg::{execute::CallbackMsg, ExecuteMsg}, +}; -use crate::state::{ - ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, - VAULT_CONFIGS, ZAPPER, +use crate::{ + state::{ + ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, + VAULT_CONFIGS, ZAPPER, + }, + update_coin_balances::query_balance, }; -use crate::update_coin_balances::query_balance; pub fn assert_is_token_owner(deps: &DepsMut, user: &Addr, account_id: &str) -> ContractResult<()> { let owner = query_nft_token_owner(deps.as_ref(), account_id)?; @@ -55,9 +55,7 @@ pub fn assert_coins_are_whitelisted( storage: &mut dyn Storage, denoms: Vec<&str>, ) -> ContractResult<()> { - denoms - .iter() - .try_for_each(|denom| assert_coin_is_whitelisted(storage, denom)) + denoms.iter().try_for_each(|denom| assert_coin_is_whitelisted(storage, denom)) } pub fn increment_coin_balance( @@ -80,9 +78,7 @@ pub fn decrement_coin_balance( ) -> ContractResult { let path = COIN_BALANCES.key((account_id, &coin.denom)); let value_opt = path.may_load(storage)?; - let new_value = value_opt - .unwrap_or_else(Uint128::zero) - .checked_sub(coin.amount)?; + let new_value = value_opt.unwrap_or_else(Uint128::zero).checked_sub(coin.amount)?; if new_value.is_zero() { path.remove(storage); } else { @@ -114,10 +110,7 @@ pub fn update_balances_msgs( account_id: &str, denoms: Vec<&str>, ) -> StdResult> { - denoms - .iter() - .map(|denom| update_balance_msg(querier, rover_addr, account_id, denom)) - .collect() + denoms.iter().map(|denom| update_balance_msg(querier, rover_addr, account_id, denom)).collect() } pub fn debt_shares_to_amount( @@ -127,9 +120,7 @@ pub fn debt_shares_to_amount( shares: Uint128, ) -> ContractResult { // total shares of debt issued for denom - let total_debt_shares = TOTAL_DEBT_SHARES - .load(deps.storage, denom) - .unwrap_or(Uint128::zero()); + let total_debt_shares = TOTAL_DEBT_SHARES.load(deps.storage, denom).unwrap_or(Uint128::zero()); // total rover debt amount in Redbank for asset let red_bank = RED_BANK.load(deps.storage)?; @@ -150,9 +141,8 @@ pub fn debt_shares_to_amount( /// which rely on pre-post querying of bank balances of Rover. /// NOTE: https://twitter.com/larry0x/status/1595919149381079041 pub fn assert_not_contract_in_config(deps: &Deps, addr_to_flag: &Addr) -> ContractResult<()> { - let vault_addrs = VAULT_CONFIGS - .keys(deps.storage, None, None, Ascending) - .collect::>>()?; + let vault_addrs = + VAULT_CONFIGS.keys(deps.storage, None, None, Ascending).collect::>>()?; let config_contracts = vec![ ACCOUNT_NFT.load(deps.storage)?, RED_BANK.load(deps.storage)?.address().clone(), @@ -161,10 +151,8 @@ pub fn assert_not_contract_in_config(deps: &Deps, addr_to_flag: &Addr) -> Contra ZAPPER.load(deps.storage)?.address().clone(), ]; - let flagged_addr_in_config = config_contracts - .into_iter() - .chain(vault_addrs) - .any(|addr| addr == *addr_to_flag); + let flagged_addr_in_config = + config_contracts.into_iter().chain(vault_addrs).any(|addr| addr == *addr_to_flag); if flagged_addr_in_config { return Err(ContractError::Unauthorized { diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index f445dc33f..fd994de6f 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -1,17 +1,24 @@ use cosmwasm_std::{ to_binary, Addr, Coin, CosmosMsg, Deps, DepsMut, QuerierWrapper, Response, Uint128, WasmMsg, }; +use mars_rover::{ + adapters::vault::{UpdateType, Vault, VaultPositionUpdate}, + error::{ContractError, ContractResult}, + msg::{ + execute::{ActionAmount, ActionCoin, CallbackMsg}, + ExecuteMsg, + }, +}; -use mars_rover::adapters::vault::{UpdateType, Vault, VaultPositionUpdate}; -use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::msg::execute::{ActionAmount, ActionCoin, CallbackMsg}; -use mars_rover::msg::ExecuteMsg; - -use crate::query::query_vault_positions; -use crate::state::{COIN_BALANCES, ORACLE, VAULT_CONFIGS}; -use crate::utils::{assert_coin_is_whitelisted, decrement_coin_balance}; -use crate::vault::rover_vault_balance_value; -use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; +use crate::{ + query::query_vault_positions, + state::{COIN_BALANCES, ORACLE, VAULT_CONFIGS}, + utils::{assert_coin_is_whitelisted, decrement_coin_balance}, + vault::{ + rover_vault_balance_value, + utils::{assert_vault_is_whitelisted, update_vault_position}, + }, +}; pub fn enter_vault( deps: DepsMut, @@ -83,10 +90,7 @@ pub fn update_vault_coin_balance( Ok(Response::new() .add_attribute("action", "rover/credit-manager/vault/update_balance") - .add_attribute( - "amount_incremented", - current_balance.checked_sub(previous_total_balance)?, - )) + .add_attribute("amount_incremented", current_balance.checked_sub(previous_total_balance)?)) } pub fn assert_denom_matches_vault_reqs( @@ -135,8 +139,6 @@ pub fn assert_only_one_vault_position(deps: DepsMut, account_id: &str) -> Contra return Err(ContractError::OnlyOneVaultPositionAllowed); } - Ok(Response::new().add_attribute( - "action", - "rover/credit-manager/callback/assert_only_one_vault_position", - )) + Ok(Response::new() + .add_attribute("action", "rover/credit-manager/callback/assert_only_one_vault_position")) } diff --git a/contracts/credit-manager/src/vault/exit.rs b/contracts/credit-manager/src/vault/exit.rs index 7041cda6b..524594a02 100644 --- a/contracts/credit-manager/src/vault/exit.rs +++ b/contracts/credit-manager/src/vault/exit.rs @@ -1,9 +1,9 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, Uint128, WasmMsg}; - -use mars_rover::adapters::vault::{UpdateType, Vault, VaultPositionUpdate}; -use mars_rover::error::ContractResult; -use mars_rover::msg::execute::CallbackMsg; -use mars_rover::msg::ExecuteMsg as RoverExecuteMsg; +use mars_rover::{ + adapters::vault::{UpdateType, Vault, VaultPositionUpdate}, + error::ContractResult, + msg::{execute::CallbackMsg, ExecuteMsg as RoverExecuteMsg}, +}; use crate::vault::utils::{ assert_vault_is_whitelisted, query_withdraw_denom_balance, update_vault_position, diff --git a/contracts/credit-manager/src/vault/exit_unlocked.rs b/contracts/credit-manager/src/vault/exit_unlocked.rs index 3bbb3d912..86676be6e 100644 --- a/contracts/credit-manager/src/vault/exit_unlocked.rs +++ b/contracts/credit-manager/src/vault/exit_unlocked.rs @@ -1,13 +1,16 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, WasmMsg}; use cosmwasm_vault_standard::extensions::lockup::UnlockingPosition; -use mars_rover::adapters::vault::{UnlockingChange, Vault, VaultPositionUpdate}; -use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::msg::execute::CallbackMsg; -use mars_rover::msg::ExecuteMsg; +use mars_rover::{ + adapters::vault::{UnlockingChange, Vault, VaultPositionUpdate}, + error::{ContractError, ContractResult}, + msg::{execute::CallbackMsg, ExecuteMsg}, +}; -use crate::state::VAULT_POSITIONS; -use crate::vault::utils::{ - assert_vault_is_whitelisted, query_withdraw_denom_balance, update_vault_position, +use crate::{ + state::VAULT_POSITIONS, + vault::utils::{ + assert_vault_is_whitelisted, query_withdraw_denom_balance, update_vault_position, + }, }; pub fn exit_vault_unlocked( @@ -23,8 +26,10 @@ pub fn exit_vault_unlocked( let matching_unlock = vault_position .get_unlocking_position(position_id) .ok_or_else(|| ContractError::NoPositionMatch(position_id.to_string()))?; - let UnlockingPosition { release_at, .. } = - vault.query_unlocking_position(&deps.querier, matching_unlock.id)?; + let UnlockingPosition { + release_at, + .. + } = vault.query_unlocking_position(&deps.querier, matching_unlock.id)?; if !release_at.is_expired(&env.block) { return Err(ContractError::UnlockNotReady {}); } diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index b9f2eeaf9..d352f5e6b 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -2,17 +2,20 @@ use std::cmp::min; use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; use cosmwasm_vault_standard::VaultInfoResponse; - -use mars_rover::adapters::vault::{ - UnlockingChange, UnlockingPositions, UpdateType, Vault, VaultPositionAmount, VaultPositionType, - VaultPositionUpdate, +use mars_rover::{ + adapters::vault::{ + UnlockingChange, UnlockingPositions, UpdateType, Vault, VaultPositionAmount, + VaultPositionType, VaultPositionUpdate, + }, + error::{ContractError, ContractResult}, }; -use mars_rover::error::{ContractError, ContractResult}; -use crate::liquidate_coin::{calculate_liquidation, repay_debt}; -use crate::state::VAULT_POSITIONS; -use crate::utils::update_balance_msg; -use crate::vault::update_vault_position; +use crate::{ + liquidate_coin::{calculate_liquidation, repay_debt}, + state::VAULT_POSITIONS, + utils::update_balance_msg, + vault::update_vault_position, +}; pub fn liquidate_vault( deps: DepsMut, @@ -23,10 +26,8 @@ pub fn liquidate_vault( request_vault: Vault, position_type: VaultPositionType, ) -> ContractResult { - let liquidatee_position = VAULT_POSITIONS.load( - deps.storage, - (liquidatee_account_id, request_vault.address.clone()), - )?; + let liquidatee_position = VAULT_POSITIONS + .load(deps.storage, (liquidatee_account_id, request_vault.address.clone()))?; match liquidatee_position { VaultPositionAmount::Unlocked(a) => match position_type { @@ -86,13 +87,8 @@ fn liquidate_unlocked( &vault_info, )?; - let repay_msg = repay_debt( - deps.storage, - &env, - liquidator_account_id, - liquidatee_account_id, - &debt, - )?; + let repay_msg = + repay_debt(deps.storage, &env, liquidator_account_id, liquidatee_account_id, &debt)?; update_vault_position( deps.storage, @@ -167,13 +163,8 @@ fn liquidate_unlocking( unlocking_positions.total(), )?; - let repay_msg = repay_debt( - deps.storage, - &env, - liquidator_account_id, - liquidatee_account_id, - &debt, - )?; + let repay_msg = + repay_debt(deps.storage, &env, liquidator_account_id, liquidatee_account_id, &debt)?; let mut total_to_liquidate = request.amount; let mut vault_withdraw_msgs = vec![]; @@ -189,7 +180,10 @@ fn liquidate_unlocking( deps.storage, liquidatee_account_id, &request_vault.address, - VaultPositionUpdate::Unlocking(UnlockingChange::Decrement { id: u.id, amount }), + VaultPositionUpdate::Unlocking(UnlockingChange::Decrement { + id: u.id, + amount, + }), )?; let msg = request_vault.force_withdraw_unlocking_msg(u.id, Some(amount))?; @@ -238,13 +232,8 @@ fn liquidate_locked( &vault_info, )?; - let repay_msg = repay_debt( - deps.storage, - &env, - liquidator_account_id, - liquidatee_account_id, - &debt, - )?; + let repay_msg = + repay_debt(deps.storage, &env, liquidator_account_id, liquidatee_account_id, &debt)?; update_vault_position( deps.storage, diff --git a/contracts/credit-manager/src/vault/mod.rs b/contracts/credit-manager/src/vault/mod.rs index 17fb700ff..fc33d7815 100644 --- a/contracts/credit-manager/src/vault/mod.rs +++ b/contracts/credit-manager/src/vault/mod.rs @@ -1,9 +1,6 @@ -pub use self::enter::*; -pub use self::exit::*; -pub use self::exit_unlocked::*; -pub use self::liquidate_vault::*; -pub use self::request_unlock::*; -pub use self::utils::*; +pub use self::{ + enter::*, exit::*, exit_unlocked::*, liquidate_vault::*, request_unlock::*, utils::*, +}; mod enter; mod exit; diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index 2eb0f7c02..1154c9bec 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -1,15 +1,20 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin, DepsMut, Reply, Response, Uint128}; - -use crate::state::VAULT_REQUEST_TEMP_STORAGE; -use crate::vault::assert_under_max_unlocking_limit; -use mars_rover::adapters::vault::{ - UnlockingChange, UpdateType, Vault, VaultBase, VaultPositionUpdate, VaultUnlockingPosition, +use mars_rover::{ + adapters::vault::{ + UnlockingChange, UpdateType, Vault, VaultBase, VaultPositionUpdate, VaultUnlockingPosition, + }, + error::{ContractError, ContractResult}, + extensions::AttrParse, }; -use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::extensions::AttrParse; -use crate::vault::utils::{assert_vault_is_whitelisted, update_vault_position}; +use crate::{ + state::VAULT_REQUEST_TEMP_STORAGE, + vault::{ + assert_under_max_unlocking_limit, + utils::{assert_vault_is_whitelisted, update_vault_position}, + }, +}; #[cw_serde] pub struct RequestTempStorage { @@ -81,8 +86,6 @@ pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResul VAULT_REQUEST_TEMP_STORAGE.remove(deps.storage); - Ok(Response::new().add_attribute( - "action", - "rover/credit-manager/vault/unlock_request/handle_reply", - )) + Ok(Response::new() + .add_attribute("action", "rover/credit-manager/vault/unlock_request/handle_reply")) } diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 3548b1e63..71b3fafca 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,11 +1,14 @@ use cosmwasm_std::{Addr, Coin, Deps, StdResult, Storage, Uint128}; - use mars_math::FractionMath; -use mars_rover::adapters::vault::{Vault, VaultPositionAmount, VaultPositionUpdate}; -use mars_rover::error::{ContractError, ContractResult}; +use mars_rover::{ + adapters::vault::{Vault, VaultPositionAmount, VaultPositionUpdate}, + error::{ContractError, ContractResult}, +}; -use crate::state::{MAX_UNLOCKING_POSITIONS, ORACLE, VAULT_CONFIGS, VAULT_POSITIONS}; -use crate::update_coin_balances::query_balance; +use crate::{ + state::{MAX_UNLOCKING_POSITIONS, ORACLE, VAULT_CONFIGS, VAULT_POSITIONS}, + update_coin_balances::query_balance, +}; pub fn assert_vault_is_whitelisted(storage: &mut dyn Storage, vault: &Vault) -> ContractResult<()> { let config = VAULT_CONFIGS @@ -46,9 +49,7 @@ pub fn update_vault_position( update: VaultPositionUpdate, ) -> ContractResult { let path = VAULT_POSITIONS.key((account_id, vault_addr.clone())); - let mut amount = path - .may_load(storage)? - .unwrap_or_else(|| update.default_amount()); + let mut amount = path.may_load(storage)?.unwrap_or_else(|| update.default_amount()); amount.update(update)?; @@ -78,9 +79,8 @@ pub fn vault_utilization_in_deposit_cap_denom( let rover_vault_balance_value = rover_vault_balance_value(deps, vault, rover_addr)?; let config = VAULT_CONFIGS.load(deps.storage, &vault.address)?; let oracle = ORACLE.load(deps.storage)?; - let deposit_cap_denom_price = oracle - .query_price(&deps.querier, &config.deposit_cap.denom)? - .price; + let deposit_cap_denom_price = + oracle.query_price(&deps.querier, &config.deposit_cap.denom)?.price; Ok(Coin { denom: config.deposit_cap.denom, diff --git a/contracts/credit-manager/src/withdraw.rs b/contracts/credit-manager/src/withdraw.rs index b727b6e26..61ae7a0f7 100644 --- a/contracts/credit-manager/src/withdraw.rs +++ b/contracts/credit-manager/src/withdraw.rs @@ -1,5 +1,4 @@ use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, DepsMut, Response}; - use mars_rover::error::{ContractError, ContractResult}; use crate::utils::decrement_coin_balance; diff --git a/contracts/credit-manager/src/zap.rs b/contracts/credit-manager/src/zap.rs index b5fe06c87..015fff05b 100644 --- a/contracts/credit-manager/src/zap.rs +++ b/contracts/credit-manager/src/zap.rs @@ -1,13 +1,16 @@ use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; +use mars_rover::{ + error::{ContractError, ContractResult}, + msg::execute::{ActionAmount, ActionCoin}, + traits::Denoms, +}; -use mars_rover::error::{ContractError, ContractResult}; -use mars_rover::msg::execute::{ActionAmount, ActionCoin}; -use mars_rover::traits::Denoms; - -use crate::state::{COIN_BALANCES, ZAPPER}; -use crate::utils::{ - assert_coin_is_whitelisted, assert_coins_are_whitelisted, decrement_coin_balance, - update_balance_msg, update_balances_msgs, +use crate::{ + state::{COIN_BALANCES, ZAPPER}, + utils::{ + assert_coin_is_whitelisted, assert_coins_are_whitelisted, decrement_coin_balance, + update_balance_msg, update_balances_msgs, + }, }; pub fn provide_liquidity( @@ -40,12 +43,8 @@ pub fn provide_liquidity( // After zap is complete, update account's LP token balance let zapper = ZAPPER.load(deps.storage)?; let zap_msg = zapper.provide_liquidity_msg(&updated_coins_in, lp_token_out, minimum_receive)?; - let update_balance_msg = update_balance_msg( - &deps.querier, - &env.contract.address, - account_id, - lp_token_out, - )?; + let update_balance_msg = + update_balance_msg(&deps.querier, &env.contract.address, account_id, lp_token_out)?; Ok(Response::new() .add_message(zap_msg) diff --git a/contracts/credit-manager/tests/helpers/assertions.rs b/contracts/credit-manager/tests/helpers/assertions.rs index 2d2a1a424..7009ce4ee 100644 --- a/contracts/credit-manager/tests/helpers/assertions.rs +++ b/contracts/credit-manager/tests/helpers/assertions.rs @@ -1,8 +1,8 @@ +use std::hash::Hash; + use anyhow::Result as AnyResult; use cw_multi_test::AppResponse; use mars_credit_manager::utils::contents_equal; -use std::hash::Hash; - use mars_rover::error::ContractError; pub fn assert_err(res: AnyResult, err: ContractError) { diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index 6fdfcd555..0b8ec87ab 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -5,9 +5,8 @@ use crate::helpers::{lp_token_info, CoinInfo, VaultTestInfo}; pub fn build_mock_coin_infos(count: usize) -> Vec { (1..=count) - .into_iter() .map(|i| CoinInfo { - denom: format!("coin_{}", i), + denom: format!("coin_{i}"), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), @@ -19,10 +18,9 @@ pub fn build_mock_coin_infos(count: usize) -> Vec { pub fn build_mock_vaults(count: usize) -> Vec { let lp_token = lp_token_info(); (1..=count) - .into_iter() .map(|i| { VaultTestInfo { - vault_token_denom: format!("vault_{}", i), + vault_token_denom: format!("vault_{i}"), lockup: Some(Duration::Time(1_209_600)), // 14 days base_token_denom: lp_token.denom.clone(), deposit_cap: coin(10000000, "uusdc"), diff --git a/contracts/credit-manager/tests/helpers/mock_entity_info.rs b/contracts/credit-manager/tests/helpers/mock_entity_info.rs index 4fc36ca45..64f2b2570 100644 --- a/contracts/credit-manager/tests/helpers/mock_entity_info.rs +++ b/contracts/credit-manager/tests/helpers/mock_entity_info.rs @@ -1,8 +1,8 @@ -use crate::helpers::CoinInfo; -use crate::helpers::VaultTestInfo; use cosmwasm_std::{coin, Decimal}; use cw_utils::Duration; +use crate::helpers::{CoinInfo, VaultTestInfo}; + pub fn uosmo_info() -> CoinInfo { CoinInfo { denom: "uosmo".to_string(), diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index abe10a796..668802991 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -1,43 +1,51 @@ use std::mem::take; use anyhow::Result as AnyResult; -use cosmwasm_std::testing::MockApi; -use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128}; -use cosmwasm_vault_standard::extensions::lockup::{LockupQueryMsg, UnlockingPosition}; -use cosmwasm_vault_standard::msg::ExtensionQueryMsg; -use cosmwasm_vault_standard::msg::VaultStandardQueryMsg::VaultExtension; +use cosmwasm_std::{coins, testing::MockApi, Addr, Coin, Decimal, Uint128}; +use cosmwasm_vault_standard::{ + extensions::lockup::{LockupQueryMsg, UnlockingPosition}, + msg::{ExtensionQueryMsg, VaultStandardQueryMsg::VaultExtension}, +}; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; -use mars_owner::OwnerUpdate; - -use mars_account_nft::config::ConfigUpdates as NftConfigUpdates; -use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; -use mars_account_nft::msg::InstantiateMsg as NftInstantiateMsg; +use mars_account_nft::{ + config::ConfigUpdates as NftConfigUpdates, + msg::{ExecuteMsg as NftExecuteMsg, InstantiateMsg as NftInstantiateMsg}, +}; use mars_health::HealthResponse; use mars_mock_oracle::msg::{ CoinPrice, ExecuteMsg as OracleExecuteMsg, InstantiateMsg as OracleInstantiateMsg, }; use mars_mock_red_bank::msg::{CoinMarketInfo, InstantiateMsg as RedBankInstantiateMsg}; -use mars_mock_vault::contract::DEFAULT_VAULT_TOKEN_PREFUND; -use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; -use mars_outpost::red_bank::QueryMsg::UserDebt; -use mars_outpost::red_bank::UserDebtResponse; -use mars_rover::adapters::oracle::{Oracle, OracleBase, OracleUnchecked}; -use mars_rover::adapters::red_bank::RedBankBase; -use mars_rover::adapters::swap::QueryMsg::EstimateExactInSwap; -use mars_rover::adapters::swap::{ - EstimateExactInSwapResponse, InstantiateMsg as SwapperInstantiateMsg, Swapper, SwapperBase, +use mars_mock_vault::{ + contract::DEFAULT_VAULT_TOKEN_PREFUND, msg::InstantiateMsg as VaultInstantiateMsg, }; -use mars_rover::adapters::vault::{VaultBase, VaultConfig, VaultUnchecked}; -use mars_rover::adapters::zapper::{Zapper, ZapperBase}; -use mars_rover::msg::execute::{Action, CallbackMsg}; -use mars_rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; -use mars_rover::msg::query::{ - CoinBalanceResponseItem, ConfigResponse, DebtShares, Positions, SharesResponseItem, - VaultInfoResponse as RoverVaultInfoResponse, VaultPositionResponseItem, VaultWithBalance, +use mars_outpost::red_bank::{QueryMsg::UserDebt, UserDebtResponse}; +use mars_owner::OwnerUpdate; +use mars_rover::{ + adapters::{ + oracle::{Oracle, OracleBase, OracleUnchecked}, + red_bank::RedBankBase, + swap::{ + EstimateExactInSwapResponse, InstantiateMsg as SwapperInstantiateMsg, + QueryMsg::EstimateExactInSwap, Swapper, SwapperBase, + }, + vault::{VaultBase, VaultConfig, VaultUnchecked}, + zapper::{Zapper, ZapperBase}, + }, + msg::{ + execute::{Action, CallbackMsg}, + instantiate::{ConfigUpdates, VaultInstantiateConfig}, + query::{ + CoinBalanceResponseItem, ConfigResponse, DebtShares, Positions, SharesResponseItem, + VaultInfoResponse as RoverVaultInfoResponse, VaultPositionResponseItem, + VaultWithBalance, + }, + zapper::{ + InstantiateMsg as ZapperInstantiateMsg, LpConfig, QueryMsg::EstimateProvideLiquidity, + }, + ExecuteMsg, InstantiateMsg, QueryMsg, + }, }; -use mars_rover::msg::zapper::QueryMsg::EstimateProvideLiquidity; -use mars_rover::msg::zapper::{InstantiateMsg as ZapperInstantiateMsg, LpConfig}; -use mars_rover::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::helpers::{ lp_token_info, mock_account_nft_contract, mock_oracle_contract, mock_red_bank_contract, @@ -130,7 +138,9 @@ impl MockEnv { self.app.execute_contract( sender.clone(), self.rover.clone(), - &ExecuteMsg::UpdateConfig { new_config }, + &ExecuteMsg::UpdateConfig { + new_config, + }, &[], ) } @@ -232,10 +242,7 @@ impl MockEnv { } pub fn query_config(&self) -> ConfigResponse { - self.app - .wrap() - .query_wasm_smart(self.rover.clone(), &QueryMsg::Config {}) - .unwrap() + self.app.wrap().query_wasm_smart(self.rover.clone(), &QueryMsg::Config {}).unwrap() } pub fn query_vault_configs( @@ -247,7 +254,10 @@ impl MockEnv { .wrap() .query_wasm_smart( self.rover.clone(), - &QueryMsg::VaultsInfo { start_after, limit }, + &QueryMsg::VaultsInfo { + start_after, + limit, + }, ) .unwrap() } @@ -278,7 +288,10 @@ impl MockEnv { .wrap() .query_wasm_smart( self.rover.clone(), - &QueryMsg::AllowedCoins { start_after, limit }, + &QueryMsg::AllowedCoins { + start_after, + limit, + }, ) .unwrap() } @@ -292,7 +305,10 @@ impl MockEnv { .wrap() .query_wasm_smart( self.rover.clone(), - &QueryMsg::AllCoinBalances { start_after, limit }, + &QueryMsg::AllCoinBalances { + start_after, + limit, + }, ) .unwrap() } @@ -306,7 +322,10 @@ impl MockEnv { .wrap() .query_wasm_smart( self.rover.clone(), - &QueryMsg::AllDebtShares { start_after, limit }, + &QueryMsg::AllDebtShares { + start_after, + limit, + }, ) .unwrap() } @@ -320,7 +339,10 @@ impl MockEnv { .wrap() .query_wasm_smart( self.rover.clone(), - &QueryMsg::AllTotalDebtShares { start_after, limit }, + &QueryMsg::AllTotalDebtShares { + start_after, + limit, + }, ) .unwrap() } @@ -328,10 +350,7 @@ impl MockEnv { pub fn query_total_debt_shares(&self, denom: &str) -> DebtShares { self.app .wrap() - .query_wasm_smart( - self.rover.clone(), - &QueryMsg::TotalDebtShares(denom.to_string()), - ) + .query_wasm_smart(self.rover.clone(), &QueryMsg::TotalDebtShares(denom.to_string())) .unwrap() } @@ -374,13 +393,11 @@ impl MockEnv { .wrap() .query_wasm_smart( vault.address.to_string(), - &VaultExtension(ExtensionQueryMsg::Lockup( - LockupQueryMsg::UnlockingPositions { - owner: addr.to_string(), - start_after: None, - limit: None, - }, - )), + &VaultExtension(ExtensionQueryMsg::Lockup(LockupQueryMsg::UnlockingPositions { + owner: addr.to_string(), + start_after: None, + limit: None, + })), ) .unwrap() } @@ -406,7 +423,10 @@ impl MockEnv { .wrap() .query_wasm_smart( self.rover.clone(), - &QueryMsg::AllVaultPositions { start_after, limit }, + &QueryMsg::AllVaultPositions { + start_after, + limit, + }, ) .unwrap() } @@ -420,7 +440,10 @@ impl MockEnv { .wrap() .query_wasm_smart( self.rover.clone(), - &QueryMsg::AllTotalVaultCoinBalances { start_after, limit }, + &QueryMsg::AllTotalVaultCoinBalances { + start_after, + limit, + }, ) .unwrap() } @@ -510,7 +533,9 @@ impl MockEnvBuilder { .execute_contract( self.get_owner(), rover.clone(), - &ExecuteMsg::UpdateConfig { new_config }, + &ExecuteMsg::UpdateConfig { + new_config, + }, &[], ) .unwrap(); @@ -524,11 +549,8 @@ impl MockEnvBuilder { let code_id = self.app.store_code(mock_rover_contract()); let red_bank = self.get_red_bank().into(); let swapper = self.deploy_swapper().into(); - let allowed_coins = self - .get_allowed_coins() - .iter() - .map(|info| info.denom.clone()) - .collect(); + let allowed_coins = + self.get_allowed_coins().iter().map(|info| info.denom.clone()).collect(); let max_close_factor = self.get_max_close_factor(); let max_unlocking_positions = self.get_max_unlocking_positions(); @@ -560,9 +582,7 @@ impl MockEnvBuilder { } fn get_owner(&self) -> Addr { - self.owner - .clone() - .unwrap_or_else(|| Addr::unchecked("owner")) + self.owner.clone().unwrap_or_else(|| Addr::unchecked("owner")) } fn get_oracle(&mut self) -> Oracle { @@ -592,7 +612,9 @@ impl MockEnvBuilder { .instantiate_contract( contract_code_id, Addr::unchecked("oracle_contract_owner"), - &OracleInstantiateMsg { prices }, + &OracleInstantiateMsg { + prices, + }, &[], "mock-oracle", None, @@ -762,13 +784,12 @@ impl MockEnvBuilder { } fn get_max_close_factor(&self) -> Decimal { - self.max_close_factor - .unwrap_or_else(|| Decimal::from_atomics(5u128, 1).unwrap()) // 50% + self.max_close_factor.unwrap_or_else(|| Decimal::from_atomics(5u128, 1).unwrap()) + // 50% } fn get_max_unlocking_positions(&self) -> Uint128 { - self.max_unlocking_positions - .unwrap_or_else(|| Uint128::new(100)) + self.max_unlocking_positions.unwrap_or_else(|| Uint128::new(100)) } //-------------------------------------------------------------------------------------------------- @@ -876,6 +897,5 @@ fn propose_new_nft_minter(app: &mut App, nft_contract: Addr, old_minter: &Addr, proposed_new_minter: Some(new_minter.into()), }, }; - app.execute_contract(old_minter.clone(), nft_contract, &proposal_msg, &[]) - .unwrap(); + app.execute_contract(old_minter.clone(), nft_contract, &proposal_msg, &[]).unwrap(); } diff --git a/contracts/credit-manager/tests/helpers/mod.rs b/contracts/credit-manager/tests/helpers/mod.rs index 6d8106982..74712753e 100644 --- a/contracts/credit-manager/tests/helpers/mod.rs +++ b/contracts/credit-manager/tests/helpers/mod.rs @@ -1,10 +1,6 @@ -pub use self::assertions::*; -pub use self::builders::*; -pub use self::contracts::*; -pub use self::mock_entity_info::*; -pub use self::mock_env::*; -pub use self::types::*; -pub use self::utils::*; +pub use self::{ + assertions::*, builders::*, contracts::*, mock_entity_info::*, mock_env::*, types::*, utils::*, +}; mod assertions; mod builders; diff --git a/contracts/credit-manager/tests/helpers/utils.rs b/contracts/credit-manager/tests/helpers/utils.rs index b704ccd6f..967ea80df 100644 --- a/contracts/credit-manager/tests/helpers/utils.rs +++ b/contracts/credit-manager/tests/helpers/utils.rs @@ -6,9 +6,5 @@ pub fn get_coin(denom: &str, coins: &[Coin]) -> Coin { } pub fn get_debt(denom: &str, coins: &[DebtAmount]) -> DebtAmount { - coins - .iter() - .find(|coin| coin.denom.as_str() == denom) - .unwrap() - .clone() + coins.iter().find(|coin| coin.denom.as_str() == denom).unwrap().clone() } diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index 2f133c8da..3d4d18851 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -1,10 +1,11 @@ use std::ops::{Mul, Sub}; use cosmwasm_std::{coin, coins, Addr, Uint128}; - use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; -use mars_rover::error::ContractError; -use mars_rover::msg::execute::Action::{Borrow, Deposit}; +use mars_rover::{ + error::ContractError, + msg::execute::Action::{Borrow, Deposit}, +}; use crate::helpers::{ assert_err, uosmo_info, AccountToFund, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, @@ -17,10 +18,7 @@ fn test_only_token_owner_can_borrow() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) - .build() - .unwrap(); + let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let another_user = Addr::unchecked("another_user"); @@ -48,17 +46,10 @@ fn test_can_only_borrow_what_is_whitelisted() { let mut mock = MockEnv::new().allowed_coins(&[coin_info]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); - let res = mock.update_credit_account( - &account_id, - &user, - vec![Borrow(coin(234, "usomething"))], - &[], - ); + let res = + mock.update_credit_account(&account_id, &user, vec![Borrow(coin(234, "usomething"))], &[]); - assert_err( - res, - ContractError::NotWhitelisted(String::from("usomething")), - ) + assert_err(res, ContractError::NotWhitelisted(String::from("usomething"))) } #[test] @@ -66,10 +57,7 @@ fn test_borrowing_zero_does_nothing() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) - .build() - .unwrap(); + let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = @@ -103,10 +91,7 @@ fn test_cannot_borrow_above_max_ltv() { let res = mock.update_credit_account( &account_id, &user, - vec![ - Deposit(coin_info.to_coin(300)), - Borrow(coin_info.to_coin(800)), - ], + vec![Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(800))], &[coin(300, coin_info.denom)], ); @@ -158,10 +143,7 @@ fn test_success_when_new_debt_asset() { let debt_shares_res = position.debts.first().unwrap(); assert_eq!(position.debts.len(), 1); - assert_eq!( - debt_shares_res.shares, - Uint128::new(42).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) - ); + assert_eq!(debt_shares_res.shares, Uint128::new(42).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED)); assert_eq!(debt_shares_res.denom, coin_info.denom); let debt_amount = Uint128::new(42) + Uint128::new(1); // simulated yield assert_eq!(debt_shares_res.amount, debt_amount); @@ -171,16 +153,10 @@ fn test_success_when_new_debt_asset() { let config = mock.query_config(); let coin = mock.query_balance(&Addr::unchecked(config.red_bank), &coin_info.denom); - assert_eq!( - coin.amount, - DEFAULT_RED_BANK_COIN_BALANCE.sub(Uint128::new(42)) - ); + assert_eq!(coin.amount, DEFAULT_RED_BANK_COIN_BALANCE.sub(Uint128::new(42))); let res = mock.query_total_debt_shares(&coin_info.denom); - assert_eq!( - res.shares, - Uint128::new(42).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED) - ); + assert_eq!(res.shares, Uint128::new(42).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED)); } #[test] @@ -206,10 +182,7 @@ fn test_debt_shares_with_debt_amount() { mock.update_credit_account( &account_id_a, &user_a, - vec![ - Deposit(coin_info.to_coin(300)), - Borrow(coin_info.to_coin(50)), - ], + vec![Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50))], &[coin(300, coin_info.denom.clone())], ) .unwrap(); @@ -219,10 +192,7 @@ fn test_debt_shares_with_debt_amount() { mock.update_credit_account( &account_id_b, &user_b, - vec![ - Deposit(coin_info.to_coin(450)), - Borrow(coin_info.to_coin(50)), - ], + vec![Deposit(coin_info.to_coin(450)), Borrow(coin_info.to_coin(50))], &[coin(450, coin_info.denom.clone())], ) .unwrap(); @@ -243,8 +213,5 @@ fn test_debt_shares_with_debt_amount() { let total = mock.query_total_debt_shares(&coin_info.denom); - assert_eq!( - total.shares, - debt_position_a.shares + debt_position_b.shares - ); + assert_eq!(total.shares, debt_position_a.shares + debt_position_b.shares); } diff --git a/contracts/credit-manager/tests/test_coin_balances.rs b/contracts/credit-manager/tests/test_coin_balances.rs index 3c9d8ceb1..1a4865b36 100644 --- a/contracts/credit-manager/tests/test_coin_balances.rs +++ b/contracts/credit-manager/tests/test_coin_balances.rs @@ -1,10 +1,9 @@ -use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{coin, coins, Addr, OverflowError}; +use cosmwasm_std::{coin, coins, Addr, OverflowError, OverflowOperation::Sub}; use cw_multi_test::{BankSudo, SudoMsg}; - -use mars_rover::error::ContractError; -use mars_rover::msg::execute::Action::Deposit; -use mars_rover::msg::execute::CallbackMsg; +use mars_rover::{ + error::ContractError, + msg::execute::{Action::Deposit, CallbackMsg}, +}; use crate::helpers::{assert_err, uosmo_info, AccountToFund, MockEnv}; diff --git a/contracts/credit-manager/tests/test_create_credit_account.rs b/contracts/credit-manager/tests/test_create_credit_account.rs index 258df22a5..8049d56e1 100644 --- a/contracts/credit-manager/tests/test_create_credit_account.rs +++ b/contracts/credit-manager/tests/test_create_credit_account.rs @@ -1,8 +1,9 @@ -use crate::helpers::MockEnv; use cosmwasm_std::{Addr, Empty}; use cw721::OwnerOfResponse; use cw721_base::QueryMsg as NftQueryMsg; +use crate::helpers::MockEnv; + pub mod helpers; #[test] diff --git a/contracts/credit-manager/tests/test_deposit.rs b/contracts/credit-manager/tests/test_deposit.rs index 1ae5627f7..81bb9244a 100644 --- a/contracts/credit-manager/tests/test_deposit.rs +++ b/contracts/credit-manager/tests/test_deposit.rs @@ -1,11 +1,9 @@ use cosmwasm_std::{coin, coins, Addr, Coin, Uint128}; - -use mars_rover::coins::Coins; -use mars_rover::error::ContractError::{ - ExtraFundsReceived, FundsMismatch, NotTokenOwner, NotWhitelisted, +use mars_rover::{ + coins::Coins, + error::ContractError::{ExtraFundsReceived, FundsMismatch, NotTokenOwner, NotWhitelisted}, + msg::{execute::Action, query::Positions}, }; -use mars_rover::msg::execute::Action; -use mars_rover::msg::query::Positions; use crate::helpers::{ assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv, @@ -40,10 +38,7 @@ fn test_only_owner_of_token_can_deposit() { fn test_deposit_nothing() { let coin_info = uosmo_info(); - let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) - .build() - .unwrap(); + let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); let user = Addr::unchecked("user"); let account_id = mock.create_credit_account(&user).unwrap(); @@ -66,10 +61,7 @@ fn test_deposit_nothing() { fn test_deposit_but_no_funds() { let coin_info = uosmo_info(); - let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) - .build() - .unwrap(); + let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); let user = Addr::unchecked("user"); let account_id = mock.create_credit_account(&user).unwrap(); @@ -163,10 +155,7 @@ fn test_extra_funds_received() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![ - coin(300, uosmo_info.denom.clone()), - coin(250, uatom_info.denom.clone()), - ], + funds: vec![coin(300, uosmo_info.denom.clone()), coin(250, uatom_info.denom.clone())], }) .build() .unwrap(); @@ -180,10 +169,7 @@ fn test_extra_funds_received() { &[coin(234, uosmo_info.denom), extra_funds.clone()], ); - assert_err( - res, - ExtraFundsReceived(Coins::try_from(vec![extra_funds]).unwrap()), - ); + assert_err(res, ExtraFundsReceived(Coins::try_from(vec![extra_funds]).unwrap())); let res = mock.query_positions(&account_id); assert_eq!(res.deposits.len(), 0); @@ -233,10 +219,7 @@ fn test_multiple_deposit_actions() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![ - coin(300, uosmo_info.denom.clone()), - coin(50, uatom_info.denom.clone()), - ], + funds: vec![coin(300, uosmo_info.denom.clone()), coin(50, uatom_info.denom.clone())], }) .build() .unwrap(); @@ -252,10 +235,7 @@ fn test_multiple_deposit_actions() { Action::Deposit(uosmo_info.to_coin(uosmo_amount.u128())), Action::Deposit(uatom_info.to_coin(uatom_amount.u128())), ], - &[ - coin(234, uosmo_info.denom.clone()), - coin(25, uatom_info.denom.clone()), - ], + &[coin(234, uosmo_info.denom.clone()), coin(25, uatom_info.denom.clone())], ) .unwrap(); @@ -272,8 +252,5 @@ fn test_multiple_deposit_actions() { } fn assert_present(res: &Positions, coin: &CoinInfo, amount: Uint128) { - res.deposits - .iter() - .find(|item| item.denom == coin.denom && item.amount == amount) - .unwrap(); + res.deposits.iter().find(|item| item.denom == coin.denom && item.amount == amount).unwrap(); } diff --git a/contracts/credit-manager/tests/test_dispatch.rs b/contracts/credit-manager/tests/test_dispatch.rs index dfd99f981..3f2cf52b7 100644 --- a/contracts/credit-manager/tests/test_dispatch.rs +++ b/contracts/credit-manager/tests/test_dispatch.rs @@ -1,9 +1,9 @@ use cosmwasm_std::{coin, Addr}; - use helpers::assert_err; -use mars_rover::error::ContractError; -use mars_rover::error::ContractError::NotTokenOwner; -use mars_rover::msg::execute::CallbackMsg; +use mars_rover::{ + error::{ContractError, ContractError::NotTokenOwner}, + msg::execute::CallbackMsg, +}; use crate::helpers::MockEnv; @@ -36,8 +36,7 @@ fn test_nothing_happens_if_no_actions_are_passed() { let res = mock.query_positions(&account_id); assert_eq!(res.deposits.len(), 0); - mock.update_credit_account(&account_id, &user, vec![], &[]) - .unwrap(); + mock.update_credit_account(&account_id, &user, vec![], &[]).unwrap(); let res = mock.query_positions(&account_id); assert_eq!(res.deposits.len(), 0); diff --git a/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs b/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs index 9d89db373..efa23e15d 100644 --- a/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs +++ b/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs @@ -5,10 +5,7 @@ pub mod helpers; #[test] fn test_pagination_on_allowed_coins_query_works() { let allowed_coins = build_mock_coin_infos(32); - let mock = MockEnv::new() - .allowed_coins(&build_mock_coin_infos(32)) - .build() - .unwrap(); + let mock = MockEnv::new().allowed_coins(&build_mock_coin_infos(32)).build().unwrap(); let coins_res = mock.query_allowed_coins(None, Some(58_u32)); @@ -41,7 +38,5 @@ fn test_pagination_on_allowed_coins_query_works() { .collect(); assert_eq!(combined.len(), allowed_coins.len()); - assert!(allowed_coins - .iter() - .all(|item| combined.contains(&item.denom))); + assert!(allowed_coins.iter().all(|item| combined.contains(&item.denom))); } diff --git a/contracts/credit-manager/tests/test_enumerate_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs index 4fac0a75a..eb152dae7 100644 --- a/contracts/credit-manager/tests/test_enumerate_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs @@ -1,7 +1,5 @@ use cosmwasm_std::{coin, Addr, Uint128}; - -use mars_rover::msg::execute::Action; -use mars_rover::msg::query::CoinBalanceResponseItem; +use mars_rover::msg::{execute::Action, query::CoinBalanceResponseItem}; use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; @@ -75,10 +73,7 @@ fn test_pagination_on_all_coin_balances_query_works() { mock.update_credit_account( &account_id_a, &user_a, - user_a_coins - .iter() - .map(|coin| Action::Deposit(coin.clone())) - .collect(), + user_a_coins.iter().map(|coin| Action::Deposit(coin.clone())).collect(), &user_a_coins, ) .unwrap(); @@ -87,10 +82,7 @@ fn test_pagination_on_all_coin_balances_query_works() { mock.update_credit_account( &account_id_b, &user_b, - user_b_coins - .iter() - .map(|coin| Action::Deposit(coin.clone())) - .collect(), + user_b_coins.iter().map(|coin| Action::Deposit(coin.clone())).collect(), &user_b_coins, ) .unwrap(); @@ -99,10 +91,7 @@ fn test_pagination_on_all_coin_balances_query_works() { mock.update_credit_account( &account_id_c, &user_c, - user_c_coins - .iter() - .map(|coin| Action::Deposit(coin.clone())) - .collect(), + user_c_coins.iter().map(|coin| Action::Deposit(coin.clone())).collect(), &user_c_coins, ) .unwrap(); @@ -120,17 +109,23 @@ fn test_pagination_on_all_coin_balances_query_works() { let all_assets_res_a = mock.query_all_coin_balances(None, None); let CoinBalanceResponseItem { - account_id, denom, .. + account_id, + denom, + .. } = all_assets_res_a.last().unwrap().clone(); let all_assets_res_b = mock.query_all_coin_balances(Some((account_id, denom)), None); let CoinBalanceResponseItem { - account_id, denom, .. + account_id, + denom, + .. } = all_assets_res_b.last().unwrap().clone(); let all_assets_res_c = mock.query_all_coin_balances(Some((account_id, denom)), None); let CoinBalanceResponseItem { - account_id, denom, .. + account_id, + denom, + .. } = all_assets_res_c.last().unwrap().clone(); let all_assets_res_d = mock.query_all_coin_balances(Some((account_id, denom)), None); @@ -184,7 +179,5 @@ fn test_pagination_on_all_coin_balances_query_works() { .collect(); assert_eq!(combined_res.len(), combined_starting_vals.len()); - assert!(combined_starting_vals - .iter() - .all(|item| combined_res.contains(item))); + assert!(combined_starting_vals.iter().all(|item| combined_res.contains(item))); } diff --git a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs index b458c15db..7d3072a94 100644 --- a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs @@ -1,8 +1,6 @@ use cosmwasm_std::{coin, Addr}; - use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; -use mars_rover::msg::execute::Action; -use mars_rover::msg::query::SharesResponseItem; +use mars_rover::msg::{execute::Action, query::SharesResponseItem}; use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; @@ -79,10 +77,7 @@ fn test_pagination_on_all_debt_shares_query_works() { user_a_coins .iter() .flat_map(|c| { - vec![ - Action::Deposit(c.clone()), - Action::Borrow(coin(1, c.denom.clone())), - ] + vec![Action::Deposit(c.clone()), Action::Borrow(coin(1, c.denom.clone()))] }) .collect::>(), &user_a_coins, @@ -96,10 +91,7 @@ fn test_pagination_on_all_debt_shares_query_works() { user_b_coins .iter() .flat_map(|c| { - vec![ - Action::Deposit(c.clone()), - Action::Borrow(coin(1, c.denom.clone())), - ] + vec![Action::Deposit(c.clone()), Action::Borrow(coin(1, c.denom.clone()))] }) .collect::>(), &user_b_coins, @@ -113,10 +105,7 @@ fn test_pagination_on_all_debt_shares_query_works() { user_c_coins .iter() .flat_map(|c| { - vec![ - Action::Deposit(c.clone()), - Action::Borrow(coin(1, c.denom.clone())), - ] + vec![Action::Deposit(c.clone()), Action::Borrow(coin(1, c.denom.clone()))] }) .collect::>(), &user_c_coins, @@ -136,17 +125,23 @@ fn test_pagination_on_all_debt_shares_query_works() { let all_debt_shares_res_a = mock.query_all_debt_shares(None, None); let SharesResponseItem { - account_id, denom, .. + account_id, + denom, + .. } = all_debt_shares_res_a.last().unwrap().clone(); let all_debt_shares_res_b = mock.query_all_debt_shares(Some((account_id, denom)), None); let SharesResponseItem { - account_id, denom, .. + account_id, + denom, + .. } = all_debt_shares_res_b.last().unwrap().clone(); let all_debt_shares_res_c = mock.query_all_debt_shares(Some((account_id, denom)), None); let SharesResponseItem { - account_id, denom, .. + account_id, + denom, + .. } = all_debt_shares_res_c.last().unwrap().clone(); let all_debt_shares_res_d = mock.query_all_debt_shares(Some((account_id, denom)), None); @@ -200,7 +195,5 @@ fn test_pagination_on_all_debt_shares_query_works() { .collect(); assert_eq!(combined_res.len(), combined_starting_vals.len()); - assert!(combined_starting_vals - .iter() - .all(|item| combined_res.contains(item))); + assert!(combined_starting_vals.iter().all(|item| combined_res.contains(item))); } diff --git a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs index 74adfa163..9b264a169 100644 --- a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs @@ -1,8 +1,6 @@ use cosmwasm_std::{coin, Addr}; - use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; -use mars_rover::msg::execute::Action; -use mars_rover::msg::query::DebtShares; +use mars_rover::msg::{execute::Action, query::DebtShares}; use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; @@ -79,10 +77,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { user_a_coins .iter() .flat_map(|c| { - vec![ - Action::Deposit(c.clone()), - Action::Borrow(coin(1, c.denom.clone())), - ] + vec![Action::Deposit(c.clone()), Action::Borrow(coin(1, c.denom.clone()))] }) .collect::>(), &user_a_coins, @@ -96,10 +91,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { user_b_coins .iter() .flat_map(|c| { - vec![ - Action::Deposit(c.clone()), - Action::Borrow(coin(1, c.denom.clone())), - ] + vec![Action::Deposit(c.clone()), Action::Borrow(coin(1, c.denom.clone()))] }) .collect::>(), &user_b_coins, @@ -113,10 +105,7 @@ fn test_pagination_on_all_total_debt_shares_query_works() { user_c_coins .iter() .flat_map(|c| { - vec![ - Action::Deposit(c.clone()), - Action::Borrow(coin(1, c.denom.clone())), - ] + vec![Action::Deposit(c.clone()), Action::Borrow(coin(1, c.denom.clone()))] }) .collect::>(), &user_c_coins, @@ -135,13 +124,22 @@ fn test_pagination_on_all_total_debt_shares_query_works() { let all_total_debt_shares_res_a = mock.query_all_total_debt_shares(None, None); - let DebtShares { denom, .. } = all_total_debt_shares_res_a.last().unwrap().clone(); + let DebtShares { + denom, + .. + } = all_total_debt_shares_res_a.last().unwrap().clone(); let all_total_debt_shares_res_b = mock.query_all_total_debt_shares(Some(denom), None); - let DebtShares { denom, .. } = all_total_debt_shares_res_b.last().unwrap().clone(); + let DebtShares { + denom, + .. + } = all_total_debt_shares_res_b.last().unwrap().clone(); let all_total_debt_shares_res_c = mock.query_all_total_debt_shares(Some(denom), None); - let DebtShares { denom, .. } = all_total_debt_shares_res_c.last().unwrap().clone(); + let DebtShares { + denom, + .. + } = all_total_debt_shares_res_c.last().unwrap().clone(); let all_total_debt_shares_res_d = mock.query_all_total_debt_shares(Some(denom), None); // Assert default is observed @@ -191,7 +189,5 @@ fn test_pagination_on_all_total_debt_shares_query_works() { .collect(); assert_eq!(combined_res.len(), combined_starting_vals.len()); - assert!(combined_starting_vals - .iter() - .all(|item| combined_res.contains(item))); + assert!(combined_starting_vals.iter().all(|item| combined_res.contains(item))); } diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index eec986b8c..87a9e4d51 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -1,5 +1,4 @@ use cosmwasm_std::Addr; - use mars_rover::msg::execute::Action; use crate::helpers::{ @@ -46,26 +45,15 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { }); let account_id_a = mock.create_credit_account(&user_a).unwrap(); - mock.update_credit_account( - &account_id_a, - &user_a, - actions.clone(), - &[lp_token.to_coin(220)], - ) - .unwrap(); + mock.update_credit_account(&account_id_a, &user_a, actions.clone(), &[lp_token.to_coin(220)]) + .unwrap(); let account_id_b = mock.create_credit_account(&user_b).unwrap(); - mock.update_credit_account( - &account_id_b, - &user_b, - actions.clone(), - &[lp_token.to_coin(220)], - ) - .unwrap(); + mock.update_credit_account(&account_id_b, &user_b, actions.clone(), &[lp_token.to_coin(220)]) + .unwrap(); let account_id_c = mock.create_credit_account(&user_c).unwrap(); - mock.update_credit_account(&account_id_c, &user_c, actions, &[lp_token.to_coin(220)]) - .unwrap(); + mock.update_credit_account(&account_id_c, &user_c, actions, &[lp_token.to_coin(220)]).unwrap(); let vaults_res = mock.query_all_total_vault_coin_balances(None, Some(58_u32)); // Assert maximum is observed @@ -108,10 +96,7 @@ fn test_pagination_on_all_vault_coin_balances_query_works() { assert_eq!(combined.len(), all_vaults.len()); assert_contents_equal( - &all_vaults - .iter() - .map(|v| v.vault_token_denom.clone()) - .collect::>(), + &all_vaults.iter().map(|v| v.vault_token_denom.clone()).collect::>(), &combined, ) } diff --git a/contracts/credit-manager/tests/test_enumerate_vault_configs.rs b/contracts/credit-manager/tests/test_enumerate_vault_configs.rs index fc7c02291..7f1f23434 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_configs.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_configs.rs @@ -7,10 +7,7 @@ pub mod helpers; #[test] fn test_pagination_on_vault_configs_query_works() { let vault_configs = build_mock_vaults(32); - let mock = MockEnv::new() - .vault_configs(&vault_configs) - .build() - .unwrap(); + let mock = MockEnv::new().vault_configs(&vault_configs).build().unwrap(); let vaults_res = mock.query_vault_configs(None, Some(58_u32)); @@ -51,10 +48,7 @@ fn test_pagination_on_vault_configs_query_works() { assert_eq!(combined.len(), vault_configs.len()); assert_contents_equal( - &vault_configs - .iter() - .map(|v| v.vault_token_denom.clone()) - .collect::>(), + &vault_configs.iter().map(|v| v.vault_token_denom.clone()).collect::>(), &combined, ) } diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index fcfaba032..97ac84477 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -1,6 +1,5 @@ use cosmwasm_std::Addr; use itertools::Itertools; - use mars_rover::msg::execute::Action; use crate::helpers::{ @@ -47,26 +46,15 @@ fn test_pagination_on_all_vault_positions_query_works() { }); let account_id_a = mock.create_credit_account(&user_a).unwrap(); - mock.update_credit_account( - &account_id_a, - &user_a, - actions.clone(), - &[lp_token.to_coin(220)], - ) - .unwrap(); + mock.update_credit_account(&account_id_a, &user_a, actions.clone(), &[lp_token.to_coin(220)]) + .unwrap(); let account_id_b = mock.create_credit_account(&user_b).unwrap(); - mock.update_credit_account( - &account_id_b, - &user_b, - actions.clone(), - &[lp_token.to_coin(220)], - ) - .unwrap(); + mock.update_credit_account(&account_id_b, &user_b, actions.clone(), &[lp_token.to_coin(220)]) + .unwrap(); let account_id_c = mock.create_credit_account(&user_c).unwrap(); - mock.update_credit_account(&account_id_c, &user_c, actions, &[lp_token.to_coin(220)]) - .unwrap(); + mock.update_credit_account(&account_id_c, &user_c, actions, &[lp_token.to_coin(220)]).unwrap(); let vaults_res = mock.query_all_vault_positions(None, Some(58_u32)); // Assert maximum is observed @@ -79,26 +67,17 @@ fn test_pagination_on_all_vault_positions_query_works() { let vaults_res_a = mock.query_all_vault_positions(None, None); let item = vaults_res_a.last().unwrap(); let vaults_res_b = mock.query_all_vault_positions( - Some(( - item.account_id.clone(), - item.position.vault.address.to_string(), - )), + Some((item.account_id.clone(), item.position.vault.address.to_string())), Some(30), ); let item = vaults_res_b.last().unwrap(); let vaults_res_c = mock.query_all_vault_positions( - Some(( - item.account_id.clone(), - item.position.vault.address.to_string(), - )), + Some((item.account_id.clone(), item.position.vault.address.to_string())), Some(30), ); let item = vaults_res_c.last().unwrap(); let vaults_res_d = mock.query_all_vault_positions( - Some(( - item.account_id.clone(), - item.position.vault.address.to_string(), - )), + Some((item.account_id.clone(), item.position.vault.address.to_string())), None, ); @@ -123,10 +102,7 @@ fn test_pagination_on_all_vault_positions_query_works() { assert_eq!(deduped.len(), all_vaults.len()); assert_contents_equal( - &all_vaults - .iter() - .map(|v| v.vault_token_denom.clone()) - .collect::>(), + &all_vaults.iter().map(|v| v.vault_token_denom.clone()).collect::>(), &deduped, ) } diff --git a/contracts/credit-manager/tests/test_fields_vault_limit.rs b/contracts/credit-manager/tests/test_fields_vault_limit.rs index 6877d7b38..1e5c1c2ec 100644 --- a/contracts/credit-manager/tests/test_fields_vault_limit.rs +++ b/contracts/credit-manager/tests/test_fields_vault_limit.rs @@ -1,6 +1,8 @@ use cosmwasm_std::{coin, Addr, Decimal}; -use mars_rover::error::ContractError; -use mars_rover::msg::execute::Action::{Deposit, EnterVault}; +use mars_rover::{ + error::ContractError, + msg::execute::Action::{Deposit, EnterVault}, +}; use crate::helpers::{ assert_err, lp_token_info, unlocked_vault_info, AccountToFund, CoinInfo, MockEnv, VaultTestInfo, diff --git a/contracts/credit-manager/tests/test_flagged_contract.rs b/contracts/credit-manager/tests/test_flagged_contract.rs index b08a3bc51..7c1d45cad 100644 --- a/contracts/credit-manager/tests/test_flagged_contract.rs +++ b/contracts/credit-manager/tests/test_flagged_contract.rs @@ -1,8 +1,6 @@ use cosmwasm_std::{coin, Addr}; - use helpers::assert_err; -use mars_rover::error::ContractError::Unauthorized; -use mars_rover::msg::execute::Action; +use mars_rover::{error::ContractError::Unauthorized, msg::execute::Action}; use crate::helpers::MockEnv; diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index bea6284b8..ae6a7681f 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -1,14 +1,17 @@ use std::ops::{Add, Mul}; use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128}; - use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use mars_math::{FractionMath, Fractional}; use mars_mock_oracle::msg::CoinPrice; -use mars_rover::error::ContractError; -use mars_rover::msg::execute::Action::{Borrow, Deposit}; -use mars_rover::msg::instantiate::ConfigUpdates; -use mars_rover::msg::query::DebtAmount; +use mars_rover::{ + error::ContractError, + msg::{ + execute::Action::{Borrow, Deposit}, + instantiate::ConfigUpdates, + query::DebtAmount, + }, +}; use crate::helpers::{ assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv, @@ -112,23 +115,17 @@ fn test_terra_ragnarok() { assert_eq!(position.debts.len(), 1); let health = mock.query_health(&account_id); - let assets_value = (deposit_amount + borrow_amount) - .checked_mul_floor(coin_info.price) - .unwrap(); + let assets_value = (deposit_amount + borrow_amount).checked_mul_floor(coin_info.price).unwrap(); assert_eq!(health.total_collateral_value, assets_value); // Note: Simulated yield from mock_red_bank makes debt position more expensive - let debts_value = borrow_amount - .add(Uint128::new(1)) - .checked_mul_floor(coin_info.price) - .unwrap(); + let debts_value = + borrow_amount.add(Uint128::new(1)).checked_mul_floor(coin_info.price).unwrap(); assert_eq!(health.total_debt_value, debts_value); assert_eq!( health.liquidation_health_factor, Some(Decimal::from_ratio( - assets_value - .checked_mul_floor(coin_info.liquidation_threshold) - .unwrap(), + assets_value.checked_mul_floor(coin_info.liquidation_threshold).unwrap(), debts_value )) ); @@ -179,12 +176,8 @@ fn test_debts_no_assets() { .unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); - let res = mock.update_credit_account( - &account_id, - &user, - vec![Borrow(coin_info.to_coin(100))], - &[], - ); + let res = + mock.update_credit_account(&account_id, &user, vec![Borrow(coin_info.to_coin(100))], &[]); assert_err( res, @@ -238,10 +231,7 @@ fn test_cannot_borrow_more_than_healthy() { mock.update_credit_account( &account_id, &user, - vec![ - Deposit(coin_info.to_coin(300)), - Borrow(coin_info.to_coin(50)), - ], + vec![Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50))], &[Coin::new(Uint128::new(300).into(), coin_info.denom.clone())], ) .unwrap(); @@ -256,31 +246,16 @@ fn test_cannot_borrow_more_than_healthy() { assert_eq!(health.total_collateral_value, assets_value); let debts_value = Uint128::new(120); assert_eq!(health.total_debt_value, debts_value); - assert_eq!( - health.liquidation_health_factor, - Some(Decimal::from_ratio(454u128, 120u128)) - ); - assert_eq!( - health.max_ltv_health_factor, - Some(Decimal::from_ratio(413u128, 120u128)) - ); + assert_eq!(health.liquidation_health_factor, Some(Decimal::from_ratio(454u128, 120u128))); + assert_eq!(health.max_ltv_health_factor, Some(Decimal::from_ratio(413u128, 120u128))); assert!(!health.liquidatable); assert!(!health.above_max_ltv); - mock.update_credit_account( - &account_id, - &user, - vec![Borrow(coin_info.to_coin(100))], - &[], - ) - .unwrap(); + mock.update_credit_account(&account_id, &user, vec![Borrow(coin_info.to_coin(100))], &[]) + .unwrap(); - let res = mock.update_credit_account( - &account_id, - &user, - vec![Borrow(coin_info.to_coin(150))], - &[], - ); + let res = + mock.update_credit_account(&account_id, &user, vec![Borrow(coin_info.to_coin(150))], &[]); assert_err( res, @@ -296,14 +271,8 @@ fn test_cannot_borrow_more_than_healthy() { assert_eq!(health.total_collateral_value, assets_value); let debts_value = Uint128::new(359); assert_eq!(health.total_debt_value, debts_value); - assert_eq!( - health.liquidation_health_factor, - Some(Decimal::from_ratio(585u128, 359u128)) - ); - assert_eq!( - health.max_ltv_health_factor, - Some(Decimal::from_ratio(532u128, 359u128)) - ); + assert_eq!(health.liquidation_health_factor, Some(Decimal::from_ratio(585u128, 359u128))); + assert_eq!(health.max_ltv_health_factor, Some(Decimal::from_ratio(532u128, 359u128))); assert!(!health.liquidatable); assert!(!health.above_max_ltv); } @@ -350,10 +319,7 @@ fn test_cannot_borrow_more_but_not_liquidatable() { mock.update_credit_account( &account_id, &user, - vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(50)), - ], + vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(50))], &[Coin::new(300, uosmo_info.denom)], ) .unwrap(); @@ -472,9 +438,7 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { health.liquidation_health_factor, Some(Decimal::from_ratio( lqdt_adjusted_assets_value, - (borrowed_amount + Uint128::one()) - .checked_mul_floor(uatom_info.price) - .unwrap() + (borrowed_amount + Uint128::one()).checked_mul_floor(uatom_info.price).unwrap() )) ); let ltv_adjusted_assets_value = deposit_amount @@ -493,9 +457,7 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { health.max_ltv_health_factor, Some(Decimal::from_ratio( ltv_adjusted_assets_value, - (borrowed_amount + Uint128::one()) - .checked_mul_floor(uatom_info.price) - .unwrap() + (borrowed_amount + Uint128::one()).checked_mul_floor(uatom_info.price).unwrap() )) ); assert!(!health.liquidatable); @@ -554,10 +516,7 @@ fn test_debt_value() { Borrow(uosmo_info.to_coin(user_a_borrowed_amount_osmo.u128())), Deposit(uosmo_info.to_coin(user_a_deposit_amount_osmo.u128())), ], - &[Coin::new( - user_a_deposit_amount_osmo.into(), - uosmo_info.denom.clone(), - )], + &[Coin::new(user_a_deposit_amount_osmo.into(), uosmo_info.denom.clone())], ) .unwrap(); @@ -573,10 +532,7 @@ fn test_debt_value() { Borrow(uatom_info.to_coin(user_b_borrowed_amount_atom.u128())), Deposit(uosmo_info.to_coin(user_b_deposit_amount.u128())), ], - &[Coin::new( - user_b_deposit_amount.into(), - uosmo_info.denom.clone(), - )], + &[Coin::new(user_b_deposit_amount.into(), uosmo_info.denom.clone())], ) .unwrap(); @@ -593,40 +549,25 @@ fn test_debt_value() { let user_a_debt_shares_atom = user_a_borrowed_amount_atom.mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED); - assert_eq!( - user_a_debt_shares_atom, - find_by_denom(&uatom_info.denom, &position_a.debts).shares - ); + assert_eq!(user_a_debt_shares_atom, find_by_denom(&uatom_info.denom, &position_a.debts).shares); let position_b = mock.query_positions(&account_id_b); let user_b_debt_shares_atom = user_a_debt_shares_atom .multiply_ratio(user_b_borrowed_amount_atom, interim_red_bank_debt.amount); - assert_eq!( - user_b_debt_shares_atom, - find_by_denom(&uatom_info.denom, &position_b.debts).shares - ); + assert_eq!(user_b_debt_shares_atom, find_by_denom(&uatom_info.denom, &position_b.debts).shares); let red_bank_atom_res = mock.query_total_debt_shares(&uatom_info.denom); - assert_eq!( - red_bank_atom_res.shares, - user_a_debt_shares_atom + user_b_debt_shares_atom - ); + assert_eq!(red_bank_atom_res.shares, user_a_debt_shares_atom + user_b_debt_shares_atom); let user_a_owed_atom = red_bank_atom_debt .amount - .checked_mul_ceil(Fractional( - user_a_debt_shares_atom, - red_bank_atom_res.shares, - )) - .unwrap(); - let user_a_owed_atom_value = user_a_owed_atom - .checked_mul_floor(uatom_info.price) + .checked_mul_ceil(Fractional(user_a_debt_shares_atom, red_bank_atom_res.shares)) .unwrap(); + let user_a_owed_atom_value = user_a_owed_atom.checked_mul_floor(uatom_info.price).unwrap(); - let osmo_debt_value = (user_a_borrowed_amount_osmo + Uint128::one()) - .checked_mul_floor(uosmo_info.price) - .unwrap(); + let osmo_debt_value = + (user_a_borrowed_amount_osmo + Uint128::one()).checked_mul_floor(uosmo_info.price).unwrap(); let total_debt_value = user_a_owed_atom_value.add(osmo_debt_value); assert_eq!(health.total_debt_value, total_debt_value); @@ -647,10 +588,7 @@ fn test_debt_value() { assert_eq!( health.liquidation_health_factor, - Some(Decimal::from_ratio( - lqdt_adjusted_assets_value, - total_debt_value - )) + Some(Decimal::from_ratio(lqdt_adjusted_assets_value, total_debt_value)) ); let ltv_adjusted_assets_value = user_a_deposit_amount_osmo @@ -668,10 +606,7 @@ fn test_debt_value() { ); assert_eq!( health.max_ltv_health_factor, - Some(Decimal::from_ratio( - ltv_adjusted_assets_value, - total_debt_value - )) + Some(Decimal::from_ratio(ltv_adjusted_assets_value, total_debt_value)) ); } @@ -694,10 +629,7 @@ fn test_delisted_assets_drop_max_ltv() { mock.update_credit_account( &account_id, &user, - vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(100)), - ], + vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(100))], &[uosmo_info.to_coin(300)], ) .unwrap(); @@ -719,15 +651,9 @@ fn test_delisted_assets_drop_max_ltv() { // Values should be the same assert_eq!(prev_health.total_debt_value, curr_health.total_debt_value); - assert_eq!( - prev_health.total_collateral_value, - curr_health.total_collateral_value - ); + assert_eq!(prev_health.total_collateral_value, curr_health.total_collateral_value); - assert_eq!( - prev_health.liquidation_health_factor, - curr_health.liquidation_health_factor - ); + assert_eq!(prev_health.liquidation_health_factor, curr_health.liquidation_health_factor); assert_eq!( prev_health.liquidation_threshold_adjusted_collateral, curr_health.liquidation_threshold_adjusted_collateral @@ -736,18 +662,9 @@ fn test_delisted_assets_drop_max_ltv() { // Should have been changed due to de-listing assert_ne!(prev_health.above_max_ltv, curr_health.above_max_ltv); - assert_ne!( - prev_health.max_ltv_adjusted_collateral, - curr_health.max_ltv_adjusted_collateral - ); - assert_ne!( - prev_health.max_ltv_health_factor, - curr_health.max_ltv_health_factor - ); - assert_eq!( - curr_health.max_ltv_health_factor, - Some(Decimal::raw(811881188118811881u128)) - ); + assert_ne!(prev_health.max_ltv_adjusted_collateral, curr_health.max_ltv_adjusted_collateral); + assert_ne!(prev_health.max_ltv_health_factor, curr_health.max_ltv_health_factor); + assert_eq!(curr_health.max_ltv_health_factor, Some(Decimal::raw(811881188118811881u128))); } fn find_by_denom<'a>(denom: &'a str, shares: &'a [DebtAmount]) -> &'a DebtAmount { diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index fcfaa4045..0fb57f813 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -60,25 +60,17 @@ fn test_vault_configs_set_on_instantiate() { }, ]; - let mock = MockEnv::new() - .vault_configs(&vault_configs) - .build() - .unwrap(); + let mock = MockEnv::new().vault_configs(&vault_configs).build().unwrap(); let res = mock.query_vault_configs(None, None); assert_contents_equal( &res.iter().map(|v| v.vault.clone()).collect::>(), - &vault_configs - .iter() - .map(|info| mock.get_vault(info)) - .collect::>(), + &vault_configs.iter().map(|info| mock.get_vault(info)).collect::>(), ); } #[test] fn test_raises_on_invalid_vaults_addr() { - let mock = MockEnv::new() - .pre_deployed_vault("%%%INVALID%%%", &unlocked_vault_info()) - .build(); + let mock = MockEnv::new().pre_deployed_vault("%%%INVALID%%%", &unlocked_vault_info()).build(); if mock.is_ok() { panic!("Should have thrown an error"); @@ -149,18 +141,12 @@ fn test_allowed_coins_set_on_instantiate() { liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), }, ]; - let mock = MockEnv::new() - .allowed_coins(&allowed_coins) - .build() - .unwrap(); + let mock = MockEnv::new().allowed_coins(&allowed_coins).build().unwrap(); let res = mock.query_allowed_coins(None, None); assert_contents_equal( &res, - &allowed_coins - .iter() - .map(|info| info.denom.clone()) - .collect::>(), + &allowed_coins.iter().map(|info| info.denom.clone()).collect::>(), ) } @@ -215,9 +201,7 @@ fn test_max_close_factor_set_on_instantiate() { #[test] fn test_max_close_factor_validated() { - let mock = MockEnv::new() - .max_close_factor(Decimal::from_atomics(1244u128, 3).unwrap()) - .build(); + let mock = MockEnv::new().max_close_factor(Decimal::from_atomics(1244u128, 3).unwrap()).build(); if mock.is_ok() { panic!("Should have thrown an error: Max close factor should be below 1"); diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 6f8c2bf0f..0b9ddef8d 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -1,9 +1,12 @@ use cosmwasm_std::{coins, Addr, Coin, Decimal, OverflowError, OverflowOperation, Uint128}; - use mars_mock_oracle::msg::CoinPrice; -use mars_rover::error::ContractError; -use mars_rover::error::ContractError::{AboveMaxLTV, LiquidationNotProfitable, NotLiquidatable}; -use mars_rover::msg::execute::Action::{Borrow, Deposit, EnterVault, LiquidateCoin}; +use mars_rover::{ + error::{ + ContractError, + ContractError::{AboveMaxLTV, LiquidationNotProfitable, NotLiquidatable}, + }, + msg::execute::Action::{Borrow, Deposit, EnterVault, LiquidateCoin}, +}; use crate::helpers::{ assert_err, get_coin, get_debt, lp_token_info, uatom_info, ujake_info, unlocked_vault_info, @@ -34,10 +37,7 @@ fn test_can_only_liquidate_unhealthy_accounts() { mock.update_credit_account( &liquidatee_account_id, &liquidatee, - vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(50)), - ], + vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(50))], &[Coin::new(300, uosmo_info.clone().denom)], ) .unwrap(); @@ -149,10 +149,7 @@ fn test_liquidatee_does_not_have_requested_asset() { mock.update_credit_account( &liquidatee_account_id, &liquidatee, - vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(105)), - ], + vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(105))], &[Coin::new(300, uosmo_info.denom)], ) .unwrap(); @@ -210,10 +207,7 @@ fn test_liquidatee_does_not_have_debt_coin() { mock.update_credit_account( &liquidatee_account_id, &liquidatee, - vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(105)), - ], + vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(105))], &[Coin::new(300, uosmo_info.denom.clone())], ) .unwrap(); @@ -226,10 +220,7 @@ fn test_liquidatee_does_not_have_debt_coin() { mock.update_credit_account( &random_user_token, &random_user, - vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(ujake_info.to_coin(10)), - ], + vec![Deposit(uosmo_info.to_coin(300)), Borrow(ujake_info.to_coin(10))], &[Coin::new(300, uosmo_info.denom)], ) .unwrap(); @@ -278,10 +269,7 @@ fn test_liquidator_does_not_have_enough_to_pay_debt() { mock.update_credit_account( &liquidatee_account_id, &liquidatee, - vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(100)), - ], + vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(100))], &[Coin::new(300, uosmo_info.clone().denom)], ) .unwrap(); @@ -337,10 +325,7 @@ fn test_liquidator_left_in_unhealthy_state() { mock.update_credit_account( &liquidatee_account_id, &liquidatee, - vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(100)), - ], + vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(100))], &[Coin::new(300, uosmo_info.clone().denom)], ) .unwrap(); @@ -471,10 +456,7 @@ fn test_debt_amount_adjusted_to_close_factor_max() { mock.update_credit_account( &liquidatee_account_id, &liquidatee, - vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(100)), - ], + vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(100))], &[Coin::new(300, uosmo_info.denom.clone())], ) .unwrap(); @@ -626,10 +608,7 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { mock.update_credit_account( &liquidatee_account_id, &liquidatee, - vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(100)), - ], + vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(100))], &[Coin::new(300, uosmo_info.denom.clone())], ) .unwrap(); @@ -702,10 +681,7 @@ fn test_debt_amount_no_adjustment() { mock.update_credit_account( &liquidatee_account_id, &liquidatee, - vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(100)), - ], + vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(100))], &[Coin::new(300, uosmo_info.denom.clone())], ) .unwrap(); diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 15fc6d9bd..739174287 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -1,12 +1,11 @@ -use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::StdError::NotFound; -use cosmwasm_std::{Addr, Decimal, OverflowError, Uint128}; - +use cosmwasm_std::{ + Addr, Decimal, OverflowError, OverflowOperation::Sub, StdError::NotFound, Uint128, +}; use mars_mock_oracle::msg::CoinPrice; -use mars_rover::adapters::vault::{VaultBase, VaultPositionType}; -use mars_rover::error::ContractError; -use mars_rover::msg::execute::Action::{ - Borrow, Deposit, EnterVault, LiquidateVault, RequestVaultUnlock, +use mars_rover::{ + adapters::vault::{VaultBase, VaultPositionType}, + error::ContractError, + msg::execute::Action::{Borrow, Deposit, EnterVault, LiquidateVault, RequestVaultUnlock}, }; use crate::helpers::{ @@ -575,26 +574,8 @@ fn test_liquidate_unlocking_liquidation_order() { // Third bucket partially liquidated: 11 of 20 // Fourth bucket retained: 0 of 168 assert_eq!(vault_amount.unlocking().positions().len(), 2); - assert_eq!( - vault_amount - .unlocking() - .positions() - .first() - .unwrap() - .coin - .amount, - Uint128::new(9) - ); - assert_eq!( - vault_amount - .unlocking() - .positions() - .get(1) - .unwrap() - .coin - .amount, - Uint128::new(168) - ); + assert_eq!(vault_amount.unlocking().positions().first().unwrap().coin.amount, Uint128::new(9)); + assert_eq!(vault_amount.unlocking().positions().get(1).unwrap().coin.amount, Uint128::new(168)); assert_eq!(position.deposits.len(), 1); let jake_balance = get_coin("ujake", &position.deposits); diff --git a/contracts/credit-manager/tests/test_refund_balances.rs b/contracts/credit-manager/tests/test_refund_balances.rs index e67f0d728..67fabd7fe 100644 --- a/contracts/credit-manager/tests/test_refund_balances.rs +++ b/contracts/credit-manager/tests/test_refund_balances.rs @@ -1,5 +1,4 @@ use cosmwasm_std::{coin, Addr, Uint128}; - use mars_rover::msg::execute::Action::{Deposit, EnterVault, RefundAllCoinBalances}; use crate::helpers::{ @@ -18,10 +17,7 @@ fn test_refund_coin_balances_when_balances() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![ - coin(234, uosmo_info.denom.clone()), - coin(25, uatom_info.denom.clone()), - ], + funds: vec![coin(234, uosmo_info.denom.clone()), coin(25, uatom_info.denom.clone())], }) .build() .unwrap(); diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index fc7b0f94c..44a4b2324 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -1,10 +1,11 @@ use std::ops::{Add, Mul, Sub}; use cosmwasm_std::{coin, coins, Addr, Decimal, OverflowError, OverflowOperation, Uint128}; - use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; -use mars_rover::error::ContractError; -use mars_rover::msg::execute::Action::{Borrow, Deposit, Repay, Withdraw}; +use mars_rover::{ + error::ContractError, + msg::execute::Action::{Borrow, Deposit, Repay, Withdraw}, +}; use crate::helpers::{ assert_err, uosmo_info, AccountToFund, CoinInfo, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, @@ -40,10 +41,7 @@ fn test_only_token_owner_can_repay() { fn test_repaying_with_zero_debt_raises() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) - .build() - .unwrap(); + let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); // When passing some amount @@ -102,10 +100,7 @@ fn test_raises_when_repaying_what_is_not_owed() { mock.update_credit_account( &account_id_b, &user_b, - vec![ - Deposit(uatom_info.to_coin(100)), - Borrow(uatom_info.to_coin(12)), - ], + vec![Deposit(uatom_info.to_coin(100)), Borrow(uatom_info.to_coin(12))], &[uatom_info.to_coin(100)], ) .unwrap(); @@ -195,23 +190,15 @@ fn test_repay_less_than_total_debt() { mock.update_credit_account( &account_id, &user, - vec![ - Deposit(coin_info.to_coin(300)), - Borrow(coin_info.to_coin(50)), - ], + vec![Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50))], &[coin(300, coin_info.denom.clone())], ) .unwrap(); let interim_red_bank_debt = mock.query_red_bank_debt(&coin_info.denom); - mock.update_credit_account( - &account_id, - &user, - vec![Repay(coin_info.to_action_coin(20))], - &[], - ) - .unwrap(); + mock.update_credit_account(&account_id, &user, vec![Repay(coin_info.to_action_coin(20))], &[]) + .unwrap(); let position = mock.query_positions(&account_id); assert_eq!(position.deposits.len(), 1); @@ -238,10 +225,7 @@ fn test_repay_less_than_total_debt() { let config = mock.query_config(); let red_bank_addr = Addr::unchecked(config.red_bank); let coin = mock.query_balance(&red_bank_addr, &coin_info.denom); - assert_eq!( - coin.amount, - DEFAULT_RED_BANK_COIN_BALANCE.sub(Uint128::new(30)) - ); + assert_eq!(coin.amount, DEFAULT_RED_BANK_COIN_BALANCE.sub(Uint128::new(30))); mock.update_credit_account( &account_id, @@ -266,10 +250,7 @@ fn test_repay_less_than_total_debt() { let coin = mock.query_balance(&mock.rover, &coin_info.denom); assert_eq!(coin.amount, Uint128::new(299)); let coin = mock.query_balance(&red_bank_addr, &coin_info.denom); - assert_eq!( - coin.amount, - DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1)) - ); + assert_eq!(coin.amount, DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1))); } #[test] @@ -317,10 +298,7 @@ fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { let config = mock.query_config(); let coin = mock.query_balance(&Addr::unchecked(config.red_bank), &coin_info.denom); - assert_eq!( - coin.amount, - DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1)) - ); + assert_eq!(coin.amount, DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1))); } #[test] @@ -363,8 +341,5 @@ fn test_amount_none_repays_total_debt() { let config = mock.query_config(); let coin = mock.query_balance(&Addr::unchecked(config.red_bank), &coin_info.denom); - assert_eq!( - coin.amount, - DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1)) - ); + assert_eq!(coin.amount, DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1))); } diff --git a/contracts/credit-manager/tests/test_swap.rs b/contracts/credit-manager/tests/test_swap.rs index 822bb5a34..be8a5504f 100644 --- a/contracts/credit-manager/tests/test_swap.rs +++ b/contracts/credit-manager/tests/test_swap.rs @@ -1,9 +1,11 @@ -use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{coins, Addr, Coin, Decimal, OverflowError, Uint128}; - -use mars_rover::error::ContractError; -use mars_rover::msg::execute::Action::{Deposit, SwapExactIn}; -use mars_rover::msg::execute::{ActionAmount, ActionCoin}; +use cosmwasm_std::{coins, Addr, Coin, Decimal, OverflowError, OverflowOperation::Sub, Uint128}; +use mars_rover::{ + error::ContractError, + msg::execute::{ + Action::{Deposit, SwapExactIn}, + ActionAmount, ActionCoin, + }, +}; use mars_swapper_mock::contract::MOCK_SWAP_RESULT; use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv}; @@ -45,10 +47,7 @@ fn test_denom_out_must_be_whitelisted() { let osmo_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[osmo_info.clone()]) - .build() - .unwrap(); + let mut mock = MockEnv::new().allowed_coins(&[osmo_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( @@ -71,10 +70,8 @@ fn test_no_amount_sent() { let atom_info = uatom_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) - .build() - .unwrap(); + let mut mock = + MockEnv::new().allowed_coins(&[osmo_info.clone(), atom_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( @@ -97,10 +94,8 @@ fn test_user_has_zero_balance_for_swap_req() { let atom_info = uatom_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) - .build() - .unwrap(); + let mut mock = + MockEnv::new().allowed_coins(&[osmo_info.clone(), atom_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( diff --git a/contracts/credit-manager/tests/test_update_admin.rs b/contracts/credit-manager/tests/test_update_admin.rs index 05579bc27..77c84b735 100644 --- a/contracts/credit-manager/tests/test_update_admin.rs +++ b/contracts/credit-manager/tests/test_update_admin.rs @@ -1,6 +1,8 @@ use cosmwasm_std::Addr; -use mars_owner::OwnerError::{NotOwner, NotProposedOwner, StateTransitionError}; -use mars_owner::OwnerUpdate; +use mars_owner::{ + OwnerError::{NotOwner, NotProposedOwner, StateTransitionError}, + OwnerUpdate, +}; use mars_rover::error::ContractError::OwnerError; use crate::helpers::{assert_err, MockEnv}; @@ -44,10 +46,7 @@ fn test_propose_new_owner() { let new_config = mock.query_config(); assert_eq!(new_config.owner, original_config.owner); - assert_ne!( - new_config.proposed_new_owner, - original_config.proposed_new_owner - ); + assert_ne!(new_config.proposed_new_owner, original_config.proposed_new_owner); assert_eq!(new_config.proposed_new_owner, Some(new_owner)); } @@ -84,10 +83,7 @@ fn test_clear_proposed() { let latest_config = mock.query_config(); assert_eq!(latest_config.owner, original_config.owner); - assert_ne!( - latest_config.proposed_new_owner, - interim_config.proposed_new_owner - ); + assert_ne!(latest_config.proposed_new_owner, interim_config.proposed_new_owner); assert_eq!(latest_config.proposed_new_owner, None); } @@ -113,11 +109,7 @@ fn test_accept_owner_role() { ); assert_err(res, OwnerError(NotProposedOwner {})); - mock.update_owner( - &Addr::unchecked(new_owner.clone()), - OwnerUpdate::AcceptProposed, - ) - .unwrap(); + mock.update_owner(&Addr::unchecked(new_owner.clone()), OwnerUpdate::AcceptProposed).unwrap(); let new_config = mock.query_config(); diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index a8650453e..b6f32516f 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -1,15 +1,20 @@ use cosmwasm_std::{coin, Addr, Decimal, Uint128}; use cw_multi_test::{BasicApp, Executor}; - use mars_mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; -use mars_rover::adapters::oracle::{OracleBase, OracleUnchecked}; -use mars_rover::adapters::swap::SwapperBase; -use mars_rover::adapters::vault::{VaultBase, VaultConfig}; -use mars_rover::adapters::zapper::ZapperBase; -use mars_rover::error::ContractError::InvalidConfig; -use mars_rover::msg::instantiate::{ConfigUpdates, VaultInstantiateConfig}; -use mars_rover::msg::query::VaultInfoResponse; +use mars_rover::{ + adapters::{ + oracle::{OracleBase, OracleUnchecked}, + swap::SwapperBase, + vault::{VaultBase, VaultConfig}, + zapper::ZapperBase, + }, + error::ContractError::InvalidConfig, + msg::{ + instantiate::{ConfigUpdates, VaultInstantiateConfig}, + query::VaultInfoResponse, + }, +}; use crate::helpers::{ assert_err, locked_vault_info, mock_oracle_contract, mock_vault_contract, uatom_info, @@ -143,10 +148,7 @@ fn test_update_config_works_with_full_config() { assert_eq!(new_config.account_nft, Some(new_nft_contract.to_string())); assert_ne!(new_config.account_nft, original_config.account_nft); - assert_eq!( - new_config.owner.unwrap(), - original_config.owner.clone().unwrap() - ); + assert_eq!(new_config.owner.unwrap(), original_config.owner.clone().unwrap()); assert_eq!( new_queried_vault_configs, @@ -171,16 +173,10 @@ fn test_update_config_works_with_full_config() { assert_ne!(new_config.zapper, original_config.zapper); assert_eq!(new_config.max_close_factor, new_close_factor); - assert_ne!( - new_config.max_close_factor, - original_config.max_close_factor - ); + assert_ne!(new_config.max_close_factor, original_config.max_close_factor); assert_eq!(new_config.max_unlocking_positions, new_unlocking_max); - assert_ne!( - new_config.max_unlocking_positions, - original_config.max_unlocking_positions - ); + assert_ne!(new_config.max_unlocking_positions, original_config.max_unlocking_positions); assert_eq!(&new_config.swapper, new_swapper.address()); assert_ne!(new_config.swapper, original_config.swapper); @@ -215,23 +211,14 @@ fn test_update_config_works_with_some_config() { assert_ne!(new_config.account_nft, original_config.account_nft); assert_eq!(new_config.max_unlocking_positions, new_max_unlocking); - assert_ne!( - new_config.max_unlocking_positions, - original_config.max_unlocking_positions - ); + assert_ne!(new_config.max_unlocking_positions, original_config.max_unlocking_positions); // Unchanged configs assert_eq!(new_config.owner, original_config.owner); - assert_eq!( - new_config.proposed_new_owner, - original_config.proposed_new_owner - ); + assert_eq!(new_config.proposed_new_owner, original_config.proposed_new_owner); assert_eq!(new_config.red_bank, original_config.red_bank); assert_eq!(new_config.oracle, original_config.oracle); - assert_eq!( - new_config.max_close_factor, - original_config.max_close_factor - ); + assert_eq!(new_config.max_close_factor, original_config.max_close_factor); assert_eq!(new_config.swapper, original_config.swapper); assert_eq!(new_config.zapper, original_config.zapper); assert_eq!(original_allowed_coins, new_queried_allowed_coins); @@ -298,10 +285,7 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { assert_eq!(new_config.red_bank, original_config.red_bank); assert_eq!(new_config.oracle, original_config.oracle); assert_eq!(new_config.zapper, original_config.zapper); - assert_eq!( - new_config.max_close_factor, - original_config.max_close_factor - ); + assert_eq!(new_config.max_close_factor, original_config.max_close_factor); assert_eq!(new_config.swapper, original_config.swapper); } diff --git a/contracts/credit-manager/tests/test_utilization_query.rs b/contracts/credit-manager/tests/test_utilization_query.rs index eb772f88d..144ea661c 100644 --- a/contracts/credit-manager/tests/test_utilization_query.rs +++ b/contracts/credit-manager/tests/test_utilization_query.rs @@ -1,8 +1,9 @@ use cosmwasm_std::{Addr, Decimal, Uint128}; - -use mars_rover::msg::execute::Action::{Deposit, EnterVault}; -use mars_rover::msg::execute::ActionAmount::Exact; -use mars_rover::msg::execute::ActionCoin; +use mars_rover::msg::execute::{ + Action::{Deposit, EnterVault}, + ActionAmount::Exact, + ActionCoin, +}; use crate::helpers::{ ujake_info, unlocked_vault_info, uosmo_info, AccountToFund, CoinInfo, MockEnv, VaultTestInfo, @@ -12,10 +13,7 @@ pub mod helpers; #[test] fn test_utilization_is_zero() { - let mock = MockEnv::new() - .vault_configs(&[unlocked_vault_info()]) - .build() - .unwrap(); + let mock = MockEnv::new().vault_configs(&[unlocked_vault_info()]).build().unwrap(); let vault_infos = mock.query_vault_configs(None, None); assert_eq!(1, vault_infos.len()); let vault = vault_infos.first().unwrap(); diff --git a/contracts/credit-manager/tests/test_vault_enter.rs b/contracts/credit-manager/tests/test_vault_enter.rs index 84c075953..0846778af 100644 --- a/contracts/credit-manager/tests/test_vault_enter.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -1,12 +1,13 @@ -use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::StdError::NotFound; -use cosmwasm_std::{Addr, OverflowError, Uint128}; - +use cosmwasm_std::{Addr, OverflowError, OverflowOperation::Sub, StdError::NotFound, Uint128}; use mars_mock_vault::contract::STARTING_VAULT_SHARES; -use mars_rover::adapters::vault::VaultBase; -use mars_rover::error::ContractError; -use mars_rover::msg::execute::Action::{Deposit, EnterVault}; -use mars_rover::msg::execute::{ActionAmount, ActionCoin}; +use mars_rover::{ + adapters::vault::VaultBase, + error::ContractError, + msg::execute::{ + Action::{Deposit, EnterVault}, + ActionAmount, ActionCoin, + }, +}; use crate::helpers::{ assert_err, locked_vault_info, lp_token_info, uatom_info, unlocked_vault_info, uosmo_info, @@ -51,10 +52,7 @@ fn test_deposit_denom_is_whitelisted() { let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .vault_configs(&[leverage_vault.clone()]) - .build() - .unwrap(); + let mut mock = MockEnv::new().vault_configs(&[leverage_vault.clone()]).build().unwrap(); let vault = mock.get_vault(&leverage_vault); let account_id = mock.create_credit_account(&user).unwrap(); @@ -97,10 +95,7 @@ fn test_vault_is_whitelisted() { &[], ); - assert_err( - res, - ContractError::NotWhitelisted("unknown_vault".to_string()), - ); + assert_err(res, ContractError::NotWhitelisted("unknown_vault".to_string())); } #[test] @@ -249,14 +244,8 @@ fn test_successful_deposit_into_locked_vault() { let res = mock.query_positions(&account_id); assert_eq!(res.vaults.len(), 1); - assert_eq!( - STARTING_VAULT_SHARES, - res.vaults.first().unwrap().amount.locked() - ); - assert_eq!( - Uint128::zero(), - res.vaults.first().unwrap().amount.unlocked() - ); + assert_eq!(STARTING_VAULT_SHARES, res.vaults.first().unwrap().amount.locked()); + assert_eq!(Uint128::zero(), res.vaults.first().unwrap().amount.unlocked()); let amount = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().amount.locked()); assert_eq!(amount, Uint128::new(23)); @@ -306,10 +295,7 @@ fn test_successful_deposit_into_unlocked_vault() { let res = mock.query_positions(&account_id); assert_eq!(res.vaults.len(), 1); - assert_eq!( - STARTING_VAULT_SHARES, - res.vaults.first().unwrap().amount.unlocked() - ); + assert_eq!(STARTING_VAULT_SHARES, res.vaults.first().unwrap().amount.unlocked()); assert_eq!(Uint128::zero(), res.vaults.first().unwrap().amount.locked()); let amount = mock.query_preview_redeem(&vault, res.vaults.first().unwrap().amount.unlocked()); @@ -440,9 +426,7 @@ fn test_successful_deposit_with_implied_full_balance_amount() { assert_eq!(res.deposits.len(), 0); // Assert vault indeed has those tokens - let base_denom = mock.query_balance( - &Addr::unchecked(vault.address), - &leverage_vault.base_token_denom, - ); + let base_denom = + mock.query_balance(&Addr::unchecked(vault.address), &leverage_vault.base_token_denom); assert_eq!(base_denom.amount, Uint128::new(200)) } diff --git a/contracts/credit-manager/tests/test_vault_exit.rs b/contracts/credit-manager/tests/test_vault_exit.rs index 20181ee3b..91eb1a265 100644 --- a/contracts/credit-manager/tests/test_vault_exit.rs +++ b/contracts/credit-manager/tests/test_vault_exit.rs @@ -1,11 +1,13 @@ -use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{coin, Addr, Coin, OverflowError, Uint128}; - +use cosmwasm_std::{coin, Addr, Coin, OverflowError, OverflowOperation::Sub, Uint128}; use mars_mock_vault::contract::STARTING_VAULT_SHARES; -use mars_rover::adapters::vault::VaultBase; -use mars_rover::error::ContractError; -use mars_rover::error::ContractError::{NotTokenOwner, NotWhitelisted}; -use mars_rover::msg::execute::Action::{Deposit, EnterVault, ExitVault}; +use mars_rover::{ + adapters::vault::VaultBase, + error::{ + ContractError, + ContractError::{NotTokenOwner, NotWhitelisted}, + }, + msg::execute::Action::{Deposit, EnterVault, ExitVault}, +}; use crate::helpers::{ assert_err, locked_vault_info, lp_token_info, uatom_info, unlocked_vault_info, uosmo_info, diff --git a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs index cb445637c..33e870b8f 100644 --- a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs +++ b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs @@ -1,13 +1,14 @@ use cosmwasm_std::{Addr, Uint128}; use cw_utils::Duration; - use mars_mock_vault::contract::STARTING_VAULT_SHARES; -use mars_rover::adapters::vault::VaultUnchecked; -use mars_rover::error::ContractError; -use mars_rover::msg::execute::Action::{ - Deposit, EnterVault, ExitVaultUnlocked, RequestVaultUnlock, +use mars_rover::{ + adapters::vault::VaultUnchecked, + error::ContractError, + msg::{ + execute::Action::{Deposit, EnterVault, ExitVaultUnlocked, RequestVaultUnlock}, + query::Positions, + }, }; -use mars_rover::msg::query::Positions; use crate::helpers::{ assert_err, generate_mock_vault, get_coin, locked_vault_info, lp_token_info, AccountToFund, @@ -21,10 +22,7 @@ fn test_only_owner_can_withdraw_unlocked_for_account() { let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .vault_configs(&[leverage_vault.clone()]) - .build() - .unwrap(); + let mut mock = MockEnv::new().vault_configs(&[leverage_vault.clone()]).build().unwrap(); let vault = mock.get_vault(&leverage_vault); let account_id = mock.create_credit_account(&user).unwrap(); @@ -33,7 +31,10 @@ fn test_only_owner_can_withdraw_unlocked_for_account() { let res = mock.update_credit_account( &account_id, &bad_guy, - vec![ExitVaultUnlocked { id: 423, vault }], + vec![ExitVaultUnlocked { + id: 423, + vault, + }], &[], ); @@ -57,7 +58,10 @@ fn test_can_only_take_action_on_whitelisted_vaults() { let res = mock.update_credit_account( &account_id, &user, - vec![ExitVaultUnlocked { id: 234, vault }], + vec![ExitVaultUnlocked { + id: 234, + vault, + }], &[], ); @@ -279,7 +283,10 @@ fn test_withdraw_unlock_success_time_expiring() { ) .unwrap(); - let Positions { deposits, .. } = mock.query_positions(&account_id); + let Positions { + deposits, + .. + } = mock.query_positions(&account_id); assert_eq!(deposits.len(), 0); mock.app.update_block(|block| { @@ -304,7 +311,9 @@ fn test_withdraw_unlock_success_time_expiring() { .unwrap(); let Positions { - vaults, deposits, .. + vaults, + deposits, + .. } = mock.query_positions(&account_id); // Users vault position decrements @@ -356,7 +365,10 @@ fn test_withdraw_unlock_success_block_expiring() { ) .unwrap(); - let Positions { deposits, .. } = mock.query_positions(&account_id); + let Positions { + deposits, + .. + } = mock.query_positions(&account_id); assert_eq!(deposits.len(), 0); mock.app.update_block(|block| { @@ -381,7 +393,9 @@ fn test_withdraw_unlock_success_block_expiring() { .unwrap(); let Positions { - vaults, deposits, .. + vaults, + deposits, + .. } = mock.query_positions(&account_id); // Users vault position decrements @@ -397,14 +411,5 @@ fn test_withdraw_unlock_success_block_expiring() { } fn get_lockup_id(positions: &Positions) -> u64 { - positions - .vaults - .first() - .unwrap() - .amount - .unlocking() - .positions() - .first() - .unwrap() - .id + positions.vaults.first().unwrap().amount.unlocking().positions().first().unwrap().id } diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs index 64b370700..71cbaa222 100644 --- a/contracts/credit-manager/tests/test_vault_request_unlock.rs +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -1,12 +1,12 @@ -use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{coins, Addr, OverflowError, Uint128}; +use cosmwasm_std::{coins, Addr, OverflowError, OverflowOperation::Sub, Uint128}; use cw_multi_test::{BankSudo, SudoMsg}; use cw_utils::{Duration, Expiration}; - use mars_mock_vault::contract::STARTING_VAULT_SHARES; -use mars_rover::adapters::vault::VaultUnchecked; -use mars_rover::error::ContractError; -use mars_rover::msg::execute::Action::{Deposit, EnterVault, RequestVaultUnlock}; +use mars_rover::{ + adapters::vault::VaultUnchecked, + error::ContractError, + msg::execute::Action::{Deposit, EnterVault, RequestVaultUnlock}, +}; use crate::helpers::{ assert_err, locked_vault_info, lp_token_info, unlocked_vault_info, AccountToFund, MockEnv, @@ -19,10 +19,7 @@ fn test_only_owner_can_request_unlocked() { let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .vault_configs(&[leverage_vault.clone()]) - .build() - .unwrap(); + let mut mock = MockEnv::new().vault_configs(&[leverage_vault.clone()]).build().unwrap(); let vault = mock.get_vault(&leverage_vault); let account_id = mock.create_credit_account(&user).unwrap(); @@ -73,10 +70,7 @@ fn test_request_when_unnecessary() { let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .vault_configs(&[leverage_vault.clone()]) - .build() - .unwrap(); + let mut mock = MockEnv::new().vault_configs(&[leverage_vault.clone()]).build().unwrap(); let vault = mock.get_vault(&leverage_vault); let account_id = mock.create_credit_account(&user).unwrap(); @@ -104,10 +98,7 @@ fn test_no_vault_tokens_for_request() { let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .vault_configs(&[leverage_vault.clone()]) - .build() - .unwrap(); + let mut mock = MockEnv::new().vault_configs(&[leverage_vault.clone()]).build().unwrap(); let vault = mock.get_vault(&leverage_vault); let account_id = mock.create_credit_account(&user).unwrap(); diff --git a/contracts/credit-manager/tests/test_withdraw.rs b/contracts/credit-manager/tests/test_withdraw.rs index 4629c9ca4..b681b54e0 100644 --- a/contracts/credit-manager/tests/test_withdraw.rs +++ b/contracts/credit-manager/tests/test_withdraw.rs @@ -1,9 +1,8 @@ -use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{coin, coins, Addr, Coin, OverflowError, Uint128}; - -use mars_rover::error::ContractError; -use mars_rover::error::ContractError::NotTokenOwner; -use mars_rover::msg::execute::Action; +use cosmwasm_std::{coin, coins, Addr, Coin, OverflowError, OverflowOperation::Sub, Uint128}; +use mars_rover::{ + error::{ContractError, ContractError::NotTokenOwner}, + msg::execute::Action, +}; use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv}; @@ -40,10 +39,7 @@ fn test_only_owner_of_token_can_withdraw() { fn test_withdraw_nothing() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) - .build() - .unwrap(); + let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( @@ -63,10 +59,7 @@ fn test_withdraw_nothing() { fn test_withdraw_but_no_funds() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) - .build() - .unwrap(); + let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( @@ -106,10 +99,7 @@ fn test_withdraw_but_not_enough_funds() { let res = mock.update_credit_account( &account_id, &user, - vec![ - Action::Deposit(coin_info.to_coin(300)), - Action::Withdraw(coin_info.to_coin(400)), - ], + vec![Action::Deposit(coin_info.to_coin(300)), Action::Withdraw(coin_info.to_coin(400))], &[coin(300, coin_info.denom)], ); @@ -206,10 +196,7 @@ fn test_multiple_withdraw_actions() { .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: vec![ - coin(234, uosmo_info.denom.clone()), - coin(25, uatom_info.denom.clone()), - ], + funds: vec![coin(234, uosmo_info.denom.clone()), coin(25, uatom_info.denom.clone())], }) .build() .unwrap(); @@ -225,10 +212,7 @@ fn test_multiple_withdraw_actions() { Action::Deposit(uosmo_info.to_coin(uosmo_amount.u128())), Action::Deposit(uatom_info.to_coin(uatom_amount.u128())), ], - &[ - coin(234, uosmo_info.denom.clone()), - coin(25, uatom_info.denom.clone()), - ], + &[coin(234, uosmo_info.denom.clone()), coin(25, uatom_info.denom.clone())], ) .unwrap(); diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs index c6d61e9e2..efe094e84 100644 --- a/contracts/credit-manager/tests/test_zap_provide.rs +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -1,12 +1,14 @@ -use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{Addr, OverflowError, Uint128}; -use mars_zapper_mock::contract::STARTING_LP_POOL_TOKENS; use std::ops::Mul; -use mars_rover::error::ContractError as RoverError; -use mars_rover::msg::execute::Action::{Deposit, ProvideLiquidity, WithdrawLiquidity}; -use mars_rover::msg::execute::{ActionAmount, ActionCoin}; -use mars_zapper_mock::error::ContractError; +use cosmwasm_std::{Addr, OverflowError, OverflowOperation::Sub, Uint128}; +use mars_rover::{ + error::ContractError as RoverError, + msg::execute::{ + Action::{Deposit, ProvideLiquidity, WithdrawLiquidity}, + ActionAmount, ActionCoin, + }, +}; +use mars_zapper_mock::{contract::STARTING_LP_POOL_TOKENS, error::ContractError}; use crate::helpers::{ assert_err, get_coin, lp_token_info, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, @@ -289,10 +291,7 @@ fn test_successful_zap() { let config = mock.query_config(); let lp_balance = mock.query_balance(&Addr::unchecked(config.zapper.clone()), &lp_token.denom); // prefunded minus minted - assert_eq!( - lp_balance.amount, - Uint128::new(10_000_000) - STARTING_LP_POOL_TOKENS - ); + assert_eq!(lp_balance.amount, Uint128::new(10_000_000) - STARTING_LP_POOL_TOKENS); let atom_balance = mock.query_balance(&Addr::unchecked(config.zapper.clone()), &atom.denom); assert_eq!(atom_balance.amount, Uint128::new(100)); let osmo_balance = mock.query_balance(&Addr::unchecked(config.zapper), &osmo.denom); @@ -362,10 +361,7 @@ fn test_can_provide_unbalanced() { let positions = mock.query_positions(&account_id); assert_eq!(positions.deposits.len(), 2); let lp_balance = get_coin(&lp_token.denom, &positions.deposits); - assert_eq!( - lp_balance.amount, - STARTING_LP_POOL_TOKENS.multiply_ratio(1u128, 2u128) - ); + assert_eq!(lp_balance.amount, STARTING_LP_POOL_TOKENS.multiply_ratio(1u128, 2u128)); let atom_balance = get_coin(&atom.denom, &positions.deposits); assert_eq!(atom_balance.amount, Uint128::new(50)); @@ -434,8 +430,5 @@ fn test_order_does_not_matter() { let positions = mock.query_positions(&account_id); assert_eq!(positions.deposits.len(), 1); let lp_balance = get_coin(&lp_token.denom, &positions.deposits); - assert_eq!( - lp_balance.amount, - STARTING_LP_POOL_TOKENS.mul(Uint128::new(2)) - ); + assert_eq!(lp_balance.amount, STARTING_LP_POOL_TOKENS.mul(Uint128::new(2))); } diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index c260715a0..1bc592873 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -1,10 +1,14 @@ -use cosmwasm_std::OverflowOperation::Sub; -use cosmwasm_std::{Addr, OverflowError, Uint128}; - -use mars_rover::error::ContractError as RoverError; -use mars_rover::msg::execute::Action::{Deposit, ProvideLiquidity, WithdrawLiquidity}; -use mars_rover::msg::execute::{ActionAmount, ActionCoin}; -use mars_rover::msg::instantiate::ConfigUpdates; +use cosmwasm_std::{Addr, OverflowError, OverflowOperation::Sub, Uint128}; +use mars_rover::{ + error::ContractError as RoverError, + msg::{ + execute::{ + Action::{Deposit, ProvideLiquidity, WithdrawLiquidity}, + ActionAmount, ActionCoin, + }, + instantiate::ConfigUpdates, + }, +}; use mars_zapper_mock::contract::STARTING_LP_POOL_TOKENS; use crate::helpers::{ diff --git a/contracts/mock-credit-manager/src/contract.rs b/contracts/mock-credit-manager/src/contract.rs index 1fa13f923..2078ffa9f 100644 --- a/contracts/mock-credit-manager/src/contract.rs +++ b/contracts/mock-credit-manager/src/contract.rs @@ -5,9 +5,7 @@ use cosmwasm_std::{ }; use mars_rover::msg::QueryMsg; -use crate::execute::set_health_response; -use crate::msg::ExecuteMsg; -use crate::query::query_health; +use crate::{execute::set_health_response, msg::ExecuteMsg, query::query_health}; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( @@ -37,7 +35,9 @@ pub fn execute( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::Health { account_id } => to_binary(&query_health(deps, account_id)?), + QueryMsg::Health { + account_id, + } => to_binary(&query_health(deps, account_id)?), _ => unimplemented!("query msg not supported"), } } diff --git a/contracts/mock-credit-manager/src/execute.rs b/contracts/mock-credit-manager/src/execute.rs index b08f652b2..af62a3d60 100644 --- a/contracts/mock-credit-manager/src/execute.rs +++ b/contracts/mock-credit-manager/src/execute.rs @@ -1,7 +1,8 @@ -use crate::state::HEALTH_RESPONSES; use cosmwasm_std::{DepsMut, Response, StdResult}; use mars_health::HealthResponse; +use crate::state::HEALTH_RESPONSES; + pub fn set_health_response( deps: DepsMut, account_id: String, diff --git a/contracts/mock-credit-manager/src/query.rs b/contracts/mock-credit-manager/src/query.rs index 1a79f40d1..845df79f2 100644 --- a/contracts/mock-credit-manager/src/query.rs +++ b/contracts/mock-credit-manager/src/query.rs @@ -1,7 +1,8 @@ -use crate::state::HEALTH_RESPONSES; use cosmwasm_std::{Deps, StdResult}; use mars_health::HealthResponse; +use crate::state::HEALTH_RESPONSES; + pub fn query_health(deps: Deps, account_id: String) -> StdResult { HEALTH_RESPONSES.load(deps.storage, &account_id) } diff --git a/contracts/mock-credit-manager/src/state.rs b/contracts/mock-credit-manager/src/state.rs index 9a5dc087f..2d1f62c09 100644 --- a/contracts/mock-credit-manager/src/state.rs +++ b/contracts/mock-credit-manager/src/state.rs @@ -1,5 +1,4 @@ use cw_storage_plus::Map; - use mars_health::HealthResponse; pub const HEALTH_RESPONSES: Map<&str, HealthResponse> = Map::new("health_responses"); // Map diff --git a/contracts/mock-oracle/src/contract.rs b/contracts/mock-oracle/src/contract.rs index f752070b8..f946c3955 100644 --- a/contracts/mock-oracle/src/contract.rs +++ b/contracts/mock-oracle/src/contract.rs @@ -3,8 +3,10 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use mars_outpost::oracle::PriceResponse; -use crate::msg::{CoinPrice, ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::COIN_PRICE; +use crate::{ + msg::{CoinPrice, ExecuteMsg, InstantiateMsg, QueryMsg}, + state::COIN_PRICE, +}; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( @@ -39,11 +41,16 @@ fn change_price(deps: DepsMut, coin: CoinPrice) -> StdResult { #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::Price { denom } => to_binary(&query_price(deps, denom)?), + QueryMsg::Price { + denom, + } => to_binary(&query_price(deps, denom)?), } } fn query_price(deps: Deps, denom: String) -> StdResult { let price = COIN_PRICE.load(deps.storage, denom.clone())?; - Ok(PriceResponse { denom, price }) + Ok(PriceResponse { + denom, + price, + }) } diff --git a/contracts/mock-oracle/src/msg.rs b/contracts/mock-oracle/src/msg.rs index 18789a3e7..2c5feab25 100644 --- a/contracts/mock-oracle/src/msg.rs +++ b/contracts/mock-oracle/src/msg.rs @@ -22,5 +22,7 @@ pub enum ExecuteMsg { #[derive(QueryResponses)] pub enum QueryMsg { #[returns(mars_outpost::oracle::PriceResponse)] - Price { denom: String }, + Price { + denom: String, + }, } diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index d59a7f626..e17127d68 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -1,14 +1,15 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; - -use crate::execute::{borrow, repay}; -use crate::msg::InstantiateMsg; -use crate::query::{query_debt, query_market}; -use crate::state::COIN_MARKET_INFO; - use mars_outpost::red_bank; +use crate::{ + execute::{borrow, repay}, + msg::InstantiateMsg, + query::{query_debt, query_market}, + state::COIN_MARKET_INFO, +}; + #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, @@ -30,8 +31,14 @@ pub fn execute( msg: red_bank::ExecuteMsg, ) -> StdResult { match msg { - red_bank::ExecuteMsg::Borrow { denom, amount, .. } => borrow(deps, info, denom, amount), - red_bank::ExecuteMsg::Repay { .. } => repay(deps, info), + red_bank::ExecuteMsg::Borrow { + denom, + amount, + .. + } => borrow(deps, info, denom, amount), + red_bank::ExecuteMsg::Repay { + .. + } => repay(deps, info), _ => unimplemented!("Msg not supported!"), } } @@ -39,8 +46,13 @@ pub fn execute( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: red_bank::QueryMsg) -> StdResult { match msg { - red_bank::QueryMsg::UserDebt { user, denom } => to_binary(&query_debt(deps, user, denom)?), - red_bank::QueryMsg::Market { denom } => to_binary(&query_market(deps, denom)?), + red_bank::QueryMsg::UserDebt { + user, + denom, + } => to_binary(&query_debt(deps, user, denom)?), + red_bank::QueryMsg::Market { + denom, + } => to_binary(&query_market(deps, denom)?), _ => unimplemented!("Query not supported!"), } } diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs index 745b5b2f7..b9a02c96b 100644 --- a/contracts/mock-red-bank/src/execute.rs +++ b/contracts/mock-red-bank/src/execute.rs @@ -1,7 +1,6 @@ use cosmwasm_std::{coin, BankMsg, CosmosMsg, DepsMut, MessageInfo, Response, StdResult, Uint128}; -use crate::helpers::load_debt_amount; -use crate::state::DEBT_AMOUNT; +use crate::{helpers::load_debt_amount, state::DEBT_AMOUNT}; pub fn borrow( deps: DepsMut, @@ -14,9 +13,7 @@ pub fn borrow( DEBT_AMOUNT.save( deps.storage, (info.sender.clone(), denom.clone()), - &debt_amount - .checked_add(amount)? - .checked_add(Uint128::new(1))?, // The extra unit is simulated accrued interest + &debt_amount.checked_add(amount)?.checked_add(Uint128::new(1))?, // The extra unit is simulated accrued interest )?; let transfer_msg = CosmosMsg::Bank(BankMsg::Send { diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index 35c1e5791..b60640353 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -1,10 +1,7 @@ use cosmwasm_std::{Deps, StdResult, Uint128}; -use mars_outpost::red_bank::Market; +use mars_outpost::red_bank::{Market, UserDebtResponse}; -use crate::helpers::load_debt_amount; -use crate::state::COIN_MARKET_INFO; - -use mars_outpost::red_bank::UserDebtResponse; +use crate::{helpers::load_debt_amount, state::COIN_MARKET_INFO}; pub fn query_debt(deps: Deps, user: String, denom: String) -> StdResult { let user_addr = deps.api.addr_validate(&user)?; diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index c9239eea1..1391ac69c 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -1,25 +1,30 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{coin, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; -use cosmwasm_vault_standard::extensions::force_unlock::ForceUnlockExecuteMsg; -use cosmwasm_vault_standard::extensions::lockup::{LockupExecuteMsg, LockupQueryMsg}; -use cosmwasm_vault_standard::msg::{ExtensionExecuteMsg, ExtensionQueryMsg}; - +use cosmwasm_vault_standard::{ + extensions::{ + force_unlock::ForceUnlockExecuteMsg, + lockup::{LockupExecuteMsg, LockupQueryMsg}, + }, + msg::{ExtensionExecuteMsg, ExtensionQueryMsg}, +}; use mars_rover::adapters::vault::{ExecuteMsg, QueryMsg}; -use crate::deposit::deposit; -use crate::error::ContractResult; -use crate::msg::InstantiateMsg; -use crate::query::{ - query_lockup_duration, query_unlocking_position, query_unlocking_positions, query_vault_info, - query_vault_token_supply, shares_to_base_denom_amount, -}; -use crate::state::{ - CHAIN_BANK, COIN_BALANCE, LOCKUP_TIME, NEXT_LOCKUP_ID, ORACLE, TOTAL_VAULT_SHARES, - VAULT_TOKEN_DENOM, +use crate::{ + deposit::deposit, + error::ContractResult, + msg::InstantiateMsg, + query::{ + query_lockup_duration, query_unlocking_position, query_unlocking_positions, + query_vault_info, query_vault_token_supply, shares_to_base_denom_amount, + }, + state::{ + CHAIN_BANK, COIN_BALANCE, LOCKUP_TIME, NEXT_LOCKUP_ID, ORACLE, TOTAL_VAULT_SHARES, + VAULT_TOKEN_DENOM, + }, + unlock::{request_unlock, withdraw_unlocked, withdraw_unlocking_force}, + withdraw::{redeem_force, withdraw}, }; -use crate::unlock::{request_unlock, withdraw_unlocked, withdraw_unlocking_force}; -use crate::withdraw::{redeem_force, withdraw}; pub const STARTING_VAULT_SHARES: Uint128 = Uint128::new(1_000_000); @@ -52,19 +57,30 @@ pub fn execute( msg: ExecuteMsg, ) -> ContractResult { match msg { - ExecuteMsg::Deposit { .. } => deposit(deps, info), - ExecuteMsg::Redeem { .. } => withdraw(deps, info), + ExecuteMsg::Deposit { + .. + } => deposit(deps, info), + ExecuteMsg::Redeem { + .. + } => withdraw(deps, info), ExecuteMsg::VaultExtension(ext) => match ext { ExtensionExecuteMsg::Lockup(lockup_msg) => match lockup_msg { - LockupExecuteMsg::WithdrawUnlocked { lockup_id, .. } => { - withdraw_unlocked(deps, env, &info.sender, lockup_id) - } - LockupExecuteMsg::Unlock { .. } => request_unlock(deps, env, info), + LockupExecuteMsg::WithdrawUnlocked { + lockup_id, + .. + } => withdraw_unlocked(deps, env, &info.sender, lockup_id), + LockupExecuteMsg::Unlock { + .. + } => request_unlock(deps, env, info), }, ExtensionExecuteMsg::ForceUnlock(force_msg) => match force_msg { - ForceUnlockExecuteMsg::ForceRedeem { .. } => redeem_force(deps, info), + ForceUnlockExecuteMsg::ForceRedeem { + .. + } => redeem_force(deps, info), ForceUnlockExecuteMsg::ForceWithdrawUnlocking { - lockup_id, amount, .. + lockup_id, + amount, + .. } => withdraw_unlocking_force(deps, &info.sender, lockup_id, amount), _ => unimplemented!(), }, @@ -77,17 +93,19 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> ContractResult { let res = match msg { QueryMsg::TotalVaultTokenSupply {} => to_binary(&query_vault_token_supply(deps.storage)?), QueryMsg::Info {} => to_binary(&query_vault_info(deps)?), - QueryMsg::PreviewRedeem { amount } => { - to_binary(&shares_to_base_denom_amount(deps.storage, amount)?) - } + QueryMsg::PreviewRedeem { + amount, + } => to_binary(&shares_to_base_denom_amount(deps.storage, amount)?), QueryMsg::VaultExtension(ext) => match ext { ExtensionQueryMsg::Lockup(lockup_msg) => match lockup_msg { - LockupQueryMsg::UnlockingPositions { owner, .. } => { - to_binary(&query_unlocking_positions(deps, owner)?) - } - LockupQueryMsg::UnlockingPosition { lockup_id, .. } => { - to_binary(&query_unlocking_position(deps, lockup_id)?) - } + LockupQueryMsg::UnlockingPositions { + owner, + .. + } => to_binary(&query_unlocking_positions(deps, owner)?), + LockupQueryMsg::UnlockingPosition { + lockup_id, + .. + } => to_binary(&query_unlocking_position(deps, lockup_id)?), LockupQueryMsg::LockupDuration {} => to_binary(&query_lockup_duration(deps)?), }, }, diff --git a/contracts/mock-vault/src/deposit.rs b/contracts/mock-vault/src/deposit.rs index 12f6fc1c9..5577d496e 100644 --- a/contracts/mock-vault/src/deposit.rs +++ b/contracts/mock-vault/src/deposit.rs @@ -1,9 +1,10 @@ use cosmwasm_std::{BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdResult, Uint128}; -use crate::contract::STARTING_VAULT_SHARES; -use crate::error::ContractError; -use crate::error::ContractError::WrongDenomSent; -use crate::state::{CHAIN_BANK, COIN_BALANCE, ORACLE, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}; +use crate::{ + contract::STARTING_VAULT_SHARES, + error::{ContractError, ContractError::WrongDenomSent}, + state::{CHAIN_BANK, COIN_BALANCE, ORACLE, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}, +}; pub fn deposit(deps: DepsMut, info: MessageInfo) -> Result { let total_shares = TOTAL_VAULT_SHARES.load(deps.storage)?; @@ -47,9 +48,10 @@ pub fn deposit(deps: DepsMut, info: MessageInfo) -> Result StdResult { let denom = VAULT_TOKEN_DENOM.load(deps.storage)?; - CHAIN_BANK.update(deps.storage, |bank_amount| -> StdResult<_> { - Ok(bank_amount - amount) - })?; + CHAIN_BANK.update(deps.storage, |bank_amount| -> StdResult<_> { Ok(bank_amount - amount) })?; - Ok(Coin { denom, amount }) + Ok(Coin { + denom, + amount, + }) } diff --git a/contracts/mock-vault/src/query.rs b/contracts/mock-vault/src/query.rs index 66f4787ef..023e50bef 100644 --- a/contracts/mock-vault/src/query.rs +++ b/contracts/mock-vault/src/query.rs @@ -1,12 +1,12 @@ use cosmwasm_std::{Deps, Order, StdError, StdResult, Storage, Uint128}; -use cosmwasm_vault_standard::extensions::lockup::UnlockingPosition; -use cosmwasm_vault_standard::msg::VaultInfoResponse; +use cosmwasm_vault_standard::{extensions::lockup::UnlockingPosition, msg::VaultInfoResponse}; use cw_utils::Duration; -use crate::error::ContractError::NotLockingVault; -use crate::error::ContractResult; -use crate::state::{ - COIN_BALANCE, LOCKUP_TIME, TOTAL_VAULT_SHARES, UNLOCKING_POSITIONS, VAULT_TOKEN_DENOM, +use crate::{ + error::{ContractError::NotLockingVault, ContractResult}, + state::{ + COIN_BALANCE, LOCKUP_TIME, TOTAL_VAULT_SHARES, UNLOCKING_POSITIONS, VAULT_TOKEN_DENOM, + }, }; pub fn shares_to_base_denom_amount( diff --git a/contracts/mock-vault/src/state.rs b/contracts/mock-vault/src/state.rs index 9ca84f41f..074209015 100644 --- a/contracts/mock-vault/src/state.rs +++ b/contracts/mock-vault/src/state.rs @@ -2,7 +2,6 @@ use cosmwasm_std::{Addr, Coin, Uint128}; use cosmwasm_vault_standard::extensions::lockup::UnlockingPosition; use cw_storage_plus::{Item, Map}; use cw_utils::Duration; - use mars_rover::adapters::oracle::Oracle; pub const VAULT_TOKEN_DENOM: Item = Item::new("vault_token_denom"); diff --git a/contracts/mock-vault/src/unlock.rs b/contracts/mock-vault/src/unlock.rs index d3ff64206..b1f48dd7e 100644 --- a/contracts/mock-vault/src/unlock.rs +++ b/contracts/mock-vault/src/unlock.rs @@ -6,9 +6,11 @@ use cosmwasm_vault_standard::extensions::lockup::{ }; use cw_utils::{Duration, Expiration}; -use crate::error::ContractError; -use crate::state::{COIN_BALANCE, LOCKUP_TIME, NEXT_LOCKUP_ID, UNLOCKING_POSITIONS}; -use crate::withdraw::{get_vault_token, withdraw_state_update}; +use crate::{ + error::ContractError, + state::{COIN_BALANCE, LOCKUP_TIME, NEXT_LOCKUP_ID, UNLOCKING_POSITIONS}, + withdraw::{get_vault_token, withdraw_state_update}, +}; pub fn request_unlock( deps: DepsMut, @@ -56,11 +58,8 @@ pub fn withdraw_unlocked( .may_load(deps.storage, sender.clone())? .ok_or(ContractError::UnlockRequired {})?; - let matching_position = lockups - .iter() - .find(|p| p.id == id) - .ok_or(ContractError::UnlockRequired {})? - .clone(); + let matching_position = + lockups.iter().find(|p| p.id == id).ok_or(ContractError::UnlockRequired {})?.clone(); if &matching_position.owner != sender { return Err(ContractError::Unauthorized {}); diff --git a/contracts/mock-vault/src/withdraw.rs b/contracts/mock-vault/src/withdraw.rs index 3ae2d3d96..282bb6310 100644 --- a/contracts/mock-vault/src/withdraw.rs +++ b/contracts/mock-vault/src/withdraw.rs @@ -3,9 +3,11 @@ use cosmwasm_std::{ Uint128, }; -use crate::error::{ContractError, ContractResult}; -use crate::query::shares_to_base_denom_amount; -use crate::state::{CHAIN_BANK, COIN_BALANCE, LOCKUP_TIME, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}; +use crate::{ + error::{ContractError, ContractResult}, + query::shares_to_base_denom_amount, + state::{CHAIN_BANK, COIN_BALANCE, LOCKUP_TIME, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}, +}; pub fn withdraw(deps: DepsMut, info: MessageInfo) -> ContractResult { let lockup_time = LOCKUP_TIME.load(deps.storage)?; @@ -71,7 +73,5 @@ pub fn get_vault_token(storage: &mut dyn Storage, funds: Vec) -> ContractR } fn mock_lp_token_burn(storage: &mut dyn Storage, amount: Uint128) -> Result { - CHAIN_BANK.update(storage, |bank_amount| -> StdResult<_> { - Ok(bank_amount + amount) - }) + CHAIN_BANK.update(storage, |bank_amount| -> StdResult<_> { Ok(bank_amount + amount) }) } diff --git a/contracts/swapper/base/src/contract.rs b/contracts/swapper/base/src/contract.rs index 780e80fa9..ba8ba4816 100644 --- a/contracts/swapper/base/src/contract.rs +++ b/contracts/swapper/base/src/contract.rs @@ -5,14 +5,14 @@ use cosmwasm_std::{ DepsMut, Env, MessageInfo, Order, Response, WasmMsg, }; use cw_storage_plus::{Bound, Map}; -use mars_owner::OwnerInit::SetInitialOwner; -use mars_owner::{Owner, OwnerUpdate}; - -use mars_rover::adapters::swap::{ - EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg, RouteResponse, - RoutesResponse, +use mars_owner::{Owner, OwnerInit::SetInitialOwner, OwnerUpdate}; +use mars_rover::{ + adapters::swap::{ + EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg, RouteResponse, + RoutesResponse, + }, + error::ContractError as RoverError, }; -use mars_rover::error::ContractError as RoverError; use crate::{ContractResult, Route}; @@ -61,8 +61,13 @@ where deps: DepsMut, msg: InstantiateMsg, ) -> ContractResult> { - self.owner - .initialize(deps.storage, deps.api, SetInitialOwner { owner: msg.owner })?; + self.owner.initialize( + deps.storage, + deps.api, + SetInitialOwner { + owner: msg.owner, + }, + )?; Ok(Response::default()) } @@ -96,16 +101,18 @@ where pub fn query(&self, deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { let res = match msg { QueryMsg::Owner {} => to_binary(&self.owner.query(deps.storage)?), - QueryMsg::EstimateExactInSwap { coin_in, denom_out } => { - to_binary(&self.estimate_exact_in_swap(deps, env, coin_in, denom_out)?) - } + QueryMsg::EstimateExactInSwap { + coin_in, + denom_out, + } => to_binary(&self.estimate_exact_in_swap(deps, env, coin_in, denom_out)?), QueryMsg::Route { denom_in, denom_out, } => to_binary(&self.query_route(deps, denom_in, denom_out)?), - QueryMsg::Routes { start_after, limit } => { - to_binary(&self.query_routes(deps, start_after, limit)?) - } + QueryMsg::Routes { + start_after, + limit, + } => to_binary(&self.query_routes(deps, start_after, limit)?), }; res.map_err(Into::into) } @@ -153,9 +160,7 @@ where coin_in: Coin, denom_out: String, ) -> ContractResult { - let route = self - .routes - .load(deps.storage, (coin_in.denom.clone(), denom_out))?; + let route = self.routes.load(deps.storage, (coin_in.denom.clone(), denom_out))?; route.estimate_exact_in_swap(&deps.querier, &env, &coin_in) } @@ -212,12 +217,9 @@ where .into()); }; - let denom_in_balance = deps - .querier - .query_balance(env.contract.address.clone(), denom_in)?; - let denom_out_balance = deps - .querier - .query_balance(env.contract.address, denom_out)?; + let denom_in_balance = + deps.querier.query_balance(env.contract.address.clone(), denom_in)?; + let denom_out_balance = deps.querier.query_balance(env.contract.address, denom_out)?; let transfer_msg = CosmosMsg::Bank(BankMsg::Send { to_address: recipient.to_string(), @@ -245,8 +247,7 @@ where route.validate(&deps.querier, &denom_in, &denom_out)?; - self.routes - .save(deps.storage, (denom_in.clone(), denom_out.clone()), &route)?; + self.routes.save(deps.storage, (denom_in.clone(), denom_out.clone()), &route)?; Ok(Response::new() .add_attribute("action", "rover/base/set_route") diff --git a/contracts/swapper/base/src/error.rs b/contracts/swapper/base/src/error.rs index a9e911820..2411d51b4 100644 --- a/contracts/swapper/base/src/error.rs +++ b/contracts/swapper/base/src/error.rs @@ -12,7 +12,9 @@ pub enum ContractError { DecimalRangeExceeded(#[from] DecimalRangeExceeded), #[error("Invalid route: {reason}")] - InvalidRoute { reason: String }, + InvalidRoute { + reason: String, + }, #[error("{0}")] Overflow(#[from] OverflowError), @@ -21,7 +23,10 @@ pub enum ContractError { CheckedMultiplyRatio(#[from] CheckedMultiplyRatioError), #[error("{denom_a:?}-{denom_b:?} is not an available pool")] - PoolNotFound { denom_a: String, denom_b: String }, + PoolNotFound { + denom_a: String, + denom_b: String, + }, #[error("{0}")] Rover(#[from] RoverError), diff --git a/contracts/swapper/base/src/traits.rs b/contracts/swapper/base/src/traits.rs index 80f7e4eb3..ba2afdef8 100644 --- a/contracts/swapper/base/src/traits.rs +++ b/contracts/swapper/base/src/traits.rs @@ -1,11 +1,10 @@ use std::fmt::{Debug, Display}; use cosmwasm_std::{Coin, CosmosMsg, CustomMsg, CustomQuery, Decimal, Env, QuerierWrapper}; +use mars_rover::adapters::swap::EstimateExactInSwapResponse; use schemars::JsonSchema; use serde::{de::DeserializeOwned, Serialize}; -use mars_rover::adapters::swap::EstimateExactInSwapResponse; - use crate::ContractResult; pub trait Route: diff --git a/contracts/swapper/mock/src/contract.rs b/contracts/swapper/mock/src/contract.rs index d0adc1619..c2765f8d3 100644 --- a/contracts/swapper/mock/src/contract.rs +++ b/contracts/swapper/mock/src/contract.rs @@ -2,7 +2,6 @@ use cosmwasm_std::{ coins, to_binary, BankMsg, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdError, StdResult, Uint128, }; - use mars_rover::adapters::swap::{ EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg, }; @@ -28,8 +27,12 @@ pub fn execute( ) -> StdResult { match msg { ExecuteMsg::UpdateOwner(_) => unimplemented!("not implemented"), - ExecuteMsg::SetRoute { .. } => unimplemented!("not implemented"), - ExecuteMsg::TransferResult { .. } => unimplemented!("not implemented"), + ExecuteMsg::SetRoute { + .. + } => unimplemented!("not implemented"), + ExecuteMsg::TransferResult { + .. + } => unimplemented!("not implemented"), ExecuteMsg::SwapExactIn { coin_in, denom_out, @@ -41,10 +44,18 @@ pub fn execute( #[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::Owner { .. } => unimplemented!("not implemented"), - QueryMsg::Route { .. } => unimplemented!("not implemented"), - QueryMsg::Routes { .. } => unimplemented!("not implemented"), - QueryMsg::EstimateExactInSwap { .. } => to_binary(&estimate_exact_in_swap()), + QueryMsg::Owner { + .. + } => unimplemented!("not implemented"), + QueryMsg::Route { + .. + } => unimplemented!("not implemented"), + QueryMsg::Routes { + .. + } => unimplemented!("not implemented"), + QueryMsg::EstimateExactInSwap { + .. + } => to_binary(&estimate_exact_in_swap()), } } @@ -62,17 +73,13 @@ pub fn swap_exact_in( denom_out: String, _slippage: Decimal, ) -> StdResult { - let denom_in_balance = deps - .querier - .query_balance(env.contract.address, coin_in.denom)?; + let denom_in_balance = deps.querier.query_balance(env.contract.address, coin_in.denom)?; if denom_in_balance.amount < coin_in.amount { return Err(StdError::generic_err("Did not send funds")); } if denom_out != "uosmo" { - return Err(StdError::generic_err( - "Mock swapper can only have uosmo as denom out", - )); + return Err(StdError::generic_err("Mock swapper can only have uosmo as denom out")); } // This is dependent on the mock env to pre-fund this contract with uosmo coins diff --git a/contracts/swapper/osmosis/src/contract.rs b/contracts/swapper/osmosis/src/contract.rs index bb4e781f7..c97f9572d 100644 --- a/contracts/swapper/osmosis/src/contract.rs +++ b/contracts/swapper/osmosis/src/contract.rs @@ -18,11 +18,7 @@ pub fn instantiate( _info: MessageInfo, msg: InstantiateMsg, ) -> ContractResult { - set_contract_version( - deps.storage, - &format!("crates.io:{}", CONTRACT_NAME), - CONTRACT_VERSION, - )?; + set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; OsmosisSwap::default().instantiate(deps, msg) } diff --git a/contracts/swapper/osmosis/src/helpers.rs b/contracts/swapper/osmosis/src/helpers.rs index 25ec7394b..5f09f658e 100644 --- a/contracts/swapper/osmosis/src/helpers.rs +++ b/contracts/swapper/osmosis/src/helpers.rs @@ -1,6 +1,6 @@ +use std::{collections::HashSet, hash::Hash}; + use cosmwasm_std::{Decimal, Uint128}; -use std::collections::HashSet; -use std::hash::Hash; /// Build a hashset from array data pub(crate) fn hashset(data: &[T]) -> HashSet { diff --git a/contracts/swapper/osmosis/src/route.rs b/contracts/swapper/osmosis/src/route.rs index 4810df2a1..8ab78136a 100644 --- a/contracts/swapper/osmosis/src/route.rs +++ b/contracts/swapper/osmosis/src/route.rs @@ -25,7 +25,7 @@ impl fmt::Display for OsmosisRoute { .map(|step| format!("{}:{}", step.pool_id, step.token_out_denom)) .collect::>() .join("|"); - write!(f, "{}", s) + write!(f, "{s}") } } @@ -93,8 +93,7 @@ impl Route for OsmosisRoute { if prev_denom_out != denom_out { return Err(ContractError::InvalidRoute { reason: format!( - "the route's output denom {} does not match the desired output {}", - prev_denom_out, denom_out + "the route's output denom {prev_denom_out} does not match the desired output {denom_out}", ), }); } @@ -139,7 +138,9 @@ impl Route for OsmosisRoute { coin_in: &Coin, ) -> ContractResult { let out_amount = query_out_amount(querier, &env.block, coin_in, &self.0)?; - Ok(EstimateExactInSwapResponse { amount: out_amount }) + Ok(EstimateExactInSwapResponse { + amount: out_amount, + }) } } @@ -162,19 +163,13 @@ fn query_out_amount( let mut price = Decimal::one(); let mut denom_in = coin_in.denom.clone(); for step in steps { - let step_price = query_twap_price( - querier, - step.pool_id, - &denom_in, - &step.token_out_denom, - start_time, - )?; + let step_price = + query_twap_price(querier, step.pool_id, &denom_in, &step.token_out_denom, start_time)?; price = price.checked_mul(step_price)?; denom_in = step.token_out_denom.clone(); } - let out_amount = coin_in - .amount - .checked_multiply_ratio(price.numerator(), price.denominator())?; + let out_amount = + coin_in.amount.checked_multiply_ratio(price.numerator(), price.denominator())?; Ok(out_amount) } diff --git a/contracts/swapper/osmosis/tests/helpers.rs b/contracts/swapper/osmosis/tests/helpers.rs index 150a95b9e..42c365ae8 100644 --- a/contracts/swapper/osmosis/tests/helpers.rs +++ b/contracts/swapper/osmosis/tests/helpers.rs @@ -1,33 +1,27 @@ +use std::{fmt::Display, str::FromStr}; + use cosmwasm_std::{Coin, Decimal, Uint128}; +use mars_rover::adapters::swap::InstantiateMsg; use osmosis_std::types::osmosis::gamm::v1beta1::{ MsgSwapExactAmountIn, MsgSwapExactAmountInResponse, SwapAmountInRoute, }; -use std::fmt::Display; -use std::str::FromStr; - -use osmosis_testing::cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest; use osmosis_testing::{ - Account, Bank, ExecuteResponse, Gamm, OsmosisTestApp, Runner, RunnerError, SigningAccount, Wasm, + cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest, Account, Bank, ExecuteResponse, + Gamm, OsmosisTestApp, Runner, RunnerError, SigningAccount, Wasm, }; -use mars_rover::adapters::swap::InstantiateMsg; - const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); pub fn wasm_file() -> String { let artifacts_dir = std::env::var("ARTIFACTS_DIR_PATH").unwrap_or_else(|_| "artifacts".to_string()); let snaked_name = CONTRACT_NAME.replace('-', "_"); - format!("../../../{}/{}.wasm", artifacts_dir, snaked_name) + format!("../../../{artifacts_dir}/{snaked_name}.wasm") } pub fn instantiate_contract(wasm: &Wasm, owner: &SigningAccount) -> String { let wasm_byte_code = std::fs::read(wasm_file()).unwrap(); - let code_id = wasm - .store_code(&wasm_byte_code, None, owner) - .unwrap() - .data - .code_id; + let code_id = wasm.store_code(&wasm_byte_code, None, owner).unwrap().data.code_id; wasm.instantiate( code_id, @@ -113,7 +107,7 @@ pub fn query_price_from_pool(gamm: &Gamm, pool_id: u64, denom: & } else if coin_2.denom == denom { Decimal::from_ratio(coin_1_amt, coin_2_amt) } else { - panic!("{} not found in the pool {}", denom, pool_id) + panic!("{denom} not found in the pool {pool_id}") } } @@ -130,11 +124,15 @@ pub fn query_balance(bank: &Bank, addr: &str, denom: &str) -> u1 pub fn assert_err(actual: RunnerError, expected: impl Display) { match actual { - RunnerError::ExecuteError { msg } => { - assert!(msg.contains(&format!("{}", expected))) + RunnerError::ExecuteError { + msg, + } => { + assert!(msg.contains(&format!("{expected}"))) } - RunnerError::QueryError { msg } => { - assert!(msg.contains(&format!("{}", expected))) + RunnerError::QueryError { + msg, + } => { + assert!(msg.contains(&format!("{expected}"))) } _ => panic!("Unhandled error"), } diff --git a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs index bf154bc54..1d6c69853 100644 --- a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs +++ b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs @@ -1,12 +1,14 @@ extern crate core; -use crate::helpers::instantiate_contract; +use std::collections::HashMap; + use cosmwasm_std::coin; use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; use mars_swapper_osmosis::route::OsmosisRoute; use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; use osmosis_testing::{Gamm, Module, OsmosisTestApp, SigningAccount, Wasm}; -use std::collections::HashMap; + +use crate::helpers::instantiate_contract; pub mod helpers; @@ -123,10 +125,7 @@ fn create_pools_and_routes( let gamm = Gamm::new(app); let pool_atom_osmo = gamm - .create_basic_pool( - &[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], - signer, - ) + .create_basic_pool(&[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], signer) .unwrap() .data .pool_id; diff --git a/contracts/swapper/osmosis/tests/test_estimate.rs b/contracts/swapper/osmosis/tests/test_estimate.rs index 8d65af0a2..b14eea59b 100644 --- a/contracts/swapper/osmosis/tests/test_estimate.rs +++ b/contracts/swapper/osmosis/tests/test_estimate.rs @@ -1,9 +1,8 @@ use cosmwasm_std::{coin, Uint128}; -use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; -use osmosis_testing::{Gamm, Module, OsmosisTestApp, RunnerResult, Wasm}; - use mars_rover::adapters::swap::{EstimateExactInSwapResponse, ExecuteMsg, QueryMsg}; use mars_swapper_osmosis::route::OsmosisRoute; +use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; +use osmosis_testing::{Gamm, Module, OsmosisTestApp, RunnerResult, Wasm}; use crate::helpers::{ assert_err, instantiate_contract, query_price_from_pool, swap_to_create_twap_records, @@ -15,9 +14,7 @@ pub mod helpers; fn test_error_on_route_not_found() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let owner = app - .init_account(&[coin(1_000_000_000_000, "uosmo")]) - .unwrap(); + let owner = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); let contract_addr = instantiate_contract(&wasm, &owner); @@ -29,10 +26,7 @@ fn test_error_on_route_not_found() { }, ); - assert_err( - res.unwrap_err(), - "swapper_osmosis::route::OsmosisRoute not found", - ); + assert_err(res.unwrap_err(), "swapper_osmosis::route::OsmosisRoute not found"); } #[test] @@ -41,31 +35,19 @@ fn test_estimate_swap_one_step() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uatom"), - coin(1_000_000_000_000, "uosmo"), - ]) + .init_account(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")]) .unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); let gamm = Gamm::new(&app); let pool_atom_osmo = gamm - .create_basic_pool( - &[coin(1_500_000, "uatom"), coin(6_000_000, "uosmo")], - &signer, - ) + .create_basic_pool(&[coin(1_500_000, "uatom"), coin(6_000_000, "uosmo")], &signer) .unwrap() .data .pool_id; - swap_to_create_twap_records( - &app, - &signer, - pool_atom_osmo, - coin(10u128, "uatom"), - "uosmo", - ); + swap_to_create_twap_records(&app, &signer, pool_atom_osmo, coin(10u128, "uatom"), "uosmo"); wasm.execute( &contract_addr, @@ -116,10 +98,7 @@ fn test_estimate_swap_multi_step() { let gamm = Gamm::new(&app); let pool_atom_osmo = gamm - .create_basic_pool( - &[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], - &signer, - ) + .create_basic_pool(&[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], &signer) .unwrap() .data .pool_id; diff --git a/contracts/swapper/osmosis/tests/test_instantiate.rs b/contracts/swapper/osmosis/tests/test_instantiate.rs index b01f34341..2f39da155 100644 --- a/contracts/swapper/osmosis/tests/test_instantiate.rs +++ b/contracts/swapper/osmosis/tests/test_instantiate.rs @@ -1,8 +1,7 @@ use cosmwasm_std::coin; use mars_owner::OwnerResponse; -use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; - use mars_rover::adapters::swap::{InstantiateMsg, QueryMsg}; +use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; use crate::helpers::{instantiate_contract, wasm_file}; @@ -12,9 +11,7 @@ pub mod helpers; fn test_owner_set_on_instantiate() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let signer = app - .init_account(&[coin(1_000_000_000_000, "uosmo")]) - .unwrap(); + let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); @@ -26,16 +23,10 @@ fn test_owner_set_on_instantiate() { fn test_raises_on_invalid_owner_addr() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let signer = app - .init_account(&[coin(1_000_000_000_000, "uosmo")]) - .unwrap(); + let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); let wasm_byte_code = std::fs::read(wasm_file()).unwrap(); - let code_id = wasm - .store_code(&wasm_byte_code, None, &signer) - .unwrap() - .data - .code_id; + let code_id = wasm.store_code(&wasm_byte_code, None, &signer).unwrap().data.code_id; let owner = "%%%INVALID%%%"; let res = wasm.instantiate( diff --git a/contracts/swapper/osmosis/tests/test_set_route.rs b/contracts/swapper/osmosis/tests/test_set_route.rs index 231f211af..805858a22 100644 --- a/contracts/swapper/osmosis/tests/test_set_route.rs +++ b/contracts/swapper/osmosis/tests/test_set_route.rs @@ -1,12 +1,10 @@ -use cosmwasm_std::coin; -use cosmwasm_std::StdError::GenericErr; +use cosmwasm_std::{coin, StdError::GenericErr}; use mars_owner::OwnerError; -use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; -use osmosis_testing::{Gamm, Module, OsmosisTestApp, Wasm}; - use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; use mars_swapper_base::ContractError; use mars_swapper_osmosis::route::OsmosisRoute; +use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; +use osmosis_testing::{Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract}; @@ -17,9 +15,7 @@ fn test_only_owner_can_set_routes() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let accs = app - .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) - .unwrap(); + let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); let owner = &accs[0]; let bad_guy = &accs[1]; @@ -55,9 +51,7 @@ fn test_must_pass_at_least_one_step() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let signer = app - .init_account(&[coin(1_000_000_000_000, "uosmo")]) - .unwrap(); + let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); @@ -87,9 +81,7 @@ fn test_must_be_available_in_osmosis() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let signer = app - .init_account(&[coin(1_000_000_000_000, "uosmo")]) - .unwrap(); + let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); @@ -123,20 +115,14 @@ fn test_step_does_not_contain_input_denom() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uatom"), - coin(1_000_000_000_000, "uosmo"), - ]) + .init_account(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")]) .unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); let gamm = Gamm::new(&app); let pool_atom_osmo = gamm - .create_basic_pool( - &[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], - &signer, - ) + .create_basic_pool(&[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], &signer) .unwrap() .data .pool_id; @@ -160,10 +146,7 @@ fn test_step_does_not_contain_input_denom() { assert_err( res_err, ContractError::InvalidRoute { - reason: format!( - "step 1: pool {} does not contain input denom umars", - pool_atom_osmo - ), + reason: format!("step 1: pool {pool_atom_osmo} does not contain input denom umars",), }, ); } @@ -174,20 +157,14 @@ fn test_step_does_not_contain_output_denom() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[ - coin(1_000_000_000_000, "umars"), - coin(1_000_000_000_000, "uosmo"), - ]) + .init_account(&[coin(1_000_000_000_000, "umars"), coin(1_000_000_000_000, "uosmo")]) .unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); let gamm = Gamm::new(&app); let pool_mars_osmo = gamm - .create_basic_pool( - &[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], - &signer, - ) + .create_basic_pool(&[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], &signer) .unwrap() .data .pool_id; @@ -211,10 +188,7 @@ fn test_step_does_not_contain_output_denom() { assert_err( res_err, ContractError::InvalidRoute { - reason: format!( - "step 1: pool {} does not contain output denom uweth", - pool_mars_osmo - ), + reason: format!("step 1: pool {pool_mars_osmo} does not contain output denom uweth"), }, ); } @@ -237,26 +211,17 @@ fn test_steps_do_not_loop() { let gamm = Gamm::new(&app); let pool_atom_osmo = gamm - .create_basic_pool( - &[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], - &signer, - ) + .create_basic_pool(&[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], &signer) .unwrap() .data .pool_id; let pool_osmo_usdc = gamm - .create_basic_pool( - &[coin(6_000_000, "uosmo"), coin(1_500_000, "uusdc")], - &signer, - ) + .create_basic_pool(&[coin(6_000_000, "uosmo"), coin(1_500_000, "uusdc")], &signer) .unwrap() .data .pool_id; let pool_osmo_mars = gamm - .create_basic_pool( - &[coin(6_000_000, "uosmo"), coin(1_500_000, "umars")], - &signer, - ) + .create_basic_pool(&[coin(6_000_000, "uosmo"), coin(1_500_000, "umars")], &signer) .unwrap() .data .pool_id; @@ -307,20 +272,14 @@ fn test_step_output_does_not_match() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uatom"), - coin(1_000_000_000_000, "uosmo"), - ]) + .init_account(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")]) .unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); let gamm = Gamm::new(&app); let pool_atom_osmo = gamm - .create_basic_pool( - &[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], - &signer, - ) + .create_basic_pool(&[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], &signer) .unwrap() .data .pool_id; @@ -367,10 +326,7 @@ fn test_set_route_success() { let gamm = Gamm::new(&app); let pool_mars_osmo = gamm - .create_basic_pool( - &[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], - &signer, - ) + .create_basic_pool(&[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], &signer) .unwrap() .data .pool_id; @@ -413,8 +369,5 @@ fn test_set_route_success() { assert_eq!(res.denom_in, "umars".to_string()); assert_eq!(res.denom_out, "uweth".to_string()); - assert_eq!( - res.route.to_string(), - format!("{}:uosmo|{}:uweth", pool_mars_osmo, pool_weth_osmo) - ); + assert_eq!(res.route.to_string(), format!("{pool_mars_osmo}:uosmo|{pool_weth_osmo}:uweth")); } diff --git a/contracts/swapper/osmosis/tests/test_swap.rs b/contracts/swapper/osmosis/tests/test_swap.rs index 7bc49aa0e..7831c03ed 100644 --- a/contracts/swapper/osmosis/tests/test_swap.rs +++ b/contracts/swapper/osmosis/tests/test_swap.rs @@ -1,11 +1,9 @@ use cosmwasm_std::{coin, Addr, Decimal}; -use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; -use osmosis_testing::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; - -use mars_rover::adapters::swap::ExecuteMsg; -use mars_rover::error::ContractError as RoverError; +use mars_rover::{adapters::swap::ExecuteMsg, error::ContractError as RoverError}; use mars_swapper_base::ContractError; use mars_swapper_osmosis::route::OsmosisRoute; +use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; +use osmosis_testing::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{ assert_err, instantiate_contract, query_balance, swap_to_create_twap_records, @@ -18,9 +16,7 @@ fn test_transfer_callback_only_internal() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let accs = app - .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) - .unwrap(); + let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); let owner = &accs[0]; let bad_guy = &accs[1]; @@ -54,10 +50,7 @@ fn test_swap_exact_in_slippage_too_high() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uosmo"), - coin(1_000_000_000_000, "umars"), - ]) + .init_account(&[coin(1_000_000_000_000, "uosmo"), coin(1_000_000_000_000, "umars")]) .unwrap(); let whale = app.init_account(&[coin(1_000_000, "umars")]).unwrap(); @@ -65,21 +58,12 @@ fn test_swap_exact_in_slippage_too_high() { let gamm = Gamm::new(&app); let pool_mars_osmo = gamm - .create_basic_pool( - &[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], - &signer, - ) + .create_basic_pool(&[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], &signer) .unwrap() .data .pool_id; - swap_to_create_twap_records( - &app, - &signer, - pool_mars_osmo, - coin(10u128, "umars"), - "uosmo", - ); + swap_to_create_twap_records(&app, &signer, pool_mars_osmo, coin(10u128, "umars"), "uosmo"); let route = OsmosisRoute(vec![SwapAmountInRoute { pool_id: pool_mars_osmo, @@ -124,10 +108,7 @@ fn test_swap_exact_in_success() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uosmo"), - coin(1_000_000_000_000, "umars"), - ]) + .init_account(&[coin(1_000_000_000_000, "uosmo"), coin(1_000_000_000_000, "umars")]) .unwrap(); let user = app.init_account(&[coin(10_000, "umars")]).unwrap(); @@ -135,21 +116,12 @@ fn test_swap_exact_in_success() { let gamm = Gamm::new(&app); let pool_mars_osmo = gamm - .create_basic_pool( - &[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], - &signer, - ) + .create_basic_pool(&[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], &signer) .unwrap() .data .pool_id; - swap_to_create_twap_records( - &app, - &signer, - pool_mars_osmo, - coin(10u128, "umars"), - "uosmo", - ); + swap_to_create_twap_records(&app, &signer, pool_mars_osmo, coin(10u128, "umars"), "uosmo"); wasm.execute( &contract_addr, diff --git a/contracts/swapper/osmosis/tests/test_update_admin.rs b/contracts/swapper/osmosis/tests/test_update_admin.rs index 04b314837..a6fe10c0a 100644 --- a/contracts/swapper/osmosis/tests/test_update_admin.rs +++ b/contracts/swapper/osmosis/tests/test_update_admin.rs @@ -1,9 +1,8 @@ use cosmwasm_std::coin; -use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; - use mars_owner::{OwnerResponse, OwnerUpdate}; use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg}; use mars_swapper_osmosis::route::OsmosisRoute; +use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; use crate::helpers::instantiate_contract; @@ -14,9 +13,7 @@ fn test_initial_state() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let accs = app - .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) - .unwrap(); + let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); let owner = &accs[0]; let contract_addr = instantiate_contract(&wasm, owner); @@ -31,9 +28,7 @@ fn test_only_owner_can_propose() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let accs = app - .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 3) - .unwrap(); + let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 3).unwrap(); let owner = &accs[0]; let bad_guy = &accs[1]; @@ -55,9 +50,7 @@ fn test_propose_new_owner() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let accs = app - .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) - .unwrap(); + let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); let owner = &accs[0]; let new_owner = &accs[1]; @@ -83,9 +76,7 @@ fn test_only_owner_can_clear_proposed() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let accs = app - .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 3) - .unwrap(); + let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 3).unwrap(); let owner = &accs[0]; let bad_guy = &accs[1]; let new_owner = &accs[2]; @@ -116,9 +107,7 @@ fn test_clear_proposed() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let accs = app - .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) - .unwrap(); + let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); let owner = &accs[0]; let new_owner = &accs[1]; @@ -152,9 +141,7 @@ fn test_only_proposed_owner_can_accept_role() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let accs = app - .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) - .unwrap(); + let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); let owner = &accs[0]; let new_owner = &accs[1]; @@ -184,9 +171,7 @@ fn test_accept_owner_role() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let accs = app - .init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2) - .unwrap(); + let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); let owner = &accs[0]; let new_owner = &accs[1]; diff --git a/contracts/zapper/base/src/contract.rs b/contracts/zapper/base/src/contract.rs index fcbfece3b..59222ffd8 100644 --- a/contracts/zapper/base/src/contract.rs +++ b/contracts/zapper/base/src/contract.rs @@ -59,9 +59,9 @@ where recipient, minimum_receive, ), - ExecuteMsg::WithdrawLiquidity { recipient } => { - Self::execute_withdraw_liquidity(deps, env, info, recipient) - } + ExecuteMsg::WithdrawLiquidity { + recipient, + } => Self::execute_withdraw_liquidity(deps, env, info, recipient), ExecuteMsg::Callback(msg) => { // Can only be called by the contract itself if info.sender != env.contract.address { @@ -83,9 +83,9 @@ where lp_token_out, coins_in, } => Self::query_estimate_provide_liquidity(deps, env, lp_token_out, coins_in), - QueryMsg::EstimateWithdrawLiquidity { coin_in } => { - Self::query_estimate_withdraw_liquidity(deps, env, coin_in) - } + QueryMsg::EstimateWithdrawLiquidity { + coin_in, + } => Self::query_estimate_withdraw_liquidity(deps, env, coin_in), } } @@ -112,17 +112,14 @@ where // Query current contract coin balances let mut coin_balances: Vec = Vec::with_capacity(info.funds.len() + 1); // funds + lp token for funded_coin in info.funds { - let mut coin_balance = deps - .querier - .query_balance(&env.contract.address, &funded_coin.denom)?; + let mut coin_balance = + deps.querier.query_balance(&env.contract.address, &funded_coin.denom)?; coin_balance.amount = coin_balance.amount.checked_sub(funded_coin.amount)?; coin_balances.push(coin_balance); } // Query current contract LP token balance - let lp_token_balance = deps - .querier - .query_balance(&env.contract.address, &lp_token_out)?; + let lp_token_balance = deps.querier.query_balance(&env.contract.address, &lp_token_out)?; coin_balances.push(lp_token_balance); // Callbacks to return remaining coins and LP tokens @@ -162,16 +159,14 @@ where let mut coin_balances: Vec = Vec::with_capacity(coins_returned.len() + 1); // coins returned + lp token for coin_returned in coins_returned.to_vec() { let coin_returned: Coin = coin_returned.try_into()?; - let coin_balance = deps - .querier - .query_balance(&env.contract.address, coin_returned.denom)?; + let coin_balance = + deps.querier.query_balance(&env.contract.address, coin_returned.denom)?; coin_balances.push(coin_balance); } // Query current contract LP token balance - let mut lp_token_balance = deps - .querier - .query_balance(&env.contract.address, &lp_token.denom)?; + let mut lp_token_balance = + deps.querier.query_balance(&env.contract.address, &lp_token.denom)?; lp_token_balance.amount = lp_token_balance.amount.checked_sub(lp_token.amount)?; coin_balances.push(lp_token_balance); @@ -193,9 +188,8 @@ where balance_before: Coin, recipient: Addr, ) -> Result { - let balance_after = deps - .querier - .query_balance(env.contract.address, &balance_before.denom)?; + let balance_after = + deps.querier.query_balance(env.contract.address, &balance_before.denom)?; let return_amount = balance_after.amount.checked_sub(balance_before.amount)?; if return_amount.is_zero() { diff --git a/contracts/zapper/base/src/msg.rs b/contracts/zapper/base/src/msg.rs index 20609aa67..9642f3cdd 100644 --- a/contracts/zapper/base/src/msg.rs +++ b/contracts/zapper/base/src/msg.rs @@ -44,5 +44,7 @@ pub enum QueryMsg { coins_in: Vec, }, #[returns(Vec)] - EstimateWithdrawLiquidity { coin_in: Coin }, + EstimateWithdrawLiquidity { + coin_in: Coin, + }, } diff --git a/contracts/zapper/base/src/traits.rs b/contracts/zapper/base/src/traits.rs index 77630bbd4..c04828c75 100644 --- a/contracts/zapper/base/src/traits.rs +++ b/contracts/zapper/base/src/traits.rs @@ -1,6 +1,5 @@ use cosmwasm_std::Deps; -use cw_dex::traits::Pool; -use cw_dex::CwDexError; +use cw_dex::{traits::Pool, CwDexError}; pub trait LpPool { /// Returns the matching pool given a LP token. diff --git a/contracts/zapper/mock/src/contract.rs b/contracts/zapper/mock/src/contract.rs index 4acae12f4..b5dc09641 100644 --- a/contracts/zapper/mock/src/contract.rs +++ b/contracts/zapper/mock/src/contract.rs @@ -1,13 +1,14 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; - use mars_rover::msg::zapper::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::error::ContractResult; -use crate::execute::{provide_liquidity, withdraw_liquidity}; -use crate::query::{estimate_provide_liquidity, estimate_withdraw_liquidity}; -use crate::state::{COIN_BALANCES, COIN_CONFIG, ORACLE}; +use crate::{ + error::ContractResult, + execute::{provide_liquidity, withdraw_liquidity}, + query::{estimate_provide_liquidity, estimate_withdraw_liquidity}, + state::{COIN_BALANCES, COIN_CONFIG, ORACLE}, +}; pub const STARTING_LP_POOL_TOKENS: Uint128 = Uint128::new(1_000_000); @@ -26,10 +27,7 @@ pub fn instantiate( COIN_CONFIG.save( deps.storage, &config.lp_token_denom, - &vec![ - config.lp_pair_denoms.0.to_string(), - config.lp_pair_denoms.1.to_string(), - ], + &vec![config.lp_pair_denoms.0.to_string(), config.lp_pair_denoms.1.to_string()], )?; // Store balances of each of the underlying @@ -60,7 +58,9 @@ pub fn execute( minimum_receive, .. } => provide_liquidity(deps, info, lp_token_out, minimum_receive), - ExecuteMsg::WithdrawLiquidity { .. } => withdraw_liquidity(deps, info), + ExecuteMsg::WithdrawLiquidity { + .. + } => withdraw_liquidity(deps, info), } } @@ -71,9 +71,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> ContractResult { lp_token_out, coins_in, } => to_binary(&estimate_provide_liquidity(&deps, &lp_token_out, coins_in)?), - QueryMsg::EstimateWithdrawLiquidity { coin_in } => { - to_binary(&estimate_withdraw_liquidity(deps.storage, &coin_in)?) - } + QueryMsg::EstimateWithdrawLiquidity { + coin_in, + } => to_binary(&estimate_withdraw_liquidity(deps.storage, &coin_in)?), }; res.map_err(Into::into) } diff --git a/contracts/zapper/mock/src/error.rs b/contracts/zapper/mock/src/error.rs index 750f2f945..b1212fbd8 100644 --- a/contracts/zapper/mock/src/error.rs +++ b/contracts/zapper/mock/src/error.rs @@ -1,8 +1,7 @@ use cosmwasm_std::{CheckedMultiplyRatioError, StdError}; use cw_utils::PaymentError; -use thiserror::Error; - use mars_rover::error::ContractError as RoverError; +use thiserror::Error; pub type ContractResult = Result; diff --git a/contracts/zapper/mock/src/execute.rs b/contracts/zapper/mock/src/execute.rs index 56bd0c097..74fa313d1 100644 --- a/contracts/zapper/mock/src/execute.rs +++ b/contracts/zapper/mock/src/execute.rs @@ -3,9 +3,11 @@ use cosmwasm_std::{ }; use cw_utils::one_coin; -use crate::error::{ContractError, ContractResult}; -use crate::query::{estimate_provide_liquidity, estimate_withdraw_liquidity}; -use crate::state::{COIN_BALANCES, COIN_CONFIG, LP_TOKEN_SUPPLY}; +use crate::{ + error::{ContractError, ContractResult}, + query::{estimate_provide_liquidity, estimate_withdraw_liquidity}, + state::{COIN_BALANCES, COIN_CONFIG, LP_TOKEN_SUPPLY}, +}; pub fn provide_liquidity( deps: DepsMut, @@ -36,9 +38,7 @@ pub fn provide_liquidity( deps.storage, (&lp_token_out_denom, &coin.denom), |amount_opt| -> StdResult<_> { - Ok(amount_opt - .unwrap_or(Uint128::zero()) - .checked_add(coin.amount)?) + Ok(amount_opt.unwrap_or(Uint128::zero()).checked_add(coin.amount)?) }, )?; } @@ -65,9 +65,7 @@ pub fn withdraw_liquidity(deps: DepsMut, info: MessageInfo) -> ContractResult StdResult<_> { - Ok(amount_opt - .unwrap_or(Uint128::zero()) - .checked_sub(coin.amount)?) + Ok(amount_opt.unwrap_or(Uint128::zero()).checked_sub(coin.amount)?) }, )?; } @@ -87,9 +85,7 @@ fn mock_lp_token_mint( amount: Uint128, lp_token_out_denom: &str, ) -> Result<(), StdError> { - let total_supply = LP_TOKEN_SUPPLY - .load(storage, lp_token_out_denom) - .unwrap_or(Uint128::zero()); + let total_supply = LP_TOKEN_SUPPLY.load(storage, lp_token_out_denom).unwrap_or(Uint128::zero()); LP_TOKEN_SUPPLY.save(storage, lp_token_out_denom, &(total_supply + amount))?; Ok(()) } diff --git a/contracts/zapper/mock/src/query.rs b/contracts/zapper/mock/src/query.rs index fcbf6a377..565efcc52 100644 --- a/contracts/zapper/mock/src/query.rs +++ b/contracts/zapper/mock/src/query.rs @@ -1,17 +1,17 @@ use cosmwasm_std::{Coin, Deps, StdResult, Storage, Uint128}; -use crate::contract::STARTING_LP_POOL_TOKENS; -use crate::error::ContractError; -use crate::state::{COIN_BALANCES, COIN_CONFIG, LP_TOKEN_SUPPLY, ORACLE}; +use crate::{ + contract::STARTING_LP_POOL_TOKENS, + error::ContractError, + state::{COIN_BALANCES, COIN_CONFIG, LP_TOKEN_SUPPLY, ORACLE}, +}; pub fn estimate_provide_liquidity( deps: &Deps, lp_token_out: &str, coins_in: Vec, ) -> Result { - let total_supply = LP_TOKEN_SUPPLY - .load(deps.storage, lp_token_out) - .unwrap_or(Uint128::zero()); + let total_supply = LP_TOKEN_SUPPLY.load(deps.storage, lp_token_out).unwrap_or(Uint128::zero()); let lp_tokens_estimate = if total_supply.is_zero() { STARTING_LP_POOL_TOKENS diff --git a/contracts/zapper/mock/src/state.rs b/contracts/zapper/mock/src/state.rs index 522b3359a..d030f0a00 100644 --- a/contracts/zapper/mock/src/state.rs +++ b/contracts/zapper/mock/src/state.rs @@ -1,6 +1,5 @@ use cosmwasm_std::Uint128; use cw_storage_plus::{Item, Map}; - use mars_rover::adapters::oracle::Oracle; pub const ORACLE: Item = Item::new("oracle"); diff --git a/contracts/zapper/osmosis/src/contract.rs b/contracts/zapper/osmosis/src/contract.rs index b3f78c160..598b3d82a 100644 --- a/contracts/zapper/osmosis/src/contract.rs +++ b/contracts/zapper/osmosis/src/contract.rs @@ -1,8 +1,9 @@ -use crate::lp_pool::OsmosisLpPool; use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use cw2::set_contract_version; use mars_zapper_base::{ContractError, ExecuteMsg, InstantiateMsg, QueryMsg, ZapperBase}; +use crate::lp_pool::OsmosisLpPool; + /// The Osmosis zapper contract inherits logic from the base zapper contract pub type OsmosisZapper = ZapperBase; @@ -16,11 +17,7 @@ pub fn instantiate( _info: MessageInfo, msg: InstantiateMsg, ) -> Result { - set_contract_version( - deps.storage, - &format!("crates.io:{}", CONTRACT_NAME), - CONTRACT_VERSION, - )?; + set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; OsmosisZapper::default().instantiate(deps, msg) } diff --git a/contracts/zapper/osmosis/src/lp_pool.rs b/contracts/zapper/osmosis/src/lp_pool.rs index b28b008d2..5dabddc33 100644 --- a/contracts/zapper/osmosis/src/lp_pool.rs +++ b/contracts/zapper/osmosis/src/lp_pool.rs @@ -1,9 +1,8 @@ +use std::str::FromStr; + use cosmwasm_std::Deps; -use cw_dex::osmosis::OsmosisPool; -use cw_dex::traits::Pool; -use cw_dex::CwDexError; +use cw_dex::{osmosis::OsmosisPool, traits::Pool, CwDexError}; use mars_zapper_base::LpPool; -use std::str::FromStr; pub struct OsmosisLpPool {} @@ -20,9 +19,8 @@ impl OsmosisLpPool { return Err(CwDexError::NotLpToken {}); } - let pool_id_str = lp_token_denom - .strip_prefix("gamm/pool/") - .ok_or(CwDexError::NotLpToken {})?; + let pool_id_str = + lp_token_denom.strip_prefix("gamm/pool/").ok_or(CwDexError::NotLpToken {})?; let pool_id = u64::from_str(pool_id_str).map_err(|_| CwDexError::NotLpToken {})?; diff --git a/contracts/zapper/osmosis/tests/helpers.rs b/contracts/zapper/osmosis/tests/helpers.rs index ae077f1bd..fef5c11d4 100644 --- a/contracts/zapper/osmosis/tests/helpers.rs +++ b/contracts/zapper/osmosis/tests/helpers.rs @@ -1,10 +1,10 @@ -use std::fmt::Display; -use std::str::FromStr; - -use osmosis_testing::cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest; -use osmosis_testing::{Bank, OsmosisTestApp, RunnerError, SigningAccount, Wasm}; +use std::{fmt::Display, str::FromStr}; use mars_zapper_base::InstantiateMsg; +use osmosis_testing::{ + cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest, Bank, OsmosisTestApp, RunnerError, + SigningAccount, Wasm, +}; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); @@ -12,29 +12,18 @@ pub fn wasm_file() -> String { let artifacts_dir = std::env::var("ARTIFACTS_DIR_PATH").unwrap_or_else(|_| "artifacts".to_string()); let snaked_name = CONTRACT_NAME.replace('-', "_"); - format!("../../../{}/{}.wasm", artifacts_dir, snaked_name) + format!("../../../{artifacts_dir}/{snaked_name}.wasm") } pub fn instantiate_contract(wasm: &Wasm, owner: &SigningAccount) -> String { println!("WASM name: {}", wasm_file()); let wasm_byte_code = std::fs::read(wasm_file()).unwrap(); - let code_id = wasm - .store_code(&wasm_byte_code, None, owner) + let code_id = wasm.store_code(&wasm_byte_code, None, owner).unwrap().data.code_id; + + wasm.instantiate(code_id, &InstantiateMsg {}, None, Some("zapper-osmosis-contract"), &[], owner) .unwrap() .data - .code_id; - - wasm.instantiate( - code_id, - &InstantiateMsg {}, - None, - Some("zapper-osmosis-contract"), - &[], - owner, - ) - .unwrap() - .data - .address + .address } pub fn query_balance(bank: &Bank, addr: &str, denom: &str) -> u128 { @@ -50,13 +39,17 @@ pub fn query_balance(bank: &Bank, addr: &str, denom: &str) -> u1 pub fn assert_err(actual: RunnerError, expected: impl Display) { match actual { - RunnerError::ExecuteError { msg } => { - println!("ExecuteError, msg: {}", msg); - assert!(msg.contains(&format!("{}", expected))) + RunnerError::ExecuteError { + msg, + } => { + println!("ExecuteError, msg: {msg}"); + assert!(msg.contains(&format!("{expected}"))) } - RunnerError::QueryError { msg } => { - println!("QueryError, msg: {}", msg); - assert!(msg.contains(&format!("{}", expected))) + RunnerError::QueryError { + msg, + } => { + println!("QueryError, msg: {msg}"); + assert!(msg.contains(&format!("{expected}"))) } _ => panic!("Unhandled error"), } diff --git a/contracts/zapper/osmosis/tests/test_callback.rs b/contracts/zapper/osmosis/tests/test_callback.rs index 5fc8fe283..75b6d6f78 100644 --- a/contracts/zapper/osmosis/tests/test_callback.rs +++ b/contracts/zapper/osmosis/tests/test_callback.rs @@ -1,7 +1,6 @@ use cosmwasm_std::{coin, Addr, Coin}; -use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; - use mars_zapper_base::{CallbackMsg, ContractError, ExecuteMsg}; +use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract}; @@ -13,13 +12,7 @@ fn test_only_contract_itself_can_callback() { let wasm = Wasm::new(&app); let accs = app - .init_accounts( - &[ - coin(1_000_000_000_000, "uatom"), - coin(1_000_000_000_000, "uosmo"), - ], - 2, - ) + .init_accounts(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")], 2) .unwrap(); let owner = &accs[0]; let user = &accs[1]; diff --git a/contracts/zapper/osmosis/tests/test_provide_liquidity.rs b/contracts/zapper/osmosis/tests/test_provide_liquidity.rs index 328328858..c74045d82 100644 --- a/contracts/zapper/osmosis/tests/test_provide_liquidity.rs +++ b/contracts/zapper/osmosis/tests/test_provide_liquidity.rs @@ -1,8 +1,7 @@ use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; -use osmosis_testing::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; - use mars_zapper_base::{ExecuteMsg, QueryMsg}; +use osmosis_testing::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract, query_balance}; @@ -14,10 +13,7 @@ fn test_provide_liquidity_with_invalid_lp_token() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uatom"), - coin(1_000_000_000_000, "uosmo"), - ]) + .init_account(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")]) .unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); @@ -43,18 +39,12 @@ fn test_provide_liquidity_with_invalid_coins() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uatom"), - coin(1_000_000_000_000, "uosmo"), - ]) + .init_account(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")]) .unwrap(); let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool( - &[coin(2_000_000, "uatom"), coin(4_000_000, "uosmo")], - &signer, - ) + .create_basic_pool(&[coin(2_000_000, "uatom"), coin(4_000_000, "uosmo")], &signer) .unwrap() .data .pool_id; @@ -65,7 +55,7 @@ fn test_provide_liquidity_with_invalid_coins() { wasm.execute( &contract_addr, &ExecuteMsg::ProvideLiquidity { - lp_token_out: format!("gamm/pool/{}", pool_id), + lp_token_out: format!("gamm/pool/{pool_id}"), recipient: None, minimum_receive: Uint128::one(), }, @@ -81,27 +71,18 @@ fn test_provide_liquidity_with_min_not_received() { let wasm = Wasm::new(&app); let accs = app - .init_accounts( - &[ - coin(1_000_000_000_000, "uatom"), - coin(1_000_000_000_000, "uosmo"), - ], - 2, - ) + .init_accounts(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")], 2) .unwrap(); let owner = &accs[0]; let user = &accs[1]; let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool( - &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], - owner, - ) + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], owner) .unwrap() .data .pool_id; - let pool_denom = format!("gamm/pool/{}", pool_id); + let pool_denom = format!("gamm/pool/{pool_id}"); let contract_addr = instantiate_contract(&wasm, owner); @@ -154,27 +135,18 @@ fn test_provide_liquidity_with_one_coin() { let uatom_acc_balance = 1_000_000_000_000u128; let uosmo_acc_balance = 1_000_000_000_000u128; let accs = app - .init_accounts( - &[ - coin(uatom_acc_balance, "uatom"), - coin(uosmo_acc_balance, "uosmo"), - ], - 2, - ) + .init_accounts(&[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")], 2) .unwrap(); let owner = &accs[0]; let user = &accs[1]; let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool( - &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], - owner, - ) + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], owner) .unwrap() .data .pool_id; - let pool_denom = format!("gamm/pool/{}", pool_id); + let pool_denom = format!("gamm/pool/{pool_id}"); let contract_addr = instantiate_contract(&wasm, owner); @@ -221,10 +193,7 @@ fn test_provide_liquidity_with_one_coin() { let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); assert_eq!(user_pool_balance, estimate_amount.u128()); let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); - assert_eq!( - user_uatom_balance, - uatom_acc_balance - uatom_liquidity_amount - ); + assert_eq!(user_uatom_balance, uatom_acc_balance - uatom_liquidity_amount); let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); assert_eq!(user_uosmo_balance, uosmo_acc_balance); } @@ -237,27 +206,18 @@ fn test_provide_liquidity_with_two_balanced_coins() { let uatom_acc_balance = 1_000_000_000_000u128; let uosmo_acc_balance = 1_000_000_000_000u128; let accs = app - .init_accounts( - &[ - coin(uatom_acc_balance, "uatom"), - coin(uosmo_acc_balance, "uosmo"), - ], - 2, - ) + .init_accounts(&[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")], 2) .unwrap(); let owner = &accs[0]; let user = &accs[1]; let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool( - &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], - owner, - ) + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], owner) .unwrap() .data .pool_id; - let pool_denom = format!("gamm/pool/{}", pool_id); + let pool_denom = format!("gamm/pool/{pool_id}"); let contract_addr = instantiate_contract(&wasm, owner); @@ -270,10 +230,8 @@ fn test_provide_liquidity_with_two_balanced_coins() { let uatom_liquidity_amount = 5_000_000u128; let uosmo_liquidity_amount = 10_000_000u128; - let coins_in = vec![ - coin(uatom_liquidity_amount, "uatom"), - coin(uosmo_liquidity_amount, "uosmo"), - ]; + let coins_in = + vec![coin(uatom_liquidity_amount, "uatom"), coin(uosmo_liquidity_amount, "uosmo")]; let estimate_amount: Uint128 = wasm .query( @@ -308,15 +266,9 @@ fn test_provide_liquidity_with_two_balanced_coins() { let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); assert_eq!(user_pool_balance, estimate_amount.u128()); let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); - assert_eq!( - user_uatom_balance, - uatom_acc_balance - uatom_liquidity_amount - ); + assert_eq!(user_uatom_balance, uatom_acc_balance - uatom_liquidity_amount); let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); - assert_eq!( - user_uosmo_balance, - uosmo_acc_balance - uosmo_liquidity_amount - ); + assert_eq!(user_uosmo_balance, uosmo_acc_balance - uosmo_liquidity_amount); } #[test] @@ -327,27 +279,18 @@ fn test_provide_liquidity_with_two_unbalanced_coins() { let uatom_acc_balance = 1_000_000_000_000u128; let uosmo_acc_balance = 1_000_000_000_000u128; let accs = app - .init_accounts( - &[ - coin(uatom_acc_balance, "uatom"), - coin(uosmo_acc_balance, "uosmo"), - ], - 2, - ) + .init_accounts(&[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")], 2) .unwrap(); let owner = &accs[0]; let user = &accs[1]; let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool( - &[coin(20_000_000_000, "uatom"), coin(40_000_000_000, "uosmo")], - owner, - ) + .create_basic_pool(&[coin(20_000_000_000, "uatom"), coin(40_000_000_000, "uosmo")], owner) .unwrap() .data .pool_id; - let pool_denom = format!("gamm/pool/{}", pool_id); + let pool_denom = format!("gamm/pool/{pool_id}"); let contract_addr = instantiate_contract(&wasm, owner); @@ -360,10 +303,8 @@ fn test_provide_liquidity_with_two_unbalanced_coins() { let uatom_liquidity_amount = 5_000_000u128; let uosmo_liquidity_amount = 22_000_000u128; - let coins_in = vec![ - coin(uatom_liquidity_amount, "uatom"), - coin(uosmo_liquidity_amount, "uosmo"), - ]; + let coins_in = + vec![coin(uatom_liquidity_amount, "uatom"), coin(uosmo_liquidity_amount, "uosmo")]; let estimate_amount: Uint128 = wasm .query( @@ -385,18 +326,10 @@ fn test_provide_liquidity_with_two_unbalanced_coins() { }, ) .unwrap(); - let uatom_estimate_amount = estimate_coins - .iter() - .find(|c| c.denom == "uatom") - .unwrap() - .amount - .u128(); - let uosmo_estimate_amount = estimate_coins - .iter() - .find(|c| c.denom == "uosmo") - .unwrap() - .amount - .u128(); + let uatom_estimate_amount = + estimate_coins.iter().find(|c| c.denom == "uatom").unwrap().amount.u128(); + let uosmo_estimate_amount = + estimate_coins.iter().find(|c| c.denom == "uosmo").unwrap().amount.u128(); assert_eq!(uatom_estimate_amount, 4950000u128); assert_eq!(uosmo_estimate_amount, 9900000u128); @@ -435,13 +368,7 @@ fn test_provide_liquidity_with_different_recipient() { let uatom_acc_balance = 1_000_000_000_000u128; let uosmo_acc_balance = 1_000_000_000_000u128; let accs = app - .init_accounts( - &[ - coin(uatom_acc_balance, "uatom"), - coin(uosmo_acc_balance, "uosmo"), - ], - 3, - ) + .init_accounts(&[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")], 3) .unwrap(); let owner = &accs[0]; let user = &accs[1]; @@ -449,14 +376,11 @@ fn test_provide_liquidity_with_different_recipient() { let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool( - &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], - owner, - ) + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], owner) .unwrap() .data .pool_id; - let pool_denom = format!("gamm/pool/{}", pool_id); + let pool_denom = format!("gamm/pool/{pool_id}"); let contract_addr = instantiate_contract(&wasm, owner); @@ -471,10 +395,8 @@ fn test_provide_liquidity_with_different_recipient() { let uatom_liquidity_amount = 5_000_000u128; let uosmo_liquidity_amount = 10_000_000u128; - let coins_in = vec![ - coin(uatom_liquidity_amount, "uatom"), - coin(uosmo_liquidity_amount, "uosmo"), - ]; + let coins_in = + vec![coin(uatom_liquidity_amount, "uatom"), coin(uosmo_liquidity_amount, "uosmo")]; let estimate_amount: Uint128 = wasm .query( @@ -509,15 +431,9 @@ fn test_provide_liquidity_with_different_recipient() { let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); assert_eq!(user_pool_balance, 0u128); let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); - assert_eq!( - user_uatom_balance, - uatom_acc_balance - uatom_liquidity_amount - ); + assert_eq!(user_uatom_balance, uatom_acc_balance - uatom_liquidity_amount); let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); - assert_eq!( - user_uosmo_balance, - uosmo_acc_balance - uosmo_liquidity_amount - ); + assert_eq!(user_uosmo_balance, uosmo_acc_balance - uosmo_liquidity_amount); let recipient_pool_balance = query_balance(&bank, &recipient.address(), &pool_denom); assert_eq!(recipient_pool_balance, estimate_amount.u128()); diff --git a/contracts/zapper/osmosis/tests/test_queries.rs b/contracts/zapper/osmosis/tests/test_queries.rs index f6292f9b3..2de4a1d5d 100644 --- a/contracts/zapper/osmosis/tests/test_queries.rs +++ b/contracts/zapper/osmosis/tests/test_queries.rs @@ -1,10 +1,9 @@ +use std::{ops::Div, str::FromStr}; + use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; -use osmosis_testing::{Gamm, Module, OsmosisTestApp, Wasm}; -use std::ops::Div; -use std::str::FromStr; - use mars_zapper_base::QueryMsg; +use osmosis_testing::{Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract}; @@ -15,9 +14,7 @@ fn test_estimate_provide_liquidity_with_invalid_lp_token() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let signer = app - .init_account(&[coin(1_000_000_000_000, "uosmo")]) - .unwrap(); + let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); @@ -39,18 +36,12 @@ fn test_estimate_provide_liquidity_with_invalid_coins() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uatom"), - coin(1_000_000_000_000, "uosmo"), - ]) + .init_account(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")]) .unwrap(); let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool( - &[coin(2_000_000, "uatom"), coin(4_000_000, "uosmo")], - &signer, - ) + .create_basic_pool(&[coin(2_000_000, "uatom"), coin(4_000_000, "uosmo")], &signer) .unwrap() .data .pool_id; @@ -78,18 +69,12 @@ fn test_estimate_provide_liquidity_successfully() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uatom"), - coin(1_000_000_000_000, "uosmo"), - ]) + .init_account(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")]) .unwrap(); let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool( - &[coin(2_000_000, "uatom"), coin(4_000_000, "uosmo")], - &signer, - ) + .create_basic_pool(&[coin(2_000_000, "uatom"), coin(4_000_000, "uosmo")], &signer) .unwrap() .data .pool_id; @@ -119,9 +104,7 @@ fn test_estimate_withdraw_liquidity_with_invalid_lp_token() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let signer = app - .init_account(&[coin(1_000_000_000_000, "uosmo")]) - .unwrap(); + let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); @@ -142,18 +125,12 @@ fn test_estimate_withdraw_liquidity_successfully() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uatom"), - coin(1_000_000_000_000, "uosmo"), - ]) + .init_account(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")]) .unwrap(); let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool( - &[coin(2_000_000, "uatom"), coin(4_000_000, "uosmo")], - &signer, - ) + .create_basic_pool(&[coin(2_000_000, "uatom"), coin(4_000_000, "uosmo")], &signer) .unwrap() .data .pool_id; diff --git a/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs b/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs index f94017a7e..98e4599ff 100644 --- a/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs +++ b/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs @@ -1,9 +1,8 @@ use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; use cw_utils::PaymentError; -use osmosis_testing::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; - use mars_zapper_base::{ContractError, ExecuteMsg, QueryMsg}; +use osmosis_testing::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract, query_balance}; @@ -15,10 +14,7 @@ fn test_withdraw_liquidity_without_funds() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[ - coin(1_000_000_000_000, "gamm/pool/1"), - coin(1_000_000_000_000, "uosmo"), - ]) + .init_account(&[coin(1_000_000_000_000, "gamm/pool/1"), coin(1_000_000_000_000, "uosmo")]) .unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); @@ -26,15 +22,14 @@ fn test_withdraw_liquidity_without_funds() { let res_err = wasm .execute( &contract_addr, - &ExecuteMsg::WithdrawLiquidity { recipient: None }, + &ExecuteMsg::WithdrawLiquidity { + recipient: None, + }, &[], &signer, ) .unwrap_err(); - assert_err( - res_err, - ContractError::PaymentError(PaymentError::NoFunds {}), - ); + assert_err(res_err, ContractError::PaymentError(PaymentError::NoFunds {})); } #[test] @@ -43,10 +38,7 @@ fn test_withdraw_liquidity_with_more_than_one_coin_sent() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[ - coin(1_000_000_000_000, "gamm/pool/1"), - coin(1_000_000_000_000, "uosmo"), - ]) + .init_account(&[coin(1_000_000_000_000, "gamm/pool/1"), coin(1_000_000_000_000, "uosmo")]) .unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); @@ -54,15 +46,14 @@ fn test_withdraw_liquidity_with_more_than_one_coin_sent() { let res_err = wasm .execute( &contract_addr, - &ExecuteMsg::WithdrawLiquidity { recipient: None }, + &ExecuteMsg::WithdrawLiquidity { + recipient: None, + }, &[coin(1_000_000, "gamm/pool/1"), coin(2_000_000, "uosmo")], &signer, ) .unwrap_err(); - assert_err( - res_err, - ContractError::PaymentError(PaymentError::MultipleDenoms {}), - ); + assert_err(res_err, ContractError::PaymentError(PaymentError::MultipleDenoms {})); } #[test] @@ -70,16 +61,16 @@ fn test_withdraw_liquidity_with_invalid_lp_token() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let signer = app - .init_account(&[coin(1_000_000_000_000, "uosmo")]) - .unwrap(); + let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); let res_err = wasm .execute( &contract_addr, - &ExecuteMsg::WithdrawLiquidity { recipient: None }, + &ExecuteMsg::WithdrawLiquidity { + recipient: None, + }, &[coin(1_000_000, "uosmo")], &signer, ) @@ -95,27 +86,18 @@ fn test_withdraw_liquidity_successfully() { let uatom_acc_balance = 1_000_000_000_000u128; let uosmo_acc_balance = 1_000_000_000_000u128; let accs = app - .init_accounts( - &[ - coin(uatom_acc_balance, "uatom"), - coin(uosmo_acc_balance, "uosmo"), - ], - 2, - ) + .init_accounts(&[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")], 2) .unwrap(); let owner = &accs[0]; let user = &accs[1]; let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool( - &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], - owner, - ) + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], owner) .unwrap() .data .pool_id; - let pool_denom = format!("gamm/pool/{}", pool_id); + let pool_denom = format!("gamm/pool/{pool_id}"); let contract_addr = instantiate_contract(&wasm, owner); @@ -133,10 +115,7 @@ fn test_withdraw_liquidity_successfully() { recipient: None, minimum_receive: Uint128::one(), }, - &[ - coin(uatom_liquidity_amount, "uatom"), - coin(uosmo_liquidity_amount, "uosmo"), - ], + &[coin(uatom_liquidity_amount, "uatom"), coin(uosmo_liquidity_amount, "uosmo")], user, ) .unwrap(); @@ -154,24 +133,18 @@ fn test_withdraw_liquidity_successfully() { }, ) .unwrap(); - let uatom_estimate_amount = estimate_coins - .iter() - .find(|c| c.denom == "uatom") - .unwrap() - .amount - .u128(); - let uosmo_estimate_amount = estimate_coins - .iter() - .find(|c| c.denom == "uosmo") - .unwrap() - .amount - .u128(); + let uatom_estimate_amount = + estimate_coins.iter().find(|c| c.denom == "uatom").unwrap().amount.u128(); + let uosmo_estimate_amount = + estimate_coins.iter().find(|c| c.denom == "uosmo").unwrap().amount.u128(); assert_eq!(uatom_estimate_amount, 4950000u128); assert_eq!(uosmo_estimate_amount, 9900000u128); wasm.execute( &contract_addr, - &ExecuteMsg::WithdrawLiquidity { recipient: None }, + &ExecuteMsg::WithdrawLiquidity { + recipient: None, + }, &[coin(user_pool_balance, &pool_denom)], user, ) @@ -187,15 +160,9 @@ fn test_withdraw_liquidity_successfully() { let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); assert_eq!(user_pool_balance, 0u128); let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); - assert_eq!( - user_uatom_balance, - user_uatom_balance_before + uatom_estimate_amount - ); + assert_eq!(user_uatom_balance, user_uatom_balance_before + uatom_estimate_amount); let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); - assert_eq!( - user_uosmo_balance, - user_uosmo_balance_before + uosmo_estimate_amount - ); + assert_eq!(user_uosmo_balance, user_uosmo_balance_before + uosmo_estimate_amount); } #[test] @@ -206,13 +173,7 @@ fn test_withdraw_liquidity_with_different_recipient_successfully() { let uatom_acc_balance = 1_000_000_000_000u128; let uosmo_acc_balance = 1_000_000_000_000u128; let accs = app - .init_accounts( - &[ - coin(uatom_acc_balance, "uatom"), - coin(uosmo_acc_balance, "uosmo"), - ], - 3, - ) + .init_accounts(&[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")], 3) .unwrap(); let owner = &accs[0]; let user = &accs[1]; @@ -220,14 +181,11 @@ fn test_withdraw_liquidity_with_different_recipient_successfully() { let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool( - &[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], - owner, - ) + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], owner) .unwrap() .data .pool_id; - let pool_denom = format!("gamm/pool/{}", pool_id); + let pool_denom = format!("gamm/pool/{pool_id}"); let contract_addr = instantiate_contract(&wasm, owner); @@ -245,10 +203,7 @@ fn test_withdraw_liquidity_with_different_recipient_successfully() { recipient: None, minimum_receive: Uint128::one(), }, - &[ - coin(uatom_liquidity_amount, "uatom"), - coin(uosmo_liquidity_amount, "uosmo"), - ], + &[coin(uatom_liquidity_amount, "uatom"), coin(uosmo_liquidity_amount, "uosmo")], user, ) .unwrap(); @@ -271,18 +226,10 @@ fn test_withdraw_liquidity_with_different_recipient_successfully() { }, ) .unwrap(); - let uatom_estimate_amount = estimate_coins - .iter() - .find(|c| c.denom == "uatom") - .unwrap() - .amount - .u128(); - let uosmo_estimate_amount = estimate_coins - .iter() - .find(|c| c.denom == "uosmo") - .unwrap() - .amount - .u128(); + let uatom_estimate_amount = + estimate_coins.iter().find(|c| c.denom == "uatom").unwrap().amount.u128(); + let uosmo_estimate_amount = + estimate_coins.iter().find(|c| c.denom == "uosmo").unwrap().amount.u128(); assert_eq!(uatom_estimate_amount, 4950000u128); assert_eq!(uosmo_estimate_amount, 9900000u128); @@ -313,13 +260,7 @@ fn test_withdraw_liquidity_with_different_recipient_successfully() { let recipient_pool_balance = query_balance(&bank, &recipient.address(), &pool_denom); assert_eq!(recipient_pool_balance, 0u128); let recipient_uatom_balance = query_balance(&bank, &recipient.address(), "uatom"); - assert_eq!( - recipient_uatom_balance, - recipient_uatom_balance_before + uatom_estimate_amount - ); + assert_eq!(recipient_uatom_balance, recipient_uatom_balance_before + uatom_estimate_amount); let recipient_uosmo_balance = query_balance(&bank, &recipient.address(), "uosmo"); - assert_eq!( - recipient_uosmo_balance, - recipient_uosmo_balance_before + uosmo_estimate_amount - ); + assert_eq!(recipient_uosmo_balance, recipient_uosmo_balance_before + uosmo_estimate_amount); } diff --git a/packages/chains/osmosis/src/helpers.rs b/packages/chains/osmosis/src/helpers.rs index 4a5a35aae..56f6c046e 100644 --- a/packages/chains/osmosis/src/helpers.rs +++ b/packages/chains/osmosis/src/helpers.rs @@ -3,13 +3,19 @@ use std::str::FromStr; use cosmwasm_std::{ coin, Decimal, Empty, QuerierWrapper, QueryRequest, StdError, StdResult, Uint128, }; - -use osmosis_std::shim::Timestamp; -use osmosis_std::types::cosmos::base::v1beta1::Coin; -use osmosis_std::types::osmosis::gamm::v1beta1::{PoolAsset, PoolParams, QueryPoolRequest}; -use osmosis_std::types::osmosis::gamm::v2::GammQuerier; -use osmosis_std::types::osmosis::twap::v1beta1::TwapQuerier; - +use osmosis_std::{ + shim::Timestamp, + types::{ + cosmos::base::v1beta1::Coin, + osmosis::{ + gamm::{ + v1beta1::{PoolAsset, PoolParams, QueryPoolRequest}, + v2::GammQuerier, + }, + twap::v1beta1::TwapQuerier, + }, + }, +}; use serde::{Deserialize, Serialize}; // NOTE: Use custom Pool (`id` type as String) due to problem with json (de)serialization discrepancy between go and rust side. @@ -32,10 +38,8 @@ impl Pool { None => return Err(StdError::generic_err("missing coin")), // just in case, it shouldn't happen Some(osmosis_coin) => osmosis_coin, }; - let cosmwasm_coin = coin( - Uint128::from_str(&osmosis_coin.amount)?.u128(), - &osmosis_coin.denom, - ); + let cosmwasm_coin = + coin(Uint128::from_str(&osmosis_coin.amount)?.u128(), &osmosis_coin.denom); Ok(cosmwasm_coin) } } @@ -47,16 +51,16 @@ pub struct QueryPoolResponse { /// Query an Osmosis pool's coin depths and the supply of of liquidity token pub fn query_pool(querier: &QuerierWrapper, pool_id: u64) -> StdResult { - let req: QueryRequest = QueryPoolRequest { pool_id }.into(); + let req: QueryRequest = QueryPoolRequest { + pool_id, + } + .into(); let res: QueryPoolResponse = querier.query(&req)?; Ok(res.pool) } pub fn has_denom(denom: &str, pool_assets: &[PoolAsset]) -> bool { - pool_assets - .iter() - .flat_map(|asset| &asset.token) - .any(|coin| coin.denom == denom) + pool_assets.iter().flat_map(|asset| &asset.token).any(|coin| coin.denom == denom) } /// Query the spot price of a coin, denominated in OSMO diff --git a/packages/health/src/health.rs b/packages/health/src/health.rs index 1f70f985b..0587f5d9a 100644 --- a/packages/health/src/health.rs +++ b/packages/health/src/health.rs @@ -1,6 +1,6 @@ -use cosmwasm_schema::cw_serde; use std::fmt; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Decimal, Uint128}; #[cw_serde] @@ -37,14 +37,12 @@ impl fmt::Display for Health { impl Health { #[inline] pub fn is_liquidatable(&self) -> bool { - self.liquidation_health_factor - .map_or(false, |hf| hf < Decimal::one()) + self.liquidation_health_factor.map_or(false, |hf| hf < Decimal::one()) } #[inline] pub fn is_above_max_ltv(&self) -> bool { - self.max_ltv_health_factor - .map_or(false, |hf| hf < Decimal::one()) + self.max_ltv_health_factor.map_or(false, |hf| hf < Decimal::one()) } } diff --git a/packages/math/src/fraction.rs b/packages/math/src/fraction.rs index 62694adb1..668eef187 100644 --- a/packages/math/src/fraction.rs +++ b/packages/math/src/fraction.rs @@ -63,9 +63,7 @@ impl FractionMath for Uint128 { rhs: F, ) -> Result { let divisor = rhs.denominator().into(); - let res = self - .full_mul(rhs.numerator().into()) - .checked_div(divisor.into())?; + let res = self.full_mul(rhs.numerator().into()).checked_div(divisor.into())?; Ok(res.try_into()?) } @@ -75,9 +73,7 @@ impl FractionMath for Uint128 { ) -> Result { let floor_result = self.checked_mul_floor(rhs.clone())?; let divisor = rhs.denominator().into(); - let remainder = self - .full_mul(rhs.numerator().into()) - .checked_rem(divisor.into())?; + let remainder = self.full_mul(rhs.numerator().into()).checked_rem(divisor.into())?; if !remainder.is_zero() { Ok(Uint128::one().checked_add(floor_result)?) } else { @@ -92,9 +88,7 @@ impl FractionMath for Uint128 { rhs: F, ) -> Result { let divisor = rhs.numerator().into(); - let res = self - .full_mul(rhs.denominator().into()) - .checked_div(divisor.into())?; + let res = self.full_mul(rhs.denominator().into()).checked_div(divisor.into())?; Ok(res.try_into()?) } } diff --git a/packages/math/tests/test_div_floor.rs b/packages/math/tests/test_div_floor.rs index b24153617..5a63f8167 100644 --- a/packages/math/tests/test_div_floor.rs +++ b/packages/math/tests/test_div_floor.rs @@ -1,7 +1,8 @@ use cosmwasm_std::{ConversionOverflowError, Decimal, DivideByZeroError, Uint128}; - -use mars_math::CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}; -use mars_math::{FractionMath, Fractional}; +use mars_math::{ + CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}, + FractionMath, Fractional, +}; #[test] fn div_floor_raises_with_zero() { @@ -40,10 +41,7 @@ fn div_floor_does_not_round_on_even_divide() { fn div_floor_works_when_operation_temporarily_takes_above_max() { let fraction = Fractional(21u128, 8u128); let res = Uint128::MAX.checked_div_floor(fraction).unwrap(); // 129_631_377_874_643_224_176_523_659_974_006_937_697.1428 - assert_eq!( - Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_697), - res - ) + assert_eq!(Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_697), res) } #[test] @@ -55,9 +53,7 @@ fn div_floor_works_with_decimal() { #[test] fn div_floor_works_with_decimal_evenly() { - let res = Uint128::new(60) - .checked_div_floor(Decimal::from_atomics(6u128, 0).unwrap()) - .unwrap(); + let res = Uint128::new(60).checked_div_floor(Decimal::from_atomics(6u128, 0).unwrap()).unwrap(); assert_eq!(res, Uint128::new(10)); } diff --git a/packages/math/tests/test_mul_ceil.rs b/packages/math/tests/test_mul_ceil.rs index 032cc3e6e..ccd22af54 100644 --- a/packages/math/tests/test_mul_ceil.rs +++ b/packages/math/tests/test_mul_ceil.rs @@ -1,6 +1,8 @@ use cosmwasm_std::{ConversionOverflowError, Decimal, DivideByZeroError, Uint128}; -use mars_math::CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}; -use mars_math::{FractionMath, Fractional}; +use mars_math::{ + CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}, + FractionMath, Fractional, +}; #[test] fn mul_ceil_works_with_zero() { @@ -34,10 +36,7 @@ fn mul_ceil_does_not_round_on_even_divide() { fn mul_ceil_works_when_operation_temporarily_takes_above_max() { let fraction = Fractional(8u128, 21u128); let res = Uint128::MAX.checked_mul_ceil(fraction).unwrap(); // 129_631_377_874_643_224_176_523_659_974_006_937_697.1428 - assert_eq!( - Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_698), - res - ) + assert_eq!(Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_698), res) } #[test] diff --git a/packages/math/tests/test_mul_floor.rs b/packages/math/tests/test_mul_floor.rs index cf861866b..4b848fb3d 100644 --- a/packages/math/tests/test_mul_floor.rs +++ b/packages/math/tests/test_mul_floor.rs @@ -1,7 +1,8 @@ use cosmwasm_std::{ConversionOverflowError, Decimal, DivideByZeroError, Uint128}; - -use mars_math::CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}; -use mars_math::{FractionMath, Fractional}; +use mars_math::{ + CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}, + FractionMath, Fractional, +}; #[test] fn mul_floor_works_with_zero() { @@ -36,10 +37,7 @@ fn mul_floor_does_not_round_on_even_divide() { fn mul_floor_works_when_operation_temporarily_takes_above_max() { let fraction = Fractional(8u128, 21u128); let res = Uint128::MAX.checked_mul_floor(fraction).unwrap(); // 129_631_377_874_643_224_176_523_659_974_006_937_697.1428 - assert_eq!( - Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_697), - res - ) + assert_eq!(Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_697), res) } #[test] diff --git a/packages/outpost/src/address_provider.rs b/packages/outpost/src/address_provider.rs index 6c893b7d9..017273ee3 100644 --- a/packages/outpost/src/address_provider.rs +++ b/packages/outpost/src/address_provider.rs @@ -1,6 +1,4 @@ -use std::any::type_name; -use std::fmt; -use std::str::FromStr; +use std::{any::type_name, fmt, str::FromStr}; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::StdError; @@ -44,7 +42,7 @@ impl fmt::Display for MarsAddressType { MarsAddressType::RewardsCollector => "rewards_collector", MarsAddressType::SafetyFund => "safety_fund", }; - write!(f, "{}", s) + write!(f, "{s}") } } @@ -93,7 +91,9 @@ pub enum ExecuteMsg { address: String, }, /// Propose to transfer the contract's ownership to another account - TransferOwnership { new_owner: String }, + TransferOwnership { + new_owner: String, + }, } #[cw_serde] @@ -129,17 +129,17 @@ pub struct AddressResponseItem { pub mod helpers { use std::collections::HashMap; - use super::{AddressResponseItem, MarsAddressType, QueryMsg}; use cosmwasm_std::{Addr, Deps, StdResult}; + use super::{AddressResponseItem, MarsAddressType, QueryMsg}; + pub fn query_address( deps: Deps, address_provider_addr: &Addr, contract: MarsAddressType, ) -> StdResult { - let res: AddressResponseItem = deps - .querier - .query_wasm_smart(address_provider_addr, &QueryMsg::Address(contract))?; + let res: AddressResponseItem = + deps.querier.query_wasm_smart(address_provider_addr, &QueryMsg::Address(contract))?; deps.api.addr_validate(&res.address) } diff --git a/packages/outpost/src/error.rs b/packages/outpost/src/error.rs index 510f1ac8e..efbe23014 100644 --- a/packages/outpost/src/error.rs +++ b/packages/outpost/src/error.rs @@ -13,7 +13,10 @@ pub enum MarsError { InstantiateParamsUnavailable {}, #[error("Incorrect number of addresses, expected {expected:?}, got {actual:?}")] - AddressesQueryWrongNumber { expected: u32, actual: u32 }, + AddressesQueryWrongNumber { + expected: u32, + actual: u32, + }, #[error("Invalid param: {param_name} is {invalid_value}, but it should be {predicate}")] InvalidParam { @@ -23,14 +26,16 @@ pub enum MarsError { }, #[error("Failed to deserialize RPC query response for: {target_type}")] - Deserialize { target_type: String }, + Deserialize { + target_type: String, + }, } impl From for StdError { fn from(source: MarsError) -> Self { match source { MarsError::Std(e) => e, - e => StdError::generic_err(format!("{}", e)), + e => StdError::generic_err(format!("{e}")), } } } diff --git a/packages/outpost/src/incentives.rs b/packages/outpost/src/incentives.rs index f379e2aeb..62594bb91 100644 --- a/packages/outpost/src/incentives.rs +++ b/packages/outpost/src/incentives.rs @@ -87,9 +87,13 @@ pub enum QueryMsg { /// Query info about asset incentive for a given denom #[returns(AssetIncentiveResponse)] - AssetIncentive { denom: String }, + AssetIncentive { + denom: String, + }, /// Query user current unclaimed rewards #[returns(Uint128)] - UserUnclaimedRewards { user: String }, + UserUnclaimedRewards { + user: String, + }, } diff --git a/packages/outpost/src/math.rs b/packages/outpost/src/math.rs index 40098bac8..bba01eb7e 100644 --- a/packages/outpost/src/math.rs +++ b/packages/outpost/src/math.rs @@ -82,10 +82,12 @@ pub fn multiply_uint128_by_decimal_and_ceil(a: Uint128, b: Decimal) -> StdResult #[cfg(test)] mod tests { - use super::*; - use cosmwasm_std::{ConversionOverflowError, OverflowOperation}; use std::str::FromStr; + use cosmwasm_std::{ConversionOverflowError, OverflowOperation}; + + use super::*; + const DECIMAL_FRACTIONAL: Uint128 = Uint128::new(1_000_000_000_000_000_000u128); // 1*10**18 const DECIMAL_FRACTIONAL_SQUARED: Uint128 = Uint128::new(1_000_000_000_000_000_000_000_000_000_000_000_000u128); // (1*10**18)**2 = 1*10**36 @@ -178,10 +180,7 @@ mod tests { let a = Uint128::new(1_000_000_000_000_000_000); let b = Decimal::from_ratio(1u128, DECIMAL_FRACTIONAL); let c = divide_uint128_by_decimal(a, b).unwrap(); - assert_eq!( - c, - Uint128::new(1_000_000_000_000_000_000_000_000_000_000_000_000) - ); + assert_eq!(c, Uint128::new(1_000_000_000_000_000_000_000_000_000_000_000_000)); // Division is truncated let a = Uint128::new(100); @@ -234,10 +233,7 @@ mod tests { let a = Uint128::new(1_000_000_000_000_000_000); let b = Decimal::from_ratio(1u128, DECIMAL_FRACTIONAL); let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); - assert_eq!( - c, - Uint128::new(1_000_000_000_000_000_000_000_000_000_000_000_000) - ); + assert_eq!(c, Uint128::new(1_000_000_000_000_000_000_000_000_000_000_000_000)); // Division is rounded up let a = Uint128::new(100); diff --git a/packages/outpost/src/oracle.rs b/packages/outpost/src/oracle.rs index c969245e3..78eb74d4e 100644 --- a/packages/outpost/src/oracle.rs +++ b/packages/outpost/src/oracle.rs @@ -14,13 +14,20 @@ pub type InstantiateMsg = Config; #[cw_serde] pub enum ExecuteMsg { /// Update contract config - UpdateConfig { owner: String }, + UpdateConfig { + owner: String, + }, /// Specify the price source to be used for a coin /// /// NOTE: The input parameters for method are chain-specific. - SetPriceSource { denom: String, price_source: T }, + SetPriceSource { + denom: String, + price_source: T, + }, /// Remove price source for a coin - RemovePriceSource { denom: String }, + RemovePriceSource { + denom: String, + }, } #[cw_serde] @@ -33,7 +40,9 @@ pub enum QueryMsg { /// /// NOTE: The response type of this query is chain-specific. #[returns(PriceSourceResponse)] - PriceSource { denom: String }, + PriceSource { + denom: String, + }, /// Enumerate all coins' price sources. /// /// NOTE: The response type of this query is chain-specific. @@ -47,7 +56,9 @@ pub enum QueryMsg { /// NOTE: This query may be dependent on block time (e.g. if the price source is TWAP), so may not /// work properly with time travel queries on archive nodes. #[returns(PriceResponse)] - Price { denom: String }, + Price { + denom: String, + }, /// Enumerate all coins' prices. /// /// NOTE: This query may be dependent on block time (e.g. if the price source is TWAP), so may not @@ -72,9 +83,10 @@ pub struct PriceResponse { } pub mod helpers { - use super::{PriceResponse, QueryMsg}; use cosmwasm_std::{Decimal, QuerierWrapper, StdResult}; + use super::{PriceResponse, QueryMsg}; + pub fn query_price( querier: &QuerierWrapper, oracle: impl Into, diff --git a/packages/outpost/src/red_bank/interest_rate_model.rs b/packages/outpost/src/red_bank/interest_rate_model.rs index adfdd35da..ea160b9bf 100644 --- a/packages/outpost/src/red_bank/interest_rate_model.rs +++ b/packages/outpost/src/red_bank/interest_rate_model.rs @@ -1,9 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Decimal, StdError, StdResult}; -use crate::error::MarsError; -use crate::helpers::decimal_param_le_one; -use crate::math; +use crate::{error::MarsError, helpers::decimal_param_le_one, math}; #[cw_serde] #[derive(Eq, Default)] diff --git a/packages/outpost/src/red_bank/market.rs b/packages/outpost/src/red_bank/market.rs index 831ec02e7..d6e09de75 100644 --- a/packages/outpost/src/red_bank/market.rs +++ b/packages/outpost/src/red_bank/market.rs @@ -1,9 +1,11 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Decimal, StdResult, Uint128}; -use crate::error::MarsError; -use crate::helpers::{decimal_param_le_one, decimal_param_lt_one}; -use crate::red_bank::InterestRateModel; +use crate::{ + error::MarsError, + helpers::{decimal_param_le_one, decimal_param_lt_one}, + red_bank::InterestRateModel, +}; #[cw_serde] pub struct Market { @@ -93,9 +95,7 @@ impl Market { } pub fn update_interest_rates(&mut self, current_utilization_rate: Decimal) -> StdResult<()> { - self.borrow_rate = self - .interest_rate_model - .get_borrow_rate(current_utilization_rate)?; + self.borrow_rate = self.interest_rate_model.get_borrow_rate(current_utilization_rate)?; self.liquidity_rate = self.interest_rate_model.get_liquidity_rate( self.borrow_rate, diff --git a/packages/outpost/src/red_bank/msg.rs b/packages/outpost/src/red_bank/msg.rs index 61d4e0aec..a00a45efd 100644 --- a/packages/outpost/src/red_bank/msg.rs +++ b/packages/outpost/src/red_bank/msg.rs @@ -1,7 +1,8 @@ -use crate::red_bank::InterestRateModel; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Decimal, Uint128}; +use crate::red_bank::InterestRateModel; + #[cw_serde] pub struct InstantiateMsg { /// Market configuration @@ -12,7 +13,9 @@ pub struct InstantiateMsg { #[allow(clippy::large_enum_variant)] pub enum ExecuteMsg { /// Update contract config (only owner can call) - UpdateConfig { config: CreateOrUpdateConfig }, + UpdateConfig { + config: CreateOrUpdateConfig, + }, /// Initialize an asset on the money market (only owner can call) InitAsset { @@ -140,7 +143,9 @@ pub enum QueryMsg { /// Get asset market #[returns(crate::red_bank::Market)] - Market { denom: String }, + Market { + denom: String, + }, /// Enumerate markets with pagination #[returns(Vec)] @@ -151,7 +156,10 @@ pub enum QueryMsg { /// Get uncollateralized limit for given user and asset #[returns(crate::red_bank::UncollateralizedLoanLimitResponse)] - UncollateralizedLoanLimit { user: String, denom: String }, + UncollateralizedLoanLimit { + user: String, + denom: String, + }, /// Get all uncollateralized limits for a given user #[returns(Vec)] @@ -163,7 +171,10 @@ pub enum QueryMsg { /// Get user debt position for a specific asset #[returns(crate::red_bank::UserDebtResponse)] - UserDebt { user: String, denom: String }, + UserDebt { + user: String, + denom: String, + }, /// Get all debt positions for a user #[returns(Vec)] @@ -175,7 +186,10 @@ pub enum QueryMsg { /// Get user collateral position for a specific asset #[returns(crate::red_bank::UserCollateralResponse)] - UserCollateral { user: String, denom: String }, + UserCollateral { + user: String, + denom: String, + }, /// Get all collateral positions for a user #[returns(Vec)] @@ -187,17 +201,25 @@ pub enum QueryMsg { /// Get user position #[returns(crate::red_bank::UserPositionResponse)] - UserPosition { user: String }, + UserPosition { + user: String, + }, /// Get liquidity scaled amount for a given underlying asset amount. /// (i.e: how much scaled collateral is added if the given amount is deposited) #[returns(Uint128)] - ScaledLiquidityAmount { denom: String, amount: Uint128 }, + ScaledLiquidityAmount { + denom: String, + amount: Uint128, + }, /// Get equivalent scaled debt for a given underlying asset amount. /// (i.e: how much scaled debt is added if the given amount is borrowed) #[returns(Uint128)] - ScaledDebtAmount { denom: String, amount: Uint128 }, + ScaledDebtAmount { + denom: String, + amount: Uint128, + }, /// Get underlying asset amount for a given asset and scaled amount. /// (i.e. How much underlying asset will be released if withdrawing by burning a given scaled diff --git a/packages/outpost/src/red_bank/types.rs b/packages/outpost/src/red_bank/types.rs index 39876d0d8..675685a43 100644 --- a/packages/outpost/src/red_bank/types.rs +++ b/packages/outpost/src/red_bank/types.rs @@ -1,8 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Decimal, Uint128}; -use crate::error::MarsError; -use crate::helpers::decimal_param_le_one; +use crate::{error::MarsError, helpers::decimal_param_le_one}; /// Global configuration #[cw_serde] diff --git a/packages/outpost/src/rewards_collector.rs b/packages/outpost/src/rewards_collector.rs index 48954414b..1766fa0a6 100644 --- a/packages/outpost/src/rewards_collector.rs +++ b/packages/outpost/src/rewards_collector.rs @@ -1,8 +1,10 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Api, Decimal, StdResult, Uint128}; -use crate::error::MarsError; -use crate::helpers::{decimal_param_le_one, integer_param_gt_zero}; +use crate::{ + error::MarsError, + helpers::{decimal_param_le_one, integer_param_gt_zero}, +}; const MAX_SLIPPAGE_TOLERANCE_PERCENTAGE: u64 = 50; @@ -115,7 +117,9 @@ pub type InstantiateMsg = Config; #[cw_serde] pub enum ExecuteMsg { /// Update contract config - UpdateConfig { new_cfg: CreateOrUpdateConfig }, + UpdateConfig { + new_cfg: CreateOrUpdateConfig, + }, /// Configure the route for swapping an asset /// @@ -158,7 +162,10 @@ pub enum QueryMsg { /// /// NOTE: The response type of this query is chain-specific. #[returns(RouteResponse)] - Route { denom_in: String, denom_out: String }, + Route { + denom_in: String, + denom_out: String, + }, /// Enumerate all swap routes. /// /// NOTE: The response type of this query is chain-specific. diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs index 4be3d6b84..2a14d6c81 100644 --- a/packages/rover/src/adapters/oracle.rs +++ b/packages/rover/src/adapters/oracle.rs @@ -1,6 +1,5 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, Coin, QuerierWrapper, StdResult, Uint128}; - use mars_math::FractionMath; use mars_outpost::oracle::{PriceResponse, QueryMsg}; diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index 36632090b..06a10fae5 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -3,9 +3,7 @@ use cosmwasm_std::{ to_binary, Addr, Api, Coin, CosmosMsg, QuerierWrapper, QueryRequest, StdResult, Uint128, WasmMsg, WasmQuery, }; - -use mars_outpost::red_bank; -use mars_outpost::red_bank::Market; +use mars_outpost::{red_bank, red_bank::Market}; #[cw_serde] pub struct RedBankBase(T); @@ -53,7 +51,9 @@ impl RedBank { pub fn repay_msg(&self, coin: &Coin) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address().to_string(), - msg: to_binary(&red_bank::ExecuteMsg::Repay { on_behalf_of: None })?, + msg: to_binary(&red_bank::ExecuteMsg::Repay { + on_behalf_of: None, + })?, funds: vec![coin.clone()], })) } diff --git a/packages/rover/src/adapters/swap/mod.rs b/packages/rover/src/adapters/swap/mod.rs index 2fcab0307..6afc2b84f 100644 --- a/packages/rover/src/adapters/swap/mod.rs +++ b/packages/rover/src/adapters/swap/mod.rs @@ -1,5 +1,4 @@ mod msgs; mod swapper; -pub use self::msgs::*; -pub use self::swapper::*; +pub use self::{msgs::*, swapper::*}; diff --git a/packages/rover/src/adapters/swap/msgs.rs b/packages/rover/src/adapters/swap/msgs.rs index 2134db856..bd270548a 100644 --- a/packages/rover/src/adapters/swap/msgs.rs +++ b/packages/rover/src/adapters/swap/msgs.rs @@ -43,7 +43,10 @@ pub enum QueryMsg { Owner {}, /// Get route for swapping an input denom into an output denom #[returns(RouteResponse)] - Route { denom_in: String, denom_out: String }, + Route { + denom_in: String, + denom_out: String, + }, /// Enumerate all swapper routes #[returns(RoutesResponse)] Routes { @@ -53,7 +56,10 @@ pub enum QueryMsg { /// Return current spot price swapping In for Out /// Warning: Do not use this as an oracle price feed. Use Mars-Oracle for pricing. #[returns(EstimateExactInSwapResponse)] - EstimateExactInSwap { coin_in: Coin, denom_out: String }, + EstimateExactInSwap { + coin_in: Coin, + denom_out: String, + }, } #[cw_serde] diff --git a/packages/rover/src/adapters/vault/amount.rs b/packages/rover/src/adapters/vault/amount.rs index 498f40e98..b52a80293 100644 --- a/packages/rover/src/adapters/vault/amount.rs +++ b/packages/rover/src/adapters/vault/amount.rs @@ -1,10 +1,10 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Uint128; -use crate::adapters::vault::{ - UnlockingChange, UpdateType, VaultPositionUpdate, VaultUnlockingPosition, +use crate::{ + adapters::vault::{UnlockingChange, UpdateType, VaultPositionUpdate, VaultUnlockingPosition}, + error::{ContractError, ContractResult}, }; -use crate::error::{ContractError, ContractResult}; #[cw_serde] pub enum VaultPositionAmount { @@ -40,12 +40,9 @@ impl VaultPositionAmount { pub fn get_unlocking_position(&self, id: u64) -> Option { match self { - VaultPositionAmount::Locking(amount) => amount - .unlocking - .positions() - .iter() - .find(|p| p.id == id) - .cloned(), + VaultPositionAmount::Locking(amount) => { + amount.unlocking.positions().iter().find(|p| p.id == id).cloned() + } _ => None, } } @@ -66,9 +63,10 @@ impl VaultPositionAmount { }, VaultPositionUpdate::Unlocking(u) => match u { UnlockingChange::Add(p) => amount.unlocking.add(p), - UnlockingChange::Decrement { id, amount: a } => { - amount.unlocking.decrement(id, a) - } + UnlockingChange::Decrement { + id, + amount: a, + } => amount.unlocking.decrement(id, a), }, _ => Err(ContractError::MismatchedVaultType {}), }, diff --git a/packages/rover/src/adapters/vault/base.rs b/packages/rover/src/adapters/vault/base.rs index 4744227c9..7c4c6fe61 100644 --- a/packages/rover/src/adapters/vault/base.rs +++ b/packages/rover/src/adapters/vault/base.rs @@ -5,22 +5,23 @@ use cosmwasm_std::{ to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, QuerierWrapper, QueryRequest, StdError, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, }; -use cosmwasm_vault_standard::extensions::force_unlock::ForceUnlockExecuteMsg::{ - ForceRedeem, ForceWithdrawUnlocking, +use cosmwasm_vault_standard::{ + extensions::{ + force_unlock::ForceUnlockExecuteMsg::{ForceRedeem, ForceWithdrawUnlocking}, + lockup::{ + LockupExecuteMsg::{Unlock, WithdrawUnlocked}, + LockupQueryMsg, + LockupQueryMsg::LockupDuration, + UnlockingPosition, + }, + }, + msg::{ExtensionExecuteMsg, ExtensionQueryMsg, VaultStandardExecuteMsg, VaultStandardQueryMsg}, + VaultInfoResponse, }; -use cosmwasm_vault_standard::extensions::lockup::LockupExecuteMsg::{Unlock, WithdrawUnlocked}; -use cosmwasm_vault_standard::extensions::lockup::LockupQueryMsg::LockupDuration; -use cosmwasm_vault_standard::extensions::lockup::{LockupQueryMsg, UnlockingPosition}; -use cosmwasm_vault_standard::msg::{ - ExtensionExecuteMsg, ExtensionQueryMsg, VaultStandardExecuteMsg, VaultStandardQueryMsg, -}; -use cosmwasm_vault_standard::VaultInfoResponse; use cw_utils::Duration; - use mars_math::FractionMath; -use crate::adapters::oracle::Oracle; -use crate::traits::Stringify; +use crate::{adapters::oracle::Oracle, traits::Stringify}; pub const VAULT_REQUEST_REPLY_ID: u64 = 10_001; @@ -35,7 +36,9 @@ pub struct VaultBase { impl VaultBase { pub fn new(address: T) -> Self { - Self { address } + Self { + address, + } } } @@ -66,10 +69,7 @@ impl From for VaultUnchecked { impl Stringify for Vec { fn to_string(&self) -> String { - self.iter() - .map(|v| v.address.clone()) - .collect::>() - .join(", ") + self.iter().map(|v| v.address.clone()).collect::>().join(", ") } } @@ -114,12 +114,12 @@ impl Vault { denom: vault_info.vault_token, amount, }], - msg: to_binary(&ExecuteMsg::VaultExtension( - ExtensionExecuteMsg::ForceUnlock(ForceRedeem { + msg: to_binary(&ExecuteMsg::VaultExtension(ExtensionExecuteMsg::ForceUnlock( + ForceRedeem { recipient: None, amount, - }), - ))?, + }, + )))?, }); Ok(withdraw_msg) } @@ -132,13 +132,13 @@ impl Vault { let withdraw_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![], - msg: to_binary(&ExecuteMsg::VaultExtension( - ExtensionExecuteMsg::ForceUnlock(ForceWithdrawUnlocking { + msg: to_binary(&ExecuteMsg::VaultExtension(ExtensionExecuteMsg::ForceUnlock( + ForceWithdrawUnlocking { lockup_id, amount, recipient: None, - }), - ))?, + }, + )))?, }); Ok(withdraw_msg) } @@ -148,11 +148,9 @@ impl Vault { CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address.to_string(), funds: vec![coin.clone()], - msg: to_binary(&ExecuteMsg::VaultExtension(ExtensionExecuteMsg::Lockup( - Unlock { - amount: coin.amount, - }, - )))?, + msg: to_binary(&ExecuteMsg::VaultExtension(ExtensionExecuteMsg::Lockup(Unlock { + amount: coin.amount, + })))?, }), VAULT_REQUEST_REPLY_ID, ); @@ -197,7 +195,9 @@ impl Vault { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), msg: to_binary(&QueryMsg::VaultExtension(ExtensionQueryMsg::Lockup( - LockupQueryMsg::UnlockingPosition { lockup_id }, + LockupQueryMsg::UnlockingPosition { + lockup_id, + }, )))?, })) } @@ -218,7 +218,9 @@ impl Vault { ) -> StdResult { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: self.address.to_string(), - msg: to_binary(&QueryMsg::PreviewRedeem { amount })?, + msg: to_binary(&QueryMsg::PreviewRedeem { + amount, + })?, })) } diff --git a/packages/rover/src/adapters/vault/config.rs b/packages/rover/src/adapters/vault/config.rs index ad0d1a100..4cc7fed5b 100644 --- a/packages/rover/src/adapters/vault/config.rs +++ b/packages/rover/src/adapters/vault/config.rs @@ -1,8 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Decimal}; -use crate::error::ContractError; -use crate::error::ContractError::InvalidConfig; +use crate::error::{ContractError, ContractError::InvalidConfig}; #[cw_serde] pub struct VaultConfig { diff --git a/packages/rover/src/adapters/vault/mod.rs b/packages/rover/src/adapters/vault/mod.rs index c9aaf16db..0fea29ca6 100644 --- a/packages/rover/src/adapters/vault/mod.rs +++ b/packages/rover/src/adapters/vault/mod.rs @@ -4,8 +4,4 @@ mod config; mod position; mod update; -pub use self::amount::*; -pub use self::base::*; -pub use self::config::*; -pub use self::position::*; -pub use self::update::*; +pub use self::{amount::*, base::*, config::*, position::*, update::*}; diff --git a/packages/rover/src/adapters/vault/position.rs b/packages/rover/src/adapters/vault/position.rs index 49ca2a79d..90679b945 100644 --- a/packages/rover/src/adapters/vault/position.rs +++ b/packages/rover/src/adapters/vault/position.rs @@ -1,8 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Coin; -use crate::adapters::vault::Vault; -use crate::adapters::vault::VaultPositionAmount; +use crate::adapters::vault::{Vault, VaultPositionAmount}; #[cw_serde] pub struct VaultUnlockingPosition { diff --git a/packages/rover/src/adapters/vault/update.rs b/packages/rover/src/adapters/vault/update.rs index 51e8ec120..76d3f32c8 100644 --- a/packages/rover/src/adapters/vault/update.rs +++ b/packages/rover/src/adapters/vault/update.rs @@ -15,7 +15,10 @@ pub enum UpdateType { #[cw_serde] pub enum UnlockingChange { Add(VaultUnlockingPosition), - Decrement { id: u64, amount: Uint128 }, + Decrement { + id: u64, + amount: Uint128, + }, } #[cw_serde] @@ -28,9 +31,9 @@ pub enum VaultPositionUpdate { impl VaultPositionUpdate { pub fn default_amount(&self) -> VaultPositionAmount { match self { - VaultPositionUpdate::Unlocked { .. } => { - VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::zero())) - } + VaultPositionUpdate::Unlocked { + .. + } => VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::zero())), _ => VaultPositionAmount::Locking(LockingVaultAmount { locked: VaultAmount::new(Uint128::zero()), unlocking: UnlockingPositions::new(vec![]), diff --git a/packages/rover/src/adapters/zapper.rs b/packages/rover/src/adapters/zapper.rs index c4e49b4cc..7a3623dd6 100644 --- a/packages/rover/src/adapters/zapper.rs +++ b/packages/rover/src/adapters/zapper.rs @@ -82,7 +82,9 @@ impl Zapper { pub fn withdraw_liquidity_msg(&self, lp_token: &Coin) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address().to_string(), - msg: to_binary(&ExecuteMsg::WithdrawLiquidity { recipient: None })?, + msg: to_binary(&ExecuteMsg::WithdrawLiquidity { + recipient: None, + })?, funds: vec![lp_token.clone()], })) } diff --git a/packages/rover/src/coins.rs b/packages/rover/src/coins.rs index 4305e8bb2..0842ef7ff 100644 --- a/packages/rover/src/coins.rs +++ b/packages/rover/src/coins.rs @@ -1,14 +1,19 @@ -use std::any::type_name; -use std::collections::{BTreeMap, HashSet}; -use std::fmt; -use std::str::FromStr; +use std::{ + any::type_name, + collections::{BTreeMap, HashSet}, + fmt, + str::FromStr, +}; -use crate::msg::execute::ActionCoin; -use crate::traits::{Denoms, Stringify}; use cosmwasm_std::{Coin, StdError, StdResult, Uint128}; use schemars::JsonSchema; use serde::{de, Serialize}; +use crate::{ + msg::execute::ActionCoin, + traits::{Denoms, Stringify}, +}; + /// A collection of coins, similar to Cosmos SDK's `sdk.Coins` struct. /// /// Differently from `sdk.Coins`, which is a vector of `sdk.Coin`, here we implement Coins as a BTreeMap @@ -103,23 +108,18 @@ impl<'de> de::Deserialize<'de> for Coins { while let Some((denom, amount_str)) = access.next_entry::()? { if seen_denoms.contains(&denom) { return Err(de::Error::custom(format!( - "failed to parse into Coins! duplicate denom: {}", - denom + "failed to parse into Coins! duplicate denom: {denom}" ))); } let amount = Uint128::from_str(&amount_str).map_err(|_| { de::Error::custom(format!( - "failed to parse into Coins! invalid amount: {}", - amount_str + "failed to parse into Coins! invalid amount: {amount_str}" )) })?; if amount.is_zero() { - return Err(de::Error::custom(format!( - "amount for denom {} is zero", - denom - ))); + return Err(de::Error::custom(format!("amount for denom {denom} is zero"))); } seen_denoms.insert(denom.clone()); @@ -188,21 +188,17 @@ impl FromStr for Coins { if c.is_alphabetic() { let amount = Uint128::from_str(&s[..i])?; let denom = String::from(&s[i..]); - return Ok(Coin { amount, denom }); + return Ok(Coin { + amount, + denom, + }); } } - Err(StdError::parse_err( - type_name::(), - format!("invalid coin string: {s}"), - )) + Err(StdError::parse_err(type_name::(), format!("invalid coin string: {s}"))) }; - s.split(',') - .into_iter() - .map(parse_coin_str) - .collect::>>()? - .try_into() + s.split(',').map(parse_coin_str).collect::>>()?.try_into() } } @@ -236,7 +232,10 @@ impl Coins { pub fn into_vec(self) -> Vec { self.0 .into_iter() - .map(|(denom, amount)| Coin { denom, amount }) + .map(|(denom, amount)| Coin { + denom, + amount, + }) .collect() } @@ -258,10 +257,7 @@ impl Coins { /// NOTE: the syntax can be simpler if Uint128 has an inplace add method... pub fn add(&mut self, coin: &Coin) -> StdResult<()> { - let amount = self - .0 - .entry(coin.denom.clone()) - .or_insert_with(Uint128::zero); + let amount = self.0.entry(coin.denom.clone()).or_insert_with(Uint128::zero); *amount = amount.checked_add(coin.amount)?; Ok(()) } @@ -276,20 +272,14 @@ impl Coins { } Ok(()) } else { - Err(StdError::generic_err(format!( - "not found in coin list: {}", - to_deduct.denom - ))) + Err(StdError::generic_err(format!("not found in coin list: {}", to_deduct.denom))) } } } impl Stringify for &[Coin] { fn to_string(&self) -> String { - self.iter() - .map(|coin| coin.clone().denom) - .collect::>() - .join(", ") + self.iter().map(|coin| coin.clone().denom).collect::>().join(", ") } } diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 4588826e9..f185a984b 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -19,7 +19,10 @@ pub enum ContractError { }, #[error("Vault deposit would result in exceeding limit. With deposit: {new_value:?}, Maximum: {maximum:?}")] - AboveVaultDepositCap { new_value: String, maximum: String }, + AboveVaultDepositCap { + new_value: String, + maximum: String, + }, #[error("{0}")] OwnerError(#[from] OwnerError), @@ -60,13 +63,21 @@ pub enum ContractError { #[error( "Actions did not result in improved health factor: before: {prev_hf:?}, after: {new_hf:?}" )] - HealthNotImproved { prev_hf: String, new_hf: String }, + HealthNotImproved { + prev_hf: String, + new_hf: String, + }, #[error("{reason:?}")] - InvalidConfig { reason: String }, + InvalidConfig { + reason: String, + }, #[error("Paying down {debt_coin:?} for {request_coin:?} does not result in a profit for the liquidator")] - LiquidationNotProfitable { debt_coin: Coin, request_coin: Coin }, + LiquidationNotProfitable { + debt_coin: Coin, + request_coin: Coin, + }, #[error("Issued incorrect action for vault type")] MismatchedVaultType, @@ -89,7 +100,10 @@ pub enum ContractError { }, #[error("{user:?} is not the owner of {account_id:?}")] - NotTokenOwner { user: String, account_id: String }, + NotTokenOwner { + user: String, + account_id: String, + }, #[error("{0} is not whitelisted")] NotWhitelisted(String), @@ -113,7 +127,10 @@ pub enum ContractError { Std(#[from] StdError), #[error("{user:?} is not authorized to {action:?}")] - Unauthorized { user: String, action: String }, + Unauthorized { + user: String, + action: String, + }, #[error("There is more time left on the lock period")] UnlockNotReady, diff --git a/packages/rover/src/extensions/reply.rs b/packages/rover/src/extensions/reply.rs index 2f52a60d3..44cb534e1 100644 --- a/packages/rover/src/extensions/reply.rs +++ b/packages/rover/src/extensions/reply.rs @@ -29,7 +29,7 @@ impl AttrParse for Reply { .events .iter() .find(|event| { - event.ty == format!("wasm-{}", UNLOCKING_POSITION_CREATED_EVENT_TYPE) + event.ty == format!("wasm-{UNLOCKING_POSITION_CREATED_EVENT_TYPE}") }) .ok_or_else(|| StdError::generic_err("No unlock event"))?; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 6d347893c..742dddd9e 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,10 +1,11 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; - use mars_owner::OwnerUpdate; -use crate::adapters::vault::{Vault, VaultPositionType, VaultUnchecked}; -use crate::msg::instantiate::ConfigUpdates; +use crate::{ + adapters::vault::{Vault, VaultPositionType, VaultUnchecked}, + msg::instantiate::ConfigUpdates, +}; #[cw_serde] pub enum ExecuteMsg { @@ -23,7 +24,9 @@ pub enum ExecuteMsg { // Privileged messages //-------------------------------------------------------------------------------------------------- /// Update contract config constants - UpdateConfig { new_config: ConfigUpdates }, + UpdateConfig { + new_config: ConfigUpdates, + }, /// Manages owner role state UpdateOwner(OwnerUpdate), /// Internal actions only callable by the contract itself @@ -89,7 +92,10 @@ pub enum Action { amount: Uint128, }, /// Withdraws the assets for unlocking position id from vault. Required time must have elapsed. - ExitVaultUnlocked { id: u64, vault: VaultUnchecked }, + ExitVaultUnlocked { + id: u64, + vault: VaultUnchecked, + }, /// Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt /// denom/amount of what the liquidator wants to payoff and 2) the request coin denom which the /// liquidatee should have a balance of. The amount returned to liquidator will be the request coin @@ -133,7 +139,9 @@ pub enum Action { }, /// Send LP token and withdraw corresponding reserve assets from pool. /// If `lp_token.amount: AccountBalance`, the account balance of `lp_token.denom` will be used. - WithdrawLiquidity { lp_token: ActionCoin }, + WithdrawLiquidity { + lp_token: ActionCoin, + }, /// Refunds all coin balances back to user wallet RefundAllCoinBalances {}, } @@ -150,7 +158,10 @@ pub enum CallbackMsg { }, /// Borrow specified amount of coin from Red Bank; /// Increase the token's coin amount and debt shares; - Borrow { account_id: String, coin: Coin }, + Borrow { + account_id: String, + coin: Coin, + }, /// Repay coin of specified amount back to Red Bank; /// Decrement the token's coin amount and debt shares; /// If `coin.amount: AccountBalance` is passed, the repaid amount will be the minimum @@ -161,7 +172,9 @@ pub enum CallbackMsg { }, /// Calculate the account's max loan-to-value health factor. If above 1, /// emits a `position_changed` event. If 1 or below, raises an error. - AssertBelowMaxLTV { account_id: String }, + AssertBelowMaxLTV { + account_id: String, + }, /// Adds coin to a vault strategy EnterVault { account_id: String, @@ -237,9 +250,13 @@ pub enum CallbackMsg { lp_token: ActionCoin, }, /// Checks to ensure only one vault position is taken per credit account - AssertOneVaultPositionOnly { account_id: String }, + AssertOneVaultPositionOnly { + account_id: String, + }, /// Refunds all coin balances back to user wallet - RefundAllCoinBalances { account_id: String }, + RefundAllCoinBalances { + account_id: String, + }, } impl CallbackMsg { diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index b8342c245..4b53066cf 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -1,12 +1,16 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Decimal, Uint128}; -use crate::adapters::oracle::OracleUnchecked; -use crate::adapters::red_bank::RedBankUnchecked; -use crate::adapters::swap::SwapperUnchecked; -use crate::adapters::vault::{VaultConfig, VaultUnchecked}; -use crate::adapters::zapper::ZapperUnchecked; -use crate::traits::Stringify; +use crate::{ + adapters::{ + oracle::OracleUnchecked, + red_bank::RedBankUnchecked, + swap::SwapperUnchecked, + vault::{VaultConfig, VaultUnchecked}, + zapper::ZapperUnchecked, + }, + traits::Stringify, +}; #[cw_serde] pub struct InstantiateMsg { diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index ebbee4c10..a689f5948 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -1,8 +1,10 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Decimal, Uint128}; -use crate::adapters::vault::{Vault, VaultConfig, VaultPosition, VaultUnchecked}; -use crate::traits::Coins; +use crate::{ + adapters::vault::{Vault, VaultConfig, VaultPosition, VaultUnchecked}, + traits::Coins, +}; #[cw_serde] #[derive(QueryResponses)] @@ -24,10 +26,14 @@ pub enum QueryMsg { }, /// All positions represented by token with value #[returns(Positions)] - Positions { account_id: String }, + Positions { + account_id: String, + }, /// The health of the account represented by token #[returns(mars_health::HealthResponse)] - Health { account_id: String }, + Health { + account_id: String, + }, /// Enumerate coin balances for all token positions; start_after accepts (account_id, denom) #[returns(Vec)] AllCoinBalances { @@ -57,7 +63,9 @@ pub enum QueryMsg { }, /// Get total vault coin balance in Rover for vault #[returns(Uint128)] - TotalVaultCoinBalance { vault: VaultUnchecked }, + TotalVaultCoinBalance { + vault: VaultUnchecked, + }, /// Enumerate all total vault coin balances; start_after accepts vault addr #[returns(Vec)] AllTotalVaultCoinBalances { @@ -72,7 +80,9 @@ pub enum QueryMsg { }, /// Estimate coins withdrawn if exchanged for LP tokens #[returns(Vec)] - EstimateWithdrawLiquidity { lp_token: Coin }, + EstimateWithdrawLiquidity { + lp_token: Coin, + }, } #[cw_serde] diff --git a/packages/rover/src/msg/zapper.rs b/packages/rover/src/msg/zapper.rs index dbaadcb42..72fb9ca03 100644 --- a/packages/rover/src/msg/zapper.rs +++ b/packages/rover/src/msg/zapper.rs @@ -1,9 +1,10 @@ // TODO: should be removed when liquidity-helper is finalized and published to crates.io -use crate::adapters::oracle::OracleUnchecked; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Uint128}; +use crate::adapters::oracle::OracleUnchecked; + #[cw_serde] pub struct LpConfig { pub lp_token_denom: String, @@ -37,5 +38,7 @@ pub enum QueryMsg { coins_in: Vec, }, #[returns(Vec)] - EstimateWithdrawLiquidity { coin_in: Coin }, + EstimateWithdrawLiquidity { + coin_in: Coin, + }, } diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..c218077f7 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,4 @@ +group_imports = "StdExternalCrate" # nightly feature +imports_granularity = "Crate" # nightly feature +max_width = 100 +use_small_heuristics = "off" From afbd223deebf6ebea64c2671b46d58e254a842d0 Mon Sep 17 00:00:00 2001 From: elfedylabs <120417955+elfedylabs@users.noreply.github.com> Date: Wed, 11 Jan 2023 08:12:15 -0300 Subject: [PATCH 120/218] Add relevant event attributes (#92) Signed-off-by: Federico Rodriguez Co-authored-by: Gabe Rodriguez --- contracts/credit-manager/src/borrow.rs | 3 +- contracts/credit-manager/src/deposit.rs | 2 +- contracts/credit-manager/src/execute.rs | 3 +- .../credit-manager/src/liquidate_coin.rs | 7 ++-- contracts/credit-manager/src/refund.rs | 3 +- contracts/credit-manager/src/repay.rs | 30 +++++----------- contracts/credit-manager/src/swap.rs | 5 ++- .../src/update_coin_balances.rs | 34 ++++++++----------- contracts/credit-manager/src/vault/enter.rs | 7 +++- contracts/credit-manager/src/vault/exit.rs | 5 ++- .../credit-manager/src/vault/exit_unlocked.rs | 5 ++- .../src/vault/liquidate_vault.rs | 21 +++++------- .../src/vault/request_unlock.rs | 10 ++++-- contracts/credit-manager/src/withdraw.rs | 3 +- contracts/credit-manager/src/zap.rs | 12 +++++-- packages/rover/src/coins.rs | 2 +- scripts/deploy/addresses/osmo-test-4.json | 11 +++--- 17 files changed, 85 insertions(+), 78 deletions(-) diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index 9e715ea2c..486d43c9a 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -51,6 +51,7 @@ pub fn borrow(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> Contract Ok(Response::new() .add_message(red_bank.borrow_msg(&coin)?) .add_attribute("action", "rover/credit-manager/borrow") + .add_attribute("account_id", account_id) .add_attribute("debt_shares_added", debt_shares_to_add) - .add_attribute("coins_borrowed", coin.amount)) + .add_attribute("coin_borrowed", coin.to_string())) } diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index 3220509eb..47abb0db5 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -27,7 +27,7 @@ pub fn deposit( Ok(response .add_attribute("action", "rover/credit-manager/callback/deposit") - .add_attribute("deposit_received", coin.to_string())) + .add_attribute("coin_deposited", coin.to_string())) } /// Assert that fund of exactly the same type and amount was sent along with a message diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 288abd28c..a09e2d376 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -187,7 +187,8 @@ pub fn dispatch_actions( Ok(response .add_messages(callback_msgs) - .add_attribute("action", "rover/execute/update_credit_account")) + .add_attribute("action", "rover/execute/update_credit_account") + .add_attribute("account_id", account_id.to_string())) } pub fn execute_callback( diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index 19288047b..c39157aec 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -48,11 +48,10 @@ pub fn liquidate_coin( Ok(Response::new() .add_message(repay_msg) .add_attribute("action", "rover/credit-manager/liquidate_coin") + .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) - .add_attribute("debt_repaid_denom", debt.denom) - .add_attribute("debt_repaid_amount", debt.amount) - .add_attribute("request_coin_denom", request.denom) - .add_attribute("request_coin_amount", request.amount)) + .add_attribute("coin_debt_repaid", debt.to_string()) + .add_attribute("coin_liquidated", request.to_string())) } /// Calculates precise debt & request coin amounts to liquidate diff --git a/contracts/credit-manager/src/refund.rs b/contracts/credit-manager/src/refund.rs index b3a6280bf..241d21405 100644 --- a/contracts/credit-manager/src/refund.rs +++ b/contracts/credit-manager/src/refund.rs @@ -25,5 +25,6 @@ pub fn refund_coin_balances(deps: DepsMut, env: Env, account_id: &str) -> Contra .collect::>>()?; Ok(Response::new() .add_messages(withdraw_msgs) - .add_attribute("action", "rover/credit-manager/callback/refund_coin_balances")) + .add_attribute("action", "rover/credit-manager/callback/refund_coin_balances") + .add_attribute("account_id", account_id.to_string())) } diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index 6fd902a6d..540c6ea20 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -21,14 +21,11 @@ pub fn repay( let (debt_amount, debt_shares) = current_debt_for_denom(deps.as_ref(), &env, account_id, &coin.denom)?; let amount_to_repay = min(debt_amount, coin.amount.value().unwrap_or(Uint128::MAX)); - let shares_to_repay = debt_amount_to_shares( - deps.as_ref(), - &env, - &Coin { - denom: coin.denom.to_string(), - amount: amount_to_repay, - }, - )?; + let coin_to_repay = Coin { + denom: coin.denom.to_string(), + amount: amount_to_repay, + }; + let shares_to_repay = debt_amount_to_shares(deps.as_ref(), &env, &coin_to_repay)?; // Decrement token's debt position if amount_to_repay == debt_amount { @@ -49,26 +46,17 @@ pub fn repay( &total_debt_shares.checked_sub(shares_to_repay)?, )?; - decrement_coin_balance( - deps.storage, - account_id, - &Coin { - denom: coin.denom.to_string(), - amount: amount_to_repay, - }, - )?; + decrement_coin_balance(deps.storage, account_id, &coin_to_repay)?; let red_bank = RED_BANK.load(deps.storage)?; - let red_bank_repay_msg = red_bank.repay_msg(&Coin { - denom: coin.denom.to_string(), - amount: amount_to_repay, - })?; + let red_bank_repay_msg = red_bank.repay_msg(&coin_to_repay)?; Ok(Response::new() .add_message(red_bank_repay_msg) .add_attribute("action", "rover/credit-manager/repay") + .add_attribute("account_id", account_id) .add_attribute("debt_shares_repaid", shares_to_repay) - .add_attribute("coins_repaid", amount_to_repay)) + .add_attribute("coin_repaid", coin_to_repay.to_string())) } fn debt_amount_to_shares(deps: Deps, env: &Env, coin: &Coin) -> ContractResult { diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs index 91d9c6c1d..ab71544b6 100644 --- a/contracts/credit-manager/src/swap.rs +++ b/contracts/credit-manager/src/swap.rs @@ -44,5 +44,8 @@ pub fn swap_exact_in( Ok(Response::new() .add_message(swapper.swap_exact_in_msg(&coin_in_to_trade, denom_out, slippage)?) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit-manager/swapper")) + .add_attribute("action", "rover/credit-manager/swapper") + .add_attribute("account_id", account_id) + .add_attribute("coin_in", coin_in_to_trade.to_string()) + .add_attribute("denom_out", denom_out)) } diff --git a/contracts/credit-manager/src/update_coin_balances.rs b/contracts/credit-manager/src/update_coin_balances.rs index 0a6ce8899..8fc72a67f 100644 --- a/contracts/credit-manager/src/update_coin_balances.rs +++ b/contracts/credit-manager/src/update_coin_balances.rs @@ -26,31 +26,25 @@ pub fn update_coin_balance( let curr = query_balance(&deps.querier, &env.contract.address, &prev.denom)?; if prev.amount > curr.amount { let amount_to_reduce = prev.amount.checked_sub(curr.amount)?; - decrement_coin_balance( - deps.storage, - account_id, - &Coin { - denom: curr.denom.clone(), - amount: amount_to_reduce, - }, - )?; + let coin_to_reduce = Coin { + denom: curr.denom, + amount: amount_to_reduce, + }; + decrement_coin_balance(deps.storage, account_id, &coin_to_reduce)?; Ok(Response::new() .add_attribute("action", "rover/credit-manager/update_coin_balance") - .add_attribute("denom", curr.denom) - .add_attribute("decremented", amount_to_reduce)) + .add_attribute("account_id", account_id) + .add_attribute("coin_decremented", coin_to_reduce.to_string())) } else { let amount_to_increment = curr.amount.checked_sub(prev.amount)?; - increment_coin_balance( - deps.storage, - account_id, - &Coin { - denom: curr.denom.clone(), - amount: amount_to_increment, - }, - )?; + let coin_to_increment = Coin { + denom: curr.denom, + amount: amount_to_increment, + }; + increment_coin_balance(deps.storage, account_id, &coin_to_increment)?; Ok(Response::new() .add_attribute("action", "rover/credit-manager/update_coin_balance") - .add_attribute("denom", curr.denom) - .add_attribute("incremented", amount_to_increment)) + .add_attribute("account_id", account_id) + .add_attribute("coin_incremented", coin_to_increment.to_string())) } } diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index fd994de6f..8edb2cab6 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -59,7 +59,10 @@ pub fn enter_vault( Ok(Response::new() .add_message(vault.deposit_msg(&coin_to_enter)?) .add_message(update_vault_balance_msg) - .add_attribute("action", "rover/credit-manager/vault/deposit")) + .add_attribute("action", "rover/credit-manager/vault/enter") + .add_attribute("account_id", account_id) + .add_attribute("vault_addr", vault.address.to_string()) + .add_attribute("amount_deposited", amount.to_string())) } pub fn update_vault_coin_balance( @@ -90,6 +93,8 @@ pub fn update_vault_coin_balance( Ok(Response::new() .add_attribute("action", "rover/credit-manager/vault/update_balance") + .add_attribute("account_id", account_id.to_string()) + .add_attribute("vault_addr", vault.address.to_string()) .add_attribute("amount_incremented", current_balance.checked_sub(previous_total_balance)?)) } diff --git a/contracts/credit-manager/src/vault/exit.rs b/contracts/credit-manager/src/vault/exit.rs index 524594a02..36ca7ed5b 100644 --- a/contracts/credit-manager/src/vault/exit.rs +++ b/contracts/credit-manager/src/vault/exit.rs @@ -45,5 +45,8 @@ pub fn exit_vault( Ok(Response::new() .add_message(withdraw_msg) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit-manager/vault/withdraw")) + .add_attribute("action", "rover/credit-manager/vault/exit") + .add_attribute("account_id", account_id) + .add_attribute("vault_addr", vault.address.to_string()) + .add_attribute("amount_withdrawn", amount.to_string())) } diff --git a/contracts/credit-manager/src/vault/exit_unlocked.rs b/contracts/credit-manager/src/vault/exit_unlocked.rs index 86676be6e..ea4bd22af 100644 --- a/contracts/credit-manager/src/vault/exit_unlocked.rs +++ b/contracts/credit-manager/src/vault/exit_unlocked.rs @@ -61,5 +61,8 @@ pub fn exit_vault_unlocked( Ok(Response::new() .add_message(withdraw_unlocked_msg) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit-manager/vault/unlock")) + .add_attribute("action", "rover/credit-manager/vault/exit_unlocked") + .add_attribute("account_id", account_id) + .add_attribute("vault_addr", vault.address.to_string()) + .add_attribute("position_id", position_id.to_string())) } diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index d352f5e6b..7bdef2869 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -111,11 +111,10 @@ fn liquidate_unlocked( .add_message(vault_withdraw_msg) .add_message(update_coin_balance_msg) .add_attribute("action", "rover/credit-manager/liquidate_vault/unlocked") + .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) - .add_attribute("debt_repaid_denom", debt.denom) - .add_attribute("debt_repaid_amount", debt.amount) - .add_attribute("vault_coin_denom", request.denom) - .add_attribute("vault_coin_liquidated", request.amount)) + .add_attribute("coin_debt_repaid", debt.to_string()) + .add_attribute("coin_liquidated", request.to_string())) } /// Converts vault coins to their underlying value. This allows for pricing and liquidation @@ -204,11 +203,10 @@ fn liquidate_unlocking( .add_messages(vault_withdraw_msgs) .add_message(update_coin_balance_msg) .add_attribute("action", "rover/credit-manager/liquidate_vault/unlocking") + .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) - .add_attribute("debt_repaid_denom", debt.denom) - .add_attribute("debt_repaid_amount", debt.amount) - .add_attribute("vault_coin_denom", request.denom) - .add_attribute("vault_coin_liquidated", request.amount)) + .add_attribute("coin_debt_repaid", debt.to_string()) + .add_attribute("coin_liquidated", request.to_string())) } fn liquidate_locked( @@ -257,9 +255,8 @@ fn liquidate_locked( .add_message(vault_withdraw_msg) .add_message(update_coin_balance_msg) .add_attribute("action", "rover/credit-manager/liquidate_vault/locked") + .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) - .add_attribute("debt_repaid_denom", debt.denom) - .add_attribute("debt_repaid_amount", debt.amount) - .add_attribute("vault_coin_denom", request.denom) - .add_attribute("vault_coin_liquidated", request.amount)) + .add_attribute("coin_debt_repaid", debt.to_string()) + .add_attribute("coin_liquidated", request.to_string())) } diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index 1154c9bec..75ffad728 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -61,7 +61,10 @@ pub fn request_vault_unlock( Ok(Response::new() .add_submessage(request_unlock_msg) - .add_attribute("action", "rover/credit-manager/vault/request_unlock")) + .add_attribute("action", "rover/credit-manager/vault/request_unlock") + .add_attribute("account_id", account_id) + .add_attribute("vault_addr", vault.address) + .add_attribute("unlock_amount", amount)) } pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResult { @@ -87,5 +90,8 @@ pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResul VAULT_REQUEST_TEMP_STORAGE.remove(deps.storage); Ok(Response::new() - .add_attribute("action", "rover/credit-manager/vault/unlock_request/handle_reply")) + .add_attribute("action", "rover/credit-manager/vault/unlock_request/handle_reply") + .add_attribute("account_id", &storage.account_id) + .add_attribute("vault_addr", storage.vault_addr.to_string()) + .add_attribute("position_id", unlocking_position.id.to_string())) } diff --git a/contracts/credit-manager/src/withdraw.rs b/contracts/credit-manager/src/withdraw.rs index 61ae7a0f7..291798d98 100644 --- a/contracts/credit-manager/src/withdraw.rs +++ b/contracts/credit-manager/src/withdraw.rs @@ -24,5 +24,6 @@ pub fn withdraw( Ok(Response::new() .add_message(transfer_msg) .add_attribute("action", "rover/credit-manager/callback/withdraw") - .add_attribute("withdrawn", coin.to_string())) + .add_attribute("account_id", account_id) + .add_attribute("coin_withdrawn", coin.to_string())) } diff --git a/contracts/credit-manager/src/zap.rs b/contracts/credit-manager/src/zap.rs index 015fff05b..08b38b47e 100644 --- a/contracts/credit-manager/src/zap.rs +++ b/contracts/credit-manager/src/zap.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; use mars_rover::{ error::{ContractError, ContractResult}, msg::execute::{ActionAmount, ActionCoin}, - traits::Denoms, + traits::{Denoms, Stringify}, }; use crate::{ @@ -49,7 +49,10 @@ pub fn provide_liquidity( Ok(Response::new() .add_message(zap_msg) .add_message(update_balance_msg) - .add_attribute("action", "rover/credit-manager/provide_liquidity")) + .add_attribute("action", "rover/credit-manager/provide_liquidity") + .add_attribute("account_id", account_id) + .add_attribute("coins_in", updated_coins_in.as_slice().to_string()) + .add_attribute("lp_token_out", lp_token_out)) } pub fn withdraw_liquidity( @@ -92,7 +95,10 @@ pub fn withdraw_liquidity( Ok(Response::new() .add_message(zap_msg) .add_messages(update_balances_msgs) - .add_attribute("action", "rover/credit-manager/withdraw_liquidity")) + .add_attribute("action", "rover/credit-manager/withdraw_liquidity") + .add_attribute("account_id", account_id) + .add_attribute("coin_in", lp_token.to_string()) + .add_attribute("coins_out", coins_out.as_slice().to_string())) } pub fn estimate_provide_liquidity( diff --git a/packages/rover/src/coins.rs b/packages/rover/src/coins.rs index 0842ef7ff..b3b8dbe79 100644 --- a/packages/rover/src/coins.rs +++ b/packages/rover/src/coins.rs @@ -279,7 +279,7 @@ impl Coins { impl Stringify for &[Coin] { fn to_string(&self) -> String { - self.iter().map(|coin| coin.clone().denom).collect::>().join(", ") + self.iter().map(|coin| coin.to_string()).collect::>().join(",") } } diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 3351bddd6..0e9554d4f 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,8 +1,7 @@ { - "mockVault": "osmo1666zn7mz558793eka3ll2dsll34w3nsls3etcpgyka88mxepqmss0eyjl0", - "marsOracleAdapter": "osmo18lu2s7jzrga3zf0e30exd60dvfkw90krzddt9zhqm9zpap80jmfs2gckz6", - "swapper": "osmo1sgme3vlj96dyapq4a5s6paf4huw0eflvgrkhs5xruntlavn9g4aswe2ejk", - "zapper": "osmo1hzn6judff44stlnmyuu8vaxjkrc2nurc2xvu00u0hs6gxrjx54esla3lxv", - "creditManager": "osmo1vzx4gy7rphrzkjyym4vwml995rydaqy8jqx5mcx7elmxuyypcvtqhd7huh", - "accountNft": "osmo1prkk67duuvzvp2v96y4t0mcln4slfvcq9p4v6jy28p5gyt64nq5ss8p6j5" + "mockVault": "osmo1mpqtycwquw9gn74v0zakegwve9d3tzlx5ly4s74t75j4779py5ts73atcr", + "swapper": "osmo178fmx9q5q7qv6f42a36w89r72kpxa3c0v5974y5an9yqj0zf59csttun37", + "zapper": "osmo1f9h3jpe85e5q6a0pfyhfahpghl94hgj0j3nvdqm5qp0xqyq0a22q2j7s9t", + "creditManager": "osmo1j6dnsuzvgnsnudr8tqvdeds0kma63ctpqu9265w6uqz9snfwu0ts0cau3v", + "accountNft": "osmo1nrffysduac9yf3yk4vyj2d6m50x2069v5ukvqp4363key0dnfneql6u693" } From 88b937e877221f7820c410c9d77ea1c8487a0f0b Mon Sep 17 00:00:00 2001 From: Dohko_01 <11282287+dav1de24@users.noreply.github.com> Date: Thu, 12 Jan 2023 22:54:32 +0900 Subject: [PATCH 121/218] Create LICENSE (#94) --- LICENSE | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From e8b25f0f84f737e29dfdff1733b7fa5a3175b3f9 Mon Sep 17 00:00:00 2001 From: Dohko_01 <11282287+dav1de24@users.noreply.github.com> Date: Thu, 12 Jan 2023 22:54:54 +0900 Subject: [PATCH 122/218] Update README.md (#95) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 02c434bc9..3ee72e0ec 100644 --- a/README.md +++ b/README.md @@ -148,3 +148,8 @@ yarn deploy:osmosis ### Deployment Addresses published in [/scripts/deploy/addresses](https://github.com/mars-protocol/rover/tree/master/scripts/deploy/addresses) + + +## License + +Contents of this repository are open source under [GNU General Public License v3](./LICENSE) or later. From cdd81e58cdc918807a6afb173faa6c68551560b4 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 17 Jan 2023 16:04:42 +0100 Subject: [PATCH 123/218] Outposts update + redeployment (#96) * Outposts update + redeployment * remove caching --- .github/workflows/coverage.yml | 4 - Cargo.lock | 10 +- Cargo.toml | 5 +- contracts/account-nft/Cargo.toml | 1 + contracts/credit-manager/Cargo.toml | 1 + contracts/mock-credit-manager/Cargo.toml | 1 + contracts/mock-oracle/Cargo.toml | 1 + contracts/mock-red-bank/Cargo.toml | 1 + contracts/mock-vault/Cargo.toml | 1 + contracts/swapper/base/Cargo.toml | 1 + contracts/swapper/mock/Cargo.toml | 1 + contracts/swapper/osmosis/Cargo.toml | 1 + contracts/swapper/osmosis/src/route.rs | 11 +- contracts/zapper/base/Cargo.toml | 1 + contracts/zapper/mock/Cargo.toml | 1 + contracts/zapper/osmosis/Cargo.toml | 1 + coverage_grcov.Makefile.toml | 2 +- packages/chains/osmosis/Cargo.toml | 4 +- packages/chains/osmosis/src/helpers.rs | 53 +++++++- packages/health/Cargo.toml | 1 + packages/math/Cargo.toml | 1 + packages/outpost/Cargo.toml | 2 + packages/outpost/src/address_provider.rs | 25 +++- packages/outpost/src/error.rs | 7 +- packages/outpost/src/helpers.rs | 31 +++++ packages/outpost/src/incentives.rs | 38 +++++- packages/outpost/src/oracle.rs | 29 ++-- packages/outpost/src/red_bank/msg.rs | 12 +- packages/outpost/src/red_bank/types.rs | 26 +++- packages/outpost/src/rewards_collector.rs | 118 +++++++++++------ packages/rover/Cargo.toml | 1 + .../mars-mock-red-bank.json | 125 +++++++++++++++--- scripts/deploy/addresses/osmo-test-4.json | 10 +- scripts/deploy/base/deployer.ts | 25 ++-- scripts/deploy/base/index.ts | 2 +- scripts/deploy/base/rover.ts | 2 +- scripts/deploy/osmosis/config.ts | 90 ++++++++++--- scripts/types/config.ts | 9 +- .../MarsMockRedBank.client.ts | 51 ++++++- .../MarsMockRedBank.message-composer.ts | 37 +++++- .../MarsMockRedBank.react-query.ts | 48 ++++++- .../MarsMockRedBank.types.ts | 31 ++++- scripts/types/priceSource.ts | 46 ++++++- 43 files changed, 697 insertions(+), 171 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 9619ea26c..123b4cc52 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -29,10 +29,6 @@ jobs: override: true components: llvm-tools-preview - # selecting a toolchain should happen before the plugin, as the cache uses the current rustc version as its cache key - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - - name: Install cargo make uses: davidB/rust-cargo-make@v1 diff --git a/Cargo.lock b/Cargo.lock index ab191d20a..f96787fe5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1127,6 +1127,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "mars-owner", "thiserror", ] @@ -1340,8 +1341,7 @@ dependencies = [ [[package]] name = "osmosis-std" version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d6fe6ac7fcba45ed61d738091d33c838c4cabbcf4892dc7aa56d19d39cc976" +source = "git+https://github.com/osmosis-labs/osmosis-rust?rev=5f2493aa13569167431add6282916683b4aaf3c8#5f2493aa13569167431add6282916683b4aaf3c8" dependencies = [ "chrono", "cosmwasm-std", @@ -1367,8 +1367,7 @@ dependencies = [ [[package]] name = "osmosis-std-derive" version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a455e262a6fdfd3914f3a4e11e6bc0ce491901cb9d507d7856d7ef6e129e90c6" +source = "git+https://github.com/osmosis-labs/osmosis-rust?rev=5f2493aa13569167431add6282916683b4aaf3c8#5f2493aa13569167431add6282916683b4aaf3c8" dependencies = [ "itertools", "proc-macro2", @@ -1379,8 +1378,7 @@ dependencies = [ [[package]] name = "osmosis-testing" version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42636a83a181b7c4f94658eb3bb219caa51ff55bcc69750cd7ae4bf50b12cb2f" +source = "git+https://github.com/osmosis-labs/osmosis-rust?rev=5f2493aa13569167431add6282916683b4aaf3c8#5f2493aa13569167431add6282916683b4aaf3c8" dependencies = [ "base64", "bindgen", diff --git a/Cargo.toml b/Cargo.toml index c13dd9842..a37b30ff3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ authors = [ "larry_0x ", "Piotr Babel ", ] +license = "GPL-v3-or-later" edition = "2021" repository = "https://github.com/mars-protocol/rover" homepage = "https://marsprotocol.io" @@ -42,8 +43,8 @@ cw-multi-test = "0.16.1" cw-utils = "0.16.0" cw-storage-plus = "1.0.1" itertools = "0.10.5" -osmosis-std = "0.13.2" -osmosis-testing = "0.13.2" +osmosis-std = { git = "https://github.com/osmosis-labs/osmosis-rust", rev = "5f2493aa13569167431add6282916683b4aaf3c8" } +osmosis-testing = { git = "https://github.com/osmosis-labs/osmosis-rust", rev = "5f2493aa13569167431add6282916683b4aaf3c8" } schemars = "0.8.11" serde = { version = "1.0.150", default-features = false, features = ["derive"] } thiserror = "1.0.37" diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 088ce447e..7c0b90be6 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-account-nft" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 30095de88..c1a3fd06e 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-credit-manager" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/contracts/mock-credit-manager/Cargo.toml b/contracts/mock-credit-manager/Cargo.toml index 7b5b29bb0..6e265632c 100644 --- a/contracts/mock-credit-manager/Cargo.toml +++ b/contracts/mock-credit-manager/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-mock-credit-manager" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/contracts/mock-oracle/Cargo.toml b/contracts/mock-oracle/Cargo.toml index 8364044a4..4dc391b21 100644 --- a/contracts/mock-oracle/Cargo.toml +++ b/contracts/mock-oracle/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-mock-oracle" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index 3ad43dc94..1a391d86a 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-mock-red-bank" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/contracts/mock-vault/Cargo.toml b/contracts/mock-vault/Cargo.toml index 0d6b07c67..37a84fc6e 100644 --- a/contracts/mock-vault/Cargo.toml +++ b/contracts/mock-vault/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-mock-vault" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/contracts/swapper/base/Cargo.toml b/contracts/swapper/base/Cargo.toml index 4e4b587f9..202abf786 100644 --- a/contracts/swapper/base/Cargo.toml +++ b/contracts/swapper/base/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-swapper-base" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/contracts/swapper/mock/Cargo.toml b/contracts/swapper/mock/Cargo.toml index 316414a14..fb2c19163 100644 --- a/contracts/swapper/mock/Cargo.toml +++ b/contracts/swapper/mock/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-swapper-mock" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index e114cb83c..684c9c534 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-swapper-osmosis" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/contracts/swapper/osmosis/src/route.rs b/contracts/swapper/osmosis/src/route.rs index 8ab78136a..c9beab023 100644 --- a/contracts/swapper/osmosis/src/route.rs +++ b/contracts/swapper/osmosis/src/route.rs @@ -4,7 +4,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ BlockInfo, Coin, CosmosMsg, Decimal, Empty, Env, Fraction, QuerierWrapper, Uint128, }; -use mars_osmosis::helpers::{has_denom, query_pool, query_twap_price}; +use mars_osmosis::helpers::{has_denom, query_arithmetic_twap_price, query_pool}; use mars_rover::adapters::swap::EstimateExactInSwapResponse; use mars_swapper_base::{ContractError, ContractResult, Route}; use osmosis_std::types::osmosis::gamm::v1beta1::{MsgSwapExactAmountIn, SwapAmountInRoute}; @@ -163,8 +163,13 @@ fn query_out_amount( let mut price = Decimal::one(); let mut denom_in = coin_in.denom.clone(); for step in steps { - let step_price = - query_twap_price(querier, step.pool_id, &denom_in, &step.token_out_denom, start_time)?; + let step_price = query_arithmetic_twap_price( + querier, + step.pool_id, + &denom_in, + &step.token_out_denom, + start_time, + )?; price = price.checked_mul(step_price)?; denom_in = step.token_out_denom.clone(); } diff --git a/contracts/zapper/base/Cargo.toml b/contracts/zapper/base/Cargo.toml index 44e558d63..9226f1947 100644 --- a/contracts/zapper/base/Cargo.toml +++ b/contracts/zapper/base/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-zapper-base" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/contracts/zapper/mock/Cargo.toml b/contracts/zapper/mock/Cargo.toml index 46708a5ae..5c6e59113 100644 --- a/contracts/zapper/mock/Cargo.toml +++ b/contracts/zapper/mock/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-zapper-mock" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/contracts/zapper/osmosis/Cargo.toml b/contracts/zapper/osmosis/Cargo.toml index cd2cf7006..91f565bdc 100644 --- a/contracts/zapper/osmosis/Cargo.toml +++ b/contracts/zapper/osmosis/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-zapper-osmosis" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/coverage_grcov.Makefile.toml b/coverage_grcov.Makefile.toml index d77fbc7bb..02653340d 100644 --- a/coverage_grcov.Makefile.toml +++ b/coverage_grcov.Makefile.toml @@ -41,7 +41,7 @@ set -eux grcov ${COVERAGE_PROF_OUTPUT} \ -b ${COVERAGE_BINARIES} -s ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY} \ - -t ${GRCOV_OUTPUT_TYPE} --llvm --branch --ignore-not-existing --ignore "/*" --ignore "*/tests/*" --ignore "*/mock*" --ignore "target/*" --ignore "*/swapper/*" --ignore "*/zapper/*" -o ${GRCOV_OUTPUT_PATH} + -t ${GRCOV_OUTPUT_TYPE} --llvm --branch --ignore-not-existing --ignore "/*" --ignore "*/tests/*" --ignore "*/mock*" --ignore "target/*" --ignore "*/swapper/*" --ignore "*/zapper/*" --ignore "*/packages/outpost/*" -o ${GRCOV_OUTPUT_PATH} ''' dependencies = ["install-grcov", "coverage-grcov-prepare-outdir", "coverage-grcov-run-test"] diff --git a/packages/chains/osmosis/Cargo.toml b/packages/chains/osmosis/Cargo.toml index 68f32f32b..cf78161a4 100755 --- a/packages/chains/osmosis/Cargo.toml +++ b/packages/chains/osmosis/Cargo.toml @@ -3,6 +3,7 @@ name = "mars-osmosis" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } +license = { workspace = true } repository = { workspace = true } homepage = { workspace = true } documentation = { workspace = true } @@ -12,11 +13,10 @@ keywords = { workspace = true } doctest = false [features] -# for quicker tests, cargo test --lib # for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] [dependencies] cosmwasm-std = { workspace = true } osmosis-std = { workspace = true } -serde = { workspace = true } \ No newline at end of file +serde = { workspace = true } diff --git a/packages/chains/osmosis/src/helpers.rs b/packages/chains/osmosis/src/helpers.rs index 56f6c046e..91a631d92 100644 --- a/packages/chains/osmosis/src/helpers.rs +++ b/packages/chains/osmosis/src/helpers.rs @@ -4,10 +4,11 @@ use cosmwasm_std::{ coin, Decimal, Empty, QuerierWrapper, QueryRequest, StdError, StdResult, Uint128, }; use osmosis_std::{ - shim::Timestamp, + shim::{Duration, Timestamp}, types::{ cosmos::base::v1beta1::Coin, osmosis::{ + downtimedetector::v1beta1::DowntimedetectorQuerier, gamm::{ v1beta1::{PoolAsset, PoolParams, QueryPoolRequest}, v2::GammQuerier, @@ -79,17 +80,17 @@ pub fn query_spot_price( Ok(price) } -/// Query the twap price of a coin, denominated in OSMO. +/// Query arithmetic twap price of a coin, denominated in OSMO. /// `start_time` must be within 48 hours of current block time. #[allow(deprecated)] // FIXME: arithmetic_twap_to_now shouldn't be deprecated, make clippy happy for now -pub fn query_twap_price( +pub fn query_arithmetic_twap_price( querier: &QuerierWrapper, pool_id: u64, base_denom: &str, quote_denom: &str, start_time: u64, ) -> StdResult { - let arithmetic_twap_res = TwapQuerier::new(querier).arithmetic_twap_to_now( + let twap_res = TwapQuerier::new(querier).arithmetic_twap_to_now( pool_id, base_denom.to_string(), quote_denom.to_string(), @@ -98,10 +99,52 @@ pub fn query_twap_price( nanos: 0, }), )?; - let price = Decimal::from_str(&arithmetic_twap_res.arithmetic_twap)?; + let price = Decimal::from_str(&twap_res.arithmetic_twap)?; Ok(price) } +/// Query geometric twap price of a coin, denominated in OSMO. +/// `start_time` must be within 48 hours of current block time. +#[allow(deprecated)] // FIXME: geometric_twap_to_now shouldn't be deprecated, make clippy happy for now +pub fn query_geometric_twap_price( + querier: &QuerierWrapper, + pool_id: u64, + base_denom: &str, + quote_denom: &str, + start_time: u64, +) -> StdResult { + let twap_res = TwapQuerier::new(querier).geometric_twap_to_now( + pool_id, + base_denom.to_string(), + quote_denom.to_string(), + Some(Timestamp { + seconds: start_time as i64, + nanos: 0, + }), + )?; + let price = Decimal::from_str(&twap_res.geometric_twap)?; + Ok(price) +} + +/// Has it been $RECOVERY_PERIOD since the chain has been down for $DOWNTIME_PERIOD. +/// +/// https://github.com/osmosis-labs/osmosis/tree/main/x/downtime-detector +pub fn recovered_since_downtime_of_length( + querier: &QuerierWrapper, + downtime: i32, + recovery: u64, +) -> StdResult { + let downtime_detector_res = DowntimedetectorQuerier::new(querier) + .recovered_since_downtime_of_length( + downtime, + Some(Duration { + seconds: recovery as i64, + nanos: 0, + }), + )?; + Ok(downtime_detector_res.succesfully_recovered) +} + #[cfg(test)] mod tests { use super::*; diff --git a/packages/health/Cargo.toml b/packages/health/Cargo.toml index 359fbde6e..aec6fd9dd 100644 --- a/packages/health/Cargo.toml +++ b/packages/health/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-health" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/packages/math/Cargo.toml b/packages/math/Cargo.toml index 1a21c21ba..52b3d7f95 100644 --- a/packages/math/Cargo.toml +++ b/packages/math/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-math" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/packages/outpost/Cargo.toml b/packages/outpost/Cargo.toml index e674b91ed..2b4adbd64 100644 --- a/packages/outpost/Cargo.toml +++ b/packages/outpost/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-outpost" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } @@ -19,4 +20,5 @@ backtraces = ["cosmwasm-std/backtraces"] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +mars-owner = { workspace = true } thiserror = { workspace = true } diff --git a/packages/outpost/src/address_provider.rs b/packages/outpost/src/address_provider.rs index 017273ee3..3f5d80dcf 100644 --- a/packages/outpost/src/address_provider.rs +++ b/packages/outpost/src/address_provider.rs @@ -2,6 +2,7 @@ use std::{any::type_name, fmt, str::FromStr}; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::StdError; +use mars_owner::OwnerUpdate; #[cw_serde] #[derive(Copy, Eq, Hash)] @@ -83,6 +84,12 @@ pub struct InstantiateMsg { pub prefix: String, } +#[cw_serde] +pub struct Config { + /// The address prefix of the chain this contract is deployed on + pub prefix: String, +} + #[cw_serde] pub enum ExecuteMsg { /// Set address @@ -90,17 +97,15 @@ pub enum ExecuteMsg { address_type: MarsAddressType, address: String, }, - /// Propose to transfer the contract's ownership to another account - TransferOwnership { - new_owner: String, - }, + /// Manages admin role state + UpdateOwner(OwnerUpdate), } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { /// Get config - #[returns(Config)] + #[returns(ConfigResponse)] Config {}, /// Get a single address #[returns(AddressResponseItem)] @@ -116,7 +121,15 @@ pub enum QueryMsg { }, } -pub type Config = InstantiateMsg; +#[cw_serde] +pub struct ConfigResponse { + /// The contract's owner + pub owner: Option, + /// The contract's proposed owner + pub proposed_new_owner: Option, + /// The address prefix of the chain this contract is deployed on + pub prefix: String, +} #[cw_serde] pub struct AddressResponseItem { diff --git a/packages/outpost/src/error.rs b/packages/outpost/src/error.rs index efbe23014..12da549d4 100644 --- a/packages/outpost/src/error.rs +++ b/packages/outpost/src/error.rs @@ -29,13 +29,18 @@ pub enum MarsError { Deserialize { target_type: String, }, + + #[error("Invalid denom: {reason}")] + InvalidDenom { + reason: String, + }, } impl From for StdError { fn from(source: MarsError) -> Self { match source { MarsError::Std(e) => e, - e => StdError::generic_err(format!("{e}")), + e => StdError::generic_err(e.to_string()), } } } diff --git a/packages/outpost/src/helpers.rs b/packages/outpost/src/helpers.rs index d385906df..9f0030504 100644 --- a/packages/outpost/src/helpers.rs +++ b/packages/outpost/src/helpers.rs @@ -61,3 +61,34 @@ pub fn integer_param_gt_zero(param_value: u64, param_name: &str) -> Result<(), M pub fn zero_address() -> Addr { Addr::unchecked("") } + +/// follows cosmos SDK validation logic where denoms can be 3 - 128 characters long +/// and starts with a letter, followed but either a letter, number, or separator ( ‘/' , ‘:' , ‘.’ , ‘_’ , or '-') +/// reference: https://github.com/cosmos/cosmos-sdk/blob/7728516abfab950dc7a9120caad4870f1f962df5/types/coin.go#L865-L867 +pub fn validate_native_denom(denom: &str) -> Result<(), MarsError> { + if denom.len() < 3 || denom.len() > 128 { + return Err(MarsError::InvalidDenom { + reason: "Invalid denom length".to_string(), + }); + } + + let mut chars = denom.chars(); + let first = chars.next().unwrap(); + if !first.is_ascii_alphabetic() { + return Err(MarsError::InvalidDenom { + reason: "First character is not ASCII alphabetic".to_string(), + }); + } + + let set = ['/', ':', '.', '_', '-']; + for c in chars { + if !(c.is_ascii_alphanumeric() || set.contains(&c)) { + return Err(MarsError::InvalidDenom { + reason: "Not all characters are ASCII alphanumeric or one of: / : . _ -" + .to_string(), + }); + } + } + + Ok(()) +} diff --git a/packages/outpost/src/incentives.rs b/packages/outpost/src/incentives.rs index 62594bb91..0a798bfc9 100644 --- a/packages/outpost/src/incentives.rs +++ b/packages/outpost/src/incentives.rs @@ -1,11 +1,10 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Decimal, Uint128}; +use cosmwasm_std::{Addr, Decimal, Timestamp, Uint128}; +use mars_owner::OwnerUpdate; /// Global configuration #[cw_serde] pub struct Config { - /// Contract owner - pub owner: Addr, /// Address provider pub address_provider: Addr, /// Mars Token Denom @@ -17,6 +16,10 @@ pub struct Config { pub struct AssetIncentive { /// How much MARS per second is emitted to be then distributed to all Red Bank depositors pub emission_per_second: Uint128, + /// Start time for the incentive. Uses current block time if start_time not provided + pub start_time: Timestamp, + /// How many seconds the incentives last + pub duration: u64, /// Total MARS assigned for distribution since the start of the incentive pub index: Decimal, /// Last time (in seconds) index was updated @@ -42,13 +45,20 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { - /// Set emission per second for an asset to its depositor at Red Bank + /// Set incentive params for an asset to its depositor at Red Bank. + /// + /// If there is no incentive for the asset, all params are required. + /// New incentive can be set (rescheduled) if current one has finished (current_block_time > start_time + duration). SetAssetIncentive { /// Asset denom associated with the incentives denom: String, /// How many MARS will be assigned per second to be distributed among all Red Bank /// depositors - emission_per_second: Uint128, + emission_per_second: Option, + /// Start time for the incentive + start_time: Option, + /// How many seconds the incentives last + duration: Option, }, /// Handle balance change updating user and asset rewards. @@ -72,17 +82,19 @@ pub enum ExecuteMsg { /// Update contract config (only callable by owner) UpdateConfig { - owner: Option, address_provider: Option, mars_denom: Option, }, + + /// Manages admin role state + UpdateOwner(OwnerUpdate), } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { /// Query contract config - #[returns(Config)] + #[returns(ConfigResponse)] Config {}, /// Query info about asset incentive for a given denom @@ -97,3 +109,15 @@ pub enum QueryMsg { user: String, }, } + +#[cw_serde] +pub struct ConfigResponse { + /// The contract's owner + pub owner: Option, + /// The contract's proposed owner + pub proposed_new_owner: Option, + /// Address provider + pub address_provider: Addr, + /// Mars Token Denom + pub mars_denom: String, +} diff --git a/packages/outpost/src/oracle.rs b/packages/outpost/src/oracle.rs index 78eb74d4e..d5b9d02cc 100644 --- a/packages/outpost/src/oracle.rs +++ b/packages/outpost/src/oracle.rs @@ -1,22 +1,23 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Decimal; +use mars_owner::OwnerUpdate; #[cw_serde] -pub struct Config { +pub struct InstantiateMsg { /// The contract's owner, who can update config and price sources - pub owner: T, + pub owner: String, /// The asset in which prices are denominated in pub base_denom: String, } -pub type InstantiateMsg = Config; +#[cw_serde] +pub struct Config { + /// The asset in which prices are denominated in + pub base_denom: String, +} #[cw_serde] pub enum ExecuteMsg { - /// Update contract config - UpdateConfig { - owner: String, - }, /// Specify the price source to be used for a coin /// /// NOTE: The input parameters for method are chain-specific. @@ -28,13 +29,15 @@ pub enum ExecuteMsg { RemovePriceSource { denom: String, }, + /// Manages admin role state + UpdateOwner(OwnerUpdate), } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { /// Query contract config. - #[returns(Config)] + #[returns(ConfigResponse)] Config {}, /// Query a coin's price source. /// @@ -70,6 +73,16 @@ pub enum QueryMsg { }, } +#[cw_serde] +pub struct ConfigResponse { + /// The contract's owner + pub owner: Option, + /// The contract's proposed owner + pub proposed_new_owner: Option, + /// The asset in which prices are denominated in + pub base_denom: String, +} + #[cw_serde] pub struct PriceSourceResponse { pub denom: String, diff --git a/packages/outpost/src/red_bank/msg.rs b/packages/outpost/src/red_bank/msg.rs index a00a45efd..73d4cd7a5 100644 --- a/packages/outpost/src/red_bank/msg.rs +++ b/packages/outpost/src/red_bank/msg.rs @@ -1,10 +1,15 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Decimal, Uint128}; +use mars_owner::OwnerUpdate; use crate::red_bank::InterestRateModel; #[cw_serde] pub struct InstantiateMsg { + /// Contract's owner + pub owner: String, + /// Contract's emergency owner + pub emergency_owner: String, /// Market configuration pub config: CreateOrUpdateConfig, } @@ -12,6 +17,12 @@ pub struct InstantiateMsg { #[cw_serde] #[allow(clippy::large_enum_variant)] pub enum ExecuteMsg { + /// Manages owner state + UpdateOwner(OwnerUpdate), + + /// Manages emergency owner state + UpdateEmergencyOwner(OwnerUpdate), + /// Update contract config (only owner can call) UpdateConfig { config: CreateOrUpdateConfig, @@ -106,7 +117,6 @@ pub enum ExecuteMsg { #[cw_serde] pub struct CreateOrUpdateConfig { - pub owner: Option, pub address_provider: Option, pub close_factor: Option, } diff --git a/packages/outpost/src/red_bank/types.rs b/packages/outpost/src/red_bank/types.rs index 675685a43..39fe542f6 100644 --- a/packages/outpost/src/red_bank/types.rs +++ b/packages/outpost/src/red_bank/types.rs @@ -6,8 +6,6 @@ use crate::{error::MarsError, helpers::decimal_param_le_one}; /// Global configuration #[cw_serde] pub struct Config { - /// Contract owner - pub owner: T, /// Address provider returns addresses for all protocol contracts pub address_provider: T, /// Maximum percentage of outstanding debt that can be covered by a liquidator @@ -67,7 +65,21 @@ pub struct Position { pub asset_price: Decimal, } -pub type ConfigResponse = Config; +#[cw_serde] +pub struct ConfigResponse { + /// The contract's owner + pub owner: Option, + /// The contract's proposed owner + pub proposed_new_owner: Option, + /// The contract's emergency owner + pub emergency_owner: Option, + /// The contract's proposed emergency owner + pub proposed_new_emergency_owner: Option, + /// Address provider returns addresses for all protocol contracts + pub address_provider: String, + /// Maximum percentage of outstanding debt that can be covered by a liquidator + pub close_factor: Decimal, +} #[cw_serde] pub struct UncollateralizedLoanLimitResponse { @@ -105,12 +117,12 @@ pub struct UserCollateralResponse { pub struct UserPositionResponse { /// Total value of all enabled collateral assets. /// If an asset is disabled as collateral, it will not be included in this value. - pub total_enabled_collateral: Decimal, + pub total_enabled_collateral: Uint128, /// Total value of all collateralized debts. /// If the user has an uncollateralized loan limit in an asset, the debt in this asset will not /// be included in this value. - pub total_collateralized_debt: Decimal, - pub weighted_max_ltv_collateral: Decimal, - pub weighted_liquidation_threshold_collateral: Decimal, + pub total_collateralized_debt: Uint128, + pub weighted_max_ltv_collateral: Uint128, + pub weighted_liquidation_threshold_collateral: Uint128, pub health_status: UserHealthStatus, } diff --git a/packages/outpost/src/rewards_collector.rs b/packages/outpost/src/rewards_collector.rs index 1766fa0a6..1ef1be1dc 100644 --- a/packages/outpost/src/rewards_collector.rs +++ b/packages/outpost/src/rewards_collector.rs @@ -1,20 +1,20 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Api, Decimal, StdResult, Uint128}; +use mars_owner::OwnerUpdate; use crate::{ error::MarsError, - helpers::{decimal_param_le_one, integer_param_gt_zero}, + helpers::{decimal_param_le_one, integer_param_gt_zero, validate_native_denom}, }; const MAX_SLIPPAGE_TOLERANCE_PERCENTAGE: u64 = 50; -/// Global configuration #[cw_serde] -pub struct Config { - /// Contract owner - pub owner: T, +pub struct InstantiateMsg { + /// The contract's owner + pub owner: String, /// Address provider returns addresses for all protocol contracts - pub address_provider: T, + pub address_provider: String, /// Percentage of fees that are sent to the safety fund pub safety_tax_rate: Decimal, /// The asset to which the safety fund share is converted @@ -33,7 +33,29 @@ pub struct Config { pub slippage_tolerance: Decimal, } -impl Config { +#[cw_serde] +pub struct Config { + /// Address provider returns addresses for all protocol contracts + pub address_provider: Addr, + /// Percentage of fees that are sent to the safety fund + pub safety_tax_rate: Decimal, + /// The asset to which the safety fund share is converted + pub safety_fund_denom: String, + /// The asset to which the fee collector share is converted + pub fee_collector_denom: String, + /// The channel ID of the mars hub + pub channel_id: String, + /// Revision number of Mars Hub's IBC client + pub timeout_revision: u64, + /// Number of blocks after which an IBC transfer is to be considered failed, if no acknowledgement is received + pub timeout_blocks: u64, + /// Number of seconds after which an IBC transfer is to be considered failed, if no acknowledgement is received + pub timeout_seconds: u64, + /// Maximum percentage of price movement (minimum amount you accept to receive during swap) + pub slippage_tolerance: Decimal, +} + +impl Config { pub fn validate(&self) -> Result<(), MarsError> { decimal_param_le_one(self.safety_tax_rate, "safety_tax_rate")?; @@ -49,49 +71,32 @@ impl Config { }); } + validate_native_denom(&self.safety_fund_denom)?; + validate_native_denom(&self.fee_collector_denom)?; + Ok(()) } } -impl Config { - pub fn check(&self, api: &dyn Api) -> StdResult> { +impl Config { + pub fn checked(api: &dyn Api, msg: InstantiateMsg) -> StdResult { Ok(Config { - owner: api.addr_validate(&self.owner)?, - address_provider: api.addr_validate(&self.address_provider)?, - safety_tax_rate: self.safety_tax_rate, - safety_fund_denom: self.safety_fund_denom.clone(), - fee_collector_denom: self.fee_collector_denom.clone(), - channel_id: self.channel_id.clone(), - timeout_revision: self.timeout_revision, - timeout_blocks: self.timeout_blocks, - timeout_seconds: self.timeout_seconds, - slippage_tolerance: self.slippage_tolerance, + address_provider: api.addr_validate(&msg.address_provider)?, + safety_tax_rate: msg.safety_tax_rate, + safety_fund_denom: msg.safety_fund_denom, + fee_collector_denom: msg.fee_collector_denom, + channel_id: msg.channel_id, + timeout_revision: msg.timeout_revision, + timeout_blocks: msg.timeout_blocks, + timeout_seconds: msg.timeout_seconds, + slippage_tolerance: msg.slippage_tolerance, }) } } -impl From> for Config { - fn from(cfg: Config) -> Self { - Self { - owner: cfg.owner.into(), - address_provider: cfg.address_provider.into(), - safety_tax_rate: cfg.safety_tax_rate, - safety_fund_denom: cfg.safety_fund_denom.clone(), - fee_collector_denom: cfg.fee_collector_denom.clone(), - channel_id: cfg.channel_id.clone(), - timeout_revision: cfg.timeout_revision, - timeout_blocks: cfg.timeout_blocks, - timeout_seconds: cfg.timeout_seconds, - slippage_tolerance: cfg.slippage_tolerance, - } - } -} - #[cw_serde] #[derive(Default)] -pub struct CreateOrUpdateConfig { - /// Contract owner - pub owner: Option, +pub struct UpdateConfig { /// Address provider returns addresses for all protocol contracts pub address_provider: Option, /// Percentage of fees that are sent to the safety fund @@ -112,13 +117,14 @@ pub struct CreateOrUpdateConfig { pub slippage_tolerance: Option, } -pub type InstantiateMsg = Config; - #[cw_serde] pub enum ExecuteMsg { + /// Manages admin role state + UpdateOwner(OwnerUpdate), + /// Update contract config UpdateConfig { - new_cfg: CreateOrUpdateConfig, + new_cfg: UpdateConfig, }, /// Configure the route for swapping an asset @@ -152,11 +158,37 @@ pub enum ExecuteMsg { }, } +#[cw_serde] +pub struct ConfigResponse { + /// The contract's owner + pub owner: Option, + /// The contract's proposed owner + pub proposed_new_owner: Option, + /// Address provider returns addresses for all protocol contracts + pub address_provider: String, + /// Percentage of fees that are sent to the safety fund + pub safety_tax_rate: Decimal, + /// The asset to which the safety fund share is converted + pub safety_fund_denom: String, + /// The asset to which the fee collector share is converted + pub fee_collector_denom: String, + /// The channel ID of the mars hub + pub channel_id: String, + /// Revision number of Mars Hub's IBC client + pub timeout_revision: u64, + /// Number of blocks after which an IBC transfer is to be considered failed, if no acknowledgement is received + pub timeout_blocks: u64, + /// Number of seconds after which an IBC transfer is to be considered failed, if no acknowledgement is received + pub timeout_seconds: u64, + /// Maximum percentage of price movement (minimum amount you accept to receive during swap) + pub slippage_tolerance: Decimal, +} + #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { /// Get config parameters - #[returns(Config)] + #[returns(ConfigResponse)] Config {}, /// Get routes for swapping an input denom into an output denom. /// diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 5b1422cbf..c32d21a77 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -2,6 +2,7 @@ name = "mars-rover" version = { workspace = true } authors = { workspace = true } +license = { workspace = true } edition = { workspace = true } repository = { workspace = true } homepage = { workspace = true } diff --git a/schemas/mars-mock-red-bank/mars-mock-red-bank.json b/schemas/mars-mock-red-bank/mars-mock-red-bank.json index 1b5de8439..00a10784e 100644 --- a/schemas/mars-mock-red-bank/mars-mock-red-bank.json +++ b/schemas/mars-mock-red-bank/mars-mock-red-bank.json @@ -53,6 +53,32 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", "oneOf": [ + { + "description": "Manages owner state", + "type": "object", + "required": [ + "update_owner" + ], + "properties": { + "update_owner": { + "$ref": "#/definitions/OwnerUpdate" + } + }, + "additionalProperties": false + }, + { + "description": "Manages emergency owner state", + "type": "object", + "required": [ + "update_emergency_owner" + ], + "properties": { + "update_emergency_owner": { + "$ref": "#/definitions/OwnerUpdate" + } + }, + "additionalProperties": false + }, { "description": "Update contract config (only owner can call)", "type": "object", @@ -385,12 +411,6 @@ "type": "null" } ] - }, - "owner": { - "type": [ - "string", - "null" - ] } }, "additionalProperties": false @@ -529,6 +549,53 @@ }, "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" + ] + } + ] + }, "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" @@ -930,13 +997,11 @@ "responses": { "config": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config_for_String", - "description": "Global configuration", + "title": "ConfigResponse", "type": "object", "required": [ "address_provider", - "close_factor", - "owner" + "close_factor" ], "properties": { "address_provider": { @@ -951,9 +1016,33 @@ } ] }, + "emergency_owner": { + "description": "The contract's emergency owner", + "type": [ + "string", + "null" + ] + }, "owner": { - "description": "Contract owner", - "type": "string" + "description": "The contract's owner", + "type": [ + "string", + "null" + ] + }, + "proposed_new_emergency_owner": { + "description": "The contract's proposed emergency owner", + "type": [ + "string", + "null" + ] + }, + "proposed_new_owner": { + "description": "The contract's proposed owner", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false, @@ -1655,7 +1744,7 @@ "description": "Total value of all collateralized debts. If the user has an uncollateralized loan limit in an asset, the debt in this asset will not be included in this value.", "allOf": [ { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" } ] }, @@ -1663,15 +1752,15 @@ "description": "Total value of all enabled collateral assets. If an asset is disabled as collateral, it will not be included in this value.", "allOf": [ { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" } ] }, "weighted_liquidation_threshold_collateral": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" }, "weighted_max_ltv_collateral": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/Uint128" } }, "additionalProperties": false, @@ -1680,6 +1769,10 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "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" + }, "UserHealthStatus": { "oneOf": [ { diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 0e9554d4f..e3ea0ff08 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,7 @@ { - "mockVault": "osmo1mpqtycwquw9gn74v0zakegwve9d3tzlx5ly4s74t75j4779py5ts73atcr", - "swapper": "osmo178fmx9q5q7qv6f42a36w89r72kpxa3c0v5974y5an9yqj0zf59csttun37", - "zapper": "osmo1f9h3jpe85e5q6a0pfyhfahpghl94hgj0j3nvdqm5qp0xqyq0a22q2j7s9t", - "creditManager": "osmo1j6dnsuzvgnsnudr8tqvdeds0kma63ctpqu9265w6uqz9snfwu0ts0cau3v", - "accountNft": "osmo1nrffysduac9yf3yk4vyj2d6m50x2069v5ukvqp4363key0dnfneql6u693" + "mockVault": "osmo1wvxv84qma9py24zs5x969gqepl7j45fjqmxxxzmyf60uqhqpuatsrlph5h", + "swapper": "osmo166t9h8suck5azga0khf7c6t5ehr20lxqdqz4part648fqrllln9q9x2rcv", + "zapper": "osmo1xt35xkqgel45nlxcm546443evzely5juessnshqlttvcz4gx5eqs5etf7y", + "creditManager": "osmo1nxsw7jwwes5js7hq0uac000hu3dx68v8x30e7mzgm6ynv0ujld9s2qupm3", + "accountNft": "osmo1ayf47vcufnnx5y7aksd73nwqzzfmcd48y8pt8xg5kdpypc0lsgdsqkgg83" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 3fbe954b2..3530a9066 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -85,7 +85,7 @@ export class Deployer { } const msg: VaultInstantiateMsg = { - base_token_denom: this.config.testActions.vault.mock.baseToken.denom, + base_token_denom: this.config.testActions.vault.mock.baseToken, oracle: this.config.oracle.addr, vault_token_denom: this.config.testActions.vault.mock.vaultTokenDenom, lockup: this.config.testActions.vault.mock.lockup, @@ -144,14 +144,14 @@ export class Deployer { async instantiateCreditManager() { const msg: RoverInstantiateMsg = { max_unlocking_positions: this.config.maxUnlockingPositions, - allowed_coins: this.config.allowedCoins, - vault_configs: this.config.vaults, + allowed_coins: this.config.allowedCoins.map((c) => c.denom), + vault_configs: this.config.vaults.map((v) => ({ config: v.config, vault: v.vault })), oracle: this.config.oracle.addr, owner: this.deployerAddr, red_bank: this.config.redBank.addr, max_close_factor: this.config.maxCloseFactor, swapper: this.storage.addresses.swapper!, - zapper: this.config.zapper.addr, + zapper: this.storage.addresses.zapper!, } if (this.config.testActions) { @@ -250,35 +250,30 @@ export class Deployer { return } - const { client, addr } = await this.getOutpostsDeployer() - - for (const coin of this.config - .testActions!.zap.coinsIn.map((c) => ({ denom: c.denom, priceSource: c.priceSource })) - .concat(this.config.testActions!.zap.denomOut) - .concat(this.config.testActions!.vault.mock.baseToken)) { + for (const coin of this.config.allowedCoins) { const msg = { set_price_source: { denom: coin.denom, price_source: coin.priceSource as PriceSource, }, } + printBlue(`Setting price source for ${coin.denom}: ${JSON.stringify(coin.priceSource)}`) + const { client, addr } = await this.getOutpostsDeployer() await client.execute(addr, this.config.oracle.addr, msg, 'auto') } this.storage.actions.oraclePricesSet = true } - async setupRedBankMarketsForZapDenoms() { + async setupRedBankMarkets() { if (this.storage.actions.redBankMarketsSet) { printGray('Red bank markets already set') return } + const { client, addr } = await this.getOutpostsDeployer() - for (const denom of this.config - .testActions!.zap.coinsIn.map((c) => c.denom) - .concat(this.config.testActions!.zap.denomOut.denom) - .concat(this.config.testActions!.vault.mock.baseToken.denom)) { + for (const denom of this.config.allowedCoins.map((c) => c.denom)) { try { await client.queryContractSmart(this.config.redBank.addr, { market: { diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 1c686d12d..cfdbc1b15 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -36,7 +36,7 @@ export const taskRunner = async ({ if (config.testActions) { await deployer.grantCreditLines() await deployer.setupOraclePrices() - await deployer.setupRedBankMarketsForZapDenoms() + await deployer.setupRedBankMarkets() const rover = await deployer.newUserRoverClient(config.testActions) await rover.createCreditAccount() diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 9637a5edc..d37c575e0 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -255,7 +255,7 @@ export class Rover { } } - async getLockup(v: VaultInstantiateConfig): Promise { + private async getLockup(v: VaultInstantiateConfig): Promise { try { return await this.cwClient.queryContractSmart(v.vault.address, { vault_extension: { diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 9472c1e7d..1a2edc431 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -3,12 +3,25 @@ import { DeploymentConfig, VaultType } from '../../types/config' const uosmo = 'uosmo' const uatom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2' const udig = 'ibc/307E5C96C8F60D1CBEE269A9A86C0834E1DB06F2B3788AE4F716EDB97A48B97D' +const ujuno = 'ibc/46B44899322F3CD854D2D46DEEF881958467CDD4B3B10086DA49296BBED94BED' const gammPool1 = 'gamm/pool/1' +const gammPool497 = 'gamm/pool/497' -const autoCompoundingVault = 'osmo1v40lnedgvake8p7f49gvqu0q3vc9sx3qpc0jqtyfdyw25d4vg8us38an37' +const vaultOsmoAtom1 = 'osmo1v40lnedgvake8p7f49gvqu0q3vc9sx3qpc0jqtyfdyw25d4vg8us38an37' +const vaultOsmoAtom7 = 'osmo108q2krqr0y9g0rtesenvsw68sap2xefelwwjs0wedyvdl0cmrntqvllfjk' +const vaultOsmoAtom14 = 'osmo1eht92w5dr0vx8dzl6dn9770yq0ycln50zfhzvz8uc6928mp8vvgqwcram9' +const vaultJunoOsmo1 = 'osmo1g5hryv0gp9dzlchkp3yxk8fmcf5asjun6cxkvyffetqzkwmvy75qfmeq3f' +const vaultJunoOsmo7 = 'osmo1jtuvr47taunfdhwrkns0cufwa3qlsz66qwwa9vvn4cc5eltzrtxs4zkaus' +const vaultJunoOsmo14 = 'osmo1rclt7lsfp0c89ydf9umuhwlg28maw6z87jak3ly7u2lefnyzdz2s8gsepe' export const osmosisTestnetConfig: DeploymentConfig = { - allowedCoins: [uosmo, uatom, gammPool1], + allowedCoins: [ + { denom: uosmo, priceSource: { fixed: { price: '1' } } }, + { denom: uatom, priceSource: { arithmetic_twap: { pool_id: 1, window_size: 1800 } } }, + { denom: ujuno, priceSource: { arithmetic_twap: { pool_id: 497, window_size: 1800 } } }, + { denom: gammPool1, priceSource: { xyk_liquidity_token: { pool_id: 1 } } }, + { denom: gammPool497, priceSource: { xyk_liquidity_token: { pool_id: 497 } } }, + ], chain: { baseDenom: uosmo, defaultGasPrice: 0.1, @@ -21,27 +34,71 @@ export const osmosisTestnetConfig: DeploymentConfig = { maxCloseFactor: '0.6', maxUnlockingPositions: '10', maxValueForBurn: '1000000', - // Get the latest addresses from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-4.json - oracle: { - addr: 'osmo1jnkun9gcajn96a4yh7atzkq98c9sm0xrsqk7xtes07ujyn7xh5rqjymxxv', - }, - redBank: { addr: 'osmo18w58j2dlpre6kslls9w88aur5ud8000wvg8pw4fp80p6q97g6qtqvhztpv' }, + // Latest from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-4.json + oracle: { addr: 'osmo1z97d9lvgknwm9h9fmy08jx52yynwce28hd8weuq6t6550n3np2usqunz6a' }, + redBank: { addr: 'osmo1tyg72uru87ws0rldfq723a0fr6qle33etww6uk2545xtf2te7d8s8fmud7' }, swapRoutes: [ { denomIn: uosmo, denomOut: uatom, route: [{ token_out_denom: uatom, pool_id: '1' }] }, { denomIn: uatom, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '1' }] }, + { denomIn: uosmo, denomOut: ujuno, route: [{ token_out_denom: ujuno, pool_id: '497' }] }, + { denomIn: ujuno, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '497' }] }, ], - zapper: { addr: 'osmo150dpk65f6deunksn94xtvu249hnr2hwqe335ukucltlwh3uz87hq898s7q' }, + // Latest from: https://stats.apollo.farm/api/vaults/v1/all vaults: [ { - // https://github.com/apollodao/apollo-config/blob/master/config.json#L114 - vault: { address: autoCompoundingVault }, + vault: { address: vaultOsmoAtom1 }, + config: { + deposit_cap: { denom: 'uosmo', amount: '1000000000' }, // 1000 osmo + liquidation_threshold: '0.75', + max_ltv: '0.65', + whitelisted: true, + }, + }, + { + vault: { address: vaultOsmoAtom7 }, config: { - deposit_cap: { denom: uosmo, amount: '100000000' }, // 100 osmo + deposit_cap: { denom: 'uosmo', amount: '1000000000' }, // 1000 osmo liquidation_threshold: '0.75', max_ltv: '0.65', whitelisted: true, }, }, + { + vault: { address: vaultOsmoAtom14 }, + config: { + deposit_cap: { denom: 'uosmo', amount: '1000000000' }, // 1000 osmo + liquidation_threshold: '0.75', + max_ltv: '0.65', + whitelisted: true, + }, + }, + { + vault: { address: vaultJunoOsmo1 }, + config: { + deposit_cap: { denom: 'uosmo', amount: '500000000' }, // 500 osmo + liquidation_threshold: '0.55', + max_ltv: '0.5', + whitelisted: true, + }, + }, + { + vault: { address: vaultJunoOsmo7 }, + config: { + deposit_cap: { denom: 'uosmo', amount: '500000000' }, // 500 osmo + liquidation_threshold: '0.55', + max_ltv: '0.5', + whitelisted: true, + }, + }, + { + vault: { address: vaultJunoOsmo14 }, + config: { + deposit_cap: { denom: 'uosmo', amount: '500000000' }, // 500 osmo + liquidation_threshold: '0.55', + max_ltv: '0.5', + whitelisted: true, + }, + }, ], testActions: { vault: { @@ -57,7 +114,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { vaultTokenDenom: udig, type: VaultType.LOCKED, lockup: { time: 900 }, // 15 mins - baseToken: { denom: gammPool1, priceSource: { xyk_liquidity_token: { pool_id: 1 } } }, + baseToken: gammPool1, }, }, outpostsDeployerMnemonic: @@ -82,10 +139,13 @@ export const osmosisTestnetConfig: DeploymentConfig = { withdrawAmount: '12', zap: { coinsIn: [ - { denom: uatom, amount: '1', priceSource: { twap: { pool_id: 1, window_size: 1800 } } }, - { denom: uosmo, amount: '3', priceSource: { fixed: { price: '1' } } }, + { + denom: uatom, + amount: '1', + }, + { denom: uosmo, amount: '3' }, ], - denomOut: { denom: gammPool1, priceSource: { xyk_liquidity_token: { pool_id: 1 } } }, + denomOut: gammPool1, }, }, } diff --git a/scripts/types/config.ts b/scripts/types/config.ts index b741025e2..3be420aad 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -23,9 +23,8 @@ export interface DeploymentConfig { deployerMnemonic: string oracle: { addr: string } redBank: { addr: string } - zapper: { addr: string } vaults: VaultInstantiateConfig[] - allowedCoins: string[] + allowedCoins: { denom: string; priceSource: PriceSource }[] maxCloseFactor: string maxValueForBurn: string maxUnlockingPositions: string @@ -48,7 +47,7 @@ export interface TestActions { config: VaultConfig vaultTokenDenom: string lockup?: Duration - baseToken: { denom: string; priceSource: PriceSource } + baseToken: string } } outpostsDeployerMnemonic: string @@ -65,8 +64,8 @@ export interface TestActions { } withdrawAmount: string zap: { - coinsIn: { amount: string; denom: string; priceSource: PriceSource }[] - denomOut: { denom: string; priceSource: PriceSource } + coinsIn: { amount: string; denom: string }[] + denomOut: string } unzapAmount: string } diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index 35007a833..555762256 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -12,12 +12,13 @@ import { InstantiateMsg, CoinMarketInfo, ExecuteMsg, + OwnerUpdate, Uint128, CreateOrUpdateConfig, InitOrUpdateAssetParams, InterestRateModel, QueryMsg, - ConfigForString, + ConfigResponse, Market, ArrayOfMarket, UncollateralizedLoanLimitResponse, @@ -31,7 +32,7 @@ import { } from './MarsMockRedBank.types' export interface MarsMockRedBankReadOnlyInterface { contractAddress: string - config: () => Promise + config: () => Promise market: ({ denom }: { denom: string }) => Promise markets: ({ limit, @@ -123,7 +124,7 @@ export class MarsMockRedBankQueryClient implements MarsMockRedBankReadOnlyInterf this.underlyingDebtAmount = this.underlyingDebtAmount.bind(this) } - config = async (): Promise => { + config = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { config: {}, }) @@ -309,6 +310,16 @@ export class MarsMockRedBankQueryClient implements MarsMockRedBankReadOnlyInterf export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterface { contractAddress: string sender: string + updateOwner: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + updateEmergencyOwner: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise updateConfig: ( { config, @@ -445,6 +456,8 @@ export class MarsMockRedBankClient this.client = client this.sender = sender this.contractAddress = contractAddress + this.updateOwner = this.updateOwner.bind(this) + this.updateEmergencyOwner = this.updateEmergencyOwner.bind(this) this.updateConfig = this.updateConfig.bind(this) this.initAsset = this.initAsset.bind(this) this.updateAsset = this.updateAsset.bind(this) @@ -457,6 +470,38 @@ export class MarsMockRedBankClient this.updateAssetCollateralStatus = this.updateAssetCollateralStatus.bind(this) } + updateOwner = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_owner: {}, + }, + fee, + memo, + funds, + ) + } + updateEmergencyOwner = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_emergency_owner: {}, + }, + fee, + memo, + funds, + ) + } updateConfig = async ( { config, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts index 4b2c8b3ec..ebdeb9317 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts @@ -14,12 +14,13 @@ import { InstantiateMsg, CoinMarketInfo, ExecuteMsg, + OwnerUpdate, Uint128, CreateOrUpdateConfig, InitOrUpdateAssetParams, InterestRateModel, QueryMsg, - ConfigForString, + ConfigResponse, Market, ArrayOfMarket, UncollateralizedLoanLimitResponse, @@ -34,6 +35,8 @@ import { export interface MarsMockRedBankMessage { contractAddress: string sender: string + updateOwner: (funds?: Coin[]) => MsgExecuteContractEncodeObject + updateEmergencyOwner: (funds?: Coin[]) => MsgExecuteContractEncodeObject updateConfig: ( { config, @@ -144,6 +147,8 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { constructor(sender: string, contractAddress: string) { this.sender = sender this.contractAddress = contractAddress + this.updateOwner = this.updateOwner.bind(this) + this.updateEmergencyOwner = this.updateEmergencyOwner.bind(this) this.updateConfig = this.updateConfig.bind(this) this.initAsset = this.initAsset.bind(this) this.updateAsset = this.updateAsset.bind(this) @@ -156,6 +161,36 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { this.updateAssetCollateralStatus = this.updateAssetCollateralStatus.bind(this) } + updateOwner = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_owner: {}, + }), + ), + funds, + }), + } + } + updateEmergencyOwner = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_emergency_owner: {}, + }), + ), + funds, + }), + } + } updateConfig = ( { config, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index 0648c3b4b..48801171c 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -13,12 +13,13 @@ import { InstantiateMsg, CoinMarketInfo, ExecuteMsg, + OwnerUpdate, Uint128, CreateOrUpdateConfig, InitOrUpdateAssetParams, InterestRateModel, QueryMsg, - ConfigForString, + ConfigResponse, Market, ArrayOfMarket, UncollateralizedLoanLimitResponse, @@ -443,12 +444,12 @@ export function useMarsMockRedBankMarketQuery({ ) } export interface MarsMockRedBankConfigQuery - extends MarsMockRedBankReactQuery {} -export function useMarsMockRedBankConfigQuery({ + extends MarsMockRedBankReactQuery {} +export function useMarsMockRedBankConfigQuery({ client, options, }: MarsMockRedBankConfigQuery) { - return useQuery( + return useQuery( marsMockRedBankQueryKeys.config(client?.contractAddress), () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, @@ -692,3 +693,42 @@ export function useMarsMockRedBankUpdateConfigMutation( options, ) } +export interface MarsMockRedBankUpdateEmergencyOwnerMutation { + client: MarsMockRedBankClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsMockRedBankUpdateEmergencyOwnerMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateEmergencyOwner(msg, fee, memo, funds), + options, + ) +} +export interface MarsMockRedBankUpdateOwnerMutation { + client: MarsMockRedBankClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsMockRedBankUpdateOwnerMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateOwner(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index cb4cf4c09..8a083bee5 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -16,6 +16,12 @@ export interface CoinMarketInfo { max_ltv: Decimal } export type ExecuteMsg = + | { + update_owner: OwnerUpdate + } + | { + update_emergency_owner: OwnerUpdate + } | { update_config: { config: CreateOrUpdateConfig @@ -77,11 +83,19 @@ export type ExecuteMsg = enable: boolean } } +export type OwnerUpdate = + | { + propose_new_owner: { + proposed: string + } + } + | 'clear_proposed' + | 'accept_proposed' + | 'abolish_owner_role' export type Uint128 = string export interface CreateOrUpdateConfig { address_provider?: string | null close_factor?: Decimal | null - owner?: string | null } export interface InitOrUpdateAssetParams { borrow_enabled?: boolean | null @@ -182,10 +196,13 @@ export type QueryMsg = denom: string } } -export interface ConfigForString { +export interface ConfigResponse { address_provider: string close_factor: Decimal - owner: string + emergency_owner?: string | null + owner?: string | null + proposed_new_emergency_owner?: string | null + proposed_new_owner?: string | null } export interface Market { borrow_enabled: boolean @@ -235,8 +252,8 @@ export type UserHealthStatus = } export interface UserPositionResponse { health_status: UserHealthStatus - total_collateralized_debt: Decimal - total_enabled_collateral: Decimal - weighted_liquidation_threshold_collateral: Decimal - weighted_max_ltv_collateral: Decimal + total_collateralized_debt: Uint128 + total_enabled_collateral: Uint128 + weighted_liquidation_threshold_collateral: Uint128 + weighted_max_ltv_collateral: Uint128 } diff --git a/scripts/types/priceSource.ts b/scripts/types/priceSource.ts index aae29eb77..0be7c8054 100644 --- a/scripts/types/priceSource.ts +++ b/scripts/types/priceSource.ts @@ -1,7 +1,7 @@ export type PriceSource = | { fixed: { - price: string + price: Decimal } } | { @@ -10,7 +10,15 @@ export type PriceSource = } } | { - twap: { + arithmetic_twap: { + downtime_detector?: DowntimeDetector | null + pool_id: number + window_size: number + } + } + | { + geometric_twap: { + downtime_detector?: DowntimeDetector | null pool_id: number window_size: number } @@ -20,3 +28,37 @@ export type PriceSource = pool_id: number } } + +export type Decimal = string + +export type Downtime = + | 'duration30s' + | 'duration1m' + | 'duration2m' + | 'duration3m' + | 'duration4m' + | 'duration5m' + | 'duration10m' + | 'duration20m' + | 'duration30m' + | 'duration40m' + | 'duration50m' + | 'duration1h' + | 'duration15h' + | 'duration2h' + | 'duration25h' + | 'duration3h' + | 'duration4h' + | 'duration5h' + | 'duration6h' + | 'duration9h' + | 'duration12h' + | 'duration18h' + | 'duration24h' + | 'duration36h' + | 'duration48h' + +export interface DowntimeDetector { + downtime: Downtime + recovery: number +} From 0a490d49d1777f59f6972704c39b9e73e3288b14 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 18 Jan 2023 15:29:11 +0100 Subject: [PATCH 124/218] Redeploy w/ latest oracle/red bank (#97) redeploy --- scripts/deploy/addresses/osmo-test-4.json | 10 +++++----- scripts/deploy/osmosis/config.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index e3ea0ff08..b8f7e0441 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,7 @@ { - "mockVault": "osmo1wvxv84qma9py24zs5x969gqepl7j45fjqmxxxzmyf60uqhqpuatsrlph5h", - "swapper": "osmo166t9h8suck5azga0khf7c6t5ehr20lxqdqz4part648fqrllln9q9x2rcv", - "zapper": "osmo1xt35xkqgel45nlxcm546443evzely5juessnshqlttvcz4gx5eqs5etf7y", - "creditManager": "osmo1nxsw7jwwes5js7hq0uac000hu3dx68v8x30e7mzgm6ynv0ujld9s2qupm3", - "accountNft": "osmo1ayf47vcufnnx5y7aksd73nwqzzfmcd48y8pt8xg5kdpypc0lsgdsqkgg83" + "mockVault": "osmo1xq08h608zlw3l8l3ejkh0w7c8yavxf60syqeylu4vycmysggn2kssdmpws", + "swapper": "osmo1atg4wj5kkc509w6lakd8ksdkf79256uz8ddtx85s60hf3zh6mq5qatv3kx", + "zapper": "osmo1sdr0h3da08mqrpjwyq5c7cutxe2snk3e97eknrk5tphqjvnc45pqpk808t", + "creditManager": "osmo1smkdahl85qzyd2gpfknmw78y54c8wqkm2whvq9fc5mu20hefz7hsc8ee4v", + "accountNft": "osmo1f3vvrrzndf7dr33m7hg6jaz38fyja9e88f8r6xufjmxppwpl35kq8sxr8l" } diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 1a2edc431..6087356f6 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -35,8 +35,8 @@ export const osmosisTestnetConfig: DeploymentConfig = { maxUnlockingPositions: '10', maxValueForBurn: '1000000', // Latest from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-4.json - oracle: { addr: 'osmo1z97d9lvgknwm9h9fmy08jx52yynwce28hd8weuq6t6550n3np2usqunz6a' }, - redBank: { addr: 'osmo1tyg72uru87ws0rldfq723a0fr6qle33etww6uk2545xtf2te7d8s8fmud7' }, + oracle: { addr: 'osmo1dqz2u3c8rs5e7w5fnchsr2mpzzsxew69wtdy0aq4jsd76w7upmsstqe0s8' }, + redBank: { addr: 'osmo1t0dl6r27phqetfu0geaxrng0u9zn8qgrdwztapt5xr32adtwptaq6vwg36' }, swapRoutes: [ { denomIn: uosmo, denomOut: uatom, route: [{ token_out_denom: uatom, pool_id: '1' }] }, { denomIn: uatom, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '1' }] }, From 11925c18e4a4d164169bb70218a7cc7e4db103e8 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 18 Jan 2023 21:21:34 +0100 Subject: [PATCH 125/218] Simplify vault pricing method (#98) * simplify vault pricing method * redeploy --- packages/rover/src/adapters/vault/base.rs | 6 +----- scripts/deploy/addresses/osmo-test-4.json | 10 +++++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/rover/src/adapters/vault/base.rs b/packages/rover/src/adapters/vault/base.rs index 7c4c6fe61..ba54ffd44 100644 --- a/packages/rover/src/adapters/vault/base.rs +++ b/packages/rover/src/adapters/vault/base.rs @@ -241,11 +241,7 @@ impl Vault { if total_supply.is_zero() { return Ok(Uint128::zero()); }; - - let total_underlying = self.query_preview_redeem(querier, total_supply)?; - let amount_in_underlying = amount - .checked_multiply_ratio(total_underlying, total_supply) - .map_err(|_| StdError::generic_err("CheckedMultiplyRatioError"))?; + let amount_in_underlying = self.query_preview_redeem(querier, amount)?; let vault_info = self.query_info(querier)?; let price_res = oracle.query_price(querier, &vault_info.base_token)?; let amount_value = amount_in_underlying diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index b8f7e0441..4e9018999 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,7 @@ { - "mockVault": "osmo1xq08h608zlw3l8l3ejkh0w7c8yavxf60syqeylu4vycmysggn2kssdmpws", - "swapper": "osmo1atg4wj5kkc509w6lakd8ksdkf79256uz8ddtx85s60hf3zh6mq5qatv3kx", - "zapper": "osmo1sdr0h3da08mqrpjwyq5c7cutxe2snk3e97eknrk5tphqjvnc45pqpk808t", - "creditManager": "osmo1smkdahl85qzyd2gpfknmw78y54c8wqkm2whvq9fc5mu20hefz7hsc8ee4v", - "accountNft": "osmo1f3vvrrzndf7dr33m7hg6jaz38fyja9e88f8r6xufjmxppwpl35kq8sxr8l" + "mockVault": "osmo12wa5m60x0s3kdsr2qz2u2zw3afesqtlz688lq4avjhw4mmf4uu4q0ctwvc", + "swapper": "osmo1223jlew7m75xxpan25v0057q27z66tytppkehlcfae5zp6wsjlpq5lj8ql", + "zapper": "osmo1dzxd98pu3syx8n6nwxkugg3rxk4tcta46g88dl8xe76d7vsen8fs37k9kv", + "creditManager": "osmo1djlx4lcd70jrpdtfnk9srjzgpzw7xw6jc32fj9qyqvjxyrlc80cswqhhfa", + "accountNft": "osmo1drnjmpaplz4lhrw99qd4ykjlk3f2qrp6xuhj87jp6jv4zkurcjvqr25gmk" } From 2008338fc07832077fee4b30e92029fc5ecb8194 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 19 Jan 2023 21:44:54 +0100 Subject: [PATCH 126/218] Validate no duplicate vault tokens in config [oak audit] (#101) more vault validation logic --- contracts/credit-manager/src/instantiate.rs | 25 +++++++- contracts/credit-manager/src/update_config.rs | 2 +- .../credit-manager/tests/helpers/mock_env.rs | 16 ++--- .../credit-manager/tests/test_instantiate.rs | 60 +++++++++++++++++-- .../tests/test_update_config.rs | 59 ++++++++++++------ 5 files changed, 123 insertions(+), 39 deletions(-) diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index e0eac027d..b50ee9608 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use cosmwasm_std::{Decimal, DepsMut}; +use cosmwasm_std::{Api, Decimal, DepsMut, QuerierWrapper, StdResult}; use mars_owner::OwnerInit::SetInitialOwner; use mars_rover::{ error::{ContractError::InvalidConfig, ContractResult}, @@ -30,7 +30,7 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { assert_lte_to_one(&msg.max_close_factor)?; MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; - assert_no_duplicate_vaults(&msg.vault_configs)?; + assert_no_duplicate_vaults(deps.api, &deps.querier, &msg.vault_configs)?; msg.vault_configs.iter().try_for_each(|v| -> ContractResult<_> { v.config.check()?; let vault = v.vault.check(deps.api)?; @@ -45,13 +45,32 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { Ok(()) } -pub fn assert_no_duplicate_vaults(vaults: &[VaultInstantiateConfig]) -> ContractResult<()> { +pub fn assert_no_duplicate_vaults( + api: &dyn Api, + querier: &QuerierWrapper, + vaults: &[VaultInstantiateConfig], +) -> ContractResult<()> { let set: HashSet<_> = vaults.iter().map(|v| v.vault.address.clone()).collect(); if set.len() != vaults.len() { return Err(InvalidConfig { reason: "Duplicate vault configs present".to_string(), }); } + + let set: HashSet<_> = vaults + .iter() + .map(|v| { + let vault = v.vault.check(api)?; + let info = vault.query_info(querier)?; + Ok(info.vault_token) + }) + .collect::>()?; + if set.len() != vaults.len() { + return Err(InvalidConfig { + reason: "Multiple vaults share the same vault token".to_string(), + }); + } + Ok(()) } diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index 6d725a52d..7582726a6 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -53,7 +53,7 @@ pub fn update_config( } if let Some(configs) = new_config.vault_configs { - assert_no_duplicate_vaults(&configs)?; + assert_no_duplicate_vaults(deps.api, &deps.querier, &configs)?; VAULT_CONFIGS.clear(deps.storage); configs.iter().try_for_each(|v| -> ContractResult<_> { v.config.check()?; diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 668802991..a5dcdf244 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -836,16 +836,12 @@ impl MockEnvBuilder { self } - pub fn pre_deployed_vault(&mut self, address: &str, info: &VaultTestInfo) -> &mut Self { - let config = VaultInstantiateConfig { - vault: VaultBase::new(address.to_string()), - config: VaultConfig { - deposit_cap: info.deposit_cap.clone(), - max_ltv: info.max_ltv, - liquidation_threshold: info.liquidation_threshold, - whitelisted: true, - }, - }; + pub fn pre_deployed_vault( + &mut self, + info: &VaultTestInfo, + config: Option, + ) -> &mut Self { + let config = config.unwrap_or(self.deploy_vault(info)); let new_list = match self.pre_deployed_vaults.clone() { None => Some(vec![config]), Some(mut curr) => { diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 0fb57f813..3c4112421 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -1,4 +1,8 @@ use cosmwasm_std::{coin, Decimal}; +use mars_rover::{ + adapters::vault::{VaultBase, VaultConfig}, + msg::instantiate::VaultInstantiateConfig, +}; use crate::helpers::{ assert_contents_equal, locked_vault_info, uatom_info, ujake_info, unlocked_vault_info, @@ -70,7 +74,22 @@ fn test_vault_configs_set_on_instantiate() { #[test] fn test_raises_on_invalid_vaults_addr() { - let mock = MockEnv::new().pre_deployed_vault("%%%INVALID%%%", &unlocked_vault_info()).build(); + let mock = MockEnv::new() + .pre_deployed_vault( + &unlocked_vault_info(), + Some(VaultInstantiateConfig { + vault: VaultBase { + address: "%%%INVALID%%%".to_string(), + }, + config: VaultConfig { + deposit_cap: Default::default(), + max_ltv: Default::default(), + liquidation_threshold: Default::default(), + whitelisted: false, + }, + }), + ) + .build(); if mock.is_ok() { panic!("Should have thrown an error"); @@ -78,10 +97,9 @@ fn test_raises_on_invalid_vaults_addr() { } #[test] -fn test_raises_on_invalid_vaults_config() { +fn test_instantiate_raises_on_invalid_vaults_config() { let mock = MockEnv::new() .pre_deployed_vault( - "addr_123", &VaultTestInfo { vault_token_denom: "uleverage".to_string(), lockup: None, @@ -90,6 +108,7 @@ fn test_raises_on_invalid_vaults_config() { liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), base_token_denom: "lp_denom_123".to_string(), }, + None, ) .build(); @@ -99,7 +118,6 @@ fn test_raises_on_invalid_vaults_config() { let mock = MockEnv::new() .pre_deployed_vault( - "addr_123", &VaultTestInfo { vault_token_denom: "uleverage".to_string(), lockup: None, @@ -108,19 +126,49 @@ fn test_raises_on_invalid_vaults_config() { liquidation_threshold: Decimal::from_atomics(9u128, 0).unwrap(), base_token_denom: "lp_denom_123".to_string(), }, + None, ) .build(); if mock.is_ok() { panic!("Should have thrown an error: liquidation_threshold > 1"); } + + let mock = MockEnv::new() + .pre_deployed_vault( + &VaultTestInfo { + vault_token_denom: "uleverage".to_string(), + lockup: None, + deposit_cap: coin(10_000_000, "uusdc"), + max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(9u128, 0).unwrap(), + base_token_denom: "lp_denom_123".to_string(), + }, + None, + ) + .pre_deployed_vault( + &VaultTestInfo { + vault_token_denom: "uleverage".to_string(), + lockup: None, + deposit_cap: coin(10_000_000, "uusdc"), + max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), + base_token_denom: "xyz".to_string(), + }, + None, + ) + .build(); + + if mock.is_ok() { + panic!("Should have thrown an error: duplicate vault token denoms"); + } } #[test] fn test_duplicate_vaults_raises() { let mock = MockEnv::new() - .pre_deployed_vault("addr_123", &locked_vault_info()) - .pre_deployed_vault("addr_123", &unlocked_vault_info()) + .pre_deployed_vault(&locked_vault_info(), None) + .pre_deployed_vault(&unlocked_vault_info(), None) .build(); if mock.is_ok() { panic!("Should have thrown an error"); diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index b6f32516f..804df207c 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -51,6 +51,13 @@ fn test_only_owner_can_update_config() { fn test_raises_on_invalid_vaults_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); + + let mut vault_config = deploy_vault(&mut mock.app); + + // Invalid config. Max LTV should be lower than liquidation threshold. + vault_config.config.max_ltv = Decimal::from_atomics(8u128, 1).unwrap(); + vault_config.config.liquidation_threshold = Decimal::from_atomics(7u128, 1).unwrap(); + let res = mock.update_config( &Addr::unchecked(original_config.owner.clone().unwrap()), ConfigUpdates { @@ -60,15 +67,7 @@ fn test_raises_on_invalid_vaults_config() { max_close_factor: None, max_unlocking_positions: None, swapper: None, - vault_configs: Some(vec![VaultInstantiateConfig { - vault: VaultBase::new("vault_123".to_string()), - config: VaultConfig { - deposit_cap: coin(10_000_000, "uusdc"), - max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), - whitelisted: true, - }, - }]), + vault_configs: Some(vec![vault_config]), zapper: None, }, ); @@ -80,8 +79,13 @@ fn test_raises_on_invalid_vaults_config() { }, ); + let mut vault_config = deploy_vault(&mut mock.app); + + // Invalid config. Liquidation threshold should be <= 1. + vault_config.config.liquidation_threshold = Decimal::from_atomics(9u128, 0).unwrap(); + let res = mock.update_config( - &Addr::unchecked(original_config.owner.unwrap()), + &Addr::unchecked(original_config.owner.clone().unwrap()), ConfigUpdates { account_nft: None, allowed_coins: None, @@ -89,15 +93,7 @@ fn test_raises_on_invalid_vaults_config() { max_close_factor: None, max_unlocking_positions: None, swapper: None, - vault_configs: Some(vec![VaultInstantiateConfig { - vault: VaultBase::new("vault_123".to_string()), - config: VaultConfig { - deposit_cap: coin(10_000_000, "uusdc"), - max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(9u128, 0).unwrap(), - whitelisted: true, - }, - }]), + vault_configs: Some(vec![vault_config]), zapper: None, }, ); @@ -108,6 +104,31 @@ fn test_raises_on_invalid_vaults_config() { reason: "max ltv or liquidation threshold are invalid".to_string(), }, ); + + // Duplicate vault tokens + let vault_a = deploy_vault(&mut mock.app); + let vault_b = deploy_vault(&mut mock.app); + + let res = mock.update_config( + &Addr::unchecked(original_config.owner.unwrap()), + ConfigUpdates { + account_nft: None, + allowed_coins: None, + oracle: None, + max_close_factor: None, + max_unlocking_positions: None, + swapper: None, + vault_configs: Some(vec![vault_a, vault_b]), + zapper: None, + }, + ); + + assert_err( + res, + InvalidConfig { + reason: "Multiple vaults share the same vault token".to_string(), + }, + ); } #[test] From 2ac027711a458bdaba8ab5ea858a7846c422a5e7 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 19 Jan 2023 21:45:15 +0100 Subject: [PATCH 127/218] Lowering max LTV for delisted vaults [Oak audit] (#99) Lowering max LTV for delisted vaults --- contracts/credit-manager/src/health.rs | 27 ++++-- contracts/credit-manager/src/vault/utils.rs | 15 ++-- .../credit-manager/tests/helpers/builders.rs | 1 + .../tests/helpers/mock_entity_info.rs | 1 + .../credit-manager/tests/helpers/mock_env.rs | 2 +- .../credit-manager/tests/helpers/types.rs | 1 + .../tests/test_fields_vault_limit.rs | 1 + contracts/credit-manager/tests/test_health.rs | 90 ++++++++++++++++++- .../credit-manager/tests/test_instantiate.rs | 5 ++ .../tests/test_utilization_query.rs | 2 + .../credit-manager/tests/test_vault_enter.rs | 24 +++-- 11 files changed, 148 insertions(+), 21 deletions(-) diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 84680ba74..e462cc0bf 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -12,6 +12,7 @@ use mars_rover::{ use crate::{ query::query_positions, state::{ALLOWED_COINS, ORACLE, RED_BANK, VAULT_CONFIGS}, + vault::vault_is_whitelisted, }; /// Used as storage when trying to compute Health @@ -98,15 +99,23 @@ fn calculate_vaults_value( total_collateral_value = total_collateral_value.checked_add(vault_coin_value)?; let config = VAULT_CONFIGS.load(deps.storage, &v.vault.address)?; + let info = v.vault.query_info(&deps.querier)?; + + // If vault has been de-listed, drop MaxLTV to zero + let checked_vault_max_ltv = if vault_is_whitelisted(deps.storage, &v.vault)? { + config.max_ltv + } else { + Decimal::zero() + }; + max_ltv_adjusted_collateral = vault_coin_value - .checked_mul_floor(config.max_ltv)? + .checked_mul_floor(checked_vault_max_ltv)? .checked_add(max_ltv_adjusted_collateral)?; liquidation_threshold_adjusted_collateral = vault_coin_value .checked_mul_floor(config.liquidation_threshold)? .checked_add(liquidation_threshold_adjusted_collateral)?; // Unlocking positions denominated in underlying token - let info = v.vault.query_info(&deps.querier)?; let PriceResponse { price, .. @@ -116,11 +125,19 @@ fn calculate_vaults_value( liquidation_threshold, .. } = red_bank.query_market(&deps.querier, &info.base_token)?; + + // If base token has been de-listed, drop MaxLTV to zero + let checked_base_max_ltv = if ALLOWED_COINS.contains(deps.storage, &info.base_token) { + max_loan_to_value + } else { + Decimal::zero() + }; + for u in v.amount.unlocking().positions() { let underlying_value = u.coin.amount.checked_mul_floor(price)?; total_collateral_value = total_collateral_value.checked_add(underlying_value)?; max_ltv_adjusted_collateral = underlying_value - .checked_mul_floor(max_loan_to_value)? + .checked_mul_floor(checked_base_max_ltv)? .checked_add(max_ltv_adjusted_collateral)?; liquidation_threshold_adjusted_collateral = underlying_value .checked_mul_floor(liquidation_threshold)? @@ -154,12 +171,12 @@ fn calculate_deposits_value(deps: &Deps, deposits: &[Coin]) -> ContractResult ContractResult<()> { - let config = VAULT_CONFIGS - .may_load(storage, &vault.address)? - .and_then(|config| config.whitelisted.then_some(true)); - if config.is_none() { +pub fn assert_vault_is_whitelisted(storage: &dyn Storage, vault: &Vault) -> ContractResult<()> { + let is_whitelisted = vault_is_whitelisted(storage, vault)?; + if !is_whitelisted { return Err(ContractError::NotWhitelisted(vault.address.to_string())); } Ok(()) } +pub fn vault_is_whitelisted(storage: &dyn Storage, vault: &Vault) -> ContractResult { + let config = VAULT_CONFIGS + .may_load(storage, &vault.address)? + .and_then(|config| config.whitelisted.then_some(true)); + Ok(config.is_some()) +} + pub fn assert_under_max_unlocking_limit( storage: &mut dyn Storage, account_id: &str, diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index 0b8ec87ab..da1ac7696 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -26,6 +26,7 @@ pub fn build_mock_vaults(count: usize) -> Vec { deposit_cap: coin(10000000, "uusdc"), max_ltv: lp_token.max_ltv, liquidation_threshold: lp_token.liquidation_threshold, + whitelisted: true, } }) .collect() diff --git a/contracts/credit-manager/tests/helpers/mock_entity_info.rs b/contracts/credit-manager/tests/helpers/mock_entity_info.rs index 64f2b2570..efea10ae4 100644 --- a/contracts/credit-manager/tests/helpers/mock_entity_info.rs +++ b/contracts/credit-manager/tests/helpers/mock_entity_info.rs @@ -59,5 +59,6 @@ pub fn generate_mock_vault(lockup: Option) -> VaultTestInfo { deposit_cap: coin(10_000_000, "uusdc"), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + whitelisted: true, } } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index a5dcdf244..1af1debeb 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -699,7 +699,7 @@ impl MockEnvBuilder { deposit_cap: vault.deposit_cap.clone(), max_ltv: vault.max_ltv, liquidation_threshold: vault.liquidation_threshold, - whitelisted: true, + whitelisted: vault.whitelisted, }, } } diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 3049943a8..1896e4f46 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -35,6 +35,7 @@ pub struct VaultTestInfo { pub deposit_cap: Coin, pub max_ltv: Decimal, pub liquidation_threshold: Decimal, + pub whitelisted: bool, } impl CoinInfo { diff --git a/contracts/credit-manager/tests/test_fields_vault_limit.rs b/contracts/credit-manager/tests/test_fields_vault_limit.rs index 1e5c1c2ec..6353fa83e 100644 --- a/contracts/credit-manager/tests/test_fields_vault_limit.rs +++ b/contracts/credit-manager/tests/test_fields_vault_limit.rs @@ -29,6 +29,7 @@ fn test_can_only_have_a_single_vault_position() { deposit_cap: coin(10_000_000, "uusdc"), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + whitelisted: true, }; let user = Addr::unchecked("user"); diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index ae6a7681f..ad4e9d60d 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -5,16 +5,18 @@ use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use mars_math::{FractionMath, Fractional}; use mars_mock_oracle::msg::CoinPrice; use mars_rover::{ + adapters::vault::VaultConfig, error::ContractError, msg::{ - execute::Action::{Borrow, Deposit}, - instantiate::ConfigUpdates, + execute::Action::{Borrow, Deposit, EnterVault}, + instantiate::{ConfigUpdates, VaultInstantiateConfig}, query::DebtAmount, }, }; use crate::helpers::{ - assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv, + assert_err, lp_token_info, uatom_info, ujake_info, unlocked_vault_info, uosmo_info, + AccountToFund, CoinInfo, MockEnv, }; pub mod helpers; @@ -611,7 +613,7 @@ fn test_debt_value() { } #[test] -fn test_delisted_assets_drop_max_ltv() { +fn test_delisted_deposits_drop_max_ltv() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); @@ -667,6 +669,86 @@ fn test_delisted_assets_drop_max_ltv() { assert_eq!(curr_health.max_ltv_health_factor, Some(Decimal::raw(811881188118811881u128))); } +#[test] +fn test_delisted_vaults_drop_max_ltv() { + let lp_token = lp_token_info(); + let leverage_vault = unlocked_vault_info(); + let atom = uatom_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone()]) + .vault_configs(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + Borrow(atom.to_coin(100)), + EnterVault { + vault, + coin: lp_token.to_action_coin(200), + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + let prev_health = mock.query_health(&account_id); + + let vault_configs = mock.query_vault_configs(None, None); + let v = vault_configs.first().unwrap(); + let new_vault_config = VaultInstantiateConfig { + vault: v.vault.clone(), + config: VaultConfig { + deposit_cap: v.config.deposit_cap.clone(), + max_ltv: v.config.max_ltv, + liquidation_threshold: v.config.liquidation_threshold, + whitelisted: false, + }, + }; + + // Remove uosmo from the coin whitelist + let res = mock.query_config(); + mock.update_config( + &Addr::unchecked(res.owner.unwrap()), + ConfigUpdates { + vault_configs: Some(vec![new_vault_config]), + ..Default::default() + }, + ) + .unwrap(); + + let curr_health = mock.query_health(&account_id); + + // // Values should be the same + assert_eq!(prev_health.total_debt_value, curr_health.total_debt_value); + assert_eq!(prev_health.total_collateral_value, curr_health.total_collateral_value); + + assert_eq!(prev_health.liquidation_health_factor, curr_health.liquidation_health_factor); + assert_eq!( + prev_health.liquidation_threshold_adjusted_collateral, + curr_health.liquidation_threshold_adjusted_collateral + ); + assert_eq!(prev_health.liquidatable, curr_health.liquidatable); + + // Should have been changed due to de-listing + assert_ne!(prev_health.above_max_ltv, curr_health.above_max_ltv); + assert_ne!(prev_health.max_ltv_adjusted_collateral, curr_health.max_ltv_adjusted_collateral); + assert_ne!(prev_health.max_ltv_health_factor, curr_health.max_ltv_health_factor); + assert_eq!(curr_health.max_ltv_health_factor, Some(Decimal::raw(811881188118811881u128))); +} + fn find_by_denom<'a>(denom: &'a str, shares: &'a [DebtAmount]) -> &'a DebtAmount { shares.iter().find(|item| item.denom == *denom).unwrap() } diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 3c4112421..fef0dce3f 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -45,6 +45,7 @@ fn test_vault_configs_set_on_instantiate() { deposit_cap: coin(1_000_000, "uusdc"), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + whitelisted: true, }, VaultTestInfo { vault_token_denom: "vault_contract_2".to_string(), @@ -53,6 +54,7 @@ fn test_vault_configs_set_on_instantiate() { deposit_cap: coin(1_000_000, "uusdc"), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + whitelisted: true, }, VaultTestInfo { vault_token_denom: "vault_contract_3".to_string(), @@ -61,6 +63,7 @@ fn test_vault_configs_set_on_instantiate() { deposit_cap: coin(1_000_000, "uusdc"), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + whitelisted: true, }, ]; @@ -107,6 +110,7 @@ fn test_instantiate_raises_on_invalid_vaults_config() { max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), base_token_denom: "lp_denom_123".to_string(), + whitelisted: true, }, None, ) @@ -125,6 +129,7 @@ fn test_instantiate_raises_on_invalid_vaults_config() { max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 0).unwrap(), base_token_denom: "lp_denom_123".to_string(), + whitelisted: true, }, None, ) diff --git a/contracts/credit-manager/tests/test_utilization_query.rs b/contracts/credit-manager/tests/test_utilization_query.rs index 144ea661c..45429f992 100644 --- a/contracts/credit-manager/tests/test_utilization_query.rs +++ b/contracts/credit-manager/tests/test_utilization_query.rs @@ -39,6 +39,7 @@ fn test_utilization_if_cap_is_base_denom() { deposit_cap: base_info.to_coin(100), max_ltv: Default::default(), liquidation_threshold: Default::default(), + whitelisted: true, }; let mut mock = MockEnv::new() @@ -96,6 +97,7 @@ fn test_utilization_in_other_denom() { deposit_cap: osmo_info.to_coin(50_000_000), max_ltv: Default::default(), liquidation_threshold: Default::default(), + whitelisted: true, }; let user = Addr::unchecked("user"); diff --git a/contracts/credit-manager/tests/test_vault_enter.rs b/contracts/credit-manager/tests/test_vault_enter.rs index 0846778af..a5d8f7dc5 100644 --- a/contracts/credit-manager/tests/test_vault_enter.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -1,4 +1,6 @@ -use cosmwasm_std::{Addr, OverflowError, OverflowOperation::Sub, StdError::NotFound, Uint128}; +use cosmwasm_std::{ + coin, Addr, Decimal, OverflowError, OverflowOperation::Sub, StdError::NotFound, Uint128, +}; use mars_mock_vault::contract::STARTING_VAULT_SHARES; use mars_rover::{ adapters::vault::VaultBase, @@ -11,7 +13,7 @@ use mars_rover::{ use crate::helpers::{ assert_err, locked_vault_info, lp_token_info, uatom_info, unlocked_vault_info, uosmo_info, - AccountToFund, MockEnv, + AccountToFund, MockEnv, VaultTestInfo, }; pub mod helpers; @@ -74,28 +76,38 @@ fn test_deposit_denom_is_whitelisted() { fn test_vault_is_whitelisted() { let uatom = uatom_info(); let uosmo = uosmo_info(); - let leverage_vault = unlocked_vault_info(); + let leverage_vault = VaultTestInfo { + vault_token_denom: "uleverage".to_string(), + lockup: None, + base_token_denom: uatom.denom.clone(), + deposit_cap: coin(10_000_000, "uusdc"), + max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + whitelisted: false, + }; let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .allowed_coins(&[uatom.clone(), uosmo]) - .vault_configs(&[leverage_vault]) + .vault_configs(&[leverage_vault.clone()]) .build() .unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); + let vault = mock.get_vault(&leverage_vault); + let res = mock.update_credit_account( &account_id, &user, vec![EnterVault { - vault: VaultBase::new("unknown_vault".to_string()), + vault: vault.clone(), coin: uatom.to_action_coin(200), }], &[], ); - assert_err(res, ContractError::NotWhitelisted("unknown_vault".to_string())); + assert_err(res, ContractError::NotWhitelisted(vault.address)); } #[test] From 0f78c10f8ea7522437c7eae7b4332d84ea5d073f Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 19 Jan 2023 22:16:39 +0100 Subject: [PATCH 128/218] Allow Rover to update nft config [Oak audit] (#100) * Add nft config update msg to rover * build passes * migrate messages to rover * add nft config tests * update scripts --- Cargo.lock | 2 + contracts/account-nft/examples/schema.rs | 2 +- contracts/account-nft/src/contract.rs | 8 +- contracts/account-nft/src/execute.rs | 16 +- contracts/account-nft/src/lib.rs | 3 - contracts/account-nft/src/query.rs | 8 +- contracts/account-nft/src/state.rs | 5 +- .../account-nft/tests/helpers/mock_env.rs | 23 +- .../tests/helpers/mock_env_builder.rs | 11 +- .../account-nft/tests/test_burn_allowance.rs | 5 +- contracts/account-nft/tests/test_mint.rs | 9 +- .../account-nft/tests/test_proposed_minter.rs | 2 +- .../account-nft/tests/test_update_config.rs | 6 +- contracts/credit-manager/Cargo.toml | 2 +- contracts/credit-manager/src/contract.rs | 9 +- contracts/credit-manager/src/execute.rs | 2 +- contracts/credit-manager/src/update_config.rs | 42 ++- .../credit-manager/tests/helpers/mock_env.rs | 40 +- .../credit-manager/tests/test_instantiate.rs | 2 + .../tests/test_update_nft_config.rs | 107 ++++++ packages/rover/Cargo.toml | 2 + .../rover/src/adapters/account_nft}/error.rs | 0 .../rover/src/adapters/account_nft/mod.rs | 5 + .../src/adapters/account_nft}/msg/execute.rs | 4 +- .../adapters/account_nft}/msg/instantiate.rs | 0 .../src/adapters/account_nft}/msg/mod.rs | 0 .../src/adapters/account_nft}/msg/query.rs | 2 +- .../src/adapters/account_nft/nft_config.rs | 12 +- packages/rover/src/adapters/mod.rs | 1 + packages/rover/src/msg/execute.rs | 11 +- .../mars-account-nft/mars-account-nft.json | 48 +-- .../mars-credit-manager.json | 48 ++- scripts/deploy/addresses/osmo-test-4.json | 10 +- scripts/deploy/base/deployer.ts | 2 +- scripts/deploy/base/rover.ts | 4 +- scripts/package.json | 20 +- .../mars-account-nft/MarsAccountNft.client.ts | 12 +- .../MarsAccountNft.message-composer.ts | 8 +- .../MarsAccountNft.react-query.ts | 12 +- .../mars-account-nft/MarsAccountNft.types.ts | 6 +- .../MarsCreditManager.client.ts | 45 ++- .../MarsCreditManager.message-composer.ts | 44 ++- .../MarsCreditManager.react-query.ts | 26 +- .../MarsCreditManager.types.ts | 11 +- scripts/yarn.lock | 352 +++++++++--------- 45 files changed, 653 insertions(+), 336 deletions(-) create mode 100644 contracts/credit-manager/tests/test_update_nft_config.rs rename {contracts/account-nft/src => packages/rover/src/adapters/account_nft}/error.rs (100%) create mode 100644 packages/rover/src/adapters/account_nft/mod.rs rename {contracts/account-nft/src => packages/rover/src/adapters/account_nft}/msg/execute.rs (97%) rename {contracts/account-nft/src => packages/rover/src/adapters/account_nft}/msg/instantiate.rs (100%) rename {contracts/account-nft/src => packages/rover/src/adapters/account_nft}/msg/mod.rs (100%) rename {contracts/account-nft/src => packages/rover/src/adapters/account_nft}/msg/query.rs (98%) rename contracts/account-nft/src/config.rs => packages/rover/src/adapters/account_nft/nft_config.rs (64%) diff --git a/Cargo.lock b/Cargo.lock index f96787fe5..4bd84fa29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1153,6 +1153,8 @@ dependencies = [ "cosmwasm-vault-standard", "cw-storage-plus 1.0.1", "cw-utils 0.16.0", + "cw721", + "cw721-base", "mars-health", "mars-math", "mars-outpost", diff --git a/contracts/account-nft/examples/schema.rs b/contracts/account-nft/examples/schema.rs index b41c869ad..ccb459acd 100644 --- a/contracts/account-nft/examples/schema.rs +++ b/contracts/account-nft/examples/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use mars_account_nft::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_rover::adapters::account_nft::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 1e70b379f..72264749b 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -8,12 +8,12 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw721::ContractInfoResponse; use cw721_base::Cw721Contract; +use mars_rover::adapters::account_nft::{ + ContractError, ExecuteMsg, InstantiateMsg, NftConfig, QueryMsg, +}; use crate::{ - config::Config, - error::ContractError, execute::{accept_minter_role, burn, mint, update_config}, - msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, query::{query_config, query_next_id}, state::{CONFIG, NEXT_ID}, }; @@ -37,7 +37,7 @@ pub fn instantiate( CONFIG.save( deps.storage, - &Config { + &NftConfig { max_value_for_burn: msg.max_value_for_burn, proposed_new_minter: None, }, diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index 526e51705..c1cdb27f4 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -4,15 +4,17 @@ use cosmwasm_std::{ use cw721::Cw721Execute; use cw721_base::MintMsg; use mars_health::HealthResponse; -use mars_rover::msg::QueryMsg::Health; - -use crate::{ - config::ConfigUpdates, - contract::Parent, - error::{ +use mars_rover::{ + adapters::account_nft::{ ContractError, ContractError::{BaseError, BurnNotAllowed}, + NftConfigUpdates, }, + msg::QueryMsg::Health, +}; + +use crate::{ + contract::Parent, state::{CONFIG, NEXT_ID}, }; @@ -66,7 +68,7 @@ pub fn burn( pub fn update_config( deps: DepsMut, info: MessageInfo, - updates: ConfigUpdates, + updates: NftConfigUpdates, ) -> Result { let current_minter = Parent::default().minter.load(deps.storage)?; if info.sender != current_minter { diff --git a/contracts/account-nft/src/lib.rs b/contracts/account-nft/src/lib.rs index 3d18ee96c..b36849240 100644 --- a/contracts/account-nft/src/lib.rs +++ b/contracts/account-nft/src/lib.rs @@ -1,7 +1,4 @@ -pub mod config; pub mod contract; -pub mod error; pub mod execute; -pub mod msg; pub mod query; pub mod state; diff --git a/contracts/account-nft/src/query.rs b/contracts/account-nft/src/query.rs index 11696799f..12d4102d4 100644 --- a/contracts/account-nft/src/query.rs +++ b/contracts/account-nft/src/query.rs @@ -1,11 +1,9 @@ use cosmwasm_std::{Deps, StdResult}; +use mars_rover::adapters::account_nft::UncheckedNftConfig; -use crate::{ - config::UncheckedConfig, - state::{CONFIG, NEXT_ID}, -}; +use crate::state::{CONFIG, NEXT_ID}; -pub fn query_config(deps: Deps) -> StdResult { +pub fn query_config(deps: Deps) -> StdResult { Ok(CONFIG.load(deps.storage)?.into()) } diff --git a/contracts/account-nft/src/state.rs b/contracts/account-nft/src/state.rs index a911fc64d..cc685680e 100644 --- a/contracts/account-nft/src/state.rs +++ b/contracts/account-nft/src/state.rs @@ -1,6 +1,5 @@ use cw_storage_plus::Item; +use mars_rover::adapters::account_nft::NftConfig; -use crate::config::Config; - -pub const CONFIG: Item = Item::new("config"); +pub const CONFIG: Item = Item::new("config"); pub const NEXT_ID: Item = Item::new("next_id"); diff --git a/contracts/account-nft/tests/helpers/mock_env.rs b/contracts/account-nft/tests/helpers/mock_env.rs index d2a2fe58a..4189b9d68 100644 --- a/contracts/account-nft/tests/helpers/mock_env.rs +++ b/contracts/account-nft/tests/helpers/mock_env.rs @@ -2,16 +2,13 @@ use anyhow::Result as AnyResult; use cosmwasm_std::Addr; use cw721::OwnerOfResponse; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; -use mars_account_nft::{ - config::{ConfigUpdates, UncheckedConfig}, - msg::{ - ExecuteMsg as ExtendedExecuteMsg, - ExecuteMsg::{AcceptMinterRole, UpdateConfig}, - QueryMsg, - }, -}; use mars_health::HealthResponse; use mars_mock_credit_manager::msg::ExecuteMsg::SetHealthResponse; +use mars_rover::adapters::account_nft::{ + ExecuteMsg, + ExecuteMsg::{AcceptMinterRole, UpdateConfig}, + NftConfigUpdates, QueryMsg, UncheckedNftConfig, +}; use crate::helpers::MockEnvBuilder; @@ -33,7 +30,7 @@ impl MockEnv { } } - pub fn query_config(&mut self) -> UncheckedConfig { + pub fn query_config(&mut self) -> UncheckedNftConfig { self.app.wrap().query_wasm_smart(self.nft_contract.clone(), &QueryMsg::Config {}).unwrap() } @@ -80,7 +77,7 @@ impl MockEnv { let res = self.app.execute_contract( self.minter.clone(), self.nft_contract.clone(), - &ExtendedExecuteMsg::Mint { + &ExecuteMsg::Mint { user: token_owner.into(), }, &[], @@ -102,7 +99,7 @@ impl MockEnv { self.app.execute_contract( sender.clone(), self.nft_contract.clone(), - &ExtendedExecuteMsg::Burn { + &ExecuteMsg::Burn { token_id: token_id.to_string(), }, &[], @@ -116,7 +113,7 @@ impl MockEnv { ) -> AnyResult { self.update_config( sender, - &ConfigUpdates { + &NftConfigUpdates { max_value_for_burn: None, proposed_new_minter: Some(proposed_new_minter.to_string()), }, @@ -135,7 +132,7 @@ impl MockEnv { pub fn update_config( &mut self, sender: &Addr, - updates: &ConfigUpdates, + updates: &NftConfigUpdates, ) -> AnyResult { self.app.execute_contract( sender.clone(), diff --git a/contracts/account-nft/tests/helpers/mock_env_builder.rs b/contracts/account-nft/tests/helpers/mock_env_builder.rs index a7607e2d9..9411d9111 100644 --- a/contracts/account-nft/tests/helpers/mock_env_builder.rs +++ b/contracts/account-nft/tests/helpers/mock_env_builder.rs @@ -3,12 +3,9 @@ use std::mem::take; use anyhow::Result as AnyResult; use cosmwasm_std::{Addr, Empty}; use cw_multi_test::{BasicApp, Executor}; -use mars_account_nft::{ - config::ConfigUpdates, - msg::{ - ExecuteMsg::{AcceptMinterRole, UpdateConfig}, - InstantiateMsg, - }, +use mars_rover::adapters::account_nft::{ + ExecuteMsg::{AcceptMinterRole, UpdateConfig}, + InstantiateMsg, NftConfigUpdates, }; use crate::helpers::{ @@ -98,7 +95,7 @@ impl MockEnvBuilder { minter, nft_contract.clone(), &UpdateConfig { - updates: ConfigUpdates { + updates: NftConfigUpdates { max_value_for_burn: None, proposed_new_minter: Some(cm_addr.clone().into()), }, diff --git a/contracts/account-nft/tests/test_burn_allowance.rs b/contracts/account-nft/tests/test_burn_allowance.rs index f557dcc7c..096109b2a 100644 --- a/contracts/account-nft/tests/test_burn_allowance.rs +++ b/contracts/account-nft/tests/test_burn_allowance.rs @@ -1,8 +1,7 @@ use cosmwasm_std::{Addr, Empty, StdResult, Uint128}; use cw721::NftInfoResponse; -use mars_account_nft::{ - error::{ContractError, ContractError::BurnNotAllowed}, - msg::QueryMsg::NftInfo, +use mars_rover::adapters::account_nft::{ + ContractError, ContractError::BurnNotAllowed, QueryMsg::NftInfo, }; use crate::helpers::{below_max_for_burn, generate_health_response, MockEnv, MAX_VALUE_FOR_BURN}; diff --git a/contracts/account-nft/tests/test_mint.rs b/contracts/account-nft/tests/test_mint.rs index ba50f7fc0..d64b375af 100644 --- a/contracts/account-nft/tests/test_mint.rs +++ b/contracts/account-nft/tests/test_mint.rs @@ -4,9 +4,8 @@ use cosmwasm_std::Addr; use cw721::OwnerOfResponse; use cw721_base::ContractError::Unauthorized; use cw_multi_test::Executor; -use mars_account_nft::{ - error::{ContractError, ContractError::BaseError}, - msg::{ExecuteMsg as ExtendedExecuteMsg, QueryMsg::OwnerOf}, +use mars_rover::adapters::account_nft::{ + ContractError, ContractError::BaseError, ExecuteMsg, QueryMsg::OwnerOf, }; use crate::helpers::{below_max_for_burn, MockEnv}; @@ -62,7 +61,7 @@ fn test_only_minter_can_mint() { let res = mock.app.execute_contract( bad_guy.clone(), mock.nft_contract.clone(), - &ExtendedExecuteMsg::Mint { + &ExecuteMsg::Mint { user: bad_guy.into(), }, &[], @@ -95,7 +94,7 @@ fn test_normal_base_cw721_actions_can_still_be_taken() { let token_id = mock.mint(&rover_user_a).unwrap(); let rover_user_b = Addr::unchecked("rover_user_b"); - let transfer_msg: ExtendedExecuteMsg = ExtendedExecuteMsg::TransferNft { + let transfer_msg: ExecuteMsg = ExecuteMsg::TransferNft { token_id: token_id.clone(), recipient: rover_user_b.clone().into(), }; diff --git a/contracts/account-nft/tests/test_proposed_minter.rs b/contracts/account-nft/tests/test_proposed_minter.rs index 808d07ae4..4d5b72523 100644 --- a/contracts/account-nft/tests/test_proposed_minter.rs +++ b/contracts/account-nft/tests/test_proposed_minter.rs @@ -1,6 +1,6 @@ use cosmwasm_std::Addr; use cw721_base::MinterResponse; -use mars_account_nft::msg::QueryMsg; +use mars_rover::adapters::account_nft::QueryMsg; use crate::helpers::MockEnv; diff --git a/contracts/account-nft/tests/test_update_config.rs b/contracts/account-nft/tests/test_update_config.rs index 79176ae0b..4b06a37c3 100644 --- a/contracts/account-nft/tests/test_update_config.rs +++ b/contracts/account-nft/tests/test_update_config.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{Addr, Uint128}; -use mars_account_nft::config::ConfigUpdates; +use mars_rover::adapters::account_nft::NftConfigUpdates; use crate::helpers::MockEnv; @@ -12,7 +12,7 @@ fn test_only_minter_can_update_config() { let bad_guy = Addr::unchecked("bad_guy"); let res = mock.update_config( &bad_guy, - &ConfigUpdates { + &NftConfigUpdates { max_value_for_burn: None, proposed_new_minter: None, }, @@ -30,7 +30,7 @@ fn test_minter_can_update_config() { let new_max_burn_val = Uint128::new(4918453); let new_proposed_minter = "new_proposed_minter".to_string(); - let updates = ConfigUpdates { + let updates = NftConfigUpdates { max_value_for_burn: Some(new_max_burn_val), proposed_new_minter: Some(new_proposed_minter.clone()), }; diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index c1a3fd06e..5b763d170 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -27,7 +27,6 @@ cw721 = { workspace = true } cw721-base = { workspace = true } cw-item-set = { workspace = true } cw-storage-plus = { workspace = true } -mars-account-nft = { workspace = true } mars-math = { workspace = true } mars-health = { workspace = true } mars-outpost = { workspace = true } @@ -39,6 +38,7 @@ anyhow = { workspace = true } cw-multi-test = { workspace = true } cw-utils = { workspace = true } itertools = { workspace = true } +mars-account-nft = { workspace = true } mars-mock-oracle = { workspace = true } mars-mock-red-bank = { workspace = true } mars-mock-vault = { workspace = true } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index a876e9a4c..847623d06 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -19,7 +19,7 @@ use crate::{ query_config, query_positions, query_total_debt_shares, query_total_vault_coin_balance, query_vaults_info, }, - update_config::{update_config, update_owner}, + update_config::{update_config, update_nft_config, update_owner}, vault::handle_unlock_request_reply, zap::{estimate_provide_liquidity, estimate_withdraw_liquidity}, }; @@ -49,8 +49,11 @@ pub fn execute( match msg { ExecuteMsg::CreateCreditAccount {} => create_credit_account(deps, info.sender), ExecuteMsg::UpdateConfig { - new_config, - } => update_config(deps, info, new_config), + updates, + } => update_config(deps, info, updates), + ExecuteMsg::UpdateNftConfig { + updates, + } => update_nft_config(deps, info, updates), ExecuteMsg::UpdateOwner(update) => update_owner(deps, info, update), ExecuteMsg::Callback(callback) => execute_callback(deps, info, env, callback), ExecuteMsg::UpdateCreditAccount { diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index a09e2d376..0ebc5c730 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -1,8 +1,8 @@ use cosmwasm_std::{ to_binary, Addr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdResult, WasmMsg, }; -use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; use mars_rover::{ + adapters::account_nft::ExecuteMsg as NftExecuteMsg, coins::Coins, error::{ContractError, ContractResult}, msg::execute::{Action, CallbackMsg}, diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index 7582726a6..98c550682 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, MessageInfo, Response, WasmMsg}; -use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; use mars_owner::OwnerUpdate; use mars_rover::{ + adapters::account_nft::{ExecuteMsg as NftExecuteMsg, NftConfigUpdates}, error::ContractResult, msg::instantiate::ConfigUpdates, traits::{FallbackStr, Stringify}, @@ -18,14 +18,14 @@ use crate::{ pub fn update_config( deps: DepsMut, info: MessageInfo, - new_config: ConfigUpdates, + updates: ConfigUpdates, ) -> ContractResult { OWNER.assert_owner(deps.storage, &info.sender)?; let mut response = Response::new().add_attribute("action", "rover/credit-manager/update_config"); - if let Some(addr_str) = new_config.account_nft { + if let Some(addr_str) = updates.account_nft { let validated = deps.api.addr_validate(&addr_str)?; ACCOUNT_NFT.save(deps.storage, &validated)?; @@ -42,7 +42,7 @@ pub fn update_config( .add_attribute("value", addr_str); } - if let Some(coins) = new_config.allowed_coins { + if let Some(coins) = updates.allowed_coins { assert_no_duplicate_coins(&coins)?; ALLOWED_COINS.clear(deps.storage); coins.iter().try_for_each(|denom| ALLOWED_COINS.insert(deps.storage, denom).map(|_| ()))?; @@ -52,7 +52,7 @@ pub fn update_config( .add_attribute("value", coins.join(", ").fallback("None")); } - if let Some(configs) = new_config.vault_configs { + if let Some(configs) = updates.vault_configs { assert_no_duplicate_vaults(deps.api, &deps.querier, &configs)?; VAULT_CONFIGS.clear(deps.storage); configs.iter().try_for_each(|v| -> ContractResult<_> { @@ -65,25 +65,25 @@ pub fn update_config( .add_attribute("value", configs.to_string().fallback("None")) } - if let Some(unchecked) = new_config.oracle { + if let Some(unchecked) = updates.oracle { ORACLE.save(deps.storage, &unchecked.check(deps.api)?)?; response = response.add_attribute("key", "oracle").add_attribute("value", unchecked.address()); } - if let Some(unchecked) = new_config.swapper { + if let Some(unchecked) = updates.swapper { SWAPPER.save(deps.storage, &unchecked.check(deps.api)?)?; response = response.add_attribute("key", "swapper").add_attribute("value", unchecked.address()); } - if let Some(unchecked) = new_config.zapper { + if let Some(unchecked) = updates.zapper { ZAPPER.save(deps.storage, &unchecked.check(deps.api)?)?; response = response.add_attribute("key", "zapper").add_attribute("value", unchecked.address()); } - if let Some(cf) = new_config.max_close_factor { + if let Some(cf) = updates.max_close_factor { assert_lte_to_one(&cf)?; MAX_CLOSE_FACTOR.save(deps.storage, &cf)?; response = response @@ -91,7 +91,7 @@ pub fn update_config( .add_attribute("value", cf.to_string()); } - if let Some(num) = new_config.max_unlocking_positions { + if let Some(num) = updates.max_unlocking_positions { MAX_UNLOCKING_POSITIONS.save(deps.storage, &num)?; response = response .add_attribute("key", "max_unlocking_positions") @@ -108,3 +108,25 @@ pub fn update_owner( ) -> ContractResult { Ok(OWNER.update(deps, info, update)?) } + +pub fn update_nft_config( + deps: DepsMut, + info: MessageInfo, + updates: NftConfigUpdates, +) -> ContractResult { + OWNER.assert_owner(deps.storage, &info.sender)?; + + let nft_contract = ACCOUNT_NFT.load(deps.storage)?; + + let update_config_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: nft_contract.to_string(), + funds: vec![], + msg: to_binary(&NftExecuteMsg::UpdateConfig { + updates, + })?, + }); + + Ok(Response::new() + .add_attribute("action", "rover/credit-manager/update_nft_config") + .add_message(update_config_msg)) +} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 1af1debeb..890c65e0e 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -7,10 +7,6 @@ use cosmwasm_vault_standard::{ msg::{ExtensionQueryMsg, VaultStandardQueryMsg::VaultExtension}, }; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; -use mars_account_nft::{ - config::ConfigUpdates as NftConfigUpdates, - msg::{ExecuteMsg as NftExecuteMsg, InstantiateMsg as NftInstantiateMsg}, -}; use mars_health::HealthResponse; use mars_mock_oracle::msg::{ CoinPrice, ExecuteMsg as OracleExecuteMsg, InstantiateMsg as OracleInstantiateMsg, @@ -23,6 +19,10 @@ use mars_outpost::red_bank::{QueryMsg::UserDebt, UserDebtResponse}; use mars_owner::OwnerUpdate; use mars_rover::{ adapters::{ + account_nft::{ + ExecuteMsg as NftExecuteMsg, InstantiateMsg as NftInstantiateMsg, NftConfigUpdates, + QueryMsg as NftQueryMsg, UncheckedNftConfig, + }, oracle::{Oracle, OracleBase, OracleUnchecked}, red_bank::RedBankBase, swap::{ @@ -133,13 +133,28 @@ impl MockEnv { pub fn update_config( &mut self, sender: &Addr, - new_config: ConfigUpdates, + updates: ConfigUpdates, ) -> AnyResult { self.app.execute_contract( sender.clone(), self.rover.clone(), &ExecuteMsg::UpdateConfig { - new_config, + updates, + }, + &[], + ) + } + + pub fn update_nft_config( + &mut self, + sender: &Addr, + updates: NftConfigUpdates, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.rover.clone(), + &ExecuteMsg::UpdateNftConfig { + updates, }, &[], ) @@ -245,6 +260,14 @@ impl MockEnv { self.app.wrap().query_wasm_smart(self.rover.clone(), &QueryMsg::Config {}).unwrap() } + pub fn query_nft_config(&self) -> UncheckedNftConfig { + let config = self.query_config(); + self.app + .wrap() + .query_wasm_smart(config.account_nft.unwrap(), &NftQueryMsg::Config {}) + .unwrap() + } + pub fn query_vault_configs( &self, start_after: Option, @@ -485,6 +508,7 @@ impl MockEnvBuilder { pub fn build(&mut self) -> AnyResult { let rover = self.get_rover()?; let mars_oracle = self.get_oracle(); + self.deploy_nft_contract(&rover); self.fund_users(); @@ -528,13 +552,13 @@ impl MockEnvBuilder { } } - pub fn update_config(&mut self, rover: &Addr, new_config: ConfigUpdates) { + pub fn update_config(&mut self, rover: &Addr, updates: ConfigUpdates) { self.app .execute_contract( self.get_owner(), rover.clone(), &ExecuteMsg::UpdateConfig { - new_config, + updates, }, &[], ) diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index fef0dce3f..eca947dbe 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -148,6 +148,7 @@ fn test_instantiate_raises_on_invalid_vaults_config() { max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 0).unwrap(), base_token_denom: "lp_denom_123".to_string(), + whitelisted: true, }, None, ) @@ -159,6 +160,7 @@ fn test_instantiate_raises_on_invalid_vaults_config() { max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), base_token_denom: "xyz".to_string(), + whitelisted: true, }, None, ) diff --git a/contracts/credit-manager/tests/test_update_nft_config.rs b/contracts/credit-manager/tests/test_update_nft_config.rs new file mode 100644 index 000000000..ae4a79a77 --- /dev/null +++ b/contracts/credit-manager/tests/test_update_nft_config.rs @@ -0,0 +1,107 @@ +extern crate core; + +use cosmwasm_std::{Addr, Uint128}; +use cw_multi_test::Executor; +use mars_owner::OwnerError::NotOwner; +use mars_rover::{ + adapters::account_nft::{ExecuteMsg, NftConfigUpdates}, + error::ContractError, +}; + +use crate::helpers::{assert_err, MockEnv}; + +pub mod helpers; + +#[test] +fn test_only_owner_can_update_config() { + let mut mock = MockEnv::new().build().unwrap(); + let bad_guy = Addr::unchecked("bad_guy"); + + // Attempt updating from Rover + let res = mock.update_nft_config( + &bad_guy, + NftConfigUpdates { + max_value_for_burn: None, + proposed_new_minter: Some(bad_guy.to_string()), + }, + ); + assert_err(res, ContractError::OwnerError(NotOwner {})); + + // Attempt updating directly from the NFT contract + mock.app + .execute_contract( + bad_guy.clone(), + Addr::unchecked(mock.query_config().account_nft.unwrap()), + &ExecuteMsg::UpdateConfig { + updates: NftConfigUpdates { + max_value_for_burn: None, + proposed_new_minter: Some(bad_guy.to_string()), + }, + }, + &[], + ) + .unwrap_err(); +} + +#[test] +fn test_raises_on_invalid_config() { + let mut mock = MockEnv::new().build().unwrap(); + + let res = mock.update_nft_config( + &Addr::unchecked(mock.query_config().owner.unwrap()), + NftConfigUpdates { + max_value_for_burn: None, + proposed_new_minter: Some("".to_string()), + }, + ); + + if res.is_ok() { + panic!("should have thrown error due to bad proposed_new_minter input") + } +} + +#[test] +fn test_update_config_works_with_full_config() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_nft_config(); + + let new_max_value = Some(Uint128::new(1122334455)); + let new_proposed = Some("spiderman_12345".to_string()); + mock.update_nft_config( + &Addr::unchecked(mock.query_config().owner.unwrap()), + NftConfigUpdates { + max_value_for_burn: new_max_value, + proposed_new_minter: new_proposed.clone(), + }, + ) + .unwrap(); + + let new_config = mock.query_nft_config(); + assert_eq!(Some(new_config.max_value_for_burn), new_max_value); + assert_eq!(new_config.proposed_new_minter, new_proposed); + + assert_ne!(new_config.max_value_for_burn, original_config.max_value_for_burn); + assert_ne!(new_config.proposed_new_minter, original_config.proposed_new_minter); +} + +#[test] +fn test_update_config_works_with_some_config() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_nft_config(); + + let new_proposed = Some("spiderman_12345".to_string()); + mock.update_nft_config( + &Addr::unchecked(mock.query_config().owner.unwrap()), + NftConfigUpdates { + max_value_for_burn: None, + proposed_new_minter: new_proposed.clone(), + }, + ) + .unwrap(); + + let new_config = mock.query_nft_config(); + assert_eq!(new_config.max_value_for_burn, original_config.max_value_for_burn); + assert_eq!(new_config.proposed_new_minter, new_proposed); + + assert_ne!(new_config.proposed_new_minter, original_config.proposed_new_minter); +} diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index c32d21a77..cdf5ed7e5 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -21,6 +21,8 @@ backtraces = ["cosmwasm-std/backtraces"] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cosmwasm-vault-standard = { workspace = true } +cw721 = { workspace = true } +cw721-base = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } mars-health = { workspace = true } diff --git a/contracts/account-nft/src/error.rs b/packages/rover/src/adapters/account_nft/error.rs similarity index 100% rename from contracts/account-nft/src/error.rs rename to packages/rover/src/adapters/account_nft/error.rs diff --git a/packages/rover/src/adapters/account_nft/mod.rs b/packages/rover/src/adapters/account_nft/mod.rs new file mode 100644 index 000000000..58ca0a7f7 --- /dev/null +++ b/packages/rover/src/adapters/account_nft/mod.rs @@ -0,0 +1,5 @@ +mod error; +mod msg; +mod nft_config; + +pub use self::{error::*, msg::*, nft_config::*}; diff --git a/contracts/account-nft/src/msg/execute.rs b/packages/rover/src/adapters/account_nft/msg/execute.rs similarity index 97% rename from contracts/account-nft/src/msg/execute.rs rename to packages/rover/src/adapters/account_nft/msg/execute.rs index 14bb91e9b..31a44a206 100644 --- a/contracts/account-nft/src/msg/execute.rs +++ b/packages/rover/src/adapters/account_nft/msg/execute.rs @@ -5,7 +5,7 @@ use cosmwasm_std::{Binary, Empty, StdError}; use cw721::Expiration; use cw721_base::ExecuteMsg as ParentExecuteMsg; -use crate::{config::ConfigUpdates, error::ContractError}; +use crate::adapters::account_nft::{ContractError, NftConfigUpdates}; #[cw_serde] pub enum ExecuteMsg { @@ -14,7 +14,7 @@ pub enum ExecuteMsg { //-------------------------------------------------------------------------------------------------- /// Update config in storage. Only minter can execute. UpdateConfig { - updates: ConfigUpdates, + updates: NftConfigUpdates, }, /// Accept the proposed minter role. Only the proposed new minter can execute. diff --git a/contracts/account-nft/src/msg/instantiate.rs b/packages/rover/src/adapters/account_nft/msg/instantiate.rs similarity index 100% rename from contracts/account-nft/src/msg/instantiate.rs rename to packages/rover/src/adapters/account_nft/msg/instantiate.rs diff --git a/contracts/account-nft/src/msg/mod.rs b/packages/rover/src/adapters/account_nft/msg/mod.rs similarity index 100% rename from contracts/account-nft/src/msg/mod.rs rename to packages/rover/src/adapters/account_nft/msg/mod.rs diff --git a/contracts/account-nft/src/msg/query.rs b/packages/rover/src/adapters/account_nft/msg/query.rs similarity index 98% rename from contracts/account-nft/src/msg/query.rs rename to packages/rover/src/adapters/account_nft/msg/query.rs index 7387be867..fb4a34585 100644 --- a/contracts/account-nft/src/msg/query.rs +++ b/packages/rover/src/adapters/account_nft/msg/query.rs @@ -10,7 +10,7 @@ pub enum QueryMsg { //-------------------------------------------------------------------------------------------------- // Extended messages //-------------------------------------------------------------------------------------------------- - #[returns(crate::config::UncheckedConfig)] + #[returns(crate::adapters::account_nft::UncheckedNftConfig)] Config {}, #[returns(u64)] diff --git a/contracts/account-nft/src/config.rs b/packages/rover/src/adapters/account_nft/nft_config.rs similarity index 64% rename from contracts/account-nft/src/config.rs rename to packages/rover/src/adapters/account_nft/nft_config.rs index bc8412920..7c6f2a247 100644 --- a/contracts/account-nft/src/config.rs +++ b/packages/rover/src/adapters/account_nft/nft_config.rs @@ -2,16 +2,16 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; #[cw_serde] -pub struct ConfigBase { +pub struct NftConfigBase { pub max_value_for_burn: Uint128, pub proposed_new_minter: Option, } -pub type Config = ConfigBase; -pub type UncheckedConfig = ConfigBase; +pub type NftConfig = NftConfigBase; +pub type UncheckedNftConfig = NftConfigBase; -impl From for UncheckedConfig { - fn from(config: Config) -> Self { +impl From for UncheckedNftConfig { + fn from(config: NftConfig) -> Self { Self { max_value_for_burn: config.max_value_for_burn, proposed_new_minter: config.proposed_new_minter.map(Into::into), @@ -20,7 +20,7 @@ impl From for UncheckedConfig { } #[cw_serde] -pub struct ConfigUpdates { +pub struct NftConfigUpdates { pub max_value_for_burn: Option, pub proposed_new_minter: Option, } diff --git a/packages/rover/src/adapters/mod.rs b/packages/rover/src/adapters/mod.rs index 1008d92c5..969b535c7 100644 --- a/packages/rover/src/adapters/mod.rs +++ b/packages/rover/src/adapters/mod.rs @@ -1,3 +1,4 @@ +pub mod account_nft; pub mod oracle; pub mod red_bank; pub mod swap; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 742dddd9e..30a905b75 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -3,7 +3,10 @@ use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128 use mars_owner::OwnerUpdate; use crate::{ - adapters::vault::{Vault, VaultPositionType, VaultUnchecked}, + adapters::{ + account_nft::NftConfigUpdates, + vault::{Vault, VaultPositionType, VaultUnchecked}, + }, msg::instantiate::ConfigUpdates, }; @@ -25,10 +28,14 @@ pub enum ExecuteMsg { //-------------------------------------------------------------------------------------------------- /// Update contract config constants UpdateConfig { - new_config: ConfigUpdates, + updates: ConfigUpdates, }, /// Manages owner role state UpdateOwner(OwnerUpdate), + /// Update nft contract config + UpdateNftConfig { + updates: NftConfigUpdates, + }, /// Internal actions only callable by the contract itself Callback(CallbackMsg), } diff --git a/schemas/mars-account-nft/mars-account-nft.json b/schemas/mars-account-nft/mars-account-nft.json index f19db989a..6d123e994 100644 --- a/schemas/mars-account-nft/mars-account-nft.json +++ b/schemas/mars-account-nft/mars-account-nft.json @@ -60,7 +60,7 @@ ], "properties": { "updates": { - "$ref": "#/definitions/ConfigUpdates" + "$ref": "#/definitions/NftConfigUpdates" } }, "additionalProperties": false @@ -304,28 +304,6 @@ "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" }, - "ConfigUpdates": { - "type": "object", - "properties": { - "max_value_for_burn": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "proposed_new_minter": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, "Expiration": { "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", "oneOf": [ @@ -373,6 +351,28 @@ } ] }, + "NftConfigUpdates": { + "type": "object", + "properties": { + "max_value_for_burn": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "proposed_new_minter": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, "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": [ @@ -1194,7 +1194,7 @@ }, "config": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ConfigBase_for_String", + "title": "NftConfigBase_for_String", "type": "object", "required": [ "max_value_for_burn" diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index a32842cb8..435bc1c43 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -233,10 +233,10 @@ "update_config": { "type": "object", "required": [ - "new_config" + "updates" ], "properties": { - "new_config": { + "updates": { "$ref": "#/definitions/ConfigUpdates" } }, @@ -258,6 +258,28 @@ }, "additionalProperties": false }, + { + "description": "Update nft contract config", + "type": "object", + "required": [ + "update_nft_config" + ], + "properties": { + "update_nft_config": { + "type": "object", + "required": [ + "updates" + ], + "properties": { + "updates": { + "$ref": "#/definitions/NftConfigUpdates" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Internal actions only callable by the contract itself", "type": "object", @@ -1259,6 +1281,28 @@ "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" }, + "NftConfigUpdates": { + "type": "object", + "properties": { + "max_value_for_burn": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "proposed_new_minter": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, "OracleBase_for_String": { "type": "string" }, diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 4e9018999..74d77a077 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,7 @@ { - "mockVault": "osmo12wa5m60x0s3kdsr2qz2u2zw3afesqtlz688lq4avjhw4mmf4uu4q0ctwvc", - "swapper": "osmo1223jlew7m75xxpan25v0057q27z66tytppkehlcfae5zp6wsjlpq5lj8ql", - "zapper": "osmo1dzxd98pu3syx8n6nwxkugg3rxk4tcta46g88dl8xe76d7vsen8fs37k9kv", - "creditManager": "osmo1djlx4lcd70jrpdtfnk9srjzgpzw7xw6jc32fj9qyqvjxyrlc80cswqhhfa", - "accountNft": "osmo1drnjmpaplz4lhrw99qd4ykjlk3f2qrp6xuhj87jp6jv4zkurcjvqr25gmk" + "mockVault": "osmo1z3ent379n9fzen9598hl7hnueh2k786e0gkxhusvxus9ag2ud5jqqv0ev7", + "swapper": "osmo1fjw3qw8yf8slzdjs9m7n9e45xkz3ewvxlk78ts6t3s4wq8886vlqkhfn4m", + "zapper": "osmo12rnm0lzadwr7fcpcqwjv6kpqtfahwyrrq5gte7px33xrh4z6gvtsnk4gef", + "creditManager": "osmo1494dljftmcx3e6scmw2dlvrxaj2j8afc89qyqpg245hg70dd2l4s6dk434", + "accountNft": "osmo12vex6ad7l87ru637xnfhvaaggxq6pw4cjzk3ut0l3hurjcfh05ess2a8nr" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 3530a9066..957247320 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -188,7 +188,7 @@ export class Deployer { this.deployerAddr, this.storage.addresses.creditManager!, ) - await client.updateConfig({ newConfig: { account_nft: this.storage.addresses.accountNft } }) + await client.updateConfig({ updates: { account_nft: this.storage.addresses.accountNft } }) this.storage.actions.acceptedOwnership = true printGreen(`Rover accepts ownership of Nft contract`) } else { diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index d37c575e0..8daf0439a 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -35,8 +35,8 @@ export class Rover { this.nft = new MarsAccountNftQueryClient(cwClient, storage.addresses.accountNft!) } - async updateConfig(newConfig: ConfigUpdates) { - await this.exec.updateConfig({ newConfig }) + async updateConfig(updates: ConfigUpdates) { + await this.exec.updateConfig({ updates }) } async createCreditAccount() { diff --git a/scripts/package.json b/scripts/package.json index db16fcf80..9f1a2014f 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -13,11 +13,11 @@ "format-check": "prettier --check ." }, "dependencies": { - "@cosmjs/cosmwasm-stargate": "^0.29.4", - "@cosmjs/stargate": "^0.29.4", + "@cosmjs/cosmwasm-stargate": "^0.29.5", + "@cosmjs/stargate": "^0.29.5", "@cosmwasm/ts-codegen": "^0.24.0", "chalk": "4.1.2", - "cosmjs-types": "^0.5.2", + "cosmjs-types": "^0.6.1", "lodash": "^4.17.21", "long": "^5.2.1", "prepend-file": "^2.0.1" @@ -25,13 +25,13 @@ "devDependencies": { "@babel/preset-env": "^7.20.2", "@babel/preset-typescript": "^7.18.6", - "@types/jest": "^29.2.3", - "@typescript-eslint/eslint-plugin": "^5.45.0", - "@typescript-eslint/parser": "^5.45.0", - "eslint": "^8.28.0", - "eslint-config-prettier": "^8.5.0", + "@types/jest": "^29.2.6", + "@typescript-eslint/eslint-plugin": "^5.48.2", + "@typescript-eslint/parser": "^5.48.2", + "eslint": "^8.32.0", + "eslint-config-prettier": "^8.6.0", "jest": "^29.3.1", - "prettier": "^2.8.0", - "typescript": "^4.9.3" + "prettier": "^2.8.3", + "typescript": "^4.9.4" } } diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts index de80142cb..427066f2b 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts @@ -15,7 +15,7 @@ import { Expiration, Timestamp, Uint64, - ConfigUpdates, + NftConfigUpdates, QueryMsg, AllNftInfoResponseForEmpty, OwnerOfResponse, @@ -26,14 +26,14 @@ import { TokensResponse, ApprovalResponse, ApprovalsResponse, - ConfigBaseForString, + NftConfigBaseForString, ContractInfoResponse, MinterResponse, NumTokensResponse, } from './MarsAccountNft.types' export interface MarsAccountNftReadOnlyInterface { contractAddress: string - config: () => Promise + config: () => Promise nextId: () => Promise ownerOf: ({ includeExpired, @@ -119,7 +119,7 @@ export class MarsAccountNftQueryClient implements MarsAccountNftReadOnlyInterfac this.minter = this.minter.bind(this) } - config = async (): Promise => { + config = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { config: {}, }) @@ -269,7 +269,7 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface { updates, }: { - updates: ConfigUpdates + updates: NftConfigUpdates }, fee?: number | StdFee | 'auto', memo?: string, @@ -404,7 +404,7 @@ export class MarsAccountNftClient { updates, }: { - updates: ConfigUpdates + updates: NftConfigUpdates }, fee: number | StdFee | 'auto' = 'auto', memo?: string, diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts index 5df812250..5ca93434a 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts @@ -17,7 +17,7 @@ import { Expiration, Timestamp, Uint64, - ConfigUpdates, + NftConfigUpdates, QueryMsg, AllNftInfoResponseForEmpty, OwnerOfResponse, @@ -28,7 +28,7 @@ import { TokensResponse, ApprovalResponse, ApprovalsResponse, - ConfigBaseForString, + NftConfigBaseForString, ContractInfoResponse, MinterResponse, NumTokensResponse, @@ -40,7 +40,7 @@ export interface MarsAccountNftMessage { { updates, }: { - updates: ConfigUpdates + updates: NftConfigUpdates }, funds?: Coin[], ) => MsgExecuteContractEncodeObject @@ -147,7 +147,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { { updates, }: { - updates: ConfigUpdates + updates: NftConfigUpdates }, funds?: Coin[], ): MsgExecuteContractEncodeObject => { diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts index e3c215a32..8ea571906 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts @@ -16,7 +16,7 @@ import { Expiration, Timestamp, Uint64, - ConfigUpdates, + NftConfigUpdates, QueryMsg, AllNftInfoResponseForEmpty, OwnerOfResponse, @@ -27,7 +27,7 @@ import { TokensResponse, ApprovalResponse, ApprovalsResponse, - ConfigBaseForString, + NftConfigBaseForString, ContractInfoResponse, MinterResponse, NumTokensResponse, @@ -335,12 +335,12 @@ export function useMarsAccountNftNextIdQuery({ ) } export interface MarsAccountNftConfigQuery - extends MarsAccountNftReactQuery {} -export function useMarsAccountNftConfigQuery({ + extends MarsAccountNftReactQuery {} +export function useMarsAccountNftConfigQuery({ client, options, }: MarsAccountNftConfigQuery) { - return useQuery( + return useQuery( marsAccountNftQueryKeys.config(client?.contractAddress), () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, @@ -551,7 +551,7 @@ export function useMarsAccountNftAcceptMinterRoleMutation( export interface MarsAccountNftUpdateConfigMutation { client: MarsAccountNftClient msg: { - updates: ConfigUpdates + updates: NftConfigUpdates } args?: { fee?: number | StdFee | 'auto' diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index 0fa4760f5..b9711e41f 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -15,7 +15,7 @@ export interface InstantiateMsg { export type ExecuteMsg = | { update_config: { - updates: ConfigUpdates + updates: NftConfigUpdates } } | { @@ -81,7 +81,7 @@ export type Expiration = } export type Timestamp = Uint64 export type Uint64 = string -export interface ConfigUpdates { +export interface NftConfigUpdates { max_value_for_burn?: Uint128 | null proposed_new_minter?: string | null } @@ -183,7 +183,7 @@ export interface ApprovalResponse { export interface ApprovalsResponse { approvals: Approval[] } -export interface ConfigBaseForString { +export interface NftConfigBaseForString { max_value_for_burn: Uint128 proposed_new_minter?: string | null } diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index c9847d101..939809023 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -28,6 +28,7 @@ import { Addr, ActionCoin, ConfigUpdates, + NftConfigUpdates, VaultBaseForAddr, QueryMsg, ArrayOfCoinBalanceResponseItem, @@ -317,9 +318,9 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt ) => Promise updateConfig: ( { - newConfig, + updates, }: { - newConfig: ConfigUpdates + updates: ConfigUpdates }, fee?: number | StdFee | 'auto', memo?: string, @@ -330,6 +331,16 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt memo?: string, funds?: Coin[], ) => Promise + updateNftConfig: ( + { + updates, + }: { + updates: NftConfigUpdates + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise callback: ( fee?: number | StdFee | 'auto', memo?: string, @@ -353,6 +364,7 @@ export class MarsCreditManagerClient this.updateCreditAccount = this.updateCreditAccount.bind(this) this.updateConfig = this.updateConfig.bind(this) this.updateOwner = this.updateOwner.bind(this) + this.updateNftConfig = this.updateNftConfig.bind(this) this.callback = this.callback.bind(this) } @@ -400,9 +412,9 @@ export class MarsCreditManagerClient } updateConfig = async ( { - newConfig, + updates, }: { - newConfig: ConfigUpdates + updates: ConfigUpdates }, fee: number | StdFee | 'auto' = 'auto', memo?: string, @@ -413,7 +425,7 @@ export class MarsCreditManagerClient this.contractAddress, { update_config: { - new_config: newConfig, + updates, }, }, fee, @@ -437,6 +449,29 @@ export class MarsCreditManagerClient funds, ) } + updateNftConfig = async ( + { + updates, + }: { + updates: NftConfigUpdates + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_nft_config: { + updates, + }, + }, + fee, + memo, + funds, + ) + } callback = async ( fee: number | StdFee | 'auto' = 'auto', memo?: string, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 92a600f33..9340c404a 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -29,6 +29,7 @@ import { Addr, ActionCoin, ConfigUpdates, + NftConfigUpdates, VaultBaseForAddr, QueryMsg, ArrayOfCoinBalanceResponseItem, @@ -73,13 +74,21 @@ export interface MarsCreditManagerMessage { ) => MsgExecuteContractEncodeObject updateConfig: ( { - newConfig, + updates, }: { - newConfig: ConfigUpdates + updates: ConfigUpdates }, funds?: Coin[], ) => MsgExecuteContractEncodeObject updateOwner: (funds?: Coin[]) => MsgExecuteContractEncodeObject + updateNftConfig: ( + { + updates, + }: { + updates: NftConfigUpdates + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject callback: (funds?: Coin[]) => MsgExecuteContractEncodeObject } export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessage { @@ -93,6 +102,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag this.updateCreditAccount = this.updateCreditAccount.bind(this) this.updateConfig = this.updateConfig.bind(this) this.updateOwner = this.updateOwner.bind(this) + this.updateNftConfig = this.updateNftConfig.bind(this) this.callback = this.callback.bind(this) } @@ -140,9 +150,9 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag } updateConfig = ( { - newConfig, + updates, }: { - newConfig: ConfigUpdates + updates: ConfigUpdates }, funds?: Coin[], ): MsgExecuteContractEncodeObject => { @@ -154,7 +164,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag msg: toUtf8( JSON.stringify({ update_config: { - new_config: newConfig, + updates, }, }), ), @@ -177,6 +187,30 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag }), } } + updateNftConfig = ( + { + updates, + }: { + updates: NftConfigUpdates + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_nft_config: { + updates, + }, + }), + ), + funds, + }), + } + } callback = (funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 54c1d4f0a..8c10addfb 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -29,6 +29,7 @@ import { Addr, ActionCoin, ConfigUpdates, + NftConfigUpdates, VaultBaseForAddr, QueryMsg, ArrayOfCoinBalanceResponseItem, @@ -493,6 +494,29 @@ export function useMarsCreditManagerCallbackMutation( options, ) } +export interface MarsCreditManagerUpdateNftConfigMutation { + client: MarsCreditManagerClient + msg: { + updates: NftConfigUpdates + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsCreditManagerUpdateNftConfigMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateNftConfig(msg, fee, memo, funds), + options, + ) +} export interface MarsCreditManagerUpdateOwnerMutation { client: MarsCreditManagerClient args?: { @@ -515,7 +539,7 @@ export function useMarsCreditManagerUpdateOwnerMutation( export interface MarsCreditManagerUpdateConfigMutation { client: MarsCreditManagerClient msg: { - newConfig: ConfigUpdates + updates: ConfigUpdates } args?: { fee?: number | StdFee | 'auto' diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index a63ce8749..40418a895 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -52,12 +52,17 @@ export type ExecuteMsg = } | { update_config: { - new_config: ConfigUpdates + updates: ConfigUpdates } } | { update_owner: OwnerUpdate } + | { + update_nft_config: { + updates: NftConfigUpdates + } + } | { callback: CallbackMsg } @@ -280,6 +285,10 @@ export interface ConfigUpdates { vault_configs?: VaultInstantiateConfig[] | null zapper?: ZapperBaseForString | null } +export interface NftConfigUpdates { + max_value_for_burn?: Uint128 | null + proposed_new_minter?: string | null +} export interface VaultBaseForAddr { address: Addr } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 608c0014c..c41dc4858 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1192,138 +1192,138 @@ "@noble/hashes" "^1.0.0" protobufjs "^6.8.8" -"@cosmjs/amino@^0.29.4": - version "0.29.4" - resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.29.4.tgz#93d5f90033cb2af1573627582cd2cf8a515c3ef4" - integrity sha512-FBjaJ4oUKFtH34O7XjUk370x8sF7EbXD29miXrm0Rl5GEtEORJgQwutXQllHo5gBkpOxC+ZQ40CibXhPzH7G7A== - dependencies: - "@cosmjs/crypto" "^0.29.4" - "@cosmjs/encoding" "^0.29.4" - "@cosmjs/math" "^0.29.4" - "@cosmjs/utils" "^0.29.4" - -"@cosmjs/cosmwasm-stargate@^0.29.4": - version "0.29.4" - resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.29.4.tgz#877ce28b071381e3b2586f71fad20f53634237f5" - integrity sha512-/47QmB+fLuzhcoeTTwoGtyz73yH22txfj5r+iZmHbpQ2vwCoeyG1WVNuylDGOELOej74ngCC1LCPr2/6gGHIMQ== - dependencies: - "@cosmjs/amino" "^0.29.4" - "@cosmjs/crypto" "^0.29.4" - "@cosmjs/encoding" "^0.29.4" - "@cosmjs/math" "^0.29.4" - "@cosmjs/proto-signing" "^0.29.4" - "@cosmjs/stargate" "^0.29.4" - "@cosmjs/tendermint-rpc" "^0.29.4" - "@cosmjs/utils" "^0.29.4" +"@cosmjs/amino@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.29.5.tgz#053b4739a90b15b9e2b781ccd484faf64bd49aec" + integrity sha512-Qo8jpC0BiziTSUqpkNatBcwtKNhCovUnFul9SlT/74JUCdLYaeG5hxr3q1cssQt++l4LvlcpF+OUXL48XjNjLw== + dependencies: + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + +"@cosmjs/cosmwasm-stargate@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.29.5.tgz#3f257da682658833e0f4eb9e8ff758e4d927663a" + integrity sha512-TNdSvm2tEE3XMCuxHxquzls56t40hC8qnLeYJWHsY2ECZmRK3KrnpRReEr7N7bLtODToK7X/riYrV0JaYxjrYA== + dependencies: + "@cosmjs/amino" "^0.29.5" + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/proto-signing" "^0.29.5" + "@cosmjs/stargate" "^0.29.5" + "@cosmjs/tendermint-rpc" "^0.29.5" + "@cosmjs/utils" "^0.29.5" cosmjs-types "^0.5.2" long "^4.0.0" pako "^2.0.2" -"@cosmjs/crypto@^0.29.4": - version "0.29.4" - resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.29.4.tgz#2198e1d2da9eb310df9ed8b8609dbf1a370e900b" - integrity sha512-PmSxoFl/Won7kHZv3PQUUgdmEiAMqdY7XnEnVh9PbU7Hht6uo7PQ+M0eIGW3NIXYKmn6oVExER+xOfLfq4YNGw== +"@cosmjs/crypto@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.29.5.tgz#ab99fc382b93d8a8db075780cf07487a0f9519fd" + integrity sha512-2bKkaLGictaNL0UipQCL6C1afaisv6k8Wr/GCLx9FqiyFkh9ZgRHDyetD64ZsjnWV/N/D44s/esI+k6oPREaiQ== dependencies: - "@cosmjs/encoding" "^0.29.4" - "@cosmjs/math" "^0.29.4" - "@cosmjs/utils" "^0.29.4" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/utils" "^0.29.5" "@noble/hashes" "^1" bn.js "^5.2.0" elliptic "^6.5.4" libsodium-wrappers "^0.7.6" -"@cosmjs/encoding@^0.29.4": - version "0.29.4" - resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.29.4.tgz#0ae1b78c064dacda7c35eeb7c35ed7b55951d94f" - integrity sha512-nlwCh4j+kIqEcwNu8AFSmqXGj0bvF4nLC3J1X0eJyJenlgJBiiAGjYp3nxMf/ZjKkZP65Fq7MXVtAYs3K8xvvQ== +"@cosmjs/encoding@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.29.5.tgz#009a4b1c596cdfd326f30ccfa79f5e56daa264f2" + integrity sha512-G4rGl/Jg4dMCw5u6PEZHZcoHnUBlukZODHbm/wcL4Uu91fkn5jVo5cXXZcvs4VCkArVGrEj/52eUgTZCmOBGWQ== dependencies: base64-js "^1.3.0" bech32 "^1.1.4" readonly-date "^1.0.0" -"@cosmjs/json-rpc@^0.29.4": - version "0.29.4" - resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.29.4.tgz#9d71f4277181925ca41e5ccaf9b084606899b08e" - integrity sha512-pmb918u7QlLUOX7twJCBC7bK/L87uhknnP2yh9f+n7zmmslBbwENxLdM6KBAb1Yc0tbzSaLLLRoaN8kvfaiwUA== +"@cosmjs/json-rpc@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.29.5.tgz#5e483a9bd98a6270f935adf0dfd8a1e7eb777fe4" + integrity sha512-C78+X06l+r9xwdM1yFWIpGl03LhB9NdM1xvZpQHwgCOl0Ir/WV8pw48y3Ez2awAoUBRfTeejPe4KvrE6NoIi/w== dependencies: - "@cosmjs/stream" "^0.29.4" + "@cosmjs/stream" "^0.29.5" xstream "^11.14.0" -"@cosmjs/math@^0.29.4": - version "0.29.4" - resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.29.4.tgz#9e9079826090718de75ff239a63314b0baf63999" - integrity sha512-IvT1Cj3qOMGqz7v5FxdDCBEIDL2k9m5rufrkuD4oL9kS79ebnhA0lquX6ApPubUohTXl+5PnLo02W8HEH6Stkg== +"@cosmjs/math@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.29.5.tgz#722c96e080d6c2b62215ce9f4c70da7625b241b6" + integrity sha512-2GjKcv+A9f86MAWYLUkjhw1/WpRl2R1BTb3m9qPG7lzMA7ioYff9jY5SPCfafKdxM4TIQGxXQlYGewQL16O68Q== dependencies: bn.js "^5.2.0" -"@cosmjs/proto-signing@^0.29.4": - version "0.29.4" - resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.4.tgz#8f314a936f07d15f5414280bec8aabb6de4078db" - integrity sha512-GdLOhMd54LZgG+kHf7uAWGYDT628yVhXPMWaG/1i3f3Kq4VsZgFBwJhhziM5kWblmFjBOhooGRwLrBnOxMusCg== +"@cosmjs/proto-signing@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.5.tgz#af3b62a46c2c2f1d2327d678b13b7262db1fe87c" + integrity sha512-QRrS7CiKaoETdgIqvi/7JC2qCwCR7lnWaUsTzh/XfRy3McLkEd+cXbKAW3cygykv7IN0VAEIhZd2lyIfT8KwNA== dependencies: - "@cosmjs/amino" "^0.29.4" - "@cosmjs/crypto" "^0.29.4" - "@cosmjs/encoding" "^0.29.4" - "@cosmjs/math" "^0.29.4" - "@cosmjs/utils" "^0.29.4" + "@cosmjs/amino" "^0.29.5" + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/utils" "^0.29.5" cosmjs-types "^0.5.2" long "^4.0.0" -"@cosmjs/socket@^0.29.4": - version "0.29.4" - resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.29.4.tgz#e025ea5d455647529bc7cc9794390e4af3a282fe" - integrity sha512-+J79SRVD6VSnGqKmUwLFAxXTiLYw/AmAu+075RenQxzkCxehGrGpaty+T3jJGE2p1458AjgkrmTQnfp6f+HIWw== +"@cosmjs/socket@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.29.5.tgz#a48df6b4c45dc6a6ef8e47232725dd4aa556ac2d" + integrity sha512-5VYDupIWbIXq3ftPV1LkS5Ya/T7Ol/AzWVhNxZ79hPe/mBfv1bGau/LqIYOm2zxGlgm9hBHOTmWGqNYDwr9LNQ== dependencies: - "@cosmjs/stream" "^0.29.4" + "@cosmjs/stream" "^0.29.5" isomorphic-ws "^4.0.1" ws "^7" xstream "^11.14.0" -"@cosmjs/stargate@^0.29.4": - version "0.29.4" - resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.29.4.tgz#d66494ab8de8a886aedd46706a7560482cf18ad5" - integrity sha512-MEzBkOhYX0tdGgJg4mxFDjf7NMJYKNtXHX0Fu4HVACdBrxLlStf630KodmzPFlLOHfiz3CB/mqj3TobZZz8mTw== +"@cosmjs/stargate@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.29.5.tgz#d597af1c85a3c2af7b5bdbec34d5d40692cc09e4" + integrity sha512-hjEv8UUlJruLrYGJcUZXM/CziaINOKwfVm2BoSdUnNTMxGvY/jC1ABHKeZUYt9oXHxEJ1n9+pDqzbKc8pT0nBw== dependencies: "@confio/ics23" "^0.6.8" - "@cosmjs/amino" "^0.29.4" - "@cosmjs/encoding" "^0.29.4" - "@cosmjs/math" "^0.29.4" - "@cosmjs/proto-signing" "^0.29.4" - "@cosmjs/stream" "^0.29.4" - "@cosmjs/tendermint-rpc" "^0.29.4" - "@cosmjs/utils" "^0.29.4" + "@cosmjs/amino" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/proto-signing" "^0.29.5" + "@cosmjs/stream" "^0.29.5" + "@cosmjs/tendermint-rpc" "^0.29.5" + "@cosmjs/utils" "^0.29.5" cosmjs-types "^0.5.2" long "^4.0.0" protobufjs "~6.11.3" xstream "^11.14.0" -"@cosmjs/stream@^0.29.4": - version "0.29.4" - resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.29.4.tgz#6f882c0200ba3f5b4e8004eaad9f3c41471cd288" - integrity sha512-bCcPIxxyrvtIusmZEOVZT5YS3t+dfB6wfLO6tadumYyPcCBJkXLRK6LSYcsiPjnTNr2UAlCSZX24djM2mFvlWQ== +"@cosmjs/stream@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.29.5.tgz#350981cac496d04939b92ee793b9b19f44bc1d4e" + integrity sha512-TToTDWyH1p05GBtF0Y8jFw2C+4783ueDCmDyxOMM6EU82IqpmIbfwcdMOCAm0JhnyMh+ocdebbFvnX/sGKzRAA== dependencies: xstream "^11.14.0" -"@cosmjs/tendermint-rpc@^0.29.4": - version "0.29.4" - resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.4.tgz#1c763f726f26618538b99add5c707392aeac85e0" - integrity sha512-ByW5tFK8epc7fx82DogUaLkWSyr9scAY9UmOC9Zn4uFfISOwRdN5c0DvYZ1pI49elMT3/MroIdORotdtYirm1g== - dependencies: - "@cosmjs/crypto" "^0.29.4" - "@cosmjs/encoding" "^0.29.4" - "@cosmjs/json-rpc" "^0.29.4" - "@cosmjs/math" "^0.29.4" - "@cosmjs/socket" "^0.29.4" - "@cosmjs/stream" "^0.29.4" - "@cosmjs/utils" "^0.29.4" +"@cosmjs/tendermint-rpc@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.5.tgz#f205c10464212bdf843f91bb2e4a093b618cb5c2" + integrity sha512-ar80twieuAxsy0x2za/aO3kBr2DFPAXDmk2ikDbmkda+qqfXgl35l9CVAAjKRqd9d+cRvbQyb5M4wy6XQpEV6w== + dependencies: + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/json-rpc" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/socket" "^0.29.5" + "@cosmjs/stream" "^0.29.5" + "@cosmjs/utils" "^0.29.5" axios "^0.21.2" readonly-date "^1.0.0" xstream "^11.14.0" -"@cosmjs/utils@^0.29.4": - version "0.29.4" - resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.4.tgz#8a80da006fe2b544a3c36f557e4b782810e532fd" - integrity sha512-X1pZWRHDbTPLa6cYW0NHvtig+lSxOdLAX7K/xp67ywBy2knnDOyzz1utGTOowmiM98XuV9quK/BWePKkJOaHpQ== +"@cosmjs/utils@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.5.tgz#3fed1b3528ae8c5f1eb5d29b68755bebfd3294ee" + integrity sha512-m7h+RXDUxOzEOGt4P+3OVPX7PuakZT3GBmaM/Y2u+abN3xZkziykD/NvedYFvvCCdQo714XcGl33bwifS9FZPQ== "@cosmwasm/ts-codegen@^0.24.0": version "0.24.0" @@ -1358,25 +1358,25 @@ shelljs "0.8.5" wasm-ast-types "^0.17.0" -"@eslint/eslintrc@^1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.3.tgz#2b044ab39fdfa75b4688184f9e573ce3c5b0ff95" - integrity sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg== +"@eslint/eslintrc@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e" + integrity sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA== dependencies: ajv "^6.12.4" debug "^4.3.2" espree "^9.4.0" - globals "^13.15.0" + globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.11.6": - version "0.11.7" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f" - integrity sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw== +"@humanwhocodes/config-array@^0.11.8": + version "0.11.8" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" + integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" @@ -1898,10 +1898,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.2.3": - version "29.2.3" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.3.tgz#f5fd88e43e5a9e4221ca361e23790d48fcf0a211" - integrity sha512-6XwoEbmatfyoCjWRX7z0fKMmgYKe9+/HrviJ5k0X/tjJWHGAezZOfYaxqQKuzG/TvQyr+ktjm4jgbk0s4/oF2w== +"@types/jest@^29.2.6": + version "29.2.6" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.6.tgz#1d43c8e533463d0437edef30b2d45d5aa3d95b0a" + integrity sha512-XEUC/Tgw3uMh6Ho8GkUtQ2lPhY5Fmgyp3TdlkTJs1W9VgNxs+Ow/x3Elh8lHQKqCbZL0AubQuqWjHVT033Hhrw== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -1958,14 +1958,14 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.45.0": - version "5.45.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.0.tgz#ffa505cf961d4844d38cfa19dcec4973a6039e41" - integrity sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA== +"@typescript-eslint/eslint-plugin@^5.48.2": + version "5.48.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz#112e6ae1e23a1dc8333ce82bb9c65c2608b4d8a3" + integrity sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg== dependencies: - "@typescript-eslint/scope-manager" "5.45.0" - "@typescript-eslint/type-utils" "5.45.0" - "@typescript-eslint/utils" "5.45.0" + "@typescript-eslint/scope-manager" "5.48.2" + "@typescript-eslint/type-utils" "5.48.2" + "@typescript-eslint/utils" "5.48.2" debug "^4.3.4" ignore "^5.2.0" natural-compare-lite "^1.4.0" @@ -1973,72 +1973,72 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.45.0": - version "5.45.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.45.0.tgz#b18a5f6b3cf1c2b3e399e9d2df4be40d6b0ddd0e" - integrity sha512-brvs/WSM4fKUmF5Ot/gEve6qYiCMjm6w4HkHPfS6ZNmxTS0m0iNN4yOChImaCkqc1hRwFGqUyanMXuGal6oyyQ== +"@typescript-eslint/parser@^5.48.2": + version "5.48.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.48.2.tgz#c9edef2a0922d26a37dba03be20c5fff378313b3" + integrity sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw== dependencies: - "@typescript-eslint/scope-manager" "5.45.0" - "@typescript-eslint/types" "5.45.0" - "@typescript-eslint/typescript-estree" "5.45.0" + "@typescript-eslint/scope-manager" "5.48.2" + "@typescript-eslint/types" "5.48.2" + "@typescript-eslint/typescript-estree" "5.48.2" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.45.0": - version "5.45.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.45.0.tgz#7a4ac1bfa9544bff3f620ab85947945938319a96" - integrity sha512-noDMjr87Arp/PuVrtvN3dXiJstQR1+XlQ4R1EvzG+NMgXi8CuMCXpb8JqNtFHKceVSQ985BZhfRdowJzbv4yKw== +"@typescript-eslint/scope-manager@5.48.2": + version "5.48.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz#bb7676cb78f1e94921eaab637a4b5d596f838abc" + integrity sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw== dependencies: - "@typescript-eslint/types" "5.45.0" - "@typescript-eslint/visitor-keys" "5.45.0" + "@typescript-eslint/types" "5.48.2" + "@typescript-eslint/visitor-keys" "5.48.2" -"@typescript-eslint/type-utils@5.45.0": - version "5.45.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.45.0.tgz#aefbc954c40878fcebeabfb77d20d84a3da3a8b2" - integrity sha512-DY7BXVFSIGRGFZ574hTEyLPRiQIvI/9oGcN8t1A7f6zIs6ftbrU0nhyV26ZW//6f85avkwrLag424n+fkuoJ1Q== +"@typescript-eslint/type-utils@5.48.2": + version "5.48.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz#7d3aeca9fa37a7ab7e3d9056a99b42f342c48ad7" + integrity sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew== dependencies: - "@typescript-eslint/typescript-estree" "5.45.0" - "@typescript-eslint/utils" "5.45.0" + "@typescript-eslint/typescript-estree" "5.48.2" + "@typescript-eslint/utils" "5.48.2" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.45.0": - version "5.45.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.45.0.tgz#794760b9037ee4154c09549ef5a96599621109c5" - integrity sha512-QQij+u/vgskA66azc9dCmx+rev79PzX8uDHpsqSjEFtfF2gBUTRCpvYMh2gw2ghkJabNkPlSUCimsyBEQZd1DA== +"@typescript-eslint/types@5.48.2": + version "5.48.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.48.2.tgz#635706abb1ec164137f92148f06f794438c97b8e" + integrity sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA== -"@typescript-eslint/typescript-estree@5.45.0": - version "5.45.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.45.0.tgz#f70a0d646d7f38c0dfd6936a5e171a77f1e5291d" - integrity sha512-maRhLGSzqUpFcZgXxg1qc/+H0bT36lHK4APhp0AEUVrpSwXiRAomm/JGjSG+kNUio5kAa3uekCYu/47cnGn5EQ== +"@typescript-eslint/typescript-estree@5.48.2": + version "5.48.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz#6e206b462942b32383582a6c9251c05021cc21b0" + integrity sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg== dependencies: - "@typescript-eslint/types" "5.45.0" - "@typescript-eslint/visitor-keys" "5.45.0" + "@typescript-eslint/types" "5.48.2" + "@typescript-eslint/visitor-keys" "5.48.2" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.45.0": - version "5.45.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.45.0.tgz#9cca2996eee1b8615485a6918a5c763629c7acf5" - integrity sha512-OUg2JvsVI1oIee/SwiejTot2OxwU8a7UfTFMOdlhD2y+Hl6memUSL4s98bpUTo8EpVEr0lmwlU7JSu/p2QpSvA== +"@typescript-eslint/utils@5.48.2": + version "5.48.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.48.2.tgz#3777a91dcb22b8499a25519e06eef2e9569295a3" + integrity sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow== dependencies: "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.45.0" - "@typescript-eslint/types" "5.45.0" - "@typescript-eslint/typescript-estree" "5.45.0" + "@typescript-eslint/scope-manager" "5.48.2" + "@typescript-eslint/types" "5.48.2" + "@typescript-eslint/typescript-estree" "5.48.2" eslint-scope "^5.1.1" eslint-utils "^3.0.0" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.45.0": - version "5.45.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.0.tgz#e0d160e9e7fdb7f8da697a5b78e7a14a22a70528" - integrity sha512-jc6Eccbn2RtQPr1s7th6jJWQHBHI6GBVQkCHoJFQ5UreaKm59Vxw+ynQUPPY2u2Amquc+7tmEoC2G52ApsGNNg== +"@typescript-eslint/visitor-keys@5.48.2": + version "5.48.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz#c247582a0bcce467461d7b696513bf9455000060" + integrity sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ== dependencies: - "@typescript-eslint/types" "5.45.0" + "@typescript-eslint/types" "5.48.2" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -2516,6 +2516,14 @@ cosmjs-types@^0.5.2: long "^4.0.0" protobufjs "~6.11.2" +cosmjs-types@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.6.1.tgz#4944e83d0fa87880243a11813bdff0e313d39a68" + integrity sha512-fRz6yzElHHBULDyLArF/G1UkkTWW4r3RondBUGnmSsZWYI5NpfDn32MVa5aRmpaaf4tJI2cbnXHs9fykwU7Ttg== + dependencies: + long "^4.0.0" + protobufjs "~6.11.2" + cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2693,10 +2701,10 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-config-prettier@^8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" - integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== +eslint-config-prettier@^8.6.0: + version "8.6.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz#dec1d29ab728f4fa63061774e1672ac4e363d207" + integrity sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA== eslint-scope@^5.1.1: version "5.1.1" @@ -2731,13 +2739,13 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.28.0: - version "8.28.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.28.0.tgz#81a680732634677cc890134bcdd9fdfea8e63d6e" - integrity sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ== +eslint@^8.32.0: + version "8.32.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.32.0.tgz#d9690056bb6f1a302bd991e7090f5b68fbaea861" + integrity sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ== dependencies: - "@eslint/eslintrc" "^1.3.3" - "@humanwhocodes/config-array" "^0.11.6" + "@eslint/eslintrc" "^1.4.1" + "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" ajv "^6.10.0" @@ -2756,7 +2764,7 @@ eslint@^8.28.0: file-entry-cache "^6.0.1" find-up "^5.0.0" glob-parent "^6.0.2" - globals "^13.15.0" + globals "^13.19.0" grapheme-splitter "^1.0.4" ignore "^5.2.0" import-fresh "^3.0.0" @@ -3092,10 +3100,10 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.15.0: - version "13.17.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" - integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== +globals@^13.19.0: + version "13.19.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.19.0.tgz#7a42de8e6ad4f7242fbcca27ea5b23aca367b5c8" + integrity sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ== dependencies: type-fest "^0.20.2" @@ -4338,10 +4346,10 @@ prettier@^2.6.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== -prettier@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.0.tgz#c7df58393c9ba77d6fba3921ae01faf994fb9dc9" - integrity sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA== +prettier@^2.8.3: + version "2.8.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.3.tgz#ab697b1d3dd46fb4626fbe2f543afe0cc98d8632" + integrity sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw== pretty-format@^29.0.0, pretty-format@^29.2.1: version "29.2.1" @@ -4862,10 +4870,10 @@ type@^2.7.2: resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== -typescript@^4.9.3: - version "4.9.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db" - integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA== +typescript@^4.9.4: + version "4.9.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" + integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" From 5388276d02e609a9f82384c3f5e23adffd7a089f Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 19 Jan 2023 22:43:24 +0100 Subject: [PATCH 129/218] Remove test_ prefix (#102) Update test prefix --- .../account-nft/tests/test_burn_allowance.rs | 10 +++---- .../account-nft/tests/test_instantiate.rs | 2 +- contracts/account-nft/tests/test_mint.rs | 10 +++---- .../account-nft/tests/test_proposed_minter.rs | 8 ++--- .../account-nft/tests/test_update_config.rs | 4 +-- contracts/credit-manager/tests/test_borrow.rs | 12 ++++---- .../tests/test_coin_balances.rs | 8 ++--- .../tests/test_create_credit_account.rs | 6 ++-- .../credit-manager/tests/test_deposit.rs | 16 +++++----- .../credit-manager/tests/test_dispatch.rs | 6 ++-- .../tests/test_enumerate_allowed_coins.rs | 2 +- .../tests/test_enumerate_allowed_vaults.rs | 1 - .../tests/test_enumerate_coin_balances.rs | 2 +- .../tests/test_enumerate_debt_shares.rs | 2 +- .../tests/test_enumerate_total_debt_shares.rs | 2 +- .../test_enumerate_vault_coin_balances.rs | 2 +- .../tests/test_enumerate_vault_configs.rs | 2 +- .../tests/test_enumerate_vault_positions.rs | 2 +- .../tests/test_fields_vault_limit.rs | 2 +- .../tests/test_flagged_contract.rs | 2 +- contracts/credit-manager/tests/test_health.rs | 18 +++++------ .../credit-manager/tests/test_instantiate.rs | 30 +++++++++---------- .../tests/test_liquidate_coin.rs | 24 +++++++-------- .../tests/test_liquidate_vault.rs | 18 +++++------ .../tests/test_refund_balances.rs | 4 +-- contracts/credit-manager/tests/test_repay.rs | 14 ++++----- contracts/credit-manager/tests/test_swap.rs | 14 ++++----- .../credit-manager/tests/test_update_admin.rs | 10 +++---- .../tests/test_update_config.rs | 18 +++++------ .../tests/test_update_nft_config.rs | 8 ++--- .../tests/test_utilization_query.rs | 6 ++-- .../credit-manager/tests/test_vault_enter.rs | 20 ++++++------- .../credit-manager/tests/test_vault_exit.rs | 8 ++--- .../tests/test_vault_exit_unlocked.rs | 14 ++++----- .../tests/test_vault_request_unlock.rs | 14 ++++----- .../credit-manager/tests/test_withdraw.rs | 14 ++++----- .../credit-manager/tests/test_zap_provide.rs | 18 +++++------ .../credit-manager/tests/test_zap_withdraw.rs | 16 +++++----- .../osmosis/tests/test_enumerate_routes.rs | 2 +- .../swapper/osmosis/tests/test_estimate.rs | 6 ++-- .../swapper/osmosis/tests/test_instantiate.rs | 4 +-- .../swapper/osmosis/tests/test_set_route.rs | 16 +++++----- contracts/swapper/osmosis/tests/test_swap.rs | 6 ++-- .../osmosis/tests/test_update_admin.rs | 14 ++++----- .../zapper/osmosis/tests/test_callback.rs | 2 +- .../osmosis/tests/test_provide_liquidity.rs | 14 ++++----- .../zapper/osmosis/tests/test_queries.rs | 10 +++---- .../osmosis/tests/test_withdraw_liquidity.rs | 10 +++---- 48 files changed, 226 insertions(+), 227 deletions(-) delete mode 100644 contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs diff --git a/contracts/account-nft/tests/test_burn_allowance.rs b/contracts/account-nft/tests/test_burn_allowance.rs index 096109b2a..53b704d9f 100644 --- a/contracts/account-nft/tests/test_burn_allowance.rs +++ b/contracts/account-nft/tests/test_burn_allowance.rs @@ -9,7 +9,7 @@ use crate::helpers::{below_max_for_burn, generate_health_response, MockEnv, MAX_ pub mod helpers; #[test] -fn test_burn_not_allowed_if_too_many_debts() { +fn burn_not_allowed_if_too_many_debts() { let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); let user = Addr::unchecked("user"); @@ -28,7 +28,7 @@ fn test_burn_not_allowed_if_too_many_debts() { } #[test] -fn test_burn_not_allowed_if_too_much_collateral() { +fn burn_not_allowed_if_too_much_collateral() { let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); let user = Addr::unchecked("user"); @@ -47,7 +47,7 @@ fn test_burn_not_allowed_if_too_much_collateral() { } #[test] -fn test_burn_allowance_works_with_both_debt_and_collateral() { +fn burn_allowance_works_with_both_debt_and_collateral() { let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); let user = Addr::unchecked("user"); @@ -66,7 +66,7 @@ fn test_burn_allowance_works_with_both_debt_and_collateral() { } #[test] -fn test_burn_allowance_at_exactly_max() { +fn burn_allowance_at_exactly_max() { let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); let user = Addr::unchecked("user"); @@ -77,7 +77,7 @@ fn test_burn_allowance_at_exactly_max() { } #[test] -fn test_burn_allowance_when_under_max() { +fn burn_allowance_when_under_max() { let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); let user = Addr::unchecked("user"); diff --git a/contracts/account-nft/tests/test_instantiate.rs b/contracts/account-nft/tests/test_instantiate.rs index 248cb1315..48ceff4ee 100644 --- a/contracts/account-nft/tests/test_instantiate.rs +++ b/contracts/account-nft/tests/test_instantiate.rs @@ -3,7 +3,7 @@ use crate::helpers::{MockEnv, MAX_VALUE_FOR_BURN}; pub mod helpers; #[test] -fn test_storage_vars_set_on_instantiate() { +fn storage_vars_set_on_instantiate() { let mut mock = MockEnv::new().build().unwrap(); let config = mock.query_config(); diff --git a/contracts/account-nft/tests/test_mint.rs b/contracts/account-nft/tests/test_mint.rs index d64b375af..eca2f3478 100644 --- a/contracts/account-nft/tests/test_mint.rs +++ b/contracts/account-nft/tests/test_mint.rs @@ -13,7 +13,7 @@ use crate::helpers::{below_max_for_burn, MockEnv}; pub mod helpers; #[test] -fn test_id_incrementer() { +fn id_incrementer() { let mut mock = MockEnv::new().build().unwrap(); let user_1 = Addr::unchecked("user_1"); @@ -33,7 +33,7 @@ fn test_id_incrementer() { } #[test] -fn test_id_incrementer_works_despite_burns() { +fn id_incrementer_works_despite_burns() { let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); let user = Addr::unchecked("user"); @@ -54,7 +54,7 @@ fn test_id_incrementer_works_despite_burns() { } #[test] -fn test_only_minter_can_mint() { +fn only_minter_can_mint() { let mut mock = MockEnv::new().set_minter("mr_minter").build().unwrap(); let bad_guy = Addr::unchecked("bad_guy"); @@ -71,7 +71,7 @@ fn test_only_minter_can_mint() { } #[test] -fn test_only_token_owner_can_burn() { +fn only_token_owner_can_burn() { let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); let user = Addr::unchecked("user"); @@ -87,7 +87,7 @@ fn test_only_token_owner_can_burn() { } #[test] -fn test_normal_base_cw721_actions_can_still_be_taken() { +fn normal_base_cw721_actions_can_still_be_taken() { let mut mock = MockEnv::new().build().unwrap(); let rover_user_a = Addr::unchecked("rover_user_a"); diff --git a/contracts/account-nft/tests/test_proposed_minter.rs b/contracts/account-nft/tests/test_proposed_minter.rs index 4d5b72523..ccb139e92 100644 --- a/contracts/account-nft/tests/test_proposed_minter.rs +++ b/contracts/account-nft/tests/test_proposed_minter.rs @@ -7,7 +7,7 @@ use crate::helpers::MockEnv; pub mod helpers; #[test] -fn test_only_minter_can_propose_new_minter() { +fn only_minter_can_propose_new_minter() { let mut mock = MockEnv::new().build().unwrap(); let bad_guy = Addr::unchecked("bad_guy"); @@ -19,7 +19,7 @@ fn test_only_minter_can_propose_new_minter() { } #[test] -fn test_propose_minter_stores() { +fn propose_minter_stores() { let mut mock = MockEnv::new().build().unwrap(); let new_minter = Addr::unchecked("new_minter"); @@ -30,7 +30,7 @@ fn test_propose_minter_stores() { } #[test] -fn test_proposed_minter_can_accept_role() { +fn proposed_minter_can_accept_role() { let mut mock = MockEnv::new().build().unwrap(); let new_minter = Addr::unchecked("new_minter"); @@ -50,7 +50,7 @@ fn test_proposed_minter_can_accept_role() { } #[test] -fn test_only_proposed_minter_can_accept() { +fn only_proposed_minter_can_accept() { let mut mock = MockEnv::new().build().unwrap(); let new_minter = Addr::unchecked("new_minter"); diff --git a/contracts/account-nft/tests/test_update_config.rs b/contracts/account-nft/tests/test_update_config.rs index 4b06a37c3..ad8d26748 100644 --- a/contracts/account-nft/tests/test_update_config.rs +++ b/contracts/account-nft/tests/test_update_config.rs @@ -6,7 +6,7 @@ use crate::helpers::MockEnv; pub mod helpers; #[test] -fn test_only_minter_can_update_config() { +fn only_minter_can_update_config() { let mut mock = MockEnv::new().build().unwrap(); let bad_guy = Addr::unchecked("bad_guy"); @@ -24,7 +24,7 @@ fn test_only_minter_can_update_config() { } #[test] -fn test_minter_can_update_config() { +fn minter_can_update_config() { let mut mock = MockEnv::new().build().unwrap(); let new_max_burn_val = Uint128::new(4918453); diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index 3d4d18851..8f3e0fd39 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -14,7 +14,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_only_token_owner_can_borrow() { +fn only_token_owner_can_borrow() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -39,7 +39,7 @@ fn test_only_token_owner_can_borrow() { } #[test] -fn test_can_only_borrow_what_is_whitelisted() { +fn can_only_borrow_what_is_whitelisted() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -53,7 +53,7 @@ fn test_can_only_borrow_what_is_whitelisted() { } #[test] -fn test_borrowing_zero_does_nothing() { +fn borrowing_zero_does_nothing() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -71,7 +71,7 @@ fn test_borrowing_zero_does_nothing() { } #[test] -fn test_cannot_borrow_above_max_ltv() { +fn cannot_borrow_above_max_ltv() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -105,7 +105,7 @@ fn test_cannot_borrow_above_max_ltv() { } #[test] -fn test_success_when_new_debt_asset() { +fn success_when_new_debt_asset() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -160,7 +160,7 @@ fn test_success_when_new_debt_asset() { } #[test] -fn test_debt_shares_with_debt_amount() { +fn debt_shares_with_debt_amount() { let coin_info = uosmo_info(); let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); diff --git a/contracts/credit-manager/tests/test_coin_balances.rs b/contracts/credit-manager/tests/test_coin_balances.rs index 1a4865b36..e72b9a83f 100644 --- a/contracts/credit-manager/tests/test_coin_balances.rs +++ b/contracts/credit-manager/tests/test_coin_balances.rs @@ -10,7 +10,7 @@ use crate::helpers::{assert_err, uosmo_info, AccountToFund, MockEnv}; pub mod helpers; #[test] -fn test_only_rover_can_call_update_coin_balances() { +fn only_rover_can_call_update_coin_balances() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); @@ -26,7 +26,7 @@ fn test_only_rover_can_call_update_coin_balances() { } #[test] -fn test_user_does_not_have_enough_to_pay_diff() { +fn user_does_not_have_enough_to_pay_diff() { let osmo_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -67,7 +67,7 @@ fn test_user_does_not_have_enough_to_pay_diff() { } #[test] -fn test_user_gets_rebalanced_down() { +fn user_gets_rebalanced_down() { let osmo_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -105,7 +105,7 @@ fn test_user_gets_rebalanced_down() { } #[test] -fn test_user_gets_rebalanced_up() { +fn user_gets_rebalanced_up() { let osmo_info = uosmo_info(); let user = Addr::unchecked("user"); diff --git a/contracts/credit-manager/tests/test_create_credit_account.rs b/contracts/credit-manager/tests/test_create_credit_account.rs index 8049d56e1..3fa33ba73 100644 --- a/contracts/credit-manager/tests/test_create_credit_account.rs +++ b/contracts/credit-manager/tests/test_create_credit_account.rs @@ -7,7 +7,7 @@ use crate::helpers::MockEnv; pub mod helpers; #[test] -fn test_create_credit_account_fails_without_nft_contract_set() { +fn create_credit_account_fails_without_nft_contract_set() { let mut mock = MockEnv::new().no_nft_contract().build().unwrap(); let user = Addr::unchecked("user"); let res = mock.create_credit_account(&user); @@ -18,7 +18,7 @@ fn test_create_credit_account_fails_without_nft_contract_set() { } #[test] -fn test_create_credit_account_fails_without_nft_contract_owner() { +fn create_credit_account_fails_without_nft_contract_owner() { let mut mock = MockEnv::new().no_nft_contract_minter().build().unwrap(); let user = Addr::unchecked("user"); @@ -30,7 +30,7 @@ fn test_create_credit_account_fails_without_nft_contract_owner() { } #[test] -fn test_create_credit_account_success() { +fn create_credit_account_success() { let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); diff --git a/contracts/credit-manager/tests/test_deposit.rs b/contracts/credit-manager/tests/test_deposit.rs index 81bb9244a..4562955e1 100644 --- a/contracts/credit-manager/tests/test_deposit.rs +++ b/contracts/credit-manager/tests/test_deposit.rs @@ -12,7 +12,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_only_owner_of_token_can_deposit() { +fn only_owner_of_token_can_deposit() { let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); let account_id = mock.create_credit_account(&user).unwrap(); @@ -35,7 +35,7 @@ fn test_only_owner_of_token_can_deposit() { } #[test] -fn test_deposit_nothing() { +fn deposit_nothing() { let coin_info = uosmo_info(); let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); @@ -58,7 +58,7 @@ fn test_deposit_nothing() { } #[test] -fn test_deposit_but_no_funds() { +fn deposit_but_no_funds() { let coin_info = uosmo_info(); let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); @@ -86,7 +86,7 @@ fn test_deposit_but_no_funds() { } #[test] -fn test_deposit_but_not_enough_funds() { +fn deposit_but_not_enough_funds() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -117,7 +117,7 @@ fn test_deposit_but_not_enough_funds() { } #[test] -fn test_can_only_deposit_allowed_assets() { +fn can_only_deposit_allowed_assets() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -146,7 +146,7 @@ fn test_can_only_deposit_allowed_assets() { } #[test] -fn test_extra_funds_received() { +fn extra_funds_received() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); @@ -176,7 +176,7 @@ fn test_extra_funds_received() { } #[test] -fn test_deposit_success() { +fn deposit_success() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -210,7 +210,7 @@ fn test_deposit_success() { } #[test] -fn test_multiple_deposit_actions() { +fn multiple_deposit_actions() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); diff --git a/contracts/credit-manager/tests/test_dispatch.rs b/contracts/credit-manager/tests/test_dispatch.rs index 3f2cf52b7..f46306441 100644 --- a/contracts/credit-manager/tests/test_dispatch.rs +++ b/contracts/credit-manager/tests/test_dispatch.rs @@ -10,7 +10,7 @@ use crate::helpers::MockEnv; pub mod helpers; #[test] -fn test_dispatch_only_allowed_for_token_owner() { +fn dispatch_only_allowed_for_token_owner() { let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); let account_id = mock.create_credit_account(&user).unwrap(); @@ -28,7 +28,7 @@ fn test_dispatch_only_allowed_for_token_owner() { } #[test] -fn test_nothing_happens_if_no_actions_are_passed() { +fn nothing_happens_if_no_actions_are_passed() { let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); let account_id = mock.create_credit_account(&user).unwrap(); @@ -43,7 +43,7 @@ fn test_nothing_happens_if_no_actions_are_passed() { } #[test] -fn test_only_rover_can_execute_callbacks() { +fn only_rover_can_execute_callbacks() { let mut mock = MockEnv::new().build().unwrap(); let external_user = Addr::unchecked("external_user"); diff --git a/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs b/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs index efa23e15d..a30dd6035 100644 --- a/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs +++ b/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs @@ -3,7 +3,7 @@ use crate::helpers::{build_mock_coin_infos, MockEnv}; pub mod helpers; #[test] -fn test_pagination_on_allowed_coins_query_works() { +fn pagination_on_allowed_coins_query_works() { let allowed_coins = build_mock_coin_infos(32); let mock = MockEnv::new().allowed_coins(&build_mock_coin_infos(32)).build().unwrap(); diff --git a/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs b/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs deleted file mode 100644 index 8b1378917..000000000 --- a/contracts/credit-manager/tests/test_enumerate_allowed_vaults.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/contracts/credit-manager/tests/test_enumerate_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs index eb152dae7..cfb07c8a5 100644 --- a/contracts/credit-manager/tests/test_enumerate_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs @@ -6,7 +6,7 @@ use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; pub mod helpers; #[test] -fn test_pagination_on_all_coin_balances_query_works() { +fn pagination_on_all_coin_balances_query_works() { let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); let user_c = Addr::unchecked("user_c"); diff --git a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs index 7d3072a94..e856d8be3 100644 --- a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs @@ -7,7 +7,7 @@ use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; pub mod helpers; #[test] -fn test_pagination_on_all_debt_shares_query_works() { +fn pagination_on_all_debt_shares_query_works() { let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); let user_c = Addr::unchecked("user_c"); diff --git a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs index 9b264a169..7055c8f63 100644 --- a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs @@ -7,7 +7,7 @@ use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; pub mod helpers; #[test] -fn test_pagination_on_all_total_debt_shares_query_works() { +fn pagination_on_all_total_debt_shares_query_works() { let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); let user_c = Addr::unchecked("user_c"); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index 87a9e4d51..ed843498d 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -9,7 +9,7 @@ pub mod helpers; #[test] #[ignore] // Test ignored due to Fields limitation on vault position amounts -fn test_pagination_on_all_vault_coin_balances_query_works() { +fn pagination_on_all_vault_coin_balances_query_works() { let lp_token = lp_token_info(); let user_a = Addr::unchecked("user_a"); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_configs.rs b/contracts/credit-manager/tests/test_enumerate_vault_configs.rs index 7f1f23434..155f10acf 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_configs.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_configs.rs @@ -5,7 +5,7 @@ use crate::helpers::{assert_contents_equal, build_mock_vaults, MockEnv}; pub mod helpers; #[test] -fn test_pagination_on_vault_configs_query_works() { +fn pagination_on_vault_configs_query_works() { let vault_configs = build_mock_vaults(32); let mock = MockEnv::new().vault_configs(&vault_configs).build().unwrap(); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index 97ac84477..561ca566d 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -10,7 +10,7 @@ pub mod helpers; #[test] #[ignore] // Test ignored due to Fields limitation on vault position amounts -fn test_pagination_on_all_vault_positions_query_works() { +fn pagination_on_all_vault_positions_query_works() { let lp_token = lp_token_info(); let user_a = Addr::unchecked("user_a"); diff --git a/contracts/credit-manager/tests/test_fields_vault_limit.rs b/contracts/credit-manager/tests/test_fields_vault_limit.rs index 6353fa83e..af76f8dce 100644 --- a/contracts/credit-manager/tests/test_fields_vault_limit.rs +++ b/contracts/credit-manager/tests/test_fields_vault_limit.rs @@ -11,7 +11,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_can_only_have_a_single_vault_position() { +fn can_only_have_a_single_vault_position() { let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); diff --git a/contracts/credit-manager/tests/test_flagged_contract.rs b/contracts/credit-manager/tests/test_flagged_contract.rs index 7c1d45cad..a1b5ef56d 100644 --- a/contracts/credit-manager/tests/test_flagged_contract.rs +++ b/contracts/credit-manager/tests/test_flagged_contract.rs @@ -7,7 +7,7 @@ use crate::helpers::MockEnv; pub mod helpers; #[test] -fn test_addresses_in_config_cannot_execute_msgs() { +fn addresses_in_config_cannot_execute_msgs() { let mut mock = MockEnv::new().build().unwrap(); let config = mock.query_config(); let vault_addrs = mock diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index ad4e9d60d..fe5c314ca 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -30,7 +30,7 @@ pub mod helpers; /// liquidatable: false /// above_max_ltv: false #[test] -fn test_only_assets_with_no_debts() { +fn only_assets_with_no_debts() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -78,7 +78,7 @@ fn test_only_assets_with_no_debts() { /// liquidatable: false /// above_max_ltv: false #[test] -fn test_terra_ragnarok() { +fn terra_ragnarok() { let coin_info = CoinInfo { denom: "uluna".to_string(), price: Decimal::from_atomics(100u128, 1).unwrap(), @@ -165,7 +165,7 @@ fn test_terra_ragnarok() { /// liquidatable: true /// above_max_ltv: true #[test] -fn test_debts_no_assets() { +fn debts_no_assets() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -216,7 +216,7 @@ fn test_debts_no_assets() { /// Step 3: User borrows 100 /// AboveMaxLtv error thrown #[test] -fn test_cannot_borrow_more_than_healthy() { +fn cannot_borrow_more_than_healthy() { let coin_info = ujake_info(); let user = Addr::unchecked("user"); @@ -291,7 +291,7 @@ fn test_cannot_borrow_more_than_healthy() { /// Health: liquidatable: true /// above_max_ltv: true #[test] -fn test_cannot_borrow_more_but_not_liquidatable() { +fn cannot_borrow_more_but_not_liquidatable() { let uosmo_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(23654u128, 4).unwrap(), @@ -367,7 +367,7 @@ fn test_cannot_borrow_more_but_not_liquidatable() { /// liquidatable: false /// above_max_ltv: false #[test] -fn test_assets_and_ltv_lqdt_adjusted_value() { +fn assets_and_ltv_lqdt_adjusted_value() { let uosmo_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), @@ -473,7 +473,7 @@ fn test_assets_and_ltv_lqdt_adjusted_value() { /// Deposits 101 osmo /// Test validates User A's debt value & health factors #[test] -fn test_debt_value() { +fn debt_value() { let uosmo_info = CoinInfo { denom: "uosmo".to_string(), price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), @@ -613,7 +613,7 @@ fn test_debt_value() { } #[test] -fn test_delisted_deposits_drop_max_ltv() { +fn delisted_deposits_drop_max_ltv() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); @@ -670,7 +670,7 @@ fn test_delisted_deposits_drop_max_ltv() { } #[test] -fn test_delisted_vaults_drop_max_ltv() { +fn delisted_vaults_drop_max_ltv() { let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); let atom = uatom_info(); diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index eca947dbe..9b47c2e4f 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -12,7 +12,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_owner_set_on_instantiate() { +fn owner_set_on_instantiate() { let owner = "owner_addr"; let mock = MockEnv::new().owner(owner).build().unwrap(); let res = mock.query_config(); @@ -20,7 +20,7 @@ fn test_owner_set_on_instantiate() { } #[test] -fn test_raises_on_invalid_owner_addr() { +fn raises_on_invalid_owner_addr() { let owner = "%%%INVALID%%%"; let res = MockEnv::new().owner(owner).build(); if res.is_ok() { @@ -29,14 +29,14 @@ fn test_raises_on_invalid_owner_addr() { } #[test] -fn test_nft_contract_addr_not_set_on_instantiate() { +fn nft_contract_addr_not_set_on_instantiate() { let mock = MockEnv::new().no_nft_contract().build().unwrap(); let res = mock.query_config(); assert_eq!(res.account_nft, None); } #[test] -fn test_vault_configs_set_on_instantiate() { +fn vault_configs_set_on_instantiate() { let vault_configs = vec![ VaultTestInfo { vault_token_denom: "vault_contract_1".to_string(), @@ -76,7 +76,7 @@ fn test_vault_configs_set_on_instantiate() { } #[test] -fn test_raises_on_invalid_vaults_addr() { +fn raises_on_invalid_vaults_addr() { let mock = MockEnv::new() .pre_deployed_vault( &unlocked_vault_info(), @@ -100,7 +100,7 @@ fn test_raises_on_invalid_vaults_addr() { } #[test] -fn test_instantiate_raises_on_invalid_vaults_config() { +fn instantiate_raises_on_invalid_vaults_config() { let mock = MockEnv::new() .pre_deployed_vault( &VaultTestInfo { @@ -172,7 +172,7 @@ fn test_instantiate_raises_on_invalid_vaults_config() { } #[test] -fn test_duplicate_vaults_raises() { +fn duplicate_vaults_raises() { let mock = MockEnv::new() .pre_deployed_vault(&locked_vault_info(), None) .pre_deployed_vault(&unlocked_vault_info(), None) @@ -183,7 +183,7 @@ fn test_duplicate_vaults_raises() { } #[test] -fn test_allowed_coins_set_on_instantiate() { +fn allowed_coins_set_on_instantiate() { let allowed_coins = vec![ uosmo_info(), uatom_info(), @@ -206,7 +206,7 @@ fn test_allowed_coins_set_on_instantiate() { } #[test] -fn test_duplicate_coins_raises() { +fn duplicate_coins_raises() { let allowed_coins = vec![uosmo_info(), uosmo_info(), uatom_info()]; let mock = MockEnv::new().allowed_coins(&allowed_coins).build(); if mock.is_ok() { @@ -215,7 +215,7 @@ fn test_duplicate_coins_raises() { } #[test] -fn test_red_bank_set_on_instantiate() { +fn red_bank_set_on_instantiate() { let red_bank_addr = "mars_red_bank_contract_123".to_string(); let mock = MockEnv::new().red_bank(&red_bank_addr).build().unwrap(); let res = mock.query_config(); @@ -223,7 +223,7 @@ fn test_red_bank_set_on_instantiate() { } #[test] -fn test_raises_on_invalid_red_bank_addr() { +fn raises_on_invalid_red_bank_addr() { let mock = MockEnv::new().red_bank("%%%INVALID%%%").build(); if mock.is_ok() { panic!("Should have thrown an error"); @@ -231,7 +231,7 @@ fn test_raises_on_invalid_red_bank_addr() { } #[test] -fn test_oracle_set_on_instantiate() { +fn oracle_set_on_instantiate() { let oracle_contract = "oracle_contract_456".to_string(); let mock = MockEnv::new().oracle(&oracle_contract).build().unwrap(); let res = mock.query_config(); @@ -239,7 +239,7 @@ fn test_oracle_set_on_instantiate() { } #[test] -fn test_raises_on_invalid_oracle_addr() { +fn raises_on_invalid_oracle_addr() { let mock = MockEnv::new().oracle("%%%INVALID%%%").build(); if mock.is_ok() { panic!("Should have thrown an error"); @@ -247,7 +247,7 @@ fn test_raises_on_invalid_oracle_addr() { } #[test] -fn test_max_close_factor_set_on_instantiate() { +fn max_close_factor_set_on_instantiate() { let mock = MockEnv::new().build().unwrap(); let res = mock.query_config(); let mock_default = Decimal::from_atomics(5u128, 1).unwrap(); @@ -255,7 +255,7 @@ fn test_max_close_factor_set_on_instantiate() { } #[test] -fn test_max_close_factor_validated() { +fn max_close_factor_validated() { let mock = MockEnv::new().max_close_factor(Decimal::from_atomics(1244u128, 3).unwrap()).build(); if mock.is_ok() { diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_coin.rs index 0b9ddef8d..126eddebe 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_coin.rs @@ -19,7 +19,7 @@ pub mod helpers; // https://docs.google.com/spreadsheets/d/1_Bs1Fc1RLf5IARvaXZ0QjigoMWSJQhhrRUtQ8uyoLdI/edit?pli=1#gid=1857897311 #[test] -fn test_can_only_liquidate_unhealthy_accounts() { +fn can_only_liquidate_unhealthy_accounts() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); @@ -69,7 +69,7 @@ fn test_can_only_liquidate_unhealthy_accounts() { } #[test] -fn test_vault_positions_contribute_to_health() { +fn vault_positions_contribute_to_health() { let atom_info = uatom_info(); let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); @@ -130,7 +130,7 @@ fn test_vault_positions_contribute_to_health() { } #[test] -fn test_liquidatee_does_not_have_requested_asset() { +fn liquidatee_does_not_have_requested_asset() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); let ujake_info = ujake_info(); @@ -183,7 +183,7 @@ fn test_liquidatee_does_not_have_requested_asset() { } #[test] -fn test_liquidatee_does_not_have_debt_coin() { +fn liquidatee_does_not_have_debt_coin() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); let ujake_info = ujake_info(); @@ -251,7 +251,7 @@ fn test_liquidatee_does_not_have_debt_coin() { } #[test] -fn test_liquidator_does_not_have_enough_to_pay_debt() { +fn liquidator_does_not_have_enough_to_pay_debt() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); @@ -307,7 +307,7 @@ fn test_liquidator_does_not_have_enough_to_pay_debt() { } #[test] -fn test_liquidator_left_in_unhealthy_state() { +fn liquidator_left_in_unhealthy_state() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); @@ -365,7 +365,7 @@ fn test_liquidator_left_in_unhealthy_state() { } #[test] -fn test_liquidation_not_profitable_after_calculations() { +fn liquidation_not_profitable_after_calculations() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); let ujake_info = ujake_info(); @@ -433,7 +433,7 @@ fn test_liquidation_not_profitable_after_calculations() { } #[test] -fn test_debt_amount_adjusted_to_close_factor_max() { +fn debt_amount_adjusted_to_close_factor_max() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); let liquidator = Addr::unchecked("liquidator"); @@ -506,7 +506,7 @@ fn test_debt_amount_adjusted_to_close_factor_max() { } #[test] -fn test_debt_amount_adjusted_to_total_debt_for_denom() { +fn debt_amount_adjusted_to_total_debt_for_denom() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); let ujake_info = ujake_info(); @@ -586,7 +586,7 @@ fn test_debt_amount_adjusted_to_total_debt_for_denom() { } #[test] -fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { +fn debt_amount_adjusted_to_max_allowed_by_request_coin() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); let liquidator = Addr::unchecked("liquidator"); @@ -658,7 +658,7 @@ fn test_debt_amount_adjusted_to_max_allowed_by_request_coin() { } #[test] -fn test_debt_amount_no_adjustment() { +fn debt_amount_no_adjustment() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); let liquidator = Addr::unchecked("liquidator"); @@ -729,4 +729,4 @@ fn test_debt_amount_no_adjustment() { } #[test] -fn test_liquidate_with_no_deposited_funds() {} +fn liquidate_with_no_deposited_funds() {} diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 739174287..384434095 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -19,7 +19,7 @@ pub mod helpers; // https://docs.google.com/spreadsheets/d/1rXa_8eKbtp1wQ0Mm1Rny7QzSLsko9D7UQTtO7NrAssA/edit#gid=2127757089 #[test] -fn test_liquidatee_must_have_the_request_vault_position() { +fn liquidatee_must_have_the_request_vault_position() { let uatom = uatom_info(); let uosmo = uosmo_info(); let leverage_vault = unlocked_vault_info(); @@ -69,7 +69,7 @@ fn test_liquidatee_must_have_the_request_vault_position() { } #[test] -fn test_liquidatee_is_not_liquidatable() { +fn liquidatee_is_not_liquidatable() { let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); @@ -126,7 +126,7 @@ fn test_liquidatee_is_not_liquidatable() { } #[test] -fn test_liquidator_does_not_have_debt_coin_in_credit_account() { +fn liquidator_does_not_have_debt_coin_in_credit_account() { let lp_token = lp_token_info(); let ujake = ujake_info(); let leverage_vault = unlocked_vault_info(); @@ -191,7 +191,7 @@ fn test_liquidator_does_not_have_debt_coin_in_credit_account() { } #[test] -fn test_wrong_position_type_sent_for_unlocked_vault() { +fn wrong_position_type_sent_for_unlocked_vault() { let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); @@ -256,7 +256,7 @@ fn test_wrong_position_type_sent_for_unlocked_vault() { } #[test] -fn test_wrong_position_type_sent_for_locked_vault() { +fn wrong_position_type_sent_for_locked_vault() { let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); @@ -307,7 +307,7 @@ fn test_wrong_position_type_sent_for_locked_vault() { } #[test] -fn test_liquidate_unlocked_vault() { +fn liquidate_unlocked_vault() { let lp_token = lp_token_info(); let ujake = ujake_info(); let leverage_vault = unlocked_vault_info(); @@ -393,7 +393,7 @@ fn test_liquidate_unlocked_vault() { } #[test] -fn test_liquidate_locked_vault() { +fn liquidate_locked_vault() { let lp_token = lp_token_info(); let atom = uatom_info(); let leverage_vault = locked_vault_info(); @@ -482,7 +482,7 @@ fn test_liquidate_locked_vault() { } #[test] -fn test_liquidate_unlocking_liquidation_order() { +fn liquidate_unlocking_liquidation_order() { let lp_token = lp_token_info(); let ujake = ujake_info(); let leverage_vault = locked_vault_info(); @@ -595,7 +595,7 @@ fn test_liquidate_unlocking_liquidation_order() { // NOTE: liquidation calculation+adjustments are quite complex, full cases in test_liquidate_coin.rs #[test] -fn test_liquidation_calculation_adjustment() { +fn liquidation_calculation_adjustment() { let lp_token = lp_token_info(); let ujake = ujake_info(); let leverage_vault = unlocked_vault_info(); diff --git a/contracts/credit-manager/tests/test_refund_balances.rs b/contracts/credit-manager/tests/test_refund_balances.rs index 67fabd7fe..ec28e2ff8 100644 --- a/contracts/credit-manager/tests/test_refund_balances.rs +++ b/contracts/credit-manager/tests/test_refund_balances.rs @@ -8,7 +8,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_refund_coin_balances_when_balances() { +fn refund_coin_balances_when_balances() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); @@ -46,7 +46,7 @@ fn test_refund_coin_balances_when_balances() { } #[test] -fn test_refund_coin_balances_when_no_balances() { +fn refund_coin_balances_when_no_balances() { let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index 44a4b2324..ad22dec46 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -14,7 +14,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_only_token_owner_can_repay() { +fn only_token_owner_can_repay() { let coin_info = uosmo_info(); let owner = Addr::unchecked("owner"); let mut mock = MockEnv::new().build().unwrap(); @@ -38,7 +38,7 @@ fn test_only_token_owner_can_repay() { } #[test] -fn test_repaying_with_zero_debt_raises() { +fn repaying_with_zero_debt_raises() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); @@ -66,7 +66,7 @@ fn test_repaying_with_zero_debt_raises() { } #[test] -fn test_raises_when_repaying_what_is_not_owed() { +fn raises_when_repaying_what_is_not_owed() { let uosmo_info = uosmo_info(); let uatom_info = CoinInfo { @@ -120,7 +120,7 @@ fn test_raises_when_repaying_what_is_not_owed() { } #[test] -fn test_raises_when_not_enough_assets_to_repay() { +fn raises_when_not_enough_assets_to_repay() { let uosmo_info = uosmo_info(); let uatom_info = CoinInfo { @@ -167,7 +167,7 @@ fn test_raises_when_not_enough_assets_to_repay() { } #[test] -fn test_repay_less_than_total_debt() { +fn repay_less_than_total_debt() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -254,7 +254,7 @@ fn test_repay_less_than_total_debt() { } #[test] -fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { +fn pays_max_debt_when_attempting_to_repay_more_than_owed() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -302,7 +302,7 @@ fn test_pays_max_debt_when_attempting_to_repay_more_than_owed() { } #[test] -fn test_amount_none_repays_total_debt() { +fn amount_none_repays_total_debt() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); diff --git a/contracts/credit-manager/tests/test_swap.rs b/contracts/credit-manager/tests/test_swap.rs index be8a5504f..b4d5858ee 100644 --- a/contracts/credit-manager/tests/test_swap.rs +++ b/contracts/credit-manager/tests/test_swap.rs @@ -13,7 +13,7 @@ use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv} pub mod helpers; #[test] -fn test_only_token_owner_can_swap_for_account() { +fn only_token_owner_can_swap_for_account() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); @@ -43,7 +43,7 @@ fn test_only_token_owner_can_swap_for_account() { } #[test] -fn test_denom_out_must_be_whitelisted() { +fn denom_out_must_be_whitelisted() { let osmo_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -65,7 +65,7 @@ fn test_denom_out_must_be_whitelisted() { } #[test] -fn test_no_amount_sent() { +fn no_amount_sent() { let osmo_info = uosmo_info(); let atom_info = uatom_info(); @@ -89,7 +89,7 @@ fn test_no_amount_sent() { } #[test] -fn test_user_has_zero_balance_for_swap_req() { +fn user_has_zero_balance_for_swap_req() { let osmo_info = uosmo_info(); let atom_info = uatom_info(); @@ -120,7 +120,7 @@ fn test_user_has_zero_balance_for_swap_req() { } #[test] -fn test_user_does_not_have_enough_balance_for_swap_req() { +fn user_does_not_have_enough_balance_for_swap_req() { let osmo_info = uosmo_info(); let atom_info = uatom_info(); @@ -160,7 +160,7 @@ fn test_user_does_not_have_enough_balance_for_swap_req() { } #[test] -fn test_swap_success_with_specified_amount() { +fn swap_success_with_specified_amount() { let atom_info = uatom_info(); let osmo_info = uosmo_info(); @@ -207,7 +207,7 @@ fn test_swap_success_with_specified_amount() { } #[test] -fn test_swap_success_with_amount_none() { +fn swap_success_with_amount_none() { let atom_info = uatom_info(); let osmo_info = uosmo_info(); diff --git a/contracts/credit-manager/tests/test_update_admin.rs b/contracts/credit-manager/tests/test_update_admin.rs index 77c84b735..76d1bdce2 100644 --- a/contracts/credit-manager/tests/test_update_admin.rs +++ b/contracts/credit-manager/tests/test_update_admin.rs @@ -10,7 +10,7 @@ use crate::helpers::{assert_err, MockEnv}; pub mod helpers; #[test] -fn test_initialized_state() { +fn initialized_state() { let mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); @@ -19,7 +19,7 @@ fn test_initialized_state() { } #[test] -fn test_propose_new_owner() { +fn propose_new_owner() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); @@ -51,7 +51,7 @@ fn test_propose_new_owner() { } #[test] -fn test_clear_proposed() { +fn clear_proposed() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); @@ -88,7 +88,7 @@ fn test_clear_proposed() { } #[test] -fn test_accept_owner_role() { +fn accept_owner_role() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); @@ -118,7 +118,7 @@ fn test_accept_owner_role() { } #[test] -fn test_abolish_owner_role() { +fn abolish_owner_role() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 804df207c..d458e427d 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -24,7 +24,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_only_owner_can_update_config() { +fn only_owner_can_update_config() { let mut mock = MockEnv::new().build().unwrap(); let new_owner = Addr::unchecked("bad_guy"); @@ -48,7 +48,7 @@ fn test_only_owner_can_update_config() { } #[test] -fn test_raises_on_invalid_vaults_config() { +fn raises_on_invalid_vaults_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); @@ -132,7 +132,7 @@ fn test_raises_on_invalid_vaults_config() { } #[test] -fn test_update_config_works_with_full_config() { +fn update_config_works_with_full_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let original_allowed_coins = mock.query_allowed_coins(None, None); @@ -204,7 +204,7 @@ fn test_update_config_works_with_full_config() { } #[test] -fn test_update_config_works_with_some_config() { +fn update_config_works_with_some_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let original_allowed_coins = mock.query_allowed_coins(None, None); @@ -247,7 +247,7 @@ fn test_update_config_works_with_some_config() { } #[test] -fn test_update_config_removes_properly() { +fn update_config_removes_properly() { let uatom = uatom_info(); let uosmo = uosmo_info(); let leverage_vault = locked_vault_info(); @@ -283,7 +283,7 @@ fn test_update_config_removes_properly() { } #[test] -fn test_update_config_does_nothing_when_nothing_is_passed() { +fn update_config_does_nothing_when_nothing_is_passed() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let original_vault_configs = mock.query_vault_configs(None, None); @@ -311,7 +311,7 @@ fn test_update_config_does_nothing_when_nothing_is_passed() { } #[test] -fn test_max_close_factor_validated_on_update() { +fn max_close_factor_validated_on_update() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( @@ -331,7 +331,7 @@ fn test_max_close_factor_validated_on_update() { } #[test] -fn test_raises_on_duplicate_vault_configs() { +fn raises_on_duplicate_vault_configs() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( @@ -376,7 +376,7 @@ fn test_raises_on_duplicate_vault_configs() { } #[test] -fn test_raises_on_duplicate_coin_configs() { +fn raises_on_duplicate_coin_configs() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( diff --git a/contracts/credit-manager/tests/test_update_nft_config.rs b/contracts/credit-manager/tests/test_update_nft_config.rs index ae4a79a77..7f3470389 100644 --- a/contracts/credit-manager/tests/test_update_nft_config.rs +++ b/contracts/credit-manager/tests/test_update_nft_config.rs @@ -13,7 +13,7 @@ use crate::helpers::{assert_err, MockEnv}; pub mod helpers; #[test] -fn test_only_owner_can_update_config() { +fn _only_owner_can_update_config() { let mut mock = MockEnv::new().build().unwrap(); let bad_guy = Addr::unchecked("bad_guy"); @@ -44,7 +44,7 @@ fn test_only_owner_can_update_config() { } #[test] -fn test_raises_on_invalid_config() { +fn _raises_on_invalid_config() { let mut mock = MockEnv::new().build().unwrap(); let res = mock.update_nft_config( @@ -61,7 +61,7 @@ fn test_raises_on_invalid_config() { } #[test] -fn test_update_config_works_with_full_config() { +fn _update_config_works_with_full_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_nft_config(); @@ -85,7 +85,7 @@ fn test_update_config_works_with_full_config() { } #[test] -fn test_update_config_works_with_some_config() { +fn _update_config_works_with_some_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_nft_config(); diff --git a/contracts/credit-manager/tests/test_utilization_query.rs b/contracts/credit-manager/tests/test_utilization_query.rs index 45429f992..ee396188e 100644 --- a/contracts/credit-manager/tests/test_utilization_query.rs +++ b/contracts/credit-manager/tests/test_utilization_query.rs @@ -12,7 +12,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_utilization_is_zero() { +fn utilization_is_zero() { let mock = MockEnv::new().vault_configs(&[unlocked_vault_info()]).build().unwrap(); let vault_infos = mock.query_vault_configs(None, None); assert_eq!(1, vault_infos.len()); @@ -22,7 +22,7 @@ fn test_utilization_is_zero() { } #[test] -fn test_utilization_if_cap_is_base_denom() { +fn utilization_if_cap_is_base_denom() { let user = Addr::unchecked("user"); let base_info = CoinInfo { denom: "base_denom".to_string(), @@ -86,7 +86,7 @@ fn test_utilization_if_cap_is_base_denom() { Utilization denominated in uosmo = 1_000_000 * 2.3654 / .25 ---> 9461600 */ #[test] -fn test_utilization_in_other_denom() { +fn utilization_in_other_denom() { let osmo_info = uosmo_info(); let jake_info = ujake_info(); diff --git a/contracts/credit-manager/tests/test_vault_enter.rs b/contracts/credit-manager/tests/test_vault_enter.rs index a5d8f7dc5..3d75a0fc6 100644 --- a/contracts/credit-manager/tests/test_vault_enter.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -19,7 +19,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_only_account_owner_can_take_action() { +fn only_account_owner_can_take_action() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); @@ -49,7 +49,7 @@ fn test_only_account_owner_can_take_action() { } #[test] -fn test_deposit_denom_is_whitelisted() { +fn deposit_denom_is_whitelisted() { let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); @@ -73,7 +73,7 @@ fn test_deposit_denom_is_whitelisted() { } #[test] -fn test_vault_is_whitelisted() { +fn vault_is_whitelisted() { let uatom = uatom_info(); let uosmo = uosmo_info(); let leverage_vault = VaultTestInfo { @@ -111,7 +111,7 @@ fn test_vault_is_whitelisted() { } #[test] -fn test_deposited_coin_matches_vault_requirements() { +fn deposited_coin_matches_vault_requirements() { let uatom = uatom_info(); let leverage_vault = unlocked_vault_info(); @@ -143,7 +143,7 @@ fn test_deposited_coin_matches_vault_requirements() { } #[test] -fn test_fails_if_not_enough_funds_for_implied_deposit() { +fn fails_if_not_enough_funds_for_implied_deposit() { let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); @@ -179,7 +179,7 @@ fn test_fails_if_not_enough_funds_for_implied_deposit() { } #[test] -fn test_fails_if_not_enough_funds_for_enumerated_deposit() { +fn fails_if_not_enough_funds_for_enumerated_deposit() { let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); @@ -217,7 +217,7 @@ fn test_fails_if_not_enough_funds_for_enumerated_deposit() { } #[test] -fn test_successful_deposit_into_locked_vault() { +fn successful_deposit_into_locked_vault() { let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); @@ -270,7 +270,7 @@ fn test_successful_deposit_into_locked_vault() { } #[test] -fn test_successful_deposit_into_unlocked_vault() { +fn successful_deposit_into_unlocked_vault() { let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); @@ -321,7 +321,7 @@ fn test_successful_deposit_into_unlocked_vault() { } #[test] -fn test_vault_deposit_must_be_under_cap() { +fn vault_deposit_must_be_under_cap() { let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); @@ -399,7 +399,7 @@ fn test_vault_deposit_must_be_under_cap() { } #[test] -fn test_successful_deposit_with_implied_full_balance_amount() { +fn successful_deposit_with_implied_full_balance_amount() { let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); diff --git a/contracts/credit-manager/tests/test_vault_exit.rs b/contracts/credit-manager/tests/test_vault_exit.rs index 91eb1a265..4c8287373 100644 --- a/contracts/credit-manager/tests/test_vault_exit.rs +++ b/contracts/credit-manager/tests/test_vault_exit.rs @@ -17,7 +17,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_only_owner_of_token_can_withdraw_from_vault() { +fn only_owner_of_token_can_withdraw_from_vault() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); @@ -44,7 +44,7 @@ fn test_only_owner_of_token_can_withdraw_from_vault() { } #[test] -fn test_can_only_take_action_on_whitelisted_vaults() { +fn can_only_take_action_on_whitelisted_vaults() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); @@ -64,7 +64,7 @@ fn test_can_only_take_action_on_whitelisted_vaults() { } #[test] -fn test_no_unlocked_vault_coins_to_withdraw() { +fn no_unlocked_vault_coins_to_withdraw() { let uatom = uatom_info(); let uosmo = uosmo_info(); @@ -109,7 +109,7 @@ fn test_no_unlocked_vault_coins_to_withdraw() { } #[test] -fn test_withdraw_with_unlocked_vault_coins() { +fn withdraw_with_unlocked_vault_coins() { let lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); diff --git a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs index 33e870b8f..11a40abbe 100644 --- a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs +++ b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs @@ -18,7 +18,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_only_owner_can_withdraw_unlocked_for_account() { +fn only_owner_can_withdraw_unlocked_for_account() { let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); @@ -48,7 +48,7 @@ fn test_only_owner_can_withdraw_unlocked_for_account() { } #[test] -fn test_can_only_take_action_on_whitelisted_vaults() { +fn can_only_take_action_on_whitelisted_vaults() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); @@ -69,7 +69,7 @@ fn test_can_only_take_action_on_whitelisted_vaults() { } #[test] -fn test_not_owner_of_unlocking_position() { +fn not_owner_of_unlocking_position() { let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); @@ -141,7 +141,7 @@ fn test_not_owner_of_unlocking_position() { } #[test] -fn test_unlocking_position_not_ready_time() { +fn unlocking_position_not_ready_time() { let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); @@ -194,7 +194,7 @@ fn test_unlocking_position_not_ready_time() { } #[test] -fn test_unlocking_position_not_ready_blocks() { +fn unlocking_position_not_ready_blocks() { let lp_token = lp_token_info(); let leverage_vault = generate_mock_vault(Some(Duration::Height(100_000))); @@ -247,7 +247,7 @@ fn test_unlocking_position_not_ready_blocks() { } #[test] -fn test_withdraw_unlock_success_time_expiring() { +fn withdraw_unlock_success_time_expiring() { let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); @@ -329,7 +329,7 @@ fn test_withdraw_unlock_success_time_expiring() { } #[test] -fn test_withdraw_unlock_success_block_expiring() { +fn withdraw_unlock_success_block_expiring() { let lp_token = lp_token_info(); let leverage_vault = generate_mock_vault(Some(Duration::Height(100_000))); diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs index 71cbaa222..243fe215e 100644 --- a/contracts/credit-manager/tests/test_vault_request_unlock.rs +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -15,7 +15,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_only_owner_can_request_unlocked() { +fn only_owner_can_request_unlocked() { let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); @@ -45,7 +45,7 @@ fn test_only_owner_can_request_unlocked() { } #[test] -fn test_can_only_take_action_on_whitelisted_vaults() { +fn can_only_take_action_on_whitelisted_vaults() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); @@ -66,7 +66,7 @@ fn test_can_only_take_action_on_whitelisted_vaults() { } #[test] -fn test_request_when_unnecessary() { +fn request_when_unnecessary() { let leverage_vault = unlocked_vault_info(); let user = Addr::unchecked("user"); @@ -94,7 +94,7 @@ fn test_request_when_unnecessary() { } #[test] -fn test_no_vault_tokens_for_request() { +fn no_vault_tokens_for_request() { let leverage_vault = locked_vault_info(); let user = Addr::unchecked("user"); @@ -132,7 +132,7 @@ fn test_no_vault_tokens_for_request() { } #[test] -fn test_not_enough_vault_tokens_for_request() { +fn not_enough_vault_tokens_for_request() { let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); @@ -186,7 +186,7 @@ fn test_not_enough_vault_tokens_for_request() { } #[test] -fn test_request_unlocked() { +fn request_unlocked() { let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); @@ -260,7 +260,7 @@ fn test_request_unlocked() { } #[test] -fn test_cannot_request_more_than_max() { +fn cannot_request_more_than_max() { let lp_token = lp_token_info(); let leverage_vault = locked_vault_info(); diff --git a/contracts/credit-manager/tests/test_withdraw.rs b/contracts/credit-manager/tests/test_withdraw.rs index b681b54e0..af2a775ac 100644 --- a/contracts/credit-manager/tests/test_withdraw.rs +++ b/contracts/credit-manager/tests/test_withdraw.rs @@ -9,7 +9,7 @@ use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv} pub mod helpers; #[test] -fn test_only_owner_of_token_can_withdraw() { +fn only_owner_of_token_can_withdraw() { let coin_info = uosmo_info(); let owner = Addr::unchecked("owner"); let mut mock = MockEnv::new().build().unwrap(); @@ -36,7 +36,7 @@ fn test_only_owner_of_token_can_withdraw() { } #[test] -fn test_withdraw_nothing() { +fn withdraw_nothing() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); @@ -56,7 +56,7 @@ fn test_withdraw_nothing() { } #[test] -fn test_withdraw_but_no_funds() { +fn withdraw_but_no_funds() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); @@ -83,7 +83,7 @@ fn test_withdraw_but_no_funds() { } #[test] -fn test_withdraw_but_not_enough_funds() { +fn withdraw_but_not_enough_funds() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -117,7 +117,7 @@ fn test_withdraw_but_not_enough_funds() { } #[test] -fn test_cannot_withdraw_more_than_healthy() { +fn cannot_withdraw_more_than_healthy() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -154,7 +154,7 @@ fn test_cannot_withdraw_more_than_healthy() { } #[test] -fn test_withdraw_success() { +fn withdraw_success() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -187,7 +187,7 @@ fn test_withdraw_success() { } #[test] -fn test_multiple_withdraw_actions() { +fn multiple_withdraw_actions() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs index efe094e84..051f30f3e 100644 --- a/contracts/credit-manager/tests/test_zap_provide.rs +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -17,7 +17,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_only_token_owner_can_zap_for_account() { +fn only_token_owner_can_zap_for_account() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); @@ -44,7 +44,7 @@ fn test_only_token_owner_can_zap_for_account() { } #[test] -fn test_does_not_have_enough_tokens_to_provide_liq() { +fn does_not_have_enough_tokens_to_provide_liq() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); @@ -86,7 +86,7 @@ fn test_does_not_have_enough_tokens_to_provide_liq() { } #[test] -fn test_lp_token_out_must_be_whitelisted() { +fn lp_token_out_must_be_whitelisted() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); @@ -121,7 +121,7 @@ fn test_lp_token_out_must_be_whitelisted() { } #[test] -fn test_coins_in_must_be_whitelisted() { +fn coins_in_must_be_whitelisted() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); @@ -152,7 +152,7 @@ fn test_coins_in_must_be_whitelisted() { } #[test] -fn test_min_received_too_high() { +fn min_received_too_high() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); @@ -190,7 +190,7 @@ fn test_min_received_too_high() { } #[test] -fn test_wrong_denom_provided() { +fn wrong_denom_provided() { let atom = uatom_info(); let jake = ujake_info(); let lp_token = lp_token_info(); @@ -231,7 +231,7 @@ fn test_wrong_denom_provided() { } #[test] -fn test_successful_zap() { +fn successful_zap() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); @@ -299,7 +299,7 @@ fn test_successful_zap() { } #[test] -fn test_can_provide_unbalanced() { +fn can_provide_unbalanced() { let atom = uatom_info(); let lp_token = lp_token_info(); @@ -371,7 +371,7 @@ fn test_can_provide_unbalanced() { } #[test] -fn test_order_does_not_matter() { +fn order_does_not_matter() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index 1bc592873..16d490996 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -18,7 +18,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_only_token_owner_can_unzap_for_account() { +fn only_token_owner_can_unzap_for_account() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); @@ -46,7 +46,7 @@ fn test_only_token_owner_can_unzap_for_account() { } #[test] -fn test_lp_token_in_must_be_whitelisted() { +fn lp_token_in_must_be_whitelisted() { let lp_token = lp_token_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); @@ -65,7 +65,7 @@ fn test_lp_token_in_must_be_whitelisted() { } #[test] -fn test_coins_out_must_be_whitelisted() { +fn coins_out_must_be_whitelisted() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); @@ -122,7 +122,7 @@ fn test_coins_out_must_be_whitelisted() { } #[test] -fn test_does_not_have_the_tokens_to_withdraw_liq() { +fn does_not_have_the_tokens_to_withdraw_liq() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); @@ -169,7 +169,7 @@ fn test_does_not_have_the_tokens_to_withdraw_liq() { } #[test] -fn test_amount_zero_passed() { +fn amount_zero_passed() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); @@ -214,7 +214,7 @@ fn test_amount_zero_passed() { } #[test] -fn test_amount_none_passed_with_no_balance() { +fn amount_none_passed_with_no_balance() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); @@ -244,7 +244,7 @@ fn test_amount_none_passed_with_no_balance() { } #[test] -fn test_successful_unzap_specified_amount() { +fn successful_unzap_specified_amount() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); @@ -307,7 +307,7 @@ fn test_successful_unzap_specified_amount() { } #[test] -fn test_successful_unzap_unspecified_amount() { +fn successful_unzap_unspecified_amount() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); diff --git a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs index 1d6c69853..3ce435011 100644 --- a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs +++ b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs @@ -13,7 +13,7 @@ use crate::helpers::instantiate_contract; pub mod helpers; #[test] -fn test_enumerating_routes() { +fn enumerating_routes() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let signer = app diff --git a/contracts/swapper/osmosis/tests/test_estimate.rs b/contracts/swapper/osmosis/tests/test_estimate.rs index b14eea59b..c566fe3e9 100644 --- a/contracts/swapper/osmosis/tests/test_estimate.rs +++ b/contracts/swapper/osmosis/tests/test_estimate.rs @@ -11,7 +11,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_error_on_route_not_found() { +fn error_on_route_not_found() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let owner = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); @@ -30,7 +30,7 @@ fn test_error_on_route_not_found() { } #[test] -fn test_estimate_swap_one_step() { +fn estimate_swap_one_step() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -81,7 +81,7 @@ fn test_estimate_swap_one_step() { } #[test] -fn test_estimate_swap_multi_step() { +fn estimate_swap_multi_step() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); diff --git a/contracts/swapper/osmosis/tests/test_instantiate.rs b/contracts/swapper/osmosis/tests/test_instantiate.rs index 2f39da155..2dc880eed 100644 --- a/contracts/swapper/osmosis/tests/test_instantiate.rs +++ b/contracts/swapper/osmosis/tests/test_instantiate.rs @@ -8,7 +8,7 @@ use crate::helpers::{instantiate_contract, wasm_file}; pub mod helpers; #[test] -fn test_owner_set_on_instantiate() { +fn owner_set_on_instantiate() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); @@ -20,7 +20,7 @@ fn test_owner_set_on_instantiate() { } #[test] -fn test_raises_on_invalid_owner_addr() { +fn raises_on_invalid_owner_addr() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); diff --git a/contracts/swapper/osmosis/tests/test_set_route.rs b/contracts/swapper/osmosis/tests/test_set_route.rs index 805858a22..16dac2908 100644 --- a/contracts/swapper/osmosis/tests/test_set_route.rs +++ b/contracts/swapper/osmosis/tests/test_set_route.rs @@ -11,7 +11,7 @@ use crate::helpers::{assert_err, instantiate_contract}; pub mod helpers; #[test] -fn test_only_owner_can_set_routes() { +fn only_owner_can_set_routes() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -47,7 +47,7 @@ fn test_only_owner_can_set_routes() { } #[test] -fn test_must_pass_at_least_one_step() { +fn must_pass_at_least_one_step() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -77,7 +77,7 @@ fn test_must_pass_at_least_one_step() { } #[test] -fn test_must_be_available_in_osmosis() { +fn must_be_available_in_osmosis() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -110,7 +110,7 @@ fn test_must_be_available_in_osmosis() { } #[test] -fn test_step_does_not_contain_input_denom() { +fn step_does_not_contain_input_denom() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -152,7 +152,7 @@ fn test_step_does_not_contain_input_denom() { } #[test] -fn test_step_does_not_contain_output_denom() { +fn step_does_not_contain_output_denom() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -194,7 +194,7 @@ fn test_step_does_not_contain_output_denom() { } #[test] -fn test_steps_do_not_loop() { +fn steps_do_not_loop() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -267,7 +267,7 @@ fn test_steps_do_not_loop() { } #[test] -fn test_step_output_does_not_match() { +fn step_output_does_not_match() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -310,7 +310,7 @@ fn test_step_output_does_not_match() { } #[test] -fn test_set_route_success() { +fn set_route_success() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); diff --git a/contracts/swapper/osmosis/tests/test_swap.rs b/contracts/swapper/osmosis/tests/test_swap.rs index 7831c03ed..1d51ed49a 100644 --- a/contracts/swapper/osmosis/tests/test_swap.rs +++ b/contracts/swapper/osmosis/tests/test_swap.rs @@ -12,7 +12,7 @@ use crate::helpers::{ pub mod helpers; #[test] -fn test_transfer_callback_only_internal() { +fn transfer_callback_only_internal() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -45,7 +45,7 @@ fn test_transfer_callback_only_internal() { } #[test] -fn test_swap_exact_in_slippage_too_high() { +fn swap_exact_in_slippage_too_high() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -103,7 +103,7 @@ fn test_swap_exact_in_slippage_too_high() { } #[test] -fn test_swap_exact_in_success() { +fn swap_exact_in_success() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); diff --git a/contracts/swapper/osmosis/tests/test_update_admin.rs b/contracts/swapper/osmosis/tests/test_update_admin.rs index a6fe10c0a..ea74bc9c6 100644 --- a/contracts/swapper/osmosis/tests/test_update_admin.rs +++ b/contracts/swapper/osmosis/tests/test_update_admin.rs @@ -9,7 +9,7 @@ use crate::helpers::instantiate_contract; pub mod helpers; #[test] -fn test_initial_state() { +fn initial_state() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -24,7 +24,7 @@ fn test_initial_state() { } #[test] -fn test_only_owner_can_propose() { +fn only_owner_can_propose() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -46,7 +46,7 @@ fn test_only_owner_can_propose() { } #[test] -fn test_propose_new_owner() { +fn propose_new_owner() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -72,7 +72,7 @@ fn test_propose_new_owner() { } #[test] -fn test_only_owner_can_clear_proposed() { +fn only_owner_can_clear_proposed() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -103,7 +103,7 @@ fn test_only_owner_can_clear_proposed() { } #[test] -fn test_clear_proposed() { +fn clear_proposed() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -137,7 +137,7 @@ fn test_clear_proposed() { } #[test] -fn test_only_proposed_owner_can_accept_role() { +fn only_proposed_owner_can_accept_role() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -167,7 +167,7 @@ fn test_only_proposed_owner_can_accept_role() { } #[test] -fn test_accept_owner_role() { +fn accept_owner_role() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); diff --git a/contracts/zapper/osmosis/tests/test_callback.rs b/contracts/zapper/osmosis/tests/test_callback.rs index 75b6d6f78..352c75053 100644 --- a/contracts/zapper/osmosis/tests/test_callback.rs +++ b/contracts/zapper/osmosis/tests/test_callback.rs @@ -7,7 +7,7 @@ use crate::helpers::{assert_err, instantiate_contract}; pub mod helpers; #[test] -fn test_only_contract_itself_can_callback() { +fn only_contract_itself_can_callback() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); diff --git a/contracts/zapper/osmosis/tests/test_provide_liquidity.rs b/contracts/zapper/osmosis/tests/test_provide_liquidity.rs index c74045d82..bf62dfc9e 100644 --- a/contracts/zapper/osmosis/tests/test_provide_liquidity.rs +++ b/contracts/zapper/osmosis/tests/test_provide_liquidity.rs @@ -8,7 +8,7 @@ use crate::helpers::{assert_err, instantiate_contract, query_balance}; pub mod helpers; #[test] -fn test_provide_liquidity_with_invalid_lp_token() { +fn provide_liquidity_with_invalid_lp_token() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -34,7 +34,7 @@ fn test_provide_liquidity_with_invalid_lp_token() { } #[test] -fn test_provide_liquidity_with_invalid_coins() { +fn provide_liquidity_with_invalid_coins() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -66,7 +66,7 @@ fn test_provide_liquidity_with_invalid_coins() { } #[test] -fn test_provide_liquidity_with_min_not_received() { +fn provide_liquidity_with_min_not_received() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -128,7 +128,7 @@ fn test_provide_liquidity_with_min_not_received() { } #[test] -fn test_provide_liquidity_with_one_coin() { +fn provide_liquidity_with_one_coin() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -199,7 +199,7 @@ fn test_provide_liquidity_with_one_coin() { } #[test] -fn test_provide_liquidity_with_two_balanced_coins() { +fn provide_liquidity_with_two_balanced_coins() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -272,7 +272,7 @@ fn test_provide_liquidity_with_two_balanced_coins() { } #[test] -fn test_provide_liquidity_with_two_unbalanced_coins() { +fn provide_liquidity_with_two_unbalanced_coins() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -361,7 +361,7 @@ fn test_provide_liquidity_with_two_unbalanced_coins() { } #[test] -fn test_provide_liquidity_with_different_recipient() { +fn provide_liquidity_with_different_recipient() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); diff --git a/contracts/zapper/osmosis/tests/test_queries.rs b/contracts/zapper/osmosis/tests/test_queries.rs index 2de4a1d5d..bd1572682 100644 --- a/contracts/zapper/osmosis/tests/test_queries.rs +++ b/contracts/zapper/osmosis/tests/test_queries.rs @@ -10,7 +10,7 @@ use crate::helpers::{assert_err, instantiate_contract}; pub mod helpers; #[test] -fn test_estimate_provide_liquidity_with_invalid_lp_token() { +fn estimate_provide_liquidity_with_invalid_lp_token() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -31,7 +31,7 @@ fn test_estimate_provide_liquidity_with_invalid_lp_token() { } #[test] -fn test_estimate_provide_liquidity_with_invalid_coins() { +fn estimate_provide_liquidity_with_invalid_coins() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -64,7 +64,7 @@ fn test_estimate_provide_liquidity_with_invalid_coins() { } #[test] -fn test_estimate_provide_liquidity_successfully() { +fn estimate_provide_liquidity_successfully() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -100,7 +100,7 @@ fn test_estimate_provide_liquidity_successfully() { } #[test] -fn test_estimate_withdraw_liquidity_with_invalid_lp_token() { +fn estimate_withdraw_liquidity_with_invalid_lp_token() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -120,7 +120,7 @@ fn test_estimate_withdraw_liquidity_with_invalid_lp_token() { } #[test] -fn test_estimate_withdraw_liquidity_successfully() { +fn estimate_withdraw_liquidity_successfully() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); diff --git a/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs b/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs index 98e4599ff..aefe521ee 100644 --- a/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs +++ b/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs @@ -9,7 +9,7 @@ use crate::helpers::{assert_err, instantiate_contract, query_balance}; pub mod helpers; #[test] -fn test_withdraw_liquidity_without_funds() { +fn withdraw_liquidity_without_funds() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -33,7 +33,7 @@ fn test_withdraw_liquidity_without_funds() { } #[test] -fn test_withdraw_liquidity_with_more_than_one_coin_sent() { +fn withdraw_liquidity_with_more_than_one_coin_sent() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -57,7 +57,7 @@ fn test_withdraw_liquidity_with_more_than_one_coin_sent() { } #[test] -fn test_withdraw_liquidity_with_invalid_lp_token() { +fn withdraw_liquidity_with_invalid_lp_token() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -79,7 +79,7 @@ fn test_withdraw_liquidity_with_invalid_lp_token() { } #[test] -fn test_withdraw_liquidity_successfully() { +fn withdraw_liquidity_successfully() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); @@ -166,7 +166,7 @@ fn test_withdraw_liquidity_successfully() { } #[test] -fn test_withdraw_liquidity_with_different_recipient_successfully() { +fn withdraw_liquidity_with_different_recipient_successfully() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); From e4602e0f7883418ae25290a51364ab2feb5e6402 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 23 Jan 2023 11:00:27 +0100 Subject: [PATCH 130/218] Drop maxLTV is vault base token disallowed [Oak audit] (#105) Drop maxLTV is vault base token disallowed --- contracts/credit-manager/src/health.rs | 6 +- contracts/credit-manager/tests/test_health.rs | 72 ++++++++++++++++++- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index e462cc0bf..4e5bfe253 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -101,8 +101,10 @@ fn calculate_vaults_value( let config = VAULT_CONFIGS.load(deps.storage, &v.vault.address)?; let info = v.vault.query_info(&deps.querier)?; - // If vault has been de-listed, drop MaxLTV to zero - let checked_vault_max_ltv = if vault_is_whitelisted(deps.storage, &v.vault)? { + // If vault or base token has been de-listed, drop MaxLTV to zero + let checked_vault_max_ltv = if vault_is_whitelisted(deps.storage, &v.vault)? + && ALLOWED_COINS.contains(deps.storage, &info.base_token) + { config.max_ltv } else { Decimal::zero() diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index fe5c314ca..c4d55c251 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -718,7 +718,7 @@ fn delisted_vaults_drop_max_ltv() { }, }; - // Remove uosmo from the coin whitelist + // Blacklist vault let res = mock.query_config(); mock.update_config( &Addr::unchecked(res.owner.unwrap()), @@ -731,7 +731,75 @@ fn delisted_vaults_drop_max_ltv() { let curr_health = mock.query_health(&account_id); - // // Values should be the same + // Values should be the same + assert_eq!(prev_health.total_debt_value, curr_health.total_debt_value); + assert_eq!(prev_health.total_collateral_value, curr_health.total_collateral_value); + + assert_eq!(prev_health.liquidation_health_factor, curr_health.liquidation_health_factor); + assert_eq!( + prev_health.liquidation_threshold_adjusted_collateral, + curr_health.liquidation_threshold_adjusted_collateral + ); + assert_eq!(prev_health.liquidatable, curr_health.liquidatable); + + // Should have been changed due to de-listing + assert_ne!(prev_health.above_max_ltv, curr_health.above_max_ltv); + assert_ne!(prev_health.max_ltv_adjusted_collateral, curr_health.max_ltv_adjusted_collateral); + assert_ne!(prev_health.max_ltv_health_factor, curr_health.max_ltv_health_factor); + assert_eq!(curr_health.max_ltv_health_factor, Some(Decimal::raw(811881188118811881u128))); +} + +#[test] +fn vault_base_token_delisting_drops_max_ltv() { + let lp_token = lp_token_info(); + let leverage_vault = unlocked_vault_info(); + let atom = uatom_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone(), atom.clone()]) + .vault_configs(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + Borrow(atom.to_coin(100)), + EnterVault { + vault, + coin: lp_token.to_action_coin(200), + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + let prev_health = mock.query_health(&account_id); + + // Remove LP token from the coin whitelist + let res = mock.query_config(); + mock.update_config( + &Addr::unchecked(res.owner.unwrap()), + ConfigUpdates { + allowed_coins: Some(vec![atom.denom]), + ..Default::default() + }, + ) + .unwrap(); + + let curr_health = mock.query_health(&account_id); + + // Values should be the same assert_eq!(prev_health.total_debt_value, curr_health.total_debt_value); assert_eq!(prev_health.total_collateral_value, curr_health.total_collateral_value); From 3978df666190dd07da9fd88af2305f74a86bee1c Mon Sep 17 00:00:00 2001 From: Larry Engineer <26318510+larry0x@users.noreply.github.com> Date: Mon, 23 Jan 2023 11:02:31 +0000 Subject: [PATCH 131/218] Use the correct SPDX license identifier (#104) * use the correct SPDX license identifier * format build profile * format dependencies * use two space indentation --- Cargo.toml | 70 +++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a37b30ff3..3e45555a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,30 +1,30 @@ [workspace] members = [ - "contracts/account-nft", - "contracts/credit-manager", - "contracts/swapper/*", - "contracts/zapper/*", - "packages/chains/*", - "packages/health", - "packages/outpost", - "packages/rover", - "packages/math", + "contracts/account-nft", + "contracts/credit-manager", + "contracts/swapper/*", + "contracts/zapper/*", + "packages/chains/*", + "packages/health", + "packages/outpost", + "packages/rover", + "packages/math", - # Mock contracts - "contracts/mock-oracle", - "contracts/mock-red-bank", - "contracts/mock-vault", - "contracts/mock-credit-manager", + # Mock contracts + "contracts/mock-oracle", + "contracts/mock-red-bank", + "contracts/mock-vault", + "contracts/mock-credit-manager", ] [workspace.package] version = "1.0.0" authors = [ - "grod220 ", - "larry_0x ", - "Piotr Babel ", + "grod220 ", + "Larry Engineer ", + "Piotr Babel ", ] -license = "GPL-v3-or-later" +license = "GPL-3.0-or-later" edition = "2021" repository = "https://github.com/mars-protocol/rover" homepage = "https://marsprotocol.io" @@ -59,25 +59,25 @@ mars-owner = "1.0.0" mars-rover = { version = "1.0.0", path = "./packages/rover" } # contracts -mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } -mars-zapper-base = { version = "1.0.0", path = "./contracts/zapper/base" } +mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } +mars-swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } +mars-zapper-base = { version = "1.0.0", path = "./contracts/zapper/base" } # mocks -mars-mock-credit-manager = { version = "1.0.0", path = "./contracts/mock-credit-manager", features = ["library"] } -mars-mock-oracle = { version = "1.0.0", path = "./contracts/mock-oracle", features = ["library"] } -mars-mock-red-bank = { version = "1.0.0", path = "./contracts/mock-red-bank", features = ["library"] } -mars-mock-vault = { version = "1.0.0", path = "./contracts/mock-vault", features = ["library"] } -mars-swapper-mock = { version = "1.0.0", path = "./contracts/swapper/mock", features = ["library"] } -mars-zapper-mock = { version = "1.0.0", path = "./contracts/zapper/mock", features = ["library"] } +mars-mock-credit-manager = { version = "1.0.0", path = "./contracts/mock-credit-manager", features = ["library"] } +mars-mock-oracle = { version = "1.0.0", path = "./contracts/mock-oracle", features = ["library"] } +mars-mock-red-bank = { version = "1.0.0", path = "./contracts/mock-red-bank", features = ["library"] } +mars-mock-vault = { version = "1.0.0", path = "./contracts/mock-vault", features = ["library"] } +mars-swapper-mock = { version = "1.0.0", path = "./contracts/swapper/mock", features = ["library"] } +mars-zapper-mock = { version = "1.0.0", path = "./contracts/zapper/mock", features = ["library"] } [profile.release] -opt-level = 3 -debug = false -rpath = false -lto = true +codegen-units = 1 +debug = false debug-assertions = false -codegen-units = 1 -panic = 'abort' -incremental = false -overflow-checks = true +incremental = false +lto = true +overflow-checks = true +opt-level = 3 +panic = "abort" +rpath = false From 6ef2d8a5e14c80242c5c099c5306add0622b2747 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 23 Jan 2023 22:37:19 +0100 Subject: [PATCH 132/218] Allow actions that improve maxLTV (when unhealthy) (#103) * Allow actions that improve maxLTV * update build scripts * chatgpt improved conditional * Add assertion for no health factor case * Update schema * review updates + build script config updates --- Cargo.toml | 2 +- contracts/credit-manager/src/execute.rs | 16 +++- contracts/credit-manager/src/health.rs | 45 ++++++--- .../credit-manager/src/liquidate_coin.rs | 5 +- contracts/credit-manager/tests/test_health.rs | 93 ++++++++++++++++++- .../tests/test_liquidate_vault.rs | 2 +- packages/rover/src/msg/execute.rs | 11 ++- packages/rover/src/traits.rs | 8 +- .../mars-credit-manager.json | 78 +++++++++++++++- scripts/deploy/addresses/osmo-test-4.json | 10 +- scripts/deploy/base/deployer.ts | 3 +- scripts/deploy/osmosis/config.ts | 30 +++--- .../MarsCreditManager.client.ts | 1 + .../MarsCreditManager.message-composer.ts | 1 + .../MarsCreditManager.react-query.ts | 1 + .../MarsCreditManager.types.ts | 11 ++- 16 files changed, 258 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3e45555a1..8587f9a8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ [workspace.package] version = "1.0.0" authors = [ - "grod220 ", + "Gabe R. ", "Larry Engineer ", "Piotr Babel ", ] diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 0ebc5c730..733abb508 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -11,7 +11,7 @@ use mars_rover::{ use crate::{ borrow::borrow, deposit::deposit, - health::assert_below_max_ltv, + health::{assert_max_ltv, compute_health}, liquidate_coin::liquidate_coin, refund::refund_coin_balances, repay::repay, @@ -56,6 +56,7 @@ pub fn dispatch_actions( let mut response = Response::new(); let mut callbacks: Vec = vec![]; let mut received_coins = Coins::try_from(info.funds)?; + let prev_health = compute_health(deps.as_ref(), &env, account_id)?; for action in actions { match action { @@ -174,9 +175,13 @@ pub fn dispatch_actions( CallbackMsg::AssertOneVaultPositionOnly { account_id: account_id.to_string(), }, - // after user selected actions, we assert LTV is healthy; if not, throw error and revert all actions - CallbackMsg::AssertBelowMaxLTV { + // after user selected actions, we assert LTV is either: + // - Healthy, if prior to actions MaxLTV health factor >= 1 or None + // - Not further weakened, if prior to actions MaxLTV health factor < 1 + // Else, throw error and revert all actions + CallbackMsg::AssertMaxLTV { account_id: account_id.to_string(), + prev_health, }, ]); @@ -214,9 +219,10 @@ pub fn execute_callback( account_id, coin, } => repay(deps, env, &account_id, &coin), - CallbackMsg::AssertBelowMaxLTV { + CallbackMsg::AssertMaxLTV { account_id, - } => assert_below_max_ltv(deps.as_ref(), env, &account_id), + prev_health, + } => assert_max_ltv(deps.as_ref(), env, &account_id, prev_health), CallbackMsg::EnterVault { account_id, vault, diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 4e5bfe253..796949d54 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -7,6 +7,7 @@ use mars_rover::{ adapters::vault::VaultPosition, error::{ContractError, ContractResult}, msg::query::{DebtAmount, Positions}, + traits::Stringify, }; use crate::{ @@ -208,13 +209,31 @@ fn calculate_total_debt_value(deps: &Deps, debts: &[DebtAmount]) -> ContractResu Ok(total) } -pub fn assert_below_max_ltv(deps: Deps, env: Env, account_id: &str) -> ContractResult { - let health = compute_health(deps, &env, account_id)?; - - if health.is_above_max_ltv() { +pub fn assert_max_ltv( + deps: Deps, + env: Env, + account_id: &str, + prev_health: Health, +) -> ContractResult { + let new_health = compute_health(deps, &env, account_id)?; + + // If previous health was in a bad state, assert it did not further weaken + if prev_health.is_above_max_ltv() { + if let (Some(prev_hf), Some(new_hf)) = + (prev_health.max_ltv_health_factor, new_health.max_ltv_health_factor) + { + if prev_hf > new_hf { + return Err(ContractError::HealthNotImproved { + prev_hf: prev_hf.to_string(), + new_hf: new_hf.to_string(), + }); + } + } + // if previous health was in a good state, assert it's still healthy + } else if new_health.is_above_max_ltv() { return Err(ContractError::AboveMaxLTV { account_id: account_id.to_string(), - max_ltv_health_factor: val_or_na(health.max_ltv_health_factor), + max_ltv_health_factor: new_health.max_ltv_health_factor.to_string(), }); } @@ -222,18 +241,14 @@ pub fn assert_below_max_ltv(deps: Deps, env: Env, account_id: &str) -> ContractR .add_attribute("timestamp", env.block.time.seconds().to_string()) .add_attribute("height", env.block.height.to_string()) .add_attribute("account_id", account_id) - .add_attribute("assets_value", health.total_collateral_value.to_string()) - .add_attribute("debts_value", health.total_debt_value.to_string()) - .add_attribute("lqdt_health_factor", val_or_na(health.liquidation_health_factor)) - .add_attribute("liquidatable", health.is_liquidatable().to_string()) - .add_attribute("max_ltv_health_factor", val_or_na(health.max_ltv_health_factor)) - .add_attribute("above_max_ltv", health.is_above_max_ltv().to_string()); + .add_attribute("collateral_value", new_health.total_collateral_value.to_string()) + .add_attribute("debts_value", new_health.total_debt_value.to_string()) + .add_attribute("lqdt_health_factor", new_health.liquidation_health_factor.to_string()) + .add_attribute("liquidatable", new_health.is_liquidatable().to_string()) + .add_attribute("max_ltv_health_factor", new_health.max_ltv_health_factor.to_string()) + .add_attribute("above_max_ltv", new_health.is_above_max_ltv().to_string()); Ok(Response::new() .add_attribute("action", "rover/credit-manager/callback/assert_health") .add_event(event)) } - -pub fn val_or_na(opt: Option) -> String { - opt.map_or_else(|| "n/a".to_string(), |dec| dec.to_string()) -} diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index c39157aec..c0ad93ffd 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -8,10 +8,11 @@ use mars_rover::{ adapters::oracle::Oracle, error::{ContractError, ContractResult}, msg::execute::CallbackMsg, + traits::Stringify, }; use crate::{ - health::{compute_health, val_or_na}, + health::compute_health, repay::current_debt_for_denom, state::{COIN_BALANCES, MAX_CLOSE_FACTOR, ORACLE, RED_BANK}, utils::{decrement_coin_balance, increment_coin_balance}, @@ -73,7 +74,7 @@ pub fn calculate_liquidation( if !health.is_liquidatable() { return Err(ContractError::NotLiquidatable { account_id: liquidatee_account_id.to_string(), - lqdt_health_factor: val_or_na(health.liquidation_health_factor), + lqdt_health_factor: health.liquidation_health_factor.to_string(), }); } diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index c4d55c251..f3ff02c3d 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -1,6 +1,6 @@ use std::ops::{Add, Mul}; -use cosmwasm_std::{coins, Addr, Coin, Decimal, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Coin, Decimal, Uint128}; use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use mars_math::{FractionMath, Fractional}; use mars_mock_oracle::msg::CoinPrice; @@ -8,7 +8,10 @@ use mars_rover::{ adapters::vault::VaultConfig, error::ContractError, msg::{ - execute::Action::{Borrow, Deposit, EnterVault}, + execute::{ + Action::{Borrow, Deposit, EnterVault, Repay, Withdraw}, + ActionAmount, ActionCoin, + }, instantiate::{ConfigUpdates, VaultInstantiateConfig}, query::DebtAmount, }, @@ -344,9 +347,9 @@ fn cannot_borrow_more_but_not_liquidatable() { assert_err( res, - ContractError::AboveMaxLTV { - account_id: account_id.clone(), - max_ltv_health_factor: "0.946759259259259259".to_string(), + ContractError::HealthNotImproved { + prev_hf: "0.975490196078431372".to_string(), + new_hf: "0.946759259259259259".to_string(), }, ); @@ -817,6 +820,86 @@ fn vault_base_token_delisting_drops_max_ltv() { assert_eq!(curr_health.max_ltv_health_factor, Some(Decimal::raw(811881188118811881u128))); } +#[test] +fn can_take_actions_if_ltv_does_not_weaken() { + let uosmo_info = CoinInfo { + denom: "uosmo".to_string(), + price: Decimal::from_atomics(23654u128, 4).unwrap(), + max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), + liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), + }; + let uatom_info = CoinInfo { + denom: "uatom".to_string(), + price: Decimal::from_atomics(102u128, 1).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(75u128, 2).unwrap(), + liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), + }; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![coin(400, uosmo_info.denom.clone()), coin(50, uatom_info.denom.clone())], + }) + .build() + .unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(50))], + &[Coin::new(300, uosmo_info.denom.clone())], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(24u128, 0).unwrap(), + }); + + let health = mock.query_health(&account_id); + assert!(!health.liquidatable); + assert!(health.above_max_ltv); + + // Despite account in an unhealthy state (above max LTV), + // because the health factor improved, this transaction succeeds + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(uosmo_info.to_coin(1))], + &[uosmo_info.to_coin(1)], + ) + .unwrap(); + + // Assert success if account update renders no health factor change + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(uosmo_info.to_coin(1)), Withdraw(uosmo_info.to_coin(1))], + &[uosmo_info.to_coin(1)], + ) + .unwrap(); + + // Assert success if next state does not have a health factor given all debt is paid + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(uatom_info.to_coin(50)), + Repay(ActionCoin { + denom: uatom_info.denom.clone(), + amount: ActionAmount::AccountBalance, + }), + ], + &[uatom_info.to_coin(50)], + ) + .unwrap(); +} + fn find_by_denom<'a>(denom: &'a str, shares: &'a [DebtAmount]) -> &'a DebtAmount { shares.iter().find(|item| item.denom == *denom).unwrap() } diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 384434095..6430506d9 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -120,7 +120,7 @@ fn liquidatee_is_not_liquidatable() { res, ContractError::NotLiquidatable { account_id: liquidatee_account_id, - lqdt_health_factor: "n/a".to_string(), + lqdt_health_factor: "None".to_string(), }, ) } diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 30a905b75..b81b3034b 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; +use mars_health::Health; use mars_owner::OwnerUpdate; use crate::{ @@ -177,9 +178,13 @@ pub enum CallbackMsg { account_id: String, coin: ActionCoin, }, - /// Calculate the account's max loan-to-value health factor. If above 1, - /// emits a `position_changed` event. If 1 or below, raises an error. - AssertBelowMaxLTV { + /// Assert MaxLTV is either: + /// - Healthy, if prior to actions MaxLTV health factor >= 1 or None + /// - Not further weakened, if prior to actions MaxLTV health factor < 1 + /// Emits a `position_changed` event. + #[serde(rename = "assert_max_ltv")] + AssertMaxLTV { + prev_health: Health, account_id: String, }, /// Adds coin to a vault strategy diff --git a/packages/rover/src/traits.rs b/packages/rover/src/traits.rs index a1af9c7bd..729943bf9 100644 --- a/packages/rover/src/traits.rs +++ b/packages/rover/src/traits.rs @@ -1,9 +1,15 @@ -use cosmwasm_std::Coin; +use cosmwasm_std::{Coin, Decimal}; pub trait Stringify { fn to_string(&self) -> String; } +impl Stringify for Option { + fn to_string(&self) -> String { + self.map_or_else(|| "None".to_string(), |dec| dec.to_string()) + } +} + pub trait Denoms { fn to_denoms(&self) -> Vec<&str>; } diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 435bc1c43..8a0539f47 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -756,20 +756,24 @@ "additionalProperties": false }, { - "description": "Calculate the account's max loan-to-value health factor. If above 1, emits a `position_changed` event. If 1 or below, raises an error.", + "description": "Assert MaxLTV is either: - Healthy, if prior to actions MaxLTV health factor >= 1 or None - Not further weakened, if prior to actions MaxLTV health factor < 1 Emits a `position_changed` event.", "type": "object", "required": [ - "assert_below_max_l_t_v" + "assert_max_ltv" ], "properties": { - "assert_below_max_l_t_v": { + "assert_max_ltv": { "type": "object", "required": [ - "account_id" + "account_id", + "prev_health" ], "properties": { "account_id": { "type": "string" + }, + "prev_health": { + "$ref": "#/definitions/Health" } }, "additionalProperties": false @@ -1281,6 +1285,72 @@ "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" }, + "Health": { + "type": "object", + "required": [ + "liquidation_threshold_adjusted_collateral", + "max_ltv_adjusted_collateral", + "total_collateral_value", + "total_debt_value" + ], + "properties": { + "liquidation_health_factor": { + "description": "The sum of the value of all collaterals multiplied by their liquidation threshold over the total value of debt", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "liquidation_threshold_adjusted_collateral": { + "description": "The sum of the value of all colletarals adjusted by their Liquidation Threshold", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "max_ltv_adjusted_collateral": { + "description": "The sum of the value of all colletarals adjusted by their Max LTV", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "max_ltv_health_factor": { + "description": "The sum of the value of all collaterals multiplied by their max LTV, over the total value of debt", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "total_collateral_value": { + "description": "The sum of the value of all collaterals", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "total_debt_value": { + "description": "The sum of the value of all debts", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, "NftConfigUpdates": { "type": "object", "properties": { diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 74d77a077..b10b0e926 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,7 @@ { - "mockVault": "osmo1z3ent379n9fzen9598hl7hnueh2k786e0gkxhusvxus9ag2ud5jqqv0ev7", - "swapper": "osmo1fjw3qw8yf8slzdjs9m7n9e45xkz3ewvxlk78ts6t3s4wq8886vlqkhfn4m", - "zapper": "osmo12rnm0lzadwr7fcpcqwjv6kpqtfahwyrrq5gte7px33xrh4z6gvtsnk4gef", - "creditManager": "osmo1494dljftmcx3e6scmw2dlvrxaj2j8afc89qyqpg245hg70dd2l4s6dk434", - "accountNft": "osmo12vex6ad7l87ru637xnfhvaaggxq6pw4cjzk3ut0l3hurjcfh05ess2a8nr" + "mockVault": "osmo1zhmm3hcs0a9yu6pvdeyup0ujayd6apmhurkuhffdf4jga79gqwzsagf5sx", + "swapper": "osmo1npf09lld68z5qg00runrxxxname23plp3lkqx8r4ku7rg2qaj6eq4wjl3d", + "zapper": "osmo1kcnvvwh43qcd77f6v5st2xx6pknepyrxmk9k07llx3v0qsjv39jsgrncht", + "creditManager": "osmo1prwnxn3vlvh0kqmwxn8whqnavk8ze9hrccwpsapysgpa3pj8r2csy84grp", + "accountNft": "osmo1ua5rw84jxg6e7ma4hx7v7yhqcks74cjnx38gpnsvtfzrtxhwcvjqgsxulx" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 957247320..9e476e950 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -25,6 +25,7 @@ import { MarsAccountNftClient } from '../../types/generated/mars-account-nft/Mar import { MarsCreditManagerClient } from '../../types/generated/mars-credit-manager/MarsCreditManager.client' import { InitOrUpdateAssetParams } from '../../types/generated/mars-mock-red-bank/MarsMockRedBank.types' import { PriceSource } from '../../types/priceSource' +import { kebabCase } from 'lodash' export class Deployer { constructor( @@ -58,7 +59,7 @@ export class Deployer { this.deployerAddr, codeId, msg, - `mars-${name}`, + `mars-${kebabCase(name)}`, 'auto', { admin: this.deployerAddr }, ) diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 6087356f6..61d4daa94 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -49,8 +49,8 @@ export const osmosisTestnetConfig: DeploymentConfig = { vault: { address: vaultOsmoAtom1 }, config: { deposit_cap: { denom: 'uosmo', amount: '1000000000' }, // 1000 osmo - liquidation_threshold: '0.75', - max_ltv: '0.65', + liquidation_threshold: '0.535', + max_ltv: '0.506', whitelisted: true, }, }, @@ -58,8 +58,8 @@ export const osmosisTestnetConfig: DeploymentConfig = { vault: { address: vaultOsmoAtom7 }, config: { deposit_cap: { denom: 'uosmo', amount: '1000000000' }, // 1000 osmo - liquidation_threshold: '0.75', - max_ltv: '0.65', + liquidation_threshold: '0.535', + max_ltv: '0.506', whitelisted: true, }, }, @@ -67,8 +67,8 @@ export const osmosisTestnetConfig: DeploymentConfig = { vault: { address: vaultOsmoAtom14 }, config: { deposit_cap: { denom: 'uosmo', amount: '1000000000' }, // 1000 osmo - liquidation_threshold: '0.75', - max_ltv: '0.65', + liquidation_threshold: '0.535', + max_ltv: '0.506', whitelisted: true, }, }, @@ -76,8 +76,8 @@ export const osmosisTestnetConfig: DeploymentConfig = { vault: { address: vaultJunoOsmo1 }, config: { deposit_cap: { denom: 'uosmo', amount: '500000000' }, // 500 osmo - liquidation_threshold: '0.55', - max_ltv: '0.5', + liquidation_threshold: '0.441', + max_ltv: '0.4115', whitelisted: true, }, }, @@ -85,8 +85,8 @@ export const osmosisTestnetConfig: DeploymentConfig = { vault: { address: vaultJunoOsmo7 }, config: { deposit_cap: { denom: 'uosmo', amount: '500000000' }, // 500 osmo - liquidation_threshold: '0.55', - max_ltv: '0.5', + liquidation_threshold: '0.441', + max_ltv: '0.4115', whitelisted: true, }, }, @@ -94,8 +94,8 @@ export const osmosisTestnetConfig: DeploymentConfig = { vault: { address: vaultJunoOsmo14 }, config: { deposit_cap: { denom: 'uosmo', amount: '500000000' }, // 500 osmo - liquidation_threshold: '0.55', - max_ltv: '0.5', + liquidation_threshold: '0.441', + max_ltv: '0.4115', whitelisted: true, }, }, @@ -107,8 +107,8 @@ export const osmosisTestnetConfig: DeploymentConfig = { mock: { config: { deposit_cap: { denom: uosmo, amount: '100000000' }, // 100 osmo - liquidation_threshold: '0.75', - max_ltv: '0.65', + liquidation_threshold: '0.585', + max_ltv: '0.569', whitelisted: true, }, vaultTokenDenom: udig, @@ -124,7 +124,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { defaultCreditLine: '100000000000', depositAmount: '100', secondaryDenom: uatom, - startingAmountForTestUser: '2000000', + startingAmountForTestUser: '2500000', swap: { slippage: '0.4', amount: '40', diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 939809023..5a08e6058 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -29,6 +29,7 @@ import { ActionCoin, ConfigUpdates, NftConfigUpdates, + Health, VaultBaseForAddr, QueryMsg, ArrayOfCoinBalanceResponseItem, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 9340c404a..6aca78328 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -30,6 +30,7 @@ import { ActionCoin, ConfigUpdates, NftConfigUpdates, + Health, VaultBaseForAddr, QueryMsg, ArrayOfCoinBalanceResponseItem, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 8c10addfb..b2f2eb4c1 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -30,6 +30,7 @@ import { ActionCoin, ConfigUpdates, NftConfigUpdates, + Health, VaultBaseForAddr, QueryMsg, ArrayOfCoinBalanceResponseItem, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 40418a895..0af06718c 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -176,8 +176,9 @@ export type CallbackMsg = } } | { - assert_below_max_l_t_v: { + assert_max_ltv: { account_id: string + prev_health: Health } } | { @@ -289,6 +290,14 @@ export interface NftConfigUpdates { max_value_for_burn?: Uint128 | null proposed_new_minter?: string | null } +export interface Health { + liquidation_health_factor?: Decimal | null + liquidation_threshold_adjusted_collateral: Uint128 + max_ltv_adjusted_collateral: Uint128 + max_ltv_health_factor?: Decimal | null + total_collateral_value: Uint128 + total_debt_value: Uint128 +} export interface VaultBaseForAddr { address: Addr } From ad2540bfbfcafffddaf562e4e3d5f0a8e83fa8f9 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 24 Jan 2023 11:18:18 +0100 Subject: [PATCH 133/218] Updated LTVs on testnet (#106) --- scripts/deploy/osmosis/config.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 61d4daa94..254654507 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -49,8 +49,8 @@ export const osmosisTestnetConfig: DeploymentConfig = { vault: { address: vaultOsmoAtom1 }, config: { deposit_cap: { denom: 'uosmo', amount: '1000000000' }, // 1000 osmo - liquidation_threshold: '0.535', - max_ltv: '0.506', + liquidation_threshold: '0.65', + max_ltv: '0.63', whitelisted: true, }, }, @@ -58,8 +58,8 @@ export const osmosisTestnetConfig: DeploymentConfig = { vault: { address: vaultOsmoAtom7 }, config: { deposit_cap: { denom: 'uosmo', amount: '1000000000' }, // 1000 osmo - liquidation_threshold: '0.535', - max_ltv: '0.506', + liquidation_threshold: '0.65', + max_ltv: '0.63', whitelisted: true, }, }, @@ -67,8 +67,8 @@ export const osmosisTestnetConfig: DeploymentConfig = { vault: { address: vaultOsmoAtom14 }, config: { deposit_cap: { denom: 'uosmo', amount: '1000000000' }, // 1000 osmo - liquidation_threshold: '0.535', - max_ltv: '0.506', + liquidation_threshold: '0.65', + max_ltv: '0.63', whitelisted: true, }, }, From 08acde2f132e3afbcae7e665d05fea4c6505fa72 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 25 Jan 2023 11:02:16 +0100 Subject: [PATCH 134/218] Add lend msg to actions (#45) * Add lend action * review updates --- Cargo.lock | 65 +- contracts/credit-manager/src/contract.rs | 16 +- contracts/credit-manager/src/execute.rs | 20 +- contracts/credit-manager/src/lend.rs | 43 + contracts/credit-manager/src/lib.rs | 1 + contracts/credit-manager/src/query.rs | 80 +- contracts/credit-manager/src/state.rs | 3 + contracts/credit-manager/src/utils.rs | 26 +- contracts/credit-manager/src/vault/enter.rs | 11 - .../credit-manager/tests/helpers/mock_env.rs | 66 +- contracts/credit-manager/tests/test_borrow.rs | 2 +- .../tests/test_enumerate_lent_shares.rs | 193 +++++ .../tests/test_enumerate_total_lent_shares.rs | 187 +++++ .../test_enumerate_vault_coin_balances.rs | 1 - .../tests/test_enumerate_vault_positions.rs | 1 - .../tests/test_fields_vault_limit.rs | 68 -- contracts/credit-manager/tests/test_lend.rs | 185 ++++ contracts/mock-red-bank/Cargo.toml | 1 + contracts/mock-red-bank/src/contract.rs | 11 +- contracts/mock-red-bank/src/execute.rs | 29 +- contracts/mock-red-bank/src/helpers.rs | 12 +- contracts/mock-red-bank/src/query.rs | 22 +- contracts/mock-red-bank/src/state.rs | 2 + packages/rover/src/adapters/red_bank.rs | 28 + packages/rover/src/msg/execute.rs | 11 +- packages/rover/src/msg/query.rs | 31 + .../mars-credit-manager.json | 269 +++++- .../mars-mock-credit-manager.json | 208 +++++ scripts/deploy/addresses/osmo-test-4.json | 10 +- scripts/deploy/base/deployer.ts | 4 +- scripts/deploy/base/index.ts | 1 + scripts/deploy/base/rover.ts | 9 + scripts/deploy/osmosis/config.ts | 27 +- scripts/types/config.ts | 3 +- .../MarsCreditManager.client.ts | 54 ++ .../MarsCreditManager.message-composer.ts | 3 + .../MarsCreditManager.react-query.ts | 87 ++ .../MarsCreditManager.types.ts | 40 +- .../MarsMockCreditManager.client.ts | 54 ++ .../MarsMockCreditManager.message-composer.ts | 3 + .../MarsMockCreditManager.react-query.ts | 87 ++ .../MarsMockCreditManager.types.ts | 26 + scripts/yarn.lock | 792 ++++++++---------- 43 files changed, 2141 insertions(+), 651 deletions(-) create mode 100644 contracts/credit-manager/src/lend.rs create mode 100644 contracts/credit-manager/tests/test_enumerate_lent_shares.rs create mode 100644 contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs delete mode 100644 contracts/credit-manager/tests/test_fields_vault_limit.rs create mode 100644 contracts/credit-manager/tests/test_lend.rs diff --git a/Cargo.lock b/Cargo.lock index 4bd84fa29..eb663c5bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,9 +41,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.60" +version = "0.1.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" +checksum = "689894c2db1ea643a50834b999abf1c110887402542955ff5451dab8f861f9ed" dependencies = [ "proc-macro2", "quote", @@ -161,9 +161,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byteorder" @@ -249,7 +249,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20b42021d8488665b1a0d9748f1f81df7235362d194f44481e2e61bf376b77b4" dependencies = [ - "prost 0.11.5", + "prost 0.11.6", "prost-types", "tendermint-proto", ] @@ -655,9 +655,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ "signature", ] @@ -850,9 +850,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "group" @@ -1096,6 +1096,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.0.1", + "cw-utils 0.16.0", "mars-outpost", ] @@ -1260,9 +1261,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nom" -version = "7.1.2" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -1333,7 +1334,7 @@ dependencies = [ "chrono", "cosmwasm-std", "osmosis-std-derive 0.12.0", - "prost 0.11.5", + "prost 0.11.6", "prost-types", "schemars", "serde", @@ -1348,7 +1349,7 @@ dependencies = [ "chrono", "cosmwasm-std", "osmosis-std-derive 0.13.2", - "prost 0.11.5", + "prost 0.11.6", "prost-types", "schemars", "serde", @@ -1387,7 +1388,7 @@ dependencies = [ "cosmrs", "cosmwasm-std", "osmosis-std 0.13.2", - "prost 0.11.5", + "prost 0.11.6", "serde", "serde_json", "thiserror", @@ -1438,9 +1439,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" dependencies = [ "unicode-ident", ] @@ -1457,12 +1458,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c01db6702aa05baa3f57dec92b8eeeeb4cb19e894e73996b32a4093289e54592" +checksum = "21dc42e00223fc37204bd4aa177e69420c604ca4a183209a8f9de30c6d934698" dependencies = [ "bytes", - "prost-derive 0.11.5", + "prost-derive 0.11.6", ] [[package]] @@ -1480,9 +1481,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8842bad1a5419bca14eac663ba798f6bc19c413c2fdceb5f3ba3b0932d96720" +checksum = "8bda8c0881ea9f722eb9629376db3d0b903b462477c1aafcb0566610ac28ac5d" dependencies = [ "anyhow", "itertools", @@ -1493,12 +1494,12 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017f79637768cde62820bc2d4fe0e45daaa027755c323ad077767c6c5f173091" +checksum = "a5e0526209433e96d83d750dd81a99118edbc55739e7e61a46764fd2ad537788" dependencies = [ "bytes", - "prost 0.11.5", + "prost 0.11.6", ] [[package]] @@ -1527,9 +1528,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -1834,7 +1835,7 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost 0.11.5", + "prost 0.11.6", "prost-types", "ripemd160", "serde", @@ -1860,7 +1861,7 @@ dependencies = [ "flex-error", "num-derive", "num-traits", - "prost 0.11.5", + "prost 0.11.6", "prost-types", "serde", "serde_bytes", @@ -1870,9 +1871,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] @@ -2018,9 +2019,9 @@ checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "which" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" dependencies = [ "either", "libc", diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 847623d06..11d9a62ff 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -14,10 +14,11 @@ use crate::{ health::compute_health, instantiate::store_config, query::{ - query_all_coin_balances, query_all_debt_shares, query_all_total_debt_shares, + query_all_coin_balances, query_all_debt_shares, query_all_lent_shares, + query_all_total_debt_shares, query_all_total_lent_shares, query_all_total_vault_coin_balances, query_all_vault_positions, query_allowed_coins, - query_config, query_positions, query_total_debt_shares, query_total_vault_coin_balance, - query_vaults_info, + query_config, query_positions, query_total_debt_shares, query_total_lent_shares, + query_total_vault_coin_balance, query_vaults_info, }, update_config::{update_config, update_nft_config, update_owner}, vault::handle_unlock_request_reply, @@ -102,6 +103,15 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { start_after, limit, } => to_binary(&query_all_total_debt_shares(deps, start_after, limit)?), + QueryMsg::AllLentShares { + start_after, + limit, + } => to_binary(&query_all_lent_shares(deps, start_after, limit)?), + QueryMsg::TotalLentShares(denom) => to_binary(&query_total_lent_shares(deps, &denom)?), + QueryMsg::AllTotalLentShares { + start_after, + limit, + } => to_binary(&query_all_total_lent_shares(deps, start_after, limit)?), QueryMsg::TotalVaultCoinBalance { vault, } => to_binary(&query_total_vault_coin_balance(deps, &vault, &env.contract.address)?), diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 733abb508..3772842b3 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -12,6 +12,7 @@ use crate::{ borrow::borrow, deposit::deposit, health::{assert_max_ltv, compute_health}, + lend::lend, liquidate_coin::liquidate_coin, refund::refund_coin_balances, repay::repay, @@ -20,8 +21,8 @@ use crate::{ update_coin_balances::update_coin_balance, utils::{assert_is_token_owner, assert_not_contract_in_config}, vault::{ - assert_only_one_vault_position, enter_vault, exit_vault, exit_vault_unlocked, - liquidate_vault, request_vault_unlock, update_vault_coin_balance, + enter_vault, exit_vault, exit_vault_unlocked, liquidate_vault, request_vault_unlock, + update_vault_coin_balance, }, withdraw::withdraw, zap::{provide_liquidity, withdraw_liquidity}, @@ -76,6 +77,10 @@ pub fn dispatch_actions( account_id: account_id.to_string(), coin: coin.clone(), }), + Action::Lend(coin) => callbacks.push(CallbackMsg::Lend { + account_id: account_id.to_string(), + coin: coin.clone(), + }), Action::EnterVault { vault, coin, @@ -171,10 +176,6 @@ pub fn dispatch_actions( } callbacks.extend([ - // Fields of Mars ONLY assertion. Only one vault position per credit account - CallbackMsg::AssertOneVaultPositionOnly { - account_id: account_id.to_string(), - }, // after user selected actions, we assert LTV is either: // - Healthy, if prior to actions MaxLTV health factor >= 1 or None // - Not further weakened, if prior to actions MaxLTV health factor < 1 @@ -219,6 +220,10 @@ pub fn execute_callback( account_id, coin, } => repay(deps, env, &account_id, &coin), + CallbackMsg::Lend { + account_id, + coin, + } => lend(deps, env, &account_id, coin), CallbackMsg::AssertMaxLTV { account_id, prev_health, @@ -302,9 +307,6 @@ pub fn execute_callback( account_id, lp_token, } => withdraw_liquidity(deps, env, &account_id, &lp_token), - CallbackMsg::AssertOneVaultPositionOnly { - account_id, - } => assert_only_one_vault_position(deps, &account_id), CallbackMsg::RefundAllCoinBalances { account_id, } => refund_coin_balances(deps, env, &account_id), diff --git a/contracts/credit-manager/src/lend.rs b/contracts/credit-manager/src/lend.rs new file mode 100644 index 000000000..9bb8046a1 --- /dev/null +++ b/contracts/credit-manager/src/lend.rs @@ -0,0 +1,43 @@ +use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; +use mars_rover::error::{ContractError, ContractResult}; + +use crate::{ + state::{LENT_SHARES, RED_BANK, TOTAL_LENT_SHARES}, + utils::{assert_coin_is_whitelisted, decrement_coin_balance}, +}; + +pub static DEFAULT_LENT_SHARES_PER_COIN: Uint128 = Uint128::new(1_000_000); + +pub fn lend(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> ContractResult { + if coin.amount.is_zero() { + return Err(ContractError::NoAmount); + } + + assert_coin_is_whitelisted(deps.storage, &coin.denom)?; + + let red_bank = RED_BANK.load(deps.storage)?; + let total_lent = red_bank.query_lent(&deps.querier, &env.contract.address, &coin.denom)?; + + let lent_shares_to_add = if total_lent.is_zero() { + coin.amount.checked_mul(DEFAULT_LENT_SHARES_PER_COIN)? + } else { + TOTAL_LENT_SHARES + .load(deps.storage, &coin.denom)? + .checked_multiply_ratio(coin.amount, total_lent)? + }; + + let add_shares = |shares: Option| -> ContractResult { + Ok(shares.unwrap_or_else(Uint128::zero).checked_add(lent_shares_to_add)?) + }; + TOTAL_LENT_SHARES.update(deps.storage, &coin.denom, add_shares)?; + LENT_SHARES.update(deps.storage, (account_id, &coin.denom), add_shares)?; + + decrement_coin_balance(deps.storage, account_id, &coin)?; + + Ok(Response::new() + .add_message(red_bank.lend_msg(&coin)?) + .add_attribute("action", "rover/credit-manager/lend") + .add_attribute("account_id", account_id) + .add_attribute("lent_shares_added", lent_shares_to_add) + .add_attribute("coins_lent", coin.amount)) +} diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index ae6f9d3cd..0500954b4 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -5,6 +5,7 @@ pub mod deposit; pub mod execute; pub mod health; pub mod instantiate; +pub mod lend; pub mod liquidate_coin; pub mod query; pub mod refund; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 84bd68fa8..c67be6029 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -4,18 +4,19 @@ use mars_rover::{ adapters::vault::{Vault, VaultBase, VaultPosition, VaultUnchecked}, error::ContractResult, msg::query::{ - CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, Positions, - SharesResponseItem, VaultInfoResponse, VaultPositionResponseItem, VaultWithBalance, + CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, LentAmount, LentShares, + Positions, SharesResponseItem, VaultInfoResponse, VaultPositionResponseItem, + VaultWithBalance, }, }; use crate::{ state::{ - ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, MAX_CLOSE_FACTOR, + ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, LENT_SHARES, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, - VAULT_CONFIGS, VAULT_POSITIONS, ZAPPER, + TOTAL_LENT_SHARES, VAULT_CONFIGS, VAULT_POSITIONS, ZAPPER, }, - utils::debt_shares_to_amount, + utils::{debt_shares_to_amount, lent_shares_to_amount}, vault::vault_utilization_in_deposit_cap_denom, }; @@ -42,6 +43,7 @@ pub fn query_positions(deps: Deps, env: &Env, account_id: &str) -> ContractResul account_id: account_id.to_string(), deposits: query_coin_balances(deps, account_id)?, debts: query_debt_amounts(deps, env, account_id)?, + lends: query_lent_amounts(deps, env, account_id)?, vaults: query_vault_positions(deps, account_id)?, }) } @@ -69,6 +71,22 @@ pub fn query_all_coin_balances( .collect()) } +fn query_lent_amounts(deps: Deps, env: &Env, account_id: &str) -> ContractResult> { + LENT_SHARES + .prefix(account_id) + .range(deps.storage, None, None, Order::Ascending) + .map(|res| { + let (denom, shares) = res?; + let coin = lent_shares_to_amount(deps, &env.contract.address, &denom, shares)?; + Ok(LentAmount { + denom, + shares, + amount: coin.amount, + }) + }) + .collect() +} + fn query_debt_amounts(deps: Deps, env: &Env, account_id: &str) -> ContractResult> { DEBT_SHARES .prefix(account_id) @@ -122,6 +140,30 @@ pub fn query_all_debt_shares( .collect()) } +pub fn query_all_lent_shares( + deps: Deps, + start_after: Option<(String, String)>, + limit: Option, +) -> StdResult> { + let start = start_after + .as_ref() + .map(|(account_id, denom)| Bound::exclusive((account_id.as_str(), denom.as_str()))); + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + LENT_SHARES + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|item| { + let ((account_id, denom), shares) = item?; + Ok(SharesResponseItem { + account_id, + denom, + shares, + }) + }) + .collect() +} + pub fn query_vaults_info( deps: Deps, env: Env, @@ -227,6 +269,14 @@ pub fn query_total_debt_shares(deps: Deps, denom: &str) -> StdResult }) } +pub fn query_total_lent_shares(deps: Deps, denom: &str) -> StdResult { + let shares = TOTAL_LENT_SHARES.load(deps.storage, denom)?; + Ok(LentShares { + denom: denom.to_string(), + shares, + }) +} + pub fn query_all_total_debt_shares( deps: Deps, start_after: Option, @@ -248,6 +298,26 @@ pub fn query_all_total_debt_shares( .collect()) } +pub fn query_all_total_lent_shares( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult> { + let start = start_after.as_ref().map(|denom| Bound::exclusive(denom.as_str())); + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + TOTAL_LENT_SHARES + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|item| { + let (denom, shares) = item?; + Ok(LentShares { + denom, + shares, + }) + }) + .collect() +} + pub fn query_total_vault_coin_balance( deps: Deps, unchecked: &VaultUnchecked, diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 20ba2c70d..e691b68eb 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -31,6 +31,9 @@ pub const MAX_UNLOCKING_POSITIONS: Item = Item::new("max_unlocking_posi pub const COIN_BALANCES: Map<(&str, &str), Uint128> = Map::new("coin_balance"); // Map<(AccountId, Denom), Amount> pub const DEBT_SHARES: Map<(&str, &str), Uint128> = Map::new("debt_shares"); // Map<(AccountId, Denom), Shares> pub const TOTAL_DEBT_SHARES: Map<&str, Uint128> = Map::new("total_debt_shares"); // Map +pub const LENT_SHARES: Map<(&str, &str), Uint128> = Map::new("lent_shares"); // Map<(AccountId, Denom), Shares> +pub const TOTAL_LENT_SHARES: Map<&str, Uint128> = Map::new("total_lent_shares"); // Map + pub const VAULT_POSITIONS: Map<(&str, Addr), VaultPositionAmount> = Map::new("vault_positions"); // Map<(AccountId, VaultAddr), VaultPositionAmount> // Temporary state to save variables to be used on reply handling diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index b534b395f..9fe4c4433 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -15,7 +15,7 @@ use mars_rover::{ use crate::{ state::{ ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, - VAULT_CONFIGS, ZAPPER, + TOTAL_LENT_SHARES, VAULT_CONFIGS, ZAPPER, }, update_coin_balances::query_balance, }; @@ -135,6 +135,30 @@ pub fn debt_shares_to_amount( }) } +pub fn lent_shares_to_amount( + deps: Deps, + rover_addr: &Addr, + denom: &str, + shares: Uint128, +) -> ContractResult { + // total shares of lent issued for denom + let total_lent_shares = TOTAL_LENT_SHARES.load(deps.storage, denom).unwrap_or(Uint128::zero()); + + // total rover lent amount in Redbank for asset + let red_bank = RED_BANK.load(deps.storage)?; + let total_lent_amount = red_bank.query_lent(&deps.querier, rover_addr, denom)?; + + // amount of lent for account's position + // NOTE: Given the nature of integers, the lent amount is rounded down. + // This means the account donates the fractional unit to the lending pool. + let amount = total_lent_amount.checked_multiply_ratio(shares, total_lent_shares)?; + + Ok(Coin { + denom: denom.to_string(), + amount, + }) +} + /// Contracts we call from Rover should not be attempting to execute actions. /// This assertion prevents a kind of reentrancy attack where a contract we call (that turned evil) /// can deposit into their own credit account and trick our state updates like update_coin_balances.rs diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index 8edb2cab6..92ab40a9f 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -11,7 +11,6 @@ use mars_rover::{ }; use crate::{ - query::query_vault_positions, state::{COIN_BALANCES, ORACLE, VAULT_CONFIGS}, utils::{assert_coin_is_whitelisted, decrement_coin_balance}, vault::{ @@ -137,13 +136,3 @@ pub fn assert_deposit_is_under_cap( Ok(()) } - -pub fn assert_only_one_vault_position(deps: DepsMut, account_id: &str) -> ContractResult { - let vaults = query_vault_positions(deps.as_ref(), account_id)?; - if vaults.len() > 1 { - return Err(ContractError::OnlyOneVaultPositionAllowed); - } - - Ok(Response::new() - .add_attribute("action", "rover/credit-manager/callback/assert_only_one_vault_position")) -} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 890c65e0e..386e4bd5b 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -15,7 +15,10 @@ use mars_mock_red_bank::msg::{CoinMarketInfo, InstantiateMsg as RedBankInstantia use mars_mock_vault::{ contract::DEFAULT_VAULT_TOKEN_PREFUND, msg::InstantiateMsg as VaultInstantiateMsg, }; -use mars_outpost::red_bank::{QueryMsg::UserDebt, UserDebtResponse}; +use mars_outpost::red_bank::{ + QueryMsg::{UserCollateral, UserDebt}, + UserCollateralResponse, UserDebtResponse, +}; use mars_owner::OwnerUpdate; use mars_rover::{ adapters::{ @@ -36,9 +39,9 @@ use mars_rover::{ execute::{Action, CallbackMsg}, instantiate::{ConfigUpdates, VaultInstantiateConfig}, query::{ - CoinBalanceResponseItem, ConfigResponse, DebtShares, Positions, SharesResponseItem, - VaultInfoResponse as RoverVaultInfoResponse, VaultPositionResponseItem, - VaultWithBalance, + CoinBalanceResponseItem, ConfigResponse, DebtShares, LentShares, Positions, + SharesResponseItem, VaultInfoResponse as RoverVaultInfoResponse, + VaultPositionResponseItem, VaultWithBalance, }, zapper::{ InstantiateMsg as ZapperInstantiateMsg, LpConfig, QueryMsg::EstimateProvideLiquidity, @@ -370,6 +373,40 @@ impl MockEnv { .unwrap() } + pub fn query_all_lent_shares( + &self, + start_after: Option<(String, String)>, + limit: Option, + ) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::AllLentShares { + start_after, + limit, + }, + ) + .unwrap() + } + + pub fn query_all_total_lent_shares( + &self, + start_after: Option, + limit: Option, + ) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::AllTotalLentShares { + start_after, + limit, + }, + ) + .unwrap() + } + pub fn query_total_debt_shares(&self, denom: &str) -> DebtShares { self.app .wrap() @@ -377,6 +414,13 @@ impl MockEnv { .unwrap() } + pub fn query_total_lent_shares(&self, denom: &str) -> LentShares { + self.app + .wrap() + .query_wasm_smart(self.rover.clone(), &QueryMsg::TotalLentShares(denom.to_string())) + .unwrap() + } + pub fn query_red_bank_debt(&self, denom: &str) -> UserDebtResponse { let config = self.query_config(); self.app @@ -391,6 +435,20 @@ impl MockEnv { .unwrap() } + pub fn query_red_bank_collateral(&self, denom: &str) -> UserCollateralResponse { + let config = self.query_config(); + self.app + .wrap() + .query_wasm_smart( + config.red_bank, + &UserCollateral { + user: self.rover.to_string(), + denom: denom.into(), + }, + ) + .unwrap() + } + pub fn query_preview_redeem(&self, vault: &VaultUnchecked, shares: Uint128) -> Uint128 { vault .check(&MockApi::default()) diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index 8f3e0fd39..799f41f25 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -145,7 +145,7 @@ fn success_when_new_debt_asset() { assert_eq!(position.debts.len(), 1); assert_eq!(debt_shares_res.shares, Uint128::new(42).mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED)); assert_eq!(debt_shares_res.denom, coin_info.denom); - let debt_amount = Uint128::new(42) + Uint128::new(1); // simulated yield + let debt_amount = Uint128::new(42) + Uint128::new(1); // simulated interest assert_eq!(debt_shares_res.amount, debt_amount); let coin = mock.query_balance(&mock.rover, &coin_info.denom); diff --git a/contracts/credit-manager/tests/test_enumerate_lent_shares.rs b/contracts/credit-manager/tests/test_enumerate_lent_shares.rs new file mode 100644 index 000000000..5fc519240 --- /dev/null +++ b/contracts/credit-manager/tests/test_enumerate_lent_shares.rs @@ -0,0 +1,193 @@ +use cosmwasm_std::{coin, Addr}; +use mars_credit_manager::lend::DEFAULT_LENT_SHARES_PER_COIN; +use mars_rover::msg::{execute::Action, query::SharesResponseItem}; + +use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn pagination_on_all_lent_shares_query_works() { + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let user_c = Addr::unchecked("user_c"); + + let user_a_coins = vec![ + coin(10, "coin_1"), + coin(10, "coin_2"), + coin(10, "coin_3"), + coin(10, "coin_4"), + coin(10, "coin_5"), + coin(10, "coin_6"), + coin(10, "coin_7"), + coin(10, "coin_8"), + coin(10, "coin_9"), + coin(10, "coin_10"), + coin(10, "coin_11"), + coin(10, "coin_12"), + coin(10, "coin_13"), + coin(10, "coin_14"), + ]; + + let user_b_coins = vec![ + coin(10, "coin_15"), + coin(10, "coin_16"), + coin(10, "coin_17"), + coin(10, "coin_18"), + coin(10, "coin_19"), + coin(10, "coin_20"), + coin(10, "coin_21"), + coin(10, "coin_22"), + coin(10, "coin_23"), + coin(10, "coin_24"), + ]; + + let user_c_coins = vec![ + coin(10, "coin_25"), + coin(10, "coin_26"), + coin(10, "coin_27"), + coin(10, "coin_28"), + coin(10, "coin_29"), + coin(10, "coin_30"), + coin(10, "coin_31"), + coin(10, "coin_32"), + ]; + + let mut mock = MockEnv::new() + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: user_a_coins.clone(), + }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: user_b_coins.clone(), + }) + .fund_account(AccountToFund { + addr: user_c.clone(), + funds: user_c_coins.clone(), + }) + .allowed_coins(&build_mock_coin_infos(32)) + .build() + .unwrap(); + + let account_id_a = mock.create_credit_account(&user_a).unwrap(); + mock.update_credit_account( + &account_id_a, + &user_a, + user_a_coins + .iter() + .flat_map(|c| vec![Action::Deposit(c.clone()), Action::Lend(coin(1, c.denom.clone()))]) + .collect::>(), + &user_a_coins, + ) + .unwrap(); + + let account_id_b = mock.create_credit_account(&user_b).unwrap(); + mock.update_credit_account( + &account_id_b, + &user_b, + user_b_coins + .iter() + .flat_map(|c| vec![Action::Deposit(c.clone()), Action::Lend(coin(1, c.denom.clone()))]) + .collect::>(), + &user_b_coins, + ) + .unwrap(); + + let account_id_c = mock.create_credit_account(&user_c).unwrap(); + mock.update_credit_account( + &account_id_c, + &user_c, + user_c_coins + .iter() + .flat_map(|c| vec![Action::Deposit(c.clone()), Action::Lend(coin(1, c.denom.clone()))]) + .collect::>(), + &user_c_coins, + ) + .unwrap(); + + let all_lent_shares_res = mock.query_all_lent_shares(None, Some(58_u32)); + + // Assert maximum is observed + assert_eq!(all_lent_shares_res.len(), 30); + + let all_lent_shares_res = mock.query_all_lent_shares(None, Some(2_u32)); + + // Assert limit request is observed + assert_eq!(all_lent_shares_res.len(), 2); + + let all_lent_shares_res_a = mock.query_all_lent_shares(None, None); + + let SharesResponseItem { + account_id, + denom, + .. + } = all_lent_shares_res_a.last().unwrap().clone(); + let all_lent_shares_res_b = mock.query_all_lent_shares(Some((account_id, denom)), None); + + let SharesResponseItem { + account_id, + denom, + .. + } = all_lent_shares_res_b.last().unwrap().clone(); + let all_lent_shares_res_c = mock.query_all_lent_shares(Some((account_id, denom)), None); + + let SharesResponseItem { + account_id, + denom, + .. + } = all_lent_shares_res_c.last().unwrap().clone(); + let all_lent_shares_res_d = mock.query_all_lent_shares(Some((account_id, denom)), None); + + // Assert default is observed + assert_eq!(all_lent_shares_res_a.len(), 10); + assert_eq!(all_lent_shares_res_b.len(), 10); + assert_eq!(all_lent_shares_res_c.len(), 10); + + assert_eq!(all_lent_shares_res_d.len(), 2); + + let combined_res: Vec = all_lent_shares_res_a + .iter() + .cloned() + .chain(all_lent_shares_res_b.iter().cloned()) + .chain(all_lent_shares_res_c.iter().cloned()) + .chain(all_lent_shares_res_d.iter().cloned()) + .collect(); + + let user_a_response_items = user_a_coins + .iter() + .map(|coin| SharesResponseItem { + account_id: account_id_a.clone(), + denom: coin.denom.clone(), + shares: DEFAULT_LENT_SHARES_PER_COIN, + }) + .collect::>(); + + let user_b_response_items = user_b_coins + .iter() + .map(|coin| SharesResponseItem { + account_id: account_id_b.clone(), + denom: coin.denom.clone(), + shares: DEFAULT_LENT_SHARES_PER_COIN, + }) + .collect::>(); + + let user_c_response_items = user_c_coins + .iter() + .map(|coin| SharesResponseItem { + account_id: account_id_c.clone(), + denom: coin.denom.clone(), + shares: DEFAULT_LENT_SHARES_PER_COIN, + }) + .collect::>(); + + let combined_starting_vals: Vec = user_a_response_items + .iter() + .cloned() + .chain(user_b_response_items) + .chain(user_c_response_items) + .collect(); + + assert_eq!(combined_res.len(), combined_starting_vals.len()); + assert!(combined_starting_vals.iter().all(|item| combined_res.contains(item))); +} diff --git a/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs b/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs new file mode 100644 index 000000000..6387dda9d --- /dev/null +++ b/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs @@ -0,0 +1,187 @@ +use cosmwasm_std::{coin, Addr}; +use mars_credit_manager::lend::DEFAULT_LENT_SHARES_PER_COIN; +use mars_rover::msg::{execute::Action, query::LentShares}; + +use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn pagination_on_all_total_lent_shares_query_works() { + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let user_c = Addr::unchecked("user_c"); + + let user_a_coins = vec![ + coin(10, "coin_1"), + coin(10, "coin_2"), + coin(10, "coin_3"), + coin(10, "coin_4"), + coin(10, "coin_5"), + coin(10, "coin_6"), + coin(10, "coin_7"), + coin(10, "coin_8"), + coin(10, "coin_9"), + coin(10, "coin_10"), + coin(10, "coin_11"), + coin(10, "coin_12"), + coin(10, "coin_13"), + coin(10, "coin_14"), + ]; + + let user_b_coins = vec![ + coin(10, "coin_15"), + coin(10, "coin_16"), + coin(10, "coin_17"), + coin(10, "coin_18"), + coin(10, "coin_19"), + coin(10, "coin_20"), + coin(10, "coin_21"), + coin(10, "coin_22"), + coin(10, "coin_23"), + coin(10, "coin_24"), + ]; + + let user_c_coins = vec![ + coin(10, "coin_25"), + coin(10, "coin_26"), + coin(10, "coin_27"), + coin(10, "coin_28"), + coin(10, "coin_29"), + coin(10, "coin_30"), + coin(10, "coin_31"), + coin(10, "coin_32"), + ]; + + let mut mock = MockEnv::new() + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: user_a_coins.clone(), + }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: user_b_coins.clone(), + }) + .fund_account(AccountToFund { + addr: user_c.clone(), + funds: user_c_coins.clone(), + }) + .allowed_coins(&build_mock_coin_infos(32)) + .build() + .unwrap(); + + let account_id_a = mock.create_credit_account(&user_a).unwrap(); + mock.update_credit_account( + &account_id_a, + &user_a, + user_a_coins + .iter() + .flat_map(|c| vec![Action::Deposit(c.clone()), Action::Lend(coin(1, c.denom.clone()))]) + .collect::>(), + &user_a_coins, + ) + .unwrap(); + + let account_id_b = mock.create_credit_account(&user_b).unwrap(); + mock.update_credit_account( + &account_id_b, + &user_b, + user_b_coins + .iter() + .flat_map(|c| vec![Action::Deposit(c.clone()), Action::Lend(coin(1, c.denom.clone()))]) + .collect::>(), + &user_b_coins, + ) + .unwrap(); + + let account_id_c = mock.create_credit_account(&user_c).unwrap(); + mock.update_credit_account( + &account_id_c, + &user_c, + user_c_coins + .iter() + .flat_map(|c| vec![Action::Deposit(c.clone()), Action::Lend(coin(1, c.denom.clone()))]) + .collect::>(), + &user_c_coins, + ) + .unwrap(); + + let all_total_lent_shares_res = mock.query_all_total_lent_shares(None, Some(58_u32)); + + // Assert maximum is observed + assert_eq!(all_total_lent_shares_res.len(), 30); + + let all_total_lent_shares_res = mock.query_all_total_lent_shares(None, Some(2_u32)); + + // Assert limit request is observed + assert_eq!(all_total_lent_shares_res.len(), 2); + + let all_total_lent_shares_res_a = mock.query_all_total_lent_shares(None, None); + + let LentShares { + denom, + .. + } = all_total_lent_shares_res_a.last().unwrap().clone(); + let all_total_lent_shares_res_b = mock.query_all_total_lent_shares(Some(denom), None); + + let LentShares { + denom, + .. + } = all_total_lent_shares_res_b.last().unwrap().clone(); + let all_total_lent_shares_res_c = mock.query_all_total_lent_shares(Some(denom), None); + + let LentShares { + denom, + .. + } = all_total_lent_shares_res_c.last().unwrap().clone(); + let all_total_lent_shares_res_d = mock.query_all_total_lent_shares(Some(denom), None); + + // Assert default is observed + assert_eq!(all_total_lent_shares_res_a.len(), 10); + assert_eq!(all_total_lent_shares_res_b.len(), 10); + assert_eq!(all_total_lent_shares_res_c.len(), 10); + + assert_eq!(all_total_lent_shares_res_d.len(), 2); + + let combined_res: Vec = all_total_lent_shares_res_a + .iter() + .cloned() + .chain(all_total_lent_shares_res_b.iter().cloned()) + .chain(all_total_lent_shares_res_c.iter().cloned()) + .chain(all_total_lent_shares_res_d.iter().cloned()) + .collect(); + + let user_a_response_items = user_a_coins + .iter() + .map(|coin| LentShares { + denom: coin.denom.clone(), + shares: DEFAULT_LENT_SHARES_PER_COIN, + }) + .collect::>(); + + let user_b_response_items = user_b_coins + .iter() + .map(|coin| LentShares { + denom: coin.denom.clone(), + shares: DEFAULT_LENT_SHARES_PER_COIN, + }) + .collect::>(); + + let user_c_response_items = user_c_coins + .iter() + .map(|coin| LentShares { + denom: coin.denom.clone(), + shares: DEFAULT_LENT_SHARES_PER_COIN, + }) + .collect::>(); + + let combined_starting_vals: Vec = user_a_response_items + .iter() + .cloned() + .chain(user_b_response_items) + .chain(user_c_response_items) + .collect(); + + assert_eq!(combined_res.len(), combined_starting_vals.len()); + assert!(combined_starting_vals.iter().all(|item| combined_res.contains(item))); +} diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs index ed843498d..5faf0da09 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs @@ -8,7 +8,6 @@ use crate::helpers::{ pub mod helpers; #[test] -#[ignore] // Test ignored due to Fields limitation on vault position amounts fn pagination_on_all_vault_coin_balances_query_works() { let lp_token = lp_token_info(); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index 561ca566d..6cc5f6f1f 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -9,7 +9,6 @@ use crate::helpers::{ pub mod helpers; #[test] -#[ignore] // Test ignored due to Fields limitation on vault position amounts fn pagination_on_all_vault_positions_query_works() { let lp_token = lp_token_info(); diff --git a/contracts/credit-manager/tests/test_fields_vault_limit.rs b/contracts/credit-manager/tests/test_fields_vault_limit.rs deleted file mode 100644 index af76f8dce..000000000 --- a/contracts/credit-manager/tests/test_fields_vault_limit.rs +++ /dev/null @@ -1,68 +0,0 @@ -use cosmwasm_std::{coin, Addr, Decimal}; -use mars_rover::{ - error::ContractError, - msg::execute::Action::{Deposit, EnterVault}, -}; - -use crate::helpers::{ - assert_err, lp_token_info, unlocked_vault_info, AccountToFund, CoinInfo, MockEnv, VaultTestInfo, -}; - -pub mod helpers; - -#[test] -fn can_only_have_a_single_vault_position() { - let lp_token = lp_token_info(); - let leverage_vault = unlocked_vault_info(); - - let degen_vault_token = CoinInfo { - denom: "udegen452".to_string(), - price: Decimal::from_atomics(121u128, 3).unwrap(), - max_ltv: Decimal::from_atomics(4u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), - liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), - }; - let degen_vault = VaultTestInfo { - vault_token_denom: "udegen".to_string(), - lockup: None, - base_token_denom: degen_vault_token.denom.clone(), - deposit_cap: coin(10_000_000, "uusdc"), - max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), - whitelisted: true, - }; - - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), degen_vault_token.clone()]) - .vault_configs(&[leverage_vault.clone(), degen_vault.clone()]) - .fund_account(AccountToFund { - addr: user.clone(), - funds: vec![lp_token.to_coin(300), degen_vault_token.to_coin(300)], - }) - .build() - .unwrap(); - - let lev_vault = mock.get_vault(&leverage_vault); - let degen_vault = mock.get_vault(°en_vault); - let account_id = mock.create_credit_account(&user).unwrap(); - - let res = mock.update_credit_account( - &account_id, - &user, - vec![ - Deposit(lp_token.to_coin(200)), - EnterVault { - vault: lev_vault, - coin: lp_token.to_action_coin(200), - }, - Deposit(degen_vault_token.to_coin(200)), - EnterVault { - vault: degen_vault, - coin: degen_vault_token.to_action_coin(200), - }, - ], - &[lp_token.to_coin(200), degen_vault_token.to_coin(200)], - ); - assert_err(res, ContractError::OnlyOneVaultPositionAllowed); -} diff --git a/contracts/credit-manager/tests/test_lend.rs b/contracts/credit-manager/tests/test_lend.rs new file mode 100644 index 000000000..b274f61dd --- /dev/null +++ b/contracts/credit-manager/tests/test_lend.rs @@ -0,0 +1,185 @@ +use std::ops::{Add, Mul}; + +use cosmwasm_std::{coin, coins, Addr, OverflowError, OverflowOperation, Uint128}; +use mars_credit_manager::lend::DEFAULT_LENT_SHARES_PER_COIN; +use mars_rover::{ + error::ContractError, + msg::execute::Action::{Deposit, Lend}, +}; + +use crate::helpers::{ + assert_err, uosmo_info, AccountToFund, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, +}; + +pub mod helpers; + +#[test] +fn only_token_owner_can_lend() { + let coin_info = uosmo_info(); + let owner = Addr::unchecked("owner"); + let mut mock = MockEnv::new().build().unwrap(); + let account_id = mock.create_credit_account(&owner).unwrap(); + + let another_user = Addr::unchecked("another_user"); + let res = mock.update_credit_account( + &account_id, + &another_user, + vec![Lend(coin_info.to_coin(12312))], + &[], + ); + + assert_err( + res, + ContractError::NotTokenOwner { + user: another_user.into(), + account_id, + }, + ) +} + +#[test] +fn can_only_lend_what_is_whitelisted() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().allowed_coins(&[coin_info]).build().unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); + + let res = + mock.update_credit_account(&account_id, &user, vec![Lend(coin(234, "usomething"))], &[]); + + assert_err(res, ContractError::NotWhitelisted(String::from("usomething"))) +} + +#[test] +fn lending_zero_raises() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account(&account_id, &user, vec![Lend(coin_info.to_coin(0))], &[]); + + assert_err(res, ContractError::NoAmount) +} + +#[test] +fn raises_when_not_enough_assets_to_lend() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id_a = mock.create_credit_account(&user).unwrap(); + + let res = mock.update_credit_account( + &account_id_a, + &user, + vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_coin(500))], + &[coin_info.to_coin(300)], + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: OverflowOperation::Sub, + operand1: "300".to_string(), + operand2: "500".to_string(), + }), + ) +} + +#[test] +fn successful_lend() { + let coin_info = uosmo_info(); + + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id_a = mock.create_credit_account(&user_a).unwrap(); + + let position = mock.query_positions(&account_id_a); + assert_eq!(position.deposits.len(), 0); + assert_eq!(position.lends.len(), 0); + + mock.update_credit_account( + &account_id_a, + &user_a, + vec![Deposit(coin_info.to_coin(300))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + let red_bank_collateral = mock.query_red_bank_collateral(&coin_info.denom); + assert_eq!(red_bank_collateral.amount, Uint128::zero()); + + mock.update_credit_account(&account_id_a, &user_a, vec![Lend(coin_info.to_coin(50))], &[]) + .unwrap(); + + // Assert deposits decreased + let position = mock.query_positions(&account_id_a); + assert_eq!(position.deposits.len(), 1); + let deposit_res = position.deposits.first().unwrap(); + let expected_net_deposit_amount = Uint128::new(250); // Deposit - Lent + assert_eq!(deposit_res.amount, expected_net_deposit_amount); + + // Assert lend position amount increased + let lent_res = position.lends.first().unwrap(); + assert_eq!(position.lends.len(), 1); + assert_eq!(lent_res.denom, coin_info.denom); + let lent_amount = Uint128::new(50) + Uint128::new(1); // simulated yield + assert_eq!(lent_res.amount, lent_amount); + assert_eq!(lent_res.shares, Uint128::new(50).mul(DEFAULT_LENT_SHARES_PER_COIN)); + + // Assert total lent positions increased + let total = mock.query_total_lent_shares(&coin_info.denom); + assert_eq!(total.denom, coin_info.denom); + assert_eq!(total.shares, DEFAULT_LENT_SHARES_PER_COIN.mul(Uint128::new(50))); + + // Assert Rover has indeed sent those tokens to Red Bank + let balance = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(balance.amount, Uint128::new(250)); + + let config = mock.query_config(); + let red_bank_addr = Addr::unchecked(config.red_bank); + let balance = mock.query_balance(&red_bank_addr, &coin_info.denom); + assert_eq!(balance.amount, DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(50))); + + // Assert Rover's collateral balance in Red bank + let red_bank_collateral = mock.query_red_bank_collateral(&coin_info.denom); + assert_eq!(red_bank_collateral.amount, lent_amount); + + // Second user comes and performs a lend + let account_id_b = mock.create_credit_account(&user_b).unwrap(); + mock.update_credit_account( + &account_id_b, + &user_b, + vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_coin(50))], + &[coin(300, coin_info.denom)], + ) + .unwrap(); + + // Assert lend position shares amount is proportionally right given existing participant in pool + let position = mock.query_positions(&account_id_b); + let expected_shares = total.shares.multiply_ratio(Uint128::new(50), red_bank_collateral.amount); + assert_eq!(position.lends.first().unwrap().shares, expected_shares); +} diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index 1a391d86a..2d1aa1192 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -22,4 +22,5 @@ library = [] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } mars-outpost = { workspace = true } diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index e17127d68..2ccae8884 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -4,9 +4,9 @@ use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, use mars_outpost::red_bank; use crate::{ - execute::{borrow, repay}, + execute::{borrow, deposit, repay}, msg::InstantiateMsg, - query::{query_debt, query_market}, + query::{query_collateral, query_debt, query_market}, state::COIN_MARKET_INFO, }; @@ -39,6 +39,9 @@ pub fn execute( red_bank::ExecuteMsg::Repay { .. } => repay(deps, info), + red_bank::ExecuteMsg::Deposit { + .. + } => deposit(deps, info), _ => unimplemented!("Msg not supported!"), } } @@ -53,6 +56,10 @@ pub fn query(deps: Deps, _env: Env, msg: red_bank::QueryMsg) -> StdResult to_binary(&query_market(deps, denom)?), + red_bank::QueryMsg::UserCollateral { + user, + denom, + } => to_binary(&query_collateral(deps, user, denom)?), _ => unimplemented!("Query not supported!"), } } diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs index b9a02c96b..05523d3f8 100644 --- a/contracts/mock-red-bank/src/execute.rs +++ b/contracts/mock-red-bank/src/execute.rs @@ -1,6 +1,12 @@ -use cosmwasm_std::{coin, BankMsg, CosmosMsg, DepsMut, MessageInfo, Response, StdResult, Uint128}; +use cosmwasm_std::{ + coin, BankMsg, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, Uint128, +}; +use cw_utils::one_coin; -use crate::{helpers::load_debt_amount, state::DEBT_AMOUNT}; +use crate::{ + helpers::{load_collateral_amount, load_debt_amount}, + state::{COLLATERAL_AMOUNT, DEBT_AMOUNT}, +}; pub fn borrow( deps: DepsMut, @@ -25,14 +31,29 @@ pub fn borrow( } pub fn repay(deps: DepsMut, info: MessageInfo) -> StdResult { - let coin_sent = info.funds.first().unwrap(); + let coin_sent = + one_coin(&info).map_err(|_| StdError::generic_err("Repay coin reqs not met"))?; let debt_amount = load_debt_amount(deps.storage, &info.sender, &coin_sent.denom)?; DEBT_AMOUNT.save( deps.storage, - (info.sender.clone(), coin_sent.denom.clone()), + (info.sender, coin_sent.denom.clone()), &debt_amount.checked_sub(coin_sent.amount)?, )?; Ok(Response::new()) } + +pub fn deposit(deps: DepsMut, info: MessageInfo) -> StdResult { + let to_deposit = + one_coin(&info).map_err(|_| StdError::generic_err("Deposit coin reqs not met"))?; + let collateral_amount = load_collateral_amount(deps.storage, &info.sender, &to_deposit.denom)?; + + COLLATERAL_AMOUNT.save( + deps.storage, + (info.sender, to_deposit.denom.clone()), + &collateral_amount.checked_add(to_deposit.amount)?.checked_add(Uint128::new(1))?, // The extra unit is simulated accrued yield + )?; + + Ok(Response::new()) +} diff --git a/contracts/mock-red-bank/src/helpers.rs b/contracts/mock-red-bank/src/helpers.rs index 4d1e06adb..32b8b414a 100644 --- a/contracts/mock-red-bank/src/helpers.rs +++ b/contracts/mock-red-bank/src/helpers.rs @@ -1,9 +1,19 @@ use cosmwasm_std::{Addr, StdResult, Storage, Uint128}; -use crate::state::DEBT_AMOUNT; +use crate::state::{COLLATERAL_AMOUNT, DEBT_AMOUNT}; pub fn load_debt_amount(storage: &dyn Storage, user: &Addr, denom: &str) -> StdResult { Ok(DEBT_AMOUNT .may_load(storage, (user.clone(), denom.to_string()))? .unwrap_or_else(Uint128::zero)) } + +pub fn load_collateral_amount( + storage: &dyn Storage, + user: &Addr, + denom: &str, +) -> StdResult { + Ok(COLLATERAL_AMOUNT + .may_load(storage, (user.clone(), denom.to_string()))? + .unwrap_or_else(Uint128::zero)) +} diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index b60640353..8d6dcda38 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -1,7 +1,10 @@ use cosmwasm_std::{Deps, StdResult, Uint128}; -use mars_outpost::red_bank::{Market, UserDebtResponse}; +use mars_outpost::red_bank::{Market, UserCollateralResponse, UserDebtResponse}; -use crate::{helpers::load_debt_amount, state::COIN_MARKET_INFO}; +use crate::{ + helpers::{load_collateral_amount, load_debt_amount}, + state::COIN_MARKET_INFO, +}; pub fn query_debt(deps: Deps, user: String, denom: String) -> StdResult { let user_addr = deps.api.addr_validate(&user)?; @@ -23,3 +26,18 @@ pub fn query_market(deps: Deps, denom: String) -> StdResult { ..Default::default() }) } + +pub fn query_collateral( + deps: Deps, + user: String, + denom: String, +) -> StdResult { + let user_addr = deps.api.addr_validate(&user)?; + let amount = load_collateral_amount(deps.storage, &user_addr, &denom)?; + Ok(UserCollateralResponse { + denom, + amount, + amount_scaled: Default::default(), + enabled: true, + }) +} diff --git a/contracts/mock-red-bank/src/state.rs b/contracts/mock-red-bank/src/state.rs index 0589d126b..b006b1b40 100644 --- a/contracts/mock-red-bank/src/state.rs +++ b/contracts/mock-red-bank/src/state.rs @@ -5,5 +5,7 @@ use crate::msg::CoinMarketInfo; // Map<(DebtHolder, CoinDenom), AmountOfDebt> pub const DEBT_AMOUNT: Map<(Addr, String), Uint128> = Map::new("debt_amount"); +// Map<(CollateralHolder, CoinDenom), AmountOfCollateral> +pub const COLLATERAL_AMOUNT: Map<(Addr, String), Uint128> = Map::new("collateral_amount"); // Map pub const COIN_MARKET_INFO: Map = Map::new("coin_market_info"); diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index 06a10fae5..bcd3b34c6 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -58,6 +58,34 @@ impl RedBank { })) } + /// Generate message for lending a specified amount of coin + pub fn lend_msg(&self, coin: &Coin) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address().to_string(), + msg: to_binary(&red_bank::ExecuteMsg::Deposit { + on_behalf_of: None, + })?, + funds: vec![coin.clone()], + })) + } + + pub fn query_lent( + &self, + querier: &QuerierWrapper, + user_address: &Addr, + denom: &str, + ) -> StdResult { + let response: red_bank::UserCollateralResponse = + querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.address().to_string(), + msg: to_binary(&red_bank::QueryMsg::UserCollateral { + user: user_address.to_string(), + denom: denom.to_string(), + })?, + }))?; + Ok(response.amount) + } + pub fn query_debt( &self, querier: &QuerierWrapper, diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index b81b3034b..9eacc2563 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -80,6 +80,8 @@ pub enum Action { Withdraw(Coin), /// Borrow coin of specified amount from Red Bank Borrow(Coin), + /// Lend coin to the Red Bank + Lend(Coin), /// Repay coin of specified amount back to Red Bank. If `amount: AccountBalance` is passed, /// the repaid amount will be the minimum between account balance for denom and total owed. Repay(ActionCoin), @@ -178,6 +180,11 @@ pub enum CallbackMsg { account_id: String, coin: ActionCoin, }, + /// Lend coin to the Red Bank + Lend { + account_id: String, + coin: Coin, + }, /// Assert MaxLTV is either: /// - Healthy, if prior to actions MaxLTV health factor >= 1 or None /// - Not further weakened, if prior to actions MaxLTV health factor < 1 @@ -261,10 +268,6 @@ pub enum CallbackMsg { account_id: String, lp_token: ActionCoin, }, - /// Checks to ensure only one vault position is taken per credit account - AssertOneVaultPositionOnly { - account_id: String, - }, /// Refunds all coin balances back to user wallet RefundAllCoinBalances { account_id: String, diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index a689f5948..8c9bb0b10 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -55,6 +55,21 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, + /// Enumerate debt shares for all token positions; start_after accepts (account_id, denom) + #[returns(Vec)] + AllLentShares { + start_after: Option<(String, String)>, + limit: Option, + }, + /// Total debt shares issued for Coin + #[returns(LentShares)] + TotalLentShares(String), + /// Enumerate total lent shares for all supported coins; start_after accepts denom string + #[returns(Vec)] + AllTotalLentShares { + start_after: Option, + limit: Option, + }, /// Enumerate all vault positions; start_after accepts (account_id, addr) #[returns(Vec)] AllVaultPositions { @@ -114,6 +129,12 @@ pub struct DebtShares { pub shares: Uint128, } +#[cw_serde] +pub struct LentShares { + pub denom: String, + pub shares: Uint128, +} + #[cw_serde] pub struct DebtAmount { pub denom: String, @@ -123,6 +144,15 @@ pub struct DebtAmount { pub amount: Uint128, } +#[cw_serde] +pub struct LentAmount { + pub denom: String, + /// number of shares in lent pool + pub shares: Uint128, + /// amount of coins + pub amount: Uint128, +} + impl Coins for Vec { fn to_coins(&self) -> Vec { self.iter() @@ -147,6 +177,7 @@ pub struct Positions { pub account_id: String, pub deposits: Vec, pub debts: Vec, + pub lends: Vec, pub vaults: Vec, } diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 8a0539f47..0cbbec4da 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -337,6 +337,19 @@ }, "additionalProperties": false }, + { + "description": "Lend coin to the Red Bank", + "type": "object", + "required": [ + "lend" + ], + "properties": { + "lend": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + }, { "description": "Repay coin of specified amount back to Red Bank. If `amount: AccountBalance` is passed, the repaid amount will be the minimum between account balance for denom and total owed.", "type": "object", @@ -755,6 +768,32 @@ }, "additionalProperties": false }, + { + "description": "Lend coin to the Red Bank", + "type": "object", + "required": [ + "lend" + ], + "properties": { + "lend": { + "type": "object", + "required": [ + "account_id", + "coin" + ], + "properties": { + "account_id": { + "type": "string" + }, + "coin": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Assert MaxLTV is either: - Healthy, if prior to actions MaxLTV health factor >= 1 or None - Not further weakened, if prior to actions MaxLTV health factor < 1 Emits a `position_changed` event.", "type": "object", @@ -1139,28 +1178,6 @@ }, "additionalProperties": false }, - { - "description": "Checks to ensure only one vault position is taken per credit account", - "type": "object", - "required": [ - "assert_one_vault_position_only" - ], - "properties": { - "assert_one_vault_position_only": { - "type": "object", - "required": [ - "account_id" - ], - "properties": { - "account_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "Refunds all coin balances back to user wallet", "type": "object", @@ -1756,6 +1773,89 @@ }, "additionalProperties": false }, + { + "description": "Enumerate debt shares for all token positions; start_after accepts (account_id, denom)", + "type": "object", + "required": [ + "all_lent_shares" + ], + "properties": { + "all_lent_shares": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Total debt shares issued for Coin", + "type": "object", + "required": [ + "total_lent_shares" + ], + "properties": { + "total_lent_shares": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate total lent shares for all supported coins; start_after accepts denom string", + "type": "object", + "required": [ + "all_total_lent_shares" + ], + "properties": { + "all_total_lent_shares": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Enumerate all vault positions; start_after accepts (account_id, addr)", "type": "object", @@ -2009,6 +2109,40 @@ } } }, + "all_lent_shares": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_SharesResponseItem", + "type": "array", + "items": { + "$ref": "#/definitions/SharesResponseItem" + }, + "definitions": { + "SharesResponseItem": { + "type": "object", + "required": [ + "account_id", + "denom", + "shares" + ], + "properties": { + "account_id": { + "type": "string" + }, + "denom": { + "type": "string" + }, + "shares": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "all_total_debt_shares": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_DebtShares", @@ -2039,6 +2173,36 @@ } } }, + "all_total_lent_shares": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_LentShares", + "type": "array", + "items": { + "$ref": "#/definitions/LentShares" + }, + "definitions": { + "LentShares": { + "type": "object", + "required": [ + "denom", + "shares" + ], + "properties": { + "denom": { + "type": "string" + }, + "shares": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "all_total_vault_coin_balances": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_VaultWithBalance", @@ -2416,6 +2580,7 @@ "account_id", "debts", "deposits", + "lends", "vaults" ], "properties": { @@ -2434,6 +2599,12 @@ "$ref": "#/definitions/Coin" } }, + "lends": { + "type": "array", + "items": { + "$ref": "#/definitions/LentAmount" + } + }, "vaults": { "type": "array", "items": { @@ -2492,6 +2663,36 @@ }, "additionalProperties": false }, + "LentAmount": { + "type": "object", + "required": [ + "amount", + "denom", + "shares" + ], + "properties": { + "amount": { + "description": "amount of coins", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "type": "string" + }, + "shares": { + "description": "number of shares in lent pool", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, "LockingVaultAmount": { "type": "object", "required": [ @@ -2627,6 +2828,30 @@ } } }, + "total_lent_shares": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LentShares", + "type": "object", + "required": [ + "denom", + "shares" + ], + "properties": { + "denom": { + "type": "string" + }, + "shares": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "total_vault_coin_balance": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Uint128", diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 98be73633..13fc0c857 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -350,6 +350,89 @@ }, "additionalProperties": false }, + { + "description": "Enumerate debt shares for all token positions; start_after accepts (account_id, denom)", + "type": "object", + "required": [ + "all_lent_shares" + ], + "properties": { + "all_lent_shares": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Total debt shares issued for Coin", + "type": "object", + "required": [ + "total_lent_shares" + ], + "properties": { + "total_lent_shares": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate total lent shares for all supported coins; start_after accepts denom string", + "type": "object", + "required": [ + "all_total_lent_shares" + ], + "properties": { + "all_total_lent_shares": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Enumerate all vault positions; start_after accepts (account_id, addr)", "type": "object", @@ -603,6 +686,40 @@ } } }, + "all_lent_shares": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_SharesResponseItem", + "type": "array", + "items": { + "$ref": "#/definitions/SharesResponseItem" + }, + "definitions": { + "SharesResponseItem": { + "type": "object", + "required": [ + "account_id", + "denom", + "shares" + ], + "properties": { + "account_id": { + "type": "string" + }, + "denom": { + "type": "string" + }, + "shares": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "all_total_debt_shares": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_DebtShares", @@ -633,6 +750,36 @@ } } }, + "all_total_lent_shares": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_LentShares", + "type": "array", + "items": { + "$ref": "#/definitions/LentShares" + }, + "definitions": { + "LentShares": { + "type": "object", + "required": [ + "denom", + "shares" + ], + "properties": { + "denom": { + "type": "string" + }, + "shares": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "all_total_vault_coin_balances": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_VaultWithBalance", @@ -1010,6 +1157,7 @@ "account_id", "debts", "deposits", + "lends", "vaults" ], "properties": { @@ -1028,6 +1176,12 @@ "$ref": "#/definitions/Coin" } }, + "lends": { + "type": "array", + "items": { + "$ref": "#/definitions/LentAmount" + } + }, "vaults": { "type": "array", "items": { @@ -1086,6 +1240,36 @@ }, "additionalProperties": false }, + "LentAmount": { + "type": "object", + "required": [ + "amount", + "denom", + "shares" + ], + "properties": { + "amount": { + "description": "amount of coins", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "type": "string" + }, + "shares": { + "description": "number of shares in lent pool", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, "LockingVaultAmount": { "type": "object", "required": [ @@ -1221,6 +1405,30 @@ } } }, + "total_lent_shares": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LentShares", + "type": "object", + "required": [ + "denom", + "shares" + ], + "properties": { + "denom": { + "type": "string" + }, + "shares": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "total_vault_coin_balance": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Uint128", diff --git a/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index b10b0e926..679939b24 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,7 @@ { - "mockVault": "osmo1zhmm3hcs0a9yu6pvdeyup0ujayd6apmhurkuhffdf4jga79gqwzsagf5sx", - "swapper": "osmo1npf09lld68z5qg00runrxxxname23plp3lkqx8r4ku7rg2qaj6eq4wjl3d", - "zapper": "osmo1kcnvvwh43qcd77f6v5st2xx6pknepyrxmk9k07llx3v0qsjv39jsgrncht", - "creditManager": "osmo1prwnxn3vlvh0kqmwxn8whqnavk8ze9hrccwpsapysgpa3pj8r2csy84grp", - "accountNft": "osmo1ua5rw84jxg6e7ma4hx7v7yhqcks74cjnx38gpnsvtfzrtxhwcvjqgsxulx" + "mockVault": "osmo132xghzdpsk2zm9a3tu2sh3zttfralkgp5ha7dvfu4age4xfpx4zqf3vqj6", + "swapper": "osmo1ewg8munakw2u0qh8lee375ytpg97zezf8lpnzrt2025xc93xx2ksw6zg64", + "zapper": "osmo1uvuewqqx0rtecx94yqauud62lkh4k9ayl7606nvtxd7uxhtrk7jqtauxq8", + "creditManager": "osmo13tj65wjs85wj0ry4daaqt2cp22p8dkd3uzajefp0c0tp37sgzfxqdyqu72", + "accountNft": "osmo1c93332nnydpn8f66zd4jka94hxvltff3tzt0fy0k7j3clyx265wq9csmfe" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 9e476e950..700fe4e5a 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -228,7 +228,9 @@ export class Deployer { const client = await setupClient(this.config, wallet) const addr = await getAddress(wallet) - for (const denom of [this.config.chain.baseDenom, this.config.testActions!.secondaryDenom]) { + for (const denom of this.config.allowedCoins + .filter((c) => c.grantCreditLine) + .map((c) => c.denom)) { const msg = { update_uncollateralized_loan_limit: { user: this.storage.addresses.creditManager, diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index cfdbc1b15..3c00d7e60 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -41,6 +41,7 @@ export const taskRunner = async ({ const rover = await deployer.newUserRoverClient(config.testActions) await rover.createCreditAccount() await rover.deposit() + await rover.lend() await rover.borrow() await rover.swap() await rover.repay() diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 8daf0439a..ee2020e40 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -62,6 +62,15 @@ export class Rover { printGreen(`Deposited into credit account: ${amount} ${this.config.chain.baseDenom}`) } + async lend() { + const amount = this.actions.lendAmount + await this.updateCreditAccount([{ lend: { amount, denom: this.config.chain.baseDenom } }], []) + const positions = await this.query.positions({ accountId: this.accountId! }) + assert.equal(positions.lends.length, 1) + assert.equal(positions.lends[0].denom, this.config.chain.baseDenom) + printGreen(`Lent to Red Bank: ${amount} ${this.config.chain.baseDenom}`) + } + async withdraw() { const amount = this.actions.withdrawAmount const positionsBefore = await this.query.positions({ accountId: this.accountId! }) diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 254654507..9a27927bb 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -16,11 +16,27 @@ const vaultJunoOsmo14 = 'osmo1rclt7lsfp0c89ydf9umuhwlg28maw6z87jak3ly7u2lefnyzdz export const osmosisTestnetConfig: DeploymentConfig = { allowedCoins: [ - { denom: uosmo, priceSource: { fixed: { price: '1' } } }, - { denom: uatom, priceSource: { arithmetic_twap: { pool_id: 1, window_size: 1800 } } }, - { denom: ujuno, priceSource: { arithmetic_twap: { pool_id: 497, window_size: 1800 } } }, - { denom: gammPool1, priceSource: { xyk_liquidity_token: { pool_id: 1 } } }, - { denom: gammPool497, priceSource: { xyk_liquidity_token: { pool_id: 497 } } }, + { denom: uosmo, priceSource: { fixed: { price: '1' } }, grantCreditLine: true }, + { + denom: uatom, + priceSource: { arithmetic_twap: { pool_id: 1, window_size: 1800 } }, + grantCreditLine: true, + }, + { + denom: ujuno, + priceSource: { arithmetic_twap: { pool_id: 497, window_size: 1800 } }, + grantCreditLine: true, + }, + { + denom: gammPool1, + priceSource: { xyk_liquidity_token: { pool_id: 1 } }, + grantCreditLine: false, + }, + { + denom: gammPool497, + priceSource: { xyk_liquidity_token: { pool_id: 497 } }, + grantCreditLine: false, + }, ], chain: { baseDenom: uosmo, @@ -123,6 +139,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { repayAmount: '11', defaultCreditLine: '100000000000', depositAmount: '100', + lendAmount: '10', secondaryDenom: uatom, startingAmountForTestUser: '2500000', swap: { diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 3be420aad..320037517 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -24,7 +24,7 @@ export interface DeploymentConfig { oracle: { addr: string } redBank: { addr: string } vaults: VaultInstantiateConfig[] - allowedCoins: { denom: string; priceSource: PriceSource }[] + allowedCoins: { denom: string; priceSource: PriceSource; grantCreditLine: boolean }[] maxCloseFactor: string maxValueForBurn: string maxUnlockingPositions: string @@ -55,6 +55,7 @@ export interface TestActions { defaultCreditLine: string startingAmountForTestUser: string depositAmount: string + lendAmount: string borrowAmount: string repayAmount: string swap: { diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 5a08e6058..15a988d27 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -38,6 +38,8 @@ import { SharesResponseItem, ArrayOfDebtShares, DebtShares, + ArrayOfLentShares, + LentShares, ArrayOfVaultWithBalance, VaultWithBalance, VaultPositionAmount, @@ -55,6 +57,7 @@ import { HealthResponse, Positions, DebtAmount, + LentAmount, ArrayOfVaultInfoResponse, VaultInfoResponse, } from './MarsCreditManager.types' @@ -99,6 +102,21 @@ export interface MarsCreditManagerReadOnlyInterface { limit?: number startAfter?: string }) => Promise + allLentShares: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }) => Promise + totalLentShares: () => Promise + allTotalLentShares: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }) => Promise allVaultPositions: ({ limit, startAfter, @@ -139,6 +157,9 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn this.allDebtShares = this.allDebtShares.bind(this) this.totalDebtShares = this.totalDebtShares.bind(this) this.allTotalDebtShares = this.allTotalDebtShares.bind(this) + this.allLentShares = this.allLentShares.bind(this) + this.totalLentShares = this.totalLentShares.bind(this) + this.allTotalLentShares = this.allTotalLentShares.bind(this) this.allVaultPositions = this.allVaultPositions.bind(this) this.totalVaultCoinBalance = this.totalVaultCoinBalance.bind(this) this.allTotalVaultCoinBalances = this.allTotalVaultCoinBalances.bind(this) @@ -240,6 +261,39 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn }, }) } + allLentShares = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_lent_shares: { + limit, + start_after: startAfter, + }, + }) + } + totalLentShares = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + total_lent_shares: {}, + }) + } + allTotalLentShares = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_total_lent_shares: { + limit, + start_after: startAfter, + }, + }) + } allVaultPositions = async ({ limit, startAfter, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 6aca78328..b2778ee51 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -39,6 +39,8 @@ import { SharesResponseItem, ArrayOfDebtShares, DebtShares, + ArrayOfLentShares, + LentShares, ArrayOfVaultWithBalance, VaultWithBalance, VaultPositionAmount, @@ -56,6 +58,7 @@ import { HealthResponse, Positions, DebtAmount, + LentAmount, ArrayOfVaultInfoResponse, VaultInfoResponse, } from './MarsCreditManager.types' diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index b2f2eb4c1..3535ae9a2 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -39,6 +39,8 @@ import { SharesResponseItem, ArrayOfDebtShares, DebtShares, + ArrayOfLentShares, + LentShares, ArrayOfVaultWithBalance, VaultWithBalance, VaultPositionAmount, @@ -56,6 +58,7 @@ import { HealthResponse, Positions, DebtAmount, + LentAmount, ArrayOfVaultInfoResponse, VaultInfoResponse, } from './MarsCreditManager.types' @@ -120,6 +123,30 @@ export const marsCreditManagerQueryKeys = { args, }, ] as const, + allLentShares: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'all_lent_shares', + args, + }, + ] as const, + totalLentShares: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'total_lent_shares', + args, + }, + ] as const, + allTotalLentShares: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'all_total_lent_shares', + args, + }, + ] as const, allVaultPositions: (contractAddress: string | undefined, args?: Record) => [ { @@ -288,6 +315,66 @@ export function useMarsCreditManagerAllVaultPositionsQuery< { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsCreditManagerAllTotalLentSharesQuery + extends MarsCreditManagerReactQuery { + args: { + limit?: number + startAfter?: string + } +} +export function useMarsCreditManagerAllTotalLentSharesQuery({ + client, + args, + options, +}: MarsCreditManagerAllTotalLentSharesQuery) { + return useQuery( + marsCreditManagerQueryKeys.allTotalLentShares(client?.contractAddress, args), + () => + client + ? client.allTotalLentShares({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerTotalLentSharesQuery + extends MarsCreditManagerReactQuery {} +export function useMarsCreditManagerTotalLentSharesQuery({ + client, + options, +}: MarsCreditManagerTotalLentSharesQuery) { + return useQuery( + marsCreditManagerQueryKeys.totalLentShares(client?.contractAddress), + () => (client ? client.totalLentShares() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerAllLentSharesQuery + extends MarsCreditManagerReactQuery { + args: { + limit?: number + startAfter?: string[][] + } +} +export function useMarsCreditManagerAllLentSharesQuery({ + client, + args, + options, +}: MarsCreditManagerAllLentSharesQuery) { + return useQuery( + marsCreditManagerQueryKeys.allLentShares(client?.contractAddress, args), + () => + client + ? client.allLentShares({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsCreditManagerAllTotalDebtSharesQuery extends MarsCreditManagerReactQuery { args: { diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 0af06718c..209580c9b 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -76,6 +76,9 @@ export type Action = | { borrow: Coin } + | { + lend: Coin + } | { repay: ActionCoin } @@ -175,6 +178,12 @@ export type CallbackMsg = coin: ActionCoin } } + | { + lend: { + account_id: string + coin: Coin + } + } | { assert_max_ltv: { account_id: string @@ -261,11 +270,6 @@ export type CallbackMsg = lp_token: ActionCoin } } - | { - assert_one_vault_position_only: { - account_id: string - } - } | { refund_all_coin_balances: { account_id: string @@ -348,6 +352,21 @@ export type QueryMsg = start_after?: string | null } } + | { + all_lent_shares: { + limit?: number | null + start_after?: [string, string] | null + } + } + | { + total_lent_shares: string + } + | { + all_total_lent_shares: { + limit?: number | null + start_after?: string | null + } + } | { all_vault_positions: { limit?: number | null @@ -393,6 +412,11 @@ export interface DebtShares { denom: string shares: Uint128 } +export type ArrayOfLentShares = LentShares[] +export interface LentShares { + denom: string + shares: Uint128 +} export type ArrayOfVaultWithBalance = VaultWithBalance[] export interface VaultWithBalance { balance: Uint128 @@ -452,6 +476,7 @@ export interface Positions { account_id: string debts: DebtAmount[] deposits: Coin[] + lends: LentAmount[] vaults: VaultPosition[] } export interface DebtAmount { @@ -459,6 +484,11 @@ export interface DebtAmount { denom: string shares: Uint128 } +export interface LentAmount { + amount: Uint128 + denom: string + shares: Uint128 +} export type ArrayOfVaultInfoResponse = VaultInfoResponse[] export interface VaultInfoResponse { config: VaultConfig diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index f4dfb7155..a5ac81d97 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -22,6 +22,8 @@ import { SharesResponseItem, ArrayOfDebtShares, DebtShares, + ArrayOfLentShares, + LentShares, Addr, ArrayOfVaultWithBalance, VaultWithBalance, @@ -40,6 +42,7 @@ import { ArrayOfCoin, Positions, DebtAmount, + LentAmount, ArrayOfVaultInfoResponse, VaultInfoResponse, VaultConfig, @@ -85,6 +88,21 @@ export interface MarsMockCreditManagerReadOnlyInterface { limit?: number startAfter?: string }) => Promise + allLentShares: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }) => Promise + totalLentShares: () => Promise + allTotalLentShares: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }) => Promise allVaultPositions: ({ limit, startAfter, @@ -125,6 +143,9 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe this.allDebtShares = this.allDebtShares.bind(this) this.totalDebtShares = this.totalDebtShares.bind(this) this.allTotalDebtShares = this.allTotalDebtShares.bind(this) + this.allLentShares = this.allLentShares.bind(this) + this.totalLentShares = this.totalLentShares.bind(this) + this.allTotalLentShares = this.allTotalLentShares.bind(this) this.allVaultPositions = this.allVaultPositions.bind(this) this.totalVaultCoinBalance = this.totalVaultCoinBalance.bind(this) this.allTotalVaultCoinBalances = this.allTotalVaultCoinBalances.bind(this) @@ -226,6 +247,39 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe }, }) } + allLentShares = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_lent_shares: { + limit, + start_after: startAfter, + }, + }) + } + totalLentShares = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + total_lent_shares: {}, + }) + } + allTotalLentShares = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_total_lent_shares: { + limit, + start_after: startAfter, + }, + }) + } allVaultPositions = async ({ limit, startAfter, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index 039413d6d..83ad465f6 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -23,6 +23,8 @@ import { SharesResponseItem, ArrayOfDebtShares, DebtShares, + ArrayOfLentShares, + LentShares, Addr, ArrayOfVaultWithBalance, VaultWithBalance, @@ -41,6 +43,7 @@ import { ArrayOfCoin, Positions, DebtAmount, + LentAmount, ArrayOfVaultInfoResponse, VaultInfoResponse, VaultConfig, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index ff95c833e..e85bda23e 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -23,6 +23,8 @@ import { SharesResponseItem, ArrayOfDebtShares, DebtShares, + ArrayOfLentShares, + LentShares, Addr, ArrayOfVaultWithBalance, VaultWithBalance, @@ -41,6 +43,7 @@ import { ArrayOfCoin, Positions, DebtAmount, + LentAmount, ArrayOfVaultInfoResponse, VaultInfoResponse, VaultConfig, @@ -117,6 +120,30 @@ export const marsMockCreditManagerQueryKeys = { args, }, ] as const, + allLentShares: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], + method: 'all_lent_shares', + args, + }, + ] as const, + totalLentShares: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], + method: 'total_lent_shares', + args, + }, + ] as const, + allTotalLentShares: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], + method: 'all_total_lent_shares', + args, + }, + ] as const, allVaultPositions: (contractAddress: string | undefined, args?: Record) => [ { @@ -285,6 +312,66 @@ export function useMarsMockCreditManagerAllVaultPositionsQuery< { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsMockCreditManagerAllTotalLentSharesQuery + extends MarsMockCreditManagerReactQuery { + args: { + limit?: number + startAfter?: string + } +} +export function useMarsMockCreditManagerAllTotalLentSharesQuery({ + client, + args, + options, +}: MarsMockCreditManagerAllTotalLentSharesQuery) { + return useQuery( + marsMockCreditManagerQueryKeys.allTotalLentShares(client?.contractAddress, args), + () => + client + ? client.allTotalLentShares({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsMockCreditManagerTotalLentSharesQuery + extends MarsMockCreditManagerReactQuery {} +export function useMarsMockCreditManagerTotalLentSharesQuery({ + client, + options, +}: MarsMockCreditManagerTotalLentSharesQuery) { + return useQuery( + marsMockCreditManagerQueryKeys.totalLentShares(client?.contractAddress), + () => (client ? client.totalLentShares() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsMockCreditManagerAllLentSharesQuery + extends MarsMockCreditManagerReactQuery { + args: { + limit?: number + startAfter?: string[][] + } +} +export function useMarsMockCreditManagerAllLentSharesQuery({ + client, + args, + options, +}: MarsMockCreditManagerAllLentSharesQuery) { + return useQuery( + marsMockCreditManagerQueryKeys.allLentShares(client?.contractAddress, args), + () => + client + ? client.allLentShares({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsMockCreditManagerAllTotalDebtSharesQuery extends MarsMockCreditManagerReactQuery { args: { diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 978a599d5..4de047a59 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -73,6 +73,21 @@ export type QueryMsg = start_after?: string | null } } + | { + all_lent_shares: { + limit?: number | null + start_after?: [string, string] | null + } + } + | { + total_lent_shares: string + } + | { + all_total_lent_shares: { + limit?: number | null + start_after?: string | null + } + } | { all_vault_positions: { limit?: number | null @@ -126,6 +141,11 @@ export interface DebtShares { denom: string shares: Uint128 } +export type ArrayOfLentShares = LentShares[] +export interface LentShares { + denom: string + shares: Uint128 +} export type Addr = string export type ArrayOfVaultWithBalance = VaultWithBalance[] export interface VaultWithBalance { @@ -179,6 +199,7 @@ export interface Positions { account_id: string debts: DebtAmount[] deposits: Coin[] + lends: LentAmount[] vaults: VaultPosition[] } export interface DebtAmount { @@ -186,6 +207,11 @@ export interface DebtAmount { denom: string shares: Uint128 } +export interface LentAmount { + amount: Uint128 + denom: string + shares: Uint128 +} export type ArrayOfVaultInfoResponse = VaultInfoResponse[] export interface VaultInfoResponse { config: VaultConfig diff --git a/scripts/yarn.lock b/scripts/yarn.lock index c41dc4858..9d74763b1 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -17,10 +17,10 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.4", "@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30" - integrity sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5": + version "7.20.10" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.10.tgz#9d92fa81b87542fff50e848ed585b4212c1d34ec" + integrity sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg== "@babel/core@7.18.10": version "7.18.10" @@ -44,24 +44,24 @@ semver "^6.3.0" "@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.6.tgz#7122ae4f5c5a37c0946c066149abd8e75f81540f" - integrity sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg== + version "7.20.12" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d" + integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.6" - "@babel/helper-compilation-targets" "^7.19.3" - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helpers" "^7.19.4" - "@babel/parser" "^7.19.6" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.6" - "@babel/types" "^7.19.4" + "@babel/generator" "^7.20.7" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helpers" "^7.20.7" + "@babel/parser" "^7.20.7" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.12" + "@babel/types" "^7.20.7" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.1" + json5 "^2.2.2" semver "^6.3.0" "@babel/generator@7.18.12": @@ -73,12 +73,12 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/generator@^7.18.10", "@babel/generator@^7.19.6", "@babel/generator@^7.20.1", "@babel/generator@^7.7.2": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.1.tgz#ef32ecd426222624cbd94871a7024639cf61a9fa" - integrity sha512-u1dMdBUmA7Z0rBB97xh8pIhviK7oItYOkjbsCxTWMknyvbQRBwX7/gn4JXurRdirWMFh+ZtYARqkA6ydogVZpg== +"@babel/generator@^7.18.10", "@babel/generator@^7.20.7", "@babel/generator@^7.7.2": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.7.tgz#f8ef57c8242665c5929fe2e8d82ba75460187b4a" + integrity sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw== dependencies: - "@babel/types" "^7.20.0" + "@babel/types" "^7.20.7" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" @@ -97,36 +97,38 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.3", "@babel/helper-compilation-targets@^7.20.0": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a" - integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0", "@babel/helper-compilation-targets@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb" + integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== dependencies: - "@babel/compat-data" "^7.20.0" + "@babel/compat-data" "^7.20.5" "@babel/helper-validator-option" "^7.18.6" browserslist "^4.21.3" + lru-cache "^5.1.1" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b" - integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.5", "@babel/helper-create-class-features-plugin@^7.20.7": + version "7.20.12" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.12.tgz#4349b928e79be05ed2d1643b20b99bb87c503819" + integrity sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" - "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.20.7" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-replace-supers" "^7.20.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/helper-split-export-declaration" "^7.18.6" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" - integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz#5ea79b59962a09ec2acf20a963a01ab4d076ccca" + integrity sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - regexpu-core "^5.1.0" + regexpu-core "^5.2.1" "@babel/helper-define-polyfill-provider@^0.3.2", "@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" @@ -167,12 +169,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-member-expression-to-functions@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" - integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== +"@babel/helper-member-expression-to-functions@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz#a6f26e919582275a93c3aa6594756d71b0bb7f05" + integrity sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw== dependencies: - "@babel/types" "^7.18.9" + "@babel/types" "^7.20.7" "@babel/helper-module-imports@^7.18.6": version "7.18.6" @@ -181,19 +183,19 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz#6c52cc3ac63b70952d33ee987cbee1c9368b533f" - integrity sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw== +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.20.11": + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz#df4c7af713c557938c50ea3ad0117a7944b2f1b0" + integrity sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.19.4" + "@babel/helper-simple-access" "^7.20.2" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.6" - "@babel/types" "^7.19.4" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.10" + "@babel/types" "^7.20.7" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -202,17 +204,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" - integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== - -"@babel/helper-plugin-utils@^7.20.2": +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.20.2" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== -"@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": +"@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== @@ -222,25 +219,26 @@ "@babel/helper-wrap-function" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9", "@babel/helper-replace-supers@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" - integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331" + integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A== dependencies: "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.20.7" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.19.1" - "@babel/types" "^7.19.0" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.7" + "@babel/types" "^7.20.7" -"@babel/helper-simple-access@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz#be553f4951ac6352df2567f7daa19a0ee15668e7" - integrity sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg== +"@babel/helper-simple-access@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" + integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== dependencies: - "@babel/types" "^7.19.4" + "@babel/types" "^7.20.2" -"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": +"@babel/helper-skip-transparent-expression-wrappers@^7.20.0": version "7.20.0" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== @@ -270,23 +268,23 @@ integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== "@babel/helper-wrap-function@^7.18.9": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz#89f18335cff1152373222f76a4b37799636ae8b1" - integrity sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg== + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz#75e2d84d499a0ab3b31c33bcfe59d6b8a45f62e3" + integrity sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q== dependencies: "@babel/helper-function-name" "^7.19.0" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" + "@babel/traverse" "^7.20.5" + "@babel/types" "^7.20.5" -"@babel/helpers@^7.18.9", "@babel/helpers@^7.19.4": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.1.tgz#2ab7a0fcb0a03b5bf76629196ed63c2d7311f4c9" - integrity sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg== +"@babel/helpers@^7.18.9", "@babel/helpers@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.7.tgz#04502ff0feecc9f20ecfaad120a18f011a8e6dce" + integrity sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA== dependencies: - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.1" - "@babel/types" "^7.20.0" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.7" + "@babel/types" "^7.20.7" "@babel/highlight@^7.18.6": version "7.18.6" @@ -302,10 +300,10 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.19.6", "@babel/parser@^7.20.1": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.1.tgz#3e045a92f7b4623cafc2425eddcb8cf2e54f9cc5" - integrity sha512-hp0AYxaZJhxULfM1zyp7Wgr+pSUKBcP3M+PHnSzWGdXOzg/kHWIgiUWARvubhUKGOEw3xqY4x+lyZ9ytBVcELw== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.7.tgz#66fe23b3c8569220817d5feb8b9dcdc95bb4f71b" + integrity sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -315,21 +313,21 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz#a11af19aa373d68d561f08e0a57242350ed0ec50" - integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz#d9c85589258539a22a901033853101a6198d4ef1" + integrity sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" - "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" + "@babel/plugin-proposal-optional-chaining" "^7.20.7" "@babel/plugin-proposal-async-generator-functions@^7.18.10", "@babel/plugin-proposal-async-generator-functions@^7.20.1": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz#352f02baa5d69f4e7529bdac39aaa02d41146af9" - integrity sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326" + integrity sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA== dependencies: "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-syntax-async-generators" "^7.8.4" @@ -342,12 +340,12 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-proposal-class-static-block@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz#8aa81d403ab72d3962fc06c26e222dacfc9b9020" - integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.20.7.tgz#92592e9029b13b15be0f7ce6a7aedc2879ca45a7" + integrity sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ== dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.20.7" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-class-static-block" "^7.14.5" "@babel/plugin-proposal-dynamic-import@^7.18.6": @@ -383,11 +381,11 @@ "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-proposal-logical-assignment-operators@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz#8148cbb350483bf6220af06fa6db3690e14b2e23" - integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz#dfbcaa8f7b4d37b51e8bfb46d94a5aea2bb89d83" + integrity sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": @@ -417,27 +415,16 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.18.8" -"@babel/plugin-proposal-object-rest-spread@^7.18.9": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz#a8fc86e8180ff57290c91a75d83fe658189b642d" - integrity sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q== - dependencies: - "@babel/compat-data" "^7.19.4" - "@babel/helper-compilation-targets" "^7.19.3" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.18.8" - -"@babel/plugin-proposal-object-rest-spread@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz#a556f59d555f06961df1e572bb5eca864c84022d" - integrity sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ== +"@babel/plugin-proposal-object-rest-spread@^7.18.9", "@babel/plugin-proposal-object-rest-spread@^7.20.2": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" + integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== dependencies: - "@babel/compat-data" "^7.20.1" - "@babel/helper-compilation-targets" "^7.20.0" + "@babel/compat-data" "^7.20.5" + "@babel/helper-compilation-targets" "^7.20.7" "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.20.1" + "@babel/plugin-transform-parameters" "^7.20.7" "@babel/plugin-proposal-optional-catch-binding@^7.18.6": version "7.18.6" @@ -447,13 +434,13 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993" - integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== +"@babel/plugin-proposal-optional-chaining@^7.18.9", "@babel/plugin-proposal-optional-chaining@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz#49f2b372519ab31728cc14115bb0998b15bfda55" + integrity sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-proposal-private-methods@^7.18.6": @@ -465,13 +452,13 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-proposal-private-property-in-object@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz#a64137b232f0aca3733a67eb1a144c192389c503" - integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw== + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz#309c7668f2263f1c711aa399b5a9a6291eef6135" + integrity sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.20.5" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": @@ -623,20 +610,20 @@ "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-arrow-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" - integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz#bea332b0e8b2dab3dafe55a163d8227531ab0551" + integrity sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-async-to-generator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz#ccda3d1ab9d5ced5265fdb13f1882d5476c71615" - integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354" + integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q== dependencies: "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-remap-async-to-generator" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-transform-block-scoped-functions@^7.18.6": version "7.18.6" @@ -645,68 +632,40 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-block-scoping@^7.18.9": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.0.tgz#91fe5e6ffc9ba13cb6c95ed7f0b1204f68c988c5" - integrity sha512-sXOohbpHZSk7GjxK9b3dKB7CfqUD5DwOH+DggKzOQ7TXYP+RCSbRykfjQmn/zq+rBjycVRtLf9pYhAaEJA786w== - dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - -"@babel/plugin-transform-block-scoping@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz#f59b1767e6385c663fd0bce655db6ca9c8b236ed" - integrity sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ== +"@babel/plugin-transform-block-scoping@^7.18.9", "@babel/plugin-transform-block-scoping@^7.20.2": + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.11.tgz#9f5a3424bd112a3f32fe0cf9364fbb155cff262a" + integrity sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw== dependencies: "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-classes@^7.18.9": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz#0e61ec257fba409c41372175e7c1e606dc79bb20" - integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== +"@babel/plugin-transform-classes@^7.18.9", "@babel/plugin-transform-classes@^7.20.2": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz#f438216f094f6bb31dc266ebfab8ff05aecad073" + integrity sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.19.0" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-replace-supers" "^7.18.9" - "@babel/helper-split-export-declaration" "^7.18.6" - globals "^11.1.0" - -"@babel/plugin-transform-classes@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz#c0033cf1916ccf78202d04be4281d161f6709bb2" - integrity sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-compilation-targets" "^7.20.7" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" "@babel/helper-optimise-call-expression" "^7.18.6" "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-replace-supers" "^7.19.1" + "@babel/helper-replace-supers" "^7.20.7" "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" "@babel/plugin-transform-computed-properties@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" - integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-destructuring@^7.18.9": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.0.tgz#712829ef4825d9cc04bb379de316f981e9a6f648" - integrity sha512-1dIhvZfkDVx/zn2S1aFwlruspTt4189j7fEkH0Y0VyuDM6bQt7bD6kLcz3l4IlLG+e5OReaBz9ROAbttRtUHqA== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz#704cc2fd155d1c996551db8276d55b9d46e4d0aa" + integrity sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/template" "^7.20.7" -"@babel/plugin-transform-destructuring@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz#c23741cfa44ddd35f5e53896e88c75331b8b2792" - integrity sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw== +"@babel/plugin-transform-destructuring@^7.18.9", "@babel/plugin-transform-destructuring@^7.20.2": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz#8bda578f71620c7de7c93af590154ba331415454" + integrity sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA== dependencies: "@babel/helper-plugin-utils" "^7.20.2" @@ -764,30 +723,30 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-modules-amd@^7.18.6", "@babel/plugin-transform-modules-amd@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz#aca391801ae55d19c4d8d2ebfeaa33df5f2a2cbd" - integrity sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg== + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz#3daccca8e4cc309f03c3a0c4b41dc4b26f55214a" + integrity sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g== dependencies: - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-modules-commonjs@^7.18.6", "@babel/plugin-transform-modules-commonjs@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz#25b32feef24df8038fc1ec56038917eacb0b730c" - integrity sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ== + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz#8cb23010869bf7669fd4b3098598b6b2be6dc607" + integrity sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw== dependencies: - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-simple-access" "^7.19.4" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-simple-access" "^7.20.2" "@babel/plugin-transform-modules-systemjs@^7.18.9", "@babel/plugin-transform-modules-systemjs@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz#59e2a84064b5736a4471b1aa7b13d4431d327e0d" - integrity sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ== + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz#467ec6bba6b6a50634eea61c9c232654d8a4696e" + integrity sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw== dependencies: "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-validator-identifier" "^7.19.1" "@babel/plugin-transform-modules-umd@^7.18.6": @@ -799,12 +758,12 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-named-capturing-groups-regex@^7.18.6", "@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz#ec7455bab6cd8fb05c525a94876f435a48128888" - integrity sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw== + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8" + integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-create-regexp-features-plugin" "^7.20.5" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-new-target@^7.18.6": version "7.18.6" @@ -821,12 +780,12 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-replace-supers" "^7.18.6" -"@babel/plugin-transform-parameters@^7.18.8", "@babel/plugin-transform-parameters@^7.20.1": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.1.tgz#9a5aa370fdcce36f110455e9369db7afca0f9eeb" - integrity sha512-nDvKLrAvl+kf6BOy1UJ3MGwzzfTMgppxwiD2Jb4LO3xjYyZq30oQzDNJbCQpMdG9+j2IXHoiMrw5Cm/L6ZoxXQ== +"@babel/plugin-transform-parameters@^7.18.8", "@babel/plugin-transform-parameters@^7.20.1", "@babel/plugin-transform-parameters@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz#0ee349e9d1bc96e78e3b37a7af423a4078a7083f" + integrity sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-property-literals@^7.18.6": version "7.18.6" @@ -836,12 +795,12 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-regenerator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73" - integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz#57cda588c7ffb7f4f8483cc83bdcea02a907f04d" + integrity sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - regenerator-transform "^0.15.0" + "@babel/helper-plugin-utils" "^7.20.2" + regenerator-transform "^0.15.1" "@babel/plugin-transform-reserved-words@^7.18.6": version "7.18.6" @@ -870,12 +829,12 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-spread@^7.18.9", "@babel/plugin-transform-spread@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" - integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz#c2d83e0b99d3bf83e07b11995ee24bf7ca09401e" + integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/plugin-transform-sticky-regex@^7.18.6": version "7.18.6" @@ -899,12 +858,12 @@ "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-typescript@^7.18.6": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.0.tgz#2c7ec62b8bfc21482f3748789ba294a46a375169" - integrity sha512-xOAsAFaun3t9hCwZ13Qe7gq423UgMZ6zAgmLxeGGapFqlT/X3L5qT2btjiVLlFn7gWtMaVyceS5VxGAuKbgizw== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.7.tgz#673f49499cd810ae32a1ea5f3f8fab370987e055" + integrity sha512-m3wVKEvf6SoszD8pu4NZz3PvfKRCMgk6D6d0Qi9hNnlM5M6CFS92EgF4EiHVLKbU0r/r7ty1hg7NPZwE7WRbYw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-create-class-features-plugin" "^7.20.7" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-typescript" "^7.20.0" "@babel/plugin-transform-unicode-escapes@^7.18.10": @@ -1105,20 +1064,20 @@ "@babel/plugin-transform-typescript" "^7.18.6" "@babel/runtime@^7.11.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.8.4": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9" - integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd" + integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ== dependencies: - regenerator-runtime "^0.13.10" + regenerator-runtime "^0.13.11" -"@babel/template@^7.18.10", "@babel/template@^7.3.3": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" - integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== +"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" + integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.10" - "@babel/types" "^7.18.10" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" "@babel/traverse@7.18.11": version "7.18.11" @@ -1136,19 +1095,19 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.18.10", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.19.6", "@babel/traverse@^7.20.1", "@babel/traverse@^7.7.2": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.1.tgz#9b15ccbf882f6d107eeeecf263fbcdd208777ec8" - integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA== +"@babel/traverse@^7.18.10", "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.7.2": + version "7.20.12" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.12.tgz#7f0f787b3a67ca4475adef1f56cb94f6abd4a4b5" + integrity sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.1" + "@babel/generator" "^7.20.7" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.20.1" - "@babel/types" "^7.20.0" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" debug "^4.1.0" globals "^11.1.0" @@ -1161,19 +1120,10 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.19.4", "@babel/types@^7.20.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.0.tgz#52c94cf8a7e24e89d2a194c25c35b17a64871479" - integrity sha512-Jlgt3H0TajCW164wkTOTzHkZb075tMQMULzrLUoUeKmO7eFL96GgDxf7/Axhc5CAuKE3KFyVW1p6ysKsi2oXAg== - dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - -"@babel/types@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.2.tgz#67ac09266606190f496322dbaff360fdaa5e7842" - integrity sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog== +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f" + integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== dependencies: "@babel/helper-string-parser" "^7.19.4" "@babel/helper-validator-identifier" "^7.19.1" @@ -1464,13 +1414,6 @@ "@types/node" "*" jest-mock "^29.3.1" -"@jest/expect-utils@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.2.2.tgz#460a5b5a3caf84d4feb2668677393dd66ff98665" - integrity sha512-vwnVmrVhTmGgQzyvcpze08br91OL61t9O0lJMDyb6Y/D8EKQ9V7rGUb/p7PDt0GPzK0zFYqXWFo4EO2legXmkg== - dependencies: - jest-get-type "^29.2.0" - "@jest/expect-utils@^29.3.1": version "29.3.1" resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.3.1.tgz#531f737039e9b9e27c42449798acb5bba01935b6" @@ -1635,18 +1578,6 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jest/types@^29.2.1": - version "29.2.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.2.1.tgz#ec9c683094d4eb754e41e2119d8bdaef01cf6da0" - integrity sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw== - dependencies: - "@jest/schemas" "^29.0.0" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - "@jest/types@^29.3.1": version "29.3.1" resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.3.1.tgz#7c5a80777cb13e703aeec6788d044150341147e3" @@ -1705,9 +1636,9 @@ integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== "@noble/hashes@^1", "@noble/hashes@^1.0.0": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.3.tgz#360afc77610e0a61f3417e497dcf36862e4f8111" - integrity sha512-CE0FCR57H2acVI5UOzIGSSIYxZ6v/HOhDR0Ro9VLyhnzLwx0o8W1mmgaqlEUx4049qJDlIBRztv5k+MM8vbO3A== + version "1.1.5" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.5.tgz#1a0377f3b9020efe2fae03290bd2a12140c95c11" + integrity sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -1818,9 +1749,9 @@ integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== "@sinonjs/commons@^1.7.0": - version "1.8.4" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.4.tgz#d1f2d80f1bd0f2520873f161588bd9b7f8567120" - integrity sha512-RpmQdHVo8hCEHDVpO39zToS9jOhR6nw+/lQAzRNq9ErrGV9IeHM71XCn68svVl/euFeVW6BWX4p35gkhbOcSIQ== + version "1.8.6" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9" + integrity sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ== dependencies: type-detect "4.0.8" @@ -1832,12 +1763,12 @@ "@sinonjs/commons" "^1.7.0" "@types/babel__core@^7.1.14": - version "7.1.19" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" - integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== + version "7.20.0" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891" + integrity sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ== dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" "@types/babel__generator" "*" "@types/babel__template" "*" "@types/babel__traverse" "*" @@ -1858,9 +1789,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.2.tgz#235bf339d17185bdec25e024ca19cce257cc7309" - integrity sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg== + version "7.18.3" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.3.tgz#dfc508a85781e5698d5b33443416b6268c4b3e8d" + integrity sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w== dependencies: "@babel/types" "^7.3.0" @@ -1873,9 +1804,9 @@ "@types/node" "*" "@types/graceful-fs@^4.1.3": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" - integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" + integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== dependencies: "@types/node" "*" @@ -1912,9 +1843,9 @@ integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/lodash@^4.14.182": - version "4.14.187" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.187.tgz#122ff0a7192115b4c1a19444ab4482caa77e2c9d" - integrity sha512-MrO/xLXCaUgZy3y96C/iOsaIqZSeupyTImKClHunL5GrmaiII2VwvWmLBu2hwa0Kp0sV19CsyjtrTc/Fx8rg/A== + version "4.14.191" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa" + integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ== "@types/long@^4.0.1": version "4.0.2" @@ -1927,14 +1858,14 @@ integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/node@*", "@types/node@>=13.7.0": - version "18.11.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" - integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== + version "18.11.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" + integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== "@types/prettier@^2.1.5", "@types/prettier@^2.6.1": - version "2.7.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e" - integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow== + version "2.7.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" + integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== "@types/semver@^7.3.12": version "7.3.13" @@ -1952,9 +1883,9 @@ integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== "@types/yargs@^17.0.8": - version "17.0.13" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76" - integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg== + version "17.0.20" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.20.tgz#107f0fcc13bd4a524e352b41c49fe88aab5c54d5" + integrity sha512-eknWrTHofQuPk2iuqDm1waA7V6xPlbgBoaaXEgYkClhLOnB0TtbW+srJaOToAgawPxPlHQzwypFA2bhZaUGP5A== dependencies: "@types/yargs-parser" "*" @@ -2128,9 +2059,9 @@ any-promise@^1.0.0: integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== anymatch@^3.0.3: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" @@ -2353,9 +2284,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001400: - version "1.0.30001429" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz#70cdae959096756a85713b36dd9cb82e62325639" - integrity sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg== + version "1.0.30001446" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001446.tgz#6d4ba828ab19f49f9bcd14a8430d30feebf1e0c5" + integrity sha512-fEoga4PrImGcwUUGEol/PoFCSBnSkA9drgdkxXkJLsUBOnJ8rs3zDv6ApqYXGQFOyMPsjh79naWhF4DAxbF8rw== case@1.6.3: version "1.6.3" @@ -2406,9 +2337,9 @@ chardet@^0.7.0: integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== ci-info@^3.2.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.5.0.tgz#bfac2a29263de4c829d806b1ab478e35091e171f" - integrity sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw== + version "3.7.1" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.7.1.tgz#708a6cdae38915d597afdf3b145f2f8e1ff55f3f" + integrity sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w== cjs-module-lexer@^1.0.0: version "1.2.2" @@ -2502,9 +2433,9 @@ convert-source-map@^2.0.0: integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.25.1: - version "3.26.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.26.0.tgz#94e2cf8ba3e63800c4956ea298a6473bc9d62b44" - integrity sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A== + version "3.27.2" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.27.2.tgz#607c50ad6db8fd8326af0b2883ebb987be3786da" + integrity sha512-welaYuF7ZtbYKGrIy7y3eb40d37rG1FvzEOfe7hSLd2iD6duMDqUhRfSvCGyC46HhR6Y8JXXdZ2lnRUMkPBpvg== dependencies: browserslist "^4.21.4" @@ -2581,11 +2512,6 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -diff-sequences@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.2.0.tgz#4c55b5b40706c7b5d2c5c75999a50c56d214e8f6" - integrity sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw== - diff-sequences@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.3.1.tgz#104b5b95fe725932421a9c6e5b4bef84c3f2249e" @@ -2785,9 +2711,9 @@ eslint@^8.32.0: text-table "^0.2.0" espree@^9.4.0: - version "9.4.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.0.tgz#cd4bc3d6e9336c433265fc0aa016fc1aaf182f8a" - integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw== + version "9.4.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.1.tgz#51d6092615567a2c2cff7833445e37c28c0065bd" + integrity sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg== dependencies: acorn "^8.8.0" acorn-jsx "^5.3.2" @@ -2855,18 +2781,7 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0: - version "29.2.2" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.2.2.tgz#ba2dd0d7e818727710324a6e7f13dd0e6d086106" - integrity sha512-hE09QerxZ5wXiOhqkXy5d2G9ar+EqOyifnCXCpMNu+vZ6DG9TJ6CO2c2kPDSLqERTTWrO7OZj8EkYHQqSd78Yw== - dependencies: - "@jest/expect-utils" "^29.2.2" - jest-get-type "^29.2.0" - jest-matcher-utils "^29.2.2" - jest-message-util "^29.2.1" - jest-util "^29.2.1" - -expect@^29.3.1: +expect@^29.0.0, expect@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/expect/-/expect-29.3.1.tgz#92877aad3f7deefc2e3f6430dd195b92295554a6" integrity sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA== @@ -2929,9 +2844,9 @@ fast-levenshtein@^2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== dependencies: reusify "^1.0.4" @@ -3028,9 +2943,9 @@ get-caller-file@^2.0.5: integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-intrinsic@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" - integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + version "1.2.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" + integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== dependencies: function-bind "^1.1.1" has "^1.0.3" @@ -3207,9 +3122,9 @@ iconv-lite@^0.4.17, iconv-lite@^0.4.24: safer-buffer ">= 2.1.2 < 3" ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" @@ -3506,16 +3421,6 @@ jest-config@^29.3.1: slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.2.1.tgz#027e42f5a18b693fb2e88f81b0ccab533c08faee" - integrity sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.2.0" - jest-get-type "^29.2.0" - pretty-format "^29.2.1" - jest-diff@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.3.1.tgz#d8215b72fed8f1e647aed2cae6c752a89e757527" @@ -3607,16 +3512,6 @@ jest-leak-detector@^29.3.1: jest-get-type "^29.2.0" pretty-format "^29.3.1" -jest-matcher-utils@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz#9202f8e8d3a54733266784ce7763e9a08688269c" - integrity sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw== - dependencies: - chalk "^4.0.0" - jest-diff "^29.2.1" - jest-get-type "^29.2.0" - pretty-format "^29.2.1" - jest-matcher-utils@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz#6e7f53512f80e817dfa148672bd2d5d04914a572" @@ -3627,21 +3522,6 @@ jest-matcher-utils@^29.3.1: jest-get-type "^29.2.0" pretty-format "^29.3.1" -jest-message-util@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.2.1.tgz#3a51357fbbe0cc34236f17a90d772746cf8d9193" - integrity sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.2.1" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.2.1" - slash "^3.0.0" - stack-utils "^2.0.3" - jest-message-util@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.3.1.tgz#37bc5c468dfe5120712053dd03faf0f053bd6adb" @@ -3667,9 +3547,9 @@ jest-mock@^29.3.1: jest-util "^29.3.1" jest-pnp-resolver@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== jest-regex-util@^28.0.2: version "28.0.2" @@ -3801,18 +3681,6 @@ jest-util@^28.1.3: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.2.1.tgz#f26872ba0dc8cbefaba32c34f98935f6cf5fc747" - integrity sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g== - dependencies: - "@jest/types" "^29.2.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - jest-util@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.3.1.tgz#1dda51e378bbcb7e3bc9d8ab651445591ed373e1" @@ -3881,9 +3749,9 @@ jest@^29.3.1: jest-cli "^29.3.1" js-sdsl@^4.1.4: - version "4.1.5" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.5.tgz#1ff1645e6b4d1b028cd3f862db88c9d887f26e2a" - integrity sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q== + version "4.3.0" + resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711" + integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ== js-tokens@^4.0.0: version "4.0.0" @@ -3930,10 +3798,10 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== +json5@^2.2.1, json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== kleur@^3.0.3: version "3.0.3" @@ -4009,6 +3877,13 @@ long@^5.2.0, long@^5.2.1: resolved "https://registry.yarnpkg.com/long/-/long-5.2.1.tgz#e27595d0083d103d2fa2c20c7699f8e0c92b897f" integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A== +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -4097,9 +3972,9 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: brace-expansion "^1.1.7" minimatch@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" - integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" @@ -4158,9 +4033,9 @@ node-int64@^0.4.0: integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-releases@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" - integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== + version "2.0.8" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.8.tgz#0f349cdc8fcfa39a92ac0be9bc48b7706292b9ae" + integrity sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A== normalize-path@^3.0.0: version "3.0.0" @@ -4256,9 +4131,9 @@ p-try@^2.0.0: integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== pako@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.4.tgz#6cebc4bbb0b6c73b0d5b8d7e8476e2b2fbea576d" - integrity sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg== + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== parent-module@^1.0.0: version "1.0.1" @@ -4341,26 +4216,12 @@ prepend-file@^2.0.1: dependencies: temp-write "^4.0.0" -prettier@^2.6.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" - integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== - -prettier@^2.8.3: +prettier@^2.6.2, prettier@^2.8.3: version "2.8.3" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.3.tgz#ab697b1d3dd46fb4626fbe2f543afe0cc98d8632" integrity sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw== -pretty-format@^29.0.0, pretty-format@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.2.1.tgz#86e7748fe8bbc96a6a4e04fa99172630907a9611" - integrity sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA== - dependencies: - "@jest/schemas" "^29.0.0" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -pretty-format@^29.3.1: +pretty-format@^29.0.0, pretty-format@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.3.1.tgz#1841cac822b02b4da8971dacb03e8a871b4722da" integrity sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg== @@ -4397,9 +4258,9 @@ protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: long "^4.0.0" punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== queue-microtask@^1.2.2: version "1.2.3" @@ -4435,15 +4296,15 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.13.10: - version "0.13.10" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee" - integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw== +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== -regenerator-transform@^0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" - integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== +regenerator-transform@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" + integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== dependencies: "@babel/runtime" "^7.8.4" @@ -4452,17 +4313,17 @@ regexpp@^3.2.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -regexpu-core@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.1.tgz#a69c26f324c1e962e9ffd0b88b055caba8089139" - integrity sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ== +regexpu-core@^5.2.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.2.tgz#3e4e5d12103b64748711c3aad69934d7718e75fc" + integrity sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw== dependencies: regenerate "^1.4.2" regenerate-unicode-properties "^10.1.0" regjsgen "^0.7.1" regjsparser "^0.9.1" unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" regjsgen@^0.7.1: version "0.7.1" @@ -4499,9 +4360,9 @@ resolve-from@^5.0.0: integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve.exports@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" - integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + version "1.1.1" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.1.tgz#05cfd5b3edf641571fd46fa608b610dda9ead999" + integrity sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ== resolve@^1.1.6, resolve@^1.14.2, resolve@^1.20.0: version "1.22.1" @@ -4635,9 +4496,9 @@ sprintf-js@~1.0.2: integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== stack-utils@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" - integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: escape-string-regexp "^2.0.0" @@ -4888,10 +4749,10 @@ unicode-match-property-ecmascript@^2.0.0: unicode-canonical-property-names-ecmascript "^2.0.0" unicode-property-aliases-ecmascript "^2.0.0" -unicode-match-property-value-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" - integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== unicode-property-aliases-ecmascript@^2.0.0: version "2.1.0" @@ -5005,20 +4866,25 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@^21.0.0: +yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@^17.3.1: - version "17.6.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.1.tgz#712508771045019cda059bc1ba3ae091aaa1402e" - integrity sha512-leBuCGrL4dAd6ispNOGsJlhd0uZ6Qehkbu/B9KCR+Pxa/NVdNwi+i31lo0buCm6XxhJQFshXCD0/evfV4xfoUg== + version "17.6.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541" + integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw== dependencies: cliui "^8.0.1" escalade "^3.1.1" @@ -5026,7 +4892,7 @@ yargs@^17.3.1: require-directory "^2.1.1" string-width "^4.2.3" y18n "^5.0.5" - yargs-parser "^21.0.0" + yargs-parser "^21.1.1" yocto-queue@^0.1.0: version "0.1.0" From 772beb8d0eb6cf3e39e97f48a4c65859af20563c Mon Sep 17 00:00:00 2001 From: piobab Date: Mon, 30 Jan 2023 18:08:21 +0100 Subject: [PATCH 135/218] Mp 2107 cleanup (#109) * Update env for artifacts. * Use red-bank deps. * Bump osmosis deps. * cw-dex bump to latest develop commit. * Remove namespaces from action attributes/events. * Update schema. --- Cargo.lock | 98 +++++-- Cargo.toml | 40 +-- Makefile.toml | 4 + contracts/account-nft/src/execute.rs | 2 +- contracts/credit-manager/Cargo.toml | 2 +- contracts/credit-manager/src/borrow.rs | 2 +- contracts/credit-manager/src/deposit.rs | 2 +- contracts/credit-manager/src/execute.rs | 4 +- contracts/credit-manager/src/health.rs | 6 +- contracts/credit-manager/src/lend.rs | 2 +- .../credit-manager/src/liquidate_coin.rs | 2 +- contracts/credit-manager/src/refund.rs | 2 +- contracts/credit-manager/src/repay.rs | 2 +- contracts/credit-manager/src/swap.rs | 2 +- .../src/update_coin_balances.rs | 4 +- contracts/credit-manager/src/update_config.rs | 7 +- contracts/credit-manager/src/vault/enter.rs | 4 +- contracts/credit-manager/src/vault/exit.rs | 2 +- .../credit-manager/src/vault/exit_unlocked.rs | 2 +- .../src/vault/liquidate_vault.rs | 6 +- .../src/vault/request_unlock.rs | 4 +- contracts/credit-manager/src/withdraw.rs | 2 +- contracts/credit-manager/src/zap.rs | 4 +- .../credit-manager/tests/helpers/mock_env.rs | 6 +- contracts/mock-oracle/Cargo.toml | 8 +- contracts/mock-oracle/src/contract.rs | 2 +- contracts/mock-oracle/src/msg.rs | 2 +- contracts/mock-red-bank/Cargo.toml | 10 +- contracts/mock-red-bank/examples/schema.rs | 2 +- contracts/mock-red-bank/src/contract.rs | 2 +- contracts/mock-red-bank/src/query.rs | 2 +- contracts/swapper/base/src/contract.rs | 6 +- contracts/swapper/mock/src/contract.rs | 4 +- contracts/swapper/osmosis/Cargo.toml | 4 +- contracts/swapper/osmosis/tests/helpers.rs | 2 +- .../osmosis/tests/test_enumerate_routes.rs | 2 +- .../swapper/osmosis/tests/test_estimate.rs | 2 +- .../swapper/osmosis/tests/test_instantiate.rs | 2 +- .../swapper/osmosis/tests/test_set_route.rs | 2 +- contracts/swapper/osmosis/tests/test_swap.rs | 2 +- .../osmosis/tests/test_update_admin.rs | 2 +- contracts/zapper/base/Cargo.toml | 4 +- contracts/zapper/base/src/contract.rs | 6 +- contracts/zapper/osmosis/Cargo.toml | 8 +- contracts/zapper/osmosis/tests/helpers.rs | 2 +- .../zapper/osmosis/tests/test_callback.rs | 2 +- .../osmosis/tests/test_provide_liquidity.rs | 2 +- .../zapper/osmosis/tests/test_queries.rs | 2 +- .../osmosis/tests/test_withdraw_liquidity.rs | 2 +- coverage_grcov.Makefile.toml | 2 +- packages/chains/osmosis/Cargo.toml | 22 -- packages/chains/osmosis/src/helpers.rs | 187 ------------- packages/chains/osmosis/src/lib.rs | 1 - packages/outpost/Cargo.toml | 24 -- packages/outpost/src/address_provider.rs | 173 ------------ packages/outpost/src/error.rs | 81 ------ packages/outpost/src/helpers.rs | 94 ------- packages/outpost/src/incentives.rs | 123 -------- packages/outpost/src/lib.rs | 8 - packages/outpost/src/math.rs | 263 ------------------ packages/outpost/src/oracle.rs | 116 -------- .../src/red_bank/interest_rate_model.rs | 224 --------------- packages/outpost/src/red_bank/market.rs | 128 --------- packages/outpost/src/red_bank/mod.rs | 9 - packages/outpost/src/red_bank/msg.rs | 251 ----------------- packages/outpost/src/red_bank/types.rs | 128 --------- packages/outpost/src/rewards_collector.rs | 218 --------------- packages/rover/Cargo.toml | 2 +- packages/rover/src/adapters/oracle.rs | 2 +- packages/rover/src/adapters/red_bank.rs | 2 +- .../mars-mock-red-bank.json | 4 +- 71 files changed, 172 insertions(+), 2185 deletions(-) delete mode 100755 packages/chains/osmosis/Cargo.toml delete mode 100644 packages/chains/osmosis/src/helpers.rs delete mode 100644 packages/chains/osmosis/src/lib.rs delete mode 100644 packages/outpost/Cargo.toml delete mode 100644 packages/outpost/src/address_provider.rs delete mode 100644 packages/outpost/src/error.rs delete mode 100644 packages/outpost/src/helpers.rs delete mode 100644 packages/outpost/src/incentives.rs delete mode 100644 packages/outpost/src/lib.rs delete mode 100644 packages/outpost/src/math.rs delete mode 100644 packages/outpost/src/oracle.rs delete mode 100644 packages/outpost/src/red_bank/interest_rate_model.rs delete mode 100644 packages/outpost/src/red_bank/market.rs delete mode 100644 packages/outpost/src/red_bank/mod.rs delete mode 100644 packages/outpost/src/red_bank/msg.rs delete mode 100644 packages/outpost/src/red_bank/types.rs delete mode 100644 packages/outpost/src/rewards_collector.rs diff --git a/Cargo.lock b/Cargo.lock index eb663c5bf..e8a218066 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,7 +31,7 @@ checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "apollo-utils" version = "0.1.0" -source = "git+https://github.com/apollodao/apollo-utils.git?rev=bfd1abd8cd9716dccad3e74aeb3704cad9f1f41a#bfd1abd8cd9716dccad3e74aeb3704cad9f1f41a" +source = "git+https://github.com/apollodao/apollo-utils.git?rev=e12b38158778f6279525c4be96789a5f488d2659#e12b38158778f6279525c4be96789a5f488d2659" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -418,13 +418,13 @@ dependencies = [ [[package]] name = "cw-dex" version = "0.0.1" -source = "git+https://github.com/apollodao/cw-dex?rev=b9d18cfbba8aa09ae4ee9459d308cda87ae5ffb3#b9d18cfbba8aa09ae4ee9459d308cda87ae5ffb3" +source = "git+https://github.com/apollodao/cw-dex?rev=1ce5566963851f7cab0eed117240654f3cb2d78d#1ce5566963851f7cab0eed117240654f3cb2d78d" dependencies = [ "apollo-utils", "cosmwasm-schema", "cosmwasm-std", "cw-asset", - "cw-storage-plus 0.16.0", + "cw-storage-plus 1.0.1", "cw-utils 0.11.1", "cw-utils 0.16.0", "cw20", @@ -1043,8 +1043,8 @@ dependencies = [ "mars-mock-oracle", "mars-mock-red-bank", "mars-mock-vault", - "mars-outpost", "mars-owner", + "mars-red-bank-types", "mars-rover", "mars-swapper-mock", "mars-zapper-mock", @@ -1086,7 +1086,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.0.1", - "mars-outpost", + "mars-red-bank-types", ] [[package]] @@ -1097,7 +1097,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.0.1", "cw-utils 0.16.0", - "mars-outpost", + "mars-red-bank-types", ] [[package]] @@ -1116,32 +1116,35 @@ dependencies = [ [[package]] name = "mars-osmosis" version = "1.0.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=3e57365d82b96967ef1bc7ec183c4a86bc88d5e7#3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" dependencies = [ "cosmwasm-std", - "osmosis-std 0.13.2", + "osmosis-std 0.14.0", "serde", ] [[package]] -name = "mars-outpost" +name = "mars-owner" version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5644a8b047a0d64d04706414805872f35439755e057431152dd36e6f369be24" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "mars-owner", + "cw-storage-plus 1.0.1", + "schemars", "thiserror", ] [[package]] -name = "mars-owner" +name = "mars-red-bank-types" version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5644a8b047a0d64d04706414805872f35439755e057431152dd36e6f369be24" +source = "git+https://github.com/mars-protocol/red-bank?rev=3e57365d82b96967ef1bc7ec183c4a86bc88d5e7#3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", - "schemars", + "mars-owner", + "mars-utils", "thiserror", ] @@ -1158,8 +1161,8 @@ dependencies = [ "cw721-base", "mars-health", "mars-math", - "mars-outpost", "mars-owner", + "mars-red-bank-types", "schemars", "serde", "thiserror", @@ -1204,12 +1207,21 @@ dependencies = [ "mars-owner", "mars-rover", "mars-swapper-base", - "osmosis-std 0.13.2", - "osmosis-testing", + "osmosis-std 0.14.0", + "osmosis-test-tube", "schemars", "thiserror", ] +[[package]] +name = "mars-utils" +version = "1.0.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=3e57365d82b96967ef1bc7ec183c4a86bc88d5e7#3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" +dependencies = [ + "cosmwasm-std", + "thiserror", +] + [[package]] name = "mars-zapper-base" version = "1.0.0" @@ -1244,7 +1256,7 @@ dependencies = [ "cw-utils 0.16.0", "cw2 1.0.1", "mars-zapper-base", - "osmosis-testing", + "osmosis-test-tube", ] [[package]] @@ -1329,7 +1341,7 @@ checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "osmosis-std" version = "0.12.0" -source = "git+https://github.com/apollodao/osmosis-rust?rev=52ca8eaa4b2926fea01a68f4d7e7253ad29d88b3#52ca8eaa4b2926fea01a68f4d7e7253ad29d88b3" +source = "git+https://github.com/apollodao/osmosis-rust.git?branch=osmosis-v13#38a48533ed88243070b105cb9085e34c2b0c4eeb" dependencies = [ "chrono", "cosmwasm-std", @@ -1344,7 +1356,24 @@ dependencies = [ [[package]] name = "osmosis-std" version = "0.13.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?rev=5f2493aa13569167431add6282916683b4aaf3c8#5f2493aa13569167431add6282916683b4aaf3c8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d6fe6ac7fcba45ed61d738091d33c838c4cabbcf4892dc7aa56d19d39cc976" +dependencies = [ + "chrono", + "cosmwasm-std", + "osmosis-std-derive 0.13.2", + "prost 0.11.6", + "prost-types", + "schemars", + "serde", + "serde-cw-value", +] + +[[package]] +name = "osmosis-std" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc0a9075efd64ed5a8be3bf134cbf1080570d68384f2ad58ffaac6c00d063fd" dependencies = [ "chrono", "cosmwasm-std", @@ -1359,7 +1388,7 @@ dependencies = [ [[package]] name = "osmosis-std-derive" version = "0.12.0" -source = "git+https://github.com/apollodao/osmosis-rust?rev=52ca8eaa4b2926fea01a68f4d7e7253ad29d88b3#52ca8eaa4b2926fea01a68f4d7e7253ad29d88b3" +source = "git+https://github.com/apollodao/osmosis-rust.git?branch=osmosis-v13#38a48533ed88243070b105cb9085e34c2b0c4eeb" dependencies = [ "itertools", "proc-macro2", @@ -1370,7 +1399,8 @@ dependencies = [ [[package]] name = "osmosis-std-derive" version = "0.13.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?rev=5f2493aa13569167431add6282916683b4aaf3c8#5f2493aa13569167431add6282916683b4aaf3c8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a455e262a6fdfd3914f3a4e11e6bc0ce491901cb9d507d7856d7ef6e129e90c6" dependencies = [ "itertools", "proc-macro2", @@ -1379,9 +1409,10 @@ dependencies = [ ] [[package]] -name = "osmosis-testing" -version = "0.13.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?rev=5f2493aa13569167431add6282916683b4aaf3c8#5f2493aa13569167431add6282916683b4aaf3c8" +name = "osmosis-test-tube" +version = "14.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e7d470a607f4016906fee20ff51275b399ffad9903240dc462c0f5226650c7" dependencies = [ "base64", "bindgen", @@ -1391,6 +1422,7 @@ dependencies = [ "prost 0.11.6", "serde", "serde_json", + "test-tube", "thiserror", ] @@ -1878,6 +1910,22 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-tube" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2791215b77f72c219df4d3e95d78539ee66950a0449961ba6d0ab1c7b538673f" +dependencies = [ + "base64", + "cosmrs", + "cosmwasm-std", + "osmosis-std 0.13.2", + "prost 0.11.6", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "textwrap" version = "0.16.0" diff --git a/Cargo.toml b/Cargo.toml index 8587f9a8b..c1a43e666 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,9 +4,7 @@ members = [ "contracts/credit-manager", "contracts/swapper/*", "contracts/zapper/*", - "packages/chains/*", "packages/health", - "packages/outpost", "packages/rover", "packages/math", @@ -32,29 +30,31 @@ documentation = "https://docs.marsprotocol.io/" keywords = ["mars", "cosmos", "cosmwasm"] [workspace.dependencies] -anyhow = "1.0.66" -cosmwasm-schema = "1.1.9" -cosmwasm-std = "1.1.9" -cw2 = "1.0.0" -cw721 = "0.16.0" -cw721-base = { version = "0.16.0", features = ["library"] } -cw-item-set = { version = "0.7.0", default-features = false, features = ["iterator"] } -cw-multi-test = "0.16.1" -cw-utils = "0.16.0" -cw-storage-plus = "1.0.1" -itertools = "0.10.5" -osmosis-std = { git = "https://github.com/osmosis-labs/osmosis-rust", rev = "5f2493aa13569167431add6282916683b4aaf3c8" } -osmosis-testing = { git = "https://github.com/osmosis-labs/osmosis-rust", rev = "5f2493aa13569167431add6282916683b4aaf3c8" } -schemars = "0.8.11" -serde = { version = "1.0.150", default-features = false, features = ["derive"] } -thiserror = "1.0.37" +anyhow = "1.0.66" +cosmwasm-schema = "1.1.9" +cosmwasm-std = "1.1.9" +cw2 = "1.0.0" +cw721 = "0.16.0" +cw721-base = { version = "0.16.0", features = ["library"] } +cw-item-set = { version = "0.7.0", default-features = false, features = ["iterator"] } +cw-multi-test = "0.16.1" +cw-utils = "0.16.0" +cw-storage-plus = "1.0.1" +itertools = "0.10.5" +osmosis-std = "0.14.0" +osmosis-test-tube = "14.1.1" +schemars = "0.8.11" +serde = { version = "1.0.150", default-features = false, features = ["derive"] } +thiserror = "1.0.37" # packages cosmwasm-vault-standard = { version = "0.1.0", features = ["lockup", "force-unlock"] } +# FIXME: develop branch dependency till Apollo finish audit +cw-dex = { git = "https://github.com/apollodao/cw-dex", rev = "1ce5566963851f7cab0eed117240654f3cb2d78d", features = ["osmosis"] } mars-health = { version = "1.0.0", path = "./packages/health" } mars-math = { version = "1.0.0", path = "./packages/math" } -mars-osmosis = { version = "1.0.0", path = "./packages/chains/osmosis" } -mars-outpost = { version = "1.0.0", path = "./packages/outpost" } +mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" } +mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank", rev = "3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" } mars-owner = "1.0.0" mars-rover = { version = "1.0.0", path = "./packages/rover" } diff --git a/Makefile.toml b/Makefile.toml index 1d53d3f4f..2fc4c9e8b 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -6,6 +6,10 @@ extend = [ [config] default_to_workspace = false +[env] +# Directory with wasm files used by integration tests (another directory can be used instead, for example 'artifacts' from rust-optimizer) +ARTIFACTS_DIR_PATH = "target/wasm32-unknown-unknown/release" + [tasks.build] command = "cargo" args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked"] diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index c1cdb27f4..f1c358fc0 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -75,7 +75,7 @@ pub fn update_config( return Err(BaseError(cw721_base::ContractError::Unauthorized {})); } - let mut response = Response::new().add_attribute("action", "rover/account_nft/update_config"); + let mut response = Response::new().add_attribute("action", "update_config"); let mut config = CONFIG.load(deps.storage)?; if let Some(max) = updates.max_value_for_burn { diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 5b763d170..75bb2bb27 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -29,7 +29,7 @@ cw-item-set = { workspace = true } cw-storage-plus = { workspace = true } mars-math = { workspace = true } mars-health = { workspace = true } -mars-outpost = { workspace = true } +mars-red-bank-types = { workspace = true } mars-owner = { workspace = true } mars-rover = { workspace = true } diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index 486d43c9a..05a73bdf2 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -50,7 +50,7 @@ pub fn borrow(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> Contract Ok(Response::new() .add_message(red_bank.borrow_msg(&coin)?) - .add_attribute("action", "rover/credit-manager/borrow") + .add_attribute("action", "borrow") .add_attribute("account_id", account_id) .add_attribute("debt_shares_added", debt_shares_to_add) .add_attribute("coin_borrowed", coin.to_string())) diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index 47abb0db5..3ac2ae7a9 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -26,7 +26,7 @@ pub fn deposit( increment_coin_balance(storage, account_id, coin)?; Ok(response - .add_attribute("action", "rover/credit-manager/callback/deposit") + .add_attribute("action", "callback/deposit") .add_attribute("coin_deposited", coin.to_string())) } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 3772842b3..c3966796b 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -39,9 +39,7 @@ pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult ContractRe Ok(Response::new() .add_message(red_bank.lend_msg(&coin)?) - .add_attribute("action", "rover/credit-manager/lend") + .add_attribute("action", "lend") .add_attribute("account_id", account_id) .add_attribute("lent_shares_added", lent_shares_to_add) .add_attribute("coins_lent", coin.amount)) diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index c0ad93ffd..e0171d267 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -48,7 +48,7 @@ pub fn liquidate_coin( Ok(Response::new() .add_message(repay_msg) - .add_attribute("action", "rover/credit-manager/liquidate_coin") + .add_attribute("action", "liquidate_coin") .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("coin_debt_repaid", debt.to_string()) diff --git a/contracts/credit-manager/src/refund.rs b/contracts/credit-manager/src/refund.rs index 241d21405..4afec272f 100644 --- a/contracts/credit-manager/src/refund.rs +++ b/contracts/credit-manager/src/refund.rs @@ -25,6 +25,6 @@ pub fn refund_coin_balances(deps: DepsMut, env: Env, account_id: &str) -> Contra .collect::>>()?; Ok(Response::new() .add_messages(withdraw_msgs) - .add_attribute("action", "rover/credit-manager/callback/refund_coin_balances") + .add_attribute("action", "callback/refund_coin_balances") .add_attribute("account_id", account_id.to_string())) } diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index 540c6ea20..ca4d0e86f 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -53,7 +53,7 @@ pub fn repay( Ok(Response::new() .add_message(red_bank_repay_msg) - .add_attribute("action", "rover/credit-manager/repay") + .add_attribute("action", "repay") .add_attribute("account_id", account_id) .add_attribute("debt_shares_repaid", shares_to_repay) .add_attribute("coin_repaid", coin_to_repay.to_string())) diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs index ab71544b6..f403e3b7e 100644 --- a/contracts/credit-manager/src/swap.rs +++ b/contracts/credit-manager/src/swap.rs @@ -44,7 +44,7 @@ pub fn swap_exact_in( Ok(Response::new() .add_message(swapper.swap_exact_in_msg(&coin_in_to_trade, denom_out, slippage)?) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit-manager/swapper") + .add_attribute("action", "swapper") .add_attribute("account_id", account_id) .add_attribute("coin_in", coin_in_to_trade.to_string()) .add_attribute("denom_out", denom_out)) diff --git a/contracts/credit-manager/src/update_coin_balances.rs b/contracts/credit-manager/src/update_coin_balances.rs index 8fc72a67f..3d0934f95 100644 --- a/contracts/credit-manager/src/update_coin_balances.rs +++ b/contracts/credit-manager/src/update_coin_balances.rs @@ -32,7 +32,7 @@ pub fn update_coin_balance( }; decrement_coin_balance(deps.storage, account_id, &coin_to_reduce)?; Ok(Response::new() - .add_attribute("action", "rover/credit-manager/update_coin_balance") + .add_attribute("action", "update_coin_balance") .add_attribute("account_id", account_id) .add_attribute("coin_decremented", coin_to_reduce.to_string())) } else { @@ -43,7 +43,7 @@ pub fn update_coin_balance( }; increment_coin_balance(deps.storage, account_id, &coin_to_increment)?; Ok(Response::new() - .add_attribute("action", "rover/credit-manager/update_coin_balance") + .add_attribute("action", "update_coin_balance") .add_attribute("account_id", account_id) .add_attribute("coin_incremented", coin_to_increment.to_string())) } diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index 98c550682..b802110ce 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -22,8 +22,7 @@ pub fn update_config( ) -> ContractResult { OWNER.assert_owner(deps.storage, &info.sender)?; - let mut response = - Response::new().add_attribute("action", "rover/credit-manager/update_config"); + let mut response = Response::new().add_attribute("action", "update_config"); if let Some(addr_str) = updates.account_nft { let validated = deps.api.addr_validate(&addr_str)?; @@ -126,7 +125,5 @@ pub fn update_nft_config( })?, }); - Ok(Response::new() - .add_attribute("action", "rover/credit-manager/update_nft_config") - .add_message(update_config_msg)) + Ok(Response::new().add_attribute("action", "update_nft_config").add_message(update_config_msg)) } diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index 92ab40a9f..6dfb08db5 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -58,7 +58,7 @@ pub fn enter_vault( Ok(Response::new() .add_message(vault.deposit_msg(&coin_to_enter)?) .add_message(update_vault_balance_msg) - .add_attribute("action", "rover/credit-manager/vault/enter") + .add_attribute("action", "vault/enter") .add_attribute("account_id", account_id) .add_attribute("vault_addr", vault.address.to_string()) .add_attribute("amount_deposited", amount.to_string())) @@ -91,7 +91,7 @@ pub fn update_vault_coin_balance( )?; Ok(Response::new() - .add_attribute("action", "rover/credit-manager/vault/update_balance") + .add_attribute("action", "vault/update_balance") .add_attribute("account_id", account_id.to_string()) .add_attribute("vault_addr", vault.address.to_string()) .add_attribute("amount_incremented", current_balance.checked_sub(previous_total_balance)?)) diff --git a/contracts/credit-manager/src/vault/exit.rs b/contracts/credit-manager/src/vault/exit.rs index 36ca7ed5b..5e7a9df3a 100644 --- a/contracts/credit-manager/src/vault/exit.rs +++ b/contracts/credit-manager/src/vault/exit.rs @@ -45,7 +45,7 @@ pub fn exit_vault( Ok(Response::new() .add_message(withdraw_msg) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit-manager/vault/exit") + .add_attribute("action", "vault/exit") .add_attribute("account_id", account_id) .add_attribute("vault_addr", vault.address.to_string()) .add_attribute("amount_withdrawn", amount.to_string())) diff --git a/contracts/credit-manager/src/vault/exit_unlocked.rs b/contracts/credit-manager/src/vault/exit_unlocked.rs index ea4bd22af..b5e05c7e8 100644 --- a/contracts/credit-manager/src/vault/exit_unlocked.rs +++ b/contracts/credit-manager/src/vault/exit_unlocked.rs @@ -61,7 +61,7 @@ pub fn exit_vault_unlocked( Ok(Response::new() .add_message(withdraw_unlocked_msg) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit-manager/vault/exit_unlocked") + .add_attribute("action", "vault/exit_unlocked") .add_attribute("account_id", account_id) .add_attribute("vault_addr", vault.address.to_string()) .add_attribute("position_id", position_id.to_string())) diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index 7bdef2869..a33326e9e 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -110,7 +110,7 @@ fn liquidate_unlocked( .add_message(repay_msg) .add_message(vault_withdraw_msg) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit-manager/liquidate_vault/unlocked") + .add_attribute("action", "liquidate_vault/unlocked") .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("coin_debt_repaid", debt.to_string()) @@ -202,7 +202,7 @@ fn liquidate_unlocking( .add_message(repay_msg) .add_messages(vault_withdraw_msgs) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit-manager/liquidate_vault/unlocking") + .add_attribute("action", "liquidate_vault/unlocking") .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("coin_debt_repaid", debt.to_string()) @@ -254,7 +254,7 @@ fn liquidate_locked( .add_message(repay_msg) .add_message(vault_withdraw_msg) .add_message(update_coin_balance_msg) - .add_attribute("action", "rover/credit-manager/liquidate_vault/locked") + .add_attribute("action", "liquidate_vault/locked") .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("coin_debt_repaid", debt.to_string()) diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index 75ffad728..d29e3404f 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -61,7 +61,7 @@ pub fn request_vault_unlock( Ok(Response::new() .add_submessage(request_unlock_msg) - .add_attribute("action", "rover/credit-manager/vault/request_unlock") + .add_attribute("action", "vault/request_unlock") .add_attribute("account_id", account_id) .add_attribute("vault_addr", vault.address) .add_attribute("unlock_amount", amount)) @@ -90,7 +90,7 @@ pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResul VAULT_REQUEST_TEMP_STORAGE.remove(deps.storage); Ok(Response::new() - .add_attribute("action", "rover/credit-manager/vault/unlock_request/handle_reply") + .add_attribute("action", "vault/unlock_request/handle_reply") .add_attribute("account_id", &storage.account_id) .add_attribute("vault_addr", storage.vault_addr.to_string()) .add_attribute("position_id", unlocking_position.id.to_string())) diff --git a/contracts/credit-manager/src/withdraw.rs b/contracts/credit-manager/src/withdraw.rs index 291798d98..035ba0f72 100644 --- a/contracts/credit-manager/src/withdraw.rs +++ b/contracts/credit-manager/src/withdraw.rs @@ -23,7 +23,7 @@ pub fn withdraw( Ok(Response::new() .add_message(transfer_msg) - .add_attribute("action", "rover/credit-manager/callback/withdraw") + .add_attribute("action", "callback/withdraw") .add_attribute("account_id", account_id) .add_attribute("coin_withdrawn", coin.to_string())) } diff --git a/contracts/credit-manager/src/zap.rs b/contracts/credit-manager/src/zap.rs index 08b38b47e..616a74071 100644 --- a/contracts/credit-manager/src/zap.rs +++ b/contracts/credit-manager/src/zap.rs @@ -49,7 +49,7 @@ pub fn provide_liquidity( Ok(Response::new() .add_message(zap_msg) .add_message(update_balance_msg) - .add_attribute("action", "rover/credit-manager/provide_liquidity") + .add_attribute("action", "provide_liquidity") .add_attribute("account_id", account_id) .add_attribute("coins_in", updated_coins_in.as_slice().to_string()) .add_attribute("lp_token_out", lp_token_out)) @@ -95,7 +95,7 @@ pub fn withdraw_liquidity( Ok(Response::new() .add_message(zap_msg) .add_messages(update_balances_msgs) - .add_attribute("action", "rover/credit-manager/withdraw_liquidity") + .add_attribute("action", "withdraw_liquidity") .add_attribute("account_id", account_id) .add_attribute("coin_in", lp_token.to_string()) .add_attribute("coins_out", coins_out.as_slice().to_string())) diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 386e4bd5b..eb1fceb17 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -15,11 +15,11 @@ use mars_mock_red_bank::msg::{CoinMarketInfo, InstantiateMsg as RedBankInstantia use mars_mock_vault::{ contract::DEFAULT_VAULT_TOKEN_PREFUND, msg::InstantiateMsg as VaultInstantiateMsg, }; -use mars_outpost::red_bank::{ +use mars_owner::OwnerUpdate; +use mars_red_bank_types::red_bank::{ QueryMsg::{UserCollateral, UserDebt}, UserCollateralResponse, UserDebtResponse, }; -use mars_owner::OwnerUpdate; use mars_rover::{ adapters::{ account_nft::{ @@ -923,7 +923,7 @@ impl MockEnvBuilder { info: &VaultTestInfo, config: Option, ) -> &mut Self { - let config = config.unwrap_or(self.deploy_vault(info)); + let config = config.unwrap_or_else(|| self.deploy_vault(info)); let new_list = match self.pre_deployed_vaults.clone() { None => Some(vec![config]), Some(mut curr) => { diff --git a/contracts/mock-oracle/Cargo.toml b/contracts/mock-oracle/Cargo.toml index 4dc391b21..92a70d7b9 100644 --- a/contracts/mock-oracle/Cargo.toml +++ b/contracts/mock-oracle/Cargo.toml @@ -19,7 +19,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -mars-outpost = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +mars-red-bank-types = { workspace = true } diff --git a/contracts/mock-oracle/src/contract.rs b/contracts/mock-oracle/src/contract.rs index f946c3955..e70aa3646 100644 --- a/contracts/mock-oracle/src/contract.rs +++ b/contracts/mock-oracle/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; -use mars_outpost::oracle::PriceResponse; +use mars_red_bank_types::oracle::PriceResponse; use crate::{ msg::{CoinPrice, ExecuteMsg, InstantiateMsg, QueryMsg}, diff --git a/contracts/mock-oracle/src/msg.rs b/contracts/mock-oracle/src/msg.rs index 2c5feab25..680683b26 100644 --- a/contracts/mock-oracle/src/msg.rs +++ b/contracts/mock-oracle/src/msg.rs @@ -21,7 +21,7 @@ pub enum ExecuteMsg { #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - #[returns(mars_outpost::oracle::PriceResponse)] + #[returns(mars_red_bank_types::oracle::PriceResponse)] Price { denom: String, }, diff --git a/contracts/mock-red-bank/Cargo.toml b/contracts/mock-red-bank/Cargo.toml index 2d1aa1192..7bcd571be 100644 --- a/contracts/mock-red-bank/Cargo.toml +++ b/contracts/mock-red-bank/Cargo.toml @@ -19,8 +19,8 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -mars-outpost = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +mars-red-bank-types = { workspace = true } diff --git a/contracts/mock-red-bank/examples/schema.rs b/contracts/mock-red-bank/examples/schema.rs index dbd1c8fb4..a1e7c40b8 100644 --- a/contracts/mock-red-bank/examples/schema.rs +++ b/contracts/mock-red-bank/examples/schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; use mars_mock_red_bank::msg::InstantiateMsg; -use mars_outpost::red_bank::{ExecuteMsg, QueryMsg}; +use mars_red_bank_types::red_bank::{ExecuteMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index 2ccae8884..a57e096b7 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; -use mars_outpost::red_bank; +use mars_red_bank_types::red_bank; use crate::{ execute::{borrow, deposit, repay}, diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index 8d6dcda38..9ba5a7d29 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{Deps, StdResult, Uint128}; -use mars_outpost::red_bank::{Market, UserCollateralResponse, UserDebtResponse}; +use mars_red_bank_types::red_bank::{Market, UserCollateralResponse, UserDebtResponse}; use crate::{ helpers::{load_collateral_amount, load_debt_amount}, diff --git a/contracts/swapper/base/src/contract.rs b/contracts/swapper/base/src/contract.rs index ba8ba4816..3d8dfad51 100644 --- a/contracts/swapper/base/src/contract.rs +++ b/contracts/swapper/base/src/contract.rs @@ -192,7 +192,7 @@ where Ok(Response::new() .add_message(swap_msg) .add_message(transfer_msg) - .add_attribute("action", "rover/swapper/swap_fn") + .add_attribute("action", "swap_fn") .add_attribute("denom_in", coin_in.denom) .add_attribute("amount_in", coin_in.amount) .add_attribute("denom_out", denom_out) @@ -230,9 +230,7 @@ where .collect(), }); - Ok(Response::new() - .add_attribute("action", "rover/swapper/transfer_result") - .add_message(transfer_msg)) + Ok(Response::new().add_attribute("action", "transfer_result").add_message(transfer_msg)) } fn set_route( diff --git a/contracts/swapper/mock/src/contract.rs b/contracts/swapper/mock/src/contract.rs index c2765f8d3..96f48f68b 100644 --- a/contracts/swapper/mock/src/contract.rs +++ b/contracts/swapper/mock/src/contract.rs @@ -89,7 +89,5 @@ pub fn swap_exact_in( amount: coins(MOCK_SWAP_RESULT.u128(), denom_out), }); - Ok(Response::new() - .add_attribute("action", "rover/swapper/transfer_result") - .add_message(transfer_msg)) + Ok(Response::new().add_attribute("action", "transfer_result").add_message(transfer_msg)) } diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index 684c9c534..d73b265c6 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -32,5 +32,5 @@ schemars = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -anyhow = { workspace = true } -osmosis-testing = { workspace = true } +anyhow = { workspace = true } +osmosis-test-tube = { workspace = true } diff --git a/contracts/swapper/osmosis/tests/helpers.rs b/contracts/swapper/osmosis/tests/helpers.rs index 42c365ae8..e933fbd94 100644 --- a/contracts/swapper/osmosis/tests/helpers.rs +++ b/contracts/swapper/osmosis/tests/helpers.rs @@ -5,7 +5,7 @@ use mars_rover::adapters::swap::InstantiateMsg; use osmosis_std::types::osmosis::gamm::v1beta1::{ MsgSwapExactAmountIn, MsgSwapExactAmountInResponse, SwapAmountInRoute, }; -use osmosis_testing::{ +use osmosis_test_tube::{ cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest, Account, Bank, ExecuteResponse, Gamm, OsmosisTestApp, Runner, RunnerError, SigningAccount, Wasm, }; diff --git a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs index 3ce435011..03cff9515 100644 --- a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs +++ b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs @@ -6,7 +6,7 @@ use cosmwasm_std::coin; use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; use mars_swapper_osmosis::route::OsmosisRoute; use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; -use osmosis_testing::{Gamm, Module, OsmosisTestApp, SigningAccount, Wasm}; +use osmosis_test_tube::{Gamm, Module, OsmosisTestApp, SigningAccount, Wasm}; use crate::helpers::instantiate_contract; diff --git a/contracts/swapper/osmosis/tests/test_estimate.rs b/contracts/swapper/osmosis/tests/test_estimate.rs index c566fe3e9..eed11381b 100644 --- a/contracts/swapper/osmosis/tests/test_estimate.rs +++ b/contracts/swapper/osmosis/tests/test_estimate.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{coin, Uint128}; use mars_rover::adapters::swap::{EstimateExactInSwapResponse, ExecuteMsg, QueryMsg}; use mars_swapper_osmosis::route::OsmosisRoute; use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; -use osmosis_testing::{Gamm, Module, OsmosisTestApp, RunnerResult, Wasm}; +use osmosis_test_tube::{Gamm, Module, OsmosisTestApp, RunnerResult, Wasm}; use crate::helpers::{ assert_err, instantiate_contract, query_price_from_pool, swap_to_create_twap_records, diff --git a/contracts/swapper/osmosis/tests/test_instantiate.rs b/contracts/swapper/osmosis/tests/test_instantiate.rs index 2dc880eed..8a5872859 100644 --- a/contracts/swapper/osmosis/tests/test_instantiate.rs +++ b/contracts/swapper/osmosis/tests/test_instantiate.rs @@ -1,7 +1,7 @@ use cosmwasm_std::coin; use mars_owner::OwnerResponse; use mars_rover::adapters::swap::{InstantiateMsg, QueryMsg}; -use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; +use osmosis_test_tube::{Account, Module, OsmosisTestApp, Wasm}; use crate::helpers::{instantiate_contract, wasm_file}; diff --git a/contracts/swapper/osmosis/tests/test_set_route.rs b/contracts/swapper/osmosis/tests/test_set_route.rs index 16dac2908..f6124f9c2 100644 --- a/contracts/swapper/osmosis/tests/test_set_route.rs +++ b/contracts/swapper/osmosis/tests/test_set_route.rs @@ -4,7 +4,7 @@ use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; use mars_swapper_base::ContractError; use mars_swapper_osmosis::route::OsmosisRoute; use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; -use osmosis_testing::{Gamm, Module, OsmosisTestApp, Wasm}; +use osmosis_test_tube::{Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract}; diff --git a/contracts/swapper/osmosis/tests/test_swap.rs b/contracts/swapper/osmosis/tests/test_swap.rs index 1d51ed49a..cf6233bdf 100644 --- a/contracts/swapper/osmosis/tests/test_swap.rs +++ b/contracts/swapper/osmosis/tests/test_swap.rs @@ -3,7 +3,7 @@ use mars_rover::{adapters::swap::ExecuteMsg, error::ContractError as RoverError} use mars_swapper_base::ContractError; use mars_swapper_osmosis::route::OsmosisRoute; use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; -use osmosis_testing::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; +use osmosis_test_tube::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{ assert_err, instantiate_contract, query_balance, swap_to_create_twap_records, diff --git a/contracts/swapper/osmosis/tests/test_update_admin.rs b/contracts/swapper/osmosis/tests/test_update_admin.rs index ea74bc9c6..75e353be8 100644 --- a/contracts/swapper/osmosis/tests/test_update_admin.rs +++ b/contracts/swapper/osmosis/tests/test_update_admin.rs @@ -2,7 +2,7 @@ use cosmwasm_std::coin; use mars_owner::{OwnerResponse, OwnerUpdate}; use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg}; use mars_swapper_osmosis::route::OsmosisRoute; -use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; +use osmosis_test_tube::{Account, Module, OsmosisTestApp, Wasm}; use crate::helpers::instantiate_contract; diff --git a/contracts/zapper/base/Cargo.toml b/contracts/zapper/base/Cargo.toml index 9226f1947..97d5a8322 100644 --- a/contracts/zapper/base/Cargo.toml +++ b/contracts/zapper/base/Cargo.toml @@ -21,10 +21,8 @@ library = [] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw-dex = { workspace = true } cw-utils = { workspace = true } schemars = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } - -# FIXME: develop branch dependency till Apollo finish audit -cw-dex = { git = "https://github.com/apollodao/cw-dex", rev = "b9d18cfbba8aa09ae4ee9459d308cda87ae5ffb3" } diff --git a/contracts/zapper/base/src/contract.rs b/contracts/zapper/base/src/contract.rs index 59222ffd8..ef610668d 100644 --- a/contracts/zapper/base/src/contract.rs +++ b/contracts/zapper/base/src/contract.rs @@ -125,7 +125,7 @@ where // Callbacks to return remaining coins and LP tokens let callback_msgs = prepare_return_coin_callbacks(&env, recipient.clone(), coin_balances)?; - let event = Event::new("rover/zapper/execute_provide_liquidity") + let event = Event::new("execute_provide_liquidity") .add_attribute("lp_token_out", lp_token_out) .add_attribute("minimum_receive", minimum_receive) .add_attribute("recipient", recipient); @@ -173,7 +173,7 @@ where // Callbacks to return remaining coins and LP tokens let callback_msgs = prepare_return_coin_callbacks(&env, recipient.clone(), coin_balances)?; - let event = Event::new("rover/zapper/execute_withdraw_liquidity") + let event = Event::new("execute_withdraw_liquidity") .add_attribute("lp_token", lp_token.denom) .add_attribute("coins_returned", coins_returned_str) .add_attribute("recipient", recipient); @@ -205,7 +205,7 @@ where amount: vec![return_coin.clone()], }); - let event = Event::new("rover/zapper/execute_callback_return_lp_tokens") + let event = Event::new("execute_callback_return_lp_tokens") .add_attribute("coin_returned", return_coin.to_string()) .add_attribute("recipient", recipient); diff --git a/contracts/zapper/osmosis/Cargo.toml b/contracts/zapper/osmosis/Cargo.toml index 91f565bdc..08c6f0e4d 100644 --- a/contracts/zapper/osmosis/Cargo.toml +++ b/contracts/zapper/osmosis/Cargo.toml @@ -21,11 +21,9 @@ library = [] [dependencies] cosmwasm-std = { workspace = true } cw2 = { workspace = true } +cw-dex = { workspace = true } mars-zapper-base = { workspace = true } -# FIXME: develop branch dependency till Apollo finish audit -cw-dex = { git = "https://github.com/apollodao/cw-dex", rev = "b9d18cfbba8aa09ae4ee9459d308cda87ae5ffb3", features = ["osmosis"] } - [dev-dependencies] -cw-utils = { workspace = true } -osmosis-testing = { workspace = true } +cw-utils = { workspace = true } +osmosis-test-tube = { workspace = true } diff --git a/contracts/zapper/osmosis/tests/helpers.rs b/contracts/zapper/osmosis/tests/helpers.rs index fef5c11d4..76a971414 100644 --- a/contracts/zapper/osmosis/tests/helpers.rs +++ b/contracts/zapper/osmosis/tests/helpers.rs @@ -1,7 +1,7 @@ use std::{fmt::Display, str::FromStr}; use mars_zapper_base::InstantiateMsg; -use osmosis_testing::{ +use osmosis_test_tube::{ cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest, Bank, OsmosisTestApp, RunnerError, SigningAccount, Wasm, }; diff --git a/contracts/zapper/osmosis/tests/test_callback.rs b/contracts/zapper/osmosis/tests/test_callback.rs index 352c75053..bbb5a57f6 100644 --- a/contracts/zapper/osmosis/tests/test_callback.rs +++ b/contracts/zapper/osmosis/tests/test_callback.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{coin, Addr, Coin}; use mars_zapper_base::{CallbackMsg, ContractError, ExecuteMsg}; -use osmosis_testing::{Account, Module, OsmosisTestApp, Wasm}; +use osmosis_test_tube::{Account, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract}; diff --git a/contracts/zapper/osmosis/tests/test_provide_liquidity.rs b/contracts/zapper/osmosis/tests/test_provide_liquidity.rs index bf62dfc9e..519c34226 100644 --- a/contracts/zapper/osmosis/tests/test_provide_liquidity.rs +++ b/contracts/zapper/osmosis/tests/test_provide_liquidity.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; use mars_zapper_base::{ExecuteMsg, QueryMsg}; -use osmosis_testing::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; +use osmosis_test_tube::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract, query_balance}; diff --git a/contracts/zapper/osmosis/tests/test_queries.rs b/contracts/zapper/osmosis/tests/test_queries.rs index bd1572682..4bcfbe274 100644 --- a/contracts/zapper/osmosis/tests/test_queries.rs +++ b/contracts/zapper/osmosis/tests/test_queries.rs @@ -3,7 +3,7 @@ use std::{ops::Div, str::FromStr}; use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; use mars_zapper_base::QueryMsg; -use osmosis_testing::{Gamm, Module, OsmosisTestApp, Wasm}; +use osmosis_test_tube::{Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract}; diff --git a/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs b/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs index aefe521ee..799c11433 100644 --- a/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs +++ b/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; use cw_utils::PaymentError; use mars_zapper_base::{ContractError, ExecuteMsg, QueryMsg}; -use osmosis_testing::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; +use osmosis_test_tube::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract, query_balance}; diff --git a/coverage_grcov.Makefile.toml b/coverage_grcov.Makefile.toml index 02653340d..d77fbc7bb 100644 --- a/coverage_grcov.Makefile.toml +++ b/coverage_grcov.Makefile.toml @@ -41,7 +41,7 @@ set -eux grcov ${COVERAGE_PROF_OUTPUT} \ -b ${COVERAGE_BINARIES} -s ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY} \ - -t ${GRCOV_OUTPUT_TYPE} --llvm --branch --ignore-not-existing --ignore "/*" --ignore "*/tests/*" --ignore "*/mock*" --ignore "target/*" --ignore "*/swapper/*" --ignore "*/zapper/*" --ignore "*/packages/outpost/*" -o ${GRCOV_OUTPUT_PATH} + -t ${GRCOV_OUTPUT_TYPE} --llvm --branch --ignore-not-existing --ignore "/*" --ignore "*/tests/*" --ignore "*/mock*" --ignore "target/*" --ignore "*/swapper/*" --ignore "*/zapper/*" -o ${GRCOV_OUTPUT_PATH} ''' dependencies = ["install-grcov", "coverage-grcov-prepare-outdir", "coverage-grcov-run-test"] diff --git a/packages/chains/osmosis/Cargo.toml b/packages/chains/osmosis/Cargo.toml deleted file mode 100755 index cf78161a4..000000000 --- a/packages/chains/osmosis/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "mars-osmosis" -version = { workspace = true } -authors = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -documentation = { workspace = true } -keywords = { workspace = true } - -[lib] -doctest = false - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cosmwasm-std = { workspace = true } -osmosis-std = { workspace = true } -serde = { workspace = true } diff --git a/packages/chains/osmosis/src/helpers.rs b/packages/chains/osmosis/src/helpers.rs deleted file mode 100644 index 91a631d92..000000000 --- a/packages/chains/osmosis/src/helpers.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::str::FromStr; - -use cosmwasm_std::{ - coin, Decimal, Empty, QuerierWrapper, QueryRequest, StdError, StdResult, Uint128, -}; -use osmosis_std::{ - shim::{Duration, Timestamp}, - types::{ - cosmos::base::v1beta1::Coin, - osmosis::{ - downtimedetector::v1beta1::DowntimedetectorQuerier, - gamm::{ - v1beta1::{PoolAsset, PoolParams, QueryPoolRequest}, - v2::GammQuerier, - }, - twap::v1beta1::TwapQuerier, - }, - }, -}; -use serde::{Deserialize, Serialize}; - -// NOTE: Use custom Pool (`id` type as String) due to problem with json (de)serialization discrepancy between go and rust side. -// https://github.com/osmosis-labs/osmosis-rust/issues/42 -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct Pool { - pub id: String, - pub address: String, - pub pool_params: Option, - pub future_pool_governor: String, - pub pool_assets: Vec, - pub total_shares: Option, - pub total_weight: String, -} - -impl Pool { - /// Unwraps Osmosis coin into Cosmwasm coin - pub fn unwrap_coin(osmosis_coin: &Option) -> StdResult { - let osmosis_coin = match osmosis_coin { - None => return Err(StdError::generic_err("missing coin")), // just in case, it shouldn't happen - Some(osmosis_coin) => osmosis_coin, - }; - let cosmwasm_coin = - coin(Uint128::from_str(&osmosis_coin.amount)?.u128(), &osmosis_coin.denom); - Ok(cosmwasm_coin) - } -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct QueryPoolResponse { - pub pool: Pool, -} - -/// Query an Osmosis pool's coin depths and the supply of of liquidity token -pub fn query_pool(querier: &QuerierWrapper, pool_id: u64) -> StdResult { - let req: QueryRequest = QueryPoolRequest { - pool_id, - } - .into(); - let res: QueryPoolResponse = querier.query(&req)?; - Ok(res.pool) -} - -pub fn has_denom(denom: &str, pool_assets: &[PoolAsset]) -> bool { - pool_assets.iter().flat_map(|asset| &asset.token).any(|coin| coin.denom == denom) -} - -/// Query the spot price of a coin, denominated in OSMO -pub fn query_spot_price( - querier: &QuerierWrapper, - pool_id: u64, - base_denom: &str, - quote_denom: &str, -) -> StdResult { - let spot_price_res = GammQuerier::new(querier).spot_price( - pool_id, - base_denom.to_string(), - quote_denom.to_string(), - )?; - let price = Decimal::from_str(&spot_price_res.spot_price)?; - Ok(price) -} - -/// Query arithmetic twap price of a coin, denominated in OSMO. -/// `start_time` must be within 48 hours of current block time. -#[allow(deprecated)] // FIXME: arithmetic_twap_to_now shouldn't be deprecated, make clippy happy for now -pub fn query_arithmetic_twap_price( - querier: &QuerierWrapper, - pool_id: u64, - base_denom: &str, - quote_denom: &str, - start_time: u64, -) -> StdResult { - let twap_res = TwapQuerier::new(querier).arithmetic_twap_to_now( - pool_id, - base_denom.to_string(), - quote_denom.to_string(), - Some(Timestamp { - seconds: start_time as i64, - nanos: 0, - }), - )?; - let price = Decimal::from_str(&twap_res.arithmetic_twap)?; - Ok(price) -} - -/// Query geometric twap price of a coin, denominated in OSMO. -/// `start_time` must be within 48 hours of current block time. -#[allow(deprecated)] // FIXME: geometric_twap_to_now shouldn't be deprecated, make clippy happy for now -pub fn query_geometric_twap_price( - querier: &QuerierWrapper, - pool_id: u64, - base_denom: &str, - quote_denom: &str, - start_time: u64, -) -> StdResult { - let twap_res = TwapQuerier::new(querier).geometric_twap_to_now( - pool_id, - base_denom.to_string(), - quote_denom.to_string(), - Some(Timestamp { - seconds: start_time as i64, - nanos: 0, - }), - )?; - let price = Decimal::from_str(&twap_res.geometric_twap)?; - Ok(price) -} - -/// Has it been $RECOVERY_PERIOD since the chain has been down for $DOWNTIME_PERIOD. -/// -/// https://github.com/osmosis-labs/osmosis/tree/main/x/downtime-detector -pub fn recovered_since_downtime_of_length( - querier: &QuerierWrapper, - downtime: i32, - recovery: u64, -) -> StdResult { - let downtime_detector_res = DowntimedetectorQuerier::new(querier) - .recovered_since_downtime_of_length( - downtime, - Some(Duration { - seconds: recovery as i64, - nanos: 0, - }), - )?; - Ok(downtime_detector_res.succesfully_recovered) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_unwrapping_coin() { - let pool = Pool { - id: "1111".to_string(), - address: "".to_string(), - pool_params: None, - future_pool_governor: "".to_string(), - pool_assets: vec![ - PoolAsset { - token: Some(Coin { - denom: "denom_1".to_string(), - amount: "123".to_string(), - }), - weight: "500".to_string(), - }, - PoolAsset { - token: Some(Coin { - denom: "denom_2".to_string(), - amount: "430".to_string(), - }), - weight: "500".to_string(), - }, - ], - total_shares: None, - total_weight: "".to_string(), - }; - - let res_err = Pool::unwrap_coin(&pool.total_shares).unwrap_err(); - assert_eq!(res_err, StdError::generic_err("missing coin")); - - let res = Pool::unwrap_coin(&pool.pool_assets[0].token).unwrap(); - assert_eq!(res, coin(123, "denom_1")); - let res = Pool::unwrap_coin(&pool.pool_assets[1].token).unwrap(); - assert_eq!(res, coin(430, "denom_2")); - } -} diff --git a/packages/chains/osmosis/src/lib.rs b/packages/chains/osmosis/src/lib.rs deleted file mode 100644 index 1630fabcd..000000000 --- a/packages/chains/osmosis/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod helpers; diff --git a/packages/outpost/Cargo.toml b/packages/outpost/Cargo.toml deleted file mode 100644 index 2b4adbd64..000000000 --- a/packages/outpost/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "mars-outpost" -version = { workspace = true } -authors = { workspace = true } -license = { workspace = true } -edition = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -documentation = { workspace = true } -keywords = { workspace = true } - -[lib] -doctest = false - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -mars-owner = { workspace = true } -thiserror = { workspace = true } diff --git a/packages/outpost/src/address_provider.rs b/packages/outpost/src/address_provider.rs deleted file mode 100644 index 3f5d80dcf..000000000 --- a/packages/outpost/src/address_provider.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::{any::type_name, fmt, str::FromStr}; - -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::StdError; -use mars_owner::OwnerUpdate; - -#[cw_serde] -#[derive(Copy, Eq, Hash)] -pub enum MarsAddressType { - Incentives, - Oracle, - RedBank, - RewardsCollector, - /// Protocol admin is an ICS-27 interchain account controlled by Mars Hub's x/gov module. - /// This account will take the owner and admin roles of outpost contracts. - /// - /// Owner means the account who can invoke certain priviliged execute methods on a contract, - /// such as updating the config. - /// Admin means the account who can migrate a contract. - ProtocolAdmin, - /// The `fee_collector` module account controlled by Mars Hub's x/distribution module. - /// Funds sent to this account will be distributed as staking rewards. - /// - /// NOTE: This is a Mars Hub address with the `mars` bech32 prefix, which may not be recognized - /// by the `api.addr_validate` method. - FeeCollector, - /// The module account controlled by the by Mars Hub's x/safety module. - /// Funds sent to this account will be deposited into the safety fund. - /// - /// NOTE: This is a Mars Hub address with the `mars` bech32 prefix, which may not be recognized - /// by the `api.addr_validate` method. - SafetyFund, -} - -impl fmt::Display for MarsAddressType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - MarsAddressType::FeeCollector => "fee_collector", - MarsAddressType::Incentives => "incentives", - MarsAddressType::Oracle => "oracle", - MarsAddressType::ProtocolAdmin => "protocol_admin", - MarsAddressType::RedBank => "red_bank", - MarsAddressType::RewardsCollector => "rewards_collector", - MarsAddressType::SafetyFund => "safety_fund", - }; - write!(f, "{s}") - } -} - -impl FromStr for MarsAddressType { - type Err = StdError; - - fn from_str(s: &str) -> Result { - match s { - "fee_collector" => Ok(MarsAddressType::FeeCollector), - "incentives" => Ok(MarsAddressType::Incentives), - "oracle" => Ok(MarsAddressType::Oracle), - "protocol_admin" => Ok(MarsAddressType::ProtocolAdmin), - "red_bank" => Ok(MarsAddressType::RedBank), - "rewards_collector" => Ok(MarsAddressType::RewardsCollector), - "safety_fund" => Ok(MarsAddressType::SafetyFund), - _ => Err(StdError::parse_err(type_name::(), s)), - } - } -} - -/// Essentially, mars-address-provider is a required init param for all other contracts, so it needs -/// to be initialised first (Only owner can be set on initialization). So the deployment looks like -/// this: -/// -/// 1. Init the address provider -/// 2. Init all other contracts, passing in the address provider address (not ALL contracts need this -/// but many do) -/// 3. Update the address provider, with an update config call to contain all the other contract addresses -/// from step 2, this is why we need it to be owned by an EOA (externally owned account) - so we -/// can do this update as part of the deployment -/// 4. Update the owner of the address provider contract at the end of deployment to be either a. the -/// multisig or b. the gov/council contract -#[cw_serde] -pub struct InstantiateMsg { - /// The contract's owner - pub owner: String, - /// The address prefix of the chain this contract is deployed on - pub prefix: String, -} - -#[cw_serde] -pub struct Config { - /// The address prefix of the chain this contract is deployed on - pub prefix: String, -} - -#[cw_serde] -pub enum ExecuteMsg { - /// Set address - SetAddress { - address_type: MarsAddressType, - address: String, - }, - /// Manages admin role state - UpdateOwner(OwnerUpdate), -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Get config - #[returns(ConfigResponse)] - Config {}, - /// Get a single address - #[returns(AddressResponseItem)] - Address(MarsAddressType), - /// Get a list of addresses - #[returns(Vec)] - Addresses(Vec), - /// Query all stored addresses with pagination - #[returns(Vec)] - AllAddresses { - start_after: Option, - limit: Option, - }, -} - -#[cw_serde] -pub struct ConfigResponse { - /// The contract's owner - pub owner: Option, - /// The contract's proposed owner - pub proposed_new_owner: Option, - /// The address prefix of the chain this contract is deployed on - pub prefix: String, -} - -#[cw_serde] -pub struct AddressResponseItem { - /// The type of address - pub address_type: MarsAddressType, - /// Address value - pub address: String, -} - -pub mod helpers { - use std::collections::HashMap; - - use cosmwasm_std::{Addr, Deps, StdResult}; - - use super::{AddressResponseItem, MarsAddressType, QueryMsg}; - - pub fn query_address( - deps: Deps, - address_provider_addr: &Addr, - contract: MarsAddressType, - ) -> StdResult { - let res: AddressResponseItem = - deps.querier.query_wasm_smart(address_provider_addr, &QueryMsg::Address(contract))?; - - deps.api.addr_validate(&res.address) - } - - pub fn query_addresses( - deps: Deps, - address_provider_addr: &Addr, - contracts: Vec, - ) -> StdResult> { - let res: Vec = deps - .querier - .query_wasm_smart(address_provider_addr, &QueryMsg::Addresses(contracts))?; - - res.iter() - .map(|item| Ok((item.address_type, deps.api.addr_validate(&item.address)?))) - .collect() - } -} diff --git a/packages/outpost/src/error.rs b/packages/outpost/src/error.rs deleted file mode 100644 index 12da549d4..000000000 --- a/packages/outpost/src/error.rs +++ /dev/null @@ -1,81 +0,0 @@ -use cosmwasm_std::StdError; -use thiserror::Error; - -#[derive(Error, Debug, PartialEq)] -pub enum MarsError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("All params should be available during instantiation")] - InstantiateParamsUnavailable {}, - - #[error("Incorrect number of addresses, expected {expected:?}, got {actual:?}")] - AddressesQueryWrongNumber { - expected: u32, - actual: u32, - }, - - #[error("Invalid param: {param_name} is {invalid_value}, but it should be {predicate}")] - InvalidParam { - param_name: String, - invalid_value: String, - predicate: String, - }, - - #[error("Failed to deserialize RPC query response for: {target_type}")] - Deserialize { - target_type: String, - }, - - #[error("Invalid denom: {reason}")] - InvalidDenom { - reason: String, - }, -} - -impl From for StdError { - fn from(source: MarsError) -> Self { - match source { - MarsError::Std(e) => e, - e => StdError::generic_err(e.to_string()), - } - } -} - -// TESTS - -#[cfg(test)] -mod tests { - use super::*; - use crate::error::MarsError; - - #[test] - fn test_mars_error_to_std_error() { - { - let mars_error = MarsError::Unauthorized {}; - - let std_error: StdError = mars_error.into(); - - assert_eq!(std_error, StdError::generic_err("Unauthorized")) - } - - { - let mars_error = MarsError::Std(StdError::generic_err("Some error")); - - let std_error: StdError = mars_error.into(); - - assert_eq!(std_error, StdError::generic_err("Some error")) - } - - { - let mars_error = MarsError::Std(StdError::invalid_data_size(1, 2)); - - let std_error: StdError = mars_error.into(); - - assert_eq!(std_error, StdError::invalid_data_size(1, 2)) - } - } -} diff --git a/packages/outpost/src/helpers.rs b/packages/outpost/src/helpers.rs deleted file mode 100644 index 9f0030504..000000000 --- a/packages/outpost/src/helpers.rs +++ /dev/null @@ -1,94 +0,0 @@ -use cosmwasm_std::{coins, Addr, Api, BankMsg, CosmosMsg, Decimal, StdResult, Uint128}; - -use crate::error::MarsError; - -pub fn build_send_asset_msg(recipient_addr: &Addr, denom: &str, amount: Uint128) -> CosmosMsg { - CosmosMsg::Bank(BankMsg::Send { - to_address: recipient_addr.into(), - amount: coins(amount.u128(), denom), - }) -} - -/// Used when unwrapping an optional address sent in a contract call by a user. -/// Validates addreess if present, otherwise uses a given default value. -pub fn option_string_to_addr( - api: &dyn Api, - option_string: Option, - default: Addr, -) -> StdResult { - match option_string { - Some(input_addr) => api.addr_validate(&input_addr), - None => Ok(default), - } -} - -pub fn decimal_param_lt_one(param_value: Decimal, param_name: &str) -> Result<(), MarsError> { - if !param_value.lt(&Decimal::one()) { - Err(MarsError::InvalidParam { - param_name: param_name.to_string(), - invalid_value: param_value.to_string(), - predicate: "< 1".to_string(), - }) - } else { - Ok(()) - } -} - -pub fn decimal_param_le_one(param_value: Decimal, param_name: &str) -> Result<(), MarsError> { - if !param_value.le(&Decimal::one()) { - Err(MarsError::InvalidParam { - param_name: param_name.to_string(), - invalid_value: param_value.to_string(), - predicate: "<= 1".to_string(), - }) - } else { - Ok(()) - } -} - -pub fn integer_param_gt_zero(param_value: u64, param_name: &str) -> Result<(), MarsError> { - if !param_value.gt(&0) { - Err(MarsError::InvalidParam { - param_name: param_name.to_string(), - invalid_value: param_value.to_string(), - predicate: "> 0".to_string(), - }) - } else { - Ok(()) - } -} - -pub fn zero_address() -> Addr { - Addr::unchecked("") -} - -/// follows cosmos SDK validation logic where denoms can be 3 - 128 characters long -/// and starts with a letter, followed but either a letter, number, or separator ( ‘/' , ‘:' , ‘.’ , ‘_’ , or '-') -/// reference: https://github.com/cosmos/cosmos-sdk/blob/7728516abfab950dc7a9120caad4870f1f962df5/types/coin.go#L865-L867 -pub fn validate_native_denom(denom: &str) -> Result<(), MarsError> { - if denom.len() < 3 || denom.len() > 128 { - return Err(MarsError::InvalidDenom { - reason: "Invalid denom length".to_string(), - }); - } - - let mut chars = denom.chars(); - let first = chars.next().unwrap(); - if !first.is_ascii_alphabetic() { - return Err(MarsError::InvalidDenom { - reason: "First character is not ASCII alphabetic".to_string(), - }); - } - - let set = ['/', ':', '.', '_', '-']; - for c in chars { - if !(c.is_ascii_alphanumeric() || set.contains(&c)) { - return Err(MarsError::InvalidDenom { - reason: "Not all characters are ASCII alphanumeric or one of: / : . _ -" - .to_string(), - }); - } - } - - Ok(()) -} diff --git a/packages/outpost/src/incentives.rs b/packages/outpost/src/incentives.rs deleted file mode 100644 index 0a798bfc9..000000000 --- a/packages/outpost/src/incentives.rs +++ /dev/null @@ -1,123 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Decimal, Timestamp, Uint128}; -use mars_owner::OwnerUpdate; - -/// Global configuration -#[cw_serde] -pub struct Config { - /// Address provider - pub address_provider: Addr, - /// Mars Token Denom - pub mars_denom: String, -} - -/// Incentive Metadata for a given incentive -#[cw_serde] -pub struct AssetIncentive { - /// How much MARS per second is emitted to be then distributed to all Red Bank depositors - pub emission_per_second: Uint128, - /// Start time for the incentive. Uses current block time if start_time not provided - pub start_time: Timestamp, - /// How many seconds the incentives last - pub duration: u64, - /// Total MARS assigned for distribution since the start of the incentive - pub index: Decimal, - /// Last time (in seconds) index was updated - pub last_updated: u64, -} - -/// Response to AssetIncentive query -#[cw_serde] -pub struct AssetIncentiveResponse { - /// Existing asset incentive for a given address. Will return None if it doesn't exist - pub asset_incentive: Option, -} - -#[cw_serde] -pub struct InstantiateMsg { - /// Contract owner - pub owner: String, - /// Address provider - pub address_provider: String, - /// Mars token denom - pub mars_denom: String, -} - -#[cw_serde] -pub enum ExecuteMsg { - /// Set incentive params for an asset to its depositor at Red Bank. - /// - /// If there is no incentive for the asset, all params are required. - /// New incentive can be set (rescheduled) if current one has finished (current_block_time > start_time + duration). - SetAssetIncentive { - /// Asset denom associated with the incentives - denom: String, - /// How many MARS will be assigned per second to be distributed among all Red Bank - /// depositors - emission_per_second: Option, - /// Start time for the incentive - start_time: Option, - /// How many seconds the incentives last - duration: Option, - }, - - /// Handle balance change updating user and asset rewards. - /// Sent from an external contract, triggered on user balance changes. - /// Will return an empty response if no incentive is applied for the asset - BalanceChange { - /// User address. Address is trusted as it must be validated by the Red Bank - /// contract before calling this method - user_addr: Addr, - /// Denom of the asset of which deposited balance is changed - denom: String, - /// The user's scaled collateral amount up to the instant before the change - user_amount_scaled_before: Uint128, - /// The market's total scaled collateral amount up to the instant before the change - total_amount_scaled_before: Uint128, - }, - - /// Claim rewards. MARS rewards accrued by the user will be staked into xMARS before - /// being sent. - ClaimRewards {}, - - /// Update contract config (only callable by owner) - UpdateConfig { - address_provider: Option, - mars_denom: Option, - }, - - /// Manages admin role state - UpdateOwner(OwnerUpdate), -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Query contract config - #[returns(ConfigResponse)] - Config {}, - - /// Query info about asset incentive for a given denom - #[returns(AssetIncentiveResponse)] - AssetIncentive { - denom: String, - }, - - /// Query user current unclaimed rewards - #[returns(Uint128)] - UserUnclaimedRewards { - user: String, - }, -} - -#[cw_serde] -pub struct ConfigResponse { - /// The contract's owner - pub owner: Option, - /// The contract's proposed owner - pub proposed_new_owner: Option, - /// Address provider - pub address_provider: Addr, - /// Mars Token Denom - pub mars_denom: String, -} diff --git a/packages/outpost/src/lib.rs b/packages/outpost/src/lib.rs deleted file mode 100644 index 3b29cf38f..000000000 --- a/packages/outpost/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod address_provider; -pub mod error; -pub mod helpers; -pub mod incentives; -pub mod math; -pub mod oracle; -pub mod red_bank; -pub mod rewards_collector; diff --git a/packages/outpost/src/math.rs b/packages/outpost/src/math.rs deleted file mode 100644 index bba01eb7e..000000000 --- a/packages/outpost/src/math.rs +++ /dev/null @@ -1,263 +0,0 @@ -use std::convert::TryInto; - -use cosmwasm_std::{ - CheckedFromRatioError, Decimal, Fraction, OverflowError, OverflowOperation, StdError, - StdResult, Uint128, Uint256, -}; - -pub fn uint128_checked_div_with_ceil( - numerator: Uint128, - denominator: Uint128, -) -> StdResult { - let mut result = numerator.checked_div(denominator)?; - - if !numerator.checked_rem(denominator)?.is_zero() { - result += Uint128::from(1_u128); - } - - Ok(result) -} - -/// Divide 'a' by 'b'. -pub fn divide_decimal_by_decimal(a: Decimal, b: Decimal) -> StdResult { - Decimal::checked_from_ratio(a.numerator(), b.numerator()).map_err(|e| match e { - CheckedFromRatioError::Overflow => StdError::Overflow { - source: OverflowError { - operation: OverflowOperation::Mul, - operand1: a.numerator().to_string(), - operand2: a.denominator().to_string(), - }, - }, - CheckedFromRatioError::DivideByZero => StdError::DivideByZero { - source: cosmwasm_std::DivideByZeroError { - operand: b.to_string(), - }, - }, - }) -} - -/// Divide Uint128 by Decimal. -/// (Uint128 / numerator / denominator) is equal to (Uint128 * denominator / numerator). -pub fn divide_uint128_by_decimal(a: Uint128, b: Decimal) -> StdResult { - // (Uint128 / numerator / denominator) is equal to (Uint128 * denominator / numerator). - let numerator_u256 = a.full_mul(b.denominator()); - let denominator_u256 = Uint256::from(b.numerator()); - - let result_u256 = numerator_u256 / denominator_u256; - - let result = result_u256.try_into()?; - Ok(result) -} - -/// Divide Uint128 by Decimal, rounding up to the nearest integer. -pub fn divide_uint128_by_decimal_and_ceil(a: Uint128, b: Decimal) -> StdResult { - // (Uint128 / numerator / denominator) is equal to (Uint128 * denominator / numerator). - let numerator_u256 = a.full_mul(b.denominator()); - let denominator_u256 = Uint256::from(b.numerator()); - - let mut result_u256 = numerator_u256 / denominator_u256; - - if numerator_u256.checked_rem(denominator_u256)? > Uint256::zero() { - result_u256 += Uint256::from(1_u32); - } - - let result = result_u256.try_into()?; - Ok(result) -} - -/// Multiply Uint128 by Decimal, rounding up to the nearest integer. -pub fn multiply_uint128_by_decimal_and_ceil(a: Uint128, b: Decimal) -> StdResult { - let numerator_u256 = a.full_mul(b.numerator()); - let denominator_u256 = Uint256::from(b.denominator()); - - let mut result_u256 = numerator_u256 / denominator_u256; - - if numerator_u256.checked_rem(denominator_u256)? > Uint256::zero() { - result_u256 += Uint256::from(1_u32); - } - - let result = result_u256.try_into()?; - Ok(result) -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use cosmwasm_std::{ConversionOverflowError, OverflowOperation}; - - use super::*; - - const DECIMAL_FRACTIONAL: Uint128 = Uint128::new(1_000_000_000_000_000_000u128); // 1*10**18 - const DECIMAL_FRACTIONAL_SQUARED: Uint128 = - Uint128::new(1_000_000_000_000_000_000_000_000_000_000_000_000u128); // (1*10**18)**2 = 1*10**36 - - #[test] - fn test_uint128_checked_div_with_ceil() { - let a = Uint128::new(120u128); - let b = Uint128::zero(); - uint128_checked_div_with_ceil(a, b).unwrap_err(); - - let a = Uint128::new(120u128); - let b = Uint128::new(60_u128); - let c = uint128_checked_div_with_ceil(a, b).unwrap(); - assert_eq!(c, Uint128::new(2u128)); - - let a = Uint128::new(120u128); - let b = Uint128::new(119_u128); - let c = uint128_checked_div_with_ceil(a, b).unwrap(); - assert_eq!(c, Uint128::new(2u128)); - - let a = Uint128::new(120u128); - let b = Uint128::new(120_u128); - let c = uint128_checked_div_with_ceil(a, b).unwrap(); - assert_eq!(c, Uint128::new(1u128)); - - let a = Uint128::new(120u128); - let b = Uint128::new(121_u128); - let c = uint128_checked_div_with_ceil(a, b).unwrap(); - assert_eq!(c, Uint128::new(1u128)); - - let a = Uint128::zero(); - let b = Uint128::new(121_u128); - let c = uint128_checked_div_with_ceil(a, b).unwrap(); - assert_eq!(c, Uint128::zero()); - } - - #[test] - fn checked_decimal_division() { - let a = Decimal::from_ratio(99988u128, 100u128); - let b = Decimal::from_ratio(24997u128, 100u128); - let c = divide_decimal_by_decimal(a, b).unwrap(); - assert_eq!(c, Decimal::from_str("4.0").unwrap()); - - let a = Decimal::from_ratio(123456789u128, 1000000u128); - let b = Decimal::from_ratio(33u128, 1u128); - let c = divide_decimal_by_decimal(a, b).unwrap(); - assert_eq!(c, Decimal::from_str("3.741114818181818181").unwrap()); - - let a = Decimal::MAX; - let b = Decimal::MAX; - let c = divide_decimal_by_decimal(a, b).unwrap(); - assert_eq!(c, Decimal::one()); - - // Note: DivideByZeroError is not public so we just check if dividing by zero returns error - let a = Decimal::one(); - let b = Decimal::zero(); - divide_decimal_by_decimal(a, b).unwrap_err(); - - let a = Decimal::MAX; - let b = Decimal::from_ratio(1u128, DECIMAL_FRACTIONAL); - let res_error = divide_decimal_by_decimal(a, b).unwrap_err(); - assert_eq!( - res_error, - OverflowError::new(OverflowOperation::Mul, Uint128::MAX, DECIMAL_FRACTIONAL).into() - ); - } - - #[test] - fn test_divide_uint128_by_decimal() { - let a = Uint128::new(120u128); - let b = Decimal::from_ratio(120u128, 15u128); - let c = divide_uint128_by_decimal(a, b).unwrap(); - assert_eq!(c, Uint128::new(15u128)); - - let a = Uint128::new(DECIMAL_FRACTIONAL.u128()); - let b = Decimal::from_ratio(DECIMAL_FRACTIONAL.u128(), 1u128); - let c = divide_uint128_by_decimal(a, b).unwrap(); - assert_eq!(c, Uint128::new(1u128)); - - let a = Uint128::new(DECIMAL_FRACTIONAL.u128()); - let b = Decimal::from_ratio(1u128, DECIMAL_FRACTIONAL.u128()); - let c = divide_uint128_by_decimal(a, b).unwrap(); - assert_eq!(c, Uint128::new(DECIMAL_FRACTIONAL_SQUARED.u128())); - - let a = Uint128::MAX; - let b = Decimal::one(); - let c = divide_uint128_by_decimal(a, b).unwrap(); - assert_eq!(c, Uint128::MAX); - - let a = Uint128::new(1_000_000_000_000_000_000); - let b = Decimal::from_ratio(1u128, DECIMAL_FRACTIONAL); - let c = divide_uint128_by_decimal(a, b).unwrap(); - assert_eq!(c, Uint128::new(1_000_000_000_000_000_000_000_000_000_000_000_000)); - - // Division is truncated - let a = Uint128::new(100); - let b = Decimal::from_ratio(3u128, 1u128); - let c = divide_uint128_by_decimal(a, b).unwrap(); - assert_eq!(c, Uint128::new(33)); - - let a = Uint128::new(75); - let b = Decimal::from_ratio(100u128, 1u128); - let c = divide_uint128_by_decimal(a, b).unwrap(); - assert_eq!(c, Uint128::new(0)); - - // Overflow - let a = Uint128::MAX; - let b = Decimal::from_ratio(1_u128, 10_u128); - let res_error = divide_uint128_by_decimal(a, b).unwrap_err(); - assert_eq!( - res_error, - ConversionOverflowError::new( - "Uint256", - "Uint128", - "3402823669209384634633746074317682114550" - ) - .into() - ); - } - - #[test] - fn test_divide_uint128_by_decimal_and_ceil() { - let a = Uint128::new(120u128); - let b = Decimal::from_ratio(120u128, 15u128); - let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); - assert_eq!(c, Uint128::new(15u128)); - - let a = Uint128::new(DECIMAL_FRACTIONAL.u128()); - let b = Decimal::from_ratio(DECIMAL_FRACTIONAL.u128(), 1u128); - let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); - assert_eq!(c, Uint128::new(1u128)); - - let a = Uint128::new(DECIMAL_FRACTIONAL.u128()); - let b = Decimal::from_ratio(1u128, DECIMAL_FRACTIONAL.u128()); - let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); - assert_eq!(c, Uint128::new(DECIMAL_FRACTIONAL_SQUARED.u128())); - - let a = Uint128::MAX; - let b = Decimal::one(); - let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); - assert_eq!(c, Uint128::MAX); - - let a = Uint128::new(1_000_000_000_000_000_000); - let b = Decimal::from_ratio(1u128, DECIMAL_FRACTIONAL); - let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); - assert_eq!(c, Uint128::new(1_000_000_000_000_000_000_000_000_000_000_000_000)); - - // Division is rounded up - let a = Uint128::new(100); - let b = Decimal::from_ratio(3u128, 1u128); - let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); - assert_eq!(c, Uint128::new(34)); - - let a = Uint128::new(75); - let b = Decimal::from_ratio(100u128, 1u128); - let c = divide_uint128_by_decimal_and_ceil(a, b).unwrap(); - assert_eq!(c, Uint128::new(1)); - - // Overflow - let a = Uint128::MAX; - let b = Decimal::from_ratio(1_u128, 10_u128); - let res_error = divide_uint128_by_decimal_and_ceil(a, b).unwrap_err(); - assert_eq!( - res_error, - ConversionOverflowError::new( - "Uint256", - "Uint128", - "3402823669209384634633746074317682114550" - ) - .into() - ); - } -} diff --git a/packages/outpost/src/oracle.rs b/packages/outpost/src/oracle.rs deleted file mode 100644 index d5b9d02cc..000000000 --- a/packages/outpost/src/oracle.rs +++ /dev/null @@ -1,116 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Decimal; -use mars_owner::OwnerUpdate; - -#[cw_serde] -pub struct InstantiateMsg { - /// The contract's owner, who can update config and price sources - pub owner: String, - /// The asset in which prices are denominated in - pub base_denom: String, -} - -#[cw_serde] -pub struct Config { - /// The asset in which prices are denominated in - pub base_denom: String, -} - -#[cw_serde] -pub enum ExecuteMsg { - /// Specify the price source to be used for a coin - /// - /// NOTE: The input parameters for method are chain-specific. - SetPriceSource { - denom: String, - price_source: T, - }, - /// Remove price source for a coin - RemovePriceSource { - denom: String, - }, - /// Manages admin role state - UpdateOwner(OwnerUpdate), -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Query contract config. - #[returns(ConfigResponse)] - Config {}, - /// Query a coin's price source. - /// - /// NOTE: The response type of this query is chain-specific. - #[returns(PriceSourceResponse)] - PriceSource { - denom: String, - }, - /// Enumerate all coins' price sources. - /// - /// NOTE: The response type of this query is chain-specific. - #[returns(Vec>)] - PriceSources { - start_after: Option, - limit: Option, - }, - /// Query a coin's price. - /// - /// NOTE: This query may be dependent on block time (e.g. if the price source is TWAP), so may not - /// work properly with time travel queries on archive nodes. - #[returns(PriceResponse)] - Price { - denom: String, - }, - /// Enumerate all coins' prices. - /// - /// NOTE: This query may be dependent on block time (e.g. if the price source is TWAP), so may not - /// work properly with time travel queries on archive nodes. - #[returns(Vec)] - Prices { - start_after: Option, - limit: Option, - }, -} - -#[cw_serde] -pub struct ConfigResponse { - /// The contract's owner - pub owner: Option, - /// The contract's proposed owner - pub proposed_new_owner: Option, - /// The asset in which prices are denominated in - pub base_denom: String, -} - -#[cw_serde] -pub struct PriceSourceResponse { - pub denom: String, - pub price_source: T, -} - -#[cw_serde] -pub struct PriceResponse { - pub denom: String, - pub price: Decimal, -} - -pub mod helpers { - use cosmwasm_std::{Decimal, QuerierWrapper, StdResult}; - - use super::{PriceResponse, QueryMsg}; - - pub fn query_price( - querier: &QuerierWrapper, - oracle: impl Into, - denom: impl Into, - ) -> StdResult { - let res: PriceResponse = querier.query_wasm_smart( - oracle.into(), - &QueryMsg::Price { - denom: denom.into(), - }, - )?; - Ok(res.price) - } -} diff --git a/packages/outpost/src/red_bank/interest_rate_model.rs b/packages/outpost/src/red_bank/interest_rate_model.rs deleted file mode 100644 index ea160b9bf..000000000 --- a/packages/outpost/src/red_bank/interest_rate_model.rs +++ /dev/null @@ -1,224 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Decimal, StdError, StdResult}; - -use crate::{error::MarsError, helpers::decimal_param_le_one, math}; - -#[cw_serde] -#[derive(Eq, Default)] -pub struct InterestRateModel { - /// Optimal utilization rate - pub optimal_utilization_rate: Decimal, - /// Base rate - pub base: Decimal, - /// Slope parameter for interest rate model function when utilization_rate < optimal_utilization_rate - pub slope_1: Decimal, - /// Slope parameter for interest rate model function when utilization_rate >= optimal_utilization_rate - pub slope_2: Decimal, -} - -impl InterestRateModel { - pub fn validate(&self) -> Result<(), MarsError> { - decimal_param_le_one(self.optimal_utilization_rate, "optimal_utilization_rate")?; - Ok(()) - } - - pub fn get_borrow_rate(&self, current_utilization_rate: Decimal) -> StdResult { - let new_borrow_rate = if current_utilization_rate <= self.optimal_utilization_rate { - if current_utilization_rate.is_zero() { - // prevent division by zero when optimal_utilization_rate is zero - self.base - } else { - // The borrow interest rates increase slowly with utilization - self.base - + self.slope_1.checked_mul(math::divide_decimal_by_decimal( - current_utilization_rate, - self.optimal_utilization_rate, - )?)? - } - } else { - // The borrow interest rates increase sharply with utilization - self.base - + self.slope_1 - + math::divide_decimal_by_decimal( - self.slope_2 - .checked_mul(current_utilization_rate - self.optimal_utilization_rate)?, - Decimal::one() - self.optimal_utilization_rate, - )? - }; - Ok(new_borrow_rate) - } - - pub fn get_liquidity_rate( - &self, - borrow_rate: Decimal, - current_utilization_rate: Decimal, - reserve_factor: Decimal, - ) -> StdResult { - borrow_rate - .checked_mul(current_utilization_rate)? - // This operation should not underflow as reserve_factor is checked to be <= 1 - .checked_mul(Decimal::one() - reserve_factor) - .map_err(StdError::from) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::red_bank::Market; - - #[test] - fn test_model_lifecycle() { - let optimal_utilization_rate = Decimal::percent(80); - let reserve_factor = Decimal::percent(20); - - let model = InterestRateModel { - optimal_utilization_rate, - base: Decimal::zero(), - slope_1: Decimal::percent(7), - slope_2: Decimal::percent(45), - }; - - let mut market = Market { - borrow_rate: Decimal::percent(10), - liquidity_rate: Decimal::zero(), - reserve_factor, - interest_rate_model: model.clone(), - ..Default::default() - }; - - let diff = Decimal::percent(10); - let utilization_rate = optimal_utilization_rate - diff; - - market.update_interest_rates(utilization_rate).unwrap(); - - let expected_borrow_rate = model.base - + math::divide_decimal_by_decimal( - model.slope_1.checked_mul(utilization_rate).unwrap(), - model.optimal_utilization_rate, - ) - .unwrap(); - - assert_eq!(market.borrow_rate, expected_borrow_rate); - assert_eq!( - market.liquidity_rate, - expected_borrow_rate - .checked_mul(utilization_rate) - .unwrap() - .checked_mul(Decimal::one() - reserve_factor) - .unwrap() - ); - } - - #[test] - fn test_interest_rates_calculation() { - let model = InterestRateModel { - optimal_utilization_rate: Decimal::percent(80), - base: Decimal::zero(), - slope_1: Decimal::percent(7), - slope_2: Decimal::percent(45), - }; - - // current utilization rate < optimal utilization rate - { - let current_utilization_rate = Decimal::percent(79); - let new_borrow_rate = model.get_borrow_rate(current_utilization_rate).unwrap(); - - let expected_borrow_rate = model.base - + math::divide_decimal_by_decimal( - model.slope_1.checked_mul(current_utilization_rate).unwrap(), - model.optimal_utilization_rate, - ) - .unwrap(); - - assert_eq!(new_borrow_rate, expected_borrow_rate); - } - - // current utilization rate == optimal utilization rate - { - let current_utilization_rate = Decimal::percent(80); - let new_borrow_rate = model.get_borrow_rate(current_utilization_rate).unwrap(); - - let expected_borrow_rate = model.base - + math::divide_decimal_by_decimal( - model.slope_1.checked_mul(current_utilization_rate).unwrap(), - model.optimal_utilization_rate, - ) - .unwrap(); - - assert_eq!(new_borrow_rate, expected_borrow_rate); - } - - // current utilization rate >= optimal utilization rate - { - let current_utilization_rate = Decimal::percent(81); - let new_borrow_rate = model.get_borrow_rate(current_utilization_rate).unwrap(); - - let expected_borrow_rate = model.base - + model.slope_1 - + math::divide_decimal_by_decimal( - model - .slope_2 - .checked_mul(current_utilization_rate - model.optimal_utilization_rate) - .unwrap(), - Decimal::one() - model.optimal_utilization_rate, - ) - .unwrap(); - - assert_eq!(new_borrow_rate, expected_borrow_rate); - } - - // current utilization rate == 100% and optimal utilization rate == 100% - { - let model = InterestRateModel { - optimal_utilization_rate: Decimal::percent(100), - base: Decimal::zero(), - slope_1: Decimal::percent(7), - slope_2: Decimal::zero(), - }; - - let current_utilization_rate = Decimal::percent(100); - let new_borrow_rate = model.get_borrow_rate(current_utilization_rate).unwrap(); - - let expected_borrow_rate = Decimal::percent(7); - - assert_eq!(new_borrow_rate, expected_borrow_rate); - } - - // current utilization rate == 0% and optimal utilization rate == 0% - { - let model = InterestRateModel { - optimal_utilization_rate: Decimal::percent(0), - base: Decimal::percent(2), - slope_1: Decimal::percent(7), - slope_2: Decimal::zero(), - }; - - let current_utilization_rate = Decimal::percent(0); - let new_borrow_rate = model.get_borrow_rate(current_utilization_rate).unwrap(); - - let expected_borrow_rate = Decimal::percent(2); - - assert_eq!(new_borrow_rate, expected_borrow_rate); - } - - // current utilization rate == 20% and optimal utilization rate == 0% - { - let model = InterestRateModel { - optimal_utilization_rate: Decimal::percent(0), - base: Decimal::percent(2), - slope_1: Decimal::percent(1), - slope_2: Decimal::percent(5), - }; - - let current_utilization_rate = Decimal::percent(20); - let new_borrow_rate = model.get_borrow_rate(current_utilization_rate).unwrap(); - - let expected_borrow_rate = model.base - + model.slope_1 - + model.slope_2.checked_mul(current_utilization_rate).unwrap(); - - assert_eq!(new_borrow_rate, expected_borrow_rate); - } - } -} diff --git a/packages/outpost/src/red_bank/market.rs b/packages/outpost/src/red_bank/market.rs deleted file mode 100644 index d6e09de75..000000000 --- a/packages/outpost/src/red_bank/market.rs +++ /dev/null @@ -1,128 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Decimal, StdResult, Uint128}; - -use crate::{ - error::MarsError, - helpers::{decimal_param_le_one, decimal_param_lt_one}, - red_bank::InterestRateModel, -}; - -#[cw_serde] -pub struct Market { - /// Denom of the asset - pub denom: String, - - /// Max base asset that can be borrowed per "base asset" collateral when using the asset as collateral - pub max_loan_to_value: Decimal, - /// Base asset amount in debt position per "base asset" of asset collateral that if surpassed makes the user's position liquidatable. - pub liquidation_threshold: Decimal, - /// Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral - /// from user in an amount equal to debt repayed + bonus) - pub liquidation_bonus: Decimal, - /// Portion of the borrow rate that is kept as protocol rewards - pub reserve_factor: Decimal, - - /// model (params + internal state) that defines how interest rate behaves - pub interest_rate_model: InterestRateModel, - - /// Borrow index (Used to compute borrow interest) - pub borrow_index: Decimal, - /// Liquidity index (Used to compute deposit interest) - pub liquidity_index: Decimal, - /// Rate charged to borrowers - pub borrow_rate: Decimal, - /// Rate paid to depositors - pub liquidity_rate: Decimal, - /// Timestamp (seconds) where indexes and where last updated - pub indexes_last_updated: u64, - - /// Total collateral scaled for the market's currency - pub collateral_total_scaled: Uint128, - /// Total debt scaled for the market's currency - pub debt_total_scaled: Uint128, - - /// If false cannot deposit - pub deposit_enabled: bool, - /// If false cannot borrow - pub borrow_enabled: bool, - /// Deposit Cap (defined in terms of the asset) - pub deposit_cap: Uint128, -} - -impl Default for Market { - fn default() -> Self { - Market { - denom: "".to_string(), - borrow_index: Decimal::one(), - liquidity_index: Decimal::one(), - borrow_rate: Decimal::zero(), - liquidity_rate: Decimal::zero(), - max_loan_to_value: Decimal::zero(), - reserve_factor: Decimal::zero(), - indexes_last_updated: 0, - collateral_total_scaled: Uint128::zero(), - debt_total_scaled: Uint128::zero(), - liquidation_threshold: Decimal::one(), - liquidation_bonus: Decimal::zero(), - interest_rate_model: InterestRateModel::default(), - deposit_enabled: true, - borrow_enabled: true, - // By default the cap should be unlimited (no cap) - deposit_cap: Uint128::MAX, - } - } -} - -impl Market { - pub fn validate(&self) -> Result<(), MarsError> { - decimal_param_lt_one(self.reserve_factor, "reserve_factor")?; - decimal_param_le_one(self.max_loan_to_value, "max_loan_to_value")?; - decimal_param_le_one(self.liquidation_threshold, "liquidation_threshold")?; - decimal_param_le_one(self.liquidation_bonus, "liquidation_bonus")?; - - // liquidation_threshold should be greater than max_loan_to_value - if self.liquidation_threshold <= self.max_loan_to_value { - return Err(MarsError::InvalidParam { - param_name: "liquidation_threshold".to_string(), - invalid_value: self.liquidation_threshold.to_string(), - predicate: format!("> {} (max LTV)", self.max_loan_to_value), - }); - } - - self.interest_rate_model.validate()?; - - Ok(()) - } - - pub fn update_interest_rates(&mut self, current_utilization_rate: Decimal) -> StdResult<()> { - self.borrow_rate = self.interest_rate_model.get_borrow_rate(current_utilization_rate)?; - - self.liquidity_rate = self.interest_rate_model.get_liquidity_rate( - self.borrow_rate, - current_utilization_rate, - self.reserve_factor, - )?; - - Ok(()) - } - - pub fn increase_collateral(&mut self, amount_scaled: Uint128) -> StdResult<()> { - self.collateral_total_scaled = self.collateral_total_scaled.checked_add(amount_scaled)?; - Ok(()) - } - - pub fn increase_debt(&mut self, amount_scaled: Uint128) -> StdResult<()> { - self.debt_total_scaled = self.debt_total_scaled.checked_add(amount_scaled)?; - Ok(()) - } - - pub fn decrease_collateral(&mut self, amount_scaled: Uint128) -> StdResult<()> { - self.collateral_total_scaled = self.collateral_total_scaled.checked_sub(amount_scaled)?; - Ok(()) - } - - pub fn decrease_debt(&mut self, amount_scaled: Uint128) -> StdResult<()> { - self.debt_total_scaled = self.debt_total_scaled.checked_sub(amount_scaled)?; - Ok(()) - } -} diff --git a/packages/outpost/src/red_bank/mod.rs b/packages/outpost/src/red_bank/mod.rs deleted file mode 100644 index cc1a55a51..000000000 --- a/packages/outpost/src/red_bank/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod interest_rate_model; -mod market; -mod msg; -mod types; - -pub use interest_rate_model::*; -pub use market::*; -pub use msg::*; -pub use types::*; diff --git a/packages/outpost/src/red_bank/msg.rs b/packages/outpost/src/red_bank/msg.rs deleted file mode 100644 index 73d4cd7a5..000000000 --- a/packages/outpost/src/red_bank/msg.rs +++ /dev/null @@ -1,251 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Decimal, Uint128}; -use mars_owner::OwnerUpdate; - -use crate::red_bank::InterestRateModel; - -#[cw_serde] -pub struct InstantiateMsg { - /// Contract's owner - pub owner: String, - /// Contract's emergency owner - pub emergency_owner: String, - /// Market configuration - pub config: CreateOrUpdateConfig, -} - -#[cw_serde] -#[allow(clippy::large_enum_variant)] -pub enum ExecuteMsg { - /// Manages owner state - UpdateOwner(OwnerUpdate), - - /// Manages emergency owner state - UpdateEmergencyOwner(OwnerUpdate), - - /// Update contract config (only owner can call) - UpdateConfig { - config: CreateOrUpdateConfig, - }, - - /// Initialize an asset on the money market (only owner can call) - InitAsset { - /// Asset related info - denom: String, - /// Asset parameters - params: InitOrUpdateAssetParams, - }, - - /// Update an asset on the money market (only owner can call) - UpdateAsset { - /// Asset related info - denom: String, - /// Asset parameters - params: InitOrUpdateAssetParams, - }, - - /// Update uncollateralized loan limit for a given user and asset. - /// Overrides previous value if any. A limit of zero means no - /// uncollateralized limit and the debt in that asset needs to be - /// collateralized (only owner can call) - UpdateUncollateralizedLoanLimit { - /// Address that receives the credit - user: String, - /// Asset the user receives the credit in - denom: String, - /// Limit for the uncolateralize loan. - new_limit: Uint128, - }, - - /// Deposit native coins. Deposited coins must be sent in the transaction - /// this call is made - Deposit { - /// Address that will receive the maTokens - on_behalf_of: Option, - }, - - /// Withdraw an amount of the asset burning an equivalent amount of maTokens. - Withdraw { - /// Asset to withdraw - denom: String, - /// Amount to be withdrawn. If None is specified, the full amount will be withdrawn. - amount: Option, - /// The address where the withdrawn amount is sent - recipient: Option, - }, - - /// Borrow native coins. If borrow allowed, amount is added to caller's debt - /// and sent to the address. - Borrow { - /// Asset to borrow - denom: String, - /// Amount to borrow - amount: Uint128, - /// The address where the borrowed amount is sent - recipient: Option, - }, - - /// Repay native coins loan. Coins used to repay must be sent in the - /// transaction this call is made. - Repay { - /// Repay the funds for the user - on_behalf_of: Option, - }, - - /// Liquidate under-collateralized native loans. Coins used to repay must be sent in the - /// transaction this call is made. - /// - /// The liquidator will receive collateral shares. To get the underlying asset, consider sending - /// a separate `withdraw` execute message. - Liquidate { - /// The address of the borrower getting liquidated - user: String, - /// Denom of the collateral asset, which liquidator gets from the borrower - collateral_denom: String, - /// The address for receiving underlying collateral - recipient: Option, - }, - - /// Update (enable / disable) asset as collateral for the caller - UpdateAssetCollateralStatus { - /// Asset to update status for - denom: String, - /// Option to enable (true) / disable (false) asset as collateral - enable: bool, - }, -} - -#[cw_serde] -pub struct CreateOrUpdateConfig { - pub address_provider: Option, - pub close_factor: Option, -} - -#[cw_serde] -pub struct InitOrUpdateAssetParams { - /// Portion of the borrow rate that is kept as protocol rewards - pub reserve_factor: Option, - /// Max uusd that can be borrowed per uusd of collateral when using the asset as collateral - pub max_loan_to_value: Option, - /// uusd amount in debt position per uusd of asset collateral that if surpassed makes the user's position liquidatable. - pub liquidation_threshold: Option, - /// Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral - /// from user in an amount equal to debt repayed + bonus) - pub liquidation_bonus: Option, - - /// Interest rate strategy to calculate borrow_rate and liquidity_rate - pub interest_rate_model: Option, - - /// If false cannot deposit - pub deposit_enabled: Option, - /// If false cannot borrow - pub borrow_enabled: Option, - /// Deposit Cap defined in terms of the asset (Unlimited by default) - pub deposit_cap: Option, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Get config - #[returns(crate::red_bank::ConfigResponse)] - Config {}, - - /// Get asset market - #[returns(crate::red_bank::Market)] - Market { - denom: String, - }, - - /// Enumerate markets with pagination - #[returns(Vec)] - Markets { - start_after: Option, - limit: Option, - }, - - /// Get uncollateralized limit for given user and asset - #[returns(crate::red_bank::UncollateralizedLoanLimitResponse)] - UncollateralizedLoanLimit { - user: String, - denom: String, - }, - - /// Get all uncollateralized limits for a given user - #[returns(Vec)] - UncollateralizedLoanLimits { - user: String, - start_after: Option, - limit: Option, - }, - - /// Get user debt position for a specific asset - #[returns(crate::red_bank::UserDebtResponse)] - UserDebt { - user: String, - denom: String, - }, - - /// Get all debt positions for a user - #[returns(Vec)] - UserDebts { - user: String, - start_after: Option, - limit: Option, - }, - - /// Get user collateral position for a specific asset - #[returns(crate::red_bank::UserCollateralResponse)] - UserCollateral { - user: String, - denom: String, - }, - - /// Get all collateral positions for a user - #[returns(Vec)] - UserCollaterals { - user: String, - start_after: Option, - limit: Option, - }, - - /// Get user position - #[returns(crate::red_bank::UserPositionResponse)] - UserPosition { - user: String, - }, - - /// Get liquidity scaled amount for a given underlying asset amount. - /// (i.e: how much scaled collateral is added if the given amount is deposited) - #[returns(Uint128)] - ScaledLiquidityAmount { - denom: String, - amount: Uint128, - }, - - /// Get equivalent scaled debt for a given underlying asset amount. - /// (i.e: how much scaled debt is added if the given amount is borrowed) - #[returns(Uint128)] - ScaledDebtAmount { - denom: String, - amount: Uint128, - }, - - /// Get underlying asset amount for a given asset and scaled amount. - /// (i.e. How much underlying asset will be released if withdrawing by burning a given scaled - /// collateral amount stored in state.) - #[returns(Uint128)] - UnderlyingLiquidityAmount { - denom: String, - amount_scaled: Uint128, - }, - - /// Get underlying debt amount for a given asset and scaled amounts. - /// (i.e: How much underlying asset needs to be repaid to cancel a given scaled debt - /// amount stored in state) - #[returns(Uint128)] - UnderlyingDebtAmount { - denom: String, - amount_scaled: Uint128, - }, -} diff --git a/packages/outpost/src/red_bank/types.rs b/packages/outpost/src/red_bank/types.rs deleted file mode 100644 index 39fe542f6..000000000 --- a/packages/outpost/src/red_bank/types.rs +++ /dev/null @@ -1,128 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Decimal, Uint128}; - -use crate::{error::MarsError, helpers::decimal_param_le_one}; - -/// Global configuration -#[cw_serde] -pub struct Config { - /// Address provider returns addresses for all protocol contracts - pub address_provider: T, - /// Maximum percentage of outstanding debt that can be covered by a liquidator - pub close_factor: Decimal, -} - -impl Config { - pub fn validate(&self) -> Result<(), MarsError> { - decimal_param_le_one(self.close_factor, "close_factor")?; - Ok(()) - } -} - -#[cw_serde] -#[derive(Default)] -pub struct Collateral { - /// Scaled collateral amount - pub amount_scaled: Uint128, - /// Whether this asset is enabled as collateral - /// - /// Set to true by default, unless the user explicitly disables it by invoking the - /// `update_asset_collateral_status` execute method. - /// - /// If disabled, the asset will not be subject to liquidation, but will not be considered when - /// evaluting the user's health factor either. - pub enabled: bool, -} - -/// Debt for each asset and user -#[cw_serde] -#[derive(Default)] -pub struct Debt { - /// Scaled debt amount - pub amount_scaled: Uint128, - /// Marker for uncollateralized debt - pub uncollateralized: bool, -} - -#[cw_serde] -pub enum UserHealthStatus { - NotBorrowing, - Borrowing { - max_ltv_hf: Decimal, - liq_threshold_hf: Decimal, - }, -} - -/// User asset settlement -#[derive(Default, Debug)] -pub struct Position { - pub denom: String, - pub collateral_amount: Uint128, - pub debt_amount: Uint128, - pub uncollateralized_debt: bool, - pub max_ltv: Decimal, - pub liquidation_threshold: Decimal, - pub asset_price: Decimal, -} - -#[cw_serde] -pub struct ConfigResponse { - /// The contract's owner - pub owner: Option, - /// The contract's proposed owner - pub proposed_new_owner: Option, - /// The contract's emergency owner - pub emergency_owner: Option, - /// The contract's proposed emergency owner - pub proposed_new_emergency_owner: Option, - /// Address provider returns addresses for all protocol contracts - pub address_provider: String, - /// Maximum percentage of outstanding debt that can be covered by a liquidator - pub close_factor: Decimal, -} - -#[cw_serde] -pub struct UncollateralizedLoanLimitResponse { - /// Asset denom - pub denom: String, - /// Uncollateralized loan limit in this asset - pub limit: Uint128, -} - -#[cw_serde] -pub struct UserDebtResponse { - /// Asset denom - pub denom: String, - /// Scaled debt amount stored in contract state - pub amount_scaled: Uint128, - /// Underlying asset amount that is actually owed at the current block - pub amount: Uint128, - /// Marker for uncollateralized debt - pub uncollateralized: bool, -} - -#[cw_serde] -pub struct UserCollateralResponse { - /// Asset denom - pub denom: String, - /// Scaled collateral amount stored in contract state - pub amount_scaled: Uint128, - /// Underlying asset amount that is actually deposited at the current block - pub amount: Uint128, - /// Wether the user is using asset as collateral or not - pub enabled: bool, -} - -#[cw_serde] -pub struct UserPositionResponse { - /// Total value of all enabled collateral assets. - /// If an asset is disabled as collateral, it will not be included in this value. - pub total_enabled_collateral: Uint128, - /// Total value of all collateralized debts. - /// If the user has an uncollateralized loan limit in an asset, the debt in this asset will not - /// be included in this value. - pub total_collateralized_debt: Uint128, - pub weighted_max_ltv_collateral: Uint128, - pub weighted_liquidation_threshold_collateral: Uint128, - pub health_status: UserHealthStatus, -} diff --git a/packages/outpost/src/rewards_collector.rs b/packages/outpost/src/rewards_collector.rs deleted file mode 100644 index 1ef1be1dc..000000000 --- a/packages/outpost/src/rewards_collector.rs +++ /dev/null @@ -1,218 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Api, Decimal, StdResult, Uint128}; -use mars_owner::OwnerUpdate; - -use crate::{ - error::MarsError, - helpers::{decimal_param_le_one, integer_param_gt_zero, validate_native_denom}, -}; - -const MAX_SLIPPAGE_TOLERANCE_PERCENTAGE: u64 = 50; - -#[cw_serde] -pub struct InstantiateMsg { - /// The contract's owner - pub owner: String, - /// Address provider returns addresses for all protocol contracts - pub address_provider: String, - /// Percentage of fees that are sent to the safety fund - pub safety_tax_rate: Decimal, - /// The asset to which the safety fund share is converted - pub safety_fund_denom: String, - /// The asset to which the fee collector share is converted - pub fee_collector_denom: String, - /// The channel ID of the mars hub - pub channel_id: String, - /// Revision number of Mars Hub's IBC client - pub timeout_revision: u64, - /// Number of blocks after which an IBC transfer is to be considered failed, if no acknowledgement is received - pub timeout_blocks: u64, - /// Number of seconds after which an IBC transfer is to be considered failed, if no acknowledgement is received - pub timeout_seconds: u64, - /// Maximum percentage of price movement (minimum amount you accept to receive during swap) - pub slippage_tolerance: Decimal, -} - -#[cw_serde] -pub struct Config { - /// Address provider returns addresses for all protocol contracts - pub address_provider: Addr, - /// Percentage of fees that are sent to the safety fund - pub safety_tax_rate: Decimal, - /// The asset to which the safety fund share is converted - pub safety_fund_denom: String, - /// The asset to which the fee collector share is converted - pub fee_collector_denom: String, - /// The channel ID of the mars hub - pub channel_id: String, - /// Revision number of Mars Hub's IBC client - pub timeout_revision: u64, - /// Number of blocks after which an IBC transfer is to be considered failed, if no acknowledgement is received - pub timeout_blocks: u64, - /// Number of seconds after which an IBC transfer is to be considered failed, if no acknowledgement is received - pub timeout_seconds: u64, - /// Maximum percentage of price movement (minimum amount you accept to receive during swap) - pub slippage_tolerance: Decimal, -} - -impl Config { - pub fn validate(&self) -> Result<(), MarsError> { - decimal_param_le_one(self.safety_tax_rate, "safety_tax_rate")?; - - integer_param_gt_zero(self.timeout_revision, "timeout_revision")?; - integer_param_gt_zero(self.timeout_blocks, "timeout_blocks")?; - integer_param_gt_zero(self.timeout_seconds, "timeout_seconds")?; - - if self.slippage_tolerance > Decimal::percent(MAX_SLIPPAGE_TOLERANCE_PERCENTAGE) { - return Err(MarsError::InvalidParam { - param_name: "slippage_tolerance".to_string(), - invalid_value: self.slippage_tolerance.to_string(), - predicate: format!("<= {}", Decimal::percent(MAX_SLIPPAGE_TOLERANCE_PERCENTAGE)), - }); - } - - validate_native_denom(&self.safety_fund_denom)?; - validate_native_denom(&self.fee_collector_denom)?; - - Ok(()) - } -} - -impl Config { - pub fn checked(api: &dyn Api, msg: InstantiateMsg) -> StdResult { - Ok(Config { - address_provider: api.addr_validate(&msg.address_provider)?, - safety_tax_rate: msg.safety_tax_rate, - safety_fund_denom: msg.safety_fund_denom, - fee_collector_denom: msg.fee_collector_denom, - channel_id: msg.channel_id, - timeout_revision: msg.timeout_revision, - timeout_blocks: msg.timeout_blocks, - timeout_seconds: msg.timeout_seconds, - slippage_tolerance: msg.slippage_tolerance, - }) - } -} - -#[cw_serde] -#[derive(Default)] -pub struct UpdateConfig { - /// Address provider returns addresses for all protocol contracts - pub address_provider: Option, - /// Percentage of fees that are sent to the safety fund - pub safety_tax_rate: Option, - /// The asset to which the safety fund share is converted - pub safety_fund_denom: Option, - /// The asset to which the fee collector share is converted - pub fee_collector_denom: Option, - /// The channel id of the mars hub - pub channel_id: Option, - /// Revision number of Mars Hub's IBC light client - pub timeout_revision: Option, - /// Number of blocks after which an IBC transfer is to be considered failed, if no acknowledgement is received - pub timeout_blocks: Option, - /// Number of seconds after which an IBC transfer is to be considered failed, if no acknowledgement is received - pub timeout_seconds: Option, - /// Maximum percentage of price movement (minimum amount you accept to receive during swap) - pub slippage_tolerance: Option, -} - -#[cw_serde] -pub enum ExecuteMsg { - /// Manages admin role state - UpdateOwner(OwnerUpdate), - - /// Update contract config - UpdateConfig { - new_cfg: UpdateConfig, - }, - - /// Configure the route for swapping an asset - /// - /// This is chain-specific, and can include parameters such as slippage tolerance and the routes - /// for multi-step swaps - SetRoute { - denom_in: String, - denom_out: String, - route: Route, - }, - - /// Withdraw maTokens from the red bank - WithdrawFromRedBank { - denom: String, - amount: Option, - }, - - /// Distribute the accrued protocol income between the safety fund and the fee modules on mars hub, - /// according to the split set in config. - /// Callable by any address. - DistributeRewards { - denom: String, - amount: Option, - }, - - /// Swap any asset on the contract - SwapAsset { - denom: String, - amount: Option, - }, -} - -#[cw_serde] -pub struct ConfigResponse { - /// The contract's owner - pub owner: Option, - /// The contract's proposed owner - pub proposed_new_owner: Option, - /// Address provider returns addresses for all protocol contracts - pub address_provider: String, - /// Percentage of fees that are sent to the safety fund - pub safety_tax_rate: Decimal, - /// The asset to which the safety fund share is converted - pub safety_fund_denom: String, - /// The asset to which the fee collector share is converted - pub fee_collector_denom: String, - /// The channel ID of the mars hub - pub channel_id: String, - /// Revision number of Mars Hub's IBC client - pub timeout_revision: u64, - /// Number of blocks after which an IBC transfer is to be considered failed, if no acknowledgement is received - pub timeout_blocks: u64, - /// Number of seconds after which an IBC transfer is to be considered failed, if no acknowledgement is received - pub timeout_seconds: u64, - /// Maximum percentage of price movement (minimum amount you accept to receive during swap) - pub slippage_tolerance: Decimal, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Get config parameters - #[returns(ConfigResponse)] - Config {}, - /// Get routes for swapping an input denom into an output denom. - /// - /// NOTE: The response type of this query is chain-specific. - #[returns(RouteResponse)] - Route { - denom_in: String, - denom_out: String, - }, - /// Enumerate all swap routes. - /// - /// NOTE: The response type of this query is chain-specific. - #[returns(Vec>)] - Routes { - start_after: Option<(String, String)>, - limit: Option, - }, -} - -#[cw_serde] -pub struct RouteResponse { - pub denom_in: String, - pub denom_out: String, - pub route: Route, -} - -pub type RoutesResponse = Vec>; diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index cdf5ed7e5..1669a7c2f 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -27,7 +27,7 @@ cw-storage-plus = { workspace = true } cw-utils = { workspace = true } mars-health = { workspace = true } mars-math = { workspace = true } -mars-outpost = { workspace = true } +mars-red-bank-types = { workspace = true } mars-owner = { workspace = true } schemars = { workspace = true } serde = { workspace = true } diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs index 2a14d6c81..d95bb96f9 100644 --- a/packages/rover/src/adapters/oracle.rs +++ b/packages/rover/src/adapters/oracle.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, Coin, QuerierWrapper, StdResult, Uint128}; use mars_math::FractionMath; -use mars_outpost::oracle::{PriceResponse, QueryMsg}; +use mars_red_bank_types::oracle::{PriceResponse, QueryMsg}; use crate::error::ContractResult; diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index bcd3b34c6..8fd832b13 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{ to_binary, Addr, Api, Coin, CosmosMsg, QuerierWrapper, QueryRequest, StdResult, Uint128, WasmMsg, WasmQuery, }; -use mars_outpost::{red_bank, red_bank::Market}; +use mars_red_bank_types::{red_bank, red_bank::Market}; #[cw_serde] pub struct RedBankBase(T); diff --git a/schemas/mars-mock-red-bank/mars-mock-red-bank.json b/schemas/mars-mock-red-bank/mars-mock-red-bank.json index 00a10784e..314bff7d0 100644 --- a/schemas/mars-mock-red-bank/mars-mock-red-bank.json +++ b/schemas/mars-mock-red-bank/mars-mock-red-bank.json @@ -213,7 +213,7 @@ "type": "object", "properties": { "on_behalf_of": { - "description": "Address that will receive the maTokens", + "description": "Address that will receive the coins", "type": [ "string", "null" @@ -226,7 +226,7 @@ "additionalProperties": false }, { - "description": "Withdraw an amount of the asset burning an equivalent amount of maTokens.", + "description": "Withdraw native coins", "type": "object", "required": [ "withdraw" From a86cd387bbbedae83ee2971b30d956ace37ccaa3 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 1 Feb 2023 12:45:56 +0100 Subject: [PATCH 136/218] Replace enumerations with cw-paginate (#110) * Replace enumerations with cw-paginate * swapping field order to prevent clone --- Cargo.lock | 13 ++ Cargo.toml | 1 + contracts/credit-manager/Cargo.toml | 1 + contracts/credit-manager/src/query.rs | 160 ++++++++----------------- contracts/swapper/base/Cargo.toml | 1 + contracts/swapper/base/src/contract.rs | 25 ++-- 6 files changed, 77 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8a218066..45f9819b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -461,6 +461,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-paginate" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9ef244714397f68fa66de13ea6fefbc54d991f12da41ae076f92887aa893ed" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "serde", +] + [[package]] name = "cw-storage-plus" version = "0.16.0" @@ -1031,6 +1042,7 @@ dependencies = [ "cosmwasm-vault-standard", "cw-item-set", "cw-multi-test", + "cw-paginate", "cw-storage-plus 1.0.1", "cw-utils 0.16.0", "cw2 1.0.1", @@ -1174,6 +1186,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "cw-paginate", "cw-storage-plus 1.0.1", "mars-owner", "mars-rover", diff --git a/Cargo.toml b/Cargo.toml index c1a43e666..b21715e92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ cw721 = "0.16.0" cw721-base = { version = "0.16.0", features = ["library"] } cw-item-set = { version = "0.7.0", default-features = false, features = ["iterator"] } cw-multi-test = "0.16.1" +cw-paginate = "0.2.0" cw-utils = "0.16.0" cw-storage-plus = "1.0.1" itertools = "0.10.5" diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 75bb2bb27..71a572193 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -26,6 +26,7 @@ cw2 = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true } cw-item-set = { workspace = true } +cw-paginate = { workspace = true } cw-storage-plus = { workspace = true } mars-math = { workspace = true } mars-health = { workspace = true } diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index c67be6029..c18ddfa46 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,4 +1,5 @@ use cosmwasm_std::{Addr, Coin, Deps, Env, Order, StdResult, Uint128}; +use cw_paginate::paginate_map; use cw_storage_plus::Bound; use mars_rover::{ adapters::vault::{Vault, VaultBase, VaultPosition, VaultUnchecked}, @@ -56,19 +57,13 @@ pub fn query_all_coin_balances( let start = start_after .as_ref() .map(|(account_id, denom)| Bound::exclusive((account_id.as_str(), denom.as_str()))); - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - - Ok(COIN_BALANCES - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .collect::>>()? - .iter() - .map(|((account_id, denom), amount)| CoinBalanceResponseItem { - account_id: account_id.to_string(), - denom: denom.to_string(), - amount: *amount, + paginate_map(&COIN_BALANCES, deps.storage, start, limit, |(account_id, denom), amount| { + Ok(CoinBalanceResponseItem { + account_id, + denom, + amount, }) - .collect()) + }) } fn query_lent_amounts(deps: Deps, env: &Env, account_id: &str) -> ContractResult> { @@ -125,19 +120,13 @@ pub fn query_all_debt_shares( let start = start_after .as_ref() .map(|(account_id, denom)| Bound::exclusive((account_id.as_str(), denom.as_str()))); - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - - Ok(DEBT_SHARES - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .collect::>>()? - .iter() - .map(|((account_id, denom), shares)| SharesResponseItem { - account_id: account_id.to_string(), - denom: denom.to_string(), - shares: *shares, + paginate_map(&DEBT_SHARES, deps.storage, start, limit, |(account_id, denom), shares| { + Ok(SharesResponseItem { + account_id, + denom, + shares, }) - .collect()) + }) } pub fn query_all_lent_shares( @@ -148,20 +137,13 @@ pub fn query_all_lent_shares( let start = start_after .as_ref() .map(|(account_id, denom)| Bound::exclusive((account_id.as_str(), denom.as_str()))); - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - - LENT_SHARES - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|item| { - let ((account_id, denom), shares) = item?; - Ok(SharesResponseItem { - account_id, - denom, - shares, - }) + paginate_map(&LENT_SHARES, deps.storage, start, limit, |(account_id, denom), shares| { + Ok(SharesResponseItem { + account_id, + denom, + shares, }) - .collect() + }) } pub fn query_vaults_info( @@ -178,26 +160,18 @@ pub fn query_vaults_info( } None => None, }; - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - - VAULT_CONFIGS - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|res| { - let (addr, config) = res?; - let vault = VaultBase::new(addr); - Ok(VaultInfoResponse { - vault: vault.clone().into(), - config, - utilization: vault_utilization_in_deposit_cap_denom( - &deps, - &vault, - &env.contract.address, - )?, - }) + paginate_map(&VAULT_CONFIGS, deps.storage, start, limit, |addr, config| { + let vault = VaultBase::new(addr); + Ok(VaultInfoResponse { + config, + utilization: vault_utilization_in_deposit_cap_denom( + &deps, + &vault, + &env.contract.address, + )?, + vault: vault.into(), }) - .collect() + }) } pub fn query_vault_positions(deps: Deps, account_id: &str) -> ContractResult> { @@ -226,22 +200,15 @@ pub fn query_all_vault_positions( } None => None, }; - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - - Ok(VAULT_POSITIONS - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .collect::>>()? - .iter() - .map(|((account_id, addr), amount)| VaultPositionResponseItem { - account_id: account_id.clone(), + paginate_map(&VAULT_POSITIONS, deps.storage, start, limit, |(account_id, addr), amount| { + Ok(VaultPositionResponseItem { + account_id, position: VaultPosition { - vault: VaultBase::new(addr.clone()), - amount: amount.clone(), + vault: VaultBase::new(addr), + amount, }, }) - .collect()) + }) } /// NOTE: This implementation of the query function assumes the map `ALLOWED_COINS` only saves `Empty`. @@ -283,19 +250,12 @@ pub fn query_all_total_debt_shares( limit: Option, ) -> StdResult> { let start = start_after.as_ref().map(|denom| Bound::exclusive(denom.as_str())); - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - - Ok(TOTAL_DEBT_SHARES - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .collect::>>()? - .iter() - .map(|(denom, shares)| DebtShares { - denom: denom.to_string(), - shares: *shares, + paginate_map(&TOTAL_DEBT_SHARES, deps.storage, start, limit, |denom, shares| { + Ok(DebtShares { + denom, + shares, }) - .collect()) + }) } pub fn query_all_total_lent_shares( @@ -304,18 +264,12 @@ pub fn query_all_total_lent_shares( limit: Option, ) -> StdResult> { let start = start_after.as_ref().map(|denom| Bound::exclusive(denom.as_str())); - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - TOTAL_LENT_SHARES - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|item| { - let (denom, shares) = item?; - Ok(LentShares { - denom, - shares, - }) + paginate_map(&TOTAL_LENT_SHARES, deps.storage, start, limit, |denom, shares| { + Ok(LentShares { + denom, + shares, }) - .collect() + }) } pub fn query_total_vault_coin_balance( @@ -341,20 +295,12 @@ pub fn query_all_total_vault_coin_balances( } None => None, }; - - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - - VAULT_CONFIGS - .keys(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|res| { - let addr = res?; - let vault = VaultBase::new(addr); - let balance = vault.query_balance(&deps.querier, rover_addr)?; - Ok(VaultWithBalance { - vault, - balance, - }) + paginate_map(&VAULT_CONFIGS, deps.storage, start, limit, |addr, _| { + let vault = VaultBase::new(addr); + let balance = vault.query_balance(&deps.querier, rover_addr)?; + Ok(VaultWithBalance { + vault, + balance, }) - .collect() + }) } diff --git a/contracts/swapper/base/Cargo.toml b/contracts/swapper/base/Cargo.toml index 202abf786..28ba3abfd 100644 --- a/contracts/swapper/base/Cargo.toml +++ b/contracts/swapper/base/Cargo.toml @@ -21,6 +21,7 @@ library = [] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw-paginate = { workspace = true } cw-storage-plus = { workspace = true } mars-rover = { workspace = true } mars-owner = { workspace = true } diff --git a/contracts/swapper/base/src/contract.rs b/contracts/swapper/base/src/contract.rs index 3d8dfad51..b9abee3db 100644 --- a/contracts/swapper/base/src/contract.rs +++ b/contracts/swapper/base/src/contract.rs @@ -2,8 +2,9 @@ use std::marker::PhantomData; use cosmwasm_std::{ to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, CustomMsg, CustomQuery, Decimal, Deps, - DepsMut, Env, MessageInfo, Order, Response, WasmMsg, + DepsMut, Env, MessageInfo, Response, WasmMsg, }; +use cw_paginate::paginate_map; use cw_storage_plus::{Bound, Map}; use mars_owner::{Owner, OwnerInit::SetInitialOwner, OwnerUpdate}; use mars_rover::{ @@ -16,9 +17,6 @@ use mars_rover::{ use crate::{ContractResult, Route}; -const DEFAULT_LIMIT: u32 = 5; -const MAX_LIMIT: u32 = 10; - pub struct SwapBase<'a, Q, M, R> where Q: CustomQuery, @@ -136,21 +134,14 @@ where start_after: Option<(String, String)>, limit: Option, ) -> ContractResult> { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; let start = start_after.map(Bound::exclusive); - - self.routes - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|item| { - let ((denom_in, denom_out), route) = item?; - Ok(RouteResponse { - denom_in, - denom_out, - route, - }) + paginate_map(&self.routes, deps.storage, start, limit, |(denom_in, denom_out), route| { + Ok(RouteResponse { + denom_in, + denom_out, + route, }) - .collect() + }) } fn estimate_exact_in_swap( From 67bc7bd3ee8a79a636f637616a5f6238614b2e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Rodr=C3=ADguez?= Date: Tue, 14 Feb 2023 11:07:42 -0300 Subject: [PATCH 137/218] Include denom in coin_lent attribute (#114) --- contracts/credit-manager/src/lend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/credit-manager/src/lend.rs b/contracts/credit-manager/src/lend.rs index a429b73e9..34b723d93 100644 --- a/contracts/credit-manager/src/lend.rs +++ b/contracts/credit-manager/src/lend.rs @@ -39,5 +39,5 @@ pub fn lend(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> ContractRe .add_attribute("action", "lend") .add_attribute("account_id", account_id) .add_attribute("lent_shares_added", lent_shares_to_add) - .add_attribute("coins_lent", coin.amount)) + .add_attribute("coin_lent", coin.to_string())) } From 8636f1cd6cd50362752f40c0d141196fd0571793 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 16 Feb 2023 18:50:35 +0100 Subject: [PATCH 138/218] [Pt. 1] New health contract + packages (#111) * New health contract + packages * Review updates --- Cargo.lock | 69 +- Cargo.toml | 85 +- contracts/account-nft/Cargo.toml | 1 + .../tests/helpers/health_responses.rs | 2 +- .../account-nft/tests/helpers/mock_env.rs | 2 +- .../tests/helpers/mock_env_builder.rs | 27 +- contracts/health/Cargo.toml | 42 + contracts/health/examples/schema.rs | 10 + contracts/health/src/compute.rs | 65 + contracts/health/src/contract.rs | 71 ++ contracts/health/src/lib.rs | 5 + contracts/health/src/querier.rs | 65 + contracts/health/src/state.rs | 6 + contracts/health/src/update_config.rs | 19 + .../health/tests/helpers/mock_contracts.rs | 47 + contracts/health/tests/helpers/mock_env.rs | 214 ++++ .../health/tests/helpers/mock_env_builder.rs | 241 ++++ contracts/health/tests/helpers/mod.rs | 5 + contracts/health/tests/test_compute_health.rs | 262 ++++ contracts/health/tests/test_update_config.rs | 49 + contracts/mock-credit-manager/Cargo.toml | 14 +- contracts/mock-credit-manager/src/contract.rs | 36 +- contracts/mock-credit-manager/src/execute.rs | 26 +- contracts/mock-credit-manager/src/msg.rs | 20 +- contracts/mock-credit-manager/src/query.rs | 36 +- contracts/mock-credit-manager/src/state.rs | 14 +- contracts/mock-red-bank/src/contract.rs | 6 +- contracts/mock-red-bank/src/execute.rs | 25 +- contracts/mock-vault/src/deposit.rs | 8 +- contracts/mock-vault/src/error.rs | 3 + contracts/mock-vault/src/query.rs | 2 +- packages/health-computer/Cargo.toml | 32 + packages/health-computer/examples/schema.rs | 17 + packages/health-computer/src/data_types.rs | 31 + .../health-computer/src/health_computer.rs | 201 +++ packages/health-computer/src/lib.rs | 4 + .../tests/helpers/mock_coin_info.rs | 74 ++ packages/health-computer/tests/helpers/mod.rs | 3 + .../tests/test_health_scenarios.rs | 1082 +++++++++++++++++ .../tests/test_input_validation.rs | 254 ++++ packages/health-types/Cargo.toml | 25 + packages/health-types/src/error.rs | 42 + packages/health-types/src/health.rs | 78 ++ packages/health-types/src/lib.rs | 7 + packages/health-types/src/msg.rs | 35 + packages/rover/src/adapters/vault/position.rs | 90 +- .../mars-mock-credit-manager.json | 313 +++++ .../MarsMockCreditManager.client.ts | 128 +- .../MarsMockCreditManager.message-composer.ts | 121 +- .../MarsMockCreditManager.react-query.ts | 95 +- .../MarsMockCreditManager.types.ts | 136 ++- 51 files changed, 4068 insertions(+), 177 deletions(-) create mode 100644 contracts/health/Cargo.toml create mode 100644 contracts/health/examples/schema.rs create mode 100644 contracts/health/src/compute.rs create mode 100644 contracts/health/src/contract.rs create mode 100644 contracts/health/src/lib.rs create mode 100644 contracts/health/src/querier.rs create mode 100644 contracts/health/src/state.rs create mode 100644 contracts/health/src/update_config.rs create mode 100644 contracts/health/tests/helpers/mock_contracts.rs create mode 100644 contracts/health/tests/helpers/mock_env.rs create mode 100644 contracts/health/tests/helpers/mock_env_builder.rs create mode 100644 contracts/health/tests/helpers/mod.rs create mode 100644 contracts/health/tests/test_compute_health.rs create mode 100644 contracts/health/tests/test_update_config.rs create mode 100644 packages/health-computer/Cargo.toml create mode 100644 packages/health-computer/examples/schema.rs create mode 100644 packages/health-computer/src/data_types.rs create mode 100644 packages/health-computer/src/health_computer.rs create mode 100644 packages/health-computer/src/lib.rs create mode 100644 packages/health-computer/tests/helpers/mock_coin_info.rs create mode 100644 packages/health-computer/tests/helpers/mod.rs create mode 100644 packages/health-computer/tests/test_health_scenarios.rs create mode 100644 packages/health-computer/tests/test_input_validation.rs create mode 100644 packages/health-types/Cargo.toml create mode 100644 packages/health-types/src/error.rs create mode 100644 packages/health-types/src/health.rs create mode 100644 packages/health-types/src/lib.rs create mode 100644 packages/health-types/src/msg.rs diff --git a/Cargo.lock b/Cargo.lock index 45f9819b8..773d95ee8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1029,6 +1029,7 @@ dependencies = [ "mars-health", "mars-mock-credit-manager", "mars-rover", + "mars-rover-health-types", "thiserror", ] @@ -1086,8 +1087,8 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.0.1", "cw-utils 0.16.0", - "mars-health", "mars-rover", + "mars-rover-health-types", "thiserror", ] @@ -1180,6 +1181,61 @@ dependencies = [ "thiserror", ] +[[package]] +name = "mars-rover-health" +version = "1.0.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-vault-standard", + "cw-multi-test", + "cw-storage-plus 1.0.1", + "cw-utils 0.16.0", + "cw2 1.0.1", + "mars-math", + "mars-mock-credit-manager", + "mars-mock-oracle", + "mars-mock-red-bank", + "mars-mock-vault", + "mars-owner", + "mars-red-bank-types", + "mars-rover", + "mars-rover-health-computer", + "mars-rover-health-types", + "thiserror", +] + +[[package]] +name = "mars-rover-health-computer" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-vault-standard", + "mars-math", + "mars-red-bank-types", + "mars-rover", + "mars-rover-health-types", + "schemars", + "serde", + "serde-wasm-bindgen", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "mars-rover-health-types" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "mars-math", + "mars-owner", + "mars-red-bank-types", + "thiserror", +] + [[package]] name = "mars-swapper-base" version = "1.0.0" @@ -1702,6 +1758,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_bytes" version = "0.11.8" diff --git a/Cargo.toml b/Cargo.toml index b21715e92..45a6112f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,24 @@ [workspace] members = [ - "contracts/account-nft", - "contracts/credit-manager", - "contracts/swapper/*", - "contracts/zapper/*", - "packages/health", - "packages/rover", - "packages/math", + # prod contracts + "contracts/account-nft", + "contracts/credit-manager", + "contracts/swapper/*", + "contracts/zapper/*", + "contracts/health", - # Mock contracts - "contracts/mock-oracle", - "contracts/mock-red-bank", - "contracts/mock-vault", - "contracts/mock-credit-manager", + # mock contracts + "contracts/mock-oracle", + "contracts/mock-red-bank", + "contracts/mock-vault", + "contracts/mock-credit-manager", + + # packages + "packages/health", # TODO: to delete + "packages/health-computer", + "packages/health-types", + "packages/rover", + "packages/math", ] [workspace.package] @@ -30,34 +36,39 @@ documentation = "https://docs.marsprotocol.io/" keywords = ["mars", "cosmos", "cosmwasm"] [workspace.dependencies] -anyhow = "1.0.66" -cosmwasm-schema = "1.1.9" -cosmwasm-std = "1.1.9" -cw2 = "1.0.0" -cw721 = "0.16.0" -cw721-base = { version = "0.16.0", features = ["library"] } -cw-item-set = { version = "0.7.0", default-features = false, features = ["iterator"] } -cw-multi-test = "0.16.1" -cw-paginate = "0.2.0" -cw-utils = "0.16.0" -cw-storage-plus = "1.0.1" -itertools = "0.10.5" -osmosis-std = "0.14.0" -osmosis-test-tube = "14.1.1" -schemars = "0.8.11" -serde = { version = "1.0.150", default-features = false, features = ["derive"] } -thiserror = "1.0.37" +anyhow = "1.0.66" +cosmwasm-schema = "1.1.9" +cosmwasm-std = "1.1.9" +cw2 = "1.0.0" +cw721 = "0.16.0" +cw721-base = { version = "0.16.0", features = ["library"] } +cw-item-set = { version = "0.7.0", default-features = false, features = ["iterator"] } +cw-multi-test = "0.16.1" +cw-paginate = "0.2.0" +cw-utils = "0.16.0" +cw-storage-plus = "1.0.1" +itertools = "0.10.5" +osmosis-std = "0.14.0" +osmosis-test-tube = "14.1.1" +schemars = "0.8.11" +serde = { version = "1.0.150", default-features = false, features = ["derive"] } +serde_json = "1.0.91" +serde-wasm-bindgen = "0.4.5" +thiserror = "1.0.37" +wasm-bindgen = "0.2.83" # packages -cosmwasm-vault-standard = { version = "0.1.0", features = ["lockup", "force-unlock"] } +cosmwasm-vault-standard = { version = "0.1.0", features = ["lockup", "force-unlock"] } # FIXME: develop branch dependency till Apollo finish audit -cw-dex = { git = "https://github.com/apollodao/cw-dex", rev = "1ce5566963851f7cab0eed117240654f3cb2d78d", features = ["osmosis"] } -mars-health = { version = "1.0.0", path = "./packages/health" } -mars-math = { version = "1.0.0", path = "./packages/math" } -mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" } -mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank", rev = "3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" } -mars-owner = "1.0.0" -mars-rover = { version = "1.0.0", path = "./packages/rover" } +cw-dex = { git = "https://github.com/apollodao/cw-dex", rev = "1ce5566963851f7cab0eed117240654f3cb2d78d", features = ["osmosis"] } +mars-health = { version = "1.0.0", path = "./packages/health" } # TODO to delete +mars-rover-health-computer = { version = "1.0.0", path = "./packages/health-computer" } +mars-rover-health-types = { version = "1.0.0", path = "./packages/health-types" } +mars-math = { version = "1.0.0", path = "./packages/math" } +mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" } +mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank", rev = "3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" } +mars-owner = "1.0.0" +mars-rover = { version = "1.0.0", path = "./packages/rover" } # contracts mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 7c0b90be6..5e5fea6e1 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -32,4 +32,5 @@ thiserror = { workspace = true } [dev-dependencies] anyhow = { workspace = true } cw-multi-test = { workspace = true } +mars-rover-health-types = { workspace = true } mars-mock-credit-manager = { workspace = true } diff --git a/contracts/account-nft/tests/helpers/health_responses.rs b/contracts/account-nft/tests/helpers/health_responses.rs index c7824c1f7..bd87a3ad0 100644 --- a/contracts/account-nft/tests/helpers/health_responses.rs +++ b/contracts/account-nft/tests/helpers/health_responses.rs @@ -1,7 +1,7 @@ use std::ops::Sub; use cosmwasm_std::Uint128; -use mars_health::HealthResponse; +use mars_rover_health_types::HealthResponse; pub const MAX_VALUE_FOR_BURN: Uint128 = Uint128::new(1000); diff --git a/contracts/account-nft/tests/helpers/mock_env.rs b/contracts/account-nft/tests/helpers/mock_env.rs index 4189b9d68..6113afc84 100644 --- a/contracts/account-nft/tests/helpers/mock_env.rs +++ b/contracts/account-nft/tests/helpers/mock_env.rs @@ -2,13 +2,13 @@ use anyhow::Result as AnyResult; use cosmwasm_std::Addr; use cw721::OwnerOfResponse; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; -use mars_health::HealthResponse; use mars_mock_credit_manager::msg::ExecuteMsg::SetHealthResponse; use mars_rover::adapters::account_nft::{ ExecuteMsg, ExecuteMsg::{AcceptMinterRole, UpdateConfig}, NftConfigUpdates, QueryMsg, UncheckedNftConfig, }; +use mars_rover_health_types::HealthResponse; use crate::helpers::MockEnvBuilder; diff --git a/contracts/account-nft/tests/helpers/mock_env_builder.rs b/contracts/account-nft/tests/helpers/mock_env_builder.rs index 9411d9111..5cc5b832a 100644 --- a/contracts/account-nft/tests/helpers/mock_env_builder.rs +++ b/contracts/account-nft/tests/helpers/mock_env_builder.rs @@ -1,11 +1,15 @@ use std::mem::take; use anyhow::Result as AnyResult; -use cosmwasm_std::{Addr, Empty}; +use cosmwasm_std::Addr; use cw_multi_test::{BasicApp, Executor}; -use mars_rover::adapters::account_nft::{ - ExecuteMsg::{AcceptMinterRole, UpdateConfig}, - InstantiateMsg, NftConfigUpdates, +use mars_mock_credit_manager::msg::InstantiateMsg as MockCmInstantiateMsg; +use mars_rover::{ + adapters::account_nft::{ + ExecuteMsg::{AcceptMinterRole, UpdateConfig}, + InstantiateMsg, NftConfigUpdates, + }, + msg::query::ConfigResponse, }; use crate::helpers::{ @@ -78,7 +82,20 @@ impl MockEnvBuilder { .instantiate_contract( code_id, self.deployer.clone(), - &Empty {}, + // Will be removed on upcoming PR + &MockCmInstantiateMsg { + config: ConfigResponse { + owner: None, + proposed_new_owner: None, + account_nft: None, + red_bank: "".to_string(), + oracle: "".to_string(), + max_close_factor: Default::default(), + max_unlocking_positions: Default::default(), + swapper: "".to_string(), + zapper: "".to_string(), + }, + }, &[], "mock-credit-manager", None, diff --git a/contracts/health/Cargo.toml b/contracts/health/Cargo.toml new file mode 100644 index 000000000..ebe855036 --- /dev/null +++ b/contracts/health/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "mars-rover-health" +version = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw-storage-plus = { workspace = true } +mars-owner = { workspace = true } +mars-rover-health-computer = { workspace = true } +mars-rover-health-types = { workspace = true } +mars-rover = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +anyhow = { workspace = true } +cosmwasm-vault-standard = { workspace = true } +cw-multi-test = { workspace = true } +cw-utils = { workspace = true } +mars-math = { workspace = true } +mars-mock-credit-manager = { workspace = true } +mars-mock-oracle = { workspace = true } +mars-mock-red-bank = { workspace = true } +mars-mock-vault = { workspace = true } +mars-red-bank-types = { workspace = true } diff --git a/contracts/health/examples/schema.rs b/contracts/health/examples/schema.rs new file mode 100644 index 000000000..ccb459acd --- /dev/null +++ b/contracts/health/examples/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use mars_rover::adapters::account_nft::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/health/src/compute.rs b/contracts/health/src/compute.rs new file mode 100644 index 000000000..80e15f35a --- /dev/null +++ b/contracts/health/src/compute.rs @@ -0,0 +1,65 @@ +use std::collections::HashMap; + +use cosmwasm_std::{Deps, StdResult}; +use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; +use mars_rover_health_types::{HealthError::CreditManagerNotSet, HealthResponse, HealthResult}; + +use crate::{querier::HealthQuerier, state::CREDIT_MANAGER}; + +/// Uses `mars-rover-health-computer` which is a data agnostic package given +/// it's compiled to .wasm and shared with the frontend. +/// This function queries all necessary data to pass to `HealthComputer`. +pub fn compute_health(deps: Deps, account_id: &str) -> HealthResult { + let credit_manager_addr = + CREDIT_MANAGER.may_load(deps.storage)?.ok_or(CreditManagerNotSet {})?; + let querier = HealthQuerier::new(&deps.querier, &credit_manager_addr); + + let positions = querier.query_positions(account_id)?; + + // Get the denoms that need prices + markets + let deposit_denoms = positions.deposits.iter().map(|d| &d.denom).collect::>(); + let debt_denoms = positions.debts.iter().map(|d| &d.denom).collect::>(); + let vault_infos = positions + .vaults + .iter() + .map(|v| { + let info = v.vault.query_info(&deps.querier)?; + Ok((v.vault.address.clone(), info)) + }) + .collect::>>()?; + let vault_base_token_denoms = vault_infos.values().map(|v| &v.base_token).collect::>(); + + // Collect prices + markets + let (oracle, red_bank) = querier.query_deps()?; + let mut denoms_data: DenomsData = Default::default(); + deposit_denoms.into_iter().chain(debt_denoms).chain(vault_base_token_denoms).try_for_each( + |denom| -> StdResult<()> { + let price = oracle.query_price(&deps.querier, denom)?.price; + denoms_data.prices.insert(denom.clone(), price); + let market = red_bank.query_market(&deps.querier, denom)?; + denoms_data.markets.insert(denom.clone(), market); + Ok(()) + }, + )?; + + // Collect all vault data + let mut vaults_data: VaultsData = Default::default(); + positions.vaults.iter().try_for_each(|v| -> HealthResult<()> { + let vault_coin_value = v.query_values(&deps.querier, &oracle)?; + vaults_data.vault_values.insert(v.vault.address.clone(), vault_coin_value); + let config = querier.query_vault_config(&v.vault)?; + vaults_data.vault_configs.insert(v.vault.address.clone(), config); + Ok(()) + })?; + + let allowed_coins = querier.query_allowed_coins()?; + + let computer = HealthComputer { + positions, + denoms_data, + vaults_data, + allowed_coins, + }; + + Ok(computer.compute_health()?.into()) +} diff --git a/contracts/health/src/contract.rs b/contracts/health/src/contract.rs new file mode 100644 index 000000000..5ff368675 --- /dev/null +++ b/contracts/health/src/contract.rs @@ -0,0 +1,71 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; +use cw2::set_contract_version; +use mars_owner::OwnerInit::SetInitialOwner; +use mars_rover_health_types::{ConfigResponse, ExecuteMsg, HealthResult, InstantiateMsg, QueryMsg}; + +use crate::{ + compute::compute_health, + state::{CREDIT_MANAGER, OWNER}, + update_config::update_config, +}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _: Env, + _: MessageInfo, + msg: InstantiateMsg, +) -> HealthResult { + set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; + + OWNER.initialize( + deps.storage, + deps.api, + SetInitialOwner { + owner: msg.owner, + }, + )?; + + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> HealthResult { + match msg { + ExecuteMsg::UpdateOwner(update) => Ok(OWNER.update(deps, info, update)?), + ExecuteMsg::UpdateConfig { + credit_manager, + } => update_config(deps, info, credit_manager), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> HealthResult { + let res = match msg { + QueryMsg::Health { + account_id, + } => to_binary(&compute_health(deps, &account_id)?), + QueryMsg::Config {} => to_binary(&query_config(deps)?), + }; + res.map_err(Into::into) +} + +pub fn query_config(deps: Deps) -> HealthResult { + let credit_manager_addr = CREDIT_MANAGER.may_load(deps.storage)?.map(|a| a.into()); + let owner_response = OWNER.query(deps.storage)?; + + Ok(ConfigResponse { + credit_manager_addr, + owner_response, + }) +} diff --git a/contracts/health/src/lib.rs b/contracts/health/src/lib.rs new file mode 100644 index 000000000..b202a2fad --- /dev/null +++ b/contracts/health/src/lib.rs @@ -0,0 +1,5 @@ +pub mod compute; +pub mod contract; +pub mod querier; +pub mod state; +pub mod update_config; diff --git a/contracts/health/src/querier.rs b/contracts/health/src/querier.rs new file mode 100644 index 000000000..55ff6c35f --- /dev/null +++ b/contracts/health/src/querier.rs @@ -0,0 +1,65 @@ +use cosmwasm_std::{Addr, QuerierWrapper}; +use mars_rover::{ + adapters::{ + oracle::Oracle, + red_bank::RedBank, + vault::{Vault, VaultConfig}, + }, + msg::query::{ConfigResponse, Positions, QueryMsg, VaultInfoResponse}, +}; +use mars_rover_health_types::HealthResult; + +pub struct HealthQuerier<'a> { + querier: &'a QuerierWrapper<'a>, + credit_manager_addr: &'a Addr, +} + +impl<'a> HealthQuerier<'a> { + pub fn new(querier: &'a QuerierWrapper, credit_manager_addr: &'a Addr) -> Self { + Self { + querier, + credit_manager_addr, + } + } + + pub fn query_positions(&self, account_id: &str) -> HealthResult { + Ok(self.querier.query_wasm_smart( + self.credit_manager_addr.to_string(), + &QueryMsg::Positions { + account_id: account_id.to_string(), + }, + )?) + } + + pub fn query_deps(&self) -> HealthResult<(Oracle, RedBank)> { + let config: ConfigResponse = self + .querier + .query_wasm_smart(self.credit_manager_addr.to_string(), &QueryMsg::Config {})?; + Ok(( + Oracle::new(Addr::unchecked(config.oracle)), + RedBank::new(Addr::unchecked(config.red_bank)), + )) + } + + pub fn query_allowed_coins(&self) -> HealthResult> { + let allowed_coins: Vec = self.querier.query_wasm_smart( + self.credit_manager_addr.to_string(), + &QueryMsg::AllowedCoins { + start_after: None, + limit: None, + }, + )?; + Ok(allowed_coins) + } + + pub fn query_vault_config(&self, vault: &Vault) -> HealthResult { + let vaults_info: Vec = self.querier.query_wasm_smart( + self.credit_manager_addr.to_string(), + &QueryMsg::VaultsInfo { + start_after: Some(vault.into()), + limit: None, + }, + )?; + Ok(vaults_info.first().unwrap().clone().config) + } +} diff --git a/contracts/health/src/state.rs b/contracts/health/src/state.rs new file mode 100644 index 000000000..b255706b0 --- /dev/null +++ b/contracts/health/src/state.rs @@ -0,0 +1,6 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::Item; +use mars_owner::Owner; + +pub const OWNER: Owner = Owner::new("owner"); +pub const CREDIT_MANAGER: Item = Item::new("credit_manager"); diff --git a/contracts/health/src/update_config.rs b/contracts/health/src/update_config.rs new file mode 100644 index 000000000..c11487bf5 --- /dev/null +++ b/contracts/health/src/update_config.rs @@ -0,0 +1,19 @@ +use cosmwasm_std::{DepsMut, MessageInfo, Response}; +use mars_rover_health_types::HealthResult; + +use crate::state::{CREDIT_MANAGER, OWNER}; + +pub fn update_config( + deps: DepsMut, + info: MessageInfo, + credit_manager: String, +) -> HealthResult { + OWNER.assert_owner(deps.storage, &info.sender)?; + let validated = deps.api.addr_validate(&credit_manager)?; + CREDIT_MANAGER.save(deps.storage, &validated)?; + + Ok(Response::new() + .add_attribute("action", "update_config") + .add_attribute("key", "credit_manager_addr") + .add_attribute("value", credit_manager)) +} diff --git a/contracts/health/tests/helpers/mock_contracts.rs b/contracts/health/tests/helpers/mock_contracts.rs new file mode 100644 index 000000000..a8a86dd35 --- /dev/null +++ b/contracts/health/tests/helpers/mock_contracts.rs @@ -0,0 +1,47 @@ +use cosmwasm_std::Empty; +use cw_multi_test::{Contract, ContractWrapper}; + +pub fn mock_health_contract() -> Box> { + let contract = ContractWrapper::new( + mars_rover_health::contract::execute, + mars_rover_health::contract::instantiate, + mars_rover_health::contract::query, + ); + Box::new(contract) +} + +pub fn mock_credit_manager_contract() -> Box> { + let contract = ContractWrapper::new( + mars_mock_credit_manager::contract::execute, + mars_mock_credit_manager::contract::instantiate, + mars_mock_credit_manager::contract::query, + ); + Box::new(contract) +} + +pub fn mock_vault_contract() -> Box> { + let contract = ContractWrapper::new( + mars_mock_vault::contract::execute, + mars_mock_vault::contract::instantiate, + mars_mock_vault::contract::query, + ); + Box::new(contract) +} + +pub fn mock_oracle_contract() -> Box> { + let contract = ContractWrapper::new( + mars_mock_oracle::contract::execute, + mars_mock_oracle::contract::instantiate, + mars_mock_oracle::contract::query, + ); + Box::new(contract) +} + +pub fn mock_red_bank_contract() -> Box> { + let contract = ContractWrapper::new( + mars_mock_red_bank::contract::execute, + mars_mock_red_bank::contract::instantiate, + mars_mock_red_bank::contract::query, + ); + Box::new(contract) +} diff --git a/contracts/health/tests/helpers/mock_env.rs b/contracts/health/tests/helpers/mock_env.rs new file mode 100644 index 000000000..0aa4ff580 --- /dev/null +++ b/contracts/health/tests/helpers/mock_env.rs @@ -0,0 +1,214 @@ +use anyhow::Result as AnyResult; +use cosmwasm_std::{Addr, Coin, Decimal, Empty, StdResult, Uint128}; +use cosmwasm_vault_standard::{ + VaultInfoResponse, VaultStandardExecuteMsg::Deposit, VaultStandardQueryMsg::Info, +}; +use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; +use mars_mock_credit_manager::msg::ExecuteMsg::{ + SetAllowedCoins, SetPositionsResponse, SetVaultConfig, +}; +use mars_mock_oracle::msg::{CoinPrice, ExecuteMsg::ChangePrice}; +use mars_mock_red_bank::msg::CoinMarketInfo; +use mars_mock_vault::contract::STARTING_VAULT_SHARES; +use mars_red_bank_types::red_bank::{ExecuteMsg::UpdateAsset, InitOrUpdateAssetParams}; +use mars_rover::msg::{ + query::{Positions, VaultInfoResponse as CmVaultConfig}, + QueryMsg::VaultsInfo, +}; +use mars_rover_health_types::{ConfigResponse, ExecuteMsg::UpdateConfig, HealthResponse, QueryMsg}; + +use crate::helpers::MockEnvBuilder; + +pub struct MockEnv { + pub app: BasicApp, + pub deployer: Addr, + pub health_contract: Addr, + pub cm_contract: Addr, + pub vault_contract: Addr, + pub oracle: Addr, + pub red_bank: Addr, +} + +#[allow(clippy::new_ret_no_self)] +impl MockEnv { + pub fn new() -> MockEnvBuilder { + MockEnvBuilder { + app: App::default(), + deployer: Addr::unchecked("deployer"), + health_contract: None, + set_cm_config: true, + cm_contract: None, + vault_contract: None, + oracle: None, + red_bank: None, + } + } + + pub fn query_health(&self, account_id: &str) -> StdResult { + self.app.wrap().query_wasm_smart( + self.health_contract.clone(), + &QueryMsg::Health { + account_id: account_id.to_string(), + }, + ) + } + + pub fn query_config(&self) -> ConfigResponse { + self.app + .wrap() + .query_wasm_smart(self.health_contract.clone(), &QueryMsg::Config {}) + .unwrap() + } + + pub fn query_vault_configs(&self) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.cm_contract.clone(), + &VaultsInfo { + start_after: None, + limit: None, + }, + ) + .unwrap() + } + + pub fn update_config( + &mut self, + sender: &Addr, + credit_manager_addr: &Addr, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.health_contract.clone(), + &UpdateConfig { + credit_manager: credit_manager_addr.to_string(), + }, + &[], + ) + } + + pub fn set_positions_response(&mut self, account_id: &str, positions: &Positions) { + self.app + .execute_contract( + self.deployer.clone(), + self.cm_contract.clone(), + &SetPositionsResponse { + account_id: account_id.to_string(), + positions: positions.clone(), + }, + &[], + ) + .unwrap(); + } + + // Meant to ensure that the vault issues shares correctly to match the position response + pub fn deposit_into_vault(&mut self, base_token_amount: Uint128) { + let info: VaultInfoResponse = self + .app + .wrap() + .query_wasm_smart(self.vault_contract.clone(), &Info:: {}) + .unwrap(); + + let coin_to_deposit = Coin { + denom: info.base_token.clone(), + amount: base_token_amount, + }; + + // Seed deployer with vault tokens + self.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: self.deployer.clone().to_string(), + amount: vec![coin_to_deposit.clone()], + })) + .unwrap(); + + // Seed vault contract with vault tokens + self.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: self.vault_contract.clone().to_string(), + amount: vec![Coin { + denom: info.vault_token, + amount: STARTING_VAULT_SHARES, + }], + })) + .unwrap(); + + self.app + .execute_contract( + self.deployer.clone(), + self.vault_contract.clone(), + &Deposit:: { + amount: base_token_amount, + recipient: None, + }, + &[coin_to_deposit], + ) + .unwrap(); + } + + pub fn set_price(&mut self, denom: &str, price: Decimal) { + self.app + .execute_contract( + self.deployer.clone(), + self.oracle.clone(), + &ChangePrice(CoinPrice { + denom: denom.to_string(), + price, + }), + &[], + ) + .unwrap(); + } + + pub fn set_market(&mut self, denom: &str, market: &CoinMarketInfo) { + self.app + .execute_contract( + self.deployer.clone(), + self.red_bank.clone(), + &UpdateAsset { + denom: denom.to_string(), + params: InitOrUpdateAssetParams { + max_loan_to_value: Some(market.max_ltv), + liquidation_threshold: Some(market.liquidation_threshold), + liquidation_bonus: Some(market.liquidation_bonus), + reserve_factor: None, + interest_rate_model: None, + deposit_enabled: None, + borrow_enabled: None, + deposit_cap: None, + }, + }, + &[], + ) + .unwrap(); + } + + pub fn set_allowed_coins(&mut self, allowed_coins: &[String]) { + self.app + .execute_contract( + self.deployer.clone(), + self.cm_contract.clone(), + &SetAllowedCoins(allowed_coins.to_vec()), + &[], + ) + .unwrap(); + } + + pub fn vault_allowed(&mut self, allowed: bool) { + let mut config = self.query_vault_configs().first().unwrap().clone().config; + config.whitelisted = allowed; + + self.app + .execute_contract( + self.deployer.clone(), + self.cm_contract.clone(), + &SetVaultConfig { + address: self.vault_contract.to_string(), + config, + }, + &[], + ) + .unwrap(); + } +} diff --git a/contracts/health/tests/helpers/mock_env_builder.rs b/contracts/health/tests/helpers/mock_env_builder.rs new file mode 100644 index 000000000..a09041d71 --- /dev/null +++ b/contracts/health/tests/helpers/mock_env_builder.rs @@ -0,0 +1,241 @@ +use std::mem::take; + +use anyhow::Result as AnyResult; +use cosmwasm_std::{coin, Addr, Decimal}; +use cw_multi_test::{BasicApp, Executor}; +use cw_utils::Duration; +use mars_mock_credit_manager::msg::{ + ExecuteMsg::SetVaultConfig, InstantiateMsg as CmMockInstantiateMsg, +}; +use mars_mock_oracle::msg::InstantiateMsg as OracleInstantiateMsg; +use mars_mock_red_bank::msg::InstantiateMsg as RedBankInstantiateMsg; +use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; +use mars_rover::{ + adapters::{oracle::OracleUnchecked, vault::VaultConfig}, + msg::query::ConfigResponse, +}; +use mars_rover_health_types::{ExecuteMsg::UpdateConfig, InstantiateMsg}; + +use crate::helpers::{ + mock_credit_manager_contract, mock_health_contract, mock_oracle_contract, + mock_red_bank_contract, mock_vault_contract, MockEnv, +}; + +pub struct MockEnvBuilder { + pub app: BasicApp, + pub deployer: Addr, + pub health_contract: Option, + pub cm_contract: Option, + pub vault_contract: Option, + pub oracle: Option, + pub red_bank: Option, + pub set_cm_config: bool, +} + +impl MockEnvBuilder { + pub fn build(&mut self) -> AnyResult { + if self.set_cm_config { + self.add_cm_to_config(); + } + + Ok(MockEnv { + deployer: self.deployer.clone(), + health_contract: self.get_health_contract(), + vault_contract: self.get_vault_contract(), + oracle: self.get_oracle(), + red_bank: self.get_red_bank(), + cm_contract: self.get_cm_contract(), + app: take(&mut self.app), + }) + } + + pub fn skip_cm_config(&mut self) -> &mut Self { + self.set_cm_config = false; + self + } + + fn add_cm_to_config(&mut self) { + let health_contract = self.get_health_contract(); + let cm_contract = self.get_cm_contract(); + + self.app + .execute_contract( + self.deployer.clone(), + health_contract, + &UpdateConfig { + credit_manager: cm_contract.to_string(), + }, + &[], + ) + .unwrap(); + } + + fn get_oracle(&mut self) -> Addr { + if self.oracle.is_none() { + self.deploy_oracle() + } + self.oracle.clone().unwrap() + } + + fn deploy_oracle(&mut self) { + let contract = mock_oracle_contract(); + let code_id = self.app.store_code(contract); + + let addr = self + .app + .instantiate_contract( + code_id, + self.deployer.clone(), + &OracleInstantiateMsg { + prices: vec![], + }, + &[], + "mock-oracle", + None, + ) + .unwrap(); + self.oracle = Some(addr); + } + + fn get_red_bank(&mut self) -> Addr { + if self.red_bank.is_none() { + self.deploy_red_bank() + } + self.red_bank.clone().unwrap() + } + + fn deploy_red_bank(&mut self) { + let contract = mock_red_bank_contract(); + let code_id = self.app.store_code(contract); + + let addr = self + .app + .instantiate_contract( + code_id, + self.deployer.clone(), + &RedBankInstantiateMsg { + coins: vec![], + }, + &[], + "mock-red-bank", + None, + ) + .unwrap(); + self.red_bank = Some(addr); + } + + fn get_cm_contract(&mut self) -> Addr { + if self.cm_contract.is_none() { + self.deploy_cm_contract() + } + self.cm_contract.clone().unwrap() + } + + fn deploy_cm_contract(&mut self) { + let contract = mock_credit_manager_contract(); + let code_id = self.app.store_code(contract); + let red_bank = self.get_red_bank().to_string(); + let oracle = self.get_oracle().to_string(); + + let cm_addr = self + .app + .instantiate_contract( + code_id, + self.deployer.clone(), + &CmMockInstantiateMsg { + config: ConfigResponse { + owner: Some(self.deployer.to_string()), + red_bank, + oracle, + proposed_new_owner: None, + account_nft: None, + max_close_factor: Default::default(), + max_unlocking_positions: Default::default(), + swapper: "n/a".to_string(), + zapper: "n/a".to_string(), + }, + }, + &[], + "mock-credit-manager-contract", + Some(self.deployer.clone().into()), + ) + .unwrap(); + self.cm_contract = Some(cm_addr.clone()); + + // Set mock vault with a starting config + let vault = self.get_vault_contract().to_string(); + self.app + .execute_contract( + self.deployer.clone(), + cm_addr, + &SetVaultConfig { + address: vault, + config: VaultConfig { + deposit_cap: coin(10000000u128, "uusdc"), + max_ltv: Decimal::from_atomics(4u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(44u128, 2).unwrap(), + whitelisted: true, + }, + }, + &[], + ) + .unwrap(); + } + + fn get_vault_contract(&mut self) -> Addr { + if self.vault_contract.is_none() { + self.deploy_vault_contract() + } + self.vault_contract.clone().unwrap() + } + + fn deploy_vault_contract(&mut self) { + let contract = mock_vault_contract(); + let code_id = self.app.store_code(contract); + + let addr = self + .app + .instantiate_contract( + code_id, + self.deployer.clone(), + &VaultInstantiateMsg { + vault_token_denom: "vault_token_xyz".to_string(), + lockup: Some(Duration::Height(100)), + base_token_denom: "base_token_abc".to_string(), + oracle: OracleUnchecked::new("oracle_123".to_string()), + }, + &[], + "mock-vault", + None, + ) + .unwrap(); + self.vault_contract = Some(addr); + } + + fn get_health_contract(&mut self) -> Addr { + if self.health_contract.is_none() { + self.deploy_health_contract() + } + self.health_contract.clone().unwrap() + } + + fn deploy_health_contract(&mut self) { + let contract = mock_health_contract(); + let code_id = self.app.store_code(contract); + + let addr = self + .app + .instantiate_contract( + code_id, + self.deployer.clone(), + &InstantiateMsg { + owner: self.deployer.clone().into(), + }, + &[], + "mock-health-contract", + Some(self.deployer.clone().into()), + ) + .unwrap(); + self.health_contract = Some(addr); + } +} diff --git a/contracts/health/tests/helpers/mod.rs b/contracts/health/tests/helpers/mod.rs new file mode 100644 index 000000000..98f82a33a --- /dev/null +++ b/contracts/health/tests/helpers/mod.rs @@ -0,0 +1,5 @@ +pub use self::{mock_contracts::*, mock_env::*, mock_env_builder::*}; + +mod mock_contracts; +mod mock_env; +mod mock_env_builder; diff --git a/contracts/health/tests/test_compute_health.rs b/contracts/health/tests/test_compute_health.rs new file mode 100644 index 000000000..d97e2e5aa --- /dev/null +++ b/contracts/health/tests/test_compute_health.rs @@ -0,0 +1,262 @@ +use cosmwasm_std::{Coin, Decimal, StdError, Uint128}; +use mars_math::FractionMath; +use mars_mock_red_bank::msg::CoinMarketInfo; +use mars_rover::{ + adapters::vault::{ + LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultPosition, + VaultPositionAmount, VaultUnlockingPosition, + }, + msg::query::Positions, +}; + +use crate::helpers::MockEnv; + +pub mod helpers; + +#[test] +fn raises_when_credit_manager_not_set() { + let mock = MockEnv::new().skip_cm_config().build().unwrap(); + let err: StdError = mock.query_health("xyz").unwrap_err(); + assert_eq!( + err, + StdError::generic_err( + "Querier contract error: The credit manager address has not been set in config" + .to_string() + ) + ); +} + +#[test] +fn raises_with_non_existent_account_id() { + let mock = MockEnv::new().build().unwrap(); + let err: StdError = mock.query_health("xyz").unwrap_err(); + assert_eq!( + err, + StdError::generic_err( + "Querier contract error: Generic error: Querier contract error: mars_rover::msg::query::Positions not found" + .to_string() + ) + ); +} + +#[test] +fn computes_correct_position_with_zero_assets() { + let mut mock = MockEnv::new().build().unwrap(); + + let account_id = "123"; + mock.set_positions_response( + account_id, + &Positions { + account_id: account_id.to_string(), + deposits: vec![], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + ); + + let health = mock.query_health(account_id).unwrap(); + assert_eq!(health.total_debt_value, Uint128::zero()); + assert_eq!(health.total_collateral_value, Uint128::zero()); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::zero()); + assert_eq!(health.max_ltv_health_factor, None); + assert_eq!(health.liquidation_health_factor, None); + assert!(!health.liquidatable); + assert!(!health.above_max_ltv); +} + +// Testable via only unlocking positions +#[test] +fn adds_vault_base_denoms_to_oracle_and_red_bank() { + let mut mock = MockEnv::new().build().unwrap(); + + let vault_base_token = "base_token_abc"; + let account_id = "123"; + + let unlocking_amount = Uint128::new(22); + + mock.set_positions_response( + account_id, + &Positions { + account_id: account_id.to_string(), + deposits: vec![], + debts: vec![], + lends: vec![], + vaults: vec![VaultPosition { + vault: Vault::new(mock.vault_contract.clone()), + amount: VaultPositionAmount::Locking(LockingVaultAmount { + locked: VaultAmount::new(Uint128::zero()), + unlocking: UnlockingPositions::new(vec![VaultUnlockingPosition { + id: 1443, + coin: Coin { + denom: vault_base_token.to_string(), + amount: unlocking_amount, + }, + }]), + }), + }], + }, + ); + + mock.set_allowed_coins(&[vault_base_token.to_string()]); + + let max_ltv = Decimal::from_atomics(4523u128, 4).unwrap(); + let liquidation_threshold = Decimal::from_atomics(5u128, 1).unwrap(); + + mock.set_price(vault_base_token, Decimal::one()); + mock.set_market( + vault_base_token, + &CoinMarketInfo { + denom: vault_base_token.to_string(), + max_ltv, + liquidation_threshold, + liquidation_bonus: Decimal::from_atomics(9u128, 2).unwrap(), + }, + ); + + let health = mock.query_health(account_id).unwrap(); + assert_eq!(health.total_debt_value, Uint128::zero()); + assert_eq!(health.total_collateral_value, unlocking_amount); + assert_eq!( + health.max_ltv_adjusted_collateral, + unlocking_amount.checked_mul_floor(max_ltv).unwrap() + ); + assert_eq!( + health.liquidation_threshold_adjusted_collateral, + unlocking_amount.checked_mul_floor(liquidation_threshold).unwrap() + ); + assert_eq!(health.max_ltv_health_factor, None); + assert_eq!(health.liquidation_health_factor, None); + assert!(!health.liquidatable); + assert!(!health.above_max_ltv); +} + +#[test] +fn allowed_coins_work() { + let mut mock = MockEnv::new().build().unwrap(); + + mock.set_allowed_coins(&[]); + + let umars = "umars"; + mock.set_price(umars, Decimal::one()); + + let max_ltv = Decimal::from_atomics(4523u128, 4).unwrap(); + let liquidation_threshold = Decimal::from_atomics(5u128, 1).unwrap(); + + mock.set_market( + umars, + &CoinMarketInfo { + denom: umars.to_string(), + max_ltv, + liquidation_threshold, + liquidation_bonus: Decimal::from_atomics(9u128, 2).unwrap(), + }, + ); + + let deposit_amount = Uint128::new(30); + + let account_id = "123"; + mock.set_positions_response( + account_id, + &Positions { + account_id: account_id.to_string(), + deposits: vec![Coin { + denom: umars.to_string(), + amount: deposit_amount, + }], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + ); + + let health = mock.query_health(account_id).unwrap(); + assert_eq!(health.total_debt_value, Uint128::zero()); + assert_eq!(health.total_collateral_value, deposit_amount); // price of 1 + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); // coin not in whitelist + assert_eq!( + health.liquidation_threshold_adjusted_collateral, + deposit_amount.checked_mul_floor(liquidation_threshold).unwrap() + ); + assert_eq!(health.max_ltv_health_factor, None); + assert_eq!(health.liquidation_health_factor, None); + assert!(!health.liquidatable); + assert!(!health.above_max_ltv); + + // Add to whitelist + mock.set_allowed_coins(&[umars.to_string()]); + let health = mock.query_health(account_id).unwrap(); + // Now reflects deposit value + assert_eq!( + health.max_ltv_adjusted_collateral, + deposit_amount.checked_mul_floor(max_ltv).unwrap() + ); +} + +#[test] +fn vault_whitelist_affects_max_ltv() { + let mut mock = MockEnv::new().build().unwrap(); + + let vault_base_token = "base_token_abc"; + let account_id = "123"; + + let vault_token_amount = Uint128::new(1_000_000); + let base_token_amount = Uint128::new(100); + + mock.deposit_into_vault(base_token_amount); + + mock.set_positions_response( + account_id, + &Positions { + account_id: account_id.to_string(), + deposits: vec![], + debts: vec![], + lends: vec![], + vaults: vec![VaultPosition { + vault: Vault::new(mock.vault_contract.clone()), + amount: VaultPositionAmount::Unlocked(VaultAmount::new(vault_token_amount)), + }], + }, + ); + mock.set_allowed_coins(&[vault_base_token.to_string()]); + + let max_ltv = Decimal::from_atomics(4523u128, 4).unwrap(); + let liquidation_threshold = Decimal::from_atomics(5u128, 1).unwrap(); + + mock.set_price(vault_base_token, Decimal::one()); + mock.set_market( + vault_base_token, + &CoinMarketInfo { + denom: vault_base_token.to_string(), + max_ltv, + liquidation_threshold, + liquidation_bonus: Decimal::from_atomics(9u128, 2).unwrap(), + }, + ); + + let vault_configs = mock.query_vault_configs(); + let vault_max_ltv = vault_configs.first().unwrap().config.max_ltv; + let vault_liq_threshold = vault_configs.first().unwrap().config.liquidation_threshold; + + let health = mock.query_health(account_id).unwrap(); + assert_eq!(health.total_debt_value, Uint128::zero()); + assert_eq!(health.total_collateral_value, base_token_amount); + assert_eq!( + health.max_ltv_adjusted_collateral, + base_token_amount.checked_mul_floor(vault_max_ltv).unwrap() + ); + assert_eq!( + health.liquidation_threshold_adjusted_collateral, + base_token_amount.checked_mul_floor(vault_liq_threshold).unwrap() + ); + assert_eq!(health.max_ltv_health_factor, None); + assert_eq!(health.liquidation_health_factor, None); + assert!(!health.liquidatable); + assert!(!health.above_max_ltv); + + // After de-listing, maxLTV drops to zero + mock.vault_allowed(false); + let health = mock.query_health(account_id).unwrap(); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); +} diff --git a/contracts/health/tests/test_update_config.rs b/contracts/health/tests/test_update_config.rs new file mode 100644 index 000000000..bb6740c3d --- /dev/null +++ b/contracts/health/tests/test_update_config.rs @@ -0,0 +1,49 @@ +use cosmwasm_std::{Addr, StdError}; +use mars_owner::OwnerError::NotOwner; +use mars_rover_health_types::{ + HealthError, + HealthError::{Owner, Std}, +}; + +use crate::helpers::MockEnv; + +pub mod helpers; + +#[test] +fn only_owner_can_update_config() { + let mut mock = MockEnv::new().build().unwrap(); + + let new_cm_addr = Addr::unchecked("xyz"); + let bad_guy = Addr::unchecked("bad_guy"); + let err: HealthError = + mock.update_config(&bad_guy, &new_cm_addr).unwrap_err().downcast().unwrap(); + + assert_eq!(err, Owner(NotOwner {})); +} + +#[test] +fn raises_on_invalid_config() { + let mut mock = MockEnv::new().build().unwrap(); + + let new_cm_addr = Addr::unchecked(""); + let err: HealthError = + mock.update_config(&mock.deployer.clone(), &new_cm_addr).unwrap_err().downcast().unwrap(); + + assert_eq!(err, Std(StdError::generic_err("Invalid input: human address too short"))); +} + +#[test] +fn update_config_works() { + let mut mock = MockEnv::new().build().unwrap(); + + let new_cm_addr = Addr::unchecked("abc"); + mock.update_config(&mock.deployer.clone(), &new_cm_addr).unwrap(); + + let new_config = mock.query_config(); + + assert_eq!(new_config.credit_manager_addr, Some("abc".to_string())); + assert_eq!(new_config.owner_response.owner, Some(mock.deployer.to_string())); + assert_eq!(new_config.owner_response.proposed, None); + assert!(new_config.owner_response.initialized); + assert!(!new_config.owner_response.abolished); +} diff --git a/contracts/mock-credit-manager/Cargo.toml b/contracts/mock-credit-manager/Cargo.toml index 6e265632c..d013f92e3 100644 --- a/contracts/mock-credit-manager/Cargo.toml +++ b/contracts/mock-credit-manager/Cargo.toml @@ -19,10 +19,10 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -mars-health = { workspace = true } -mars-rover = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +mars-rover-health-types = { workspace = true } +mars-rover = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/mock-credit-manager/src/contract.rs b/contracts/mock-credit-manager/src/contract.rs index 2078ffa9f..1affb2a44 100644 --- a/contracts/mock-credit-manager/src/contract.rs +++ b/contracts/mock-credit-manager/src/contract.rs @@ -1,19 +1,24 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, -}; +use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use mars_rover::msg::QueryMsg; -use crate::{execute::set_health_response, msg::ExecuteMsg, query::query_health}; +use crate::{ + execute::{set_allowed_coins, set_health_response, set_position_response, set_vault_config}, + msg::{ExecuteMsg, InstantiateMsg}, + query::{query_allowed_coins, query_config, query_health, query_positions, query_vaults_info}, + state::{ALLOWED_COINS, CONFIG}, +}; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - _deps: DepsMut, + deps: DepsMut, _env: Env, _info: MessageInfo, - _msg: Empty, + msg: InstantiateMsg, ) -> StdResult { + CONFIG.save(deps.storage, &msg.config)?; + ALLOWED_COINS.save(deps.storage, &vec![])?; Ok(Response::default()) } @@ -29,6 +34,15 @@ pub fn execute( account_id, response, } => set_health_response(deps, account_id, response), + ExecuteMsg::SetPositionsResponse { + account_id, + positions, + } => set_position_response(deps, account_id, positions), + ExecuteMsg::SetAllowedCoins(allowed_coins) => set_allowed_coins(deps, allowed_coins), + ExecuteMsg::SetVaultConfig { + address, + config, + } => set_vault_config(deps, &address, config), } } @@ -38,6 +52,16 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::Health { account_id, } => to_binary(&query_health(deps, account_id)?), + QueryMsg::Positions { + account_id, + } => to_binary(&query_positions(deps, account_id)?), + QueryMsg::Config {} => to_binary(&query_config(deps)?), + QueryMsg::AllowedCoins { + .. + } => to_binary(&query_allowed_coins(deps)?), + QueryMsg::VaultsInfo { + .. + } => to_binary(&query_vaults_info(deps)?), _ => unimplemented!("query msg not supported"), } } diff --git a/contracts/mock-credit-manager/src/execute.rs b/contracts/mock-credit-manager/src/execute.rs index af62a3d60..92f93a668 100644 --- a/contracts/mock-credit-manager/src/execute.rs +++ b/contracts/mock-credit-manager/src/execute.rs @@ -1,7 +1,8 @@ -use cosmwasm_std::{DepsMut, Response, StdResult}; -use mars_health::HealthResponse; +use cosmwasm_std::{Addr, DepsMut, Response, StdResult}; +use mars_rover::{adapters::vault::VaultConfig, msg::query::Positions}; +use mars_rover_health_types::HealthResponse; -use crate::state::HEALTH_RESPONSES; +use crate::state::{ALLOWED_COINS, HEALTH_RESPONSES, POSITION_RESPONSES, VAULT_CONFIGS}; pub fn set_health_response( deps: DepsMut, @@ -11,3 +12,22 @@ pub fn set_health_response( HEALTH_RESPONSES.save(deps.storage, &account_id, &response)?; Ok(Response::new()) } + +pub fn set_position_response( + deps: DepsMut, + account_id: String, + positions: Positions, +) -> StdResult { + POSITION_RESPONSES.save(deps.storage, &account_id, &positions)?; + Ok(Response::new()) +} + +pub fn set_allowed_coins(deps: DepsMut, coins: Vec) -> StdResult { + ALLOWED_COINS.save(deps.storage, &coins)?; + Ok(Response::new()) +} + +pub fn set_vault_config(deps: DepsMut, address: &str, config: VaultConfig) -> StdResult { + VAULT_CONFIGS.save(deps.storage, &Addr::unchecked(address), &config)?; + Ok(Response::new()) +} diff --git a/contracts/mock-credit-manager/src/msg.rs b/contracts/mock-credit-manager/src/msg.rs index 0c5d4ed4c..de4ec2afa 100644 --- a/contracts/mock-credit-manager/src/msg.rs +++ b/contracts/mock-credit-manager/src/msg.rs @@ -1,5 +1,14 @@ use cosmwasm_schema::cw_serde; -use mars_health::HealthResponse; +use mars_rover::{ + adapters::vault::VaultConfig, + msg::query::{ConfigResponse, Positions}, +}; +use mars_rover_health_types::HealthResponse; + +#[cw_serde] +pub struct InstantiateMsg { + pub config: ConfigResponse, +} #[cw_serde] pub enum ExecuteMsg { @@ -7,4 +16,13 @@ pub enum ExecuteMsg { account_id: String, response: HealthResponse, }, + SetPositionsResponse { + account_id: String, + positions: Positions, + }, + SetAllowedCoins(Vec), + SetVaultConfig { + address: String, + config: VaultConfig, + }, } diff --git a/contracts/mock-credit-manager/src/query.rs b/contracts/mock-credit-manager/src/query.rs index 845df79f2..25c203f7c 100644 --- a/contracts/mock-credit-manager/src/query.rs +++ b/contracts/mock-credit-manager/src/query.rs @@ -1,8 +1,38 @@ -use cosmwasm_std::{Deps, StdResult}; -use mars_health::HealthResponse; +use cosmwasm_std::{coin, Deps, Order, StdResult}; +use mars_rover::{ + adapters::vault::VaultUnchecked, + msg::query::{ConfigResponse, Positions, VaultInfoResponse}, +}; +use mars_rover_health_types::HealthResponse; -use crate::state::HEALTH_RESPONSES; +use crate::state::{ALLOWED_COINS, CONFIG, HEALTH_RESPONSES, POSITION_RESPONSES, VAULT_CONFIGS}; pub fn query_health(deps: Deps, account_id: String) -> StdResult { HEALTH_RESPONSES.load(deps.storage, &account_id) } + +pub fn query_positions(deps: Deps, account_id: String) -> StdResult { + POSITION_RESPONSES.load(deps.storage, &account_id) +} + +pub fn query_config(deps: Deps) -> StdResult { + CONFIG.load(deps.storage) +} + +pub fn query_allowed_coins(deps: Deps) -> StdResult> { + ALLOWED_COINS.load(deps.storage) +} + +pub fn query_vaults_info(deps: Deps) -> StdResult> { + VAULT_CONFIGS + .range(deps.storage, None, None, Order::Ascending) + .map(|res| { + let (addr, config) = res?; + Ok(VaultInfoResponse { + config, + utilization: coin(1000000, "uusdc"), + vault: VaultUnchecked::new(addr.to_string()), + }) + }) + .collect() +} diff --git a/contracts/mock-credit-manager/src/state.rs b/contracts/mock-credit-manager/src/state.rs index 2d1f62c09..154964fb5 100644 --- a/contracts/mock-credit-manager/src/state.rs +++ b/contracts/mock-credit-manager/src/state.rs @@ -1,4 +1,14 @@ -use cw_storage_plus::Map; -use mars_health::HealthResponse; +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; +use mars_rover::{ + adapters::vault::VaultConfig, + msg::query::{ConfigResponse, Positions}, +}; +use mars_rover_health_types::HealthResponse; + +pub const CONFIG: Item = Item::new("config"); +pub const ALLOWED_COINS: Item> = Item::new("allowed_coins"); // Vec +pub const VAULT_CONFIGS: Map<&Addr, VaultConfig> = Map::new("vault_configs"); pub const HEALTH_RESPONSES: Map<&str, HealthResponse> = Map::new("health_responses"); // Map +pub const POSITION_RESPONSES: Map<&str, Positions> = Map::new("position_responses"); // Map diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index a57e096b7..191666845 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, use mars_red_bank_types::red_bank; use crate::{ - execute::{borrow, deposit, repay}, + execute::{borrow, deposit, repay, update_asset}, msg::InstantiateMsg, query::{query_collateral, query_debt, query_market}, state::COIN_MARKET_INFO, @@ -42,6 +42,10 @@ pub fn execute( red_bank::ExecuteMsg::Deposit { .. } => deposit(deps, info), + red_bank::ExecuteMsg::UpdateAsset { + denom, + params, + } => update_asset(deps, &denom, params), _ => unimplemented!("Msg not supported!"), } } diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs index 05523d3f8..1d9264b66 100644 --- a/contracts/mock-red-bank/src/execute.rs +++ b/contracts/mock-red-bank/src/execute.rs @@ -1,11 +1,13 @@ use cosmwasm_std::{ - coin, BankMsg, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, Uint128, + coin, BankMsg, CosmosMsg, Decimal, DepsMut, MessageInfo, Response, StdError, StdResult, Uint128, }; use cw_utils::one_coin; +use mars_red_bank_types::red_bank::InitOrUpdateAssetParams; use crate::{ helpers::{load_collateral_amount, load_debt_amount}, - state::{COLLATERAL_AMOUNT, DEBT_AMOUNT}, + msg::CoinMarketInfo, + state::{COIN_MARKET_INFO, COLLATERAL_AMOUNT, DEBT_AMOUNT}, }; pub fn borrow( @@ -57,3 +59,22 @@ pub fn deposit(deps: DepsMut, info: MessageInfo) -> StdResult { Ok(Response::new()) } + +pub fn update_asset( + deps: DepsMut, + denom: &str, + params: InitOrUpdateAssetParams, +) -> StdResult { + COIN_MARKET_INFO.save( + deps.storage, + denom.to_string(), + &CoinMarketInfo { + denom: denom.to_string(), + max_ltv: params.max_loan_to_value.unwrap_or(Decimal::zero()), + liquidation_threshold: params.liquidation_threshold.unwrap_or(Decimal::zero()), + liquidation_bonus: params.liquidation_bonus.unwrap_or(Decimal::zero()), + }, + )?; + + Ok(Response::new()) +} diff --git a/contracts/mock-vault/src/deposit.rs b/contracts/mock-vault/src/deposit.rs index 5577d496e..20ceb8665 100644 --- a/contracts/mock-vault/src/deposit.rs +++ b/contracts/mock-vault/src/deposit.rs @@ -2,7 +2,10 @@ use cosmwasm_std::{BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, Std use crate::{ contract::STARTING_VAULT_SHARES, - error::{ContractError, ContractError::WrongDenomSent}, + error::{ + ContractError, + ContractError::{NoCoinsSent, WrongDenomSent}, + }, state::{CHAIN_BANK, COIN_BALANCE, ORACLE, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}, }; @@ -25,7 +28,8 @@ pub fn deposit(deps: DepsMut, info: MessageInfo) -> Result c.amount, - _ => return Err(WrongDenomSent), + Some(c) if c.denom != balance.denom => return Err(WrongDenomSent), + _ => return Err(NoCoinsSent), }; COIN_BALANCE.save( deps.storage, diff --git a/contracts/mock-vault/src/error.rs b/contracts/mock-vault/src/error.rs index f8808ae97..e83f91950 100644 --- a/contracts/mock-vault/src/error.rs +++ b/contracts/mock-vault/src/error.rs @@ -18,6 +18,9 @@ pub enum ContractError { #[error("Lockup position {0} not found")] LockupPositionNotFound(u64), + #[error("Attempting to deposit, but did not sent any tokens")] + NoCoinsSent, + #[error("This vault is not a locking vault, action not allowed")] NotLockingVault, diff --git a/contracts/mock-vault/src/query.rs b/contracts/mock-vault/src/query.rs index 023e50bef..ea9dee35d 100644 --- a/contracts/mock-vault/src/query.rs +++ b/contracts/mock-vault/src/query.rs @@ -57,5 +57,5 @@ pub fn query_unlocking_positions( } pub fn query_vault_token_supply(storage: &dyn Storage) -> ContractResult { - Ok(TOTAL_VAULT_SHARES.load(storage)?) + Ok(TOTAL_VAULT_SHARES.may_load(storage)?.unwrap_or(Uint128::zero())) } diff --git a/packages/health-computer/Cargo.toml b/packages/health-computer/Cargo.toml new file mode 100644 index 000000000..d64090086 --- /dev/null +++ b/packages/health-computer/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "mars-rover-health-computer" +version = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] +doctest = false + +[features] +backtraces = ["cosmwasm-std/backtraces"] +default = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cosmwasm-vault-standard = { workspace = true } +mars-math = { workspace = true } +mars-rover-health-types = { workspace = true } +mars-red-bank-types = { workspace = true } +mars-rover = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +serde-wasm-bindgen = { workspace = true } +wasm-bindgen = { workspace = true } diff --git a/packages/health-computer/examples/schema.rs b/packages/health-computer/examples/schema.rs new file mode 100644 index 000000000..87ab3158e --- /dev/null +++ b/packages/health-computer/examples/schema.rs @@ -0,0 +1,17 @@ +use std::{env::current_dir, fs}; + +use mars_rover_health_computer::HealthComputer; +use schemars::schema_for; + +fn main() -> std::io::Result<()> { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + fs::create_dir_all(&out_dir).unwrap(); + let path = out_dir.join("mars-rover-health-computer.json"); + + let schema = schema_for!(HealthComputer); + let output = serde_json::to_string_pretty(&schema).unwrap(); + fs::write(path, output)?; + + Ok(()) +} diff --git a/packages/health-computer/src/data_types.rs b/packages/health-computer/src/data_types.rs new file mode 100644 index 000000000..c779c98be --- /dev/null +++ b/packages/health-computer/src/data_types.rs @@ -0,0 +1,31 @@ +use std::collections::HashMap; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Decimal, Uint128}; +use mars_red_bank_types::red_bank::Market; +use mars_rover::adapters::vault::{VaultConfig, VaultPositionValue}; + +/// Used as storage when trying to compute Health +#[cw_serde] +pub struct CollateralValue { + pub total_collateral_value: Uint128, + pub max_ltv_adjusted_collateral: Uint128, + pub liquidation_threshold_adjusted_collateral: Uint128, +} + +#[cw_serde] +#[derive(Default)] +pub struct DenomsData { + /// Must include data from info.base token for the vaults + pub prices: HashMap, + pub markets: HashMap, +} + +#[cw_serde] +#[derive(Default)] +pub struct VaultsData { + /// explain this, unlocked or locked value + /// given the pricing method of vaults, cannot use individual coins + pub vault_values: HashMap, + pub vault_configs: HashMap, +} diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs new file mode 100644 index 000000000..e22482854 --- /dev/null +++ b/packages/health-computer/src/health_computer.rs @@ -0,0 +1,201 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Decimal, Uint128}; +use mars_math::FractionMath; +use mars_red_bank_types::red_bank::Market; +use mars_rover::msg::query::Positions; +use mars_rover_health_types::{ + Health, + HealthError::{MissingMarket, MissingPrice, MissingVaultConfig, MissingVaultValues}, + HealthResult, +}; + +use crate::{CollateralValue, DenomsData, VaultsData}; + +/// `HealthComputer` is a shared struct with the frontend that gets compiled to wasm. +/// For this reason, it uses a dependency-injection-like pattern where all required data is needed up front. +#[cw_serde] +pub struct HealthComputer { + pub positions: Positions, + pub denoms_data: DenomsData, + pub vaults_data: VaultsData, + pub allowed_coins: Vec, +} + +impl HealthComputer { + pub fn compute_health(&self) -> HealthResult { + let CollateralValue { + total_collateral_value, + max_ltv_adjusted_collateral, + liquidation_threshold_adjusted_collateral, + } = self.calculate_collateral_value()?; + + let total_debt_value = self.calculate_total_debt_value()?; + + let max_ltv_health_factor = if total_debt_value.is_zero() { + None + } else { + Some(Decimal::checked_from_ratio(max_ltv_adjusted_collateral, total_debt_value)?) + }; + + let liquidation_health_factor = if total_debt_value.is_zero() { + None + } else { + Some(Decimal::checked_from_ratio( + liquidation_threshold_adjusted_collateral, + total_debt_value, + )?) + }; + + Ok(Health { + total_debt_value, + total_collateral_value, + max_ltv_adjusted_collateral, + liquidation_threshold_adjusted_collateral, + max_ltv_health_factor, + liquidation_health_factor, + }) + } + + fn calculate_total_debt_value(&self) -> HealthResult { + let mut total = Uint128::zero(); + for debt in &self.positions.debts { + let coin_price = + self.denoms_data.prices.get(&debt.denom).ok_or(MissingPrice(debt.denom.clone()))?; + let debt_value = debt.amount.checked_mul_floor(*coin_price)?; + total = total.checked_add(debt_value)?; + } + Ok(total) + } + + fn calculate_collateral_value(&self) -> HealthResult { + let deposits = self.calculate_deposits_value()?; + let vaults = self.calculate_vaults_value()?; + + Ok(CollateralValue { + total_collateral_value: deposits + .total_collateral_value + .checked_add(vaults.total_collateral_value)?, + max_ltv_adjusted_collateral: deposits + .max_ltv_adjusted_collateral + .checked_add(vaults.max_ltv_adjusted_collateral)?, + liquidation_threshold_adjusted_collateral: deposits + .liquidation_threshold_adjusted_collateral + .checked_add(vaults.liquidation_threshold_adjusted_collateral)?, + }) + } + + fn calculate_deposits_value(&self) -> HealthResult { + let mut total_collateral_value = Uint128::zero(); + let mut max_ltv_adjusted_collateral = Uint128::zero(); + let mut liquidation_threshold_adjusted_collateral = Uint128::zero(); + + for c in &self.positions.deposits { + let coin_price = + self.denoms_data.prices.get(&c.denom).ok_or(MissingPrice(c.denom.clone()))?; + let deposit_value = c.amount.checked_mul_floor(*coin_price)?; + total_collateral_value = total_collateral_value.checked_add(deposit_value)?; + + let &Market { + max_loan_to_value, + liquidation_threshold, + .. + } = self.denoms_data.markets.get(&c.denom).ok_or(MissingMarket(c.denom.clone()))?; + + // If coin has been de-listed, drop MaxLTV to zero + let checked_max_ltv = if self.allowed_coins.contains(&c.denom) { + max_loan_to_value + } else { + Decimal::zero() + }; + let max_ltv_adjusted = deposit_value.checked_mul_floor(checked_max_ltv)?; + max_ltv_adjusted_collateral = + max_ltv_adjusted_collateral.checked_add(max_ltv_adjusted)?; + + let liq_adjusted = deposit_value.checked_mul_floor(liquidation_threshold)?; + liquidation_threshold_adjusted_collateral = + liquidation_threshold_adjusted_collateral.checked_add(liq_adjusted)?; + } + Ok(CollateralValue { + total_collateral_value, + max_ltv_adjusted_collateral, + liquidation_threshold_adjusted_collateral, + }) + } + + fn calculate_vaults_value(&self) -> HealthResult { + let mut total_collateral_value = Uint128::zero(); + let mut max_ltv_adjusted_collateral = Uint128::zero(); + let mut liquidation_threshold_adjusted_collateral = Uint128::zero(); + + for v in &self.positions.vaults { + let values = self + .vaults_data + .vault_values + .get(&v.vault.address) + .ok_or(MissingVaultValues(v.vault.address.to_string()))?; + + total_collateral_value = total_collateral_value.checked_add(values.total_value()?)?; + + let config = self + .vaults_data + .vault_configs + .get(&v.vault.address) + .ok_or(MissingVaultConfig(v.vault.address.to_string()))?; + + // If vault or base token has been de-listed, drop MaxLTV to zero + let base_token_whitelisted = self.allowed_coins.contains(&values.base_coin.denom); + let checked_vault_max_ltv = if config.whitelisted && base_token_whitelisted { + config.max_ltv + } else { + Decimal::zero() + }; + + max_ltv_adjusted_collateral = values + .vault_coin + .value + .checked_mul_floor(checked_vault_max_ltv)? + .checked_add(max_ltv_adjusted_collateral)?; + + liquidation_threshold_adjusted_collateral = values + .vault_coin + .value + .checked_mul_floor(config.liquidation_threshold)? + .checked_add(liquidation_threshold_adjusted_collateral)?; + + let &Market { + max_loan_to_value, + liquidation_threshold, + .. + } = self + .denoms_data + .markets + .get(&values.base_coin.denom) + .ok_or(MissingMarket(values.base_coin.denom.clone()))?; + + // If base token has been de-listed, drop MaxLTV to zero + let checked_base_max_ltv = if base_token_whitelisted { + max_loan_to_value + } else { + Decimal::zero() + }; + + max_ltv_adjusted_collateral = values + .base_coin + .value + .checked_mul_floor(checked_base_max_ltv)? + .checked_add(max_ltv_adjusted_collateral)?; + + liquidation_threshold_adjusted_collateral = values + .base_coin + .value + .checked_mul_floor(liquidation_threshold)? + .checked_add(liquidation_threshold_adjusted_collateral)?; + } + + Ok(CollateralValue { + total_collateral_value, + max_ltv_adjusted_collateral, + liquidation_threshold_adjusted_collateral, + }) + } +} diff --git a/packages/health-computer/src/lib.rs b/packages/health-computer/src/lib.rs new file mode 100644 index 000000000..8bd9d95c1 --- /dev/null +++ b/packages/health-computer/src/lib.rs @@ -0,0 +1,4 @@ +mod data_types; +mod health_computer; + +pub use self::{data_types::*, health_computer::*}; diff --git a/packages/health-computer/tests/helpers/mock_coin_info.rs b/packages/health-computer/tests/helpers/mock_coin_info.rs new file mode 100644 index 000000000..4976dd249 --- /dev/null +++ b/packages/health-computer/tests/helpers/mock_coin_info.rs @@ -0,0 +1,74 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Decimal; +use mars_red_bank_types::red_bank::Market; + +#[cw_serde] +pub struct CoinInfo { + pub price: Decimal, + pub market: Market, +} + +pub fn umars_info() -> CoinInfo { + CoinInfo { + price: Decimal::from_atomics(1u128, 0).unwrap(), + market: Market { + denom: "umars".to_string(), + max_loan_to_value: Decimal::from_atomics(8u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(84u128, 2).unwrap(), + liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), + ..Default::default() + }, + } +} + +pub fn udai_info() -> CoinInfo { + CoinInfo { + price: Decimal::from_atomics(313451u128, 6).unwrap(), + market: Market { + denom: "udai".to_string(), + max_loan_to_value: Decimal::from_atomics(85u128, 2).unwrap(), + liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), + liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + ..Default::default() + }, + } +} + +pub fn uluna_info() -> CoinInfo { + CoinInfo { + price: Decimal::from_atomics(100u128, 1).unwrap(), + market: Market { + denom: "uluna".to_string(), + max_loan_to_value: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + ..Default::default() + }, + } +} + +pub fn ustars_info() -> CoinInfo { + CoinInfo { + price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), + market: Market { + denom: "ustars".to_string(), + max_loan_to_value: Decimal::from_atomics(6u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + ..Default::default() + }, + } +} + +pub fn ujuno_info() -> CoinInfo { + CoinInfo { + price: Decimal::from_atomics(7012302005u128, 3).unwrap(), + market: Market { + denom: "ujuno".to_string(), + max_loan_to_value: Decimal::from_atomics(8u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), + liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), + ..Default::default() + }, + } +} diff --git a/packages/health-computer/tests/helpers/mod.rs b/packages/health-computer/tests/helpers/mod.rs new file mode 100644 index 000000000..ebd5ee99d --- /dev/null +++ b/packages/health-computer/tests/helpers/mod.rs @@ -0,0 +1,3 @@ +pub use self::mock_coin_info::*; + +mod mock_coin_info; diff --git a/packages/health-computer/tests/test_health_scenarios.rs b/packages/health-computer/tests/test_health_scenarios.rs new file mode 100644 index 000000000..e9c81a952 --- /dev/null +++ b/packages/health-computer/tests/test_health_scenarios.rs @@ -0,0 +1,1082 @@ +use std::{collections::HashMap, ops::Add, str::FromStr}; + +use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; +use mars_math::FractionMath; +use mars_rover::{ + adapters::vault::{ + CoinValue, LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultConfig, + VaultPosition, VaultPositionAmount, VaultPositionValue, VaultUnlockingPosition, + }, + msg::query::{DebtAmount, Positions}, +}; +use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; + +use crate::helpers::{udai_info, ujuno_info, uluna_info, umars_info, ustars_info}; + +pub mod helpers; + +/// Action: User deposits 300 mars (1 price) +/// Health: assets_value: 300 +/// debt value 0 +/// liquidatable: false +/// above_max_ltv: false +#[test] +fn only_assets_with_no_debts() { + let umars = umars_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(umars.market.denom.clone(), umars.price)]), + markets: HashMap::from([(umars.market.denom.clone(), umars.market.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let deposit_amount = Uint128::new(300); + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![Coin { + denom: umars.market.denom.clone(), + amount: deposit_amount, + }], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + allowed_coins: vec![umars.market.denom.clone()], + }; + + let health = h.compute_health().unwrap(); + let collateral_value = deposit_amount.checked_mul_floor(umars.price).unwrap(); + assert_eq!(health.total_collateral_value, collateral_value); + assert_eq!( + health.max_ltv_adjusted_collateral, + collateral_value.checked_mul_floor(umars.market.max_loan_to_value).unwrap() + ); + assert_eq!( + health.liquidation_threshold_adjusted_collateral, + collateral_value.checked_mul_floor(umars.market.liquidation_threshold).unwrap() + ); + assert_eq!(health.total_debt_value, Uint128::zero()); + assert_eq!(health.liquidation_health_factor, None); + assert_eq!(health.max_ltv_health_factor, None); + assert!(!health.is_liquidatable()); + assert!(!health.is_above_max_ltv()); +} + +/// Step 1: User deposits 12 luna (100 price) and borrows 2 luna +/// Health: assets_value: 1400 +/// debt value 200 +/// liquidatable: false +/// above_max_ltv: false +/// Step 2: luna price goes to zero +/// Health: assets_value: 0 +/// debt value 0 (still debt shares outstanding) +/// liquidatable: false +/// above_max_ltv: false +#[test] +fn terra_ragnarok() { + let mut uluna = uluna_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(uluna.market.denom.clone(), uluna.price)]), + markets: HashMap::from([(uluna.market.denom.clone(), uluna.market.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let deposit_amount = Uint128::new(12); + let borrow_amount = Uint128::new(3); + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![Coin { + denom: uluna.market.denom.clone(), + amount: deposit_amount, + }], + debts: vec![DebtAmount { + denom: uluna.market.denom.clone(), + amount: borrow_amount, + shares: Uint128::new(100), + }], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data: vaults_data.clone(), + allowed_coins: vec![uluna.market.denom.clone()], + }; + + let health = h.compute_health().unwrap(); + let collateral_value = deposit_amount.checked_mul_floor(uluna.price).unwrap(); + let debts_value = borrow_amount.checked_mul_floor(uluna.price).unwrap(); + + assert_eq!(health.total_collateral_value, collateral_value); + assert_eq!( + health.max_ltv_adjusted_collateral, + collateral_value.checked_mul_floor(uluna.market.max_loan_to_value).unwrap() + ); + assert_eq!( + health.liquidation_threshold_adjusted_collateral, + collateral_value.checked_mul_floor(uluna.market.liquidation_threshold).unwrap() + ); + assert_eq!(health.total_debt_value, borrow_amount.checked_mul_floor(uluna.price).unwrap()); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_ratio( + collateral_value.checked_mul_floor(uluna.market.liquidation_threshold).unwrap(), + debts_value + )) + ); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_ratio( + collateral_value.checked_mul_floor(uluna.market.max_loan_to_value).unwrap(), + debts_value, + )) + ); + assert!(!health.is_liquidatable()); + assert!(!health.is_above_max_ltv()); + + // Terra implosion + uluna.price = Decimal::zero(); + + let denoms_data = DenomsData { + prices: HashMap::from([(uluna.market.denom.clone(), uluna.price)]), + markets: HashMap::from([(uluna.market.denom.clone(), uluna.market.clone())]), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![Coin { + denom: uluna.market.denom.clone(), + amount: deposit_amount, + }], + debts: vec![DebtAmount { + denom: uluna.market.denom.clone(), + amount: borrow_amount, + shares: Uint128::new(100), + }], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + allowed_coins: vec![uluna.market.denom], + }; + + let health = h.compute_health().unwrap(); + assert_eq!(health.total_collateral_value, Uint128::zero()); + assert_eq!(health.total_debt_value, Uint128::zero()); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::zero()); + assert_eq!(health.liquidation_health_factor, None); + assert_eq!(health.max_ltv_health_factor, None); + assert!(!health.is_liquidatable()); + assert!(!health.is_above_max_ltv()); +} + +/// Actions: User deposits 300 stars +/// and borrows 49 juno +/// Health: assets_value: 1569456334491.12991516325 +/// debt value 350615100.25 +/// liquidatable: false +/// above_max_ltv: false +#[test] +fn ltv_and_lqdt_adjusted_values() { + let ustars = ustars_info(); + let ujuno = ujuno_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (ustars.market.denom.clone(), ustars.price), + (ujuno.market.denom.clone(), ujuno.price), + ]), + markets: HashMap::from([ + (ustars.market.denom.clone(), ustars.market.clone()), + (ujuno.market.denom.clone(), ujuno.market.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let deposit_amount = Uint128::new(300); + let borrow_amount = Uint128::new(49); + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![ + Coin { + denom: ustars.market.denom.clone(), + amount: deposit_amount, + }, + Coin { + denom: ujuno.market.denom.clone(), + amount: borrow_amount, + }, + ], + debts: vec![DebtAmount { + denom: ujuno.market.denom.clone(), + shares: Uint128::new(12345), + amount: borrow_amount.add(Uint128::one()), // simulated interest + }], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + allowed_coins: vec![ustars.market.denom.clone(), ujuno.market.denom.clone()], + }; + + let health = h.compute_health().unwrap(); + assert_eq!( + health.total_collateral_value, + deposit_amount + .checked_mul_floor(ustars.price) + .unwrap() + .add(borrow_amount.checked_mul_floor(ujuno.price).unwrap()) + ); + assert_eq!( + health.total_debt_value, + Uint128::new(350_615_100) // with simulated interest + ); + let lqdt_adjusted_assets_value = deposit_amount + .checked_mul_floor(ustars.price) + .unwrap() + .checked_mul_floor(ustars.market.liquidation_threshold) + .unwrap() + .add( + borrow_amount + .checked_mul_floor(ujuno.price) + .unwrap() + .checked_mul_floor(ujuno.market.liquidation_threshold) + .unwrap(), + ); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_ratio( + lqdt_adjusted_assets_value, + (borrow_amount + Uint128::one()).checked_mul_floor(ujuno.price).unwrap() + )) + ); + let ltv_adjusted_assets_value = deposit_amount + .checked_mul_floor(ustars.price) + .unwrap() + .checked_mul_floor(ustars.market.max_loan_to_value) + .unwrap() + .add( + borrow_amount + .checked_mul_floor(ujuno.price) + .unwrap() + .checked_mul_floor(ujuno.market.max_loan_to_value) + .unwrap(), + ); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_ratio( + ltv_adjusted_assets_value, + (borrow_amount + Uint128::one()).checked_mul_floor(ujuno.price).unwrap() + )) + ); + assert!(!health.is_liquidatable()); + assert!(!health.is_above_max_ltv()); +} + +/// Borrows 30 stars +/// Borrows 49 juno +/// Deposits 298 stars +/// Test validates debt calculation results +#[test] +fn debt_value() { + let ustars = ustars_info(); + let ujuno = ujuno_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (ustars.market.denom.clone(), ustars.price), + (ujuno.market.denom.clone(), ujuno.price), + ]), + markets: HashMap::from([ + (ustars.market.denom.clone(), ustars.market.clone()), + (ujuno.market.denom.clone(), ujuno.market.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let deposit_amount_stars = Uint128::new(298); + let borrowed_amount_juno = Uint128::new(49); + let borrowed_amount_stars = Uint128::new(30); + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![ + Coin { + denom: ustars.market.denom.clone(), + amount: deposit_amount_stars, + }, + Coin { + denom: ujuno.market.denom.clone(), + amount: borrowed_amount_juno, + }, + Coin { + denom: ustars.market.denom.clone(), + amount: borrowed_amount_stars, + }, + ], + debts: vec![ + DebtAmount { + denom: ujuno.market.denom.clone(), + shares: Uint128::new(12345), + amount: borrowed_amount_juno.add(Uint128::one()), // simulated interest + }, + DebtAmount { + denom: ustars.market.denom.clone(), + shares: Uint128::new(12345), + amount: borrowed_amount_stars.add(Uint128::one()), // simulated interest + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + allowed_coins: vec![ustars.market.denom.clone(), ujuno.market.denom.clone()], + }; + + let health = h.compute_health().unwrap(); + + assert!(!health.is_above_max_ltv()); + assert!(!health.is_liquidatable()); + + let juno_debt_value = + borrowed_amount_juno.add(Uint128::one()).checked_mul_floor(ujuno.price).unwrap(); + + let stars_debt_value = + borrowed_amount_stars.add(Uint128::one()).checked_mul_floor(ustars.price).unwrap(); + + let total_debt_value = juno_debt_value.add(stars_debt_value); + assert_eq!(health.total_debt_value, total_debt_value); + + let lqdt_adjusted_assets_value = deposit_amount_stars + .checked_mul_floor(ustars.price) + .unwrap() + .checked_mul_floor(ustars.market.liquidation_threshold) + .unwrap() + .add( + borrowed_amount_stars + .checked_mul_floor(ustars.price) + .unwrap() + .checked_mul_floor(ustars.market.liquidation_threshold) + .unwrap(), + ) + .add( + borrowed_amount_juno + .checked_mul_floor(ujuno.price) + .unwrap() + .checked_mul_floor(ujuno.market.liquidation_threshold) + .unwrap(), + ); + + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_ratio(lqdt_adjusted_assets_value, total_debt_value)) + ); + + let max_ltv_adjusted_assets_value = deposit_amount_stars + .checked_mul_floor(ustars.price) + .unwrap() + .checked_mul_floor(ustars.market.max_loan_to_value) + .unwrap() + .add( + borrowed_amount_stars + .checked_mul_floor(ustars.price) + .unwrap() + .checked_mul_floor(ustars.market.max_loan_to_value) + .unwrap(), + ) + .add( + borrowed_amount_juno + .checked_mul_floor(ujuno.price) + .unwrap() + .checked_mul_floor(ujuno.market.max_loan_to_value) + .unwrap(), + ); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_ratio(max_ltv_adjusted_assets_value, total_debt_value)) + ); +} + +#[test] +fn above_max_ltv_below_liq_threshold() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.market.denom.clone(), umars.price), + (udai.market.denom.clone(), udai.price), + ]), + markets: HashMap::from([ + (umars.market.denom.clone(), umars.market.clone()), + (udai.market.denom.clone(), udai.market.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + debts: vec![DebtAmount { + denom: udai.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + allowed_coins: vec![umars.market.denom, udai.market.denom], + }; + + let health = h.compute_health().unwrap(); + assert_eq!(health.total_collateral_value, Uint128::new(1210)); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(968)); + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(1017)); + assert_eq!(health.total_debt_value, Uint128::new(971)); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_str("0.996910401647785787").unwrap()) + ); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_str("1.047373841400617919").unwrap()) + ); + assert!(health.is_above_max_ltv()); + assert!(!health.is_liquidatable()); +} + +#[test] +fn liquidatable() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.market.denom.clone(), umars.price), + (udai.market.denom.clone(), udai.price), + ]), + markets: HashMap::from([ + (umars.market.denom.clone(), umars.market.clone()), + (udai.market.denom.clone(), udai.market.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + debts: vec![ + DebtAmount { + denom: udai.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + allowed_coins: vec![umars.market.denom, udai.market.denom], + }; + + let health = h.compute_health().unwrap(); + assert_eq!(health.total_collateral_value, Uint128::new(1210)); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(968)); + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(1017)); + assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_str("0.826643894107600341").unwrap()) + ); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_str("0.868488471391972672").unwrap()) + ); + assert!(health.is_above_max_ltv()); + assert!(health.is_liquidatable()); +} + +#[test] +fn allowed_coins_influence_max_ltv() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.market.denom.clone(), umars.price), + (udai.market.denom.clone(), udai.price), + ]), + markets: HashMap::from([ + (umars.market.denom.clone(), umars.market.clone()), + (udai.market.denom.clone(), udai.market.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + debts: vec![ + DebtAmount { + denom: udai.market.denom, + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + allowed_coins: vec![umars.market.denom], + }; + + let health = h.compute_health().unwrap(); + assert_eq!(health.total_collateral_value, Uint128::new(1210)); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(960)); + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(1017)); + assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_str("0.819812126387702818").unwrap()) + ); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_str("0.868488471391972672").unwrap()) + ); + assert!(health.is_above_max_ltv()); + assert!(health.is_liquidatable()); +} + +#[test] +fn unlocked_vault() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.market.denom.clone(), umars.price), + (udai.market.denom.clone(), udai.price), + ]), + markets: HashMap::from([ + (umars.market.denom.clone(), umars.market.clone()), + (udai.market.denom.clone(), udai.market.clone()), + ]), + }; + + let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); + + let vaults_data = VaultsData { + vault_values: HashMap::from([( + vault.address.clone(), + VaultPositionValue { + vault_coin: CoinValue { + denom: "leverage_vault_123".to_string(), + amount: Default::default(), + value: Uint128::new(5264), + }, + base_coin: CoinValue { + denom: udai.market.denom.clone(), + amount: Default::default(), + value: Default::default(), + }, + }, + )]), + vault_configs: HashMap::from([( + vault.address.clone(), + VaultConfig { + deposit_cap: Default::default(), + max_ltv: Decimal::from_str("0.4").unwrap(), + liquidation_threshold: Decimal::from_str("0.5").unwrap(), + whitelisted: true, + }, + )]), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + debts: vec![ + DebtAmount { + denom: udai.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![VaultPosition { + vault, + amount: VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::new(5264))), + }], + }, + denoms_data, + vaults_data, + allowed_coins: vec![umars.market.denom, udai.market.denom], + }; + + let health = h.compute_health().unwrap(); + assert_eq!(health.total_collateral_value, Uint128::new(6474)); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(3073)); + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(3649)); + assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_str("2.624252775405636208").unwrap()) + ); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_str("3.116140051238257899").unwrap()) + ); + assert!(!health.is_above_max_ltv()); + assert!(!health.is_liquidatable()); +} + +#[test] +fn locked_vault() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.market.denom.clone(), umars.price), + (udai.market.denom.clone(), udai.price), + ]), + markets: HashMap::from([ + (umars.market.denom.clone(), umars.market.clone()), + (udai.market.denom.clone(), udai.market.clone()), + ]), + }; + + let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); + + let vaults_data = VaultsData { + vault_values: HashMap::from([( + vault.address.clone(), + VaultPositionValue { + vault_coin: CoinValue { + denom: "leverage_vault_123".to_string(), + amount: Default::default(), + value: Uint128::new(5264), + }, + base_coin: CoinValue { + denom: udai.market.denom.clone(), + amount: Default::default(), + value: Default::default(), + }, + }, + )]), + vault_configs: HashMap::from([( + vault.address.clone(), + VaultConfig { + deposit_cap: Default::default(), + max_ltv: Decimal::from_str("0.4").unwrap(), + liquidation_threshold: Decimal::from_str("0.5").unwrap(), + whitelisted: true, + }, + )]), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + debts: vec![ + DebtAmount { + denom: udai.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![VaultPosition { + vault, + amount: VaultPositionAmount::Locking(LockingVaultAmount { + locked: VaultAmount::new(Uint128::new(42451613)), + unlocking: UnlockingPositions::new(vec![]), + }), + }], + }, + denoms_data, + vaults_data, + allowed_coins: vec![umars.market.denom, udai.market.denom], + }; + + let health = h.compute_health().unwrap(); + assert_eq!(health.total_collateral_value, Uint128::new(6474)); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(3073)); + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(3649)); + assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_str("2.624252775405636208").unwrap()) + ); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_str("3.116140051238257899").unwrap()) + ); + assert!(!health.is_above_max_ltv()); + assert!(!health.is_liquidatable()); +} + +#[test] +fn locked_vault_with_unlocking_positions() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.market.denom.clone(), umars.price), + (udai.market.denom.clone(), udai.price), + ]), + markets: HashMap::from([ + (umars.market.denom.clone(), umars.market.clone()), + (udai.market.denom.clone(), udai.market.clone()), + ]), + }; + + let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); + + let vaults_data = VaultsData { + vault_values: HashMap::from([( + vault.address.clone(), + VaultPositionValue { + vault_coin: CoinValue { + denom: "leverage_vault_123".to_string(), + amount: Default::default(), + value: Uint128::new(5000), + }, + base_coin: CoinValue { + denom: udai.market.denom.clone(), + amount: Default::default(), + value: Uint128::new(264), + }, + }, + )]), + vault_configs: HashMap::from([( + vault.address.clone(), + VaultConfig { + deposit_cap: Default::default(), + max_ltv: Decimal::from_str("0.4").unwrap(), + liquidation_threshold: Decimal::from_str("0.5").unwrap(), + whitelisted: true, + }, + )]), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + debts: vec![ + DebtAmount { + denom: udai.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![VaultPosition { + vault, + amount: VaultPositionAmount::Locking(LockingVaultAmount { + locked: VaultAmount::new(Uint128::new(40330000)), + unlocking: UnlockingPositions::new(vec![ + VaultUnlockingPosition { + id: 0, + coin: coin(840, udai.market.denom.clone()), + }, + VaultUnlockingPosition { + id: 1, + coin: coin(3, udai.market.denom.clone()), + }, + ]), + }), + }], + }, + denoms_data, + vaults_data, + allowed_coins: vec![umars.market.denom, udai.market.denom], + }; + + let health = h.compute_health().unwrap(); + assert_eq!(health.total_collateral_value, Uint128::new(6474)); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(3192)); + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(3754)); + assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_str("2.72587532023911187").unwrap()) + ); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_str("3.205807002561912894").unwrap()) + ); + assert!(!health.is_above_max_ltv()); + assert!(!health.is_liquidatable()); +} + +#[test] +fn vault_is_not_whitelisted() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.market.denom.clone(), umars.price), + (udai.market.denom.clone(), udai.price), + ]), + markets: HashMap::from([ + (umars.market.denom.clone(), umars.market.clone()), + (udai.market.denom.clone(), udai.market.clone()), + ]), + }; + + let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); + + let vaults_data = VaultsData { + vault_values: HashMap::from([( + vault.address.clone(), + VaultPositionValue { + vault_coin: CoinValue { + denom: "leverage_vault_123".to_string(), + amount: Default::default(), + value: Uint128::new(5264), + }, + base_coin: CoinValue { + denom: udai.market.denom.clone(), + amount: Default::default(), + value: Default::default(), + }, + }, + )]), + vault_configs: HashMap::from([( + vault.address.clone(), + VaultConfig { + deposit_cap: Default::default(), + max_ltv: Decimal::from_str("0.4").unwrap(), + liquidation_threshold: Decimal::from_str("0.5").unwrap(), + whitelisted: false, + }, + )]), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + debts: vec![ + DebtAmount { + denom: udai.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![VaultPosition { + vault, + amount: VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::new(5264))), + }], + }, + denoms_data, + vaults_data, + allowed_coins: vec![umars.market.denom, udai.market.denom], + }; + + let health = h.compute_health().unwrap(); + assert_eq!(health.total_collateral_value, Uint128::new(6474)); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(968)); + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(3649)); + assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_str("0.826643894107600341").unwrap()) + ); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_str("3.116140051238257899").unwrap()) + ); + assert!(health.is_above_max_ltv()); + assert!(!health.is_liquidatable()); +} + +/// Delisting base token will make even vault token maxLTV to drop +#[test] +fn vault_base_token_is_not_whitelisted() { + let umars = umars_info(); + let udai = udai_info(); + let ujuno = ujuno_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.market.denom.clone(), umars.price), + (udai.market.denom.clone(), udai.price), + (ujuno.market.denom.clone(), ujuno.price), + ]), + markets: HashMap::from([ + (umars.market.denom.clone(), umars.market.clone()), + (udai.market.denom.clone(), udai.market.clone()), + (ujuno.market.denom.clone(), ujuno.market.clone()), + ]), + }; + + let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); + + let vaults_data = VaultsData { + vault_values: HashMap::from([( + vault.address.clone(), + VaultPositionValue { + vault_coin: CoinValue { + denom: "leverage_vault_123".to_string(), + amount: Default::default(), + value: Uint128::new(5000), + }, + base_coin: CoinValue { + denom: ujuno.market.denom.clone(), + amount: Default::default(), + value: Uint128::new(497873442), + }, + }, + )]), + vault_configs: HashMap::from([( + vault.address.clone(), + VaultConfig { + deposit_cap: Default::default(), + max_ltv: Decimal::from_str("0.4").unwrap(), + liquidation_threshold: Decimal::from_str("0.5").unwrap(), + whitelisted: true, + }, + )]), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + debts: vec![ + DebtAmount { + denom: udai.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![VaultPosition { + vault, + amount: VaultPositionAmount::Locking(LockingVaultAmount { + locked: VaultAmount::new(Uint128::new(40330000)), + unlocking: UnlockingPositions::new(vec![ + VaultUnlockingPosition { + id: 0, + coin: coin(60, ujuno.market.denom.clone()), + }, + VaultUnlockingPosition { + id: 1, + coin: coin(11, ujuno.market.denom), + }, + ]), + }), + }], + }, + denoms_data, + vaults_data, + allowed_coins: vec![umars.market.denom, udai.market.denom], + }; + + let health = h.compute_health().unwrap(); + assert_eq!(health.total_collateral_value, Uint128::new(497879652)); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(968)); // Lower due to vault blacklisted + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(448089614)); + assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_str("0.826643894107600341").unwrap()) + ); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_str("382655.520068317677198975").unwrap()) + ); + assert!(health.is_above_max_ltv()); + assert!(!health.is_liquidatable()); +} diff --git a/packages/health-computer/tests/test_input_validation.rs b/packages/health-computer/tests/test_input_validation.rs new file mode 100644 index 000000000..6fb62326a --- /dev/null +++ b/packages/health-computer/tests/test_input_validation.rs @@ -0,0 +1,254 @@ +use std::collections::HashMap; + +use cosmwasm_std::{coin, Addr, Uint128}; +use mars_rover::{ + adapters::vault::{ + CoinValue, Vault, VaultAmount, VaultConfig, VaultPosition, VaultPositionAmount, + VaultPositionValue, + }, + msg::query::{DebtAmount, Positions}, +}; +use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; +use mars_rover_health_types::HealthError; + +use crate::helpers::{udai_info, umars_info}; + +pub mod helpers; + +#[test] +fn missing_price_data() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(umars.market.denom.clone(), umars.price)]), + markets: HashMap::from([ + (umars.market.denom.clone(), umars.market.clone()), + (udai.market.denom.clone(), udai.market.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + debts: vec![ + DebtAmount { + denom: udai.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + allowed_coins: vec![umars.market.denom, udai.market.denom.clone()], + }; + + let err: HealthError = h.compute_health().unwrap_err(); + assert_eq!(err, HealthError::MissingPrice(udai.market.denom)) +} + +#[test] +fn missing_market_data() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.market.denom.clone(), umars.price), + (udai.market.denom.clone(), udai.price), + ]), + markets: HashMap::from([(udai.market.denom.clone(), udai.market.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + debts: vec![ + DebtAmount { + denom: udai.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + allowed_coins: vec![umars.market.denom.clone(), udai.market.denom], + }; + + let err: HealthError = h.compute_health().unwrap_err(); + assert_eq!(err, HealthError::MissingMarket(umars.market.denom)) +} + +#[test] +fn missing_market_data_for_vault_base_token() { + let denoms_data = DenomsData { + prices: HashMap::default(), + markets: HashMap::default(), + }; + + let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); + + let vaults_data = VaultsData { + vault_values: HashMap::from([( + vault.address.clone(), + VaultPositionValue { + vault_coin: CoinValue { + denom: "leverage_vault_123".to_string(), + amount: Default::default(), + value: Default::default(), + }, + base_coin: CoinValue { + denom: "base_token_xyz".to_string(), + amount: Default::default(), + value: Default::default(), + }, + }, + )]), + vault_configs: HashMap::from([( + vault.address.clone(), + VaultConfig { + deposit_cap: Default::default(), + max_ltv: Default::default(), + liquidation_threshold: Default::default(), + whitelisted: false, + }, + )]), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![], + debts: vec![], + lends: vec![], + vaults: vec![VaultPosition { + vault, + amount: VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::one())), + }], + }, + denoms_data, + vaults_data, + allowed_coins: vec![], + }; + + let err: HealthError = h.compute_health().unwrap_err(); + assert_eq!(err, HealthError::MissingMarket("base_token_xyz".to_string())) +} + +#[test] +fn missing_vault_value() { + let denoms_data = DenomsData { + prices: HashMap::default(), + markets: HashMap::default(), + }; + + let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: HashMap::from([( + vault.address.clone(), + VaultConfig { + deposit_cap: Default::default(), + max_ltv: Default::default(), + liquidation_threshold: Default::default(), + whitelisted: false, + }, + )]), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![], + debts: vec![], + lends: vec![], + vaults: vec![VaultPosition { + vault: vault.clone(), + amount: VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::one())), + }], + }, + denoms_data, + vaults_data, + allowed_coins: vec![], + }; + + let err: HealthError = h.compute_health().unwrap_err(); + assert_eq!(err, HealthError::MissingVaultValues(vault.address.to_string())) +} + +#[test] +fn missing_vault_config() { + let denoms_data = DenomsData { + prices: HashMap::default(), + markets: HashMap::default(), + }; + + let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); + + let vaults_data = VaultsData { + vault_values: HashMap::from([( + vault.address.clone(), + VaultPositionValue { + vault_coin: CoinValue { + denom: "abc".to_string(), + amount: Default::default(), + value: Default::default(), + }, + base_coin: CoinValue { + denom: "xyz".to_string(), + amount: Default::default(), + value: Default::default(), + }, + }, + )]), + vault_configs: HashMap::default(), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![], + debts: vec![], + lends: vec![], + vaults: vec![VaultPosition { + vault: vault.clone(), + amount: VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::one())), + }], + }, + denoms_data, + vaults_data, + allowed_coins: vec![], + }; + + let err: HealthError = h.compute_health().unwrap_err(); + assert_eq!(err, HealthError::MissingVaultConfig(vault.address.to_string())) +} diff --git a/packages/health-types/Cargo.toml b/packages/health-types/Cargo.toml new file mode 100644 index 000000000..6a8e0e1c3 --- /dev/null +++ b/packages/health-types/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "mars-rover-health-types" +version = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] +doctest = false + +[features] +backtraces = ["cosmwasm-std/backtraces"] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +mars-math = { workspace = true } +mars-owner = { workspace = true } +mars-red-bank-types = { workspace = true } +thiserror = { workspace = true } diff --git a/packages/health-types/src/error.rs b/packages/health-types/src/error.rs new file mode 100644 index 000000000..e9c57660c --- /dev/null +++ b/packages/health-types/src/error.rs @@ -0,0 +1,42 @@ +use cosmwasm_std::{CheckedFromRatioError, OverflowError, StdError}; +use mars_math::CheckedMultiplyFractionError; +use mars_owner::OwnerError; +use thiserror::Error; + +pub type HealthResult = Result; + +#[derive(Error, Debug, PartialEq)] +pub enum HealthError { + #[error("{0}")] + CheckedFromRatio(#[from] CheckedFromRatioError), + + #[error("{0}")] + CheckedMultiplyFraction(#[from] CheckedMultiplyFractionError), + + #[error("The credit manager address has not been set in config")] + CreditManagerNotSet, + + #[error("{0} was not provided a market to compute health with")] + MissingMarket(String), + + #[error("{0} was not provided a price to compute health with")] + MissingPrice(String), + + #[error("{0} was not provided a vault config to compute health with")] + MissingVaultConfig(String), + + #[error("{0} was not provided vault info to compute health with")] + MissingVaultInfo(String), + + #[error("{0} was not provided vault coin + base coin values to compute health with")] + MissingVaultValues(String), + + #[error("{0}")] + Overflow(#[from] OverflowError), + + #[error("{0}")] + Owner(#[from] OwnerError), + + #[error("{0}")] + Std(#[from] StdError), +} diff --git a/packages/health-types/src/health.rs b/packages/health-types/src/health.rs new file mode 100644 index 000000000..88af3b4b8 --- /dev/null +++ b/packages/health-types/src/health.rs @@ -0,0 +1,78 @@ +use std::fmt; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Decimal, Uint128}; + +#[cw_serde] +pub struct Health { + /// The sum of the value of all debts + pub total_debt_value: Uint128, + /// The sum of the value of all collaterals + pub total_collateral_value: Uint128, + /// The sum of the value of all colletarals adjusted by their Max LTV + pub max_ltv_adjusted_collateral: Uint128, + /// The sum of the value of all colletarals adjusted by their Liquidation Threshold + pub liquidation_threshold_adjusted_collateral: Uint128, + /// The sum of the value of all collaterals multiplied by their max LTV, over the total value of debt + pub max_ltv_health_factor: Option, + /// The sum of the value of all collaterals multiplied by their liquidation threshold over the total value of debt + pub liquidation_health_factor: Option, +} + +impl fmt::Display for Health { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "(total_debt_value: {}, total_collateral_value: {}, max_ltv_adjusted_collateral: {}, lqdt_threshold_adjusted_collateral: {}, max_ltv_health_factor: {}, liquidation_health_factor: {})", + self.total_debt_value, + self.total_collateral_value, + self.max_ltv_adjusted_collateral, + self.liquidation_threshold_adjusted_collateral, + self.max_ltv_health_factor.map_or("n/a".to_string(), |x| x.to_string()), + self.liquidation_health_factor.map_or("n/a".to_string(), |x| x.to_string()) + ) + } +} + +impl Health { + #[inline] + pub fn is_liquidatable(&self) -> bool { + is_below_one(&self.liquidation_health_factor) + } + + #[inline] + pub fn is_above_max_ltv(&self) -> bool { + is_below_one(&self.max_ltv_health_factor) + } +} + +pub fn is_below_one(health_factor: &Option) -> bool { + health_factor.map_or(false, |hf| hf < Decimal::one()) +} + +#[cw_serde] +pub struct HealthResponse { + pub total_debt_value: Uint128, + pub total_collateral_value: Uint128, + pub max_ltv_adjusted_collateral: Uint128, + pub liquidation_threshold_adjusted_collateral: Uint128, + pub max_ltv_health_factor: Option, + pub liquidation_health_factor: Option, + pub liquidatable: bool, + pub above_max_ltv: bool, +} + +impl From for HealthResponse { + fn from(h: Health) -> Self { + Self { + total_debt_value: h.total_debt_value, + total_collateral_value: h.total_collateral_value, + max_ltv_adjusted_collateral: h.max_ltv_adjusted_collateral, + liquidation_threshold_adjusted_collateral: h.liquidation_threshold_adjusted_collateral, + max_ltv_health_factor: h.max_ltv_health_factor, + liquidation_health_factor: h.liquidation_health_factor, + liquidatable: h.is_liquidatable(), + above_max_ltv: h.is_above_max_ltv(), + } + } +} diff --git a/packages/health-types/src/lib.rs b/packages/health-types/src/lib.rs new file mode 100644 index 000000000..106067349 --- /dev/null +++ b/packages/health-types/src/lib.rs @@ -0,0 +1,7 @@ +mod error; +mod health; +mod msg; + +pub use error::*; +pub use health::*; +pub use msg::*; diff --git a/packages/health-types/src/msg.rs b/packages/health-types/src/msg.rs new file mode 100644 index 000000000..86e4defc8 --- /dev/null +++ b/packages/health-types/src/msg.rs @@ -0,0 +1,35 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use mars_owner::{OwnerResponse, OwnerUpdate}; + +#[cw_serde] +pub struct InstantiateMsg { + /// The address with privileged access to update config + pub owner: String, +} + +#[cw_serde] +pub enum ExecuteMsg { + /// Manages owner role state + UpdateOwner(OwnerUpdate), + /// Update contract config constants + UpdateConfig { + credit_manager: String, + }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(crate::HealthResponse)] + Health { + account_id: String, + }, + #[returns(ConfigResponse)] + Config {}, +} + +#[cw_serde] +pub struct ConfigResponse { + pub credit_manager_addr: Option, + pub owner_response: OwnerResponse, +} diff --git a/packages/rover/src/adapters/vault/position.rs b/packages/rover/src/adapters/vault/position.rs index 90679b945..7a0fa9fad 100644 --- a/packages/rover/src/adapters/vault/position.rs +++ b/packages/rover/src/adapters/vault/position.rs @@ -1,7 +1,11 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::Coin; +use cosmwasm_std::{Coin, QuerierWrapper, StdError, StdResult, Uint128}; +use mars_math::FractionMath; -use crate::adapters::vault::{Vault, VaultPositionAmount}; +use crate::adapters::{ + oracle::Oracle, + vault::{Vault, VaultPositionAmount}, +}; #[cw_serde] pub struct VaultUnlockingPosition { @@ -23,3 +27,85 @@ pub enum VaultPositionType { LOCKED, UNLOCKING, } + +#[cw_serde] +pub struct CoinValue { + pub denom: String, + pub amount: Uint128, + pub value: Uint128, +} + +#[cw_serde] +pub struct VaultPositionValue { + /// value of locked or unlocked + pub vault_coin: CoinValue, + /// value of all unlocking positions + pub base_coin: CoinValue, +} + +impl VaultPositionValue { + pub fn total_value(&self) -> StdResult { + Ok(self.base_coin.value.checked_add(self.vault_coin.value)?) + } +} + +impl VaultPosition { + pub fn query_values( + &self, + querier: &QuerierWrapper, + oracle: &Oracle, + ) -> StdResult { + Ok(VaultPositionValue { + vault_coin: self.vault_coin_value(querier, oracle)?, + base_coin: self.base_coin_value(querier, oracle)?, + }) + } + + fn vault_coin_value(&self, querier: &QuerierWrapper, oracle: &Oracle) -> StdResult { + let vault_info = self.vault.query_info(querier)?; + + let total_supply = self.vault.query_total_vault_coins_issued(querier)?; + if total_supply.is_zero() { + return Ok(CoinValue { + denom: vault_info.vault_token, + amount: Uint128::zero(), + value: Uint128::zero(), + }); + }; + + let vault_coin_amount = self.amount.unlocked().checked_add(self.amount.locked())?; + let amount_in_base_coin = self.vault.query_preview_redeem(querier, vault_coin_amount)?; + let price_res = oracle.query_price(querier, &vault_info.base_token)?; + let total_value = amount_in_base_coin + .checked_mul_floor(price_res.price) + .map_err(|_| StdError::generic_err("CheckedMultiplyFractionError"))?; + Ok(CoinValue { + denom: vault_info.vault_token, + amount: vault_coin_amount, + value: total_value, + }) + } + + fn base_coin_value(&self, querier: &QuerierWrapper, oracle: &Oracle) -> StdResult { + let vault_info = self.vault.query_info(querier)?; + let base_token_price = oracle.query_price(querier, &vault_info.base_token)?.price; + + let total_value = self.amount.unlocking().positions().iter().try_fold( + Uint128::zero(), + |acc, curr| -> StdResult { + let value = curr + .coin + .amount + .checked_mul_floor(base_token_price) + .map_err(|_| StdError::generic_err("CheckedMultiplyFractionError"))?; + Ok(acc.checked_add(value)?) + }, + )?; + + Ok(CoinValue { + denom: vault_info.base_token, + amount: self.amount.unlocking().total(), + value: total_value, + }) + } +} diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 13fc0c857..e67a67751 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -36,9 +36,123 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "set_positions_response" + ], + "properties": { + "set_positions_response": { + "type": "object", + "required": [ + "account_id", + "positions" + ], + "properties": { + "account_id": { + "type": "string" + }, + "positions": { + "$ref": "#/definitions/Positions" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "set_allowed_coins" + ], + "properties": { + "set_allowed_coins": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "set_vault_config" + ], + "properties": { + "set_vault_config": { + "type": "object", + "required": [ + "address", + "config" + ], + "properties": { + "address": { + "type": "string" + }, + "config": { + "$ref": "#/definitions/VaultConfig" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "DebtAmount": { + "type": "object", + "required": [ + "amount", + "denom", + "shares" + ], + "properties": { + "amount": { + "description": "amount of coins", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "type": "string" + }, + "shares": { + "description": "number of shares in debt pool", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" @@ -95,9 +209,208 @@ }, "additionalProperties": false }, + "LentAmount": { + "type": "object", + "required": [ + "amount", + "denom", + "shares" + ], + "properties": { + "amount": { + "description": "amount of coins", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "type": "string" + }, + "shares": { + "description": "number of shares in lent pool", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, + "LockingVaultAmount": { + "type": "object", + "required": [ + "locked", + "unlocking" + ], + "properties": { + "locked": { + "$ref": "#/definitions/VaultAmount" + }, + "unlocking": { + "$ref": "#/definitions/UnlockingPositions" + } + }, + "additionalProperties": false + }, + "Positions": { + "type": "object", + "required": [ + "account_id", + "debts", + "deposits", + "lends", + "vaults" + ], + "properties": { + "account_id": { + "type": "string" + }, + "debts": { + "type": "array", + "items": { + "$ref": "#/definitions/DebtAmount" + } + }, + "deposits": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "lends": { + "type": "array", + "items": { + "$ref": "#/definitions/LentAmount" + } + }, + "vaults": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultPosition" + } + } + }, + "additionalProperties": false + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" + }, + "UnlockingPositions": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultUnlockingPosition" + } + }, + "VaultAmount": { + "$ref": "#/definitions/Uint128" + }, + "VaultBase_for_Addr": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + }, + "VaultConfig": { + "type": "object", + "required": [ + "deposit_cap", + "liquidation_threshold", + "max_ltv", + "whitelisted" + ], + "properties": { + "deposit_cap": { + "$ref": "#/definitions/Coin" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv": { + "$ref": "#/definitions/Decimal" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "VaultPosition": { + "type": "object", + "required": [ + "amount", + "vault" + ], + "properties": { + "amount": { + "$ref": "#/definitions/VaultPositionAmount" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + }, + "additionalProperties": false + }, + "VaultPositionAmount": { + "oneOf": [ + { + "type": "object", + "required": [ + "unlocked" + ], + "properties": { + "unlocked": { + "$ref": "#/definitions/VaultAmount" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "locking" + ], + "properties": { + "locking": { + "$ref": "#/definitions/LockingVaultAmount" + } + }, + "additionalProperties": false + } + ] + }, + "VaultUnlockingPosition": { + "type": "object", + "required": [ + "coin", + "id" + ], + "properties": { + "coin": { + "description": "Coins that are awaiting to be unlocked (underlying, not vault tokens)", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false } } }, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index a5ac81d97..220ea0864 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -12,10 +12,23 @@ import { ExecuteMsg, Decimal, Uint128, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, + Addr, HealthResponse, + Positions, + DebtAmount, + Coin, + LentAmount, + VaultPosition, + LockingVaultAmount, + VaultUnlockingPosition, + VaultBaseForAddr, + VaultConfig, QueryMsg, VaultBaseForString, - Coin, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, @@ -24,28 +37,15 @@ import { DebtShares, ArrayOfLentShares, LentShares, - Addr, ArrayOfVaultWithBalance, VaultWithBalance, - VaultBaseForAddr, - VaultPositionAmount, - VaultAmount, - VaultAmount1, - UnlockingPositions, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, - VaultPosition, - LockingVaultAmount, - VaultUnlockingPosition, ArrayOfString, ConfigResponse, ArrayOfCoin, - Positions, - DebtAmount, - LentAmount, ArrayOfVaultInfoResponse, VaultInfoResponse, - VaultConfig, } from './MarsMockCreditManager.types' export interface MarsMockCreditManagerReadOnlyInterface { contractAddress: string @@ -352,6 +352,35 @@ export interface MarsMockCreditManagerInterface extends MarsMockCreditManagerRea memo?: string, funds?: Coin[], ) => Promise + setPositionsResponse: ( + { + accountId, + positions, + }: { + accountId: string + positions: Positions + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + setAllowedCoins: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + setVaultConfig: ( + { + address, + config, + }: { + address: string + config: VaultConfig + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise } export class MarsMockCreditManagerClient extends MarsMockCreditManagerQueryClient @@ -367,6 +396,9 @@ export class MarsMockCreditManagerClient this.sender = sender this.contractAddress = contractAddress this.setHealthResponse = this.setHealthResponse.bind(this) + this.setPositionsResponse = this.setPositionsResponse.bind(this) + this.setAllowedCoins = this.setAllowedCoins.bind(this) + this.setVaultConfig = this.setVaultConfig.bind(this) } setHealthResponse = async ( @@ -395,4 +427,72 @@ export class MarsMockCreditManagerClient funds, ) } + setPositionsResponse = async ( + { + accountId, + positions, + }: { + accountId: string + positions: Positions + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + set_positions_response: { + account_id: accountId, + positions, + }, + }, + fee, + memo, + funds, + ) + } + setAllowedCoins = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + set_allowed_coins: {}, + }, + fee, + memo, + funds, + ) + } + setVaultConfig = async ( + { + address, + config, + }: { + address: string + config: VaultConfig + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + set_vault_config: { + address, + config, + }, + }, + fee, + memo, + funds, + ) + } } diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index 83ad465f6..afadc31e4 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -13,10 +13,23 @@ import { ExecuteMsg, Decimal, Uint128, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, + Addr, HealthResponse, + Positions, + DebtAmount, + Coin, + LentAmount, + VaultPosition, + LockingVaultAmount, + VaultUnlockingPosition, + VaultBaseForAddr, + VaultConfig, QueryMsg, VaultBaseForString, - Coin, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, @@ -25,28 +38,15 @@ import { DebtShares, ArrayOfLentShares, LentShares, - Addr, ArrayOfVaultWithBalance, VaultWithBalance, - VaultBaseForAddr, - VaultPositionAmount, - VaultAmount, - VaultAmount1, - UnlockingPositions, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, - VaultPosition, - LockingVaultAmount, - VaultUnlockingPosition, ArrayOfString, ConfigResponse, ArrayOfCoin, - Positions, - DebtAmount, - LentAmount, ArrayOfVaultInfoResponse, VaultInfoResponse, - VaultConfig, } from './MarsMockCreditManager.types' export interface MarsMockCreditManagerMessage { contractAddress: string @@ -61,6 +61,27 @@ export interface MarsMockCreditManagerMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject + setPositionsResponse: ( + { + accountId, + positions, + }: { + accountId: string + positions: Positions + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + setAllowedCoins: (funds?: Coin[]) => MsgExecuteContractEncodeObject + setVaultConfig: ( + { + address, + config, + }: { + address: string + config: VaultConfig + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject } export class MarsMockCreditManagerMessageComposer implements MarsMockCreditManagerMessage { sender: string @@ -70,6 +91,9 @@ export class MarsMockCreditManagerMessageComposer implements MarsMockCreditManag this.sender = sender this.contractAddress = contractAddress this.setHealthResponse = this.setHealthResponse.bind(this) + this.setPositionsResponse = this.setPositionsResponse.bind(this) + this.setAllowedCoins = this.setAllowedCoins.bind(this) + this.setVaultConfig = this.setVaultConfig.bind(this) } setHealthResponse = ( @@ -99,4 +123,73 @@ export class MarsMockCreditManagerMessageComposer implements MarsMockCreditManag }), } } + setPositionsResponse = ( + { + accountId, + positions, + }: { + accountId: string + positions: Positions + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + set_positions_response: { + account_id: accountId, + positions, + }, + }), + ), + funds, + }), + } + } + setAllowedCoins = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + set_allowed_coins: {}, + }), + ), + funds, + }), + } + } + setVaultConfig = ( + { + address, + config, + }: { + address: string + config: VaultConfig + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + set_vault_config: { + address, + config, + }, + }), + ), + funds, + }), + } + } } diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index e85bda23e..4b01390ea 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -13,10 +13,23 @@ import { ExecuteMsg, Decimal, Uint128, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, + Addr, HealthResponse, + Positions, + DebtAmount, + Coin, + LentAmount, + VaultPosition, + LockingVaultAmount, + VaultUnlockingPosition, + VaultBaseForAddr, + VaultConfig, QueryMsg, VaultBaseForString, - Coin, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, @@ -25,28 +38,15 @@ import { DebtShares, ArrayOfLentShares, LentShares, - Addr, ArrayOfVaultWithBalance, VaultWithBalance, - VaultBaseForAddr, - VaultPositionAmount, - VaultAmount, - VaultAmount1, - UnlockingPositions, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, - VaultPosition, - LockingVaultAmount, - VaultUnlockingPosition, ArrayOfString, ConfigResponse, ArrayOfCoin, - Positions, - DebtAmount, - LentAmount, ArrayOfVaultInfoResponse, VaultInfoResponse, - VaultConfig, } from './MarsMockCreditManager.types' import { MarsMockCreditManagerQueryClient, @@ -558,6 +558,73 @@ export function useMarsMockCreditManagerConfigQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsMockCreditManagerSetVaultConfigMutation { + client: MarsMockCreditManagerClient + msg: { + address: string + config: VaultConfig + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsMockCreditManagerSetVaultConfigMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.setVaultConfig(msg, fee, memo, funds), + options, + ) +} +export interface MarsMockCreditManagerSetAllowedCoinsMutation { + client: MarsMockCreditManagerClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsMockCreditManagerSetAllowedCoinsMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, args: { fee, memo, funds } = {} }) => client.setAllowedCoins(fee, memo, funds), + options, + ) +} +export interface MarsMockCreditManagerSetPositionsResponseMutation { + client: MarsMockCreditManagerClient + msg: { + accountId: string + positions: Positions + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsMockCreditManagerSetPositionsResponseMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.setPositionsResponse(msg, fee, memo, funds), + options, + ) +} export interface MarsMockCreditManagerSetHealthResponseMutation { client: MarsMockCreditManagerClient msg: { diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 4de047a59..f195b5528 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -8,14 +8,41 @@ export interface InstantiateMsg { [k: string]: unknown } -export type ExecuteMsg = { - set_health_response: { - account_id: string - response: HealthResponse - } -} +export type ExecuteMsg = + | { + set_health_response: { + account_id: string + response: HealthResponse + } + } + | { + set_positions_response: { + account_id: string + positions: Positions + } + } + | { + set_allowed_coins: string[] + } + | { + set_vault_config: { + address: string + config: VaultConfig + } + } export type Decimal = string export type Uint128 = string +export type VaultPositionAmount = + | { + unlocked: VaultAmount + } + | { + locking: LockingVaultAmount + } +export type VaultAmount = string +export type VaultAmount1 = string +export type UnlockingPositions = VaultUnlockingPosition[] +export type Addr = string export interface HealthResponse { above_max_ltv: boolean liquidatable: boolean @@ -26,6 +53,49 @@ export interface HealthResponse { total_collateral_value: Uint128 total_debt_value: Uint128 } +export interface Positions { + account_id: string + debts: DebtAmount[] + deposits: Coin[] + lends: LentAmount[] + vaults: VaultPosition[] +} +export interface DebtAmount { + amount: Uint128 + denom: string + shares: Uint128 +} +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} +export interface LentAmount { + amount: Uint128 + denom: string + shares: Uint128 +} +export interface VaultPosition { + amount: VaultPositionAmount + vault: VaultBaseForAddr +} +export interface LockingVaultAmount { + locked: VaultAmount1 + unlocking: UnlockingPositions +} +export interface VaultUnlockingPosition { + coin: Coin + id: number +} +export interface VaultBaseForAddr { + address: Addr +} +export interface VaultConfig { + deposit_cap: Coin + liquidation_threshold: Decimal + max_ltv: Decimal + whitelisted: boolean +} export type QueryMsg = | { config: {} @@ -119,11 +189,6 @@ export type QueryMsg = export interface VaultBaseForString { address: string } -export interface Coin { - amount: Uint128 - denom: string - [k: string]: unknown -} export type ArrayOfCoinBalanceResponseItem = CoinBalanceResponseItem[] export interface CoinBalanceResponseItem { account_id: string @@ -146,42 +211,16 @@ export interface LentShares { denom: string shares: Uint128 } -export type Addr = string export type ArrayOfVaultWithBalance = VaultWithBalance[] export interface VaultWithBalance { balance: Uint128 vault: VaultBaseForAddr } -export interface VaultBaseForAddr { - address: Addr -} -export type VaultPositionAmount = - | { - unlocked: VaultAmount - } - | { - locking: LockingVaultAmount - } -export type VaultAmount = string -export type VaultAmount1 = string -export type UnlockingPositions = VaultUnlockingPosition[] export type ArrayOfVaultPositionResponseItem = VaultPositionResponseItem[] export interface VaultPositionResponseItem { account_id: string position: VaultPosition } -export interface VaultPosition { - amount: VaultPositionAmount - vault: VaultBaseForAddr -} -export interface LockingVaultAmount { - locked: VaultAmount1 - unlocking: UnlockingPositions -} -export interface VaultUnlockingPosition { - coin: Coin - id: number -} export type ArrayOfString = string[] export interface ConfigResponse { account_nft?: string | null @@ -195,32 +234,9 @@ export interface ConfigResponse { zapper: string } export type ArrayOfCoin = Coin[] -export interface Positions { - account_id: string - debts: DebtAmount[] - deposits: Coin[] - lends: LentAmount[] - vaults: VaultPosition[] -} -export interface DebtAmount { - amount: Uint128 - denom: string - shares: Uint128 -} -export interface LentAmount { - amount: Uint128 - denom: string - shares: Uint128 -} export type ArrayOfVaultInfoResponse = VaultInfoResponse[] export interface VaultInfoResponse { config: VaultConfig utilization: Coin vault: VaultBaseForString } -export interface VaultConfig { - deposit_cap: Coin - liquidation_threshold: Decimal - max_ltv: Decimal - whitelisted: boolean -} From c03de18305b6603cebe6efe4fac87d12381f5c5f Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 21 Feb 2023 19:41:50 +0100 Subject: [PATCH 139/218] Remove readme CEX example (#117) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ee72e0ec..c3a7e47f4 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Rover takes a different approach for Mars protocol, which utilizes a generalized The target audience of the credit manager is risk-seeking investors who wish to undertake leveraged trading or yield farming activities. -To start, a user first needs to access the Mars credit manager contract and request the opening of a credit account. The credit account is analogous to a "sub-account" on centralized trading platforms such as FTX, and is represented by a non-fungible token (NFT). +To start, a user first needs to access the Mars credit manager contract and request the opening of a credit account. The credit account is analogous to a "sub-account" on centralized trading platforms and is represented by a non-fungible token (NFT). ```rust pub enum ExecuteMsg { From c5eb453aa7cef4794fd623bf07437bacbdb7abec Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 22 Feb 2023 11:13:05 +0100 Subject: [PATCH 140/218] [Part 2] Integration of new health contract for all (#112) Integrate health contract --- Cargo.lock | 133 +-- Cargo.toml | 14 +- contracts/account-nft/Cargo.toml | 24 +- contracts/account-nft/examples/schema.rs | 2 +- contracts/account-nft/src/contract.rs | 10 +- .../account-nft/src}/error.rs | 3 + contracts/account-nft/src/execute.rs | 36 +- contracts/account-nft/src/lib.rs | 3 + .../account-nft/src}/msg/execute.rs | 2 +- .../account-nft/src}/msg/instantiate.rs | 3 + .../account-nft/src}/msg/mod.rs | 0 .../account-nft/src}/msg/query.rs | 2 +- .../account-nft/src}/nft_config.rs | 3 + contracts/account-nft/src/query.rs | 6 +- contracts/account-nft/src/state.rs | 3 +- .../tests/helpers/mock_contracts.rs | 8 +- .../account-nft/tests/helpers/mock_env.rs | 20 +- .../tests/helpers/mock_env_builder.rs | 118 +-- .../account-nft/tests/test_burn_allowance.rs | 28 +- .../account-nft/tests/test_instantiate.rs | 19 +- contracts/account-nft/tests/test_mint.rs | 9 +- contracts/account-nft/tests/test_ownership.rs | 1 - .../account-nft/tests/test_proposed_minter.rs | 2 +- .../account-nft/tests/test_update_config.rs | 6 +- contracts/credit-manager/Cargo.toml | 5 +- contracts/credit-manager/src/contract.rs | 14 +- contracts/credit-manager/src/execute.rs | 12 +- contracts/credit-manager/src/health.rs | 225 +---- contracts/credit-manager/src/instantiate.rs | 5 +- .../credit-manager/src/liquidate_coin.rs | 6 +- contracts/credit-manager/src/query.rs | 34 +- contracts/credit-manager/src/state.rs | 2 + contracts/credit-manager/src/update_config.rs | 13 +- contracts/credit-manager/src/vault/enter.rs | 4 +- contracts/credit-manager/src/vault/utils.rs | 25 +- .../credit-manager/tests/helpers/contracts.rs | 9 + .../credit-manager/tests/helpers/mock_env.rs | 124 ++- .../tests/test_enumerate_allowed_coins.rs | 5 - .../credit-manager/tests/test_update_admin.rs | 12 +- .../tests/test_update_config.rs | 16 +- .../tests/test_update_nft_config.rs | 26 +- .../tests/test_vault_query_info.rs | 64 ++ .../tests/test_vault_query_value.rs | 107 +++ contracts/health/examples/schema.rs | 2 +- contracts/health/src/querier.rs | 11 +- contracts/health/tests/helpers/mock_env.rs | 20 +- .../health/tests/helpers/mock_env_builder.rs | 1 + contracts/health/tests/test_compute_health.rs | 12 +- contracts/mock-credit-manager/src/contract.rs | 17 +- contracts/mock-credit-manager/src/execute.rs | 12 +- contracts/mock-credit-manager/src/msg.rs | 5 - contracts/mock-credit-manager/src/query.rs | 29 +- contracts/mock-credit-manager/src/state.rs | 2 - .../mock-health}/Cargo.toml | 11 +- contracts/mock-health/src/contract.rs | 47 + contracts/mock-health/src/lib.rs | 3 + contracts/mock-health/src/msg.rs | 10 + contracts/mock-health/src/state.rs | 4 + packages/health-types/Cargo.toml | 1 - packages/health-types/examples/schema.rs | 10 + packages/health/src/health.rs | 74 -- packages/health/src/lib.rs | 3 - packages/rover/Cargo.toml | 3 +- .../rover/src/adapters/account_nft/mod.rs | 5 - packages/rover/src/adapters/health.rs | 46 + packages/rover/src/adapters/mod.rs | 2 +- packages/rover/src/adapters/vault/base.rs | 24 +- packages/rover/src/error.rs | 2 +- packages/rover/src/msg/execute.rs | 9 +- packages/rover/src/msg/instantiate.rs | 4 + packages/rover/src/msg/query.rs | 34 +- schema.Makefile.toml | 2 + .../mars-account-nft/mars-account-nft.json | 19 + .../mars-credit-manager.json | 496 ++++++---- .../mars-mock-credit-manager.json | 464 ++++++---- .../mars-rover-health-computer.json | 562 ++++++++++++ .../mars-rover-health-types.json | 269 ++++++ scripts/deploy/addresses/osmo-test-4.json | 11 +- scripts/deploy/base/deployer.ts | 26 + scripts/deploy/base/index.ts | 3 + scripts/deploy/osmosis/config.ts | 2 +- scripts/package.json | 14 +- .../mars-account-nft/MarsAccountNft.types.ts | 3 + .../MarsCreditManager.client.ts | 56 +- .../MarsCreditManager.message-composer.ts | 21 +- .../MarsCreditManager.react-query.ts | 103 ++- .../MarsCreditManager.types.ts | 95 +- .../MarsMockCreditManager.client.ts | 81 +- .../MarsMockCreditManager.message-composer.ts | 45 +- .../MarsMockCreditManager.react-query.ts | 113 +-- .../MarsMockCreditManager.types.ts | 45 +- .../MarsRoverHealthComputer.client.ts | 32 + ...arsRoverHealthComputer.message-composer.ts | 32 + .../MarsRoverHealthComputer.react-query.ts | 33 + .../MarsRoverHealthComputer.types.ts | 118 +++ .../mars-rover-health-computer/bundle.ts | 14 + .../MarsRoverHealthTypes.client.ts | 125 +++ .../MarsRoverHealthTypes.message-composer.ts | 86 ++ .../MarsRoverHealthTypes.react-query.ts | 127 +++ .../MarsRoverHealthTypes.types.ts | 59 ++ .../mars-rover-health-types/bundle.ts | 14 + .../generated/mars-swapper-base/bundle.ts | 10 +- .../generated/mars-zapper-base/bundle.ts | 10 +- scripts/types/instantiateMsgs.ts | 2 + scripts/types/storageItems.ts | 3 + scripts/yarn.lock | 859 ++++++++++-------- 106 files changed, 3834 insertions(+), 1686 deletions(-) rename {packages/rover/src/adapters/account_nft => contracts/account-nft/src}/error.rs (84%) rename {packages/rover/src/adapters/account_nft => contracts/account-nft/src}/msg/execute.rs (98%) rename {packages/rover/src/adapters/account_nft => contracts/account-nft/src}/msg/instantiate.rs (89%) rename {packages/rover/src/adapters/account_nft => contracts/account-nft/src}/msg/mod.rs (100%) rename {packages/rover/src/adapters/account_nft => contracts/account-nft/src}/msg/query.rs (98%) rename {packages/rover/src/adapters/account_nft => contracts/account-nft/src}/nft_config.rs (80%) delete mode 100644 contracts/account-nft/tests/test_ownership.rs create mode 100644 contracts/credit-manager/tests/test_vault_query_info.rs create mode 100644 contracts/credit-manager/tests/test_vault_query_value.rs rename {packages/health => contracts/mock-health}/Cargo.toml (62%) create mode 100644 contracts/mock-health/src/contract.rs create mode 100644 contracts/mock-health/src/lib.rs create mode 100644 contracts/mock-health/src/msg.rs create mode 100644 contracts/mock-health/src/state.rs create mode 100644 packages/health-types/examples/schema.rs delete mode 100644 packages/health/src/health.rs delete mode 100644 packages/health/src/lib.rs delete mode 100644 packages/rover/src/adapters/account_nft/mod.rs create mode 100644 packages/rover/src/adapters/health.rs create mode 100644 schemas/mars-rover-health-computer/mars-rover-health-computer.json create mode 100644 schemas/mars-rover-health-types/mars-rover-health-types.json create mode 100644 scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts create mode 100644 scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts create mode 100644 scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts create mode 100644 scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts create mode 100644 scripts/types/generated/mars-rover-health-computer/bundle.ts create mode 100644 scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts create mode 100644 scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts create mode 100644 scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts create mode 100644 scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts create mode 100644 scripts/types/generated/mars-rover-health-types/bundle.ts diff --git a/Cargo.lock b/Cargo.lock index 773d95ee8..1bd3281ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,9 +24,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "apollo-utils" @@ -41,9 +41,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.62" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "689894c2db1ea643a50834b999abf1c110887402542955ff5451dab8f861f9ed" +checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", @@ -173,9 +173,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cexpr" @@ -204,9 +204,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.4.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +checksum = "77ed9a53e5d4d9c573ae844bfac6872b159cb1d1585a83b29e7a64b7eef7332a" dependencies = [ "glob", "libc", @@ -276,9 +276,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.1.9" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227315dc11f0bb22a273d0c43d3ba8ef52041c42cf959f09045388a89c57e661" +checksum = "7fecd74d3a0041114110d1260f77fcb644c5d2403549b37096c44f0e643a5177" dependencies = [ "digest 0.10.6", "ed25519-zebra", @@ -289,18 +289,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.1.9" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fca30d51f7e5fbfa6440d8b10d7df0231bdf77e97fd3fe5d0cb79cc4822e50c" +checksum = "d5abeeb891e6d0098402e4d3d042f90451db52651d2fe14b170e69a1dd3e4115" dependencies = [ "syn", ] [[package]] name = "cosmwasm-schema" -version = "1.1.9" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04135971e2c3b867eb793ca4e832543c077dbf72edaef7672699190f8fcdb619" +checksum = "9118e36843df6648fd0a626c46438f87038f296ec750cef3832cafc483c483f9" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -311,9 +311,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.1.9" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06c8f516a13ae481016aa35f0b5c4652459e8aee65b15b6fb51547a07cea5a0" +checksum = "78d6fc9854ac14e46cb69b0f396547893f93d2847aef975950ebbe73342324f3" dependencies = [ "proc-macro2", "quote", @@ -702,9 +702,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "elliptic-curve" @@ -777,9 +777,9 @@ checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", @@ -791,9 +791,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -801,33 +801,33 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-core", "futures-sink", @@ -948,9 +948,9 @@ checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -1026,9 +1026,7 @@ dependencies = [ "cw2 1.0.1", "cw721", "cw721-base", - "mars-health", - "mars-mock-credit-manager", - "mars-rover", + "mars-mock-rover-health", "mars-rover-health-types", "thiserror", ] @@ -1051,7 +1049,6 @@ dependencies = [ "cw721-base", "itertools", "mars-account-nft", - "mars-health", "mars-math", "mars-mock-oracle", "mars-mock-red-bank", @@ -1059,18 +1056,12 @@ dependencies = [ "mars-owner", "mars-red-bank-types", "mars-rover", + "mars-rover-health", + "mars-rover-health-types", "mars-swapper-mock", "mars-zapper-mock", ] -[[package]] -name = "mars-health" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", -] - [[package]] name = "mars-math" version = "1.0.0" @@ -1113,6 +1104,16 @@ dependencies = [ "mars-red-bank-types", ] +[[package]] +name = "mars-mock-rover-health" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "mars-rover-health-types", +] + [[package]] name = "mars-mock-vault" version = "1.0.0" @@ -1172,10 +1173,11 @@ dependencies = [ "cw-utils 0.16.0", "cw721", "cw721-base", - "mars-health", + "mars-account-nft", "mars-math", "mars-owner", "mars-red-bank-types", + "mars-rover-health-types", "schemars", "serde", "thiserror", @@ -1232,7 +1234,6 @@ dependencies = [ "cosmwasm-std", "mars-math", "mars-owner", - "mars-red-bank-types", "thiserror", ] @@ -1391,9 +1392,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "opaque-debug" @@ -1540,9 +1541,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] @@ -1771,9 +1772,9 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718dc5fff5b36f99093fc49b280cfc96ce6fc824317783bff5a1fed0c7a64819" +checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" dependencies = [ "serde", ] @@ -1802,9 +1803,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" dependencies = [ "itoa", "ryu", @@ -2093,9 +2094,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2103,9 +2104,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", @@ -2118,9 +2119,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2128,9 +2129,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -2141,9 +2142,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "which" diff --git a/Cargo.toml b/Cargo.toml index 45a6112f3..1270bab3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,13 +8,13 @@ members = [ "contracts/health", # mock contracts + "contracts/mock-credit-manager", + "contracts/mock-health", "contracts/mock-oracle", "contracts/mock-red-bank", "contracts/mock-vault", - "contracts/mock-credit-manager", # packages - "packages/health", # TODO: to delete "packages/health-computer", "packages/health-types", "packages/rover", @@ -38,7 +38,7 @@ keywords = ["mars", "cosmos", "cosmwasm"] [workspace.dependencies] anyhow = "1.0.66" cosmwasm-schema = "1.1.9" -cosmwasm-std = "1.1.9" +cosmwasm-std = "=1.1.9" # TODO: 1.2.1 requires CheckedMultiplyFractionError exposure: https://github.com/CosmWasm/cosmwasm/pull/1608 cw2 = "1.0.0" cw721 = "0.16.0" cw721-base = { version = "0.16.0", features = ["library"] } @@ -71,15 +71,17 @@ mars-owner = "1.0.0" mars-rover = { version = "1.0.0", path = "./packages/rover" } # contracts -mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } -mars-zapper-base = { version = "1.0.0", path = "./contracts/zapper/base" } +mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } +mars-rover-health = { version = "1.0.0", path = "./contracts/health" } +mars-swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } +mars-zapper-base = { version = "1.0.0", path = "./contracts/zapper/base" } # mocks mars-mock-credit-manager = { version = "1.0.0", path = "./contracts/mock-credit-manager", features = ["library"] } mars-mock-oracle = { version = "1.0.0", path = "./contracts/mock-oracle", features = ["library"] } mars-mock-red-bank = { version = "1.0.0", path = "./contracts/mock-red-bank", features = ["library"] } mars-mock-vault = { version = "1.0.0", path = "./contracts/mock-vault", features = ["library"] } +mars-mock-rover-health = { version = "1.0.0", path = "./contracts/mock-health", features = ["library"] } mars-swapper-mock = { version = "1.0.0", path = "./contracts/swapper/mock", features = ["library"] } mars-zapper-mock = { version = "1.0.0", path = "./contracts/zapper/mock", features = ["library"] } diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 5e5fea6e1..38939890a 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -19,18 +19,16 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -cw721 = { workspace = true } -cw721-base = { workspace = true } -cw-storage-plus = { workspace = true } -mars-health = { workspace = true } -mars-rover = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw721 = { workspace = true } +cw721-base = { workspace = true } +cw-storage-plus = { workspace = true } +mars-rover-health-types = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] -anyhow = { workspace = true } -cw-multi-test = { workspace = true } -mars-rover-health-types = { workspace = true } -mars-mock-credit-manager = { workspace = true } +anyhow = { workspace = true } +cw-multi-test = { workspace = true } +mars-mock-rover-health = { workspace = true } diff --git a/contracts/account-nft/examples/schema.rs b/contracts/account-nft/examples/schema.rs index ccb459acd..b41c869ad 100644 --- a/contracts/account-nft/examples/schema.rs +++ b/contracts/account-nft/examples/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use mars_rover::adapters::account_nft::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_account_nft::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 72264749b..fc0ba99ba 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -8,12 +8,12 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw721::ContractInfoResponse; use cw721_base::Cw721Contract; -use mars_rover::adapters::account_nft::{ - ContractError, ExecuteMsg, InstantiateMsg, NftConfig, QueryMsg, -}; use crate::{ + error::ContractError, execute::{accept_minter_role, burn, mint, update_config}, + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + nft_config::NftConfig, query::{query_config, query_next_id}, state::{CONFIG, NEXT_ID}, }; @@ -35,11 +35,15 @@ pub fn instantiate( NEXT_ID.save(deps.storage, &1)?; + let health_contract_addr = + msg.health_contract.map(|unchecked| deps.api.addr_validate(&unchecked)).transpose()?; + CONFIG.save( deps.storage, &NftConfig { max_value_for_burn: msg.max_value_for_burn, proposed_new_minter: None, + health_contract_addr, }, )?; diff --git a/packages/rover/src/adapters/account_nft/error.rs b/contracts/account-nft/src/error.rs similarity index 84% rename from packages/rover/src/adapters/account_nft/error.rs rename to contracts/account-nft/src/error.rs index 779d64378..162957ccb 100644 --- a/packages/rover/src/adapters/account_nft/error.rs +++ b/contracts/account-nft/src/error.rs @@ -20,4 +20,7 @@ pub enum ContractError { current_balances: Uint128, max_value_allowed: Uint128, }, + + #[error("Health contract should be added to config before burns are allowed")] + HealthContractNotSet, } diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index f1c358fc0..b87c122f3 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -3,18 +3,15 @@ use cosmwasm_std::{ }; use cw721::Cw721Execute; use cw721_base::MintMsg; -use mars_health::HealthResponse; -use mars_rover::{ - adapters::account_nft::{ - ContractError, - ContractError::{BaseError, BurnNotAllowed}, - NftConfigUpdates, - }, - msg::QueryMsg::Health, -}; +use mars_rover_health_types::{HealthResponse, QueryMsg::Health}; use crate::{ contract::Parent, + error::{ + ContractError, + ContractError::{BaseError, BurnNotAllowed, HealthContractNotSet}, + }, + nft_config::NftConfigUpdates, state::{CONFIG, NEXT_ID}, }; @@ -44,21 +41,24 @@ pub fn burn( info: MessageInfo, token_id: String, ) -> Result { + let config = CONFIG.load(deps.storage)?; + if config.health_contract_addr.is_none() { + return Err(HealthContractNotSet); + } + let response: HealthResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - // Expects the minter to be the credit manager - contract_addr: Parent::default().minter.load(deps.storage)?.into(), + contract_addr: config.health_contract_addr.unwrap().into(), msg: to_binary(&Health { account_id: token_id.clone(), })?, }))?; - let max_value_allowed = CONFIG.load(deps.storage)?.max_value_for_burn; let current_balances = response.total_debt_value.checked_add(response.total_collateral_value)?; - if current_balances > max_value_allowed { + if current_balances > config.max_value_for_burn { return Err(BurnNotAllowed { current_balances, - max_value_allowed, + max_value_allowed: config.max_value_for_burn, }); } @@ -78,6 +78,14 @@ pub fn update_config( let mut response = Response::new().add_attribute("action", "update_config"); let mut config = CONFIG.load(deps.storage)?; + if let Some(unchecked) = updates.health_contract_addr { + let addr = deps.api.addr_validate(&unchecked)?; + config.health_contract_addr = Some(addr.clone()); + response = response + .add_attribute("key", "health_contract_addr") + .add_attribute("value", addr.to_string()); + } + if let Some(max) = updates.max_value_for_burn { config.max_value_for_burn = max; response = response diff --git a/contracts/account-nft/src/lib.rs b/contracts/account-nft/src/lib.rs index b36849240..5a129b1c2 100644 --- a/contracts/account-nft/src/lib.rs +++ b/contracts/account-nft/src/lib.rs @@ -1,4 +1,7 @@ pub mod contract; +pub mod error; pub mod execute; +pub mod msg; +pub mod nft_config; pub mod query; pub mod state; diff --git a/packages/rover/src/adapters/account_nft/msg/execute.rs b/contracts/account-nft/src/msg/execute.rs similarity index 98% rename from packages/rover/src/adapters/account_nft/msg/execute.rs rename to contracts/account-nft/src/msg/execute.rs index 31a44a206..859988138 100644 --- a/packages/rover/src/adapters/account_nft/msg/execute.rs +++ b/contracts/account-nft/src/msg/execute.rs @@ -5,7 +5,7 @@ use cosmwasm_std::{Binary, Empty, StdError}; use cw721::Expiration; use cw721_base::ExecuteMsg as ParentExecuteMsg; -use crate::adapters::account_nft::{ContractError, NftConfigUpdates}; +use crate::{error::ContractError, nft_config::NftConfigUpdates}; #[cw_serde] pub enum ExecuteMsg { diff --git a/packages/rover/src/adapters/account_nft/msg/instantiate.rs b/contracts/account-nft/src/msg/instantiate.rs similarity index 89% rename from packages/rover/src/adapters/account_nft/msg/instantiate.rs rename to contracts/account-nft/src/msg/instantiate.rs index f5329167b..4be27949b 100644 --- a/packages/rover/src/adapters/account_nft/msg/instantiate.rs +++ b/contracts/account-nft/src/msg/instantiate.rs @@ -10,6 +10,9 @@ pub struct InstantiateMsg { /// The maximum value of Debts + Collaterals (denominated in base token) for an account /// before burns are disallowed for the NFT. Meant to prevent accidental account deletions pub max_value_for_burn: Uint128, + /// Used to validate the account id's health status allows for burning. + /// Can be set later, but no burning allowed until set. + pub health_contract: Option, //-------------------------------------------------------------------------------------------------- // Base cw721 messages diff --git a/packages/rover/src/adapters/account_nft/msg/mod.rs b/contracts/account-nft/src/msg/mod.rs similarity index 100% rename from packages/rover/src/adapters/account_nft/msg/mod.rs rename to contracts/account-nft/src/msg/mod.rs diff --git a/packages/rover/src/adapters/account_nft/msg/query.rs b/contracts/account-nft/src/msg/query.rs similarity index 98% rename from packages/rover/src/adapters/account_nft/msg/query.rs rename to contracts/account-nft/src/msg/query.rs index fb4a34585..d37ed9126 100644 --- a/packages/rover/src/adapters/account_nft/msg/query.rs +++ b/contracts/account-nft/src/msg/query.rs @@ -10,7 +10,7 @@ pub enum QueryMsg { //-------------------------------------------------------------------------------------------------- // Extended messages //-------------------------------------------------------------------------------------------------- - #[returns(crate::adapters::account_nft::UncheckedNftConfig)] + #[returns(crate::nft_config::UncheckedNftConfig)] Config {}, #[returns(u64)] diff --git a/packages/rover/src/adapters/account_nft/nft_config.rs b/contracts/account-nft/src/nft_config.rs similarity index 80% rename from packages/rover/src/adapters/account_nft/nft_config.rs rename to contracts/account-nft/src/nft_config.rs index 7c6f2a247..6dc3c4aa2 100644 --- a/packages/rover/src/adapters/account_nft/nft_config.rs +++ b/contracts/account-nft/src/nft_config.rs @@ -5,6 +5,7 @@ use cosmwasm_std::{Addr, Uint128}; pub struct NftConfigBase { pub max_value_for_burn: Uint128, pub proposed_new_minter: Option, + pub health_contract_addr: Option, } pub type NftConfig = NftConfigBase; @@ -15,6 +16,7 @@ impl From for UncheckedNftConfig { Self { max_value_for_burn: config.max_value_for_burn, proposed_new_minter: config.proposed_new_minter.map(Into::into), + health_contract_addr: config.health_contract_addr.map(Into::into), } } } @@ -23,4 +25,5 @@ impl From for UncheckedNftConfig { pub struct NftConfigUpdates { pub max_value_for_burn: Option, pub proposed_new_minter: Option, + pub health_contract_addr: Option, } diff --git a/contracts/account-nft/src/query.rs b/contracts/account-nft/src/query.rs index 12d4102d4..0ebf69e2a 100644 --- a/contracts/account-nft/src/query.rs +++ b/contracts/account-nft/src/query.rs @@ -1,7 +1,9 @@ use cosmwasm_std::{Deps, StdResult}; -use mars_rover::adapters::account_nft::UncheckedNftConfig; -use crate::state::{CONFIG, NEXT_ID}; +use crate::{ + nft_config::UncheckedNftConfig, + state::{CONFIG, NEXT_ID}, +}; pub fn query_config(deps: Deps) -> StdResult { Ok(CONFIG.load(deps.storage)?.into()) diff --git a/contracts/account-nft/src/state.rs b/contracts/account-nft/src/state.rs index cc685680e..b7cb2ff90 100644 --- a/contracts/account-nft/src/state.rs +++ b/contracts/account-nft/src/state.rs @@ -1,5 +1,6 @@ use cw_storage_plus::Item; -use mars_rover::adapters::account_nft::NftConfig; + +use crate::nft_config::NftConfig; pub const CONFIG: Item = Item::new("config"); pub const NEXT_ID: Item = Item::new("next_id"); diff --git a/contracts/account-nft/tests/helpers/mock_contracts.rs b/contracts/account-nft/tests/helpers/mock_contracts.rs index 6e4241982..20c0cfa63 100644 --- a/contracts/account-nft/tests/helpers/mock_contracts.rs +++ b/contracts/account-nft/tests/helpers/mock_contracts.rs @@ -10,11 +10,11 @@ pub fn mock_nft_contract() -> Box> { Box::new(contract) } -pub fn mock_credit_manager_contract() -> Box> { +pub fn mock_health_contract() -> Box> { let contract = ContractWrapper::new( - mars_mock_credit_manager::contract::execute, - mars_mock_credit_manager::contract::instantiate, - mars_mock_credit_manager::contract::query, + mars_mock_rover_health::contract::execute, + mars_mock_rover_health::contract::instantiate, + mars_mock_rover_health::contract::query, ); Box::new(contract) } diff --git a/contracts/account-nft/tests/helpers/mock_env.rs b/contracts/account-nft/tests/helpers/mock_env.rs index 6113afc84..d3c8e54e3 100644 --- a/contracts/account-nft/tests/helpers/mock_env.rs +++ b/contracts/account-nft/tests/helpers/mock_env.rs @@ -2,12 +2,15 @@ use anyhow::Result as AnyResult; use cosmwasm_std::Addr; use cw721::OwnerOfResponse; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; -use mars_mock_credit_manager::msg::ExecuteMsg::SetHealthResponse; -use mars_rover::adapters::account_nft::{ - ExecuteMsg, - ExecuteMsg::{AcceptMinterRole, UpdateConfig}, - NftConfigUpdates, QueryMsg, UncheckedNftConfig, +use mars_account_nft::{ + msg::{ + ExecuteMsg, + ExecuteMsg::{AcceptMinterRole, UpdateConfig}, + QueryMsg, + }, + nft_config::{NftConfigUpdates, UncheckedNftConfig}, }; +use mars_mock_rover_health::msg::ExecuteMsg::SetHealthResponse; use mars_rover_health_types::HealthResponse; use crate::helpers::MockEnvBuilder; @@ -27,6 +30,8 @@ impl MockEnv { minter: None, deployer: Addr::unchecked("deployer"), nft_contract: None, + health_contract: None, + set_health_contract: true, } } @@ -60,10 +65,12 @@ impl MockEnv { account_id: &str, response: &HealthResponse, ) -> AppResponse { + let config = self.query_config(); + self.app .execute_contract( sender.clone(), - self.minter.clone(), + Addr::unchecked(config.health_contract_addr.unwrap()), &SetHealthResponse { account_id: account_id.to_string(), response: response.clone(), @@ -116,6 +123,7 @@ impl MockEnv { &NftConfigUpdates { max_value_for_burn: None, proposed_new_minter: Some(proposed_new_minter.to_string()), + health_contract_addr: None, }, ) } diff --git a/contracts/account-nft/tests/helpers/mock_env_builder.rs b/contracts/account-nft/tests/helpers/mock_env_builder.rs index 5cc5b832a..579ab9694 100644 --- a/contracts/account-nft/tests/helpers/mock_env_builder.rs +++ b/contracts/account-nft/tests/helpers/mock_env_builder.rs @@ -1,26 +1,19 @@ use std::mem::take; use anyhow::Result as AnyResult; -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Empty}; use cw_multi_test::{BasicApp, Executor}; -use mars_mock_credit_manager::msg::InstantiateMsg as MockCmInstantiateMsg; -use mars_rover::{ - adapters::account_nft::{ - ExecuteMsg::{AcceptMinterRole, UpdateConfig}, - InstantiateMsg, NftConfigUpdates, - }, - msg::query::ConfigResponse, -}; +use mars_account_nft::msg::InstantiateMsg; -use crate::helpers::{ - mock_credit_manager_contract, mock_nft_contract, MockEnv, MAX_VALUE_FOR_BURN, -}; +use crate::helpers::{mock_health_contract, mock_nft_contract, MockEnv, MAX_VALUE_FOR_BURN}; pub struct MockEnvBuilder { pub app: BasicApp, - pub minter: Option, pub deployer: Addr, + pub minter: Option, + pub health_contract: Option, pub nft_contract: Option, + pub set_health_contract: bool, } impl MockEnvBuilder { @@ -33,11 +26,47 @@ impl MockEnvBuilder { }) } + pub fn instantiate_with_health_contract(&mut self, bool: bool) -> &mut Self { + self.set_health_contract = bool; + self + } + pub fn set_minter(&mut self, minter: &str) -> &mut Self { self.minter = Some(Addr::unchecked(minter.to_string())); self } + pub fn set_health_contract(&mut self, contract_addr: &str) -> &mut Self { + self.health_contract = Some(Addr::unchecked(contract_addr.to_string())); + self + } + + fn get_health_contract(&mut self) -> Addr { + if self.health_contract.is_none() { + return self.deploy_health_contract(); + } + self.health_contract.clone().unwrap() + } + + fn deploy_health_contract(&mut self) -> Addr { + let contract = mock_health_contract(); + let code_id = self.app.store_code(contract); + + let health_contract = self + .app + .instantiate_contract( + code_id, + self.deployer.clone(), + &Empty {}, + &[], + "mock-health-contract", + None, + ) + .unwrap(); + self.health_contract = Some(health_contract.clone()); + health_contract + } + fn get_minter(&mut self) -> Addr { self.minter.clone().unwrap_or_else(|| self.deployer.clone()) } @@ -53,6 +82,11 @@ impl MockEnvBuilder { let contract = mock_nft_contract(); let code_id = self.app.store_code(contract); let minter = self.get_minter().into(); + let health_contract = if self.set_health_contract { + Some(self.get_health_contract().into()) + } else { + None + }; let addr = self .app @@ -64,6 +98,7 @@ impl MockEnvBuilder { name: "mock_nft".to_string(), symbol: "MOCK".to_string(), minter, + health_contract, }, &[], "mock-account-nft", @@ -72,61 +107,4 @@ impl MockEnvBuilder { .unwrap(); self.nft_contract = Some(addr); } - - pub fn assign_minter_to_cm(&mut self) -> &mut Self { - let contract = mock_credit_manager_contract(); - let code_id = self.app.store_code(contract); - - let cm_addr = self - .app - .instantiate_contract( - code_id, - self.deployer.clone(), - // Will be removed on upcoming PR - &MockCmInstantiateMsg { - config: ConfigResponse { - owner: None, - proposed_new_owner: None, - account_nft: None, - red_bank: "".to_string(), - oracle: "".to_string(), - max_close_factor: Default::default(), - max_unlocking_positions: Default::default(), - swapper: "".to_string(), - zapper: "".to_string(), - }, - }, - &[], - "mock-credit-manager", - None, - ) - .unwrap(); - - let nft_contract = self.get_nft_contract(); - - let minter = self.get_minter(); - - // Propose new minter - self.app - .execute_contract( - minter, - nft_contract.clone(), - &UpdateConfig { - updates: NftConfigUpdates { - max_value_for_burn: None, - proposed_new_minter: Some(cm_addr.clone().into()), - }, - }, - &[], - ) - .unwrap(); - - // Accept new role - self.app - .execute_contract(cm_addr.clone(), nft_contract, &AcceptMinterRole {}, &[]) - .unwrap(); - - self.minter = Some(cm_addr); - self - } } diff --git a/contracts/account-nft/tests/test_burn_allowance.rs b/contracts/account-nft/tests/test_burn_allowance.rs index 53b704d9f..76fcee6ad 100644 --- a/contracts/account-nft/tests/test_burn_allowance.rs +++ b/contracts/account-nft/tests/test_burn_allowance.rs @@ -1,16 +1,30 @@ use cosmwasm_std::{Addr, Empty, StdResult, Uint128}; use cw721::NftInfoResponse; -use mars_rover::adapters::account_nft::{ - ContractError, ContractError::BurnNotAllowed, QueryMsg::NftInfo, +use mars_account_nft::{ + error::{ + ContractError, + ContractError::{BurnNotAllowed, HealthContractNotSet}, + }, + msg::QueryMsg::NftInfo, }; use crate::helpers::{below_max_for_burn, generate_health_response, MockEnv, MAX_VALUE_FOR_BURN}; pub mod helpers; +#[test] +fn burn_not_allowed_if_no_cm_set() { + let mut mock = MockEnv::new().instantiate_with_health_contract(false).build().unwrap(); + let user = Addr::unchecked("user"); + let token_id = mock.mint(&user).unwrap(); + let res = mock.burn(&user, &token_id); + let error: ContractError = res.unwrap_err().downcast().unwrap(); + assert_eq!(error, HealthContractNotSet) +} + #[test] fn burn_not_allowed_if_too_many_debts() { - let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); + let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); let token_id = mock.mint(&user).unwrap(); @@ -29,7 +43,7 @@ fn burn_not_allowed_if_too_many_debts() { #[test] fn burn_not_allowed_if_too_much_collateral() { - let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); + let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); let token_id = mock.mint(&user).unwrap(); @@ -48,7 +62,7 @@ fn burn_not_allowed_if_too_much_collateral() { #[test] fn burn_allowance_works_with_both_debt_and_collateral() { - let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); + let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); let token_id = mock.mint(&user).unwrap(); @@ -67,7 +81,7 @@ fn burn_allowance_works_with_both_debt_and_collateral() { #[test] fn burn_allowance_at_exactly_max() { - let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); + let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); let token_id = mock.mint(&user).unwrap(); @@ -78,7 +92,7 @@ fn burn_allowance_at_exactly_max() { #[test] fn burn_allowance_when_under_max() { - let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); + let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); let token_id = mock.mint(&user).unwrap(); diff --git a/contracts/account-nft/tests/test_instantiate.rs b/contracts/account-nft/tests/test_instantiate.rs index 48ceff4ee..2de246d3b 100644 --- a/contracts/account-nft/tests/test_instantiate.rs +++ b/contracts/account-nft/tests/test_instantiate.rs @@ -3,11 +3,26 @@ use crate::helpers::{MockEnv, MAX_VALUE_FOR_BURN}; pub mod helpers; #[test] -fn storage_vars_set_on_instantiate() { - let mut mock = MockEnv::new().build().unwrap(); +fn instantiated_storage_vars() { + let mut mock = MockEnv::new().instantiate_with_health_contract(false).build().unwrap(); let config = mock.query_config(); assert_eq!(config.proposed_new_minter, None); + assert_eq!(config.health_contract_addr, None); + assert_eq!(config.max_value_for_burn, MAX_VALUE_FOR_BURN); + + let next_id = mock.query_next_id(); + assert_eq!(next_id, 1); +} + +#[test] +fn instantiated_storage_vars_with_health_contract() { + let health_contract = "health_contract_xyz_abc"; + let mut mock = MockEnv::new().set_health_contract(health_contract).build().unwrap(); + + let config = mock.query_config(); + assert_eq!(config.proposed_new_minter, None); + assert_eq!(config.health_contract_addr, Some(health_contract.to_string())); assert_eq!(config.max_value_for_burn, MAX_VALUE_FOR_BURN); let next_id = mock.query_next_id(); diff --git a/contracts/account-nft/tests/test_mint.rs b/contracts/account-nft/tests/test_mint.rs index eca2f3478..a4fffbb9c 100644 --- a/contracts/account-nft/tests/test_mint.rs +++ b/contracts/account-nft/tests/test_mint.rs @@ -4,8 +4,9 @@ use cosmwasm_std::Addr; use cw721::OwnerOfResponse; use cw721_base::ContractError::Unauthorized; use cw_multi_test::Executor; -use mars_rover::adapters::account_nft::{ - ContractError, ContractError::BaseError, ExecuteMsg, QueryMsg::OwnerOf, +use mars_account_nft::{ + error::{ContractError, ContractError::BaseError}, + msg::{ExecuteMsg, QueryMsg::OwnerOf}, }; use crate::helpers::{below_max_for_burn, MockEnv}; @@ -34,7 +35,7 @@ fn id_incrementer() { #[test] fn id_incrementer_works_despite_burns() { - let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); + let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); let token_id_1 = mock.mint(&user).unwrap(); @@ -72,7 +73,7 @@ fn only_minter_can_mint() { #[test] fn only_token_owner_can_burn() { - let mut mock = MockEnv::new().assign_minter_to_cm().build().unwrap(); + let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); let token_id = mock.mint(&user).unwrap(); diff --git a/contracts/account-nft/tests/test_ownership.rs b/contracts/account-nft/tests/test_ownership.rs deleted file mode 100644 index 8b1378917..000000000 --- a/contracts/account-nft/tests/test_ownership.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/contracts/account-nft/tests/test_proposed_minter.rs b/contracts/account-nft/tests/test_proposed_minter.rs index ccb139e92..7738ba3d6 100644 --- a/contracts/account-nft/tests/test_proposed_minter.rs +++ b/contracts/account-nft/tests/test_proposed_minter.rs @@ -1,6 +1,6 @@ use cosmwasm_std::Addr; use cw721_base::MinterResponse; -use mars_rover::adapters::account_nft::QueryMsg; +use mars_account_nft::msg::QueryMsg; use crate::helpers::MockEnv; diff --git a/contracts/account-nft/tests/test_update_config.rs b/contracts/account-nft/tests/test_update_config.rs index ad8d26748..2a5b51af0 100644 --- a/contracts/account-nft/tests/test_update_config.rs +++ b/contracts/account-nft/tests/test_update_config.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{Addr, Uint128}; -use mars_rover::adapters::account_nft::NftConfigUpdates; +use mars_account_nft::nft_config::NftConfigUpdates; use crate::helpers::MockEnv; @@ -15,6 +15,7 @@ fn only_minter_can_update_config() { &NftConfigUpdates { max_value_for_burn: None, proposed_new_minter: None, + health_contract_addr: None, }, ); @@ -29,10 +30,12 @@ fn minter_can_update_config() { let new_max_burn_val = Uint128::new(4918453); let new_proposed_minter = "new_proposed_minter".to_string(); + let new_health_contract = "new_health_contract_123".to_string(); let updates = NftConfigUpdates { max_value_for_burn: Some(new_max_burn_val), proposed_new_minter: Some(new_proposed_minter.clone()), + health_contract_addr: Some(new_health_contract.clone()), }; mock.update_config(&mock.minter.clone(), &updates).unwrap(); @@ -40,4 +43,5 @@ fn minter_can_update_config() { let config = mock.query_config(); assert_eq!(config.max_value_for_burn, new_max_burn_val); assert_eq!(config.proposed_new_minter.unwrap(), new_proposed_minter); + assert_eq!(config.health_contract_addr.unwrap(), new_health_contract); } diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 71a572193..107bddab0 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -28,8 +28,9 @@ cw721-base = { workspace = true } cw-item-set = { workspace = true } cw-paginate = { workspace = true } cw-storage-plus = { workspace = true } +mars-account-nft = { workspace = true } mars-math = { workspace = true } -mars-health = { workspace = true } +mars-rover-health-types = { workspace = true } mars-red-bank-types = { workspace = true } mars-owner = { workspace = true } mars-rover = { workspace = true } @@ -39,9 +40,9 @@ anyhow = { workspace = true } cw-multi-test = { workspace = true } cw-utils = { workspace = true } itertools = { workspace = true } -mars-account-nft = { workspace = true } mars-mock-oracle = { workspace = true } mars-mock-red-bank = { workspace = true } mars-mock-vault = { workspace = true } +mars-rover-health = { workspace = true } mars-swapper-mock = { workspace = true } mars-zapper-mock = { workspace = true } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 11d9a62ff..e632b46aa 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -2,7 +2,6 @@ use cosmwasm_std::{ entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, }; use cw2::set_contract_version; -use mars_health::HealthResponse; use mars_rover::{ adapters::vault::VAULT_REQUEST_REPLY_ID, error::{ContractError, ContractResult}, @@ -11,14 +10,14 @@ use mars_rover::{ use crate::{ execute::{create_credit_account, dispatch_actions, execute_callback}, - health::compute_health, instantiate::store_config, query::{ query_all_coin_balances, query_all_debt_shares, query_all_lent_shares, query_all_total_debt_shares, query_all_total_lent_shares, query_all_total_vault_coin_balances, query_all_vault_positions, query_allowed_coins, query_config, query_positions, query_total_debt_shares, query_total_lent_shares, - query_total_vault_coin_balance, query_vaults_info, + query_total_vault_coin_balance, query_vault_info, query_vault_position_value, + query_vaults_info, }, update_config::{update_config, update_nft_config, update_owner}, vault::handle_unlock_request_reply, @@ -76,6 +75,9 @@ pub fn reply(deps: DepsMut, _: Env, reply: Reply) -> ContractResult { pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { let res = match msg { QueryMsg::Config {} => to_binary(&query_config(deps)?), + QueryMsg::VaultInfo { + vault, + } => to_binary(&query_vault_info(deps, env, vault)?), QueryMsg::VaultsInfo { start_after, limit, @@ -87,9 +89,6 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { QueryMsg::Positions { account_id, } => to_binary(&query_positions(deps, &env, &account_id)?), - QueryMsg::Health { - account_id, - } => to_binary::(&Into::into(compute_health(deps, &env, &account_id)?)), QueryMsg::AllCoinBalances { start_after, limit, @@ -135,6 +134,9 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { QueryMsg::EstimateWithdrawLiquidity { lp_token, } => to_binary(&estimate_withdraw_liquidity(deps, lp_token)?), + QueryMsg::VaultPositionValue { + vault_position, + } => to_binary(&query_vault_position_value(deps, vault_position)?), }; res.map_err(Into::into) } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index c3966796b..325a939c6 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -1,8 +1,8 @@ use cosmwasm_std::{ to_binary, Addr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdResult, WasmMsg, }; +use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; use mars_rover::{ - adapters::account_nft::ExecuteMsg as NftExecuteMsg, coins::Coins, error::{ContractError, ContractResult}, msg::execute::{Action, CallbackMsg}, @@ -11,7 +11,7 @@ use mars_rover::{ use crate::{ borrow::borrow, deposit::deposit, - health::{assert_max_ltv, compute_health}, + health::{assert_max_ltv, query_health}, lend::lend, liquidate_coin::liquidate_coin, refund::refund_coin_balances, @@ -55,7 +55,7 @@ pub fn dispatch_actions( let mut response = Response::new(); let mut callbacks: Vec = vec![]; let mut received_coins = Coins::try_from(info.funds)?; - let prev_health = compute_health(deps.as_ref(), &env, account_id)?; + let prev_health = query_health(deps.as_ref(), account_id)?; for action in actions { match action { @@ -180,7 +180,7 @@ pub fn dispatch_actions( // Else, throw error and revert all actions CallbackMsg::AssertMaxLTV { account_id: account_id.to_string(), - prev_health, + prev_max_ltv_health_factor: prev_health.max_ltv_health_factor, }, ]); @@ -224,8 +224,8 @@ pub fn execute_callback( } => lend(deps, env, &account_id, coin), CallbackMsg::AssertMaxLTV { account_id, - prev_health, - } => assert_max_ltv(deps.as_ref(), env, &account_id, prev_health), + prev_max_ltv_health_factor, + } => assert_max_ltv(deps.as_ref(), env, &account_id, &prev_max_ltv_health_factor), CallbackMsg::EnterVault { account_id, vault, diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 2fb258421..326b181ed 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -1,228 +1,31 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Coin, Decimal, Deps, Env, Event, Response, Uint128}; -use mars_health::Health; -use mars_math::FractionMath; -use mars_red_bank_types::{oracle::PriceResponse, red_bank::Market}; +use cosmwasm_std::{Decimal, Deps, Env, Event, Response}; use mars_rover::{ - adapters::vault::VaultPosition, error::{ContractError, ContractResult}, - msg::query::{DebtAmount, Positions}, traits::Stringify, }; +use mars_rover_health_types::{is_below_one, HealthResponse}; -use crate::{ - query::query_positions, - state::{ALLOWED_COINS, ORACLE, RED_BANK, VAULT_CONFIGS}, - vault::vault_is_whitelisted, -}; - -/// Used as storage when trying to compute Health -#[cw_serde] -struct CollateralValue { - pub total_collateral_value: Uint128, - pub max_ltv_adjusted_collateral: Uint128, - pub liquidation_threshold_adjusted_collateral: Uint128, -} - -/// The mars-health package, red bank, and oracle do not have knowledge of vault config or pricing. -/// Cannot use the health package so forking and adjusting for rover internally here. -pub fn compute_health(deps: Deps, env: &Env, account_id: &str) -> ContractResult { - let positions = query_positions(deps, env, account_id)?; - - let CollateralValue { - total_collateral_value, - max_ltv_adjusted_collateral, - liquidation_threshold_adjusted_collateral, - } = calculate_collateral_value(&deps, &positions)?; - - let total_debt_value = calculate_total_debt_value(&deps, &positions.debts)?; - - let max_ltv_health_factor = if total_debt_value.is_zero() { - None - } else { - Some(Decimal::checked_from_ratio(max_ltv_adjusted_collateral, total_debt_value)?) - }; - - let liquidation_health_factor = if total_debt_value.is_zero() { - None - } else { - Some(Decimal::checked_from_ratio( - liquidation_threshold_adjusted_collateral, - total_debt_value, - )?) - }; - - Ok(Health { - total_debt_value, - total_collateral_value, - max_ltv_adjusted_collateral, - liquidation_threshold_adjusted_collateral, - max_ltv_health_factor, - liquidation_health_factor, - }) -} - -fn calculate_collateral_value( - deps: &Deps, - positions: &Positions, -) -> ContractResult { - let deposits = calculate_deposits_value(deps, &positions.deposits)?; - let vaults = calculate_vaults_value(deps, &positions.vaults)?; - - Ok(CollateralValue { - total_collateral_value: deposits - .total_collateral_value - .checked_add(vaults.total_collateral_value)?, - max_ltv_adjusted_collateral: deposits - .max_ltv_adjusted_collateral - .checked_add(vaults.max_ltv_adjusted_collateral)?, - liquidation_threshold_adjusted_collateral: deposits - .liquidation_threshold_adjusted_collateral - .checked_add(vaults.liquidation_threshold_adjusted_collateral)?, - }) -} - -fn calculate_vaults_value( - deps: &Deps, - vaults: &[VaultPosition], -) -> ContractResult { - let oracle = ORACLE.load(deps.storage)?; - let red_bank = RED_BANK.load(deps.storage)?; - - let mut total_collateral_value = Uint128::zero(); - let mut max_ltv_adjusted_collateral = Uint128::zero(); - let mut liquidation_threshold_adjusted_collateral = Uint128::zero(); - - for v in vaults { - // Unlocked & locked denominated in vault coins - let vault_coin_amount = v.amount.unlocked().checked_add(v.amount.locked())?; - let vault_coin_value = v.vault.query_value(&deps.querier, &oracle, vault_coin_amount)?; - total_collateral_value = total_collateral_value.checked_add(vault_coin_value)?; - - let config = VAULT_CONFIGS.load(deps.storage, &v.vault.address)?; - let info = v.vault.query_info(&deps.querier)?; - - // If vault or base token has been de-listed, drop MaxLTV to zero - let checked_vault_max_ltv = if vault_is_whitelisted(deps.storage, &v.vault)? - && ALLOWED_COINS.contains(deps.storage, &info.base_token) - { - config.max_ltv - } else { - Decimal::zero() - }; - - max_ltv_adjusted_collateral = vault_coin_value - .checked_mul_floor(checked_vault_max_ltv)? - .checked_add(max_ltv_adjusted_collateral)?; - liquidation_threshold_adjusted_collateral = vault_coin_value - .checked_mul_floor(config.liquidation_threshold)? - .checked_add(liquidation_threshold_adjusted_collateral)?; - - // Unlocking positions denominated in underlying token - let PriceResponse { - price, - .. - } = oracle.query_price(&deps.querier, &info.base_token)?; - let Market { - max_loan_to_value, - liquidation_threshold, - .. - } = red_bank.query_market(&deps.querier, &info.base_token)?; - - // If base token has been de-listed, drop MaxLTV to zero - let checked_base_max_ltv = if ALLOWED_COINS.contains(deps.storage, &info.base_token) { - max_loan_to_value - } else { - Decimal::zero() - }; - - for u in v.amount.unlocking().positions() { - let underlying_value = u.coin.amount.checked_mul_floor(price)?; - total_collateral_value = total_collateral_value.checked_add(underlying_value)?; - max_ltv_adjusted_collateral = underlying_value - .checked_mul_floor(checked_base_max_ltv)? - .checked_add(max_ltv_adjusted_collateral)?; - liquidation_threshold_adjusted_collateral = underlying_value - .checked_mul_floor(liquidation_threshold)? - .checked_add(liquidation_threshold_adjusted_collateral)?; - } - } - - Ok(CollateralValue { - total_collateral_value, - max_ltv_adjusted_collateral, - liquidation_threshold_adjusted_collateral, - }) -} +use crate::state::HEALTH_CONTRACT; -fn calculate_deposits_value(deps: &Deps, deposits: &[Coin]) -> ContractResult { - let oracle = ORACLE.load(deps.storage)?; - let red_bank = RED_BANK.load(deps.storage)?; - - let mut total_collateral_value = Uint128::zero(); - let mut max_ltv_adjusted_collateral = Uint128::zero(); - let mut liquidation_threshold_adjusted_collateral = Uint128::zero(); - - for c in deposits { - let value = oracle.query_value(&deps.querier, c)?; - total_collateral_value = total_collateral_value.checked_add(value)?; - - let Market { - max_loan_to_value, - liquidation_threshold, - .. - } = red_bank.query_market(&deps.querier, &c.denom)?; - - // If coin has been de-listed, drop MaxLTV to zero - let checked_max_ltv = if ALLOWED_COINS.contains(deps.storage, &c.denom) { - max_loan_to_value - } else { - Decimal::zero() - }; - let max_ltv_adjusted = value.checked_mul_floor(checked_max_ltv)?; - max_ltv_adjusted_collateral = max_ltv_adjusted_collateral.checked_add(max_ltv_adjusted)?; - - let liq_adjusted = value.checked_mul_floor(liquidation_threshold)?; - liquidation_threshold_adjusted_collateral = - liquidation_threshold_adjusted_collateral.checked_add(liq_adjusted)?; - } - Ok(CollateralValue { - total_collateral_value, - max_ltv_adjusted_collateral, - liquidation_threshold_adjusted_collateral, - }) -} - -fn calculate_total_debt_value(deps: &Deps, debts: &[DebtAmount]) -> ContractResult { - let oracle = ORACLE.load(deps.storage)?; - let mut total = Uint128::zero(); - for debt in debts { - let debt_value = oracle.query_value( - &deps.querier, - &Coin { - denom: debt.denom.clone(), - amount: debt.amount, - }, - )?; - total = total.checked_add(debt_value)?; - } - Ok(total) +pub fn query_health(deps: Deps, account_id: &str) -> ContractResult { + let hm = HEALTH_CONTRACT.load(deps.storage)?; + Ok(hm.query_health(&deps.querier, account_id)?) } pub fn assert_max_ltv( deps: Deps, env: Env, account_id: &str, - prev_health: Health, + prev_max_ltv_health_factor: &Option, ) -> ContractResult { - let new_health = compute_health(deps, &env, account_id)?; + let new_health = query_health(deps, account_id)?; // If previous health was in a bad state, assert it did not further weaken - if prev_health.is_above_max_ltv() { + if is_below_one(prev_max_ltv_health_factor) { if let (Some(prev_hf), Some(new_hf)) = - (prev_health.max_ltv_health_factor, new_health.max_ltv_health_factor) + (prev_max_ltv_health_factor, new_health.max_ltv_health_factor) { - if prev_hf > new_hf { + if prev_hf > &new_hf { return Err(ContractError::HealthNotImproved { prev_hf: prev_hf.to_string(), new_hf: new_hf.to_string(), @@ -230,7 +33,7 @@ pub fn assert_max_ltv( } } // if previous health was in a good state, assert it's still healthy - } else if new_health.is_above_max_ltv() { + } else if new_health.above_max_ltv { return Err(ContractError::AboveMaxLTV { account_id: account_id.to_string(), max_ltv_health_factor: new_health.max_ltv_health_factor.to_string(), @@ -244,9 +47,9 @@ pub fn assert_max_ltv( .add_attribute("collateral_value", new_health.total_collateral_value.to_string()) .add_attribute("debts_value", new_health.total_debt_value.to_string()) .add_attribute("lqdt_health_factor", new_health.liquidation_health_factor.to_string()) - .add_attribute("liquidatable", new_health.is_liquidatable().to_string()) + .add_attribute("liquidatable", new_health.liquidatable.to_string()) .add_attribute("max_ltv_health_factor", new_health.max_ltv_health_factor.to_string()) - .add_attribute("above_max_ltv", new_health.is_above_max_ltv().to_string()); + .add_attribute("above_max_ltv", new_health.above_max_ltv.to_string()); Ok(Response::new().add_attribute("action", "callback/assert_health").add_event(event)) } diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index b50ee9608..73a465d78 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -8,8 +8,8 @@ use mars_rover::{ }; use crate::state::{ - ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, SWAPPER, - VAULT_CONFIGS, ZAPPER, + ALLOWED_COINS, HEALTH_CONTRACT, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, + RED_BANK, SWAPPER, VAULT_CONFIGS, ZAPPER, }; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { @@ -26,6 +26,7 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { SWAPPER.save(deps.storage, &msg.swapper.check(deps.api)?)?; ZAPPER.save(deps.storage, &msg.zapper.check(deps.api)?)?; MAX_UNLOCKING_POSITIONS.save(deps.storage, &msg.max_unlocking_positions)?; + HEALTH_CONTRACT.save(deps.storage, &msg.health_contract.check(deps.api)?)?; assert_lte_to_one(&msg.max_close_factor)?; MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index e0171d267..9ac3f7793 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -12,7 +12,7 @@ use mars_rover::{ }; use crate::{ - health::compute_health, + health::query_health, repay::current_debt_for_denom, state::{COIN_BALANCES, MAX_CLOSE_FACTOR, ORACLE, RED_BANK}, utils::{decrement_coin_balance, increment_coin_balance}, @@ -70,8 +70,8 @@ pub fn calculate_liquidation( request_coin_balance: Uint128, ) -> ContractResult<(Coin, Coin)> { // Assert the liquidatee's credit account is liquidatable - let health = compute_health(deps.as_ref(), env, liquidatee_account_id)?; - if !health.is_liquidatable() { + let health = query_health(deps.as_ref(), liquidatee_account_id)?; + if !health.liquidatable { return Err(ContractError::NotLiquidatable { account_id: liquidatee_account_id.to_string(), lqdt_health_factor: health.liquidation_health_factor.to_string(), diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index c18ddfa46..3f98425d1 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{Addr, Coin, Deps, Env, Order, StdResult, Uint128}; use cw_paginate::paginate_map; use cw_storage_plus::Bound; use mars_rover::{ - adapters::vault::{Vault, VaultBase, VaultPosition, VaultUnchecked}, + adapters::vault::{Vault, VaultBase, VaultPosition, VaultPositionValue, VaultUnchecked}, error::ContractResult, msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, LentAmount, LentShares, @@ -13,15 +13,14 @@ use mars_rover::{ use crate::{ state::{ - ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, LENT_SHARES, MAX_CLOSE_FACTOR, - MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, - TOTAL_LENT_SHARES, VAULT_CONFIGS, VAULT_POSITIONS, ZAPPER, + ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, HEALTH_CONTRACT, LENT_SHARES, + MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, SWAPPER, + TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, VAULT_CONFIGS, VAULT_POSITIONS, ZAPPER, }, utils::{debt_shares_to_amount, lent_shares_to_amount}, vault::vault_utilization_in_deposit_cap_denom, }; -const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; pub fn query_config(deps: Deps) -> ContractResult { @@ -36,6 +35,7 @@ pub fn query_config(deps: Deps) -> ContractResult { max_unlocking_positions: MAX_UNLOCKING_POSITIONS.load(deps.storage)?, swapper: SWAPPER.load(deps.storage)?.address().into(), zapper: ZAPPER.load(deps.storage)?.address().into(), + health_contract: HEALTH_CONTRACT.load(deps.storage)?.address().into(), }) } @@ -146,6 +146,20 @@ pub fn query_all_lent_shares( }) } +pub fn query_vault_info( + deps: Deps, + env: Env, + unchecked: VaultUnchecked, +) -> ContractResult { + let vault = unchecked.check(deps.api)?; + let config = VAULT_CONFIGS.load(deps.storage, &vault.address)?; + Ok(VaultInfoResponse { + config, + utilization: vault_utilization_in_deposit_cap_denom(&deps, &vault, &env.contract.address)?, + vault: vault.into(), + }) +} + pub fn query_vaults_info( deps: Deps, env: Env, @@ -220,7 +234,7 @@ pub fn query_allowed_coins( ) -> StdResult> { let start = start_after.as_ref().map(|denom| Bound::exclusive(denom.as_str())); - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let limit = limit.unwrap_or(DEFAULT_LIMIT) as usize; ALLOWED_COINS .items(deps.storage, start, None, Order::Ascending) @@ -304,3 +318,11 @@ pub fn query_all_total_vault_coin_balances( }) }) } + +pub fn query_vault_position_value( + deps: Deps, + vault_position: VaultPosition, +) -> StdResult { + let oracle = ORACLE.load(deps.storage)?; + vault_position.query_values(&deps.querier, &oracle) +} diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index e691b68eb..d71eceb11 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -3,6 +3,7 @@ use cw_item_set::Set; use cw_storage_plus::{Item, Map}; use mars_owner::Owner; use mars_rover::adapters::{ + health::HealthContract, oracle::Oracle, red_bank::RedBank, swap::Swapper, @@ -20,6 +21,7 @@ pub const RED_BANK: Item = Item::new("red_bank"); pub const SWAPPER: Item = Item::new("swapper"); pub const VAULT_CONFIGS: Map<&Addr, VaultConfig> = Map::new("vault_configs"); pub const ZAPPER: Item = Item::new("zapper"); +pub const HEALTH_CONTRACT: Item = Item::new("health_contract"); // Config pub const OWNER: Owner = Owner::new("owner"); diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index b802110ce..9b3bfcd7d 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, MessageInfo, Response, WasmMsg}; +use mars_account_nft::{msg::ExecuteMsg as NftExecuteMsg, nft_config::NftConfigUpdates}; use mars_owner::OwnerUpdate; use mars_rover::{ - adapters::account_nft::{ExecuteMsg as NftExecuteMsg, NftConfigUpdates}, error::ContractResult, msg::instantiate::ConfigUpdates, traits::{FallbackStr, Stringify}, @@ -10,8 +10,8 @@ use mars_rover::{ use crate::{ instantiate::{assert_lte_to_one, assert_no_duplicate_coins, assert_no_duplicate_vaults}, state::{ - ACCOUNT_NFT, ALLOWED_COINS, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, - SWAPPER, VAULT_CONFIGS, ZAPPER, + ACCOUNT_NFT, ALLOWED_COINS, HEALTH_CONTRACT, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, + ORACLE, OWNER, SWAPPER, VAULT_CONFIGS, ZAPPER, }, }; @@ -97,6 +97,13 @@ pub fn update_config( .add_attribute("value", num.to_string()); } + if let Some(unchecked) = updates.health_contract { + HEALTH_CONTRACT.save(deps.storage, &unchecked.check(deps.api)?)?; + response = response + .add_attribute("key", "health_contract") + .add_attribute("value", unchecked.address()); + } + Ok(response) } diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index 6dfb08db5..a6aca95e6 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -14,7 +14,7 @@ use crate::{ state::{COIN_BALANCES, ORACLE, VAULT_CONFIGS}, utils::{assert_coin_is_whitelisted, decrement_coin_balance}, vault::{ - rover_vault_balance_value, + rover_vault_coin_balance_value, utils::{assert_vault_is_whitelisted, update_vault_position}, }, }; @@ -120,7 +120,7 @@ pub fn assert_deposit_is_under_cap( ) -> ContractResult<()> { let oracle = ORACLE.load(deps.storage)?; let deposit_request_value = oracle.query_total_value(&deps.querier, &[coin_to_add.clone()])?; - let rover_vault_balance_value = rover_vault_balance_value(&deps, vault, rover_addr)?; + let rover_vault_balance_value = rover_vault_coin_balance_value(&deps, vault, rover_addr)?; let new_total_vault_value = rover_vault_balance_value.checked_add(deposit_request_value)?; diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 17df0ed45..6727f8b88 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,7 +1,10 @@ use cosmwasm_std::{Addr, Coin, Deps, StdResult, Storage, Uint128}; use mars_math::FractionMath; use mars_rover::{ - adapters::vault::{Vault, VaultPositionAmount, VaultPositionUpdate}, + adapters::vault::{ + LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultPosition, + VaultPositionAmount, VaultPositionUpdate, + }, error::{ContractError, ContractResult}, }; @@ -81,7 +84,7 @@ pub fn vault_utilization_in_deposit_cap_denom( vault: &Vault, rover_addr: &Addr, ) -> ContractResult { - let rover_vault_balance_value = rover_vault_balance_value(deps, vault, rover_addr)?; + let rover_vault_balance_value = rover_vault_coin_balance_value(deps, vault, rover_addr)?; let config = VAULT_CONFIGS.load(deps.storage, &vault.address)?; let oracle = ORACLE.load(deps.storage)?; let deposit_cap_denom_price = @@ -94,13 +97,25 @@ pub fn vault_utilization_in_deposit_cap_denom( } /// Total value of vault coins under Rover's management for vault -pub fn rover_vault_balance_value( +pub fn rover_vault_coin_balance_value( deps: &Deps, vault: &Vault, rover_addr: &Addr, ) -> ContractResult { let oracle = ORACLE.load(deps.storage)?; let rover_vault_coin_balance = vault.query_balance(&deps.querier, rover_addr)?; - let balance_value = vault.query_value(&deps.querier, &oracle, rover_vault_coin_balance)?; - Ok(balance_value) + let lockup = vault.query_lockup_duration(&deps.querier).ok(); + + let position = VaultPosition { + vault: vault.clone(), + amount: match lockup { + None => VaultPositionAmount::Unlocked(VaultAmount::new(rover_vault_coin_balance)), + Some(_) => VaultPositionAmount::Locking(LockingVaultAmount { + locked: VaultAmount::new(rover_vault_coin_balance), + unlocking: UnlockingPositions::new(vec![]), + }), + }, + }; + let vault_coin_balance_val = position.query_values(&deps.querier, &oracle)?.vault_coin.value; + Ok(vault_coin_balance_val) } diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index aa3017e80..03cdff63d 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -68,3 +68,12 @@ pub fn mock_zapper_contract() -> Box> { ); Box::new(contract) } + +pub fn mock_health_contract() -> Box> { + let contract = ContractWrapper::new( + mars_rover_health::contract::execute, + mars_rover_health::contract::instantiate, + mars_rover_health::contract::query, + ); + Box::new(contract) +} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index eb1fceb17..abb6287e8 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -1,13 +1,18 @@ use std::mem::take; use anyhow::Result as AnyResult; -use cosmwasm_std::{coins, testing::MockApi, Addr, Coin, Decimal, Uint128}; +use cosmwasm_std::{coins, testing::MockApi, Addr, Coin, Decimal, StdResult, Uint128}; use cosmwasm_vault_standard::{ extensions::lockup::{LockupQueryMsg, UnlockingPosition}, msg::{ExtensionQueryMsg, VaultStandardQueryMsg::VaultExtension}, }; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; -use mars_health::HealthResponse; +use mars_account_nft::{ + msg::{ + ExecuteMsg as NftExecuteMsg, InstantiateMsg as NftInstantiateMsg, QueryMsg as NftQueryMsg, + }, + nft_config::{NftConfigUpdates, UncheckedNftConfig}, +}; use mars_mock_oracle::msg::{ CoinPrice, ExecuteMsg as OracleExecuteMsg, InstantiateMsg as OracleInstantiateMsg, }; @@ -22,17 +27,17 @@ use mars_red_bank_types::red_bank::{ }; use mars_rover::{ adapters::{ - account_nft::{ - ExecuteMsg as NftExecuteMsg, InstantiateMsg as NftInstantiateMsg, NftConfigUpdates, - QueryMsg as NftQueryMsg, UncheckedNftConfig, - }, + health::HealthContract, oracle::{Oracle, OracleBase, OracleUnchecked}, red_bank::RedBankBase, swap::{ EstimateExactInSwapResponse, InstantiateMsg as SwapperInstantiateMsg, QueryMsg::EstimateExactInSwap, Swapper, SwapperBase, }, - vault::{VaultBase, VaultConfig, VaultUnchecked}, + vault::{ + VaultBase, VaultConfig, VaultPosition, VaultPositionValue as VPositionValue, + VaultUnchecked, + }, zapper::{Zapper, ZapperBase}, }, msg::{ @@ -47,13 +52,18 @@ use mars_rover::{ InstantiateMsg as ZapperInstantiateMsg, LpConfig, QueryMsg::EstimateProvideLiquidity, }, ExecuteMsg, InstantiateMsg, QueryMsg, + QueryMsg::VaultPositionValue, }, }; +use mars_rover_health_types::{ + ExecuteMsg::UpdateConfig, HealthResponse, InstantiateMsg as HealthInstantiateMsg, + QueryMsg::Health, +}; use crate::helpers::{ - lp_token_info, mock_account_nft_contract, mock_oracle_contract, mock_red_bank_contract, - mock_rover_contract, mock_swapper_contract, mock_vault_contract, mock_zapper_contract, - AccountToFund, CoinInfo, VaultTestInfo, + lp_token_info, mock_account_nft_contract, mock_health_contract, mock_oracle_contract, + mock_red_bank_contract, mock_rover_contract, mock_swapper_contract, mock_vault_contract, + mock_zapper_contract, AccountToFund, CoinInfo, VaultTestInfo, }; pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000); @@ -62,6 +72,7 @@ pub struct MockEnv { pub app: BasicApp, pub rover: Addr, pub mars_oracle: Addr, + pub health_contract: HealthContract, } pub struct MockEnvBuilder { @@ -77,6 +88,7 @@ pub struct MockEnvBuilder { pub accounts_to_fund: Vec, pub max_close_factor: Option, pub max_unlocking_positions: Option, + pub health_contract: Option, } #[allow(clippy::new_ret_no_self)] @@ -95,6 +107,7 @@ impl MockEnv { accounts_to_fund: vec![], max_close_factor: None, max_unlocking_positions: None, + health_contract: None, } } @@ -247,8 +260,8 @@ impl MockEnv { self.app .wrap() .query_wasm_smart( - self.rover.clone(), - &QueryMsg::Health { + self.health_contract.clone().address(), + &Health { account_id: account_id.to_string(), }, ) @@ -271,6 +284,15 @@ impl MockEnv { .unwrap() } + pub fn query_vault_config(&self, vault: &VaultUnchecked) -> StdResult { + self.app.wrap().query_wasm_smart( + self.rover.clone(), + &QueryMsg::VaultInfo { + vault: vault.clone(), + }, + ) + } + pub fn query_vault_configs( &self, start_after: Option, @@ -560,6 +582,18 @@ impl MockEnv { ) .unwrap() } + + pub fn query_vault_position_value( + &self, + position: &VaultPosition, + ) -> StdResult { + self.app.wrap().query_wasm_smart( + self.rover.clone(), + &VaultPositionValue { + vault_position: position.clone(), + }, + ) + } } impl MockEnvBuilder { @@ -567,6 +601,9 @@ impl MockEnvBuilder { let rover = self.get_rover()?; let mars_oracle = self.get_oracle(); + let health_contract = self.get_health_contract(); + self.update_health_contract_config(rover.clone()); + self.deploy_nft_contract(&rover); self.fund_users(); @@ -574,6 +611,7 @@ impl MockEnvBuilder { app: take(&mut self.app), rover, mars_oracle: mars_oracle.address().clone(), + health_contract, }) } @@ -642,6 +680,7 @@ impl MockEnvBuilder { let oracle = self.get_oracle().into(); let zapper = self.deploy_zapper(&oracle)?.into(); + let health_contract = self.get_health_contract().into(); self.app.instantiate_contract( code_id, @@ -656,6 +695,7 @@ impl MockEnvBuilder { max_unlocking_positions, swapper, zapper, + health_contract, }, &[], "mock-rover-contract", @@ -689,6 +729,19 @@ impl MockEnvBuilder { denom: "uusdc".to_string(), price: Decimal::from_atomics(12345u128, 4).unwrap(), }); + + // Ensures vault base token denoms are pricable in the oracle + // even if they are not whitelisted in Rover + let price_denoms = prices.iter().map(|c| c.denom.clone()).collect::>(); + self.vault_configs.clone().unwrap_or_default().iter().for_each(|v| { + if !price_denoms.contains(&v.base_token_denom) { + prices.push(CoinPrice { + denom: v.base_token_denom.clone(), + price: Decimal::from_atomics(456u128, 5).unwrap(), + }); + } + }); + let addr = self .app .instantiate_contract( @@ -705,6 +758,51 @@ impl MockEnvBuilder { OracleBase::new(addr) } + fn get_health_contract(&mut self) -> HealthContract { + if self.health_contract.is_none() { + let hc = self.deploy_health_contract(); + self.health_contract = Some(hc); + } + self.health_contract.clone().unwrap() + } + + pub fn deploy_health_contract(&mut self) -> HealthContract { + let contract_code_id = self.app.store_code(mock_health_contract()); + let owner = Addr::unchecked("health_contract_owner"); + + let addr = self + .app + .instantiate_contract( + contract_code_id, + owner.clone(), + &HealthInstantiateMsg { + owner: owner.to_string(), + }, + &[], + "mock-health-contract", + Some(owner.to_string()), + ) + .unwrap(); + + HealthContract::new(addr) + } + + fn update_health_contract_config(&mut self, cm_addr: Addr) { + let owner = Addr::unchecked("health_contract_owner"); + let health_contract = self.get_health_contract(); + + self.app + .execute_contract( + owner, + health_contract.address().clone(), + &UpdateConfig { + credit_manager: cm_addr.to_string(), + }, + &[], + ) + .unwrap(); + } + fn get_red_bank(&mut self) -> RedBankBase { if self.red_bank.is_none() { let addr = self.deploy_red_bank(); @@ -957,6 +1055,7 @@ fn deploy_nft_contract(app: &mut App, minter: &Addr) -> Addr { minter.clone(), &NftInstantiateMsg { max_value_for_burn: Default::default(), + health_contract: None, name: "Rover Credit Account".to_string(), symbol: "RCA".to_string(), minter: minter.to_string(), @@ -973,6 +1072,7 @@ fn propose_new_nft_minter(app: &mut App, nft_contract: Addr, old_minter: &Addr, updates: NftConfigUpdates { max_value_for_burn: None, proposed_new_minter: Some(new_minter.into()), + health_contract_addr: None, }, }; app.execute_contract(old_minter.clone(), nft_contract, &proposal_msg, &[]).unwrap(); diff --git a/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs b/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs index a30dd6035..17b8fe81a 100644 --- a/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs +++ b/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs @@ -7,11 +7,6 @@ fn pagination_on_allowed_coins_query_works() { let allowed_coins = build_mock_coin_infos(32); let mock = MockEnv::new().allowed_coins(&build_mock_coin_infos(32)).build().unwrap(); - let coins_res = mock.query_allowed_coins(None, Some(58_u32)); - - // Assert maximum is observed - assert_eq!(coins_res.len(), 30); - let coins_res = mock.query_allowed_coins(None, Some(2_u32)); // Assert limit request is observed diff --git a/contracts/credit-manager/tests/test_update_admin.rs b/contracts/credit-manager/tests/test_update_admin.rs index 76d1bdce2..0198c7862 100644 --- a/contracts/credit-manager/tests/test_update_admin.rs +++ b/contracts/credit-manager/tests/test_update_admin.rs @@ -3,7 +3,7 @@ use mars_owner::{ OwnerError::{NotOwner, NotProposedOwner, StateTransitionError}, OwnerUpdate, }; -use mars_rover::error::ContractError::OwnerError; +use mars_rover::error::ContractError::Owner; use crate::helpers::{assert_err, MockEnv}; @@ -33,7 +33,7 @@ fn propose_new_owner() { proposed: bad_guy.to_string(), }, ); - assert_err(res, OwnerError(NotOwner {})); + assert_err(res, Owner(NotOwner {})); mock.update_owner( &Addr::unchecked(original_config.owner.clone().unwrap()), @@ -72,7 +72,7 @@ fn clear_proposed() { // only owner can clear let bad_guy = Addr::unchecked("bad_guy"); let res = mock.update_owner(&bad_guy, OwnerUpdate::ClearProposed); - assert_err(res, OwnerError(NotOwner {})); + assert_err(res, Owner(NotOwner {})); mock.update_owner( &Addr::unchecked(original_config.owner.clone().unwrap()), @@ -107,7 +107,7 @@ fn accept_owner_role() { &Addr::unchecked(original_config.owner.unwrap()), OwnerUpdate::AcceptProposed, ); - assert_err(res, OwnerError(NotProposedOwner {})); + assert_err(res, Owner(NotProposedOwner {})); mock.update_owner(&Addr::unchecked(new_owner.clone()), OwnerUpdate::AcceptProposed).unwrap(); @@ -125,7 +125,7 @@ fn abolish_owner_role() { // Only owner can abolish role let bad_guy = Addr::unchecked("bad_guy"); let res = mock.update_owner(&bad_guy, OwnerUpdate::AbolishOwnerRole); - assert_err(res, OwnerError(NotOwner {})); + assert_err(res, Owner(NotOwner {})); mock.update_owner( &Addr::unchecked(original_config.owner.clone().unwrap()), @@ -145,5 +145,5 @@ fn abolish_owner_role() { proposed: original_config.owner.unwrap(), }, ); - assert_err(res, OwnerError(StateTransitionError {})); + assert_err(res, Owner(StateTransitionError {})); } diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index d458e427d..98a836c96 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -4,6 +4,7 @@ use mars_mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; use mars_rover::{ adapters::{ + health::HealthContractUnchecked, oracle::{OracleBase, OracleUnchecked}, swap::SwapperBase, vault::{VaultBase, VaultConfig}, @@ -39,6 +40,7 @@ fn only_owner_can_update_config() { swapper: None, vault_configs: None, zapper: None, + health_contract: None, }, ); @@ -69,6 +71,7 @@ fn raises_on_invalid_vaults_config() { swapper: None, vault_configs: Some(vec![vault_config]), zapper: None, + health_contract: None, }, ); @@ -95,6 +98,7 @@ fn raises_on_invalid_vaults_config() { swapper: None, vault_configs: Some(vec![vault_config]), zapper: None, + health_contract: None, }, ); @@ -120,6 +124,7 @@ fn raises_on_invalid_vaults_config() { swapper: None, vault_configs: Some(vec![vault_a, vault_b]), zapper: None, + health_contract: None, }, ); @@ -146,6 +151,7 @@ fn update_config_works_with_full_config() { let new_close_factor = Decimal::from_atomics(32u128, 2).unwrap(); let new_unlocking_max = Uint128::new(321); let new_swapper = SwapperBase::new("new_swapper".to_string()); + let new_health_contract = HealthContractUnchecked::new("new_health_contract".to_string()); mock.update_config( &Addr::unchecked(original_config.owner.clone().unwrap()), @@ -158,6 +164,7 @@ fn update_config_works_with_full_config() { swapper: Some(new_swapper.clone()), vault_configs: Some(new_vault_configs.clone()), zapper: Some(new_zapper.clone()), + health_contract: Some(new_health_contract.clone()), }, ) .unwrap(); @@ -201,6 +208,9 @@ fn update_config_works_with_full_config() { assert_eq!(&new_config.swapper, new_swapper.address()); assert_ne!(new_config.swapper, original_config.swapper); + + assert_eq!(&new_config.health_contract, new_health_contract.address()); + assert_ne!(new_config.health_contract, original_config.health_contract); } #[test] @@ -242,6 +252,7 @@ fn update_config_works_with_some_config() { assert_eq!(new_config.max_close_factor, original_config.max_close_factor); assert_eq!(new_config.swapper, original_config.swapper); assert_eq!(new_config.zapper, original_config.zapper); + assert_eq!(new_config.health_contract, original_config.health_contract); assert_eq!(original_allowed_coins, new_queried_allowed_coins); assert_eq!(new_queried_vault_configs, original_vault_configs); } @@ -308,6 +319,7 @@ fn update_config_does_nothing_when_nothing_is_passed() { assert_eq!(new_config.zapper, original_config.zapper); assert_eq!(new_config.max_close_factor, original_config.max_close_factor); assert_eq!(new_config.swapper, original_config.swapper); + assert_eq!(new_config.health_contract, original_config.health_contract); } #[test] @@ -364,6 +376,7 @@ fn raises_on_duplicate_vault_configs() { }, ]), zapper: None, + health_contract: None, }, ); @@ -394,6 +407,7 @@ fn raises_on_duplicate_coin_configs() { swapper: None, vault_configs: None, zapper: None, + health_contract: None, }, ); @@ -440,7 +454,7 @@ fn deploy_vault(app: &mut BasicApp) -> VaultInstantiateConfig { &VaultInstantiateMsg { vault_token_denom: "vault_xyz".to_string(), lockup: None, - base_token_denom: "base_token".to_string(), + base_token_denom: "uusdc".to_string(), oracle: OracleBase::new("oracle".to_string()), }, &[], diff --git a/contracts/credit-manager/tests/test_update_nft_config.rs b/contracts/credit-manager/tests/test_update_nft_config.rs index 7f3470389..742184896 100644 --- a/contracts/credit-manager/tests/test_update_nft_config.rs +++ b/contracts/credit-manager/tests/test_update_nft_config.rs @@ -2,18 +2,16 @@ extern crate core; use cosmwasm_std::{Addr, Uint128}; use cw_multi_test::Executor; +use mars_account_nft::{msg::ExecuteMsg, nft_config::NftConfigUpdates}; use mars_owner::OwnerError::NotOwner; -use mars_rover::{ - adapters::account_nft::{ExecuteMsg, NftConfigUpdates}, - error::ContractError, -}; +use mars_rover::error::ContractError; use crate::helpers::{assert_err, MockEnv}; pub mod helpers; #[test] -fn _only_owner_can_update_config() { +fn only_owner_can_update_config() { let mut mock = MockEnv::new().build().unwrap(); let bad_guy = Addr::unchecked("bad_guy"); @@ -23,9 +21,10 @@ fn _only_owner_can_update_config() { NftConfigUpdates { max_value_for_burn: None, proposed_new_minter: Some(bad_guy.to_string()), + health_contract_addr: None, }, ); - assert_err(res, ContractError::OwnerError(NotOwner {})); + assert_err(res, ContractError::Owner(NotOwner {})); // Attempt updating directly from the NFT contract mock.app @@ -36,6 +35,7 @@ fn _only_owner_can_update_config() { updates: NftConfigUpdates { max_value_for_burn: None, proposed_new_minter: Some(bad_guy.to_string()), + health_contract_addr: None, }, }, &[], @@ -44,7 +44,7 @@ fn _only_owner_can_update_config() { } #[test] -fn _raises_on_invalid_config() { +fn raises_on_invalid_config() { let mut mock = MockEnv::new().build().unwrap(); let res = mock.update_nft_config( @@ -52,6 +52,7 @@ fn _raises_on_invalid_config() { NftConfigUpdates { max_value_for_burn: None, proposed_new_minter: Some("".to_string()), + health_contract_addr: None, }, ); @@ -61,17 +62,20 @@ fn _raises_on_invalid_config() { } #[test] -fn _update_config_works_with_full_config() { +fn update_config_works_with_full_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_nft_config(); let new_max_value = Some(Uint128::new(1122334455)); let new_proposed = Some("spiderman_12345".to_string()); + let new_health_contract = Some("new_health_contract_xyz".to_string()); + mock.update_nft_config( &Addr::unchecked(mock.query_config().owner.unwrap()), NftConfigUpdates { max_value_for_burn: new_max_value, proposed_new_minter: new_proposed.clone(), + health_contract_addr: new_health_contract.clone(), }, ) .unwrap(); @@ -79,13 +83,15 @@ fn _update_config_works_with_full_config() { let new_config = mock.query_nft_config(); assert_eq!(Some(new_config.max_value_for_burn), new_max_value); assert_eq!(new_config.proposed_new_minter, new_proposed); + assert_eq!(new_config.health_contract_addr, new_health_contract); assert_ne!(new_config.max_value_for_burn, original_config.max_value_for_burn); assert_ne!(new_config.proposed_new_minter, original_config.proposed_new_minter); + assert_ne!(new_config.health_contract_addr, original_config.health_contract_addr); } #[test] -fn _update_config_works_with_some_config() { +fn update_config_works_with_some_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_nft_config(); @@ -95,6 +101,7 @@ fn _update_config_works_with_some_config() { NftConfigUpdates { max_value_for_burn: None, proposed_new_minter: new_proposed.clone(), + health_contract_addr: None, }, ) .unwrap(); @@ -102,6 +109,7 @@ fn _update_config_works_with_some_config() { let new_config = mock.query_nft_config(); assert_eq!(new_config.max_value_for_burn, original_config.max_value_for_burn); assert_eq!(new_config.proposed_new_minter, new_proposed); + assert_eq!(new_config.health_contract_addr, original_config.health_contract_addr); assert_ne!(new_config.proposed_new_minter, original_config.proposed_new_minter); } diff --git a/contracts/credit-manager/tests/test_vault_query_info.rs b/contracts/credit-manager/tests/test_vault_query_info.rs new file mode 100644 index 000000000..3fdfa8858 --- /dev/null +++ b/contracts/credit-manager/tests/test_vault_query_info.rs @@ -0,0 +1,64 @@ +use cosmwasm_std::{Addr, StdError}; +use mars_rover::{ + adapters::vault::VaultUnchecked, + msg::execute::Action::{Deposit, EnterVault}, +}; + +use crate::helpers::{lp_token_info, unlocked_vault_info, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn raises_if_vault_not_in_config() { + let mock = MockEnv::new().build().unwrap(); + let err = mock.query_vault_config(&VaultUnchecked::new("abc".to_string())).unwrap_err(); + assert_eq!( + err, + StdError::generic_err( + "Querier contract error: mars_rover::adapters::vault::config::VaultConfig not found" + .to_string() + ) + ); +} + +#[test] +fn successfully_queries_with_utilization() { + let lp_token = lp_token_info(); + let leverage_vault = unlocked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone()]) + .vault_configs(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault: vault.clone(), + coin: lp_token.to_action_coin(23), + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + let res = mock.query_vault_config(&vault).unwrap(); + assert_eq!(res.vault, vault); + assert!(!res.utilization.amount.is_zero()); + assert_eq!(res.config.deposit_cap, leverage_vault.deposit_cap); + assert_eq!(res.config.max_ltv, leverage_vault.max_ltv); + assert_eq!(res.config.liquidation_threshold, leverage_vault.liquidation_threshold); + assert_eq!(res.config.whitelisted, leverage_vault.whitelisted); +} diff --git a/contracts/credit-manager/tests/test_vault_query_value.rs b/contracts/credit-manager/tests/test_vault_query_value.rs new file mode 100644 index 000000000..7c5322f5d --- /dev/null +++ b/contracts/credit-manager/tests/test_vault_query_value.rs @@ -0,0 +1,107 @@ +use std::ops::Div; + +use cosmwasm_std::{Addr, Uint128}; +use mars_math::FractionMath; +use mars_mock_vault::contract::STARTING_VAULT_SHARES; +use mars_rover::{ + adapters::vault::{Vault, VaultAmount, VaultPosition, VaultPositionAmount}, + msg::execute::Action::{Deposit, EnterVault, RequestVaultUnlock}, +}; + +use crate::helpers::{ + locked_vault_info, lp_token_info, unlocked_vault_info, AccountToFund, MockEnv, +}; + +pub mod helpers; + +#[test] +fn raises_if_vault_not_available_to_price() { + let mock = MockEnv::new().build().unwrap(); + + let vault_position = VaultPosition { + vault: Vault::new(Addr::unchecked("xyz")), + amount: VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::new(213))), + }; + + mock.query_vault_position_value(&vault_position).unwrap_err(); +} + +#[test] +fn returns_zero_if_vault_empty() { + let lp_token = lp_token_info(); + let leverage_vault = unlocked_vault_info(); + + let mock = MockEnv::new() + .allowed_coins(&[lp_token]) + .vault_configs(&[leverage_vault.clone()]) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let vault_position = VaultPosition { + vault: Vault::new(Addr::unchecked(vault.address)), + amount: VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::new(213))), + }; + + let value = mock.query_vault_position_value(&vault_position).unwrap(); + assert_eq!(value.vault_coin.value, Uint128::zero()); + assert_eq!(value.base_coin.value, Uint128::zero()); +} + +#[test] +fn accurately_prices() { + let lp_token = lp_token_info(); + let leverage_vault = locked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[lp_token.clone()]) + .vault_configs(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault: vault.clone(), + coin: lp_token.to_action_coin(50), + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + let positions = mock.query_positions(&account_id); + let vault_position = positions.vaults.first().unwrap(); + let value = mock.query_vault_position_value(vault_position).unwrap(); + assert_eq!(value.vault_coin.value, Uint128::new(50).checked_mul_floor(lp_token.price).unwrap()); + assert_eq!(value.base_coin.value, Uint128::zero()); + + // Check case with unlocking positions + mock.update_credit_account( + &account_id, + &user, + vec![RequestVaultUnlock { + vault, + amount: STARTING_VAULT_SHARES.div(Uint128::new(2)), + }], + &[], + ) + .unwrap(); + + let positions = mock.query_positions(&account_id); + let vault_position = positions.vaults.first().unwrap(); + let value = mock.query_vault_position_value(vault_position).unwrap(); + assert_eq!(value.vault_coin.value, Uint128::new(25).checked_mul_floor(lp_token.price).unwrap()); + assert_eq!(value.base_coin.value, Uint128::new(25).checked_mul_floor(lp_token.price).unwrap()); +} diff --git a/contracts/health/examples/schema.rs b/contracts/health/examples/schema.rs index ccb459acd..440f56bd9 100644 --- a/contracts/health/examples/schema.rs +++ b/contracts/health/examples/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use mars_rover::adapters::account_nft::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_rover_health_types::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/health/src/querier.rs b/contracts/health/src/querier.rs index 55ff6c35f..7010f50fc 100644 --- a/contracts/health/src/querier.rs +++ b/contracts/health/src/querier.rs @@ -46,20 +46,19 @@ impl<'a> HealthQuerier<'a> { self.credit_manager_addr.to_string(), &QueryMsg::AllowedCoins { start_after: None, - limit: None, + limit: Some(u32::MAX), }, )?; Ok(allowed_coins) } pub fn query_vault_config(&self, vault: &Vault) -> HealthResult { - let vaults_info: Vec = self.querier.query_wasm_smart( + let vault_info: VaultInfoResponse = self.querier.query_wasm_smart( self.credit_manager_addr.to_string(), - &QueryMsg::VaultsInfo { - start_after: Some(vault.into()), - limit: None, + &QueryMsg::VaultInfo { + vault: vault.into(), }, )?; - Ok(vaults_info.first().unwrap().clone().config) + Ok(vault_info.config) } } diff --git a/contracts/health/tests/helpers/mock_env.rs b/contracts/health/tests/helpers/mock_env.rs index 0aa4ff580..b43fdecc5 100644 --- a/contracts/health/tests/helpers/mock_env.rs +++ b/contracts/health/tests/helpers/mock_env.rs @@ -11,9 +11,12 @@ use mars_mock_oracle::msg::{CoinPrice, ExecuteMsg::ChangePrice}; use mars_mock_red_bank::msg::CoinMarketInfo; use mars_mock_vault::contract::STARTING_VAULT_SHARES; use mars_red_bank_types::red_bank::{ExecuteMsg::UpdateAsset, InitOrUpdateAssetParams}; -use mars_rover::msg::{ - query::{Positions, VaultInfoResponse as CmVaultConfig}, - QueryMsg::VaultsInfo, +use mars_rover::{ + adapters::vault::VaultUnchecked, + msg::{ + query::{Positions, VaultInfoResponse as CmVaultConfig}, + QueryMsg::VaultInfo, + }, }; use mars_rover_health_types::{ConfigResponse, ExecuteMsg::UpdateConfig, HealthResponse, QueryMsg}; @@ -60,14 +63,13 @@ impl MockEnv { .unwrap() } - pub fn query_vault_configs(&self) -> Vec { + pub fn query_vault_config(&self, vault: &VaultUnchecked) -> CmVaultConfig { self.app .wrap() .query_wasm_smart( self.cm_contract.clone(), - &VaultsInfo { - start_after: None, - limit: None, + &VaultInfo { + vault: vault.clone(), }, ) .unwrap() @@ -195,8 +197,8 @@ impl MockEnv { .unwrap(); } - pub fn vault_allowed(&mut self, allowed: bool) { - let mut config = self.query_vault_configs().first().unwrap().clone().config; + pub fn vault_allowed(&mut self, vault: &VaultUnchecked, allowed: bool) { + let mut config = self.query_vault_config(vault).config; config.whitelisted = allowed; self.app diff --git a/contracts/health/tests/helpers/mock_env_builder.rs b/contracts/health/tests/helpers/mock_env_builder.rs index a09041d71..2c0336bc6 100644 --- a/contracts/health/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/helpers/mock_env_builder.rs @@ -153,6 +153,7 @@ impl MockEnvBuilder { max_unlocking_positions: Default::default(), swapper: "n/a".to_string(), zapper: "n/a".to_string(), + health_contract: "n/a".to_string(), }, }, &[], diff --git a/contracts/health/tests/test_compute_health.rs b/contracts/health/tests/test_compute_health.rs index d97e2e5aa..b4942fdc8 100644 --- a/contracts/health/tests/test_compute_health.rs +++ b/contracts/health/tests/test_compute_health.rs @@ -206,6 +206,8 @@ fn vault_whitelist_affects_max_ltv() { mock.deposit_into_vault(base_token_amount); + let vault = Vault::new(mock.vault_contract.clone()); + mock.set_positions_response( account_id, &Positions { @@ -214,7 +216,7 @@ fn vault_whitelist_affects_max_ltv() { debts: vec![], lends: vec![], vaults: vec![VaultPosition { - vault: Vault::new(mock.vault_contract.clone()), + vault: vault.clone(), amount: VaultPositionAmount::Unlocked(VaultAmount::new(vault_token_amount)), }], }, @@ -235,9 +237,9 @@ fn vault_whitelist_affects_max_ltv() { }, ); - let vault_configs = mock.query_vault_configs(); - let vault_max_ltv = vault_configs.first().unwrap().config.max_ltv; - let vault_liq_threshold = vault_configs.first().unwrap().config.liquidation_threshold; + let vault_config = mock.query_vault_config(&vault.clone().into()); + let vault_max_ltv = vault_config.config.max_ltv; + let vault_liq_threshold = vault_config.config.liquidation_threshold; let health = mock.query_health(account_id).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); @@ -256,7 +258,7 @@ fn vault_whitelist_affects_max_ltv() { assert!(!health.above_max_ltv); // After de-listing, maxLTV drops to zero - mock.vault_allowed(false); + mock.vault_allowed(&vault.into(), false); let health = mock.query_health(account_id).unwrap(); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); } diff --git a/contracts/mock-credit-manager/src/contract.rs b/contracts/mock-credit-manager/src/contract.rs index 1affb2a44..91183efa9 100644 --- a/contracts/mock-credit-manager/src/contract.rs +++ b/contracts/mock-credit-manager/src/contract.rs @@ -4,9 +4,9 @@ use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, use mars_rover::msg::QueryMsg; use crate::{ - execute::{set_allowed_coins, set_health_response, set_position_response, set_vault_config}, + execute::{set_allowed_coins, set_position_response, set_vault_config}, msg::{ExecuteMsg, InstantiateMsg}, - query::{query_allowed_coins, query_config, query_health, query_positions, query_vaults_info}, + query::{query_allowed_coins, query_config, query_positions, query_vault_info}, state::{ALLOWED_COINS, CONFIG}, }; @@ -30,10 +30,6 @@ pub fn execute( msg: ExecuteMsg, ) -> StdResult { match msg { - ExecuteMsg::SetHealthResponse { - account_id, - response, - } => set_health_response(deps, account_id, response), ExecuteMsg::SetPositionsResponse { account_id, positions, @@ -49,9 +45,6 @@ pub fn execute( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::Health { - account_id, - } => to_binary(&query_health(deps, account_id)?), QueryMsg::Positions { account_id, } => to_binary(&query_positions(deps, account_id)?), @@ -59,9 +52,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::AllowedCoins { .. } => to_binary(&query_allowed_coins(deps)?), - QueryMsg::VaultsInfo { - .. - } => to_binary(&query_vaults_info(deps)?), + QueryMsg::VaultInfo { + vault, + } => to_binary(&query_vault_info(deps, vault)?), _ => unimplemented!("query msg not supported"), } } diff --git a/contracts/mock-credit-manager/src/execute.rs b/contracts/mock-credit-manager/src/execute.rs index 92f93a668..345c87dcc 100644 --- a/contracts/mock-credit-manager/src/execute.rs +++ b/contracts/mock-credit-manager/src/execute.rs @@ -1,17 +1,7 @@ use cosmwasm_std::{Addr, DepsMut, Response, StdResult}; use mars_rover::{adapters::vault::VaultConfig, msg::query::Positions}; -use mars_rover_health_types::HealthResponse; -use crate::state::{ALLOWED_COINS, HEALTH_RESPONSES, POSITION_RESPONSES, VAULT_CONFIGS}; - -pub fn set_health_response( - deps: DepsMut, - account_id: String, - response: HealthResponse, -) -> StdResult { - HEALTH_RESPONSES.save(deps.storage, &account_id, &response)?; - Ok(Response::new()) -} +use crate::state::{ALLOWED_COINS, POSITION_RESPONSES, VAULT_CONFIGS}; pub fn set_position_response( deps: DepsMut, diff --git a/contracts/mock-credit-manager/src/msg.rs b/contracts/mock-credit-manager/src/msg.rs index de4ec2afa..21e50cd51 100644 --- a/contracts/mock-credit-manager/src/msg.rs +++ b/contracts/mock-credit-manager/src/msg.rs @@ -3,7 +3,6 @@ use mars_rover::{ adapters::vault::VaultConfig, msg::query::{ConfigResponse, Positions}, }; -use mars_rover_health_types::HealthResponse; #[cw_serde] pub struct InstantiateMsg { @@ -12,10 +11,6 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { - SetHealthResponse { - account_id: String, - response: HealthResponse, - }, SetPositionsResponse { account_id: String, positions: Positions, diff --git a/contracts/mock-credit-manager/src/query.rs b/contracts/mock-credit-manager/src/query.rs index 25c203f7c..fc27e2535 100644 --- a/contracts/mock-credit-manager/src/query.rs +++ b/contracts/mock-credit-manager/src/query.rs @@ -1,15 +1,10 @@ -use cosmwasm_std::{coin, Deps, Order, StdResult}; +use cosmwasm_std::{coin, Deps, StdResult}; use mars_rover::{ adapters::vault::VaultUnchecked, msg::query::{ConfigResponse, Positions, VaultInfoResponse}, }; -use mars_rover_health_types::HealthResponse; -use crate::state::{ALLOWED_COINS, CONFIG, HEALTH_RESPONSES, POSITION_RESPONSES, VAULT_CONFIGS}; - -pub fn query_health(deps: Deps, account_id: String) -> StdResult { - HEALTH_RESPONSES.load(deps.storage, &account_id) -} +use crate::state::{ALLOWED_COINS, CONFIG, POSITION_RESPONSES, VAULT_CONFIGS}; pub fn query_positions(deps: Deps, account_id: String) -> StdResult { POSITION_RESPONSES.load(deps.storage, &account_id) @@ -23,16 +18,12 @@ pub fn query_allowed_coins(deps: Deps) -> StdResult> { ALLOWED_COINS.load(deps.storage) } -pub fn query_vaults_info(deps: Deps) -> StdResult> { - VAULT_CONFIGS - .range(deps.storage, None, None, Order::Ascending) - .map(|res| { - let (addr, config) = res?; - Ok(VaultInfoResponse { - config, - utilization: coin(1000000, "uusdc"), - vault: VaultUnchecked::new(addr.to_string()), - }) - }) - .collect() +pub fn query_vault_info(deps: Deps, vault: VaultUnchecked) -> StdResult { + let validated = deps.api.addr_validate(&vault.address)?; + let config = VAULT_CONFIGS.load(deps.storage, &validated)?; + Ok(VaultInfoResponse { + config, + utilization: coin(1000000, "uusdc"), + vault: VaultUnchecked::new(validated.to_string()), + }) } diff --git a/contracts/mock-credit-manager/src/state.rs b/contracts/mock-credit-manager/src/state.rs index 154964fb5..6f314337a 100644 --- a/contracts/mock-credit-manager/src/state.rs +++ b/contracts/mock-credit-manager/src/state.rs @@ -4,11 +4,9 @@ use mars_rover::{ adapters::vault::VaultConfig, msg::query::{ConfigResponse, Positions}, }; -use mars_rover_health_types::HealthResponse; pub const CONFIG: Item = Item::new("config"); pub const ALLOWED_COINS: Item> = Item::new("allowed_coins"); // Vec pub const VAULT_CONFIGS: Map<&Addr, VaultConfig> = Map::new("vault_configs"); -pub const HEALTH_RESPONSES: Map<&str, HealthResponse> = Map::new("health_responses"); // Map pub const POSITION_RESPONSES: Map<&str, Positions> = Map::new("position_responses"); // Map diff --git a/packages/health/Cargo.toml b/contracts/mock-health/Cargo.toml similarity index 62% rename from packages/health/Cargo.toml rename to contracts/mock-health/Cargo.toml index aec6fd9dd..9a04d18f8 100644 --- a/packages/health/Cargo.toml +++ b/contracts/mock-health/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mars-health" +name = "mars-mock-rover-health" version = { workspace = true } authors = { workspace = true } license = { workspace = true } @@ -10,13 +10,16 @@ documentation = { workspace = true } keywords = { workspace = true } [lib] -doctest = false +crate-type = ["cdylib", "rlib"] [features] # for quicker tests, cargo test --lib # for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] +library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +mars-rover-health-types = { workspace = true } diff --git a/contracts/mock-health/src/contract.rs b/contracts/mock-health/src/contract.rs new file mode 100644 index 000000000..8e6bb56dd --- /dev/null +++ b/contracts/mock-health/src/contract.rs @@ -0,0 +1,47 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +}; +use mars_rover_health_types::{HealthResponse, HealthResult, QueryMsg}; + +use crate::{msg::ExecuteMsg, state::HEALTH_RESPONSES}; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate(_: DepsMut, _: Env, _: MessageInfo, _: Empty) -> HealthResult { + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute(deps: DepsMut, _: Env, _: MessageInfo, msg: ExecuteMsg) -> HealthResult { + match msg { + ExecuteMsg::SetHealthResponse { + account_id, + response, + } => set_health_response(deps, account_id, response), + } +} + +pub fn set_health_response( + deps: DepsMut, + account_id: String, + response: HealthResponse, +) -> HealthResult { + HEALTH_RESPONSES.save(deps.storage, &account_id, &response)?; + Ok(Response::new()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> HealthResult { + let res = match msg { + QueryMsg::Health { + account_id, + } => to_binary(&query_health(deps, &account_id)?), + _ => unimplemented!("query msg not supported"), + }; + res.map_err(Into::into) +} + +pub fn query_health(deps: Deps, account_id: &str) -> StdResult { + HEALTH_RESPONSES.load(deps.storage, account_id) +} diff --git a/contracts/mock-health/src/lib.rs b/contracts/mock-health/src/lib.rs new file mode 100644 index 000000000..4934c19d5 --- /dev/null +++ b/contracts/mock-health/src/lib.rs @@ -0,0 +1,3 @@ +pub mod contract; +pub mod msg; +pub mod state; diff --git a/contracts/mock-health/src/msg.rs b/contracts/mock-health/src/msg.rs new file mode 100644 index 000000000..070ba6435 --- /dev/null +++ b/contracts/mock-health/src/msg.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::cw_serde; +use mars_rover_health_types::HealthResponse; + +#[cw_serde] +pub enum ExecuteMsg { + SetHealthResponse { + account_id: String, + response: HealthResponse, + }, +} diff --git a/contracts/mock-health/src/state.rs b/contracts/mock-health/src/state.rs new file mode 100644 index 000000000..1dcd921a3 --- /dev/null +++ b/contracts/mock-health/src/state.rs @@ -0,0 +1,4 @@ +use cw_storage_plus::Map; +use mars_rover_health_types::HealthResponse; + +pub const HEALTH_RESPONSES: Map<&str, HealthResponse> = Map::new("health_responses"); // Map diff --git a/packages/health-types/Cargo.toml b/packages/health-types/Cargo.toml index 6a8e0e1c3..fb773e62c 100644 --- a/packages/health-types/Cargo.toml +++ b/packages/health-types/Cargo.toml @@ -21,5 +21,4 @@ cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } mars-math = { workspace = true } mars-owner = { workspace = true } -mars-red-bank-types = { workspace = true } thiserror = { workspace = true } diff --git a/packages/health-types/examples/schema.rs b/packages/health-types/examples/schema.rs new file mode 100644 index 000000000..440f56bd9 --- /dev/null +++ b/packages/health-types/examples/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use mars_rover_health_types::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/packages/health/src/health.rs b/packages/health/src/health.rs deleted file mode 100644 index 0587f5d9a..000000000 --- a/packages/health/src/health.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::fmt; - -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Decimal, Uint128}; - -#[cw_serde] -pub struct Health { - /// The sum of the value of all debts - pub total_debt_value: Uint128, - /// The sum of the value of all collaterals - pub total_collateral_value: Uint128, - /// The sum of the value of all colletarals adjusted by their Max LTV - pub max_ltv_adjusted_collateral: Uint128, - /// The sum of the value of all colletarals adjusted by their Liquidation Threshold - pub liquidation_threshold_adjusted_collateral: Uint128, - /// The sum of the value of all collaterals multiplied by their max LTV, over the total value of debt - pub max_ltv_health_factor: Option, - /// The sum of the value of all collaterals multiplied by their liquidation threshold over the total value of debt - pub liquidation_health_factor: Option, -} - -impl fmt::Display for Health { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "(total_debt_value: {}, total_collateral_value: {}, max_ltv_adjusted_collateral: {}, lqdt_threshold_adjusted_collateral: {}, max_ltv_health_factor: {}, liquidation_health_factor: {})", - self.total_debt_value, - self.total_collateral_value, - self.max_ltv_adjusted_collateral, - self.liquidation_threshold_adjusted_collateral, - self.max_ltv_health_factor.map_or("n/a".to_string(), |x| x.to_string()), - self.liquidation_health_factor.map_or("n/a".to_string(), |x| x.to_string()) - ) - } -} - -impl Health { - #[inline] - pub fn is_liquidatable(&self) -> bool { - self.liquidation_health_factor.map_or(false, |hf| hf < Decimal::one()) - } - - #[inline] - pub fn is_above_max_ltv(&self) -> bool { - self.max_ltv_health_factor.map_or(false, |hf| hf < Decimal::one()) - } -} - -#[cw_serde] -pub struct HealthResponse { - pub total_debt_value: Uint128, - pub total_collateral_value: Uint128, - pub max_ltv_adjusted_collateral: Uint128, - pub liquidation_threshold_adjusted_collateral: Uint128, - pub max_ltv_health_factor: Option, - pub liquidation_health_factor: Option, - pub liquidatable: bool, - pub above_max_ltv: bool, -} - -impl From for HealthResponse { - fn from(h: Health) -> Self { - Self { - total_debt_value: h.total_debt_value, - total_collateral_value: h.total_collateral_value, - max_ltv_adjusted_collateral: h.max_ltv_adjusted_collateral, - liquidation_threshold_adjusted_collateral: h.liquidation_threshold_adjusted_collateral, - max_ltv_health_factor: h.max_ltv_health_factor, - liquidation_health_factor: h.liquidation_health_factor, - liquidatable: h.is_liquidatable(), - above_max_ltv: h.is_above_max_ltv(), - } - } -} diff --git a/packages/health/src/lib.rs b/packages/health/src/lib.rs deleted file mode 100644 index 120fd3dda..000000000 --- a/packages/health/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod health; - -pub use self::health::*; diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 1669a7c2f..d8903b4e6 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -25,7 +25,8 @@ cw721 = { workspace = true } cw721-base = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } -mars-health = { workspace = true } +mars-account-nft = { workspace = true } +mars-rover-health-types = { workspace = true } mars-math = { workspace = true } mars-red-bank-types = { workspace = true } mars-owner = { workspace = true } diff --git a/packages/rover/src/adapters/account_nft/mod.rs b/packages/rover/src/adapters/account_nft/mod.rs deleted file mode 100644 index 58ca0a7f7..000000000 --- a/packages/rover/src/adapters/account_nft/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod error; -mod msg; -mod nft_config; - -pub use self::{error::*, msg::*, nft_config::*}; diff --git a/packages/rover/src/adapters/health.rs b/packages/rover/src/adapters/health.rs new file mode 100644 index 000000000..e83db35d5 --- /dev/null +++ b/packages/rover/src/adapters/health.rs @@ -0,0 +1,46 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Api, QuerierWrapper, StdResult}; +use mars_rover_health_types::{HealthResponse, QueryMsg}; + +#[cw_serde] +pub struct HealthContractBase(T); + +impl HealthContractBase { + pub fn new(address: T) -> HealthContractBase { + HealthContractBase(address) + } + + pub fn address(&self) -> &T { + &self.0 + } +} + +pub type HealthContractUnchecked = HealthContractBase; +pub type HealthContract = HealthContractBase; + +impl From for HealthContractUnchecked { + fn from(health: HealthContract) -> Self { + Self(health.address().to_string()) + } +} + +impl HealthContractUnchecked { + pub fn check(&self, api: &dyn Api) -> StdResult { + Ok(HealthContractBase::new(api.addr_validate(self.address())?)) + } +} + +impl HealthContract { + pub fn query_health( + &self, + querier: &QuerierWrapper, + account_id: &str, + ) -> StdResult { + querier.query_wasm_smart( + self.address().to_string(), + &QueryMsg::Health { + account_id: account_id.to_string(), + }, + ) + } +} diff --git a/packages/rover/src/adapters/mod.rs b/packages/rover/src/adapters/mod.rs index 969b535c7..d344d25d3 100644 --- a/packages/rover/src/adapters/mod.rs +++ b/packages/rover/src/adapters/mod.rs @@ -1,4 +1,4 @@ -pub mod account_nft; +pub mod health; pub mod oracle; pub mod red_bank; pub mod swap; diff --git a/packages/rover/src/adapters/vault/base.rs b/packages/rover/src/adapters/vault/base.rs index ba54ffd44..8353c90ee 100644 --- a/packages/rover/src/adapters/vault/base.rs +++ b/packages/rover/src/adapters/vault/base.rs @@ -3,7 +3,7 @@ use std::hash::Hash; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, QuerierWrapper, - QueryRequest, StdError, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, + QueryRequest, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, }; use cosmwasm_vault_standard::{ extensions::{ @@ -19,9 +19,8 @@ use cosmwasm_vault_standard::{ VaultInfoResponse, }; use cw_utils::Duration; -use mars_math::FractionMath; -use crate::{adapters::oracle::Oracle, traits::Stringify}; +use crate::traits::Stringify; pub const VAULT_REQUEST_REPLY_ID: u64 = 10_001; @@ -230,23 +229,4 @@ impl Vault { msg: to_binary(&QueryMsg::TotalVaultTokenSupply {})?, })) } - - pub fn query_value( - &self, - querier: &QuerierWrapper, - oracle: &Oracle, - amount: Uint128, - ) -> StdResult { - let total_supply = self.query_total_vault_coins_issued(querier)?; - if total_supply.is_zero() { - return Ok(Uint128::zero()); - }; - let amount_in_underlying = self.query_preview_redeem(querier, amount)?; - let vault_info = self.query_info(querier)?; - let price_res = oracle.query_price(querier, &vault_info.base_token)?; - let amount_value = amount_in_underlying - .checked_mul_floor(price_res.price) - .map_err(|_| StdError::generic_err("CheckedMultiplyFractionError"))?; - Ok(amount_value) - } } diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index f185a984b..d14008c33 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -25,7 +25,7 @@ pub enum ContractError { }, #[error("{0}")] - OwnerError(#[from] OwnerError), + Owner(#[from] OwnerError), #[error("{0} is not an available coin to request")] CoinNotAvailable(String), diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 9eacc2563..def73380f 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,13 +1,10 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; -use mars_health::Health; +use mars_account_nft::nft_config::NftConfigUpdates; use mars_owner::OwnerUpdate; use crate::{ - adapters::{ - account_nft::NftConfigUpdates, - vault::{Vault, VaultPositionType, VaultUnchecked}, - }, + adapters::vault::{Vault, VaultPositionType, VaultUnchecked}, msg::instantiate::ConfigUpdates, }; @@ -191,8 +188,8 @@ pub enum CallbackMsg { /// Emits a `position_changed` event. #[serde(rename = "assert_max_ltv")] AssertMaxLTV { - prev_health: Health, account_id: String, + prev_max_ltv_health_factor: Option, }, /// Adds coin to a vault strategy EnterVault { diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index 4b53066cf..7f7a3193f 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -3,6 +3,7 @@ use cosmwasm_std::{Decimal, Uint128}; use crate::{ adapters::{ + health::HealthContractUnchecked, oracle::OracleUnchecked, red_bank::RedBankUnchecked, swap::SwapperUnchecked, @@ -35,6 +36,8 @@ pub struct InstantiateMsg { pub swapper: SwapperUnchecked, /// Helper contract for adding/removing liquidity pub zapper: ZapperUnchecked, + /// Helper contract for calculating health factor + pub health_contract: HealthContractUnchecked, } #[cw_serde] @@ -73,4 +76,5 @@ pub struct ConfigUpdates { pub max_unlocking_positions: Option, pub swapper: Option, pub zapper: Option, + pub health_contract: Option, } diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 8c9bb0b10..7da340fb2 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -12,7 +12,12 @@ pub enum QueryMsg { /// Rover contract-level config #[returns(ConfigResponse)] Config {}, - /// Configs & deposit caps on vaults + /// Config & deposit caps on vault + #[returns(VaultInfoResponse)] + VaultInfo { + vault: VaultUnchecked, + }, + /// Configs & deposit caps on all vaults #[returns(Vec)] VaultsInfo { start_after: Option, @@ -29,11 +34,6 @@ pub enum QueryMsg { Positions { account_id: String, }, - /// The health of the account represented by token - #[returns(mars_health::HealthResponse)] - Health { - account_id: String, - }, /// Enumerate coin balances for all token positions; start_after accepts (account_id, denom) #[returns(Vec)] AllCoinBalances { @@ -98,6 +98,13 @@ pub enum QueryMsg { EstimateWithdrawLiquidity { lp_token: Coin, }, + /// Returns the value of the a vault coin position. + /// Given the extremely low price-per-coin and lack of precision, individual vault + /// coins cannot be priced, hence you must send the whole amount you want priced. + #[returns(crate::adapters::vault::VaultPositionValue)] + VaultPositionValue { + vault_position: VaultPosition, + }, } #[cw_serde] @@ -164,14 +171,6 @@ impl Coins for Vec { } } -#[cw_serde] -pub struct CoinValue { - pub denom: String, - pub amount: Uint128, - pub price: Decimal, - pub value: Decimal, -} - #[cw_serde] pub struct Positions { pub account_id: String, @@ -193,12 +192,6 @@ pub struct VaultWithBalance { pub balance: Uint128, } -#[cw_serde] -pub struct VaultPositionValue { - pub position: VaultPosition, - pub value: Decimal, -} - #[cw_serde] pub struct ConfigResponse { pub owner: Option, @@ -210,4 +203,5 @@ pub struct ConfigResponse { pub max_unlocking_positions: Uint128, pub swapper: String, pub zapper: String, + pub health_contract: String, } diff --git a/schema.Makefile.toml b/schema.Makefile.toml index 81e88dbe1..e66ca4e18 100644 --- a/schema.Makefile.toml +++ b/schema.Makefile.toml @@ -19,6 +19,8 @@ fn main() -> std::io::Result<()> { "mars-mock-vault", "mars-mock-oracle", "mars-mock-credit-manager", + "mars-rover-health-computer", + "mars-rover-health-types", ]; for contract in contracts { diff --git a/schemas/mars-account-nft/mars-account-nft.json b/schemas/mars-account-nft/mars-account-nft.json index 6d123e994..4681339cb 100644 --- a/schemas/mars-account-nft/mars-account-nft.json +++ b/schemas/mars-account-nft/mars-account-nft.json @@ -13,6 +13,13 @@ "symbol" ], "properties": { + "health_contract": { + "description": "Used to validate the account id's health status allows for burning. Can be set later, but no burning allowed until set.", + "type": [ + "string", + "null" + ] + }, "max_value_for_burn": { "description": "The maximum value of Debts + Collaterals (denominated in base token) for an account before burns are disallowed for the NFT. Meant to prevent accidental account deletions", "allOf": [ @@ -354,6 +361,12 @@ "NftConfigUpdates": { "type": "object", "properties": { + "health_contract_addr": { + "type": [ + "string", + "null" + ] + }, "max_value_for_burn": { "anyOf": [ { @@ -1200,6 +1213,12 @@ "max_value_for_burn" ], "properties": { + "health_contract_addr": { + "type": [ + "string", + "null" + ] + }, "max_value_for_burn": { "$ref": "#/definitions/Uint128" }, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 0cbbec4da..5a51715ba 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -8,6 +8,7 @@ "type": "object", "required": [ "allowed_coins", + "health_contract", "max_close_factor", "max_unlocking_positions", "oracle", @@ -25,6 +26,14 @@ "type": "string" } }, + "health_contract": { + "description": "Helper contract for calculating health factor", + "allOf": [ + { + "$ref": "#/definitions/HealthContractBase_for_String" + } + ] + }, "max_close_factor": { "description": "The maximum percent a liquidator can decrease the debt amount of the liquidatee", "allOf": [ @@ -106,6 +115,9 @@ "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" }, + "HealthContractBase_for_String": { + "type": "string" + }, "OracleBase_for_String": { "type": "string" }, @@ -804,15 +816,21 @@ "assert_max_ltv": { "type": "object", "required": [ - "account_id", - "prev_health" + "account_id" ], "properties": { "account_id": { "type": "string" }, - "prev_health": { - "$ref": "#/definitions/Health" + "prev_max_ltv_health_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -1236,6 +1254,16 @@ "type": "string" } }, + "health_contract": { + "anyOf": [ + { + "$ref": "#/definitions/HealthContractBase_for_String" + }, + { + "type": "null" + } + ] + }, "max_close_factor": { "anyOf": [ { @@ -1302,75 +1330,18 @@ "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" }, - "Health": { - "type": "object", - "required": [ - "liquidation_threshold_adjusted_collateral", - "max_ltv_adjusted_collateral", - "total_collateral_value", - "total_debt_value" - ], - "properties": { - "liquidation_health_factor": { - "description": "The sum of the value of all collaterals multiplied by their liquidation threshold over the total value of debt", - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "liquidation_threshold_adjusted_collateral": { - "description": "The sum of the value of all colletarals adjusted by their Liquidation Threshold", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "max_ltv_adjusted_collateral": { - "description": "The sum of the value of all colletarals adjusted by their Max LTV", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "max_ltv_health_factor": { - "description": "The sum of the value of all collaterals multiplied by their max LTV, over the total value of debt", - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "total_collateral_value": { - "description": "The sum of the value of all collaterals", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "total_debt_value": { - "description": "The sum of the value of all debts", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "additionalProperties": false + "HealthContractBase_for_String": { + "type": "string" }, "NftConfigUpdates": { "type": "object", "properties": { + "health_contract_addr": { + "type": [ + "string", + "null" + ] + }, "max_value_for_burn": { "anyOf": [ { @@ -1543,7 +1514,29 @@ "additionalProperties": false }, { - "description": "Configs & deposit caps on vaults", + "description": "Config & deposit caps on vault", + "type": "object", + "required": [ + "vault_info" + ], + "properties": { + "vault_info": { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Configs & deposit caps on all vaults", "type": "object", "required": [ "vaults_info" @@ -1628,28 +1621,6 @@ }, "additionalProperties": false }, - { - "description": "The health of the account represented by token", - "type": "object", - "required": [ - "health" - ], - "properties": { - "health": { - "type": "object", - "required": [ - "account_id" - ], - "properties": { - "account_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "Enumerate coin balances for all token positions; start_after accepts (account_id, denom)", "type": "object", @@ -2002,9 +1973,35 @@ } }, "additionalProperties": false + }, + { + "description": "Returns the value of the a vault coin position. Given the extremely low price-per-coin and lack of precision, individual vault coins cannot be priced, hence you must send the whole amount you want priced.", + "type": "object", + "required": [ + "vault_position_value" + ], + "properties": { + "vault_position_value": { + "type": "object", + "required": [ + "vault_position" + ], + "properties": { + "vault_position": { + "$ref": "#/definitions/VaultPosition" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Coin": { "type": "object", "required": [ @@ -2020,10 +2017,47 @@ } } }, + "LockingVaultAmount": { + "type": "object", + "required": [ + "locked", + "unlocking" + ], + "properties": { + "locked": { + "$ref": "#/definitions/VaultAmount" + }, + "unlocking": { + "$ref": "#/definitions/UnlockingPositions" + } + }, + "additionalProperties": false + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" }, + "UnlockingPositions": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultUnlockingPosition" + } + }, + "VaultAmount": { + "$ref": "#/definitions/Uint128" + }, + "VaultBase_for_Addr": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + }, "VaultBase_for_String": { "type": "object", "required": [ @@ -2035,6 +2069,74 @@ } }, "additionalProperties": false + }, + "VaultPosition": { + "type": "object", + "required": [ + "amount", + "vault" + ], + "properties": { + "amount": { + "$ref": "#/definitions/VaultPositionAmount" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + }, + "additionalProperties": false + }, + "VaultPositionAmount": { + "oneOf": [ + { + "type": "object", + "required": [ + "unlocked" + ], + "properties": { + "unlocked": { + "$ref": "#/definitions/VaultAmount" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "locking" + ], + "properties": { + "locking": { + "$ref": "#/definitions/LockingVaultAmount" + } + }, + "additionalProperties": false + } + ] + }, + "VaultUnlockingPosition": { + "type": "object", + "required": [ + "coin", + "id" + ], + "properties": { + "coin": { + "description": "Coins that are awaiting to be unlocked (underlying, not vault tokens)", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false } } }, @@ -2416,6 +2518,7 @@ "title": "ConfigResponse", "type": "object", "required": [ + "health_contract", "max_close_factor", "max_unlocking_positions", "oracle", @@ -2430,6 +2533,9 @@ "null" ] }, + "health_contract": { + "type": "string" + }, "max_close_factor": { "$ref": "#/definitions/Decimal" }, @@ -2508,70 +2614,6 @@ } } }, - "health": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HealthResponse", - "type": "object", - "required": [ - "above_max_ltv", - "liquidatable", - "liquidation_threshold_adjusted_collateral", - "max_ltv_adjusted_collateral", - "total_collateral_value", - "total_debt_value" - ], - "properties": { - "above_max_ltv": { - "type": "boolean" - }, - "liquidatable": { - "type": "boolean" - }, - "liquidation_health_factor": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "liquidation_threshold_adjusted_collateral": { - "$ref": "#/definitions/Uint128" - }, - "max_ltv_adjusted_collateral": { - "$ref": "#/definitions/Uint128" - }, - "max_ltv_health_factor": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "total_collateral_value": { - "$ref": "#/definitions/Uint128" - }, - "total_debt_value": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "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" - } - } - }, "positions": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Positions", @@ -2858,6 +2900,148 @@ "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" }, + "vault_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VaultInfoResponse", + "type": "object", + "required": [ + "config", + "utilization", + "vault" + ], + "properties": { + "config": { + "$ref": "#/definitions/VaultConfig" + }, + "utilization": { + "description": "The amount the vault has been utilized, denominated in the same denom set in the vault config's deposit cap", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VaultBase_for_String": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + }, + "VaultConfig": { + "type": "object", + "required": [ + "deposit_cap", + "liquidation_threshold", + "max_ltv", + "whitelisted" + ], + "properties": { + "deposit_cap": { + "$ref": "#/definitions/Coin" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv": { + "$ref": "#/definitions/Decimal" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, + "vault_position_value": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VaultPositionValue", + "type": "object", + "required": [ + "base_coin", + "vault_coin" + ], + "properties": { + "base_coin": { + "description": "value of all unlocking positions", + "allOf": [ + { + "$ref": "#/definitions/CoinValue" + } + ] + }, + "vault_coin": { + "description": "value of locked or unlocked", + "allOf": [ + { + "$ref": "#/definitions/CoinValue" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "CoinValue": { + "type": "object", + "required": [ + "amount", + "denom", + "value" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "vaults_info": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_VaultInfoResponse", diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index e67a67751..17de152c1 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -12,31 +12,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", "oneOf": [ - { - "type": "object", - "required": [ - "set_health_response" - ], - "properties": { - "set_health_response": { - "type": "object", - "required": [ - "account_id", - "response" - ], - "properties": { - "account_id": { - "type": "string" - }, - "response": { - "$ref": "#/definitions/HealthResponse" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "type": "object", "required": [ @@ -157,58 +132,6 @@ "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" }, - "HealthResponse": { - "type": "object", - "required": [ - "above_max_ltv", - "liquidatable", - "liquidation_threshold_adjusted_collateral", - "max_ltv_adjusted_collateral", - "total_collateral_value", - "total_debt_value" - ], - "properties": { - "above_max_ltv": { - "type": "boolean" - }, - "liquidatable": { - "type": "boolean" - }, - "liquidation_health_factor": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "liquidation_threshold_adjusted_collateral": { - "$ref": "#/definitions/Uint128" - }, - "max_ltv_adjusted_collateral": { - "$ref": "#/definitions/Uint128" - }, - "max_ltv_health_factor": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "total_collateral_value": { - "$ref": "#/definitions/Uint128" - }, - "total_debt_value": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, "LentAmount": { "type": "object", "required": [ @@ -433,7 +356,29 @@ "additionalProperties": false }, { - "description": "Configs & deposit caps on vaults", + "description": "Config & deposit caps on vault", + "type": "object", + "required": [ + "vault_info" + ], + "properties": { + "vault_info": { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Configs & deposit caps on all vaults", "type": "object", "required": [ "vaults_info" @@ -518,28 +463,6 @@ }, "additionalProperties": false }, - { - "description": "The health of the account represented by token", - "type": "object", - "required": [ - "health" - ], - "properties": { - "health": { - "type": "object", - "required": [ - "account_id" - ], - "properties": { - "account_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "Enumerate coin balances for all token positions; start_after accepts (account_id, denom)", "type": "object", @@ -892,9 +815,35 @@ } }, "additionalProperties": false + }, + { + "description": "Returns the value of the a vault coin position. Given the extremely low price-per-coin and lack of precision, individual vault coins cannot be priced, hence you must send the whole amount you want priced.", + "type": "object", + "required": [ + "vault_position_value" + ], + "properties": { + "vault_position_value": { + "type": "object", + "required": [ + "vault_position" + ], + "properties": { + "vault_position": { + "$ref": "#/definitions/VaultPosition" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Coin": { "type": "object", "required": [ @@ -910,10 +859,47 @@ } } }, + "LockingVaultAmount": { + "type": "object", + "required": [ + "locked", + "unlocking" + ], + "properties": { + "locked": { + "$ref": "#/definitions/VaultAmount" + }, + "unlocking": { + "$ref": "#/definitions/UnlockingPositions" + } + }, + "additionalProperties": false + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" }, + "UnlockingPositions": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultUnlockingPosition" + } + }, + "VaultAmount": { + "$ref": "#/definitions/Uint128" + }, + "VaultBase_for_Addr": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + }, "VaultBase_for_String": { "type": "object", "required": [ @@ -925,6 +911,74 @@ } }, "additionalProperties": false + }, + "VaultPosition": { + "type": "object", + "required": [ + "amount", + "vault" + ], + "properties": { + "amount": { + "$ref": "#/definitions/VaultPositionAmount" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + }, + "additionalProperties": false + }, + "VaultPositionAmount": { + "oneOf": [ + { + "type": "object", + "required": [ + "unlocked" + ], + "properties": { + "unlocked": { + "$ref": "#/definitions/VaultAmount" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "locking" + ], + "properties": { + "locking": { + "$ref": "#/definitions/LockingVaultAmount" + } + }, + "additionalProperties": false + } + ] + }, + "VaultUnlockingPosition": { + "type": "object", + "required": [ + "coin", + "id" + ], + "properties": { + "coin": { + "description": "Coins that are awaiting to be unlocked (underlying, not vault tokens)", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false } } }, @@ -1306,6 +1360,7 @@ "title": "ConfigResponse", "type": "object", "required": [ + "health_contract", "max_close_factor", "max_unlocking_positions", "oracle", @@ -1320,6 +1375,9 @@ "null" ] }, + "health_contract": { + "type": "string" + }, "max_close_factor": { "$ref": "#/definitions/Decimal" }, @@ -1398,70 +1456,6 @@ } } }, - "health": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HealthResponse", - "type": "object", - "required": [ - "above_max_ltv", - "liquidatable", - "liquidation_threshold_adjusted_collateral", - "max_ltv_adjusted_collateral", - "total_collateral_value", - "total_debt_value" - ], - "properties": { - "above_max_ltv": { - "type": "boolean" - }, - "liquidatable": { - "type": "boolean" - }, - "liquidation_health_factor": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "liquidation_threshold_adjusted_collateral": { - "$ref": "#/definitions/Uint128" - }, - "max_ltv_adjusted_collateral": { - "$ref": "#/definitions/Uint128" - }, - "max_ltv_health_factor": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "total_collateral_value": { - "$ref": "#/definitions/Uint128" - }, - "total_debt_value": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "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" - } - } - }, "positions": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Positions", @@ -1748,6 +1742,148 @@ "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" }, + "vault_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VaultInfoResponse", + "type": "object", + "required": [ + "config", + "utilization", + "vault" + ], + "properties": { + "config": { + "$ref": "#/definitions/VaultConfig" + }, + "utilization": { + "description": "The amount the vault has been utilized, denominated in the same denom set in the vault config's deposit cap", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VaultBase_for_String": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + }, + "VaultConfig": { + "type": "object", + "required": [ + "deposit_cap", + "liquidation_threshold", + "max_ltv", + "whitelisted" + ], + "properties": { + "deposit_cap": { + "$ref": "#/definitions/Coin" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv": { + "$ref": "#/definitions/Decimal" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, + "vault_position_value": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VaultPositionValue", + "type": "object", + "required": [ + "base_coin", + "vault_coin" + ], + "properties": { + "base_coin": { + "description": "value of all unlocking positions", + "allOf": [ + { + "$ref": "#/definitions/CoinValue" + } + ] + }, + "vault_coin": { + "description": "value of locked or unlocked", + "allOf": [ + { + "$ref": "#/definitions/CoinValue" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "CoinValue": { + "type": "object", + "required": [ + "amount", + "denom", + "value" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "vaults_info": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_VaultInfoResponse", diff --git a/schemas/mars-rover-health-computer/mars-rover-health-computer.json b/schemas/mars-rover-health-computer/mars-rover-health-computer.json new file mode 100644 index 000000000..947402157 --- /dev/null +++ b/schemas/mars-rover-health-computer/mars-rover-health-computer.json @@ -0,0 +1,562 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HealthComputer", + "description": "`HealthComputer` is a shared struct with the frontend that gets compiled to wasm. For this reason, it uses a dependency-injection-like pattern where all required data is needed up front.", + "type": "object", + "required": [ + "allowed_coins", + "denoms_data", + "positions", + "vaults_data" + ], + "properties": { + "allowed_coins": { + "type": "array", + "items": { + "type": "string" + } + }, + "denoms_data": { + "$ref": "#/definitions/DenomsData" + }, + "positions": { + "$ref": "#/definitions/Positions" + }, + "vaults_data": { + "$ref": "#/definitions/VaultsData" + } + }, + "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" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CoinValue": { + "type": "object", + "required": [ + "amount", + "denom", + "value" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "DebtAmount": { + "type": "object", + "required": [ + "amount", + "denom", + "shares" + ], + "properties": { + "amount": { + "description": "amount of coins", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "type": "string" + }, + "shares": { + "description": "number of shares in debt pool", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "DenomsData": { + "type": "object", + "required": [ + "markets", + "prices" + ], + "properties": { + "markets": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Market" + } + }, + "prices": { + "description": "Must include data from info.base token for the vaults", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false + }, + "InterestRateModel": { + "type": "object", + "required": [ + "base", + "optimal_utilization_rate", + "slope_1", + "slope_2" + ], + "properties": { + "base": { + "description": "Base rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "optimal_utilization_rate": { + "description": "Optimal utilization rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope_1": { + "description": "Slope parameter for interest rate model function when utilization_rate < optimal_utilization_rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope_2": { + "description": "Slope parameter for interest rate model function when utilization_rate >= optimal_utilization_rate", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "LentAmount": { + "type": "object", + "required": [ + "amount", + "denom", + "shares" + ], + "properties": { + "amount": { + "description": "amount of coins", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "type": "string" + }, + "shares": { + "description": "number of shares in lent pool", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, + "LockingVaultAmount": { + "type": "object", + "required": [ + "locked", + "unlocking" + ], + "properties": { + "locked": { + "$ref": "#/definitions/VaultAmount" + }, + "unlocking": { + "$ref": "#/definitions/UnlockingPositions" + } + }, + "additionalProperties": false + }, + "Market": { + "type": "object", + "required": [ + "borrow_enabled", + "borrow_index", + "borrow_rate", + "collateral_total_scaled", + "debt_total_scaled", + "denom", + "deposit_cap", + "deposit_enabled", + "indexes_last_updated", + "interest_rate_model", + "liquidation_bonus", + "liquidation_threshold", + "liquidity_index", + "liquidity_rate", + "max_loan_to_value", + "reserve_factor" + ], + "properties": { + "borrow_enabled": { + "description": "If false cannot borrow", + "type": "boolean" + }, + "borrow_index": { + "description": "Borrow index (Used to compute borrow interest)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "borrow_rate": { + "description": "Rate charged to borrowers", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "collateral_total_scaled": { + "description": "Total collateral scaled for the market's currency", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "debt_total_scaled": { + "description": "Total debt scaled for the market's currency", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "Denom of the asset", + "type": "string" + }, + "deposit_cap": { + "description": "Deposit Cap (defined in terms of the asset)", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "deposit_enabled": { + "description": "If false cannot deposit", + "type": "boolean" + }, + "indexes_last_updated": { + "description": "Timestamp (seconds) where indexes and where last updated", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "interest_rate_model": { + "description": "model (params + internal state) that defines how interest rate behaves", + "allOf": [ + { + "$ref": "#/definitions/InterestRateModel" + } + ] + }, + "liquidation_bonus": { + "description": "Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral from user in an amount equal to debt repayed + bonus)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "liquidation_threshold": { + "description": "Base asset amount in debt position per \"base asset\" of asset collateral that if surpassed makes the user's position liquidatable.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "liquidity_index": { + "description": "Liquidity index (Used to compute deposit interest)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "liquidity_rate": { + "description": "Rate paid to depositors", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "max_loan_to_value": { + "description": "Max base asset that can be borrowed per \"base asset\" collateral when using the asset as collateral", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "reserve_factor": { + "description": "Portion of the borrow rate that is kept as protocol rewards", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "Positions": { + "type": "object", + "required": [ + "account_id", + "debts", + "deposits", + "lends", + "vaults" + ], + "properties": { + "account_id": { + "type": "string" + }, + "debts": { + "type": "array", + "items": { + "$ref": "#/definitions/DebtAmount" + } + }, + "deposits": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "lends": { + "type": "array", + "items": { + "$ref": "#/definitions/LentAmount" + } + }, + "vaults": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultPosition" + } + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "UnlockingPositions": { + "type": "array", + "items": { + "$ref": "#/definitions/VaultUnlockingPosition" + } + }, + "VaultAmount": { + "$ref": "#/definitions/Uint128" + }, + "VaultBase_for_Addr": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + }, + "VaultConfig": { + "type": "object", + "required": [ + "deposit_cap", + "liquidation_threshold", + "max_ltv", + "whitelisted" + ], + "properties": { + "deposit_cap": { + "$ref": "#/definitions/Coin" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv": { + "$ref": "#/definitions/Decimal" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "VaultPosition": { + "type": "object", + "required": [ + "amount", + "vault" + ], + "properties": { + "amount": { + "$ref": "#/definitions/VaultPositionAmount" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + }, + "additionalProperties": false + }, + "VaultPositionAmount": { + "oneOf": [ + { + "type": "object", + "required": [ + "unlocked" + ], + "properties": { + "unlocked": { + "$ref": "#/definitions/VaultAmount" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "locking" + ], + "properties": { + "locking": { + "$ref": "#/definitions/LockingVaultAmount" + } + }, + "additionalProperties": false + } + ] + }, + "VaultPositionValue": { + "type": "object", + "required": [ + "base_coin", + "vault_coin" + ], + "properties": { + "base_coin": { + "description": "value of all unlocking positions", + "allOf": [ + { + "$ref": "#/definitions/CoinValue" + } + ] + }, + "vault_coin": { + "description": "value of locked or unlocked", + "allOf": [ + { + "$ref": "#/definitions/CoinValue" + } + ] + } + }, + "additionalProperties": false + }, + "VaultUnlockingPosition": { + "type": "object", + "required": [ + "coin", + "id" + ], + "properties": { + "coin": { + "description": "Coins that are awaiting to be unlocked (underlying, not vault tokens)", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "id": { + "description": "Unique identifier representing the unlocking position. Needed for `ExecuteMsg::WithdrawUnlocked {}` call.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "VaultsData": { + "type": "object", + "required": [ + "vault_configs", + "vault_values" + ], + "properties": { + "vault_configs": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/VaultConfig" + } + }, + "vault_values": { + "description": "explain this, unlocked or locked value given the pricing method of vaults, cannot use individual coins", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/VaultPositionValue" + } + } + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/schemas/mars-rover-health-types/mars-rover-health-types.json b/schemas/mars-rover-health-types/mars-rover-health-types.json new file mode 100644 index 000000000..37084c39c --- /dev/null +++ b/schemas/mars-rover-health-types/mars-rover-health-types.json @@ -0,0 +1,269 @@ +{ + "contract_name": "mars-rover-health-types", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "description": "The address with privileged access to update config", + "type": "string" + } + }, + "additionalProperties": false + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Manages owner role state", + "type": "object", + "required": [ + "update_owner" + ], + "properties": { + "update_owner": { + "$ref": "#/definitions/OwnerUpdate" + } + }, + "additionalProperties": false + }, + { + "description": "Update contract config constants", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "credit_manager" + ], + "properties": { + "credit_manager": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "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" + ] + } + ] + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "health" + ], + "properties": { + "health": { + "type": "object", + "required": [ + "account_id" + ], + "properties": { + "account_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "type": "object", + "required": [ + "owner_response" + ], + "properties": { + "credit_manager_addr": { + "type": [ + "string", + "null" + ] + }, + "owner_response": { + "$ref": "#/definitions/OwnerResponse" + } + }, + "additionalProperties": false, + "definitions": { + "OwnerResponse": { + "description": "Returned from Owner.query()", + "type": "object", + "required": [ + "abolished", + "initialized" + ], + "properties": { + "abolished": { + "type": "boolean" + }, + "initialized": { + "type": "boolean" + }, + "owner": { + "type": [ + "string", + "null" + ] + }, + "proposed": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + } + }, + "health": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HealthResponse", + "type": "object", + "required": [ + "above_max_ltv", + "liquidatable", + "liquidation_threshold_adjusted_collateral", + "max_ltv_adjusted_collateral", + "total_collateral_value", + "total_debt_value" + ], + "properties": { + "above_max_ltv": { + "type": "boolean" + }, + "liquidatable": { + "type": "boolean" + }, + "liquidation_health_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "liquidation_threshold_adjusted_collateral": { + "$ref": "#/definitions/Uint128" + }, + "max_ltv_adjusted_collateral": { + "$ref": "#/definitions/Uint128" + }, + "max_ltv_health_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "total_collateral_value": { + "$ref": "#/definitions/Uint128" + }, + "total_debt_value": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "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/scripts/deploy/addresses/osmo-test-4.json b/scripts/deploy/addresses/osmo-test-4.json index 679939b24..0accccb2d 100644 --- a/scripts/deploy/addresses/osmo-test-4.json +++ b/scripts/deploy/addresses/osmo-test-4.json @@ -1,7 +1,8 @@ { - "mockVault": "osmo132xghzdpsk2zm9a3tu2sh3zttfralkgp5ha7dvfu4age4xfpx4zqf3vqj6", - "swapper": "osmo1ewg8munakw2u0qh8lee375ytpg97zezf8lpnzrt2025xc93xx2ksw6zg64", - "zapper": "osmo1uvuewqqx0rtecx94yqauud62lkh4k9ayl7606nvtxd7uxhtrk7jqtauxq8", - "creditManager": "osmo13tj65wjs85wj0ry4daaqt2cp22p8dkd3uzajefp0c0tp37sgzfxqdyqu72", - "accountNft": "osmo1c93332nnydpn8f66zd4jka94hxvltff3tzt0fy0k7j3clyx265wq9csmfe" + "mockVault": "osmo173jhe6v3pzhnm600ghczs4jxfjftfm0a037vy7yda6s0z04jlrusfk3my2", + "swapper": "osmo1uj6r9tu440wwp2mhtagh48yvmeyeaqt2xa7kdnlhyrqcuthlj4ss7ghg6n", + "zapper": "osmo1ua8dwc9v8qjh7n3qf8kg6xvrwjm5yu9xxln7yjvgmrvfzaxvzsuqfcdnjq", + "healthContract": "osmo1zn52qqmxeusmes3teyced7wucjqf3gdjtg20zu832mnhyqmd7mlsxz3vqz", + "creditManager": "osmo12hgn4jec4tftahm7spf7c2aqsqrxzzk50hkq60e89atumyu0zvys7vzxdc", + "accountNft": "osmo1l8c3g6zy7kjhuh8d2kqyvxkw0myn4puxv0tzcdf9nwxd386r9l7s3vlhzq" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 700fe4e5a..88be797e5 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -5,6 +5,7 @@ import { ARTIFACTS_PATH, Storage } from './storage' import fs from 'fs' import { InstantiateMsgs } from '../../types/instantiateMsgs' import { InstantiateMsg as NftInstantiateMsg } from '../../types/generated/mars-account-nft/MarsAccountNft.types' +import { InstantiateMsg as HealthInstantiateMsg } from '../../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' import { InstantiateMsg as VaultInstantiateMsg } from '../../types/generated/mars-mock-vault/MarsMockVault.types' import { InstantiateMsg as SwapperInstantiateMsg } from '../../types/generated/mars-swapper-base/MarsSwapperBase.types' import { InstantiateMsg as ZapperInstantiateMsg } from '../../types/generated/mars-zapper-base/MarsZapperBase.types' @@ -26,6 +27,7 @@ import { MarsCreditManagerClient } from '../../types/generated/mars-credit-manag import { InitOrUpdateAssetParams } from '../../types/generated/mars-mock-red-bank/MarsMockRedBank.types' import { PriceSource } from '../../types/priceSource' import { kebabCase } from 'lodash' +import { MarsRoverHealthTypesClient } from '../../types/generated/mars-rover-health-types/MarsRoverHealthTypes.client' export class Deployer { constructor( @@ -69,6 +71,29 @@ export class Deployer { ) } + async instantiateHealthContract() { + const msg: HealthInstantiateMsg = { + owner: this.deployerAddr, + } + await this.instantiate('healthContract', this.storage.codeIds.healthContract!, msg) + } + + async setCmOnHealthContract() { + if (this.storage.actions.healthContractConfigUpdate) { + printGray('Credit manager address') + } else { + let hExec = new MarsRoverHealthTypesClient( + this.cwClient, + this.deployerAddr, + this.storage.addresses.healthContract!, + ) + + printBlue('Setting credit manager address on health contract config') + await hExec.updateConfig({ creditManager: this.storage.addresses.creditManager! }) + } + this.storage.actions.healthContractConfigUpdate = true + } + async instantiateNftContract() { const msg: NftInstantiateMsg = { max_value_for_burn: this.config.maxValueForBurn, @@ -153,6 +178,7 @@ export class Deployer { max_close_factor: this.config.maxCloseFactor, swapper: this.storage.addresses.swapper!, zapper: this.storage.addresses.zapper!, + health_contract: this.storage.addresses.healthContract!, } if (this.config.testActions) { diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 3c00d7e60..b3ec42ae0 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -21,14 +21,17 @@ export const taskRunner = async ({ await deployer.upload('mockVault', wasmFile('mars_mock_vault')) await deployer.upload('swapper', wasmFile(swapperContractName)) await deployer.upload('zapper', wasmFile(zapperContractName)) + await deployer.upload('healthContract', wasmFile('mars_rover_health')) await deployer.upload('creditManager', wasmFile('mars_credit_manager')) // Instantiate contracts await deployer.instantiateMockVault() await deployer.instantiateSwapper() await deployer.instantiateZapper() + await deployer.instantiateHealthContract() await deployer.instantiateCreditManager() await deployer.instantiateNftContract() + await deployer.setCmOnHealthContract() await deployer.transferNftContractOwnership() await deployer.saveDeploymentAddrsToFile() diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/config.ts index 9a27927bb..2aea212c1 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/config.ts @@ -141,7 +141,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { depositAmount: '100', lendAmount: '10', secondaryDenom: uatom, - startingAmountForTestUser: '2500000', + startingAmountForTestUser: '4000000', // If test actions run out of gas, increment this swap: { slippage: '0.4', amount: '40', diff --git a/scripts/package.json b/scripts/package.json index 9f1a2014f..8022cec09 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -25,13 +25,13 @@ "devDependencies": { "@babel/preset-env": "^7.20.2", "@babel/preset-typescript": "^7.18.6", - "@types/jest": "^29.2.6", - "@typescript-eslint/eslint-plugin": "^5.48.2", - "@typescript-eslint/parser": "^5.48.2", - "eslint": "^8.32.0", + "@types/jest": "^29.4.0", + "@typescript-eslint/eslint-plugin": "^5.51.0", + "@typescript-eslint/parser": "^5.51.0", + "eslint": "^8.33.0", "eslint-config-prettier": "^8.6.0", - "jest": "^29.3.1", - "prettier": "^2.8.3", - "typescript": "^4.9.4" + "jest": "^29.4.2", + "prettier": "^2.8.4", + "typescript": "^4.9.5" } } diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index b9711e41f..54dd22b63 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -7,6 +7,7 @@ export type Uint128 = string export interface InstantiateMsg { + health_contract?: string | null max_value_for_burn: Uint128 minter: string name: string @@ -82,6 +83,7 @@ export type Expiration = export type Timestamp = Uint64 export type Uint64 = string export interface NftConfigUpdates { + health_contract_addr?: string | null max_value_for_burn?: Uint128 | null proposed_new_minter?: string | null } @@ -184,6 +186,7 @@ export interface ApprovalsResponse { approvals: Approval[] } export interface NftConfigBaseForString { + health_contract_addr?: string | null max_value_for_burn: Uint128 proposed_new_minter?: string | null } diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 15a988d27..c47b53b6f 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -8,6 +8,7 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { + HealthContractBaseForString, Decimal, Uint128, OracleBaseForString, @@ -29,9 +30,15 @@ import { ActionCoin, ConfigUpdates, NftConfigUpdates, - Health, VaultBaseForAddr, QueryMsg, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, + VaultPosition, + LockingVaultAmount, + VaultUnlockingPosition, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, @@ -42,28 +49,23 @@ import { LentShares, ArrayOfVaultWithBalance, VaultWithBalance, - VaultPositionAmount, - VaultAmount, - VaultAmount1, - UnlockingPositions, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, - VaultPosition, - LockingVaultAmount, - VaultUnlockingPosition, ArrayOfString, ConfigResponse, ArrayOfCoin, - HealthResponse, Positions, DebtAmount, LentAmount, - ArrayOfVaultInfoResponse, VaultInfoResponse, + VaultPositionValue, + CoinValue, + ArrayOfVaultInfoResponse, } from './MarsCreditManager.types' export interface MarsCreditManagerReadOnlyInterface { contractAddress: string config: () => Promise + vaultInfo: ({ vault }: { vault: VaultBaseForString }) => Promise vaultsInfo: ({ limit, startAfter, @@ -79,7 +81,6 @@ export interface MarsCreditManagerReadOnlyInterface { startAfter?: string }) => Promise positions: ({ accountId }: { accountId: string }) => Promise - health: ({ accountId }: { accountId: string }) => Promise allCoinBalances: ({ limit, startAfter, @@ -140,6 +141,11 @@ export interface MarsCreditManagerReadOnlyInterface { lpTokenOut: string }) => Promise estimateWithdrawLiquidity: ({ lpToken }: { lpToken: Coin }) => Promise + vaultPositionValue: ({ + vaultPosition, + }: { + vaultPosition: VaultPosition + }) => Promise } export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyInterface { client: CosmWasmClient @@ -149,10 +155,10 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn this.client = client this.contractAddress = contractAddress this.config = this.config.bind(this) + this.vaultInfo = this.vaultInfo.bind(this) this.vaultsInfo = this.vaultsInfo.bind(this) this.allowedCoins = this.allowedCoins.bind(this) this.positions = this.positions.bind(this) - this.health = this.health.bind(this) this.allCoinBalances = this.allCoinBalances.bind(this) this.allDebtShares = this.allDebtShares.bind(this) this.totalDebtShares = this.totalDebtShares.bind(this) @@ -165,6 +171,7 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn this.allTotalVaultCoinBalances = this.allTotalVaultCoinBalances.bind(this) this.estimateProvideLiquidity = this.estimateProvideLiquidity.bind(this) this.estimateWithdrawLiquidity = this.estimateWithdrawLiquidity.bind(this) + this.vaultPositionValue = this.vaultPositionValue.bind(this) } config = async (): Promise => { @@ -172,6 +179,13 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn config: {}, }) } + vaultInfo = async ({ vault }: { vault: VaultBaseForString }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + vault_info: { + vault, + }, + }) + } vaultsInfo = async ({ limit, startAfter, @@ -207,13 +221,6 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn }, }) } - health = async ({ accountId }: { accountId: string }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - health: { - account_id: accountId, - }, - }) - } allCoinBalances = async ({ limit, startAfter, @@ -350,6 +357,17 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn }, }) } + vaultPositionValue = async ({ + vaultPosition, + }: { + vaultPosition: VaultPosition + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + vault_position_value: { + vault_position: vaultPosition, + }, + }) + } } export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInterface { contractAddress: string diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index b2778ee51..4cd0c2261 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -9,6 +9,7 @@ import { MsgExecuteContractEncodeObject } from 'cosmwasm' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { + HealthContractBaseForString, Decimal, Uint128, OracleBaseForString, @@ -30,9 +31,15 @@ import { ActionCoin, ConfigUpdates, NftConfigUpdates, - Health, VaultBaseForAddr, QueryMsg, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, + VaultPosition, + LockingVaultAmount, + VaultUnlockingPosition, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, @@ -43,24 +50,18 @@ import { LentShares, ArrayOfVaultWithBalance, VaultWithBalance, - VaultPositionAmount, - VaultAmount, - VaultAmount1, - UnlockingPositions, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, - VaultPosition, - LockingVaultAmount, - VaultUnlockingPosition, ArrayOfString, ConfigResponse, ArrayOfCoin, - HealthResponse, Positions, DebtAmount, LentAmount, - ArrayOfVaultInfoResponse, VaultInfoResponse, + VaultPositionValue, + CoinValue, + ArrayOfVaultInfoResponse, } from './MarsCreditManager.types' export interface MarsCreditManagerMessage { contractAddress: string diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 3535ae9a2..9aa8863be 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -9,6 +9,7 @@ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tan import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { + HealthContractBaseForString, Decimal, Uint128, OracleBaseForString, @@ -30,9 +31,15 @@ import { ActionCoin, ConfigUpdates, NftConfigUpdates, - Health, VaultBaseForAddr, QueryMsg, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, + VaultPosition, + LockingVaultAmount, + VaultUnlockingPosition, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, @@ -43,24 +50,18 @@ import { LentShares, ArrayOfVaultWithBalance, VaultWithBalance, - VaultPositionAmount, - VaultAmount, - VaultAmount1, - UnlockingPositions, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, - VaultPosition, - LockingVaultAmount, - VaultUnlockingPosition, ArrayOfString, ConfigResponse, ArrayOfCoin, - HealthResponse, Positions, DebtAmount, LentAmount, - ArrayOfVaultInfoResponse, VaultInfoResponse, + VaultPositionValue, + CoinValue, + ArrayOfVaultInfoResponse, } from './MarsCreditManager.types' import { MarsCreditManagerQueryClient, MarsCreditManagerClient } from './MarsCreditManager.client' export const marsCreditManagerQueryKeys = { @@ -75,6 +76,10 @@ export const marsCreditManagerQueryKeys = { [ { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, ] as const, + vaultInfo: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'vault_info', args }, + ] as const, vaultsInfo: (contractAddress: string | undefined, args?: Record) => [ { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'vaults_info', args }, @@ -87,10 +92,6 @@ export const marsCreditManagerQueryKeys = { [ { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'positions', args }, ] as const, - health: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'health', args }, - ] as const, allCoinBalances: (contractAddress: string | undefined, args?: Record) => [ { @@ -193,6 +194,14 @@ export const marsCreditManagerQueryKeys = { args, }, ] as const, + vaultPositionValue: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'vault_position_value', + args, + }, + ] as const, } export interface MarsCreditManagerReactQuery { client: MarsCreditManagerQueryClient | undefined @@ -203,6 +212,28 @@ export interface MarsCreditManagerReactQuery { initialData?: undefined } } +export interface MarsCreditManagerVaultPositionValueQuery + extends MarsCreditManagerReactQuery { + args: { + vaultPosition: VaultPosition + } +} +export function useMarsCreditManagerVaultPositionValueQuery({ + client, + args, + options, +}: MarsCreditManagerVaultPositionValueQuery) { + return useQuery( + marsCreditManagerQueryKeys.vaultPositionValue(client?.contractAddress, args), + () => + client + ? client.vaultPositionValue({ + vaultPosition: args.vaultPosition, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsCreditManagerEstimateWithdrawLiquidityQuery extends MarsCreditManagerReactQuery { args: { @@ -459,28 +490,6 @@ export function useMarsCreditManagerAllCoinBalancesQuery - extends MarsCreditManagerReactQuery { - args: { - accountId: string - } -} -export function useMarsCreditManagerHealthQuery({ - client, - args, - options, -}: MarsCreditManagerHealthQuery) { - return useQuery( - marsCreditManagerQueryKeys.health(client?.contractAddress, args), - () => - client - ? client.health({ - accountId: args.accountId, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} export interface MarsCreditManagerPositionsQuery extends MarsCreditManagerReactQuery { args: { @@ -551,6 +560,28 @@ export function useMarsCreditManagerVaultsInfoQuery + extends MarsCreditManagerReactQuery { + args: { + vault: VaultBaseForString + } +} +export function useMarsCreditManagerVaultInfoQuery({ + client, + args, + options, +}: MarsCreditManagerVaultInfoQuery) { + return useQuery( + marsCreditManagerQueryKeys.vaultInfo(client?.contractAddress, args), + () => + client + ? client.vaultInfo({ + vault: args.vault, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsCreditManagerConfigQuery extends MarsCreditManagerReactQuery {} export function useMarsCreditManagerConfigQuery({ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 209580c9b..db115e1c0 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -5,6 +5,7 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ +export type HealthContractBaseForString = string export type Decimal = string export type Uint128 = string export type OracleBaseForString = string @@ -13,6 +14,7 @@ export type SwapperBaseForString = string export type ZapperBaseForString = string export interface InstantiateMsg { allowed_coins: string[] + health_contract: HealthContractBaseForString max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: OracleBaseForString @@ -187,7 +189,7 @@ export type CallbackMsg = | { assert_max_ltv: { account_id: string - prev_health: Health + prev_max_ltv_health_factor?: Decimal | null } } | { @@ -283,6 +285,7 @@ export interface ActionCoin { export interface ConfigUpdates { account_nft?: string | null allowed_coins?: string[] | null + health_contract?: HealthContractBaseForString | null max_close_factor?: Decimal | null max_unlocking_positions?: Uint128 | null oracle?: OracleBaseForString | null @@ -291,17 +294,10 @@ export interface ConfigUpdates { zapper?: ZapperBaseForString | null } export interface NftConfigUpdates { + health_contract_addr?: string | null max_value_for_burn?: Uint128 | null proposed_new_minter?: string | null } -export interface Health { - liquidation_health_factor?: Decimal | null - liquidation_threshold_adjusted_collateral: Uint128 - max_ltv_adjusted_collateral: Uint128 - max_ltv_health_factor?: Decimal | null - total_collateral_value: Uint128 - total_debt_value: Uint128 -} export interface VaultBaseForAddr { address: Addr } @@ -309,6 +305,11 @@ export type QueryMsg = | { config: {} } + | { + vault_info: { + vault: VaultBaseForString + } + } | { vaults_info: { limit?: number | null @@ -326,11 +327,6 @@ export type QueryMsg = account_id: string } } - | { - health: { - account_id: string - } - } | { all_coin_balances: { limit?: number | null @@ -395,6 +391,33 @@ export type QueryMsg = lp_token: Coin } } + | { + vault_position_value: { + vault_position: VaultPosition + } + } +export type VaultPositionAmount = + | { + unlocked: VaultAmount + } + | { + locking: LockingVaultAmount + } +export type VaultAmount = string +export type VaultAmount1 = string +export type UnlockingPositions = VaultUnlockingPosition[] +export interface VaultPosition { + amount: VaultPositionAmount + vault: VaultBaseForAddr +} +export interface LockingVaultAmount { + locked: VaultAmount1 + unlocking: UnlockingPositions +} +export interface VaultUnlockingPosition { + coin: Coin + id: number +} export type ArrayOfCoinBalanceResponseItem = CoinBalanceResponseItem[] export interface CoinBalanceResponseItem { account_id: string @@ -422,36 +445,15 @@ export interface VaultWithBalance { balance: Uint128 vault: VaultBaseForAddr } -export type VaultPositionAmount = - | { - unlocked: VaultAmount - } - | { - locking: LockingVaultAmount - } -export type VaultAmount = string -export type VaultAmount1 = string -export type UnlockingPositions = VaultUnlockingPosition[] export type ArrayOfVaultPositionResponseItem = VaultPositionResponseItem[] export interface VaultPositionResponseItem { account_id: string position: VaultPosition } -export interface VaultPosition { - amount: VaultPositionAmount - vault: VaultBaseForAddr -} -export interface LockingVaultAmount { - locked: VaultAmount1 - unlocking: UnlockingPositions -} -export interface VaultUnlockingPosition { - coin: Coin - id: number -} export type ArrayOfString = string[] export interface ConfigResponse { account_nft?: string | null + health_contract: string max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: string @@ -462,16 +464,6 @@ export interface ConfigResponse { zapper: string } export type ArrayOfCoin = Coin[] -export interface HealthResponse { - above_max_ltv: boolean - liquidatable: boolean - liquidation_health_factor?: Decimal | null - liquidation_threshold_adjusted_collateral: Uint128 - max_ltv_adjusted_collateral: Uint128 - max_ltv_health_factor?: Decimal | null - total_collateral_value: Uint128 - total_debt_value: Uint128 -} export interface Positions { account_id: string debts: DebtAmount[] @@ -489,9 +481,18 @@ export interface LentAmount { denom: string shares: Uint128 } -export type ArrayOfVaultInfoResponse = VaultInfoResponse[] export interface VaultInfoResponse { config: VaultConfig utilization: Coin vault: VaultBaseForString } +export interface VaultPositionValue { + base_coin: CoinValue + vault_coin: CoinValue +} +export interface CoinValue { + amount: Uint128 + denom: string + value: Uint128 +} +export type ArrayOfVaultInfoResponse = VaultInfoResponse[] diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index 220ea0864..48d6f5495 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -10,14 +10,13 @@ import { StdFee } from '@cosmjs/amino' import { InstantiateMsg, ExecuteMsg, - Decimal, Uint128, VaultPositionAmount, VaultAmount, VaultAmount1, UnlockingPositions, Addr, - HealthResponse, + Decimal, Positions, DebtAmount, Coin, @@ -44,12 +43,15 @@ import { ArrayOfString, ConfigResponse, ArrayOfCoin, - ArrayOfVaultInfoResponse, VaultInfoResponse, + VaultPositionValue, + CoinValue, + ArrayOfVaultInfoResponse, } from './MarsMockCreditManager.types' export interface MarsMockCreditManagerReadOnlyInterface { contractAddress: string config: () => Promise + vaultInfo: ({ vault }: { vault: VaultBaseForString }) => Promise vaultsInfo: ({ limit, startAfter, @@ -65,7 +67,6 @@ export interface MarsMockCreditManagerReadOnlyInterface { startAfter?: string }) => Promise positions: ({ accountId }: { accountId: string }) => Promise - health: ({ accountId }: { accountId: string }) => Promise allCoinBalances: ({ limit, startAfter, @@ -126,6 +127,11 @@ export interface MarsMockCreditManagerReadOnlyInterface { lpTokenOut: string }) => Promise estimateWithdrawLiquidity: ({ lpToken }: { lpToken: Coin }) => Promise + vaultPositionValue: ({ + vaultPosition, + }: { + vaultPosition: VaultPosition + }) => Promise } export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerReadOnlyInterface { client: CosmWasmClient @@ -135,10 +141,10 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe this.client = client this.contractAddress = contractAddress this.config = this.config.bind(this) + this.vaultInfo = this.vaultInfo.bind(this) this.vaultsInfo = this.vaultsInfo.bind(this) this.allowedCoins = this.allowedCoins.bind(this) this.positions = this.positions.bind(this) - this.health = this.health.bind(this) this.allCoinBalances = this.allCoinBalances.bind(this) this.allDebtShares = this.allDebtShares.bind(this) this.totalDebtShares = this.totalDebtShares.bind(this) @@ -151,6 +157,7 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe this.allTotalVaultCoinBalances = this.allTotalVaultCoinBalances.bind(this) this.estimateProvideLiquidity = this.estimateProvideLiquidity.bind(this) this.estimateWithdrawLiquidity = this.estimateWithdrawLiquidity.bind(this) + this.vaultPositionValue = this.vaultPositionValue.bind(this) } config = async (): Promise => { @@ -158,6 +165,13 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe config: {}, }) } + vaultInfo = async ({ vault }: { vault: VaultBaseForString }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + vault_info: { + vault, + }, + }) + } vaultsInfo = async ({ limit, startAfter, @@ -193,13 +207,6 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe }, }) } - health = async ({ accountId }: { accountId: string }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - health: { - account_id: accountId, - }, - }) - } allCoinBalances = async ({ limit, startAfter, @@ -336,22 +343,21 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe }, }) } + vaultPositionValue = async ({ + vaultPosition, + }: { + vaultPosition: VaultPosition + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + vault_position_value: { + vault_position: vaultPosition, + }, + }) + } } export interface MarsMockCreditManagerInterface extends MarsMockCreditManagerReadOnlyInterface { contractAddress: string sender: string - setHealthResponse: ( - { - accountId, - response, - }: { - accountId: string - response: HealthResponse - }, - fee?: number | StdFee | 'auto', - memo?: string, - funds?: Coin[], - ) => Promise setPositionsResponse: ( { accountId, @@ -395,38 +401,11 @@ export class MarsMockCreditManagerClient this.client = client this.sender = sender this.contractAddress = contractAddress - this.setHealthResponse = this.setHealthResponse.bind(this) this.setPositionsResponse = this.setPositionsResponse.bind(this) this.setAllowedCoins = this.setAllowedCoins.bind(this) this.setVaultConfig = this.setVaultConfig.bind(this) } - setHealthResponse = async ( - { - accountId, - response, - }: { - accountId: string - response: HealthResponse - }, - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - set_health_response: { - account_id: accountId, - response, - }, - }, - fee, - memo, - funds, - ) - } setPositionsResponse = async ( { accountId, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index afadc31e4..f95f4ec8b 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -11,14 +11,13 @@ import { toUtf8 } from '@cosmjs/encoding' import { InstantiateMsg, ExecuteMsg, - Decimal, Uint128, VaultPositionAmount, VaultAmount, VaultAmount1, UnlockingPositions, Addr, - HealthResponse, + Decimal, Positions, DebtAmount, Coin, @@ -45,22 +44,14 @@ import { ArrayOfString, ConfigResponse, ArrayOfCoin, - ArrayOfVaultInfoResponse, VaultInfoResponse, + VaultPositionValue, + CoinValue, + ArrayOfVaultInfoResponse, } from './MarsMockCreditManager.types' export interface MarsMockCreditManagerMessage { contractAddress: string sender: string - setHealthResponse: ( - { - accountId, - response, - }: { - accountId: string - response: HealthResponse - }, - funds?: Coin[], - ) => MsgExecuteContractEncodeObject setPositionsResponse: ( { accountId, @@ -90,39 +81,11 @@ export class MarsMockCreditManagerMessageComposer implements MarsMockCreditManag constructor(sender: string, contractAddress: string) { this.sender = sender this.contractAddress = contractAddress - this.setHealthResponse = this.setHealthResponse.bind(this) this.setPositionsResponse = this.setPositionsResponse.bind(this) this.setAllowedCoins = this.setAllowedCoins.bind(this) this.setVaultConfig = this.setVaultConfig.bind(this) } - setHealthResponse = ( - { - accountId, - response, - }: { - accountId: string - response: HealthResponse - }, - funds?: Coin[], - ): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - set_health_response: { - account_id: accountId, - response, - }, - }), - ), - funds, - }), - } - } setPositionsResponse = ( { accountId, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index 4b01390ea..b887bdab7 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -11,14 +11,13 @@ import { StdFee } from '@cosmjs/amino' import { InstantiateMsg, ExecuteMsg, - Decimal, Uint128, VaultPositionAmount, VaultAmount, VaultAmount1, UnlockingPositions, Addr, - HealthResponse, + Decimal, Positions, DebtAmount, Coin, @@ -45,8 +44,10 @@ import { ArrayOfString, ConfigResponse, ArrayOfCoin, - ArrayOfVaultInfoResponse, VaultInfoResponse, + VaultPositionValue, + CoinValue, + ArrayOfVaultInfoResponse, } from './MarsMockCreditManager.types' import { MarsMockCreditManagerQueryClient, @@ -64,6 +65,10 @@ export const marsMockCreditManagerQueryKeys = { [ { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, ] as const, + vaultInfo: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'vault_info', args }, + ] as const, vaultsInfo: (contractAddress: string | undefined, args?: Record) => [ { @@ -84,10 +89,6 @@ export const marsMockCreditManagerQueryKeys = { [ { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'positions', args }, ] as const, - health: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'health', args }, - ] as const, allCoinBalances: (contractAddress: string | undefined, args?: Record) => [ { @@ -190,6 +191,14 @@ export const marsMockCreditManagerQueryKeys = { args, }, ] as const, + vaultPositionValue: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], + method: 'vault_position_value', + args, + }, + ] as const, } export interface MarsMockCreditManagerReactQuery { client: MarsMockCreditManagerQueryClient | undefined @@ -200,6 +209,28 @@ export interface MarsMockCreditManagerReactQuery { initialData?: undefined } } +export interface MarsMockCreditManagerVaultPositionValueQuery + extends MarsMockCreditManagerReactQuery { + args: { + vaultPosition: VaultPosition + } +} +export function useMarsMockCreditManagerVaultPositionValueQuery({ + client, + args, + options, +}: MarsMockCreditManagerVaultPositionValueQuery) { + return useQuery( + marsMockCreditManagerQueryKeys.vaultPositionValue(client?.contractAddress, args), + () => + client + ? client.vaultPositionValue({ + vaultPosition: args.vaultPosition, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsMockCreditManagerEstimateWithdrawLiquidityQuery extends MarsMockCreditManagerReactQuery { args: { @@ -454,28 +485,6 @@ export function useMarsMockCreditManagerAllCoinBalancesQuery< { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsMockCreditManagerHealthQuery - extends MarsMockCreditManagerReactQuery { - args: { - accountId: string - } -} -export function useMarsMockCreditManagerHealthQuery({ - client, - args, - options, -}: MarsMockCreditManagerHealthQuery) { - return useQuery( - marsMockCreditManagerQueryKeys.health(client?.contractAddress, args), - () => - client - ? client.health({ - accountId: args.accountId, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} export interface MarsMockCreditManagerPositionsQuery extends MarsMockCreditManagerReactQuery { args: { @@ -546,6 +555,28 @@ export function useMarsMockCreditManagerVaultsInfoQuery + extends MarsMockCreditManagerReactQuery { + args: { + vault: VaultBaseForString + } +} +export function useMarsMockCreditManagerVaultInfoQuery({ + client, + args, + options, +}: MarsMockCreditManagerVaultInfoQuery) { + return useQuery( + marsMockCreditManagerQueryKeys.vaultInfo(client?.contractAddress, args), + () => + client + ? client.vaultInfo({ + vault: args.vault, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsMockCreditManagerConfigQuery extends MarsMockCreditManagerReactQuery {} export function useMarsMockCreditManagerConfigQuery({ @@ -625,27 +656,3 @@ export function useMarsMockCreditManagerSetPositionsResponseMutation( options, ) } -export interface MarsMockCreditManagerSetHealthResponseMutation { - client: MarsMockCreditManagerClient - msg: { - accountId: string - response: HealthResponse - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsMockCreditManagerSetHealthResponseMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => - client.setHealthResponse(msg, fee, memo, funds), - options, - ) -} diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index f195b5528..029ddf5e5 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -9,12 +9,6 @@ export interface InstantiateMsg { [k: string]: unknown } export type ExecuteMsg = - | { - set_health_response: { - account_id: string - response: HealthResponse - } - } | { set_positions_response: { account_id: string @@ -30,7 +24,6 @@ export type ExecuteMsg = config: VaultConfig } } -export type Decimal = string export type Uint128 = string export type VaultPositionAmount = | { @@ -43,16 +36,7 @@ export type VaultAmount = string export type VaultAmount1 = string export type UnlockingPositions = VaultUnlockingPosition[] export type Addr = string -export interface HealthResponse { - above_max_ltv: boolean - liquidatable: boolean - liquidation_health_factor?: Decimal | null - liquidation_threshold_adjusted_collateral: Uint128 - max_ltv_adjusted_collateral: Uint128 - max_ltv_health_factor?: Decimal | null - total_collateral_value: Uint128 - total_debt_value: Uint128 -} +export type Decimal = string export interface Positions { account_id: string debts: DebtAmount[] @@ -100,6 +84,11 @@ export type QueryMsg = | { config: {} } + | { + vault_info: { + vault: VaultBaseForString + } + } | { vaults_info: { limit?: number | null @@ -117,11 +106,6 @@ export type QueryMsg = account_id: string } } - | { - health: { - account_id: string - } - } | { all_coin_balances: { limit?: number | null @@ -186,6 +170,11 @@ export type QueryMsg = lp_token: Coin } } + | { + vault_position_value: { + vault_position: VaultPosition + } + } export interface VaultBaseForString { address: string } @@ -224,6 +213,7 @@ export interface VaultPositionResponseItem { export type ArrayOfString = string[] export interface ConfigResponse { account_nft?: string | null + health_contract: string max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: string @@ -234,9 +224,18 @@ export interface ConfigResponse { zapper: string } export type ArrayOfCoin = Coin[] -export type ArrayOfVaultInfoResponse = VaultInfoResponse[] export interface VaultInfoResponse { config: VaultConfig utilization: Coin vault: VaultBaseForString } +export interface VaultPositionValue { + base_coin: CoinValue + vault_coin: CoinValue +} +export interface CoinValue { + amount: Uint128 + denom: string + value: Uint128 +} +export type ArrayOfVaultInfoResponse = VaultInfoResponse[] diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts new file mode 100644 index 000000000..44f1cfce5 --- /dev/null +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts @@ -0,0 +1,32 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { + Decimal, + Uint128, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, + Addr, + HealthComputer, + DenomsData, + Market, + InterestRateModel, + Positions, + DebtAmount, + Coin, + LentAmount, + VaultPosition, + LockingVaultAmount, + VaultUnlockingPosition, + VaultBaseForAddr, + VaultsData, + VaultConfig, + VaultPositionValue, + CoinValue, +} from './MarsRoverHealthComputer.types' diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts new file mode 100644 index 000000000..44f1cfce5 --- /dev/null +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts @@ -0,0 +1,32 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { + Decimal, + Uint128, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, + Addr, + HealthComputer, + DenomsData, + Market, + InterestRateModel, + Positions, + DebtAmount, + Coin, + LentAmount, + VaultPosition, + LockingVaultAmount, + VaultUnlockingPosition, + VaultBaseForAddr, + VaultsData, + VaultConfig, + VaultPositionValue, + CoinValue, +} from './MarsRoverHealthComputer.types' diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts new file mode 100644 index 000000000..5360f5142 --- /dev/null +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts @@ -0,0 +1,33 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { + Decimal, + Uint128, + VaultPositionAmount, + VaultAmount, + VaultAmount1, + UnlockingPositions, + Addr, + HealthComputer, + DenomsData, + Market, + InterestRateModel, + Positions, + DebtAmount, + Coin, + LentAmount, + VaultPosition, + LockingVaultAmount, + VaultUnlockingPosition, + VaultBaseForAddr, + VaultsData, + VaultConfig, + VaultPositionValue, + CoinValue, +} from './MarsRoverHealthComputer.types' +import './MarsRoverHealthComputer.client' diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts new file mode 100644 index 000000000..00293fc76 --- /dev/null +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts @@ -0,0 +1,118 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export type Decimal = string +export type Uint128 = string +export type VaultPositionAmount = + | { + unlocked: VaultAmount + } + | { + locking: LockingVaultAmount + } +export type VaultAmount = string +export type VaultAmount1 = string +export type UnlockingPositions = VaultUnlockingPosition[] +export type Addr = string +export interface HealthComputer { + allowed_coins: string[] + denoms_data: DenomsData + positions: Positions + vaults_data: VaultsData +} +export interface DenomsData { + markets: { + [k: string]: Market + } + prices: { + [k: string]: Decimal + } +} +export interface Market { + borrow_enabled: boolean + borrow_index: Decimal + borrow_rate: Decimal + collateral_total_scaled: Uint128 + debt_total_scaled: Uint128 + denom: string + deposit_cap: Uint128 + deposit_enabled: boolean + indexes_last_updated: number + interest_rate_model: InterestRateModel + liquidation_bonus: Decimal + liquidation_threshold: Decimal + liquidity_index: Decimal + liquidity_rate: Decimal + max_loan_to_value: Decimal + reserve_factor: Decimal +} +export interface InterestRateModel { + base: Decimal + optimal_utilization_rate: Decimal + slope_1: Decimal + slope_2: Decimal +} +export interface Positions { + account_id: string + debts: DebtAmount[] + deposits: Coin[] + lends: LentAmount[] + vaults: VaultPosition[] +} +export interface DebtAmount { + amount: Uint128 + denom: string + shares: Uint128 +} +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} +export interface LentAmount { + amount: Uint128 + denom: string + shares: Uint128 +} +export interface VaultPosition { + amount: VaultPositionAmount + vault: VaultBaseForAddr +} +export interface LockingVaultAmount { + locked: VaultAmount1 + unlocking: UnlockingPositions +} +export interface VaultUnlockingPosition { + coin: Coin + id: number +} +export interface VaultBaseForAddr { + address: Addr +} +export interface VaultsData { + vault_configs: { + [k: string]: VaultConfig + } + vault_values: { + [k: string]: VaultPositionValue + } +} +export interface VaultConfig { + deposit_cap: Coin + liquidation_threshold: Decimal + max_ltv: Decimal + whitelisted: boolean +} +export interface VaultPositionValue { + base_coin: CoinValue + vault_coin: CoinValue +} +export interface CoinValue { + amount: Uint128 + denom: string + value: Uint128 +} diff --git a/scripts/types/generated/mars-rover-health-computer/bundle.ts b/scripts/types/generated/mars-rover-health-computer/bundle.ts new file mode 100644 index 000000000..4304094d6 --- /dev/null +++ b/scripts/types/generated/mars-rover-health-computer/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _24 from './MarsRoverHealthComputer.types' +import * as _25 from './MarsRoverHealthComputer.client' +import * as _26 from './MarsRoverHealthComputer.message-composer' +import * as _27 from './MarsRoverHealthComputer.react-query' +export namespace contracts { + export const MarsRoverHealthComputer = { ..._24, ..._25, ..._26, ..._27 } +} diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts new file mode 100644 index 000000000..547189d9a --- /dev/null +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts @@ -0,0 +1,125 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { Coin, StdFee } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + OwnerUpdate, + QueryMsg, + ConfigResponse, + OwnerResponse, + Decimal, + Uint128, + HealthResponse, +} from './MarsRoverHealthTypes.types' +export interface MarsRoverHealthTypesReadOnlyInterface { + contractAddress: string + health: ({ accountId }: { accountId: string }) => Promise + config: () => Promise +} +export class MarsRoverHealthTypesQueryClient implements MarsRoverHealthTypesReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.health = this.health.bind(this) + this.config = this.config.bind(this) + } + + health = async ({ accountId }: { accountId: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + health: { + account_id: accountId, + }, + }) + } + config = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + config: {}, + }) + } +} +export interface MarsRoverHealthTypesInterface extends MarsRoverHealthTypesReadOnlyInterface { + contractAddress: string + sender: string + updateOwner: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + updateConfig: ( + { + creditManager, + }: { + creditManager: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise +} +export class MarsRoverHealthTypesClient + extends MarsRoverHealthTypesQueryClient + implements MarsRoverHealthTypesInterface +{ + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.updateOwner = this.updateOwner.bind(this) + this.updateConfig = this.updateConfig.bind(this) + } + + updateOwner = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_owner: {}, + }, + fee, + memo, + funds, + ) + } + updateConfig = async ( + { + creditManager, + }: { + creditManager: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_config: { + credit_manager: creditManager, + }, + }, + fee, + memo, + funds, + ) + } +} diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts new file mode 100644 index 000000000..db2d977e5 --- /dev/null +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts @@ -0,0 +1,86 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { Coin } from '@cosmjs/amino' +import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + InstantiateMsg, + ExecuteMsg, + OwnerUpdate, + QueryMsg, + ConfigResponse, + OwnerResponse, + Decimal, + Uint128, + HealthResponse, +} from './MarsRoverHealthTypes.types' +export interface MarsRoverHealthTypesMessage { + contractAddress: string + sender: string + updateOwner: (funds?: Coin[]) => MsgExecuteContractEncodeObject + updateConfig: ( + { + creditManager, + }: { + creditManager: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject +} +export class MarsRoverHealthTypesMessageComposer implements MarsRoverHealthTypesMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.updateOwner = this.updateOwner.bind(this) + this.updateConfig = this.updateConfig.bind(this) + } + + updateOwner = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_owner: {}, + }), + ), + funds, + }), + } + } + updateConfig = ( + { + creditManager, + }: { + creditManager: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_config: { + credit_manager: creditManager, + }, + }), + ), + funds, + }), + } + } +} diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts new file mode 100644 index 000000000..5136fd146 --- /dev/null +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts @@ -0,0 +1,127 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee, Coin } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + OwnerUpdate, + QueryMsg, + ConfigResponse, + OwnerResponse, + Decimal, + Uint128, + HealthResponse, +} from './MarsRoverHealthTypes.types' +import { + MarsRoverHealthTypesQueryClient, + MarsRoverHealthTypesClient, +} from './MarsRoverHealthTypes.client' +export const marsRoverHealthTypesQueryKeys = { + contract: [ + { + contract: 'marsRoverHealthTypes', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...marsRoverHealthTypesQueryKeys.contract[0], address: contractAddress }] as const, + health: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsRoverHealthTypesQueryKeys.address(contractAddress)[0], method: 'health', args }, + ] as const, + config: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsRoverHealthTypesQueryKeys.address(contractAddress)[0], method: 'config', args }, + ] as const, +} +export interface MarsRoverHealthTypesReactQuery { + client: MarsRoverHealthTypesQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MarsRoverHealthTypesConfigQuery + extends MarsRoverHealthTypesReactQuery {} +export function useMarsRoverHealthTypesConfigQuery({ + client, + options, +}: MarsRoverHealthTypesConfigQuery) { + return useQuery( + marsRoverHealthTypesQueryKeys.config(client?.contractAddress), + () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsRoverHealthTypesHealthQuery + extends MarsRoverHealthTypesReactQuery { + args: { + accountId: string + } +} +export function useMarsRoverHealthTypesHealthQuery({ + client, + args, + options, +}: MarsRoverHealthTypesHealthQuery) { + return useQuery( + marsRoverHealthTypesQueryKeys.health(client?.contractAddress, args), + () => + client + ? client.health({ + accountId: args.accountId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsRoverHealthTypesUpdateConfigMutation { + client: MarsRoverHealthTypesClient + msg: { + creditManager: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsRoverHealthTypesUpdateConfigMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateConfig(msg, fee, memo, funds), + options, + ) +} +export interface MarsRoverHealthTypesUpdateOwnerMutation { + client: MarsRoverHealthTypesClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsRoverHealthTypesUpdateOwnerMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateOwner(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts new file mode 100644 index 000000000..bf8a8c4bc --- /dev/null +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts @@ -0,0 +1,59 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + owner: string +} +export type ExecuteMsg = + | { + update_owner: OwnerUpdate + } + | { + update_config: { + credit_manager: string + } + } +export type OwnerUpdate = + | { + propose_new_owner: { + proposed: string + } + } + | 'clear_proposed' + | 'accept_proposed' + | 'abolish_owner_role' +export type QueryMsg = + | { + health: { + account_id: string + } + } + | { + config: {} + } +export interface ConfigResponse { + credit_manager_addr?: string | null + owner_response: OwnerResponse +} +export interface OwnerResponse { + abolished: boolean + initialized: boolean + owner?: string | null + proposed?: string | null +} +export type Decimal = string +export type Uint128 = string +export interface HealthResponse { + above_max_ltv: boolean + liquidatable: boolean + liquidation_health_factor?: Decimal | null + liquidation_threshold_adjusted_collateral: Uint128 + max_ltv_adjusted_collateral: Uint128 + max_ltv_health_factor?: Decimal | null + total_collateral_value: Uint128 + total_debt_value: Uint128 +} diff --git a/scripts/types/generated/mars-rover-health-types/bundle.ts b/scripts/types/generated/mars-rover-health-types/bundle.ts new file mode 100644 index 000000000..26a907af8 --- /dev/null +++ b/scripts/types/generated/mars-rover-health-types/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _28 from './MarsRoverHealthTypes.types' +import * as _29 from './MarsRoverHealthTypes.client' +import * as _30 from './MarsRoverHealthTypes.message-composer' +import * as _31 from './MarsRoverHealthTypes.react-query' +export namespace contracts { + export const MarsRoverHealthTypes = { ..._28, ..._29, ..._30, ..._31 } +} diff --git a/scripts/types/generated/mars-swapper-base/bundle.ts b/scripts/types/generated/mars-swapper-base/bundle.ts index 7793f5f39..0e9e9246a 100644 --- a/scripts/types/generated/mars-swapper-base/bundle.ts +++ b/scripts/types/generated/mars-swapper-base/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _24 from './MarsSwapperBase.types' -import * as _25 from './MarsSwapperBase.client' -import * as _26 from './MarsSwapperBase.message-composer' -import * as _27 from './MarsSwapperBase.react-query' +import * as _32 from './MarsSwapperBase.types' +import * as _33 from './MarsSwapperBase.client' +import * as _34 from './MarsSwapperBase.message-composer' +import * as _35 from './MarsSwapperBase.react-query' export namespace contracts { - export const MarsSwapperBase = { ..._24, ..._25, ..._26, ..._27 } + export const MarsSwapperBase = { ..._32, ..._33, ..._34, ..._35 } } diff --git a/scripts/types/generated/mars-zapper-base/bundle.ts b/scripts/types/generated/mars-zapper-base/bundle.ts index 0254e9b04..4dbe416d5 100644 --- a/scripts/types/generated/mars-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-zapper-base/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _28 from './MarsZapperBase.types' -import * as _29 from './MarsZapperBase.client' -import * as _30 from './MarsZapperBase.message-composer' -import * as _31 from './MarsZapperBase.react-query' +import * as _36 from './MarsZapperBase.types' +import * as _37 from './MarsZapperBase.client' +import * as _38 from './MarsZapperBase.message-composer' +import * as _39 from './MarsZapperBase.react-query' export namespace contracts { - export const MarsZapperBase = { ..._28, ..._29, ..._30, ..._31 } + export const MarsZapperBase = { ..._36, ..._37, ..._38, ..._39 } } diff --git a/scripts/types/instantiateMsgs.ts b/scripts/types/instantiateMsgs.ts index 2ded837db..dc712679c 100644 --- a/scripts/types/instantiateMsgs.ts +++ b/scripts/types/instantiateMsgs.ts @@ -5,6 +5,7 @@ import { InstantiateMsg as OracleInstantiateMsg } from './generated/mars-mock-or import { InstantiateMsg as RoverInstantiateMsg } from './generated/mars-credit-manager/MarsCreditManager.types' import { InstantiateMsg as SwapperInstantiateMsg } from './generated/mars-swapper-base/MarsSwapperBase.types' import { InstantiateMsg as ZapperInstantiateMsg } from './generated/mars-zapper-base/MarsZapperBase.types' +import { InstantiateMsg as HealthInstantiateMsg } from './generated/mars-rover-health-types/MarsRoverHealthTypes.types' export type InstantiateMsgs = | NftInstantiateMsg @@ -14,3 +15,4 @@ export type InstantiateMsgs = | RoverInstantiateMsg | SwapperInstantiateMsg | ZapperInstantiateMsg + | HealthInstantiateMsg diff --git a/scripts/types/storageItems.ts b/scripts/types/storageItems.ts index 7edfa61fe..a25a009c9 100644 --- a/scripts/types/storageItems.ts +++ b/scripts/types/storageItems.ts @@ -6,6 +6,7 @@ export interface StorageItems { mockOracle?: number swapper?: number zapper?: number + healthContract?: number creditManager?: number } addresses: { @@ -13,6 +14,7 @@ export interface StorageItems { mockVault?: string swapper?: string zapper?: string + healthContract?: string creditManager?: string } actions: { @@ -23,6 +25,7 @@ export interface StorageItems { grantedCreditLines?: boolean oraclePricesSet?: boolean redBankMarketsSet?: boolean + healthContractConfigUpdate?: boolean } owner?: string } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 9d74763b1..2911283b4 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1358,61 +1358,61 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.3.1": - version "29.3.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.3.1.tgz#3e3f876e4e47616ea3b1464b9fbda981872e9583" - integrity sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg== +"@jest/console@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.4.2.tgz#f78374905c2454764152904a344a2d5226b0ef09" + integrity sha512-0I/rEJwMpV9iwi9cDEnT71a5nNGK9lj8Z4+1pRAU2x/thVXCDnaTGrvxyK+cAqZTFVFCiR+hfVrP4l2m+dCmQg== dependencies: - "@jest/types" "^29.3.1" + "@jest/types" "^29.4.2" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.3.1" - jest-util "^29.3.1" + jest-message-util "^29.4.2" + jest-util "^29.4.2" slash "^3.0.0" -"@jest/core@^29.3.1": - version "29.3.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.3.1.tgz#bff00f413ff0128f4debec1099ba7dcd649774a1" - integrity sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw== +"@jest/core@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.4.2.tgz#6e999b67bdc2df9d96ba9b142465bda71ee472c2" + integrity sha512-KGuoQah0P3vGNlaS/l9/wQENZGNKGoWb+OPxh3gz+YzG7/XExvYu34MzikRndQCdM2S0tzExN4+FL37i6gZmCQ== dependencies: - "@jest/console" "^29.3.1" - "@jest/reporters" "^29.3.1" - "@jest/test-result" "^29.3.1" - "@jest/transform" "^29.3.1" - "@jest/types" "^29.3.1" + "@jest/console" "^29.4.2" + "@jest/reporters" "^29.4.2" + "@jest/test-result" "^29.4.2" + "@jest/transform" "^29.4.2" + "@jest/types" "^29.4.2" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^29.2.0" - jest-config "^29.3.1" - jest-haste-map "^29.3.1" - jest-message-util "^29.3.1" - jest-regex-util "^29.2.0" - jest-resolve "^29.3.1" - jest-resolve-dependencies "^29.3.1" - jest-runner "^29.3.1" - jest-runtime "^29.3.1" - jest-snapshot "^29.3.1" - jest-util "^29.3.1" - jest-validate "^29.3.1" - jest-watcher "^29.3.1" + jest-changed-files "^29.4.2" + jest-config "^29.4.2" + jest-haste-map "^29.4.2" + jest-message-util "^29.4.2" + jest-regex-util "^29.4.2" + jest-resolve "^29.4.2" + jest-resolve-dependencies "^29.4.2" + jest-runner "^29.4.2" + jest-runtime "^29.4.2" + jest-snapshot "^29.4.2" + jest-util "^29.4.2" + jest-validate "^29.4.2" + jest-watcher "^29.4.2" micromatch "^4.0.4" - pretty-format "^29.3.1" + pretty-format "^29.4.2" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.3.1": - version "29.3.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.3.1.tgz#eb039f726d5fcd14698acd072ac6576d41cfcaa6" - integrity sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag== +"@jest/environment@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.4.2.tgz#ee92c316ee2fbdf0bcd9d2db0ef42d64fea26b56" + integrity sha512-JKs3VUtse0vQfCaFGJRX1bir9yBdtasxziSyu+pIiEllAQOe4oQhdCYIf3+Lx+nGglFktSKToBnRJfD5QKp+NQ== dependencies: - "@jest/fake-timers" "^29.3.1" - "@jest/types" "^29.3.1" + "@jest/fake-timers" "^29.4.2" + "@jest/types" "^29.4.2" "@types/node" "*" - jest-mock "^29.3.1" + jest-mock "^29.4.2" "@jest/expect-utils@^29.3.1": version "29.3.1" @@ -1421,46 +1421,53 @@ dependencies: jest-get-type "^29.2.0" -"@jest/expect@^29.3.1": - version "29.3.1" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.3.1.tgz#456385b62894349c1d196f2d183e3716d4c6a6cd" - integrity sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg== +"@jest/expect-utils@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.4.2.tgz#cd0065dfdd8e8a182aa350cc121db97b5eed7b3f" + integrity sha512-Dd3ilDJpBnqa0GiPN7QrudVs0cczMMHtehSo2CSTjm3zdHx0RcpmhFNVEltuEFeqfLIyWKFI224FsMSQ/nsJQA== dependencies: - expect "^29.3.1" - jest-snapshot "^29.3.1" + jest-get-type "^29.4.2" -"@jest/fake-timers@^29.3.1": - version "29.3.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.3.1.tgz#b140625095b60a44de820876d4c14da1aa963f67" - integrity sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A== +"@jest/expect@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.4.2.tgz#2d4a6a41b29380957c5094de19259f87f194578b" + integrity sha512-NUAeZVApzyaeLjfWIV/64zXjA2SS+NuUPHpAlO7IwVMGd5Vf9szTl9KEDlxY3B4liwLO31os88tYNHl6cpjtKQ== dependencies: - "@jest/types" "^29.3.1" - "@sinonjs/fake-timers" "^9.1.2" + expect "^29.4.2" + jest-snapshot "^29.4.2" + +"@jest/fake-timers@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.4.2.tgz#af43ee1a5720b987d0348f80df98f2cb17d45cd0" + integrity sha512-Ny1u0Wg6kCsHFWq7A/rW/tMhIedq2siiyHyLpHCmIhP7WmcAmd2cx95P+0xtTZlj5ZbJxIRQi4OPydZZUoiSQQ== + dependencies: + "@jest/types" "^29.4.2" + "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^29.3.1" - jest-mock "^29.3.1" - jest-util "^29.3.1" + jest-message-util "^29.4.2" + jest-mock "^29.4.2" + jest-util "^29.4.2" -"@jest/globals@^29.3.1": - version "29.3.1" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.3.1.tgz#92be078228e82d629df40c3656d45328f134a0c6" - integrity sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q== +"@jest/globals@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.4.2.tgz#73f85f5db0e17642258b25fd0b9fc89ddedb50eb" + integrity sha512-zCk70YGPzKnz/I9BNFDPlK+EuJLk21ur/NozVh6JVM86/YYZtZHqxFFQ62O9MWq7uf3vIZnvNA0BzzrtxD9iyg== dependencies: - "@jest/environment" "^29.3.1" - "@jest/expect" "^29.3.1" - "@jest/types" "^29.3.1" - jest-mock "^29.3.1" + "@jest/environment" "^29.4.2" + "@jest/expect" "^29.4.2" + "@jest/types" "^29.4.2" + jest-mock "^29.4.2" -"@jest/reporters@^29.3.1": - version "29.3.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.3.1.tgz#9a6d78c109608e677c25ddb34f907b90e07b4310" - integrity sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA== +"@jest/reporters@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.4.2.tgz#6abfa923941daae0acc76a18830ee9e79a22042d" + integrity sha512-10yw6YQe75zCgYcXgEND9kw3UZZH5tJeLzWv4vTk/2mrS1aY50A37F+XT2hPO5OqQFFnUWizXD8k1BMiATNfUw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.3.1" - "@jest/test-result" "^29.3.1" - "@jest/transform" "^29.3.1" - "@jest/types" "^29.3.1" + "@jest/console" "^29.4.2" + "@jest/test-result" "^29.4.2" + "@jest/transform" "^29.4.2" + "@jest/types" "^29.4.2" "@jridgewell/trace-mapping" "^0.3.15" "@types/node" "*" chalk "^4.0.0" @@ -1473,9 +1480,9 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.3.1" - jest-util "^29.3.1" - jest-worker "^29.3.1" + jest-message-util "^29.4.2" + jest-util "^29.4.2" + jest-worker "^29.4.2" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" @@ -1495,33 +1502,40 @@ dependencies: "@sinclair/typebox" "^0.24.1" -"@jest/source-map@^29.2.0": - version "29.2.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.2.0.tgz#ab3420c46d42508dcc3dc1c6deee0b613c235744" - integrity sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ== +"@jest/schemas@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.2.tgz#cf7cfe97c5649f518452b176c47ed07486270fc1" + integrity sha512-ZrGzGfh31NtdVH8tn0mgJw4khQuNHiKqdzJAFbCaERbyCP9tHlxWuL/mnMu8P7e/+k4puWjI1NOzi/sFsjce/g== + dependencies: + "@sinclair/typebox" "^0.25.16" + +"@jest/source-map@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.2.tgz#f9815d59e25cd3d6828e41489cd239271018d153" + integrity sha512-tIoqV5ZNgYI9XCKXMqbYe5JbumcvyTgNN+V5QW4My033lanijvCD0D4PI9tBw4pRTqWOc00/7X3KVvUh+qnF4Q== dependencies: "@jridgewell/trace-mapping" "^0.3.15" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.3.1": - version "29.3.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.3.1.tgz#92cd5099aa94be947560a24610aa76606de78f50" - integrity sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw== +"@jest/test-result@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.4.2.tgz#34b0ba069f2e3072261e4884c8fb6bd15ed6fb8d" + integrity sha512-HZsC3shhiHVvMtP+i55MGR5bPcc3obCFbA5bzIOb8pCjwBZf11cZliJncCgaVUbC5yoQNuGqCkC0Q3t6EItxZA== dependencies: - "@jest/console" "^29.3.1" - "@jest/types" "^29.3.1" + "@jest/console" "^29.4.2" + "@jest/types" "^29.4.2" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.3.1": - version "29.3.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz#fa24b3b050f7a59d48f7ef9e0b782ab65123090d" - integrity sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA== +"@jest/test-sequencer@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.4.2.tgz#8b48e5bc4af80b42edacaf2a733d4f295edf28fb" + integrity sha512-9Z2cVsD6CcObIVrWigHp2McRJhvCxL27xHtrZFgNC1RwnoSpDx6fZo8QYjJmziFlW9/hr78/3sxF54S8B6v8rg== dependencies: - "@jest/test-result" "^29.3.1" + "@jest/test-result" "^29.4.2" graceful-fs "^4.2.9" - jest-haste-map "^29.3.1" + jest-haste-map "^29.4.2" slash "^3.0.0" "@jest/transform@28.1.3": @@ -1545,26 +1559,26 @@ slash "^3.0.0" write-file-atomic "^4.0.1" -"@jest/transform@^29.3.1": - version "29.3.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.3.1.tgz#1e6bd3da4af50b5c82a539b7b1f3770568d6e36d" - integrity sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug== +"@jest/transform@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.4.2.tgz#b24b72dbab4c8675433a80e222d6a8ef4656fb81" + integrity sha512-kf1v5iTJHn7p9RbOsBuc/lcwyPtJaZJt5885C98omWz79NIeD3PfoiiaPSu7JyCyFzNOIzKhmMhQLUhlTL9BvQ== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.3.1" + "@jest/types" "^29.4.2" "@jridgewell/trace-mapping" "^0.3.15" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.3.1" - jest-regex-util "^29.2.0" - jest-util "^29.3.1" + jest-haste-map "^29.4.2" + jest-regex-util "^29.4.2" + jest-util "^29.4.2" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" - write-file-atomic "^4.0.1" + write-file-atomic "^4.0.2" "@jest/types@^28.1.3": version "28.1.3" @@ -1590,6 +1604,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.4.2": + version "29.4.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.4.2.tgz#8f724a414b1246b2bfd56ca5225d9e1f39540d82" + integrity sha512-CKlngyGP0fwlgC1BRUtPZSiWLBhyS9dKwKmyGxk8Z6M82LBEGB2aLQSg+U1MyLsU+M7UjnlLllBM2BLWKVm/Uw== + dependencies: + "@jest/schemas" "^29.4.2" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -1748,19 +1774,24 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== -"@sinonjs/commons@^1.7.0": - version "1.8.6" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9" - integrity sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ== +"@sinclair/typebox@^0.25.16": + version "0.25.21" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.21.tgz#763b05a4b472c93a8db29b2c3e359d55b29ce272" + integrity sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g== + +"@sinonjs/commons@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" + integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^9.1.2": - version "9.1.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" - integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== +"@sinonjs/fake-timers@^10.0.2": + version "10.0.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz#d10549ed1f423d80639c528b6c7f5a1017747d0c" + integrity sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw== dependencies: - "@sinonjs/commons" "^1.7.0" + "@sinonjs/commons" "^2.0.0" "@types/babel__core@^7.1.14": version "7.20.0" @@ -1829,10 +1860,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.2.6": - version "29.2.6" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.6.tgz#1d43c8e533463d0437edef30b2d45d5aa3d95b0a" - integrity sha512-XEUC/Tgw3uMh6Ho8GkUtQ2lPhY5Fmgyp3TdlkTJs1W9VgNxs+Ow/x3Elh8lHQKqCbZL0AubQuqWjHVT033Hhrw== +"@types/jest@^29.4.0": + version "29.4.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.4.0.tgz#a8444ad1704493e84dbf07bb05990b275b3b9206" + integrity sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -1889,87 +1920,88 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.48.2": - version "5.48.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz#112e6ae1e23a1dc8333ce82bb9c65c2608b4d8a3" - integrity sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg== +"@typescript-eslint/eslint-plugin@^5.51.0": + version "5.51.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz#da3f2819633061ced84bb82c53bba45a6fe9963a" + integrity sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ== dependencies: - "@typescript-eslint/scope-manager" "5.48.2" - "@typescript-eslint/type-utils" "5.48.2" - "@typescript-eslint/utils" "5.48.2" + "@typescript-eslint/scope-manager" "5.51.0" + "@typescript-eslint/type-utils" "5.51.0" + "@typescript-eslint/utils" "5.51.0" debug "^4.3.4" + grapheme-splitter "^1.0.4" ignore "^5.2.0" natural-compare-lite "^1.4.0" regexpp "^3.2.0" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.48.2": - version "5.48.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.48.2.tgz#c9edef2a0922d26a37dba03be20c5fff378313b3" - integrity sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw== +"@typescript-eslint/parser@^5.51.0": + version "5.51.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.51.0.tgz#2d74626652096d966ef107f44b9479f02f51f271" + integrity sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA== dependencies: - "@typescript-eslint/scope-manager" "5.48.2" - "@typescript-eslint/types" "5.48.2" - "@typescript-eslint/typescript-estree" "5.48.2" + "@typescript-eslint/scope-manager" "5.51.0" + "@typescript-eslint/types" "5.51.0" + "@typescript-eslint/typescript-estree" "5.51.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.48.2": - version "5.48.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz#bb7676cb78f1e94921eaab637a4b5d596f838abc" - integrity sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw== +"@typescript-eslint/scope-manager@5.51.0": + version "5.51.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz#ad3e3c2ecf762d9a4196c0fbfe19b142ac498990" + integrity sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ== dependencies: - "@typescript-eslint/types" "5.48.2" - "@typescript-eslint/visitor-keys" "5.48.2" + "@typescript-eslint/types" "5.51.0" + "@typescript-eslint/visitor-keys" "5.51.0" -"@typescript-eslint/type-utils@5.48.2": - version "5.48.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz#7d3aeca9fa37a7ab7e3d9056a99b42f342c48ad7" - integrity sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew== +"@typescript-eslint/type-utils@5.51.0": + version "5.51.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz#7af48005531700b62a20963501d47dfb27095988" + integrity sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ== dependencies: - "@typescript-eslint/typescript-estree" "5.48.2" - "@typescript-eslint/utils" "5.48.2" + "@typescript-eslint/typescript-estree" "5.51.0" + "@typescript-eslint/utils" "5.51.0" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.48.2": - version "5.48.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.48.2.tgz#635706abb1ec164137f92148f06f794438c97b8e" - integrity sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA== +"@typescript-eslint/types@5.51.0": + version "5.51.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.51.0.tgz#e7c1622f46c7eea7e12bbf1edfb496d4dec37c90" + integrity sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw== -"@typescript-eslint/typescript-estree@5.48.2": - version "5.48.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz#6e206b462942b32383582a6c9251c05021cc21b0" - integrity sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg== +"@typescript-eslint/typescript-estree@5.51.0": + version "5.51.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz#0ec8170d7247a892c2b21845b06c11eb0718f8de" + integrity sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA== dependencies: - "@typescript-eslint/types" "5.48.2" - "@typescript-eslint/visitor-keys" "5.48.2" + "@typescript-eslint/types" "5.51.0" + "@typescript-eslint/visitor-keys" "5.51.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.48.2": - version "5.48.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.48.2.tgz#3777a91dcb22b8499a25519e06eef2e9569295a3" - integrity sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow== +"@typescript-eslint/utils@5.51.0": + version "5.51.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.51.0.tgz#074f4fabd5b12afe9c8aa6fdee881c050f8b4d47" + integrity sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw== dependencies: "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.48.2" - "@typescript-eslint/types" "5.48.2" - "@typescript-eslint/typescript-estree" "5.48.2" + "@typescript-eslint/scope-manager" "5.51.0" + "@typescript-eslint/types" "5.51.0" + "@typescript-eslint/typescript-estree" "5.51.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.48.2": - version "5.48.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz#c247582a0bcce467461d7b696513bf9455000060" - integrity sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ== +"@typescript-eslint/visitor-keys@5.51.0": + version "5.51.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz#c0147dd9a36c0de758aaebd5b48cae1ec59eba87" + integrity sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ== dependencies: - "@typescript-eslint/types" "5.48.2" + "@typescript-eslint/types" "5.51.0" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -2097,15 +2129,15 @@ axios@^0.21.2: dependencies: follow-redirects "^1.14.0" -babel-jest@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.3.1.tgz#05c83e0d128cd48c453eea851482a38782249f44" - integrity sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA== +babel-jest@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.4.2.tgz#b17b9f64be288040877cbe2649f91ac3b63b2ba6" + integrity sha512-vcghSqhtowXPG84posYkkkzcZsdayFkubUgbE3/1tuGbX7AQtwCkkNA/wIbB0BMjuCPoqTkiDyKN7Ty7d3uwNQ== dependencies: - "@jest/transform" "^29.3.1" + "@jest/transform" "^29.4.2" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.2.0" + babel-preset-jest "^29.4.2" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" @@ -2121,10 +2153,10 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz#23ee99c37390a98cfddf3ef4a78674180d823094" - integrity sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA== +babel-plugin-jest-hoist@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.4.2.tgz#22aa43e255230f02371ffef1cac7eedef58f60bc" + integrity sha512-5HZRCfMeWypFEonRbEkwWXtNS1sQK159LhRVyRuLzyfVBxDy/34Tr/rg4YVi0SScSJ4fqeaR/OIeceJ/LaQ0pQ== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -2181,12 +2213,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz#3048bea3a1af222e3505e4a767a974c95a7620dc" - integrity sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA== +babel-preset-jest@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.4.2.tgz#f0b20c6a79a9f155515e72a2d4f537fe002a4e38" + integrity sha512-ecWdaLY/8JyfUDr0oELBMpj3R5I1L6ZqG+kRJmwqfHtLWuPrJStR0LUkvUhfykJWTsXXMnohsayN/twltBbDrQ== dependencies: - babel-plugin-jest-hoist "^29.2.0" + babel-plugin-jest-hoist "^29.4.2" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: @@ -2517,6 +2549,11 @@ diff-sequences@^29.3.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.3.1.tgz#104b5b95fe725932421a9c6e5b4bef84c3f2249e" integrity sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ== +diff-sequences@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.2.tgz#711fe6bd8a5869fe2539cee4a5152425ff671fda" + integrity sha512-R6P0Y6PrsH3n4hUXxL3nns0rbRk6Q33js3ygJBeEpbzLzgcNuJ61+u0RXasFpTKISw99TxUzFnumSnRLsjhLaw== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -2665,10 +2702,10 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.32.0: - version "8.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.32.0.tgz#d9690056bb6f1a302bd991e7090f5b68fbaea861" - integrity sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ== +eslint@^8.33.0: + version "8.33.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.33.0.tgz#02f110f32998cb598c6461f24f4d306e41ca33d7" + integrity sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA== dependencies: "@eslint/eslintrc" "^1.4.1" "@humanwhocodes/config-array" "^0.11.8" @@ -2781,7 +2818,7 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0, expect@^29.3.1: +expect@^29.0.0: version "29.3.1" resolved "https://registry.yarnpkg.com/expect/-/expect-29.3.1.tgz#92877aad3f7deefc2e3f6430dd195b92295554a6" integrity sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA== @@ -2792,6 +2829,17 @@ expect@^29.0.0, expect@^29.3.1: jest-message-util "^29.3.1" jest-util "^29.3.1" +expect@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.4.2.tgz#2ae34eb88de797c64a1541ad0f1e2ea8a7a7b492" + integrity sha512-+JHYg9O3hd3RlICG90OPVjRkPBoiUH7PxvDVMnRiaq1g6JUgZStX514erMl0v2Dc5SkfVbm7ztqbd6qHHPn+mQ== + dependencies: + "@jest/expect-utils" "^29.4.2" + jest-get-type "^29.4.2" + jest-matcher-utils "^29.4.2" + jest-message-util "^29.4.2" + jest-util "^29.4.2" + ext@^1.1.2: version "1.7.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" @@ -3342,82 +3390,82 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.2.0.tgz#b6598daa9803ea6a4dce7968e20ab380ddbee289" - integrity sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA== +jest-changed-files@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.4.2.tgz#bee1fafc8b620d6251423d1978a0080546bc4376" + integrity sha512-Qdd+AXdqD16PQa+VsWJpxR3kN0JyOCX1iugQfx5nUgAsI4gwsKviXkpclxOK9ZnwaY2IQVHz+771eAvqeOlfuw== dependencies: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.3.1.tgz#177d07c5c0beae8ef2937a67de68f1e17bbf1b4a" - integrity sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg== +jest-circus@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.4.2.tgz#2d00c04baefd0ee2a277014cd494d4b5970663ed" + integrity sha512-wW3ztp6a2P5c1yOc1Cfrt5ozJ7neWmqeXm/4SYiqcSriyisgq63bwFj1NuRdSR5iqS0CMEYwSZd89ZA47W9zUg== dependencies: - "@jest/environment" "^29.3.1" - "@jest/expect" "^29.3.1" - "@jest/test-result" "^29.3.1" - "@jest/types" "^29.3.1" + "@jest/environment" "^29.4.2" + "@jest/expect" "^29.4.2" + "@jest/test-result" "^29.4.2" + "@jest/types" "^29.4.2" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^29.3.1" - jest-matcher-utils "^29.3.1" - jest-message-util "^29.3.1" - jest-runtime "^29.3.1" - jest-snapshot "^29.3.1" - jest-util "^29.3.1" + jest-each "^29.4.2" + jest-matcher-utils "^29.4.2" + jest-message-util "^29.4.2" + jest-runtime "^29.4.2" + jest-snapshot "^29.4.2" + jest-util "^29.4.2" p-limit "^3.1.0" - pretty-format "^29.3.1" + pretty-format "^29.4.2" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.3.1.tgz#e89dff427db3b1df50cea9a393ebd8640790416d" - integrity sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ== +jest-cli@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.4.2.tgz#94a2f913a0a7a49d11bee98ad88bf48baae941f4" + integrity sha512-b+eGUtXq/K2v7SH3QcJvFvaUaCDS1/YAZBYz0m28Q/Ppyr+1qNaHmVYikOrbHVbZqYQs2IeI3p76uy6BWbXq8Q== dependencies: - "@jest/core" "^29.3.1" - "@jest/test-result" "^29.3.1" - "@jest/types" "^29.3.1" + "@jest/core" "^29.4.2" + "@jest/test-result" "^29.4.2" + "@jest/types" "^29.4.2" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.3.1" - jest-util "^29.3.1" - jest-validate "^29.3.1" + jest-config "^29.4.2" + jest-util "^29.4.2" + jest-validate "^29.4.2" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.3.1.tgz#0bc3dcb0959ff8662957f1259947aedaefb7f3c6" - integrity sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg== +jest-config@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.4.2.tgz#15386dd9ed2f7059516915515f786b8836a98f07" + integrity sha512-919CtnXic52YM0zW4C1QxjG6aNueX1kBGthuMtvFtRTAxhKfJmiXC9qwHmi6o2josjbDz8QlWyY55F1SIVmCWA== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.3.1" - "@jest/types" "^29.3.1" - babel-jest "^29.3.1" + "@jest/test-sequencer" "^29.4.2" + "@jest/types" "^29.4.2" + babel-jest "^29.4.2" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.3.1" - jest-environment-node "^29.3.1" - jest-get-type "^29.2.0" - jest-regex-util "^29.2.0" - jest-resolve "^29.3.1" - jest-runner "^29.3.1" - jest-util "^29.3.1" - jest-validate "^29.3.1" + jest-circus "^29.4.2" + jest-environment-node "^29.4.2" + jest-get-type "^29.4.2" + jest-regex-util "^29.4.2" + jest-resolve "^29.4.2" + jest-runner "^29.4.2" + jest-util "^29.4.2" + jest-validate "^29.4.2" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.3.1" + pretty-format "^29.4.2" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -3431,41 +3479,56 @@ jest-diff@^29.3.1: jest-get-type "^29.2.0" pretty-format "^29.3.1" -jest-docblock@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.2.0.tgz#307203e20b637d97cee04809efc1d43afc641e82" - integrity sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A== +jest-diff@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.4.2.tgz#b88502d5dc02d97f6512d73c37da8b36f49b4871" + integrity sha512-EK8DSajVtnjx9sa1BkjZq3mqChm2Cd8rIzdXkQMA8e0wuXq53ypz6s5o5V8HRZkoEt2ywJ3eeNWFKWeYr8HK4g== dependencies: - detect-newline "^3.0.0" + chalk "^4.0.0" + diff-sequences "^29.4.2" + jest-get-type "^29.4.2" + pretty-format "^29.4.2" -jest-each@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.3.1.tgz#bc375c8734f1bb96625d83d1ca03ef508379e132" - integrity sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA== +jest-docblock@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.2.tgz#c78a95eedf9a24c0a6cc16cf2abdc4b8b0f2531b" + integrity sha512-dV2JdahgClL34Y5vLrAHde3nF3yo2jKRH+GIYJuCpfqwEJZcikzeafVTGAjbOfKPG17ez9iWXwUYp7yefeCRag== dependencies: - "@jest/types" "^29.3.1" - chalk "^4.0.0" - jest-get-type "^29.2.0" - jest-util "^29.3.1" - pretty-format "^29.3.1" + detect-newline "^3.0.0" -jest-environment-node@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.3.1.tgz#5023b32472b3fba91db5c799a0d5624ad4803e74" - integrity sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag== +jest-each@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.4.2.tgz#e1347aff1303f4c35470827a62c029d389c5d44a" + integrity sha512-trvKZb0JYiCndc55V1Yh0Luqi7AsAdDWpV+mKT/5vkpnnFQfuQACV72IoRV161aAr6kAVIBpmYzwhBzm34vQkA== dependencies: - "@jest/environment" "^29.3.1" - "@jest/fake-timers" "^29.3.1" - "@jest/types" "^29.3.1" + "@jest/types" "^29.4.2" + chalk "^4.0.0" + jest-get-type "^29.4.2" + jest-util "^29.4.2" + pretty-format "^29.4.2" + +jest-environment-node@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.4.2.tgz#0eab835b41e25fd0c1a72f62665fc8db08762ad2" + integrity sha512-MLPrqUcOnNBc8zTOfqBbxtoa8/Ee8tZ7UFW7hRDQSUT+NGsvS96wlbHGTf+EFAT9KC3VNb7fWEM6oyvmxtE/9w== + dependencies: + "@jest/environment" "^29.4.2" + "@jest/fake-timers" "^29.4.2" + "@jest/types" "^29.4.2" "@types/node" "*" - jest-mock "^29.3.1" - jest-util "^29.3.1" + jest-mock "^29.4.2" + jest-util "^29.4.2" jest-get-type@^29.2.0: version "29.2.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.2.0.tgz#726646f927ef61d583a3b3adb1ab13f3a5036408" integrity sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA== +jest-get-type@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.2.tgz#7cb63f154bca8d8f57364d01614477d466fa43fe" + integrity sha512-vERN30V5i2N6lqlFu4ljdTqQAgrkTFMC9xaIIfOPYBw04pufjXRty5RuXBiB1d72tGbURa/UgoiHB90ruOSivg== + jest-haste-map@^28.1.3: version "28.1.3" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.3.tgz#abd5451129a38d9841049644f34b034308944e2b" @@ -3485,32 +3548,32 @@ jest-haste-map@^28.1.3: optionalDependencies: fsevents "^2.3.2" -jest-haste-map@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.3.1.tgz#af83b4347f1dae5ee8c2fb57368dc0bb3e5af843" - integrity sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A== +jest-haste-map@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.4.2.tgz#9112df3f5121e643f1b2dcbaa86ab11b0b90b49a" + integrity sha512-WkUgo26LN5UHPknkezrBzr7lUtV1OpGsp+NfXbBwHztsFruS3gz+AMTTBcEklvi8uPzpISzYjdKXYZQJXBnfvw== dependencies: - "@jest/types" "^29.3.1" + "@jest/types" "^29.4.2" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^29.2.0" - jest-util "^29.3.1" - jest-worker "^29.3.1" + jest-regex-util "^29.4.2" + jest-util "^29.4.2" + jest-worker "^29.4.2" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz#95336d020170671db0ee166b75cd8ef647265518" - integrity sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA== +jest-leak-detector@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.4.2.tgz#8f05c6680e0cb46a1d577c0d3da9793bed3ea97b" + integrity sha512-Wa62HuRJmWXtX9F00nUpWlrbaH5axeYCdyRsOs/+Rb1Vb6+qWTlB5rKwCCRKtorM7owNwKsyJ8NRDUcZ8ghYUA== dependencies: - jest-get-type "^29.2.0" - pretty-format "^29.3.1" + jest-get-type "^29.4.2" + pretty-format "^29.4.2" jest-matcher-utils@^29.3.1: version "29.3.1" @@ -3522,6 +3585,16 @@ jest-matcher-utils@^29.3.1: jest-get-type "^29.2.0" pretty-format "^29.3.1" +jest-matcher-utils@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.4.2.tgz#08d0bf5abf242e3834bec92c7ef5071732839e85" + integrity sha512-EZaAQy2je6Uqkrm6frnxBIdaWtSYFoR8SVb2sNLAtldswlR/29JAgx+hy67llT3+hXBaLB0zAm5UfeqerioZyg== + dependencies: + chalk "^4.0.0" + jest-diff "^29.4.2" + jest-get-type "^29.4.2" + pretty-format "^29.4.2" + jest-message-util@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.3.1.tgz#37bc5c468dfe5120712053dd03faf0f053bd6adb" @@ -3537,14 +3610,29 @@ jest-message-util@^29.3.1: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.3.1.tgz#60287d92e5010979d01f218c6b215b688e0f313e" - integrity sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA== +jest-message-util@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.4.2.tgz#309a2924eae6ca67cf7f25781a2af1902deee717" + integrity sha512-SElcuN4s6PNKpOEtTInjOAA8QvItu0iugkXqhYyguRvQoXapg5gN+9RQxLAkakChZA7Y26j6yUCsFWN+hlKD6g== dependencies: - "@jest/types" "^29.3.1" + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.4.2" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.4.2" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.4.2.tgz#e1054be66fb3e975d26d4528fcde6979e4759de8" + integrity sha512-x1FSd4Gvx2yIahdaIKoBjwji6XpboDunSJ95RpntGrYulI1ByuYQCKN/P7hvk09JB74IonU3IPLdkutEWYt++g== + dependencies: + "@jest/types" "^29.4.2" "@types/node" "*" - jest-util "^29.3.1" + jest-util "^29.4.2" jest-pnp-resolver@^1.2.2: version "1.2.3" @@ -3556,93 +3644,94 @@ jest-regex-util@^28.0.2: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== -jest-regex-util@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.2.0.tgz#82ef3b587e8c303357728d0322d48bbfd2971f7b" - integrity sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA== +jest-regex-util@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.2.tgz#19187cca35d301f8126cf7a021dd4dcb7b58a1ca" + integrity sha512-XYZXOqUl1y31H6VLMrrUL1ZhXuiymLKPz0BO1kEeR5xER9Tv86RZrjTm74g5l9bPJQXA/hyLdaVPN/sdqfteig== -jest-resolve-dependencies@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz#a6a329708a128e68d67c49f38678a4a4a914c3bf" - integrity sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA== +jest-resolve-dependencies@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.4.2.tgz#6359db606f5967b68ca8bbe9dbc07a4306c12bf7" + integrity sha512-6pL4ptFw62rjdrPk7rRpzJYgcRqRZNsZTF1VxVTZMishbO6ObyWvX57yHOaNGgKoADtAHRFYdHQUEvYMJATbDg== dependencies: - jest-regex-util "^29.2.0" - jest-snapshot "^29.3.1" + jest-regex-util "^29.4.2" + jest-snapshot "^29.4.2" -jest-resolve@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.3.1.tgz#9a4b6b65387a3141e4a40815535c7f196f1a68a7" - integrity sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw== +jest-resolve@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.4.2.tgz#8831f449671d08d161fe493003f61dc9b55b808e" + integrity sha512-RtKWW0mbR3I4UdkOrW7552IFGLYQ5AF9YrzD0FnIOkDu0rAMlA5/Y1+r7lhCAP4nXSBTaE7ueeqj6IOwZpgoqw== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.3.1" + jest-haste-map "^29.4.2" jest-pnp-resolver "^1.2.2" - jest-util "^29.3.1" - jest-validate "^29.3.1" + jest-util "^29.4.2" + jest-validate "^29.4.2" resolve "^1.20.0" - resolve.exports "^1.1.0" + resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.3.1.tgz#a92a879a47dd096fea46bb1517b0a99418ee9e2d" - integrity sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA== +jest-runner@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.4.2.tgz#2bcecf72303369df4ef1e6e983c22a89870d5125" + integrity sha512-wqwt0drm7JGjwdH+x1XgAl+TFPH7poowMguPQINYxaukCqlczAcNLJiK+OLxUxQAEWMdy+e6nHZlFHO5s7EuRg== dependencies: - "@jest/console" "^29.3.1" - "@jest/environment" "^29.3.1" - "@jest/test-result" "^29.3.1" - "@jest/transform" "^29.3.1" - "@jest/types" "^29.3.1" + "@jest/console" "^29.4.2" + "@jest/environment" "^29.4.2" + "@jest/test-result" "^29.4.2" + "@jest/transform" "^29.4.2" + "@jest/types" "^29.4.2" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^29.2.0" - jest-environment-node "^29.3.1" - jest-haste-map "^29.3.1" - jest-leak-detector "^29.3.1" - jest-message-util "^29.3.1" - jest-resolve "^29.3.1" - jest-runtime "^29.3.1" - jest-util "^29.3.1" - jest-watcher "^29.3.1" - jest-worker "^29.3.1" + jest-docblock "^29.4.2" + jest-environment-node "^29.4.2" + jest-haste-map "^29.4.2" + jest-leak-detector "^29.4.2" + jest-message-util "^29.4.2" + jest-resolve "^29.4.2" + jest-runtime "^29.4.2" + jest-util "^29.4.2" + jest-watcher "^29.4.2" + jest-worker "^29.4.2" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.3.1.tgz#21efccb1a66911d6d8591276a6182f520b86737a" - integrity sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A== - dependencies: - "@jest/environment" "^29.3.1" - "@jest/fake-timers" "^29.3.1" - "@jest/globals" "^29.3.1" - "@jest/source-map" "^29.2.0" - "@jest/test-result" "^29.3.1" - "@jest/transform" "^29.3.1" - "@jest/types" "^29.3.1" +jest-runtime@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.4.2.tgz#d86b764c5b95d76cb26ed1f32644e99de5d5c134" + integrity sha512-3fque9vtpLzGuxT9eZqhxi+9EylKK/ESfhClv4P7Y9sqJPs58LjVhTt8jaMp/pRO38agll1CkSu9z9ieTQeRrw== + dependencies: + "@jest/environment" "^29.4.2" + "@jest/fake-timers" "^29.4.2" + "@jest/globals" "^29.4.2" + "@jest/source-map" "^29.4.2" + "@jest/test-result" "^29.4.2" + "@jest/transform" "^29.4.2" + "@jest/types" "^29.4.2" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.3.1" - jest-message-util "^29.3.1" - jest-mock "^29.3.1" - jest-regex-util "^29.2.0" - jest-resolve "^29.3.1" - jest-snapshot "^29.3.1" - jest-util "^29.3.1" + jest-haste-map "^29.4.2" + jest-message-util "^29.4.2" + jest-mock "^29.4.2" + jest-regex-util "^29.4.2" + jest-resolve "^29.4.2" + jest-snapshot "^29.4.2" + jest-util "^29.4.2" + semver "^7.3.5" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.3.1.tgz#17bcef71a453adc059a18a32ccbd594b8cc4e45e" - integrity sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA== +jest-snapshot@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.4.2.tgz#ba1fb9abb279fd2c85109ff1757bc56b503bbb3a" + integrity sha512-PdfubrSNN5KwroyMH158R23tWcAXJyx4pvSvWls1dHoLCaUhGul9rsL3uVjtqzRpkxlkMavQjGuWG1newPgmkw== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" @@ -3650,23 +3739,23 @@ jest-snapshot@^29.3.1: "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.3.1" - "@jest/transform" "^29.3.1" - "@jest/types" "^29.3.1" + "@jest/expect-utils" "^29.4.2" + "@jest/transform" "^29.4.2" + "@jest/types" "^29.4.2" "@types/babel__traverse" "^7.0.6" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.3.1" + expect "^29.4.2" graceful-fs "^4.2.9" - jest-diff "^29.3.1" - jest-get-type "^29.2.0" - jest-haste-map "^29.3.1" - jest-matcher-utils "^29.3.1" - jest-message-util "^29.3.1" - jest-util "^29.3.1" + jest-diff "^29.4.2" + jest-get-type "^29.4.2" + jest-haste-map "^29.4.2" + jest-matcher-utils "^29.4.2" + jest-message-util "^29.4.2" + jest-util "^29.4.2" natural-compare "^1.4.0" - pretty-format "^29.3.1" + pretty-format "^29.4.2" semver "^7.3.5" jest-util@^28.1.3: @@ -3693,30 +3782,42 @@ jest-util@^29.3.1: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.3.1.tgz#d56fefaa2e7d1fde3ecdc973c7f7f8f25eea704a" - integrity sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g== +jest-util@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.4.2.tgz#3db8580b295df453a97de4a1b42dd2578dabd2c2" + integrity sha512-wKnm6XpJgzMUSRFB7YF48CuwdzuDIHenVuoIb1PLuJ6F+uErZsuDkU+EiExkChf6473XcawBrSfDSnXl+/YG4g== dependencies: - "@jest/types" "^29.3.1" + "@jest/types" "^29.4.2" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.4.2.tgz#3b3f8c4910ab9a3442d2512e2175df6b3f77b915" + integrity sha512-tto7YKGPJyFbhcKhIDFq8B5od+eVWD/ySZ9Tvcp/NGCvYA4RQbuzhbwYWtIjMT5W5zA2W0eBJwu4HVw34d5G6Q== + dependencies: + "@jest/types" "^29.4.2" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^29.2.0" + jest-get-type "^29.4.2" leven "^3.1.0" - pretty-format "^29.3.1" + pretty-format "^29.4.2" -jest-watcher@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.3.1.tgz#3341547e14fe3c0f79f9c3a4c62dbc3fc977fd4a" - integrity sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg== +jest-watcher@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.4.2.tgz#09c0f4c9a9c7c0807fcefb1445b821c6f7953b7c" + integrity sha512-onddLujSoGiMJt+tKutehIidABa175i/Ays+QvKxCqBwp7fvxP3ZhKsrIdOodt71dKxqk4sc0LN41mWLGIK44w== dependencies: - "@jest/test-result" "^29.3.1" - "@jest/types" "^29.3.1" + "@jest/test-result" "^29.4.2" + "@jest/types" "^29.4.2" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.3.1" + jest-util "^29.4.2" string-length "^4.0.1" jest-worker@^28.1.3: @@ -3728,25 +3829,25 @@ jest-worker@^28.1.3: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.3.1.tgz#e9462161017a9bb176380d721cab022661da3d6b" - integrity sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw== +jest-worker@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.4.2.tgz#d9b2c3bafc69311d84d94e7fb45677fc8976296f" + integrity sha512-VIuZA2hZmFyRbchsUCHEehoSf2HEl0YVF8SDJqtPnKorAaBuh42V8QsLnde0XP5F6TyCynGPEGgBOn3Fc+wZGw== dependencies: "@types/node" "*" - jest-util "^29.3.1" + jest-util "^29.4.2" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.3.1.tgz#c130c0d551ae6b5459b8963747fed392ddbde122" - integrity sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA== +jest@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.4.2.tgz#4c2127d03a71dc187f386156ef155dbf323fb7be" + integrity sha512-+5hLd260vNIHu+7ZgMIooSpKl7Jp5pHKb51e73AJU3owd5dEo/RfVwHbA/na3C/eozrt3hJOLGf96c7EWwIAzg== dependencies: - "@jest/core" "^29.3.1" - "@jest/types" "^29.3.1" + "@jest/core" "^29.4.2" + "@jest/types" "^29.4.2" import-local "^3.0.2" - jest-cli "^29.3.1" + jest-cli "^29.4.2" js-sdsl@^4.1.4: version "4.3.0" @@ -4216,11 +4317,16 @@ prepend-file@^2.0.1: dependencies: temp-write "^4.0.0" -prettier@^2.6.2, prettier@^2.8.3: +prettier@^2.6.2: version "2.8.3" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.3.tgz#ab697b1d3dd46fb4626fbe2f543afe0cc98d8632" integrity sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw== +prettier@^2.8.4: + version "2.8.4" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3" + integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw== + pretty-format@^29.0.0, pretty-format@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.3.1.tgz#1841cac822b02b4da8971dacb03e8a871b4722da" @@ -4230,6 +4336,15 @@ pretty-format@^29.0.0, pretty-format@^29.3.1: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.4.2: + version "29.4.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.2.tgz#64bf5ccc0d718c03027d94ac957bdd32b3fb2401" + integrity sha512-qKlHR8yFVCbcEWba0H0TOC8dnLlO4vPlyEjRPw31FZ2Rupy9nLa8ZLbYny8gWEl8CkEhJqAE6IzdNELTBVcBEg== + dependencies: + "@jest/schemas" "^29.4.2" + ansi-styles "^5.0.0" + react-is "^18.0.0" + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -4359,10 +4474,10 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve.exports@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.1.tgz#05cfd5b3edf641571fd46fa608b610dda9ead999" - integrity sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ== +resolve.exports@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.0.tgz#c1a0028c2d166ec2fbf7d0644584927e76e7400e" + integrity sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg== resolve@^1.1.6, resolve@^1.14.2, resolve@^1.20.0: version "1.22.1" @@ -4731,10 +4846,10 @@ type@^2.7.2: resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== -typescript@^4.9.4: - version "4.9.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" - integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== +typescript@^4.9.5: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" @@ -4840,7 +4955,7 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^4.0.1: +write-file-atomic@^4.0.1, write-file-atomic@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== From 9eb7150c5f84097fcfd9f1aff09b4783509ed493 Mon Sep 17 00:00:00 2001 From: brimigs <85972460+brimigs@users.noreply.github.com> Date: Thu, 23 Feb 2023 13:51:56 -0500 Subject: [PATCH 141/218] Add in multisig responsibilities (#116) --- .../osmosis/multisig-responsibilities.md | 93 +++++++++++++++++++ scripts/multisig/osmosis/multisig-setup.md | 43 +++++++++ 2 files changed, 136 insertions(+) create mode 100644 scripts/multisig/osmosis/multisig-responsibilities.md create mode 100644 scripts/multisig/osmosis/multisig-setup.md diff --git a/scripts/multisig/osmosis/multisig-responsibilities.md b/scripts/multisig/osmosis/multisig-responsibilities.md new file mode 100644 index 000000000..3b4124968 --- /dev/null +++ b/scripts/multisig/osmosis/multisig-responsibilities.md @@ -0,0 +1,93 @@ +# Osmosis Multisig Overview + +The multisig on Osmosis is set to have 5 multisig holders with a threshold of 3, meaning that 3 signatures are needed for any transaction to pass. + +The Osmosis multisig being used for this project is `osmo14w4x949nwcrqgfe53pxs3k7x53p0gvlrq34l5n` + +## Verifying Contracts + +### The multisig holders are responsible for verifying the contracts at the time of deployment and at the time of any contract migration. + +1. Get the wasm binary executable on your local machine. + + For account-nft, credit-manager, swapper, and zapper contracts: + + ```bash + git clone https://github.com/mars-protocol/rover.git + git checkout + cargo make rust-optimizer + ``` + + Note: Intel/AMD 64-bit processor is required. While there is experimental ARM support for CosmWasm/rust-optimizer, it's discouraged to use in production and the wasm bytecode will not match up to an Intel compiled wasm file. + +2. Download the wasm from the chain. + + ```bash + osmosisd query wasm code --node download.wasm + ``` + +3. Verify that the diff is empty between them. If any value is returned, then the wasm files differ. + + ```bash + diff artifacts/.wasm download.wasm + ``` + +## Query contract configs + +### Multisig holders are responsible for verifying all configs are set accurately at the time of deployment at the time of any contract migration. + +- Account NFT Contract Config: + + ```bash + QUERY='{"config": {}}' + osmosisd query wasm contract-state smart [contract_address] "$QUERY" --output json --node=[node_url] + ``` + + ```bash + QUERY='{"minter": {}}' + osmosisd query wasm contract-state smart [contract_address] "$QUERY" --output json --node=[node_url] + ``` + +- Account Credit Manager Contract Config: + + ```bash + QUERY='{"config": {}}' + osmosisd query wasm contract-state smart [contract_address] "$QUERY" --output json --node=[node_url] + ``` + + ```bash + QUERY='{"vaults_info": {}}' + osmosisd query wasm contract-state smart [contract_address] "$QUERY" --output json --node=[node_url] + ``` + + ```bash + QUERY='{"allowed_coins": {}}' + osmosisd query wasm contract-state smart [contract_address] "$QUERY" --output json --node=[node_url] + ``` + +- Account Swapper Contract Config: + + ```bash + QUERY='{"route": { "denom_in": "uosmo", "denom_out": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2" }}' + osmosisd query wasm contract-state smart [contract_address] "$QUERY" --output json --node=[node_url] + ``` + + ```bash + QUERY='{"route": { "denom_in": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", "denom_out": "uosmo" }}' + osmosisd query wasm contract-state smart [contract_address] "$QUERY" --output json --node=[node_url] + ``` + + ```bash + QUERY='{"route": { "denom_in": "uosmo", "denom_out": "ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858" }}' + osmosisd query wasm contract-state smart [contract_address] "$QUERY" --output json --node=[node_url] + ``` + + ```bash + QUERY='{"route": { "denom_in": "ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858", "denom_out": "uosmo" }}' + osmosisd query wasm contract-state smart [contract_address] "$QUERY" --output json --node=[node_url] + ``` + + ```bash + QUERY='{"owner": {}}' + osmosisd query wasm contract-state smart [contract_address] "$QUERY" --output json --node=[node_url] + ``` diff --git a/scripts/multisig/osmosis/multisig-setup.md b/scripts/multisig/osmosis/multisig-setup.md new file mode 100644 index 000000000..99b247b2a --- /dev/null +++ b/scripts/multisig/osmosis/multisig-setup.md @@ -0,0 +1,43 @@ +# Osmosis Multisig Overview + +The multisig on Osmosis is set to have 5 multisig holders with a threshold of 3, meaning that 3 signatures are needed for any transaction to pass. + +The Osmosis multisig being used for this project is `osmo14w4x949nwcrqgfe53pxs3k7x53p0gvlrq34l5n` + +## Set up Osmosisd + +Osmosisd is the daemon for the osmosis blockchain. To install, follow [this documentation](https://docs.osmosis.zone/osmosis-core/osmosisd/). + +## Set up the multisig on your local network + +_Steps 2-4 must be completed by ALL multisig holders to properly set up their local keyring in their machine._ + +1. Generate the public keys of each of the 5 multisig holder's wallets. In order to generate a public key, the wallet must be active and have made at least one transaction on the specified network to return a public key. + + ```bash + osmosisd query account [address] --node=[node_URL] + ``` + +2. Add each public key to the keys list in your local network. + + ```bash + osmosisd keys add [name] --pubkey=[pubkey] + ``` + + Note: The pubkey must be entered with the same syntax as shown in Step 1. + +3. Generate the multisig. + + ```bash + osmosisd keys add osmosis_multisig \ + --multisig=[name1],[name2],[name3],[name4],[name5] \ + --multisig-threshold=3 + ``` + +4. Assert that it was completed correctly. + + ```bash + osmosisd keys show osmosis_multisig + ``` + +5. Update the config with the new mutlisig address in `rover/scripts/deploy/osmosis/config`, which will set the owner and admin of the smart contracts to the multisig upon deployment. From e3d5c80c874e38b11a39d0252fe191044bdd9615 Mon Sep 17 00:00:00 2001 From: brimigs <85972460+brimigs@users.noreply.github.com> Date: Thu, 23 Feb 2023 13:52:31 -0500 Subject: [PATCH 142/218] deploy update to match v2-fields-of-mars (#118) --- README.md | 22 +- .../osmo-test-4-testnet-deployer-owner.json | 7 + scripts/deploy/base/deployer.ts | 104 +- scripts/deploy/base/index.ts | 24 +- scripts/deploy/base/setupDeployer.ts | 4 +- scripts/deploy/base/storage.ts | 12 +- scripts/deploy/osmosis/index.ts | 10 - scripts/deploy/osmosis/mainnnet.ts | 63 + .../osmosis/{config.ts => testnet-config.ts} | 122 +- scripts/deploy/osmosis/testnet-deployer.ts | 9 + scripts/deploy/osmosis/testnet-multisig.ts | 15 + scripts/package.json | 4 +- scripts/types/config.ts | 11 +- scripts/yarn.lock | 1299 ++++++++--------- 14 files changed, 878 insertions(+), 828 deletions(-) create mode 100644 scripts/deploy/addresses/osmo-test-4-testnet-deployer-owner.json delete mode 100644 scripts/deploy/osmosis/index.ts create mode 100644 scripts/deploy/osmosis/mainnnet.ts rename scripts/deploy/osmosis/{config.ts => testnet-config.ts} (57%) create mode 100644 scripts/deploy/osmosis/testnet-deployer.ts create mode 100644 scripts/deploy/osmosis/testnet-multisig.ts diff --git a/README.md b/README.md index c3a7e47f4..eaae50c14 100644 --- a/README.md +++ b/README.md @@ -138,17 +138,31 @@ For Rust cw-multi tests + osmosis-testing suite (requires mars_swapper_osmosis.w cargo test ``` -For Typescript end-to-end testnet deployment & tests against that deployment: +For Typescript testnet deployment with the deployer address being the owner and admin of the contracts & end-to-end tests: ```shell cd scripts yarn install -yarn deploy:osmosis +yarn deploy:osmosis:testnet-deployer ``` -### Deployment +For Typescript testnet deployment with the multisig address being the owner and admin of the contracts & end-to-end tests: +```shell +cd scripts +yarn install +yarn deploy:osmosis:testnet-multisig +``` -Addresses published in [/scripts/deploy/addresses](https://github.com/mars-protocol/rover/tree/master/scripts/deploy/addresses) +For mainnet: +```shell +cd scripts +yarn install +yarn deploy:osmosis:mainnet +``` + +### Deployment +- osmosis-1 (TBD) +- [osmo-test-4 with deployer owner](./scripts/deploy/addresses/osmo-test-4-testnet-deployer-owner.json) ## License diff --git a/scripts/deploy/addresses/osmo-test-4-testnet-deployer-owner.json b/scripts/deploy/addresses/osmo-test-4-testnet-deployer-owner.json new file mode 100644 index 000000000..164294b7e --- /dev/null +++ b/scripts/deploy/addresses/osmo-test-4-testnet-deployer-owner.json @@ -0,0 +1,7 @@ +{ + "mockVault": "osmo1h49lznwt2v3396pzrc8q3jh39u6qjhkazx5p5epe38t3c0umgdvqp08e90", + "swapper": "osmo175ypxl0c67yrcex0764we29tae7h8keyvafydmtqj065de9smynsdpqn5m", + "zapper": "osmo1wjdvc6mxda8l04pwnr7nwtqz6fmg6gahrg4tuq9c8x6gwtkhranslzyhn5", + "creditManager": "osmo1q7khj532p2fyvmnu83tul6xddl6yl0d0kmrzdz2pfel3lkxem92sw6zqrl", + "accountNft": "osmo1jsgdpkpawyxlxqqjdqs0kk2p27gc4n29jwcsqzzekevycs7xryrq8h7al9" +} diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 88be797e5..03f426ae3 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -5,11 +5,17 @@ import { ARTIFACTS_PATH, Storage } from './storage' import fs from 'fs' import { InstantiateMsgs } from '../../types/instantiateMsgs' import { InstantiateMsg as NftInstantiateMsg } from '../../types/generated/mars-account-nft/MarsAccountNft.types' -import { InstantiateMsg as HealthInstantiateMsg } from '../../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' import { InstantiateMsg as VaultInstantiateMsg } from '../../types/generated/mars-mock-vault/MarsMockVault.types' -import { InstantiateMsg as SwapperInstantiateMsg } from '../../types/generated/mars-swapper-base/MarsSwapperBase.types' +import { InstantiateMsg as HealthInstantiateMsg } from '../../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' +import { + ExecuteMsg as SwapperExecute, + InstantiateMsg as SwapperInstantiateMsg, +} from '../../types/generated/mars-swapper-base/MarsSwapperBase.types' import { InstantiateMsg as ZapperInstantiateMsg } from '../../types/generated/mars-zapper-base/MarsZapperBase.types' -import { InstantiateMsg as RoverInstantiateMsg } from '../../types/generated/mars-credit-manager/MarsCreditManager.types' +import { + ExecuteMsg as CreditManagerExecute, + InstantiateMsg as RoverInstantiateMsg, +} from '../../types/generated/mars-credit-manager/MarsCreditManager.types' import { Rover } from './rover' import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing' import { getAddress, getWallet, setupClient } from './setupDeployer' @@ -23,10 +29,13 @@ import { MarsSwapperBaseQueryClient, } from '../../types/generated/mars-swapper-base/MarsSwapperBase.client' import { MarsAccountNftClient } from '../../types/generated/mars-account-nft/MarsAccountNft.client' -import { MarsCreditManagerClient } from '../../types/generated/mars-credit-manager/MarsCreditManager.client' +import { + MarsCreditManagerClient, + MarsCreditManagerQueryClient, +} from '../../types/generated/mars-credit-manager/MarsCreditManager.client' import { InitOrUpdateAssetParams } from '../../types/generated/mars-mock-red-bank/MarsMockRedBank.types' -import { PriceSource } from '../../types/priceSource' import { kebabCase } from 'lodash' +import { MarsMockOracleQueryClient } from '../../types/generated/mars-mock-oracle/MarsMockOracle.client' import { MarsRoverHealthTypesClient } from '../../types/generated/mars-rover-health-types/MarsRoverHealthTypes.client' export class Deployer { @@ -63,14 +72,13 @@ export class Deployer { msg, `mars-${kebabCase(name)}`, 'auto', - { admin: this.deployerAddr }, + { admin: this.config.multisigAddr ? this.config.multisigAddr : this.deployerAddr }, ) this.storage.addresses[name] = contractAddress printGreen( `${this.config.chain.id} :: ${name} Contract Address : ${this.storage.addresses[name]}`, ) } - async instantiateHealthContract() { const msg: HealthInstantiateMsg = { owner: this.deployerAddr, @@ -93,7 +101,6 @@ export class Deployer { } this.storage.actions.healthContractConfigUpdate = true } - async instantiateNftContract() { const msg: NftInstantiateMsg = { max_value_for_burn: this.config.maxValueForBurn, @@ -170,7 +177,7 @@ export class Deployer { async instantiateCreditManager() { const msg: RoverInstantiateMsg = { max_unlocking_positions: this.config.maxUnlockingPositions, - allowed_coins: this.config.allowedCoins.map((c) => c.denom), + allowed_coins: this.config.allowedCoins, vault_configs: this.config.vaults.map((v) => ({ config: v.config, vault: v.vault })), oracle: this.config.oracle.addr, owner: this.deployerAddr, @@ -233,10 +240,10 @@ export class Deployer { return this.getRoverClient(address, client, testActions) } - async saveDeploymentAddrsToFile() { + async saveDeploymentAddrsToFile(label: string) { const addressesDir = resolve(join(__dirname, '../../../deploy/addresses')) await writeFile( - `${addressesDir}/${this.config.chain.id}.json`, + `${addressesDir}/${this.config.chain.id}-${label}.json`, JSON.stringify(this.storage.addresses), ) } @@ -254,9 +261,9 @@ export class Deployer { const client = await setupClient(this.config, wallet) const addr = await getAddress(wallet) - for (const denom of this.config.allowedCoins + for (const denom of this.config.testActions?.allowedCoinsConfig .filter((c) => c.grantCreditLine) - .map((c) => c.denom)) { + .map((c) => c.denom) ?? []) { const msg = { update_uncollateralized_loan_limit: { user: this.storage.addresses.creditManager, @@ -279,17 +286,22 @@ export class Deployer { return } - for (const coin of this.config.allowedCoins) { - const msg = { - set_price_source: { - denom: coin.denom, - price_source: coin.priceSource as PriceSource, - }, + for (const coin of this.config.testActions?.allowedCoinsConfig ?? []) { + const oQuery = new MarsMockOracleQueryClient(this.cwClient, this.config.oracle.addr) + try { + await oQuery.price({ denom: coin.denom }) + printGray(`Price source already set for ${coin.denom}`) + } catch (e) { + const msg = { + set_price_source: { + denom: coin.denom, + price_source: coin.priceSource, + }, + } + printBlue(`Setting price source for ${coin.denom}: ${JSON.stringify(coin.priceSource)}`) + const { client, addr } = await this.getOutpostsDeployer() + await client.execute(addr, this.config.oracle.addr, msg, 'auto') } - - printBlue(`Setting price source for ${coin.denom}: ${JSON.stringify(coin.priceSource)}`) - const { client, addr } = await this.getOutpostsDeployer() - await client.execute(addr, this.config.oracle.addr, msg, 'auto') } this.storage.actions.oraclePricesSet = true } @@ -302,7 +314,7 @@ export class Deployer { const { client, addr } = await this.getOutpostsDeployer() - for (const denom of this.config.allowedCoins.map((c) => c.denom)) { + for (const denom of this.config.testActions?.allowedCoinsConfig.map((c) => c.denom) ?? []) { try { await client.queryContractSmart(this.config.redBank.addr, { market: { @@ -343,6 +355,50 @@ export class Deployer { this.storage.actions.redBankMarketsSet = true } + async updateCreditManagerOwner() { + if (!this.config.multisigAddr) throw new Error('No multisig addresses to transfer ownership to') + + const msg: CreditManagerExecute = { + update_owner: { + propose_new_owner: { + proposed: this.config.multisigAddr, + }, + }, + } + await this.cwClient.execute( + this.deployerAddr, + this.storage.addresses.creditManager!, + msg, + 'auto', + ) + printGreen('Owner updated to Multisig for Credit Manager Contract') + + const cmQuery = new MarsCreditManagerQueryClient( + this.cwClient, + this.storage.addresses.creditManager!, + ) + const creditManagerConfig = await cmQuery.config() + assert.equal(creditManagerConfig.proposed_new_owner, this.config.multisigAddr) + } + + async updateSwapperOwner() { + if (!this.config.multisigAddr) throw new Error('No multisig addresses to transfer ownership to') + + const msg: SwapperExecute = { + update_owner: { + propose_new_owner: { + proposed: this.config.multisigAddr, + }, + }, + } + await this.cwClient.execute(this.deployerAddr, this.storage.addresses.swapper!, msg, 'auto') + printGreen('Owner updated to Multisig for Swapper Contract') + + const swQuery = new MarsSwapperBaseQueryClient(this.cwClient, this.storage.addresses.swapper!) + const swapperOwner = await swQuery.owner() + assert.equal(swapperOwner.proposed, this.config.multisigAddr) + } + private async getOutpostsDeployer() { const wallet = await getWallet( this.config.testActions!.outpostsDeployerMnemonic, diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index b3ec42ae0..af0ff711e 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -5,23 +5,17 @@ import { wasmFile } from '../../utils/environment' export interface TaskRunnerProps { config: DeploymentConfig - swapperContractName: string - zapperContractName: string + label: string } -export const taskRunner = async ({ - config, - swapperContractName, - zapperContractName, -}: TaskRunnerProps) => { - const deployer = await setupDeployer(config) +export const taskRunner = async ({ config, label }: TaskRunnerProps) => { + const deployer = await setupDeployer(config, label) try { // Upload contracts await deployer.upload('accountNft', wasmFile('mars_account_nft')) await deployer.upload('mockVault', wasmFile('mars_mock_vault')) - await deployer.upload('swapper', wasmFile(swapperContractName)) - await deployer.upload('zapper', wasmFile(zapperContractName)) - await deployer.upload('healthContract', wasmFile('mars_rover_health')) + await deployer.upload('swapper', wasmFile(config.swapperContractName)) + await deployer.upload('zapper', wasmFile(config.zapperContractName)) await deployer.upload('creditManager', wasmFile('mars_credit_manager')) // Instantiate contracts @@ -33,7 +27,7 @@ export const taskRunner = async ({ await deployer.instantiateNftContract() await deployer.setCmOnHealthContract() await deployer.transferNftContractOwnership() - await deployer.saveDeploymentAddrsToFile() + await deployer.saveDeploymentAddrsToFile(label) // Test basic user flows if (config.testActions) { @@ -63,6 +57,12 @@ export const taskRunner = async ({ await rover.refundAllBalances() } + // If multisig is set, transfer ownership from deployer to multisig + if (config.multisigAddr) { + await deployer.updateCreditManagerOwner() + await deployer.updateSwapperOwner() + } + printYellow('COMPLETE') } catch (e) { printRed(e) diff --git a/scripts/deploy/base/setupDeployer.ts b/scripts/deploy/base/setupDeployer.ts index 83cef77f6..6eda32256 100644 --- a/scripts/deploy/base/setupDeployer.ts +++ b/scripts/deploy/base/setupDeployer.ts @@ -28,13 +28,13 @@ export const setupClient = async (config: DeploymentConfig, wallet: DirectSecp25 ) } -export const setupDeployer = async (config: DeploymentConfig) => { +export const setupDeployer = async (config: DeploymentConfig, label: string) => { const wallet = await getWallet(config.deployerMnemonic, config.chain.prefix) const client = await setupClient(config, wallet) const addr = await getAddress(wallet) const balance = await client.getBalance(addr, config.chain.baseDenom) printGray(`Deployer addr: ${addr}, balance: ${parseInt(balance.amount) / 1e6} ${balance.denom}`) - const storage = await Storage.load(config.chain.id) + const storage = await Storage.load(config.chain.id, label) return new Deployer(config, client, addr, storage) } diff --git a/scripts/deploy/base/storage.ts b/scripts/deploy/base/storage.ts index fb0c52c3f..c380190c9 100644 --- a/scripts/deploy/base/storage.ts +++ b/scripts/deploy/base/storage.ts @@ -9,25 +9,25 @@ export class Storage implements StorageItems { public codeIds: StorageItems['codeIds'] public actions: StorageItems['actions'] - constructor(private chainId: string, items: StorageItems) { + constructor(private chainId: string, private label: string, items: StorageItems) { this.addresses = items.addresses this.codeIds = items.codeIds this.actions = items.actions } - static async load(chainId: string): Promise { + static async load(chainId: string, label: string): Promise { try { - const data = await readFile(path.join(ARTIFACTS_PATH, `${chainId}.json`), 'utf8') + const data = await readFile(path.join(ARTIFACTS_PATH, `${chainId}-${label}.json`), 'utf8') const items = JSON.parse(data) as StorageItems - return new this(chainId, items) + return new this(chainId, label, items) } catch (e) { - return new this(chainId, { addresses: {}, codeIds: {}, actions: {} }) + return new this(chainId, label, { addresses: {}, codeIds: {}, actions: {} }) } } async save() { await writeFile( - path.join(ARTIFACTS_PATH, `${this.chainId}.json`), + path.join(ARTIFACTS_PATH, `${this.chainId}-${this.label}.json`), JSON.stringify(this, null, 2), ) } diff --git a/scripts/deploy/osmosis/index.ts b/scripts/deploy/osmosis/index.ts deleted file mode 100644 index 62854fae0..000000000 --- a/scripts/deploy/osmosis/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { taskRunner } from '../base' -import { osmosisTestnetConfig } from './config' - -void (async function () { - await taskRunner({ - config: osmosisTestnetConfig, - swapperContractName: 'mars_swapper_osmosis', - zapperContractName: 'mars_zapper_osmosis', - }) -})() diff --git a/scripts/deploy/osmosis/mainnnet.ts b/scripts/deploy/osmosis/mainnnet.ts new file mode 100644 index 000000000..8426dcdb2 --- /dev/null +++ b/scripts/deploy/osmosis/mainnnet.ts @@ -0,0 +1,63 @@ +import { taskRunner } from '../base' +import { DeploymentConfig } from '../../types/config' + +const uosmo = 'uosmo' +const uatom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2' +const axlUSDC = 'ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858' +const gammPool1 = 'gamm/pool/1' +const gammPool678 = 'gamm/pool/678' + +export const osmosisMainnetConfig: DeploymentConfig = { + multisigAddr: 'osmo14w4x949nwcrqgfe53pxs3k7x53p0gvlrq34l5n', + allowedCoins: [uosmo, uatom, axlUSDC, gammPool1, gammPool678], + chain: { + baseDenom: uosmo, + defaultGasPrice: 0.1, + id: 'osmosis-1', + prefix: 'osmo', + rpcEndpoint: 'https://rpc.osmosis.zone', + }, + deployerMnemonic: 'TO BE INSERTED AT TIME OF DEPLOYMENT', + maxCloseFactor: '0.5', + maxUnlockingPositions: '1', + maxValueForBurn: '10000', + // oracle and redbank contract addresses can be found: https://github.com/mars-protocol/red-bank/blob/master/README.md#osmosis-1 + oracle: { addr: 'osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g' }, + redBank: { addr: 'osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg' }, + swapRoutes: [ + { denomIn: uosmo, denomOut: uatom, route: [{ token_out_denom: uatom, pool_id: '1' }] }, + { denomIn: uatom, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '1' }] }, + { denomIn: uosmo, denomOut: axlUSDC, route: [{ token_out_denom: axlUSDC, pool_id: '678' }] }, + { denomIn: axlUSDC, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '678' }] }, + ], + // Latest from: https://stats.apollo.farm/api/vaults/v1/all + vaults: [ + { + vault: { address: 'osmo1g3kmqpp8608szfp0pdag3r6z85npph7wmccat8lgl3mp407kv73qlj7qwp' }, + config: { + deposit_cap: { denom: axlUSDC, amount: '2000000000000' }, // $2M + max_ltv: '0.63', + liquidation_threshold: '0.65', + whitelisted: true, + }, + }, + { + vault: { address: 'osmo1jfmwayj8jqp9tfy4v4eks5c2jpnqdumn8x8xvfllng0wfes770qqp7jl4j' }, + config: { + deposit_cap: { denom: axlUSDC, amount: '750000000000' }, // $750k + max_ltv: '0.65', + liquidation_threshold: '0.66', + whitelisted: true, + }, + }, + ], + swapperContractName: 'mars_swapper_osmosis', + zapperContractName: 'mars_zapper_osmosis', +} + +void (async function () { + await taskRunner({ + config: osmosisMainnetConfig, + label: 'mainnet', + }) +})() diff --git a/scripts/deploy/osmosis/config.ts b/scripts/deploy/osmosis/testnet-config.ts similarity index 57% rename from scripts/deploy/osmosis/config.ts rename to scripts/deploy/osmosis/testnet-config.ts index 2aea212c1..0b151254b 100644 --- a/scripts/deploy/osmosis/config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -7,37 +7,32 @@ const ujuno = 'ibc/46B44899322F3CD854D2D46DEEF881958467CDD4B3B10086DA49296BBED94 const gammPool1 = 'gamm/pool/1' const gammPool497 = 'gamm/pool/497' -const vaultOsmoAtom1 = 'osmo1v40lnedgvake8p7f49gvqu0q3vc9sx3qpc0jqtyfdyw25d4vg8us38an37' -const vaultOsmoAtom7 = 'osmo108q2krqr0y9g0rtesenvsw68sap2xefelwwjs0wedyvdl0cmrntqvllfjk' -const vaultOsmoAtom14 = 'osmo1eht92w5dr0vx8dzl6dn9770yq0ycln50zfhzvz8uc6928mp8vvgqwcram9' -const vaultJunoOsmo1 = 'osmo1g5hryv0gp9dzlchkp3yxk8fmcf5asjun6cxkvyffetqzkwmvy75qfmeq3f' -const vaultJunoOsmo7 = 'osmo1jtuvr47taunfdhwrkns0cufwa3qlsz66qwwa9vvn4cc5eltzrtxs4zkaus' -const vaultJunoOsmo14 = 'osmo1rclt7lsfp0c89ydf9umuhwlg28maw6z87jak3ly7u2lefnyzdz2s8gsepe' +const vaultOsmoAtom1 = 'osmo1zktjv92f76epswjvyxzzt3yyskpw7k6jsyu0kmq4zzc5fphrjumqlahctp' +const vaultOsmoAtom7 = 'osmo167j3yttwzcm3785tzk4jse2qdkppcy2xxrn5u6srqv7s93wnq6yqw8zhg5' +const vaultOsmoAtom14 = 'osmo1tp2m6g39h8mvhnu3plqjyen5s63023gj8w873l8wvly0cd77l6hsaa73wt' +const atomOsmoConfig = { + config: { + deposit_cap: { denom: uatom, amount: '1000000000' }, // 1000 atom + max_ltv: '0.63', + liquidation_threshold: '0.65', + whitelisted: true, + }, +} + +const vaultJunoOsmo1 = 'osmo1r6h0pafu3wq0kf6yv09qhc8qvuku2d6fua0rpwwv46h7hd8u586scxspjf' +const vaultJunoOsmo7 = 'osmo1gr5epxn67q6202l3hy0mcnu7qc039v22pa6x2tsk23zwg235n9jsq6pmes' +const vaultJunoOsmo14 = 'osmo1d6knwkelyr9eklewnn9htkess4ttpxpf2cze9ec0xfw7e3fj0ggssqzfpp' +const junoOsmoConfig = { + config: { + deposit_cap: { denom: uatom, amount: '500000000' }, // 500 atom + max_ltv: '0.65', + liquidation_threshold: '0.66', + whitelisted: true, + }, +} export const osmosisTestnetConfig: DeploymentConfig = { - allowedCoins: [ - { denom: uosmo, priceSource: { fixed: { price: '1' } }, grantCreditLine: true }, - { - denom: uatom, - priceSource: { arithmetic_twap: { pool_id: 1, window_size: 1800 } }, - grantCreditLine: true, - }, - { - denom: ujuno, - priceSource: { arithmetic_twap: { pool_id: 497, window_size: 1800 } }, - grantCreditLine: true, - }, - { - denom: gammPool1, - priceSource: { xyk_liquidity_token: { pool_id: 1 } }, - grantCreditLine: false, - }, - { - denom: gammPool497, - priceSource: { xyk_liquidity_token: { pool_id: 497 } }, - grantCreditLine: false, - }, - ], + allowedCoins: [uosmo, uatom, ujuno, gammPool1, gammPool497], chain: { baseDenom: uosmo, defaultGasPrice: 0.1, @@ -63,60 +58,55 @@ export const osmosisTestnetConfig: DeploymentConfig = { vaults: [ { vault: { address: vaultOsmoAtom1 }, - config: { - deposit_cap: { denom: 'uosmo', amount: '1000000000' }, // 1000 osmo - liquidation_threshold: '0.65', - max_ltv: '0.63', - whitelisted: true, - }, + ...atomOsmoConfig, }, { vault: { address: vaultOsmoAtom7 }, - config: { - deposit_cap: { denom: 'uosmo', amount: '1000000000' }, // 1000 osmo - liquidation_threshold: '0.65', - max_ltv: '0.63', - whitelisted: true, - }, + ...atomOsmoConfig, }, { vault: { address: vaultOsmoAtom14 }, - config: { - deposit_cap: { denom: 'uosmo', amount: '1000000000' }, // 1000 osmo - liquidation_threshold: '0.65', - max_ltv: '0.63', - whitelisted: true, - }, + ...atomOsmoConfig, }, { vault: { address: vaultJunoOsmo1 }, - config: { - deposit_cap: { denom: 'uosmo', amount: '500000000' }, // 500 osmo - liquidation_threshold: '0.441', - max_ltv: '0.4115', - whitelisted: true, - }, + ...junoOsmoConfig, }, { vault: { address: vaultJunoOsmo7 }, - config: { - deposit_cap: { denom: 'uosmo', amount: '500000000' }, // 500 osmo - liquidation_threshold: '0.441', - max_ltv: '0.4115', - whitelisted: true, - }, + ...junoOsmoConfig, }, { vault: { address: vaultJunoOsmo14 }, - config: { - deposit_cap: { denom: 'uosmo', amount: '500000000' }, // 500 osmo - liquidation_threshold: '0.441', - max_ltv: '0.4115', - whitelisted: true, - }, + ...junoOsmoConfig, }, ], + swapperContractName: 'mars_swapper_osmosis', + zapperContractName: 'mars_zapper_osmosis', testActions: { + allowedCoinsConfig: [ + { denom: uosmo, priceSource: { fixed: { price: '1' } }, grantCreditLine: true }, + { + denom: uatom, + priceSource: { geometric_twap: { pool_id: 1, window_size: 1800 } }, + grantCreditLine: true, + }, + { + denom: ujuno, + priceSource: { geometric_twap: { pool_id: 497, window_size: 1800 } }, + grantCreditLine: true, + }, + { + denom: gammPool1, + priceSource: { xyk_liquidity_token: { pool_id: 1 } }, + grantCreditLine: false, + }, + { + denom: gammPool497, + priceSource: { xyk_liquidity_token: { pool_id: 497 } }, + grantCreditLine: false, + }, + ], vault: { depositAmount: '1000000', withdrawAmount: '1000000', @@ -141,7 +131,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { depositAmount: '100', lendAmount: '10', secondaryDenom: uatom, - startingAmountForTestUser: '4000000', // If test actions run out of gas, increment this + startingAmountForTestUser: '2500000', swap: { slippage: '0.4', amount: '40', diff --git a/scripts/deploy/osmosis/testnet-deployer.ts b/scripts/deploy/osmosis/testnet-deployer.ts new file mode 100644 index 000000000..edc58daec --- /dev/null +++ b/scripts/deploy/osmosis/testnet-deployer.ts @@ -0,0 +1,9 @@ +import { taskRunner } from '../base' +import { osmosisTestnetConfig } from './testnet-config' + +void (async function () { + await taskRunner({ + config: osmosisTestnetConfig, + label: 'testnet-deployer-owner', + }) +})() diff --git a/scripts/deploy/osmosis/testnet-multisig.ts b/scripts/deploy/osmosis/testnet-multisig.ts new file mode 100644 index 000000000..bf20f1349 --- /dev/null +++ b/scripts/deploy/osmosis/testnet-multisig.ts @@ -0,0 +1,15 @@ +import { taskRunner } from '../base' +import { osmosisTestnetConfig } from './testnet-config' + +void (async function () { + await taskRunner({ + config: { + ...osmosisTestnetConfig, + testActions: undefined, + oracle: { addr: 'osmo1lcdrm4wpycdlruxv34t6rmvmy9fnehynrkwme00vfyqnzcg0qqxqwdlyzg' }, + redBank: { addr: 'osmo1t5w9qqp0drassayyv23m6sh70kw754xxd78t8tmscljysnnv0avqk87a6f' }, + multisigAddr: 'osmo14w4x949nwcrqgfe53pxs3k7x53p0gvlrq34l5n', + }, + label: 'testnet-multisig', + }) +})() diff --git a/scripts/package.json b/scripts/package.json index 8022cec09..27e9d7d1f 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -3,7 +3,9 @@ "version": "1.0.0", "license": "GPL-3.0-or-later", "scripts": { - "deploy:osmosis": "yarn build && node build/deploy/osmosis", + "deploy:osmosis-testnet": "yarn build && node build/deploy/osmosis/testnet-deployer.js", + "deploy:osmosis-testnet-multisig": "yarn build && node build/deploy/osmosis/testnet-multisig.js", + "deploy:osmosis-mainnet": "yarn build && node build/deploy/osmosis/mainnet.js", "generate-types": "yarn rust-schema && tsc --project codegen-tsconfig.json && rm -rf types/generated && node build/codegen && node build/codegen/insertIgnores.js && yarn format", "rust-schema": "cd ../ && cargo make generate-all-schemas && cd scripts", "compile-wasm": "cd ../ && cargo make rust-optimizer && cd scripts", diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 320037517..fd7eb32eb 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -10,7 +10,10 @@ export enum VaultType { UNLOCKED, } -export type VaultInfo = { lockup: { time: number } | undefined; tokens: VaultInfoResponse } +export interface VaultInfo { + lockup: { time: number } | undefined + tokens: VaultInfoResponse +} export interface DeploymentConfig { chain: { @@ -24,12 +27,15 @@ export interface DeploymentConfig { oracle: { addr: string } redBank: { addr: string } vaults: VaultInstantiateConfig[] - allowedCoins: { denom: string; priceSource: PriceSource; grantCreditLine: boolean }[] + allowedCoins: string[] maxCloseFactor: string maxValueForBurn: string maxUnlockingPositions: string swapRoutes: SwapRoute[] testActions?: TestActions + swapperContractName: string + zapperContractName: string + multisigAddr?: string } export interface SwapRoute { @@ -39,6 +45,7 @@ export interface SwapRoute { } export interface TestActions { + allowedCoinsConfig: { denom: string; priceSource: PriceSource; grantCreditLine: boolean }[] vault: { depositAmount: string withdrawAmount: string diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 2911283b4..c4e9c861a 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.1.0": +"@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== @@ -18,9 +18,9 @@ "@babel/highlight" "^7.18.6" "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5": - version "7.20.10" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.10.tgz#9d92fa81b87542fff50e848ed585b4212c1d34ec" - integrity sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.0.tgz#c241dc454e5b5917e40d37e525e2f4530c399298" + integrity sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g== "@babel/core@7.18.10": version "7.18.10" @@ -44,20 +44,20 @@ semver "^6.3.0" "@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.20.12" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d" - integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.0.tgz#1341aefdcc14ccc7553fcc688dd8986a2daffc13" + integrity sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA== dependencies: - "@ampproject/remapping" "^2.1.0" + "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.7" + "@babel/generator" "^7.21.0" "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-module-transforms" "^7.20.11" - "@babel/helpers" "^7.20.7" - "@babel/parser" "^7.20.7" + "@babel/helper-module-transforms" "^7.21.0" + "@babel/helpers" "^7.21.0" + "@babel/parser" "^7.21.0" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.12" - "@babel/types" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -73,13 +73,14 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/generator@^7.18.10", "@babel/generator@^7.20.7", "@babel/generator@^7.7.2": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.7.tgz#f8ef57c8242665c5929fe2e8d82ba75460187b4a" - integrity sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw== +"@babel/generator@^7.18.10", "@babel/generator@^7.21.0", "@babel/generator@^7.7.2": + version "7.21.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.1.tgz#951cc626057bc0af2c35cd23e9c64d384dea83dd" + integrity sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA== dependencies: - "@babel/types" "^7.20.7" + "@babel/types" "^7.21.0" "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" "@babel/helper-annotate-as-pure@^7.18.6": @@ -108,27 +109,27 @@ lru-cache "^5.1.1" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.5", "@babel/helper-create-class-features-plugin@^7.20.7": - version "7.20.12" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.12.tgz#4349b928e79be05ed2d1643b20b99bb87c503819" - integrity sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz#64f49ecb0020532f19b1d014b03bccaa1ab85fb9" + integrity sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-member-expression-to-functions" "^7.20.7" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-member-expression-to-functions" "^7.21.0" "@babel/helper-optimise-call-expression" "^7.18.6" "@babel/helper-replace-supers" "^7.20.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz#5ea79b59962a09ec2acf20a963a01ab4d076ccca" - integrity sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.0.tgz#53ff78472e5ce10a52664272a239787107603ebb" + integrity sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - regexpu-core "^5.2.1" + regexpu-core "^5.3.1" "@babel/helper-define-polyfill-provider@^0.3.2", "@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" @@ -154,13 +155,13 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" + integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" + "@babel/template" "^7.20.7" + "@babel/types" "^7.21.0" "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" @@ -169,12 +170,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-member-expression-to-functions@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz#a6f26e919582275a93c3aa6594756d71b0bb7f05" - integrity sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw== +"@babel/helper-member-expression-to-functions@^7.20.7", "@babel/helper-member-expression-to-functions@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5" + integrity sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q== dependencies: - "@babel/types" "^7.20.7" + "@babel/types" "^7.21.0" "@babel/helper-module-imports@^7.18.6": version "7.18.6" @@ -183,10 +184,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.20.11": - version "7.20.11" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz#df4c7af713c557938c50ea3ad0117a7944b2f1b0" - integrity sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg== +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.0.tgz#89a8f86ad748870e3d024e470b2e8405e869db67" + integrity sha512-eD/JQ21IG2i1FraJnTMbUarAUkA7G988ofehG5MDCRXaUU91rEBJuCeSoou2Sk1y4RbLYXzqEg1QLwEmRU4qcQ== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" @@ -194,8 +195,8 @@ "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.19.1" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.10" - "@babel/types" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -262,10 +263,10 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== -"@babel/helper-validator-option@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" - integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== +"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" + integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== "@babel/helper-wrap-function@^7.18.9": version "7.20.5" @@ -277,14 +278,14 @@ "@babel/traverse" "^7.20.5" "@babel/types" "^7.20.5" -"@babel/helpers@^7.18.9", "@babel/helpers@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.7.tgz#04502ff0feecc9f20ecfaad120a18f011a8e6dce" - integrity sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA== +"@babel/helpers@^7.18.9", "@babel/helpers@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e" + integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== dependencies: "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.7" - "@babel/types" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" "@babel/highlight@^7.18.6": version "7.18.6" @@ -300,10 +301,10 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.7.tgz#66fe23b3c8569220817d5feb8b9dcdc95bb4f71b" - integrity sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.20.7", "@babel/parser@^7.21.0": + version "7.21.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.1.tgz#a8f81ee2fe872af23faea4b17a08fcc869de7bcc" + integrity sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -340,11 +341,11 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-proposal-class-static-block@^7.18.6": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.20.7.tgz#92592e9029b13b15be0f7ce6a7aedc2879ca45a7" - integrity sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz#77bdd66fb7b605f3a61302d224bdfacf5547977d" + integrity sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.20.7" + "@babel/helper-create-class-features-plugin" "^7.21.0" "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-class-static-block" "^7.14.5" @@ -435,9 +436,9 @@ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-proposal-optional-chaining@^7.18.9", "@babel/plugin-proposal-optional-chaining@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz#49f2b372519ab31728cc14115bb0998b15bfda55" - integrity sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" + integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== dependencies: "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" @@ -452,12 +453,12 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-proposal-private-property-in-object@^7.18.6": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz#309c7668f2263f1c711aa399b5a9a6291eef6135" - integrity sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz#19496bd9883dd83c23c7d7fc45dcd9ad02dfa1dc" + integrity sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.20.5" + "@babel/helper-create-class-features-plugin" "^7.21.0" "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" @@ -633,21 +634,21 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-block-scoping@^7.18.9", "@babel/plugin-transform-block-scoping@^7.20.2": - version "7.20.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.11.tgz#9f5a3424bd112a3f32fe0cf9364fbb155cff262a" - integrity sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz#e737b91037e5186ee16b76e7ae093358a5634f02" + integrity sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ== dependencies: "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-classes@^7.18.9", "@babel/plugin-transform-classes@^7.20.2": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz#f438216f094f6bb31dc266ebfab8ff05aecad073" - integrity sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665" + integrity sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-compilation-targets" "^7.20.7" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" + "@babel/helper-function-name" "^7.21.0" "@babel/helper-optimise-call-expression" "^7.18.6" "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-replace-supers" "^7.20.7" @@ -693,11 +694,11 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-for-of@^7.18.8": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" - integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz#964108c9988de1a60b4be2354a7d7e245f36e86e" + integrity sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-function-name@^7.18.9": version "7.18.9" @@ -857,12 +858,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-typescript@^7.18.6": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.7.tgz#673f49499cd810ae32a1ea5f3f8fab370987e055" - integrity sha512-m3wVKEvf6SoszD8pu4NZz3PvfKRCMgk6D6d0Qi9hNnlM5M6CFS92EgF4EiHVLKbU0r/r7ty1hg7NPZwE7WRbYw== +"@babel/plugin-transform-typescript@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.0.tgz#f0956a153679e3b377ae5b7f0143427151e4c848" + integrity sha512-xo///XTPp3mDzTtrqXoBlK9eiAYW3wv9JXglcn/u1bi60RW11dEUxIgA8cbnDhutS1zacjMRmAwxE0gMklLnZg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.20.7" + "@babel/helper-create-class-features-plugin" "^7.21.0" "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-typescript" "^7.20.0" @@ -1055,18 +1056,23 @@ esutils "^2.0.2" "@babel/preset-typescript@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399" - integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.0.tgz#bcbbca513e8213691fe5d4b23d9251e01f00ebff" + integrity sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-transform-typescript" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-validator-option" "^7.21.0" + "@babel/plugin-transform-typescript" "^7.21.0" + +"@babel/regjsgen@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" + integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.11.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.8.4": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd" - integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" + integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== dependencies: regenerator-runtime "^0.13.11" @@ -1095,19 +1101,19 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.18.10", "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.7.2": - version "7.20.12" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.12.tgz#7f0f787b3a67ca4475adef1f56cb94f6abd4a4b5" - integrity sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ== +"@babel/traverse@^7.18.10", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.7.2": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.0.tgz#0e1807abd5db98e6a19c204b80ed1e3f5bca0edc" + integrity sha512-Xdt2P1H4LKTO8ApPfnO1KmzYMFpp7D/EinoXzLYN/cHcBNrVCAkAtGUcXnHXrl/VGktureU6fkQrHSBE2URfoA== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.7" + "@babel/generator" "^7.21.0" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" + "@babel/helper-function-name" "^7.21.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" + "@babel/parser" "^7.21.0" + "@babel/types" "^7.21.0" debug "^4.1.0" globals "^11.1.0" @@ -1120,10 +1126,10 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f" - integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.0.tgz#1da00d89c2f18b226c9207d96edbeb79316a1819" + integrity sha512-uR7NWq2VNFnDi7EYqiRz2Jv/VQIu38tu64Zy8TX2nQFQ6etJ9V/Rr2msW8BS132mum2rL645qpDrLtAJtVpuow== dependencies: "@babel/helper-string-parser" "^7.19.4" "@babel/helper-validator-identifier" "^7.19.1" @@ -1358,116 +1364,109 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.4.2.tgz#f78374905c2454764152904a344a2d5226b0ef09" - integrity sha512-0I/rEJwMpV9iwi9cDEnT71a5nNGK9lj8Z4+1pRAU2x/thVXCDnaTGrvxyK+cAqZTFVFCiR+hfVrP4l2m+dCmQg== +"@jest/console@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.4.3.tgz#1f25a99f7f860e4c46423b5b1038262466fadde1" + integrity sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A== dependencies: - "@jest/types" "^29.4.2" + "@jest/types" "^29.4.3" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.4.2" - jest-util "^29.4.2" + jest-message-util "^29.4.3" + jest-util "^29.4.3" slash "^3.0.0" -"@jest/core@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.4.2.tgz#6e999b67bdc2df9d96ba9b142465bda71ee472c2" - integrity sha512-KGuoQah0P3vGNlaS/l9/wQENZGNKGoWb+OPxh3gz+YzG7/XExvYu34MzikRndQCdM2S0tzExN4+FL37i6gZmCQ== +"@jest/core@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.4.3.tgz#829dd65bffdb490de5b0f69e97de8e3b5eadd94b" + integrity sha512-56QvBq60fS4SPZCuM7T+7scNrkGIe7Mr6PVIXUpu48ouvRaWOFqRPV91eifvFM0ay2HmfswXiGf97NGUN5KofQ== dependencies: - "@jest/console" "^29.4.2" - "@jest/reporters" "^29.4.2" - "@jest/test-result" "^29.4.2" - "@jest/transform" "^29.4.2" - "@jest/types" "^29.4.2" + "@jest/console" "^29.4.3" + "@jest/reporters" "^29.4.3" + "@jest/test-result" "^29.4.3" + "@jest/transform" "^29.4.3" + "@jest/types" "^29.4.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^29.4.2" - jest-config "^29.4.2" - jest-haste-map "^29.4.2" - jest-message-util "^29.4.2" - jest-regex-util "^29.4.2" - jest-resolve "^29.4.2" - jest-resolve-dependencies "^29.4.2" - jest-runner "^29.4.2" - jest-runtime "^29.4.2" - jest-snapshot "^29.4.2" - jest-util "^29.4.2" - jest-validate "^29.4.2" - jest-watcher "^29.4.2" + jest-changed-files "^29.4.3" + jest-config "^29.4.3" + jest-haste-map "^29.4.3" + jest-message-util "^29.4.3" + jest-regex-util "^29.4.3" + jest-resolve "^29.4.3" + jest-resolve-dependencies "^29.4.3" + jest-runner "^29.4.3" + jest-runtime "^29.4.3" + jest-snapshot "^29.4.3" + jest-util "^29.4.3" + jest-validate "^29.4.3" + jest-watcher "^29.4.3" micromatch "^4.0.4" - pretty-format "^29.4.2" + pretty-format "^29.4.3" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.4.2.tgz#ee92c316ee2fbdf0bcd9d2db0ef42d64fea26b56" - integrity sha512-JKs3VUtse0vQfCaFGJRX1bir9yBdtasxziSyu+pIiEllAQOe4oQhdCYIf3+Lx+nGglFktSKToBnRJfD5QKp+NQ== +"@jest/environment@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.4.3.tgz#9fe2f3169c3b33815dc4bd3960a064a83eba6548" + integrity sha512-dq5S6408IxIa+lr54zeqce+QgI+CJT4nmmA+1yzFgtcsGK8c/EyiUb9XQOgz3BMKrRDfKseeOaxj2eO8LlD3lA== dependencies: - "@jest/fake-timers" "^29.4.2" - "@jest/types" "^29.4.2" + "@jest/fake-timers" "^29.4.3" + "@jest/types" "^29.4.3" "@types/node" "*" - jest-mock "^29.4.2" + jest-mock "^29.4.3" -"@jest/expect-utils@^29.3.1": - version "29.3.1" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.3.1.tgz#531f737039e9b9e27c42449798acb5bba01935b6" - integrity sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g== +"@jest/expect-utils@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.4.3.tgz#95ce4df62952f071bcd618225ac7c47eaa81431e" + integrity sha512-/6JWbkxHOP8EoS8jeeTd9dTfc9Uawi+43oLKHfp6zzux3U2hqOOVnV3ai4RpDYHOccL6g+5nrxpoc8DmJxtXVQ== dependencies: - jest-get-type "^29.2.0" + jest-get-type "^29.4.3" -"@jest/expect-utils@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.4.2.tgz#cd0065dfdd8e8a182aa350cc121db97b5eed7b3f" - integrity sha512-Dd3ilDJpBnqa0GiPN7QrudVs0cczMMHtehSo2CSTjm3zdHx0RcpmhFNVEltuEFeqfLIyWKFI224FsMSQ/nsJQA== +"@jest/expect@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.4.3.tgz#d31a28492e45a6bcd0f204a81f783fe717045c6e" + integrity sha512-iktRU/YsxEtumI9zsPctYUk7ptpC+AVLLk1Ax3AsA4g1C+8OOnKDkIQBDHtD5hA/+VtgMd5AWI5gNlcAlt2vxQ== dependencies: - jest-get-type "^29.4.2" + expect "^29.4.3" + jest-snapshot "^29.4.3" -"@jest/expect@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.4.2.tgz#2d4a6a41b29380957c5094de19259f87f194578b" - integrity sha512-NUAeZVApzyaeLjfWIV/64zXjA2SS+NuUPHpAlO7IwVMGd5Vf9szTl9KEDlxY3B4liwLO31os88tYNHl6cpjtKQ== +"@jest/fake-timers@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.4.3.tgz#31e982638c60fa657d310d4b9d24e023064027b0" + integrity sha512-4Hote2MGcCTWSD2gwl0dwbCpBRHhE6olYEuTj8FMowdg3oQWNKr2YuxenPQYZ7+PfqPY1k98wKDU4Z+Hvd4Tiw== dependencies: - expect "^29.4.2" - jest-snapshot "^29.4.2" - -"@jest/fake-timers@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.4.2.tgz#af43ee1a5720b987d0348f80df98f2cb17d45cd0" - integrity sha512-Ny1u0Wg6kCsHFWq7A/rW/tMhIedq2siiyHyLpHCmIhP7WmcAmd2cx95P+0xtTZlj5ZbJxIRQi4OPydZZUoiSQQ== - dependencies: - "@jest/types" "^29.4.2" + "@jest/types" "^29.4.3" "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^29.4.2" - jest-mock "^29.4.2" - jest-util "^29.4.2" + jest-message-util "^29.4.3" + jest-mock "^29.4.3" + jest-util "^29.4.3" -"@jest/globals@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.4.2.tgz#73f85f5db0e17642258b25fd0b9fc89ddedb50eb" - integrity sha512-zCk70YGPzKnz/I9BNFDPlK+EuJLk21ur/NozVh6JVM86/YYZtZHqxFFQ62O9MWq7uf3vIZnvNA0BzzrtxD9iyg== +"@jest/globals@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.4.3.tgz#63a2c4200d11bc6d46f12bbe25b07f771fce9279" + integrity sha512-8BQ/5EzfOLG7AaMcDh7yFCbfRLtsc+09E1RQmRBI4D6QQk4m6NSK/MXo+3bJrBN0yU8A2/VIcqhvsOLFmziioA== dependencies: - "@jest/environment" "^29.4.2" - "@jest/expect" "^29.4.2" - "@jest/types" "^29.4.2" - jest-mock "^29.4.2" + "@jest/environment" "^29.4.3" + "@jest/expect" "^29.4.3" + "@jest/types" "^29.4.3" + jest-mock "^29.4.3" -"@jest/reporters@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.4.2.tgz#6abfa923941daae0acc76a18830ee9e79a22042d" - integrity sha512-10yw6YQe75zCgYcXgEND9kw3UZZH5tJeLzWv4vTk/2mrS1aY50A37F+XT2hPO5OqQFFnUWizXD8k1BMiATNfUw== +"@jest/reporters@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.4.3.tgz#0a68a0c0f20554760cc2e5443177a0018969e353" + integrity sha512-sr2I7BmOjJhyqj9ANC6CTLsL4emMoka7HkQpcoMRlhCbQJjz2zsRzw0BDPiPyEFDXAbxKgGFYuQZiSJ1Y6YoTg== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.4.2" - "@jest/test-result" "^29.4.2" - "@jest/transform" "^29.4.2" - "@jest/types" "^29.4.2" + "@jest/console" "^29.4.3" + "@jest/test-result" "^29.4.3" + "@jest/transform" "^29.4.3" + "@jest/types" "^29.4.3" "@jridgewell/trace-mapping" "^0.3.15" "@types/node" "*" chalk "^4.0.0" @@ -1480,9 +1479,9 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.4.2" - jest-util "^29.4.2" - jest-worker "^29.4.2" + jest-message-util "^29.4.3" + jest-util "^29.4.3" + jest-worker "^29.4.3" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" @@ -1495,47 +1494,40 @@ dependencies: "@sinclair/typebox" "^0.24.1" -"@jest/schemas@^29.0.0": - version "29.0.0" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.0.0.tgz#5f47f5994dd4ef067fb7b4188ceac45f77fe952a" - integrity sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA== - dependencies: - "@sinclair/typebox" "^0.24.1" - -"@jest/schemas@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.2.tgz#cf7cfe97c5649f518452b176c47ed07486270fc1" - integrity sha512-ZrGzGfh31NtdVH8tn0mgJw4khQuNHiKqdzJAFbCaERbyCP9tHlxWuL/mnMu8P7e/+k4puWjI1NOzi/sFsjce/g== +"@jest/schemas@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" + integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== dependencies: "@sinclair/typebox" "^0.25.16" -"@jest/source-map@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.2.tgz#f9815d59e25cd3d6828e41489cd239271018d153" - integrity sha512-tIoqV5ZNgYI9XCKXMqbYe5JbumcvyTgNN+V5QW4My033lanijvCD0D4PI9tBw4pRTqWOc00/7X3KVvUh+qnF4Q== +"@jest/source-map@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.3.tgz#ff8d05cbfff875d4a791ab679b4333df47951d20" + integrity sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w== dependencies: "@jridgewell/trace-mapping" "^0.3.15" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.4.2.tgz#34b0ba069f2e3072261e4884c8fb6bd15ed6fb8d" - integrity sha512-HZsC3shhiHVvMtP+i55MGR5bPcc3obCFbA5bzIOb8pCjwBZf11cZliJncCgaVUbC5yoQNuGqCkC0Q3t6EItxZA== +"@jest/test-result@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.4.3.tgz#e13d973d16c8c7cc0c597082d5f3b9e7f796ccb8" + integrity sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA== dependencies: - "@jest/console" "^29.4.2" - "@jest/types" "^29.4.2" + "@jest/console" "^29.4.3" + "@jest/types" "^29.4.3" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.4.2.tgz#8b48e5bc4af80b42edacaf2a733d4f295edf28fb" - integrity sha512-9Z2cVsD6CcObIVrWigHp2McRJhvCxL27xHtrZFgNC1RwnoSpDx6fZo8QYjJmziFlW9/hr78/3sxF54S8B6v8rg== +"@jest/test-sequencer@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.4.3.tgz#0862e876a22993385a0f3e7ea1cc126f208a2898" + integrity sha512-yi/t2nES4GB4G0mjLc0RInCq/cNr9dNwJxcGg8sslajua5Kb4kmozAc+qPLzplhBgfw1vLItbjyHzUN92UXicw== dependencies: - "@jest/test-result" "^29.4.2" + "@jest/test-result" "^29.4.3" graceful-fs "^4.2.9" - jest-haste-map "^29.4.2" + jest-haste-map "^29.4.3" slash "^3.0.0" "@jest/transform@28.1.3": @@ -1559,22 +1551,22 @@ slash "^3.0.0" write-file-atomic "^4.0.1" -"@jest/transform@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.4.2.tgz#b24b72dbab4c8675433a80e222d6a8ef4656fb81" - integrity sha512-kf1v5iTJHn7p9RbOsBuc/lcwyPtJaZJt5885C98omWz79NIeD3PfoiiaPSu7JyCyFzNOIzKhmMhQLUhlTL9BvQ== +"@jest/transform@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.4.3.tgz#f7d17eac9cb5bb2e1222ea199c7c7e0835e0c037" + integrity sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.4.2" + "@jest/types" "^29.4.3" "@jridgewell/trace-mapping" "^0.3.15" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.4.2" - jest-regex-util "^29.4.2" - jest-util "^29.4.2" + jest-haste-map "^29.4.3" + jest-regex-util "^29.4.3" + jest-util "^29.4.3" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" @@ -1592,24 +1584,12 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jest/types@^29.3.1": - version "29.3.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.3.1.tgz#7c5a80777cb13e703aeec6788d044150341147e3" - integrity sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA== +"@jest/types@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.4.3.tgz#9069145f4ef09adf10cec1b2901b2d390031431f" + integrity sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA== dependencies: - "@jest/schemas" "^29.0.0" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jest/types@^29.4.2": - version "29.4.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.4.2.tgz#8f724a414b1246b2bfd56ca5225d9e1f39540d82" - integrity sha512-CKlngyGP0fwlgC1BRUtPZSiWLBhyS9dKwKmyGxk8Z6M82LBEGB2aLQSg+U1MyLsU+M7UjnlLllBM2BLWKVm/Uw== - dependencies: - "@jest/schemas" "^29.4.2" + "@jest/schemas" "^29.4.3" "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" @@ -1648,7 +1628,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": version "0.3.17" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== @@ -1662,9 +1642,9 @@ integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== "@noble/hashes@^1", "@noble/hashes@^1.0.0": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.5.tgz#1a0377f3b9020efe2fae03290bd2a12140c95c11" - integrity sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ== + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" + integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -1775,9 +1755,9 @@ integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== "@sinclair/typebox@^0.25.16": - version "0.25.21" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.21.tgz#763b05a4b472c93a8db29b2c3e359d55b29ce272" - integrity sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g== + version "0.25.23" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.23.tgz#1c15b0d2b872d89cc0f47c7243eacb447df8b8bd" + integrity sha512-VEB8ygeP42CFLWyAJhN5OklpxUliqdNEUcXb4xZ/CINqtYGTjL5ukluKdKzQ0iWdUxyQ7B0539PAUhHKrCNWSQ== "@sinonjs/commons@^2.0.0": version "2.0.0" @@ -1889,9 +1869,9 @@ integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/node@*", "@types/node@>=13.7.0": - version "18.11.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" - integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== + version "18.14.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.0.tgz#94c47b9217bbac49d4a67a967fdcdeed89ebb7d0" + integrity sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A== "@types/prettier@^2.1.5", "@types/prettier@^2.6.1": version "2.7.2" @@ -1914,20 +1894,20 @@ integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== "@types/yargs@^17.0.8": - version "17.0.20" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.20.tgz#107f0fcc13bd4a524e352b41c49fe88aab5c54d5" - integrity sha512-eknWrTHofQuPk2iuqDm1waA7V6xPlbgBoaaXEgYkClhLOnB0TtbW+srJaOToAgawPxPlHQzwypFA2bhZaUGP5A== + version "17.0.22" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.22.tgz#7dd37697691b5f17d020f3c63e7a45971ff71e9a" + integrity sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g== dependencies: "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^5.51.0": - version "5.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz#da3f2819633061ced84bb82c53bba45a6fe9963a" - integrity sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ== + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz#24b8b4a952f3c615fe070e3c461dd852b5056734" + integrity sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw== dependencies: - "@typescript-eslint/scope-manager" "5.51.0" - "@typescript-eslint/type-utils" "5.51.0" - "@typescript-eslint/utils" "5.51.0" + "@typescript-eslint/scope-manager" "5.53.0" + "@typescript-eslint/type-utils" "5.53.0" + "@typescript-eslint/utils" "5.53.0" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -1937,71 +1917,71 @@ tsutils "^3.21.0" "@typescript-eslint/parser@^5.51.0": - version "5.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.51.0.tgz#2d74626652096d966ef107f44b9479f02f51f271" - integrity sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA== + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.53.0.tgz#a1f2b9ae73b83181098747e96683f1b249ecab52" + integrity sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ== dependencies: - "@typescript-eslint/scope-manager" "5.51.0" - "@typescript-eslint/types" "5.51.0" - "@typescript-eslint/typescript-estree" "5.51.0" + "@typescript-eslint/scope-manager" "5.53.0" + "@typescript-eslint/types" "5.53.0" + "@typescript-eslint/typescript-estree" "5.53.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.51.0": - version "5.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz#ad3e3c2ecf762d9a4196c0fbfe19b142ac498990" - integrity sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ== +"@typescript-eslint/scope-manager@5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz#42b54f280e33c82939275a42649701024f3fafef" + integrity sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w== dependencies: - "@typescript-eslint/types" "5.51.0" - "@typescript-eslint/visitor-keys" "5.51.0" + "@typescript-eslint/types" "5.53.0" + "@typescript-eslint/visitor-keys" "5.53.0" -"@typescript-eslint/type-utils@5.51.0": - version "5.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz#7af48005531700b62a20963501d47dfb27095988" - integrity sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ== +"@typescript-eslint/type-utils@5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.53.0.tgz#41665449935ba9b4e6a1ba6e2a3f4b2c31d6cf97" + integrity sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw== dependencies: - "@typescript-eslint/typescript-estree" "5.51.0" - "@typescript-eslint/utils" "5.51.0" + "@typescript-eslint/typescript-estree" "5.53.0" + "@typescript-eslint/utils" "5.53.0" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.51.0": - version "5.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.51.0.tgz#e7c1622f46c7eea7e12bbf1edfb496d4dec37c90" - integrity sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw== +"@typescript-eslint/types@5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.53.0.tgz#f79eca62b97e518ee124086a21a24f3be267026f" + integrity sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A== -"@typescript-eslint/typescript-estree@5.51.0": - version "5.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz#0ec8170d7247a892c2b21845b06c11eb0718f8de" - integrity sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA== +"@typescript-eslint/typescript-estree@5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz#bc651dc28cf18ab248ecd18a4c886c744aebd690" + integrity sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w== dependencies: - "@typescript-eslint/types" "5.51.0" - "@typescript-eslint/visitor-keys" "5.51.0" + "@typescript-eslint/types" "5.53.0" + "@typescript-eslint/visitor-keys" "5.53.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.51.0": - version "5.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.51.0.tgz#074f4fabd5b12afe9c8aa6fdee881c050f8b4d47" - integrity sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw== +"@typescript-eslint/utils@5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.53.0.tgz#e55eaad9d6fffa120575ffaa530c7e802f13bce8" + integrity sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g== dependencies: "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.51.0" - "@typescript-eslint/types" "5.51.0" - "@typescript-eslint/typescript-estree" "5.51.0" + "@typescript-eslint/scope-manager" "5.53.0" + "@typescript-eslint/types" "5.53.0" + "@typescript-eslint/typescript-estree" "5.53.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.51.0": - version "5.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz#c0147dd9a36c0de758aaebd5b48cae1ec59eba87" - integrity sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ== +"@typescript-eslint/visitor-keys@5.53.0": + version "5.53.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz#8a5126623937cdd909c30d8fa72f79fa56cc1a9f" + integrity sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w== dependencies: - "@typescript-eslint/types" "5.51.0" + "@typescript-eslint/types" "5.53.0" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -2010,9 +1990,9 @@ acorn-jsx@^5.3.2: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^8.8.0: - version "8.8.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" - integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== + version "8.8.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" @@ -2129,15 +2109,15 @@ axios@^0.21.2: dependencies: follow-redirects "^1.14.0" -babel-jest@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.4.2.tgz#b17b9f64be288040877cbe2649f91ac3b63b2ba6" - integrity sha512-vcghSqhtowXPG84posYkkkzcZsdayFkubUgbE3/1tuGbX7AQtwCkkNA/wIbB0BMjuCPoqTkiDyKN7Ty7d3uwNQ== +babel-jest@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.4.3.tgz#478b84d430972b277ad67dd631be94abea676792" + integrity sha512-o45Wyn32svZE+LnMVWv/Z4x0SwtLbh4FyGcYtR20kIWd+rdrDZ9Fzq8Ml3MYLD+mZvEdzCjZsCnYZ2jpJyQ+Nw== dependencies: - "@jest/transform" "^29.4.2" + "@jest/transform" "^29.4.3" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.4.2" + babel-preset-jest "^29.4.3" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" @@ -2153,10 +2133,10 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.4.2.tgz#22aa43e255230f02371ffef1cac7eedef58f60bc" - integrity sha512-5HZRCfMeWypFEonRbEkwWXtNS1sQK159LhRVyRuLzyfVBxDy/34Tr/rg4YVi0SScSJ4fqeaR/OIeceJ/LaQ0pQ== +babel-plugin-jest-hoist@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.4.3.tgz#ad1dfb5d31940957e00410ef7d9b2aa94b216101" + integrity sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -2213,12 +2193,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.4.2.tgz#f0b20c6a79a9f155515e72a2d4f537fe002a4e38" - integrity sha512-ecWdaLY/8JyfUDr0oELBMpj3R5I1L6ZqG+kRJmwqfHtLWuPrJStR0LUkvUhfykJWTsXXMnohsayN/twltBbDrQ== +babel-preset-jest@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.4.3.tgz#bb926b66ae253b69c6e3ef87511b8bb5c53c5b52" + integrity sha512-gWx6COtSuma6n9bw+8/F+2PCXrIgxV/D1TJFnp6OyBK2cxPWg0K9p/sriNYeifKjpUkMViWQ09DSWtzJQRETsw== dependencies: - babel-plugin-jest-hoist "^29.4.2" + babel-plugin-jest-hoist "^29.4.3" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: @@ -2273,15 +2253,15 @@ brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== -browserslist@^4.21.3, browserslist@^4.21.4: - version "4.21.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" - integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== +browserslist@^4.21.3, browserslist@^4.21.5: + version "4.21.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" + integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== dependencies: - caniuse-lite "^1.0.30001400" - electron-to-chromium "^1.4.251" - node-releases "^2.0.6" - update-browserslist-db "^1.0.9" + caniuse-lite "^1.0.30001449" + electron-to-chromium "^1.4.284" + node-releases "^2.0.8" + update-browserslist-db "^1.0.10" bser@2.1.1: version "2.1.1" @@ -2315,10 +2295,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001400: - version "1.0.30001446" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001446.tgz#6d4ba828ab19f49f9bcd14a8430d30feebf1e0c5" - integrity sha512-fEoga4PrImGcwUUGEol/PoFCSBnSkA9drgdkxXkJLsUBOnJ8rs3zDv6ApqYXGQFOyMPsjh79naWhF4DAxbF8rw== +caniuse-lite@^1.0.30001449: + version "1.0.30001457" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz#6af34bb5d720074e2099432aa522c21555a18301" + integrity sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA== case@1.6.3: version "1.6.3" @@ -2369,9 +2349,9 @@ chardet@^0.7.0: integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== ci-info@^3.2.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.7.1.tgz#708a6cdae38915d597afdf3b145f2f8e1ff55f3f" - integrity sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w== + version "3.8.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" + integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== cjs-module-lexer@^1.0.0: version "1.2.2" @@ -2465,11 +2445,11 @@ convert-source-map@^2.0.0: integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.25.1: - version "3.27.2" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.27.2.tgz#607c50ad6db8fd8326af0b2883ebb987be3786da" - integrity sha512-welaYuF7ZtbYKGrIy7y3eb40d37rG1FvzEOfe7hSLd2iD6duMDqUhRfSvCGyC46HhR6Y8JXXdZ2lnRUMkPBpvg== + version "3.28.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.28.0.tgz#c08456d854608a7264530a2afa281fadf20ecee6" + integrity sha512-myzPgE7QodMg4nnd3K1TDoES/nADRStM8Gpz0D6nhkwbmwEnE0ZGJgoWsvQ722FR8D7xS0n0LV556RcEicjTyg== dependencies: - browserslist "^4.21.4" + browserslist "^4.21.5" cosmjs-types@^0.5.2: version "0.5.2" @@ -2526,15 +2506,20 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@4.2.2, deepmerge@^4.2.2: +deepmerge@4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +deepmerge@^4.2.2: + version "4.3.0" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.0.tgz#65491893ec47756d44719ae520e0e2609233b59b" + integrity sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og== + define-properties@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" - integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== + version "1.2.0" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" + integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== dependencies: has-property-descriptors "^1.0.0" object-keys "^1.1.1" @@ -2544,15 +2529,10 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -diff-sequences@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.3.1.tgz#104b5b95fe725932421a9c6e5b4bef84c3f2249e" - integrity sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ== - -diff-sequences@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.2.tgz#711fe6bd8a5869fe2539cee4a5152425ff671fda" - integrity sha512-R6P0Y6PrsH3n4hUXxL3nns0rbRk6Q33js3ygJBeEpbzLzgcNuJ61+u0RXasFpTKISw99TxUzFnumSnRLsjhLaw== +diff-sequences@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" + integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== dir-glob@^3.0.1: version "3.0.1" @@ -2573,10 +2553,10 @@ dotty@0.1.2: resolved "https://registry.yarnpkg.com/dotty/-/dotty-0.1.2.tgz#512d44cc4111a724931226259297f235e8484f6f" integrity sha512-V0EWmKeH3DEhMwAZ+8ZB2Ao4OK6p++Z0hsDtZq3N0+0ZMVqkzrcEGROvOnZpLnvBg5PTNG23JEDLAm64gPaotQ== -electron-to-chromium@^1.4.251: - version "1.4.284" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" - integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== +electron-to-chromium@^1.4.284: + version "1.4.306" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.306.tgz#3f16bc14e150ad79803145fffeaf0bee15d3fca7" + integrity sha512-1zGmLFfpcs2v7ELt/1HgLZF6Gm2CCHaAdNKxd9Ge4INSU/HDYWjs7fcWU6eVMmhkpwmh+52ZrGCUU+Ji9OJihA== elliptic@^6.5.4: version "6.5.4" @@ -2703,9 +2683,9 @@ eslint-visitor-keys@^3.3.0: integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== eslint@^8.33.0: - version "8.33.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.33.0.tgz#02f110f32998cb598c6461f24f4d306e41ca33d7" - integrity sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA== + version "8.34.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.34.0.tgz#fe0ab0ef478104c1f9ebc5537e303d25a8fb22d6" + integrity sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg== dependencies: "@eslint/eslintrc" "^1.4.1" "@humanwhocodes/config-array" "^0.11.8" @@ -2762,9 +2742,9 @@ esprima@^4.0.0: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + version "1.4.2" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.2.tgz#c6d3fee05dd665808e2ad870631f221f5617b1d1" + integrity sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng== dependencies: estraverse "^5.1.0" @@ -2818,27 +2798,16 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0: - version "29.3.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.3.1.tgz#92877aad3f7deefc2e3f6430dd195b92295554a6" - integrity sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA== +expect@^29.0.0, expect@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.4.3.tgz#5e47757316df744fe3b8926c3ae8a3ebdafff7fe" + integrity sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg== dependencies: - "@jest/expect-utils" "^29.3.1" - jest-get-type "^29.2.0" - jest-matcher-utils "^29.3.1" - jest-message-util "^29.3.1" - jest-util "^29.3.1" - -expect@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.4.2.tgz#2ae34eb88de797c64a1541ad0f1e2ea8a7a7b492" - integrity sha512-+JHYg9O3hd3RlICG90OPVjRkPBoiUH7PxvDVMnRiaq1g6JUgZStX514erMl0v2Dc5SkfVbm7ztqbd6qHHPn+mQ== - dependencies: - "@jest/expect-utils" "^29.4.2" - jest-get-type "^29.4.2" - jest-matcher-utils "^29.4.2" - jest-message-util "^29.4.2" - jest-util "^29.4.2" + "@jest/expect-utils" "^29.4.3" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.4.3" + jest-message-util "^29.4.3" + jest-util "^29.4.3" ext@^1.1.2: version "1.7.0" @@ -3064,9 +3033,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.19.0: - version "13.19.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.19.0.tgz#7a42de8e6ad4f7242fbcca27ea5b23aca367b5c8" - integrity sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ== + version "13.20.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" + integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== dependencies: type-fest "^0.20.2" @@ -3390,144 +3359,129 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.4.2.tgz#bee1fafc8b620d6251423d1978a0080546bc4376" - integrity sha512-Qdd+AXdqD16PQa+VsWJpxR3kN0JyOCX1iugQfx5nUgAsI4gwsKviXkpclxOK9ZnwaY2IQVHz+771eAvqeOlfuw== +jest-changed-files@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.4.3.tgz#7961fe32536b9b6d5c28dfa0abcfab31abcf50a7" + integrity sha512-Vn5cLuWuwmi2GNNbokPOEcvrXGSGrqVnPEZV7rC6P7ck07Dyw9RFnvWglnupSh+hGys0ajGtw/bc2ZgweljQoQ== dependencies: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.4.2.tgz#2d00c04baefd0ee2a277014cd494d4b5970663ed" - integrity sha512-wW3ztp6a2P5c1yOc1Cfrt5ozJ7neWmqeXm/4SYiqcSriyisgq63bwFj1NuRdSR5iqS0CMEYwSZd89ZA47W9zUg== +jest-circus@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.4.3.tgz#fff7be1cf5f06224dd36a857d52a9efeb005ba04" + integrity sha512-Vw/bVvcexmdJ7MLmgdT3ZjkJ3LKu8IlpefYokxiqoZy6OCQ2VAm6Vk3t/qHiAGUXbdbJKJWnc8gH3ypTbB/OBw== dependencies: - "@jest/environment" "^29.4.2" - "@jest/expect" "^29.4.2" - "@jest/test-result" "^29.4.2" - "@jest/types" "^29.4.2" + "@jest/environment" "^29.4.3" + "@jest/expect" "^29.4.3" + "@jest/test-result" "^29.4.3" + "@jest/types" "^29.4.3" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^29.4.2" - jest-matcher-utils "^29.4.2" - jest-message-util "^29.4.2" - jest-runtime "^29.4.2" - jest-snapshot "^29.4.2" - jest-util "^29.4.2" + jest-each "^29.4.3" + jest-matcher-utils "^29.4.3" + jest-message-util "^29.4.3" + jest-runtime "^29.4.3" + jest-snapshot "^29.4.3" + jest-util "^29.4.3" p-limit "^3.1.0" - pretty-format "^29.4.2" + pretty-format "^29.4.3" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.4.2.tgz#94a2f913a0a7a49d11bee98ad88bf48baae941f4" - integrity sha512-b+eGUtXq/K2v7SH3QcJvFvaUaCDS1/YAZBYz0m28Q/Ppyr+1qNaHmVYikOrbHVbZqYQs2IeI3p76uy6BWbXq8Q== +jest-cli@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.4.3.tgz#fe31fdd0c90c765f392b8b7c97e4845071cd2163" + integrity sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg== dependencies: - "@jest/core" "^29.4.2" - "@jest/test-result" "^29.4.2" - "@jest/types" "^29.4.2" + "@jest/core" "^29.4.3" + "@jest/test-result" "^29.4.3" + "@jest/types" "^29.4.3" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.4.2" - jest-util "^29.4.2" - jest-validate "^29.4.2" + jest-config "^29.4.3" + jest-util "^29.4.3" + jest-validate "^29.4.3" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.4.2.tgz#15386dd9ed2f7059516915515f786b8836a98f07" - integrity sha512-919CtnXic52YM0zW4C1QxjG6aNueX1kBGthuMtvFtRTAxhKfJmiXC9qwHmi6o2josjbDz8QlWyY55F1SIVmCWA== +jest-config@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.4.3.tgz#fca9cdfe6298ae6d04beef1624064d455347c978" + integrity sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.4.2" - "@jest/types" "^29.4.2" - babel-jest "^29.4.2" + "@jest/test-sequencer" "^29.4.3" + "@jest/types" "^29.4.3" + babel-jest "^29.4.3" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.4.2" - jest-environment-node "^29.4.2" - jest-get-type "^29.4.2" - jest-regex-util "^29.4.2" - jest-resolve "^29.4.2" - jest-runner "^29.4.2" - jest-util "^29.4.2" - jest-validate "^29.4.2" + jest-circus "^29.4.3" + jest-environment-node "^29.4.3" + jest-get-type "^29.4.3" + jest-regex-util "^29.4.3" + jest-resolve "^29.4.3" + jest-runner "^29.4.3" + jest-util "^29.4.3" + jest-validate "^29.4.3" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.4.2" + pretty-format "^29.4.3" slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.3.1.tgz#d8215b72fed8f1e647aed2cae6c752a89e757527" - integrity sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.3.1" - jest-get-type "^29.2.0" - pretty-format "^29.3.1" - -jest-diff@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.4.2.tgz#b88502d5dc02d97f6512d73c37da8b36f49b4871" - integrity sha512-EK8DSajVtnjx9sa1BkjZq3mqChm2Cd8rIzdXkQMA8e0wuXq53ypz6s5o5V8HRZkoEt2ywJ3eeNWFKWeYr8HK4g== +jest-diff@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.4.3.tgz#42f4eb34d0bf8c0fb08b0501069b87e8e84df347" + integrity sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA== dependencies: chalk "^4.0.0" - diff-sequences "^29.4.2" - jest-get-type "^29.4.2" - pretty-format "^29.4.2" + diff-sequences "^29.4.3" + jest-get-type "^29.4.3" + pretty-format "^29.4.3" -jest-docblock@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.2.tgz#c78a95eedf9a24c0a6cc16cf2abdc4b8b0f2531b" - integrity sha512-dV2JdahgClL34Y5vLrAHde3nF3yo2jKRH+GIYJuCpfqwEJZcikzeafVTGAjbOfKPG17ez9iWXwUYp7yefeCRag== +jest-docblock@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" + integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== dependencies: detect-newline "^3.0.0" -jest-each@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.4.2.tgz#e1347aff1303f4c35470827a62c029d389c5d44a" - integrity sha512-trvKZb0JYiCndc55V1Yh0Luqi7AsAdDWpV+mKT/5vkpnnFQfuQACV72IoRV161aAr6kAVIBpmYzwhBzm34vQkA== +jest-each@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.4.3.tgz#a434c199a2f6151c5e3dc80b2d54586bdaa72819" + integrity sha512-1ElHNAnKcbJb/b+L+7j0/w7bDvljw4gTv1wL9fYOczeJrbTbkMGQ5iQPFJ3eFQH19VPTx1IyfePdqSpePKss7Q== dependencies: - "@jest/types" "^29.4.2" + "@jest/types" "^29.4.3" chalk "^4.0.0" - jest-get-type "^29.4.2" - jest-util "^29.4.2" - pretty-format "^29.4.2" - -jest-environment-node@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.4.2.tgz#0eab835b41e25fd0c1a72f62665fc8db08762ad2" - integrity sha512-MLPrqUcOnNBc8zTOfqBbxtoa8/Ee8tZ7UFW7hRDQSUT+NGsvS96wlbHGTf+EFAT9KC3VNb7fWEM6oyvmxtE/9w== - dependencies: - "@jest/environment" "^29.4.2" - "@jest/fake-timers" "^29.4.2" - "@jest/types" "^29.4.2" + jest-get-type "^29.4.3" + jest-util "^29.4.3" + pretty-format "^29.4.3" + +jest-environment-node@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.4.3.tgz#579c4132af478befc1889ddc43c2413a9cdbe014" + integrity sha512-gAiEnSKF104fsGDXNkwk49jD/0N0Bqu2K9+aMQXA6avzsA9H3Fiv1PW2D+gzbOSR705bWd2wJZRFEFpV0tXISg== + dependencies: + "@jest/environment" "^29.4.3" + "@jest/fake-timers" "^29.4.3" + "@jest/types" "^29.4.3" "@types/node" "*" - jest-mock "^29.4.2" - jest-util "^29.4.2" - -jest-get-type@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.2.0.tgz#726646f927ef61d583a3b3adb1ab13f3a5036408" - integrity sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA== + jest-mock "^29.4.3" + jest-util "^29.4.3" -jest-get-type@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.2.tgz#7cb63f154bca8d8f57364d01614477d466fa43fe" - integrity sha512-vERN30V5i2N6lqlFu4ljdTqQAgrkTFMC9xaIIfOPYBw04pufjXRty5RuXBiB1d72tGbURa/UgoiHB90ruOSivg== +jest-get-type@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" + integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== jest-haste-map@^28.1.3: version "28.1.3" @@ -3548,91 +3502,66 @@ jest-haste-map@^28.1.3: optionalDependencies: fsevents "^2.3.2" -jest-haste-map@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.4.2.tgz#9112df3f5121e643f1b2dcbaa86ab11b0b90b49a" - integrity sha512-WkUgo26LN5UHPknkezrBzr7lUtV1OpGsp+NfXbBwHztsFruS3gz+AMTTBcEklvi8uPzpISzYjdKXYZQJXBnfvw== +jest-haste-map@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.4.3.tgz#085a44283269e7ace0645c63a57af0d2af6942e2" + integrity sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ== dependencies: - "@jest/types" "^29.4.2" + "@jest/types" "^29.4.3" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^29.4.2" - jest-util "^29.4.2" - jest-worker "^29.4.2" + jest-regex-util "^29.4.3" + jest-util "^29.4.3" + jest-worker "^29.4.3" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.4.2.tgz#8f05c6680e0cb46a1d577c0d3da9793bed3ea97b" - integrity sha512-Wa62HuRJmWXtX9F00nUpWlrbaH5axeYCdyRsOs/+Rb1Vb6+qWTlB5rKwCCRKtorM7owNwKsyJ8NRDUcZ8ghYUA== - dependencies: - jest-get-type "^29.4.2" - pretty-format "^29.4.2" - -jest-matcher-utils@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz#6e7f53512f80e817dfa148672bd2d5d04914a572" - integrity sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ== - dependencies: - chalk "^4.0.0" - jest-diff "^29.3.1" - jest-get-type "^29.2.0" - pretty-format "^29.3.1" - -jest-matcher-utils@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.4.2.tgz#08d0bf5abf242e3834bec92c7ef5071732839e85" - integrity sha512-EZaAQy2je6Uqkrm6frnxBIdaWtSYFoR8SVb2sNLAtldswlR/29JAgx+hy67llT3+hXBaLB0zAm5UfeqerioZyg== +jest-leak-detector@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.4.3.tgz#2b35191d6b35aa0256e63a9b79b0f949249cf23a" + integrity sha512-9yw4VC1v2NspMMeV3daQ1yXPNxMgCzwq9BocCwYrRgXe4uaEJPAN0ZK37nFBhcy3cUwEVstFecFLaTHpF7NiGA== dependencies: - chalk "^4.0.0" - jest-diff "^29.4.2" - jest-get-type "^29.4.2" - pretty-format "^29.4.2" + jest-get-type "^29.4.3" + pretty-format "^29.4.3" -jest-message-util@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.3.1.tgz#37bc5c468dfe5120712053dd03faf0f053bd6adb" - integrity sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA== +jest-matcher-utils@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.4.3.tgz#ea68ebc0568aebea4c4213b99f169ff786df96a0" + integrity sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg== dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.3.1" - "@types/stack-utils" "^2.0.0" chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.3.1" - slash "^3.0.0" - stack-utils "^2.0.3" + jest-diff "^29.4.3" + jest-get-type "^29.4.3" + pretty-format "^29.4.3" -jest-message-util@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.4.2.tgz#309a2924eae6ca67cf7f25781a2af1902deee717" - integrity sha512-SElcuN4s6PNKpOEtTInjOAA8QvItu0iugkXqhYyguRvQoXapg5gN+9RQxLAkakChZA7Y26j6yUCsFWN+hlKD6g== +jest-message-util@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.4.3.tgz#65b5280c0fdc9419503b49d4f48d4999d481cb5b" + integrity sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.4.2" + "@jest/types" "^29.4.3" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^29.4.2" + pretty-format "^29.4.3" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.4.2.tgz#e1054be66fb3e975d26d4528fcde6979e4759de8" - integrity sha512-x1FSd4Gvx2yIahdaIKoBjwji6XpboDunSJ95RpntGrYulI1ByuYQCKN/P7hvk09JB74IonU3IPLdkutEWYt++g== +jest-mock@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.4.3.tgz#23d84a20a74cdfff0510fdbeefb841ed57b0fe7e" + integrity sha512-LjFgMg+xed9BdkPMyIJh+r3KeHt1klXPJYBULXVVAkbTaaKjPX1o1uVCAZADMEp/kOxGTwy/Ot8XbvgItOrHEg== dependencies: - "@jest/types" "^29.4.2" + "@jest/types" "^29.4.3" "@types/node" "*" - jest-util "^29.4.2" + jest-util "^29.4.3" jest-pnp-resolver@^1.2.2: version "1.2.3" @@ -3644,94 +3573,93 @@ jest-regex-util@^28.0.2: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== -jest-regex-util@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.2.tgz#19187cca35d301f8126cf7a021dd4dcb7b58a1ca" - integrity sha512-XYZXOqUl1y31H6VLMrrUL1ZhXuiymLKPz0BO1kEeR5xER9Tv86RZrjTm74g5l9bPJQXA/hyLdaVPN/sdqfteig== +jest-regex-util@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" + integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== -jest-resolve-dependencies@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.4.2.tgz#6359db606f5967b68ca8bbe9dbc07a4306c12bf7" - integrity sha512-6pL4ptFw62rjdrPk7rRpzJYgcRqRZNsZTF1VxVTZMishbO6ObyWvX57yHOaNGgKoADtAHRFYdHQUEvYMJATbDg== +jest-resolve-dependencies@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.4.3.tgz#9ad7f23839a6d88cef91416bda9393a6e9fd1da5" + integrity sha512-uvKMZAQ3nmXLH7O8WAOhS5l0iWyT3WmnJBdmIHiV5tBbdaDZ1wqtNX04FONGoaFvSOSHBJxnwAVnSn1WHdGVaw== dependencies: - jest-regex-util "^29.4.2" - jest-snapshot "^29.4.2" + jest-regex-util "^29.4.3" + jest-snapshot "^29.4.3" -jest-resolve@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.4.2.tgz#8831f449671d08d161fe493003f61dc9b55b808e" - integrity sha512-RtKWW0mbR3I4UdkOrW7552IFGLYQ5AF9YrzD0FnIOkDu0rAMlA5/Y1+r7lhCAP4nXSBTaE7ueeqj6IOwZpgoqw== +jest-resolve@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.4.3.tgz#3c5b5c984fa8a763edf9b3639700e1c7900538e2" + integrity sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.4.2" + jest-haste-map "^29.4.3" jest-pnp-resolver "^1.2.2" - jest-util "^29.4.2" - jest-validate "^29.4.2" + jest-util "^29.4.3" + jest-validate "^29.4.3" resolve "^1.20.0" resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.4.2.tgz#2bcecf72303369df4ef1e6e983c22a89870d5125" - integrity sha512-wqwt0drm7JGjwdH+x1XgAl+TFPH7poowMguPQINYxaukCqlczAcNLJiK+OLxUxQAEWMdy+e6nHZlFHO5s7EuRg== +jest-runner@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.4.3.tgz#68dc82c68645eda12bea42b5beece6527d7c1e5e" + integrity sha512-GWPTEiGmtHZv1KKeWlTX9SIFuK19uLXlRQU43ceOQ2hIfA5yPEJC7AMkvFKpdCHx6pNEdOD+2+8zbniEi3v3gA== dependencies: - "@jest/console" "^29.4.2" - "@jest/environment" "^29.4.2" - "@jest/test-result" "^29.4.2" - "@jest/transform" "^29.4.2" - "@jest/types" "^29.4.2" + "@jest/console" "^29.4.3" + "@jest/environment" "^29.4.3" + "@jest/test-result" "^29.4.3" + "@jest/transform" "^29.4.3" + "@jest/types" "^29.4.3" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^29.4.2" - jest-environment-node "^29.4.2" - jest-haste-map "^29.4.2" - jest-leak-detector "^29.4.2" - jest-message-util "^29.4.2" - jest-resolve "^29.4.2" - jest-runtime "^29.4.2" - jest-util "^29.4.2" - jest-watcher "^29.4.2" - jest-worker "^29.4.2" + jest-docblock "^29.4.3" + jest-environment-node "^29.4.3" + jest-haste-map "^29.4.3" + jest-leak-detector "^29.4.3" + jest-message-util "^29.4.3" + jest-resolve "^29.4.3" + jest-runtime "^29.4.3" + jest-util "^29.4.3" + jest-watcher "^29.4.3" + jest-worker "^29.4.3" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.4.2.tgz#d86b764c5b95d76cb26ed1f32644e99de5d5c134" - integrity sha512-3fque9vtpLzGuxT9eZqhxi+9EylKK/ESfhClv4P7Y9sqJPs58LjVhTt8jaMp/pRO38agll1CkSu9z9ieTQeRrw== - dependencies: - "@jest/environment" "^29.4.2" - "@jest/fake-timers" "^29.4.2" - "@jest/globals" "^29.4.2" - "@jest/source-map" "^29.4.2" - "@jest/test-result" "^29.4.2" - "@jest/transform" "^29.4.2" - "@jest/types" "^29.4.2" +jest-runtime@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.4.3.tgz#f25db9874dcf35a3ab27fdaabca426666cc745bf" + integrity sha512-F5bHvxSH+LvLV24vVB3L8K467dt3y3dio6V3W89dUz9nzvTpqd/HcT9zfYKL2aZPvD63vQFgLvaUX/UpUhrP6Q== + dependencies: + "@jest/environment" "^29.4.3" + "@jest/fake-timers" "^29.4.3" + "@jest/globals" "^29.4.3" + "@jest/source-map" "^29.4.3" + "@jest/test-result" "^29.4.3" + "@jest/transform" "^29.4.3" + "@jest/types" "^29.4.3" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.4.2" - jest-message-util "^29.4.2" - jest-mock "^29.4.2" - jest-regex-util "^29.4.2" - jest-resolve "^29.4.2" - jest-snapshot "^29.4.2" - jest-util "^29.4.2" - semver "^7.3.5" + jest-haste-map "^29.4.3" + jest-message-util "^29.4.3" + jest-mock "^29.4.3" + jest-regex-util "^29.4.3" + jest-resolve "^29.4.3" + jest-snapshot "^29.4.3" + jest-util "^29.4.3" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.4.2.tgz#ba1fb9abb279fd2c85109ff1757bc56b503bbb3a" - integrity sha512-PdfubrSNN5KwroyMH158R23tWcAXJyx4pvSvWls1dHoLCaUhGul9rsL3uVjtqzRpkxlkMavQjGuWG1newPgmkw== +jest-snapshot@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.4.3.tgz#183d309371450d9c4a3de7567ed2151eb0e91145" + integrity sha512-NGlsqL0jLPDW91dz304QTM/SNO99lpcSYYAjNiX0Ou+sSGgkanKBcSjCfp/pqmiiO1nQaOyLp6XQddAzRcx3Xw== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" @@ -3739,23 +3667,23 @@ jest-snapshot@^29.4.2: "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.4.2" - "@jest/transform" "^29.4.2" - "@jest/types" "^29.4.2" + "@jest/expect-utils" "^29.4.3" + "@jest/transform" "^29.4.3" + "@jest/types" "^29.4.3" "@types/babel__traverse" "^7.0.6" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.4.2" + expect "^29.4.3" graceful-fs "^4.2.9" - jest-diff "^29.4.2" - jest-get-type "^29.4.2" - jest-haste-map "^29.4.2" - jest-matcher-utils "^29.4.2" - jest-message-util "^29.4.2" - jest-util "^29.4.2" + jest-diff "^29.4.3" + jest-get-type "^29.4.3" + jest-haste-map "^29.4.3" + jest-matcher-utils "^29.4.3" + jest-message-util "^29.4.3" + jest-util "^29.4.3" natural-compare "^1.4.0" - pretty-format "^29.4.2" + pretty-format "^29.4.3" semver "^7.3.5" jest-util@^28.1.3: @@ -3770,54 +3698,42 @@ jest-util@^28.1.3: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.3.1.tgz#1dda51e378bbcb7e3bc9d8ab651445591ed373e1" - integrity sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ== +jest-util@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.4.3.tgz#851a148e23fc2b633c55f6dad2e45d7f4579f496" + integrity sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q== dependencies: - "@jest/types" "^29.3.1" + "@jest/types" "^29.4.3" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.4.2.tgz#3db8580b295df453a97de4a1b42dd2578dabd2c2" - integrity sha512-wKnm6XpJgzMUSRFB7YF48CuwdzuDIHenVuoIb1PLuJ6F+uErZsuDkU+EiExkChf6473XcawBrSfDSnXl+/YG4g== +jest-validate@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.4.3.tgz#a13849dec4f9e95446a7080ad5758f58fa88642f" + integrity sha512-J3u5v7aPQoXPzaar6GndAVhdQcZr/3osWSgTeKg5v574I9ybX/dTyH0AJFb5XgXIB7faVhf+rS7t4p3lL9qFaw== dependencies: - "@jest/types" "^29.4.2" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.4.2.tgz#3b3f8c4910ab9a3442d2512e2175df6b3f77b915" - integrity sha512-tto7YKGPJyFbhcKhIDFq8B5od+eVWD/ySZ9Tvcp/NGCvYA4RQbuzhbwYWtIjMT5W5zA2W0eBJwu4HVw34d5G6Q== - dependencies: - "@jest/types" "^29.4.2" + "@jest/types" "^29.4.3" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^29.4.2" + jest-get-type "^29.4.3" leven "^3.1.0" - pretty-format "^29.4.2" + pretty-format "^29.4.3" -jest-watcher@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.4.2.tgz#09c0f4c9a9c7c0807fcefb1445b821c6f7953b7c" - integrity sha512-onddLujSoGiMJt+tKutehIidABa175i/Ays+QvKxCqBwp7fvxP3ZhKsrIdOodt71dKxqk4sc0LN41mWLGIK44w== +jest-watcher@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.4.3.tgz#e503baa774f0c2f8f3c8db98a22ebf885f19c384" + integrity sha512-zwlXH3DN3iksoIZNk73etl1HzKyi5FuQdYLnkQKm5BW4n8HpoG59xSwpVdFrnh60iRRaRBGw0gcymIxjJENPcA== dependencies: - "@jest/test-result" "^29.4.2" - "@jest/types" "^29.4.2" + "@jest/test-result" "^29.4.3" + "@jest/types" "^29.4.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.4.2" + jest-util "^29.4.3" string-length "^4.0.1" jest-worker@^28.1.3: @@ -3829,25 +3745,25 @@ jest-worker@^28.1.3: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.4.2.tgz#d9b2c3bafc69311d84d94e7fb45677fc8976296f" - integrity sha512-VIuZA2hZmFyRbchsUCHEehoSf2HEl0YVF8SDJqtPnKorAaBuh42V8QsLnde0XP5F6TyCynGPEGgBOn3Fc+wZGw== +jest-worker@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.4.3.tgz#9a4023e1ea1d306034237c7133d7da4240e8934e" + integrity sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA== dependencies: "@types/node" "*" - jest-util "^29.4.2" + jest-util "^29.4.3" merge-stream "^2.0.0" supports-color "^8.0.0" jest@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.4.2.tgz#4c2127d03a71dc187f386156ef155dbf323fb7be" - integrity sha512-+5hLd260vNIHu+7ZgMIooSpKl7Jp5pHKb51e73AJU3owd5dEo/RfVwHbA/na3C/eozrt3hJOLGf96c7EWwIAzg== + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.4.3.tgz#1b8be541666c6feb99990fd98adac4737e6e6386" + integrity sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA== dependencies: - "@jest/core" "^29.4.2" - "@jest/types" "^29.4.2" + "@jest/core" "^29.4.3" + "@jest/types" "^29.4.3" import-local "^3.0.2" - jest-cli "^29.4.2" + jest-cli "^29.4.3" js-sdsl@^4.1.4: version "4.3.0" @@ -3923,16 +3839,16 @@ levn@^0.4.1: type-check "~0.4.0" libsodium-wrappers@^0.7.6: - version "0.7.10" - resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz#13ced44cacb0fc44d6ac9ce67d725956089ce733" - integrity sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg== + version "0.7.11" + resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.11.tgz#53bd20606dffcc54ea2122133c7da38218f575f7" + integrity sha512-SrcLtXj7BM19vUKtQuyQKiQCRJPgbpauzl3s0rSwD+60wtHqSUuqcoawlMDheCJga85nKOQwxNYQxf/CKAvs6Q== dependencies: - libsodium "^0.7.0" + libsodium "^0.7.11" -libsodium@^0.7.0: - version "0.7.10" - resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.10.tgz#c2429a7e4c0836f879d701fec2c8a208af024159" - integrity sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ== +libsodium@^0.7.11: + version "0.7.11" + resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.11.tgz#cd10aae7bcc34a300cc6ad0ac88fcca674cfbc2e" + integrity sha512-WPfJ7sS53I2s4iM58QxY3Inb83/6mjlYgcmZs7DJsvDlnmVUwNinBCi5vBT43P6bHRy01O4zsMU2CoVR6xJ40A== lines-and-columns@^1.1.6: version "1.2.4" @@ -4085,9 +4001,9 @@ minimist@1.2.6: integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== minimist@^1.2.6: - version "1.2.7" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" - integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== mkdirp@1.0.4, mkdirp@^1.0.4: version "1.0.4" @@ -4133,10 +4049,10 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.6: - version "2.0.8" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.8.tgz#0f349cdc8fcfa39a92ac0be9bc48b7706292b9ae" - integrity sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A== +node-releases@^2.0.8: + version "2.0.10" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" + integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== normalize-path@^3.0.0: version "3.0.0" @@ -4317,31 +4233,17 @@ prepend-file@^2.0.1: dependencies: temp-write "^4.0.0" -prettier@^2.6.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.3.tgz#ab697b1d3dd46fb4626fbe2f543afe0cc98d8632" - integrity sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw== - -prettier@^2.8.4: +prettier@^2.6.2, prettier@^2.8.4: version "2.8.4" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3" integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw== -pretty-format@^29.0.0, pretty-format@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.3.1.tgz#1841cac822b02b4da8971dacb03e8a871b4722da" - integrity sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg== +pretty-format@^29.0.0, pretty-format@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.3.tgz#25500ada21a53c9e8423205cf0337056b201244c" + integrity sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA== dependencies: - "@jest/schemas" "^29.0.0" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -pretty-format@^29.4.2: - version "29.4.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.2.tgz#64bf5ccc0d718c03027d94ac957bdd32b3fb2401" - integrity sha512-qKlHR8yFVCbcEWba0H0TOC8dnLlO4vPlyEjRPw31FZ2Rupy9nLa8ZLbYny8gWEl8CkEhJqAE6IzdNELTBVcBEg== - dependencies: - "@jest/schemas" "^29.4.2" + "@jest/schemas" "^29.4.3" ansi-styles "^5.0.0" react-is "^18.0.0" @@ -4428,23 +4330,18 @@ regexpp@^3.2.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -regexpu-core@^5.2.1: - version "5.2.2" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.2.tgz#3e4e5d12103b64748711c3aad69934d7718e75fc" - integrity sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw== +regexpu-core@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.1.tgz#66900860f88def39a5cb79ebd9490e84f17bcdfb" + integrity sha512-nCOzW2V/X15XpLsK2rlgdwrysrBq+AauCn+omItIz4R1pIcmeot5zvjdmOBRLzEH/CkC6IxMJVmxDe3QcMuNVQ== dependencies: + "@babel/regjsgen" "^0.8.0" regenerate "^1.4.2" regenerate-unicode-properties "^10.1.0" - regjsgen "^0.7.1" regjsparser "^0.9.1" unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.1.0" -regjsgen@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.7.1.tgz#ee5ef30e18d3f09b7c369b76e7c2373ed25546f6" - integrity sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA== - regjsparser@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" @@ -4874,7 +4771,7 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== -update-browserslist-db@^1.0.9: +update-browserslist-db@^1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== @@ -4902,9 +4799,9 @@ uuid@^3.3.2: integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== v8-to-istanbul@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz#b6f994b0b5d4ef255e17a0d17dc444a9f5132fa4" - integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w== + version "9.1.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" + integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA== dependencies: "@jridgewell/trace-mapping" "^0.3.12" "@types/istanbul-lib-coverage" "^2.0.1" @@ -4997,9 +4894,9 @@ yargs-parser@^21.1.1: integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@^17.3.1: - version "17.6.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541" - integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw== + version "17.7.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967" + integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw== dependencies: cliui "^8.0.1" escalade "^3.1.1" From e47da9636c36be1e7e6d9d6f0902b1bf103d04be Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 24 Feb 2023 11:57:39 +0100 Subject: [PATCH 143/218] [Part 3] Frontend .wasm health calculation examples (#113) Node/Web health .wasm examples --- .github/workflows/scripts.yml | 7 + Cargo.lock | 11 + contracts/account-nft/src/execute.rs | 8 +- .../account-nft/tests/test_burn_allowance.rs | 2 +- contracts/credit-manager/Cargo.toml | 4 +- contracts/credit-manager/src/health.rs | 4 +- contracts/credit-manager/src/utils.rs | 5 +- .../tests/test_flagged_contract.rs | 1 + packages/health-computer/Cargo.toml | 20 +- packages/health-computer/src/javascript.rs | 23 + packages/health-computer/src/lib.rs | 3 +- scripts/codegen-tsconfig.json | 2 +- scripts/health/DataFetcher.ts | 108 ++ scripts/health/example-node.ts | 16 + scripts/health/example-react/.gitignore | 25 + scripts/health/example-react/index.html | 12 + scripts/health/example-react/package.json | 27 + scripts/health/example-react/src/App.tsx | 64 + scripts/health/example-react/src/main.tsx | 14 + scripts/health/example-react/src/utils.ts | 30 + .../health/example-react/src/vite-env.d.ts | 1 + scripts/health/example-react/tsconfig.json | 21 + .../health/example-react/tsconfig.node.json | 9 + scripts/health/example-react/vite.config.ts | 16 + scripts/health/example-react/yarn.lock | 1287 +++++++++++++++++ scripts/health/pkg-node/index.d.ts | 7 + scripts/health/pkg-node/index.js | 484 +++++++ scripts/health/pkg-node/index_bg.wasm | Bin 0 -> 162616 bytes scripts/health/pkg-node/index_bg.wasm.d.ts | 12 + scripts/health/pkg-node/package.json | 27 + scripts/health/pkg-web/index.d.ts | 43 + scripts/health/pkg-web/index.js | 517 +++++++ scripts/health/pkg-web/index_bg.wasm | Bin 0 -> 161692 bytes scripts/health/pkg-web/index_bg.wasm.d.ts | 12 + scripts/health/pkg-web/package.json | 28 + scripts/package.json | 11 +- scripts/tsconfig.json | 6 +- scripts/yarn.lock | 201 ++- 38 files changed, 3040 insertions(+), 28 deletions(-) create mode 100644 packages/health-computer/src/javascript.rs create mode 100644 scripts/health/DataFetcher.ts create mode 100644 scripts/health/example-node.ts create mode 100644 scripts/health/example-react/.gitignore create mode 100644 scripts/health/example-react/index.html create mode 100644 scripts/health/example-react/package.json create mode 100644 scripts/health/example-react/src/App.tsx create mode 100644 scripts/health/example-react/src/main.tsx create mode 100644 scripts/health/example-react/src/utils.ts create mode 100644 scripts/health/example-react/src/vite-env.d.ts create mode 100644 scripts/health/example-react/tsconfig.json create mode 100644 scripts/health/example-react/tsconfig.node.json create mode 100644 scripts/health/example-react/vite.config.ts create mode 100644 scripts/health/example-react/yarn.lock create mode 100644 scripts/health/pkg-node/index.d.ts create mode 100644 scripts/health/pkg-node/index.js create mode 100644 scripts/health/pkg-node/index_bg.wasm create mode 100644 scripts/health/pkg-node/index_bg.wasm.d.ts create mode 100644 scripts/health/pkg-node/package.json create mode 100644 scripts/health/pkg-web/index.d.ts create mode 100644 scripts/health/pkg-web/index.js create mode 100644 scripts/health/pkg-web/index_bg.wasm create mode 100644 scripts/health/pkg-web/index_bg.wasm.d.ts create mode 100644 scripts/health/pkg-web/package.json diff --git a/.github/workflows/scripts.yml b/.github/workflows/scripts.yml index f01588bb3..73f8f41ec 100644 --- a/.github/workflows/scripts.yml +++ b/.github/workflows/scripts.yml @@ -53,5 +53,12 @@ jobs: yarn generate-types git diff --exit-code + - name: Compile latest health computer helpers + run: yarn compile-health-all + - name: Check build run: yarn build + + - name: Check react health example builds + working-directory: ./scripts/health/example-react + run: yarn install && yarn build diff --git a/Cargo.lock b/Cargo.lock index 1bd3281ed..3014ece0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,6 +237,16 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-oid" version = "0.9.1" @@ -1212,6 +1222,7 @@ dependencies = [ name = "mars-rover-health-computer" version = "1.0.0" dependencies = [ + "console_error_panic_hook", "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vault-standard", diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index b87c122f3..72ad6604c 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -42,12 +42,12 @@ pub fn burn( token_id: String, ) -> Result { let config = CONFIG.load(deps.storage)?; - if config.health_contract_addr.is_none() { - return Err(HealthContractNotSet); - } + let Some(health_contract_addr) = config.health_contract_addr else { + return Err(HealthContractNotSet) + }; let response: HealthResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: config.health_contract_addr.unwrap().into(), + contract_addr: health_contract_addr.into(), msg: to_binary(&Health { account_id: token_id.clone(), })?, diff --git a/contracts/account-nft/tests/test_burn_allowance.rs b/contracts/account-nft/tests/test_burn_allowance.rs index 76fcee6ad..901f60c42 100644 --- a/contracts/account-nft/tests/test_burn_allowance.rs +++ b/contracts/account-nft/tests/test_burn_allowance.rs @@ -13,7 +13,7 @@ use crate::helpers::{below_max_for_burn, generate_health_response, MockEnv, MAX_ pub mod helpers; #[test] -fn burn_not_allowed_if_no_cm_set() { +fn burn_not_allowed_if_no_health_contract_set() { let mut mock = MockEnv::new().instantiate_with_health_contract(false).build().unwrap(); let user = Addr::unchecked("user"); let token_id = mock.mint(&user).unwrap(); diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 107bddab0..1cb2c42f3 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -30,10 +30,10 @@ cw-paginate = { workspace = true } cw-storage-plus = { workspace = true } mars-account-nft = { workspace = true } mars-math = { workspace = true } -mars-rover-health-types = { workspace = true } -mars-red-bank-types = { workspace = true } mars-owner = { workspace = true } +mars-red-bank-types = { workspace = true } mars-rover = { workspace = true } +mars-rover-health-types = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 326b181ed..1a7c6fe6a 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -8,8 +8,8 @@ use mars_rover_health_types::{is_below_one, HealthResponse}; use crate::state::HEALTH_CONTRACT; pub fn query_health(deps: Deps, account_id: &str) -> ContractResult { - let hm = HEALTH_CONTRACT.load(deps.storage)?; - Ok(hm.query_health(&deps.querier, account_id)?) + let hc = HEALTH_CONTRACT.load(deps.storage)?; + Ok(hc.query_health(&deps.querier, account_id)?) } pub fn assert_max_ltv( diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 9fe4c4433..3f176434d 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -14,8 +14,8 @@ use mars_rover::{ use crate::{ state::{ - ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, ORACLE, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, - TOTAL_LENT_SHARES, VAULT_CONFIGS, ZAPPER, + ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, HEALTH_CONTRACT, ORACLE, RED_BANK, SWAPPER, + TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, VAULT_CONFIGS, ZAPPER, }, update_coin_balances::query_balance, }; @@ -173,6 +173,7 @@ pub fn assert_not_contract_in_config(deps: &Deps, addr_to_flag: &Addr) -> Contra ORACLE.load(deps.storage)?.address().clone(), SWAPPER.load(deps.storage)?.address().clone(), ZAPPER.load(deps.storage)?.address().clone(), + HEALTH_CONTRACT.load(deps.storage)?.address().clone(), ]; let flagged_addr_in_config = diff --git a/contracts/credit-manager/tests/test_flagged_contract.rs b/contracts/credit-manager/tests/test_flagged_contract.rs index a1b5ef56d..fb4414a42 100644 --- a/contracts/credit-manager/tests/test_flagged_contract.rs +++ b/contracts/credit-manager/tests/test_flagged_contract.rs @@ -22,6 +22,7 @@ fn addresses_in_config_cannot_execute_msgs() { config.oracle, config.swapper, config.zapper, + config.health_contract, ] .into_iter() .chain(vault_addrs) diff --git a/packages/health-computer/Cargo.toml b/packages/health-computer/Cargo.toml index d64090086..0a375b7c5 100644 --- a/packages/health-computer/Cargo.toml +++ b/packages/health-computer/Cargo.toml @@ -2,31 +2,41 @@ name = "mars-rover-health-computer" version = { workspace = true } authors = { workspace = true } -license = { workspace = true } edition = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } documentation = { workspace = true } keywords = { workspace = true } +# Current bug in wasm-pack does not allow workspace inheritance for these properties. +# Revert to workspace inheritance when https://github.com/rustwasm/wasm-pack/pull/1185 +# has been included in the next wasm-pack version. +license = "GPL-3.0-or-later" +repository = "https://github.com/mars-protocol/rover" +homepage = "https://marsprotocol.io" + [lib] crate-type = ["cdylib", "rlib"] doctest = false [features] backtraces = ["cosmwasm-std/backtraces"] -default = [] +default = ["console_error_panic_hook"] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cosmwasm-vault-standard = { workspace = true } mars-math = { workspace = true } -mars-rover-health-types = { workspace = true } mars-red-bank-types = { workspace = true } mars-rover = { workspace = true } +mars-rover-health-types = { workspace = true } schemars = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } serde-wasm-bindgen = { workspace = true } wasm-bindgen = { workspace = true } + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.7", optional = true } diff --git a/packages/health-computer/src/javascript.rs b/packages/health-computer/src/javascript.rs new file mode 100644 index 000000000..454233d2e --- /dev/null +++ b/packages/health-computer/src/javascript.rs @@ -0,0 +1,23 @@ +use cosmwasm_schema::serde::{de::DeserializeOwned, Serialize}; +use mars_rover_health_types::HealthResponse; +use wasm_bindgen::prelude::*; + +use crate::HealthComputer; + +#[wasm_bindgen] +pub fn compute_health_js(val: JsValue) -> JsValue { + let c: HealthComputer = deserialize(val); + let health = c.compute_health().unwrap(); + let health_response: HealthResponse = health.into(); + serialize(health_response) +} + +pub fn serialize(val: T) -> JsValue { + serde_wasm_bindgen::to_value(&val).unwrap() +} + +pub fn deserialize(val: JsValue) -> T { + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); + serde_wasm_bindgen::from_value(val).unwrap() +} diff --git a/packages/health-computer/src/lib.rs b/packages/health-computer/src/lib.rs index 8bd9d95c1..ec8903836 100644 --- a/packages/health-computer/src/lib.rs +++ b/packages/health-computer/src/lib.rs @@ -1,4 +1,5 @@ mod data_types; mod health_computer; +mod javascript; -pub use self::{data_types::*, health_computer::*}; +pub use self::{data_types::*, health_computer::*, javascript::*}; diff --git a/scripts/codegen-tsconfig.json b/scripts/codegen-tsconfig.json index f27757e2e..2f467db9e 100644 --- a/scripts/codegen-tsconfig.json +++ b/scripts/codegen-tsconfig.json @@ -1,5 +1,5 @@ { "extends": "./tsconfig.json", "include": ["codegen"], - "exclude": ["deploy", "types", "utils", "build"] + "exclude": ["deploy", "types", "utils", "build", "health/example-react"] } diff --git a/scripts/health/DataFetcher.ts b/scripts/health/DataFetcher.ts new file mode 100644 index 000000000..157e1733e --- /dev/null +++ b/scripts/health/DataFetcher.ts @@ -0,0 +1,108 @@ +import { Positions } from '../types/generated/mars-credit-manager/MarsCreditManager.types' +import { MarsCreditManagerQueryClient } from '../types/generated/mars-credit-manager/MarsCreditManager.client' +import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient' +import { HealthResponse } from '../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' +import { + DenomsData, + HealthComputer, + VaultsData, +} from '../types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types' +import { MarsMockOracleQueryClient } from '../types/generated/mars-mock-oracle/MarsMockOracle.client' +import { MarsMockRedBankQueryClient } from '../types/generated/mars-mock-red-bank/MarsMockRedBank.client' +import { MarsMockVaultQueryClient } from '../types/generated/mars-mock-vault/MarsMockVault.client' + +export class DataFetcher { + constructor( + private healthComputer: (h: HealthComputer) => HealthResponse, + private creditManagerAddr: string, + private oracleAddr: string, + private redBankAddr: string, + private rpcEndpoint: string, + ) {} + + getClient = async (): Promise => { + return await CosmWasmClient.connect(this.rpcEndpoint) + } + + fetchPositions = async (accountId: string): Promise => { + const cmQuery = new MarsCreditManagerQueryClient(await this.getClient(), this.creditManagerAddr) + return await cmQuery.positions({ accountId }) + } + + fetchMarkets = async (denoms: string[]): Promise => { + const rQuery = new MarsMockRedBankQueryClient(await this.getClient(), this.redBankAddr) + const promises = denoms.map(async (denom) => await rQuery.market({ denom })) + const responses = await Promise.all(promises) + return responses.reduce((acc, curr) => { + acc[curr.denom] = curr + return acc + }, {} as DenomsData['markets']) + } + + fetchPrices = async (denoms: string[]): Promise => { + const oQuery = new MarsMockOracleQueryClient(await this.getClient(), this.oracleAddr) + const promises = denoms.map(async (denom) => await oQuery.price({ denom })) + const responses = await Promise.all(promises) + return responses.reduce((acc, curr) => { + acc[curr.denom] = curr.price + return acc + }, {} as DenomsData['prices']) + } + + fetchDenomsData = async (positions: Positions): Promise => { + const depositDenoms = positions.deposits.map((c) => c.denom) + const debtDenoms = positions.debts.map((c) => c.denom) + const vaultBaseTokenDenoms = await Promise.all( + positions.vaults.map(async (v) => { + const vQuery = new MarsMockVaultQueryClient(await this.getClient(), v.vault.address) + const info = await vQuery.info() + return info.base_token + }), + ) + + const allDenoms = depositDenoms.concat(debtDenoms).concat(vaultBaseTokenDenoms) + + return { + markets: await this.fetchMarkets(allDenoms), + prices: await this.fetchPrices(allDenoms), + } + } + + fetchAllowedCoins = async (): Promise => { + const cmQuery = new MarsCreditManagerQueryClient(await this.getClient(), this.creditManagerAddr) + return await cmQuery.allowedCoins({}) + } + + fetchVaultsData = async (positions: Positions): Promise => { + const vaultsData = { vault_values: {}, vault_configs: {} } as VaultsData + const cmQuery = new MarsCreditManagerQueryClient(await this.getClient(), this.creditManagerAddr) + await Promise.all( + positions.vaults.map(async (v) => { + const values = await cmQuery.vaultPositionValue({ vaultPosition: v }) + vaultsData.vault_values[v.vault.address] = values + + const info = await cmQuery.vaultInfo({ vault: v.vault }) + vaultsData.vault_configs[v.vault.address] = info.config + }), + ) + return vaultsData + } + + fetchHealth = async (accountId: string): Promise => { + const positions = await this.fetchPositions(accountId) + + const [denoms_data, vaults_data, allowed_coins] = await Promise.all([ + this.fetchDenomsData(positions), + this.fetchVaultsData(positions), + this.fetchAllowedCoins(), + ]) + + let data = { + positions, + denoms_data, + allowed_coins, + vaults_data, + } + return this.healthComputer(data) + } +} diff --git a/scripts/health/example-node.ts b/scripts/health/example-node.ts new file mode 100644 index 000000000..598c82504 --- /dev/null +++ b/scripts/health/example-node.ts @@ -0,0 +1,16 @@ +import { DataFetcher } from './DataFetcher' +import { Storage } from '../deploy/base/storage' +import { compute_health_js } from './pkg-node' +import { osmosisTestnetConfig } from '../deploy/osmosis/testnet-config' +;(async () => { + const storage = await Storage.load(osmosisTestnetConfig.chain.id, 'testnet-deployer-owner') + const dataFetcher = new DataFetcher( + compute_health_js, + storage.addresses.creditManager!, + osmosisTestnetConfig.oracle.addr, + osmosisTestnetConfig.redBank.addr, + osmosisTestnetConfig.chain.rpcEndpoint, + ) + const health = await dataFetcher.fetchHealth('9') + console.log(health) +})() diff --git a/scripts/health/example-react/.gitignore b/scripts/health/example-react/.gitignore new file mode 100644 index 000000000..fc5ae9f0c --- /dev/null +++ b/scripts/health/example-react/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +.vercel diff --git a/scripts/health/example-react/index.html b/scripts/health/example-react/index.html new file mode 100644 index 000000000..8ecd8575b --- /dev/null +++ b/scripts/health/example-react/index.html @@ -0,0 +1,12 @@ + + + + + + Health compute react example + + +

+ + + diff --git a/scripts/health/example-react/package.json b/scripts/health/example-react/package.json new file mode 100644 index 000000000..64216a555 --- /dev/null +++ b/scripts/health/example-react/package.json @@ -0,0 +1,27 @@ +{ + "name": "example-react", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-json-view": "^1.21.3", + "react-query": "^3.39.3", + "react-spinners": "^0.13.8" + }, + "devDependencies": { + "@types/react": "^18.0.27", + "@types/react-dom": "^18.0.10", + "@vitejs/plugin-react": "^3.1.0", + "typescript": "^4.9.3", + "vite": "^4.1.0", + "vite-plugin-top-level-await": "^1.2.3", + "vite-plugin-wasm": "^3.1.1" + } +} diff --git a/scripts/health/example-react/src/App.tsx b/scripts/health/example-react/src/App.tsx new file mode 100644 index 000000000..8899e89d0 --- /dev/null +++ b/scripts/health/example-react/src/App.tsx @@ -0,0 +1,64 @@ +import { useState } from 'react' +import { creditManager } from '../../../deploy/addresses/osmo-test-4.json' +import { useQuery } from 'react-query' +import { fetchHealth, fetchPositions } from './utils' +import ReactJson from 'react-json-view' +import { SyncLoader } from 'react-spinners' + +function App() { + const [cmAddr, setCmAddr] = useState(creditManager) + const [accountId, setAccountId] = useState('1') + + const postions = useQuery([cmAddr, accountId, 'positions'], () => + fetchPositions(cmAddr, accountId), + ) + + const health = useQuery([cmAddr, accountId, 'health'], () => fetchHealth(cmAddr, accountId)) + + return ( +
+
+ +
+
+ +
+
+ Positions:{' '} + {postions.isLoading ? ( + + ) : postions.error ? ( + 'error ❌' + ) : postions.data ? ( + + ) : undefined} +
+
+ Health:{' '} + {health.isLoading ? ( + + ) : health.error ? ( + 'error ❌' + ) : health.data ? ( + + ) : undefined} +
+
+ ) +} + +export default App diff --git a/scripts/health/example-react/src/main.tsx b/scripts/health/example-react/src/main.tsx new file mode 100644 index 000000000..c827865e1 --- /dev/null +++ b/scripts/health/example-react/src/main.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import { QueryClient, QueryClientProvider } from 'react-query' + +const queryClient = new QueryClient() + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + + + , +) diff --git a/scripts/health/example-react/src/utils.ts b/scripts/health/example-react/src/utils.ts new file mode 100644 index 000000000..4300e781a --- /dev/null +++ b/scripts/health/example-react/src/utils.ts @@ -0,0 +1,30 @@ +import { Positions } from '../../../types/generated/mars-credit-manager/MarsCreditManager.types' + +import init, { compute_health_js } from '../../pkg-web' +import { HealthResponse } from '../../../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' +import { DataFetcher } from '../../DataFetcher' +import { osmosisTestnetConfig } from '../../../deploy/osmosis/testnet-config' + +const getFetcher = (cmAddress: string) => { + return new DataFetcher( + compute_health_js, + cmAddress, + osmosisTestnetConfig.oracle.addr, + osmosisTestnetConfig.redBank.addr, + osmosisTestnetConfig.chain.rpcEndpoint, + ) +} + +export const fetchPositions = async (cmAddress: string, accountId: string): Promise => { + const dataFetcher = getFetcher(cmAddress) + return await dataFetcher.fetchPositions(accountId) +} + +export const fetchHealth = async ( + cmAddress: string, + accountId: string, +): Promise => { + await init() + const dataFetcher = getFetcher(cmAddress) + return await dataFetcher.fetchHealth(accountId) +} diff --git a/scripts/health/example-react/src/vite-env.d.ts b/scripts/health/example-react/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/scripts/health/example-react/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/scripts/health/example-react/tsconfig.json b/scripts/health/example-react/tsconfig.json new file mode 100644 index 000000000..3d0a51a86 --- /dev/null +++ b/scripts/health/example-react/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/scripts/health/example-react/tsconfig.node.json b/scripts/health/example-react/tsconfig.node.json new file mode 100644 index 000000000..9d31e2aed --- /dev/null +++ b/scripts/health/example-react/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/scripts/health/example-react/vite.config.ts b/scripts/health/example-react/vite.config.ts new file mode 100644 index 000000000..5e39ccbe5 --- /dev/null +++ b/scripts/health/example-react/vite.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import wasm from 'vite-plugin-wasm' +import topLevelAwait from 'vite-plugin-top-level-await' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), wasm(), topLevelAwait()], + + server: { + fs: { + // Allow serving files from one level up to the project root + allow: ['..'], + }, + }, +}) diff --git a/scripts/health/example-react/yarn.lock b/scripts/health/example-react/yarn.lock new file mode 100644 index 000000000..66d1a61d7 --- /dev/null +++ b/scripts/health/example-react/yarn.lock @@ -0,0 +1,1287 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.20.5": + version "7.20.14" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.14.tgz#4106fc8b755f3e3ee0a0a7c27dde5de1d2b2baf8" + integrity sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw== + +"@babel/core@^7.20.12": + version "7.20.12" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d" + integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.7" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helpers" "^7.20.7" + "@babel/parser" "^7.20.7" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.12" + "@babel/types" "^7.20.7" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.2" + semver "^6.3.0" + +"@babel/generator@^7.20.7": + version "7.20.14" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.14.tgz#9fa772c9f86a46c6ac9b321039400712b96f64ce" + integrity sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg== + dependencies: + "@babel/types" "^7.20.7" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb" + integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== + dependencies: + "@babel/compat-data" "^7.20.5" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" + lru-cache "^5.1.1" + semver "^6.3.0" + +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + +"@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.20.11": + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz#df4c7af713c557938c50ea3ad0117a7944b2f1b0" + integrity sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.10" + "@babel/types" "^7.20.7" + +"@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.19.0": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" + integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== + +"@babel/helper-simple-access@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" + integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== + dependencies: + "@babel/types" "^7.20.2" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helpers@^7.20.7": + version "7.20.13" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.13.tgz#e3cb731fb70dc5337134cadc24cbbad31cc87ad2" + integrity sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg== + dependencies: + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.13" + "@babel/types" "^7.20.7" + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.20.13", "@babel/parser@^7.20.7": + version "7.20.13" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.13.tgz#ddf1eb5a813588d2fb1692b70c6fce75b945c088" + integrity sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw== + +"@babel/plugin-transform-react-jsx-self@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz#3849401bab7ae8ffa1e3e5687c94a753fc75bda7" + integrity sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-react-jsx-source@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz#88578ae8331e5887e8ce28e4c9dc83fb29da0b86" + integrity sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + +"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2": + version "7.20.13" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b" + integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA== + dependencies: + regenerator-runtime "^0.13.11" + +"@babel/template@^7.18.10", "@babel/template@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" + integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + +"@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13": + version "7.20.13" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.13.tgz#817c1ba13d11accca89478bd5481b2d168d07473" + integrity sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.7" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.20.13" + "@babel/types" "^7.20.7" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.2", "@babel/types@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f" + integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + +"@esbuild/android-arm64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23" + integrity sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg== + +"@esbuild/android-arm@0.15.18": + version "0.15.18" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz#266d40b8fdcf87962df8af05b76219bc786b4f80" + integrity sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw== + +"@esbuild/android-arm@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz#025b6246d3f68b7bbaa97069144fb5fb70f2fff2" + integrity sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw== + +"@esbuild/android-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz#c820e0fef982f99a85c4b8bfdd582835f04cd96e" + integrity sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ== + +"@esbuild/darwin-arm64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz#edef4487af6b21afabba7be5132c26d22379b220" + integrity sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w== + +"@esbuild/darwin-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz#42829168730071c41ef0d028d8319eea0e2904b4" + integrity sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg== + +"@esbuild/freebsd-arm64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz#1f4af488bfc7e9ced04207034d398e793b570a27" + integrity sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw== + +"@esbuild/freebsd-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz#636306f19e9bc981e06aa1d777302dad8fddaf72" + integrity sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug== + +"@esbuild/linux-arm64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz#a003f7ff237c501e095d4f3a09e58fc7b25a4aca" + integrity sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g== + +"@esbuild/linux-arm@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz#b591e6a59d9c4fe0eeadd4874b157ab78cf5f196" + integrity sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ== + +"@esbuild/linux-ia32@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz#24333a11027ef46a18f57019450a5188918e2a54" + integrity sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg== + +"@esbuild/linux-loong64@0.15.18": + version "0.15.18" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz#128b76ecb9be48b60cf5cfc1c63a4f00691a3239" + integrity sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ== + +"@esbuild/linux-loong64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz#d5ad459d41ed42bbd4d005256b31882ec52227d8" + integrity sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ== + +"@esbuild/linux-mips64el@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz#4e5967a665c38360b0a8205594377d4dcf9c3726" + integrity sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw== + +"@esbuild/linux-ppc64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz#206443a02eb568f9fdf0b438fbd47d26e735afc8" + integrity sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g== + +"@esbuild/linux-riscv64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz#c351e433d009bf256e798ad048152c8d76da2fc9" + integrity sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw== + +"@esbuild/linux-s390x@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz#661f271e5d59615b84b6801d1c2123ad13d9bd87" + integrity sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w== + +"@esbuild/linux-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz#e4ba18e8b149a89c982351443a377c723762b85f" + integrity sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw== + +"@esbuild/netbsd-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz#7d4f4041e30c5c07dd24ffa295c73f06038ec775" + integrity sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA== + +"@esbuild/openbsd-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz#970fa7f8470681f3e6b1db0cc421a4af8060ec35" + integrity sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg== + +"@esbuild/sunos-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz#abc60e7c4abf8b89fb7a4fe69a1484132238022c" + integrity sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw== + +"@esbuild/win32-arm64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz#7b0ff9e8c3265537a7a7b1fd9a24e7bd39fcd87a" + integrity sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw== + +"@esbuild/win32-ia32@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz#e90fe5267d71a7b7567afdc403dfd198c292eb09" + integrity sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig== + +"@esbuild/win32-x64@0.16.17": + version "0.16.17" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091" + integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q== + +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + +"@swc/core-darwin-arm64@1.3.32": + version "1.3.32" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.32.tgz#841b0a244c2c75e67bb9d3cb665b2ede601e4e3a" + integrity sha512-o19bhlxuUgjUElm6i+QhXgZ0vD6BebiB/gQpK3en5aAwhOvinwr4sah3GqFXsQzz/prKVDuMkj9SW6F/Ug5hgg== + +"@swc/core-darwin-x64@1.3.32": + version "1.3.32" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.32.tgz#125247c6a5d7189776a6973b0a539a07576d5585" + integrity sha512-hVEGd+v5Afh+YekGADOGKwhuS4/AXk91nLuk7pmhWkk8ceQ1cfmah90kXjIXUlCe2G172MLRfHNWlZxr29E/Og== + +"@swc/core-linux-arm-gnueabihf@1.3.32": + version "1.3.32" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.32.tgz#d8ba8da3707b91e62059e65e375fc6093c4d5f82" + integrity sha512-5X01WqI9EbJ69oHAOGlI08YqvEIXMfT/mCJ1UWDQBb21xWRE2W1yFAAeuqOLtiagLrXjPv/UKQ0S2gyWQR5AXQ== + +"@swc/core-linux-arm64-gnu@1.3.32": + version "1.3.32" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.32.tgz#15973e533c45e2976e57c590613e3e5dca0e62ad" + integrity sha512-PTJ6oPiutkNBg+m22bUUPa4tNuMmsgpSnsnv2wnWVOgK0lhvQT6bAPTUXDq/8peVAgR/SlpP2Ht8TRRqYMRjRQ== + +"@swc/core-linux-arm64-musl@1.3.32": + version "1.3.32" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.32.tgz#0e20f30885c8588bd13e7e5628ce8bdbe6cb44b6" + integrity sha512-lG0VOuYNPWOCJ99Aza69cTljjeft/wuRQeYFF8d+1xCQS/OT7gnbgi7BOz39uSHIPTBqfzdIsuvzdKlp9QydrQ== + +"@swc/core-linux-x64-gnu@1.3.32": + version "1.3.32" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.32.tgz#2ea5e17d13a70e6a13742282cf30e92e4074f4c5" + integrity sha512-ecqtSWX4NBrs7Ji2VX3fDWeqUfrbLlYqBuufAziCM27xMxwlAVgmyGQk4FYgoQ3SAUAu3XFH87+3Q7uWm2X7xg== + +"@swc/core-linux-x64-musl@1.3.32": + version "1.3.32" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.32.tgz#d96cde03d54f13f77dd1d6602a049bd2baaef446" + integrity sha512-rl3dMcUuENVkpk5NGW/LXovjK0+JFm4GWPjy4NM3Q5cPvhBpGwSeLZlR+zAw9K0fdGoIXiayRTTfENrQwwsH+g== + +"@swc/core-win32-arm64-msvc@1.3.32": + version "1.3.32" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.32.tgz#3870113492cc3d2679945372f4975ce7f60594cc" + integrity sha512-VlybAZp8DcS66CH1LDnfp9zdwbPlnGXREtHDMHaBfK9+80AWVTg+zn0tCYz+HfcrRONqxbudwOUIPj+dwl/8jw== + +"@swc/core-win32-ia32-msvc@1.3.32": + version "1.3.32" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.32.tgz#643519663778e64941c8b730197d4ba762dffcb0" + integrity sha512-MEUMdpUFIQ+RD+K/iHhHKfu0TFNj9VXwIxT5hmPeqyboKo095CoFEFBJ0sHG04IGlnu8T9i+uE2Pi18qUEbFug== + +"@swc/core-win32-x64-msvc@1.3.32": + version "1.3.32" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.32.tgz#aa9595cb93d9dd5bbc7e5cfdabd8bb086ea6c21d" + integrity sha512-DPMoneNFQco7SqmVVOUv1Vn53YmoImEfrAPMY9KrqQzgfzqNTuL2JvfxUqfAxwQ6pEKYAdyKJvZ483rIhgG9XQ== + +"@swc/core@^1.3.10": + version "1.3.32" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.32.tgz#ecb3c9d7717e0a15796230def1b17968b18228f1" + integrity sha512-Yx/n1j+uUkcqlJAW8IRg8Qymgkdow6NHJZPFShiR0YiaYq2sXY+JHmvh16O6GkL91Y+gTlDUS7uVgDz50czJUQ== + optionalDependencies: + "@swc/core-darwin-arm64" "1.3.32" + "@swc/core-darwin-x64" "1.3.32" + "@swc/core-linux-arm-gnueabihf" "1.3.32" + "@swc/core-linux-arm64-gnu" "1.3.32" + "@swc/core-linux-arm64-musl" "1.3.32" + "@swc/core-linux-x64-gnu" "1.3.32" + "@swc/core-linux-x64-musl" "1.3.32" + "@swc/core-win32-arm64-msvc" "1.3.32" + "@swc/core-win32-ia32-msvc" "1.3.32" + "@swc/core-win32-x64-msvc" "1.3.32" + +"@types/prop-types@*": + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + +"@types/react-dom@^18.0.10": + version "18.0.10" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.10.tgz#3b66dec56aa0f16a6cc26da9e9ca96c35c0b4352" + integrity sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^18.0.27": + version "18.0.27" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.27.tgz#d9425abe187a00f8a5ec182b010d4fd9da703b71" + integrity sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + +"@vitejs/plugin-react@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-3.1.0.tgz#d1091f535eab8b83d6e74034d01e27d73c773240" + integrity sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g== + dependencies: + "@babel/core" "^7.20.12" + "@babel/plugin-transform-react-jsx-self" "^7.18.6" + "@babel/plugin-transform-react-jsx-source" "^7.19.6" + magic-string "^0.27.0" + react-refresh "^0.14.0" + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base16@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" + integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ== + +big-integer@^1.6.16: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +broadcast-channel@^3.4.1: + version "3.7.0" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937" + integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg== + dependencies: + "@babel/runtime" "^7.7.2" + detect-node "^2.1.0" + js-sha3 "0.8.0" + microseconds "0.2.0" + nano-time "1.0.0" + oblivious-set "1.0.0" + rimraf "3.0.2" + unload "2.2.0" + +browserslist@^4.21.3: + version "4.21.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" + integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== + dependencies: + caniuse-lite "^1.0.30001449" + electron-to-chromium "^1.4.284" + node-releases "^2.0.8" + update-browserslist-db "^1.0.10" + +caniuse-lite@^1.0.30001449: + version "1.0.30001450" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001450.tgz#022225b91200589196b814b51b1bbe45144cf74f" + integrity sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +cross-fetch@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + +csstype@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" + integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== + +debug@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +detect-node@^2.0.4, detect-node@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +electron-to-chromium@^1.4.284: + version "1.4.284" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" + integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== + +esbuild-android-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz#20a7ae1416c8eaade917fb2453c1259302c637a5" + integrity sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA== + +esbuild-android-arm64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz#9cc0ec60581d6ad267568f29cf4895ffdd9f2f04" + integrity sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ== + +esbuild-darwin-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz#428e1730ea819d500808f220fbc5207aea6d4410" + integrity sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg== + +esbuild-darwin-arm64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz#b6dfc7799115a2917f35970bfbc93ae50256b337" + integrity sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA== + +esbuild-freebsd-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz#4e190d9c2d1e67164619ae30a438be87d5eedaf2" + integrity sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA== + +esbuild-freebsd-arm64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz#18a4c0344ee23bd5a6d06d18c76e2fd6d3f91635" + integrity sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA== + +esbuild-linux-32@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz#9a329731ee079b12262b793fb84eea762e82e0ce" + integrity sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg== + +esbuild-linux-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz#532738075397b994467b514e524aeb520c191b6c" + integrity sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw== + +esbuild-linux-arm64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz#5372e7993ac2da8f06b2ba313710d722b7a86e5d" + integrity sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug== + +esbuild-linux-arm@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz#e734aaf259a2e3d109d4886c9e81ec0f2fd9a9cc" + integrity sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA== + +esbuild-linux-mips64le@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz#c0487c14a9371a84eb08fab0e1d7b045a77105eb" + integrity sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ== + +esbuild-linux-ppc64le@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz#af048ad94eed0ce32f6d5a873f7abe9115012507" + integrity sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w== + +esbuild-linux-riscv64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz#423ed4e5927bd77f842bd566972178f424d455e6" + integrity sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg== + +esbuild-linux-s390x@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz#21d21eaa962a183bfb76312e5a01cc5ae48ce8eb" + integrity sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ== + +esbuild-netbsd-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz#ae75682f60d08560b1fe9482bfe0173e5110b998" + integrity sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg== + +esbuild-openbsd-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz#79591a90aa3b03e4863f93beec0d2bab2853d0a8" + integrity sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ== + +esbuild-sunos-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz#fd528aa5da5374b7e1e93d36ef9b07c3dfed2971" + integrity sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw== + +esbuild-windows-32@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz#0e92b66ecdf5435a76813c4bc5ccda0696f4efc3" + integrity sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ== + +esbuild-windows-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz#0fc761d785414284fc408e7914226d33f82420d0" + integrity sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw== + +esbuild-windows-arm64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz#5b5bdc56d341d0922ee94965c89ee120a6a86eb7" + integrity sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ== + +esbuild@^0.15.9: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.18.tgz#ea894adaf3fbc036d32320a00d4d6e4978a2f36d" + integrity sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q== + optionalDependencies: + "@esbuild/android-arm" "0.15.18" + "@esbuild/linux-loong64" "0.15.18" + esbuild-android-64 "0.15.18" + esbuild-android-arm64 "0.15.18" + esbuild-darwin-64 "0.15.18" + esbuild-darwin-arm64 "0.15.18" + esbuild-freebsd-64 "0.15.18" + esbuild-freebsd-arm64 "0.15.18" + esbuild-linux-32 "0.15.18" + esbuild-linux-64 "0.15.18" + esbuild-linux-arm "0.15.18" + esbuild-linux-arm64 "0.15.18" + esbuild-linux-mips64le "0.15.18" + esbuild-linux-ppc64le "0.15.18" + esbuild-linux-riscv64 "0.15.18" + esbuild-linux-s390x "0.15.18" + esbuild-netbsd-64 "0.15.18" + esbuild-openbsd-64 "0.15.18" + esbuild-sunos-64 "0.15.18" + esbuild-windows-32 "0.15.18" + esbuild-windows-64 "0.15.18" + esbuild-windows-arm64 "0.15.18" + +esbuild@^0.16.14: + version "0.16.17" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259" + integrity sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg== + optionalDependencies: + "@esbuild/android-arm" "0.16.17" + "@esbuild/android-arm64" "0.16.17" + "@esbuild/android-x64" "0.16.17" + "@esbuild/darwin-arm64" "0.16.17" + "@esbuild/darwin-x64" "0.16.17" + "@esbuild/freebsd-arm64" "0.16.17" + "@esbuild/freebsd-x64" "0.16.17" + "@esbuild/linux-arm" "0.16.17" + "@esbuild/linux-arm64" "0.16.17" + "@esbuild/linux-ia32" "0.16.17" + "@esbuild/linux-loong64" "0.16.17" + "@esbuild/linux-mips64el" "0.16.17" + "@esbuild/linux-ppc64" "0.16.17" + "@esbuild/linux-riscv64" "0.16.17" + "@esbuild/linux-s390x" "0.16.17" + "@esbuild/linux-x64" "0.16.17" + "@esbuild/netbsd-x64" "0.16.17" + "@esbuild/openbsd-x64" "0.16.17" + "@esbuild/sunos-x64" "0.16.17" + "@esbuild/win32-arm64" "0.16.17" + "@esbuild/win32-ia32" "0.16.17" + "@esbuild/win32-x64" "0.16.17" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +fbemitter@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-3.0.0.tgz#00b2a1af5411254aab416cd75f9e6289bee4bff3" + integrity sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw== + dependencies: + fbjs "^3.0.0" + +fbjs-css-vars@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" + integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== + +fbjs@^3.0.0, fbjs@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6" + integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ== + dependencies: + cross-fetch "^3.1.5" + fbjs-css-vars "^1.0.0" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.30" + +flux@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.3.tgz#573b504a24982c4768fdfb59d8d2ea5637d72ee7" + integrity sha512-yKAbrp7JhZhj6uiT1FTuVMlIAT1J4jqEyBpFApi1kxpGZCvacMVc/t1pMQyotqHhAgvoE3bNvAykhCo2CLjnYw== + dependencies: + fbemitter "^3.0.0" + fbjs "^3.0.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== + dependencies: + has "^1.0.3" + +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +lodash.curry@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" + integrity sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA== + +lodash.flow@^3.3.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" + integrity sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw== + +loose-envify@^1.0.0, loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +magic-string@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3" + integrity sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.13" + +match-sorter@^6.0.2: + version "6.3.1" + resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda" + integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw== + dependencies: + "@babel/runtime" "^7.12.5" + remove-accents "0.4.2" + +microseconds@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" + integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nano-time@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" + integrity sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA== + dependencies: + big-integer "^1.6.16" + +nanoid@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +node-releases@^2.0.8: + version "2.0.9" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.9.tgz#fe66405285382b0c4ac6bcfbfbe7e8a510650b4d" + integrity sha512-2xfmOrRkGogbTK9R6Leda0DGiXeY3p2NJpy4+gNCffdUvV6mdEJnaDEic1i3Ec2djAo8jWYoJMR5PB0MSMpxUA== + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +oblivious-set@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" + integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +postcss@^8.4.18, postcss@^8.4.21: + version "8.4.21" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" + integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + +pure-color@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" + integrity sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA== + +react-base16-styling@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c" + integrity sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ== + dependencies: + base16 "^1.0.0" + lodash.curry "^4.0.1" + lodash.flow "^3.3.0" + pure-color "^1.2.0" + +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react-json-view@^1.21.3: + version "1.21.3" + resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475" + integrity sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw== + dependencies: + flux "^4.0.1" + react-base16-styling "^0.6.0" + react-lifecycles-compat "^3.0.4" + react-textarea-autosize "^8.3.2" + +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-query@^3.39.3: + version "3.39.3" + resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.3.tgz#4cea7127c6c26bdea2de5fb63e51044330b03f35" + integrity sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g== + dependencies: + "@babel/runtime" "^7.5.5" + broadcast-channel "^3.4.1" + match-sorter "^6.0.2" + +react-refresh@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" + integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== + +react-spinners@^0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.13.8.tgz#5262571be0f745d86bbd49a1e6b49f9f9cb19acc" + integrity sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA== + +react-textarea-autosize@^8.3.2: + version "8.4.0" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.4.0.tgz#4d0244d6a50caa897806b8c44abc0540a69bfc8c" + integrity sha512-YrTFaEHLgJsi8sJVYHBzYn+mkP3prGkmP2DKb/tm0t7CLJY5t1Rxix8070LAKb0wby7bl/lf2EeHkuMihMZMwQ== + dependencies: + "@babel/runtime" "^7.10.2" + use-composed-ref "^1.3.0" + use-latest "^1.2.1" + +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA== + +resolve@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +rimraf@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rollup@^2.79.1: + version "2.79.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" + integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== + optionalDependencies: + fsevents "~2.3.2" + +rollup@^3.10.0: + version "3.12.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.12.1.tgz#2975b97713e4af98c15e7024b88292d7fddb3853" + integrity sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig== + optionalDependencies: + fsevents "~2.3.2" + +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +typescript@^4.9.3: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + +ua-parser-js@^0.7.30: + version "0.7.33" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532" + integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw== + +unload@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7" + integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== + dependencies: + "@babel/runtime" "^7.6.2" + detect-node "^2.0.4" + +update-browserslist-db@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +use-composed-ref@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" + integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== + +use-isomorphic-layout-effect@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" + integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== + +use-latest@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2" + integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw== + dependencies: + use-isomorphic-layout-effect "^1.1.1" + +uuid@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" + integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== + +vite-plugin-top-level-await@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.2.3.tgz#6aa76335045acbca1d93e0c68a519c5d7fe4c759" + integrity sha512-Hc4W7R+Hw4413lzv+kuVdXLrgNU+Pb07853Q15n/4JytEtyLykpN2rmtlG7J5uWij2btDXEEiv+DcrCiRZo/zA== + dependencies: + "@swc/core" "^1.3.10" + uuid "^9.0.0" + +vite-plugin-wasm@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/vite-plugin-wasm/-/vite-plugin-wasm-3.1.1.tgz#6d5729e5cec959dae6e8f40891cec4d4f64d0270" + integrity sha512-m1Y2QKWVNRXOfVyT6E/VcaL1yugq1nqGgFMRUIUS8S3rstFjdMxClyzG65casCQo7O9bGcUB3NU0n/RBgDMqGQ== + dependencies: + vite "^3" + +vite@^3: + version "3.2.5" + resolved "https://registry.yarnpkg.com/vite/-/vite-3.2.5.tgz#dee5678172a8a0ab3e547ad4148c3d547f90e86a" + integrity sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ== + dependencies: + esbuild "^0.15.9" + postcss "^8.4.18" + resolve "^1.22.1" + rollup "^2.79.1" + optionalDependencies: + fsevents "~2.3.2" + +vite@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.1.1.tgz#3b18b81a4e85ce3df5cbdbf4c687d93ebf402e6b" + integrity sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg== + dependencies: + esbuild "^0.16.14" + postcss "^8.4.21" + resolve "^1.22.1" + rollup "^3.10.0" + optionalDependencies: + fsevents "~2.3.2" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== diff --git a/scripts/health/pkg-node/index.d.ts b/scripts/health/pkg-node/index.d.ts new file mode 100644 index 000000000..49113852c --- /dev/null +++ b/scripts/health/pkg-node/index.d.ts @@ -0,0 +1,7 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @param {any} val + * @returns {any} + */ +export function compute_health_js(val: any): any diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js new file mode 100644 index 000000000..0282aaaf8 --- /dev/null +++ b/scripts/health/pkg-node/index.js @@ -0,0 +1,484 @@ +let imports = {} +imports['__wbindgen_placeholder__'] = module.exports +let wasm +const { TextEncoder, TextDecoder } = require(`util`) + +const heap = new Array(128).fill(undefined) + +heap.push(undefined, null, true, false) + +function getObject(idx) { + return heap[idx] +} + +let heap_next = heap.length + +function dropObject(idx) { + if (idx < 132) return + heap[idx] = heap_next + heap_next = idx +} + +function takeObject(idx) { + const ret = getObject(idx) + dropObject(idx) + return ret +} + +let WASM_VECTOR_LEN = 0 + +let cachedUint8Memory0 = null + +function getUint8Memory0() { + if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer) + } + return cachedUint8Memory0 +} + +let cachedTextEncoder = new TextEncoder('utf-8') + +const encodeString = + typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view) + } + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg) + view.set(buf) + return { + read: arg.length, + written: buf.length, + } + } + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg) + const ptr = malloc(buf.length) + getUint8Memory0() + .subarray(ptr, ptr + buf.length) + .set(buf) + WASM_VECTOR_LEN = buf.length + return ptr + } + + let len = arg.length + let ptr = malloc(len) + + const mem = getUint8Memory0() + + let offset = 0 + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset) + if (code > 0x7f) break + mem[ptr + offset] = code + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset) + } + ptr = realloc(ptr, len, (len = offset + arg.length * 3)) + const view = getUint8Memory0().subarray(ptr + offset, ptr + len) + const ret = encodeString(arg, view) + + offset += ret.written + } + + WASM_VECTOR_LEN = offset + return ptr +} + +function isLikeNone(x) { + return x === undefined || x === null +} + +let cachedInt32Memory0 = null + +function getInt32Memory0() { + if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer) + } + return cachedInt32Memory0 +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1) + const idx = heap_next + heap_next = heap[idx] + + heap[idx] = obj + return idx +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) + +cachedTextDecoder.decode() + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)) +} + +let cachedFloat64Memory0 = null + +function getFloat64Memory0() { + if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) { + cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer) + } + return cachedFloat64Memory0 +} + +let cachedBigInt64Memory0 = null + +function getBigInt64Memory0() { + if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) { + cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer) + } + return cachedBigInt64Memory0 +} + +function debugString(val) { + // primitive types + const type = typeof val + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}` + } + if (type == 'string') { + return `"${val}"` + } + if (type == 'symbol') { + const description = val.description + if (description == null) { + return 'Symbol' + } else { + return `Symbol(${description})` + } + } + if (type == 'function') { + const name = val.name + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})` + } else { + return 'Function' + } + } + // objects + if (Array.isArray(val)) { + const length = val.length + let debug = '[' + if (length > 0) { + debug += debugString(val[0]) + } + for (let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]) + } + debug += ']' + return debug + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)) + let className + if (builtInMatches.length > 1) { + className = builtInMatches[1] + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val) + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')' + } catch (_) { + return 'Object' + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}` + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className +} +/** + * @param {any} val + * @returns {any} + */ +module.exports.compute_health_js = function (val) { + const ret = wasm.compute_health_js(addHeapObject(val)) + return takeObject(ret) +} + +function handleError(f, args) { + try { + return f.apply(this, args) + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)) + } +} + +module.exports.__wbindgen_object_drop_ref = function (arg0) { + takeObject(arg0) +} + +module.exports.__wbindgen_is_object = function (arg0) { + const val = getObject(arg0) + const ret = typeof val === 'object' && val !== null + return ret +} + +module.exports.__wbindgen_is_undefined = function (arg0) { + const ret = getObject(arg0) === undefined + return ret +} + +module.exports.__wbindgen_in = function (arg0, arg1) { + const ret = getObject(arg0) in getObject(arg1) + return ret +} + +module.exports.__wbindgen_string_get = function (arg0, arg1) { + const obj = getObject(arg1) + const ret = typeof obj === 'string' ? obj : undefined + var ptr0 = isLikeNone(ret) + ? 0 + : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + var len0 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len0 + getInt32Memory0()[arg0 / 4 + 0] = ptr0 +} + +module.exports.__wbindgen_is_bigint = function (arg0) { + const ret = typeof getObject(arg0) === 'bigint' + return ret +} + +module.exports.__wbindgen_bigint_from_u64 = function (arg0) { + const ret = BigInt.asUintN(64, arg0) + return addHeapObject(ret) +} + +module.exports.__wbindgen_jsval_eq = function (arg0, arg1) { + const ret = getObject(arg0) === getObject(arg1) + return ret +} + +module.exports.__wbindgen_error_new = function (arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)) + return addHeapObject(ret) +} + +module.exports.__wbindgen_boolean_get = function (arg0) { + const v = getObject(arg0) + const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 + return ret +} + +module.exports.__wbindgen_is_string = function (arg0) { + const ret = typeof getObject(arg0) === 'string' + return ret +} + +module.exports.__wbg_new_abda76e883ba8a5f = function () { + const ret = new Error() + return addHeapObject(ret) +} + +module.exports.__wbg_stack_658279fe44541cf6 = function (arg0, arg1) { + const ret = getObject(arg1).stack + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len0 + getInt32Memory0()[arg0 / 4 + 0] = ptr0 +} + +module.exports.__wbg_error_f851667af71bcfc6 = function (arg0, arg1) { + try { + console.error(getStringFromWasm0(arg0, arg1)) + } finally { + wasm.__wbindgen_free(arg0, arg1) + } +} + +module.exports.__wbindgen_jsval_loose_eq = function (arg0, arg1) { + const ret = getObject(arg0) == getObject(arg1) + return ret +} + +module.exports.__wbindgen_number_get = function (arg0, arg1) { + const obj = getObject(arg1) + const ret = typeof obj === 'number' ? obj : undefined + getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret + getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret) +} + +module.exports.__wbindgen_object_clone_ref = function (arg0) { + const ret = getObject(arg0) + return addHeapObject(ret) +} + +module.exports.__wbindgen_string_new = function (arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1) + return addHeapObject(ret) +} + +module.exports.__wbg_getwithrefkey_15c62c2b8546208d = function (arg0, arg1) { + const ret = getObject(arg0)[getObject(arg1)] + return addHeapObject(ret) +} + +module.exports.__wbg_set_20cbc34131e76824 = function (arg0, arg1, arg2) { + getObject(arg0)[takeObject(arg1)] = takeObject(arg2) +} + +module.exports.__wbg_get_27fe3dac1c4d0224 = function (arg0, arg1) { + const ret = getObject(arg0)[arg1 >>> 0] + return addHeapObject(ret) +} + +module.exports.__wbg_length_e498fbc24f9c1d4f = function (arg0) { + const ret = getObject(arg0).length + return ret +} + +module.exports.__wbindgen_is_function = function (arg0) { + const ret = typeof getObject(arg0) === 'function' + return ret +} + +module.exports.__wbg_next_b7d530c04fd8b217 = function (arg0) { + const ret = getObject(arg0).next + return addHeapObject(ret) +} + +module.exports.__wbg_next_88560ec06a094dea = function () { + return handleError(function (arg0) { + const ret = getObject(arg0).next() + return addHeapObject(ret) + }, arguments) +} + +module.exports.__wbg_done_1ebec03bbd919843 = function (arg0) { + const ret = getObject(arg0).done + return ret +} + +module.exports.__wbg_value_6ac8da5cc5b3efda = function (arg0) { + const ret = getObject(arg0).value + return addHeapObject(ret) +} + +module.exports.__wbg_iterator_55f114446221aa5a = function () { + const ret = Symbol.iterator + return addHeapObject(ret) +} + +module.exports.__wbg_get_baf4855f9a986186 = function () { + return handleError(function (arg0, arg1) { + const ret = Reflect.get(getObject(arg0), getObject(arg1)) + return addHeapObject(ret) + }, arguments) +} + +module.exports.__wbg_call_95d1ea488d03e4e8 = function () { + return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)) + return addHeapObject(ret) + }, arguments) +} + +module.exports.__wbg_new_f9876326328f45ed = function () { + const ret = new Object() + return addHeapObject(ret) +} + +module.exports.__wbg_isArray_39d28997bf6b96b4 = function (arg0) { + const ret = Array.isArray(getObject(arg0)) + return ret +} + +module.exports.__wbg_instanceof_ArrayBuffer_a69f02ee4c4f5065 = function (arg0) { + let result + try { + result = getObject(arg0) instanceof ArrayBuffer + } catch { + result = false + } + const ret = result + return ret +} + +module.exports.__wbg_isSafeInteger_8c4789029e885159 = function (arg0) { + const ret = Number.isSafeInteger(getObject(arg0)) + return ret +} + +module.exports.__wbg_entries_4e1315b774245952 = function (arg0) { + const ret = Object.entries(getObject(arg0)) + return addHeapObject(ret) +} + +module.exports.__wbg_buffer_cf65c07de34b9a08 = function (arg0) { + const ret = getObject(arg0).buffer + return addHeapObject(ret) +} + +module.exports.__wbg_new_537b7341ce90bb31 = function (arg0) { + const ret = new Uint8Array(getObject(arg0)) + return addHeapObject(ret) +} + +module.exports.__wbg_set_17499e8aa4003ebd = function (arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0) +} + +module.exports.__wbg_length_27a2afe8ab42b09f = function (arg0) { + const ret = getObject(arg0).length + return ret +} + +module.exports.__wbg_instanceof_Uint8Array_01cebe79ca606cca = function (arg0) { + let result + try { + result = getObject(arg0) instanceof Uint8Array + } catch { + result = false + } + const ret = result + return ret +} + +module.exports.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) { + const v = getObject(arg1) + const ret = typeof v === 'bigint' ? v : undefined + getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret + getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret) +} + +module.exports.__wbindgen_debug_string = function (arg0, arg1) { + const ret = debugString(getObject(arg1)) + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len0 + getInt32Memory0()[arg0 / 4 + 0] = ptr0 +} + +module.exports.__wbindgen_throw = function (arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)) +} + +module.exports.__wbindgen_memory = function () { + const ret = wasm.memory + return addHeapObject(ret) +} + +const path = require('path').join(__dirname, 'index_bg.wasm') +const bytes = require('fs').readFileSync(path) + +const wasmModule = new WebAssembly.Module(bytes) +const wasmInstance = new WebAssembly.Instance(wasmModule, imports) +wasm = wasmInstance.exports +module.exports.__wasm = wasm diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..05baa1527cfa1006ba508ae047c09fea21b32d35 GIT binary patch literal 162616 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

?1ZvLsAr^0DmWwCR0jUk< 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 kyzp=`HkUVO=DwusBF~#0TUTTWB>pF literal 0 HcmV?d00001 diff --git a/scripts/health/pkg-node/index_bg.wasm.d.ts b/scripts/health/pkg-node/index_bg.wasm.d.ts new file mode 100644 index 000000000..a16c4ff88 --- /dev/null +++ b/scripts/health/pkg-node/index_bg.wasm.d.ts @@ -0,0 +1,12 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory +export function compute_health_js(a: number): number +export function allocate(a: number): number +export function deallocate(a: number): void +export function requires_iterator(): void +export function interface_version_8(): void +export function __wbindgen_malloc(a: number): number +export function __wbindgen_realloc(a: number, b: number, c: number): number +export function __wbindgen_free(a: number, b: number): void +export function __wbindgen_exn_store(a: number): void diff --git a/scripts/health/pkg-node/package.json b/scripts/health/pkg-node/package.json new file mode 100644 index 000000000..cda469b01 --- /dev/null +++ b/scripts/health/pkg-node/package.json @@ -0,0 +1,27 @@ +{ + "name": "mars-rover-health-computer", + "collaborators": [ + "Gabe R. ", + "Larry Engineer ", + "Piotr Babel " + ], + "version": "1.0.0", + "license": "GPL-3.0-or-later", + "repository": { + "type": "git", + "url": "https://github.com/mars-protocol/rover" + }, + "files": [ + "index_bg.wasm", + "index.js", + "index.d.ts" + ], + "main": "index.js", + "homepage": "https://marsprotocol.io", + "types": "index.d.ts", + "keywords": [ + "mars", + "cosmos", + "cosmwasm" + ] +} diff --git a/scripts/health/pkg-web/index.d.ts b/scripts/health/pkg-web/index.d.ts new file mode 100644 index 000000000..b52017ac2 --- /dev/null +++ b/scripts/health/pkg-web/index.d.ts @@ -0,0 +1,43 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @param {any} val + * @returns {any} + */ +export function compute_health_js(val: any): any + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module + +export interface InitOutput { + readonly memory: WebAssembly.Memory + readonly compute_health_js: (a: number) => number + readonly allocate: (a: number) => number + readonly deallocate: (a: number) => void + readonly requires_iterator: () => void + readonly interface_version_8: () => void + readonly __wbindgen_malloc: (a: number) => number + readonly __wbindgen_realloc: (a: number, b: number, c: number) => number + readonly __wbindgen_free: (a: number, b: number) => void + readonly __wbindgen_exn_store: (a: number) => void +} + +export type SyncInitInput = BufferSource | WebAssembly.Module +/** + * Instantiates the given `module`, which can either be bytes or + * a precompiled `WebAssembly.Module`. + * + * @param {SyncInitInput} module + * + * @returns {InitOutput} + */ +export function initSync(module: SyncInitInput): InitOutput + +/** + * If `module_or_path` is {RequestInfo} or {URL}, makes a request and + * for everything else, calls `WebAssembly.instantiate` directly. + * + * @param {InitInput | Promise} module_or_path + * + * @returns {Promise} + */ +export default function init(module_or_path?: InitInput | Promise): Promise diff --git a/scripts/health/pkg-web/index.js b/scripts/health/pkg-web/index.js new file mode 100644 index 000000000..ee75177a2 --- /dev/null +++ b/scripts/health/pkg-web/index.js @@ -0,0 +1,517 @@ +let wasm + +const heap = new Array(128).fill(undefined) + +heap.push(undefined, null, true, false) + +function getObject(idx) { + return heap[idx] +} + +let heap_next = heap.length + +function dropObject(idx) { + if (idx < 132) return + heap[idx] = heap_next + heap_next = idx +} + +function takeObject(idx) { + const ret = getObject(idx) + dropObject(idx) + return ret +} + +let WASM_VECTOR_LEN = 0 + +let cachedUint8Memory0 = null + +function getUint8Memory0() { + if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer) + } + return cachedUint8Memory0 +} + +const cachedTextEncoder = new TextEncoder('utf-8') + +const encodeString = + typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view) + } + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg) + view.set(buf) + return { + read: arg.length, + written: buf.length, + } + } + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg) + const ptr = malloc(buf.length) + getUint8Memory0() + .subarray(ptr, ptr + buf.length) + .set(buf) + WASM_VECTOR_LEN = buf.length + return ptr + } + + let len = arg.length + let ptr = malloc(len) + + const mem = getUint8Memory0() + + let offset = 0 + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset) + if (code > 0x7f) break + mem[ptr + offset] = code + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset) + } + ptr = realloc(ptr, len, (len = offset + arg.length * 3)) + const view = getUint8Memory0().subarray(ptr + offset, ptr + len) + const ret = encodeString(arg, view) + + offset += ret.written + } + + WASM_VECTOR_LEN = offset + return ptr +} + +function isLikeNone(x) { + return x === undefined || x === null +} + +let cachedInt32Memory0 = null + +function getInt32Memory0() { + if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer) + } + return cachedInt32Memory0 +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1) + const idx = heap_next + heap_next = heap[idx] + + heap[idx] = obj + return idx +} + +const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) + +cachedTextDecoder.decode() + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)) +} + +let cachedFloat64Memory0 = null + +function getFloat64Memory0() { + if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) { + cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer) + } + return cachedFloat64Memory0 +} + +let cachedBigInt64Memory0 = null + +function getBigInt64Memory0() { + if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) { + cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer) + } + return cachedBigInt64Memory0 +} + +function debugString(val) { + // primitive types + const type = typeof val + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}` + } + if (type == 'string') { + return `"${val}"` + } + if (type == 'symbol') { + const description = val.description + if (description == null) { + return 'Symbol' + } else { + return `Symbol(${description})` + } + } + if (type == 'function') { + const name = val.name + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})` + } else { + return 'Function' + } + } + // objects + if (Array.isArray(val)) { + const length = val.length + let debug = '[' + if (length > 0) { + debug += debugString(val[0]) + } + for (let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]) + } + debug += ']' + return debug + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)) + let className + if (builtInMatches.length > 1) { + className = builtInMatches[1] + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val) + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')' + } catch (_) { + return 'Object' + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}` + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className +} +/** + * @param {any} val + * @returns {any} + */ +export function compute_health_js(val) { + const ret = wasm.compute_health_js(addHeapObject(val)) + return takeObject(ret) +} + +function handleError(f, args) { + try { + return f.apply(this, args) + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)) + } +} + +async function load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports) + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn( + '`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n', + e, + ) + } else { + throw e + } + } + } + + const bytes = await module.arrayBuffer() + return await WebAssembly.instantiate(bytes, imports) + } else { + const instance = await WebAssembly.instantiate(module, imports) + + if (instance instanceof WebAssembly.Instance) { + return { instance, module } + } else { + return instance + } + } +} + +function getImports() { + const imports = {} + imports.wbg = {} + imports.wbg.__wbindgen_object_drop_ref = function (arg0) { + takeObject(arg0) + } + imports.wbg.__wbindgen_is_object = function (arg0) { + const val = getObject(arg0) + const ret = typeof val === 'object' && val !== null + return ret + } + imports.wbg.__wbindgen_is_undefined = function (arg0) { + const ret = getObject(arg0) === undefined + return ret + } + imports.wbg.__wbindgen_in = function (arg0, arg1) { + const ret = getObject(arg0) in getObject(arg1) + return ret + } + imports.wbg.__wbindgen_string_get = function (arg0, arg1) { + const obj = getObject(arg1) + const ret = typeof obj === 'string' ? obj : undefined + var ptr0 = isLikeNone(ret) + ? 0 + : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + var len0 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len0 + getInt32Memory0()[arg0 / 4 + 0] = ptr0 + } + imports.wbg.__wbindgen_is_bigint = function (arg0) { + const ret = typeof getObject(arg0) === 'bigint' + return ret + } + imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) { + const ret = BigInt.asUintN(64, arg0) + return addHeapObject(ret) + } + imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) { + const ret = getObject(arg0) === getObject(arg1) + return ret + } + imports.wbg.__wbindgen_error_new = function (arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)) + return addHeapObject(ret) + } + imports.wbg.__wbindgen_boolean_get = function (arg0) { + const v = getObject(arg0) + const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 + return ret + } + imports.wbg.__wbindgen_is_string = function (arg0) { + const ret = typeof getObject(arg0) === 'string' + return ret + } + imports.wbg.__wbg_new_abda76e883ba8a5f = function () { + const ret = new Error() + return addHeapObject(ret) + } + imports.wbg.__wbg_stack_658279fe44541cf6 = function (arg0, arg1) { + const ret = getObject(arg1).stack + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len0 + getInt32Memory0()[arg0 / 4 + 0] = ptr0 + } + imports.wbg.__wbg_error_f851667af71bcfc6 = function (arg0, arg1) { + try { + console.error(getStringFromWasm0(arg0, arg1)) + } finally { + wasm.__wbindgen_free(arg0, arg1) + } + } + imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) { + const ret = getObject(arg0) == getObject(arg1) + return ret + } + imports.wbg.__wbindgen_number_get = function (arg0, arg1) { + const obj = getObject(arg1) + const ret = typeof obj === 'number' ? obj : undefined + getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret + getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret) + } + imports.wbg.__wbindgen_object_clone_ref = function (arg0) { + const ret = getObject(arg0) + return addHeapObject(ret) + } + imports.wbg.__wbindgen_string_new = function (arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1) + return addHeapObject(ret) + } + imports.wbg.__wbg_getwithrefkey_15c62c2b8546208d = function (arg0, arg1) { + const ret = getObject(arg0)[getObject(arg1)] + return addHeapObject(ret) + } + imports.wbg.__wbg_set_20cbc34131e76824 = function (arg0, arg1, arg2) { + getObject(arg0)[takeObject(arg1)] = takeObject(arg2) + } + imports.wbg.__wbg_get_27fe3dac1c4d0224 = function (arg0, arg1) { + const ret = getObject(arg0)[arg1 >>> 0] + return addHeapObject(ret) + } + imports.wbg.__wbg_length_e498fbc24f9c1d4f = function (arg0) { + const ret = getObject(arg0).length + return ret + } + imports.wbg.__wbindgen_is_function = function (arg0) { + const ret = typeof getObject(arg0) === 'function' + return ret + } + imports.wbg.__wbg_next_b7d530c04fd8b217 = function (arg0) { + const ret = getObject(arg0).next + return addHeapObject(ret) + } + imports.wbg.__wbg_next_88560ec06a094dea = function () { + return handleError(function (arg0) { + const ret = getObject(arg0).next() + return addHeapObject(ret) + }, arguments) + } + imports.wbg.__wbg_done_1ebec03bbd919843 = function (arg0) { + const ret = getObject(arg0).done + return ret + } + imports.wbg.__wbg_value_6ac8da5cc5b3efda = function (arg0) { + const ret = getObject(arg0).value + return addHeapObject(ret) + } + imports.wbg.__wbg_iterator_55f114446221aa5a = function () { + const ret = Symbol.iterator + return addHeapObject(ret) + } + imports.wbg.__wbg_get_baf4855f9a986186 = function () { + return handleError(function (arg0, arg1) { + const ret = Reflect.get(getObject(arg0), getObject(arg1)) + return addHeapObject(ret) + }, arguments) + } + imports.wbg.__wbg_call_95d1ea488d03e4e8 = function () { + return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)) + return addHeapObject(ret) + }, arguments) + } + imports.wbg.__wbg_new_f9876326328f45ed = function () { + const ret = new Object() + return addHeapObject(ret) + } + imports.wbg.__wbg_isArray_39d28997bf6b96b4 = function (arg0) { + const ret = Array.isArray(getObject(arg0)) + return ret + } + imports.wbg.__wbg_instanceof_ArrayBuffer_a69f02ee4c4f5065 = function (arg0) { + let result + try { + result = getObject(arg0) instanceof ArrayBuffer + } catch { + result = false + } + const ret = result + return ret + } + imports.wbg.__wbg_isSafeInteger_8c4789029e885159 = function (arg0) { + const ret = Number.isSafeInteger(getObject(arg0)) + return ret + } + imports.wbg.__wbg_entries_4e1315b774245952 = function (arg0) { + const ret = Object.entries(getObject(arg0)) + return addHeapObject(ret) + } + imports.wbg.__wbg_buffer_cf65c07de34b9a08 = function (arg0) { + const ret = getObject(arg0).buffer + return addHeapObject(ret) + } + imports.wbg.__wbg_new_537b7341ce90bb31 = function (arg0) { + const ret = new Uint8Array(getObject(arg0)) + return addHeapObject(ret) + } + imports.wbg.__wbg_set_17499e8aa4003ebd = function (arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0) + } + imports.wbg.__wbg_length_27a2afe8ab42b09f = function (arg0) { + const ret = getObject(arg0).length + return ret + } + imports.wbg.__wbg_instanceof_Uint8Array_01cebe79ca606cca = function (arg0) { + let result + try { + result = getObject(arg0) instanceof Uint8Array + } catch { + result = false + } + const ret = result + return ret + } + imports.wbg.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) { + const v = getObject(arg1) + const ret = typeof v === 'bigint' ? v : undefined + getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret + getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret) + } + imports.wbg.__wbindgen_debug_string = function (arg0, arg1) { + const ret = debugString(getObject(arg1)) + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len0 + getInt32Memory0()[arg0 / 4 + 0] = ptr0 + } + imports.wbg.__wbindgen_throw = function (arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)) + } + imports.wbg.__wbindgen_memory = function () { + const ret = wasm.memory + return addHeapObject(ret) + } + + return imports +} + +function initMemory(imports, maybe_memory) {} + +function finalizeInit(instance, module) { + wasm = instance.exports + init.__wbindgen_wasm_module = module + cachedBigInt64Memory0 = null + cachedFloat64Memory0 = null + cachedInt32Memory0 = null + cachedUint8Memory0 = null + + return wasm +} + +function initSync(module) { + const imports = getImports() + + initMemory(imports) + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module) + } + + const instance = new WebAssembly.Instance(module, imports) + + return finalizeInit(instance, module) +} + +async function init(input) { + if (typeof input === 'undefined') { + input = new URL('index_bg.wasm', import.meta.url) + } + const imports = getImports() + + if ( + typeof input === 'string' || + (typeof Request === 'function' && input instanceof Request) || + (typeof URL === 'function' && input instanceof URL) + ) { + input = fetch(input) + } + + initMemory(imports) + + const { instance, module } = await load(await input, imports) + + return finalizeInit(instance, module) +} + +export { initSync } +export default init diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8c8394ce00e0d6cf18765d8a3952a92d1109dfd0 GIT binary patch literal 161692 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�iEp`Jb6<1}v00**S+TP7cSH-1->vA_wZKkN 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 literal 0 HcmV?d00001 diff --git a/scripts/health/pkg-web/index_bg.wasm.d.ts b/scripts/health/pkg-web/index_bg.wasm.d.ts new file mode 100644 index 000000000..a16c4ff88 --- /dev/null +++ b/scripts/health/pkg-web/index_bg.wasm.d.ts @@ -0,0 +1,12 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory +export function compute_health_js(a: number): number +export function allocate(a: number): number +export function deallocate(a: number): void +export function requires_iterator(): void +export function interface_version_8(): void +export function __wbindgen_malloc(a: number): number +export function __wbindgen_realloc(a: number, b: number, c: number): number +export function __wbindgen_free(a: number, b: number): void +export function __wbindgen_exn_store(a: number): void diff --git a/scripts/health/pkg-web/package.json b/scripts/health/pkg-web/package.json new file mode 100644 index 000000000..5b4fb8ae0 --- /dev/null +++ b/scripts/health/pkg-web/package.json @@ -0,0 +1,28 @@ +{ + "name": "mars-rover-health-computer", + "collaborators": [ + "Gabe R. ", + "Larry Engineer ", + "Piotr Babel " + ], + "version": "1.0.0", + "license": "GPL-3.0-or-later", + "repository": { + "type": "git", + "url": "https://github.com/mars-protocol/rover" + }, + "files": [ + "index_bg.wasm", + "index.js", + "index.d.ts" + ], + "module": "index.js", + "homepage": "https://marsprotocol.io", + "types": "index.d.ts", + "sideEffects": false, + "keywords": [ + "mars", + "cosmos", + "cosmwasm" + ] +} diff --git a/scripts/package.json b/scripts/package.json index 27e9d7d1f..a237c28da 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -9,7 +9,12 @@ "generate-types": "yarn rust-schema && tsc --project codegen-tsconfig.json && rm -rf types/generated && node build/codegen && node build/codegen/insertIgnores.js && yarn format", "rust-schema": "cd ../ && cargo make generate-all-schemas && cd scripts", "compile-wasm": "cd ../ && cargo make rust-optimizer && cd scripts", - "build": "tsc", + "compile-health-base": "wasm-pack build ../packages/health-computer --out-name index --release", + "compile-health-web": "yarn run compile-health-base --target web --out-dir ../../scripts/health/pkg-web && rm health/pkg-web/.gitignore", + "compile-health-node": "yarn run compile-health-base --target nodejs --out-dir ../../scripts/health/pkg-node && rm health/pkg-node/.gitignore", + "compile-health-all": "yarn compile-health-node && yarn compile-health-web && yarn format", + "copy-wasm": "copyfiles health/**/*.wasm ./build", + "build": "tsc && yarn copy-wasm", "lint": "eslint . && yarn build && yarn format-check", "format": "prettier --write .", "format-check": "prettier --check ." @@ -19,10 +24,12 @@ "@cosmjs/stargate": "^0.29.5", "@cosmwasm/ts-codegen": "^0.24.0", "chalk": "4.1.2", + "copyfiles": "^2.4.1", "cosmjs-types": "^0.6.1", "lodash": "^4.17.21", "long": "^5.2.1", - "prepend-file": "^2.0.1" + "prepend-file": "^2.0.1", + "wasm-pack": "^0.10.3" }, "devDependencies": { "@babel/preset-env": "^7.20.2", diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index 408f9a5d6..e46512a16 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -25,8 +25,8 @@ "strictNullChecks": true, "suppressImplicitAnyIndexErrors": true, "allowSyntheticDefaultImports": true, - "skipLibCheck": true + "skipLibCheck": true, + "allowJs": true }, - "include": ["**/*.ts", "**/*.tsx", "types.d.ts"], - "exclude": ["node_modules", "./build/**/*", "types/generated/**/*"] + "exclude": ["node_modules", "./build/**/*", "types/generated/**/*", "health/example-react"] } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index c4e9c861a..a8147bdbb 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -2102,7 +2102,7 @@ ast-stringify@0.1.0: dependencies: "@babel/runtime" "^7.11.2" -axios@^0.21.2: +axios@^0.21.1, axios@^0.21.2: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== @@ -2216,6 +2216,15 @@ bech32@^1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== +binary-install@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/binary-install/-/binary-install-0.1.1.tgz#c1b22f174581764e5c52cd16664cf1d287e38bd4" + integrity sha512-DqED0D/6LrS+BHDkKn34vhRqOGjy5gTMgvYZsGK2TpNbdPuz4h+MRlNgGv5QBRd7pWq/jylM4eKNCizgAq3kNQ== + dependencies: + axios "^0.21.1" + rimraf "^3.0.2" + tar "^6.1.0" + bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" @@ -2348,6 +2357,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + ci-info@^3.2.0: version "3.8.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" @@ -2381,6 +2395,15 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -2444,6 +2467,19 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +copyfiles@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.4.1.tgz#d2dcff60aaad1015f09d0b66e7f0f1c5cd3c5da5" + integrity sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg== + dependencies: + glob "^7.0.5" + minimatch "^3.0.3" + mkdirp "^1.0.4" + noms "0.0.0" + through2 "^2.0.1" + untildify "^4.0.0" + yargs "^16.1.0" + core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.25.1: version "3.28.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.28.0.tgz#c08456d854608a7264530a2afa281fadf20ecee6" @@ -2451,6 +2487,11 @@ core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.25.1: dependencies: browserslist "^4.21.5" +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + cosmjs-types@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.5.2.tgz#2d42b354946f330dfb5c90a87fdc2a36f97b965d" @@ -2929,6 +2970,13 @@ follow-redirects@^1.14.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -3015,7 +3063,7 @@ glob@8.0.3: minimatch "^5.0.1" once "^1.3.0" -glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.0.0, glob@^7.0.5, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -3172,7 +3220,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3, inherits@^2.0.4: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3307,6 +3355,16 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -3981,7 +4039,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -4005,7 +4063,27 @@ minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -mkdirp@1.0.4, mkdirp@^1.0.4: +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.1.tgz#084031141113657662d40f66f9c2329036892128" + integrity sha512-KS4CHIsDfOZetnT+u6fwxyFADXLamtkPxkGScmmtTW//MlRrImV+LtbmbJpLQ86Hw7km/utbfEfndhGBrfwvlA== + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp@1.0.4, mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -4054,6 +4132,14 @@ node-releases@^2.0.8: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== +noms@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859" + integrity sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow== + dependencies: + inherits "^2.0.1" + readable-stream "~1.0.31" + normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -4247,6 +4333,11 @@ pretty-format@^29.0.0, pretty-format@^29.4.3: ansi-styles "^5.0.0" react-is "^18.0.0" +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -4289,6 +4380,29 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +readable-stream@~1.0.31: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readonly-date@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/readonly-date/-/readonly-date-1.0.0.tgz#5af785464d8c7d7c40b9d738cbde8c646f97dcd9" @@ -4436,6 +4550,11 @@ rxjs@^6.4.0: dependencies: tslib "^1.9.0" +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -4539,6 +4658,18 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -4618,6 +4749,18 @@ symbol-observable@^2.0.3: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-2.0.3.tgz#5b521d3d07a43c351055fa43b8355b62d33fd16a" integrity sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA== +tar@^6.1.0: + version "6.1.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" + integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^4.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" @@ -4662,6 +4805,14 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" +through2@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -4771,6 +4922,11 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== + update-browserslist-db@^1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" @@ -4786,6 +4942,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + util@^0.10.3: version "0.10.4" resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" @@ -4826,6 +4987,13 @@ wasm-ast-types@^0.17.0: case "1.6.3" deepmerge "4.2.2" +wasm-pack@^0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/wasm-pack/-/wasm-pack-0.10.3.tgz#2d7dd78ba539c34b3817e2249c3f30c646c84b69" + integrity sha512-dg1PPyp+QwWrhfHsgG12K/y5xzwfaAoK1yuVC/DUAuQsDy5JywWDuA7Y/ionGwQz+JBZVw8jknaKBnaxaJfwTA== + dependencies: + binary-install "^0.1.0" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -4873,6 +5041,11 @@ xstream@^11.14.0: globalthis "^1.0.1" symbol-observable "^2.0.3" +xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" @@ -4888,11 +5061,29 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== +yargs@^16.1.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + yargs@^17.3.1: version "17.7.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967" From 5a79c805a4dd7d15776e9b1cd2875a09e6fd896c Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 2 Mar 2023 17:48:36 +0100 Subject: [PATCH 144/218] Extract utilization to its own query (#119) --- contracts/credit-manager/src/contract.rs | 15 ++- contracts/credit-manager/src/query.rs | 36 +++--- .../credit-manager/tests/helpers/mock_env.rs | 24 +++- .../tests/test_update_config.rs | 5 +- .../tests/test_utilization_query.rs | 49 ++++---- ...ery_info.rs => test_vault_query_config.rs} | 1 - contracts/health/src/querier.rs | 6 +- contracts/health/tests/helpers/mock_env.rs | 6 +- contracts/mock-credit-manager/src/contract.rs | 6 +- contracts/mock-credit-manager/src/query.rs | 9 +- packages/rover/src/msg/query.rs | 23 ++-- .../mars-credit-manager.json | 111 +++++++++++++----- .../mars-mock-credit-manager.json | 111 +++++++++++++----- scripts/health/DataFetcher.ts | 4 +- .../MarsCreditManager.client.ts | 38 ++++-- .../MarsCreditManager.message-composer.ts | 5 +- .../MarsCreditManager.react-query.ts | 71 +++++++---- .../MarsCreditManager.types.ts | 18 ++- .../MarsMockCreditManager.client.ts | 38 ++++-- .../MarsMockCreditManager.message-composer.ts | 5 +- .../MarsMockCreditManager.react-query.ts | 75 ++++++++---- .../MarsMockCreditManager.types.ts | 18 ++- 22 files changed, 465 insertions(+), 209 deletions(-) rename contracts/credit-manager/tests/{test_vault_query_info.rs => test_vault_query_config.rs} (97%) diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index e632b46aa..ee8fe93c1 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -16,8 +16,8 @@ use crate::{ query_all_total_debt_shares, query_all_total_lent_shares, query_all_total_vault_coin_balances, query_all_vault_positions, query_allowed_coins, query_config, query_positions, query_total_debt_shares, query_total_lent_shares, - query_total_vault_coin_balance, query_vault_info, query_vault_position_value, - query_vaults_info, + query_total_vault_coin_balance, query_vault_config, query_vault_position_value, + query_vault_utilization, query_vaults_config, }, update_config::{update_config, update_nft_config, update_owner}, vault::handle_unlock_request_reply, @@ -75,13 +75,16 @@ pub fn reply(deps: DepsMut, _: Env, reply: Reply) -> ContractResult { pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { let res = match msg { QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::VaultInfo { + QueryMsg::VaultConfig { vault, - } => to_binary(&query_vault_info(deps, env, vault)?), - QueryMsg::VaultsInfo { + } => to_binary(&query_vault_config(deps, vault)?), + QueryMsg::VaultsConfig { start_after, limit, - } => to_binary(&query_vaults_info(deps, env, start_after, limit)?), + } => to_binary(&query_vaults_config(deps, start_after, limit)?), + QueryMsg::VaultUtilization { + vault, + } => to_binary(&query_vault_utilization(deps, env, vault)?), QueryMsg::AllowedCoins { start_after, limit, diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 3f98425d1..9c86ac950 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -6,8 +6,8 @@ use mars_rover::{ error::ContractResult, msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, LentAmount, LentShares, - Positions, SharesResponseItem, VaultInfoResponse, VaultPositionResponseItem, - VaultWithBalance, + Positions, SharesResponseItem, VaultConfigResponse, VaultPositionResponseItem, + VaultUtilizationResponse, VaultWithBalance, }, }; @@ -146,26 +146,23 @@ pub fn query_all_lent_shares( }) } -pub fn query_vault_info( +pub fn query_vault_config( deps: Deps, - env: Env, unchecked: VaultUnchecked, -) -> ContractResult { +) -> ContractResult { let vault = unchecked.check(deps.api)?; let config = VAULT_CONFIGS.load(deps.storage, &vault.address)?; - Ok(VaultInfoResponse { + Ok(VaultConfigResponse { config, - utilization: vault_utilization_in_deposit_cap_denom(&deps, &vault, &env.contract.address)?, vault: vault.into(), }) } -pub fn query_vaults_info( +pub fn query_vaults_config( deps: Deps, - env: Env, start_after: Option, limit: Option, -) -> ContractResult> { +) -> ContractResult> { let vault: Vault; let start = match &start_after { Some(unchecked) => { @@ -176,18 +173,25 @@ pub fn query_vaults_info( }; paginate_map(&VAULT_CONFIGS, deps.storage, start, limit, |addr, config| { let vault = VaultBase::new(addr); - Ok(VaultInfoResponse { + Ok(VaultConfigResponse { config, - utilization: vault_utilization_in_deposit_cap_denom( - &deps, - &vault, - &env.contract.address, - )?, vault: vault.into(), }) }) } +pub fn query_vault_utilization( + deps: Deps, + env: Env, + unchecked: VaultUnchecked, +) -> ContractResult { + let vault = unchecked.check(deps.api)?; + Ok(VaultUtilizationResponse { + vault: vault.clone().into(), + utilization: vault_utilization_in_deposit_cap_denom(&deps, &vault, &env.contract.address)?, + }) +} + pub fn query_vault_positions(deps: Deps, account_id: &str) -> ContractResult> { VAULT_POSITIONS .prefix(account_id) diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index abb6287e8..3a66360bf 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -45,8 +45,8 @@ use mars_rover::{ instantiate::{ConfigUpdates, VaultInstantiateConfig}, query::{ CoinBalanceResponseItem, ConfigResponse, DebtShares, LentShares, Positions, - SharesResponseItem, VaultInfoResponse as RoverVaultInfoResponse, - VaultPositionResponseItem, VaultWithBalance, + SharesResponseItem, VaultConfigResponse, VaultPositionResponseItem, + VaultUtilizationResponse, VaultWithBalance, }, zapper::{ InstantiateMsg as ZapperInstantiateMsg, LpConfig, QueryMsg::EstimateProvideLiquidity, @@ -284,10 +284,10 @@ impl MockEnv { .unwrap() } - pub fn query_vault_config(&self, vault: &VaultUnchecked) -> StdResult { + pub fn query_vault_config(&self, vault: &VaultUnchecked) -> StdResult { self.app.wrap().query_wasm_smart( self.rover.clone(), - &QueryMsg::VaultInfo { + &QueryMsg::VaultConfig { vault: vault.clone(), }, ) @@ -297,12 +297,12 @@ impl MockEnv { &self, start_after: Option, limit: Option, - ) -> Vec { + ) -> Vec { self.app .wrap() .query_wasm_smart( self.rover.clone(), - &QueryMsg::VaultsInfo { + &QueryMsg::VaultsConfig { start_after, limit, }, @@ -310,6 +310,18 @@ impl MockEnv { .unwrap() } + pub fn query_vault_utilization( + &self, + vault: &VaultUnchecked, + ) -> StdResult { + self.app.wrap().query_wasm_smart( + self.rover.clone(), + &QueryMsg::VaultUtilization { + vault: vault.clone(), + }, + ) + } + pub fn get_vault(&self, vault: &VaultTestInfo) -> VaultUnchecked { self.query_vault_configs(None, Some(30)) // Max limit .iter() diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 98a836c96..ac4f39413 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -13,7 +13,7 @@ use mars_rover::{ error::ContractError::InvalidConfig, msg::{ instantiate::{ConfigUpdates, VaultInstantiateConfig}, - query::VaultInfoResponse, + query::VaultConfigResponse, }, }; @@ -182,10 +182,9 @@ fn update_config_works_with_full_config() { new_queried_vault_configs, new_vault_configs .iter() - .map(|v| VaultInfoResponse { + .map(|v| VaultConfigResponse { vault: v.vault.clone(), config: v.config.clone(), - utilization: coin(0, "uusdc"), }) .collect::>() ); diff --git a/contracts/credit-manager/tests/test_utilization_query.rs b/contracts/credit-manager/tests/test_utilization_query.rs index ee396188e..075579c9a 100644 --- a/contracts/credit-manager/tests/test_utilization_query.rs +++ b/contracts/credit-manager/tests/test_utilization_query.rs @@ -1,8 +1,11 @@ use cosmwasm_std::{Addr, Decimal, Uint128}; -use mars_rover::msg::execute::{ - Action::{Deposit, EnterVault}, - ActionAmount::Exact, - ActionCoin, +use mars_rover::{ + adapters::vault::VaultUnchecked, + msg::execute::{ + Action::{Deposit, EnterVault}, + ActionAmount::Exact, + ActionCoin, + }, }; use crate::helpers::{ @@ -11,14 +14,22 @@ use crate::helpers::{ pub mod helpers; +#[test] +fn raises_if_vault_not_found() { + let mock = MockEnv::new().build().unwrap(); + mock.query_vault_utilization(&VaultUnchecked::new("some_vault".to_string())).unwrap_err(); +} + #[test] fn utilization_is_zero() { - let mock = MockEnv::new().vault_configs(&[unlocked_vault_info()]).build().unwrap(); - let vault_infos = mock.query_vault_configs(None, None); - assert_eq!(1, vault_infos.len()); - let vault = vault_infos.first().unwrap(); - assert_eq!(Uint128::zero(), vault.utilization.amount); - assert_eq!(vault.config.deposit_cap.denom, vault.utilization.denom); + let leverage_vault = unlocked_vault_info(); + let mock = MockEnv::new().vault_configs(&[leverage_vault.clone()]).build().unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let res = mock.query_vault_utilization(&vault).unwrap(); + + assert_eq!(Uint128::zero(), res.utilization.amount); + assert_eq!(leverage_vault.deposit_cap.denom, res.utilization.denom); } #[test] @@ -61,7 +72,7 @@ fn utilization_if_cap_is_base_denom() { vec![ Deposit(base_info.to_coin(50)), EnterVault { - vault, + vault: vault.clone(), coin: ActionCoin { denom: base_info.denom.clone(), amount: Exact(Uint128::new(50)), @@ -72,10 +83,9 @@ fn utilization_if_cap_is_base_denom() { ) .unwrap(); - let vault_infos = mock.query_vault_configs(None, None); - let vault = vault_infos.first().unwrap(); - assert_eq!(vault.config.deposit_cap.denom, vault.utilization.denom); - assert_eq!(Uint128::new(50), vault.utilization.amount); + let res = mock.query_vault_utilization(&vault).unwrap(); + assert_eq!(leverage_vault.deposit_cap.denom, res.utilization.denom); + assert_eq!(Uint128::new(50), res.utilization.amount); } /* @@ -120,7 +130,7 @@ fn utilization_in_other_denom() { vec![ Deposit(jake_info.to_coin(1_000_000)), EnterVault { - vault, + vault: vault.clone(), coin: ActionCoin { denom: jake_info.denom.clone(), amount: Exact(Uint128::new(1_000_000)), @@ -131,8 +141,7 @@ fn utilization_in_other_denom() { ) .unwrap(); - let vault_infos = mock.query_vault_configs(None, None); - let vault = vault_infos.first().unwrap(); - assert_eq!(vault.config.deposit_cap.denom, vault.utilization.denom); - assert_eq!(Uint128::new(9461600), vault.utilization.amount); + let res = mock.query_vault_utilization(&vault).unwrap(); + assert_eq!(leverage_vault.deposit_cap.denom, res.utilization.denom); + assert_eq!(Uint128::new(9461600), res.utilization.amount); } diff --git a/contracts/credit-manager/tests/test_vault_query_info.rs b/contracts/credit-manager/tests/test_vault_query_config.rs similarity index 97% rename from contracts/credit-manager/tests/test_vault_query_info.rs rename to contracts/credit-manager/tests/test_vault_query_config.rs index 3fdfa8858..1069b43bb 100644 --- a/contracts/credit-manager/tests/test_vault_query_info.rs +++ b/contracts/credit-manager/tests/test_vault_query_config.rs @@ -56,7 +56,6 @@ fn successfully_queries_with_utilization() { let res = mock.query_vault_config(&vault).unwrap(); assert_eq!(res.vault, vault); - assert!(!res.utilization.amount.is_zero()); assert_eq!(res.config.deposit_cap, leverage_vault.deposit_cap); assert_eq!(res.config.max_ltv, leverage_vault.max_ltv); assert_eq!(res.config.liquidation_threshold, leverage_vault.liquidation_threshold); diff --git a/contracts/health/src/querier.rs b/contracts/health/src/querier.rs index 7010f50fc..e99845527 100644 --- a/contracts/health/src/querier.rs +++ b/contracts/health/src/querier.rs @@ -5,7 +5,7 @@ use mars_rover::{ red_bank::RedBank, vault::{Vault, VaultConfig}, }, - msg::query::{ConfigResponse, Positions, QueryMsg, VaultInfoResponse}, + msg::query::{ConfigResponse, Positions, QueryMsg, VaultConfigResponse}, }; use mars_rover_health_types::HealthResult; @@ -53,9 +53,9 @@ impl<'a> HealthQuerier<'a> { } pub fn query_vault_config(&self, vault: &Vault) -> HealthResult { - let vault_info: VaultInfoResponse = self.querier.query_wasm_smart( + let vault_info: VaultConfigResponse = self.querier.query_wasm_smart( self.credit_manager_addr.to_string(), - &QueryMsg::VaultInfo { + &QueryMsg::VaultConfig { vault: vault.into(), }, )?; diff --git a/contracts/health/tests/helpers/mock_env.rs b/contracts/health/tests/helpers/mock_env.rs index b43fdecc5..a3f17252d 100644 --- a/contracts/health/tests/helpers/mock_env.rs +++ b/contracts/health/tests/helpers/mock_env.rs @@ -14,8 +14,8 @@ use mars_red_bank_types::red_bank::{ExecuteMsg::UpdateAsset, InitOrUpdateAssetPa use mars_rover::{ adapters::vault::VaultUnchecked, msg::{ - query::{Positions, VaultInfoResponse as CmVaultConfig}, - QueryMsg::VaultInfo, + query::{Positions, VaultConfigResponse as CmVaultConfig}, + QueryMsg::VaultConfig, }, }; use mars_rover_health_types::{ConfigResponse, ExecuteMsg::UpdateConfig, HealthResponse, QueryMsg}; @@ -68,7 +68,7 @@ impl MockEnv { .wrap() .query_wasm_smart( self.cm_contract.clone(), - &VaultInfo { + &VaultConfig { vault: vault.clone(), }, ) diff --git a/contracts/mock-credit-manager/src/contract.rs b/contracts/mock-credit-manager/src/contract.rs index 91183efa9..4de0b53b6 100644 --- a/contracts/mock-credit-manager/src/contract.rs +++ b/contracts/mock-credit-manager/src/contract.rs @@ -6,7 +6,7 @@ use mars_rover::msg::QueryMsg; use crate::{ execute::{set_allowed_coins, set_position_response, set_vault_config}, msg::{ExecuteMsg, InstantiateMsg}, - query::{query_allowed_coins, query_config, query_positions, query_vault_info}, + query::{query_allowed_coins, query_config, query_positions, query_vault_config}, state::{ALLOWED_COINS, CONFIG}, }; @@ -52,9 +52,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::AllowedCoins { .. } => to_binary(&query_allowed_coins(deps)?), - QueryMsg::VaultInfo { + QueryMsg::VaultConfig { vault, - } => to_binary(&query_vault_info(deps, vault)?), + } => to_binary(&query_vault_config(deps, vault)?), _ => unimplemented!("query msg not supported"), } } diff --git a/contracts/mock-credit-manager/src/query.rs b/contracts/mock-credit-manager/src/query.rs index fc27e2535..53da1d29a 100644 --- a/contracts/mock-credit-manager/src/query.rs +++ b/contracts/mock-credit-manager/src/query.rs @@ -1,7 +1,7 @@ -use cosmwasm_std::{coin, Deps, StdResult}; +use cosmwasm_std::{Deps, StdResult}; use mars_rover::{ adapters::vault::VaultUnchecked, - msg::query::{ConfigResponse, Positions, VaultInfoResponse}, + msg::query::{ConfigResponse, Positions, VaultConfigResponse}, }; use crate::state::{ALLOWED_COINS, CONFIG, POSITION_RESPONSES, VAULT_CONFIGS}; @@ -18,12 +18,11 @@ pub fn query_allowed_coins(deps: Deps) -> StdResult> { ALLOWED_COINS.load(deps.storage) } -pub fn query_vault_info(deps: Deps, vault: VaultUnchecked) -> StdResult { +pub fn query_vault_config(deps: Deps, vault: VaultUnchecked) -> StdResult { let validated = deps.api.addr_validate(&vault.address)?; let config = VAULT_CONFIGS.load(deps.storage, &validated)?; - Ok(VaultInfoResponse { + Ok(VaultConfigResponse { config, - utilization: coin(1000000, "uusdc"), vault: VaultUnchecked::new(validated.to_string()), }) } diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 7da340fb2..a9e91edd1 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -13,16 +13,22 @@ pub enum QueryMsg { #[returns(ConfigResponse)] Config {}, /// Config & deposit caps on vault - #[returns(VaultInfoResponse)] - VaultInfo { + #[returns(VaultConfigResponse)] + VaultConfig { vault: VaultUnchecked, }, /// Configs & deposit caps on all vaults - #[returns(Vec)] - VaultsInfo { + #[returns(Vec)] + VaultsConfig { start_after: Option, limit: Option, }, + /// The amount the vault has been utilized, + /// denominated in the same denom set in the vault config's deposit cap + #[returns(VaultUtilizationResponse)] + VaultUtilization { + vault: VaultUnchecked, + }, /// Whitelisted coins #[returns(Vec)] AllowedCoins { @@ -108,11 +114,14 @@ pub enum QueryMsg { } #[cw_serde] -pub struct VaultInfoResponse { +pub struct VaultConfigResponse { pub vault: VaultUnchecked, pub config: VaultConfig, - /// The amount the vault has been utilized, - /// denominated in the same denom set in the vault config's deposit cap +} + +#[cw_serde] +pub struct VaultUtilizationResponse { + pub vault: VaultUnchecked, pub utilization: Coin, } diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 5a51715ba..3ee613aa1 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -1517,10 +1517,10 @@ "description": "Config & deposit caps on vault", "type": "object", "required": [ - "vault_info" + "vault_config" ], "properties": { - "vault_info": { + "vault_config": { "type": "object", "required": [ "vault" @@ -1539,10 +1539,10 @@ "description": "Configs & deposit caps on all vaults", "type": "object", "required": [ - "vaults_info" + "vaults_config" ], "properties": { - "vaults_info": { + "vaults_config": { "type": "object", "properties": { "limit": { @@ -1569,6 +1569,28 @@ }, "additionalProperties": false }, + { + "description": "The amount the vault has been utilized, denominated in the same denom set in the vault config's deposit cap", + "type": "object", + "required": [ + "vault_utilization" + ], + "properties": { + "vault_utilization": { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Whitelisted coins", "type": "object", @@ -2900,27 +2922,18 @@ "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" }, - "vault_info": { + "vault_config": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VaultInfoResponse", + "title": "VaultConfigResponse", "type": "object", "required": [ "config", - "utilization", "vault" ], "properties": { "config": { "$ref": "#/definitions/VaultConfig" }, - "utilization": { - "description": "The amount the vault has been utilized, denominated in the same denom set in the vault config's deposit cap", - "allOf": [ - { - "$ref": "#/definitions/Coin" - } - ] - }, "vault": { "$ref": "#/definitions/VaultBase_for_String" } @@ -3042,12 +3055,63 @@ } } }, - "vaults_info": { + "vault_utilization": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_VaultInfoResponse", + "title": "VaultUtilizationResponse", + "type": "object", + "required": [ + "utilization", + "vault" + ], + "properties": { + "utilization": { + "$ref": "#/definitions/Coin" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VaultBase_for_String": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "vaults_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_VaultConfigResponse", "type": "array", "items": { - "$ref": "#/definitions/VaultInfoResponse" + "$ref": "#/definitions/VaultConfigResponse" }, "definitions": { "Coin": { @@ -3109,25 +3173,16 @@ }, "additionalProperties": false }, - "VaultInfoResponse": { + "VaultConfigResponse": { "type": "object", "required": [ "config", - "utilization", "vault" ], "properties": { "config": { "$ref": "#/definitions/VaultConfig" }, - "utilization": { - "description": "The amount the vault has been utilized, denominated in the same denom set in the vault config's deposit cap", - "allOf": [ - { - "$ref": "#/definitions/Coin" - } - ] - }, "vault": { "$ref": "#/definitions/VaultBase_for_String" } diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 17de152c1..3860e0a93 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -359,10 +359,10 @@ "description": "Config & deposit caps on vault", "type": "object", "required": [ - "vault_info" + "vault_config" ], "properties": { - "vault_info": { + "vault_config": { "type": "object", "required": [ "vault" @@ -381,10 +381,10 @@ "description": "Configs & deposit caps on all vaults", "type": "object", "required": [ - "vaults_info" + "vaults_config" ], "properties": { - "vaults_info": { + "vaults_config": { "type": "object", "properties": { "limit": { @@ -411,6 +411,28 @@ }, "additionalProperties": false }, + { + "description": "The amount the vault has been utilized, denominated in the same denom set in the vault config's deposit cap", + "type": "object", + "required": [ + "vault_utilization" + ], + "properties": { + "vault_utilization": { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Whitelisted coins", "type": "object", @@ -1742,27 +1764,18 @@ "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" }, - "vault_info": { + "vault_config": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VaultInfoResponse", + "title": "VaultConfigResponse", "type": "object", "required": [ "config", - "utilization", "vault" ], "properties": { "config": { "$ref": "#/definitions/VaultConfig" }, - "utilization": { - "description": "The amount the vault has been utilized, denominated in the same denom set in the vault config's deposit cap", - "allOf": [ - { - "$ref": "#/definitions/Coin" - } - ] - }, "vault": { "$ref": "#/definitions/VaultBase_for_String" } @@ -1884,12 +1897,63 @@ } } }, - "vaults_info": { + "vault_utilization": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_VaultInfoResponse", + "title": "VaultUtilizationResponse", + "type": "object", + "required": [ + "utilization", + "vault" + ], + "properties": { + "utilization": { + "$ref": "#/definitions/Coin" + }, + "vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VaultBase_for_String": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "vaults_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_VaultConfigResponse", "type": "array", "items": { - "$ref": "#/definitions/VaultInfoResponse" + "$ref": "#/definitions/VaultConfigResponse" }, "definitions": { "Coin": { @@ -1951,25 +2015,16 @@ }, "additionalProperties": false }, - "VaultInfoResponse": { + "VaultConfigResponse": { "type": "object", "required": [ "config", - "utilization", "vault" ], "properties": { "config": { "$ref": "#/definitions/VaultConfig" }, - "utilization": { - "description": "The amount the vault has been utilized, denominated in the same denom set in the vault config's deposit cap", - "allOf": [ - { - "$ref": "#/definitions/Coin" - } - ] - }, "vault": { "$ref": "#/definitions/VaultBase_for_String" } diff --git a/scripts/health/DataFetcher.ts b/scripts/health/DataFetcher.ts index 157e1733e..bc16fea89 100644 --- a/scripts/health/DataFetcher.ts +++ b/scripts/health/DataFetcher.ts @@ -81,8 +81,8 @@ export class DataFetcher { const values = await cmQuery.vaultPositionValue({ vaultPosition: v }) vaultsData.vault_values[v.vault.address] = values - const info = await cmQuery.vaultInfo({ vault: v.vault }) - vaultsData.vault_configs[v.vault.address] = info.config + const res = await cmQuery.vaultConfig({ vault: v.vault }) + vaultsData.vault_configs[v.vault.address] = res.config }), ) return vaultsData diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index c47b53b6f..2925331d0 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -57,22 +57,24 @@ import { Positions, DebtAmount, LentAmount, - VaultInfoResponse, + VaultConfigResponse, VaultPositionValue, CoinValue, - ArrayOfVaultInfoResponse, + VaultUtilizationResponse, + ArrayOfVaultConfigResponse, } from './MarsCreditManager.types' export interface MarsCreditManagerReadOnlyInterface { contractAddress: string config: () => Promise - vaultInfo: ({ vault }: { vault: VaultBaseForString }) => Promise - vaultsInfo: ({ + vaultConfig: ({ vault }: { vault: VaultBaseForString }) => Promise + vaultsConfig: ({ limit, startAfter, }: { limit?: number startAfter?: VaultBaseForString - }) => Promise + }) => Promise + vaultUtilization: ({ vault }: { vault: VaultBaseForString }) => Promise allowedCoins: ({ limit, startAfter, @@ -155,8 +157,9 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn this.client = client this.contractAddress = contractAddress this.config = this.config.bind(this) - this.vaultInfo = this.vaultInfo.bind(this) - this.vaultsInfo = this.vaultsInfo.bind(this) + this.vaultConfig = this.vaultConfig.bind(this) + this.vaultsConfig = this.vaultsConfig.bind(this) + this.vaultUtilization = this.vaultUtilization.bind(this) this.allowedCoins = this.allowedCoins.bind(this) this.positions = this.positions.bind(this) this.allCoinBalances = this.allCoinBalances.bind(this) @@ -179,27 +182,38 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn config: {}, }) } - vaultInfo = async ({ vault }: { vault: VaultBaseForString }): Promise => { + vaultConfig = async ({ vault }: { vault: VaultBaseForString }): Promise => { return this.client.queryContractSmart(this.contractAddress, { - vault_info: { + vault_config: { vault, }, }) } - vaultsInfo = async ({ + vaultsConfig = async ({ limit, startAfter, }: { limit?: number startAfter?: VaultBaseForString - }): Promise => { + }): Promise => { return this.client.queryContractSmart(this.contractAddress, { - vaults_info: { + vaults_config: { limit, start_after: startAfter, }, }) } + vaultUtilization = async ({ + vault, + }: { + vault: VaultBaseForString + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + vault_utilization: { + vault, + }, + }) + } allowedCoins = async ({ limit, startAfter, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 4cd0c2261..03d649f32 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -58,10 +58,11 @@ import { Positions, DebtAmount, LentAmount, - VaultInfoResponse, + VaultConfigResponse, VaultPositionValue, CoinValue, - ArrayOfVaultInfoResponse, + VaultUtilizationResponse, + ArrayOfVaultConfigResponse, } from './MarsCreditManager.types' export interface MarsCreditManagerMessage { contractAddress: string diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 9aa8863be..7f730608d 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -58,10 +58,11 @@ import { Positions, DebtAmount, LentAmount, - VaultInfoResponse, + VaultConfigResponse, VaultPositionValue, CoinValue, - ArrayOfVaultInfoResponse, + VaultUtilizationResponse, + ArrayOfVaultConfigResponse, } from './MarsCreditManager.types' import { MarsCreditManagerQueryClient, MarsCreditManagerClient } from './MarsCreditManager.client' export const marsCreditManagerQueryKeys = { @@ -76,13 +77,21 @@ export const marsCreditManagerQueryKeys = { [ { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, ] as const, - vaultInfo: (contractAddress: string | undefined, args?: Record) => + vaultConfig: (contractAddress: string | undefined, args?: Record) => [ - { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'vault_info', args }, + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'vault_config', args }, ] as const, - vaultsInfo: (contractAddress: string | undefined, args?: Record) => + vaultsConfig: (contractAddress: string | undefined, args?: Record) => [ - { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'vaults_info', args }, + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'vaults_config', args }, + ] as const, + vaultUtilization: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsCreditManagerQueryKeys.address(contractAddress)[0], + method: 'vault_utilization', + args, + }, ] as const, allowedCoins: (contractAddress: string | undefined, args?: Record) => [ @@ -536,23 +545,45 @@ export function useMarsCreditManagerAllowedCoinsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsCreditManagerVaultsInfoQuery - extends MarsCreditManagerReactQuery { +export interface MarsCreditManagerVaultUtilizationQuery + extends MarsCreditManagerReactQuery { + args: { + vault: VaultBaseForString + } +} +export function useMarsCreditManagerVaultUtilizationQuery({ + client, + args, + options, +}: MarsCreditManagerVaultUtilizationQuery) { + return useQuery( + marsCreditManagerQueryKeys.vaultUtilization(client?.contractAddress, args), + () => + client + ? client.vaultUtilization({ + vault: args.vault, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsCreditManagerVaultsConfigQuery + extends MarsCreditManagerReactQuery { args: { limit?: number startAfter?: VaultBaseForString } } -export function useMarsCreditManagerVaultsInfoQuery({ +export function useMarsCreditManagerVaultsConfigQuery({ client, args, options, -}: MarsCreditManagerVaultsInfoQuery) { - return useQuery( - marsCreditManagerQueryKeys.vaultsInfo(client?.contractAddress, args), +}: MarsCreditManagerVaultsConfigQuery) { + return useQuery( + marsCreditManagerQueryKeys.vaultsConfig(client?.contractAddress, args), () => client - ? client.vaultsInfo({ + ? client.vaultsConfig({ limit: args.limit, startAfter: args.startAfter, }) @@ -560,22 +591,22 @@ export function useMarsCreditManagerVaultsInfoQuery - extends MarsCreditManagerReactQuery { +export interface MarsCreditManagerVaultConfigQuery + extends MarsCreditManagerReactQuery { args: { vault: VaultBaseForString } } -export function useMarsCreditManagerVaultInfoQuery({ +export function useMarsCreditManagerVaultConfigQuery({ client, args, options, -}: MarsCreditManagerVaultInfoQuery) { - return useQuery( - marsCreditManagerQueryKeys.vaultInfo(client?.contractAddress, args), +}: MarsCreditManagerVaultConfigQuery) { + return useQuery( + marsCreditManagerQueryKeys.vaultConfig(client?.contractAddress, args), () => client - ? client.vaultInfo({ + ? client.vaultConfig({ vault: args.vault, }) : Promise.reject(new Error('Invalid client')), diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index db115e1c0..c71c09ca3 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -306,16 +306,21 @@ export type QueryMsg = config: {} } | { - vault_info: { + vault_config: { vault: VaultBaseForString } } | { - vaults_info: { + vaults_config: { limit?: number | null start_after?: VaultBaseForString | null } } + | { + vault_utilization: { + vault: VaultBaseForString + } + } | { allowed_coins: { limit?: number | null @@ -481,9 +486,8 @@ export interface LentAmount { denom: string shares: Uint128 } -export interface VaultInfoResponse { +export interface VaultConfigResponse { config: VaultConfig - utilization: Coin vault: VaultBaseForString } export interface VaultPositionValue { @@ -495,4 +499,8 @@ export interface CoinValue { denom: string value: Uint128 } -export type ArrayOfVaultInfoResponse = VaultInfoResponse[] +export interface VaultUtilizationResponse { + utilization: Coin + vault: VaultBaseForString +} +export type ArrayOfVaultConfigResponse = VaultConfigResponse[] diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index 48d6f5495..1888a90ca 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -43,22 +43,24 @@ import { ArrayOfString, ConfigResponse, ArrayOfCoin, - VaultInfoResponse, + VaultConfigResponse, VaultPositionValue, CoinValue, - ArrayOfVaultInfoResponse, + VaultUtilizationResponse, + ArrayOfVaultConfigResponse, } from './MarsMockCreditManager.types' export interface MarsMockCreditManagerReadOnlyInterface { contractAddress: string config: () => Promise - vaultInfo: ({ vault }: { vault: VaultBaseForString }) => Promise - vaultsInfo: ({ + vaultConfig: ({ vault }: { vault: VaultBaseForString }) => Promise + vaultsConfig: ({ limit, startAfter, }: { limit?: number startAfter?: VaultBaseForString - }) => Promise + }) => Promise + vaultUtilization: ({ vault }: { vault: VaultBaseForString }) => Promise allowedCoins: ({ limit, startAfter, @@ -141,8 +143,9 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe this.client = client this.contractAddress = contractAddress this.config = this.config.bind(this) - this.vaultInfo = this.vaultInfo.bind(this) - this.vaultsInfo = this.vaultsInfo.bind(this) + this.vaultConfig = this.vaultConfig.bind(this) + this.vaultsConfig = this.vaultsConfig.bind(this) + this.vaultUtilization = this.vaultUtilization.bind(this) this.allowedCoins = this.allowedCoins.bind(this) this.positions = this.positions.bind(this) this.allCoinBalances = this.allCoinBalances.bind(this) @@ -165,27 +168,38 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe config: {}, }) } - vaultInfo = async ({ vault }: { vault: VaultBaseForString }): Promise => { + vaultConfig = async ({ vault }: { vault: VaultBaseForString }): Promise => { return this.client.queryContractSmart(this.contractAddress, { - vault_info: { + vault_config: { vault, }, }) } - vaultsInfo = async ({ + vaultsConfig = async ({ limit, startAfter, }: { limit?: number startAfter?: VaultBaseForString - }): Promise => { + }): Promise => { return this.client.queryContractSmart(this.contractAddress, { - vaults_info: { + vaults_config: { limit, start_after: startAfter, }, }) } + vaultUtilization = async ({ + vault, + }: { + vault: VaultBaseForString + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + vault_utilization: { + vault, + }, + }) + } allowedCoins = async ({ limit, startAfter, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index f95f4ec8b..5b864520a 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -44,10 +44,11 @@ import { ArrayOfString, ConfigResponse, ArrayOfCoin, - VaultInfoResponse, + VaultConfigResponse, VaultPositionValue, CoinValue, - ArrayOfVaultInfoResponse, + VaultUtilizationResponse, + ArrayOfVaultConfigResponse, } from './MarsMockCreditManager.types' export interface MarsMockCreditManagerMessage { contractAddress: string diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index b887bdab7..7c6fc1570 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -44,10 +44,11 @@ import { ArrayOfString, ConfigResponse, ArrayOfCoin, - VaultInfoResponse, + VaultConfigResponse, VaultPositionValue, CoinValue, - ArrayOfVaultInfoResponse, + VaultUtilizationResponse, + ArrayOfVaultConfigResponse, } from './MarsMockCreditManager.types' import { MarsMockCreditManagerQueryClient, @@ -65,15 +66,27 @@ export const marsMockCreditManagerQueryKeys = { [ { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, ] as const, - vaultInfo: (contractAddress: string | undefined, args?: Record) => + vaultConfig: (contractAddress: string | undefined, args?: Record) => [ - { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'vault_info', args }, + { + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], + method: 'vault_config', + args, + }, ] as const, - vaultsInfo: (contractAddress: string | undefined, args?: Record) => + vaultsConfig: (contractAddress: string | undefined, args?: Record) => [ { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], - method: 'vaults_info', + method: 'vaults_config', + args, + }, + ] as const, + vaultUtilization: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], + method: 'vault_utilization', args, }, ] as const, @@ -531,23 +544,45 @@ export function useMarsMockCreditManagerAllowedCoinsQuery { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsMockCreditManagerVaultsInfoQuery - extends MarsMockCreditManagerReactQuery { +export interface MarsMockCreditManagerVaultUtilizationQuery + extends MarsMockCreditManagerReactQuery { + args: { + vault: VaultBaseForString + } +} +export function useMarsMockCreditManagerVaultUtilizationQuery({ + client, + args, + options, +}: MarsMockCreditManagerVaultUtilizationQuery) { + return useQuery( + marsMockCreditManagerQueryKeys.vaultUtilization(client?.contractAddress, args), + () => + client + ? client.vaultUtilization({ + vault: args.vault, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsMockCreditManagerVaultsConfigQuery + extends MarsMockCreditManagerReactQuery { args: { limit?: number startAfter?: VaultBaseForString } } -export function useMarsMockCreditManagerVaultsInfoQuery({ +export function useMarsMockCreditManagerVaultsConfigQuery({ client, args, options, -}: MarsMockCreditManagerVaultsInfoQuery) { - return useQuery( - marsMockCreditManagerQueryKeys.vaultsInfo(client?.contractAddress, args), +}: MarsMockCreditManagerVaultsConfigQuery) { + return useQuery( + marsMockCreditManagerQueryKeys.vaultsConfig(client?.contractAddress, args), () => client - ? client.vaultsInfo({ + ? client.vaultsConfig({ limit: args.limit, startAfter: args.startAfter, }) @@ -555,22 +590,22 @@ export function useMarsMockCreditManagerVaultsInfoQuery - extends MarsMockCreditManagerReactQuery { +export interface MarsMockCreditManagerVaultConfigQuery + extends MarsMockCreditManagerReactQuery { args: { vault: VaultBaseForString } } -export function useMarsMockCreditManagerVaultInfoQuery({ +export function useMarsMockCreditManagerVaultConfigQuery({ client, args, options, -}: MarsMockCreditManagerVaultInfoQuery) { - return useQuery( - marsMockCreditManagerQueryKeys.vaultInfo(client?.contractAddress, args), +}: MarsMockCreditManagerVaultConfigQuery) { + return useQuery( + marsMockCreditManagerQueryKeys.vaultConfig(client?.contractAddress, args), () => client - ? client.vaultInfo({ + ? client.vaultConfig({ vault: args.vault, }) : Promise.reject(new Error('Invalid client')), diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 029ddf5e5..190757452 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -85,16 +85,21 @@ export type QueryMsg = config: {} } | { - vault_info: { + vault_config: { vault: VaultBaseForString } } | { - vaults_info: { + vaults_config: { limit?: number | null start_after?: VaultBaseForString | null } } + | { + vault_utilization: { + vault: VaultBaseForString + } + } | { allowed_coins: { limit?: number | null @@ -224,9 +229,8 @@ export interface ConfigResponse { zapper: string } export type ArrayOfCoin = Coin[] -export interface VaultInfoResponse { +export interface VaultConfigResponse { config: VaultConfig - utilization: Coin vault: VaultBaseForString } export interface VaultPositionValue { @@ -238,4 +242,8 @@ export interface CoinValue { denom: string value: Uint128 } -export type ArrayOfVaultInfoResponse = VaultInfoResponse[] +export interface VaultUtilizationResponse { + utilization: Coin + vault: VaultBaseForString +} +export type ArrayOfVaultConfigResponse = VaultConfigResponse[] From 1dae705dd040a56a6b057f8e2215413f9776feb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Rodr=C3=ADguez?= Date: Fri, 3 Mar 2023 09:07:09 -0300 Subject: [PATCH 145/218] Log unlocking position amount (#121) --- contracts/credit-manager/src/vault/request_unlock.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index d29e3404f..39ae4e509 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -93,5 +93,6 @@ pub fn handle_unlock_request_reply(deps: DepsMut, reply: Reply) -> ContractResul .add_attribute("action", "vault/unlock_request/handle_reply") .add_attribute("account_id", &storage.account_id) .add_attribute("vault_addr", storage.vault_addr.to_string()) - .add_attribute("position_id", unlocking_position.id.to_string())) + .add_attribute("position_id", unlocking_position.id.to_string()) + .add_attribute("position_amount", unlocking_position.base_token_amount)) } From e8d63118a6e08a301a108f8bb9a161013bd5fa22 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 3 Mar 2023 17:54:57 +0100 Subject: [PATCH 146/218] Update account nft burn logic (#120) update account nft burn logic --- contracts/account-nft/src/error.rs | 9 ++-- contracts/account-nft/src/execute.rs | 21 ++++++--- .../tests/helpers/health_responses.rs | 4 +- .../account-nft/tests/test_burn_allowance.rs | 43 +++++++------------ 4 files changed, 35 insertions(+), 42 deletions(-) diff --git a/contracts/account-nft/src/error.rs b/contracts/account-nft/src/error.rs index 162957ccb..becfc015b 100644 --- a/contracts/account-nft/src/error.rs +++ b/contracts/account-nft/src/error.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{OverflowError, StdError, Uint128}; +use cosmwasm_std::{OverflowError, StdError}; use cw721_base::ContractError as Base721Error; use thiserror::Error; @@ -13,12 +13,9 @@ pub enum ContractError { #[error("{0}")] Overflow(#[from] OverflowError), - #[error( - "Account balances too high. Collateral + Debts = {current_balances:?}. Max allowed is {max_value_allowed:?}" - )] + #[error("{reason:?}")] BurnNotAllowed { - current_balances: Uint128, - max_value_allowed: Uint128, + reason: String, }, #[error("Health contract should be added to config before burns are allowed")] diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index 72ad6604c..77a6b123f 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -33,8 +33,9 @@ pub fn mint( Parent::default().mint(deps, env, info, mint_msg_override).map_err(Into::into) } -/// Checks first to ensure the balance of debts and collateral does not exceed the config -/// set amount. This is to ensure accounts are not accidentally deleted. +/// A few checks to ensure accounts are not accidentally deleted: +/// - Cannot burn if debt balance +/// - Cannot burn if collateral exceeding config set amount pub fn burn( deps: DepsMut, env: Env, @@ -53,12 +54,18 @@ pub fn burn( })?, }))?; - let current_balances = - response.total_debt_value.checked_add(response.total_collateral_value)?; - if current_balances > config.max_value_for_burn { + if !response.total_debt_value.is_zero() { return Err(BurnNotAllowed { - current_balances, - max_value_allowed: config.max_value_for_burn, + reason: format!("Account has a debt balance. Value: {}.", response.total_debt_value), + }); + } + + if response.total_collateral_value > config.max_value_for_burn { + return Err(BurnNotAllowed { + reason: format!( + "Account collateral value exceeds config set max ({}). Total collateral value: {}.", + config.max_value_for_burn, response.total_collateral_value + ), }); } diff --git a/contracts/account-nft/tests/helpers/health_responses.rs b/contracts/account-nft/tests/helpers/health_responses.rs index bd87a3ad0..c54cd5006 100644 --- a/contracts/account-nft/tests/helpers/health_responses.rs +++ b/contracts/account-nft/tests/helpers/health_responses.rs @@ -20,8 +20,8 @@ pub fn generate_health_response(debt_value: u128, collateral_value: u128) -> Hea pub fn below_max_for_burn() -> HealthResponse { HealthResponse { - total_debt_value: MAX_VALUE_FOR_BURN.sub(Uint128::one()), - total_collateral_value: Default::default(), + total_debt_value: Default::default(), + total_collateral_value: MAX_VALUE_FOR_BURN.sub(Uint128::one()), max_ltv_adjusted_collateral: Default::default(), liquidation_threshold_adjusted_collateral: Default::default(), max_ltv_health_factor: None, diff --git a/contracts/account-nft/tests/test_burn_allowance.rs b/contracts/account-nft/tests/test_burn_allowance.rs index 901f60c42..f13b664fe 100644 --- a/contracts/account-nft/tests/test_burn_allowance.rs +++ b/contracts/account-nft/tests/test_burn_allowance.rs @@ -1,3 +1,5 @@ +use std::ops::Add; + use cosmwasm_std::{Addr, Empty, StdResult, Uint128}; use cw721::NftInfoResponse; use mars_account_nft::{ @@ -23,7 +25,7 @@ fn burn_not_allowed_if_no_health_contract_set() { } #[test] -fn burn_not_allowed_if_too_many_debts() { +fn burn_not_allowed_if_debt_balance() { let mut mock = MockEnv::new().build().unwrap(); let user = Addr::unchecked("user"); @@ -35,8 +37,7 @@ fn burn_not_allowed_if_too_many_debts() { assert_eq!( error, BurnNotAllowed { - current_balances: Uint128::new(10_000), - max_value_allowed: MAX_VALUE_FOR_BURN + reason: "Account has a debt balance. Value: 10000.".to_string(), } ) } @@ -47,34 +48,18 @@ fn burn_not_allowed_if_too_much_collateral() { let user = Addr::unchecked("user"); let token_id = mock.mint(&user).unwrap(); - mock.set_health_response(&user, &token_id, &generate_health_response(0, 10_000)); - - let res = mock.burn(&user, &token_id); - let error: ContractError = res.unwrap_err().downcast().unwrap(); - assert_eq!( - error, - BurnNotAllowed { - current_balances: Uint128::new(10_000), - max_value_allowed: MAX_VALUE_FOR_BURN - } - ) -} - -#[test] -fn burn_allowance_works_with_both_debt_and_collateral() { - let mut mock = MockEnv::new().build().unwrap(); - - let user = Addr::unchecked("user"); - let token_id = mock.mint(&user).unwrap(); - mock.set_health_response(&user, &token_id, &generate_health_response(501, 500)); + mock.set_health_response( + &user, + &token_id, + &generate_health_response(0, MAX_VALUE_FOR_BURN.add(Uint128::one()).into()), + ); let res = mock.burn(&user, &token_id); let error: ContractError = res.unwrap_err().downcast().unwrap(); assert_eq!( error, BurnNotAllowed { - current_balances: Uint128::new(1_001), - max_value_allowed: MAX_VALUE_FOR_BURN + reason: "Account collateral value exceeds config set max (1000). Total collateral value: 1001.".to_string() } ) } @@ -85,7 +70,11 @@ fn burn_allowance_at_exactly_max() { let user = Addr::unchecked("user"); let token_id = mock.mint(&user).unwrap(); - mock.set_health_response(&user, &token_id, &generate_health_response(500, 500)); + mock.set_health_response( + &user, + &token_id, + &generate_health_response(0, MAX_VALUE_FOR_BURN.into()), + ); mock.burn(&user, &token_id).unwrap(); } @@ -96,7 +85,7 @@ fn burn_allowance_when_under_max() { let user = Addr::unchecked("user"); let token_id = mock.mint(&user).unwrap(); - mock.set_health_response(&user, &token_id, &generate_health_response(500, 500)); + mock.set_health_response(&user, &token_id, &generate_health_response(0, 500)); // Assert no errors on calling for NftInfo let _: NftInfoResponse = mock From d4e2f74652f7fdd98056a6a799363de64de6fe0e Mon Sep 17 00:00:00 2001 From: brimigs <85972460+brimigs@users.noreply.github.com> Date: Wed, 8 Mar 2023 17:41:04 -0500 Subject: [PATCH 147/218] Mp 1742 add reclaim msg to actions (#93) --- contracts/credit-manager/src/execute.rs | 9 + contracts/credit-manager/src/lib.rs | 1 + contracts/credit-manager/src/reclaim.rs | 93 +++++++ .../credit-manager/tests/test_reclaim.rs | 226 ++++++++++++++++++ contracts/mock-red-bank/src/contract.rs | 7 +- contracts/mock-red-bank/src/execute.rs | 25 +- contracts/mock-red-bank/src/helpers.rs | 6 + packages/rover/src/adapters/red_bank.rs | 13 + packages/rover/src/error.rs | 3 + packages/rover/src/msg/execute.rs | 8 + .../mars-credit-manager.json | 39 +++ scripts/deploy/base/index.ts | 1 + scripts/deploy/base/rover.ts | 13 + scripts/deploy/osmosis/testnet-config.ts | 1 + scripts/types/config.ts | 1 + .../MarsCreditManager.types.ts | 9 + 16 files changed, 453 insertions(+), 2 deletions(-) create mode 100644 contracts/credit-manager/src/reclaim.rs create mode 100644 contracts/credit-manager/tests/test_reclaim.rs diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 325a939c6..4156f3c8c 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -14,6 +14,7 @@ use crate::{ health::{assert_max_ltv, query_health}, lend::lend, liquidate_coin::liquidate_coin, + reclaim::reclaim, refund::refund_coin_balances, repay::repay, state::ACCOUNT_NFT, @@ -79,6 +80,10 @@ pub fn dispatch_actions( account_id: account_id.to_string(), coin: coin.clone(), }), + Action::Reclaim(coin) => callbacks.push(CallbackMsg::Reclaim { + account_id: account_id.to_string(), + coin: coin.clone(), + }), Action::EnterVault { vault, coin, @@ -222,6 +227,10 @@ pub fn execute_callback( account_id, coin, } => lend(deps, env, &account_id, coin), + CallbackMsg::Reclaim { + account_id, + coin, + } => reclaim(deps, env, &account_id, &coin), CallbackMsg::AssertMaxLTV { account_id, prev_max_ltv_health_factor, diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 0500954b4..6c0d21591 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -8,6 +8,7 @@ pub mod instantiate; pub mod lend; pub mod liquidate_coin; pub mod query; +pub mod reclaim; pub mod refund; pub mod repay; pub mod state; diff --git a/contracts/credit-manager/src/reclaim.rs b/contracts/credit-manager/src/reclaim.rs new file mode 100644 index 000000000..f03698853 --- /dev/null +++ b/contracts/credit-manager/src/reclaim.rs @@ -0,0 +1,93 @@ +use std::cmp::min; + +use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; +use mars_rover::{ + error::{ContractError, ContractResult}, + msg::execute::ActionCoin, +}; + +use crate::{ + state::{LENT_SHARES, RED_BANK, TOTAL_LENT_SHARES}, + utils::{increment_coin_balance, lent_shares_to_amount}, +}; + +pub fn reclaim( + deps: DepsMut, + env: Env, + account_id: &str, + coin: &ActionCoin, +) -> ContractResult { + let (lent_amount, lent_shares) = + current_lent_amount_for_denom(deps.as_ref(), &env, account_id, &coin.denom)?; + let amount_to_reclaim = min(lent_amount, coin.amount.value().unwrap_or(Uint128::MAX)); + let shares_to_reclaim = lent_amount_to_shares( + deps.as_ref(), + &env, + &Coin { + denom: coin.denom.to_string(), + amount: amount_to_reclaim, + }, + )?; + + // Decrement token's lent position + if amount_to_reclaim == lent_amount { + LENT_SHARES.remove(deps.storage, (account_id, &coin.denom)); + } else { + LENT_SHARES.save( + deps.storage, + (account_id, &coin.denom), + &lent_shares.checked_sub(shares_to_reclaim)?, + )?; + } + + // Decrement total lent shares for coin + let total_lent_shares = TOTAL_LENT_SHARES.load(deps.storage, &coin.denom)?; + TOTAL_LENT_SHARES.save( + deps.storage, + &coin.denom, + &total_lent_shares.checked_sub(shares_to_reclaim)?, + )?; + + increment_coin_balance( + deps.storage, + account_id, + &Coin { + denom: coin.denom.to_string(), + amount: amount_to_reclaim, + }, + )?; + + let red_bank = RED_BANK.load(deps.storage)?; + let red_bank_reclaim_msg = red_bank.reclaim_msg(&Coin { + denom: coin.denom.to_string(), + amount: amount_to_reclaim, + })?; + + Ok(Response::new() + .add_message(red_bank_reclaim_msg) + .add_attribute("action", "reclaim") + .add_attribute("lent_shares_reclaimed", shares_to_reclaim) + .add_attribute("coin_reclaimed", format!("{}{}", amount_to_reclaim, &coin.denom))) +} + +fn lent_amount_to_shares(deps: Deps, env: &Env, coin: &Coin) -> ContractResult { + let red_bank = RED_BANK.load(deps.storage)?; + let total_lent_shares = TOTAL_LENT_SHARES.load(deps.storage, &coin.denom)?; + let total_lent = red_bank.query_lent(&deps.querier, &env.contract.address, &coin.denom)?; + let shares = total_lent_shares.checked_multiply_ratio(coin.amount, total_lent)?; + Ok(shares) +} + +/// Get token's current lent amount for denom +/// Returns -> (lent amount, lent shares) +pub fn current_lent_amount_for_denom( + deps: Deps, + env: &Env, + account_id: &str, + denom: &str, +) -> ContractResult<(Uint128, Uint128)> { + let lent_shares = + LENT_SHARES.load(deps.storage, (account_id, denom)).map_err(|_| ContractError::NoneLent)?; + let coin = lent_shares_to_amount(deps, &env.contract.address, denom, lent_shares)?; + Ok((coin.amount, lent_shares)) +} diff --git a/contracts/credit-manager/tests/test_reclaim.rs b/contracts/credit-manager/tests/test_reclaim.rs new file mode 100644 index 000000000..3211988b0 --- /dev/null +++ b/contracts/credit-manager/tests/test_reclaim.rs @@ -0,0 +1,226 @@ +use cosmwasm_std::{coin, coins, Addr, Uint128}; +use mars_rover::{ + error::ContractError, + msg::execute::Action::{Deposit, Lend, Reclaim}, +}; + +use crate::helpers::{assert_err, get_coin, uosmo_info, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn test_only_token_owner_can_reclaim() { + let coin_info = uosmo_info(); + let owner = Addr::unchecked("owner"); + let mut mock = MockEnv::new().build().unwrap(); + let account_id = mock.create_credit_account(&owner).unwrap(); + + let another_user = Addr::unchecked("another_user"); + let res = mock.update_credit_account( + &account_id, + &another_user, + vec![Reclaim(coin_info.to_action_coin(56789))], + &[], + ); + + assert_err( + res, + ContractError::NotTokenOwner { + user: another_user.into(), + account_id, + }, + ) +} + +#[test] +fn test_reclaiming_with_zero_lent() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); + + // When passing some amount + let res = mock.update_credit_account( + &account_id, + &user, + vec![Reclaim(coin_info.to_action_coin(10))], + &[], + ); + + assert_err(res, ContractError::NoneLent); + + // When passing no amount + let res = mock.update_credit_account( + &account_id, + &user, + vec![Reclaim(coin_info.to_action_coin_full_balance())], + &[], + ); + + assert_err(res, ContractError::NoneLent); +} + +#[test] +fn test_when_trying_to_reclaim_more_than_lent() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_coin(50))], + &[coin_info.to_coin(300)], + ) + .unwrap(); + + // Assert account id's position + let positions = mock.query_positions(&account_id); + assert_eq!(positions.deposits.len(), 1); + assert_eq!(get_coin(&coin_info.denom, &positions.deposits), coin_info.to_coin(250)); + + // Assert Rover's balances + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, Uint128::new(250)); + + mock.update_credit_account( + &account_id, + &user, + vec![Reclaim(coin_info.to_action_coin(500))], + &[], + ) + .unwrap(); + + // Should reclaim only max value lent which is 50 not entire 500 + // Entire lent share should go to zero + + // Assert account id's position + let positions = mock.query_positions(&account_id); + assert_eq!(positions.deposits.len(), 1); + assert_eq!(positions.lends.len(), 0); + assert_eq!(get_coin(&coin_info.denom, &positions.deposits), coin_info.to_coin(301)); + + // Assert Rover's balances + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, Uint128::new(301)); +} + +#[test] +fn reclaiming_less_than_entire_lent_share() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_coin(200))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + // Assert account id's position + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.lends.len(), 1); + assert_eq!(get_coin(&coin_info.denom, &position.deposits), coin_info.to_coin(100)); + + // Assert Rover's balances + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, Uint128::new(100)); + + mock.update_credit_account( + &account_id, + &user, + vec![Reclaim(coin_info.to_action_coin(100))], + &[], + ) + .unwrap(); + + // lent share should still exist but value should decrease and coin balance should increase + + // Assert account id's position + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.lends.len(), 1); + assert_eq!(get_coin(&coin_info.denom, &position.deposits), coin_info.to_coin(200)); + assert_eq!(position.lends.first().unwrap().amount, Uint128::new(101)); + // Assert Rover's balances + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, Uint128::new(200)); +} + +#[test] +fn reclaiming_the_entire_lent_share() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_coin(100))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + // Assert account id's position + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.lends.len(), 1); + assert_eq!(get_coin(&coin_info.denom, &position.deposits), coin_info.to_coin(200)); + assert_eq!(position.lends.first().unwrap().amount, Uint128::new(101)); + + // Assert Rover's balances + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, Uint128::new(200)); + + mock.update_credit_account( + &account_id, + &user, + vec![Reclaim(coin_info.to_action_coin(101))], + &[], + ) + .unwrap(); + + // lent share should be removed + + // Assert account id's position + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.lends.len(), 0); + assert_eq!(get_coin(&coin_info.denom, &position.deposits), coin_info.to_coin(301)); + + // Assert Rover's balances + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, Uint128::new(301)); +} diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index 191666845..fc17d3c36 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, use mars_red_bank_types::red_bank; use crate::{ - execute::{borrow, deposit, repay, update_asset}, + execute::{borrow, deposit, repay, update_asset, withdraw}, msg::InstantiateMsg, query::{query_collateral, query_debt, query_market}, state::COIN_MARKET_INFO, @@ -42,6 +42,11 @@ pub fn execute( red_bank::ExecuteMsg::Deposit { .. } => deposit(deps, info), + red_bank::ExecuteMsg::Withdraw { + denom, + amount, + .. + } => withdraw(deps, info, &denom, &amount), red_bank::ExecuteMsg::UpdateAsset { denom, params, diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs index 1d9264b66..2e61a1b45 100644 --- a/contracts/mock-red-bank/src/execute.rs +++ b/contracts/mock-red-bank/src/execute.rs @@ -5,7 +5,7 @@ use cw_utils::one_coin; use mars_red_bank_types::red_bank::InitOrUpdateAssetParams; use crate::{ - helpers::{load_collateral_amount, load_debt_amount}, + helpers::{load_collateral_amount, load_debt_amount, load_lent_amount}, msg::CoinMarketInfo, state::{COIN_MARKET_INFO, COLLATERAL_AMOUNT, DEBT_AMOUNT}, }; @@ -60,6 +60,29 @@ pub fn deposit(deps: DepsMut, info: MessageInfo) -> StdResult { Ok(Response::new()) } +pub fn withdraw( + deps: DepsMut, + info: MessageInfo, + denom: &str, + amount: &Option, +) -> StdResult { + let total_lent = load_lent_amount(deps.storage, &info.sender, denom)?; + let amount_to_reclaim = amount.unwrap_or(total_lent); + + COLLATERAL_AMOUNT.save( + deps.storage, + (info.sender.clone(), denom.to_string()), + &total_lent.checked_sub(amount_to_reclaim)?, + )?; + + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.to_string(), + amount: vec![coin(amount_to_reclaim.u128(), denom)], + }); + + Ok(Response::new().add_message(transfer_msg)) +} + pub fn update_asset( deps: DepsMut, denom: &str, diff --git a/contracts/mock-red-bank/src/helpers.rs b/contracts/mock-red-bank/src/helpers.rs index 32b8b414a..58ecc5be0 100644 --- a/contracts/mock-red-bank/src/helpers.rs +++ b/contracts/mock-red-bank/src/helpers.rs @@ -17,3 +17,9 @@ pub fn load_collateral_amount( .may_load(storage, (user.clone(), denom.to_string()))? .unwrap_or_else(Uint128::zero)) } + +pub fn load_lent_amount(storage: &dyn Storage, user: &Addr, denom: &str) -> StdResult { + Ok(COLLATERAL_AMOUNT + .may_load(storage, (user.clone(), denom.to_string()))? + .unwrap_or_else(Uint128::zero)) +} diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index 8fd832b13..671075ebf 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -69,6 +69,19 @@ impl RedBank { })) } + /// Generate message for reclaiming a specified amount of lent coin + pub fn reclaim_msg(&self, coin: &Coin) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.address().to_string(), + msg: to_binary(&red_bank::ExecuteMsg::Withdraw { + denom: coin.denom.clone(), + amount: Some(coin.amount), + recipient: None, + })?, + funds: vec![], + })) + } + pub fn query_lent( &self, querier: &QuerierWrapper, diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index d14008c33..39fbdcf18 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -88,6 +88,9 @@ pub enum ContractError { #[error("No debt to repay")] NoDebt, + #[error("Nothing lent to reclaim")] + NoneLent, + #[error("Position {0} was not a valid position for this account id in this vault")] NoPositionMatch(String), diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index def73380f..d0970e243 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -79,6 +79,8 @@ pub enum Action { Borrow(Coin), /// Lend coin to the Red Bank Lend(Coin), + /// Reclaim the coins that were lent to the Red Bank. + Reclaim(ActionCoin), /// Repay coin of specified amount back to Red Bank. If `amount: AccountBalance` is passed, /// the repaid amount will be the minimum between account balance for denom and total owed. Repay(ActionCoin), @@ -182,6 +184,12 @@ pub enum CallbackMsg { account_id: String, coin: Coin, }, + /// Reclaim lent coin from the Red Bank; + /// Decrement the token's lent shares and increment the coin amount; + Reclaim { + account_id: String, + coin: ActionCoin, + }, /// Assert MaxLTV is either: /// - Healthy, if prior to actions MaxLTV health factor >= 1 or None /// - Not further weakened, if prior to actions MaxLTV health factor < 1 diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 3ee613aa1..078e24720 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -362,6 +362,19 @@ }, "additionalProperties": false }, + { + "description": "Reclaim the coins that were lent to the Red Bank.", + "type": "object", + "required": [ + "reclaim" + ], + "properties": { + "reclaim": { + "$ref": "#/definitions/ActionCoin" + } + }, + "additionalProperties": false + }, { "description": "Repay coin of specified amount back to Red Bank. If `amount: AccountBalance` is passed, the repaid amount will be the minimum between account balance for denom and total owed.", "type": "object", @@ -806,6 +819,32 @@ }, "additionalProperties": false }, + { + "description": "Reclaim lent coin from the Red Bank; Decrement the token's lent shares and increment the coin amount;", + "type": "object", + "required": [ + "reclaim" + ], + "properties": { + "reclaim": { + "type": "object", + "required": [ + "account_id", + "coin" + ], + "properties": { + "account_id": { + "type": "string" + }, + "coin": { + "$ref": "#/definitions/ActionCoin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Assert MaxLTV is either: - Healthy, if prior to actions MaxLTV health factor >= 1 or None - Not further weakened, if prior to actions MaxLTV health factor < 1 Emits a `position_changed` event.", "type": "object", diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index af0ff711e..1def9503e 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -42,6 +42,7 @@ export const taskRunner = async ({ config, label }: TaskRunnerProps) => { await rover.borrow() await rover.swap() await rover.repay() + await rover.reclaim() await rover.withdraw() const vaultConfig = config.vaults[0] diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index ee2020e40..48b5334ad 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -108,6 +108,19 @@ export class Rover { ) } + async reclaim() { + const amount = this.actions.reclaimAmount + await this.updateCreditAccount([ + { reclaim: { amount: { exact: amount }, denom: this.config.chain.baseDenom } }, + ]) + const positions = await this.query.positions({ accountId: this.accountId! }) + printGreen( + `User reclaimed: ${amount} ${ + this.config.chain.baseDenom + }. Lent amount remaining: ${JSON.stringify(positions.lends)}`, + ) + } + async swap() { const amount = this.actions.swap.amount printBlue( diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index 0b151254b..ba9d7ab97 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -130,6 +130,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { defaultCreditLine: '100000000000', depositAmount: '100', lendAmount: '10', + reclaimAmount: '5', secondaryDenom: uatom, startingAmountForTestUser: '2500000', swap: { diff --git a/scripts/types/config.ts b/scripts/types/config.ts index fd7eb32eb..83d778ba8 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -65,6 +65,7 @@ export interface TestActions { lendAmount: string borrowAmount: string repayAmount: string + reclaimAmount: string swap: { amount: string slippage: string diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index c71c09ca3..df02d60d8 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -81,6 +81,9 @@ export type Action = | { lend: Coin } + | { + reclaim: ActionCoin + } | { repay: ActionCoin } @@ -186,6 +189,12 @@ export type CallbackMsg = coin: Coin } } + | { + reclaim: { + account_id: string + coin: ActionCoin + } + } | { assert_max_ltv: { account_id: string From f94bdaf20a6f51e1fbfcfc890b767407010c2bcf Mon Sep 17 00:00:00 2001 From: brimigs <85972460+brimigs@users.noreply.github.com> Date: Mon, 13 Mar 2023 06:26:08 -0400 Subject: [PATCH 148/218] Reclaim msg updates (#124) --- .../credit-manager/tests/test_reclaim.rs | 94 ++++++++++++++++++- 1 file changed, 89 insertions(+), 5 deletions(-) diff --git a/contracts/credit-manager/tests/test_reclaim.rs b/contracts/credit-manager/tests/test_reclaim.rs index 3211988b0..33207f4fc 100644 --- a/contracts/credit-manager/tests/test_reclaim.rs +++ b/contracts/credit-manager/tests/test_reclaim.rs @@ -4,12 +4,12 @@ use mars_rover::{ msg::execute::Action::{Deposit, Lend, Reclaim}, }; -use crate::helpers::{assert_err, get_coin, uosmo_info, AccountToFund, MockEnv}; +use crate::helpers::{assert_err, get_coin, uatom_info, uosmo_info, AccountToFund, MockEnv}; pub mod helpers; #[test] -fn test_only_token_owner_can_reclaim() { +fn only_token_owner_can_reclaim() { let coin_info = uosmo_info(); let owner = Addr::unchecked("owner"); let mut mock = MockEnv::new().build().unwrap(); @@ -33,7 +33,7 @@ fn test_only_token_owner_can_reclaim() { } #[test] -fn test_reclaiming_with_zero_lent() { +fn reclaiming_with_zero_lent() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); @@ -61,7 +61,7 @@ fn test_reclaiming_with_zero_lent() { } #[test] -fn test_when_trying_to_reclaim_more_than_lent() { +fn when_trying_to_reclaim_more_than_lent() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() @@ -107,7 +107,7 @@ fn test_when_trying_to_reclaim_more_than_lent() { let positions = mock.query_positions(&account_id); assert_eq!(positions.deposits.len(), 1); assert_eq!(positions.lends.len(), 0); - assert_eq!(get_coin(&coin_info.denom, &positions.deposits), coin_info.to_coin(301)); + assert_eq!(get_coin(&coin_info.denom, &positions.deposits), coin_info.to_coin(301)); // +1 for interest from red bank // Assert Rover's balances let coin = mock.query_balance(&mock.rover, &coin_info.denom); @@ -224,3 +224,87 @@ fn reclaiming_the_entire_lent_share() { let coin = mock.query_balance(&mock.rover, &coin_info.denom); assert_eq!(coin.amount, Uint128::new(301)); } +#[test] +fn reclaiming_multiple_assets() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + let user = Addr::unchecked("user"); + + let mut mock = MockEnv::new() + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, uosmo_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, uatom_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(uatom_info.to_coin(300)), Lend(uatom_info.to_coin(100))], + &[coin(300, uatom_info.denom.clone())], + ) + .unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(uosmo_info.to_coin(200)), Lend(uosmo_info.to_coin(100))], + &[coin(200, uosmo_info.denom.clone())], + ) + .unwrap(); + // Assert account id's position + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 2); + assert_eq!(position.lends.len(), 2); + assert_eq!(get_coin(&uatom_info.denom, &position.deposits), uatom_info.to_coin(200)); + assert_eq!(get_coin(&uosmo_info.denom, &position.deposits), uosmo_info.to_coin(100)); + assert_eq!(position.lends.first().unwrap().amount, Uint128::new(101)); // +1 for interest from red bank + + mock.update_credit_account( + &account_id, + &user, + vec![Reclaim(uosmo_info.to_action_coin(101))], + &[], + ) + .unwrap(); + + // 1 lent share should be removed and 1 should stay + + // Assert account id's position + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 2); + assert_eq!(position.lends.len(), 1); + assert_eq!(get_coin(&uosmo_info.denom, &position.deposits), uosmo_info.to_coin(201)); + + // Assert Rover's balances + let coin = mock.query_balance(&mock.rover, &uosmo_info.denom); + assert_eq!(coin.amount, Uint128::new(201)); + + mock.update_credit_account( + &account_id, + &user, + vec![Reclaim(uatom_info.to_action_coin(101))], + &[], + ) + .unwrap(); + + // last lent share should be removed + + // Assert account id's position + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 2); + assert_eq!(position.lends.len(), 0); + assert_eq!(get_coin(&uatom_info.denom, &position.deposits), uatom_info.to_coin(301)); + + // Assert Rover's balances + let coin = mock.query_balance(&mock.rover, &uatom_info.denom); + assert_eq!(coin.amount, Uint128::new(301)); +} From 39ac35b6ac16e9a0e256aed587249084e75c99ba Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 14 Mar 2023 10:13:48 +0100 Subject: [PATCH 149/218] Update all deps to latest version (#126) Update all deps --- Cargo.lock | 237 ++-- Cargo.toml | 40 +- contracts/credit-manager/Cargo.toml | 3 +- .../credit-manager/src/liquidate_coin.rs | 1 - contracts/credit-manager/src/utils.rs | 3 +- .../credit-manager/src/vault/exit_unlocked.rs | 2 +- .../src/vault/liquidate_vault.rs | 2 +- contracts/credit-manager/src/vault/utils.rs | 1 - .../credit-manager/tests/helpers/mock_env.rs | 4 +- contracts/credit-manager/tests/test_health.rs | 3 +- .../tests/test_vault_query_value.rs | 1 - contracts/health/Cargo.toml | 3 +- contracts/health/tests/helpers/mock_env.rs | 4 +- contracts/health/tests/test_compute_health.rs | 1 - contracts/health/tests/test_update_config.rs | 7 +- contracts/mock-health/Cargo.toml | 8 +- contracts/mock-vault/Cargo.toml | 14 +- contracts/mock-vault/src/contract.rs | 2 +- contracts/mock-vault/src/msg.rs | 2 +- contracts/mock-vault/src/query.rs | 2 +- contracts/mock-vault/src/state.rs | 2 +- contracts/mock-vault/src/unlock.rs | 4 +- contracts/swapper/mock/Cargo.toml | 8 +- contracts/zapper/base/Cargo.toml | 14 +- contracts/zapper/mock/Cargo.toml | 12 +- packages/health-computer/Cargo.toml | 10 +- .../health-computer/src/health_computer.rs | 1 - .../tests/test_health_scenarios.rs | 1 - packages/health-types/Cargo.toml | 9 +- packages/health-types/src/error.rs | 3 +- packages/math/Cargo.toml | 22 - packages/math/src/fraction.rs | 94 -- packages/math/src/lib.rs | 3 - packages/math/tests/test_div_floor.rs | 71 -- packages/math/tests/test_mul_ceil.rs | 85 -- packages/math/tests/test_mul_floor.rs | 72 -- packages/rover/Cargo.toml | 3 +- packages/rover/src/adapters/oracle.rs | 1 - packages/rover/src/adapters/vault/base.rs | 4 +- packages/rover/src/adapters/vault/position.rs | 1 - packages/rover/src/error.rs | 5 +- packages/rover/src/extensions/reply.rs | 2 +- scripts/health/example-node.ts | 5 +- scripts/health/pkg-node/index.js | 4 +- scripts/health/pkg-node/index_bg.wasm | Bin 162616 -> 162325 bytes scripts/health/pkg-node/package.json | 6 - scripts/health/pkg-web/index.js | 4 +- scripts/health/pkg-web/index_bg.wasm | Bin 161692 -> 161401 bytes scripts/health/pkg-web/package.json | 6 - scripts/package.json | 18 +- .../mars-account-nft/MarsAccountNft.client.ts | 2 +- .../MarsAccountNft.message-composer.ts | 2 +- .../MarsAccountNft.react-query.ts | 2 +- .../mars-account-nft/MarsAccountNft.types.ts | 2 +- .../generated/mars-account-nft/bundle.ts | 2 +- .../MarsCreditManager.client.ts | 2 +- .../MarsCreditManager.message-composer.ts | 2 +- .../MarsCreditManager.react-query.ts | 2 +- .../MarsCreditManager.types.ts | 2 +- .../generated/mars-credit-manager/bundle.ts | 2 +- .../MarsMockCreditManager.client.ts | 2 +- .../MarsMockCreditManager.message-composer.ts | 2 +- .../MarsMockCreditManager.react-query.ts | 2 +- .../MarsMockCreditManager.types.ts | 2 +- .../mars-mock-credit-manager/bundle.ts | 2 +- .../mars-mock-oracle/MarsMockOracle.client.ts | 2 +- .../MarsMockOracle.message-composer.ts | 2 +- .../MarsMockOracle.react-query.ts | 2 +- .../mars-mock-oracle/MarsMockOracle.types.ts | 2 +- .../generated/mars-mock-oracle/bundle.ts | 2 +- .../MarsMockRedBank.client.ts | 2 +- .../MarsMockRedBank.message-composer.ts | 2 +- .../MarsMockRedBank.react-query.ts | 2 +- .../MarsMockRedBank.types.ts | 2 +- .../generated/mars-mock-red-bank/bundle.ts | 2 +- .../mars-mock-vault/MarsMockVault.client.ts | 2 +- .../MarsMockVault.message-composer.ts | 2 +- .../MarsMockVault.react-query.ts | 2 +- .../mars-mock-vault/MarsMockVault.types.ts | 2 +- .../types/generated/mars-mock-vault/bundle.ts | 2 +- .../MarsRoverHealthComputer.client.ts | 2 +- ...arsRoverHealthComputer.message-composer.ts | 2 +- .../MarsRoverHealthComputer.react-query.ts | 2 +- .../MarsRoverHealthComputer.types.ts | 2 +- .../mars-rover-health-computer/bundle.ts | 2 +- .../MarsRoverHealthTypes.client.ts | 2 +- .../MarsRoverHealthTypes.message-composer.ts | 2 +- .../MarsRoverHealthTypes.react-query.ts | 2 +- .../MarsRoverHealthTypes.types.ts | 2 +- .../mars-rover-health-types/bundle.ts | 2 +- .../MarsSwapperBase.client.ts | 2 +- .../MarsSwapperBase.message-composer.ts | 2 +- .../MarsSwapperBase.react-query.ts | 2 +- .../MarsSwapperBase.types.ts | 2 +- .../generated/mars-swapper-base/bundle.ts | 2 +- .../mars-zapper-base/MarsZapperBase.client.ts | 2 +- .../MarsZapperBase.message-composer.ts | 2 +- .../MarsZapperBase.react-query.ts | 2 +- .../mars-zapper-base/MarsZapperBase.types.ts | 2 +- .../generated/mars-zapper-base/bundle.ts | 2 +- scripts/yarn.lock | 1007 +++++++++-------- 101 files changed, 794 insertions(+), 1123 deletions(-) delete mode 100644 packages/math/Cargo.toml delete mode 100644 packages/math/src/fraction.rs delete mode 100644 packages/math/src/lib.rs delete mode 100644 packages/math/tests/test_div_floor.rs delete mode 100644 packages/math/tests/test_mul_ceil.rs delete mode 100644 packages/math/tests/test_mul_floor.rs diff --git a/Cargo.lock b/Cargo.lock index 3014ece0f..49694077f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,13 +29,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] -name = "apollo-utils" +name = "apollo-cw-asset" version = "0.1.0" -source = "git+https://github.com/apollodao/apollo-utils.git?rev=e12b38158778f6279525c4be96789a5f488d2659#e12b38158778f6279525c4be96789a5f488d2659" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fdd3af3b24bd7343c2b74b2bbe66ff53cc52327342e26ed207b3150f324c4a" dependencies = [ + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "cw20", + "schemars", + "serde", +] + +[[package]] +name = "apollo-utils" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b05ef4d29df7a8d2459008f09ba1c9f1f0aa7ac1bdfaa510029c0cf6921c2c2" +dependencies = [ + "apollo-cw-asset", "cosmwasm-schema", "cosmwasm-std", - "cw-asset", "cw20", ] @@ -286,9 +300,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fecd74d3a0041114110d1260f77fcb644c5d2403549b37096c44f0e643a5177" +checksum = "6a8263ce52392898aa17c2a0984b7c542df416e434f6e0cb1c1a11771054d159" dependencies = [ "digest 0.10.6", "ed25519-zebra", @@ -299,18 +313,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5abeeb891e6d0098402e4d3d042f90451db52651d2fe14b170e69a1dd3e4115" +checksum = "f1895f6d7a191bb044e3c555190d1da555c2571a3af41f849f60c855580e392f" dependencies = [ "syn", ] [[package]] name = "cosmwasm-schema" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9118e36843df6648fd0a626c46438f87038f296ec750cef3832cafc483c483f9" +checksum = "b1b99f612ccf162940ae2eef9f370ee37cf2ddcf4a9a8f5ee15ec6b46a5ecd2e" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -321,9 +335,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78d6fc9854ac14e46cb69b0f396547893f93d2847aef975950ebbe73342324f3" +checksum = "a92ceea61033cb69c336abf673da017ddf251fc4e26e0cdd387eaf8bedb14e49" dependencies = [ "proc-macro2", "quote", @@ -332,9 +346,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.1.9" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13d5a84d15cf7be17dc249a21588cdb0f7ef308907c50ce2723316a7d79c3dc" +checksum = "ecc01051aab3bb88d5efe0b90f24a6df1ca96a873b12fc21b862b17539c84ee9" dependencies = [ "base64", "cosmwasm-crypto", @@ -345,23 +359,11 @@ dependencies = [ "schemars", "serde", "serde-json-wasm", + "sha2 0.10.6", "thiserror", "uint", ] -[[package]] -name = "cosmwasm-vault-standard" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48847f2c22b96d9659ff9d0b67403ed1d5cbe5470e65d293c4c455de05b237b" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils 0.16.0", - "schemars", - "serde", -] - [[package]] name = "cpufeatures" version = "0.2.5" @@ -412,41 +414,28 @@ dependencies = [ "zeroize", ] -[[package]] -name = "cw-asset" -version = "0.3.0" -source = "git+https://github.com/apollodao/cw-asset.git?rev=057fb193013ad4adfc25063b99960972d1d208bc#057fb193013ad4adfc25063b99960972d1d208bc" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.16.0", - "cw20", - "schemars", - "serde", -] - [[package]] name = "cw-dex" -version = "0.0.1" -source = "git+https://github.com/apollodao/cw-dex?rev=1ce5566963851f7cab0eed117240654f3cb2d78d#1ce5566963851f7cab0eed117240654f3cb2d78d" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c4c002da51161e832615de09aa796e6915507418a7e4658204cd6c91ce89e7" dependencies = [ + "apollo-cw-asset", "apollo-utils", "cosmwasm-schema", "cosmwasm-std", - "cw-asset", "cw-storage-plus 1.0.1", - "cw-utils 0.11.1", - "cw-utils 0.16.0", + "cw-utils 1.0.1", "cw20", - "osmosis-std 0.12.0", + "osmosis-std 0.14.0", "thiserror", ] [[package]] name = "cw-item-set" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2fe22a9086c61c6b41ee46669c6f61ca4142111d38551ca156be4fa9d9fa39d" +checksum = "dea5a233bd67babedbe96a514178a64b0c597f1f38bc474fa8d63e3f26bdceb2" dependencies = [ "cosmwasm-std", "cw-storage-plus 1.0.1", @@ -473,9 +462,9 @@ dependencies = [ [[package]] name = "cw-paginate" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9ef244714397f68fa66de13ea6fefbc54d991f12da41ae076f92887aa893ed" +checksum = "add278617f6251be1a35c781eb0fbffd44f899d8bb4dc5a9e420273a90684c4e" dependencies = [ "cosmwasm-std", "cw-storage-plus 1.0.1", @@ -506,25 +495,28 @@ dependencies = [ [[package]] name = "cw-utils" -version = "0.11.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef842a1792e4285beff7b3b518705f760fa4111dc1e296e53f3e92d1ef7f6220" +checksum = "d6a84c6c1c0acc3616398eba50783934bd6c964bad6974241eaee3460c8f5b26" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", + "cw2 0.16.0", "schemars", + "semver", "serde", "thiserror", ] [[package]] name = "cw-utils" -version = "0.16.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a84c6c1c0acc3616398eba50783934bd6c964bad6974241eaee3460c8f5b26" +checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 0.16.0", + "cw2 1.0.1", "schemars", "semver", "serde", @@ -532,18 +524,16 @@ dependencies = [ ] [[package]] -name = "cw-utils" -version = "1.0.1" +name = "cw-vault-standard" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" +checksum = "793cd7de3239b1bf187a2a61c8e37d80bb9bd6e354328bfb12070323a435eee1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 1.0.1", + "cw-utils 1.0.1", "schemars", - "semver", "serde", - "thiserror", ] [[package]] @@ -574,13 +564,13 @@ dependencies = [ [[package]] name = "cw20" -version = "0.16.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45a8794a5dd33b66af34caee52a7beceb690856adcc1682b6e3db88b2cdee62" +checksum = "91666da6c7b40c8dd5ff94df655a28114efc10c79b70b4d06f13c31e37d60609" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-utils 0.16.0", + "cw-utils 1.0.1", "schemars", "serde", ] @@ -1048,18 +1038,17 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-vault-standard", "cw-item-set", "cw-multi-test", "cw-paginate", "cw-storage-plus 1.0.1", - "cw-utils 0.16.0", + "cw-utils 1.0.1", + "cw-vault-standard", "cw2 1.0.1", "cw721", "cw721-base", "itertools", "mars-account-nft", - "mars-math", "mars-mock-oracle", "mars-mock-red-bank", "mars-mock-vault", @@ -1072,14 +1061,6 @@ dependencies = [ "mars-zapper-mock", ] -[[package]] -name = "mars-math" -version = "1.0.0" -dependencies = [ - "cosmwasm-std", - "thiserror", -] - [[package]] name = "mars-mock-credit-manager" version = "1.0.0" @@ -1087,7 +1068,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.0.1", - "cw-utils 0.16.0", + "cw-utils 1.0.1", "mars-rover", "mars-rover-health-types", "thiserror", @@ -1110,7 +1091,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.0.1", - "cw-utils 0.16.0", + "cw-utils 1.0.1", "mars-red-bank-types", ] @@ -1130,17 +1111,17 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-vault-standard", "cw-storage-plus 1.0.1", - "cw-utils 0.16.0", + "cw-utils 1.0.1", + "cw-vault-standard", "mars-rover", "thiserror", ] [[package]] name = "mars-osmosis" -version = "1.0.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=3e57365d82b96967ef1bc7ec183c4a86bc88d5e7#3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" +version = "1.0.1" +source = "git+https://github.com/mars-protocol/red-bank?rev=00301d60c38af09d8eb7980355009e2f00c6f41f#00301d60c38af09d8eb7980355009e2f00c6f41f" dependencies = [ "cosmwasm-std", "osmosis-std 0.14.0", @@ -1163,7 +1144,8 @@ dependencies = [ [[package]] name = "mars-red-bank-types" version = "1.0.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=3e57365d82b96967ef1bc7ec183c4a86bc88d5e7#3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca59bb17daa753c30d3c934e0779736200708cb89a34020fa805b1cb05e7278" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1178,13 +1160,12 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-vault-standard", "cw-storage-plus 1.0.1", - "cw-utils 0.16.0", + "cw-utils 1.0.1", + "cw-vault-standard", "cw721", "cw721-base", "mars-account-nft", - "mars-math", "mars-owner", "mars-red-bank-types", "mars-rover-health-types", @@ -1200,12 +1181,11 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-vault-standard", "cw-multi-test", "cw-storage-plus 1.0.1", - "cw-utils 0.16.0", + "cw-utils 1.0.1", + "cw-vault-standard", "cw2 1.0.1", - "mars-math", "mars-mock-credit-manager", "mars-mock-oracle", "mars-mock-red-bank", @@ -1225,8 +1205,7 @@ dependencies = [ "console_error_panic_hook", "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-vault-standard", - "mars-math", + "cw-vault-standard", "mars-red-bank-types", "mars-rover", "mars-rover-health-types", @@ -1243,7 +1222,6 @@ version = "1.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "mars-math", "mars-owner", "thiserror", ] @@ -1297,7 +1275,8 @@ dependencies = [ [[package]] name = "mars-utils" version = "1.0.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=3e57365d82b96967ef1bc7ec183c4a86bc88d5e7#3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bae572eda20842ade4bf8ab09ce0856cae5cff89dbeb7c51e9123489e48256" dependencies = [ "cosmwasm-std", "thiserror", @@ -1310,7 +1289,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-dex", - "cw-utils 0.16.0", + "cw-utils 1.0.1", "schemars", "serde", "thiserror", @@ -1323,7 +1302,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.0.1", - "cw-utils 0.16.0", + "cw-utils 1.0.1", "mars-rover", "thiserror", ] @@ -1334,7 +1313,7 @@ version = "1.0.0" dependencies = [ "cosmwasm-std", "cw-dex", - "cw-utils 0.16.0", + "cw-utils 1.0.1", "cw2 1.0.1", "mars-zapper-base", "osmosis-test-tube", @@ -1419,21 +1398,6 @@ version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" -[[package]] -name = "osmosis-std" -version = "0.12.0" -source = "git+https://github.com/apollodao/osmosis-rust.git?branch=osmosis-v13#38a48533ed88243070b105cb9085e34c2b0c4eeb" -dependencies = [ - "chrono", - "cosmwasm-std", - "osmosis-std-derive 0.12.0", - "prost 0.11.6", - "prost-types", - "schemars", - "serde", - "serde-cw-value", -] - [[package]] name = "osmosis-std" version = "0.13.2" @@ -1442,7 +1406,7 @@ checksum = "10d6fe6ac7fcba45ed61d738091d33c838c4cabbcf4892dc7aa56d19d39cc976" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.13.2", + "osmosis-std-derive", "prost 0.11.6", "prost-types", "schemars", @@ -1458,7 +1422,7 @@ checksum = "2fc0a9075efd64ed5a8be3bf134cbf1080570d68384f2ad58ffaac6c00d063fd" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.13.2", + "osmosis-std-derive", "prost 0.11.6", "prost-types", "schemars", @@ -1466,17 +1430,6 @@ dependencies = [ "serde-cw-value", ] -[[package]] -name = "osmosis-std-derive" -version = "0.12.0" -source = "git+https://github.com/apollodao/osmosis-rust.git?branch=osmosis-v13#38a48533ed88243070b105cb9085e34c2b0c4eeb" -dependencies = [ - "itertools", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "osmosis-std-derive" version = "0.13.2" @@ -1701,9 +1654,9 @@ checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "schemars" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" dependencies = [ "dyn-clone", "schemars_derive", @@ -1713,9 +1666,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" dependencies = [ "proc-macro2", "quote", @@ -1745,9 +1698,9 @@ checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" [[package]] name = "serde" -version = "1.0.152" +version = "1.0.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "71f2b4817415c6d4210bfe1c7bfcf4801b2d904cb4d0e1a8fdb651013c9e86b8" dependencies = [ "serde_derive", ] @@ -1763,18 +1716,18 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" +checksum = "a15bee9b04dd165c3f4e142628982ddde884c2022a89e8ddf99c4829bf2c3a58" dependencies = [ "serde", ] [[package]] name = "serde-wasm-bindgen" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" dependencies = [ "js-sys", "serde", @@ -1792,9 +1745,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "d071a94a3fac4aff69d023a7f411e33f40f3483f8c5190b1953822b6b76d7630" dependencies = [ "proc-macro2", "quote", @@ -1814,9 +1767,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ "itoa", "ryu", @@ -1923,9 +1876,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -2026,18 +1979,18 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 1270bab3d..8d6e569bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ members = [ "packages/health-computer", "packages/health-types", "packages/rover", - "packages/math", ] [workspace.package] @@ -36,37 +35,34 @@ documentation = "https://docs.marsprotocol.io/" keywords = ["mars", "cosmos", "cosmwasm"] [workspace.dependencies] -anyhow = "1.0.66" -cosmwasm-schema = "1.1.9" -cosmwasm-std = "=1.1.9" # TODO: 1.2.1 requires CheckedMultiplyFractionError exposure: https://github.com/CosmWasm/cosmwasm/pull/1608 -cw2 = "1.0.0" +anyhow = "1.0.69" +cosmwasm-schema = "1.2.2" +cosmwasm-std = "1.2.2" +cw2 = "1.0.1" cw721 = "0.16.0" cw721-base = { version = "0.16.0", features = ["library"] } -cw-item-set = { version = "0.7.0", default-features = false, features = ["iterator"] } -cw-multi-test = "0.16.1" -cw-paginate = "0.2.0" -cw-utils = "0.16.0" +cw-item-set = { version = "0.7.1", default-features = false, features = ["iterator"] } +cw-multi-test = "0.16.2" +cw-paginate = "0.2.1" +cw-utils = "1.0.1" cw-storage-plus = "1.0.1" itertools = "0.10.5" osmosis-std = "0.14.0" osmosis-test-tube = "14.1.1" -schemars = "0.8.11" -serde = { version = "1.0.150", default-features = false, features = ["derive"] } -serde_json = "1.0.91" -serde-wasm-bindgen = "0.4.5" -thiserror = "1.0.37" -wasm-bindgen = "0.2.83" +schemars = "0.8.12" +serde = { version = "1.0.155", default-features = false, features = ["derive"] } +serde_json = "1.0.94" +serde-wasm-bindgen = "0.5.0" +thiserror = "1.0.39" +wasm-bindgen = "0.2.84" # packages -cosmwasm-vault-standard = { version = "0.1.0", features = ["lockup", "force-unlock"] } -# FIXME: develop branch dependency till Apollo finish audit -cw-dex = { git = "https://github.com/apollodao/cw-dex", rev = "1ce5566963851f7cab0eed117240654f3cb2d78d", features = ["osmosis"] } -mars-health = { version = "1.0.0", path = "./packages/health" } # TODO to delete +cw-dex = { version = "0.1.3", features = ["osmosis"] } +cw-vault-standard = { version = "0.2.0", features = ["lockup", "force-unlock"] } mars-rover-health-computer = { version = "1.0.0", path = "./packages/health-computer" } mars-rover-health-types = { version = "1.0.0", path = "./packages/health-types" } -mars-math = { version = "1.0.0", path = "./packages/math" } -mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" } -mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank", rev = "3e57365d82b96967ef1bc7ec183c4a86bc88d5e7" } +mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "00301d60c38af09d8eb7980355009e2f00c6f41f" } +mars-red-bank-types = "1.0.0" mars-owner = "1.0.0" mars-rover = { version = "1.0.0", path = "./packages/rover" } diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 1cb2c42f3..51ed10940 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -21,15 +21,14 @@ library = [] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-vault-standard = { workspace = true } cw2 = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true } cw-item-set = { workspace = true } cw-paginate = { workspace = true } cw-storage-plus = { workspace = true } +cw-vault-standard = { workspace = true } mars-account-nft = { workspace = true } -mars-math = { workspace = true } mars-owner = { workspace = true } mars-red-bank-types = { workspace = true } mars-rover = { workspace = true } diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_coin.rs index 9ac3f7793..444cc766a 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_coin.rs @@ -3,7 +3,6 @@ use std::ops::Add; use cosmwasm_std::{ Coin, CosmosMsg, Decimal, DepsMut, Env, QuerierWrapper, Response, StdError, Storage, Uint128, }; -use mars_math::FractionMath; use mars_rover::{ adapters::oracle::Oracle, error::{ContractError, ContractResult}, diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 3f176434d..1a0b850db 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -6,7 +6,6 @@ use cosmwasm_std::{ }; use cw721::OwnerOfResponse; use cw721_base::QueryMsg; -use mars_math::{FractionMath, Fractional}; use mars_rover::{ error::{ContractError, ContractResult}, msg::{execute::CallbackMsg, ExecuteMsg}, @@ -127,7 +126,7 @@ pub fn debt_shares_to_amount( let total_debt_amount = red_bank.query_debt(&deps.querier, rover_addr, denom)?; // Amount of debt for token's position. Rounded up to favor participants in the debt pool. - let amount = total_debt_amount.checked_mul_ceil(Fractional(shares, total_debt_shares))?; + let amount = total_debt_amount.checked_mul_ceil((shares, total_debt_shares))?; Ok(Coin { denom: denom.to_string(), diff --git a/contracts/credit-manager/src/vault/exit_unlocked.rs b/contracts/credit-manager/src/vault/exit_unlocked.rs index b5e05c7e8..00f0bd73d 100644 --- a/contracts/credit-manager/src/vault/exit_unlocked.rs +++ b/contracts/credit-manager/src/vault/exit_unlocked.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, WasmMsg}; -use cosmwasm_vault_standard::extensions::lockup::UnlockingPosition; +use cw_vault_standard::extensions::lockup::UnlockingPosition; use mars_rover::{ adapters::vault::{UnlockingChange, Vault, VaultPositionUpdate}, error::{ContractError, ContractResult}, diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index a33326e9e..7050815dd 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -1,7 +1,7 @@ use std::cmp::min; use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; -use cosmwasm_vault_standard::VaultInfoResponse; +use cw_vault_standard::VaultInfoResponse; use mars_rover::{ adapters::vault::{ UnlockingChange, UnlockingPositions, UpdateType, Vault, VaultPositionAmount, diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 6727f8b88..5c37d8c3d 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,5 +1,4 @@ use cosmwasm_std::{Addr, Coin, Deps, StdResult, Storage, Uint128}; -use mars_math::FractionMath; use mars_rover::{ adapters::vault::{ LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultPosition, diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 3a66360bf..033f7d9ad 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -2,11 +2,11 @@ use std::mem::take; use anyhow::Result as AnyResult; use cosmwasm_std::{coins, testing::MockApi, Addr, Coin, Decimal, StdResult, Uint128}; -use cosmwasm_vault_standard::{ +use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; +use cw_vault_standard::{ extensions::lockup::{LockupQueryMsg, UnlockingPosition}, msg::{ExtensionQueryMsg, VaultStandardQueryMsg::VaultExtension}, }; -use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use mars_account_nft::{ msg::{ ExecuteMsg as NftExecuteMsg, InstantiateMsg as NftInstantiateMsg, QueryMsg as NftQueryMsg, diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index f3ff02c3d..bc191cf0a 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -2,7 +2,6 @@ use std::ops::{Add, Mul}; use cosmwasm_std::{coin, coins, Addr, Coin, Decimal, Uint128}; use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; -use mars_math::{FractionMath, Fractional}; use mars_mock_oracle::msg::CoinPrice; use mars_rover::{ adapters::vault::VaultConfig, @@ -567,7 +566,7 @@ fn debt_value() { let user_a_owed_atom = red_bank_atom_debt .amount - .checked_mul_ceil(Fractional(user_a_debt_shares_atom, red_bank_atom_res.shares)) + .checked_mul_ceil((user_a_debt_shares_atom, red_bank_atom_res.shares)) .unwrap(); let user_a_owed_atom_value = user_a_owed_atom.checked_mul_floor(uatom_info.price).unwrap(); diff --git a/contracts/credit-manager/tests/test_vault_query_value.rs b/contracts/credit-manager/tests/test_vault_query_value.rs index 7c5322f5d..b25ea697c 100644 --- a/contracts/credit-manager/tests/test_vault_query_value.rs +++ b/contracts/credit-manager/tests/test_vault_query_value.rs @@ -1,7 +1,6 @@ use std::ops::Div; use cosmwasm_std::{Addr, Uint128}; -use mars_math::FractionMath; use mars_mock_vault::contract::STARTING_VAULT_SHARES; use mars_rover::{ adapters::vault::{Vault, VaultAmount, VaultPosition, VaultPositionAmount}, diff --git a/contracts/health/Cargo.toml b/contracts/health/Cargo.toml index ebe855036..a3abf1b2e 100644 --- a/contracts/health/Cargo.toml +++ b/contracts/health/Cargo.toml @@ -31,10 +31,9 @@ thiserror = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -cosmwasm-vault-standard = { workspace = true } cw-multi-test = { workspace = true } cw-utils = { workspace = true } -mars-math = { workspace = true } +cw-vault-standard = { workspace = true } mars-mock-credit-manager = { workspace = true } mars-mock-oracle = { workspace = true } mars-mock-red-bank = { workspace = true } diff --git a/contracts/health/tests/helpers/mock_env.rs b/contracts/health/tests/helpers/mock_env.rs index a3f17252d..4e5ea78eb 100644 --- a/contracts/health/tests/helpers/mock_env.rs +++ b/contracts/health/tests/helpers/mock_env.rs @@ -1,9 +1,9 @@ use anyhow::Result as AnyResult; use cosmwasm_std::{Addr, Coin, Decimal, Empty, StdResult, Uint128}; -use cosmwasm_vault_standard::{ +use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; +use cw_vault_standard::{ VaultInfoResponse, VaultStandardExecuteMsg::Deposit, VaultStandardQueryMsg::Info, }; -use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use mars_mock_credit_manager::msg::ExecuteMsg::{ SetAllowedCoins, SetPositionsResponse, SetVaultConfig, }; diff --git a/contracts/health/tests/test_compute_health.rs b/contracts/health/tests/test_compute_health.rs index b4942fdc8..b65215bb2 100644 --- a/contracts/health/tests/test_compute_health.rs +++ b/contracts/health/tests/test_compute_health.rs @@ -1,5 +1,4 @@ use cosmwasm_std::{Coin, Decimal, StdError, Uint128}; -use mars_math::FractionMath; use mars_mock_red_bank::msg::CoinMarketInfo; use mars_rover::{ adapters::vault::{ diff --git a/contracts/health/tests/test_update_config.rs b/contracts/health/tests/test_update_config.rs index bb6740c3d..4c3d4df6b 100644 --- a/contracts/health/tests/test_update_config.rs +++ b/contracts/health/tests/test_update_config.rs @@ -29,7 +29,12 @@ fn raises_on_invalid_config() { let err: HealthError = mock.update_config(&mock.deployer.clone(), &new_cm_addr).unwrap_err().downcast().unwrap(); - assert_eq!(err, Std(StdError::generic_err("Invalid input: human address too short"))); + assert_eq!( + err, + Std(StdError::generic_err( + "Invalid input: human address too short for this mock implementation (must be >= 3)." + )) + ); } #[test] diff --git a/contracts/mock-health/Cargo.toml b/contracts/mock-health/Cargo.toml index 9a04d18f8..18879c5fe 100644 --- a/contracts/mock-health/Cargo.toml +++ b/contracts/mock-health/Cargo.toml @@ -19,7 +19,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -mars-rover-health-types = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +mars-rover-health-types = { workspace = true } diff --git a/contracts/mock-vault/Cargo.toml b/contracts/mock-vault/Cargo.toml index 37a84fc6e..0ebd8d9fe 100644 --- a/contracts/mock-vault/Cargo.toml +++ b/contracts/mock-vault/Cargo.toml @@ -19,10 +19,10 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cosmwasm-vault-standard = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -mars-rover = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +cw-vault-standard = { workspace = true } +mars-rover = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index 1391ac69c..b49f815f6 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{coin, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; -use cosmwasm_vault_standard::{ +use cw_vault_standard::{ extensions::{ force_unlock::ForceUnlockExecuteMsg, lockup::{LockupExecuteMsg, LockupQueryMsg}, diff --git a/contracts/mock-vault/src/msg.rs b/contracts/mock-vault/src/msg.rs index ad170542e..c619eb9cc 100644 --- a/contracts/mock-vault/src/msg.rs +++ b/contracts/mock-vault/src/msg.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::cw_serde; use cw_utils::Duration; use mars_rover::adapters::oracle::OracleUnchecked; -// Remaining messages in cosmwasm-vault-standard +// Remaining messages in cw-vault-standard #[cw_serde] pub struct InstantiateMsg { /// Denom for vault token diff --git a/contracts/mock-vault/src/query.rs b/contracts/mock-vault/src/query.rs index ea9dee35d..e269ebc56 100644 --- a/contracts/mock-vault/src/query.rs +++ b/contracts/mock-vault/src/query.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Deps, Order, StdError, StdResult, Storage, Uint128}; -use cosmwasm_vault_standard::{extensions::lockup::UnlockingPosition, msg::VaultInfoResponse}; use cw_utils::Duration; +use cw_vault_standard::{extensions::lockup::UnlockingPosition, msg::VaultInfoResponse}; use crate::{ error::{ContractError::NotLockingVault, ContractResult}, diff --git a/contracts/mock-vault/src/state.rs b/contracts/mock-vault/src/state.rs index 074209015..20fabdbc8 100644 --- a/contracts/mock-vault/src/state.rs +++ b/contracts/mock-vault/src/state.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Addr, Coin, Uint128}; -use cosmwasm_vault_standard::extensions::lockup::UnlockingPosition; use cw_storage_plus::{Item, Map}; use cw_utils::Duration; +use cw_vault_standard::extensions::lockup::UnlockingPosition; use mars_rover::adapters::oracle::Oracle; pub const VAULT_TOKEN_DENOM: Item = Item::new("vault_token_denom"); diff --git a/contracts/mock-vault/src/unlock.rs b/contracts/mock-vault/src/unlock.rs index b1f48dd7e..a8ed1fb83 100644 --- a/contracts/mock-vault/src/unlock.rs +++ b/contracts/mock-vault/src/unlock.rs @@ -1,10 +1,10 @@ use cosmwasm_std::{ Addr, BankMsg, Coin, CosmosMsg, DepsMut, Env, Event, MessageInfo, Response, StdResult, Uint128, }; -use cosmwasm_vault_standard::extensions::lockup::{ +use cw_utils::{Duration, Expiration}; +use cw_vault_standard::extensions::lockup::{ UnlockingPosition, UNLOCKING_POSITION_ATTR_KEY, UNLOCKING_POSITION_CREATED_EVENT_TYPE, }; -use cw_utils::{Duration, Expiration}; use crate::{ error::ContractError, diff --git a/contracts/swapper/mock/Cargo.toml b/contracts/swapper/mock/Cargo.toml index fb2c19163..6c73a6207 100644 --- a/contracts/swapper/mock/Cargo.toml +++ b/contracts/swapper/mock/Cargo.toml @@ -19,10 +19,10 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -mars-rover = { workspace = true } -thiserror = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +mars-rover = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/contracts/zapper/base/Cargo.toml b/contracts/zapper/base/Cargo.toml index 97d5a8322..4b59a42ed 100644 --- a/contracts/zapper/base/Cargo.toml +++ b/contracts/zapper/base/Cargo.toml @@ -19,10 +19,10 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-dex = { workspace = true } -cw-utils = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-dex = { workspace = true } +cw-utils = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/zapper/mock/Cargo.toml b/contracts/zapper/mock/Cargo.toml index 5c6e59113..9be0a109b 100644 --- a/contracts/zapper/mock/Cargo.toml +++ b/contracts/zapper/mock/Cargo.toml @@ -19,9 +19,9 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -mars-rover = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +mars-rover = { workspace = true } +thiserror = { workspace = true } diff --git a/packages/health-computer/Cargo.toml b/packages/health-computer/Cargo.toml index 0a375b7c5..371712010 100644 --- a/packages/health-computer/Cargo.toml +++ b/packages/health-computer/Cargo.toml @@ -6,13 +6,6 @@ edition = { workspace = true } documentation = { workspace = true } keywords = { workspace = true } -# Current bug in wasm-pack does not allow workspace inheritance for these properties. -# Revert to workspace inheritance when https://github.com/rustwasm/wasm-pack/pull/1185 -# has been included in the next wasm-pack version. -license = "GPL-3.0-or-later" -repository = "https://github.com/mars-protocol/rover" -homepage = "https://marsprotocol.io" - [lib] crate-type = ["cdylib", "rlib"] doctest = false @@ -24,8 +17,7 @@ default = ["console_error_panic_hook"] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-vault-standard = { workspace = true } -mars-math = { workspace = true } +cw-vault-standard = { workspace = true } mars-red-bank-types = { workspace = true } mars-rover = { workspace = true } mars-rover-health-types = { workspace = true } diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index e22482854..44290c5c4 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -1,6 +1,5 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Decimal, Uint128}; -use mars_math::FractionMath; use mars_red_bank_types::red_bank::Market; use mars_rover::msg::query::Positions; use mars_rover_health_types::{ diff --git a/packages/health-computer/tests/test_health_scenarios.rs b/packages/health-computer/tests/test_health_scenarios.rs index e9c81a952..d0350aea1 100644 --- a/packages/health-computer/tests/test_health_scenarios.rs +++ b/packages/health-computer/tests/test_health_scenarios.rs @@ -1,7 +1,6 @@ use std::{collections::HashMap, ops::Add, str::FromStr}; use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; -use mars_math::FractionMath; use mars_rover::{ adapters::vault::{ CoinValue, LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultConfig, diff --git a/packages/health-types/Cargo.toml b/packages/health-types/Cargo.toml index fb773e62c..dd87f341f 100644 --- a/packages/health-types/Cargo.toml +++ b/packages/health-types/Cargo.toml @@ -17,8 +17,7 @@ doctest = false backtraces = ["cosmwasm-std/backtraces"] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -mars-math = { workspace = true } -mars-owner = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +mars-owner = { workspace = true } +thiserror = { workspace = true } diff --git a/packages/health-types/src/error.rs b/packages/health-types/src/error.rs index e9c57660c..f4cfd17bc 100644 --- a/packages/health-types/src/error.rs +++ b/packages/health-types/src/error.rs @@ -1,5 +1,4 @@ -use cosmwasm_std::{CheckedFromRatioError, OverflowError, StdError}; -use mars_math::CheckedMultiplyFractionError; +use cosmwasm_std::{CheckedFromRatioError, CheckedMultiplyFractionError, OverflowError, StdError}; use mars_owner::OwnerError; use thiserror::Error; diff --git a/packages/math/Cargo.toml b/packages/math/Cargo.toml deleted file mode 100644 index 52b3d7f95..000000000 --- a/packages/math/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "mars-math" -version = { workspace = true } -authors = { workspace = true } -license = { workspace = true } -edition = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -documentation = { workspace = true } -keywords = { workspace = true } - -[lib] -doctest = false - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cosmwasm-std = { workspace = true } -thiserror = { workspace = true } diff --git a/packages/math/src/fraction.rs b/packages/math/src/fraction.rs deleted file mode 100644 index 668eef187..000000000 --- a/packages/math/src/fraction.rs +++ /dev/null @@ -1,94 +0,0 @@ -use cosmwasm_std::{ConversionOverflowError, DivideByZeroError, Fraction, OverflowError, Uint128}; -use thiserror::Error; - -// TODO: Delete when merged: https://github.com/CosmWasm/cosmwasm/pull/1566 - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum CheckedMultiplyFractionError { - #[error("{0}")] - DivideByZero(#[from] DivideByZeroError), - - #[error("{0}")] - ConversionOverflow(#[from] ConversionOverflowError), - - #[error("{0}")] - Overflow(#[from] OverflowError), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Fractional(pub T, pub T); - -impl + PartialEq> Fraction for Fractional { - fn numerator(&self) -> T { - self.0 - } - fn denominator(&self) -> T { - self.1 - } - fn inv(&self) -> Option { - if self.numerator() == 0u8.into() { - None - } else { - Some(Fractional(self.1, self.0)) - } - } -} - -pub trait FractionMath { - fn checked_mul_floor, T: Into>( - self, - rhs: F, - ) -> Result - where - Self: Sized; - - fn checked_mul_ceil + Clone, T: Into>( - self, - rhs: F, - ) -> Result - where - Self: Sized; - - fn checked_div_floor, T: Into>( - self, - rhs: F, - ) -> Result - where - Self: Sized; -} - -impl FractionMath for Uint128 { - fn checked_mul_floor, T: Into>( - self, - rhs: F, - ) -> Result { - let divisor = rhs.denominator().into(); - let res = self.full_mul(rhs.numerator().into()).checked_div(divisor.into())?; - Ok(res.try_into()?) - } - - fn checked_mul_ceil + Clone, T: Into>( - self, - rhs: F, - ) -> Result { - let floor_result = self.checked_mul_floor(rhs.clone())?; - let divisor = rhs.denominator().into(); - let remainder = self.full_mul(rhs.numerator().into()).checked_rem(divisor.into())?; - if !remainder.is_zero() { - Ok(Uint128::one().checked_add(floor_result)?) - } else { - Ok(floor_result) - } - } - - // Note: Should not use .inv() on decimal and then just use self.checked_mul_floor(inverted). - // Inverting a decimal does large number math and a loss of precision causing off by one bugs. - fn checked_div_floor, T: Into>( - self, - rhs: F, - ) -> Result { - let divisor = rhs.numerator().into(); - let res = self.full_mul(rhs.denominator().into()).checked_div(divisor.into())?; - Ok(res.try_into()?) - } -} diff --git a/packages/math/src/lib.rs b/packages/math/src/lib.rs deleted file mode 100644 index 5108f1928..000000000 --- a/packages/math/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod fraction; - -pub use self::fraction::*; diff --git a/packages/math/tests/test_div_floor.rs b/packages/math/tests/test_div_floor.rs deleted file mode 100644 index 5a63f8167..000000000 --- a/packages/math/tests/test_div_floor.rs +++ /dev/null @@ -1,71 +0,0 @@ -use cosmwasm_std::{ConversionOverflowError, Decimal, DivideByZeroError, Uint128}; -use mars_math::{ - CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}, - FractionMath, Fractional, -}; - -#[test] -fn div_floor_raises_with_zero() { - let fraction = Fractional(Uint128::zero(), Uint128::new(21)); - assert_eq!( - Uint128::new(123456).checked_div_floor(fraction), - Err(DivideByZero(DivideByZeroError { - operand: "2592576".to_string() - })), - ); -} - -#[test] -fn div_floor_does_nothing_with_one() { - let fraction = Fractional(Uint128::one(), Uint128::one()); - let res = Uint128::new(123456).checked_div_floor(fraction).unwrap(); - assert_eq!(Uint128::new(123456), res) -} - -#[test] -fn div_floor_rounds_down_with_normal_case() { - let fraction = Fractional(5u128, 21u128); - let res = Uint128::new(123456).checked_div_floor(fraction).unwrap(); // 518515.2 - assert_eq!(Uint128::new(518515), res) -} - -#[test] -fn div_floor_does_not_round_on_even_divide() { - let fraction = Fractional(5u128, 2u128); - let res = Uint128::new(25).checked_div_floor(fraction).unwrap(); - - assert_eq!(Uint128::new(10), res) -} - -#[test] -fn div_floor_works_when_operation_temporarily_takes_above_max() { - let fraction = Fractional(21u128, 8u128); - let res = Uint128::MAX.checked_div_floor(fraction).unwrap(); // 129_631_377_874_643_224_176_523_659_974_006_937_697.1428 - assert_eq!(Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_697), res) -} - -#[test] -fn div_floor_works_with_decimal() { - let decimal = Decimal::from_ratio(21u128, 8u128); - let res = Uint128::new(123456).checked_div_floor(decimal).unwrap(); // 47030.857 - assert_eq!(Uint128::new(47030), res) -} - -#[test] -fn div_floor_works_with_decimal_evenly() { - let res = Uint128::new(60).checked_div_floor(Decimal::from_atomics(6u128, 0).unwrap()).unwrap(); - assert_eq!(res, Uint128::new(10)); -} - -#[test] -fn checked_div_floor_does_not_panic_on_overflow() { - let fraction = Fractional(8u128, 21u128); - assert_eq!( - Uint128::MAX.checked_div_floor(fraction), - Err(ConversionOverflow(ConversionOverflowError { - source_type: "Uint256", - target_type: "Uint128", - value: "893241213167463466591358344508391555069".to_string() - })), - ); -} diff --git a/packages/math/tests/test_mul_ceil.rs b/packages/math/tests/test_mul_ceil.rs deleted file mode 100644 index ccd22af54..000000000 --- a/packages/math/tests/test_mul_ceil.rs +++ /dev/null @@ -1,85 +0,0 @@ -use cosmwasm_std::{ConversionOverflowError, Decimal, DivideByZeroError, Uint128}; -use mars_math::{ - CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}, - FractionMath, Fractional, -}; - -#[test] -fn mul_ceil_works_with_zero() { - let fraction = Fractional(Uint128::zero(), Uint128::new(21)); - let res = Uint128::new(123456).checked_mul_ceil(fraction).unwrap(); - assert_eq!(Uint128::zero(), res) -} - -#[test] -fn mul_ceil_does_nothing_with_one() { - let fraction = Fractional(Uint128::one(), Uint128::one()); - let res = Uint128::new(123456).checked_mul_ceil(fraction).unwrap(); - assert_eq!(Uint128::new(123456), res) -} - -#[test] -fn mul_ceil_rounds_up_with_normal_case() { - let fraction = Fractional(8u128, 21u128); - let res = Uint128::new(123456).checked_mul_ceil(fraction).unwrap(); // 47030.857 - assert_eq!(Uint128::new(47031), res) -} - -#[test] -fn mul_ceil_does_not_round_on_even_divide() { - let fraction = Fractional(2u128, 5u128); - let res = Uint128::new(25).checked_mul_ceil(fraction).unwrap(); - assert_eq!(Uint128::new(10), res) -} - -#[test] -fn mul_ceil_works_when_operation_temporarily_takes_above_max() { - let fraction = Fractional(8u128, 21u128); - let res = Uint128::MAX.checked_mul_ceil(fraction).unwrap(); // 129_631_377_874_643_224_176_523_659_974_006_937_697.1428 - assert_eq!(Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_698), res) -} - -#[test] -fn mul_ceil_works_with_decimal() { - let decimal = Decimal::from_ratio(8u128, 21u128); - let res = Uint128::new(123456).checked_mul_ceil(decimal).unwrap(); // 47030.857 - assert_eq!(Uint128::new(47031), res) -} - -#[test] -#[should_panic(expected = "ConversionOverflowError")] -fn mul_ceil_panics_on_overflow() { - let fraction = Fractional(21u128, 8u128); - Uint128::MAX.checked_mul_ceil(fraction).unwrap(); -} - -#[test] -fn checked_mul_ceil_does_not_panic_on_overflow() { - let fraction = Fractional(21u128, 8u128); - assert_eq!( - Uint128::MAX.checked_mul_ceil(fraction), - Err(ConversionOverflow(ConversionOverflowError { - source_type: "Uint256", - target_type: "Uint128", - value: "893241213167463466591358344508391555069".to_string() // raises prior to rounding up - })), - ); -} - -#[test] -#[should_panic(expected = "DivideByZeroError")] -fn mul_ceil_panics_on_zero_div() { - let fraction = Fractional(21u128, 0u128); - Uint128::new(123456).checked_mul_ceil(fraction).unwrap(); -} - -#[test] -fn checked_mul_ceil_does_not_panic_on_zero_div() { - let fraction = Fractional(21u128, 0u128); - assert_eq!( - Uint128::new(123456).checked_mul_ceil(fraction), - Err(DivideByZero(DivideByZeroError { - operand: "2592576".to_string() - })), - ); -} diff --git a/packages/math/tests/test_mul_floor.rs b/packages/math/tests/test_mul_floor.rs deleted file mode 100644 index 4b848fb3d..000000000 --- a/packages/math/tests/test_mul_floor.rs +++ /dev/null @@ -1,72 +0,0 @@ -use cosmwasm_std::{ConversionOverflowError, Decimal, DivideByZeroError, Uint128}; -use mars_math::{ - CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}, - FractionMath, Fractional, -}; - -#[test] -fn mul_floor_works_with_zero() { - let fraction = Fractional(Uint128::zero(), Uint128::new(21)); - let res = Uint128::new(123456).checked_mul_floor(fraction).unwrap(); - assert_eq!(Uint128::zero(), res) -} - -#[test] -fn mul_floor_does_nothing_with_one() { - let fraction = Fractional(Uint128::one(), Uint128::one()); - let res = Uint128::new(123456).checked_mul_floor(fraction).unwrap(); - assert_eq!(Uint128::new(123456), res) -} - -#[test] -fn mul_floor_rounds_down_with_normal_case() { - let fraction = Fractional(8u128, 21u128); - let res = Uint128::new(123456).checked_mul_floor(fraction).unwrap(); // 47030.857 - assert_eq!(Uint128::new(47030), res) -} - -#[test] -fn mul_floor_does_not_round_on_even_divide() { - let fraction = Fractional(2u128, 5u128); - let res = Uint128::new(25).checked_mul_floor(fraction).unwrap(); - - assert_eq!(Uint128::new(10), res) -} - -#[test] -fn mul_floor_works_when_operation_temporarily_takes_above_max() { - let fraction = Fractional(8u128, 21u128); - let res = Uint128::MAX.checked_mul_floor(fraction).unwrap(); // 129_631_377_874_643_224_176_523_659_974_006_937_697.1428 - assert_eq!(Uint128::new(129_631_377_874_643_224_176_523_659_974_006_937_697), res) -} - -#[test] -fn mul_floor_works_with_decimal() { - let decimal = Decimal::from_ratio(8u128, 21u128); - let res = Uint128::new(123456).checked_mul_floor(decimal).unwrap(); // 47030.857 - assert_eq!(Uint128::new(47030), res) -} - -#[test] -fn checked_mul_floor_does_not_panic_on_overflow() { - let fraction = Fractional(21u128, 8u128); - assert_eq!( - Uint128::MAX.checked_mul_floor(fraction), - Err(ConversionOverflow(ConversionOverflowError { - source_type: "Uint256", - target_type: "Uint128", - value: "893241213167463466591358344508391555069".to_string() - })), - ); -} - -#[test] -fn checked_mul_floor_does_not_panic_on_zero_div() { - let fraction = Fractional(21u128, 0u128); - assert_eq!( - Uint128::new(123456).checked_mul_floor(fraction), - Err(DivideByZero(DivideByZeroError { - operand: "2592576".to_string() - })), - ); -} diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index d8903b4e6..237937f20 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -20,14 +20,13 @@ backtraces = ["cosmwasm-std/backtraces"] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } -cosmwasm-vault-standard = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } +cw-vault-standard = { workspace = true } mars-account-nft = { workspace = true } mars-rover-health-types = { workspace = true } -mars-math = { workspace = true } mars-red-bank-types = { workspace = true } mars-owner = { workspace = true } schemars = { workspace = true } diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs index d95bb96f9..dda445b99 100644 --- a/packages/rover/src/adapters/oracle.rs +++ b/packages/rover/src/adapters/oracle.rs @@ -1,6 +1,5 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, Coin, QuerierWrapper, StdResult, Uint128}; -use mars_math::FractionMath; use mars_red_bank_types::oracle::{PriceResponse, QueryMsg}; use crate::error::ContractResult; diff --git a/packages/rover/src/adapters/vault/base.rs b/packages/rover/src/adapters/vault/base.rs index 8353c90ee..2d4ee0b03 100644 --- a/packages/rover/src/adapters/vault/base.rs +++ b/packages/rover/src/adapters/vault/base.rs @@ -5,7 +5,8 @@ use cosmwasm_std::{ to_binary, Addr, Api, BalanceResponse, BankQuery, Coin, CosmosMsg, QuerierWrapper, QueryRequest, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, }; -use cosmwasm_vault_standard::{ +use cw_utils::Duration; +use cw_vault_standard::{ extensions::{ force_unlock::ForceUnlockExecuteMsg::{ForceRedeem, ForceWithdrawUnlocking}, lockup::{ @@ -18,7 +19,6 @@ use cosmwasm_vault_standard::{ msg::{ExtensionExecuteMsg, ExtensionQueryMsg, VaultStandardExecuteMsg, VaultStandardQueryMsg}, VaultInfoResponse, }; -use cw_utils::Duration; use crate::traits::Stringify; diff --git a/packages/rover/src/adapters/vault/position.rs b/packages/rover/src/adapters/vault/position.rs index 7a0fa9fad..498931666 100644 --- a/packages/rover/src/adapters/vault/position.rs +++ b/packages/rover/src/adapters/vault/position.rs @@ -1,6 +1,5 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, QuerierWrapper, StdError, StdResult, Uint128}; -use mars_math::FractionMath; use crate::adapters::{ oracle::Oracle, diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 39fbdcf18..e7fec8900 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -1,8 +1,7 @@ use cosmwasm_std::{ - CheckedFromRatioError, CheckedMultiplyRatioError, Coin, DecimalRangeExceeded, OverflowError, - StdError, Uint128, + CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError, Coin, + DecimalRangeExceeded, OverflowError, StdError, Uint128, }; -use mars_math::CheckedMultiplyFractionError; use mars_owner::OwnerError; use thiserror::Error; diff --git a/packages/rover/src/extensions/reply.rs b/packages/rover/src/extensions/reply.rs index 44cb534e1..3e946f5ef 100644 --- a/packages/rover/src/extensions/reply.rs +++ b/packages/rover/src/extensions/reply.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Reply, StdError, StdResult, SubMsgResult}; -use cosmwasm_vault_standard::extensions::lockup::{ +use cw_vault_standard::extensions::lockup::{ UNLOCKING_POSITION_ATTR_KEY, UNLOCKING_POSITION_CREATED_EVENT_TYPE, }; diff --git a/scripts/health/example-node.ts b/scripts/health/example-node.ts index 598c82504..56f24cc96 100644 --- a/scripts/health/example-node.ts +++ b/scripts/health/example-node.ts @@ -1,12 +1,11 @@ import { DataFetcher } from './DataFetcher' -import { Storage } from '../deploy/base/storage' import { compute_health_js } from './pkg-node' import { osmosisTestnetConfig } from '../deploy/osmosis/testnet-config' +import OsmosisAddresses from '../deploy/addresses/osmo-test-4.json' ;(async () => { - const storage = await Storage.load(osmosisTestnetConfig.chain.id, 'testnet-deployer-owner') const dataFetcher = new DataFetcher( compute_health_js, - storage.addresses.creditManager!, + OsmosisAddresses.creditManager, osmosisTestnetConfig.oracle.addr, osmosisTestnetConfig.redBank.addr, osmosisTestnetConfig.chain.rpcEndpoint, diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js index 0282aaaf8..8425efaba 100644 --- a/scripts/health/pkg-node/index.js +++ b/scripts/health/pkg-node/index.js @@ -325,12 +325,12 @@ module.exports.__wbindgen_string_new = function (arg0, arg1) { return addHeapObject(ret) } -module.exports.__wbg_getwithrefkey_15c62c2b8546208d = function (arg0, arg1) { +module.exports.__wbg_getwithrefkey_5e6d9547403deab8 = function (arg0, arg1) { const ret = getObject(arg0)[getObject(arg1)] return addHeapObject(ret) } -module.exports.__wbg_set_20cbc34131e76824 = function (arg0, arg1, arg2) { +module.exports.__wbg_set_841ac57cff3d672b = function (arg0, arg1, arg2) { getObject(arg0)[takeObject(arg1)] = takeObject(arg2) } diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index 05baa1527cfa1006ba508ae047c09fea21b32d35..509278b34c5290bc2af46755abf75a1056bcc890 100644 GIT binary patch delta 23201 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 KKUuU?x6YKkc;B%zg>U}@r`M7Q3d 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 diff --git a/scripts/health/pkg-node/package.json b/scripts/health/pkg-node/package.json index cda469b01..0708ff70e 100644 --- a/scripts/health/pkg-node/package.json +++ b/scripts/health/pkg-node/package.json @@ -6,18 +6,12 @@ "Piotr Babel " ], "version": "1.0.0", - "license": "GPL-3.0-or-later", - "repository": { - "type": "git", - "url": "https://github.com/mars-protocol/rover" - }, "files": [ "index_bg.wasm", "index.js", "index.d.ts" ], "main": "index.js", - "homepage": "https://marsprotocol.io", "types": "index.d.ts", "keywords": [ "mars", diff --git a/scripts/health/pkg-web/index.js b/scripts/health/pkg-web/index.js index ee75177a2..6e3f91f83 100644 --- a/scripts/health/pkg-web/index.js +++ b/scripts/health/pkg-web/index.js @@ -337,11 +337,11 @@ function getImports() { const ret = getStringFromWasm0(arg0, arg1) return addHeapObject(ret) } - imports.wbg.__wbg_getwithrefkey_15c62c2b8546208d = function (arg0, arg1) { + imports.wbg.__wbg_getwithrefkey_5e6d9547403deab8 = function (arg0, arg1) { const ret = getObject(arg0)[getObject(arg1)] return addHeapObject(ret) } - imports.wbg.__wbg_set_20cbc34131e76824 = function (arg0, arg1, arg2) { + imports.wbg.__wbg_set_841ac57cff3d672b = function (arg0, arg1, arg2) { getObject(arg0)[takeObject(arg1)] = takeObject(arg2) } imports.wbg.__wbg_get_27fe3dac1c4d0224 = function (arg0, arg1) { diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm index 8c8394ce00e0d6cf18765d8a3952a92d1109dfd0..b7ee3bd97c1a2ba4e5f0b81ff811f5148b673aaa 100644 GIT binary patch delta 22601 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 delta 22859 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 diff --git a/scripts/health/pkg-web/package.json b/scripts/health/pkg-web/package.json index 5b4fb8ae0..351494756 100644 --- a/scripts/health/pkg-web/package.json +++ b/scripts/health/pkg-web/package.json @@ -6,18 +6,12 @@ "Piotr Babel " ], "version": "1.0.0", - "license": "GPL-3.0-or-later", - "repository": { - "type": "git", - "url": "https://github.com/mars-protocol/rover" - }, "files": [ "index_bg.wasm", "index.js", "index.d.ts" ], "module": "index.js", - "homepage": "https://marsprotocol.io", "types": "index.d.ts", "sideEffects": false, "keywords": [ diff --git a/scripts/package.json b/scripts/package.json index a237c28da..301230327 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -20,12 +20,12 @@ "format-check": "prettier --check ." }, "dependencies": { - "@cosmjs/cosmwasm-stargate": "^0.29.5", - "@cosmjs/stargate": "^0.29.5", - "@cosmwasm/ts-codegen": "^0.24.0", + "@cosmjs/cosmwasm-stargate": "^0.30.0", + "@cosmjs/stargate": "^0.30.0", + "@cosmwasm/ts-codegen": "^0.25.2", "chalk": "4.1.2", "copyfiles": "^2.4.1", - "cosmjs-types": "^0.6.1", + "cosmjs-types": "^0.7.1", "lodash": "^4.17.21", "long": "^5.2.1", "prepend-file": "^2.0.1", @@ -35,11 +35,11 @@ "@babel/preset-env": "^7.20.2", "@babel/preset-typescript": "^7.18.6", "@types/jest": "^29.4.0", - "@typescript-eslint/eslint-plugin": "^5.51.0", - "@typescript-eslint/parser": "^5.51.0", - "eslint": "^8.33.0", - "eslint-config-prettier": "^8.6.0", - "jest": "^29.4.2", + "@typescript-eslint/eslint-plugin": "^5.54.1", + "@typescript-eslint/parser": "^5.54.1", + "eslint": "^8.36.0", + "eslint-config-prettier": "^8.7.0", + "jest": "^29.5.0", "prettier": "^2.8.4", "typescript": "^4.9.5" } diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts index 427066f2b..c7c7b97b8 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts index 5ca93434a..451848cd4 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts index 8ea571906..5a1150611 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index 54dd22b63..39b5d5754 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/bundle.ts b/scripts/types/generated/mars-account-nft/bundle.ts index 3fef9c899..4659022b2 100644 --- a/scripts/types/generated/mars-account-nft/bundle.ts +++ b/scripts/types/generated/mars-account-nft/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 2925331d0..3a0bc9fb6 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 03d649f32..43a029360 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 7f730608d..910ae1142 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index df02d60d8..6c3af344a 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-credit-manager/bundle.ts b/scripts/types/generated/mars-credit-manager/bundle.ts index fbcf26e0d..bfd27b400 100644 --- a/scripts/types/generated/mars-credit-manager/bundle.ts +++ b/scripts/types/generated/mars-credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index 1888a90ca..8a6a05f77 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index 5b864520a..2e42564c6 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index 7c6fc1570..b09a9940c 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 190757452..1b4082298 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/bundle.ts b/scripts/types/generated/mars-mock-credit-manager/bundle.ts index e6069a3ac..81f6963d5 100644 --- a/scripts/types/generated/mars-mock-credit-manager/bundle.ts +++ b/scripts/types/generated/mars-mock-credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts index c276f83c2..88e05d8e0 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts index fb6c923b9..45e3f7ca6 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts index d279c8513..bbe28d32c 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts index 8e19a4f82..7539a5a7c 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/bundle.ts b/scripts/types/generated/mars-mock-oracle/bundle.ts index c42be8bae..b7754dbf9 100644 --- a/scripts/types/generated/mars-mock-oracle/bundle.ts +++ b/scripts/types/generated/mars-mock-oracle/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index 555762256..a5c8c392b 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts index ebdeb9317..c7e3873b1 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index 48801171c..447e5cd31 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index 8a083bee5..d946d23b6 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/bundle.ts b/scripts/types/generated/mars-mock-red-bank/bundle.ts index 97c046386..3273e8c47 100644 --- a/scripts/types/generated/mars-mock-red-bank/bundle.ts +++ b/scripts/types/generated/mars-mock-red-bank/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts index eac26ad70..dc4ed49b8 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts index 08add0925..1a3bd82ae 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts index 2d900f88e..ad8104ca1 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts index a66861b5b..ff9ebbe5a 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/bundle.ts b/scripts/types/generated/mars-mock-vault/bundle.ts index 2b9660d97..16068b735 100644 --- a/scripts/types/generated/mars-mock-vault/bundle.ts +++ b/scripts/types/generated/mars-mock-vault/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts index 44f1cfce5..92617b6fb 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts index 44f1cfce5..92617b6fb 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts index 5360f5142..c5fd6501a 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts index 00293fc76..25aa78ea7 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-computer/bundle.ts b/scripts/types/generated/mars-rover-health-computer/bundle.ts index 4304094d6..b6745bfa0 100644 --- a/scripts/types/generated/mars-rover-health-computer/bundle.ts +++ b/scripts/types/generated/mars-rover-health-computer/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts index 547189d9a..b6e85a462 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts index db2d977e5..c56b2cae0 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts index 5136fd146..f68f8e43a 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts index bf8a8c4bc..be9992a8e 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-types/bundle.ts b/scripts/types/generated/mars-rover-health-types/bundle.ts index 26a907af8..eb595dcc2 100644 --- a/scripts/types/generated/mars-rover-health-types/bundle.ts +++ b/scripts/types/generated/mars-rover-health-types/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts index 00e8bc426..bc1140d90 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts index 86c7378c4..12569a5d0 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts index f9597c3af..1384359a8 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts index 2524e475e..06111224b 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/bundle.ts b/scripts/types/generated/mars-swapper-base/bundle.ts index 0e9e9246a..b1e02ac0e 100644 --- a/scripts/types/generated/mars-swapper-base/bundle.ts +++ b/scripts/types/generated/mars-swapper-base/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-zapper-base/MarsZapperBase.client.ts b/scripts/types/generated/mars-zapper-base/MarsZapperBase.client.ts index f00619dea..3d3a07ed9 100644 --- a/scripts/types/generated/mars-zapper-base/MarsZapperBase.client.ts +++ b/scripts/types/generated/mars-zapper-base/MarsZapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-zapper-base/MarsZapperBase.message-composer.ts b/scripts/types/generated/mars-zapper-base/MarsZapperBase.message-composer.ts index 9276335a1..6fdf18ecf 100644 --- a/scripts/types/generated/mars-zapper-base/MarsZapperBase.message-composer.ts +++ b/scripts/types/generated/mars-zapper-base/MarsZapperBase.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-zapper-base/MarsZapperBase.react-query.ts b/scripts/types/generated/mars-zapper-base/MarsZapperBase.react-query.ts index 853d9806d..e4d82fcef 100644 --- a/scripts/types/generated/mars-zapper-base/MarsZapperBase.react-query.ts +++ b/scripts/types/generated/mars-zapper-base/MarsZapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-zapper-base/MarsZapperBase.types.ts b/scripts/types/generated/mars-zapper-base/MarsZapperBase.types.ts index 478ca253f..42512cda2 100644 --- a/scripts/types/generated/mars-zapper-base/MarsZapperBase.types.ts +++ b/scripts/types/generated/mars-zapper-base/MarsZapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-zapper-base/bundle.ts b/scripts/types/generated/mars-zapper-base/bundle.ts index 4dbe416d5..ae32e69ea 100644 --- a/scripts/types/generated/mars-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-zapper-base/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.24.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/yarn.lock b/scripts/yarn.lock index a8147bdbb..36b448f54 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1148,143 +1148,143 @@ "@noble/hashes" "^1.0.0" protobufjs "^6.8.8" -"@cosmjs/amino@^0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.29.5.tgz#053b4739a90b15b9e2b781ccd484faf64bd49aec" - integrity sha512-Qo8jpC0BiziTSUqpkNatBcwtKNhCovUnFul9SlT/74JUCdLYaeG5hxr3q1cssQt++l4LvlcpF+OUXL48XjNjLw== - dependencies: - "@cosmjs/crypto" "^0.29.5" - "@cosmjs/encoding" "^0.29.5" - "@cosmjs/math" "^0.29.5" - "@cosmjs/utils" "^0.29.5" - -"@cosmjs/cosmwasm-stargate@^0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.29.5.tgz#3f257da682658833e0f4eb9e8ff758e4d927663a" - integrity sha512-TNdSvm2tEE3XMCuxHxquzls56t40hC8qnLeYJWHsY2ECZmRK3KrnpRReEr7N7bLtODToK7X/riYrV0JaYxjrYA== - dependencies: - "@cosmjs/amino" "^0.29.5" - "@cosmjs/crypto" "^0.29.5" - "@cosmjs/encoding" "^0.29.5" - "@cosmjs/math" "^0.29.5" - "@cosmjs/proto-signing" "^0.29.5" - "@cosmjs/stargate" "^0.29.5" - "@cosmjs/tendermint-rpc" "^0.29.5" - "@cosmjs/utils" "^0.29.5" - cosmjs-types "^0.5.2" +"@cosmjs/amino@^0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.30.0.tgz#159b6b2f131e02292802f4c082efb726ca23890d" + integrity sha512-DFPu0dALghQj7A9b8SHnrpNiMsGi4lEs2ZM7XvO/baYgktn8iS5P9LQXmDch5UhKkjJy+uz5aAMD9iFlK6opCQ== + dependencies: + "@cosmjs/crypto" "^0.30.0" + "@cosmjs/encoding" "^0.30.0" + "@cosmjs/math" "^0.30.0" + "@cosmjs/utils" "^0.30.0" + +"@cosmjs/cosmwasm-stargate@^0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.30.0.tgz#b03c6c1383ef658695fcb02e6e1f4df2ddbd4710" + integrity sha512-nPJWpwSGpc15K88/9uxZmolEbixkeGzmxpTv6IbMAH23LXeJs7P+tXtgPe41ZjUw8Lt9Zc7ZOaOAqVveqqRktQ== + dependencies: + "@cosmjs/amino" "^0.30.0" + "@cosmjs/crypto" "^0.30.0" + "@cosmjs/encoding" "^0.30.0" + "@cosmjs/math" "^0.30.0" + "@cosmjs/proto-signing" "^0.30.0" + "@cosmjs/stargate" "^0.30.0" + "@cosmjs/tendermint-rpc" "^0.30.0" + "@cosmjs/utils" "^0.30.0" + cosmjs-types "^0.7.1" long "^4.0.0" pako "^2.0.2" -"@cosmjs/crypto@^0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.29.5.tgz#ab99fc382b93d8a8db075780cf07487a0f9519fd" - integrity sha512-2bKkaLGictaNL0UipQCL6C1afaisv6k8Wr/GCLx9FqiyFkh9ZgRHDyetD64ZsjnWV/N/D44s/esI+k6oPREaiQ== +"@cosmjs/crypto@^0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.30.0.tgz#eadcc98792ce2b3417e8dbc4e84bd250b842459f" + integrity sha512-qjR5vG7G2Hyc1oE5W1vm73Mf7723ooQXWvUBj5QTsNHI4EFJ52jCHhHb7Ynuh6WmBDpfisCios7AWTpA2PSmAQ== dependencies: - "@cosmjs/encoding" "^0.29.5" - "@cosmjs/math" "^0.29.5" - "@cosmjs/utils" "^0.29.5" + "@cosmjs/encoding" "^0.30.0" + "@cosmjs/math" "^0.30.0" + "@cosmjs/utils" "^0.30.0" "@noble/hashes" "^1" bn.js "^5.2.0" elliptic "^6.5.4" libsodium-wrappers "^0.7.6" -"@cosmjs/encoding@^0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.29.5.tgz#009a4b1c596cdfd326f30ccfa79f5e56daa264f2" - integrity sha512-G4rGl/Jg4dMCw5u6PEZHZcoHnUBlukZODHbm/wcL4Uu91fkn5jVo5cXXZcvs4VCkArVGrEj/52eUgTZCmOBGWQ== +"@cosmjs/encoding@^0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.30.0.tgz#a3ab019538f3550d2ee12964148cddfc11e1eeb2" + integrity sha512-b/ZAyAEaUstk6ctcF44ufEWKVMv6S732jUxTz3eGmGCKbZhlVPmk5P2Pc4IHl+IJPtCBoeCLG8KcskVtkNoPvQ== dependencies: base64-js "^1.3.0" bech32 "^1.1.4" readonly-date "^1.0.0" -"@cosmjs/json-rpc@^0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.29.5.tgz#5e483a9bd98a6270f935adf0dfd8a1e7eb777fe4" - integrity sha512-C78+X06l+r9xwdM1yFWIpGl03LhB9NdM1xvZpQHwgCOl0Ir/WV8pw48y3Ez2awAoUBRfTeejPe4KvrE6NoIi/w== +"@cosmjs/json-rpc@^0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.30.0.tgz#a9bec5368bed5e9000b06aa489d121971b1894c3" + integrity sha512-zTPM+mPzq67wI/cphe8I3/4gsIe017c4l/BSiFD7jqjsOKyjhuKzNLuABPGNNRnPo6k9I1Lgb/Z/QPWp9rXuMA== dependencies: - "@cosmjs/stream" "^0.29.5" + "@cosmjs/stream" "^0.30.0" xstream "^11.14.0" -"@cosmjs/math@^0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.29.5.tgz#722c96e080d6c2b62215ce9f4c70da7625b241b6" - integrity sha512-2GjKcv+A9f86MAWYLUkjhw1/WpRl2R1BTb3m9qPG7lzMA7ioYff9jY5SPCfafKdxM4TIQGxXQlYGewQL16O68Q== +"@cosmjs/math@^0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.30.0.tgz#26058f971e89f45ae68598312527584735d3e41e" + integrity sha512-RfrRJe/p7Eu2/ulFQT9IUXA6CL43klcTJu+DRu/iI91LFf64L8Ycid5EQKkEUk4mpI+D2WGx5N0FT99h7pYuCw== dependencies: bn.js "^5.2.0" -"@cosmjs/proto-signing@^0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.5.tgz#af3b62a46c2c2f1d2327d678b13b7262db1fe87c" - integrity sha512-QRrS7CiKaoETdgIqvi/7JC2qCwCR7lnWaUsTzh/XfRy3McLkEd+cXbKAW3cygykv7IN0VAEIhZd2lyIfT8KwNA== - dependencies: - "@cosmjs/amino" "^0.29.5" - "@cosmjs/crypto" "^0.29.5" - "@cosmjs/encoding" "^0.29.5" - "@cosmjs/math" "^0.29.5" - "@cosmjs/utils" "^0.29.5" - cosmjs-types "^0.5.2" +"@cosmjs/proto-signing@^0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.30.0.tgz#de7dddec7fa8dc231c4b82142c4aefffe00673e5" + integrity sha512-+AKJa8xomn/qt73mu98yRQdiqb8wfrmwZGOCsUNRMSs5GIj8ltGNwTwElKY2VjK+O9buG7NSyVQBEGycC5Nj0A== + dependencies: + "@cosmjs/amino" "^0.30.0" + "@cosmjs/crypto" "^0.30.0" + "@cosmjs/encoding" "^0.30.0" + "@cosmjs/math" "^0.30.0" + "@cosmjs/utils" "^0.30.0" + cosmjs-types "^0.7.1" long "^4.0.0" -"@cosmjs/socket@^0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.29.5.tgz#a48df6b4c45dc6a6ef8e47232725dd4aa556ac2d" - integrity sha512-5VYDupIWbIXq3ftPV1LkS5Ya/T7Ol/AzWVhNxZ79hPe/mBfv1bGau/LqIYOm2zxGlgm9hBHOTmWGqNYDwr9LNQ== +"@cosmjs/socket@^0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.30.0.tgz#bb45bc5c5a6a47009c7acd20c9b8320e8e733848" + integrity sha512-zEheZY292C0ec5jx1egOTVmDjBqHMciXpNNDAyo8QpZkVNq9Q4XGKvPOH67jSRQUTiy9NxKXW1Tqr84kqZ3C3A== dependencies: - "@cosmjs/stream" "^0.29.5" + "@cosmjs/stream" "^0.30.0" isomorphic-ws "^4.0.1" ws "^7" xstream "^11.14.0" -"@cosmjs/stargate@^0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.29.5.tgz#d597af1c85a3c2af7b5bdbec34d5d40692cc09e4" - integrity sha512-hjEv8UUlJruLrYGJcUZXM/CziaINOKwfVm2BoSdUnNTMxGvY/jC1ABHKeZUYt9oXHxEJ1n9+pDqzbKc8pT0nBw== +"@cosmjs/stargate@^0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.30.0.tgz#8a6192abc52376d428c124de7902659fd7a89281" + integrity sha512-BrE1iV7M0/oBSTM5doDS+qX4Na1sVtSYMQsGUV3wde49gVttVsoHTNufk4KESQ7lfGemSwgOMsgoKBs/M8TnlA== dependencies: "@confio/ics23" "^0.6.8" - "@cosmjs/amino" "^0.29.5" - "@cosmjs/encoding" "^0.29.5" - "@cosmjs/math" "^0.29.5" - "@cosmjs/proto-signing" "^0.29.5" - "@cosmjs/stream" "^0.29.5" - "@cosmjs/tendermint-rpc" "^0.29.5" - "@cosmjs/utils" "^0.29.5" - cosmjs-types "^0.5.2" + "@cosmjs/amino" "^0.30.0" + "@cosmjs/encoding" "^0.30.0" + "@cosmjs/math" "^0.30.0" + "@cosmjs/proto-signing" "^0.30.0" + "@cosmjs/stream" "^0.30.0" + "@cosmjs/tendermint-rpc" "^0.30.0" + "@cosmjs/utils" "^0.30.0" + cosmjs-types "^0.7.1" long "^4.0.0" protobufjs "~6.11.3" xstream "^11.14.0" -"@cosmjs/stream@^0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.29.5.tgz#350981cac496d04939b92ee793b9b19f44bc1d4e" - integrity sha512-TToTDWyH1p05GBtF0Y8jFw2C+4783ueDCmDyxOMM6EU82IqpmIbfwcdMOCAm0JhnyMh+ocdebbFvnX/sGKzRAA== +"@cosmjs/stream@^0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.30.0.tgz#c6849e895b6a08ef3abcc97d309f467ef63ca05b" + integrity sha512-MkspvIDkZGZQcT8AOkCbG3vXzYED5jLJlki+FuNDWGOVCZVv89IF/v59f+Hg5pJUShRmGZ1cWfmpzYkGHxdEog== dependencies: xstream "^11.14.0" -"@cosmjs/tendermint-rpc@^0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.5.tgz#f205c10464212bdf843f91bb2e4a093b618cb5c2" - integrity sha512-ar80twieuAxsy0x2za/aO3kBr2DFPAXDmk2ikDbmkda+qqfXgl35l9CVAAjKRqd9d+cRvbQyb5M4wy6XQpEV6w== - dependencies: - "@cosmjs/crypto" "^0.29.5" - "@cosmjs/encoding" "^0.29.5" - "@cosmjs/json-rpc" "^0.29.5" - "@cosmjs/math" "^0.29.5" - "@cosmjs/socket" "^0.29.5" - "@cosmjs/stream" "^0.29.5" - "@cosmjs/utils" "^0.29.5" +"@cosmjs/tendermint-rpc@^0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.30.0.tgz#5279434387a78cac17bc40f2541eaaf7b1081f03" + integrity sha512-QpNXwxfCPTHXrfq/LzXvwRxDjTV0R16kPISJ00zu3zSTtANCnBMZiNNFszrCdZKw81fNKo8/Rf2T0gCNvWfGog== + dependencies: + "@cosmjs/crypto" "^0.30.0" + "@cosmjs/encoding" "^0.30.0" + "@cosmjs/json-rpc" "^0.30.0" + "@cosmjs/math" "^0.30.0" + "@cosmjs/socket" "^0.30.0" + "@cosmjs/stream" "^0.30.0" + "@cosmjs/utils" "^0.30.0" axios "^0.21.2" readonly-date "^1.0.0" xstream "^11.14.0" -"@cosmjs/utils@^0.29.5": - version "0.29.5" - resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.5.tgz#3fed1b3528ae8c5f1eb5d29b68755bebfd3294ee" - integrity sha512-m7h+RXDUxOzEOGt4P+3OVPX7PuakZT3GBmaM/Y2u+abN3xZkziykD/NvedYFvvCCdQo714XcGl33bwifS9FZPQ== +"@cosmjs/utils@^0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.30.0.tgz#d493abd906ee72702d61a6bb8a45cef5203bec12" + integrity sha512-A0YaBUlAojyuygqXOjDZa6YT/NOeScq08CqMD5VfU0COlzJS2DKN0z0jfYDt3t3VEXT5ZV8OaLbN3jHah1cSlA== -"@cosmwasm/ts-codegen@^0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.24.0.tgz#61015220a9dccfd35dec46bcb67786441111f096" - integrity sha512-g5ufDroLzOzsMvnKyAz7i5M3Z3k8XYetRv9PnGW0Zbgj4sziw6jDimPJ3Ubnput+i380day7LnCwVT7zLIEzCQ== +"@cosmwasm/ts-codegen@^0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.25.2.tgz#68bbddbafbbc4488446cf2c2986318e18ec65446" + integrity sha512-XCrEXOgFjJvkvSWg6fXh2Ika9unHCJWH2ffbeKeGnzr6x6UX1iCH+8KDcgqMPdvpIOCBaxy9keFoaAqVFue9Ag== dependencies: "@babel/core" "7.18.10" "@babel/generator" "7.18.12" @@ -1312,16 +1312,28 @@ parse-package-name "1.0.0" rimraf "3.0.2" shelljs "0.8.5" - wasm-ast-types "^0.17.0" + wasm-ast-types "^0.18.2" -"@eslint/eslintrc@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e" - integrity sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA== +"@eslint-community/eslint-utils@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.2.0.tgz#a831e6e468b4b2b5ae42bf658bea015bf10bc518" + integrity sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403" + integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ== + +"@eslint/eslintrc@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.1.tgz#7888fe7ec8f21bc26d646dbd2c11cd776e21192d" + integrity sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.4.0" + espree "^9.5.0" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -1329,6 +1341,11 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@eslint/js@8.36.0": + version "8.36.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.36.0.tgz#9837f768c03a1e4a30bd304a64fb8844f0e72efe" + integrity sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg== + "@humanwhocodes/config-array@^0.11.8": version "0.11.8" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" @@ -1364,61 +1381,61 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.4.3.tgz#1f25a99f7f860e4c46423b5b1038262466fadde1" - integrity sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A== +"@jest/console@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.5.0.tgz#593a6c5c0d3f75689835f1b3b4688c4f8544cb57" + integrity sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ== dependencies: - "@jest/types" "^29.4.3" + "@jest/types" "^29.5.0" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.4.3" - jest-util "^29.4.3" + jest-message-util "^29.5.0" + jest-util "^29.5.0" slash "^3.0.0" -"@jest/core@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.4.3.tgz#829dd65bffdb490de5b0f69e97de8e3b5eadd94b" - integrity sha512-56QvBq60fS4SPZCuM7T+7scNrkGIe7Mr6PVIXUpu48ouvRaWOFqRPV91eifvFM0ay2HmfswXiGf97NGUN5KofQ== +"@jest/core@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.5.0.tgz#76674b96904484e8214614d17261cc491e5f1f03" + integrity sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ== dependencies: - "@jest/console" "^29.4.3" - "@jest/reporters" "^29.4.3" - "@jest/test-result" "^29.4.3" - "@jest/transform" "^29.4.3" - "@jest/types" "^29.4.3" + "@jest/console" "^29.5.0" + "@jest/reporters" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^29.4.3" - jest-config "^29.4.3" - jest-haste-map "^29.4.3" - jest-message-util "^29.4.3" + jest-changed-files "^29.5.0" + jest-config "^29.5.0" + jest-haste-map "^29.5.0" + jest-message-util "^29.5.0" jest-regex-util "^29.4.3" - jest-resolve "^29.4.3" - jest-resolve-dependencies "^29.4.3" - jest-runner "^29.4.3" - jest-runtime "^29.4.3" - jest-snapshot "^29.4.3" - jest-util "^29.4.3" - jest-validate "^29.4.3" - jest-watcher "^29.4.3" + jest-resolve "^29.5.0" + jest-resolve-dependencies "^29.5.0" + jest-runner "^29.5.0" + jest-runtime "^29.5.0" + jest-snapshot "^29.5.0" + jest-util "^29.5.0" + jest-validate "^29.5.0" + jest-watcher "^29.5.0" micromatch "^4.0.4" - pretty-format "^29.4.3" + pretty-format "^29.5.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.4.3.tgz#9fe2f3169c3b33815dc4bd3960a064a83eba6548" - integrity sha512-dq5S6408IxIa+lr54zeqce+QgI+CJT4nmmA+1yzFgtcsGK8c/EyiUb9XQOgz3BMKrRDfKseeOaxj2eO8LlD3lA== +"@jest/environment@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65" + integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ== dependencies: - "@jest/fake-timers" "^29.4.3" - "@jest/types" "^29.4.3" + "@jest/fake-timers" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" - jest-mock "^29.4.3" + jest-mock "^29.5.0" "@jest/expect-utils@^29.4.3": version "29.4.3" @@ -1427,46 +1444,53 @@ dependencies: jest-get-type "^29.4.3" -"@jest/expect@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.4.3.tgz#d31a28492e45a6bcd0f204a81f783fe717045c6e" - integrity sha512-iktRU/YsxEtumI9zsPctYUk7ptpC+AVLLk1Ax3AsA4g1C+8OOnKDkIQBDHtD5hA/+VtgMd5AWI5gNlcAlt2vxQ== +"@jest/expect-utils@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036" + integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg== dependencies: - expect "^29.4.3" - jest-snapshot "^29.4.3" + jest-get-type "^29.4.3" -"@jest/fake-timers@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.4.3.tgz#31e982638c60fa657d310d4b9d24e023064027b0" - integrity sha512-4Hote2MGcCTWSD2gwl0dwbCpBRHhE6olYEuTj8FMowdg3oQWNKr2YuxenPQYZ7+PfqPY1k98wKDU4Z+Hvd4Tiw== +"@jest/expect@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.5.0.tgz#80952f5316b23c483fbca4363ce822af79c38fba" + integrity sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g== dependencies: - "@jest/types" "^29.4.3" + expect "^29.5.0" + jest-snapshot "^29.5.0" + +"@jest/fake-timers@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.5.0.tgz#d4d09ec3286b3d90c60bdcd66ed28d35f1b4dc2c" + integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg== + dependencies: + "@jest/types" "^29.5.0" "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^29.4.3" - jest-mock "^29.4.3" - jest-util "^29.4.3" + jest-message-util "^29.5.0" + jest-mock "^29.5.0" + jest-util "^29.5.0" -"@jest/globals@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.4.3.tgz#63a2c4200d11bc6d46f12bbe25b07f771fce9279" - integrity sha512-8BQ/5EzfOLG7AaMcDh7yFCbfRLtsc+09E1RQmRBI4D6QQk4m6NSK/MXo+3bJrBN0yU8A2/VIcqhvsOLFmziioA== +"@jest/globals@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.5.0.tgz#6166c0bfc374c58268677539d0c181f9c1833298" + integrity sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ== dependencies: - "@jest/environment" "^29.4.3" - "@jest/expect" "^29.4.3" - "@jest/types" "^29.4.3" - jest-mock "^29.4.3" + "@jest/environment" "^29.5.0" + "@jest/expect" "^29.5.0" + "@jest/types" "^29.5.0" + jest-mock "^29.5.0" -"@jest/reporters@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.4.3.tgz#0a68a0c0f20554760cc2e5443177a0018969e353" - integrity sha512-sr2I7BmOjJhyqj9ANC6CTLsL4emMoka7HkQpcoMRlhCbQJjz2zsRzw0BDPiPyEFDXAbxKgGFYuQZiSJ1Y6YoTg== +"@jest/reporters@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.5.0.tgz#985dfd91290cd78ddae4914ba7921bcbabe8ac9b" + integrity sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.4.3" - "@jest/test-result" "^29.4.3" - "@jest/transform" "^29.4.3" - "@jest/types" "^29.4.3" + "@jest/console" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" "@jridgewell/trace-mapping" "^0.3.15" "@types/node" "*" chalk "^4.0.0" @@ -1479,9 +1503,9 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.4.3" - jest-util "^29.4.3" - jest-worker "^29.4.3" + jest-message-util "^29.5.0" + jest-util "^29.5.0" + jest-worker "^29.5.0" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" @@ -1510,24 +1534,24 @@ callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.4.3.tgz#e13d973d16c8c7cc0c597082d5f3b9e7f796ccb8" - integrity sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA== +"@jest/test-result@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.5.0.tgz#7c856a6ca84f45cc36926a4e9c6b57f1973f1408" + integrity sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ== dependencies: - "@jest/console" "^29.4.3" - "@jest/types" "^29.4.3" + "@jest/console" "^29.5.0" + "@jest/types" "^29.5.0" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.4.3.tgz#0862e876a22993385a0f3e7ea1cc126f208a2898" - integrity sha512-yi/t2nES4GB4G0mjLc0RInCq/cNr9dNwJxcGg8sslajua5Kb4kmozAc+qPLzplhBgfw1vLItbjyHzUN92UXicw== +"@jest/test-sequencer@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz#34d7d82d3081abd523dbddc038a3ddcb9f6d3cc4" + integrity sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ== dependencies: - "@jest/test-result" "^29.4.3" + "@jest/test-result" "^29.5.0" graceful-fs "^4.2.9" - jest-haste-map "^29.4.3" + jest-haste-map "^29.5.0" slash "^3.0.0" "@jest/transform@28.1.3": @@ -1551,22 +1575,22 @@ slash "^3.0.0" write-file-atomic "^4.0.1" -"@jest/transform@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.4.3.tgz#f7d17eac9cb5bb2e1222ea199c7c7e0835e0c037" - integrity sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg== +"@jest/transform@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.5.0.tgz#cf9c872d0965f0cbd32f1458aa44a2b1988b00f9" + integrity sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.4.3" + "@jest/types" "^29.5.0" "@jridgewell/trace-mapping" "^0.3.15" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.4.3" + jest-haste-map "^29.5.0" jest-regex-util "^29.4.3" - jest-util "^29.4.3" + jest-util "^29.5.0" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" @@ -1596,6 +1620,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" + integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== + dependencies: + "@jest/schemas" "^29.4.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -1900,14 +1936,14 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.51.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz#24b8b4a952f3c615fe070e3c461dd852b5056734" - integrity sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw== +"@typescript-eslint/eslint-plugin@^5.54.1": + version "5.54.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.1.tgz#0c5091289ce28372e38ab8d28e861d2dbe1ab29e" + integrity sha512-a2RQAkosH3d3ZIV08s3DcL/mcGc2M/UC528VkPULFxR9VnVPT8pBu0IyBAJJmVsCmhVfwQX1v6q+QGnmSe1bew== dependencies: - "@typescript-eslint/scope-manager" "5.53.0" - "@typescript-eslint/type-utils" "5.53.0" - "@typescript-eslint/utils" "5.53.0" + "@typescript-eslint/scope-manager" "5.54.1" + "@typescript-eslint/type-utils" "5.54.1" + "@typescript-eslint/utils" "5.54.1" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -1916,72 +1952,72 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.51.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.53.0.tgz#a1f2b9ae73b83181098747e96683f1b249ecab52" - integrity sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ== +"@typescript-eslint/parser@^5.54.1": + version "5.54.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.54.1.tgz#05761d7f777ef1c37c971d3af6631715099b084c" + integrity sha512-8zaIXJp/nG9Ff9vQNh7TI+C3nA6q6iIsGJ4B4L6MhZ7mHnTMR4YP5vp2xydmFXIy8rpyIVbNAG44871LMt6ujg== dependencies: - "@typescript-eslint/scope-manager" "5.53.0" - "@typescript-eslint/types" "5.53.0" - "@typescript-eslint/typescript-estree" "5.53.0" + "@typescript-eslint/scope-manager" "5.54.1" + "@typescript-eslint/types" "5.54.1" + "@typescript-eslint/typescript-estree" "5.54.1" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz#42b54f280e33c82939275a42649701024f3fafef" - integrity sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w== +"@typescript-eslint/scope-manager@5.54.1": + version "5.54.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.54.1.tgz#6d864b4915741c608a58ce9912edf5a02bb58735" + integrity sha512-zWKuGliXxvuxyM71UA/EcPxaviw39dB2504LqAmFDjmkpO8qNLHcmzlh6pbHs1h/7YQ9bnsO8CCcYCSA8sykUg== dependencies: - "@typescript-eslint/types" "5.53.0" - "@typescript-eslint/visitor-keys" "5.53.0" + "@typescript-eslint/types" "5.54.1" + "@typescript-eslint/visitor-keys" "5.54.1" -"@typescript-eslint/type-utils@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.53.0.tgz#41665449935ba9b4e6a1ba6e2a3f4b2c31d6cf97" - integrity sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw== +"@typescript-eslint/type-utils@5.54.1": + version "5.54.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.54.1.tgz#4825918ec27e55da8bb99cd07ec2a8e5f50ab748" + integrity sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g== dependencies: - "@typescript-eslint/typescript-estree" "5.53.0" - "@typescript-eslint/utils" "5.53.0" + "@typescript-eslint/typescript-estree" "5.54.1" + "@typescript-eslint/utils" "5.54.1" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.53.0.tgz#f79eca62b97e518ee124086a21a24f3be267026f" - integrity sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A== +"@typescript-eslint/types@5.54.1": + version "5.54.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.54.1.tgz#29fbac29a716d0f08c62fe5de70c9b6735de215c" + integrity sha512-G9+1vVazrfAfbtmCapJX8jRo2E4MDXxgm/IMOF4oGh3kq7XuK3JRkOg6y2Qu1VsTRmWETyTkWt1wxy7X7/yLkw== -"@typescript-eslint/typescript-estree@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz#bc651dc28cf18ab248ecd18a4c886c744aebd690" - integrity sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w== +"@typescript-eslint/typescript-estree@5.54.1": + version "5.54.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.1.tgz#df7b6ae05fd8fef724a87afa7e2f57fa4a599be1" + integrity sha512-bjK5t+S6ffHnVwA0qRPTZrxKSaFYocwFIkZx5k7pvWfsB1I57pO/0M0Skatzzw1sCkjJ83AfGTL0oFIFiDX3bg== dependencies: - "@typescript-eslint/types" "5.53.0" - "@typescript-eslint/visitor-keys" "5.53.0" + "@typescript-eslint/types" "5.54.1" + "@typescript-eslint/visitor-keys" "5.54.1" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.53.0.tgz#e55eaad9d6fffa120575ffaa530c7e802f13bce8" - integrity sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g== +"@typescript-eslint/utils@5.54.1": + version "5.54.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.54.1.tgz#7a3ee47409285387b9d4609ea7e1020d1797ec34" + integrity sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ== dependencies: "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.53.0" - "@typescript-eslint/types" "5.53.0" - "@typescript-eslint/typescript-estree" "5.53.0" + "@typescript-eslint/scope-manager" "5.54.1" + "@typescript-eslint/types" "5.54.1" + "@typescript-eslint/typescript-estree" "5.54.1" eslint-scope "^5.1.1" eslint-utils "^3.0.0" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz#8a5126623937cdd909c30d8fa72f79fa56cc1a9f" - integrity sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w== +"@typescript-eslint/visitor-keys@5.54.1": + version "5.54.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.1.tgz#d7a8a0f7181d6ac748f4d47b2306e0513b98bf8b" + integrity sha512-q8iSoHTgwCfgcRJ2l2x+xCbu8nBlRAlsQ33k24Adj8eoVBE0f8dUeI+bAa8F84Mv05UGbAx57g2zrRsYIooqQg== dependencies: - "@typescript-eslint/types" "5.53.0" + "@typescript-eslint/types" "5.54.1" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -2109,15 +2145,15 @@ axios@^0.21.1, axios@^0.21.2: dependencies: follow-redirects "^1.14.0" -babel-jest@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.4.3.tgz#478b84d430972b277ad67dd631be94abea676792" - integrity sha512-o45Wyn32svZE+LnMVWv/Z4x0SwtLbh4FyGcYtR20kIWd+rdrDZ9Fzq8Ml3MYLD+mZvEdzCjZsCnYZ2jpJyQ+Nw== +babel-jest@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.5.0.tgz#3fe3ddb109198e78b1c88f9ebdecd5e4fc2f50a5" + integrity sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q== dependencies: - "@jest/transform" "^29.4.3" + "@jest/transform" "^29.5.0" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.4.3" + babel-preset-jest "^29.5.0" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" @@ -2133,10 +2169,10 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.4.3.tgz#ad1dfb5d31940957e00410ef7d9b2aa94b216101" - integrity sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q== +babel-plugin-jest-hoist@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a" + integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -2193,12 +2229,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.4.3.tgz#bb926b66ae253b69c6e3ef87511b8bb5c53c5b52" - integrity sha512-gWx6COtSuma6n9bw+8/F+2PCXrIgxV/D1TJFnp6OyBK2cxPWg0K9p/sriNYeifKjpUkMViWQ09DSWtzJQRETsw== +babel-preset-jest@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2" + integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== dependencies: - babel-plugin-jest-hoist "^29.4.3" + babel-plugin-jest-hoist "^29.5.0" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: @@ -2492,18 +2528,10 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cosmjs-types@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.5.2.tgz#2d42b354946f330dfb5c90a87fdc2a36f97b965d" - integrity sha512-zxCtIJj8v3Di7s39uN4LNcN3HIE1z0B9Z0SPE8ZNQR0oSzsuSe1ACgxoFkvhkS7WBasCAFcglS11G2hyfd5tPg== - dependencies: - long "^4.0.0" - protobufjs "~6.11.2" - -cosmjs-types@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.6.1.tgz#4944e83d0fa87880243a11813bdff0e313d39a68" - integrity sha512-fRz6yzElHHBULDyLArF/G1UkkTWW4r3RondBUGnmSsZWYI5NpfDn32MVa5aRmpaaf4tJI2cbnXHs9fykwU7Ttg== +cosmjs-types@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.7.1.tgz#7ad355f63206fb829b565ed3463791d33b10c3d7" + integrity sha512-qP89SGwi6YpvMTrM9CPzTfZ0JPNlXzgimqMLsa/ZjzW+L6MC8TCr6XmoWtFOT6GSfefvJLwFWq7YCtL456Bdzg== dependencies: long "^4.0.0" protobufjs "~6.11.2" @@ -2685,10 +2713,10 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-config-prettier@^8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz#dec1d29ab728f4fa63061774e1672ac4e363d207" - integrity sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA== +eslint-config-prettier@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.7.0.tgz#f1cc58a8afebc50980bd53475451df146c13182d" + integrity sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA== eslint-scope@^5.1.1: version "5.1.1" @@ -2723,12 +2751,15 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.33.0: - version "8.34.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.34.0.tgz#fe0ab0ef478104c1f9ebc5537e303d25a8fb22d6" - integrity sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg== +eslint@^8.36.0: + version "8.36.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.36.0.tgz#1bd72202200a5492f91803b113fb8a83b11285cf" + integrity sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw== dependencies: - "@eslint/eslintrc" "^1.4.1" + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.4.0" + "@eslint/eslintrc" "^2.0.1" + "@eslint/js" "8.36.0" "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -2739,10 +2770,9 @@ eslint@^8.33.0: doctrine "^3.0.0" escape-string-regexp "^4.0.0" eslint-scope "^7.1.1" - eslint-utils "^3.0.0" eslint-visitor-keys "^3.3.0" - espree "^9.4.0" - esquery "^1.4.0" + espree "^9.5.0" + esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" @@ -2763,15 +2793,14 @@ eslint@^8.33.0: minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.1" - regexpp "^3.2.0" strip-ansi "^6.0.1" strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.4.0: - version "9.4.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.1.tgz#51d6092615567a2c2cff7833445e37c28c0065bd" - integrity sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg== +espree@^9.5.0: + version "9.5.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.0.tgz#3646d4e3f58907464edba852fa047e6a27bdf113" + integrity sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw== dependencies: acorn "^8.8.0" acorn-jsx "^5.3.2" @@ -2782,10 +2811,10 @@ esprima@^4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0: - version "1.4.2" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.2.tgz#c6d3fee05dd665808e2ad870631f221f5617b1d1" - integrity sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng== +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== dependencies: estraverse "^5.1.0" @@ -2839,7 +2868,7 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0, expect@^29.4.3: +expect@^29.0.0: version "29.4.3" resolved "https://registry.yarnpkg.com/expect/-/expect-29.4.3.tgz#5e47757316df744fe3b8926c3ae8a3ebdafff7fe" integrity sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg== @@ -2850,6 +2879,17 @@ expect@^29.0.0, expect@^29.4.3: jest-message-util "^29.4.3" jest-util "^29.4.3" +expect@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" + integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== + dependencies: + "@jest/expect-utils" "^29.5.0" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.5.0" + jest-message-util "^29.5.0" + jest-util "^29.5.0" + ext@^1.1.2: version "1.7.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" @@ -3417,82 +3457,83 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.4.3.tgz#7961fe32536b9b6d5c28dfa0abcfab31abcf50a7" - integrity sha512-Vn5cLuWuwmi2GNNbokPOEcvrXGSGrqVnPEZV7rC6P7ck07Dyw9RFnvWglnupSh+hGys0ajGtw/bc2ZgweljQoQ== +jest-changed-files@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" + integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== dependencies: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.4.3.tgz#fff7be1cf5f06224dd36a857d52a9efeb005ba04" - integrity sha512-Vw/bVvcexmdJ7MLmgdT3ZjkJ3LKu8IlpefYokxiqoZy6OCQ2VAm6Vk3t/qHiAGUXbdbJKJWnc8gH3ypTbB/OBw== +jest-circus@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.5.0.tgz#b5926989449e75bff0d59944bae083c9d7fb7317" + integrity sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA== dependencies: - "@jest/environment" "^29.4.3" - "@jest/expect" "^29.4.3" - "@jest/test-result" "^29.4.3" - "@jest/types" "^29.4.3" + "@jest/environment" "^29.5.0" + "@jest/expect" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^29.4.3" - jest-matcher-utils "^29.4.3" - jest-message-util "^29.4.3" - jest-runtime "^29.4.3" - jest-snapshot "^29.4.3" - jest-util "^29.4.3" + jest-each "^29.5.0" + jest-matcher-utils "^29.5.0" + jest-message-util "^29.5.0" + jest-runtime "^29.5.0" + jest-snapshot "^29.5.0" + jest-util "^29.5.0" p-limit "^3.1.0" - pretty-format "^29.4.3" + pretty-format "^29.5.0" + pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.4.3.tgz#fe31fdd0c90c765f392b8b7c97e4845071cd2163" - integrity sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg== +jest-cli@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.5.0.tgz#b34c20a6d35968f3ee47a7437ff8e53e086b4a67" + integrity sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw== dependencies: - "@jest/core" "^29.4.3" - "@jest/test-result" "^29.4.3" - "@jest/types" "^29.4.3" + "@jest/core" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/types" "^29.5.0" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.4.3" - jest-util "^29.4.3" - jest-validate "^29.4.3" + jest-config "^29.5.0" + jest-util "^29.5.0" + jest-validate "^29.5.0" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.4.3.tgz#fca9cdfe6298ae6d04beef1624064d455347c978" - integrity sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ== +jest-config@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.5.0.tgz#3cc972faec8c8aaea9ae158c694541b79f3748da" + integrity sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.4.3" - "@jest/types" "^29.4.3" - babel-jest "^29.4.3" + "@jest/test-sequencer" "^29.5.0" + "@jest/types" "^29.5.0" + babel-jest "^29.5.0" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.4.3" - jest-environment-node "^29.4.3" + jest-circus "^29.5.0" + jest-environment-node "^29.5.0" jest-get-type "^29.4.3" jest-regex-util "^29.4.3" - jest-resolve "^29.4.3" - jest-runner "^29.4.3" - jest-util "^29.4.3" - jest-validate "^29.4.3" + jest-resolve "^29.5.0" + jest-runner "^29.5.0" + jest-util "^29.5.0" + jest-validate "^29.5.0" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.4.3" + pretty-format "^29.5.0" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -3506,6 +3547,16 @@ jest-diff@^29.4.3: jest-get-type "^29.4.3" pretty-format "^29.4.3" +jest-diff@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63" + integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.4.3" + jest-get-type "^29.4.3" + pretty-format "^29.5.0" + jest-docblock@^29.4.3: version "29.4.3" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" @@ -3513,28 +3564,28 @@ jest-docblock@^29.4.3: dependencies: detect-newline "^3.0.0" -jest-each@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.4.3.tgz#a434c199a2f6151c5e3dc80b2d54586bdaa72819" - integrity sha512-1ElHNAnKcbJb/b+L+7j0/w7bDvljw4gTv1wL9fYOczeJrbTbkMGQ5iQPFJ3eFQH19VPTx1IyfePdqSpePKss7Q== +jest-each@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.5.0.tgz#fc6e7014f83eac68e22b7195598de8554c2e5c06" + integrity sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA== dependencies: - "@jest/types" "^29.4.3" + "@jest/types" "^29.5.0" chalk "^4.0.0" jest-get-type "^29.4.3" - jest-util "^29.4.3" - pretty-format "^29.4.3" + jest-util "^29.5.0" + pretty-format "^29.5.0" -jest-environment-node@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.4.3.tgz#579c4132af478befc1889ddc43c2413a9cdbe014" - integrity sha512-gAiEnSKF104fsGDXNkwk49jD/0N0Bqu2K9+aMQXA6avzsA9H3Fiv1PW2D+gzbOSR705bWd2wJZRFEFpV0tXISg== +jest-environment-node@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.5.0.tgz#f17219d0f0cc0e68e0727c58b792c040e332c967" + integrity sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw== dependencies: - "@jest/environment" "^29.4.3" - "@jest/fake-timers" "^29.4.3" - "@jest/types" "^29.4.3" + "@jest/environment" "^29.5.0" + "@jest/fake-timers" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" - jest-mock "^29.4.3" - jest-util "^29.4.3" + jest-mock "^29.5.0" + jest-util "^29.5.0" jest-get-type@^29.4.3: version "29.4.3" @@ -3560,32 +3611,32 @@ jest-haste-map@^28.1.3: optionalDependencies: fsevents "^2.3.2" -jest-haste-map@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.4.3.tgz#085a44283269e7ace0645c63a57af0d2af6942e2" - integrity sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ== +jest-haste-map@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.5.0.tgz#69bd67dc9012d6e2723f20a945099e972b2e94de" + integrity sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA== dependencies: - "@jest/types" "^29.4.3" + "@jest/types" "^29.5.0" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" jest-regex-util "^29.4.3" - jest-util "^29.4.3" - jest-worker "^29.4.3" + jest-util "^29.5.0" + jest-worker "^29.5.0" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.4.3.tgz#2b35191d6b35aa0256e63a9b79b0f949249cf23a" - integrity sha512-9yw4VC1v2NspMMeV3daQ1yXPNxMgCzwq9BocCwYrRgXe4uaEJPAN0ZK37nFBhcy3cUwEVstFecFLaTHpF7NiGA== +jest-leak-detector@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz#cf4bdea9615c72bac4a3a7ba7e7930f9c0610c8c" + integrity sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow== dependencies: jest-get-type "^29.4.3" - pretty-format "^29.4.3" + pretty-format "^29.5.0" jest-matcher-utils@^29.4.3: version "29.4.3" @@ -3597,6 +3648,16 @@ jest-matcher-utils@^29.4.3: jest-get-type "^29.4.3" pretty-format "^29.4.3" +jest-matcher-utils@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5" + integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw== + dependencies: + chalk "^4.0.0" + jest-diff "^29.5.0" + jest-get-type "^29.4.3" + pretty-format "^29.5.0" + jest-message-util@^29.4.3: version "29.4.3" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.4.3.tgz#65b5280c0fdc9419503b49d4f48d4999d481cb5b" @@ -3612,14 +3673,29 @@ jest-message-util@^29.4.3: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.4.3.tgz#23d84a20a74cdfff0510fdbeefb841ed57b0fe7e" - integrity sha512-LjFgMg+xed9BdkPMyIJh+r3KeHt1klXPJYBULXVVAkbTaaKjPX1o1uVCAZADMEp/kOxGTwy/Ot8XbvgItOrHEg== +jest-message-util@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e" + integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA== dependencies: - "@jest/types" "^29.4.3" + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.5.0" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.5.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed" + integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw== + dependencies: + "@jest/types" "^29.5.0" "@types/node" "*" - jest-util "^29.4.3" + jest-util "^29.5.0" jest-pnp-resolver@^1.2.2: version "1.2.3" @@ -3636,88 +3712,88 @@ jest-regex-util@^29.4.3: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== -jest-resolve-dependencies@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.4.3.tgz#9ad7f23839a6d88cef91416bda9393a6e9fd1da5" - integrity sha512-uvKMZAQ3nmXLH7O8WAOhS5l0iWyT3WmnJBdmIHiV5tBbdaDZ1wqtNX04FONGoaFvSOSHBJxnwAVnSn1WHdGVaw== +jest-resolve-dependencies@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz#f0ea29955996f49788bf70996052aa98e7befee4" + integrity sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg== dependencies: jest-regex-util "^29.4.3" - jest-snapshot "^29.4.3" + jest-snapshot "^29.5.0" -jest-resolve@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.4.3.tgz#3c5b5c984fa8a763edf9b3639700e1c7900538e2" - integrity sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw== +jest-resolve@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.5.0.tgz#b053cc95ad1d5f6327f0ac8aae9f98795475ecdc" + integrity sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.4.3" + jest-haste-map "^29.5.0" jest-pnp-resolver "^1.2.2" - jest-util "^29.4.3" - jest-validate "^29.4.3" + jest-util "^29.5.0" + jest-validate "^29.5.0" resolve "^1.20.0" resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.4.3.tgz#68dc82c68645eda12bea42b5beece6527d7c1e5e" - integrity sha512-GWPTEiGmtHZv1KKeWlTX9SIFuK19uLXlRQU43ceOQ2hIfA5yPEJC7AMkvFKpdCHx6pNEdOD+2+8zbniEi3v3gA== +jest-runner@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.5.0.tgz#6a57c282eb0ef749778d444c1d758c6a7693b6f8" + integrity sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ== dependencies: - "@jest/console" "^29.4.3" - "@jest/environment" "^29.4.3" - "@jest/test-result" "^29.4.3" - "@jest/transform" "^29.4.3" - "@jest/types" "^29.4.3" + "@jest/console" "^29.5.0" + "@jest/environment" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" jest-docblock "^29.4.3" - jest-environment-node "^29.4.3" - jest-haste-map "^29.4.3" - jest-leak-detector "^29.4.3" - jest-message-util "^29.4.3" - jest-resolve "^29.4.3" - jest-runtime "^29.4.3" - jest-util "^29.4.3" - jest-watcher "^29.4.3" - jest-worker "^29.4.3" + jest-environment-node "^29.5.0" + jest-haste-map "^29.5.0" + jest-leak-detector "^29.5.0" + jest-message-util "^29.5.0" + jest-resolve "^29.5.0" + jest-runtime "^29.5.0" + jest-util "^29.5.0" + jest-watcher "^29.5.0" + jest-worker "^29.5.0" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.4.3.tgz#f25db9874dcf35a3ab27fdaabca426666cc745bf" - integrity sha512-F5bHvxSH+LvLV24vVB3L8K467dt3y3dio6V3W89dUz9nzvTpqd/HcT9zfYKL2aZPvD63vQFgLvaUX/UpUhrP6Q== +jest-runtime@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.5.0.tgz#c83f943ee0c1da7eb91fa181b0811ebd59b03420" + integrity sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw== dependencies: - "@jest/environment" "^29.4.3" - "@jest/fake-timers" "^29.4.3" - "@jest/globals" "^29.4.3" + "@jest/environment" "^29.5.0" + "@jest/fake-timers" "^29.5.0" + "@jest/globals" "^29.5.0" "@jest/source-map" "^29.4.3" - "@jest/test-result" "^29.4.3" - "@jest/transform" "^29.4.3" - "@jest/types" "^29.4.3" + "@jest/test-result" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.4.3" - jest-message-util "^29.4.3" - jest-mock "^29.4.3" + jest-haste-map "^29.5.0" + jest-message-util "^29.5.0" + jest-mock "^29.5.0" jest-regex-util "^29.4.3" - jest-resolve "^29.4.3" - jest-snapshot "^29.4.3" - jest-util "^29.4.3" + jest-resolve "^29.5.0" + jest-snapshot "^29.5.0" + jest-util "^29.5.0" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.4.3.tgz#183d309371450d9c4a3de7567ed2151eb0e91145" - integrity sha512-NGlsqL0jLPDW91dz304QTM/SNO99lpcSYYAjNiX0Ou+sSGgkanKBcSjCfp/pqmiiO1nQaOyLp6XQddAzRcx3Xw== +jest-snapshot@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.5.0.tgz#c9c1ce0331e5b63cd444e2f95a55a73b84b1e8ce" + integrity sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" @@ -3725,23 +3801,22 @@ jest-snapshot@^29.4.3: "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.4.3" - "@jest/transform" "^29.4.3" - "@jest/types" "^29.4.3" + "@jest/expect-utils" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" "@types/babel__traverse" "^7.0.6" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.4.3" + expect "^29.5.0" graceful-fs "^4.2.9" - jest-diff "^29.4.3" + jest-diff "^29.5.0" jest-get-type "^29.4.3" - jest-haste-map "^29.4.3" - jest-matcher-utils "^29.4.3" - jest-message-util "^29.4.3" - jest-util "^29.4.3" + jest-matcher-utils "^29.5.0" + jest-message-util "^29.5.0" + jest-util "^29.5.0" natural-compare "^1.4.0" - pretty-format "^29.4.3" + pretty-format "^29.5.0" semver "^7.3.5" jest-util@^28.1.3: @@ -3768,30 +3843,42 @@ jest-util@^29.4.3: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.4.3.tgz#a13849dec4f9e95446a7080ad5758f58fa88642f" - integrity sha512-J3u5v7aPQoXPzaar6GndAVhdQcZr/3osWSgTeKg5v574I9ybX/dTyH0AJFb5XgXIB7faVhf+rS7t4p3lL9qFaw== +jest-util@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" + integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== dependencies: - "@jest/types" "^29.4.3" + "@jest/types" "^29.5.0" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.5.0.tgz#8e5a8f36178d40e47138dc00866a5f3bd9916ffc" + integrity sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ== + dependencies: + "@jest/types" "^29.5.0" camelcase "^6.2.0" chalk "^4.0.0" jest-get-type "^29.4.3" leven "^3.1.0" - pretty-format "^29.4.3" + pretty-format "^29.5.0" -jest-watcher@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.4.3.tgz#e503baa774f0c2f8f3c8db98a22ebf885f19c384" - integrity sha512-zwlXH3DN3iksoIZNk73etl1HzKyi5FuQdYLnkQKm5BW4n8HpoG59xSwpVdFrnh60iRRaRBGw0gcymIxjJENPcA== +jest-watcher@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.5.0.tgz#cf7f0f949828ba65ddbbb45c743a382a4d911363" + integrity sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA== dependencies: - "@jest/test-result" "^29.4.3" - "@jest/types" "^29.4.3" + "@jest/test-result" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.4.3" + jest-util "^29.5.0" string-length "^4.0.1" jest-worker@^28.1.3: @@ -3803,25 +3890,25 @@ jest-worker@^28.1.3: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.4.3.tgz#9a4023e1ea1d306034237c7133d7da4240e8934e" - integrity sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA== +jest-worker@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.5.0.tgz#bdaefb06811bd3384d93f009755014d8acb4615d" + integrity sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA== dependencies: "@types/node" "*" - jest-util "^29.4.3" + jest-util "^29.5.0" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.4.2: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.4.3.tgz#1b8be541666c6feb99990fd98adac4737e6e6386" - integrity sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA== +jest@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e" + integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== dependencies: - "@jest/core" "^29.4.3" - "@jest/types" "^29.4.3" + "@jest/core" "^29.5.0" + "@jest/types" "^29.5.0" import-local "^3.0.2" - jest-cli "^29.4.3" + jest-cli "^29.5.0" js-sdsl@^4.1.4: version "4.3.0" @@ -4333,6 +4420,15 @@ pretty-format@^29.0.0, pretty-format@^29.4.3: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a" + integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw== + dependencies: + "@jest/schemas" "^29.4.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -4370,6 +4466,11 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== +pure-rand@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.1.tgz#31207dddd15d43f299fdcdb2f572df65030c19af" + integrity sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -4975,10 +5076,10 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -wasm-ast-types@^0.17.0: - version "0.17.0" - resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.17.0.tgz#417280a61d60ea9964667cf2edb8f5281dc295d7" - integrity sha512-WeriXPbG67iI51Mf/5qRR0xcpEaTO/Wyjpl+vsmjZ5K6q/0W6iO03zHsESNIH/hpc5FPTpb0Y0L9xAtnnNe9Ow== +wasm-ast-types@^0.18.2: + version "0.18.2" + resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.18.2.tgz#2161f390e7a2e4ad45a46bfdeda6d2d56801ece5" + integrity sha512-SKsuc56aYcl2UQM1Zm8IJZ2efvNAYhu/pPgH2OJZvUahGDi8W+jqnrKx9hIJeKW2GQdhboIIPFKY7PihWzgzgQ== dependencies: "@babel/runtime" "^7.18.9" "@babel/types" "7.18.10" From e582aa4ca05b8617f1d1d4cbeb00be93c798400b Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 17 Mar 2023 16:03:47 +0100 Subject: [PATCH 150/218] Adding emergency owner role (#128) Adding emergency role --- Cargo.lock | 4 +- Cargo.toml | 2 +- contracts/credit-manager/src/contract.rs | 2 + .../credit-manager/src/emergency_update.rs | 57 ++++++ contracts/credit-manager/src/lib.rs | 1 + .../tests/helpers/mock_entity_info.rs | 8 +- .../credit-manager/tests/helpers/mock_env.rs | 39 +++- .../tests/test_emergency_powers.rs | 174 ++++++++++++++++++ .../credit-manager/tests/test_instantiate.rs | 2 +- packages/rover/src/msg/execute.rs | 21 +++ .../mars-credit-manager.json | 82 +++++++++ .../mars-rover-health-types.json | 35 ++++ .../mars-swapper-base/mars-swapper-base.json | 35 ++++ .../MarsCreditManager.client.ts | 23 +++ .../MarsCreditManager.message-composer.ts | 18 ++ .../MarsCreditManager.react-query.ts | 21 +++ .../MarsCreditManager.types.ts | 19 ++ .../MarsRoverHealthTypes.types.ts | 7 + .../MarsSwapperBase.types.ts | 7 + 19 files changed, 551 insertions(+), 6 deletions(-) create mode 100644 contracts/credit-manager/src/emergency_update.rs create mode 100644 contracts/credit-manager/tests/test_emergency_powers.rs diff --git a/Cargo.lock b/Cargo.lock index 49694077f..a08c6ed5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1130,9 +1130,9 @@ dependencies = [ [[package]] name = "mars-owner" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5644a8b047a0d64d04706414805872f35439755e057431152dd36e6f369be24" +checksum = "8ca010da465b4a5ea7274f59132d22b7c10765295c73d5744add2c1fea6c5e38" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/Cargo.toml b/Cargo.toml index 8d6e569bf..922cfc760 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ mars-rover-health-computer = { version = "1.0.0", path = "./packages/health-comp mars-rover-health-types = { version = "1.0.0", path = "./packages/health-types" } mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "00301d60c38af09d8eb7980355009e2f00c6f41f" } mars-red-bank-types = "1.0.0" -mars-owner = "1.0.0" +mars-owner = { version = "1.0.0", features = ["emergency-owner"] } mars-rover = { version = "1.0.0", path = "./packages/rover" } # contracts diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index ee8fe93c1..aeb7046bc 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -9,6 +9,7 @@ use mars_rover::{ }; use crate::{ + emergency_update::emergency_config_update, execute::{create_credit_account, dispatch_actions, execute_callback}, instantiate::store_config, query::{ @@ -60,6 +61,7 @@ pub fn execute( account_id, actions, } => dispatch_actions(deps, env, info, &account_id, &actions), + ExecuteMsg::EmergencyConfigUpdate(update) => emergency_config_update(deps, info, update), } } diff --git a/contracts/credit-manager/src/emergency_update.rs b/contracts/credit-manager/src/emergency_update.rs new file mode 100644 index 000000000..64bf9b2a0 --- /dev/null +++ b/contracts/credit-manager/src/emergency_update.rs @@ -0,0 +1,57 @@ +use cosmwasm_std::{Decimal, DepsMut, MessageInfo, Response, Uint128}; +use mars_rover::{ + adapters::vault::VaultUnchecked, + error::{ContractError::InvalidConfig, ContractResult}, + msg::execute::EmergencyUpdate, +}; + +use crate::state::{ALLOWED_COINS, OWNER, VAULT_CONFIGS}; + +pub fn emergency_config_update( + deps: DepsMut, + info: MessageInfo, + update: EmergencyUpdate, +) -> ContractResult { + OWNER.assert_emergency_owner(deps.storage, &info.sender)?; + + match update { + EmergencyUpdate::SetZeroMaxLtv(v) => set_zero_max_ltv(deps, v), + EmergencyUpdate::SetZeroDepositCap(v) => set_zero_deposit_cap(deps, v), + EmergencyUpdate::DisallowCoin(denom) => disallow_coin(deps, &denom), + } +} + +pub fn set_zero_max_ltv(deps: DepsMut, v: VaultUnchecked) -> ContractResult { + let vault = deps.api.addr_validate(&v.address)?; + let mut config = VAULT_CONFIGS.load(deps.storage, &vault)?; + config.max_ltv = Decimal::zero(); + VAULT_CONFIGS.save(deps.storage, &vault, &config)?; + + Ok(Response::new() + .add_attribute("action", "set_zero_max_ltv") + .add_attribute("vault", v.address)) +} + +pub fn set_zero_deposit_cap(deps: DepsMut, v: VaultUnchecked) -> ContractResult { + let vault = deps.api.addr_validate(&v.address)?; + let mut config = VAULT_CONFIGS.load(deps.storage, &vault)?; + config.deposit_cap.amount = Uint128::zero(); + VAULT_CONFIGS.save(deps.storage, &vault, &config)?; + + Ok(Response::new() + .add_attribute("action", "set_zero_deposit_cap") + .add_attribute("vault", v.address)) +} + +pub fn disallow_coin(deps: DepsMut, denom: &str) -> ContractResult { + let result = ALLOWED_COINS.remove(deps.storage, denom)?; + if !result { + return Err(InvalidConfig { + reason: format!("{denom} not in config"), + }); + } + + Ok(Response::new() + .add_attribute("action", "disallow_coin") + .add_attribute("denom", denom.to_string())) +} diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 6c0d21591..f4b8411bd 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -2,6 +2,7 @@ pub mod contract; pub mod borrow; pub mod deposit; +pub mod emergency_update; pub mod execute; pub mod health; pub mod instantiate; diff --git a/contracts/credit-manager/tests/helpers/mock_entity_info.rs b/contracts/credit-manager/tests/helpers/mock_entity_info.rs index efea10ae4..ba22ae557 100644 --- a/contracts/credit-manager/tests/helpers/mock_entity_info.rs +++ b/contracts/credit-manager/tests/helpers/mock_entity_info.rs @@ -51,9 +51,15 @@ pub fn unlocked_vault_info() -> VaultTestInfo { } pub fn generate_mock_vault(lockup: Option) -> VaultTestInfo { + let vault_token_denom = if lockup.is_some() { + "uleverage-locked".to_string() + } else { + "uleverage-unlocked".to_string() + }; + let lp_token = lp_token_info(); VaultTestInfo { - vault_token_denom: "uleverage".to_string(), + vault_token_denom, lockup, base_token_denom: lp_token.denom, deposit_cap: coin(10_000_000, "uusdc"), diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 033f7d9ad..8c72b8d6b 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -41,7 +41,7 @@ use mars_rover::{ zapper::{Zapper, ZapperBase}, }, msg::{ - execute::{Action, CallbackMsg}, + execute::{Action, CallbackMsg, EmergencyUpdate}, instantiate::{ConfigUpdates, VaultInstantiateConfig}, query::{ CoinBalanceResponseItem, ConfigResponse, DebtShares, LentShares, Positions, @@ -78,6 +78,7 @@ pub struct MockEnv { pub struct MockEnvBuilder { pub app: BasicApp, pub owner: Option, + pub emergency_owner: Option, pub vault_configs: Option>, pub pre_deployed_vaults: Option>, pub allowed_coins: Option>, @@ -97,6 +98,7 @@ impl MockEnv { MockEnvBuilder { app: App::default(), owner: None, + emergency_owner: None, vault_configs: None, pre_deployed_vaults: None, allowed_coins: None, @@ -161,6 +163,19 @@ impl MockEnv { ) } + pub fn emergency_update( + &mut self, + sender: &Addr, + update: EmergencyUpdate, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.rover.clone(), + &ExecuteMsg::EmergencyConfigUpdate(update), + &[], + ) + } + pub fn update_nft_config( &mut self, sender: &Addr, @@ -611,6 +626,8 @@ impl MockEnv { impl MockEnvBuilder { pub fn build(&mut self) -> AnyResult { let rover = self.get_rover()?; + self.set_emergency_owner(&rover); + let mars_oracle = self.get_oracle(); let health_contract = self.get_health_contract(); @@ -660,6 +677,21 @@ impl MockEnvBuilder { } } + pub fn set_emergency_owner(&mut self, rover: &Addr) { + if let Some(eo) = self.emergency_owner.clone() { + self.app + .execute_contract( + self.get_owner(), + rover.clone(), + &ExecuteMsg::UpdateOwner(OwnerUpdate::SetEmergencyOwner { + emergency_owner: eo.to_string(), + }), + &[], + ) + .unwrap(); + } + } + pub fn update_config(&mut self, rover: &Addr, updates: ConfigUpdates) { self.app .execute_contract( @@ -998,6 +1030,11 @@ impl MockEnvBuilder { self } + pub fn emergency_owner(&mut self, eo: &Addr) -> &mut Self { + self.emergency_owner = Some(eo.clone()); + self + } + pub fn vault_configs(&mut self, vault_configs: &[VaultTestInfo]) -> &mut Self { self.vault_configs = Some(vault_configs.to_vec()); self diff --git a/contracts/credit-manager/tests/test_emergency_powers.rs b/contracts/credit-manager/tests/test_emergency_powers.rs new file mode 100644 index 000000000..0e36c7191 --- /dev/null +++ b/contracts/credit-manager/tests/test_emergency_powers.rs @@ -0,0 +1,174 @@ +use cosmwasm_std::{Addr, StdError::NotFound}; +use mars_owner::OwnerError::NotEmergencyOwner; +use mars_rover::{ + adapters::vault::VaultUnchecked, + error::ContractError::{InvalidConfig, Owner, Std}, + msg::execute::EmergencyUpdate, +}; + +use crate::helpers::{ + assert_err, locked_vault_info, uatom_info, unlocked_vault_info, uosmo_info, MockEnv, +}; + +pub mod helpers; + +#[test] +fn only_emergency_owner_can_invoke_emergency_powers() { + let emergency_owner = Addr::unchecked("miles_morales"); + let mut mock = MockEnv::new().emergency_owner(&emergency_owner).build().unwrap(); + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.emergency_update(&bad_guy, EmergencyUpdate::DisallowCoin("uosmo".to_string())); + assert_err(res, Owner(NotEmergencyOwner {})) +} + +#[test] +fn not_callable_if_no_emergency_role_set() { + let mut mock = MockEnv::new().build().unwrap(); + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.emergency_update(&bad_guy, EmergencyUpdate::DisallowCoin("uosmo".to_string())); + assert_err(res, Owner(NotEmergencyOwner {})) +} + +#[test] +fn emergency_owner_can_blacklist_coins() { + let emergency_owner = Addr::unchecked("miles_morales"); + let osmo_info = uosmo_info(); + let atom_info = uatom_info(); + + let mut mock = MockEnv::new() + .emergency_owner(&emergency_owner) + .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) + .build() + .unwrap(); + + let allowed_coins_before = mock.query_allowed_coins(None, None); + assert_eq!(allowed_coins_before.len(), 2); + + mock.emergency_update(&emergency_owner, EmergencyUpdate::DisallowCoin(osmo_info.denom)) + .unwrap(); + + let allowed_coins_after = mock.query_allowed_coins(None, None); + assert_eq!(allowed_coins_after.len(), 1); + assert_eq!(allowed_coins_after.first().unwrap(), &atom_info.denom) +} + +#[test] +fn raises_if_coin_does_not_exist() { + let emergency_owner = Addr::unchecked("miles_morales"); + let osmo_info = uosmo_info(); + + let mut mock = MockEnv::new() + .emergency_owner(&emergency_owner) + .allowed_coins(&[osmo_info]) + .build() + .unwrap(); + + let res = + mock.emergency_update(&emergency_owner, EmergencyUpdate::DisallowCoin("xyz".to_string())); + + assert_err( + res, + InvalidConfig { + reason: "xyz not in config".to_string(), + }, + ) +} + +#[test] +fn emergency_owner_can_drop_vault_max_ltv() { + let emergency_owner = Addr::unchecked("miles_morales"); + let vault_a = locked_vault_info(); + let vault_b = unlocked_vault_info(); + + let mut mock = MockEnv::new() + .emergency_owner(&emergency_owner) + .vault_configs(&[vault_a.clone(), vault_b.clone()]) + .build() + .unwrap(); + + let vault_a_addr = mock.get_vault(&vault_a); + let vault_b_addr = mock.get_vault(&vault_b); + + let vault_a_config_before = mock.query_vault_config(&vault_a_addr).unwrap(); + assert!(!vault_a_config_before.config.max_ltv.is_zero()); + let vault_b_config_before = mock.query_vault_config(&vault_b_addr).unwrap(); + assert!(!vault_b_config_before.config.max_ltv.is_zero()); + + mock.emergency_update(&emergency_owner, EmergencyUpdate::SetZeroMaxLtv(vault_a_addr.clone())) + .unwrap(); + + let vault_a_config_after = mock.query_vault_config(&vault_a_addr).unwrap(); + assert!(vault_a_config_after.config.max_ltv.is_zero()); // Dropped to zero ✅ + let vault_b_config_after = mock.query_vault_config(&vault_b_addr).unwrap(); + assert!(!vault_b_config_after.config.max_ltv.is_zero()); +} + +#[test] +fn raises_if_vault_does_not_exist_for_max_ltv_drop() { + let emergency_owner = Addr::unchecked("miles_morales"); + + let mut mock = MockEnv::new().emergency_owner(&emergency_owner).build().unwrap(); + + let res = mock.emergency_update( + &emergency_owner, + EmergencyUpdate::SetZeroMaxLtv(VaultUnchecked::new("vault_addr_123".to_string())), + ); + + assert_err( + res, + Std(NotFound { + kind: "mars_rover::adapters::vault::config::VaultConfig".to_string(), + }), + ) +} + +#[test] +fn emergency_owner_can_drop_deposit_cap() { + let emergency_owner = Addr::unchecked("miles_morales"); + let vault_a = locked_vault_info(); + let vault_b = unlocked_vault_info(); + + let mut mock = MockEnv::new() + .emergency_owner(&emergency_owner) + .vault_configs(&[vault_a.clone(), vault_b.clone()]) + .build() + .unwrap(); + + let vault_a_addr = mock.get_vault(&vault_a); + let vault_b_addr = mock.get_vault(&vault_b); + + let vault_a_config_before = mock.query_vault_config(&vault_a_addr).unwrap(); + assert!(!vault_a_config_before.config.deposit_cap.amount.is_zero()); + let vault_b_config_before = mock.query_vault_config(&vault_b_addr).unwrap(); + assert!(!vault_b_config_before.config.deposit_cap.amount.is_zero()); + + mock.emergency_update( + &emergency_owner, + EmergencyUpdate::SetZeroDepositCap(vault_a_addr.clone()), + ) + .unwrap(); + + let vault_a_config_after = mock.query_vault_config(&vault_a_addr).unwrap(); + assert!(vault_a_config_after.config.deposit_cap.amount.is_zero()); // Dropped to zero ✅ + let vault_b_config_after = mock.query_vault_config(&vault_b_addr).unwrap(); + assert!(!vault_b_config_after.config.deposit_cap.amount.is_zero()); +} + +#[test] +fn raises_if_vault_does_not_exist_for_deposit_cap_drop() { + let emergency_owner = Addr::unchecked("miles_morales"); + + let mut mock = MockEnv::new().emergency_owner(&emergency_owner).build().unwrap(); + + let res = mock.emergency_update( + &emergency_owner, + EmergencyUpdate::SetZeroDepositCap(VaultUnchecked::new("vault_addr_123".to_string())), + ); + + assert_err( + res, + Std(NotFound { + kind: "mars_rover::adapters::vault::config::VaultConfig".to_string(), + }), + ) +} diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 9b47c2e4f..24a08407f 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -175,7 +175,7 @@ fn instantiate_raises_on_invalid_vaults_config() { fn duplicate_vaults_raises() { let mock = MockEnv::new() .pre_deployed_vault(&locked_vault_info(), None) - .pre_deployed_vault(&unlocked_vault_info(), None) + .pre_deployed_vault(&locked_vault_info(), None) .build(); if mock.is_ok() { panic!("Should have thrown an error"); diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index d0970e243..6901d7f70 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -28,6 +28,20 @@ pub enum ExecuteMsg { UpdateConfig { updates: ConfigUpdates, }, + /// Emergency owner has a narrow amount of config changes it is allowed to do: + /// - Lower maxLTV of vault to zero + /// - Lower deposit cap of vault to zero + /// - Remove asset from ALLOWED_COINS list. This has a second order consequence disallowing of that coin: + /// - Borrow + /// - Deposit + /// - Swap into + /// - Zap with/into + /// - Unzap into + /// Coin would still be allowed to: + /// - Withdraw + /// - Swap out of + /// - Repay loan of + EmergencyConfigUpdate(EmergencyUpdate), /// Manages owner role state UpdateOwner(OwnerUpdate), /// Update nft contract config @@ -68,6 +82,13 @@ impl From<&Coin> for ActionCoin { } } +#[cw_serde] +pub enum EmergencyUpdate { + SetZeroMaxLtv(VaultUnchecked), + SetZeroDepositCap(VaultUnchecked), + DisallowCoin(String), +} + /// The list of actions that users can perform on their positions #[cw_serde] pub enum Action { diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 078e24720..df65de0f1 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -257,6 +257,19 @@ }, "additionalProperties": false }, + { + "description": "Emergency owner has a narrow amount of config changes it is allowed to do: - Lower maxLTV of vault to zero - Lower deposit cap of vault to zero - Remove asset from ALLOWED_COINS list. This has a second order consequence disallowing of that coin: - Borrow - Deposit - Swap into - Zap with/into - Unzap into Coin would still be allowed to: - Withdraw - Swap out of - Repay loan of", + "type": "object", + "required": [ + "emergency_config_update" + ], + "properties": { + "emergency_config_update": { + "$ref": "#/definitions/EmergencyUpdate" + } + }, + "additionalProperties": false + }, { "description": "Manages owner role state", "type": "object", @@ -1369,6 +1382,46 @@ "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" }, + "EmergencyUpdate": { + "oneOf": [ + { + "type": "object", + "required": [ + "set_zero_max_ltv" + ], + "properties": { + "set_zero_max_ltv": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "set_zero_deposit_cap" + ], + "properties": { + "set_zero_deposit_cap": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "disallow_coin" + ], + "properties": { + "disallow_coin": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, "HealthContractBase_for_String": { "type": "string" }, @@ -1447,6 +1500,35 @@ "enum": [ "abolish_owner_role" ] + }, + { + "description": "A separate entity managed by Owner that can be used for granting specific emergency powers.", + "type": "object", + "required": [ + "set_emergency_owner" + ], + "properties": { + "set_emergency_owner": { + "type": "object", + "required": [ + "emergency_owner" + ], + "properties": { + "emergency_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove the entity in the Emergency Owner role", + "type": "string", + "enum": [ + "clear_emergency_owner" + ] } ] }, diff --git a/schemas/mars-rover-health-types/mars-rover-health-types.json b/schemas/mars-rover-health-types/mars-rover-health-types.json index 37084c39c..c9c6a5462 100644 --- a/schemas/mars-rover-health-types/mars-rover-health-types.json +++ b/schemas/mars-rover-health-types/mars-rover-health-types.json @@ -102,6 +102,35 @@ "enum": [ "abolish_owner_role" ] + }, + { + "description": "A separate entity managed by Owner that can be used for granting specific emergency powers.", + "type": "object", + "required": [ + "set_emergency_owner" + ], + "properties": { + "set_emergency_owner": { + "type": "object", + "required": [ + "emergency_owner" + ], + "properties": { + "emergency_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove the entity in the Emergency Owner role", + "type": "string", + "enum": [ + "clear_emergency_owner" + ] } ] } @@ -181,6 +210,12 @@ "abolished": { "type": "boolean" }, + "emergency_owner": { + "type": [ + "string", + "null" + ] + }, "initialized": { "type": "boolean" }, diff --git a/schemas/mars-swapper-base/mars-swapper-base.json b/schemas/mars-swapper-base/mars-swapper-base.json index 2a3a79534..0b88dbcc9 100644 --- a/schemas/mars-swapper-base/mars-swapper-base.json +++ b/schemas/mars-swapper-base/mars-swapper-base.json @@ -197,6 +197,35 @@ "enum": [ "abolish_owner_role" ] + }, + { + "description": "A separate entity managed by Owner that can be used for granting specific emergency powers.", + "type": "object", + "required": [ + "set_emergency_owner" + ], + "properties": { + "set_emergency_owner": { + "type": "object", + "required": [ + "emergency_owner" + ], + "properties": { + "emergency_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove the entity in the Emergency Owner role", + "type": "string", + "enum": [ + "clear_emergency_owner" + ] } ] }, @@ -375,6 +404,12 @@ "abolished": { "type": "boolean" }, + "emergency_owner": { + "type": [ + "string", + "null" + ] + }, "initialized": { "type": "boolean" }, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 3a0bc9fb6..9fd6144cd 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -24,6 +24,7 @@ import { Action, ActionAmount, VaultPositionType, + EmergencyUpdate, OwnerUpdate, CallbackMsg, Addr, @@ -413,6 +414,11 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt memo?: string, funds?: Coin[], ) => Promise + emergencyConfigUpdate: ( + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise updateOwner: ( fee?: number | StdFee | 'auto', memo?: string, @@ -450,6 +456,7 @@ export class MarsCreditManagerClient this.createCreditAccount = this.createCreditAccount.bind(this) this.updateCreditAccount = this.updateCreditAccount.bind(this) this.updateConfig = this.updateConfig.bind(this) + this.emergencyConfigUpdate = this.emergencyConfigUpdate.bind(this) this.updateOwner = this.updateOwner.bind(this) this.updateNftConfig = this.updateNftConfig.bind(this) this.callback = this.callback.bind(this) @@ -520,6 +527,22 @@ export class MarsCreditManagerClient funds, ) } + emergencyConfigUpdate = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + emergency_config_update: {}, + }, + fee, + memo, + funds, + ) + } updateOwner = async ( fee: number | StdFee | 'auto' = 'auto', memo?: string, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 43a029360..1dd761131 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -25,6 +25,7 @@ import { Action, ActionAmount, VaultPositionType, + EmergencyUpdate, OwnerUpdate, CallbackMsg, Addr, @@ -86,6 +87,7 @@ export interface MarsCreditManagerMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject + emergencyConfigUpdate: (funds?: Coin[]) => MsgExecuteContractEncodeObject updateOwner: (funds?: Coin[]) => MsgExecuteContractEncodeObject updateNftConfig: ( { @@ -107,6 +109,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag this.createCreditAccount = this.createCreditAccount.bind(this) this.updateCreditAccount = this.updateCreditAccount.bind(this) this.updateConfig = this.updateConfig.bind(this) + this.emergencyConfigUpdate = this.emergencyConfigUpdate.bind(this) this.updateOwner = this.updateOwner.bind(this) this.updateNftConfig = this.updateNftConfig.bind(this) this.callback = this.callback.bind(this) @@ -178,6 +181,21 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag }), } } + emergencyConfigUpdate = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + emergency_config_update: {}, + }), + ), + funds, + }), + } + } updateOwner = (funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 910ae1142..672ab99d2 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -25,6 +25,7 @@ import { Action, ActionAmount, VaultPositionType, + EmergencyUpdate, OwnerUpdate, CallbackMsg, Addr, @@ -686,6 +687,26 @@ export function useMarsCreditManagerUpdateOwnerMutation( options, ) } +export interface MarsCreditManagerEmergencyConfigUpdateMutation { + client: MarsCreditManagerClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsCreditManagerEmergencyConfigUpdateMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.emergencyConfigUpdate(msg, fee, memo, funds), + options, + ) +} export interface MarsCreditManagerUpdateConfigMutation { client: MarsCreditManagerClient msg: { diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 6c3af344a..8324498e8 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -57,6 +57,9 @@ export type ExecuteMsg = updates: ConfigUpdates } } + | { + emergency_config_update: EmergencyUpdate + } | { update_owner: OwnerUpdate } @@ -154,6 +157,16 @@ export type ActionAmount = exact: Uint128 } export type VaultPositionType = 'u_n_l_o_c_k_e_d' | 'l_o_c_k_e_d' | 'u_n_l_o_c_k_i_n_g' +export type EmergencyUpdate = + | { + set_zero_max_ltv: VaultBaseForString + } + | { + set_zero_deposit_cap: VaultBaseForString + } + | { + disallow_coin: string + } export type OwnerUpdate = | { propose_new_owner: { @@ -163,6 +176,12 @@ export type OwnerUpdate = | 'clear_proposed' | 'accept_proposed' | 'abolish_owner_role' + | { + set_emergency_owner: { + emergency_owner: string + } + } + | 'clear_emergency_owner' export type CallbackMsg = | { withdraw: { diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts index be9992a8e..4646815c0 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts @@ -26,6 +26,12 @@ export type OwnerUpdate = | 'clear_proposed' | 'accept_proposed' | 'abolish_owner_role' + | { + set_emergency_owner: { + emergency_owner: string + } + } + | 'clear_emergency_owner' export type QueryMsg = | { health: { @@ -41,6 +47,7 @@ export interface ConfigResponse { } export interface OwnerResponse { abolished: boolean + emergency_owner?: string | null initialized: boolean owner?: string | null proposed?: string | null diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts index 06111224b..abac5d2ea 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts @@ -42,6 +42,12 @@ export type OwnerUpdate = | 'clear_proposed' | 'accept_proposed' | 'abolish_owner_role' + | { + set_emergency_owner: { + emergency_owner: string + } + } + | 'clear_emergency_owner' export type Uint128 = string export type Decimal = string export type Addr = string @@ -80,6 +86,7 @@ export interface EstimateExactInSwapResponse { } export interface OwnerResponse { abolished: boolean + emergency_owner?: string | null initialized: boolean owner?: string | null proposed?: string | null From e41163276401859ef27a6e32133980ea2cb973b7 Mon Sep 17 00:00:00 2001 From: piobab Date: Sat, 18 Mar 2023 01:05:41 +0100 Subject: [PATCH 151/218] Liquidate lent asset (#125) * Add lend denoms to health calculation. * Add liquidate lent coin msgs. * Add tests for lent collateral. * Add tests for lent liquidation. * Cleanup. * Fix clippy. * Update schema. * Remove duplicated code. * Simplify liquidation. Split deposit / lend to separate msgs. * Update schema. * Add test case for liquidate full lent position. --- contracts/credit-manager/src/execute.rs | 110 +++-- contracts/credit-manager/src/lib.rs | 3 +- ...liquidate_coin.rs => liquidate_deposit.rs} | 4 +- .../credit-manager/src/liquidate_lend.rs | 68 +++ contracts/credit-manager/src/reclaim.rs | 2 +- contracts/credit-manager/src/utils.rs | 32 +- .../src/vault/liquidate_vault.rs | 2 +- .../credit-manager/tests/helpers/utils.rs | 6 +- ...date_coin.rs => test_liquidate_deposit.rs} | 49 +- .../tests/test_liquidate_lend.rs | 429 ++++++++++++++++++ .../tests/test_liquidate_vault.rs | 85 ++-- contracts/health/src/compute.rs | 12 +- .../health-computer/src/health_computer.rs | 28 +- .../tests/test_health_scenarios.rs | 140 +++++- packages/rover/src/msg/execute.rs | 66 +-- packages/rover/src/msg/query.rs | 11 + .../mars-credit-manager.json | 213 +++++---- .../MarsCreditManager.client.ts | 2 + .../MarsCreditManager.message-composer.ts | 2 + .../MarsCreditManager.react-query.ts | 2 + .../MarsCreditManager.types.ts | 51 ++- 21 files changed, 1054 insertions(+), 263 deletions(-) rename contracts/credit-manager/src/{liquidate_coin.rs => liquidate_deposit.rs} (98%) create mode 100644 contracts/credit-manager/src/liquidate_lend.rs rename contracts/credit-manager/tests/{test_liquidate_coin.rs => test_liquidate_deposit.rs} (95%) create mode 100644 contracts/credit-manager/tests/test_liquidate_lend.rs diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 4156f3c8c..65c005763 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -5,7 +5,7 @@ use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; use mars_rover::{ coins::Coins, error::{ContractError, ContractResult}, - msg::execute::{Action, CallbackMsg}, + msg::execute::{Action, CallbackMsg, LiquidateRequest}, }; use crate::{ @@ -13,7 +13,8 @@ use crate::{ deposit::deposit, health::{assert_max_ltv, query_health}, lend::lend, - liquidate_coin::liquidate_coin, + liquidate_deposit::liquidate_deposit, + liquidate_lend::liquidate_lend, reclaim::reclaim, refund::refund_coin_balances, repay::repay, @@ -92,28 +93,36 @@ pub fn dispatch_actions( vault: vault.check(deps.api)?, coin: coin.clone(), }), - Action::LiquidateCoin { + Action::Liquidate { liquidatee_account_id, debt_coin, - request_coin_denom, - } => callbacks.push(CallbackMsg::LiquidateCoin { - liquidator_account_id: account_id.to_string(), - liquidatee_account_id: liquidatee_account_id.to_string(), - debt_coin: debt_coin.clone(), - request_coin_denom: request_coin_denom.clone(), - }), - Action::LiquidateVault { - liquidatee_account_id, - debt_coin, - request_vault, - position_type, - } => callbacks.push(CallbackMsg::LiquidateVault { - liquidator_account_id: account_id.to_string(), - liquidatee_account_id: liquidatee_account_id.to_string(), - debt_coin: debt_coin.clone(), - request_vault: request_vault.check(deps.api)?, - position_type: position_type.clone(), - }), + request, + } => match request { + LiquidateRequest::Deposit(denom) => callbacks.push(CallbackMsg::Liquidate { + liquidator_account_id: account_id.to_string(), + liquidatee_account_id: liquidatee_account_id.to_string(), + debt_coin: debt_coin.clone(), + request: LiquidateRequest::Deposit(denom.clone()), + }), + LiquidateRequest::Lend(denom) => callbacks.push(CallbackMsg::Liquidate { + liquidator_account_id: account_id.to_string(), + liquidatee_account_id: liquidatee_account_id.to_string(), + debt_coin: debt_coin.clone(), + request: LiquidateRequest::Lend(denom.clone()), + }), + LiquidateRequest::Vault { + request_vault, + position_type, + } => callbacks.push(CallbackMsg::Liquidate { + liquidator_account_id: account_id.to_string(), + liquidatee_account_id: liquidatee_account_id.to_string(), + debt_coin: debt_coin.clone(), + request: LiquidateRequest::Vault { + request_vault: request_vault.check(deps.api)?, + position_type: position_type.clone(), + }, + }), + }, Action::SwapExactIn { coin_in, denom_out, @@ -251,34 +260,41 @@ pub fn execute_callback( previous_total_balance, &env.contract.address, ), - CallbackMsg::LiquidateCoin { - liquidator_account_id, - liquidatee_account_id, - debt_coin, - request_coin_denom, - } => liquidate_coin( - deps, - env, - &liquidator_account_id, - &liquidatee_account_id, - debt_coin, - &request_coin_denom, - ), - CallbackMsg::LiquidateVault { + CallbackMsg::Liquidate { liquidator_account_id, liquidatee_account_id, debt_coin, - request_vault, - position_type, - } => liquidate_vault( - deps, - env, - &liquidator_account_id, - &liquidatee_account_id, - debt_coin, - request_vault, - position_type, - ), + request, + } => match request { + LiquidateRequest::Deposit(request_coin_denom) => liquidate_deposit( + deps, + env, + &liquidator_account_id, + &liquidatee_account_id, + debt_coin, + &request_coin_denom, + ), + LiquidateRequest::Lend(request_coin_denom) => liquidate_lend( + deps, + env, + &liquidator_account_id, + &liquidatee_account_id, + debt_coin, + &request_coin_denom, + ), + LiquidateRequest::Vault { + request_vault, + position_type, + } => liquidate_vault( + deps, + env, + &liquidator_account_id, + &liquidatee_account_id, + debt_coin, + request_vault, + position_type, + ), + }, CallbackMsg::SwapExactIn { account_id, coin_in, diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index f4b8411bd..525d6aba9 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -7,7 +7,8 @@ pub mod execute; pub mod health; pub mod instantiate; pub mod lend; -pub mod liquidate_coin; +pub mod liquidate_deposit; +pub mod liquidate_lend; pub mod query; pub mod reclaim; pub mod refund; diff --git a/contracts/credit-manager/src/liquidate_coin.rs b/contracts/credit-manager/src/liquidate_deposit.rs similarity index 98% rename from contracts/credit-manager/src/liquidate_coin.rs rename to contracts/credit-manager/src/liquidate_deposit.rs index 444cc766a..59953ec66 100644 --- a/contracts/credit-manager/src/liquidate_coin.rs +++ b/contracts/credit-manager/src/liquidate_deposit.rs @@ -17,7 +17,7 @@ use crate::{ utils::{decrement_coin_balance, increment_coin_balance}, }; -pub fn liquidate_coin( +pub fn liquidate_deposit( deps: DepsMut, env: Env, liquidator_account_id: &str, @@ -47,7 +47,7 @@ pub fn liquidate_coin( Ok(Response::new() .add_message(repay_msg) - .add_attribute("action", "liquidate_coin") + .add_attribute("action", "liquidate_deposit") .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("coin_debt_repaid", debt.to_string()) diff --git a/contracts/credit-manager/src/liquidate_lend.rs b/contracts/credit-manager/src/liquidate_lend.rs new file mode 100644 index 000000000..7028c28c1 --- /dev/null +++ b/contracts/credit-manager/src/liquidate_lend.rs @@ -0,0 +1,68 @@ +use cosmwasm_std::{Coin, DepsMut, Env, Response}; +use mars_rover::error::ContractResult; + +use crate::{ + liquidate_deposit::{calculate_liquidation, repay_debt}, + reclaim::{current_lent_amount_for_denom, lent_amount_to_shares}, + utils::{decrement_lent_shares, increment_lent_shares}, +}; + +pub fn liquidate_lend( + deps: DepsMut, + env: Env, + liquidator_account_id: &str, + liquidatee_account_id: &str, + debt_coin: Coin, + request_coin_denom: &str, +) -> ContractResult { + // Check how much lent coin is available for reclaim (can be withdrawn from Red Bank) + let (total_lent_amount, _) = current_lent_amount_for_denom( + deps.as_ref(), + &env, + liquidatee_account_id, + request_coin_denom, + )?; + + let (debt, request) = calculate_liquidation( + &deps, + &env, + liquidatee_account_id, + &debt_coin, + request_coin_denom, + total_lent_amount, + )?; + + let repay_msg = + repay_debt(deps.storage, &env, liquidator_account_id, liquidatee_account_id, &debt)?; + + let shares_to_transfer = lent_amount_to_shares( + deps.as_ref(), + &env, + &Coin { + denom: request_coin_denom.to_string(), + amount: request.amount, + }, + )?; + + // Transfer requested lent coin from liquidatee to liquidator + decrement_lent_shares( + deps.storage, + liquidatee_account_id, + request_coin_denom, + shares_to_transfer, + )?; + increment_lent_shares( + deps.storage, + liquidator_account_id, + request_coin_denom, + shares_to_transfer, + )?; + + Ok(Response::new() + .add_message(repay_msg) + .add_attribute("action", "liquidate_lend") + .add_attribute("account_id", liquidator_account_id) + .add_attribute("liquidatee_account_id", liquidatee_account_id) + .add_attribute("coin_debt_repaid", debt.to_string()) + .add_attribute("coin_liquidated", request.to_string())) +} diff --git a/contracts/credit-manager/src/reclaim.rs b/contracts/credit-manager/src/reclaim.rs index f03698853..305080620 100644 --- a/contracts/credit-manager/src/reclaim.rs +++ b/contracts/credit-manager/src/reclaim.rs @@ -70,7 +70,7 @@ pub fn reclaim( .add_attribute("coin_reclaimed", format!("{}{}", amount_to_reclaim, &coin.denom))) } -fn lent_amount_to_shares(deps: Deps, env: &Env, coin: &Coin) -> ContractResult { +pub fn lent_amount_to_shares(deps: Deps, env: &Env, coin: &Coin) -> ContractResult { let red_bank = RED_BANK.load(deps.storage)?; let total_lent_shares = TOTAL_LENT_SHARES.load(deps.storage, &coin.denom)?; let total_lent = red_bank.query_lent(&deps.querier, &env.contract.address, &coin.denom)?; diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 1a0b850db..3078e3ca3 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -13,8 +13,8 @@ use mars_rover::{ use crate::{ state::{ - ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, HEALTH_CONTRACT, ORACLE, RED_BANK, SWAPPER, - TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, VAULT_CONFIGS, ZAPPER, + ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, HEALTH_CONTRACT, LENT_SHARES, ORACLE, RED_BANK, + SWAPPER, TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, VAULT_CONFIGS, ZAPPER, }, update_coin_balances::query_balance, }; @@ -86,6 +86,34 @@ pub fn decrement_coin_balance( Ok(new_value) } +pub fn increment_lent_shares( + storage: &mut dyn Storage, + account_id: &str, + denom: &str, + shares: Uint128, +) -> ContractResult { + LENT_SHARES.update(storage, (account_id, denom), |value_opt| { + value_opt.unwrap_or_else(Uint128::zero).checked_add(shares).map_err(ContractError::Overflow) + }) +} + +pub fn decrement_lent_shares( + storage: &mut dyn Storage, + account_id: &str, + denom: &str, + shares: Uint128, +) -> ContractResult { + let path = LENT_SHARES.key((account_id, denom)); + let value_opt = path.may_load(storage)?; + let new_value = value_opt.unwrap_or_else(Uint128::zero).checked_sub(shares)?; + if new_value.is_zero() { + path.remove(storage); + } else { + path.save(storage, &new_value)?; + } + Ok(new_value) +} + pub fn update_balance_msg( querier: &QuerierWrapper, rover_addr: &Addr, diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index 7050815dd..9a50f36b5 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -11,7 +11,7 @@ use mars_rover::{ }; use crate::{ - liquidate_coin::{calculate_liquidation, repay_debt}, + liquidate_deposit::{calculate_liquidation, repay_debt}, state::VAULT_POSITIONS, utils::update_balance_msg, vault::update_vault_position, diff --git a/contracts/credit-manager/tests/helpers/utils.rs b/contracts/credit-manager/tests/helpers/utils.rs index 967ea80df..a87a16705 100644 --- a/contracts/credit-manager/tests/helpers/utils.rs +++ b/contracts/credit-manager/tests/helpers/utils.rs @@ -1,5 +1,5 @@ use cosmwasm_std::Coin; -use mars_rover::msg::query::DebtAmount; +use mars_rover::msg::query::{DebtAmount, LentAmount}; pub fn get_coin(denom: &str, coins: &[Coin]) -> Coin { coins.iter().find(|cv| cv.denom == denom).unwrap().clone() @@ -8,3 +8,7 @@ pub fn get_coin(denom: &str, coins: &[Coin]) -> Coin { pub fn get_debt(denom: &str, coins: &[DebtAmount]) -> DebtAmount { coins.iter().find(|coin| coin.denom.as_str() == denom).unwrap().clone() } + +pub fn get_lent(denom: &str, coins: &[LentAmount]) -> LentAmount { + coins.iter().find(|coin| coin.denom.as_str() == denom).unwrap().clone() +} diff --git a/contracts/credit-manager/tests/test_liquidate_coin.rs b/contracts/credit-manager/tests/test_liquidate_deposit.rs similarity index 95% rename from contracts/credit-manager/tests/test_liquidate_coin.rs rename to contracts/credit-manager/tests/test_liquidate_deposit.rs index 126eddebe..406dd53b6 100644 --- a/contracts/credit-manager/tests/test_liquidate_coin.rs +++ b/contracts/credit-manager/tests/test_liquidate_deposit.rs @@ -5,7 +5,10 @@ use mars_rover::{ ContractError, ContractError::{AboveMaxLTV, LiquidationNotProfitable, NotLiquidatable}, }, - msg::execute::Action::{Borrow, Deposit, EnterVault, LiquidateCoin}, + msg::execute::{ + Action::{Borrow, Deposit, EnterVault, Liquidate}, + LiquidateRequest, + }, }; use crate::helpers::{ @@ -51,10 +54,10 @@ fn can_only_liquidate_unhealthy_accounts() { let res = mock.update_credit_account( &liquidator_account_id, &liquidator, - vec![LiquidateCoin { + vec![Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: uatom_info.to_coin(10), - request_coin_denom: uosmo_info.denom, + request: LiquidateRequest::Deposit(uosmo_info.denom), }], &[], ); @@ -112,10 +115,10 @@ fn vault_positions_contribute_to_health() { let res = mock.update_credit_account( &liquidator_account_id, &liquidator, - vec![LiquidateCoin { + vec![Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: atom_info.to_coin(10), - request_coin_denom: atom_info.denom, + request: LiquidateRequest::Deposit(atom_info.denom), }], &[], ); @@ -170,10 +173,10 @@ fn liquidatee_does_not_have_requested_asset() { &liquidator, vec![ Borrow(uatom_info.to_coin(50)), - LiquidateCoin { + Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: uatom_info.to_coin(10), - request_coin_denom: ujake_info.denom.clone(), + request: LiquidateRequest::Deposit(ujake_info.denom.clone()), }, ], &[], @@ -238,10 +241,10 @@ fn liquidatee_does_not_have_debt_coin() { &liquidator, vec![ Borrow(uatom_info.to_coin(50)), - LiquidateCoin { + Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: ujake_info.to_coin(10), - request_coin_denom: uatom_info.denom, + request: LiquidateRequest::Deposit(uatom_info.denom), }, ], &[], @@ -288,10 +291,10 @@ fn liquidator_does_not_have_enough_to_pay_debt() { let res = mock.update_credit_account( &liquidator_account_id, &liquidator, - vec![LiquidateCoin { + vec![Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: uatom_info.to_coin(10), - request_coin_denom: uosmo_info.denom, + request: LiquidateRequest::Deposit(uosmo_info.denom), }], &[], ); @@ -346,10 +349,10 @@ fn liquidator_left_in_unhealthy_state() { &liquidator, vec![ Borrow(uatom_info.to_coin(10)), - LiquidateCoin { + Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: uatom_info.to_coin(10), - request_coin_denom: uosmo_info.denom, + request: LiquidateRequest::Deposit(uosmo_info.denom), }, ], &[], @@ -414,10 +417,10 @@ fn liquidation_not_profitable_after_calculations() { &liquidator, vec![ Deposit(uatom_info.to_coin(10)), - LiquidateCoin { + Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: uatom_info.to_coin(5), - request_coin_denom: uosmo_info.denom.clone(), + request: LiquidateRequest::Deposit(uosmo_info.denom.clone()), }, ], &[uatom_info.to_coin(10)], @@ -473,10 +476,10 @@ fn debt_amount_adjusted_to_close_factor_max() { &liquidator, vec![ Deposit(uatom_info.to_coin(50)), - LiquidateCoin { + Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: uatom_info.to_coin(50), - request_coin_denom: uosmo_info.denom, + request: LiquidateRequest::Deposit(uosmo_info.denom), }, ], &[uatom_info.to_coin(50)], @@ -551,10 +554,10 @@ fn debt_amount_adjusted_to_total_debt_for_denom() { &liquidator, vec![ Deposit(ujake_info.to_coin(50)), - LiquidateCoin { + Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: ujake_info.to_coin(50), - request_coin_denom: uosmo_info.denom, + request: LiquidateRequest::Deposit(uosmo_info.denom), }, ], &[ujake_info.to_coin(50)], @@ -625,10 +628,10 @@ fn debt_amount_adjusted_to_max_allowed_by_request_coin() { &liquidator, vec![ Deposit(uatom_info.to_coin(50)), - LiquidateCoin { + Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: uatom_info.to_coin(50), - request_coin_denom: uosmo_info.denom, + request: LiquidateRequest::Deposit(uosmo_info.denom), }, ], &[uatom_info.to_coin(50)], @@ -698,10 +701,10 @@ fn debt_amount_no_adjustment() { &liquidator, vec![ Deposit(uatom_info.to_coin(10)), - LiquidateCoin { + Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: uatom_info.to_coin(10), - request_coin_denom: uosmo_info.denom, + request: LiquidateRequest::Deposit(uosmo_info.denom), }, ], &[uatom_info.to_coin(10)], diff --git a/contracts/credit-manager/tests/test_liquidate_lend.rs b/contracts/credit-manager/tests/test_liquidate_lend.rs new file mode 100644 index 000000000..ac2e22bfd --- /dev/null +++ b/contracts/credit-manager/tests/test_liquidate_lend.rs @@ -0,0 +1,429 @@ +use cosmwasm_std::{coins, Addr, Decimal, Uint128}; +use mars_mock_oracle::msg::CoinPrice; +use mars_rover::{ + error::{ContractError, ContractError::NotLiquidatable}, + msg::execute::{ + Action::{Borrow, Deposit, Lend, Liquidate, Reclaim}, + ActionAmount, ActionCoin, LiquidateRequest, + }, +}; + +use crate::helpers::{ + assert_err, get_coin, get_debt, get_lent, uatom_info, ujake_info, uosmo_info, AccountToFund, + MockEnv, +}; + +pub mod helpers; + +#[test] +fn lent_positions_contribute_to_health() { + let uatom_info = uatom_info(); + let uosmo_info = uosmo_info(); + + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .allowed_coins(&[uatom_info.clone(), uosmo_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![uatom_info.to_coin(500), uosmo_info.to_coin(500)], + }) + .build() + .unwrap(); + + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![Deposit(uatom_info.to_coin(100)), Borrow(uosmo_info.to_coin(40))], + &[uatom_info.to_coin(100)], + ) + .unwrap(); + + let health_1 = mock.query_health(&liquidatee_account_id); + assert!(!health_1.liquidatable); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![Lend(uatom_info.to_coin(50))], + &[], + ) + .unwrap(); + + // Collateral should be the same after Lend + let health_2 = mock.query_health(&liquidatee_account_id); + assert!(!health_2.liquidatable); + // health_2.total_collateral_value bigger (+1) because of simulated yield + assert_eq!(health_1.total_collateral_value, health_2.total_collateral_value - Uint128::one()); + assert_eq!(health_1.max_ltv_adjusted_collateral, health_2.max_ltv_adjusted_collateral); + assert_eq!( + health_1.liquidation_threshold_adjusted_collateral, + health_2.liquidation_threshold_adjusted_collateral + ); + + let liquidator = Addr::unchecked("liquidator"); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![Liquidate { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uosmo_info.to_coin(10), + request: LiquidateRequest::Lend(uatom_info.denom), + }], + &[], + ); + + assert_err( + res, + NotLiquidatable { + account_id: liquidatee_account_id, + lqdt_health_factor: "9.7".to_string(), + }, + ) +} + +#[test] +fn liquidatee_does_not_have_requested_lent_coin() { + let uatom_info = uatom_info(); + let uosmo_info = uosmo_info(); + let ujake_info = ujake_info(); + + let liquidatee = Addr::unchecked("liquidatee"); + let liquidator = Addr::unchecked("liquidator"); + + let mut mock = MockEnv::new() + .allowed_coins(&[uatom_info.clone(), uosmo_info.clone(), ujake_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: vec![uatom_info.to_coin(500)], + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: vec![uosmo_info.to_coin(500)], + }) + .build() + .unwrap(); + + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(uatom_info.to_coin(100)), + Lend(uatom_info.to_coin(50)), + Borrow(uosmo_info.to_coin(100)), + ], + &[uatom_info.to_coin(100)], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: uosmo_info.denom.clone(), + price: Decimal::from_atomics(20u128, 0).unwrap(), + }); + + let health = mock.query_health(&liquidatee_account_id); + assert!(health.liquidatable); + + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(uosmo_info.to_coin(10)), + Liquidate { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uosmo_info.to_coin(10), + request: LiquidateRequest::Lend(ujake_info.denom), + }, + ], + &[uosmo_info.to_coin(10)], + ); + + assert_err(res, ContractError::NoneLent); +} + +#[test] +fn lent_position_partially_liquidated() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + + let liquidator = Addr::unchecked("liquidator"); + let liquidatee = Addr::unchecked("liquidatee"); + + let mut mock = MockEnv::new() + .max_close_factor(Decimal::from_atomics(6u128, 1).unwrap()) + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: coins(300, uosmo_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: coins(300, uatom_info.denom.clone()), + }) + .build() + .unwrap(); + + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(100)), + Lend(uosmo_info.to_coin(202)), + ], + &[uosmo_info.to_coin(300)], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(55u128, 1).unwrap(), + }); + + let health = mock.query_health(&liquidatee_account_id); + assert!(health.liquidatable); + assert_eq!(health.total_collateral_value, Uint128::new(624u128)); + assert_eq!(health.total_debt_value, Uint128::new(555u128)); + + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(uatom_info.to_coin(6)), + Liquidate { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(6), + request: LiquidateRequest::Lend(uosmo_info.denom), + }, + ], + &[uatom_info.to_coin(6)], + ) + .unwrap(); + + // Assert liquidatee's new position + let position = mock.query_positions(&liquidatee_account_id); + assert_eq!(position.deposits.len(), 2); + let osmo_balance = get_coin("uosmo", &position.deposits); + assert_eq!(osmo_balance.amount, Uint128::new(98)); + let atom_balance = get_coin("uatom", &position.deposits); + assert_eq!(atom_balance.amount, Uint128::new(100)); + + assert_eq!(position.debts.len(), 1); + let atom_debt = get_debt("uatom", &position.debts); + assert_eq!(atom_debt.amount, Uint128::new(95)); + + assert_eq!(position.lends.len(), 1); + let osmo_lent = get_lent("uosmo", &position.lends); + assert_eq!(osmo_lent.amount, Uint128::new(59)); + + // Assert liquidator's new position + let position = mock.query_positions(&liquidator_account_id); + assert_eq!(position.deposits.len(), 0); + assert_eq!(position.debts.len(), 0); + + assert_eq!(position.lends.len(), 1); + let osmo_lent = get_lent("uosmo", &position.lends); + assert_eq!(osmo_lent.amount, Uint128::new(143)); +} + +#[test] +fn lent_position_fully_liquidated() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + + let liquidator = Addr::unchecked("liquidator"); + let liquidatee = Addr::unchecked("liquidatee"); + + let mut mock = MockEnv::new() + .max_close_factor(Decimal::from_atomics(6u128, 1).unwrap()) + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: coins(300, uosmo_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: coins(300, uatom_info.denom.clone()), + }) + .build() + .unwrap(); + + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.price_change(CoinPrice { + denom: uosmo_info.denom.clone(), + price: Decimal::from_atomics(10u128, 1).unwrap(), + }); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(500)), + Lend(uosmo_info.to_coin(109)), + ], + &[uosmo_info.to_coin(300)], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(50u128, 1).unwrap(), + }); + + let health = mock.query_health(&liquidatee_account_id); + assert!(health.liquidatable); + assert_eq!(health.total_collateral_value, Uint128::new(2801u128)); + assert_eq!(health.total_debt_value, Uint128::new(2505u128)); + + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(uatom_info.to_coin(32)), + Liquidate { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(32), + request: LiquidateRequest::Lend(uosmo_info.denom), + }, + ], + &[uatom_info.to_coin(32)], + ) + .unwrap(); + + // Assert liquidatee's new position + let position = mock.query_positions(&liquidatee_account_id); + assert_eq!(position.deposits.len(), 2); + let osmo_balance = get_coin("uosmo", &position.deposits); + assert_eq!(osmo_balance.amount, Uint128::new(191)); + let atom_balance = get_coin("uatom", &position.deposits); + assert_eq!(atom_balance.amount, Uint128::new(500)); + + assert_eq!(position.debts.len(), 1); + let atom_debt = get_debt("uatom", &position.debts); + assert_eq!(atom_debt.amount, Uint128::new(481)); + + assert_eq!(position.lends.len(), 0); + + // Assert liquidator's new position + let position = mock.query_positions(&liquidator_account_id); + assert_eq!(position.deposits.len(), 1); + let atom_balance = get_coin("uatom", &position.deposits); + assert_eq!(atom_balance.amount, Uint128::new(12)); + + assert_eq!(position.debts.len(), 0); + + assert_eq!(position.lends.len(), 1); + let osmo_lent = get_lent("uosmo", &position.lends); + assert_eq!(osmo_lent.amount, Uint128::new(110)); +} + +#[test] +fn liquidate_with_reclaiming() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + + let liquidator = Addr::unchecked("liquidator"); + let liquidatee = Addr::unchecked("liquidatee"); + + let mut mock = MockEnv::new() + .max_close_factor(Decimal::from_atomics(6u128, 1).unwrap()) + .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: coins(300, uosmo_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: coins(300, uatom_info.denom.clone()), + }) + .build() + .unwrap(); + + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(300)), + Borrow(uatom_info.to_coin(100)), + Lend(uosmo_info.to_coin(202)), + ], + &[uosmo_info.to_coin(300)], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(55u128, 1).unwrap(), + }); + + let health = mock.query_health(&liquidatee_account_id); + assert!(health.liquidatable); + assert_eq!(health.total_collateral_value, Uint128::new(624u128)); + assert_eq!(health.total_debt_value, Uint128::new(555u128)); + + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(uatom_info.to_coin(10)), + Liquidate { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(10), + request: LiquidateRequest::Lend(uosmo_info.denom.clone()), + }, + Reclaim(ActionCoin { + denom: uosmo_info.denom, + amount: ActionAmount::AccountBalance, + }), + ], + &[uatom_info.to_coin(10)], + ) + .unwrap(); + + // Assert liquidatee's new position + let position = mock.query_positions(&liquidatee_account_id); + assert_eq!(position.deposits.len(), 2); + let osmo_balance = get_coin("uosmo", &position.deposits); + assert_eq!(osmo_balance.amount, Uint128::new(98)); + let atom_balance = get_coin("uatom", &position.deposits); + assert_eq!(atom_balance.amount, Uint128::new(100)); + + assert_eq!(position.debts.len(), 1); + let atom_debt = get_debt("uatom", &position.debts); + assert_eq!(atom_debt.amount, Uint128::new(93)); + + assert_eq!(position.lends.len(), 1); + let osmo_lent = get_lent("uosmo", &position.lends); + assert_eq!(osmo_lent.amount, Uint128::new(10)); + + // Assert liquidator's new position + let position = mock.query_positions(&liquidator_account_id); + assert_eq!(position.deposits.len(), 2); + let osmo_balance = get_coin("uosmo", &position.deposits); + assert_eq!(osmo_balance.amount, Uint128::new(191)); + let atom_balance = get_coin("uatom", &position.deposits); + assert_eq!(atom_balance.amount, Uint128::new(2)); + + assert_eq!(position.debts.len(), 0); + + assert_eq!(position.lends.len(), 0); +} diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 6430506d9..b1df78d46 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -5,7 +5,10 @@ use mars_mock_oracle::msg::CoinPrice; use mars_rover::{ adapters::vault::{VaultBase, VaultPositionType}, error::ContractError, - msg::execute::Action::{Borrow, Deposit, EnterVault, LiquidateVault, RequestVaultUnlock}, + msg::execute::{ + Action::{Borrow, Deposit, EnterVault, Liquidate, RequestVaultUnlock}, + LiquidateRequest, + }, }; use crate::helpers::{ @@ -51,11 +54,13 @@ fn liquidatee_must_have_the_request_vault_position() { let res = mock.update_credit_account( &liquidator_account_id, &liquidator, - vec![LiquidateVault { + vec![Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: uatom.to_coin(10), - request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), - position_type: VaultPositionType::UNLOCKED, + request: LiquidateRequest::Vault { + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKED, + }, }], &[], ); @@ -107,11 +112,13 @@ fn liquidatee_is_not_liquidatable() { let res = mock.update_credit_account( &liquidator_account_id, &liquidator, - vec![LiquidateVault { + vec![Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: lp_token.to_coin(10), - request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), - position_type: VaultPositionType::UNLOCKED, + request: LiquidateRequest::Vault { + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKED, + }, }], &[], ); @@ -171,11 +178,13 @@ fn liquidator_does_not_have_debt_coin_in_credit_account() { let res = mock.update_credit_account( &liquidator_account_id, &liquidator, - vec![LiquidateVault { + vec![Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: ujake.to_coin(10), - request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), - position_type: VaultPositionType::UNLOCKED, + request: LiquidateRequest::Vault { + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKED, + }, }], &[], ); @@ -229,11 +238,13 @@ fn wrong_position_type_sent_for_unlocked_vault() { let res = mock.update_credit_account( &liquidator_account_id, &liquidator, - vec![LiquidateVault { + vec![Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: lp_token.to_coin(10), - request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), - position_type: VaultPositionType::LOCKED, + request: LiquidateRequest::Vault { + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::LOCKED, + }, }], &[], ); @@ -243,11 +254,13 @@ fn wrong_position_type_sent_for_unlocked_vault() { let res = mock.update_credit_account( &liquidator_account_id, &liquidator, - vec![LiquidateVault { + vec![Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: lp_token.to_coin(10), - request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), - position_type: VaultPositionType::UNLOCKING, + request: LiquidateRequest::Vault { + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKING, + }, }], &[], ); @@ -294,11 +307,13 @@ fn wrong_position_type_sent_for_locked_vault() { let res = mock.update_credit_account( &liquidator_account_id, &liquidator, - vec![LiquidateVault { + vec![Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: lp_token.to_coin(10), - request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), - position_type: VaultPositionType::UNLOCKED, + request: LiquidateRequest::Vault { + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKED, + }, }], &[], ); @@ -359,11 +374,13 @@ fn liquidate_unlocked_vault() { &liquidator, vec![ Deposit(ujake.to_coin(10)), - LiquidateVault { + Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: ujake.to_coin(10), - request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), - position_type: VaultPositionType::UNLOCKED, + request: LiquidateRequest::Vault { + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKED, + }, }, ], &[ujake.to_coin(10)], @@ -445,11 +462,13 @@ fn liquidate_locked_vault() { &liquidator, vec![ Deposit(atom.to_coin(30)), - LiquidateVault { + Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: atom.to_coin(30), - request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), - position_type: VaultPositionType::LOCKED, + request: LiquidateRequest::Vault { + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::LOCKED, + }, }, ], &[atom.to_coin(30)], @@ -550,11 +569,13 @@ fn liquidate_unlocking_liquidation_order() { &liquidator, vec![ Deposit(ujake.to_coin(10)), - LiquidateVault { + Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: ujake.to_coin(10), - request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), - position_type: VaultPositionType::UNLOCKING, + request: LiquidateRequest::Vault { + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKING, + }, }, ], &[ujake.to_coin(10)], @@ -648,13 +669,15 @@ fn liquidation_calculation_adjustment() { &liquidator, vec![ Deposit(ujake.to_coin(500)), - LiquidateVault { + Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), // Given the request vault balance, this debt payment is too high. // It will be adjusted to 85, the max given the request vault value debt_coin: ujake.to_coin(500), - request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), - position_type: VaultPositionType::UNLOCKED, + request: LiquidateRequest::Vault { + request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), + position_type: VaultPositionType::UNLOCKED, + }, }, ], &[ujake.to_coin(500)], diff --git a/contracts/health/src/compute.rs b/contracts/health/src/compute.rs index 80e15f35a..179f315b7 100644 --- a/contracts/health/src/compute.rs +++ b/contracts/health/src/compute.rs @@ -19,6 +19,7 @@ pub fn compute_health(deps: Deps, account_id: &str) -> HealthResult>(); let debt_denoms = positions.debts.iter().map(|d| &d.denom).collect::>(); + let lend_denoms = positions.lends.iter().map(|d| &d.denom).collect::>(); let vault_infos = positions .vaults .iter() @@ -32,15 +33,18 @@ pub fn compute_health(deps: Deps, account_id: &str) -> HealthResult StdResult<()> { + deposit_denoms + .into_iter() + .chain(debt_denoms) + .chain(lend_denoms) + .chain(vault_base_token_denoms) + .try_for_each(|denom| -> StdResult<()> { let price = oracle.query_price(&deps.querier, denom)?.price; denoms_data.prices.insert(denom.clone(), price); let market = red_bank.query_market(&deps.querier, denom)?; denoms_data.markets.insert(denom.clone(), market); Ok(()) - }, - )?; + })?; // Collect all vault data let mut vaults_data: VaultsData = Default::default(); diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index 44290c5c4..7685842ed 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Decimal, Uint128}; +use cosmwasm_std::{Coin, Decimal, Uint128}; use mars_red_bank_types::red_bank::Market; -use mars_rover::msg::query::Positions; +use mars_rover::{msg::query::Positions, traits::Coins}; use mars_rover_health_types::{ Health, HealthError::{MissingMarket, MissingPrice, MissingVaultConfig, MissingVaultValues}, @@ -67,32 +67,36 @@ impl HealthComputer { } fn calculate_collateral_value(&self) -> HealthResult { - let deposits = self.calculate_deposits_value()?; + let deposits = self.calculate_coins_value(&self.positions.deposits)?; + let lends = self.calculate_coins_value(&self.positions.lends.to_coins())?; let vaults = self.calculate_vaults_value()?; Ok(CollateralValue { total_collateral_value: deposits .total_collateral_value - .checked_add(vaults.total_collateral_value)?, + .checked_add(vaults.total_collateral_value)? + .checked_add(lends.total_collateral_value)?, max_ltv_adjusted_collateral: deposits .max_ltv_adjusted_collateral - .checked_add(vaults.max_ltv_adjusted_collateral)?, + .checked_add(vaults.max_ltv_adjusted_collateral)? + .checked_add(lends.max_ltv_adjusted_collateral)?, liquidation_threshold_adjusted_collateral: deposits .liquidation_threshold_adjusted_collateral - .checked_add(vaults.liquidation_threshold_adjusted_collateral)?, + .checked_add(vaults.liquidation_threshold_adjusted_collateral)? + .checked_add(lends.liquidation_threshold_adjusted_collateral)?, }) } - fn calculate_deposits_value(&self) -> HealthResult { + fn calculate_coins_value(&self, coins: &[Coin]) -> HealthResult { let mut total_collateral_value = Uint128::zero(); let mut max_ltv_adjusted_collateral = Uint128::zero(); let mut liquidation_threshold_adjusted_collateral = Uint128::zero(); - for c in &self.positions.deposits { + for c in coins { let coin_price = self.denoms_data.prices.get(&c.denom).ok_or(MissingPrice(c.denom.clone()))?; - let deposit_value = c.amount.checked_mul_floor(*coin_price)?; - total_collateral_value = total_collateral_value.checked_add(deposit_value)?; + let coin_value = c.amount.checked_mul_floor(*coin_price)?; + total_collateral_value = total_collateral_value.checked_add(coin_value)?; let &Market { max_loan_to_value, @@ -106,11 +110,11 @@ impl HealthComputer { } else { Decimal::zero() }; - let max_ltv_adjusted = deposit_value.checked_mul_floor(checked_max_ltv)?; + let max_ltv_adjusted = coin_value.checked_mul_floor(checked_max_ltv)?; max_ltv_adjusted_collateral = max_ltv_adjusted_collateral.checked_add(max_ltv_adjusted)?; - let liq_adjusted = deposit_value.checked_mul_floor(liquidation_threshold)?; + let liq_adjusted = coin_value.checked_mul_floor(liquidation_threshold)?; liquidation_threshold_adjusted_collateral = liquidation_threshold_adjusted_collateral.checked_add(liq_adjusted)?; } diff --git a/packages/health-computer/tests/test_health_scenarios.rs b/packages/health-computer/tests/test_health_scenarios.rs index d0350aea1..6449fa56b 100644 --- a/packages/health-computer/tests/test_health_scenarios.rs +++ b/packages/health-computer/tests/test_health_scenarios.rs @@ -6,7 +6,7 @@ use mars_rover::{ CoinValue, LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultConfig, VaultPosition, VaultPositionAmount, VaultPositionValue, VaultUnlockingPosition, }, - msg::query::{DebtAmount, Positions}, + msg::query::{DebtAmount, LentAmount, Positions}, }; use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; @@ -1079,3 +1079,141 @@ fn vault_base_token_is_not_whitelisted() { assert!(health.is_above_max_ltv()); assert!(!health.is_liquidatable()); } + +#[test] +fn lent_coins_used_as_collateral() { + let umars = umars_info(); + let udai = udai_info(); + let uluna = uluna_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.market.denom.clone(), umars.price), + (udai.market.denom.clone(), udai.price), + (uluna.market.denom.clone(), uluna.price), + ]), + markets: HashMap::from([ + (umars.market.denom.clone(), umars.market.clone()), + (udai.market.denom.clone(), udai.market.clone()), + (uluna.market.denom.clone(), uluna.market.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.market.denom), coin(23, &udai.market.denom)], + debts: vec![DebtAmount { + denom: udai.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }], + lends: vec![ + LentAmount { + denom: udai.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(10), + }, + LentAmount { + denom: uluna.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(2), + }, + ], + vaults: vec![], + }, + denoms_data, + vaults_data, + allowed_coins: vec![umars.market.denom, udai.market.denom, uluna.market.denom], + }; + + let health = h.compute_health().unwrap(); + assert_eq!(health.total_collateral_value, Uint128::new(1230)); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(981)); + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(1031)); + assert_eq!(health.total_debt_value, Uint128::new(971)); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_str("1.010298661174047373").unwrap()) + ); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_str("1.061791967044284243").unwrap()) + ); + assert!(!health.is_above_max_ltv()); + assert!(!health.is_liquidatable()); +} + +#[test] +fn allowed_lent_coins_influence_max_ltv() { + let umars = umars_info(); + let udai = udai_info(); + let uluna = uluna_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.market.denom.clone(), umars.price), + (udai.market.denom.clone(), udai.price), + (uluna.market.denom.clone(), uluna.price), + ]), + markets: HashMap::from([ + (umars.market.denom.clone(), umars.market.clone()), + (udai.market.denom.clone(), udai.market.clone()), + (uluna.market.denom.clone(), uluna.market.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.market.denom), coin(23, &udai.market.denom)], + debts: vec![DebtAmount { + denom: udai.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }], + lends: vec![ + LentAmount { + denom: udai.market.denom.clone(), + shares: Default::default(), + amount: Uint128::new(10), + }, + LentAmount { + denom: uluna.market.denom, + shares: Default::default(), + amount: Uint128::new(2), + }, + ], + vaults: vec![], + }, + denoms_data, + vaults_data, + allowed_coins: vec![umars.market.denom, udai.market.denom], + }; + + let health = h.compute_health().unwrap(); + assert_eq!(health.total_collateral_value, Uint128::new(1230)); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(967)); + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(1031)); + assert_eq!(health.total_debt_value, Uint128::new(971)); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_str("0.995880535530381050").unwrap()) + ); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_str("1.061791967044284243").unwrap()) + ); + assert!(health.is_above_max_ltv()); + assert!(!health.is_liquidatable()); +} diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 6901d7f70..21383f0ae 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -89,6 +89,34 @@ pub enum EmergencyUpdate { DisallowCoin(String), } +#[cw_serde] +pub enum LiquidateRequest { + /// Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt + /// denom/amount of what the liquidator wants to payoff and 2) the request coin denom which the + /// liquidatee should have a balance of. The amount returned to liquidator will be the request coin + /// of the amount that precisely matches the value of the debt + a liquidation bonus. + /// The debt amount will be adjusted down if: + /// - Exceeds liquidatee's total debt for denom + /// - Not enough liquidatee request coin balance to match + /// - The value of the debt repaid exceeds the maximum close factor % + /// + /// Liquidation should prioritize first the not lent coin and if more needs to be serviced to the liquidator + /// it should reclaim (withdrawn from Red Bank). + Deposit(String), + /// Pay back debt of a liquidatable rover account for a via liquidating a Lent position. + /// Lent shares are transfered from the liquidatable to the liquidator. + Lend(String), + /// Pay back debt of a liquidatable rover account for a via liquidating a vault position. + /// Similar to `Deposit` msg and will make similar adjustments to the request. + /// The vault position will be withdrawn (and force withdrawn if a locked vault position) and + /// the underlying assets will transferred to the liquidator. + /// The `VaultPositionType` will determine which bucket to liquidate from. + Vault { + request_vault: T, + position_type: VaultPositionType, + }, +} + /// The list of actions that users can perform on their positions #[cw_serde] pub enum Action { @@ -126,33 +154,14 @@ pub enum Action { id: u64, vault: VaultUnchecked, }, - /// Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt - /// denom/amount of what the liquidator wants to payoff and 2) the request coin denom which the - /// liquidatee should have a balance of. The amount returned to liquidator will be the request coin - /// of the amount that precisely matches the value of the debt + a liquidation bonus. - /// The debt amount will be adjusted down if: - /// - Exceeds liquidatee's total debt for denom - /// - Not enough liquidatee request coin balance to match - /// - The value of the debt repaid exceeds the maximum close factor % - LiquidateCoin { + /// Pay back debt of a liquidatable rover account for a via liquidating a specific type of the position. + Liquidate { /// The credit account id of the one with a liquidation threshold health factor 1 or below liquidatee_account_id: String, - /// The coin debt that the liquidator wishes to pay back on behalf of the liquidatee. - /// The liquidator must already have these assets in their credit account. - debt_coin: Coin, /// The coin they wish to acquire from the liquidatee (amount returned will include the bonus) - request_coin_denom: String, - }, - /// Pay back debt of a liquidatable rover account for a via liquidating a vault position. - /// Similar to LiquidateCoin {} msg and will make similar adjustments to the request. - /// The vault position will be withdrawn (and force withdrawn if a locked vault position) and - /// the underlying assets will transferred to the liquidator. - /// The `VaultPositionType` will determine which bucket to liquidate from. - LiquidateVault { - liquidatee_account_id: String, debt_coin: Coin, - request_vault: VaultUnchecked, - position_type: VaultPositionType, + /// Position details to be liquidated + request: LiquidateRequest, }, /// Perform a swapper with an exact-in amount. Requires slippage allowance %. /// If `coin_in.amount: AccountBalance`, the accounts entire balance of `coin_in.denom` will be used. @@ -253,18 +262,11 @@ pub enum CallbackMsg { position_id: u64, }, /// Pay back debts of a liquidatable rover account for a bonus - LiquidateCoin { + Liquidate { liquidator_account_id: String, liquidatee_account_id: String, debt_coin: Coin, - request_coin_denom: String, - }, - LiquidateVault { - liquidator_account_id: String, - liquidatee_account_id: String, - debt_coin: Coin, - request_vault: Vault, - position_type: VaultPositionType, + request: LiquidateRequest, }, /// Perform a swapper with an exact-in amount. Requires slippage allowance %. /// If `coin_in.amount: AccountBalance`, the accounts entire balance of `coin_in.denom` will be used. diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index a9e91edd1..46aff579b 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -180,6 +180,17 @@ impl Coins for Vec { } } +impl Coins for Vec { + fn to_coins(&self) -> Vec { + self.iter() + .map(|l| Coin { + denom: l.denom.to_string(), + amount: l.amount, + }) + .collect() + } +} + #[cw_serde] pub struct Positions { pub account_id: String, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index df65de0f1..f2ceb0d76 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -508,22 +508,22 @@ "additionalProperties": false }, { - "description": "Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt denom/amount of what the liquidator wants to payoff and 2) the request coin denom which the liquidatee should have a balance of. The amount returned to liquidator will be the request coin of the amount that precisely matches the value of the debt + a liquidation bonus. The debt amount will be adjusted down if: - Exceeds liquidatee's total debt for denom - Not enough liquidatee request coin balance to match - The value of the debt repaid exceeds the maximum close factor %", + "description": "Pay back debt of a liquidatable rover account for a via liquidating a specific type of the position.", "type": "object", "required": [ - "liquidate_coin" + "liquidate" ], "properties": { - "liquidate_coin": { + "liquidate": { "type": "object", "required": [ "debt_coin", "liquidatee_account_id", - "request_coin_denom" + "request" ], "properties": { "debt_coin": { - "description": "The coin debt that the liquidator wishes to pay back on behalf of the liquidatee. The liquidator must already have these assets in their credit account.", + "description": "The coin they wish to acquire from the liquidatee (amount returned will include the bonus)", "allOf": [ { "$ref": "#/definitions/Coin" @@ -534,43 +534,13 @@ "description": "The credit account id of the one with a liquidation threshold health factor 1 or below", "type": "string" }, - "request_coin_denom": { - "description": "The coin they wish to acquire from the liquidatee (amount returned will include the bonus)", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Pay back debt of a liquidatable rover account for a via liquidating a vault position. Similar to LiquidateCoin {} msg and will make similar adjustments to the request. The vault position will be withdrawn (and force withdrawn if a locked vault position) and the underlying assets will transferred to the liquidator. The `VaultPositionType` will determine which bucket to liquidate from.", - "type": "object", - "required": [ - "liquidate_vault" - ], - "properties": { - "liquidate_vault": { - "type": "object", - "required": [ - "debt_coin", - "liquidatee_account_id", - "position_type", - "request_vault" - ], - "properties": { - "debt_coin": { - "$ref": "#/definitions/Coin" - }, - "liquidatee_account_id": { - "type": "string" - }, - "position_type": { - "$ref": "#/definitions/VaultPositionType" - }, - "request_vault": { - "$ref": "#/definitions/VaultBase_for_String" + "request": { + "description": "Position details to be liquidated", + "allOf": [ + { + "$ref": "#/definitions/LiquidateRequest_for_VaultBase_for_String" + } + ] } }, "additionalProperties": false @@ -1052,16 +1022,16 @@ "description": "Pay back debts of a liquidatable rover account for a bonus", "type": "object", "required": [ - "liquidate_coin" + "liquidate" ], "properties": { - "liquidate_coin": { + "liquidate": { "type": "object", "required": [ "debt_coin", "liquidatee_account_id", "liquidator_account_id", - "request_coin_denom" + "request" ], "properties": { "debt_coin": { @@ -1073,45 +1043,8 @@ "liquidator_account_id": { "type": "string" }, - "request_coin_denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "liquidate_vault" - ], - "properties": { - "liquidate_vault": { - "type": "object", - "required": [ - "debt_coin", - "liquidatee_account_id", - "liquidator_account_id", - "position_type", - "request_vault" - ], - "properties": { - "debt_coin": { - "$ref": "#/definitions/Coin" - }, - "liquidatee_account_id": { - "type": "string" - }, - "liquidator_account_id": { - "type": "string" - }, - "position_type": { - "$ref": "#/definitions/VaultPositionType" - }, - "request_vault": { - "$ref": "#/definitions/VaultBase_for_Addr" + "request": { + "$ref": "#/definitions/LiquidateRequest_for_VaultBase_for_Addr" } }, "additionalProperties": false @@ -1425,6 +1358,118 @@ "HealthContractBase_for_String": { "type": "string" }, + "LiquidateRequest_for_VaultBase_for_Addr": { + "oneOf": [ + { + "description": "Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt denom/amount of what the liquidator wants to payoff and 2) the request coin denom which the liquidatee should have a balance of. The amount returned to liquidator will be the request coin of the amount that precisely matches the value of the debt + a liquidation bonus. The debt amount will be adjusted down if: - Exceeds liquidatee's total debt for denom - Not enough liquidatee request coin balance to match - The value of the debt repaid exceeds the maximum close factor %\n\nLiquidation should prioritize first the not lent coin and if more needs to be serviced to the liquidator it should reclaim (withdrawn from Red Bank).", + "type": "object", + "required": [ + "deposit" + ], + "properties": { + "deposit": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Pay back debt of a liquidatable rover account for a via liquidating a Lent position. Lent shares are transfered from the liquidatable to the liquidator.", + "type": "object", + "required": [ + "lend" + ], + "properties": { + "lend": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Pay back debt of a liquidatable rover account for a via liquidating a vault position. Similar to `Deposit` msg and will make similar adjustments to the request. The vault position will be withdrawn (and force withdrawn if a locked vault position) and the underlying assets will transferred to the liquidator. The `VaultPositionType` will determine which bucket to liquidate from.", + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "type": "object", + "required": [ + "position_type", + "request_vault" + ], + "properties": { + "position_type": { + "$ref": "#/definitions/VaultPositionType" + }, + "request_vault": { + "$ref": "#/definitions/VaultBase_for_Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "LiquidateRequest_for_VaultBase_for_String": { + "oneOf": [ + { + "description": "Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt denom/amount of what the liquidator wants to payoff and 2) the request coin denom which the liquidatee should have a balance of. The amount returned to liquidator will be the request coin of the amount that precisely matches the value of the debt + a liquidation bonus. The debt amount will be adjusted down if: - Exceeds liquidatee's total debt for denom - Not enough liquidatee request coin balance to match - The value of the debt repaid exceeds the maximum close factor %\n\nLiquidation should prioritize first the not lent coin and if more needs to be serviced to the liquidator it should reclaim (withdrawn from Red Bank).", + "type": "object", + "required": [ + "deposit" + ], + "properties": { + "deposit": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Pay back debt of a liquidatable rover account for a via liquidating a Lent position. Lent shares are transfered from the liquidatable to the liquidator.", + "type": "object", + "required": [ + "lend" + ], + "properties": { + "lend": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Pay back debt of a liquidatable rover account for a via liquidating a vault position. Similar to `Deposit` msg and will make similar adjustments to the request. The vault position will be withdrawn (and force withdrawn if a locked vault position) and the underlying assets will transferred to the liquidator. The `VaultPositionType` will determine which bucket to liquidate from.", + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "type": "object", + "required": [ + "position_type", + "request_vault" + ], + "properties": { + "position_type": { + "$ref": "#/definitions/VaultPositionType" + }, + "request_vault": { + "$ref": "#/definitions/VaultBase_for_String" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "NftConfigUpdates": { "type": "object", "properties": { diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 9fd6144cd..a55efe476 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -23,11 +23,13 @@ import { ExecuteMsg, Action, ActionAmount, + LiquidateRequestForVaultBaseForString, VaultPositionType, EmergencyUpdate, OwnerUpdate, CallbackMsg, Addr, + LiquidateRequestForVaultBaseForAddr, ActionCoin, ConfigUpdates, NftConfigUpdates, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 1dd761131..46492154d 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -24,11 +24,13 @@ import { ExecuteMsg, Action, ActionAmount, + LiquidateRequestForVaultBaseForString, VaultPositionType, EmergencyUpdate, OwnerUpdate, CallbackMsg, Addr, + LiquidateRequestForVaultBaseForAddr, ActionCoin, ConfigUpdates, NftConfigUpdates, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 672ab99d2..7f5397022 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -24,11 +24,13 @@ import { ExecuteMsg, Action, ActionAmount, + LiquidateRequestForVaultBaseForString, VaultPositionType, EmergencyUpdate, OwnerUpdate, CallbackMsg, Addr, + LiquidateRequestForVaultBaseForAddr, ActionCoin, ConfigUpdates, NftConfigUpdates, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 8324498e8..42a5efc31 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -115,18 +115,10 @@ export type Action = } } | { - liquidate_coin: { + liquidate: { debt_coin: Coin liquidatee_account_id: string - request_coin_denom: string - } - } - | { - liquidate_vault: { - debt_coin: Coin - liquidatee_account_id: string - position_type: VaultPositionType - request_vault: VaultBaseForString + request: LiquidateRequestForVaultBaseForString } } | { @@ -156,6 +148,19 @@ export type ActionAmount = | { exact: Uint128 } +export type LiquidateRequestForVaultBaseForString = + | { + deposit: string + } + | { + lend: string + } + | { + vault: { + position_type: VaultPositionType + request_vault: VaultBaseForString + } + } export type VaultPositionType = 'u_n_l_o_c_k_e_d' | 'l_o_c_k_e_d' | 'u_n_l_o_c_k_i_n_g' export type EmergencyUpdate = | { @@ -256,20 +261,11 @@ export type CallbackMsg = } } | { - liquidate_coin: { + liquidate: { debt_coin: Coin liquidatee_account_id: string liquidator_account_id: string - request_coin_denom: string - } - } - | { - liquidate_vault: { - debt_coin: Coin - liquidatee_account_id: string - liquidator_account_id: string - position_type: VaultPositionType - request_vault: VaultBaseForAddr + request: LiquidateRequestForVaultBaseForAddr } } | { @@ -306,6 +302,19 @@ export type CallbackMsg = } } export type Addr = string +export type LiquidateRequestForVaultBaseForAddr = + | { + deposit: string + } + | { + lend: string + } + | { + vault: { + position_type: VaultPositionType + request_vault: VaultBaseForAddr + } + } export interface ActionCoin { amount: ActionAmount denom: string From 33d49d05e1e7373a04165de5b09e13edf492cad2 Mon Sep 17 00:00:00 2001 From: brimigs <85972460+brimigs@users.noreply.github.com> Date: Mon, 20 Mar 2023 10:33:22 -0400 Subject: [PATCH 152/218] Allow anyone to repay debt (#127) --- README.md | 7 +- contracts/credit-manager/src/execute.rs | 28 +- contracts/credit-manager/src/repay.rs | 44 +- contracts/credit-manager/tests/test_health.rs | 11 +- contracts/credit-manager/tests/test_repay.rs | 52 ++- .../tests/test_repay_for_recipient.rs | 398 ++++++++++++++++++ packages/rover/src/msg/execute.rs | 13 +- .../mars-credit-manager.json | 49 ++- scripts/deploy/base/rover.ts | 2 +- .../MarsCreditManager.types.ts | 12 +- 10 files changed, 588 insertions(+), 28 deletions(-) create mode 100644 contracts/credit-manager/tests/test_repay_for_recipient.rs diff --git a/README.md b/README.md index eaae50c14..a04b9a304 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,12 @@ pub enum Action { Deposit(Coin), Withdraw(Coin), Borrow(Coin), - Repay(Coin), + /// The sender will repay on behalf of the recipient account. If 'recipient_account_id: None', + /// the sender repays to its own account. + Repay { + recipient_account_id: Option, + coin: ActionCoin, + }, EnterVault { vault: VaultUnchecked, denom: String, diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 65c005763..229730d8e 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -17,7 +17,7 @@ use crate::{ liquidate_lend::liquidate_lend, reclaim::reclaim, refund::refund_coin_balances, - repay::repay, + repay::{repay, repay_for_recipient}, state::ACCOUNT_NFT, swap::swap_exact_in, update_coin_balances::update_coin_balance, @@ -73,10 +73,23 @@ pub fn dispatch_actions( account_id: account_id.to_string(), coin: coin.clone(), }), - Action::Repay(coin) => callbacks.push(CallbackMsg::Repay { - account_id: account_id.to_string(), - coin: coin.clone(), - }), + Action::Repay { + recipient_account_id, + coin, + } => { + if let Some(recipient) = recipient_account_id { + callbacks.push(CallbackMsg::RepayForRecipient { + benefactor_account_id: account_id.to_string(), + recipient_account_id: recipient.clone(), + coin: coin.clone(), + }) + } else { + callbacks.push(CallbackMsg::Repay { + account_id: account_id.to_string(), + coin: coin.clone(), + }) + } + } Action::Lend(coin) => callbacks.push(CallbackMsg::Lend { account_id: account_id.to_string(), coin: coin.clone(), @@ -232,6 +245,11 @@ pub fn execute_callback( account_id, coin, } => repay(deps, env, &account_id, &coin), + CallbackMsg::RepayForRecipient { + benefactor_account_id, + recipient_account_id, + coin, + } => repay_for_recipient(deps, env, &benefactor_account_id, &recipient_account_id, coin), CallbackMsg::Lend { account_id, coin, diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index ca4d0e86f..ce64a3228 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -1,14 +1,17 @@ use std::cmp::min; -use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; +use cosmwasm_std::{to_binary, Coin, CosmosMsg, Deps, DepsMut, Env, Response, Uint128, WasmMsg}; use mars_rover::{ error::{ContractError, ContractResult}, - msg::execute::ActionCoin, + msg::{ + execute::{ActionCoin, CallbackMsg::Repay}, + ExecuteMsg, + }, }; use crate::{ state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}, - utils::{debt_shares_to_amount, decrement_coin_balance}, + utils::{debt_shares_to_amount, decrement_coin_balance, increment_coin_balance}, }; pub fn repay( @@ -81,3 +84,38 @@ pub fn current_debt_for_denom( let coin = debt_shares_to_amount(deps, &env.contract.address, denom, debt_shares)?; Ok((coin.amount, debt_shares)) } + +pub fn repay_for_recipient( + deps: DepsMut, + env: Env, + benefactor_account_id: &str, + recipient_account_id: &str, + coin: ActionCoin, +) -> ContractResult { + let (debt_amount, _) = + current_debt_for_denom(deps.as_ref(), &env, recipient_account_id, &coin.denom)?; + let amount_to_repay = min(debt_amount, coin.amount.value().unwrap_or(Uint128::MAX)); + let coin_to_repay = &Coin { + denom: coin.denom, + amount: amount_to_repay, + }; + + decrement_coin_balance(deps.storage, benefactor_account_id, coin_to_repay)?; + increment_coin_balance(deps.storage, recipient_account_id, coin_to_repay)?; + + let repay_callback_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::Callback(Repay { + account_id: recipient_account_id.to_string(), + coin: ActionCoin::from(coin_to_repay), + }))?, + }); + + Ok(Response::new() + .add_message(repay_callback_msg) + .add_attribute("action", "repay_for_recipient") + .add_attribute("benefactor_account_id", benefactor_account_id) + .add_attribute("recipient_account_id", recipient_account_id) + .add_attribute("coin_repaid", coin_to_repay.to_string())) +} diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index bc191cf0a..610356c66 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -889,10 +889,13 @@ fn can_take_actions_if_ltv_does_not_weaken() { &user, vec![ Deposit(uatom_info.to_coin(50)), - Repay(ActionCoin { - denom: uatom_info.denom.clone(), - amount: ActionAmount::AccountBalance, - }), + Repay { + recipient_account_id: None, + coin: ActionCoin { + denom: uatom_info.denom.clone(), + amount: ActionAmount::AccountBalance, + }, + }, ], &[uatom_info.to_coin(50)], ) diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index ad22dec46..2cb28fae1 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -24,7 +24,10 @@ fn only_token_owner_can_repay() { let res = mock.update_credit_account( &account_id, &another_user, - vec![Repay(coin_info.to_action_coin(12312))], + vec![Repay { + recipient_account_id: None, + coin: coin_info.to_action_coin(12312), + }], &[], ); @@ -48,7 +51,10 @@ fn repaying_with_zero_debt_raises() { let res = mock.update_credit_account( &account_id, &user, - vec![Repay(coin_info.to_action_coin(0))], + vec![Repay { + recipient_account_id: None, + coin: coin_info.to_action_coin(0), + }], &[], ); @@ -58,7 +64,10 @@ fn repaying_with_zero_debt_raises() { let res = mock.update_credit_account( &account_id, &user, - vec![Repay(coin_info.to_action_coin_full_balance())], + vec![Repay { + recipient_account_id: None, + coin: coin_info.to_action_coin_full_balance(), + }], &[], ); @@ -111,7 +120,10 @@ fn raises_when_repaying_what_is_not_owed() { vec![ Deposit(uatom_info.to_coin(300)), Borrow(uosmo_info.to_coin(42)), - Repay(uatom_info.to_action_coin(42)), + Repay { + recipient_account_id: None, + coin: uatom_info.to_action_coin(42), + }, ], &[uatom_info.to_coin(300)], ); @@ -151,7 +163,10 @@ fn raises_when_not_enough_assets_to_repay() { Deposit(uatom_info.to_coin(300)), Borrow(uosmo_info.to_coin(50)), Withdraw(uosmo_info.to_coin(10)), - Repay(uosmo_info.to_action_coin(50)), + Repay { + recipient_account_id: None, + coin: uosmo_info.to_action_coin(50), + }, ], &[uatom_info.to_coin(300)], ); @@ -197,8 +212,16 @@ fn repay_less_than_total_debt() { let interim_red_bank_debt = mock.query_red_bank_debt(&coin_info.denom); - mock.update_credit_account(&account_id, &user, vec![Repay(coin_info.to_action_coin(20))], &[]) - .unwrap(); + mock.update_credit_account( + &account_id, + &user, + vec![Repay { + recipient_account_id: None, + coin: coin_info.to_action_coin(20), + }], + &[], + ) + .unwrap(); let position = mock.query_positions(&account_id); assert_eq!(position.deposits.len(), 1); @@ -230,7 +253,10 @@ fn repay_less_than_total_debt() { mock.update_credit_account( &account_id, &user, - vec![Repay(coin_info.to_action_coin(31))], // Interest accrued paid back as well + vec![Repay { + recipient_account_id: None, + coin: coin_info.to_action_coin(31), + }], // Interest accrued paid back as well &[], ) .unwrap(); @@ -276,7 +302,10 @@ fn pays_max_debt_when_attempting_to_repay_more_than_owed() { vec![ Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50)), - Repay(coin_info.to_action_coin(75)), + Repay { + recipient_account_id: None, + coin: coin_info.to_action_coin(75), + }, ], &[coin(300, coin_info.denom.clone())], ) @@ -324,7 +353,10 @@ fn amount_none_repays_total_debt() { vec![ Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50)), - Repay(coin_info.to_action_coin_full_balance()), + Repay { + recipient_account_id: None, + coin: coin_info.to_action_coin_full_balance(), + }, ], &[coin(300, coin_info.denom.clone())], ) diff --git a/contracts/credit-manager/tests/test_repay_for_recipient.rs b/contracts/credit-manager/tests/test_repay_for_recipient.rs new file mode 100644 index 000000000..21e6f696e --- /dev/null +++ b/contracts/credit-manager/tests/test_repay_for_recipient.rs @@ -0,0 +1,398 @@ +use std::ops::{Add, Sub}; + +use cosmwasm_std::{coin, coins, Addr, OverflowError, OverflowOperation, Uint128}; +use mars_rover::{ + error::ContractError, + msg::execute::{ + Action::{Borrow, Deposit, Repay}, + ActionAmount, ActionCoin, CallbackMsg, + }, +}; + +use crate::helpers::{ + assert_err, uosmo_info, AccountToFund, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, +}; + +pub mod helpers; + +#[test] +fn only_rover_can_call_repay_for_recipient_callback() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new().build().unwrap(); + + let res = mock.invoke_callback( + &user, + CallbackMsg::RepayForRecipient { + benefactor_account_id: "abc".to_string(), + recipient_account_id: "xyz".to_string(), + coin: ActionCoin { + denom: "udoge".to_string(), + amount: ActionAmount::AccountBalance, + }, + }, + ); + assert_err(res, ContractError::ExternalInvocation) +} + +#[test] +fn raises_when_benefactor_has_no_funds() { + let coin_info = uosmo_info(); + + let recipient = Addr::unchecked("recipient"); + let benefactor = Addr::unchecked("benefactor"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: recipient.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let recipient_account_id = mock.create_credit_account(&recipient).unwrap(); + let benefactor_account_id = mock.create_credit_account(&benefactor).unwrap(); + + mock.update_credit_account( + &recipient_account_id, + &recipient, + vec![Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + let res = mock.update_credit_account( + &benefactor_account_id, + &benefactor, + vec![ + Repay { + recipient_account_id: Some(recipient_account_id.clone()), + coin: coin_info.to_action_coin(51), + }, // +1 for interest + ], + &[], + ); + + assert_err( + res, + ContractError::Overflow(OverflowError { + operation: OverflowOperation::Sub, + operand1: "0".to_string(), + operand2: "51".to_string(), + }), + ) +} + +#[test] +fn raises_when_non_owner_of_benefactor_account_repays() { + let coin_info = uosmo_info(); + + let recipient = Addr::unchecked("recipient"); + let benefactor = Addr::unchecked("benefactor"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: benefactor.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: recipient.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let recipient_account_id = mock.create_credit_account(&recipient).unwrap(); + let benefactor_account_id = mock.create_credit_account(&benefactor).unwrap(); + + mock.update_credit_account( + &recipient_account_id, + &recipient, + vec![Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + mock.update_credit_account( + &benefactor_account_id, + &benefactor, + vec![Deposit(coin_info.to_coin(300))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + let res = mock.update_credit_account( + &recipient_account_id, + &benefactor, + vec![ + Repay { + recipient_account_id: Some(recipient_account_id.clone()), + coin: coin_info.to_action_coin(51), + }, // +1 for interest + ], + &[], + ); + + assert_err( + res, + ContractError::NotTokenOwner { + user: benefactor.to_string(), + account_id: recipient_account_id, + }, + ) +} + +#[test] +fn raises_when_benefactor_repays_account_with_no_debt() { + let coin_info = uosmo_info(); + + let recipient = Addr::unchecked("recipient"); + let benefactor = Addr::unchecked("benefactor"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: benefactor.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: recipient.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let recipient_account_id = mock.create_credit_account(&recipient).unwrap(); + let benefactor_account_id = mock.create_credit_account(&benefactor).unwrap(); + + mock.update_credit_account( + &recipient_account_id, + &recipient, + vec![Deposit(coin_info.to_coin(300))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + mock.update_credit_account( + &benefactor_account_id, + &benefactor, + vec![Deposit(coin_info.to_coin(300))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + let res = mock.update_credit_account( + &benefactor_account_id, + &benefactor, + vec![ + Repay { + recipient_account_id: Some(recipient_account_id.clone()), + coin: coin_info.to_action_coin(51), + }, // +1 for interest + ], + &[], + ); + + assert_err(res, ContractError::NoDebt) +} + +#[test] +fn benefactor_successfully_repays_on_behalf_of_recipient() { + let coin_info = uosmo_info(); + + let recipient = Addr::unchecked("recipient"); + let benefactor = Addr::unchecked("benefactor"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: benefactor.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: recipient.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let recipient_account_id = mock.create_credit_account(&recipient).unwrap(); + let benefactor_account_id = mock.create_credit_account(&benefactor).unwrap(); + + mock.update_credit_account( + &recipient_account_id, + &recipient, + vec![Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + mock.update_credit_account( + &benefactor_account_id, + &benefactor, + vec![Deposit(coin_info.to_coin(300))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + mock.update_credit_account( + &benefactor_account_id, + &benefactor, + vec![ + Repay { + recipient_account_id: Some(recipient_account_id.clone()), + coin: coin_info.to_action_coin(51), + }, // +1 for interest + ], + &[], + ) + .unwrap(); + + let recipient_position = mock.query_positions(&recipient_account_id.clone()); + assert_eq!(recipient_position.deposits.len(), 1); + assert_eq!(recipient_position.deposits.first().unwrap().amount, Uint128::new(350)); + assert_eq!(recipient_position.debts.len(), 0); + + let benefactor_position = mock.query_positions(&benefactor_account_id.clone()); + assert_eq!(benefactor_position.deposits.len(), 1); + assert_eq!(benefactor_position.deposits.first().unwrap().amount, Uint128::new(249)); + assert_eq!(benefactor_position.debts.len(), 0); + + let config = mock.query_config(); + let coin = mock.query_balance(&Addr::unchecked(config.red_bank), &coin_info.denom); + assert_eq!(coin.amount, DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1))); +} + +#[test] +fn benefactor_pays_some_of_recipient_debt() { + let coin_info = uosmo_info(); + + let recipient = Addr::unchecked("recipient"); + let benefactor = Addr::unchecked("benefactor"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: benefactor.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: recipient.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let recipient_account_id = mock.create_credit_account(&recipient).unwrap(); + let benefactor_account_id = mock.create_credit_account(&benefactor).unwrap(); + + mock.update_credit_account( + &recipient_account_id, + &recipient, + vec![Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(100))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + mock.update_credit_account( + &benefactor_account_id, + &benefactor, + vec![Deposit(coin_info.to_coin(50))], + &[coin(50, coin_info.denom.clone())], + ) + .unwrap(); + + mock.update_credit_account( + &benefactor_account_id, + &benefactor, + vec![Repay { + recipient_account_id: Some(recipient_account_id.clone()), + coin: coin_info.to_action_coin(50), + }], + &[], + ) + .unwrap(); + + let recipient_position = mock.query_positions(&recipient_account_id.clone()); + assert_eq!(recipient_position.deposits.len(), 1); + assert_eq!(recipient_position.deposits.first().unwrap().amount, Uint128::new(400)); + assert_eq!(recipient_position.debts.len(), 1); + assert_eq!(recipient_position.debts.first().unwrap().amount, Uint128::new(51)); + + let benefactor_position = mock.query_positions(&benefactor_account_id.clone()); + assert_eq!(benefactor_position.deposits.len(), 0); + assert_eq!(benefactor_position.debts.len(), 0); + + let config = mock.query_config(); + let coin = mock.query_balance(&Addr::unchecked(config.red_bank), &coin_info.denom); + assert_eq!(coin.amount, DEFAULT_RED_BANK_COIN_BALANCE.sub(Uint128::new(50))); + // total borrow = 100 - 50 +} + +#[test] +fn benefactor_attempts_to_pay_more_than_max_debt() { + let coin_info = uosmo_info(); + + let recipient = Addr::unchecked("recipient"); + let benefactor = Addr::unchecked("benefactor"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: benefactor.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: recipient.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let recipient_account_id = mock.create_credit_account(&recipient).unwrap(); + let benefactor_account_id = mock.create_credit_account(&benefactor).unwrap(); + + mock.update_credit_account( + &recipient_account_id, + &recipient, + vec![Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + mock.update_credit_account( + &benefactor_account_id, + &benefactor, + vec![Deposit(coin_info.to_coin(300))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + mock.update_credit_account( + &benefactor_account_id, + &benefactor, + vec![ + Repay { + recipient_account_id: Some(recipient_account_id.clone()), + coin: coin_info.to_action_coin(110), + }, // +1 for interest + ], + &[], + ) + .unwrap(); + + let recipient_position = mock.query_positions(&recipient_account_id.clone()); + assert_eq!(recipient_position.deposits.len(), 1); + assert_eq!(recipient_position.deposits.first().unwrap().amount, Uint128::new(350)); + assert_eq!(recipient_position.debts.len(), 0); + + let benefactor_position = mock.query_positions(&benefactor_account_id.clone()); + assert_eq!(benefactor_position.deposits.len(), 1); + assert_eq!(benefactor_position.deposits.first().unwrap().amount, Uint128::new(249)); + assert_eq!(benefactor_position.debts.len(), 0); + + let config = mock.query_config(); + let coin = mock.query_balance(&Addr::unchecked(config.red_bank), &coin_info.denom); + assert_eq!(coin.amount, DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1))); +} diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 21383f0ae..c05446f73 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -132,7 +132,12 @@ pub enum Action { Reclaim(ActionCoin), /// Repay coin of specified amount back to Red Bank. If `amount: AccountBalance` is passed, /// the repaid amount will be the minimum between account balance for denom and total owed. - Repay(ActionCoin), + /// The sender will repay on behalf of the recipient account. If 'recipient_account_id: None', + /// the sender repays to its own account. + Repay { + recipient_account_id: Option, + coin: ActionCoin, + }, /// Deposit coins into vault strategy /// If `coin.amount: AccountBalance`, Rover attempts to deposit the account's entire balance into the vault EnterVault { @@ -209,6 +214,12 @@ pub enum CallbackMsg { account_id: String, coin: ActionCoin, }, + /// Benefactor account repays debt on behalf of recipient + RepayForRecipient { + benefactor_account_id: String, + recipient_account_id: String, + coin: ActionCoin, + }, /// Lend coin to the Red Bank Lend { account_id: String, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index f2ceb0d76..9ba36e3d0 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -389,14 +389,29 @@ "additionalProperties": false }, { - "description": "Repay coin of specified amount back to Red Bank. If `amount: AccountBalance` is passed, the repaid amount will be the minimum between account balance for denom and total owed.", + "description": "Repay coin of specified amount back to Red Bank. If `amount: AccountBalance` is passed, the repaid amount will be the minimum between account balance for denom and total owed. The sender will repay on behalf of the recipient account. If 'recipient_account_id: None', the sender repays to its own account.", "type": "object", "required": [ "repay" ], "properties": { "repay": { - "$ref": "#/definitions/ActionCoin" + "type": "object", + "required": [ + "coin" + ], + "properties": { + "coin": { + "$ref": "#/definitions/ActionCoin" + }, + "recipient_account_id": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -776,6 +791,36 @@ }, "additionalProperties": false }, + { + "description": "Benefactor account repays debt on behalf of recipient", + "type": "object", + "required": [ + "repay_for_recipient" + ], + "properties": { + "repay_for_recipient": { + "type": "object", + "required": [ + "benefactor_account_id", + "coin", + "recipient_account_id" + ], + "properties": { + "benefactor_account_id": { + "type": "string" + }, + "coin": { + "$ref": "#/definitions/ActionCoin" + }, + "recipient_account_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Lend coin to the Red Bank", "type": "object", diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 48b5334ad..ee594c7e1 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -98,7 +98,7 @@ export class Rover { async repay() { const amount = this.actions.repayAmount await this.updateCreditAccount([ - { repay: { amount: { exact: amount }, denom: this.actions.secondaryDenom } }, + { repay: { coin: { amount: { exact: amount }, denom: this.actions.secondaryDenom } } }, ]) const positions = await this.query.positions({ accountId: this.accountId! }) printGreen( diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 42a5efc31..47a32de32 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -88,7 +88,10 @@ export type Action = reclaim: ActionCoin } | { - repay: ActionCoin + repay: { + coin: ActionCoin + recipient_account_id?: string | null + } } | { enter_vault: { @@ -207,6 +210,13 @@ export type CallbackMsg = coin: ActionCoin } } + | { + repay_for_recipient: { + benefactor_account_id: string + coin: ActionCoin + recipient_account_id: string + } + } | { lend: { account_id: string From 439080f2073dffa75006861e720ad216e15b08c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Rodr=C3=ADguez?= Date: Thu, 30 Mar 2023 11:01:56 -0300 Subject: [PATCH 153/218] Add account_id field to reclaim event (#130) --- contracts/credit-manager/src/reclaim.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/credit-manager/src/reclaim.rs b/contracts/credit-manager/src/reclaim.rs index 305080620..9c3c9ed38 100644 --- a/contracts/credit-manager/src/reclaim.rs +++ b/contracts/credit-manager/src/reclaim.rs @@ -66,6 +66,7 @@ pub fn reclaim( Ok(Response::new() .add_message(red_bank_reclaim_msg) .add_attribute("action", "reclaim") + .add_attribute("account_id", account_id) .add_attribute("lent_shares_reclaimed", shares_to_reclaim) .add_attribute("coin_reclaimed", format!("{}{}", amount_to_reclaim, &coin.denom))) } From 42678c6c7e4956863bae6cea14811e0e16a66ae8 Mon Sep 17 00:00:00 2001 From: brimigs <85972460+brimigs@users.noreply.github.com> Date: Fri, 31 Mar 2023 10:37:34 -0400 Subject: [PATCH 154/218] add red bank in config (#131) --- contracts/credit-manager/src/update_config.rs | 8 ++++- .../tests/test_update_config.rs | 34 +++++++++++++++++-- packages/rover/src/msg/instantiate.rs | 1 + .../mars-credit-manager.json | 13 +++++++ .../MarsCreditManager.types.ts | 1 + 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index 9b3bfcd7d..1d738ba1d 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -11,7 +11,7 @@ use crate::{ instantiate::{assert_lte_to_one, assert_no_duplicate_coins, assert_no_duplicate_vaults}, state::{ ACCOUNT_NFT, ALLOWED_COINS, HEALTH_CONTRACT, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, - ORACLE, OWNER, SWAPPER, VAULT_CONFIGS, ZAPPER, + ORACLE, OWNER, RED_BANK, SWAPPER, VAULT_CONFIGS, ZAPPER, }, }; @@ -70,6 +70,12 @@ pub fn update_config( response.add_attribute("key", "oracle").add_attribute("value", unchecked.address()); } + if let Some(unchecked) = updates.red_bank { + RED_BANK.save(deps.storage, &unchecked.check(deps.api)?)?; + response = + response.add_attribute("key", "red_bank").add_attribute("value", unchecked.address()); + } + if let Some(unchecked) = updates.swapper { SWAPPER.save(deps.storage, &unchecked.check(deps.api)?)?; response = diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index ac4f39413..8b8b3b784 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -1,11 +1,13 @@ use cosmwasm_std::{coin, Addr, Decimal, Uint128}; use cw_multi_test::{BasicApp, Executor}; use mars_mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; +use mars_mock_red_bank::msg::InstantiateMsg as RedBankInstantiateMsg; use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; use mars_rover::{ adapters::{ health::HealthContractUnchecked, oracle::{OracleBase, OracleUnchecked}, + red_bank::RedBankUnchecked, swap::SwapperBase, vault::{VaultBase, VaultConfig}, zapper::ZapperBase, @@ -18,8 +20,8 @@ use mars_rover::{ }; use crate::helpers::{ - assert_err, locked_vault_info, mock_oracle_contract, mock_vault_contract, uatom_info, - uosmo_info, MockEnv, + assert_err, locked_vault_info, mock_oracle_contract, mock_red_bank_contract, + mock_vault_contract, uatom_info, uosmo_info, MockEnv, }; pub mod helpers; @@ -35,6 +37,7 @@ fn only_owner_can_update_config() { account_nft: None, allowed_coins: None, oracle: None, + red_bank: None, max_close_factor: None, max_unlocking_positions: None, swapper: None, @@ -66,6 +69,7 @@ fn raises_on_invalid_vaults_config() { account_nft: None, allowed_coins: None, oracle: None, + red_bank: None, max_close_factor: None, max_unlocking_positions: None, swapper: None, @@ -93,6 +97,7 @@ fn raises_on_invalid_vaults_config() { account_nft: None, allowed_coins: None, oracle: None, + red_bank: None, max_close_factor: None, max_unlocking_positions: None, swapper: None, @@ -119,6 +124,7 @@ fn raises_on_invalid_vaults_config() { account_nft: None, allowed_coins: None, oracle: None, + red_bank: None, max_close_factor: None, max_unlocking_positions: None, swapper: None, @@ -147,6 +153,7 @@ fn update_config_works_with_full_config() { let new_vault_configs = vec![deploy_vault(&mut mock.app)]; let new_allowed_coins = vec!["uosmo".to_string()]; let new_oracle = deploy_new_oracle(&mut mock.app); + let new_red_bank = deploy_new_red_bank(&mut mock.app); let new_zapper = ZapperBase::new("new_zapper".to_string()); let new_close_factor = Decimal::from_atomics(32u128, 2).unwrap(); let new_unlocking_max = Uint128::new(321); @@ -159,6 +166,7 @@ fn update_config_works_with_full_config() { account_nft: Some(new_nft_contract.to_string()), allowed_coins: Some(new_allowed_coins.clone()), oracle: Some(new_oracle.clone()), + red_bank: Some(new_red_bank.clone()), max_close_factor: Some(new_close_factor), max_unlocking_positions: Some(new_unlocking_max), swapper: Some(new_swapper.clone()), @@ -196,6 +204,9 @@ fn update_config_works_with_full_config() { assert_eq!(&new_config.oracle, new_oracle.address()); assert_ne!(new_config.oracle, original_config.oracle); + assert_eq!(&new_config.red_bank, new_red_bank.address()); + assert_ne!(new_config.red_bank, original_config.red_bank); + assert_eq!(&new_config.zapper, new_zapper.address()); assert_ne!(new_config.zapper, original_config.zapper); @@ -351,6 +362,7 @@ fn raises_on_duplicate_vault_configs() { account_nft: None, allowed_coins: None, oracle: None, + red_bank: None, max_close_factor: None, max_unlocking_positions: None, swapper: None, @@ -401,6 +413,7 @@ fn raises_on_duplicate_coin_configs() { "uosmo".to_string(), ]), oracle: None, + red_bank: None, max_close_factor: None, max_unlocking_positions: None, swapper: None, @@ -444,6 +457,23 @@ fn deploy_new_oracle(app: &mut BasicApp) -> OracleUnchecked { OracleUnchecked::new(addr.to_string()) } +fn deploy_new_red_bank(app: &mut BasicApp) -> RedBankUnchecked { + let contract_code_id = app.store_code(mock_red_bank_contract()); + let addr = app + .instantiate_contract( + contract_code_id, + Addr::unchecked("red_bank_contract_owner"), + &RedBankInstantiateMsg { + coins: vec![], + }, + &[], + "mock-red-bank", + None, + ) + .unwrap(); + RedBankUnchecked::new(addr.to_string()) +} + fn deploy_vault(app: &mut BasicApp) -> VaultInstantiateConfig { let code_id = app.store_code(mock_vault_contract()); let addr = app diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index 7f7a3193f..c09e94981 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -72,6 +72,7 @@ pub struct ConfigUpdates { pub allowed_coins: Option>, pub vault_configs: Option>, pub oracle: Option, + pub red_bank: Option, pub max_close_factor: Option, pub max_unlocking_positions: Option, pub swapper: Option, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 9ba36e3d0..b6c780b02 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -1324,6 +1324,16 @@ } ] }, + "red_bank": { + "anyOf": [ + { + "$ref": "#/definitions/RedBankBase_for_String" + }, + { + "type": "null" + } + ] + }, "swapper": { "anyOf": [ { @@ -1622,6 +1632,9 @@ } ] }, + "RedBankBase_for_String": { + "type": "string" + }, "SwapperBase_for_String": { "type": "string" }, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 47a32de32..aca591891 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -336,6 +336,7 @@ export interface ConfigUpdates { max_close_factor?: Decimal | null max_unlocking_positions?: Uint128 | null oracle?: OracleBaseForString | null + red_bank?: RedBankBaseForString | null swapper?: SwapperBaseForString | null vault_configs?: VaultInstantiateConfig[] | null zapper?: ZapperBaseForString | null From 4f8f76289f516a8cb9fe57183416f397c13cfe57 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 31 Mar 2023 21:39:06 +0200 Subject: [PATCH 155/218] Update testtube deps (#132) --- Cargo.lock | 1059 ++++++++++++++--- Cargo.toml | 4 +- contracts/swapper/osmosis/src/route.rs | 4 +- contracts/swapper/osmosis/tests/helpers.rs | 5 +- .../osmosis/tests/test_enumerate_routes.rs | 2 +- .../swapper/osmosis/tests/test_estimate.rs | 2 +- .../swapper/osmosis/tests/test_set_route.rs | 2 +- contracts/swapper/osmosis/tests/test_swap.rs | 23 +- contracts/zapper/osmosis/Cargo.toml | 1 + contracts/zapper/osmosis/tests/helpers/mod.rs | 3 + .../tests/{helpers.rs => helpers/utils.rs} | 0 ...uidity.rs => test_v2_provide_liquidity.rs} | 84 +- .../{test_queries.rs => test_v2_queries.rs} | 0 ...idity.rs => test_v2_withdraw_liquidity.rs} | 48 +- .../osmosis/tests/test_v3_add_position.rs | 125 ++ 15 files changed, 1151 insertions(+), 211 deletions(-) create mode 100644 contracts/zapper/osmosis/tests/helpers/mod.rs rename contracts/zapper/osmosis/tests/{helpers.rs => helpers/utils.rs} (100%) rename contracts/zapper/osmosis/tests/{test_provide_liquidity.rs => test_v2_provide_liquidity.rs} (86%) rename contracts/zapper/osmosis/tests/{test_queries.rs => test_v2_queries.rs} (100%) rename contracts/zapper/osmosis/tests/{test_withdraw_liquidity.rs => test_v2_withdraw_liquidity.rs} (87%) create mode 100644 contracts/zapper/osmosis/tests/test_v3_add_position.rs diff --git a/Cargo.lock b/Cargo.lock index a08c6ed5b..11e691d24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,9 +24,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "apollo-cw-asset" @@ -55,13 +55,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.64" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.12", ] [[package]] @@ -70,7 +70,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -95,9 +95,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64ct" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bindgen" @@ -157,9 +157,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] @@ -191,6 +191,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + [[package]] name = "cexpr" version = "0.6.0" @@ -208,9 +214,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "num-integer", "num-traits", @@ -218,9 +224,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ed9a53e5d4d9c573ae844bfac6872b159cb1d1585a83b29e7a64b7eef7332a" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", @@ -263,9 +269,25 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.1" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" + +[[package]] +name = "core-foundation" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cosmos-sdk-proto" @@ -273,7 +295,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20b42021d8488665b1a0d9748f1f81df7235362d194f44481e2e61bf376b77b4" dependencies = [ - "prost 0.11.6", + "prost 0.11.8", "prost-types", "tendermint-proto", ] @@ -295,14 +317,15 @@ dependencies = [ "serde_json", "subtle-encoding", "tendermint", + "tendermint-rpc", "thiserror", ] [[package]] name = "cosmwasm-crypto" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8263ce52392898aa17c2a0984b7c542df416e434f6e0cb1c1a11771054d159" +checksum = "f22add0f9b2a5416df98c1d0248a8d8eedb882c38fbf0c5052b64eebe865df6d" dependencies = [ "digest 0.10.6", "ed25519-zebra", @@ -313,18 +336,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1895f6d7a191bb044e3c555190d1da555c2571a3af41f849f60c855580e392f" +checksum = "c2e64f710a18ef90d0a632cf27842e98ffc2d005a38a6f76c12fd0bc03bc1a2d" dependencies = [ - "syn", + "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b99f612ccf162940ae2eef9f370ee37cf2ddcf4a9a8f5ee15ec6b46a5ecd2e" +checksum = "fe5ad2e23a971b9e4cd57b20cee3e2e79c33799bed4b128e473aca3702bfe5dd" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -335,20 +358,20 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92ceea61033cb69c336abf673da017ddf251fc4e26e0cdd387eaf8bedb14e49" +checksum = "2926d159a9bb1a716a592b40280f1663f2491a9de3b6da77c0933cee2a2655b8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "cosmwasm-std" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc01051aab3bb88d5efe0b90f24a6df1ca96a873b12fc21b862b17539c84ee9" +checksum = "76fee88ff5bf7bef55bd37ac0619974701b99bf6bd4b16cf56ee8810718abd71" dependencies = [ "base64", "cosmwasm-crypto", @@ -366,9 +389,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" dependencies = [ "libc", ] @@ -401,6 +424,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "ct-logs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" +dependencies = [ + "sct", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -623,7 +655,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -641,16 +673,16 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.3", + "block-buffer 0.10.4", "crypto-common", "subtle", ] [[package]] name = "dyn-clone" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" [[package]] name = "ecdsa" @@ -769,6 +801,21 @@ dependencies = [ "paste", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + [[package]] name = "forward_ref" version = "1.0.0" @@ -777,12 +824,13 @@ checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" [[package]] name = "futures" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -791,9 +839,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -801,46 +849,73 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] [[package]] name = "futures-io" -version = "0.3.26" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -876,6 +951,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -885,6 +979,31 @@ dependencies = [ "ahash", ] +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -894,6 +1013,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -909,12 +1037,117 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-proxy" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" +dependencies = [ + "bytes", + "futures", + "headers", + "http", + "hyper", + "hyper-rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", + "webpki", +] + +[[package]] +name = "hyper-rustls" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +dependencies = [ + "ct-logs", + "futures-util", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "webpki", + "webpki-roots", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indenter" version = "0.3.3" @@ -923,9 +1156,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -942,9 +1175,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" @@ -991,9 +1224,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "libloading" @@ -1266,7 +1499,7 @@ dependencies = [ "mars-owner", "mars-rover", "mars-swapper-base", - "osmosis-std 0.14.0", + "osmosis-std 0.13.2 (git+https://github.com/osmosis-labs/osmosis-rust?branch=main)", "osmosis-test-tube", "schemars", "thiserror", @@ -1316,6 +1549,7 @@ dependencies = [ "cw-utils 1.0.1", "cw2 1.0.1", "mars-zapper-base", + "osmosis-std 0.13.2 (git+https://github.com/osmosis-labs/osmosis-rust?branch=main)", "osmosis-test-tube", ] @@ -1325,12 +1559,30 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", +] + [[package]] name = "nom" version = "7.1.3" @@ -1349,7 +1601,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1371,6 +1623,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + [[package]] name = "num_threads" version = "0.1.6" @@ -1392,11 +1654,17 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "os_str_bytes" -version = "6.4.1" +version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" [[package]] name = "osmosis-std" @@ -1406,8 +1674,23 @@ checksum = "10d6fe6ac7fcba45ed61d738091d33c838c4cabbcf4892dc7aa56d19d39cc976" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive", - "prost 0.11.6", + "osmosis-std-derive 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.11.8", + "prost-types", + "schemars", + "serde", + "serde-cw-value", +] + +[[package]] +name = "osmosis-std" +version = "0.13.2" +source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#6841a23fc9a0b11649acdb7eb436f564b1a87cbb" +dependencies = [ + "chrono", + "cosmwasm-std", + "osmosis-std-derive 0.13.2 (git+https://github.com/osmosis-labs/osmosis-rust?branch=main)", + "prost 0.11.8", "prost-types", "schemars", "serde", @@ -1422,8 +1705,8 @@ checksum = "2fc0a9075efd64ed5a8be3bf134cbf1080570d68384f2ad58ffaac6c00d063fd" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive", - "prost 0.11.6", + "osmosis-std-derive 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.11.8", "prost-types", "schemars", "serde", @@ -1439,21 +1722,31 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "osmosis-std-derive" +version = "0.13.2" +source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#6841a23fc9a0b11649acdb7eb436f564b1a87cbb" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] name = "osmosis-test-tube" version = "14.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e7d470a607f4016906fee20ff51275b399ffad9903240dc462c0f5226650c7" +source = "git+https://github.com/osmosis-labs/test-tube?branch=main#c45a1c88d7b4f28858d3965590525058da506b13" dependencies = [ "base64", "bindgen", "cosmrs", "cosmwasm-std", - "osmosis-std 0.13.2", - "prost 0.11.6", + "osmosis-std 0.13.2 (git+https://github.com/osmosis-labs/osmosis-rust?branch=main)", + "prost 0.11.8", "serde", "serde_json", "test-tube", @@ -1462,9 +1755,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "pbkdf2" @@ -1481,6 +1774,59 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "peg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1505,9 +1851,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" dependencies = [ "unicode-ident", ] @@ -1524,12 +1870,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dc42e00223fc37204bd4aa177e69420c604ca4a183209a8f9de30c6d934698" +checksum = "e48e50df39172a3e7eb17e14642445da64996989bc212b583015435d39a58537" dependencies = [ "bytes", - "prost-derive 0.11.6", + "prost-derive 0.11.8", ] [[package]] @@ -1542,37 +1888,36 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "prost-derive" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda8c0881ea9f722eb9629376db3d0b903b462477c1aafcb0566610ac28ac5d" +checksum = "4ea9b0f8cbe5e15a8a042d030bd96668db28ecb567ec37d691971ff5731d2b1b" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "prost-types" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e0526209433e96d83d750dd81a99118edbc55739e7e61a46764fd2ad537788" +checksum = "379119666929a1afd7a043aa6cf96fa67a6dce9af60c88095a4686dbce4c9c88" dependencies = [ - "bytes", - "prost 0.11.6", + "prost 0.11.8", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -1594,9 +1939,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "aho-corasick", "memchr", @@ -1605,9 +1950,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "rfc6979" @@ -1620,6 +1965,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "ripemd" version = "0.1.3" @@ -1646,11 +2006,54 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls", + "schannel", + "security-framework", +] + [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] [[package]] name = "schemars" @@ -1673,7 +2076,17 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", ] [[package]] @@ -1690,17 +2103,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.155" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71f2b4817415c6d4210bfe1c7bfcf4801b2d904cb4d0e1a8fdb651013c9e86b8" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" dependencies = [ "serde_derive", ] @@ -1745,13 +2181,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.155" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d071a94a3fac4aff69d023a7f411e33f40f3483f8c5190b1953822b6b76d7630" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.12", ] [[package]] @@ -1762,14 +2198,14 @@ checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "serde_json" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" dependencies = [ "itoa", "ryu", @@ -1778,13 +2214,24 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.12", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.6", ] [[package]] @@ -1837,6 +2284,31 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spki" version = "0.6.0" @@ -1886,15 +2358,14 @@ dependencies = [ ] [[package]] -name = "synstructure" -version = "0.12.6" +name = "syn" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" dependencies = [ "proc-macro2", "quote", - "syn", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -1912,7 +2383,7 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost 0.11.6", + "prost 0.11.8", "prost-types", "ripemd160", "serde", @@ -1928,6 +2399,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tendermint-config" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d42ee0abc27ef5fc34080cce8d43c189950d331631546e7dfb983b6274fa327" +dependencies = [ + "flex-error", + "serde", + "serde_json", + "tendermint", + "toml", + "url", +] + [[package]] name = "tendermint-proto" version = "0.23.9" @@ -1938,7 +2423,7 @@ dependencies = [ "flex-error", "num-derive", "num-traits", - "prost 0.11.6", + "prost 0.11.8", "prost-types", "serde", "serde_bytes", @@ -1946,6 +2431,39 @@ dependencies = [ "time", ] +[[package]] +name = "tendermint-rpc" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f14aafe3528a0f75e9f3f410b525617b2de16c4b7830a21f717eee62882ec60" +dependencies = [ + "async-trait", + "bytes", + "flex-error", + "futures", + "getrandom", + "http", + "hyper", + "hyper-proxy", + "hyper-rustls", + "peg", + "pin-project", + "serde", + "serde_bytes", + "serde_json", + "subtle-encoding", + "tendermint", + "tendermint-config", + "tendermint-proto", + "thiserror", + "time", + "tokio", + "tracing", + "url", + "uuid", + "walkdir", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -1958,14 +2476,13 @@ dependencies = [ [[package]] name = "test-tube" version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2791215b77f72c219df4d3e95d78539ee66950a0449961ba6d0ab1c7b538673f" +source = "git+https://github.com/osmosis-labs/test-tube?branch=main#c45a1c88d7b4f28858d3965590525058da506b13" dependencies = [ "base64", "cosmrs", "cosmwasm-std", - "osmosis-std 0.13.2", - "prost 0.11.6", + "osmosis-std 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.11.8", "serde", "serde_json", "thiserror", @@ -1979,22 +2496,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.12", ] [[package]] @@ -2014,6 +2531,115 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.45.0", +] + +[[package]] +name = "tokio-macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-util" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "typenum" version = "1.16.0" @@ -2032,17 +2658,49 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] -name = "unicode-xid" -version = "0.2.4" +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "uuid" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "version_check" @@ -2050,6 +2708,26 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2077,7 +2755,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -2099,7 +2777,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2110,6 +2788,35 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + [[package]] name = "which" version = "4.4.0" @@ -2152,23 +2859,103 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "zeroize" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.3.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.12", ] diff --git a/Cargo.toml b/Cargo.toml index 922cfc760..030369518 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,8 +47,8 @@ cw-paginate = "0.2.1" cw-utils = "1.0.1" cw-storage-plus = "1.0.1" itertools = "0.10.5" -osmosis-std = "0.14.0" -osmosis-test-tube = "14.1.1" +osmosis-std = { git = "https://github.com/osmosis-labs/osmosis-rust", branch = "main" } +osmosis-test-tube = { git = "https://github.com/osmosis-labs/test-tube", branch = "main" } schemars = "0.8.12" serde = { version = "1.0.155", default-features = false, features = ["derive"] } serde_json = "1.0.94" diff --git a/contracts/swapper/osmosis/src/route.rs b/contracts/swapper/osmosis/src/route.rs index c9beab023..4d9cf160a 100644 --- a/contracts/swapper/osmosis/src/route.rs +++ b/contracts/swapper/osmosis/src/route.rs @@ -7,7 +7,9 @@ use cosmwasm_std::{ use mars_osmosis::helpers::{has_denom, query_arithmetic_twap_price, query_pool}; use mars_rover::adapters::swap::EstimateExactInSwapResponse; use mars_swapper_base::{ContractError, ContractResult, Route}; -use osmosis_std::types::osmosis::gamm::v1beta1::{MsgSwapExactAmountIn, SwapAmountInRoute}; +use osmosis_std::types::osmosis::{ + gamm::v1beta1::MsgSwapExactAmountIn, poolmanager::v1beta1::SwapAmountInRoute, +}; use crate::helpers::hashset; diff --git a/contracts/swapper/osmosis/tests/helpers.rs b/contracts/swapper/osmosis/tests/helpers.rs index e933fbd94..64aa02529 100644 --- a/contracts/swapper/osmosis/tests/helpers.rs +++ b/contracts/swapper/osmosis/tests/helpers.rs @@ -2,8 +2,9 @@ use std::{fmt::Display, str::FromStr}; use cosmwasm_std::{Coin, Decimal, Uint128}; use mars_rover::adapters::swap::InstantiateMsg; -use osmosis_std::types::osmosis::gamm::v1beta1::{ - MsgSwapExactAmountIn, MsgSwapExactAmountInResponse, SwapAmountInRoute, +use osmosis_std::types::osmosis::{ + gamm::v1beta1::{MsgSwapExactAmountIn, MsgSwapExactAmountInResponse}, + poolmanager::v1beta1::SwapAmountInRoute, }; use osmosis_test_tube::{ cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest, Account, Bank, ExecuteResponse, diff --git a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs index 03cff9515..77f2ea322 100644 --- a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs +++ b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use cosmwasm_std::coin; use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; use mars_swapper_osmosis::route::OsmosisRoute; -use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; +use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; use osmosis_test_tube::{Gamm, Module, OsmosisTestApp, SigningAccount, Wasm}; use crate::helpers::instantiate_contract; diff --git a/contracts/swapper/osmosis/tests/test_estimate.rs b/contracts/swapper/osmosis/tests/test_estimate.rs index eed11381b..fc23abb23 100644 --- a/contracts/swapper/osmosis/tests/test_estimate.rs +++ b/contracts/swapper/osmosis/tests/test_estimate.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{coin, Uint128}; use mars_rover::adapters::swap::{EstimateExactInSwapResponse, ExecuteMsg, QueryMsg}; use mars_swapper_osmosis::route::OsmosisRoute; -use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; +use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; use osmosis_test_tube::{Gamm, Module, OsmosisTestApp, RunnerResult, Wasm}; use crate::helpers::{ diff --git a/contracts/swapper/osmosis/tests/test_set_route.rs b/contracts/swapper/osmosis/tests/test_set_route.rs index f6124f9c2..087584c3b 100644 --- a/contracts/swapper/osmosis/tests/test_set_route.rs +++ b/contracts/swapper/osmosis/tests/test_set_route.rs @@ -3,7 +3,7 @@ use mars_owner::OwnerError; use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; use mars_swapper_base::ContractError; use mars_swapper_osmosis::route::OsmosisRoute; -use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; +use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; use osmosis_test_tube::{Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract}; diff --git a/contracts/swapper/osmosis/tests/test_swap.rs b/contracts/swapper/osmosis/tests/test_swap.rs index cf6233bdf..6a17dddc6 100644 --- a/contracts/swapper/osmosis/tests/test_swap.rs +++ b/contracts/swapper/osmosis/tests/test_swap.rs @@ -1,9 +1,9 @@ -use cosmwasm_std::{coin, Addr, Decimal}; +use cosmwasm_std::{coin, Addr, Coin, Decimal}; use mars_rover::{adapters::swap::ExecuteMsg, error::ContractError as RoverError}; use mars_swapper_base::ContractError; use mars_swapper_osmosis::route::OsmosisRoute; -use osmosis_std::types::osmosis::gamm::v1beta1::SwapAmountInRoute; -use osmosis_test_tube::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; +use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; +use osmosis_test_tube::{Account, Bank, FeeSetting, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{ assert_err, instantiate_contract, query_balance, swap_to_create_twap_records, @@ -52,7 +52,7 @@ fn swap_exact_in_slippage_too_high() { let signer = app .init_account(&[coin(1_000_000_000_000, "uosmo"), coin(1_000_000_000_000, "umars")]) .unwrap(); - let whale = app.init_account(&[coin(1_000_000, "umars")]).unwrap(); + let whale = app.init_account(&[coin(1_000_000, "umars"), coin(1_000_000, "uosmo")]).unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); @@ -110,7 +110,16 @@ fn swap_exact_in_success() { let signer = app .init_account(&[coin(1_000_000_000_000, "uosmo"), coin(1_000_000_000_000, "umars")]) .unwrap(); - let user = app.init_account(&[coin(10_000, "umars")]).unwrap(); + + let tx_fee = 1_000_000u128; + let user_osmo_starting_amount = 10_000_000u128; + let user = app + .init_account(&[coin(10_000, "umars"), coin(user_osmo_starting_amount, "uosmo")]) + .unwrap() + .with_fee_setting(FeeSetting::Custom { + amount: Coin::new(tx_fee, "uosmo"), + gas_limit: tx_fee as u64, + }); let contract_addr = instantiate_contract(&wasm, &signer); @@ -141,7 +150,7 @@ fn swap_exact_in_success() { let bank = Bank::new(&app); let osmo_balance = query_balance(&bank, &user.address(), "uosmo"); let mars_balance = query_balance(&bank, &user.address(), "umars"); - assert_eq!(osmo_balance, 0); + assert_eq!(osmo_balance, user_osmo_starting_amount); assert_eq!(mars_balance, 10_000); wasm.execute( @@ -159,7 +168,7 @@ fn swap_exact_in_success() { // Assert user receives their new tokens let osmo_balance = query_balance(&bank, &user.address(), "uosmo"); let mars_balance = query_balance(&bank, &user.address(), "umars"); - assert_eq!(osmo_balance, 2470); + assert_eq!(osmo_balance, 2470 + user_osmo_starting_amount - tx_fee); assert_eq!(mars_balance, 0); // Assert no tokens in contract left over diff --git a/contracts/zapper/osmosis/Cargo.toml b/contracts/zapper/osmosis/Cargo.toml index 08c6f0e4d..5c9dcefc2 100644 --- a/contracts/zapper/osmosis/Cargo.toml +++ b/contracts/zapper/osmosis/Cargo.toml @@ -26,4 +26,5 @@ mars-zapper-base = { workspace = true } [dev-dependencies] cw-utils = { workspace = true } +osmosis-std = { workspace = true } osmosis-test-tube = { workspace = true } diff --git a/contracts/zapper/osmosis/tests/helpers/mod.rs b/contracts/zapper/osmosis/tests/helpers/mod.rs new file mode 100644 index 000000000..4141203e2 --- /dev/null +++ b/contracts/zapper/osmosis/tests/helpers/mod.rs @@ -0,0 +1,3 @@ +pub use self::utils::*; + +mod utils; diff --git a/contracts/zapper/osmosis/tests/helpers.rs b/contracts/zapper/osmosis/tests/helpers/utils.rs similarity index 100% rename from contracts/zapper/osmosis/tests/helpers.rs rename to contracts/zapper/osmosis/tests/helpers/utils.rs diff --git a/contracts/zapper/osmosis/tests/test_provide_liquidity.rs b/contracts/zapper/osmosis/tests/test_v2_provide_liquidity.rs similarity index 86% rename from contracts/zapper/osmosis/tests/test_provide_liquidity.rs rename to contracts/zapper/osmosis/tests/test_v2_provide_liquidity.rs index 519c34226..7278eab68 100644 --- a/contracts/zapper/osmosis/tests/test_provide_liquidity.rs +++ b/contracts/zapper/osmosis/tests/test_v2_provide_liquidity.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; use mars_zapper_base::{ExecuteMsg, QueryMsg}; -use osmosis_test_tube::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; +use osmosis_test_tube::{Account, Bank, FeeSetting, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract, query_balance}; @@ -134,21 +134,23 @@ fn provide_liquidity_with_one_coin() { let uatom_acc_balance = 1_000_000_000_000u128; let uosmo_acc_balance = 1_000_000_000_000u128; - let accs = app - .init_accounts(&[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")], 2) - .unwrap(); - let owner = &accs[0]; - let user = &accs[1]; + let starting_coins = &[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")]; + let owner = app.init_account(starting_coins).unwrap(); + let tx_fee = 1000000u128; + let user = app.init_account(starting_coins).unwrap().with_fee_setting(FeeSetting::Custom { + amount: Coin::new(tx_fee, "uosmo"), + gas_limit: tx_fee as u64, + }); let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], owner) + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], &owner) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{pool_id}"); - let contract_addr = instantiate_contract(&wasm, owner); + let contract_addr = instantiate_contract(&wasm, &owner); let bank = Bank::new(&app); @@ -179,7 +181,7 @@ fn provide_liquidity_with_one_coin() { minimum_receive: estimate_amount, }, &coins_in, - user, + &user, ) .unwrap(); @@ -195,7 +197,7 @@ fn provide_liquidity_with_one_coin() { let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); assert_eq!(user_uatom_balance, uatom_acc_balance - uatom_liquidity_amount); let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); - assert_eq!(user_uosmo_balance, uosmo_acc_balance); + assert_eq!(user_uosmo_balance, uosmo_acc_balance - tx_fee); } #[test] @@ -205,21 +207,23 @@ fn provide_liquidity_with_two_balanced_coins() { let uatom_acc_balance = 1_000_000_000_000u128; let uosmo_acc_balance = 1_000_000_000_000u128; - let accs = app - .init_accounts(&[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")], 2) - .unwrap(); - let owner = &accs[0]; - let user = &accs[1]; + let starting_coins = &[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")]; + let owner = app.init_account(starting_coins).unwrap(); + let tx_fee = 1000000u128; + let user = app.init_account(starting_coins).unwrap().with_fee_setting(FeeSetting::Custom { + amount: Coin::new(tx_fee, "uosmo"), + gas_limit: tx_fee as u64, + }); let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], owner) + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], &owner) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{pool_id}"); - let contract_addr = instantiate_contract(&wasm, owner); + let contract_addr = instantiate_contract(&wasm, &owner); let bank = Bank::new(&app); @@ -252,7 +256,7 @@ fn provide_liquidity_with_two_balanced_coins() { minimum_receive: estimate_amount, }, &coins_in, - user, + &user, ) .unwrap(); @@ -268,7 +272,7 @@ fn provide_liquidity_with_two_balanced_coins() { let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); assert_eq!(user_uatom_balance, uatom_acc_balance - uatom_liquidity_amount); let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); - assert_eq!(user_uosmo_balance, uosmo_acc_balance - uosmo_liquidity_amount); + assert_eq!(user_uosmo_balance, uosmo_acc_balance - uosmo_liquidity_amount - tx_fee); } #[test] @@ -278,21 +282,23 @@ fn provide_liquidity_with_two_unbalanced_coins() { let uatom_acc_balance = 1_000_000_000_000u128; let uosmo_acc_balance = 1_000_000_000_000u128; - let accs = app - .init_accounts(&[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")], 2) - .unwrap(); - let owner = &accs[0]; - let user = &accs[1]; + let starting_coins = &[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")]; + let owner = app.init_account(starting_coins).unwrap(); + let tx_fee = 1000000u128; + let user = app.init_account(starting_coins).unwrap().with_fee_setting(FeeSetting::Custom { + amount: Coin::new(tx_fee, "uosmo"), + gas_limit: tx_fee as u64, + }); let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool(&[coin(20_000_000_000, "uatom"), coin(40_000_000_000, "uosmo")], owner) + .create_basic_pool(&[coin(20_000_000_000, "uatom"), coin(40_000_000_000, "uosmo")], &owner) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{pool_id}"); - let contract_addr = instantiate_contract(&wasm, owner); + let contract_addr = instantiate_contract(&wasm, &owner); let bank = Bank::new(&app); @@ -341,7 +347,7 @@ fn provide_liquidity_with_two_unbalanced_coins() { minimum_receive: estimate_amount, }, &coins_in, - user, + &user, ) .unwrap(); @@ -357,7 +363,7 @@ fn provide_liquidity_with_two_unbalanced_coins() { let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); assert_eq!(user_uatom_balance, 999995000000u128); let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); - assert_eq!(user_uosmo_balance, 999990000000u128); + assert_eq!(user_uosmo_balance, 999989000000u128); } #[test] @@ -367,22 +373,24 @@ fn provide_liquidity_with_different_recipient() { let uatom_acc_balance = 1_000_000_000_000u128; let uosmo_acc_balance = 1_000_000_000_000u128; - let accs = app - .init_accounts(&[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")], 3) - .unwrap(); - let owner = &accs[0]; - let user = &accs[1]; - let recipient = &accs[2]; + let starting_coins = &[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")]; + let owner = app.init_account(starting_coins).unwrap(); + let tx_fee = 1000000u128; + let user = app.init_account(starting_coins).unwrap().with_fee_setting(FeeSetting::Custom { + amount: Coin::new(tx_fee, "uosmo"), + gas_limit: tx_fee as u64, + }); + let recipient = app.init_account(starting_coins).unwrap(); let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], owner) + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], &owner) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{pool_id}"); - let contract_addr = instantiate_contract(&wasm, owner); + let contract_addr = instantiate_contract(&wasm, &owner); let bank = Bank::new(&app); @@ -417,7 +425,7 @@ fn provide_liquidity_with_different_recipient() { minimum_receive: estimate_amount, }, &coins_in, - user, + &user, ) .unwrap(); @@ -433,7 +441,7 @@ fn provide_liquidity_with_different_recipient() { let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); assert_eq!(user_uatom_balance, uatom_acc_balance - uatom_liquidity_amount); let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); - assert_eq!(user_uosmo_balance, uosmo_acc_balance - uosmo_liquidity_amount); + assert_eq!(user_uosmo_balance, uosmo_acc_balance - uosmo_liquidity_amount - tx_fee); let recipient_pool_balance = query_balance(&bank, &recipient.address(), &pool_denom); assert_eq!(recipient_pool_balance, estimate_amount.u128()); diff --git a/contracts/zapper/osmosis/tests/test_queries.rs b/contracts/zapper/osmosis/tests/test_v2_queries.rs similarity index 100% rename from contracts/zapper/osmosis/tests/test_queries.rs rename to contracts/zapper/osmosis/tests/test_v2_queries.rs diff --git a/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs b/contracts/zapper/osmosis/tests/test_v2_withdraw_liquidity.rs similarity index 87% rename from contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs rename to contracts/zapper/osmosis/tests/test_v2_withdraw_liquidity.rs index 799c11433..cf6585254 100644 --- a/contracts/zapper/osmosis/tests/test_withdraw_liquidity.rs +++ b/contracts/zapper/osmosis/tests/test_v2_withdraw_liquidity.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; use cw_utils::PaymentError; use mars_zapper_base::{ContractError, ExecuteMsg, QueryMsg}; -use osmosis_test_tube::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; +use osmosis_test_tube::{Account, Bank, FeeSetting, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract, query_balance}; @@ -85,21 +85,23 @@ fn withdraw_liquidity_successfully() { let uatom_acc_balance = 1_000_000_000_000u128; let uosmo_acc_balance = 1_000_000_000_000u128; - let accs = app - .init_accounts(&[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")], 2) - .unwrap(); - let owner = &accs[0]; - let user = &accs[1]; + let starting_coins = &[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")]; + let owner = app.init_account(starting_coins).unwrap(); + let tx_fee = 1000000u128; + let user = app.init_account(starting_coins).unwrap().with_fee_setting(FeeSetting::Custom { + amount: Coin::new(tx_fee, "uosmo"), + gas_limit: tx_fee as u64, + }); let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], owner) + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], &owner) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{pool_id}"); - let contract_addr = instantiate_contract(&wasm, owner); + let contract_addr = instantiate_contract(&wasm, &owner); let bank = Bank::new(&app); @@ -116,7 +118,7 @@ fn withdraw_liquidity_successfully() { minimum_receive: Uint128::one(), }, &[coin(uatom_liquidity_amount, "uatom"), coin(uosmo_liquidity_amount, "uosmo")], - user, + &user, ) .unwrap(); @@ -146,7 +148,7 @@ fn withdraw_liquidity_successfully() { recipient: None, }, &[coin(user_pool_balance, &pool_denom)], - user, + &user, ) .unwrap(); @@ -162,7 +164,7 @@ fn withdraw_liquidity_successfully() { let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); assert_eq!(user_uatom_balance, user_uatom_balance_before + uatom_estimate_amount); let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); - assert_eq!(user_uosmo_balance, user_uosmo_balance_before + uosmo_estimate_amount); + assert_eq!(user_uosmo_balance, user_uosmo_balance_before + uosmo_estimate_amount - tx_fee); } #[test] @@ -172,22 +174,24 @@ fn withdraw_liquidity_with_different_recipient_successfully() { let uatom_acc_balance = 1_000_000_000_000u128; let uosmo_acc_balance = 1_000_000_000_000u128; - let accs = app - .init_accounts(&[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")], 3) - .unwrap(); - let owner = &accs[0]; - let user = &accs[1]; - let recipient = &accs[2]; + let starting_coins = &[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")]; + let owner = app.init_account(starting_coins).unwrap(); + let tx_fee = 1000000u128; + let user = app.init_account(starting_coins).unwrap().with_fee_setting(FeeSetting::Custom { + amount: Coin::new(tx_fee, "uosmo"), + gas_limit: tx_fee as u64, + }); + let recipient = app.init_account(starting_coins).unwrap(); let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], owner) + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], &owner) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{pool_id}"); - let contract_addr = instantiate_contract(&wasm, owner); + let contract_addr = instantiate_contract(&wasm, &owner); let bank = Bank::new(&app); @@ -204,7 +208,7 @@ fn withdraw_liquidity_with_different_recipient_successfully() { minimum_receive: Uint128::one(), }, &[coin(uatom_liquidity_amount, "uatom"), coin(uosmo_liquidity_amount, "uosmo")], - user, + &user, ) .unwrap(); @@ -239,7 +243,7 @@ fn withdraw_liquidity_with_different_recipient_successfully() { recipient: Some(recipient.address()), }, &[coin(user_pool_balance, &pool_denom)], - user, + &user, ) .unwrap(); @@ -255,7 +259,7 @@ fn withdraw_liquidity_with_different_recipient_successfully() { let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); assert_eq!(user_uatom_balance, user_uatom_balance_before); let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); - assert_eq!(user_uosmo_balance, user_uosmo_balance_before); + assert_eq!(user_uosmo_balance, user_uosmo_balance_before - tx_fee); let recipient_pool_balance = query_balance(&bank, &recipient.address(), &pool_denom); assert_eq!(recipient_pool_balance, 0u128); diff --git a/contracts/zapper/osmosis/tests/test_v3_add_position.rs b/contracts/zapper/osmosis/tests/test_v3_add_position.rs new file mode 100644 index 000000000..241e00a35 --- /dev/null +++ b/contracts/zapper/osmosis/tests/test_v3_add_position.rs @@ -0,0 +1,125 @@ +use cosmwasm_std::{coin, Coin}; +use osmosis_std::types::osmosis::{ + concentratedliquidity::v1beta1::MsgCreateConcentratedPool, tokenfactory::v1beta1::MsgMint, +}; +use osmosis_test_tube::{ + osmosis_std::types::osmosis::tokenfactory::v1beta1::MsgCreateDenom, Account, + ConcentratedLiquidity, Module, OsmosisTestApp, TokenFactory, +}; + +pub mod helpers; + +#[test] +fn add_position() { + let app = OsmosisTestApp::new(); + + let cl = ConcentratedLiquidity::new(&app); + let token_factory = TokenFactory::new(&app); + + let accs = app + .init_accounts(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")], 2) + .unwrap(); + let signer = &accs[0]; + + let denom0 = token_factory + .create_denom( + MsgCreateDenom { + sender: signer.address(), + subdenom: "xyz".to_string(), + }, + signer, + ) + .unwrap() + .data + .new_token_denom; + + let denom1 = token_factory + .create_denom( + MsgCreateDenom { + sender: signer.address(), + subdenom: "abc".to_string(), + }, + signer, + ) + .unwrap() + .data + .new_token_denom; + + token_factory + .mint( + MsgMint { + sender: signer.address(), + amount: Some(Coin::new(100_000_000_000, &denom0).into()), + mint_to_address: signer.address(), + }, + signer, + ) + .unwrap(); + + token_factory + .mint( + MsgMint { + sender: signer.address(), + amount: Some(Coin::new(100_000_000_000, &denom1).into()), + mint_to_address: signer.address(), + }, + signer, + ) + .unwrap(); + + let pool_id = cl + .create_concentrated_pool( + MsgCreateConcentratedPool { + sender: signer.address(), + denom0, + denom1, + tick_spacing: 1, + precision_factor_at_price_one: "-4".to_string(), + swap_fee: "0".to_string(), + }, + signer, + ) + .unwrap() + .data + .pool_id; + + assert_eq!(1, pool_id) + + // TODO: Temporarily broken. Waiting for latest. + // let position_id = cl + // .create_position( + // MsgCreatePosition { + // pool_id, + // sender: signer.address(), + // lower_tick: -1, + // upper_tick: 100, + // token_desired0: Some(v1beta1::Coin { + // denom: denom0.to_string(), + // amount: "9999999999".to_string(), + // }), + // token_desired1: Some(v1beta1::Coin { + // denom: denom1.to_string(), + // amount: "10000000000".to_string(), + // }), + // token_min_amount0: "1".to_string(), + // token_min_amount1: "1".to_string(), + // freeze_duration: None, + // }, + // signer, + // ) + // .unwrap() + // .data + // .position_id; + // + // println!("Position id: {position_id}"); + + // let liquidity = cl + // .query_total_liquidity_for_range(&QueryTotalLiquidityForRangeRequest { + // pool_id, + // }) + // .unwrap(); + + // for l in liquidity { + // println!("liquidity_amount: {}", l.liquidity_amount); + // } +} From c9713f7c792b3321a97d3b08fc213a11957799f5 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 27 Apr 2023 16:24:09 +0200 Subject: [PATCH 156/218] Fixing wasm-pack compilation bug (#134) * Fixing wasm-pack compilation bug * Upgrade deps (security) --- Cargo.lock | 298 +++--- Cargo.toml | 16 +- .../tests/test_v2_provide_liquidity.rs | 4 +- .../zapper/osmosis/tests/test_v2_queries.rs | 2 +- .../tests/test_v2_withdraw_liquidity.rs | 8 +- .../osmosis/tests/test_v3_add_position.rs | 2 +- packages/health-computer/Cargo.toml | 1 + packages/health-computer/src/lib.rs | 7 +- scripts/deploy/base/index.ts | 1 + scripts/deploy/osmosis/testnet-config.ts | 4 +- scripts/health/pkg-node/index.js | 82 +- scripts/health/pkg-node/index_bg.wasm | Bin 162325 -> 159992 bytes scripts/health/pkg-web/index.js | 72 +- scripts/health/pkg-web/index_bg.wasm | Bin 161401 -> 159068 bytes scripts/package.json | 6 +- scripts/tsconfig.json | 1 - scripts/yarn.lock | 923 ++++++++---------- 17 files changed, 688 insertions(+), 739 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11e691d24..4402514db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] @@ -61,7 +61,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.15", ] [[package]] @@ -175,9 +175,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" [[package]] name = "byteorder" @@ -235,9 +235,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "3.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "eef2b3ded6a26dfaec672a742c93c8cf6b689220324da509ec5caa20de55dc83" dependencies = [ "atty", "bitflags", @@ -285,9 +285,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cosmos-sdk-proto" @@ -295,7 +295,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20b42021d8488665b1a0d9748f1f81df7235362d194f44481e2e61bf376b77b4" dependencies = [ - "prost 0.11.8", + "prost 0.11.9", "prost-types", "tendermint-proto", ] @@ -323,9 +323,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f22add0f9b2a5416df98c1d0248a8d8eedb882c38fbf0c5052b64eebe865df6d" +checksum = "b76d2207945b8aa3ce0735da53ab9a74f75fe3e7794754c216a9edfa04e1e627" dependencies = [ "digest 0.10.6", "ed25519-zebra", @@ -336,18 +336,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e64f710a18ef90d0a632cf27842e98ffc2d005a38a6f76c12fd0bc03bc1a2d" +checksum = "1dd07af7736164d2d8126dc67fdb33b1b5c54fb5a3190395c47f46d24fc6d592" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5ad2e23a971b9e4cd57b20cee3e2e79c33799bed4b128e473aca3702bfe5dd" +checksum = "3e9e92cdce475a91659d0dc4d17836a484fc534e80e787281aa4adde4cb1798b" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -358,9 +358,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2926d159a9bb1a716a592b40280f1663f2491a9de3b6da77c0933cee2a2655b8" +checksum = "69681bac3dbeb0b00990279e3ed39e3d4406b29f538b16b712f6771322a45048" dependencies = [ "proc-macro2", "quote", @@ -369,9 +369,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76fee88ff5bf7bef55bd37ac0619974701b99bf6bd4b16cf56ee8810718abd71" +checksum = "8d39f20967baeb94709123f7bba13a25ae2fa166bc5e7813f734914df3f8f6a1" dependencies = [ "base64", "cosmwasm-crypto", @@ -389,9 +389,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -475,9 +475,9 @@ dependencies = [ [[package]] name = "cw-multi-test" -version = "0.16.2" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2eb84554bbfa6b66736abcd6a9bfdf237ee0ecb83910f746dff7f799093c80a" +checksum = "2a18afd2e201221c6d72a57f0886ef2a22151bbc9e6db7af276fde8a91081042" dependencies = [ "anyhow", "cosmwasm-std", @@ -878,7 +878,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.15", ] [[package]] @@ -923,9 +923,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "js-sys", @@ -953,9 +953,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.16" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" dependencies = [ "bytes", "fnv", @@ -1079,9 +1079,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.25" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -1224,9 +1224,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.140" +version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" [[package]] name = "libloading" @@ -1499,7 +1499,7 @@ dependencies = [ "mars-owner", "mars-rover", "mars-swapper-base", - "osmosis-std 0.13.2 (git+https://github.com/osmosis-labs/osmosis-rust?branch=main)", + "osmosis-std 0.15.2", "osmosis-test-tube", "schemars", "thiserror", @@ -1549,7 +1549,7 @@ dependencies = [ "cw-utils 1.0.1", "cw2 1.0.1", "mars-zapper-base", - "osmosis-std 0.13.2 (git+https://github.com/osmosis-labs/osmosis-rust?branch=main)", + "osmosis-std 0.15.2", "osmosis-test-tube", ] @@ -1668,14 +1668,14 @@ checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" [[package]] name = "osmosis-std" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d6fe6ac7fcba45ed61d738091d33c838c4cabbcf4892dc7aa56d19d39cc976" +checksum = "2fc0a9075efd64ed5a8be3bf134cbf1080570d68384f2ad58ffaac6c00d063fd" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", - "prost 0.11.8", + "osmosis-std-derive 0.13.2", + "prost 0.11.9", "prost-types", "schemars", "serde", @@ -1684,29 +1684,13 @@ dependencies = [ [[package]] name = "osmosis-std" -version = "0.13.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#6841a23fc9a0b11649acdb7eb436f564b1a87cbb" +version = "0.15.2" +source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#f726d5f5820cd04fedf23ea336e7174acb5b44da" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.13.2 (git+https://github.com/osmosis-labs/osmosis-rust?branch=main)", - "prost 0.11.8", - "prost-types", - "schemars", - "serde", - "serde-cw-value", -] - -[[package]] -name = "osmosis-std" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc0a9075efd64ed5a8be3bf134cbf1080570d68384f2ad58ffaac6c00d063fd" -dependencies = [ - "chrono", - "cosmwasm-std", - "osmosis-std-derive 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", - "prost 0.11.8", + "osmosis-std-derive 0.15.2", + "prost 0.11.9", "prost-types", "schemars", "serde", @@ -1727,8 +1711,8 @@ dependencies = [ [[package]] name = "osmosis-std-derive" -version = "0.13.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#6841a23fc9a0b11649acdb7eb436f564b1a87cbb" +version = "0.15.2" +source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#f726d5f5820cd04fedf23ea336e7174acb5b44da" dependencies = [ "itertools", "proc-macro2", @@ -1739,14 +1723,14 @@ dependencies = [ [[package]] name = "osmosis-test-tube" version = "14.1.1" -source = "git+https://github.com/osmosis-labs/test-tube?branch=main#c45a1c88d7b4f28858d3965590525058da506b13" +source = "git+https://github.com/osmosis-labs/test-tube?branch=main#1ba6e8021e441c7ffd1e17796ae64cb750ca57d0" dependencies = [ "base64", "bindgen", "cosmrs", "cosmwasm-std", - "osmosis-std 0.13.2 (git+https://github.com/osmosis-labs/osmosis-rust?branch=main)", - "prost 0.11.8", + "osmosis-std 0.15.2", + "prost 0.11.9", "serde", "serde_json", "test-tube", @@ -1851,9 +1835,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.54" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -1870,12 +1854,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48e50df39172a3e7eb17e14642445da64996989bc212b583015435d39a58537" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", - "prost-derive 0.11.8", + "prost-derive 0.11.9", ] [[package]] @@ -1893,9 +1877,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea9b0f8cbe5e15a8a042d030bd96668db28ecb567ec37d691971ff5731d2b1b" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools", @@ -1906,11 +1890,11 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379119666929a1afd7a043aa6cf96fa67a6dce9af60c88095a4686dbce4c9c88" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" dependencies = [ - "prost 0.11.8", + "prost 0.11.9", ] [[package]] @@ -1939,9 +1923,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.3" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ "aho-corasick", "memchr", @@ -1950,9 +1934,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" [[package]] name = "rfc6979" @@ -2134,9 +2118,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] @@ -2152,9 +2136,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15bee9b04dd165c3f4e142628982ddde884c2022a89e8ddf99c4829bf2c3a58" +checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" dependencies = [ "serde", ] @@ -2181,13 +2165,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.15", ] [[package]] @@ -2203,9 +2187,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -2220,7 +2204,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.15", ] [[package]] @@ -2260,9 +2244,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +checksum = "54c2bb1a323307527314a36bfb73f24febb08ce2b8a554bf4ffd6f51ad15198c" dependencies = [ "digest 0.10.6", "keccak", @@ -2359,9 +2343,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.12" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -2383,7 +2367,7 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost 0.11.8", + "prost 0.11.9", "prost-types", "ripemd160", "serde", @@ -2423,7 +2407,7 @@ dependencies = [ "flex-error", "num-derive", "num-traits", - "prost 0.11.8", + "prost 0.11.9", "prost-types", "serde", "serde_bytes", @@ -2476,13 +2460,12 @@ dependencies = [ [[package]] name = "test-tube" version = "0.1.1" -source = "git+https://github.com/osmosis-labs/test-tube?branch=main#c45a1c88d7b4f28858d3965590525058da506b13" +source = "git+https://github.com/osmosis-labs/test-tube?branch=main#1ba6e8021e441c7ffd1e17796ae64cb750ca57d0" dependencies = [ "base64", "cosmrs", "cosmwasm-std", - "osmosis-std 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", - "prost 0.11.8", + "prost 0.11.9", "serde", "serde_json", "thiserror", @@ -2511,7 +2494,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.15", ] [[package]] @@ -2548,9 +2531,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.27.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" dependencies = [ "autocfg", "bytes", @@ -2560,18 +2543,18 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.15", ] [[package]] @@ -2587,9 +2570,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -2616,11 +2599,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "cf9cf6a813d3f40c88b0b6b6f29a5c95c6cdbf97c1f9cc53fb820200f5ad814d" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-core", ] @@ -2865,13 +2847,13 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -2880,7 +2862,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] @@ -2889,13 +2880,28 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] @@ -2904,42 +2910,84 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "zeroize" version = "1.6.0" @@ -2957,5 +3005,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.15", ] diff --git a/Cargo.toml b/Cargo.toml index 030369518..14c622291 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,14 +35,14 @@ documentation = "https://docs.marsprotocol.io/" keywords = ["mars", "cosmos", "cosmwasm"] [workspace.dependencies] -anyhow = "1.0.69" -cosmwasm-schema = "1.2.2" -cosmwasm-std = "1.2.2" +anyhow = "1.0.70" +cosmwasm-schema = "1.2.4" +cosmwasm-std = "1.2.4" cw2 = "1.0.1" cw721 = "0.16.0" cw721-base = { version = "0.16.0", features = ["library"] } cw-item-set = { version = "0.7.1", default-features = false, features = ["iterator"] } -cw-multi-test = "0.16.2" +cw-multi-test = "0.16.4" cw-paginate = "0.2.1" cw-utils = "1.0.1" cw-storage-plus = "1.0.1" @@ -50,10 +50,10 @@ itertools = "0.10.5" osmosis-std = { git = "https://github.com/osmosis-labs/osmosis-rust", branch = "main" } osmosis-test-tube = { git = "https://github.com/osmosis-labs/test-tube", branch = "main" } schemars = "0.8.12" -serde = { version = "1.0.155", default-features = false, features = ["derive"] } -serde_json = "1.0.94" +serde = { version = "1.0.160", default-features = false, features = ["derive"] } +serde_json = "1.0.96" serde-wasm-bindgen = "0.5.0" -thiserror = "1.0.39" +thiserror = "1.0.40" wasm-bindgen = "0.2.84" # packages @@ -63,7 +63,7 @@ mars-rover-health-computer = { version = "1.0.0", path = "./packages/health-comp mars-rover-health-types = { version = "1.0.0", path = "./packages/health-types" } mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "00301d60c38af09d8eb7980355009e2f00c6f41f" } mars-red-bank-types = "1.0.0" -mars-owner = { version = "1.0.0", features = ["emergency-owner"] } +mars-owner = { version = "1.1.0", features = ["emergency-owner"] } mars-rover = { version = "1.0.0", path = "./packages/rover" } # contracts diff --git a/contracts/zapper/osmosis/tests/test_v2_provide_liquidity.rs b/contracts/zapper/osmosis/tests/test_v2_provide_liquidity.rs index 7278eab68..f5e52a039 100644 --- a/contracts/zapper/osmosis/tests/test_v2_provide_liquidity.rs +++ b/contracts/zapper/osmosis/tests/test_v2_provide_liquidity.rs @@ -336,8 +336,8 @@ fn provide_liquidity_with_two_unbalanced_coins() { estimate_coins.iter().find(|c| c.denom == "uatom").unwrap().amount.u128(); let uosmo_estimate_amount = estimate_coins.iter().find(|c| c.denom == "uosmo").unwrap().amount.u128(); - assert_eq!(uatom_estimate_amount, 4950000u128); - assert_eq!(uosmo_estimate_amount, 9900000u128); + assert_eq!(uatom_estimate_amount, 5_000_000u128); + assert_eq!(uosmo_estimate_amount, 10_000_000u128); wasm.execute( &contract_addr, diff --git a/contracts/zapper/osmosis/tests/test_v2_queries.rs b/contracts/zapper/osmosis/tests/test_v2_queries.rs index 4bcfbe274..17167d2f2 100644 --- a/contracts/zapper/osmosis/tests/test_v2_queries.rs +++ b/contracts/zapper/osmosis/tests/test_v2_queries.rs @@ -151,5 +151,5 @@ fn estimate_withdraw_liquidity_successfully() { }, ) .unwrap(); - assert_eq!(coins, vec![coin(990000, "uatom"), coin(1980000, "uosmo")]) + assert_eq!(coins, vec![coin(1000000, "uatom"), coin(2000000, "uosmo")]) } diff --git a/contracts/zapper/osmosis/tests/test_v2_withdraw_liquidity.rs b/contracts/zapper/osmosis/tests/test_v2_withdraw_liquidity.rs index cf6585254..72f4f8f26 100644 --- a/contracts/zapper/osmosis/tests/test_v2_withdraw_liquidity.rs +++ b/contracts/zapper/osmosis/tests/test_v2_withdraw_liquidity.rs @@ -139,8 +139,8 @@ fn withdraw_liquidity_successfully() { estimate_coins.iter().find(|c| c.denom == "uatom").unwrap().amount.u128(); let uosmo_estimate_amount = estimate_coins.iter().find(|c| c.denom == "uosmo").unwrap().amount.u128(); - assert_eq!(uatom_estimate_amount, 4950000u128); - assert_eq!(uosmo_estimate_amount, 9900000u128); + assert_eq!(uatom_estimate_amount, 5_000_000u128); + assert_eq!(uosmo_estimate_amount, 10_000_000u128); wasm.execute( &contract_addr, @@ -234,8 +234,8 @@ fn withdraw_liquidity_with_different_recipient_successfully() { estimate_coins.iter().find(|c| c.denom == "uatom").unwrap().amount.u128(); let uosmo_estimate_amount = estimate_coins.iter().find(|c| c.denom == "uosmo").unwrap().amount.u128(); - assert_eq!(uatom_estimate_amount, 4950000u128); - assert_eq!(uosmo_estimate_amount, 9900000u128); + assert_eq!(uatom_estimate_amount, 5_000_000u128); + assert_eq!(uosmo_estimate_amount, 10_000_000u128); wasm.execute( &contract_addr, diff --git a/contracts/zapper/osmosis/tests/test_v3_add_position.rs b/contracts/zapper/osmosis/tests/test_v3_add_position.rs index 241e00a35..c0324e67d 100644 --- a/contracts/zapper/osmosis/tests/test_v3_add_position.rs +++ b/contracts/zapper/osmosis/tests/test_v3_add_position.rs @@ -74,7 +74,7 @@ fn add_position() { denom0, denom1, tick_spacing: 1, - precision_factor_at_price_one: "-4".to_string(), + exponent_at_price_one: "-4".to_string(), swap_fee: "0".to_string(), }, signer, diff --git a/packages/health-computer/Cargo.toml b/packages/health-computer/Cargo.toml index 371712010..c415b6259 100644 --- a/packages/health-computer/Cargo.toml +++ b/packages/health-computer/Cargo.toml @@ -13,6 +13,7 @@ doctest = false [features] backtraces = ["cosmwasm-std/backtraces"] default = ["console_error_panic_hook"] +javascript = [] [dependencies] cosmwasm-schema = { workspace = true } diff --git a/packages/health-computer/src/lib.rs b/packages/health-computer/src/lib.rs index ec8903836..3fae0b93e 100644 --- a/packages/health-computer/src/lib.rs +++ b/packages/health-computer/src/lib.rs @@ -1,5 +1,8 @@ mod data_types; mod health_computer; -mod javascript; +pub use self::{data_types::*, health_computer::*}; -pub use self::{data_types::*, health_computer::*, javascript::*}; +#[cfg(feature = "javascript")] +mod javascript; +#[cfg(feature = "javascript")] +pub use self::javascript::*; diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 1def9503e..6f21a0316 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -17,6 +17,7 @@ export const taskRunner = async ({ config, label }: TaskRunnerProps) => { await deployer.upload('swapper', wasmFile(config.swapperContractName)) await deployer.upload('zapper', wasmFile(config.zapperContractName)) await deployer.upload('creditManager', wasmFile('mars_credit_manager')) + await deployer.upload('healthContract', wasmFile('mars_rover_health')) // Instantiate contracts await deployer.instantiateMockVault() diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index ba9d7ab97..90d3e955d 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -36,9 +36,9 @@ export const osmosisTestnetConfig: DeploymentConfig = { chain: { baseDenom: uosmo, defaultGasPrice: 0.1, - id: 'osmo-test-4', + id: 'osmo-test-5', prefix: 'osmo', - rpcEndpoint: 'https://rpc-test.osmosis.zone', + rpcEndpoint: 'https://rpc.osmotest5.osmosis.zone', }, deployerMnemonic: 'rely wonder join knock during sudden slow plate segment state agree also arrest mandate grief ordinary lonely lawsuit hurt super banana rule velvet cart', diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js index 8425efaba..0b53f1b82 100644 --- a/scripts/health/pkg-node/index.js +++ b/scripts/health/pkg-node/index.js @@ -1,7 +1,7 @@ let imports = {} imports['__wbindgen_placeholder__'] = module.exports let wasm -const { TextEncoder, TextDecoder } = require(`util`) +const { TextDecoder, TextEncoder } = require(`util`) const heap = new Array(128).fill(undefined) @@ -25,7 +25,18 @@ function takeObject(idx) { return ret } -let WASM_VECTOR_LEN = 0 +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1) + const idx = heap_next + heap_next = heap[idx] + + heap[idx] = obj + return idx +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) + +cachedTextDecoder.decode() let cachedUint8Memory0 = null @@ -36,6 +47,12 @@ function getUint8Memory0() { return cachedUint8Memory0 } +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)) +} + +let WASM_VECTOR_LEN = 0 + let cachedTextEncoder = new TextEncoder('utf-8') const encodeString = @@ -104,23 +121,6 @@ function getInt32Memory0() { return cachedInt32Memory0 } -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1) - const idx = heap_next - heap_next = heap[idx] - - heap[idx] = obj - return idx -} - -let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) - -cachedTextDecoder.decode() - -function getStringFromWasm0(ptr, len) { - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)) -} - let cachedFloat64Memory0 = null function getFloat64Memory0() { @@ -224,6 +224,26 @@ module.exports.__wbindgen_object_drop_ref = function (arg0) { takeObject(arg0) } +module.exports.__wbindgen_is_bigint = function (arg0) { + const ret = typeof getObject(arg0) === 'bigint' + return ret +} + +module.exports.__wbindgen_bigint_from_u64 = function (arg0) { + const ret = BigInt.asUintN(64, arg0) + return addHeapObject(ret) +} + +module.exports.__wbindgen_jsval_eq = function (arg0, arg1) { + const ret = getObject(arg0) === getObject(arg1) + return ret +} + +module.exports.__wbindgen_error_new = function (arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)) + return addHeapObject(ret) +} + module.exports.__wbindgen_is_object = function (arg0) { const val = getObject(arg0) const ret = typeof val === 'object' && val !== null @@ -251,37 +271,17 @@ module.exports.__wbindgen_string_get = function (arg0, arg1) { getInt32Memory0()[arg0 / 4 + 0] = ptr0 } -module.exports.__wbindgen_is_bigint = function (arg0) { - const ret = typeof getObject(arg0) === 'bigint' - return ret -} - -module.exports.__wbindgen_bigint_from_u64 = function (arg0) { - const ret = BigInt.asUintN(64, arg0) - return addHeapObject(ret) -} - -module.exports.__wbindgen_jsval_eq = function (arg0, arg1) { - const ret = getObject(arg0) === getObject(arg1) +module.exports.__wbindgen_is_string = function (arg0) { + const ret = typeof getObject(arg0) === 'string' return ret } -module.exports.__wbindgen_error_new = function (arg0, arg1) { - const ret = new Error(getStringFromWasm0(arg0, arg1)) - return addHeapObject(ret) -} - module.exports.__wbindgen_boolean_get = function (arg0) { const v = getObject(arg0) const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 return ret } -module.exports.__wbindgen_is_string = function (arg0) { - const ret = typeof getObject(arg0) === 'string' - return ret -} - module.exports.__wbg_new_abda76e883ba8a5f = function () { const ret = new Error() return addHeapObject(ret) diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index 509278b34c5290bc2af46755abf75a1056bcc890..657f375f844355424adef3c1ed3d436ad196be82 100644 GIT binary patch literal 159992 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┧D?6gF=0w-5oapq(dQ^1_WKDOXm5y^S1YjV* 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

xMVrM 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 literal 161401 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 VEqwaV7aaCoJa@}_XP4^oel>I0))pulj!e3L4G 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

H?v!c$WQI}7HEqI9JVT<0f;*f09a^I86QK4 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 rQg`IGtfl3f$P`UKa@$)!d&WGKuG Promise + config: () => Promise +} +export class MarsRoverHealthQueryClient implements MarsRoverHealthReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.health = this.health.bind(this) + this.config = this.config.bind(this) + } + + health = async ({ + accountId, + kind, + }: { + accountId: string + kind: AccountKind + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + health: { + account_id: accountId, + kind, + }, + }) + } + config = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + config: {}, + }) + } +} +export interface MarsRoverHealthInterface extends MarsRoverHealthReadOnlyInterface { + contractAddress: string + sender: string + updateOwner: ( + ownerUpdate: OwnerUpdate, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise + updateConfig: ( + { + creditManager, + params, + }: { + creditManager?: string + params?: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise +} +export class MarsRoverHealthClient + extends MarsRoverHealthQueryClient + implements MarsRoverHealthInterface +{ + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.updateOwner = this.updateOwner.bind(this) + this.updateConfig = this.updateConfig.bind(this) + } + + updateOwner = async ( + ownerUpdate: OwnerUpdate, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_owner: ownerUpdate, + }, + fee, + memo, + _funds, + ) + } + updateConfig = async ( + { + creditManager, + params, + }: { + creditManager?: string + params?: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_config: { + credit_manager: creditManager, + params, + }, + }, + fee, + memo, + _funds, + ) + } +} diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts new file mode 100644 index 000000000..68c87a4d0 --- /dev/null +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts @@ -0,0 +1,92 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { Coin } from '@cosmjs/amino' +import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + InstantiateMsg, + ExecuteMsg, + OwnerUpdate, + QueryMsg, + AccountKind, + ConfigResponse, + OwnerResponse, + Decimal, + Uint128, + HealthResponse, +} from './MarsRoverHealth.types' +export interface MarsRoverHealthMessage { + contractAddress: string + sender: string + updateOwner: (ownerUpdate: OwnerUpdate, _funds?: Coin[]) => MsgExecuteContractEncodeObject + updateConfig: ( + { + creditManager, + params, + }: { + creditManager?: string + params?: string + }, + _funds?: Coin[], + ) => MsgExecuteContractEncodeObject +} +export class MarsRoverHealthMessageComposer implements MarsRoverHealthMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.updateOwner = this.updateOwner.bind(this) + this.updateConfig = this.updateConfig.bind(this) + } + + updateOwner = (ownerUpdate: OwnerUpdate, _funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_owner: ownerUpdate, + }), + ), + funds: _funds, + }), + } + } + updateConfig = ( + { + creditManager, + params, + }: { + creditManager?: string + params?: string + }, + _funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_config: { + credit_manager: creditManager, + params, + }, + }), + ), + funds: _funds, + }), + } + } +} diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts new file mode 100644 index 000000000..fcd0ecc5d --- /dev/null +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts @@ -0,0 +1,125 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee, Coin } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + OwnerUpdate, + QueryMsg, + AccountKind, + ConfigResponse, + OwnerResponse, + Decimal, + Uint128, + HealthResponse, +} from './MarsRoverHealth.types' +import { MarsRoverHealthQueryClient, MarsRoverHealthClient } from './MarsRoverHealth.client' +export const marsRoverHealthQueryKeys = { + contract: [ + { + contract: 'marsRoverHealth', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...marsRoverHealthQueryKeys.contract[0], address: contractAddress }] as const, + health: (contractAddress: string | undefined, args?: Record) => + [{ ...marsRoverHealthQueryKeys.address(contractAddress)[0], method: 'health', args }] as const, + config: (contractAddress: string | undefined, args?: Record) => + [{ ...marsRoverHealthQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, +} +export interface MarsRoverHealthReactQuery { + client: MarsRoverHealthQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MarsRoverHealthConfigQuery + extends MarsRoverHealthReactQuery {} +export function useMarsRoverHealthConfigQuery({ + client, + options, +}: MarsRoverHealthConfigQuery) { + return useQuery( + marsRoverHealthQueryKeys.config(client?.contractAddress), + () => (client ? client.config() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsRoverHealthHealthQuery + extends MarsRoverHealthReactQuery { + args: { + accountId: string + kind: AccountKind + } +} +export function useMarsRoverHealthHealthQuery({ + client, + args, + options, +}: MarsRoverHealthHealthQuery) { + return useQuery( + marsRoverHealthQueryKeys.health(client?.contractAddress, args), + () => + client + ? client.health({ + accountId: args.accountId, + kind: args.kind, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsRoverHealthUpdateConfigMutation { + client: MarsRoverHealthClient + msg: { + creditManager?: string + params?: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsRoverHealthUpdateConfigMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateConfig(msg, fee, memo, funds), + options, + ) +} +export interface MarsRoverHealthUpdateOwnerMutation { + client: MarsRoverHealthClient + msg: OwnerUpdate + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsRoverHealthUpdateOwnerMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateOwner(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts new file mode 100644 index 000000000..3ef810450 --- /dev/null +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts @@ -0,0 +1,70 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + owner: string +} +export type ExecuteMsg = + | { + update_owner: OwnerUpdate + } + | { + update_config: { + credit_manager?: string | null + params?: string | null + } + } +export type OwnerUpdate = + | { + propose_new_owner: { + proposed: string + } + } + | 'clear_proposed' + | 'accept_proposed' + | 'abolish_owner_role' + | { + set_emergency_owner: { + emergency_owner: string + } + } + | 'clear_emergency_owner' +export type QueryMsg = + | { + health: { + account_id: string + kind: AccountKind + } + } + | { + config: {} + } +export type AccountKind = 'default' | 'high_levered_strategy' +export interface ConfigResponse { + credit_manager?: string | null + owner_response: OwnerResponse + params?: string | null +} +export interface OwnerResponse { + abolished: boolean + emergency_owner?: string | null + initialized: boolean + owner?: string | null + proposed?: string | null +} +export type Decimal = string +export type Uint128 = string +export interface HealthResponse { + above_max_ltv: boolean + liquidatable: boolean + liquidation_health_factor?: Decimal | null + liquidation_threshold_adjusted_collateral: Uint128 + max_ltv_adjusted_collateral: Uint128 + max_ltv_health_factor?: Decimal | null + total_collateral_value: Uint128 + total_debt_value: Uint128 +} diff --git a/scripts/types/generated/mars-rover-health/bundle.ts b/scripts/types/generated/mars-rover-health/bundle.ts new file mode 100644 index 000000000..17b65c7de --- /dev/null +++ b/scripts/types/generated/mars-rover-health/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _28 from './MarsRoverHealth.types' +import * as _29 from './MarsRoverHealth.client' +import * as _30 from './MarsRoverHealth.message-composer' +import * as _31 from './MarsRoverHealth.react-query' +export namespace contracts { + export const MarsRoverHealth = { ..._28, ..._29, ..._30, ..._31 } +} diff --git a/scripts/types/generated/mars-swapper-base/bundle.ts b/scripts/types/generated/mars-swapper-base/bundle.ts index 4562d5fd9..23d5a092f 100644 --- a/scripts/types/generated/mars-swapper-base/bundle.ts +++ b/scripts/types/generated/mars-swapper-base/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _36 from './MarsSwapperBase.types' -import * as _37 from './MarsSwapperBase.client' -import * as _38 from './MarsSwapperBase.message-composer' -import * as _39 from './MarsSwapperBase.react-query' +import * as _40 from './MarsSwapperBase.types' +import * as _41 from './MarsSwapperBase.client' +import * as _42 from './MarsSwapperBase.message-composer' +import * as _43 from './MarsSwapperBase.react-query' export namespace contracts { - export const MarsSwapperBase = { ..._36, ..._37, ..._38, ..._39 } + export const MarsSwapperBase = { ..._40, ..._41, ..._42, ..._43 } } diff --git a/scripts/types/generated/mars-swapper-osmosis/bundle.ts b/scripts/types/generated/mars-swapper-osmosis/bundle.ts index 053948fa8..5af932c8a 100644 --- a/scripts/types/generated/mars-swapper-osmosis/bundle.ts +++ b/scripts/types/generated/mars-swapper-osmosis/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _40 from './MarsSwapperOsmosis.types' -import * as _41 from './MarsSwapperOsmosis.client' -import * as _42 from './MarsSwapperOsmosis.message-composer' -import * as _43 from './MarsSwapperOsmosis.react-query' +import * as _44 from './MarsSwapperOsmosis.types' +import * as _45 from './MarsSwapperOsmosis.client' +import * as _46 from './MarsSwapperOsmosis.message-composer' +import * as _47 from './MarsSwapperOsmosis.react-query' export namespace contracts { - export const MarsSwapperOsmosis = { ..._40, ..._41, ..._42, ..._43 } + export const MarsSwapperOsmosis = { ..._44, ..._45, ..._46, ..._47 } } diff --git a/scripts/types/generated/mars-v2-zapper-base/bundle.ts b/scripts/types/generated/mars-v2-zapper-base/bundle.ts index 41be88329..d45d51de2 100644 --- a/scripts/types/generated/mars-v2-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-v2-zapper-base/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _44 from './MarsV2ZapperBase.types' -import * as _45 from './MarsV2ZapperBase.client' -import * as _46 from './MarsV2ZapperBase.message-composer' -import * as _47 from './MarsV2ZapperBase.react-query' +import * as _48 from './MarsV2ZapperBase.types' +import * as _49 from './MarsV2ZapperBase.client' +import * as _50 from './MarsV2ZapperBase.message-composer' +import * as _51 from './MarsV2ZapperBase.react-query' export namespace contracts { - export const MarsV2ZapperBase = { ..._44, ..._45, ..._46, ..._47 } + export const MarsV2ZapperBase = { ..._48, ..._49, ..._50, ..._51 } } diff --git a/scripts/types/generated/mars-v3-zapper-base/bundle.ts b/scripts/types/generated/mars-v3-zapper-base/bundle.ts index 0f80d612e..1605a0b6a 100644 --- a/scripts/types/generated/mars-v3-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-v3-zapper-base/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _48 from './MarsV3ZapperBase.types' -import * as _49 from './MarsV3ZapperBase.client' -import * as _50 from './MarsV3ZapperBase.message-composer' -import * as _51 from './MarsV3ZapperBase.react-query' +import * as _52 from './MarsV3ZapperBase.types' +import * as _53 from './MarsV3ZapperBase.client' +import * as _54 from './MarsV3ZapperBase.message-composer' +import * as _55 from './MarsV3ZapperBase.react-query' export namespace contracts { - export const MarsV3ZapperBase = { ..._48, ..._49, ..._50, ..._51 } + export const MarsV3ZapperBase = { ..._52, ..._53, ..._54, ..._55 } } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 4a0fc7c8c..c1daf80d8 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -10,26 +10,14 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" - integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== - dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/code-frame@^7.22.5": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== dependencies: "@babel/highlight" "^7.22.5" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.0": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.3.tgz#cd502a6a0b6e37d7ad72ce7e71a7160a3ae36f7e" - integrity sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ== - -"@babel/compat-data@^7.22.5": +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.5.tgz#b1f6c86a02d85d2dd3368a2b67c09add8cd0c255" integrity sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA== @@ -56,20 +44,20 @@ semver "^6.3.0" "@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.1.tgz#5de51c5206f4c6f5533562838337a603c1033cfd" - integrity sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA== + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.5.tgz#d67d9747ecf26ee7ecd3ebae1ee22225fe902a89" + integrity sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.22.0" - "@babel/helper-compilation-targets" "^7.22.1" - "@babel/helper-module-transforms" "^7.22.1" - "@babel/helpers" "^7.22.0" - "@babel/parser" "^7.22.0" - "@babel/template" "^7.21.9" - "@babel/traverse" "^7.22.1" - "@babel/types" "^7.22.0" + "@babel/code-frame" "^7.22.5" + "@babel/generator" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helpers" "^7.22.5" + "@babel/parser" "^7.22.5" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.5" + "@babel/types" "^7.22.5" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -85,17 +73,7 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/generator@^7.18.10", "@babel/generator@^7.22.0", "@babel/generator@^7.22.3", "@babel/generator@^7.7.2": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.3.tgz#0ff675d2edb93d7596c5f6728b52615cfc0df01e" - integrity sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A== - dependencies: - "@babel/types" "^7.22.3" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/generator@^7.22.5": +"@babel/generator@^7.18.10", "@babel/generator@^7.22.5", "@babel/generator@^7.7.2": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.5.tgz#1e7bf768688acfb05cf30b2369ef855e82d984f7" integrity sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA== @@ -105,27 +83,13 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" - integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-annotate-as-pure@^7.22.5": +"@babel/helper-annotate-as-pure@^7.18.6", "@babel/helper-annotate-as-pure@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== dependencies: "@babel/types" "^7.22.5" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.3.tgz#c9b83d1ba74e163e023f008a3d3204588a7ceb60" - integrity sha512-ahEoxgqNoYXm0k22TvOke48i1PkavGu0qGCmcq9ugi6gnmvKNaMjKBSrZTnWUi1CFEeNAUiVba0Wtzm03aSkJg== - dependencies: - "@babel/types" "^7.22.3" - "@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz#a3f4758efdd0190d8927fcffd261755937c71878" @@ -133,18 +97,7 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz#bfcd6b7321ffebe33290d68550e2c9d7eb7c7a58" - integrity sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ== - dependencies: - "@babel/compat-data" "^7.22.0" - "@babel/helper-validator-option" "^7.21.0" - browserslist "^4.21.3" - lru-cache "^5.1.1" - semver "^6.3.0" - -"@babel/helper-compilation-targets@^7.22.5": +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz#fc7319fc54c5e2fa14b2909cf3c5fd3046813e02" integrity sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw== @@ -155,22 +108,7 @@ lru-cache "^5.1.1" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.1.tgz#ae3de70586cc757082ae3eba57240d42f468c41b" - integrity sha512-SowrZ9BWzYFgzUMwUmowbPSGu6CXL5MSuuCkG3bejahSpSymioPmuLdhPxNOc9MjuNGjy7M/HaXvJ8G82Lywlw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.22.1" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-member-expression-to-functions" "^7.22.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.22.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/helper-split-export-declaration" "^7.18.6" - semver "^6.3.0" - -"@babel/helper-create-class-features-plugin@^7.22.5": +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz#2192a1970ece4685fbff85b48da2c32fcb130b7c" integrity sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q== @@ -185,16 +123,7 @@ "@babel/helper-split-export-declaration" "^7.22.5" semver "^6.3.0" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.1.tgz#a7ed9a8488b45b467fca353cd1a44dc5f0cf5c70" - integrity sha512-WWjdnfR3LPIe+0EY8td7WmjhytxXtjKAEpnAxun/hkNiyOaPlvGK+NZaBFIdi9ndYV3Gav7BpFvtUwnaJlwi1w== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - regexpu-core "^5.3.1" - semver "^6.3.0" - -"@babel/helper-create-regexp-features-plugin@^7.22.5": +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz#bb2bf0debfe39b831986a4efbf4066586819c6e4" integrity sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A== @@ -227,25 +156,12 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz#ac3a56dbada59ed969d712cf527bd8271fe3eba8" - integrity sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA== - -"@babel/helper-environment-visitor@^7.22.5": +"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" - integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== - dependencies: - "@babel/template" "^7.20.7" - "@babel/types" "^7.21.0" - -"@babel/helper-function-name@^7.22.5": +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== @@ -253,27 +169,13 @@ "@babel/template" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-hoist-variables@^7.22.5": +"@babel/helper-hoist-variables@^7.18.6", "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== dependencies: "@babel/types" "^7.22.5" -"@babel/helper-member-expression-to-functions@^7.22.0": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.3.tgz#4b77a12c1b4b8e9e28736ed47d8b91f00976911f" - integrity sha512-Gl7sK04b/2WOb6OPVeNy9eFKeD3L6++CzL3ykPOWqTn08xgYYK0wz4TUh2feIImDXxcVW3/9WQ1NMKY66/jfZA== - dependencies: - "@babel/types" "^7.22.3" - "@babel/helper-member-expression-to-functions@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz#0a7c56117cad3372fbf8d2fb4bf8f8d64a1e76b2" @@ -281,35 +183,14 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af" - integrity sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg== - dependencies: - "@babel/types" "^7.21.4" - -"@babel/helper-module-imports@^7.22.5": +"@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== dependencies: "@babel/types" "^7.22.5" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5", "@babel/helper-module-transforms@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.1.tgz#e0cad47fedcf3cae83c11021696376e2d5a50c63" - integrity sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw== - dependencies: - "@babel/helper-environment-visitor" "^7.22.1" - "@babel/helper-module-imports" "^7.21.4" - "@babel/helper-simple-access" "^7.21.5" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.21.9" - "@babel/traverse" "^7.22.1" - "@babel/types" "^7.22.0" - -"@babel/helper-module-transforms@^7.22.5": +"@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz#0f65daa0716961b6e96b164034e737f60a80d2ef" integrity sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw== @@ -323,13 +204,6 @@ "@babel/traverse" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/helper-optimise-call-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" - integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== - dependencies: - "@babel/types" "^7.18.6" - "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" @@ -337,27 +211,12 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.21.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56" - integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg== - -"@babel/helper-plugin-utils@^7.22.5": +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== -"@babel/helper-remap-async-to-generator@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" - integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-wrap-function" "^7.18.9" - "@babel/types" "^7.18.9" - -"@babel/helper-remap-async-to-generator@^7.22.5": +"@babel/helper-remap-async-to-generator@^7.18.9", "@babel/helper-remap-async-to-generator@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz#14a38141a7bf2165ad38da61d61cf27b43015da2" integrity sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g== @@ -367,18 +226,6 @@ "@babel/helper-wrap-function" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7", "@babel/helper-replace-supers@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.1.tgz#38cf6e56f7dc614af63a21b45565dd623f0fdc95" - integrity sha512-ut4qrkE4AuSfrwHSps51ekR1ZY/ygrP1tp0WFm8oVq6nzc/hvfV/22JylndIbsf2U2M9LOMwiSddr6y+78j+OQ== - dependencies: - "@babel/helper-environment-visitor" "^7.22.1" - "@babel/helper-member-expression-to-functions" "^7.22.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/template" "^7.21.9" - "@babel/traverse" "^7.22.1" - "@babel/types" "^7.22.0" - "@babel/helper-replace-supers@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz#71bc5fb348856dea9fdc4eafd7e2e49f585145dc" @@ -391,13 +238,6 @@ "@babel/traverse" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/helper-simple-access@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee" - integrity sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg== - dependencies: - "@babel/types" "^7.21.5" - "@babel/helper-simple-access@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" @@ -405,74 +245,35 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-skip-transparent-expression-wrappers@^7.20.0": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" - integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== - dependencies: - "@babel/types" "^7.20.0" - -"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": +"@babel/helper-skip-transparent-expression-wrappers@^7.20.0", "@babel/helper-skip-transparent-expression-wrappers@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== dependencies: "@babel/types" "^7.22.5" -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-split-export-declaration@^7.22.5": +"@babel/helper-split-export-declaration@^7.18.6", "@babel/helper-split-export-declaration@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz#88cf11050edb95ed08d596f7a044462189127a08" integrity sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ== dependencies: "@babel/types" "^7.22.5" -"@babel/helper-string-parser@^7.18.10", "@babel/helper-string-parser@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" - integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== - -"@babel/helper-string-parser@^7.22.5": +"@babel/helper-string-parser@^7.18.10", "@babel/helper-string-parser@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== - -"@babel/helper-validator-identifier@^7.22.5": +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== -"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" - integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== - -"@babel/helper-validator-option@^7.22.5": +"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== -"@babel/helper-wrap-function@^7.18.9": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz#75e2d84d499a0ab3b31c33bcfe59d6b8a45f62e3" - integrity sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q== - dependencies: - "@babel/helper-function-name" "^7.19.0" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.5" - "@babel/types" "^7.20.5" - "@babel/helper-wrap-function@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz#44d205af19ed8d872b4eefb0d2fa65f45eb34f06" @@ -483,23 +284,14 @@ "@babel/traverse" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/helpers@^7.18.9", "@babel/helpers@^7.22.0": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.3.tgz#53b74351da9684ea2f694bf0877998da26dd830e" - integrity sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w== - dependencies: - "@babel/template" "^7.21.9" - "@babel/traverse" "^7.22.1" - "@babel/types" "^7.22.3" - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== +"@babel/helpers@^7.18.9", "@babel/helpers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.5.tgz#74bb4373eb390d1ceed74a15ef97767e63120820" + integrity sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q== dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.5" + "@babel/types" "^7.22.5" "@babel/highlight@^7.22.5": version "7.22.5" @@ -515,40 +307,19 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.20.7", "@babel/parser@^7.21.9", "@babel/parser@^7.22.0", "@babel/parser@^7.22.4": - version "7.22.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.4.tgz#a770e98fd785c231af9d93f6459d36770993fb32" - integrity sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA== - -"@babel/parser@^7.22.5": +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.5.tgz#721fd042f3ce1896238cf1b341c77eb7dee7dbea" integrity sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q== -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" - integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e" integrity sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.3.tgz#a75be1365c0c3188c51399a662168c1c98108659" - integrity sha512-6r4yRwEnorYByILoDRnEqxtojYKuiIv9FojW2E8GUKo9eWBwbKcd9IiZOZpdyXc64RmyGGyPu3/uAcrz/dq2kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/plugin-transform-optional-chaining" "^7.22.3" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.5": +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz#fef09f9499b1f1c930da8a0c419db42167d792ca" integrity sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g== @@ -693,9 +464,9 @@ integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== "@babel/plugin-proposal-private-property-in-object@^7.18.6": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz#19496bd9883dd83c23c7d7fc45dcd9ad02dfa1dc" - integrity sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw== + version "7.21.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz#69d597086b6760c4126525cfa154f34631ff272c" + integrity sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-create-class-features-plugin" "^7.21.0" @@ -746,11 +517,11 @@ "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-export-default-from@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz#8df076711a4818c4ce4f23e61d622b0ba2ff84bc" - integrity sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew== + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.22.5.tgz#ac3a24b362a04415a017ab96b9b4483d0e2a6e44" + integrity sha512-ODAqWWXB/yReh/jVQDag/3/tl6lgBueQkk/TcfW/59Oykm4c8a55XloX0CTk2k2VJiFWMgHby9xNX29IbCv9dQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-namespace-from@^7.8.3": version "7.8.3" @@ -759,14 +530,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-import-assertions@^7.18.6": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" - integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== - dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - -"@babel/plugin-syntax-import-assertions@^7.22.5": +"@babel/plugin-syntax-import-assertions@^7.18.6", "@babel/plugin-syntax-import-assertions@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz#07d252e2aa0bc6125567f742cd58619cb14dce98" integrity sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg== @@ -794,14 +558,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.21.4", "@babel/plugin-syntax-jsx@^7.7.2": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz#f264ed7bf40ffc9ec239edabc17a50c4f5b6fea2" - integrity sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-syntax-jsx@^7.22.5": +"@babel/plugin-syntax-jsx@^7.22.5", "@babel/plugin-syntax-jsx@^7.7.2": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== @@ -864,14 +621,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.21.4", "@babel/plugin-syntax-typescript@^7.7.2": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz#2751948e9b7c6d771a8efa59340c15d4a2891ff8" - integrity sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-syntax-typescript@^7.22.5": +"@babel/plugin-syntax-typescript@^7.22.5", "@babel/plugin-syntax-typescript@^7.7.2": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== @@ -886,14 +636,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-arrow-functions@^7.18.6": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929" - integrity sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-arrow-functions@^7.22.5": +"@babel/plugin-transform-arrow-functions@^7.18.6", "@babel/plugin-transform-arrow-functions@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958" integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw== @@ -910,16 +653,7 @@ "@babel/helper-remap-async-to-generator" "^7.22.5" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-transform-async-to-generator@^7.18.6": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354" - integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q== - dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-remap-async-to-generator" "^7.18.9" - -"@babel/plugin-transform-async-to-generator@^7.22.5": +"@babel/plugin-transform-async-to-generator@^7.18.6", "@babel/plugin-transform-async-to-generator@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775" integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ== @@ -928,28 +662,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-remap-async-to-generator" "^7.22.5" -"@babel/plugin-transform-block-scoped-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" - integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-block-scoped-functions@^7.22.5": +"@babel/plugin-transform-block-scoped-functions@^7.18.6", "@babel/plugin-transform-block-scoped-functions@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024" integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-block-scoping@^7.18.9": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz#e737b91037e5186ee16b76e7ae093358a5634f02" - integrity sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-block-scoping@^7.22.5": +"@babel/plugin-transform-block-scoping@^7.18.9", "@babel/plugin-transform-block-scoping@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz#8bfc793b3a4b2742c0983fadc1480d843ecea31b" integrity sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg== @@ -973,22 +693,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-transform-classes@^7.18.9": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665" - integrity sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-replace-supers" "^7.20.7" - "@babel/helper-split-export-declaration" "^7.18.6" - globals "^11.1.0" - -"@babel/plugin-transform-classes@^7.22.5": +"@babel/plugin-transform-classes@^7.18.9", "@babel/plugin-transform-classes@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz#635d4e98da741fad814984639f4c0149eb0135e1" integrity sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ== @@ -1003,15 +708,7 @@ "@babel/helper-split-export-declaration" "^7.22.5" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.18.9": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz#3a2d8bb771cd2ef1cd736435f6552fe502e11b44" - integrity sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/template" "^7.20.7" - -"@babel/plugin-transform-computed-properties@^7.22.5": +"@babel/plugin-transform-computed-properties@^7.18.9", "@babel/plugin-transform-computed-properties@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869" integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg== @@ -1019,29 +716,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/template" "^7.22.5" -"@babel/plugin-transform-destructuring@^7.18.9": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz#73b46d0fd11cd6ef57dea8a381b1215f4959d401" - integrity sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-destructuring@^7.22.5": +"@babel/plugin-transform-destructuring@^7.18.9", "@babel/plugin-transform-destructuring@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz#d3aca7438f6c26c78cdd0b0ba920a336001b27cc" integrity sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" - integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-dotall-regex@^7.22.5": +"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.22.5", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165" integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw== @@ -1049,14 +731,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-duplicate-keys@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" - integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-duplicate-keys@^7.22.5": +"@babel/plugin-transform-duplicate-keys@^7.18.9", "@babel/plugin-transform-duplicate-keys@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285" integrity sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw== @@ -1071,15 +746,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-transform-exponentiation-operator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" - integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-exponentiation-operator@^7.22.5": +"@babel/plugin-transform-exponentiation-operator@^7.18.6", "@babel/plugin-transform-exponentiation-operator@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a" integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g== @@ -1095,30 +762,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-transform-for-of@^7.18.8": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc" - integrity sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-for-of@^7.22.5": +"@babel/plugin-transform-for-of@^7.18.8", "@babel/plugin-transform-for-of@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz#ab1b8a200a8f990137aff9a084f8de4099ab173f" integrity sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-function-name@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" - integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== - dependencies: - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-function-name@^7.22.5": +"@babel/plugin-transform-function-name@^7.18.9", "@babel/plugin-transform-function-name@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143" integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg== @@ -1135,14 +786,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-transform-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" - integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-literals@^7.22.5": +"@babel/plugin-transform-literals@^7.18.9", "@babel/plugin-transform-literals@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920" integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g== @@ -1157,29 +801,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-transform-member-expression-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" - integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-member-expression-literals@^7.22.5": +"@babel/plugin-transform-member-expression-literals@^7.18.6", "@babel/plugin-transform-member-expression-literals@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def" integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-modules-amd@^7.18.6": - version "7.20.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz#3daccca8e4cc309f03c3a0c4b41dc4b26f55214a" - integrity sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g== - dependencies: - "@babel/helper-module-transforms" "^7.20.11" - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-modules-amd@^7.22.5": +"@babel/plugin-transform-modules-amd@^7.18.6", "@babel/plugin-transform-modules-amd@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz#4e045f55dcf98afd00f85691a68fc0780704f526" integrity sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ== @@ -1187,16 +816,7 @@ "@babel/helper-module-transforms" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-modules-commonjs@^7.18.6", "@babel/plugin-transform-modules-commonjs@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz#d69fb947eed51af91de82e4708f676864e5e47bc" - integrity sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ== - dependencies: - "@babel/helper-module-transforms" "^7.21.5" - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/helper-simple-access" "^7.21.5" - -"@babel/plugin-transform-modules-commonjs@^7.22.5": +"@babel/plugin-transform-modules-commonjs@^7.18.6", "@babel/plugin-transform-modules-commonjs@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz#7d9875908d19b8c0536085af7b053fd5bd651bfa" integrity sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA== @@ -1205,17 +825,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" -"@babel/plugin-transform-modules-systemjs@^7.18.9": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.3.tgz#cc507e03e88d87b016feaeb5dae941e6ef50d91e" - integrity sha512-V21W3bKLxO3ZjcBJZ8biSvo5gQ85uIXW2vJfh7JSWf/4SLUSr1tOoHX3ruN4+Oqa2m+BKfsxTR1I+PsvkIWvNw== - dependencies: - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/helper-validator-identifier" "^7.19.1" - -"@babel/plugin-transform-modules-systemjs@^7.22.5": +"@babel/plugin-transform-modules-systemjs@^7.18.9", "@babel/plugin-transform-modules-systemjs@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz#18c31410b5e579a0092638f95c896c2a98a5d496" integrity sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ== @@ -1225,15 +835,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-identifier" "^7.22.5" -"@babel/plugin-transform-modules-umd@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" - integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== - dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-modules-umd@^7.22.5": +"@babel/plugin-transform-modules-umd@^7.18.6", "@babel/plugin-transform-modules-umd@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98" integrity sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ== @@ -1241,15 +843,7 @@ "@babel/helper-module-transforms" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.3.tgz#db6fb77e6b3b53ec3b8d370246f0b7cf67d35ab4" - integrity sha512-c6HrD/LpUdNNJsISQZpds3TXvfYIAbo+efE9aWmY/PmSRD0agrJ9cPMt4BmArwUQ7ZymEWTFjTyp+yReLJZh0Q== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": +"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6", "@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== @@ -1257,14 +851,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-new-target@^7.18.6": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.3.tgz#deb0377d741cbee2f45305868b9026dcd6dd96e2" - integrity sha512-5RuJdSo89wKdkRTqtM9RVVJzHum9c2s0te9rB7vZC1zKKxcioWIy+xcu4OoIAjyFZhb/bp5KkunuLin1q7Ct+w== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-new-target@^7.22.5": +"@babel/plugin-transform-new-target@^7.18.6", "@babel/plugin-transform-new-target@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d" integrity sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw== @@ -1298,15 +885,7 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.22.5" -"@babel/plugin-transform-object-super@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" - integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.6" - -"@babel/plugin-transform-object-super@^7.22.5": +"@babel/plugin-transform-object-super@^7.18.6", "@babel/plugin-transform-object-super@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c" integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw== @@ -1322,15 +901,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-transform-optional-chaining@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.3.tgz#5fd24a4a7843b76da6aeec23c7f551da5d365290" - integrity sha512-63v3/UFFxhPKT8j8u1jTTGVyITxl7/7AfOqK8C5gz1rHURPUGe3y5mvIf68eYKGoBNahtJnTxBKug4BQOnzeJg== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-transform-optional-chaining@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz#1003762b9c14295501beb41be72426736bedd1e0" @@ -1340,14 +910,7 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-transform-parameters@^7.18.8", "@babel/plugin-transform-parameters@^7.20.7": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.3.tgz#24477acfd2fd2bc901df906c9bf17fbcfeee900d" - integrity sha512-x7QHQJHPuD9VmfpzboyGJ5aHEr9r7DsAsdxdhJiTB3J3j8dyl+NFZ+rX5Q2RWFDCs61c06qBfS4ys2QYn8UkMw== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-parameters@^7.22.5": +"@babel/plugin-transform-parameters@^7.18.8", "@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz#c3542dd3c39b42c8069936e48717a8d179d63a18" integrity sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg== @@ -1372,29 +935,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" -"@babel/plugin-transform-property-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" - integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-property-literals@^7.22.5": +"@babel/plugin-transform-property-literals@^7.18.6", "@babel/plugin-transform-property-literals@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766" integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-regenerator@^7.18.6": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz#576c62f9923f94bcb1c855adc53561fd7913724e" - integrity sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - regenerator-transform "^0.15.1" - -"@babel/plugin-transform-regenerator@^7.22.5": +"@babel/plugin-transform-regenerator@^7.18.6", "@babel/plugin-transform-regenerator@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz#cd8a68b228a5f75fa01420e8cc2fc400f0fc32aa" integrity sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw== @@ -1402,14 +950,7 @@ "@babel/helper-plugin-utils" "^7.22.5" regenerator-transform "^0.15.1" -"@babel/plugin-transform-reserved-words@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" - integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-reserved-words@^7.22.5": +"@babel/plugin-transform-reserved-words@^7.18.6", "@babel/plugin-transform-reserved-words@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb" integrity sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA== @@ -1428,29 +969,14 @@ babel-plugin-polyfill-regenerator "^0.4.0" semver "^6.3.0" -"@babel/plugin-transform-shorthand-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" - integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-shorthand-properties@^7.22.5": +"@babel/plugin-transform-shorthand-properties@^7.18.6", "@babel/plugin-transform-shorthand-properties@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624" integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-spread@^7.18.9": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz#c2d83e0b99d3bf83e07b11995ee24bf7ca09401e" - integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - -"@babel/plugin-transform-spread@^7.22.5": +"@babel/plugin-transform-spread@^7.18.9", "@babel/plugin-transform-spread@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b" integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg== @@ -1458,58 +984,27 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" -"@babel/plugin-transform-sticky-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" - integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-sticky-regex@^7.22.5": +"@babel/plugin-transform-sticky-regex@^7.18.6", "@babel/plugin-transform-sticky-regex@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa" integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-template-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" - integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-template-literals@^7.22.5": +"@babel/plugin-transform-template-literals@^7.18.9", "@babel/plugin-transform-template-literals@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff" integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-typeof-symbol@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" - integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-typeof-symbol@^7.22.5": +"@babel/plugin-transform-typeof-symbol@^7.18.9", "@babel/plugin-transform-typeof-symbol@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34" integrity sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-typescript@^7.21.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.3.tgz#8f662cec8ba88c873f1c7663c0c94e3f68592f09" - integrity sha512-pyjnCIniO5PNaEuGxT28h0HbMru3qCVrMqVgVOz/krComdIrY9W6FCLBq9NWHY8HDGaUlan+UhmZElDENIfCcw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/plugin-syntax-typescript" "^7.21.4" - "@babel/plugin-transform-typescript@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.5.tgz#5c0f7adfc1b5f38c4dbc8f79b1f0f8074134bd7d" @@ -1520,14 +1015,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-typescript" "^7.22.5" -"@babel/plugin-transform-unicode-escapes@^7.18.10": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz#1e55ed6195259b0e9061d81f5ef45a9b009fb7f2" - integrity sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-unicode-escapes@^7.22.5": +"@babel/plugin-transform-unicode-escapes@^7.18.10", "@babel/plugin-transform-unicode-escapes@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz#ce0c248522b1cb22c7c992d88301a5ead70e806c" integrity sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg== @@ -1542,15 +1030,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-unicode-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" - integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-unicode-regex@^7.22.5": +"@babel/plugin-transform-unicode-regex@^7.18.6", "@babel/plugin-transform-unicode-regex@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183" integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg== @@ -1744,18 +1224,7 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-typescript@^7.18.6": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.5.tgz#68292c884b0e26070b4d66b202072d391358395f" - integrity sha512-iqe3sETat5EOrORXiQ6rWfoOg2y68Cs75B9wNxdPW4kixJxh7aXQE1KPdWLDniC24T/6dSnguF33W9j/ZZQcmA== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/helper-validator-option" "^7.21.0" - "@babel/plugin-syntax-jsx" "^7.21.4" - "@babel/plugin-transform-modules-commonjs" "^7.21.5" - "@babel/plugin-transform-typescript" "^7.21.3" - -"@babel/preset-typescript@^7.22.5": +"@babel/preset-typescript@^7.18.6", "@babel/preset-typescript@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz#16367d8b01d640e9a507577ed4ee54e0101e51c8" integrity sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ== @@ -1772,22 +1241,13 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.11.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.8.4": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb" - integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ== + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec" + integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== dependencies: regenerator-runtime "^0.13.11" -"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.21.9", "@babel/template@^7.3.3": - version "7.21.9" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.21.9.tgz#bf8dad2859130ae46088a99c1f265394877446fb" - integrity sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ== - dependencies: - "@babel/code-frame" "^7.21.4" - "@babel/parser" "^7.21.9" - "@babel/types" "^7.21.5" - -"@babel/template@^7.22.5": +"@babel/template@^7.18.10", "@babel/template@^7.22.5", "@babel/template@^7.3.3": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== @@ -1812,23 +1272,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.18.10", "@babel/traverse@^7.20.5", "@babel/traverse@^7.22.1", "@babel/traverse@^7.7.2": - version "7.22.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.4.tgz#c3cf96c5c290bd13b55e29d025274057727664c0" - integrity sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ== - dependencies: - "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.22.3" - "@babel/helper-environment-visitor" "^7.22.1" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.22.4" - "@babel/types" "^7.22.4" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.22.5": +"@babel/traverse@^7.18.10", "@babel/traverse@^7.22.5", "@babel/traverse@^7.7.2": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.5.tgz#44bd276690db6f4940fdb84e1cb4abd2f729ccd1" integrity sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ== @@ -1853,16 +1297,7 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3", "@babel/types@^7.22.4", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.22.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.4.tgz#56a2653ae7e7591365dabf20b76295410684c071" - integrity sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA== - dependencies: - "@babel/helper-string-parser" "^7.21.5" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - -"@babel/types@^7.22.5": +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== @@ -2077,10 +1512,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.42.0": - version "8.42.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6" - integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw== +"@eslint/js@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.43.0.tgz#559ca3d9ddbd6bf907ad524320a0d14b85586af0" + integrity sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg== "@humanwhocodes/config-array@^0.11.10": version "0.11.10" @@ -2404,9 +1839,9 @@ integrity sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw== "@noble/hashes@^1", "@noble/hashes@^1.0.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" - integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== + version "1.3.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" + integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -2562,9 +1997,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.0.tgz#4709d34d3eba3e1dad1950d40e80c6b5e0b81fc9" - integrity sha512-TBOjqAGf0hmaqRwpii5LLkJLg7c6OMm4nHLmpsUxwk9bBHtoTC6dAHdVWdGv4TBxj2CZOZY8Xfq8WmfoVi7n4Q== + version "7.20.1" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.1.tgz#dd6f1d2411ae677dcb2db008c962598be31d6acf" + integrity sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg== dependencies: "@babel/types" "^7.20.7" @@ -2631,14 +2066,14 @@ integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/node@*", "@types/node@>=13.7.0": - version "20.2.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.2.5.tgz#26d295f3570323b2837d322180dfbf1ba156fefb" - integrity sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ== + version "20.3.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe" + integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg== "@types/prettier@^2.1.5", "@types/prettier@^2.6.1": - version "2.7.2" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" - integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== + version "2.7.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" + integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== "@types/semver@^7.3.12": version "7.5.0" @@ -2663,14 +2098,14 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^5.59.9": - version "5.59.9" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.9.tgz#2604cfaf2b306e120044f901e20c8ed926debf15" - integrity sha512-4uQIBq1ffXd2YvF7MAvehWKW3zVv/w+mSfRAu+8cKbfj3nwzyqJLNcZJpQ/WZ1HLbJDiowwmQ6NO+63nCA+fqA== + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz#8d466aa21abea4c3f37129997b198d141f09e76f" + integrity sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.59.9" - "@typescript-eslint/type-utils" "5.59.9" - "@typescript-eslint/utils" "5.59.9" + "@typescript-eslint/scope-manager" "5.59.11" + "@typescript-eslint/type-utils" "5.59.11" + "@typescript-eslint/utils" "5.59.11" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -2679,71 +2114,71 @@ tsutils "^3.21.0" "@typescript-eslint/parser@^5.59.9": - version "5.59.9" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.9.tgz#a85c47ccdd7e285697463da15200f9a8561dd5fa" - integrity sha512-FsPkRvBtcLQ/eVK1ivDiNYBjn3TGJdXy2fhXX+rc7czWl4ARwnpArwbihSOHI2Peg9WbtGHrbThfBUkZZGTtvQ== + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.11.tgz#af7d4b7110e3068ce0b97550736de455e4250103" + integrity sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA== dependencies: - "@typescript-eslint/scope-manager" "5.59.9" - "@typescript-eslint/types" "5.59.9" - "@typescript-eslint/typescript-estree" "5.59.9" + "@typescript-eslint/scope-manager" "5.59.11" + "@typescript-eslint/types" "5.59.11" + "@typescript-eslint/typescript-estree" "5.59.11" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.59.9": - version "5.59.9" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.9.tgz#eadce1f2733389cdb58c49770192c0f95470d2f4" - integrity sha512-8RA+E+w78z1+2dzvK/tGZ2cpGigBZ58VMEHDZtpE1v+LLjzrYGc8mMaTONSxKyEkz3IuXFM0IqYiGHlCsmlZxQ== +"@typescript-eslint/scope-manager@5.59.11": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz#5d131a67a19189c42598af9fb2ea1165252001ce" + integrity sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q== dependencies: - "@typescript-eslint/types" "5.59.9" - "@typescript-eslint/visitor-keys" "5.59.9" + "@typescript-eslint/types" "5.59.11" + "@typescript-eslint/visitor-keys" "5.59.11" -"@typescript-eslint/type-utils@5.59.9": - version "5.59.9" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.9.tgz#53bfaae2e901e6ac637ab0536d1754dfef4dafc2" - integrity sha512-ksEsT0/mEHg9e3qZu98AlSrONAQtrSTljL3ow9CGej8eRo7pe+yaC/mvTjptp23Xo/xIf2mLZKC6KPv4Sji26Q== +"@typescript-eslint/type-utils@5.59.11": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz#5eb67121808a84cb57d65a15f48f5bdda25f2346" + integrity sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g== dependencies: - "@typescript-eslint/typescript-estree" "5.59.9" - "@typescript-eslint/utils" "5.59.9" + "@typescript-eslint/typescript-estree" "5.59.11" + "@typescript-eslint/utils" "5.59.11" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.59.9": - version "5.59.9" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.9.tgz#3b4e7ae63718ce1b966e0ae620adc4099a6dcc52" - integrity sha512-uW8H5NRgTVneSVTfiCVffBb8AbwWSKg7qcA4Ot3JI3MPCJGsB4Db4BhvAODIIYE5mNj7Q+VJkK7JxmRhk2Lyjw== +"@typescript-eslint/types@5.59.11": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.11.tgz#1a9018fe3c565ba6969561f2a49f330cf1fe8db1" + integrity sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA== -"@typescript-eslint/typescript-estree@5.59.9": - version "5.59.9" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.9.tgz#6bfea844e468427b5e72034d33c9fffc9557392b" - integrity sha512-pmM0/VQ7kUhd1QyIxgS+aRvMgw+ZljB3eDb+jYyp6d2bC0mQWLzUDF+DLwCTkQ3tlNyVsvZRXjFyV0LkU/aXjA== +"@typescript-eslint/typescript-estree@5.59.11": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz#b2caaa31725e17c33970c1197bcd54e3c5f42b9f" + integrity sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA== dependencies: - "@typescript-eslint/types" "5.59.9" - "@typescript-eslint/visitor-keys" "5.59.9" + "@typescript-eslint/types" "5.59.11" + "@typescript-eslint/visitor-keys" "5.59.11" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.59.9": - version "5.59.9" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.9.tgz#adee890107b5ffe02cd46fdaa6c2125fb3c6c7c4" - integrity sha512-1PuMYsju/38I5Ggblaeb98TOoUvjhRvLpLa1DoTOFaLWqaXl/1iQ1eGurTXgBY58NUdtfTXKP5xBq7q9NDaLKg== +"@typescript-eslint/utils@5.59.11": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.11.tgz#9dbff49dc80bfdd9289f9f33548f2e8db3c59ba1" + integrity sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.59.9" - "@typescript-eslint/types" "5.59.9" - "@typescript-eslint/typescript-estree" "5.59.9" + "@typescript-eslint/scope-manager" "5.59.11" + "@typescript-eslint/types" "5.59.11" + "@typescript-eslint/typescript-estree" "5.59.11" eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.59.9": - version "5.59.9" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.9.tgz#9f86ef8e95aca30fb5a705bb7430f95fc58b146d" - integrity sha512-bT7s0td97KMaLwpEBckbzj/YohnvXtqbe2XgqNvTl6RJVakY5mvENOTPvw5u66nljfZxthESpDozs86U+oLY8Q== +"@typescript-eslint/visitor-keys@5.59.11": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz#dca561ddad169dc27d62396d64f45b2d2c3ecc56" + integrity sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA== dependencies: - "@typescript-eslint/types" "5.59.9" + "@typescript-eslint/types" "5.59.11" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -2752,9 +2187,9 @@ acorn-jsx@^5.3.2: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^8.8.0: - version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + version "8.9.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59" + integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ== ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" @@ -3048,12 +2483,12 @@ brorand@^1.1.0: integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== browserslist@^4.21.3, browserslist@^4.21.5: - version "4.21.7" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.7.tgz#e2b420947e5fb0a58e8f4668ae6e23488127e551" - integrity sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA== + version "4.21.9" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635" + integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg== dependencies: - caniuse-lite "^1.0.30001489" - electron-to-chromium "^1.4.411" + caniuse-lite "^1.0.30001503" + electron-to-chromium "^1.4.431" node-releases "^2.0.12" update-browserslist-db "^1.0.11" @@ -3089,10 +2524,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001489: - version "1.0.30001491" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001491.tgz#eab0e0f392de6f7411751d148de9b5bd6b203e46" - integrity sha512-17EYIi4TLnPiTzVKMveIxU5ETlxbSO3B6iPvMbprqnKh4qJsQGk5Nh1Lp4jIMAE0XfrujsJuWZAM3oJdMHaKBA== +caniuse-lite@^1.0.30001503: + version "1.0.30001504" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001504.tgz#eaf77e5c852dfa5f82c4924468c30602ac53744a" + integrity sha512-5uo7eoOp2mKbWyfMXnGO9rJWOGU8duvzEiYITW+wivukL7yHH4gX9yuRaobu6El4jPxo6jKZfG+N6fB621GD/Q== case@1.6.3: version "1.6.3" @@ -3153,9 +2588,9 @@ ci-info@^3.2.0: integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== cjs-module-lexer@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" - integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + version "1.2.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== cli-color@^2.0.2: version "2.0.3" @@ -3266,9 +2701,9 @@ copyfiles@^2.4.1: yargs "^16.1.0" core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.30.1, core-js-compat@^3.30.2: - version "3.30.2" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.2.tgz#83f136e375babdb8c80ad3c22d67c69098c1dd8b" - integrity sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA== + version "3.31.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.0.tgz#4030847c0766cc0e803dcdfb30055d7ef2064bf1" + integrity sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw== dependencies: browserslist "^4.21.5" @@ -3379,10 +2814,10 @@ dotty@0.1.2: resolved "https://registry.yarnpkg.com/dotty/-/dotty-0.1.2.tgz#512d44cc4111a724931226259297f235e8484f6f" integrity sha512-V0EWmKeH3DEhMwAZ+8ZB2Ao4OK6p++Z0hsDtZq3N0+0ZMVqkzrcEGROvOnZpLnvBg5PTNG23JEDLAm64gPaotQ== -electron-to-chromium@^1.4.411: - version "1.4.413" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.413.tgz#0067c3122946ae234cbefb9401ecefde851cdcf2" - integrity sha512-Gd+/OAhRca06dkVxIQo/W7dr6Nmk9cx6lQdZ19GvFp51k5B/lUAokm6SJfNkdV8kFLsC3Z4sLTyEHWCnB1Efbw== +electron-to-chromium@^1.4.431: + version "1.4.433" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.433.tgz#305ef5f8ea5fe65d252aae4b0e1088f9e4842533" + integrity sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ== elliptic@^6.5.4: version "6.5.4" @@ -3497,14 +2932,14 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== eslint@^8.42.0: - version "8.42.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.42.0.tgz#7bebdc3a55f9ed7167251fe7259f75219cade291" - integrity sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A== + version "8.43.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.43.0.tgz#3e8c6066a57097adfd9d390b8fc93075f257a094" + integrity sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" "@eslint/eslintrc" "^2.0.3" - "@eslint/js" "8.42.0" + "@eslint/js" "8.43.0" "@humanwhocodes/config-array" "^0.11.10" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -5345,9 +4780,9 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== semver@^7.3.5, semver@^7.3.7: - version "7.5.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" - integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== + version "7.5.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.2.tgz#5b851e66d1be07c1cdaf37dfc856f543325a2beb" + integrity sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ== dependencies: lru-cache "^6.0.0" @@ -5775,10 +5210,10 @@ wasm-ast-types@^0.23.1: case "1.6.3" deepmerge "4.2.2" -wasm-pack@^0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/wasm-pack/-/wasm-pack-0.11.1.tgz#ca3eb5099b0e9f700ffc3b3f5ec4b956a521e808" - integrity sha512-0BKEioKJY/SMqahDEoaUUR8jrRkHO0cdYhRqqHKQMY3Bac6Eep3ZRsTlpFSSwS4LYPxd+Tb5KFFNhUikCkq8Yg== +wasm-pack@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/wasm-pack/-/wasm-pack-0.12.0.tgz#3c360e14eecbef3c705f23bca2f9df0424f748a9" + integrity sha512-2MuRmArr8x0S6GAQKJtToH2tJ9rM8nJ2yy6J+ueMm2v4deKAh0uAOT2BwJlLoZ0VnELtKBDjJ17NV/RFDt/QpA== dependencies: binary-install "^1.0.1" From 482b23428058642e2a4c03a4165554c76a6ceddf Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 19 Jun 2023 13:00:55 +0200 Subject: [PATCH 167/218] Test-tube to 0.16.0-beta.0 (#144) update v3 to latest pkg --- Cargo.lock | 147 ++++++++-------- Cargo.toml | 14 +- Makefile.toml | 9 +- contracts/v3-zapper/base/src/contract.rs | 8 +- contracts/v3-zapper/base/src/msg.rs | 3 +- .../v3-zapper/osmosis/src/position_manager.rs | 21 +-- .../osmosis/tests/helpers/generator.rs | 3 +- .../osmosis/tests/helpers/mock_env.rs | 118 ++++++------- .../osmosis/tests/test_add_position.rs | 164 ++++++++---------- .../mars-v3-zapper-base.json | 27 +-- .../MarsV3ZapperBase.client.ts | 15 +- .../MarsV3ZapperBase.message-composer.ts | 15 +- .../MarsV3ZapperBase.react-query.ts | 3 +- .../MarsV3ZapperBase.types.ts | 3 +- 14 files changed, 237 insertions(+), 313 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 562b87b0a..aaba1b5cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,7 +41,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fdd3af3b24bd7343c2b74b2bbe66ff53cc52327342e26ed207b3150f324c4a" dependencies = [ "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw20", "schemars", "serde", @@ -141,7 +141,7 @@ dependencies = [ "pbkdf2", "rand_core 0.6.4", "ripemd", - "sha2 0.10.6", + "sha2 0.10.7", "subtle", "zeroize", ] @@ -329,9 +329,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.2.6" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c0e41be7e6c7d7ab3c61cdc32fcfaa14f948491a401cbc1c74bb33b6f4b851" +checksum = "bb64554a91d6a9231127f4355d351130a0b94e663d5d9dc8b3a54ca17d83de49" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -342,18 +342,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.6" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a7ee2798c92c00dd17bebb4210f81d5f647e5e92d847959b7977e0fd29a3500" +checksum = "a0fb2ce09f41a3dae1a234d56a9988f9aff4c76441cd50ef1ee9a4f20415b028" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.6" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "407aca6f1671a08b60db8167f03bb7cb6b2378f0ddd9a030367b66ba33c2fd41" +checksum = "230e5d1cefae5331db8934763c81b9c871db6a2cd899056a5694fa71d292c815" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -364,9 +364,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.6" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d1e00b8fd27ff923c10303023626358e23a6f9079f8ebec23a8b4b0bfcd4b3" +checksum = "43dadf7c23406cb28079d69e6cb922c9c29b9157b0fe887e3b79c783b7d4bcb8" dependencies = [ "proc-macro2", "quote", @@ -375,9 +375,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.6" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d5fdfd112b070055f068fad079d490117c8e905a588b92a5a7c9276d029930" +checksum = "4337eef8dfaf8572fe6b6b415d6ec25f9308c7bb09f2da63789209fb131363be" dependencies = [ "base64", "cosmwasm-crypto", @@ -388,16 +388,16 @@ dependencies = [ "schemars", "serde", "serde-json-wasm", - "sha2 0.10.6", + "sha2 0.10.7", "thiserror", "uint", ] [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" dependencies = [ "libc", ] @@ -471,7 +471,7 @@ dependencies = [ "apollo-utils", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw20", "osmosis-std 0.14.0", @@ -486,7 +486,7 @@ checksum = "127c7bb95853b8e828bdab97065c81cb5ddc20f7339180b61b2300565aaa99d1" dependencies = [ "anyhow", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "derivative", "itertools", @@ -507,7 +507,7 @@ dependencies = [ "cosmwasm-std", "cw-address-like", "cw-ownable-derive", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "thiserror", ] @@ -530,7 +530,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "add278617f6251be1a35c781eb0fbffd44f899d8bb4dc5a9e420273a90684c4e" dependencies = [ "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "serde", ] @@ -547,9 +547,9 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" +checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" dependencies = [ "cosmwasm-std", "schemars", @@ -620,7 +620,7 @@ checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "schemars", "serde", ] @@ -632,7 +632,7 @@ source = "git+https://github.com/mars-protocol/cw-plus?rev=1a3a944#1a3a944b64cf6 dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "schemars", "serde", "thiserror", @@ -701,7 +701,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-ownable", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw2 1.0.1 (git+https://github.com/mars-protocol/cw-plus?rev=1a3a944)", "cw721 0.17.0", @@ -1255,9 +1255,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1271,7 +1271,7 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "sha2 0.10.6", + "sha2 0.10.7", "sha3", ] @@ -1326,7 +1326,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "cw721 0.17.0", "cw721-base 0.16.0", @@ -1346,7 +1346,7 @@ dependencies = [ "cosmwasm-std", "cw-multi-test", "cw-paginate", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard", "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1373,7 +1373,7 @@ version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "mars-rover", "mars-rover-health-types", @@ -1386,7 +1386,7 @@ version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "mars-red-bank-types", ] @@ -1396,7 +1396,7 @@ version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "mars-red-bank-types", ] @@ -1407,7 +1407,7 @@ version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "mars-rover-health-types", ] @@ -1417,7 +1417,7 @@ version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard", "mars-rover", @@ -1442,7 +1442,7 @@ checksum = "acd53908ffc561da878ce5ff4f5ec9f25a193af28ec0b6e7c8e6d1a0866d9dfc" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "schemars", "thiserror", ] @@ -1455,7 +1455,7 @@ checksum = "8edfc032b3aedac656da1f90f5f1bc3b735c5de373b761279ff6b0de90346bd6" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "mars-owner", "mars-utils", @@ -1483,7 +1483,7 @@ version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard", "cw721 0.17.0", @@ -1507,7 +1507,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard", "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1557,7 +1557,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-paginate", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "mars-owner", "mars-rover", "schemars", @@ -1570,7 +1570,7 @@ name = "mars-swapper-mock" version = "2.0.0" dependencies = [ "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "mars-rover", "thiserror", ] @@ -1581,13 +1581,13 @@ version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "mars-osmosis", "mars-owner", "mars-rover", "mars-swapper-base", - "osmosis-std 0.15.3", + "osmosis-std 0.16.0-beta.0", "osmosis-test-tube", "schemars", "thiserror", @@ -1622,7 +1622,7 @@ version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.0.1", + "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "mars-rover", "mars-v2-zapper-base", @@ -1638,7 +1638,7 @@ dependencies = [ "cw-utils 1.0.1", "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "mars-v2-zapper-base", - "osmosis-std 0.15.3", + "osmosis-std 0.16.0-beta.0", "osmosis-test-tube", ] @@ -1663,7 +1663,7 @@ dependencies = [ "cosmwasm-std", "mars-owner", "mars-v3-zapper-base", - "osmosis-std 0.15.3", + "osmosis-std 0.16.0-beta.0", "osmosis-test-tube", ] @@ -1787,13 +1787,13 @@ dependencies = [ [[package]] name = "osmosis-std" -version = "0.15.3" +version = "0.16.0-beta.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87725a7480b98887167edf878daa52201a13322ad88e34355a7f2ddc663e047e" +checksum = "a78f106a02bb4a35a1a7fbdadeeaedba91d25d37f77518eabe381c265511d560" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.15.3", + "osmosis-std-derive 0.16.0-beta.0", "prost 0.11.9", "prost-types", "schemars", @@ -1815,9 +1815,9 @@ dependencies = [ [[package]] name = "osmosis-std-derive" -version = "0.15.3" +version = "0.16.0-beta.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4d482a16be198ee04e0f94e10dd9b8d02332dcf33bc5ea4b255e7e25eedc5df" +checksum = "56d09ac2abf9d9711e15a4df4dadf923b12a3dcc80c4a4a5abb24c36a9500e95" dependencies = [ "itertools", "proc-macro2", @@ -1827,15 +1827,15 @@ dependencies = [ [[package]] name = "osmosis-test-tube" -version = "15.1.0" +version = "16.0.0-beta.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aba931600343de65be9cbf9ed5a98a611bbf1fc9c45994eb980cc5afb18404" +checksum = "949453e4f89ba5175f1f9f885188dabb502022dc3dd249b68471f4d0be6a3e0b" dependencies = [ "base64", "bindgen", "cosmrs", "cosmwasm-std", - "osmosis-std 0.15.3", + "osmosis-std 0.16.0-beta.0", "prost 0.11.9", "serde", "serde_json", @@ -2293,9 +2293,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" dependencies = [ "itoa", "ryu", @@ -2339,9 +2339,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -2565,9 +2565,9 @@ dependencies = [ [[package]] name = "test-tube" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4807f0de0b84340e20a6ef1353a5c9db4543e1c74cd507aedd8b52e35d80020" +checksum = "9ec7aed5905c502fb6be7684a2a9b0d7005ece58f79d4bd66a99c355ee24012c" dependencies = [ "base64", "cosmrs", @@ -2810,11 +2810,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -2826,9 +2825,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2836,9 +2835,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", @@ -2851,9 +2850,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2861,9 +2860,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", @@ -2874,15 +2873,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index d141c398c..945e381d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,8 +37,8 @@ keywords = ["mars", "cosmos", "cosmwasm"] [workspace.dependencies] anyhow = "1.0.71" -cosmwasm-schema = "1.2.6" -cosmwasm-std = "1.2.6" +cosmwasm-schema = "1.2.7" +cosmwasm-std = "1.2.7" cw2 = "1.0.1" cw721 = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main" } cw721-base = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main", features = ["library"] } @@ -46,17 +46,17 @@ cw-dex = { version = "0.2.0", features = ["osmosis"] } cw-multi-test = "0.16.5" cw-paginate = "0.2.1" cw-utils = "1.0.1" -cw-storage-plus = "1.0.1" +cw-storage-plus = "1.1.0" cw-vault-standard = { version = "0.3.0", features = ["lockup", "force-unlock"] } itertools = "0.10.5" -osmosis-std = "0.15.3" -osmosis-test-tube = "15.1.0" +osmosis-std = "0.16.0-beta.0" +osmosis-test-tube = "16.0.0-beta.0" schemars = "0.8.12" serde = { version = "1.0.164", default-features = false, features = ["derive"] } -serde_json = "1.0.96" +serde_json = "1.0.97" serde-wasm-bindgen = "0.5.0" thiserror = "1.0.40" -wasm-bindgen = "0.2.86" +wasm-bindgen = "0.2.87" # mars packages mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "00301d60c38af09d8eb7980355009e2f00c6f41f" } diff --git a/Makefile.toml b/Makefile.toml index e92b24905..44c20ef17 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -12,8 +12,7 @@ ARTIFACTS_DIR_PATH = "artifacts" [tasks.build] command = "cargo" -# TODO: After stable osmosis-test-tube CL version, remove the exclusion -args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked", "--exclude", "mars-v3-zapper-osmosis", "--workspace"] +args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked"] [tasks.rust-optimizer] script = """ @@ -30,8 +29,7 @@ docker run --rm -v "$(pwd)":/code \ [tasks.test] command = "cargo" -# TODO: After stable osmosis-test-tube CL version, remove the exclusion -args = ["test", "--locked", "--exclude", "mars-v3-zapper-osmosis", "--workspace"] +args = ["test", "--locked"] [tasks.fmt] toolchain = "nightly" @@ -40,8 +38,7 @@ args = ["fmt", "--all", "--check"] [tasks.clippy] command = "cargo" -# TODO: After stable osmosis-test-tube CL version, remove the exclusion -args = ["clippy", "--tests", "--exclude", "mars-v3-zapper-osmosis", "--workspace", "--", "-D", "warnings"] +args = ["clippy", "--tests", "--", "-D", "warnings"] [tasks.audit] command = "cargo" diff --git a/contracts/v3-zapper/base/src/contract.rs b/contracts/v3-zapper/base/src/contract.rs index 521439ac7..a46f77e35 100644 --- a/contracts/v3-zapper/base/src/contract.rs +++ b/contracts/v3-zapper/base/src/contract.rs @@ -11,7 +11,7 @@ use crate::{ error::{ContractError, ContractError::Unauthorized, ContractResult}, msg::{CallbackMsg, ExecuteMsg, InstantiateMsg, NewPositionRequest, QueryMsg}, state::OWNER, - traits::{OptionFilter, PositionManager}, + traits::PositionManager, utils::assert_exact_funds_sent, }; @@ -119,9 +119,7 @@ where request: NewPositionRequest, ) -> ContractResult { OWNER.assert_owner(deps.storage, &info.sender)?; - - let request_coins = vec![&request.token_desired0, &request.token_desired1].only_some(); - assert_exact_funds_sent(&info, &request_coins)?; + assert_exact_funds_sent(&info, &request.tokens_provided)?; // Creating positions do not guarantee all funds will be used. Refund the leftovers. let refund_msg = CosmosMsg::Wasm(WasmMsg::Execute { @@ -129,7 +127,7 @@ where funds: vec![], msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::RefundCoin { recipient: info.sender, - denoms: request_coins.iter().map(|c| c.denom.clone()).collect(), + denoms: request.tokens_provided.iter().map(|c| c.denom.clone()).collect(), }))?, }); diff --git a/contracts/v3-zapper/base/src/msg.rs b/contracts/v3-zapper/base/src/msg.rs index 88dd9445f..69778f4f6 100644 --- a/contracts/v3-zapper/base/src/msg.rs +++ b/contracts/v3-zapper/base/src/msg.rs @@ -12,8 +12,7 @@ pub struct NewPositionRequest { pub pool_id: u64, pub lower_tick: i64, pub upper_tick: i64, - pub token_desired0: Option, - pub token_desired1: Option, + pub tokens_provided: Vec, pub token_min_amount0: String, pub token_min_amount1: String, } diff --git a/contracts/v3-zapper/osmosis/src/position_manager.rs b/contracts/v3-zapper/osmosis/src/position_manager.rs index c484f77ff..96297cc53 100644 --- a/contracts/v3-zapper/osmosis/src/position_manager.rs +++ b/contracts/v3-zapper/osmosis/src/position_manager.rs @@ -22,10 +22,9 @@ impl PositionManager for OsmosisPositionManager { sender: env.contract.address.to_string(), lower_tick: p.lower_tick, upper_tick: p.upper_tick, - token_desired0: p.token_desired0.to_v1beta_coin(), - token_desired1: p.token_desired1.to_v1beta_coin(), token_min_amount0: p.token_min_amount0, token_min_amount1: p.token_min_amount1, + tokens_provided: p.tokens_provided.to_v1beta_coins(), }; Ok(create_msg.into()) } @@ -40,15 +39,17 @@ impl PositionManager for OsmosisPositionManager { } } -pub trait ToV1BetaCoin { - fn to_v1beta_coin(&self) -> Option; +pub trait ToV1BetaCoins { + fn to_v1beta_coins(&self) -> Vec; } -impl ToV1BetaCoin for Option { - fn to_v1beta_coin(&self) -> Option { - self.clone().map(|c| v1beta1::Coin { - denom: c.denom, - amount: c.amount.to_string(), - }) +impl ToV1BetaCoins for Vec { + fn to_v1beta_coins(&self) -> Vec { + self.iter() + .map(|c| v1beta1::Coin { + denom: c.denom.clone(), + amount: c.amount.to_string(), + }) + .collect() } } diff --git a/contracts/v3-zapper/osmosis/tests/helpers/generator.rs b/contracts/v3-zapper/osmosis/tests/helpers/generator.rs index 26e515e8a..9fe7e3abb 100644 --- a/contracts/v3-zapper/osmosis/tests/helpers/generator.rs +++ b/contracts/v3-zapper/osmosis/tests/helpers/generator.rs @@ -6,9 +6,8 @@ pub fn default_new_position_req() -> NewPositionRequest { pool_id: 1, lower_tick: -1, upper_tick: 100, - token_desired0: Some(coin(100_000_000, "ujuno")), - token_desired1: Some(coin(100_000_000, "umars")), token_min_amount0: "10000".to_string(), token_min_amount1: "10000".to_string(), + tokens_provided: vec![coin(100_000_000, "ujuno"), coin(100_000_000, "umars")], } } diff --git a/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs b/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs index d802d5a14..a2ec6286b 100644 --- a/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs +++ b/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs @@ -1,23 +1,30 @@ use std::{mem::take, str::FromStr}; use anyhow::Result as AnyResult; -use cosmwasm_std::{coin, Coin}; +use cosmwasm_std::coin; use mars_owner::{OwnerResponse, OwnerUpdate}; use mars_v3_zapper_base::msg::{CallbackMsg, ExecuteMsg, InstantiateMsg, QueryMsg}; use osmosis_std::types::osmosis::{ + concentratedliquidity, concentratedliquidity::v1beta1::{ - MsgCreateConcentratedPool, PositionWithUnderlyingAssetBreakdown, QueryUserPositionsRequest, + CreateConcentratedLiquidityPoolsProposal, FullPositionBreakdown, PoolRecord, PoolsRequest, + UserPositionsRequest, }, - tokenfactory::v1beta1::{MsgCreateDenom, MsgMint}, }; use osmosis_test_tube::{ cosmrs::proto::{ cosmos::bank::v1beta1::QueryBalanceRequest, cosmwasm::wasm::v1::MsgExecuteContractResponse, + prost::Message, }, - Account, Bank, Module, OsmosisTestApp, RunnerExecuteResult, SigningAccount, TokenFactory, Wasm, + Account, Bank, ConcentratedLiquidity, GovWithAppAccess, Module, OsmosisTestApp, + RunnerExecuteResult, SigningAccount, Wasm, }; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +pub const DEFAULT_STARTING_BALANCE: u128 = 1_000_000_000_000; + +pub const ATOM: &str = "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2"; +pub const DAI: &str = "ibc/0CD3A0285E1341859B5E86B6AB7682F023D03E97607CCC1DC95706411D866DF7"; pub struct MockEnv { pub app: OsmosisTestApp, @@ -54,73 +61,38 @@ impl MockEnv { ) } - pub fn create_pool(&mut self, subdenom0: &str, subdenom1: &str) -> (String, String, u64) { + pub fn create_pool(&mut self, denom0: &str, denom1: &str) -> u64 { let cl = ConcentratedLiquidity::new(&self.app); - let token_factory = TokenFactory::new(&self.app); - - let denom0 = token_factory - .create_denom( - MsgCreateDenom { - sender: self.owner.address(), - subdenom: subdenom0.to_string(), - }, - &self.owner, - ) - .unwrap() - .data - .new_token_denom; - - let denom1 = token_factory - .create_denom( - MsgCreateDenom { - sender: self.owner.address(), - subdenom: subdenom1.to_string(), - }, - &self.owner, - ) - .unwrap() - .data - .new_token_denom; - - token_factory - .mint( - MsgMint { - sender: self.owner.address(), - amount: Some(Coin::new(100_000_000_000, &denom0).into()), - mint_to_address: self.owner.address(), - }, - &self.owner, - ) - .unwrap(); - - token_factory - .mint( - MsgMint { - sender: self.owner.address(), - amount: Some(Coin::new(100_000_000_000, &denom1).into()), - mint_to_address: self.owner.address(), - }, - &self.owner, - ) - .unwrap(); - - let pool_id = cl - .create_concentrated_pool( - MsgCreateConcentratedPool { - sender: self.owner.address(), - denom0: denom0.clone(), - denom1: denom1.clone(), + let gov = GovWithAppAccess::new(&self.app); + + gov.propose_and_execute( + CreateConcentratedLiquidityPoolsProposal::TYPE_URL.to_string(), + CreateConcentratedLiquidityPoolsProposal { + title: String::from("test"), + description: String::from("test"), + pool_records: vec![PoolRecord { + denom0: denom0.to_string(), + denom1: denom1.to_string(), tick_spacing: 1, exponent_at_price_one: "-4".to_string(), - swap_fee: "0".to_string(), - }, - &self.owner, - ) + spread_factor: "500000000000000".to_string(), + }], + }, + self.owner.address(), + &self.owner, + ) + .unwrap(); + + let pools = cl + .query_pools(&PoolsRequest { + pagination: None, + }) .unwrap() - .data - .pool_id; + .pools; - (denom0, denom1, pool_id) + concentratedliquidity::v1beta1::Pool::decode(pools.last().unwrap().value.as_slice()) + .unwrap() + .id } pub fn callback( @@ -159,12 +131,13 @@ impl MockEnv { u128::from_str(&str_balance).unwrap() } - pub fn query_positions(&self, pool_id: u64) -> Vec { + pub fn query_positions(&self, pool_id: u64) -> Vec { let cl = ConcentratedLiquidity::new(&self.app); let res = cl - .query_user_positions(&QueryUserPositionsRequest { + .query_user_positions(&UserPositionsRequest { address: self.zapper.clone(), pool_id, + pagination: None, }) .unwrap(); res.positions @@ -173,7 +146,14 @@ impl MockEnv { impl MockEnvBuilder { pub fn build(&mut self) -> AnyResult { - let owner = self.app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); + let owner = self + .app + .init_account(&[ + coin(DEFAULT_STARTING_BALANCE, "uosmo"), + coin(DEFAULT_STARTING_BALANCE, ATOM), + coin(DEFAULT_STARTING_BALANCE, DAI), + ]) + .unwrap(); let zapper = self.instantiate_contract(&owner); Ok(MockEnv { diff --git a/contracts/v3-zapper/osmosis/tests/test_add_position.rs b/contracts/v3-zapper/osmosis/tests/test_add_position.rs index 80ab3f5a2..fa7f1ef1e 100644 --- a/contracts/v3-zapper/osmosis/tests/test_add_position.rs +++ b/contracts/v3-zapper/osmosis/tests/test_add_position.rs @@ -8,12 +8,12 @@ use mars_v3_zapper_base::{ }; use osmosis_test_tube::{Account, Module, Wasm}; -use crate::helpers::{assert_err, default_new_position_req, MockEnv}; +use crate::helpers::{ + assert_err, default_new_position_req, MockEnv, ATOM, DAI, DEFAULT_STARTING_BALANCE, +}; pub mod helpers; -// TODO: Remove ignores when Test-Tube fixed: https://github.com/osmosis-labs/test-tube/commit/f9c1e6ba9b69432d9a19ee9d6454819e57903327 - #[test] fn only_owner_can_add_positions() { let mock = MockEnv::new().build().unwrap(); @@ -33,10 +33,9 @@ fn only_owner_can_add_positions() { } #[test] -#[ignore = "pending concentrated liquidity type update"] fn must_send_exact_funds() { let mut mock = MockEnv::new().build().unwrap(); - let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); + mock.create_pool(DAI, ATOM); let wasm = Wasm::new(&mock.app); @@ -44,10 +43,9 @@ fn must_send_exact_funds() { pool_id: 1, lower_tick: -1, upper_tick: 100, - token_desired0: Some(coin(100_000_000, denom0.clone())), - token_desired1: Some(coin(100_000_000, denom1.clone())), token_min_amount0: "10000".to_string(), token_min_amount1: "10000".to_string(), + tokens_provided: vec![coin(100_000_000, DAI), coin(100_000_000, ATOM)], }; let err = wasm @@ -59,7 +57,7 @@ fn must_send_exact_funds() { .execute( &mock.zapper, &ExecuteMsg::CreatePosition(new_position.clone()), - &[new_position.token_desired0.clone().unwrap()], + &[new_position.tokens_provided.first().unwrap().clone()], &mock.owner, ) .unwrap_err(); @@ -75,22 +73,21 @@ fn must_send_exact_funds() { .unwrap_err(); assert_err(err, "Sent fund mismatch"); - // assert with None as well + // assert with only one token provided let new_position = NewPositionRequest { pool_id: 1, lower_tick: -1, upper_tick: 100, - token_desired0: None, - token_desired1: Some(coin(100_000_000, denom1)), token_min_amount0: "0".to_string(), token_min_amount1: "10000".to_string(), + tokens_provided: vec![coin(100_000_000, DAI)], }; let err = wasm .execute( &mock.zapper, - &ExecuteMsg::CreatePosition(new_position.clone()), - &[coin(100_000_000, denom0), new_position.token_desired1.unwrap()], + &ExecuteMsg::CreatePosition(new_position), + &[coin(100_000_000, DAI), coin(100_000_000, ATOM)], &mock.owner, ) .unwrap_err(); @@ -98,23 +95,20 @@ fn must_send_exact_funds() { } #[test] -#[ignore = "pending concentrated liquidity type update"] fn add_position_successfully() { let mut mock = MockEnv::new().build().unwrap(); - let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); - - let starting_balance = 100_000_000_000; + mock.create_pool(DAI, ATOM); // assert owner funds before - let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); - assert_eq!(starting_balance, denom0_balance); - let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); - assert_eq!(starting_balance, denom1_balance); + let denom0_balance = mock.query_balance(&mock.owner.address(), DAI); + assert_eq!(DEFAULT_STARTING_BALANCE, denom0_balance); + let denom1_balance = mock.query_balance(&mock.owner.address(), ATOM); + assert_eq!(DEFAULT_STARTING_BALANCE, denom1_balance); // assert zapper funds before - let denom0_balance = mock.query_balance(&mock.zapper, &denom0); + let denom0_balance = mock.query_balance(&mock.zapper, DAI); assert_eq!(0, denom0_balance); - let denom1_balance = mock.query_balance(&mock.zapper, &denom1); + let denom1_balance = mock.query_balance(&mock.zapper, ATOM); assert_eq!(0, denom1_balance); let wasm = Wasm::new(&mock.app); @@ -124,19 +118,15 @@ fn add_position_successfully() { pool_id: 1, lower_tick: -1, upper_tick: 100, - token_desired0: Some(coin(amount_sent, denom0.clone())), - token_desired1: Some(coin(amount_sent, denom1.clone())), token_min_amount0: "10000".to_string(), token_min_amount1: "10000".to_string(), + tokens_provided: vec![coin(amount_sent, DAI), coin(amount_sent, ATOM)], }; let res = wasm .execute( &mock.zapper, &ExecuteMsg::CreatePosition(new_position.clone()), - &[ - new_position.token_desired0.clone().unwrap(), - new_position.token_desired1.clone().unwrap(), - ], + &new_position.tokens_provided, &mock.owner, ) .unwrap(); @@ -160,38 +150,38 @@ fn add_position_successfully() { assert_eq!(1, position.pool_id); assert_eq!(new_position.lower_tick, position.lower_tick); assert_eq!(new_position.upper_tick, position.upper_tick); - assert_eq!(new_position.token_desired0.unwrap().denom, p.asset0.clone().unwrap().denom); - assert_eq!(new_position.token_desired1.unwrap().denom, p.asset1.clone().unwrap().denom); + assert_eq!( + new_position.tokens_provided.first().unwrap().denom, + p.asset0.clone().unwrap().denom + ); + assert_eq!(new_position.tokens_provided.get(1).unwrap().denom, p.asset1.clone().unwrap().denom); // assert zapper funds after - let denom0_balance = mock.query_balance(&mock.zapper, &denom0); + let denom0_balance = mock.query_balance(&mock.zapper, DAI); assert_eq!(0, denom0_balance); - let denom1_balance = mock.query_balance(&mock.zapper, &denom1); + let denom1_balance = mock.query_balance(&mock.zapper, ATOM); assert_eq!(0, denom1_balance); // assert owner funds after - let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); + let denom0_balance = mock.query_balance(&mock.owner.address(), DAI); let position_amount0 = p.asset0.clone().unwrap().amount.parse::().unwrap(); - assert_eq!(starting_balance - position_amount0, denom0_balance); + assert_eq!(DEFAULT_STARTING_BALANCE - position_amount0, denom0_balance); - let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); + let denom1_balance = mock.query_balance(&mock.owner.address(), ATOM); let position_amount1 = p.asset1.clone().unwrap().amount.parse::().unwrap(); - assert_eq!(starting_balance - position_amount1, denom1_balance); + assert_eq!(DEFAULT_STARTING_BALANCE - position_amount1, denom1_balance); } #[test] -#[ignore = "pending concentrated liquidity type update"] fn refunds_are_issued() { let mut mock = MockEnv::new().build().unwrap(); - let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); - - let starting_balance = 100_000_000_000; + mock.create_pool(DAI, ATOM); // assert owner funds before - let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); - assert_eq!(starting_balance, denom0_balance); - let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); - assert_eq!(starting_balance, denom1_balance); + let denom0_balance = mock.query_balance(&mock.owner.address(), DAI); + assert_eq!(DEFAULT_STARTING_BALANCE, denom0_balance); + let denom1_balance = mock.query_balance(&mock.owner.address(), ATOM); + assert_eq!(DEFAULT_STARTING_BALANCE, denom1_balance); let wasm = Wasm::new(&mock.app); @@ -200,56 +190,54 @@ fn refunds_are_issued() { pool_id: 1, lower_tick: -100, upper_tick: 100, - token_desired0: Some(coin(amount_sent, denom0.clone())), - token_desired1: Some(coin(amount_sent, denom1.clone())), token_min_amount0: "10000".to_string(), token_min_amount1: "10000".to_string(), + tokens_provided: vec![coin(amount_sent, DAI), coin(amount_sent, ATOM)], }; let res = wasm .execute( &mock.zapper, &ExecuteMsg::CreatePosition(new_position.clone()), - &[new_position.token_desired0.unwrap(), new_position.token_desired1.unwrap()], + &new_position.tokens_provided, &mock.owner, ) .unwrap(); // Zapper should not have a balance after tx - let denom0_balance = mock.query_balance(&mock.zapper, &denom0); + let denom0_balance = mock.query_balance(&mock.zapper, DAI); assert_eq!(0, denom0_balance); - let denom1_balance = mock.query_balance(&mock.zapper, &denom1); + let denom1_balance = mock.query_balance(&mock.zapper, ATOM); assert_eq!(0, denom1_balance); // Assert refund event emitted - let refund_amount_a = 8992255u128; + let refund_amount_a = 8999922u128; let event = res.events.iter().find(|e| e.ty == format!("wasm-{}", REFUND_EVENT_TYPE)).unwrap(); let attr = event.attributes.iter().find(|a| a.key == REFUND_AMOUNT_ATTR_KEY).unwrap(); - assert_eq!(format!("{refund_amount_a}{denom1}"), attr.value); + assert_eq!(format!("{refund_amount_a}{ATOM}"), attr.value); // No refund on denom0 - let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); - assert_eq!(starting_balance - amount_sent, denom0_balance); + let denom0_balance = mock.query_balance(&mock.owner.address(), DAI); + assert_eq!(DEFAULT_STARTING_BALANCE - amount_sent, denom0_balance); // assert refund took place for denom1 - let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); - assert_eq!(starting_balance - amount_sent + refund_amount_a, denom1_balance); + let denom1_balance = mock.query_balance(&mock.owner.address(), ATOM); + assert_eq!(DEFAULT_STARTING_BALANCE - amount_sent + refund_amount_a, denom1_balance); let new_position = NewPositionRequest { pool_id: 1, lower_tick: -100, upper_tick: -20, - token_desired0: Some(coin(amount_sent, denom0.clone())), - token_desired1: Some(coin(amount_sent, denom1.clone())), token_min_amount0: "0".to_string(), token_min_amount1: "10000".to_string(), + tokens_provided: vec![coin(amount_sent, DAI), coin(amount_sent, ATOM)], }; let res = wasm .execute( &mock.zapper, &ExecuteMsg::CreatePosition(new_position.clone()), - &[new_position.token_desired0.unwrap(), new_position.token_desired1.unwrap()], + &new_position.tokens_provided, &mock.owner, ) .unwrap(); @@ -258,26 +246,25 @@ fn refunds_are_issued() { let refund_amount_b = 10000000u128; let event = res.events.iter().find(|e| e.ty == format!("wasm-{}", REFUND_EVENT_TYPE)).unwrap(); let attr = event.attributes.iter().find(|a| a.key == REFUND_AMOUNT_ATTR_KEY).unwrap(); - assert_eq!(format!("{refund_amount_b}{denom0}"), attr.value); + assert_eq!(format!("{refund_amount_b}{DAI}"), attr.value); // Full refund on denom0 - let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); + let denom0_balance = mock.query_balance(&mock.owner.address(), DAI); // Starting balance after first position was created - let balance_before = starting_balance - amount_sent; + let balance_before = DEFAULT_STARTING_BALANCE - amount_sent; assert_eq!(balance_before, denom0_balance); // No refund for denom1 - let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); + let denom1_balance = mock.query_balance(&mock.owner.address(), ATOM); // Starting balance after first position was created - let balance_before = starting_balance - amount_sent + refund_amount_a; + let balance_before = DEFAULT_STARTING_BALANCE - amount_sent + refund_amount_a; assert_eq!(balance_before - amount_sent, denom1_balance); } #[test] -#[ignore = "pending concentrated liquidity type update"] fn adding_multiple_increments() { let mut mock = MockEnv::new().build().unwrap(); - let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); + mock.create_pool(DAI, ATOM); let wasm = Wasm::new(&mock.app); @@ -285,18 +272,14 @@ fn adding_multiple_increments() { pool_id: 1, lower_tick: -1, upper_tick: 100, - token_desired0: Some(coin(100_000_000, denom0)), - token_desired1: Some(coin(100_000_000, denom1)), token_min_amount0: "10000".to_string(), token_min_amount1: "10000".to_string(), + tokens_provided: vec![coin(100_000_000, DAI), coin(100_000_000, ATOM)], }; wasm.execute( &mock.zapper, &ExecuteMsg::CreatePosition(new_position.clone()), - &[ - new_position.token_desired0.clone().unwrap(), - new_position.token_desired1.clone().unwrap(), - ], + &new_position.tokens_provided, &mock.owner, ) .unwrap(); @@ -304,10 +287,7 @@ fn adding_multiple_increments() { wasm.execute( &mock.zapper, &ExecuteMsg::CreatePosition(new_position.clone()), - &[ - new_position.token_desired0.clone().unwrap(), - new_position.token_desired1.clone().unwrap(), - ], + &new_position.tokens_provided, &mock.owner, ) .unwrap(); @@ -316,7 +296,7 @@ fn adding_multiple_increments() { .execute( &mock.zapper, &ExecuteMsg::CreatePosition(new_position.clone()), - &[new_position.token_desired0.unwrap(), new_position.token_desired1.unwrap()], + &new_position.tokens_provided, &mock.owner, ) .unwrap(); @@ -332,18 +312,15 @@ fn adding_multiple_increments() { } #[test] -#[ignore = "pending concentrated liquidity type update"] fn error_rolls_back_state() { let mut mock = MockEnv::new().build().unwrap(); - let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); - - let starting_balance = 100_000_000_000; + mock.create_pool(DAI, ATOM); // assert owner funds before - let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); - assert_eq!(starting_balance, denom0_balance); - let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); - assert_eq!(starting_balance, denom1_balance); + let denom0_balance = mock.query_balance(&mock.owner.address(), ATOM); + assert_eq!(DEFAULT_STARTING_BALANCE, denom0_balance); + let denom1_balance = mock.query_balance(&mock.owner.address(), DAI); + assert_eq!(DEFAULT_STARTING_BALANCE, denom1_balance); let wasm = Wasm::new(&mock.app); @@ -352,28 +329,27 @@ fn error_rolls_back_state() { pool_id: 1, lower_tick: -1, upper_tick: 100, - token_desired0: Some(coin(amount_sent, denom0.clone())), - token_desired1: Some(coin(amount_sent, denom1.clone())), token_min_amount0: "10000000000000000".to_string(), token_min_amount1: "10000".to_string(), + tokens_provided: vec![coin(amount_sent, DAI), coin(amount_sent, ATOM)], }; wasm.execute( &mock.zapper, &ExecuteMsg::CreatePosition(new_position.clone()), - &[new_position.token_desired0.clone().unwrap(), new_position.token_desired1.unwrap()], + &new_position.tokens_provided, &mock.owner, ) .unwrap_err(); // assert zapper funds after - let denom0_balance = mock.query_balance(&mock.zapper, &denom0); + let denom0_balance = mock.query_balance(&mock.zapper, ATOM); assert_eq!(0, denom0_balance); - let denom1_balance = mock.query_balance(&mock.zapper, &denom1); + let denom1_balance = mock.query_balance(&mock.zapper, DAI); assert_eq!(0, denom1_balance); // assert owner funds after - let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); - assert_eq!(starting_balance, denom0_balance); - let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); - assert_eq!(starting_balance, denom1_balance); + let denom0_balance = mock.query_balance(&mock.owner.address(), ATOM); + assert_eq!(DEFAULT_STARTING_BALANCE, denom0_balance); + let denom1_balance = mock.query_balance(&mock.owner.address(), DAI); + assert_eq!(DEFAULT_STARTING_BALANCE, denom1_balance); } diff --git a/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json b/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json index 5ba2d9688..ce2b2ce36 100644 --- a/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json +++ b/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json @@ -117,6 +117,7 @@ "pool_id", "token_min_amount0", "token_min_amount1", + "tokens_provided", "upper_tick" ], "properties": { @@ -129,32 +130,18 @@ "format": "uint64", "minimum": 0.0 }, - "token_desired0": { - "anyOf": [ - { - "$ref": "#/definitions/Coin" - }, - { - "type": "null" - } - ] - }, - "token_desired1": { - "anyOf": [ - { - "$ref": "#/definitions/Coin" - }, - { - "type": "null" - } - ] - }, "token_min_amount0": { "type": "string" }, "token_min_amount1": { "type": "string" }, + "tokens_provided": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, "upper_tick": { "type": "integer", "format": "int64" diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts index 2055d8d69..f2e74e60b 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts @@ -46,18 +46,16 @@ export interface MarsV3ZapperBaseInterface extends MarsV3ZapperBaseReadOnlyInter { lowerTick, poolId, - tokenDesired0, - tokenDesired1, tokenMinAmount0, tokenMinAmount1, + tokensProvided, upperTick, }: { lowerTick: number poolId: number - tokenDesired0?: Coin - tokenDesired1?: Coin tokenMinAmount0: string tokenMinAmount1: string + tokensProvided: Coin[] upperTick: number }, fee?: number | StdFee | 'auto', @@ -99,18 +97,16 @@ export class MarsV3ZapperBaseClient { lowerTick, poolId, - tokenDesired0, - tokenDesired1, tokenMinAmount0, tokenMinAmount1, + tokensProvided, upperTick, }: { lowerTick: number poolId: number - tokenDesired0?: Coin - tokenDesired1?: Coin tokenMinAmount0: string tokenMinAmount1: string + tokensProvided: Coin[] upperTick: number }, fee: number | StdFee | 'auto' = 'auto', @@ -124,10 +120,9 @@ export class MarsV3ZapperBaseClient create_position: { lower_tick: lowerTick, pool_id: poolId, - token_desired0: tokenDesired0, - token_desired1: tokenDesired1, token_min_amount0: tokenMinAmount0, token_min_amount1: tokenMinAmount1, + tokens_provided: tokensProvided, upper_tick: upperTick, }, }, diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts index 0324d5e01..d7253b97a 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts @@ -27,18 +27,16 @@ export interface MarsV3ZapperBaseMessage { { lowerTick, poolId, - tokenDesired0, - tokenDesired1, tokenMinAmount0, tokenMinAmount1, + tokensProvided, upperTick, }: { lowerTick: number poolId: number - tokenDesired0?: Coin - tokenDesired1?: Coin tokenMinAmount0: string tokenMinAmount1: string + tokensProvided: Coin[] upperTick: number }, _funds?: Coin[], @@ -62,18 +60,16 @@ export class MarsV3ZapperBaseMessageComposer implements MarsV3ZapperBaseMessage { lowerTick, poolId, - tokenDesired0, - tokenDesired1, tokenMinAmount0, tokenMinAmount1, + tokensProvided, upperTick, }: { lowerTick: number poolId: number - tokenDesired0?: Coin - tokenDesired1?: Coin tokenMinAmount0: string tokenMinAmount1: string + tokensProvided: Coin[] upperTick: number }, _funds?: Coin[], @@ -88,10 +84,9 @@ export class MarsV3ZapperBaseMessageComposer implements MarsV3ZapperBaseMessage create_position: { lower_tick: lowerTick, pool_id: poolId, - token_desired0: tokenDesired0, - token_desired1: tokenDesired1, token_min_amount0: tokenMinAmount0, token_min_amount1: tokenMinAmount1, + tokens_provided: tokensProvided, upper_tick: upperTick, }, }), diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts index b1bf6aff3..ef5de2b44 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts @@ -98,10 +98,9 @@ export interface MarsV3ZapperBaseCreatePositionMutation { msg: { lowerTick: number poolId: number - tokenDesired0?: Coin - tokenDesired1?: Coin tokenMinAmount0: string tokenMinAmount1: string + tokensProvided: Coin[] upperTick: number } args?: { diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts index 18457c4f4..baf3d1b46 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts @@ -44,10 +44,9 @@ export type Addr = string export interface NewPositionRequest { lower_tick: number pool_id: number - token_desired0?: Coin | null - token_desired1?: Coin | null token_min_amount0: string token_min_amount1: string + tokens_provided: Coin[] upper_tick: number } export interface Coin { From c2662c093c6a279e70ceb8aba729767530ec753f Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 21 Jun 2023 14:27:43 +0200 Subject: [PATCH 168/218] Round up health debt value (#147) --- contracts/credit-manager/tests/test_borrow.rs | 2 +- contracts/credit-manager/tests/test_health.rs | 26 +++---- .../tests/test_liquidate_lend.rs | 6 +- .../credit-manager/tests/test_withdraw.rs | 2 +- .../health-computer/src/health_computer.rs | 2 +- .../tests/test_health_scenarios.rs | 70 +++++++++---------- packages/health-computer/tests/test_hls.rs | 12 ++-- 7 files changed, 60 insertions(+), 60 deletions(-) diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index 3abdd91e3..6f520c16e 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -103,7 +103,7 @@ fn cannot_borrow_above_max_ltv() { res, ContractError::AboveMaxLTV { account_id, - max_ltv_health_factor: "0.96".to_string(), + max_ltv_health_factor: "0.955223880597014925".to_string(), }, ); } diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index 563e9ebb6..41a0d14b7 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -189,7 +189,7 @@ fn debts_no_assets() { res, ContractError::AboveMaxLTV { account_id: account_id.clone(), - max_ltv_health_factor: "0.68".to_string(), + max_ltv_health_factor: "0.653846153846153846".to_string(), }, ); @@ -250,10 +250,10 @@ fn cannot_borrow_more_than_healthy() { let health = mock.query_health(&account_id, AccountKind::Default); let assets_value = Uint128::new(827); assert_eq!(health.total_collateral_value, assets_value); - let debts_value = Uint128::new(120); + let debts_value = Uint128::new(121); assert_eq!(health.total_debt_value, debts_value); - assert_eq!(health.liquidation_health_factor, Some(Decimal::from_ratio(454u128, 120u128))); - assert_eq!(health.max_ltv_health_factor, Some(Decimal::from_ratio(413u128, 120u128))); + assert_eq!(health.liquidation_health_factor, Some(Decimal::from_ratio(454u128, 121u128))); + assert_eq!(health.max_ltv_health_factor, Some(Decimal::from_ratio(413u128, 121u128))); assert!(!health.liquidatable); assert!(!health.above_max_ltv); @@ -267,7 +267,7 @@ fn cannot_borrow_more_than_healthy() { res, ContractError::AboveMaxLTV { account_id: account_id.clone(), - max_ltv_health_factor: "0.990223463687150837".to_string(), + max_ltv_health_factor: "0.988842398884239888".to_string(), }, ); @@ -275,10 +275,10 @@ fn cannot_borrow_more_than_healthy() { let health = mock.query_health(&account_id, AccountKind::Default); let assets_value = Uint128::new(1064); assert_eq!(health.total_collateral_value, assets_value); - let debts_value = Uint128::new(359); + let debts_value = Uint128::new(360); assert_eq!(health.total_debt_value, debts_value); - assert_eq!(health.liquidation_health_factor, Some(Decimal::from_ratio(585u128, 359u128))); - assert_eq!(health.max_ltv_health_factor, Some(Decimal::from_ratio(532u128, 359u128))); + assert_eq!(health.liquidation_health_factor, Some(Decimal::from_ratio(585u128, 360u128))); + assert_eq!(health.max_ltv_health_factor, Some(Decimal::from_ratio(532u128, 360u128))); assert!(!health.liquidatable); assert!(!health.above_max_ltv); } @@ -434,7 +434,7 @@ fn assets_and_ltv_lqdt_adjusted_value() { ); assert_eq!( health.total_debt_value, - Uint128::new(350_615_100) // with simulated interest + Uint128::new(350_615_101) // with simulated interest ); let lqdt_adjusted_assets_value = deposit_amount .checked_mul_floor(uosmo_info.price) @@ -452,7 +452,7 @@ fn assets_and_ltv_lqdt_adjusted_value() { health.liquidation_health_factor, Some(Decimal::from_ratio( lqdt_adjusted_assets_value, - (borrowed_amount + Uint128::one()).checked_mul_floor(uatom_info.price).unwrap() + (borrowed_amount + Uint128::one()).checked_mul_ceil(uatom_info.price).unwrap() )) ); let ltv_adjusted_assets_value = deposit_amount @@ -471,7 +471,7 @@ fn assets_and_ltv_lqdt_adjusted_value() { health.max_ltv_health_factor, Some(Decimal::from_ratio( ltv_adjusted_assets_value, - (borrowed_amount + Uint128::one()).checked_mul_floor(uatom_info.price).unwrap() + (borrowed_amount + Uint128::one()).checked_mul_ceil(uatom_info.price).unwrap() )) ); assert!(!health.liquidatable); @@ -582,10 +582,10 @@ fn debt_value() { .amount .checked_mul_ceil((user_a_debt_shares_atom, red_bank_atom_res.shares)) .unwrap(); - let user_a_owed_atom_value = user_a_owed_atom.checked_mul_floor(uatom_info.price).unwrap(); + let user_a_owed_atom_value = user_a_owed_atom.checked_mul_ceil(uatom_info.price).unwrap(); let osmo_debt_value = - (user_a_borrowed_amount_osmo + Uint128::one()).checked_mul_floor(uosmo_info.price).unwrap(); + (user_a_borrowed_amount_osmo + Uint128::one()).checked_mul_ceil(uosmo_info.price).unwrap(); let total_debt_value = user_a_owed_atom_value.add(osmo_debt_value); assert_eq!(health.total_debt_value, total_debt_value); diff --git a/contracts/credit-manager/tests/test_liquidate_lend.rs b/contracts/credit-manager/tests/test_liquidate_lend.rs index 82b7d228b..700907355 100644 --- a/contracts/credit-manager/tests/test_liquidate_lend.rs +++ b/contracts/credit-manager/tests/test_liquidate_lend.rs @@ -81,7 +81,7 @@ fn lent_positions_contribute_to_health() { res, NotLiquidatable { account_id: liquidatee_account_id, - lqdt_health_factor: "9.7".to_string(), + lqdt_health_factor: "8.818181818181818181".to_string(), }, ) } @@ -193,7 +193,7 @@ fn lent_position_partially_liquidated() { let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(health.liquidatable); assert_eq!(health.total_collateral_value, Uint128::new(624u128)); - assert_eq!(health.total_debt_value, Uint128::new(555u128)); + assert_eq!(health.total_debt_value, Uint128::new(556u128)); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -377,7 +377,7 @@ fn liquidate_with_reclaiming() { let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(health.liquidatable); assert_eq!(health.total_collateral_value, Uint128::new(624u128)); - assert_eq!(health.total_debt_value, Uint128::new(555u128)); + assert_eq!(health.total_debt_value, Uint128::new(556u128)); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); diff --git a/contracts/credit-manager/tests/test_withdraw.rs b/contracts/credit-manager/tests/test_withdraw.rs index ca861736d..6643dd3f0 100644 --- a/contracts/credit-manager/tests/test_withdraw.rs +++ b/contracts/credit-manager/tests/test_withdraw.rs @@ -145,7 +145,7 @@ fn cannot_withdraw_more_than_healthy() { res, ContractError::AboveMaxLTV { account_id: account_id.clone(), - max_ltv_health_factor: "0.95".to_string(), + max_ltv_health_factor: "0.940594059405940594".to_string(), }, ); diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index 4be2ccb0f..e412ef892 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -65,7 +65,7 @@ impl HealthComputer { for debt in &self.positions.debts { let coin_price = self.denoms_data.prices.get(&debt.denom).ok_or(MissingPrice(debt.denom.clone()))?; - let debt_value = debt.amount.checked_mul_floor(*coin_price)?; + let debt_value = debt.amount.checked_mul_ceil(*coin_price)?; total = total.checked_add(debt_value)?; } Ok(total) diff --git a/packages/health-computer/tests/test_health_scenarios.rs b/packages/health-computer/tests/test_health_scenarios.rs index a4e6f5c5c..0d8697ecb 100644 --- a/packages/health-computer/tests/test_health_scenarios.rs +++ b/packages/health-computer/tests/test_health_scenarios.rs @@ -253,7 +253,7 @@ fn ltv_and_lqdt_adjusted_values() { ); assert_eq!( health.total_debt_value, - Uint128::new(350_615_100) // with simulated interest + Uint128::new(350_615_101) // with simulated interest ); let lqdt_adjusted_assets_value = deposit_amount .checked_mul_floor(ustars.price) @@ -271,7 +271,7 @@ fn ltv_and_lqdt_adjusted_values() { health.liquidation_health_factor, Some(Decimal::from_ratio( lqdt_adjusted_assets_value, - (borrow_amount + Uint128::one()).checked_mul_floor(ujuno.price).unwrap() + (borrow_amount + Uint128::one()).checked_mul_ceil(ujuno.price).unwrap() )) ); let ltv_adjusted_assets_value = deposit_amount @@ -290,7 +290,7 @@ fn ltv_and_lqdt_adjusted_values() { health.max_ltv_health_factor, Some(Decimal::from_ratio( ltv_adjusted_assets_value, - (borrow_amount + Uint128::one()).checked_mul_floor(ujuno.price).unwrap() + (borrow_amount + Uint128::one()).checked_mul_ceil(ujuno.price).unwrap() )) ); assert!(!health.is_liquidatable()); @@ -369,10 +369,10 @@ fn debt_value() { assert!(!health.is_liquidatable()); let juno_debt_value = - borrowed_amount_juno.add(Uint128::one()).checked_mul_floor(ujuno.price).unwrap(); + borrowed_amount_juno.add(Uint128::one()).checked_mul_ceil(ujuno.price).unwrap(); let stars_debt_value = - borrowed_amount_stars.add(Uint128::one()).checked_mul_floor(ustars.price).unwrap(); + borrowed_amount_stars.add(Uint128::one()).checked_mul_ceil(ustars.price).unwrap(); let total_debt_value = juno_debt_value.add(stars_debt_value); assert_eq!(health.total_debt_value, total_debt_value); @@ -469,14 +469,14 @@ fn above_max_ltv_below_liq_threshold() { assert_eq!(health.total_collateral_value, Uint128::new(1210)); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(968)); assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(1017)); - assert_eq!(health.total_debt_value, Uint128::new(971)); + assert_eq!(health.total_debt_value, Uint128::new(972)); assert_eq!( health.max_ltv_health_factor, - Some(Decimal::from_str("0.996910401647785787").unwrap()) + Some(Decimal::from_str("0.99588477366255144").unwrap()) ); assert_eq!( health.liquidation_health_factor, - Some(Decimal::from_str("1.047373841400617919").unwrap()) + Some(Decimal::from_str("1.046296296296296296").unwrap()) ); assert!(health.is_above_max_ltv()); assert!(!health.is_liquidatable()); @@ -531,14 +531,14 @@ fn liquidatable() { assert_eq!(health.total_collateral_value, Uint128::new(1210)); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(968)); assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(1017)); - assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!(health.total_debt_value, Uint128::new(1172)); assert_eq!( health.max_ltv_health_factor, - Some(Decimal::from_str("0.826643894107600341").unwrap()) + Some(Decimal::from_str("0.825938566552901023").unwrap()) ); assert_eq!( health.liquidation_health_factor, - Some(Decimal::from_str("0.868488471391972672").unwrap()) + Some(Decimal::from_str("0.867747440273037542").unwrap()) ); assert!(health.is_above_max_ltv()); assert!(health.is_liquidatable()); @@ -595,14 +595,14 @@ fn rover_whitelist_influences_max_ltv() { assert_eq!(health.total_collateral_value, Uint128::new(1210)); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(960)); assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(1017)); - assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!(health.total_debt_value, Uint128::new(1172)); assert_eq!( health.max_ltv_health_factor, - Some(Decimal::from_str("0.819812126387702818").unwrap()) + Some(Decimal::from_str("0.819112627986348122").unwrap()) ); assert_eq!( health.liquidation_health_factor, - Some(Decimal::from_str("0.868488471391972672").unwrap()) + Some(Decimal::from_str("0.867747440273037542").unwrap()) ); assert!(health.is_above_max_ltv()); assert!(health.is_liquidatable()); @@ -686,14 +686,14 @@ fn unlocked_vault() { assert_eq!(health.total_collateral_value, Uint128::new(6474)); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(3073)); assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(3649)); - assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!(health.total_debt_value, Uint128::new(1172)); assert_eq!( health.max_ltv_health_factor, - Some(Decimal::from_str("2.624252775405636208").unwrap()) + Some(Decimal::from_str("2.622013651877133105").unwrap()) ); assert_eq!( health.liquidation_health_factor, - Some(Decimal::from_str("3.116140051238257899").unwrap()) + Some(Decimal::from_str("3.113481228668941979").unwrap()) ); assert!(!health.is_above_max_ltv()); assert!(!health.is_liquidatable()); @@ -780,14 +780,14 @@ fn locked_vault() { assert_eq!(health.total_collateral_value, Uint128::new(6474)); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(3073)); assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(3649)); - assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!(health.total_debt_value, Uint128::new(1172)); assert_eq!( health.max_ltv_health_factor, - Some(Decimal::from_str("2.624252775405636208").unwrap()) + Some(Decimal::from_str("2.622013651877133105").unwrap()) ); assert_eq!( health.liquidation_health_factor, - Some(Decimal::from_str("3.116140051238257899").unwrap()) + Some(Decimal::from_str("3.113481228668941979").unwrap()) ); assert!(!health.is_above_max_ltv()); assert!(!health.is_liquidatable()); @@ -883,14 +883,14 @@ fn locked_vault_with_unlocking_positions() { assert_eq!(health.total_collateral_value, Uint128::new(6474)); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(3192)); assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(3754)); - assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!(health.total_debt_value, Uint128::new(1172)); assert_eq!( health.max_ltv_health_factor, - Some(Decimal::from_str("2.72587532023911187").unwrap()) + Some(Decimal::from_str("2.723549488054607508").unwrap()) ); assert_eq!( health.liquidation_health_factor, - Some(Decimal::from_str("3.205807002561912894").unwrap()) + Some(Decimal::from_str("3.203071672354948805").unwrap()) ); assert!(!health.is_above_max_ltv()); assert!(!health.is_liquidatable()); @@ -974,14 +974,14 @@ fn vault_is_not_whitelisted() { assert_eq!(health.total_collateral_value, Uint128::new(6474)); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(968)); assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(3649)); - assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!(health.total_debt_value, Uint128::new(1172)); assert_eq!( health.max_ltv_health_factor, - Some(Decimal::from_str("0.826643894107600341").unwrap()) + Some(Decimal::from_str("0.825938566552901023").unwrap()) ); assert_eq!( health.liquidation_health_factor, - Some(Decimal::from_str("3.116140051238257899").unwrap()) + Some(Decimal::from_str("3.113481228668941979").unwrap()) ); assert!(health.is_above_max_ltv()); assert!(!health.is_liquidatable()); @@ -1083,14 +1083,14 @@ fn vault_base_token_is_not_whitelisted() { assert_eq!(health.total_collateral_value, Uint128::new(497879652)); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(968)); // Lower due to vault blacklisted assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(448089614)); - assert_eq!(health.total_debt_value, Uint128::new(1171)); + assert_eq!(health.total_debt_value, Uint128::new(1172)); assert_eq!( health.max_ltv_health_factor, - Some(Decimal::from_str("0.826643894107600341").unwrap()) + Some(Decimal::from_str("0.825938566552901023").unwrap()) ); assert_eq!( health.liquidation_health_factor, - Some(Decimal::from_str("382655.520068317677198975").unwrap()) + Some(Decimal::from_str("382329.022184300341296928").unwrap()) ); assert!(health.is_above_max_ltv()); assert!(!health.is_liquidatable()); @@ -1152,14 +1152,14 @@ fn lent_coins_used_as_collateral() { assert_eq!(health.total_collateral_value, Uint128::new(1230)); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(981)); assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(1031)); - assert_eq!(health.total_debt_value, Uint128::new(971)); + assert_eq!(health.total_debt_value, Uint128::new(972)); assert_eq!( health.max_ltv_health_factor, - Some(Decimal::from_str("1.010298661174047373").unwrap()) + Some(Decimal::from_str("1.009259259259259259").unwrap()) ); assert_eq!( health.liquidation_health_factor, - Some(Decimal::from_str("1.061791967044284243").unwrap()) + Some(Decimal::from_str("1.060699588477366255").unwrap()) ); assert!(!health.is_above_max_ltv()); assert!(!health.is_liquidatable()); @@ -1223,14 +1223,14 @@ fn allowed_lent_coins_influence_max_ltv() { assert_eq!(health.total_collateral_value, Uint128::new(1230)); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(967)); assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(1031)); - assert_eq!(health.total_debt_value, Uint128::new(971)); + assert_eq!(health.total_debt_value, Uint128::new(972)); assert_eq!( health.max_ltv_health_factor, - Some(Decimal::from_str("0.995880535530381050").unwrap()) + Some(Decimal::from_str("0.9948559670781893").unwrap()) ); assert_eq!( health.liquidation_health_factor, - Some(Decimal::from_str("1.061791967044284243").unwrap()) + Some(Decimal::from_str("1.060699588477366255").unwrap()) ); assert!(health.is_above_max_ltv()); assert!(!health.is_liquidatable()); diff --git a/packages/health-computer/tests/test_hls.rs b/packages/health-computer/tests/test_hls.rs index a555070f9..32ea3fa75 100644 --- a/packages/health-computer/tests/test_hls.rs +++ b/packages/health-computer/tests/test_hls.rs @@ -150,14 +150,14 @@ fn hls_vault() { assert_eq!(health.total_collateral_value, Uint128::new(6318574763758)); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(4738931072818)); assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(5054859811006)); - assert_eq!(health.total_debt_value, Uint128::new(1053095794053)); + assert_eq!(health.total_debt_value, Uint128::new(1053095794055)); assert_eq!( health.max_ltv_health_factor, - Some(Decimal::from_str("4.499999999600701092").unwrap()) + Some(Decimal::from_str("4.499999999592154861").unwrap()) ); assert_eq!( health.liquidation_health_factor, - Some(Decimal::from_str("4.799999999574207776").unwrap()) + Some(Decimal::from_str("4.799999999565091796").unwrap()) ); assert!(!health.is_above_max_ltv()); assert!(!health.is_liquidatable()); @@ -294,14 +294,14 @@ fn hls_on_blacklisted_vault() { assert_eq!(health.total_collateral_value, Uint128::new(6318574763758)); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(4738931068870)); assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(5054859811006)); - assert_eq!(health.total_debt_value, Uint128::new(1053095794053)); + assert_eq!(health.total_debt_value, Uint128::new(1053095794055)); assert_eq!( health.max_ltv_health_factor, - Some(Decimal::from_str("4.499999995851754394").unwrap()) + Some(Decimal::from_str("4.499999995843208163").unwrap()) ); assert_eq!( health.liquidation_health_factor, - Some(Decimal::from_str("4.799999999574207776").unwrap()) + Some(Decimal::from_str("4.799999999565091796").unwrap()) ); assert!(!health.is_above_max_ltv()); assert!(!health.is_liquidatable()); From 4a8a3b844b312354623edd8df44b5cf20acfb52a Mon Sep 17 00:00:00 2001 From: piobab Date: Fri, 23 Jun 2023 19:42:56 +0200 Subject: [PATCH 169/218] Dynamic LB and CF (#146) * Add new dynamic liquidation bonus and CF calculations. * Update schema. * Review comments. --- Cargo.lock | 44 +-- Cargo.toml | 2 +- contracts/credit-manager/src/execute.rs | 13 +- contracts/credit-manager/src/lib.rs | 1 + contracts/credit-manager/src/liquidate.rs | 263 ++++++++++++++++ .../credit-manager/src/liquidate_deposit.rs | 134 ++------- .../credit-manager/src/liquidate_lend.rs | 39 ++- contracts/credit-manager/src/query.rs | 5 +- contracts/credit-manager/src/state.rs | 10 + .../src/update_coin_balances.rs | 46 ++- contracts/credit-manager/src/update_config.rs | 32 +- contracts/credit-manager/src/utils.rs | 21 ++ .../src/vault/liquidate_vault.rs | 78 +++-- .../credit-manager/tests/helpers/builders.rs | 9 +- .../tests/helpers/mock_entity_info.rs | 45 ++- .../credit-manager/tests/helpers/mock_env.rs | 43 ++- .../credit-manager/tests/helpers/types.rs | 6 +- contracts/credit-manager/tests/test_health.rs | 77 ++++- .../tests/test_liquidate_deposit.rs | 282 ++++++++++++++---- .../tests/test_liquidate_lend.rs | 156 +++++++--- .../tests/test_liquidate_vault.rs | 137 +++++++-- contracts/credit-manager/tests/test_repay.rs | 17 +- .../tests/test_update_config.rs | 6 + .../tests/test_utilization_query.rs | 9 +- contracts/health/tests/helpers/defaults.rs | 10 +- .../health/tests/helpers/mock_env_builder.rs | 3 +- contracts/health/tests/test_compute_health.rs | 26 +- .../tests/helpers/mock_coin_info.rs | 42 ++- packages/rover/src/adapters/params.rs | 4 +- packages/rover/src/msg/execute.rs | 9 + packages/rover/src/msg/instantiate.rs | 2 + packages/rover/src/msg/query.rs | 1 + .../mars-credit-manager.json | 54 ++++ .../mars-mock-credit-manager.json | 6 + schemas/mars-params/mars-params.json | 191 ++++++++++-- .../mars-rover-health-computer.json | 51 +++- scripts/codegen/index.ts | 6 +- .../MarsCreditManager.types.ts | 9 + .../MarsMockCreditManager.types.ts | 1 + .../mars-params/MarsParams.client.ts | 17 +- .../MarsParams.message-composer.ts | 9 +- .../mars-params/MarsParams.react-query.ts | 25 +- .../generated/mars-params/MarsParams.types.ts | 18 +- .../MarsRoverHealthComputer.client.ts | 1 + ...arsRoverHealthComputer.message-composer.ts | 1 + .../MarsRoverHealthComputer.react-query.ts | 1 + .../MarsRoverHealthComputer.types.ts | 9 +- 47 files changed, 1551 insertions(+), 420 deletions(-) create mode 100644 contracts/credit-manager/src/liquidate.rs diff --git a/Cargo.lock b/Cargo.lock index aaba1b5cb..ed325fd55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -579,7 +579,7 @@ checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cw2 1.1.0", "schemars", "semver", "serde", @@ -615,20 +615,21 @@ dependencies = [ [[package]] name = "cw2" version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" +source = "git+https://github.com/mars-protocol/cw-plus?rev=1a3a944#1a3a944b64cf6e9fcfada48f2b09aaa1a90aef74" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", "schemars", "serde", + "thiserror", ] [[package]] name = "cw2" -version = "1.0.1" -source = "git+https://github.com/mars-protocol/cw-plus?rev=1a3a944#1a3a944b64cf6e9fcfada48f2b09aaa1a90aef74" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -703,7 +704,7 @@ dependencies = [ "cw-ownable", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "cw2 1.0.1 (git+https://github.com/mars-protocol/cw-plus?rev=1a3a944)", + "cw2 1.0.1", "cw721 0.17.0", "cw721-base 0.16.0", "schemars", @@ -1255,9 +1256,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -1327,7 +1328,7 @@ dependencies = [ "cosmwasm-std", "cw-multi-test", "cw-storage-plus 1.1.0", - "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cw2 1.1.0", "cw721 0.17.0", "cw721-base 0.16.0", "cw721-base 0.17.0", @@ -1349,7 +1350,7 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard", - "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cw2 1.1.0", "cw721 0.17.0", "cw721-base 0.17.0", "itertools", @@ -1449,14 +1450,14 @@ dependencies = [ [[package]] name = "mars-params" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8edfc032b3aedac656da1f90f5f1bc3b735c5de373b761279ff6b0de90346bd6" +checksum = "812e926698bea464b42ca1f2fd6b2400375a90f6bca84e4eb448310b95145c2e" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", - "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cw2 1.1.0", "mars-owner", "mars-utils", "schemars", @@ -1510,7 +1511,7 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard", - "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cw2 1.1.0", "mars-mock-credit-manager", "mars-mock-oracle", "mars-mock-vault", @@ -1582,7 +1583,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", - "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cw2 1.1.0", "mars-osmosis", "mars-owner", "mars-rover", @@ -1636,7 +1637,7 @@ dependencies = [ "cosmwasm-std", "cw-dex", "cw-utils 1.0.1", - "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cw2 1.1.0", "mars-v2-zapper-base", "osmosis-std 0.16.0-beta.0", "osmosis-test-tube", @@ -1648,7 +1649,7 @@ version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cw2 1.1.0", "mars-owner", "schemars", "serde", @@ -2810,10 +2811,11 @@ dependencies = [ [[package]] name = "want" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ + "log", "try-lock", ] @@ -2879,9 +2881,9 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 945e381d1..491dfe3af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ mars-rover = { version = "2.0.0", path = "./packages/rover" } # contracts mars-account-nft = { version = "2.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-params = { version = "1.0.6", features = ["library"] } +mars-params = { version = "1.0.7", features = ["library"] } mars-rover-health = { version = "2.0.0", path = "./contracts/health", features = ["library"] } mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base", features = ["library"] } mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base", features = ["library"] } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index c6bfc9a50..894c6cd92 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -22,7 +22,7 @@ use crate::{ repay::{repay, repay_for_recipient}, state::{ACCOUNT_KINDS, ACCOUNT_NFT}, swap::swap_exact_in, - update_coin_balances::update_coin_balance, + update_coin_balances::{update_coin_balance, update_coin_balance_after_vault_liquidation}, utils::{assert_is_token_owner, assert_not_contract_in_config}, vault::{ enter_vault, exit_vault, exit_vault_unlocked, liquidate_vault, request_vault_unlock, @@ -341,6 +341,17 @@ pub fn execute_callback( account_id, previous_balance, } => update_coin_balance(deps, env, &account_id, &previous_balance), + CallbackMsg::UpdateCoinBalanceAfterVaultLiquidation { + account_id, + previous_balance, + protocol_fee, + } => update_coin_balance_after_vault_liquidation( + deps, + env, + &account_id, + &previous_balance, + protocol_fee, + ), CallbackMsg::ExitVault { account_id, vault, diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index e8ea540da..a9c134b39 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -7,6 +7,7 @@ pub mod health; pub mod hls; pub mod instantiate; pub mod lend; +pub mod liquidate; pub mod liquidate_deposit; pub mod liquidate_lend; pub mod query; diff --git a/contracts/credit-manager/src/liquidate.rs b/contracts/credit-manager/src/liquidate.rs new file mode 100644 index 000000000..f6e414e9b --- /dev/null +++ b/contracts/credit-manager/src/liquidate.rs @@ -0,0 +1,263 @@ +use std::{ + cmp::{max, min}, + ops::Add, +}; + +use cosmwasm_std::{Coin, Decimal, DepsMut, Env, QuerierWrapper, StdError, Uint128}; +use mars_params::types::asset::AssetParams; +use mars_rover::{ + adapters::oracle::Oracle, + error::{ContractError, ContractResult}, + traits::Stringify, +}; +use mars_rover_health_types::HealthResponse; + +use crate::{ + health::query_health, + repay::current_debt_for_denom, + state::{ORACLE, PARAMS}, +}; + +/// Calculates precise debt, request coin amounts to liquidate, request coin transfered to liquidator and rewards-collector. +/// The debt amount will be adjusted down if: +/// - Exceeds liquidatee's total debt for denom +/// - Not enough liquidatee request coin balance to match +/// - The value of the debt repaid exceeds the Maximum Debt Repayable (MDR) +/// Returns -> (Debt Coin, Liquidator Request Coin, Liquidatee Request Coin) +/// Difference between Liquidator Request Coin and Liquidatee Request Coin goes to rewards-collector account as protocol fee. +pub fn calculate_liquidation( + deps: &DepsMut, + env: &Env, + liquidatee_account_id: &str, + debt_coin: &Coin, + request_coin: &str, + request_coin_balance: Uint128, +) -> ContractResult<(Coin, Coin, Coin)> { + // Assert the liquidatee's credit account is liquidatable + let health = query_health(deps.as_ref(), liquidatee_account_id)?; + if !health.liquidatable { + return Err(ContractError::NotLiquidatable { + account_id: liquidatee_account_id.to_string(), + lqdt_health_factor: health.liquidation_health_factor.to_string(), + }); + } + + // Ensure debt repaid does not exceed liquidatee's total debt for denom + let (total_debt_amount, _) = + current_debt_for_denom(deps.as_ref(), env, liquidatee_account_id, &debt_coin.denom)?; + + let params = PARAMS.load(deps.storage)?; + let target_health_factor = params.query_target_health_factor(&deps.querier)?; + let request_coin_params = params.query_asset_params(&deps.querier, request_coin)?; + + let oracle = ORACLE.load(deps.storage)?; + let debt_coin_price = oracle.query_price(&deps.querier, &debt_coin.denom)?.price; + let request_coin_price = oracle.query_price(&deps.querier, request_coin)?.price; + + let (debt_amount_to_repay, request_amount_to_liquidate, request_amount_received_by_liquidator) = + calculate_liquidation_amounts( + request_coin_balance, + request_coin_price, + &request_coin_params, + total_debt_amount, + debt_coin.amount, + debt_coin_price, + target_health_factor, + &health, + )?; + + // (Debt Coin, Liquidator Request Coin, Liquidatee Request Coin) + let result = ( + Coin { + denom: debt_coin.denom.clone(), + amount: debt_amount_to_repay, + }, + Coin { + denom: request_coin.to_string(), + amount: request_amount_received_by_liquidator, + }, + Coin { + denom: request_coin.to_string(), + amount: request_amount_to_liquidate, + }, + ); + + assert_liquidation_profitable(&deps.querier, &oracle, result.clone())?; + + Ok(result) +} + +/// Within this new system, the close factor (CF) will be determined dynamically using a parameter +/// known as the Target Health Factor (THF). The THF determines the ideal HF a position should be left +/// at immediately after the position has been liquidated. The CF, in turn, is a result of this parameter: +/// the maximum amount of debt that can be repaid to take the position to the THF. +/// For example, if the THF is 1.10 and a position gets liquidated at HF = 0.98, then the maximum +/// amount of debt a liquidator can repay (in other words, the CF) will be an amount such that the HF +/// after the liquidation is at maximum 1.10. +/// +/// The formula to calculate the maximum debt that can be repaid by a liquidator is as follows: +/// MDR_value = (THF * total_debt_value - liq_th_collateral_value) / (THF - (requested_collateral_liq_th * (1 + TLF))) +/// where: +/// MDR - Maximum Debt Repayable +/// THF - Target Health Factor +/// total_debt_value - Value of debt before the liquidation happens +/// liq_th_collateral_value - Value of collateral before the liquidation happens adjusted to liquidation threshold +/// requested_collateral_liq_th - Liquidation threshold of requested collateral +/// TLF - Total Liquidation Fee +#[allow(clippy::too_many_arguments)] +fn calculate_liquidation_amounts( + collateral_amount: Uint128, + collateral_price: Decimal, + collateral_params: &AssetParams, + debt_amount: Uint128, + debt_requested_to_repay: Uint128, + debt_price: Decimal, + target_health_factor: Decimal, + health: &HealthResponse, +) -> Result<(Uint128, Uint128, Uint128), ContractError> { + // if health.liquidatable == true, save to unwrap + let liquidation_health_factor = health.liquidation_health_factor.unwrap(); + + let user_collateral_value = collateral_amount.checked_mul_floor(collateral_price)?; + + let liquidation_bonus = calculate_liquidation_bonus( + liquidation_health_factor, + health.total_collateral_value, + health.total_debt_value, + collateral_params, + )?; + + let updated_tlf = calculate_total_liquidation_fee( + liquidation_health_factor, + liquidation_bonus, + collateral_params, + )?; + + let max_debt_repayable_numerator = (target_health_factor * health.total_debt_value) + - health.liquidation_threshold_adjusted_collateral; + let max_debt_repayable_denominator = target_health_factor + - (collateral_params.liquidation_threshold * (Decimal::one() + updated_tlf)); + + let max_debt_repayable_value = + max_debt_repayable_numerator.checked_div_floor(max_debt_repayable_denominator)?; + + let max_debt_repayable_amount = max_debt_repayable_value.checked_div_floor(debt_price)?; + + // calculate possible debt to repay based on available collateral + let debt_amount_possible_to_repay = user_collateral_value + .checked_div_floor(Decimal::one().add(updated_tlf))? + .checked_div_floor(debt_price)?; + + let debt_amount_to_repay = *vec![ + debt_amount, + debt_requested_to_repay, + max_debt_repayable_amount, + debt_amount_possible_to_repay, + ] + .iter() + .min() + .ok_or_else(|| StdError::generic_err("Minimum not found"))?; + + let collateral_amount_to_liquidate = debt_amount_to_repay + .checked_mul_floor(debt_price)? + .checked_mul_floor(updated_tlf.add(Decimal::one()))? + .checked_div_floor(collateral_price)?; + let collateral_amount_received_by_liquidator = debt_amount_to_repay + .checked_mul_floor(debt_price)? + .checked_mul_floor(liquidation_bonus.add(Decimal::one()))? + .checked_div_floor(collateral_price)?; + + Ok(( + debt_amount_to_repay, + collateral_amount_to_liquidate, + collateral_amount_received_by_liquidator, + )) +} + +/// In order for HF after liquidation to be higher than HF before liquidation, it is necessary that the condition holds: +/// max_total_liquidation_fee <= (liquidation_health_factor / requested_collateral_liq_th) - 1 +/// Based on that info we derive max protocol liquidation fee. It is OK to be 0. +/// +/// For more info see: https://docs.google.com/document/d/1kImPm4xd3pP8EaC1KZU8oLMFciRDd8Z-jOxv0MTQvEc/edit?usp=sharing +fn calculate_total_liquidation_fee( + liquidation_health_factor: Decimal, + liquidation_bonus: Decimal, + collateral_params: &AssetParams, +) -> Result { + let max_tlf = liquidation_health_factor.checked_div(collateral_params.liquidation_threshold)?; + let max_tlf = if max_tlf > Decimal::one() { + max_tlf - Decimal::one() + } else { + Decimal::zero() + }; + let available_plf = if max_tlf > liquidation_bonus { + max_tlf - liquidation_bonus + } else { + Decimal::zero() + }; + let updated_plf = min(collateral_params.protocol_liquidation_fee, available_plf); + let updated_tlf = updated_plf + liquidation_bonus; + Ok(updated_tlf) +} + +/// The LB will depend on the Health Factor and a couple other parameters as follows: +/// Liquidation Bonus = min( +/// starting_lb + (slope * (1 - HF)), +/// max( +/// min(CR - 1, max_lb), +/// min_lb +/// ) +/// ) +/// `CR` is the Collateralization Ratio of the position calculated as `CR = Total Assets / Total Debt`. +fn calculate_liquidation_bonus( + liquidation_health_factor: Decimal, + total_collateral_value: Uint128, + total_debt_value: Uint128, + collateral_params: &AssetParams, +) -> Result { + let collateralization_ratio = + Decimal::checked_from_ratio(total_collateral_value, total_debt_value)?; + + // (CR - 1) can't be negative + let collateralization_ratio_adjusted = if collateralization_ratio > Decimal::one() { + collateralization_ratio - Decimal::one() + } else { + Decimal::zero() + }; + + let max_lb_adjusted = max( + min(collateralization_ratio_adjusted, collateral_params.liquidation_bonus.max_lb), + collateral_params.liquidation_bonus.min_lb, + ); + + let calculated_bonus = collateral_params.liquidation_bonus.starting_lb.checked_add( + collateral_params + .liquidation_bonus + .slope + .checked_mul(Decimal::one() - liquidation_health_factor)?, + )?; + + let liquidation_bonus = min(calculated_bonus, max_lb_adjusted); + + Ok(liquidation_bonus) +} + +/// In scenarios with small amounts or large gap between coin prices, there is a possibility +/// that the liquidation will result in loss for the liquidator. This assertion prevents this. +fn assert_liquidation_profitable( + querier: &QuerierWrapper, + oracle: &Oracle, + (debt_coin, request_coin, ..): (Coin, Coin, Coin), +) -> ContractResult<()> { + let debt_value = oracle.query_total_value(querier, &[debt_coin.clone()])?; + let request_value = oracle.query_total_value(querier, &[request_coin.clone()])?; + + if debt_value >= request_value { + return Err(ContractError::LiquidationNotProfitable { + debt_coin, + request_coin, + }); + } + + Ok(()) +} diff --git a/contracts/credit-manager/src/liquidate_deposit.rs b/contracts/credit-manager/src/liquidate_deposit.rs index a00f5e0f1..f452b0340 100644 --- a/contracts/credit-manager/src/liquidate_deposit.rs +++ b/contracts/credit-manager/src/liquidate_deposit.rs @@ -1,19 +1,12 @@ -use std::ops::Add; - -use cosmwasm_std::{ - Coin, CosmosMsg, Decimal, DepsMut, Env, QuerierWrapper, Response, StdError, Storage, Uint128, -}; +use cosmwasm_std::{Coin, CosmosMsg, DepsMut, Env, Response, Storage}; use mars_rover::{ - adapters::oracle::Oracle, error::{ContractError, ContractResult}, msg::execute::CallbackMsg, - traits::Stringify, }; use crate::{ - health::query_health, - repay::current_debt_for_denom, - state::{COIN_BALANCES, ORACLE, PARAMS}, + liquidate::calculate_liquidation, + state::{COIN_BALANCES, REWARDS_COLLECTOR}, utils::{decrement_coin_balance, increment_coin_balance}, }; @@ -29,7 +22,7 @@ pub fn liquidate_deposit( .load(deps.storage, (liquidatee_account_id, request_coin_denom)) .map_err(|_| ContractError::CoinNotAvailable(request_coin_denom.to_string()))?; - let (debt, request) = calculate_liquidation( + let (debt, liquidator_request, liquidatee_request) = calculate_liquidation( &deps, &env, liquidatee_account_id, @@ -42,8 +35,17 @@ pub fn liquidate_deposit( repay_debt(deps.storage, &env, liquidator_account_id, liquidatee_account_id, &debt)?; // Transfer requested coin from liquidatee to liquidator - decrement_coin_balance(deps.storage, liquidatee_account_id, &request)?; - increment_coin_balance(deps.storage, liquidator_account_id, &request)?; + decrement_coin_balance(deps.storage, liquidatee_account_id, &liquidatee_request)?; + increment_coin_balance(deps.storage, liquidator_account_id, &liquidator_request)?; + + // Transfer protocol fee to rewards-collector account + let rewards_collector_account = REWARDS_COLLECTOR.load(deps.storage)?.account_id; + let protocol_fee_amount = liquidatee_request.amount.checked_sub(liquidator_request.amount)?; + increment_coin_balance( + deps.storage, + &rewards_collector_account, + &Coin::new(protocol_fee_amount.u128(), liquidatee_request.denom.clone()), + )?; Ok(Response::new() .add_message(repay_msg) @@ -51,87 +53,11 @@ pub fn liquidate_deposit( .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("coin_debt_repaid", debt.to_string()) - .add_attribute("coin_liquidated", request.to_string())) -} - -/// Calculates precise debt & request coin amounts to liquidate -/// The debt amount will be adjusted down if: -/// - Exceeds liquidatee's total debt for denom -/// - Not enough liquidatee request coin balance to match -/// - The value of the debt repaid exceeds the maximum close factor % -/// Returns -> (Debt Coin, Request Coin) -pub fn calculate_liquidation( - deps: &DepsMut, - env: &Env, - liquidatee_account_id: &str, - debt_coin: &Coin, - request_coin: &str, - request_coin_balance: Uint128, -) -> ContractResult<(Coin, Coin)> { - // Assert the liquidatee's credit account is liquidatable - let health = query_health(deps.as_ref(), liquidatee_account_id)?; - if !health.liquidatable { - return Err(ContractError::NotLiquidatable { - account_id: liquidatee_account_id.to_string(), - lqdt_health_factor: health.liquidation_health_factor.to_string(), - }); - } - - // Ensure debt repaid does not exceed liquidatee's total debt for denom - let (total_debt_amount, _) = - current_debt_for_denom(deps.as_ref(), env, liquidatee_account_id, &debt_coin.denom)?; - - // Ensure debt amount does not exceed close factor % of the liquidatee's total debt value - let params = PARAMS.load(deps.storage)?; - let close_factor = params.query_max_close_factor(&deps.querier)?; - let max_close_value = health.total_debt_value.checked_mul_floor(close_factor)?; - let oracle = ORACLE.load(deps.storage)?; - let debt_res = oracle.query_price(&deps.querier, &debt_coin.denom)?; - let max_close_amount = max_close_value.checked_div_floor(debt_res.price)?; - - // Calculate the maximum debt possible to repay given liquidatee's request coin balance - // FORMULA: debt amount = request value / (1 + liquidation bonus %) / debt price - let request_res = oracle.query_price(&deps.querier, request_coin)?; - let max_request_value = request_coin_balance.checked_mul_floor(request_res.price)?; - - let denom_params = params.query_asset_params(&deps.querier, &debt_coin.denom)?; - let liq_bonus_rate = denom_params.liquidation_bonus; - let request_coin_adjusted_max_debt = max_request_value - .checked_div_floor(Decimal::one().add(liq_bonus_rate))? - .checked_div_floor(debt_res.price)?; - - let final_debt_to_repay = *vec![ - debt_coin.amount, - total_debt_amount, - max_close_amount, - request_coin_adjusted_max_debt, - ] - .iter() - .min() - .ok_or_else(|| StdError::generic_err("Minimum not found"))?; - - // Calculate exact request coin amount to give to liquidator - // FORMULA: request amount = debt value * (1 + liquidation bonus %) / request coin price - let request_amount = final_debt_to_repay - .checked_mul_floor(debt_res.price)? - .checked_mul_floor(liq_bonus_rate.add(Decimal::one()))? - .checked_div_floor(request_res.price)?; - - // (Debt Coin, Request Coin) - let result = ( - Coin { - denom: debt_coin.denom.clone(), - amount: final_debt_to_repay, - }, - Coin { - denom: request_coin.to_string(), - amount: request_amount, - }, - ); - - assert_liquidation_profitable(&deps.querier, &oracle, result.clone())?; - - Ok(result) + .add_attribute("coin_liquidated", liquidatee_request.to_string()) + .add_attribute( + "protocol_fee_coin", + Coin::new(protocol_fee_amount.u128(), request_coin_denom).to_string(), + )) } pub fn repay_debt( @@ -152,23 +78,3 @@ pub fn repay_debt( .into_cosmos_msg(&env.contract.address)?; Ok(msg) } - -/// In scenarios with small amounts or large gap between coin prices, there is a possibility -/// that the liquidation will result in loss for the liquidator. This assertion prevents this. -fn assert_liquidation_profitable( - querier: &QuerierWrapper, - oracle: &Oracle, - (debt_coin, request_coin): (Coin, Coin), -) -> ContractResult<()> { - let debt_value = oracle.query_total_value(querier, &[debt_coin.clone()])?; - let request_value = oracle.query_total_value(querier, &[request_coin.clone()])?; - - if debt_value >= request_value { - return Err(ContractError::LiquidationNotProfitable { - debt_coin, - request_coin, - }); - } - - Ok(()) -} diff --git a/contracts/credit-manager/src/liquidate_lend.rs b/contracts/credit-manager/src/liquidate_lend.rs index 7028c28c1..212548e80 100644 --- a/contracts/credit-manager/src/liquidate_lend.rs +++ b/contracts/credit-manager/src/liquidate_lend.rs @@ -2,8 +2,10 @@ use cosmwasm_std::{Coin, DepsMut, Env, Response}; use mars_rover::error::ContractResult; use crate::{ - liquidate_deposit::{calculate_liquidation, repay_debt}, + liquidate::calculate_liquidation, + liquidate_deposit::repay_debt, reclaim::{current_lent_amount_for_denom, lent_amount_to_shares}, + state::REWARDS_COLLECTOR, utils::{decrement_lent_shares, increment_lent_shares}, }; @@ -23,7 +25,7 @@ pub fn liquidate_lend( request_coin_denom, )?; - let (debt, request) = calculate_liquidation( + let (debt, liquidator_request, liquidatee_request) = calculate_liquidation( &deps, &env, liquidatee_account_id, @@ -35,27 +37,44 @@ pub fn liquidate_lend( let repay_msg = repay_debt(deps.storage, &env, liquidator_account_id, liquidatee_account_id, &debt)?; - let shares_to_transfer = lent_amount_to_shares( + let shares_from_liquidatee = lent_amount_to_shares( deps.as_ref(), &env, &Coin { denom: request_coin_denom.to_string(), - amount: request.amount, + amount: liquidatee_request.amount, + }, + )?; + let shares_to_liquidator = lent_amount_to_shares( + deps.as_ref(), + &env, + &Coin { + denom: request_coin_denom.to_string(), + amount: liquidator_request.amount, }, )?; - // Transfer requested lent coin from liquidatee to liquidator decrement_lent_shares( deps.storage, liquidatee_account_id, request_coin_denom, - shares_to_transfer, + shares_from_liquidatee, )?; increment_lent_shares( deps.storage, liquidator_account_id, request_coin_denom, - shares_to_transfer, + shares_to_liquidator, + )?; + + // Transfer protocol fee to rewards-collector account + let rewards_collector_account = REWARDS_COLLECTOR.load(deps.storage)?.account_id; + let protocol_fee_shares = shares_from_liquidatee.checked_sub(shares_to_liquidator)?; + increment_lent_shares( + deps.storage, + &rewards_collector_account, + request_coin_denom, + protocol_fee_shares, )?; Ok(Response::new() @@ -64,5 +83,9 @@ pub fn liquidate_lend( .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("coin_debt_repaid", debt.to_string()) - .add_attribute("coin_liquidated", request.to_string())) + .add_attribute("coin_liquidated", liquidatee_request.to_string()) + .add_attribute( + "protocol_fee_coin", + Coin::new(protocol_fee_shares.u128(), request_coin_denom).to_string(), + )) } diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 07d84620f..37d1e2104 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -13,8 +13,8 @@ use mars_rover::{ use crate::{ state::{ ACCOUNT_NFT, COIN_BALANCES, DEBT_SHARES, HEALTH_CONTRACT, LENT_SHARES, - MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, - TOTAL_LENT_SHARES, VAULT_POSITIONS, ZAPPER, + MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, RED_BANK, REWARDS_COLLECTOR, SWAPPER, + TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, VAULT_POSITIONS, ZAPPER, }, utils::{debt_shares_to_amount, lent_shares_to_amount}, vault::vault_utilization_in_deposit_cap_denom, @@ -31,6 +31,7 @@ pub fn query_config(deps: Deps) -> ContractResult { swapper: SWAPPER.load(deps.storage)?.address().into(), zapper: ZAPPER.load(deps.storage)?.address().into(), health_contract: HEALTH_CONTRACT.load(deps.storage)?.address().into(), + rewards_collector: REWARDS_COLLECTOR.may_load(deps.storage)?.map(|rc| rc.address), }) } diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index f0a9c3331..2afd49c4c 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,3 +1,4 @@ +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::{Item, Map}; use mars_owner::Owner; @@ -9,6 +10,12 @@ use mars_rover_health_types::AccountKind; use crate::vault::RequestTempStorage; +#[cw_serde] +pub struct RewardsCollector { + pub address: String, + pub account_id: String, +} + // Contract dependencies // NOTE: Ensure assert_not_contract_in_config() is updated when an external contract is added here pub const ACCOUNT_NFT: Item = Item::new("account_nft"); @@ -36,3 +43,6 @@ pub const VAULT_POSITIONS: Map<(&str, Addr), VaultPositionAmount> = Map::new("va // Temporary state to save variables to be used on reply handling pub const VAULT_REQUEST_TEMP_STORAGE: Item = Item::new("vault_request_temp_var"); + +// (account id, addr) for rewards-collector contract +pub const REWARDS_COLLECTOR: Item = Item::new("rewards_collector"); diff --git a/contracts/credit-manager/src/update_coin_balances.rs b/contracts/credit-manager/src/update_coin_balances.rs index 3d0934f95..7b621c93c 100644 --- a/contracts/credit-manager/src/update_coin_balances.rs +++ b/contracts/credit-manager/src/update_coin_balances.rs @@ -1,10 +1,13 @@ use cosmwasm_std::{ - Addr, BalanceResponse, BankQuery, Coin, DepsMut, Env, QuerierWrapper, QueryRequest, Response, - StdResult, + Addr, BalanceResponse, BankQuery, Coin, Decimal, DepsMut, Env, QuerierWrapper, QueryRequest, + Response, StdResult, }; use mars_rover::error::ContractResult; -use crate::utils::{decrement_coin_balance, increment_coin_balance}; +use crate::{ + state::REWARDS_COLLECTOR, + utils::{decrement_coin_balance, increment_coin_balance}, +}; pub fn query_balance(querier: &QuerierWrapper, addr: &Addr, denom: &str) -> StdResult { let res: BalanceResponse = querier.query(&QueryRequest::Bank(BankQuery::Balance { @@ -48,3 +51,40 @@ pub fn update_coin_balance( .add_attribute("coin_incremented", coin_to_increment.to_string())) } } + +pub fn update_coin_balance_after_vault_liquidation( + deps: DepsMut, + env: Env, + account_id: &str, + prev: &Coin, + protocol_fee: Decimal, +) -> ContractResult { + let curr = query_balance(&deps.querier, &env.contract.address, &prev.denom)?; + let mut amount_to_increment = curr.amount.checked_sub(prev.amount)?; + + if !protocol_fee.is_zero() { + let protocol_fee_amt = amount_to_increment.checked_mul_ceil(protocol_fee)?; + amount_to_increment = amount_to_increment.checked_sub(protocol_fee_amt)?; + + let rewards_collector_account = REWARDS_COLLECTOR.load(deps.storage)?.account_id; + increment_coin_balance( + deps.storage, + &rewards_collector_account, + &Coin { + denom: curr.denom.clone(), + amount: protocol_fee_amt, + }, + )?; + }; + + let coin_to_increment = Coin { + denom: curr.denom, + amount: amount_to_increment, + }; + increment_coin_balance(deps.storage, account_id, &coin_to_increment)?; + + Ok(Response::new() + .add_attribute("action", "update_coin_balance_after_vault_liquidation") + .add_attribute("account_id", account_id) + .add_attribute("coin_incremented", coin_to_increment.to_string())) +} diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index e5417bd99..6bc7cf04d 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -3,9 +3,14 @@ use cw721_base::Action; use mars_account_nft::{msg::ExecuteMsg as NftExecuteMsg, nft_config::NftConfigUpdates}; use mars_owner::OwnerUpdate; use mars_rover::{error::ContractResult, msg::instantiate::ConfigUpdates}; - -use crate::state::{ - ACCOUNT_NFT, HEALTH_CONTRACT, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, SWAPPER, ZAPPER, +use mars_rover_health_types::AccountKind; + +use crate::{ + execute::create_credit_account, + state::{ + RewardsCollector, ACCOUNT_NFT, HEALTH_CONTRACT, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, + RED_BANK, REWARDS_COLLECTOR, SWAPPER, ZAPPER, + }, }; pub fn update_config( @@ -72,6 +77,27 @@ pub fn update_config( .add_attribute("value", unchecked.address()); } + if let Some(unchecked) = updates.rewards_collector { + let rewards_collector_addr = deps.api.addr_validate(&unchecked)?; + + let account_nft = ACCOUNT_NFT.load(deps.storage)?; + let next_id = account_nft.query_next_id(&deps.querier)?; + REWARDS_COLLECTOR.save( + deps.storage, + &RewardsCollector { + address: rewards_collector_addr.to_string(), + account_id: next_id.clone(), + }, + )?; + + let res = create_credit_account(deps, rewards_collector_addr, AccountKind::Default)?; + + response = response + .add_submessages(res.messages) + .add_attribute("key", "rewards_collector_account") + .add_attribute("value", next_id); + } + Ok(response) } diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 0522c874b..38ef12135 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -142,6 +142,27 @@ pub fn update_balances_msgs( denoms.iter().map(|denom| update_balance_msg(querier, rover_addr, account_id, denom)).collect() } +pub fn update_balance_after_vault_liquidation_msg( + querier: &QuerierWrapper, + rover_addr: &Addr, + account_id: &str, + denom: &str, + protocol_fee: Decimal, +) -> StdResult { + let previous_balance = query_balance(querier, rover_addr, denom)?; + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: rover_addr.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::Callback( + CallbackMsg::UpdateCoinBalanceAfterVaultLiquidation { + account_id: account_id.to_string(), + previous_balance, + protocol_fee, + }, + ))?, + })) +} + pub fn debt_shares_to_amount( deps: Deps, rover_addr: &Addr, diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index 9a50f36b5..03f2c8c2d 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -1,6 +1,6 @@ use std::cmp::min; -use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; +use cosmwasm_std::{Coin, Decimal, DepsMut, Env, Response, Uint128}; use cw_vault_standard::VaultInfoResponse; use mars_rover::{ adapters::vault::{ @@ -11,10 +11,8 @@ use mars_rover::{ }; use crate::{ - liquidate_deposit::{calculate_liquidation, repay_debt}, - state::VAULT_POSITIONS, - utils::update_balance_msg, - vault::update_vault_position, + liquidate::calculate_liquidation, liquidate_deposit::repay_debt, state::VAULT_POSITIONS, + utils::update_balance_after_vault_liquidation_msg, vault::update_vault_position, }; pub fn liquidate_vault( @@ -77,7 +75,7 @@ fn liquidate_unlocked( ) -> ContractResult { let vault_info = request_vault.query_info(&deps.querier)?; - let (debt, request) = calculate_vault_liquidation( + let (debt, liquidator_request, liquidatee_request) = calculate_vault_liquidation( &deps, &env, liquidatee_account_id, @@ -94,16 +92,22 @@ fn liquidate_unlocked( deps.storage, liquidatee_account_id, &request_vault.address, - VaultPositionUpdate::Unlocked(UpdateType::Decrement(request.amount)), + VaultPositionUpdate::Unlocked(UpdateType::Decrement(liquidatee_request.amount)), )?; - let vault_withdraw_msg = request_vault.withdraw_msg(&deps.querier, request.amount)?; + let vault_withdraw_msg = + request_vault.withdraw_msg(&deps.querier, liquidatee_request.amount)?; + + let protocol_fee = liquidatee_request.amount.checked_sub(liquidator_request.amount)?; + let protocol_fee_percentage = + Decimal::checked_from_ratio(protocol_fee, liquidatee_request.amount)?; - let update_coin_balance_msg = update_balance_msg( + let update_coin_balance_msg = update_balance_after_vault_liquidation_msg( &deps.querier, &env.contract.address, liquidator_account_id, &vault_info.base_token, + protocol_fee_percentage, )?; Ok(Response::new() @@ -114,7 +118,11 @@ fn liquidate_unlocked( .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("coin_debt_repaid", debt.to_string()) - .add_attribute("coin_liquidated", request.to_string())) + .add_attribute("coin_liquidated", liquidatee_request.to_string()) + .add_attribute( + "protocol_fee_coin", + Coin::new(protocol_fee.u128(), liquidatee_request.denom).to_string(), + )) } /// Converts vault coins to their underlying value. This allows for pricing and liquidation @@ -127,9 +135,9 @@ fn calculate_vault_liquidation( request_vault: &Vault, amount: Uint128, vault_info: &VaultInfoResponse, -) -> ContractResult<(Coin, Coin)> { +) -> ContractResult<(Coin, Coin, Coin)> { let total_underlying = request_vault.query_preview_redeem(&deps.querier, amount)?; - let (debt, mut request) = calculate_liquidation( + let (debt, mut liquidator_request, mut liquidatee_request) = calculate_liquidation( deps, env, liquidatee_account_id, @@ -137,9 +145,13 @@ fn calculate_vault_liquidation( &vault_info.base_token, total_underlying, )?; - request.denom = vault_info.vault_token.clone(); - request.amount = amount.checked_multiply_ratio(request.amount, total_underlying)?; - Ok((debt, request)) + liquidatee_request.denom = vault_info.vault_token.clone(); + liquidatee_request.amount = + amount.checked_multiply_ratio(liquidatee_request.amount, total_underlying)?; + liquidator_request.denom = vault_info.vault_token.clone(); + liquidator_request.amount = + amount.checked_multiply_ratio(liquidator_request.amount, total_underlying)?; + Ok((debt, liquidator_request, liquidatee_request)) } fn liquidate_unlocking( @@ -153,7 +165,7 @@ fn liquidate_unlocking( ) -> ContractResult { let vault_info = request_vault.query_info(&deps.querier)?; - let (debt, request) = calculate_liquidation( + let (debt, liquidator_request, liquidatee_request) = calculate_liquidation( &deps, &env, liquidatee_account_id, @@ -165,7 +177,7 @@ fn liquidate_unlocking( let repay_msg = repay_debt(deps.storage, &env, liquidator_account_id, liquidatee_account_id, &debt)?; - let mut total_to_liquidate = request.amount; + let mut total_to_liquidate = liquidatee_request.amount; let mut vault_withdraw_msgs = vec![]; for u in unlocking_positions.positions() { @@ -191,11 +203,16 @@ fn liquidate_unlocking( total_to_liquidate = total_to_liquidate.checked_sub(amount)?; } - let update_coin_balance_msg = update_balance_msg( + let protocol_fee = liquidatee_request.amount.checked_sub(liquidator_request.amount)?; + let protocol_fee_percentage = + Decimal::checked_from_ratio(protocol_fee, liquidatee_request.amount)?; + + let update_coin_balance_msg = update_balance_after_vault_liquidation_msg( &deps.querier, &env.contract.address, liquidator_account_id, &vault_info.base_token, + protocol_fee_percentage, )?; Ok(Response::new() @@ -206,7 +223,11 @@ fn liquidate_unlocking( .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("coin_debt_repaid", debt.to_string()) - .add_attribute("coin_liquidated", request.to_string())) + .add_attribute("coin_liquidated", liquidatee_request.to_string()) + .add_attribute( + "protocol_fee_coin", + Coin::new(protocol_fee.u128(), liquidatee_request.denom).to_string(), + )) } fn liquidate_locked( @@ -220,7 +241,7 @@ fn liquidate_locked( ) -> ContractResult { let vault_info = request_vault.query_info(&deps.querier)?; - let (debt, request) = calculate_vault_liquidation( + let (debt, liquidator_request, liquidatee_request) = calculate_vault_liquidation( &deps, &env, liquidatee_account_id, @@ -237,17 +258,22 @@ fn liquidate_locked( deps.storage, liquidatee_account_id, &request_vault.address, - VaultPositionUpdate::Locked(UpdateType::Decrement(request.amount)), + VaultPositionUpdate::Locked(UpdateType::Decrement(liquidatee_request.amount)), )?; let vault_withdraw_msg = - request_vault.force_withdraw_locked_msg(&deps.querier, request.amount)?; + request_vault.force_withdraw_locked_msg(&deps.querier, liquidatee_request.amount)?; + + let protocol_fee = liquidatee_request.amount.checked_sub(liquidator_request.amount)?; + let protocol_fee_percentage = + Decimal::checked_from_ratio(protocol_fee, liquidatee_request.amount)?; - let update_coin_balance_msg = update_balance_msg( + let update_coin_balance_msg = update_balance_after_vault_liquidation_msg( &deps.querier, &env.contract.address, liquidator_account_id, &vault_info.base_token, + protocol_fee_percentage, )?; Ok(Response::new() @@ -258,5 +284,9 @@ fn liquidate_locked( .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("coin_debt_repaid", debt.to_string()) - .add_attribute("coin_liquidated", request.to_string())) + .add_attribute("coin_liquidated", liquidatee_request.to_string()) + .add_attribute( + "protocol_fee_coin", + Coin::new(protocol_fee.u128(), liquidatee_request.denom).to_string(), + )) } diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index facc20911..2c5639aa2 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{coin, Decimal}; use cw_utils::Duration; +use mars_params::types::asset::LiquidationBonus; use crate::helpers::{lp_token_info, CoinInfo, VaultTestInfo}; @@ -10,7 +11,13 @@ pub fn build_mock_coin_infos(count: usize) -> Vec { max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), - liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, }) diff --git a/contracts/credit-manager/tests/helpers/mock_entity_info.rs b/contracts/credit-manager/tests/helpers/mock_entity_info.rs index 95ed30f38..fa7b18c7a 100644 --- a/contracts/credit-manager/tests/helpers/mock_entity_info.rs +++ b/contracts/credit-manager/tests/helpers/mock_entity_info.rs @@ -2,7 +2,10 @@ use std::str::FromStr; use cosmwasm_std::{coin, Decimal}; use cw_utils::Duration; -use mars_params::types::hls::{HlsAssetType, HlsParamsUnchecked}; +use mars_params::types::{ + asset::LiquidationBonus, + hls::{HlsAssetType, HlsParamsUnchecked}, +}; use crate::helpers::{CoinInfo, VaultTestInfo}; @@ -12,7 +15,13 @@ pub fn uosmo_info() -> CoinInfo { price: Decimal::from_atomics(25u128, 2).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, } @@ -24,7 +33,13 @@ pub fn uatom_info() -> CoinInfo { price: Decimal::from_atomics(10u128, 1).unwrap(), max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), - liquidation_bonus: Decimal::from_atomics(10u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: Some(HlsParamsUnchecked { max_loan_to_value: Decimal::from_str("0.86").unwrap(), @@ -50,7 +65,13 @@ pub fn ujake_info() -> CoinInfo { price: Decimal::from_atomics(23654u128, 4).unwrap(), max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), - liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: Some(HlsParamsUnchecked { max_loan_to_value: Decimal::from_str("0.7").unwrap(), @@ -66,7 +87,13 @@ pub fn blacklisted_coin() -> CoinInfo { price: Decimal::from_str("0.01").unwrap(), max_ltv: Decimal::from_str("0.4").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), - liquidation_bonus: Decimal::from_str("0.33").unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: false, hls: None, } @@ -78,7 +105,13 @@ pub fn lp_token_info() -> CoinInfo { price: Decimal::from_atomics(9874u128, 3).unwrap(), max_ltv: Decimal::from_atomics(63u128, 2).unwrap(), liquidation_threshold: Decimal::from_atomics(68u128, 2).unwrap(), - liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: Some(HlsParamsUnchecked { max_loan_to_value: Decimal::from_str("0.75").unwrap(), diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index d0815aed3..51ac15507 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -2,6 +2,7 @@ use std::{default::Default, mem::take, str::FromStr}; use anyhow::Result as AnyResult; use cosmwasm_std::{coins, testing::MockApi, Addr, Coin, Decimal, Empty, StdResult, Uint128}; +use cw721::TokensResponse; use cw721_base::{Action::TransferOwnership, Ownership}; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use cw_vault_standard::{ @@ -96,7 +97,7 @@ pub struct MockEnvBuilder { pub deploy_nft_contract: bool, pub set_nft_contract_minter: bool, pub accounts_to_fund: Vec, - pub max_close_factor: Option, + pub target_health_factor: Option, pub max_unlocking_positions: Option, pub health_contract: Option, } @@ -116,7 +117,7 @@ impl MockEnv { deploy_nft_contract: true, set_nft_contract_minter: true, accounts_to_fund: vec![], - max_close_factor: None, + target_health_factor: None, max_unlocking_positions: None, health_contract: None, } @@ -366,6 +367,23 @@ impl MockEnv { .unwrap() } + pub fn query_rewards_collector_account(&self) -> String { + let config = self.query_config(); + let response: TokensResponse = self + .app + .wrap() + .query_wasm_smart( + config.account_nft.unwrap(), + &NftQueryMsg::Tokens { + owner: config.rewards_collector.unwrap(), + start_after: None, + limit: None, + }, + ) + .unwrap(); + response.tokens.first().unwrap().to_string() + } + pub fn query_vault_params(&self, vault_addr: &str) -> VaultConfig { self.app .wrap() @@ -671,6 +689,17 @@ impl MockEnvBuilder { self.update_health_contract_config(&rover, params.address()); self.deploy_nft_contract(&rover); + + if self.deploy_nft_contract && self.set_nft_contract_minter { + self.update_config( + &rover, + ConfigUpdates { + rewards_collector: Some("rewards_collector_contract".to_string()), + ..Default::default() + }, + ); + } + self.fund_users(); self.deploy_vaults(); @@ -871,9 +900,9 @@ impl MockEnvBuilder { owner.clone(), &ParamsInstantiateMsg { owner: owner.to_string(), - max_close_factor: self - .max_close_factor - .unwrap_or(Decimal::from_str("0.5").unwrap()), + target_health_factor: self + .target_health_factor + .unwrap_or(Decimal::from_str("1.2").unwrap()), }, &[], "mock-params-contract", @@ -1159,8 +1188,8 @@ impl MockEnvBuilder { self } - pub fn max_close_factor(&mut self, cf: Decimal) -> &mut Self { - self.max_close_factor = Some(cf); + pub fn target_health_factor(&mut self, thf: Decimal) -> &mut Self { + self.target_health_factor = Some(thf); self } diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 8525d5648..c0287baf2 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; use cw_utils::Duration; use mars_params::types::{ - asset::{AssetParamsUnchecked, CmSettings, RedBankSettings}, + asset::{AssetParamsUnchecked, CmSettings, LiquidationBonus, RedBankSettings}, hls::HlsParamsUnchecked, }; use mars_rover::msg::execute::{ActionAmount, ActionCoin}; @@ -19,9 +19,10 @@ pub struct CoinInfo { pub price: Decimal, pub max_ltv: Decimal, pub liquidation_threshold: Decimal, - pub liquidation_bonus: Decimal, + pub liquidation_bonus: LiquidationBonus, pub whitelisted: bool, pub hls: Option, + pub protocol_liquidation_fee: Decimal, } #[cw_serde] @@ -81,6 +82,7 @@ impl From for AssetParamsUnchecked { max_loan_to_value: c.max_ltv, liquidation_threshold: c.liquidation_threshold, liquidation_bonus: c.liquidation_bonus, + protocol_liquidation_fee: c.protocol_liquidation_fee, } } } diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index 41a0d14b7..a3389ce0c 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -3,7 +3,10 @@ use std::ops::{Add, Mul}; use cosmwasm_std::{coin, coins, Addr, Coin, Decimal, Uint128}; use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use mars_mock_oracle::msg::CoinPrice; -use mars_params::msg::{AssetParamsUpdate::AddOrUpdate, VaultConfigUpdate}; +use mars_params::{ + msg::{AssetParamsUpdate::AddOrUpdate, VaultConfigUpdate}, + types::asset::LiquidationBonus, +}; use mars_rover::{ error::ContractError, msg::{ @@ -86,7 +89,13 @@ fn terra_ragnarok() { price: Decimal::from_atomics(100u128, 1).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, }; @@ -301,7 +310,13 @@ fn cannot_borrow_more_but_not_liquidatable() { price: Decimal::from_atomics(23654u128, 4).unwrap(), max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), - liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, }; @@ -310,7 +325,13 @@ fn cannot_borrow_more_but_not_liquidatable() { price: Decimal::from_atomics(102u128, 1).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(75u128, 2).unwrap(), - liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, }; @@ -381,7 +402,13 @@ fn assets_and_ltv_lqdt_adjusted_value() { price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, }; @@ -390,7 +417,13 @@ fn assets_and_ltv_lqdt_adjusted_value() { price: Decimal::from_atomics(7012302005u128, 3).unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), - liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, }; @@ -491,7 +524,13 @@ fn debt_value() { price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), max_ltv: Decimal::from_atomics(3u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), - liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, }; @@ -500,7 +539,13 @@ fn debt_value() { price: Decimal::from_atomics(7012302005u128, 3).unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), - liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, }; @@ -814,7 +859,13 @@ fn can_take_actions_if_ltv_does_not_weaken() { price: Decimal::from_atomics(23654u128, 4).unwrap(), max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), - liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, }; @@ -823,7 +874,13 @@ fn can_take_actions_if_ltv_does_not_weaken() { price: Decimal::from_atomics(102u128, 1).unwrap(), max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(75u128, 2).unwrap(), - liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, }; diff --git a/contracts/credit-manager/tests/test_liquidate_deposit.rs b/contracts/credit-manager/tests/test_liquidate_deposit.rs index d0cbdf656..57ee2bc0b 100644 --- a/contracts/credit-manager/tests/test_liquidate_deposit.rs +++ b/contracts/credit-manager/tests/test_liquidate_deposit.rs @@ -6,7 +6,7 @@ use mars_rover::{ ContractError::{AboveMaxLTV, LiquidationNotProfitable, NotLiquidatable}, }, msg::execute::{ - Action::{Borrow, Deposit, EnterVault, Liquidate}, + Action::{Borrow, Deposit, EnterVault, Liquidate, Withdraw}, LiquidateRequest, }, }; @@ -20,7 +20,7 @@ use crate::helpers::{ pub mod helpers; // Reference figures behind various scenarios -// https://docs.google.com/spreadsheets/d/1_Bs1Fc1RLf5IARvaXZ0QjigoMWSJQhhrRUtQ8uyoLdI/edit?pli=1#gid=1857897311 +// https://docs.google.com/spreadsheets/d/1H7Ajghsee2l7_litG7EWoM-kkVQOh4dbHa8WSV-Y6Jg/edit#gid=1331087474 #[test] fn can_only_liquidate_unhealthy_accounts() { @@ -363,7 +363,7 @@ fn liquidator_left_in_unhealthy_state() { res, AboveMaxLTV { account_id: liquidator_account_id, - max_ltv_health_factor: "0.727272727272727272".to_string(), + max_ltv_health_factor: "0.70909090909090909".to_string(), }, ) } @@ -437,21 +437,22 @@ fn liquidation_not_profitable_after_calculations() { } #[test] -fn debt_amount_adjusted_to_close_factor_max() { +fn target_health_factor_reached_after_max_debt_repayed() { let uosmo_info = uosmo_info(); let uatom_info = uatom_info(); let liquidator = Addr::unchecked("liquidator"); let liquidatee = Addr::unchecked("liquidatee"); + let thf = Decimal::from_atomics(12u128, 1).unwrap(); let mut mock = MockEnv::new() - .max_close_factor(Decimal::from_atomics(1u128, 1).unwrap()) + .target_health_factor(thf) .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: coins(300, uosmo_info.denom.clone()), + funds: coins(3000, uosmo_info.denom.clone()), }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: coins(300, uatom_info.denom.clone()), + funds: coins(3000, uatom_info.denom.clone()), }) .build() .unwrap(); @@ -460,14 +461,18 @@ fn debt_amount_adjusted_to_close_factor_max() { mock.update_credit_account( &liquidatee_account_id, &liquidatee, - vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(100))], - &[Coin::new(300, uosmo_info.denom.clone())], + vec![ + Deposit(uosmo_info.to_coin(3000)), + Borrow(uatom_info.to_coin(1000)), + Withdraw(uatom_info.to_coin(400)), + ], + &[Coin::new(3000, uosmo_info.denom.clone())], ) .unwrap(); mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(6u128, 0).unwrap(), + price: Decimal::from_atomics(128u128, 2).unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -476,14 +481,14 @@ fn debt_amount_adjusted_to_close_factor_max() { &liquidator_account_id, &liquidator, vec![ - Deposit(uatom_info.to_coin(50)), + Deposit(uatom_info.to_coin(561)), // MDR = 525, refund 36 Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: uatom_info.to_coin(50), + debt_coin: uatom_info.to_coin(561), request: LiquidateRequest::Deposit(uosmo_info.denom), }, ], - &[uatom_info.to_coin(50)], + &[uatom_info.to_coin(561)], ) .unwrap(); @@ -491,22 +496,37 @@ fn debt_amount_adjusted_to_close_factor_max() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.deposits.len(), 2); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(36)); + assert_eq!(osmo_balance.amount, Uint128::new(208)); let atom_balance = get_coin("uatom", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(100)); + assert_eq!(atom_balance.amount, Uint128::new(600)); assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("uatom", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(91)); + assert_eq!(atom_debt.amount, Uint128::new(476)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.deposits.len(), 2); assert_eq!(position.debts.len(), 0); let atom_balance = get_coin("uatom", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(40)); + assert_eq!(atom_balance.amount, Uint128::new(36)); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(264)); + assert_eq!(osmo_balance.amount, Uint128::new(2740)); + + // Assert rewards-collector's new position + let rewards_collector_acc_id = mock.query_rewards_collector_account(); + let position = mock.query_positions(&rewards_collector_acc_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.debts.len(), 0); + let atom_balance = get_coin("uosmo", &position.deposits); + assert_eq!(atom_balance.amount, Uint128::new(52)); + + // Assert HF for liquidatee + let account_kind = mock.query_account_kind(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, account_kind); + // it should be 1.2, but because of roundings it is hard to achieve an exact number + let health_diff = health.liquidation_health_factor.unwrap().abs_diff(thf); + assert!(health_diff < Decimal::from_atomics(1u128, 2u32).unwrap()); } #[test] @@ -517,15 +537,15 @@ fn debt_amount_adjusted_to_total_debt_for_denom() { let liquidator = Addr::unchecked("liquidator"); let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .max_close_factor(Decimal::from_atomics(1u128, 1).unwrap()) + .target_health_factor(Decimal::from_atomics(12u128, 1).unwrap()) .set_params(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: coins(300, uosmo_info.denom.clone()), + funds: coins(3000, uosmo_info.denom.clone()), }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: coins(300, ujake_info.denom.clone()), + funds: coins(3000, ujake_info.denom.clone()), }) .build() .unwrap(); @@ -535,17 +555,17 @@ fn debt_amount_adjusted_to_total_debt_for_denom() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(100)), - Borrow(ujake_info.to_coin(10)), + Deposit(uosmo_info.to_coin(3000)), + Borrow(uatom_info.to_coin(1000)), + Borrow(ujake_info.to_coin(100)), ], - &[Coin::new(300, uosmo_info.denom.clone())], + &[Coin::new(3000, uosmo_info.denom.clone())], ) .unwrap(); mock.price_change(CoinPrice { denom: uatom_info.denom, - price: Decimal::from_atomics(20u128, 0).unwrap(), + price: Decimal::from_atomics(5u128, 0).unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -554,14 +574,14 @@ fn debt_amount_adjusted_to_total_debt_for_denom() { &liquidator_account_id, &liquidator, vec![ - Deposit(ujake_info.to_coin(50)), + Deposit(ujake_info.to_coin(101)), Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: ujake_info.to_coin(50), + debt_coin: ujake_info.to_coin(101), request: LiquidateRequest::Deposit(uosmo_info.denom), }, ], - &[ujake_info.to_coin(50)], + &[ujake_info.to_coin(101)], ) .unwrap(); @@ -569,24 +589,35 @@ fn debt_amount_adjusted_to_total_debt_for_denom() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.deposits.len(), 3); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(184)); + assert_eq!(osmo_balance.amount, Uint128::new(2012)); let atom_balance = get_coin("uatom", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(100)); + assert_eq!(atom_balance.amount, Uint128::new(1000)); let jake_balance = get_coin("ujake", &position.deposits); - assert_eq!(jake_balance.amount, Uint128::new(10)); + assert_eq!(jake_balance.amount, Uint128::new(100)); assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("uatom", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(101)); + assert_eq!(atom_debt.amount, Uint128::new(1001)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.deposits.len(), 2); + assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); - let jake_balance = get_coin("ujake", &position.deposits); - assert_eq!(jake_balance.amount, Uint128::new(39)); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(116)); + assert_eq!(osmo_balance.amount, Uint128::new(972)); + + // Assert rewards-collector's new position + let rewards_collector_acc_id = mock.query_rewards_collector_account(); + let position = mock.query_positions(&rewards_collector_acc_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.debts.len(), 0); + let atom_balance = get_coin("uosmo", &position.deposits); + assert_eq!(atom_balance.amount, Uint128::new(16)); + + // Liq HF should improve + let account_kind = mock.query_account_kind(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, account_kind); + assert!(!health.liquidatable); } #[test] @@ -599,11 +630,11 @@ fn debt_amount_adjusted_to_max_allowed_by_request_coin() { .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: coins(300, uosmo_info.denom.clone()), + funds: coins(3000, uosmo_info.denom.clone()), }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: coins(300, uatom_info.denom.clone()), + funds: coins(3000, uatom_info.denom.clone()), }) .build() .unwrap(); @@ -612,14 +643,14 @@ fn debt_amount_adjusted_to_max_allowed_by_request_coin() { mock.update_credit_account( &liquidatee_account_id, &liquidatee, - vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(100))], - &[Coin::new(300, uosmo_info.denom.clone())], + vec![Deposit(uosmo_info.to_coin(3000)), Borrow(uatom_info.to_coin(1000))], + &[Coin::new(3000, uosmo_info.denom.clone())], ) .unwrap(); mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(20u128, 0).unwrap(), + price: Decimal::from_atomics(6u128, 0).unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -628,14 +659,14 @@ fn debt_amount_adjusted_to_max_allowed_by_request_coin() { &liquidator_account_id, &liquidator, vec![ - Deposit(uatom_info.to_coin(50)), + Deposit(uatom_info.to_coin(144)), Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: uatom_info.to_coin(50), + debt_coin: uatom_info.to_coin(120), request: LiquidateRequest::Deposit(uosmo_info.denom), }, ], - &[uatom_info.to_coin(50)], + &[uatom_info.to_coin(144)], ) .unwrap(); @@ -643,22 +674,35 @@ fn debt_amount_adjusted_to_max_allowed_by_request_coin() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.deposits.len(), 2); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(36)); + assert_eq!(osmo_balance.amount, Uint128::new(16)); let atom_balance = get_coin("uatom", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(100)); + assert_eq!(atom_balance.amount, Uint128::new(1000)); assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("uatom", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(98)); + assert_eq!(atom_debt.amount, Uint128::new(881)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.deposits.len(), 2); assert_eq!(position.debts.len(), 0); let atom_balance = get_coin("uatom", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(47)); + assert_eq!(atom_balance.amount, Uint128::new(24)); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(264)); + assert_eq!(osmo_balance.amount, Uint128::new(2928)); + + // Assert rewards-collector's new position + let rewards_collector_acc_id = mock.query_rewards_collector_account(); + let position = mock.query_positions(&rewards_collector_acc_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.debts.len(), 0); + let atom_balance = get_coin("uosmo", &position.deposits); + assert_eq!(atom_balance.amount, Uint128::new(56)); + + // Liq HF should improve + let account_kind = mock.query_account_kind(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, account_kind); + assert!(!health.liquidatable); } #[test] @@ -668,15 +712,15 @@ fn debt_amount_no_adjustment() { let liquidator = Addr::unchecked("liquidator"); let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .max_close_factor(Decimal::from_atomics(1u128, 1).unwrap()) + .target_health_factor(Decimal::from_atomics(12u128, 1).unwrap()) .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: coins(300, uosmo_info.denom.clone()), + funds: coins(3000, uosmo_info.denom.clone()), }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: coins(300, uatom_info.denom.clone()), + funds: coins(3000, uatom_info.denom.clone()), }) .build() .unwrap(); @@ -685,14 +729,14 @@ fn debt_amount_no_adjustment() { mock.update_credit_account( &liquidatee_account_id, &liquidatee, - vec![Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(100))], - &[Coin::new(300, uosmo_info.denom.clone())], + vec![Deposit(uosmo_info.to_coin(3000)), Borrow(uatom_info.to_coin(1000))], + &[Coin::new(3000, uosmo_info.denom.clone())], ) .unwrap(); mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(55u128, 1).unwrap(), + price: Decimal::from_atomics(59u128, 1).unwrap(), }); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -701,14 +745,14 @@ fn debt_amount_no_adjustment() { &liquidator_account_id, &liquidator, vec![ - Deposit(uatom_info.to_coin(10)), + Deposit(uatom_info.to_coin(100)), Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: uatom_info.to_coin(10), + debt_coin: uatom_info.to_coin(100), request: LiquidateRequest::Deposit(uosmo_info.denom), }, ], - &[uatom_info.to_coin(10)], + &[uatom_info.to_coin(100)], ) .unwrap(); @@ -716,21 +760,131 @@ fn debt_amount_no_adjustment() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.deposits.len(), 2); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(60)); + assert_eq!(osmo_balance.amount, Uint128::new(564)); let atom_balance = get_coin("uatom", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(100)); + assert_eq!(atom_balance.amount, Uint128::new(1000)); assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("uatom", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(91)); + assert_eq!(atom_debt.amount, Uint128::new(901)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(240)); + assert_eq!(osmo_balance.amount, Uint128::new(2392)); + + // Assert rewards-collector's new position + let rewards_collector_acc_id = mock.query_rewards_collector_account(); + let position = mock.query_positions(&rewards_collector_acc_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.debts.len(), 0); + let atom_balance = get_coin("uosmo", &position.deposits); + assert_eq!(atom_balance.amount, Uint128::new(44)); + + // Liq HF should improve + let account_kind = mock.query_account_kind(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, account_kind); + assert!(!health.liquidatable); } #[test] -fn liquidate_with_no_deposited_funds() {} +fn improve_hf_but_acc_unhealthy() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + let ujake_info = ujake_info(); + let liquidator = Addr::unchecked("liquidator"); + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .target_health_factor(Decimal::from_atomics(12u128, 1).unwrap()) + .set_params(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: coins(4000, uosmo_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: coins(4000, ujake_info.denom.clone()), + }) + .build() + .unwrap(); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![ + Deposit(uosmo_info.to_coin(4000)), + Borrow(uatom_info.to_coin(1000)), + Borrow(ujake_info.to_coin(430)), + ], + &[Coin::new(4000, uosmo_info.denom.clone())], + ) + .unwrap(); + + mock.price_change(CoinPrice { + denom: uatom_info.denom, + price: Decimal::from_atomics(10u128, 0).unwrap(), + }); + + let account_kind = mock.query_account_kind(&liquidatee_account_id); + let prev_health = mock.query_health(&liquidatee_account_id, account_kind.clone()); + + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(ujake_info.to_coin(138)), + Liquidate { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: ujake_info.to_coin(120), + request: LiquidateRequest::Deposit(uosmo_info.denom), + }, + ], + &[ujake_info.to_coin(138)], + ) + .unwrap(); + + // Assert liquidatee's new position + let position = mock.query_positions(&liquidatee_account_id); + assert_eq!(position.deposits.len(), 3); + let osmo_balance = get_coin("uosmo", &position.deposits); + assert_eq!(osmo_balance.amount, Uint128::new(2748)); + let atom_balance = get_coin("uatom", &position.deposits); + assert_eq!(atom_balance.amount, Uint128::new(1000)); + let jake_balance = get_coin("ujake", &position.deposits); + assert_eq!(jake_balance.amount, Uint128::new(430)); + + assert_eq!(position.debts.len(), 2); + let atom_debt = get_debt("uatom", &position.debts); + assert_eq!(atom_debt.amount, Uint128::new(1001)); + let jake_debt = get_debt("ujake", &position.debts); + assert_eq!(jake_debt.amount, Uint128::new(311)); + + // Assert liquidator's new position + let position = mock.query_positions(&liquidator_account_id); + assert_eq!(position.deposits.len(), 2); + assert_eq!(position.debts.len(), 0); + let osmo_balance = get_coin("uosmo", &position.deposits); + assert_eq!(osmo_balance.amount, Uint128::new(1232)); + let jake_balance = get_coin("ujake", &position.deposits); + assert_eq!(jake_balance.amount, Uint128::new(18)); + + // Assert rewards-collector's new position + let rewards_collector_acc_id = mock.query_rewards_collector_account(); + let position = mock.query_positions(&rewards_collector_acc_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.debts.len(), 0); + let atom_balance = get_coin("uosmo", &position.deposits); + assert_eq!(atom_balance.amount, Uint128::new(20)); + + // Liq HF should improve + let health = mock.query_health(&liquidatee_account_id, account_kind); + assert!(health.liquidatable); + assert!( + prev_health.liquidation_health_factor.unwrap() < health.liquidation_health_factor.unwrap() + ); +} diff --git a/contracts/credit-manager/tests/test_liquidate_lend.rs b/contracts/credit-manager/tests/test_liquidate_lend.rs index 700907355..384073b10 100644 --- a/contracts/credit-manager/tests/test_liquidate_lend.rs +++ b/contracts/credit-manager/tests/test_liquidate_lend.rs @@ -16,6 +16,9 @@ use crate::helpers::{ pub mod helpers; +// Reference figures behind various scenarios +// https://docs.google.com/spreadsheets/d/1H7Ajghsee2l7_litG7EWoM-kkVQOh4dbHa8WSV-Y6Jg/edit#gid=1331087474 + #[test] fn lent_positions_contribute_to_health() { let uatom_info = uatom_info(); @@ -158,15 +161,15 @@ fn lent_position_partially_liquidated() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .max_close_factor(Decimal::from_atomics(6u128, 1).unwrap()) + .target_health_factor(Decimal::from_atomics(12u128, 1).unwrap()) .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: coins(300, uosmo_info.denom.clone()), + funds: coins(2000, uosmo_info.denom.clone()), }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: coins(300, uatom_info.denom.clone()), + funds: coins(2000, uatom_info.denom.clone()), }) .build() .unwrap(); @@ -177,23 +180,23 @@ fn lent_position_partially_liquidated() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(100)), - Lend(uosmo_info.to_coin(202)), + Deposit(uosmo_info.to_coin(1050)), + Borrow(uatom_info.to_coin(1000)), + Lend(uosmo_info.to_coin(450)), ], - &[uosmo_info.to_coin(300)], + &[uosmo_info.to_coin(1050)], ) .unwrap(); mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(55u128, 1).unwrap(), + price: Decimal::from_atomics(22u128, 1).unwrap(), }); let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(health.liquidatable); - assert_eq!(health.total_collateral_value, Uint128::new(624u128)); - assert_eq!(health.total_debt_value, Uint128::new(556u128)); + assert_eq!(health.total_collateral_value, Uint128::new(2462u128)); + assert_eq!(health.total_debt_value, Uint128::new(2203u128)); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -201,14 +204,14 @@ fn lent_position_partially_liquidated() { &liquidator_account_id, &liquidator, vec![ - Deposit(uatom_info.to_coin(6)), + Deposit(uatom_info.to_coin(45)), Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: uatom_info.to_coin(6), + debt_coin: uatom_info.to_coin(45), request: LiquidateRequest::Lend(uosmo_info.denom), }, ], - &[uatom_info.to_coin(6)], + &[uatom_info.to_coin(45)], ) .unwrap(); @@ -216,17 +219,17 @@ fn lent_position_partially_liquidated() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.deposits.len(), 2); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(98)); + assert_eq!(osmo_balance.amount, Uint128::new(600)); let atom_balance = get_coin("uatom", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(100)); + assert_eq!(atom_balance.amount, Uint128::new(1000)); assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("uatom", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(95)); + assert_eq!(atom_debt.amount, Uint128::new(956)); assert_eq!(position.lends.len(), 1); let osmo_lent = get_lent("uosmo", &position.lends); - assert_eq!(osmo_lent.amount, Uint128::new(59)); + assert_eq!(osmo_lent.amount, Uint128::new(39)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); @@ -235,7 +238,22 @@ fn lent_position_partially_liquidated() { assert_eq!(position.lends.len(), 1); let osmo_lent = get_lent("uosmo", &position.lends); - assert_eq!(osmo_lent.amount, Uint128::new(143)); + assert_eq!(osmo_lent.amount, Uint128::new(403)); + + // Assert rewards-collector's new position + let rewards_collector_acc_id = mock.query_rewards_collector_account(); + let position = mock.query_positions(&rewards_collector_acc_id); + assert_eq!(position.deposits.len(), 0); + assert_eq!(position.debts.len(), 0); + + assert_eq!(position.lends.len(), 1); + let rc_osmo_lent = get_lent("uosmo", &position.lends); + assert_eq!(rc_osmo_lent.amount, Uint128::new(8)); + + // Liq HF should improve + let account_kind = mock.query_account_kind(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, account_kind); + assert!(!health.liquidatable); } #[test] @@ -247,7 +265,7 @@ fn lent_position_fully_liquidated() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .max_close_factor(Decimal::from_atomics(6u128, 1).unwrap()) + .target_health_factor(Decimal::from_atomics(12u128, 1).unwrap()) .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), @@ -284,10 +302,10 @@ fn lent_position_fully_liquidated() { price: Decimal::from_atomics(50u128, 1).unwrap(), }); - let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); - assert!(health.liquidatable); - assert_eq!(health.total_collateral_value, Uint128::new(2801u128)); - assert_eq!(health.total_debt_value, Uint128::new(2505u128)); + let prev_health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + assert!(prev_health.liquidatable); + assert_eq!(prev_health.total_collateral_value, Uint128::new(2801u128)); + assert_eq!(prev_health.total_debt_value, Uint128::new(2505u128)); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -316,21 +334,43 @@ fn lent_position_fully_liquidated() { assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("uatom", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(481)); + assert_eq!(atom_debt.amount, Uint128::new(480)); - assert_eq!(position.lends.len(), 0); + // FIXME: dust because of roundings, is it possible to avoid it? + assert_eq!(position.lends.len(), 1); + let osmo_balance = get_lent("uosmo", &position.lends); + assert_eq!(osmo_balance.amount, Uint128::new(1)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.deposits.len(), 1); let atom_balance = get_coin("uatom", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(12)); + assert_eq!(atom_balance.amount, Uint128::new(11)); assert_eq!(position.debts.len(), 0); assert_eq!(position.lends.len(), 1); let osmo_lent = get_lent("uosmo", &position.lends); - assert_eq!(osmo_lent.amount, Uint128::new(110)); + assert_eq!(osmo_lent.amount, Uint128::new(106)); + + // Assert rewards-collector's new position + let rewards_collector_acc_id = mock.query_rewards_collector_account(); + let position = mock.query_positions(&rewards_collector_acc_id); + assert_eq!(position.deposits.len(), 0); + assert_eq!(position.debts.len(), 0); + + assert_eq!(position.lends.len(), 1); + let rc_osmo_lent = get_lent("uosmo", &position.lends); + // FIXME: excel shows 2, simulated interest rate influence? + assert_eq!(rc_osmo_lent.amount, Uint128::new(1)); + + // Liq HF should improve + let account_kind = mock.query_account_kind(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, account_kind); + assert!(health.liquidatable); + assert!( + prev_health.liquidation_health_factor.unwrap() < health.liquidation_health_factor.unwrap() + ); } #[test] @@ -342,15 +382,15 @@ fn liquidate_with_reclaiming() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .max_close_factor(Decimal::from_atomics(6u128, 1).unwrap()) + .target_health_factor(Decimal::from_atomics(12u128, 1).unwrap()) .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), - funds: coins(300, uosmo_info.denom.clone()), + funds: coins(3000, uosmo_info.denom.clone()), }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: coins(300, uatom_info.denom.clone()), + funds: coins(3000, uatom_info.denom.clone()), }) .build() .unwrap(); @@ -361,23 +401,23 @@ fn liquidate_with_reclaiming() { &liquidatee_account_id, &liquidatee, vec![ - Deposit(uosmo_info.to_coin(300)), - Borrow(uatom_info.to_coin(100)), - Lend(uosmo_info.to_coin(202)), + Deposit(uosmo_info.to_coin(3000)), + Borrow(uatom_info.to_coin(1000)), + Lend(uosmo_info.to_coin(1500)), ], - &[uosmo_info.to_coin(300)], + &[uosmo_info.to_coin(3000)], ) .unwrap(); mock.price_change(CoinPrice { denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(55u128, 1).unwrap(), + price: Decimal::from_atomics(82u128, 1).unwrap(), }); - let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); - assert!(health.liquidatable); - assert_eq!(health.total_collateral_value, Uint128::new(624u128)); - assert_eq!(health.total_debt_value, Uint128::new(556u128)); + let prev_health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + assert!(prev_health.liquidatable); + assert_eq!(prev_health.total_collateral_value, Uint128::new(8950u128)); + assert_eq!(prev_health.total_debt_value, Uint128::new(8209u128)); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -385,10 +425,10 @@ fn liquidate_with_reclaiming() { &liquidator_account_id, &liquidator, vec![ - Deposit(uatom_info.to_coin(10)), + Deposit(uatom_info.to_coin(100)), Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: uatom_info.to_coin(10), + debt_coin: uatom_info.to_coin(100), request: LiquidateRequest::Lend(uosmo_info.denom.clone()), }, Reclaim(ActionCoin { @@ -396,7 +436,7 @@ fn liquidate_with_reclaiming() { amount: ActionAmount::AccountBalance, }), ], - &[uatom_info.to_coin(10)], + &[uatom_info.to_coin(100)], ) .unwrap(); @@ -404,27 +444,47 @@ fn liquidate_with_reclaiming() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.deposits.len(), 2); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(98)); + assert_eq!(osmo_balance.amount, Uint128::new(1500)); let atom_balance = get_coin("uatom", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(100)); + assert_eq!(atom_balance.amount, Uint128::new(1000)); assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("uatom", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(93)); + assert_eq!(atom_debt.amount, Uint128::new(960)); assert_eq!(position.lends.len(), 1); let osmo_lent = get_lent("uosmo", &position.lends); - assert_eq!(osmo_lent.amount, Uint128::new(10)); + // FIXME: excel shows 37, simulated interest rate influence? + assert_eq!(osmo_lent.amount, Uint128::new(36)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.deposits.len(), 2); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(191)); + assert_eq!(osmo_balance.amount, Uint128::new(1435)); let atom_balance = get_coin("uatom", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(2)); + assert_eq!(atom_balance.amount, Uint128::new(59)); assert_eq!(position.debts.len(), 0); assert_eq!(position.lends.len(), 0); + + // Assert rewards-collector's new position + let rewards_collector_acc_id = mock.query_rewards_collector_account(); + let position = mock.query_positions(&rewards_collector_acc_id); + assert_eq!(position.deposits.len(), 0); + assert_eq!(position.debts.len(), 0); + + assert_eq!(position.lends.len(), 1); + let rc_osmo_lent = get_lent("uosmo", &position.lends); + // FIXME: excel shows 28, simulated interest rate influence? + assert_eq!(rc_osmo_lent.amount, Uint128::new(27)); + + // Liq HF should improve + let account_kind = mock.query_account_kind(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, account_kind); + assert!(health.liquidatable); + assert!( + prev_health.liquidation_health_factor.unwrap() < health.liquidation_health_factor.unwrap() + ); } diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 37e41b02b..ddc568975 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -10,6 +10,7 @@ use mars_rover::{ LiquidateRequest, }, }; +use mars_rover_health_types::AccountKind; use crate::helpers::{ assert_err, get_coin, get_debt, locked_vault_info, lp_token_info, uatom_info, ujake_info, @@ -18,8 +19,8 @@ use crate::helpers::{ pub mod helpers; -// NOTE: Vault liquidation scenarios spreadsheet: -// https://docs.google.com/spreadsheets/d/1rXa_8eKbtp1wQ0Mm1Rny7QzSLsko9D7UQTtO7NrAssA/edit#gid=2127757089 +// Reference figures behind various scenarios +// https://docs.google.com/spreadsheets/d/1H7Ajghsee2l7_litG7EWoM-kkVQOh4dbHa8WSV-Y6Jg/edit#gid=1331087474 #[test] fn liquidatee_must_have_the_request_vault_position() { @@ -322,6 +323,7 @@ fn wrong_position_type_sent_for_locked_vault() { } #[test] + fn liquidate_unlocked_vault() { let lp_token = lp_token_info(); let ujake = ujake_info(); @@ -339,7 +341,7 @@ fn liquidate_unlocked_vault() { }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: vec![ujake.to_coin(10)], + funds: vec![ujake.to_coin(50)], }) .build() .unwrap(); @@ -364,26 +366,29 @@ fn liquidate_unlocked_vault() { mock.price_change(CoinPrice { denom: ujake.denom.clone(), - price: Decimal::from_atomics(20u128, 0).unwrap(), + price: Decimal::from_atomics(18u128, 0).unwrap(), }); + let prev_health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + assert!(prev_health.liquidatable); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); mock.update_credit_account( &liquidator_account_id, &liquidator, vec![ - Deposit(ujake.to_coin(10)), + Deposit(ujake.to_coin(50)), Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: ujake.to_coin(10), + debt_coin: ujake.to_coin(50), request: LiquidateRequest::Vault { request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), position_type: VaultPositionType::UNLOCKED, }, }, ], - &[ujake.to_coin(10)], + &[ujake.to_coin(50)], ) .unwrap(); @@ -391,7 +396,7 @@ fn liquidate_unlocked_vault() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_balance = position.vaults.first().unwrap().amount.unlocked(); - assert_eq!(vault_balance, Uint128::new(885_000)); // 1M - 115_000 + assert_eq!(vault_balance, Uint128::new(515_000)); // 1M - 485_000 assert_eq!(position.deposits.len(), 1); let jake_balance = get_coin("ujake", &position.deposits); @@ -399,14 +404,27 @@ fn liquidate_unlocked_vault() { assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("ujake", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(166)); + assert_eq!(atom_debt.amount, Uint128::new(126)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let lp = get_coin(&lp_token.denom, &position.deposits); - assert_eq!(lp.amount, Uint128::new(23)); + assert_eq!(lp.amount, Uint128::new(95)); + + // Assert rewards-collector's new position + let rewards_collector_acc_id = mock.query_rewards_collector_account(); + let position = mock.query_positions(&rewards_collector_acc_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.debts.len(), 0); + let lp = get_coin(&lp_token.denom, &position.deposits); + assert_eq!(lp.amount, Uint128::new(2)); + + // Liq HF should improve + let account_kind = mock.query_account_kind(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, account_kind); + assert!(!health.liquidatable); } #[test] @@ -455,6 +473,9 @@ fn liquidate_locked_vault() { price: Decimal::from_atomics(20u128, 0).unwrap(), }); + let prev_health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + assert!(prev_health.liquidatable); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); mock.update_credit_account( @@ -479,8 +500,8 @@ fn liquidate_locked_vault() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_amount = position.vaults.first().unwrap().amount.clone(); - // 1M - 825,000 vault tokens liquidated = 175,000 - assert_eq!(vault_amount.locked(), Uint128::new(175_000)); + // 1M - 812,500 vault tokens liquidated = 187,500 + assert_eq!(vault_amount.locked(), Uint128::new(187_500)); assert_eq!(vault_amount.unlocking().positions().len(), 0); assert_eq!(vault_amount.unlocked(), Uint128::zero()); @@ -497,10 +518,27 @@ fn liquidate_locked_vault() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let lp_balance = get_coin(&lp_token.denom, &position.deposits); - assert_eq!(lp_balance.amount, Uint128::new(66)); + assert_eq!(lp_balance.amount, Uint128::new(64)); + + // Assert rewards-collector's new position + let rewards_collector_acc_id = mock.query_rewards_collector_account(); + let position = mock.query_positions(&rewards_collector_acc_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.debts.len(), 0); + let lp_balance = get_coin(&lp_token.denom, &position.deposits); + assert_eq!(lp_balance.amount, Uint128::new(1)); + + // Liq HF should improve + let account_kind = mock.query_account_kind(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, account_kind); + assert!(health.liquidatable); + assert!( + prev_health.liquidation_health_factor.unwrap() < health.liquidation_health_factor.unwrap() + ); } #[test] + fn liquidate_unlocking_liquidation_order() { let lp_token = lp_token_info(); let ujake = ujake_info(); @@ -518,7 +556,7 @@ fn liquidate_unlocking_liquidation_order() { }) .fund_account(AccountToFund { addr: liquidator.clone(), - funds: vec![ujake.to_coin(10)], + funds: vec![ujake.to_coin(40)], }) .build() .unwrap(); @@ -562,23 +600,26 @@ fn liquidate_unlocking_liquidation_order() { price: Decimal::from_atomics(20u128, 0).unwrap(), }); + let prev_health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + assert!(prev_health.liquidatable); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); mock.update_credit_account( &liquidator_account_id, &liquidator, vec![ - Deposit(ujake.to_coin(10)), + Deposit(ujake.to_coin(40)), Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: ujake.to_coin(10), + debt_coin: ujake.to_coin(40), request: LiquidateRequest::Vault { request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), position_type: VaultPositionType::UNLOCKING, }, }, ], - &[ujake.to_coin(10)], + &[ujake.to_coin(40)], ) .unwrap(); @@ -589,14 +630,16 @@ fn liquidate_unlocking_liquidation_order() { assert_eq!(vault_amount.unlocked(), Uint128::zero()); assert_eq!(vault_amount.locked(), Uint128::zero()); - // Total liquidated: 24 LP tokens + // Total liquidated: 90 LP tokens // First bucket drained: 2 of 2 // Second bucket drained: 10 of 10 - // Third bucket partially liquidated: 11 of 20 - // Fourth bucket retained: 0 of 168 - assert_eq!(vault_amount.unlocking().positions().len(), 2); - assert_eq!(vault_amount.unlocking().positions().first().unwrap().coin.amount, Uint128::new(9)); - assert_eq!(vault_amount.unlocking().positions().get(1).unwrap().coin.amount, Uint128::new(168)); + // Third bucket partially liquidated: 20 of 20 + // Fourth bucket retained: 58 of 168 + assert_eq!(vault_amount.unlocking().positions().len(), 1); + assert_eq!( + vault_amount.unlocking().positions().first().unwrap().coin.amount, + Uint128::new(110) + ); assert_eq!(position.deposits.len(), 1); let jake_balance = get_coin("ujake", &position.deposits); @@ -604,14 +647,30 @@ fn liquidate_unlocking_liquidation_order() { assert_eq!(position.debts.len(), 1); let atom_debt = get_debt("ujake", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(166)); + assert_eq!(atom_debt.amount, Uint128::new(136)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let lp_balance = get_coin(&lp_token.denom, &position.deposits); - assert_eq!(lp_balance.amount, Uint128::new(23)); + assert_eq!(lp_balance.amount, Uint128::new(89)); + + // Assert rewards-collector's new position + let rewards_collector_acc_id = mock.query_rewards_collector_account(); + let position = mock.query_positions(&rewards_collector_acc_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(position.debts.len(), 0); + let lp_balance = get_coin(&lp_token.denom, &position.deposits); + assert_eq!(lp_balance.amount, Uint128::new(1)); + + // Liq HF should improve + let account_kind = mock.query_account_kind(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, account_kind); + assert!(health.liquidatable); + assert!( + prev_health.liquidation_health_factor.unwrap() < health.liquidation_health_factor.unwrap() + ); } // NOTE: liquidation calculation+adjustments are quite complex, full cases in test_liquidate_coin.rs @@ -635,7 +694,7 @@ fn liquidation_calculation_adjustment() { addr: liquidator.clone(), funds: vec![ujake.to_coin(500)], }) - .max_close_factor(Decimal::from_atomics(87u128, 2).unwrap()) + .target_health_factor(Decimal::from_atomics(15u128, 1).unwrap()) .build() .unwrap(); @@ -662,6 +721,9 @@ fn liquidation_calculation_adjustment() { price: Decimal::from_atomics(20u128, 0).unwrap(), }); + let prev_health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + assert!(prev_health.liquidatable); + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); mock.update_credit_account( @@ -672,7 +734,7 @@ fn liquidation_calculation_adjustment() { Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), // Given the request vault balance, this debt payment is too high. - // It will be adjusted to 85, the max given the request vault value + // It will be adjusted to 88, the max given the request vault value debt_coin: ujake.to_coin(500), request: LiquidateRequest::Vault { request_vault: VaultBase::new(mock.get_vault(&leverage_vault).address), @@ -688,7 +750,7 @@ fn liquidation_calculation_adjustment() { let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.vaults.len(), 1); let vault_balance = position.vaults.first().unwrap().amount.unlocked(); - assert_eq!(vault_balance, Uint128::new(15_000)); // Vault position liquidated by 99% + assert_eq!(vault_balance, Uint128::new(5_000)); // Vault position liquidated by 99% assert_eq!(position.deposits.len(), 1); let jake_balance = get_coin("ujake", &position.deposits); @@ -696,14 +758,27 @@ fn liquidation_calculation_adjustment() { assert_eq!(position.debts.len(), 1); let jake_debt = get_debt("ujake", &position.debts); - assert_eq!(jake_debt.amount, Uint128::new(91)); + assert_eq!(jake_debt.amount, Uint128::new(88)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); assert_eq!(position.deposits.len(), 2); let jake_balance = get_coin("ujake", &position.deposits); - assert_eq!(jake_balance.amount, Uint128::new(415)); + assert_eq!(jake_balance.amount, Uint128::new(412)); let atom_balance = get_coin(&lp_token.denom, &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(197)); + assert_eq!(atom_balance.amount, Uint128::new(196)); assert_eq!(position.debts.len(), 0); + + // Assert rewards-collector's new position + let rewards_collector_acc_id = mock.query_rewards_collector_account(); + let position = mock.query_positions(&rewards_collector_acc_id); + assert_eq!(position.deposits.len(), 1); + let atom_balance = get_coin(&lp_token.denom, &position.deposits); + assert_eq!(atom_balance.amount, Uint128::new(3)); + assert_eq!(position.debts.len(), 0); + + // Liq HF should improve + let account_kind = mock.query_account_kind(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, account_kind); + assert!(!health.liquidatable); } diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index 2de2a10b3..431ad82c9 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -2,6 +2,7 @@ use std::ops::{Add, Mul, Sub}; use cosmwasm_std::{coin, coins, Addr, Decimal, OverflowError, OverflowOperation, Uint128}; use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; +use mars_params::types::asset::LiquidationBonus; use mars_rover::{ error::ContractError, msg::execute::Action::{Borrow, Deposit, Repay, Withdraw}, @@ -83,7 +84,13 @@ fn raises_when_repaying_what_is_not_owed() { price: Decimal::from_atomics(9u128, 0).unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), - liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, }; @@ -142,7 +149,13 @@ fn raises_when_not_enough_assets_to_repay() { price: Decimal::from_atomics(9u128, 0).unwrap(), max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), - liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, }; diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 440e962c8..e8a64aa00 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -28,6 +28,7 @@ fn only_owner_can_update_config() { swapper: None, zapper: None, health_contract: None, + rewards_collector: None, }, ); @@ -48,6 +49,7 @@ fn update_config_works_with_full_config() { let new_unlocking_max = Uint128::new(321); let new_swapper = SwapperBase::new("new_swapper".to_string()); let new_health_contract = HealthContractUnchecked::new("new_health_contract".to_string()); + let new_rewards_collector = "rewards_collector_contract_new".to_string(); mock.update_config( &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), @@ -59,6 +61,7 @@ fn update_config_works_with_full_config() { swapper: Some(new_swapper.clone()), zapper: Some(new_zapper.clone()), health_contract: Some(new_health_contract.clone()), + rewards_collector: Some(new_rewards_collector.clone()), }, ) .unwrap(); @@ -90,6 +93,9 @@ fn update_config_works_with_full_config() { assert_eq!(&new_config.health_contract, new_health_contract.address()); assert_ne!(new_config.health_contract, original_config.health_contract); + + assert_eq!(new_config.rewards_collector.clone().unwrap(), new_rewards_collector); + assert_ne!(new_config.rewards_collector, original_config.rewards_collector); } #[test] diff --git a/contracts/credit-manager/tests/test_utilization_query.rs b/contracts/credit-manager/tests/test_utilization_query.rs index 1b1d6f9bb..628a7a50b 100644 --- a/contracts/credit-manager/tests/test_utilization_query.rs +++ b/contracts/credit-manager/tests/test_utilization_query.rs @@ -1,6 +1,7 @@ use std::str::FromStr; use cosmwasm_std::{Addr, Decimal, Uint128}; +use mars_params::types::asset::LiquidationBonus; use mars_rover::{ adapters::vault::VaultUnchecked, msg::execute::{ @@ -42,7 +43,13 @@ fn utilization_if_cap_is_base_denom() { price: Decimal::from_str("1").unwrap(), max_ltv: Decimal::from_str("0.6").unwrap(), liquidation_threshold: Decimal::from_str("0.7").unwrap(), - liquidation_bonus: Decimal::from_str("0.15").unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), whitelisted: true, hls: None, }; diff --git a/contracts/health/tests/helpers/defaults.rs b/contracts/health/tests/helpers/defaults.rs index b4004eb9e..6e8c7fcaa 100644 --- a/contracts/health/tests/helpers/defaults.rs +++ b/contracts/health/tests/helpers/defaults.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use cosmwasm_std::Decimal; use mars_params::types::{ - asset::{AssetParamsUnchecked, CmSettings, RedBankSettings}, + asset::{AssetParamsUnchecked, CmSettings, LiquidationBonus, RedBankSettings}, hls::HlsParamsUnchecked, }; @@ -24,6 +24,12 @@ pub fn default_asset_params(denom: &str) -> AssetParamsUnchecked { }, max_loan_to_value: Decimal::from_str("0.4523").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), - liquidation_bonus: Decimal::from_atomics(9u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), } } diff --git a/contracts/health/tests/helpers/mock_env_builder.rs b/contracts/health/tests/helpers/mock_env_builder.rs index 1125b07d9..0822ff19f 100644 --- a/contracts/health/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/helpers/mock_env_builder.rs @@ -162,6 +162,7 @@ impl MockEnvBuilder { swapper: "n/a".to_string(), zapper: "n/a".to_string(), health_contract: "n/a".to_string(), + rewards_collector: None, }, }, &[], @@ -215,7 +216,7 @@ impl MockEnvBuilder { owner.clone(), &ParamsInstantiateMsg { owner: owner.to_string(), - max_close_factor: Decimal::from_str("0.5").unwrap(), + target_health_factor: Decimal::from_str("1.2").unwrap(), }, &[], "mock-params-contract", diff --git a/contracts/health/tests/test_compute_health.rs b/contracts/health/tests/test_compute_health.rs index afee3488d..0041e13ba 100644 --- a/contracts/health/tests/test_compute_health.rs +++ b/contracts/health/tests/test_compute_health.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{Coin, Decimal, StdError, Uint128}; use mars_params::{ msg::{AssetParamsUpdate::AddOrUpdate, VaultConfigUpdate}, types::{ - asset::{AssetParamsUnchecked, CmSettings, RedBankSettings}, + asset::{AssetParamsUnchecked, CmSettings, LiquidationBonus, RedBankSettings}, hls::HlsParamsUnchecked, }, }; @@ -139,7 +139,13 @@ fn adds_vault_base_denoms_to_oracle_and_red_bank() { }, max_loan_to_value, liquidation_threshold, - liquidation_bonus: Decimal::from_atomics(9u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), }, }; @@ -174,7 +180,12 @@ fn whitelisted_coins_work() { let max_loan_to_value = Decimal::from_atomics(4523u128, 4).unwrap(); let liquidation_threshold = Decimal::from_atomics(5u128, 1).unwrap(); - let liquidation_bonus = Decimal::from_atomics(9u128, 2).unwrap(); + let liquidation_bonus = LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }; let mut asset_params = AssetParamsUnchecked { denom: umars.to_string(), @@ -194,6 +205,7 @@ fn whitelisted_coins_work() { max_loan_to_value, liquidation_threshold, liquidation_bonus, + protocol_liquidation_fee: Decimal::percent(2u64), }; let update = AddOrUpdate { @@ -291,7 +303,13 @@ fn vault_whitelist_affects_max_ltv() { }, max_loan_to_value: Decimal::from_str("0.4523").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), - liquidation_bonus: Decimal::from_atomics(9u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), }, }; diff --git a/packages/health-computer/tests/helpers/mock_coin_info.rs b/packages/health-computer/tests/helpers/mock_coin_info.rs index f765d0702..7205a4a55 100644 --- a/packages/health-computer/tests/helpers/mock_coin_info.rs +++ b/packages/health-computer/tests/helpers/mock_coin_info.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use cosmwasm_schema::cw_serde; use cosmwasm_std::Decimal; use mars_params::types::{ - asset::{AssetParams, CmSettings, RedBankSettings}, + asset::{AssetParams, CmSettings, LiquidationBonus, RedBankSettings}, hls::{HlsAssetType, HlsParams}, }; @@ -23,7 +23,12 @@ pub fn umars_info() -> CoinInfo { denom, max_loan_to_value: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(84u128, 2).unwrap(), - liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, credit_manager: CmSettings { whitelisted: true, hls: None, @@ -33,6 +38,7 @@ pub fn umars_info() -> CoinInfo { borrow_enabled: true, deposit_cap: Default::default(), }, + protocol_liquidation_fee: Decimal::percent(2u64), }, } } @@ -46,7 +52,12 @@ pub fn udai_info() -> CoinInfo { denom: "udai".to_string(), max_loan_to_value: Decimal::from_atomics(85u128, 2).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), - liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, credit_manager: CmSettings { whitelisted: true, hls: None, @@ -56,6 +67,7 @@ pub fn udai_info() -> CoinInfo { borrow_enabled: true, deposit_cap: Default::default(), }, + protocol_liquidation_fee: Decimal::percent(2u64), }, } } @@ -69,7 +81,12 @@ pub fn uluna_info() -> CoinInfo { denom, max_loan_to_value: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, credit_manager: CmSettings { whitelisted: true, hls: None, @@ -79,6 +96,7 @@ pub fn uluna_info() -> CoinInfo { borrow_enabled: true, deposit_cap: Default::default(), }, + protocol_liquidation_fee: Decimal::percent(2u64), }, } } @@ -92,7 +110,12 @@ pub fn ustars_info() -> CoinInfo { denom, max_loan_to_value: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, credit_manager: CmSettings { whitelisted: true, hls: Some(HlsParams { @@ -108,6 +131,7 @@ pub fn ustars_info() -> CoinInfo { borrow_enabled: true, deposit_cap: Default::default(), }, + protocol_liquidation_fee: Decimal::percent(2u64), }, } } @@ -121,7 +145,12 @@ pub fn ujuno_info() -> CoinInfo { denom, max_loan_to_value: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), - liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, credit_manager: CmSettings { whitelisted: true, hls: None, @@ -131,6 +160,7 @@ pub fn ujuno_info() -> CoinInfo { borrow_enabled: true, deposit_cap: Default::default(), }, + protocol_liquidation_fee: Decimal::percent(2u64), }, } } diff --git a/packages/rover/src/adapters/params.rs b/packages/rover/src/adapters/params.rs index 2759b4bac..1aa1b5186 100644 --- a/packages/rover/src/adapters/params.rs +++ b/packages/rover/src/adapters/params.rs @@ -60,7 +60,7 @@ impl Params { ) } - pub fn query_max_close_factor(&self, querier: &QuerierWrapper) -> StdResult { - querier.query_wasm_smart(self.address().to_string(), &QueryMsg::MaxCloseFactor {}) + pub fn query_target_health_factor(&self, querier: &QuerierWrapper) -> StdResult { + querier.query_wasm_smart(self.address().to_string(), &QueryMsg::TargetHealthFactor {}) } } diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index e5f6be358..56b76a784 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -281,6 +281,15 @@ pub enum CallbackMsg { /// Total balance for coin in Rover prior to withdraw previous_balance: Coin, }, + /// Used to update the coin balance of account after an async action + UpdateCoinBalanceAfterVaultLiquidation { + /// Account that needs coin balance adjustment + account_id: String, + /// Total balance for coin in Rover prior to withdraw + previous_balance: Coin, + /// Protocol fee percentage transfered to rewards-collector account + protocol_fee: Decimal, + }, /// Add Vec to liquidity pool in exchange for LP tokens ProvideLiquidity { account_id: String, diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index 606317f63..d26a6e802 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -40,4 +40,6 @@ pub struct ConfigUpdates { pub swapper: Option, pub zapper: Option, pub health_contract: Option, + /// The Mars Protocol rewards-collector contract. We collect protocol fee for its account. + pub rewards_collector: Option, } diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 0ab238469..10ac227b7 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -194,4 +194,5 @@ pub struct ConfigResponse { pub swapper: String, pub zapper: String, pub health_contract: String, + pub rewards_collector: Option, } diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index ce14a0d35..1b9c6c9bc 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -1118,6 +1118,47 @@ }, "additionalProperties": false }, + { + "description": "Used to update the coin balance of account after an async action", + "type": "object", + "required": [ + "update_coin_balance_after_vault_liquidation" + ], + "properties": { + "update_coin_balance_after_vault_liquidation": { + "type": "object", + "required": [ + "account_id", + "previous_balance", + "protocol_fee" + ], + "properties": { + "account_id": { + "description": "Account that needs coin balance adjustment", + "type": "string" + }, + "previous_balance": { + "description": "Total balance for coin in Rover prior to withdraw", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "protocol_fee": { + "description": "Protocol fee percentage transfered to rewards-collector account", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Add Vec to liquidity pool in exchange for LP tokens", "type": "object", @@ -1303,6 +1344,13 @@ } ] }, + "rewards_collector": { + "description": "The Mars Protocol rewards-collector contract. We collect protocol fee for its account.", + "type": [ + "string", + "null" + ] + }, "swapper": { "anyOf": [ { @@ -2506,6 +2554,12 @@ "red_bank": { "type": "string" }, + "rewards_collector": { + "type": [ + "string", + "null" + ] + }, "swapper": { "type": "string" }, diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 65b9f793e..434031491 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -1182,6 +1182,12 @@ "red_bank": { "type": "string" }, + "rewards_collector": { + "type": [ + "string", + "null" + ] + }, "swapper": { "type": "string" }, diff --git a/schemas/mars-params/mars-params.json b/schemas/mars-params/mars-params.json index 63588f009..873370b7f 100644 --- a/schemas/mars-params/mars-params.json +++ b/schemas/mars-params/mars-params.json @@ -1,27 +1,27 @@ { "contract_name": "mars-params", - "contract_version": "1.0.6", + "contract_version": "1.0.7", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "InstantiateMsg", "type": "object", "required": [ - "max_close_factor", - "owner" + "owner", + "target_health_factor" ], "properties": { - "max_close_factor": { - "description": "The maximum percent a liquidator can decrease the debt amount of the liquidatee", + "owner": { + "description": "Contract's owner", + "type": "string" + }, + "target_health_factor": { + "description": "Determines the ideal HF a position should be left at immediately after the position has been liquidated.", "allOf": [ { "$ref": "#/definitions/Decimal" } ] - }, - "owner": { - "description": "Contract's owner", - "type": "string" } }, "additionalProperties": false, @@ -51,10 +51,10 @@ { "type": "object", "required": [ - "update_max_close_factor" + "update_target_health_factor" ], "properties": { - "update_max_close_factor": { + "update_target_health_factor": { "$ref": "#/definitions/Decimal" } }, @@ -106,6 +106,7 @@ "liquidation_bonus", "liquidation_threshold", "max_loan_to_value", + "protocol_liquidation_fee", "red_bank" ], "properties": { @@ -116,7 +117,7 @@ "type": "string" }, "liquidation_bonus": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/LiquidationBonus" }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" @@ -124,6 +125,9 @@ "max_loan_to_value": { "$ref": "#/definitions/Decimal" }, + "protocol_liquidation_fee": { + "$ref": "#/definitions/Decimal" + }, "red_bank": { "$ref": "#/definitions/RedBankSettings" } @@ -334,6 +338,51 @@ }, "additionalProperties": false }, + "LiquidationBonus": { + "description": "The LB will depend on the Health Factor and a couple other parameters as follows: Liquidation Bonus = min( b + (slope * (1 - HF)), max( min(CR - 1, max_lb), min_lb ) )", + "type": "object", + "required": [ + "max_lb", + "min_lb", + "slope", + "starting_lb" + ], + "properties": { + "max_lb": { + "description": "Maximum LB that can be granted to a liquidator; in other words, the maxLB establishes a ceiling to the LB. This is a precautionary parameter to mitigate liquidated users being over-punished.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "min_lb": { + "description": "Minimum LB that will be granted to liquidators even when the position is undercollateralized.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope": { + "description": "Defines the slope at which the LB increases as the HF decreases. The higher the slope, the faster the LB increases as the HF decreases.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "starting_lb": { + "description": "Marks the level at which the LB starts when HF drops marginally below 1. If set at 1%, at HF = 0.999 the LB will be 1%. If set at 0%, the LB starts increasing from 0% as the HF drops below 1.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, "OwnerUpdate": { "oneOf": [ { @@ -636,10 +685,10 @@ { "type": "object", "required": [ - "max_close_factor" + "target_health_factor" ], "properties": { - "max_close_factor": { + "target_health_factor": { "type": "object", "additionalProperties": false } @@ -671,6 +720,7 @@ "liquidation_bonus", "liquidation_threshold", "max_loan_to_value", + "protocol_liquidation_fee", "red_bank" ], "properties": { @@ -681,7 +731,7 @@ "type": "string" }, "liquidation_bonus": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/LiquidationBonus" }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" @@ -689,6 +739,9 @@ "max_loan_to_value": { "$ref": "#/definitions/Decimal" }, + "protocol_liquidation_fee": { + "$ref": "#/definitions/Decimal" + }, "red_bank": { "$ref": "#/definitions/RedBankSettings" } @@ -791,6 +844,51 @@ }, "additionalProperties": false }, + "LiquidationBonus": { + "description": "The LB will depend on the Health Factor and a couple other parameters as follows: Liquidation Bonus = min( b + (slope * (1 - HF)), max( min(CR - 1, max_lb), min_lb ) )", + "type": "object", + "required": [ + "max_lb", + "min_lb", + "slope", + "starting_lb" + ], + "properties": { + "max_lb": { + "description": "Maximum LB that can be granted to a liquidator; in other words, the maxLB establishes a ceiling to the LB. This is a precautionary parameter to mitigate liquidated users being over-punished.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "min_lb": { + "description": "Minimum LB that will be granted to liquidators even when the position is undercollateralized.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope": { + "description": "Defines the slope at which the LB increases as the HF decreases. The higher the slope, the faster the LB increases as the HF decreases.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "starting_lb": { + "description": "Marks the level at which the LB starts when HF drops marginally below 1. If set at 1%, at HF = 0.999 the LB will be 1%. If set at 0%, the LB starts increasing from 0% as the HF drops below 1.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, "RedBankSettings": { "type": "object", "required": [ @@ -972,6 +1070,7 @@ "liquidation_bonus", "liquidation_threshold", "max_loan_to_value", + "protocol_liquidation_fee", "red_bank" ], "properties": { @@ -982,7 +1081,7 @@ "type": "string" }, "liquidation_bonus": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/LiquidationBonus" }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" @@ -990,6 +1089,9 @@ "max_loan_to_value": { "$ref": "#/definitions/Decimal" }, + "protocol_liquidation_fee": { + "$ref": "#/definitions/Decimal" + }, "red_bank": { "$ref": "#/definitions/RedBankSettings" } @@ -1096,6 +1198,51 @@ }, "additionalProperties": false }, + "LiquidationBonus": { + "description": "The LB will depend on the Health Factor and a couple other parameters as follows: Liquidation Bonus = min( b + (slope * (1 - HF)), max( min(CR - 1, max_lb), min_lb ) )", + "type": "object", + "required": [ + "max_lb", + "min_lb", + "slope", + "starting_lb" + ], + "properties": { + "max_lb": { + "description": "Maximum LB that can be granted to a liquidator; in other words, the maxLB establishes a ceiling to the LB. This is a precautionary parameter to mitigate liquidated users being over-punished.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "min_lb": { + "description": "Minimum LB that will be granted to liquidators even when the position is undercollateralized.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope": { + "description": "Defines the slope at which the LB increases as the HF decreases. The higher the slope, the faster the LB increases as the HF decreases.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "starting_lb": { + "description": "Marks the level at which the LB starts when HF drops marginally below 1. If set at 1%, at HF = 0.999 the LB will be 1%. If set at 0%, the LB starts increasing from 0% as the HF drops below 1.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, "RedBankSettings": { "type": "object", "required": [ @@ -1122,12 +1269,6 @@ } } }, - "max_close_factor": { - "$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" - }, "owner": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "OwnerResponse", @@ -1165,6 +1306,12 @@ }, "additionalProperties": false }, + "target_health_factor": { + "$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_config": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "VaultConfigBase_for_Addr", diff --git a/schemas/mars-rover-health-computer/mars-rover-health-computer.json b/schemas/mars-rover-health-computer/mars-rover-health-computer.json index b0640c9af..489f397e2 100644 --- a/schemas/mars-rover-health-computer/mars-rover-health-computer.json +++ b/schemas/mars-rover-health-computer/mars-rover-health-computer.json @@ -44,6 +44,7 @@ "liquidation_bonus", "liquidation_threshold", "max_loan_to_value", + "protocol_liquidation_fee", "red_bank" ], "properties": { @@ -54,7 +55,7 @@ "type": "string" }, "liquidation_bonus": { - "$ref": "#/definitions/Decimal" + "$ref": "#/definitions/LiquidationBonus" }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" @@ -62,6 +63,9 @@ "max_loan_to_value": { "$ref": "#/definitions/Decimal" }, + "protocol_liquidation_fee": { + "$ref": "#/definitions/Decimal" + }, "red_bank": { "$ref": "#/definitions/RedBankSettings" } @@ -282,6 +286,51 @@ }, "additionalProperties": false }, + "LiquidationBonus": { + "description": "The LB will depend on the Health Factor and a couple other parameters as follows: Liquidation Bonus = min( b + (slope * (1 - HF)), max( min(CR - 1, max_lb), min_lb ) )", + "type": "object", + "required": [ + "max_lb", + "min_lb", + "slope", + "starting_lb" + ], + "properties": { + "max_lb": { + "description": "Maximum LB that can be granted to a liquidator; in other words, the maxLB establishes a ceiling to the LB. This is a precautionary parameter to mitigate liquidated users being over-punished.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "min_lb": { + "description": "Minimum LB that will be granted to liquidators even when the position is undercollateralized.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "slope": { + "description": "Defines the slope at which the LB increases as the HF decreases. The higher the slope, the faster the LB increases as the HF decreases.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "starting_lb": { + "description": "Marks the level at which the LB starts when HF drops marginally below 1. If set at 1%, at HF = 0.999 the LB will be 1%. If set at 0%, the LB starts increasing from 0% as the HF drops below 1.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, "LockingVaultAmount": { "type": "object", "required": [ diff --git a/scripts/codegen/index.ts b/scripts/codegen/index.ts index 298c54b84..33e2b26cd 100644 --- a/scripts/codegen/index.ts +++ b/scripts/codegen/index.ts @@ -60,9 +60,9 @@ const fetchSchemafromGithub = async ({ void (async function () { await fetchSchemafromGithub({ - githubRepo: 'https://github.com/mars-protocol/mars-common', - commit: '1449b4b1cd21d318a48310345ea8c99a0c3cf16c', - pathToSchema: './mars-common/schemas/mars-params', + githubRepo: 'https://github.com/mars-protocol/red-bank', + commit: '32ab53b3130f3fb947c908768fe22b639536b185', + pathToSchema: './red-bank/schemas/mars-params', }) await generateTypes() })() diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 56970e776..fe1b10881 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -269,6 +269,13 @@ export type CallbackMsg = previous_balance: Coin } } + | { + update_coin_balance_after_vault_liquidation: { + account_id: string + previous_balance: Coin + protocol_fee: Decimal + } + } | { provide_liquidity: { account_id: string @@ -326,6 +333,7 @@ export interface ConfigUpdates { max_unlocking_positions?: Uint128 | null oracle?: OracleBaseForString | null red_bank?: RedBankBaseForString | null + rewards_collector?: string | null swapper?: SwapperBaseForString | null zapper?: ZapperBaseForString | null } @@ -470,6 +478,7 @@ export interface ConfigResponse { ownership: OwnerResponse params: string red_bank: string + rewards_collector?: string | null swapper: string zapper: string } diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index b20203986..636fcdcee 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -179,6 +179,7 @@ export interface ConfigResponse { ownership: OwnerResponse params: string red_bank: string + rewards_collector?: string | null swapper: string zapper: string } diff --git a/scripts/types/generated/mars-params/MarsParams.client.ts b/scripts/types/generated/mars-params/MarsParams.client.ts index ba1628600..ef6a19f05 100644 --- a/scripts/types/generated/mars-params/MarsParams.client.ts +++ b/scripts/types/generated/mars-params/MarsParams.client.ts @@ -22,6 +22,7 @@ import { AssetParamsBaseForString, CmSettingsForString, HlsParamsBaseForString, + LiquidationBonus, RedBankSettings, VaultConfigBaseForString, Coin, @@ -55,7 +56,7 @@ export interface MarsParamsReadOnlyInterface { limit?: number startAfter?: string }) => Promise - maxCloseFactor: () => Promise + targetHealthFactor: () => Promise } export class MarsParamsQueryClient implements MarsParamsReadOnlyInterface { client: CosmWasmClient @@ -69,7 +70,7 @@ export class MarsParamsQueryClient implements MarsParamsReadOnlyInterface { this.allAssetParams = this.allAssetParams.bind(this) this.vaultConfig = this.vaultConfig.bind(this) this.allVaultConfigs = this.allVaultConfigs.bind(this) - this.maxCloseFactor = this.maxCloseFactor.bind(this) + this.targetHealthFactor = this.targetHealthFactor.bind(this) } owner = async (): Promise => { @@ -119,9 +120,9 @@ export class MarsParamsQueryClient implements MarsParamsReadOnlyInterface { }, }) } - maxCloseFactor = async (): Promise => { + targetHealthFactor = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { - max_close_factor: {}, + target_health_factor: {}, }) } } @@ -134,7 +135,7 @@ export interface MarsParamsInterface extends MarsParamsReadOnlyInterface { memo?: string, _funds?: Coin[], ) => Promise - updateMaxCloseFactor: ( + updateTargetHealthFactor: ( fee?: number | StdFee | 'auto', memo?: string, _funds?: Coin[], @@ -169,7 +170,7 @@ export class MarsParamsClient extends MarsParamsQueryClient implements MarsParam this.sender = sender this.contractAddress = contractAddress this.updateOwner = this.updateOwner.bind(this) - this.updateMaxCloseFactor = this.updateMaxCloseFactor.bind(this) + this.updateTargetHealthFactor = this.updateTargetHealthFactor.bind(this) this.updateAssetParams = this.updateAssetParams.bind(this) this.updateVaultConfig = this.updateVaultConfig.bind(this) this.emergencyUpdate = this.emergencyUpdate.bind(this) @@ -192,7 +193,7 @@ export class MarsParamsClient extends MarsParamsQueryClient implements MarsParam _funds, ) } - updateMaxCloseFactor = async ( + updateTargetHealthFactor = async ( fee: number | StdFee | 'auto' = 'auto', memo?: string, _funds?: Coin[], @@ -201,7 +202,7 @@ export class MarsParamsClient extends MarsParamsQueryClient implements MarsParam this.sender, this.contractAddress, { - update_max_close_factor: {}, + update_target_health_factor: {}, }, fee, memo, diff --git a/scripts/types/generated/mars-params/MarsParams.message-composer.ts b/scripts/types/generated/mars-params/MarsParams.message-composer.ts index ce31164c8..492e5f22e 100644 --- a/scripts/types/generated/mars-params/MarsParams.message-composer.ts +++ b/scripts/types/generated/mars-params/MarsParams.message-composer.ts @@ -23,6 +23,7 @@ import { AssetParamsBaseForString, CmSettingsForString, HlsParamsBaseForString, + LiquidationBonus, RedBankSettings, VaultConfigBaseForString, Coin, @@ -41,7 +42,7 @@ export interface MarsParamsMessage { contractAddress: string sender: string updateOwner: (ownerUpdate: OwnerUpdate, _funds?: Coin[]) => MsgExecuteContractEncodeObject - updateMaxCloseFactor: (_funds?: Coin[]) => MsgExecuteContractEncodeObject + updateTargetHealthFactor: (_funds?: Coin[]) => MsgExecuteContractEncodeObject updateAssetParams: ( assetParamsUpdate: AssetParamsUpdate, _funds?: Coin[], @@ -63,7 +64,7 @@ export class MarsParamsMessageComposer implements MarsParamsMessage { this.sender = sender this.contractAddress = contractAddress this.updateOwner = this.updateOwner.bind(this) - this.updateMaxCloseFactor = this.updateMaxCloseFactor.bind(this) + this.updateTargetHealthFactor = this.updateTargetHealthFactor.bind(this) this.updateAssetParams = this.updateAssetParams.bind(this) this.updateVaultConfig = this.updateVaultConfig.bind(this) this.emergencyUpdate = this.emergencyUpdate.bind(this) @@ -84,7 +85,7 @@ export class MarsParamsMessageComposer implements MarsParamsMessage { }), } } - updateMaxCloseFactor = (_funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateTargetHealthFactor = (_funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -92,7 +93,7 @@ export class MarsParamsMessageComposer implements MarsParamsMessage { contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - update_max_close_factor: {}, + update_target_health_factor: {}, }), ), funds: _funds, diff --git a/scripts/types/generated/mars-params/MarsParams.react-query.ts b/scripts/types/generated/mars-params/MarsParams.react-query.ts index 6f034c5a8..9b4c1121e 100644 --- a/scripts/types/generated/mars-params/MarsParams.react-query.ts +++ b/scripts/types/generated/mars-params/MarsParams.react-query.ts @@ -23,6 +23,7 @@ import { AssetParamsBaseForString, CmSettingsForString, HlsParamsBaseForString, + LiquidationBonus, RedBankSettings, VaultConfigBaseForString, Coin, @@ -60,9 +61,9 @@ export const marsParamsQueryKeys = { [ { ...marsParamsQueryKeys.address(contractAddress)[0], method: 'all_vault_configs', args }, ] as const, - maxCloseFactor: (contractAddress: string | undefined, args?: Record) => + targetHealthFactor: (contractAddress: string | undefined, args?: Record) => [ - { ...marsParamsQueryKeys.address(contractAddress)[0], method: 'max_close_factor', args }, + { ...marsParamsQueryKeys.address(contractAddress)[0], method: 'target_health_factor', args }, ] as const, } export interface MarsParamsReactQuery { @@ -74,15 +75,15 @@ export interface MarsParamsReactQuery { initialData?: undefined } } -export interface MarsParamsMaxCloseFactorQuery +export interface MarsParamsTargetHealthFactorQuery extends MarsParamsReactQuery {} -export function useMarsParamsMaxCloseFactorQuery({ +export function useMarsParamsTargetHealthFactorQuery({ client, options, -}: MarsParamsMaxCloseFactorQuery) { +}: MarsParamsTargetHealthFactorQuery) { return useQuery( - marsParamsQueryKeys.maxCloseFactor(client?.contractAddress), - () => (client ? client.maxCloseFactor() : Promise.reject(new Error('Invalid client'))), + marsParamsQueryKeys.targetHealthFactor(client?.contractAddress), + () => (client ? client.targetHealthFactor() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } @@ -252,7 +253,7 @@ export function useMarsParamsUpdateAssetParamsMutation( options, ) } -export interface MarsParamsUpdateMaxCloseFactorMutation { +export interface MarsParamsUpdateTargetHealthFactorMutation { client: MarsParamsClient args?: { fee?: number | StdFee | 'auto' @@ -260,15 +261,15 @@ export interface MarsParamsUpdateMaxCloseFactorMutation { funds?: Coin[] } } -export function useMarsParamsUpdateMaxCloseFactorMutation( +export function useMarsParamsUpdateTargetHealthFactorMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => - client.updateMaxCloseFactor(msg, fee, memo, funds), + client.updateTargetHealthFactor(msg, fee, memo, funds), options, ) } diff --git a/scripts/types/generated/mars-params/MarsParams.types.ts b/scripts/types/generated/mars-params/MarsParams.types.ts index aa046b17d..6c2e27625 100644 --- a/scripts/types/generated/mars-params/MarsParams.types.ts +++ b/scripts/types/generated/mars-params/MarsParams.types.ts @@ -7,15 +7,15 @@ export type Decimal = string export interface InstantiateMsg { - max_close_factor: Decimal owner: string + target_health_factor: Decimal } export type ExecuteMsg = | { update_owner: OwnerUpdate } | { - update_max_close_factor: Decimal + update_target_health_factor: Decimal } | { update_asset_params: AssetParamsUpdate @@ -86,9 +86,10 @@ export type RedBankEmergencyUpdate = { export interface AssetParamsBaseForString { credit_manager: CmSettingsForString denom: string - liquidation_bonus: Decimal + liquidation_bonus: LiquidationBonus liquidation_threshold: Decimal max_loan_to_value: Decimal + protocol_liquidation_fee: Decimal red_bank: RedBankSettings } export interface CmSettingsForString { @@ -100,6 +101,12 @@ export interface HlsParamsBaseForString { liquidation_threshold: Decimal max_loan_to_value: Decimal } +export interface LiquidationBonus { + max_lb: Decimal + min_lb: Decimal + slope: Decimal + starting_lb: Decimal +} export interface RedBankSettings { borrow_enabled: boolean deposit_cap: Uint128 @@ -145,7 +152,7 @@ export type QueryMsg = } } | { - max_close_factor: {} + target_health_factor: {} } export type HlsAssetTypeForAddr = | { @@ -163,9 +170,10 @@ export type ArrayOfAssetParamsBaseForAddr = AssetParamsBaseForAddr[] export interface AssetParamsBaseForAddr { credit_manager: CmSettingsForAddr denom: string - liquidation_bonus: Decimal + liquidation_bonus: LiquidationBonus liquidation_threshold: Decimal max_loan_to_value: Decimal + protocol_liquidation_fee: Decimal red_bank: RedBankSettings } export interface CmSettingsForAddr { diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts index aed0db28a..af6f9a99f 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts @@ -20,6 +20,7 @@ import { AssetParamsBaseForAddr, CmSettingsForAddr, HlsParamsBaseForAddr, + LiquidationBonus, RedBankSettings, Positions, DebtAmount, diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts index aed0db28a..af6f9a99f 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts @@ -20,6 +20,7 @@ import { AssetParamsBaseForAddr, CmSettingsForAddr, HlsParamsBaseForAddr, + LiquidationBonus, RedBankSettings, Positions, DebtAmount, diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts index 9935e4df5..3edd78ad6 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts @@ -20,6 +20,7 @@ import { AssetParamsBaseForAddr, CmSettingsForAddr, HlsParamsBaseForAddr, + LiquidationBonus, RedBankSettings, Positions, DebtAmount, diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts index 4227dd32f..dd72dfbb6 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts @@ -47,9 +47,10 @@ export interface DenomsData { export interface AssetParamsBaseForAddr { credit_manager: CmSettingsForAddr denom: string - liquidation_bonus: Decimal + liquidation_bonus: LiquidationBonus liquidation_threshold: Decimal max_loan_to_value: Decimal + protocol_liquidation_fee: Decimal red_bank: RedBankSettings } export interface CmSettingsForAddr { @@ -61,6 +62,12 @@ export interface HlsParamsBaseForAddr { liquidation_threshold: Decimal max_loan_to_value: Decimal } +export interface LiquidationBonus { + max_lb: Decimal + min_lb: Decimal + slope: Decimal + starting_lb: Decimal +} export interface RedBankSettings { borrow_enabled: boolean deposit_cap: Uint128 From d8d2c6ef5c14f0224efd0ebf9a1506b98b3d2e96 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 27 Jun 2023 13:55:15 +0200 Subject: [PATCH 170/218] Max withdraw FE helper (#148) * Support max_withdraw * Update formula --- .gitignore | 3 + Cargo.lock | 323 +++- Cargo.toml | 7 +- packages/health-computer/Cargo.toml | 7 + .../health-computer/src/health_computer.rs | 93 +- packages/health-computer/src/javascript.rs | 12 +- packages/health-computer/tests/helpers/mod.rs | 3 +- .../tests/helpers/prop_test_strategies.rs | 306 ++++ .../tests/test_max_withdraw.rs | 405 +++++ .../tests/test_max_withdraw_prop_test.rs | 57 + packages/health-types/src/error.rs | 3 + scripts/health/DataFetcher.ts | 19 +- scripts/health/example-node.ts | 9 +- scripts/health/example-react/src/utils.ts | 5 +- scripts/health/pkg-node/index.d.ts | 10 +- scripts/health/pkg-node/index.js | 77 +- scripts/health/pkg-node/index_bg.wasm | Bin 166551 -> 174764 bytes scripts/health/pkg-node/index_bg.wasm.d.ts | 7 +- scripts/health/pkg-web/index.d.ts | 17 +- scripts/health/pkg-web/index.js | 75 +- scripts/health/pkg-web/index_bg.wasm | Bin 165627 -> 173840 bytes scripts/health/pkg-web/index_bg.wasm.d.ts | 7 +- scripts/package.json | 10 +- scripts/yarn.lock | 1614 ++++++++--------- 24 files changed, 2102 insertions(+), 967 deletions(-) create mode 100644 packages/health-computer/tests/helpers/prop_test_strategies.rs create mode 100644 packages/health-computer/tests/test_max_withdraw.rs create mode 100644 packages/health-computer/tests/test_max_withdraw_prop_test.rs diff --git a/.gitignore b/.gitignore index 44899f623..ba14df4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ artifacts # private scripts that I don't want to commit are prefixed by an underscore **/_*.js **/_*.ts + +# proptest generated files +**/*.proptest-regressions diff --git a/Cargo.lock b/Cargo.lock index ed325fd55..30c99d109 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,7 +67,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.22", ] [[package]] @@ -146,6 +146,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -489,7 +504,7 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "derivative", - "itertools", + "itertools 0.10.5", "k256", "prost 0.9.0", "schemars", @@ -641,9 +656,9 @@ dependencies = [ [[package]] name = "cw20" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91666da6c7b40c8dd5ff94df655a28114efc10c79b70b4d06f13c31e37d60609" +checksum = "011c45920f8200bd5d32d4fe52502506f64f2f75651ab408054d4cfc75ca3a9b" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -668,7 +683,7 @@ dependencies = [ [[package]] name = "cw721" version = "0.17.0" -source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#f8600e6a760ce6ad340ce286262c55f471b2fb70" +source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#8ff32222d4e4c261159d19a384218bd96c63ae23" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -697,7 +712,7 @@ dependencies = [ [[package]] name = "cw721-base" version = "0.17.0" -source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#f8600e6a760ce6ad340ce286262c55f471b2fb70" +source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#8ff32222d4e4c261159d19a384218bd96c63ae23" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -846,6 +861,27 @@ dependencies = [ "termcolor", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "eyre" version = "0.6.8" @@ -856,6 +892,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "ff" version = "0.12.1" @@ -953,7 +998,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.22", ] [[package]] @@ -1028,9 +1073,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" dependencies = [ "bytes", "fnv", @@ -1097,6 +1142,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -1154,9 +1205,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -1239,6 +1290,26 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1248,6 +1319,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -1256,9 +1336,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1299,9 +1379,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.146" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libloading" @@ -1313,6 +1393,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "log" version = "0.4.19" @@ -1353,7 +1445,7 @@ dependencies = [ "cw2 1.1.0", "cw721 0.17.0", "cw721-base 0.17.0", - "itertools", + "itertools 0.11.0", "mars-account-nft", "mars-mock-oracle", "mars-mock-red-bank", @@ -1534,6 +1626,7 @@ dependencies = [ "mars-params", "mars-rover", "mars-rover-health-types", + "proptest", "schemars", "serde", "serde-wasm-bindgen", @@ -1588,7 +1681,7 @@ dependencies = [ "mars-owner", "mars-rover", "mars-swapper-base", - "osmosis-std 0.16.0-beta.0", + "osmosis-std 0.16.0-beta.1", "osmosis-test-tube", "schemars", "thiserror", @@ -1639,7 +1732,7 @@ dependencies = [ "cw-utils 1.0.1", "cw2 1.1.0", "mars-v2-zapper-base", - "osmosis-std 0.16.0-beta.0", + "osmosis-std 0.16.0-beta.1", "osmosis-test-tube", ] @@ -1664,7 +1757,7 @@ dependencies = [ "cosmwasm-std", "mars-owner", "mars-v3-zapper-base", - "osmosis-std 0.16.0-beta.0", + "osmosis-std 0.16.0-beta.1", "osmosis-test-tube", ] @@ -1725,6 +1818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1788,13 +1882,13 @@ dependencies = [ [[package]] name = "osmosis-std" -version = "0.16.0-beta.0" +version = "0.16.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78f106a02bb4a35a1a7fbdadeeaedba91d25d37f77518eabe381c265511d560" +checksum = "1abcc484d988099d0422b34b85f7086478400c2508d09d0724666a2933fec342" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.16.0-beta.0", + "osmosis-std-derive 0.16.0-beta.1", "prost 0.11.9", "prost-types", "schemars", @@ -1808,7 +1902,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a455e262a6fdfd3914f3a4e11e6bc0ce491901cb9d507d7856d7ef6e129e90c6" dependencies = [ - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "syn 1.0.109", @@ -1816,11 +1910,11 @@ dependencies = [ [[package]] name = "osmosis-std-derive" -version = "0.16.0-beta.0" +version = "0.16.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d09ac2abf9d9711e15a4df4dadf923b12a3dcc80c4a4a5abb24c36a9500e95" +checksum = "8571de5fb19c9abf11bb4c9a14d9128b8bd4ff180034561fbf87f3b9c42d1f0f" dependencies = [ - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "syn 1.0.109", @@ -1836,7 +1930,7 @@ dependencies = [ "bindgen", "cosmrs", "cosmwasm-std", - "osmosis-std 0.16.0-beta.0", + "osmosis-std 0.16.0-beta.1", "prost 0.11.9", "serde", "serde_json", @@ -1915,7 +2009,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.22", ] [[package]] @@ -1940,15 +2034,41 @@ dependencies = [ "spki", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" -version = "1.0.60" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.6.29", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "prost" version = "0.9.0" @@ -1976,7 +2096,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "syn 1.0.109", @@ -1989,7 +2109,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "syn 1.0.109", @@ -2004,6 +2124,12 @@ dependencies = [ "prost 0.11.9", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.28" @@ -2013,6 +2139,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -2028,6 +2175,24 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.8.4" @@ -2036,9 +2201,15 @@ checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.2", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.7.2" @@ -2097,6 +2268,20 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustix" +version = "0.37.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + [[package]] name = "rustls" version = "0.19.1" @@ -2122,6 +2307,18 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.13" @@ -2278,7 +2475,7 @@ checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.22", ] [[package]] @@ -2294,9 +2491,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.97" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" +checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" dependencies = [ "itoa", "ryu", @@ -2311,7 +2508,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.22", ] [[package]] @@ -2450,15 +2647,29 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +dependencies = [ + "autocfg", + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "tendermint" version = "0.23.9" @@ -2602,7 +2813,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.22", ] [[package]] @@ -2662,7 +2873,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.22", ] [[package]] @@ -2749,6 +2960,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -2799,6 +3016,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.3.3" @@ -2811,11 +3037,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -2846,7 +3071,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.22", "wasm-bindgen-shared", ] @@ -2868,7 +3093,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.22", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2881,9 +3106,9 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -3090,5 +3315,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.22", ] diff --git a/Cargo.toml b/Cargo.toml index 491dfe3af..722b6ed81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ keywords = ["mars", "cosmos", "cosmwasm"] anyhow = "1.0.71" cosmwasm-schema = "1.2.7" cosmwasm-std = "1.2.7" -cw2 = "1.0.1" +cw2 = "1.1.0" cw721 = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main" } cw721-base = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main", features = ["library"] } cw-dex = { version = "0.2.0", features = ["osmosis"] } @@ -48,12 +48,13 @@ cw-paginate = "0.2.1" cw-utils = "1.0.1" cw-storage-plus = "1.1.0" cw-vault-standard = { version = "0.3.0", features = ["lockup", "force-unlock"] } -itertools = "0.10.5" +itertools = "0.11.0" osmosis-std = "0.16.0-beta.0" osmosis-test-tube = "16.0.0-beta.0" +proptest = "1.2.0" schemars = "0.8.12" serde = { version = "1.0.164", default-features = false, features = ["derive"] } -serde_json = "1.0.97" +serde_json = "1.0.99" serde-wasm-bindgen = "0.5.0" thiserror = "1.0.40" wasm-bindgen = "0.2.87" diff --git a/packages/health-computer/Cargo.toml b/packages/health-computer/Cargo.toml index 10747830c..32adde2dd 100644 --- a/packages/health-computer/Cargo.toml +++ b/packages/health-computer/Cargo.toml @@ -33,3 +33,10 @@ wasm-bindgen = { workspace = true } # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for # code size when deploying. console_error_panic_hook = { version = "0.1.7", optional = true } + +[dev-dependencies] +proptest = { workspace = true } + +[profile.release] +opt-level = 's' +lto = true diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index e412ef892..410a8c63f 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -1,3 +1,5 @@ +use std::cmp::min; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Decimal, Uint128}; use mars_params::types::{ @@ -8,7 +10,8 @@ use mars_rover::{msg::query::Positions, traits::Coins}; use mars_rover_health_types::{ AccountKind, Health, HealthError::{ - MissingHLSParams, MissingParams, MissingPrice, MissingVaultConfig, MissingVaultValues, + DenomNotPresent, MissingHLSParams, MissingParams, MissingPrice, MissingVaultConfig, + MissingVaultValues, }, HealthResult, }; @@ -31,9 +34,9 @@ impl HealthComputer { total_collateral_value, max_ltv_adjusted_collateral, liquidation_threshold_adjusted_collateral, - } = self.calculate_collateral_value()?; + } = self.total_collateral_value()?; - let total_debt_value = self.calculate_total_debt_value()?; + let total_debt_value = self.total_debt_value()?; let max_ltv_health_factor = if total_debt_value.is_zero() { None @@ -60,7 +63,75 @@ impl HealthComputer { }) } - fn calculate_total_debt_value(&self) -> HealthResult { + /// The max this account can withdraw of `withdraw_denom` and maintain max_ltv >= 1 + /// Note: This is an estimate. Guarantees to leave account healthy, but in edge cases, + /// due to rounding, it may be slightly too conservative. + pub fn max_withdraw_amount_estimate(&self, withdraw_denom: &str) -> HealthResult { + let withdraw_coin = self + .positions + .deposits + .iter() + .find(|c| c.denom == withdraw_denom) + .ok_or(DenomNotPresent(withdraw_denom.to_string()))?; + + let params = self + .denoms_data + .params + .get(withdraw_denom) + .ok_or(MissingParams(withdraw_denom.to_string()))?; + + // If no debt or coin is blacklisted (meaning does not contribute to max ltv hf), + // the total amount deposited can be withdrawn + if self.positions.debts.is_empty() || !params.credit_manager.whitelisted { + return Ok(withdraw_coin.amount); + } + + // Given the formula: + // max ltv health factor = max ltv adjusted value / debt value + // where: max ltv adjusted value = price * amount * max ltv + // The max can be calculated as: + // 1 = (total max ltv adjusted value - withdraw denom max ltv adjusted value) / debt value + // Re-arranging this to isolate max withdraw amount renders: + // max withdraw amount = (total max ltv adjusted value - debt value) / (withdraw denom price * withdraw denom max ltv) + let total_max_ltv_adjusted_value = + self.total_collateral_value()?.max_ltv_adjusted_collateral; + let debt_value = self.total_debt_value()?; + let withdraw_denom_price = self + .denoms_data + .prices + .get(withdraw_denom) + .ok_or(MissingPrice(withdraw_denom.to_string()))?; + + let withdraw_denom_max_ltv = match self.kind { + AccountKind::Default => params.max_loan_to_value, + AccountKind::HighLeveredStrategy => { + params + .credit_manager + .hls + .as_ref() + .ok_or(MissingHLSParams(withdraw_denom.to_string()))? + .max_loan_to_value + } + }; + + if debt_value >= total_max_ltv_adjusted_value { + return Ok(Uint128::zero()); + } + + // The formula in fact looks like this in practice: + // hf = rounddown(roundown(amount * price) * max ltv) / debt value + // Which means re-arranging this to isolate withdraw amount is an estimate, + // quite close, but never precisely right. For this reason, the - 1 below is meant + // to err on the side of being more conservative vs aggressive. + let max_withdraw_amount = total_max_ltv_adjusted_value + .checked_sub(debt_value)? + .checked_sub(Uint128::one())? + .checked_div_floor(withdraw_denom_price.checked_mul(withdraw_denom_max_ltv)?)?; + + Ok(min(max_withdraw_amount, withdraw_coin.amount)) + } + + fn total_debt_value(&self) -> HealthResult { let mut total = Uint128::zero(); for debt in &self.positions.debts { let coin_price = @@ -71,10 +142,10 @@ impl HealthComputer { Ok(total) } - fn calculate_collateral_value(&self) -> HealthResult { - let deposits = self.calculate_coins_value(&self.positions.deposits)?; - let lends = self.calculate_coins_value(&self.positions.lends.to_coins())?; - let vaults = self.calculate_vaults_value()?; + fn total_collateral_value(&self) -> HealthResult { + let deposits = self.coins_value(&self.positions.deposits)?; + let lends = self.coins_value(&self.positions.lends.to_coins())?; + let vaults = self.vaults_value()?; Ok(CollateralValue { total_collateral_value: deposits @@ -92,7 +163,7 @@ impl HealthComputer { }) } - fn calculate_coins_value(&self, coins: &[Coin]) -> HealthResult { + fn coins_value(&self, coins: &[Coin]) -> HealthResult { let mut total_collateral_value = Uint128::zero(); let mut max_ltv_adjusted_collateral = Uint128::zero(); let mut liquidation_threshold_adjusted_collateral = Uint128::zero(); @@ -146,7 +217,7 @@ impl HealthComputer { }) } - fn calculate_vaults_value(&self) -> HealthResult { + fn vaults_value(&self) -> HealthResult { let mut total_collateral_value = Uint128::zero(); let mut max_ltv_adjusted_collateral = Uint128::zero(); let mut liquidation_threshold_adjusted_collateral = Uint128::zero(); @@ -212,7 +283,7 @@ impl HealthComputer { .checked_add(liquidation_threshold_adjusted_collateral)?; // Step 2: Calculate Base coin values - let res = self.calculate_coins_value(&[Coin { + let res = self.coins_value(&[Coin { denom: values.base_coin.denom.clone(), amount: v.amount.unlocking().total(), }])?; diff --git a/packages/health-computer/src/javascript.rs b/packages/health-computer/src/javascript.rs index 454233d2e..f861fabeb 100644 --- a/packages/health-computer/src/javascript.rs +++ b/packages/health-computer/src/javascript.rs @@ -5,13 +5,21 @@ use wasm_bindgen::prelude::*; use crate::HealthComputer; #[wasm_bindgen] -pub fn compute_health_js(val: JsValue) -> JsValue { - let c: HealthComputer = deserialize(val); +pub fn compute_health_js(health_computer: JsValue) -> JsValue { + let c: HealthComputer = deserialize(health_computer); let health = c.compute_health().unwrap(); let health_response: HealthResponse = health.into(); serialize(health_response) } +#[wasm_bindgen] +pub fn max_withdraw_estimate_js(health_computer: JsValue, withdraw_denom: JsValue) -> JsValue { + let c: HealthComputer = deserialize(health_computer); + let denom: String = deserialize(withdraw_denom); + let max = c.max_withdraw_amount_estimate(&denom).unwrap(); + serialize(max) +} + pub fn serialize(val: T) -> JsValue { serde_wasm_bindgen::to_value(&val).unwrap() } diff --git a/packages/health-computer/tests/helpers/mod.rs b/packages/health-computer/tests/helpers/mod.rs index ebd5ee99d..eeb244934 100644 --- a/packages/health-computer/tests/helpers/mod.rs +++ b/packages/health-computer/tests/helpers/mod.rs @@ -1,3 +1,4 @@ -pub use self::mock_coin_info::*; +pub use self::{mock_coin_info::*, prop_test_strategies::*}; mod mock_coin_info; +mod prop_test_strategies; diff --git a/packages/health-computer/tests/helpers/prop_test_strategies.rs b/packages/health-computer/tests/helpers/prop_test_strategies.rs new file mode 100644 index 000000000..41295b2d4 --- /dev/null +++ b/packages/health-computer/tests/helpers/prop_test_strategies.rs @@ -0,0 +1,306 @@ +use std::collections::HashMap; + +use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use mars_params::types::{ + asset::{AssetParams, CmSettings, LiquidationBonus, RedBankSettings}, + hls::HlsParams, + vault::VaultConfig, +}; +use mars_rover::{ + adapters::vault::{ + CoinValue, LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultPosition, + VaultPositionAmount, VaultPositionValue, + }, + msg::query::{DebtAmount, LentAmount, Positions}, +}; +use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; +use mars_rover_health_types::AccountKind; +use proptest::{ + collection::vec, + prelude::{Just, Strategy}, + prop_oneof, +}; + +fn random_account_kind() -> impl Strategy { + prop_oneof![Just(AccountKind::Default), Just(AccountKind::HighLeveredStrategy)] +} + +fn random_denom() -> impl Strategy { + (5..=20) + .prop_flat_map(|len| proptest::string::string_regex(&format!("[a-z]{{{},}}", len)).unwrap()) +} + +fn random_bool() -> impl Strategy { + proptest::bool::ANY +} + +fn random_price() -> impl Strategy { + (1..=10000, 1..6) + .prop_map(|(price, offset)| Decimal::from_atomics(price as u128, offset as u32).unwrap()) +} + +fn random_coin_info() -> impl Strategy { + (random_denom(), 30..70, 2..10, 80..90, random_bool()).prop_map( + |(denom, max_ltv, liq_thresh_buffer, hls_base, whitelisted)| { + let max_loan_to_value = Decimal::from_atomics(max_ltv as u128, 2).unwrap(); + let liquidation_threshold = + max_loan_to_value + Decimal::from_atomics(liq_thresh_buffer as u128, 2).unwrap(); + let hls_max_ltv = Decimal::from_atomics(hls_base as u128, 2).unwrap(); + let hls_liq_threshold = + hls_max_ltv + Decimal::from_atomics(liq_thresh_buffer as u128, 2).unwrap(); + + AssetParams { + denom, + credit_manager: CmSettings { + whitelisted, + hls: Some(HlsParams { + max_loan_to_value: hls_max_ltv, + liquidation_threshold: hls_liq_threshold, + correlations: vec![], + }), + }, + red_bank: RedBankSettings { + deposit_enabled: true, + borrow_enabled: true, + deposit_cap: Default::default(), + }, + max_loan_to_value, + liquidation_threshold, + liquidation_bonus: LiquidationBonus { + starting_lb: Default::default(), + slope: Default::default(), + min_lb: Default::default(), + max_lb: Default::default(), + }, + protocol_liquidation_fee: Default::default(), + } + }, + ) +} + +fn random_denoms_data() -> impl Strategy { + vec((random_coin_info(), random_price()), 1..=5).prop_map(|info| { + let mut prices = HashMap::new(); + let mut params = HashMap::new(); + + for (coin_info, price) in info { + prices.insert(coin_info.denom.clone(), price); + params.insert(coin_info.denom.clone(), coin_info); + } + + DenomsData { + prices, + params, + } + }) +} + +fn random_address() -> impl Strategy { + proptest::string::string_regex("cosmos1[a-zA-Z0-9]{38}").unwrap() +} + +fn random_vault_denom() -> impl Strategy { + (random_denom()).prop_map(|denom| format!("vault_{denom}")) +} + +fn random_vault( + denoms_data: DenomsData, +) -> impl Strategy { + ( + random_address(), + random_vault_denom(), + 20..10_000, + 0..1000, + 30..70, + 2..10, + 80..90, + random_bool(), + ) + .prop_map( + move |( + addr, + vault_denom, + vault_val, + base_val, + max_ltv, + liq_thresh_buffer, + hls_base, + whitelisted, + )| { + let denoms = denoms_data + .params + .values() + .map(|params| params.denom.clone()) + .collect::>(); + let base_denom = denoms.first().unwrap(); + let position_val = VaultPositionValue { + vault_coin: CoinValue { + denom: vault_denom, + amount: Default::default(), + value: Uint128::new(vault_val as u128), + }, + // The base coin denom should only be from a denom generated from random_denoms_data() + base_coin: CoinValue { + denom: base_denom.clone(), + amount: Default::default(), + value: Uint128::new(base_val as u128), + }, + }; + let max_loan_to_value = Decimal::from_atomics(max_ltv as u128, 2).unwrap(); + let liquidation_threshold = max_loan_to_value + + Decimal::from_atomics(liq_thresh_buffer as u128, 2).unwrap(); + let hls_max_ltv = Decimal::from_atomics(hls_base as u128, 2).unwrap(); + let hls_liq_threshold = + hls_max_ltv + Decimal::from_atomics(liq_thresh_buffer as u128, 2).unwrap(); + + let config = VaultConfig { + addr: Addr::unchecked(addr.clone()), + deposit_cap: Default::default(), + max_loan_to_value, + liquidation_threshold, + whitelisted, + hls: Some(HlsParams { + max_loan_to_value: hls_max_ltv, + liquidation_threshold: hls_liq_threshold, + correlations: vec![], + }), + }; + (addr, position_val, config) + }, + ) +} + +fn random_param_maps() -> impl Strategy { + random_denoms_data().prop_flat_map(|denoms_data| { + vec(random_vault(denoms_data.clone()), 0..=3).prop_map(move |vaults| { + let mut vault_values = HashMap::new(); + let mut vault_configs = HashMap::new(); + + for (addr, position_val, config) in vaults { + let addr = Addr::unchecked(addr.clone()); + vault_values.insert(addr.clone(), position_val); + vault_configs.insert(addr, config); + } + + ( + denoms_data.clone(), + VaultsData { + vault_values, + vault_configs, + }, + ) + }) + }) +} + +fn random_deposits(denoms_data: DenomsData) -> impl Strategy> { + let denoms = denoms_data.params.keys().cloned().collect::>(); + let denoms_len = denoms.len(); + vec( + (0..denoms_len, 1..=10000).prop_map(move |(index, amount)| { + let denom = denoms.get(index).unwrap().clone(); + let amount = Uint128::new(amount as u128); + + Coin { + denom, + amount, + } + }), + 0..denoms_len, + ) +} + +fn random_debts(denoms_data: DenomsData) -> impl Strategy> { + let denoms = denoms_data.params.keys().cloned().collect::>(); + let denoms_len = denoms.len(); + vec( + (0..denoms_len, 1..=10000).prop_map(move |(index, amount)| { + let denom = denoms.get(index).unwrap().clone(); + let amount = Uint128::new(amount as u128); + + DebtAmount { + denom, + shares: amount * Uint128::new(10), + amount, + } + }), + 0..denoms_len, + ) +} + +fn random_lends(denoms_data: DenomsData) -> impl Strategy> { + let denoms = denoms_data.params.keys().cloned().collect::>(); + let denoms_len = denoms.len(); + vec( + (0..denoms_len, 1..=10000).prop_map(move |(index, amount)| { + let denom = denoms.get(index).unwrap().clone(); + let amount = Uint128::new(amount as u128); + + LentAmount { + denom, + shares: amount * Uint128::new(10), + amount, + } + }), + 0..denoms_len, + ) +} + +fn random_vault_pos_amount() -> impl Strategy { + prop_oneof![ + random_vault_amount().prop_map(VaultPositionAmount::Unlocked), + random_locking_vault_amount().prop_map(VaultPositionAmount::Locking), + ] +} + +fn random_vault_amount() -> impl Strategy { + (10..=100000).prop_map(|amount| VaultAmount::new(Uint128::new(amount as u128))) +} + +fn random_locking_vault_amount() -> impl Strategy { + (random_vault_amount()).prop_map(|locked| LockingVaultAmount { + locked, + unlocking: UnlockingPositions::new(vec![]), + }) +} + +fn random_vault_positions(vd: VaultsData) -> impl Strategy> { + let vault_addrs = vd.vault_configs.keys().cloned().collect::>(); + let addrs_len = vault_addrs.len(); + + vec( + (0..addrs_len, random_vault_pos_amount()).prop_map(move |(index, amount)| { + let addr = vault_addrs.get(index).unwrap().clone(); + + VaultPosition { + vault: Vault::new(addr), + amount, + } + }), + addrs_len, + ) +} + +pub fn random_health_computer() -> impl Strategy { + (random_param_maps()).prop_flat_map(|(denoms_data, vaults_data)| { + ( + random_account_kind(), + random_deposits(denoms_data.clone()), + random_debts(denoms_data.clone()), + random_lends(denoms_data.clone()), + random_vault_positions(vaults_data.clone()), + ) + .prop_map(move |(kind, deposits, debts, lends, vaults)| HealthComputer { + kind, + positions: Positions { + account_id: "123".to_string(), + deposits, + debts, + lends, + vaults, + }, + denoms_data: denoms_data.clone(), + vaults_data: vaults_data.clone(), + }) + }) +} diff --git a/packages/health-computer/tests/test_max_withdraw.rs b/packages/health-computer/tests/test_max_withdraw.rs new file mode 100644 index 000000000..fcef8dbeb --- /dev/null +++ b/packages/health-computer/tests/test_max_withdraw.rs @@ -0,0 +1,405 @@ +use std::{collections::HashMap, str::FromStr}; + +use cosmwasm_std::{coin, Addr, Decimal, Uint128}; +use mars_params::types::{hls::HlsParams, vault::VaultConfig}; +use mars_rover::{ + adapters::vault::{ + CoinValue, Vault, VaultAmount, VaultPosition, VaultPositionAmount, VaultPositionValue, + }, + msg::query::{DebtAmount, Positions}, +}; +use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; +use mars_rover_health_types::{AccountKind, HealthError}; + +use crate::helpers::{udai_info, umars_info, ustars_info}; + +pub mod helpers; + +#[test] +fn missing_price_data() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(umars.denom.clone(), umars.price)]), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.denom, + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let err: HealthError = h.max_withdraw_amount_estimate(&udai.denom).unwrap_err(); + assert_eq!(err, HealthError::MissingPrice(udai.denom)); +} + +#[test] +fn missing_params() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), + ]), + params: HashMap::from([(udai.denom.clone(), udai.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom, + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let err: HealthError = h.max_withdraw_amount_estimate(&umars.denom).unwrap_err(); + assert_eq!(err, HealthError::MissingParams(umars.denom)); +} + +#[test] +fn deposit_not_present() { + let denoms_data = DenomsData { + prices: Default::default(), + params: Default::default(), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let err: HealthError = h.max_withdraw_amount_estimate("xyz").unwrap_err(); + assert_eq!(err, HealthError::DenomNotPresent("xyz".to_string())); +} + +#[test] +fn blacklisted_assets_should_be_able_be_fully_withdrawn() { + let mut umars = umars_info(); + let udai = udai_info(); + + umars.params.credit_manager.whitelisted = false; + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), + ]), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let total_deposit = Uint128::new(200); + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(total_deposit.u128(), &umars.denom), coin(33, &udai.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom, + shares: Default::default(), + amount: Uint128::new(2500), + }, + DebtAmount { + denom: umars.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let health = h.compute_health().unwrap(); + assert!(health.max_ltv_health_factor < Some(Decimal::one())); + + // Can fully withdraw blacklisted asset even if unhealthy + let max_withdraw_amount = h.max_withdraw_amount_estimate(&umars.denom).unwrap(); + assert_eq!(total_deposit, max_withdraw_amount); +} + +#[test] +fn zero_when_unhealthy() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), + ]), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom.clone(), + shares: Default::default(), + amount: Uint128::new(2500), + }, + DebtAmount { + denom: umars.denom, + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let health = h.compute_health().unwrap(); + assert!(health.max_ltv_health_factor < Some(Decimal::one())); + let max_withdraw_amount = h.max_withdraw_amount_estimate(&udai.denom).unwrap(); + assert_eq!(Uint128::zero(), max_withdraw_amount); +} + +#[test] +fn no_debts() { + let ustars = ustars_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(ustars.denom.clone(), ustars.price)]), + params: HashMap::from([(ustars.denom.clone(), ustars.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let deposit_amount = Uint128::new(1200); + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(deposit_amount.u128(), &ustars.denom)], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let max_withdraw_amount = h.max_withdraw_amount_estimate(&ustars.denom).unwrap(); + assert_eq!(deposit_amount, max_withdraw_amount); +} + +#[test] +fn should_allow_max_withdraw() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), + ]), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let deposit_amount = Uint128::new(33); + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom), coin(deposit_amount.u128(), &udai.denom)], + debts: vec![DebtAmount { + denom: udai.denom.clone(), + shares: Default::default(), + amount: Uint128::new(5), + }], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + // Max when debt value is smaller than collateral value - withdraw denom value + let max_withdraw_amount = h.max_withdraw_amount_estimate(&udai.denom).unwrap(); + assert_eq!(deposit_amount, max_withdraw_amount); +} + +#[test] +fn hls_with_max_withdraw() { + let ustars = ustars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (ustars.denom.clone(), ustars.price), + (udai.denom.clone(), udai.price), + ]), + params: HashMap::from([ + (ustars.denom.clone(), ustars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + ]), + }; + + let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); + + let vaults_data = VaultsData { + vault_values: HashMap::from([( + vault.address.clone(), + VaultPositionValue { + vault_coin: CoinValue { + denom: "leverage_vault_123".to_string(), + amount: Uint128::new(5264), + value: Uint128::new(5264), + }, + base_coin: CoinValue { + denom: ustars.denom.clone(), + amount: Default::default(), + value: Default::default(), + }, + }, + )]), + vault_configs: HashMap::from([( + vault.address.clone(), + VaultConfig { + addr: vault.address.clone(), + deposit_cap: Default::default(), + max_loan_to_value: Decimal::from_str("0.4").unwrap(), + liquidation_threshold: Decimal::from_str("0.5").unwrap(), + whitelisted: true, + hls: Some(HlsParams { + max_loan_to_value: Decimal::from_str("0.75").unwrap(), + liquidation_threshold: Decimal::from_str("0.8").unwrap(), + correlations: vec![], + }), + }, + )]), + }; + + let mut h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &ustars.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom, + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: ustars.denom.clone(), + shares: Default::default(), + amount: Uint128::new(800), + }, + ], + lends: vec![], + vaults: vec![VaultPosition { + vault, + amount: VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::new(5264))), + }], + }, + denoms_data, + vaults_data, + }; + + let max_before = h.max_withdraw_amount_estimate(&ustars.denom).unwrap(); + h.kind = AccountKind::HighLeveredStrategy; + let max_after = h.max_withdraw_amount_estimate(&ustars.denom).unwrap(); + assert!(max_after > max_before) +} diff --git a/packages/health-computer/tests/test_max_withdraw_prop_test.rs b/packages/health-computer/tests/test_max_withdraw_prop_test.rs new file mode 100644 index 000000000..9b3f5342d --- /dev/null +++ b/packages/health-computer/tests/test_max_withdraw_prop_test.rs @@ -0,0 +1,57 @@ +use cosmwasm_std::{StdResult, Uint128}; +use mars_rover_health_computer::HealthComputer; +use proptest::{prelude::ProptestConfig, prop_assume, test_runner::TestRunner}; + +use crate::helpers::random_health_computer; + +pub mod helpers; + +#[test] +fn withdraw_amount_renders_healthy_max_ltv() { + let config = ProptestConfig { + cases: 200, + max_global_rejects: 1000000, + ..ProptestConfig::default() + }; + + let mut runner = TestRunner::new(config); + runner + .run(&random_health_computer(), |h| { + // Test requires at least one deposit/debt. None case tested in test_max_withdraw.rs + prop_assume!(!h.positions.deposits.is_empty()); + prop_assume!(!h.positions.debts.is_empty()); + + let random_deposit = h.positions.deposits.first().unwrap().clone(); + let params = h.denoms_data.params.get(&random_deposit.denom).unwrap(); + + let max_withdraw = h.max_withdraw_amount_estimate(&random_deposit.denom).unwrap(); + let health_before = h.compute_health().unwrap(); + if health_before.is_above_max_ltv() && params.credit_manager.whitelisted { + assert_eq!(Uint128::zero(), max_withdraw); + } else { + let h_new = decrement(&h, &random_deposit.denom, max_withdraw)?; + let health_after = h_new.compute_health().unwrap(); + + // If was unhealthy, ensure health did not worsen + if health_before.is_above_max_ltv() { + assert!( + health_after.max_ltv_health_factor.unwrap() + >= health_before.max_ltv_health_factor.unwrap() + ) + } else { + // if was healthy, ensure still healthy + assert!(!health_after.is_above_max_ltv()); + } + } + Ok(()) + }) + .unwrap(); +} + +fn decrement(h: &HealthComputer, deposit: &str, withdraw: Uint128) -> StdResult { + let mut new_h = h.clone(); + let matched_coin = + new_h.positions.deposits.iter_mut().find(|coin| coin.denom == deposit).unwrap(); + matched_coin.amount = matched_coin.amount.checked_sub(withdraw)?; + Ok(new_h) +} diff --git a/packages/health-types/src/error.rs b/packages/health-types/src/error.rs index ee673c34d..7ab8c6382 100644 --- a/packages/health-types/src/error.rs +++ b/packages/health-types/src/error.rs @@ -12,6 +12,9 @@ pub enum HealthError { #[error("{0}")] CheckedMultiplyFraction(#[from] CheckedMultiplyFractionError), + #[error("{0} not found in account's positions")] + DenomNotPresent(String), + #[error("{0} address has not been set in config")] ContractNotSet(String), diff --git a/scripts/health/DataFetcher.ts b/scripts/health/DataFetcher.ts index 2e2d8b498..845e7a777 100644 --- a/scripts/health/DataFetcher.ts +++ b/scripts/health/DataFetcher.ts @@ -14,7 +14,8 @@ import { MarsParamsQueryClient } from '../types/generated/mars-params/MarsParams export class DataFetcher { constructor( - private healthComputer: (h: HealthComputer) => HealthResponse, + private computeHealthFn: (h: HealthComputer) => HealthResponse, + private maxWithdrawFn: (h: HealthComputer, denom: string) => string, private creditManagerAddr: string, private oracleAddr: string, private paramsAddr: string, @@ -90,7 +91,7 @@ export class DataFetcher { return vaultsData } - fetchHealth = async (accountId: string): Promise => { + assembleComputer = async (accountId: string): Promise => { const positions = await this.fetchPositions(accountId) const [denoms_data, vaults_data] = await Promise.all([ @@ -98,12 +99,22 @@ export class DataFetcher { this.fetchVaultsData(positions), ]) - let data = { + return { positions, denoms_data, vaults_data, kind: 'default' as AccountKind, } - return this.healthComputer(data) + } + + computeHealth = async (accountId: string): Promise => { + const positions = await this.assembleComputer(accountId) + return this.computeHealthFn(positions) + } + + maxWithdrawAmount = async (accountId: string, denom: string): Promise => { + const positions = await this.assembleComputer(accountId) + const result = this.maxWithdrawFn(positions, denom) + return parseFloat(result) } } diff --git a/scripts/health/example-node.ts b/scripts/health/example-node.ts index 8001c7254..777288822 100644 --- a/scripts/health/example-node.ts +++ b/scripts/health/example-node.ts @@ -1,15 +1,18 @@ import { DataFetcher } from './DataFetcher' -import { compute_health_js } from './pkg-node' +import { compute_health_js, max_withdraw_estimate_js } from './pkg-node' import { osmosisTestnetConfig } from '../deploy/osmosis/testnet-config' -import OsmosisAddresses from '../deploy/addresses/osmo-test-4.json' +import OsmosisAddresses from '../deploy/addresses/osmo-test-5-testnet-deployer-owner.json' ;(async () => { const dataFetcher = new DataFetcher( compute_health_js, + max_withdraw_estimate_js, OsmosisAddresses.creditManager, osmosisTestnetConfig.oracle.addr, osmosisTestnetConfig.params.addr, osmosisTestnetConfig.chain.rpcEndpoint, ) - const health = await dataFetcher.fetchHealth('9') + const health = await dataFetcher.computeHealth('2') console.log(health) + const max_withdraw = await dataFetcher.maxWithdrawAmount('2', 'uosmo') + console.log(max_withdraw) })() diff --git a/scripts/health/example-react/src/utils.ts b/scripts/health/example-react/src/utils.ts index 4300e781a..2c3ed8412 100644 --- a/scripts/health/example-react/src/utils.ts +++ b/scripts/health/example-react/src/utils.ts @@ -1,6 +1,6 @@ import { Positions } from '../../../types/generated/mars-credit-manager/MarsCreditManager.types' -import init, { compute_health_js } from '../../pkg-web' +import init, { compute_health_js, max_withdraw_estimate_js } from '../../pkg-web' import { HealthResponse } from '../../../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' import { DataFetcher } from '../../DataFetcher' import { osmosisTestnetConfig } from '../../../deploy/osmosis/testnet-config' @@ -8,6 +8,7 @@ import { osmosisTestnetConfig } from '../../../deploy/osmosis/testnet-config' const getFetcher = (cmAddress: string) => { return new DataFetcher( compute_health_js, + max_withdraw_estimate_js, cmAddress, osmosisTestnetConfig.oracle.addr, osmosisTestnetConfig.redBank.addr, @@ -26,5 +27,5 @@ export const fetchHealth = async ( ): Promise => { await init() const dataFetcher = getFetcher(cmAddress) - return await dataFetcher.fetchHealth(accountId) + return await dataFetcher.computeHealth(accountId) } diff --git a/scripts/health/pkg-node/index.d.ts b/scripts/health/pkg-node/index.d.ts index 49113852c..7b876f7c5 100644 --- a/scripts/health/pkg-node/index.d.ts +++ b/scripts/health/pkg-node/index.d.ts @@ -1,7 +1,13 @@ /* tslint:disable */ /* eslint-disable */ /** - * @param {any} val + * @param {any} health_computer * @returns {any} */ -export function compute_health_js(val: any): any +export function compute_health_js(health_computer: any): any +/** + * @param {any} health_computer + * @param {any} withdraw_denom + * @returns {any} + */ +export function max_withdraw_estimate_js(health_computer: any, withdraw_denom: any): any diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js index 4bdb06bac..ea1b7513e 100644 --- a/scripts/health/pkg-node/index.js +++ b/scripts/health/pkg-node/index.js @@ -55,7 +55,7 @@ const encodeString = function passStringToWasm0(arg, malloc, realloc) { if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg) - const ptr = malloc(buf.length) >>> 0 + const ptr = malloc(buf.length, 1) >>> 0 getUint8Memory0() .subarray(ptr, ptr + buf.length) .set(buf) @@ -64,7 +64,7 @@ function passStringToWasm0(arg, malloc, realloc) { } let len = arg.length - let ptr = malloc(len) >>> 0 + let ptr = malloc(len, 1) >>> 0 const mem = getUint8Memory0() @@ -80,7 +80,7 @@ function passStringToWasm0(arg, malloc, realloc) { if (offset !== 0) { arg = arg.slice(offset) } - ptr = realloc(ptr, len, (len = offset + arg.length * 3)) >>> 0 + ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0 const view = getUint8Memory0().subarray(ptr + offset, ptr + len) const ret = encodeString(arg, view) @@ -205,11 +205,24 @@ function debugString(val) { return className } /** - * @param {any} val + * @param {any} health_computer * @returns {any} */ -module.exports.compute_health_js = function (val) { - const ret = wasm.compute_health_js(addHeapObject(val)) +module.exports.compute_health_js = function (health_computer) { + const ret = wasm.compute_health_js(addHeapObject(health_computer)) + return takeObject(ret) +} + +/** + * @param {any} health_computer + * @param {any} withdraw_denom + * @returns {any} + */ +module.exports.max_withdraw_estimate_js = function (health_computer, withdraw_denom) { + const ret = wasm.max_withdraw_estimate_js( + addHeapObject(health_computer), + addHeapObject(withdraw_denom), + ) return takeObject(ret) } @@ -257,12 +270,6 @@ module.exports.__wbindgen_error_new = function (arg0, arg1) { return addHeapObject(ret) } -module.exports.__wbindgen_boolean_get = function (arg0) { - const v = getObject(arg0) - const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 - return ret -} - module.exports.__wbindgen_is_string = function (arg0) { const ret = typeof getObject(arg0) === 'string' return ret @@ -283,6 +290,12 @@ module.exports.__wbindgen_jsval_eq = function (arg0, arg1) { return ret } +module.exports.__wbindgen_boolean_get = function (arg0) { + const v = getObject(arg0) + const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 + return ret +} + module.exports.__wbg_new_abda76e883ba8a5f = function () { const ret = new Error() return addHeapObject(ret) @@ -304,7 +317,7 @@ module.exports.__wbg_error_f851667af71bcfc6 = function (arg0, arg1) { deferred0_1 = arg1 console.error(getStringFromWasm0(arg0, arg1)) } finally { - wasm.__wbindgen_free(deferred0_0, deferred0_1) + wasm.__wbindgen_free(deferred0_0, deferred0_1, 1) } } @@ -339,12 +352,12 @@ module.exports.__wbg_set_841ac57cff3d672b = function (arg0, arg1, arg2) { getObject(arg0)[takeObject(arg1)] = takeObject(arg2) } -module.exports.__wbg_get_7303ed2ef026b2f5 = function (arg0, arg1) { +module.exports.__wbg_get_44be0491f933a435 = function (arg0, arg1) { const ret = getObject(arg0)[arg1 >>> 0] return addHeapObject(ret) } -module.exports.__wbg_length_820c786973abdd8a = function (arg0) { +module.exports.__wbg_length_fff51ee6522a1a18 = function (arg0) { const ret = getObject(arg0).length return ret } @@ -354,58 +367,58 @@ module.exports.__wbindgen_is_function = function (arg0) { return ret } -module.exports.__wbg_next_f4bc0e96ea67da68 = function (arg0) { +module.exports.__wbg_next_526fc47e980da008 = function (arg0) { const ret = getObject(arg0).next return addHeapObject(ret) } -module.exports.__wbg_next_ec061e48a0e72a96 = function () { +module.exports.__wbg_next_ddb3312ca1c4e32a = function () { return handleError(function (arg0) { const ret = getObject(arg0).next() return addHeapObject(ret) }, arguments) } -module.exports.__wbg_done_b6abb27d42b63867 = function (arg0) { +module.exports.__wbg_done_5c1f01fb660d73b5 = function (arg0) { const ret = getObject(arg0).done return ret } -module.exports.__wbg_value_2f4ef2036bfad28e = function (arg0) { +module.exports.__wbg_value_1695675138684bd5 = function (arg0) { const ret = getObject(arg0).value return addHeapObject(ret) } -module.exports.__wbg_iterator_7c7e58f62eb84700 = function () { +module.exports.__wbg_iterator_97f0c81209c6c35a = function () { const ret = Symbol.iterator return addHeapObject(ret) } -module.exports.__wbg_get_f53c921291c381bd = function () { +module.exports.__wbg_get_97b561fb56f034b5 = function () { return handleError(function (arg0, arg1) { const ret = Reflect.get(getObject(arg0), getObject(arg1)) return addHeapObject(ret) }, arguments) } -module.exports.__wbg_call_557a2f2deacc4912 = function () { +module.exports.__wbg_call_cb65541d95d71282 = function () { return handleError(function (arg0, arg1) { const ret = getObject(arg0).call(getObject(arg1)) return addHeapObject(ret) }, arguments) } -module.exports.__wbg_new_2b6fea4ea03b1b95 = function () { +module.exports.__wbg_new_b51585de1b234aff = function () { const ret = new Object() return addHeapObject(ret) } -module.exports.__wbg_isArray_04e59fb73f78ab5b = function (arg0) { +module.exports.__wbg_isArray_4c24b343cb13cfb1 = function (arg0) { const ret = Array.isArray(getObject(arg0)) return ret } -module.exports.__wbg_instanceof_ArrayBuffer_ef2632aa0d4bfff8 = function (arg0) { +module.exports.__wbg_instanceof_ArrayBuffer_39ac22089b74fddb = function (arg0) { let result try { result = getObject(arg0) instanceof ArrayBuffer @@ -416,36 +429,36 @@ module.exports.__wbg_instanceof_ArrayBuffer_ef2632aa0d4bfff8 = function (arg0) { return ret } -module.exports.__wbg_isSafeInteger_2088b01008075470 = function (arg0) { +module.exports.__wbg_isSafeInteger_bb8e18dd21c97288 = function (arg0) { const ret = Number.isSafeInteger(getObject(arg0)) return ret } -module.exports.__wbg_entries_13e011453776468f = function (arg0) { +module.exports.__wbg_entries_e51f29c7bba0c054 = function (arg0) { const ret = Object.entries(getObject(arg0)) return addHeapObject(ret) } -module.exports.__wbg_buffer_55ba7a6b1b92e2ac = function (arg0) { +module.exports.__wbg_buffer_085ec1f694018c4f = function (arg0) { const ret = getObject(arg0).buffer return addHeapObject(ret) } -module.exports.__wbg_new_09938a7d020f049b = function (arg0) { +module.exports.__wbg_new_8125e318e6245eed = function (arg0) { const ret = new Uint8Array(getObject(arg0)) return addHeapObject(ret) } -module.exports.__wbg_set_3698e3ca519b3c3c = function (arg0, arg1, arg2) { +module.exports.__wbg_set_5cf90238115182c3 = function (arg0, arg1, arg2) { getObject(arg0).set(getObject(arg1), arg2 >>> 0) } -module.exports.__wbg_length_0aab7ffd65ad19ed = function (arg0) { +module.exports.__wbg_length_72e2208bbc0efc61 = function (arg0) { const ret = getObject(arg0).length return ret } -module.exports.__wbg_instanceof_Uint8Array_1349640af2da2e88 = function (arg0) { +module.exports.__wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 = function (arg0) { let result try { result = getObject(arg0) instanceof Uint8Array diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index eff3224d9c8d721fbef0b1d5863dce2ec97531e8..a8d5acaa080a9cb2a35e8f8282f8ea29a905f6dd 100644 GIT binary patch delta 74197 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 delta 66573 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 number + readonly max_withdraw_estimate_js: (a: number, b: number) => number readonly allocate: (a: number) => number readonly deallocate: (a: number) => void readonly requires_stargate: () => void readonly requires_iterator: () => void readonly interface_version_8: () => void - readonly __wbindgen_malloc: (a: number) => number - readonly __wbindgen_realloc: (a: number, b: number, c: number) => number - readonly __wbindgen_free: (a: number, b: number) => void + readonly __wbindgen_malloc: (a: number, b: number) => number + readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number + readonly __wbindgen_free: (a: number, b: number, c: number) => void readonly __wbindgen_exn_store: (a: number) => void } diff --git a/scripts/health/pkg-web/index.js b/scripts/health/pkg-web/index.js index 4db53e976..0752f1347 100644 --- a/scripts/health/pkg-web/index.js +++ b/scripts/health/pkg-web/index.js @@ -59,7 +59,7 @@ const encodeString = function passStringToWasm0(arg, malloc, realloc) { if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg) - const ptr = malloc(buf.length) >>> 0 + const ptr = malloc(buf.length, 1) >>> 0 getUint8Memory0() .subarray(ptr, ptr + buf.length) .set(buf) @@ -68,7 +68,7 @@ function passStringToWasm0(arg, malloc, realloc) { } let len = arg.length - let ptr = malloc(len) >>> 0 + let ptr = malloc(len, 1) >>> 0 const mem = getUint8Memory0() @@ -84,7 +84,7 @@ function passStringToWasm0(arg, malloc, realloc) { if (offset !== 0) { arg = arg.slice(offset) } - ptr = realloc(ptr, len, (len = offset + arg.length * 3)) >>> 0 + ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0 const view = getUint8Memory0().subarray(ptr + offset, ptr + len) const ret = encodeString(arg, view) @@ -218,11 +218,24 @@ function debugString(val) { return className } /** - * @param {any} val + * @param {any} health_computer * @returns {any} */ -export function compute_health_js(val) { - const ret = wasm.compute_health_js(addHeapObject(val)) +export function compute_health_js(health_computer) { + const ret = wasm.compute_health_js(addHeapObject(health_computer)) + return takeObject(ret) +} + +/** + * @param {any} health_computer + * @param {any} withdraw_denom + * @returns {any} + */ +export function max_withdraw_estimate_js(health_computer, withdraw_denom) { + const ret = wasm.max_withdraw_estimate_js( + addHeapObject(health_computer), + addHeapObject(withdraw_denom), + ) return takeObject(ret) } @@ -297,11 +310,6 @@ function __wbg_get_imports() { const ret = new Error(getStringFromWasm0(arg0, arg1)) return addHeapObject(ret) } - imports.wbg.__wbindgen_boolean_get = function (arg0) { - const v = getObject(arg0) - const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 - return ret - } imports.wbg.__wbindgen_is_string = function (arg0) { const ret = typeof getObject(arg0) === 'string' return ret @@ -318,6 +326,11 @@ function __wbg_get_imports() { const ret = getObject(arg0) === getObject(arg1) return ret } + imports.wbg.__wbindgen_boolean_get = function (arg0) { + const v = getObject(arg0) + const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 + return ret + } imports.wbg.__wbg_new_abda76e883ba8a5f = function () { const ret = new Error() return addHeapObject(ret) @@ -337,7 +350,7 @@ function __wbg_get_imports() { deferred0_1 = arg1 console.error(getStringFromWasm0(arg0, arg1)) } finally { - wasm.__wbindgen_free(deferred0_0, deferred0_1) + wasm.__wbindgen_free(deferred0_0, deferred0_1, 1) } } imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) { @@ -365,11 +378,11 @@ function __wbg_get_imports() { imports.wbg.__wbg_set_841ac57cff3d672b = function (arg0, arg1, arg2) { getObject(arg0)[takeObject(arg1)] = takeObject(arg2) } - imports.wbg.__wbg_get_7303ed2ef026b2f5 = function (arg0, arg1) { + imports.wbg.__wbg_get_44be0491f933a435 = function (arg0, arg1) { const ret = getObject(arg0)[arg1 >>> 0] return addHeapObject(ret) } - imports.wbg.__wbg_length_820c786973abdd8a = function (arg0) { + imports.wbg.__wbg_length_fff51ee6522a1a18 = function (arg0) { const ret = getObject(arg0).length return ret } @@ -377,49 +390,49 @@ function __wbg_get_imports() { const ret = typeof getObject(arg0) === 'function' return ret } - imports.wbg.__wbg_next_f4bc0e96ea67da68 = function (arg0) { + imports.wbg.__wbg_next_526fc47e980da008 = function (arg0) { const ret = getObject(arg0).next return addHeapObject(ret) } - imports.wbg.__wbg_next_ec061e48a0e72a96 = function () { + imports.wbg.__wbg_next_ddb3312ca1c4e32a = function () { return handleError(function (arg0) { const ret = getObject(arg0).next() return addHeapObject(ret) }, arguments) } - imports.wbg.__wbg_done_b6abb27d42b63867 = function (arg0) { + imports.wbg.__wbg_done_5c1f01fb660d73b5 = function (arg0) { const ret = getObject(arg0).done return ret } - imports.wbg.__wbg_value_2f4ef2036bfad28e = function (arg0) { + imports.wbg.__wbg_value_1695675138684bd5 = function (arg0) { const ret = getObject(arg0).value return addHeapObject(ret) } - imports.wbg.__wbg_iterator_7c7e58f62eb84700 = function () { + imports.wbg.__wbg_iterator_97f0c81209c6c35a = function () { const ret = Symbol.iterator return addHeapObject(ret) } - imports.wbg.__wbg_get_f53c921291c381bd = function () { + imports.wbg.__wbg_get_97b561fb56f034b5 = function () { return handleError(function (arg0, arg1) { const ret = Reflect.get(getObject(arg0), getObject(arg1)) return addHeapObject(ret) }, arguments) } - imports.wbg.__wbg_call_557a2f2deacc4912 = function () { + imports.wbg.__wbg_call_cb65541d95d71282 = function () { return handleError(function (arg0, arg1) { const ret = getObject(arg0).call(getObject(arg1)) return addHeapObject(ret) }, arguments) } - imports.wbg.__wbg_new_2b6fea4ea03b1b95 = function () { + imports.wbg.__wbg_new_b51585de1b234aff = function () { const ret = new Object() return addHeapObject(ret) } - imports.wbg.__wbg_isArray_04e59fb73f78ab5b = function (arg0) { + imports.wbg.__wbg_isArray_4c24b343cb13cfb1 = function (arg0) { const ret = Array.isArray(getObject(arg0)) return ret } - imports.wbg.__wbg_instanceof_ArrayBuffer_ef2632aa0d4bfff8 = function (arg0) { + imports.wbg.__wbg_instanceof_ArrayBuffer_39ac22089b74fddb = function (arg0) { let result try { result = getObject(arg0) instanceof ArrayBuffer @@ -429,30 +442,30 @@ function __wbg_get_imports() { const ret = result return ret } - imports.wbg.__wbg_isSafeInteger_2088b01008075470 = function (arg0) { + imports.wbg.__wbg_isSafeInteger_bb8e18dd21c97288 = function (arg0) { const ret = Number.isSafeInteger(getObject(arg0)) return ret } - imports.wbg.__wbg_entries_13e011453776468f = function (arg0) { + imports.wbg.__wbg_entries_e51f29c7bba0c054 = function (arg0) { const ret = Object.entries(getObject(arg0)) return addHeapObject(ret) } - imports.wbg.__wbg_buffer_55ba7a6b1b92e2ac = function (arg0) { + imports.wbg.__wbg_buffer_085ec1f694018c4f = function (arg0) { const ret = getObject(arg0).buffer return addHeapObject(ret) } - imports.wbg.__wbg_new_09938a7d020f049b = function (arg0) { + imports.wbg.__wbg_new_8125e318e6245eed = function (arg0) { const ret = new Uint8Array(getObject(arg0)) return addHeapObject(ret) } - imports.wbg.__wbg_set_3698e3ca519b3c3c = function (arg0, arg1, arg2) { + imports.wbg.__wbg_set_5cf90238115182c3 = function (arg0, arg1, arg2) { getObject(arg0).set(getObject(arg1), arg2 >>> 0) } - imports.wbg.__wbg_length_0aab7ffd65ad19ed = function (arg0) { + imports.wbg.__wbg_length_72e2208bbc0efc61 = function (arg0) { const ret = getObject(arg0).length return ret } - imports.wbg.__wbg_instanceof_Uint8Array_1349640af2da2e88 = function (arg0) { + imports.wbg.__wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 = function (arg0) { let result try { result = getObject(arg0) instanceof Uint8Array diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm index ab6b44af2bb4c4080a53755d5706cc809ea354c8..de717ce777589df8cf9eec39ea2e5bd0b7c79ac1 100644 GIT binary patch delta 74321 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≤7rSjQD(W4_HB+0ZqsX*iQOHt7?JAuV zSu8Az>atd~2OUxgSytqiN&W2Vmrz!YG8mICoc4BHCfyLx(E)VCVKmf=Mc-h_LSo4R zdm$ih1Otav><+Jen}>D2A3e)u^Z7qY4cH~DH)KDG1;XFzl#v(x5}{g8z`9@MPfiZ; zrgza#_J|$cn?J5E>A(z(aZ^Ot5k-xhP;Q;&J@drUINyaQEY?)d{^@=FacPHD0r@Y9 zSzAy6mC)c-?WYZh^3^>I|e9-=ORq!NUALX+ITJH+P zP0c#I{m%?W-a+}ZKk^6G>es=*{L}6R#--2fsJzzgJ%t_zw|!u!TaDwVs02tO1t_r` z@5rB3fYSkz*YxwEo>@Q^6AoCjf!?6U2LO~49uGk2FtRxIPrC!<LG#cP8k*H; z49N68da}v~B#tl-0J-p~0FaBf75Nx}?=g^obQ<1yKOcxEm;U@IVC0w`(5g=*<2@^KI^oy$?%HAn2JSTqR z^?7j)ewVy>6@EW{@f!TPFJ0i?8Idu~i<`iUdb=TLSOe-9I-c%1&l0%>Aq%-KjO|sc z8_5QYC-WZIzz67kDSLhu?5Xlb5>p<$D(#|y<8d1X`>J#t;dm%Z z*xP0Pt|M54F`WD1G=~66>!X~=zB!3Kp>khP3KsE9&4J=ACZhe4NsG zQ*k?ZX=hMOCgU9n9HwBQ9EYih<3PEqwiGDX)CF65K~((356O`tQVie9u-2;C4Em_X zCS&-y(z+epaW52u?kY49@2H)BAm+|Uu1R8VAgkCg3Z;pbRCon*CkE>ve%zsnigpC{ zxCyso!O=J#ZA6`87D#w(4n0->mK$}NPQ|@2viZqM**a#fPPR&qY!x(uY)v-eL>D-b zMGF(zA7-t#Za0|13|~0HS02>6H%={!IPqh4?Csp_*GR#EMC?(ot0QH@Uk_18KX^Sv zjV(Y!0YDCR^di`5%S*K$gj1|72dA2zYe-?xLXn)jqbUm~x_%%xU?XX3LFM@XP7&F9 zTBP6}NG?A^J9~e8X|a3VyX{8HD8o3?pw^AdwPVd|UNl;p_ZrvS2NUjey&vQK_GL#O z?tYy2Tc3SxRO@}tUSs~D4J1{C9S^q+(lxw}T_JhhyWU&72J=w#^5DVwJ;vaD?=_ZO zbsvL>ue{gJ^sY`EtQWnrU#{a5H@uwY;gOfs^S^$10IxoI`6M1rdPQB$f8|(S{qU91 zJiPnLpw0Kc_J)+(_j%X7UR%0-9}qAc6o1-1nZ(7Mb)Wa*>t#K8!@++B5VlO2eDA>P zk43hi7@orB-@HC;%W^$8L^}O8oHlbE#bKeMle2?o8}3rl?QG`-XrxO(TxTmv!1kET z@nM{$;OFbJ1YYXdy4>5h=Qk7DbqSmi@RbSMirpFtxZLEw!i9Ml_6ezoBawa1l{5F*Dz)hS67?^y2L^}BmT$L8bS=qY9U za_{Un=f$y$Sr`InTL1EPz1a(|(7pBMfDwetPH;SgUZord&?Cc+<4~(&%vjp7d~^Np z2Z@myywZM{pZhRW^wXXCm9ax^u2FB*A8Ps|TZ!u>1HrZMCahw<3xqlxc9_F^`45{a z`H*@NrC?#P02&iSWa;+h-kp2LM~P|T^LKlD_x>J`;V0v7=lQGnvF_a*|Kklp-0!V> zYl^sk^ZRer3NhKMdizM(xpVW^-X0^8lPQi72vW$J?A`v(c`m{@l1P?trw$Xwf!m8z zbO4?W<0!~6qo>|eY4Kc8(t(R0f`U1`itF&s7ti?})sXx{13m=D^SwRsetw;ENL&#yzBU znYZx0;kADDzd{b*&~kz(m7;mqFpmJXycgf=)9Z3HD-KCt|L1#5U*Ft!-#0~Huq~6% zZ2cys-s}!89kOaSzCU%)`d_pot^f>MUxsKc+X+?}gX`)RXDiP;@MGQU)%^J=Wa!QL z^CGyZzxi`5ev==J7tedgeUQTW1s@C&XLvV$u(;n2N;a(EIwkEE&QQP|JD&3fb{=PK z_%2#|pLzIvG2Z%7#yUe(o}_lXYnrRq%-c}~YvuxL@Xi6Azj`Mj;B=1r`axaCu|?xV zwY?(W3$$YsnZn0O3bWyU-#J3u<5?e8;CIA_4dYhv#T58|n4=@7f~)ju*^fytVXA-3 zY6}qLP?t`sn!=mcF*}H;@07 zMKRkYS%GK(W}Swv^|0X%1jDw&n zx$)X%X|Wv932POu5v_ChXtN%{!}bSMMiP_x~PU`W9$e0AxTrUj^K=eXV4tNAA(Jwf7qAcW^6gY zV8^!jk^+%;4RzO846yYxXo5`>TyzQWbuVZRpc7Peb9v&~OkG4&WHv=*sA7a+6eW{F zn)sqjG*%Gj4uW*ptp&+MrX?bVoCxkm*p{ITf@D0iD)FCf-_ zZq7b~`>S&o@bV;Z=Dq!o34nOWKZl5E-f90lG9TQrO=W;H2M+cz+m>rUwZ0|)dSmV) zXubMhD=@fC|2hW0o&P$nLQiH^()EPt*v=!;N4D|U=`|fF_2ztB3RHdT<3Xp~{7^g8 zhx73!=!_Sk=bSc%N^}*>LrU}m-pT(yofNq9-(RZP^8P!%GR;ozE7%QK6-sR_=E%K7Yxsg z3KJ^Y>BM!G9YEc;^RGLy7j)uxF$xGShnl8QTlVP&x%1XrMv9@)GoW*&_2@0rM8&ay z1yic200)P22%7dzA@X?hjX@4%7cZbWDTH+EJm3(-N8NBbuLl;+yya;zQr-~C%#Dk| zN=A{wlR1Bz=;d+=MqrAOgyHJk0K{?a70aNl_v5)+gxd55RYHE1pRr0GU8Mu(tilwS zyef~g3inz?E3e+Gt8jytyejvz3b#Z{y16=B2F|N<8|(PM1kI?|Zgaq_W))_sd3CrNoY&0nSmhvKxDcFIWoI|7m|d%EVHKt* zprdU#2l7s$-eWUd_9hMPU>y*jb=K=T)Pwm|u4NU@;!$O_u7V1Vb;~MCYFd)v@b6SlOYhe<~+pGk!3m} zg6t3`Uxt?DC0{Zx8dd#xw!n&!c2INy)-X_@Vl=8i1rodFUUF+@1Ro%e5S*sZmwc(5 zS=8r}OQ^v-ABY2^`z~&XNo`s-h%&r-!~JVaJIejkG2>@I2MMaGOGHduLipc}0RVHM zi(n)5(jbEUMIjDWC8SXo_~-%W@O~Bo#u95(YZ(<3CtmaP0a zC!;XK4nA4I(f<8snKMJ8s<{2xcEds6aIBzA)(C@Z+ynOfl+|r;$E&zsFRO8e1JpB| zGseBFr!(y#F|6e6Y(?B2uIe)xVEEI>^dxh^1PsKXGg}QfXEip4!&+R1ypC0U#nYLo zVR6)vxr%;`n^=Qv3_DCu8}@|Y1W>4@cg4{+WnK!4@}4Vy7C;^}koh<)YGyr6VbBxG zZ<`|Q$|n5>S{7hR;=fI33+#2MTncNH+S1rB$I3;p2%LSV9Fn77MMNH~KFvHG6^+$v zy6m-qjhX#=cm-gtHHtG6jKXR)GyP*?;PI_yD%%j~!d*3(Y{Y>YbM4S6<(P@#EG_Z2 zHE!!P0XUS_yC_CG{$Wby`k3f{;-kpA!fcu827a%YOk&m8h6m031Nzpc|BP)S;HA$^ zYYI6(#F-V|D8|pDnf)=*E`FbRtU!#w;SUAk$ePuEfbMx*@f~n^NA?Qt?`aGZpm3uq z%uI@lwEKY+aTjq5?lEn`w^fUoN_MiD@00|TAYDDk|K^G+O6XPxTEf=>wP*kn7C4w> zz;rCQ?Z6p;Pm2NTJ>Rt=*=@MUOGhbMN(NPxl2THQVl)RIK_ymO$Jhn}i4dy@8}ToO0=QW zSVJiV6^zbP3id%tA<#dL!AOh^X6h+!kgxB}Jk(Q+mb>?6e&189sCj)ag@Fko&bBup z=P9Ry^ie)nC(clCGpblGW^O7G)z}a~m?Rhj=V1^mtlnK<>;n`A%Y73i&|jFD9P~B? zXU}RPdA`J?6fq?=IaR5gsAZJ%oU-C7^Rdb?NFId)K50=6I;oD3mkpw^3H2EIOyf463D?Ns z!AJq5UkX^O?Na~Yzo2K{DF687rJ`8$zE>haL#aG)BY8$m zLNed&CB|UN@^UXR0EdtC)z!U4pG;G4Kr|t9N^eyGMFZl>-@_8^)gtp?Z}Cv+whuw* ztrT8HC)CyBnM?YJrG>vGYYcOCvcij*Py2{d#I(#QW#SUCKl7t9QLVhn5LCjxGEqJO z?IMQN2*7YS>JYaQazcO74Bjh)nbq%=?NZr;nTdVH*>b}tneX)#(~sQnNxOPE!s&H6 zwBT#uR9Ni_EF7DQZXsu5-hB`P2{r2Vh>)}Xlg!cm#8>)n{1gq0Fu;>giO_S*vcrmV z*ja}st@|?%_Y)P7mHPbUeqv(u<##aFU%r#6EEnVC9q(kOl#3B^=R29D}&uotW*Ul!)95{echrB`9S zk-4iyruf7k7%AGj}7!Ho_T$wL9~t{^EeRA=6=tzT%C{KW%Xl0@#@g!M8kK zpAu7f{9{Tm_MItAiy^%_cLG+zT>uT!;tYX$w@%KSkjBS2!7FKz!lBOqbr?TD3>Fg} zKYsvJd!&N_?jL5nf#PH_KhrT#oZWlnyBs!zJ_LUrYY(t9Y-{o$aaMiz_jD4Pf!fXi z;LQ3V`7G};^Tr^ttv6Z6X(BU|dz_>AQ3!ku;*r6k9KgLWSYW@c%z?q;5o}wPc>u3; zM!5+U_8H2&HAJ*cY`d0wLP>46C4$^R+LZ%-!xuTYNDZDeQVhpj&%|R!ACb)le&jw+ z)Pk!rzo>@J-JL0{5toU5nWZ)2Y_ae0-_?j83h|}PEyKisBJR9`X3`RFLEDPV&xVOc z*|F-@;iA<2j;|rx0u4!pgqLWjji830UGM`Cx-oG9$lcc93n`*!Qr$3jqBVYtudi?=_jHT z={5M(ThA+{MIRC+_!w+lRo-3hZJF1}`=}k^H_(#^*b}d*%2mdgIzE#4r zf&sxwg$N_NbsFx!Pf)DQm}Re*qCdr&3rUM z*kFX-BgMkR9Y1de_tD|}a>g4ezC0Xrac&syB$33Rrq`e?<``IivFSB1La6*i1gf9Q z6ps>r5$9z>wc-yryj?49D*NZ(0Pt|%z=Ubz)%I9_T=%YHa)q z4tGiC7yEI1SmN%Bb>b+%lo%~0;&A$Ce0dVEBd193_)C5vk+Q1##?j(2R6KT!sKMdl zF=B=&%Dg;AjLM}Z+?a`v6;(Z%W`G!}TIHtC2Z1tzsHFm4*rkTpp9EaW_6=* zGW{B$DsIa-jX8bwK!dpDprZ4}i<=KEIzID4WAuBt8@{d|)6(bLoL_ zS(rPRO9U~%+jP$KTW2_2jc-PSX`ubF%@ru&n{g_DkT%b11`vj`q9%+Mp0Slg0)6Vc zBW^Y_AF=2pYlB{m7b?(LndK?pAW7eVdR}#*_Xa=}8b0-vF7O}J&FP5wJOl>=n59g? zRZ%eMsBUhDOC^)p9WGrRczP^^&jn!6Hns~IXmGH>pYzD_XABGwYu`mCz6XvV6-$_J zf&6l&7?SZ$SwZz-TPAWR({CY2Tn z_I=O;l(&y{O_*t(Is0!^o~3iZFGltui#ekP?$Q3|d7#CA0uBRS)9C^uCP`=n27kY5 z7UNB5ASv9k37ggH2?u~L4pT!YA4yHEpaoFck1hypkpARR#`%dTYwp}Zs4bLk`8m(Z zh(A3oW<2#dSgsBr8T=?5^9X}+6(($;Ibs_-a~PRECx}$PZEI9Jv?$wg3g>CtT09HC zI%A(8$_A~}_u0%Eb;=I}sgBs7v*k8+lsSHaxEd1j{Dhnw6&)j1!4+W9l$>ywS~z|{ z;W)%Acwa zucx1aB3jb~=TuAjq#QU>gR@dW0iv{at@U_B| zOGtU7lPHX+H7y11oD48m(zi5K!zjh>w<0%c$B<$M+Df(fjF-sQCWK3X4) z%G4o@Y;{XlhbKbq5T46+$0SY6C}Avg1lFNxzy-oYXt^%Nm! zUI8`P{z&GPFNt$2+L#ldY$=I=LEI#%Qei@@w#-S#ieGC-`Zw=nhD{V><*(n#OrI!* z(}Di>M6rmTa`-rLq1^LMX8v*ToL_z`bNz8*PT4$60kIul5%@?F_ch%M?r! zb@H`$GDl4kW%5_=WX_x<`n$KT7xL&_T45AS7U)Bh3)xYoMq$a!x*nim*@@pI1ZDKM z!`1!maAS}+06%5Zb?E2J6l#+Y+c=|CxW!(Y+eO;IR|8cZJzmLUTYMxvk{OE~x3|Nn z9TZ2bmWwZmwjR;QhxG&TBil}fcSfRAAaX`Y5fpj*A8S=@Tm2tHL1UG|d zi?-lnQ+UHO)RG#H;~Lmv>;!QHPS@cUzF6mUB4HFoj4l7VfS(sbIuLI4#c?$@x#JE6 z_a|CX{PKEhq$M>7$G7nnrz825rTu7jGzxaF z6?A3X_D8Nhy>5Y#xNRgnblw4iymOY9xhvz?g)#SSPhu`WApCZG-J6OGg;=EkIgte+7I+JmSw3&ELt8$$ai2C1^|30zK2OnwcKH*z!$+EOLm%QB{a7vX1M%nyD%jt zaiZEVocCUN;_e7jGw{l^W291iqeP@qyu?7CAvYA@y;c2gqb_`&h3a$9Fq0CGz$%!j z6srGE_6`l4eqpdEV5z%Bs@@J4q*7`=y)y2#B~z~Y=ut|2H5Vp7WQK$VIn42FJ@kS9 z5%PT@EF=;!7(Dnd28Nd!K|saZ2-;YXR_wh9q_l^D6z2{?!_Q}<1P|=z9UN~~>3!S7 z;95DkTDFIeD}=^I8WO%X2RehQioV-XJrA>rzM#xeXav074TXW`UEyv?j61qJ_C_KX zkj5mG+9a0r33sKkdM|z_0mj&Ns3CPj=XTOL7uaPtCg0so&dq)_m@4cB;-N50L<0m6 z&ebf`A5nt>fT9o31u|9Ws6zr9m*O6p1k8c?UlZdWU!q@4nUNTk zG!$Pc3hs-bkRQA*QVVO&rU<8*;DH6!jc8jgaom;HVh>Vy(e&rm>GRYvn9r$FDn94| ztQ>DZ_b6*-giY1s46D^lAH%sJP7laL8WYjZhB zINEsjfXjik11zW?4D4G{5xqSEaL8z&KZXGBm8V--7maKMLWi3qn3$OrW+K|x1pj$3six8$Cz9e|GZW)F{0K)VU;R=A47?wfti#vTG& z@GcN;L7(H--Hn5=PS5~5elixNXRX409%?uYAVxW6dhjNNaS%0%HV0`DI$iLUK7VtIIhN#*DxXAA_XB4_y*>JHlN;Nq7`18 zR*p7Mu*pOticzJzMDg{1kbpMxFcPpKCjo6HBp}Q7001ErQ1&dqMn=cTY$YH#0)(66 zj1I4kg8z8ACaz_}M-JsfeR}H)5w{7S3B{|&rqT$`Iv`?*^@_sZIyE%{8HgNwOv$vK zQ{}HlT)NYc3wRg+%G9i}21*lffr&|&ENWNcX<>z=7AUAuEn2Hh?1ZM6Ra5=|mXiG! zcW5+}ghKpKBcIJAp$vswG^P}rnH5?SQlJH12`yNWKDOGD%=G!xq?rdFllvh@)?yka zj)3w3)k4*vnEHgmsNS@N!tUz`+{no~7H$tC>_tH>E$j>|CrFT+ zVGrdCJEn^ecA(N1cBtOZH6!29SkGY={8}c7(|Ul*%2MwU#W^A6ep^orgC_Ns^Tea;QOq zny7{yQwHfIWLqg6OpI_woij*>wMQ<^l;$(0GIeW^w#uwQ+Vo!1t-DJ#w2q z(;&fn?_!YXkSKXj2C1HRH^$RR`UE%yXXB+vDp{H%WUEx!{V_akGCO@vW*B7)NN>bT7Y&Dk|Onv?>4eeH(up!PbGvOn1yW^>2c&j6xYO?#W5q>*{Vb*@^fuD}i7wEn?xZnf36D+T> zd-)cr4*LxGw#nH5ORDxSQ^VPWD;Th>xqx=80k*K<=h9^W=XGmmccI6=98l2e4popd zflj+B$QeoON5;BaF+reSUITMSJ-w1gY{0&U<(05#oIof#A}|JK#l%Vd!mwK>YUTi0 zRg45=-4F+P_4nqgJ;gHuvCFL-=*_KoE^kYwR{zyDpJPcRfgDT&1+e_ zQ$ok!xI=bp&D-&C)|$7=z?yHzmsz^C=I)tl{`cAD9Lh~eOVSE79(Q*G5K7_E`~;>v z>6`K-O?fz_zK2Isz6hpVu`|C6Vuwr_ehrwi;b$Ejlr?jDJ^5&b) z!FuillfVM#f1n%GWRQj?YIm59Lo#Km?$LEwvQMtN&owwc8sXr4H@f?=!6m|!(EUP# zQ;NANYXYW-mV^N7TtE!0 zX(US#2jmJJ;;ytwG3}VbVheg;*`OHgzpx@)o8TvRTZN{zgf9e~(8&K@Xe)1HrlevC zM8-gWvPr{~+l3kakkWpR{(79^gbJ<9%BX^~L824jl_N-`w|~-YSoa8g23c3(G>MUC zGNqbV0S@?IiYs?ruVU7P3^1uCx5h9tW{4+5?#B1!a`QE?lTz?{02Vd8J}pJKBRV%- z1WXh;MI2Sgd0{nB*^3b{eoBs!NxY5%-xQx6;fK>gHntnxi#pbN0keYdDe+xCRKovQ z^mKm4$D@24+{EV;Qz^$u!QB~16B|>*)Z0Pmw)30D!{Fk)fCOO1o3M_{0f&hiz?)1J zvWKu`s8bd3Dnd9OD_3HmLC-=jwO%c(B77d$Kr#li?9@uz_BMsWA{CT|O%vSnnho z)5-A%aXV&{eh3)6qheUXS>FoEg%JV<#GD~RSUy&E_vWY$zO0E-G>lm~{EJ!zn)FID z;*yj60?duD*SU696q+H+v_`qOyN>>Ik%JunAYPy78vVym+oIm&1&43+E<{z&H8QEn@xZjtQ1RRzz^8P|~^}+|B}i z91-loc0xLz7;6T)ts28<-uva}pZV7hAG-a?zZ$3OB?5F}#{)XuEm93O5)Tw_tn9i_ z0=Tf)pR)CMbOfn^a+aC?KcW}H%@Hgn1PcjaQwpO*xASq>pO-3Bfdh!x5g?yJElmwD zh!rEt|9m5&DSpAHWE+7CJhWHA)R9Q@y%#*WC!rZ=cLKS-j)75_?tgOlkw1jS$RTfl zG=2bYnK;PfZx!V_;|FicQAG;uuZ}Kae0vGAt~zQae=SZ0-Vj~ePs<{Z&d`Y+dKg@W zaOh2gF|tI9A1J%h=cmOSQ#D3JhZ?(sj>W@Bs^tike=Dq!ypO40A~&Na8t5WiO`OD& zNUXw~PX&O%esOKYO|UDiBB!*RdxD4KsWgvw#IfN!j&1SO03O%JQ|Og*%G5YMu}Mw7 z8NannWr&osWm?xNNcTrlan?SdqGC)&PT}1n+vLXcq(TjP!}JlasBo6cTzL#^|Ato0nC38b_q8S6?U3=5!9KSAro+u=%9I4E4)Dbh)mxzmbeh}|SIGVoJz=pIF# z4n-ZsKqSNW6=GKVGEU^+Jq`b*EqNR!69BafH8Dp~kASLaj0`u)7@C@Vyc2P0Wtxlh z2f<0(0d{ZT$Da&KyLUFIpa`o73c@Nvg0SNd?p2gMnnzG}LOx{?ZAoa#Qoa&CW%ndB zWjhingv{cwIV34`f=UDMDT}36lX<2Cd`L`9Ws@Wvh~h*MXm(E#rcPY+B9H%D}Tc&n5%z~L#o4jzD}?h#ehXwO%flp{J&dw2w!fiD4@iD9CfTJ}Z9Sg$P9`U-Qb7M<;| zKL}cCg^3T(ad5uEZ04ZcJL0&j2O5Z*VkiGy%0*DyaiqF1kpMnfh}poXja)k<1%p4} zm&2hpnHFaT3t_v1*DOIpe3lYG``T6qE71+$t}!8g4)Cw!QY#wJIVWzWwMwBK&?xT* z;s!=wU*Sd6pblRI7u?x`nh^#Cnvp_=`NP55k@!P+FBWLWph?J!tJLF2J=Z)`J&G(R z=>U2c?Wn-YYR8CL`}=I|2#c61QQ8rbp|qphG#BW}X~$!=14MY6(qUp6E2dplqoP|t zE0T|@F^^m&UxZx5zoJ(O$Vqy!=d50h=#6u!SL2_lSAk$fP(-8I-eKsdw6CUPiBCrt z69MW?^s6i)^rrgcLfqGzVUiT7*_zH_8c8N`K{cA6sT#wIqI7xmkd$gPQ_4wD!=q}% z)WB5%2(^4g$hE0Mnkj~m4GD})726_JNL@Fl@%D?ttCk@m8JVT$;I7n+lXE`73cwgn5tW}Qo z(V%jW)kL!8`DjR=Qa)Nv;nQBgDY$ncD5;2f(C;9RvtlPx1R=X8?xqriMQ2XKRO{FH;|DuzC+ho`(Q z?cpVS503{&y$qLXwiVP}{5`Fs=Ew4@_wrbcD)<7>M_a+t5!x>I)00fs#a-%Z39_}u?j+_%72S)6&l zGbi_x%OTtX3G$u*5|o>OpeUL*T$TGpM8!e~K>{Ix5L6U7QL)7?ws@Uvr8TrvK~b|- zo3y1hwRB~>sB}wRYOzL(EA@vq{g$sE?y~y*|IfVdIVU05wY&RU_?^r<_nBv&=b4#j zo_PkcJs~YFyZ_s`-G83h#ZWE5*sV$l?oa^Lx~#9NyKuYJac;-A_CQXF9VwQ-qq;J? z%+AcNI=;SY@51et``m727j6d?r!L$MdZS&q-FcXu`O{~rFEO6Rv>=Wi=O@Cv6B=-RQrH)kzWmRaQsy0LiQlmUOvJ1F$3q6O%(3*ev=S)|bT%>?U;J(Eb&LfD*Jpw76oZn_@KzYKz0Z zz&wm~tHs4+VQg5ABpe7a9AW4bVxx6Ophvfw9fNAwcF_)pWm3AB)f?y*p`+A60DC+` z7aAC+>Gp;2Cj{XMb*)Y@0LP9e>Gr^ak1n>W?R0x#+e#NFG{@-ToQzueZr*r(Bm{vH zpzV6Tgn{qda0KibM@}G&U@(v&klu<{QKk`tTp}$(zgvXv6oJL#%qA0WC`MDTHc5$Z zS7BR6wyQMN2FT@bDVZbN)dAT10!0RJihy~Yu-g{}^|b}1{iTAYC6|;r6?ONv#r0-! zv=r+0Kyf{=W5qDxRqXEDW$r$WTdQePqpsV_=I_P)up37KOf&?L;Iz!mvvGNh%Vr$# z6=)^^^yM)G1p@?!(JgADcEw_pgfpV)ZVv9CISIJMzA)gBn-D{JIB=Nmx>DX30bero zddJurf(+$B7iGE|lJes4%Q-*%c-#ds1jiL(2s^I5Se!sGZ>pO+)y=3ATYR~Mo28dd zA|+iC6A`<^ND8h*tcY7ZB$qD7JNZ(=ejMI;{;dLv!MaIx-a{aCvGbwl$uu{XbPhye zpYqyq0Zpy~U?6c0guSe&|8tiLDG36pK?sM(Jt0|&+e-k)v6oIdSqUxyTmf?Dk_iVr zDG{A~%>zZBJntodM5R3FN%2`rJ0w&Bs}rfP^vTmvLYu6Goa6*rz;m8_d5P1%C2pqD3^@6SEFf{`Ud&9Tn~A zIh-9(8V+FMTK{I*uT@{G(~rBGc$!bFriUyym9}9$%AQ<|W+U$j@T~Lb_2*gE+?^UF zm|0wnayIT2V4AU9KM!MQ)hJ_B>_4v_WkQXz9oEOXRiiwq;%b!b3VKjqP_I7*846Db zIFZ@L1$zBwc+N!J;hYlJ>p#S_fXyMyKeC57>rXS=(kM+4`5|%~PKyjDMD~VZ_&G_h zAJ1G&&4Cgr4xI%U$6~&sB+w6|(`40l3%sFg+(P`p8QTQMaSM3FUx+_(Kla3hR3QxF zu`@2@9xm`>e_Y57obSgoruM79n-^39$s_a^VF7Yapeu|qZsH6M^xQ=k6I~;_3kfPV z5dGpA-HeIOF&?waLJxl%p@^a)Gl;b8GCFK2;E;c~b~x3HTC?zVL5jDkR7>Gt>n*9`=9u@p%@+-Dk&dlo;aASg#aJVZc^ zO`zpu4aFwty%C5=x(6DH0!S-F1+;kEU6?%ePZl zfD*T%a=Hz;YY_6l9$>)eFWfa~l=j(;p?_4ltE+NitnmwKC&CC}_f)wvp`Ez>9PPw* z1#Ic=t^pUVxNHEt+rjgA_;zN|j_-Egw*wpui!k2@eg=$fNXMOGq}gC02TPcf=jy9|JV_8i{z~L|_?cB<_e`FtCKp6LS=NhBKJ(ZjJ?sL(XkF zkUOrD*nzh^?Z}*_Rt^}l&k01IBzPGFlM!f!j2KH1AbR2+3t4FA3kCuA2qj-I$df}S zQCUF&on~oZzCZsoV7&{9V7E~f=4-+QpFnCcJu9E!O1MY~(J^E#crLrF;qH63ZQp6y zb9B~Vt`DykO9fGdPuNY!rR^?x5rflcF_i61Zm#YdOL4E3r?lg*QAECma|YVOxcXik zXoSS%y$~l(3n8lUXe$Da*owem1Z+k4vIa}WvYFvpUZ%}Vdt(T~)MEc+1`){|GWI{j zA~S+oEQ^HNNfL+T1i*>>V47PaOZhfZC|M-0Q~1pT?-UjZ#F3M4JL{$Vbu97=b4U^c z76m{;8Fj(p?LKP@cwNBzI?}>qhM&(c$zp~7oSn3$3713i609poQRHgvU2bb|nIZ7BoDv(v_-T9((g>0I+}j!$0NFMM z{Fptb4Bo}r#237+0oyX&Gqk3}GNaKhGqz`m8uvNZ2lrrlVBebHmO<9u2$%bO5!Z3b zwdVu=?ZKVtiQ5{D>G5TT9Dor(AZ2J&P^rlBj$P@p%;5PfLQjMvOX;>|SQ!C!Q4@Oc&ZBGGxcF1RxWPk#YN*Z*~~`on7-W%|F68Ms7`! zfq$FtnBDL*>2dU}bDucB4(mF<4x>0Gahp#DX%Fi1c+j*{Ds4))aKtu8vE)W*9;V3r zntYc}fQ32_0BD3Obz^A40)Khf9KXwVRCSTN9#P#Y|Kp(hJhxff&WDbN66n}Qbc}2N zqXC`^X!_GDk&Z{coU+)VgRpHn9s^)~ILJryn_|8fr}@B+UA_-`f1t+3EIYk60Z&UY zrOJ8VV9c?7UCwhMAxU|jd6+-Fw%Q9OG}X=;4=7*Ze-F$ZfJyrJ{Gzfr!2Ka*C8(HV z5eMhqxLga6*eO-ld+nhO zYe4}nwF*Q@4X;mGdt>3GDK<^W(skV~&kZFeSD`f0uY6DvCzGp{m5L||+|o^fvW=)v zV90Tv6R-z4l|6?$fRfXL`6Zy`fVQDPB(#n-j?V+aU;>}REZ8)lChqo1+v2A&qp?Er zc>)flZKIQP-xIP!U-!KSxBHO!eDRTzMUZ(jNB8>Q9xmcoz!ZI+$X+p+d{J1E@WpL0 zDqSZaBpVefRtI62nH`McA#8(&#P>j8e`jC?xGmKkjFXfi9NS~B)B{s9rt1h*00V$G zb_4LvmklPt7D*o3Mx%5V;$LLhc|O|spIm1Wh+Xz`Y(Im!rxV!9p6=YM5V*~i3|l}8 zsl7%K7+|BB4Bx$g?@oIkpsREU%OwaUQ+?U@$;90aEBlrm>B#`_sT>P`i3LSSE0J!+ zGg7P>qZ-udwIhrhakM4~<22WXb~FQV_`MU~#{`K^T8UJkJ8lQAv%HEO_2pGKg2o{) zu6x8B7iToMFCct4B!x5h{O-lg#^5fE+pH{bTJ3f z?hHled+9=CzK1R(;!SiRDc_kwr4kWxEfhg^9wflH^T#u!G;-J+F=(6Eyfn2n#JKB# z{2#j(*}&uk8<;Lk7vZL+P+x#06*n+lxGgC!;I-!2LR~c0Zdq^xS+*>cxBH7;2$h9; zKz6-JalBHA<9{^HoGnad|M8Y|L2OG$W_P?&vhw|CdA6!;>yQWWO)np%f; z5JC`CgvrW6E>8BfVv;jBK*W=eeWyaUA?FZQ>$DyNOanaU z;7>9HEMNBV2M^~3S544vhkF#$NQbHP1 z5{S_*A!kB~kYf_kkraaCW|VU>Broq;4B~}uU{F#bC{`ilSR#Z&bObh`tzP|pz|+nu zZGxPKlG@NJA#F(^%@Wd@6tWi~P}glv3bI(fHz|Zz4(v|~f$R|>jfoIJ99X30b^((6 z6iREgi*>m)7WSR(_f8~nK$5hw8t)bF{H4p)m3Y@BgyvcaAAzBSG)agLLkZz(AA;Sa zG_rf}{IQTUt&%1&Ug<>Oq}In!JFqyphw0QE!!b*|gXt8cC&MY-D`7wamg!b$I05c- z+H=R8ntILy(1s2Uv z)xBV#kdWluNX7_`1xtmyW~ln~s|Y{Xf28Hk3-`W66IHzF()ujFIveKP@qN_U?pyI3 zd;q@lr#&cX&2`tRYgM&ry0&wh{_nMFPWH#hj`nzi*LWYBxieKk{>h)PNXk3>s6U#` zGu0J%{ob7A?){gt5i6GEmZHWHBqCKXUup3x5?q#ZGsT;E1LrH^CVC%Qd;aRE# zFMSdTO4w;!gh0PjW~&h@!;G1&M#B7MIo^v_51CtMD>t$mU>+mGippS4**rO0jZsbJ zH?!5!{9_zkf{}yUq?C%8UmRA&G4g)jelE@D;^-vu@ABF!Z&I?(Ui>50bKz#&xdT?x z8A^kkOu@w#rX4XoNghBTLC;toGC2WDs(20$ECVJkcRd8p8T^qKnjTXs#Y~%{hQV<6 z`Z?+;ef(k5Z?2lEo;J(os%iSjBjzdV{b;W0pWXBrfejUYukkU{a~=%6Z-2y;%~KPQ z{*HNScK@f*0xZ_Orav&Di!V4d*8Plmcb>XPwVUkuYE;^ZXHfq~#+$FM!b=zp^I;1lYVxj6? z#%hogK*oXt^c|T+$FXpi>$ckC!-I(zV>B3aC(EFJUa0o!T_2dHMQS`i@Xw3X@SMXm zZ;R1J$^ILLgXFByFUlV?dBQMh$jrR!)R@>St!PpYJdjJRE9LE{w-3FfSFe%YUi6ZC zxwzuJ#y)6tx&;wg-lyUK9iZm49cd$vGaWDx)}rg|a~06cfVFX4nr8b{WMdAD|576W zI0wg-QzJqh!c>n|@;SzuN(8`E@94jn=wekccmZ>gNw${=uv}V<@`Q# z^I~;@-gd~`wOEZy+Ye*Hu;SaYSdGs=`!b8=Lr-u>-XELK7OSD4>!H_!yqnGI*Ms_= z7N6e#h#vo0Qv2 z*b3H^cFwR?+|9tH>YBjRd!h+@{xM#GiQcUGm=BkzeDnBHbuoG-jchp8DFu7{%P*-} zoq8hy$-R+){Fz@!@g-3PhzC$&cv2$wDumeK za3%d+_E8tEtlShBx_3i(C7jDYkSRtjd{f+Hr7M?;sUavB?#!XEm*oO>xIul1AOZA- z?2u#R@y&Y0QkkGaMt}p%b6lGrSEvgIxBkMSrsM6BFBl=VNJ^u>-Xnl9kob*dIQjiY z9YymnH2-M+ZJC-o8nw!*8iA}6Rk2RIPby3;fp?hu@p?9pZu4?gJhm02;Pv%R9)QEi z>InUx20m0N0qqRM9tmU}dCa`NT$PM%U{H?t0G&*ij~sZ7{NBqyKz6SIbGO%QeSXGa z7e7Vd9EC@NxL~#u7;=`=F@W32k&k4UZF0*>l?oBODojyMBKMmsBfq& zGpRz|Y3M}u>4{K2_0pe{6-Kmrb!t1Hx%mtBsTQv-))&9+lYYHMsqrbwghU>QfU17!cunu1)O~YARVd{f(xf+o@0pGHbe+gjCk#PoPL;P%;Pp$(DgNQXVQ zhJEHDZ!g4~w!kJZ?Us~kydR;@yk^SQsU2zN|Q?Vnf@Ep{)xoYBDcRN z+;W~MjgIx33^iBo7K@2rY_KE*4mYJjdrC)2AFERZpp1TXYLqH%xu#Cd(CWRGhw9ZY zQie3KI>H`8CpYR2@^&&no*EkD4zRu!bN6O-ThN$KH>-DZq+u<@%EFXVb2Hv;HWN3f zf|dbys;I(3>*_6-Bi}RYw&3Nl=Aa6o%mA;AKFK-29mJQ%7xl7Gdm1gN(Y7*bbdi*X-=Wm}dKl1(m=KK3^RsHqvPMGmqRk42egt>96nq0CF zcBCNlz>WwDu@fYUA>NMn325{jP%E*Ow6y$uE4as4QcQG&CstiZPf2uyW9@~7yh=?9 zBRvU;ftH3fs5g}c;i!Am77&h!-J>?@50AE-zDFI@#b-f40%ssx4{z7Ife))IZ$Eu4 z=B@iwVazAABTUZ5c{@pYBfPdC0IlsrGHfd_t@*VegO5iYBq}Mv$F%l%fRBqu;sHLk zw!PEV%g^H@bh9LB+5Tm0gwu9NGh4p#6)eJsHk>4H%LXk1(S*TEKraT!GLUTVZ%>*t z+f{LnU8`g7gIxjEQTM9>`DZ2hawI}AOW-_X7T>RyjCqdpFT9W&c%8EO;m6L+*M2|V zU}$6i+s)vws%GHbufM7$!Rh%oYLajVylfOo<_;Bqqo%DY*$qUM}e8uGLP`yEYLw2a21$^Ql zxPu)VAM&T;0sh!Dff;lfy_4|(e|($&i78qxe?UE%(t{+gTs)-$1(E{t2GjANDiD^T z+ybcJr-NmJ6TrX$W|+oQSf$6yujy-wG5G~7LlVNgVt+3bDQzixNEN54cUmeN)m>VZ zn%{pzohSmMrD(C(yMz{(LHYOu*B$K^g5&=FadnZ`M0O_Zq<7dI9MF~U_8!3Wh9Da1 zLOr~l^a(47r))8Ec461nEHNhfgqqaSwo7IDoUp})WPeL3)ywim((eU9JrWS6vZf%Cg~guX(# zHXf)Gj|cPF)TFK`U5KCtUu;qkuYsN4 zyrhfn$!2nlG~9_IXXF_XXsF13aT#(z<9wGN_hD%!AKO&;$9xTpopi7#>za!pKR_-I zMPd`#fCH$|U^DP3wII^tg?EUdj{j18Xd7-C*|N=^EUDPwzKpP*@L^%@A( z)<%{X@$Zbmk4HDHf7r++;o*c-aszXe^da7>Z9%W@Q|9PiHFxNe@I)*ry~Fp(`$JJB zU-H(FcLZV5Wqg@y_o-EoD*fF)HOzdvPhEU90qo0^WKHa*xHyc3=%)t;VGv+U9DsvU z70P$9ivV^37kph~Gv#TBLf1=5&YEYtDNjmEv!#7*8eHRNjh~yw?Iz8oK$oELQdw~~ ziMpm4Yzm%H*Uq7|Y%qG~IwKt1v8XSG34H*J)P=5Y^FJe#!*~voU=-r>A9xIhfqk8t zYnq=?WfwMn)CR1LOs0x~_Xs^HC}3dMv<3UsMK??h7ukYQu@sA11-2Ofw{>^`nub~d z61Km~R!(B2RkrQk-dC`!M{=<9ZRGPm<4k~GjR!k>2ZXI2YZVFSU#JB+9ukU5bJ`0X3R^&FXSADXKAZ2G9CJya%xq6iJDxY>@r<5IR`M>N|_r=YzIxR)tJ39vI{R zypa`2nFpn5Pt}1xO9t8NOCLnIlpTsez~8B$YjQ%mv`cG<%-Fwu2xx<4K=8(OkZm98 zR%~WkP$~U8a8~N3VY=|PZa~dpffIGv9I4D!En>oK)BbmOx2Y9P@c4&FxfQ?)-Fh2x zVk)-d2;fT_1R}}(^Sf!^Id4ARG<2znb)F*nm@gynnnikCm5fU_Idvs2W;5rk_o4JZ5=k_zkFXU z>Jubpg5bMZN}dwi_NO*~ONVx&3wR5cs7MW@0O1UH5wQZ-NQoj`D8SFL?Smn;-sAU75fC1Fn3ySFy7g1|Z`{YCtSV&K_b#5&4p2hC+bN%)a7~Ye$n)KnmFv za^-k0Q${+ae8f))?e*l8pZpjpv%G(0nV{vwQdaTaQF6*tF(%FFHYpVSm^9*_O+3w% zdEO7`P}&bs+(CXH{%rzeUwSV-L?lisN|IQGi8SN zARUlRawj>3B(?m{ghNIkk0n5@Se5XJbpjBYjsxiAyvLb0JCN*l&f7IB*kp181tl^% zjn`q6#*qbk_fyV?ECBmqTT3S-@RQofB#s0POqt)Q&__$vun_vVnhaT(wL9V9pWmUGsw$c^p|BI{?WhQ#U zU5{s*ha|ubX%~`o>460ck^s{M9*>$aZE8yFD?deD(l89P@+=OsVHi#2B@Wc27l{Kk z09|>fxoPVe??tEcwd5h`e7+`42+r5~qD8VNN|5{k9sY?npS7t0X6(<@h1sWYENA7? ztKf{^w%f@|06=oml8j z;6Zg!`pc4IwE3h>EyzBF-gasSt4p)h9qTSG)yAFEe&$QDap!a)=v!NN1^##T4Hb2~ zqI!jYpu<_m_kdM$)@?Bx*CsncN|pT=^20G z7ccrXATgi#ee&mO_t#;7=6&DpL;#qP;Wp zCgAZxyo>)F)j6?@t@5IA6BgKM*chgPvRo%(?2EnJXUSWZxV|vaIm0)w&tT8P2Q9_a zlZ3>eS7EP8VyT=Vml^Pp9hC*AWI#6Dp9x-KO2^6@`R&z9;d5f{np!=&lm>Ba@@R@L zmf+zz!y}opiZb589;%>tEVB2F_Y@ z-3q%vWXq)8giI*jidkYFgz+MbhovzIA1$3zjH*(wo^+BgN3?PF)#pHi|0F286AtLV}Ny2Y`ecZX6+Ww>aPGQ_`pq?}am{ zhYg~x9EC0nibxOyGWgD$5Lync5*OFM8Jgl1;4Ko#gB#^*TuGYZB2@)D2qjJ7;Y%#TkrdlI9JD}n3P6Q6KhU6<{p7`UiyF~hK|0(M9N}bOkaDJrf@^_I z8jHfVB;*p&e8|Hb7>OV@2?1c3@X9nwLx4c?FJTuASrx^xOC+weXzXJbp2h73yUI-6 zFkLX`yy%r$5BegJ{dLHNg)MWVQ<;Taz$7p$Q!tB0=%6qpJ$scP1v!dp7HGs$C0;p1 z8iJ^lj~PRw5W)n1kf;ENo3FVjfkJ>Q`@xn>|YqyyXG`!DGlt z@ECI53&6PA*#eFHTol2>=}9PJ)yIX3bMerYvnGrN)ZcLO+%T>=?8X!n9N7*8;6uX+pTY`yJurt@9EA*J9RfP3wS3$)0u)>rS!u>Sr zz_2+-c-<(I93aaFK`hgvcBaU4xFnIWDZv7Oa6K+_3_gJP^8^$P4XB0i!q^Bn4?$RF z6+{VgV4)M4Eej81@xdo~WY~jD=)cVr`-r(faX~JCP>RLKq7me<(hB@-mbOSp5@`9k z7bN}!T~~- z;8_Bx`b}BEqYUO4#{n%Cd}S|SfFG@7c_3jwRGRJQ{XZbIRAO`tFbA(L#RA#4&kn># z%Q^dkeSow3b2V6TdM;GfdHCOBaiM%Ug^Zn#xD3u3sAvj0 ziGr7)I4M7thZT?Lp8Md!9ZPa^P3XID(0J@Ktb&TauK5)N48E={SGNAL8>Ah~F<)RZ z=GA>pPj)u`t0rSiuKs&wu0W2sEu)WN^$YY@ZL*pn)w7e*tw?MFwW8Teu3BW-c2ope zvQ*6ZJMtTTU&VA z98=V4*%aem4Kp}+uMjRe9oYi6NwWz8`81Elp%4|HS&>~rz>TCSsKA4*C;SZ>#}F7* ziox-@G4L17<0QoHnXni{OzKQQI5L4jxiwqzneWf~lk@pO;(i<}-gX4h*tm3`J+`I;IOq=(A( z+1J#i19{-o3tSHl)xN;3uC&fLIIXs9q}}kk8ZmT-Ebt1VmboJr_^r3=n{5tu<`cjh zxUu#2eAE2=bv3o%+{A!QYUCYL{Dz{=?~FIp%v^bKni(-2M;qLEDEPbz9WYNQdF*sJM-NYm1Wx#E>rThsT-ycMS3~|PVR)zXgL0af=Qgy z3!2InXHN(%fzwlfA?tm-6@>_31mrG!#R=b76=UtquM#}GY`*|wSxA6qadyLk4R9L~ zG#NetFn3CVw1Pp{UN)zc=>jd81gm0flgxA@LWviv4 zhMO=TAcBes34$Z;2RxE?%4iG_T_Q2eZq__EoLJ5++Tf;_ISV#SbT%L^W1gGYDK5>% zt%ubJ)O#iX47lmZl*k2`D*z;@QV@10c~=S3rd{HI)1Bd50{3i++}Uvz&#_OZz(sak z1y|fB3(Rpwyjw9TS0r#sftiTFaoo=yXrnZFJ`~F$&#@MC9fwK0n1sigVdhk_U1_sj z7SHx8X6qA~ALPQq3zJBfT;pu6(eYe=V6MVME`Sv_;v`orQQ2^ttt7}+ti5etL&jl= zjG#^JJ zC_q9m!0|K<3|ST2=LY_xo?)R5Tz5aCg9%+o)h-P7#uo1oh6OBxOVA zlhy%d^AtTk=M!i%I`Z^Z_(8Yjz!d#^Rj|IUw!XHawt880)rL(~E6eMvYHOCQs;o3m zOw*U7{$53#0`uB5eQ|6YD#~*lXENMexbs;t+>Q0+b@f#>w=An(v9Y>#edW5U8hoxR zza4+;{WLMSsYtsGZW`RL`tEJ3tLiJOt2WkGu3TNc(Q%sn^gn_dLb|=EJ_Ro7xQ^+;|3a!*t!GCYTL3>t1NpEZALMf%r#T`pwW2G)y|%e+tehGy7V7OV+fs zh?5TQ?6j7jT&wd{_Gox6!EX$H=09fYV*VMBGP$;@#tfXLFIR7v8)oUTI+9_&HcM|E zQkxNRW+CmPex9*#v+-kt$KiJ=e&fw0v-Nbe>)pBf3Ul9$x|bfFX?`(V_aAZ@qA$k} z)0nfaYU4(L&|OtkS-sL-zPu^Z!uolWIY$r5Z1cTum`mwB<$M3#l+V$HmlVLX1!3|$ zg4w@KHEU~XZ>w={Ew8I8uc>#JBkW2)%^OIQZSKBKUu{m!!KP$+mdTx~Z|vO*`Z&x> zIA!3+K3rk$nyUw9jYd>oc&CTWlXLY2`AgxKA%M$dDB{hzdc}}O;9m&;)5ytx2Hx0k( z_+@1+tS_%vJ24A&Gks9aEPT(#Z;q{IE@;X<;bU`)q@I4>D1?I;K^}x(6 zNXnLc)x5t@SM+IyH!()dVHTCoc+l}mOaSwYMOcC23_2XsceDO zh$T8A*S^L2cR3aK4q&y?G%wM;&A#h(pGnE##KF0@)l{-K$>FOQersjjs_NR?lEZH? zXRp^ai>Fl9)UKOTTR*R^a${vp{p8h^6>BS3&fZjAU$wq^^R&A13NqI&Wv+(bPdTlw zcHIIp;N-Ha*v5c2Ma8#`mND7>l%MHxTt?W ze=a~i;^}o&HT9Q_xolo}o$%aE_@3w@{NaA)iJNtCPLO^*T9{907MTxk))gpj&X;uI zr1bu_xYLYAwq$rVz`vmK7L2mH9@7TU+pXH@)@-T<7Vbp8K9-!8-K={={2o`XtF7CN zzBZe*WqMR*QDMXxfKr;xRm=3ndRd`ayG&oDZ!a{@EYlaLR`ZKxx^UPrr0R)O*I8B% z%Bv*tEvu+qS(yy;yG`zLy>R5hqKGtgO4Y4ZD=XauM*u>k&?x4{(}b*j{A5 zzFZe)?m`^d{`brem+OH;4#7)y{XMuEu3J-E@2;xdRI?I82=w5;4)f`9JxXT}Fr&(K ze|_ZuGqYS@l(l3)M8>dwfQgms3-pcw=81CMNADkCTHuczM0_7)UtC#NwW_K@Rv@nQ zXQF!oaUf=Aa%~OhZX*C2v~^?crn-twoxKO?Y!Aye--!Mt1L9u91v!($w_&PZv_e-6 zVIU_4{_evMw7RJrqn4cb%jUfm`r;AEL0>`AAbC#M9AO$=35o|Vj(3qj+F3K^JWEj`}-(`2>q|-(+YjX$iZ&J z=?CxhN^q{K3JXH`XCRy$u&lhkVznQ-+%>nXgvo*Q{^oC1>J_@lHGf>GFPNPYwHPrP z>5K5q1}sE1^YELE-?jKn$8QRL44!(DzA{*zI4>eR58<0?s_KKl!w4g}SJrG=*N$&?dR_T?=M=s}_}*B#VN+#IMdcaz z*@U1>%O%mQVhfqM>&bCZYW1d>l`aBVs9#a_Cb0Q&2;=;}83oZr0bGYU^AOI(Va4il zvTe}u^5x4B#`VP7VpF>c9ohmv5q~XQ!in^L3sd5I7s9v(3F21N)>c=R*GODzm$=%R zO1E~E>l{Tm>m2Sn&Ix>z%!2%ZyWFjn3gFL^N}WL#qtGbKtYuu)^)_p6(S3)xh`Am` zoC1Kzr#6ir=QP44EcyFe{Jw$T z9a4%gE?I8GuLQsE;>Sw)dlJ8=@Y`!%sM2FIk0B|Sw+GC}ReEv`Q}Q1Nc!IfVjV{zP zE;Ki-(R1=2xG>^G5j+b&LgRr8&5zdTTL-kgj@h=S8o1`=l^Ge@z>m{bIiDPx<@R?NY0dsU*P{KzS*+C{|UYchQR-C_$EmPeuZ9+P4Z{K zpIlzJi$B}?SHug*>5?F~i+?8kM2w)qO8Ao-umS$TN%6PBFAZ+Futwjd^WOXoK$}{+ z>-Eo40L2JMg3;=3w;w_NcWk`*+9vRV1D8Z(xqZO)`q!JVM?PRq zZqj4n6y2&P!&!c-epN3RYlhvXr^VKc<(&@9PH@R^)`LFyZ(`E-i-`UArK9=@?)tXN&SF*<3|B=hlYdQz-#oSg*6 z!DS<+!6ge?0G9_+1DCRHWj7mAwf6OZa^sKbQaR%o?6G9FBXJ zdHQx8#r3(vx9k5m5HsXE*DxE*Nhz@6dBb7L2#~ z7GbF{0%@@HuUu7Ly|EJCLEJ+aXR=)6x$h1=Qni^E@6eO8I*^s!))S1nQ}@XlH^I(P z@JH{&T5J*gTx+eFV6MAUkIa7ne$Hu)e)#R<%%e!x{}B9KTD%FD@O}#}M-%bCxl>OZ z66DLiEF!D;F>uM*C&J|dM?h(I-hs(z_7U^3gqdYbgI>U_>r{C&xsbSMb|4#@zK~y&-3U STe50&`DNvmV^>^0?EeFA?O1VxPn3@U0+s;HPrJ0k;119ARmTx5v&MLs-re z>%O7!c_U`eDXke@UQ=COF}J#Wl;LB=LS6ClnyVJis+m(-KCEWMsOr+1in8j_<(*2* zU2u8Ltl8z&vuBr8%pFlSyL|Yl@(Nv|NW=JX^`$?mnN>D#M9sXi(&6Q^=T*-w8&#u= z^q%Eh?azu=%&DjuIci>cS z)xGnOoKt=2rL#tktf(%VS2nk%dd{2?qlcC05*~=lYko2dsLiXX9#Kg<�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%0DIaLXQ6<|RlA1}X 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 diff --git a/scripts/health/pkg-web/index_bg.wasm.d.ts b/scripts/health/pkg-web/index_bg.wasm.d.ts index 1e66abd01..b8369fbcd 100644 --- a/scripts/health/pkg-web/index_bg.wasm.d.ts +++ b/scripts/health/pkg-web/index_bg.wasm.d.ts @@ -2,12 +2,13 @@ /* eslint-disable */ export const memory: WebAssembly.Memory export function compute_health_js(a: number): number +export function max_withdraw_estimate_js(a: number, b: number): number export function allocate(a: number): number export function deallocate(a: number): void export function requires_stargate(): void export function requires_iterator(): void export function interface_version_8(): void -export function __wbindgen_malloc(a: number): number -export function __wbindgen_realloc(a: number, b: number, c: number): number -export function __wbindgen_free(a: number, b: number): void +export function __wbindgen_malloc(a: number, b: number): number +export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number +export function __wbindgen_free(a: number, b: number, c: number): void export function __wbindgen_exn_store(a: number): void diff --git a/scripts/package.json b/scripts/package.json index 2699be9d0..6f35456b8 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -20,8 +20,8 @@ "format-check": "prettier --check ." }, "dependencies": { - "@cosmjs/cosmwasm-stargate": "^0.30.1", - "@cosmjs/stargate": "^0.30.1", + "@cosmjs/cosmwasm-stargate": "^0.31.0", + "@cosmjs/stargate": "^0.31.0", "@cosmwasm/ts-codegen": "^0.30.1", "chalk": "4.1.2", "copyfiles": "^2.4.1", @@ -29,15 +29,15 @@ "lodash": "^4.17.21", "long": "^5.2.3", "prepend-file": "^2.0.1", - "simple-git": "^3.19.0", + "simple-git": "^3.19.1", "wasm-pack": "^0.12.0" }, "devDependencies": { "@babel/preset-env": "^7.22.5", "@babel/preset-typescript": "^7.22.5", "@types/jest": "^29.5.2", - "@typescript-eslint/eslint-plugin": "^5.59.9", - "@typescript-eslint/parser": "^5.59.9", + "@typescript-eslint/eslint-plugin": "^5.60.1", + "@typescript-eslint/parser": "^5.60.1", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "jest": "^29.5.0", diff --git a/scripts/yarn.lock b/scripts/yarn.lock index c1daf80d8..9930d0db1 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -4,7 +4,7 @@ "@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0": version "2.2.1" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz" integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== dependencies: "@jridgewell/gen-mapping" "^0.3.0" @@ -12,19 +12,19 @@ "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz" integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== dependencies: "@babel/highlight" "^7.22.5" "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.5.tgz#b1f6c86a02d85d2dd3368a2b67c09add8cd0c255" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz" integrity sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA== "@babel/core@7.18.10": version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.10.tgz#39ad504991d77f1f3da91be0b8b949a5bc466fb8" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz" integrity sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw== dependencies: "@ampproject/remapping" "^2.1.0" @@ -45,7 +45,7 @@ "@babel/core@^7.11.6", "@babel/core@^7.12.3": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.5.tgz#d67d9747ecf26ee7ecd3ebae1ee22225fe902a89" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz" integrity sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg== dependencies: "@ampproject/remapping" "^2.2.0" @@ -66,7 +66,7 @@ "@babel/generator@7.18.12": version "7.18.12" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.12.tgz#fa58daa303757bd6f5e4bbca91b342040463d9f4" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz" integrity sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg== dependencies: "@babel/types" "^7.18.10" @@ -75,7 +75,7 @@ "@babel/generator@^7.18.10", "@babel/generator@^7.22.5", "@babel/generator@^7.7.2": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.5.tgz#1e7bf768688acfb05cf30b2369ef855e82d984f7" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz" integrity sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA== dependencies: "@babel/types" "^7.22.5" @@ -85,21 +85,21 @@ "@babel/helper-annotate-as-pure@^7.18.6", "@babel/helper-annotate-as-pure@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz" integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== dependencies: "@babel/types" "^7.22.5" "@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz#a3f4758efdd0190d8927fcffd261755937c71878" + resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz" integrity sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw== dependencies: "@babel/types" "^7.22.5" "@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz#fc7319fc54c5e2fa14b2909cf3c5fd3046813e02" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz" integrity sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw== dependencies: "@babel/compat-data" "^7.22.5" @@ -110,7 +110,7 @@ "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz#2192a1970ece4685fbff85b48da2c32fcb130b7c" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz" integrity sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" @@ -125,7 +125,7 @@ "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz#bb2bf0debfe39b831986a4efbf4066586819c6e4" + resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz" integrity sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" @@ -134,7 +134,7 @@ "@babel/helper-define-polyfill-provider@^0.3.2", "@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" + resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz" integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== dependencies: "@babel/helper-compilation-targets" "^7.17.7" @@ -146,7 +146,7 @@ "@babel/helper-define-polyfill-provider@^0.4.0": version "0.4.0" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz#487053f103110f25b9755c5980e031e93ced24d8" + resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz" integrity sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg== dependencies: "@babel/helper-compilation-targets" "^7.17.7" @@ -158,12 +158,12 @@ "@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz" integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== "@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz" integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== dependencies: "@babel/template" "^7.22.5" @@ -171,28 +171,28 @@ "@babel/helper-hoist-variables@^7.18.6", "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz" integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== dependencies: "@babel/types" "^7.22.5" "@babel/helper-member-expression-to-functions@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz#0a7c56117cad3372fbf8d2fb4bf8f8d64a1e76b2" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz" integrity sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ== dependencies: "@babel/types" "^7.22.5" "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz" integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== dependencies: "@babel/types" "^7.22.5" "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz#0f65daa0716961b6e96b164034e737f60a80d2ef" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz" integrity sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw== dependencies: "@babel/helper-environment-visitor" "^7.22.5" @@ -206,19 +206,19 @@ "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz" integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== dependencies: "@babel/types" "^7.22.5" "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== "@babel/helper-remap-async-to-generator@^7.18.9", "@babel/helper-remap-async-to-generator@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz#14a38141a7bf2165ad38da61d61cf27b43015da2" + resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz" integrity sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" @@ -228,7 +228,7 @@ "@babel/helper-replace-supers@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz#71bc5fb348856dea9fdc4eafd7e2e49f585145dc" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz" integrity sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg== dependencies: "@babel/helper-environment-visitor" "^7.22.5" @@ -240,43 +240,43 @@ "@babel/helper-simple-access@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz" integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== dependencies: "@babel/types" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers@^7.20.0", "@babel/helper-skip-transparent-expression-wrappers@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" + resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz" integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== dependencies: "@babel/types" "^7.22.5" "@babel/helper-split-export-declaration@^7.18.6", "@babel/helper-split-export-declaration@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz#88cf11050edb95ed08d596f7a044462189127a08" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz" integrity sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ== dependencies: "@babel/types" "^7.22.5" "@babel/helper-string-parser@^7.18.10", "@babel/helper-string-parser@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz" integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== "@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz" integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== "@babel/helper-wrap-function@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz#44d205af19ed8d872b4eefb0d2fa65f45eb34f06" + resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz" integrity sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw== dependencies: "@babel/helper-function-name" "^7.22.5" @@ -286,7 +286,7 @@ "@babel/helpers@^7.18.9", "@babel/helpers@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.5.tgz#74bb4373eb390d1ceed74a15ef97767e63120820" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz" integrity sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q== dependencies: "@babel/template" "^7.22.5" @@ -295,7 +295,7 @@ "@babel/highlight@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz" integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== dependencies: "@babel/helper-validator-identifier" "^7.22.5" @@ -304,24 +304,24 @@ "@babel/parser@7.18.11": version "7.18.11" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz" integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.5.tgz#721fd042f3ce1896238cf1b341c77eb7dee7dbea" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz" integrity sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz" integrity sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz#fef09f9499b1f1c930da8a0c419db42167d792ca" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz" integrity sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -330,7 +330,7 @@ "@babel/plugin-proposal-async-generator-functions@^7.18.10": version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz" integrity sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA== dependencies: "@babel/helper-environment-visitor" "^7.18.9" @@ -340,7 +340,7 @@ "@babel/plugin-proposal-class-properties@7.18.6", "@babel/plugin-proposal-class-properties@^7.18.6": version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz" integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== dependencies: "@babel/helper-create-class-features-plugin" "^7.18.6" @@ -348,7 +348,7 @@ "@babel/plugin-proposal-class-static-block@^7.18.6": version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz#77bdd66fb7b605f3a61302d224bdfacf5547977d" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz" integrity sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw== dependencies: "@babel/helper-create-class-features-plugin" "^7.21.0" @@ -357,7 +357,7 @@ "@babel/plugin-proposal-dynamic-import@^7.18.6": version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz" integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== dependencies: "@babel/helper-plugin-utils" "^7.18.6" @@ -365,7 +365,7 @@ "@babel/plugin-proposal-export-default-from@7.18.10": version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz#091f4794dbce4027c03cf4ebc64d3fb96b75c206" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz" integrity sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow== dependencies: "@babel/helper-plugin-utils" "^7.18.9" @@ -373,7 +373,7 @@ "@babel/plugin-proposal-export-namespace-from@^7.18.9": version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz" integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== dependencies: "@babel/helper-plugin-utils" "^7.18.9" @@ -381,7 +381,7 @@ "@babel/plugin-proposal-json-strings@^7.18.6": version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz" integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== dependencies: "@babel/helper-plugin-utils" "^7.18.6" @@ -389,7 +389,7 @@ "@babel/plugin-proposal-logical-assignment-operators@^7.18.9": version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz#dfbcaa8f7b4d37b51e8bfb46d94a5aea2bb89d83" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz" integrity sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug== dependencies: "@babel/helper-plugin-utils" "^7.20.2" @@ -397,7 +397,7 @@ "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz" integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== dependencies: "@babel/helper-plugin-utils" "^7.18.6" @@ -405,7 +405,7 @@ "@babel/plugin-proposal-numeric-separator@^7.18.6": version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz" integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== dependencies: "@babel/helper-plugin-utils" "^7.18.6" @@ -413,7 +413,7 @@ "@babel/plugin-proposal-object-rest-spread@7.18.9": version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz#f9434f6beb2c8cae9dfcf97d2a5941bbbf9ad4e7" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz" integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q== dependencies: "@babel/compat-data" "^7.18.8" @@ -424,7 +424,7 @@ "@babel/plugin-proposal-object-rest-spread@^7.18.9": version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz" integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== dependencies: "@babel/compat-data" "^7.20.5" @@ -435,7 +435,7 @@ "@babel/plugin-proposal-optional-catch-binding@^7.18.6": version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz" integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== dependencies: "@babel/helper-plugin-utils" "^7.18.6" @@ -443,7 +443,7 @@ "@babel/plugin-proposal-optional-chaining@^7.18.9": version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz" integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== dependencies: "@babel/helper-plugin-utils" "^7.20.2" @@ -452,7 +452,7 @@ "@babel/plugin-proposal-private-methods@^7.18.6": version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz" integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== dependencies: "@babel/helper-create-class-features-plugin" "^7.18.6" @@ -460,12 +460,12 @@ "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz" integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== "@babel/plugin-proposal-private-property-in-object@^7.18.6": version "7.21.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz#69d597086b6760c4126525cfa154f34631ff272c" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz" integrity sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" @@ -475,7 +475,7 @@ "@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz" integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.18.6" @@ -483,154 +483,154 @@ "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-bigint@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" "@babel/plugin-syntax-class-static-block@^7.14.5": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz" integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-export-default-from@^7.18.6": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.22.5.tgz#ac3a24b362a04415a017ab96b9b4483d0e2a6e44" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.22.5.tgz" integrity sha512-ODAqWWXB/yReh/jVQDag/3/tl6lgBueQkk/TcfW/59Oykm4c8a55XloX0CTk2k2VJiFWMgHby9xNX29IbCv9dQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-namespace-from@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz" integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== dependencies: "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-import-assertions@^7.18.6", "@babel/plugin-syntax-import-assertions@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz#07d252e2aa0bc6125567f742cd58619cb14dce98" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz" integrity sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-import-attributes@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz#ab840248d834410b829f569f5262b9e517555ecb" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz" integrity sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-jsx@^7.22.5", "@babel/plugin-syntax-jsx@^7.7.2": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz" integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-private-property-in-object@^7.14.5": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.22.5", "@babel/plugin-syntax-typescript@^7.7.2": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz" integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz" integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.18.6" @@ -638,14 +638,14 @@ "@babel/plugin-transform-arrow-functions@^7.18.6", "@babel/plugin-transform-arrow-functions@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958" + resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz" integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-async-generator-functions@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz#7336356d23380eda9a56314974f053a020dab0c3" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz" integrity sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg== dependencies: "@babel/helper-environment-visitor" "^7.22.5" @@ -655,7 +655,7 @@ "@babel/plugin-transform-async-to-generator@^7.18.6", "@babel/plugin-transform-async-to-generator@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz" integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ== dependencies: "@babel/helper-module-imports" "^7.22.5" @@ -664,21 +664,21 @@ "@babel/plugin-transform-block-scoped-functions@^7.18.6", "@babel/plugin-transform-block-scoped-functions@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz" integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-block-scoping@^7.18.9", "@babel/plugin-transform-block-scoping@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz#8bfc793b3a4b2742c0983fadc1480d843ecea31b" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz" integrity sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-class-properties@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz#97a56e31ad8c9dc06a0b3710ce7803d5a48cca77" + resolved "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz" integrity sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ== dependencies: "@babel/helper-create-class-features-plugin" "^7.22.5" @@ -686,7 +686,7 @@ "@babel/plugin-transform-class-static-block@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz#3e40c46f048403472d6f4183116d5e46b1bff5ba" + resolved "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz" integrity sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA== dependencies: "@babel/helper-create-class-features-plugin" "^7.22.5" @@ -695,7 +695,7 @@ "@babel/plugin-transform-classes@^7.18.9", "@babel/plugin-transform-classes@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz#635d4e98da741fad814984639f4c0149eb0135e1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz" integrity sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" @@ -710,7 +710,7 @@ "@babel/plugin-transform-computed-properties@^7.18.9", "@babel/plugin-transform-computed-properties@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869" + resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz" integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -718,14 +718,14 @@ "@babel/plugin-transform-destructuring@^7.18.9", "@babel/plugin-transform-destructuring@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz#d3aca7438f6c26c78cdd0b0ba920a336001b27cc" + resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz" integrity sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.22.5", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz" integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.5" @@ -733,14 +733,14 @@ "@babel/plugin-transform-duplicate-keys@^7.18.9", "@babel/plugin-transform-duplicate-keys@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285" + resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz" integrity sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-dynamic-import@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz#d6908a8916a810468c4edff73b5b75bda6ad393e" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz" integrity sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -748,7 +748,7 @@ "@babel/plugin-transform-exponentiation-operator@^7.18.6", "@babel/plugin-transform-exponentiation-operator@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a" + resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz" integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g== dependencies: "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" @@ -756,7 +756,7 @@ "@babel/plugin-transform-export-namespace-from@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz#57c41cb1d0613d22f548fddd8b288eedb9973a5b" + resolved "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz" integrity sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -764,14 +764,14 @@ "@babel/plugin-transform-for-of@^7.18.8", "@babel/plugin-transform-for-of@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz#ab1b8a200a8f990137aff9a084f8de4099ab173f" + resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz" integrity sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-function-name@^7.18.9", "@babel/plugin-transform-function-name@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143" + resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz" integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg== dependencies: "@babel/helper-compilation-targets" "^7.22.5" @@ -780,7 +780,7 @@ "@babel/plugin-transform-json-strings@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz#14b64352fdf7e1f737eed68de1a1468bd2a77ec0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz" integrity sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -788,14 +788,14 @@ "@babel/plugin-transform-literals@^7.18.9", "@babel/plugin-transform-literals@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920" + resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz" integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-logical-assignment-operators@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz#66ae5f068fd5a9a5dc570df16f56c2a8462a9d6c" + resolved "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz" integrity sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -803,14 +803,14 @@ "@babel/plugin-transform-member-expression-literals@^7.18.6", "@babel/plugin-transform-member-expression-literals@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def" + resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz" integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-modules-amd@^7.18.6", "@babel/plugin-transform-modules-amd@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz#4e045f55dcf98afd00f85691a68fc0780704f526" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz" integrity sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ== dependencies: "@babel/helper-module-transforms" "^7.22.5" @@ -818,7 +818,7 @@ "@babel/plugin-transform-modules-commonjs@^7.18.6", "@babel/plugin-transform-modules-commonjs@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz#7d9875908d19b8c0536085af7b053fd5bd651bfa" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz" integrity sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA== dependencies: "@babel/helper-module-transforms" "^7.22.5" @@ -827,7 +827,7 @@ "@babel/plugin-transform-modules-systemjs@^7.18.9", "@babel/plugin-transform-modules-systemjs@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz#18c31410b5e579a0092638f95c896c2a98a5d496" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz" integrity sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ== dependencies: "@babel/helper-hoist-variables" "^7.22.5" @@ -837,7 +837,7 @@ "@babel/plugin-transform-modules-umd@^7.18.6", "@babel/plugin-transform-modules-umd@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz" integrity sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ== dependencies: "@babel/helper-module-transforms" "^7.22.5" @@ -845,7 +845,7 @@ "@babel/plugin-transform-named-capturing-groups-regex@^7.18.6", "@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" + resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz" integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.5" @@ -853,14 +853,14 @@ "@babel/plugin-transform-new-target@^7.18.6", "@babel/plugin-transform-new-target@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d" + resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz" integrity sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-nullish-coalescing-operator@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz#f8872c65776e0b552e0849d7596cddd416c3e381" + resolved "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz" integrity sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -868,7 +868,7 @@ "@babel/plugin-transform-numeric-separator@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz#57226a2ed9e512b9b446517ab6fa2d17abb83f58" + resolved "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz" integrity sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -876,7 +876,7 @@ "@babel/plugin-transform-object-rest-spread@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz#9686dc3447df4753b0b2a2fae7e8bc33cdc1f2e1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz" integrity sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ== dependencies: "@babel/compat-data" "^7.22.5" @@ -887,7 +887,7 @@ "@babel/plugin-transform-object-super@^7.18.6", "@babel/plugin-transform-object-super@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz" integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -895,7 +895,7 @@ "@babel/plugin-transform-optional-catch-binding@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz#842080be3076703be0eaf32ead6ac8174edee333" + resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz" integrity sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -903,7 +903,7 @@ "@babel/plugin-transform-optional-chaining@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz#1003762b9c14295501beb41be72426736bedd1e0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz" integrity sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -912,14 +912,14 @@ "@babel/plugin-transform-parameters@^7.18.8", "@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz#c3542dd3c39b42c8069936e48717a8d179d63a18" + resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz" integrity sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-private-methods@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz#21c8af791f76674420a147ae62e9935d790f8722" + resolved "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz" integrity sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA== dependencies: "@babel/helper-create-class-features-plugin" "^7.22.5" @@ -927,7 +927,7 @@ "@babel/plugin-transform-private-property-in-object@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz#07a77f28cbb251546a43d175a1dda4cf3ef83e32" + resolved "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz" integrity sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" @@ -937,14 +937,14 @@ "@babel/plugin-transform-property-literals@^7.18.6", "@babel/plugin-transform-property-literals@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766" + resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz" integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-regenerator@^7.18.6", "@babel/plugin-transform-regenerator@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz#cd8a68b228a5f75fa01420e8cc2fc400f0fc32aa" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz" integrity sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -952,14 +952,14 @@ "@babel/plugin-transform-reserved-words@^7.18.6", "@babel/plugin-transform-reserved-words@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb" + resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz" integrity sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-runtime@7.18.10": version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz#37d14d1fa810a368fd635d4d1476c0154144a96f" + resolved "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz" integrity sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ== dependencies: "@babel/helper-module-imports" "^7.18.6" @@ -971,14 +971,14 @@ "@babel/plugin-transform-shorthand-properties@^7.18.6", "@babel/plugin-transform-shorthand-properties@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624" + resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz" integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-spread@^7.18.9", "@babel/plugin-transform-spread@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b" + resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz" integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -986,28 +986,28 @@ "@babel/plugin-transform-sticky-regex@^7.18.6", "@babel/plugin-transform-sticky-regex@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa" + resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz" integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-template-literals@^7.18.9", "@babel/plugin-transform-template-literals@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff" + resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz" integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-typeof-symbol@^7.18.9", "@babel/plugin-transform-typeof-symbol@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz" integrity sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-typescript@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.5.tgz#5c0f7adfc1b5f38c4dbc8f79b1f0f8074134bd7d" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.5.tgz" integrity sha512-SMubA9S7Cb5sGSFFUlqxyClTA9zWJ8qGQrppNUm05LtFuN1ELRFNndkix4zUJrC9F+YivWwa1dHMSyo0e0N9dA== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" @@ -1017,14 +1017,14 @@ "@babel/plugin-transform-unicode-escapes@^7.18.10", "@babel/plugin-transform-unicode-escapes@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz#ce0c248522b1cb22c7c992d88301a5ead70e806c" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz" integrity sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-unicode-property-regex@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz#098898f74d5c1e86660dc112057b2d11227f1c81" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz" integrity sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.5" @@ -1032,7 +1032,7 @@ "@babel/plugin-transform-unicode-regex@^7.18.6", "@babel/plugin-transform-unicode-regex@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz" integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.5" @@ -1040,7 +1040,7 @@ "@babel/plugin-transform-unicode-sets-regex@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz#77788060e511b708ffc7d42fdfbc5b37c3004e91" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz" integrity sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.22.5" @@ -1048,7 +1048,7 @@ "@babel/preset-env@7.18.10": version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.10.tgz#83b8dfe70d7eea1aae5a10635ab0a5fe60dfc0f4" + resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz" integrity sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA== dependencies: "@babel/compat-data" "^7.18.8" @@ -1129,7 +1129,7 @@ "@babel/preset-env@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.5.tgz#3da66078b181f3d62512c51cf7014392c511504e" + resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.5.tgz" integrity sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A== dependencies: "@babel/compat-data" "^7.22.5" @@ -1215,7 +1215,7 @@ "@babel/preset-modules@^0.1.5": version "0.1.5" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" + resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz" integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -1226,7 +1226,7 @@ "@babel/preset-typescript@^7.18.6", "@babel/preset-typescript@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz#16367d8b01d640e9a507577ed4ee54e0101e51c8" + resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz" integrity sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -1237,19 +1237,19 @@ "@babel/regjsgen@^0.8.0": version "0.8.0" - resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" + resolved "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.11.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.8.4": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz" integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== dependencies: regenerator-runtime "^0.13.11" "@babel/template@^7.18.10", "@babel/template@^7.22.5", "@babel/template@^7.3.3": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz" integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== dependencies: "@babel/code-frame" "^7.22.5" @@ -1258,7 +1258,7 @@ "@babel/traverse@7.18.11": version "7.18.11" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.11.tgz#3d51f2afbd83ecf9912bcbb5c4d94e3d2ddaa16f" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz" integrity sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ== dependencies: "@babel/code-frame" "^7.18.6" @@ -1274,7 +1274,7 @@ "@babel/traverse@^7.18.10", "@babel/traverse@^7.22.5", "@babel/traverse@^7.7.2": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.5.tgz#44bd276690db6f4940fdb84e1cb4abd2f729ccd1" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz" integrity sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ== dependencies: "@babel/code-frame" "^7.22.5" @@ -1290,7 +1290,7 @@ "@babel/types@7.18.10": version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.10.tgz#4908e81b6b339ca7c6b7a555a5fc29446f26dde6" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz" integrity sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ== dependencies: "@babel/helper-string-parser" "^7.18.10" @@ -1299,7 +1299,7 @@ "@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz" integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== dependencies: "@babel/helper-string-parser" "^7.22.5" @@ -1308,153 +1308,153 @@ "@bcoe/v8-coverage@^0.2.3": version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== "@confio/ics23@^0.6.8": version "0.6.8" - resolved "https://registry.yarnpkg.com/@confio/ics23/-/ics23-0.6.8.tgz#2a6b4f1f2b7b20a35d9a0745bb5a446e72930b3d" + resolved "https://registry.npmjs.org/@confio/ics23/-/ics23-0.6.8.tgz" integrity sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w== dependencies: "@noble/hashes" "^1.0.0" protobufjs "^6.8.8" -"@cosmjs/amino@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.30.1.tgz#7c18c14627361ba6c88e3495700ceea1f76baace" - integrity sha512-yNHnzmvAlkETDYIpeCTdVqgvrdt1qgkOXwuRVi8s27UKI5hfqyE9fJ/fuunXE6ZZPnKkjIecDznmuUOMrMvw4w== - dependencies: - "@cosmjs/crypto" "^0.30.1" - "@cosmjs/encoding" "^0.30.1" - "@cosmjs/math" "^0.30.1" - "@cosmjs/utils" "^0.30.1" - -"@cosmjs/cosmwasm-stargate@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.30.1.tgz#6f9ca310f75433a3e30d683bc6aa24eadb345d79" - integrity sha512-W/6SLUCJAJGBN+sJLXouLZikVgmqDd9LCdlMzQaxczcCHTWeJAmRvOiZGSZaSy3shw/JN1qc6g6PKpvTVgj10A== - dependencies: - "@cosmjs/amino" "^0.30.1" - "@cosmjs/crypto" "^0.30.1" - "@cosmjs/encoding" "^0.30.1" - "@cosmjs/math" "^0.30.1" - "@cosmjs/proto-signing" "^0.30.1" - "@cosmjs/stargate" "^0.30.1" - "@cosmjs/tendermint-rpc" "^0.30.1" - "@cosmjs/utils" "^0.30.1" - cosmjs-types "^0.7.1" +"@cosmjs/amino@^0.31.0": + version "0.31.0" + resolved "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.31.0.tgz" + integrity sha512-xJ5CCEK7H79FTpOuEmlpSzVI+ZeYESTVvO3wHDgbnceIyAne3C68SvyaKqLUR4uJB0Z4q4+DZHbqW6itUiv4lA== + dependencies: + "@cosmjs/crypto" "^0.31.0" + "@cosmjs/encoding" "^0.31.0" + "@cosmjs/math" "^0.31.0" + "@cosmjs/utils" "^0.31.0" + +"@cosmjs/cosmwasm-stargate@^0.31.0": + version "0.31.0" + resolved "https://registry.npmjs.org/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.31.0.tgz" + integrity sha512-l6aX++3LhaAGZO46qIgrrNF40lYhOrdPfl35Z32ks6Wf3mwgbQEZwaxnoGzwUePY7/yaIiEFJ1JO6MlVPZVuag== + dependencies: + "@cosmjs/amino" "^0.31.0" + "@cosmjs/crypto" "^0.31.0" + "@cosmjs/encoding" "^0.31.0" + "@cosmjs/math" "^0.31.0" + "@cosmjs/proto-signing" "^0.31.0" + "@cosmjs/stargate" "^0.31.0" + "@cosmjs/tendermint-rpc" "^0.31.0" + "@cosmjs/utils" "^0.31.0" + cosmjs-types "^0.8.0" long "^4.0.0" pako "^2.0.2" -"@cosmjs/crypto@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.30.1.tgz#21e94d5ca8f8ded16eee1389d2639cb5c43c3eb5" - integrity sha512-rAljUlake3MSXs9xAm87mu34GfBLN0h/1uPPV6jEwClWjNkAMotzjC0ab9MARy5FFAvYHL3lWb57bhkbt2GtzQ== +"@cosmjs/crypto@^0.31.0": + version "0.31.0" + resolved "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.31.0.tgz" + integrity sha512-UaqCe6Tgh0pe1QlZ66E13t6FlIF86QrnBXXq+EN7Xe1Rouza3fJ1ojGlPleJZkBoq3tAyYVIOOqdZIxtVj/sIQ== dependencies: - "@cosmjs/encoding" "^0.30.1" - "@cosmjs/math" "^0.30.1" - "@cosmjs/utils" "^0.30.1" + "@cosmjs/encoding" "^0.31.0" + "@cosmjs/math" "^0.31.0" + "@cosmjs/utils" "^0.31.0" "@noble/hashes" "^1" bn.js "^5.2.0" elliptic "^6.5.4" - libsodium-wrappers "^0.7.6" + libsodium-wrappers-sumo "^0.7.11" -"@cosmjs/encoding@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.30.1.tgz#b5c4e0ef7ceb1f2753688eb96400ed70f35c6058" - integrity sha512-rXmrTbgqwihORwJ3xYhIgQFfMSrwLu1s43RIK9I8EBudPx3KmnmyAKzMOVsRDo9edLFNuZ9GIvysUCwQfq3WlQ== +"@cosmjs/encoding@^0.31.0": + version "0.31.0" + resolved "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.31.0.tgz" + integrity sha512-NYGQDRxT7MIRSlcbAezwxK0FqnaSPKCH7O32cmfpHNWorFxhy9lwmBoCvoe59Kd0HmArI4h+NGzLEfX3OLnA4Q== dependencies: base64-js "^1.3.0" bech32 "^1.1.4" readonly-date "^1.0.0" -"@cosmjs/json-rpc@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.30.1.tgz#16f21305fc167598c8a23a45549b85106b2372bc" - integrity sha512-pitfC/2YN9t+kXZCbNuyrZ6M8abnCC2n62m+JtU9vQUfaEtVsgy+1Fk4TRQ175+pIWSdBMFi2wT8FWVEE4RhxQ== +"@cosmjs/json-rpc@^0.31.0": + version "0.31.0" + resolved "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.31.0.tgz" + integrity sha512-Ix2Cil2qysiLNrX+E0w3vtwCrqxGVq8jklpLA7B2vtMrw7tru/rS65fdFSy8ep0wUNLL6Ud32VXa5K0YObDOMA== dependencies: - "@cosmjs/stream" "^0.30.1" + "@cosmjs/stream" "^0.31.0" xstream "^11.14.0" -"@cosmjs/math@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.30.1.tgz#8b816ef4de5d3afa66cb9fdfb5df2357a7845b8a" - integrity sha512-yaoeI23pin9ZiPHIisa6qqLngfnBR/25tSaWpkTm8Cy10MX70UF5oN4+/t1heLaM6SSmRrhk3psRkV4+7mH51Q== +"@cosmjs/math@^0.31.0": + version "0.31.0" + resolved "https://registry.npmjs.org/@cosmjs/math/-/math-0.31.0.tgz" + integrity sha512-Sb/8Ry/+gKJaYiV6X8q45kxXC9FoV98XCY1WXtu0JQwOi61VCG2VXsURQnVvZ/EhR/CuT/swOlNKrqEs3da0fw== dependencies: bn.js "^5.2.0" -"@cosmjs/proto-signing@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.30.1.tgz#f0dda372488df9cd2677150b89b3e9c72b3cb713" - integrity sha512-tXh8pPYXV4aiJVhTKHGyeZekjj+K9s2KKojMB93Gcob2DxUjfKapFYBMJSgfKPuWUPEmyr8Q9km2hplI38ILgQ== - dependencies: - "@cosmjs/amino" "^0.30.1" - "@cosmjs/crypto" "^0.30.1" - "@cosmjs/encoding" "^0.30.1" - "@cosmjs/math" "^0.30.1" - "@cosmjs/utils" "^0.30.1" - cosmjs-types "^0.7.1" +"@cosmjs/proto-signing@^0.31.0": + version "0.31.0" + resolved "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.31.0.tgz" + integrity sha512-JNlyOJRkn8EKB9mCthkjr6lVX6eyVQ09PFdmB4/DR874E62dFTvQ+YvyKMAgN7K7Dcjj26dVlAD3f6Xs7YOGDg== + dependencies: + "@cosmjs/amino" "^0.31.0" + "@cosmjs/crypto" "^0.31.0" + "@cosmjs/encoding" "^0.31.0" + "@cosmjs/math" "^0.31.0" + "@cosmjs/utils" "^0.31.0" + cosmjs-types "^0.8.0" long "^4.0.0" -"@cosmjs/socket@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.30.1.tgz#00b22f4b5e2ab01f4d82ccdb7b2e59536bfe5ce0" - integrity sha512-r6MpDL+9N+qOS/D5VaxnPaMJ3flwQ36G+vPvYJsXArj93BjgyFB7BwWwXCQDzZ+23cfChPUfhbINOenr8N2Kow== +"@cosmjs/socket@^0.31.0": + version "0.31.0" + resolved "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.31.0.tgz" + integrity sha512-WDh9gTyiP3OCXvSAJJn33+Ef3XqMWag+bpR1TdMBxTmlTxuvU+kPy4cf6P2OF+jkkUBEA5Se2EAju0eFbJMT+w== dependencies: - "@cosmjs/stream" "^0.30.1" + "@cosmjs/stream" "^0.31.0" isomorphic-ws "^4.0.1" ws "^7" xstream "^11.14.0" -"@cosmjs/stargate@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.30.1.tgz#e1b22e1226cffc6e93914a410755f1f61057ba04" - integrity sha512-RdbYKZCGOH8gWebO7r6WvNnQMxHrNXInY/gPHPzMjbQF6UatA6fNM2G2tdgS5j5u7FTqlCI10stNXrknaNdzog== +"@cosmjs/stargate@^0.31.0": + version "0.31.0" + resolved "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.31.0.tgz" + integrity sha512-GYhk9lzZPj/QmYHC0VV/4AMoRzVcOP+EnB1YZCoWlBdLuVmpBYKRagJqWIrIwdk1E0gF2ZoESd2TYfdh1fqIpg== dependencies: "@confio/ics23" "^0.6.8" - "@cosmjs/amino" "^0.30.1" - "@cosmjs/encoding" "^0.30.1" - "@cosmjs/math" "^0.30.1" - "@cosmjs/proto-signing" "^0.30.1" - "@cosmjs/stream" "^0.30.1" - "@cosmjs/tendermint-rpc" "^0.30.1" - "@cosmjs/utils" "^0.30.1" - cosmjs-types "^0.7.1" + "@cosmjs/amino" "^0.31.0" + "@cosmjs/encoding" "^0.31.0" + "@cosmjs/math" "^0.31.0" + "@cosmjs/proto-signing" "^0.31.0" + "@cosmjs/stream" "^0.31.0" + "@cosmjs/tendermint-rpc" "^0.31.0" + "@cosmjs/utils" "^0.31.0" + cosmjs-types "^0.8.0" long "^4.0.0" protobufjs "~6.11.3" xstream "^11.14.0" -"@cosmjs/stream@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.30.1.tgz#ba038a2aaf41343696b1e6e759d8e03a9516ec1a" - integrity sha512-Fg0pWz1zXQdoxQZpdHRMGvUH5RqS6tPv+j9Eh7Q953UjMlrwZVo0YFLC8OTf/HKVf10E4i0u6aM8D69Q6cNkgQ== +"@cosmjs/stream@^0.31.0": + version "0.31.0" + resolved "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.31.0.tgz" + integrity sha512-Y+aSHwhHkLGIaQOdqRob+yga2zr9ifl9gZDKD+B7+R5pdWN5f2TTDhYWxA6YZcZ6xRmfr7u8a7tDh7iYLC/zKA== dependencies: xstream "^11.14.0" -"@cosmjs/tendermint-rpc@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.30.1.tgz#c16378892ba1ac63f72803fdf7567eab9d4f0aa0" - integrity sha512-Z3nCwhXSbPZJ++v85zHObeUggrEHVfm1u18ZRwXxFE9ZMl5mXTybnwYhczuYOl7KRskgwlB+rID0WYACxj4wdQ== - dependencies: - "@cosmjs/crypto" "^0.30.1" - "@cosmjs/encoding" "^0.30.1" - "@cosmjs/json-rpc" "^0.30.1" - "@cosmjs/math" "^0.30.1" - "@cosmjs/socket" "^0.30.1" - "@cosmjs/stream" "^0.30.1" - "@cosmjs/utils" "^0.30.1" +"@cosmjs/tendermint-rpc@^0.31.0": + version "0.31.0" + resolved "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.31.0.tgz" + integrity sha512-yo9xbeuI6UoEKIhFZ9g0dvUKLqnBzwdpEc/uldQygQc51j38gQVwFko+6sjmhieJqRYYvrYumcbJMiV6GFM9aA== + dependencies: + "@cosmjs/crypto" "^0.31.0" + "@cosmjs/encoding" "^0.31.0" + "@cosmjs/json-rpc" "^0.31.0" + "@cosmjs/math" "^0.31.0" + "@cosmjs/socket" "^0.31.0" + "@cosmjs/stream" "^0.31.0" + "@cosmjs/utils" "^0.31.0" axios "^0.21.2" readonly-date "^1.0.0" xstream "^11.14.0" -"@cosmjs/utils@^0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.30.1.tgz#6d92582341be3c2ec8d82090253cfa4b7f959edb" - integrity sha512-KvvX58MGMWh7xA+N+deCfunkA/ZNDvFLw4YbOmX3f/XBIkqrVY7qlotfy2aNb1kgp6h4B6Yc8YawJPDTfvWX7g== +"@cosmjs/utils@^0.31.0": + version "0.31.0" + resolved "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.31.0.tgz" + integrity sha512-nNcycZWUYLNJlrIXgpcgVRqdl6BXjF4YlXdxobQWpW9Tikk61bEGeAFhDYtC0PwHlokCNw0KxWiHGJL4nL7Q5A== "@cosmwasm/ts-codegen@^0.30.1": version "0.30.1" - resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.30.1.tgz#8b8a635273065261c608a76f8a50cb5045e23980" + resolved "https://registry.npmjs.org/@cosmwasm/ts-codegen/-/ts-codegen-0.30.1.tgz" integrity sha512-6ATbmtuK2MwG9fJxIi0M+Rwd0SQhsko2nA8qVXC9MRHpZJKNaXYYcof1fel/L5HJCjotmQVsoxons3rGg6dRnw== dependencies: "@babel/core" "7.18.10" @@ -1487,19 +1487,19 @@ "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.4.0": version "4.5.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" + resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz" integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== "@eslint/eslintrc@^2.0.3": version "2.0.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz" integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ== dependencies: ajv "^6.12.4" @@ -1514,12 +1514,12 @@ "@eslint/js@8.43.0": version "8.43.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.43.0.tgz#559ca3d9ddbd6bf907ad524320a0d14b85586af0" + resolved "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz" integrity sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg== "@humanwhocodes/config-array@^0.11.10": version "0.11.10" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz" integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== dependencies: "@humanwhocodes/object-schema" "^1.2.1" @@ -1528,17 +1528,17 @@ "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== "@humanwhocodes/object-schema@^1.2.1": version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== dependencies: camelcase "^5.3.1" @@ -1549,12 +1549,12 @@ "@istanbuljs/schema@^0.1.2": version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== "@jest/console@^29.5.0": version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.5.0.tgz#593a6c5c0d3f75689835f1b3b4688c4f8544cb57" + resolved "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz" integrity sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ== dependencies: "@jest/types" "^29.5.0" @@ -1566,7 +1566,7 @@ "@jest/core@^29.5.0": version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.5.0.tgz#76674b96904484e8214614d17261cc491e5f1f03" + resolved "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz" integrity sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ== dependencies: "@jest/console" "^29.5.0" @@ -1600,7 +1600,7 @@ "@jest/environment@^29.5.0": version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz" integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ== dependencies: "@jest/fake-timers" "^29.5.0" @@ -1610,14 +1610,14 @@ "@jest/expect-utils@^29.5.0": version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036" + resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz" integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg== dependencies: jest-get-type "^29.4.3" "@jest/expect@^29.5.0": version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.5.0.tgz#80952f5316b23c483fbca4363ce822af79c38fba" + resolved "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz" integrity sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g== dependencies: expect "^29.5.0" @@ -1625,7 +1625,7 @@ "@jest/fake-timers@^29.5.0": version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.5.0.tgz#d4d09ec3286b3d90c60bdcd66ed28d35f1b4dc2c" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz" integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg== dependencies: "@jest/types" "^29.5.0" @@ -1637,7 +1637,7 @@ "@jest/globals@^29.5.0": version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.5.0.tgz#6166c0bfc374c58268677539d0c181f9c1833298" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz" integrity sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ== dependencies: "@jest/environment" "^29.5.0" @@ -1647,7 +1647,7 @@ "@jest/reporters@^29.5.0": version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.5.0.tgz#985dfd91290cd78ddae4914ba7921bcbabe8ac9b" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz" integrity sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA== dependencies: "@bcoe/v8-coverage" "^0.2.3" @@ -1677,21 +1677,21 @@ "@jest/schemas@^28.1.3": version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-28.1.3.tgz#ad8b86a66f11f33619e3d7e1dcddd7f2d40ff905" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz" integrity sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg== dependencies: "@sinclair/typebox" "^0.24.1" "@jest/schemas@^29.4.3": version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz" integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== dependencies: "@sinclair/typebox" "^0.25.16" "@jest/source-map@^29.4.3": version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.3.tgz#ff8d05cbfff875d4a791ab679b4333df47951d20" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz" integrity sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w== dependencies: "@jridgewell/trace-mapping" "^0.3.15" @@ -1700,7 +1700,7 @@ "@jest/test-result@^29.5.0": version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.5.0.tgz#7c856a6ca84f45cc36926a4e9c6b57f1973f1408" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz" integrity sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ== dependencies: "@jest/console" "^29.5.0" @@ -1710,7 +1710,7 @@ "@jest/test-sequencer@^29.5.0": version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz#34d7d82d3081abd523dbddc038a3ddcb9f6d3cc4" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz" integrity sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ== dependencies: "@jest/test-result" "^29.5.0" @@ -1720,7 +1720,7 @@ "@jest/transform@28.1.3": version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.3.tgz#59d8098e50ab07950e0f2fc0fc7ec462371281b0" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz" integrity sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA== dependencies: "@babel/core" "^7.11.6" @@ -1741,7 +1741,7 @@ "@jest/transform@^29.5.0": version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.5.0.tgz#cf9c872d0965f0cbd32f1458aa44a2b1988b00f9" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz" integrity sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw== dependencies: "@babel/core" "^7.11.6" @@ -1762,7 +1762,7 @@ "@jest/types@^28.1.3": version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.3.tgz#b05de80996ff12512bc5ceb1d208285a7d11748b" + resolved "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz" integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ== dependencies: "@jest/schemas" "^28.1.3" @@ -1774,7 +1774,7 @@ "@jest/types@^29.5.0": version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" + resolved "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz" integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== dependencies: "@jest/schemas" "^29.4.3" @@ -1786,7 +1786,7 @@ "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz" integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== dependencies: "@jridgewell/set-array" "^1.0.1" @@ -1795,27 +1795,27 @@ "@jridgewell/resolve-uri@3.1.0": version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== "@jridgewell/set-array@^1.0.1": version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== "@jridgewell/sourcemap-codec@1.4.14": version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": version "0.3.18" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz" integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== dependencies: "@jridgewell/resolve-uri" "3.1.0" @@ -1823,29 +1823,29 @@ "@jsdevtools/ono@^7.1.3": version "7.1.3" - resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" + resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz" integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== "@kwsites/file-exists@^1.1.1": version "1.1.1" - resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" + resolved "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz" integrity sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw== dependencies: debug "^4.1.1" "@kwsites/promise-deferred@^1.1.1": version "1.1.1" - resolved "https://registry.yarnpkg.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz#8ace5259254426ccef57f3175bc64ed7095ed919" + resolved "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz" integrity sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw== "@noble/hashes@^1", "@noble/hashes@^1.0.0": version "1.3.1" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== "@nodelib/fs.scandir@2.1.5": version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: "@nodelib/fs.stat" "2.0.5" @@ -1853,12 +1853,12 @@ "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" @@ -1866,27 +1866,27 @@ "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz" integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== "@protobufjs/base64@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz" integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== "@protobufjs/codegen@^2.0.4": version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz" integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== "@protobufjs/eventemitter@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz" integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== "@protobufjs/fetch@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz" integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== dependencies: "@protobufjs/aspromise" "^1.1.1" @@ -1894,32 +1894,32 @@ "@protobufjs/float@^1.0.2": version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz" integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== "@protobufjs/inquire@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz" integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== "@protobufjs/path@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz" integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== "@protobufjs/pool@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz" integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== "@protobufjs/utf8@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== "@pyramation/json-schema-ref-parser@9.0.6": version "9.0.6" - resolved "https://registry.yarnpkg.com/@pyramation/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#556e416ce7dcc15a3c1afd04d6a059e03ed09aeb" + resolved "https://registry.npmjs.org/@pyramation/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz" integrity sha512-L5kToHAEc1Q87R8ZwWFaNa4tPHr8Hnm+U+DRdUVq3tUtk+EX4pCqSd34Z6EMxNi/bjTzt1syAG9J2Oo1YFlqSg== dependencies: "@jsdevtools/ono" "^7.1.3" @@ -1928,7 +1928,7 @@ "@pyramation/json-schema-to-typescript@ 11.0.4": version "11.0.4" - resolved "https://registry.yarnpkg.com/@pyramation/json-schema-to-typescript/-/json-schema-to-typescript-11.0.4.tgz#959bdb631dad336e1fdbf608a9b5908ab0da1d6b" + resolved "https://registry.npmjs.org/@pyramation/json-schema-to-typescript/-/json-schema-to-typescript-11.0.4.tgz" integrity sha512-+aSzXDLhMHOEdV2cJ7Tjg/9YenjHU5BCmClVygzwxJZ1R16NOfEn7lTAwVzb/2jivOSnhjHzMJbnSf8b6rd1zg== dependencies: "@pyramation/json-schema-ref-parser" "9.0.6" @@ -1948,31 +1948,31 @@ "@sinclair/typebox@^0.24.1": version "0.24.51" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" + resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz" integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== "@sinclair/typebox@^0.25.16": version "0.25.24" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" + resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz" integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== "@sinonjs/commons@^3.0.0": version "3.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz" integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== dependencies: type-detect "4.0.8" "@sinonjs/fake-timers@^10.0.2": version "10.2.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.2.0.tgz#b3e322a34c5f26e3184e7f6115695f299c1b1194" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.2.0.tgz" integrity sha512-OPwQlEdg40HAj5KNF8WW6q2KG4Z+cBCZb3m4ninfTZKaBmbIJodviQsDBoYMPHkOyJJMHnOJo5j2+LKDOhOACg== dependencies: "@sinonjs/commons" "^3.0.0" "@types/babel__core@^7.1.14": version "7.20.1" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz" integrity sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw== dependencies: "@babel/parser" "^7.20.7" @@ -1983,14 +1983,14 @@ "@types/babel__generator@*": version "7.6.4" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz" integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": version "7.4.1" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz" integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== dependencies: "@babel/parser" "^7.1.0" @@ -1998,14 +1998,14 @@ "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": version "7.20.1" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.1.tgz#dd6f1d2411ae677dcb2db008c962598be31d6acf" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz" integrity sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg== dependencies: "@babel/types" "^7.20.7" "@types/glob@^7.1.3": version "7.2.0" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" + resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz" integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== dependencies: "@types/minimatch" "*" @@ -2013,33 +2013,33 @@ "@types/graceful-fs@^4.1.3": version "4.1.6" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" + resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz" integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== dependencies: "@types/node" "*" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== "@types/istanbul-lib-report@*": version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== dependencies: "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^3.0.0": version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz" integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== dependencies: "@types/istanbul-lib-report" "*" "@types/jest@^29.5.2": version "29.5.2" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.2.tgz#86b4afc86e3a8f3005b297ed8a72494f89e6395b" + resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.2.tgz" integrity sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg== dependencies: expect "^29.0.0" @@ -2047,65 +2047,65 @@ "@types/json-schema@^7.0.11", "@types/json-schema@^7.0.9": version "7.0.12" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== "@types/lodash@^4.14.182": version "4.14.195" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632" + resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz" integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg== "@types/long@^4.0.1": version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + resolved "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz" integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== "@types/minimatch@*": version "5.1.2" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz" integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/node@*", "@types/node@>=13.7.0": version "20.3.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe" + resolved "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz" integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg== "@types/prettier@^2.1.5", "@types/prettier@^2.6.1": version "2.7.3" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz" integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== "@types/semver@^7.3.12": version "7.5.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" + resolved "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz" integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== "@types/stack-utils@^2.0.0": version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== "@types/yargs-parser@*": version "21.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== "@types/yargs@^17.0.8": version "17.0.24" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz" integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.59.9": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz#8d466aa21abea4c3f37129997b198d141f09e76f" - integrity sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg== +"@typescript-eslint/eslint-plugin@^5.60.1": + version "5.60.1" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.1.tgz" + integrity sha512-KSWsVvsJsLJv3c4e73y/Bzt7OpqMCADUO846bHcuWYSYM19bldbAeDv7dYyV0jwkbMfJ2XdlzwjhXtuD7OY6bw== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.59.11" - "@typescript-eslint/type-utils" "5.59.11" - "@typescript-eslint/utils" "5.59.11" + "@typescript-eslint/scope-manager" "5.60.1" + "@typescript-eslint/type-utils" "5.60.1" + "@typescript-eslint/utils" "5.60.1" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -2113,87 +2113,87 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.59.9": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.11.tgz#af7d4b7110e3068ce0b97550736de455e4250103" - integrity sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA== +"@typescript-eslint/parser@^5.60.1": + version "5.60.1" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.1.tgz" + integrity sha512-pHWlc3alg2oSMGwsU/Is8hbm3XFbcrb6P5wIxcQW9NsYBfnrubl/GhVVD/Jm/t8HXhA2WncoIRfBtnCgRGV96Q== dependencies: - "@typescript-eslint/scope-manager" "5.59.11" - "@typescript-eslint/types" "5.59.11" - "@typescript-eslint/typescript-estree" "5.59.11" + "@typescript-eslint/scope-manager" "5.60.1" + "@typescript-eslint/types" "5.60.1" + "@typescript-eslint/typescript-estree" "5.60.1" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.59.11": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz#5d131a67a19189c42598af9fb2ea1165252001ce" - integrity sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q== +"@typescript-eslint/scope-manager@5.60.1": + version "5.60.1" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz" + integrity sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ== dependencies: - "@typescript-eslint/types" "5.59.11" - "@typescript-eslint/visitor-keys" "5.59.11" + "@typescript-eslint/types" "5.60.1" + "@typescript-eslint/visitor-keys" "5.60.1" -"@typescript-eslint/type-utils@5.59.11": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz#5eb67121808a84cb57d65a15f48f5bdda25f2346" - integrity sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g== +"@typescript-eslint/type-utils@5.60.1": + version "5.60.1" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.60.1.tgz" + integrity sha512-vN6UztYqIu05nu7JqwQGzQKUJctzs3/Hg7E2Yx8rz9J+4LgtIDFWjjl1gm3pycH0P3mHAcEUBd23LVgfrsTR8A== dependencies: - "@typescript-eslint/typescript-estree" "5.59.11" - "@typescript-eslint/utils" "5.59.11" + "@typescript-eslint/typescript-estree" "5.60.1" + "@typescript-eslint/utils" "5.60.1" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.59.11": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.11.tgz#1a9018fe3c565ba6969561f2a49f330cf1fe8db1" - integrity sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA== +"@typescript-eslint/types@5.60.1": + version "5.60.1" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz" + integrity sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg== -"@typescript-eslint/typescript-estree@5.59.11": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz#b2caaa31725e17c33970c1197bcd54e3c5f42b9f" - integrity sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA== +"@typescript-eslint/typescript-estree@5.60.1": + version "5.60.1" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz" + integrity sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw== dependencies: - "@typescript-eslint/types" "5.59.11" - "@typescript-eslint/visitor-keys" "5.59.11" + "@typescript-eslint/types" "5.60.1" + "@typescript-eslint/visitor-keys" "5.60.1" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.59.11": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.11.tgz#9dbff49dc80bfdd9289f9f33548f2e8db3c59ba1" - integrity sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg== +"@typescript-eslint/utils@5.60.1": + version "5.60.1" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.1.tgz" + integrity sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.59.11" - "@typescript-eslint/types" "5.59.11" - "@typescript-eslint/typescript-estree" "5.59.11" + "@typescript-eslint/scope-manager" "5.60.1" + "@typescript-eslint/types" "5.60.1" + "@typescript-eslint/typescript-estree" "5.60.1" eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.59.11": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz#dca561ddad169dc27d62396d64f45b2d2c3ecc56" - integrity sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA== +"@typescript-eslint/visitor-keys@5.60.1": + version "5.60.1" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz" + integrity sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw== dependencies: - "@typescript-eslint/types" "5.59.11" + "@typescript-eslint/types" "5.60.1" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^8.8.0: version "8.9.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz" integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ== ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -2203,73 +2203,73 @@ ajv@^6.10.0, ajv@^6.12.4: ansi-escapes@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz" integrity sha512-tH/fSoQp4DrEodDK3QpdiWiZTSe7sBJ9eOqcQBZ0o9HTM+5M/viSEn+sPMoTuPjQQ8n++w3QJoPEjt8LVPcrCg== ansi-escapes@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== ansi-escapes@^4.2.1: version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: type-fest "^0.21.3" ansi-regex@^2.0.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== ansi-regex@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz" integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== ansi-regex@^4.1.0: version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz" integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-styles@^2.2.1: version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== ansi-styles@^3.2.1: version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" ansi-styles@^5.0.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== any-promise@^1.0.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz" integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== anymatch@^3.0.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" @@ -2277,45 +2277,45 @@ anymatch@^3.0.3: argparse@^1.0.7: version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" argparse@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== array-union@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== ast-stringify@0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/ast-stringify/-/ast-stringify-0.1.0.tgz#5c6439fbfb4513dcc26c7d34464ccd084ed91cb7" + resolved "https://registry.npmjs.org/ast-stringify/-/ast-stringify-0.1.0.tgz" integrity sha512-J1PgFYV3RG6r37+M6ySZJH406hR82okwGvFM9hLXpOvdx4WC4GEW8/qiw6pi1hKTrqcRvoHP8a7mp87egYr6iA== dependencies: "@babel/runtime" "^7.11.2" axios@^0.21.2: version "0.21.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: follow-redirects "^1.14.0" axios@^0.26.1: version "0.26.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + resolved "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz" integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== dependencies: follow-redirects "^1.14.8" babel-jest@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.5.0.tgz#3fe3ddb109198e78b1c88f9ebdecd5e4fc2f50a5" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz" integrity sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q== dependencies: "@jest/transform" "^29.5.0" @@ -2328,7 +2328,7 @@ babel-jest@^29.5.0: babel-plugin-istanbul@^6.1.1: version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -2339,7 +2339,7 @@ babel-plugin-istanbul@^6.1.1: babel-plugin-jest-hoist@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz" integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== dependencies: "@babel/template" "^7.3.3" @@ -2349,7 +2349,7 @@ babel-plugin-jest-hoist@^29.5.0: babel-plugin-polyfill-corejs2@^0.3.2: version "0.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz" integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== dependencies: "@babel/compat-data" "^7.17.7" @@ -2358,7 +2358,7 @@ babel-plugin-polyfill-corejs2@^0.3.2: babel-plugin-polyfill-corejs2@^0.4.3: version "0.4.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz#75044d90ba5043a5fb559ac98496f62f3eb668fd" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz" integrity sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw== dependencies: "@babel/compat-data" "^7.17.7" @@ -2367,7 +2367,7 @@ babel-plugin-polyfill-corejs2@^0.4.3: babel-plugin-polyfill-corejs3@^0.5.3: version "0.5.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz" integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw== dependencies: "@babel/helper-define-polyfill-provider" "^0.3.2" @@ -2375,7 +2375,7 @@ babel-plugin-polyfill-corejs3@^0.5.3: babel-plugin-polyfill-corejs3@^0.8.1: version "0.8.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz#39248263c38191f0d226f928d666e6db1b4b3a8a" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz" integrity sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q== dependencies: "@babel/helper-define-polyfill-provider" "^0.4.0" @@ -2383,21 +2383,21 @@ babel-plugin-polyfill-corejs3@^0.8.1: babel-plugin-polyfill-regenerator@^0.4.0: version "0.4.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz" integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== dependencies: "@babel/helper-define-polyfill-provider" "^0.3.3" babel-plugin-polyfill-regenerator@^0.5.0: version "0.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz#e7344d88d9ef18a3c47ded99362ae4a757609380" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz" integrity sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g== dependencies: "@babel/helper-define-polyfill-provider" "^0.4.0" babel-preset-current-node-syntax@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz" integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" @@ -2415,7 +2415,7 @@ babel-preset-current-node-syntax@^1.0.0: babel-preset-jest@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz" integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== dependencies: babel-plugin-jest-hoist "^29.5.0" @@ -2423,22 +2423,22 @@ babel-preset-jest@^29.5.0: balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.3.0: version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== bech32@^1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + resolved "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== binary-install@^1.0.1: version "1.1.0" - resolved "https://registry.yarnpkg.com/binary-install/-/binary-install-1.1.0.tgz#61195349acabf5a043f3805b03f96e506cc96d6e" + resolved "https://registry.npmjs.org/binary-install/-/binary-install-1.1.0.tgz" integrity sha512-rkwNGW+3aQVSZoD0/o3mfPN6Yxh3Id0R/xzTVBVVpGNlVz8EGwusksxRlbk/A5iKTZt9zkMn3qIqmAt3vpfbzg== dependencies: axios "^0.26.1" @@ -2447,17 +2447,17 @@ binary-install@^1.0.1: bn.js@^4.11.9: version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== bn.js@^5.2.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== brace-expansion@^1.1.7: version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" @@ -2465,26 +2465,26 @@ brace-expansion@^1.1.7: brace-expansion@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" braces@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" brorand@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== browserslist@^4.21.3, browserslist@^4.21.5: version "4.21.9" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz" integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg== dependencies: caniuse-lite "^1.0.30001503" @@ -2494,49 +2494,49 @@ browserslist@^4.21.3, browserslist@^4.21.5: bser@2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== dependencies: node-int64 "^0.4.0" buffer-from@^1.0.0: version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== call-me-maybe@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa" + resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz" integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== callsites@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase@^5.3.1: version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== camelcase@^6.2.0: version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001503: version "1.0.30001504" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001504.tgz#eaf77e5c852dfa5f82c4924468c30602ac53744a" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001504.tgz" integrity sha512-5uo7eoOp2mKbWyfMXnGO9rJWOGU8duvzEiYITW+wivukL7yHH4gX9yuRaobu6El4jPxo6jKZfG+N6fB621GD/Q== case@1.6.3: version "1.6.3" - resolved "https://registry.yarnpkg.com/case/-/case-1.6.3.tgz#0a4386e3e9825351ca2e6216c60467ff5f1ea1c9" + resolved "https://registry.npmjs.org/case/-/case-1.6.3.tgz" integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== chalk@4.1.2, chalk@^4.0.0: version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -2544,7 +2544,7 @@ chalk@4.1.2, chalk@^4.0.0: chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== dependencies: ansi-styles "^2.2.1" @@ -2555,7 +2555,7 @@ chalk@^1.0.0, chalk@^1.1.3: chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" @@ -2564,37 +2564,37 @@ chalk@^2.0.0, chalk@^2.4.2: char-regex@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== chardet@^0.4.0: version "0.4.2" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" + resolved "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz" integrity sha512-j/Toj7f1z98Hh2cYo2BVr85EpIRWqUi7rtRSGxh/cqUjqrnJe9l9UE7IUGd2vQ2p+kSHLkSzObQPZPLUC6TQwg== chardet@^0.7.0: version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== chownr@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== ci-info@^3.2.0: version "3.8.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz" integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== cjs-module-lexer@^1.0.0: version "1.2.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== cli-color@^2.0.2: version "2.0.3" - resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.3.tgz#73769ba969080629670f3f2ef69a4bf4e7cc1879" + resolved "https://registry.npmjs.org/cli-color/-/cli-color-2.0.3.tgz" integrity sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ== dependencies: d "^1.0.1" @@ -2605,19 +2605,19 @@ cli-color@^2.0.2: cli-cursor@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz" integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== dependencies: restore-cursor "^2.0.0" cli-width@^2.0.0: version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" + resolved "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz" integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== cliui@^7.0.2: version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== dependencies: string-width "^4.2.0" @@ -2626,7 +2626,7 @@ cliui@^7.0.2: cliui@^8.0.1: version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: string-width "^4.2.0" @@ -2635,61 +2635,61 @@ cliui@^8.0.1: co@^4.6.0: version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== collect-v8-coverage@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz" integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== color-convert@^1.9.0: version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: color-name "1.1.3" color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== color-name@~1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== colors@^1.1.2: version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== concat-map@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== convert-source-map@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== copyfiles@^2.4.1: version "2.4.1" - resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.4.1.tgz#d2dcff60aaad1015f09d0b66e7f0f1c5cd3c5da5" + resolved "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz" integrity sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg== dependencies: glob "^7.0.5" @@ -2702,27 +2702,19 @@ copyfiles@^2.4.1: core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.30.1, core-js-compat@^3.30.2: version "3.31.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.0.tgz#4030847c0766cc0e803dcdfb30055d7ef2064bf1" + resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz" integrity sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw== dependencies: browserslist "^4.21.5" core-util-is@~1.0.0: version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cosmjs-types@^0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.7.2.tgz#a757371abd340949c5bd5d49c6f8379ae1ffd7e2" - integrity sha512-vf2uLyktjr/XVAgEq0DjMxeAWh1yYREe7AMHDKd7EiHVqxBPCaBS+qEEQUkXbR9ndnckqr1sUG8BQhazh4X5lA== - dependencies: - long "^4.0.0" - protobufjs "~6.11.2" - cosmjs-types@^0.8.0: version "0.8.0" - resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.8.0.tgz#2ed78f3e990f770229726f95f3ef5bf9e2b6859b" + resolved "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.8.0.tgz" integrity sha512-Q2Mj95Fl0PYMWEhA2LuGEIhipF7mQwd9gTQ85DdP9jjjopeoGaDxvmPa5nakNzsq7FnO1DMTatXTAx6bxMH7Lg== dependencies: long "^4.0.0" @@ -2730,7 +2722,7 @@ cosmjs-types@^0.8.0: cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: path-key "^3.1.0" @@ -2739,7 +2731,7 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: d@1, d@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz" integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== dependencies: es5-ext "^0.10.50" @@ -2747,39 +2739,39 @@ d@1, d@^1.0.1: dargs@7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" + resolved "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz" integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" dedent@^0.7.0: version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz" integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== deep-is@^0.1.3: version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== deepmerge@4.2.2: version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== deepmerge@^4.2.2: version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== define-properties@^1.1.3: version "1.2.0" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz" integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== dependencies: has-property-descriptors "^1.0.0" @@ -2787,41 +2779,41 @@ define-properties@^1.1.3: detect-newline@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== diff-sequences@^29.4.3: version "29.4.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz" integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== dir-glob@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: path-type "^4.0.0" doctrine@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" dotty@0.1.2: version "0.1.2" - resolved "https://registry.yarnpkg.com/dotty/-/dotty-0.1.2.tgz#512d44cc4111a724931226259297f235e8484f6f" + resolved "https://registry.npmjs.org/dotty/-/dotty-0.1.2.tgz" integrity sha512-V0EWmKeH3DEhMwAZ+8ZB2Ao4OK6p++Z0hsDtZq3N0+0ZMVqkzrcEGROvOnZpLnvBg5PTNG23JEDLAm64gPaotQ== electron-to-chromium@^1.4.431: version "1.4.433" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.433.tgz#305ef5f8ea5fe65d252aae4b0e1088f9e4842533" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.433.tgz" integrity sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ== elliptic@^6.5.4: version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: bn.js "^4.11.9" @@ -2834,24 +2826,24 @@ elliptic@^6.5.4: emittery@^0.13.1: version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz" integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== error-ex@^1.3.1: version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.61, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: version "0.10.62" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" + resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz" integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== dependencies: es6-iterator "^2.0.3" @@ -2860,7 +2852,7 @@ es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@ es6-iterator@^2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz" integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== dependencies: d "1" @@ -2869,7 +2861,7 @@ es6-iterator@^2.0.3: es6-symbol@^3.1.1, es6-symbol@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz" integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== dependencies: d "^1.0.1" @@ -2877,7 +2869,7 @@ es6-symbol@^3.1.1, es6-symbol@^3.1.3: es6-weak-map@^2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" + resolved "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz" integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== dependencies: d "1" @@ -2887,32 +2879,32 @@ es6-weak-map@^2.0.3: escalade@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== escape-string-regexp@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== escape-string-regexp@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== eslint-config-prettier@^8.8.0: version "8.8.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348" + resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz" integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== eslint-scope@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== dependencies: esrecurse "^4.3.0" @@ -2920,7 +2912,7 @@ eslint-scope@^5.1.1: eslint-scope@^7.2.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz" integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== dependencies: esrecurse "^4.3.0" @@ -2928,12 +2920,12 @@ eslint-scope@^7.2.0: eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: version "3.4.1" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz" integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== eslint@^8.42.0: version "8.43.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.43.0.tgz#3e8c6066a57097adfd9d390b8fc93075f257a094" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz" integrity sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q== dependencies: "@eslint-community/eslint-utils" "^4.2.0" @@ -2978,7 +2970,7 @@ eslint@^8.42.0: espree@^9.5.2: version "9.5.2" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b" + resolved "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz" integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw== dependencies: acorn "^8.8.0" @@ -2987,41 +2979,41 @@ espree@^9.5.2: esprima@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.2: version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz" integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== dependencies: estraverse "^5.1.0" esrecurse@^4.3.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: estraverse "^5.2.0" estraverse@^4.1.1: version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== esutils@^2.0.2: version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== event-emitter@^0.3.5: version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + resolved "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz" integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== dependencies: d "1" @@ -3029,7 +3021,7 @@ event-emitter@^0.3.5: execa@^5.0.0: version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: cross-spawn "^7.0.3" @@ -3044,12 +3036,12 @@ execa@^5.0.0: exit@^0.1.2: version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== expect@^29.0.0, expect@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" + resolved "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz" integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== dependencies: "@jest/expect-utils" "^29.5.0" @@ -3060,14 +3052,14 @@ expect@^29.0.0, expect@^29.5.0: ext@^1.1.2: version "1.7.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + resolved "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz" integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== dependencies: type "^2.7.2" external-editor@^2.0.4: version "2.2.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" + resolved "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz" integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== dependencies: chardet "^0.4.0" @@ -3076,7 +3068,7 @@ external-editor@^2.0.4: external-editor@^3.0.3: version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + resolved "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz" integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== dependencies: chardet "^0.7.0" @@ -3085,12 +3077,12 @@ external-editor@^3.0.3: fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.2.9: version "3.2.12" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz" integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== dependencies: "@nodelib/fs.stat" "^2.0.2" @@ -3101,52 +3093,52 @@ fast-glob@^3.2.9: fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-levenshtein@^2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: version "1.15.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz" integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== dependencies: reusify "^1.0.4" fb-watchman@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz" integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== dependencies: bser "2.1.1" figures@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + resolved "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz" integrity sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA== dependencies: escape-string-regexp "^1.0.5" file-entry-cache@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" fill-range@^7.0.1: version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: to-regex-range "^5.0.1" find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: locate-path "^5.0.0" @@ -3154,7 +3146,7 @@ find-up@^4.0.0, find-up@^4.1.0: find-up@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" @@ -3162,7 +3154,7 @@ find-up@^5.0.0: flat-cache@^3.0.4: version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: flatted "^3.1.0" @@ -3170,54 +3162,54 @@ flat-cache@^3.0.4: flatted@^3.1.0: version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== follow-redirects@^1.14.0, follow-redirects@^1.14.8: version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== fs-minipass@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== dependencies: minipass "^3.0.0" fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@^2.3.2: version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== fuzzy@0.1.3: version "0.1.3" - resolved "https://registry.yarnpkg.com/fuzzy/-/fuzzy-0.1.3.tgz#4c76ec2ff0ac1a36a9dccf9a00df8623078d4ed8" + resolved "https://registry.npmjs.org/fuzzy/-/fuzzy-0.1.3.tgz" integrity sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w== gensync@^1.0.0-beta.2: version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== get-caller-file@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-intrinsic@^1.1.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz" integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== dependencies: function-bind "^1.1.1" @@ -3227,43 +3219,43 @@ get-intrinsic@^1.1.1: get-package-type@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== get-stdin@^8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz" integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== get-stream@^6.0.0: version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== glob-parent@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" glob-parent@^6.0.2: version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: is-glob "^4.0.3" glob-promise@^4.2.2: version "4.2.2" - resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-4.2.2.tgz#15f44bcba0e14219cd93af36da6bb905ff007877" + resolved "https://registry.npmjs.org/glob-promise/-/glob-promise-4.2.2.tgz" integrity sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw== dependencies: "@types/glob" "^7.1.3" glob@8.0.3: version "8.0.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" + resolved "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz" integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== dependencies: fs.realpath "^1.0.0" @@ -3274,7 +3266,7 @@ glob@8.0.3: glob@^7.0.0, glob@^7.0.5, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" @@ -3286,26 +3278,26 @@ glob@^7.0.0, glob@^7.0.5, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: globals@^11.1.0: version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.19.0: version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" + resolved "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz" integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== dependencies: type-fest "^0.20.2" globalthis@^1.0.1: version "1.0.3" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz" integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== dependencies: define-properties "^1.1.3" globby@^11.1.0: version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" @@ -3317,63 +3309,63 @@ globby@^11.1.0: graceful-fs@^4.1.15, graceful-fs@^4.2.9: version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== grapheme-splitter@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== graphemer@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== has-ansi@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + resolved "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== dependencies: ansi-regex "^2.0.0" has-flag@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-property-descriptors@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz" integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== dependencies: get-intrinsic "^1.1.1" has-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz" integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== has-symbols@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== has@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: function-bind "^1.1.1" hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== dependencies: inherits "^2.0.3" @@ -3381,7 +3373,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: hmac-drbg@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + resolved "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz" integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== dependencies: hash.js "^1.0.3" @@ -3390,29 +3382,29 @@ hmac-drbg@^1.0.1: html-escaper@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== human-signals@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== iconv-lite@^0.4.17, iconv-lite@^0.4.24: version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" ignore@^5.2.0: version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" @@ -3420,7 +3412,7 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: import-local@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz" integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== dependencies: pkg-dir "^4.2.0" @@ -3428,12 +3420,12 @@ import-local@^3.0.2: imurmurhash@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== inflight@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" @@ -3441,17 +3433,17 @@ inflight@^1.0.4: inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== inherits@2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== inquirer-autocomplete-prompt@^0.11.1: version "0.11.1" - resolved "https://registry.yarnpkg.com/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-0.11.1.tgz#f90ca9510a4c489882e9be294934bd8c2e575e09" + resolved "https://registry.npmjs.org/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-0.11.1.tgz" integrity sha512-VM4eNiyRD4CeUc2cyKni+F8qgHwL9WC4LdOr+mEC85qP/QNsDV+ysVqUrJYhw1TmDQu1QVhc8hbaL7wfk8SJxw== dependencies: ansi-escapes "^2.0.0" @@ -3464,7 +3456,7 @@ inquirer-autocomplete-prompt@^0.11.1: inquirer@3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.1.1.tgz#87621c4fba4072f48a8dd71c9f9df6f100b2d534" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-3.1.1.tgz" integrity sha512-H50sHQwgvvaTBd3HpKMVtL/u6LoHDvYym51gd7bGQe/+9HkCE+J0/3N5FJLfd6O6oz44hHewC2Pc2LodzWVafQ== dependencies: ansi-escapes "^2.0.0" @@ -3484,7 +3476,7 @@ inquirer@3.1.1: inquirer@^6.0.0: version "6.5.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz" integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== dependencies: ansi-escapes "^3.2.0" @@ -3503,7 +3495,7 @@ inquirer@^6.0.0: inquirerer@0.1.3: version "0.1.3" - resolved "https://registry.yarnpkg.com/inquirerer/-/inquirerer-0.1.3.tgz#ecf91dc672b3bf45211d7f64bf5e8d5e171fd2ad" + resolved "https://registry.npmjs.org/inquirerer/-/inquirerer-0.1.3.tgz" integrity sha512-yGgLUOqPxTsINBjZNZeLi3cv2zgxXtw9feaAOSJf2j6AqIT5Uxs5ZOqOrfAf+xP65Sicla1FD3iDxa3D6TsCAQ== dependencies: colors "^1.1.2" @@ -3512,96 +3504,96 @@ inquirerer@0.1.3: interpret@^1.0.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + resolved "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-core-module@^2.11.0: version "2.12.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz" integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== dependencies: has "^1.0.3" is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== is-fullwidth-code-point@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-generator-fn@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" is-number@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-path-inside@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== is-promise@^2.2.2: version "2.2.2" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" + resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== is-stream@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== isarray@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== isarray@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isexe@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== isomorphic-ws@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + resolved "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz" integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== dependencies: "@babel/core" "^7.12.3" @@ -3612,7 +3604,7 @@ istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: istanbul-lib-report@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== dependencies: istanbul-lib-coverage "^3.0.0" @@ -3621,7 +3613,7 @@ istanbul-lib-report@^3.0.0: istanbul-lib-source-maps@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== dependencies: debug "^4.1.1" @@ -3630,7 +3622,7 @@ istanbul-lib-source-maps@^4.0.0: istanbul-reports@^3.1.3: version "3.1.5" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz" integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== dependencies: html-escaper "^2.0.0" @@ -3638,7 +3630,7 @@ istanbul-reports@^3.1.3: jest-changed-files@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz" integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== dependencies: execa "^5.0.0" @@ -3646,7 +3638,7 @@ jest-changed-files@^29.5.0: jest-circus@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.5.0.tgz#b5926989449e75bff0d59944bae083c9d7fb7317" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz" integrity sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA== dependencies: "@jest/environment" "^29.5.0" @@ -3672,7 +3664,7 @@ jest-circus@^29.5.0: jest-cli@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.5.0.tgz#b34c20a6d35968f3ee47a7437ff8e53e086b4a67" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz" integrity sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw== dependencies: "@jest/core" "^29.5.0" @@ -3690,7 +3682,7 @@ jest-cli@^29.5.0: jest-config@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.5.0.tgz#3cc972faec8c8aaea9ae158c694541b79f3748da" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz" integrity sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA== dependencies: "@babel/core" "^7.11.6" @@ -3718,7 +3710,7 @@ jest-config@^29.5.0: jest-diff@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz" integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw== dependencies: chalk "^4.0.0" @@ -3728,14 +3720,14 @@ jest-diff@^29.5.0: jest-docblock@^29.4.3: version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz" integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== dependencies: detect-newline "^3.0.0" jest-each@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.5.0.tgz#fc6e7014f83eac68e22b7195598de8554c2e5c06" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz" integrity sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA== dependencies: "@jest/types" "^29.5.0" @@ -3746,7 +3738,7 @@ jest-each@^29.5.0: jest-environment-node@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.5.0.tgz#f17219d0f0cc0e68e0727c58b792c040e332c967" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz" integrity sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw== dependencies: "@jest/environment" "^29.5.0" @@ -3758,12 +3750,12 @@ jest-environment-node@^29.5.0: jest-get-type@^29.4.3: version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz" integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== jest-haste-map@^28.1.3: version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.3.tgz#abd5451129a38d9841049644f34b034308944e2b" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz" integrity sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA== dependencies: "@jest/types" "^28.1.3" @@ -3782,7 +3774,7 @@ jest-haste-map@^28.1.3: jest-haste-map@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.5.0.tgz#69bd67dc9012d6e2723f20a945099e972b2e94de" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz" integrity sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA== dependencies: "@jest/types" "^29.5.0" @@ -3801,7 +3793,7 @@ jest-haste-map@^29.5.0: jest-leak-detector@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz#cf4bdea9615c72bac4a3a7ba7e7930f9c0610c8c" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz" integrity sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow== dependencies: jest-get-type "^29.4.3" @@ -3809,7 +3801,7 @@ jest-leak-detector@^29.5.0: jest-matcher-utils@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz" integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw== dependencies: chalk "^4.0.0" @@ -3819,7 +3811,7 @@ jest-matcher-utils@^29.5.0: jest-message-util@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz" integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA== dependencies: "@babel/code-frame" "^7.12.13" @@ -3834,7 +3826,7 @@ jest-message-util@^29.5.0: jest-mock@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz" integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw== dependencies: "@jest/types" "^29.5.0" @@ -3843,22 +3835,22 @@ jest-mock@^29.5.0: jest-pnp-resolver@^1.2.2: version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== jest-regex-util@^28.0.2: version "28.0.2" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz" integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== jest-regex-util@^29.4.3: version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz" integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== jest-resolve-dependencies@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz#f0ea29955996f49788bf70996052aa98e7befee4" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz" integrity sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg== dependencies: jest-regex-util "^29.4.3" @@ -3866,7 +3858,7 @@ jest-resolve-dependencies@^29.5.0: jest-resolve@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.5.0.tgz#b053cc95ad1d5f6327f0ac8aae9f98795475ecdc" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz" integrity sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w== dependencies: chalk "^4.0.0" @@ -3881,7 +3873,7 @@ jest-resolve@^29.5.0: jest-runner@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.5.0.tgz#6a57c282eb0ef749778d444c1d758c6a7693b6f8" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz" integrity sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ== dependencies: "@jest/console" "^29.5.0" @@ -3908,7 +3900,7 @@ jest-runner@^29.5.0: jest-runtime@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.5.0.tgz#c83f943ee0c1da7eb91fa181b0811ebd59b03420" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz" integrity sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw== dependencies: "@jest/environment" "^29.5.0" @@ -3936,7 +3928,7 @@ jest-runtime@^29.5.0: jest-snapshot@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.5.0.tgz#c9c1ce0331e5b63cd444e2f95a55a73b84b1e8ce" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz" integrity sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g== dependencies: "@babel/core" "^7.11.6" @@ -3965,7 +3957,7 @@ jest-snapshot@^29.5.0: jest-util@^28.1.3: version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.3.tgz#f4f932aa0074f0679943220ff9cbba7e497028b0" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz" integrity sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ== dependencies: "@jest/types" "^28.1.3" @@ -3977,7 +3969,7 @@ jest-util@^28.1.3: jest-util@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz" integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== dependencies: "@jest/types" "^29.5.0" @@ -3989,7 +3981,7 @@ jest-util@^29.5.0: jest-validate@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.5.0.tgz#8e5a8f36178d40e47138dc00866a5f3bd9916ffc" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz" integrity sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ== dependencies: "@jest/types" "^29.5.0" @@ -4001,7 +3993,7 @@ jest-validate@^29.5.0: jest-watcher@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.5.0.tgz#cf7f0f949828ba65ddbbb45c743a382a4d911363" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz" integrity sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA== dependencies: "@jest/test-result" "^29.5.0" @@ -4015,7 +4007,7 @@ jest-watcher@^29.5.0: jest-worker@^28.1.3: version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.3.tgz#7e3c4ce3fa23d1bb6accb169e7f396f98ed4bb98" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz" integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== dependencies: "@types/node" "*" @@ -4024,7 +4016,7 @@ jest-worker@^28.1.3: jest-worker@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.5.0.tgz#bdaefb06811bd3384d93f009755014d8acb4615d" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz" integrity sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA== dependencies: "@types/node" "*" @@ -4034,7 +4026,7 @@ jest-worker@^29.5.0: jest@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e" + resolved "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz" integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== dependencies: "@jest/core" "^29.5.0" @@ -4044,12 +4036,12 @@ jest@^29.5.0: js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" @@ -4057,153 +4049,153 @@ js-yaml@^3.13.1: js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" jsesc@^2.5.1: version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== jsesc@~0.5.0: version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== json-parse-even-better-errors@^2.3.0: version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json5@^2.2.1, json5@^2.2.2: version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== kleur@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== leven@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== levn@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" type-check "~0.4.0" -libsodium-wrappers@^0.7.6: +libsodium-sumo@^0.7.11: version "0.7.11" - resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.11.tgz#53bd20606dffcc54ea2122133c7da38218f575f7" - integrity sha512-SrcLtXj7BM19vUKtQuyQKiQCRJPgbpauzl3s0rSwD+60wtHqSUuqcoawlMDheCJga85nKOQwxNYQxf/CKAvs6Q== - dependencies: - libsodium "^0.7.11" + resolved "https://registry.npmjs.org/libsodium-sumo/-/libsodium-sumo-0.7.11.tgz" + integrity sha512-bY+7ph7xpk51Ez2GbE10lXAQ5sJma6NghcIDaSPbM/G9elfrjLa0COHl/7P6Wb/JizQzl5UQontOOP1z0VwbLA== -libsodium@^0.7.11: +libsodium-wrappers-sumo@^0.7.11: version "0.7.11" - resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.11.tgz#cd10aae7bcc34a300cc6ad0ac88fcca674cfbc2e" - integrity sha512-WPfJ7sS53I2s4iM58QxY3Inb83/6mjlYgcmZs7DJsvDlnmVUwNinBCi5vBT43P6bHRy01O4zsMU2CoVR6xJ40A== + resolved "https://registry.npmjs.org/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.11.tgz" + integrity sha512-DGypHOmJbB1nZn89KIfGOAkDgfv5N6SBGC3Qvmy/On0P0WD1JQvNRS/e3UL3aFF+xC0m+MYz5M+MnRnK2HMrKQ== + dependencies: + libsodium-sumo "^0.7.11" lines-and-columns@^1.1.6: version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== locate-path@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: p-locate "^4.1.0" locate-path@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" lodash.debounce@^4.0.8: version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== lodash.merge@^4.6.2: version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash@^4.17.12, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0: version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== long@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== long@^5.2.0, long@^5.2.3: version "5.2.3" - resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + resolved "https://registry.npmjs.org/long/-/long-5.2.3.tgz" integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== lru-cache@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: yallist "^3.0.2" lru-cache@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: yallist "^4.0.0" lru-queue@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" + resolved "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz" integrity sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ== dependencies: es5-ext "~0.10.2" make-dir@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" makeerror@1.0.12: version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== dependencies: tmpl "1.0.5" memoizee@^0.4.15: version "0.4.15" - resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72" + resolved "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz" integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== dependencies: d "^1.0.1" @@ -4217,17 +4209,17 @@ memoizee@^0.4.15: merge-stream@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: braces "^3.0.2" @@ -4235,63 +4227,63 @@ micromatch@^4.0.4: mimic-fn@^1.0.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== mimic-fn@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== minimalistic-crypto-utils@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" minimatch@^5.0.1: version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" minimist@1.2.6: version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== minimist@^1.2.6: version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== minipass@^3.0.0: version "3.3.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== dependencies: yallist "^4.0.0" minipass@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + resolved "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== minizlib@^2.1.1: version "2.1.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== dependencies: minipass "^3.0.0" @@ -4299,22 +4291,22 @@ minizlib@^2.1.1: mkdirp@1.0.4, mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== ms@2.1.2: version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== mute-stream@0.0.7: version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz" integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ== mz@^2.7.0: version "2.7.0" - resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz" integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== dependencies: any-promise "^1.0.0" @@ -4323,32 +4315,32 @@ mz@^2.7.0: natural-compare-lite@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + resolved "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz" integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== next-tick@1, next-tick@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== node-int64@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-releases@^2.0.12: version "2.0.12" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz" integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ== noms@0.0.0: version "0.0.0" - resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859" + resolved "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz" integrity sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow== dependencies: inherits "^2.0.1" @@ -4356,50 +4348,50 @@ noms@0.0.0: normalize-path@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== npm-run-path@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" object-assign@^4.0.1: version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== object-keys@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== once@^1.3.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" onetime@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + resolved "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz" integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== dependencies: mimic-fn "^1.0.0" onetime@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" optionator@^0.9.1: version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: deep-is "^0.1.3" @@ -4411,57 +4403,57 @@ optionator@^0.9.1: os-tmpdir@~1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== p-limit@^2.2.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" p-locate@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: p-limit "^2.2.0" p-locate@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" p-try@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== pako@^2.0.2: version "2.1.0" - resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz" integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== parent-module@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" parse-json@^5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" @@ -4471,76 +4463,76 @@ parse-json@^5.2.0: parse-package-name@1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-package-name/-/parse-package-name-1.0.0.tgz#1a108757e4ffc6889d5e78bcc4932a97c097a5a7" + resolved "https://registry.npmjs.org/parse-package-name/-/parse-package-name-1.0.0.tgz" integrity sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg== path-exists@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.7: version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== picocolors@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pirates@^4.0.4: version "4.0.5" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz" integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== pkg-dir@^4.2.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: find-up "^4.0.0" prelude-ls@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prepend-file@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/prepend-file/-/prepend-file-2.0.1.tgz#6a624b474a65ab1f87dc24d1757d5a6d989eb2db" + resolved "https://registry.npmjs.org/prepend-file/-/prepend-file-2.0.1.tgz" integrity sha512-0hXWjmOpz5YBIk6xujS0lYtCw6IAA0wCR3fw49UGTLc3E9BIhcxgqdMa8rzGvrtt2F8wFiGP42oEpQ8fo9zhRw== dependencies: temp-write "^4.0.0" prettier@^2.6.2, prettier@^2.8.8: version "2.8.8" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== pretty-format@^29.0.0, pretty-format@^29.5.0: version "29.5.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz" integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw== dependencies: "@jest/schemas" "^29.4.3" @@ -4549,12 +4541,12 @@ pretty-format@^29.0.0, pretty-format@^29.5.0: process-nextick-args@~2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== prompts@^2.0.1: version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== dependencies: kleur "^3.0.3" @@ -4562,7 +4554,7 @@ prompts@^2.0.1: protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: version "6.11.3" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz" integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== dependencies: "@protobufjs/aspromise" "^1.1.2" @@ -4581,27 +4573,27 @@ protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: punycode@^2.1.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== pure-rand@^6.0.0: version "6.0.2" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" + resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz" integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== queue-microtask@^1.2.2: version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== react-is@^18.0.0: version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== readable-stream@~1.0.31: version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg== dependencies: core-util-is "~1.0.0" @@ -4611,7 +4603,7 @@ readable-stream@~1.0.31: readable-stream@~2.3.6: version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== dependencies: core-util-is "~1.0.0" @@ -4624,43 +4616,43 @@ readable-stream@~2.3.6: readonly-date@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/readonly-date/-/readonly-date-1.0.0.tgz#5af785464d8c7d7c40b9d738cbde8c646f97dcd9" + resolved "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz" integrity sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ== rechoir@^0.6.2: version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz" integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== dependencies: resolve "^1.1.6" regenerate-unicode-properties@^10.1.0: version "10.1.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" + resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz" integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== dependencies: regenerate "^1.4.2" regenerate@^1.4.2: version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== regenerator-runtime@^0.13.11: version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== regenerator-transform@^0.15.1: version "0.15.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" + resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz" integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== dependencies: "@babel/runtime" "^7.8.4" regexpu-core@^5.3.1: version "5.3.2" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz" integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== dependencies: "@babel/regjsgen" "^0.8.0" @@ -4672,41 +4664,41 @@ regexpu-core@^5.3.1: regjsparser@^0.9.1: version "0.9.1" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz" integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== dependencies: jsesc "~0.5.0" require-directory@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== resolve-cwd@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== dependencies: resolve-from "^5.0.0" resolve-from@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve-from@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve.exports@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz" integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== resolve@^1.1.6, resolve@^1.14.2, resolve@^1.20.0: version "1.22.2" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz" integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== dependencies: is-core-module "^2.11.0" @@ -4715,7 +4707,7 @@ resolve@^1.1.6, resolve@^1.14.2, resolve@^1.20.0: restore-cursor@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz" integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== dependencies: onetime "^2.0.0" @@ -4723,84 +4715,84 @@ restore-cursor@^2.0.0: reusify@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rimraf@3.0.2, rimraf@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" run-async@^2.2.0, run-async@^2.3.0: version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== run-parallel@^1.1.9: version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" rx-lite-aggregates@^4.0.8: version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + resolved "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz" integrity sha512-3xPNZGW93oCjiO7PtKxRK6iOVYBWBvtf9QHDfU23Oc+dLIQmAV//UnyXV/yihv81VS/UqoQPk4NegS8EFi55Hg== dependencies: rx-lite "*" rx-lite@*, rx-lite@^4.0.8: version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + resolved "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz" integrity sha512-Cun9QucwK6MIrp3mry/Y7hqD1oFqTYLQ4pGxaHTjIdaFDWRGGLikqp6u8LcWJnzpoALg9hap+JGk8sFIUuEGNA== rxjs@^6.4.0: version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: tslib "^1.9.0" safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== "safer-buffer@>= 2.1.2 < 3": version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== semver@^7.3.5, semver@^7.3.7: version "7.5.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.2.tgz#5b851e66d1be07c1cdaf37dfc856f543325a2beb" + resolved "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz" integrity sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ== dependencies: lru-cache "^6.0.0" shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== shelljs@0.8.5: version "0.8.5" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + resolved "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz" integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== dependencies: glob "^7.0.0" @@ -4809,13 +4801,13 @@ shelljs@0.8.5: signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -simple-git@^3.19.0: - version "3.19.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.19.0.tgz#fe8d0cd86a0e68372b75c0c44a0cb887201c3f7d" - integrity sha512-hyH2p9Ptxjf/xPuL7HfXbpYt9gKhC1yWDh3KYIAYJJePAKV7AEjLN4xhp7lozOdNiaJ9jlVvAbBymVlcS2jRiA== +simple-git@^3.19.1: + version "3.19.1" + resolved "https://registry.npmjs.org/simple-git/-/simple-git-3.19.1.tgz" + integrity sha512-Ck+rcjVaE1HotraRAS8u/+xgTvToTuoMkT9/l9lvuP5jftwnYUp6DwuJzsKErHgfyRk8IB8pqGHWEbM3tLgV1w== dependencies: "@kwsites/file-exists" "^1.1.1" "@kwsites/promise-deferred" "^1.1.1" @@ -4823,17 +4815,17 @@ simple-git@^3.19.0: sisteransi@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== slash@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== source-map-support@0.5.13: version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== dependencies: buffer-from "^1.0.0" @@ -4841,24 +4833,24 @@ source-map-support@0.5.13: source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== sprintf-js@~1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== stack-utils@^2.0.3: version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz" integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: escape-string-regexp "^2.0.0" string-length@^4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== dependencies: char-regex "^1.0.2" @@ -4866,7 +4858,7 @@ string-length@^4.0.1: string-width@^2.0.0, string-width@^2.1.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + resolved "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== dependencies: is-fullwidth-code-point "^2.0.0" @@ -4874,7 +4866,7 @@ string-width@^2.0.0, string-width@^2.1.0: string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" @@ -4883,98 +4875,98 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: string_decoder@~0.10.x: version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== string_decoder@~1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" strip-ansi@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== dependencies: ansi-regex "^2.0.0" strip-ansi@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz" integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow== dependencies: ansi-regex "^3.0.0" strip-ansi@^5.1.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== dependencies: ansi-regex "^4.1.0" strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-bom@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== strip-final-newline@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== supports-color@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== supports-color@^5.3.0: version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" supports-color@^7.1.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" supports-color@^8.0.0: version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== symbol-observable@^2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-2.0.3.tgz#5b521d3d07a43c351055fa43b8355b62d33fd16a" + resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz" integrity sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA== tar@^6.1.11: version "6.1.15" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69" + resolved "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz" integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A== dependencies: chownr "^2.0.0" @@ -4986,12 +4978,12 @@ tar@^6.1.11: temp-dir@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" + resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz" integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== temp-write@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-4.0.0.tgz#cd2e0825fc826ae72d201dc26eef3bf7e6fc9320" + resolved "https://registry.npmjs.org/temp-write/-/temp-write-4.0.0.tgz" integrity sha512-HIeWmj77uOOHb0QX7siN3OtwV3CTntquin6TNVg6SHOqCP3hYKmox90eeFOGaY1MqJ9WYDDjkyZrW6qS5AWpbw== dependencies: graceful-fs "^4.1.15" @@ -5002,7 +4994,7 @@ temp-write@^4.0.0: test-exclude@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== dependencies: "@istanbuljs/schema" "^0.1.2" @@ -5011,26 +5003,26 @@ test-exclude@^6.0.0: text-table@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== thenify-all@^1.0.0: version "1.6.0" - resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + resolved "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz" integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== dependencies: thenify ">= 3.1.0 < 4" "thenify@>= 3.1.0 < 4": version "3.3.1" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + resolved "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz" integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== dependencies: any-promise "^1.0.0" through2@^2.0.1: version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== dependencies: readable-stream "~2.3.6" @@ -5038,12 +5030,12 @@ through2@^2.0.1: through@^2.3.6: version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== timers-ext@^0.1.7: version "0.1.7" - resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" + resolved "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz" integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== dependencies: es5-ext "~0.10.46" @@ -5051,85 +5043,85 @@ timers-ext@^0.1.7: tmp@^0.0.33: version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== dependencies: os-tmpdir "~1.0.2" tmpl@1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-fast-properties@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tsutils@^3.21.0: version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" type-detect@4.0.8: version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== type-fest@^0.20.2: version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== type-fest@^0.21.3: version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== type@^1.0.1: version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + resolved "https://registry.npmjs.org/type/-/type-1.2.0.tgz" integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== type@^2.7.2: version "2.7.2" - resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + resolved "https://registry.npmjs.org/type/-/type-2.7.2.tgz" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== typescript@^5.1.3: version "5.1.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz" integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== unicode-match-property-ecmascript@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + resolved "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz" integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== dependencies: unicode-canonical-property-names-ecmascript "^2.0.0" @@ -5137,22 +5129,22 @@ unicode-match-property-ecmascript@^2.0.0: unicode-match-property-value-ecmascript@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz" integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== unicode-property-aliases-ecmascript@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== untildify@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + resolved "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== update-browserslist-db@^1.0.11: version "1.0.11" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz" integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== dependencies: escalade "^3.1.1" @@ -5160,31 +5152,31 @@ update-browserslist-db@^1.0.11: uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" util-deprecate@~1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== util@^0.10.3: version "0.10.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + resolved "https://registry.npmjs.org/util/-/util-0.10.4.tgz" integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== dependencies: inherits "2.0.3" uuid@^3.3.2: version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== v8-to-istanbul@^9.0.1: version "9.1.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz" integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA== dependencies: "@jridgewell/trace-mapping" "^0.3.12" @@ -5193,14 +5185,14 @@ v8-to-istanbul@^9.0.1: walker@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== dependencies: makeerror "1.0.12" wasm-ast-types@^0.23.1: version "0.23.1" - resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.23.1.tgz#b849629aa9f7a56dbb52c581ddbcb6b5ed7e2a07" + resolved "https://registry.npmjs.org/wasm-ast-types/-/wasm-ast-types-0.23.1.tgz" integrity sha512-igLcEk8VHZq62ZEwn4Jp+WRTp2D9yvTeiQd2Pc+s7LZouzSn3CwRpD42sHK2wV0UlSt2/cNbV6QywFm9Z6eM/A== dependencies: "@babel/runtime" "^7.18.9" @@ -5212,26 +5204,26 @@ wasm-ast-types@^0.23.1: wasm-pack@^0.12.0: version "0.12.0" - resolved "https://registry.yarnpkg.com/wasm-pack/-/wasm-pack-0.12.0.tgz#3c360e14eecbef3c705f23bca2f9df0424f748a9" + resolved "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.12.0.tgz" integrity sha512-2MuRmArr8x0S6GAQKJtToH2tJ9rM8nJ2yy6J+ueMm2v4deKAh0uAOT2BwJlLoZ0VnELtKBDjJ17NV/RFDt/QpA== dependencies: binary-install "^1.0.1" which@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" word-wrap@^1.2.3: version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== wrap-ansi@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -5240,12 +5232,12 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== write-file-atomic@^4.0.1, write-file-atomic@^4.0.2: version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz" integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== dependencies: imurmurhash "^0.1.4" @@ -5253,12 +5245,12 @@ write-file-atomic@^4.0.1, write-file-atomic@^4.0.2: ws@^7: version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== xstream@^11.14.0: version "11.14.0" - resolved "https://registry.yarnpkg.com/xstream/-/xstream-11.14.0.tgz#2c071d26b18310523b6877e86b4e54df068a9ae5" + resolved "https://registry.npmjs.org/xstream/-/xstream-11.14.0.tgz" integrity sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw== dependencies: globalthis "^1.0.1" @@ -5266,37 +5258,37 @@ xstream@^11.14.0: xtend@~4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^5.0.5: version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^3.0.2: version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== yallist@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yargs-parser@^20.2.2: version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== yargs-parser@^21.1.1: version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@^16.1.0: version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: cliui "^7.0.2" @@ -5309,7 +5301,7 @@ yargs@^16.1.0: yargs@^17.3.1: version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1" @@ -5322,5 +5314,5 @@ yargs@^17.3.1: yocto-queue@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 4d7643f2f1fe650f6cf472da7fcd9564b4e99c49 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 28 Jun 2023 21:51:38 +0200 Subject: [PATCH 171/218] Add max borrow js helper (#149) --- .../health-computer/src/health_computer.rs | 62 ++++ packages/health-computer/src/javascript.rs | 8 + .../health-computer/tests/test_max_borrow.rs | 311 ++++++++++++++++++ .../tests/test_max_borrow_prop_test.rs | 47 +++ scripts/health/DataFetcher.ts | 9 +- scripts/health/example-node.ts | 5 +- scripts/health/example-react/src/utils.ts | 7 +- scripts/health/pkg-node/index.d.ts | 6 + scripts/health/pkg-node/index.js | 13 + scripts/health/pkg-node/index_bg.wasm | Bin 174764 -> 177904 bytes scripts/health/pkg-node/index_bg.wasm.d.ts | 1 + scripts/health/pkg-web/index.d.ts | 7 + scripts/health/pkg-web/index.js | 13 + scripts/health/pkg-web/index_bg.wasm | Bin 173840 -> 176980 bytes scripts/health/pkg-web/index_bg.wasm.d.ts | 1 + scripts/package.json | 4 +- scripts/yarn.lock | 16 +- 17 files changed, 497 insertions(+), 13 deletions(-) create mode 100644 packages/health-computer/tests/test_max_borrow.rs create mode 100644 packages/health-computer/tests/test_max_borrow_prop_test.rs diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index 410a8c63f..5ea76b779 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -131,6 +131,68 @@ impl HealthComputer { Ok(min(max_withdraw_amount, withdraw_coin.amount)) } + /// The max this account can borrow of `borrow_denom` and maintain max_ltv >= 1 + /// Note: This is an estimate. Guarantees to leave account healthy, but in edge cases, + /// due to rounding, it may be slightly too conservative. + pub fn max_borrow_amount_estimate(&self, borrow_denom: &str) -> HealthResult { + // Given the formula: + // max ltv health factor = max ltv adjusted value / debt value + // where: max ltv adjusted value = price * amount * max ltv + // The max borrow can be calculated as: + // 1 = (max ltv adjusted value + (borrow denom amount * borrow denom price * borrow denom max ltv)) / (debt value + (borrow denom amount * borrow denom price)) + // Re-arranging this to isolate max borrow amount renders: + // max_borrow_denom_amount = (max_ltv_adjusted_value - debt_value) / (borrow_denom_price * (1 - borrow_denom_max_ltv)) + let total_max_ltv_adjusted_value = + self.total_collateral_value()?.max_ltv_adjusted_collateral; + let debt_value = self.total_debt_value()?; + + let params = self + .denoms_data + .params + .get(borrow_denom) + .ok_or(MissingParams(borrow_denom.to_string()))?; + + // Zero borrowable if unhealthy or not whitelisted + if debt_value >= total_max_ltv_adjusted_value || !params.credit_manager.whitelisted { + return Ok(Uint128::zero()); + } + + let borrow_denom_max_ltv = match self.kind { + AccountKind::Default => params.max_loan_to_value, + AccountKind::HighLeveredStrategy => { + params + .credit_manager + .hls + .as_ref() + .ok_or(MissingHLSParams(borrow_denom.to_string()))? + .max_loan_to_value + } + }; + + let borrow_denom_price = self + .denoms_data + .prices + .get(borrow_denom) + .cloned() + .ok_or(MissingPrice(borrow_denom.to_string()))?; + + // The formula in fact looks like this in practice: + // hf = rounddown(roundown(amount * price) * max ltv) / debt value + // Which means re-arranging this to isolate borrow amount is an estimate, + // quite close, but never precisely right. For this reason, the - 1 below is meant + // to err on the side of being more conservative vs aggressive. + let max_borrow_amount = total_max_ltv_adjusted_value + .checked_sub(debt_value)? + .checked_sub(Uint128::one())? + .checked_div_floor( + Decimal::one() + .checked_sub(borrow_denom_max_ltv)? + .checked_mul(borrow_denom_price)?, + )?; + + Ok(max_borrow_amount) + } + fn total_debt_value(&self) -> HealthResult { let mut total = Uint128::zero(); for debt in &self.positions.debts { diff --git a/packages/health-computer/src/javascript.rs b/packages/health-computer/src/javascript.rs index f861fabeb..4b7cc7185 100644 --- a/packages/health-computer/src/javascript.rs +++ b/packages/health-computer/src/javascript.rs @@ -20,6 +20,14 @@ pub fn max_withdraw_estimate_js(health_computer: JsValue, withdraw_denom: JsValu serialize(max) } +#[wasm_bindgen] +pub fn max_borrow_estimate_js(health_computer: JsValue, borrow_denom: JsValue) -> JsValue { + let c: HealthComputer = deserialize(health_computer); + let denom: String = deserialize(borrow_denom); + let max = c.max_borrow_amount_estimate(&denom).unwrap(); + serialize(max) +} + pub fn serialize(val: T) -> JsValue { serde_wasm_bindgen::to_value(&val).unwrap() } diff --git a/packages/health-computer/tests/test_max_borrow.rs b/packages/health-computer/tests/test_max_borrow.rs new file mode 100644 index 000000000..1ca6ac220 --- /dev/null +++ b/packages/health-computer/tests/test_max_borrow.rs @@ -0,0 +1,311 @@ +use std::{collections::HashMap, str::FromStr}; + +use cosmwasm_std::{coin, Addr, Decimal, Uint128}; +use mars_params::types::{hls::HlsParams, vault::VaultConfig}; +use mars_rover::{ + adapters::vault::{ + CoinValue, Vault, VaultAmount, VaultPosition, VaultPositionAmount, VaultPositionValue, + }, + msg::query::{DebtAmount, Positions}, +}; +use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; +use mars_rover_health_types::{AccountKind, HealthError}; + +use crate::helpers::{udai_info, umars_info, ustars_info}; + +pub mod helpers; + +#[test] +fn missing_borrow_denom_price_data() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(umars.denom.clone(), umars.price)]), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.denom, + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let err: HealthError = h.max_borrow_amount_estimate(&udai.denom).unwrap_err(); + assert_eq!(err, HealthError::MissingPrice(udai.denom)); +} + +#[test] +fn missing_borrow_denom_params() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), + ]), + params: HashMap::from([(udai.denom.clone(), udai.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom, + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let err: HealthError = h.max_borrow_amount_estimate(&umars.denom).unwrap_err(); + assert_eq!(err, HealthError::MissingParams(umars.denom)); +} + +#[test] +fn cannot_borrow_when_unhealthy() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), + ]), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom.clone(), + shares: Default::default(), + amount: Uint128::new(2500), + }, + DebtAmount { + denom: umars.denom, + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let health = h.compute_health().unwrap(); + assert!(health.max_ltv_health_factor < Some(Decimal::one())); + let max_withdraw_amount = h.max_borrow_amount_estimate(&udai.denom).unwrap(); + assert_eq!(Uint128::zero(), max_withdraw_amount); +} + +#[test] +fn hls_influences_max_borrow() { + let ustars = ustars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (ustars.denom.clone(), ustars.price), + (udai.denom.clone(), udai.price), + ]), + params: HashMap::from([ + (ustars.denom.clone(), ustars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + ]), + }; + + let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); + + let vaults_data = VaultsData { + vault_values: HashMap::from([( + vault.address.clone(), + VaultPositionValue { + vault_coin: CoinValue { + denom: "leverage_vault_123".to_string(), + amount: Uint128::new(5264), + value: Uint128::new(5264), + }, + base_coin: CoinValue { + denom: ustars.denom.clone(), + amount: Default::default(), + value: Default::default(), + }, + }, + )]), + vault_configs: HashMap::from([( + vault.address.clone(), + VaultConfig { + addr: vault.address.clone(), + deposit_cap: Default::default(), + max_loan_to_value: Decimal::from_str("0.4").unwrap(), + liquidation_threshold: Decimal::from_str("0.5").unwrap(), + whitelisted: true, + hls: Some(HlsParams { + max_loan_to_value: Decimal::from_str("0.75").unwrap(), + liquidation_threshold: Decimal::from_str("0.8").unwrap(), + correlations: vec![], + }), + }, + )]), + }; + + let mut h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &ustars.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom, + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: ustars.denom.clone(), + shares: Default::default(), + amount: Uint128::new(800), + }, + ], + lends: vec![], + vaults: vec![VaultPosition { + vault, + amount: VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::new(5264))), + }], + }, + denoms_data, + vaults_data, + }; + + let max_before = h.max_borrow_amount_estimate(&ustars.denom).unwrap(); + h.kind = AccountKind::HighLeveredStrategy; + let max_after = h.max_borrow_amount_estimate(&ustars.denom).unwrap(); + assert!(max_after > max_before); +} + +#[test] +fn max_borrow_offset_good() { + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(udai.denom.clone(), udai.price)]), + params: HashMap::from([(udai.denom.clone(), udai.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &udai.denom)], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let health = h.compute_health().unwrap(); + assert!(health.max_ltv_health_factor < Some(Decimal::one())); + let max_borrow_amount = h.max_borrow_amount_estimate(&udai.denom).unwrap(); + assert_eq!(Uint128::new(6763), max_borrow_amount); +} + +#[test] +fn max_borrow_offset_margin_of_error() { + let umars = umars_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(umars.denom.clone(), umars.price)]), + params: HashMap::from([(umars.denom.clone(), umars.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom)], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let health = h.compute_health().unwrap(); + assert!(health.max_ltv_health_factor < Some(Decimal::one())); + let max_borrow_amount = h.max_borrow_amount_estimate(&umars.denom).unwrap(); + + // Normally could be 4800, but conservative offset rounding has a margin of error + assert_eq!(Uint128::new(4795), max_borrow_amount); +} diff --git a/packages/health-computer/tests/test_max_borrow_prop_test.rs b/packages/health-computer/tests/test_max_borrow_prop_test.rs new file mode 100644 index 000000000..c66d8924e --- /dev/null +++ b/packages/health-computer/tests/test_max_borrow_prop_test.rs @@ -0,0 +1,47 @@ +use cosmwasm_std::{Coin, StdResult, Uint128}; +use mars_rover::msg::query::DebtAmount; +use mars_rover_health_computer::HealthComputer; +use proptest::test_runner::{Config, TestRunner}; + +use crate::helpers::random_health_computer; + +pub mod helpers; + +#[test] +fn max_borrow_amount_renders_healthy_max_ltv() { + let config = Config::with_cases(20000); + + let mut runner = TestRunner::new(config); + runner + .run(&random_health_computer(), |h| { + let denom_to_borrow = h.denoms_data.params.keys().next().unwrap(); + let max_borrow = h.max_borrow_amount_estimate(denom_to_borrow).unwrap(); + + let health_before = h.compute_health().unwrap(); + if health_before.is_above_max_ltv() { + assert_eq!(Uint128::zero(), max_borrow); + } else { + let h_new = add_borrow(&h, denom_to_borrow, max_borrow)?; + let health_after = h_new.compute_health().unwrap(); + + // Ensure still healthy + assert!(!health_after.is_above_max_ltv()); + } + Ok(()) + }) + .unwrap(); +} + +fn add_borrow(h: &HealthComputer, denom: &str, amount: Uint128) -> StdResult { + let mut new_h = h.clone(); + new_h.positions.debts.push(DebtAmount { + denom: denom.to_string(), + shares: amount * Uint128::new(1000), + amount, + }); + new_h.positions.deposits.push(Coin { + denom: denom.to_string(), + amount, + }); + Ok(new_h) +} diff --git a/scripts/health/DataFetcher.ts b/scripts/health/DataFetcher.ts index 845e7a777..139d21024 100644 --- a/scripts/health/DataFetcher.ts +++ b/scripts/health/DataFetcher.ts @@ -16,6 +16,7 @@ export class DataFetcher { constructor( private computeHealthFn: (h: HealthComputer) => HealthResponse, private maxWithdrawFn: (h: HealthComputer, denom: string) => string, + private maxBorrowFn: (h: HealthComputer, denom: string) => string, private creditManagerAddr: string, private oracleAddr: string, private paramsAddr: string, @@ -115,6 +116,12 @@ export class DataFetcher { maxWithdrawAmount = async (accountId: string, denom: string): Promise => { const positions = await this.assembleComputer(accountId) const result = this.maxWithdrawFn(positions, denom) - return parseFloat(result) + return parseInt(result) + } + + maxBorrowAmount = async (accountId: string, denom: string): Promise => { + const positions = await this.assembleComputer(accountId) + const result = this.maxBorrowFn(positions, denom) + return parseInt(result) } } diff --git a/scripts/health/example-node.ts b/scripts/health/example-node.ts index 777288822..77e73c954 100644 --- a/scripts/health/example-node.ts +++ b/scripts/health/example-node.ts @@ -1,11 +1,12 @@ import { DataFetcher } from './DataFetcher' -import { compute_health_js, max_withdraw_estimate_js } from './pkg-node' +import { compute_health_js, max_withdraw_estimate_js, max_borrow_estimate_js } from './pkg-node' import { osmosisTestnetConfig } from '../deploy/osmosis/testnet-config' import OsmosisAddresses from '../deploy/addresses/osmo-test-5-testnet-deployer-owner.json' ;(async () => { const dataFetcher = new DataFetcher( compute_health_js, max_withdraw_estimate_js, + max_borrow_estimate_js, OsmosisAddresses.creditManager, osmosisTestnetConfig.oracle.addr, osmosisTestnetConfig.params.addr, @@ -15,4 +16,6 @@ import OsmosisAddresses from '../deploy/addresses/osmo-test-5-testnet-deployer-o console.log(health) const max_withdraw = await dataFetcher.maxWithdrawAmount('2', 'uosmo') console.log(max_withdraw) + const max_borrow = await dataFetcher.maxBorrowAmount('2', 'uosmo') + console.log(max_borrow) })() diff --git a/scripts/health/example-react/src/utils.ts b/scripts/health/example-react/src/utils.ts index 2c3ed8412..ce8d5fffe 100644 --- a/scripts/health/example-react/src/utils.ts +++ b/scripts/health/example-react/src/utils.ts @@ -1,6 +1,10 @@ import { Positions } from '../../../types/generated/mars-credit-manager/MarsCreditManager.types' -import init, { compute_health_js, max_withdraw_estimate_js } from '../../pkg-web' +import init, { + compute_health_js, + max_withdraw_estimate_js, + max_borrow_estimate_js, +} from '../../pkg-web' import { HealthResponse } from '../../../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' import { DataFetcher } from '../../DataFetcher' import { osmosisTestnetConfig } from '../../../deploy/osmosis/testnet-config' @@ -9,6 +13,7 @@ const getFetcher = (cmAddress: string) => { return new DataFetcher( compute_health_js, max_withdraw_estimate_js, + max_borrow_estimate_js, cmAddress, osmosisTestnetConfig.oracle.addr, osmosisTestnetConfig.redBank.addr, diff --git a/scripts/health/pkg-node/index.d.ts b/scripts/health/pkg-node/index.d.ts index 7b876f7c5..9d426fff2 100644 --- a/scripts/health/pkg-node/index.d.ts +++ b/scripts/health/pkg-node/index.d.ts @@ -11,3 +11,9 @@ export function compute_health_js(health_computer: any): any * @returns {any} */ export function max_withdraw_estimate_js(health_computer: any, withdraw_denom: any): any +/** + * @param {any} health_computer + * @param {any} borrow_denom + * @returns {any} + */ +export function max_borrow_estimate_js(health_computer: any, borrow_denom: any): any diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js index ea1b7513e..22cc808d4 100644 --- a/scripts/health/pkg-node/index.js +++ b/scripts/health/pkg-node/index.js @@ -226,6 +226,19 @@ module.exports.max_withdraw_estimate_js = function (health_computer, withdraw_de return takeObject(ret) } +/** + * @param {any} health_computer + * @param {any} borrow_denom + * @returns {any} + */ +module.exports.max_borrow_estimate_js = function (health_computer, borrow_denom) { + const ret = wasm.max_borrow_estimate_js( + addHeapObject(health_computer), + addHeapObject(borrow_denom), + ) + return takeObject(ret) +} + function handleError(f, args) { try { return f.apply(this, args) diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index a8d5acaa080a9cb2a35e8f8282f8ea29a905f6dd..e875fbba437f56f56b9718ce18672229efab7472 100644 GIT binary patch delta 35661 zcmc(I33wF6wtt_RC6mmAbRa-N0`v@9*f&{4NJkb?*+c~uWk(77A|MJAWHD&OXrl$l zD##+Jk$^!&4Tx9Ns6kP=$`uq;RM6<!K@T9pf^viUS&)Q3AMUT1$I-gdOC(iQ3 z-R~rLhcp%LP0x>G%*3AbC* z(%gDnoaRX;mtWJUmdC4kJVcr=jsT&#Jf3t!<6peT?e_4Ky#5TL+FnnxMw(9U1{4sk zc&)121wd45KQt{4;mE%_aaNMQZc;)*f<_4*x5w*FA}!v!%AZN`))@Z~x8~A()*Jo? zu0^)WeP78 z$LJw(lHQ{)=zTgx-_UdPk$6~a67$7t;urcC&E6!Ii*+dRU3x$~;3`xYO?nkYEp|QN z`ia78=?Fa|@?3VAuhA~CMm#HCq?MwCo~N-7y7F9Kh@tc7O?p-2(q2mV^X>R!VrNTH zD_qkWP^Am~Y%QrWH0g%+qKu3hhA_)0M=!L_RaqRrK^P)a7(tTBEvcHdE~zPGf8>x=1jE0BCU(8x*dUe!jUY6J}ta$9Cby7gJ|RN7$mt=bw0yy??UnYs`bG0@oEF`{6;-HrL?CaYjR3j z-69Ic1*-YY3k#6ZqpM(5zj=m(e!a-roKg>vj~dmiqtUM#)vGvh9jjkYgRF(s`#Is0 z)vrNATG#0A(6f4^A=ZK#E75{r%?_DNJVsaxdVz2uBkb30`hYervxH-WzOg8`W@kdV zFQ=yA`BCan)YLBR78+FaNLqKI?be}MuRGOlP49(j|DE2&sWwVKt7}F9s(nABB}jH5 zqeeT`a}@NMf>GcP0urxtYpd8u)MCths+W#%vsU z7@<;1ow2}fZk;fm=j#l>vwz)Lb+y7TJ8CT%@AI^6* zt4=TlrL+y^v_ZBwa8#s+R1k$OuzOksQK-}L9Q0fB>ZVx7gFvIGUFc6H75cB6*KfYf zZ0FOrQ^4&vk1*RNa{z@qJd6N}{(!+$3JP}Ec)PW*{!)}wtHB9+zvx1PnndfZnvGh} zPOE35i3y*P<~KY>KtEdaYNPo?w^-SYcYx-7?deG_DB>4HFg>3walEx zF3pJ3Z?XC{-{D~ninHEn+R$pzVivI4)S?Y4`n1K{zH%}GhK{^K+LQ zlOc8vpqG^VJD}v>psN;R;CyjatBc0#$$4m)Cw9D+a}36-)v4=T`pVkXwQkyZVNy?b z7Rc8z2M?5JYWjOZFSW|MHg%sD`ev(cwNp!KhA zU8?}CSw=ESIE!8=v>Ipa15T&1LMcjI)Pj_SyvXJH3H67}%&J8cas}Ox%jQ`wE>7QW z4KatHrkBjVc>1!R0-J5kHfWPon%xaCfvfA{yV=#*7~qqx9)gNXug(CUe|vSyC{Qru>#74CZ- z6(`p9#Y~j%PM~u?T7IELRr5KC{O6t8)`aBx-(U0=GWT74!gTrh}>B zx-Jo6B)lll9WMP5jO2>C+=gb(nqrnbfO>*)=3zSp4L)EU?bkMZfaAG-kLgoy0eA%* z(t2@E8d`f1Ff>E77rp>w%Dcs_a0BA@vqqQcwoN5`q#9G}y-OCjKx!3wvzXJ_&sRoBUU?$ge&?h7+ zwGS9xzIh_7FjbUQ$hc;p;wgZS%XAuIwHeT&F4M#CpdpH8+)523RPDexJz#|gwCHsa zj0-TJ1V|3#a?o&mB1g!T=)qiLxDBtqL`(;*wV;MSk1Gq(2Hk6&889_{g@`Z$h|?p{ z6~hVEyc^Q!GwaD4Ldnto_Vh^947XWky>~-ePjtK&M4k!q+DvM=`k+A`yX(EG>sd&M zeu(AZEX;6-h<3kVs#whj)}aH|^#eQH!l-kn0d+#B#s0^Y+N=`Nu7l=vC=xK-*Ch(m zr`R6Ncg9VQgHeu|o9oss>&U=f;RvgV2)1a+xDat^oMeKrY^tM^Kq;B582^<)MIG^ehE&;4bU5s+;&_lA zvLjM6Gnk)YfK(_V!9q#n%$guGcPlGE7Em2$P!1aC2sB~D=k)HI9!g~TRxuKdBo--v zwMCih5>bdY0DHq{__$CO!6=ltj(M(79QVc{YvRqBVI@8tIU4EoASq>N9>f~D2tsCD zPELE_Hcv7=a&vRJBNPfR3f1t&;04{N0;yo`dSI`HUO5iW< z8pFq5RXKxE1)na^%Va@mH5$|i!$$u%`qmj2Os-i0Fjb7)P~*bxY&4HyqtXIKkuCPD zrGxskLi5oCo+lugDu@we;-Gcoazb94RpT)JAds!(!6}`bo;8#_wK*4@(UZqMxYBe@ zF{xLgM*wF`0Vv-dznVVNx?^y&h&U`5TsO?U#M8a%4V&&vMQ3PYFox8Chy(sK6-0(0 zQi2SFK~r@Z35xcS3<)_witt?s5Zs_B%Y-!M3YRJ!eW#5Jg_W38m9q$h^z+#DL^wx8 zIES1O54r#d<)D6a|7AO$8^j0!grx&_Bv%o|%O3mOvM|G-dsNdnx?;Ga3?&d0QIsHY zgjXz4SV}k)fl0svQt=Io@;FFsjk(0_P#+QctOztrTsMShz?npCDcbQ?mZF1hZP`K5 z7($?&(Y{cZTku~qYUu+2EU|vNb$yEkdH6hwFz4d8S?&b#IVBelPT@C_t$nvW)qEF! z7di?1IlwUtRT3L0Z%qeE_?@E*pj5D$jpx4GE~?B~EPC&2G0SV?S(BN{xB4z3v8 zHZwY+p)&J?m`k{Ct#)@bWa03?;t%CWi10YL)ZHi1X8He~VPg5kAHy(3U)?c8v^>fR zb=Ek;)!5ZPWZ?U-ri`pKeJiK5$}wsi8JwCm$4G;zGkJJwQz$(TFjLh0l~|W}3MG zdGv!tzm4eZ_NvrvR;N3IX$sv!Xw|5yghFXXHMR)T^{v*lI~&keYx$kMS}62#0aGtq z1I{W~uH_h^ir_H>4Lev=<*o!*imKuzR^;MMkB!c5tJ%12nReZ5>yqiV6Sg75=Jc`W zfN4ED?x1CjJ6dP$R>TCI7(kh2+?b3ANWu7ZFq7L%coG)LdlPzP?BtQ=?F{*s#s3*D z%y4cPxgie>(AE=c!vO6!u`vwAdnPtT#-}H4N5;XE7W+SjB(jac1gm7)^VXA-Z?0Kk zAo>)u@RS&*@3eeV>cRxhoN}s~s?Lq-SmEM&P)nn!25Pwm&pp=IsrNLGEd_nElvo?K zOJZd0-0n#3q-oWWyTLTh-E-O`*qJX*t4j27(Oc8`WgWSDIn1jWGqTvO>u;n$__1|e zXzicz0IaI(XWjw34$3dtZZ*e0al(3aW{p&KtC=tqO_%LldwC&9>4Kl@)XY>Q!nvmW zHaw&H%rZ{&!h@7C>sIz4&Af+wE8Fh52~ROQ8!)@tzYu9~4y1>g4iDU7LAsv8sCF)Rt*9Qy{ zb*Uf-4~ygI+FsOu&SZAU?45gx-nX6$&jn4I=U$Nrnq(PJ>oEL4l8L!Yl3#MKV2_pE za?Nh}{p_3xz|@5K!~}61Ss)vDGU|#PI3JyirAjaW_fCV2TCUYS>vt}>67NmP)VK+QIi+WuJ>l0IX z&=;tx2Et!hpAIq%4ErZSo+4}UqDd(8w?!rOK}Rp<5fp@}!f6=d+)S?sHLZU=@DC7b zL*7;Gzao$b%10~5yTIR9FxeE*Dx40P+|;ab5uZcN#f@#BL-)n)QSrTtr(xjyxVT38 zCOFGX3b_pr*oUbLOTyd+`s$l3<3R(k=7Txto^20a%`AH((uH|QvFx7@mZ5*%d*}-H zMhg0@(@X03Hgm_g;lF5>Z=iqs z+Ao=d3U(~1?CZ$OM_%@Ie77VMtlBI;%~#5P4iEesee&yAH$1{#($f5Etlu9AsfwF{ zcGni{fxw7+v=Hsy_2@P5SkYs(AOnJhY8`ZLzHrN5-+Z$s4`5DDfo_j6z^1iHS(+Wq)4$bf-Q z45a^9o1VCa{%Msz(S`m~)Lc$rKiHGY*y~nv`9KJTam&Ym2p=rJQU`@6qc0PI5GuhK zsJ&tlNU&i=tP6@!$YnQ2Ld4nP!hm1OG3fE@x6Zh&cR#UkJsPXh_9(8xGxrsIwy|bpRK>iEs`oS4#mOhRe z4!|nbk6A;W>cCDb4?C?ka?RZ7>z@jO(r-Lf*Y&>8tVg?~!M{f98=-`Q2UD!8*56&n z5gM52QVd6EfEza0sBgE**1rs-@;7v3Nze+~m%wxjoDObaoNjr#6*}+1r!zY7z~|ut zw?78_0d_M*+;JuhYY4!2vy5ZmxTau&;R}MXdC*t|dTs06)7{}gGB9Ku?$lGJB-`>H!kKMilcBIlN>(}iaX`j`qD4j~JbwwRHDM2Oe;2@GdEovq5 zU$z1}Oa%JwNT*}gYdaKNoPyiWK@YNd{Z?*qP0HsmK>O@$CCXp2N_M1Kffq9ou@?~s zq7ht?OBJxe8t|ejPpDvlwedyHfAq!lz}a1_!WrP!1J7E)-6@DpFTRfsS<8xBDIf&Aq214#YwYwHK3K&+Fh-yAPH_vDE=>?BVMFDF8mNO^Y_g-?V5T+)wBCUq_Z9(wga6q;Kr~$ zbRx?IN;9Wuj?>IKx-ZS~wV46toLT``Cz^imIPM3>6B5C{52BP~Dvb6q55ODHI}zo9 zD6?$)zTDvfxWAYP2Hf)cy|t433Oed&r5N|K%d}>`ULC%IhhHBMRwfyYPIoql)!5|= z`D`l<#sP=*P&^Xcb{wm*4qR%;iG%GaIGYDx-Uwe5(5*2uS~r9{(WAg%A-Uf>jGO@C)69k-g4&ZLB*r%K--X#ID+ zcMj9===WRkT6rtMPH_>@G_JLNc)trKi(q2M;zw{P~0PFwf?Gz^iGiKDZsL#(#XU*cIL$ zN4{`9_;kQ6p8Dt$!`;DU@QO(XS1MkjgG@Z;~-qfm#tx?lgZh^wF@gM3{uE% zz-;E#H*`lRE(Yi@C!*9cexuj}40IBl<1sgfne=KkuN&I#Y3#bIKMO$*o1&q3ys^|6 z^1_Xvs$~C1C;_p#sKOtO1s8?_h|MGRY$!-BYH|nHHHh6sIW{t}h%>^hWruAwTVWn& z1R~kZ!`ucVAsQ}Yj){c0@M|jC5n{?S5XJyv1Vg+kj35?XxfLPMii2QL z53ALoFjo-*Q4p(|9|EB=VR)*1-~~=eV5%_y((Z!2@6#VArdB3ai50aPgSJ5yI~aR$ z4we|a+-I5+e>^*>j)8Ou#^Ljb?K|*0~ zcsPqgKb1#ydL$FtACoDyA`>X6N=3&436@KYMVJXusE|=nF{I_s&C~&#q`(kydRO)k zBkBO6+-R2a%ENz?!@d9rIWvWeLcekQQMv|Ms%VY?Hy(q$J`2oPF4#pAK-J3p`*%3k zbWcTzXeyrE@tVGr*AP7Bf{KX!I1CSvF~)==>;8`=O}A=&@*UJ2&*4G%zWwl7@1f_Bb9d2g|Fp~HiD5ApqkSz#y9=Y8r@OVJe^oU3UujgULVS@Ko@RJ+ zqT0gRkU_KZ2qUm2+#&^Tq z>f`i}N9Y}E`1kFt=3iH(Tdl0G^O14wPn)di-_$|7|2);N=*w@?SjwL}*$zo;e{^pihCusfzjvExsLo%)EB z*Z#4eY~a$RR$rV~Z4of%AMS?VvnfXlF7 z(5j--zie^Q=a&3^44&tIpM&Rpf83Afg+Er%QESDyR`Bn>bFL>9SXKWV4$4jcvmM=R zJ@aR?hK>kPO0QtahtZ-mU1B?UIzMLVtE_W>e%^c)JJ+-ze40HHd18LN+6+hgW~%>~a=SeY{o?U60pyb|^_u4}@+K)RtBh zFBar+(INSWhgx?%grahqX`1<3_W|B-AiRiZ(j z_s~x7e#Gm$V3 zsUbDDF-~EprGr&QE$(Q}rhwrCj}|JIRH3W8L&}!2&+Y6SJV`D#CkFW+o$4h*r#BkNZpm~Q`-sMQLdn#d`O0cWXj)KXK6 zx$;5{!fI}DotktnrGb-qkp;ebUVzcnc274v(MRR>R2tl1$2%DO*d~^v55x#wS_*z> zi*RO+d4Rvm@zA%(mTA}nzFvNjMy>2u4VhVsx>Ko~Pzzjrvs_w>7SLvymQI88U8|6% zoR^uP2nxMK=B87Vnq`QE%bB3xnt7}7eF6^@y*Hh1th1SQ#a673VFNETw@mDj^I-K6 zYmsh!vuv9|9opqHOab}=3MY(41a!`zpJN*VdLfi>)^}BqO$IMC!O?O}2HiyG)Pgs)7-HD1{k*#WDPx<5W=i2ldZIRpS(DLg2(N7nsv(}HT5A%H}wWRki9#)r@ zlb5&U>F>x328jBOyf;YAYJjwaU9EoZGDkFcAsSu@Qmg71=PpPl-dSPqxj;kCOJvxE zTHH=8Sc-|(@_vZ03=_{?iaEO$%j*M$|N&!Fa*YWkQJUc%ejVtZ5xI zmLJc{IgO}i%dLpZ)U@_04jWcB#KNqs2ne%KXW)rFQPYK)hh=c3svm>CZma5LTY*^6 zTnha3gR*)vG-RQ?sTsAb#nfR{{4#yAx*7FOX6%6`k0Umy^&awUGxYv?8Ej4sd*`90 z%{7f}8W#qGnOi|YMc%W(All32(EyiNCNLTB1e~VWrl2_;io2N&9DS!8*8*HKO+MR# zYSSS3P79h7&fS6*=*SnyF$?gmOWs8fr2)D&W|shl^~ak5Qqu-0$2Wsui)Spzn+%d< zgPhoCgCtZ2d5J-I_ckhufW-U5m5SNIFb*;i7`3gg0-2Qz!s}5n#bDo1EHVWQ5~vJ< zCCyln^P2#|ClbOj#Xt{;h53PDEI=y5}4X+f~nz+ z%TCa@%EoPJ4Mx^mZRw{=mkZ@PSJ1!za=lP~+>Y8+TlOZacRnDC?P_drKk%lk)1K-i zm+b>S8aps?T0?(~H02RL^+wd|E+@CAnzT>mwWp?4&qm`>S~IjldFzw+5sO%1Vl!)f zymJ2`zi&@hAd}I7x(EdC=s?X8%6*pV6}FMy5| zDF+16Ka*c~q&jJ~+n#l))2tnd$m=FqyA##Db|)4HF{i`T9mxzm3SL~yE-qdV;AzI? zbL_Mp17N1p0|$$E$M^rRc>?zsS43lbWNChzp(%_Wf3pTHk<9%XNnvwBj7 zcbWaYq9^t6e(^1;J}$rNN$qKy%;-fe=!m?o7c9IIIk^{&YC04>#SS)e8$$}6!`PI_ zs*)S(MzRJEK70F8&7<-{FS>&sm3Lf2KhhG}w>PEHm-4RO=-Z1UU%VE|#uIYieu4)k`czW{ys%HWESuU}6SU}CoJLqWU-_MuzwdbSU-P5MzE z%B-IIE-++lM(b3$r9a(TV;M|xEFEKG8zwTi zv@kpXFVhClE$uI#hV7$_7@*BOWY*QTub5!*<^l9JQK6hRklK0qzyb`Y=LXWcWOYm- zV#Np;u5#Folu>gDugtUFjx{cfI_G#^skCmSZ2C-od?VdWKg#QGLa+Z=yy+(TloEN9 zE}Nu+w^hp}gQydg%L9Yxs@_Ung+gJgtMHFQ{ltz`MPK{P8s zeV`owHr0~WZPYOlY6p9R%>X=QN9Ez$Fwq~9zuZRurlIn?A#@zC_im@hQXl*kebUVQ zl3)AD-1r;b`&{}sIqMFppLz-d1a7}QKjXDVn4g`&`+1>{E8cMjEJ!G6<-_Q{#E4s% z+p<*NGn^Xe(I_Y_D)zbIbZumg@v^@^)_EiK5wty6(Zf(qOot(0F*CDZJ1;op_y~IO zZ-DI{NsrOWC&pB;`lF-h@yapdM$^(uVy=<-W5BMJvev$nX4i?BI_&diTLhhp5tyQe zq5g#2c_%gX+yEEV3-YTw(NSAu>RnW)>0X%V5Dg}ctKt%F_ULkqp-{P~N-QSky0EUffNO(Ug*N6cl2?zV8|vr2#dYgwv&moJT_o9Up`bEqpFm;G{R9<7uI5lXU!6o>|SWaQMB{c|W60*uqD zU6iKzpCLKGq|{>Hc&b8``|q|1RLjd)n+5V-{yxJ};*5N90yXcj@@>Y9SBq>MeGyV) zde*K%yO`n_To}K6&_)G8LU~~V)v4_eRk1^aj$n#pVz}bY6Uj%>p}`8lGqT?#Y7mWr z2tw4nNfqM)du}fp7sn^T?ud*f)(oD3HA)puP#D_#x0S$a|17AvMfRUeJ*)k;gWKc= zwS76+55OB%QY5!brrR^tvonlsRn;QY{W98ADA)%!oMX$KuiaLJprsccxCn$_Lz=MuI-k@Z)G_qFKf^ z(L{M{8a$id$e;d!IqGZKY&sNi%es!k8f8Pf@yvE=IM)RBHJ{$x7b4!i)Gmtb@R z@$(X})k-#*fq8y|+&P18Qb)X5Kf!K9p#Sy?Aca%P(wIqU6^FTz1drG;PIT)VW&fEF z3O~!^GpReD9n4D*Sw@#==^bYg&LYdVW>M_vK_<>TE`YamGW(-Qx4DvIu%S)XyNB+N zzlZ%@c?m(6%*uuEy|A4;^4)tV{gy{D(}}s}GH?(k1zyNBH{0LAZsuD1i|LO8lrXlp z3I(aQ3M)8Xwa#^gy75{cKFxH6+IN8Y+_O z9;DmAWq&?MvFDVb11Txs>=~Br9wPgovK;jgd{cwuBM(7eJ0ZV&2-Mgl{rT{WosgON z%DK@qpXz!yQ7~Y*t=3mplXLUwdha1#HW(z!@@a=%Yc;v-VR)T>mPa2Zb>9SJPiwnY z$eun+=ut?Aqf6+DI@sdrg5cq1xY_H^%tPj5yz3ELJ!R`hC@tomiIp6Wv&VACBh=Z4 zJ)a#6U(hXw&w^uZ`y(_hvIG3EpWArY!#lub#{wDzwk;@t4ZL2yQ2?px0!QBr+5_c7{APs?{71H;aj)hwC?fP9PcK$+@GseOOmN6Nk&tfP5M zX=AG5;Jg9fyCT@DICyJth7*@nFr1I~(o!HEk{sd-Qwi_wEs{?z#jHI@{=Ag#h%{iA za$q?P82LETzm&Tl$JksiPd`o_(0~R{;3koWipmcIWWO_Tuo<@RZPYDK~Dhj|-94b#Zy&(M9~jN7)-s2FkWHaD=iet#=v z*0yIVUuW%bJXYf(Gu1LRQ}vdUU!cbVW^9Suw?UDI8{}DAK#qSF?DeJm`q}@{!~~D- zMeUO2a}hZ>70mnjbLg5<`O9+=iPuyvcpm&*Ap5*bt>uD3H4C9xLoo}lMPqorRkJYJ zZEN~Mv~4ccHug>1voMQ$x8A!LZSzv?BJvp3{J|u{i=(cX1*-;<{gJLLlGb+W4PO7( zcFOMZ_Z5s~mvo!Ftq44!REZRsUj+4Qj@(fM33$8wqlo$^UGHEIndIkwyLAT`>?`^G z4x7sQPDSMhcm9v4%V+#|o*i5dD;9OoFB<~FDfc8e^F8S+>6jZZ!d0AOhd5p zEg#s0jviE;@e(b;Y~gXUo9bb5vM<8}TO3R6Mx+*hHVi}I*&tQhY z8i9HRN{;?8YlXZ<$zjNQ z(%k~s!+jJ|&EUl_mrXTnzhd8oS+bZXT6?x+$dn)}(byTqyIo1wtYP_T1~ zhuy*+r8LC8$A+;#DyNrH``U0zuu*uy-YU*r!X)NL zD!dYJmSWP{B5S;h72O|YuXk~A*|W0!d(bDJmHXeLe7pv{kEPQ>x#@lA(#PfT_o=1V zwl-yI8Fh7iB|>t_K{AT(DWh?eSm}tQ{OKUo3Kzot&wUp;E~?`T5#TZ4wU6Mx!!n46 zS3pB<^Pq;^*5E!68}=$RR^{A*KLx%W<>@omZbf{|sZ3Z~Ww37dqED3Tk2hU`D-4YX zBBt1Ck&@74Ocvloc!^eh05dSUPv{~O`P~m_v)J~He6W)U%4HwI{5&q7`;c1mMMPi^ zdnvo2OgscLb&G6th}!i30H-K$aaZ=tieI%DRVOiQ#hzbN5ulw0!~OUm#7!` z77I4Z@U5?wTMnV$?v`&KqVzDkP}w&k*eS!~>crPJ(+TA)fwApY2AdD7_lX)do?$f( z1LtmxL^VMCQ#IRNNcFLg$96a=+N5ABBzFYyrbx93g3P4{A`GoM4GbnneMI%Wdk_z% zU-A*)@=N4DK7zuwPoDpXa=;^aT`4zwOfB(!=wqaukbzHN%kPt2KcTC5Z5pNl>-oTw z#mYmUP`bmZYT>c)6BD=+7IIrdXBQq=ATI4NvFsNA1IPB~1yBg3(x9RaM{btAaIRcG#pv3F?O8BaXSbSAPEWRotro<;c$2N+Ea^dF~dF$n8 zpTj9_@Bc$@`I4O78+`WrsH}378r46bTvY5v+orxQa-+SYdhV8^6ij$e1R)L3^7rI| zqtvgpasgw&A(G`P=3a@>Pd~FZB2Ij44p523& zA9;>(c*!eH2>v@2QO>dUdZoTh4n9ViZ3{Rm(Oku^cmsFr<(QlKdlx@={jdNM*DSF^ zJSdQ{=or=eUuBH{f|_F=lkE5fH5SK7Zu5XE zl76X3>wFyQ!g{&;IJK^S4ivTBmZj<|6*#NW!u~XKL8+p5(g~^+in!Qq;<3PH3NxYA z1O`GEUw48gQU@O67>7Lj)-qCAbh)y-G z_d_Mv$VghyurF%@KIQVEZ=iskE%uzGyC|{z7)nVqPkjm84$9fzLLV6=mwij~2aF2D z`OV@oBxjh*R5E*^H{;8!ZE{Jh1bvh4?5AoWi6i^4^ozo~FTxzy1kS)16Zl#m}FHr(uB8p0W?Qxs2}#ZeVTm zSTthkK}2Mj<KFH zrgQW|dVi-coc+(mr+>$~8e1^upzdNJ_Z${-OClv2TtFFz)Us}EqaH8ha^x`;DgSwn zGHWW#8Ne)Idew61B~$;T>u8J|^(S2wJPB57FP8d@I%a_{$M#%k2tWob(Xf%JWuKLgB*UIswZ&=)-l1=3i2n8?4EfeqRM@kjWG|JXPA*2m@B_>2yF1O zk&RaIyXP?u+c1|0o145NMU8DD%so?4Pl}-_i#v&3)4VO5Eo0B3BCC-F_;FgBJMr0$ICIiY;IV~ z5zWoANCdF2G$0svFjxs*c7_2C|Kvt;m=1@P^B_`8>G0sOXoSP1Wzh(Sk4scUae)^a zgmk}{P%3tC| zD{mR&dt5e45QSi@eqQC~ZnO4PSsUScx=+(FlilXQL4gc}p-# z^@GJ`qS)xhU_6;D*qxhPm0!i3tBN*WyeFiJ5iT^WEM3&X>-%)k#4O~IuSR5bzfnKJ zib}X8gF^};$>54)b1jDyzeFK-%bPPqOJ9kTBF|S5S;g;WU=hz38EYJOWpPR!K~%$$ z9GC*?MCw9$3PO&lE3)m8z}2I|m_uXezQ`!KWbiU@t!T3Rwyx-Sf|K_0Z6@Piy;h`RKX#FYYFvTj5S)})nrl^U&sZH_2< ziXPE4g?bd5ZSt%k9>(_5MM2Sm@ZK2`pHbS%5{!~$^I3lJ33({IYV8LKZ+$T$kn3IiYc{}O-Y@eTh?XMnZTU(AQL_d%4>6yCYd$hleX7GX)*+&ob z=hd5IUc)I?9$)$ce5RH^7LorI0R!32#jAFeb8*ZbKKqIyp^E3?_=ro^Ya|BN|BD4} zj5-kC1RA`iy`mBJ>HRIp%uLamZjr5U>v7EC4Ha%*K6gB}?GX-h_?u}Ws}hn>0V}m^ zQKV%L$WJpx|Ax2tJM;0+BewAn#0^{cQY+KuF?=ztaNI)nZ!BK+MeI{qt%(?1b*jG} zS^VH4d~nRr%%w7~sc2MDWpPuH-Ei}1?tcAhwRL&}2i*GA{<;?SAfQ$Fu2b8$zv@<21MMSEkql@wrYti{ba9}qHaf4zTi07WVV{xSsRTE7)AL? zY-4%o-4cYEee)^A2%t#btOIW8BUiT+4V#Y)#lhpR4l(@YS!e;BPHm}^KeZHXM_X#I zoGWsjD0aE%6-V3Em7gTCV*fKLj~mXx{1{t=jb$Fu!c;1 zDsq4%$O=#fMrkm`r$Pp)49aFwnA$f9xx)5=a^^j|px%itk#!L^(D0FrA|Vs_&&@Na zjUVbJM$;Rql*4?_o8jeS6LWDzDR5QC?fG1ZeUzQ;G~3${kl(Zx4K6<2?=>T*`=f~P zJ(~_iV%z65)80;q<17dRvqU&)T-f2rn+re%4^vy1vrOEgPTHMo7ETJjV*Gc4Je;{>+5_eqg z?IgOv^A{5mc|WrMseLjd#U9yqC=-O&r^>yZMHlQaJl|Qg2uD(x*D!nOLlrFc64_?QoTCIShQ=_}tEr!%9 zL2hs$u`U`5xtJjn%=xe2g>g`!*x;_0%8=*643l_SP%NTyp}Mn~em6rVnQ!rnw}r#G zaEQN8@Wa9U6u$K*NnQRKgLxr?TppjWx;nF>kxw(I|FZaCCwy!pmouc#T*oiS(AY+@ z_>IAQkqzm1gSal+sl)UMS0WHl$1+E3-c#H!Sis{KyD($(UYr#oCKf@(3BF4Gbp*w5 z&qC96Ag@se6_{U8OC=_7Ky5)BWr6~RtXc_jvx1a=b`>}FUHJ@>oi1OCZ{DgTwaN*(D>sBpcaJUK)Rn<1dGa`|_B$ z_hG@_PAa>!w7-L-v5z|ra}X?#3YzQ@{sc%gi2NyXWOvaXbKi>YVu)udmOnT2ThnHW$K^>8G-mZ7Ug zlQ+MKAoObodg*oqE6Uo)Xr)F|U*%v$8y0Osu%Zo1IklqX6Iisv_CTlPusLrB!WBh- z!qBxN$!1<@ml(0k zIX5kL#&1bwI}&Sr9W zXKSe|YY?G`s9JS-KB~@q#N3l%a}U0B_2qZ?C;>$APYDGSA5}A|NBF3M8Ru9JO{mhj zf~687EQRwg{wWxO7)(5(_;U9sLshw$p{n8Mm%vcXF~Kr4TH{IQF&T(*lSgrr8f97I zRFmG8&kPXt!)i!I=+8Se@Vgiq&acA01-Y2z_}C9KPz=jChoWOyjt?X-l*4j3XmUB0 zyM*Jy7qc9AL*tEteDw)-ASvF&WoKMAGqa1im)H6e8IDhWItRM>z^B49h6|idt_pCq ze>1b96rhQH0)`&8`5kQUlx?s+4Nl3_;@fL%T7pjuzIn<8qH}bhy#`PoN8Cm=z?hR9 zR2?Lu#qnUmMly}}FWO_1*{_0@7=sSaMu^EIXO?C#hnUQ&_V~s2f%ln-RngyRvX1V(|$+riJrh|u|%OOhqD7vbVaN@!hjg5 zxLJ!wA;W`^xYEn+KBF3bTA{nzM*&V$m?on*CILbizFd)hCj(^R_ZwXH&j`Vv#l?aN za1j~&08EJZ;!94kg6b^N@N*I8O;vFXTkmevl>R4~Q= zEl-3m%M`eqTeAY%Wt$Ji^dCEu)pHT}d5820~yjW^dKHPwbP#EHIpo z;E70eHQJvkMuD}Q@@y918?pYI0-+xKUtkvo@jp>z!R4)V9(Qx?7@3L8I_}14&{6yYNsq1prXBssactvNyEtS?0j_U#}_r;e^ZR zn!f)=*GzD9%{XTvLWI#yHOzf_bEREjB>DP-RBGZ@a-M%c%doj60_sfM@MPE{+?MV5 z{Y4hG$(Yxoil5KUiYO&CaXDQa^uENX-gk9{;)fq@;R)Mk+KL}5TejjCaAhgfSc?fv z34x?g{HhvN7NQ&|euEm1V~3)@r#QnepQXCO7X+o zs=zT(d(&}5fh}QN%M8UYJ_kXi`2D>(h{>lU5R(NzN(Y7m1r@Pu(UxdWaD2#*H3BtV zvP86{DGyKr44@A5rgCRtpV}VhmRBp>?WxsJc<(W&;mQMzA(m^#IvC z>G;u^3cXMRu#dGDcNnricA8^ucw4?T5FTq%yrtuJ0(lgA@X8wJhk9vGVQ?ETHBw-( zFkdK(hY|K67GVWs^A_mrvsFIeV2p!gt6;Xv@S3d>222BoPMFOM_HKj8P~xRbc4t!m zL`^|~P!TSJ>;adtF33C*Wv}=O_OefgC1ukbBWZpJ>ZH1e0LLwCR>1s+yCR{am}=H$ zHk>l4S_mTnd18s$b z*@U^pQA5;>1I_NQ)Cr}`vGzkY(h-HITwdKz)QKR$3~wW$YIjs0uxmx_Hb#-!@$)up z$|$`OsvYwm&%O9bT_X;>7U4|3E>UB~^ebb=Z!@NdOj~hnB8Dk9hpB;b*(`-}UdRJU;N`=K4W7?FnM`PIS(M8Qg4s3{)ce|PEtY}nrBs8{T-Ib z7wTVKuGC%q+GQHBwno zjL|S~p{nx! zLP5Q>z?kyXd>WlPFOOZ=?pNkdCafZ540;)Hv~vhRy}J5>S9PiI z`}x%VgSU&pbq{mdNlEbVd$1Y;dIT4$%ku2K(J1g4nY?Bx(QB91liBW$0ztM7jxUH5)CRs2TjA2h-H`^vS_I2nR+QKOFnFU zOCBZ~V0&?gVPY`$AQcP~EraDi6W8J4#}_m#?j9jl<7nS4BSkxG zg?nVAsE%u4<caQ-NgOO72aYNth$GdX!}O!Au1qHabs*)<6^F{4t_sEv^pa)WPLg=760y z18;0t)aT05G2$K^+30yEmQ%LKId}yYFC=hHZf=R@UswFvouWs;V`!R|DKE?t-Q+dX zMR#f_AD%A$NYn$m)&xoE_`5NCaF@%c%*Rt1SGmP{N0YfTk$ste>ci+@*ol{*=@0?8Qum+E`H+ZnbYr?JldEseCpWY6Q^tY5ub$k zyC;sF9*I1HxB%itPrQ3VIlkTao-ll}b`IYzd`}xa)Bm>Z%Ce-C9>s^siL|} ze+YcjO16DS+?((t0IH)TcYV3*AyEeh_dj_^Tn&~>&lmNAdG&3UTMP)sHxkZ9<#jkL zhvtj<@y#08U@hgrTv1J)&lg$LUv_&~42nOAq+~#r$~6y*ahOQ_OGLlqRSk75h}i4# z$K|e)3Gc->V-^YjhVL6=!oTC2xiykb=wcR;k#GXSvE}(M3J2`)$Y_SdivX%z6uu7O zx-k`uMmQFMDG1k(NuPnRjd1bdN5y@j%Gdt{aXncp-@D)jIg3Xu75l{4+?Kl50=eek zkJ%s+-hyvNKN5cCqVU#>!rLwiKYLO5xoF3DAF1 zvJrT*G#rM%zkSSnH2Je&1ER bYNH6dlCK28>)`Kf8(sSVf1uDaBK-dVX2ycd delta 32783 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 number readonly max_withdraw_estimate_js: (a: number, b: number) => number + readonly max_borrow_estimate_js: (a: number, b: number) => number readonly allocate: (a: number) => number readonly deallocate: (a: number) => void readonly requires_stargate: () => void diff --git a/scripts/health/pkg-web/index.js b/scripts/health/pkg-web/index.js index 0752f1347..34c34d94c 100644 --- a/scripts/health/pkg-web/index.js +++ b/scripts/health/pkg-web/index.js @@ -239,6 +239,19 @@ export function max_withdraw_estimate_js(health_computer, withdraw_denom) { return takeObject(ret) } +/** + * @param {any} health_computer + * @param {any} borrow_denom + * @returns {any} + */ +export function max_borrow_estimate_js(health_computer, borrow_denom) { + const ret = wasm.max_borrow_estimate_js( + addHeapObject(health_computer), + addHeapObject(borrow_denom), + ) + return takeObject(ret) +} + function handleError(f, args) { try { return f.apply(this, args) diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm index de717ce777589df8cf9eec39ea2e5bd0b7c79ac1..a581775df9f276f70f59b65b4dcb2f38ebd64435 100644 GIT binary patch delta 35265 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 diff --git a/scripts/health/pkg-web/index_bg.wasm.d.ts b/scripts/health/pkg-web/index_bg.wasm.d.ts index b8369fbcd..da0075780 100644 --- a/scripts/health/pkg-web/index_bg.wasm.d.ts +++ b/scripts/health/pkg-web/index_bg.wasm.d.ts @@ -3,6 +3,7 @@ export const memory: WebAssembly.Memory export function compute_health_js(a: number): number export function max_withdraw_estimate_js(a: number, b: number): number +export function max_borrow_estimate_js(a: number, b: number): number export function allocate(a: number): number export function deallocate(a: number): void export function requires_stargate(): void diff --git a/scripts/package.json b/scripts/package.json index 6f35456b8..b8a2e870d 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -30,7 +30,7 @@ "long": "^5.2.3", "prepend-file": "^2.0.1", "simple-git": "^3.19.1", - "wasm-pack": "^0.12.0" + "wasm-pack": "^0.12.1" }, "devDependencies": { "@babel/preset-env": "^7.22.5", @@ -42,6 +42,6 @@ "eslint-config-prettier": "^8.8.0", "jest": "^29.5.0", "prettier": "^2.8.8", - "typescript": "^5.1.3" + "typescript": "^5.1.5" } } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 9930d0db1..cc300a827 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -5109,10 +5109,10 @@ type@^2.7.2: resolved "https://registry.npmjs.org/type/-/type-2.7.2.tgz" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== -typescript@^5.1.3: - version "5.1.3" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz" - integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== +typescript@^5.1.5: + version "5.1.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.5.tgz#a3ae755082488b6046fe64345d293ef26af08671" + integrity sha512-FOH+WN/DQjUvN6WgW+c4Ml3yi0PH+a/8q+kNIfRehv1wLhWONedw85iu+vQ39Wp49IzTJEsZ2lyLXpBF7mkF1g== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" @@ -5202,10 +5202,10 @@ wasm-ast-types@^0.23.1: case "1.6.3" deepmerge "4.2.2" -wasm-pack@^0.12.0: - version "0.12.0" - resolved "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.12.0.tgz" - integrity sha512-2MuRmArr8x0S6GAQKJtToH2tJ9rM8nJ2yy6J+ueMm2v4deKAh0uAOT2BwJlLoZ0VnELtKBDjJ17NV/RFDt/QpA== +wasm-pack@^0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/wasm-pack/-/wasm-pack-0.12.1.tgz#974c1fbbf5b65c9e135e0d1fba3a97de1a21a489" + integrity sha512-dIyKWUumPFsGohdndZjDXRFaokUT/kQS+SavbbiXVAvA/eN4riX5QNdB6AhXQx37zNxluxQkuixZUgJ8adKjOg== dependencies: binary-install "^1.0.1" From 8e896cd1524f789b6904e436e5d1a91123b5fbb5 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 3 Jul 2023 08:15:06 +0200 Subject: [PATCH 172/218] More precise handling of balance updates (#150) more precise handling of balance updates --- Makefile.toml | 4 +- contracts/credit-manager/src/execute.rs | 3 +- contracts/credit-manager/src/swap.rs | 11 ++- .../src/update_coin_balances.rs | 70 ++++++++++------ contracts/credit-manager/src/utils.rs | 13 ++- contracts/credit-manager/src/vault/exit.rs | 6 +- .../credit-manager/src/vault/exit_unlocked.rs | 6 +- contracts/credit-manager/src/zap.rs | 12 ++- .../tests/test_coin_balances.rs | 80 ++++++++++++++++--- packages/rover/src/error.rs | 9 +++ packages/rover/src/msg/execute.rs | 9 +++ .../mars-credit-manager.json | 16 ++++ .../MarsCreditManager.client.ts | 1 + .../MarsCreditManager.message-composer.ts | 1 + .../MarsCreditManager.react-query.ts | 1 + .../MarsCreditManager.types.ts | 2 + 16 files changed, 196 insertions(+), 48 deletions(-) diff --git a/Makefile.toml b/Makefile.toml index 44c20ef17..f6fe6bc0c 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -17,9 +17,9 @@ args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked"] [tasks.rust-optimizer] script = """ if [[ $(arch) == "arm64" ]]; then - image="cosmwasm/workspace-optimizer-arm64:0.12.13" + image="cosmwasm/workspace-optimizer-arm64:0.13.0" else - image="cosmwasm/workspace-optimizer:0.12.13" + image="cosmwasm/workspace-optimizer:0.13.0" fi docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 894c6cd92..8cf34147a 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -340,7 +340,8 @@ pub fn execute_callback( CallbackMsg::UpdateCoinBalance { account_id, previous_balance, - } => update_coin_balance(deps, env, &account_id, &previous_balance), + change, + } => update_coin_balance(deps, env, &account_id, previous_balance, change), CallbackMsg::UpdateCoinBalanceAfterVaultLiquidation { account_id, previous_balance, diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs index f8f592bff..a11d5d5d1 100644 --- a/contracts/credit-manager/src/swap.rs +++ b/contracts/credit-manager/src/swap.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Coin, Decimal, DepsMut, Env, Response, Uint128}; use mars_rover::{ error::{ContractError, ContractResult}, - msg::execute::{ActionAmount, ActionCoin}, + msg::execute::{ActionAmount, ActionCoin, ChangeExpected}, }; use crate::{ @@ -36,8 +36,13 @@ pub fn swap_exact_in( decrement_coin_balance(deps.storage, account_id, &coin_in_to_trade)?; // Updates coin balances for account after the swap has taken place - let update_coin_balance_msg = - update_balance_msg(&deps.querier, &env.contract.address, account_id, denom_out)?; + let update_coin_balance_msg = update_balance_msg( + &deps.querier, + &env.contract.address, + account_id, + denom_out, + ChangeExpected::Increase, + )?; let swapper = SWAPPER.load(deps.storage)?; diff --git a/contracts/credit-manager/src/update_coin_balances.rs b/contracts/credit-manager/src/update_coin_balances.rs index 7b621c93c..24c5ecc58 100644 --- a/contracts/credit-manager/src/update_coin_balances.rs +++ b/contracts/credit-manager/src/update_coin_balances.rs @@ -2,7 +2,10 @@ use cosmwasm_std::{ Addr, BalanceResponse, BankQuery, Coin, Decimal, DepsMut, Env, QuerierWrapper, QueryRequest, Response, StdResult, }; -use mars_rover::error::ContractResult; +use mars_rover::{ + error::{ContractError::BalanceChange, ContractResult}, + msg::execute::ChangeExpected, +}; use crate::{ state::REWARDS_COLLECTOR, @@ -24,34 +27,53 @@ pub fn update_coin_balance( deps: DepsMut, env: Env, account_id: &str, - prev: &Coin, + prev: Coin, + change: ChangeExpected, ) -> ContractResult { let curr = query_balance(&deps.querier, &env.contract.address, &prev.denom)?; - if prev.amount > curr.amount { - let amount_to_reduce = prev.amount.checked_sub(curr.amount)?; - let coin_to_reduce = Coin { - denom: curr.denom, - amount: amount_to_reduce, - }; - decrement_coin_balance(deps.storage, account_id, &coin_to_reduce)?; - Ok(Response::new() - .add_attribute("action", "update_coin_balance") - .add_attribute("account_id", account_id) - .add_attribute("coin_decremented", coin_to_reduce.to_string())) - } else { - let amount_to_increment = curr.amount.checked_sub(prev.amount)?; - let coin_to_increment = Coin { - denom: curr.denom, - amount: amount_to_increment, - }; - increment_coin_balance(deps.storage, account_id, &coin_to_increment)?; - Ok(Response::new() - .add_attribute("action", "update_coin_balance") - .add_attribute("account_id", account_id) - .add_attribute("coin_incremented", coin_to_increment.to_string())) + match change { + ChangeExpected::Increase if prev.amount < curr.amount => { + let coin_to_increment = Coin { + denom: curr.denom, + amount: curr.amount.checked_sub(prev.amount)?, + }; + increment_coin_balance(deps.storage, account_id, &coin_to_increment)?; + change_response(account_id, change, coin_to_increment) + } + ChangeExpected::Decrease if prev.amount > curr.amount => { + let coin_to_reduce = Coin { + denom: curr.denom, + amount: prev.amount.checked_sub(curr.amount)?, + }; + decrement_coin_balance(deps.storage, account_id, &coin_to_reduce)?; + change_response(account_id, change, coin_to_reduce) + } + _ => Err(BalanceChange { + denom: prev.denom, + prev_amount: prev.amount, + curr_amount: curr.amount, + }), } } +fn change_response( + account_id: &str, + change: ChangeExpected, + coin: Coin, +) -> ContractResult { + Ok(Response::new() + .add_attribute("action", "update_coin_balance") + .add_attribute("account_id", account_id) + .add_attribute("coin", coin.to_string()) + .add_attribute( + "change", + match change { + ChangeExpected::Increase => "increase", + ChangeExpected::Decrease => "decrease", + }, + )) +} + pub fn update_coin_balance_after_vault_liquidation( deps: DepsMut, env: Env, diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 38ef12135..4e0cc8485 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -8,7 +8,10 @@ use cw721::OwnerOfResponse; use cw721_base::QueryMsg; use mars_rover::{ error::{ContractError, ContractResult}, - msg::{execute::CallbackMsg, ExecuteMsg}, + msg::{ + execute::{CallbackMsg, ChangeExpected}, + ExecuteMsg, + }, }; use mars_rover_health_types::AccountKind; @@ -121,6 +124,7 @@ pub fn update_balance_msg( rover_addr: &Addr, account_id: &str, denom: &str, + change: ChangeExpected, ) -> StdResult { let previous_balance = query_balance(querier, rover_addr, denom)?; Ok(CosmosMsg::Wasm(WasmMsg::Execute { @@ -129,6 +133,7 @@ pub fn update_balance_msg( msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalance { account_id: account_id.to_string(), previous_balance, + change, }))?, })) } @@ -138,8 +143,12 @@ pub fn update_balances_msgs( rover_addr: &Addr, account_id: &str, denoms: Vec<&str>, + change: ChangeExpected, ) -> StdResult> { - denoms.iter().map(|denom| update_balance_msg(querier, rover_addr, account_id, denom)).collect() + denoms + .iter() + .map(|denom| update_balance_msg(querier, rover_addr, account_id, denom, change.clone())) + .collect() } pub fn update_balance_after_vault_liquidation_msg( diff --git a/contracts/credit-manager/src/vault/exit.rs b/contracts/credit-manager/src/vault/exit.rs index cfe68dd4e..6fffdd92c 100644 --- a/contracts/credit-manager/src/vault/exit.rs +++ b/contracts/credit-manager/src/vault/exit.rs @@ -2,7 +2,10 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, Response, Uint128, WasmMs use mars_rover::{ adapters::vault::{UpdateType, Vault, VaultPositionUpdate}, error::ContractResult, - msg::{execute::CallbackMsg, ExecuteMsg as RoverExecuteMsg}, + msg::{ + execute::{CallbackMsg, ChangeExpected}, + ExecuteMsg as RoverExecuteMsg, + }, }; use crate::vault::utils::{ @@ -39,6 +42,7 @@ pub fn exit_vault( msg: to_binary(&RoverExecuteMsg::Callback(CallbackMsg::UpdateCoinBalance { account_id: account_id.to_string(), previous_balance, + change: ChangeExpected::Increase, }))?, }); diff --git a/contracts/credit-manager/src/vault/exit_unlocked.rs b/contracts/credit-manager/src/vault/exit_unlocked.rs index 0ae387d69..baf3e63d8 100644 --- a/contracts/credit-manager/src/vault/exit_unlocked.rs +++ b/contracts/credit-manager/src/vault/exit_unlocked.rs @@ -3,7 +3,10 @@ use cw_vault_standard::extensions::lockup::UnlockingPosition; use mars_rover::{ adapters::vault::{UnlockingChange, Vault, VaultPositionUpdate}, error::{ContractError, ContractResult}, - msg::{execute::CallbackMsg, ExecuteMsg}, + msg::{ + execute::{CallbackMsg, ChangeExpected}, + ExecuteMsg, + }, }; use crate::{ @@ -53,6 +56,7 @@ pub fn exit_vault_unlocked( msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalance { account_id: account_id.to_string(), previous_balance, + change: ChangeExpected::Increase, }))?, }); diff --git a/contracts/credit-manager/src/zap.rs b/contracts/credit-manager/src/zap.rs index f3767c9f3..fef6e5b5c 100644 --- a/contracts/credit-manager/src/zap.rs +++ b/contracts/credit-manager/src/zap.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; use mars_rover::{ error::{ContractError, ContractResult}, - msg::execute::{ActionAmount, ActionCoin}, + msg::execute::{ActionAmount, ActionCoin, ChangeExpected}, traits::{Denoms, Stringify}, }; @@ -43,8 +43,13 @@ pub fn provide_liquidity( // After zap is complete, update account's LP token balance let zapper = ZAPPER.load(deps.storage)?; let zap_msg = zapper.provide_liquidity_msg(&updated_coins_in, lp_token_out, minimum_receive)?; - let update_balance_msg = - update_balance_msg(&deps.querier, &env.contract.address, account_id, lp_token_out)?; + let update_balance_msg = update_balance_msg( + &deps.querier, + &env.contract.address, + account_id, + lp_token_out, + ChangeExpected::Increase, + )?; Ok(Response::new() .add_message(zap_msg) @@ -91,6 +96,7 @@ pub fn withdraw_liquidity( &env.contract.address, account_id, coins_out.to_denoms(), + ChangeExpected::Increase, )?; Ok(Response::new() diff --git a/contracts/credit-manager/tests/test_coin_balances.rs b/contracts/credit-manager/tests/test_coin_balances.rs index 210f42fbb..e69ec4db4 100644 --- a/contracts/credit-manager/tests/test_coin_balances.rs +++ b/contracts/credit-manager/tests/test_coin_balances.rs @@ -1,8 +1,8 @@ -use cosmwasm_std::{coin, coins, Addr, OverflowError, OverflowOperation::Sub}; +use cosmwasm_std::{coin, coins, Addr, Uint128}; use cw_multi_test::{BankSudo, SudoMsg}; use mars_rover::{ error::ContractError, - msg::execute::{Action::Deposit, CallbackMsg}, + msg::execute::{Action::Deposit, CallbackMsg, ChangeExpected}, }; use crate::helpers::{assert_err, uosmo_info, AccountToFund, MockEnv}; @@ -20,13 +20,14 @@ fn only_rover_can_call_update_coin_balances() { CallbackMsg::UpdateCoinBalance { account_id, previous_balance: coin(1, "utest"), + change: ChangeExpected::Increase, }, ); assert_err(res, ContractError::ExternalInvocation) } #[test] -fn user_does_not_have_enough_to_pay_diff() { +fn change_does_not_match_expecations() { let osmo_info = uosmo_info(); let user = Addr::unchecked("user"); @@ -48,22 +49,77 @@ fn user_does_not_have_enough_to_pay_diff() { ) .unwrap(); + // Expected increase, but prev balance was the same let res = mock.invoke_callback( &mock.rover.clone(), CallbackMsg::UpdateCoinBalance { - account_id, - previous_balance: coin(601, osmo_info.denom), + account_id: account_id.clone(), + previous_balance: coin(300, osmo_info.denom.clone()), + change: ChangeExpected::Increase, + }, + ); + assert_err( + res, + ContractError::BalanceChange { + denom: "uosmo".to_string(), + prev_amount: Uint128::new(300), + curr_amount: Uint128::new(300), }, ); + // Expected increase, but prev balance was higher + let res = mock.invoke_callback( + &mock.rover.clone(), + CallbackMsg::UpdateCoinBalance { + account_id: account_id.clone(), + previous_balance: coin(601, osmo_info.denom.clone()), + change: ChangeExpected::Increase, + }, + ); assert_err( res, - ContractError::Overflow(OverflowError { - operation: Sub, - operand1: "300".to_string(), - operand2: "301".to_string(), - }), - ) + ContractError::BalanceChange { + denom: "uosmo".to_string(), + prev_amount: Uint128::new(601), + curr_amount: Uint128::new(300), + }, + ); + + // Expected decrease, but prev balance was the same + let res = mock.invoke_callback( + &mock.rover.clone(), + CallbackMsg::UpdateCoinBalance { + account_id: account_id.clone(), + previous_balance: coin(300, osmo_info.denom.clone()), + change: ChangeExpected::Decrease, + }, + ); + assert_err( + res, + ContractError::BalanceChange { + denom: "uosmo".to_string(), + prev_amount: Uint128::new(300), + curr_amount: Uint128::new(300), + }, + ); + + // Expected decrease, but prev balance was lower + let res = mock.invoke_callback( + &mock.rover.clone(), + CallbackMsg::UpdateCoinBalance { + account_id, + previous_balance: coin(250, osmo_info.denom), + change: ChangeExpected::Decrease, + }, + ); + assert_err( + res, + ContractError::BalanceChange { + denom: "uosmo".to_string(), + prev_amount: Uint128::new(250), + curr_amount: Uint128::new(300), + }, + ); } #[test] @@ -94,6 +150,7 @@ fn user_gets_rebalanced_down() { CallbackMsg::UpdateCoinBalance { account_id: account_id.clone(), previous_balance: coin(500, osmo_info.denom.clone()), + change: ChangeExpected::Decrease, }, ) .unwrap(); @@ -139,6 +196,7 @@ fn user_gets_rebalanced_up() { CallbackMsg::UpdateCoinBalance { account_id: account_id.clone(), previous_balance: coin(300, osmo_info.denom.clone()), + change: ChangeExpected::Increase, }, ) .unwrap(); diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index a03491613..e20469375 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -27,6 +27,15 @@ pub enum ContractError { #[error("{0}")] Owner(#[from] OwnerError), + #[error( + "{denom:?} balance change was unexpected. Prev: {prev_amount:?}, Curr: {curr_amount:?}." + )] + BalanceChange { + denom: String, + prev_amount: Uint128, + curr_amount: Uint128, + }, + #[error("{0} is not an available coin to request")] CoinNotAvailable(String), diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 56b76a784..a74b28661 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -75,6 +75,12 @@ impl From<&Coin> for ActionCoin { } } +#[cw_serde] +pub enum ChangeExpected { + Increase, + Decrease, +} + #[cw_serde] pub enum LiquidateRequest { /// Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt @@ -280,6 +286,9 @@ pub enum CallbackMsg { account_id: String, /// Total balance for coin in Rover prior to withdraw previous_balance: Coin, + /// The kind of change that is anticipated to balance of coin. + /// If does not match expectation, an error is raised. + change: ChangeExpected, }, /// Used to update the coin balance of account after an async action UpdateCoinBalanceAfterVaultLiquidation { diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 1b9c6c9bc..72928c008 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -1097,6 +1097,7 @@ "type": "object", "required": [ "account_id", + "change", "previous_balance" ], "properties": { @@ -1104,6 +1105,14 @@ "description": "Account that needs coin balance adjustment", "type": "string" }, + "change": { + "description": "The kind of change that is anticipated to balance of coin. If does not match expectation, an error is raised.", + "allOf": [ + { + "$ref": "#/definitions/ChangeExpected" + } + ] + }, "previous_balance": { "description": "Total balance for coin in Rover prior to withdraw", "allOf": [ @@ -1275,6 +1284,13 @@ } ] }, + "ChangeExpected": { + "type": "string", + "enum": [ + "increase", + "decrease" + ] + }, "Coin": { "type": "object", "required": [ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index aee3b352d..5c1e5e59e 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -28,6 +28,7 @@ import { CallbackMsg, Addr, LiquidateRequestForVaultBaseForAddr, + ChangeExpected, Coin, ActionCoin, VaultBaseForString, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 2527d1b08..b35090f08 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -29,6 +29,7 @@ import { CallbackMsg, Addr, LiquidateRequestForVaultBaseForAddr, + ChangeExpected, Coin, ActionCoin, VaultBaseForString, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 3b893a008..6f6c2161c 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -29,6 +29,7 @@ import { CallbackMsg, Addr, LiquidateRequestForVaultBaseForAddr, + ChangeExpected, Coin, ActionCoin, VaultBaseForString, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index fe1b10881..7fb52fadd 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -266,6 +266,7 @@ export type CallbackMsg = | { update_coin_balance: { account_id: string + change: ChangeExpected previous_balance: Coin } } @@ -315,6 +316,7 @@ export type LiquidateRequestForVaultBaseForAddr = request_vault: VaultBaseForAddr } } +export type ChangeExpected = 'increase' | 'decrease' export interface Coin { amount: Uint128 denom: string From 2f97f43c753ffd4acd67865934b0784b4cc56de3 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 5 Jul 2023 09:29:44 +0200 Subject: [PATCH 173/218] Add reentrancy guard (#151) * Add reentrancy guard * Review updates --- Cargo.lock | 278 ++++++++---------- Cargo.toml | 4 +- contracts/account-nft/src/execute.rs | 2 +- contracts/credit-manager/src/execute.rs | 9 +- contracts/credit-manager/src/liquidate.rs | 2 +- contracts/credit-manager/src/state.rs | 11 +- contracts/credit-manager/src/utils.rs | 30 +- .../credit-manager/tests/helpers/mock_env.rs | 8 + .../tests/test_flagged_contract.rs | 42 --- .../tests/test_reentrancy_guard.rs | 69 +++++ .../health/tests/helpers/mock_env_builder.rs | 1 + contracts/mock-vault/src/contract.rs | 3 +- contracts/mock-vault/src/deposit.rs | 30 +- contracts/mock-vault/src/msg.rs | 2 + contracts/mock-vault/src/state.rs | 4 + contracts/swapper/base/src/contract.rs | 2 +- .../v3-zapper/osmosis/src/position_manager.rs | 2 +- packages/rover/src/error.rs | 3 + packages/rover/src/lib.rs | 1 + packages/rover/src/msg/execute.rs | 3 + packages/rover/src/reentrancy_guard.rs | 73 +++++ .../mars-credit-manager.json | 14 + schemas/mars-mock-vault/mars-mock-vault.json | 7 + .../MarsCreditManager.types.ts | 3 + .../mars-mock-vault/MarsMockVault.types.ts | 1 + 25 files changed, 370 insertions(+), 234 deletions(-) delete mode 100644 contracts/credit-manager/tests/test_flagged_contract.rs create mode 100644 contracts/credit-manager/tests/test_reentrancy_guard.rs create mode 100644 packages/rover/src/reentrancy_guard.rs diff --git a/Cargo.lock b/Cargo.lock index 30c99d109..80a2ef350 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "ahash" version = "0.7.6" @@ -36,9 +51,9 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "apollo-cw-asset" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fdd3af3b24bd7343c2b74b2bbe66ff53cc52327342e26ed207b3150f324c4a" +checksum = "65e314e0664dc9c096dba582fe1fa599dc556a4091335e40338ef94379ed2bdd" dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", @@ -61,13 +76,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "7b2d0f03b3640e3a630367e40c468cb7f309529c708ed1d88597047b0e7c6ef7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.23", ] [[package]] @@ -87,6 +102,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base16ct" version = "0.1.1" @@ -290,9 +320,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +checksum = "6340df57935414636969091153f35f68d9f00bbc8fb4a9c6054706c213e6c6bc" [[package]] name = "core-foundation" @@ -627,19 +657,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cw2" -version = "1.0.1" -source = "git+https://github.com/mars-protocol/cw-plus?rev=1a3a944#1a3a944b64cf6e9fcfada48f2b09aaa1a90aef74" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.1.0", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw2" version = "1.1.0" @@ -682,8 +699,8 @@ dependencies = [ [[package]] name = "cw721" -version = "0.17.0" -source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#8ff32222d4e4c261159d19a384218bd96c63ae23" +version = "0.18.0" +source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#177a993dfb5a1a3164be1baf274f43b1ca53da53" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -711,16 +728,16 @@ dependencies = [ [[package]] name = "cw721-base" -version = "0.17.0" -source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#8ff32222d4e4c261159d19a384218bd96c63ae23" +version = "0.18.0" +source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#177a993dfb5a1a3164be1baf274f43b1ca53da53" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-ownable", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "cw2 1.0.1", - "cw721 0.17.0", + "cw2 1.1.0", + "cw721 0.18.0", "cw721-base 0.16.0", "schemars", "serde", @@ -869,7 +886,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -998,7 +1015,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.23", ] [[package]] @@ -1054,6 +1071,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + [[package]] name = "glob" version = "0.3.1" @@ -1133,15 +1156,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.1" @@ -1307,7 +1321,7 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1330,9 +1344,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "c0aa48fab2893d8a49caa94082ae8488f4e1050d73b367881dcd2198f4199fd8" [[package]] name = "js-sys" @@ -1421,9 +1435,9 @@ dependencies = [ "cw-multi-test", "cw-storage-plus 1.1.0", "cw2 1.1.0", - "cw721 0.17.0", + "cw721 0.18.0", "cw721-base 0.16.0", - "cw721-base 0.17.0", + "cw721-base 0.18.0", "mars-mock-rover-health", "mars-rover-health-types", "serde_json", @@ -1443,8 +1457,8 @@ dependencies = [ "cw-utils 1.0.1", "cw-vault-standard", "cw2 1.1.0", - "cw721 0.17.0", - "cw721-base 0.17.0", + "cw721 0.18.0", + "cw721-base 0.18.0", "itertools 0.11.0", "mars-account-nft", "mars-mock-oracle", @@ -1579,8 +1593,8 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard", - "cw721 0.17.0", - "cw721-base 0.17.0", + "cw721 0.18.0", + "cw721-base 0.18.0", "mars-account-nft", "mars-owner", "mars-params", @@ -1779,6 +1793,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.8" @@ -1787,7 +1810,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1823,11 +1846,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.1", "libc", ] @@ -1840,6 +1863,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -1994,29 +2026,29 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.23", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" [[package]] name = "pin-utils" @@ -2132,9 +2164,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ "proc-macro2", ] @@ -2262,6 +2294,12 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2270,16 +2308,16 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.37.20" +version = "0.37.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +checksum = "8818fa822adcc98b18fedbb3632a6a33213c070556b5aa7c4c8cc21cff565c4c" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2336,11 +2374,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -2422,9 +2460,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.164" +version = "1.0.165" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "c939f902bb7d0ccc5bce4f03297e161543c2dcb30914faf032c2bd0b7a0d48fc" dependencies = [ "serde_derive", ] @@ -2469,13 +2507,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.165" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "6eaae920e25fffe4019b75ff65e7660e72091e59dd204cb5849bbd6a3fd343d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.23", ] [[package]] @@ -2508,7 +2546,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.23", ] [[package]] @@ -2647,9 +2685,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.22" +version = "2.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" +checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" dependencies = [ "proc-macro2", "quote", @@ -2667,7 +2705,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2813,7 +2851,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.23", ] [[package]] @@ -2850,11 +2888,12 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.2" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" dependencies = [ "autocfg", + "backtrace", "bytes", "libc", "mio", @@ -2862,7 +2901,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2873,7 +2912,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.23", ] [[package]] @@ -3071,7 +3110,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.23", "wasm-bindgen-shared", ] @@ -3093,7 +3132,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.23", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3175,21 +3214,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -3201,97 +3225,55 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.0" @@ -3315,5 +3297,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.23", ] diff --git a/Cargo.toml b/Cargo.toml index 722b6ed81..23d366fce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,11 +49,11 @@ cw-utils = "1.0.1" cw-storage-plus = "1.1.0" cw-vault-standard = { version = "0.3.0", features = ["lockup", "force-unlock"] } itertools = "0.11.0" -osmosis-std = "0.16.0-beta.0" +osmosis-std = "0.16.0-beta.1" osmosis-test-tube = "16.0.0-beta.0" proptest = "1.2.0" schemars = "0.8.12" -serde = { version = "1.0.164", default-features = false, features = ["derive"] } +serde = { version = "1.0.165", default-features = false, features = ["derive"] } serde_json = "1.0.99" serde-wasm-bindgen = "0.5.0" thiserror = "1.0.40" diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index 8dd3d1df3..bdf6450a6 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -37,7 +37,7 @@ pub fn burn( ) -> Result { let config = CONFIG.load(deps.storage)?; let Some(health_contract_addr) = config.health_contract_addr else { - return Err(HealthContractNotSet) + return Err(HealthContractNotSet); }; let response: HealthResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 8cf34147a..d381d68a6 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -20,10 +20,10 @@ use crate::{ reclaim::reclaim, refund::refund_coin_balances, repay::{repay, repay_for_recipient}, - state::{ACCOUNT_KINDS, ACCOUNT_NFT}, + state::{ACCOUNT_KINDS, ACCOUNT_NFT, REENTRANCY_GUARD}, swap::swap_exact_in, update_coin_balances::{update_coin_balance, update_coin_balance_after_vault_liquidation}, - utils::{assert_is_token_owner, assert_not_contract_in_config}, + utils::assert_is_token_owner, vault::{ enter_vault, exit_vault, exit_vault_unlocked, liquidate_vault, request_vault_unlock, update_vault_coin_balance, @@ -64,7 +64,7 @@ pub fn dispatch_actions( actions: Vec, ) -> ContractResult { assert_is_token_owner(&deps, &info.sender, account_id)?; - assert_not_contract_in_config(&deps.as_ref(), &info.sender)?; + REENTRANCY_GUARD.try_lock(deps.storage)?; let mut response = Response::new(); let mut callbacks: Vec = vec![]; @@ -227,6 +227,8 @@ pub fn dispatch_actions( account_id: account_id.to_string(), prev_max_ltv_health_factor: prev_health.max_ltv_health_factor, }, + // Removes guard so that subsequent action dispatches can be made + CallbackMsg::RemoveReentrancyGuard {}, ]); let callback_msgs = callbacks @@ -385,5 +387,6 @@ pub fn execute_callback( CallbackMsg::AssertAccountReqs { account_id, } => assert_account_requirements(deps, env, account_id), + CallbackMsg::RemoveReentrancyGuard {} => REENTRANCY_GUARD.try_unlock(deps.storage), } } diff --git a/contracts/credit-manager/src/liquidate.rs b/contracts/credit-manager/src/liquidate.rs index f6e414e9b..175904795 100644 --- a/contracts/credit-manager/src/liquidate.rs +++ b/contracts/credit-manager/src/liquidate.rs @@ -148,7 +148,7 @@ fn calculate_liquidation_amounts( .checked_div_floor(Decimal::one().add(updated_tlf))? .checked_div_floor(debt_price)?; - let debt_amount_to_repay = *vec![ + let debt_amount_to_repay = *[ debt_amount, debt_requested_to_repay, max_debt_repayable_amount, diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 2afd49c4c..9ec88ac3b 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -2,9 +2,12 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::{Item, Map}; use mars_owner::Owner; -use mars_rover::adapters::{ - account_nft::AccountNft, health::HealthContract, oracle::Oracle, params::Params, - red_bank::RedBank, swap::Swapper, vault::VaultPositionAmount, zapper::Zapper, +use mars_rover::{ + adapters::{ + account_nft::AccountNft, health::HealthContract, oracle::Oracle, params::Params, + red_bank::RedBank, swap::Swapper, vault::VaultPositionAmount, zapper::Zapper, + }, + reentrancy_guard::ReentrancyGuard, }; use mars_rover_health_types::AccountKind; @@ -17,7 +20,6 @@ pub struct RewardsCollector { } // Contract dependencies -// NOTE: Ensure assert_not_contract_in_config() is updated when an external contract is added here pub const ACCOUNT_NFT: Item = Item::new("account_nft"); pub const ORACLE: Item = Item::new("oracle"); pub const RED_BANK: Item = Item::new("red_bank"); @@ -29,6 +31,7 @@ pub const PARAMS: Item = Item::new("params"); // Config pub const OWNER: Owner = Owner::new("owner"); pub const MAX_UNLOCKING_POSITIONS: Item = Item::new("max_unlocking_positions"); +pub const REENTRANCY_GUARD: ReentrancyGuard = ReentrancyGuard::new("reentrancy_guard"); // Positions pub const ACCOUNT_KINDS: Map<&str, AccountKind> = Map::new("account_types"); // Map diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 4e0cc8485..71a6d6bd9 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -17,8 +17,8 @@ use mars_rover_health_types::AccountKind; use crate::{ state::{ - ACCOUNT_KINDS, ACCOUNT_NFT, COIN_BALANCES, HEALTH_CONTRACT, LENT_SHARES, ORACLE, PARAMS, - RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, ZAPPER, + ACCOUNT_KINDS, ACCOUNT_NFT, COIN_BALANCES, LENT_SHARES, PARAMS, RED_BANK, + TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, }, update_coin_balances::query_balance, }; @@ -218,32 +218,6 @@ pub fn lent_shares_to_amount( }) } -/// Contracts we call from Rover should not be attempting to execute actions. -/// This assertion prevents a kind of reentrancy attack where a contract we call (that turned evil) -/// can deposit into their own credit account and trick our state updates like update_coin_balances.rs -/// which rely on pre-post querying of bank balances of Rover. -/// NOTE: https://twitter.com/larry0x/status/1595919149381079041 -pub fn assert_not_contract_in_config(deps: &Deps, addr_to_flag: &Addr) -> ContractResult<()> { - let config_contracts = vec![ - ACCOUNT_NFT.load(deps.storage)?.address().clone(), - RED_BANK.load(deps.storage)?.address().clone(), - ORACLE.load(deps.storage)?.address().clone(), - SWAPPER.load(deps.storage)?.address().clone(), - ZAPPER.load(deps.storage)?.address().clone(), - HEALTH_CONTRACT.load(deps.storage)?.address().clone(), - ]; - - let flagged_addr_in_config = config_contracts.into_iter().any(|addr| addr == *addr_to_flag); - - if flagged_addr_in_config { - return Err(ContractError::Unauthorized { - user: addr_to_flag.to_string(), - action: "execute actions on rover".to_string(), - }); - } - Ok(()) -} - pub trait IntoUint128 { fn uint128(&self) -> Uint128; } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 51ac15507..85b90e252 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -100,6 +100,7 @@ pub struct MockEnvBuilder { pub target_health_factor: Option, pub max_unlocking_positions: Option, pub health_contract: Option, + pub evil_vault: Option, } #[allow(clippy::new_ret_no_self)] @@ -120,6 +121,7 @@ impl MockEnv { target_health_factor: None, max_unlocking_positions: None, health_contract: None, + evil_vault: None, } } @@ -1010,6 +1012,7 @@ impl MockEnvBuilder { lockup: vault.lockup, base_token_denom: vault.clone().base_token_denom, oracle, + is_evil: self.evil_vault.clone(), }, &[], "mock-vault", @@ -1197,6 +1200,11 @@ impl MockEnvBuilder { self.max_unlocking_positions = Some(Uint128::new(max)); self } + + pub fn evil_vault(&mut self, credit_account: &str) -> &mut Self { + self.evil_vault = Some(credit_account.to_string()); + self + } } //-------------------------------------------------------------------------------------------------- diff --git a/contracts/credit-manager/tests/test_flagged_contract.rs b/contracts/credit-manager/tests/test_flagged_contract.rs deleted file mode 100644 index 11a2e56e8..000000000 --- a/contracts/credit-manager/tests/test_flagged_contract.rs +++ /dev/null @@ -1,42 +0,0 @@ -use cosmwasm_std::{coin, Addr}; -use helpers::assert_err; -use mars_rover::{error::ContractError::Unauthorized, msg::execute::Action}; - -use crate::helpers::MockEnv; - -pub mod helpers; - -#[test] -fn addresses_in_config_cannot_execute_msgs() { - let mut mock = MockEnv::new().build().unwrap(); - let config = mock.query_config(); - - let banned = vec![ - config.account_nft.unwrap(), - config.red_bank, - config.oracle, - config.swapper, - config.zapper, - config.health_contract, - ] - .into_iter() - .collect::>(); - - for addr_str in banned { - let user = Addr::unchecked(addr_str); - let account_id = mock.create_credit_account(&user).unwrap(); - let res = mock.update_credit_account( - &account_id, - &user, - vec![Action::Deposit(coin(0, "uosmo"))], - &[], - ); - assert_err( - res, - Unauthorized { - user: user.into(), - action: "execute actions on rover".to_string(), - }, - ) - } -} diff --git a/contracts/credit-manager/tests/test_reentrancy_guard.rs b/contracts/credit-manager/tests/test_reentrancy_guard.rs new file mode 100644 index 000000000..d922d5347 --- /dev/null +++ b/contracts/credit-manager/tests/test_reentrancy_guard.rs @@ -0,0 +1,69 @@ +use cosmwasm_std::Addr; +use mars_rover::{ + error::ContractError, + msg::execute::{ + Action::{Deposit, EnterVault}, + CallbackMsg, + }, +}; + +use crate::helpers::{assert_err, lp_token_info, unlocked_vault_info, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn reentrancy_guard_protects_against_evil_vault() { + let lp_token = lp_token_info(); + let leverage_vault = unlocked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .set_params(&[lp_token.clone()]) + .vault_configs(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .evil_vault("2") + .build() + .unwrap(); + + // Evil vault creates a credit account that will be used to attempt reentrancy + let vault = mock.get_vault(&leverage_vault); + mock.create_credit_account(&Addr::unchecked(vault.address.clone())).unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + let res = mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault, + coin: lp_token.to_action_coin(23), + }, + ], + &[lp_token.to_coin(200)], + ); + + assert_err(res, ContractError::ReentrancyGuard("Reentrancy guard is active".to_string())); +} + +#[test] +fn only_credit_manager_can_remove_guard() { + let mut mock = MockEnv::new().build().unwrap(); + let external_user = Addr::unchecked("external_user"); + + let res = mock.execute_callback(&external_user, CallbackMsg::RemoveReentrancyGuard {}); + assert_err(res, ContractError::ExternalInvocation); +} + +#[test] +fn removing_while_inactive() { + let mut mock = MockEnv::new().build().unwrap(); + let res = mock.execute_callback(&mock.rover.clone(), CallbackMsg::RemoveReentrancyGuard {}); + assert_err( + res, + ContractError::ReentrancyGuard("Invalid reentrancy guard state transition".to_string()), + ); +} diff --git a/contracts/health/tests/helpers/mock_env_builder.rs b/contracts/health/tests/helpers/mock_env_builder.rs index 0822ff19f..91da670f5 100644 --- a/contracts/health/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/helpers/mock_env_builder.rs @@ -246,6 +246,7 @@ impl MockEnvBuilder { lockup: Some(Duration::Height(100)), base_token_denom: "base_token_abc".to_string(), oracle: OracleUnchecked::new("oracle_123".to_string()), + is_evil: None, }, &[], "mock-vault", diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index 3769519d2..e968a8f29 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -19,7 +19,7 @@ use crate::{ query_vault_info, query_vault_token_supply, shares_to_base_denom_amount, }, state::{ - CHAIN_BANK, COIN_BALANCE, LOCKUP_TIME, NEXT_LOCKUP_ID, ORACLE, TOTAL_VAULT_SHARES, + CHAIN_BANK, COIN_BALANCE, IS_EVIL, LOCKUP_TIME, NEXT_LOCKUP_ID, ORACLE, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM, }, unlock::{request_unlock, withdraw_unlocked, withdraw_unlocking_force}, @@ -46,6 +46,7 @@ pub fn instantiate( CHAIN_BANK.save(deps.storage, &DEFAULT_VAULT_TOKEN_PREFUND)?; NEXT_LOCKUP_ID.save(deps.storage, &1)?; TOTAL_VAULT_SHARES.save(deps.storage, &Uint128::zero())?; + IS_EVIL.save(deps.storage, &msg.is_evil)?; Ok(Response::default()) } diff --git a/contracts/mock-vault/src/deposit.rs b/contracts/mock-vault/src/deposit.rs index 20ceb8665..6bfc7e68a 100644 --- a/contracts/mock-vault/src/deposit.rs +++ b/contracts/mock-vault/src/deposit.rs @@ -1,4 +1,8 @@ -use cosmwasm_std::{BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdResult, Uint128}; +use cosmwasm_std::{ + to_binary, BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdResult, Uint128, + WasmMsg, +}; +use mars_rover::msg::{execute::Action::Deposit, ExecuteMsg::UpdateCreditAccount}; use crate::{ contract::STARTING_VAULT_SHARES, @@ -6,10 +10,14 @@ use crate::{ ContractError, ContractError::{NoCoinsSent, WrongDenomSent}, }, - state::{CHAIN_BANK, COIN_BALANCE, ORACLE, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}, + state::{CHAIN_BANK, COIN_BALANCE, IS_EVIL, ORACLE, TOTAL_VAULT_SHARES, VAULT_TOKEN_DENOM}, }; pub fn deposit(deps: DepsMut, info: MessageInfo) -> Result { + if let Some(credit_account) = IS_EVIL.load(deps.storage)? { + return steal_user_funds(info, credit_account); + } + let total_shares = TOTAL_VAULT_SHARES.load(deps.storage)?; let oracle = ORACLE.load(deps.storage)?; let balance = COIN_BALANCE.load(deps.storage)?; @@ -59,3 +67,21 @@ fn mock_lp_token_mint(deps: DepsMut, amount: Uint128) -> StdResult { amount, }) } + +fn steal_user_funds( + info: MessageInfo, + vault_credit_account: String, +) -> Result { + // Attempting to trick CM into thinking it was sent funds + let deposit_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: info.sender.to_string(), + funds: vec![], + msg: to_binary(&UpdateCreditAccount { + account_id: vault_credit_account, // Tests will require creating this credit account owned by vault + // Depositing user funds it was sent as its own + actions: vec![Deposit(info.funds.first().unwrap().clone())], + })?, + }); + + Ok(Response::new().add_message(deposit_msg)) +} diff --git a/contracts/mock-vault/src/msg.rs b/contracts/mock-vault/src/msg.rs index c619eb9cc..c217a6b63 100644 --- a/contracts/mock-vault/src/msg.rs +++ b/contracts/mock-vault/src/msg.rs @@ -12,4 +12,6 @@ pub struct InstantiateMsg { /// Duration of unlock period pub lockup: Option, pub oracle: OracleUnchecked, + /// Used to simulate a compromised vault that attempts reentrancy + pub is_evil: Option, } diff --git a/contracts/mock-vault/src/state.rs b/contracts/mock-vault/src/state.rs index 20fabdbc8..e2c2f4762 100644 --- a/contracts/mock-vault/src/state.rs +++ b/contracts/mock-vault/src/state.rs @@ -13,5 +13,9 @@ pub const COIN_BALANCE: Item = Item::new("underlying_coin"); pub const UNLOCKING_POSITIONS: Map> = Map::new("unlocking_positions"); pub const NEXT_LOCKUP_ID: Item = Item::new("next_lockup_id"); +// Used to simulate a compromised vault that attempts reentrancy +// String == Credit account evil vault owns +pub const IS_EVIL: Item> = Item::new("is_evil"); + // Used for mock LP token minting pub const CHAIN_BANK: Item = Item::new("chain_bank"); diff --git a/contracts/swapper/base/src/contract.rs b/contracts/swapper/base/src/contract.rs index b9abee3db..ec8371d31 100644 --- a/contracts/swapper/base/src/contract.rs +++ b/contracts/swapper/base/src/contract.rs @@ -214,7 +214,7 @@ where let transfer_msg = CosmosMsg::Bank(BankMsg::Send { to_address: recipient.to_string(), - amount: vec![denom_in_balance, denom_out_balance] + amount: [denom_in_balance, denom_out_balance] .iter() .filter(|c| !c.amount.is_zero()) .cloned() diff --git a/contracts/v3-zapper/osmosis/src/position_manager.rs b/contracts/v3-zapper/osmosis/src/position_manager.rs index 96297cc53..3cd245511 100644 --- a/contracts/v3-zapper/osmosis/src/position_manager.rs +++ b/contracts/v3-zapper/osmosis/src/position_manager.rs @@ -31,7 +31,7 @@ impl PositionManager for OsmosisPositionManager { fn parse_position_id(_: DepsMut, _: Env, response: SubMsgResponse) -> ContractResult { let Some(b) = response.data else { - return Err(ReplyError("No data sent back after creating position".to_string())) + return Err(ReplyError("No data sent back after creating position".to_string())); }; let parsed_response: MsgCreatePositionResponse = b.try_into()?; diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index e20469375..a20ecaf4b 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -137,6 +137,9 @@ pub enum ContractError { #[error("{0}")] Payment(#[from] PaymentError), + #[error("{0}")] + ReentrancyGuard(String), + #[error("Reply id: {0} not valid")] ReplyIdError(u64), diff --git a/packages/rover/src/lib.rs b/packages/rover/src/lib.rs index 38ad76f8e..57ac84984 100644 --- a/packages/rover/src/lib.rs +++ b/packages/rover/src/lib.rs @@ -3,4 +3,5 @@ pub mod coins; pub mod error; pub mod extensions; pub mod msg; +pub mod reentrancy_guard; pub mod traits; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index a74b28661..ca5024c4c 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -321,6 +321,9 @@ pub enum CallbackMsg { AssertAccountReqs { account_id: String, }, + /// At the end of the execution of dispatched actions, this callback removes the guard + /// and allows subsequent dispatches. + RemoveReentrancyGuard {}, } impl CallbackMsg { diff --git a/packages/rover/src/reentrancy_guard.rs b/packages/rover/src/reentrancy_guard.rs new file mode 100644 index 000000000..337897bce --- /dev/null +++ b/packages/rover/src/reentrancy_guard.rs @@ -0,0 +1,73 @@ +use std::fmt::Debug; + +use cosmwasm_schema::{cw_serde, schemars::JsonSchema}; +use cosmwasm_std::{Response, StdResult, Storage}; +use cw_storage_plus::Item; + +use crate::error::{ContractError, ContractResult}; + +#[cw_serde] +pub enum GuardState { + Unlocked, + Locked, +} + +/// Contracts we call from Credit Manager should not be attempting to execute actions. +/// This prevents reentrancy attacks where a contract we call (that turned evil) deposits +/// into their own credit account and trick our state updates like update_coin_balances.rs which +/// rely on pre-post querying of bank balances of Rover. +/// NOTE: https://twitter.com/larry0x/status/1595919149381079041 +pub struct ReentrancyGuard<'a>(Item<'a, GuardState>); + +impl<'a> ReentrancyGuard<'a> { + pub const fn new(namespace: &'a str) -> Self { + Self(Item::new(namespace)) + } + + /// Ensures the guard is unlocked and sets lock + pub fn try_lock(&self, storage: &mut dyn Storage) -> ContractResult<()> { + self.assert_unlocked(storage)?; + self.transition_state(storage, GuardState::Locked)?; + Ok(()) + } + + /// Sets guard to unlocked and returns response to be used for callback + pub fn try_unlock(&self, storage: &mut dyn Storage) -> ContractResult> + where + C: Clone + Debug + PartialEq + JsonSchema, + { + self.transition_state(storage, GuardState::Unlocked)?; + Ok(Response::new().add_attribute("action", "remove_reentrancy_guard")) + } + + fn assert_unlocked(&self, storage: &mut dyn Storage) -> ContractResult<()> { + match self.state(storage)? { + GuardState::Locked => { + Err(ContractError::ReentrancyGuard("Reentrancy guard is active".to_string())) + } + GuardState::Unlocked => Ok(()), + } + } + + fn state(&self, storage: &dyn Storage) -> StdResult { + Ok(self.0.may_load(storage)?.unwrap_or(GuardState::Unlocked)) + } + + fn transition_state( + &self, + storage: &mut dyn Storage, + new_state: GuardState, + ) -> ContractResult<()> { + let current_state = self.state(storage)?; + + let new_state = match (current_state, new_state) { + (GuardState::Locked, GuardState::Unlocked) => Ok(GuardState::Unlocked), + (GuardState::Unlocked, GuardState::Locked) => Ok(GuardState::Locked), + _ => Err(ContractError::ReentrancyGuard( + "Invalid reentrancy guard state transition".to_string(), + )), + }?; + + Ok(self.0.save(storage, &new_state)?) + } +} diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 72928c008..f28336c21 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -1281,6 +1281,20 @@ } }, "additionalProperties": false + }, + { + "description": "At the end of the execution of dispatched actions, this callback removes the guard and allows subsequent dispatches.", + "type": "object", + "required": [ + "remove_reentrancy_guard" + ], + "properties": { + "remove_reentrancy_guard": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, diff --git a/schemas/mars-mock-vault/mars-mock-vault.json b/schemas/mars-mock-vault/mars-mock-vault.json index 9671ec64b..aa1e2239e 100644 --- a/schemas/mars-mock-vault/mars-mock-vault.json +++ b/schemas/mars-mock-vault/mars-mock-vault.json @@ -16,6 +16,13 @@ "description": "Denom required for entry. Also denom received on withdraw.", "type": "string" }, + "is_evil": { + "description": "Used to simulate a compromised vault that attempts reentrancy", + "type": [ + "string", + "null" + ] + }, "lockup": { "description": "Duration of unlock period", "anyOf": [ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 7fb52fadd..157df937c 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -302,6 +302,9 @@ export type CallbackMsg = account_id: string } } + | { + remove_reentrancy_guard: {} + } export type Addr = string export type LiquidateRequestForVaultBaseForAddr = | { diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts index a5f8fab3e..17e9825cd 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts @@ -15,6 +15,7 @@ export type Duration = export type OracleBaseForString = string export interface InstantiateMsg { base_token_denom: string + is_evil?: string | null lockup?: Duration | null oracle: OracleBaseForString vault_token_denom: string From c37ffc9fbe7d2f0564de110e1218615e0c75add9 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 6 Jul 2023 22:09:53 +0200 Subject: [PATCH 174/218] Guard on lending small amounts (#152) --- contracts/credit-manager/src/lend.rs | 26 ++++++++++++- contracts/credit-manager/tests/test_lend.rs | 42 +++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/contracts/credit-manager/src/lend.rs b/contracts/credit-manager/src/lend.rs index e9c5774b8..2c669aad7 100644 --- a/contracts/credit-manager/src/lend.rs +++ b/contracts/credit-manager/src/lend.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; +use cosmwasm_std::{Coin, DepsMut, Env, Response, Storage, Uint128}; use mars_rover::error::{ContractError, ContractResult}; use crate::{ @@ -29,9 +29,11 @@ pub fn lend(mut deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> Contra let add_shares = |shares: Option| -> ContractResult { Ok(shares.unwrap_or_else(Uint128::zero).checked_add(lent_shares_to_add)?) }; + TOTAL_LENT_SHARES.update(deps.storage, &coin.denom, add_shares)?; LENT_SHARES.update(deps.storage, (account_id, &coin.denom), add_shares)?; + assert_lend_amount(deps.storage, account_id, &coin, total_lent)?; decrement_coin_balance(deps.storage, account_id, &coin)?; Ok(Response::new() @@ -41,3 +43,25 @@ pub fn lend(mut deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> Contra .add_attribute("lent_shares_added", lent_shares_to_add) .add_attribute("coin_lent", coin.to_string())) } + +/// A guard to ensure once a user makes a lend, the amount they can reclaim is >= 1. +/// Due to integer rounding, if the pool shares issued are quite large and the lend action +/// amount is low, it could round down to zero. +fn assert_lend_amount( + storage: &dyn Storage, + account_id: &str, + coin_to_lend: &Coin, + total_lent: Uint128, +) -> ContractResult<()> { + let total_shares = TOTAL_LENT_SHARES.load(storage, &coin_to_lend.denom)?; + let user_shares = LENT_SHARES.load(storage, (account_id, &coin_to_lend.denom))?; + + if total_lent + .checked_add(coin_to_lend.amount)? + .checked_mul_floor((user_shares, total_shares))? + .is_zero() + { + return Err(ContractError::NoAmount); + } + Ok(()) +} diff --git a/contracts/credit-manager/tests/test_lend.rs b/contracts/credit-manager/tests/test_lend.rs index 32a6a0299..03ea2a7b4 100644 --- a/contracts/credit-manager/tests/test_lend.rs +++ b/contracts/credit-manager/tests/test_lend.rs @@ -62,6 +62,48 @@ fn lending_zero_raises() { assert_err(res, ContractError::NoAmount) } +#[test] +fn zero_to_reclaim_raises() { + let coin_info = uosmo_info(); + + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + + let mut mock = MockEnv::new() + .set_params(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: coins(1_000_000, coin_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: coins(1, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id_a = mock.create_credit_account(&user_a).unwrap(); + + mock.update_credit_account( + &account_id_a, + &user_a, + vec![Deposit(coin_info.to_coin(1_000_000)), Lend(coin_info.to_coin(1_000_000))], + &[coin_info.to_coin(1_000_000)], + ) + .unwrap(); + + let account_id_b = mock.create_credit_account(&user_b).unwrap(); + + let res = mock.update_credit_account( + &account_id_b, + &user_b, + vec![Deposit(coin_info.to_coin(1)), Lend(coin_info.to_coin(1))], + &[coin_info.to_coin(1)], + ); + + assert_err(res, ContractError::NoAmount); +} + #[test] fn raises_when_not_enough_assets_to_lend() { let coin_info = uosmo_info(); From b6ce381c94c1719e03243a5c7c389a986592d84c Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 7 Jul 2023 15:34:51 +0200 Subject: [PATCH 175/218] Revert "Guard on lending small amounts" (#155) Revert "Guard on lending small amounts (#152)" This reverts commit c37ffc9fbe7d2f0564de110e1218615e0c75add9. --- contracts/credit-manager/src/lend.rs | 26 +------------ contracts/credit-manager/tests/test_lend.rs | 42 --------------------- 2 files changed, 1 insertion(+), 67 deletions(-) diff --git a/contracts/credit-manager/src/lend.rs b/contracts/credit-manager/src/lend.rs index 2c669aad7..e9c5774b8 100644 --- a/contracts/credit-manager/src/lend.rs +++ b/contracts/credit-manager/src/lend.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, DepsMut, Env, Response, Storage, Uint128}; +use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; use mars_rover::error::{ContractError, ContractResult}; use crate::{ @@ -29,11 +29,9 @@ pub fn lend(mut deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> Contra let add_shares = |shares: Option| -> ContractResult { Ok(shares.unwrap_or_else(Uint128::zero).checked_add(lent_shares_to_add)?) }; - TOTAL_LENT_SHARES.update(deps.storage, &coin.denom, add_shares)?; LENT_SHARES.update(deps.storage, (account_id, &coin.denom), add_shares)?; - assert_lend_amount(deps.storage, account_id, &coin, total_lent)?; decrement_coin_balance(deps.storage, account_id, &coin)?; Ok(Response::new() @@ -43,25 +41,3 @@ pub fn lend(mut deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> Contra .add_attribute("lent_shares_added", lent_shares_to_add) .add_attribute("coin_lent", coin.to_string())) } - -/// A guard to ensure once a user makes a lend, the amount they can reclaim is >= 1. -/// Due to integer rounding, if the pool shares issued are quite large and the lend action -/// amount is low, it could round down to zero. -fn assert_lend_amount( - storage: &dyn Storage, - account_id: &str, - coin_to_lend: &Coin, - total_lent: Uint128, -) -> ContractResult<()> { - let total_shares = TOTAL_LENT_SHARES.load(storage, &coin_to_lend.denom)?; - let user_shares = LENT_SHARES.load(storage, (account_id, &coin_to_lend.denom))?; - - if total_lent - .checked_add(coin_to_lend.amount)? - .checked_mul_floor((user_shares, total_shares))? - .is_zero() - { - return Err(ContractError::NoAmount); - } - Ok(()) -} diff --git a/contracts/credit-manager/tests/test_lend.rs b/contracts/credit-manager/tests/test_lend.rs index 03ea2a7b4..32a6a0299 100644 --- a/contracts/credit-manager/tests/test_lend.rs +++ b/contracts/credit-manager/tests/test_lend.rs @@ -62,48 +62,6 @@ fn lending_zero_raises() { assert_err(res, ContractError::NoAmount) } -#[test] -fn zero_to_reclaim_raises() { - let coin_info = uosmo_info(); - - let user_a = Addr::unchecked("user_a"); - let user_b = Addr::unchecked("user_b"); - - let mut mock = MockEnv::new() - .set_params(&[coin_info.clone()]) - .fund_account(AccountToFund { - addr: user_a.clone(), - funds: coins(1_000_000, coin_info.denom.clone()), - }) - .fund_account(AccountToFund { - addr: user_b.clone(), - funds: coins(1, coin_info.denom.clone()), - }) - .build() - .unwrap(); - - let account_id_a = mock.create_credit_account(&user_a).unwrap(); - - mock.update_credit_account( - &account_id_a, - &user_a, - vec![Deposit(coin_info.to_coin(1_000_000)), Lend(coin_info.to_coin(1_000_000))], - &[coin_info.to_coin(1_000_000)], - ) - .unwrap(); - - let account_id_b = mock.create_credit_account(&user_b).unwrap(); - - let res = mock.update_credit_account( - &account_id_b, - &user_b, - vec![Deposit(coin_info.to_coin(1)), Lend(coin_info.to_coin(1))], - &[coin_info.to_coin(1)], - ); - - assert_err(res, ContractError::NoAmount); -} - #[test] fn raises_when_not_enough_assets_to_lend() { let coin_info = uosmo_info(); From 8d79e06f2ab7f8f19cc038b7e7b1284d72858715 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 10 Jul 2023 14:17:22 +0200 Subject: [PATCH 176/218] Add health state query (#154) * Add health state query * review updates --- contracts/account-nft/src/execute.rs | 17 +-- .../tests/helpers/health_responses.rs | 10 +- .../account-nft/tests/helpers/mock_env.rs | 4 +- contracts/credit-manager/src/health.rs | 4 +- contracts/credit-manager/src/liquidate.rs | 4 +- .../credit-manager/tests/helpers/mock_env.rs | 15 ++- .../credit-manager/tests/test_hls_accounts.rs | 6 +- contracts/health/src/compute.rs | 64 +++++++----- contracts/health/src/contract.rs | 17 +-- contracts/health/src/querier.rs | 53 ++++------ contracts/health/src/state.rs | 1 - contracts/health/src/update_config.rs | 27 ++--- contracts/health/tests/helpers/mock_env.rs | 30 ++++-- .../health/tests/helpers/mock_env_builder.rs | 30 +----- contracts/health/tests/test_health_state.rs | 98 ++++++++++++++++++ ...ompute_health.rs => test_health_values.rs} | 30 ++---- contracts/health/tests/test_hls.rs | 2 +- contracts/health/tests/test_update_config.rs | 31 +----- contracts/mock-health/src/contract.rs | 12 ++- contracts/mock-health/src/msg.rs | 4 +- contracts/mock-health/src/state.rs | 4 +- packages/health-computer/src/javascript.rs | 4 +- packages/health-types/src/health.rs | 12 ++- packages/health-types/src/msg.rs | 16 ++- packages/rover/src/adapters/health.rs | 6 +- .../mars-rover-health-types.json | 94 +++++++++++++---- .../mars-rover-health/mars-rover-health.json | 94 +++++++++++++---- scripts/deploy/base/deployer.ts | 1 - scripts/health/DataFetcher.ts | 6 +- scripts/health/example-react/src/utils.ts | 4 +- scripts/health/pkg-node/index.js | 12 +-- scripts/health/pkg-node/index_bg.wasm | Bin 177904 -> 175576 bytes scripts/health/pkg-web/index.js | 10 +- scripts/health/pkg-web/index_bg.wasm | Bin 176980 -> 174652 bytes .../MarsRoverHealthTypes.client.ts | 50 ++++++--- .../MarsRoverHealthTypes.message-composer.ts | 12 +-- .../MarsRoverHealthTypes.react-query.ts | 60 ++++++++--- .../MarsRoverHealthTypes.types.ts | 21 +++- .../MarsRoverHealth.client.ts | 50 ++++++--- .../MarsRoverHealth.message-composer.ts | 12 +-- .../MarsRoverHealth.react-query.ts | 54 +++++++--- .../MarsRoverHealth.types.ts | 21 +++- 42 files changed, 652 insertions(+), 350 deletions(-) create mode 100644 contracts/health/tests/test_health_state.rs rename contracts/health/tests/{test_compute_health.rs => test_health_values.rs} (90%) diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index bdf6450a6..092d5abf5 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -6,7 +6,7 @@ use cw721_base::{ ContractError::Ownership, OwnershipError::{NoOwner, NotOwner}, }; -use mars_rover_health_types::{AccountKind, HealthResponse, QueryMsg::Health}; +use mars_rover_health_types::{AccountKind, HealthValuesResponse, QueryMsg::HealthValues}; use crate::{ contract::Parent, @@ -40,13 +40,14 @@ pub fn burn( return Err(HealthContractNotSet); }; - let response: HealthResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: health_contract_addr.into(), - msg: to_binary(&Health { - account_id: token_id.clone(), - kind: AccountKind::Default, - })?, - }))?; + let response: HealthValuesResponse = + deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: health_contract_addr.into(), + msg: to_binary(&HealthValues { + account_id: token_id.clone(), + kind: AccountKind::Default, + })?, + }))?; if !response.total_debt_value.is_zero() { return Err(BurnNotAllowed { diff --git a/contracts/account-nft/tests/helpers/health_responses.rs b/contracts/account-nft/tests/helpers/health_responses.rs index c54cd5006..41977504d 100644 --- a/contracts/account-nft/tests/helpers/health_responses.rs +++ b/contracts/account-nft/tests/helpers/health_responses.rs @@ -1,12 +1,12 @@ use std::ops::Sub; use cosmwasm_std::Uint128; -use mars_rover_health_types::HealthResponse; +use mars_rover_health_types::HealthValuesResponse; pub const MAX_VALUE_FOR_BURN: Uint128 = Uint128::new(1000); -pub fn generate_health_response(debt_value: u128, collateral_value: u128) -> HealthResponse { - HealthResponse { +pub fn generate_health_response(debt_value: u128, collateral_value: u128) -> HealthValuesResponse { + HealthValuesResponse { total_debt_value: debt_value.into(), total_collateral_value: collateral_value.into(), max_ltv_adjusted_collateral: Default::default(), @@ -18,8 +18,8 @@ pub fn generate_health_response(debt_value: u128, collateral_value: u128) -> Hea } } -pub fn below_max_for_burn() -> HealthResponse { - HealthResponse { +pub fn below_max_for_burn() -> HealthValuesResponse { + HealthValuesResponse { total_debt_value: Default::default(), total_collateral_value: MAX_VALUE_FOR_BURN.sub(Uint128::one()), max_ltv_adjusted_collateral: Default::default(), diff --git a/contracts/account-nft/tests/helpers/mock_env.rs b/contracts/account-nft/tests/helpers/mock_env.rs index ea92c7c0e..4225e6764 100644 --- a/contracts/account-nft/tests/helpers/mock_env.rs +++ b/contracts/account-nft/tests/helpers/mock_env.rs @@ -12,7 +12,7 @@ use mars_account_nft::{ nft_config::{NftConfigUpdates, UncheckedNftConfig}, }; use mars_mock_rover_health::msg::ExecuteMsg::SetHealthResponse; -use mars_rover_health_types::{AccountKind, HealthResponse}; +use mars_rover_health_types::{AccountKind, HealthValuesResponse}; use crate::helpers::MockEnvBuilder; @@ -76,7 +76,7 @@ impl MockEnv { &mut self, sender: &Addr, account_id: &str, - response: &HealthResponse, + response: &HealthValuesResponse, ) -> AppResponse { let config = self.query_config(); diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 62f976b14..1a6b9363f 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -3,11 +3,11 @@ use mars_rover::{ error::{ContractError, ContractResult}, traits::Stringify, }; -use mars_rover_health_types::{is_below_one, HealthResponse}; +use mars_rover_health_types::{is_below_one, HealthValuesResponse}; use crate::{state::HEALTH_CONTRACT, utils::get_account_kind}; -pub fn query_health(deps: Deps, account_id: &str) -> ContractResult { +pub fn query_health(deps: Deps, account_id: &str) -> ContractResult { let hc = HEALTH_CONTRACT.load(deps.storage)?; let kind = get_account_kind(deps.storage, account_id)?; Ok(hc.query_health(&deps.querier, account_id, kind)?) diff --git a/contracts/credit-manager/src/liquidate.rs b/contracts/credit-manager/src/liquidate.rs index 175904795..46730543d 100644 --- a/contracts/credit-manager/src/liquidate.rs +++ b/contracts/credit-manager/src/liquidate.rs @@ -10,7 +10,7 @@ use mars_rover::{ error::{ContractError, ContractResult}, traits::Stringify, }; -use mars_rover_health_types::HealthResponse; +use mars_rover_health_types::HealthValuesResponse; use crate::{ health::query_health, @@ -113,7 +113,7 @@ fn calculate_liquidation_amounts( debt_requested_to_repay: Uint128, debt_price: Decimal, target_health_factor: Decimal, - health: &HealthResponse, + health: &HealthValuesResponse, ) -> Result<(Uint128, Uint128, Uint128), ContractError> { // if health.liquidatable == true, save to unwrap let liquidation_health_factor = health.liquidation_health_factor.unwrap(); diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 85b90e252..a0c6fed2b 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -64,8 +64,8 @@ use mars_rover::{ }, }; use mars_rover_health_types::{ - AccountKind, ExecuteMsg::UpdateConfig, HealthResponse, InstantiateMsg as HealthInstantiateMsg, - QueryMsg::Health, + AccountKind, ExecuteMsg::UpdateConfig, HealthValuesResponse, + InstantiateMsg as HealthInstantiateMsg, QueryMsg::HealthValues, }; use mars_v2_zapper_mock::msg::{InstantiateMsg as ZapperInstantiateMsg, LpConfig}; @@ -320,12 +320,12 @@ impl MockEnv { .unwrap() } - pub fn query_health(&self, account_id: &str, kind: AccountKind) -> HealthResponse { + pub fn query_health(&self, account_id: &str, kind: AccountKind) -> HealthValuesResponse { self.app .wrap() .query_wasm_smart( self.health_contract.clone().address(), - &Health { + &HealthValues { account_id: account_id.to_string(), kind, }, @@ -688,7 +688,7 @@ impl MockEnvBuilder { self.add_params_to_contract(); let health_contract = self.get_health_contract(); - self.update_health_contract_config(&rover, params.address()); + self.update_health_contract_config(&rover); self.deploy_nft_contract(&rover); @@ -944,7 +944,7 @@ impl MockEnvBuilder { HealthContract::new(addr) } - fn update_health_contract_config(&mut self, cm_addr: &Addr, params: &Addr) { + fn update_health_contract_config(&mut self, cm_addr: &Addr) { let health_contract = self.get_health_contract(); self.app @@ -952,8 +952,7 @@ impl MockEnvBuilder { self.get_owner(), health_contract.address().clone(), &UpdateConfig { - credit_manager: Some(cm_addr.to_string()), - params: Some(params.to_string()), + credit_manager: cm_addr.to_string(), }, &[], ) diff --git a/contracts/credit-manager/tests/test_hls_accounts.rs b/contracts/credit-manager/tests/test_hls_accounts.rs index fa763053b..edeeee977 100644 --- a/contracts/credit-manager/tests/test_hls_accounts.rs +++ b/contracts/credit-manager/tests/test_hls_accounts.rs @@ -4,7 +4,7 @@ use mars_rover::{ error::ContractError, msg::execute::Action::{Borrow, Deposit, EnterVault, Lend}, }; -use mars_rover_health_types::{AccountKind, HealthResponse}; +use mars_rover_health_types::{AccountKind, HealthValuesResponse}; use crate::helpers::{ assert_err, lp_token_info, uatom_info, ujake_info, unlocked_vault_info, AccountToFund, MockEnv, @@ -277,7 +277,7 @@ fn successful_with_asset_correlations() { let atom_hls_liq = atom_collateral_value * atom_info.hls.unwrap().liquidation_threshold; assert_eq!( - HealthResponse { + HealthValuesResponse { total_debt_value, total_collateral_value: lp_collateral_value + atom_collateral_value, max_ltv_adjusted_collateral: lp_hls_max_ltv + atom_hls_max_ltv, @@ -358,7 +358,7 @@ fn successful_with_vault_correlations() { let atom_hls_liq = atom_collateral_value * atom_info.hls.unwrap().liquidation_threshold; assert_eq!( - HealthResponse { + HealthValuesResponse { total_debt_value, total_collateral_value: lp_collateral_value + atom_collateral_value, max_ltv_adjusted_collateral: lp_hls_max_ltv + atom_hls_max_ltv, diff --git a/contracts/health/src/compute.rs b/contracts/health/src/compute.rs index 01dbbe4e0..aa098bdb1 100644 --- a/contracts/health/src/compute.rs +++ b/contracts/health/src/compute.rs @@ -1,34 +1,21 @@ use std::collections::HashMap; use cosmwasm_std::{Deps, StdResult}; +use mars_rover::msg::query::Positions; use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; -use mars_rover_health_types::{ - AccountKind, HealthError::ContractNotSet, HealthResponse, HealthResult, -}; +use mars_rover_health_types::{AccountKind, HealthResult, HealthState, HealthValuesResponse}; -use crate::{ - querier::HealthQuerier, - state::{CREDIT_MANAGER, PARAMS}, -}; +use crate::querier::HealthQuerier; /// Uses `mars-rover-health-computer` which is a data agnostic package given /// it's compiled to .wasm and shared with the frontend. /// This function queries all necessary data to pass to `HealthComputer`. pub fn compute_health( deps: Deps, - account_id: &str, kind: AccountKind, -) -> HealthResult { - let credit_manager_addr = CREDIT_MANAGER - .may_load(deps.storage)? - .ok_or(ContractNotSet("credit_manger".to_string()))?; - let params_contract_addr = - PARAMS.may_load(deps.storage)?.ok_or(ContractNotSet("params".to_string()))?; - - let querier = HealthQuerier::new(&deps.querier, &credit_manager_addr, ¶ms_contract_addr); - - let positions = querier.query_positions(account_id)?; - + q: HealthQuerier, + positions: Positions, +) -> HealthResult { // Get the denoms that need prices + markets let deposit_denoms = positions.deposits.iter().map(|d| &d.denom).collect::>(); let debt_denoms = positions.debts.iter().map(|d| &d.denom).collect::>(); @@ -44,7 +31,6 @@ pub fn compute_health( let vault_base_token_denoms = vault_infos.values().map(|v| &v.base_token).collect::>(); // Collect prices + asset - let (oracle, params) = querier.query_deps()?; let mut denoms_data: DenomsData = Default::default(); deposit_denoms .into_iter() @@ -52,9 +38,9 @@ pub fn compute_health( .chain(lend_denoms) .chain(vault_base_token_denoms) .try_for_each(|denom| -> StdResult<()> { - let price = oracle.query_price(&deps.querier, denom)?.price; + let price = q.oracle.query_price(&deps.querier, denom)?.price; denoms_data.prices.insert(denom.clone(), price); - let params = params.query_asset_params(&deps.querier, denom)?; + let params = q.params.query_asset_params(&deps.querier, denom)?; denoms_data.params.insert(denom.clone(), params); Ok(()) })?; @@ -62,9 +48,9 @@ pub fn compute_health( // Collect all vault data let mut vaults_data: VaultsData = Default::default(); positions.vaults.iter().try_for_each(|v| -> HealthResult<()> { - let vault_coin_value = v.query_values(&deps.querier, &oracle)?; + let vault_coin_value = v.query_values(&deps.querier, &q.oracle)?; vaults_data.vault_values.insert(v.vault.address.clone(), vault_coin_value); - let config = querier.query_vault_config(&v.vault)?; + let config = q.query_vault_config(&v.vault)?; vaults_data.vault_configs.insert(v.vault.address.clone(), config); Ok(()) })?; @@ -78,3 +64,33 @@ pub fn compute_health( Ok(computer.compute_health()?.into()) } + +pub fn health_values( + deps: Deps, + account_id: &str, + kind: AccountKind, +) -> HealthResult { + let q = HealthQuerier::new(&deps)?; + let positions = q.query_positions(account_id)?; + compute_health(deps, kind, q, positions) +} + +pub fn health_state(deps: Deps, account_id: &str, kind: AccountKind) -> HealthResult { + let q = HealthQuerier::new(&deps)?; + let positions = q.query_positions(account_id)?; + + // Helpful to not have to do computations & query the oracle for cases + // like liquidations where oracle circuit breakers may hinder it. + if positions.debts.is_empty() { + return Ok(HealthState::Healthy); + } + + let health = compute_health(deps, kind, q, positions)?; + if !health.above_max_ltv { + Ok(HealthState::Healthy) + } else { + Ok(HealthState::Unhealthy { + max_ltv_health_factor: health.max_ltv_health_factor.unwrap(), + }) + } +} diff --git a/contracts/health/src/contract.rs b/contracts/health/src/contract.rs index 5827df495..4997aaae2 100644 --- a/contracts/health/src/contract.rs +++ b/contracts/health/src/contract.rs @@ -6,8 +6,8 @@ use mars_owner::OwnerInit::SetInitialOwner; use mars_rover_health_types::{ConfigResponse, ExecuteMsg, HealthResult, InstantiateMsg, QueryMsg}; use crate::{ - compute::compute_health, - state::{CREDIT_MANAGER, OWNER, PARAMS}, + compute::{health_state, health_values}, + state::{CREDIT_MANAGER, OWNER}, update_config::update_config, }; @@ -45,18 +45,21 @@ pub fn execute( ExecuteMsg::UpdateOwner(update) => Ok(OWNER.update(deps, info, update)?), ExecuteMsg::UpdateConfig { credit_manager, - params, - } => update_config(deps, info, credit_manager, params), + } => update_config(deps, info, credit_manager), } } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> HealthResult { let res = match msg { - QueryMsg::Health { + QueryMsg::HealthValues { account_id, kind, - } => to_binary(&compute_health(deps, &account_id, kind)?), + } => to_binary(&health_values(deps, &account_id, kind)?), + QueryMsg::HealthState { + account_id, + kind, + } => to_binary(&health_state(deps, &account_id, kind)?), QueryMsg::Config {} => to_binary(&query_config(deps)?), }; res.map_err(Into::into) @@ -64,12 +67,10 @@ pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> HealthResult { pub fn query_config(deps: Deps) -> HealthResult { let credit_manager = CREDIT_MANAGER.may_load(deps.storage)?.map(Into::into); - let params = PARAMS.may_load(deps.storage)?.map(Into::into); let owner_response = OWNER.query(deps.storage)?; Ok(ConfigResponse { credit_manager, - params, owner_response, }) } diff --git a/contracts/health/src/querier.rs b/contracts/health/src/querier.rs index 2d1b0ee3f..0e3fe5c20 100644 --- a/contracts/health/src/querier.rs +++ b/contracts/health/src/querier.rs @@ -1,55 +1,46 @@ -use cosmwasm_std::{Addr, QuerierWrapper}; -use mars_params::{msg::QueryMsg as ParamsQueryMsg, types::vault::VaultConfig}; +use cosmwasm_std::{Addr, Deps, QuerierWrapper, StdResult}; +use mars_params::types::vault::VaultConfig; use mars_rover::{ adapters::{oracle::Oracle, params::Params, vault::Vault}, msg::query::{ConfigResponse, Positions, QueryMsg as CmQueryMsg}, }; use mars_rover_health_types::HealthResult; +use crate::state::CREDIT_MANAGER; + pub struct HealthQuerier<'a> { querier: &'a QuerierWrapper<'a>, - credit_manager_addr: &'a Addr, - params_contract_addr: &'a Addr, + credit_manager: Addr, + pub params: Params, + pub oracle: Oracle, } impl<'a> HealthQuerier<'a> { - pub fn new( - querier: &'a QuerierWrapper, - credit_manager_addr: &'a Addr, - params_contract_addr: &'a Addr, - ) -> Self { - Self { - querier, - credit_manager_addr, - params_contract_addr, - } + pub fn new(deps: &'a Deps) -> StdResult { + let credit_manager = CREDIT_MANAGER.load(deps.storage)?; + let config: ConfigResponse = + deps.querier.query_wasm_smart(credit_manager.to_string(), &CmQueryMsg::Config {})?; + + Ok(Self { + querier: &deps.querier, + credit_manager, + params: Params::new(Addr::unchecked(config.params)), + oracle: Oracle::new(Addr::unchecked(config.oracle)), + }) } pub fn query_positions(&self, account_id: &str) -> HealthResult { Ok(self.querier.query_wasm_smart( - self.credit_manager_addr.to_string(), + self.credit_manager.to_string(), &CmQueryMsg::Positions { account_id: account_id.to_string(), }, )?) } - pub fn query_deps(&self) -> HealthResult<(Oracle, Params)> { - let config: ConfigResponse = self - .querier - .query_wasm_smart(self.credit_manager_addr.to_string(), &CmQueryMsg::Config {})?; - Ok(( - Oracle::new(Addr::unchecked(config.oracle)), - Params::new(Addr::unchecked(config.params)), - )) - } - pub fn query_vault_config(&self, vault: &Vault) -> HealthResult { - Ok(self.querier.query_wasm_smart( - self.params_contract_addr.to_string(), - &ParamsQueryMsg::VaultConfig { - address: vault.address.to_string(), - }, - )?) + Ok(self + .params + .query_vault_config(self.querier, &Addr::unchecked(vault.address.to_string()))?) } } diff --git a/contracts/health/src/state.rs b/contracts/health/src/state.rs index 2236f49f9..b255706b0 100644 --- a/contracts/health/src/state.rs +++ b/contracts/health/src/state.rs @@ -4,4 +4,3 @@ use mars_owner::Owner; pub const OWNER: Owner = Owner::new("owner"); pub const CREDIT_MANAGER: Item = Item::new("credit_manager"); -pub const PARAMS: Item = Item::new("params"); diff --git a/contracts/health/src/update_config.rs b/contracts/health/src/update_config.rs index 9b52a9386..e0ea9efd3 100644 --- a/contracts/health/src/update_config.rs +++ b/contracts/health/src/update_config.rs @@ -1,31 +1,20 @@ use cosmwasm_std::{DepsMut, MessageInfo, Response}; use mars_rover_health_types::HealthResult; -use crate::state::{CREDIT_MANAGER, OWNER, PARAMS}; +use crate::state::{CREDIT_MANAGER, OWNER}; pub fn update_config( deps: DepsMut, info: MessageInfo, - credit_manager_opt: Option, - params_opt: Option, + credit_manager: String, ) -> HealthResult { OWNER.assert_owner(deps.storage, &info.sender)?; - let mut response = Response::new().add_attribute("action", "update_config"); + let validated = deps.api.addr_validate(&credit_manager)?; + CREDIT_MANAGER.save(deps.storage, &validated)?; - if let Some(cm) = credit_manager_opt { - let validated = deps.api.addr_validate(&cm)?; - CREDIT_MANAGER.save(deps.storage, &validated)?; - - response = response.add_attribute("key", "credit_manager_addr").add_attribute("value", cm); - } - - if let Some(params) = params_opt { - let validated = deps.api.addr_validate(¶ms)?; - PARAMS.save(deps.storage, &validated)?; - - response = response.add_attribute("key", "params_addr").add_attribute("value", params); - } - - Ok(response) + Ok(Response::new() + .add_attribute("action", "update_config") + .add_attribute("key", "credit_manager_addr") + .add_attribute("value", credit_manager)) } diff --git a/contracts/health/tests/helpers/mock_env.rs b/contracts/health/tests/helpers/mock_env.rs index d259ccaec..e8720d57a 100644 --- a/contracts/health/tests/helpers/mock_env.rs +++ b/contracts/health/tests/helpers/mock_env.rs @@ -17,7 +17,8 @@ use mars_params::{ }; use mars_rover::{adapters::vault::VaultUnchecked, msg::query::Positions}; use mars_rover_health_types::{ - AccountKind, ConfigResponse, ExecuteMsg::UpdateConfig, HealthResponse, QueryMsg, + AccountKind, ConfigResponse, ExecuteMsg::UpdateConfig, HealthState, HealthValuesResponse, + QueryMsg, }; use crate::helpers::MockEnvBuilder; @@ -40,7 +41,6 @@ impl MockEnv { deployer: Addr::unchecked("deployer"), health_contract: None, set_cm_config: true, - set_params_config: true, cm_contract: None, vault_contract: None, oracle: None, @@ -48,10 +48,28 @@ impl MockEnv { } } - pub fn query_health(&self, account_id: &str, kind: AccountKind) -> StdResult { + pub fn query_health_values( + &self, + account_id: &str, + kind: AccountKind, + ) -> StdResult { self.app.wrap().query_wasm_smart( self.health_contract.clone(), - &QueryMsg::Health { + &QueryMsg::HealthValues { + account_id: account_id.to_string(), + kind, + }, + ) + } + + pub fn query_health_state( + &self, + account_id: &str, + kind: AccountKind, + ) -> StdResult { + self.app.wrap().query_wasm_smart( + self.health_contract.clone(), + &QueryMsg::HealthState { account_id: account_id.to_string(), kind, }, @@ -80,15 +98,13 @@ impl MockEnv { pub fn update_config( &mut self, sender: &Addr, - credit_manager: Option, - params: Option, + credit_manager: String, ) -> AnyResult { self.app.execute_contract( sender.clone(), self.health_contract.clone(), &UpdateConfig { credit_manager, - params, }, &[], ) diff --git a/contracts/health/tests/helpers/mock_env_builder.rs b/contracts/health/tests/helpers/mock_env_builder.rs index 91da670f5..79edc2afc 100644 --- a/contracts/health/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/helpers/mock_env_builder.rs @@ -32,7 +32,6 @@ pub struct MockEnvBuilder { pub oracle: Option, pub params: Option, pub set_cm_config: bool, - pub set_params_config: bool, } impl MockEnvBuilder { @@ -41,10 +40,6 @@ impl MockEnvBuilder { self.add_cm_to_config(); } - if self.set_params_config { - self.add_params_to_config(); - } - Ok(MockEnv { deployer: self.deployer.clone(), health_contract: self.get_health_contract(), @@ -61,11 +56,6 @@ impl MockEnvBuilder { self } - pub fn skip_params_config(&mut self) -> &mut Self { - self.set_params_config = false; - self - } - fn add_cm_to_config(&mut self) { let health_contract = self.get_health_contract(); let cm_contract = self.get_cm_contract(); @@ -75,25 +65,7 @@ impl MockEnvBuilder { self.deployer.clone(), health_contract, &UpdateConfig { - credit_manager: Some(cm_contract.to_string()), - params: None, - }, - &[], - ) - .unwrap(); - } - - fn add_params_to_config(&mut self) { - let health_contract = self.get_health_contract(); - let params_contract = self.get_params_contract(); - - self.app - .execute_contract( - self.deployer.clone(), - health_contract, - &UpdateConfig { - credit_manager: None, - params: Some(params_contract.to_string()), + credit_manager: cm_contract.to_string(), }, &[], ) diff --git a/contracts/health/tests/test_health_state.rs b/contracts/health/tests/test_health_state.rs new file mode 100644 index 000000000..480b82b93 --- /dev/null +++ b/contracts/health/tests/test_health_state.rs @@ -0,0 +1,98 @@ +use cosmwasm_std::{Coin, Decimal, Uint128}; +use mars_params::msg::AssetParamsUpdate::AddOrUpdate; +use mars_rover::msg::query::{DebtAmount, Positions}; +use mars_rover_health_types::{AccountKind, HealthState}; + +use crate::helpers::{default_asset_params, MockEnv}; + +pub mod helpers; + +#[test] +fn zero_debts_results_in_healthy_state() { + let mut mock = MockEnv::new().build().unwrap(); + + let account_id = "1352524"; + mock.set_positions_response( + account_id, + &Positions { + account_id: account_id.to_string(), + deposits: vec![Coin { + denom: "xyz".to_string(), + amount: Uint128::one(), + }], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + ); + + let state = mock.query_health_state(account_id, AccountKind::Default).unwrap(); + + assert_eq!(state, HealthState::Healthy); +} + +#[test] +fn computing_health_when_healthy() { + let mut mock = MockEnv::new().build().unwrap(); + + let umars = "umars"; + mock.set_price(umars, Decimal::one()); + mock.update_asset_params(AddOrUpdate { + params: default_asset_params(umars), + }); + + let account_id = "123"; + mock.set_positions_response( + account_id, + &Positions { + account_id: account_id.to_string(), + deposits: vec![Coin { + denom: umars.to_string(), + amount: Uint128::new(100), + }], + debts: vec![DebtAmount { + denom: umars.to_string(), + shares: Default::default(), + amount: Uint128::new(30), + }], + lends: vec![], + vaults: vec![], + }, + ); + + let state = mock.query_health_state(account_id, AccountKind::Default).unwrap(); + assert_eq!(state, HealthState::Healthy); +} + +#[test] +fn computing_health_when_unhealthy() { + let mut mock = MockEnv::new().build().unwrap(); + + let umars = "umars"; + mock.set_price(umars, Decimal::one()); + mock.update_asset_params(AddOrUpdate { + params: default_asset_params(umars), + }); + + let account_id = "123"; + mock.set_positions_response( + account_id, + &Positions { + account_id: account_id.to_string(), + deposits: vec![Coin { + denom: umars.to_string(), + amount: Uint128::new(100), + }], + debts: vec![DebtAmount { + denom: umars.to_string(), + shares: Default::default(), + amount: Uint128::new(250), + }], + lends: vec![], + vaults: vec![], + }, + ); + + let state = mock.query_health_state(account_id, AccountKind::Default).unwrap(); + assert!(matches!(state, HealthState::Unhealthy { .. })); +} diff --git a/contracts/health/tests/test_compute_health.rs b/contracts/health/tests/test_health_values.rs similarity index 90% rename from contracts/health/tests/test_compute_health.rs rename to contracts/health/tests/test_health_values.rs index 0041e13ba..07adc497b 100644 --- a/contracts/health/tests/test_compute_health.rs +++ b/contracts/health/tests/test_health_values.rs @@ -24,23 +24,11 @@ pub mod helpers; #[test] fn raises_when_credit_manager_not_set() { let mock = MockEnv::new().skip_cm_config().build().unwrap(); - let err: StdError = mock.query_health("xyz", AccountKind::Default).unwrap_err(); + let err: StdError = mock.query_health_values("xyz", AccountKind::Default).unwrap_err(); assert_eq!( err, StdError::generic_err( - "Querier contract error: credit_manger address has not been set in config".to_string() - ) - ); -} - -#[test] -fn raises_when_params_contract_not_set() { - let mock = MockEnv::new().skip_params_config().build().unwrap(); - let err: StdError = mock.query_health("xyz", AccountKind::Default).unwrap_err(); - assert_eq!( - err, - StdError::generic_err( - "Querier contract error: params address has not been set in config".to_string() + "Querier contract error: cosmwasm_std::addresses::Addr not found".to_string() ) ); } @@ -48,7 +36,7 @@ fn raises_when_params_contract_not_set() { #[test] fn raises_with_non_existent_account_id() { let mock = MockEnv::new().build().unwrap(); - let err: StdError = mock.query_health("xyz", AccountKind::Default).unwrap_err(); + let err: StdError = mock.query_health_values("xyz", AccountKind::Default).unwrap_err(); assert_eq!( err, StdError::generic_err( @@ -74,7 +62,7 @@ fn computes_correct_position_with_zero_assets() { }, ); - let health = mock.query_health(account_id, AccountKind::Default).unwrap(); + let health = mock.query_health_values(account_id, AccountKind::Default).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, Uint128::zero()); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); @@ -153,7 +141,7 @@ fn adds_vault_base_denoms_to_oracle_and_red_bank() { mock.set_price(vault_base_token, Decimal::one()); - let health = mock.query_health(account_id, AccountKind::Default).unwrap(); + let health = mock.query_health_values(account_id, AccountKind::Default).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, unlocking_amount); assert_eq!( @@ -231,7 +219,7 @@ fn whitelisted_coins_work() { }, ); - let health = mock.query_health(account_id, AccountKind::Default).unwrap(); + let health = mock.query_health_values(account_id, AccountKind::Default).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, deposit_amount); // price of 1 assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); // coin not in whitelist @@ -249,7 +237,7 @@ fn whitelisted_coins_work() { mock.update_asset_params(AddOrUpdate { params: asset_params, }); - let health = mock.query_health(account_id, AccountKind::Default).unwrap(); + let health = mock.query_health_values(account_id, AccountKind::Default).unwrap(); // Now reflects deposit value assert_eq!( health.max_ltv_adjusted_collateral, @@ -319,7 +307,7 @@ fn vault_whitelist_affects_max_ltv() { let mut vault_config = mock.query_vault_config(&vault.into()); - let health = mock.query_health(account_id, AccountKind::Default).unwrap(); + let health = mock.query_health_values(account_id, AccountKind::Default).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, base_token_amount); assert_eq!( @@ -342,6 +330,6 @@ fn vault_whitelist_affects_max_ltv() { config: vault_config.into(), }); - let health = mock.query_health(account_id, AccountKind::Default).unwrap(); + let health = mock.query_health_values(account_id, AccountKind::Default).unwrap(); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); } diff --git a/contracts/health/tests/test_hls.rs b/contracts/health/tests/test_hls.rs index 21e90b173..15fbf8df5 100644 --- a/contracts/health/tests/test_hls.rs +++ b/contracts/health/tests/test_hls.rs @@ -55,7 +55,7 @@ fn hls_account_kind_passed_along() { let vault_config = mock.query_vault_config(&vault.into()); - let health = mock.query_health(account_id, AccountKind::HighLeveredStrategy).unwrap(); + let health = mock.query_health_values(account_id, AccountKind::HighLeveredStrategy).unwrap(); assert_eq!(health.total_debt_value, positions.debts.first().unwrap().amount); assert_eq!(health.total_collateral_value, base_token_amount); assert_eq!( diff --git a/contracts/health/tests/test_update_config.rs b/contracts/health/tests/test_update_config.rs index 0502baf70..c7f51a41e 100644 --- a/contracts/health/tests/test_update_config.rs +++ b/contracts/health/tests/test_update_config.rs @@ -16,7 +16,7 @@ fn only_owner_can_update_config() { let new_cm_addr = "xyz".to_string(); let bad_guy = Addr::unchecked("bad_guy"); let err: HealthError = - mock.update_config(&bad_guy, Some(new_cm_addr), None).unwrap_err().downcast().unwrap(); + mock.update_config(&bad_guy, new_cm_addr).unwrap_err().downcast().unwrap(); assert_eq!(err, Owner(NotOwner {})); } @@ -25,11 +25,8 @@ fn only_owner_can_update_config() { fn raises_on_invalid_config() { let mut mock = MockEnv::new().build().unwrap(); - let err: HealthError = mock - .update_config(&mock.deployer.clone(), Some("".to_string()), None) - .unwrap_err() - .downcast() - .unwrap(); + let err: HealthError = + mock.update_config(&mock.deployer.clone(), "".to_string()).unwrap_err().downcast().unwrap(); assert_eq!( err, @@ -39,33 +36,15 @@ fn raises_on_invalid_config() { ); } -#[test] -fn update_partial_config_works() { - let mut mock = MockEnv::new().skip_params_config().skip_cm_config().build().unwrap(); - - mock.update_config(&mock.deployer.clone(), Some("abc".to_string()), None).unwrap(); - - let new_config = mock.query_config(); - - assert_eq!(new_config.credit_manager, Some("abc".to_string())); - assert_eq!(new_config.params, None); - assert_eq!(new_config.owner_response.owner, Some(mock.deployer.to_string())); - assert_eq!(new_config.owner_response.proposed, None); - assert!(new_config.owner_response.initialized); - assert!(!new_config.owner_response.abolished); -} - #[test] fn update_full_config_works() { - let mut mock = MockEnv::new().skip_params_config().skip_cm_config().build().unwrap(); + let mut mock = MockEnv::new().skip_cm_config().build().unwrap(); - mock.update_config(&mock.deployer.clone(), Some("abc".to_string()), Some("xyz".to_string())) - .unwrap(); + mock.update_config(&mock.deployer.clone(), "abc".to_string()).unwrap(); let new_config = mock.query_config(); assert_eq!(new_config.credit_manager, Some("abc".to_string())); - assert_eq!(new_config.params, Some("xyz".to_string())); assert_eq!(new_config.owner_response.owner, Some(mock.deployer.to_string())); assert_eq!(new_config.owner_response.proposed, None); assert!(new_config.owner_response.initialized); diff --git a/contracts/mock-health/src/contract.rs b/contracts/mock-health/src/contract.rs index c94e9d3f9..1c386e398 100644 --- a/contracts/mock-health/src/contract.rs +++ b/contracts/mock-health/src/contract.rs @@ -3,7 +3,7 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{ to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; -use mars_rover_health_types::{AccountKind, HealthResponse, HealthResult, QueryMsg}; +use mars_rover_health_types::{AccountKind, HealthResult, HealthValuesResponse, QueryMsg}; use crate::{msg::ExecuteMsg, state::HEALTH_RESPONSES}; @@ -27,7 +27,7 @@ pub fn set_health_response( deps: DepsMut, account_id: String, kind: AccountKind, - response: HealthResponse, + response: HealthValuesResponse, ) -> HealthResult { HEALTH_RESPONSES.save(deps.storage, (&account_id, &kind.to_string()), &response)?; Ok(Response::new()) @@ -36,7 +36,7 @@ pub fn set_health_response( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> HealthResult { let res = match msg { - QueryMsg::Health { + QueryMsg::HealthValues { account_id, kind, } => to_binary(&query_health(deps, &account_id, kind)?), @@ -45,6 +45,10 @@ pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> HealthResult { res.map_err(Into::into) } -pub fn query_health(deps: Deps, account_id: &str, kind: AccountKind) -> StdResult { +pub fn query_health( + deps: Deps, + account_id: &str, + kind: AccountKind, +) -> StdResult { HEALTH_RESPONSES.load(deps.storage, (account_id, &kind.to_string())) } diff --git a/contracts/mock-health/src/msg.rs b/contracts/mock-health/src/msg.rs index 8f224d78f..76999e1df 100644 --- a/contracts/mock-health/src/msg.rs +++ b/contracts/mock-health/src/msg.rs @@ -1,11 +1,11 @@ use cosmwasm_schema::cw_serde; -use mars_rover_health_types::{AccountKind, HealthResponse}; +use mars_rover_health_types::{AccountKind, HealthValuesResponse}; #[cw_serde] pub enum ExecuteMsg { SetHealthResponse { account_id: String, kind: AccountKind, - response: HealthResponse, + response: HealthValuesResponse, }, } diff --git a/contracts/mock-health/src/state.rs b/contracts/mock-health/src/state.rs index 21d6f69cb..b29a02281 100644 --- a/contracts/mock-health/src/state.rs +++ b/contracts/mock-health/src/state.rs @@ -1,4 +1,4 @@ use cw_storage_plus::Map; -use mars_rover_health_types::HealthResponse; +use mars_rover_health_types::HealthValuesResponse; -pub const HEALTH_RESPONSES: Map<(&str, &str), HealthResponse> = Map::new("health_responses"); // Map<(account_id, AccountKind string), HealthResponse> +pub const HEALTH_RESPONSES: Map<(&str, &str), HealthValuesResponse> = Map::new("health_responses"); // Map<(account_id, AccountKind string), HealthResponse> diff --git a/packages/health-computer/src/javascript.rs b/packages/health-computer/src/javascript.rs index 4b7cc7185..6605b2d47 100644 --- a/packages/health-computer/src/javascript.rs +++ b/packages/health-computer/src/javascript.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::serde::{de::DeserializeOwned, Serialize}; -use mars_rover_health_types::HealthResponse; +use mars_rover_health_types::HealthValuesResponse; use wasm_bindgen::prelude::*; use crate::HealthComputer; @@ -8,7 +8,7 @@ use crate::HealthComputer; pub fn compute_health_js(health_computer: JsValue) -> JsValue { let c: HealthComputer = deserialize(health_computer); let health = c.compute_health().unwrap(); - let health_response: HealthResponse = health.into(); + let health_response: HealthValuesResponse = health.into(); serialize(health_response) } diff --git a/packages/health-types/src/health.rs b/packages/health-types/src/health.rs index 88af3b4b8..7e173726c 100644 --- a/packages/health-types/src/health.rs +++ b/packages/health-types/src/health.rs @@ -51,7 +51,7 @@ pub fn is_below_one(health_factor: &Option) -> bool { } #[cw_serde] -pub struct HealthResponse { +pub struct HealthValuesResponse { pub total_debt_value: Uint128, pub total_collateral_value: Uint128, pub max_ltv_adjusted_collateral: Uint128, @@ -62,7 +62,7 @@ pub struct HealthResponse { pub above_max_ltv: bool, } -impl From for HealthResponse { +impl From for HealthValuesResponse { fn from(h: Health) -> Self { Self { total_debt_value: h.total_debt_value, @@ -76,3 +76,11 @@ impl From for HealthResponse { } } } + +#[cw_serde] +pub enum HealthState { + Healthy, + Unhealthy { + max_ltv_health_factor: Decimal, + }, +} diff --git a/packages/health-types/src/msg.rs b/packages/health-types/src/msg.rs index 2006ffac1..869a93c0b 100644 --- a/packages/health-types/src/msg.rs +++ b/packages/health-types/src/msg.rs @@ -15,16 +15,23 @@ pub enum ExecuteMsg { UpdateOwner(OwnerUpdate), /// Update contract config constants UpdateConfig { - credit_manager: Option, - params: Option, + credit_manager: String, }, } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - #[returns(crate::HealthResponse)] - Health { + /// Returns all values that comprise health for account + #[returns(crate::HealthValuesResponse)] + HealthValues { + account_id: String, + kind: AccountKind, + }, + /// Returns Healthy or Unhealthy state. Does not do health calculations if no debt. + /// This is helpful in the cases like liquidation where we should not query the oracle if can help it. + #[returns(crate::HealthState)] + HealthState { account_id: String, kind: AccountKind, }, @@ -35,6 +42,5 @@ pub enum QueryMsg { #[cw_serde] pub struct ConfigResponse { pub credit_manager: Option, - pub params: Option, pub owner_response: OwnerResponse, } diff --git a/packages/rover/src/adapters/health.rs b/packages/rover/src/adapters/health.rs index 125424c92..d3a6b89cf 100644 --- a/packages/rover/src/adapters/health.rs +++ b/packages/rover/src/adapters/health.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, QuerierWrapper, StdResult}; -use mars_rover_health_types::{AccountKind, HealthResponse, QueryMsg}; +use mars_rover_health_types::{AccountKind, HealthValuesResponse, QueryMsg}; #[cw_serde] pub struct HealthContractBase(T); @@ -36,10 +36,10 @@ impl HealthContract { querier: &QuerierWrapper, account_id: &str, kind: AccountKind, - ) -> StdResult { + ) -> StdResult { querier.query_wasm_smart( self.address().to_string(), - &QueryMsg::Health { + &QueryMsg::HealthValues { account_id: account_id.to_string(), kind, }, diff --git a/schemas/mars-rover-health-types/mars-rover-health-types.json b/schemas/mars-rover-health-types/mars-rover-health-types.json index d9f2488b5..9e4b37fa7 100644 --- a/schemas/mars-rover-health-types/mars-rover-health-types.json +++ b/schemas/mars-rover-health-types/mars-rover-health-types.json @@ -43,18 +43,12 @@ "properties": { "update_config": { "type": "object", + "required": [ + "credit_manager" + ], "properties": { "credit_manager": { - "type": [ - "string", - "null" - ] - }, - "params": { - "type": [ - "string", - "null" - ] + "type": "string" } }, "additionalProperties": false @@ -147,12 +141,39 @@ "title": "QueryMsg", "oneOf": [ { + "description": "Returns all values that comprise health for account", + "type": "object", + "required": [ + "health_values" + ], + "properties": { + "health_values": { + "type": "object", + "required": [ + "account_id", + "kind" + ], + "properties": { + "account_id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/AccountKind" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns Healthy or Unhealthy state. Does not do health calculations if no debt. This is helpful in the cases like liquidation where we should not query the oracle if can help it.", "type": "object", "required": [ - "health" + "health_state" ], "properties": { - "health": { + "health_state": { "type": "object", "required": [ "account_id", @@ -214,12 +235,6 @@ }, "owner_response": { "$ref": "#/definitions/OwnerResponse" - }, - "params": { - "type": [ - "string", - "null" - ] } }, "additionalProperties": false, @@ -261,9 +276,48 @@ } } }, - "health": { + "health_state": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HealthState", + "oneOf": [ + { + "type": "string", + "enum": [ + "healthy" + ] + }, + { + "type": "object", + "required": [ + "unhealthy" + ], + "properties": { + "unhealthy": { + "type": "object", + "required": [ + "max_ltv_health_factor" + ], + "properties": { + "max_ltv_health_factor": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "health_values": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HealthResponse", + "title": "HealthValuesResponse", "type": "object", "required": [ "above_max_ltv", diff --git a/schemas/mars-rover-health/mars-rover-health.json b/schemas/mars-rover-health/mars-rover-health.json index 181685a9a..fd7502044 100644 --- a/schemas/mars-rover-health/mars-rover-health.json +++ b/schemas/mars-rover-health/mars-rover-health.json @@ -43,18 +43,12 @@ "properties": { "update_config": { "type": "object", + "required": [ + "credit_manager" + ], "properties": { "credit_manager": { - "type": [ - "string", - "null" - ] - }, - "params": { - "type": [ - "string", - "null" - ] + "type": "string" } }, "additionalProperties": false @@ -147,12 +141,39 @@ "title": "QueryMsg", "oneOf": [ { + "description": "Returns all values that comprise health for account", + "type": "object", + "required": [ + "health_values" + ], + "properties": { + "health_values": { + "type": "object", + "required": [ + "account_id", + "kind" + ], + "properties": { + "account_id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/AccountKind" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns Healthy or Unhealthy state. Does not do health calculations if no debt. This is helpful in the cases like liquidation where we should not query the oracle if can help it.", "type": "object", "required": [ - "health" + "health_state" ], "properties": { - "health": { + "health_state": { "type": "object", "required": [ "account_id", @@ -214,12 +235,6 @@ }, "owner_response": { "$ref": "#/definitions/OwnerResponse" - }, - "params": { - "type": [ - "string", - "null" - ] } }, "additionalProperties": false, @@ -261,9 +276,48 @@ } } }, - "health": { + "health_state": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HealthState", + "oneOf": [ + { + "type": "string", + "enum": [ + "healthy" + ] + }, + { + "type": "object", + "required": [ + "unhealthy" + ], + "properties": { + "unhealthy": { + "type": "object", + "required": [ + "max_ltv_health_factor" + ], + "properties": { + "max_ltv_health_factor": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "health_values": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HealthResponse", + "title": "HealthValuesResponse", "type": "object", "required": [ "above_max_ltv", diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 1723edaa5..9027bd95e 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -99,7 +99,6 @@ export class Deployer { printBlue('Setting credit manager address & params on health contract config') await hExec.updateConfig({ creditManager: this.storage.addresses.creditManager!, - params: this.config.params.addr, }) } this.storage.actions.healthContractConfigUpdate = true diff --git a/scripts/health/DataFetcher.ts b/scripts/health/DataFetcher.ts index 139d21024..bf4e028f0 100644 --- a/scripts/health/DataFetcher.ts +++ b/scripts/health/DataFetcher.ts @@ -1,7 +1,7 @@ import { Positions } from '../types/generated/mars-credit-manager/MarsCreditManager.types' import { MarsCreditManagerQueryClient } from '../types/generated/mars-credit-manager/MarsCreditManager.client' import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient' -import { HealthResponse } from '../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' +import { HealthValuesResponse } from '../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' import { AccountKind, DenomsData, @@ -14,7 +14,7 @@ import { MarsParamsQueryClient } from '../types/generated/mars-params/MarsParams export class DataFetcher { constructor( - private computeHealthFn: (h: HealthComputer) => HealthResponse, + private computeHealthFn: (h: HealthComputer) => HealthValuesResponse, private maxWithdrawFn: (h: HealthComputer, denom: string) => string, private maxBorrowFn: (h: HealthComputer, denom: string) => string, private creditManagerAddr: string, @@ -108,7 +108,7 @@ export class DataFetcher { } } - computeHealth = async (accountId: string): Promise => { + computeHealth = async (accountId: string): Promise => { const positions = await this.assembleComputer(accountId) return this.computeHealthFn(positions) } diff --git a/scripts/health/example-react/src/utils.ts b/scripts/health/example-react/src/utils.ts index ce8d5fffe..acc16a9b2 100644 --- a/scripts/health/example-react/src/utils.ts +++ b/scripts/health/example-react/src/utils.ts @@ -5,7 +5,7 @@ import init, { max_withdraw_estimate_js, max_borrow_estimate_js, } from '../../pkg-web' -import { HealthResponse } from '../../../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' +import { HealthValuesResponse } from '../../../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' import { DataFetcher } from '../../DataFetcher' import { osmosisTestnetConfig } from '../../../deploy/osmosis/testnet-config' @@ -29,7 +29,7 @@ export const fetchPositions = async (cmAddress: string, accountId: string): Prom export const fetchHealth = async ( cmAddress: string, accountId: string, -): Promise => { +): Promise => { await init() const dataFetcher = getFetcher(cmAddress) return await dataFetcher.computeHealth(accountId) diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js index 22cc808d4..3cb48434d 100644 --- a/scripts/health/pkg-node/index.js +++ b/scripts/health/pkg-node/index.js @@ -283,6 +283,12 @@ module.exports.__wbindgen_error_new = function (arg0, arg1) { return addHeapObject(ret) } +module.exports.__wbindgen_boolean_get = function (arg0) { + const v = getObject(arg0) + const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 + return ret +} + module.exports.__wbindgen_is_string = function (arg0) { const ret = typeof getObject(arg0) === 'string' return ret @@ -303,12 +309,6 @@ module.exports.__wbindgen_jsval_eq = function (arg0, arg1) { return ret } -module.exports.__wbindgen_boolean_get = function (arg0) { - const v = getObject(arg0) - const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 - return ret -} - module.exports.__wbg_new_abda76e883ba8a5f = function () { const ret = new Error() return addHeapObject(ret) diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index e875fbba437f56f56b9718ce18672229efab7472..a1e5bc5a1076f46ce804587b2fd013290b436cee 100644 GIT binary patch literal 175576 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 literal 177904 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 D-<2&> diff --git a/scripts/health/pkg-web/index.js b/scripts/health/pkg-web/index.js index 34c34d94c..90a4e193c 100644 --- a/scripts/health/pkg-web/index.js +++ b/scripts/health/pkg-web/index.js @@ -323,6 +323,11 @@ function __wbg_get_imports() { const ret = new Error(getStringFromWasm0(arg0, arg1)) return addHeapObject(ret) } + imports.wbg.__wbindgen_boolean_get = function (arg0) { + const v = getObject(arg0) + const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 + return ret + } imports.wbg.__wbindgen_is_string = function (arg0) { const ret = typeof getObject(arg0) === 'string' return ret @@ -339,11 +344,6 @@ function __wbg_get_imports() { const ret = getObject(arg0) === getObject(arg1) return ret } - imports.wbg.__wbindgen_boolean_get = function (arg0) { - const v = getObject(arg0) - const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 - return ret - } imports.wbg.__wbg_new_abda76e883ba8a5f = function () { const ret = new Error() return addHeapObject(ret) diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm index a581775df9f276f70f59b65b4dcb2f38ebd64435..5b262d3878132535741fe4aa8670aeace7c60bf2 100644 GIT binary patch literal 174652 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%0A=!ZXcA+~e;A$-I~2W0;b>*w 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%0DRg7em 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 literal 176980 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

@l?raTVKd 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 diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts index 1ce2076f1..84e1b32c4 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts @@ -15,13 +15,27 @@ import { AccountKind, ConfigResponse, OwnerResponse, + HealthState, Decimal, Uint128, - HealthResponse, + HealthValuesResponse, } from './MarsRoverHealthTypes.types' export interface MarsRoverHealthTypesReadOnlyInterface { contractAddress: string - health: ({ accountId, kind }: { accountId: string; kind: AccountKind }) => Promise + healthValues: ({ + accountId, + kind, + }: { + accountId: string + kind: AccountKind + }) => Promise + healthState: ({ + accountId, + kind, + }: { + accountId: string + kind: AccountKind + }) => Promise config: () => Promise } export class MarsRoverHealthTypesQueryClient implements MarsRoverHealthTypesReadOnlyInterface { @@ -31,19 +45,34 @@ export class MarsRoverHealthTypesQueryClient implements MarsRoverHealthTypesRead constructor(client: CosmWasmClient, contractAddress: string) { this.client = client this.contractAddress = contractAddress - this.health = this.health.bind(this) + this.healthValues = this.healthValues.bind(this) + this.healthState = this.healthState.bind(this) this.config = this.config.bind(this) } - health = async ({ + healthValues = async ({ + accountId, + kind, + }: { + accountId: string + kind: AccountKind + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + health_values: { + account_id: accountId, + kind, + }, + }) + } + healthState = async ({ accountId, kind, }: { accountId: string kind: AccountKind - }): Promise => { + }): Promise => { return this.client.queryContractSmart(this.contractAddress, { - health: { + health_state: { account_id: accountId, kind, }, @@ -67,10 +96,8 @@ export interface MarsRoverHealthTypesInterface extends MarsRoverHealthTypesReadO updateConfig: ( { creditManager, - params, }: { - creditManager?: string - params?: string + creditManager: string }, fee?: number | StdFee | 'auto', memo?: string, @@ -114,10 +141,8 @@ export class MarsRoverHealthTypesClient updateConfig = async ( { creditManager, - params, }: { - creditManager?: string - params?: string + creditManager: string }, fee: number | StdFee | 'auto' = 'auto', memo?: string, @@ -129,7 +154,6 @@ export class MarsRoverHealthTypesClient { update_config: { credit_manager: creditManager, - params, }, }, fee, diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts index 1c8ce1020..b1e527f2c 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts @@ -17,9 +17,10 @@ import { AccountKind, ConfigResponse, OwnerResponse, + HealthState, Decimal, Uint128, - HealthResponse, + HealthValuesResponse, } from './MarsRoverHealthTypes.types' export interface MarsRoverHealthTypesMessage { contractAddress: string @@ -28,10 +29,8 @@ export interface MarsRoverHealthTypesMessage { updateConfig: ( { creditManager, - params, }: { - creditManager?: string - params?: string + creditManager: string }, _funds?: Coin[], ) => MsgExecuteContractEncodeObject @@ -65,10 +64,8 @@ export class MarsRoverHealthTypesMessageComposer implements MarsRoverHealthTypes updateConfig = ( { creditManager, - params, }: { - creditManager?: string - params?: string + creditManager: string }, _funds?: Coin[], ): MsgExecuteContractEncodeObject => { @@ -81,7 +78,6 @@ export class MarsRoverHealthTypesMessageComposer implements MarsRoverHealthTypes JSON.stringify({ update_config: { credit_manager: creditManager, - params, }, }), ), diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts index 196536783..30696bad7 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts @@ -16,9 +16,10 @@ import { AccountKind, ConfigResponse, OwnerResponse, + HealthState, Decimal, Uint128, - HealthResponse, + HealthValuesResponse, } from './MarsRoverHealthTypes.types' import { MarsRoverHealthTypesQueryClient, @@ -32,9 +33,21 @@ export const marsRoverHealthTypesQueryKeys = { ] as const, address: (contractAddress: string | undefined) => [{ ...marsRoverHealthTypesQueryKeys.contract[0], address: contractAddress }] as const, - health: (contractAddress: string | undefined, args?: Record) => + healthValues: (contractAddress: string | undefined, args?: Record) => [ - { ...marsRoverHealthTypesQueryKeys.address(contractAddress)[0], method: 'health', args }, + { + ...marsRoverHealthTypesQueryKeys.address(contractAddress)[0], + method: 'health_values', + args, + }, + ] as const, + healthState: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsRoverHealthTypesQueryKeys.address(contractAddress)[0], + method: 'health_state', + args, + }, ] as const, config: (contractAddress: string | undefined, args?: Record) => [ @@ -62,23 +75,47 @@ export function useMarsRoverHealthTypesConfigQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsRoverHealthTypesHealthQuery - extends MarsRoverHealthTypesReactQuery { +export interface MarsRoverHealthTypesHealthStateQuery + extends MarsRoverHealthTypesReactQuery { + args: { + accountId: string + kind: AccountKind + } +} +export function useMarsRoverHealthTypesHealthStateQuery({ + client, + args, + options, +}: MarsRoverHealthTypesHealthStateQuery) { + return useQuery( + marsRoverHealthTypesQueryKeys.healthState(client?.contractAddress, args), + () => + client + ? client.healthState({ + accountId: args.accountId, + kind: args.kind, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsRoverHealthTypesHealthValuesQuery + extends MarsRoverHealthTypesReactQuery { args: { accountId: string kind: AccountKind } } -export function useMarsRoverHealthTypesHealthQuery({ +export function useMarsRoverHealthTypesHealthValuesQuery({ client, args, options, -}: MarsRoverHealthTypesHealthQuery) { - return useQuery( - marsRoverHealthTypesQueryKeys.health(client?.contractAddress, args), +}: MarsRoverHealthTypesHealthValuesQuery) { + return useQuery( + marsRoverHealthTypesQueryKeys.healthValues(client?.contractAddress, args), () => client - ? client.health({ + ? client.healthValues({ accountId: args.accountId, kind: args.kind, }) @@ -89,8 +126,7 @@ export function useMarsRoverHealthTypesHealthQuery({ export interface MarsRoverHealthTypesUpdateConfigMutation { client: MarsRoverHealthTypesClient msg: { - creditManager?: string - params?: string + creditManager: string } args?: { fee?: number | StdFee | 'auto' diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts index 3ef810450..3c3220122 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts @@ -14,8 +14,7 @@ export type ExecuteMsg = } | { update_config: { - credit_manager?: string | null - params?: string | null + credit_manager: string } } export type OwnerUpdate = @@ -35,7 +34,13 @@ export type OwnerUpdate = | 'clear_emergency_owner' export type QueryMsg = | { - health: { + health_values: { + account_id: string + kind: AccountKind + } + } + | { + health_state: { account_id: string kind: AccountKind } @@ -47,7 +52,6 @@ export type AccountKind = 'default' | 'high_levered_strategy' export interface ConfigResponse { credit_manager?: string | null owner_response: OwnerResponse - params?: string | null } export interface OwnerResponse { abolished: boolean @@ -56,9 +60,16 @@ export interface OwnerResponse { owner?: string | null proposed?: string | null } +export type HealthState = + | 'healthy' + | { + unhealthy: { + max_ltv_health_factor: Decimal + } + } export type Decimal = string export type Uint128 = string -export interface HealthResponse { +export interface HealthValuesResponse { above_max_ltv: boolean liquidatable: boolean liquidation_health_factor?: Decimal | null diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.client.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.client.ts index 18ad4cdac..5c8b18f5c 100644 --- a/scripts/types/generated/mars-rover-health/MarsRoverHealth.client.ts +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.client.ts @@ -15,13 +15,27 @@ import { AccountKind, ConfigResponse, OwnerResponse, + HealthState, Decimal, Uint128, - HealthResponse, + HealthValuesResponse, } from './MarsRoverHealth.types' export interface MarsRoverHealthReadOnlyInterface { contractAddress: string - health: ({ accountId, kind }: { accountId: string; kind: AccountKind }) => Promise + healthValues: ({ + accountId, + kind, + }: { + accountId: string + kind: AccountKind + }) => Promise + healthState: ({ + accountId, + kind, + }: { + accountId: string + kind: AccountKind + }) => Promise config: () => Promise } export class MarsRoverHealthQueryClient implements MarsRoverHealthReadOnlyInterface { @@ -31,19 +45,34 @@ export class MarsRoverHealthQueryClient implements MarsRoverHealthReadOnlyInterf constructor(client: CosmWasmClient, contractAddress: string) { this.client = client this.contractAddress = contractAddress - this.health = this.health.bind(this) + this.healthValues = this.healthValues.bind(this) + this.healthState = this.healthState.bind(this) this.config = this.config.bind(this) } - health = async ({ + healthValues = async ({ + accountId, + kind, + }: { + accountId: string + kind: AccountKind + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + health_values: { + account_id: accountId, + kind, + }, + }) + } + healthState = async ({ accountId, kind, }: { accountId: string kind: AccountKind - }): Promise => { + }): Promise => { return this.client.queryContractSmart(this.contractAddress, { - health: { + health_state: { account_id: accountId, kind, }, @@ -67,10 +96,8 @@ export interface MarsRoverHealthInterface extends MarsRoverHealthReadOnlyInterfa updateConfig: ( { creditManager, - params, }: { - creditManager?: string - params?: string + creditManager: string }, fee?: number | StdFee | 'auto', memo?: string, @@ -114,10 +141,8 @@ export class MarsRoverHealthClient updateConfig = async ( { creditManager, - params, }: { - creditManager?: string - params?: string + creditManager: string }, fee: number | StdFee | 'auto' = 'auto', memo?: string, @@ -129,7 +154,6 @@ export class MarsRoverHealthClient { update_config: { credit_manager: creditManager, - params, }, }, fee, diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts index 68c87a4d0..8999a8bef 100644 --- a/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts @@ -17,9 +17,10 @@ import { AccountKind, ConfigResponse, OwnerResponse, + HealthState, Decimal, Uint128, - HealthResponse, + HealthValuesResponse, } from './MarsRoverHealth.types' export interface MarsRoverHealthMessage { contractAddress: string @@ -28,10 +29,8 @@ export interface MarsRoverHealthMessage { updateConfig: ( { creditManager, - params, }: { - creditManager?: string - params?: string + creditManager: string }, _funds?: Coin[], ) => MsgExecuteContractEncodeObject @@ -65,10 +64,8 @@ export class MarsRoverHealthMessageComposer implements MarsRoverHealthMessage { updateConfig = ( { creditManager, - params, }: { - creditManager?: string - params?: string + creditManager: string }, _funds?: Coin[], ): MsgExecuteContractEncodeObject => { @@ -81,7 +78,6 @@ export class MarsRoverHealthMessageComposer implements MarsRoverHealthMessage { JSON.stringify({ update_config: { credit_manager: creditManager, - params, }, }), ), diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts index fcd0ecc5d..d14327817 100644 --- a/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts @@ -16,9 +16,10 @@ import { AccountKind, ConfigResponse, OwnerResponse, + HealthState, Decimal, Uint128, - HealthResponse, + HealthValuesResponse, } from './MarsRoverHealth.types' import { MarsRoverHealthQueryClient, MarsRoverHealthClient } from './MarsRoverHealth.client' export const marsRoverHealthQueryKeys = { @@ -29,8 +30,14 @@ export const marsRoverHealthQueryKeys = { ] as const, address: (contractAddress: string | undefined) => [{ ...marsRoverHealthQueryKeys.contract[0], address: contractAddress }] as const, - health: (contractAddress: string | undefined, args?: Record) => - [{ ...marsRoverHealthQueryKeys.address(contractAddress)[0], method: 'health', args }] as const, + healthValues: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsRoverHealthQueryKeys.address(contractAddress)[0], method: 'health_values', args }, + ] as const, + healthState: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsRoverHealthQueryKeys.address(contractAddress)[0], method: 'health_state', args }, + ] as const, config: (contractAddress: string | undefined, args?: Record) => [{ ...marsRoverHealthQueryKeys.address(contractAddress)[0], method: 'config', args }] as const, } @@ -55,23 +62,47 @@ export function useMarsRoverHealthConfigQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsRoverHealthHealthQuery - extends MarsRoverHealthReactQuery { +export interface MarsRoverHealthHealthStateQuery + extends MarsRoverHealthReactQuery { args: { accountId: string kind: AccountKind } } -export function useMarsRoverHealthHealthQuery({ +export function useMarsRoverHealthHealthStateQuery({ client, args, options, -}: MarsRoverHealthHealthQuery) { - return useQuery( - marsRoverHealthQueryKeys.health(client?.contractAddress, args), +}: MarsRoverHealthHealthStateQuery) { + return useQuery( + marsRoverHealthQueryKeys.healthState(client?.contractAddress, args), () => client - ? client.health({ + ? client.healthState({ + accountId: args.accountId, + kind: args.kind, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsRoverHealthHealthValuesQuery + extends MarsRoverHealthReactQuery { + args: { + accountId: string + kind: AccountKind + } +} +export function useMarsRoverHealthHealthValuesQuery({ + client, + args, + options, +}: MarsRoverHealthHealthValuesQuery) { + return useQuery( + marsRoverHealthQueryKeys.healthValues(client?.contractAddress, args), + () => + client + ? client.healthValues({ accountId: args.accountId, kind: args.kind, }) @@ -82,8 +113,7 @@ export function useMarsRoverHealthHealthQuery({ export interface MarsRoverHealthUpdateConfigMutation { client: MarsRoverHealthClient msg: { - creditManager?: string - params?: string + creditManager: string } args?: { fee?: number | StdFee | 'auto' diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts index 3ef810450..3c3220122 100644 --- a/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts @@ -14,8 +14,7 @@ export type ExecuteMsg = } | { update_config: { - credit_manager?: string | null - params?: string | null + credit_manager: string } } export type OwnerUpdate = @@ -35,7 +34,13 @@ export type OwnerUpdate = | 'clear_emergency_owner' export type QueryMsg = | { - health: { + health_values: { + account_id: string + kind: AccountKind + } + } + | { + health_state: { account_id: string kind: AccountKind } @@ -47,7 +52,6 @@ export type AccountKind = 'default' | 'high_levered_strategy' export interface ConfigResponse { credit_manager?: string | null owner_response: OwnerResponse - params?: string | null } export interface OwnerResponse { abolished: boolean @@ -56,9 +60,16 @@ export interface OwnerResponse { owner?: string | null proposed?: string | null } +export type HealthState = + | 'healthy' + | { + unhealthy: { + max_ltv_health_factor: Decimal + } + } export type Decimal = string export type Uint128 = string -export interface HealthResponse { +export interface HealthValuesResponse { above_max_ltv: boolean liquidatable: boolean liquidation_health_factor?: Decimal | null From 8d1af2ccb28cc9becaa103fc06a7cdffb0c4a2f7 Mon Sep 17 00:00:00 2001 From: brimigs <85972460+brimigs@users.noreply.github.com> Date: Mon, 10 Jul 2023 09:16:03 -0400 Subject: [PATCH 177/218] Mp 3003 have lend take action coin (#153) --- Cargo.lock | 278 ++++++++++-------- contracts/credit-manager/src/execute.rs | 2 +- contracts/credit-manager/src/lend.rs | 64 ++-- .../tests/test_enumerate_lent_shares.rs | 99 ++++--- .../tests/test_enumerate_total_lent_shares.rs | 99 ++++--- .../credit-manager/tests/test_hls_accounts.rs | 2 +- contracts/credit-manager/tests/test_lend.rs | 159 +++++++++- .../tests/test_liquidate_lend.rs | 10 +- .../credit-manager/tests/test_reclaim.rs | 10 +- packages/rover/src/msg/execute.rs | 4 +- .../mars-credit-manager.json | 4 +- scripts/deploy/base/rover.ts | 5 +- .../osmosis/{mainnnet.ts => mainnet.ts} | 1 - .../MarsCreditManager.types.ts | 4 +- 14 files changed, 489 insertions(+), 252 deletions(-) rename scripts/deploy/osmosis/{mainnnet.ts => mainnet.ts} (97%) diff --git a/Cargo.lock b/Cargo.lock index 80a2ef350..f36f31321 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "ahash" version = "0.7.6" @@ -51,9 +36,9 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "apollo-cw-asset" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e314e0664dc9c096dba582fe1fa599dc556a4091335e40338ef94379ed2bdd" +checksum = "90fdd3af3b24bd7343c2b74b2bbe66ff53cc52327342e26ed207b3150f324c4a" dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", @@ -76,13 +61,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.69" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2d0f03b3640e3a630367e40c468cb7f309529c708ed1d88597047b0e7c6ef7" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.22", ] [[package]] @@ -102,21 +87,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "backtrace" -version = "0.3.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base16ct" version = "0.1.1" @@ -320,9 +290,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.3" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6340df57935414636969091153f35f68d9f00bbc8fb4a9c6054706c213e6c6bc" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" [[package]] name = "core-foundation" @@ -657,6 +627,19 @@ dependencies = [ "serde", ] +[[package]] +name = "cw2" +version = "1.0.1" +source = "git+https://github.com/mars-protocol/cw-plus?rev=1a3a944#1a3a944b64cf6e9fcfada48f2b09aaa1a90aef74" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.1.0", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw2" version = "1.1.0" @@ -699,8 +682,8 @@ dependencies = [ [[package]] name = "cw721" -version = "0.18.0" -source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#177a993dfb5a1a3164be1baf274f43b1ca53da53" +version = "0.17.0" +source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#8ff32222d4e4c261159d19a384218bd96c63ae23" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -728,16 +711,16 @@ dependencies = [ [[package]] name = "cw721-base" -version = "0.18.0" -source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#177a993dfb5a1a3164be1baf274f43b1ca53da53" +version = "0.17.0" +source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#8ff32222d4e4c261159d19a384218bd96c63ae23" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-ownable", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "cw2 1.1.0", - "cw721 0.18.0", + "cw2 1.0.1", + "cw721 0.17.0", "cw721-base 0.16.0", "schemars", "serde", @@ -886,7 +869,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1015,7 +998,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.22", ] [[package]] @@ -1071,12 +1054,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gimli" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" - [[package]] name = "glob" version = "0.3.1" @@ -1156,6 +1133,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.1" @@ -1321,7 +1307,7 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1344,9 +1330,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.7" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0aa48fab2893d8a49caa94082ae8488f4e1050d73b367881dcd2198f4199fd8" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" @@ -1435,9 +1421,9 @@ dependencies = [ "cw-multi-test", "cw-storage-plus 1.1.0", "cw2 1.1.0", - "cw721 0.18.0", + "cw721 0.17.0", "cw721-base 0.16.0", - "cw721-base 0.18.0", + "cw721-base 0.17.0", "mars-mock-rover-health", "mars-rover-health-types", "serde_json", @@ -1457,8 +1443,8 @@ dependencies = [ "cw-utils 1.0.1", "cw-vault-standard", "cw2 1.1.0", - "cw721 0.18.0", - "cw721-base 0.18.0", + "cw721 0.17.0", + "cw721-base 0.17.0", "itertools 0.11.0", "mars-account-nft", "mars-mock-oracle", @@ -1593,8 +1579,8 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard", - "cw721 0.18.0", - "cw721-base 0.18.0", + "cw721 0.17.0", + "cw721-base 0.17.0", "mars-account-nft", "mars-owner", "mars-params", @@ -1793,15 +1779,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - [[package]] name = "mio" version = "0.8.8" @@ -1810,7 +1787,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1846,11 +1823,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi 0.2.6", "libc", ] @@ -1863,15 +1840,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.18.0" @@ -2026,29 +1994,29 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.22", ] [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -2164,9 +2132,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.29" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -2294,12 +2262,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - [[package]] name = "rustc-hash" version = "1.1.0" @@ -2308,16 +2270,16 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.37.22" +version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8818fa822adcc98b18fedbb3632a6a33213c070556b5aa7c4c8cc21cff565c4c" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2374,11 +2336,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -2460,9 +2422,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.165" +version = "1.0.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c939f902bb7d0ccc5bce4f03297e161543c2dcb30914faf032c2bd0b7a0d48fc" +checksum = "7daf513456463b42aa1d94cff7e0c24d682b429f020b9afa4f5ba5c40a22b237" dependencies = [ "serde_derive", ] @@ -2507,13 +2469,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.165" +version = "1.0.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eaae920e25fffe4019b75ff65e7660e72091e59dd204cb5849bbd6a3fd343d7" +checksum = "b69b106b68bc8054f0e974e70d19984040f8a5cf9215ca82626ea4853f82c4b9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.22", ] [[package]] @@ -2546,7 +2508,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.22", ] [[package]] @@ -2685,9 +2647,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" dependencies = [ "proc-macro2", "quote", @@ -2705,7 +2667,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2851,7 +2813,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.22", ] [[package]] @@ -2888,12 +2850,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", - "backtrace", "bytes", "libc", "mio", @@ -2901,7 +2862,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2912,7 +2873,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.22", ] [[package]] @@ -3110,7 +3071,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.22", "wasm-bindgen-shared", ] @@ -3132,7 +3093,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.22", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3214,6 +3175,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -3225,55 +3201,97 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.0" @@ -3297,5 +3315,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.22", ] diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index d381d68a6..71590c3af 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -273,7 +273,7 @@ pub fn execute_callback( CallbackMsg::Lend { account_id, coin, - } => lend(deps, env, &account_id, coin), + } => lend(deps, env, &account_id, &coin), CallbackMsg::Reclaim { account_id, coin, diff --git a/contracts/credit-manager/src/lend.rs b/contracts/credit-manager/src/lend.rs index e9c5774b8..7008436c4 100644 --- a/contracts/credit-manager/src/lend.rs +++ b/contracts/credit-manager/src/lend.rs @@ -1,43 +1,73 @@ -use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; -use mars_rover::error::{ContractError, ContractResult}; +use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; +use mars_rover::{ + error::{ContractError, ContractResult}, + msg::execute::ActionCoin, +}; use crate::{ - state::{LENT_SHARES, RED_BANK, TOTAL_LENT_SHARES}, + state::{COIN_BALANCES, LENT_SHARES, RED_BANK, TOTAL_LENT_SHARES}, utils::{assert_coin_is_whitelisted, decrement_coin_balance}, }; pub static DEFAULT_LENT_SHARES_PER_COIN: Uint128 = Uint128::new(1_000_000); -pub fn lend(mut deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> ContractResult { - if coin.amount.is_zero() { - return Err(ContractError::NoAmount); - } - +pub fn lend( + mut deps: DepsMut, + env: Env, + account_id: &str, + coin: &ActionCoin, +) -> ContractResult { assert_coin_is_whitelisted(&mut deps, &coin.denom)?; + let amount_to_lend = Coin { + denom: coin.denom.to_string(), + amount: get_lend_amount(deps.as_ref(), account_id, coin)?, + }; + + // Total Credit Manager has lent to Red Bank for denom let red_bank = RED_BANK.load(deps.storage)?; - let total_lent = red_bank.query_lent(&deps.querier, &env.contract.address, &coin.denom)?; + let total_lent = + red_bank.query_lent(&deps.querier, &env.contract.address, &amount_to_lend.denom)?; let lent_shares_to_add = if total_lent.is_zero() { - coin.amount.checked_mul(DEFAULT_LENT_SHARES_PER_COIN)? + amount_to_lend.amount.checked_mul(DEFAULT_LENT_SHARES_PER_COIN)? } else { TOTAL_LENT_SHARES - .load(deps.storage, &coin.denom)? - .checked_multiply_ratio(coin.amount, total_lent)? + .load(deps.storage, &amount_to_lend.denom)? + .checked_multiply_ratio(amount_to_lend.amount, total_lent)? }; let add_shares = |shares: Option| -> ContractResult { Ok(shares.unwrap_or_else(Uint128::zero).checked_add(lent_shares_to_add)?) }; - TOTAL_LENT_SHARES.update(deps.storage, &coin.denom, add_shares)?; - LENT_SHARES.update(deps.storage, (account_id, &coin.denom), add_shares)?; - decrement_coin_balance(deps.storage, account_id, &coin)?; + TOTAL_LENT_SHARES.update(deps.storage, &amount_to_lend.denom, add_shares)?; + LENT_SHARES.update(deps.storage, (account_id, &amount_to_lend.denom), add_shares)?; + + decrement_coin_balance(deps.storage, account_id, &amount_to_lend)?; + + let red_bank_lend_msg = red_bank.lend_msg(&amount_to_lend)?; Ok(Response::new() - .add_message(red_bank.lend_msg(&coin)?) + .add_message(red_bank_lend_msg) .add_attribute("action", "lend") .add_attribute("account_id", account_id) .add_attribute("lent_shares_added", lent_shares_to_add) - .add_attribute("coin_lent", coin.to_string())) + .add_attribute("coin_lent", &amount_to_lend.denom)) +} + +/// Queries balance to ensure passing EXACT is not too high. +/// Also asserts the amount is greater than zero. +fn get_lend_amount(deps: Deps, account_id: &str, coin: &ActionCoin) -> ContractResult { + let amount_to_lend = if let Some(amount) = coin.amount.value() { + amount + } else { + COIN_BALANCES.may_load(deps.storage, (account_id, &coin.denom))?.unwrap_or(Uint128::zero()) + }; + + if amount_to_lend.is_zero() { + Err(ContractError::NoAmount) + } else { + Ok(amount_to_lend) + } } diff --git a/contracts/credit-manager/tests/test_enumerate_lent_shares.rs b/contracts/credit-manager/tests/test_enumerate_lent_shares.rs index 6e8ccd91a..b3c90c8c2 100644 --- a/contracts/credit-manager/tests/test_enumerate_lent_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_lent_shares.rs @@ -1,6 +1,9 @@ use cosmwasm_std::{coin, Addr}; use mars_credit_manager::lend::DEFAULT_LENT_SHARES_PER_COIN; -use mars_rover::msg::{execute::Action, query::SharesResponseItem}; +use mars_rover::msg::{ + execute::{Action, ActionAmount, ActionCoin}, + query::SharesResponseItem, +}; use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; @@ -13,44 +16,44 @@ fn pagination_on_all_lent_shares_query_works() { let user_c = Addr::unchecked("user_c"); let user_a_coins = vec![ - coin(10, "coin_1"), - coin(10, "coin_2"), - coin(10, "coin_3"), - coin(10, "coin_4"), - coin(10, "coin_5"), - coin(10, "coin_6"), - coin(10, "coin_7"), - coin(10, "coin_8"), - coin(10, "coin_9"), - coin(10, "coin_10"), - coin(10, "coin_11"), - coin(10, "coin_12"), - coin(10, "coin_13"), - coin(10, "coin_14"), + coin(1, "coin_1"), + coin(1, "coin_2"), + coin(1, "coin_3"), + coin(1, "coin_4"), + coin(1, "coin_5"), + coin(1, "coin_6"), + coin(1, "coin_7"), + coin(1, "coin_8"), + coin(1, "coin_9"), + coin(1, "coin_10"), + coin(1, "coin_11"), + coin(1, "coin_12"), + coin(1, "coin_13"), + coin(1, "coin_14"), ]; let user_b_coins = vec![ - coin(10, "coin_15"), - coin(10, "coin_16"), - coin(10, "coin_17"), - coin(10, "coin_18"), - coin(10, "coin_19"), - coin(10, "coin_20"), - coin(10, "coin_21"), - coin(10, "coin_22"), - coin(10, "coin_23"), - coin(10, "coin_24"), + coin(1, "coin_15"), + coin(1, "coin_16"), + coin(1, "coin_17"), + coin(1, "coin_18"), + coin(1, "coin_19"), + coin(1, "coin_20"), + coin(1, "coin_21"), + coin(1, "coin_22"), + coin(1, "coin_23"), + coin(1, "coin_24"), ]; let user_c_coins = vec![ - coin(10, "coin_25"), - coin(10, "coin_26"), - coin(10, "coin_27"), - coin(10, "coin_28"), - coin(10, "coin_29"), - coin(10, "coin_30"), - coin(10, "coin_31"), - coin(10, "coin_32"), + coin(1, "coin_25"), + coin(1, "coin_26"), + coin(1, "coin_27"), + coin(1, "coin_28"), + coin(1, "coin_29"), + coin(1, "coin_30"), + coin(1, "coin_31"), + coin(1, "coin_32"), ]; let mut mock = MockEnv::new() @@ -76,7 +79,15 @@ fn pagination_on_all_lent_shares_query_works() { &user_a, user_a_coins .iter() - .flat_map(|c| vec![Action::Deposit(c.clone()), Action::Lend(coin(1, c.denom.clone()))]) + .flat_map(|c| { + vec![ + Action::Deposit(c.clone()), + Action::Lend(ActionCoin { + denom: c.denom.clone(), + amount: ActionAmount::Exact(c.amount), + }), + ] + }) .collect::>(), &user_a_coins, ) @@ -88,7 +99,15 @@ fn pagination_on_all_lent_shares_query_works() { &user_b, user_b_coins .iter() - .flat_map(|c| vec![Action::Deposit(c.clone()), Action::Lend(coin(1, c.denom.clone()))]) + .flat_map(|c| { + vec![ + Action::Deposit(c.clone()), + Action::Lend(ActionCoin { + denom: c.denom.clone(), + amount: ActionAmount::Exact(c.amount), + }), + ] + }) .collect::>(), &user_b_coins, ) @@ -100,7 +119,15 @@ fn pagination_on_all_lent_shares_query_works() { &user_c, user_c_coins .iter() - .flat_map(|c| vec![Action::Deposit(c.clone()), Action::Lend(coin(1, c.denom.clone()))]) + .flat_map(|c| { + vec![ + Action::Deposit(c.clone()), + Action::Lend(ActionCoin { + denom: c.denom.clone(), + amount: ActionAmount::Exact(c.amount), + }), + ] + }) .collect::>(), &user_c_coins, ) diff --git a/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs b/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs index 9ed38a7d3..2bbd3fc49 100644 --- a/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs @@ -1,6 +1,9 @@ use cosmwasm_std::{coin, Addr}; use mars_credit_manager::lend::DEFAULT_LENT_SHARES_PER_COIN; -use mars_rover::msg::{execute::Action, query::LentShares}; +use mars_rover::msg::{ + execute::{Action, ActionAmount, ActionCoin}, + query::LentShares, +}; use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; @@ -13,44 +16,44 @@ fn pagination_on_all_total_lent_shares_query_works() { let user_c = Addr::unchecked("user_c"); let user_a_coins = vec![ - coin(10, "coin_1"), - coin(10, "coin_2"), - coin(10, "coin_3"), - coin(10, "coin_4"), - coin(10, "coin_5"), - coin(10, "coin_6"), - coin(10, "coin_7"), - coin(10, "coin_8"), - coin(10, "coin_9"), - coin(10, "coin_10"), - coin(10, "coin_11"), - coin(10, "coin_12"), - coin(10, "coin_13"), - coin(10, "coin_14"), + coin(1, "coin_1"), + coin(1, "coin_2"), + coin(1, "coin_3"), + coin(1, "coin_4"), + coin(1, "coin_5"), + coin(1, "coin_6"), + coin(1, "coin_7"), + coin(1, "coin_8"), + coin(1, "coin_9"), + coin(1, "coin_10"), + coin(1, "coin_11"), + coin(1, "coin_12"), + coin(1, "coin_13"), + coin(1, "coin_14"), ]; let user_b_coins = vec![ - coin(10, "coin_15"), - coin(10, "coin_16"), - coin(10, "coin_17"), - coin(10, "coin_18"), - coin(10, "coin_19"), - coin(10, "coin_20"), - coin(10, "coin_21"), - coin(10, "coin_22"), - coin(10, "coin_23"), - coin(10, "coin_24"), + coin(1, "coin_15"), + coin(1, "coin_16"), + coin(1, "coin_17"), + coin(1, "coin_18"), + coin(1, "coin_19"), + coin(1, "coin_20"), + coin(1, "coin_21"), + coin(1, "coin_22"), + coin(1, "coin_23"), + coin(1, "coin_24"), ]; let user_c_coins = vec![ - coin(10, "coin_25"), - coin(10, "coin_26"), - coin(10, "coin_27"), - coin(10, "coin_28"), - coin(10, "coin_29"), - coin(10, "coin_30"), - coin(10, "coin_31"), - coin(10, "coin_32"), + coin(1, "coin_25"), + coin(1, "coin_26"), + coin(1, "coin_27"), + coin(1, "coin_28"), + coin(1, "coin_29"), + coin(1, "coin_30"), + coin(1, "coin_31"), + coin(1, "coin_32"), ]; let mut mock = MockEnv::new() @@ -76,7 +79,15 @@ fn pagination_on_all_total_lent_shares_query_works() { &user_a, user_a_coins .iter() - .flat_map(|c| vec![Action::Deposit(c.clone()), Action::Lend(coin(1, c.denom.clone()))]) + .flat_map(|c| { + vec![ + Action::Deposit(c.clone()), + Action::Lend(ActionCoin { + denom: c.denom.clone(), + amount: ActionAmount::Exact(c.amount), + }), + ] + }) .collect::>(), &user_a_coins, ) @@ -88,7 +99,15 @@ fn pagination_on_all_total_lent_shares_query_works() { &user_b, user_b_coins .iter() - .flat_map(|c| vec![Action::Deposit(c.clone()), Action::Lend(coin(1, c.denom.clone()))]) + .flat_map(|c| { + vec![ + Action::Deposit(c.clone()), + Action::Lend(ActionCoin { + denom: c.denom.clone(), + amount: ActionAmount::Exact(c.amount), + }), + ] + }) .collect::>(), &user_b_coins, ) @@ -100,7 +119,15 @@ fn pagination_on_all_total_lent_shares_query_works() { &user_c, user_c_coins .iter() - .flat_map(|c| vec![Action::Deposit(c.clone()), Action::Lend(coin(1, c.denom.clone()))]) + .flat_map(|c| { + vec![ + Action::Deposit(c.clone()), + Action::Lend(ActionCoin { + denom: c.denom.clone(), + amount: ActionAmount::Exact(c.amount), + }), + ] + }) .collect::>(), &user_c_coins, ) diff --git a/contracts/credit-manager/tests/test_hls_accounts.rs b/contracts/credit-manager/tests/test_hls_accounts.rs index edeeee977..edba6caa2 100644 --- a/contracts/credit-manager/tests/test_hls_accounts.rs +++ b/contracts/credit-manager/tests/test_hls_accounts.rs @@ -189,7 +189,7 @@ fn wrong_correlations_does_not_qualify() { &account_id, &user, vec![ - Lend(jake_info.to_coin(50)), + Lend(jake_info.to_action_coin(50)), Deposit(jake_info.to_coin(50)), Deposit(atom_info.to_coin(300)), Borrow(atom_info.to_coin(1)), diff --git a/contracts/credit-manager/tests/test_lend.rs b/contracts/credit-manager/tests/test_lend.rs index 32a6a0299..0d21d58c7 100644 --- a/contracts/credit-manager/tests/test_lend.rs +++ b/contracts/credit-manager/tests/test_lend.rs @@ -4,11 +4,14 @@ use cosmwasm_std::{coin, coins, Addr, OverflowError, OverflowOperation, Uint128} use mars_credit_manager::lend::DEFAULT_LENT_SHARES_PER_COIN; use mars_rover::{ error::ContractError, - msg::execute::Action::{Deposit, Lend}, + msg::execute::{ + Action::{Deposit, Lend}, + ActionAmount, ActionCoin, + }, }; use crate::helpers::{ - assert_err, uosmo_info, AccountToFund, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, + assert_err, blacklisted_coin, uosmo_info, AccountToFund, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, }; pub mod helpers; @@ -24,7 +27,7 @@ fn only_token_owner_can_lend() { let res = mock.update_credit_account( &account_id, &another_user, - vec![Lend(coin_info.to_coin(12312))], + vec![Lend(coin_info.to_action_coin(12312))], &[], ); @@ -39,15 +42,19 @@ fn only_token_owner_can_lend() { #[test] fn can_only_lend_what_is_whitelisted() { - let coin_info = uosmo_info(); + let coin_info = blacklisted_coin(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().set_params(&[coin_info]).build().unwrap(); + let mut mock = MockEnv::new().set_params(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); - let res = - mock.update_credit_account(&account_id, &user, vec![Lend(coin(234, "usomething"))], &[]); + let res = mock.update_credit_account( + &account_id, + &user, + vec![Lend(coin_info.to_action_coin(50))], + &[], + ); - assert_err(res, ContractError::NotWhitelisted(String::from("usomething"))) + assert_err(res, ContractError::NotWhitelisted(String::from("uluna"))) } #[test] @@ -57,7 +64,12 @@ fn lending_zero_raises() { let mut mock = MockEnv::new().set_params(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); - let res = mock.update_credit_account(&account_id, &user, vec![Lend(coin_info.to_coin(0))], &[]); + let res = mock.update_credit_account( + &account_id, + &user, + vec![Lend(coin_info.to_action_coin(0))], + &[], + ); assert_err(res, ContractError::NoAmount) } @@ -81,7 +93,7 @@ fn raises_when_not_enough_assets_to_lend() { let res = mock.update_credit_account( &account_id_a, &user, - vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_coin(500))], + vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_action_coin(500))], &[coin_info.to_coin(300)], ); @@ -95,6 +107,43 @@ fn raises_when_not_enough_assets_to_lend() { ) } +#[test] +fn raises_when_attempting_to_lend_account_balance_with_no_funds() { + let coin_info = uosmo_info(); + + let user_a = Addr::unchecked("user_a"); + + let mut mock = MockEnv::new() + .set_params(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id_a = mock.create_credit_account(&user_a).unwrap(); + + let position = mock.query_positions(&account_id_a); + assert_eq!(position.deposits.len(), 0); + assert_eq!(position.lends.len(), 0); + + let red_bank_collateral = mock.query_red_bank_collateral(&coin_info.denom); + assert_eq!(red_bank_collateral.amount, Uint128::zero()); + + let res = mock.update_credit_account( + &account_id_a, + &user_a, + vec![Lend(ActionCoin { + denom: "uosmo".to_string(), + amount: ActionAmount::AccountBalance, + })], + &[], + ); + + assert_err(res, ContractError::NoAmount) +} + #[test] fn successful_lend() { let coin_info = uosmo_info(); @@ -132,8 +181,13 @@ fn successful_lend() { let red_bank_collateral = mock.query_red_bank_collateral(&coin_info.denom); assert_eq!(red_bank_collateral.amount, Uint128::zero()); - mock.update_credit_account(&account_id_a, &user_a, vec![Lend(coin_info.to_coin(50))], &[]) - .unwrap(); + mock.update_credit_account( + &account_id_a, + &user_a, + vec![Lend(coin_info.to_action_coin(50))], + &[], + ) + .unwrap(); // Assert deposits decreased let position = mock.query_positions(&account_id_a); @@ -173,7 +227,7 @@ fn successful_lend() { mock.update_credit_account( &account_id_b, &user_b, - vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_coin(50))], + vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_action_coin(50))], &[coin(300, coin_info.denom)], ) .unwrap(); @@ -183,3 +237,82 @@ fn successful_lend() { let expected_shares = total.shares.multiply_ratio(Uint128::new(50), red_bank_collateral.amount); assert_eq!(position.lends.first().unwrap().shares, expected_shares); } + +#[test] +fn successful_account_balance_lend() { + let coin_info = uosmo_info(); + + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + + let mut mock = MockEnv::new() + .set_params(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user_a.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: user_b.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id_a = mock.create_credit_account(&user_a).unwrap(); + + let position = mock.query_positions(&account_id_a); + assert_eq!(position.deposits.len(), 0); + assert_eq!(position.lends.len(), 0); + + mock.update_credit_account( + &account_id_a, + &user_a, + vec![Deposit(coin_info.to_coin(300))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + let red_bank_collateral = mock.query_red_bank_collateral(&coin_info.denom); + assert_eq!(red_bank_collateral.amount, Uint128::zero()); + + mock.update_credit_account( + &account_id_a, + &user_a, + vec![Lend(ActionCoin { + denom: "uosmo".to_string(), + amount: ActionAmount::AccountBalance, + })], + &[], + ) + .unwrap(); + + // Assert deposits decreased + let position = mock.query_positions(&account_id_a); + assert_eq!(position.deposits.len(), 0); + + // Assert lend position amount increased + let lent_res = position.lends.first().unwrap(); + assert_eq!(position.lends.len(), 1); + assert_eq!(lent_res.denom, coin_info.denom); + let lent_amount = Uint128::new(300) + Uint128::new(1); // account balance + simulated yield + assert_eq!(lent_res.amount, lent_amount); + assert_eq!(lent_res.shares, Uint128::new(300).mul(DEFAULT_LENT_SHARES_PER_COIN)); + + // Assert total lent positions increased + let total = mock.query_total_lent_shares(&coin_info.denom); + assert_eq!(total.denom, coin_info.denom); + assert_eq!(total.shares, DEFAULT_LENT_SHARES_PER_COIN.mul(Uint128::new(300))); + + // Assert Rover has indeed sent those tokens to Red Bank + let balance = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(balance.amount, Uint128::new(0)); //total deposited minus account balance -> 300-300=0 + + let config = mock.query_config(); + let red_bank_addr = Addr::unchecked(config.red_bank); + let balance = mock.query_balance(&red_bank_addr, &coin_info.denom); + assert_eq!(balance.amount, DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(300))); + + // Assert Rover's collateral balance in Red bank + let red_bank_collateral = mock.query_red_bank_collateral(&coin_info.denom); + assert_eq!(red_bank_collateral.amount, lent_amount); +} diff --git a/contracts/credit-manager/tests/test_liquidate_lend.rs b/contracts/credit-manager/tests/test_liquidate_lend.rs index 384073b10..c5ae90e6b 100644 --- a/contracts/credit-manager/tests/test_liquidate_lend.rs +++ b/contracts/credit-manager/tests/test_liquidate_lend.rs @@ -50,7 +50,7 @@ fn lent_positions_contribute_to_health() { mock.update_credit_account( &liquidatee_account_id, &liquidatee, - vec![Lend(uatom_info.to_coin(50))], + vec![Lend(uatom_info.to_action_coin(50))], &[], ) .unwrap(); @@ -118,7 +118,7 @@ fn liquidatee_does_not_have_requested_lent_coin() { &liquidatee, vec![ Deposit(uatom_info.to_coin(100)), - Lend(uatom_info.to_coin(50)), + Lend(uatom_info.to_action_coin(50)), Borrow(uosmo_info.to_coin(100)), ], &[uatom_info.to_coin(100)], @@ -182,7 +182,7 @@ fn lent_position_partially_liquidated() { vec![ Deposit(uosmo_info.to_coin(1050)), Borrow(uatom_info.to_coin(1000)), - Lend(uosmo_info.to_coin(450)), + Lend(uosmo_info.to_action_coin(450)), ], &[uosmo_info.to_coin(1050)], ) @@ -291,7 +291,7 @@ fn lent_position_fully_liquidated() { vec![ Deposit(uosmo_info.to_coin(300)), Borrow(uatom_info.to_coin(500)), - Lend(uosmo_info.to_coin(109)), + Lend(uosmo_info.to_action_coin(109)), ], &[uosmo_info.to_coin(300)], ) @@ -403,7 +403,7 @@ fn liquidate_with_reclaiming() { vec![ Deposit(uosmo_info.to_coin(3000)), Borrow(uatom_info.to_coin(1000)), - Lend(uosmo_info.to_coin(1500)), + Lend(uosmo_info.to_action_coin(1500)), ], &[uosmo_info.to_coin(3000)], ) diff --git a/contracts/credit-manager/tests/test_reclaim.rs b/contracts/credit-manager/tests/test_reclaim.rs index 1b11892b4..c0a957c1b 100644 --- a/contracts/credit-manager/tests/test_reclaim.rs +++ b/contracts/credit-manager/tests/test_reclaim.rs @@ -78,7 +78,7 @@ fn when_trying_to_reclaim_more_than_lent() { mock.update_credit_account( &account_id, &user, - vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_coin(50))], + vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_action_coin(50))], &[coin_info.to_coin(300)], ) .unwrap(); @@ -133,7 +133,7 @@ fn reclaiming_less_than_entire_lent_share() { mock.update_credit_account( &account_id, &user, - vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_coin(200))], + vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_action_coin(200))], &[coin(300, coin_info.denom.clone())], ) .unwrap(); @@ -188,7 +188,7 @@ fn reclaiming_the_entire_lent_share() { mock.update_credit_account( &account_id, &user, - vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_coin(100))], + vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_action_coin(100))], &[coin(300, coin_info.denom.clone())], ) .unwrap(); @@ -248,7 +248,7 @@ fn reclaiming_multiple_assets() { mock.update_credit_account( &account_id, &user, - vec![Deposit(uatom_info.to_coin(300)), Lend(uatom_info.to_coin(100))], + vec![Deposit(uatom_info.to_coin(300)), Lend(uatom_info.to_action_coin(100))], &[coin(300, uatom_info.denom.clone())], ) .unwrap(); @@ -256,7 +256,7 @@ fn reclaiming_multiple_assets() { mock.update_credit_account( &account_id, &user, - vec![Deposit(uosmo_info.to_coin(200)), Lend(uosmo_info.to_coin(100))], + vec![Deposit(uosmo_info.to_coin(200)), Lend(uosmo_info.to_action_coin(100))], &[coin(200, uosmo_info.denom.clone())], ) .unwrap(); diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index ca5024c4c..5b8b554a0 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -119,7 +119,7 @@ pub enum Action { /// Borrow coin of specified amount from Red Bank Borrow(Coin), /// Lend coin to the Red Bank - Lend(Coin), + Lend(ActionCoin), /// Reclaim the coins that were lent to the Red Bank. Reclaim(ActionCoin), /// Repay coin of specified amount back to Red Bank. If `amount: AccountBalance` is passed, @@ -216,7 +216,7 @@ pub enum CallbackMsg { /// Lend coin to the Red Bank Lend { account_id: String, - coin: Coin, + coin: ActionCoin, }, /// Reclaim lent coin from the Red Bank; /// Decrement the token's lent shares and increment the coin amount; diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index f28336c21..f7e6a530e 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -318,7 +318,7 @@ ], "properties": { "lend": { - "$ref": "#/definitions/Coin" + "$ref": "#/definitions/ActionCoin" } }, "additionalProperties": false @@ -794,7 +794,7 @@ "type": "string" }, "coin": { - "$ref": "#/definitions/Coin" + "$ref": "#/definitions/ActionCoin" } }, "additionalProperties": false diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 1446888a6..373210971 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -71,7 +71,10 @@ export class Rover { async lend() { const amount = this.actions.lendAmount - await this.updateCreditAccount([{ lend: { amount, denom: this.config.chain.baseDenom } }], []) + await this.updateCreditAccount( + [{ lend: { amount: { exact: amount }, denom: this.config.chain.baseDenom } }], + [], + ) const positions = await this.query.positions({ accountId: this.accountId! }) assert.equal(positions.lends.length, 1) assert.equal(positions.lends[0].denom, this.config.chain.baseDenom) diff --git a/scripts/deploy/osmosis/mainnnet.ts b/scripts/deploy/osmosis/mainnet.ts similarity index 97% rename from scripts/deploy/osmosis/mainnnet.ts rename to scripts/deploy/osmosis/mainnet.ts index 10676712e..e028a9cf1 100644 --- a/scripts/deploy/osmosis/mainnnet.ts +++ b/scripts/deploy/osmosis/mainnet.ts @@ -30,7 +30,6 @@ export const osmosisMainnetConfig: DeploymentConfig = { { denomIn: uosmo, denomOut: axlUSDC, route: [{ token_out_denom: axlUSDC, pool_id: '678' }] }, { denomIn: axlUSDC, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '678' }] }, ], - // Latest from: https://stats.apollo.farm/api/vaults/v1/all vaults: [ { addr: 'osmo1g3kmqpp8608szfp0pdag3r6z85npph7wmccat8lgl3mp407kv73qlj7qwp', diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 157df937c..ae42439ee 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -66,7 +66,7 @@ export type Action = borrow: Coin } | { - lend: Coin + lend: ActionCoin } | { reclaim: ActionCoin @@ -197,7 +197,7 @@ export type CallbackMsg = | { lend: { account_id: string - coin: Coin + coin: ActionCoin } } | { From ce06c1a1c70ee951170401ae5fc5a2233e3cbcf6 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 12 Jul 2023 14:36:43 +0200 Subject: [PATCH 178/218] Self liquidations not permitted (#157) self liquidations not permitted --- contracts/credit-manager/src/execute.rs | 64 ++++++++++--------- contracts/credit-manager/src/liquidate.rs | 11 ++++ contracts/credit-manager/tests/test_lend.rs | 2 +- .../tests/test_liquidate_guard.rs | 46 +++++++++++++ packages/rover/src/error.rs | 3 + 5 files changed, 95 insertions(+), 31 deletions(-) create mode 100644 contracts/credit-manager/tests/test_liquidate_guard.rs diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 71590c3af..99e737939 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -15,6 +15,7 @@ use crate::{ health::{assert_max_ltv, query_health}, hls::assert_account_requirements, lend::lend, + liquidate::assert_not_self_liquidation, liquidate_deposit::liquidate_deposit, liquidate_lend::liquidate_lend, reclaim::reclaim, @@ -303,36 +304,39 @@ pub fn execute_callback( liquidatee_account_id, debt_coin, request, - } => match request { - LiquidateRequest::Deposit(request_coin_denom) => liquidate_deposit( - deps, - env, - &liquidator_account_id, - &liquidatee_account_id, - debt_coin, - &request_coin_denom, - ), - LiquidateRequest::Lend(request_coin_denom) => liquidate_lend( - deps, - env, - &liquidator_account_id, - &liquidatee_account_id, - debt_coin, - &request_coin_denom, - ), - LiquidateRequest::Vault { - request_vault, - position_type, - } => liquidate_vault( - deps, - env, - &liquidator_account_id, - &liquidatee_account_id, - debt_coin, - request_vault, - position_type, - ), - }, + } => { + assert_not_self_liquidation(&liquidator_account_id, &liquidatee_account_id)?; + match request { + LiquidateRequest::Deposit(request_coin_denom) => liquidate_deposit( + deps, + env, + &liquidator_account_id, + &liquidatee_account_id, + debt_coin, + &request_coin_denom, + ), + LiquidateRequest::Lend(request_coin_denom) => liquidate_lend( + deps, + env, + &liquidator_account_id, + &liquidatee_account_id, + debt_coin, + &request_coin_denom, + ), + LiquidateRequest::Vault { + request_vault, + position_type, + } => liquidate_vault( + deps, + env, + &liquidator_account_id, + &liquidatee_account_id, + debt_coin, + request_vault, + position_type, + ), + } + } CallbackMsg::SwapExactIn { account_id, coin_in, diff --git a/contracts/credit-manager/src/liquidate.rs b/contracts/credit-manager/src/liquidate.rs index 46730543d..2d4d98252 100644 --- a/contracts/credit-manager/src/liquidate.rs +++ b/contracts/credit-manager/src/liquidate.rs @@ -261,3 +261,14 @@ fn assert_liquidation_profitable( Ok(()) } + +/// Guards against the case an account is trying to liquidate itself +pub fn assert_not_self_liquidation( + liquidator_account_id: &str, + liquidatee_account_id: &str, +) -> ContractResult<()> { + if liquidator_account_id == liquidatee_account_id { + return Err(ContractError::SelfLiquidation); + } + Ok(()) +} diff --git a/contracts/credit-manager/tests/test_lend.rs b/contracts/credit-manager/tests/test_lend.rs index 0d21d58c7..624d3c1ac 100644 --- a/contracts/credit-manager/tests/test_lend.rs +++ b/contracts/credit-manager/tests/test_lend.rs @@ -252,7 +252,7 @@ fn successful_account_balance_lend() { funds: coins(300, coin_info.denom.clone()), }) .fund_account(AccountToFund { - addr: user_b.clone(), + addr: user_b, funds: coins(300, coin_info.denom.clone()), }) .build() diff --git a/contracts/credit-manager/tests/test_liquidate_guard.rs b/contracts/credit-manager/tests/test_liquidate_guard.rs new file mode 100644 index 000000000..8ff44b5aa --- /dev/null +++ b/contracts/credit-manager/tests/test_liquidate_guard.rs @@ -0,0 +1,46 @@ +use cosmwasm_std::{coins, Addr}; +use mars_rover::{ + error::ContractError, + msg::execute::{ + Action::{Borrow, Deposit, Liquidate}, + LiquidateRequest, + }, +}; + +use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn cannot_liquidate_own_account() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + let liquidator = Addr::unchecked("liquidator"); + let mut mock = MockEnv::new() + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: coins(3000, uatom_info.denom.clone()), + }) + .build() + .unwrap(); + + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + let res = mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(uatom_info.to_coin(100)), + Borrow(uatom_info.to_coin(1000)), + Liquidate { + liquidatee_account_id: liquidator_account_id.clone(), // Should not be allowed + debt_coin: uatom_info.to_coin(100), + request: LiquidateRequest::Deposit(uosmo_info.denom), + }, + ], + &[uatom_info.to_coin(100)], + ); + + assert_err(res, ContractError::SelfLiquidation); +} diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index a20ecaf4b..4ac015a55 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -146,6 +146,9 @@ pub enum ContractError { #[error("{0}")] RequirementsNotMet(String), + #[error("Cannot request liquidation on own credit account")] + SelfLiquidation, + #[error("{0}")] Std(#[from] StdError), From 0af9366c440ae50d8510e87001c5feecdde2d4ae Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 12 Jul 2023 16:30:15 +0200 Subject: [PATCH 179/218] Liquidation uses new Oracle pricing kind (#156) Liquidation uses different pricing kind --- Cargo.lock | 415 ++++--- Cargo.toml | 12 +- contracts/account-nft/Cargo.toml | 1 + contracts/account-nft/src/execute.rs | 2 + contracts/credit-manager/src/execute.rs | 11 +- contracts/credit-manager/src/health.rs | 88 +- contracts/credit-manager/src/liquidate.rs | 16 +- contracts/credit-manager/src/query.rs | 3 +- contracts/credit-manager/src/vault/enter.rs | 7 +- contracts/credit-manager/src/vault/utils.rs | 6 +- .../credit-manager/tests/helpers/mock_env.rs | 20 +- contracts/credit-manager/tests/test_health.rs | 41 +- .../credit-manager/tests/test_hls_accounts.rs | 11 +- .../tests/test_liquidate_deposit.rs | 47 +- .../tests/test_liquidate_lend.rs | 36 +- .../tests/test_liquidate_vault.rs | 26 +- .../tests/test_liquidation_pricing.rs | 97 ++ .../tests/test_update_config.rs | 3 + contracts/health/Cargo.toml | 1 + contracts/health/src/compute.rs | 18 +- contracts/health/src/contract.rs | 6 +- contracts/health/tests/helpers/mock_env.rs | 8 +- contracts/health/tests/test_health_state.rs | 14 +- contracts/health/tests/test_health_values.rs | 31 +- contracts/health/tests/test_hls.rs | 9 +- .../health/tests/test_liquidation_pricing.rs | 97 ++ contracts/mock-health/src/contract.rs | 1 + contracts/mock-oracle/src/contract.rs | 33 +- contracts/mock-oracle/src/msg.rs | 3 + contracts/mock-oracle/src/state.rs | 3 +- contracts/mock-vault/Cargo.toml | 15 +- contracts/mock-vault/src/deposit.rs | 6 +- contracts/v2-zapper/mock/Cargo.toml | 1 + contracts/v2-zapper/mock/src/query.rs | 7 +- packages/health-types/Cargo.toml | 9 +- packages/health-types/src/health.rs | 17 + packages/health-types/src/msg.rs | 3 + packages/rover/src/adapters/health.rs | 24 +- packages/rover/src/adapters/oracle.rs | 22 +- packages/rover/src/adapters/red_bank.rs | 4 +- packages/rover/src/adapters/vault/position.rs | 24 +- packages/rover/src/msg/execute.rs | 4 +- .../mars-credit-manager.json | 45 +- .../mars-mock-oracle/mars-mock-oracle.json | 50 +- .../mars-mock-red-bank.json | 244 +--- .../mars-rover-health-types.json | 16 + .../mars-rover-health/mars-rover-health.json | 16 + scripts/deploy/base/deployer.ts | 6 - scripts/deploy/base/storage.ts | 6 +- scripts/health/DataFetcher.ts | 22 +- scripts/health/example-react/index.html | 2 +- scripts/health/pkg-node/index.js | 10 +- scripts/health/pkg-node/index_bg.wasm | Bin 175576 -> 175592 bytes scripts/health/pkg-web/index.js | 8 +- scripts/health/pkg-web/index_bg.wasm | Bin 174652 -> 174668 bytes scripts/package.json | 16 +- .../MarsCreditManager.client.ts | 1 + .../MarsCreditManager.message-composer.ts | 1 + .../MarsCreditManager.react-query.ts | 1 + .../MarsCreditManager.types.ts | 9 +- .../mars-mock-oracle/MarsMockOracle.client.ts | 11 +- .../MarsMockOracle.message-composer.ts | 6 + .../MarsMockOracle.react-query.ts | 4 + .../mars-mock-oracle/MarsMockOracle.types.ts | 3 + .../MarsMockRedBank.client.ts | 38 +- .../MarsMockRedBank.message-composer.ts | 45 +- .../MarsMockRedBank.react-query.ts | 26 +- .../MarsMockRedBank.types.ts | 29 +- .../MarsRoverHealthTypes.client.ts | 11 + .../MarsRoverHealthTypes.message-composer.ts | 1 + .../MarsRoverHealthTypes.react-query.ts | 5 + .../MarsRoverHealthTypes.types.ts | 3 + .../MarsRoverHealth.client.ts | 11 + .../MarsRoverHealth.message-composer.ts | 1 + .../MarsRoverHealth.react-query.ts | 5 + .../MarsRoverHealth.types.ts | 3 + scripts/yarn.lock | 1030 ++++++++++------- 77 files changed, 1693 insertions(+), 1194 deletions(-) create mode 100644 contracts/credit-manager/tests/test_liquidation_pricing.rs create mode 100644 contracts/health/tests/test_liquidation_pricing.rs diff --git a/Cargo.lock b/Cargo.lock index f36f31321..408fa942a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "ahash" version = "0.7.6" @@ -36,9 +51,9 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "apollo-cw-asset" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fdd3af3b24bd7343c2b74b2bbe66ff53cc52327342e26ed207b3150f324c4a" +checksum = "65e314e0664dc9c096dba582fe1fa599dc556a4091335e40338ef94379ed2bdd" dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", @@ -61,13 +76,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.25", ] [[package]] @@ -87,6 +102,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base16ct" version = "0.1.1" @@ -290,9 +320,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +checksum = "6340df57935414636969091153f35f68d9f00bbc8fb4a9c6054706c213e6c6bc" [[package]] name = "core-foundation" @@ -410,9 +440,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -627,19 +657,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cw2" -version = "1.0.1" -source = "git+https://github.com/mars-protocol/cw-plus?rev=1a3a944#1a3a944b64cf6e9fcfada48f2b09aaa1a90aef74" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.1.0", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw2" version = "1.1.0" @@ -682,8 +699,8 @@ dependencies = [ [[package]] name = "cw721" -version = "0.17.0" -source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#8ff32222d4e4c261159d19a384218bd96c63ae23" +version = "0.18.0" +source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#177a993dfb5a1a3164be1baf274f43b1ca53da53" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -711,16 +728,16 @@ dependencies = [ [[package]] name = "cw721-base" -version = "0.17.0" -source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#8ff32222d4e4c261159d19a384218bd96c63ae23" +version = "0.18.0" +source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#177a993dfb5a1a3164be1baf274f43b1ca53da53" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-ownable", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "cw2 1.0.1", - "cw721 0.17.0", + "cw2 1.1.0", + "cw721 0.18.0", "cw721-base 0.16.0", "schemars", "serde", @@ -869,7 +886,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -998,7 +1015,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.25", ] [[package]] @@ -1054,6 +1071,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + [[package]] name = "glob" version = "0.3.1" @@ -1125,28 +1148,25 @@ dependencies = [ ] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -1305,9 +1325,9 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi 0.3.2", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1330,9 +1350,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" [[package]] name = "js-sys" @@ -1421,10 +1441,11 @@ dependencies = [ "cw-multi-test", "cw-storage-plus 1.1.0", "cw2 1.1.0", - "cw721 0.17.0", + "cw721 0.18.0", "cw721-base 0.16.0", - "cw721-base 0.17.0", + "cw721-base 0.18.0", "mars-mock-rover-health", + "mars-red-bank-types", "mars-rover-health-types", "serde_json", "thiserror", @@ -1443,8 +1464,8 @@ dependencies = [ "cw-utils 1.0.1", "cw-vault-standard", "cw2 1.1.0", - "cw721 0.17.0", - "cw721-base 0.17.0", + "cw721 0.18.0", + "cw721-base 0.18.0", "itertools 0.11.0", "mars-account-nft", "mars-mock-oracle", @@ -1513,6 +1534,7 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard", + "mars-red-bank-types", "mars-rover", "thiserror", ] @@ -1559,14 +1581,15 @@ dependencies = [ [[package]] name = "mars-red-bank-types" -version = "1.0.0" +version = "1.1.0-develop-0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca59bb17daa753c30d3c934e0779736200708cb89a34020fa805b1cb05e7278" +checksum = "c00f93d7e4b71d29d7cf448f12295d68129c761a9ab7c165e87150a3d715599e" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "mars-owner", "mars-utils", + "strum", "thiserror", ] @@ -1579,8 +1602,8 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard", - "cw721 0.17.0", - "cw721-base 0.17.0", + "cw721 0.18.0", + "cw721-base 0.18.0", "mars-account-nft", "mars-owner", "mars-params", @@ -1609,6 +1632,7 @@ dependencies = [ "mars-mock-vault", "mars-owner", "mars-params", + "mars-red-bank-types", "mars-rover", "mars-rover-health-computer", "mars-rover-health-types", @@ -1641,6 +1665,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "mars-owner", + "mars-red-bank-types", "thiserror", ] @@ -1681,7 +1706,7 @@ dependencies = [ "mars-owner", "mars-rover", "mars-swapper-base", - "osmosis-std 0.16.0-beta.1", + "osmosis-std 0.16.0", "osmosis-test-tube", "schemars", "thiserror", @@ -1718,6 +1743,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", + "mars-red-bank-types", "mars-rover", "mars-v2-zapper-base", "thiserror", @@ -1732,7 +1758,7 @@ dependencies = [ "cw-utils 1.0.1", "cw2 1.1.0", "mars-v2-zapper-base", - "osmosis-std 0.16.0-beta.1", + "osmosis-std 0.16.0", "osmosis-test-tube", ] @@ -1757,7 +1783,7 @@ dependencies = [ "cosmwasm-std", "mars-owner", "mars-v3-zapper-base", - "osmosis-std 0.16.0-beta.1", + "osmosis-std 0.16.0", "osmosis-test-tube", ] @@ -1779,6 +1805,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.8" @@ -1787,7 +1822,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1823,11 +1858,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.2", "libc", ] @@ -1840,6 +1875,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -1882,13 +1926,13 @@ dependencies = [ [[package]] name = "osmosis-std" -version = "0.16.0-beta.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abcc484d988099d0422b34b85f7086478400c2508d09d0724666a2933fec342" +checksum = "c254b73ab62f7e7a19eb7249cf1443e49999ecc8169bb4535a855b6e87d55498" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.16.0-beta.1", + "osmosis-std-derive 0.16.0", "prost 0.11.9", "prost-types", "schemars", @@ -1910,27 +1954,28 @@ dependencies = [ [[package]] name = "osmosis-std-derive" -version = "0.16.0-beta.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8571de5fb19c9abf11bb4c9a14d9128b8bd4ff180034561fbf87f3b9c42d1f0f" +checksum = "67a6feeb2c5de684262032a6719ff58cfecfdffa5f31eba1835faa857ac3a762" dependencies = [ "itertools 0.10.5", "proc-macro2", + "prost-types", "quote", "syn 1.0.109", ] [[package]] name = "osmosis-test-tube" -version = "16.0.0-beta.0" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949453e4f89ba5175f1f9f885188dabb502022dc3dd249b68471f4d0be6a3e0b" +checksum = "f4988b1215f3fffdb1af5ae4930ed97e0be2d61221fc27dff7b89973670cbb50" dependencies = [ "base64", "bindgen", "cosmrs", "cosmwasm-std", - "osmosis-std 0.16.0-beta.1", + "osmosis-std 0.16.0", "prost 0.11.9", "serde", "serde_json", @@ -1940,9 +1985,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" [[package]] name = "pbkdf2" @@ -1994,29 +2039,29 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.25", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" [[package]] name = "pin-utils" @@ -2042,9 +2087,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" dependencies = [ "unicode-ident", ] @@ -2132,9 +2177,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ "proc-macro2", ] @@ -2195,13 +2240,25 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.4" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax 0.7.3", +] + +[[package]] +name = "regex-automata" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.2", + "regex-syntax 0.7.3", ] [[package]] @@ -2212,9 +2269,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" [[package]] name = "rfc6979" @@ -2262,6 +2319,12 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2270,16 +2333,16 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.37.20" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2307,6 +2370,12 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustversion" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f" + [[package]] name = "rusty-fork" version = "0.3.0" @@ -2321,9 +2390,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" [[package]] name = "same-file" @@ -2336,11 +2405,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -2422,9 +2491,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.167" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daf513456463b42aa1d94cff7e0c24d682b429f020b9afa4f5ba5c40a22b237" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" dependencies = [ "serde_derive", ] @@ -2460,22 +2529,22 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +checksum = "5a16be4fe5320ade08736447e3198294a5ea9a6d44dde6f35f0a5e06859c427a" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.167" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69b106b68bc8054f0e974e70d19984040f8a5cf9215ca82626ea4853f82c4b9" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.25", ] [[package]] @@ -2491,9 +2560,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" +checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" dependencies = [ "itoa", "ryu", @@ -2502,13 +2571,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +checksum = "1d89a8107374290037607734c0b73a85db7ed80cae314b3c5791f192a496e731" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.25", ] [[package]] @@ -2619,6 +2688,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + [[package]] name = "subtle" version = "2.5.0" @@ -2647,9 +2738,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.22" +version = "2.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" dependencies = [ "proc-macro2", "quote", @@ -2667,7 +2758,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2777,9 +2868,9 @@ dependencies = [ [[package]] name = "test-tube" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ec7aed5905c502fb6be7684a2a9b0d7005ece58f79d4bd66a99c355ee24012c" +checksum = "2152b79646afbca662896e917b94fb839af6939eb592b7af9cd5dcb1cd42f99e" dependencies = [ "base64", "cosmrs", @@ -2798,22 +2889,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.25", ] [[package]] @@ -2850,11 +2941,12 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.2" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" dependencies = [ "autocfg", + "backtrace", "bytes", "libc", "mio", @@ -2862,7 +2954,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2873,7 +2965,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.25", ] [[package]] @@ -2974,9 +3066,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" [[package]] name = "unicode-normalization" @@ -3071,7 +3163,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.25", "wasm-bindgen-shared", ] @@ -3093,7 +3185,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.25", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3175,21 +3267,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -3201,97 +3278,55 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.0" @@ -3315,5 +3350,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.25", ] diff --git a/Cargo.toml b/Cargo.toml index 23d366fce..42e043324 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,20 +49,20 @@ cw-utils = "1.0.1" cw-storage-plus = "1.1.0" cw-vault-standard = { version = "0.3.0", features = ["lockup", "force-unlock"] } itertools = "0.11.0" -osmosis-std = "0.16.0-beta.1" -osmosis-test-tube = "16.0.0-beta.0" +osmosis-std = "0.16.0" +osmosis-test-tube = "16.0.0" proptest = "1.2.0" schemars = "0.8.12" -serde = { version = "1.0.165", default-features = false, features = ["derive"] } -serde_json = "1.0.99" +serde = { version = "1.0.171", default-features = false, features = ["derive"] } +serde_json = "1.0.100" serde-wasm-bindgen = "0.5.0" -thiserror = "1.0.40" +thiserror = "1.0.43" wasm-bindgen = "0.2.87" # mars packages mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "00301d60c38af09d8eb7980355009e2f00c6f41f" } mars-owner = { version = "1.2.0", features = ["emergency-owner"] } -mars-red-bank-types = "1.0.0" +mars-red-bank-types = "1.1.0-develop-0" mars-rover-health-computer = { version = "2.0.0", path = "./packages/health-computer" } mars-rover-health-types = { version = "2.0.0", path = "./packages/health-types" } mars-rover = { version = "2.0.0", path = "./packages/rover" } diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 83c16ca20..7247c0cd4 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -25,6 +25,7 @@ cw2 = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true } cw-storage-plus = { workspace = true } +mars-red-bank-types = { workspace = true } mars-rover-health-types = { workspace = true } thiserror = { workspace = true } diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index 092d5abf5..23eec6f63 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -6,6 +6,7 @@ use cw721_base::{ ContractError::Ownership, OwnershipError::{NoOwner, NotOwner}, }; +use mars_red_bank_types::oracle::ActionKind; use mars_rover_health_types::{AccountKind, HealthValuesResponse, QueryMsg::HealthValues}; use crate::{ @@ -46,6 +47,7 @@ pub fn burn( msg: to_binary(&HealthValues { account_id: token_id.clone(), kind: AccountKind::Default, + action: ActionKind::Default, })?, }))?; diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 99e737939..49eab92e2 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -2,6 +2,7 @@ use cosmwasm_std::{ to_binary, Addr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdResult, WasmMsg, }; use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ coins::Coins, error::{ContractError, ContractResult}, @@ -12,7 +13,7 @@ use mars_rover_health_types::AccountKind; use crate::{ borrow::borrow, deposit::deposit, - health::{assert_max_ltv, query_health}, + health::{assert_max_ltv, query_health_state}, hls::assert_account_requirements, lend::lend, liquidate::assert_not_self_liquidation, @@ -70,7 +71,7 @@ pub fn dispatch_actions( let mut response = Response::new(); let mut callbacks: Vec = vec![]; let mut received_coins = Coins::try_from(info.funds)?; - let prev_health = query_health(deps.as_ref(), account_id)?; + let prev_health_state = query_health_state(deps.as_ref(), account_id, ActionKind::Default)?; for action in actions { match action { @@ -226,7 +227,7 @@ pub fn dispatch_actions( // Else, throw error and revert all actions CallbackMsg::AssertMaxLTV { account_id: account_id.to_string(), - prev_max_ltv_health_factor: prev_health.max_ltv_health_factor, + prev_health_state, }, // Removes guard so that subsequent action dispatches can be made CallbackMsg::RemoveReentrancyGuard {}, @@ -281,8 +282,8 @@ pub fn execute_callback( } => reclaim(deps, env, &account_id, &coin), CallbackMsg::AssertMaxLTV { account_id, - prev_max_ltv_health_factor, - } => assert_max_ltv(deps.as_ref(), env, &account_id, &prev_max_ltv_health_factor), + prev_health_state, + } => assert_max_ltv(deps.as_ref(), &account_id, prev_health_state), CallbackMsg::EnterVault { account_id, vault, diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 1a6b9363f..84ef6c5de 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -1,56 +1,76 @@ -use cosmwasm_std::{Decimal, Deps, Env, Event, Response}; -use mars_rover::{ - error::{ContractError, ContractResult}, - traits::Stringify, -}; -use mars_rover_health_types::{is_below_one, HealthValuesResponse}; +use cosmwasm_std::{Deps, Response}; +use mars_red_bank_types::oracle::ActionKind; +use mars_rover::error::{ContractError, ContractResult}; +use mars_rover_health_types::{HealthState, HealthValuesResponse}; use crate::{state::HEALTH_CONTRACT, utils::get_account_kind}; -pub fn query_health(deps: Deps, account_id: &str) -> ContractResult { +pub fn query_health_state( + deps: Deps, + account_id: &str, + action: ActionKind, +) -> ContractResult { + let hc = HEALTH_CONTRACT.load(deps.storage)?; + let kind = get_account_kind(deps.storage, account_id)?; + Ok(hc.query_health_state(&deps.querier, account_id, kind, action)?) +} + +pub fn query_health_values( + deps: Deps, + account_id: &str, + action: ActionKind, +) -> ContractResult { let hc = HEALTH_CONTRACT.load(deps.storage)?; let kind = get_account_kind(deps.storage, account_id)?; - Ok(hc.query_health(&deps.querier, account_id, kind)?) + Ok(hc.query_health_values(&deps.querier, account_id, kind, action)?) } pub fn assert_max_ltv( deps: Deps, - env: Env, account_id: &str, - prev_max_ltv_health_factor: &Option, + prev_health: HealthState, ) -> ContractResult { - let new_health = query_health(deps, account_id)?; + let new_health = query_health_state(deps, account_id, ActionKind::Default)?; - // If previous health was in a bad state, assert it did not further weaken - if is_below_one(prev_max_ltv_health_factor) { - if let (Some(prev_hf), Some(new_hf)) = - (prev_max_ltv_health_factor, new_health.max_ltv_health_factor) - { - if prev_hf > &new_hf { + match (&prev_health, &new_health) { + // If account ends in a healthy state, all good! ✅ + (_, HealthState::Healthy) => {} + // If previous health was in an unhealthy state, assert it did not further weaken ⚠️ + ( + HealthState::Unhealthy { + max_ltv_health_factor: prev_hf, + .. + }, + HealthState::Unhealthy { + max_ltv_health_factor: new_hf, + .. + }, + ) => { + if prev_hf > new_hf { return Err(ContractError::HealthNotImproved { prev_hf: prev_hf.to_string(), new_hf: new_hf.to_string(), }); } } - // if previous health was in a good state, assert it's still healthy - } else if new_health.above_max_ltv { - return Err(ContractError::AboveMaxLTV { - account_id: account_id.to_string(), - max_ltv_health_factor: new_health.max_ltv_health_factor.to_string(), - }); + // Else, it went from healthy to unhealthy, raise! ❌ + ( + HealthState::Healthy, + HealthState::Unhealthy { + max_ltv_health_factor, + .. + }, + ) => { + return Err(ContractError::AboveMaxLTV { + account_id: account_id.to_string(), + max_ltv_health_factor: max_ltv_health_factor.to_string(), + }); + } } - let event = Event::new("position_changed") - .add_attribute("timestamp", env.block.time.seconds().to_string()) - .add_attribute("height", env.block.height.to_string()) + Ok(Response::new() + .add_attribute("action", "callback/assert_health") .add_attribute("account_id", account_id) - .add_attribute("collateral_value", new_health.total_collateral_value.to_string()) - .add_attribute("debts_value", new_health.total_debt_value.to_string()) - .add_attribute("lqdt_health_factor", new_health.liquidation_health_factor.to_string()) - .add_attribute("liquidatable", new_health.liquidatable.to_string()) - .add_attribute("max_ltv_health_factor", new_health.max_ltv_health_factor.to_string()) - .add_attribute("above_max_ltv", new_health.above_max_ltv.to_string()); - - Ok(Response::new().add_attribute("action", "callback/assert_health").add_event(event)) + .add_attribute("prev_health_state", prev_health.to_string()) + .add_attribute("new_health_state", new_health.to_string())) } diff --git a/contracts/credit-manager/src/liquidate.rs b/contracts/credit-manager/src/liquidate.rs index 2d4d98252..5c60f469b 100644 --- a/contracts/credit-manager/src/liquidate.rs +++ b/contracts/credit-manager/src/liquidate.rs @@ -5,6 +5,7 @@ use std::{ use cosmwasm_std::{Coin, Decimal, DepsMut, Env, QuerierWrapper, StdError, Uint128}; use mars_params::types::asset::AssetParams; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ adapters::oracle::Oracle, error::{ContractError, ContractResult}, @@ -13,7 +14,7 @@ use mars_rover::{ use mars_rover_health_types::HealthValuesResponse; use crate::{ - health::query_health, + health::query_health_values, repay::current_debt_for_denom, state::{ORACLE, PARAMS}, }; @@ -34,7 +35,8 @@ pub fn calculate_liquidation( request_coin_balance: Uint128, ) -> ContractResult<(Coin, Coin, Coin)> { // Assert the liquidatee's credit account is liquidatable - let health = query_health(deps.as_ref(), liquidatee_account_id)?; + let health = + query_health_values(deps.as_ref(), liquidatee_account_id, ActionKind::Liquidation)?; if !health.liquidatable { return Err(ContractError::NotLiquidatable { account_id: liquidatee_account_id.to_string(), @@ -51,8 +53,10 @@ pub fn calculate_liquidation( let request_coin_params = params.query_asset_params(&deps.querier, request_coin)?; let oracle = ORACLE.load(deps.storage)?; - let debt_coin_price = oracle.query_price(&deps.querier, &debt_coin.denom)?.price; - let request_coin_price = oracle.query_price(&deps.querier, request_coin)?.price; + let debt_coin_price = + oracle.query_price(&deps.querier, &debt_coin.denom, ActionKind::Liquidation)?.price; + let request_coin_price = + oracle.query_price(&deps.querier, request_coin, ActionKind::Liquidation)?.price; let (debt_amount_to_repay, request_amount_to_liquidate, request_amount_received_by_liquidator) = calculate_liquidation_amounts( @@ -249,8 +253,8 @@ fn assert_liquidation_profitable( oracle: &Oracle, (debt_coin, request_coin, ..): (Coin, Coin, Coin), ) -> ContractResult<()> { - let debt_value = oracle.query_total_value(querier, &[debt_coin.clone()])?; - let request_value = oracle.query_total_value(querier, &[request_coin.clone()])?; + let debt_value = oracle.query_value(querier, &debt_coin, ActionKind::Liquidation)?; + let request_value = oracle.query_value(querier, &request_coin, ActionKind::Liquidation)?; if debt_value >= request_value { return Err(ContractError::LiquidationNotProfitable { diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 37d1e2104..4bee85f1e 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,6 +1,7 @@ use cosmwasm_std::{Coin, Deps, Env, Order, StdResult}; use cw_paginate::paginate_map; use cw_storage_plus::Bound; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ adapters::vault::{VaultBase, VaultPosition, VaultPositionValue, VaultUnchecked}, error::ContractResult, @@ -240,5 +241,5 @@ pub fn query_vault_position_value( vault_position: VaultPosition, ) -> StdResult { let oracle = ORACLE.load(deps.storage)?; - vault_position.query_values(&deps.querier, &oracle) + vault_position.query_values(&deps.querier, &oracle, ActionKind::Default) } diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index 31a019965..3fe1e4781 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -1,6 +1,7 @@ use cosmwasm_std::{ to_binary, Addr, Coin, CosmosMsg, Deps, DepsMut, QuerierWrapper, Response, Uint128, WasmMsg, }; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ adapters::vault::{UpdateType, Vault, VaultPositionUpdate}, error::{ContractError, ContractResult}, @@ -119,14 +120,16 @@ pub fn assert_deposit_is_under_cap( rover_addr: &Addr, ) -> ContractResult<()> { let oracle = ORACLE.load(deps.storage)?; - let deposit_request_value = oracle.query_total_value(&deps.querier, &[coin_to_add.clone()])?; + let deposit_request_value = + oracle.query_value(&deps.querier, coin_to_add, ActionKind::Default)?; let rover_vault_balance_value = rover_vault_coin_balance_value(&deps, vault, rover_addr)?; let new_total_vault_value = rover_vault_balance_value.checked_add(deposit_request_value)?; let params = PARAMS.load(deps.storage)?; let config = params.query_vault_config(&deps.querier, &vault.address)?; - let deposit_cap_value = oracle.query_total_value(&deps.querier, &[config.deposit_cap])?; + let deposit_cap_value = + oracle.query_value(&deps.querier, &config.deposit_cap, ActionKind::Default)?; if new_total_vault_value > deposit_cap_value { return Err(ContractError::AboveVaultDepositCap { diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 6f2045bfe..09d34dafe 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,4 +1,5 @@ use cosmwasm_std::{Addr, Coin, Deps, DepsMut, StdResult, Storage, Uint128}; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ adapters::vault::{ LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultPosition, @@ -89,7 +90,7 @@ pub fn vault_utilization_in_deposit_cap_denom( let config = params.query_vault_config(&deps.querier, &vault.address)?; let oracle = ORACLE.load(deps.storage)?; let deposit_cap_denom_price = - oracle.query_price(&deps.querier, &config.deposit_cap.denom)?.price; + oracle.query_price(&deps.querier, &config.deposit_cap.denom, ActionKind::Default)?.price; Ok(Coin { denom: config.deposit_cap.denom, @@ -117,6 +118,7 @@ pub fn rover_vault_coin_balance_value( }), }, }; - let vault_coin_balance_val = position.query_values(&deps.querier, &oracle)?.vault_coin.value; + let vault_coin_balance_val = + position.query_values(&deps.querier, &oracle, ActionKind::Default)?.vault_coin.value; Ok(vault_coin_balance_val) } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index a0c6fed2b..b828e1349 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -34,9 +34,12 @@ use mars_params::{ vault::{VaultConfig, VaultConfigUnchecked}, }, }; -use mars_red_bank_types::red_bank::{ - QueryMsg::{UserCollateral, UserDebt}, - UserCollateralResponse, UserDebtResponse, +use mars_red_bank_types::{ + oracle::ActionKind, + red_bank::{ + QueryMsg::{UserCollateral, UserDebt}, + UserCollateralResponse, UserDebtResponse, + }, }; use mars_rover::{ adapters::{ @@ -320,7 +323,12 @@ impl MockEnv { .unwrap() } - pub fn query_health(&self, account_id: &str, kind: AccountKind) -> HealthValuesResponse { + pub fn query_health( + &self, + account_id: &str, + kind: AccountKind, + action: ActionKind, + ) -> HealthValuesResponse { self.app .wrap() .query_wasm_smart( @@ -328,6 +336,7 @@ impl MockEnv { &HealthValues { account_id: account_id.to_string(), kind, + action, }, ) .unwrap() @@ -846,11 +855,13 @@ impl MockEnvBuilder { .get_coin_params() .iter() .map(|item| CoinPrice { + pricing: ActionKind::Default, denom: item.denom.clone(), price: item.price, }) .collect(); prices.push(CoinPrice { + pricing: ActionKind::Default, denom: "uusdc".to_string(), price: Decimal::from_atomics(12345u128, 4).unwrap(), }); @@ -861,6 +872,7 @@ impl MockEnvBuilder { self.vault_configs.clone().unwrap_or_default().iter().for_each(|v| { if !price_denoms.contains(&v.base_token_denom) { prices.push(CoinPrice { + pricing: ActionKind::Default, denom: v.base_token_denom.clone(), price: Decimal::from_atomics(456u128, 5).unwrap(), }); diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index a3389ce0c..a25a94c62 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -7,6 +7,7 @@ use mars_params::{ msg::{AssetParamsUpdate::AddOrUpdate, VaultConfigUpdate}, types::asset::LiquidationBonus, }; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ error::ContractError, msg::{ @@ -62,7 +63,7 @@ fn only_assets_with_no_debts() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); - let health = mock.query_health(&account_id, AccountKind::Default); + let health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); let assets_value = deposit_amount.checked_mul_floor(coin_info.price).unwrap(); assert_eq!(health.total_collateral_value, assets_value); assert_eq!(health.total_debt_value, Uint128::zero()); @@ -129,7 +130,7 @@ fn terra_ragnarok() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 1); - let health = mock.query_health(&account_id, AccountKind::Default); + let health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); let assets_value = (deposit_amount + borrow_amount).checked_mul_floor(coin_info.price).unwrap(); assert_eq!(health.total_collateral_value, assets_value); // Note: Simulated yield from mock_red_bank makes debt position more expensive @@ -155,6 +156,7 @@ fn terra_ragnarok() { assert!(!health.above_max_ltv); mock.price_change(CoinPrice { + pricing: ActionKind::Default, denom: coin_info.denom, price: Decimal::zero(), }); @@ -163,7 +165,7 @@ fn terra_ragnarok() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 1); - let health = mock.query_health(&account_id, AccountKind::Default); + let health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); assert_eq!(health.total_collateral_value, Uint128::zero()); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.liquidation_health_factor, None); @@ -207,7 +209,7 @@ fn debts_no_assets() { assert_eq!(position.deposits.len(), 0); assert_eq!(position.debts.len(), 0); - let health = mock.query_health(&account_id, AccountKind::Default); + let health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); assert_eq!(health.total_collateral_value, Uint128::zero()); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.liquidation_health_factor, None); @@ -256,7 +258,7 @@ fn cannot_borrow_more_than_healthy() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 1); - let health = mock.query_health(&account_id, AccountKind::Default); + let health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); let assets_value = Uint128::new(827); assert_eq!(health.total_collateral_value, assets_value); let debts_value = Uint128::new(121); @@ -281,7 +283,7 @@ fn cannot_borrow_more_than_healthy() { ); // All valid on step 2 as well (meaning step 3 did not go through) - let health = mock.query_health(&account_id, AccountKind::Default); + let health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); let assets_value = Uint128::new(1064); assert_eq!(health.total_collateral_value, assets_value); let debts_value = Uint128::new(360); @@ -355,16 +357,17 @@ fn cannot_borrow_more_but_not_liquidatable() { ) .unwrap(); - let health = mock.query_health(&account_id, AccountKind::Default); + let health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); assert!(!health.liquidatable); assert!(!health.above_max_ltv); mock.price_change(CoinPrice { + pricing: ActionKind::Default, denom: uatom_info.denom.clone(), price: Decimal::from_atomics(24u128, 0).unwrap(), }); - let health = mock.query_health(&account_id, AccountKind::Default); + let health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); assert!(!health.liquidatable); assert!(health.above_max_ltv); @@ -380,11 +383,12 @@ fn cannot_borrow_more_but_not_liquidatable() { ); mock.price_change(CoinPrice { + pricing: ActionKind::Default, denom: uatom_info.denom, price: Decimal::from_atomics(35u128, 0).unwrap(), }); - let health = mock.query_health(&account_id, AccountKind::Default); + let health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); assert!(health.liquidatable); assert!(health.above_max_ltv); } @@ -457,7 +461,7 @@ fn assets_and_ltv_lqdt_adjusted_value() { assert_eq!(position.deposits.len(), 2); assert_eq!(position.debts.len(), 1); - let health = mock.query_health(&account_id, AccountKind::Default); + let health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); assert_eq!( health.total_collateral_value, deposit_amount @@ -604,7 +608,7 @@ fn debt_value() { assert_eq!(position_a.deposits.len(), 2); assert_eq!(position_a.debts.len(), 2); - let health = mock.query_health(&account_id_a, AccountKind::Default); + let health = mock.query_health(&account_id_a, AccountKind::Default, ActionKind::Default); assert!(!health.above_max_ltv); assert!(!health.liquidatable); @@ -697,7 +701,7 @@ fn delisted_deposits_drop_max_ltv() { ) .unwrap(); - let prev_health = mock.query_health(&account_id, AccountKind::Default); + let prev_health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); // Blacklist osmo in params contract uosmo_info.whitelisted = false; @@ -705,7 +709,7 @@ fn delisted_deposits_drop_max_ltv() { params: uosmo_info.into(), }); - let curr_health = mock.query_health(&account_id, AccountKind::Default); + let curr_health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); // Values should be the same assert_eq!(prev_health.total_debt_value, curr_health.total_debt_value); @@ -760,7 +764,7 @@ fn delisted_vaults_drop_max_ltv() { ) .unwrap(); - let prev_health = mock.query_health(&account_id, AccountKind::Default); + let prev_health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); // Blacklist vault let mut config = mock.query_vault_params(&vault.address); @@ -769,7 +773,7 @@ fn delisted_vaults_drop_max_ltv() { config: config.into(), }); - let curr_health = mock.query_health(&account_id, AccountKind::Default); + let curr_health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); // Values should be the same assert_eq!(prev_health.total_debt_value, curr_health.total_debt_value); @@ -824,7 +828,7 @@ fn vault_base_token_delisting_drops_max_ltv() { ) .unwrap(); - let prev_health = mock.query_health(&account_id, AccountKind::Default); + let prev_health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); // Blacklist LP token in params contract lp_token.whitelisted = false; @@ -832,7 +836,7 @@ fn vault_base_token_delisting_drops_max_ltv() { params: lp_token.into(), }); - let curr_health = mock.query_health(&account_id, AccountKind::Default); + let curr_health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); // Values should be the same assert_eq!(prev_health.total_debt_value, curr_health.total_debt_value); @@ -905,11 +909,12 @@ fn can_take_actions_if_ltv_does_not_weaken() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Default, denom: uatom_info.denom.clone(), price: Decimal::from_atomics(24u128, 0).unwrap(), }); - let health = mock.query_health(&account_id, AccountKind::Default); + let health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); assert!(!health.liquidatable); assert!(health.above_max_ltv); diff --git a/contracts/credit-manager/tests/test_hls_accounts.rs b/contracts/credit-manager/tests/test_hls_accounts.rs index edba6caa2..ecfed772f 100644 --- a/contracts/credit-manager/tests/test_hls_accounts.rs +++ b/contracts/credit-manager/tests/test_hls_accounts.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{coins, Addr, Decimal, Uint128}; use mars_params::{msg::AssetParamsUpdate::AddOrUpdate, types::hls::HlsAssetType}; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ error::ContractError, msg::execute::Action::{Borrow, Deposit, EnterVault, Lend}, @@ -266,7 +267,8 @@ fn successful_with_asset_correlations() { ) .unwrap(); - let hls_health = mock.query_health(&account_id, AccountKind::HighLeveredStrategy); + let hls_health = + mock.query_health(&account_id, AccountKind::HighLeveredStrategy, ActionKind::Default); let total_debt_value = atom_info.price * Uint128::new(atom_borrow_amount) + Uint128::one(); let lp_collateral_value = lp_token.price * Uint128::new(lp_deposit_amount); let atom_collateral_value = atom_info.price * Uint128::new(atom_borrow_amount); @@ -295,7 +297,7 @@ fn successful_with_asset_correlations() { hls_health ); - let default_health = mock.query_health(&account_id, AccountKind::Default); + let default_health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); assert_ne!(hls_health, default_health); } @@ -347,7 +349,8 @@ fn successful_with_vault_correlations() { ) .unwrap(); - let hls_health = mock.query_health(&account_id, AccountKind::HighLeveredStrategy); + let hls_health = + mock.query_health(&account_id, AccountKind::HighLeveredStrategy, ActionKind::Default); let total_debt_value = atom_info.price * Uint128::new(atom_borrow_amount) + Uint128::one(); let lp_collateral_value = lp_token.price * Uint128::new(lp_deposit_amount); let atom_collateral_value = atom_info.price * Uint128::new(atom_borrow_amount); @@ -376,6 +379,6 @@ fn successful_with_vault_correlations() { hls_health ); - let default_health = mock.query_health(&account_id, AccountKind::Default); + let default_health = mock.query_health(&account_id, AccountKind::Default, ActionKind::Default); assert_ne!(hls_health, default_health); } diff --git a/contracts/credit-manager/tests/test_liquidate_deposit.rs b/contracts/credit-manager/tests/test_liquidate_deposit.rs index 57ee2bc0b..6301c8494 100644 --- a/contracts/credit-manager/tests/test_liquidate_deposit.rs +++ b/contracts/credit-manager/tests/test_liquidate_deposit.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{coins, Addr, Coin, Decimal, OverflowError, OverflowOperation, Uint128}; use mars_mock_oracle::msg::CoinPrice; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ error::{ ContractError, @@ -46,7 +47,8 @@ fn can_only_liquidate_unhealthy_accounts() { ) .unwrap(); - let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(!health.liquidatable); let liquidator = Addr::unchecked("liquidator"); @@ -107,7 +109,8 @@ fn vault_positions_contribute_to_health() { ) .unwrap(); - let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(!health.liquidatable); let liquidator = Addr::unchecked("liquidator"); @@ -158,10 +161,12 @@ fn liquidatee_does_not_have_requested_asset() { ) .unwrap(); - let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(!health.liquidatable); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uatom_info.denom.clone(), price: Decimal::from_atomics(20u128, 0).unwrap(), }); @@ -216,7 +221,8 @@ fn liquidatee_does_not_have_debt_coin() { ) .unwrap(); - let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(!health.liquidatable); // Seeding a jakecoin borrow @@ -230,6 +236,7 @@ fn liquidatee_does_not_have_debt_coin() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uatom_info.denom.clone(), price: Decimal::from_atomics(20u128, 0).unwrap(), }); @@ -278,10 +285,12 @@ fn liquidator_does_not_have_enough_to_pay_debt() { ) .unwrap(); - let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(!health.liquidatable); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uatom_info.denom.clone(), price: Decimal::from_atomics(20u128, 0).unwrap(), }); @@ -334,10 +343,12 @@ fn liquidator_left_in_unhealthy_state() { ) .unwrap(); - let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(!health.liquidatable); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uatom_info.denom.clone(), price: Decimal::from_atomics(20u128, 0).unwrap(), }); @@ -349,7 +360,7 @@ fn liquidator_left_in_unhealthy_state() { &liquidator_account_id, &liquidator, vec![ - Borrow(uatom_info.to_coin(10)), + Borrow(uatom_info.to_coin(1000)), Liquidate { liquidatee_account_id: liquidatee_account_id.clone(), debt_coin: uatom_info.to_coin(10), @@ -363,7 +374,7 @@ fn liquidator_left_in_unhealthy_state() { res, AboveMaxLTV { account_id: liquidator_account_id, - max_ltv_health_factor: "0.70909090909090909".to_string(), + max_ltv_health_factor: "0.858141858141858141".to_string(), }, ) } @@ -402,11 +413,13 @@ fn liquidation_not_profitable_after_calculations() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: ujake_info.denom, price: Decimal::from_atomics(100u128, 0).unwrap(), }); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uosmo_info.denom.clone(), price: Decimal::from_atomics(2u128, 0).unwrap(), }); @@ -471,6 +484,7 @@ fn target_health_factor_reached_after_max_debt_repayed() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uatom_info.denom.clone(), price: Decimal::from_atomics(128u128, 2).unwrap(), }); @@ -523,7 +537,7 @@ fn target_health_factor_reached_after_max_debt_repayed() { // Assert HF for liquidatee let account_kind = mock.query_account_kind(&liquidatee_account_id); - let health = mock.query_health(&liquidatee_account_id, account_kind); + let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); // it should be 1.2, but because of roundings it is hard to achieve an exact number let health_diff = health.liquidation_health_factor.unwrap().abs_diff(thf); assert!(health_diff < Decimal::from_atomics(1u128, 2u32).unwrap()); @@ -564,6 +578,7 @@ fn debt_amount_adjusted_to_total_debt_for_denom() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uatom_info.denom, price: Decimal::from_atomics(5u128, 0).unwrap(), }); @@ -616,7 +631,7 @@ fn debt_amount_adjusted_to_total_debt_for_denom() { // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); - let health = mock.query_health(&liquidatee_account_id, account_kind); + let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); assert!(!health.liquidatable); } @@ -649,6 +664,7 @@ fn debt_amount_adjusted_to_max_allowed_by_request_coin() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uatom_info.denom.clone(), price: Decimal::from_atomics(6u128, 0).unwrap(), }); @@ -701,7 +717,7 @@ fn debt_amount_adjusted_to_max_allowed_by_request_coin() { // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); - let health = mock.query_health(&liquidatee_account_id, account_kind); + let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); assert!(!health.liquidatable); } @@ -735,6 +751,7 @@ fn debt_amount_no_adjustment() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uatom_info.denom.clone(), price: Decimal::from_atomics(59u128, 1).unwrap(), }); @@ -785,7 +802,7 @@ fn debt_amount_no_adjustment() { // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); - let health = mock.query_health(&liquidatee_account_id, account_kind); + let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); assert!(!health.liquidatable); } @@ -824,12 +841,14 @@ fn improve_hf_but_acc_unhealthy() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uatom_info.denom, price: Decimal::from_atomics(10u128, 0).unwrap(), }); let account_kind = mock.query_account_kind(&liquidatee_account_id); - let prev_health = mock.query_health(&liquidatee_account_id, account_kind.clone()); + let prev_health = + mock.query_health(&liquidatee_account_id, account_kind.clone(), ActionKind::Liquidation); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -882,7 +901,7 @@ fn improve_hf_but_acc_unhealthy() { assert_eq!(atom_balance.amount, Uint128::new(20)); // Liq HF should improve - let health = mock.query_health(&liquidatee_account_id, account_kind); + let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); assert!(health.liquidatable); assert!( prev_health.liquidation_health_factor.unwrap() < health.liquidation_health_factor.unwrap() diff --git a/contracts/credit-manager/tests/test_liquidate_lend.rs b/contracts/credit-manager/tests/test_liquidate_lend.rs index c5ae90e6b..2fa88008a 100644 --- a/contracts/credit-manager/tests/test_liquidate_lend.rs +++ b/contracts/credit-manager/tests/test_liquidate_lend.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{coins, Addr, Decimal, Uint128}; use mars_mock_oracle::msg::CoinPrice; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ error::{ContractError, ContractError::NotLiquidatable}, msg::execute::{ @@ -44,7 +45,8 @@ fn lent_positions_contribute_to_health() { ) .unwrap(); - let health_1 = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let health_1 = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(!health_1.liquidatable); mock.update_credit_account( @@ -56,7 +58,8 @@ fn lent_positions_contribute_to_health() { .unwrap(); // Collateral should be the same after Lend - let health_2 = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let health_2 = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(!health_2.liquidatable); // health_2.total_collateral_value bigger (+1) because of simulated yield assert_eq!(health_1.total_collateral_value, health_2.total_collateral_value - Uint128::one()); @@ -126,11 +129,13 @@ fn liquidatee_does_not_have_requested_lent_coin() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uosmo_info.denom.clone(), price: Decimal::from_atomics(20u128, 0).unwrap(), }); - let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(health.liquidatable); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -189,11 +194,13 @@ fn lent_position_partially_liquidated() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uatom_info.denom.clone(), price: Decimal::from_atomics(22u128, 1).unwrap(), }); - let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(health.liquidatable); assert_eq!(health.total_collateral_value, Uint128::new(2462u128)); assert_eq!(health.total_debt_value, Uint128::new(2203u128)); @@ -252,7 +259,7 @@ fn lent_position_partially_liquidated() { // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); - let health = mock.query_health(&liquidatee_account_id, account_kind); + let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); assert!(!health.liquidatable); } @@ -281,6 +288,13 @@ fn lent_position_fully_liquidated() { let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Default, + denom: uosmo_info.denom.clone(), + price: Decimal::from_atomics(10u128, 1).unwrap(), + }); + + mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uosmo_info.denom.clone(), price: Decimal::from_atomics(10u128, 1).unwrap(), }); @@ -298,11 +312,13 @@ fn lent_position_fully_liquidated() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uatom_info.denom.clone(), price: Decimal::from_atomics(50u128, 1).unwrap(), }); - let prev_health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let prev_health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(prev_health.liquidatable); assert_eq!(prev_health.total_collateral_value, Uint128::new(2801u128)); assert_eq!(prev_health.total_debt_value, Uint128::new(2505u128)); @@ -366,7 +382,7 @@ fn lent_position_fully_liquidated() { // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); - let health = mock.query_health(&liquidatee_account_id, account_kind); + let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); assert!(health.liquidatable); assert!( prev_health.liquidation_health_factor.unwrap() < health.liquidation_health_factor.unwrap() @@ -410,11 +426,13 @@ fn liquidate_with_reclaiming() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: uatom_info.denom.clone(), price: Decimal::from_atomics(82u128, 1).unwrap(), }); - let prev_health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let prev_health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(prev_health.liquidatable); assert_eq!(prev_health.total_collateral_value, Uint128::new(8950u128)); assert_eq!(prev_health.total_debt_value, Uint128::new(8209u128)); @@ -482,7 +500,7 @@ fn liquidate_with_reclaiming() { // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); - let health = mock.query_health(&liquidatee_account_id, account_kind); + let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); assert!(health.liquidatable); assert!( prev_health.liquidation_health_factor.unwrap() < health.liquidation_health_factor.unwrap() diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index ddc568975..a6663c7d4 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -2,6 +2,7 @@ use cosmwasm_std::{ Addr, Decimal, OverflowError, OverflowOperation::Sub, StdError::NotFound, Uint128, }; use mars_mock_oracle::msg::CoinPrice; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ adapters::vault::{VaultBase, VaultPositionType}, error::ContractError, @@ -169,6 +170,7 @@ fn liquidator_does_not_have_debt_coin_in_credit_account() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: ujake.denom.clone(), price: Decimal::from_atomics(20u128, 0).unwrap(), }); @@ -365,11 +367,13 @@ fn liquidate_unlocked_vault() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: ujake.denom.clone(), price: Decimal::from_atomics(18u128, 0).unwrap(), }); - let prev_health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let prev_health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(prev_health.liquidatable); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -423,7 +427,7 @@ fn liquidate_unlocked_vault() { // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); - let health = mock.query_health(&liquidatee_account_id, account_kind); + let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); assert!(!health.liquidatable); } @@ -469,11 +473,13 @@ fn liquidate_locked_vault() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: atom.denom.clone(), price: Decimal::from_atomics(20u128, 0).unwrap(), }); - let prev_health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let prev_health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(prev_health.liquidatable); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -530,7 +536,7 @@ fn liquidate_locked_vault() { // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); - let health = mock.query_health(&liquidatee_account_id, account_kind); + let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); assert!(health.liquidatable); assert!( prev_health.liquidation_health_factor.unwrap() < health.liquidation_health_factor.unwrap() @@ -596,11 +602,13 @@ fn liquidate_unlocking_liquidation_order() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: ujake.denom.clone(), price: Decimal::from_atomics(20u128, 0).unwrap(), }); - let prev_health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let prev_health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(prev_health.liquidatable); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -666,7 +674,7 @@ fn liquidate_unlocking_liquidation_order() { // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); - let health = mock.query_health(&liquidatee_account_id, account_kind); + let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); assert!(health.liquidatable); assert!( prev_health.liquidation_health_factor.unwrap() < health.liquidation_health_factor.unwrap() @@ -717,11 +725,13 @@ fn liquidation_calculation_adjustment() { .unwrap(); mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, denom: ujake.denom.clone(), price: Decimal::from_atomics(20u128, 0).unwrap(), }); - let prev_health = mock.query_health(&liquidatee_account_id, AccountKind::Default); + let prev_health = + mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); assert!(prev_health.liquidatable); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -779,6 +789,6 @@ fn liquidation_calculation_adjustment() { // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); - let health = mock.query_health(&liquidatee_account_id, account_kind); + let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); assert!(!health.liquidatable); } diff --git a/contracts/credit-manager/tests/test_liquidation_pricing.rs b/contracts/credit-manager/tests/test_liquidation_pricing.rs new file mode 100644 index 000000000..33d8c080d --- /dev/null +++ b/contracts/credit-manager/tests/test_liquidation_pricing.rs @@ -0,0 +1,97 @@ +use cosmwasm_std::{coins, Addr, Coin, Decimal}; +use mars_mock_oracle::msg::CoinPrice; +use mars_red_bank_types::oracle::ActionKind; +use mars_rover::{ + error::ContractError, + msg::execute::{ + Action::{Borrow, Deposit, Liquidate}, + LiquidateRequest, + }, +}; + +use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn liquidation_uses_correct_price_kind() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + let liquidator = Addr::unchecked("liquidator"); + let liquidatee = Addr::unchecked("liquidatee"); + let mut mock = MockEnv::new() + .target_health_factor(Decimal::from_atomics(12u128, 1).unwrap()) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: liquidatee.clone(), + funds: coins(3000, uosmo_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: liquidator.clone(), + funds: coins(3000, uatom_info.denom.clone()), + }) + .build() + .unwrap(); + let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); + + mock.update_credit_account( + &liquidatee_account_id, + &liquidatee, + vec![Deposit(uosmo_info.to_coin(3000)), Borrow(uatom_info.to_coin(1000))], + &[Coin::new(3000, uosmo_info.denom.clone())], + ) + .unwrap(); + + let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); + + // The liquidation should not acknowledge DEFAULT pricing changes + mock.price_change(CoinPrice { + pricing: ActionKind::Default, + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(59u128, 1).unwrap(), + }); + + let res = mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(uatom_info.to_coin(100)), + Liquidate { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(100), + request: LiquidateRequest::Deposit(uosmo_info.denom.clone()), + }, + ], + &[uatom_info.to_coin(100)], + ); + + assert_err( + res, + ContractError::NotLiquidatable { + account_id: liquidatee_account_id.clone(), + lqdt_health_factor: "1.483516483516483516".to_string(), + }, + ); + + // The liquidation should acknowledge LIQUIDATION pricing changes and go through fine + mock.price_change(CoinPrice { + pricing: ActionKind::Liquidation, + denom: uatom_info.denom.clone(), + price: Decimal::from_atomics(59u128, 1).unwrap(), + }); + + mock.update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(uatom_info.to_coin(100)), + Liquidate { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(100), + request: LiquidateRequest::Deposit(uosmo_info.denom), + }, + ], + &[uatom_info.to_coin(100)], + ) + .unwrap(); +} diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index e8a64aa00..927c4097e 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -1,6 +1,7 @@ use cosmwasm_std::{Addr, Decimal, Empty, Uint128}; use cw_multi_test::{BasicApp, Executor}; use mars_mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ adapters::{ health::HealthContractUnchecked, oracle::OracleUnchecked, red_bank::RedBankUnchecked, @@ -168,10 +169,12 @@ fn deploy_new_oracle(app: &mut BasicApp) -> OracleUnchecked { &OracleInstantiateMsg { prices: vec![ CoinPrice { + pricing: ActionKind::Default, denom: "uusdc".to_string(), price: Decimal::from_atomics(12345u128, 4).unwrap(), }, CoinPrice { + pricing: ActionKind::Default, denom: "vault_xyz".to_string(), price: Decimal::from_atomics(989685877u128, 8).unwrap(), }, diff --git a/contracts/health/Cargo.toml b/contracts/health/Cargo.toml index b4f6143f4..2cbfad5fb 100644 --- a/contracts/health/Cargo.toml +++ b/contracts/health/Cargo.toml @@ -25,6 +25,7 @@ cw2 = { workspace = true } cw-storage-plus = { workspace = true } mars-owner = { workspace = true } mars-params = { workspace = true } +mars-red-bank-types = { workspace = true } mars-rover-health-computer = { workspace = true } mars-rover-health-types = { workspace = true } mars-rover = { workspace = true } diff --git a/contracts/health/src/compute.rs b/contracts/health/src/compute.rs index aa098bdb1..eb613ca8a 100644 --- a/contracts/health/src/compute.rs +++ b/contracts/health/src/compute.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use cosmwasm_std::{Deps, StdResult}; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::msg::query::Positions; use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; use mars_rover_health_types::{AccountKind, HealthResult, HealthState, HealthValuesResponse}; @@ -15,6 +16,7 @@ pub fn compute_health( kind: AccountKind, q: HealthQuerier, positions: Positions, + action: ActionKind, ) -> HealthResult { // Get the denoms that need prices + markets let deposit_denoms = positions.deposits.iter().map(|d| &d.denom).collect::>(); @@ -38,7 +40,7 @@ pub fn compute_health( .chain(lend_denoms) .chain(vault_base_token_denoms) .try_for_each(|denom| -> StdResult<()> { - let price = q.oracle.query_price(&deps.querier, denom)?.price; + let price = q.oracle.query_price(&deps.querier, denom, action.clone())?.price; denoms_data.prices.insert(denom.clone(), price); let params = q.params.query_asset_params(&deps.querier, denom)?; denoms_data.params.insert(denom.clone(), params); @@ -48,7 +50,7 @@ pub fn compute_health( // Collect all vault data let mut vaults_data: VaultsData = Default::default(); positions.vaults.iter().try_for_each(|v| -> HealthResult<()> { - let vault_coin_value = v.query_values(&deps.querier, &q.oracle)?; + let vault_coin_value = v.query_values(&deps.querier, &q.oracle, action.clone())?; vaults_data.vault_values.insert(v.vault.address.clone(), vault_coin_value); let config = q.query_vault_config(&v.vault)?; vaults_data.vault_configs.insert(v.vault.address.clone(), config); @@ -69,13 +71,19 @@ pub fn health_values( deps: Deps, account_id: &str, kind: AccountKind, + action: ActionKind, ) -> HealthResult { let q = HealthQuerier::new(&deps)?; let positions = q.query_positions(account_id)?; - compute_health(deps, kind, q, positions) + compute_health(deps, kind, q, positions, action) } -pub fn health_state(deps: Deps, account_id: &str, kind: AccountKind) -> HealthResult { +pub fn health_state( + deps: Deps, + account_id: &str, + kind: AccountKind, + action: ActionKind, +) -> HealthResult { let q = HealthQuerier::new(&deps)?; let positions = q.query_positions(account_id)?; @@ -85,7 +93,7 @@ pub fn health_state(deps: Deps, account_id: &str, kind: AccountKind) -> HealthRe return Ok(HealthState::Healthy); } - let health = compute_health(deps, kind, q, positions)?; + let health = compute_health(deps, kind, q, positions, action)?; if !health.above_max_ltv { Ok(HealthState::Healthy) } else { diff --git a/contracts/health/src/contract.rs b/contracts/health/src/contract.rs index 4997aaae2..de496ae3c 100644 --- a/contracts/health/src/contract.rs +++ b/contracts/health/src/contract.rs @@ -55,11 +55,13 @@ pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> HealthResult { QueryMsg::HealthValues { account_id, kind, - } => to_binary(&health_values(deps, &account_id, kind)?), + action, + } => to_binary(&health_values(deps, &account_id, kind, action)?), QueryMsg::HealthState { account_id, kind, - } => to_binary(&health_state(deps, &account_id, kind)?), + action, + } => to_binary(&health_state(deps, &account_id, kind, action)?), QueryMsg::Config {} => to_binary(&query_config(deps)?), }; res.map_err(Into::into) diff --git a/contracts/health/tests/helpers/mock_env.rs b/contracts/health/tests/helpers/mock_env.rs index e8720d57a..ef09e22b5 100644 --- a/contracts/health/tests/helpers/mock_env.rs +++ b/contracts/health/tests/helpers/mock_env.rs @@ -15,6 +15,7 @@ use mars_params::{ }, types::vault::VaultConfig, }; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{adapters::vault::VaultUnchecked, msg::query::Positions}; use mars_rover_health_types::{ AccountKind, ConfigResponse, ExecuteMsg::UpdateConfig, HealthState, HealthValuesResponse, @@ -52,12 +53,14 @@ impl MockEnv { &self, account_id: &str, kind: AccountKind, + action: ActionKind, ) -> StdResult { self.app.wrap().query_wasm_smart( self.health_contract.clone(), &QueryMsg::HealthValues { account_id: account_id.to_string(), kind, + action, }, ) } @@ -66,12 +69,14 @@ impl MockEnv { &self, account_id: &str, kind: AccountKind, + action: ActionKind, ) -> StdResult { self.app.wrap().query_wasm_smart( self.health_contract.clone(), &QueryMsg::HealthState { account_id: account_id.to_string(), kind, + action, }, ) } @@ -169,12 +174,13 @@ impl MockEnv { .unwrap(); } - pub fn set_price(&mut self, denom: &str, price: Decimal) { + pub fn set_price(&mut self, denom: &str, price: Decimal, pricing: ActionKind) { self.app .execute_contract( self.deployer.clone(), self.oracle.clone(), &ChangePrice(CoinPrice { + pricing, denom: denom.to_string(), price, }), diff --git a/contracts/health/tests/test_health_state.rs b/contracts/health/tests/test_health_state.rs index 480b82b93..54e8cedf5 100644 --- a/contracts/health/tests/test_health_state.rs +++ b/contracts/health/tests/test_health_state.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{Coin, Decimal, Uint128}; use mars_params::msg::AssetParamsUpdate::AddOrUpdate; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::msg::query::{DebtAmount, Positions}; use mars_rover_health_types::{AccountKind, HealthState}; @@ -26,7 +27,8 @@ fn zero_debts_results_in_healthy_state() { }, ); - let state = mock.query_health_state(account_id, AccountKind::Default).unwrap(); + let state = + mock.query_health_state(account_id, AccountKind::Default, ActionKind::Default).unwrap(); assert_eq!(state, HealthState::Healthy); } @@ -36,7 +38,7 @@ fn computing_health_when_healthy() { let mut mock = MockEnv::new().build().unwrap(); let umars = "umars"; - mock.set_price(umars, Decimal::one()); + mock.set_price(umars, Decimal::one(), ActionKind::Default); mock.update_asset_params(AddOrUpdate { params: default_asset_params(umars), }); @@ -60,7 +62,8 @@ fn computing_health_when_healthy() { }, ); - let state = mock.query_health_state(account_id, AccountKind::Default).unwrap(); + let state = + mock.query_health_state(account_id, AccountKind::Default, ActionKind::Default).unwrap(); assert_eq!(state, HealthState::Healthy); } @@ -69,7 +72,7 @@ fn computing_health_when_unhealthy() { let mut mock = MockEnv::new().build().unwrap(); let umars = "umars"; - mock.set_price(umars, Decimal::one()); + mock.set_price(umars, Decimal::one(), ActionKind::Default); mock.update_asset_params(AddOrUpdate { params: default_asset_params(umars), }); @@ -93,6 +96,7 @@ fn computing_health_when_unhealthy() { }, ); - let state = mock.query_health_state(account_id, AccountKind::Default).unwrap(); + let state = + mock.query_health_state(account_id, AccountKind::Default, ActionKind::Default).unwrap(); assert!(matches!(state, HealthState::Unhealthy { .. })); } diff --git a/contracts/health/tests/test_health_values.rs b/contracts/health/tests/test_health_values.rs index 07adc497b..a6d8a4c33 100644 --- a/contracts/health/tests/test_health_values.rs +++ b/contracts/health/tests/test_health_values.rs @@ -8,6 +8,7 @@ use mars_params::{ hls::HlsParamsUnchecked, }, }; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ adapters::vault::{ LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultPosition, @@ -24,7 +25,8 @@ pub mod helpers; #[test] fn raises_when_credit_manager_not_set() { let mock = MockEnv::new().skip_cm_config().build().unwrap(); - let err: StdError = mock.query_health_values("xyz", AccountKind::Default).unwrap_err(); + let err: StdError = + mock.query_health_values("xyz", AccountKind::Default, ActionKind::Default).unwrap_err(); assert_eq!( err, StdError::generic_err( @@ -36,7 +38,8 @@ fn raises_when_credit_manager_not_set() { #[test] fn raises_with_non_existent_account_id() { let mock = MockEnv::new().build().unwrap(); - let err: StdError = mock.query_health_values("xyz", AccountKind::Default).unwrap_err(); + let err: StdError = + mock.query_health_values("xyz", AccountKind::Default, ActionKind::Default).unwrap_err(); assert_eq!( err, StdError::generic_err( @@ -62,7 +65,8 @@ fn computes_correct_position_with_zero_assets() { }, ); - let health = mock.query_health_values(account_id, AccountKind::Default).unwrap(); + let health = + mock.query_health_values(account_id, AccountKind::Default, ActionKind::Default).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, Uint128::zero()); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); @@ -139,9 +143,10 @@ fn adds_vault_base_denoms_to_oracle_and_red_bank() { mock.update_asset_params(update); - mock.set_price(vault_base_token, Decimal::one()); + mock.set_price(vault_base_token, Decimal::one(), ActionKind::Default); - let health = mock.query_health_values(account_id, AccountKind::Default).unwrap(); + let health = + mock.query_health_values(account_id, AccountKind::Default, ActionKind::Default).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, unlocking_amount); assert_eq!( @@ -164,7 +169,7 @@ fn whitelisted_coins_work() { let umars = "umars"; - mock.set_price(umars, Decimal::one()); + mock.set_price(umars, Decimal::one(), ActionKind::Default); let max_loan_to_value = Decimal::from_atomics(4523u128, 4).unwrap(); let liquidation_threshold = Decimal::from_atomics(5u128, 1).unwrap(); @@ -219,7 +224,8 @@ fn whitelisted_coins_work() { }, ); - let health = mock.query_health_values(account_id, AccountKind::Default).unwrap(); + let health = + mock.query_health_values(account_id, AccountKind::Default, ActionKind::Default).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, deposit_amount); // price of 1 assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); // coin not in whitelist @@ -237,7 +243,8 @@ fn whitelisted_coins_work() { mock.update_asset_params(AddOrUpdate { params: asset_params, }); - let health = mock.query_health_values(account_id, AccountKind::Default).unwrap(); + let health = + mock.query_health_values(account_id, AccountKind::Default, ActionKind::Default).unwrap(); // Now reflects deposit value assert_eq!( health.max_ltv_adjusted_collateral, @@ -303,11 +310,12 @@ fn vault_whitelist_affects_max_ltv() { mock.update_asset_params(update); - mock.set_price(vault_base_token, Decimal::one()); + mock.set_price(vault_base_token, Decimal::one(), ActionKind::Default); let mut vault_config = mock.query_vault_config(&vault.into()); - let health = mock.query_health_values(account_id, AccountKind::Default).unwrap(); + let health = + mock.query_health_values(account_id, AccountKind::Default, ActionKind::Default).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, base_token_amount); assert_eq!( @@ -330,6 +338,7 @@ fn vault_whitelist_affects_max_ltv() { config: vault_config.into(), }); - let health = mock.query_health_values(account_id, AccountKind::Default).unwrap(); + let health = + mock.query_health_values(account_id, AccountKind::Default, ActionKind::Default).unwrap(); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); } diff --git a/contracts/health/tests/test_hls.rs b/contracts/health/tests/test_hls.rs index 15fbf8df5..ba19eacf8 100644 --- a/contracts/health/tests/test_hls.rs +++ b/contracts/health/tests/test_hls.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use cosmwasm_std::{Decimal, Uint128}; use mars_params::msg::AssetParamsUpdate::AddOrUpdate; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ adapters::vault::{Vault, VaultAmount, VaultPosition, VaultPositionAmount}, msg::query::{DebtAmount, Positions}, @@ -42,7 +43,7 @@ fn hls_account_kind_passed_along() { }], }; mock.set_positions_response(account_id, &positions); - mock.set_price(debt_token, Decimal::one()); + mock.set_price(debt_token, Decimal::one(), ActionKind::Default); mock.update_asset_params(AddOrUpdate { params: default_asset_params(debt_token), }); @@ -51,11 +52,13 @@ fn hls_account_kind_passed_along() { params: default_asset_params(vault_base_token), }); - mock.set_price(vault_base_token, Decimal::one()); + mock.set_price(vault_base_token, Decimal::one(), ActionKind::Default); let vault_config = mock.query_vault_config(&vault.into()); - let health = mock.query_health_values(account_id, AccountKind::HighLeveredStrategy).unwrap(); + let health = mock + .query_health_values(account_id, AccountKind::HighLeveredStrategy, ActionKind::Default) + .unwrap(); assert_eq!(health.total_debt_value, positions.debts.first().unwrap().amount); assert_eq!(health.total_collateral_value, base_token_amount); assert_eq!( diff --git a/contracts/health/tests/test_liquidation_pricing.rs b/contracts/health/tests/test_liquidation_pricing.rs new file mode 100644 index 000000000..a040785ff --- /dev/null +++ b/contracts/health/tests/test_liquidation_pricing.rs @@ -0,0 +1,97 @@ +use std::str::FromStr; + +use cosmwasm_std::{Coin, Decimal, StdError, Uint128}; +use mars_params::{ + msg::AssetParamsUpdate::AddOrUpdate, + types::{ + asset::{AssetParamsUnchecked, CmSettings, LiquidationBonus, RedBankSettings}, + hls::HlsParamsUnchecked, + }, +}; +use mars_red_bank_types::oracle::ActionKind; +use mars_rover::msg::query::{DebtAmount, Positions}; +use mars_rover_health_types::AccountKind; + +use crate::helpers::MockEnv; + +pub mod helpers; + +#[test] +fn uses_liquidation_pricing() { + let mut mock = MockEnv::new().build().unwrap(); + + let umars = "umars"; + mock.set_price(umars, Decimal::one(), ActionKind::Liquidation); + + let update = AddOrUpdate { + params: AssetParamsUnchecked { + denom: umars.to_string(), + credit_manager: CmSettings { + whitelisted: false, + hls: Some(HlsParamsUnchecked { + max_loan_to_value: Decimal::from_str("0.8").unwrap(), + liquidation_threshold: Decimal::from_str("0.9").unwrap(), + correlations: vec![], + }), + }, + red_bank: RedBankSettings { + deposit_enabled: false, + borrow_enabled: false, + deposit_cap: Default::default(), + }, + max_loan_to_value: Decimal::from_atomics(4523u128, 4).unwrap(), + liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), + }, + }; + + mock.update_asset_params(update); + + let account_id = "123"; + mock.set_positions_response( + account_id, + &Positions { + account_id: account_id.to_string(), + deposits: vec![Coin { + denom: umars.to_string(), + amount: Uint128::new(30), + }], + debts: vec![DebtAmount { + denom: umars.to_string(), + shares: Default::default(), + amount: Uint128::new(2), + }], + lends: vec![], + vaults: vec![], + }, + ); + + // Default pricing should error + let err: StdError = + mock.query_health_state(account_id, AccountKind::Default, ActionKind::Default).unwrap_err(); + assert_eq!( + err, + StdError::generic_err( + "Querier contract error: Generic error: Querier contract error: cosmwasm_std::math::decimal::Decimal not found".to_string() + ) + ); + let err: StdError = mock + .query_health_values(account_id, AccountKind::Default, ActionKind::Default) + .unwrap_err(); + assert_eq!( + err, + StdError::generic_err( + "Querier contract error: Generic error: Querier contract error: cosmwasm_std::math::decimal::Decimal not found".to_string() + ) + ); + + // Liquidation pricing is used and succeeds + mock.query_health_state(account_id, AccountKind::Default, ActionKind::Liquidation).unwrap(); + mock.query_health_values(account_id, AccountKind::Default, ActionKind::Liquidation).unwrap(); +} diff --git a/contracts/mock-health/src/contract.rs b/contracts/mock-health/src/contract.rs index 1c386e398..50b68ddab 100644 --- a/contracts/mock-health/src/contract.rs +++ b/contracts/mock-health/src/contract.rs @@ -39,6 +39,7 @@ pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> HealthResult { QueryMsg::HealthValues { account_id, kind, + .. } => to_binary(&query_health(deps, &account_id, kind)?), _ => unimplemented!("query msg not supported"), }; diff --git a/contracts/mock-oracle/src/contract.rs b/contracts/mock-oracle/src/contract.rs index e70aa3646..89f47774c 100644 --- a/contracts/mock-oracle/src/contract.rs +++ b/contracts/mock-oracle/src/contract.rs @@ -1,11 +1,11 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; -use mars_red_bank_types::oracle::PriceResponse; +use mars_red_bank_types::oracle::{ActionKind, PriceResponse}; use crate::{ msg::{CoinPrice, ExecuteMsg, InstantiateMsg, QueryMsg}, - state::COIN_PRICE, + state::{DEFAULT_COIN_PRICE, LIQUIDATION_COIN_PRICE}, }; #[cfg_attr(not(feature = "library"), entry_point)] @@ -16,7 +16,8 @@ pub fn instantiate( msg: InstantiateMsg, ) -> StdResult { for item in msg.prices { - COIN_PRICE.save(deps.storage, item.denom, &item.price)? + DEFAULT_COIN_PRICE.save(deps.storage, item.denom.clone(), &item.price)?; + LIQUIDATION_COIN_PRICE.save(deps.storage, item.denom, &item.price)?; } Ok(Response::default()) } @@ -29,12 +30,19 @@ pub fn execute( msg: ExecuteMsg, ) -> StdResult { match msg { - ExecuteMsg::ChangePrice(item) => change_price(deps, item), + ExecuteMsg::ChangePrice(coin) => change_price(deps, coin), } } fn change_price(deps: DepsMut, coin: CoinPrice) -> StdResult { - COIN_PRICE.save(deps.storage, coin.denom, &coin.price)?; + match coin.pricing { + ActionKind::Default => { + DEFAULT_COIN_PRICE.save(deps.storage, coin.denom, &coin.price)?; + } + ActionKind::Liquidation => { + LIQUIDATION_COIN_PRICE.save(deps.storage, coin.denom, &coin.price)? + } + } Ok(Response::new()) } @@ -43,12 +51,21 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Price { denom, - } => to_binary(&query_price(deps, denom)?), + kind, + } => to_binary(&query_price(deps, denom, kind)?), } } -fn query_price(deps: Deps, denom: String) -> StdResult { - let price = COIN_PRICE.load(deps.storage, denom.clone())?; +fn query_price( + deps: Deps, + denom: String, + kind_opt: Option, +) -> StdResult { + let price = match kind_opt.unwrap_or(ActionKind::Default) { + ActionKind::Default => DEFAULT_COIN_PRICE.load(deps.storage, denom.clone())?, + ActionKind::Liquidation => LIQUIDATION_COIN_PRICE.load(deps.storage, denom.clone())?, + }; + Ok(PriceResponse { denom, price, diff --git a/contracts/mock-oracle/src/msg.rs b/contracts/mock-oracle/src/msg.rs index 680683b26..42d7f52cc 100644 --- a/contracts/mock-oracle/src/msg.rs +++ b/contracts/mock-oracle/src/msg.rs @@ -1,8 +1,10 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Decimal; +use mars_red_bank_types::oracle::ActionKind; #[cw_serde] pub struct CoinPrice { + pub pricing: ActionKind, pub denom: String, pub price: Decimal, } @@ -24,5 +26,6 @@ pub enum QueryMsg { #[returns(mars_red_bank_types::oracle::PriceResponse)] Price { denom: String, + kind: Option, }, } diff --git a/contracts/mock-oracle/src/state.rs b/contracts/mock-oracle/src/state.rs index dc549d09e..dd583e1fb 100644 --- a/contracts/mock-oracle/src/state.rs +++ b/contracts/mock-oracle/src/state.rs @@ -1,4 +1,5 @@ use cosmwasm_std::Decimal; use cw_storage_plus::Map; -pub const COIN_PRICE: Map = Map::new("coin_price"); +pub const DEFAULT_COIN_PRICE: Map = Map::new("default_coin_price"); +pub const LIQUIDATION_COIN_PRICE: Map = Map::new("liquidation_coin_price"); diff --git a/contracts/mock-vault/Cargo.toml b/contracts/mock-vault/Cargo.toml index 0ebd8d9fe..502e45145 100644 --- a/contracts/mock-vault/Cargo.toml +++ b/contracts/mock-vault/Cargo.toml @@ -19,10 +19,11 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -cw-vault-standard = { workspace = true } -mars-rover = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +cw-vault-standard = { workspace = true } +mars-red-bank-types = { workspace = true } +mars-rover = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/mock-vault/src/deposit.rs b/contracts/mock-vault/src/deposit.rs index 6bfc7e68a..0891cbdff 100644 --- a/contracts/mock-vault/src/deposit.rs +++ b/contracts/mock-vault/src/deposit.rs @@ -2,6 +2,7 @@ use cosmwasm_std::{ to_binary, BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdResult, Uint128, WasmMsg, }; +use mars_red_bank_types::oracle::ActionKind; use mars_rover::msg::{execute::Action::Deposit, ExecuteMsg::UpdateCreditAccount}; use crate::{ @@ -26,8 +27,9 @@ pub fn deposit(deps: DepsMut, info: MessageInfo) -> Result>>()?; let oracle = ORACLE.load(deps.storage)?; - let total_underlying_value = oracle.query_total_value(&deps.querier, &coins)?; - let given_value = oracle.query_total_value(&deps.querier, &coins_in)?; + let total_underlying_value = + oracle.query_total_value(&deps.querier, &coins, ActionKind::Default)?; + let given_value = + oracle.query_total_value(&deps.querier, &coins_in, ActionKind::Default)?; total_supply.checked_multiply_ratio(given_value, total_underlying_value)? }; Ok(lp_tokens_estimate) diff --git a/packages/health-types/Cargo.toml b/packages/health-types/Cargo.toml index dd87f341f..e52233316 100644 --- a/packages/health-types/Cargo.toml +++ b/packages/health-types/Cargo.toml @@ -17,7 +17,8 @@ doctest = false backtraces = ["cosmwasm-std/backtraces"] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -mars-owner = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +mars-red-bank-types = { workspace = true } +mars-owner = { workspace = true } +thiserror = { workspace = true } diff --git a/packages/health-types/src/health.rs b/packages/health-types/src/health.rs index 7e173726c..35a9d9e65 100644 --- a/packages/health-types/src/health.rs +++ b/packages/health-types/src/health.rs @@ -84,3 +84,20 @@ pub enum HealthState { max_ltv_health_factor: Decimal, }, } + +impl fmt::Display for HealthState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + HealthState::Healthy => write!(f, "healthy"), + HealthState::Unhealthy { + max_ltv_health_factor, + } => { + write!( + f, + "unhealthy (max_ltv_health_factor: {:?}", + max_ltv_health_factor.to_string(), + ) + } + } + } +} diff --git a/packages/health-types/src/msg.rs b/packages/health-types/src/msg.rs index 869a93c0b..248b2fb55 100644 --- a/packages/health-types/src/msg.rs +++ b/packages/health-types/src/msg.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use mars_owner::{OwnerResponse, OwnerUpdate}; +use mars_red_bank_types::oracle::ActionKind; use crate::AccountKind; @@ -27,6 +28,7 @@ pub enum QueryMsg { HealthValues { account_id: String, kind: AccountKind, + action: ActionKind, }, /// Returns Healthy or Unhealthy state. Does not do health calculations if no debt. /// This is helpful in the cases like liquidation where we should not query the oracle if can help it. @@ -34,6 +36,7 @@ pub enum QueryMsg { HealthState { account_id: String, kind: AccountKind, + action: ActionKind, }, #[returns(ConfigResponse)] Config {}, diff --git a/packages/rover/src/adapters/health.rs b/packages/rover/src/adapters/health.rs index d3a6b89cf..305564f30 100644 --- a/packages/rover/src/adapters/health.rs +++ b/packages/rover/src/adapters/health.rs @@ -1,6 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, QuerierWrapper, StdResult}; -use mars_rover_health_types::{AccountKind, HealthValuesResponse, QueryMsg}; +use mars_red_bank_types::oracle::ActionKind; +use mars_rover_health_types::{AccountKind, HealthState, HealthValuesResponse, QueryMsg}; #[cw_serde] pub struct HealthContractBase(T); @@ -31,17 +32,36 @@ impl HealthContractUnchecked { } impl HealthContract { - pub fn query_health( + pub fn query_health_state( &self, querier: &QuerierWrapper, account_id: &str, kind: AccountKind, + action: ActionKind, + ) -> StdResult { + querier.query_wasm_smart( + self.address().to_string(), + &QueryMsg::HealthState { + account_id: account_id.to_string(), + kind, + action, + }, + ) + } + + pub fn query_health_values( + &self, + querier: &QuerierWrapper, + account_id: &str, + kind: AccountKind, + action: ActionKind, ) -> StdResult { querier.query_wasm_smart( self.address().to_string(), &QueryMsg::HealthValues { account_id: account_id.to_string(), kind, + action, }, ) } diff --git a/packages/rover/src/adapters/oracle.rs b/packages/rover/src/adapters/oracle.rs index dda445b99..795db0bac 100644 --- a/packages/rover/src/adapters/oracle.rs +++ b/packages/rover/src/adapters/oracle.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, Coin, QuerierWrapper, StdResult, Uint128}; -use mars_red_bank_types::oracle::{PriceResponse, QueryMsg}; +use mars_red_bank_types::oracle::{ActionKind, PriceResponse, QueryMsg}; use crate::error::ContractResult; @@ -33,28 +33,40 @@ impl OracleUnchecked { } impl Oracle { - pub fn query_price(&self, querier: &QuerierWrapper, denom: &str) -> StdResult { + pub fn query_price( + &self, + querier: &QuerierWrapper, + denom: &str, + pricing: ActionKind, + ) -> StdResult { querier.query_wasm_smart( self.address().to_string(), &QueryMsg::Price { denom: denom.to_string(), + kind: Some(pricing), }, ) } - pub fn query_value(&self, querier: &QuerierWrapper, coin: &Coin) -> ContractResult { - self.query_total_value(querier, &[coin.clone()]) + pub fn query_value( + &self, + querier: &QuerierWrapper, + coin: &Coin, + action: ActionKind, + ) -> ContractResult { + self.query_total_value(querier, &[coin.clone()], action) } pub fn query_total_value( &self, querier: &QuerierWrapper, coins: &[Coin], + action: ActionKind, ) -> ContractResult { Ok(coins .iter() .map(|coin| { - let res = self.query_price(querier, &coin.denom)?; + let res = self.query_price(querier, &coin.denom, action.clone())?; Ok(coin.amount.checked_mul_floor(res.price)?) }) .collect::>>()? diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index 7758c34ff..3af2bd9c1 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -62,9 +62,7 @@ impl RedBank { pub fn lend_msg(&self, coin: &Coin) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address().to_string(), - msg: to_binary(&red_bank::ExecuteMsg::Deposit { - on_behalf_of: None, - })?, + msg: to_binary(&red_bank::ExecuteMsg::Deposit {})?, funds: vec![coin.clone()], })) } diff --git a/packages/rover/src/adapters/vault/position.rs b/packages/rover/src/adapters/vault/position.rs index 498931666..6ad7c2218 100644 --- a/packages/rover/src/adapters/vault/position.rs +++ b/packages/rover/src/adapters/vault/position.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, QuerierWrapper, StdError, StdResult, Uint128}; +use mars_red_bank_types::oracle::ActionKind; use crate::adapters::{ oracle::Oracle, @@ -53,14 +54,20 @@ impl VaultPosition { &self, querier: &QuerierWrapper, oracle: &Oracle, + action: ActionKind, ) -> StdResult { Ok(VaultPositionValue { - vault_coin: self.vault_coin_value(querier, oracle)?, - base_coin: self.base_coin_value(querier, oracle)?, + vault_coin: self.vault_coin_value(querier, oracle, action.clone())?, + base_coin: self.base_coin_value(querier, oracle, action)?, }) } - fn vault_coin_value(&self, querier: &QuerierWrapper, oracle: &Oracle) -> StdResult { + fn vault_coin_value( + &self, + querier: &QuerierWrapper, + oracle: &Oracle, + action: ActionKind, + ) -> StdResult { let vault_info = self.vault.query_info(querier)?; let total_supply = self.vault.query_total_vault_coins_issued(querier)?; @@ -74,7 +81,7 @@ impl VaultPosition { let vault_coin_amount = self.amount.unlocked().checked_add(self.amount.locked())?; let amount_in_base_coin = self.vault.query_preview_redeem(querier, vault_coin_amount)?; - let price_res = oracle.query_price(querier, &vault_info.base_token)?; + let price_res = oracle.query_price(querier, &vault_info.base_token, action)?; let total_value = amount_in_base_coin .checked_mul_floor(price_res.price) .map_err(|_| StdError::generic_err("CheckedMultiplyFractionError"))?; @@ -85,9 +92,14 @@ impl VaultPosition { }) } - fn base_coin_value(&self, querier: &QuerierWrapper, oracle: &Oracle) -> StdResult { + fn base_coin_value( + &self, + querier: &QuerierWrapper, + oracle: &Oracle, + action: ActionKind, + ) -> StdResult { let vault_info = self.vault.query_info(querier)?; - let base_token_price = oracle.query_price(querier, &vault_info.base_token)?.price; + let base_token_price = oracle.query_price(querier, &vault_info.base_token, action)?.price; let total_value = self.amount.unlocking().positions().iter().try_fold( Uint128::zero(), diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 5b8b554a0..d28bee2b3 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; use mars_account_nft::nft_config::NftConfigUpdates; use mars_owner::OwnerUpdate; -use mars_rover_health_types::AccountKind; +use mars_rover_health_types::{AccountKind, HealthState}; use crate::{ adapters::vault::{Vault, VaultPositionType, VaultUnchecked}, @@ -231,7 +231,7 @@ pub enum CallbackMsg { #[serde(rename = "assert_max_ltv")] AssertMaxLTV { account_id: String, - prev_max_ltv_health_factor: Option, + prev_health_state: HealthState, }, /// Adds coin to a vault strategy EnterVault { diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index f7e6a530e..e45b5352b 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -838,21 +838,15 @@ "assert_max_ltv": { "type": "object", "required": [ - "account_id" + "account_id", + "prev_health_state" ], "properties": { "account_id": { "type": "string" }, - "prev_max_ltv_health_factor": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] + "prev_health_state": { + "$ref": "#/definitions/HealthState" } }, "additionalProperties": false @@ -1411,6 +1405,37 @@ "HealthContractBase_for_String": { "type": "string" }, + "HealthState": { + "oneOf": [ + { + "type": "string", + "enum": [ + "healthy" + ] + }, + { + "type": "object", + "required": [ + "unhealthy" + ], + "properties": { + "unhealthy": { + "type": "object", + "required": [ + "max_ltv_health_factor" + ], + "properties": { + "max_ltv_health_factor": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "LiquidateRequest_for_VaultBase_for_Addr": { "oneOf": [ { diff --git a/schemas/mars-mock-oracle/mars-mock-oracle.json b/schemas/mars-mock-oracle/mars-mock-oracle.json index 58f742def..bd429132e 100644 --- a/schemas/mars-mock-oracle/mars-mock-oracle.json +++ b/schemas/mars-mock-oracle/mars-mock-oracle.json @@ -19,11 +19,20 @@ }, "additionalProperties": false, "definitions": { + "ActionKind": { + "description": "Differentiator for the action (liquidate, withdraw, borrow etc.) being performed.", + "type": "string", + "enum": [ + "default", + "liquidation" + ] + }, "CoinPrice": { "type": "object", "required": [ "denom", - "price" + "price", + "pricing" ], "properties": { "denom": { @@ -31,6 +40,9 @@ }, "price": { "$ref": "#/definitions/Decimal" + }, + "pricing": { + "$ref": "#/definitions/ActionKind" } }, "additionalProperties": false @@ -59,11 +71,20 @@ } ], "definitions": { + "ActionKind": { + "description": "Differentiator for the action (liquidate, withdraw, borrow etc.) being performed.", + "type": "string", + "enum": [ + "default", + "liquidation" + ] + }, "CoinPrice": { "type": "object", "required": [ "denom", - "price" + "price", + "pricing" ], "properties": { "denom": { @@ -71,6 +92,9 @@ }, "price": { "$ref": "#/definitions/Decimal" + }, + "pricing": { + "$ref": "#/definitions/ActionKind" } }, "additionalProperties": false @@ -99,6 +123,16 @@ "properties": { "denom": { "type": "string" + }, + "kind": { + "anyOf": [ + { + "$ref": "#/definitions/ActionKind" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -106,7 +140,17 @@ }, "additionalProperties": false } - ] + ], + "definitions": { + "ActionKind": { + "description": "Differentiator for the action (liquidate, withdraw, borrow etc.) being performed.", + "type": "string", + "enum": [ + "default", + "liquidation" + ] + } + } }, "migrate": null, "sudo": null, diff --git a/schemas/mars-mock-red-bank/mars-mock-red-bank.json b/schemas/mars-mock-red-bank/mars-mock-red-bank.json index abe4975ce..d551a7d1d 100644 --- a/schemas/mars-mock-red-bank/mars-mock-red-bank.json +++ b/schemas/mars-mock-red-bank/mars-mock-red-bank.json @@ -25,19 +25,6 @@ }, "additionalProperties": false }, - { - "description": "Manages emergency owner state", - "type": "object", - "required": [ - "update_emergency_owner" - ], - "properties": { - "update_emergency_owner": { - "$ref": "#/definitions/OwnerUpdate" - } - }, - "additionalProperties": false - }, { "description": "Update contract config (only owner can call)", "type": "object", @@ -170,15 +157,6 @@ "properties": { "deposit": { "type": "object", - "properties": { - "on_behalf_of": { - "description": "Address that will receive the coins", - "type": [ - "string", - "null" - ] - } - }, "additionalProperties": false } }, @@ -360,16 +338,6 @@ "string", "null" ] - }, - "close_factor": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] } }, "additionalProperties": false @@ -381,31 +349,6 @@ "InitOrUpdateAssetParams": { "type": "object", "properties": { - "borrow_enabled": { - "description": "If false cannot borrow", - "type": [ - "boolean", - "null" - ] - }, - "deposit_cap": { - "description": "Deposit Cap defined in terms of the asset (Unlimited by default)", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "deposit_enabled": { - "description": "If false cannot deposit", - "type": [ - "boolean", - "null" - ] - }, "interest_rate_model": { "description": "Interest rate strategy to calculate borrow_rate and liquidity_rate", "anyOf": [ @@ -417,39 +360,6 @@ } ] }, - "liquidation_bonus": { - "description": "Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral from user in an amount equal to debt repayed + bonus)", - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "liquidation_threshold": { - "description": "uusd amount in debt position per uusd of asset collateral that if surpassed makes the user's position liquidatable.", - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "max_loan_to_value": { - "description": "Max uusd that can be borrowed per uusd of collateral when using the asset as collateral", - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, "reserve_factor": { "description": "Portion of the borrow rate that is kept as protocol rewards", "anyOf": [ @@ -552,6 +462,35 @@ "enum": [ "abolish_owner_role" ] + }, + { + "description": "A separate entity managed by Owner that can be used for granting specific emergency powers.", + "type": "object", + "required": [ + "set_emergency_owner" + ], + "properties": { + "set_emergency_owner": { + "type": "object", + "required": [ + "emergency_owner" + ], + "properties": { + "emergency_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove the entity in the Emergency Owner role", + "type": "string", + "enum": [ + "clear_emergency_owner" + ] } ] }, @@ -959,29 +898,13 @@ "title": "ConfigResponse", "type": "object", "required": [ - "address_provider", - "close_factor" + "address_provider" ], "properties": { "address_provider": { "description": "Address provider returns addresses for all protocol contracts", "type": "string" }, - "close_factor": { - "description": "Maximum percentage of outstanding debt that can be covered by a liquidator", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "emergency_owner": { - "description": "The contract's emergency owner", - "type": [ - "string", - "null" - ] - }, "owner": { "description": "The contract's owner", "type": [ @@ -989,13 +912,6 @@ "null" ] }, - "proposed_new_emergency_owner": { - "description": "The contract's proposed emergency owner", - "type": [ - "string", - "null" - ] - }, "proposed_new_owner": { "description": "The contract's proposed owner", "type": [ @@ -1004,41 +920,25 @@ ] } }, - "additionalProperties": false, - "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - } - } + "additionalProperties": false }, "market": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Market", "type": "object", "required": [ - "borrow_enabled", "borrow_index", "borrow_rate", "collateral_total_scaled", "debt_total_scaled", "denom", - "deposit_cap", - "deposit_enabled", "indexes_last_updated", "interest_rate_model", - "liquidation_bonus", - "liquidation_threshold", "liquidity_index", "liquidity_rate", - "max_loan_to_value", "reserve_factor" ], "properties": { - "borrow_enabled": { - "description": "If false cannot borrow", - "type": "boolean" - }, "borrow_index": { "description": "Borrow index (Used to compute borrow interest)", "allOf": [ @@ -1075,18 +975,6 @@ "description": "Denom of the asset", "type": "string" }, - "deposit_cap": { - "description": "Deposit Cap (defined in terms of the asset)", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "deposit_enabled": { - "description": "If false cannot deposit", - "type": "boolean" - }, "indexes_last_updated": { "description": "Timestamp (seconds) where indexes and where last updated", "type": "integer", @@ -1101,22 +989,6 @@ } ] }, - "liquidation_bonus": { - "description": "Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral from user in an amount equal to debt repayed + bonus)", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "liquidation_threshold": { - "description": "Base asset amount in debt position per \"base asset\" of asset collateral that if surpassed makes the user's position liquidatable.", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, "liquidity_index": { "description": "Liquidity index (Used to compute deposit interest)", "allOf": [ @@ -1133,14 +1005,6 @@ } ] }, - "max_loan_to_value": { - "description": "Max base asset that can be borrowed per \"base asset\" collateral when using the asset as collateral", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, "reserve_factor": { "description": "Portion of the borrow rate that is kept as protocol rewards", "allOf": [ @@ -1265,28 +1129,18 @@ "Market": { "type": "object", "required": [ - "borrow_enabled", "borrow_index", "borrow_rate", "collateral_total_scaled", "debt_total_scaled", "denom", - "deposit_cap", - "deposit_enabled", "indexes_last_updated", "interest_rate_model", - "liquidation_bonus", - "liquidation_threshold", "liquidity_index", "liquidity_rate", - "max_loan_to_value", "reserve_factor" ], "properties": { - "borrow_enabled": { - "description": "If false cannot borrow", - "type": "boolean" - }, "borrow_index": { "description": "Borrow index (Used to compute borrow interest)", "allOf": [ @@ -1323,18 +1177,6 @@ "description": "Denom of the asset", "type": "string" }, - "deposit_cap": { - "description": "Deposit Cap (defined in terms of the asset)", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "deposit_enabled": { - "description": "If false cannot deposit", - "type": "boolean" - }, "indexes_last_updated": { "description": "Timestamp (seconds) where indexes and where last updated", "type": "integer", @@ -1349,22 +1191,6 @@ } ] }, - "liquidation_bonus": { - "description": "Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral from user in an amount equal to debt repayed + bonus)", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "liquidation_threshold": { - "description": "Base asset amount in debt position per \"base asset\" of asset collateral that if surpassed makes the user's position liquidatable.", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, "liquidity_index": { "description": "Liquidity index (Used to compute deposit interest)", "allOf": [ @@ -1381,14 +1207,6 @@ } ] }, - "max_loan_to_value": { - "description": "Max base asset that can be borrowed per \"base asset\" collateral when using the asset as collateral", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, "reserve_factor": { "description": "Portion of the borrow rate that is kept as protocol rewards", "allOf": [ diff --git a/schemas/mars-rover-health-types/mars-rover-health-types.json b/schemas/mars-rover-health-types/mars-rover-health-types.json index 9e4b37fa7..19acb2622 100644 --- a/schemas/mars-rover-health-types/mars-rover-health-types.json +++ b/schemas/mars-rover-health-types/mars-rover-health-types.json @@ -151,12 +151,16 @@ "type": "object", "required": [ "account_id", + "action", "kind" ], "properties": { "account_id": { "type": "string" }, + "action": { + "$ref": "#/definitions/ActionKind" + }, "kind": { "$ref": "#/definitions/AccountKind" } @@ -177,12 +181,16 @@ "type": "object", "required": [ "account_id", + "action", "kind" ], "properties": { "account_id": { "type": "string" }, + "action": { + "$ref": "#/definitions/ActionKind" + }, "kind": { "$ref": "#/definitions/AccountKind" } @@ -213,6 +221,14 @@ "default", "high_levered_strategy" ] + }, + "ActionKind": { + "description": "Differentiator for the action (liquidate, withdraw, borrow etc.) being performed.", + "type": "string", + "enum": [ + "default", + "liquidation" + ] } } }, diff --git a/schemas/mars-rover-health/mars-rover-health.json b/schemas/mars-rover-health/mars-rover-health.json index fd7502044..9dcdb23ae 100644 --- a/schemas/mars-rover-health/mars-rover-health.json +++ b/schemas/mars-rover-health/mars-rover-health.json @@ -151,12 +151,16 @@ "type": "object", "required": [ "account_id", + "action", "kind" ], "properties": { "account_id": { "type": "string" }, + "action": { + "$ref": "#/definitions/ActionKind" + }, "kind": { "$ref": "#/definitions/AccountKind" } @@ -177,12 +181,16 @@ "type": "object", "required": [ "account_id", + "action", "kind" ], "properties": { "account_id": { "type": "string" }, + "action": { + "$ref": "#/definitions/ActionKind" + }, "kind": { "$ref": "#/definitions/AccountKind" } @@ -213,6 +221,14 @@ "default", "high_levered_strategy" ] + }, + "ActionKind": { + "description": "Differentiator for the action (liquidate, withdraw, borrow etc.) being performed.", + "type": "string", + "enum": [ + "default", + "liquidation" + ] } } }, diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 9027bd95e..67a9ad5b1 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -325,19 +325,13 @@ export class Deployer { init_asset: { denom, params: { - max_loan_to_value: '0.65', reserve_factor: '0.2', - liquidation_threshold: '0.7', - liquidation_bonus: '0.1', interest_rate_model: { optimal_utilization_rate: '0.1', base: '0.3', slope_1: '0.25', slope_2: '0.3', }, - deposit_cap: '1000000000', - deposit_enabled: true, - borrow_enabled: true, }, }, } diff --git a/scripts/deploy/base/storage.ts b/scripts/deploy/base/storage.ts index c380190c9..ef4e9cb08 100644 --- a/scripts/deploy/base/storage.ts +++ b/scripts/deploy/base/storage.ts @@ -9,7 +9,11 @@ export class Storage implements StorageItems { public codeIds: StorageItems['codeIds'] public actions: StorageItems['actions'] - constructor(private chainId: string, private label: string, items: StorageItems) { + constructor( + private chainId: string, + private label: string, + items: StorageItems, + ) { this.addresses = items.addresses this.codeIds = items.codeIds this.actions = items.actions diff --git a/scripts/health/DataFetcher.ts b/scripts/health/DataFetcher.ts index bf4e028f0..bfd6fd839 100644 --- a/scripts/health/DataFetcher.ts +++ b/scripts/health/DataFetcher.ts @@ -39,20 +39,26 @@ export class DataFetcher { params: await pQuery.assetParams({ denom }), })) const responses = await Promise.all(promises) - return responses.reduce((acc, curr) => { - acc[curr.denom] = curr.params - return acc - }, {} as DenomsData['params']) + return responses.reduce( + (acc, curr) => { + acc[curr.denom] = curr.params + return acc + }, + {} as DenomsData['params'], + ) } fetchPrices = async (denoms: string[]): Promise => { const oQuery = new MarsMockOracleQueryClient(await this.getClient(), this.oracleAddr) const promises = denoms.map(async (denom) => await oQuery.price({ denom })) const responses = await Promise.all(promises) - return responses.reduce((acc, curr) => { - acc[curr.denom] = curr.price - return acc - }, {} as DenomsData['prices']) + return responses.reduce( + (acc, curr) => { + acc[curr.denom] = curr.price + return acc + }, + {} as DenomsData['prices'], + ) } fetchDenomsData = async (positions: Positions): Promise => { diff --git a/scripts/health/example-react/index.html b/scripts/health/example-react/index.html index 8ecd8575b..bd2d2b2c8 100644 --- a/scripts/health/example-react/index.html +++ b/scripts/health/example-react/index.html @@ -1,4 +1,4 @@ - + diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js index 3cb48434d..8a1f60229 100644 --- a/scripts/health/pkg-node/index.js +++ b/scripts/health/pkg-node/index.js @@ -283,14 +283,14 @@ module.exports.__wbindgen_error_new = function (arg0, arg1) { return addHeapObject(ret) } -module.exports.__wbindgen_boolean_get = function (arg0) { - const v = getObject(arg0) - const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 +module.exports.__wbindgen_is_string = function (arg0) { + const ret = typeof getObject(arg0) === 'string' return ret } -module.exports.__wbindgen_is_string = function (arg0) { - const ret = typeof getObject(arg0) === 'string' +module.exports.__wbindgen_boolean_get = function (arg0) { + const v = getObject(arg0) + const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 return ret } diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index a1e5bc5a1076f46ce804587b2fd013290b436cee..52b7e5f44ed79384bdfe1043e293288e2ef0c601 100644 GIT binary patch delta 1953 zcmY*Zdr;I>6y`g-JY*3T2NX3&UeoDo%`L#Z)K8Dye~*=p>z@2lSMl(ce_B z%)tS1w-$@~eP<0*2;qEYBFU7>2aN|z+MO#f0A32dMmx6Q z_mCT#x-Ht7vMgNL)en?xmAKdd8LnZK(eizs+q+wwZ=;;aiMqep_VS!pR4A$dEF4C|u(U#*W~ zKH`mLzT=3uk7_9I^h~O@DkZ!9cwog$e}4ULxV%(O4+elAYZm#>#Cvml*XE$o#A z4X}xOR?N_PU;EMiV3T1tBaw*9geQ<9W^Kkb8QEY3`(R@=3XV&UY)clddYMS|G1jgz znMkbo4xh+p&Tec)oxWq_bjQTA+b~U?7`OvIBqt8dMy^WE?j$enB=71V@97}#Z71(V z85LT@?i`$P#bl9i5OwgXGN?Xwwx36pLCigbIrfv3$I+88nMvASv8n)8jlSJ;H9EdB z65dBK+fH733;`q$D(yJZAftZ~mdQ^#RalB~NkgqEVooV8xp_1@jUageMuU8*Y*Z!s z+{T=uM8oM5aKCqiieF?|>rSW|y8h+$zgUcc`6_7hG)4){}sguNYbs|rILFiX{TPJX%V4R|13LcN}C8cl94X*(qFW$B|S)J0Jn z-Pt>1D3nc%A_MJpW``r`EmkxbzGCYbI;`>Za0+(nCl3qyi_n#_yh!TBCPzz7P86Bx z7h;2=DS*w0p+>YY<2Z`)DkWW~sDwm#G*zO#Ib5tCM~N=Jwp_VzH;4WGKS=1$evTzS z-%|%vPmWf;3cImgvc*=%l9%D6%I)0hRu$?U^lYxGS%#z=TFYgwakQ?yDLipfcDt}c zD)X3X$IrpsU!V?gzzb^EpP5v5na5sqrX}#5>;6L`4(SWfz1v z#6bm7S);}>0R|n7Wg&`+w?;G`sR9)|(O@KaR?K|BuH~Pv-|K$e(_dH5+`Hj>^@eZ8 zE9Q&VeiOWsl9nYUc}(z3S|-BK4=j_cY(9mtZ_r#^rWrnD2ov#!G7Nu!`*?(VsK9yH zbkLP%bA7};DEBq2K@CQE6 zmCq15R^e%(8cq)#PDKuJEA%F))RA}0tc+cp2>Z4*o{9Ll$ACBM)+Y_gqPPzuRi2Hs z4yDW#k!K}PRf^aVUrqii%x3C9ZNp)Pb%j}E_s`d<+Cv9Eg-uuoC4-y}Ho<8W6@%}G zVw*TQJ)4>}r1-f~7R>4dtPxvhA5w;mpEC<};_RHUP^RO&iF)ne^u^~@+vAs*ROw|) z#=$B6T2gCJ{heL4hU#oAadkaa*~PFmDGhxVrw(C}b`zC5m?h0DCfi3fxu>C2M5lH1 zqf(n{$}ARbXy-*$Ho-S{0F|eRlH7Pt?L05%Xy(1kB2t`@4LpBmBGs)?DbeD}9)F0w zzXah?Rq>GM_+d!WorHlGD0uk zw7{`oDoT;ZJ7RSpWjf@r-k94~{iiG$g@Zo0%DYZR98Z{n9NuLzddqc_un-N9ADD=2 zd2)4s?a$xZSts}MsN$d#-MCmT{>=jk?Fhh0ujfv?(MzR->| z%CvD$hq}b&@{t3ddp4b)4A=Q1GT;xDH1S7VR+fLnumg3xl3~B`Gsq91{W)OXtA)^AtR zyW3DoRSvl)52w5_ST5d;D%UKs-Lsgn2VZET<7yUN-n?cn}wKB1rm5g1zPe=3dx?8*xjE;L{W^aj-gGx}sq`M+@!W_)h4677FL_s=l9}huNcp z`d0*;hfM87w{(13FN&aI@bq5Po^R<(+4M@s`+Pzs)@GrvEVj~S;1M6!k7D^!D>ajG zpOD(Rw;o?QL~>m}nyZgFw_Tmo%H8fhjT+tb$Sy@&xT#Z%2Jw}H$jId&YR-46PfWfh z3UZ5f7QB;lSIaTEqbExg_2C(+g#Px=3J35r3dgM9p<%adrxtqKRMa!8NZ_r z574YE)_p>T_AOcopSU?(;nz$jLS}S8yf?t)f6~S|*f7#); z2hXHJBcIg&HmK6LD>ejVGxE65HNoiOyGOPH9?GWC`Do%%NeW4M<=B`sYBF=ru>|U{ zaPM(!4p`_AwZba0a?&WD`Fw%VCC77KFXWro=S1KpEJ@>zTN4S^Pb%C1OMeZ4-9tXUYFn71Ri{D@#kFl%(_VWV0v{#eqEY{Olk;`j0qy)O^w1 zA>hIG;aYg{8IR+^H#QsHd0nFn=(zT_Os;)W=7EPisw;-1nf{_rAUwsqS8&0ftVS^~ z204CYGmDB*ST#T{R7A|i2`{#OKE|-Jk5TOKMEg9X0}ZTTK9X6`JZuvw3((@D^5a$@ z49H{CRw9k8W>K^fmpm{{Ec_C);QM%_dia%^NKLpVS{S_8NEgQB5fsnMRU!zWg zR1uVcER2&=OLaWWUDdXCV(3X3xUUu!!nx=;SqK z*K?3h9cH#57cD3i(~NjW>vv>C!6eejqMdJZj zH2R*IZE!v}66uHWp@V$w2tr71R9mIoHfh%$#R~bhjiOcHs)jmpMb!yh_42X1jUagn zDbb&%*jcd^v1k<+ttjw;UA$Y5PZ=4EYgT2GRt*$FZU+w-i5?oJfHQ zWqH!~*B6m@Z#Vg~xs@xS&INy1FJYt$Zc$+b^Oc1Ge{Yq(pDVRDUqU?#l_~ao1ciDi zYM?LMoJfP%{v?W~o$jnYiK4`#L^`SQ@o@`xi;>HR{t8%K5}gx8V@cktHum0F8vnoa zDH7|GsoI$tE}F;DN)M0HGO6B&4V^68#ZD$)mOhDm6AtZFqpq3$+^x>!g3W48hzp)o zvwkd1N+fLK((mg^HK;oMT(IMUgc=vT)>Ak~?H#Q4UL`u?y}g3X*n0ns&QCG>LD o6daY%+Xb^!34dXlOwR{X;xLi@IX&$s9rlfx2BJljnTdt{3(uOGZU6uP delta 1845 zcmY*Zdr;I>6yEPF%VXJiC@VxF%S#a@b9`(jgedYDU)iWLS-xj9U!YA{**K7{vMkT$ z2i;UeQouJU7rl}>so*0+MI{G~Mj;d*peTfhjdpignCYK$&Ufy2?)mQho%^e<)&5ee z&3UP4;j3GyNlxC7oUB^tki0<&z+t5Y!<9SGaTnUIA=dz2;&cHsAL0R8aUZvF1s3&l zdPMiAgU(VV{X^~4LRIvFPSZJhOux|+dO%&&P3P%%YNIoBfm-PlHPb`tr2F)gs_7YB zRDN}d8`p)M=tLLVv58WsQ^wtRf#=vw7PUfG=^EA0b*h!2;0DzxQCdIO zW?aS{T*4#VQ-1PzIP)6#fQMs9?=r1_PT!L?i%q98G zlkp{6-0nM*%F}u5TMeM9^xZ@Kq_gm0gVy`vIWKwlG2o=+y_e~1NC%8@ykxX1pfgchadP?+*> zSwH2;0Vk#Kpr<`kf3UzF6(0(-MYHU9Mya!ms!KzZ4W(9gc4>qSkF~|4x$&pJ`a73f zY@bM@BoziJQI+9jvhc*p#5eiS)?p5C;e3EQ7Un%zF3$@j`MhtC1l5lCip%-a_`d5!+LG=q&sIMu<@x;Xp`W zlKVDWYC;AqtlflLu!zf>p_OCJZf-#)<(k>2sq$^tikW82cCd>up;v^+fE#4dxlH^a zJHosKo^~>Xe>q=oWUc3PaH*Zd4&hhZeEyDh6rL!lWN5#0|u2*xY)=uu1jsX0QK5 zj3}tbfyvAqM59DPAVq<>2U8fk8$|x>RuGL5}w+pVX;PX56;*w6Z*b&#kxMA4|Zips9{r$h+OXFmdcxhbD?6WIji(*MXY6rK&o^xC)`JaT2vl6QN6dktK_G8oI$j7m{ zSjK*Am5lXu*1UHg?QiQQ9_$^t#5Tvu#_Ocfo2*%{KGF|8qyTIE>Hl}lIVPE|Vrv|A ahtb$EBJVSL?kxkBWud|Hn;x|g^ZyT57>n5e diff --git a/scripts/package.json b/scripts/package.json index b8a2e870d..a5482fc29 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -33,15 +33,15 @@ "wasm-pack": "^0.12.1" }, "devDependencies": { - "@babel/preset-env": "^7.22.5", + "@babel/preset-env": "^7.22.7", "@babel/preset-typescript": "^7.22.5", - "@types/jest": "^29.5.2", - "@typescript-eslint/eslint-plugin": "^5.60.1", - "@typescript-eslint/parser": "^5.60.1", - "eslint": "^8.42.0", + "@types/jest": "^29.5.3", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.44.0", "eslint-config-prettier": "^8.8.0", - "jest": "^29.5.0", - "prettier": "^2.8.8", - "typescript": "^5.1.5" + "jest": "^29.6.1", + "prettier": "^3.0.0", + "typescript": "^5.1.6" } } diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 5c1e5e59e..ce1e29bc5 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -27,6 +27,7 @@ import { OwnerUpdate, CallbackMsg, Addr, + HealthState, LiquidateRequestForVaultBaseForAddr, ChangeExpected, Coin, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index b35090f08..3d079498b 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -28,6 +28,7 @@ import { OwnerUpdate, CallbackMsg, Addr, + HealthState, LiquidateRequestForVaultBaseForAddr, ChangeExpected, Coin, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 6f6c2161c..fb0106416 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -28,6 +28,7 @@ import { OwnerUpdate, CallbackMsg, Addr, + HealthState, LiquidateRequestForVaultBaseForAddr, ChangeExpected, Coin, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index ae42439ee..f5f4f4978 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -209,7 +209,7 @@ export type CallbackMsg = | { assert_max_ltv: { account_id: string - prev_max_ltv_health_factor?: Decimal | null + prev_health_state: HealthState } } | { @@ -306,6 +306,13 @@ export type CallbackMsg = remove_reentrancy_guard: {} } export type Addr = string +export type HealthState = + | 'healthy' + | { + unhealthy: { + max_ltv_health_factor: Decimal + } + } export type LiquidateRequestForVaultBaseForAddr = | { deposit: string diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts index 5b0c1636d..551a1f36d 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts @@ -9,6 +9,7 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/co import { Coin, StdFee } from '@cosmjs/amino' import { Decimal, + ActionKind, InstantiateMsg, CoinPrice, ExecuteMsg, @@ -17,7 +18,7 @@ import { } from './MarsMockOracle.types' export interface MarsMockOracleReadOnlyInterface { contractAddress: string - price: ({ denom }: { denom: string }) => Promise + price: ({ denom, kind }: { denom: string; kind?: ActionKind }) => Promise } export class MarsMockOracleQueryClient implements MarsMockOracleReadOnlyInterface { client: CosmWasmClient @@ -29,10 +30,11 @@ export class MarsMockOracleQueryClient implements MarsMockOracleReadOnlyInterfac this.price = this.price.bind(this) } - price = async ({ denom }: { denom: string }): Promise => { + price = async ({ denom, kind }: { denom: string; kind?: ActionKind }): Promise => { return this.client.queryContractSmart(this.contractAddress, { price: { denom, + kind, }, }) } @@ -44,9 +46,11 @@ export interface MarsMockOracleInterface extends MarsMockOracleReadOnlyInterface { denom, price, + pricing, }: { denom: string price: Decimal + pricing: ActionKind }, fee?: number | StdFee | 'auto', memo?: string, @@ -73,9 +77,11 @@ export class MarsMockOracleClient { denom, price, + pricing, }: { denom: string price: Decimal + pricing: ActionKind }, fee: number | StdFee | 'auto' = 'auto', memo?: string, @@ -88,6 +94,7 @@ export class MarsMockOracleClient change_price: { denom, price, + pricing, }, }, fee, diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts index cc61324fd..24af8211f 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts @@ -11,6 +11,7 @@ import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { Decimal, + ActionKind, InstantiateMsg, CoinPrice, ExecuteMsg, @@ -24,9 +25,11 @@ export interface MarsMockOracleMessage { { denom, price, + pricing, }: { denom: string price: Decimal + pricing: ActionKind }, _funds?: Coin[], ) => MsgExecuteContractEncodeObject @@ -45,9 +48,11 @@ export class MarsMockOracleMessageComposer implements MarsMockOracleMessage { { denom, price, + pricing, }: { denom: string price: Decimal + pricing: ActionKind }, _funds?: Coin[], ): MsgExecuteContractEncodeObject => { @@ -61,6 +66,7 @@ export class MarsMockOracleMessageComposer implements MarsMockOracleMessage { change_price: { denom, price, + pricing, }, }), ), diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts index 88fb79839..741950b0e 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts @@ -10,6 +10,7 @@ import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee, Coin } from '@cosmjs/amino' import { Decimal, + ActionKind, InstantiateMsg, CoinPrice, ExecuteMsg, @@ -41,6 +42,7 @@ export interface MarsMockOraclePriceQuery extends MarsMockOracleReactQuery { args: { denom: string + kind?: ActionKind } } export function useMarsMockOraclePriceQuery({ @@ -54,6 +56,7 @@ export function useMarsMockOraclePriceQuery({ client ? client.price({ denom: args.denom, + kind: args.kind, }) : Promise.reject(new Error('Invalid client')), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, @@ -64,6 +67,7 @@ export interface MarsMockOracleChangePriceMutation { msg: { denom: string price: Decimal + pricing: ActionKind } args?: { fee?: number | StdFee | 'auto' diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts index 238619450..372d9e005 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts @@ -6,12 +6,14 @@ */ export type Decimal = string +export type ActionKind = 'default' | 'liquidation' export interface InstantiateMsg { prices: CoinPrice[] } export interface CoinPrice { denom: string price: Decimal + pricing: ActionKind } export type ExecuteMsg = { change_price: CoinPrice @@ -19,6 +21,7 @@ export type ExecuteMsg = { export type QueryMsg = { price: { denom: string + kind?: ActionKind | null } } export interface PriceResponse { diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index d569a8ff0..a322d9a07 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -315,12 +315,6 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa memo?: string, _funds?: Coin[], ) => Promise - updateEmergencyOwner: ( - ownerUpdate: OwnerUpdate, - fee?: number | StdFee | 'auto', - memo?: string, - _funds?: Coin[], - ) => Promise updateConfig: ( { config, @@ -370,11 +364,6 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa _funds?: Coin[], ) => Promise deposit: ( - { - onBehalfOf, - }: { - onBehalfOf?: string - }, fee?: number | StdFee | 'auto', memo?: string, _funds?: Coin[], @@ -458,7 +447,6 @@ export class MarsMockRedBankClient this.sender = sender this.contractAddress = contractAddress this.updateOwner = this.updateOwner.bind(this) - this.updateEmergencyOwner = this.updateEmergencyOwner.bind(this) this.updateConfig = this.updateConfig.bind(this) this.initAsset = this.initAsset.bind(this) this.updateAsset = this.updateAsset.bind(this) @@ -488,23 +476,6 @@ export class MarsMockRedBankClient _funds, ) } - updateEmergencyOwner = async ( - ownerUpdate: OwnerUpdate, - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - _funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - update_emergency_owner: ownerUpdate, - }, - fee, - memo, - _funds, - ) - } updateConfig = async ( { config, @@ -610,11 +581,6 @@ export class MarsMockRedBankClient ) } deposit = async ( - { - onBehalfOf, - }: { - onBehalfOf?: string - }, fee: number | StdFee | 'auto' = 'auto', memo?: string, _funds?: Coin[], @@ -623,9 +589,7 @@ export class MarsMockRedBankClient this.sender, this.contractAddress, { - deposit: { - on_behalf_of: onBehalfOf, - }, + deposit: {}, }, fee, memo, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts index 283884871..d62238c64 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts @@ -35,10 +35,6 @@ export interface MarsMockRedBankMessage { contractAddress: string sender: string updateOwner: (ownerUpdate: OwnerUpdate, _funds?: Coin[]) => MsgExecuteContractEncodeObject - updateEmergencyOwner: ( - ownerUpdate: OwnerUpdate, - _funds?: Coin[], - ) => MsgExecuteContractEncodeObject updateConfig: ( { config, @@ -79,14 +75,7 @@ export interface MarsMockRedBankMessage { }, _funds?: Coin[], ) => MsgExecuteContractEncodeObject - deposit: ( - { - onBehalfOf, - }: { - onBehalfOf?: string - }, - _funds?: Coin[], - ) => MsgExecuteContractEncodeObject + deposit: (_funds?: Coin[]) => MsgExecuteContractEncodeObject withdraw: ( { amount, @@ -150,7 +139,6 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { this.sender = sender this.contractAddress = contractAddress this.updateOwner = this.updateOwner.bind(this) - this.updateEmergencyOwner = this.updateEmergencyOwner.bind(this) this.updateConfig = this.updateConfig.bind(this) this.initAsset = this.initAsset.bind(this) this.updateAsset = this.updateAsset.bind(this) @@ -178,24 +166,6 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }), } } - updateEmergencyOwner = ( - ownerUpdate: OwnerUpdate, - _funds?: Coin[], - ): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - update_emergency_owner: ownerUpdate, - }), - ), - funds: _funds, - }), - } - } updateConfig = ( { config, @@ -304,14 +274,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }), } } - deposit = ( - { - onBehalfOf, - }: { - onBehalfOf?: string - }, - _funds?: Coin[], - ): MsgExecuteContractEncodeObject => { + deposit = (_funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -319,9 +282,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - deposit: { - on_behalf_of: onBehalfOf, - }, + deposit: {}, }), ), funds: _funds, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index d0f614991..d51b8c185 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -574,9 +574,6 @@ export function useMarsMockRedBankWithdrawMutation( } export interface MarsMockRedBankDepositMutation { client: MarsMockRedBankClient - msg: { - onBehalfOf?: string - } args?: { fee?: number | StdFee | 'auto' memo?: string @@ -590,7 +587,7 @@ export function useMarsMockRedBankDepositMutation( >, ) { return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.deposit(msg, fee, memo, funds), + ({ client, args: { fee, memo, funds } = {} }) => client.deposit(fee, memo, funds), options, ) } @@ -692,27 +689,6 @@ export function useMarsMockRedBankUpdateConfigMutation( options, ) } -export interface MarsMockRedBankUpdateEmergencyOwnerMutation { - client: MarsMockRedBankClient - msg: OwnerUpdate - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsMockRedBankUpdateEmergencyOwnerMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => - client.updateEmergencyOwner(msg, fee, memo, funds), - options, - ) -} export interface MarsMockRedBankUpdateOwnerMutation { client: MarsMockRedBankClient msg: OwnerUpdate diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index 7b992d5b3..bd6fbf61f 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -12,9 +12,6 @@ export type ExecuteMsg = | { update_owner: OwnerUpdate } - | { - update_emergency_owner: OwnerUpdate - } | { update_config: { config: CreateOrUpdateConfig @@ -40,9 +37,7 @@ export type ExecuteMsg = } } | { - deposit: { - on_behalf_of?: string | null - } + deposit: {} } | { withdraw: { @@ -85,20 +80,19 @@ export type OwnerUpdate = | 'clear_proposed' | 'accept_proposed' | 'abolish_owner_role' + | { + set_emergency_owner: { + emergency_owner: string + } + } + | 'clear_emergency_owner' export type Decimal = string export type Uint128 = string export interface CreateOrUpdateConfig { address_provider?: string | null - close_factor?: Decimal | null } export interface InitOrUpdateAssetParams { - borrow_enabled?: boolean | null - deposit_cap?: Uint128 | null - deposit_enabled?: boolean | null interest_rate_model?: InterestRateModel | null - liquidation_bonus?: Decimal | null - liquidation_threshold?: Decimal | null - max_loan_to_value?: Decimal | null reserve_factor?: Decimal | null } export interface InterestRateModel { @@ -192,28 +186,19 @@ export type QueryMsg = } export interface ConfigResponse { address_provider: string - close_factor: Decimal - emergency_owner?: string | null owner?: string | null - proposed_new_emergency_owner?: string | null proposed_new_owner?: string | null } export interface Market { - borrow_enabled: boolean borrow_index: Decimal borrow_rate: Decimal collateral_total_scaled: Uint128 debt_total_scaled: Uint128 denom: string - deposit_cap: Uint128 - deposit_enabled: boolean indexes_last_updated: number interest_rate_model: InterestRateModel - liquidation_bonus: Decimal - liquidation_threshold: Decimal liquidity_index: Decimal liquidity_rate: Decimal - max_loan_to_value: Decimal reserve_factor: Decimal } export type ArrayOfMarket = Market[] diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts index 84e1b32c4..5e977309a 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts @@ -12,6 +12,7 @@ import { ExecuteMsg, OwnerUpdate, QueryMsg, + ActionKind, AccountKind, ConfigResponse, OwnerResponse, @@ -24,16 +25,20 @@ export interface MarsRoverHealthTypesReadOnlyInterface { contractAddress: string healthValues: ({ accountId, + action, kind, }: { accountId: string + action: ActionKind kind: AccountKind }) => Promise healthState: ({ accountId, + action, kind, }: { accountId: string + action: ActionKind kind: AccountKind }) => Promise config: () => Promise @@ -52,28 +57,34 @@ export class MarsRoverHealthTypesQueryClient implements MarsRoverHealthTypesRead healthValues = async ({ accountId, + action, kind, }: { accountId: string + action: ActionKind kind: AccountKind }): Promise => { return this.client.queryContractSmart(this.contractAddress, { health_values: { account_id: accountId, + action, kind, }, }) } healthState = async ({ accountId, + action, kind, }: { accountId: string + action: ActionKind kind: AccountKind }): Promise => { return this.client.queryContractSmart(this.contractAddress, { health_state: { account_id: accountId, + action, kind, }, }) diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts index b1e527f2c..3ad63f380 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts @@ -14,6 +14,7 @@ import { ExecuteMsg, OwnerUpdate, QueryMsg, + ActionKind, AccountKind, ConfigResponse, OwnerResponse, diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts index 30696bad7..fd11d4b6b 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts @@ -13,6 +13,7 @@ import { ExecuteMsg, OwnerUpdate, QueryMsg, + ActionKind, AccountKind, ConfigResponse, OwnerResponse, @@ -79,6 +80,7 @@ export interface MarsRoverHealthTypesHealthStateQuery extends MarsRoverHealthTypesReactQuery { args: { accountId: string + action: ActionKind kind: AccountKind } } @@ -93,6 +95,7 @@ export function useMarsRoverHealthTypesHealthStateQuery({ client ? client.healthState({ accountId: args.accountId, + action: args.action, kind: args.kind, }) : Promise.reject(new Error('Invalid client')), @@ -103,6 +106,7 @@ export interface MarsRoverHealthTypesHealthValuesQuery extends MarsRoverHealthTypesReactQuery { args: { accountId: string + action: ActionKind kind: AccountKind } } @@ -117,6 +121,7 @@ export function useMarsRoverHealthTypesHealthValuesQuery Promise healthState: ({ accountId, + action, kind, }: { accountId: string + action: ActionKind kind: AccountKind }) => Promise config: () => Promise @@ -52,28 +57,34 @@ export class MarsRoverHealthQueryClient implements MarsRoverHealthReadOnlyInterf healthValues = async ({ accountId, + action, kind, }: { accountId: string + action: ActionKind kind: AccountKind }): Promise => { return this.client.queryContractSmart(this.contractAddress, { health_values: { account_id: accountId, + action, kind, }, }) } healthState = async ({ accountId, + action, kind, }: { accountId: string + action: ActionKind kind: AccountKind }): Promise => { return this.client.queryContractSmart(this.contractAddress, { health_state: { account_id: accountId, + action, kind, }, }) diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts index 8999a8bef..82f57f9ba 100644 --- a/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts @@ -14,6 +14,7 @@ import { ExecuteMsg, OwnerUpdate, QueryMsg, + ActionKind, AccountKind, ConfigResponse, OwnerResponse, diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts index d14327817..7dad1a045 100644 --- a/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts @@ -13,6 +13,7 @@ import { ExecuteMsg, OwnerUpdate, QueryMsg, + ActionKind, AccountKind, ConfigResponse, OwnerResponse, @@ -66,6 +67,7 @@ export interface MarsRoverHealthHealthStateQuery extends MarsRoverHealthReactQuery { args: { accountId: string + action: ActionKind kind: AccountKind } } @@ -80,6 +82,7 @@ export function useMarsRoverHealthHealthStateQuery({ client ? client.healthState({ accountId: args.accountId, + action: args.action, kind: args.kind, }) : Promise.reject(new Error('Invalid client')), @@ -90,6 +93,7 @@ export interface MarsRoverHealthHealthValuesQuery extends MarsRoverHealthReactQuery { args: { accountId: string + action: ActionKind kind: AccountKind } } @@ -104,6 +108,7 @@ export function useMarsRoverHealthHealthValuesQuery= 2.1.2 < 3" -ignore@^5.2.0: +ignore@^5.2.0, ignore@^5.2.4: version "5.2.4" - resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== import-fresh@^3.0.0, import-fresh@^3.2.1: @@ -3636,75 +3746,75 @@ jest-changed-files@^29.5.0: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz" - integrity sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA== +jest-circus@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.1.tgz#861dab37e71a89907d1c0fabc54a0019738ed824" + integrity sha512-tPbYLEiBU4MYAL2XoZme/bgfUeotpDBd81lgHLCbDZZFaGmECk0b+/xejPFtmiBP87GgP/y4jplcRpbH+fgCzQ== dependencies: - "@jest/environment" "^29.5.0" - "@jest/expect" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/environment" "^29.6.1" + "@jest/expect" "^29.6.1" + "@jest/test-result" "^29.6.1" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^29.5.0" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-runtime "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" + jest-each "^29.6.1" + jest-matcher-utils "^29.6.1" + jest-message-util "^29.6.1" + jest-runtime "^29.6.1" + jest-snapshot "^29.6.1" + jest-util "^29.6.1" p-limit "^3.1.0" - pretty-format "^29.5.0" + pretty-format "^29.6.1" pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz" - integrity sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw== +jest-cli@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.1.tgz#99d9afa7449538221c71f358f0fdd3e9c6e89f72" + integrity sha512-607dSgTA4ODIN6go9w6xY3EYkyPFGicx51a69H7yfvt7lN53xNswEVLovq+E77VsTRi5fWprLH0yl4DJgE8Ing== dependencies: - "@jest/core" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/core" "^29.6.1" + "@jest/test-result" "^29.6.1" + "@jest/types" "^29.6.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-config "^29.6.1" + jest-util "^29.6.1" + jest-validate "^29.6.1" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz" - integrity sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA== +jest-config@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.1.tgz#d785344509065d53a238224c6cdc0ed8e2f2f0dd" + integrity sha512-XdjYV2fy2xYixUiV2Wc54t3Z4oxYPAELUzWnV6+mcbq0rh742X2p52pii5A3oeRzYjLnQxCsZmp0qpI6klE2cQ== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.5.0" - "@jest/types" "^29.5.0" - babel-jest "^29.5.0" + "@jest/test-sequencer" "^29.6.1" + "@jest/types" "^29.6.1" + babel-jest "^29.6.1" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.5.0" - jest-environment-node "^29.5.0" + jest-circus "^29.6.1" + jest-environment-node "^29.6.1" jest-get-type "^29.4.3" jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-runner "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-resolve "^29.6.1" + jest-runner "^29.6.1" + jest-util "^29.6.1" + jest-validate "^29.6.1" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.5.0" + pretty-format "^29.6.1" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -3718,6 +3828,16 @@ jest-diff@^29.5.0: jest-get-type "^29.4.3" pretty-format "^29.5.0" +jest-diff@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.1.tgz#13df6db0a89ee6ad93c747c75c85c70ba941e545" + integrity sha512-FsNCvinvl8oVxpNLttNQX7FAq7vR+gMDGj90tiP7siWw1UdakWUGqrylpsYrpvj908IYckm5Y0Q7azNAozU1Kg== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.4.3" + jest-get-type "^29.4.3" + pretty-format "^29.6.1" + jest-docblock@^29.4.3: version "29.4.3" resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz" @@ -3725,28 +3845,28 @@ jest-docblock@^29.4.3: dependencies: detect-newline "^3.0.0" -jest-each@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz" - integrity sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA== +jest-each@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.1.tgz#975058e5b8f55c6780beab8b6ab214921815c89c" + integrity sha512-n5eoj5eiTHpKQCAVcNTT7DRqeUmJ01hsAL0Q1SMiBHcBcvTKDELixQOGMCpqhbIuTcfC4kMfSnpmDqRgRJcLNQ== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" chalk "^4.0.0" jest-get-type "^29.4.3" - jest-util "^29.5.0" - pretty-format "^29.5.0" + jest-util "^29.6.1" + pretty-format "^29.6.1" -jest-environment-node@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz" - integrity sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw== +jest-environment-node@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.1.tgz#08a122dece39e58bc388da815a2166c58b4abec6" + integrity sha512-ZNIfAiE+foBog24W+2caIldl4Irh8Lx1PUhg/GZ0odM1d/h2qORAsejiFc7zb+SEmYPn1yDZzEDSU5PmDkmVLQ== dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/environment" "^29.6.1" + "@jest/fake-timers" "^29.6.1" + "@jest/types" "^29.6.1" "@types/node" "*" - jest-mock "^29.5.0" - jest-util "^29.5.0" + jest-mock "^29.6.1" + jest-util "^29.6.1" jest-get-type@^29.4.3: version "29.4.3" @@ -3772,32 +3892,32 @@ jest-haste-map@^28.1.3: optionalDependencies: fsevents "^2.3.2" -jest-haste-map@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz" - integrity sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA== +jest-haste-map@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.1.tgz#62655c7a1c1b349a3206441330fb2dbdb4b63803" + integrity sha512-0m7f9PZXxOCk1gRACiVgX85knUKPKLPg4oRCjLoqIm9brTHXaorMA0JpmtmVkQiT8nmXyIVoZd/nnH1cfC33ig== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" jest-regex-util "^29.4.3" - jest-util "^29.5.0" - jest-worker "^29.5.0" + jest-util "^29.6.1" + jest-worker "^29.6.1" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz" - integrity sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow== +jest-leak-detector@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.1.tgz#66a902c81318e66e694df7d096a95466cb962f8e" + integrity sha512-OrxMNyZirpOEwkF3UHnIkAiZbtkBWiye+hhBweCHkVbCgyEy71Mwbb5zgeTNYWJBi1qgDVfPC1IwO9dVEeTLwQ== dependencies: jest-get-type "^29.4.3" - pretty-format "^29.5.0" + pretty-format "^29.6.1" jest-matcher-utils@^29.5.0: version "29.5.0" @@ -3809,6 +3929,16 @@ jest-matcher-utils@^29.5.0: jest-get-type "^29.4.3" pretty-format "^29.5.0" +jest-matcher-utils@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz#6c60075d84655d6300c5d5128f46531848160b53" + integrity sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA== + dependencies: + chalk "^4.0.0" + jest-diff "^29.6.1" + jest-get-type "^29.4.3" + pretty-format "^29.6.1" + jest-message-util@^29.5.0: version "29.5.0" resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz" @@ -3824,14 +3954,29 @@ jest-message-util@^29.5.0: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz" - integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw== +jest-message-util@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.1.tgz#d0b21d87f117e1b9e165e24f245befd2ff34ff8d" + integrity sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ== dependencies: - "@jest/types" "^29.5.0" + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.6.1" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.1.tgz#049ee26aea8cbf54c764af649070910607316517" + integrity sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw== + dependencies: + "@jest/types" "^29.6.1" "@types/node" "*" - jest-util "^29.5.0" + jest-util "^29.6.1" jest-pnp-resolver@^1.2.2: version "1.2.3" @@ -3848,112 +3993,110 @@ jest-regex-util@^29.4.3: resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz" integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== -jest-resolve-dependencies@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz" - integrity sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg== +jest-resolve-dependencies@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.1.tgz#b85b06670f987a62515bbf625d54a499e3d708f5" + integrity sha512-BbFvxLXtcldaFOhNMXmHRWx1nXQO5LoXiKSGQcA1LxxirYceZT6ch8KTE1bK3X31TNG/JbkI7OkS/ABexVahiw== dependencies: jest-regex-util "^29.4.3" - jest-snapshot "^29.5.0" + jest-snapshot "^29.6.1" -jest-resolve@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz" - integrity sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w== +jest-resolve@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.1.tgz#4c3324b993a85e300add2f8609f51b80ddea39ee" + integrity sha512-AeRkyS8g37UyJiP9w3mmI/VXU/q8l/IH52vj/cDAyScDcemRbSBhfX/NMYIGilQgSVwsjxrCHf3XJu4f+lxCMg== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" + jest-haste-map "^29.6.1" jest-pnp-resolver "^1.2.2" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-util "^29.6.1" + jest-validate "^29.6.1" resolve "^1.20.0" resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz" - integrity sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ== +jest-runner@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.1.tgz#54557087e7972d345540d622ab5bfc3d8f34688c" + integrity sha512-tw0wb2Q9yhjAQ2w8rHRDxteryyIck7gIzQE4Reu3JuOBpGp96xWgF0nY8MDdejzrLCZKDcp8JlZrBN/EtkQvPQ== dependencies: - "@jest/console" "^29.5.0" - "@jest/environment" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/console" "^29.6.1" + "@jest/environment" "^29.6.1" + "@jest/test-result" "^29.6.1" + "@jest/transform" "^29.6.1" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" jest-docblock "^29.4.3" - jest-environment-node "^29.5.0" - jest-haste-map "^29.5.0" - jest-leak-detector "^29.5.0" - jest-message-util "^29.5.0" - jest-resolve "^29.5.0" - jest-runtime "^29.5.0" - jest-util "^29.5.0" - jest-watcher "^29.5.0" - jest-worker "^29.5.0" + jest-environment-node "^29.6.1" + jest-haste-map "^29.6.1" + jest-leak-detector "^29.6.1" + jest-message-util "^29.6.1" + jest-resolve "^29.6.1" + jest-runtime "^29.6.1" + jest-util "^29.6.1" + jest-watcher "^29.6.1" + jest-worker "^29.6.1" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz" - integrity sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw== - dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/globals" "^29.5.0" - "@jest/source-map" "^29.4.3" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" +jest-runtime@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.1.tgz#8a0fc9274ef277f3d70ba19d238e64334958a0dc" + integrity sha512-D6/AYOA+Lhs5e5il8+5pSLemjtJezUr+8zx+Sn8xlmOux3XOqx4d8l/2udBea8CRPqqrzhsKUsN/gBDE/IcaPQ== + dependencies: + "@jest/environment" "^29.6.1" + "@jest/fake-timers" "^29.6.1" + "@jest/globals" "^29.6.1" + "@jest/source-map" "^29.6.0" + "@jest/test-result" "^29.6.1" + "@jest/transform" "^29.6.1" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" + jest-haste-map "^29.6.1" + jest-message-util "^29.6.1" + jest-mock "^29.6.1" jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" + jest-resolve "^29.6.1" + jest-snapshot "^29.6.1" + jest-util "^29.6.1" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz" - integrity sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g== +jest-snapshot@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.1.tgz#0d083cb7de716d5d5cdbe80d598ed2fbafac0239" + integrity sha512-G4UQE1QQ6OaCgfY+A0uR1W2AY0tGXUPQpoUClhWHq1Xdnx1H6JOrC2nH5lqnOEqaDgbHFgIwZ7bNq24HpB180A== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/babel__traverse" "^7.0.6" + "@jest/expect-utils" "^29.6.1" + "@jest/transform" "^29.6.1" + "@jest/types" "^29.6.1" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.5.0" + expect "^29.6.1" graceful-fs "^4.2.9" - jest-diff "^29.5.0" + jest-diff "^29.6.1" jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + jest-matcher-utils "^29.6.1" + jest-message-util "^29.6.1" + jest-util "^29.6.1" natural-compare "^1.4.0" - pretty-format "^29.5.0" - semver "^7.3.5" + pretty-format "^29.6.1" + semver "^7.5.3" jest-util@^28.1.3: version "28.1.3" @@ -3979,30 +4122,42 @@ jest-util@^29.5.0: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz" - integrity sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ== +jest-util@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.1.tgz#c9e29a87a6edbf1e39e6dee2b4689b8a146679cb" + integrity sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.1.tgz#765e684af6e2c86dce950aebefbbcd4546d69f7b" + integrity sha512-r3Ds69/0KCN4vx4sYAbGL1EVpZ7MSS0vLmd3gV78O+NAx3PDQQukRU5hNHPXlyqCgFY8XUk7EuTMLugh0KzahA== + dependencies: + "@jest/types" "^29.6.1" camelcase "^6.2.0" chalk "^4.0.0" jest-get-type "^29.4.3" leven "^3.1.0" - pretty-format "^29.5.0" + pretty-format "^29.6.1" -jest-watcher@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz" - integrity sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA== +jest-watcher@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.1.tgz#7c0c43ddd52418af134c551c92c9ea31e5ec942e" + integrity sha512-d4wpjWTS7HEZPaaj8m36QiaP856JthRZkrgcIY/7ISoUWPIillrXM23WPboZVLbiwZBt4/qn2Jke84Sla6JhFA== dependencies: - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/test-result" "^29.6.1" + "@jest/types" "^29.6.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.5.0" + jest-util "^29.6.1" string-length "^4.0.1" jest-worker@^28.1.3: @@ -4014,25 +4169,25 @@ jest-worker@^28.1.3: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz" - integrity sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA== +jest-worker@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.1.tgz#64b015f0e985ef3a8ad049b61fe92b3db74a5319" + integrity sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA== dependencies: "@types/node" "*" - jest-util "^29.5.0" + jest-util "^29.6.1" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz" - integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== +jest@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.6.1.tgz#74be1cb719c3abe439f2d94aeb18e6540a5b02ad" + integrity sha512-Nirw5B4nn69rVUZtemCQhwxOBhm0nsp3hmtF4rzCeWD7BkjAXRIji7xWQfnTNbz9g0aVsBX6aZK3n+23LM6uDw== dependencies: - "@jest/core" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/core" "^29.6.1" + "@jest/types" "^29.6.1" import-local "^3.0.2" - jest-cli "^29.5.0" + jest-cli "^29.6.1" js-tokens@^4.0.0: version "4.0.0" @@ -4389,17 +4544,17 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" - word-wrap "^1.2.3" os-tmpdir@~1.0.2: version "1.0.2" @@ -4525,11 +4680,16 @@ prepend-file@^2.0.1: dependencies: temp-write "^4.0.0" -prettier@^2.6.2, prettier@^2.8.8: +prettier@^2.6.2: version "2.8.8" resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== +prettier@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.0.tgz#e7b19f691245a21d618c68bc54dc06122f6105ae" + integrity sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g== + pretty-format@^29.0.0, pretty-format@^29.5.0: version "29.5.0" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz" @@ -4539,6 +4699,15 @@ pretty-format@^29.0.0, pretty-format@^29.5.0: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.1.tgz#ec838c288850b7c4f9090b867c2d4f4edbfb0f3e" + integrity sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog== + dependencies: + "@jest/schemas" "^29.6.0" + ansi-styles "^5.0.0" + react-is "^18.0.0" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" @@ -4771,10 +4940,10 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.5, semver@^7.3.7: - version "7.5.2" - resolved "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz" - integrity sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ== +semver@^7.5.0, semver@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" @@ -5065,18 +5234,16 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -tslib@^1.8.1, tslib@^1.9.0: +ts-api-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d" + integrity sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A== + +tslib@^1.9.0: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" @@ -5109,10 +5276,10 @@ type@^2.7.2: resolved "https://registry.npmjs.org/type/-/type-2.7.2.tgz" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== -typescript@^5.1.5: - version "5.1.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.5.tgz#a3ae755082488b6046fe64345d293ef26af08671" - integrity sha512-FOH+WN/DQjUvN6WgW+c4Ml3yi0PH+a/8q+kNIfRehv1wLhWONedw85iu+vQ39Wp49IzTJEsZ2lyLXpBF7mkF1g== +typescript@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" + integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" @@ -5216,11 +5383,6 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" From 3543f06e00c690edf2ea43ef9f710d193dec1f03 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 18 Jul 2023 12:50:12 +0200 Subject: [PATCH 180/218] Lent shares handled in Red Bank (#159) Red Bank handles shares --- Cargo.lock | 92 +++---- Cargo.toml | 9 +- contracts/credit-manager/src/borrow.rs | 12 +- contracts/credit-manager/src/contract.rs | 22 +- contracts/credit-manager/src/execute.rs | 10 +- contracts/credit-manager/src/hls.rs | 14 +- contracts/credit-manager/src/instantiate.rs | 6 +- contracts/credit-manager/src/lend.rs | 36 +-- contracts/credit-manager/src/liquidate.rs | 5 +- .../credit-manager/src/liquidate_deposit.rs | 1 - .../credit-manager/src/liquidate_lend.rs | 70 ++---- contracts/credit-manager/src/query.rs | 79 +----- contracts/credit-manager/src/reclaim.rs | 82 ++---- contracts/credit-manager/src/repay.rs | 24 +- contracts/credit-manager/src/state.rs | 2 - contracts/credit-manager/src/update_config.rs | 5 +- contracts/credit-manager/src/utils.rs | 66 +---- .../src/vault/liquidate_vault.rs | 5 - .../credit-manager/tests/helpers/mock_env.rs | 70 ++---- .../credit-manager/tests/helpers/utils.rs | 6 +- .../tests/test_enumerate_lent_shares.rs | 220 ---------------- .../tests/test_enumerate_total_lent_shares.rs | 214 ---------------- contracts/credit-manager/tests/test_lend.rs | 54 ++-- .../tests/test_liquidate_lend.rs | 167 ++----------- contracts/mock-red-bank/src/contract.rs | 17 +- contracts/mock-red-bank/src/execute.rs | 54 +++- contracts/mock-red-bank/src/helpers.rs | 19 +- contracts/mock-red-bank/src/query.rs | 31 ++- contracts/mock-red-bank/src/state.rs | 6 +- packages/health-computer/Cargo.toml | 3 - .../health-computer/src/health_computer.rs | 4 +- .../tests/helpers/prop_test_strategies.rs | 26 +- .../tests/test_health_scenarios.rs | 28 +-- packages/rover/src/adapters/red_bank.rs | 91 ++++--- packages/rover/src/msg/query.rs | 37 +-- .../mars-credit-manager.json | 211 +--------------- .../mars-mock-credit-manager.json | 235 +----------------- .../mars-mock-red-bank.json | 28 +++ .../mars-rover-health-computer.json | 32 +-- scripts/health/pkg-node/index.js | 12 +- scripts/health/pkg-node/index_bg.wasm | Bin 175592 -> 173167 bytes scripts/health/pkg-web/index.js | 10 +- scripts/health/pkg-web/index_bg.wasm | Bin 174668 -> 172243 bytes scripts/package.json | 6 +- .../mars-account-nft/MarsAccountNft.client.ts | 2 +- .../MarsAccountNft.message-composer.ts | 2 +- .../MarsAccountNft.react-query.ts | 2 +- .../mars-account-nft/MarsAccountNft.types.ts | 2 +- .../generated/mars-account-nft/bundle.ts | 2 +- .../MarsCreditManager.client.ts | 58 +---- .../MarsCreditManager.message-composer.ts | 7 +- .../MarsCreditManager.react-query.ts | 91 +------ .../MarsCreditManager.types.ts | 35 +-- .../generated/mars-credit-manager/bundle.ts | 2 +- .../MarsMockCreditManager.client.ts | 56 +---- .../MarsMockCreditManager.message-composer.ts | 5 +- .../MarsMockCreditManager.react-query.ts | 89 +------ .../MarsMockCreditManager.types.ts | 29 +-- .../mars-mock-credit-manager/bundle.ts | 2 +- .../mars-mock-oracle/MarsMockOracle.client.ts | 2 +- .../MarsMockOracle.message-composer.ts | 2 +- .../MarsMockOracle.react-query.ts | 2 +- .../mars-mock-oracle/MarsMockOracle.types.ts | 2 +- .../generated/mars-mock-oracle/bundle.ts | 2 +- .../MarsMockRedBank.client.ts | 31 ++- .../MarsMockRedBank.message-composer.ts | 29 ++- .../MarsMockRedBank.react-query.ts | 12 +- .../MarsMockRedBank.types.ts | 9 +- .../generated/mars-mock-red-bank/bundle.ts | 2 +- .../mars-mock-vault/MarsMockVault.client.ts | 2 +- .../MarsMockVault.message-composer.ts | 2 +- .../MarsMockVault.react-query.ts | 2 +- .../mars-mock-vault/MarsMockVault.types.ts | 2 +- .../types/generated/mars-mock-vault/bundle.ts | 2 +- .../mars-params/MarsParams.client.ts | 2 +- .../MarsParams.message-composer.ts | 2 +- .../mars-params/MarsParams.react-query.ts | 2 +- .../generated/mars-params/MarsParams.types.ts | 2 +- scripts/types/generated/mars-params/bundle.ts | 2 +- .../MarsRoverHealthComputer.client.ts | 3 +- ...arsRoverHealthComputer.message-composer.ts | 3 +- .../MarsRoverHealthComputer.react-query.ts | 3 +- .../MarsRoverHealthComputer.types.ts | 9 +- .../mars-rover-health-computer/bundle.ts | 2 +- .../MarsRoverHealthTypes.client.ts | 2 +- .../MarsRoverHealthTypes.message-composer.ts | 2 +- .../MarsRoverHealthTypes.react-query.ts | 2 +- .../MarsRoverHealthTypes.types.ts | 2 +- .../mars-rover-health-types/bundle.ts | 2 +- .../MarsRoverHealth.client.ts | 2 +- .../MarsRoverHealth.message-composer.ts | 2 +- .../MarsRoverHealth.react-query.ts | 2 +- .../MarsRoverHealth.types.ts | 2 +- .../generated/mars-rover-health/bundle.ts | 2 +- .../MarsSwapperBase.client.ts | 2 +- .../MarsSwapperBase.message-composer.ts | 2 +- .../MarsSwapperBase.react-query.ts | 2 +- .../MarsSwapperBase.types.ts | 2 +- .../generated/mars-swapper-base/bundle.ts | 2 +- .../MarsSwapperOsmosis.client.ts | 2 +- .../MarsSwapperOsmosis.message-composer.ts | 2 +- .../MarsSwapperOsmosis.react-query.ts | 2 +- .../MarsSwapperOsmosis.types.ts | 2 +- .../generated/mars-swapper-osmosis/bundle.ts | 2 +- .../MarsV2ZapperBase.client.ts | 2 +- .../MarsV2ZapperBase.message-composer.ts | 2 +- .../MarsV2ZapperBase.react-query.ts | 2 +- .../MarsV2ZapperBase.types.ts | 2 +- .../generated/mars-v2-zapper-base/bundle.ts | 2 +- .../MarsV3ZapperBase.client.ts | 2 +- .../MarsV3ZapperBase.message-composer.ts | 2 +- .../MarsV3ZapperBase.react-query.ts | 2 +- .../MarsV3ZapperBase.types.ts | 2 +- .../generated/mars-v3-zapper-base/bundle.ts | 2 +- scripts/yarn.lock | 67 +++-- 115 files changed, 611 insertions(+), 2154 deletions(-) delete mode 100644 contracts/credit-manager/tests/test_enumerate_lent_shares.rs delete mode 100644 contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs diff --git a/Cargo.lock b/Cargo.lock index 408fa942a..52b22f210 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,9 +45,9 @@ checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "apollo-cw-asset" @@ -82,7 +82,7 @@ checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -320,9 +320,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6340df57935414636969091153f35f68d9f00bbc8fb4a9c6054706c213e6c6bc" +checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" [[package]] name = "core-foundation" @@ -787,9 +787,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" [[package]] name = "ecdsa" @@ -1015,7 +1015,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -1350,9 +1350,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" @@ -1581,9 +1581,9 @@ dependencies = [ [[package]] name = "mars-red-bank-types" -version = "1.1.0-develop-0" +version = "1.1.0-ntrn-2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00f93d7e4b71d29d7cf448f12295d68129c761a9ab7c165e87150a3d715599e" +checksum = "95aa8267e5681a101153d1ebc5b2a57a8d9f704c6aae519646ea5288d7fab8b5" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1985,9 +1985,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pbkdf2" @@ -2054,7 +2054,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -2087,9 +2087,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +checksum = "92de25114670a878b1261c79c9f8f729fb97e95bac93f6312f583c60dd6a1dfe" dependencies = [ "unicode-ident", ] @@ -2177,9 +2177,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb" dependencies = [ "proc-macro2", ] @@ -2247,18 +2247,18 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.7.3", + "regex-syntax 0.7.4", ] [[package]] name = "regex-automata" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.3", + "regex-syntax 0.7.4", ] [[package]] @@ -2269,9 +2269,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "rfc6979" @@ -2372,9 +2372,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "rusty-fork" @@ -2390,9 +2390,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -2485,9 +2485,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" @@ -2529,9 +2529,9 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a16be4fe5320ade08736447e3198294a5ea9a6d44dde6f35f0a5e06859c427a" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] @@ -2544,7 +2544,7 @@ checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -2560,9 +2560,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" dependencies = [ "itoa", "ryu", @@ -2577,7 +2577,7 @@ checksum = "1d89a8107374290037607734c0b73a85db7ed80cae314b3c5791f192a496e731" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -2738,9 +2738,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.25" +version = "2.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" dependencies = [ "proc-macro2", "quote", @@ -2904,7 +2904,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -2965,7 +2965,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -3066,9 +3066,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -3163,7 +3163,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", "wasm-bindgen-shared", ] @@ -3185,7 +3185,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3350,5 +3350,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] diff --git a/Cargo.toml b/Cargo.toml index 42e043324..8c38c7e70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] -members = [ +resolver = "2" +members = [ # prod contracts "contracts/account-nft", "contracts/credit-manager", @@ -36,7 +37,7 @@ documentation = "https://docs.marsprotocol.io/" keywords = ["mars", "cosmos", "cosmwasm"] [workspace.dependencies] -anyhow = "1.0.71" +anyhow = "1.0.72" cosmwasm-schema = "1.2.7" cosmwasm-std = "1.2.7" cw2 = "1.1.0" @@ -54,7 +55,7 @@ osmosis-test-tube = "16.0.0" proptest = "1.2.0" schemars = "0.8.12" serde = { version = "1.0.171", default-features = false, features = ["derive"] } -serde_json = "1.0.100" +serde_json = "1.0.103" serde-wasm-bindgen = "0.5.0" thiserror = "1.0.43" wasm-bindgen = "0.2.87" @@ -62,7 +63,7 @@ wasm-bindgen = "0.2.87" # mars packages mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "00301d60c38af09d8eb7980355009e2f00c6f41f" } mars-owner = { version = "1.2.0", features = ["emergency-owner"] } -mars-red-bank-types = "1.1.0-develop-0" +mars-red-bank-types = "1.1.0-ntrn-2" mars-rover-health-computer = { version = "2.0.0", path = "./packages/health-computer" } mars-rover-health-types = { version = "2.0.0", path = "./packages/health-types" } mars-rover = { version = "2.0.0", path = "./packages/rover" } diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index 8b3bbcade..2de084336 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, DepsMut, Env, Response, Uint128}; +use cosmwasm_std::{Coin, DepsMut, Response, Uint128}; use mars_rover::error::{ContractError, ContractResult}; use crate::{ @@ -13,12 +13,7 @@ pub static DEFAULT_DEBT_SHARES_PER_COIN_BORROWED: Uint128 = Uint128::new(1_000_0 /// else, get debt ownership % and multiply by total existing shares /// /// increment total debt shares, token debt shares, and asset amount -pub fn borrow( - mut deps: DepsMut, - env: Env, - account_id: &str, - coin: Coin, -) -> ContractResult { +pub fn borrow(mut deps: DepsMut, account_id: &str, coin: Coin) -> ContractResult { if coin.amount.is_zero() { return Err(ContractError::NoAmount); } @@ -26,8 +21,7 @@ pub fn borrow( assert_coin_is_whitelisted(&mut deps, &coin.denom)?; let red_bank = RED_BANK.load(deps.storage)?; - let total_debt_amount = - red_bank.query_debt(&deps.querier, &env.contract.address, &coin.denom)?; + let total_debt_amount = red_bank.query_debt(&deps.querier, &coin.denom)?; let debt_shares_to_add = if total_debt_amount.is_zero() { coin.amount.checked_mul(DEFAULT_DEBT_SHARES_PER_COIN_BORROWED)? diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index e5c613792..abdaed1d7 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -12,9 +12,8 @@ use crate::{ execute::{create_credit_account, dispatch_actions, execute_callback}, instantiate::store_config, query::{ - query_all_coin_balances, query_all_debt_shares, query_all_lent_shares, - query_all_total_debt_shares, query_all_total_lent_shares, query_all_vault_positions, - query_config, query_positions, query_total_debt_shares, query_total_lent_shares, + query_all_coin_balances, query_all_debt_shares, query_all_total_debt_shares, + query_all_vault_positions, query_config, query_positions, query_total_debt_shares, query_vault_position_value, query_vault_utilization, }, repay::repay_from_wallet, @@ -30,12 +29,12 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, - _env: Env, + env: Env, _info: MessageInfo, msg: InstantiateMsg, ) -> ContractResult { set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; - store_config(deps, &msg)?; + store_config(deps, env, &msg)?; Ok(Response::default()) } @@ -50,7 +49,7 @@ pub fn execute( ExecuteMsg::CreateCreditAccount(kind) => create_credit_account(deps, info.sender, kind), ExecuteMsg::UpdateConfig { updates, - } => update_config(deps, info, updates), + } => update_config(deps, env, info, updates), ExecuteMsg::UpdateNftConfig { config, ownership, @@ -87,7 +86,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { } => to_binary(&query_vault_utilization(deps, env, vault)?), QueryMsg::Positions { account_id, - } => to_binary(&query_positions(deps, &env, &account_id)?), + } => to_binary(&query_positions(deps, &account_id)?), QueryMsg::AllCoinBalances { start_after, limit, @@ -101,15 +100,6 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { start_after, limit, } => to_binary(&query_all_total_debt_shares(deps, start_after, limit)?), - QueryMsg::AllLentShares { - start_after, - limit, - } => to_binary(&query_all_lent_shares(deps, start_after, limit)?), - QueryMsg::TotalLentShares(denom) => to_binary(&query_total_lent_shares(deps, &denom)?), - QueryMsg::AllTotalLentShares { - start_after, - limit, - } => to_binary(&query_all_total_lent_shares(deps, start_after, limit)?), QueryMsg::AllVaultPositions { start_after, limit, diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 49eab92e2..2a334f609 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -262,11 +262,11 @@ pub fn execute_callback( CallbackMsg::Borrow { coin, account_id, - } => borrow(deps, env, &account_id, coin), + } => borrow(deps, &account_id, coin), CallbackMsg::Repay { account_id, coin, - } => repay(deps, env, &account_id, &coin), + } => repay(deps, &account_id, &coin), CallbackMsg::RepayForRecipient { benefactor_account_id, recipient_account_id, @@ -275,11 +275,11 @@ pub fn execute_callback( CallbackMsg::Lend { account_id, coin, - } => lend(deps, env, &account_id, &coin), + } => lend(deps, &account_id, &coin), CallbackMsg::Reclaim { account_id, coin, - } => reclaim(deps, env, &account_id, &coin), + } => reclaim(deps, &account_id, &coin), CallbackMsg::AssertMaxLTV { account_id, prev_health_state, @@ -391,7 +391,7 @@ pub fn execute_callback( } => refund_coin_balances(deps, env, &account_id), CallbackMsg::AssertAccountReqs { account_id, - } => assert_account_requirements(deps, env, account_id), + } => assert_account_requirements(deps, account_id), CallbackMsg::RemoveReentrancyGuard {} => REENTRANCY_GUARD.try_unlock(deps.storage), } } diff --git a/contracts/credit-manager/src/hls.rs b/contracts/credit-manager/src/hls.rs index 78e97473f..0f0519f91 100644 --- a/contracts/credit-manager/src/hls.rs +++ b/contracts/credit-manager/src/hls.rs @@ -1,20 +1,16 @@ -use cosmwasm_std::{Deps, DepsMut, Env, Response}; +use cosmwasm_std::{Deps, DepsMut, Response}; use mars_params::types::hls::HlsAssetType; use mars_rover::error::{ContractError, ContractResult}; use mars_rover_health_types::AccountKind; use crate::{query::query_positions, state::PARAMS, utils::get_account_kind}; -pub fn assert_account_requirements( - deps: DepsMut, - env: Env, - account_id: String, -) -> ContractResult { +pub fn assert_account_requirements(deps: DepsMut, account_id: String) -> ContractResult { let kind = get_account_kind(deps.storage, &account_id)?; match kind { AccountKind::Default => {} // No restrictions - AccountKind::HighLeveredStrategy => assert_hls_rules(deps.as_ref(), &env, &account_id)?, + AccountKind::HighLeveredStrategy => assert_hls_rules(deps.as_ref(), &account_id)?, } Ok(Response::new() @@ -23,9 +19,9 @@ pub fn assert_account_requirements( .add_attribute("account_kind", kind.to_string())) } -fn assert_hls_rules(deps: Deps, env: &Env, account_id: &str) -> ContractResult<()> { +fn assert_hls_rules(deps: Deps, account_id: &str) -> ContractResult<()> { // Rule #1 - There can only be 0 or 1 debt denom in the account - let positions = query_positions(deps, env, account_id)?; + let positions = query_positions(deps, account_id)?; if positions.debts.len() > 1 { return Err(ContractError::HLS { diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index f1b8cc27f..243efd5bb 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::DepsMut; +use cosmwasm_std::{DepsMut, Env}; use mars_owner::OwnerInit::SetInitialOwner; use mars_rover::{error::ContractResult, msg::InstantiateMsg}; @@ -6,7 +6,7 @@ use crate::state::{ HEALTH_CONTRACT, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, RED_BANK, SWAPPER, ZAPPER, }; -pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { +pub fn store_config(deps: DepsMut, env: Env, msg: &InstantiateMsg) -> ContractResult<()> { OWNER.initialize( deps.storage, deps.api, @@ -15,7 +15,7 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { }, )?; - RED_BANK.save(deps.storage, &msg.red_bank.check(deps.api)?)?; + RED_BANK.save(deps.storage, &msg.red_bank.check(deps.api, env.contract.address)?)?; ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; SWAPPER.save(deps.storage, &msg.swapper.check(deps.api)?)?; ZAPPER.save(deps.storage, &msg.zapper.check(deps.api)?)?; diff --git a/contracts/credit-manager/src/lend.rs b/contracts/credit-manager/src/lend.rs index 7008436c4..f75e104bc 100644 --- a/contracts/credit-manager/src/lend.rs +++ b/contracts/credit-manager/src/lend.rs @@ -1,22 +1,15 @@ -use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; +use cosmwasm_std::{Coin, Deps, DepsMut, Response, Uint128}; use mars_rover::{ error::{ContractError, ContractResult}, msg::execute::ActionCoin, }; use crate::{ - state::{COIN_BALANCES, LENT_SHARES, RED_BANK, TOTAL_LENT_SHARES}, + state::{COIN_BALANCES, RED_BANK}, utils::{assert_coin_is_whitelisted, decrement_coin_balance}, }; -pub static DEFAULT_LENT_SHARES_PER_COIN: Uint128 = Uint128::new(1_000_000); - -pub fn lend( - mut deps: DepsMut, - env: Env, - account_id: &str, - coin: &ActionCoin, -) -> ContractResult { +pub fn lend(mut deps: DepsMut, account_id: &str, coin: &ActionCoin) -> ContractResult { assert_coin_is_whitelisted(&mut deps, &coin.denom)?; let amount_to_lend = Coin { @@ -24,35 +17,14 @@ pub fn lend( amount: get_lend_amount(deps.as_ref(), account_id, coin)?, }; - // Total Credit Manager has lent to Red Bank for denom - let red_bank = RED_BANK.load(deps.storage)?; - let total_lent = - red_bank.query_lent(&deps.querier, &env.contract.address, &amount_to_lend.denom)?; - - let lent_shares_to_add = if total_lent.is_zero() { - amount_to_lend.amount.checked_mul(DEFAULT_LENT_SHARES_PER_COIN)? - } else { - TOTAL_LENT_SHARES - .load(deps.storage, &amount_to_lend.denom)? - .checked_multiply_ratio(amount_to_lend.amount, total_lent)? - }; - - let add_shares = |shares: Option| -> ContractResult { - Ok(shares.unwrap_or_else(Uint128::zero).checked_add(lent_shares_to_add)?) - }; - - TOTAL_LENT_SHARES.update(deps.storage, &amount_to_lend.denom, add_shares)?; - LENT_SHARES.update(deps.storage, (account_id, &amount_to_lend.denom), add_shares)?; - decrement_coin_balance(deps.storage, account_id, &amount_to_lend)?; - let red_bank_lend_msg = red_bank.lend_msg(&amount_to_lend)?; + let red_bank_lend_msg = RED_BANK.load(deps.storage)?.lend_msg(&amount_to_lend, account_id)?; Ok(Response::new() .add_message(red_bank_lend_msg) .add_attribute("action", "lend") .add_attribute("account_id", account_id) - .add_attribute("lent_shares_added", lent_shares_to_add) .add_attribute("coin_lent", &amount_to_lend.denom)) } diff --git a/contracts/credit-manager/src/liquidate.rs b/contracts/credit-manager/src/liquidate.rs index 5c60f469b..ea96b88d2 100644 --- a/contracts/credit-manager/src/liquidate.rs +++ b/contracts/credit-manager/src/liquidate.rs @@ -3,7 +3,7 @@ use std::{ ops::Add, }; -use cosmwasm_std::{Coin, Decimal, DepsMut, Env, QuerierWrapper, StdError, Uint128}; +use cosmwasm_std::{Coin, Decimal, DepsMut, QuerierWrapper, StdError, Uint128}; use mars_params::types::asset::AssetParams; use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ @@ -28,7 +28,6 @@ use crate::{ /// Difference between Liquidator Request Coin and Liquidatee Request Coin goes to rewards-collector account as protocol fee. pub fn calculate_liquidation( deps: &DepsMut, - env: &Env, liquidatee_account_id: &str, debt_coin: &Coin, request_coin: &str, @@ -46,7 +45,7 @@ pub fn calculate_liquidation( // Ensure debt repaid does not exceed liquidatee's total debt for denom let (total_debt_amount, _) = - current_debt_for_denom(deps.as_ref(), env, liquidatee_account_id, &debt_coin.denom)?; + current_debt_for_denom(deps.as_ref(), liquidatee_account_id, &debt_coin.denom)?; let params = PARAMS.load(deps.storage)?; let target_health_factor = params.query_target_health_factor(&deps.querier)?; diff --git a/contracts/credit-manager/src/liquidate_deposit.rs b/contracts/credit-manager/src/liquidate_deposit.rs index f452b0340..e3b8cd643 100644 --- a/contracts/credit-manager/src/liquidate_deposit.rs +++ b/contracts/credit-manager/src/liquidate_deposit.rs @@ -24,7 +24,6 @@ pub fn liquidate_deposit( let (debt, liquidator_request, liquidatee_request) = calculate_liquidation( &deps, - &env, liquidatee_account_id, &debt_coin, request_coin_denom, diff --git a/contracts/credit-manager/src/liquidate_lend.rs b/contracts/credit-manager/src/liquidate_lend.rs index 212548e80..c8b2a52f2 100644 --- a/contracts/credit-manager/src/liquidate_lend.rs +++ b/contracts/credit-manager/src/liquidate_lend.rs @@ -1,12 +1,11 @@ use cosmwasm_std::{Coin, DepsMut, Env, Response}; -use mars_rover::error::ContractResult; +use mars_rover::error::{ContractError::NoneLent, ContractResult}; use crate::{ liquidate::calculate_liquidation, liquidate_deposit::repay_debt, - reclaim::{current_lent_amount_for_denom, lent_amount_to_shares}, - state::REWARDS_COLLECTOR, - utils::{decrement_lent_shares, increment_lent_shares}, + state::{RED_BANK, REWARDS_COLLECTOR}, + utils::increment_coin_balance, }; pub fn liquidate_lend( @@ -18,74 +17,51 @@ pub fn liquidate_lend( request_coin_denom: &str, ) -> ContractResult { // Check how much lent coin is available for reclaim (can be withdrawn from Red Bank) - let (total_lent_amount, _) = current_lent_amount_for_denom( - deps.as_ref(), - &env, + let total_lent_amount = RED_BANK.load(deps.storage)?.query_lent( + &deps.querier, liquidatee_account_id, request_coin_denom, )?; + if total_lent_amount.is_zero() { + return Err(NoneLent); + } + let (debt, liquidator_request, liquidatee_request) = calculate_liquidation( &deps, - &env, liquidatee_account_id, &debt_coin, request_coin_denom, total_lent_amount, )?; + // Liquidator pays down debt on behalf of liquidatee let repay_msg = repay_debt(deps.storage, &env, liquidator_account_id, liquidatee_account_id, &debt)?; - let shares_from_liquidatee = lent_amount_to_shares( - deps.as_ref(), - &env, - &Coin { - denom: request_coin_denom.to_string(), - amount: liquidatee_request.amount, - }, - )?; - let shares_to_liquidator = lent_amount_to_shares( - deps.as_ref(), - &env, - &Coin { - denom: request_coin_denom.to_string(), - amount: liquidator_request.amount, - }, - )?; + // Liquidatee's lent coin reclaimed from Red Bank + let red_bank = RED_BANK.load(deps.storage)?; + let reclaim_from_liquidatee_msg = + red_bank.reclaim_msg(&liquidatee_request, liquidatee_account_id)?; - decrement_lent_shares( - deps.storage, - liquidatee_account_id, - request_coin_denom, - shares_from_liquidatee, - )?; - increment_lent_shares( - deps.storage, - liquidator_account_id, - request_coin_denom, - shares_to_liquidator, - )?; + // Liquidator gets portion of reclaimed lent coin + increment_coin_balance(deps.storage, liquidator_account_id, &liquidator_request)?; // Transfer protocol fee to rewards-collector account let rewards_collector_account = REWARDS_COLLECTOR.load(deps.storage)?.account_id; - let protocol_fee_shares = shares_from_liquidatee.checked_sub(shares_to_liquidator)?; - increment_lent_shares( - deps.storage, - &rewards_collector_account, - request_coin_denom, - protocol_fee_shares, - )?; + let protocol_fee_coin = Coin { + denom: request_coin_denom.to_string(), + amount: liquidatee_request.amount.checked_sub(liquidator_request.amount)?, + }; + increment_coin_balance(deps.storage, &rewards_collector_account, &protocol_fee_coin)?; Ok(Response::new() .add_message(repay_msg) + .add_message(reclaim_from_liquidatee_msg) .add_attribute("action", "liquidate_lend") .add_attribute("account_id", liquidator_account_id) .add_attribute("liquidatee_account_id", liquidatee_account_id) .add_attribute("coin_debt_repaid", debt.to_string()) .add_attribute("coin_liquidated", liquidatee_request.to_string()) - .add_attribute( - "protocol_fee_coin", - Coin::new(protocol_fee_shares.u128(), request_coin_denom).to_string(), - )) + .add_attribute("protocol_fee_coin", protocol_fee_coin.to_string())) } diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 4bee85f1e..a39cc576d 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -6,18 +6,18 @@ use mars_rover::{ adapters::vault::{VaultBase, VaultPosition, VaultPositionValue, VaultUnchecked}, error::ContractResult, msg::query::{ - CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, LentAmount, LentShares, - Positions, SharesResponseItem, VaultPositionResponseItem, VaultUtilizationResponse, + CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, Positions, + SharesResponseItem, VaultPositionResponseItem, VaultUtilizationResponse, }, }; use crate::{ state::{ - ACCOUNT_NFT, COIN_BALANCES, DEBT_SHARES, HEALTH_CONTRACT, LENT_SHARES, - MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, RED_BANK, REWARDS_COLLECTOR, SWAPPER, - TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, VAULT_POSITIONS, ZAPPER, + ACCOUNT_NFT, COIN_BALANCES, DEBT_SHARES, HEALTH_CONTRACT, MAX_UNLOCKING_POSITIONS, ORACLE, + OWNER, PARAMS, RED_BANK, REWARDS_COLLECTOR, SWAPPER, TOTAL_DEBT_SHARES, VAULT_POSITIONS, + ZAPPER, }, - utils::{debt_shares_to_amount, lent_shares_to_amount}, + utils::debt_shares_to_amount, vault::vault_utilization_in_deposit_cap_denom, }; @@ -25,7 +25,7 @@ pub fn query_config(deps: Deps) -> ContractResult { Ok(ConfigResponse { ownership: OWNER.query(deps.storage)?, account_nft: ACCOUNT_NFT.may_load(deps.storage)?.map(|a| a.address().into()), - red_bank: RED_BANK.load(deps.storage)?.address().into(), + red_bank: RED_BANK.load(deps.storage)?.addr.into(), oracle: ORACLE.load(deps.storage)?.address().into(), params: PARAMS.load(deps.storage)?.address().into(), max_unlocking_positions: MAX_UNLOCKING_POSITIONS.load(deps.storage)?, @@ -36,12 +36,12 @@ pub fn query_config(deps: Deps) -> ContractResult { }) } -pub fn query_positions(deps: Deps, env: &Env, account_id: &str) -> ContractResult { +pub fn query_positions(deps: Deps, account_id: &str) -> ContractResult { Ok(Positions { account_id: account_id.to_string(), deposits: query_coin_balances(deps, account_id)?, - debts: query_debt_amounts(deps, env, account_id)?, - lends: query_lent_amounts(deps, env, account_id)?, + debts: query_debt_amounts(deps, account_id)?, + lends: RED_BANK.load(deps.storage)?.query_all_lent(&deps.querier, account_id)?, vaults: query_vault_positions(deps, account_id)?, }) } @@ -63,29 +63,13 @@ pub fn query_all_coin_balances( }) } -fn query_lent_amounts(deps: Deps, env: &Env, account_id: &str) -> ContractResult> { - LENT_SHARES - .prefix(account_id) - .range(deps.storage, None, None, Order::Ascending) - .map(|res| { - let (denom, shares) = res?; - let coin = lent_shares_to_amount(deps, &env.contract.address, &denom, shares)?; - Ok(LentAmount { - denom, - shares, - amount: coin.amount, - }) - }) - .collect() -} - -fn query_debt_amounts(deps: Deps, env: &Env, account_id: &str) -> ContractResult> { +fn query_debt_amounts(deps: Deps, account_id: &str) -> ContractResult> { DEBT_SHARES .prefix(account_id) .range(deps.storage, None, None, Order::Ascending) .map(|res| { let (denom, shares) = res?; - let coin = debt_shares_to_amount(deps, &env.contract.address, &denom, shares)?; + let coin = debt_shares_to_amount(deps, &denom, shares)?; Ok(DebtAmount { denom, shares, @@ -126,23 +110,6 @@ pub fn query_all_debt_shares( }) } -pub fn query_all_lent_shares( - deps: Deps, - start_after: Option<(String, String)>, - limit: Option, -) -> StdResult> { - let start = start_after - .as_ref() - .map(|(account_id, denom)| Bound::exclusive((account_id.as_str(), denom.as_str()))); - paginate_map(&LENT_SHARES, deps.storage, start, limit, |(account_id, denom), shares| { - Ok(SharesResponseItem { - account_id, - denom, - shares, - }) - }) -} - pub fn query_vault_utilization( deps: Deps, env: Env, @@ -200,14 +167,6 @@ pub fn query_total_debt_shares(deps: Deps, denom: &str) -> StdResult }) } -pub fn query_total_lent_shares(deps: Deps, denom: &str) -> StdResult { - let shares = TOTAL_LENT_SHARES.load(deps.storage, denom)?; - Ok(LentShares { - denom: denom.to_string(), - shares, - }) -} - pub fn query_all_total_debt_shares( deps: Deps, start_after: Option, @@ -222,20 +181,6 @@ pub fn query_all_total_debt_shares( }) } -pub fn query_all_total_lent_shares( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult> { - let start = start_after.as_ref().map(|denom| Bound::exclusive(denom.as_str())); - paginate_map(&TOTAL_LENT_SHARES, deps.storage, start, limit, |denom, shares| { - Ok(LentShares { - denom, - shares, - }) - }) -} - pub fn query_vault_position_value( deps: Deps, vault_position: VaultPosition, diff --git a/contracts/credit-manager/src/reclaim.rs b/contracts/credit-manager/src/reclaim.rs index 9c3c9ed38..5faf553a3 100644 --- a/contracts/credit-manager/src/reclaim.rs +++ b/contracts/credit-manager/src/reclaim.rs @@ -1,53 +1,22 @@ use std::cmp::min; -use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; +use cosmwasm_std::{Coin, DepsMut, Response, Uint128}; use mars_rover::{ - error::{ContractError, ContractResult}, + error::{ContractError::NoneLent, ContractResult}, msg::execute::ActionCoin, }; -use crate::{ - state::{LENT_SHARES, RED_BANK, TOTAL_LENT_SHARES}, - utils::{increment_coin_balance, lent_shares_to_amount}, -}; +use crate::{state::RED_BANK, utils::increment_coin_balance}; -pub fn reclaim( - deps: DepsMut, - env: Env, - account_id: &str, - coin: &ActionCoin, -) -> ContractResult { - let (lent_amount, lent_shares) = - current_lent_amount_for_denom(deps.as_ref(), &env, account_id, &coin.denom)?; +pub fn reclaim(deps: DepsMut, account_id: &str, coin: &ActionCoin) -> ContractResult { + let red_bank = RED_BANK.load(deps.storage)?; + let lent_amount = red_bank.query_lent(&deps.querier, account_id, &coin.denom)?; let amount_to_reclaim = min(lent_amount, coin.amount.value().unwrap_or(Uint128::MAX)); - let shares_to_reclaim = lent_amount_to_shares( - deps.as_ref(), - &env, - &Coin { - denom: coin.denom.to_string(), - amount: amount_to_reclaim, - }, - )?; - // Decrement token's lent position - if amount_to_reclaim == lent_amount { - LENT_SHARES.remove(deps.storage, (account_id, &coin.denom)); - } else { - LENT_SHARES.save( - deps.storage, - (account_id, &coin.denom), - &lent_shares.checked_sub(shares_to_reclaim)?, - )?; + if amount_to_reclaim.is_zero() { + return Err(NoneLent); } - // Decrement total lent shares for coin - let total_lent_shares = TOTAL_LENT_SHARES.load(deps.storage, &coin.denom)?; - TOTAL_LENT_SHARES.save( - deps.storage, - &coin.denom, - &total_lent_shares.checked_sub(shares_to_reclaim)?, - )?; - increment_coin_balance( deps.storage, account_id, @@ -57,38 +26,17 @@ pub fn reclaim( }, )?; - let red_bank = RED_BANK.load(deps.storage)?; - let red_bank_reclaim_msg = red_bank.reclaim_msg(&Coin { - denom: coin.denom.to_string(), - amount: amount_to_reclaim, - })?; + let red_bank_reclaim_msg = red_bank.reclaim_msg( + &Coin { + denom: coin.denom.to_string(), + amount: amount_to_reclaim, + }, + account_id, + )?; Ok(Response::new() .add_message(red_bank_reclaim_msg) .add_attribute("action", "reclaim") .add_attribute("account_id", account_id) - .add_attribute("lent_shares_reclaimed", shares_to_reclaim) .add_attribute("coin_reclaimed", format!("{}{}", amount_to_reclaim, &coin.denom))) } - -pub fn lent_amount_to_shares(deps: Deps, env: &Env, coin: &Coin) -> ContractResult { - let red_bank = RED_BANK.load(deps.storage)?; - let total_lent_shares = TOTAL_LENT_SHARES.load(deps.storage, &coin.denom)?; - let total_lent = red_bank.query_lent(&deps.querier, &env.contract.address, &coin.denom)?; - let shares = total_lent_shares.checked_multiply_ratio(coin.amount, total_lent)?; - Ok(shares) -} - -/// Get token's current lent amount for denom -/// Returns -> (lent amount, lent shares) -pub fn current_lent_amount_for_denom( - deps: Deps, - env: &Env, - account_id: &str, - denom: &str, -) -> ContractResult<(Uint128, Uint128)> { - let lent_shares = - LENT_SHARES.load(deps.storage, (account_id, denom)).map_err(|_| ContractError::NoneLent)?; - let coin = lent_shares_to_amount(deps, &env.contract.address, denom, lent_shares)?; - Ok((coin.amount, lent_shares)) -} diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index 3b17bc9a9..d4311b3a5 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -18,21 +18,16 @@ use crate::{ utils::{debt_shares_to_amount, decrement_coin_balance, increment_coin_balance}, }; -pub fn repay( - deps: DepsMut, - env: Env, - account_id: &str, - coin: &ActionCoin, -) -> ContractResult { +pub fn repay(deps: DepsMut, account_id: &str, coin: &ActionCoin) -> ContractResult { // Ensure repayment does not exceed max debt on account let (debt_amount, debt_shares) = - current_debt_for_denom(deps.as_ref(), &env, account_id, &coin.denom)?; + current_debt_for_denom(deps.as_ref(), account_id, &coin.denom)?; let amount_to_repay = min(debt_amount, coin.amount.value().unwrap_or(Uint128::MAX)); let coin_to_repay = Coin { denom: coin.denom.to_string(), amount: amount_to_repay, }; - let shares_to_repay = debt_amount_to_shares(deps.as_ref(), &env, &coin_to_repay)?; + let shares_to_repay = debt_amount_to_shares(deps.as_ref(), &coin_to_repay)?; // Decrement token's debt position if amount_to_repay == debt_amount { @@ -66,11 +61,10 @@ pub fn repay( .add_attribute("coin_repaid", coin_to_repay.to_string())) } -fn debt_amount_to_shares(deps: Deps, env: &Env, coin: &Coin) -> ContractResult { +fn debt_amount_to_shares(deps: Deps, coin: &Coin) -> ContractResult { let red_bank = RED_BANK.load(deps.storage)?; let total_debt_shares = TOTAL_DEBT_SHARES.load(deps.storage, &coin.denom)?; - let total_debt_amount = - red_bank.query_debt(&deps.querier, &env.contract.address, &coin.denom)?; + let total_debt_amount = red_bank.query_debt(&deps.querier, &coin.denom)?; let shares = total_debt_shares.checked_multiply_ratio(coin.amount, total_debt_amount)?; Ok(shares) } @@ -79,13 +73,12 @@ fn debt_amount_to_shares(deps: Deps, env: &Env, coin: &Coin) -> ContractResult (debt amount, debt shares) pub fn current_debt_for_denom( deps: Deps, - env: &Env, account_id: &str, denom: &str, ) -> ContractResult<(Uint128, Uint128)> { let debt_shares = DEBT_SHARES.load(deps.storage, (account_id, denom)).map_err(|_| ContractError::NoDebt)?; - let coin = debt_shares_to_amount(deps, &env.contract.address, denom, debt_shares)?; + let coin = debt_shares_to_amount(deps, denom, debt_shares)?; Ok((coin.amount, debt_shares)) } @@ -97,7 +90,7 @@ pub fn repay_for_recipient( coin: ActionCoin, ) -> ContractResult { let (debt_amount, _) = - current_debt_for_denom(deps.as_ref(), &env, recipient_account_id, &coin.denom)?; + current_debt_for_denom(deps.as_ref(), recipient_account_id, &coin.denom)?; let amount_to_repay = min(debt_amount, coin.amount.value().unwrap_or(Uint128::MAX)); let coin_to_repay = &Coin { denom: coin.denom, @@ -132,8 +125,7 @@ pub fn repay_from_wallet( ) -> ContractResult { let coin_sent = one_coin(&info)?; - let (debt_amount, _) = - current_debt_for_denom(deps.as_ref(), &env, &account_id, &coin_sent.denom)?; + let (debt_amount, _) = current_debt_for_denom(deps.as_ref(), &account_id, &coin_sent.denom)?; let amount_to_repay = min(debt_amount, coin_sent.amount); let coin_to_repay = Coin { denom: coin_sent.denom.clone(), diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 9ec88ac3b..e9e84cfdf 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -38,8 +38,6 @@ pub const ACCOUNT_KINDS: Map<&str, AccountKind> = Map::new("account_types"); // pub const COIN_BALANCES: Map<(&str, &str), Uint128> = Map::new("coin_balance"); // Map<(AccountId, Denom), Amount> pub const DEBT_SHARES: Map<(&str, &str), Uint128> = Map::new("debt_shares"); // Map<(AccountId, Denom), Shares> pub const TOTAL_DEBT_SHARES: Map<&str, Uint128> = Map::new("total_debt_shares"); // Map -pub const LENT_SHARES: Map<(&str, &str), Uint128> = Map::new("lent_shares"); // Map<(AccountId, Denom), Shares> -pub const TOTAL_LENT_SHARES: Map<&str, Uint128> = Map::new("total_lent_shares"); // Map pub const VAULT_POSITIONS: Map<(&str, Addr), VaultPositionAmount> = Map::new("vault_positions"); // Map<(AccountId, VaultAddr), VaultPositionAmount> diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index 6bc7cf04d..d1c0068ff 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, MessageInfo, Response, WasmMsg}; +use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, MessageInfo, Response, WasmMsg}; use cw721_base::Action; use mars_account_nft::{msg::ExecuteMsg as NftExecuteMsg, nft_config::NftConfigUpdates}; use mars_owner::OwnerUpdate; @@ -15,6 +15,7 @@ use crate::{ pub fn update_config( deps: DepsMut, + env: Env, info: MessageInfo, updates: ConfigUpdates, ) -> ContractResult { @@ -46,7 +47,7 @@ pub fn update_config( } if let Some(unchecked) = updates.red_bank { - RED_BANK.save(deps.storage, &unchecked.check(deps.api)?)?; + RED_BANK.save(deps.storage, &unchecked.check(deps.api, env.contract.address)?)?; response = response.add_attribute("key", "red_bank").add_attribute("value", unchecked.address()); } diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 71a6d6bd9..083f077ea 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -16,10 +16,7 @@ use mars_rover::{ use mars_rover_health_types::AccountKind; use crate::{ - state::{ - ACCOUNT_KINDS, ACCOUNT_NFT, COIN_BALANCES, LENT_SHARES, PARAMS, RED_BANK, - TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, - }, + state::{ACCOUNT_KINDS, ACCOUNT_NFT, COIN_BALANCES, PARAMS, RED_BANK, TOTAL_DEBT_SHARES}, update_coin_balances::query_balance, }; @@ -91,34 +88,6 @@ pub fn decrement_coin_balance( Ok(new_value) } -pub fn increment_lent_shares( - storage: &mut dyn Storage, - account_id: &str, - denom: &str, - shares: Uint128, -) -> ContractResult { - LENT_SHARES.update(storage, (account_id, denom), |value_opt| { - value_opt.unwrap_or_else(Uint128::zero).checked_add(shares).map_err(ContractError::Overflow) - }) -} - -pub fn decrement_lent_shares( - storage: &mut dyn Storage, - account_id: &str, - denom: &str, - shares: Uint128, -) -> ContractResult { - let path = LENT_SHARES.key((account_id, denom)); - let value_opt = path.may_load(storage)?; - let new_value = value_opt.unwrap_or_else(Uint128::zero).checked_sub(shares)?; - if new_value.is_zero() { - path.remove(storage); - } else { - path.save(storage, &new_value)?; - } - Ok(new_value) -} - pub fn update_balance_msg( querier: &QuerierWrapper, rover_addr: &Addr, @@ -172,18 +141,13 @@ pub fn update_balance_after_vault_liquidation_msg( })) } -pub fn debt_shares_to_amount( - deps: Deps, - rover_addr: &Addr, - denom: &str, - shares: Uint128, -) -> ContractResult { +pub fn debt_shares_to_amount(deps: Deps, denom: &str, shares: Uint128) -> ContractResult { // total shares of debt issued for denom let total_debt_shares = TOTAL_DEBT_SHARES.load(deps.storage, denom).unwrap_or(Uint128::zero()); // total rover debt amount in Redbank for asset let red_bank = RED_BANK.load(deps.storage)?; - let total_debt_amount = red_bank.query_debt(&deps.querier, rover_addr, denom)?; + let total_debt_amount = red_bank.query_debt(&deps.querier, denom)?; // Amount of debt for token's position. Rounded up to favor participants in the debt pool. let amount = total_debt_amount.checked_mul_ceil((shares, total_debt_shares))?; @@ -194,30 +158,6 @@ pub fn debt_shares_to_amount( }) } -pub fn lent_shares_to_amount( - deps: Deps, - rover_addr: &Addr, - denom: &str, - shares: Uint128, -) -> ContractResult { - // total shares of lent issued for denom - let total_lent_shares = TOTAL_LENT_SHARES.load(deps.storage, denom).unwrap_or(Uint128::zero()); - - // total rover lent amount in Redbank for asset - let red_bank = RED_BANK.load(deps.storage)?; - let total_lent_amount = red_bank.query_lent(&deps.querier, rover_addr, denom)?; - - // amount of lent for account's position - // NOTE: Given the nature of integers, the lent amount is rounded down. - // This means the account donates the fractional unit to the lending pool. - let amount = total_lent_amount.checked_multiply_ratio(shares, total_lent_shares)?; - - Ok(Coin { - denom: denom.to_string(), - amount, - }) -} - pub trait IntoUint128 { fn uint128(&self) -> Uint128; } diff --git a/contracts/credit-manager/src/vault/liquidate_vault.rs b/contracts/credit-manager/src/vault/liquidate_vault.rs index 03f2c8c2d..0084d91d7 100644 --- a/contracts/credit-manager/src/vault/liquidate_vault.rs +++ b/contracts/credit-manager/src/vault/liquidate_vault.rs @@ -77,7 +77,6 @@ fn liquidate_unlocked( let (debt, liquidator_request, liquidatee_request) = calculate_vault_liquidation( &deps, - &env, liquidatee_account_id, &debt_coin, &request_vault, @@ -129,7 +128,6 @@ fn liquidate_unlocked( /// values to be determined. Afterward, the final amount is converted back into vault coins. fn calculate_vault_liquidation( deps: &DepsMut, - env: &Env, liquidatee_account_id: &str, debt_coin: &Coin, request_vault: &Vault, @@ -139,7 +137,6 @@ fn calculate_vault_liquidation( let total_underlying = request_vault.query_preview_redeem(&deps.querier, amount)?; let (debt, mut liquidator_request, mut liquidatee_request) = calculate_liquidation( deps, - env, liquidatee_account_id, debt_coin, &vault_info.base_token, @@ -167,7 +164,6 @@ fn liquidate_unlocking( let (debt, liquidator_request, liquidatee_request) = calculate_liquidation( &deps, - &env, liquidatee_account_id, &debt_coin, &vault_info.base_token, @@ -243,7 +239,6 @@ fn liquidate_locked( let (debt, liquidator_request, liquidatee_request) = calculate_vault_liquidation( &deps, - &env, liquidatee_account_id, &debt_coin, &request_vault, diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index b828e1349..0431fab44 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -47,7 +47,7 @@ use mars_rover::{ health::HealthContract, oracle::{Oracle, OracleBase, OracleUnchecked}, params::Params, - red_bank::RedBankBase, + red_bank::RedBankUnchecked, swap::{ EstimateExactInSwapResponse, InstantiateMsg as SwapperInstantiateMsg, QueryMsg::EstimateExactInSwap, Swapper, SwapperBase, @@ -59,8 +59,8 @@ use mars_rover::{ execute::{Action, CallbackMsg}, instantiate::ConfigUpdates, query::{ - CoinBalanceResponseItem, ConfigResponse, DebtShares, LentShares, Positions, - SharesResponseItem, VaultPositionResponseItem, VaultUtilizationResponse, + CoinBalanceResponseItem, ConfigResponse, DebtShares, Positions, SharesResponseItem, + VaultPositionResponseItem, VaultUtilizationResponse, }, ExecuteMsg, InstantiateMsg, QueryMsg, QueryMsg::{EstimateProvideLiquidity, VaultPositionValue}, @@ -96,7 +96,7 @@ pub struct MockEnvBuilder { pub coin_params: Option>, pub oracle: Option, pub params: Option, - pub red_bank: Option>, + pub red_bank: Option, pub deploy_nft_contract: bool, pub set_nft_contract_minter: bool, pub accounts_to_fund: Vec, @@ -507,40 +507,6 @@ impl MockEnv { .unwrap() } - pub fn query_all_lent_shares( - &self, - start_after: Option<(String, String)>, - limit: Option, - ) -> Vec { - self.app - .wrap() - .query_wasm_smart( - self.rover.clone(), - &QueryMsg::AllLentShares { - start_after, - limit, - }, - ) - .unwrap() - } - - pub fn query_all_total_lent_shares( - &self, - start_after: Option, - limit: Option, - ) -> Vec { - self.app - .wrap() - .query_wasm_smart( - self.rover.clone(), - &QueryMsg::AllTotalLentShares { - start_after, - limit, - }, - ) - .unwrap() - } - pub fn query_total_debt_shares(&self, denom: &str) -> DebtShares { self.app .wrap() @@ -548,13 +514,6 @@ impl MockEnv { .unwrap() } - pub fn query_total_lent_shares(&self, denom: &str) -> LentShares { - self.app - .wrap() - .query_wasm_smart(self.rover.clone(), &QueryMsg::TotalLentShares(denom.to_string())) - .unwrap() - } - pub fn query_red_bank_debt(&self, denom: &str) -> UserDebtResponse { let config = self.query_config(); self.app @@ -569,7 +528,11 @@ impl MockEnv { .unwrap() } - pub fn query_red_bank_collateral(&self, denom: &str) -> UserCollateralResponse { + pub fn query_red_bank_collateral( + &self, + account_id: &str, + denom: &str, + ) -> UserCollateralResponse { let config = self.query_config(); self.app .wrap() @@ -577,6 +540,7 @@ impl MockEnv { config.red_bank, &UserCollateral { user: self.rover.to_string(), + account_id: Some(account_id.to_string()), denom: denom.into(), }, ) @@ -809,7 +773,7 @@ impl MockEnvBuilder { fn get_rover(&mut self) -> AnyResult { let code_id = self.app.store_code(mock_rover_contract()); - let red_bank = self.get_red_bank().into(); + let red_bank = self.get_red_bank(); let swapper = self.deploy_swapper().into(); let max_unlocking_positions = self.get_max_unlocking_positions(); @@ -971,15 +935,15 @@ impl MockEnvBuilder { .unwrap(); } - fn get_red_bank(&mut self) -> RedBankBase { + fn get_red_bank(&mut self) -> RedBankUnchecked { if self.red_bank.is_none() { - let addr = self.deploy_red_bank(); - self.red_bank = Some(addr); + let rb = self.deploy_red_bank(); + self.red_bank = Some(rb); } self.red_bank.clone().unwrap() } - pub fn deploy_red_bank(&mut self) -> RedBankBase { + pub fn deploy_red_bank(&mut self) -> RedBankUnchecked { let contract_code_id = self.app.store_code(mock_red_bank_contract()); let addr = self .app @@ -1007,7 +971,7 @@ impl MockEnvBuilder { .unwrap(); } - RedBankBase::new(addr) + RedBankUnchecked::new(addr.to_string()) } fn deploy_vault(&mut self, vault: &VaultTestInfo) -> Addr { @@ -1178,7 +1142,7 @@ impl MockEnvBuilder { } pub fn red_bank(&mut self, red_bank: &str) -> &mut Self { - self.red_bank = Some(RedBankBase::new(Addr::unchecked(red_bank))); + self.red_bank = Some(RedBankUnchecked::new(red_bank.to_string())); self } diff --git a/contracts/credit-manager/tests/helpers/utils.rs b/contracts/credit-manager/tests/helpers/utils.rs index a87a16705..967ea80df 100644 --- a/contracts/credit-manager/tests/helpers/utils.rs +++ b/contracts/credit-manager/tests/helpers/utils.rs @@ -1,5 +1,5 @@ use cosmwasm_std::Coin; -use mars_rover::msg::query::{DebtAmount, LentAmount}; +use mars_rover::msg::query::DebtAmount; pub fn get_coin(denom: &str, coins: &[Coin]) -> Coin { coins.iter().find(|cv| cv.denom == denom).unwrap().clone() @@ -8,7 +8,3 @@ pub fn get_coin(denom: &str, coins: &[Coin]) -> Coin { pub fn get_debt(denom: &str, coins: &[DebtAmount]) -> DebtAmount { coins.iter().find(|coin| coin.denom.as_str() == denom).unwrap().clone() } - -pub fn get_lent(denom: &str, coins: &[LentAmount]) -> LentAmount { - coins.iter().find(|coin| coin.denom.as_str() == denom).unwrap().clone() -} diff --git a/contracts/credit-manager/tests/test_enumerate_lent_shares.rs b/contracts/credit-manager/tests/test_enumerate_lent_shares.rs deleted file mode 100644 index b3c90c8c2..000000000 --- a/contracts/credit-manager/tests/test_enumerate_lent_shares.rs +++ /dev/null @@ -1,220 +0,0 @@ -use cosmwasm_std::{coin, Addr}; -use mars_credit_manager::lend::DEFAULT_LENT_SHARES_PER_COIN; -use mars_rover::msg::{ - execute::{Action, ActionAmount, ActionCoin}, - query::SharesResponseItem, -}; - -use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; - -pub mod helpers; - -#[test] -fn pagination_on_all_lent_shares_query_works() { - let user_a = Addr::unchecked("user_a"); - let user_b = Addr::unchecked("user_b"); - let user_c = Addr::unchecked("user_c"); - - let user_a_coins = vec![ - coin(1, "coin_1"), - coin(1, "coin_2"), - coin(1, "coin_3"), - coin(1, "coin_4"), - coin(1, "coin_5"), - coin(1, "coin_6"), - coin(1, "coin_7"), - coin(1, "coin_8"), - coin(1, "coin_9"), - coin(1, "coin_10"), - coin(1, "coin_11"), - coin(1, "coin_12"), - coin(1, "coin_13"), - coin(1, "coin_14"), - ]; - - let user_b_coins = vec![ - coin(1, "coin_15"), - coin(1, "coin_16"), - coin(1, "coin_17"), - coin(1, "coin_18"), - coin(1, "coin_19"), - coin(1, "coin_20"), - coin(1, "coin_21"), - coin(1, "coin_22"), - coin(1, "coin_23"), - coin(1, "coin_24"), - ]; - - let user_c_coins = vec![ - coin(1, "coin_25"), - coin(1, "coin_26"), - coin(1, "coin_27"), - coin(1, "coin_28"), - coin(1, "coin_29"), - coin(1, "coin_30"), - coin(1, "coin_31"), - coin(1, "coin_32"), - ]; - - let mut mock = MockEnv::new() - .fund_account(AccountToFund { - addr: user_a.clone(), - funds: user_a_coins.clone(), - }) - .fund_account(AccountToFund { - addr: user_b.clone(), - funds: user_b_coins.clone(), - }) - .fund_account(AccountToFund { - addr: user_c.clone(), - funds: user_c_coins.clone(), - }) - .set_params(&build_mock_coin_infos(32)) - .build() - .unwrap(); - - let account_id_a = mock.create_credit_account(&user_a).unwrap(); - mock.update_credit_account( - &account_id_a, - &user_a, - user_a_coins - .iter() - .flat_map(|c| { - vec![ - Action::Deposit(c.clone()), - Action::Lend(ActionCoin { - denom: c.denom.clone(), - amount: ActionAmount::Exact(c.amount), - }), - ] - }) - .collect::>(), - &user_a_coins, - ) - .unwrap(); - - let account_id_b = mock.create_credit_account(&user_b).unwrap(); - mock.update_credit_account( - &account_id_b, - &user_b, - user_b_coins - .iter() - .flat_map(|c| { - vec![ - Action::Deposit(c.clone()), - Action::Lend(ActionCoin { - denom: c.denom.clone(), - amount: ActionAmount::Exact(c.amount), - }), - ] - }) - .collect::>(), - &user_b_coins, - ) - .unwrap(); - - let account_id_c = mock.create_credit_account(&user_c).unwrap(); - mock.update_credit_account( - &account_id_c, - &user_c, - user_c_coins - .iter() - .flat_map(|c| { - vec![ - Action::Deposit(c.clone()), - Action::Lend(ActionCoin { - denom: c.denom.clone(), - amount: ActionAmount::Exact(c.amount), - }), - ] - }) - .collect::>(), - &user_c_coins, - ) - .unwrap(); - - let all_lent_shares_res = mock.query_all_lent_shares(None, Some(58_u32)); - - // Assert maximum is observed - assert_eq!(all_lent_shares_res.len(), 30); - - let all_lent_shares_res = mock.query_all_lent_shares(None, Some(2_u32)); - - // Assert limit request is observed - assert_eq!(all_lent_shares_res.len(), 2); - - let all_lent_shares_res_a = mock.query_all_lent_shares(None, None); - - let SharesResponseItem { - account_id, - denom, - .. - } = all_lent_shares_res_a.last().unwrap().clone(); - let all_lent_shares_res_b = mock.query_all_lent_shares(Some((account_id, denom)), None); - - let SharesResponseItem { - account_id, - denom, - .. - } = all_lent_shares_res_b.last().unwrap().clone(); - let all_lent_shares_res_c = mock.query_all_lent_shares(Some((account_id, denom)), None); - - let SharesResponseItem { - account_id, - denom, - .. - } = all_lent_shares_res_c.last().unwrap().clone(); - let all_lent_shares_res_d = mock.query_all_lent_shares(Some((account_id, denom)), None); - - // Assert default is observed - assert_eq!(all_lent_shares_res_a.len(), 10); - assert_eq!(all_lent_shares_res_b.len(), 10); - assert_eq!(all_lent_shares_res_c.len(), 10); - - assert_eq!(all_lent_shares_res_d.len(), 2); - - let combined_res: Vec = all_lent_shares_res_a - .iter() - .cloned() - .chain(all_lent_shares_res_b.iter().cloned()) - .chain(all_lent_shares_res_c.iter().cloned()) - .chain(all_lent_shares_res_d.iter().cloned()) - .collect(); - - let user_a_response_items = user_a_coins - .iter() - .map(|coin| SharesResponseItem { - account_id: account_id_a.clone(), - denom: coin.denom.clone(), - shares: DEFAULT_LENT_SHARES_PER_COIN, - }) - .collect::>(); - - let user_b_response_items = user_b_coins - .iter() - .map(|coin| SharesResponseItem { - account_id: account_id_b.clone(), - denom: coin.denom.clone(), - shares: DEFAULT_LENT_SHARES_PER_COIN, - }) - .collect::>(); - - let user_c_response_items = user_c_coins - .iter() - .map(|coin| SharesResponseItem { - account_id: account_id_c.clone(), - denom: coin.denom.clone(), - shares: DEFAULT_LENT_SHARES_PER_COIN, - }) - .collect::>(); - - let combined_starting_vals: Vec = user_a_response_items - .iter() - .cloned() - .chain(user_b_response_items) - .chain(user_c_response_items) - .collect(); - - assert_eq!(combined_res.len(), combined_starting_vals.len()); - assert!(combined_starting_vals.iter().all(|item| combined_res.contains(item))); -} diff --git a/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs b/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs deleted file mode 100644 index 2bbd3fc49..000000000 --- a/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs +++ /dev/null @@ -1,214 +0,0 @@ -use cosmwasm_std::{coin, Addr}; -use mars_credit_manager::lend::DEFAULT_LENT_SHARES_PER_COIN; -use mars_rover::msg::{ - execute::{Action, ActionAmount, ActionCoin}, - query::LentShares, -}; - -use crate::helpers::{build_mock_coin_infos, AccountToFund, MockEnv}; - -pub mod helpers; - -#[test] -fn pagination_on_all_total_lent_shares_query_works() { - let user_a = Addr::unchecked("user_a"); - let user_b = Addr::unchecked("user_b"); - let user_c = Addr::unchecked("user_c"); - - let user_a_coins = vec![ - coin(1, "coin_1"), - coin(1, "coin_2"), - coin(1, "coin_3"), - coin(1, "coin_4"), - coin(1, "coin_5"), - coin(1, "coin_6"), - coin(1, "coin_7"), - coin(1, "coin_8"), - coin(1, "coin_9"), - coin(1, "coin_10"), - coin(1, "coin_11"), - coin(1, "coin_12"), - coin(1, "coin_13"), - coin(1, "coin_14"), - ]; - - let user_b_coins = vec![ - coin(1, "coin_15"), - coin(1, "coin_16"), - coin(1, "coin_17"), - coin(1, "coin_18"), - coin(1, "coin_19"), - coin(1, "coin_20"), - coin(1, "coin_21"), - coin(1, "coin_22"), - coin(1, "coin_23"), - coin(1, "coin_24"), - ]; - - let user_c_coins = vec![ - coin(1, "coin_25"), - coin(1, "coin_26"), - coin(1, "coin_27"), - coin(1, "coin_28"), - coin(1, "coin_29"), - coin(1, "coin_30"), - coin(1, "coin_31"), - coin(1, "coin_32"), - ]; - - let mut mock = MockEnv::new() - .fund_account(AccountToFund { - addr: user_a.clone(), - funds: user_a_coins.clone(), - }) - .fund_account(AccountToFund { - addr: user_b.clone(), - funds: user_b_coins.clone(), - }) - .fund_account(AccountToFund { - addr: user_c.clone(), - funds: user_c_coins.clone(), - }) - .set_params(&build_mock_coin_infos(32)) - .build() - .unwrap(); - - let account_id_a = mock.create_credit_account(&user_a).unwrap(); - mock.update_credit_account( - &account_id_a, - &user_a, - user_a_coins - .iter() - .flat_map(|c| { - vec![ - Action::Deposit(c.clone()), - Action::Lend(ActionCoin { - denom: c.denom.clone(), - amount: ActionAmount::Exact(c.amount), - }), - ] - }) - .collect::>(), - &user_a_coins, - ) - .unwrap(); - - let account_id_b = mock.create_credit_account(&user_b).unwrap(); - mock.update_credit_account( - &account_id_b, - &user_b, - user_b_coins - .iter() - .flat_map(|c| { - vec![ - Action::Deposit(c.clone()), - Action::Lend(ActionCoin { - denom: c.denom.clone(), - amount: ActionAmount::Exact(c.amount), - }), - ] - }) - .collect::>(), - &user_b_coins, - ) - .unwrap(); - - let account_id_c = mock.create_credit_account(&user_c).unwrap(); - mock.update_credit_account( - &account_id_c, - &user_c, - user_c_coins - .iter() - .flat_map(|c| { - vec![ - Action::Deposit(c.clone()), - Action::Lend(ActionCoin { - denom: c.denom.clone(), - amount: ActionAmount::Exact(c.amount), - }), - ] - }) - .collect::>(), - &user_c_coins, - ) - .unwrap(); - - let all_total_lent_shares_res = mock.query_all_total_lent_shares(None, Some(58_u32)); - - // Assert maximum is observed - assert_eq!(all_total_lent_shares_res.len(), 30); - - let all_total_lent_shares_res = mock.query_all_total_lent_shares(None, Some(2_u32)); - - // Assert limit request is observed - assert_eq!(all_total_lent_shares_res.len(), 2); - - let all_total_lent_shares_res_a = mock.query_all_total_lent_shares(None, None); - - let LentShares { - denom, - .. - } = all_total_lent_shares_res_a.last().unwrap().clone(); - let all_total_lent_shares_res_b = mock.query_all_total_lent_shares(Some(denom), None); - - let LentShares { - denom, - .. - } = all_total_lent_shares_res_b.last().unwrap().clone(); - let all_total_lent_shares_res_c = mock.query_all_total_lent_shares(Some(denom), None); - - let LentShares { - denom, - .. - } = all_total_lent_shares_res_c.last().unwrap().clone(); - let all_total_lent_shares_res_d = mock.query_all_total_lent_shares(Some(denom), None); - - // Assert default is observed - assert_eq!(all_total_lent_shares_res_a.len(), 10); - assert_eq!(all_total_lent_shares_res_b.len(), 10); - assert_eq!(all_total_lent_shares_res_c.len(), 10); - - assert_eq!(all_total_lent_shares_res_d.len(), 2); - - let combined_res: Vec = all_total_lent_shares_res_a - .iter() - .cloned() - .chain(all_total_lent_shares_res_b.iter().cloned()) - .chain(all_total_lent_shares_res_c.iter().cloned()) - .chain(all_total_lent_shares_res_d.iter().cloned()) - .collect(); - - let user_a_response_items = user_a_coins - .iter() - .map(|coin| LentShares { - denom: coin.denom.clone(), - shares: DEFAULT_LENT_SHARES_PER_COIN, - }) - .collect::>(); - - let user_b_response_items = user_b_coins - .iter() - .map(|coin| LentShares { - denom: coin.denom.clone(), - shares: DEFAULT_LENT_SHARES_PER_COIN, - }) - .collect::>(); - - let user_c_response_items = user_c_coins - .iter() - .map(|coin| LentShares { - denom: coin.denom.clone(), - shares: DEFAULT_LENT_SHARES_PER_COIN, - }) - .collect::>(); - - let combined_starting_vals: Vec = user_a_response_items - .iter() - .cloned() - .chain(user_b_response_items) - .chain(user_c_response_items) - .collect(); - - assert_eq!(combined_res.len(), combined_starting_vals.len()); - assert!(combined_starting_vals.iter().all(|item| combined_res.contains(item))); -} diff --git a/contracts/credit-manager/tests/test_lend.rs b/contracts/credit-manager/tests/test_lend.rs index 624d3c1ac..6904039e1 100644 --- a/contracts/credit-manager/tests/test_lend.rs +++ b/contracts/credit-manager/tests/test_lend.rs @@ -1,7 +1,6 @@ -use std::ops::{Add, Mul}; +use std::ops::Add; use cosmwasm_std::{coin, coins, Addr, OverflowError, OverflowOperation, Uint128}; -use mars_credit_manager::lend::DEFAULT_LENT_SHARES_PER_COIN; use mars_rover::{ error::ContractError, msg::execute::{ @@ -128,7 +127,7 @@ fn raises_when_attempting_to_lend_account_balance_with_no_funds() { assert_eq!(position.deposits.len(), 0); assert_eq!(position.lends.len(), 0); - let red_bank_collateral = mock.query_red_bank_collateral(&coin_info.denom); + let red_bank_collateral = mock.query_red_bank_collateral(&account_id_a, &coin_info.denom); assert_eq!(red_bank_collateral.amount, Uint128::zero()); let res = mock.update_credit_account( @@ -178,7 +177,7 @@ fn successful_lend() { ) .unwrap(); - let red_bank_collateral = mock.query_red_bank_collateral(&coin_info.denom); + let red_bank_collateral = mock.query_red_bank_collateral(&account_id_a, &coin_info.denom); assert_eq!(red_bank_collateral.amount, Uint128::zero()); mock.update_credit_account( @@ -202,12 +201,6 @@ fn successful_lend() { assert_eq!(lent_res.denom, coin_info.denom); let lent_amount = Uint128::new(50) + Uint128::new(1); // simulated yield assert_eq!(lent_res.amount, lent_amount); - assert_eq!(lent_res.shares, Uint128::new(50).mul(DEFAULT_LENT_SHARES_PER_COIN)); - - // Assert total lent positions increased - let total = mock.query_total_lent_shares(&coin_info.denom); - assert_eq!(total.denom, coin_info.denom); - assert_eq!(total.shares, DEFAULT_LENT_SHARES_PER_COIN.mul(Uint128::new(50))); // Assert Rover has indeed sent those tokens to Red Bank let balance = mock.query_balance(&mock.rover, &coin_info.denom); @@ -219,7 +212,7 @@ fn successful_lend() { assert_eq!(balance.amount, DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(50))); // Assert Rover's collateral balance in Red bank - let red_bank_collateral = mock.query_red_bank_collateral(&coin_info.denom); + let red_bank_collateral = mock.query_red_bank_collateral(&account_id_a, &coin_info.denom); assert_eq!(red_bank_collateral.amount, lent_amount); // Second user comes and performs a lend @@ -231,53 +224,42 @@ fn successful_lend() { &[coin(300, coin_info.denom)], ) .unwrap(); - - // Assert lend position shares amount is proportionally right given existing participant in pool - let position = mock.query_positions(&account_id_b); - let expected_shares = total.shares.multiply_ratio(Uint128::new(50), red_bank_collateral.amount); - assert_eq!(position.lends.first().unwrap().shares, expected_shares); } #[test] fn successful_account_balance_lend() { let coin_info = uosmo_info(); - - let user_a = Addr::unchecked("user_a"); - let user_b = Addr::unchecked("user_b"); + let user = Addr::unchecked("user"); let mut mock = MockEnv::new() .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { - addr: user_a.clone(), - funds: coins(300, coin_info.denom.clone()), - }) - .fund_account(AccountToFund { - addr: user_b, + addr: user.clone(), funds: coins(300, coin_info.denom.clone()), }) .build() .unwrap(); - let account_id_a = mock.create_credit_account(&user_a).unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); - let position = mock.query_positions(&account_id_a); + let position = mock.query_positions(&account_id); assert_eq!(position.deposits.len(), 0); assert_eq!(position.lends.len(), 0); mock.update_credit_account( - &account_id_a, - &user_a, + &account_id, + &user, vec![Deposit(coin_info.to_coin(300))], &[coin(300, coin_info.denom.clone())], ) .unwrap(); - let red_bank_collateral = mock.query_red_bank_collateral(&coin_info.denom); + let red_bank_collateral = mock.query_red_bank_collateral(&account_id, &coin_info.denom); assert_eq!(red_bank_collateral.amount, Uint128::zero()); mock.update_credit_account( - &account_id_a, - &user_a, + &account_id, + &user, vec![Lend(ActionCoin { denom: "uosmo".to_string(), amount: ActionAmount::AccountBalance, @@ -287,7 +269,7 @@ fn successful_account_balance_lend() { .unwrap(); // Assert deposits decreased - let position = mock.query_positions(&account_id_a); + let position = mock.query_positions(&account_id); assert_eq!(position.deposits.len(), 0); // Assert lend position amount increased @@ -296,12 +278,6 @@ fn successful_account_balance_lend() { assert_eq!(lent_res.denom, coin_info.denom); let lent_amount = Uint128::new(300) + Uint128::new(1); // account balance + simulated yield assert_eq!(lent_res.amount, lent_amount); - assert_eq!(lent_res.shares, Uint128::new(300).mul(DEFAULT_LENT_SHARES_PER_COIN)); - - // Assert total lent positions increased - let total = mock.query_total_lent_shares(&coin_info.denom); - assert_eq!(total.denom, coin_info.denom); - assert_eq!(total.shares, DEFAULT_LENT_SHARES_PER_COIN.mul(Uint128::new(300))); // Assert Rover has indeed sent those tokens to Red Bank let balance = mock.query_balance(&mock.rover, &coin_info.denom); @@ -313,6 +289,6 @@ fn successful_account_balance_lend() { assert_eq!(balance.amount, DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(300))); // Assert Rover's collateral balance in Red bank - let red_bank_collateral = mock.query_red_bank_collateral(&coin_info.denom); + let red_bank_collateral = mock.query_red_bank_collateral(&account_id, &coin_info.denom); assert_eq!(red_bank_collateral.amount, lent_amount); } diff --git a/contracts/credit-manager/tests/test_liquidate_lend.rs b/contracts/credit-manager/tests/test_liquidate_lend.rs index 2fa88008a..2fe415406 100644 --- a/contracts/credit-manager/tests/test_liquidate_lend.rs +++ b/contracts/credit-manager/tests/test_liquidate_lend.rs @@ -4,15 +4,14 @@ use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ error::{ContractError, ContractError::NotLiquidatable}, msg::execute::{ - Action::{Borrow, Deposit, Lend, Liquidate, Reclaim}, - ActionAmount, ActionCoin, LiquidateRequest, + Action::{Borrow, Deposit, Lend, Liquidate}, + LiquidateRequest, }, }; use mars_rover_health_types::AccountKind; use crate::helpers::{ - assert_err, get_coin, get_debt, get_lent, uatom_info, ujake_info, uosmo_info, AccountToFund, - MockEnv, + assert_err, get_coin, get_debt, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, }; pub mod helpers; @@ -235,28 +234,26 @@ fn lent_position_partially_liquidated() { assert_eq!(atom_debt.amount, Uint128::new(956)); assert_eq!(position.lends.len(), 1); - let osmo_lent = get_lent("uosmo", &position.lends); + let osmo_lent = get_coin("uosmo", &position.lends); assert_eq!(osmo_lent.amount, Uint128::new(39)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.deposits.len(), 0); + assert_eq!(position.lends.len(), 0); assert_eq!(position.debts.len(), 0); - - assert_eq!(position.lends.len(), 1); - let osmo_lent = get_lent("uosmo", &position.lends); - assert_eq!(osmo_lent.amount, Uint128::new(403)); + assert_eq!(position.deposits.len(), 1); + let osmo_deposited = get_coin("uosmo", &position.deposits); + assert_eq!(osmo_deposited.amount, Uint128::new(404)); // Assert rewards-collector's new position let rewards_collector_acc_id = mock.query_rewards_collector_account(); let position = mock.query_positions(&rewards_collector_acc_id); - assert_eq!(position.deposits.len(), 0); + assert_eq!(position.deposits.len(), 1); + let rc_osmo_deposited = get_coin("uosmo", &position.deposits); + assert_eq!(rc_osmo_deposited.amount, Uint128::new(8)); + assert_eq!(position.lends.len(), 0); assert_eq!(position.debts.len(), 0); - assert_eq!(position.lends.len(), 1); - let rc_osmo_lent = get_lent("uosmo", &position.lends); - assert_eq!(rc_osmo_lent.amount, Uint128::new(8)); - // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); @@ -354,149 +351,27 @@ fn lent_position_fully_liquidated() { // FIXME: dust because of roundings, is it possible to avoid it? assert_eq!(position.lends.len(), 1); - let osmo_balance = get_lent("uosmo", &position.lends); + let osmo_balance = get_coin("uosmo", &position.lends); assert_eq!(osmo_balance.amount, Uint128::new(1)); // Assert liquidator's new position let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.deposits.len(), 1); - let atom_balance = get_coin("uatom", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(11)); - - assert_eq!(position.debts.len(), 0); - - assert_eq!(position.lends.len(), 1); - let osmo_lent = get_lent("uosmo", &position.lends); - assert_eq!(osmo_lent.amount, Uint128::new(106)); - - // Assert rewards-collector's new position - let rewards_collector_acc_id = mock.query_rewards_collector_account(); - let position = mock.query_positions(&rewards_collector_acc_id); - assert_eq!(position.deposits.len(), 0); + assert_eq!(position.lends.len(), 0); assert_eq!(position.debts.len(), 0); - - assert_eq!(position.lends.len(), 1); - let rc_osmo_lent = get_lent("uosmo", &position.lends); - // FIXME: excel shows 2, simulated interest rate influence? - assert_eq!(rc_osmo_lent.amount, Uint128::new(1)); - - // Liq HF should improve - let account_kind = mock.query_account_kind(&liquidatee_account_id); - let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); - assert!(health.liquidatable); - assert!( - prev_health.liquidation_health_factor.unwrap() < health.liquidation_health_factor.unwrap() - ); -} - -#[test] -fn liquidate_with_reclaiming() { - let uosmo_info = uosmo_info(); - let uatom_info = uatom_info(); - - let liquidator = Addr::unchecked("liquidator"); - let liquidatee = Addr::unchecked("liquidatee"); - - let mut mock = MockEnv::new() - .target_health_factor(Decimal::from_atomics(12u128, 1).unwrap()) - .set_params(&[uosmo_info.clone(), uatom_info.clone()]) - .fund_account(AccountToFund { - addr: liquidatee.clone(), - funds: coins(3000, uosmo_info.denom.clone()), - }) - .fund_account(AccountToFund { - addr: liquidator.clone(), - funds: coins(3000, uatom_info.denom.clone()), - }) - .build() - .unwrap(); - - let liquidatee_account_id = mock.create_credit_account(&liquidatee).unwrap(); - - mock.update_credit_account( - &liquidatee_account_id, - &liquidatee, - vec![ - Deposit(uosmo_info.to_coin(3000)), - Borrow(uatom_info.to_coin(1000)), - Lend(uosmo_info.to_action_coin(1500)), - ], - &[uosmo_info.to_coin(3000)], - ) - .unwrap(); - - mock.price_change(CoinPrice { - pricing: ActionKind::Liquidation, - denom: uatom_info.denom.clone(), - price: Decimal::from_atomics(82u128, 1).unwrap(), - }); - - let prev_health = - mock.query_health(&liquidatee_account_id, AccountKind::Default, ActionKind::Liquidation); - assert!(prev_health.liquidatable); - assert_eq!(prev_health.total_collateral_value, Uint128::new(8950u128)); - assert_eq!(prev_health.total_debt_value, Uint128::new(8209u128)); - - let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); - - mock.update_credit_account( - &liquidator_account_id, - &liquidator, - vec![ - Deposit(uatom_info.to_coin(100)), - Liquidate { - liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: uatom_info.to_coin(100), - request: LiquidateRequest::Lend(uosmo_info.denom.clone()), - }, - Reclaim(ActionCoin { - denom: uosmo_info.denom, - amount: ActionAmount::AccountBalance, - }), - ], - &[uatom_info.to_coin(100)], - ) - .unwrap(); - - // Assert liquidatee's new position - let position = mock.query_positions(&liquidatee_account_id); assert_eq!(position.deposits.len(), 2); - let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(1500)); let atom_balance = get_coin("uatom", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(1000)); - - assert_eq!(position.debts.len(), 1); - let atom_debt = get_debt("uatom", &position.debts); - assert_eq!(atom_debt.amount, Uint128::new(960)); - - assert_eq!(position.lends.len(), 1); - let osmo_lent = get_lent("uosmo", &position.lends); - // FIXME: excel shows 37, simulated interest rate influence? - assert_eq!(osmo_lent.amount, Uint128::new(36)); - - // Assert liquidator's new position - let position = mock.query_positions(&liquidator_account_id); - assert_eq!(position.deposits.len(), 2); - let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(1435)); - let atom_balance = get_coin("uatom", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(59)); - - assert_eq!(position.debts.len(), 0); - - assert_eq!(position.lends.len(), 0); + assert_eq!(atom_balance.amount, Uint128::new(11)); + let osmo_deposit = get_coin("uosmo", &position.deposits); + assert_eq!(osmo_deposit.amount, Uint128::new(107)); // Assert rewards-collector's new position let rewards_collector_acc_id = mock.query_rewards_collector_account(); let position = mock.query_positions(&rewards_collector_acc_id); - assert_eq!(position.deposits.len(), 0); + assert_eq!(position.lends.len(), 0); assert_eq!(position.debts.len(), 0); - - assert_eq!(position.lends.len(), 1); - let rc_osmo_lent = get_lent("uosmo", &position.lends); - // FIXME: excel shows 28, simulated interest rate influence? - assert_eq!(rc_osmo_lent.amount, Uint128::new(27)); + assert_eq!(position.deposits.len(), 1); + let rc_osmo_lent = get_coin("uosmo", &position.deposits); + assert_eq!(rc_osmo_lent.amount, Uint128::new(2)); // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index 0150a5d1e..7c5aaf089 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -7,7 +7,7 @@ use mars_red_bank_types::red_bank; use crate::{ execute::{borrow, deposit, repay, withdraw}, - query::{query_collateral, query_debt}, + query::{query_collateral, query_collaterals, query_debt}, }; #[cfg_attr(not(feature = "library"), entry_point)] @@ -37,13 +37,14 @@ pub fn execute( .. } => repay(deps, info), red_bank::ExecuteMsg::Deposit { - .. - } => deposit(deps, info), + account_id, + } => deposit(deps, info, account_id), red_bank::ExecuteMsg::Withdraw { denom, amount, + account_id, .. - } => withdraw(deps, info, &denom, &amount), + } => withdraw(deps, info, &denom, &amount, account_id), _ => unimplemented!("Msg not supported!"), } } @@ -57,8 +58,14 @@ pub fn query(deps: Deps, _env: Env, msg: red_bank::QueryMsg) -> StdResult to_binary(&query_debt(deps, user, denom)?), red_bank::QueryMsg::UserCollateral { user, + account_id, denom, - } => to_binary(&query_collateral(deps, user, denom)?), + } => to_binary(&query_collateral(deps, user, account_id, denom)?), + red_bank::QueryMsg::UserCollaterals { + user, + account_id, + .. + } => to_binary(&query_collaterals(deps, user, account_id)?), _ => unimplemented!("Query not supported!"), } } diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs index 29ab3d054..de176c45b 100644 --- a/contracts/mock-red-bank/src/execute.rs +++ b/contracts/mock-red-bank/src/execute.rs @@ -4,8 +4,8 @@ use cosmwasm_std::{ use cw_utils::one_coin; use crate::{ - helpers::{load_collateral_amount, load_debt_amount, load_lent_amount}, - state::{COLLATERAL_AMOUNT, DEBT_AMOUNT}, + helpers::{load_collateral_amount, load_debt_amount}, + state::{COLLATERAL_AMOUNT, COLLATERAL_DENOMS, DEBT_AMOUNT}, }; pub fn borrow( @@ -44,17 +44,36 @@ pub fn repay(deps: DepsMut, info: MessageInfo) -> StdResult { Ok(Response::new()) } -pub fn deposit(deps: DepsMut, info: MessageInfo) -> StdResult { +pub fn deposit( + deps: DepsMut, + info: MessageInfo, + account_id: Option, +) -> StdResult { let to_deposit = one_coin(&info).map_err(|_| StdError::generic_err("Deposit coin reqs not met"))?; - let collateral_amount = load_collateral_amount(deps.storage, &info.sender, &to_deposit.denom)?; + let collateral_amount = load_collateral_amount( + deps.storage, + info.sender.as_str(), + &account_id.clone().unwrap_or_default(), + &to_deposit.denom, + )?; COLLATERAL_AMOUNT.save( deps.storage, - (info.sender, to_deposit.denom.clone()), + (info.sender.to_string(), account_id.clone().unwrap_or_default(), to_deposit.denom.clone()), &collateral_amount.checked_add(to_deposit.amount)?.checked_add(Uint128::new(1))?, // The extra unit is simulated accrued yield )?; + COLLATERAL_DENOMS.update( + deps.storage, + (info.sender.to_string(), account_id.clone().unwrap_or_default()), + |denoms_opt| -> StdResult<_> { + let mut denoms = denoms_opt.unwrap_or_default(); + denoms.push(to_deposit.denom.clone()); + Ok(denoms) + }, + )?; + Ok(Response::new()) } @@ -63,14 +82,33 @@ pub fn withdraw( info: MessageInfo, denom: &str, amount: &Option, + account_id: Option, ) -> StdResult { - let total_lent = load_lent_amount(deps.storage, &info.sender, denom)?; + let total_lent = load_collateral_amount( + deps.storage, + info.sender.as_str(), + &account_id.clone().unwrap_or_default(), + denom, + )?; let amount_to_reclaim = amount.unwrap_or(total_lent); + let new_amount = total_lent.checked_sub(amount_to_reclaim)?; COLLATERAL_AMOUNT.save( deps.storage, - (info.sender.clone(), denom.to_string()), - &total_lent.checked_sub(amount_to_reclaim)?, + (info.sender.to_string(), account_id.clone().unwrap_or_default(), denom.to_string()), + &new_amount, + )?; + + COLLATERAL_DENOMS.update( + deps.storage, + (info.sender.to_string(), account_id.clone().unwrap_or_default()), + |denoms_opt| -> StdResult<_> { + let mut denoms = denoms_opt.unwrap_or_default(); + if new_amount.is_zero() { + denoms.retain(|s| s != denom); + } + Ok(denoms) + }, )?; let transfer_msg = CosmosMsg::Bank(BankMsg::Send { diff --git a/contracts/mock-red-bank/src/helpers.rs b/contracts/mock-red-bank/src/helpers.rs index 58ecc5be0..47cfc2bee 100644 --- a/contracts/mock-red-bank/src/helpers.rs +++ b/contracts/mock-red-bank/src/helpers.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Addr, StdResult, Storage, Uint128}; -use crate::state::{COLLATERAL_AMOUNT, DEBT_AMOUNT}; +use crate::state::{COLLATERAL_AMOUNT, COLLATERAL_DENOMS, DEBT_AMOUNT}; pub fn load_debt_amount(storage: &dyn Storage, user: &Addr, denom: &str) -> StdResult { Ok(DEBT_AMOUNT @@ -10,16 +10,21 @@ pub fn load_debt_amount(storage: &dyn Storage, user: &Addr, denom: &str) -> StdR pub fn load_collateral_amount( storage: &dyn Storage, - user: &Addr, + addr: &str, + account_id: &str, denom: &str, ) -> StdResult { Ok(COLLATERAL_AMOUNT - .may_load(storage, (user.clone(), denom.to_string()))? + .may_load(storage, (addr.to_string(), account_id.to_string(), denom.to_string()))? .unwrap_or_else(Uint128::zero)) } -pub fn load_lent_amount(storage: &dyn Storage, user: &Addr, denom: &str) -> StdResult { - Ok(COLLATERAL_AMOUNT - .may_load(storage, (user.clone(), denom.to_string()))? - .unwrap_or_else(Uint128::zero)) +pub fn load_collateral_denoms( + storage: &dyn Storage, + addr: &str, + account_id: &str, +) -> StdResult> { + Ok(COLLATERAL_DENOMS + .may_load(storage, (addr.to_string(), account_id.to_string()))? + .unwrap_or(vec![])) } diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index 9302b1784..2bedf41d9 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Deps, StdResult, Uint128}; use mars_red_bank_types::red_bank::{UserCollateralResponse, UserDebtResponse}; -use crate::helpers::{load_collateral_amount, load_debt_amount}; +use crate::helpers::{load_collateral_amount, load_collateral_denoms, load_debt_amount}; pub fn query_debt(deps: Deps, user: String, denom: String) -> StdResult { let user_addr = deps.api.addr_validate(&user)?; @@ -17,10 +17,11 @@ pub fn query_debt(deps: Deps, user: String, denom: String) -> StdResult, denom: String, ) -> StdResult { - let user_addr = deps.api.addr_validate(&user)?; - let amount = load_collateral_amount(deps.storage, &user_addr, &denom)?; + let amount = + load_collateral_amount(deps.storage, &user, &account_id.unwrap_or_default(), &denom)?; Ok(UserCollateralResponse { denom, amount, @@ -28,3 +29,27 @@ pub fn query_collateral( enabled: true, }) } + +pub fn query_collaterals( + deps: Deps, + user: String, + account_id: Option, +) -> StdResult> { + load_collateral_denoms(deps.storage, &user, &account_id.clone().unwrap_or_default())? + .into_iter() + .map(|denom| { + load_collateral_amount( + deps.storage, + &user, + &account_id.clone().unwrap_or_default(), + &denom, + ) + .map(|amount| UserCollateralResponse { + denom, + amount_scaled: Default::default(), + amount, + enabled: true, + }) + }) + .collect() +} diff --git a/contracts/mock-red-bank/src/state.rs b/contracts/mock-red-bank/src/state.rs index edc30820a..29250f1df 100644 --- a/contracts/mock-red-bank/src/state.rs +++ b/contracts/mock-red-bank/src/state.rs @@ -3,5 +3,7 @@ use cw_storage_plus::Map; // Map<(DebtHolder, CoinDenom), AmountOfDebt> pub const DEBT_AMOUNT: Map<(Addr, String), Uint128> = Map::new("debt_amount"); -// Map<(CollateralHolder, CoinDenom), AmountOfCollateral> -pub const COLLATERAL_AMOUNT: Map<(Addr, String), Uint128> = Map::new("collateral_amount"); +// Map<(Addr, CmAccountId, CoinDenom), AmountOfCollateral> +pub const COLLATERAL_AMOUNT: Map<(String, String, String), Uint128> = Map::new("collateral_amount"); +// Map<(Addr, CmAccountId), Vec> : Used for tracking total denoms user deposited +pub const COLLATERAL_DENOMS: Map<(String, String), Vec> = Map::new("collateral_denoms"); diff --git a/packages/health-computer/Cargo.toml b/packages/health-computer/Cargo.toml index 32adde2dd..195928895 100644 --- a/packages/health-computer/Cargo.toml +++ b/packages/health-computer/Cargo.toml @@ -37,6 +37,3 @@ console_error_panic_hook = { version = "0.1.7", optional = true } [dev-dependencies] proptest = { workspace = true } -[profile.release] -opt-level = 's' -lto = true diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index 5ea76b779..0e567bbf9 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -6,7 +6,7 @@ use mars_params::types::{ asset::{AssetParams, CmSettings}, vault::VaultConfig, }; -use mars_rover::{msg::query::Positions, traits::Coins}; +use mars_rover::msg::query::Positions; use mars_rover_health_types::{ AccountKind, Health, HealthError::{ @@ -206,7 +206,7 @@ impl HealthComputer { fn total_collateral_value(&self) -> HealthResult { let deposits = self.coins_value(&self.positions.deposits)?; - let lends = self.coins_value(&self.positions.lends.to_coins())?; + let lends = self.coins_value(&self.positions.lends)?; let vaults = self.vaults_value()?; Ok(CollateralValue { diff --git a/packages/health-computer/tests/helpers/prop_test_strategies.rs b/packages/health-computer/tests/helpers/prop_test_strategies.rs index 41295b2d4..cd9e75723 100644 --- a/packages/health-computer/tests/helpers/prop_test_strategies.rs +++ b/packages/health-computer/tests/helpers/prop_test_strategies.rs @@ -11,7 +11,7 @@ use mars_rover::{ CoinValue, LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultPosition, VaultPositionAmount, VaultPositionValue, }, - msg::query::{DebtAmount, LentAmount, Positions}, + msg::query::{DebtAmount, Positions}, }; use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; use mars_rover_health_types::AccountKind; @@ -193,7 +193,7 @@ fn random_param_maps() -> impl Strategy { }) } -fn random_deposits(denoms_data: DenomsData) -> impl Strategy> { +fn random_coins(denoms_data: DenomsData) -> impl Strategy> { let denoms = denoms_data.params.keys().cloned().collect::>(); let denoms_len = denoms.len(); vec( @@ -228,24 +228,6 @@ fn random_debts(denoms_data: DenomsData) -> impl Strategy impl Strategy> { - let denoms = denoms_data.params.keys().cloned().collect::>(); - let denoms_len = denoms.len(); - vec( - (0..denoms_len, 1..=10000).prop_map(move |(index, amount)| { - let denom = denoms.get(index).unwrap().clone(); - let amount = Uint128::new(amount as u128); - - LentAmount { - denom, - shares: amount * Uint128::new(10), - amount, - } - }), - 0..denoms_len, - ) -} - fn random_vault_pos_amount() -> impl Strategy { prop_oneof![ random_vault_amount().prop_map(VaultPositionAmount::Unlocked), @@ -285,9 +267,9 @@ pub fn random_health_computer() -> impl Strategy { (random_param_maps()).prop_flat_map(|(denoms_data, vaults_data)| { ( random_account_kind(), - random_deposits(denoms_data.clone()), + random_coins(denoms_data.clone()), random_debts(denoms_data.clone()), - random_lends(denoms_data.clone()), + random_coins(denoms_data.clone()), random_vault_positions(vaults_data.clone()), ) .prop_map(move |(kind, deposits, debts, lends, vaults)| HealthComputer { diff --git a/packages/health-computer/tests/test_health_scenarios.rs b/packages/health-computer/tests/test_health_scenarios.rs index 0d8697ecb..241a4bf85 100644 --- a/packages/health-computer/tests/test_health_scenarios.rs +++ b/packages/health-computer/tests/test_health_scenarios.rs @@ -7,7 +7,7 @@ use mars_rover::{ CoinValue, LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultPosition, VaultPositionAmount, VaultPositionValue, VaultUnlockingPosition, }, - msg::query::{DebtAmount, LentAmount, Positions}, + msg::query::{DebtAmount, Positions}, }; use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; use mars_rover_health_types::AccountKind; @@ -1130,18 +1130,7 @@ fn lent_coins_used_as_collateral() { shares: Default::default(), amount: Uint128::new(3100), }], - lends: vec![ - LentAmount { - denom: udai.denom, - shares: Default::default(), - amount: Uint128::new(10), - }, - LentAmount { - denom: uluna.denom, - shares: Default::default(), - amount: Uint128::new(2), - }, - ], + lends: vec![coin(10, udai.denom), coin(2, uluna.denom)], vaults: vec![], }, denoms_data, @@ -1201,18 +1190,7 @@ fn allowed_lent_coins_influence_max_ltv() { shares: Default::default(), amount: Uint128::new(3100), }], - lends: vec![ - LentAmount { - denom: udai.denom, - shares: Default::default(), - amount: Uint128::new(10), - }, - LentAmount { - denom: uluna.denom, - shares: Default::default(), - amount: Uint128::new(2), - }, - ], + lends: vec![coin(10, udai.denom), coin(2, uluna.denom)], vaults: vec![], }, denoms_data, diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index 3af2bd9c1..7386f8e1f 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -6,30 +6,41 @@ use cosmwasm_std::{ use mars_red_bank_types::red_bank; #[cw_serde] -pub struct RedBankBase(T); +pub struct RedBankUnchecked(String); -impl RedBankBase { - pub fn new(address: T) -> RedBankBase { - RedBankBase(address) +impl RedBankUnchecked { + pub fn new(address: String) -> Self { + Self(address) } - pub fn address(&self) -> &T { + pub fn address(&self) -> &str { &self.0 } -} -pub type RedBankUnchecked = RedBankBase; -pub type RedBank = RedBankBase; + pub fn check(&self, api: &dyn Api, credit_manager: Addr) -> StdResult { + let addr = api.addr_validate(self.address())?; + Ok(RedBank::new(addr, credit_manager)) + } +} impl From for RedBankUnchecked { fn from(red_bank: RedBank) -> Self { - Self(red_bank.0.to_string()) + Self(red_bank.addr.to_string()) } } -impl RedBankUnchecked { - pub fn check(&self, api: &dyn Api) -> StdResult { - Ok(RedBankBase(api.addr_validate(self.address())?)) +#[cw_serde] +pub struct RedBank { + pub addr: Addr, + credit_manager: Addr, +} + +impl RedBank { + pub fn new(addr: Addr, credit_manager: Addr) -> Self { + Self { + addr, + credit_manager, + } } } @@ -37,7 +48,7 @@ impl RedBank { /// Generate message for borrowing a specified amount of coin pub fn borrow_msg(&self, coin: &Coin) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: self.address().to_string(), + contract_addr: self.addr.to_string(), msg: to_binary(&red_bank::ExecuteMsg::Borrow { denom: coin.denom.to_string(), amount: coin.amount, @@ -50,7 +61,7 @@ impl RedBank { /// Generate message for repaying a specified amount of coin pub fn repay_msg(&self, coin: &Coin) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: self.address().to_string(), + contract_addr: self.addr.to_string(), msg: to_binary(&red_bank::ExecuteMsg::Repay { on_behalf_of: None, })?, @@ -59,22 +70,25 @@ impl RedBank { } /// Generate message for lending a specified amount of coin - pub fn lend_msg(&self, coin: &Coin) -> StdResult { + pub fn lend_msg(&self, coin: &Coin, account_id: &str) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: self.address().to_string(), - msg: to_binary(&red_bank::ExecuteMsg::Deposit {})?, + contract_addr: self.addr.to_string(), + msg: to_binary(&red_bank::ExecuteMsg::Deposit { + account_id: Some(account_id.to_string()), + })?, funds: vec![coin.clone()], })) } /// Generate message for reclaiming a specified amount of lent coin - pub fn reclaim_msg(&self, coin: &Coin) -> StdResult { + pub fn reclaim_msg(&self, coin: &Coin, account_id: &str) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: self.address().to_string(), + contract_addr: self.addr.to_string(), msg: to_binary(&red_bank::ExecuteMsg::Withdraw { denom: coin.denom.clone(), amount: Some(coin.amount), recipient: None, + account_id: Some(account_id.to_string()), })?, funds: vec![], })) @@ -83,31 +97,52 @@ impl RedBank { pub fn query_lent( &self, querier: &QuerierWrapper, - user_address: &Addr, + account_id: &str, denom: &str, ) -> StdResult { let response: red_bank::UserCollateralResponse = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: self.address().to_string(), + contract_addr: self.addr.to_string(), msg: to_binary(&red_bank::QueryMsg::UserCollateral { - user: user_address.to_string(), + user: self.credit_manager.to_string(), + account_id: Some(account_id.to_string()), denom: denom.to_string(), })?, }))?; Ok(response.amount) } - pub fn query_debt( + pub fn query_all_lent( &self, querier: &QuerierWrapper, - user_address: &Addr, - denom: &str, - ) -> StdResult { + account_id: &str, + ) -> StdResult> { + let responses: Vec = + querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.addr.to_string(), + msg: to_binary(&red_bank::QueryMsg::UserCollaterals { + user: self.credit_manager.to_string(), + account_id: Some(account_id.to_string()), + start_after: None, + limit: None, + })?, + }))?; + let all_lent_coins = responses + .iter() + .map(|r| Coin { + denom: r.denom.clone(), + amount: r.amount, + }) + .collect(); + Ok(all_lent_coins) + } + + pub fn query_debt(&self, querier: &QuerierWrapper, denom: &str) -> StdResult { let response: red_bank::UserDebtResponse = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: self.address().to_string(), + contract_addr: self.addr.to_string(), msg: to_binary(&red_bank::QueryMsg::UserDebt { - user: user_address.to_string(), + user: self.credit_manager.to_string(), denom: denom.to_string(), })?, }))?; diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 10ac227b7..d273d0716 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -49,21 +49,6 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, - /// Enumerate debt shares for all token positions; start_after accepts (account_id, denom) - #[returns(Vec)] - AllLentShares { - start_after: Option<(String, String)>, - limit: Option, - }, - /// Total debt shares issued for Coin - #[returns(LentShares)] - TotalLentShares(String), - /// Enumerate total lent shares for all supported coins; start_after accepts denom string - #[returns(Vec)] - AllTotalLentShares { - start_after: Option, - limit: Option, - }, /// Enumerate all vault positions; start_after accepts (account_id, addr) #[returns(Vec)] AllVaultPositions { @@ -131,15 +116,6 @@ pub struct DebtAmount { pub amount: Uint128, } -#[cw_serde] -pub struct LentAmount { - pub denom: String, - /// number of shares in lent pool - pub shares: Uint128, - /// amount of coins - pub amount: Uint128, -} - impl Coins for Vec { fn to_coins(&self) -> Vec { self.iter() @@ -151,23 +127,12 @@ impl Coins for Vec { } } -impl Coins for Vec { - fn to_coins(&self) -> Vec { - self.iter() - .map(|l| Coin { - denom: l.denom.to_string(), - amount: l.amount, - }) - .collect() - } -} - #[cw_serde] pub struct Positions { pub account_id: String, pub deposits: Vec, pub debts: Vec, - pub lends: Vec, + pub lends: Vec, pub vaults: Vec, } diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index e45b5352b..602078af8 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -57,7 +57,7 @@ "description": "The Mars Protocol money market contract where we borrow assets from", "allOf": [ { - "$ref": "#/definitions/RedBankBase_for_String" + "$ref": "#/definitions/RedBankUnchecked" } ] }, @@ -89,7 +89,7 @@ "ParamsBase_for_String": { "type": "string" }, - "RedBankBase_for_String": { + "RedBankUnchecked": { "type": "string" }, "SwapperBase_for_String": { @@ -1361,7 +1361,7 @@ "red_bank": { "anyOf": [ { - "$ref": "#/definitions/RedBankBase_for_String" + "$ref": "#/definitions/RedBankUnchecked" }, { "type": "null" @@ -1649,7 +1649,7 @@ } ] }, - "RedBankBase_for_String": { + "RedBankUnchecked": { "type": "string" }, "SwapperBase_for_String": { @@ -1902,89 +1902,6 @@ }, "additionalProperties": false }, - { - "description": "Enumerate debt shares for all token positions; start_after accepts (account_id, denom)", - "type": "object", - "required": [ - "all_lent_shares" - ], - "properties": { - "all_lent_shares": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "array", - "null" - ], - "items": [ - { - "type": "string" - }, - { - "type": "string" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Total debt shares issued for Coin", - "type": "object", - "required": [ - "total_lent_shares" - ], - "properties": { - "total_lent_shares": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Enumerate total lent shares for all supported coins; start_after accepts denom string", - "type": "object", - "required": [ - "all_total_lent_shares" - ], - "properties": { - "all_total_lent_shares": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "Enumerate all vault positions; start_after accepts (account_id, addr)", "type": "object", @@ -2322,40 +2239,6 @@ } } }, - "all_lent_shares": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_SharesResponseItem", - "type": "array", - "items": { - "$ref": "#/definitions/SharesResponseItem" - }, - "definitions": { - "SharesResponseItem": { - "type": "object", - "required": [ - "account_id", - "denom", - "shares" - ], - "properties": { - "account_id": { - "type": "string" - }, - "denom": { - "type": "string" - }, - "shares": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, "all_total_debt_shares": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_DebtShares", @@ -2386,36 +2269,6 @@ } } }, - "all_total_lent_shares": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_LentShares", - "type": "array", - "items": { - "$ref": "#/definitions/LentShares" - }, - "definitions": { - "LentShares": { - "type": "object", - "required": [ - "denom", - "shares" - ], - "properties": { - "denom": { - "type": "string" - }, - "shares": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, "all_vault_positions": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_VaultPositionResponseItem", @@ -2730,7 +2583,7 @@ "lends": { "type": "array", "items": { - "$ref": "#/definitions/LentAmount" + "$ref": "#/definitions/Coin" } }, "vaults": { @@ -2791,36 +2644,6 @@ }, "additionalProperties": false }, - "LentAmount": { - "type": "object", - "required": [ - "amount", - "denom", - "shares" - ], - "properties": { - "amount": { - "description": "amount of coins", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "denom": { - "type": "string" - }, - "shares": { - "description": "number of shares in lent pool", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "additionalProperties": false - }, "LockingVaultAmount": { "type": "object", "required": [ @@ -2956,30 +2779,6 @@ } } }, - "total_lent_shares": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "LentShares", - "type": "object", - "required": [ - "denom", - "shares" - ], - "properties": { - "denom": { - "type": "string" - }, - "shares": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, "vault_position_value": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "VaultPositionValue", diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 434031491..64a692faf 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -88,36 +88,6 @@ }, "additionalProperties": false }, - "LentAmount": { - "type": "object", - "required": [ - "amount", - "denom", - "shares" - ], - "properties": { - "amount": { - "description": "amount of coins", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "denom": { - "type": "string" - }, - "shares": { - "description": "number of shares in lent pool", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "additionalProperties": false - }, "LockingVaultAmount": { "type": "object", "required": [ @@ -162,7 +132,7 @@ "lends": { "type": "array", "items": { - "$ref": "#/definitions/LentAmount" + "$ref": "#/definitions/Coin" } }, "vaults": { @@ -475,89 +445,6 @@ }, "additionalProperties": false }, - { - "description": "Enumerate debt shares for all token positions; start_after accepts (account_id, denom)", - "type": "object", - "required": [ - "all_lent_shares" - ], - "properties": { - "all_lent_shares": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "array", - "null" - ], - "items": [ - { - "type": "string" - }, - { - "type": "string" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Total debt shares issued for Coin", - "type": "object", - "required": [ - "total_lent_shares" - ], - "properties": { - "total_lent_shares": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "Enumerate total lent shares for all supported coins; start_after accepts denom string", - "type": "object", - "required": [ - "all_total_lent_shares" - ], - "properties": { - "all_total_lent_shares": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "Enumerate all vault positions; start_after accepts (account_id, addr)", "type": "object", @@ -895,40 +782,6 @@ } } }, - "all_lent_shares": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_SharesResponseItem", - "type": "array", - "items": { - "$ref": "#/definitions/SharesResponseItem" - }, - "definitions": { - "SharesResponseItem": { - "type": "object", - "required": [ - "account_id", - "denom", - "shares" - ], - "properties": { - "account_id": { - "type": "string" - }, - "denom": { - "type": "string" - }, - "shares": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, "all_total_debt_shares": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_DebtShares", @@ -959,36 +812,6 @@ } } }, - "all_total_lent_shares": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_LentShares", - "type": "array", - "items": { - "$ref": "#/definitions/LentShares" - }, - "definitions": { - "LentShares": { - "type": "object", - "required": [ - "denom", - "shares" - ], - "properties": { - "denom": { - "type": "string" - }, - "shares": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, "all_vault_positions": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_VaultPositionResponseItem", @@ -1303,7 +1126,7 @@ "lends": { "type": "array", "items": { - "$ref": "#/definitions/LentAmount" + "$ref": "#/definitions/Coin" } }, "vaults": { @@ -1364,36 +1187,6 @@ }, "additionalProperties": false }, - "LentAmount": { - "type": "object", - "required": [ - "amount", - "denom", - "shares" - ], - "properties": { - "amount": { - "description": "amount of coins", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "denom": { - "type": "string" - }, - "shares": { - "description": "number of shares in lent pool", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "additionalProperties": false - }, "LockingVaultAmount": { "type": "object", "required": [ @@ -1529,30 +1322,6 @@ } } }, - "total_lent_shares": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "LentShares", - "type": "object", - "required": [ - "denom", - "shares" - ], - "properties": { - "denom": { - "type": "string" - }, - "shares": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, "vault_position_value": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "VaultPositionValue", diff --git a/schemas/mars-mock-red-bank/mars-mock-red-bank.json b/schemas/mars-mock-red-bank/mars-mock-red-bank.json index d551a7d1d..a3f38b4d6 100644 --- a/schemas/mars-mock-red-bank/mars-mock-red-bank.json +++ b/schemas/mars-mock-red-bank/mars-mock-red-bank.json @@ -157,6 +157,15 @@ "properties": { "deposit": { "type": "object", + "properties": { + "account_id": { + "description": "Credit account id (Rover)", + "type": [ + "string", + "null" + ] + } + }, "additionalProperties": false } }, @@ -175,6 +184,13 @@ "denom" ], "properties": { + "account_id": { + "description": "Credit account id (Rover)", + "type": [ + "string", + "null" + ] + }, "amount": { "description": "Amount to be withdrawn. If None is specified, the full amount will be withdrawn.", "anyOf": [ @@ -708,6 +724,12 @@ "user" ], "properties": { + "account_id": { + "type": [ + "string", + "null" + ] + }, "denom": { "type": "string" }, @@ -733,6 +755,12 @@ "user" ], "properties": { + "account_id": { + "type": [ + "string", + "null" + ] + }, "limit": { "type": [ "integer", diff --git a/schemas/mars-rover-health-computer/mars-rover-health-computer.json b/schemas/mars-rover-health-computer/mars-rover-health-computer.json index 489f397e2..756314a5d 100644 --- a/schemas/mars-rover-health-computer/mars-rover-health-computer.json +++ b/schemas/mars-rover-health-computer/mars-rover-health-computer.json @@ -256,36 +256,6 @@ }, "additionalProperties": false }, - "LentAmount": { - "type": "object", - "required": [ - "amount", - "denom", - "shares" - ], - "properties": { - "amount": { - "description": "amount of coins", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "denom": { - "type": "string" - }, - "shares": { - "description": "number of shares in lent pool", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "additionalProperties": false - }, "LiquidationBonus": { "description": "The LB will depend on the Health Factor and a couple other parameters as follows: Liquidation Bonus = min( b + (slope * (1 - HF)), max( min(CR - 1, max_lb), min_lb ) )", "type": "object", @@ -375,7 +345,7 @@ "lends": { "type": "array", "items": { - "$ref": "#/definitions/LentAmount" + "$ref": "#/definitions/Coin" } }, "vaults": { diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js index 8a1f60229..22cc808d4 100644 --- a/scripts/health/pkg-node/index.js +++ b/scripts/health/pkg-node/index.js @@ -288,12 +288,6 @@ module.exports.__wbindgen_is_string = function (arg0) { return ret } -module.exports.__wbindgen_boolean_get = function (arg0) { - const v = getObject(arg0) - const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 - return ret -} - module.exports.__wbindgen_is_bigint = function (arg0) { const ret = typeof getObject(arg0) === 'bigint' return ret @@ -309,6 +303,12 @@ module.exports.__wbindgen_jsval_eq = function (arg0, arg1) { return ret } +module.exports.__wbindgen_boolean_get = function (arg0) { + const v = getObject(arg0) + const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 + return ret +} + module.exports.__wbg_new_abda76e883ba8a5f = function () { const ret = new Error() return addHeapObject(ret) diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index 52b7e5f44ed79384bdfe1043e293288e2ef0c601..1af8ec37266e78e439f5b846687b8b48ef59701c 100644 GIT binary patch literal 173167 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 literal 175592 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

^@EVsydN}# zjsVxI6Szp>lYKG?9DVSk$c00BepSd!tT2JM_6SWFlx67T5;nPGzG3JQK?u&@fD<)# zLvdjU<{7O82Xf~*kUP(T+<6Xk;~KP33-e#Y7m>O_ZzVST;Xq*z4vZ{Yj0-trSMmff zV66vfGfQ<-Nr$A=h9v?_uL)#Z&grjuhuJ z1t_f!b2FXsH8*-GsSaP!2>nk~3Bx#lC||1>2UWg!!^Rbz`#Y_pJ@rwicQfR&`t91C z`Mk6U*T2w}BbonHSAJp&l)`o6UT9}Gx#=Dz@2^T4+Gq__dAJ&qOaaSRY3e@phHR;(Si z%pgErRclog7D$T7^i?tmaWVH_(LL!E?$wqiYce_Dg$kO^Sszp$l7KP7d>m8O?q3?Y zflfl5R)(}&L-~!_=NYUpPju{#Yy@4A4PwJZCL2_rhJYMKXjwi*L`j5pV(iYiFf^dJ z)yhm|7j^}<0_c-^!bF|cw@NXlnP83N<`Cn83hLSOf6PqyhwW?*8{|$Q^Ue{&NJeWp z$wUk)^~4}sK@8-s!I5Z03NWcfYlO`uU-pfBurHQKR~mMncBP>q6amd9 zB2AF1a^LU3S53BzC7%3N+tTg3>y~dfFIg(cU(R%IUmzdx5o(0mZ&Q8DEm#TiHS)ls zk9)vhk6qlY^_&=f0s|<|z==Kh*j;8$dqBPyHII_jmT~M9oht6C_P0h8%s*MCu}=><15hPQVaq%$*wy^Bbo(x7Ce zu6sz1CYiPNKf^$_BF{n|dS_*fRC%FuOoob+b+oLJ7lLN$Iy?ag@?lnWq+L}DHF<*i za+_gFcjrR3<CvKN11HppJf zOAwGf_iuSP`w52HS-gVz0Xx>MOM4qCXZ8C_= z@Sn>|n>S$E>Ct^S+>BO#zzc&%r<0{aM7T~Pgqgs(WuF0L5S0yP732vLx`p;**22kk z&e@>zrZqsB3LTVi$0ifkm5}pEoR)?dV}q2^m=*XmmV209C=I#k(^PY2wK4d78Z$WzFXSmX;h{IBwKalA(-&Ls z#ttJ~!6aRU?&EqOq!Q@(eD5y)G|kZ0jDHX#hSSm$od5zRW!j-|kpCh()eB?HQswZM zcU5fx_~H@t*_=!#OAv?%pc2z3qr3O&@&7m|)(VRkG)P z60ZN0MjXNr^vf*8p>rkj(ky6PM)D6B(oxMxbp0Cf{CYI0cn{2LkQ1q(1sbD=PBdf9 z^Ha5`${9Gh4WTPl<6etoW%9s8<4lOM-=K+EoU2e&B#d}0d*tcaU}_t5)^dHQh!A2z zq}*W>Keu5OGN01{1QQ&x)a6Za^E$ldlusIWCoEmpzg3*V?2TDuB~Rw#SH&AUm!R*P z)&gbf`ed1QEECWq>54&K#iG#oi!;CEWQb)iIh@xY97_6n7!1yFh2imVDx;oiUPC|& zc#Snt6OwhS>Ai5yGS?Z}Vrl1y%+vy%Ea-O8i2<@W`gTaIfG}B?T@>c21>8Gke)03K z>x;?!Oz0>3V92Zeq8G|#nU_l=am+OwF&#*8^>*Z-frf7zS3IJa^M9V z9|PEX3h@!GEl441ztg_1;ZC)bN}Oh#jPNmwbjgmFf_u9Q2zPUw1q#EP4;lcHozgk( z2u%FS52wTVp~whBtYmq>Hw3u(lcgUd)PoOOjC3n}|Gi~@csi0FJt==Kdyn1WlUgP{ z!Y4`q>@b#^YhSx=2Qk~-@ssuZFsBh6KRI$QHDL#vzY#TsDXgLxuhUGi&j)fM+c@yI zn!vilO{_L|ATV>2`3F@>{0#vB3Ks1UBJ~D%%5kC*rNfURv)%?k3Uu&o;7d-1Juc?- zndwu*d`C<#q-um=$p9o`j=ec0GzE_)$Wg`+N=;MhSiayByu(zQzuG`1G&fbF$T<{L za={xILjnAe*x`yajCJKK3}mFEw*<7}l!zjuX}JhlY^qGOe;N6JmRk)rNEa$GLI(A^ z?)>3A;08y`hIb_9nbgNgAqWVu#d07eeNFpas%N?Wtkv0A=wLhvFd}*vAtu!mY5zIv za#AhGU(6HfeyujPw49^~GfG!dhF6GY=k0Z_aTGZ9**ZgA%rApSS8`>iPrw{-efymc zr+h@k_;n7(jYf5euGA(k2E9ma_kc-+c{ z7I+#*@Xpa<;*$Va34m-}Ep>r3akm`XgKr`o@op}y0(WJk8MWw)M<`3Uppd7S<+iM@ zsxs*xc>ytN8A5PRb|+>SwMWx3$YX++Bu@N^xXc9Q{V`6r&)M|DVk81NZ(A>?_bbth z#aNQS@p%@!894g#xQqz6f{$80oV^tAVI9p6Xlk0m9*>q33fkpKi^UmEAYqmw+XVil zv*It`DN5%2!C-gjO#)4r)@$@Lf51HX-9o~iNAJF@geB8IP6>NH{F)`KiZZ2@ggqA) z64u{-T_vn<;~NsTSHhlWx$7T`ggtY&)NlAtN5TTILYZO;}bak|iYr7W-AF zr*P=H@GB9KAnTa&Nk%Fke|dPMUUly1Ycp)6ARaypNhv%TV&YaCR$a!Cj2q ziczK$YYY+6b{O&AZmlCW`dX?>TtrNW{>E7%D@f%#tQ5$K>SqNB8D^>0dr=XZrCM>8 zYMq14`&p{?Z7vKfvs5jV!^cJBmMShYnnn>fjC!2zlarMyxp$VT*_gSdDu0Z(RLviO z2VIsbD@Kak;6auu){eJS<$TBz@L}dIc91tzsfN$|%23rxsqWf@ZLrXY$4!Q6sG~qZ zkNbo+Ey++d2a^m{`%33v<}Z}R$-PQ8wBj3vB5&ZP)>M;?co0WnHf_9oEPt@$@*{aWz;r%jM^N}Wdxg} ziH>{ZSDjG{lgg+a$beWjEt3vF!+K4zY`sZ~dcvedcF3eHL`i}x#GAD9Ck9{ZkbrM{ zWP=R|XVUf^40hm-CI-u#No(@}_}xs}dUt97++xikGih})7q)nXCsu#4SU zH-k`Fiwt&}m#t|?vTEvP4s{(zt&d3~hr7-Mo!k1drY@QOahkfPzjjksMOj9asXNm( zb^Yzv)ztNEe8bc&M6@{*Sit#5W9nXzsSDTtlW`^huuNS*&Q0CC3#7eDrtYAsDx10t zq5nxu-5j|Xwhq0>Tz#fnmM*J@NZ3EDMy>97|1ruaX6x*ku>Bl?54PBW7vGjUstOLM zx;2jwM}-2g&~36wWcFZ=Sj5&4<^Uni_PWA7u@#BJNkh~rM?Vs=jXUP#_ec}U)FFo~{7HtR;e_59&WJSurE)1YadX!I zvXZbR%tvS9V^cv)A>j;DRX7`>Uy9VE#T;i1-VMrJcF~yBR+)>lnpTk(z?qCsN-J<0 z`V>n;kil?<2@35AEt!imTR-cIvr3K&TmB%F$4YZ?IHL=FaE5O+7P-YVkXt~(Li8LG z0GZ2X3Xfg&G5V43Y z5MTyPFhh$GpadlmgCJs_Dx)L{qX`-i70-Kmg^Azqzt%qI-oD+f$cZz2)I@QgbI;jl zf2_Us`rd293lyje1`w#L^Ipw!Wmm;7dJ)CT0lL^9D?nGj7#OIpU?>a3eZd} zF%9DkAzz@pirXhGN_g;NQ^UadunlBXXi<+d4GQYn6y?JHB7K7nI98BvoXFUJ*)}W$ zO|H9!!v(wXJNQ86W8Gnu4kIb!fE()YT2_ww-HierV3f9iQCcK*4#fdON^FjWJ+dJ8 zN61g*b|fuGi2a138oCPvsdv#+2>-=AR%N0@w`Xka5kZ8Ls;G;`J^_hqA!i* zqx}zj&pmz;-q}X;M>IxDdVl|+M^wYz)WA;iM|y02ng9v$q3yEhs02|Tf%aFRw77qZ z1HV3N@i|FxfdC*QzX{QbRbrwGyk(Q9Dku5=H2o}|!}+gMwh|Tpe(U%IiikFr0{#OO zp`-k-aZ93oo|Esfz#)zu2`}B*cG7>4#coS{5i?R=f^do#nPsfwDlFUTc2fLBb zQzU{n(g#K1if_>+gtluxO(LoT7wBPs(cCxW?P5*|O<_02EEFud+t%kD+n{emM)zMe ziwWPWas4)tpe8Y>+Qu>XbyZ|(Ris~lpa;ddlE^gt!Lz0aN0tfnE^lb>sQOkfHtP`s zV6D{{o`u`9H09pcsUH|tE<81>v%d`BL!n6u;EpOFv>1$1#j~0s^*Sl`i8PoSP6s5G zw~hjcAYHQwCNWe zEinhjCOrDpkACj+%icnlqRAJIe)gsZB<{enh)$5)gPF`YpKMe)wSfz}?2GAM|9IKiVj<|lx#;b~-AId3D{%bRW?r#ai6vHIX5TM=;5v#Zs%4>? z*e*pEcRuo@e54=!GPAz6IL0Nj?b8ewQv!ZZ2bcOIP$@>^ktdNV7{S|y`(_|QB-sjP zo{QcF`Y$j5qP6uN2)%95t||EST=dY^4tT3dSt~;JITO+W3nKO8gP)Eh^49$5U6UiR zE#YPltx$x<9#V*I0EK!sw=!D6Mnx-xXIaZ$bZ#NFNFICz3Dt@(TB9i_2I>n~EN5$! zfG38ozYkfKnQcQ&AsKq*ZQnp-U#7Tm7itpZ)n0SBhMEvDQNhhq=DoC6ozK*g#!j63 zVD)$cFJKYfT85_U(e1qs0tLO~L=osYhGX~&^d6(;XrV~rNv&SRN$2smN0C52iVzWR zxBcn_x_w(DSuNdh=4 z8~Qbwy`KsuSG`{hl^0~=pqRGwB9e0_@?n8}kVi(B6z{@MoVPVDH3~gcn~aFdYWRg( z?bcIJNpc_JFDA!c^RXT|JQV0adJx7$2OsODmUu+n;%iFJ?@9eJDpYSpb@S$9lJ><+ z0X_SP1U%r8acOvui4{h0%Inr^ppEu4iEK@HCCUvnmtCwJ&TM$lo?@DKRqGIk5pT~& zB=w1cwx?{a2wJP=3SEZX4_*W1G)-1cGgXz-OlfkMX4>dHUt>#RKLFJZGOj6mND-?i z5MG81>vOqG`P!+<1yF4%Aj}gPRuC&j*=@`8@v=LTP9))9Ub`udz-8Ycp8#&yC__1F zS(ZXTtJDg$SM2{Q2-7U!(&n(HVn`?0)x;A5Uj6t4Qm1QWvfV>ufYe)jVWw)u!?QTO z4#pj-rXpoqR~r1Xb{oQJzVm2%21u7!Xy!YZ^Buya;@B8Mg2D_-*Gj}6gTukbXS-iC zrgDsN1o%~xCQ_gENEOp$=@S_#H5<}|QdFl>_^2MRK$x{c)a#tTvI^Kf=`7E}Ww2lu z;Hv7xW?k%7*G-4@m|b*2`T)VO=uC7%5h)M<0}gCocpJTFL!UNpr~j(MwuWI1Ali}z z0M&6i*HF=R(SEy^|T|#;xxekhg3uI zY+(eC*iWBd^%(9%+yAQbyxDyN}Y1GioN#n59I$MFaItWYoe= zCK4{1!@3p|SNs>9sd5?^(xc`nrvZ2^q++$wFrQ#qfQk1So1zFufu>L9tR1p^lBR$~ zG^O%&$284R0$O@NW3mQj*g^QfhNmWJa1fPxE8G|B1BB+nYgm1dDJ)+4&W`&Y`ob`# znXhoCG}|MroNOKzk+!hoiA<#Q0FjJ8Kg<_C>!XY!bYJENRyPO3MhvN?^<*0|aCC2_ zyh?$~dc;01WhSVPM*s*yQRC)jzQXiqm+`2^b9g8>H%H>7Jm<10YI}KZZj0I~uM5vR z<@3&MP!)R)fx9UpoKp33^V&$h&x8vrM0C;f3xK@+&V^?p+)zjOK-sN<)Do)s)TU+a{m12ZQqTC9Y zlrSQbnnv;^T60+H%Gbf6a`M#GM0iC>EXdMmgU^*4M!oa6~vRCEam6UBeimt_$I+4J)zb3wG-p!|pMs#|wR7*gZCY-Tg>G z>D$^DICse*uwn;Jfg z|6RNh%nMVu_F4#AAASqLImb@O2e|}^(TBhZp+Xvb)UYG-Q7=U;z6w<*$`_2Uu4IHu^Gk+u$FB~e*cBOFu=CpRK}Oc=t4Q$H>!P*0-gycjc|RzAowXY@fB2;XPszh8 zA=L$F0;ABDtB7eSjg!jY2itGuf&e6pdtyRN4re)=*5P7fd*{T?6{qa(>_P$S}sSvD|%u`7LVT+VG7 zPGtipn&0?hEZPUL0=H%7rVAvYNK9Z&NlBvUej5&ENH)k?S{RCfLkP^20%sMgYhcf) z#N&PXmHM|;rC7nOQh83)npdiL2vv&26ic_Y+SYPGBU%lX$(h54IFYzvrUhhxCkTUQ z4qA!P#E#UB*fY^Kn!G7>(EZ2tH&X9{WCG;xi|*YX!?z9U!GE0ct6&$(t_ zwzo1J34#V~l_GXedA7g}y4O;IVX-=OwTB%+!Acr{4y9wxH$}OmT^a4$`n6h~1N2@f z{s`Ku$)euVK%C?3O}>rCo-+IT7|;X@J+3Ta5GHRn$b+PAa<6GwNSQkP#aZrTRZp>F z6o8Rk<@kdT1-n=`7*QSSCOTA^FOpJ=N@7-W;1QAo6Q5=gMf8m+ieU`wt5xi|lv>5> zLUt4JPfi*`r>dJyrEEN!ADA<*<&81=VRXm7rkz!`PV~#prb) zFo|~eNTc^%Q+CQWpC0LWscz1 zl)faL0CsQ|^MQm$hU!tLJ z#M}w;d6_Rde=$k9eQm%*zeZB-vK%HD_l^vt{9Yg3O>2evWic9zC6yy_~_l zQ>G-uh+_Kjqa2Y<)~I5)r=4bL#=QEP$)sUl#^OLtmh~uj7LrK=m{#ZGb=+EZlJ`kA zSiv}x3Bzob>3iaA2@*%sys?4y*Ry8Gm#Eks@>&oaz_OrNDoS)C_PLH0hf&@-XKA8K zioiiNin>ZbgnCgIuQ(W9kq<4UY`xg7-R1aAtq$|{{GxHMTd~ja@vC9CTp(LoZbsLl zY@M=4iWKuFpUM{bK;PvI%a10j>nL1h;3*nd8Q>*((`sbPcwrcmYCCy(djg#ob1n6A z+05mWT)$K8-<;gd#qvCJ%C>cYKV;E-i?!EW zpinXCC^VqXYw#07|8C|yd(^huqE~Tg+!k$ivjFS`-e7xdVG+hb;M;!Sh}d~~w{ePm z3I6&Sv$u%j@&GPVe1hvbYEd@@Inn%_yVbhmREk7W6vn%lwqpM%HYv)^)YEoBB)GUZ z_Ol3HeXfvdP|%doU#Xzm$4yZU)Cl-h0!2n?mtQg`I0dearPnj8*?R{W}S8)+Y}_6 zJt28o@J@I(UB~A0yc+P6ac`T=#i3RFrSsfLJ#*SaEV(Z2H%EZB)GSKi!7_@bgrQ^MxSLKI7 zU#(ZNW!h<8ERm{*CeE6$i`8le@~*}07_nMOdA?uAWp=Qy8+ou8RFQ}fG5n)`Z>U8d z1pqGGsevxV2G1@t&K!f;2z0IlYSJ}jhMsBXR2nw|>eU_fz!tZyTLM!p0mvP6u2dCz zj7a(qE)G#e@?T?@vt}&Vr2*kV2p|i*?QWN}Wttw#QApL2YQ!}Q*VW;ghwFH_ZVuO5 z?V3)7{~5wgVD3e(WlT-RL}j<*x5H+`vXaE+24yvq4k+y!fx$p*$W$VFjVML;L_yfd zhj7GVyOST;mTmk>@uLY=@-Y}iG(D52GHxJDLC$f;rJd|QOIFnL|AP8g)+ybF$zp*z zm4n;~!6dQM0iw%&f38KJYkii#bKe1xq1o`cV&_;~)1RExr`c5YUG(}azlv|;H%UbH z{W15?^2f;z{&o?RqxB^9;JuTKnu)c-(7wID^YK4Fba8Bw;_^~&R!9euvl5!XIjl3F z3ojXP#O(57HO3P4TBggIM`<9;Dyjj>cuV>gUP3oXoOQ7(zJB;2R9y%(jTwbg7r@(v^&tx^O_mu3fT5?D|c>cujHO836eQ zn$CurphoMoN3p0!4Q;gq_iR}HK;w}8HS~ue+1-N-4rTo|et*;CV5 z0RUb?-W>;8lXiGwAv3C5&G5Tw%dEUu$;zgPlqwr#sA4xtJp)L83RvKp6`LteSdtsN zh1sww6iyOJX_99N!5r{SbVV*C1}QmFs57#hJ*FY@66KomSUA;T|-P(&eI2D|%sA>-}6Ab(nX zMuJtwGdXu52lDE&&WWOAP4Vki35pxk8@YXDb~k>{?6)RweSBB1g{v1&F26YpqJi^S zuH|M9KV4NhpAnC;GQ-jI?3&0ou62dwBOxQ z<_E&NWmrHygPqBz;HT8f*wGsSN0Hkk@36CeCHI&@B<-!ng2}gsiFfd5wfMF1G4aT6 zxjC8TY9U-_>$jvw_#uPwGkOW}ge2JSr*);WgW>mq@P0;DQd{wtP|)lR$;8}$vWJmz z%g&@W+Jv+;PSjZTpvvVq8;;97g-oz1`UFS;_2Wz*cBeBqsD5#ve|wTdOoSb&0N1^` zVno`*mAl;vp_uXe_vwDOo}Rak?M)2W)J=&N2%4-yomU*De-I{Oa{Z#gTGJqe_RHJ)9C;Vr?uxx zFieZ($`Vv!Dw}EzCA-Tfqwd&bySrqPRp5)YST5^c!WBM#gjdCf06>^3n3Q;ZikIff1MX2^ZKIYy|Rh-zWn(`%ON1W|?^AlbKcD#(9 z(P8gvPLwUb0fGOjo}zEo`!|zI6gI@ocHNBojc26etR^01`jc}2(UN=xOI8@E zC!2fAIDd&%jAWD^!l$26WRxR_=xFvP4nxz51zokY*-w#Y`LcXTW=r~vr}9$)&!8t* z`7>M6C*px6En)j5Uea20eX6sZjsaWm+H@j%_q5R$C(4(MtU`r?G{Q2qC(!Nk3E+SX z-IAWKQtBBs_LO?h{aO%E3LVc{+*r(j=N3j_gA8<5T)_0oIb2|$g)`6L0w@I+j4ax# z^W4&3NWU`#<2t^2j0qeU6F@52keq++M9w=5X~?@dP8^kKJYB{H}76MhQoU(LZsAS40;D5@StVwVvYRs;J*f^4egT(jjUGfR+HYnE8E zUx*T{vYKly>NCOxD??&qtsg^PD-czEs%pg^(A&Kk#hDL_VZWWCT;9qBp1unk5qF5% ziYM3Ytzy>xREHE#bF0OVS*d2aNFCnIbY;K8i}8ez3tirx)iIlK^HzqMh;wnTKw>W6 z7o>9=w2WuoO18bDkm(qI`s;k~_+Qj~acno(=VzCg)Pd}1A7P%9uZnD|u`IB$tb{c& zZM4Iu(xk@W9mrIkX$jby%?pKTzeYXkU3>-@tc;8PviaBoy3fdf?nB8+_xXK1%M*+LlNU5vLW0vIt<@XN)<|2s z1Z4`%wsVH^pktw?O;q6}_{&-h{#Yj&X42AuPue^$yh`)xy_ncvnWzWS}@C-qxPV(qtP7}l-z)0Jm4 zJeWsbq%7poC_B(9MkQtW(B(V|{bzI^sQEGH*uG^R4-u~m0oB0Cszl)H1C;H5;BSQ0 zQmYrE!>`OL^1`gvGu&tU;!z1@6wy)eD)N^HDRQ+JnGQwpY77?ntcpm=$2|AkN4*vu zTs=_BCsf3OBl|^w>tK<8uOgu@Wi2{}dZ3o4RYbv30}V;y87MNNBGL*6ib%Z}DDo33 zqP&R%MaU~VSmdiuQe>zvwwt97mkTN~REw<10}Y*05v=%X=nVIQF4(9dmJ9{xKC2?4 z&=ffqiby~oDDqJik#m9~PkRvxAO%1Xe+r7Yv-Fq9yeC6tU|?>hGLGf=p~S98X-e(R zN>m*L9@|+FM!n69D;GMm0a)=&sJ^~x?R@gqfZXuI&;fHdn!*z&ivL9A=D z7`aFLnp^<4la}B={NzwmjFR_nN1??ygJF-&5YR+#SbZ{!Hbx`L;@ZpGe$eOrX3XRp zex?sVvFqQ1AZ|2(;6vH(m>(GgyGjaJ_M)X;;A^9NTJ_L|Xr@WEp#Zp+wUKf%Xjg!S zl_eHr1qXG>OJjz|U;#905?VAe;?_g62C_9#lk&k77go6>8Q2I=3-= zKxN^%VqeIslKlkX;`7dFCSErbWAn3i(&(IKa0Z+H)YjsioXCxn7uC6O54n4{=61|7 zW_3`g#&O(MYf2b6k8}2UEBF@NsJ3%V(_1E z?*WjT%OiQ~En~{D#D9<6ufN2J^N_#aqmUddlwOjXM|zD%^2S@(^Z@-gvju@3%&5@@ z-Q{tY#gk4j9h|C!kSb&w$#I3S;g})x+M8H|uOVe4`CD@P5!;3$%5SA?p56<-wz<>1Pg8b~ z7pP{!B&HW^U2ooE0t$M~WrS=+#e1iZfmehnCCte1yuc@;UbJMq6^H*NaJ;nqG4HAq zJ)-hNkH}B-fV~Z!=#hjIJrX8LCwe$Xwy1VL5+epx-y;L2-q{kbs<1E~tatj=TLKQWl|PSU9PNg7c%c2q>E)X`e-=z)_oY6B-})CNw{sLee|BhDce zOiX!}2DEQ|E&wyFupzwIc|sEJ!j8{i;Xyj=_>9<&&(Q2MR>}x5p=-=%LOd8l$4qba zh`8$bj6CgpE*Z>bSu+KqacOnihVykDZNkKZH|QBG0wJ6ywbvR&eilEG>!xT! zz-pe^ew4*bf-R{8MI8W-qNlWAI4ziT^$--Wx~F{?XmBb@4;8@9Lzj0-~x=I*WDT>`F+C2-ys{s`H(LIsF>)*d31Z@S4lou2@| z!#cy}>QUX|w-i)Wri~`>A!uwh%)IJ6 z;W44};--7fGFjc?@E0Ix+DTi?a>s*n~b+ui$R1+!3DI)VpG6??r`~oN)QVjXHtD@{h_NH zwd!w4Zs!69jOXeMEuS_MO^7*owbWVRqK_fj=;!HJuftP%QGNRySZ}WO%L5-=u4NU_ z=S+nKGO&vN7w|P_UpMeiCoVCKBoiVTlEp!K;%&5tEGnkIN&1g0$Dd>NJWl4)64IJXDXkYuoj6-zvynux?C+v?^J` zR4*|fT7Zeh`ItB@Omjr%uJCQK_aaOCS&8ThI<#LVH&$)w10xK=LZ~}ZhHYk5Gqi7e zaCk)p(mU&C)O`82mOjj&Ml)pI7gaF1+DZC+lP=`}ZFnDba(3*si^^ZxC@AUN>o8E=reR);YO@V#nT|EDV_#?qj(wsqBcWksn6nRBC%)r zDC|=@VjOCKI2p6;H$q3OWQ&rL3Di@Is9~edg{Oe?EmreTc9JiNW%5O52Qp{3oAO_h zOlZ!N3EO+hrd32YrgKN}{PVL0E(*&Oa2Gwsrrk?XXGFU_q#yYP(U=X2!V1GiiECge zK*-s~FD=@{T<}!#T3qD9Y?j6j(5h-;75f5;&*Ef|qM<@cmXF`!??V@O4{}K|)ST3c zOJ(1P*o!z}w1_*1i=Yd~Lr%f&iil;QxuTXe$Vb#|EcUZ?q-Q2TYbY{Q2RS5~(plCp zt%34VjcX{e5@k5dzFu6Qwd^UySVa^AK8cC8QbOoWTy%>wRSDj2>pYg)sXH*r34pl{ zS-0C{ToFe#Nz~mGapa!~<~`!~R`*(-kW0aQQSr+XWS`_uKC-63LeIsb{7S*rN~yo| zpB5MYE8h;iFtdt2jtGHPe@Daut*IF`#d}!VVC}0(v?UPT7G2F{7lo7-3OcbN0juT0 zs~1-zt7LkDQlU%M<&(UzDavn)Zq*&W62(!INLNwYb)6?a&V}qrITtZQ#ms2m5JSnn zAF$|I7=iuPG$Ex~*P=)T z`PFzw^+3D13Eu5Lz-r8cyR%+p zA|I9wD+(jtsx4>`40MM*1LF;&G^x4FSnIl(Ox~alYdh+km;==R1p7yEZ|Y3B zdC9f=0*z7ue1S?p+?>&X)vIAR;Fvs!L^J<1yg@1e$_34cl)@8Sa**#^xGD#vx{&l; z#U?Mpf-Jznqpxew6;UbKW%$TBOSQC;{?dYpEwZ%0)@o6(8HitVmX;pOSz3B9e`yWY zqP!jUQCM2~HBe_*T7zF`XStPGY-uF{8CZ|m2g1_Qc(t?`Won{VIQIlHE9xS)Ykrg2 z1U_nRe_R#fqwC#dq5ZGZx2sN!IY3_@W2uDgCP)+rM#Opi<2dX?;mq~L464naKaW$s~ zkaA8MFQFnLPQMAokU)5yENagva!vq2u^vlO_C|iXlOu^(A4Aq#tr(itI$46tHcmRh zl`9FycN*L|CangyY~@u&U;(fDu2l)1{I2|~P5)gvSsVTvjYLy@C#EVZk4&ZCnmk`7 z#+@)b+v#~UE<4j=C71-22r*zZgLhR;3XDq{dqe!(bvI_biNtFikM|6=*ERjge4iR8WhRY_2a@bXe;;L299sZ&h z+fgITcWiko?949a51T4Y(E}x-)UB3jf;v+^y}f)03W4Cfv;9%_#c261R)VJZQM!fR zBZC9(59UTEqA}rid<08csX02&x@HRYh{OvHTE3$ugyL$==>AGiEzqm2F)-kI1cls9 zE2-5a11u>P4`CK+0S)YfFzAf(8UPXBPB2hVmE=QSVD5xd>l{^{Dwd$E99oDGTBuHE z!iFmA(^@IV7k9BG*b=iydxTvTb^;&We8ZYxN>DJCdO$;x52U^BVf(QCq0wTe2DtW} z+juEIs0`EO<&y(WY6J__4AuG=2_%r^o@Js?|8sQ>Ald8AFD14^ozN<@GJ*>VUKGT< zX;1(4-0Do$d)zCDwG>0u58(NI2dXOb7IV_b>!&($0g%n18k`IR{;%vY?2caU=+(6w zRB&TmrrG<=!6gT?83EkKW1mQ>V&69_(K;)_uJ6NE!&-6OJJ5S^azl7D3BZ`uOLv%y zw5N^WWwx$V(;KT2!jW}>_Xu30KK@_=Sq&6Hbs7X*f}XM*hQj)^^SeSZ#6}k^GcwAV zk_RT#v$b8i#sbOynk#sxo~l*wCNAM%J`SkZRdVRk9i^Wa+GtlZea%AD0l80*BW=6}o?dhsYx4Ic00J;y;|;fIMLEqxetTR&7*%ml#jD+ptMAA3xb+${*^ zJjO%8ca_O1MepW!Wlv8r+4FmzT}&=Ob|Q6tJQ<7`6D5ec8ks8BpKQC9ohbtvAaXM=#ayIib2YV_YpHfe1G<42D3ZTx z)LM^&QoZzPET98QI65~-rKf{Bx0R`M?m1ORF@W<8ej%OP%9M(M(?al*+~S>V($)rJ z)KUR%j1LQyc#KK@|2BvhGl&-m{(uVSj_gS095KU?`BrTECb;9Gb`Y!V^}G z?HiT6z`3Jf31tk;ybLHR&xmd5yw%k?Z7-Ci*G#asOVlt=4?qxec~uW+qI6)n^J%8q zXsB&kbvX$cq-NOMIQ(UMqZ!ilL|d2Y%63pb(MH4Dl^49f(NNON+>M4G4ulM)$hVha zCX2HJAO#G?*=I7Le|kVzy^;l`q7EXOmW)?WhIaq4eX>&Q8~B@UC2TY22KYtC=O%4n zNs0JsyR~^`nPO^O$D|3=rPtL`PN>FFyvAaHn6CK4*56}4j!^V~i^D3g&NeGUhJ?bY zQ63Ri6Ex-Rx9!=b+*vg$K}PyI*aA@$XcJdKS+0*jnchyEAy zEOJgAFee&y_hNOcw&Le5a<95FfnvqS|GK@k zXjJ_oeyX_m=#DKO`g|C#UNK%w|1FoFPl})a3fEst{M*jQQdVZ;ic}qwndhubX#XNB zGfgM*urkwPnm`!zU5c`5SkU#MwOpz?L1BSUqwyfhpH0aZ+r*gY3$t0Qoxd*o>I+8+ zVlMdKWGR67ZRInx7n_~UMlY3u!hYWB#{^=?$DN)0ZK!=tQO~E zVpB(XE@-wZW;aEtgvssIs~csJg$l}|lT(K&%iqNAB9NS?SY_-R+KY}Xs*UHC8-S=^ z54LEC;*1lDi3?Uf*c`e>>;}P^@Zri2D+X%^M*tZ6;@18boBLmE9{581Ngn}bOf}IY~s}^yDjJXfc@&3SvM*U`Y($$au2$TZDt-0i3X+INESA+$1OSv%aO)Y+o7zE z9a>VWnirEEGkJ-`MtaT+An`Nklp3WsPwCEY#Dpan9QeFfx2~GG$RtxHtJnbNbYueQebhHlbPOS>L5^UQkF0MdX5z z&}4BKwF*ZPGLVdT>FQi6O{$VznTY`h$gxh50U1S2Z5A=K&W) z!Xibp`Gy8YPE5uNmzvHO8(4kEKgEHK`;0yFmnW`SY+&R6P&0P=8$rcdHDj~U78u|F zE@I#%*A=)}`-hsczYWaiB+RD{^O0)kr@v`eUL+0Qt#Vx6BMDbA2az@}R*`w&z3UxT z5S6fGP9!J-;iW0Eii*=ei7ry+P-|rW3Sum=LDi6A3%x1(NeeU35ybq|Q5al|#;J&C zoSA4v!_(}|#FGAoY`?A-!FsGqzJn0)=_0KWl4H7JaqvU@*!5Gie~_S(6Jf~E74vsO z^h7@rC<`XL(w~{kr!4Yk#=)Oi<13jBAwZf#NQ2fK%Y6I!fmCRAbmOxeX2A|{Zk0b-ImG5Lob4c^(8bTprgJ>GRenvdO4U|E3X56_HKdYc)L zz5u&L6MSNgAncG9yA$738j#;e?&-1LKQv@9a67i@0D6F zX8%p-)u#r?vDs8Dp5E4lhEjjfR-#|~nUxF@~Pr*=iLXG+q*osT6g#wpf$aYSc zCgT;xNz%8_MghQWx22>X2$3Xvlk{b)1y1@M@>C5-`evRUlJq|plcB3jBOtSh*o$Z$ zR}TDSW;yx0<};V{JFVam1?R8j%EVs!gIOBPzDs+&U>J%6Q5PWDGBnF9rG$9$E`%z0 zDV!lfz~+|5{DaVtooG}L=%f@vf}Gu3Dh#Vr84bKCXq$Z*6*I>*Ye=2$TuPkymZ}@% z3g&4`tXyZ!kTEaS)Ug*5XpU z03O4swGCqkdq6t0QmW!Dy3C9E>i3j3k|yuPYS6LZRP<}GR}oK>zs>7D};w`O9B%3y`yGPlSw>8 zkpj#RManKPK{FsOA1Q=1K(7}m2d;o-y56-^O9arhdcy9uWP-Jc_YG)oitI2EIOb}g z$pWLGfO%LDvHLJ}z|R%q;upR&^h(Y*vwVI$R@KOKiT;7r){rOkL>wJsqQ>YhMx~l< zah2SIk}P5-65=n^HK>ZXlW2#=2eI92Ha36Nkz#GW0f~DI9E#@hpWzZbC(>_!Q*t*I zsNQHY;Tgg_?)0kj2XW=fTB1d%ra_?rbn6pd=D!Ckr=gD-1d;3C*eHB(^{1P_~oryMJ}4|AY} zU~fJ)JUbi@ba<|Fh4}z@Jn+TgaH>NOGlCFE_Uw+olc5- zOS8bR6$giRXVGcEw5w^Lbxi{>QHyMIUvL_5a&~}wS8>$_dw$JU;Ur^N{9w6XUX-Zl6OcFtqA*Gj3ISz<-6S+CRE$bd1Xm4!=9d3aeapDzpvrCm#zD}x{cfYyc*|vv0;zM zBv<^!$W#~2yx7O9Yq;)dPjz+9->-D0x@)*jvxjFL*Yljz@LI0VQR}yIJ?XW68@H!P z2}A?0;Pxz*=a+FkOfA=QJwR;k>_sh zB)6JGGf@d}Qj1;0D0?%RrrLQJclPTEAq}h=QA)5xZz!3iwmTk6qN6@>4lEPB?xll% z!2yWMThS!Z3|ZiUuafI%Z_K8Y6n3g%saDJ=fCn#N{KauX)%zX$MFhI> zOKt0tR+RF;n4}#dn^9E!)qnc;ztp(P$Zf5Nx;ftQBJSq=C&~9y!kLlmiv%P|1owx3 zu7@2SiWnz+!ke_4Q~dMQ8>vTH|0$F$Al@&>-J5f&UI++%(D&~wg>-J4a8TZAX)Vs6 z^(_g~&&Di*o5A_fRaV=D8j&Re8DP;OXOSfO4HdmPIZs%SN1uoZ&T>j?TpUzK6V?Jx z_~1Fc;kpmoKZ~d?`Dr|vCBU%3D4+(G(gOrAR5(WI0gxLmFlDz>2diKKY@n`_^(#e( zIUNu}j0hiidR$Il-JGb;#R%-DH}jC@8SQZ!>$R2Gpod@uEW;E!3ax7moS`# z{~;d4ioa??y==TbDH=CR*hda^VvYo7{$6NIW$)7S-EB9dX_AD-IXcvYqGOAz3Zra-#YocIPdq}JBqV@=Pd3L$w!}p-tcUa zXbp~u;!e8JblST^k~6oLl1`5pi;E5WG@qD^AGS9ko!cIIBUIbXm6*14nrqItmo%(d zI%QqU9`PxfZO|L2k-)(KJ9K?o5$>(mn&Ro{ubI{grHc%x~n+CeJDO%KXNg{3Psaks(4M zXgfzAj$x~`Z^(Xob8?o>iMOB86~sKP&K@xp@tW#8ojvi)2ssIi74Y{oBTut?yt_p> zutlrsabeXUU^EE8Vh$J|dP9-hupO#c#sUrTCP{J79uN>C;hVY1m)YHZIy9bKUXFd|3{#IVF`@}7%E)cRj-d=Idry+z zAejSTwM(7;24TwXw$!7@lyheDlmNwb*7|jlL=Zk>Cum(&A19Z=SOEjd$L?jPA43+H z_qO?pb-#zJe#3mgAnTyO_re z?S_2%4DCsM`(a6!E-96kOTkU%*Q8+9omlPwiY4l#)nMdotXiyN;J+_)TdYt`8J;ZF zq$B}}(IyFSgpTkYSt{Ep7Nw4BW!TLz$LB3$P0wwtMILAa$b1Ehe6%d&i#+Wwawbg_ zNYHskSAHMnYKy#<4=nOATjY{1>7&q+zqLgdIZUzve_=>Q(3}OU-miaQYvYBeeh3aV zBS>Iozp2ngKzr@zHJy^{>Dj0pl47=9&_>JgcwG}U_g@}s;#KYh!CH%Oq!Irna3r<` z_c>CmPXkA~hg5$RM;bBJ<3(^JQnxP5k&Yx4M>_n?<48bs9!Em-?@u5cNm_+bL*qy{ z>+RF+o^93T78GYAy$Ed#inAr>W%>1HJ%dOU7eQL_h#8I*9Ic+7=c)3X?B{8xdU}?p zqJ0qA-iq+_Wum*+izoPg_Lrfp@Vg4deVGg>*Yl@(PZ;g>D4}JoB{4c}NO7>;#ZI~* zJ>Bl!q3hXpcOt)5b?7KjnZd4A16lQh*4yO|`>>-ziXfoZZf+rD@nChNUciZ7V*z8!F~gs%Qb-9&EEZm`#J(>2`Q}M%m%{ zWsn0X2#hCO~QW`5qf2=K+sKlBL$@r9%pLn`=Hvh(w7^oU;sJ3qY` z;W*nT9H&J%=CJdN^!PIDTylu;6X?BE?0f`;G!>^rQ3ntuJ3ndhNoccCr9v8h=}DvI zv!72IM@j;CWu$Q|(5|nSG&09?gcE+7aPrcUMhLEv#uW>YMoBqe7ioOy1fYr>iaomk zB>T9{;zGXfzbQe>2h3Mw~hFIb&gdC=^1#EeRYfLbL@W71FveSqyovuq#;FMZx9Vu@0 z=+*t!u4ribwAmlp&M>q=0bM2Z_{9y)#P#+4p{*Yt+WPrJLxSEXNg6NZCFKET$yYWg zHvx4<$2uD02Gfm;Psk!u%Me(xgjE(Xa6KAbiHT4#J)HC|@wJr%*)_tHUvv%~EEulU)Ik4MY zBqLUr!Nr^?3sh}6@CK2RF4xTmCXYMgr?(^BtJDBsuv@cvjbY^X&4Aboz>6BC-=mXP z**~cb(Cj3WF|#-|V?80&0cLFk2un6JxW1ViY&Rt0vN|MrVu1jqak|iPG)m!GO0Py` zsgrYh>4k8gQil5&gXiGBp|C|Nxm6ha$I6nrRTu;{Ggew0m}c7QScU1Z)DD)<^TvSp zHQfZWw+VLeQd+&{TKmhnWP^{=i9GHGk;P!l+Llbzx6!-vKpqmUADjmmwg;UD0A;3G znv3Vog)O1sNB!Q3R@BXZKpp#`L^erJG{dUH*=E#V2=N4clHF*(uAD?M&_DMf>}93| zxALE6dSBdYlgN2VBTlf#N337W0s>lFxSle(H9%pD!CeI(a)_3$0(+A}64K&l$~WvJ zHp9phdeBfkU?KH@jbJsm_LFFtD4IO?$)kVw?|$_oAOA|kVc4aEpYvG;UeC#TH#TzWG;{Oqm=vP2y;U$kjL)AtX2W0y$7baNfiO^XLf( z`DyG0yuc@w{iebex$THB>cM;XpfvZLv)ayfdL6IsOviETbceWeGy}}+UrRG!`y3Su ziM|MxF8=Ieu{yDfL(%{+ze?R;IwrRny)MXTC1R?8Y}&AMHBisK)gftvxg<7(CGI(0 zStb`7IsHqGQkLhHb*6$%?Og%EENA(6a*o+TBfoR(+xeED4~@{2#x(mDbquY)6JRH6 z7%yeIdQ2$GJ({TiApKn|9}!hLuk@-^+9~+l?sg5x?Ji00StIRkPuBzO?yGf0JE!3d z>0#5(W#P?hX7o0dwU)ilpz~a#+g9mI7$$%OO?64N3(eMM0@~j|Yic}|8a&R0fvn^5 z?RFY;{%YGvp7$*FD_`!Bs9gmRiJVG>R=>5;p$DlHG{=*(ozkpTHjeQboUe5^*--~U zu@-`q-i)yFg6&9oTqNODq~_8V|*3O)VbpZri6&qd9BS zVyGkdA26`&39DWG-5r}IM5}LLQ+3IFA(u9bK6N&mUAcn{znMiEvaVgkaBYlPuAVTj zy?iCm3F>l^udHkiHmf$9PlQ=yR+&Yc)z|6DtiFD5R7NcDl4Ro}A(I zYo#^s*mPq26T3oTvsr}+;_sTud!ZvWpW<51{H>$Z>;2g3*J+Atns6mgV#1X?iJ8u* zm7OKZAyT!aHcxlJ_(qNVAMM?g5V(-mnF6)N=3rj zp0=iNv$6MbBZ+jmFRKiMb6|8cgB=TJ(8Z_r^R&ByVT>;8jE)<0_BTsHx4)V6nTRuC z>&rr<58KR&+8P z+s>A_KqrCwAf41AlHvP|u3(wavZ9ll)fhtwWO8#Lldl#;5bcTYz*OmE5>l(t1u|)~ z@We2gbhI{CL7PYz^F@<6r`*)1Py;M$P0W_)D|7cPorW6MbuOdRSf{OSyks5I+=i3| zSEyT%*S4;VB#=!luv)g;=mQH2K|!=kWK+%5cZgmVewHg_MIrf&212N1g?2Ga3&L3p`Vlew7S{(H^RAE>dIcphbVuOji6PV3U7i|0GczikA|Q@GeOmZ0UGPzF zCk*8}^C+QBR7nY$uqC2|G9N8}(G#SPm_NvZ4W=TW!EYnB*uE_J`e=l;xWygy|g00n!qLFgv($ zPzJlNA}#e{5ezc_Dh*JM66m#wV7QeA7iR9U9TQht;B z0pvALP4!F&t!RTqF_7fNaFrmus5Sp}6ecclbu$q)hagfe{!_I-T{_Lg3qxh3oIkSu5DP*N~S z<|PGN@*>gTwDgxHPQd~=f&dsOm=Hr0tlH3k)t*OP9?f-3^_Cl*E1g$!#6*w+VZVna+Gb{e^8G^;kigI(3P>#K6;Qwi6p$SQfdVR- zv?!ns)>7cqKQ0Pr>g8o9pgR0brhwLkk^*l2Iw+u3@C{QyPUL{6^eNzuJ_USxuK|Tp z1l(M0*3e;53iu+>V2}a^aQq{qfY>@E>Cw4T(rXM-z{Vg2Y_JckqJRyjfEa;90UL`@ zK%YXVfaZ6-G8wQT3i!&)P(a8+lyB}{4F$Z87(XZ=0Z&eh{J(ZN9baD+Jgun#}vd;uvUi4(v-C=TR z$W?)Iqk9gy zCAVYV4WxFn67p2vcxd zuO2JBJeYp%XIvRV^fSMjaW^D@)bgvlF_c(Yk0HT4W^ap8Vknv?5n<~@Y*C=s$+Q`P zBFt+;#9Arr>s|}{Ij*pl#+}%u*LqcJRaIS8jZjrnI$ce^l|`yT@vh~hPTbnN$~siP zv%4`@)HA(WrK*~$s(ELZGTkN2=?Z_hLhmxY6H=S8dt+(H5jaZg_f^y*TiP%dQxk5? zyK?+4ER;U%7U`zJ?P_)N>Hs&s62PbjZ^m8uMysa}z01`@E~~A*QLAUIS{Jma2hYe| z`Bhd=A9RbFzNLK=sESpJm^1VarJi}ji3U9#$b|=}c0_F?<5q6v4 z#XVhjzmeiMcqg~I6fIUOY6c6-=nwd9UO3-e0xY%LMN-v*9-{NrhJsu+fWlcpL5>`= zB6ECi`P(!M@S_V33iIU#3LwLEv%HA#-Ker5*Uca;9lU2k-knpGwhYug#>V=J3aydd z2zYI5iw0SFy=zJ@=z>TZdmd^HwHeiKbu{zlJUqwvJ?H`?|ZDS_p0 ztiPNPTm8=0gg4r95E{X7Kxkx*JW>5E2Q%X1pz0I4vf%e?u-bAE6Af8@m)CELPID?L zuMiXYyPv;twnYf60W)0C7pNVVLd*3;ClH<|I=xYmCZ5f7da;r9TF$=5RCaChv$AqU zsN_}Gt;O7UI;`qZoGUJ1!E{*irZX3~IZK8{L;Eq@M)|1qKtc=sC2%r(M4e!Ujb3ug zERMZhAsqO@=kHWkRq%r?7b($gg&m5I64$J zxkpncBbfDPww@!PIs{5ylEV*3mVr5YY~KUq!Knc;E_!`QLYS>3O@s-POg_nV zL+p&oTy*y!chAMhco1@TU$~JsxQn2YF3?KBlE=BfwMJBTVBf=NMR7)pfqf4Un}Jx# z(eVAKq+a?OIM{Tvw}iEzK?|9CO+kfQ;j?v2w@@-Rg^~gZ6VFiIL_8tL-4M$=?lW+* zqFJTWq$I?3NOAftD$-4dzU$rLspat?dTRN>q=a`UDd8QDQg>5Jx@l{5i6UO6EGZ!u zOAlYq$%kr&f5UaHYKz-LI}o_Gu+VH@lV~j^Bnt2%kh5)#TC1S%e-gA-OA9##oOUd7#BwTGQq7WzUTk!^W;Y~7tyFtQ6$-P?oF7@nhR#MMd4S+h%dQb`hOy)b1CtTD?c&iCM*U7$D z*CQ<_AJ6LKx4}IrhVkZ?0pka(k|p6Yfnq~CD^D)AZ74_f9o!D|Qq|MPl5f+GwzfK~ zGmCe{vpp~(@V?-W#V>oEz9v9om9F?}U#=@*FE(GN5lI4dR3M~YX}IQ0JV#8C@$7~U zvjJdY0wzeDi&gNnn;~?2hLI65GcY3Y?D1iqJ-z_XK0sT7@?L9mM&=GGB-p^(*iUF> zTA&!r+38@QqRD3gnb?d%e3X-vzjB>Kd9u#?>X$#}z%vQ*&Ms}>#!#X1T)Gxvk7>BS_zZ9B)AR2xtwh>XN?M!>d*b`e6 zSx(F$j?G-2)YO^w5e7dVnD!CoB?1(O1rREY!|sLya1{8Fg$}T#Q?aMKK3p+v%|r!F zQnz}*tI8a#nD)~Z(>Clk&+IVMK0-+6qD)(#jg$)}s28?k1{wCy=7x4)*!#8dt7OCUj_)Ei>FFx4dsFl^EzUm3#|M^=!8@$E7}a#|%wPWuUx2e`N` z+L3&OkcS@3+Y%&`+GVRJNR~b?2y1n-enexyckyrSL}fm}4$&boO^X)cH{Ppqp_yNL z9_e72vkTtYP`S{{gbL3;Xp93*Sh%xHKg1O zAeQYSUDyoJ)=05ZCAPf(8P24DIYdp%$rVL4YGxEwlZ)6}LN9Pki(Rm1mMRHOfJ(Nm zqV4E9m&i6fThCvPBk4Ng$k5x`aCbO{o4ncq3-V)$R;bOlj&8WqLGjNa@*y}qihHYZnPcp`ebz}ee3j3e)SbbBE z&X-ez?9MuIIS$*WI9_qKA%lZ8TsB!8;hCghP@#M#2D+DSoR$F1{Vey|p){VHcQc%{ zZ<*nIoy|4UDS<^~Z}ou;np94Ho$S5fBQAX$RfR(WM#3^{KYIVW7NPtG}rxFqNDPXq&|4MND& z!Cnv473KI3rb=c~B{S>#ihRW)s-%qOojbSFSAl)Z!LAK|^T9s$j{xiu`9Df78t{Lf zBn$izgv>l-$276)Jbx`PwMcJ8F;Vx~{(D<^ENO&A*sj+84Cf%Gxwe6oku9Q4y&8}D z`>>@%qUCGbTUF`^k49~4T=^(pZ+Cm4iuo(-*6n*I>%FUk4sdlvIR^6uT^+oJdYcm& zu_1*LLJz)Gw@@Q)zl{#E0o`L_zKwo(7E2Y<52dKKFI22dVruTeJ?ae<svxUj?(BQO!$ToDWDw7Ja$}A6q^7fT^80?t|NSQXnnhVWcw>vxd)vyei zW60bt*^e-}%C9s}3mP21Z6^bhhH!^oM^eMsRfqAnFgaQ-RYYtAN>IQo7>u6Igv|9| zV{zV|OxOBIan_z(O8ZYv+mnj}PqYJ-ZwH>BD)Qv)z!QSwcyhY`MDuepEw?~CU6b%H zCNpbzcYL4=N9@V$z>~xF+__>PONn+t=;5^sP(}v8s4=!N$|ZXYj=*lu&ye4B4Co%@Z1aI@ASPCH#uUq&iZ>$jsO^;PHB<0@}_ye^`zk?Iz7 z@ifG`%T z)Bi4feOM5FxowDTvTs$L6=bZh>|rJ0a5q-uf|7+cJy~eelZ7_5tO)GP>p3}FyYRa? zDMI=65~m1l!d^)cdNB03O#MucyG$u)TxeNxM@H0ew{fcY?=H_5>#AF3Yc-Jt3IpdI z8NiAI|3R>lnJpClYp!?!DLG2X+qu&gZh=#Gw{tgcceiqv+uf_VV}h92S@9s{5$^or z!abG6R;@z&qHF^<^cSz7dQO^gLq9V~+}@)X#EfaV-+FVhkvksHaPbAM>$slyMfICb zso!*;oAIhgdD$c8$vVYuCSe|XncQ`1ef&s&{yFstl*NaelNB<|ETyw$BM8m51)A3i z`b1>o!qK~Lq%4$5Yr+y{ zGL+(5jm&_uHKQs&Ri*i!M#5JjdL<#WeY8=^!s4uG4onM2NY|d!q0Wh&A)VM6(k#lY zU`B0n=>zD>e)J0 zd_9QFb`q@4rbx3_FfZ<@3@pNb*fFxwyKevmmv*xMY$SBK^JB4nHGnS!py ztrywTOuSRFJ^kKj8Da`mZ&L3Kmf_HHTJPg#^ zX9wnJ>kYYsGtnoQ`QNbG75Q&R@mcD+1d}6IEmZ#Q zMRy==K0c|N@@P}^b_Pv=0(k~<#R&2W@F4aOZ|MChk_=wMsaB13=Lh3s~$hY-IW>&VFpxWb;`BJCn^*NkoQNsU!?A ziiKNT;=M>n8iY!1&bL=hNYiZGwIgkC1=;I@Y0Ya>f_DKefDQ5<>UH=j+Ugc32HH9) z=9pWomzby|R=$QXLvEmzFimfzPs1j*(1*k#kg#3x}^B zxpE=-uFw2psOk{!dtV@3ewgq;0&G)R^ap%fl5%>EX&GPrZ6<12_7O`;5*O$7^D6z6 zR#lwR&z1TqX)q@R6XO&GKCBn*;^TIK@PL)axX+$YIfjNzwY^fC5EA?(T;#|dI@LkL zfzfMlV16Q8MKCW8e^Ky8k`RU?9TW$?NF{w_NlLRo#NQcX(f>THhy zIa3YDi|Ji<4i?C1s#M?-Fg05}MD6~e*Bz^Mor7(Y8|VDmn8*AsMQt$|O|G4$wEK^O zip9^(YVwCF{E{dn@ODLyOT-$G{);2OH#iy!NsC!0u(}?gLD=wl6`JHO$obydk2*|! zZVil*PJpAIctq@0%@_V4a}YCD*bZ}a=?`W>`-P7?l6^{&^t|JUY&NInjMMC%)Tyq+ zi7Htk5o{)0pFEzEW_ zDwojUTtD}#LT#;FE}HAV;W|9olC;KIS~lIY)pWmvP}&QFqWW*r9N6Odrqw?E$$5R> z3J(GC%nBV$(F1eHTt&66w7SLU+e=aH0)*|QsCEI0_EJ>40D<{|3biW%bFLC?h;B-BxO?_X zPApR=mZ>R(=+20beQmj~BL{)V)%Hz97RKxv;c!peMQcdT3t2OAR$zTXeWC+6$se0|;#UVqOtvK~Pu`M(V2y$31 zKz*Wh_Z`(5i6qEXBntg2=vh&*?-QE2jJKDL`y5)wL@kJ*yvSRV{og!CN-Je0x$FmlC|XbtNWVEtseORb zM8(rb)z?1Z$Frm28ua)0po2UKVW0){e_10Wq(fCCoI5OxTkp|Y zuu^+sd#319-l5(gY4*{SpH#;_!LMD>2d=E5f6?#J*WH;ybqE)wztZV@J;$nM-i`{@ zL-_$2$I31;i7cg%14luv#m>Lb0<$yUcy69?u#30z+lt){737$<^ zSRxl!)|9Ip?R+VRrd|ovI*w%NRBON8tyX0w5Q=J@Z`Qb)s2B-?Q^O9r; zKb^clWi}Aj`?AczVu2i$YXt*9G&YKZ4)Y+U_#c{fp$p8-ZfK{&%|@DEIpO z_5a!|{kBnSgPE3eiuI$t^=5nlw5?tFhROKx$*;-+B^wk!U%mUu;ssnynEY>?j34ce z7il+UApFDdg#bhuO$-0K^g{G#?}qz&(VF~*foE@8Q(z#&q=0!n+Fy6R=Z5To%dKIk!+oJz{YcJY*6qEBq#o;G8j`7pk z4-t^@C(*s86x48_7N7jn*=Xvfn2;}5E|{iSJ(7XmhmVEymHGO7y&@X#*!_5KgAAhU zclO4)^85zfKW9GB2t{dLzf>3_HHpjBtvGeB9_jny=alJ35Nqw;VkE4&f6ZiXZ8XKiXyTINLtm)+OmhzI^LZ5~$+a z*_yYvtm(0Bacg%~5m_D$gu}OWflc0igEklerrJYV#c>XE!qjLk={R5YhA}LNdHM#M zS^OD@$@r}G=WSgCQ^nH!7gpId#nYdM+L|T5m9Na(tOz>5EViff+9CR$Z^-_&c0s~6 zRSP~vlfAp^MKjWgd}F?Tw=E8&0?4mI>gn9Qbk4d&MNEFq#D%_X)kZpdxaY~rBa zp3d!5unryItX}Itz9HX8%S28+`WZU9wm5w@bhJ3nU9qm%Q}tXpTugtC_fO%TD)#7o zw|Js@-)3CT>X%;6X(q8D-th3iSkW99Kyjifzx-%-MM4ZU8%$cF0wYS4ObUD>C`%yt zC)pF-To;veW3lTXJsCHUnZIlOeV{OR86=$90x+zT`|)W+}DCZ!0sxRE8K zA`5t3#Pn+SRyRo}cpJ7@gPWqzDQr_{6~VGuoXL)E0-u&2eKKOHc~i-hDvAm*-R8C~ zYdnW7Bq{+~%fcYe#9`|qX^-y`w-#NQ?%nWuo?$iCE73c9yz`L-3JtuaS;&)CDp?&k zcU}0-gQ-rJYz4GKlASrtg~n(Kge^umqqrD(Bj^FhIEFGQHUO1QhfOPxX-R4=Mp>LX z0i_;C3|=MhmnH8VTh259q4ZjsTmVEYYX?%k>QEQ&k@W5EjeRgd1ApgvmeiW!xxXuk z#T>Xbrxapsu)A>xb~id$^6`0a%g0NYRWE^;p3?SWu*zdbHV7=Mp9&06He@zqYU~GN z!84B`pfRX3#`(+4zo@7-VnTJcamFzhT)sSPDF!(%c5YJ=6odss5hhER9umdKvDjOg zDK>bS;BC^K0f&T*i4Pzh(A@|< znest?E%{n#W=C(8k5Dl&D2M8}kCi20<69Lv4Aa`8g>ldJdQN1%r#Xs?^=x1Z_yIZE z5%f}L5n9+#6?Un^bM)UmYlN@j2w^Es2uA?BWOC46EkmFTapB3KXX;aY7vHDMp!R^k ziaP~oXXK=eAmWmfYTpPhD2IX_0s&Ug#xggAMmUhAzz$=3T)<*=_(r-#E5snL1@FGa z=baKjYpWOPsjYOlR(=DLiLHInhB%KQ0QT5r7Jp+YPZoTGv1P$;BpDfXKs<#K3nHm- zKzyhe_a$e1tM1#Y7wisShI)XDlb23@1EkxD3-cA`H_~*OyOx+cIgI-tq6}*T zO45ML=PQo()`-$K@)ahGMEM#QMwC0+)(aX0yEsV`B5-k(86Vj+vf7+{tN!ecZeG%P zHZavGGHrBqpin}gZod$CV~daaszS};X}a3u#i?YfEB5w$I@NtS*8`B9b+nXH$t!%> zL(N$JClhovu9s4>b8wxhvqO{X@kaM$^nX<%BwW>nU6uc6ZiBD_EEi`HUUyT9CSJfW z0eMPzFP}R92MxNS3KFM4)R*Z$klhebDy{LI0c6kD8IEho2efYmtS7`euP=z_bbyQm zMWUWXteLpGlt;0EtApqxk*sU1OGd5Wp@Zb=S|eA*yMK79cQp%N6pMVjb14=i%_)`@ zTbf^;U%LfrboG{!Xf4fGifH}+&7BE=l~vvUpXJ_}JNM3VLH0$S84-qMHW-*;QMn^3 zBq0iz83n`41qOzhVHS`C85EVbOsULBt*}ffF=^jiu*|efsjRFlt+edT(zmSFUQzy^ z?|Gi*&YcA?_5J_<=$+>|=Q+#o{Lb(E&TlzO5lpM757QbcGzH?D8E%_kFv48GP7KLw zBx_uss@9uIxEY);1`vifXmb|AUsyyS{=ykx0Y#=_w4KHZxnuPMxvoYzt1t|5)hD=? z#7MkTj)7b291oG!$E`U^5Qdjnh6*Y60oPe(xJue&2G&)DOR*AWt}xp$t9;Bmr^0H%H&5mW-Mz2e>-VakRk zOh<^(2OWVqgw;AmzU1Qi_F`RF?TyhdB=VsH5xKw4zdw;1W0WOHAB)lrf(`Fwry$J%!Kickfnd1Nc{=Z_KNl%E2Bo{l<{~`^ zW;mIdB}2htvxb!z=_iYoFeU%rMKBDLSw!-WWT+_8(@P2_hKi8ikUw( zLbBP3K{7pnmc)Ui2FYAbBHpPXPh6lf3*Bsn>@6eqRVR)iJs>+;seem)pe@Ic9**$^ z>0!Vx$C4h9kfikLnS{6Uf2pXPBrlMfR3+tPm8zt;rYN&&YdCNGo?$7tBdv#?gCURx{q*oj4wUi(>u?xx`#Oph4xq&?*q=Uggb-rh%jChj0skO zhbRX@oQWC;X{h+fK1L-9`HKmf%Piy<=6X8POvCteX=Gt>)QDwlWR#rcs0GrOCD3}Z#46jY zvn6fO4Wy2GV-5^O)8tSQ9y!{fXQ~LrztB+JvSv46E?ZXr4#$^CL`Z!KZ0I zRf#@sbc*{~t}%L%pgXS}f{uw-usaDAPEf_9FwmaVHCQM^t5Q65vqEM7kx$12@JJoV zL=N+E=GZZBm=}|~Vwi6AkkEWMhh=-(QReibh|Hi+fvH_mz(-{%;Cbd|>Ack5%z=ZP zkHSu3uFg7yx$Yz{uiSnpR$?qM+Ws&5)gi3lIy?u^2VsrEE}l#)Mq3?t#IzC5(R&J0 z{+^gO2rikI+DGO=0yQsX&9QHmB^`guUOo5!UXGmW^-5vN&WI)ezct@|?_}mX&WATs zynh<+v5Tiq^0egMRQ-h>(EP^uATscJ(tNp38bNOy55*%$MT$X;7Xwf@2qAUXn!E1e42nz8F()Vlw#}g{MwUJ;}qjuV(m~I+SV2#&oD1M+LsG zXtI;#D>559CWr2ZKE9&Ky?sTK`}v9{;VZKIu_$vPPN2!5D<||7*#r8Di0$huGAp%x zU(r}^(vo7U&Mq!Rga`}tj_)ff?d2=-OGt3)z}PwVB|J)N+%~U3o}zVxa1^cL!8m&_ zgH~gift~Fl!um6Si)gZO5sjtyEE*cPh)xN35$z9LM9lS^cU;J$2)+XHS(h6hAK=MP zO&1iTp2ogooIbsbw9GxBl~%8%#!y=_;UeBK)bdaXOc%00>fbnhiTl2xh=zTfJQKH< zOxEfV?S=6+@bEoCp)=?ICwI|Q+(jiWj+YJhqtUXUjC0!S@O9l;G68pwH~EShH%ZGU zFGFgIdtnRJxVR7!e8!MnAQu^>^maR0#_eR&#kif=)B%$;KBkF*hp41VD6Tu>ijqlJ z8Rt>SEZ@acQjY5h7t?fa7EY$=0mgfrOeNL5+)P!+bTbvtu4CHq1VWFW$#|K{t}3a- z7+yThtMVp$Vg03-VFFgq7F|Jn=(obvB?$p)m@X%m|2@l@#Z`Kxd@7zo4(vR5&h(f> zr5R6Ku!M-Vu;l8_ptW-@E160)JY?4|nLA)pPkWtBEveeZ$!aAvUK!SWw&nZ&Gqy|n z{T^qy8UI-<<3CHP`ab?Msto;SNdeX4 zKjTQ9ZN;@5l2LO-@kID6LZ8GMuLk$flx^PRZQhJ+B~!Mmbrs&UD~cz(^O$rKEwW@W z9HXg0lJpTGBtC0_e2J?`yn;mdvYIig-|5laGrft}_AW%#aA1PYu$ ze@!lCTU2V0Jf$j)Zp5!OQ3zF`2^H$+@g~W^g&kW7Ohrem!v*|awJK$Z((r6CX%7b7~BdzIU{IBWt4)(1f4+Q`%( z{(BM^p)cLg^wg28kg!9kcog~5Qp`a^$S8qF@u1SlqnLUTmt3kWwJ(f->U%lr9{B;^ z$EL1%B+L*PtPjhTXG#c6+J`a`fzkV5CL%D4@5@94?!rA`gx@dFgZfWqv}?GBNnY79;|nX9;Z3+m_g~Y?LNAzwwanH)oaN2g4)dA2-8*f&S0&`l z^%#p)+q|jUikXePS~AVtMu$zUbLiX&eqfSliC6GHV;STybsM;!>Gze%P&|PyiyF(d zhYbfqu;@zl(>KuIHhNxpX~YCY@&~Y}ezI4#9o{m2`7t?Yq$X-QVWcx+%F6ZJ+prX+C@uFM?Z%D1& z@ucYuPcBle3N6Z|S-s-ilq0ves@RWI|Z{VJ+6RZOb8=rYgvW-xM8`obK6vrLvtD!6~j4Uq^otbG!vIe5TPp%2h=2xQK_gX=*F0zjJk+n#>hW z0zT6i)c9VFL54Do@krUHmY_mTe@T7bNVms^@4yKlF)(SX&=Q#lU4!if=1q%+@f;JU zb%?b*xhO4lvj+Tcjdt%9aIL)rssinV#K3H%v(e!GP~7xlD3}fW%of)s_4w3FGd^{^ z>kb4xNh5fYyy~UiB;!?|YK|qXOEpRE8_X0b2O`w6r@*R_HxWW#Ig+X_BiGbC>eTncJ7I%%K`7_o|s)Tcfu&QbOulGdf4$_eKw*B ztt?;v7~j_~NXJN0OyaULx&1d~<}H&K0+4%N?tgHBTZThXIeIouroC*a&3JskGRK^| z+(*a7zgR<2jn8FvHpf3;D)7(J#f7V_UTE^NHsc5hiYZo4Qgd;RJI}K?_TQM-3(cVt z>q~|NURxwjxj&8bGl|Y0cx{>1WM(h)hGe|9$OLkLM6tW)lMr`pj$s(o6Xzs2XMM>@ zeZ02&aLxJ?STH>u&O`(g*pW;`FvUH9uh!2JOoG?^D2VW4I8~^ovqb9*L6c+|(x_k9fgT_;k?<^ds2wYYc}N>lvdSEPY_3p1eKAUu&a$%Am)rkTK|CA@^fVS7(f>40jFau#fxOTE+0y`vZLe zivxZMjCo?WbUoW+{Ofl#=Zz5>#|SbgHOptqDS*cWj52}}b%}e^CZ?;nWQrR+Fy?yjCiXHQH5$fazWl$#n<%n3 z9S#nu(pV?|ZLHc_OJKo<2K)AXpY06pAx-eLo@IM1hk21PVC#6&Lo5K%vLM4H;0q;g z?}Y*5NoKeWH#xClB=rynwOSxJvwA=a15WKpw1)-6sTXtmB$DHQBm_-KnoIQDlUFj1 zX=9u}ZD^5*ow^ynxaPG7a@KRTE%!v_YAvv-p3Cic9?B{9^n5P2xEN!!OXnHWxRuTG zSYs(xr-9;gKNDoi=&2PHxV??0JcJ^jEiD6DeJ8{&`@oELkwvoTqcWwK zbqdPjBBoKku zWNMJspeMc2x*W^*wHq#t(eC6ht8G7Srkdm5W%QT$FIpwi(Y* zI`c$AV;;rBr+b|x9QR7ZmP^E^4O7n-@bFeLECU#go9WGmgyd=AAZGODZ*yNaekQJ0TFE>$kqgyENM*&_de%E_tpfMd$MKL6KMij zOF7Z1GdcpAz%jl+=mUN^mL|Z;Vre(hM`;|CgC1@a>K^F??Fw!MLkbWJ(hOor8rfhp z*q7h|g`wC%(l{qh@hFttTY^V1GqNuPz;r}luyi;awA_Efg!CNN%b9T~?|4#~wVGeT zX^KOnxDr>x4h>Hu)I#hnw#uv?H~VNXApFC_6>SGc9!lvvSC?(TK2w#P%&9eVR5RK* z*v&!hqe;xCq&~?|Wo)l5wWKO#)$}zc20i@ORaR1@j!5Ud1H%xZ?9~wr(jKLXbeN{I zQexUv$cVO?lqN)(61|GNF)~pW`P-0-JoZS4QV+#tIOH}(Reg;X#=J4|iOz z@pQKAS)BW7&CET_r~6^j9(9FnxU9ty-uuvfk5(obKb!V@zNl+^mKSBH9ELO?EgOC9 z7+?Nf`bUQx7IxK}!{xBhULpN$nAntj@0Jk6-W$!|!*Inr-UwfR8{y+{gmgFd3LYPa zZ%TJN2BzLR9zwrnaA=0Yh_?o#+*fxl9KfF@M)Y75bgeHQA&jz^0iz?DlYX!hCsoQK z7Iv8cQU(YEQbwILA<|{7Wa_odq5ry--pif}WDmxOTdHYG{afI1TY^{PVoBz;)LEiG z%e;aL`=S(I@!+#$g{TPgFb}iLpT(1NY>$uFK1+G{t4_w7)PlW07Il5_*M#Upkij#1 zfHL3x^&sK*lnt{~S*#pa#ff4qA$(G22>Osx-9%0M0R&3UR6r{{Ct~lQ^ z?G>u7&v9}tGY5g&#vQUQ~%(LJd&_o-L;Byy#zbTievBs+ly^StYQV2&aa4Zeg#L@S?{Fy z94~JdoS!bgIAwd$C8KrPev~CwuGU=V)}3*c7pt>5WrGzjJd5*)Ea9u0a}xQv)2JkGePIM<-g?7-d;>~f=wV*rO9kPV!q=%`v+-UFcUsm^h_SqJB+RdqI3Pq{!Y#n4G;3l3_a7Ju3=Fkd24#JH(NJsbA=ki%h~n8IRw|bbd1pApdwPalgYV3xBjZz{J!#+v0FA zIrBKyPXk)^iS9qh;|BEvEF|VvXK8#A36IA@lwX3;!Q(|I^FsKPW{xf}{!caFHd1+nY`9WGV8nSVZf`A+VkKcgI;#vnd2-=a8Bs(1NkTtjA3GZ zho({OI1G`44pc)#aK-e-vW7A%nf#DMC|l-C4JR^bA^D->r8ae^rlj_e$ag~s7$UmE zh;qw8{nvpI!Eq4o26opO0bXMS4x`hzE!@V`Z6zv#GdUt=M~Hty82F|oXLIhXc&R}g zBCQZ-C1#L5Wfx<}Hi?o*&Zu5Oi*X)_21>-1OmgL-RaT;^FHQJOefbt(ms1Ez=;p}p z^|r-XYP&cZR>0%GmtUH$==$%Fnu(8S#1u>Zj-Rv&70o-1-u=Q;3FdN(`^Tt|s= zJcD*skLK`FiL{oHo)v#Cemr)Ehhr{B;5Ib71_x0!?C?Avn;}4|j^W z58SDTlDBXchz@b@I2SN-VP&aF+?b*@HNktrfe({M?zIYia_pnm;gefzZ z0biV$DzeS)XES88K;0LDdW)Ty2&iU2p=*0~d&+6o2%`SN zgAd!O6eNK=`!s!12(ve45*@4p6QCpmClAloDg>dD5eX{_*DNQ-ao8726kJQXN!=M& zm$+AQ=oYWmJArwVDdSi*nR;^kdXAe0ZX{Tao2KM-q+#klVL8k-j1Bxhni;`K)@-LE zr4(-^PBth>xf!&U5FwNsL?_~ef*VihG8korUT|=n}2t92$rBT}FPaE4zvYowR#b-94YW{L|Oo z5$R4S0}X!q;MYF+wVVGDwgWAO!!oJkS?OJ=YdE$2JZGL5)-Eo9C+1(QC#n>YT*Dbm zW)laILwqz+AoVz^rzDc83CM-neiboV`&0yo0eH(yU4O``%ik~p4Y;fTZJ%wWsTSh6 z1`!3(#R!n1GQm}%jspJmsfo}5XOx?h-r*G2<=8nK-{7IrIP>%$(_x;DwvjMZ&%O0_ z%3O}bsq@Ke9kkIQ{tHXi{1+z$shC&z!v=(r?rzWzP*}=0q_%8Ixmu)wW$IjxYT|a( zln#oEtPk@9hUZh}ar$<0!LRAI4A(-oWJ@wOqgvl~YnG8B`= z+c%;5+J;H!?qAkkpk=1UA=q_CgcS$lVbCC^A7?{}E_kQSj+)@VJa5#>Wbjyya+A8= zhgQIm>Fqr^sEMp3RUS%ne5PzN7@5+gBk_wS)PT;}XQDb@3k;)+=3% zs#3ejZvMc9QozcB=5*PbmmSs5Pe2sfbnQkSF{ zI*bD(As;sm)~!o*=QKRZG(MlK4!oa9$vN;X-O zOT$^*FW)3&_WXLD9hKgyg{V4&OL$X{QKxs-EApm_K8UH0P1iD`eV{o(>OdQfP3Gm+;^obO0 z&%rNY?Av%91>HQ3GluGl4H&fC1J8DeSrf+a^e-BL)42dbnH(kO!brdkTZP%>2Q}t0 zcc8kJZ~#v(Z+MLJ{8sA}E)GH3KpUxn%u3JxHO75*0@zlNfSrlt@0Y+mQ3BjH*(Kzr zqD0Upk0lChQ3O^W1Hy>#Bn;pO6VzeW~&EmDB0vw8JH_=+Lp+PQ@AjDPu9iR-hwu;R!jN?IvrR7DHk2N#zq^u3U(wv~E~S zkF~=A>u2U&D~FBW-drvdylGi;@OtR4vZmCMi-#_dA$}1sM0G!smIdoo%Ys*#H!ab= z$h_(7)FtLk%ZC^Ei#-Q-!th_TP|M0JHv@D#{8`63avO@82W`+a!0+aMaE1U%1&1hN z#2%Gm-cx)xjyK81W+1Vp!(dV;!<$C1vRtiHBZ610#||6c7ZG;_ND(3q2JhFu@H!j4 z&NNVs_M5MRmi^|B&op04su%bj2Wfh_NSA<|xmd^t)YyXh_e==%uGiZOe&qtl$Grer zAm&ZQsV8Wb**2cK^XWrYY6`<$`!m3vEI&+@!RXg=bpMhdF<76vhPvUL;| zpcp|u)F+IboUQu$Su^|{20hHzn8lYk!)zB58umNO-n^Muui40=MksNG3n!L<|CnfT!Nb%#iaI@!AvJDKzEf}7)!Lv3cm?I51?xwR$ z4yFa8U1oBq8`g(4jdJH1GxQeUdd5CTS-I#(&AQCq8U-$@mVcAgz*o_{{JaSLxL462 zzKLL2s?z5wnm1GLUS_2K7xg~!ChAq^v(PwYL+_h1T>_syeB@)jf}i~W!DJ@! zH8&9)nhCzDyt;_TulF0hi~6wgny&%D($r7bhAs^)Cx;~Rzyh(8jTOaCb>B^nXTEia zSW=W}Dnk`X<+Im8m3%51DF0Yq5rDAzqapi(gEMcS|BRKEiMcS}cHBrbCpVTCXDlSB zREmUe7=HR5DlJMqO0_9XmDq=0BnnS~)&XRvyiX~Uy;2?yQnE}tJLOlDGM>6i?U6Q2 znSu8qKi)g#ex)oJFr}I69kd%C&OfNIqSQb1&>jbNoOCPm9{mXb-^+V)uPtX-HaJfz z&%of|_sLHABc)6X2o9o2cFOM>I8r~<17@1tL@96p7y|x9KV@(vyAbc6o8AvQ0M3-1 zoprCW=7(pyA3rP8QZ{^smSVj+Yd58-Tag*FQ;OiIq!$4YrIgZcvPk7_r5xNV<>P)z z($QY@_gie8P%ZL328|k;EOAr$G6A_MJd%hXqS1(G)Td3q!w-Cp+5+l1$U7bj;m1zh zst++ngr;Jy5>!xQR1ISgDHvXdWv5>FEI~8Ay#U0$_Hl+$X2>|UWcRT3%-3dMYAmV) zUsSV!CkvJ6T`3-54iQ)(}El>yxabfH?as}UKqZ*$mBp_<#Q%vH?H@G>|eT!3XR&Jz&~ocEd& z=T-j=Snu;+Jz>43-+I9uKz|^U*FY*&RdPa`3X=g-Zvncu0L^Jal--gzK*UHI~*|Y>*(5J zlEo2x;wNbD|IcJ0R|quWOS|Hvgl70Fs>UMpJ+}qa%Nm5j1*` zjm|)cD94mpmw3eCSYR8Aycl?vzc<5gF#L=_!A$!1(4;(VftE!nWs`qXu{pFy;kWLt zXeyBP_{rxEStzg4@=;~|9hc@-O!f0IDttrtK%b~SXhX_$>Jb1+ep8b~pP(!F) zAGLFL7}W9CHthJES|OiWsZzfY|R32x*eNTgd%CBPRNY}ZHMM%K_`db zY;fvv`go37g$>G~EFLbc#}F5^o*f5NZ7l}r(V+DkOzR{4TCWHb5hL)%#$O^Lkodh5N z6CG6PChoGeWQ3C$R8UBT^@( z8US-#T59LzyJF2|;~52sh5}NPB@2y1BvP#5Xb6Nw(@wu?LYhc?$uUEbK^O_aHLGYu zYW@JDdMscDTn(@s%EIjsATn4H9w0L6*Uwr$0Ut9I6b>-6tB(#9iRJnO%pZZ_05c?( zX9gI49}O^7#R%~7f&qr*xYq#FVYK)dOyy13&Jqq%-C2jX;#0M4Z$@_cd z?MeJcl{_|6)cyL}qlxeBo%rKl;%iUIPpIVKnY=H3m!NTw8~ebIdlz-+e!k{q67Les zJy3mBV2sFo-TwuGJrzBwuRZO3SVc+q@UgK6Q7@Iq1b3-kV~DtVs8hHj_cF7>?jSz1 z$aqmN*9qr(R`O$Fnul4%Z6}$9BdMnuYAiV|bso22x;&p?WTmWP8!f+E%nmhQ)O5YS zw~9Rt*za{|w_(pPkZ7lfh%{PIL8KRD_dNl##tz3jolLwVwZDhPsR^|L)(jNS_}6m-Kf8*3cS*8tVSyy6E%bG{FlW?YlmsCl? z#w&pEGYW(39O8(&UUe>E0SB`xg_dxB0+T;jP9zeDX0!IisnEZbz{3H^SbfW&=vFfWFT3< zNj(iQ3LOut8|Y&v^_*E3|U_4 zJ}}=$jwuQ=XDr~?q&t4!MSAs}b{qsf7sGPE9t$=-dP6USg$y=-{A)U0t(#L93dcN> z>&-$sc}R7OhH#wpqI&3(c823g6fo2qoO}rSVU>WXMQ{l-q%hr5$2BKGf>nlqs9#0U zTa(;%8n-G5oXQ|$a&VVNGI^F?A|i^m7YwGNOreH1B-bjzJZT`NNiRHSk9I;n%swq%G4nWb=V z2Aq%#4VkfShPOW~%`7@dV_NXCj==Gd_A-F}%-BSo)XN!+iY)|vR_ZkN6z6v(_s3Jh zamz!%f9rr_a+1gpAkZI;qFN12pMo;OOGZ!t_rr`m+4sV5$as(=0)-n5+d(fav!1q7 z1){3RB=rDh6;7@YDNE5ABZR$ybAeq{|S84cT z(jX;$B85WVv=8LZR&og`(0x9FVcn7YcKb zw~?0y5(;x+c9JHKBNVcji9%)Zi4P0Cmg7dM<)5(C8#_D;HxLKTJ3%4>~arB*b+OLnski zB#lYDFgsd&^@Peat*GvSMU}qO9n6~MBB*edjm42UHj{{?V1E)D;#>EIqKZg_3FJgx zebO+AeA3_?<5(aj=K5mdacRXD6Z4Qj^IfRa3#fwvjmq%vQAa2!GCcA&fglNr|2AcS zT?BswMdR9~eQ>i74TDmayqW{45ofVNG={1H$>fVuxF3^~Y^I)~; zQ8(P=hz2{rs{Mnr^7F>uvJ*3XMz#GDbRLxA9%P_|dD3n^DCS!wqma z7=W;6;pGs?+);0+q$xJg3h&XLLN$@2ubKr~#BzQlk~=Z;WJ`_0+*6O=amY%(U^DnI zg(nx&G`%WIttrT4sqD#8naM(&B!uK*7!!*XwR4dLwbB9vvEC{rGMRg+qRbyOKp0c& z$ZE_E3P;6p%-Qsb)bjw7sv!#bDRt`s0^N54o+esQ9zz)5kuRA5ySPXm+RgJFmU+4s zJJ4ZG$EX@zIBAm6Q8;@IEPhz#3bPN?$l;Qj#V|$BAt58t4)UeyRpvwqr0SEPGm@+$ z2{2PoC{(ko$xNh@q%*_eut{P>sbD-9fjT1~o8fa2XNyv#vS|SErzaa%Pxvx&kvh%D zoRUBhl{2DemW&*ZvFst7t%{vstfq|0lqD1cmybPxb}XT|&=-n}PAC+sv_L2}#T`Q^ zK5ltgLh+jij>$PAvA-3-!P%TZ!H^uL!;0^GYbut+O?A4v#Y&A2R$ZIbBxe0J zI5!RRs917{S%6`dGON*mAaM&+sI@%Q+J{cuS~q1>|Ec!=|ay(0U^5%-0 zktm+L24R;E#Ykv8sTN^8_4SV*vJg6JEFY}YA;J}wHHWaHTn11hBPZB}1RJK~WOApa zatmeEregs8x-b;>uM3Gs=6deDrM)_a($JLh1E_x@qYtPvK<3o@hJ+BYSpfV%jx?cI zq$J5rTN1TVOHKy*L6}I{>IwF{5LVkT)oDXKegKgN0q$`mM~=?}{C$NH_7&0wW+F2$ zr;b3|Ov{c|$fv%1Gl4yz$Jc+|lx;-2#GV2tx}z{qa`DyL3>i*!0lN^e+$}5%!@s9h zlNwPB5%7wv?n;%-`tLHmOW`o8t%ab#M2o#yD{}N!$Q$aeF{>5v?(c z>^aHL1j`-h6t1HgxTLrbPNL2(MqA*duNrq158Ana2QyG54zba}0;$<=40u z@r`K?R7e)^;-)1c5n&BH(bryq|5m!*V_sAo2*y;%nn6 z_GwkJ76tH;cyn2;c^+{~G(0?88(I!Tc#>j2PtzWQVAhU*+E$)LDzemJhZLXpvs&|q zA2=kx^9AsNFRC{Xr|B9OZM2lKf?V_cT=O!yeyv=?dUAmzEFXPx&GmEDW^(;qxkmTo zs#LCneR5U%xhlh4EP%N7TJlZm$!EIYYl_sxyNBHM$@iPam5Q!PovCsZ!9+D!{YE~b zw7PxviA-A0Pdh$LOE;PpYqiIVZt?@*qSP7V!!ja-CH1Td%I|@Mp`Xe1UtuozzG9P^ zT>qt91({qztWFn}maK&PnSmuAg#DWD=a(uCqc$d1!?0yzrEiGn;(3tgWqL+@8!N1l zQy#SiRvi(LE6GjF-mIA2CWbD~r0F&>yL)2fYUp-r+?U%@q{NvS+-?W3JoOQqZ-iQIO{RAk901XMJNM$vUh{ z2WRNYOD`fgd{2|I)*@Qm=(zxvM3^v1SMud~&o zN#2&G&UIev=5+g-=GHA6>n~Z^+*;qVva@xifv>q~Q&&@CeP>fEaqDRRy4GgOZf{RF zn@DiIg0cru?na(Nd0x&lU-f{mu->o@4eja1rp}cc>s#vArYWRx>gY^2u4@Kg2ZQwA;^~rZKQO9q!n*plrEmI>K0|tFsBfl8-l_}I z9T5NQ*AK@pwuJVBtNwscS z<*li2q6zh0M|x9Nx}_nF4(9)}gY?;9^-p#<*(a=i&;CBRPxz!h;URs(qT%fP!}^4W z_X&%>veS?36CTwkJi1R<{gIu2Y@hJSeZu4VguOoD;y&S$KH*dPgva*@pV}upp-*^X zpKxiP@T5NB$$i38`h=(U377Q=m-h*y{bj~XI2O_^T^qdx%^j(Zj&$d;t!-&2xT~R4 zQA_IE>o<1P!778eGdC_xcWN**Z>-ULv94+Dx|PjoSRnj}L=gMht(Nt+9G}mh&r>u} z?`^Da3(7F{^eg#7m3C%p%bKROS@oG%g%_mjsq8|r{SF3eTWbrzwszJxuhazQ^IJa* z>w~?f+xcY1tzk5sn^)F1uI~~bf$4_{vN>diK@XHLEMuL4V&$6phR)XZ?A!x>5WB2j z&GZ1zW@_l%+?!S_!AljtY5W4zY_4x_s&6r<&-BLZ*=I5x9XEl&Qfo`Ptg~rj+G}cQ z>TIfSZhEhoWI+5@ucfuqTb=gWn5bIYH^T7R(+yqi9Zj3l&0DQs9woVHPfr$j@2`28|o{Xn^w2iiJFQxuP)GwYW{7vU4o-E#6&2I)jmg+!v^P4)_nn6L+#frgSq?)T#sI6aW4ePx1DymJKbfTUs*Tr+Q%n zJnNAl?XP$WFHJ3sg{DRiL2oT0OLkK0bl;H_Z)w>kbKa z72g&FX~H_+^!&*5+L&fytSqmpsWM1xOjnpOEi_uq)b}3h-2&nS+nKFh&5bP+JH5s< zIn4-2dtEcDy*2Hv8@+n3u4(N;##U=rXGc?``7(R<+ZQe_FDDIesf)bTNU@C_8k6JM zRW2N>ZzLnBZ!l6>hy##HTRmfko@q=sG;OSJ_9jl8?OE15@@Rg02Tzx0i1X$CR8A_d zjAT|b-v#e>o+>wN$Ae^2{2Qe;B+InH68yYkV{4<3bSrg;)~=-@$yd5FRsZJluDbO* zjbB@R!v>_vjtZZVl{K_(Z0mxFn)24yZ?5lXXm4ukG$Yj0{`b}-Y{)1;Lw z`G?>cu+aHNZJA%+NvoK0{7J%BewZ3G|3F&>1^&1h$x}2C&dZ|Ap|-y3b;dF0i<|Bd#IFY4Vs)fKjH3Ge6gyMSNFD>@OsX7^^# z)?UgGt>4B|^rL_NC_Z}ht>hJl16V^|p?Z#|?+Pyc%f{bQ!f%=%_S%SfT1y&v>JIz4iN4P-{zT(|K)@mnp-=%+9mdE zZp9#xEf;w(`ua8SKx7}o?Au%0+R}}0>o7uS=_IRekgq?#ji+emO}D#Gi>H;>`U+wC z5};^lPp>g7&{{!#Y3eKEHyZQor?Kp8I$lSG8K#rr6nO?>=!7u1${)lqUPbeDsW7RV@P6UJ;p+ ztp?f^4s(&=G1`~%t#Ri<E5z@myl>;TonMHn*P%*U*=1aY`|D0H_o;E&m|NGhrW0+4KfQR-o1QPBx{5Yc z)ZSva@kzszW>Wb&sFM)7pVsQ%o1P4gXB-Ul4gs)(y0VRu+ki)$;2*#v;c299UlR=R`sOq1TUszMNGnQv z8_`0%&UL8O=v_V&KtpP5T8poNI@XQwm82a!2NTv0oHD_dF=AUAnS+DY{jXv$%e-n? z_X3M>7Ah5i!UoqfT3#1+Sm}4x86$l`EQ`+ijrg#vHgKx8Wu@)SJ^C~n9Wjkn|9`#F z(`L+sY}7*DfbUDRNo{{AfH#}7bQ6C87Hv%7SbhMo-D~QAjGCFo=Z{KS!}$Kc25=fe z+Ug5Djnq|7T{4VBdtfqtg$}R2(|{C$$oD5mFGFycPvb!Of*C_I{_?QHxPnr#)0@yFr(UQ-(?2=U@%p`;-k%_mMuC;%cGhc*_;#!_26 zGe#~Vjf^0zJe^Ehu?D4sh+6%`yZA1Agz1cla*gaSJv{V4P+!1b;0qwcTfXeYPKvLxokzPDFgX8oiUikH8z75IxCSBd@XnHT&R%kUl&4XQa)i<9uWNgy#)JmRG z!o%`phsK-~7HKWuyV`nA8WEzYA%oR*#1|1CbduNBwR%H(Yv!}n&3BER3(2)cmbR=e z*~d4*+0$8J4|KG4wKw#lhs#Ol_gmf8ixEh&Y2^yOg*mh1cM-odw2sk{eyJM5C@z?kJZoV&F*SvIH`;yizNTo}=R;L;p14{B` z0yB08kMkWBBYEbKXB_wY|*|lag zu)6xrhIKwkT4RWlVIk-TAK^aFFv*15aGn@SI^jo?nPDY?FuZ~?#4jrOb@DD86#+v& zzdU|9{9Jw(zYp_G?Gi4o=cjo$JMIR;T{to7QSXsK@OKAePNUwdZ*Q;P8nE&}S0Meb zs8cxW#CV;CKO23L@Jqx?<7`Q9F-$7_l3(nPQPmSBA5UC9X{CGC@-FyrTUqmYcX97%NP- zt5$uTIQ7kUcnYU1G=LIerDjIPsJ!&J|*=JGBc9_A09IL864C;lSRY3zjG-?U8N8-V&4{(Xx!>;+n<&y1NE&4;>I zQ1^3mh~zD7`PN>ZA%FQ8?>F+hiQm7WVQe(whiBY3K7R%|!K~vu+$nF)DZX=a2~W{j zUyqf^jR@1mcg=On=|J^IgD)@Bpr$ojk(F`G$>#0J<{dgKOpKPs@Li^^y_6w)P2V~) z>8R&%(nuE&Z&W>DI7KJQ5gvx;5gteQW`3eg;p*f3gtu^9euDQ;@)K?8`#yfR@XJo~ zDZ-x);&>SPyp^X!4zE+*Pmg6G_4L-(%p#df#``cfTK9ucDH?`M8l_&Bt8kopxa8<*zKP+oD%FS{sb%|ygsmtS8k8_??G{|l~`60G6 zrki1rr=R9khK2O<05vvkU1_)lrCHXWD6g1u7V#5pgyF?O7}7MnsFCM$F8w}{KEKMg z=G7Hh-8azdzx_wl`u>e`0LRED5Ai$w-B+zI3UwAkU6g-1z!Yuw&e zzV}^M|K#5GPyXVGt1E^MesIZeHng?8?XjXKKJwF+O<$aFaLt2vj-NSn?8QH+n_IZ- zXv3k7<&O-1;a};$mEOJlzrXin_mh8VdFjy?o?Q9gOQV^-=!M2L&7C-?@!({nQ`S0H)jpZ!P>hwKWlPBu=9g@x{9*Wh-bJfn_+NU@3jYu9;=AE@8zP>b z6^;|mE-%q1oZly0&?j8jCwwkp@y)QlG~w*}HxVA6^?ftpYAsk26%<~}y+ z5BIJDz;yaT7l8$-D18@`Wu&|M|+3CY=AX4XGDCc;UT+jyz++ zbC2xUbLY6jADDXNc@w_k#^qI|E6)GP*+*V7;jcV=+nv`P*!1!GBS%d52k-yuDO*Zz z{?AK~M2@PyjY}_?_rZmaeExyDu;&j)#+&f>W?z2l53cO|x%KK)6MpHV_kHw-k6pEI^sCh-{Op0r!}rFk zUzqXgTob?p)b(sm@ z@bIbMdi0YUKD_VM6(+pvGpo0cZ$9v=Z@$`S!vFl`%7Tm5-0|t3zS?ZU6aMQvzxnXX zzx(?OuXdX7*Sn8?ZvUNM`AYn?Z6^Hww{2^Vu3UZfsjuxa;iZ-LSFbvH_rqtrc8v)y zpYY8OUUTK*8!mqBdK13qvPo^@xBTm=&ev`>;p@Ks?AJee&r`Qu_u8!{eEs&$-H)yO z?8~2h?SKhCcGLdrKHN6{8xOqpWfR^owCKKtDWP))_lbHuD`; zqM^ADU;5}r57ip_#?8LsKQDcB+GW=*HFiU+dfk7zTJ0H5HHvOA}_n7$z={*8F5y|M`KoQH_7Ke>u@^HsQBzdEv&7eR|`k>g-Mv zzH-C%FJF2|-rrW(+e~=Nh+l8L<(Xw)-eT`E;ctKUrgwels&`%WA^RE=KJDE%JoMeA zGafo%UvI)++V|1_S@Gq`ANigw8HuH^EBAfuo==|ro8Q{En((RD%=%UJw{QB)5&M7% zulVHNPq*Lr;6H~sUpC=K@BP!VrT0DgwJPUs6aHPQ z`>ns&cGD||o##zBIsew%7cJWStv@?2neeEa=Y92sYwvh}j(fy}r|x>kZOIqL9roM^ zJcy+dRUiD*v$rO1n&XNGBK_sxe9J30CI7hG9pZm~Y0m0jc%=v0+%YD6!gurutM{_g-`OYp`5=7EseX#*&#T^Cue8dWJJ*|8#l)Ja|E@meX-3i# zpJQ5uv+`;-((D(uWf7KcN0D*EB#LhpPLot=mqorxU>^2gUYW;8qc>z z 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 U%htBEtwx?XDlSjCGIlfkKc-%E{r~^~ literal 174668 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 bOexyfWu(!vwau-o(O0I#wEnG(jSK$=_hBoB diff --git a/scripts/package.json b/scripts/package.json index a5482fc29..056eff62d 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -22,7 +22,7 @@ "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.31.0", "@cosmjs/stargate": "^0.31.0", - "@cosmwasm/ts-codegen": "^0.30.1", + "@cosmwasm/ts-codegen": "^0.33.0", "chalk": "4.1.2", "copyfiles": "^2.4.1", "cosmjs-types": "^0.8.0", @@ -33,12 +33,12 @@ "wasm-pack": "^0.12.1" }, "devDependencies": { - "@babel/preset-env": "^7.22.7", + "@babel/preset-env": "^7.22.9", "@babel/preset-typescript": "^7.22.5", "@types/jest": "^29.5.3", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", - "eslint": "^8.44.0", + "eslint": "^8.45.0", "eslint-config-prettier": "^8.8.0", "jest": "^29.6.1", "prettier": "^3.0.0", diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts index 60f35edf1..d2c9dce82 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts index a18854cc9..ac85511d1 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts index 67af28fd2..ceea0e594 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index 2d35e803f..f7ef1687e 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/bundle.ts b/scripts/types/generated/mars-account-nft/bundle.ts index 8dc6478e4..b0ca12db2 100644 --- a/scripts/types/generated/mars-account-nft/bundle.ts +++ b/scripts/types/generated/mars-account-nft/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index ce1e29bc5..416214da2 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -12,7 +12,7 @@ import { Uint128, OracleBaseForString, ParamsBaseForString, - RedBankBaseForString, + RedBankUnchecked, SwapperBaseForString, ZapperBaseForString, InstantiateMsg, @@ -50,8 +50,6 @@ import { SharesResponseItem, ArrayOfDebtShares, DebtShares, - ArrayOfLentShares, - LentShares, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, ConfigResponse, @@ -59,7 +57,6 @@ import { ArrayOfCoin, Positions, DebtAmount, - LentAmount, VaultPositionValue, CoinValue, VaultUtilizationResponse, @@ -92,21 +89,6 @@ export interface MarsCreditManagerReadOnlyInterface { limit?: number startAfter?: string }) => Promise - allLentShares: ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string[][] - }) => Promise - totalLentShares: () => Promise - allTotalLentShares: ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string - }) => Promise allVaultPositions: ({ limit, startAfter, @@ -143,9 +125,6 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn this.allDebtShares = this.allDebtShares.bind(this) this.totalDebtShares = this.totalDebtShares.bind(this) this.allTotalDebtShares = this.allTotalDebtShares.bind(this) - this.allLentShares = this.allLentShares.bind(this) - this.totalLentShares = this.totalLentShares.bind(this) - this.allTotalLentShares = this.allTotalLentShares.bind(this) this.allVaultPositions = this.allVaultPositions.bind(this) this.estimateProvideLiquidity = this.estimateProvideLiquidity.bind(this) this.estimateWithdrawLiquidity = this.estimateWithdrawLiquidity.bind(this) @@ -229,39 +208,6 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn }, }) } - allLentShares = async ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string[][] - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - all_lent_shares: { - limit, - start_after: startAfter, - }, - }) - } - totalLentShares = async (): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - total_lent_shares: {}, - }) - } - allTotalLentShares = async ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - all_total_lent_shares: { - limit, - start_after: startAfter, - }, - }) - } allVaultPositions = async ({ limit, startAfter, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 3d079498b..5373877f3 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -13,7 +13,7 @@ import { Uint128, OracleBaseForString, ParamsBaseForString, - RedBankBaseForString, + RedBankUnchecked, SwapperBaseForString, ZapperBaseForString, InstantiateMsg, @@ -51,8 +51,6 @@ import { SharesResponseItem, ArrayOfDebtShares, DebtShares, - ArrayOfLentShares, - LentShares, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, ConfigResponse, @@ -60,7 +58,6 @@ import { ArrayOfCoin, Positions, DebtAmount, - LentAmount, VaultPositionValue, CoinValue, VaultUtilizationResponse, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index fb0106416..59b3c61b1 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -13,7 +13,7 @@ import { Uint128, OracleBaseForString, ParamsBaseForString, - RedBankBaseForString, + RedBankUnchecked, SwapperBaseForString, ZapperBaseForString, InstantiateMsg, @@ -51,8 +51,6 @@ import { SharesResponseItem, ArrayOfDebtShares, DebtShares, - ArrayOfLentShares, - LentShares, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, ConfigResponse, @@ -60,7 +58,6 @@ import { ArrayOfCoin, Positions, DebtAmount, - LentAmount, VaultPositionValue, CoinValue, VaultUtilizationResponse, @@ -126,30 +123,6 @@ export const marsCreditManagerQueryKeys = { args, }, ] as const, - allLentShares: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], - method: 'all_lent_shares', - args, - }, - ] as const, - totalLentShares: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], - method: 'total_lent_shares', - args, - }, - ] as const, - allTotalLentShares: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], - method: 'all_total_lent_shares', - args, - }, - ] as const, allVaultPositions: (contractAddress: string | undefined, args?: Record) => [ { @@ -285,66 +258,6 @@ export function useMarsCreditManagerAllVaultPositionsQuery< { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsCreditManagerAllTotalLentSharesQuery - extends MarsCreditManagerReactQuery { - args: { - limit?: number - startAfter?: string - } -} -export function useMarsCreditManagerAllTotalLentSharesQuery({ - client, - args, - options, -}: MarsCreditManagerAllTotalLentSharesQuery) { - return useQuery( - marsCreditManagerQueryKeys.allTotalLentShares(client?.contractAddress, args), - () => - client - ? client.allTotalLentShares({ - limit: args.limit, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsCreditManagerTotalLentSharesQuery - extends MarsCreditManagerReactQuery {} -export function useMarsCreditManagerTotalLentSharesQuery({ - client, - options, -}: MarsCreditManagerTotalLentSharesQuery) { - return useQuery( - marsCreditManagerQueryKeys.totalLentShares(client?.contractAddress), - () => (client ? client.totalLentShares() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsCreditManagerAllLentSharesQuery - extends MarsCreditManagerReactQuery { - args: { - limit?: number - startAfter?: string[][] - } -} -export function useMarsCreditManagerAllLentSharesQuery({ - client, - args, - options, -}: MarsCreditManagerAllLentSharesQuery) { - return useQuery( - marsCreditManagerQueryKeys.allLentShares(client?.contractAddress, args), - () => - client - ? client.allLentShares({ - limit: args.limit, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} export interface MarsCreditManagerAllTotalDebtSharesQuery extends MarsCreditManagerReactQuery { args: { diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index f5f4f4978..b64727b90 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -9,7 +9,7 @@ export type HealthContractBaseForString = string export type Uint128 = string export type OracleBaseForString = string export type ParamsBaseForString = string -export type RedBankBaseForString = string +export type RedBankUnchecked = string export type SwapperBaseForString = string export type ZapperBaseForString = string export interface InstantiateMsg { @@ -18,7 +18,7 @@ export interface InstantiateMsg { oracle: OracleBaseForString owner: string params: ParamsBaseForString - red_bank: RedBankBaseForString + red_bank: RedBankUnchecked swapper: SwapperBaseForString zapper: ZapperBaseForString } @@ -344,7 +344,7 @@ export interface ConfigUpdates { health_contract?: HealthContractBaseForString | null max_unlocking_positions?: Uint128 | null oracle?: OracleBaseForString | null - red_bank?: RedBankBaseForString | null + red_bank?: RedBankUnchecked | null rewards_collector?: string | null swapper?: SwapperBaseForString | null zapper?: ZapperBaseForString | null @@ -396,21 +396,6 @@ export type QueryMsg = start_after?: string | null } } - | { - all_lent_shares: { - limit?: number | null - start_after?: [string, string] | null - } - } - | { - total_lent_shares: string - } - | { - all_total_lent_shares: { - limit?: number | null - start_after?: string | null - } - } | { all_vault_positions: { limit?: number | null @@ -472,11 +457,6 @@ export interface DebtShares { denom: string shares: Uint128 } -export type ArrayOfLentShares = LentShares[] -export interface LentShares { - denom: string - shares: Uint128 -} export type ArrayOfVaultPositionResponseItem = VaultPositionResponseItem[] export interface VaultPositionResponseItem { account_id: string @@ -506,7 +486,7 @@ export interface Positions { account_id: string debts: DebtAmount[] deposits: Coin[] - lends: LentAmount[] + lends: Coin[] vaults: VaultPosition[] } export interface DebtAmount { @@ -514,11 +494,6 @@ export interface DebtAmount { denom: string shares: Uint128 } -export interface LentAmount { - amount: Uint128 - denom: string - shares: Uint128 -} export interface VaultPositionValue { base_coin: CoinValue vault_coin: CoinValue diff --git a/scripts/types/generated/mars-credit-manager/bundle.ts b/scripts/types/generated/mars-credit-manager/bundle.ts index f9684e57d..ce848a6da 100644 --- a/scripts/types/generated/mars-credit-manager/bundle.ts +++ b/scripts/types/generated/mars-credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index 3709a1ebd..5ad60802c 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -19,7 +19,6 @@ import { Positions, DebtAmount, Coin, - LentAmount, VaultPosition, LockingVaultAmount, VaultUnlockingPosition, @@ -33,8 +32,6 @@ import { SharesResponseItem, ArrayOfDebtShares, DebtShares, - ArrayOfLentShares, - LentShares, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, ConfigResponse, @@ -72,21 +69,6 @@ export interface MarsMockCreditManagerReadOnlyInterface { limit?: number startAfter?: string }) => Promise - allLentShares: ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string[][] - }) => Promise - totalLentShares: () => Promise - allTotalLentShares: ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string - }) => Promise allVaultPositions: ({ limit, startAfter, @@ -123,9 +105,6 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe this.allDebtShares = this.allDebtShares.bind(this) this.totalDebtShares = this.totalDebtShares.bind(this) this.allTotalDebtShares = this.allTotalDebtShares.bind(this) - this.allLentShares = this.allLentShares.bind(this) - this.totalLentShares = this.totalLentShares.bind(this) - this.allTotalLentShares = this.allTotalLentShares.bind(this) this.allVaultPositions = this.allVaultPositions.bind(this) this.estimateProvideLiquidity = this.estimateProvideLiquidity.bind(this) this.estimateWithdrawLiquidity = this.estimateWithdrawLiquidity.bind(this) @@ -209,39 +188,6 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe }, }) } - allLentShares = async ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string[][] - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - all_lent_shares: { - limit, - start_after: startAfter, - }, - }) - } - totalLentShares = async (): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - total_lent_shares: {}, - }) - } - allTotalLentShares = async ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - all_total_lent_shares: { - limit, - start_after: startAfter, - }, - }) - } allVaultPositions = async ({ limit, startAfter, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index d9b3e812f..8464316bd 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -20,7 +20,6 @@ import { Positions, DebtAmount, Coin, - LentAmount, VaultPosition, LockingVaultAmount, VaultUnlockingPosition, @@ -34,8 +33,6 @@ import { SharesResponseItem, ArrayOfDebtShares, DebtShares, - ArrayOfLentShares, - LentShares, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, ConfigResponse, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index 5ee09ccd2..7869e8b0e 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -20,7 +20,6 @@ import { Positions, DebtAmount, Coin, - LentAmount, VaultPosition, LockingVaultAmount, VaultUnlockingPosition, @@ -34,8 +33,6 @@ import { SharesResponseItem, ArrayOfDebtShares, DebtShares, - ArrayOfLentShares, - LentShares, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, ConfigResponse, @@ -113,30 +110,6 @@ export const marsMockCreditManagerQueryKeys = { args, }, ] as const, - allLentShares: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], - method: 'all_lent_shares', - args, - }, - ] as const, - totalLentShares: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], - method: 'total_lent_shares', - args, - }, - ] as const, - allTotalLentShares: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], - method: 'all_total_lent_shares', - args, - }, - ] as const, allVaultPositions: (contractAddress: string | undefined, args?: Record) => [ { @@ -272,66 +245,6 @@ export function useMarsMockCreditManagerAllVaultPositionsQuery< { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsMockCreditManagerAllTotalLentSharesQuery - extends MarsMockCreditManagerReactQuery { - args: { - limit?: number - startAfter?: string - } -} -export function useMarsMockCreditManagerAllTotalLentSharesQuery({ - client, - args, - options, -}: MarsMockCreditManagerAllTotalLentSharesQuery) { - return useQuery( - marsMockCreditManagerQueryKeys.allTotalLentShares(client?.contractAddress, args), - () => - client - ? client.allTotalLentShares({ - limit: args.limit, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsMockCreditManagerTotalLentSharesQuery - extends MarsMockCreditManagerReactQuery {} -export function useMarsMockCreditManagerTotalLentSharesQuery({ - client, - options, -}: MarsMockCreditManagerTotalLentSharesQuery) { - return useQuery( - marsMockCreditManagerQueryKeys.totalLentShares(client?.contractAddress), - () => (client ? client.totalLentShares() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsMockCreditManagerAllLentSharesQuery - extends MarsMockCreditManagerReactQuery { - args: { - limit?: number - startAfter?: string[][] - } -} -export function useMarsMockCreditManagerAllLentSharesQuery({ - client, - args, - options, -}: MarsMockCreditManagerAllLentSharesQuery) { - return useQuery( - marsMockCreditManagerQueryKeys.allLentShares(client?.contractAddress, args), - () => - client - ? client.allLentShares({ - limit: args.limit, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} export interface MarsMockCreditManagerAllTotalDebtSharesQuery extends MarsMockCreditManagerReactQuery { args: { diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 636fcdcee..1137cd4c1 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -30,7 +30,7 @@ export interface Positions { account_id: string debts: DebtAmount[] deposits: Coin[] - lends: LentAmount[] + lends: Coin[] vaults: VaultPosition[] } export interface DebtAmount { @@ -43,11 +43,6 @@ export interface Coin { denom: string [k: string]: unknown } -export interface LentAmount { - amount: Uint128 - denom: string - shares: Uint128 -} export interface VaultPosition { amount: VaultPositionAmount vault: VaultBaseForAddr @@ -103,21 +98,6 @@ export type QueryMsg = start_after?: string | null } } - | { - all_lent_shares: { - limit?: number | null - start_after?: [string, string] | null - } - } - | { - total_lent_shares: string - } - | { - all_total_lent_shares: { - limit?: number | null - start_after?: string | null - } - } | { all_vault_positions: { limit?: number | null @@ -161,11 +141,6 @@ export interface DebtShares { denom: string shares: Uint128 } -export type ArrayOfLentShares = LentShares[] -export interface LentShares { - denom: string - shares: Uint128 -} export type ArrayOfVaultPositionResponseItem = VaultPositionResponseItem[] export interface VaultPositionResponseItem { account_id: string diff --git a/scripts/types/generated/mars-mock-credit-manager/bundle.ts b/scripts/types/generated/mars-mock-credit-manager/bundle.ts index 77553d891..a4c5d75e7 100644 --- a/scripts/types/generated/mars-mock-credit-manager/bundle.ts +++ b/scripts/types/generated/mars-mock-credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts index 551a1f36d..25b7eacd1 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts index 24af8211f..1e7c23f47 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts index 741950b0e..41bda5bb4 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts index 372d9e005..41a4636fb 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/bundle.ts b/scripts/types/generated/mars-mock-oracle/bundle.ts index 5c78f5e26..b5e4d4668 100644 --- a/scripts/types/generated/mars-mock-oracle/bundle.ts +++ b/scripts/types/generated/mars-mock-oracle/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index a322d9a07..516d2eff5 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -67,17 +67,21 @@ export interface MarsMockRedBankReadOnlyInterface { user: string }) => Promise userCollateral: ({ + accountId, denom, user, }: { + accountId?: string denom: string user: string }) => Promise userCollaterals: ({ + accountId, limit, startAfter, user, }: { + accountId?: string limit?: number startAfter?: string user: string @@ -212,30 +216,36 @@ export class MarsMockRedBankQueryClient implements MarsMockRedBankReadOnlyInterf }) } userCollateral = async ({ + accountId, denom, user, }: { + accountId?: string denom: string user: string }): Promise => { return this.client.queryContractSmart(this.contractAddress, { user_collateral: { + account_id: accountId, denom, user, }, }) } userCollaterals = async ({ + accountId, limit, startAfter, user, }: { + accountId?: string limit?: number startAfter?: string user: string }): Promise => { return this.client.queryContractSmart(this.contractAddress, { user_collaterals: { + account_id: accountId, limit, start_after: startAfter, user, @@ -364,16 +374,23 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa _funds?: Coin[], ) => Promise deposit: ( + { + accountId, + }: { + accountId?: string + }, fee?: number | StdFee | 'auto', memo?: string, _funds?: Coin[], ) => Promise withdraw: ( { + accountId, amount, denom, recipient, }: { + accountId?: string amount?: Uint128 denom: string recipient?: string @@ -581,6 +598,11 @@ export class MarsMockRedBankClient ) } deposit = async ( + { + accountId, + }: { + accountId?: string + }, fee: number | StdFee | 'auto' = 'auto', memo?: string, _funds?: Coin[], @@ -589,7 +611,9 @@ export class MarsMockRedBankClient this.sender, this.contractAddress, { - deposit: {}, + deposit: { + account_id: accountId, + }, }, fee, memo, @@ -598,10 +622,12 @@ export class MarsMockRedBankClient } withdraw = async ( { + accountId, amount, denom, recipient, }: { + accountId?: string amount?: Uint128 denom: string recipient?: string @@ -615,6 +641,7 @@ export class MarsMockRedBankClient this.contractAddress, { withdraw: { + account_id: accountId, amount, denom, recipient, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts index d62238c64..c4ffdbfbe 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -75,13 +75,22 @@ export interface MarsMockRedBankMessage { }, _funds?: Coin[], ) => MsgExecuteContractEncodeObject - deposit: (_funds?: Coin[]) => MsgExecuteContractEncodeObject + deposit: ( + { + accountId, + }: { + accountId?: string + }, + _funds?: Coin[], + ) => MsgExecuteContractEncodeObject withdraw: ( { + accountId, amount, denom, recipient, }: { + accountId?: string amount?: Uint128 denom: string recipient?: string @@ -274,7 +283,14 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }), } } - deposit = (_funds?: Coin[]): MsgExecuteContractEncodeObject => { + deposit = ( + { + accountId, + }: { + accountId?: string + }, + _funds?: Coin[], + ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -282,7 +298,9 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - deposit: {}, + deposit: { + account_id: accountId, + }, }), ), funds: _funds, @@ -291,10 +309,12 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { } withdraw = ( { + accountId, amount, denom, recipient, }: { + accountId?: string amount?: Uint128 denom: string recipient?: string @@ -309,6 +329,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { msg: toUtf8( JSON.stringify({ withdraw: { + account_id: accountId, amount, denom, recipient, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index d51b8c185..76512df99 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -253,6 +253,7 @@ export function useMarsMockRedBankUserPositionQuery extends MarsMockRedBankReactQuery { args: { + accountId?: string limit?: number startAfter?: string user: string @@ -268,6 +269,7 @@ export function useMarsMockRedBankUserCollateralsQuery client ? client.userCollaterals({ + accountId: args.accountId, limit: args.limit, startAfter: args.startAfter, user: args.user, @@ -279,6 +281,7 @@ export function useMarsMockRedBankUserCollateralsQuery extends MarsMockRedBankReactQuery { args: { + accountId?: string denom: string user: string } @@ -293,6 +296,7 @@ export function useMarsMockRedBankUserCollateralQuery client ? client.userCollateral({ + accountId: args.accountId, denom: args.denom, user: args.user, }) @@ -551,6 +555,7 @@ export function useMarsMockRedBankBorrowMutation( export interface MarsMockRedBankWithdrawMutation { client: MarsMockRedBankClient msg: { + accountId?: string amount?: Uint128 denom: string recipient?: string @@ -574,6 +579,9 @@ export function useMarsMockRedBankWithdrawMutation( } export interface MarsMockRedBankDepositMutation { client: MarsMockRedBankClient + msg: { + accountId?: string + } args?: { fee?: number | StdFee | 'auto' memo?: string @@ -587,7 +595,7 @@ export function useMarsMockRedBankDepositMutation( >, ) { return useMutation( - ({ client, args: { fee, memo, funds } = {} }) => client.deposit(fee, memo, funds), + ({ client, msg, args: { fee, memo, funds } = {} }) => client.deposit(msg, fee, memo, funds), options, ) } diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index bd6fbf61f..8d7a95b93 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -37,10 +37,13 @@ export type ExecuteMsg = } } | { - deposit: {} + deposit: { + account_id?: string | null + } } | { withdraw: { + account_id?: string | null amount?: Uint128 | null denom: string recipient?: string | null @@ -144,12 +147,14 @@ export type QueryMsg = } | { user_collateral: { + account_id?: string | null denom: string user: string } } | { user_collaterals: { + account_id?: string | null limit?: number | null start_after?: string | null user: string diff --git a/scripts/types/generated/mars-mock-red-bank/bundle.ts b/scripts/types/generated/mars-mock-red-bank/bundle.ts index a3d47dd39..c84ef8a33 100644 --- a/scripts/types/generated/mars-mock-red-bank/bundle.ts +++ b/scripts/types/generated/mars-mock-red-bank/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts index 1a768125a..b4f6ec669 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts index d2ba7f57a..519565852 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts index a0b667cb5..7471e9544 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts index 17e9825cd..34bbafd00 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/bundle.ts b/scripts/types/generated/mars-mock-vault/bundle.ts index 09436699b..13a0df976 100644 --- a/scripts/types/generated/mars-mock-vault/bundle.ts +++ b/scripts/types/generated/mars-mock-vault/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-params/MarsParams.client.ts b/scripts/types/generated/mars-params/MarsParams.client.ts index ef6a19f05..294fbdb36 100644 --- a/scripts/types/generated/mars-params/MarsParams.client.ts +++ b/scripts/types/generated/mars-params/MarsParams.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-params/MarsParams.message-composer.ts b/scripts/types/generated/mars-params/MarsParams.message-composer.ts index 492e5f22e..cc58c0561 100644 --- a/scripts/types/generated/mars-params/MarsParams.message-composer.ts +++ b/scripts/types/generated/mars-params/MarsParams.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-params/MarsParams.react-query.ts b/scripts/types/generated/mars-params/MarsParams.react-query.ts index 9b4c1121e..520fa75dc 100644 --- a/scripts/types/generated/mars-params/MarsParams.react-query.ts +++ b/scripts/types/generated/mars-params/MarsParams.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-params/MarsParams.types.ts b/scripts/types/generated/mars-params/MarsParams.types.ts index 6c2e27625..06be3ad82 100644 --- a/scripts/types/generated/mars-params/MarsParams.types.ts +++ b/scripts/types/generated/mars-params/MarsParams.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-params/bundle.ts b/scripts/types/generated/mars-params/bundle.ts index 41fb67b93..ff151def8 100644 --- a/scripts/types/generated/mars-params/bundle.ts +++ b/scripts/types/generated/mars-params/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts index af6f9a99f..7975e3670 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -25,7 +25,6 @@ import { Positions, DebtAmount, Coin, - LentAmount, VaultPosition, LockingVaultAmount, VaultUnlockingPosition, diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts index af6f9a99f..7975e3670 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -25,7 +25,6 @@ import { Positions, DebtAmount, Coin, - LentAmount, VaultPosition, LockingVaultAmount, VaultUnlockingPosition, diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts index 3edd78ad6..eac6eec57 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -25,7 +25,6 @@ import { Positions, DebtAmount, Coin, - LentAmount, VaultPosition, LockingVaultAmount, VaultUnlockingPosition, diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts index dd72dfbb6..de9ab3311 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -77,7 +77,7 @@ export interface Positions { account_id: string debts: DebtAmount[] deposits: Coin[] - lends: LentAmount[] + lends: Coin[] vaults: VaultPosition[] } export interface DebtAmount { @@ -90,11 +90,6 @@ export interface Coin { denom: string [k: string]: unknown } -export interface LentAmount { - amount: Uint128 - denom: string - shares: Uint128 -} export interface VaultPosition { amount: VaultPositionAmount vault: VaultBaseForAddr diff --git a/scripts/types/generated/mars-rover-health-computer/bundle.ts b/scripts/types/generated/mars-rover-health-computer/bundle.ts index 2bedb8ef9..6293c4bca 100644 --- a/scripts/types/generated/mars-rover-health-computer/bundle.ts +++ b/scripts/types/generated/mars-rover-health-computer/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts index 5e977309a..a5aad0a3c 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts index 3ad63f380..6a04f642c 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts index fd11d4b6b..078bebe94 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts index 15c316e26..2baa48f36 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-types/bundle.ts b/scripts/types/generated/mars-rover-health-types/bundle.ts index 6e5aaf5b4..3b5018ea8 100644 --- a/scripts/types/generated/mars-rover-health-types/bundle.ts +++ b/scripts/types/generated/mars-rover-health-types/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.client.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.client.ts index 20cb9f308..835f5c332 100644 --- a/scripts/types/generated/mars-rover-health/MarsRoverHealth.client.ts +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts index 82f57f9ba..25c5073e9 100644 --- a/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts index 7dad1a045..7d2887532 100644 --- a/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts index 15c316e26..2baa48f36 100644 --- a/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health/bundle.ts b/scripts/types/generated/mars-rover-health/bundle.ts index 17b65c7de..f6ed130bf 100644 --- a/scripts/types/generated/mars-rover-health/bundle.ts +++ b/scripts/types/generated/mars-rover-health/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts index 0f0f8b72d..0de1840fc 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts index 87590c048..2aa6622c1 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts index 0c7423ac8..c1a0960a3 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts index cb948d183..a31209939 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/bundle.ts b/scripts/types/generated/mars-swapper-base/bundle.ts index 23d5a092f..4081ec530 100644 --- a/scripts/types/generated/mars-swapper-base/bundle.ts +++ b/scripts/types/generated/mars-swapper-base/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client.ts b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client.ts index 42a2e06e5..3956ae6b2 100644 --- a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client.ts +++ b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.message-composer.ts b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.message-composer.ts index 44949f44f..976853c40 100644 --- a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.message-composer.ts +++ b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.react-query.ts b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.react-query.ts index 08701f352..5233cb560 100644 --- a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.react-query.ts +++ b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.types.ts b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.types.ts index 0046c3fb7..fcba8dcc6 100644 --- a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.types.ts +++ b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-osmosis/bundle.ts b/scripts/types/generated/mars-swapper-osmosis/bundle.ts index 5af932c8a..82c4e575c 100644 --- a/scripts/types/generated/mars-swapper-osmosis/bundle.ts +++ b/scripts/types/generated/mars-swapper-osmosis/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts index 76c6d1909..27526a515 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts index 2c09e9b6d..d9e3e4049 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts index 6f09e0720..4a696cb73 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts index d367530a7..0187a8a97 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v2-zapper-base/bundle.ts b/scripts/types/generated/mars-v2-zapper-base/bundle.ts index d45d51de2..0de79da53 100644 --- a/scripts/types/generated/mars-v2-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-v2-zapper-base/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts index f2e74e60b..9f853c576 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts index d7253b97a..4926738b8 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts index ef5de2b44..be8d0055f 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts index baf3d1b46..2004e3271 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v3-zapper-base/bundle.ts b/scripts/types/generated/mars-v3-zapper-base/bundle.ts index 1605a0b6a..9770f90f3 100644 --- a/scripts/types/generated/mars-v3-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-v3-zapper-base/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 922271826..86858e4e4 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -32,6 +32,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.6.tgz#15606a20341de59ba02cd2fcc5086fcbe73bf544" integrity sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg== +"@babel/compat-data@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" + integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== + "@babel/core@7.18.10": version "7.18.10" resolved "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz" @@ -129,6 +134,17 @@ browserslist "^4.21.9" lru-cache "^5.1.1" +"@babel/helper-compilation-targets@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892" + integrity sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw== + dependencies: + "@babel/compat-data" "^7.22.9" + "@babel/helper-validator-option" "^7.22.5" + browserslist "^4.21.9" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.5": version "7.22.5" resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz" @@ -1178,13 +1194,13 @@ core-js-compat "^3.22.1" semver "^6.3.0" -"@babel/preset-env@^7.22.7": - version "7.22.7" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.7.tgz#a1ef34b64a80653c22ce4d9c25603cfa76fc168a" - integrity sha512-1whfDtW+CzhETuzYXfcgZAh8/GFMeEbz0V5dVgya8YeJyCU6Y/P2Gnx4Qb3MylK68Zu9UiwUvbPMPTpFAOJ+sQ== +"@babel/preset-env@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.9.tgz#57f17108eb5dfd4c5c25a44c1977eba1df310ac7" + integrity sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g== dependencies: - "@babel/compat-data" "^7.22.6" - "@babel/helper-compilation-targets" "^7.22.6" + "@babel/compat-data" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.9" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-option" "^7.22.5" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.5" @@ -1258,11 +1274,11 @@ "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" "@babel/preset-modules" "^0.1.5" "@babel/types" "^7.22.5" - "@nicolo-ribaudo/semver-v6" "^6.3.3" babel-plugin-polyfill-corejs2 "^0.4.4" babel-plugin-polyfill-corejs3 "^0.8.2" babel-plugin-polyfill-regenerator "^0.5.1" core-js-compat "^3.31.0" + semver "^6.3.1" "@babel/preset-modules@^0.1.5": version "0.1.5" @@ -1503,10 +1519,10 @@ resolved "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.31.0.tgz" integrity sha512-nNcycZWUYLNJlrIXgpcgVRqdl6BXjF4YlXdxobQWpW9Tikk61bEGeAFhDYtC0PwHlokCNw0KxWiHGJL4nL7Q5A== -"@cosmwasm/ts-codegen@^0.30.1": - version "0.30.1" - resolved "https://registry.npmjs.org/@cosmwasm/ts-codegen/-/ts-codegen-0.30.1.tgz" - integrity sha512-6ATbmtuK2MwG9fJxIi0M+Rwd0SQhsko2nA8qVXC9MRHpZJKNaXYYcof1fel/L5HJCjotmQVsoxons3rGg6dRnw== +"@cosmwasm/ts-codegen@^0.33.0": + version "0.33.0" + resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.33.0.tgz#02734a945eb7f353e37e4a1f1efc360778e32546" + integrity sha512-dZDyDTt6pCEdb30L6xN92QUDkAQ1Ic7BVSI+z60Pw0wSMDGFo2av8P9DWJLpmn/pgHuOAaM4CbJ1pJg5g/utLg== dependencies: "@babel/core" "7.18.10" "@babel/generator" "7.18.12" @@ -1534,7 +1550,7 @@ parse-package-name "1.0.0" rimraf "3.0.2" shelljs "0.8.5" - wasm-ast-types "^0.23.1" + wasm-ast-types "^0.24.0" "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.3.0": version "4.4.0" @@ -3021,10 +3037,10 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz" integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== -eslint@^8.44.0: - version "8.44.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.44.0.tgz#51246e3889b259bbcd1d7d736a0c10add4f0e500" - integrity sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A== +eslint@^8.45.0: + version "8.45.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.45.0.tgz#bab660f90d18e1364352c0a6b7c6db8edb458b78" + integrity sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" @@ -3051,7 +3067,6 @@ eslint@^8.44.0: globals "^13.19.0" graphemer "^1.4.0" ignore "^5.2.0" - import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" is-path-inside "^3.0.3" @@ -3063,7 +3078,6 @@ eslint@^8.44.0: natural-compare "^1.4.0" optionator "^0.9.3" strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" text-table "^0.2.0" espree@^9.6.0: @@ -3512,7 +3526,7 @@ ignore@^5.2.0, ignore@^5.2.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== -import-fresh@^3.0.0, import-fresh@^3.2.1: +import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -4940,6 +4954,11 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + semver@^7.5.0, semver@^7.5.3: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" @@ -5092,7 +5111,7 @@ strip-final-newline@^2.0.0: resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -5357,10 +5376,10 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -wasm-ast-types@^0.23.1: - version "0.23.1" - resolved "https://registry.npmjs.org/wasm-ast-types/-/wasm-ast-types-0.23.1.tgz" - integrity sha512-igLcEk8VHZq62ZEwn4Jp+WRTp2D9yvTeiQd2Pc+s7LZouzSn3CwRpD42sHK2wV0UlSt2/cNbV6QywFm9Z6eM/A== +wasm-ast-types@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.24.0.tgz#8a154e651a0707cf28ab82dbc084e600f6e5b9cf" + integrity sha512-Ok3PdrfSa0EG8TDXtER40SBYZxhTOTPuytCoNl0BXha4okh8OeeqypTK+Oe8I1XgvLcskTyaC8gxKAQh9Z0yCA== dependencies: "@babel/runtime" "^7.18.9" "@babel/types" "7.18.10" From a4becb670f7c659064b21e05234e3caf2972720d Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 19 Jul 2023 11:16:35 +0200 Subject: [PATCH 181/218] Remove unzap whitelist check (#160) Remove zap whitelist --- contracts/credit-manager/src/vault/utils.rs | 2 +- contracts/credit-manager/src/zap.rs | 13 ++-- .../credit-manager/tests/test_zap_withdraw.rs | 77 +------------------ contracts/mock-vault/src/withdraw.rs | 2 +- packages/rover/src/reentrancy_guard.rs | 2 +- 5 files changed, 9 insertions(+), 87 deletions(-) diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 09d34dafe..d794d7905 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -30,7 +30,7 @@ pub fn vault_is_whitelisted(deps: &mut DepsMut, vault: &Vault) -> ContractResult } pub fn assert_under_max_unlocking_limit( - storage: &mut dyn Storage, + storage: &dyn Storage, account_id: &str, vault: &Vault, ) -> ContractResult<()> { diff --git a/contracts/credit-manager/src/zap.rs b/contracts/credit-manager/src/zap.rs index fef6e5b5c..c04eb88fd 100644 --- a/contracts/credit-manager/src/zap.rs +++ b/contracts/credit-manager/src/zap.rs @@ -61,14 +61,12 @@ pub fn provide_liquidity( } pub fn withdraw_liquidity( - mut deps: DepsMut, + deps: DepsMut, env: Env, account_id: &str, lp_token_action: &ActionCoin, minimum_receive: Vec, ) -> ContractResult { - assert_coin_is_whitelisted(&mut deps, &lp_token_action.denom)?; - let lp_token = Coin { denom: lp_token_action.denom.clone(), amount: match lp_token_action.amount { @@ -84,13 +82,12 @@ pub fn withdraw_liquidity( } let zapper = ZAPPER.load(deps.storage)?; - let coins_out = zapper.estimate_withdraw_liquidity(&deps.querier, &lp_token)?; - assert_coins_are_whitelisted(&mut deps, coins_out.to_denoms())?; - decrement_coin_balance(deps.storage, account_id, &lp_token)?; + let unzap_msg = zapper.withdraw_liquidity_msg(&lp_token, minimum_receive)?; + // After unzap is complete, update account's coin balances - let zap_msg = zapper.withdraw_liquidity_msg(&lp_token, minimum_receive)?; + let coins_out = zapper.estimate_withdraw_liquidity(&deps.querier, &lp_token)?; let update_balances_msgs = update_balances_msgs( &deps.querier, &env.contract.address, @@ -100,7 +97,7 @@ pub fn withdraw_liquidity( )?; Ok(Response::new() - .add_message(zap_msg) + .add_message(unzap_msg) .add_messages(update_balances_msgs) .add_attribute("action", "withdraw_liquidity") .add_attribute("account_id", account_id) diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index c7ce12349..472225f0c 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -1,5 +1,4 @@ use cosmwasm_std::{coin, Addr, Coin, OverflowError, OverflowOperation::Sub, Uint128}; -use mars_params::msg::AssetParamsUpdate::AddOrUpdate; use mars_rover::{ error::ContractError as RoverError, msg::execute::{ @@ -13,8 +12,7 @@ use mars_v2_zapper_mock::{ }; use crate::helpers::{ - assert_err, blacklisted_coin, get_coin, lp_token_info, uatom_info, uosmo_info, AccountToFund, - MockEnv, + assert_err, get_coin, lp_token_info, uatom_info, uosmo_info, AccountToFund, MockEnv, }; pub mod helpers; @@ -48,79 +46,6 @@ fn only_token_owner_can_unzap_for_account() { ) } -#[test] -fn lp_token_in_must_be_whitelisted() { - let blacklisted = blacklisted_coin(); - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().build().unwrap(); - - let account_id = mock.create_credit_account(&user).unwrap(); - let res = mock.update_credit_account( - &account_id, - &user, - vec![WithdrawLiquidity { - lp_token: blacklisted.to_action_coin(100), - minimum_receive: vec![], - }], - &[], - ); - - assert_err(res, RoverError::NotWhitelisted(blacklisted.denom)) -} - -#[test] -fn coins_out_must_be_whitelisted() { - let atom = uatom_info(); - let mut osmo = uosmo_info(); - let lp_token = lp_token_info(); - - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) - .fund_account(AccountToFund { - addr: user.clone(), - funds: vec![atom.to_coin(300), osmo.to_coin(300)], - }) - .build() - .unwrap(); - - // Seed zapper with denoms so test can estimate withdraws - let account_id = mock.create_credit_account(&user).unwrap(); - mock.update_credit_account( - &account_id, - &user, - vec![ - Deposit(atom.to_coin(100)), - Deposit(osmo.to_coin(50)), - ProvideLiquidity { - coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], - lp_token_out: lp_token.denom.clone(), - minimum_receive: Uint128::zero(), - }, - ], - &[atom.to_coin(100), osmo.to_coin(50)], - ) - .unwrap(); - - // update params to disallow denoms out - osmo.whitelisted = false; - mock.update_asset_params(AddOrUpdate { - params: osmo.clone().into(), - }); - - let res = mock.update_credit_account( - &account_id, - &user, - vec![WithdrawLiquidity { - lp_token: lp_token.to_action_coin(100_000), - minimum_receive: vec![], - }], - &[], - ); - - assert_err(res, RoverError::NotWhitelisted(osmo.denom)) -} - #[test] fn does_not_have_the_tokens_to_withdraw_liq() { let atom = uatom_info(); diff --git a/contracts/mock-vault/src/withdraw.rs b/contracts/mock-vault/src/withdraw.rs index 282bb6310..321bec5f8 100644 --- a/contracts/mock-vault/src/withdraw.rs +++ b/contracts/mock-vault/src/withdraw.rs @@ -60,7 +60,7 @@ pub fn withdraw_state_update( Ok(base_amount) } -pub fn get_vault_token(storage: &mut dyn Storage, funds: Vec) -> ContractResult { +pub fn get_vault_token(storage: &dyn Storage, funds: Vec) -> ContractResult { let vault_token_denom = VAULT_TOKEN_DENOM.load(storage)?; let res = funds.iter().find(|coin| coin.denom == vault_token_denom); match res { diff --git a/packages/rover/src/reentrancy_guard.rs b/packages/rover/src/reentrancy_guard.rs index 337897bce..c5cc01dd7 100644 --- a/packages/rover/src/reentrancy_guard.rs +++ b/packages/rover/src/reentrancy_guard.rs @@ -40,7 +40,7 @@ impl<'a> ReentrancyGuard<'a> { Ok(Response::new().add_attribute("action", "remove_reentrancy_guard")) } - fn assert_unlocked(&self, storage: &mut dyn Storage) -> ContractResult<()> { + fn assert_unlocked(&self, storage: &dyn Storage) -> ContractResult<()> { match self.state(storage)? { GuardState::Locked => { Err(ContractError::ReentrancyGuard("Reentrancy guard is active".to_string())) From 8fda30b3b5e62f64da8a8c3afafd459f957a40e1 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 20 Jul 2023 09:50:59 +0200 Subject: [PATCH 182/218] Withdraw incentives rewards (#161) Withdraw rewards --- Cargo.lock | 63 ++++--- Cargo.toml | 9 +- Makefile.toml | 2 +- contracts/credit-manager/Cargo.toml | 19 ++- contracts/credit-manager/src/claim_rewards.rs | 31 ++++ contracts/credit-manager/src/execute.rs | 7 + contracts/credit-manager/src/instantiate.rs | 6 +- contracts/credit-manager/src/lib.rs | 1 + contracts/credit-manager/src/query.rs | 7 +- contracts/credit-manager/src/state.rs | 6 +- contracts/credit-manager/src/update_config.rs | 12 +- .../credit-manager/tests/helpers/contracts.rs | 9 + .../credit-manager/tests/helpers/mock_env.rs | 84 +++++++++- .../tests/test_claim_rewards.rs | 96 +++++++++++ .../tests/test_update_config.rs | 10 +- .../health/tests/helpers/mock_env_builder.rs | 1 + contracts/mock-incentives/Cargo.toml | 25 +++ contracts/mock-incentives/src/contract.rs | 56 +++++++ contracts/mock-incentives/src/execute.rs | 48 ++++++ contracts/mock-incentives/src/lib.rs | 5 + contracts/mock-incentives/src/query.rs | 14 ++ contracts/mock-incentives/src/state.rs | 5 + packages/rover/src/adapters/incentives.rs | 72 ++++++++ packages/rover/src/adapters/mod.rs | 1 + packages/rover/src/msg/execute.rs | 7 + packages/rover/src/msg/instantiate.rs | 9 +- packages/rover/src/msg/query.rs | 1 + .../mars-credit-manager.json | 65 ++++++++ .../mars-mock-credit-manager.json | 4 + scripts/deploy/base/deployer.ts | 1 + scripts/deploy/osmosis/mainnet.ts | 1 + scripts/deploy/osmosis/testnet-config.ts | 1 + scripts/package.json | 4 +- scripts/types/config.ts | 1 + .../MarsCreditManager.client.ts | 1 + .../MarsCreditManager.message-composer.ts | 1 + .../MarsCreditManager.react-query.ts | 1 + .../MarsCreditManager.types.ts | 12 ++ .../MarsMockCreditManager.types.ts | 1 + scripts/yarn.lock | 154 ++++++++---------- 40 files changed, 698 insertions(+), 155 deletions(-) create mode 100644 contracts/credit-manager/src/claim_rewards.rs create mode 100644 contracts/credit-manager/tests/test_claim_rewards.rs create mode 100644 contracts/mock-incentives/Cargo.toml create mode 100644 contracts/mock-incentives/src/contract.rs create mode 100644 contracts/mock-incentives/src/execute.rs create mode 100644 contracts/mock-incentives/src/lib.rs create mode 100644 contracts/mock-incentives/src/query.rs create mode 100644 contracts/mock-incentives/src/state.rs create mode 100644 packages/rover/src/adapters/incentives.rs diff --git a/Cargo.lock b/Cargo.lock index 52b22f210..098f89bb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,6 +215,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bnum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" + [[package]] name = "bs58" version = "0.4.0" @@ -374,9 +380,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.2.7" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb64554a91d6a9231127f4355d351130a0b94e663d5d9dc8b3a54ca17d83de49" +checksum = "0d076a08ec01ed23c4396aca98ec73a38fa1fee5f310465add52b4108181c7a8" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -387,18 +393,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.7" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0fb2ce09f41a3dae1a234d56a9988f9aff4c76441cd50ef1ee9a4f20415b028" +checksum = "dec361f3c09d7b41221948fc17be9b3c96cb58e55a02f82da36f888a651f2584" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.7" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230e5d1cefae5331db8934763c81b9c871db6a2cd899056a5694fa71d292c815" +checksum = "bb6b2fb76758ef59cddc77f2e2ae91c22f77da49037e9f182e9c2833f0e959b1" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -409,9 +415,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.7" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43dadf7c23406cb28079d69e6cb922c9c29b9157b0fe887e3b79c783b7d4bcb8" +checksum = "2bfa39422f0d9f1c9a6fd3711573258495314dfa3aae738ea825ecd9964bc659" dependencies = [ "proc-macro2", "quote", @@ -420,11 +426,12 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.7" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4337eef8dfaf8572fe6b6b415d6ec25f9308c7bb09f2da63789209fb131363be" +checksum = "1f6dc2ee23313add5ecacc3ccac217b9967ad9d2d11bd56e5da6aa65a9da6138" dependencies = [ "base64", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", "derivative", @@ -435,7 +442,6 @@ dependencies = [ "serde-json-wasm", "sha2 0.10.7", "thiserror", - "uint", ] [[package]] @@ -447,12 +453,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" @@ -1468,6 +1468,7 @@ dependencies = [ "cw721-base 0.18.0", "itertools 0.11.0", "mars-account-nft", + "mars-mock-incentives", "mars-mock-oracle", "mars-mock-red-bank", "mars-mock-vault", @@ -1494,6 +1495,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "mars-mock-incentives" +version = "2.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.1.0", + "mars-red-bank-types", +] + [[package]] name = "mars-mock-oracle" version = "2.0.0" @@ -2676,12 +2687,6 @@ dependencies = [ "der", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.10.0" @@ -3040,18 +3045,6 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unarray" version = "0.1.4" diff --git a/Cargo.toml b/Cargo.toml index 8c38c7e70..694e54cfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ # mock contracts "contracts/mock-credit-manager", "contracts/mock-health", + "contracts/mock-incentives", "contracts/mock-oracle", "contracts/mock-red-bank", "contracts/mock-vault", @@ -27,7 +28,8 @@ version = "2.0.0" authors = [ "Gabe R. ", "Larry Engineer ", - "Piotr Babel ", + "Piotr B. ", + "Brianna M. " ] license = "GPL-3.0-or-later" edition = "2021" @@ -38,8 +40,8 @@ keywords = ["mars", "cosmos", "cosmwasm"] [workspace.dependencies] anyhow = "1.0.72" -cosmwasm-schema = "1.2.7" -cosmwasm-std = "1.2.7" +cosmwasm-schema = "1.3.0" +cosmwasm-std = "1.3.0" cw2 = "1.1.0" cw721 = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main" } cw721-base = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main", features = ["library"] } @@ -78,6 +80,7 @@ mars-v3-zapper-base = { version = "2.0.0", path = "./contracts/v3-zapper/base", # mocks mars-mock-credit-manager = { version = "2.0.0", path = "./contracts/mock-credit-manager", features = ["library"] } +mars-mock-incentives = { version = "2.0.0", path = "./contracts/mock-incentives", features = ["library"] } mars-mock-oracle = { version = "2.0.0", path = "./contracts/mock-oracle", features = ["library"] } mars-mock-red-bank = { version = "2.0.0", path = "./contracts/mock-red-bank", features = ["library"] } mars-mock-vault = { version = "2.0.0", path = "./contracts/mock-vault", features = ["library"] } diff --git a/Makefile.toml b/Makefile.toml index f6fe6bc0c..354f8045a 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -22,7 +22,7 @@ else image="cosmwasm/workspace-optimizer:0.13.0" fi docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ ${image} """ diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index d037bf2e3..a06db00db 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -36,12 +36,13 @@ mars-rover = { workspace = true } mars-rover-health-types = { workspace = true } [dev-dependencies] -anyhow = { workspace = true } -cw-multi-test = { workspace = true } -itertools = { workspace = true } -mars-mock-oracle = { workspace = true } -mars-mock-red-bank = { workspace = true } -mars-mock-vault = { workspace = true } -mars-rover-health = { workspace = true } -mars-swapper-mock = { workspace = true } -mars-v2-zapper-mock = { workspace = true } +anyhow = { workspace = true } +cw-multi-test = { workspace = true } +itertools = { workspace = true } +mars-mock-incentives = { workspace = true } +mars-mock-oracle = { workspace = true } +mars-mock-red-bank = { workspace = true } +mars-mock-vault = { workspace = true } +mars-rover-health = { workspace = true } +mars-swapper-mock = { workspace = true } +mars-v2-zapper-mock = { workspace = true } diff --git a/contracts/credit-manager/src/claim_rewards.rs b/contracts/credit-manager/src/claim_rewards.rs new file mode 100644 index 000000000..5ee61a823 --- /dev/null +++ b/contracts/credit-manager/src/claim_rewards.rs @@ -0,0 +1,31 @@ +use cosmwasm_std::{DepsMut, Env, Response}; +use mars_rover::{ + error::{ContractError, ContractResult}, + msg::execute::ChangeExpected, + traits::Denoms, +}; + +use crate::{state::INCENTIVES, utils::update_balances_msgs}; + +pub fn claim_rewards(deps: DepsMut, env: Env, account_id: &str) -> ContractResult { + let incentives = INCENTIVES.load(deps.storage)?; + + let unclaimed_rewards = incentives.query_unclaimed_rewards(&deps.querier, account_id)?; + if unclaimed_rewards.is_empty() { + return Err(ContractError::NoAmount); + } + + let update_balances_msgs = update_balances_msgs( + &deps.querier, + &env.contract.address, + account_id, + unclaimed_rewards.to_denoms(), + ChangeExpected::Increase, + )?; + + Ok(Response::new() + .add_message(incentives.claim_rewards_msg(account_id)?) + .add_messages(update_balances_msgs) + .add_attribute("action", "claim_rewards") + .add_attribute("account_id", account_id)) +} diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 2a334f609..1bbc2bd84 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -12,6 +12,7 @@ use mars_rover_health_types::AccountKind; use crate::{ borrow::borrow, + claim_rewards::claim_rewards, deposit::deposit, health::{assert_max_ltv, query_health_state}, hls::assert_account_requirements, @@ -112,6 +113,9 @@ pub fn dispatch_actions( account_id: account_id.to_string(), coin, }), + Action::ClaimRewards {} => callbacks.push(CallbackMsg::ClaimRewards { + account_id: account_id.to_string(), + }), Action::EnterVault { vault, coin, @@ -280,6 +284,9 @@ pub fn execute_callback( account_id, coin, } => reclaim(deps, &account_id, &coin), + CallbackMsg::ClaimRewards { + account_id, + } => claim_rewards(deps, env, &account_id), CallbackMsg::AssertMaxLTV { account_id, prev_health_state, diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 243efd5bb..21cff406a 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -3,7 +3,8 @@ use mars_owner::OwnerInit::SetInitialOwner; use mars_rover::{error::ContractResult, msg::InstantiateMsg}; use crate::state::{ - HEALTH_CONTRACT, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, RED_BANK, SWAPPER, ZAPPER, + HEALTH_CONTRACT, INCENTIVES, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, RED_BANK, SWAPPER, + ZAPPER, }; pub fn store_config(deps: DepsMut, env: Env, msg: &InstantiateMsg) -> ContractResult<()> { @@ -15,13 +16,14 @@ pub fn store_config(deps: DepsMut, env: Env, msg: &InstantiateMsg) -> ContractRe }, )?; - RED_BANK.save(deps.storage, &msg.red_bank.check(deps.api, env.contract.address)?)?; + RED_BANK.save(deps.storage, &msg.red_bank.check(deps.api, env.contract.address.clone())?)?; ORACLE.save(deps.storage, &msg.oracle.check(deps.api)?)?; SWAPPER.save(deps.storage, &msg.swapper.check(deps.api)?)?; ZAPPER.save(deps.storage, &msg.zapper.check(deps.api)?)?; MAX_UNLOCKING_POSITIONS.save(deps.storage, &msg.max_unlocking_positions)?; HEALTH_CONTRACT.save(deps.storage, &msg.health_contract.check(deps.api)?)?; PARAMS.save(deps.storage, &msg.params.check(deps.api)?)?; + INCENTIVES.save(deps.storage, &msg.incentives.check(deps.api, env.contract.address)?)?; Ok(()) } diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index a9c134b39..7301eb9ec 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -1,6 +1,7 @@ pub mod contract; pub mod borrow; +pub mod claim_rewards; pub mod deposit; pub mod execute; pub mod health; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index a39cc576d..ceab2752d 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -13,9 +13,9 @@ use mars_rover::{ use crate::{ state::{ - ACCOUNT_NFT, COIN_BALANCES, DEBT_SHARES, HEALTH_CONTRACT, MAX_UNLOCKING_POSITIONS, ORACLE, - OWNER, PARAMS, RED_BANK, REWARDS_COLLECTOR, SWAPPER, TOTAL_DEBT_SHARES, VAULT_POSITIONS, - ZAPPER, + ACCOUNT_NFT, COIN_BALANCES, DEBT_SHARES, HEALTH_CONTRACT, INCENTIVES, + MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, RED_BANK, REWARDS_COLLECTOR, SWAPPER, + TOTAL_DEBT_SHARES, VAULT_POSITIONS, ZAPPER, }, utils::debt_shares_to_amount, vault::vault_utilization_in_deposit_cap_denom, @@ -26,6 +26,7 @@ pub fn query_config(deps: Deps) -> ContractResult { ownership: OWNER.query(deps.storage)?, account_nft: ACCOUNT_NFT.may_load(deps.storage)?.map(|a| a.address().into()), red_bank: RED_BANK.load(deps.storage)?.addr.into(), + incentives: INCENTIVES.load(deps.storage)?.addr.into(), oracle: ORACLE.load(deps.storage)?.address().into(), params: PARAMS.load(deps.storage)?.address().into(), max_unlocking_positions: MAX_UNLOCKING_POSITIONS.load(deps.storage)?, diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index e9e84cfdf..b56813f9e 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -4,8 +4,9 @@ use cw_storage_plus::{Item, Map}; use mars_owner::Owner; use mars_rover::{ adapters::{ - account_nft::AccountNft, health::HealthContract, oracle::Oracle, params::Params, - red_bank::RedBank, swap::Swapper, vault::VaultPositionAmount, zapper::Zapper, + account_nft::AccountNft, health::HealthContract, incentives::Incentives, oracle::Oracle, + params::Params, red_bank::RedBank, swap::Swapper, vault::VaultPositionAmount, + zapper::Zapper, }, reentrancy_guard::ReentrancyGuard, }; @@ -27,6 +28,7 @@ pub const SWAPPER: Item = Item::new("swapper"); pub const ZAPPER: Item = Item::new("zapper"); pub const HEALTH_CONTRACT: Item = Item::new("health_contract"); pub const PARAMS: Item = Item::new("params"); +pub const INCENTIVES: Item = Item::new("incentives"); // Config pub const OWNER: Owner = Owner::new("owner"); diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index d1c0068ff..baf7f4a6f 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -8,8 +8,8 @@ use mars_rover_health_types::AccountKind; use crate::{ execute::create_credit_account, state::{ - RewardsCollector, ACCOUNT_NFT, HEALTH_CONTRACT, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, - RED_BANK, REWARDS_COLLECTOR, SWAPPER, ZAPPER, + RewardsCollector, ACCOUNT_NFT, HEALTH_CONTRACT, INCENTIVES, MAX_UNLOCKING_POSITIONS, + ORACLE, OWNER, RED_BANK, REWARDS_COLLECTOR, SWAPPER, ZAPPER, }, }; @@ -47,7 +47,7 @@ pub fn update_config( } if let Some(unchecked) = updates.red_bank { - RED_BANK.save(deps.storage, &unchecked.check(deps.api, env.contract.address)?)?; + RED_BANK.save(deps.storage, &unchecked.check(deps.api, env.contract.address.clone())?)?; response = response.add_attribute("key", "red_bank").add_attribute("value", unchecked.address()); } @@ -78,6 +78,12 @@ pub fn update_config( .add_attribute("value", unchecked.address()); } + if let Some(unchecked) = updates.incentives { + INCENTIVES.save(deps.storage, &unchecked.check(deps.api, env.contract.address)?)?; + response = + response.add_attribute("key", "incentives").add_attribute("value", unchecked.address()); + } + if let Some(unchecked) = updates.rewards_collector { let rewards_collector_addr = deps.api.addr_validate(&unchecked)?; diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index ebf4b7da9..c4cc9a37a 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -33,6 +33,15 @@ pub fn mock_red_bank_contract() -> Box> { Box::new(contract) } +pub fn mock_incentives_contract() -> Box> { + let contract = ContractWrapper::new( + mars_mock_incentives::contract::execute, + mars_mock_incentives::contract::instantiate, + mars_mock_incentives::contract::query, + ); + Box::new(contract) +} + pub fn mock_oracle_contract() -> Box> { let contract = ContractWrapper::new( mars_mock_oracle::contract::execute, diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 0431fab44..f1c1ff3eb 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -35,6 +35,7 @@ use mars_params::{ }, }; use mars_red_bank_types::{ + incentives::{ExecuteMsg::BalanceChange, QueryMsg::UserUnclaimedRewards}, oracle::ActionKind, red_bank::{ QueryMsg::{UserCollateral, UserDebt}, @@ -45,6 +46,7 @@ use mars_rover::{ adapters::{ account_nft::AccountNftUnchecked, health::HealthContract, + incentives::{Incentives, IncentivesUnchecked}, oracle::{Oracle, OracleBase, OracleUnchecked}, params::Params, red_bank::RedBankUnchecked, @@ -73,9 +75,10 @@ use mars_rover_health_types::{ use mars_v2_zapper_mock::msg::{InstantiateMsg as ZapperInstantiateMsg, LpConfig}; use crate::helpers::{ - lp_token_info, mock_account_nft_contract, mock_health_contract, mock_oracle_contract, - mock_params_contract, mock_red_bank_contract, mock_rover_contract, mock_swapper_contract, - mock_v2_zapper_contract, mock_vault_contract, AccountToFund, CoinInfo, VaultTestInfo, + lp_token_info, mock_account_nft_contract, mock_health_contract, mock_incentives_contract, + mock_oracle_contract, mock_params_contract, mock_red_bank_contract, mock_rover_contract, + mock_swapper_contract, mock_v2_zapper_contract, mock_vault_contract, AccountToFund, CoinInfo, + VaultTestInfo, }; pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000); @@ -85,6 +88,7 @@ pub struct MockEnv { pub rover: Addr, pub mars_oracle: Addr, pub health_contract: HealthContract, + pub incentives: Incentives, pub params: Params, } @@ -97,6 +101,7 @@ pub struct MockEnvBuilder { pub oracle: Option, pub params: Option, pub red_bank: Option, + pub incentives: Option, pub deploy_nft_contract: bool, pub set_nft_contract_minter: bool, pub accounts_to_fund: Vec, @@ -118,6 +123,7 @@ impl MockEnv { oracle: None, params: None, red_bank: None, + incentives: None, deploy_nft_contract: true, set_nft_contract_minter: true, accounts_to_fund: vec![], @@ -307,6 +313,32 @@ impl MockEnv { ) } + pub fn add_incentive_reward(&mut self, account_id: &str, coin: Coin) { + // Register reward in mock contract + self.app + .execute_contract( + self.rover.clone(), + self.incentives.addr.clone(), + &BalanceChange { + user_addr: self.rover.clone(), + account_id: Some(account_id.to_string()), + denom: coin.denom.clone(), + user_amount_scaled_before: coin.amount, + total_amount_scaled_before: Default::default(), + }, + &[], + ) + .unwrap(); + + // Mint token for incentives contract so it can be claimed + self.app + .sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: self.incentives.addr.to_string(), + amount: vec![coin], + })) + .unwrap(); + } + //-------------------------------------------------------------------------------------------------- // Queries //-------------------------------------------------------------------------------------------------- @@ -342,6 +374,22 @@ impl MockEnv { .unwrap() } + pub fn query_unclaimed_rewards(&self, account_id: &str) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.incentives.clone().addr, + &UserUnclaimedRewards { + user: self.rover.to_string(), + account_id: Some(account_id.to_string()), + start_after_collateral_denom: None, + start_after_incentive_denom: None, + limit: None, + }, + ) + .unwrap() + } + pub fn query_balance(&self, addr: &Addr, denom: &str) -> Coin { self.app.wrap().query_balance(addr.clone(), denom).unwrap() } @@ -656,6 +704,8 @@ impl MockEnvBuilder { self.set_emergency_owner(&rover); let mars_oracle = self.get_oracle(); + let incentives = + Incentives::new(Addr::unchecked(self.get_incentives().address()), rover.clone()); let params = self.get_params_contract(); self.add_params_to_contract(); @@ -684,6 +734,7 @@ impl MockEnvBuilder { rover, mars_oracle: mars_oracle.address().clone(), health_contract, + incentives, params, }) } @@ -774,6 +825,7 @@ impl MockEnvBuilder { fn get_rover(&mut self) -> AnyResult { let code_id = self.app.store_code(mock_rover_contract()); let red_bank = self.get_red_bank(); + let incentives = self.get_incentives(); let swapper = self.deploy_swapper().into(); let max_unlocking_positions = self.get_max_unlocking_positions(); @@ -794,6 +846,7 @@ impl MockEnvBuilder { zapper, health_contract, params, + incentives, }, &[], "mock-rover-contract", @@ -974,6 +1027,31 @@ impl MockEnvBuilder { RedBankUnchecked::new(addr.to_string()) } + fn get_incentives(&mut self) -> IncentivesUnchecked { + if self.incentives.is_none() { + let rb = self.deploy_incentives(); + self.incentives = Some(rb); + } + self.incentives.clone().unwrap() + } + + pub fn deploy_incentives(&mut self) -> IncentivesUnchecked { + let contract_code_id = self.app.store_code(mock_incentives_contract()); + let addr = self + .app + .instantiate_contract( + contract_code_id, + Addr::unchecked("incentives_contract_owner"), + &Empty {}, + &[], + "mock-incentives", + None, + ) + .unwrap(); + + IncentivesUnchecked::new(addr.to_string()) + } + fn deploy_vault(&mut self, vault: &VaultTestInfo) -> Addr { let code_id = self.app.store_code(mock_vault_contract()); let oracle = self.get_oracle().into(); diff --git a/contracts/credit-manager/tests/test_claim_rewards.rs b/contracts/credit-manager/tests/test_claim_rewards.rs new file mode 100644 index 000000000..1d816d940 --- /dev/null +++ b/contracts/credit-manager/tests/test_claim_rewards.rs @@ -0,0 +1,96 @@ +use cosmwasm_std::{Addr, Uint128}; +use mars_rover::{error::ContractError, msg::execute::Action::ClaimRewards}; + +use crate::helpers::{assert_err, get_coin, uatom_info, ujake_info, uosmo_info, MockEnv}; + +pub mod helpers; + +#[test] +fn claiming_rewards_when_having_none() { + let mut mock = MockEnv::new().build().unwrap(); + + let user = Addr::unchecked("user"); + let account_id = mock.create_credit_account(&user).unwrap(); + + let unclaimed = mock.query_unclaimed_rewards(&account_id); + assert!(unclaimed.is_empty()); + + let res = mock.update_credit_account(&account_id, &user, vec![ClaimRewards {}], &[]); + assert_err(res, ContractError::NoAmount); +} + +#[test] +fn claiming_a_single_reward() { + let coin_info = uosmo_info(); + let mut mock = MockEnv::new().build().unwrap(); + + let user = Addr::unchecked("user"); + let account_id = mock.create_credit_account(&user).unwrap(); + + let unclaimed = mock.query_unclaimed_rewards(&account_id); + assert!(unclaimed.is_empty()); + + mock.add_incentive_reward(&account_id, coin_info.to_coin(123)); + + let unclaimed = mock.query_unclaimed_rewards(&account_id); + assert_eq!(unclaimed.len(), 1); + + mock.update_credit_account(&account_id, &user, vec![ClaimRewards {}], &[]).unwrap(); + + // Check account id deposit balance + let positions = mock.query_positions(&account_id); + assert_eq!(positions.deposits.len(), 1); + assert_eq!(positions.deposits.first().unwrap().amount, Uint128::new(123)); + assert_eq!(positions.deposits.first().unwrap().denom, coin_info.denom); + + // Ensure money is in bank module for credit manager + let cm_balance = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(cm_balance.amount, Uint128::new(123)); +} + +#[test] +fn claiming_multiple_rewards() { + let osmo_info = uosmo_info(); + let atom_info = uatom_info(); + let jake_info = ujake_info(); + + let mut mock = MockEnv::new().build().unwrap(); + + let user = Addr::unchecked("user"); + let account_id = mock.create_credit_account(&user).unwrap(); + + let unclaimed = mock.query_unclaimed_rewards(&account_id); + assert!(unclaimed.is_empty()); + + mock.add_incentive_reward(&account_id, osmo_info.to_coin(123)); + mock.add_incentive_reward(&account_id, atom_info.to_coin(555)); + mock.add_incentive_reward(&account_id, jake_info.to_coin(12)); + + let unclaimed = mock.query_unclaimed_rewards(&account_id); + assert_eq!(unclaimed.len(), 3); + + mock.update_credit_account(&account_id, &user, vec![ClaimRewards {}], &[]).unwrap(); + + // Check account id deposit balance + let positions = mock.query_positions(&account_id); + assert_eq!(positions.deposits.len(), 3); + + let osmo_claimed = get_coin(&osmo_info.denom, &positions.deposits); + assert_eq!(osmo_claimed.amount, Uint128::new(123)); + + let atom_claimed = get_coin(&atom_info.denom, &positions.deposits); + assert_eq!(atom_claimed.amount, Uint128::new(555)); + + let jake_claimed = get_coin(&jake_info.denom, &positions.deposits); + assert_eq!(jake_claimed.amount, Uint128::new(12)); + + // Ensure money is in bank module for credit manager + let osmo_balance = mock.query_balance(&mock.rover, &osmo_info.denom); + assert_eq!(osmo_balance.amount, Uint128::new(123)); + + let atom_balance = mock.query_balance(&mock.rover, &atom_info.denom); + assert_eq!(atom_balance.amount, Uint128::new(555)); + + let jake_balance = mock.query_balance(&mock.rover, &jake_info.denom); + assert_eq!(jake_balance.amount, Uint128::new(12)); +} diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 927c4097e..3b57f6a55 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -4,8 +4,8 @@ use mars_mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ adapters::{ - health::HealthContractUnchecked, oracle::OracleUnchecked, red_bank::RedBankUnchecked, - swap::SwapperBase, zapper::ZapperBase, + health::HealthContractUnchecked, incentives::IncentivesUnchecked, oracle::OracleUnchecked, + red_bank::RedBankUnchecked, swap::SwapperBase, zapper::ZapperBase, }, msg::instantiate::ConfigUpdates, }; @@ -25,6 +25,7 @@ fn only_owner_can_update_config() { account_nft: None, oracle: None, red_bank: None, + incentives: None, max_unlocking_positions: None, swapper: None, zapper: None, @@ -46,6 +47,7 @@ fn update_config_works_with_full_config() { let new_nft_contract = mock.deploy_new_nft_contract().unwrap(); let new_oracle = deploy_new_oracle(&mut mock.app); let new_red_bank = deploy_new_red_bank(&mut mock.app); + let new_incentives = IncentivesUnchecked::new("new_incentives".to_string()); let new_zapper = ZapperBase::new("new_zapper".to_string()); let new_unlocking_max = Uint128::new(321); let new_swapper = SwapperBase::new("new_swapper".to_string()); @@ -58,6 +60,7 @@ fn update_config_works_with_full_config() { account_nft: Some(new_nft_contract.clone()), oracle: Some(new_oracle.clone()), red_bank: Some(new_red_bank.clone()), + incentives: Some(new_incentives.clone()), max_unlocking_positions: Some(new_unlocking_max), swapper: Some(new_swapper.clone()), zapper: Some(new_zapper.clone()), @@ -97,6 +100,9 @@ fn update_config_works_with_full_config() { assert_eq!(new_config.rewards_collector.clone().unwrap(), new_rewards_collector); assert_ne!(new_config.rewards_collector, original_config.rewards_collector); + + assert_eq!(&new_config.incentives, new_incentives.address()); + assert_ne!(new_config.incentives, original_config.incentives); } #[test] diff --git a/contracts/health/tests/helpers/mock_env_builder.rs b/contracts/health/tests/helpers/mock_env_builder.rs index 79edc2afc..fb699e8a7 100644 --- a/contracts/health/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/helpers/mock_env_builder.rs @@ -127,6 +127,7 @@ impl MockEnvBuilder { abolished: false, }, red_bank: "n/a".to_string(), + incentives: "n/a".to_string(), oracle, params, account_nft: None, diff --git a/contracts/mock-incentives/Cargo.toml b/contracts/mock-incentives/Cargo.toml new file mode 100644 index 000000000..820ec89bb --- /dev/null +++ b/contracts/mock-incentives/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "mars-mock-incentives" +version = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +mars-red-bank-types = { workspace = true } diff --git a/contracts/mock-incentives/src/contract.rs b/contracts/mock-incentives/src/contract.rs new file mode 100644 index 000000000..9eb10dba1 --- /dev/null +++ b/contracts/mock-incentives/src/contract.rs @@ -0,0 +1,56 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +}; +use mars_red_bank_types::incentives; + +use crate::{ + execute::{balance_change, claim_rewards}, + query::query_unclaimed_rewards, +}; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: Empty, +) -> StdResult { + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: incentives::ExecuteMsg, +) -> StdResult { + match msg { + incentives::ExecuteMsg::ClaimRewards { + account_id, + .. + } => claim_rewards(deps, info, account_id), + incentives::ExecuteMsg::BalanceChange { + user_addr, + account_id, + denom, + user_amount_scaled_before, + .. + } => balance_change(deps, info, user_addr, account_id, denom, user_amount_scaled_before), + _ => unimplemented!("Msg not supported!"), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: incentives::QueryMsg) -> StdResult { + match msg { + incentives::QueryMsg::UserUnclaimedRewards { + user, + account_id, + .. + } => to_binary(&query_unclaimed_rewards(deps, &user, &account_id)?), + _ => unimplemented!("Query not supported!"), + } +} diff --git a/contracts/mock-incentives/src/execute.rs b/contracts/mock-incentives/src/execute.rs new file mode 100644 index 000000000..e058bbbbd --- /dev/null +++ b/contracts/mock-incentives/src/execute.rs @@ -0,0 +1,48 @@ +use cosmwasm_std::{ + Addr, BankMsg, Coin, CosmosMsg, DepsMut, MessageInfo, Response, StdResult, Uint128, +}; + +use crate::{query::query_unclaimed_rewards, state::UNCLAIMED_REWARDS}; + +pub fn claim_rewards( + deps: DepsMut, + info: MessageInfo, + account_id: Option, +) -> StdResult { + let unclaimed = query_unclaimed_rewards(deps.as_ref(), info.sender.as_str(), &account_id)?; + + UNCLAIMED_REWARDS.remove(deps.storage, (info.sender.clone(), account_id.unwrap_or_default())); + + // Mock env responsible for seeding contract with coins + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.to_string(), + amount: unclaimed, + }); + + Ok(Response::new().add_message(transfer_msg)) +} + +/// Privileged sudo message for adding unclaimed rewards for user +pub fn balance_change( + deps: DepsMut, + info: MessageInfo, + user_addr: Addr, + account_id: Option, + denom: String, + user_amount_scaled_before: Uint128, +) -> StdResult { + let mut unclaimed = query_unclaimed_rewards(deps.as_ref(), user_addr.as_str(), &account_id)?; + + unclaimed.push(Coin { + denom, + amount: user_amount_scaled_before, + }); + + UNCLAIMED_REWARDS.save( + deps.storage, + (info.sender, account_id.clone().unwrap_or_default()), + &unclaimed, + )?; + + Ok(Response::new()) +} diff --git a/contracts/mock-incentives/src/lib.rs b/contracts/mock-incentives/src/lib.rs new file mode 100644 index 000000000..ad39f7fcd --- /dev/null +++ b/contracts/mock-incentives/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; + +pub mod execute; +pub mod query; +pub mod state; diff --git a/contracts/mock-incentives/src/query.rs b/contracts/mock-incentives/src/query.rs new file mode 100644 index 000000000..422e68776 --- /dev/null +++ b/contracts/mock-incentives/src/query.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::{Coin, Deps, StdResult}; + +use crate::state::UNCLAIMED_REWARDS; + +pub fn query_unclaimed_rewards( + deps: Deps, + user: &str, + account_id: &Option, +) -> StdResult> { + let user_addr = deps.api.addr_validate(user)?; + Ok(UNCLAIMED_REWARDS + .may_load(deps.storage, (user_addr, account_id.clone().unwrap_or_default()))? + .unwrap_or_default()) +} diff --git a/contracts/mock-incentives/src/state.rs b/contracts/mock-incentives/src/state.rs new file mode 100644 index 000000000..7c6c2b94d --- /dev/null +++ b/contracts/mock-incentives/src/state.rs @@ -0,0 +1,5 @@ +use cosmwasm_std::{Addr, Coin}; +use cw_storage_plus::Map; + +// Map<(Addr, CmAccountId), Unclaimed Coins> +pub const UNCLAIMED_REWARDS: Map<(Addr, String), Vec> = Map::new("unclaimed_rewards"); diff --git a/packages/rover/src/adapters/incentives.rs b/packages/rover/src/adapters/incentives.rs new file mode 100644 index 000000000..c79330c11 --- /dev/null +++ b/packages/rover/src/adapters/incentives.rs @@ -0,0 +1,72 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{to_binary, Addr, Api, Coin, CosmosMsg, QuerierWrapper, StdResult, WasmMsg}; +use mars_red_bank_types::{incentives, incentives::QueryMsg}; + +#[cw_serde] +pub struct IncentivesUnchecked(String); + +impl IncentivesUnchecked { + pub fn new(address: String) -> Self { + Self(address) + } + + pub fn address(&self) -> &str { + &self.0 + } + + pub fn check(&self, api: &dyn Api, credit_manager: Addr) -> StdResult { + let addr = api.addr_validate(self.address())?; + Ok(Incentives::new(addr, credit_manager)) + } +} + +impl From for IncentivesUnchecked { + fn from(red_bank: Incentives) -> Self { + Self(red_bank.addr.to_string()) + } +} + +#[cw_serde] +pub struct Incentives { + pub addr: Addr, + credit_manager: Addr, +} + +impl Incentives { + pub fn new(addr: Addr, credit_manager: Addr) -> Self { + Self { + addr, + credit_manager, + } + } + + pub fn claim_rewards_msg(&self, account_id: &str) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.addr.to_string(), + msg: to_binary(&incentives::ExecuteMsg::ClaimRewards { + account_id: Some(account_id.to_string()), + start_after_collateral_denom: None, + start_after_incentive_denom: None, + limit: None, + })?, + funds: vec![], + })) + } + + pub fn query_unclaimed_rewards( + &self, + querier: &QuerierWrapper, + account_id: &str, + ) -> StdResult> { + querier.query_wasm_smart( + self.addr.to_string(), + &QueryMsg::UserUnclaimedRewards { + user: self.credit_manager.to_string(), + account_id: Some(account_id.to_string()), + start_after_collateral_denom: None, + start_after_incentive_denom: None, + limit: None, + }, + ) + } +} diff --git a/packages/rover/src/adapters/mod.rs b/packages/rover/src/adapters/mod.rs index 5bd68402e..3ba80bd82 100644 --- a/packages/rover/src/adapters/mod.rs +++ b/packages/rover/src/adapters/mod.rs @@ -1,5 +1,6 @@ pub mod account_nft; pub mod health; +pub mod incentives; pub mod oracle; pub mod params; pub mod red_bank; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index d28bee2b3..936ceec45 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -122,6 +122,9 @@ pub enum Action { Lend(ActionCoin), /// Reclaim the coins that were lent to the Red Bank. Reclaim(ActionCoin), + /// For assets lent to the Red Bank, some can accumulate incentive rewards. + /// This message claims all of them adds them to account balance. + ClaimRewards {}, /// Repay coin of specified amount back to Red Bank. If `amount: AccountBalance` is passed, /// the repaid amount will be the minimum between account balance for denom and total owed. /// The sender will repay on behalf of the recipient account. If 'recipient_account_id: None', @@ -224,6 +227,10 @@ pub enum CallbackMsg { account_id: String, coin: ActionCoin, }, + /// Calls incentive contract to claim all rewards and increments account balance + ClaimRewards { + account_id: String, + }, /// Assert MaxLTV is either: /// - Healthy, if prior to actions MaxLTV health factor >= 1 or None /// - Not further weakened, if prior to actions MaxLTV health factor < 1 diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index d26a6e802..ac2d36352 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -2,9 +2,9 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Uint128; use crate::adapters::{ - account_nft::AccountNftUnchecked, health::HealthContractUnchecked, oracle::OracleUnchecked, - params::ParamsUnchecked, red_bank::RedBankUnchecked, swap::SwapperUnchecked, - zapper::ZapperUnchecked, + account_nft::AccountNftUnchecked, health::HealthContractUnchecked, + incentives::IncentivesUnchecked, oracle::OracleUnchecked, params::ParamsUnchecked, + red_bank::RedBankUnchecked, swap::SwapperUnchecked, zapper::ZapperUnchecked, }; #[cw_serde] @@ -27,6 +27,8 @@ pub struct InstantiateMsg { pub health_contract: HealthContractUnchecked, /// Contract that stores asset and vault params pub params: ParamsUnchecked, + /// Contract that handles lending incentive rewards + pub incentives: IncentivesUnchecked, } /// Used when you want to update fields on Instantiate config @@ -36,6 +38,7 @@ pub struct ConfigUpdates { pub account_nft: Option, pub oracle: Option, pub red_bank: Option, + pub incentives: Option, pub max_unlocking_positions: Option, pub swapper: Option, pub zapper: Option, diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index d273d0716..5ead04e95 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -153,6 +153,7 @@ pub struct ConfigResponse { pub ownership: OwnerResponse, pub account_nft: Option, pub red_bank: String, + pub incentives: String, pub oracle: String, pub params: String, pub max_unlocking_positions: Uint128, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 602078af8..9dc788bfc 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -8,6 +8,7 @@ "type": "object", "required": [ "health_contract", + "incentives", "max_unlocking_positions", "oracle", "owner", @@ -25,6 +26,14 @@ } ] }, + "incentives": { + "description": "Contract that handles lending incentive rewards", + "allOf": [ + { + "$ref": "#/definitions/IncentivesUnchecked" + } + ] + }, "max_unlocking_positions": { "description": "The maximum number of unlocking positions an account can have simultaneously Note: As health checking requires looping through each, this number must not be too large. If so, having too many could prevent the account from being liquidated due to gas constraints.", "allOf": [ @@ -83,6 +92,9 @@ "HealthContractBase_for_String": { "type": "string" }, + "IncentivesUnchecked": { + "type": "string" + }, "OracleBase_for_String": { "type": "string" }, @@ -336,6 +348,20 @@ }, "additionalProperties": false }, + { + "description": "For assets lent to the Red Bank, some can accumulate incentive rewards. This message claims all of them adds them to account balance.", + "type": "object", + "required": [ + "claim_rewards" + ], + "properties": { + "claim_rewards": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Repay coin of specified amount back to Red Bank. If `amount: AccountBalance` is passed, the repaid amount will be the minimum between account balance for denom and total owed. The sender will repay on behalf of the recipient account. If 'recipient_account_id: None', the sender repays to its own account.", "type": "object", @@ -828,6 +854,28 @@ }, "additionalProperties": false }, + { + "description": "Calls incentive contract to claim all rewards and increments account balance", + "type": "object", + "required": [ + "claim_rewards" + ], + "properties": { + "claim_rewards": { + "type": "object", + "required": [ + "account_id" + ], + "properties": { + "account_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Assert MaxLTV is either: - Healthy, if prior to actions MaxLTV health factor >= 1 or None - Not further weakened, if prior to actions MaxLTV health factor < 1 Emits a `position_changed` event.", "type": "object", @@ -1338,6 +1386,16 @@ } ] }, + "incentives": { + "anyOf": [ + { + "$ref": "#/definitions/IncentivesUnchecked" + }, + { + "type": "null" + } + ] + }, "max_unlocking_positions": { "anyOf": [ { @@ -1436,6 +1494,9 @@ } ] }, + "IncentivesUnchecked": { + "type": "string" + }, "LiquidateRequest_for_VaultBase_for_Addr": { "oneOf": [ { @@ -2429,6 +2490,7 @@ "type": "object", "required": [ "health_contract", + "incentives", "max_unlocking_positions", "oracle", "ownership", @@ -2447,6 +2509,9 @@ "health_contract": { "type": "string" }, + "incentives": { + "type": "string" + }, "max_unlocking_positions": { "$ref": "#/definitions/Uint128" }, diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 64a692faf..370d85eb5 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -972,6 +972,7 @@ "type": "object", "required": [ "health_contract", + "incentives", "max_unlocking_positions", "oracle", "ownership", @@ -990,6 +991,9 @@ "health_contract": { "type": "string" }, + "incentives": { + "type": "string" + }, "max_unlocking_positions": { "$ref": "#/definitions/Uint128" }, diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 67a9ad5b1..6d3c00fe0 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -186,6 +186,7 @@ export class Deployer { swapper: this.storage.addresses.swapper!, zapper: this.storage.addresses.zapper!, health_contract: this.storage.addresses.healthContract!, + incentives: this.config.incentives.addr, } await this.instantiate('creditManager', this.storage.codeIds.creditManager!, msg) diff --git a/scripts/deploy/osmosis/mainnet.ts b/scripts/deploy/osmosis/mainnet.ts index e028a9cf1..294008fa5 100644 --- a/scripts/deploy/osmosis/mainnet.ts +++ b/scripts/deploy/osmosis/mainnet.ts @@ -23,6 +23,7 @@ export const osmosisMainnetConfig: DeploymentConfig = { // oracle and redbank contract addresses can be found: https://github.com/mars-protocol/red-bank/blob/master/README.md#osmosis-1 oracle: { addr: 'osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g' }, redBank: { addr: 'osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg' }, + incentives: { addr: 'osmo1nkahswfr8shg8rlxqwup0vgahp0dk4x8w6tkv3rra8rratnut36sk22vrm' }, params: { addr: 'TBD' }, swapRoutes: [ { denomIn: uosmo, denomOut: uatom, route: [{ token_out_denom: uatom, pool_id: '1' }] }, diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index fccece58d..213694bb6 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -34,6 +34,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { oracle: { addr: 'osmo1khe29uw3t85nmmp3mtr8dls7v2qwsfk3tndu5h4w5g2r5tzlz5qqarq2e2' }, redBank: { addr: 'osmo1dl4rylasnd7mtfzlkdqn2gr0ss4gvyykpvr6d7t5ylzf6z535n9s5jjt8u' }, params: { addr: 'osmo1xvg28lrr72662t9u0hntt76lyax9zvptdvdmff4k2q9dhjm8x6ws9zym4v' }, + incentives: { addr: 'osmo1zyz57xf82963mcsgqu3hq5y0h9mrltm4ttq2qe5mjth9ezp3375qe0sm7d' }, swapRoutes: [ { denomIn: uosmo, denomOut: nUSDC, route: [{ token_out_denom: nUSDC, pool_id: '6' }] }, { denomIn: nUSDC, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '6' }] }, diff --git a/scripts/package.json b/scripts/package.json index 056eff62d..ce6498e77 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -36,8 +36,8 @@ "@babel/preset-env": "^7.22.9", "@babel/preset-typescript": "^7.22.5", "@types/jest": "^29.5.3", - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^6.1.0", + "@typescript-eslint/parser": "^6.1.0", "eslint": "^8.45.0", "eslint-config-prettier": "^8.8.0", "jest": "^29.6.1", diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 7d3529914..3b56a455d 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -23,6 +23,7 @@ export interface DeploymentConfig { deployerMnemonic: string oracle: { addr: string } redBank: { addr: string } + incentives: { addr: string } params: { addr: string } vaults: VaultConfigBaseForString[] allowedCoins: string[] diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 416214da2..6262dcabd 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -9,6 +9,7 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/co import { StdFee } from '@cosmjs/amino' import { HealthContractBaseForString, + IncentivesUnchecked, Uint128, OracleBaseForString, ParamsBaseForString, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 5373877f3..d60772dad 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -10,6 +10,7 @@ import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { HealthContractBaseForString, + IncentivesUnchecked, Uint128, OracleBaseForString, ParamsBaseForString, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 59b3c61b1..066fc5f5e 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -10,6 +10,7 @@ import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { HealthContractBaseForString, + IncentivesUnchecked, Uint128, OracleBaseForString, ParamsBaseForString, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index b64727b90..3860aec54 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -6,6 +6,7 @@ */ export type HealthContractBaseForString = string +export type IncentivesUnchecked = string export type Uint128 = string export type OracleBaseForString = string export type ParamsBaseForString = string @@ -14,6 +15,7 @@ export type SwapperBaseForString = string export type ZapperBaseForString = string export interface InstantiateMsg { health_contract: HealthContractBaseForString + incentives: IncentivesUnchecked max_unlocking_positions: Uint128 oracle: OracleBaseForString owner: string @@ -71,6 +73,9 @@ export type Action = | { reclaim: ActionCoin } + | { + claim_rewards: {} + } | { repay: { coin: ActionCoin @@ -206,6 +211,11 @@ export type CallbackMsg = coin: ActionCoin } } + | { + claim_rewards: { + account_id: string + } + } | { assert_max_ltv: { account_id: string @@ -342,6 +352,7 @@ export interface VaultBaseForString { export interface ConfigUpdates { account_nft?: AccountNftBaseForString | null health_contract?: HealthContractBaseForString | null + incentives?: IncentivesUnchecked | null max_unlocking_positions?: Uint128 | null oracle?: OracleBaseForString | null red_bank?: RedBankUnchecked | null @@ -465,6 +476,7 @@ export interface VaultPositionResponseItem { export interface ConfigResponse { account_nft?: string | null health_contract: string + incentives: string max_unlocking_positions: Uint128 oracle: string ownership: OwnerResponse diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 1137cd4c1..ae9fd8eff 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -149,6 +149,7 @@ export interface VaultPositionResponseItem { export interface ConfigResponse { account_nft?: string | null health_contract: string + incentives: string max_unlocking_positions: Uint128 oracle: string ownership: OwnerResponse diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 86858e4e4..1b62982c0 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1552,14 +1552,14 @@ shelljs "0.8.5" wasm-ast-types "^0.24.0" -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.3.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.5.0": +"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.5.1": version "4.5.1" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== @@ -2148,7 +2148,7 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/json-schema@^7.0.11": +"@types/json-schema@^7.0.11", "@types/json-schema@^7.0.12": version "7.0.12" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== @@ -2178,9 +2178,9 @@ resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz" integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== -"@types/semver@^7.3.12": +"@types/semver@^7.5.0": version "7.5.0" - resolved "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== "@types/stack-utils@^2.0.0": @@ -2200,92 +2200,90 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.0.0.tgz#19ff4f1cab8d6f8c2c1825150f7a840bc5d9bdc4" - integrity sha512-xuv6ghKGoiq856Bww/yVYnXGsKa588kY3M0XK7uUW/3fJNNULKRfZfSBkMTSpqGG/8ZCXCadfh8G/z/B4aqS/A== - dependencies: - "@eslint-community/regexpp" "^4.5.0" - "@typescript-eslint/scope-manager" "6.0.0" - "@typescript-eslint/type-utils" "6.0.0" - "@typescript-eslint/utils" "6.0.0" - "@typescript-eslint/visitor-keys" "6.0.0" +"@typescript-eslint/eslint-plugin@^6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.1.0.tgz#96f3ca6615717659d06c9f7161a1d14ab0c49c66" + integrity sha512-qg7Bm5TyP/I7iilGyp6DRqqkt8na00lI6HbjWZObgk3FFSzH5ypRwAHXJhJkwiRtTcfn+xYQIMOR5kJgpo6upw== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.1.0" + "@typescript-eslint/type-utils" "6.1.0" + "@typescript-eslint/utils" "6.1.0" + "@typescript-eslint/visitor-keys" "6.1.0" debug "^4.3.4" - grapheme-splitter "^1.0.4" graphemer "^1.4.0" ignore "^5.2.4" natural-compare "^1.4.0" natural-compare-lite "^1.4.0" - semver "^7.5.0" + semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/parser@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.0.0.tgz#46b2600fd1f67e62fc00a28093a75f41bf7effc4" - integrity sha512-TNaufYSPrr1U8n+3xN+Yp9g31vQDJqhXzzPSHfQDLcaO4tU+mCfODPxCwf4H530zo7aUBE3QIdxCXamEnG04Tg== +"@typescript-eslint/parser@^6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.1.0.tgz#3135bf65dca5340d8650703eb8cb83113e156ee5" + integrity sha512-hIzCPvX4vDs4qL07SYzyomamcs2/tQYXg5DtdAfj35AyJ5PIUqhsLf4YrEIFzZcND7R2E8tpQIZKayxg8/6Wbw== dependencies: - "@typescript-eslint/scope-manager" "6.0.0" - "@typescript-eslint/types" "6.0.0" - "@typescript-eslint/typescript-estree" "6.0.0" - "@typescript-eslint/visitor-keys" "6.0.0" + "@typescript-eslint/scope-manager" "6.1.0" + "@typescript-eslint/types" "6.1.0" + "@typescript-eslint/typescript-estree" "6.1.0" + "@typescript-eslint/visitor-keys" "6.1.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.0.0.tgz#8ede47a37cb2b7ed82d329000437abd1113b5e11" - integrity sha512-o4q0KHlgCZTqjuaZ25nw5W57NeykZT9LiMEG4do/ovwvOcPnDO1BI5BQdCsUkjxFyrCL0cSzLjvIMfR9uo7cWg== +"@typescript-eslint/scope-manager@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.1.0.tgz#a6cdbe11630614f8c04867858a42dd56590796ed" + integrity sha512-AxjgxDn27hgPpe2rQe19k0tXw84YCOsjDJ2r61cIebq1t+AIxbgiXKvD4999Wk49GVaAcdJ/d49FYel+Pp3jjw== dependencies: - "@typescript-eslint/types" "6.0.0" - "@typescript-eslint/visitor-keys" "6.0.0" + "@typescript-eslint/types" "6.1.0" + "@typescript-eslint/visitor-keys" "6.1.0" -"@typescript-eslint/type-utils@6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.0.0.tgz#0478d8a94f05e51da2877cc0500f1b3c27ac7e18" - integrity sha512-ah6LJvLgkoZ/pyJ9GAdFkzeuMZ8goV6BH7eC9FPmojrnX9yNCIsfjB+zYcnex28YO3RFvBkV6rMV6WpIqkPvoQ== +"@typescript-eslint/type-utils@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.1.0.tgz#21cc6c3bc1980b03f9eb4e64580d0c5be6f08215" + integrity sha512-kFXBx6QWS1ZZ5Ni89TyT1X9Ag6RXVIVhqDs0vZE/jUeWlBv/ixq2diua6G7ece6+fXw3TvNRxP77/5mOMusx2w== dependencies: - "@typescript-eslint/typescript-estree" "6.0.0" - "@typescript-eslint/utils" "6.0.0" + "@typescript-eslint/typescript-estree" "6.1.0" + "@typescript-eslint/utils" "6.1.0" debug "^4.3.4" ts-api-utils "^1.0.1" -"@typescript-eslint/types@6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.0.0.tgz#19795f515f8decbec749c448b0b5fc76d82445a1" - integrity sha512-Zk9KDggyZM6tj0AJWYYKgF0yQyrcnievdhG0g5FqyU3Y2DRxJn4yWY21sJC0QKBckbsdKKjYDV2yVrrEvuTgxg== +"@typescript-eslint/types@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.1.0.tgz#2d607c62827bb416ada5c96ebfa2ef84e45a8dfa" + integrity sha512-+Gfd5NHCpDoHDOaU/yIF3WWRI2PcBRKKpP91ZcVbL0t5tQpqYWBs3z/GGhvU+EV1D0262g9XCnyqQh19prU0JQ== -"@typescript-eslint/typescript-estree@6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.0.0.tgz#1e09aab7320e404fb9f83027ea568ac24e372f81" - integrity sha512-2zq4O7P6YCQADfmJ5OTDQTP3ktajnXIRrYAtHM9ofto/CJZV3QfJ89GEaM2BNGeSr1KgmBuLhEkz5FBkS2RQhQ== +"@typescript-eslint/typescript-estree@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.1.0.tgz#ea382f6482ba698d7e993a88ce5391ea7a66c33d" + integrity sha512-nUKAPWOaP/tQjU1IQw9sOPCDavs/iU5iYLiY/6u7gxS7oKQoi4aUxXS1nrrVGTyBBaGesjkcwwHkbkiD5eBvcg== dependencies: - "@typescript-eslint/types" "6.0.0" - "@typescript-eslint/visitor-keys" "6.0.0" + "@typescript-eslint/types" "6.1.0" + "@typescript-eslint/visitor-keys" "6.1.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" - semver "^7.5.0" + semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.0.0.tgz#27a16d0d8f2719274a39417b9782f7daa3802db0" - integrity sha512-SOr6l4NB6HE4H/ktz0JVVWNXqCJTOo/mHnvIte1ZhBQ0Cvd04x5uKZa3zT6tiodL06zf5xxdK8COiDvPnQ27JQ== - dependencies: - "@eslint-community/eslint-utils" "^4.3.0" - "@types/json-schema" "^7.0.11" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "6.0.0" - "@typescript-eslint/types" "6.0.0" - "@typescript-eslint/typescript-estree" "6.0.0" - eslint-scope "^5.1.1" - semver "^7.5.0" - -"@typescript-eslint/visitor-keys@6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.0.0.tgz#0b49026049fbd096d2c00c5e784866bc69532a31" - integrity sha512-cvJ63l8c0yXdeT5POHpL0Q1cZoRcmRKFCtSjNGJxPkcP571EfZMcNbzWAc7oK3D1dRzm/V5EwtkANTZxqvuuUA== - dependencies: - "@typescript-eslint/types" "6.0.0" +"@typescript-eslint/utils@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.1.0.tgz#1641843792b4e3451cc692e2c73055df8b26f453" + integrity sha512-wp652EogZlKmQoMS5hAvWqRKplXvkuOnNzZSE0PVvsKjpexd/XznRVHAtrfHFYmqaJz0DFkjlDsGYC9OXw+OhQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.1.0" + "@typescript-eslint/types" "6.1.0" + "@typescript-eslint/typescript-estree" "6.1.0" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.1.0.tgz#d2b84dff6b58944d3257ea03687e269a788c73be" + integrity sha512-yQeh+EXhquh119Eis4k0kYhj9vmFzNpbhM3LftWQVwqVjipCkwHBQOZutcYW+JVkjtTG9k8nrZU1UoNedPDd1A== + dependencies: + "@typescript-eslint/types" "6.1.0" eslint-visitor-keys "^3.4.1" acorn-jsx@^5.3.2: @@ -3016,14 +3014,6 @@ eslint-config-prettier@^8.8.0: resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz" integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - eslint-scope@^7.2.0: version "7.2.0" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz" @@ -3108,11 +3098,6 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" @@ -3436,11 +3421,6 @@ graceful-fs@^4.1.15, graceful-fs@^4.2.9: resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== - graphemer@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" @@ -4959,7 +4939,7 @@ semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.5.0, semver@^7.5.3: +semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== From 0b29ebc901ffa0eb847f9f4bde0ce8b364f1b2b2 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 21 Jul 2023 15:34:08 +0200 Subject: [PATCH 183/218] Add v1 -> v2 migration endpoint (#162) * Add v1 -> v2 migration endpoint * review updates --- Cargo.lock | 48 +++--- contracts/credit-manager/src/contract.rs | 18 ++- contracts/credit-manager/src/lib.rs | 1 + .../credit-manager/src/migrations/helpers.rs | 24 +++ .../credit-manager/src/migrations/mod.rs | 2 + .../credit-manager/src/migrations/v2_0_0.rs | 62 ++++++++ contracts/credit-manager/src/state.rs | 11 +- contracts/credit-manager/src/update_config.rs | 9 +- .../credit-manager/tests/test_migration_v2.rs | 141 ++++++++++++++++++ contracts/swapper/osmosis/tests/helpers.rs | 12 +- .../v2-zapper/osmosis/tests/helpers/utils.rs | 6 +- .../osmosis/tests/helpers/mock_env.rs | 23 +-- packages/rover/Cargo.toml | 1 + packages/rover/src/adapters/mod.rs | 1 + .../rover/src/adapters/rewards_collector.rs | 7 + packages/rover/src/error.rs | 4 + packages/rover/src/msg/migrate.rs | 20 +++ packages/rover/src/msg/mod.rs | 2 + schemas/mars-mock-vault/mars-mock-vault.json | 2 +- 19 files changed, 335 insertions(+), 59 deletions(-) create mode 100644 contracts/credit-manager/src/migrations/helpers.rs create mode 100644 contracts/credit-manager/src/migrations/mod.rs create mode 100644 contracts/credit-manager/src/migrations/v2_0_0.rs create mode 100644 contracts/credit-manager/tests/test_migration_v2.rs create mode 100644 packages/rover/src/adapters/rewards_collector.rs create mode 100644 packages/rover/src/msg/migrate.rs diff --git a/Cargo.lock b/Cargo.lock index 098f89bb3..2b20ba881 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,9 +633,9 @@ dependencies = [ [[package]] name = "cw-vault-standard" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e72c053adf399c2dc95cf07ee0c8723dfceeb13ba127cc96ba31d96e454919" +checksum = "a6fa4a5fff2fbf57eb19b06eb757d21a84933c9526531160ef77b445d8e9d36a" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1613,6 +1613,7 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard", + "cw2 1.1.0", "cw721 0.18.0", "cw721-base 0.18.0", "mars-account-nft", @@ -1717,7 +1718,7 @@ dependencies = [ "mars-owner", "mars-rover", "mars-swapper-base", - "osmosis-std 0.16.0", + "osmosis-std 0.16.1", "osmosis-test-tube", "schemars", "thiserror", @@ -1769,7 +1770,7 @@ dependencies = [ "cw-utils 1.0.1", "cw2 1.1.0", "mars-v2-zapper-base", - "osmosis-std 0.16.0", + "osmosis-std 0.16.1", "osmosis-test-tube", ] @@ -1794,7 +1795,7 @@ dependencies = [ "cosmwasm-std", "mars-owner", "mars-v3-zapper-base", - "osmosis-std 0.16.0", + "osmosis-std 0.16.1", "osmosis-test-tube", ] @@ -1937,13 +1938,13 @@ dependencies = [ [[package]] name = "osmosis-std" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c254b73ab62f7e7a19eb7249cf1443e49999ecc8169bb4535a855b6e87d55498" +checksum = "2fa46d2ad5ae738572887974e000934374ce3546b820505c0ee19ca708e49622" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.16.0", + "osmosis-std-derive 0.16.1", "prost 0.11.9", "prost-types", "schemars", @@ -1965,9 +1966,9 @@ dependencies = [ [[package]] name = "osmosis-std-derive" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a6feeb2c5de684262032a6719ff58cfecfdffa5f31eba1835faa857ac3a762" +checksum = "11c2ba5535743617d6f44ae8d572d064fabab6d06ffcf403512f89c58954dbe9" dependencies = [ "itertools 0.10.5", "proc-macro2", @@ -1978,15 +1979,15 @@ dependencies = [ [[package]] name = "osmosis-test-tube" -version = "16.0.0" +version = "16.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4988b1215f3fffdb1af5ae4930ed97e0be2d61221fc27dff7b89973670cbb50" +checksum = "527375c01396e7e4de4ccc18a0141aeb6b342dc089d30c57282025f3a8753e72" dependencies = [ "base64", "bindgen", "cosmrs", "cosmwasm-std", - "osmosis-std 0.16.0", + "osmosis-std 0.16.1", "prost 0.11.9", "serde", "serde_json", @@ -2098,9 +2099,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92de25114670a878b1261c79c9f8f729fb97e95bac93f6312f583c60dd6a1dfe" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] @@ -2188,9 +2189,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" dependencies = [ "proc-macro2", ] @@ -2502,9 +2503,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.171" +version = "1.0.173" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +checksum = "e91f70896d6720bc714a4a57d22fc91f1db634680e65c8efe13323f1fa38d53f" dependencies = [ "serde_derive", ] @@ -2549,9 +2550,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.171" +version = "1.0.173" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +checksum = "a6250dde8342e0232232be9ca3db7aa40aceb5a3e5dd9bddbc00d99a007cde49" dependencies = [ "proc-macro2", "quote", @@ -2873,13 +2874,14 @@ dependencies = [ [[package]] name = "test-tube" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2152b79646afbca662896e917b94fb839af6939eb592b7af9cd5dcb1cd42f99e" +checksum = "a520f35c8ca6665ea5231a81ce9110f49a6901a1cbedb751e72859e83320bfea" dependencies = [ "base64", "cosmrs", "cosmwasm-std", + "osmosis-std 0.16.1", "prost 0.11.9", "serde", "serde_json", diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index abdaed1d7..b6320503d 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -5,12 +5,14 @@ use cw2::set_contract_version; use mars_rover::{ adapters::vault::VAULT_REQUEST_REPLY_ID, error::{ContractError, ContractResult}, - msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, }; use crate::{ execute::{create_credit_account, dispatch_actions, execute_callback}, instantiate::store_config, + migrations, + migrations::helpers::assert_migration_env, query::{ query_all_coin_balances, query_all_debt_shares, query_all_total_debt_shares, query_all_vault_positions, query_config, query_positions, query_total_debt_shares, @@ -23,8 +25,8 @@ use crate::{ zap::{estimate_provide_liquidity, estimate_withdraw_liquidity}, }; -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( @@ -117,3 +119,13 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { }; res.map_err(Into::into) } + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> ContractResult { + match msg { + MigrateMsg::V1_0_0ToV2_0_0(updates) => { + assert_migration_env(deps.storage, "1.0.0", "2.0.0")?; + migrations::v2_0_0::migrate(deps, env, updates) + } + } +} diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 7301eb9ec..73a3194cc 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -11,6 +11,7 @@ pub mod lend; pub mod liquidate; pub mod liquidate_deposit; pub mod liquidate_lend; +pub mod migrations; pub mod query; pub mod reclaim; pub mod refund; diff --git a/contracts/credit-manager/src/migrations/helpers.rs b/contracts/credit-manager/src/migrations/helpers.rs new file mode 100644 index 000000000..012d83798 --- /dev/null +++ b/contracts/credit-manager/src/migrations/helpers.rs @@ -0,0 +1,24 @@ +use cosmwasm_std::Storage; +use cw2::{assert_contract_version, VersionError::WrongVersion}; +use mars_rover::error::{ContractError::Version, ContractResult}; + +use crate::contract::{CONTRACT_NAME, CONTRACT_VERSION}; + +pub fn assert_migration_env( + storage: &dyn Storage, + old_version: &str, + new_version: &str, +) -> ContractResult<()> { + // Assert contract name & from-version is correct + assert_contract_version(storage, CONTRACT_NAME, old_version)?; + + // Assert to-version is correct + if CONTRACT_VERSION != new_version { + return Err(Version(WrongVersion { + expected: new_version.to_string(), + found: CONTRACT_VERSION.to_string(), + })); + } + + Ok(()) +} diff --git a/contracts/credit-manager/src/migrations/mod.rs b/contracts/credit-manager/src/migrations/mod.rs new file mode 100644 index 000000000..4ae0ec13a --- /dev/null +++ b/contracts/credit-manager/src/migrations/mod.rs @@ -0,0 +1,2 @@ +pub mod helpers; +pub mod v2_0_0; diff --git a/contracts/credit-manager/src/migrations/v2_0_0.rs b/contracts/credit-manager/src/migrations/v2_0_0.rs new file mode 100644 index 000000000..facb15b1f --- /dev/null +++ b/contracts/credit-manager/src/migrations/v2_0_0.rs @@ -0,0 +1,62 @@ +use cosmwasm_std::{DepsMut, Env, Response}; +use cw2::set_contract_version; +use mars_owner::OwnerInit; +use mars_rover::{error::ContractResult, msg::migrate::V2Updates}; + +use crate::{ + contract::{CONTRACT_NAME, CONTRACT_VERSION}, + state::{HEALTH_CONTRACT, INCENTIVES, OWNER, PARAMS, REWARDS_COLLECTOR, SWAPPER}, +}; + +const FROM_VERSION: &str = "1.0.0"; + +/// Taken from original Owner package version: https://github.com/mars-protocol/owner/blob/e807c6b12511987577645c8bad68cc7bd6da5398/src/owner.rs#L158 +pub mod v1_owner { + use cosmwasm_schema::cw_serde; + use cosmwasm_std::Addr; + use cw_storage_plus::Item; + + pub const OWNER: Item = Item::new("owner"); + + #[cw_serde] + pub enum OwnerState { + B(OwnerSetNoneProposed), + } + + #[cw_serde] + pub struct OwnerSetNoneProposed { + pub owner: Addr, + } + + pub fn current_owner(state: OwnerState) -> Addr { + match state { + OwnerState::B(b) => b.owner, + } + } +} + +pub fn migrate(deps: DepsMut, env: Env, updates: V2Updates) -> ContractResult { + HEALTH_CONTRACT.save(deps.storage, &updates.health_contract.check(deps.api)?)?; + PARAMS.save(deps.storage, &updates.params.check(deps.api)?)?; + INCENTIVES.save(deps.storage, &updates.incentives.check(deps.api, env.contract.address)?)?; + REWARDS_COLLECTOR.save(deps.storage, &updates.rewards_collector)?; + SWAPPER.save(deps.storage, &updates.swapper.check(deps.api)?)?; + + // Owner package updated, re-initializing + let old_owner_state = v1_owner::OWNER.load(deps.storage)?; + let old_owner = v1_owner::current_owner(old_owner_state); + v1_owner::OWNER.remove(deps.storage); + OWNER.initialize( + deps.storage, + deps.api, + OwnerInit::SetInitialOwner { + owner: old_owner.to_string(), + }, + )?; + + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::new() + .add_attribute("action", "migrate") + .add_attribute("from_version", FROM_VERSION) + .add_attribute("to_version", CONTRACT_VERSION)) +} diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index b56813f9e..2f70744e9 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,12 +1,11 @@ -use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::{Item, Map}; use mars_owner::Owner; use mars_rover::{ adapters::{ account_nft::AccountNft, health::HealthContract, incentives::Incentives, oracle::Oracle, - params::Params, red_bank::RedBank, swap::Swapper, vault::VaultPositionAmount, - zapper::Zapper, + params::Params, red_bank::RedBank, rewards_collector::RewardsCollector, swap::Swapper, + vault::VaultPositionAmount, zapper::Zapper, }, reentrancy_guard::ReentrancyGuard, }; @@ -14,12 +13,6 @@ use mars_rover_health_types::AccountKind; use crate::vault::RequestTempStorage; -#[cw_serde] -pub struct RewardsCollector { - pub address: String, - pub account_id: String, -} - // Contract dependencies pub const ACCOUNT_NFT: Item = Item::new("account_nft"); pub const ORACLE: Item = Item::new("oracle"); diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index baf7f4a6f..91955f3d7 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -2,14 +2,17 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, MessageInfo, Response, Wa use cw721_base::Action; use mars_account_nft::{msg::ExecuteMsg as NftExecuteMsg, nft_config::NftConfigUpdates}; use mars_owner::OwnerUpdate; -use mars_rover::{error::ContractResult, msg::instantiate::ConfigUpdates}; +use mars_rover::{ + adapters::rewards_collector::RewardsCollector, error::ContractResult, + msg::instantiate::ConfigUpdates, +}; use mars_rover_health_types::AccountKind; use crate::{ execute::create_credit_account, state::{ - RewardsCollector, ACCOUNT_NFT, HEALTH_CONTRACT, INCENTIVES, MAX_UNLOCKING_POSITIONS, - ORACLE, OWNER, RED_BANK, REWARDS_COLLECTOR, SWAPPER, ZAPPER, + ACCOUNT_NFT, HEALTH_CONTRACT, INCENTIVES, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, + REWARDS_COLLECTOR, SWAPPER, ZAPPER, }, }; diff --git a/contracts/credit-manager/tests/test_migration_v2.rs b/contracts/credit-manager/tests/test_migration_v2.rs new file mode 100644 index 000000000..60a7da9bd --- /dev/null +++ b/contracts/credit-manager/tests/test_migration_v2.rs @@ -0,0 +1,141 @@ +use cosmwasm_std::{ + testing::{mock_dependencies, mock_env}, + Addr, +}; +use cw2::VersionError; +use mars_credit_manager::{ + contract::{migrate, CONTRACT_NAME}, + migrations::v2_0_0::{v1_owner, v1_owner::OwnerSetNoneProposed}, + state::{HEALTH_CONTRACT, INCENTIVES, OWNER, PARAMS, REWARDS_COLLECTOR, SWAPPER}, +}; +use mars_rover::{ + adapters::{ + health::HealthContractUnchecked, incentives::IncentivesUnchecked, params::ParamsUnchecked, + rewards_collector::RewardsCollector, swap::SwapperUnchecked, + }, + error::ContractError, + msg::{migrate::V2Updates, MigrateMsg}, +}; + +pub mod helpers; + +#[test] +fn wrong_contract_name() { + let mut deps = mock_dependencies(); + cw2::set_contract_version(deps.as_mut().storage, "contract_xyz", "1.0.0").unwrap(); + + let err = migrate( + deps.as_mut(), + mock_env(), + MigrateMsg::V1_0_0ToV2_0_0(V2Updates { + health_contract: HealthContractUnchecked::new("health".to_string()), + params: ParamsUnchecked::new("params".to_string()), + incentives: IncentivesUnchecked::new("incentives".to_string()), + swapper: SwapperUnchecked::new("swapper".to_string()), + rewards_collector: RewardsCollector { + address: "xyz".to_string(), + account_id: "123".to_string(), + }, + }), + ) + .unwrap_err(); + + assert_eq!( + err, + ContractError::Version(VersionError::WrongContract { + expected: "mars-credit-manager".to_string(), + found: "contract_xyz".to_string() + }) + ); +} + +#[test] +fn wrong_contract_version() { + let mut deps = mock_dependencies(); + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "4.1.0").unwrap(); + + let err = migrate( + deps.as_mut(), + mock_env(), + MigrateMsg::V1_0_0ToV2_0_0(V2Updates { + health_contract: HealthContractUnchecked::new("health".to_string()), + params: ParamsUnchecked::new("params".to_string()), + incentives: IncentivesUnchecked::new("incentives".to_string()), + swapper: SwapperUnchecked::new("swapper".to_string()), + rewards_collector: RewardsCollector { + address: "xyz".to_string(), + account_id: "123".to_string(), + }, + }), + ) + .unwrap_err(); + + assert_eq!( + err, + ContractError::Version(VersionError::WrongVersion { + expected: "1.0.0".to_string(), + found: "4.1.0".to_string() + }) + ); +} + +#[test] +fn successful_migration() { + let mut deps = mock_dependencies(); + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "1.0.0").unwrap(); + + let old_owner = "spiderman_246"; + v1_owner::OWNER + .save( + deps.as_mut().storage, + &v1_owner::OwnerState::B(OwnerSetNoneProposed { + owner: Addr::unchecked(old_owner), + }), + ) + .unwrap(); + + let health_contract = "health_addr_123".to_string(); + let params = "params_addr_456".to_string(); + let incentives = "incentives_addr_789".to_string(); + let swapper = "swapper_addr_012".to_string(); + let rewards_collector = RewardsCollector { + address: "rewards_collector_addr".to_string(), + account_id: "4117".to_string(), + }; + + migrate( + deps.as_mut(), + mock_env(), + MigrateMsg::V1_0_0ToV2_0_0(V2Updates { + health_contract: HealthContractUnchecked::new(health_contract.clone()), + params: ParamsUnchecked::new(params.clone()), + incentives: IncentivesUnchecked::new(incentives.clone()), + swapper: SwapperUnchecked::new(swapper.clone()), + rewards_collector: rewards_collector.clone(), + }), + ) + .unwrap(); + + let set_health_contract = + HEALTH_CONTRACT.load(deps.as_ref().storage).unwrap().address().to_string(); + assert_eq!(health_contract, set_health_contract); + + let set_params = PARAMS.load(deps.as_ref().storage).unwrap().address().to_string(); + assert_eq!(params, set_params); + + let set_incentives = INCENTIVES.load(deps.as_ref().storage).unwrap().addr.to_string(); + assert_eq!(incentives, set_incentives); + + let set_swapper = SWAPPER.load(deps.as_ref().storage).unwrap().address().to_string(); + assert_eq!(swapper, set_swapper); + + let set_rewards = REWARDS_COLLECTOR.load(deps.as_ref().storage).unwrap(); + assert_eq!(rewards_collector, set_rewards); + + let o = OWNER.query(deps.as_ref().storage).unwrap(); + assert_eq!(old_owner.to_string(), o.owner.unwrap()); + assert!(o.proposed.is_none()); + assert!(o.initialized); + assert!(!o.abolished); + assert!(o.emergency_owner.is_none()); +} diff --git a/contracts/swapper/osmosis/tests/helpers.rs b/contracts/swapper/osmosis/tests/helpers.rs index 64aa02529..3065e4c54 100644 --- a/contracts/swapper/osmosis/tests/helpers.rs +++ b/contracts/swapper/osmosis/tests/helpers.rs @@ -2,13 +2,15 @@ use std::{fmt::Display, str::FromStr}; use cosmwasm_std::{Coin, Decimal, Uint128}; use mars_rover::adapters::swap::InstantiateMsg; -use osmosis_std::types::osmosis::{ - gamm::v1beta1::{MsgSwapExactAmountIn, MsgSwapExactAmountInResponse}, - poolmanager::v1beta1::SwapAmountInRoute, +use osmosis_std::types::{ + cosmos::bank::v1beta1::QueryBalanceRequest, + osmosis::{ + gamm::v1beta1::{MsgSwapExactAmountIn, MsgSwapExactAmountInResponse}, + poolmanager::v1beta1::SwapAmountInRoute, + }, }; use osmosis_test_tube::{ - cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest, Account, Bank, ExecuteResponse, - Gamm, OsmosisTestApp, Runner, RunnerError, SigningAccount, Wasm, + Account, Bank, ExecuteResponse, Gamm, OsmosisTestApp, Runner, RunnerError, SigningAccount, Wasm, }; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); diff --git a/contracts/v2-zapper/osmosis/tests/helpers/utils.rs b/contracts/v2-zapper/osmosis/tests/helpers/utils.rs index e1145bd9b..b1701f78d 100644 --- a/contracts/v2-zapper/osmosis/tests/helpers/utils.rs +++ b/contracts/v2-zapper/osmosis/tests/helpers/utils.rs @@ -1,10 +1,8 @@ use std::{fmt::Display, str::FromStr}; use mars_v2_zapper_base::InstantiateMsg; -use osmosis_test_tube::{ - cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest, Bank, OsmosisTestApp, RunnerError, - SigningAccount, Wasm, -}; +use osmosis_std::types::cosmos::bank::v1beta1::QueryBalanceRequest; +use osmosis_test_tube::{Bank, OsmosisTestApp, RunnerError, SigningAccount, Wasm}; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); diff --git a/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs b/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs index a2ec6286b..72dd02775 100644 --- a/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs +++ b/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs @@ -4,20 +4,20 @@ use anyhow::Result as AnyResult; use cosmwasm_std::coin; use mars_owner::{OwnerResponse, OwnerUpdate}; use mars_v3_zapper_base::msg::{CallbackMsg, ExecuteMsg, InstantiateMsg, QueryMsg}; -use osmosis_std::types::osmosis::{ - concentratedliquidity, - concentratedliquidity::v1beta1::{ - CreateConcentratedLiquidityPoolsProposal, FullPositionBreakdown, PoolRecord, PoolsRequest, - UserPositionsRequest, +use osmosis_std::types::{ + cosmos::bank::v1beta1::QueryBalanceRequest, + cosmwasm::wasm::v1::MsgExecuteContractResponse, + osmosis::{ + concentratedliquidity, + concentratedliquidity::v1beta1::{ + CreateConcentratedLiquidityPoolsProposal, FullPositionBreakdown, PoolRecord, + PoolsRequest, UserPositionsRequest, + }, }, }; use osmosis_test_tube::{ - cosmrs::proto::{ - cosmos::bank::v1beta1::QueryBalanceRequest, cosmwasm::wasm::v1::MsgExecuteContractResponse, - prost::Message, - }, - Account, Bank, ConcentratedLiquidity, GovWithAppAccess, Module, OsmosisTestApp, - RunnerExecuteResult, SigningAccount, Wasm, + cosmrs::proto::prost::Message, Account, Bank, ConcentratedLiquidity, GovWithAppAccess, Module, + OsmosisTestApp, RunnerExecuteResult, SigningAccount, Wasm, }; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); @@ -79,6 +79,7 @@ impl MockEnv { }], }, self.owner.address(), + false, &self.owner, ) .unwrap(); diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 49cba23cd..bf43e8433 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -20,6 +20,7 @@ backtraces = ["cosmwasm-std/backtraces"] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw2 = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true } cw-storage-plus = { workspace = true } diff --git a/packages/rover/src/adapters/mod.rs b/packages/rover/src/adapters/mod.rs index 3ba80bd82..244546040 100644 --- a/packages/rover/src/adapters/mod.rs +++ b/packages/rover/src/adapters/mod.rs @@ -4,6 +4,7 @@ pub mod incentives; pub mod oracle; pub mod params; pub mod red_bank; +pub mod rewards_collector; pub mod swap; pub mod vault; pub mod zapper; diff --git a/packages/rover/src/adapters/rewards_collector.rs b/packages/rover/src/adapters/rewards_collector.rs new file mode 100644 index 000000000..4e00986d0 --- /dev/null +++ b/packages/rover/src/adapters/rewards_collector.rs @@ -0,0 +1,7 @@ +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct RewardsCollector { + pub address: String, + pub account_id: String, +} diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 4ac015a55..1291d6d2b 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -2,6 +2,7 @@ use cosmwasm_std::{ CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError, Coin, DecimalRangeExceeded, OverflowError, StdError, Uint128, }; +use cw2::VersionError; use cw_utils::PaymentError; use mars_owner::OwnerError; use thiserror::Error; @@ -160,4 +161,7 @@ pub enum ContractError { #[error("There is more time left on the lock period")] UnlockNotReady, + + #[error("{0}")] + Version(#[from] VersionError), } diff --git a/packages/rover/src/msg/migrate.rs b/packages/rover/src/msg/migrate.rs new file mode 100644 index 000000000..0096f4756 --- /dev/null +++ b/packages/rover/src/msg/migrate.rs @@ -0,0 +1,20 @@ +use cosmwasm_schema::cw_serde; + +use crate::adapters::{ + health::HealthContractUnchecked, incentives::IncentivesUnchecked, params::ParamsUnchecked, + rewards_collector::RewardsCollector, swap::SwapperUnchecked, +}; + +#[cw_serde] +pub struct V2Updates { + pub health_contract: HealthContractUnchecked, + pub params: ParamsUnchecked, + pub incentives: IncentivesUnchecked, + pub swapper: SwapperUnchecked, + pub rewards_collector: RewardsCollector, +} + +#[cw_serde] +pub enum MigrateMsg { + V1_0_0ToV2_0_0(V2Updates), +} diff --git a/packages/rover/src/msg/mod.rs b/packages/rover/src/msg/mod.rs index 50cb8a531..2be94301f 100644 --- a/packages/rover/src/msg/mod.rs +++ b/packages/rover/src/msg/mod.rs @@ -1,7 +1,9 @@ pub mod execute; pub mod instantiate; +pub mod migrate; pub mod query; pub use execute::ExecuteMsg; pub use instantiate::InstantiateMsg; +pub use migrate::MigrateMsg; pub use query::QueryMsg; diff --git a/schemas/mars-mock-vault/mars-mock-vault.json b/schemas/mars-mock-vault/mars-mock-vault.json index aa1e2239e..c0878bd64 100644 --- a/schemas/mars-mock-vault/mars-mock-vault.json +++ b/schemas/mars-mock-vault/mars-mock-vault.json @@ -320,7 +320,7 @@ "description": "Additional ExecuteMsg variants for vaults that enable the Lockup extension.", "oneOf": [ { - "description": "Unlock is called to initiate unlocking a locked position held by the vault. The caller must pass the native vault tokens in the funds field. Emits an event with type `UNLOCKING_POSITION_CREATED_EVENT_TYPE` with an attribute with key `UNLOCKING_POSITION_ATTR_KEY` containing an u64 lockup_id. Also encodes the u64 lockup ID as binary and returns it in the Response's data field, so that it can be read by SubMsg replies.\n\nLike Redeem, this takes an amount so that the same API can be used for CW4626 and native tokens.", + "description": "Unlock is called to initiate unlocking a locked position held by the vault. The caller must pass the native vault tokens in the funds field. Emits an event with type `UNLOCKING_POSITION_CREATED_EVENT_TYPE` with an attribute with key `UNLOCKING_POSITION_ATTR_KEY` containing an u64 lockup_id.\n\nLike Redeem, this takes an amount so that the same API can be used for CW4626 and native tokens.", "type": "object", "required": [ "unlock" From c37d93439f7a5330aa9ae63408050ec26fc2a961 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 25 Jul 2023 09:31:44 +0200 Subject: [PATCH 184/218] Remove v3 zapper & update swapper (#163) --- Cargo.lock | 190 ++----- Cargo.toml | 12 +- contracts/swapper/base/Cargo.toml | 21 +- contracts/swapper/base/examples/schema.rs | 2 +- contracts/swapper/base/src/contract.rs | 35 +- contracts/swapper/base/src/error.rs | 27 +- contracts/swapper/base/src/traits.rs | 2 +- contracts/swapper/mock/Cargo.toml | 10 +- contracts/swapper/mock/src/contract.rs | 2 +- contracts/swapper/osmosis/Cargo.toml | 35 -- contracts/swapper/osmosis/examples/schema.rs | 11 - contracts/swapper/osmosis/src/contract.rs | 38 -- contracts/swapper/osmosis/src/helpers.rs | 18 - contracts/swapper/osmosis/src/lib.rs | 3 - contracts/swapper/osmosis/src/route.rs | 182 ------- contracts/swapper/osmosis/tests/helpers.rs | 142 ----- .../osmosis/tests/test_enumerate_routes.rs | 185 ------- .../swapper/osmosis/tests/test_estimate.rs | 183 ------- .../swapper/osmosis/tests/test_instantiate.rs | 46 -- .../swapper/osmosis/tests/test_set_route.rs | 373 ------------- contracts/swapper/osmosis/tests/test_swap.rs | 179 ------ .../osmosis/tests/test_update_admin.rs | 201 ------- contracts/v3-zapper/base/Cargo.toml | 28 - contracts/v3-zapper/base/examples/schema.rs | 10 - contracts/v3-zapper/base/src/contract.rs | 169 ------ contracts/v3-zapper/base/src/error.rs | 26 - contracts/v3-zapper/base/src/lib.rs | 6 - contracts/v3-zapper/base/src/msg.rs | 46 -- contracts/v3-zapper/base/src/state.rs | 3 - contracts/v3-zapper/base/src/traits.rs | 30 - contracts/v3-zapper/base/src/utils.rs | 18 - contracts/v3-zapper/osmosis/Cargo.toml | 29 - contracts/v3-zapper/osmosis/src/contract.rs | 43 -- contracts/v3-zapper/osmosis/src/lib.rs | 2 - .../v3-zapper/osmosis/src/position_manager.rs | 55 -- .../osmosis/tests/helpers/assertions.rs | 21 - .../osmosis/tests/helpers/generator.rs | 13 - .../osmosis/tests/helpers/mock_env.rs | 208 ------- .../v3-zapper/osmosis/tests/helpers/mod.rs | 5 - .../osmosis/tests/test_add_position.rs | 355 ------------ .../v3-zapper/osmosis/tests/test_callback.rs | 24 - .../osmosis/tests/test_update_owner.rs | 54 -- schema.Makefile.toml | 2 - .../mars-swapper-osmosis.json | 513 ------------------ .../mars-v3-zapper-base.json | 294 ---------- .../MarsSwapperOsmosis.client.ts | 288 ---------- .../MarsSwapperOsmosis.message-composer.ts | 187 ------- .../MarsSwapperOsmosis.react-query.ts | 246 --------- .../MarsSwapperOsmosis.types.ts | 105 ---- .../generated/mars-swapper-osmosis/bundle.ts | 14 - .../generated/mars-v2-zapper-base/bundle.ts | 10 +- .../MarsV3ZapperBase.client.ts | 168 ------ .../MarsV3ZapperBase.message-composer.ts | 128 ----- .../MarsV3ZapperBase.react-query.ts | 123 ----- .../MarsV3ZapperBase.types.ts | 66 --- .../generated/mars-v3-zapper-base/bundle.ts | 14 - 56 files changed, 134 insertions(+), 5066 deletions(-) delete mode 100644 contracts/swapper/osmosis/Cargo.toml delete mode 100644 contracts/swapper/osmosis/examples/schema.rs delete mode 100644 contracts/swapper/osmosis/src/contract.rs delete mode 100644 contracts/swapper/osmosis/src/helpers.rs delete mode 100644 contracts/swapper/osmosis/src/lib.rs delete mode 100644 contracts/swapper/osmosis/src/route.rs delete mode 100644 contracts/swapper/osmosis/tests/helpers.rs delete mode 100644 contracts/swapper/osmosis/tests/test_enumerate_routes.rs delete mode 100644 contracts/swapper/osmosis/tests/test_estimate.rs delete mode 100644 contracts/swapper/osmosis/tests/test_instantiate.rs delete mode 100644 contracts/swapper/osmosis/tests/test_set_route.rs delete mode 100644 contracts/swapper/osmosis/tests/test_swap.rs delete mode 100644 contracts/swapper/osmosis/tests/test_update_admin.rs delete mode 100644 contracts/v3-zapper/base/Cargo.toml delete mode 100644 contracts/v3-zapper/base/examples/schema.rs delete mode 100644 contracts/v3-zapper/base/src/contract.rs delete mode 100644 contracts/v3-zapper/base/src/error.rs delete mode 100644 contracts/v3-zapper/base/src/lib.rs delete mode 100644 contracts/v3-zapper/base/src/msg.rs delete mode 100644 contracts/v3-zapper/base/src/state.rs delete mode 100644 contracts/v3-zapper/base/src/traits.rs delete mode 100644 contracts/v3-zapper/base/src/utils.rs delete mode 100644 contracts/v3-zapper/osmosis/Cargo.toml delete mode 100644 contracts/v3-zapper/osmosis/src/contract.rs delete mode 100644 contracts/v3-zapper/osmosis/src/lib.rs delete mode 100644 contracts/v3-zapper/osmosis/src/position_manager.rs delete mode 100644 contracts/v3-zapper/osmosis/tests/helpers/assertions.rs delete mode 100644 contracts/v3-zapper/osmosis/tests/helpers/generator.rs delete mode 100644 contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs delete mode 100644 contracts/v3-zapper/osmosis/tests/helpers/mod.rs delete mode 100644 contracts/v3-zapper/osmosis/tests/test_add_position.rs delete mode 100644 contracts/v3-zapper/osmosis/tests/test_callback.rs delete mode 100644 contracts/v3-zapper/osmosis/tests/test_update_owner.rs delete mode 100644 schemas/mars-swapper-osmosis/mars-swapper-osmosis.json delete mode 100644 schemas/mars-v3-zapper-base/mars-v3-zapper-base.json delete mode 100644 scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client.ts delete mode 100644 scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.message-composer.ts delete mode 100644 scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.react-query.ts delete mode 100644 scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.types.ts delete mode 100644 scripts/types/generated/mars-swapper-osmosis/bundle.ts delete mode 100644 scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts delete mode 100644 scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts delete mode 100644 scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts delete mode 100644 scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts delete mode 100644 scripts/types/generated/mars-v3-zapper-base/bundle.ts diff --git a/Cargo.lock b/Cargo.lock index 2b20ba881..1907d1b48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,13 +76,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.71" +version = "0.1.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" +checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -141,7 +141,7 @@ version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "clap", @@ -197,6 +197,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + [[package]] name = "block-buffer" version = "0.9.0" @@ -297,7 +303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", - "bitflags", + "bitflags 1.3.2", "clap_lex", "indexmap", "strsim", @@ -841,9 +847,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "elliptic-curve" @@ -911,12 +917,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "ff" @@ -1015,7 +1018,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -1129,7 +1132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64", - "bitflags", + "bitflags 1.3.2", "bytes", "headers-core", "http", @@ -1310,26 +1313,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.2", - "libc", - "windows-sys", -] - [[package]] name = "itertools" version = "0.10.5" @@ -1421,9 +1404,9 @@ checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" [[package]] name = "log" @@ -1550,16 +1533,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "mars-osmosis" -version = "1.0.1" -source = "git+https://github.com/mars-protocol/red-bank?rev=00301d60c38af09d8eb7980355009e2f00c6f41f#00301d60c38af09d8eb7980355009e2f00c6f41f" -dependencies = [ - "cosmwasm-std", - "osmosis-std 0.14.0", - "serde", -] - [[package]] name = "mars-owner" version = "1.2.0" @@ -1690,7 +1663,7 @@ dependencies = [ "cw-paginate", "cw-storage-plus 1.1.0", "mars-owner", - "mars-rover", + "mars-red-bank-types", "schemars", "serde", "thiserror", @@ -1700,28 +1673,10 @@ dependencies = [ name = "mars-swapper-mock" version = "2.0.0" dependencies = [ + "anyhow", "cosmwasm-std", - "cw-storage-plus 1.1.0", - "mars-rover", - "thiserror", -] - -[[package]] -name = "mars-swapper-osmosis" -version = "2.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.1.0", - "cw2 1.1.0", - "mars-osmosis", - "mars-owner", - "mars-rover", - "mars-swapper-base", - "osmosis-std 0.16.1", - "osmosis-test-tube", - "schemars", - "thiserror", + "cw-multi-test", + "mars-red-bank-types", ] [[package]] @@ -1774,31 +1729,6 @@ dependencies = [ "osmosis-test-tube", ] -[[package]] -name = "mars-v3-zapper-base" -version = "2.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw2 1.1.0", - "mars-owner", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "mars-v3-zapper-osmosis" -version = "2.0.0" -dependencies = [ - "anyhow", - "cosmwasm-std", - "mars-owner", - "mars-v3-zapper-base", - "osmosis-std 0.16.1", - "osmosis-test-tube", -] - [[package]] name = "memchr" version = "2.5.0" @@ -1860,9 +1790,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", "libm", @@ -2066,7 +1996,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -2113,7 +2043,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" dependencies = [ "bit-set", - "bitflags", + "bitflags 1.3.2", "byteorder", "lazy_static", "num-traits", @@ -2189,9 +2119,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -2247,7 +2177,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -2345,13 +2275,12 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.37.23" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" dependencies = [ - "bitflags", + "bitflags 2.3.3", "errno", - "io-lifetimes", "libc", "linux-raw-sys", "windows-sys", @@ -2474,11 +2403,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -2487,9 +2416,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -2503,9 +2432,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.173" +version = "1.0.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91f70896d6720bc714a4a57d22fc91f1db634680e65c8efe13323f1fa38d53f" +checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b" dependencies = [ "serde_derive", ] @@ -2550,13 +2479,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.173" +version = "1.0.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6250dde8342e0232232be9ca3db7aa40aceb5a3e5dd9bddbc00d99a007cde49" +checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -2583,13 +2512,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d89a8107374290037607734c0b73a85db7ed80cae314b3c5791f192a496e731" +checksum = "e168eaaf71e8f9bd6037feb05190485708e019f4fd87d161b3c0a0d37daf85e5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -2744,9 +2673,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.26" +version = "2.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" dependencies = [ "proc-macro2", "quote", @@ -2755,11 +2684,10 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" dependencies = [ - "autocfg", "cfg-if", "fastrand", "redox_syscall", @@ -2896,22 +2824,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -2972,7 +2900,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -3158,7 +3086,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", "wasm-bindgen-shared", ] @@ -3180,7 +3108,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3345,5 +3273,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] diff --git a/Cargo.toml b/Cargo.toml index 694e54cfc..f486c412b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ members = [ "contracts/credit-manager", "contracts/swapper/*", "contracts/v2-zapper/*", - "contracts/v3-zapper/*", "contracts/health", # mock contracts @@ -50,16 +49,16 @@ cw-multi-test = "0.16.5" cw-paginate = "0.2.1" cw-utils = "1.0.1" cw-storage-plus = "1.1.0" -cw-vault-standard = { version = "0.3.0", features = ["lockup", "force-unlock"] } +cw-vault-standard = { version = "0.3.1", features = ["lockup", "force-unlock"] } itertools = "0.11.0" -osmosis-std = "0.16.0" -osmosis-test-tube = "16.0.0" +osmosis-std = "0.16.1" +osmosis-test-tube = "16.0.1" proptest = "1.2.0" schemars = "0.8.12" -serde = { version = "1.0.171", default-features = false, features = ["derive"] } +serde = { version = "1.0.175", default-features = false, features = ["derive"] } serde_json = "1.0.103" serde-wasm-bindgen = "0.5.0" -thiserror = "1.0.43" +thiserror = "1.0.44" wasm-bindgen = "0.2.87" # mars packages @@ -76,7 +75,6 @@ mars-params = { version = "1.0.7", features = ["library"] } mars-rover-health = { version = "2.0.0", path = "./contracts/health", features = ["library"] } mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base", features = ["library"] } mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base", features = ["library"] } -mars-v3-zapper-base = { version = "2.0.0", path = "./contracts/v3-zapper/base", features = ["library"] } # mocks mars-mock-credit-manager = { version = "2.0.0", path = "./contracts/mock-credit-manager", features = ["library"] } diff --git a/contracts/swapper/base/Cargo.toml b/contracts/swapper/base/Cargo.toml index 28ba3abfd..42c5d3221 100644 --- a/contracts/swapper/base/Cargo.toml +++ b/contracts/swapper/base/Cargo.toml @@ -12,6 +12,9 @@ keywords = { workspace = true } [lib] crate-type = ["cdylib", "rlib"] +[profile.release] +overflow-checks = true + [features] # for quicker tests, cargo test --lib # for more explicit tests, cargo test --features=backtraces @@ -19,12 +22,12 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-paginate = { workspace = true } -cw-storage-plus = { workspace = true } -mars-rover = { workspace = true } -mars-owner = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-paginate = { workspace = true } +cw-storage-plus = { workspace = true } +mars-owner = { workspace = true } +mars-red-bank-types = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/swapper/base/examples/schema.rs b/contracts/swapper/base/examples/schema.rs index 2984d9d30..bd347f5f1 100644 --- a/contracts/swapper/base/examples/schema.rs +++ b/contracts/swapper/base/examples/schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; use cosmwasm_std::Empty; -use mars_rover::adapters::swap::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_red_bank_types::swapper::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/swapper/base/src/contract.rs b/contracts/swapper/base/src/contract.rs index ec8371d31..21408efc6 100644 --- a/contracts/swapper/base/src/contract.rs +++ b/contracts/swapper/base/src/contract.rs @@ -7,15 +7,12 @@ use cosmwasm_std::{ use cw_paginate::paginate_map; use cw_storage_plus::{Bound, Map}; use mars_owner::{Owner, OwnerInit::SetInitialOwner, OwnerUpdate}; -use mars_rover::{ - adapters::swap::{ - EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg, RouteResponse, - RoutesResponse, - }, - error::ContractError as RoverError, +use mars_red_bank_types::swapper::{ + EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg, RouteResponse, + RoutesResponse, }; -use crate::{ContractResult, Route}; +use crate::{ContractError, ContractResult, Route}; pub struct SwapBase<'a, Q, M, R> where @@ -124,7 +121,7 @@ where Ok(RouteResponse { denom_in: denom_in.clone(), denom_out: denom_out.clone(), - route: self.routes.load(deps.storage, (denom_in, denom_out))?, + route: self.get_route(deps, &denom_in, &denom_out)?, }) } @@ -151,7 +148,7 @@ where coin_in: Coin, denom_out: String, ) -> ContractResult { - let route = self.routes.load(deps.storage, (coin_in.denom.clone(), denom_out))?; + let route = self.get_route(deps, &coin_in.denom, &denom_out)?; route.estimate_exact_in_swap(&deps.querier, &env, &coin_in) } @@ -166,7 +163,11 @@ where ) -> ContractResult> { let swap_msg = self .routes - .load(deps.storage, (coin_in.denom.clone(), denom_out.clone()))? + .load(deps.storage, (coin_in.denom.clone(), denom_out.clone())) + .map_err(|_| ContractError::NoRoute { + from: coin_in.denom.clone(), + to: denom_out.clone(), + })? .build_exact_in_swap_msg(&deps.querier, &env, &coin_in, slippage)?; // Check balance of result of swapper and send back result to sender @@ -201,11 +202,10 @@ where ) -> ContractResult> { // Internal callback only if info.sender != env.contract.address { - return Err(RoverError::Unauthorized { + return Err(ContractError::Unauthorized { user: info.sender.to_string(), action: "transfer result".to_string(), - } - .into()); + }); }; let denom_in_balance = @@ -245,6 +245,15 @@ where .add_attribute("route", route.to_string())) } + fn get_route(&self, deps: Deps, denom_in: &str, denom_out: &str) -> ContractResult { + self.routes.load(deps.storage, (denom_in.to_string(), denom_out.to_string())).map_err( + |_| ContractError::NoRoute { + from: denom_in.to_string(), + to: denom_out.to_string(), + }, + ) + } + fn update_owner( &self, deps: DepsMut, diff --git a/contracts/swapper/base/src/error.rs b/contracts/swapper/base/src/error.rs index 2411d51b4..f1e8e59ba 100644 --- a/contracts/swapper/base/src/error.rs +++ b/contracts/swapper/base/src/error.rs @@ -1,6 +1,8 @@ -use cosmwasm_std::{CheckedMultiplyRatioError, DecimalRangeExceeded, OverflowError, StdError}; +use cosmwasm_std::{ + CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError, + DecimalRangeExceeded, OverflowError, StdError, +}; use mars_owner::OwnerError; -use mars_rover::error::ContractError as RoverError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -22,17 +24,32 @@ pub enum ContractError { #[error("{0}")] CheckedMultiplyRatio(#[from] CheckedMultiplyRatioError), + #[error("{0}")] + CheckedFromRatioError(#[from] CheckedFromRatioError), + + #[error("{0}")] + CheckedMultiplyFractionError(#[from] CheckedMultiplyFractionError), + #[error("{denom_a:?}-{denom_b:?} is not an available pool")] PoolNotFound { denom_a: String, denom_b: String, }, - #[error("{0}")] - Rover(#[from] RoverError), - #[error("{0}")] Std(#[from] StdError), + + #[error("{user:?} is not authorized to {action:?}")] + Unauthorized { + user: String, + action: String, + }, + + #[error("No route found from {from} to {to}")] + NoRoute { + from: String, + to: String, + }, } pub type ContractResult = Result; diff --git a/contracts/swapper/base/src/traits.rs b/contracts/swapper/base/src/traits.rs index ba2afdef8..a607d1e5c 100644 --- a/contracts/swapper/base/src/traits.rs +++ b/contracts/swapper/base/src/traits.rs @@ -1,7 +1,7 @@ use std::fmt::{Debug, Display}; use cosmwasm_std::{Coin, CosmosMsg, CustomMsg, CustomQuery, Decimal, Env, QuerierWrapper}; -use mars_rover::adapters::swap::EstimateExactInSwapResponse; +use mars_red_bank_types::swapper::EstimateExactInSwapResponse; use schemars::JsonSchema; use serde::{de::DeserializeOwned, Serialize}; diff --git a/contracts/swapper/mock/Cargo.toml b/contracts/swapper/mock/Cargo.toml index 7892f6183..924918749 100644 --- a/contracts/swapper/mock/Cargo.toml +++ b/contracts/swapper/mock/Cargo.toml @@ -19,7 +19,9 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -mars-rover = { workspace = true } -thiserror = { workspace = true } +cosmwasm-std = { workspace = true } +mars-red-bank-types = { workspace = true } + +[dev-dependencies] +anyhow = { workspace = true } +cw-multi-test = { workspace = true } diff --git a/contracts/swapper/mock/src/contract.rs b/contracts/swapper/mock/src/contract.rs index 96f48f68b..cffd7fcfe 100644 --- a/contracts/swapper/mock/src/contract.rs +++ b/contracts/swapper/mock/src/contract.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{ coins, to_binary, BankMsg, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdError, StdResult, Uint128, }; -use mars_rover::adapters::swap::{ +use mars_red_bank_types::swapper::{ EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, QueryMsg, }; diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml deleted file mode 100644 index 7d209e8a3..000000000 --- a/contracts/swapper/osmosis/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "mars-swapper-osmosis" -version = { workspace = true } -authors = { workspace = true } -license = { workspace = true } -edition = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -documentation = { workspace = true } -keywords = { workspace = true } - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -library = [] - -[dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -cw-storage-plus = { workspace = true } -mars-osmosis = { workspace = true } -mars-owner = { workspace = true } -mars-swapper-base = { workspace = true } -mars-rover = { workspace = true } -osmosis-std = { workspace = true } -schemars = { workspace = true } -thiserror = { workspace = true } - -[dev-dependencies] -osmosis-test-tube = { workspace = true } diff --git a/contracts/swapper/osmosis/examples/schema.rs b/contracts/swapper/osmosis/examples/schema.rs deleted file mode 100644 index 136a9f4a3..000000000 --- a/contracts/swapper/osmosis/examples/schema.rs +++ /dev/null @@ -1,11 +0,0 @@ -use cosmwasm_schema::write_api; -use mars_rover::adapters::swap::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use mars_swapper_osmosis::route::OsmosisRoute; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - query: QueryMsg, - execute: ExecuteMsg, - } -} diff --git a/contracts/swapper/osmosis/src/contract.rs b/contracts/swapper/osmosis/src/contract.rs deleted file mode 100644 index c97f9572d..000000000 --- a/contracts/swapper/osmosis/src/contract.rs +++ /dev/null @@ -1,38 +0,0 @@ -use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response}; -use cw2::set_contract_version; -use mars_rover::adapters::swap::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use mars_swapper_base::{ContractResult, SwapBase}; - -use crate::route::OsmosisRoute; - -/// The Osmosis swapper contract inherits logic from the base swapper contract -pub type OsmosisSwap<'a> = SwapBase<'a, Empty, Empty, OsmosisRoute>; - -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> ContractResult { - set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; - OsmosisSwap::default().instantiate(deps, msg) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> ContractResult { - OsmosisSwap::default().execute(deps, env, info, msg) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { - OsmosisSwap::default().query(deps, env, msg) -} diff --git a/contracts/swapper/osmosis/src/helpers.rs b/contracts/swapper/osmosis/src/helpers.rs deleted file mode 100644 index 5f09f658e..000000000 --- a/contracts/swapper/osmosis/src/helpers.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::{collections::HashSet, hash::Hash}; - -use cosmwasm_std::{Decimal, Uint128}; - -/// Build a hashset from array data -pub(crate) fn hashset(data: &[T]) -> HashSet { - data.iter().cloned().collect() -} - -pub trait IntoUint128 { - fn uint128(&self) -> Uint128; -} - -impl IntoUint128 for Decimal { - fn uint128(&self) -> Uint128 { - *self * Uint128::new(1) - } -} diff --git a/contracts/swapper/osmosis/src/lib.rs b/contracts/swapper/osmosis/src/lib.rs deleted file mode 100644 index fdbe29de5..000000000 --- a/contracts/swapper/osmosis/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod contract; -pub mod helpers; -pub mod route; diff --git a/contracts/swapper/osmosis/src/route.rs b/contracts/swapper/osmosis/src/route.rs deleted file mode 100644 index 4d9cf160a..000000000 --- a/contracts/swapper/osmosis/src/route.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::fmt; - -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{ - BlockInfo, Coin, CosmosMsg, Decimal, Empty, Env, Fraction, QuerierWrapper, Uint128, -}; -use mars_osmosis::helpers::{has_denom, query_arithmetic_twap_price, query_pool}; -use mars_rover::adapters::swap::EstimateExactInSwapResponse; -use mars_swapper_base::{ContractError, ContractResult, Route}; -use osmosis_std::types::osmosis::{ - gamm::v1beta1::MsgSwapExactAmountIn, poolmanager::v1beta1::SwapAmountInRoute, -}; - -use crate::helpers::hashset; - -/// 10 min in seconds (Risk Team recommendation) -const TWAP_WINDOW_SIZE_SECONDS: u64 = 600u64; - -#[cw_serde] -pub struct OsmosisRoute(pub Vec); - -impl fmt::Display for OsmosisRoute { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = self - .0 - .iter() - .map(|step| format!("{}:{}", step.pool_id, step.token_out_denom)) - .collect::>() - .join("|"); - write!(f, "{s}") - } -} - -impl Route for OsmosisRoute { - // Perform basic validation of the swap steps - fn validate( - &self, - querier: &QuerierWrapper, - denom_in: &str, - denom_out: &str, - ) -> ContractResult<()> { - let steps = &self.0; - - // there must be at least one step - if steps.is_empty() { - return Err(ContractError::InvalidRoute { - reason: "the route must contain at least one step".to_string(), - }); - } - - // for each step: - // - the pool must contain the input and output denoms - // - the output denom must not be the same as the input denom of a previous step (i.e. the route must not contain a loop) - let mut prev_denom_out = denom_in; - let mut seen_denoms = hashset(&[denom_in]); - for (i, step) in steps.iter().enumerate() { - let pool = query_pool(querier, step.pool_id)?; - - if !has_denom(prev_denom_out, &pool.pool_assets) { - return Err(ContractError::InvalidRoute { - reason: format!( - "step {}: pool {} does not contain input denom {}", - i + 1, - step.pool_id, - prev_denom_out - ), - }); - } - - if !has_denom(&step.token_out_denom, &pool.pool_assets) { - return Err(ContractError::InvalidRoute { - reason: format!( - "step {}: pool {} does not contain output denom {}", - i + 1, - step.pool_id, - &step.token_out_denom - ), - }); - } - - if seen_denoms.contains(step.token_out_denom.as_str()) { - return Err(ContractError::InvalidRoute { - reason: format!( - "route contains a loop: denom {} seen twice", - step.token_out_denom - ), - }); - } - - prev_denom_out = &step.token_out_denom; - seen_denoms.insert(&step.token_out_denom); - } - - // the route's final output denom must match the desired output denom - if prev_denom_out != denom_out { - return Err(ContractError::InvalidRoute { - reason: format!( - "the route's output denom {prev_denom_out} does not match the desired output {denom_out}", - ), - }); - } - - Ok(()) - } - - /// Build a CosmosMsg that swaps given an input denom and amount - fn build_exact_in_swap_msg( - &self, - querier: &QuerierWrapper, - env: &Env, - coin_in: &Coin, - slippage: Decimal, - ) -> ContractResult { - let steps = &self.0; - - steps.first().ok_or(ContractError::InvalidRoute { - reason: "the route must contain at least one step".to_string(), - })?; - - let out_amount = query_out_amount(querier, &env.block, coin_in, steps)?; - let min_out_amount = (Decimal::one() - slippage) * out_amount; - - let swap_msg: CosmosMsg = MsgSwapExactAmountIn { - sender: env.contract.address.to_string(), - routes: steps.to_vec(), - token_in: Some(osmosis_std::types::cosmos::base::v1beta1::Coin { - denom: coin_in.denom.clone(), - amount: coin_in.amount.to_string(), - }), - token_out_min_amount: min_out_amount.to_string(), - } - .into(); - Ok(swap_msg) - } - - fn estimate_exact_in_swap( - &self, - querier: &QuerierWrapper, - env: &Env, - coin_in: &Coin, - ) -> ContractResult { - let out_amount = query_out_amount(querier, &env.block, coin_in, &self.0)?; - Ok(EstimateExactInSwapResponse { - amount: out_amount, - }) - } -} - -/// Query how much amount of denom_out we get for denom_in. -/// -/// Example calculation: -/// If we want to swap atom to usdc and configured routes are [pool_1 (atom/osmo), pool_69 (osmo/usdc)] (no direct pool of atom/usdc): -/// 1) query pool_1 to get price for atom/osmo -/// 2) query pool_69 to get price for osmo/usdc -/// 3) atom/usdc = (price for atom/osmo) * (price for osmo/usdc) -/// 4) usdc_out_amount = (atom amount) * (price for atom/usdc) -fn query_out_amount( - querier: &QuerierWrapper, - block: &BlockInfo, - coin_in: &Coin, - steps: &[SwapAmountInRoute], -) -> ContractResult { - let start_time = block.time.seconds() - TWAP_WINDOW_SIZE_SECONDS; - - let mut price = Decimal::one(); - let mut denom_in = coin_in.denom.clone(); - for step in steps { - let step_price = query_arithmetic_twap_price( - querier, - step.pool_id, - &denom_in, - &step.token_out_denom, - start_time, - )?; - price = price.checked_mul(step_price)?; - denom_in = step.token_out_denom.clone(); - } - - let out_amount = - coin_in.amount.checked_multiply_ratio(price.numerator(), price.denominator())?; - Ok(out_amount) -} diff --git a/contracts/swapper/osmosis/tests/helpers.rs b/contracts/swapper/osmosis/tests/helpers.rs deleted file mode 100644 index 3065e4c54..000000000 --- a/contracts/swapper/osmosis/tests/helpers.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::{fmt::Display, str::FromStr}; - -use cosmwasm_std::{Coin, Decimal, Uint128}; -use mars_rover::adapters::swap::InstantiateMsg; -use osmosis_std::types::{ - cosmos::bank::v1beta1::QueryBalanceRequest, - osmosis::{ - gamm::v1beta1::{MsgSwapExactAmountIn, MsgSwapExactAmountInResponse}, - poolmanager::v1beta1::SwapAmountInRoute, - }, -}; -use osmosis_test_tube::{ - Account, Bank, ExecuteResponse, Gamm, OsmosisTestApp, Runner, RunnerError, SigningAccount, Wasm, -}; - -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); - -pub fn wasm_file() -> String { - let artifacts_dir = - std::env::var("ARTIFACTS_DIR_PATH").unwrap_or_else(|_| "artifacts".to_string()); - let snaked_name = CONTRACT_NAME.replace('-', "_"); - format!("../../../{artifacts_dir}/{snaked_name}.wasm") -} - -pub fn instantiate_contract(wasm: &Wasm, owner: &SigningAccount) -> String { - let wasm_byte_code = std::fs::read(wasm_file()).unwrap(); - let code_id = wasm.store_code(&wasm_byte_code, None, owner).unwrap().data.code_id; - - wasm.instantiate( - code_id, - &InstantiateMsg { - owner: owner.address(), - }, - None, - Some("swapper-osmosis-contract"), - &[], - owner, - ) - .unwrap() - .data - .address -} - -/// Every execution creates new block and block timestamp will +5 secs from last block -/// (see https://github.com/osmosis-labs/osmosis-rust/issues/53#issuecomment-1311451418). -/// -/// We need to swap n times to pass TWAP_WINDOW_SIZE_SECONDS (10 min). Every swap moves block 5 sec so -/// n = TWAP_WINDOW_SIZE_SECONDS / 5 sec = 600 sec / 5 sec = 120. -/// We need to swap at least 120 times to create historical index for TWAP. -pub fn swap_to_create_twap_records( - app: &OsmosisTestApp, - signer: &SigningAccount, - pool_id: u64, - coin_in: Coin, - denom_out: &str, -) { - swap_n_times(app, signer, pool_id, coin_in, denom_out, 120u64); -} - -pub fn swap_n_times( - app: &OsmosisTestApp, - signer: &SigningAccount, - pool_id: u64, - coin_in: Coin, - denom_out: &str, - n: u64, -) { - for _ in 0..n { - swap(app, signer, pool_id, coin_in.clone(), denom_out); - } -} - -fn swap( - app: &OsmosisTestApp, - signer: &SigningAccount, - pool_id: u64, - coin_in: Coin, - denom_out: &str, -) -> ExecuteResponse { - app.execute::<_, MsgSwapExactAmountInResponse>( - MsgSwapExactAmountIn { - sender: signer.address(), - routes: vec![SwapAmountInRoute { - pool_id, - token_out_denom: denom_out.to_string(), - }], - token_in: Some(coin_in.into()), - token_out_min_amount: "1".to_string(), - }, - MsgSwapExactAmountIn::TYPE_URL, - signer, - ) - .unwrap() -} - -/// Query price for 1 denom from pool_id (quoted in second denom from the pool). -/// -/// Example: -/// pool consists of: 250 uosmo and 100 uatom -/// query price for uatom so 1 uatom = 2.5 uosmo -pub fn query_price_from_pool(gamm: &Gamm, pool_id: u64, denom: &str) -> Decimal { - let pool_assets = &gamm.query_pool(pool_id).unwrap().pool_assets; - let coin_1 = pool_assets[0].token.as_ref().unwrap(); - let coin_2 = &pool_assets[1].token.as_ref().unwrap(); - let coin_1_amt = Uint128::from_str(&coin_1.amount).unwrap(); - let coin_2_amt = Uint128::from_str(&coin_2.amount).unwrap(); - - if coin_1.denom == denom { - Decimal::from_ratio(coin_2_amt, coin_1_amt) - } else if coin_2.denom == denom { - Decimal::from_ratio(coin_1_amt, coin_2_amt) - } else { - panic!("{denom} not found in the pool {pool_id}") - } -} - -pub fn query_balance(bank: &Bank, addr: &str, denom: &str) -> u128 { - bank.query_balance(&QueryBalanceRequest { - address: addr.to_string(), - denom: denom.to_string(), - }) - .unwrap() - .balance - .map(|c| u128::from_str(&c.amount).unwrap()) - .unwrap_or(0) -} - -pub fn assert_err(actual: RunnerError, expected: impl Display) { - match actual { - RunnerError::ExecuteError { - msg, - } => { - assert!(msg.contains(&format!("{expected}"))) - } - RunnerError::QueryError { - msg, - } => { - assert!(msg.contains(&format!("{expected}"))) - } - _ => panic!("Unhandled error"), - } -} diff --git a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs b/contracts/swapper/osmosis/tests/test_enumerate_routes.rs deleted file mode 100644 index 77f2ea322..000000000 --- a/contracts/swapper/osmosis/tests/test_enumerate_routes.rs +++ /dev/null @@ -1,185 +0,0 @@ -extern crate core; - -use std::collections::HashMap; - -use cosmwasm_std::coin; -use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; -use mars_swapper_osmosis::route::OsmosisRoute; -use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; -use osmosis_test_tube::{Gamm, Module, OsmosisTestApp, SigningAccount, Wasm}; - -use crate::helpers::instantiate_contract; - -pub mod helpers; - -#[test] -fn enumerating_routes() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uatom"), - coin(1_000_000_000_000, "uosmo"), - coin(1_000_000_000_000, "umars"), - coin(1_000_000_000_000, "uusdc"), - ]) - .unwrap(); - - let contract_addr = instantiate_contract(&wasm, &signer); - - let routes = create_pools_and_routes(&app, &signer); - - wasm.execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "uatom".to_string(), - denom_out: "umars".to_string(), - route: routes.get(&("uatom", "umars")).unwrap().clone(), - }, - &[], - &signer, - ) - .unwrap(); - - wasm.execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "uatom".to_string(), - denom_out: "uusdc".to_string(), - route: routes.get(&("uatom", "uusdc")).unwrap().clone(), - }, - &[], - &signer, - ) - .unwrap(); - - wasm.execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "uosmo".to_string(), - denom_out: "umars".to_string(), - route: routes.get(&("uosmo", "umars")).unwrap().clone(), - }, - &[], - &signer, - ) - .unwrap(); - - // NOTE: the response is ordered alphabetically - let expected = vec![ - RouteResponse { - denom_in: "uatom".to_string(), - denom_out: "umars".to_string(), - route: routes.get(&("uatom", "umars")).unwrap().clone(), - }, - RouteResponse { - denom_in: "uatom".to_string(), - denom_out: "uusdc".to_string(), - route: routes.get(&("uatom", "uusdc")).unwrap().clone(), - }, - RouteResponse { - denom_in: "uosmo".to_string(), - denom_out: "umars".to_string(), - route: routes.get(&("uosmo", "umars")).unwrap().clone(), - }, - ]; - - let res: Vec> = wasm - .query( - &contract_addr, - &QueryMsg::Routes { - start_after: None, - limit: None, - }, - ) - .unwrap(); - assert_eq!(res, expected); - - let res: Vec> = wasm - .query( - &contract_addr, - &QueryMsg::Routes { - start_after: None, - limit: Some(1), - }, - ) - .unwrap(); - assert_eq!(res, expected[..1]); - - let res: Vec> = wasm - .query( - &contract_addr, - &QueryMsg::Routes { - start_after: Some(("uatom".to_string(), "uosmo".to_string())), - limit: None, - }, - ) - .unwrap(); - assert_eq!(res, expected[1..]); -} - -fn create_pools_and_routes( - app: &OsmosisTestApp, - signer: &SigningAccount, -) -> HashMap<(&'static str, &'static str), OsmosisRoute> { - let gamm = Gamm::new(app); - - let pool_atom_osmo = gamm - .create_basic_pool(&[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], signer) - .unwrap() - .data - .pool_id; - let pool_osmo_mars = gamm - .create_basic_pool(&[coin(100_000, "uosmo"), coin(1_000_000, "umars")], signer) - .unwrap() - .data - .pool_id; - let pool_osmo_usdc = gamm - .create_basic_pool(&[coin(100_000, "uosmo"), coin(1_000_000, "uusdc")], signer) - .unwrap() - .data - .pool_id; - - let mut map = HashMap::new(); - - // uosmo -> umars - map.insert( - ("uosmo", "umars"), - OsmosisRoute(vec![SwapAmountInRoute { - pool_id: pool_osmo_mars, - token_out_denom: "umars".to_string(), - }]), - ); - - // uatom -> uosmo -> umars - map.insert( - ("uatom", "umars"), - OsmosisRoute(vec![ - SwapAmountInRoute { - pool_id: pool_atom_osmo, - token_out_denom: "uosmo".to_string(), - }, - SwapAmountInRoute { - pool_id: pool_osmo_mars, - token_out_denom: "umars".to_string(), - }, - ]), - ); - - // uatom -> uosmo -> uusdc - map.insert( - ("uatom", "uusdc"), - OsmosisRoute(vec![ - SwapAmountInRoute { - pool_id: pool_atom_osmo, - token_out_denom: "uosmo".to_string(), - }, - SwapAmountInRoute { - pool_id: pool_osmo_usdc, - token_out_denom: "uusdc".to_string(), - }, - ]), - ); - - map -} diff --git a/contracts/swapper/osmosis/tests/test_estimate.rs b/contracts/swapper/osmosis/tests/test_estimate.rs deleted file mode 100644 index fc23abb23..000000000 --- a/contracts/swapper/osmosis/tests/test_estimate.rs +++ /dev/null @@ -1,183 +0,0 @@ -use cosmwasm_std::{coin, Uint128}; -use mars_rover::adapters::swap::{EstimateExactInSwapResponse, ExecuteMsg, QueryMsg}; -use mars_swapper_osmosis::route::OsmosisRoute; -use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; -use osmosis_test_tube::{Gamm, Module, OsmosisTestApp, RunnerResult, Wasm}; - -use crate::helpers::{ - assert_err, instantiate_contract, query_price_from_pool, swap_to_create_twap_records, -}; - -pub mod helpers; - -#[test] -fn error_on_route_not_found() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - let owner = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); - - let contract_addr = instantiate_contract(&wasm, &owner); - - let res: RunnerResult = wasm.query( - &contract_addr, - &QueryMsg::EstimateExactInSwap { - coin_in: coin(1000, "jake"), - denom_out: "mars".to_string(), - }, - ); - - assert_err(res.unwrap_err(), "swapper_osmosis::route::OsmosisRoute not found"); -} - -#[test] -fn estimate_swap_one_step() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let signer = app - .init_account(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")]) - .unwrap(); - - let contract_addr = instantiate_contract(&wasm, &signer); - - let gamm = Gamm::new(&app); - let pool_atom_osmo = gamm - .create_basic_pool(&[coin(1_500_000, "uatom"), coin(6_000_000, "uosmo")], &signer) - .unwrap() - .data - .pool_id; - - swap_to_create_twap_records(&app, &signer, pool_atom_osmo, coin(10u128, "uatom"), "uosmo"); - - wasm.execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "uosmo".to_string(), - denom_out: "uatom".to_string(), - route: OsmosisRoute(vec![SwapAmountInRoute { - pool_id: pool_atom_osmo, - token_out_denom: "uatom".to_string(), - }]), - }, - &[], - &signer, - ) - .unwrap(); - - let coin_in_amount = Uint128::from(1000u128); - let uosmo_price = query_price_from_pool(&gamm, pool_atom_osmo, "uosmo"); - let expected_output = coin_in_amount * uosmo_price; - - let res: EstimateExactInSwapResponse = wasm - .query( - &contract_addr, - &QueryMsg::EstimateExactInSwap { - coin_in: coin(coin_in_amount.u128(), "uosmo"), - denom_out: "uatom".to_string(), - }, - ) - .unwrap(); - assert_eq!(res.amount, expected_output); -} - -#[test] -fn estimate_swap_multi_step() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uatom"), - coin(1_000_000_000_000, "uosmo"), - coin(1_000_000_000_000, "umars"), - coin(1_000_000_000_000, "uusdc"), - ]) - .unwrap(); - - let contract_addr = instantiate_contract(&wasm, &signer); - - let gamm = Gamm::new(&app); - let pool_atom_osmo = gamm - .create_basic_pool(&[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], &signer) - .unwrap() - .data - .pool_id; - let pool_osmo_mars = gamm - .create_basic_pool(&[coin(100_000, "uosmo"), coin(1_000_000, "umars")], &signer) - .unwrap() - .data - .pool_id; - let pool_osmo_usdc = gamm - .create_basic_pool(&[coin(100_000, "uosmo"), coin(1_000_000, "uusdc")], &signer) - .unwrap() - .data - .pool_id; - - swap_to_create_twap_records(&app, &signer, pool_atom_osmo, coin(4u128, "uosmo"), "uatom"); - - wasm.execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "uatom".to_string(), - denom_out: "umars".to_string(), - route: OsmosisRoute(vec![ - SwapAmountInRoute { - pool_id: pool_atom_osmo, - token_out_denom: "uosmo".to_string(), - }, - SwapAmountInRoute { - pool_id: pool_osmo_mars, - token_out_denom: "umars".to_string(), - }, - ]), - }, - &[], - &signer, - ) - .unwrap(); - - wasm.execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "uatom".to_string(), - denom_out: "uusdc".to_string(), - route: OsmosisRoute(vec![ - SwapAmountInRoute { - pool_id: pool_atom_osmo, - token_out_denom: "uosmo".to_string(), - }, - SwapAmountInRoute { - pool_id: pool_osmo_usdc, - token_out_denom: "uusdc".to_string(), - }, - ]), - }, - &[], - &signer, - ) - .unwrap(); - - let coin_in_amount = Uint128::from(1000u128); - let uatom_price = query_price_from_pool(&gamm, pool_atom_osmo, "uatom"); - let uosmo_price = query_price_from_pool(&gamm, pool_osmo_usdc, "uosmo"); - let expected_output = coin_in_amount * uatom_price * uosmo_price; - - // atom/usdc = (price for atom/osmo) * (price for osmo/usdc) - // usdc_out_amount = (atom amount) * (price for atom/usdc) - // - // 1 osmo = 4 atom => atom/osmo = 0.25 - // 1 osmo = 10 usdc => osmo/usdc = 10 - // - // atom/usdc = 0.25 * 10 = 2.5 - // usdc_out_amount = 1000 * 2.5 = 2500 - let res: EstimateExactInSwapResponse = wasm - .query( - &contract_addr, - &QueryMsg::EstimateExactInSwap { - coin_in: coin(coin_in_amount.u128(), "uatom"), - denom_out: "uusdc".to_string(), - }, - ) - .unwrap(); - assert_eq!(res.amount, expected_output); -} diff --git a/contracts/swapper/osmosis/tests/test_instantiate.rs b/contracts/swapper/osmosis/tests/test_instantiate.rs deleted file mode 100644 index 8a5872859..000000000 --- a/contracts/swapper/osmosis/tests/test_instantiate.rs +++ /dev/null @@ -1,46 +0,0 @@ -use cosmwasm_std::coin; -use mars_owner::OwnerResponse; -use mars_rover::adapters::swap::{InstantiateMsg, QueryMsg}; -use osmosis_test_tube::{Account, Module, OsmosisTestApp, Wasm}; - -use crate::helpers::{instantiate_contract, wasm_file}; - -pub mod helpers; - -#[test] -fn owner_set_on_instantiate() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); - - let contract_addr = instantiate_contract(&wasm, &signer); - - let res: OwnerResponse = wasm.query(&contract_addr, &QueryMsg::Owner {}).unwrap(); - assert_eq!(res.owner, Some(signer.address())); -} - -#[test] -fn raises_on_invalid_owner_addr() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); - - let wasm_byte_code = std::fs::read(wasm_file()).unwrap(); - let code_id = wasm.store_code(&wasm_byte_code, None, &signer).unwrap().data.code_id; - - let owner = "%%%INVALID%%%"; - let res = wasm.instantiate( - code_id, - &InstantiateMsg { - owner: owner.to_string(), - }, - None, - Some("swapper-osmosis-contract"), - &[], - &signer, - ); - - if res.is_ok() { - panic!("Should have thrown an error"); - } -} diff --git a/contracts/swapper/osmosis/tests/test_set_route.rs b/contracts/swapper/osmosis/tests/test_set_route.rs deleted file mode 100644 index 087584c3b..000000000 --- a/contracts/swapper/osmosis/tests/test_set_route.rs +++ /dev/null @@ -1,373 +0,0 @@ -use cosmwasm_std::{coin, StdError::GenericErr}; -use mars_owner::OwnerError; -use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg, RouteResponse}; -use mars_swapper_base::ContractError; -use mars_swapper_osmosis::route::OsmosisRoute; -use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; -use osmosis_test_tube::{Gamm, Module, OsmosisTestApp, Wasm}; - -use crate::helpers::{assert_err, instantiate_contract}; - -pub mod helpers; - -#[test] -fn only_owner_can_set_routes() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); - let owner = &accs[0]; - let bad_guy = &accs[1]; - - let contract_addr = instantiate_contract(&wasm, owner); - - let res_err = wasm - .execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "mars".to_string(), - denom_out: "weth".to_string(), - route: OsmosisRoute(vec![ - SwapAmountInRoute { - pool_id: 1, - token_out_denom: "osmo".to_string(), - }, - SwapAmountInRoute { - pool_id: 2, - token_out_denom: "weth".to_string(), - }, - ]), - }, - &[], - bad_guy, - ) - .unwrap_err(); - - assert_err(res_err, OwnerError::NotOwner {}); -} - -#[test] -fn must_pass_at_least_one_step() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); - - let contract_addr = instantiate_contract(&wasm, &signer); - - let res_err = wasm - .execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "mars".to_string(), - denom_out: "weth".to_string(), - route: OsmosisRoute(vec![]), - }, - &[], - &signer, - ) - .unwrap_err(); - - assert_err( - res_err, - ContractError::InvalidRoute { - reason: "the route must contain at least one step".to_string(), - }, - ); -} - -#[test] -fn must_be_available_in_osmosis() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); - - let contract_addr = instantiate_contract(&wasm, &signer); - - let res_err = wasm - .execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "mars".to_string(), - denom_out: "weth".to_string(), - route: OsmosisRoute(vec![SwapAmountInRoute { - pool_id: 1, - token_out_denom: "osmo".to_string(), - }]), - }, - &[], - &signer, - ) - .unwrap_err(); - - assert_err( - res_err, - ContractError::Std(GenericErr { - msg: "Querier contract error".to_string(), - }), - ); -} - -#[test] -fn step_does_not_contain_input_denom() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let signer = app - .init_account(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")]) - .unwrap(); - - let contract_addr = instantiate_contract(&wasm, &signer); - - let gamm = Gamm::new(&app); - let pool_atom_osmo = gamm - .create_basic_pool(&[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], &signer) - .unwrap() - .data - .pool_id; - - let res_err = wasm - .execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "umars".to_string(), - denom_out: "uweth".to_string(), - route: OsmosisRoute(vec![SwapAmountInRoute { - pool_id: pool_atom_osmo, - token_out_denom: "uosmo".to_string(), - }]), - }, - &[], - &signer, - ) - .unwrap_err(); - - assert_err( - res_err, - ContractError::InvalidRoute { - reason: format!("step 1: pool {pool_atom_osmo} does not contain input denom umars",), - }, - ); -} - -#[test] -fn step_does_not_contain_output_denom() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let signer = app - .init_account(&[coin(1_000_000_000_000, "umars"), coin(1_000_000_000_000, "uosmo")]) - .unwrap(); - - let contract_addr = instantiate_contract(&wasm, &signer); - - let gamm = Gamm::new(&app); - let pool_mars_osmo = gamm - .create_basic_pool(&[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], &signer) - .unwrap() - .data - .pool_id; - - let res_err = wasm - .execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "umars".to_string(), - denom_out: "uweth".to_string(), - route: OsmosisRoute(vec![SwapAmountInRoute { - pool_id: pool_mars_osmo, - token_out_denom: "uweth".to_string(), - }]), - }, - &[], - &signer, - ) - .unwrap_err(); - - assert_err( - res_err, - ContractError::InvalidRoute { - reason: format!("step 1: pool {pool_mars_osmo} does not contain output denom uweth"), - }, - ); -} - -#[test] -fn steps_do_not_loop() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uatom"), - coin(1_000_000_000_000, "uosmo"), - coin(1_000_000_000_000, "umars"), - coin(1_000_000_000_000, "uusdc"), - ]) - .unwrap(); - - let contract_addr = instantiate_contract(&wasm, &signer); - - let gamm = Gamm::new(&app); - let pool_atom_osmo = gamm - .create_basic_pool(&[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], &signer) - .unwrap() - .data - .pool_id; - let pool_osmo_usdc = gamm - .create_basic_pool(&[coin(6_000_000, "uosmo"), coin(1_500_000, "uusdc")], &signer) - .unwrap() - .data - .pool_id; - let pool_osmo_mars = gamm - .create_basic_pool(&[coin(6_000_000, "uosmo"), coin(1_500_000, "umars")], &signer) - .unwrap() - .data - .pool_id; - - let res_err = wasm - .execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "uatom".to_string(), - denom_out: "umars".to_string(), - route: OsmosisRoute(vec![ - SwapAmountInRoute { - pool_id: pool_atom_osmo, - token_out_denom: "uosmo".to_string(), - }, - SwapAmountInRoute { - pool_id: pool_osmo_usdc, - token_out_denom: "uusdc".to_string(), - }, - SwapAmountInRoute { - pool_id: pool_osmo_usdc, - token_out_denom: "uosmo".to_string(), - }, - SwapAmountInRoute { - pool_id: pool_osmo_mars, - token_out_denom: "umars".to_string(), - }, - ]), - }, - &[], - &signer, - ) - .unwrap_err(); - - // invalid - route contains a loop - // this example: ATOM -> OSMO -> USDC -> OSMO -> MARS - assert_err( - res_err, - ContractError::InvalidRoute { - reason: "route contains a loop: denom uosmo seen twice".to_string(), - }, - ); -} - -#[test] -fn step_output_does_not_match() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let signer = app - .init_account(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")]) - .unwrap(); - - let contract_addr = instantiate_contract(&wasm, &signer); - - let gamm = Gamm::new(&app); - let pool_atom_osmo = gamm - .create_basic_pool(&[coin(6_000_000, "uatom"), coin(1_500_000, "uosmo")], &signer) - .unwrap() - .data - .pool_id; - - let res_err = wasm - .execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "uatom".to_string(), - denom_out: "umars".to_string(), - route: OsmosisRoute(vec![SwapAmountInRoute { - pool_id: pool_atom_osmo, - token_out_denom: "uosmo".to_string(), - }]), - }, - &[], - &signer, - ) - .unwrap_err(); - - assert_err( - res_err, - ContractError::InvalidRoute { - reason: "the route's output denom uosmo does not match the desired output umars" - .to_string(), - }, - ); -} - -#[test] -fn set_route_success() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let signer = app - .init_account(&[ - coin(1_000_000_000_000, "uosmo"), - coin(1_000_000_000_000, "umars"), - coin(1_000_000_000_000, "uweth"), - ]) - .unwrap(); - - let contract_addr = instantiate_contract(&wasm, &signer); - - let gamm = Gamm::new(&app); - let pool_mars_osmo = gamm - .create_basic_pool(&[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], &signer) - .unwrap() - .data - .pool_id; - let pool_weth_osmo = gamm - .create_basic_pool(&[coin(100_000, "uweth"), coin(1_000_000, "uosmo")], &signer) - .unwrap() - .data - .pool_id; - - wasm.execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "umars".to_string(), - denom_out: "uweth".to_string(), - route: OsmosisRoute(vec![ - SwapAmountInRoute { - pool_id: pool_mars_osmo, - token_out_denom: "uosmo".to_string(), - }, - SwapAmountInRoute { - pool_id: pool_weth_osmo, - token_out_denom: "uweth".to_string(), - }, - ]), - }, - &[], - &signer, - ) - .unwrap(); - - let res: RouteResponse = wasm - .query( - &contract_addr, - &QueryMsg::Route { - denom_in: "umars".to_string(), - denom_out: "uweth".to_string(), - }, - ) - .unwrap(); - - assert_eq!(res.denom_in, "umars".to_string()); - assert_eq!(res.denom_out, "uweth".to_string()); - assert_eq!(res.route.to_string(), format!("{pool_mars_osmo}:uosmo|{pool_weth_osmo}:uweth")); -} diff --git a/contracts/swapper/osmosis/tests/test_swap.rs b/contracts/swapper/osmosis/tests/test_swap.rs deleted file mode 100644 index 6a17dddc6..000000000 --- a/contracts/swapper/osmosis/tests/test_swap.rs +++ /dev/null @@ -1,179 +0,0 @@ -use cosmwasm_std::{coin, Addr, Coin, Decimal}; -use mars_rover::{adapters::swap::ExecuteMsg, error::ContractError as RoverError}; -use mars_swapper_base::ContractError; -use mars_swapper_osmosis::route::OsmosisRoute; -use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; -use osmosis_test_tube::{Account, Bank, FeeSetting, Gamm, Module, OsmosisTestApp, Wasm}; - -use crate::helpers::{ - assert_err, instantiate_contract, query_balance, swap_to_create_twap_records, -}; - -pub mod helpers; - -#[test] -fn transfer_callback_only_internal() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); - let owner = &accs[0]; - let bad_guy = &accs[1]; - - let contract_addr = instantiate_contract(&wasm, owner); - - let res_err = wasm - .execute( - &contract_addr, - &ExecuteMsg::::TransferResult { - recipient: Addr::unchecked(bad_guy.address()), - denom_in: "mars".to_string(), - denom_out: "osmo".to_string(), - }, - &[], - bad_guy, - ) - .unwrap_err(); - - assert_err( - res_err, - ContractError::Rover(RoverError::Unauthorized { - user: bad_guy.address(), - action: "transfer result".to_string(), - }), - ); -} - -#[test] -fn swap_exact_in_slippage_too_high() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let signer = app - .init_account(&[coin(1_000_000_000_000, "uosmo"), coin(1_000_000_000_000, "umars")]) - .unwrap(); - let whale = app.init_account(&[coin(1_000_000, "umars"), coin(1_000_000, "uosmo")]).unwrap(); - - let contract_addr = instantiate_contract(&wasm, &signer); - - let gamm = Gamm::new(&app); - let pool_mars_osmo = gamm - .create_basic_pool(&[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], &signer) - .unwrap() - .data - .pool_id; - - swap_to_create_twap_records(&app, &signer, pool_mars_osmo, coin(10u128, "umars"), "uosmo"); - - let route = OsmosisRoute(vec![SwapAmountInRoute { - pool_id: pool_mars_osmo, - token_out_denom: "uosmo".to_string(), - }]); - - wasm.execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "umars".to_string(), - denom_out: "uosmo".to_string(), - route, - }, - &[], - &signer, - ) - .unwrap(); - - // whale does a huge trade - let res_err = wasm - .execute( - &contract_addr, - &ExecuteMsg::::SwapExactIn { - coin_in: coin(1_000_000, "umars"), - denom_out: "uosmo".to_string(), - slippage: Decimal::percent(5), - }, - &[coin(1_000_000, "umars")], - &whale, - ) - .unwrap_err(); - - assert_err( - res_err, - "uosmo token is lesser than min amount: calculated amount is lesser than min amount", - ) -} - -#[test] -fn swap_exact_in_success() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let signer = app - .init_account(&[coin(1_000_000_000_000, "uosmo"), coin(1_000_000_000_000, "umars")]) - .unwrap(); - - let tx_fee = 1_000_000u128; - let user_osmo_starting_amount = 10_000_000u128; - let user = app - .init_account(&[coin(10_000, "umars"), coin(user_osmo_starting_amount, "uosmo")]) - .unwrap() - .with_fee_setting(FeeSetting::Custom { - amount: Coin::new(tx_fee, "uosmo"), - gas_limit: tx_fee as u64, - }); - - let contract_addr = instantiate_contract(&wasm, &signer); - - let gamm = Gamm::new(&app); - let pool_mars_osmo = gamm - .create_basic_pool(&[coin(6_000_000, "umars"), coin(1_500_000, "uosmo")], &signer) - .unwrap() - .data - .pool_id; - - swap_to_create_twap_records(&app, &signer, pool_mars_osmo, coin(10u128, "umars"), "uosmo"); - - wasm.execute( - &contract_addr, - &ExecuteMsg::SetRoute { - denom_in: "umars".to_string(), - denom_out: "uosmo".to_string(), - route: OsmosisRoute(vec![SwapAmountInRoute { - pool_id: pool_mars_osmo, - token_out_denom: "uosmo".to_string(), - }]), - }, - &[], - &signer, - ) - .unwrap(); - - let bank = Bank::new(&app); - let osmo_balance = query_balance(&bank, &user.address(), "uosmo"); - let mars_balance = query_balance(&bank, &user.address(), "umars"); - assert_eq!(osmo_balance, user_osmo_starting_amount); - assert_eq!(mars_balance, 10_000); - - wasm.execute( - &contract_addr, - &ExecuteMsg::::SwapExactIn { - coin_in: coin(10_000, "umars"), - denom_out: "uosmo".to_string(), - slippage: Decimal::percent(6), - }, - &[coin(10_000, "umars")], - &user, - ) - .unwrap(); - - // Assert user receives their new tokens - let osmo_balance = query_balance(&bank, &user.address(), "uosmo"); - let mars_balance = query_balance(&bank, &user.address(), "umars"); - assert_eq!(osmo_balance, 2470 + user_osmo_starting_amount - tx_fee); - assert_eq!(mars_balance, 0); - - // Assert no tokens in contract left over - let osmo_balance = query_balance(&bank, &contract_addr, "uosmo"); - let mars_balance = query_balance(&bank, &contract_addr, "umars"); - assert_eq!(osmo_balance, 0); - assert_eq!(mars_balance, 0); -} diff --git a/contracts/swapper/osmosis/tests/test_update_admin.rs b/contracts/swapper/osmosis/tests/test_update_admin.rs deleted file mode 100644 index 75e353be8..000000000 --- a/contracts/swapper/osmosis/tests/test_update_admin.rs +++ /dev/null @@ -1,201 +0,0 @@ -use cosmwasm_std::coin; -use mars_owner::{OwnerResponse, OwnerUpdate}; -use mars_rover::adapters::swap::{ExecuteMsg, QueryMsg}; -use mars_swapper_osmosis::route::OsmosisRoute; -use osmosis_test_tube::{Account, Module, OsmosisTestApp, Wasm}; - -use crate::helpers::instantiate_contract; - -pub mod helpers; - -#[test] -fn initial_state() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); - let owner = &accs[0]; - - let contract_addr = instantiate_contract(&wasm, owner); - - let res: OwnerResponse = wasm.query(&contract_addr, &QueryMsg::Owner {}).unwrap(); - assert_eq!(res.owner.unwrap(), owner.address()); - assert_eq!(res.proposed, None); -} - -#[test] -fn only_owner_can_propose() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 3).unwrap(); - let owner = &accs[0]; - let bad_guy = &accs[1]; - - let contract_addr = instantiate_contract(&wasm, owner); - - wasm.execute( - &contract_addr, - &ExecuteMsg::::UpdateOwner(OwnerUpdate::ProposeNewOwner { - proposed: bad_guy.address(), - }), - &[], - bad_guy, - ) - .unwrap_err(); -} - -#[test] -fn propose_new_owner() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); - let owner = &accs[0]; - let new_owner = &accs[1]; - - let contract_addr = instantiate_contract(&wasm, owner); - - wasm.execute( - &contract_addr, - &ExecuteMsg::::UpdateOwner(OwnerUpdate::ProposeNewOwner { - proposed: new_owner.address(), - }), - &[], - owner, - ) - .unwrap(); - - let res: OwnerResponse = wasm.query(&contract_addr, &QueryMsg::Owner {}).unwrap(); - assert_eq!(res.owner.unwrap(), owner.address()); - assert_eq!(res.proposed.unwrap(), new_owner.address()); -} - -#[test] -fn only_owner_can_clear_proposed() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 3).unwrap(); - let owner = &accs[0]; - let bad_guy = &accs[1]; - let new_owner = &accs[2]; - - let contract_addr = instantiate_contract(&wasm, owner); - - wasm.execute( - &contract_addr, - &ExecuteMsg::::UpdateOwner(OwnerUpdate::ProposeNewOwner { - proposed: new_owner.address(), - }), - &[], - owner, - ) - .unwrap(); - - wasm.execute( - &contract_addr, - &ExecuteMsg::::UpdateOwner(OwnerUpdate::ClearProposed), - &[], - bad_guy, - ) - .unwrap_err(); -} - -#[test] -fn clear_proposed() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); - let owner = &accs[0]; - let new_owner = &accs[1]; - - let contract_addr = instantiate_contract(&wasm, owner); - - wasm.execute( - &contract_addr, - &ExecuteMsg::::UpdateOwner(OwnerUpdate::ProposeNewOwner { - proposed: new_owner.address(), - }), - &[], - owner, - ) - .unwrap(); - - wasm.execute( - &contract_addr, - &ExecuteMsg::::UpdateOwner(OwnerUpdate::ClearProposed), - &[], - owner, - ) - .unwrap(); - - let res: OwnerResponse = wasm.query(&contract_addr, &QueryMsg::Owner {}).unwrap(); - assert_eq!(res.owner.unwrap(), owner.address()); - assert_eq!(res.proposed, None); -} - -#[test] -fn only_proposed_owner_can_accept_role() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); - let owner = &accs[0]; - let new_owner = &accs[1]; - - let contract_addr = instantiate_contract(&wasm, owner); - - wasm.execute( - &contract_addr, - &ExecuteMsg::::UpdateOwner(OwnerUpdate::ProposeNewOwner { - proposed: new_owner.address(), - }), - &[], - owner, - ) - .unwrap(); - - wasm.execute( - &contract_addr, - &ExecuteMsg::::UpdateOwner(OwnerUpdate::AcceptProposed), - &[], - owner, - ) - .unwrap_err(); -} - -#[test] -fn accept_owner_role() { - let app = OsmosisTestApp::new(); - let wasm = Wasm::new(&app); - - let accs = app.init_accounts(&[coin(1_000_000_000_000, "uosmo")], 2).unwrap(); - let owner = &accs[0]; - let new_owner = &accs[1]; - - let contract_addr = instantiate_contract(&wasm, owner); - - wasm.execute( - &contract_addr, - &ExecuteMsg::::UpdateOwner(OwnerUpdate::ProposeNewOwner { - proposed: new_owner.address(), - }), - &[], - owner, - ) - .unwrap(); - - wasm.execute( - &contract_addr, - &ExecuteMsg::::UpdateOwner(OwnerUpdate::AcceptProposed), - &[], - new_owner, - ) - .unwrap(); - - let res: OwnerResponse = wasm.query(&contract_addr, &QueryMsg::Owner {}).unwrap(); - assert_eq!(res.owner.unwrap(), new_owner.address()); - assert_eq!(res.proposed, None); -} diff --git a/contracts/v3-zapper/base/Cargo.toml b/contracts/v3-zapper/base/Cargo.toml deleted file mode 100644 index ce820518f..000000000 --- a/contracts/v3-zapper/base/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "mars-v3-zapper-base" -version = { workspace = true } -authors = { workspace = true } -license = { workspace = true } -edition = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -documentation = { workspace = true } -keywords = { workspace = true } - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -library = [] - -[dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -mars-owner = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } diff --git a/contracts/v3-zapper/base/examples/schema.rs b/contracts/v3-zapper/base/examples/schema.rs deleted file mode 100644 index 96558c085..000000000 --- a/contracts/v3-zapper/base/examples/schema.rs +++ /dev/null @@ -1,10 +0,0 @@ -use cosmwasm_schema::write_api; -use mars_v3_zapper_base::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - query: QueryMsg, - execute: ExecuteMsg, - } -} diff --git a/contracts/v3-zapper/base/src/contract.rs b/contracts/v3-zapper/base/src/contract.rs deleted file mode 100644 index a46f77e35..000000000 --- a/contracts/v3-zapper/base/src/contract.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::marker::PhantomData; - -use cosmwasm_std::{ - to_binary, Addr, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Env, Event, MessageInfo, Reply, - Response, StdError, StdResult, SubMsg, WasmMsg, -}; -use cw2::set_contract_version; -use mars_owner::OwnerInit::SetInitialOwner; - -use crate::{ - error::{ContractError, ContractError::Unauthorized, ContractResult}, - msg::{CallbackMsg, ExecuteMsg, InstantiateMsg, NewPositionRequest, QueryMsg}, - state::OWNER, - traits::PositionManager, - utils::assert_exact_funds_sent, -}; - -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -pub const V3_POSITION_CREATED_EVENT_TYPE: &str = "v3_position_created"; -pub const V3_POSITION_ATTR_KEY: &str = "position_id"; -pub const REFUND_EVENT_TYPE: &str = "execute_callback_refund_coin"; -pub const REFUND_AMOUNT_ATTR_KEY: &str = "coins_returned"; -pub const REFUND_RECIPIENT_ATTR_KEY: &str = "recipient"; - -pub const CREATE_POSITION_REPLY_ID: u64 = 1001; - -pub struct V3ZapperBase -where - M: PositionManager, -{ - pub position_manager: PhantomData, -} - -impl Default for V3ZapperBase -where - M: PositionManager, -{ - fn default() -> Self { - Self { - position_manager: PhantomData, - } - } -} - -impl V3ZapperBase -where - M: PositionManager, -{ - pub fn instantiate(&self, deps: DepsMut, msg: InstantiateMsg) -> ContractResult { - set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; - - OWNER.initialize( - deps.storage, - deps.api, - SetInitialOwner { - owner: msg.owner, - }, - )?; - - Ok(Response::default()) - } - - pub fn execute( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, - ) -> ContractResult { - match msg { - ExecuteMsg::CreatePosition(p) => self.create_position(deps, env, info, p), - ExecuteMsg::UpdateOwner(update) => Ok(OWNER.update(deps, info, update)?), - ExecuteMsg::Callback(msg) => self.handle_callback(deps, env, info, msg), - } - } - - fn handle_callback( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: CallbackMsg, - ) -> ContractResult { - if info.sender != env.contract.address { - return Err(Unauthorized); - } - match msg { - CallbackMsg::RefundCoin { - recipient, - denoms, - } => self.refund_coin(deps, env, recipient, &denoms), - } - } - - pub fn query(&self, deps: Deps, _: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Owner {} => to_binary(&OWNER.query(deps.storage)?), - } - } - - pub fn reply(&self, deps: DepsMut, env: Env, reply: Reply) -> ContractResult { - let response = reply.result.into_result().map_err(StdError::generic_err)?; - let position_id = match reply.id { - CREATE_POSITION_REPLY_ID => M::parse_position_id(deps, env, response)?, - id => return Err(ContractError::ReplyError(format!("reply id {id} is not valid"))), - }; - let event = Event::new(V3_POSITION_CREATED_EVENT_TYPE.to_string()) - .add_attribute(V3_POSITION_ATTR_KEY.to_string(), position_id); - Ok(Response::new().add_event(event)) - } - - pub fn create_position( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - request: NewPositionRequest, - ) -> ContractResult { - OWNER.assert_owner(deps.storage, &info.sender)?; - assert_exact_funds_sent(&info, &request.tokens_provided)?; - - // Creating positions do not guarantee all funds will be used. Refund the leftovers. - let refund_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: env.contract.address.to_string(), - funds: vec![], - msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::RefundCoin { - recipient: info.sender, - denoms: request.tokens_provided.iter().map(|c| c.denom.clone()).collect(), - }))?, - }); - - let create_msg = M::create_new_position(deps, env, request)?; - let create_submsg = SubMsg::reply_on_success(create_msg, CREATE_POSITION_REPLY_ID); - - Ok(Response::new().add_submessage(create_submsg).add_message(refund_msg)) - } - - pub fn refund_coin( - &self, - deps: DepsMut, - env: Env, - recipient: Addr, - denoms: &[String], - ) -> ContractResult { - let mut coins_to_return = vec![]; - for denom in denoms { - let balance = deps.querier.query_balance(env.contract.address.clone(), denom)?; - if !balance.amount.is_zero() { - coins_to_return.push(balance) - } - } - - let coins_refunded = - coins_to_return.iter().map(|c| c.to_string()).collect::>().join(", "); - - let transfer_msg = CosmosMsg::Bank(BankMsg::Send { - to_address: recipient.to_string(), - amount: coins_to_return, - }); - - let event = Event::new(REFUND_EVENT_TYPE) - .add_attribute(REFUND_AMOUNT_ATTR_KEY, coins_refunded) - .add_attribute(REFUND_RECIPIENT_ATTR_KEY, recipient); - - Ok(Response::new().add_message(transfer_msg).add_event(event)) - } -} diff --git a/contracts/v3-zapper/base/src/error.rs b/contracts/v3-zapper/base/src/error.rs deleted file mode 100644 index 37e09022d..000000000 --- a/contracts/v3-zapper/base/src/error.rs +++ /dev/null @@ -1,26 +0,0 @@ -use cosmwasm_std::StdError; -use mars_owner::OwnerError; -use thiserror::Error; - -pub type ContractResult = Result; - -#[derive(Error, Debug)] -pub enum ContractError { - #[error("Sent fund mismatch. Expected: {expected:?}, received {received:?}")] - FundsMismatch { - expected: String, - received: String, - }, - - #[error("{0}")] - OwnerError(#[from] OwnerError), - - #[error("Submessage Reply Error: {0}")] - ReplyError(String), - - #[error("{0}")] - Std(#[from] StdError), - - #[error("Caller not permitted to perform action")] - Unauthorized, -} diff --git a/contracts/v3-zapper/base/src/lib.rs b/contracts/v3-zapper/base/src/lib.rs deleted file mode 100644 index a51ef6eb3..000000000 --- a/contracts/v3-zapper/base/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod contract; -pub mod error; -pub mod msg; -pub mod state; -pub mod traits; -pub mod utils; diff --git a/contracts/v3-zapper/base/src/msg.rs b/contracts/v3-zapper/base/src/msg.rs deleted file mode 100644 index 69778f4f6..000000000 --- a/contracts/v3-zapper/base/src/msg.rs +++ /dev/null @@ -1,46 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Coin}; -use mars_owner::OwnerUpdate; - -#[cw_serde] -pub struct InstantiateMsg { - pub owner: String, -} - -#[cw_serde] -pub struct NewPositionRequest { - pub pool_id: u64, - pub lower_tick: i64, - pub upper_tick: i64, - pub tokens_provided: Vec, - pub token_min_amount0: String, - pub token_min_amount1: String, -} - -#[cw_serde] -pub enum ExecuteMsg { - /// Expects the corresponding tokens to be sent in Funds. The position created will be owned - /// by the zapper contract itself. Consumer should expect an event with - /// V3_POSITION_CREATED_EVENT_TYPE & V3_POSITION_ATTR_KEY, used to parse the id of the position - /// created. In the event not all funds are issued, the remaining is refunded to the caller. - CreatePosition(NewPositionRequest), - UpdateOwner(OwnerUpdate), - Callback(CallbackMsg), -} - -#[cw_serde] -pub enum CallbackMsg { - RefundCoin { - recipient: Addr, - denoms: Vec, - }, -} - -impl CallbackMsg {} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(mars_owner::OwnerResponse)] - Owner {}, -} diff --git a/contracts/v3-zapper/base/src/state.rs b/contracts/v3-zapper/base/src/state.rs deleted file mode 100644 index 0c0e31ebd..000000000 --- a/contracts/v3-zapper/base/src/state.rs +++ /dev/null @@ -1,3 +0,0 @@ -use mars_owner::Owner; - -pub const OWNER: Owner = Owner::new("owner"); diff --git a/contracts/v3-zapper/base/src/traits.rs b/contracts/v3-zapper/base/src/traits.rs deleted file mode 100644 index 1c11985e9..000000000 --- a/contracts/v3-zapper/base/src/traits.rs +++ /dev/null @@ -1,30 +0,0 @@ -use cosmwasm_std::{Coin, CosmosMsg, DepsMut, Env, SubMsgResponse}; - -use crate::{error::ContractResult, msg::NewPositionRequest}; - -pub trait PositionManager { - /// Owner should be the zapper contract itself (not the user) - fn create_new_position( - deps: DepsMut, - env: Env, - request: NewPositionRequest, - ) -> ContractResult; - - /// Responsible for parsing the reply message to acquire the id of - /// the newly created position - fn parse_position_id( - deps: DepsMut, - env: Env, - response: SubMsgResponse, - ) -> ContractResult; -} - -pub trait OptionFilter { - fn only_some(&self) -> Vec; -} - -impl OptionFilter for Vec<&Option> { - fn only_some(&self) -> Vec { - self.iter().filter_map(|x| x.as_ref()).cloned().collect() - } -} diff --git a/contracts/v3-zapper/base/src/utils.rs b/contracts/v3-zapper/base/src/utils.rs deleted file mode 100644 index db356b4c7..000000000 --- a/contracts/v3-zapper/base/src/utils.rs +++ /dev/null @@ -1,18 +0,0 @@ -use cosmwasm_std::{Coin, MessageInfo}; - -use crate::error::{ContractError, ContractResult}; - -/// Assert that fund of exactly the same type and amount was sent along with a message -pub fn assert_exact_funds_sent(info: &MessageInfo, expected: &[Coin]) -> ContractResult<()> { - let same_quantity = info.funds.len() == expected.len(); - let all_expected_in_funds = expected.iter().all(|e| info.funds.iter().any(|i| e == i)); - - if !same_quantity || !all_expected_in_funds { - return Err(ContractError::FundsMismatch { - expected: expected.iter().map(|c| c.to_string()).collect::>().join(", "), - received: info.funds.iter().map(|c| c.to_string()).collect::>().join(", "), - }); - } - - Ok(()) -} diff --git a/contracts/v3-zapper/osmosis/Cargo.toml b/contracts/v3-zapper/osmosis/Cargo.toml deleted file mode 100644 index 78a01350e..000000000 --- a/contracts/v3-zapper/osmosis/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "mars-v3-zapper-osmosis" -version = { workspace = true } -authors = { workspace = true } -license = { workspace = true } -edition = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -documentation = { workspace = true } -keywords = { workspace = true } - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -library = [] - -[dependencies] -cosmwasm-std = { workspace = true } -mars-owner = { workspace = true } -mars-v3-zapper-base = { workspace = true } -osmosis-std = { workspace = true } - -[dev-dependencies] -anyhow = { workspace = true } -osmosis-test-tube = { workspace = true } diff --git a/contracts/v3-zapper/osmosis/src/contract.rs b/contracts/v3-zapper/osmosis/src/contract.rs deleted file mode 100644 index 1cad74783..000000000 --- a/contracts/v3-zapper/osmosis/src/contract.rs +++ /dev/null @@ -1,43 +0,0 @@ -use cosmwasm_std::{ - entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, -}; -use mars_v3_zapper_base::{ - contract::V3ZapperBase, - error::ContractResult, - msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, -}; - -use crate::position_manager::OsmosisPositionManager; - -/// The Osmosis v3 zapper contract inherits logic from the base v3 zapper contract -pub type OsmosisV3Zapper = V3ZapperBase; - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> ContractResult { - OsmosisV3Zapper::default().instantiate(deps, msg) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> ContractResult { - OsmosisV3Zapper::default().execute(deps, env, info, msg) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - OsmosisV3Zapper::default().query(deps, env, msg) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn reply(deps: DepsMut, env: Env, reply: Reply) -> ContractResult { - OsmosisV3Zapper::default().reply(deps, env, reply) -} diff --git a/contracts/v3-zapper/osmosis/src/lib.rs b/contracts/v3-zapper/osmosis/src/lib.rs deleted file mode 100644 index 5bd14ddb3..000000000 --- a/contracts/v3-zapper/osmosis/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod contract; -pub mod position_manager; diff --git a/contracts/v3-zapper/osmosis/src/position_manager.rs b/contracts/v3-zapper/osmosis/src/position_manager.rs deleted file mode 100644 index 3cd245511..000000000 --- a/contracts/v3-zapper/osmosis/src/position_manager.rs +++ /dev/null @@ -1,55 +0,0 @@ -use cosmwasm_std::{Coin, CosmosMsg, DepsMut, Env, SubMsgResponse}; -use mars_v3_zapper_base::{ - error::{ContractError::ReplyError, ContractResult}, - msg::NewPositionRequest, - traits::PositionManager, -}; -use osmosis_std::types::{ - cosmos::base::v1beta1, - osmosis::concentratedliquidity::v1beta1::{MsgCreatePosition, MsgCreatePositionResponse}, -}; - -pub struct OsmosisPositionManager {} - -impl PositionManager for OsmosisPositionManager { - fn create_new_position( - _: DepsMut, - env: Env, - p: NewPositionRequest, - ) -> ContractResult { - let create_msg = MsgCreatePosition { - pool_id: p.pool_id, - sender: env.contract.address.to_string(), - lower_tick: p.lower_tick, - upper_tick: p.upper_tick, - token_min_amount0: p.token_min_amount0, - token_min_amount1: p.token_min_amount1, - tokens_provided: p.tokens_provided.to_v1beta_coins(), - }; - Ok(create_msg.into()) - } - - fn parse_position_id(_: DepsMut, _: Env, response: SubMsgResponse) -> ContractResult { - let Some(b) = response.data else { - return Err(ReplyError("No data sent back after creating position".to_string())); - }; - - let parsed_response: MsgCreatePositionResponse = b.try_into()?; - Ok(parsed_response.position_id.to_string()) - } -} - -pub trait ToV1BetaCoins { - fn to_v1beta_coins(&self) -> Vec; -} - -impl ToV1BetaCoins for Vec { - fn to_v1beta_coins(&self) -> Vec { - self.iter() - .map(|c| v1beta1::Coin { - denom: c.denom.clone(), - amount: c.amount.to_string(), - }) - .collect() - } -} diff --git a/contracts/v3-zapper/osmosis/tests/helpers/assertions.rs b/contracts/v3-zapper/osmosis/tests/helpers/assertions.rs deleted file mode 100644 index 71b73dc50..000000000 --- a/contracts/v3-zapper/osmosis/tests/helpers/assertions.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::fmt::Display; - -use osmosis_test_tube::RunnerError; - -pub fn assert_err(actual: RunnerError, expected: impl Display) { - match actual { - RunnerError::ExecuteError { - msg, - } => { - println!("ExecuteError, msg: {msg}"); - assert!(msg.contains(&format!("{expected}"))) - } - RunnerError::QueryError { - msg, - } => { - println!("QueryError, msg: {msg}"); - assert!(msg.contains(&format!("{expected}"))) - } - _ => panic!("Unhandled error"), - } -} diff --git a/contracts/v3-zapper/osmosis/tests/helpers/generator.rs b/contracts/v3-zapper/osmosis/tests/helpers/generator.rs deleted file mode 100644 index 9fe7e3abb..000000000 --- a/contracts/v3-zapper/osmosis/tests/helpers/generator.rs +++ /dev/null @@ -1,13 +0,0 @@ -use cosmwasm_std::coin; -use mars_v3_zapper_base::msg::NewPositionRequest; - -pub fn default_new_position_req() -> NewPositionRequest { - NewPositionRequest { - pool_id: 1, - lower_tick: -1, - upper_tick: 100, - token_min_amount0: "10000".to_string(), - token_min_amount1: "10000".to_string(), - tokens_provided: vec![coin(100_000_000, "ujuno"), coin(100_000_000, "umars")], - } -} diff --git a/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs b/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs deleted file mode 100644 index 72dd02775..000000000 --- a/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs +++ /dev/null @@ -1,208 +0,0 @@ -use std::{mem::take, str::FromStr}; - -use anyhow::Result as AnyResult; -use cosmwasm_std::coin; -use mars_owner::{OwnerResponse, OwnerUpdate}; -use mars_v3_zapper_base::msg::{CallbackMsg, ExecuteMsg, InstantiateMsg, QueryMsg}; -use osmosis_std::types::{ - cosmos::bank::v1beta1::QueryBalanceRequest, - cosmwasm::wasm::v1::MsgExecuteContractResponse, - osmosis::{ - concentratedliquidity, - concentratedliquidity::v1beta1::{ - CreateConcentratedLiquidityPoolsProposal, FullPositionBreakdown, PoolRecord, - PoolsRequest, UserPositionsRequest, - }, - }, -}; -use osmosis_test_tube::{ - cosmrs::proto::prost::Message, Account, Bank, ConcentratedLiquidity, GovWithAppAccess, Module, - OsmosisTestApp, RunnerExecuteResult, SigningAccount, Wasm, -}; - -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -pub const DEFAULT_STARTING_BALANCE: u128 = 1_000_000_000_000; - -pub const ATOM: &str = "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2"; -pub const DAI: &str = "ibc/0CD3A0285E1341859B5E86B6AB7682F023D03E97607CCC1DC95706411D866DF7"; - -pub struct MockEnv { - pub app: OsmosisTestApp, - pub owner: SigningAccount, - pub zapper: String, -} - -pub struct MockEnvBuilder { - pub app: OsmosisTestApp, -} - -#[allow(clippy::new_ret_no_self)] -impl MockEnv { - pub fn new() -> MockEnvBuilder { - MockEnvBuilder { - app: OsmosisTestApp::new(), - } - } - - //-------------------------------------------------------------------------------------------------- - // Execute Msgs - //-------------------------------------------------------------------------------------------------- - pub fn update_owner( - &mut self, - update: OwnerUpdate, - alt_signer: Option<&SigningAccount>, - ) -> RunnerExecuteResult { - let wasm = Wasm::new(&self.app); - wasm.execute( - &self.zapper, - &ExecuteMsg::UpdateOwner(update), - &[coin(5000, "uosmo")], - alt_signer.unwrap_or(&self.owner), - ) - } - - pub fn create_pool(&mut self, denom0: &str, denom1: &str) -> u64 { - let cl = ConcentratedLiquidity::new(&self.app); - let gov = GovWithAppAccess::new(&self.app); - - gov.propose_and_execute( - CreateConcentratedLiquidityPoolsProposal::TYPE_URL.to_string(), - CreateConcentratedLiquidityPoolsProposal { - title: String::from("test"), - description: String::from("test"), - pool_records: vec![PoolRecord { - denom0: denom0.to_string(), - denom1: denom1.to_string(), - tick_spacing: 1, - exponent_at_price_one: "-4".to_string(), - spread_factor: "500000000000000".to_string(), - }], - }, - self.owner.address(), - false, - &self.owner, - ) - .unwrap(); - - let pools = cl - .query_pools(&PoolsRequest { - pagination: None, - }) - .unwrap() - .pools; - - concentratedliquidity::v1beta1::Pool::decode(pools.last().unwrap().value.as_slice()) - .unwrap() - .id - } - - pub fn callback( - &mut self, - msg: CallbackMsg, - alt_signer: Option<&SigningAccount>, - ) -> RunnerExecuteResult { - let wasm = Wasm::new(&self.app); - wasm.execute( - &self.zapper, - &ExecuteMsg::Callback(msg), - &[coin(5000, "uosmo")], - alt_signer.unwrap_or(&self.owner), - ) - } - - //-------------------------------------------------------------------------------------------------- - // Queries - //-------------------------------------------------------------------------------------------------- - pub fn query_ownership(&self) -> OwnerResponse { - let wasm = Wasm::new(&self.app); - wasm.query(&self.zapper, &QueryMsg::Owner {}).unwrap() - } - - pub fn query_balance(&self, address: &str, denom: &str) -> u128 { - let bank = Bank::new(&self.app); - let str_balance = bank - .query_balance(&QueryBalanceRequest { - address: address.to_string(), - denom: denom.to_string(), - }) - .unwrap() - .balance - .unwrap() - .amount; - u128::from_str(&str_balance).unwrap() - } - - pub fn query_positions(&self, pool_id: u64) -> Vec { - let cl = ConcentratedLiquidity::new(&self.app); - let res = cl - .query_user_positions(&UserPositionsRequest { - address: self.zapper.clone(), - pool_id, - pagination: None, - }) - .unwrap(); - res.positions - } -} - -impl MockEnvBuilder { - pub fn build(&mut self) -> AnyResult { - let owner = self - .app - .init_account(&[ - coin(DEFAULT_STARTING_BALANCE, "uosmo"), - coin(DEFAULT_STARTING_BALANCE, ATOM), - coin(DEFAULT_STARTING_BALANCE, DAI), - ]) - .unwrap(); - let zapper = self.instantiate_contract(&owner); - - Ok(MockEnv { - app: take(&mut self.app), - owner, - zapper, - }) - } - - pub fn wasm_file(&self) -> Vec { - let artifacts_dir = - std::env::var("ARTIFACTS_DIR_PATH").unwrap_or_else(|_| "artifacts".to_string()); - let snaked_name = CONTRACT_NAME.replace('-', "_"); - let relative_dir = format!("../../../{artifacts_dir}"); - - let wasm_file_path = format!("{relative_dir}/{snaked_name}.wasm"); - - match std::fs::read(wasm_file_path.clone()) { - Ok(bytes) => { - println!("{wasm_file_path}"); - bytes - } - // Retry if in arch64 environment - Err(_) => { - let alt_file_path = format!("{relative_dir}/{snaked_name}-aarch64.wasm"); - println!("{}", alt_file_path); - std::fs::read(alt_file_path).unwrap() - } - } - } - - pub fn instantiate_contract(&mut self, owner: &SigningAccount) -> String { - let wasm = Wasm::new(&self.app); - let wasm_byte_code = self.wasm_file(); - let code_id = wasm.store_code(&wasm_byte_code, None, owner).unwrap().data.code_id; - - wasm.instantiate( - code_id, - &InstantiateMsg { - owner: owner.address(), - }, - None, - Some("v3-zapper-osmosis-contract"), - &[], - owner, - ) - .unwrap() - .data - .address - } -} diff --git a/contracts/v3-zapper/osmosis/tests/helpers/mod.rs b/contracts/v3-zapper/osmosis/tests/helpers/mod.rs deleted file mode 100644 index 976b5a81f..000000000 --- a/contracts/v3-zapper/osmosis/tests/helpers/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub use self::{assertions::*, generator::*, mock_env::*}; - -mod assertions; -mod generator; -mod mock_env; diff --git a/contracts/v3-zapper/osmosis/tests/test_add_position.rs b/contracts/v3-zapper/osmosis/tests/test_add_position.rs deleted file mode 100644 index fa7f1ef1e..000000000 --- a/contracts/v3-zapper/osmosis/tests/test_add_position.rs +++ /dev/null @@ -1,355 +0,0 @@ -use cosmwasm_std::coin; -use mars_v3_zapper_base::{ - contract::{ - REFUND_AMOUNT_ATTR_KEY, REFUND_EVENT_TYPE, V3_POSITION_ATTR_KEY, - V3_POSITION_CREATED_EVENT_TYPE, - }, - msg::{ExecuteMsg, NewPositionRequest}, -}; -use osmosis_test_tube::{Account, Module, Wasm}; - -use crate::helpers::{ - assert_err, default_new_position_req, MockEnv, ATOM, DAI, DEFAULT_STARTING_BALANCE, -}; - -pub mod helpers; - -#[test] -fn only_owner_can_add_positions() { - let mock = MockEnv::new().build().unwrap(); - let bad_guy = mock.app.init_account(&[coin(1_000_000, "uosmo")]).unwrap(); - - let wasm = Wasm::new(&mock.app); - let err = wasm - .execute( - &mock.zapper, - &ExecuteMsg::CreatePosition(default_new_position_req()), - &[], - &bad_guy, - ) - .unwrap_err(); - - assert_err(err, "Caller is not owner"); -} - -#[test] -fn must_send_exact_funds() { - let mut mock = MockEnv::new().build().unwrap(); - mock.create_pool(DAI, ATOM); - - let wasm = Wasm::new(&mock.app); - - let new_position = NewPositionRequest { - pool_id: 1, - lower_tick: -1, - upper_tick: 100, - token_min_amount0: "10000".to_string(), - token_min_amount1: "10000".to_string(), - tokens_provided: vec![coin(100_000_000, DAI), coin(100_000_000, ATOM)], - }; - - let err = wasm - .execute(&mock.zapper, &ExecuteMsg::CreatePosition(new_position.clone()), &[], &mock.owner) - .unwrap_err(); - assert_err(err, "Sent fund mismatch"); - - let err = wasm - .execute( - &mock.zapper, - &ExecuteMsg::CreatePosition(new_position.clone()), - &[new_position.tokens_provided.first().unwrap().clone()], - &mock.owner, - ) - .unwrap_err(); - assert_err(err, "Sent fund mismatch"); - - let err = wasm - .execute( - &mock.zapper, - &ExecuteMsg::CreatePosition(new_position), - &[coin(1000, "uosmo")], - &mock.owner, - ) - .unwrap_err(); - assert_err(err, "Sent fund mismatch"); - - // assert with only one token provided - let new_position = NewPositionRequest { - pool_id: 1, - lower_tick: -1, - upper_tick: 100, - token_min_amount0: "0".to_string(), - token_min_amount1: "10000".to_string(), - tokens_provided: vec![coin(100_000_000, DAI)], - }; - - let err = wasm - .execute( - &mock.zapper, - &ExecuteMsg::CreatePosition(new_position), - &[coin(100_000_000, DAI), coin(100_000_000, ATOM)], - &mock.owner, - ) - .unwrap_err(); - assert_err(err, "Sent fund mismatch"); -} - -#[test] -fn add_position_successfully() { - let mut mock = MockEnv::new().build().unwrap(); - mock.create_pool(DAI, ATOM); - - // assert owner funds before - let denom0_balance = mock.query_balance(&mock.owner.address(), DAI); - assert_eq!(DEFAULT_STARTING_BALANCE, denom0_balance); - let denom1_balance = mock.query_balance(&mock.owner.address(), ATOM); - assert_eq!(DEFAULT_STARTING_BALANCE, denom1_balance); - - // assert zapper funds before - let denom0_balance = mock.query_balance(&mock.zapper, DAI); - assert_eq!(0, denom0_balance); - let denom1_balance = mock.query_balance(&mock.zapper, ATOM); - assert_eq!(0, denom1_balance); - - let wasm = Wasm::new(&mock.app); - - let amount_sent = 100_000_000; - let new_position = NewPositionRequest { - pool_id: 1, - lower_tick: -1, - upper_tick: 100, - token_min_amount0: "10000".to_string(), - token_min_amount1: "10000".to_string(), - tokens_provided: vec![coin(amount_sent, DAI), coin(amount_sent, ATOM)], - }; - let res = wasm - .execute( - &mock.zapper, - &ExecuteMsg::CreatePosition(new_position.clone()), - &new_position.tokens_provided, - &mock.owner, - ) - .unwrap(); - - // Assert correct event on logs - let event = res - .events - .iter() - .find(|e| e.ty == format!("wasm-{}", V3_POSITION_CREATED_EVENT_TYPE)) - .unwrap(); - let attr = event.attributes.iter().find(|a| a.key == V3_POSITION_ATTR_KEY).unwrap(); - assert_eq!("1", attr.value); - - // assert zapper has that position opened as expected - let positions = mock.query_positions(1); - assert_eq!(1, positions.len()); - let p = positions.first().unwrap(); - let position = p.position.clone().unwrap(); - assert_eq!(1, position.position_id); - assert_eq!(mock.zapper, position.address); - assert_eq!(1, position.pool_id); - assert_eq!(new_position.lower_tick, position.lower_tick); - assert_eq!(new_position.upper_tick, position.upper_tick); - assert_eq!( - new_position.tokens_provided.first().unwrap().denom, - p.asset0.clone().unwrap().denom - ); - assert_eq!(new_position.tokens_provided.get(1).unwrap().denom, p.asset1.clone().unwrap().denom); - - // assert zapper funds after - let denom0_balance = mock.query_balance(&mock.zapper, DAI); - assert_eq!(0, denom0_balance); - let denom1_balance = mock.query_balance(&mock.zapper, ATOM); - assert_eq!(0, denom1_balance); - - // assert owner funds after - let denom0_balance = mock.query_balance(&mock.owner.address(), DAI); - let position_amount0 = p.asset0.clone().unwrap().amount.parse::().unwrap(); - assert_eq!(DEFAULT_STARTING_BALANCE - position_amount0, denom0_balance); - - let denom1_balance = mock.query_balance(&mock.owner.address(), ATOM); - let position_amount1 = p.asset1.clone().unwrap().amount.parse::().unwrap(); - assert_eq!(DEFAULT_STARTING_BALANCE - position_amount1, denom1_balance); -} - -#[test] -fn refunds_are_issued() { - let mut mock = MockEnv::new().build().unwrap(); - mock.create_pool(DAI, ATOM); - - // assert owner funds before - let denom0_balance = mock.query_balance(&mock.owner.address(), DAI); - assert_eq!(DEFAULT_STARTING_BALANCE, denom0_balance); - let denom1_balance = mock.query_balance(&mock.owner.address(), ATOM); - assert_eq!(DEFAULT_STARTING_BALANCE, denom1_balance); - - let wasm = Wasm::new(&mock.app); - - let amount_sent = 10_000_000; - let new_position = NewPositionRequest { - pool_id: 1, - lower_tick: -100, - upper_tick: 100, - token_min_amount0: "10000".to_string(), - token_min_amount1: "10000".to_string(), - tokens_provided: vec![coin(amount_sent, DAI), coin(amount_sent, ATOM)], - }; - - let res = wasm - .execute( - &mock.zapper, - &ExecuteMsg::CreatePosition(new_position.clone()), - &new_position.tokens_provided, - &mock.owner, - ) - .unwrap(); - - // Zapper should not have a balance after tx - let denom0_balance = mock.query_balance(&mock.zapper, DAI); - assert_eq!(0, denom0_balance); - let denom1_balance = mock.query_balance(&mock.zapper, ATOM); - assert_eq!(0, denom1_balance); - - // Assert refund event emitted - let refund_amount_a = 8999922u128; - let event = res.events.iter().find(|e| e.ty == format!("wasm-{}", REFUND_EVENT_TYPE)).unwrap(); - let attr = event.attributes.iter().find(|a| a.key == REFUND_AMOUNT_ATTR_KEY).unwrap(); - assert_eq!(format!("{refund_amount_a}{ATOM}"), attr.value); - - // No refund on denom0 - let denom0_balance = mock.query_balance(&mock.owner.address(), DAI); - assert_eq!(DEFAULT_STARTING_BALANCE - amount_sent, denom0_balance); - - // assert refund took place for denom1 - let denom1_balance = mock.query_balance(&mock.owner.address(), ATOM); - assert_eq!(DEFAULT_STARTING_BALANCE - amount_sent + refund_amount_a, denom1_balance); - - let new_position = NewPositionRequest { - pool_id: 1, - lower_tick: -100, - upper_tick: -20, - token_min_amount0: "0".to_string(), - token_min_amount1: "10000".to_string(), - tokens_provided: vec![coin(amount_sent, DAI), coin(amount_sent, ATOM)], - }; - - let res = wasm - .execute( - &mock.zapper, - &ExecuteMsg::CreatePosition(new_position.clone()), - &new_position.tokens_provided, - &mock.owner, - ) - .unwrap(); - - // Assert refund event emitted - let refund_amount_b = 10000000u128; - let event = res.events.iter().find(|e| e.ty == format!("wasm-{}", REFUND_EVENT_TYPE)).unwrap(); - let attr = event.attributes.iter().find(|a| a.key == REFUND_AMOUNT_ATTR_KEY).unwrap(); - assert_eq!(format!("{refund_amount_b}{DAI}"), attr.value); - - // Full refund on denom0 - let denom0_balance = mock.query_balance(&mock.owner.address(), DAI); - // Starting balance after first position was created - let balance_before = DEFAULT_STARTING_BALANCE - amount_sent; - assert_eq!(balance_before, denom0_balance); - - // No refund for denom1 - let denom1_balance = mock.query_balance(&mock.owner.address(), ATOM); - // Starting balance after first position was created - let balance_before = DEFAULT_STARTING_BALANCE - amount_sent + refund_amount_a; - assert_eq!(balance_before - amount_sent, denom1_balance); -} - -#[test] -fn adding_multiple_increments() { - let mut mock = MockEnv::new().build().unwrap(); - mock.create_pool(DAI, ATOM); - - let wasm = Wasm::new(&mock.app); - - let new_position = NewPositionRequest { - pool_id: 1, - lower_tick: -1, - upper_tick: 100, - token_min_amount0: "10000".to_string(), - token_min_amount1: "10000".to_string(), - tokens_provided: vec![coin(100_000_000, DAI), coin(100_000_000, ATOM)], - }; - wasm.execute( - &mock.zapper, - &ExecuteMsg::CreatePosition(new_position.clone()), - &new_position.tokens_provided, - &mock.owner, - ) - .unwrap(); - - wasm.execute( - &mock.zapper, - &ExecuteMsg::CreatePosition(new_position.clone()), - &new_position.tokens_provided, - &mock.owner, - ) - .unwrap(); - - let res = wasm - .execute( - &mock.zapper, - &ExecuteMsg::CreatePosition(new_position.clone()), - &new_position.tokens_provided, - &mock.owner, - ) - .unwrap(); - - // Assert incrementing position id on logs - let event = res - .events - .iter() - .find(|e| e.ty == format!("wasm-{}", V3_POSITION_CREATED_EVENT_TYPE)) - .unwrap(); - let attr = event.attributes.iter().find(|a| a.key == V3_POSITION_ATTR_KEY).unwrap(); - assert_eq!("3", attr.value); -} - -#[test] -fn error_rolls_back_state() { - let mut mock = MockEnv::new().build().unwrap(); - mock.create_pool(DAI, ATOM); - - // assert owner funds before - let denom0_balance = mock.query_balance(&mock.owner.address(), ATOM); - assert_eq!(DEFAULT_STARTING_BALANCE, denom0_balance); - let denom1_balance = mock.query_balance(&mock.owner.address(), DAI); - assert_eq!(DEFAULT_STARTING_BALANCE, denom1_balance); - - let wasm = Wasm::new(&mock.app); - - let amount_sent = 100_000_000; - let new_position = NewPositionRequest { - pool_id: 1, - lower_tick: -1, - upper_tick: 100, - token_min_amount0: "10000000000000000".to_string(), - token_min_amount1: "10000".to_string(), - tokens_provided: vec![coin(amount_sent, DAI), coin(amount_sent, ATOM)], - }; - wasm.execute( - &mock.zapper, - &ExecuteMsg::CreatePosition(new_position.clone()), - &new_position.tokens_provided, - &mock.owner, - ) - .unwrap_err(); - - // assert zapper funds after - let denom0_balance = mock.query_balance(&mock.zapper, ATOM); - assert_eq!(0, denom0_balance); - let denom1_balance = mock.query_balance(&mock.zapper, DAI); - assert_eq!(0, denom1_balance); - - // assert owner funds after - let denom0_balance = mock.query_balance(&mock.owner.address(), ATOM); - assert_eq!(DEFAULT_STARTING_BALANCE, denom0_balance); - let denom1_balance = mock.query_balance(&mock.owner.address(), DAI); - assert_eq!(DEFAULT_STARTING_BALANCE, denom1_balance); -} diff --git a/contracts/v3-zapper/osmosis/tests/test_callback.rs b/contracts/v3-zapper/osmosis/tests/test_callback.rs deleted file mode 100644 index 2dda27dc3..000000000 --- a/contracts/v3-zapper/osmosis/tests/test_callback.rs +++ /dev/null @@ -1,24 +0,0 @@ -use cosmwasm_std::{coin, Addr}; -use mars_v3_zapper_base::msg::CallbackMsg; -use osmosis_test_tube::Account; - -use crate::helpers::{assert_err, MockEnv}; - -pub mod helpers; - -#[test] -fn only_owner_can_invoke_callback() { - let mut mock = MockEnv::new().build().unwrap(); - let bad_guy = mock.app.init_account(&[coin(1_000_000, "uosmo")]).unwrap(); - let err = mock - .callback( - CallbackMsg::RefundCoin { - recipient: Addr::unchecked(bad_guy.address()), - denoms: vec!["xyz".to_string()], - }, - Some(&bad_guy), - ) - .unwrap_err(); - - assert_err(err, "Caller not permitted to perform action"); -} diff --git a/contracts/v3-zapper/osmosis/tests/test_update_owner.rs b/contracts/v3-zapper/osmosis/tests/test_update_owner.rs deleted file mode 100644 index 427b57f68..000000000 --- a/contracts/v3-zapper/osmosis/tests/test_update_owner.rs +++ /dev/null @@ -1,54 +0,0 @@ -use cosmwasm_std::coin; -use mars_owner::OwnerUpdate; -use osmosis_test_tube::Account; - -use crate::helpers::{assert_err, MockEnv}; - -pub mod helpers; - -#[test] -fn owner_set_on_init() { - let mock = MockEnv::new().build().unwrap(); - assert!(mock.query_ownership().owner.is_some()); -} - -#[test] -fn only_owner_can_update_ownership() { - let mut mock = MockEnv::new().build().unwrap(); - let bad_guy = mock.app.init_account(&[coin(1_000_000, "uosmo")]).unwrap(); - let err = mock - .update_owner( - OwnerUpdate::ProposeNewOwner { - proposed: bad_guy.address(), - }, - Some(&bad_guy), - ) - .unwrap_err(); - - assert_err(err, "Caller is not owner") -} - -#[test] -fn owner_can_be_updated() { - let mut mock = MockEnv::new().build().unwrap(); - - let ownership = mock.query_ownership(); - assert_eq!(ownership.proposed, None); - - let new_owner = mock.app.init_account(&[coin(1_000_000_000, "uosmo")]).unwrap(); - mock.update_owner( - OwnerUpdate::ProposeNewOwner { - proposed: new_owner.address(), - }, - None, - ) - .unwrap(); - - let ownership = mock.query_ownership(); - assert_eq!(ownership.proposed, Some(new_owner.address())); - - mock.update_owner(OwnerUpdate::AcceptProposed {}, Some(&new_owner)).unwrap(); - let ownership = mock.query_ownership(); - assert_eq!(ownership.owner, Some(new_owner.address())); - assert_eq!(ownership.proposed, None); -} diff --git a/schema.Makefile.toml b/schema.Makefile.toml index 7a1ffc2d6..75547d6a9 100644 --- a/schema.Makefile.toml +++ b/schema.Makefile.toml @@ -14,9 +14,7 @@ fn main() -> std::io::Result<()> { "mars-credit-manager", "mars-account-nft", "mars-swapper-base", - "mars-swapper-osmosis", "mars-v2-zapper-base", - "mars-v3-zapper-base", "mars-mock-red-bank", "mars-mock-vault", "mars-mock-oracle", diff --git a/schemas/mars-swapper-osmosis/mars-swapper-osmosis.json b/schemas/mars-swapper-osmosis/mars-swapper-osmosis.json deleted file mode 100644 index 83be6070f..000000000 --- a/schemas/mars-swapper-osmosis/mars-swapper-osmosis.json +++ /dev/null @@ -1,513 +0,0 @@ -{ - "contract_name": "mars-swapper-osmosis", - "contract_version": "2.0.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "owner" - ], - "properties": { - "owner": { - "description": "The contract's owner, who can update config", - "type": "string" - } - }, - "additionalProperties": false - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Manges owner role state", - "type": "object", - "required": [ - "update_owner" - ], - "properties": { - "update_owner": { - "$ref": "#/definitions/OwnerUpdate" - } - }, - "additionalProperties": false - }, - { - "description": "Configure the route for swapping an asset\n\nThis is chain-specific, and can include parameters such as slippage tolerance and the routes for multi-step swaps", - "type": "object", - "required": [ - "set_route" - ], - "properties": { - "set_route": { - "type": "object", - "required": [ - "denom_in", - "denom_out", - "route" - ], - "properties": { - "denom_in": { - "type": "string" - }, - "denom_out": { - "type": "string" - }, - "route": { - "$ref": "#/definitions/OsmosisRoute" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %.", - "type": "object", - "required": [ - "swap_exact_in" - ], - "properties": { - "swap_exact_in": { - "type": "object", - "required": [ - "coin_in", - "denom_out", - "slippage" - ], - "properties": { - "coin_in": { - "$ref": "#/definitions/Coin" - }, - "denom_out": { - "type": "string" - }, - "slippage": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Send swapper results back to swapper. Also refunds extra if sent more than needed. Internal use only.", - "type": "object", - "required": [ - "transfer_result" - ], - "properties": { - "transfer_result": { - "type": "object", - "required": [ - "denom_in", - "denom_out", - "recipient" - ], - "properties": { - "denom_in": { - "type": "string" - }, - "denom_out": { - "type": "string" - }, - "recipient": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "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" - }, - "OsmosisRoute": { - "type": "array", - "items": { - "$ref": "#/definitions/SwapAmountInRoute" - } - }, - "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" - ] - }, - { - "description": "A separate entity managed by Owner that can be used for granting specific emergency powers.", - "type": "object", - "required": [ - "set_emergency_owner" - ], - "properties": { - "set_emergency_owner": { - "type": "object", - "required": [ - "emergency_owner" - ], - "properties": { - "emergency_owner": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Remove the entity in the Emergency Owner role", - "type": "string", - "enum": [ - "clear_emergency_owner" - ] - } - ] - }, - "SwapAmountInRoute": { - "type": "object", - "required": [ - "pool_id", - "token_out_denom" - ], - "properties": { - "pool_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "token_out_denom": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "description": "Query contract owner config", - "type": "object", - "required": [ - "owner" - ], - "properties": { - "owner": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Get route for swapping an input denom into an output denom", - "type": "object", - "required": [ - "route" - ], - "properties": { - "route": { - "type": "object", - "required": [ - "denom_in", - "denom_out" - ], - "properties": { - "denom_in": { - "type": "string" - }, - "denom_out": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Enumerate all swapper routes", - "type": "object", - "required": [ - "routes" - ], - "properties": { - "routes": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "array", - "null" - ], - "items": [ - { - "type": "string" - }, - { - "type": "string" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Return current spot price swapping In for Out Warning: Do not use this as an oracle price feed. Use Mars-Oracle for pricing.", - "type": "object", - "required": [ - "estimate_exact_in_swap" - ], - "properties": { - "estimate_exact_in_swap": { - "type": "object", - "required": [ - "coin_in", - "denom_out" - ], - "properties": { - "coin_in": { - "$ref": "#/definitions/Coin" - }, - "denom_out": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "migrate": null, - "sudo": null, - "responses": { - "estimate_exact_in_swap": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "EstimateExactInSwapResponse", - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "owner": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OwnerResponse", - "description": "Returned from Owner.query()", - "type": "object", - "required": [ - "abolished", - "initialized" - ], - "properties": { - "abolished": { - "type": "boolean" - }, - "emergency_owner": { - "type": [ - "string", - "null" - ] - }, - "initialized": { - "type": "boolean" - }, - "owner": { - "type": [ - "string", - "null" - ] - }, - "proposed": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "route": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "RouteResponse_for_Empty", - "type": "object", - "required": [ - "denom_in", - "denom_out", - "route" - ], - "properties": { - "denom_in": { - "type": "string" - }, - "denom_out": { - "type": "string" - }, - "route": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false, - "definitions": { - "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" - } - } - }, - "routes": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_RouteResponse_for_Empty", - "type": "array", - "items": { - "$ref": "#/definitions/RouteResponse_for_Empty" - }, - "definitions": { - "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" - }, - "RouteResponse_for_Empty": { - "type": "object", - "required": [ - "denom_in", - "denom_out", - "route" - ], - "properties": { - "denom_in": { - "type": "string" - }, - "denom_out": { - "type": "string" - }, - "route": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - } - } - } - } -} diff --git a/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json b/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json deleted file mode 100644 index ce2b2ce36..000000000 --- a/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json +++ /dev/null @@ -1,294 +0,0 @@ -{ - "contract_name": "mars-v3-zapper-base", - "contract_version": "2.0.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "owner" - ], - "properties": { - "owner": { - "type": "string" - } - }, - "additionalProperties": false - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Expects the corresponding tokens to be sent in Funds. The position created will be owned by the zapper contract itself. Consumer should expect an event with V3_POSITION_CREATED_EVENT_TYPE & V3_POSITION_ATTR_KEY, used to parse the id of the position created. In the event not all funds are issued, the remaining is refunded to the caller.", - "type": "object", - "required": [ - "create_position" - ], - "properties": { - "create_position": { - "$ref": "#/definitions/NewPositionRequest" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "update_owner" - ], - "properties": { - "update_owner": { - "$ref": "#/definitions/OwnerUpdate" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "callback" - ], - "properties": { - "callback": { - "$ref": "#/definitions/CallbackMsg" - } - }, - "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" - }, - "CallbackMsg": { - "oneOf": [ - { - "type": "object", - "required": [ - "refund_coin" - ], - "properties": { - "refund_coin": { - "type": "object", - "required": [ - "denoms", - "recipient" - ], - "properties": { - "denoms": { - "type": "array", - "items": { - "type": "string" - } - }, - "recipient": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "NewPositionRequest": { - "type": "object", - "required": [ - "lower_tick", - "pool_id", - "token_min_amount0", - "token_min_amount1", - "tokens_provided", - "upper_tick" - ], - "properties": { - "lower_tick": { - "type": "integer", - "format": "int64" - }, - "pool_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "token_min_amount0": { - "type": "string" - }, - "token_min_amount1": { - "type": "string" - }, - "tokens_provided": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "upper_tick": { - "type": "integer", - "format": "int64" - } - }, - "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" - ] - }, - { - "description": "A separate entity managed by Owner that can be used for granting specific emergency powers.", - "type": "object", - "required": [ - "set_emergency_owner" - ], - "properties": { - "set_emergency_owner": { - "type": "object", - "required": [ - "emergency_owner" - ], - "properties": { - "emergency_owner": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Remove the entity in the Emergency Owner role", - "type": "string", - "enum": [ - "clear_emergency_owner" - ] - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "owner": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "migrate": null, - "sudo": null, - "responses": { - "owner": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OwnerResponse", - "description": "Returned from Owner.query()", - "type": "object", - "required": [ - "abolished", - "initialized" - ], - "properties": { - "abolished": { - "type": "boolean" - }, - "emergency_owner": { - "type": [ - "string", - "null" - ] - }, - "initialized": { - "type": "boolean" - }, - "owner": { - "type": [ - "string", - "null" - ] - }, - "proposed": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - } -} diff --git a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client.ts b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client.ts deleted file mode 100644 index 3956ae6b2..000000000 --- a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client.ts +++ /dev/null @@ -1,288 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee } from '@cosmjs/amino' -import { - InstantiateMsg, - ExecuteMsg, - OwnerUpdate, - OsmosisRoute, - Uint128, - Decimal, - Addr, - SwapAmountInRoute, - Coin, - QueryMsg, - EstimateExactInSwapResponse, - OwnerResponse, - RouteResponseForEmpty, - Empty, - ArrayOfRouteResponseForEmpty, -} from './MarsSwapperOsmosis.types' -export interface MarsSwapperOsmosisReadOnlyInterface { - contractAddress: string - owner: () => Promise - route: ({ - denomIn, - denomOut, - }: { - denomIn: string - denomOut: string - }) => Promise - routes: ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string[][] - }) => Promise - estimateExactInSwap: ({ - coinIn, - denomOut, - }: { - coinIn: Coin - denomOut: string - }) => Promise -} -export class MarsSwapperOsmosisQueryClient implements MarsSwapperOsmosisReadOnlyInterface { - client: CosmWasmClient - contractAddress: string - - constructor(client: CosmWasmClient, contractAddress: string) { - this.client = client - this.contractAddress = contractAddress - this.owner = this.owner.bind(this) - this.route = this.route.bind(this) - this.routes = this.routes.bind(this) - this.estimateExactInSwap = this.estimateExactInSwap.bind(this) - } - - owner = async (): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - owner: {}, - }) - } - route = async ({ - denomIn, - denomOut, - }: { - denomIn: string - denomOut: string - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - route: { - denom_in: denomIn, - denom_out: denomOut, - }, - }) - } - routes = async ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string[][] - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - routes: { - limit, - start_after: startAfter, - }, - }) - } - estimateExactInSwap = async ({ - coinIn, - denomOut, - }: { - coinIn: Coin - denomOut: string - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - estimate_exact_in_swap: { - coin_in: coinIn, - denom_out: denomOut, - }, - }) - } -} -export interface MarsSwapperOsmosisInterface extends MarsSwapperOsmosisReadOnlyInterface { - contractAddress: string - sender: string - updateOwner: ( - ownerUpdate: OwnerUpdate, - fee?: number | StdFee | 'auto', - memo?: string, - _funds?: Coin[], - ) => Promise - setRoute: ( - { - denomIn, - denomOut, - route, - }: { - denomIn: string - denomOut: string - route: OsmosisRoute - }, - fee?: number | StdFee | 'auto', - memo?: string, - _funds?: Coin[], - ) => Promise - swapExactIn: ( - { - coinIn, - denomOut, - slippage, - }: { - coinIn: Coin - denomOut: string - slippage: Decimal - }, - fee?: number | StdFee | 'auto', - memo?: string, - _funds?: Coin[], - ) => Promise - transferResult: ( - { - denomIn, - denomOut, - recipient, - }: { - denomIn: string - denomOut: string - recipient: Addr - }, - fee?: number | StdFee | 'auto', - memo?: string, - _funds?: Coin[], - ) => Promise -} -export class MarsSwapperOsmosisClient - extends MarsSwapperOsmosisQueryClient - implements MarsSwapperOsmosisInterface -{ - client: SigningCosmWasmClient - sender: string - contractAddress: string - - constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { - super(client, contractAddress) - this.client = client - this.sender = sender - this.contractAddress = contractAddress - this.updateOwner = this.updateOwner.bind(this) - this.setRoute = this.setRoute.bind(this) - this.swapExactIn = this.swapExactIn.bind(this) - this.transferResult = this.transferResult.bind(this) - } - - updateOwner = async ( - ownerUpdate: OwnerUpdate, - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - _funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - update_owner: ownerUpdate, - }, - fee, - memo, - _funds, - ) - } - setRoute = async ( - { - denomIn, - denomOut, - route, - }: { - denomIn: string - denomOut: string - route: OsmosisRoute - }, - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - _funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - set_route: { - denom_in: denomIn, - denom_out: denomOut, - route, - }, - }, - fee, - memo, - _funds, - ) - } - swapExactIn = async ( - { - coinIn, - denomOut, - slippage, - }: { - coinIn: Coin - denomOut: string - slippage: Decimal - }, - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - _funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - swap_exact_in: { - coin_in: coinIn, - denom_out: denomOut, - slippage, - }, - }, - fee, - memo, - _funds, - ) - } - transferResult = async ( - { - denomIn, - denomOut, - recipient, - }: { - denomIn: string - denomOut: string - recipient: Addr - }, - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - _funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - transfer_result: { - denom_in: denomIn, - denom_out: denomOut, - recipient, - }, - }, - fee, - memo, - _funds, - ) - } -} diff --git a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.message-composer.ts b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.message-composer.ts deleted file mode 100644 index 976853c40..000000000 --- a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.message-composer.ts +++ /dev/null @@ -1,187 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' -import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' -import { toUtf8 } from '@cosmjs/encoding' -import { - InstantiateMsg, - ExecuteMsg, - OwnerUpdate, - OsmosisRoute, - Uint128, - Decimal, - Addr, - SwapAmountInRoute, - Coin, - QueryMsg, - EstimateExactInSwapResponse, - OwnerResponse, - RouteResponseForEmpty, - Empty, - ArrayOfRouteResponseForEmpty, -} from './MarsSwapperOsmosis.types' -export interface MarsSwapperOsmosisMessage { - contractAddress: string - sender: string - updateOwner: (ownerUpdate: OwnerUpdate, _funds?: Coin[]) => MsgExecuteContractEncodeObject - setRoute: ( - { - denomIn, - denomOut, - route, - }: { - denomIn: string - denomOut: string - route: OsmosisRoute - }, - _funds?: Coin[], - ) => MsgExecuteContractEncodeObject - swapExactIn: ( - { - coinIn, - denomOut, - slippage, - }: { - coinIn: Coin - denomOut: string - slippage: Decimal - }, - _funds?: Coin[], - ) => MsgExecuteContractEncodeObject - transferResult: ( - { - denomIn, - denomOut, - recipient, - }: { - denomIn: string - denomOut: string - recipient: Addr - }, - _funds?: Coin[], - ) => MsgExecuteContractEncodeObject -} -export class MarsSwapperOsmosisMessageComposer implements MarsSwapperOsmosisMessage { - sender: string - contractAddress: string - - constructor(sender: string, contractAddress: string) { - this.sender = sender - this.contractAddress = contractAddress - this.updateOwner = this.updateOwner.bind(this) - this.setRoute = this.setRoute.bind(this) - this.swapExactIn = this.swapExactIn.bind(this) - this.transferResult = this.transferResult.bind(this) - } - - updateOwner = (ownerUpdate: OwnerUpdate, _funds?: Coin[]): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - update_owner: ownerUpdate, - }), - ), - funds: _funds, - }), - } - } - setRoute = ( - { - denomIn, - denomOut, - route, - }: { - denomIn: string - denomOut: string - route: OsmosisRoute - }, - _funds?: Coin[], - ): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - set_route: { - denom_in: denomIn, - denom_out: denomOut, - route, - }, - }), - ), - funds: _funds, - }), - } - } - swapExactIn = ( - { - coinIn, - denomOut, - slippage, - }: { - coinIn: Coin - denomOut: string - slippage: Decimal - }, - _funds?: Coin[], - ): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - swap_exact_in: { - coin_in: coinIn, - denom_out: denomOut, - slippage, - }, - }), - ), - funds: _funds, - }), - } - } - transferResult = ( - { - denomIn, - denomOut, - recipient, - }: { - denomIn: string - denomOut: string - recipient: Addr - }, - _funds?: Coin[], - ): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - transfer_result: { - denom_in: denomIn, - denom_out: denomOut, - recipient, - }, - }), - ), - funds: _funds, - }), - } - } -} diff --git a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.react-query.ts b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.react-query.ts deleted file mode 100644 index 5233cb560..000000000 --- a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.react-query.ts +++ /dev/null @@ -1,246 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' -import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee } from '@cosmjs/amino' -import { - InstantiateMsg, - ExecuteMsg, - OwnerUpdate, - OsmosisRoute, - Uint128, - Decimal, - Addr, - SwapAmountInRoute, - Coin, - QueryMsg, - EstimateExactInSwapResponse, - OwnerResponse, - RouteResponseForEmpty, - Empty, - ArrayOfRouteResponseForEmpty, -} from './MarsSwapperOsmosis.types' -import { - MarsSwapperOsmosisQueryClient, - MarsSwapperOsmosisClient, -} from './MarsSwapperOsmosis.client' -export const marsSwapperOsmosisQueryKeys = { - contract: [ - { - contract: 'marsSwapperOsmosis', - }, - ] as const, - address: (contractAddress: string | undefined) => - [{ ...marsSwapperOsmosisQueryKeys.contract[0], address: contractAddress }] as const, - owner: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsSwapperOsmosisQueryKeys.address(contractAddress)[0], method: 'owner', args }, - ] as const, - route: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsSwapperOsmosisQueryKeys.address(contractAddress)[0], method: 'route', args }, - ] as const, - routes: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsSwapperOsmosisQueryKeys.address(contractAddress)[0], method: 'routes', args }, - ] as const, - estimateExactInSwap: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsSwapperOsmosisQueryKeys.address(contractAddress)[0], - method: 'estimate_exact_in_swap', - args, - }, - ] as const, -} -export interface MarsSwapperOsmosisReactQuery { - client: MarsSwapperOsmosisQueryClient | undefined - options?: Omit< - UseQueryOptions, - "'queryKey' | 'queryFn' | 'initialData'" - > & { - initialData?: undefined - } -} -export interface MarsSwapperOsmosisEstimateExactInSwapQuery - extends MarsSwapperOsmosisReactQuery { - args: { - coinIn: Coin - denomOut: string - } -} -export function useMarsSwapperOsmosisEstimateExactInSwapQuery({ - client, - args, - options, -}: MarsSwapperOsmosisEstimateExactInSwapQuery) { - return useQuery( - marsSwapperOsmosisQueryKeys.estimateExactInSwap(client?.contractAddress, args), - () => - client - ? client.estimateExactInSwap({ - coinIn: args.coinIn, - denomOut: args.denomOut, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsSwapperOsmosisRoutesQuery - extends MarsSwapperOsmosisReactQuery { - args: { - limit?: number - startAfter?: string[][] - } -} -export function useMarsSwapperOsmosisRoutesQuery({ - client, - args, - options, -}: MarsSwapperOsmosisRoutesQuery) { - return useQuery( - marsSwapperOsmosisQueryKeys.routes(client?.contractAddress, args), - () => - client - ? client.routes({ - limit: args.limit, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsSwapperOsmosisRouteQuery - extends MarsSwapperOsmosisReactQuery { - args: { - denomIn: string - denomOut: string - } -} -export function useMarsSwapperOsmosisRouteQuery({ - client, - args, - options, -}: MarsSwapperOsmosisRouteQuery) { - return useQuery( - marsSwapperOsmosisQueryKeys.route(client?.contractAddress, args), - () => - client - ? client.route({ - denomIn: args.denomIn, - denomOut: args.denomOut, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsSwapperOsmosisOwnerQuery - extends MarsSwapperOsmosisReactQuery {} -export function useMarsSwapperOsmosisOwnerQuery({ - client, - options, -}: MarsSwapperOsmosisOwnerQuery) { - return useQuery( - marsSwapperOsmosisQueryKeys.owner(client?.contractAddress), - () => (client ? client.owner() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsSwapperOsmosisTransferResultMutation { - client: MarsSwapperOsmosisClient - msg: { - denomIn: string - denomOut: string - recipient: Addr - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsSwapperOsmosisTransferResultMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => - client.transferResult(msg, fee, memo, funds), - options, - ) -} -export interface MarsSwapperOsmosisSwapExactInMutation { - client: MarsSwapperOsmosisClient - msg: { - coinIn: Coin - denomOut: string - slippage: Decimal - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsSwapperOsmosisSwapExactInMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.swapExactIn(msg, fee, memo, funds), - options, - ) -} -export interface MarsSwapperOsmosisSetRouteMutation { - client: MarsSwapperOsmosisClient - msg: { - denomIn: string - denomOut: string - route: OsmosisRoute - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsSwapperOsmosisSetRouteMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.setRoute(msg, fee, memo, funds), - options, - ) -} -export interface MarsSwapperOsmosisUpdateOwnerMutation { - client: MarsSwapperOsmosisClient - msg: OwnerUpdate - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsSwapperOsmosisUpdateOwnerMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateOwner(msg, fee, memo, funds), - options, - ) -} diff --git a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.types.ts b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.types.ts deleted file mode 100644 index fcba8dcc6..000000000 --- a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.types.ts +++ /dev/null @@ -1,105 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -export interface InstantiateMsg { - owner: string -} -export type ExecuteMsg = - | { - update_owner: OwnerUpdate - } - | { - set_route: { - denom_in: string - denom_out: string - route: OsmosisRoute - } - } - | { - swap_exact_in: { - coin_in: Coin - denom_out: string - slippage: Decimal - } - } - | { - transfer_result: { - denom_in: string - denom_out: string - recipient: Addr - } - } -export type OwnerUpdate = - | { - propose_new_owner: { - proposed: string - } - } - | 'clear_proposed' - | 'accept_proposed' - | 'abolish_owner_role' - | { - set_emergency_owner: { - emergency_owner: string - } - } - | 'clear_emergency_owner' -export type OsmosisRoute = SwapAmountInRoute[] -export type Uint128 = string -export type Decimal = string -export type Addr = string -export interface SwapAmountInRoute { - pool_id: number - token_out_denom: string - [k: string]: unknown -} -export interface Coin { - amount: Uint128 - denom: string - [k: string]: unknown -} -export type QueryMsg = - | { - owner: {} - } - | { - route: { - denom_in: string - denom_out: string - } - } - | { - routes: { - limit?: number | null - start_after?: [string, string] | null - } - } - | { - estimate_exact_in_swap: { - coin_in: Coin - denom_out: string - } - } -export interface EstimateExactInSwapResponse { - amount: Uint128 -} -export interface OwnerResponse { - abolished: boolean - emergency_owner?: string | null - initialized: boolean - owner?: string | null - proposed?: string | null -} -export interface RouteResponseForEmpty { - denom_in: string - denom_out: string - route: Empty -} -export interface Empty { - [k: string]: unknown -} -export type ArrayOfRouteResponseForEmpty = RouteResponseForEmpty[] diff --git a/scripts/types/generated/mars-swapper-osmosis/bundle.ts b/scripts/types/generated/mars-swapper-osmosis/bundle.ts deleted file mode 100644 index 82c4e575c..000000000 --- a/scripts/types/generated/mars-swapper-osmosis/bundle.ts +++ /dev/null @@ -1,14 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import * as _44 from './MarsSwapperOsmosis.types' -import * as _45 from './MarsSwapperOsmosis.client' -import * as _46 from './MarsSwapperOsmosis.message-composer' -import * as _47 from './MarsSwapperOsmosis.react-query' -export namespace contracts { - export const MarsSwapperOsmosis = { ..._44, ..._45, ..._46, ..._47 } -} diff --git a/scripts/types/generated/mars-v2-zapper-base/bundle.ts b/scripts/types/generated/mars-v2-zapper-base/bundle.ts index 0de79da53..bde1a9130 100644 --- a/scripts/types/generated/mars-v2-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-v2-zapper-base/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _48 from './MarsV2ZapperBase.types' -import * as _49 from './MarsV2ZapperBase.client' -import * as _50 from './MarsV2ZapperBase.message-composer' -import * as _51 from './MarsV2ZapperBase.react-query' +import * as _44 from './MarsV2ZapperBase.types' +import * as _45 from './MarsV2ZapperBase.client' +import * as _46 from './MarsV2ZapperBase.message-composer' +import * as _47 from './MarsV2ZapperBase.react-query' export namespace contracts { - export const MarsV2ZapperBase = { ..._48, ..._49, ..._50, ..._51 } + export const MarsV2ZapperBase = { ..._44, ..._45, ..._46, ..._47 } } diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts deleted file mode 100644 index 9f853c576..000000000 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts +++ /dev/null @@ -1,168 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee } from '@cosmjs/amino' -import { - InstantiateMsg, - ExecuteMsg, - Uint128, - OwnerUpdate, - CallbackMsg, - Addr, - NewPositionRequest, - Coin, - QueryMsg, - OwnerResponse, -} from './MarsV3ZapperBase.types' -export interface MarsV3ZapperBaseReadOnlyInterface { - contractAddress: string - owner: () => Promise -} -export class MarsV3ZapperBaseQueryClient implements MarsV3ZapperBaseReadOnlyInterface { - client: CosmWasmClient - contractAddress: string - - constructor(client: CosmWasmClient, contractAddress: string) { - this.client = client - this.contractAddress = contractAddress - this.owner = this.owner.bind(this) - } - - owner = async (): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - owner: {}, - }) - } -} -export interface MarsV3ZapperBaseInterface extends MarsV3ZapperBaseReadOnlyInterface { - contractAddress: string - sender: string - createPosition: ( - { - lowerTick, - poolId, - tokenMinAmount0, - tokenMinAmount1, - tokensProvided, - upperTick, - }: { - lowerTick: number - poolId: number - tokenMinAmount0: string - tokenMinAmount1: string - tokensProvided: Coin[] - upperTick: number - }, - fee?: number | StdFee | 'auto', - memo?: string, - _funds?: Coin[], - ) => Promise - updateOwner: ( - ownerUpdate: OwnerUpdate, - fee?: number | StdFee | 'auto', - memo?: string, - _funds?: Coin[], - ) => Promise - callback: ( - callbackMsg: CallbackMsg, - fee?: number | StdFee | 'auto', - memo?: string, - _funds?: Coin[], - ) => Promise -} -export class MarsV3ZapperBaseClient - extends MarsV3ZapperBaseQueryClient - implements MarsV3ZapperBaseInterface -{ - client: SigningCosmWasmClient - sender: string - contractAddress: string - - constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { - super(client, contractAddress) - this.client = client - this.sender = sender - this.contractAddress = contractAddress - this.createPosition = this.createPosition.bind(this) - this.updateOwner = this.updateOwner.bind(this) - this.callback = this.callback.bind(this) - } - - createPosition = async ( - { - lowerTick, - poolId, - tokenMinAmount0, - tokenMinAmount1, - tokensProvided, - upperTick, - }: { - lowerTick: number - poolId: number - tokenMinAmount0: string - tokenMinAmount1: string - tokensProvided: Coin[] - upperTick: number - }, - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - _funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - create_position: { - lower_tick: lowerTick, - pool_id: poolId, - token_min_amount0: tokenMinAmount0, - token_min_amount1: tokenMinAmount1, - tokens_provided: tokensProvided, - upper_tick: upperTick, - }, - }, - fee, - memo, - _funds, - ) - } - updateOwner = async ( - ownerUpdate: OwnerUpdate, - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - _funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - update_owner: ownerUpdate, - }, - fee, - memo, - _funds, - ) - } - callback = async ( - callbackMsg: CallbackMsg, - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - _funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - callback: callbackMsg, - }, - fee, - memo, - _funds, - ) - } -} diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts deleted file mode 100644 index 4926738b8..000000000 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts +++ /dev/null @@ -1,128 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' -import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' -import { toUtf8 } from '@cosmjs/encoding' -import { - InstantiateMsg, - ExecuteMsg, - Uint128, - OwnerUpdate, - CallbackMsg, - Addr, - NewPositionRequest, - Coin, - QueryMsg, - OwnerResponse, -} from './MarsV3ZapperBase.types' -export interface MarsV3ZapperBaseMessage { - contractAddress: string - sender: string - createPosition: ( - { - lowerTick, - poolId, - tokenMinAmount0, - tokenMinAmount1, - tokensProvided, - upperTick, - }: { - lowerTick: number - poolId: number - tokenMinAmount0: string - tokenMinAmount1: string - tokensProvided: Coin[] - upperTick: number - }, - _funds?: Coin[], - ) => MsgExecuteContractEncodeObject - updateOwner: (ownerUpdate: OwnerUpdate, _funds?: Coin[]) => MsgExecuteContractEncodeObject - callback: (callbackMsg: CallbackMsg, _funds?: Coin[]) => MsgExecuteContractEncodeObject -} -export class MarsV3ZapperBaseMessageComposer implements MarsV3ZapperBaseMessage { - sender: string - contractAddress: string - - constructor(sender: string, contractAddress: string) { - this.sender = sender - this.contractAddress = contractAddress - this.createPosition = this.createPosition.bind(this) - this.updateOwner = this.updateOwner.bind(this) - this.callback = this.callback.bind(this) - } - - createPosition = ( - { - lowerTick, - poolId, - tokenMinAmount0, - tokenMinAmount1, - tokensProvided, - upperTick, - }: { - lowerTick: number - poolId: number - tokenMinAmount0: string - tokenMinAmount1: string - tokensProvided: Coin[] - upperTick: number - }, - _funds?: Coin[], - ): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - create_position: { - lower_tick: lowerTick, - pool_id: poolId, - token_min_amount0: tokenMinAmount0, - token_min_amount1: tokenMinAmount1, - tokens_provided: tokensProvided, - upper_tick: upperTick, - }, - }), - ), - funds: _funds, - }), - } - } - updateOwner = (ownerUpdate: OwnerUpdate, _funds?: Coin[]): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - update_owner: ownerUpdate, - }), - ), - funds: _funds, - }), - } - } - callback = (callbackMsg: CallbackMsg, _funds?: Coin[]): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - callback: callbackMsg, - }), - ), - funds: _funds, - }), - } - } -} diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts deleted file mode 100644 index be8d0055f..000000000 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts +++ /dev/null @@ -1,123 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' -import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' -import { StdFee } from '@cosmjs/amino' -import { - InstantiateMsg, - ExecuteMsg, - Uint128, - OwnerUpdate, - CallbackMsg, - Addr, - NewPositionRequest, - Coin, - QueryMsg, - OwnerResponse, -} from './MarsV3ZapperBase.types' -import { MarsV3ZapperBaseQueryClient, MarsV3ZapperBaseClient } from './MarsV3ZapperBase.client' -export const marsV3ZapperBaseQueryKeys = { - contract: [ - { - contract: 'marsV3ZapperBase', - }, - ] as const, - address: (contractAddress: string | undefined) => - [{ ...marsV3ZapperBaseQueryKeys.contract[0], address: contractAddress }] as const, - owner: (contractAddress: string | undefined, args?: Record) => - [{ ...marsV3ZapperBaseQueryKeys.address(contractAddress)[0], method: 'owner', args }] as const, -} -export interface MarsV3ZapperBaseReactQuery { - client: MarsV3ZapperBaseQueryClient | undefined - options?: Omit< - UseQueryOptions, - "'queryKey' | 'queryFn' | 'initialData'" - > & { - initialData?: undefined - } -} -export interface MarsV3ZapperBaseOwnerQuery - extends MarsV3ZapperBaseReactQuery {} -export function useMarsV3ZapperBaseOwnerQuery({ - client, - options, -}: MarsV3ZapperBaseOwnerQuery) { - return useQuery( - marsV3ZapperBaseQueryKeys.owner(client?.contractAddress), - () => (client ? client.owner() : Promise.reject(new Error('Invalid client'))), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsV3ZapperBaseCallbackMutation { - client: MarsV3ZapperBaseClient - msg: CallbackMsg - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsV3ZapperBaseCallbackMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.callback(msg, fee, memo, funds), - options, - ) -} -export interface MarsV3ZapperBaseUpdateOwnerMutation { - client: MarsV3ZapperBaseClient - msg: OwnerUpdate - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsV3ZapperBaseUpdateOwnerMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateOwner(msg, fee, memo, funds), - options, - ) -} -export interface MarsV3ZapperBaseCreatePositionMutation { - client: MarsV3ZapperBaseClient - msg: { - lowerTick: number - poolId: number - tokenMinAmount0: string - tokenMinAmount1: string - tokensProvided: Coin[] - upperTick: number - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsV3ZapperBaseCreatePositionMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => - client.createPosition(msg, fee, memo, funds), - options, - ) -} diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts deleted file mode 100644 index 2004e3271..000000000 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts +++ /dev/null @@ -1,66 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -export interface InstantiateMsg { - owner: string -} -export type ExecuteMsg = - | { - create_position: NewPositionRequest - } - | { - update_owner: OwnerUpdate - } - | { - callback: CallbackMsg - } -export type Uint128 = string -export type OwnerUpdate = - | { - propose_new_owner: { - proposed: string - } - } - | 'clear_proposed' - | 'accept_proposed' - | 'abolish_owner_role' - | { - set_emergency_owner: { - emergency_owner: string - } - } - | 'clear_emergency_owner' -export type CallbackMsg = { - refund_coin: { - denoms: string[] - recipient: Addr - } -} -export type Addr = string -export interface NewPositionRequest { - lower_tick: number - pool_id: number - token_min_amount0: string - token_min_amount1: string - tokens_provided: Coin[] - upper_tick: number -} -export interface Coin { - amount: Uint128 - denom: string - [k: string]: unknown -} -export type QueryMsg = { - owner: {} -} -export interface OwnerResponse { - abolished: boolean - emergency_owner?: string | null - initialized: boolean - owner?: string | null - proposed?: string | null -} diff --git a/scripts/types/generated/mars-v3-zapper-base/bundle.ts b/scripts/types/generated/mars-v3-zapper-base/bundle.ts deleted file mode 100644 index 9770f90f3..000000000 --- a/scripts/types/generated/mars-v3-zapper-base/bundle.ts +++ /dev/null @@ -1,14 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.33.0. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import * as _52 from './MarsV3ZapperBase.types' -import * as _53 from './MarsV3ZapperBase.client' -import * as _54 from './MarsV3ZapperBase.message-composer' -import * as _55 from './MarsV3ZapperBase.react-query' -export namespace contracts { - export const MarsV3ZapperBase = { ..._52, ..._53, ..._54, ..._55 } -} From edbd76bda128e2679fdfd7a18b0e7b2027b7094f Mon Sep 17 00:00:00 2001 From: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com> Date: Tue, 25 Jul 2023 15:30:50 +0200 Subject: [PATCH 185/218] Mp 3156 health computer max borrow to wallet (#164) * add max borrow to wallet to HealthComputer * compile health-computer for frontend * [health computer]: update tests * [health computer] combine prop test files for borrow * [health computer] add ts types --- .../health-computer/src/health_computer.rs | 50 +++++++---- packages/health-computer/src/javascript.rs | 11 ++- packages/health-computer/tests/helpers/mod.rs | 3 +- .../prop_test_runner.rs} | 12 ++- .../tests/test_max_borrow_deposit.rs | 76 ++++++++++++++++ .../tests/test_max_borrow_prop.rs | 14 +++ ...orrow.rs => test_max_borrow_validation.rs} | 85 ++---------------- .../tests/test_max_borrow_wallet.rs | 76 ++++++++++++++++ packages/health-types/src/account.rs | 6 ++ scripts/health/DataFetcher.ts | 15 +++- scripts/health/example-node.ts | 4 +- scripts/health/pkg-node/index.d.ts | 3 +- scripts/health/pkg-node/index.js | 16 ++-- scripts/health/pkg-node/index_bg.wasm | Bin 173167 -> 176105 bytes scripts/health/pkg-node/index_bg.wasm.d.ts | 2 +- scripts/health/pkg-web/index.d.ts | 5 +- scripts/health/pkg-web/index.js | 14 +-- scripts/health/pkg-web/index_bg.wasm | Bin 172243 -> 175181 bytes scripts/health/pkg-web/index_bg.wasm.d.ts | 2 +- 19 files changed, 267 insertions(+), 127 deletions(-) rename packages/health-computer/tests/{test_max_borrow_prop_test.rs => helpers/prop_test_runner.rs} (85%) create mode 100644 packages/health-computer/tests/test_max_borrow_deposit.rs create mode 100644 packages/health-computer/tests/test_max_borrow_prop.rs rename packages/health-computer/tests/{test_max_borrow.rs => test_max_borrow_validation.rs} (73%) create mode 100644 packages/health-computer/tests/test_max_borrow_wallet.rs diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index 0e567bbf9..4cf0e2f4a 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -8,7 +8,7 @@ use mars_params::types::{ }; use mars_rover::msg::query::Positions; use mars_rover_health_types::{ - AccountKind, Health, + AccountKind, BorrowTarget, Health, HealthError::{ DenomNotPresent, MissingHLSParams, MissingParams, MissingPrice, MissingVaultConfig, MissingVaultValues, @@ -134,14 +134,14 @@ impl HealthComputer { /// The max this account can borrow of `borrow_denom` and maintain max_ltv >= 1 /// Note: This is an estimate. Guarantees to leave account healthy, but in edge cases, /// due to rounding, it may be slightly too conservative. - pub fn max_borrow_amount_estimate(&self, borrow_denom: &str) -> HealthResult { + pub fn max_borrow_amount_estimate( + &self, + borrow_denom: &str, + target: &BorrowTarget, + ) -> HealthResult { // Given the formula: // max ltv health factor = max ltv adjusted value / debt value // where: max ltv adjusted value = price * amount * max ltv - // The max borrow can be calculated as: - // 1 = (max ltv adjusted value + (borrow denom amount * borrow denom price * borrow denom max ltv)) / (debt value + (borrow denom amount * borrow denom price)) - // Re-arranging this to isolate max borrow amount renders: - // max_borrow_denom_amount = (max_ltv_adjusted_value - debt_value) / (borrow_denom_price * (1 - borrow_denom_max_ltv)) let total_max_ltv_adjusted_value = self.total_collateral_value()?.max_ltv_adjusted_collateral; let debt_value = self.total_debt_value()?; @@ -176,19 +176,35 @@ impl HealthComputer { .cloned() .ok_or(MissingPrice(borrow_denom.to_string()))?; - // The formula in fact looks like this in practice: + // The formulas look like this in practice: // hf = rounddown(roundown(amount * price) * max ltv) / debt value // Which means re-arranging this to isolate borrow amount is an estimate, - // quite close, but never precisely right. For this reason, the - 1 below is meant - // to err on the side of being more conservative vs aggressive. - let max_borrow_amount = total_max_ltv_adjusted_value - .checked_sub(debt_value)? - .checked_sub(Uint128::one())? - .checked_div_floor( - Decimal::one() - .checked_sub(borrow_denom_max_ltv)? - .checked_mul(borrow_denom_price)?, - )?; + // quite close, but never precisely right. For this reason, the - 1 of the formulas + // below are meant to err on the side of being more conservative vs aggressive. + let max_borrow_amount = match target { + // The max borrow for deposit can be calculated as: + // 1 = (max ltv adjusted value + (borrow denom amount * borrow denom price * borrow denom max ltv)) / (debt value + (borrow denom amount * borrow denom price)) + // Re-arranging this to isolate borrow denom amount renders: + // max_borrow_denom_amount = (max_ltv_adjusted_value - debt_value) / (borrow_denom_price * (1 - borrow_denom_max_ltv)) + BorrowTarget::Deposit => total_max_ltv_adjusted_value + .checked_sub(debt_value)? + .checked_sub(Uint128::one())? + .checked_div_floor( + Decimal::one() + .checked_sub(borrow_denom_max_ltv)? + .checked_mul(borrow_denom_price)?, + )?, + + // Borrowing assets to wallet does not count towards collateral. It only adds to debts. + // Hence, the max borrow to wallet can be calculated as: + // 1 = (max ltv adjusted value) / (debt value + (borrow denom amount * borrow denom price)) + // Re-arranging this to isolate borrow denom amount renders: + // borrow denom amount = (max ltv adjusted value - debt_value) / denom_price + BorrowTarget::Wallet => total_max_ltv_adjusted_value + .checked_sub(debt_value)? + .checked_sub(Uint128::one())? + .checked_div_floor(borrow_denom_price)?, + }; Ok(max_borrow_amount) } diff --git a/packages/health-computer/src/javascript.rs b/packages/health-computer/src/javascript.rs index 6605b2d47..3fce3a3bd 100644 --- a/packages/health-computer/src/javascript.rs +++ b/packages/health-computer/src/javascript.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::serde::{de::DeserializeOwned, Serialize}; -use mars_rover_health_types::HealthValuesResponse; +use mars_rover_health_types::{BorrowTarget, HealthValuesResponse}; use wasm_bindgen::prelude::*; use crate::HealthComputer; @@ -21,10 +21,15 @@ pub fn max_withdraw_estimate_js(health_computer: JsValue, withdraw_denom: JsValu } #[wasm_bindgen] -pub fn max_borrow_estimate_js(health_computer: JsValue, borrow_denom: JsValue) -> JsValue { +pub fn max_borrow_estimate_js( + health_computer: JsValue, + borrow_denom: JsValue, + target: JsValue, +) -> JsValue { let c: HealthComputer = deserialize(health_computer); let denom: String = deserialize(borrow_denom); - let max = c.max_borrow_amount_estimate(&denom).unwrap(); + let target: BorrowTarget = deserialize(target); + let max = c.max_borrow_amount_estimate(&denom, &target).unwrap(); serialize(max) } diff --git a/packages/health-computer/tests/helpers/mod.rs b/packages/health-computer/tests/helpers/mod.rs index eeb244934..245d9fdce 100644 --- a/packages/health-computer/tests/helpers/mod.rs +++ b/packages/health-computer/tests/helpers/mod.rs @@ -1,4 +1,5 @@ -pub use self::{mock_coin_info::*, prop_test_strategies::*}; +pub use self::{mock_coin_info::*, prop_test_runner::*, prop_test_strategies::*}; mod mock_coin_info; +mod prop_test_runner; mod prop_test_strategies; diff --git a/packages/health-computer/tests/test_max_borrow_prop_test.rs b/packages/health-computer/tests/helpers/prop_test_runner.rs similarity index 85% rename from packages/health-computer/tests/test_max_borrow_prop_test.rs rename to packages/health-computer/tests/helpers/prop_test_runner.rs index c66d8924e..66d291401 100644 --- a/packages/health-computer/tests/test_max_borrow_prop_test.rs +++ b/packages/health-computer/tests/helpers/prop_test_runner.rs @@ -1,21 +1,19 @@ use cosmwasm_std::{Coin, StdResult, Uint128}; use mars_rover::msg::query::DebtAmount; use mars_rover_health_computer::HealthComputer; +use mars_rover_health_types::BorrowTarget; use proptest::test_runner::{Config, TestRunner}; -use crate::helpers::random_health_computer; +use super::random_health_computer; -pub mod helpers; - -#[test] -fn max_borrow_amount_renders_healthy_max_ltv() { - let config = Config::with_cases(20000); +pub fn max_borrow_prop_test_runner(cases: u32, target: &BorrowTarget) { + let config = Config::with_cases(cases); let mut runner = TestRunner::new(config); runner .run(&random_health_computer(), |h| { let denom_to_borrow = h.denoms_data.params.keys().next().unwrap(); - let max_borrow = h.max_borrow_amount_estimate(denom_to_borrow).unwrap(); + let max_borrow = h.max_borrow_amount_estimate(denom_to_borrow, target).unwrap(); let health_before = h.compute_health().unwrap(); if health_before.is_above_max_ltv() { diff --git a/packages/health-computer/tests/test_max_borrow_deposit.rs b/packages/health-computer/tests/test_max_borrow_deposit.rs new file mode 100644 index 000000000..1b23064cb --- /dev/null +++ b/packages/health-computer/tests/test_max_borrow_deposit.rs @@ -0,0 +1,76 @@ +use std::collections::HashMap; + +use cosmwasm_std::{coin, Uint128}; +use mars_rover::msg::query::Positions; +use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; +use mars_rover_health_types::{AccountKind, BorrowTarget}; + +use crate::helpers::{udai_info, umars_info}; + +pub mod helpers; + +#[test] +fn max_borrow_deposit_offset_good() { + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(udai.denom.clone(), udai.price)]), + params: HashMap::from([(udai.denom.clone(), udai.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &udai.denom)], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let max_borrow_amount = + h.max_borrow_amount_estimate(&udai.denom, &BorrowTarget::Deposit).unwrap(); + assert_eq!(Uint128::new(6763), max_borrow_amount); +} + +#[test] +fn max_borrow_deposit_offset_margin_of_error() { + let umars = umars_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(umars.denom.clone(), umars.price)]), + params: HashMap::from([(umars.denom.clone(), umars.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom)], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let max_borrow_amount = + h.max_borrow_amount_estimate(&umars.denom, &BorrowTarget::Deposit).unwrap(); + + // Normally could be 4800, but conservative offset rounding has a margin of error + assert_eq!(Uint128::new(4795), max_borrow_amount); +} diff --git a/packages/health-computer/tests/test_max_borrow_prop.rs b/packages/health-computer/tests/test_max_borrow_prop.rs new file mode 100644 index 000000000..06490834a --- /dev/null +++ b/packages/health-computer/tests/test_max_borrow_prop.rs @@ -0,0 +1,14 @@ +use helpers::max_borrow_prop_test_runner; +use mars_rover_health_types::BorrowTarget; + +pub mod helpers; + +#[test] +fn max_borrow_amount_deposit_renders_healthy_max_ltv() { + max_borrow_prop_test_runner(2000, &BorrowTarget::Deposit); +} + +#[test] +fn max_borrow_amount_wallet_renders_healthy_max_ltv() { + max_borrow_prop_test_runner(2000, &BorrowTarget::Wallet); +} diff --git a/packages/health-computer/tests/test_max_borrow.rs b/packages/health-computer/tests/test_max_borrow_validation.rs similarity index 73% rename from packages/health-computer/tests/test_max_borrow.rs rename to packages/health-computer/tests/test_max_borrow_validation.rs index 1ca6ac220..9c5d8fe34 100644 --- a/packages/health-computer/tests/test_max_borrow.rs +++ b/packages/health-computer/tests/test_max_borrow_validation.rs @@ -9,7 +9,7 @@ use mars_rover::{ msg::query::{DebtAmount, Positions}, }; use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; -use mars_rover_health_types::{AccountKind, HealthError}; +use mars_rover_health_types::{AccountKind, BorrowTarget, HealthError}; use crate::helpers::{udai_info, umars_info, ustars_info}; @@ -57,7 +57,8 @@ fn missing_borrow_denom_price_data() { vaults_data, }; - let err: HealthError = h.max_borrow_amount_estimate(&udai.denom).unwrap_err(); + let err: HealthError = + h.max_borrow_amount_estimate(&udai.denom, &BorrowTarget::Deposit).unwrap_err(); assert_eq!(err, HealthError::MissingPrice(udai.denom)); } @@ -103,7 +104,8 @@ fn missing_borrow_denom_params() { vaults_data, }; - let err: HealthError = h.max_borrow_amount_estimate(&umars.denom).unwrap_err(); + let err: HealthError = + h.max_borrow_amount_estimate(&umars.denom, &BorrowTarget::Deposit).unwrap_err(); assert_eq!(err, HealthError::MissingParams(umars.denom)); } @@ -152,9 +154,8 @@ fn cannot_borrow_when_unhealthy() { vaults_data, }; - let health = h.compute_health().unwrap(); - assert!(health.max_ltv_health_factor < Some(Decimal::one())); - let max_withdraw_amount = h.max_borrow_amount_estimate(&udai.denom).unwrap(); + let max_withdraw_amount = + h.max_borrow_amount_estimate(&udai.denom, &BorrowTarget::Deposit).unwrap(); assert_eq!(Uint128::zero(), max_withdraw_amount); } @@ -236,76 +237,8 @@ fn hls_influences_max_borrow() { vaults_data, }; - let max_before = h.max_borrow_amount_estimate(&ustars.denom).unwrap(); + let max_before = h.max_borrow_amount_estimate(&ustars.denom, &BorrowTarget::Deposit).unwrap(); h.kind = AccountKind::HighLeveredStrategy; - let max_after = h.max_borrow_amount_estimate(&ustars.denom).unwrap(); + let max_after = h.max_borrow_amount_estimate(&ustars.denom, &BorrowTarget::Deposit).unwrap(); assert!(max_after > max_before); } - -#[test] -fn max_borrow_offset_good() { - let udai = udai_info(); - - let denoms_data = DenomsData { - prices: HashMap::from([(udai.denom.clone(), udai.price)]), - params: HashMap::from([(udai.denom.clone(), udai.params.clone())]), - }; - - let vaults_data = VaultsData { - vault_values: Default::default(), - vault_configs: Default::default(), - }; - - let h = HealthComputer { - kind: AccountKind::Default, - positions: Positions { - account_id: "123".to_string(), - deposits: vec![coin(1200, &udai.denom)], - debts: vec![], - lends: vec![], - vaults: vec![], - }, - denoms_data, - vaults_data, - }; - - let health = h.compute_health().unwrap(); - assert!(health.max_ltv_health_factor < Some(Decimal::one())); - let max_borrow_amount = h.max_borrow_amount_estimate(&udai.denom).unwrap(); - assert_eq!(Uint128::new(6763), max_borrow_amount); -} - -#[test] -fn max_borrow_offset_margin_of_error() { - let umars = umars_info(); - - let denoms_data = DenomsData { - prices: HashMap::from([(umars.denom.clone(), umars.price)]), - params: HashMap::from([(umars.denom.clone(), umars.params.clone())]), - }; - - let vaults_data = VaultsData { - vault_values: Default::default(), - vault_configs: Default::default(), - }; - - let h = HealthComputer { - kind: AccountKind::Default, - positions: Positions { - account_id: "123".to_string(), - deposits: vec![coin(1200, &umars.denom)], - debts: vec![], - lends: vec![], - vaults: vec![], - }, - denoms_data, - vaults_data, - }; - - let health = h.compute_health().unwrap(); - assert!(health.max_ltv_health_factor < Some(Decimal::one())); - let max_borrow_amount = h.max_borrow_amount_estimate(&umars.denom).unwrap(); - - // Normally could be 4800, but conservative offset rounding has a margin of error - assert_eq!(Uint128::new(4795), max_borrow_amount); -} diff --git a/packages/health-computer/tests/test_max_borrow_wallet.rs b/packages/health-computer/tests/test_max_borrow_wallet.rs new file mode 100644 index 000000000..b13c84305 --- /dev/null +++ b/packages/health-computer/tests/test_max_borrow_wallet.rs @@ -0,0 +1,76 @@ +use std::collections::HashMap; + +use cosmwasm_std::{coin, Uint128}; +use mars_rover::msg::query::Positions; +use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; +use mars_rover_health_types::{AccountKind, BorrowTarget}; + +use crate::helpers::{udai_info, umars_info}; + +pub mod helpers; + +#[test] +fn max_borrow_wallet_offset_good() { + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(udai.denom.clone(), udai.price)]), + params: HashMap::from([(udai.denom.clone(), udai.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &udai.denom)], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let max_borrow_amount = + h.max_borrow_amount_estimate(&udai.denom, &BorrowTarget::Wallet).unwrap(); + assert_eq!(Uint128::new(1014), max_borrow_amount); +} + +#[test] +fn max_borrow_wallet_offset_margin_of_error() { + let umars = umars_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(umars.denom.clone(), umars.price)]), + params: HashMap::from([(umars.denom.clone(), umars.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom)], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let max_borrow_amount = + h.max_borrow_amount_estimate(&umars.denom, &BorrowTarget::Wallet).unwrap(); + + // Normally could be 960, but conservative offset rounding has a margin of error + assert_eq!(Uint128::new(959), max_borrow_amount); +} diff --git a/packages/health-types/src/account.rs b/packages/health-types/src/account.rs index fa49c32f3..7ecbea4f4 100644 --- a/packages/health-types/src/account.rs +++ b/packages/health-types/src/account.rs @@ -13,3 +13,9 @@ impl fmt::Display for AccountKind { write!(f, "{:?}", self) } } + +#[cw_serde] +pub enum BorrowTarget { + Deposit, + Wallet, +} diff --git a/scripts/health/DataFetcher.ts b/scripts/health/DataFetcher.ts index bfd6fd839..cd8b8d12a 100644 --- a/scripts/health/DataFetcher.ts +++ b/scripts/health/DataFetcher.ts @@ -12,11 +12,16 @@ import { MarsMockOracleQueryClient } from '../types/generated/mars-mock-oracle/M import { MarsMockVaultQueryClient } from '../types/generated/mars-mock-vault/MarsMockVault.client' import { MarsParamsQueryClient } from '../types/generated/mars-params/MarsParams.client' +export enum BorrowTarget { + DEPOSIT = 'deposit', + WALLET = 'wallet', +} + export class DataFetcher { constructor( private computeHealthFn: (h: HealthComputer) => HealthValuesResponse, private maxWithdrawFn: (h: HealthComputer, denom: string) => string, - private maxBorrowFn: (h: HealthComputer, denom: string) => string, + private maxBorrowFn: (h: HealthComputer, denom: string, target: BorrowTarget) => string, private creditManagerAddr: string, private oracleAddr: string, private paramsAddr: string, @@ -125,9 +130,13 @@ export class DataFetcher { return parseInt(result) } - maxBorrowAmount = async (accountId: string, denom: string): Promise => { + maxBorrowAmount = async ( + accountId: string, + denom: string, + target: BorrowTarget, + ): Promise => { const positions = await this.assembleComputer(accountId) - const result = this.maxBorrowFn(positions, denom) + const result = this.maxBorrowFn(positions, denom, target) return parseInt(result) } } diff --git a/scripts/health/example-node.ts b/scripts/health/example-node.ts index 77e73c954..41d444d3d 100644 --- a/scripts/health/example-node.ts +++ b/scripts/health/example-node.ts @@ -1,4 +1,4 @@ -import { DataFetcher } from './DataFetcher' +import { BorrowTarget, DataFetcher } from './DataFetcher' import { compute_health_js, max_withdraw_estimate_js, max_borrow_estimate_js } from './pkg-node' import { osmosisTestnetConfig } from '../deploy/osmosis/testnet-config' import OsmosisAddresses from '../deploy/addresses/osmo-test-5-testnet-deployer-owner.json' @@ -16,6 +16,6 @@ import OsmosisAddresses from '../deploy/addresses/osmo-test-5-testnet-deployer-o console.log(health) const max_withdraw = await dataFetcher.maxWithdrawAmount('2', 'uosmo') console.log(max_withdraw) - const max_borrow = await dataFetcher.maxBorrowAmount('2', 'uosmo') + const max_borrow = await dataFetcher.maxBorrowAmount('2', 'uosmo', BorrowTarget.DEPOSIT) console.log(max_borrow) })() diff --git a/scripts/health/pkg-node/index.d.ts b/scripts/health/pkg-node/index.d.ts index 9d426fff2..4eb55d35a 100644 --- a/scripts/health/pkg-node/index.d.ts +++ b/scripts/health/pkg-node/index.d.ts @@ -14,6 +14,7 @@ export function max_withdraw_estimate_js(health_computer: any, withdraw_denom: a /** * @param {any} health_computer * @param {any} borrow_denom + * @param {any} target * @returns {any} */ -export function max_borrow_estimate_js(health_computer: any, borrow_denom: any): any +export function max_borrow_estimate_js(health_computer: any, borrow_denom: any, target: any): any diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js index 22cc808d4..45ad2e94b 100644 --- a/scripts/health/pkg-node/index.js +++ b/scripts/health/pkg-node/index.js @@ -229,12 +229,14 @@ module.exports.max_withdraw_estimate_js = function (health_computer, withdraw_de /** * @param {any} health_computer * @param {any} borrow_denom + * @param {any} target * @returns {any} */ -module.exports.max_borrow_estimate_js = function (health_computer, borrow_denom) { +module.exports.max_borrow_estimate_js = function (health_computer, borrow_denom, target) { const ret = wasm.max_borrow_estimate_js( addHeapObject(health_computer), addHeapObject(borrow_denom), + addHeapObject(target), ) return takeObject(ret) } @@ -288,6 +290,12 @@ module.exports.__wbindgen_is_string = function (arg0) { return ret } +module.exports.__wbindgen_boolean_get = function (arg0) { + const v = getObject(arg0) + const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 + return ret +} + module.exports.__wbindgen_is_bigint = function (arg0) { const ret = typeof getObject(arg0) === 'bigint' return ret @@ -303,12 +311,6 @@ module.exports.__wbindgen_jsval_eq = function (arg0, arg1) { return ret } -module.exports.__wbindgen_boolean_get = function (arg0) { - const v = getObject(arg0) - const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 - return ret -} - module.exports.__wbg_new_abda76e883ba8a5f = function () { const ret = new Error() return addHeapObject(ret) diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index 1af8ec37266e78e439f5b846687b8b48ef59701c..14b08ac1823ad82ade0dedb75ca469664571992b 100644 GIT binary patch delta 37109 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 diff --git a/scripts/health/pkg-node/index_bg.wasm.d.ts b/scripts/health/pkg-node/index_bg.wasm.d.ts index da0075780..d1cdc713e 100644 --- a/scripts/health/pkg-node/index_bg.wasm.d.ts +++ b/scripts/health/pkg-node/index_bg.wasm.d.ts @@ -3,7 +3,7 @@ export const memory: WebAssembly.Memory export function compute_health_js(a: number): number export function max_withdraw_estimate_js(a: number, b: number): number -export function max_borrow_estimate_js(a: number, b: number): number +export function max_borrow_estimate_js(a: number, b: number, c: number): number export function allocate(a: number): number export function deallocate(a: number): void export function requires_stargate(): void diff --git a/scripts/health/pkg-web/index.d.ts b/scripts/health/pkg-web/index.d.ts index ad869a928..ff9825e73 100644 --- a/scripts/health/pkg-web/index.d.ts +++ b/scripts/health/pkg-web/index.d.ts @@ -14,9 +14,10 @@ export function max_withdraw_estimate_js(health_computer: any, withdraw_denom: a /** * @param {any} health_computer * @param {any} borrow_denom + * @param {any} target * @returns {any} */ -export function max_borrow_estimate_js(health_computer: any, borrow_denom: any): any +export function max_borrow_estimate_js(health_computer: any, borrow_denom: any, target: any): any export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module @@ -24,7 +25,7 @@ export interface InitOutput { readonly memory: WebAssembly.Memory readonly compute_health_js: (a: number) => number readonly max_withdraw_estimate_js: (a: number, b: number) => number - readonly max_borrow_estimate_js: (a: number, b: number) => number + readonly max_borrow_estimate_js: (a: number, b: number, c: number) => number readonly allocate: (a: number) => number readonly deallocate: (a: number) => void readonly requires_stargate: () => void diff --git a/scripts/health/pkg-web/index.js b/scripts/health/pkg-web/index.js index 34c34d94c..39ba49197 100644 --- a/scripts/health/pkg-web/index.js +++ b/scripts/health/pkg-web/index.js @@ -242,12 +242,14 @@ export function max_withdraw_estimate_js(health_computer, withdraw_denom) { /** * @param {any} health_computer * @param {any} borrow_denom + * @param {any} target * @returns {any} */ -export function max_borrow_estimate_js(health_computer, borrow_denom) { +export function max_borrow_estimate_js(health_computer, borrow_denom, target) { const ret = wasm.max_borrow_estimate_js( addHeapObject(health_computer), addHeapObject(borrow_denom), + addHeapObject(target), ) return takeObject(ret) } @@ -327,6 +329,11 @@ function __wbg_get_imports() { const ret = typeof getObject(arg0) === 'string' return ret } + imports.wbg.__wbindgen_boolean_get = function (arg0) { + const v = getObject(arg0) + const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 + return ret + } imports.wbg.__wbindgen_is_bigint = function (arg0) { const ret = typeof getObject(arg0) === 'bigint' return ret @@ -339,11 +346,6 @@ function __wbg_get_imports() { const ret = getObject(arg0) === getObject(arg1) return ret } - imports.wbg.__wbindgen_boolean_get = function (arg0) { - const v = getObject(arg0) - const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 - return ret - } imports.wbg.__wbg_new_abda76e883ba8a5f = function () { const ret = new Error() return addHeapObject(ret) diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm index f1d563c44b884576a971e7ef34912b3de37fd458..da9085229bc82a6b0bfde80ea3e0df6427714c35 100644 GIT binary patch delta 37543 zcmchA33yaR*7mLH?xd5>auc$zx0?jQj_e?6E;|B(ih#HgAwm*JAnXcszyM)UqZg)7 z8H@}HDk@IUpr8Z=MFjyxWfTzvH3~Wr8O=B-|9k4*+vy756yWE{Od2-2k1eMLk z7Pqh6GDl2l{LaRGUI}K zjQ=Oj6rFiqWmWvnb$mO&>NoZ+`-Yun-?L-vJyzk^!|V7v{NMar{xko9pXNLH_xwx# z6@Q(d;otEec|HFp|C;aOZ}Q#zEI-HJ;tl*?{53$l&EMeP@SpfzUd!)w(EkhUFq`@s zyPsD$4zb7iR`#@?Kfu3Yx7{IAcA`2nE&nV3JDY7L9_OPLvDeuSUdCQw z;Xl6;cC7ln@WqUsG)6}B@}K09Om}!a5iXDB)W2I)tLX76A{{#OI&>!zoF3ifrNsB| zMPdvk@(2e-oTP{(ieM5t1kl_AFMSRG>>z+B0Q8S!9$le0jp^qpHdK9*c@wni$jAml zB_vu)pmNOoA%H4h45$$Gq|rM%nVqa26}_JI+{3)Ch7yK@d(eV3B%+0)_!U&1If$gcjId&ppv8!1eem`pV z71CdgOUCcVaXnb5p~ibzgV8B|7+%XFu?ZrBD8-_x45F0f zCXsg=Kce87w`vCWLDwD7LG8Fl?p;)objONCkNfdGoK*Z~0)WU<0-W1hAV z#L4o0>#q0lFgRVVnq}$GBwFK(BjUf z5Y<<=jHiBK*-6IaoLd8l$(j?{Kg{_N+23x}FDRahO=-OZ@sGE@mp{JLXwl{)cFg#- zO>t!TOTZ^huY#k#Q$43`E(6rd?XvOvIsN|4)t)&&ViBtG{T0K=6HOlWk+G@6_3T6A zmku|;Kdj>>=Z7pJ-1xP_a0CZmah4#R#)yvB8k?fdc4BU8+y+;=YFofYuw!P3SPI|SlLrU?6W<4nY`xl=pUCMbBMa9`sbdDDEHl0 z_Kw}dd>Eh@@_GZMhN`=by;qK*;o$3i9ou9~>-`iUQ?I%OY&rX?Z_%N5UVR}jJ{7N9 z3(+eU%5qOu&%P#>5v5n;4rcEgALbrs%c@_}zhP{dQPp=9O6b~ec6)GsxaRVPMMQga zx5m_CT=D3 L|lVSyg`2SbEa<{smdena`*Wkyc_j;Q6JrXcOl9|5hQBt!t@>i@QwjrPQa%X8eNAp_hZnSIjGX4LcMNttzwpiuv(}e4-UHv z^&`RIgTaBuq8qETP=GM7X$*Ry{q8=>4LaA7NBC29hn6@Z)#tP*toRZ~L_!wmN0dU4 zF`lSf=EKi|`C6j-8Kvw+ildcdj4us=;|euhOYEbl&rl*LlZt#u9~8&{5+U5fhiE;w zs0p|~sSrJqO6fTPc(;U4@+ARY3oc&K|DciRzWDBrBsgi^9V(oJ?hZGcB(rN%s1Emh z^ln6m2L+e$LX2e8?<1}w7>C}#C{8W(g#@DNWt6+n=eDCH|8#fMQERKX)t?T1O{2Fm z;3Xca;Fxq7sGT~3h@F66rxp*Ay9PUR{V1dkRXBCWj6z>H(Z-=yIKWeOb$S^>jrcSG z?Tb;V=g3qWs8rw9=-m?V5-OYcTwl^gpGOaw<%5DT!^f%qO}r2U*Xwzqu4%kTJ;A-4 z=V~7PnrUDP@Q%jKF_WkzEinm&EZ}-p=;IRa5Q(5K8R^6cTGD)9VnsPXQ4AmkVA#oO zkcXEx$>aBBXi0spx!0GuL`z0q_{^MkK0;G-6jJkiOj1PyXohA&dF-B)?GKQQ`q4pT z2*gk~I&@IOjv;Q?#Jl^cAJNibkSQ+er$S#uk?aks^f)7UuUDgv@n&F#1c%ju+QeJO z$xZF8|Hu%)?&XD;$XwKN8WKfb0MbzPs+6^id*v{0U|uOQ>X|o_;_H}K13VfP ztvd&JW3*`9HNYF|$R(+#h5KAOqkf1e^o45S!_n2D^dZvgXK-B<#dN3M@H{5H=P>E@ z@p}_dvB~BbnZO^k&lrno1dh47 z>7= zf{_dnv4$6tgmmeyAt13G$Vc0D4yPwVBD|ZARUI0RpZK)7?crr{*N?@u4Tu z(P{qq=fS@R^f_{Jj2H%eov$TyEN zLc*h%v9c@~jj=(3BL$`LA(06h>Mr!fX>lg!W&=nEEJ!q@;_mDMaUn`AuX}sq%!8$jszI7_0zj$S(=*G`k`}3xMeynOT3*Dp9Uo zi_?Q%vuXkLnMr&a>cjydrVueJOtC8TV>;>|=_F;PG3!8CE+Ng31nYq8J(A3jI(ANE zBrr5mvR?m42Ms_lP=s5?N~B~G=4A|Em;oS6%xSg?=(x2|u#aC(wtxjBX#i@WmPna6 zcokAPJW?U^gkJwF#&z??aSeR+gHItYSwFMIUP3s7Wvdp|B!u4D#-a2lRglPS<=Db~ zsTAydHTRCbu#y?aIQaZ|!8QGR-3FUy5ibz^HKxvM_`oRnP zgk+2gOg;*{P!UPthPEmjrRW_Y4`mWmOq$)5TaXOpRO)!3ubBoSNKG#gL8_`=X-{yp z=>DCcL%O3dOTikeo@AgGv@#Zrtl*U^jEI}A<>!_fV{Yo^e+c%6*D6*O3b_ z8Hq{XK|*SAtBA~BHPVdVz~ zl)rvdN+{*mSFSK(MyK9?~HL2na;eKSd zh^bS-j#!`;+HD1GthE(w>;X&e}x&H?fL=#j7+_Z>4c*`oI}tI|e-8;8au z0p{B=o%pI1M#jxapkVi#dqtQ9bBTyC|7Ka-+M9b%TNl~tfbU5{%_PP33r1sjR<}d)kr_YE5vm8hNL3JY=&NCCIPo|6~=;FGWeDi#=2V)l8*971?YSRX*-a7 zlGOlh?7JmBbPF=+0Ia_yg(F+UtyyV7@DQ>Xi)OjrFe-QGt^M1Af>_#1aujLMI#H%g zpRmHgslG^Rb(9vVMXMK#S8vVqQ3();ZlXPw`IHl?Bm+Twq0}-^`?p*B_%9$Dgu-wr zDlF{Ao%&`%uB@z#nlFp1;lN>+(hAg8ApxVaTwqOo0tZMqeTuR3whZvzvD;GH1oPe{2La5q_8`c*eJHgHSfgE}X(MEm z-JXD=#O>*AE$V5?K#U#gbXnd2=!578NU3R1k0MvipuJsCOuuX0G4&4k0P?nFE9FQeWw#O%0GzvGTvoDdv^3lYWSJ#6_KuT+~BV5^lv zZUAhrpF%sVByt0A2Pv@IN+CA@c7y`^tQ2wsV8nU`~ zN+LG^*Fb?YRtmWRureAg=d2WR17H=Da^6ZIHvm>1oZ>bGqszE|LbE0_K~urwqxqR; zf;T3}nc(uWMsBy+G7wVCISdw@|D*81^b3n6Z0Qcq6S2IyJbxDJYVCbF%)KwBFQU!8 zh{ioZP74ACfyp4yzO%L$t2<1LmwN^SCiTId!LmvJU@NM}7i?o}g^^Y?g*L;AUb+f< z2BFw9a7SPx4{M^!>={^P5^w~zJFNYxJ;waWg}h>g@y+DjS=c;)tf!KIi-n-vP=Nkp z?{680rtE9nvU;M}&)T4AK%166%my=~&>9ZSDyTum@TmzYNQBHrqDwAAp(8HLT>>BqN=SoM>&dkv1 zz-`dDEoTeTx?L~&Ci4X zjrk?;cV6%=w#6LzAX zEsZmx7LG?{r3;7hx(cIq;Y41s(uiLCwDG|rY-_JH%I<9s`aFNH28<8hJC{gs#nPdM zD5s`By3`v3-9V*X5Z&JPF}^Nu!7Eo*M=!pXMaezq6k3F0QMA&SxWp_i*?4|QsIhuU ztg&|qYNRp?*b$W1IzSR*|I#+>gX(`T?F+(GU$yKy#>Q8#T>b#-H%N2zVcnqxtmFr9jG) zwN~97#>Xq9jb4_8;?)k&w>2uNhi%k!n#;tFjbXV;%n5{Pt%_RQ$VtMU3|_EIWht~2 z*$iy4r6~7N^ive5#h#A(G+D@?scj(!%PD>*8A@A^70W_0&%kB^~(1G67&5V}D2Ke?F-C3RC zT=f$=^gpYzjb=t%b(hr%99%s_jACwMy%_HbgTfnL9lj=p@og1G`oq^Yv$l-&ztVQ{ zUaVx)&yBku?q+WBhkNuVNv1fl#b5hyO0LZc)O3YNKzlT3+&CIkVU4VUbUm=e3JajU z#cHHH(t;mZVf1;V09bB*B#rH^e*2NXQ={gsO$+RRLIK3+-b*{6E{yKy*1DKIiczO# zk7BpShS_5r`fEqffUg@(On1w=n|a+*>|iDt@2x8~-hQ%~G5oRcD03^_Dt5Oq@v$2l znez3=_R%J5Wz{eU%wtvUK(&a+SFx4Vk3XKy5ZJkX6I*Fa`rAVo9sl`Ta(WOYe!t;b zeZ_`5Sw~A=fmKWyjV%eZ>JaS+YdD{9xz6!&bCHIa zPsKZNu$PyS=Dl`>_#uuZVBssm;@Rkc?*GtJ?fv$`fz!f)+Y1K>AyZ36ZlKue~L zBt#NhKuLzBM6Mbn9LEoIMp*5`f&|OT2cGG` zjv70kN$f(XB3B?c+AEiVel#tbCZ;l&_c5tp)|_Rk@>^FJ;hQs>*bBsK2~6t!H*Rig z^xm?OS6-<8a!W4vR$hQyc#h*!yJc}(0`amqN(hw+tC}XbkPzZAw!9qYIl99xN@k(D zFwx7pzBMvxe4g4y0Mte4nDK+s*EC6|DzTtN)6Q+T3qDItqXdqGgz!N8k9IsW^zHE} zdjknOV6-FgK{IGlp}O68e%oBn%D3$DRvHUm)8lzv%$1j^q-Afqk{R4=0AXNNr(t}6p|HUpX)rfd$0>!c& zoSmpX@J1PDeX2*i=?{Ufb8z1X#?QS_?RyV8;jrrR1N#_1{+n^bp{bc=|3rn4$!S#s zd&2xw|GgD+CIoZ$i9`Mo!#Os#+bNUClI(&4gC88(@P*Ts0n{c1`zJ0s>mkoK_@s5* z1!K4`4ABP>?IA!10b*+ehdPYs-*3ahj8EV1h6P0j zLS4-Wog6-h&~Y21KDaT`*4JDxvR!Aq^ntDhVz92k&iHlJp@-vGTN;OY8|;;w`#U^` zUqnMmOR_a6mpqhZX;8+%4+rq`CyfU_+)2Xkg^%Nm4?k3mBOmuOwte)??}g+GAD8Cs zJAh)jI*C?|R$a+=R&#C+Dw(7t<#9WZ*H z>}EuMAItKLLv>?~yT3ocP8gd{-DPa~XL?8|bZ9l#==$Y}j7mR>uUCq2L;-QKc$zLp zcw~g}5Z}2Lqx3mqSy!-8)3y7FEqp@HD(EyiE(z~XAP+t zB+)`}-UIrTeiSu!P?RebXk$21p&$FrNceYW&SP9Z{V$_;!xjHu5N{cKNo228_n{5y^Q0^>JzU@Sn=$p@&-1$P4DT=A zl&xE7=9`MfeFt|kTxf$M;nW+q{<55}dZxPWmt+=at;{&B9PO2v5r4j$rydKmZhQ{q zH%6Ur?%&S5sE6xSw5HP(&sGr=4*xtPhQZbR5@>|Xi<5y=Sh(gH zMG)bDH-TX@LP_IrP(U6mB%m-YoPfMkC!PP0OLQc7!*OK6O~*0gE0FN)6K@ zQ6r8ndb}wR4l-kPC(Ho62Fk>K(VvDLzI9Q-vg?3M2z zDAM#J-x>A4cBDw(Z_UXW_?wTMA~RI^+YkzUW`_8MeiRyXp$(ksnHM|`zUq6ih_jBp zRzc?}Y^x~xPXACk%7C>cVxSH&tWmHYi{aQTc1W9{Ou-pFokZK=@5LF8I~wKR*KBmK ze9ktD$PjiNoLfWKmOC~qm8&`8!!7XQ_#`du;L~d#BLM)*IR$7Xsc$2njH5JC5;q|R zbwl)1X863}GBbQ0ZxoV6Q7HSg*H$wTl|Wo*MZV}Ph3CrR0O*UQgK->*>H(%jBPIn! z^ISk|St`ohtRS`u)q#}daO7ca^?>+~n`Q9LOGRoJdy0KgvnPzb$obZ#VqOHh9d{n6 z95A73hdj9{(VDUlJ$GFnnA3FRoH)A1*r&@?w8DaqWT+_JQ0d?ahh z5-y!6s-jr$2&*I(LCf+`ffkNq1w{b@Zd)o|jAlLC6YXpQf&xg_RB7Ud zVPJn18muCW4b;IGG!iKxpazXlmWrv6bF9i${P#j^+5LyE3)5Mt3Ghoax^GV}0shMP8pp%S* z%PbkbXiL$yE!2FLz-}Q^zvz|3?`uNGSops;!)&MoOj_JgdQ5s_1wt@x3; zwtActZs-c?F|j|1jbfo9E1A`M_R0a#5n^P&$V_2tQr@MpfD&M=iGZ_d9Ss<=f9?~9 zQdk~exlVLRWjCZhj-G~YgA@NcQ`d7k`HHPj=(uRX3Gug7u;elEUMl;H<%`W}tUPiH zT^C8#YioefJ2h9Ovw5&~*1VFz9&oX~;@T|M$~BJqT7 zC`vb(2;SQp3|w0+#N zXRU^yoxn~iS48C~#9R@5^Aw2GM&Cja@&*UfBjr8~=&+WfFznAl@kLgw5PVpj(i@83ajkxlbqhbuVS76OTA3WD=}!654hB(^CC*}$~M zX5QbVaH3bmR&|K5T(tN(~07 zArKrQBaj_W*8HO*dzbOrHR8!mY@lb$8Wc-Bhl!}66I+pv3CEGcBd`*Jes z(y8jq`eV}hsxzCPvf(Wnte3LVi1}S`Mt@r%{UOn=D@$kF#hYDN%iJw?Jk8Kq!0UHL z9vqpL+@1y}PzsF#f({fmq$~3>Lrm$)k~|0PGIc{#c4e(Xq2trRENP?+6oJIgfyiAl zk$W(Mt3^JA)&Tm^y{5ziBhlFwMc*$Y6cdI4hG}#bh}1Q@J)~E_0;MA-hy+o%O3GO; z$wDk0Xa(WfDB84RbN|n~T~6$FiSOFsoHK2AK`o^1t`Kv(%hlZ>#RW90vo4km?1I)Eg3cp`)f(Z#D&+o;91UqDfD>y_mQomp!W<#R6P?frvwM?9sC# zO=nBPekF9;=x5G~%{sdtv(j%myH*XM2Fc+t6wYoT#`IyU**5WgA6C-rWoZ5ScKm2z zsXESG!V!o1vN%!RmnHG?(_&p;){bvIE%x_it@xVLqMag{FD;R-2nhZ93S zMo|y}TVGqf=5)=y{n&iiHfz{GHVk$c0XLDoQz^_H5WNF6FR4`FK(>Lnf!R!*`N;kFH3W*RES z4rMoYx^xvRycxD>e;dT|(&$C=TA%oE*kBveRoAmx&fXO_jbt;h z^;q-nNH&BqT_lcTEu!e0A8G$w9)}b|MzOAZ>k6@G6f1`s6gryyjqMQ6jAp}Osu1T# zv!Uc(JBCfPO%@@vhPO=?8~2Q1oFLvB%dRE2*)42>4JjAnHe}6;TUZyy*S;-w-OBFp z26QtVTEkG$BVY-IipFR?;Dqh>n9y&-PSu)sYsTHi!koOeQWWK}n;LaG%<5(a(&^Bn z8q9{`vpg`)F)ntD$Fy-wJU5<=5Lx5dGWaVduutr$Ywwg%I%zhr1x+cbyikCt|pQS8t!lrt+H8;^0I!neRR= zuAaow_``ML)=BIisJB}I9yvHBT!m~L=`|AznUCBRh3qxH>IE^Ri1pw*Pm5covQ+-s zX|cYDeGKrsC$r4Zda#3DkLheOOZM*or?ey9*U6`|UJBNSB zO9CVUzO8SYlLpT3_fnEzZ^yYJRX>ixB|GTKAQVbL3|zS^k^#NW1aRWM3PgtJF@?o; zu;NfC=9V)iKn{Kj!IKpgqJAp7DReJ% z2Yt;WV(2vHYr7segNnGK73JVgnhq(3jI*c|38)z+QB0$V7mrM18RMuEs9hKEFBCR- z^*8f@1B-qpt!OJY(uuwbhJ`L-kqCmBeENn5q)@j>>xFGsE+e(^*Tt_0gJdr?b9%;Oa+#N|-!kMwmfkLN)6s z5{IqGih-*!8l^bA8d9DLq!1D484B9f*!rlLF^fIO>T5z~vm};t0ytvzLZ>1qtKy{w zeMpgXoK0R`!ObC90S3%rA9Sn55QFX^t!oEOzxNt_ujzyGiGhP_i|S@Lh=U3Uxpr`c z7(N$cdhZG`c`nN)RknQ@%Msh>vOLU8>36d%x#7U|1{Yn`DWj$7^>;HZowgyMfAmrg zhhZNK3nRHa+KXSNUR!hj-E11Wit6SIWOe@oVs)25toJ;Y!}3J_JhnRdeTdK;ejIJ4 z@qdIaj^*%Ecx6B|o6l~uHy=aA@Wrf+_}hF|{CnhGCh`LopwtthWC2TM*9zd7DAq4v z6VoiR?qOaW^5`F#5|QlDE&A^f*?#uGpP_F9`#pWX^*5n!4d|Q7*&s1(DQjOdXA!&o zzo8RuDE+Pjo0UsC^)3(4X^llET+JamttC2rTh4AHY4hL{tV7M5#dt%;#NlPWZG~uC z!9FC{v5dXtevo(*$Ct$W%h$Cop_@E~4a&I+$FV0d=6;se)*e$%x=-RDRZqQj{cC@IEtnNhE`i;5CX)ic6ZI*M_CjORMvdF zmW^R~(1aK)`Q=DUU#9Pn;p9$zk=1qgv)5{Ifxk<(BGYwZcK{&Ds! z8(cGDJ^RmpGbRrHzFJYSL5_(RHn2&S?AVJron%KXc3Ute_A!i!PEW`&QG+pY4=Li2 z8`+C&umA$C)1+T9_G^8(6&p6H!dQ^V-swTIFEy)*Z&F199J$wnMnb{jFWWP z7EW|I{*+0V(5KlpHl(KZX?F8}LlEq0UEB|1)MnNa!gRrAR$&pO%pwR~E(Sr$i6EKJ zVi5wB{O)I=v9lo}^m(=fa-rsd=h-40e_T=1>;*Q7u_HC}{*L0@7US#|&s4Kl-4?NS z*G%2Yo@Z`rirXVvz652mVtLJwmslmU~)WdE7rk_+3zXoO%hLSXOXs0*ezb$&bD?8I>u)Y$%fUoPT+2*>|pW@01dM^`X4jc z`W-AgpmM#pgFO(l=5dUPPU?F!sv%6E;1|Ej+SABg_bR5cV?uq6<#hX&^bjynR-tZr zHBth*@h+s{cjG;f2ECO`E_-)LEegKxMe%FwN?2^3evOSB)&T1*nS+^bo~^Rfku%#Y zbp#2&(_z-We91f;UKT@kvXZRE)f+^HZY}n(9JLpM3M~YW?POE`9As%N8x!+6kR%I{ zG-6}B=yCCBExK&G__dbx3A}DZ%JQ#b=q@(IUXq~UK}(WncCk8IQ>}g-tC2PDiuYb; z*YTCBM8+Fz5ieUU*1y4S_SEATEv*KaUME6!vo?I&D$#j2>*aqoibwfFNioaC2^|=M z++q8zx1z(4Ob-;9S=G54y-uF1$z4k<_RMk-TZp+RIMVIAM5H3& z=sd(iK0K1~T)hla9_(jerwbQwx6D!O!^IcJ(wjpZEOfi(G%-={)4Cvv-zEC}Ua*-E9GKWvl90RZnGWwj9mFUqVtKZB!fp)X03?J5{IBuYaPo?!h za3Po{8}mD@s*#Vf*%){U0Zl5=r($YqNOZq20|gKhIm<(94SU%Fc^)10zrzi<}o7udARk=E{CyGfVm zn6lSD?ob@)T))L4&H7w0z|y0@W-4A+iO1U&u|679ik9GurBPK%&jCOyBiSL9I_s|c z@n@|`NN1X2ZX>C*Jj2+-E^eRjGG9!e@CEZX1~IUWB^Vg;Cx`(8{PQ~&R{MmFqCpKL zD8o1#k`7*gyXCVR%mna?dIcM>kL&GRc-WNJEtfa-awh5(e^l8~tMl zODFLH#wF|<)}sXHpR;-cW4I&4G-@GR6DlX9Vf7`9b7JvZ?5}K|*u0meW?l!ArreFPwxSi(U~WZ27gEIMyk_z_3 zFp}F|8a9-vBD$aGxs5TZ0jFHVy8WyV+ahrx-H34F4f{;@%y;6%e)fK5)l%6Z=y8SA zL1<6JrcxQho!_^``|n{ZqU;^P4zL@kVaU`>+;b2G*NM#sSTE1f*G-iX*BQhQ2cWEP z7s&_NFn)eZBP=&-ixVFl#B;L84e|0J)=^X(Vz|>GwjE+GSf~An!l%Uy*kDk~>i1b| zE1q^!1ErsRAGU1}KK%pMJjvXJgi<7rXGD8Qsm6l(m>BZ`>*P6DXZ9Obo?`U}tWWrU z?u8}^;kjR&_<&uTvj)iPiTd81a7Y$xq3|&~gieqVwXcgC53|lW$DwKKk@|P!gaI1w z2sV(vf^yM5+(D?^dgbflxx=jepNGbynq8Au?lZ}VV^z{;TRiuP8$M*slLJ~9DZ(^v zBH_UhIwtP_kc|NUef}Z456+a2SYJ3Vd;~+seIK(yM5vEHX4#1}TVQyhW5GnkH`L9W zVAVQ;#mDaVMBgKLVYC2}fB6-f*&J^-(r}g;jom{K@);Q{vQTtPPtW)MIS!unBa* z^MgZ3O4B!;v;?KT4L&_fKWayS(GP*1$B|B#add0b?@S;)TnpE?o)RA&W7(bwB%tY- z)9zCuzhBU zIdqb3N2gp6_kYQ@hM5P&nc751i=sYeT9M9yXM8Ouo1vcz4Q%x$Jt>Y zl}g0L)nN?=XMG1nSo@pdX2{$M*6AXY`1g10o}g3nbQT%0<=?X}fksWyKN%#?De-o_ z+)e(uo^{SXYS#d3cDR0qLb5=FXycV4&wnU}|G?UM<%KGlXr9!!K=mpjVhUBX_6K$! zZ3nkGi-UJhd?kjQWmi#nxfOo;EKA`}ekI;Ki&x;D0_)=34UUHD{7O~{%Q^%oxctId zHXv~`@pZO-7>;SoH=9y(>yHq!O%Jl4$g}IT$i=RUjEEzF zA`kh{dEJHIieA4m-xcQc?$CE*v2FWLL2Yr+M1e=LyYXA$ocflE#90Rj+WUTG!#cen zwE~B}FDP4Z5mvt1pnMU(v8(y217hfJ>`gFIvkUAhIM>6G;~5jZBX~SNet`{W)tk<= z!5WC=gY+JR9h$r}c+CvfC||Aegq8y=U_~V#!NL~+1@h)NUpfxKd70;@KQMqmbMPOmmV~hV$HF1T;)B*e_Z_9 zpz(ZN2v2Ko4sVQSZ?=ZHF^8{#c32sPM%0IrLg~;+{?K&|-J$lbSrfurhu{>z8xeda zXT3$wXnqQ$%usm>oSrIgJ!l^Zou=0u<>-r%m{@7x=@@-;5M+$LgF-=&;u)29=0^|L z0QYO9Jc38BnG(xc@b#OdKm&ELvl;I*r11i!*>)QnZr3mCFbAepW1}oUgxrKPFYbt; zas0OHo{%FxON)e_MS5W*na6VAJQ+xUB+$%sa7Q5B6cv<_(jh2l&J#b!@xCNod&l#! z?2Q@`&)YD#ZzS-KS^SyTF`i@eU&+B10%@Aqx_&;1w@)T>|GH=iKz5ACGo+`de<}rL5`O{MVxfkWP(JS7y+N)a;4ey9< zDg4{0vd5858YAkjcwDqk1&*(Zv8lW(w5Q5cerMZ1W3L1AU>%XAhzC=7w&t#jb7fBpS@7C+=~qa4BFAn zc~J(bH5lw0^^UkskPlQKb5}}9CnvY#&3Q5#Dt>9sAC7FiT#gs4L{%1VE?-tb3kKk+ z1M`Ns43HA}ERUq4z@LaOvv>;}qlw7oqcU)^+etK4zr$O3NVS~Nv<p3fSftK)*;R5&A_h$2+ZanNog~pcKB}k!*EiL)eDVyqR zVRRs=g5u?I5)G7BNjIL6!_TtF=j3eX(vM=cdrnMm&71R;KZrH0`HNlcB+Qw*?I*Cm zTUidnUv2pz)VpBed|YPL#QHXTaJrp`;A}r68^Il{7w)$Fs)!9|As|R^(|`R!T;CSM z|8udlEgus5IY%4i_#%990%fT<)t2YP_VcJ_)9qIG6B+GzD~{#B;C7ILx>($fFO4vq z@}UAy$E%2E-axR1QWI3aUDM-c*bW}1 zn&Jb{Yns;d8I|Ru)Ww-R!XLJP(<1sH7}~s9mQx7Uc@%s_C&9rd57UVl^X!Z~yMZ-j zFQtl1wSL+;rxP5o2Y2HQjOW__F-}5!A$Wxq#`Q9W?NtK8bcG+<&i@jn=kP4A%t2la za)*G4p>Z3P3a7H|eR4W0YDQW6yEIF1DRVP6KOWo2M51@9kA&M-T)j`^Zs7DdK-6l6BWOGkA=z^G-|j#9`{ zr@LXybl^^j&#AVv$!)nHKW!elu@ePAfZ(FJ;*Eu09@NE4*2qr!b5{avr{JQa9!dO1 zmJR}KCikB3@T*gAOOqisL7XsfA_;B}>4s_pxq7QFx=(UNp9{*peHJb%uc)l5En^E{ z(2m{+k~>iwNnJM`f`aAPi9JpdG2u9tOc>%>axENpr$t0A?-awaf`di(?v1)*7A&_~ zxEP+xi8hOB9Cq;2CPhxJdG?)AEmTw4J;0r0l))mK=n#be8vx}$)Ww=r)LSDv_ zr=%DpIw;R|in*M+gnM>Qw7m+U)nU}@8GLVvu#^3#p22qe0C z`4u29=>?PDZD@;p$Qj#SqiFBz`c$$%%U1x}DL9j+0KiFG#&o2RHZ!S3)TV=skpH-*!4T9e%M%LFRHhp%$!1cXZg5NNK}vq$I%J0)8oLCFtFqNFg@@}my*m* zl>IDQeCa++wEEEOK+Wtwr|dtcu4JMUF~ZS>PR)fbENlf^xvC{D20!>0YtiC`P?prq z7d;|Uk#OkTpssaJ^F`<`JcJ(!Tu}jW-bMWkrh(%2Hzkb!d3`8fI#Z`_Xlr1{OVtA3U(+QRRyU7G|F&1Ir#( zFcHwO!p@ExDl3U58dHJ)3p5W6BWt)}sKw&l@n-6qpj3{Zn_0m6k}Ak;Zt$YtOb9-X zW<c!u@ zq5&A%2%*K{EqL4n#$KHkGar9MV!kitYU~??YBA!$;XK_D2hR(`c{4u^I?3fU2%X+= zONN`90-!H0=xA|7W^QsCZ>)sK4dQm=gOG@`l|*W6ulq=rFL^ z$i0YFu#2dw{V9DMUaQB!kRqI3F!=-_lW)`lO3%fb$1za8lZ|db1vIV5nY0N{q8cY+>dikpBm(&%bBNf3K9(+HR1&5MfB z|8!MM4m(>;fJoSNrryDH+Z|hZ=w^9(2w)g__(NY19C$I=957T|o#~b3O=v96M4E{H z2j%&xK)i2t8T9@a*kKv+Utou)ZvO~9!j4`@&f)z?ImRDhA`uBP6ZL4WuseXpA~jR~ zAtcaTL&%g{O{j;znik@b*LrDWK=%6ObQBQo!6F^iSW^6wLY(BGr5|fjc3sr7e~xqz zkiSDkW zkE=7`(m_Z18&0qplhB~$hC{EF<>D`sSdEc?3JLKz3>}EsB4KO`NI~BCjt!+ z5glNETJI9qbmq;C&$mU0pE~gD`0`z-F*1hU*{DE-KBj(mHBFk_ZWc+((d+mxLtQ07w-E2D*x{+)tp<`orYV zZZP)gHjsZS{aC2(fM5L!9!_goOBsP&8Xp?tLHPVjZv0*nA3yF72ZMe?pl02qm z4ND3Q5~gpUAlYo>f>^JlAQ^{flElX9CIqFuN2B=(#~0R!t2^^FF{lGijWRJq3fc{n zrgh-)V@#9~2M%ZedQ>mTo1g}nO$P`%MYj2V0x*jw=Eum#g8My27GfYs&9B%gvODt| z{9nI;fJ7es&2*EM30rh3EI!+jD$k+FmNuS4ku5z;d7A}V+Cb+}?3M@gMziW2FC7ZoneA$iXW*l8=UJAs8IZ!_p_xco1IQkOo#$f*M+nqmZUx$Tg zZ*mIyTz5X!z7Bg)8mvJWf*f;N&~;cVSX0)6f9&+XPve4)IGIM%@+T3D4FtDtXcFA? z1cHrb(Z3LIV{jFvHkxDWsN}>zQBM+ZqrvsPtq2C$P-_XgvEpr1R&pTo7fmZZvlpq3 zKEjFn^}|H!Y#Xz8fNG1qW?_L>2_G(3TDOqHXHg)N=*s`U#84%aW}S) zacd{~8a|kgy^OsE+np!GuD-mvSaS{k5J|8V&ABM4riIQ!@h5sk`@Vcu)3m{Hu*Bf_ z9R3hAISSUzE*$C4=*RD4Z;H+RU>lruh~c9<6V%huH@u+wx`%(a?cu};K}=C^}#(?;{hwcXG^QM@fMDY^FN5iw_=ID@P6^ut-K=__ltkSP5Jx%$k|FAsVVB; z$*Kx-I`-`$);|?}Z$mrkMagZvA7A!>c=0x5Tm69e`ZoSnh$Pf`vGsO-g?Wjh{9$qC zcHSfM{Us~{N?}>8;(0K$=88M`jgH7WOYWSNS3DuVbW;B0DWa^1_Yk#r;u-tS?K_Ht zck(#bP=~5at@-v&-jb(BD&{|oXnKVY_lO(A9Ys_=pV;oY5LF39>`idv;ZDe(S~9(0 z#%%lr`1~0N&$H6+g&R_{D4!>?h$|4^314S?;;p;5mwwV+qw;1>o+0Y);ytmK<(r5d z^IW$WJ&`ZSe$1^ZLjkN1U@IeuJ~Z&QM!xV8T`t~U=(|ZsA7Jlm_Lcf>9>Zd z$`F7Z4O5gTxIOVvcKWbZpce*}-uR{zOrKs*JW;!=Ab;`%ZQQtn$QOeur6klV|3)FP*MbqJW9wSOG7J&&PHY^#dX9#z%->C2lX|nPFu}N<>hI zB?!iEMv#zr6)r)Ug}hS8M`xcx6j9*>;$3j*elq>L8h!$$?;6pqh^h#=vx8ZJ6YSqIkx_bKt@WS)6bF1&;bwO_|)*Axyv;c17P!B0G4hc)=G z!q*3%%{P7F?}x9y$u|SwrGG@?Ail|78P$|;1|r?2)FAi=VLqV*|3b7qgsE1j4>f zK*Y@`@~p2nQt01hIi>xbpjo?)?Yp<{DCNnWGYg7mbeK{yfdq=8%t=rcYVgC{tX0I% z7CRFN@@w{$tB_u5&!WMl)pB(|ed zR3#QJebZ-@V)kj5Up&5KLjDBJD)ORO^k@FW{L(+ht#L-5fbyq`8)oz7uG8>FiQ;iQ z(`Cl4o6WQ0{zO4-=J3>!I7OmN1(KhEID~94U;7chA%9{)NwGGygb*y%3Z`qtGbc}m zbV9xar2la}NdaQ!Pbn##YxZZFsuYT|b9kG`dGMyA5Jg6{o(uhCXPUTiF2DNfBZwk# zKe!kZXTbz*dcnlvycsh~^R>H5O3^h*>8g?xls+kM`Xnn!>5(oD%;hPO!HiNSewxc~ zY+sFR)XaVbvkE5UYj@7o?#?eQ>7^Y+JdHtHy7bZ%r7m5}zMK25JC8K#(`Ff}f+1lF zVhwYjR4PE15^cuZsrgzC(g`ISPAe{%0hDGIL%P``d_;!$`flDnV=`cg7wzUv%Acc| z9MMZ#n;|;i!&`@M0}OH8;S4eH9-fhM24TWuU_K;c!FVm7CIlcFktrTSI?u`!JMY0? z`R~;so-z$2}<3-u!NT$h|zQy?R#?P3U2M!1Z zT_WC@$J?|Ej#>)PQNZ)Ag7GAKi!psQ$xX`H^EO0oRT+H*$Te{{^|MC zWks+2;5pPN&A&;Ssu2Dnf7IEek4`SQvox=CZU?OTO2*3>@vbQ}Bd4_# zC-3Fg#pbnC6))h1;-lv0wG@5J`E}`M4nU`A#xe_cPWY}z@poe5C+Gfs%{PwWNw^?% zP&`-8+wvm^#nE#9SH2-f+_M-*fZ;x~n70nwkfX{qDHN&uaIW^(#n@m)TILcyAdohf z(vroy5AaB_Y6*|=+nGoeaN)DVyDYry@V^lz(%9i&;HMF4hZ!19YOfs*M>x1V&qd(~ zGdwebiOW(H zSlYHGbvfU`@6HHoPcm285W=3$H#`-@kg1^sl#u-pL=5MNYY45)Wkja^KLiT z@trZ8s46>*UGOHAOui_*58;79@Fx+bC7jk#I3B=qwY-xmmpVI4#v1(qkLBf^L=n=+ zee3}YdAKJZ;9nGU?PAI$bM-Q8<_v35)(>NW{`JCV`^NwlO;BuiYF=>xEl0Gx8CuJh zZM8BheIZ;2;I`tUf0XA%(f&c+J$(a`Gz53UC9UNpeD-Lr5sMz=L;Q}e=8TsHmq6OX zB_Y!bE|oeC-{oM&0fuTLWyi+49C09!tjZQv+Yo*k-z)eu`gc!w+witiJ=i2u}n4>G3$=v$OZT}Rv$jPrJcci?*!UrHBY82q~SDQFwzc^?_t!JSb$Gykr< z$Kwue2#5E;V?n%qY`Y%j+|~mw#gFSM{t390NeI(CItwn*`EIyW4$?cX=DmD&zRidy z?d^TIB*8y{OUeu29$1Ylite7GdNuDB+oh)}Z5TA?k|x)sXU%_B^JA`rj@PJ43Ps=} zLfty^9;M?o;-8N~7*4%LWUl3R#Wq~6N)bK+=_JY|`AP^xO3HK!^FaZJt1w zuU=Jm{(dy_!@ZH~ervEW5KzWC12S+00*lS$Vk3@yW<>B|#yOT&Ifl=5c0bOKamJX_ z!B`kV`sZ>gio(LU!gx4y#)pN4#Wh8UW0QYxBp#;GnkQann!3prl1C^9lqVUb}jmotVb5gEp&@E$SfDD^FLVk z@7O=tH|#7s&i>9;IBNJm_$hvYzs}F|YJQII<@@+M{2P9Tf6Kq;U-N(Q@Awb=pZrb! z7Jr++%Fptz_-p($|B1iBf9Cu7k9>)P{=3XRWHVl6YxxStA@(HS$+qzO`RM?=W2`KB z4PBWfFS5t^4NoEY3ttW3clkxOf&I)bvLD$pUc`S!{8#pG_6ysDQo#@M_xU0IcYYYD zgM0=5h?nxW*l+wpeuRI_A7`(#%j{$J39ptNe86_{mzdzkSvfz$-eZ9i>;MYRv9H-S z_5qq$#W(Y%d=LML9b@-w=8y6+WbS9he3@ep01vV0RcLUzW0~W7wv7Fa9c5bqPz>Cu z>0JPMnLo~-;}7x;d?Vk%?lKFH^I=QbUiJzvVlT7EkIN&D@%=4%a-i~9WPq`g#wc%> z`X_l5(;YsK*X7Zi`nOA~6+J;cX>9YR#-WIN9hCjmGGr$r`!lb`PFDWrjpL{u9iz3T z>R#^AnJ-LZdhv3=g{dcbFK3R$MuJo*Jndej=@y4Xyktya&N9_yztO7&=7w0oe4z>PLqyzypiA6~oCh>p9304it4 z4P|mb)UV=mqTdSY*8|Lt*E^jedx-F8Jewb(z9T*QD^%`4XD8x2vq&Q`!N-b>E(rs9 zMM>qVgbs}DGhVO%foy$KgOMQGgGu$%CC!*GT<7M{!*vEC?IXOS(N+7T_nV?C=8f`b zk@{seQ|G;-xW1FhV`!Mn>-Fe|?P0b^?v9?PBxj-L2a`u2eon^YQf}rI_Zj&qr5XFE z4#s44A(JfF#6BZ6b+e?@$Eo=;yeTb)uUln|Nt@OZur5rZO=OuwOV&)uBwDhjXP@y~ z+BjBLIVSx>b_?~QDLU_>#A=GhI&#Gu=F#6JR&cBPnAhpij}g1XQUX0#yn_VV|A4?` z30k+Ve91|2M=-uQy6Gd|6|nc0N$8sHd3b&|K=<#+s}` zXrN!R-jon`Us+n&yKw?>F;$(DJt|n3q&ZRbi|ij!^>CA(_3S2-s7V$TtHo-@6IqEy zPSZ(%c(&;>{=!nDWwVdjF~ik-TGY`BV3w+1K%l-;S=2m-0cvlHEc|{=zfVWCWb7j& zy47I(j*-6=t!{Q6!E_s}%^>GTm|dfFli@~1>w)Zu@ki@h0C#hnAE>~If}4%fw$sp9 zc)Mo!ZPzXvzoXh|_+2F9&&c@O?aEPYM*HFTeW(3M{AP8a_~H%}|E$CC@Q*M#xvZ1X zyva~wZO2*h2S7Q!lg9P^tUxb*5Q*9cE8{zjzgi;BEJ2YAY`V7d?xbJ2&iX_#)aqPd zRaMmYxcaVEPewDHQI*t8mym(y~lW#p9{Sy+uFtw)g@^wpz{7buDA6LWt4ASoM z=ohIpT-|3J>N=c6v+4CWu?l0s_0Iw_v)f2kW(2x@10E>oemO)^-CCGlLU=)UR0eK{ zV>Bg3PCs_o_#&s4-Bg@AWCKUS;Gxk!T$3JsKG;`ZU4sZGAQd zZ01Cp>ymi=K=k5FP?Hz>9Ss$|i2Ga`YvM@sC;3wNVxGfuw4}kkq7=OvqDCdEKP6YW z1sO$FhTESqIEs0+lm%W$L$zXpDGNNlWNOYyZG`+62*^e`^|^oDA$R7IYPN6JbkgJ%r_w#~)9j^{%Q_e09$X&buV@X-yPuuK|A`CgT zq$O!-E2utbqBB6q%w#JwE%%0H=mwc!b?7h?oz%fmSRk4>+`eRUgc1>@Kw|--a~j%A z(o&23_*uM2OI5!h+;a*wM-#_jKT+eTLPOM2a}{+fm9;L=&?A-S1mU#;O1DO94oKJ) z;A(-Ef(j_sY;a1b{^S6FC6Vp66+PRckD`H!iJF4WOd=_0){1FbCx;46oz}_WMj%PA zpbs>vEb8RYXylTBpb?*C(I{0*F$t$TdV?NBO>K~)fM!HXH_M>t$vAN6HF7#QO*>YD zJv1HXUaM9*ayc_cw^Fp)nk4-IO4O4OLQOIJ{W9wTBjO|A0qnCuW!-HY?3--t?;91M z1*5tgvYTX9BiXg(L5tFFm*`?mei9^_eT~3VwIphqYsBP443Y)OxDw9f zT@nU|?9VBON=TD5ICOBbokR1ni%VI+&>)Oj03xQl3;ax%a%A!`W{JiY1*PrTtEm%f!5-wqB{7EGc z10;Jpa|kvRj>u=VNnoiG3}{nFn#n5Yj*8Mry`2Xi=$UmBsrje~i*hgE_d4r83$`JL z%EW&r)+`VdS!yR`1tw>h7(>^fw$>3PF^we@LQ0B2e+S9cMKU`ysWi@$Kvx9Sk6vCEEel3T?n*hv5=QzG z{SJgSMZCZlu1VFFibl?3{#~GypG&3ZcNP}-Xu0DBSinqW7$B78ltHaDahze8HiWgz z=cDi_^EIUK5c6fB29|eUJyP1#WmrF7ycVGs@qWGpjNIAJSD$oXAtX$9%=0(!#W+G& zmK-9N9})uYCSxdEt05*I%W8yFY(V?jHAvX**#ltbgu+~eUMF9?VV=FC1v zk`2Db=z?{?vV~MF2@_`N%L?NDCXQ<6PtoepbXeLFbKk^K!4`opm>y86nlIUrgBHtB z1B;%fnQB!k$TD`j}A+80efis~1og7EG7Ef9)p%>QhY?F233TaNUr@()n{wck% zG{@+d@sG{)MQEzlpukG`y%NUTKLsqpX7T`t%uLBn(iIX?1ppEFFS37%{zu^Q&ouk=cK(W!|K7oD5l!+V08$W)Wc2^Vl>$`@>lNhy$*D}>Y7+YNuvfBj8*$+)j>jp_3$2yOMv}GDzDH|i+!oEOsGyN4-mRgO{Q&v zjS$2d8D!AOkw|;RG^b#fVT!RZfKDb5%s2-v55WvKWnhsoGe{BD@`8p6_9?Z`VrBHz zt&I9Q%1G7uf!B<#LrZws3ggY8H}c|TM(nW8e8UQ(|FGoVn^x>qvbi!4z6A2rl$lAt z(hC`i`XZmC^ErC)vb{nKZGw8MyewiCnZfn#zSlwY&XIEB7 z(yR_k(W<^zp*G&VEv3sPj$MW{&zvw~)cd$@jgp2HhKAjbBYOAD9^3G7nJ->nc&}=@D&Hf+!7y zf`@@vIA|u;LKmD1myGixx->Jp=MpVBMWkyn+m3m2{K`zCV75JSW9aSI2QG8JgSa1V z6B?7|fo}2ndS(4q(Z1LjQv<2;$ivx~klymLK@`hsy~v zs6GIODA3~#b>v9CZG|y)WKw{7&@n$||CoHA8IyT5^Xl)=%_c%_@EwhrTQJfQWa3CI zfHDjdC}FA)n3bS2JQ%&ttM8b^mI5H|BHKZ@>u03`yz6e6nbw@9f zj{$dRk(kVAElP_4v&_3A!%t&D!)}^IESoebWHN%R{&1p2`3j@zj@-Z{vS&FKLw8|8 zK}YV?_YjsvMMXp~*<2Nuh&92SsaiB-PhSl@k?aJ3XiY_+uS1UJrDjVg7D+K0jpn8p z!E4coIdULrG_weRgwwAW^GBy=U*hTy7%pr|at=g*u=BiNf)JrJc^;ye5E;VbUyL5m z7^4I37#)lUVrXluJ{miQRagMqljLXcn6y}HdNrwS6mXQWcucbMC|Y=MOMPR<7%hAQ z4IaY&>6r8+;y>~)NkUpALn-K{j!nK*iVpFDoMy})(aw?ub2hEs{KP20i(HtP1EtWf z5Nkt&qc56e7$HQYiWSD9v2AjRHZ&vGoDV`rRwLzLvNZK#vjh|>4K;Q`au zP2e`mgGroSeDlk>cw=o*HFus zGiVTh%?D-R1F2;Rw44&8(UA!XViC&=PZn^|_2b`Sq^Q@>gmYKds`V ztrq6z-ujnXg$}Mvb!09~&Vg_w|5s`TOHXBs2`gA4o!jXQt&sh$F?}hW#dzWIe{W(c zPVdf4OpyNd%ad~9U(cQVDO+8cTCj_;)y7AKQ{i9lG3BLhH8|eVTt2rK2Xb(e{)vCx zI`Sc4cn9p`pcnuhyO=8-MI$!B4LJb5N%A0HX_PCHO{A~bq>fW>1B z4xN$Tk=f0jG`^ovzix13E1~d}^O0DHh$O5PQ~x`*~93B!Zl|s%P}U*nU)b5&6xr$;cLea?F_ko2o3>GyL-LJ z_;JqYnCkng6?KG<7Dyb7Ei{&t*E43%9eX{7WanvEGBDe8SY{6pg=F^yPsosjp=X^< z?yHQM*Ov|&hu?L+jtYb4=h5lPjs?4MGBow>ml1D#&ng@!y>QQdqvD>(%4G{v>13*G z(E>^unTv-bwQ%utnfht*8vxxEcn`nDMLQGg&UxT^<#1!`)-liEC9U|O5@X4d7C7d4 ze#t%PrrFW~yl9oNVCh6&d!Mm?`4(gKvaJ-ouDGR9QXFTDUFmk06HhsD&NE|Wy0N)< zBvJC4l>>~y%hPymi7|b7li*N{vgM8VnfogDFTasRpX4yLlQD6yl+WB}Xe(P{CI+q4 zF#H)S=bNoeW`~Sv%YCs}d{u>f!}fWMv7)3I=6FX*50Iwv=T$c`Hm0)IefP6|!!-xa z!W@IqstQLc0!*Az1{ zauJ1@ogDKKH0k7+fogEmz&w-QdD{PBb0;%`j$7F<8F2Q75L*Vh6Esmn1>z`)klG3> zx<-lp()UB{IcbC0m(Cfi{iGA)0fam|kN|{gYx3|rby8ZEqpP9x>TJMxaRlhlk5Hsy zt8r<~%;w-m9C8#7b}pufN|Nf4^A1q z58TR*RE~Y1AoK{4s9CfW%|}A!Y9Bm~>3iL%GXC~ZJ!94SELLkgzy4=T<}(jvv3->X z9%{lNTtACpEZi7aI?m;W%^OkqVreYnMc}PRZnRHl^}o}B@P2HI)Gv&zM>~c=r{I9z z82DI|0qNpGTSXcp_BlM0CmVP7G0{}NQh>1pMNjV@+<{X{*N+k;GF zZ+W7z_VIL5E05nsY#jE)2!7~Z951c_HO@Sd8jW*t>=&&1`;54OOzvb15%(;mkMCprCxoKKCiXSIw!$O0#87$PW-pRqa99W&{u zf_wqSiDz#MTL0a)w7k`_-Zd^g6CpKClUlCco6ua)q$WS1Ngi@9!1qEekW0c`OKBy3 zZA)^jg#vbRbl}0&qhfCY%eA`}k@Z<>n;@E|m!gh^s6W{vL0H1bv12m=TXAt?h4I1G zrX2nJv9-0=!e8zxjV{|-1=|2n#g@0!hy(=TfhJ?kD=wR zi1&lr8;q>Z(`%?-`k@-wPMQo6rKk&&V>}Z2s{Mkw)dqhXXST(Y8|9B}%>%E5a~7z# z7$@9C<_R~Ho%HiMbI}ddkUz|JpyTw@%5?kPb|&dFD_8YL*tw(@t=vREBBlo(x(rRb z{Vp_ZwHh#$Gvx%Eq%#1@2)0H(Rmuo91ht8V)X;{YmMo}XJ9PSRMLV@ux6`@+z5zOC zC$Qi+>EwVif(=z)-O%|f8sa(9`N;fkTUARfjm<8UbrJJNAUm5D1WIduCj~R$0;6MI z95%~r)gMmTIsQmw%hh9{-?Pvkr-jpjf-D;hzrMX#&=J6}?F$$BBPCeWLVrEYNs!Uh zN*VLV0Mt5kaA{EhMSpO7Q$+SZmfFNB7?5~HYF<>fTk|gTqd|3{KVA!?HWR4L3g)km z8rDGv&Kszy2skC82}BO{5c5X?1WO7H%B#gti5CY_t|9pG$nyYnZl868YmvjY2hghv z1GM{&Ug(dJwS|~;T7>2~b2{eLTpJ#F=_L~*LHI>@MxgG|En(iTVwt*IC z^fV$9EKKuIuMyNMnhgVvR)hLQ69Ud9G(@b7iN#M?55O+MWf90UMgylZJW*s7#M5ez zOLqlYJu?Q`i9;-x`C`e@5lgw?dGakAO}3ZwvR^Ad+@8aIWxvAcv5x1m^D~hUJ7ez-zh_qv;5*~H9c`%n z%0$oVU~aH|>@co@-ubF45OJaXFpn|#rFhS-D@&+n>@;N0XVG&#BV%WbXF~`OdM6+p zC|w0~;ibAh07SIHw&WVf90~&d%cj+fiG0sX#=l-@?L;kRTYA>iwis(-mrUHW%ypwqEQdb06Q5~cVVpCm6&$)Pm7s1Tw<{QFCfAs#Gb!^a7l=YzkqN`hz)-M;gAr; ze*wV>BKe%8A&K18L#_PobT$VYM;MV1K!~kvpBi_(vc>56YCR))PcO1O z^Y#qm=T{mp?;-2$n?1LOn80*BNbXJ1h*f=`ap$YI)LEi?UVWR@Ru;e3nX?m>uT~dv z)}ylfzCalETd%z{nDJBFD%JO}Grg&D)`0_zpZ~?kIXEL@-8axVPta}z?USchFS2v~ z6$aD){eyu)*Yh0O3iYLfSOCD5a9In1A3Ha!zDUDr@JB)m`f)!7TLGNFLCL#lujY@S zY=_LIW(FINzu%0xjd$Pg*vo^R6jZ&`o>;Us0yXPnv+JS_mz^OIpkqF)gz7_#Fv{kA z384r>KXhx9y8j`!9ER%F z2FxYzVA`9owjcE7t4|nrfABi#=&m2$%>GmRZa6Y4@5sAoi>rOf%+u%q30^PCa++Dy z@=ys*Ey@cw^NN@WSMnb6qOCk`=2cT(3i4nHS~`CEM;iOGa@|Lfp%~zL0qOrOA2%ZX z|G~$;-6P3DZAu>7@W{|D&E*@rw!2MS%?jwsFqW zV`l0VzZf%qitkxh2(uA|UaQcq0=7Ud_7Sp*oeT35x$$;G|M;REhwDfZ< z&c7J!=XS=;7gok@`4LRkTwi(tK@HR2`pGzZp+)TK?~u&ak5TXh*MB>2H2$g0{}0IS z|LISVy-_`eF{twT0@EY)W4{<9e}0}{JY#tO?MvSDJgqY`Ft~proPrQKatMM8MxTG* z%Zs;Ee)#Vs7HHWm0hFlAi+a zA2QYEkN%WeY^I+6qbH@#{?QCUW&O(@2j6s7WOCNJ%cirK-sXy;zaAKXQ%emSW#m9n zgDc1l7Rzx0j(s5=X;F!e|7o&MofT_1dzr64Th-UW@;Q4}Jm+FJA-LpX6=QZTl{-r4 zPh7p;i%W)d`nU%%z4mdU2~G|uK`V~FpJFnP%4ikag&g#aEiP3?%oiyuBj)i%qgZ&u z+2>tq%tCa6Evi=Nk7=mz9N8QI{c*Tg;-}Y5)PqcmK~6H7<~e|Orx=&C>avrDBhpJnN(!6YRlG%=%+R@!U7thhc>2miAOT~IG zYs$V9uX|Z~V;Uu)9F6iQA^@SO$pcm@GLK)YmsUBWSYuZI+J!=oX4iYImi%tQcvUp3 zNF!+39y-8n8#c2rNWlF|#jqH5T}z^!O+Zip<dcp|KB-gggb#{2mVhyGsF)X423`O%t*p&m{uE3XR~mgtA%=u zq@xGy6bH@3kehIscP)pBabg=CuvRUu&qmUjiTJz$ z8`HjMKV*j-v+Aw5TBg?`skcK0?p&^a2k|}lk@kREOGW`~HT9U7mdJ*&NbzPOJMTFo zrK2?#!S_UU5-Ux9hXeyHz-NW+vVLeINe{Vr-xafxSsvfHQGArlZb^Fq(~k9k?!z+m zDGtVuqTL3}87zOIXA1c9n3$fzzF_0Uz*JToRr~_*Nzym%07mar9ZzKo8QWPkGM(M; zVm-w_Gg%W?9!ZOs)`&Ir9)cm$k>h?Wu6c?<4Owp*^0`JVL%h_G#h~f88nWe-H!zEJ zi`oBNwbDdUIw`c8?|V*6Yr?uV#!Uf-9#f#dj>2q3=_m`qh`U08E1na-Ww8Okpl>6V z#q$2T_tqJ;p_S3t(a`jv=c>9kX8(}W<8N|pydD~BS!__=a1%?b?E*|U(`x&)97JlS zm!VbjwGpOA$pbIYVFM3tdNDcci8Slwav`bY?8wj225r;xVq81xU2m@Rlnl64$3~a`dK>mr%*b+4?$SPC7 zAO{{67n`vs_@Pzek>;!|+a`v#XA1)VpPYv{$%w|uA&yQ=iFJ08Vn``yM;Ho#n1mp_ zlVdD`uucy2tGPNk*kEyAdzKTBXBTw#B9A>_rc2ibtWnz{mkthxuGOVwmEidbSPO1G z(g=*7Rop87DNS z7s7P<8Gtk}L5eI8y6qeS@(F>&m>?H6nXQmpBLw6f0- zTC#oEPYUr!Yu3k8E6_Z>27=Z4pRL(_X$PT2;V$>#{LZ>ohqYN_bUPaQ$Y{fQVP*cb z4O^7FGw1-hdMhnvv}KxSf3W<1p|xXaY`1v1Eo+=pZRe92(-8PwU7g8y5}*ah^%ZP| zTw(+rY^-}b=40!`*mf+@Q){=WuM^AKF}Pa9_I4~e5UdSmrlk$^!CFs31_g74FGXJJ zF}Yc3sDO;8_L*w~IL%xXkH`$ggkgYTa0lT&7#xXoL0d0{YXQa_I=~xV>Uvs{MDO-2 zn*B>m?!=z;T(2p(fekTp4;HDN*#g}XH~1j`dqLF|4`DBT`3=Qy9qQBXP7dTgrEw6r-+VZF$8>vFdtb_ z#Mewc_bqUF0i=cZ!Kt77R#fG%l@V)>1FvTK@81fzZf?dh_lVAJROx;hMvG}4K$Lh` z{HU{sScO=X%ch6Fa~f6NJ1s8fvd(<{X>nZ-)`HiZ7L$6gCcON#FnX{_lz*uQ%iwRH zu1fF87Qs_fwV^i~$kNQMH6#VnNXkW8gqWIpoj8>fS#9{I>{!+1HoO6>1-T2yP)$&W z=+lpl#=b#p?uSf-@APBCDa^c)(XK@FzmYXgI6&qNIc6fj90ht~&`BLAR^G@;dC}Lx z*Pqp614W1aY=oUm8;EOr&=Cp_XY}(JoVf?HlA^jl+sliWiDd&=`|z?AuxZOyRJ}HU zZDw)uO0#`%$a7F2?z)NjV{yOPd}GzT?uP6wH^CY@D$d=+R}lhO!R4W`($8C@Y5L^yN_YG}|dw z3}b_^`4gWEV*@D6x{Xb=H-TYvwqkDrH<#VUI6*u$g5mo17V+H(Hr|Gm7Xoa^stLEV z_Ka_;5gSIbF=P&08p+bJvZsz>X}s*Ms;;A0gp*&qU-TZ!hEn;)u`HRL5WB`={*Q6- zY95BLa~xZRI?v}Z%qIndMOL27T6HJ;3|ZaBvw5oh+Ow0akT7+Ss2R`3+HVQNLq+!^ z%(sL^=X|!tWW5hhiv#)4XopV=*93?kSnj$BYzA0OY@NWS@FS;1(nOZZpZ!YIPG#|; z|3vmCI*gsf4kDbvYy9X|(Wn62{QhY%wt%JZgQv}^xK^+ab%qwQ zjPN4dU)PJ4iF*oJQpy4F0gNN;xhSSHSV{VRitS$^o-bqx-ZO8JF7Sd4_tc0(g)Bbv zJnUj9P)N4Y7xgF!?w9)_nb0pT72#7@daH{jAgV$${%$6+d3-E0E1?Y>q+ENc?mEyhWY-spJEQosbBckyP<`3)u;-C;vjdpM+X>`gV z9~SyU0XmhoC6oEk1=yxweZb}!YY5M&`wujigp=6b$ zg0OgB=yLc>th^Qhp=T+kA6qMRG>9!5_2B@LvZc8wbwGT9bP)hO{SF66>L=TkrjIh=+QktIfbYerk4ueUF^e-hgRTNZ?=Lmn&|2gbERG@TD|J-R~lna{GwGMhFZO6G+4b|Gsh z*AK2=bkVEaMYQ>BzJO^c9Jqj`u>Mt(7qFSE8}+~!%SQiSx<7M`?i<|0ve}&?=N|S@ z(qSx0*}N7bBf&n3*Nl)nh3^!I@9trDkdzEu$e#F5A zwTM*%V~rd<$=)Q7Zc(-7;l=F!TmK89;Y{~xqLJc}FD%_ang~wO2x7{xjPdU{ZvAqNznfX<;S;gX=^eqU{Zxw69%Syz;RhZ*n z#l}@^vU~e#K;ug>BKlqswMI0(mnFNOq0De(4!9RwdPwBo%Q8^5{9e{Qk9ZmKf^Atu zLtO39uy=OCSX#o=XlFoIT;OuNpb;VEpL}&A!1@um7v#e$eDpe?Q!i%pMuJnNt+j>= zwPZJwazQ0>eTjGPW%J#ZxYbnMcpqDfD^KFn)hxBSEokzll!NR+T@(RV+~l%8a>Miu zlz|UWRQcAh+od~U(OTA*9jBSRK`fFCJt!6YsV**dsQm z?W-F87#q&qmTsyMyEecCJoZRc+(vvgs?{OVF7$05Kf^3I^pEW{Ig}ZWCw=h6T5bXT z*dwCX<81eTBSg3@a%~~1V18SO%1t3E2_qr8Cdte{|i-0u0fSgwy?&~!QV)ob6ecAPc+}bUUgf<+E?}c4)#2ATWesA zDBlS~a$QN)ft_p}vsE^+FR5%!9(qe<@2i^s5*y{{Y3VQt;wFP=55tyBS|GS0X>ssku&kib zHs}?0f9$q0$WL4K0I3A%AGn17^$KfAVxO@Gi{&x#z#f+EJxjY>FkHg|{j7L%56kdu z+C#<&<$WXmwTE?u524wsY{EuQ9|E zd*LhCwO(Yt!A5w>9;VU7W8kG?@M0LM4G61%mCNZq?4DBqD~2^{S*SJY6^iAAx%gT(iT!c2XUZANHuXxGKn+RB+g)y zIKxfi;GJ1~Pw0gJeO(3@EUC9JU}wkNHmUwE$N~eQ5KH>i)S=Hn$XBcbSbgQ^Xvm?6 zmCH+gLZTcb7IYu+;%laeVXwZ`6cF5vROuX-d>8n-o6IKxpjT-@XdUJVWC7(+L07-l zjRp_NqhAH~HtEM)GB<*4AVcv>bJmR(8~|ZzS2DqK3c;BU+*Q8T?y~;7Oi$h-tsQ(S zfqam-1#p^>O+;*~nE5W-DDHcYwHo=yi`DeywKek7eM~8w zUy1mGYzV2%f`igEviu-x2<^G$AT+}H2gR=k*(knlqZw9jHA83)nP0Tu41Yf()FW_T z^ghg9%c#K45SwNxOwgE6FqmjlUtt?mBQif=Ej%UmmQ+*3)DN*SM^`U@$cDnxd+7t# z#k1>GlW{oIcFM5o`bE1w+h!8Ut*3_lRYKJ8HeHNje9( zOxyiUN;XSsKXq8lJi%J=k`q-=onXCL zL>YOnlk}Y@#P_wV8Os-GC)t95`FN9E|NH=oQuSTOEyF_JkC@(2KWAq^eh(}9(a%v% zH->b}6aIU!9AAdfYfgx-PO>adK1n#;A2@PC)ccaXS?^1@e8Xua(VerfT5VwJm*Ssa zvVPsV(On0t2m~nHt9DEF@EquAr7xr+oVH}p+Q{z{1OzIVFFR(ceZ5F^9`W_Bz&r)_%Br&e!6vhT*3d4zZx5~H?*K^Cix1fuyC@@7UmwgkMHwCS zO~H(92a(Z9-+8F&_-XbE!)-P3*crAn!n|$8)NW$W#CI zTJ~jC)H(L9v)vJDmWW7SHKJQuHI!kw@IgdMna35nb&0w{JaL9~7P;TE8$xc#(LE<1 z^1%1(A3&&T{txU9hBJ?kf0Rcb&I_zv)+xIySfY{og<#7_(eVRi79SR)F0kf4c`I9X zYu?JUkkBiLoXOPDvlrMJIuz;r6Rxnlcv1}iiFKp2KsxTuPZU-6+`br0BF(if{!tbXhZk#&h>1T5w+p+Z`W_5H`t zVGI2t0=vU2I9wh$r0J{QwJTUmP;OVyEjB3)W>}0=63nnz?c(Rvs@1E-jb}cu`tTA{ z89(qv)j$4#W!?DVDN;o8x*XYuunc5IJgFObSWIstJn)Z)`fz_6b1}wO@n8q)IPX278(k5>&Klkl8O6uYWR&FBtPm>bsivS~6R)Kf?)58b zAFH+*qsoW{>tZmL?BDL-DUh&v4*rTQ;uoRB%(=DbUJf}nGpw0863npXZUY3{gs^7! z)WNF7PX00rGv87W-`C^0Re>;`!$Tx5&Xm0O-F#Vy3K$u|LsdXgUDBa;tNL36ZyE+= zbts0<<*b|N9mh|Bmd)z%WCXqI@uq!`kcztU^$97Ev9=zNnl~0qy)y(dRGuIWI3kC>RwJHW)+n9lEP{@?6)!S2}p z!oT=HWQz3_dvLmkRFV*8=GA4s9r5W4Wt zS7K~K-hnUutx7b6PY$9xz7a1(aHtVaA-nC{Mmz~8(cz8xBT+@cS5fgf7O}lC&y?S3 z0J8WfH}sr!Ij}c2s!ZL^qrwp!6+blQjc}-(l+A~+OJYVgPq~pk!azgB=UU)-9*F%c z2yEXR;O1=s6wqfm<%LD_s|~O%%}>IZUMPC^c!l^Vn_uU~w|_71#j8#EmgHUktX6zs zWI?zH?|1~X4v(~%`hrvIrHPl|Nc(65JDO$GU z-2+QjgEeWN?1=`a>qjW*3MR`RMUwW|X6ZvzT9-V3-xbN4l}JVgfiJ8@(jQDdd_R)D zVDfirIYI4iH~lF!P1|>~r{!yqRD;QXU0HgLT=Im6sOZ5Pij+RQbM&c~k>}MQXE;}V zS>*NM3Hlz&i$fj^`y+_cMJ~Bn;K3VlMY{pw1_-oz3E}|pASL|(Pq{=|AD&{o{bGXf z_T_F7-IJ##)V_imG5SGjJIt(j{uOaUPo5BXor=^eiVh(Qz6G+r$sK2VAzmcNNd0;0 z9lTw)rZ9Z0{|d-*f{eN*BwiwUTG`ms6TD{8oy=UyryOuXc(y zH}b^5`adm~FQp$6V6Bifu1H}&UDqjpQc*5q}3n2Uq`c$#>tFV778)sU}^|6dGQ zGWY+4zhA0Js$zKwLk19ve`W~&b^N&--%t)R>mISTKhH@&XIghKp&kM|q~Icw;)#UB zLFyPRsB``KWA*>!AZFtLetX+%mzj=h4!bqv(w&v8YBG?2;tYJbsoE4Xb75JBDZqsk zd!2cQE{&3$n57rU&~^lq>#1(t;&^m3k`02T`L8WoQ(rRisLJinWAAcI@q+sz* zO4dQ|qPFTv%b!QGZZ&-PZ%Edyh8thFqVz3lDlyn{F#!i#&eH!)#c9Ff!_-sV8vX`h zU0<}lm9NJgg12ww{m5xpHWVAh6Qacsei6A*oq3|@K9tKlZ*@hPRmX>Nd{n~%_~SM{ z`pUYQV#9FWDE@tU2;lU=_fKXeUUY>S_wjJPhP^4qkAP|VrdU3Lw_&|S)d+r=9TBT; z=U=mr#LSU=5Q4oU`2hA&RrDynkpBMge09OLcVYF!k(i=B#t_GG2DtGNilQMOuJ~tje2pB& zVKzU?e9p`%3RunB$3dlO;c5$P*0208z;oz6jC(M4KG?_N^&Xs~;dABOy#4Q^y`QYw zfdE@5ZpcIw9dTKIal(iD`8a^{#fh;)cnVfur#i|XZOm*~?*@DkVzUpQX+}$A-9`b+ zLLWZrj8jykeG!fTc9D2b3qln!(;r#T1WQ7!7PH(Rof}xLMH@33B^l?>M6||dA>aa0 z@7}CC=HauE04~IvqP1)aOl{zXwg?|6J(@2kzJG*QUz4fud^E?coIw`j@I z;*xb`)kT42gYorI^CZsi7L$kalt2Vfane4F;qz*!S~1jh(DRFB*wAchITDHOS^p)Ab9RUGia)h9`Yb>$B96Q{)w(dugrhSAT>_++@E z1tREuYOIP#03w_YuYqF(oww4s=(CkJYa&HiR{wN9T< zO=cI+wVqm~z#|Dr6JD>GJV5m&(D1cz-07!J{)S^XC`D-g;fs*j?Pkv|Fu~Mn=FB?H znZ@0hNC)`Lp4;$0o7?7?TYF~1X=X98t~t06`DgRmI0~%53IS9=cb4eT*JrvgC>E90#+%PMQp#USbiuO0=Pp>9I{q6F1LI;0!S9d$#e z!d|b49mTT)W^0&H5;Xd(3hG$mM+dhKItE5p8r?26O?Ri%ApmMk9ZMmWDER1A68TyW zKK>nS4k(f62Smg(G-Uz)X#mZ+qa~B^nk6gHvuA2`c;Crq{|z_XD(q z+@hj&uk8MK`}wG0enqQ?@4aaC2B(mJ$1N|a;wwTy2rU6$!^dZUa{;(WQ?@ViM|X!I z=+>fhwUX{Up45Rz>c;B@60~~iVhlZA$|SvP2=>SMKHcUfk#y3{I4=+(y&DFN9Y)`Y zNkou9G?gdq_%joAU7H8*nDf5K#SKq7-ocjjKDIjfi|vjCt_VfmPQV8c`F^# zFCm@aD<}{et*KGyPAP0Z7-+%MaQnvnK&)sLTdU254n+3C-GLy2ydHS@f>;Y|jbjZ$ z!L6r4UqTYBj@(jz0b>b{ZjM;+s*t{x|u z!`U$1#L$AOo}4lyGE&pxPmhIrE_8gvk7PpMk&XlPXF+1E;%MD7AR)lV%WK7dgW)6)u=N=jB(M@>W8eaEp;BY2?V-tJaoZR;@3x9HV|Xe*_Z5L8 zi#Nyc@p0uFs}&e)oxr)IF!YLzqU%^bnhtfx@+Y-(=v)5DI}~l&fOrw$&3Agi_v*2# zk$Jczj`NDif0W_;svG z7&CY~_r-8kX-Y7y7x8A|ml=Fu)@^Q88Gx*Z@U*oaFG44tc6i$3=^)N6;$5Sn5T$<% zPe*b8UA(K9zL>}B@e!)hA9=?j@W*8lVp?Q%YN9iq>+nE*C{riTo;`WmL~X+4{3+wL zv13b6F9!KS^q9pnBg(C0m6(oXn6q^iV9=+|fQl$3^6vI<~?Z>yoR z@yBFa*#zw<#;oSv#5R(qSh4H~kt@mz&_XVGOY&+er31XuxzX=;eBcC|)`gZq^kvnG$rpFJaQR^HUv zhX5OfypO%2bS`fjaS3T6ON97vF7KLjU6hG;H-wla9C6^bJi?nr<9R$|Xm>;j5hB73 z6j}&IBAykJu7^05i(q*Q;=~|1cx>6@yD1aOqqUHX#Sm|`W`M? zgl3HcXpc$x;|lY~_ntL<>MeP5CQlD7A1{g*@?}vpTpOJU;`U`cm2^%EzV2BuW)UA4 zPM0G+%em-uH*c6Q6Tmk65NeQ8Wsb={a}lL~SFum`@d;P5&pg1`{oRH5d^`*Agy!E3 zVs1sZzov_{0M0=^z*jyRD)s8Ca%y*>3zDp(Xqo<9HJ80wT%n=I=Z~8_HE#-OlzRZ2 ziif(VQ7lA*6U9qQcw?6t{c;JP9yzIjsZat9#K@&w^IYA0#ud$nEC7n~8mdkdVasrq zc0;0QwhVs?!_2y68Afr>QfPWx?e*&ZAI-Gw&=FV;#fpL$(5p;DYX~o3ym1IaiN8=p z7W0&PJ(Ebv->r3|hg{8x&(S7KpF0f_VNdY3 z6k)93Ez|b{mL|U6G%OO6$7_@F=WDa)%)(05MZ1_HPORWfbK_H0ImfLLl9IGxbMt2B zcWfU7SNft{E*tp}vdQDLe9|32bW*D5zY@bQN)?4GG5oSru@Y%Lboh(Wt~uu11-E0w zp=AM-+2an)FA}F$^1`eoC?>S%S&Aob+?=_2P!ge_%f!4A-mFDvRxv<_0nZ7O@mJgN zci5rnuBftH94z6D>Q zNpqdNMcyI#! zop_rUMxiQkLTfy85GMp;(2W;Q1R;sXfk(mf0LpBP9z@*06PmXk>ABM;&&kv7oIZU@ ze%`b^ZMK+vFaG-8=V+M_pEGyHlzeSo-mJ-a)8-(3!9sjm{#|nx%&04YA4!@s)S3<6 z5P75%@~6$6+8J@8%hbFXN>9XLIw-UAXU@%^HZFfK(uAemCipKfcN}^fivmJo&TB$0M~I~=P`mmS#Y;mH6bZnHD4QbwkM$J&S~t9e?OG9S>R#M;%oYr?~*M0BP{ z;3>uPh&aBQH;%mC51SduoBbkY4a9MVU$k1ob9t#>%wEI&fg>m`@od6F?a}i$JkQ|SEOxKu?IPPZG_BOchT`9Ad2WYt6wyDL zR&4(4!Y?sS52@?h@6du-O*jk1bKz6A$p-!LkKZmL$R-0b~hD%t5@5swNCpB(SEapXdR(wV)Kvh{SEc(SO)Oz| zkdJgI0yB|r7*akDX%pe9aDx}|__ZH{sIHt12QNEu37ZnXgjGHE5dNO>o#ic5r6uYv z!b9w9r=LZf24SbST$SE>ReIZ1>FvR^c)65!Z)CUgd@$eamxvXMZ*Y78r0&ZScP!&c zSpc!Yi8M49cDg>&xiT$=KEiM3m{71Fc=Lw}1NhEv+Bqd#dkQNx5c!DkA2@zALk0-mf zw4-S8B<~nk10dpcT2pCdsOeZW_ep--RloXrRY^uhdpyL Date: Wed, 26 Jul 2023 11:25:33 +0200 Subject: [PATCH 186/218] Typesafe wasm exports (#165) --- Cargo.lock | 111 ++--- Cargo.toml | 3 +- packages/health-computer/Cargo.toml | 2 +- .../health-computer/src/health_computer.rs | 3 + packages/health-computer/src/javascript.rs | 44 +- packages/health-types/Cargo.toml | 5 + packages/health-types/src/account.rs | 3 + packages/health-types/src/health.rs | 3 + scripts/health/DataFetcher.ts | 21 +- scripts/health/example-node.ts | 4 +- scripts/health/pkg-node/index.d.ts | 47 +- scripts/health/pkg-node/index.js | 406 +++--------------- scripts/health/pkg-node/index_bg.wasm | Bin 176105 -> 236591 bytes scripts/health/pkg-node/index_bg.wasm.d.ts | 5 +- scripts/health/pkg-node/package.json | 3 +- scripts/health/pkg-web/index.d.ts | 52 ++- scripts/health/pkg-web/index.js | 371 +++------------- scripts/health/pkg-web/index_bg.wasm | Bin 175181 -> 236444 bytes scripts/health/pkg-web/index_bg.wasm.d.ts | 5 +- scripts/health/pkg-web/package.json | 3 +- scripts/package.json | 4 +- scripts/yarn.lock | 100 ++--- 22 files changed, 371 insertions(+), 824 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1907d1b48..3f3ab81cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -514,9 +514,9 @@ dependencies = [ [[package]] name = "cw-dex" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ab6ff93c73a386948fa0f834d029a7b29b350b7b622043506cefd2c81e5716" +checksum = "8b25ff7cf4e15288be9021a797eb1074cfa97ccb2152d1c645ba9b5d6b31a82c" dependencies = [ "apollo-cw-asset", "apollo-utils", @@ -525,7 +525,7 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw20", - "osmosis-std 0.14.0", + "osmosis-std", "thiserror", ] @@ -1086,6 +1086,19 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "group" version = "0.12.1" @@ -1638,8 +1651,8 @@ dependencies = [ "proptest", "schemars", "serde", - "serde-wasm-bindgen", "serde_json", + "tsify", "wasm-bindgen", ] @@ -1651,7 +1664,12 @@ dependencies = [ "cosmwasm-std", "mars-owner", "mars-red-bank-types", + "schemars", + "serde", + "serde_json", "thiserror", + "tsify", + "wasm-bindgen", ] [[package]] @@ -1725,7 +1743,7 @@ dependencies = [ "cw-utils 1.0.1", "cw2 1.1.0", "mars-v2-zapper-base", - "osmosis-std 0.16.1", + "osmosis-std", "osmosis-test-tube", ] @@ -1850,22 +1868,6 @@ version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" -[[package]] -name = "osmosis-std" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc0a9075efd64ed5a8be3bf134cbf1080570d68384f2ad58ffaac6c00d063fd" -dependencies = [ - "chrono", - "cosmwasm-std", - "osmosis-std-derive 0.13.2", - "prost 0.11.9", - "prost-types", - "schemars", - "serde", - "serde-cw-value", -] - [[package]] name = "osmosis-std" version = "0.16.1" @@ -1874,7 +1876,7 @@ checksum = "2fa46d2ad5ae738572887974e000934374ce3546b820505c0ee19ca708e49622" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.16.1", + "osmosis-std-derive", "prost 0.11.9", "prost-types", "schemars", @@ -1882,18 +1884,6 @@ dependencies = [ "serde-cw-value", ] -[[package]] -name = "osmosis-std-derive" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a455e262a6fdfd3914f3a4e11e6bc0ce491901cb9d507d7856d7ef6e129e90c6" -dependencies = [ - "itertools 0.10.5", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "osmosis-std-derive" version = "0.16.1" @@ -1917,7 +1907,7 @@ dependencies = [ "bindgen", "cosmrs", "cosmwasm-std", - "osmosis-std 0.16.1", + "osmosis-std", "prost 0.11.9", "serde", "serde_json", @@ -2373,7 +2363,7 @@ checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals", + "serde_derive_internals 0.26.0", "syn 1.0.109", ] @@ -2457,17 +2447,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde-wasm-bindgen" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - [[package]] name = "serde_bytes" version = "0.11.12" @@ -2499,6 +2478,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "serde_derive_internals" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.27", +] + [[package]] name = "serde_json" version = "1.0.103" @@ -2809,7 +2799,7 @@ dependencies = [ "base64", "cosmrs", "cosmwasm-std", - "osmosis-std 0.16.1", + "osmosis-std", "prost 0.11.9", "serde", "serde_json", @@ -2969,6 +2959,31 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "tsify" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b26cf145f2f3b9ff84e182c448eaf05468e247f148cf3d2a7d67d78ff023a0" +dependencies = [ + "gloo-utils", + "serde", + "serde_json", + "tsify-macros", + "wasm-bindgen", +] + +[[package]] +name = "tsify-macros" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a94b0f0954b3e59bfc2c246b4c8574390d94a4ad4ad246aaf2fb07d7dfd3b47" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals 0.28.0", + "syn 2.0.27", +] + [[package]] name = "typenum" version = "1.16.0" diff --git a/Cargo.toml b/Cargo.toml index f486c412b..4b01fce98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ cosmwasm-std = "1.3.0" cw2 = "1.1.0" cw721 = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main" } cw721-base = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main", features = ["library"] } -cw-dex = { version = "0.2.0", features = ["osmosis"] } +cw-dex = { version = "0.3.1", features = ["osmosis"] } cw-multi-test = "0.16.5" cw-paginate = "0.2.1" cw-utils = "1.0.1" @@ -59,6 +59,7 @@ serde = { version = "1.0.175", default-features = false, features = serde_json = "1.0.103" serde-wasm-bindgen = "0.5.0" thiserror = "1.0.44" +tsify = "0.4.5" wasm-bindgen = "0.2.87" # mars packages diff --git a/packages/health-computer/Cargo.toml b/packages/health-computer/Cargo.toml index 195928895..f9a06515c 100644 --- a/packages/health-computer/Cargo.toml +++ b/packages/health-computer/Cargo.toml @@ -25,7 +25,7 @@ mars-rover-health-types = { workspace = true } schemars = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -serde-wasm-bindgen = { workspace = true } +tsify = { workspace = true } wasm-bindgen = { workspace = true } # The `console_error_panic_hook` crate provides better debugging of panics by diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index 4cf0e2f4a..06c3d68a2 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -15,12 +15,15 @@ use mars_rover_health_types::{ }, HealthResult, }; +use tsify::Tsify; use crate::{CollateralValue, DenomsData, VaultsData}; /// `HealthComputer` is a shared struct with the frontend that gets compiled to wasm. /// For this reason, it uses a dependency-injection-like pattern where all required data is needed up front. #[cw_serde] +#[derive(Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct HealthComputer { pub kind: AccountKind, pub positions: Positions, diff --git a/packages/health-computer/src/javascript.rs b/packages/health-computer/src/javascript.rs index 3fce3a3bd..80d5242c3 100644 --- a/packages/health-computer/src/javascript.rs +++ b/packages/health-computer/src/javascript.rs @@ -1,44 +1,28 @@ -use cosmwasm_schema::serde::{de::DeserializeOwned, Serialize}; use mars_rover_health_types::{BorrowTarget, HealthValuesResponse}; use wasm_bindgen::prelude::*; use crate::HealthComputer; +// Note: Arguments and return values must use: +// #[derive(Tsify)] +// #[tsify(into_wasm_abi, from_wasm_abi)] +// as attributes in order for Typescript type generation to work + #[wasm_bindgen] -pub fn compute_health_js(health_computer: JsValue) -> JsValue { - let c: HealthComputer = deserialize(health_computer); - let health = c.compute_health().unwrap(); - let health_response: HealthValuesResponse = health.into(); - serialize(health_response) +pub fn compute_health_js(c: HealthComputer) -> HealthValuesResponse { + c.compute_health().unwrap().into() } #[wasm_bindgen] -pub fn max_withdraw_estimate_js(health_computer: JsValue, withdraw_denom: JsValue) -> JsValue { - let c: HealthComputer = deserialize(health_computer); - let denom: String = deserialize(withdraw_denom); - let max = c.max_withdraw_amount_estimate(&denom).unwrap(); - serialize(max) +pub fn max_withdraw_estimate_js(c: HealthComputer, withdraw_denom: String) -> String { + c.max_withdraw_amount_estimate(&withdraw_denom).unwrap().to_string() } #[wasm_bindgen] pub fn max_borrow_estimate_js( - health_computer: JsValue, - borrow_denom: JsValue, - target: JsValue, -) -> JsValue { - let c: HealthComputer = deserialize(health_computer); - let denom: String = deserialize(borrow_denom); - let target: BorrowTarget = deserialize(target); - let max = c.max_borrow_amount_estimate(&denom, &target).unwrap(); - serialize(max) -} - -pub fn serialize(val: T) -> JsValue { - serde_wasm_bindgen::to_value(&val).unwrap() -} - -pub fn deserialize(val: JsValue) -> T { - #[cfg(feature = "console_error_panic_hook")] - console_error_panic_hook::set_once(); - serde_wasm_bindgen::from_value(val).unwrap() + c: HealthComputer, + borrow_denom: String, + target: BorrowTarget, +) -> String { + c.max_borrow_amount_estimate(&borrow_denom, &target).unwrap().to_string() } diff --git a/packages/health-types/Cargo.toml b/packages/health-types/Cargo.toml index e52233316..bc22ef506 100644 --- a/packages/health-types/Cargo.toml +++ b/packages/health-types/Cargo.toml @@ -21,4 +21,9 @@ cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } mars-red-bank-types = { workspace = true } mars-owner = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } thiserror = { workspace = true } +tsify = { workspace = true } +wasm-bindgen = { workspace = true } diff --git a/packages/health-types/src/account.rs b/packages/health-types/src/account.rs index 7ecbea4f4..12e773bed 100644 --- a/packages/health-types/src/account.rs +++ b/packages/health-types/src/account.rs @@ -1,6 +1,7 @@ use std::fmt; use cosmwasm_schema::cw_serde; +use tsify::Tsify; #[cw_serde] pub enum AccountKind { @@ -15,6 +16,8 @@ impl fmt::Display for AccountKind { } #[cw_serde] +#[derive(Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi)] pub enum BorrowTarget { Deposit, Wallet, diff --git a/packages/health-types/src/health.rs b/packages/health-types/src/health.rs index 35a9d9e65..5c55d5ced 100644 --- a/packages/health-types/src/health.rs +++ b/packages/health-types/src/health.rs @@ -2,6 +2,7 @@ use std::fmt; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Decimal, Uint128}; +use tsify::Tsify; #[cw_serde] pub struct Health { @@ -51,6 +52,8 @@ pub fn is_below_one(health_factor: &Option) -> bool { } #[cw_serde] +#[derive(Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi)] pub struct HealthValuesResponse { pub total_debt_value: Uint128, pub total_collateral_value: Uint128, diff --git a/scripts/health/DataFetcher.ts b/scripts/health/DataFetcher.ts index cd8b8d12a..99497f661 100644 --- a/scripts/health/DataFetcher.ts +++ b/scripts/health/DataFetcher.ts @@ -1,9 +1,11 @@ import { Positions } from '../types/generated/mars-credit-manager/MarsCreditManager.types' import { MarsCreditManagerQueryClient } from '../types/generated/mars-credit-manager/MarsCreditManager.client' import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient' -import { HealthValuesResponse } from '../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' import { AccountKind, + HealthValuesResponse, +} from '../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' +import { DenomsData, HealthComputer, VaultsData, @@ -11,17 +13,18 @@ import { import { MarsMockOracleQueryClient } from '../types/generated/mars-mock-oracle/MarsMockOracle.client' import { MarsMockVaultQueryClient } from '../types/generated/mars-mock-vault/MarsMockVault.client' import { MarsParamsQueryClient } from '../types/generated/mars-params/MarsParams.client' - -export enum BorrowTarget { - DEPOSIT = 'deposit', - WALLET = 'wallet', -} +import { + BorrowTarget, + compute_health_js, + max_borrow_estimate_js, + max_withdraw_estimate_js, +} from './pkg-web' export class DataFetcher { constructor( - private computeHealthFn: (h: HealthComputer) => HealthValuesResponse, - private maxWithdrawFn: (h: HealthComputer, denom: string) => string, - private maxBorrowFn: (h: HealthComputer, denom: string, target: BorrowTarget) => string, + private computeHealthFn: typeof compute_health_js, + private maxWithdrawFn: typeof max_withdraw_estimate_js, + private maxBorrowFn: typeof max_borrow_estimate_js, private creditManagerAddr: string, private oracleAddr: string, private paramsAddr: string, diff --git a/scripts/health/example-node.ts b/scripts/health/example-node.ts index 41d444d3d..e5ec2b2fe 100644 --- a/scripts/health/example-node.ts +++ b/scripts/health/example-node.ts @@ -1,4 +1,4 @@ -import { BorrowTarget, DataFetcher } from './DataFetcher' +import { DataFetcher } from './DataFetcher' import { compute_health_js, max_withdraw_estimate_js, max_borrow_estimate_js } from './pkg-node' import { osmosisTestnetConfig } from '../deploy/osmosis/testnet-config' import OsmosisAddresses from '../deploy/addresses/osmo-test-5-testnet-deployer-owner.json' @@ -16,6 +16,6 @@ import OsmosisAddresses from '../deploy/addresses/osmo-test-5-testnet-deployer-o console.log(health) const max_withdraw = await dataFetcher.maxWithdrawAmount('2', 'uosmo') console.log(max_withdraw) - const max_borrow = await dataFetcher.maxBorrowAmount('2', 'uosmo', BorrowTarget.DEPOSIT) + const max_borrow = await dataFetcher.maxBorrowAmount('2', 'uosmo', 'deposit') console.log(max_borrow) })() diff --git a/scripts/health/pkg-node/index.d.ts b/scripts/health/pkg-node/index.d.ts index 4eb55d35a..f85c51620 100644 --- a/scripts/health/pkg-node/index.d.ts +++ b/scripts/health/pkg-node/index.d.ts @@ -1,20 +1,43 @@ /* tslint:disable */ /* eslint-disable */ /** - * @param {any} health_computer - * @returns {any} + * @param {HealthComputer} c + * @returns {HealthValuesResponse} */ -export function compute_health_js(health_computer: any): any +export function compute_health_js(c: HealthComputer): HealthValuesResponse /** - * @param {any} health_computer - * @param {any} withdraw_denom - * @returns {any} + * @param {HealthComputer} c + * @param {string} withdraw_denom + * @returns {string} */ -export function max_withdraw_estimate_js(health_computer: any, withdraw_denom: any): any +export function max_withdraw_estimate_js(c: HealthComputer, withdraw_denom: string): string /** - * @param {any} health_computer - * @param {any} borrow_denom - * @param {any} target - * @returns {any} + * @param {HealthComputer} c + * @param {string} borrow_denom + * @param {BorrowTarget} target + * @returns {string} */ -export function max_borrow_estimate_js(health_computer: any, borrow_denom: any, target: any): any +export function max_borrow_estimate_js( + c: HealthComputer, + borrow_denom: string, + target: BorrowTarget, +): string +export interface HealthComputer { + kind: AccountKind + positions: Positions + denoms_data: DenomsData + vaults_data: VaultsData +} + +export interface HealthValuesResponse { + total_debt_value: Uint128 + total_collateral_value: Uint128 + max_ltv_adjusted_collateral: Uint128 + liquidation_threshold_adjusted_collateral: Uint128 + max_ltv_health_factor: Decimal | null + liquidation_health_factor: Decimal | null + liquidatable: boolean + above_max_ltv: boolean +} + +export type BorrowTarget = 'deposit' | 'wallet' diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js index 45ad2e94b..061e4324e 100644 --- a/scripts/health/pkg-node/index.js +++ b/scripts/health/pkg-node/index.js @@ -13,6 +13,15 @@ function getObject(idx) { let heap_next = heap.length +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1) + const idx = heap_next + heap_next = heap[idx] + + heap[idx] = obj + return idx +} + function dropObject(idx) { if (idx < 132) return heap[idx] = heap_next @@ -112,133 +121,62 @@ function getStringFromWasm0(ptr, len) { ptr = ptr >>> 0 return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)) } - -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1) - const idx = heap_next - heap_next = heap[idx] - - heap[idx] = obj - return idx -} - -let cachedFloat64Memory0 = null - -function getFloat64Memory0() { - if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) { - cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer) - } - return cachedFloat64Memory0 -} - -let cachedBigInt64Memory0 = null - -function getBigInt64Memory0() { - if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) { - cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer) - } - return cachedBigInt64Memory0 -} - -function debugString(val) { - // primitive types - const type = typeof val - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}` - } - if (type == 'string') { - return `"${val}"` - } - if (type == 'symbol') { - const description = val.description - if (description == null) { - return 'Symbol' - } else { - return `Symbol(${description})` - } - } - if (type == 'function') { - const name = val.name - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})` - } else { - return 'Function' - } - } - // objects - if (Array.isArray(val)) { - const length = val.length - let debug = '[' - if (length > 0) { - debug += debugString(val[0]) - } - for (let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]) - } - debug += ']' - return debug - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)) - let className - if (builtInMatches.length > 1) { - className = builtInMatches[1] - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val) - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')' - } catch (_) { - return 'Object' - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}` - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className -} /** - * @param {any} health_computer - * @returns {any} + * @param {HealthComputer} c + * @returns {HealthValuesResponse} */ -module.exports.compute_health_js = function (health_computer) { - const ret = wasm.compute_health_js(addHeapObject(health_computer)) +module.exports.compute_health_js = function (c) { + const ret = wasm.compute_health_js(addHeapObject(c)) return takeObject(ret) } /** - * @param {any} health_computer - * @param {any} withdraw_denom - * @returns {any} + * @param {HealthComputer} c + * @param {string} withdraw_denom + * @returns {string} */ -module.exports.max_withdraw_estimate_js = function (health_computer, withdraw_denom) { - const ret = wasm.max_withdraw_estimate_js( - addHeapObject(health_computer), - addHeapObject(withdraw_denom), - ) - return takeObject(ret) +module.exports.max_withdraw_estimate_js = function (c, withdraw_denom) { + let deferred2_0 + let deferred2_1 + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16) + const ptr0 = passStringToWasm0(withdraw_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + wasm.max_withdraw_estimate_js(retptr, addHeapObject(c), ptr0, len0) + var r0 = getInt32Memory0()[retptr / 4 + 0] + var r1 = getInt32Memory0()[retptr / 4 + 1] + deferred2_0 = r0 + deferred2_1 = r1 + return getStringFromWasm0(r0, r1) + } finally { + wasm.__wbindgen_add_to_stack_pointer(16) + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1) + } } /** - * @param {any} health_computer - * @param {any} borrow_denom - * @param {any} target - * @returns {any} + * @param {HealthComputer} c + * @param {string} borrow_denom + * @param {BorrowTarget} target + * @returns {string} */ -module.exports.max_borrow_estimate_js = function (health_computer, borrow_denom, target) { - const ret = wasm.max_borrow_estimate_js( - addHeapObject(health_computer), - addHeapObject(borrow_denom), - addHeapObject(target), - ) - return takeObject(ret) +module.exports.max_borrow_estimate_js = function (c, borrow_denom, target) { + let deferred2_0 + let deferred2_1 + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16) + const ptr0 = passStringToWasm0(borrow_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + wasm.max_borrow_estimate_js(retptr, addHeapObject(c), ptr0, len0, addHeapObject(target)) + var r0 = getInt32Memory0()[retptr / 4 + 0] + var r1 = getInt32Memory0()[retptr / 4 + 1] + deferred2_0 = r0 + deferred2_1 = r1 + return getStringFromWasm0(r0, r1) + } finally { + wasm.__wbindgen_add_to_stack_pointer(16) + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1) + } } function handleError(f, args) { @@ -249,14 +187,9 @@ function handleError(f, args) { } } -module.exports.__wbindgen_object_drop_ref = function (arg0) { - takeObject(arg0) -} - -module.exports.__wbindgen_is_object = function (arg0) { - const val = getObject(arg0) - const ret = typeof val === 'object' && val !== null - return ret +module.exports.__wbindgen_object_clone_ref = function (arg0) { + const ret = getObject(arg0) + return addHeapObject(ret) } module.exports.__wbindgen_is_undefined = function (arg0) { @@ -264,9 +197,8 @@ module.exports.__wbindgen_is_undefined = function (arg0) { return ret } -module.exports.__wbindgen_in = function (arg0, arg1) { - const ret = getObject(arg0) in getObject(arg1) - return ret +module.exports.__wbindgen_object_drop_ref = function (arg0) { + takeObject(arg0) } module.exports.__wbindgen_string_get = function (arg0, arg1) { @@ -280,234 +212,24 @@ module.exports.__wbindgen_string_get = function (arg0, arg1) { getInt32Memory0()[arg0 / 4 + 0] = ptr1 } -module.exports.__wbindgen_error_new = function (arg0, arg1) { - const ret = new Error(getStringFromWasm0(arg0, arg1)) - return addHeapObject(ret) -} - -module.exports.__wbindgen_is_string = function (arg0) { - const ret = typeof getObject(arg0) === 'string' - return ret -} - -module.exports.__wbindgen_boolean_get = function (arg0) { - const v = getObject(arg0) - const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 - return ret -} - -module.exports.__wbindgen_is_bigint = function (arg0) { - const ret = typeof getObject(arg0) === 'bigint' - return ret -} - -module.exports.__wbindgen_bigint_from_u64 = function (arg0) { - const ret = BigInt.asUintN(64, arg0) - return addHeapObject(ret) -} - -module.exports.__wbindgen_jsval_eq = function (arg0, arg1) { - const ret = getObject(arg0) === getObject(arg1) - return ret -} - -module.exports.__wbg_new_abda76e883ba8a5f = function () { - const ret = new Error() - return addHeapObject(ret) -} - -module.exports.__wbg_stack_658279fe44541cf6 = function (arg0, arg1) { - const ret = getObject(arg1).stack - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) - const len1 = WASM_VECTOR_LEN - getInt32Memory0()[arg0 / 4 + 1] = len1 - getInt32Memory0()[arg0 / 4 + 0] = ptr1 -} - -module.exports.__wbg_error_f851667af71bcfc6 = function (arg0, arg1) { - let deferred0_0 - let deferred0_1 - try { - deferred0_0 = arg0 - deferred0_1 = arg1 - console.error(getStringFromWasm0(arg0, arg1)) - } finally { - wasm.__wbindgen_free(deferred0_0, deferred0_1, 1) - } -} - -module.exports.__wbindgen_jsval_loose_eq = function (arg0, arg1) { - const ret = getObject(arg0) == getObject(arg1) - return ret -} - -module.exports.__wbindgen_number_get = function (arg0, arg1) { - const obj = getObject(arg1) - const ret = typeof obj === 'number' ? obj : undefined - getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret - getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret) -} - -module.exports.__wbindgen_object_clone_ref = function (arg0) { - const ret = getObject(arg0) - return addHeapObject(ret) -} - -module.exports.__wbindgen_string_new = function (arg0, arg1) { - const ret = getStringFromWasm0(arg0, arg1) - return addHeapObject(ret) -} - -module.exports.__wbg_getwithrefkey_5e6d9547403deab8 = function (arg0, arg1) { - const ret = getObject(arg0)[getObject(arg1)] - return addHeapObject(ret) -} - -module.exports.__wbg_set_841ac57cff3d672b = function (arg0, arg1, arg2) { - getObject(arg0)[takeObject(arg1)] = takeObject(arg2) -} - -module.exports.__wbg_get_44be0491f933a435 = function (arg0, arg1) { - const ret = getObject(arg0)[arg1 >>> 0] - return addHeapObject(ret) -} - -module.exports.__wbg_length_fff51ee6522a1a18 = function (arg0) { - const ret = getObject(arg0).length - return ret -} - -module.exports.__wbindgen_is_function = function (arg0) { - const ret = typeof getObject(arg0) === 'function' - return ret -} - -module.exports.__wbg_next_526fc47e980da008 = function (arg0) { - const ret = getObject(arg0).next - return addHeapObject(ret) -} - -module.exports.__wbg_next_ddb3312ca1c4e32a = function () { - return handleError(function (arg0) { - const ret = getObject(arg0).next() - return addHeapObject(ret) - }, arguments) -} - -module.exports.__wbg_done_5c1f01fb660d73b5 = function (arg0) { - const ret = getObject(arg0).done - return ret -} - -module.exports.__wbg_value_1695675138684bd5 = function (arg0) { - const ret = getObject(arg0).value - return addHeapObject(ret) -} - -module.exports.__wbg_iterator_97f0c81209c6c35a = function () { - const ret = Symbol.iterator - return addHeapObject(ret) -} - -module.exports.__wbg_get_97b561fb56f034b5 = function () { +module.exports.__wbg_parse_670c19d4e984792e = function () { return handleError(function (arg0, arg1) { - const ret = Reflect.get(getObject(arg0), getObject(arg1)) + const ret = JSON.parse(getStringFromWasm0(arg0, arg1)) return addHeapObject(ret) }, arguments) } -module.exports.__wbg_call_cb65541d95d71282 = function () { - return handleError(function (arg0, arg1) { - const ret = getObject(arg0).call(getObject(arg1)) +module.exports.__wbg_stringify_e25465938f3f611f = function () { + return handleError(function (arg0) { + const ret = JSON.stringify(getObject(arg0)) return addHeapObject(ret) }, arguments) } -module.exports.__wbg_new_b51585de1b234aff = function () { - const ret = new Object() - return addHeapObject(ret) -} - -module.exports.__wbg_isArray_4c24b343cb13cfb1 = function (arg0) { - const ret = Array.isArray(getObject(arg0)) - return ret -} - -module.exports.__wbg_instanceof_ArrayBuffer_39ac22089b74fddb = function (arg0) { - let result - try { - result = getObject(arg0) instanceof ArrayBuffer - } catch { - result = false - } - const ret = result - return ret -} - -module.exports.__wbg_isSafeInteger_bb8e18dd21c97288 = function (arg0) { - const ret = Number.isSafeInteger(getObject(arg0)) - return ret -} - -module.exports.__wbg_entries_e51f29c7bba0c054 = function (arg0) { - const ret = Object.entries(getObject(arg0)) - return addHeapObject(ret) -} - -module.exports.__wbg_buffer_085ec1f694018c4f = function (arg0) { - const ret = getObject(arg0).buffer - return addHeapObject(ret) -} - -module.exports.__wbg_new_8125e318e6245eed = function (arg0) { - const ret = new Uint8Array(getObject(arg0)) - return addHeapObject(ret) -} - -module.exports.__wbg_set_5cf90238115182c3 = function (arg0, arg1, arg2) { - getObject(arg0).set(getObject(arg1), arg2 >>> 0) -} - -module.exports.__wbg_length_72e2208bbc0efc61 = function (arg0) { - const ret = getObject(arg0).length - return ret -} - -module.exports.__wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 = function (arg0) { - let result - try { - result = getObject(arg0) instanceof Uint8Array - } catch { - result = false - } - const ret = result - return ret -} - -module.exports.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) { - const v = getObject(arg1) - const ret = typeof v === 'bigint' ? v : undefined - getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret - getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret) -} - -module.exports.__wbindgen_debug_string = function (arg0, arg1) { - const ret = debugString(getObject(arg1)) - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) - const len1 = WASM_VECTOR_LEN - getInt32Memory0()[arg0 / 4 + 1] = len1 - getInt32Memory0()[arg0 / 4 + 0] = ptr1 -} - module.exports.__wbindgen_throw = function (arg0, arg1) { throw new Error(getStringFromWasm0(arg0, arg1)) } -module.exports.__wbindgen_memory = function () { - const ret = wasm.memory - return addHeapObject(ret) -} - const path = require('path').join(__dirname, 'index_bg.wasm') const bytes = require('fs').readFileSync(path) diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index 14b08ac1823ad82ade0dedb75ca469664571992b..61e320e62bd0958a136a9130ecdac95d570cd2a2 100644 GIT binary patch literal 236591 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%0D$bblJ;N{if;~n@> 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 diff --git a/scripts/health/pkg-node/index_bg.wasm.d.ts b/scripts/health/pkg-node/index_bg.wasm.d.ts index d1cdc713e..0bb9b4cc2 100644 --- a/scripts/health/pkg-node/index_bg.wasm.d.ts +++ b/scripts/health/pkg-node/index_bg.wasm.d.ts @@ -2,8 +2,8 @@ /* eslint-disable */ export const memory: WebAssembly.Memory export function compute_health_js(a: number): number -export function max_withdraw_estimate_js(a: number, b: number): number -export function max_borrow_estimate_js(a: number, b: number, c: number): number +export function max_withdraw_estimate_js(a: number, b: number, c: number, d: number): void +export function max_borrow_estimate_js(a: number, b: number, c: number, d: number, e: number): void export function allocate(a: number): number export function deallocate(a: number): void export function requires_stargate(): void @@ -11,5 +11,6 @@ export function requires_iterator(): void export function interface_version_8(): void export function __wbindgen_malloc(a: number, b: number): number export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number +export function __wbindgen_add_to_stack_pointer(a: number): number export function __wbindgen_free(a: number, b: number, c: number): void export function __wbindgen_exn_store(a: number): void diff --git a/scripts/health/pkg-node/package.json b/scripts/health/pkg-node/package.json index fe512637d..43efde671 100644 --- a/scripts/health/pkg-node/package.json +++ b/scripts/health/pkg-node/package.json @@ -3,7 +3,8 @@ "collaborators": [ "Gabe R. ", "Larry Engineer ", - "Piotr Babel " + "Piotr B. ", + "Brianna M. " ], "version": "2.0.0", "files": [ diff --git a/scripts/health/pkg-web/index.d.ts b/scripts/health/pkg-web/index.d.ts index ff9825e73..97fb27879 100644 --- a/scripts/health/pkg-web/index.d.ts +++ b/scripts/health/pkg-web/index.d.ts @@ -1,31 +1,54 @@ /* tslint:disable */ /* eslint-disable */ /** - * @param {any} health_computer - * @returns {any} + * @param {HealthComputer} c + * @returns {HealthValuesResponse} */ -export function compute_health_js(health_computer: any): any +export function compute_health_js(c: HealthComputer): HealthValuesResponse /** - * @param {any} health_computer - * @param {any} withdraw_denom - * @returns {any} + * @param {HealthComputer} c + * @param {string} withdraw_denom + * @returns {string} */ -export function max_withdraw_estimate_js(health_computer: any, withdraw_denom: any): any +export function max_withdraw_estimate_js(c: HealthComputer, withdraw_denom: string): string /** - * @param {any} health_computer - * @param {any} borrow_denom - * @param {any} target - * @returns {any} + * @param {HealthComputer} c + * @param {string} borrow_denom + * @param {BorrowTarget} target + * @returns {string} */ -export function max_borrow_estimate_js(health_computer: any, borrow_denom: any, target: any): any +export function max_borrow_estimate_js( + c: HealthComputer, + borrow_denom: string, + target: BorrowTarget, +): string +export interface HealthComputer { + kind: AccountKind + positions: Positions + denoms_data: DenomsData + vaults_data: VaultsData +} + +export interface HealthValuesResponse { + total_debt_value: Uint128 + total_collateral_value: Uint128 + max_ltv_adjusted_collateral: Uint128 + liquidation_threshold_adjusted_collateral: Uint128 + max_ltv_health_factor: Decimal | null + liquidation_health_factor: Decimal | null + liquidatable: boolean + above_max_ltv: boolean +} + +export type BorrowTarget = 'deposit' | 'wallet' export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module export interface InitOutput { readonly memory: WebAssembly.Memory readonly compute_health_js: (a: number) => number - readonly max_withdraw_estimate_js: (a: number, b: number) => number - readonly max_borrow_estimate_js: (a: number, b: number, c: number) => number + readonly max_withdraw_estimate_js: (a: number, b: number, c: number, d: number) => void + readonly max_borrow_estimate_js: (a: number, b: number, c: number, d: number, e: number) => void readonly allocate: (a: number) => number readonly deallocate: (a: number) => void readonly requires_stargate: () => void @@ -33,6 +56,7 @@ export interface InitOutput { readonly interface_version_8: () => void readonly __wbindgen_malloc: (a: number, b: number) => number readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number + readonly __wbindgen_add_to_stack_pointer: (a: number) => number readonly __wbindgen_free: (a: number, b: number, c: number) => void readonly __wbindgen_exn_store: (a: number) => void } diff --git a/scripts/health/pkg-web/index.js b/scripts/health/pkg-web/index.js index 39ba49197..e45fa0e5a 100644 --- a/scripts/health/pkg-web/index.js +++ b/scripts/health/pkg-web/index.js @@ -10,6 +10,15 @@ function getObject(idx) { let heap_next = heap.length +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1) + const idx = heap_next + heap_next = heap[idx] + + heap[idx] = obj + return idx +} + function dropObject(idx) { if (idx < 132) return heap[idx] = heap_next @@ -125,133 +134,62 @@ function getStringFromWasm0(ptr, len) { ptr = ptr >>> 0 return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)) } - -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1) - const idx = heap_next - heap_next = heap[idx] - - heap[idx] = obj - return idx -} - -let cachedFloat64Memory0 = null - -function getFloat64Memory0() { - if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) { - cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer) - } - return cachedFloat64Memory0 -} - -let cachedBigInt64Memory0 = null - -function getBigInt64Memory0() { - if (cachedBigInt64Memory0 === null || cachedBigInt64Memory0.byteLength === 0) { - cachedBigInt64Memory0 = new BigInt64Array(wasm.memory.buffer) - } - return cachedBigInt64Memory0 -} - -function debugString(val) { - // primitive types - const type = typeof val - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}` - } - if (type == 'string') { - return `"${val}"` - } - if (type == 'symbol') { - const description = val.description - if (description == null) { - return 'Symbol' - } else { - return `Symbol(${description})` - } - } - if (type == 'function') { - const name = val.name - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})` - } else { - return 'Function' - } - } - // objects - if (Array.isArray(val)) { - const length = val.length - let debug = '[' - if (length > 0) { - debug += debugString(val[0]) - } - for (let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]) - } - debug += ']' - return debug - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)) - let className - if (builtInMatches.length > 1) { - className = builtInMatches[1] - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val) - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')' - } catch (_) { - return 'Object' - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}` - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className -} /** - * @param {any} health_computer - * @returns {any} + * @param {HealthComputer} c + * @returns {HealthValuesResponse} */ -export function compute_health_js(health_computer) { - const ret = wasm.compute_health_js(addHeapObject(health_computer)) +export function compute_health_js(c) { + const ret = wasm.compute_health_js(addHeapObject(c)) return takeObject(ret) } /** - * @param {any} health_computer - * @param {any} withdraw_denom - * @returns {any} + * @param {HealthComputer} c + * @param {string} withdraw_denom + * @returns {string} */ -export function max_withdraw_estimate_js(health_computer, withdraw_denom) { - const ret = wasm.max_withdraw_estimate_js( - addHeapObject(health_computer), - addHeapObject(withdraw_denom), - ) - return takeObject(ret) +export function max_withdraw_estimate_js(c, withdraw_denom) { + let deferred2_0 + let deferred2_1 + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16) + const ptr0 = passStringToWasm0(withdraw_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + wasm.max_withdraw_estimate_js(retptr, addHeapObject(c), ptr0, len0) + var r0 = getInt32Memory0()[retptr / 4 + 0] + var r1 = getInt32Memory0()[retptr / 4 + 1] + deferred2_0 = r0 + deferred2_1 = r1 + return getStringFromWasm0(r0, r1) + } finally { + wasm.__wbindgen_add_to_stack_pointer(16) + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1) + } } /** - * @param {any} health_computer - * @param {any} borrow_denom - * @param {any} target - * @returns {any} + * @param {HealthComputer} c + * @param {string} borrow_denom + * @param {BorrowTarget} target + * @returns {string} */ -export function max_borrow_estimate_js(health_computer, borrow_denom, target) { - const ret = wasm.max_borrow_estimate_js( - addHeapObject(health_computer), - addHeapObject(borrow_denom), - addHeapObject(target), - ) - return takeObject(ret) +export function max_borrow_estimate_js(c, borrow_denom, target) { + let deferred2_0 + let deferred2_1 + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16) + const ptr0 = passStringToWasm0(borrow_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + wasm.max_borrow_estimate_js(retptr, addHeapObject(c), ptr0, len0, addHeapObject(target)) + var r0 = getInt32Memory0()[retptr / 4 + 0] + var r1 = getInt32Memory0()[retptr / 4 + 1] + deferred2_0 = r0 + deferred2_1 = r1 + return getStringFromWasm0(r0, r1) + } finally { + wasm.__wbindgen_add_to_stack_pointer(16) + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1) + } } function handleError(f, args) { @@ -295,21 +233,16 @@ async function __wbg_load(module, imports) { function __wbg_get_imports() { const imports = {} imports.wbg = {} - imports.wbg.__wbindgen_object_drop_ref = function (arg0) { - takeObject(arg0) - } - imports.wbg.__wbindgen_is_object = function (arg0) { - const val = getObject(arg0) - const ret = typeof val === 'object' && val !== null - return ret + imports.wbg.__wbindgen_object_clone_ref = function (arg0) { + const ret = getObject(arg0) + return addHeapObject(ret) } imports.wbg.__wbindgen_is_undefined = function (arg0) { const ret = getObject(arg0) === undefined return ret } - imports.wbg.__wbindgen_in = function (arg0, arg1) { - const ret = getObject(arg0) in getObject(arg1) - return ret + imports.wbg.__wbindgen_object_drop_ref = function (arg0) { + takeObject(arg0) } imports.wbg.__wbindgen_string_get = function (arg0, arg1) { const obj = getObject(arg1) @@ -321,195 +254,21 @@ function __wbg_get_imports() { getInt32Memory0()[arg0 / 4 + 1] = len1 getInt32Memory0()[arg0 / 4 + 0] = ptr1 } - imports.wbg.__wbindgen_error_new = function (arg0, arg1) { - const ret = new Error(getStringFromWasm0(arg0, arg1)) - return addHeapObject(ret) - } - imports.wbg.__wbindgen_is_string = function (arg0) { - const ret = typeof getObject(arg0) === 'string' - return ret - } - imports.wbg.__wbindgen_boolean_get = function (arg0) { - const v = getObject(arg0) - const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 - return ret - } - imports.wbg.__wbindgen_is_bigint = function (arg0) { - const ret = typeof getObject(arg0) === 'bigint' - return ret - } - imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) { - const ret = BigInt.asUintN(64, arg0) - return addHeapObject(ret) - } - imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) { - const ret = getObject(arg0) === getObject(arg1) - return ret - } - imports.wbg.__wbg_new_abda76e883ba8a5f = function () { - const ret = new Error() - return addHeapObject(ret) - } - imports.wbg.__wbg_stack_658279fe44541cf6 = function (arg0, arg1) { - const ret = getObject(arg1).stack - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) - const len1 = WASM_VECTOR_LEN - getInt32Memory0()[arg0 / 4 + 1] = len1 - getInt32Memory0()[arg0 / 4 + 0] = ptr1 - } - imports.wbg.__wbg_error_f851667af71bcfc6 = function (arg0, arg1) { - let deferred0_0 - let deferred0_1 - try { - deferred0_0 = arg0 - deferred0_1 = arg1 - console.error(getStringFromWasm0(arg0, arg1)) - } finally { - wasm.__wbindgen_free(deferred0_0, deferred0_1, 1) - } - } - imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) { - const ret = getObject(arg0) == getObject(arg1) - return ret - } - imports.wbg.__wbindgen_number_get = function (arg0, arg1) { - const obj = getObject(arg1) - const ret = typeof obj === 'number' ? obj : undefined - getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret - getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret) - } - imports.wbg.__wbindgen_object_clone_ref = function (arg0) { - const ret = getObject(arg0) - return addHeapObject(ret) - } - imports.wbg.__wbindgen_string_new = function (arg0, arg1) { - const ret = getStringFromWasm0(arg0, arg1) - return addHeapObject(ret) - } - imports.wbg.__wbg_getwithrefkey_5e6d9547403deab8 = function (arg0, arg1) { - const ret = getObject(arg0)[getObject(arg1)] - return addHeapObject(ret) - } - imports.wbg.__wbg_set_841ac57cff3d672b = function (arg0, arg1, arg2) { - getObject(arg0)[takeObject(arg1)] = takeObject(arg2) - } - imports.wbg.__wbg_get_44be0491f933a435 = function (arg0, arg1) { - const ret = getObject(arg0)[arg1 >>> 0] - return addHeapObject(ret) - } - imports.wbg.__wbg_length_fff51ee6522a1a18 = function (arg0) { - const ret = getObject(arg0).length - return ret - } - imports.wbg.__wbindgen_is_function = function (arg0) { - const ret = typeof getObject(arg0) === 'function' - return ret - } - imports.wbg.__wbg_next_526fc47e980da008 = function (arg0) { - const ret = getObject(arg0).next - return addHeapObject(ret) - } - imports.wbg.__wbg_next_ddb3312ca1c4e32a = function () { - return handleError(function (arg0) { - const ret = getObject(arg0).next() - return addHeapObject(ret) - }, arguments) - } - imports.wbg.__wbg_done_5c1f01fb660d73b5 = function (arg0) { - const ret = getObject(arg0).done - return ret - } - imports.wbg.__wbg_value_1695675138684bd5 = function (arg0) { - const ret = getObject(arg0).value - return addHeapObject(ret) - } - imports.wbg.__wbg_iterator_97f0c81209c6c35a = function () { - const ret = Symbol.iterator - return addHeapObject(ret) - } - imports.wbg.__wbg_get_97b561fb56f034b5 = function () { + imports.wbg.__wbg_parse_670c19d4e984792e = function () { return handleError(function (arg0, arg1) { - const ret = Reflect.get(getObject(arg0), getObject(arg1)) + const ret = JSON.parse(getStringFromWasm0(arg0, arg1)) return addHeapObject(ret) }, arguments) } - imports.wbg.__wbg_call_cb65541d95d71282 = function () { - return handleError(function (arg0, arg1) { - const ret = getObject(arg0).call(getObject(arg1)) + imports.wbg.__wbg_stringify_e25465938f3f611f = function () { + return handleError(function (arg0) { + const ret = JSON.stringify(getObject(arg0)) return addHeapObject(ret) }, arguments) } - imports.wbg.__wbg_new_b51585de1b234aff = function () { - const ret = new Object() - return addHeapObject(ret) - } - imports.wbg.__wbg_isArray_4c24b343cb13cfb1 = function (arg0) { - const ret = Array.isArray(getObject(arg0)) - return ret - } - imports.wbg.__wbg_instanceof_ArrayBuffer_39ac22089b74fddb = function (arg0) { - let result - try { - result = getObject(arg0) instanceof ArrayBuffer - } catch { - result = false - } - const ret = result - return ret - } - imports.wbg.__wbg_isSafeInteger_bb8e18dd21c97288 = function (arg0) { - const ret = Number.isSafeInteger(getObject(arg0)) - return ret - } - imports.wbg.__wbg_entries_e51f29c7bba0c054 = function (arg0) { - const ret = Object.entries(getObject(arg0)) - return addHeapObject(ret) - } - imports.wbg.__wbg_buffer_085ec1f694018c4f = function (arg0) { - const ret = getObject(arg0).buffer - return addHeapObject(ret) - } - imports.wbg.__wbg_new_8125e318e6245eed = function (arg0) { - const ret = new Uint8Array(getObject(arg0)) - return addHeapObject(ret) - } - imports.wbg.__wbg_set_5cf90238115182c3 = function (arg0, arg1, arg2) { - getObject(arg0).set(getObject(arg1), arg2 >>> 0) - } - imports.wbg.__wbg_length_72e2208bbc0efc61 = function (arg0) { - const ret = getObject(arg0).length - return ret - } - imports.wbg.__wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 = function (arg0) { - let result - try { - result = getObject(arg0) instanceof Uint8Array - } catch { - result = false - } - const ret = result - return ret - } - imports.wbg.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) { - const v = getObject(arg1) - const ret = typeof v === 'bigint' ? v : undefined - getBigInt64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? BigInt(0) : ret - getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret) - } - imports.wbg.__wbindgen_debug_string = function (arg0, arg1) { - const ret = debugString(getObject(arg1)) - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) - const len1 = WASM_VECTOR_LEN - getInt32Memory0()[arg0 / 4 + 1] = len1 - getInt32Memory0()[arg0 / 4 + 0] = ptr1 - } imports.wbg.__wbindgen_throw = function (arg0, arg1) { throw new Error(getStringFromWasm0(arg0, arg1)) } - imports.wbg.__wbindgen_memory = function () { - const ret = wasm.memory - return addHeapObject(ret) - } return imports } @@ -519,8 +278,6 @@ function __wbg_init_memory(imports, maybe_memory) {} function __wbg_finalize_init(instance, module) { wasm = instance.exports __wbg_init.__wbindgen_wasm_module = module - cachedBigInt64Memory0 = null - cachedFloat64Memory0 = null cachedInt32Memory0 = null cachedUint8Memory0 = null diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm index da9085229bc82a6b0bfde80ea3e0df6427714c35..0db4a61ff17f3f942707203733773be7c0b8b1fb 100644 GIT binary patch literal 236444 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%0DhdUkwH&5gFvuO0ip|Il=n$WJB~2*&U&z(QHe% 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 literal 175181 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%0DNN!QxG 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�X>HNmE6k|i*_&a18| zDcpTBw5D3SPlBqoHBAjox!J4dO?9T1g>FG>bH=l+(>uCs;}#>`&N#2NeKqTuqf_#G Jo56mS{|`vjh426X diff --git a/scripts/health/pkg-web/index_bg.wasm.d.ts b/scripts/health/pkg-web/index_bg.wasm.d.ts index d1cdc713e..0bb9b4cc2 100644 --- a/scripts/health/pkg-web/index_bg.wasm.d.ts +++ b/scripts/health/pkg-web/index_bg.wasm.d.ts @@ -2,8 +2,8 @@ /* eslint-disable */ export const memory: WebAssembly.Memory export function compute_health_js(a: number): number -export function max_withdraw_estimate_js(a: number, b: number): number -export function max_borrow_estimate_js(a: number, b: number, c: number): number +export function max_withdraw_estimate_js(a: number, b: number, c: number, d: number): void +export function max_borrow_estimate_js(a: number, b: number, c: number, d: number, e: number): void export function allocate(a: number): number export function deallocate(a: number): void export function requires_stargate(): void @@ -11,5 +11,6 @@ export function requires_iterator(): void export function interface_version_8(): void export function __wbindgen_malloc(a: number, b: number): number export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number +export function __wbindgen_add_to_stack_pointer(a: number): number export function __wbindgen_free(a: number, b: number, c: number): void export function __wbindgen_exn_store(a: number): void diff --git a/scripts/health/pkg-web/package.json b/scripts/health/pkg-web/package.json index 777dd0e5c..2520117e2 100644 --- a/scripts/health/pkg-web/package.json +++ b/scripts/health/pkg-web/package.json @@ -3,7 +3,8 @@ "collaborators": [ "Gabe R. ", "Larry Engineer ", - "Piotr Babel " + "Piotr B. ", + "Brianna M. " ], "version": "2.0.0", "files": [ diff --git a/scripts/package.json b/scripts/package.json index ce6498e77..a05f9b382 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -36,8 +36,8 @@ "@babel/preset-env": "^7.22.9", "@babel/preset-typescript": "^7.22.5", "@types/jest": "^29.5.3", - "@typescript-eslint/eslint-plugin": "^6.1.0", - "@typescript-eslint/parser": "^6.1.0", + "@typescript-eslint/eslint-plugin": "^6.2.0", + "@typescript-eslint/parser": "^6.2.0", "eslint": "^8.45.0", "eslint-config-prettier": "^8.8.0", "jest": "^29.6.1", diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 1b62982c0..050c365f1 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -2200,16 +2200,16 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.1.0.tgz#96f3ca6615717659d06c9f7161a1d14ab0c49c66" - integrity sha512-qg7Bm5TyP/I7iilGyp6DRqqkt8na00lI6HbjWZObgk3FFSzH5ypRwAHXJhJkwiRtTcfn+xYQIMOR5kJgpo6upw== +"@typescript-eslint/eslint-plugin@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.2.0.tgz#57047c400be0632d4797ac081af8d399db3ebc3b" + integrity sha512-rClGrMuyS/3j0ETa1Ui7s6GkLhfZGKZL3ZrChLeAiACBE/tRc1wq8SNZESUuluxhLj9FkUefRs2l6bCIArWBiQ== dependencies: "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.1.0" - "@typescript-eslint/type-utils" "6.1.0" - "@typescript-eslint/utils" "6.1.0" - "@typescript-eslint/visitor-keys" "6.1.0" + "@typescript-eslint/scope-manager" "6.2.0" + "@typescript-eslint/type-utils" "6.2.0" + "@typescript-eslint/utils" "6.2.0" + "@typescript-eslint/visitor-keys" "6.2.0" debug "^4.3.4" graphemer "^1.4.0" ignore "^5.2.4" @@ -2218,72 +2218,72 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/parser@^6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.1.0.tgz#3135bf65dca5340d8650703eb8cb83113e156ee5" - integrity sha512-hIzCPvX4vDs4qL07SYzyomamcs2/tQYXg5DtdAfj35AyJ5PIUqhsLf4YrEIFzZcND7R2E8tpQIZKayxg8/6Wbw== +"@typescript-eslint/parser@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.2.0.tgz#d37c30b0f459c6f39455335d8f4f085919a1c644" + integrity sha512-igVYOqtiK/UsvKAmmloQAruAdUHihsOCvplJpplPZ+3h4aDkC/UKZZNKgB6h93ayuYLuEymU3h8nF1xMRbh37g== dependencies: - "@typescript-eslint/scope-manager" "6.1.0" - "@typescript-eslint/types" "6.1.0" - "@typescript-eslint/typescript-estree" "6.1.0" - "@typescript-eslint/visitor-keys" "6.1.0" + "@typescript-eslint/scope-manager" "6.2.0" + "@typescript-eslint/types" "6.2.0" + "@typescript-eslint/typescript-estree" "6.2.0" + "@typescript-eslint/visitor-keys" "6.2.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.1.0.tgz#a6cdbe11630614f8c04867858a42dd56590796ed" - integrity sha512-AxjgxDn27hgPpe2rQe19k0tXw84YCOsjDJ2r61cIebq1t+AIxbgiXKvD4999Wk49GVaAcdJ/d49FYel+Pp3jjw== +"@typescript-eslint/scope-manager@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.2.0.tgz#412a710d8fa20bc045533b3b19f423810b24f87a" + integrity sha512-1ZMNVgm5nnHURU8ZSJ3snsHzpFeNK84rdZjluEVBGNu7jDymfqceB3kdIZ6A4xCfEFFhRIB6rF8q/JIqJd2R0Q== dependencies: - "@typescript-eslint/types" "6.1.0" - "@typescript-eslint/visitor-keys" "6.1.0" + "@typescript-eslint/types" "6.2.0" + "@typescript-eslint/visitor-keys" "6.2.0" -"@typescript-eslint/type-utils@6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.1.0.tgz#21cc6c3bc1980b03f9eb4e64580d0c5be6f08215" - integrity sha512-kFXBx6QWS1ZZ5Ni89TyT1X9Ag6RXVIVhqDs0vZE/jUeWlBv/ixq2diua6G7ece6+fXw3TvNRxP77/5mOMusx2w== +"@typescript-eslint/type-utils@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.2.0.tgz#02b27a3eeb41aa5460d6275d12cce5dd72e1c9fc" + integrity sha512-DnGZuNU2JN3AYwddYIqrVkYW0uUQdv0AY+kz2M25euVNlujcN2u+rJgfJsBFlUEzBB6OQkUqSZPyuTLf2bP5mw== dependencies: - "@typescript-eslint/typescript-estree" "6.1.0" - "@typescript-eslint/utils" "6.1.0" + "@typescript-eslint/typescript-estree" "6.2.0" + "@typescript-eslint/utils" "6.2.0" debug "^4.3.4" ts-api-utils "^1.0.1" -"@typescript-eslint/types@6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.1.0.tgz#2d607c62827bb416ada5c96ebfa2ef84e45a8dfa" - integrity sha512-+Gfd5NHCpDoHDOaU/yIF3WWRI2PcBRKKpP91ZcVbL0t5tQpqYWBs3z/GGhvU+EV1D0262g9XCnyqQh19prU0JQ== +"@typescript-eslint/types@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.2.0.tgz#b341a4e6d5f609267306b07afc6f62bcf92b1495" + integrity sha512-1nRRaDlp/XYJQLvkQJG5F3uBTno5SHPT7XVcJ5n1/k2WfNI28nJsvLakxwZRNY5spuatEKO7d5nZWsQpkqXwBA== -"@typescript-eslint/typescript-estree@6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.1.0.tgz#ea382f6482ba698d7e993a88ce5391ea7a66c33d" - integrity sha512-nUKAPWOaP/tQjU1IQw9sOPCDavs/iU5iYLiY/6u7gxS7oKQoi4aUxXS1nrrVGTyBBaGesjkcwwHkbkiD5eBvcg== +"@typescript-eslint/typescript-estree@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.0.tgz#4969944b831b481996aa4fbd73c7164ca683b8ef" + integrity sha512-Mts6+3HQMSM+LZCglsc2yMIny37IhUgp1Qe8yJUYVyO6rHP7/vN0vajKu3JvHCBIy8TSiKddJ/Zwu80jhnGj1w== dependencies: - "@typescript-eslint/types" "6.1.0" - "@typescript-eslint/visitor-keys" "6.1.0" + "@typescript-eslint/types" "6.2.0" + "@typescript-eslint/visitor-keys" "6.2.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.1.0.tgz#1641843792b4e3451cc692e2c73055df8b26f453" - integrity sha512-wp652EogZlKmQoMS5hAvWqRKplXvkuOnNzZSE0PVvsKjpexd/XznRVHAtrfHFYmqaJz0DFkjlDsGYC9OXw+OhQ== +"@typescript-eslint/utils@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.2.0.tgz#606a20e5c13883c2d2bd0538ddc4b96b8d410979" + integrity sha512-RCFrC1lXiX1qEZN8LmLrxYRhOkElEsPKTVSNout8DMzf8PeWoQG7Rxz2SadpJa3VSh5oYKGwt7j7X/VRg+Y3OQ== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.12" "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.1.0" - "@typescript-eslint/types" "6.1.0" - "@typescript-eslint/typescript-estree" "6.1.0" + "@typescript-eslint/scope-manager" "6.2.0" + "@typescript-eslint/types" "6.2.0" + "@typescript-eslint/typescript-estree" "6.2.0" semver "^7.5.4" -"@typescript-eslint/visitor-keys@6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.1.0.tgz#d2b84dff6b58944d3257ea03687e269a788c73be" - integrity sha512-yQeh+EXhquh119Eis4k0kYhj9vmFzNpbhM3LftWQVwqVjipCkwHBQOZutcYW+JVkjtTG9k8nrZU1UoNedPDd1A== +"@typescript-eslint/visitor-keys@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.0.tgz#71943f42fdaa2ec86dc3222091f41761a49ae71a" + integrity sha512-QbaYUQVKKo9bgCzpjz45llCfwakyoxHetIy8CAvYCtd16Zu1KrpzNHofwF8kGkpPOxZB2o6kz+0nqH8ZkIzuoQ== dependencies: - "@typescript-eslint/types" "6.1.0" + "@typescript-eslint/types" "6.2.0" eslint-visitor-keys "^3.4.1" acorn-jsx@^5.3.2: From 886384cb84eb2f24ab49413c97ff7643004083cd Mon Sep 17 00:00:00 2001 From: piobab Date: Wed, 26 Jul 2023 14:07:18 +0200 Subject: [PATCH 187/218] Mp 3123 simplify liquidation (#166) Use red bank liquidation package with simplified v1.1 liquidation logic. --- Cargo.lock | 78 ++++++-- Cargo.toml | 4 +- contracts/credit-manager/Cargo.toml | 1 + contracts/credit-manager/src/liquidate.rs | 167 +----------------- ...ver - Dynamic LB & CF test cases v1.1.xlsx | Bin 0 -> 37028 bytes .../tests/test_liquidate_deposit.rs | 52 +++--- .../tests/test_liquidate_lend.rs | 16 +- .../tests/test_liquidate_vault.rs | 32 ++-- contracts/mock-incentives/src/execute.rs | 2 +- contracts/mock-red-bank/src/execute.rs | 4 +- packages/health-types/Cargo.toml | 1 + packages/health-types/src/health.rs | 14 ++ packages/rover/Cargo.toml | 1 + packages/rover/src/error.rs | 4 + 14 files changed, 142 insertions(+), 234 deletions(-) create mode 100644 contracts/credit-manager/tests/files/Rover - Dynamic LB & CF test cases v1.1.xlsx diff --git a/Cargo.lock b/Cargo.lock index 3f3ab81cc..c649793d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1441,7 +1441,7 @@ dependencies = [ "cw721-base 0.16.0", "cw721-base 0.18.0", "mars-mock-rover-health", - "mars-red-bank-types", + "mars-red-bank-types 1.1.0-ntrn-2", "mars-rover-health-types", "serde_json", "thiserror", @@ -1464,13 +1464,14 @@ dependencies = [ "cw721-base 0.18.0", "itertools 0.11.0", "mars-account-nft", + "mars-liquidation", "mars-mock-incentives", "mars-mock-oracle", "mars-mock-red-bank", "mars-mock-vault", "mars-owner", "mars-params", - "mars-red-bank-types", + "mars-red-bank-types 1.1.0-ntrn-2", "mars-rover", "mars-rover-health", "mars-rover-health-types", @@ -1478,6 +1479,28 @@ dependencies = [ "mars-v2-zapper-mock", ] +[[package]] +name = "mars-health" +version = "1.1.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=cf28835abb717b00a2f30eef06db4ba32d3eeae2#cf28835abb717b00a2f30eef06db4ba32d3eeae2" +dependencies = [ + "cosmwasm-std", + "mars-params", + "mars-red-bank-types 1.1.0", + "thiserror", +] + +[[package]] +name = "mars-liquidation" +version = "1.0.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=cf28835abb717b00a2f30eef06db4ba32d3eeae2#cf28835abb717b00a2f30eef06db4ba32d3eeae2" +dependencies = [ + "cosmwasm-std", + "mars-health", + "mars-params", + "thiserror", +] + [[package]] name = "mars-mock-credit-manager" version = "2.0.0" @@ -1498,7 +1521,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", - "mars-red-bank-types", + "mars-red-bank-types 1.1.0-ntrn-2", ] [[package]] @@ -1508,7 +1531,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", - "mars-red-bank-types", + "mars-red-bank-types 1.1.0-ntrn-2", ] [[package]] @@ -1519,7 +1542,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "mars-red-bank-types", + "mars-red-bank-types 1.1.0-ntrn-2", ] [[package]] @@ -1541,7 +1564,7 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard", - "mars-red-bank-types", + "mars-red-bank-types 1.1.0-ntrn-2", "mars-rover", "thiserror", ] @@ -1562,15 +1585,14 @@ dependencies = [ [[package]] name = "mars-params" version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e926698bea464b42ca1f2fd6b2400375a90f6bca84e4eb448310b95145c2e" +source = "git+https://github.com/mars-protocol/red-bank?rev=cf28835abb717b00a2f30eef06db4ba32d3eeae2#cf28835abb717b00a2f30eef06db4ba32d3eeae2" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", "cw2 1.1.0", "mars-owner", - "mars-utils", + "mars-utils 1.1.0", "schemars", "serde", "thiserror", @@ -1585,7 +1607,20 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "mars-owner", - "mars-utils", + "mars-utils 1.0.0", + "strum", + "thiserror", +] + +[[package]] +name = "mars-red-bank-types" +version = "1.1.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=cf28835abb717b00a2f30eef06db4ba32d3eeae2#cf28835abb717b00a2f30eef06db4ba32d3eeae2" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "mars-owner", + "mars-utils 1.1.0", "strum", "thiserror", ] @@ -1603,9 +1638,10 @@ dependencies = [ "cw721 0.18.0", "cw721-base 0.18.0", "mars-account-nft", + "mars-liquidation", "mars-owner", "mars-params", - "mars-red-bank-types", + "mars-red-bank-types 1.1.0-ntrn-2", "mars-rover-health-types", "mars-v2-zapper-base", "schemars", @@ -1630,7 +1666,7 @@ dependencies = [ "mars-mock-vault", "mars-owner", "mars-params", - "mars-red-bank-types", + "mars-red-bank-types 1.1.0-ntrn-2", "mars-rover", "mars-rover-health-computer", "mars-rover-health-types", @@ -1662,8 +1698,9 @@ version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "mars-health", "mars-owner", - "mars-red-bank-types", + "mars-red-bank-types 1.1.0-ntrn-2", "schemars", "serde", "serde_json", @@ -1681,7 +1718,7 @@ dependencies = [ "cw-paginate", "cw-storage-plus 1.1.0", "mars-owner", - "mars-red-bank-types", + "mars-red-bank-types 1.1.0-ntrn-2", "schemars", "serde", "thiserror", @@ -1694,7 +1731,7 @@ dependencies = [ "anyhow", "cosmwasm-std", "cw-multi-test", - "mars-red-bank-types", + "mars-red-bank-types 1.1.0-ntrn-2", ] [[package]] @@ -1707,6 +1744,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "mars-utils" +version = "1.1.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=cf28835abb717b00a2f30eef06db4ba32d3eeae2#cf28835abb717b00a2f30eef06db4ba32d3eeae2" +dependencies = [ + "cosmwasm-std", + "thiserror", +] + [[package]] name = "mars-v2-zapper-base" version = "2.0.0" @@ -1728,7 +1774,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "mars-red-bank-types", + "mars-red-bank-types 1.1.0-ntrn-2", "mars-rover", "mars-v2-zapper-base", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 4b01fce98..f7dca12ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,8 @@ tsify = "0.4.5" wasm-bindgen = "0.2.87" # mars packages +mars-health = { git = "https://github.com/mars-protocol/red-bank", rev = "cf28835abb717b00a2f30eef06db4ba32d3eeae2" } +mars-liquidation = { git = "https://github.com/mars-protocol/red-bank", rev = "cf28835abb717b00a2f30eef06db4ba32d3eeae2" } mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "00301d60c38af09d8eb7980355009e2f00c6f41f" } mars-owner = { version = "1.2.0", features = ["emergency-owner"] } mars-red-bank-types = "1.1.0-ntrn-2" @@ -72,7 +74,7 @@ mars-rover = { version = "2.0.0", path = "./packages/rover" } # contracts mars-account-nft = { version = "2.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-params = { version = "1.0.7", features = ["library"] } +mars-params = { git = "https://github.com/mars-protocol/red-bank", rev = "cf28835abb717b00a2f30eef06db4ba32d3eeae2", features = ["library"] } mars-rover-health = { version = "2.0.0", path = "./contracts/health", features = ["library"] } mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base", features = ["library"] } mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base", features = ["library"] } diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index a06db00db..bb68169eb 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -29,6 +29,7 @@ cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw-vault-standard = { workspace = true } mars-account-nft = { workspace = true } +mars-liquidation = { workspace = true } mars-params = { workspace = true } mars-owner = { workspace = true } mars-red-bank-types = { workspace = true } diff --git a/contracts/credit-manager/src/liquidate.rs b/contracts/credit-manager/src/liquidate.rs index ea96b88d2..561ad9c94 100644 --- a/contracts/credit-manager/src/liquidate.rs +++ b/contracts/credit-manager/src/liquidate.rs @@ -1,17 +1,11 @@ -use std::{ - cmp::{max, min}, - ops::Add, -}; - -use cosmwasm_std::{Coin, Decimal, DepsMut, QuerierWrapper, StdError, Uint128}; -use mars_params::types::asset::AssetParams; +use cosmwasm_std::{Coin, DepsMut, QuerierWrapper, Uint128}; +use mars_liquidation::liquidation::calculate_liquidation_amounts; use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ adapters::oracle::Oracle, error::{ContractError, ContractResult}, traits::Stringify, }; -use mars_rover_health_types::HealthValuesResponse; use crate::{ health::query_health_values, @@ -66,7 +60,7 @@ pub fn calculate_liquidation( debt_coin.amount, debt_coin_price, target_health_factor, - &health, + &health.into(), )?; // (Debt Coin, Liquidator Request Coin, Liquidatee Request Coin) @@ -90,161 +84,6 @@ pub fn calculate_liquidation( Ok(result) } -/// Within this new system, the close factor (CF) will be determined dynamically using a parameter -/// known as the Target Health Factor (THF). The THF determines the ideal HF a position should be left -/// at immediately after the position has been liquidated. The CF, in turn, is a result of this parameter: -/// the maximum amount of debt that can be repaid to take the position to the THF. -/// For example, if the THF is 1.10 and a position gets liquidated at HF = 0.98, then the maximum -/// amount of debt a liquidator can repay (in other words, the CF) will be an amount such that the HF -/// after the liquidation is at maximum 1.10. -/// -/// The formula to calculate the maximum debt that can be repaid by a liquidator is as follows: -/// MDR_value = (THF * total_debt_value - liq_th_collateral_value) / (THF - (requested_collateral_liq_th * (1 + TLF))) -/// where: -/// MDR - Maximum Debt Repayable -/// THF - Target Health Factor -/// total_debt_value - Value of debt before the liquidation happens -/// liq_th_collateral_value - Value of collateral before the liquidation happens adjusted to liquidation threshold -/// requested_collateral_liq_th - Liquidation threshold of requested collateral -/// TLF - Total Liquidation Fee -#[allow(clippy::too_many_arguments)] -fn calculate_liquidation_amounts( - collateral_amount: Uint128, - collateral_price: Decimal, - collateral_params: &AssetParams, - debt_amount: Uint128, - debt_requested_to_repay: Uint128, - debt_price: Decimal, - target_health_factor: Decimal, - health: &HealthValuesResponse, -) -> Result<(Uint128, Uint128, Uint128), ContractError> { - // if health.liquidatable == true, save to unwrap - let liquidation_health_factor = health.liquidation_health_factor.unwrap(); - - let user_collateral_value = collateral_amount.checked_mul_floor(collateral_price)?; - - let liquidation_bonus = calculate_liquidation_bonus( - liquidation_health_factor, - health.total_collateral_value, - health.total_debt_value, - collateral_params, - )?; - - let updated_tlf = calculate_total_liquidation_fee( - liquidation_health_factor, - liquidation_bonus, - collateral_params, - )?; - - let max_debt_repayable_numerator = (target_health_factor * health.total_debt_value) - - health.liquidation_threshold_adjusted_collateral; - let max_debt_repayable_denominator = target_health_factor - - (collateral_params.liquidation_threshold * (Decimal::one() + updated_tlf)); - - let max_debt_repayable_value = - max_debt_repayable_numerator.checked_div_floor(max_debt_repayable_denominator)?; - - let max_debt_repayable_amount = max_debt_repayable_value.checked_div_floor(debt_price)?; - - // calculate possible debt to repay based on available collateral - let debt_amount_possible_to_repay = user_collateral_value - .checked_div_floor(Decimal::one().add(updated_tlf))? - .checked_div_floor(debt_price)?; - - let debt_amount_to_repay = *[ - debt_amount, - debt_requested_to_repay, - max_debt_repayable_amount, - debt_amount_possible_to_repay, - ] - .iter() - .min() - .ok_or_else(|| StdError::generic_err("Minimum not found"))?; - - let collateral_amount_to_liquidate = debt_amount_to_repay - .checked_mul_floor(debt_price)? - .checked_mul_floor(updated_tlf.add(Decimal::one()))? - .checked_div_floor(collateral_price)?; - let collateral_amount_received_by_liquidator = debt_amount_to_repay - .checked_mul_floor(debt_price)? - .checked_mul_floor(liquidation_bonus.add(Decimal::one()))? - .checked_div_floor(collateral_price)?; - - Ok(( - debt_amount_to_repay, - collateral_amount_to_liquidate, - collateral_amount_received_by_liquidator, - )) -} - -/// In order for HF after liquidation to be higher than HF before liquidation, it is necessary that the condition holds: -/// max_total_liquidation_fee <= (liquidation_health_factor / requested_collateral_liq_th) - 1 -/// Based on that info we derive max protocol liquidation fee. It is OK to be 0. -/// -/// For more info see: https://docs.google.com/document/d/1kImPm4xd3pP8EaC1KZU8oLMFciRDd8Z-jOxv0MTQvEc/edit?usp=sharing -fn calculate_total_liquidation_fee( - liquidation_health_factor: Decimal, - liquidation_bonus: Decimal, - collateral_params: &AssetParams, -) -> Result { - let max_tlf = liquidation_health_factor.checked_div(collateral_params.liquidation_threshold)?; - let max_tlf = if max_tlf > Decimal::one() { - max_tlf - Decimal::one() - } else { - Decimal::zero() - }; - let available_plf = if max_tlf > liquidation_bonus { - max_tlf - liquidation_bonus - } else { - Decimal::zero() - }; - let updated_plf = min(collateral_params.protocol_liquidation_fee, available_plf); - let updated_tlf = updated_plf + liquidation_bonus; - Ok(updated_tlf) -} - -/// The LB will depend on the Health Factor and a couple other parameters as follows: -/// Liquidation Bonus = min( -/// starting_lb + (slope * (1 - HF)), -/// max( -/// min(CR - 1, max_lb), -/// min_lb -/// ) -/// ) -/// `CR` is the Collateralization Ratio of the position calculated as `CR = Total Assets / Total Debt`. -fn calculate_liquidation_bonus( - liquidation_health_factor: Decimal, - total_collateral_value: Uint128, - total_debt_value: Uint128, - collateral_params: &AssetParams, -) -> Result { - let collateralization_ratio = - Decimal::checked_from_ratio(total_collateral_value, total_debt_value)?; - - // (CR - 1) can't be negative - let collateralization_ratio_adjusted = if collateralization_ratio > Decimal::one() { - collateralization_ratio - Decimal::one() - } else { - Decimal::zero() - }; - - let max_lb_adjusted = max( - min(collateralization_ratio_adjusted, collateral_params.liquidation_bonus.max_lb), - collateral_params.liquidation_bonus.min_lb, - ); - - let calculated_bonus = collateral_params.liquidation_bonus.starting_lb.checked_add( - collateral_params - .liquidation_bonus - .slope - .checked_mul(Decimal::one() - liquidation_health_factor)?, - )?; - - let liquidation_bonus = min(calculated_bonus, max_lb_adjusted); - - Ok(liquidation_bonus) -} - /// In scenarios with small amounts or large gap between coin prices, there is a possibility /// that the liquidation will result in loss for the liquidator. This assertion prevents this. fn assert_liquidation_profitable( diff --git a/contracts/credit-manager/tests/files/Rover - Dynamic LB & CF test cases v1.1.xlsx b/contracts/credit-manager/tests/files/Rover - Dynamic LB & CF test cases v1.1.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..82b5d8e4fd34d202b9a65c46c5d5ddb2ba477f82 GIT binary patch literal 37028 zcmeFZWlZFKx~>Z}?(Xhxjk~)xH166o?(Xgs?(XjH?hcK+yF1OH-*?Sk^I@HRvL}<1 z>|`ocp`gCNUH{~{>bb8U3esOdQGp;KA%UWNpH+bV>4y3F+{J>y$WGtc%*xdMA5WO* zT`Vm!rnRkl8DSx=P|o1LZG=hxXOZum^n6MEvU~em_E$h7 zEA*pa2JQ<7Q2@HZ3)rK{R(T0Tr6RAw8}OjF-`6mZV75f+=2NO=388l$D30IQ_v^#o zI!70Dhu2Yufbrz}Xj_EchJ$O!bvomW#hfpEn|^%3^Bo&*=6Kp&uc01cYoMgDsL|P| z7_TF4(p8X#fP^qG^9KV40)qGg1f=kP8YT08qWpJ7`Sm|h{=1@N`A?Mpt|(dm6Xm}v zO1A$*`R|I7{XbFuyQ1XyPn7?zC^`QV<^OS%SpP4G&iJ1!|7}_R=kw0L{CUUO+Rohm zhq1APJ;PsL{*F?8*=~me_1*6oB8(yyercs$A)rz~qF32^;VbXgDwgDcpAkxIq{m$Z z%ZUwZ=0#s`V*-X%54G0!C79zL8IC(2__#@v-m@fUy!qcPZ_@Yq!-7dUA4t1%n$P%gqoWO{XciG9TdBpC4ees&8`U!_ zqi#0eHfZo~jT%bG3*4h$ytWF;)^l`8B5s@Xj8@OBGPF!jl~Qq|SS2#gRK<_IuB+W? zvFq=^dJ5VcGtrhGI5bR4gktf_JNAq-l;K6AgpXh7@C(Y`hK_A1i3kcXHl1JBuTA%p zf?QkAvTpj+^kExt_S0+j2>1(L2cH73nw}&5u-t2Q-(iqG5JI2uiGaeYY-l$w7JpU% zW}AABB8rhy=V!G4#zrawOFqloj+`7A=;1o=Wa_8 zyw76~dtcUQ#-7|0-zh)TtGTnTnk3c zD0bgH{W?>}30I_)dU9-JDBXmvNLjipXcK;SzW?^_sUqKtWNFekmGcvR!5$N*=4gR4 zfK}v-pAl(ed?$IVEJ-Es%*|0tmcsR6ZA`k0)aY5Ch?ka{1q&b)#V7k7`lu|Iz(1=Y zzdaF;RaK~94n3np9qww`b7D%e0#C`3E#QL+1PwDy@QfGYD+Y^5{3XOUTw(=DI1-as z1RhZs1`x%Si)q#k2@DN{*V(+zfbYkj4 zn<|*;6u`9>5y3xLzdbv6tj5d+DYx2r8OA|3$8$?y_b4w+^{J>lJG5Jpj9<(K zj?@Ryn#LT=Y8vW_a*4$9=X=+d4Lj2l!=qw+1?|BB3)P1X*rk*5c~6c~BjkW>e=^=u z)NM`&5qn1ySqXE2KmvnpeKMZrW+OCUlSD2ib>ZT&G0G7i^?To49TKF&Z$HK*hNKpP zvxwG7GOCY6(sxTaTTSq+OLvdj^qsI zmexhgv|>zoRFzT-rGrNE$|B&AwPj*UtHDBT#bD7e*1rpOf0`Q4%%w}qPs_hP>PepX zDr3=~q)@EH4!r~qi>qFWq$%`c2uady2DHj;z0x8?7eI&0%uI~*m=^<9 z?mu?KG|+?&9UU|(X6xDhVtQr!Y(}V!85{2E;lva@dB@B>`8cojld^L`ff8l3P@i_$ z&)MJAg^y4e>)`8%zJ9m)sppCKuYMxdKw+l6@+LhcDH z5Bc0;lwa_jY*P4)%58Pcf%+<-9_!FiW^KLn3K6g_guypKDRJ06BU~oo2|a-Eb{3hn zyyntiAIbe0;FI4E1VCM)4JRa_kx~YY=&De%o2k;ceC_RPA3Z^^{OQH!l404&{gH=+ za5X%;u9bU63H{ZAabHlJ23K&?F}<{Vvf-(5gd)B%@+Eq8#+sT+Dd7X$1t499dZMtX z^wp#==jDv^hK$Sy|4gIC8_t@@C* zx|<=01moN0iMNYJXP;50k*l1XnMf4O#ol?)m)qmsCDlJmYk9Znu%$XHq#Ic0{Gdr2 z)Vx4AOiz{|p8AnyiYitsY}uly8RZx!YhV=kLOA&TB%e)u4N39_wUmT)6 zBA(e5k_*~A6`tPkQx1F^`=5Y9 z{Vmy<7d$vJhgV%FzeK3Z?ytUNAg^0eprLe}`?kSGFmpV9+oX=Tv4JI)*FnE*M#aVi zr|IwL1MeM0#>~l+jg0fCex$9;gJ#~}OQ-HVj_@pBhHr3jkNNA zm>VJ2<-LS|?C@w`)q1$yFWgAEd+eO^2L7{?tOM`|>p%bjapV8*c9JB2v*SO_Xk9yF zi+`Tf^nbpOoRWs_WkdwuLU9RZbdOsM3}HDJ3$QA{*OMqju)rt1o>@(c%-!nUAD{B* z(`7~YT89(J!AX;|2gX=CdOQM;Jh^V$@dX`4BeJfJR6hq&wxd*z#c-6}Hr>Mrt46Um zId&5UX+Z*<#5vPK&J6>$kS9pcMHvHE!XnIorndFi( zHr(>vZs0WVWs4d)6Kp)G&F__tzF1Rq5$({buzQq08B`RX%GQ1Uh18$B+5gYI>_27j z7pMHamsKCJ+hs$1_j~h~qFBWW?H?2+qhZysD5H+cCr=?&70tC+2+0kPPkMg!Aq^?O zx#QqmO6(bQpRl?!2TJ{Ymr4LJrLK+_Hg_Kr(YH;=Z5h74?cmM(b~7|Vi3MMJdr-fM z8?NNU`(#>+h<#3(1i!0G4_RKmvOn9kxt6n3H9Ha z8xRYIDV9zX`EQuhj1hhw_8f4U0g})75e8lS7Inroo}+bhEnrl2y9c!ERrv7H--8po zh@0Q4>B38wvW8ZPRV8NqEn_;Y>IQTAZ7DZ9hw?m>ygc1-C5Oe22l#_`x8EwRrTchv z5wb?-MD!!-aqiPY_p*2nhbEsAt{y*0B?Y`>_nqbssRRIjl8W^wsa!6almTuY|Bwn9 za~fMrS3;fHwF4i%+_Lq4w%e%jRo6tF;k6xaJd7%w2j3ch`l+uT8{A#{L0?wzSwSry z!in*lyrM_Nhx+ATs1o;yDxRg2qFzZ2s74%1#;+4|*X@*1-gr)j5S(H|NOQYc;TV>* zgEht5O#?MQy=dyKc5Gq@c|(Wl(~h%*7jR1t8QDxm((EBmLzcYth}+W! zm{g}bHBf5z586{?$99Yr7QG~gU;3on)L4Ho6DQz$W(Lk}wPaFz=Qm{bwo%z{$mA@d z<`%J_hcEgyObyN>{4Z2t#0};R0fIQbh@XwruNLKVwQHO!!0#DAcQVT$Y{+)Q8LxKp2C%6VE* z99oI=iGu3yvx7rj?{^O(^(xW}2s=)=2iZcfQJ*pFzO61%>MS`c?r$pqDb-3Vi*LZX z`|z>kjNi|1m&QI{S;V8MQ@_Mr6uBe)Rg8ZC0V%!&bWY_}GO46yyk zdy7TgFTdG(itmM4h-(Uvvvk|h)@kViWMIbok?Oe_fl`Vd>`x#_>uMs7`N+_HA3-G4 zZ@6@&+&-zjSK*gIS>e@1bx3oblB-&)=W;mg9JR9~vE~o2N%9y{2@aBlP#OTlJ#=5n zgku4haV%J1P<0YSGHkI}w}MRv``GLtLE+}TYPg>XOP~!>7Jiz>=5ZQ;TCp|f>DSlb;Z~#OR{Lp` zQHu&rF6!$`V$K@7q<^&RE=ke4#rk`R!=4qnqd;i15GH{_B`VM-S~ay_sTQDukuAwS zysYWo?OT1_Mws z!(tw7vA!2RDTr)ODlJo1$`gEaKGpx;7iwiBCO|*l4;Hm6(XBgt5|~H{-4R9+MMLC{ zVAZ_a9U$&(B_zQ*kRFjF5yqBcIw0M#)y1E%4cWOxlqGckSyvXx*cA($NX@MAi`%|| zzwRLeo@fr4(UgchV5QgsHD?c7dpM)mW0}C1l%8QC{3d2b4Kig7Uq>k^Vr!26jCYj0 zaWk&a_iiY5jM9YHs1OX?)y9F@Iu|Q4H!pakkYosJc9YTJ600~PQbsu@nN6FRd7F}9%hSt$nAiw_6Pp!%|c1u8FT_%PIp zaHvDh;xq5O5Qn>0)@XT=Ab^a0;X;R+?b~GDS;b0{QGFB_yjF*gpVra?>jt((^U2pS zcJ5$&?Dg|CX2ZyQM}+oM8iv76C44Nm(Dy9Zq3XszMlW3J=N^?^{=pQnfds8&3v$Ul zjjYO+>g$%@IDk-Y-FQ>pK83z~tp?XPFjw--HjVvV6~dv`s&WX#kT30GbYg7VfuE8h zc>TUw>vE;Ayz5)mJItSPPs`CzRPu>Di3I<~F^J=@d>B1Y+i-dpuvn#MF6Hb$SbUEg!puMDlEFgGgyn zvn$#5Q^+HZ$zk(gXo<)rFby0ai2SgB40czL#Klg&U>Rb2GT@fdJdlRbu0nlS3N$dQ z=aD^$9d@q_%lCu*4mH@bGWap^o7A!(QYqdA3i zSJ80=*EvntcAS2QzxLY+;uiQ5sUXx`rP@tg`yEhju-U+|#6+V;a!c=tmWu(kC z4C7ADo~(Ug1>S^ZlV38U2*3z^l*8<=yUn7>2?RZq7nhH#XRS+ohs)RVe(}rt_8^aC zL+?rpIs4Y2^qbaWxzrc(bCv*e(kne2BkR?2Tgq$P(I$dx%FX!AtSoY%XHcED2L3a7 ze(WyT@v;4<7M@9#!Tx5&sTAOkGctaML2DVWacksp775-rTb~R3;AdVT}I$mp9$8P-_PiM(2jz5*uzD}%<625S&@p4A!ZKa{HKW%pLL zEN9uy`9c|Ci-_P$uAE|NaHpb-nYye)@Y#(UaiT}hI$gOmYq$y?91j?*ynf&RvNr8@ z18&Y*e&%VmcG3XgU8x)5zCB88AD=an?Bh{2+u82MQn$x*%cO2q{LC8aD8F)Oyif}q zb33dd|EXtRaZ-c7OfoXP?86XFCQTuO!nh>T9$0zKD&1fl;QZ|YqG7!)J4LA58uyCp z2h@<}evF~=M(${QunUdHJnCh@*=8u)16*Z1nmGoC6k7<`NEJ74G_yw zc|x!&<#%FFjXURz=*qBzC#KAidqP0Mg({~Bqpo?fa*!7ZkFTBrcwzkg`|wwt3W-q? zp<*_I!Q;DlG5;Bx?IjaKC%f0cJj z;LlhgR>_KfKM7F|da6(ueDza6e`K!!38;=kjXAEJZRMTBU1lL>nwPBW$MvYIjNSJK z7--wuMZ9Z_q4DjYXs=1ykzrOW^%SJE?PF7$R%dUIpZX+hE9v<8(Adr@k#l;$-raHg?xMFw>PK2s zjY&9P2HkG>S2kb9tS=PNv}VJ(EP<1#Uhvba=1@hC$eDUC^98@)O4@xO{rzMX9fQ}v zQxS~27?-`tgrq|3wO4-#ZKb9QibIyYgXVDXH_-&vVM~Aiz}adDrNZoR+$qhoG;q$~ zf$JuqNkIJNYe;HA4Lw8GJ?xXc97>}R4TW<=7$7SusHC_>)75zGtWe!e0}|%YP1ILk7}MRLcIpj4&1A=?_BmcN_^)LvIbMis*K#P zG_?Ds#F7OIHxf$05i1h!Ya<8cEOl6Z7;&}VPaNyOb9uG5z$WR*iMS%Bo8PSnkkd2! zTJRD9^=_o@caShujmJU-ZS2y}a$k-IzHvgpypFxR8FzLGGoYQfk&l!#sWmjn9#+6o z2pEx+RGYR>eT^ca0{5shpeeDCDnI+SWbx~+w;(uNhGhl<1Z1_-n*(7odr)>OAo)m6 zm>vZ{D%?Vtfl)K-K|jgEhdgK=GRysVbad-rR)}-Iw!H@aVDxA*|ib z&kM~|y(1P%dsAWWY+q{D986<8Azw&XXI@hGN3zc?rqiLV-oWT+SZ=M5L8(xd&_jpf z4O9e(hhhXR%BFu6>UXGZ_R08x$OLf?FPQv8MaNjpOjaKkMl-qbHh_CW|}yZkJnK*-_PQ2Ru}* zbRLI-6**Tg>4)~+exXy`qFwG-{~;73%1LL{PeLL8*OxMXjp+Z0P*em(Qk_VIn!X`0 zlFmUiN=oeHHL>Fi_`xK7<-djVR^%-Ltd9iuU_)_E`2ZAE5DMdL%;Zjas}W~@;8qYp zeFZBX{cc-`WY`Mzz6By30)_m7xh@!z?Uu@Db2^-#-{94oEVc<1d=iEiDw}v7@WGa? zm0Z4v>``H_H*t1Eu-bT@ee`3&k{HFi9dTus1WlUsB`OzmaA6A23cX?d|{e zG}dg)*XB%m5Jo1h$TV%3<)aBb4*qq<=pq+xmqkz&WP}S3acmoB{W$bmww=UpEjD7K zkhwT?{t`nlSGN6FkL#8E&f+-|ZoMy@nh6fQrKST%AXhXbi5Q<2%%YjJ`^LmunyIa0 zbQ<^4)Qm7XM67LnD!5FPt&a(|?BSqqC;Mynh1FqIta^7~X%7qdZrun?;bO!;yKP|E%%>8nse-eL4TgTNu`1FCr$uf#SB?VHXM@`t!)!`c z;&W}GZZfAWdsj_Adv2qU-`46}OZr{vylgyc{q1JeI%^8`G_bdW^l(m3wW?l#W~+9P z@yQxcx-nuYEv5Nwe{>*HmU>J1g3t^ksq{FyqrB9)_NV$^6%u)_scqy5QuJAl(GSab zhB{6FNiI}~hDdWj6D;Be zCuBbSPmdH)^Q0f~3Sx0WVm7Kf3+Cdhk*F=*Ao%fgb6h?Hw?Qt8s_*MV*aVqB;|niR z9nu3N5Q7F{HoM|OTFc-q+d&2u>ww*h7htKt)JIOKUjs#N0=2Z=gVRj>w3nC0f z%ZQADnXmL9C}4)tGh(3W!Xo+d*igw}SDUY2N0#6-nN+5$(zQ_vP4Ybuka#xA=w}O{ zq^~wE&_w2j<_#v<7sY8?Are<|kiJVSbYS~rls0!W$2_flFF`@8jTO3!J&C541#x!M zgJR=9zkK+JjcZBzLuIt|1T1S`k+aLvg#|Tn0aQ=T)BysW>l!Y;p}mIgGMVbL@m9oD&e*oui!j~|A` zi3PDJ0}6=qbFJ|vWCM4~pk)c$>X+}a5_F4!Xn?UQN`>uFOo`oNvm8gfCxm1mmHtr5 z04tYLun6mN)_2m$sR-9rgG?79CHvb3w@gy z;e);YD?I57ByY=(Bfd;Cs*5}LyvkRA| ztK?(Y>s!(uFiCT>xRmNbi3RtoPEgGoevpqHJ8QHwL8M8yhFBls~gwfk6i&nPcST#FPnOgG4dj|#s*9jowd#Q#Af(XuQ z-?4h*?#uf~Ca>*kE z+f|QbMcp@jS1Ie{nj0zg!hnu=wrHhWWO&_u&rUvTQtdNnu%uRcOt?JzOK1n5#%0(3 zeuYOuCpY72*L|9P_)KcrS5K?21G;-;I zN*#CG>!^=paSw-a+}PXuXk)o_Q-<*a5fni4uwHY$h>Yp^ot5`4Gp>Efbm){1>-k;B zZp~=*{rY$7$N5y6{`@6c(Y>&I8Bmo9nlw05KC=p7W~9P6C%>H}A7wkh^cV5f)9W*0G|E zu2ONkCEz1*eJ`Zz4EPVxqW+1U#LW&}BXz{98DeR)-#^Qv$fS0w*wLI}TgvtHUB%W( zZysKFvc0^!o!8&h{O{#&vy4-}-izKiR4|3l#W+oApNFQ7bC|ry-gvqc84Ven+P%qk zK8TPuNk9dzS)&G?tt!~Dsdy2@szRgy$N7G5vGNqlbH~;ECvc&KY%|*8YdfC8JL;h29nf))#h3bE4F7lt6i`KtSbIG`${cp`>HzQOo z3ag~fUo(ct$AT$TD^Bz;%|-JcnoIv5&86j!=2C08@L!rs2LntKCCX|l7`kJj1;{Em zeOztHuh7>{{u_OXl)6~uZWlJ%xsgg@H?_l&#?7%f?R$rIQe(;UvC*szkEgfi(?Fw+ z_-0ij#%rg0kf$F|S}TZRA9SBKi^pp9Y|!@?J6FTHpqM-OSTUQ5lS6%)G7l~t*79+a z4tuq{C^s^(2Z`{D0h@j7G&bG6&j#;WbZ1hMhjbKpavHe{j4Ue(11=tQR`htJQUxC! z5`NCNi9sdj~ruT1+onu_w(^S&o`8N-ZCDEkzhaMJ+k{pRgDLZ-*fLjb#_m z$?!5K;F{L1C&N8}0(^kh9Dk>0yTWKbT1X=)nMjK$rIZaj*)7E{xm>xYkX};>KQtEh88A6$SQ*HFX=iQ0G-`pn8=X|&ukboU}#^GSmlaC>L zM;&aDo`Mav5ld{G$2Q&>?GHw~bz5L!Mzty+4J(DTck!vfbdt`P(eti0dtd#moNaqKM=@j#ius3BoHKl2K${Z1+(C4LU__ zZ22@`h=9gX$@)Y`w%+|~ZB=8CrjB7EhKTM$G%vTg^r#cIrHrpS{gyG%?Qs0>(pgt& zMT|z{8`ca2N|e~{ktgq|eEMQ&>xqb28b)j^nTwri`*Rk8spSjGVVOf51BCFLf>zFo zFgr>06;Ko4h{R}8X}Hwf zriCkLymiO>*W{R`0WrQ6mqOqhfoHbDDMVk)Ay9((lDO-lqWUV#^{t#dDkEp)=%))) z^TrH69hiK8p_vE-qGjI61)kYxRCT2XtH!W!^H78Mnz~rEOTcDTo~NDt-p*4oFtpoX z&1|;0@C7xTJ~Pp~*hFvkZbqOYx^DuQ==UW*pQ`2n{!Ex}S=_xhVc|_$HZ5;rB%q6s z4$8>+{#_>hzu4$g=&v9Bc5d+~^HRE*eZK6B0)^6fn&$6DX(vk?_6X!oh>UeY2KG=L za^BzTH=UNj>U(a^ehb8#kV{TQF`ZAZok_)1>mk&J`Vl6#1qV55_m?@dxA%lD4L=mIuz%o(%tE%CoHS**V)>K? zhCBz{W|aI>oJp`$k8QJzT@OwfsgIItn#X9^)B63^xOuM=OS@SZT*imh@(Dj`1*hlR z+oc~vm)DF(k2kwF)jAim!gdQEfW7xD3V!X*!sW*|CdnvXNt@xSm*}D0xh3111;bk1 zgSUom*^X6I4NLH;;3q8fTGrXhr)G|X;u-5?NBwsJUif#2fv$X2e>z+-y zM6FR1kJRn$(7BRM-T5Zsx>wuiEYCWwc$q_0ay}Rhg{vl6fuLYt_WKD5`gzjw<6%)w zzDKOeTiv>4G{00E%f#6C4^Hj~v>}Bw#c?d@_uZuTO7JeE-%M>YCP8vb)xRS{K88fJ zf)NDm|JM6yeG($j&E(7cuBiVI^J9v*gu9GYo&FoUYKnX zwpJguuhk&aM+))|6Zqd+WOeLM4|R|RKk|FRWvwz>Xgm zM@*jEx#i~8byx4N6-SST4K;q#c`oQN-G~ZyNjbYdHyM;sJh;D}=e#&StNeZ~6?;Rf zF#T`OcWVzf=Kzv7_-4k8H8XW5h3YPYsxx&bZ2|OBUF;5B&3!8plAE_|161fEoL*(h z;6uL)s>;;1eOGROi`m9xY}d{%rJ(AwU5U!4^$N)kbR8kzB~F@63UxnjM^)M<2whZi z;oVP7QYvN*Jfb2q&>&J{Wy_tIS^ZhhJiSO*5AMqX6jf5q(oB-nxO!$LhQP$!eZ-0- z$(>h)<=H<10!TM@5g4yf)Xkv-E6=)#>8%k88aBp1}k&G1}jm~#s&+S`r5dD&AkIysC?FnA8qI1fc?7k zXdZZNaRBsEHCR0FS81q{Y2yn7VDIwIi9C3Jgd!fkP` zcSPqG2~>T>2+>=O08QJIpJm12ts>C91}lEPhri0fzCogE)Qnd>Bq)SJlD)h0$5%fMU07Pg^OxG zZt205K0F8!48$h$UNy++$G@By{}`RxhaW4{cM+G# zb>=x>R0_xR8Y;SKSHUchFU5wpf{F|8h~c(%GC?0Ta-R$o7pfAjD#3*1fBdTU1#Pe? zX@p07#gP9#6h6x?E_AH|BU>ISDh|cswj4}|ONxj+W!RRS`OY(>o+AcKF7F(rT5Wus z#f8u=E(6z;!e2MKd4W+jchXw)`t@XY#~~ z3n}Ll@$|i5Tv}|urW7)52ei^2TkNLk*+^Q9;C$R z{W2sF5Ec2qtQ!3VN&gcgE{gvDrW!e^t3dwis?kc;t;fI2h<~3y*3fj`;XwWk_DaCP z1IX`L9AKC0O^06+h`mUeYpZLkf=v#X^Rc(EJNE9XrIWTxkYRqEUY9I6Df_lgn(+G4 z^w`(=n0oMe5^UpcWcd7XcK7onSX4CoJO4)lY~SZeu!^WZC&3EY-*wGqJ}hsdPn&2z z)OiYyODKS%24p{u9o+D_++muhJ&A9MQy?VeH4sE!xSQvlva)V z_?4>y7FrV=Z!xRpsCv}9lLrB8udaHhYTe8E<@<7ho{sek!?P_dQ_yI{Hxt^sXkCQY zpy!OMspZIYEKbF-7w4UKZ;5_KNMHqzK+kK}Iu-LkhbP>Kun-x*h$GmuIA6=8j@;`y z(>uu}8Uw`kn{LxFpL^SJcz;3wLEBJ~lB5m!uc+8Euct?PPnw7?>plvMGJdeQyXrsSa`td zzY$?z!l7mNM2U*4P##xY=|l$zdMZR*NbJUFkbZ_Bl6|w4F^5;Fiu~bzU#U|8$HM9& z|L-jeHE z2$Ikpu))00*;3u`J9rJPf9|Athh_6+Pzil(EK6TLW*o9is^Kh1Tw=0Au;gR%-E<6x zl$g+c;VJg^VCI)*I}>lD^)i$62On7KK=s+xnh;6S4rN@g* ztEIy8r1vSM6jCHLO(LO3zKm!6&fT@OLS@MQn^URwavjA;-(}YaCggb%$OBBv(YIEh zR~WtQDJrzGUE{oXHmN|WBp4+##O`q*F5zUfdpEuB)G9-dk>5@Bt77+Oo$x_h+?HZogjj{4~u&t=z zJwNz z7f2yILTHc(>cN_K;l3hSEt}#`UeOXvB(JA3z{A8BB661yd~U}=Ya&{+P0!Qqsd8~8 z(pJ$N@Xn=V3Z-Ms#7E$SMqw2VL?lrQjx~sdJ3}VaZ?i2U@Ff(ZB7Ebqa{I{(BFh;Z z=lkBL{pTu!Mvj7$Q4PP%#x!39!;K879P6Xs8#~XuT2cn3peAx0_^vBPy}#oE>rSsl!X`Ge%H-Lf6@oGFX_Ok{E| zQrQkwBw`ajMb1kgMoYX%=4Ft6Fg8ztURwgZruRjSumY4U^0qu8pyEiY=Y; znddU%w$=FpTWDReBMaC*elo(*L+pT>>YOx#9T=OXqy0^zwWlR1!13}rOn^P8gL}D(+Y+4 zge6V{s+~65eDrY9GGlTaLfFAeW!BT-0W_mE0o>3Li0X1!CCeiE}@Kw8|EeC6O>V53P8z0bGx~MaY>X+-aPz% z)8H{lEM_In17pM6f&=8OR;k>w{z7tXHcsy{fhV$T&rp^IjSZ$CNoh&}?i|kYA*g!; z-uzzAZ`V2n*2!ul4+JNqJ4jnKS3RwbiPkUbjH91t-I{lelhfiNmSUO#R1$Ckv4e}l zc8oY|jf)ZG0#}nW9F6d{cN}z<15s}1@y?(BY{(`1_?p`^sS{Q7Qr4NfXnmI)m9)Tt zFl`Nm#XleDqmpaOIZ{)uV4Xhy;ombeO7Sbrj39>uA3Z5TO5KvE3#cC zFiBoHz6{0oZ2F?R9#QTLhlVh+3A92&*^Ug!paaTF4_G213h5nFt3RUPJ&XRWY?zzZ6x#xI)yXQ7TC@{IS z{kaCuAb&!5e_4G0mx1u_|DvK{X1^kV@*C{ZJNqdhgV2l~FMeVX8uFutas|~%M;eIF0^P#J8TIbLt9=Co{UWLN=*y=I0tQx zWhJ|Knl@VbUlh+%Z#{IGH%1*50ki(nyi?9|_rE#Hk;tFj8Ee)C`im*xL z;ZNgOx?I-y(Xi<}g>8<#;!H&wxjFx8O#LF$+AU*8v#X62T3w?>kGVb`-dGZ2$_1rG(D&yGkOYK1W_X{(-@kPZ(q;{*8ltttAA6s%xm* zzFB@hNLX;`&xE8%#oSR!=si)Sx_tYmGVMC>-{DM{{P2pyt^IYHBcmny)avcRlK|@M zwvXL%lEnWwVm|ZasZe;mss2#$HuQ59`R5INsgHC<+Tl)oL6c;~AF<1*$%`-DWO=*l zQ|zKNo4-!U@t)9utFr-7?6s~HR`gW;p<%-z@mZN3zEr$Cm#TmS{~WQxDm5ww=#T4f zt7GLD1pso^II9L#i96A9_z|JvCG?z8eh)JnXiZ=FULrc_NO6A-vAl3Ist1NQ+2+KZ zrClq$Eo$3?(>XV{1k)=2_K2teTJO+;K+!<*+uWG>q!Q=-&iLG$sy#Mht?;!gsQ|W5 zd8>rOgcYLf7w(GAyK>bcI2$Wuo**F{kRdrXHqTJrI@>GwjQFPp_F^eA8;Ob84@6|= zgUNzeGPk=p4HWc81G~~ri-I)>K=ky%Y(R8>ptaW}Ni~A10$yV_*(P+mg~tbW*@4fjo~S+23tgcd zgGb+KaJ6dtWWlOd7d#(YOa4$}s+tuD-}r*=wj7&}P$)l=(m#ED}# zPP>6i9Z>6dF_y@$z4mc4_6wlwN@dF*3uD{>Aq?vf#MplCHeI4rDR)sXw=(~H95Lx0 zC@He9c)WRCPFt*z>iVOxqWC) zW5tA6_2MTJ9u&LoK#FHaWq;foDO)Lt6fFXbpf7a&6^lrfLK+XRFpor#jH_ve%2uDa zk^Q`n=WIs9G*h!FEv8S#+#j5A#>F{zq2#-NZA`avX+fz{xeOVfPO>txvWw2th|C#} z(3OA?aWdGB2YR}8RAkWXAl7~T?ib+O>=8^&F;u4+0w9&wFeKlVa8ZT|RK`-ceNRQS z={+YK^krNq-XZ zO~2_1EcAHu_qB(79V#h%@N643R-WPGhcIEN;tH(XaU?Ra_`sZ_u=vX3HLq1f_w#I2 zED$Ptk04Rz4HS+R|ja$ z+lFidMJ|ah z4@g>rM|T0F;$&Vh9lqTN(WO~F*}?d(2!UElrM8ePoCCzJ%b)`p_=Ad+siO^@1w*k) z`?_IBvc~v%>FXiT1}^EYp0cnqzx~3SMhl!b&3OxJb!4(-QC>@ z?ry=|1H2D%&fSEYyYH^^bI@eaI zYcJVg2BeA(;y+DKbdgU$mdPF&KM1ilV>I=UZWTb#XiEqWo@K)`}CUjW!8Y z^BOf&PHQpI@OVb>6W4ktL#cQfek-BRS;Gg0S!I)hm!YM6l=z`*(Q20WKBog6 ze)Aj%#7HR;RI^|zU7QrVMuD%4Et71NXGW{(Y>SMbjVf7Gvstw{c-q#X=U5N43Ay(^ zPtWLAvtV)^p-OY6_dC9elMQE#fQG`LMgwdyE8^=Z9Zc!d|xIjngx>lMIg zj=LKZC^G+QXaA*C`Y?Kfm$BFnwC0&e%dw04W^DZIC66|3pY;M|*m&yCM;MrfPT|t# zLCe)0bW?gY&7Ap7eFIu7-sQ%P>q3X?buhxZT9p|X=27DLXSonXdRQECmPnTILg$LX z5Vr_ibLZR(!x!3J%3K^E+opPs#O{QScI;-AZ+<{F^&##BQbFT+Kq z<=c%qkXOSJd)u{32iV|dAxy{^bKND~(`e{H_|xG% zvC(6M*YD$fz(a%=s6%-Qy@=JUVnVcMN84UdAyD$QcsK3z8j(BKzGJ|p#6%xH%GS>> z%11ozjk|sAioQj4bvJr%tjW+rVyJk3HUxdK+p#MWT|F?l{&v}AJBnE9ylK#!F?gou zAsM+-E%)ZSL8@MZdp~s4{(i{Pu0EED<2Ww!gVY!1GK{OQo?2Nq^*?jIo3?n+PUGe- z=EWXgl&Kwwl*gFIriuHTJ~(%56vR=%-x;Dp-{u`feKZpMK`ZD+x{^5mqNz047+r9Rz$*n-LDD=oyq5`#m<2OST1#OOlmhj=U zHK`j&5k7q=rl5%uUQT?C1(0qy1lMRpxDM6K-#vjrP=V;2#xo!5K_L+M!?lNS_sta{ z1u=Bz)w9g{-xS8@5>bdUN(R!IgowdN)^?_olDRjhV@uW+h~bbDrx}haezrCn;`t@l z%FqCE?JHVJY7c{z4pbs_5E|MFi*w=ZgLO9xkZV;Ba?NBFAlGI$-ZT;8tKueH2<7$n z`Lq#$e$k}B8#?FA2knr5$-fk$j!}mt5RC_$sJighuw$X}C1P+g97+htm$vTYF*~>z zZg5b;Y1#dAkLZp>589xr^=&7yz|I;ftUG;=o2!#aF9qkY017?XHFR0G3qc+W$rCeRwa zRqNfMUp%m%Y{i(PU^F5;poR$$c58K@R%f&f0+wrDyS=_p&}0H3^EZMcNm>~s?!Yw%h(ZtCDe~@Y~HW zy??9WP9%#i#fsW*tQ^?IIi89t?a+OB=^1Z=IPSB6s3Fa#HP-y~GT)S6sBd;XtAme{ zEjD69G8_)_jD|D^-kpL7!9DYm8-L2CpnAZhK>^hjKGj-noU40hBLtw=3Msz!Ba-q* z)ntw_Q@D>d-e;0-th}KpeIaM~5qUa_jDfKmR=L)*>N24x*Z$%0)r_ppIZas8$?IC$oIjxLFF1p71Sj1sxrFNP0m2M=fwIKU(#h=9(W)fjZaL z)w-`I!0>}n5o{z3g8Ug6Y{3wGB?|JK;+WT2|30Sx9{~t6K5E`l0iB&LurIeZ>GpZ_u9Pbb^30b!Ux$o z8C^GlqXS$B^3kTp%&e7n2QeMfxhKFz>^QH<84Y^9T|jHE7}aZ73@{G3ha6EX8A2M+^?V5@+oc1b1NIVVfn7u2jBI zXod8}wxDG@{dBq9#sV@*K%!t`?z0(Sn1W#MfUo?JYXrY#E@EJFRI=sa^bt!^KPldw zt*x`AGskJ{w$xOAS!Kp;0yDs0a2`~}ZuhLVFl+AmJs2wvxGRknmwC5(iLilw+=dVj zJ&pe=8fY@-I>eDikwmg%%>mV;SJ*n)m*73{5M97Mat6K)6{N9eM!De!?J zn?Ye$D-M#1Wn^|1u?Qf7oGB>PCjZuKtBF+p0L=#K>;k_-jj}zi^8ST$CA`5*ydBM& z*N!YwX-Lnf#PcBmrwxt?)^xF?`HoSirXkmz(3$t-K+qAcpAyxxwj z0t8(n9)hl#7Z2q(e+69;CILa$IE07toBs;BPTqb21YHe|i}EI^?`63%0YO(VoS>b% z)vZ&e^LpLeI6S2ea#!(CTZ6>)fN`-q#t(;qSzH$UluBy`p?e*#xUrtt4dN8mU#ZyYgcsmbzrj}t3v2tn0O^wYhG4TM#mR#bch_TNEQOL~(}Vp?=t@R9q>pT6mnj9JzeA}vhWK~@D5;s{!8G(&od z;lyRb#d8waV3%6+eO&CEtEEc~j(rVF-cAenR=|l4PBPHY=c6OS!+@Kh)u*JY01*%k z>*n%}?#<{yF+8g9c_t#4F{BL{2BzvT+#hu3pd8m3W!$ADFB z3o~sB{VM`1Ay+x5F@5~%S3{JRyn&vw$Rel9}hRCxflkV(ed7xDpLV5Wi( zPvWD?f!d4i_`cVt>2qR=I;!_d)*d29nnZNGvgsy{d;3$8r+fT}zsrAXN;FN5qpE~r z9U$GTI6MK;tzwX~$dUyqzFi`QOg;3j z(?~Jx#?+qEJPz90Sc5omkV=I9?ZtO=FW{A6Ap8U95OiHf%HO|rx5JXf|IC8QRE6ya zHCRFBC%Y*@E@Q{QuW)fCC`s?tm{P7U>n2$tMA~<=UIox?)lo_3Tsk1k)E(I&u04dI zu9-_nrzs$_b}l*O15lo=Z^pI5hW5_*_Dy~*tahQTPfSLfUTCfoX>$;K!s zmJn5`Z%E3hz5%kZ$eb;^5f`OBq8?dIVGC!71rN?~tZLP00ZV1Ux}&beBA_>4kd8xjHTt4BD`@td3M}xE~1ZW=DU?m zr>2)fbIBCv9}CJ5W!V_Uf$6N3V<7>{>mcWJFOQK?-pDVt4i|{*yaH#y#c3rAwc`S1 zP}xV<^^Tlt<)v^uK?MTDu`P|`tyt~aO6xuwDz24(e+;)BS9EJ-CY_F5U$E;4qHFJ{ z=lk{i0wFY$KB-lsYA&xnBY4OyHu?AhA%C;*79VMynHGJo$pkG({X|t33^B`{$kl88 zL)Ao9h-}|&Gq~Ek0aUtnXp3-L^>kPk4nL@~xpC&}0a!$ea^_x}&#kk4QS!UX8`5W$ zgbjN+KWTU?f8uw1XPMdZ;Jmc<|Ln6Tt20(nYN>prGBa0D+mV*{r2;||Sy)3Wm#I;) zrNO@?ruSVaCc`@{JqC_7Bec(Rx;HRgw|*^+y4y@{ z{+b;%DN0y+;PnY6(=VKAvhabe~@RJh0KSX`p*rnmk-0r z8iT~X@~(Wf0&lkGD1-9>s$*$6m5$8Fw?97B%sZiCc%~T9|D$91d&_s6T~Y~cc7i(m zt^+ct86OBjhtdX_(lmn&^uhvmWHM7qj7YN3wjpHa=qv18p${=qYBX1Oh6NIM(?{)_ zXkB)8ubgt5&Y8xpr%7AESEho?E zLo{^$AsYH|;NBtrC;o#{BZhSk&;%$o+P_t5B!DtifU~8Zh?J+rM<1oipQYOWtJOzK zm!`pFl~?lgZeKj~!lf~U!h=+!`rGoO|C3bvRVV#7sb(3rJ@RHsGPovq_PyijAi!Y* z_myHI2=EQ{xS>-LV6O=6KwT*lpd-~~v(S@n}U`ODoOIy8+gIkxf|vDzB$cEIf{e!4V@_;`Q> zB6vi4-my8LRv4UqyV)Lx0!X8%r55rS5{}*Kdq|^LEPH!b8asIJki0uml~)K0h>4cC zyK$hsu(IF6Y&>rJP}%cN^Jb2s^|FZRR`sV|i6z~5N!c;LWgD09ki1|O%kZ}6+|e&{ zo5trcJ(s!FY>aJM91~|tO&A>z zc!|Kw6%40hA0a}Hr{hG9pQMCB9@AX<9YKY^19@g%5MNa|7{-(b65Ka=W8^KtDZph* z2)YHhY=t#!=he!%)|hOO>o)AQ{UcRxm6AeGRo&vmVNT={f`ioMv_$SQF4T&9n~*J3 zsts#4NMQ;}5ZmO4I$wG#nt{%!HxvL|wjC_rVS+Gl0zGO|te;-e^DGd;x}GEq1oXz@ zB$l7j!ZCJfsXl^ILy z{dYdOBov}3k_oy1xwa8!@AF``b)*;lYnxoE7>*xx+61#A*-?TK{|^k<<)z?P@WDWF zIiM()QvLmYz$P4y+F!}mI)OhWhWUZ=doowOhArO6g8?168}tMIAaNe5Yu7`=7)Q~LE#f9U{GUooZ>w3T_3 zsneW8&k+GMJJ)VPx@~}TAGN9@r)nvMasZ56)P}{doN!s4)uP8~`nes&B@DZ~sTgd> zf;TQ0Y-cA$g{{M-VqS(?0n^6M%Cs0;@HVLI&)VdQJ8AL->bMJL75Rndg=sN1(BF!X zB?O)EMtE;+WFaWo`h%>XapPBiYGyI%MbD}f)S8=D7rx9uibo2T^q?#y^Zf!pX9CLi z^B5sS8fHcz6-M?hALBKV=FgG4Y~SYI4l`hyigP2fY+2FCG!BfR`Wyp{OAn+7Bv^<2 zkQ&Nr?tsE~ON~InM^K-}D30JoVRbxh9X*u@w<0c6ReCcuUwk=DhH8w#^qcAlUK$Q= zr~BBxMZi(R#f+sEn5bFVF~DFhtY-@H3Y89dyEz9~4j-@n@^C5Mlh7Gv7wvG@toPlZ zrdn_aKIF^8pB(|4(z=w|kRJ^pAp`mnfVCk^kUdSA-b6i^SOy{FtRGA1qFVbu+&eGI z!wJVKwb&4Q@i!6?=4u!dGKI*#^W@(TM^yY-fhg=#dDH@bNpjtPh_k$m8J-yj*i>0V z#=fECDn>w$<)AgM<4IIzjX4^;4ep#Jb=#SEA1EhZyN+x_ z_#Q$dctc@S?at5qvs*f8Y!^oq%OuTh#kfX%LtKE(!P$ks{SN9KS#k& z7U@iU@7pdwAYLqxz5-uO$$U#YG*&vTLcsbmS4s$JE*gZh(jXD1V@L&+rvg(lO@YPO zO866IC-JhpGw8?LYEmxqov)W(L9HI9bbf`dsZ^OrX*7%ojGtE$u2c}*66G;gVgu3O zgaqL&y}K#j<&{ZkRPr!*JG5MY`pMSrf4J8=@HqM&Np{*3YvXoqfRh|gQ(3=7{|*wf z151a5G8mBO0Q1^q^{`&#dZfch?iDdURmJ$nWf6U7+HcU`qLe0g=y_v3qLT}0hvc?` zz~~%%XHw$g3uyX}x?wr%TFGJ3k3FOp_SQ3L(?tdccl~ROor4Wnmg869u+SL&pmsP7 z=OX>5Hr_&+sC&r?XAP^&I6ccQJdP0I9v zOGnI}uCZ}r{%rJ|+{~mHXT^gTVfKS5;Ys|8lSS@B`c{XZ+oA%_4FyyX+QQ%VA-#e9 zZHdng?;cfBR%_Mel=TNxfm!3ci@owTOcr=F5kFb@d?JcYSFpI2~Q7Rq)qAU zdNLqYpG|3z5Zm-DkSC)}=;{va$D}VTel9gY+gWSEr2({^jtwtmeo3!L?2h?WB{$dm z(&hK=;{#|rl+FVmaIkJ4wH@h%q;M!e+qnVc+y5Lo{d;ZqsBQi~({^jWX}d)@qVfM- z+oAkYxc0^d76!DB-yiRMKzY`3jsQ>>1)4L|fD8U19!tW_Rxl>=hR0 z7q=0dzl?%(XnL}RT%^y>020NCLRIhoDXIgS5Q&@-4c+-o+%?)dSDT!ab1;7h{!0tt za6MJVsO6}ZnJ-P9yrEy22YMWk5TAUTeS7!io_ z_dFIzjYcS5ZqT?x;%c0ggU6%O5RkYhm{I=<&bN+OHQ9uv4BG0tNy?M8g1zG6hUMwQ zCq%D4Yln8XkXHhpjRjw0Rm44c#_8msIA+p;pg}ykl-O zja$FCV$|kj#9FtP?=zX|o4M=ZS!uDjXlZ5-`pNcs-M}e`AMl#^8J{#Gf_WEt(g5;f zC3mmK(~VNp7u2tAgCdiZTN8=P5}`c~_v~X?RKqAG4Lt+sxQ@|$cGC`V&bJUbafIVf z5k>qG$m~*W4Gi0#-kV0KlbEW|PPTO5?9eaO{7mj$3tQr+(c5{cD=Xgxf?6XJ0QJ^( zCmpBE%Grr>XKRiEg8E(EI_vvrepOYYlk+f^Qx`NCdCwv-E!tLU_Tw zNLQ)jEil6RrDmdU-6q&Amp!$mD04hLyK8-)9MG*vaJyB&OxM z^-L4;-U-pw+<2)Tip?&Jqh`WO$rRIC=ky_@@TOG$U?j?rlPSS}(5hdgy{f zpWm5Vtdpw%T{=x?M$PI-rb`zl{)hT9tUnIa^y{h4@-L)kdrGe!O@D)L zUOMEUKtKiXe>dHr{u*d@#yYkJ`ttU+4`&rW9Df$AWFbGp_gE>AGk#lJM5=h34Vukc zCi%^tOgVm%2U`SKq2~U|yij^mBQ`RyT^g;HcIi-=Yu(Vk#R%Jn&tUbbEb2X(%8LkG ze3-c(`@}};@XycRa?!=~e1sw`*%}yj+AKhT)1wZ{W0JBmwXog3AxqNj?{5^BL1s|m zFpR-FYiIo=j22Jv^|Rjx#1~y2{ne)g3OVIc2bXK#eH4y2Q91qAo6VSEt(eVsEUj&B zGGLM|o9^K_(5t@hw+_s&I~>%yP9Iw2EgRJ*an^4~OKIJN?sERI|Z>cCWZrg9k6;}lQDJcXL{ zkzux!_>ujo@a)2pGSskp-55(8*wUt6vFkNKYOL^@ifBoxcxyX$p!iPI1xgJtVe*%7 zM`YEukPN9Vd_`lhD@;in-Y;ij#4f-WDYku$irACL^)5xV$*zdd#(o4;B9c>Ve7E8p zIFKbJa`ZT%kOyabYoK7C)EO%wQl3_dYQQ{gcCv4dT#!#h7CanoGOpT7B6!Ci5VFyy zUAUyot*vLPB9~o|$njP{?D#NPSKF5HL6qarWr5ww6ir@>wZ<)Xb2k_5Hy1F|y%m zLK$&KKg2UzU(%)JrhK>QQ8trh#czYv>9mDfn8>?fNxK{$##0KjNw|yg_HrvL?69$n z6Lu+(Z()BG&F?2o6(Ws~T(J0HC{Vva6|UTQ%Z_ou*2TV4kOQ^=&$vqehdEn8bb1M5VNM=B)w{5=879{&T>XAQe+85vVh zzA!R=zlwF-PBNNof(^N71@BkNYKSoM4b7QjQTm-U$T*?kKG@Wdk=?{|1^#*wc*~G6 zrUnX@wDP3Iy>>Apdg)Lp+7T0?D;Sw?LI|#HR5gLj#i$b*2X|}b{8s3cM-m7WB}|B! z7BE)6b3sV^3Z#ICk>dp9tcXLn(1*%Iz~=HA#R$savyBH^8|B)Y2YZWUBqO;h+;euh zURJ?frlc`8c9p({h2&@*V+57&ykZg33TJI6TpN+2L5X{jcx0{tlHq%OWUM$xHJn5P zeIpR*a6>9=6T^-hbP!;YDCst{k%i-ga58VcABQz%PTX?}W#2LFXhfkxD@(<=*scO7-!mJv&TqQKZcYIVFSE738SOGK927NCT z&2iKeE7gI*-zHRum#Z)P=5)28s~Xnk@Q5yx8sExb~_m>PHnVPcjbIT3HjTiZn`Hs}qBPMXLCM{AQ8 z{pJ;Qv@QqxRIihk%gZQp+P`$ZJ3%PjLitGlThE zl$3lFWE*wtMzK(nheu+*%nB%T5~W!?TC%SQZ5?eQ?*#6}1RhHD;w25GK(=i#H&WO90iHp|p~0uTx?#WWHygBA{u<)xCO?;r`abyv{> zL*Lrhk$I|>c|oZ4kyDT zH$YTHh=7Ob8~EQnOjy4jreD_e(?{xYebX>5Vd?XDa@HyQ#BPum2}zD_X%3~qY$imp zwiCHFap=v`c?B{vshpXES3N&oUlZ?V@TooCn~;WYX-ZVUVyK`3ia$yfD?BGxM{mI~ zRCC0qP5eNFNv1X(>x_2O9gMO~Aaq{~hI@9-EPL5eV(pR&x$v#Or?hY}nI9Q*3+oV@ z=pJPotd2M2^N8$$&j7L`k9;v@gLBBcT$?)sg70uN@vXFJu7$UpUDn4~Bb$j?4R{jy z=tK{tMq%lL@k(<-g>o5WzL+lUE4cn?7iK%%e9j==u#;msNjHC0Z2tGVbA66N>JyDS zMz{#3=`F_{SQiFyl$i$jhL|o&?r|2{k{iimO9e;ghA&kjujy2^`&$E4Av&qu&)6d>fwGRbS*+mrhY6B~JT;*psj>#U1#szX4P+HI?C{Bo-W3wZLpo*M{8R*mLW zDY8do-+5hjDJ@n|M7Z(p3SRdu9k6!_Kap^LdD zY#H~s{9zEcZ!p?>+lTmxwU#nYL=InuJMmv?eIk*tqJvf;yCdOU_}EFE)|?mdflH;~ zar<$)RT@Dl;6}**HZt(AdVD8fWod62gbc%S@oDd~%gbF?W*b-EI<*fVhGEbc`S`L{Y8ixXUGKAqx$QLwAN zf&>il%DUkZL!p~3zJkVa-I%c;a;2aoC(|9LdRaikoGRMl;(BA;8FA4vjKLWoI>dfs z%#7!9wAs4T5j3755&Dvh6tIwtbW-pPi9Gx*N^^n;^S5LU;&F2Iy>TU)V`Rj`qZ8nq z0~-l9nUETmbF?!<<6ZE!z!(Lo96V+4v4;C7&<%Ztlh)3b|5@kfO4bm;c&PWZdO^seiL_th=-*~RRcO{wf-K+)g2h8S`)n< zbkYpie#wQ#30}SEi|A{ZYf1GYuzs%{2Or1yO%#vi)M=X^wBr`-ucoK8h<^xFca%$siFcwLP7$CcwH$$=$iO| z0saO7Sj1oeKGEMBApWIqtK;~vUitSAj~0Q9#6Lxm0AT+Cp&D zpQ9`T?(q*OkF{X`ISMt{A5k8Qs{L~mN5HK82QH6gH~u+FE1(eEA5b21HUBvZ9Mm6C z9#aVZIf@GOA5k9nwEZ2*Lxl0K_6;zD{^5>(Z4LY9FbA-IhWX>u`}8o%-^TzaV8j5} zJc$ASbx8DcGEa|l1O%3!w8fXtQJzMUA12Mmm&J3yrw2y;PHPqIIpAMsLOw@%dg9XW zv_=5slYhUtC$#=LYU(-Q(^DdTr!@&EN%%Y9BdtG<(x=BP{7%dM)pL|5v>vhk*jrDF zLjO+7nfy86BdtFU`=?cvf2V~*_Z;O3t-s1iKku!lCN%v{>sIMG$`e|D zWpX|5t*1MVf2Rea@*MDy)*t8Mr(1S^r&X%`9OVhEzxFCWr}cDu&F_)k^FMhhLdRVLe)rh^9Ovnp StdResult<_> { let mut denoms = denoms_opt.unwrap_or_default(); denoms.push(to_deposit.denom.clone()); @@ -101,7 +101,7 @@ pub fn withdraw( COLLATERAL_DENOMS.update( deps.storage, - (info.sender.to_string(), account_id.clone().unwrap_or_default()), + (info.sender.to_string(), account_id.unwrap_or_default()), |denoms_opt| -> StdResult<_> { let mut denoms = denoms_opt.unwrap_or_default(); if new_amount.is_zero() { diff --git a/packages/health-types/Cargo.toml b/packages/health-types/Cargo.toml index bc22ef506..ee0afc2fd 100644 --- a/packages/health-types/Cargo.toml +++ b/packages/health-types/Cargo.toml @@ -19,6 +19,7 @@ backtraces = ["cosmwasm-std/backtraces"] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +mars-health = { workspace = true } mars-red-bank-types = { workspace = true } mars-owner = { workspace = true } schemars = { workspace = true } diff --git a/packages/health-types/src/health.rs b/packages/health-types/src/health.rs index 5c55d5ced..ba9bab0f9 100644 --- a/packages/health-types/src/health.rs +++ b/packages/health-types/src/health.rs @@ -2,6 +2,7 @@ use std::fmt; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Decimal, Uint128}; +use mars_health::health::Health as RedBankHealth; use tsify::Tsify; #[cw_serde] @@ -80,6 +81,19 @@ impl From for HealthValuesResponse { } } +impl From for RedBankHealth { + fn from(h: HealthValuesResponse) -> Self { + Self { + total_debt_value: h.total_debt_value, + total_collateral_value: h.total_collateral_value, + max_ltv_adjusted_collateral: h.max_ltv_adjusted_collateral, + liquidation_threshold_adjusted_collateral: h.liquidation_threshold_adjusted_collateral, + max_ltv_health_factor: h.max_ltv_health_factor, + liquidation_health_factor: h.liquidation_health_factor, + } + } +} + #[cw_serde] pub enum HealthState { Healthy, diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index bf43e8433..3fc4e4b69 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -27,6 +27,7 @@ cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw-vault-standard = { workspace = true } mars-account-nft = { workspace = true } +mars-liquidation = { workspace = true } mars-rover-health-types = { workspace = true } mars-red-bank-types = { workspace = true } mars-v2-zapper-base = { workspace = true } diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 1291d6d2b..56d3f7117 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -4,6 +4,7 @@ use cosmwasm_std::{ }; use cw2::VersionError; use cw_utils::PaymentError; +use mars_liquidation::error::LiquidationError; use mars_owner::OwnerError; use thiserror::Error; @@ -164,4 +165,7 @@ pub enum ContractError { #[error("{0}")] Version(#[from] VersionError), + + #[error("{0}")] + Liquidation(#[from] LiquidationError), } From 78aaeb6122caf9973f33dfe0e6f230e21e58c94c Mon Sep 17 00:00:00 2001 From: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com> Date: Tue, 1 Aug 2023 14:11:46 +0200 Subject: [PATCH 188/218] Mp 3158 health computer max borrow to vault (#167) --- .../health-computer/src/health_computer.rs | 50 ++++++++ .../tests/helpers/mock_vault_config.rs | 13 +++ packages/health-computer/tests/helpers/mod.rs | 5 +- .../tests/helpers/prop_test_runner.rs | 109 ++++++++++++++---- .../tests/helpers/prop_test_strategies.rs | 3 +- .../tests/test_max_borrow_prop.rs | 11 ++ .../tests/test_max_borrow_vault.rs | 97 ++++++++++++++++ packages/health-types/src/account.rs | 4 + 8 files changed, 269 insertions(+), 23 deletions(-) create mode 100644 packages/health-computer/tests/helpers/mock_vault_config.rs create mode 100644 packages/health-computer/tests/test_max_borrow_vault.rs diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index 06c3d68a2..56a66fbb4 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -207,6 +207,56 @@ impl HealthComputer { .checked_sub(debt_value)? .checked_sub(Uint128::one())? .checked_div_floor(borrow_denom_price)?, + + // When borrowing assets to add to a vault, the amount deposited into the vault counts towards collateral. + // The health factor can be calculated as: + // 1 = (max ltv adjusted value + (borrow amount * borrow price * vault max ltv)) / (debt value + (borrow amount * borrow price)) + // Re-arranging this to isolate borrow amount renders: + // borrow amount = (max ltv adjusted value - debt value) / (borrow price * (1 - vault max ltv) + BorrowTarget::Vault { + address, + } => { + let VaultConfig { + addr, + max_loan_to_value, + whitelisted, + hls, + .. + } = self + .vaults_data + .vault_configs + .get(address) + .ok_or(MissingVaultConfig(address.to_string()))?; + + // If vault or base token has been de-listed, drop MaxLTV to zero + let checked_vault_max_ltv = if *whitelisted { + match self.kind { + AccountKind::Default => *max_loan_to_value, + AccountKind::HighLeveredStrategy => { + hls.as_ref() + .ok_or(MissingHLSParams(addr.to_string()))? + .max_loan_to_value + } + } + } else { + Decimal::zero() + }; + + // The max borrow for deposit can be calculated as: + // 1 = (total_max_ltv_adjusted_value + (max_borrow_denom_amount * borrow_denom_price * checked_vault_max_ltv)) / (debt_value + (max_borrow_denom_amount * borrow_denom_price)) + // Re-arranging this to isolate borrow denom amount renders: + // max_borrow_denom_amount = (total_max_ltv_adjusted_value - debt_value) / (borrow_denom_price * (1 - checked_vault_max_ltv)) + // Which means re-arranging this to isolate borrow amount is an estimate, + // quite close, but never precisely right. For this reason, the - 1 of the formulas + // below are meant to err on the side of being more conservative vs aggressive. + total_max_ltv_adjusted_value + .checked_sub(debt_value)? + .checked_sub(Uint128::one())? + .checked_div_floor( + borrow_denom_price + .checked_mul(Decimal::one().checked_sub(checked_vault_max_ltv)?)?, + )? + } }; Ok(max_borrow_amount) diff --git a/packages/health-computer/tests/helpers/mock_vault_config.rs b/packages/health-computer/tests/helpers/mock_vault_config.rs new file mode 100644 index 000000000..4a6041ebf --- /dev/null +++ b/packages/health-computer/tests/helpers/mock_vault_config.rs @@ -0,0 +1,13 @@ +use cosmwasm_std::{coin, Addr, Decimal}; +use mars_params::types::vault::VaultConfig; + +pub fn osmo_atom_1_config() -> VaultConfig { + VaultConfig { + addr: Addr::unchecked("osmoatom1"), + deposit_cap: coin(1000000000000, "uatom"), + max_loan_to_value: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(74u128, 2).unwrap(), + whitelisted: true, + hls: None, + } +} diff --git a/packages/health-computer/tests/helpers/mod.rs b/packages/health-computer/tests/helpers/mod.rs index 245d9fdce..d7e0918f3 100644 --- a/packages/health-computer/tests/helpers/mod.rs +++ b/packages/health-computer/tests/helpers/mod.rs @@ -1,5 +1,8 @@ -pub use self::{mock_coin_info::*, prop_test_runner::*, prop_test_strategies::*}; +pub use self::{ + mock_coin_info::*, mock_vault_config::*, prop_test_runner::*, prop_test_strategies::*, +}; mod mock_coin_info; +mod mock_vault_config; mod prop_test_runner; mod prop_test_strategies; diff --git a/packages/health-computer/tests/helpers/prop_test_runner.rs b/packages/health-computer/tests/helpers/prop_test_runner.rs index 66d291401..3860542fb 100644 --- a/packages/health-computer/tests/helpers/prop_test_runner.rs +++ b/packages/health-computer/tests/helpers/prop_test_runner.rs @@ -1,8 +1,14 @@ use cosmwasm_std::{Coin, StdResult, Uint128}; -use mars_rover::msg::query::DebtAmount; +use mars_rover::{ + adapters::vault::{CoinValue, VaultPositionValue}, + msg::query::DebtAmount, +}; use mars_rover_health_computer::HealthComputer; use mars_rover_health_types::BorrowTarget; -use proptest::test_runner::{Config, TestRunner}; +use proptest::{ + strategy::Strategy, + test_runner::{Config, TestRunner}, +}; use super::random_health_computer; @@ -11,35 +17,96 @@ pub fn max_borrow_prop_test_runner(cases: u32, target: &BorrowTarget) { let mut runner = TestRunner::new(config); runner - .run(&random_health_computer(), |h| { - let denom_to_borrow = h.denoms_data.params.keys().next().unwrap(); - let max_borrow = h.max_borrow_amount_estimate(denom_to_borrow, target).unwrap(); + .run( + &random_health_computer().prop_filter("At least one vault needs to be present", |h| { + match target { + BorrowTarget::Vault { + .. + } => !h.positions.vaults.is_empty(), + _ => true, + } + }), + |h| { + let updated_target = match target { + BorrowTarget::Deposit => BorrowTarget::Deposit, + BorrowTarget::Wallet => BorrowTarget::Wallet, + BorrowTarget::Vault { + .. + } => { + let vault_position = h.positions.vaults.first().unwrap(); + BorrowTarget::Vault { + address: vault_position.vault.address.clone(), + } + } + }; - let health_before = h.compute_health().unwrap(); - if health_before.is_above_max_ltv() { - assert_eq!(Uint128::zero(), max_borrow); - } else { - let h_new = add_borrow(&h, denom_to_borrow, max_borrow)?; - let health_after = h_new.compute_health().unwrap(); + let denom_to_borrow = h.denoms_data.params.keys().next().unwrap(); + let max_borrow = + h.max_borrow_amount_estimate(denom_to_borrow, &updated_target).unwrap(); - // Ensure still healthy - assert!(!health_after.is_above_max_ltv()); - } - Ok(()) - }) + let health_before = h.compute_health().unwrap(); + if health_before.is_above_max_ltv() { + assert_eq!(Uint128::zero(), max_borrow); + } else { + let h_new = add_borrow(&h, denom_to_borrow, max_borrow, &updated_target)?; + let health_after = h_new.compute_health().unwrap(); + + // Ensure still healthy + assert!(!health_after.is_above_max_ltv(),); + } + Ok(()) + }, + ) .unwrap(); } -fn add_borrow(h: &HealthComputer, denom: &str, amount: Uint128) -> StdResult { +fn add_borrow( + h: &HealthComputer, + denom: &str, + amount: Uint128, + target: &BorrowTarget, +) -> StdResult { let mut new_h = h.clone(); + new_h.positions.debts.push(DebtAmount { denom: denom.to_string(), shares: amount * Uint128::new(1000), amount, }); - new_h.positions.deposits.push(Coin { - denom: denom.to_string(), - amount, - }); + + match target { + BorrowTarget::Deposit => { + new_h.positions.deposits.push(Coin { + denom: denom.to_string(), + amount, + }); + } + BorrowTarget::Wallet => {} + BorrowTarget::Vault { + address, + } => { + let price = new_h.denoms_data.prices.get(denom).unwrap(); + let value = amount.mul_floor(*price); + + if let Some(vault_value) = new_h.vaults_data.vault_values.get_mut(address) { + vault_value.vault_coin.value += value; + } else { + new_h.vaults_data.vault_values.insert(address.clone(), { + VaultPositionValue { + vault_coin: CoinValue { + denom: denom.to_string(), + amount, + value, + }, + base_coin: CoinValue { + denom: denom.to_string(), + amount: Uint128::zero(), + value: Uint128::zero(), + }, + } + }); + } + } + } Ok(new_h) } diff --git a/packages/health-computer/tests/helpers/prop_test_strategies.rs b/packages/health-computer/tests/helpers/prop_test_strategies.rs index cd9e75723..48f3d8375 100644 --- a/packages/health-computer/tests/helpers/prop_test_strategies.rs +++ b/packages/health-computer/tests/helpers/prop_test_strategies.rs @@ -174,7 +174,8 @@ fn random_param_maps() -> impl Strategy { random_denoms_data().prop_flat_map(|denoms_data| { vec(random_vault(denoms_data.clone()), 0..=3).prop_map(move |vaults| { let mut vault_values = HashMap::new(); - let mut vault_configs = HashMap::new(); + let mut vault_configs: HashMap> = + HashMap::new(); for (addr, position_val, config) in vaults { let addr = Addr::unchecked(addr.clone()); diff --git a/packages/health-computer/tests/test_max_borrow_prop.rs b/packages/health-computer/tests/test_max_borrow_prop.rs index 06490834a..5788a455a 100644 --- a/packages/health-computer/tests/test_max_borrow_prop.rs +++ b/packages/health-computer/tests/test_max_borrow_prop.rs @@ -1,3 +1,4 @@ +use cosmwasm_std::Addr; use helpers::max_borrow_prop_test_runner; use mars_rover_health_types::BorrowTarget; @@ -12,3 +13,13 @@ fn max_borrow_amount_deposit_renders_healthy_max_ltv() { fn max_borrow_amount_wallet_renders_healthy_max_ltv() { max_borrow_prop_test_runner(2000, &BorrowTarget::Wallet); } + +#[test] +fn max_borrow_amount_vault_renders_healthy_max_ltv() { + max_borrow_prop_test_runner( + 2000, + &BorrowTarget::Vault { + address: Addr::unchecked("123"), + }, + ); +} diff --git a/packages/health-computer/tests/test_max_borrow_vault.rs b/packages/health-computer/tests/test_max_borrow_vault.rs new file mode 100644 index 000000000..611b93bfb --- /dev/null +++ b/packages/health-computer/tests/test_max_borrow_vault.rs @@ -0,0 +1,97 @@ +use std::collections::HashMap; + +use cosmwasm_std::{coin, Uint128}; +use mars_rover::msg::query::Positions; +use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; +use mars_rover_health_types::{AccountKind, BorrowTarget}; + +use crate::helpers::{osmo_atom_1_config, udai_info, umars_info}; + +pub mod helpers; + +#[test] +fn max_borrow_vault_offset_good() { + let udai = udai_info(); + let osmo_atom_1_config = osmo_atom_1_config(); + + let denoms_data = DenomsData { + prices: HashMap::from([(udai.denom.clone(), udai.price)]), + params: HashMap::from([(udai.denom.clone(), udai.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: HashMap::from([( + osmo_atom_1_config.addr.clone(), + osmo_atom_1_config.clone(), + )]), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &udai.denom)], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let max_borrow_amount = h + .max_borrow_amount_estimate( + &udai.denom, + &BorrowTarget::Vault { + address: osmo_atom_1_config.addr.clone(), + }, + ) + .unwrap(); + + assert_eq!(Uint128::new(3381), max_borrow_amount); +} + +#[test] +fn max_borrow_vault_offset_margin_of_error() { + let umars = umars_info(); + let osmo_atom_1_config = osmo_atom_1_config(); + + let denoms_data = DenomsData { + prices: HashMap::from([(umars.denom.clone(), umars.price)]), + params: HashMap::from([(umars.denom.clone(), umars.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: HashMap::from([( + osmo_atom_1_config.addr.clone(), + osmo_atom_1_config.clone(), + )]), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom)], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let max_borrow_amount = h + .max_borrow_amount_estimate( + &umars.denom, + &BorrowTarget::Vault { + address: osmo_atom_1_config.addr.clone(), + }, + ) + .unwrap(); + + // Normally could be 3200, but conservative offset rounding has a margin of error + assert_eq!(Uint128::new(3196), max_borrow_amount); +} diff --git a/packages/health-types/src/account.rs b/packages/health-types/src/account.rs index 12e773bed..7d91c202f 100644 --- a/packages/health-types/src/account.rs +++ b/packages/health-types/src/account.rs @@ -1,6 +1,7 @@ use std::fmt; use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; use tsify::Tsify; #[cw_serde] @@ -21,4 +22,7 @@ impl fmt::Display for AccountKind { pub enum BorrowTarget { Deposit, Wallet, + Vault { + address: Addr, + }, } From 1a9a249272edc4d742a0318934ddd569ce725dca Mon Sep 17 00:00:00 2001 From: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com> Date: Wed, 2 Aug 2023 10:14:32 +0200 Subject: [PATCH 189/218] Mp 3158 swap amounts (#169) * [health computer] add swap amount support * [health computer] finish swap, tests, and generate files * minor styling fixes * pr comments * updated ltv function for health computer --- .../health-computer/src/health_computer.rs | 143 ++++++- packages/health-computer/src/javascript.rs | 12 +- .../tests/helpers/mock_coin_info.rs | 35 ++ packages/health-computer/tests/helpers/mod.rs | 6 +- ...t_runner.rs => prop_test_runner_borrow.rs} | 0 .../tests/helpers/prop_test_runner_swap.rs | 107 +++++ .../health-computer/tests/test_max_swap.rs | 88 +++++ .../tests/test_max_swap_prop.rs | 14 + .../tests/test_max_swap_validation.rs | 365 ++++++++++++++++++ packages/health-types/src/account.rs | 7 + scripts/health/pkg-node/index.d.ts | 17 +- scripts/health/pkg-node/index.js | 28 ++ scripts/health/pkg-node/index_bg.wasm | Bin 236591 -> 246657 bytes scripts/health/pkg-node/index_bg.wasm.d.ts | 9 + scripts/health/pkg-web/index.d.ts | 26 +- scripts/health/pkg-web/index.js | 28 ++ scripts/health/pkg-web/index_bg.wasm | Bin 236444 -> 246510 bytes scripts/health/pkg-web/index_bg.wasm.d.ts | 9 + 18 files changed, 875 insertions(+), 19 deletions(-) rename packages/health-computer/tests/helpers/{prop_test_runner.rs => prop_test_runner_borrow.rs} (100%) create mode 100644 packages/health-computer/tests/helpers/prop_test_runner_swap.rs create mode 100644 packages/health-computer/tests/test_max_swap.rs create mode 100644 packages/health-computer/tests/test_max_swap_prop.rs create mode 100644 packages/health-computer/tests/test_max_swap_validation.rs diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index 56a66fbb4..d5635c3fe 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -13,7 +13,7 @@ use mars_rover_health_types::{ DenomNotPresent, MissingHLSParams, MissingParams, MissingPrice, MissingVaultConfig, MissingVaultValues, }, - HealthResult, + HealthResult, SwapKind, }; use tsify::Tsify; @@ -134,6 +134,112 @@ impl HealthComputer { Ok(min(max_withdraw_amount, withdraw_coin.amount)) } + pub fn max_swap_amount_estimate( + &self, + from_denom: &str, + to_denom: &str, + kind: &SwapKind, + ) -> HealthResult { + let from_coin = self + .positions + .deposits + .iter() + .find(|c| c.denom == *from_denom) + .ok_or(DenomNotPresent(from_denom.to_string()))?; + + // If no debt the total amount deposited can be swapped (only for default swaps) + if kind == &SwapKind::Default && self.positions.debts.is_empty() { + return Ok(from_coin.amount); + } + + let total_max_ltv_adjusted_value = + self.total_collateral_value()?.max_ltv_adjusted_collateral; + + let debt_value = self.total_debt_value()?; + + if debt_value >= total_max_ltv_adjusted_value { + return Ok(Uint128::zero()); + } + + let from_ltv = self.get_coin_max_ltv(from_denom)?; + let to_ltv = self.get_coin_max_ltv(to_denom)?; + + // Don't allow swapping when one of the assets is not whitelisted + if from_ltv == Decimal::zero() || to_ltv == Decimal::zero() { + return Ok(Uint128::zero()); + } + + let from_price = + self.denoms_data.prices.get(from_denom).ok_or(MissingPrice(from_denom.to_string()))?; + + // An asset that has a price of 1 and max ltv of 0.5 has a collateral_value of 0.5. + // Swapping that asset for an asset with the same price, but 0.8 max ltv results in a collateral_value of 0.8. + // Therefore, when the asset that is swapped to has a higher or equal max ltv than the asset swapped from, + // the collateral value will increase and we can allow the full balance to be swapped. + let swappable_amount = if to_ltv >= from_ltv { + from_coin.amount + } else { + // In order to calculate the output of the swap, the formula looks like this: + // 1 = (collateral_value + to_amount * to_price * to_ltv - from_amount * from_price * from_ltv) / debt_value + // The unknown variables here are to_amount and from_amount. In order to only have 1 unknown variable, from_amount, + // to_amount can be replaced by: + // to_amount = from_amount * from_price / to_price + // This results in the following formula: + // 1 = (collateral_value + from_amount * from_price / to_price * to_price * to_ltv - from_amount * from_price * from_ltv) / debt_value + // Rearranging this formula to isolate from_amount results in the following formula: + // from_amount = (collateral_value - debt_value) / (from_price * ( from_ltv - to_ltv)) + let amount = total_max_ltv_adjusted_value + .checked_sub(debt_value)? + .checked_sub(Uint128::one())? + .checked_div_floor(from_price.checked_mul(from_ltv - to_ltv)?)?; + + // Cap the swappable amount at the current balance of the coin + min(amount, from_coin.amount) + }; + + match kind { + SwapKind::Default => Ok(swappable_amount), + + SwapKind::Margin => { + // If the swappable amount is less than the available amount, no need to further calculate + // the margin borrow amount. + if swappable_amount < from_coin.amount { + return Ok(swappable_amount); + } + + let from_coin_value = from_coin.amount.checked_mul_floor(*from_price)?; + + // This represents the max ltv adjusted value of the coin being swapped from + let swap_from_ltv_value = from_coin_value.checked_mul_floor(from_ltv)?; + + // The from_denom is always taken on as debt, as the trade is the bullish direction + // of the to_denom (expecting it to outpace the borrow rate from the from_denom) + let swap_to_ltv_value = from_coin_value.checked_mul_floor(to_ltv)?; + + let total_max_ltv_adjust_value_after_swap = total_max_ltv_adjusted_value + .checked_sub(swap_from_ltv_value)? + .checked_add(swap_to_ltv_value)?; + + // The total swappable amount for margin is represented by the available coin balance + the + // the maximum amount that can be borrowed (and then swapped). + // This is represented by the formula: + // 1 = (collateral_after_swap + borrow_amount * borrow_price * to_ltv) / (debt + borrow_amount * borrow_price) + // Rearranging this results in: + // borrow_amount = (collateral_after_swap - debt) / ((1 - to_ltv) * borrow_price) + let borrow_amount = total_max_ltv_adjust_value_after_swap + .checked_sub(debt_value)? + .checked_sub(Uint128::one())? + .checked_div_floor( + Decimal::one().checked_sub(to_ltv)?.checked_mul(*from_price)?, + )?; + + // The total amount that can be swapped is then the balance of the coin + the additional amount + // that can be borrowed. + Ok(borrow_amount.checked_add(from_coin.amount)?) + } + } + } + /// The max this account can borrow of `borrow_denom` and maintain max_ltv >= 1 /// Note: This is an estimate. Guarantees to leave account healthy, but in edge cases, /// due to rounding, it may be slightly too conservative. @@ -308,25 +414,15 @@ impl HealthComputer { let AssetParams { credit_manager: CmSettings { - whitelisted, hls, + .. }, - max_loan_to_value, liquidation_threshold, .. } = self.denoms_data.params.get(&c.denom).ok_or(MissingParams(c.denom.clone()))?; - // If coin has been de-listed, drop MaxLTV to zero - let checked_max_ltv = if *whitelisted { - match self.kind { - AccountKind::Default => *max_loan_to_value, - AccountKind::HighLeveredStrategy => { - hls.as_ref().ok_or(MissingHLSParams(c.denom.clone()))?.max_loan_to_value - } - } - } else { - Decimal::zero() - }; + let checked_max_ltv = self.get_coin_max_ltv(&c.denom)?; + let max_ltv_adjusted = coin_value.checked_mul_floor(checked_max_ltv)?; max_ltv_adjusted_collateral = max_ltv_adjusted_collateral.checked_add(max_ltv_adjusted)?; @@ -433,4 +529,23 @@ impl HealthComputer { liquidation_threshold_adjusted_collateral, }) } + + fn get_coin_max_ltv(&self, denom: &str) -> HealthResult { + let params = self.denoms_data.params.get(denom).ok_or(MissingParams(denom.to_string()))?; + + // If the coin has been de-listed, drop MaxLTV to zero + if !params.credit_manager.whitelisted { + return Ok(Decimal::zero()); + } + + match self.kind { + AccountKind::Default => Ok(params.max_loan_to_value), + AccountKind::HighLeveredStrategy => Ok(params + .credit_manager + .hls + .as_ref() + .ok_or(MissingHLSParams(denom.to_string()))? + .max_loan_to_value), + } + } } diff --git a/packages/health-computer/src/javascript.rs b/packages/health-computer/src/javascript.rs index 80d5242c3..7e226b176 100644 --- a/packages/health-computer/src/javascript.rs +++ b/packages/health-computer/src/javascript.rs @@ -1,4 +1,4 @@ -use mars_rover_health_types::{BorrowTarget, HealthValuesResponse}; +use mars_rover_health_types::{BorrowTarget, HealthValuesResponse, SwapKind}; use wasm_bindgen::prelude::*; use crate::HealthComputer; @@ -26,3 +26,13 @@ pub fn max_borrow_estimate_js( ) -> String { c.max_borrow_amount_estimate(&borrow_denom, &target).unwrap().to_string() } + +#[wasm_bindgen] +pub fn max_swap_estimate_js( + c: HealthComputer, + from_denom: String, + to_denom: String, + kind: SwapKind, +) -> String { + c.max_swap_amount_estimate(&from_denom, &to_denom, &kind).unwrap().to_string() +} diff --git a/packages/health-computer/tests/helpers/mock_coin_info.rs b/packages/health-computer/tests/helpers/mock_coin_info.rs index 7205a4a55..8e99f8c80 100644 --- a/packages/health-computer/tests/helpers/mock_coin_info.rs +++ b/packages/health-computer/tests/helpers/mock_coin_info.rs @@ -164,3 +164,38 @@ pub fn ujuno_info() -> CoinInfo { }, } } + +pub fn uatom_info() -> CoinInfo { + let denom = "uatom".to_string(); + CoinInfo { + denom: denom.clone(), + price: Decimal::from_atomics(941236u128, 6).unwrap(), + params: AssetParams { + denom, + max_loan_to_value: Decimal::from_atomics(65u128, 2).unwrap(), + liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + credit_manager: CmSettings { + whitelisted: true, + hls: Some(HlsParams { + max_loan_to_value: Decimal::from_str("0.71").unwrap(), + liquidation_threshold: Decimal::from_str("0.74").unwrap(), + correlations: vec![HlsAssetType::Coin { + denom: "stAtom".to_string(), + }], + }), + }, + red_bank: RedBankSettings { + deposit_enabled: true, + borrow_enabled: true, + deposit_cap: Default::default(), + }, + protocol_liquidation_fee: Decimal::percent(2u64), + }, + } +} diff --git a/packages/health-computer/tests/helpers/mod.rs b/packages/health-computer/tests/helpers/mod.rs index d7e0918f3..8f0c6fc98 100644 --- a/packages/health-computer/tests/helpers/mod.rs +++ b/packages/health-computer/tests/helpers/mod.rs @@ -1,8 +1,10 @@ pub use self::{ - mock_coin_info::*, mock_vault_config::*, prop_test_runner::*, prop_test_strategies::*, + mock_coin_info::*, mock_vault_config::*, prop_test_runner_borrow::*, prop_test_runner_swap::*, + prop_test_strategies::*, }; mod mock_coin_info; mod mock_vault_config; -mod prop_test_runner; +mod prop_test_runner_borrow; +mod prop_test_runner_swap; mod prop_test_strategies; diff --git a/packages/health-computer/tests/helpers/prop_test_runner.rs b/packages/health-computer/tests/helpers/prop_test_runner_borrow.rs similarity index 100% rename from packages/health-computer/tests/helpers/prop_test_runner.rs rename to packages/health-computer/tests/helpers/prop_test_runner_borrow.rs diff --git a/packages/health-computer/tests/helpers/prop_test_runner_swap.rs b/packages/health-computer/tests/helpers/prop_test_runner_swap.rs new file mode 100644 index 000000000..633cc869f --- /dev/null +++ b/packages/health-computer/tests/helpers/prop_test_runner_swap.rs @@ -0,0 +1,107 @@ +use cosmwasm_std::{Coin, StdResult, Uint128}; +use mars_rover::msg::query::DebtAmount; +use mars_rover_health_computer::HealthComputer; +use mars_rover_health_types::SwapKind; +use proptest::{ + strategy::Strategy, + test_runner::{Config, TestRunner}, +}; + +use super::random_health_computer; + +pub fn max_swap_prop_test_runner(cases: u32, kind: &SwapKind) { + let config = Config::with_cases(cases); + + let mut runner = TestRunner::new(config); + runner + .run( + &random_health_computer().prop_filter( + "For swap we need to ensure 2 available denom params and 1 valid deposit", + |h| { + if h.denoms_data.params.len() < 2 { + false + } else { + let from_denom = h.denoms_data.params.keys().next().unwrap(); + h.positions + .deposits + .iter() + .map(|d| &d.denom) + .collect::>() + .contains(&from_denom) + } + }, + ), + |h| { + let from_denom = h.denoms_data.params.keys().next().unwrap(); + let to_denom = h.denoms_data.params.keys().nth(1).unwrap(); + + let max_swap = h.max_swap_amount_estimate(from_denom, to_denom, kind).unwrap(); + + let health_before = h.compute_health().unwrap(); + if health_before.is_above_max_ltv() { + assert_eq!(Uint128::zero(), max_swap); + } else { + let h_new = add_swap(&h, from_denom, to_denom, max_swap)?; + let health_after = h_new.compute_health().unwrap(); + + // Ensure still healthy + assert!(!health_after.is_above_max_ltv()); + } + Ok(()) + }, + ) + .unwrap(); +} + +fn add_swap( + h: &HealthComputer, + from_denom: &str, + to_denom: &str, + amount: Uint128, +) -> StdResult { + let mut new_h = h.clone(); + + let from_coin_index = + new_h.positions.deposits.iter().position(|c| c.denom == from_denom).unwrap(); + + let from_coin = new_h.positions.deposits.get_mut(from_coin_index).unwrap(); + let from_price = new_h.denoms_data.prices.get(from_denom).unwrap(); + let to_price = new_h.denoms_data.prices.get(to_denom).unwrap(); + + // Subtract the amount from current deposited balance + if amount < from_coin.amount { + from_coin.amount -= amount; + } else { + // If there the amount is larger than the balance of the coin, we need to add the remaining to the debts. + let debt_amount = amount - from_coin.amount; + new_h.positions.deposits.remove(from_coin_index); + + if let Some(debt_coin_index) = + new_h.positions.debts.iter().position(|c| c.denom == from_denom) + { + let debt_coin = new_h.positions.debts.get_mut(debt_coin_index).unwrap(); + debt_coin.amount += debt_amount; + } else { + new_h.positions.debts.push(DebtAmount { + denom: from_denom.to_string(), + shares: debt_amount * Uint128::new(1000), + amount: debt_amount, + }); + } + } + + // Add the swapped coins to the deposits + let to_coin_amount = amount.mul_ceil(from_price / to_price); + + if let Some(to_coin_index) = new_h.positions.deposits.iter().position(|c| c.denom == to_denom) { + let to_coin = new_h.positions.deposits.get_mut(to_coin_index).unwrap(); + to_coin.amount += to_coin_amount; + } else { + new_h.positions.deposits.push(Coin { + denom: to_denom.to_string(), + amount: to_coin_amount, + }); + } + + Ok(new_h) +} diff --git a/packages/health-computer/tests/test_max_swap.rs b/packages/health-computer/tests/test_max_swap.rs new file mode 100644 index 000000000..9b461b004 --- /dev/null +++ b/packages/health-computer/tests/test_max_swap.rs @@ -0,0 +1,88 @@ +use std::collections::HashMap; + +use cosmwasm_std::{coin, Uint128}; +use mars_rover::msg::query::Positions; +use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; +use mars_rover_health_types::{AccountKind, SwapKind}; + +use crate::helpers::{udai_info, umars_info}; + +pub mod helpers; + +#[test] +fn max_swap_default() { + let udai = udai_info(); + let umars = umars_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (udai.denom.clone(), udai.price), + (umars.denom.clone(), umars.price), + ]), + params: HashMap::from([ + (udai.denom.clone(), udai.params.clone()), + (umars.denom.clone(), umars.params.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &udai.denom)], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let max_borrow_amount = + h.max_swap_amount_estimate(&udai.denom, &umars.denom, &SwapKind::Default).unwrap(); + assert_eq!(Uint128::new(1200), max_borrow_amount); +} + +#[test] +fn max_swap_margin() { + let udai = udai_info(); + let umars = umars_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (udai.denom.clone(), udai.price), + (umars.denom.clone(), umars.price), + ]), + params: HashMap::from([ + (udai.denom.clone(), udai.params.clone()), + (umars.denom.clone(), umars.params.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(5000, &udai.denom), coin(500, &umars.denom)], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let max_borrow_amount = + h.max_swap_amount_estimate(&udai.denom, &umars.denom, &SwapKind::Margin).unwrap(); + assert_eq!(Uint128::new(31351), max_borrow_amount); +} diff --git a/packages/health-computer/tests/test_max_swap_prop.rs b/packages/health-computer/tests/test_max_swap_prop.rs new file mode 100644 index 000000000..6068fb5df --- /dev/null +++ b/packages/health-computer/tests/test_max_swap_prop.rs @@ -0,0 +1,14 @@ +use helpers::max_swap_prop_test_runner; +use mars_rover_health_types::SwapKind; + +pub mod helpers; + +#[test] +fn max_swap_amount_default_renders_healthy_max_ltv() { + max_swap_prop_test_runner(2000, &SwapKind::Default); +} + +#[test] +fn max_swap_amount_margin_renders_healthy_max_ltv() { + max_swap_prop_test_runner(2000, &SwapKind::Margin); +} diff --git a/packages/health-computer/tests/test_max_swap_validation.rs b/packages/health-computer/tests/test_max_swap_validation.rs new file mode 100644 index 000000000..603c3ea5d --- /dev/null +++ b/packages/health-computer/tests/test_max_swap_validation.rs @@ -0,0 +1,365 @@ +use std::{collections::HashMap, str::FromStr}; + +use cosmwasm_std::{coin, Addr, Decimal, Uint128}; +use mars_params::types::{hls::HlsParams, vault::VaultConfig}; +use mars_rover::{ + adapters::vault::{ + CoinValue, Vault, VaultAmount, VaultPosition, VaultPositionAmount, VaultPositionValue, + }, + msg::query::{DebtAmount, Positions}, +}; +use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; +use mars_rover_health_types::{AccountKind, HealthError, SwapKind}; + +use crate::helpers::{uatom_info, udai_info, umars_info, ustars_info}; + +pub mod helpers; + +#[test] +fn missing_price_data() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(umars.denom.clone(), umars.price)]), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let err: HealthError = + h.max_swap_amount_estimate(&udai.denom, &umars.denom, &SwapKind::Default).unwrap_err(); + assert_eq!(err, HealthError::MissingPrice(udai.denom)); +} + +#[test] +fn missing_params() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), + ]), + params: HashMap::from([(udai.denom.clone(), udai.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: umars.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let err: HealthError = + h.max_swap_amount_estimate(&umars.denom, &udai.denom, &SwapKind::Default).unwrap_err(); + assert_eq!(err, HealthError::MissingParams(umars.denom)); +} + +#[test] +fn deposit_not_present() { + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: Default::default(), + params: Default::default(), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let err: HealthError = + h.max_swap_amount_estimate("xyz", &udai.denom, &SwapKind::Default).unwrap_err(); + assert_eq!(err, HealthError::DenomNotPresent("xyz".to_string())); +} + +#[test] +fn zero_when_unhealthy() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), + ]), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom.clone(), + shares: Default::default(), + amount: Uint128::new(2500), + }, + DebtAmount { + denom: umars.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let health = h.compute_health().unwrap(); + assert!(health.max_ltv_health_factor < Some(Decimal::one())); + let max_swap_amount = + h.max_swap_amount_estimate(&udai.denom, &umars.denom, &SwapKind::Default).unwrap(); + assert_eq!(Uint128::zero(), max_swap_amount); +} + +#[test] +fn no_debts() { + let ustars = ustars_info(); + let umars = umars_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (ustars.denom.clone(), ustars.price), + (umars.denom.clone(), umars.price), + ]), + params: HashMap::from([ + (ustars.denom.clone(), ustars.params.clone()), + (umars.denom.clone(), umars.params.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let deposit_amount = Uint128::new(1200); + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(deposit_amount.u128(), &ustars.denom)], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let max_swap_amount = + h.max_swap_amount_estimate(&ustars.denom, &umars.denom, &SwapKind::Default).unwrap(); + assert_eq!(deposit_amount, max_swap_amount); +} + +#[test] +fn should_allow_max_swap() { + let umars = umars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), + ]), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + ]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let deposit_amount = Uint128::new(33); + let h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom), coin(deposit_amount.u128(), &udai.denom)], + debts: vec![DebtAmount { + denom: udai.denom.clone(), + shares: Default::default(), + amount: Uint128::new(5), + }], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + // Max when debt value is smaller than collateral value - withdraw denom value + let max_swap_amount = + h.max_swap_amount_estimate(&udai.denom, &umars.denom, &SwapKind::Default).unwrap(); + assert_eq!(deposit_amount, max_swap_amount); +} + +#[test] +fn hls_with_max_withdraw() { + let ustars = ustars_info(); + let uatom = uatom_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (ustars.denom.clone(), ustars.price), + (uatom.denom.clone(), uatom.price), + ]), + params: HashMap::from([ + (ustars.denom.clone(), ustars.params.clone()), + (uatom.denom.clone(), uatom.params.clone()), + ]), + }; + + let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); + + let vaults_data = VaultsData { + vault_values: HashMap::from([( + vault.address.clone(), + VaultPositionValue { + vault_coin: CoinValue { + denom: "leverage_vault_123".to_string(), + amount: Uint128::new(5264), + value: Uint128::new(5264), + }, + base_coin: CoinValue { + denom: ustars.denom.clone(), + amount: Default::default(), + value: Default::default(), + }, + }, + )]), + vault_configs: HashMap::from([( + vault.address.clone(), + VaultConfig { + addr: vault.address.clone(), + deposit_cap: Default::default(), + max_loan_to_value: Decimal::from_str("0.4").unwrap(), + liquidation_threshold: Decimal::from_str("0.5").unwrap(), + whitelisted: true, + hls: Some(HlsParams { + max_loan_to_value: Decimal::from_str("0.75").unwrap(), + liquidation_threshold: Decimal::from_str("0.8").unwrap(), + correlations: vec![], + }), + }, + )]), + }; + + let mut h = HealthComputer { + kind: AccountKind::Default, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &ustars.denom)], + debts: vec![ + DebtAmount { + denom: uatom.denom.clone(), + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: ustars.denom.clone(), + shares: Default::default(), + amount: Uint128::new(800), + }, + ], + lends: vec![], + vaults: vec![VaultPosition { + vault, + amount: VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::new(5264))), + }], + }, + denoms_data, + vaults_data, + }; + + let max_before = + h.max_swap_amount_estimate(&ustars.denom, &uatom.denom, &SwapKind::Default).unwrap(); + h.kind = AccountKind::HighLeveredStrategy; + let max_after = + h.max_swap_amount_estimate(&ustars.denom, &uatom.denom, &SwapKind::Default).unwrap(); + assert!(max_after > max_before) +} diff --git a/packages/health-types/src/account.rs b/packages/health-types/src/account.rs index 7d91c202f..f2bf0bea9 100644 --- a/packages/health-types/src/account.rs +++ b/packages/health-types/src/account.rs @@ -26,3 +26,10 @@ pub enum BorrowTarget { address: Addr, }, } +#[cw_serde] +#[derive(Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub enum SwapKind { + Default, + Margin, +} diff --git a/scripts/health/pkg-node/index.d.ts b/scripts/health/pkg-node/index.d.ts index f85c51620..1f4cefaeb 100644 --- a/scripts/health/pkg-node/index.d.ts +++ b/scripts/health/pkg-node/index.d.ts @@ -22,6 +22,19 @@ export function max_borrow_estimate_js( borrow_denom: string, target: BorrowTarget, ): string +/** + * @param {HealthComputer} c + * @param {string} from_denom + * @param {string} to_denom + * @param {SwapKind} kind + * @returns {string} + */ +export function max_swap_estimate_js( + c: HealthComputer, + from_denom: string, + to_denom: string, + kind: SwapKind, +): string export interface HealthComputer { kind: AccountKind positions: Positions @@ -40,4 +53,6 @@ export interface HealthValuesResponse { above_max_ltv: boolean } -export type BorrowTarget = 'deposit' | 'wallet' +export type SwapKind = 'default' | 'margin' + +export type BorrowTarget = 'deposit' | 'wallet' | { vault: { address: Addr } } diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js index 061e4324e..2727bffdc 100644 --- a/scripts/health/pkg-node/index.js +++ b/scripts/health/pkg-node/index.js @@ -179,6 +179,34 @@ module.exports.max_borrow_estimate_js = function (c, borrow_denom, target) { } } +/** + * @param {HealthComputer} c + * @param {string} from_denom + * @param {string} to_denom + * @param {SwapKind} kind + * @returns {string} + */ +module.exports.max_swap_estimate_js = function (c, from_denom, to_denom, kind) { + let deferred3_0 + let deferred3_1 + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16) + const ptr0 = passStringToWasm0(from_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + const ptr1 = passStringToWasm0(to_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len1 = WASM_VECTOR_LEN + wasm.max_swap_estimate_js(retptr, addHeapObject(c), ptr0, len0, ptr1, len1, addHeapObject(kind)) + var r0 = getInt32Memory0()[retptr / 4 + 0] + var r1 = getInt32Memory0()[retptr / 4 + 1] + deferred3_0 = r0 + deferred3_1 = r1 + return getStringFromWasm0(r0, r1) + } finally { + wasm.__wbindgen_add_to_stack_pointer(16) + wasm.__wbindgen_free(deferred3_0, deferred3_1, 1) + } +} + function handleError(f, args) { try { return f.apply(this, args) diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index 61e320e62bd0958a136a9130ecdac95d570cd2a2..c1c46d7230a7912617302d12651a661ee262100b 100644 GIT binary patch delta 76007 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 A_y7O^ delta 65664 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 number readonly max_withdraw_estimate_js: (a: number, b: number, c: number, d: number) => void readonly max_borrow_estimate_js: (a: number, b: number, c: number, d: number, e: number) => void + readonly max_swap_estimate_js: ( + a: number, + b: number, + c: number, + d: number, + e: number, + f: number, + g: number, + ) => void readonly allocate: (a: number) => number readonly deallocate: (a: number) => void readonly requires_stargate: () => void diff --git a/scripts/health/pkg-web/index.js b/scripts/health/pkg-web/index.js index e45fa0e5a..07bd035fd 100644 --- a/scripts/health/pkg-web/index.js +++ b/scripts/health/pkg-web/index.js @@ -192,6 +192,34 @@ export function max_borrow_estimate_js(c, borrow_denom, target) { } } +/** + * @param {HealthComputer} c + * @param {string} from_denom + * @param {string} to_denom + * @param {SwapKind} kind + * @returns {string} + */ +export function max_swap_estimate_js(c, from_denom, to_denom, kind) { + let deferred3_0 + let deferred3_1 + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16) + const ptr0 = passStringToWasm0(from_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + const ptr1 = passStringToWasm0(to_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len1 = WASM_VECTOR_LEN + wasm.max_swap_estimate_js(retptr, addHeapObject(c), ptr0, len0, ptr1, len1, addHeapObject(kind)) + var r0 = getInt32Memory0()[retptr / 4 + 0] + var r1 = getInt32Memory0()[retptr / 4 + 1] + deferred3_0 = r0 + deferred3_1 = r1 + return getStringFromWasm0(r0, r1) + } finally { + wasm.__wbindgen_add_to_stack_pointer(16) + wasm.__wbindgen_free(deferred3_0, deferred3_1, 1) + } +} + function handleError(f, args) { try { return f.apply(this, args) diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm index 0db4a61ff17f3f942707203733773be7c0b8b1fb..63a003f67339d4a7df76a2bf3c566e77cfade891 100644 GIT binary patch delta 75208 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 delta 65624 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 T=!HfDSzH^k7ybvCdON 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 Date: Sat, 5 Aug 2023 16:26:02 +0100 Subject: [PATCH 190/218] Fix unsupported Wasm import error (#171) * make CW-incompatible dependencies optional * update readme --- README.md | 32 ++++++++++++++++--- packages/health-computer/Cargo.toml | 6 ++-- .../health-computer/src/health_computer.rs | 5 +-- packages/health-types/Cargo.toml | 5 +-- packages/health-types/src/account.rs | 9 +++--- packages/health-types/src/health.rs | 5 +-- 6 files changed, 45 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a04b9a304..28215fe02 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Rover + A generalized credit protocol built on Mars lending market ## Overview @@ -9,7 +10,6 @@ Rover takes a different approach for Mars protocol, which utilizes a generalized ### Credit manager and credit accounts - The target audience of the credit manager is risk-seeking investors who wish to undertake leveraged trading or yield farming activities. To start, a user first needs to access the Mars credit manager contract and request the opening of a credit account. The credit account is analogous to a "sub-account" on centralized trading platforms and is represented by a non-fungible token (NFT). @@ -88,6 +88,24 @@ Vault writers interested in integrating with Rover must write their vaults to abide by the [Cosmos Vault Standard](https://github.com/apollodao/cosmos-vault-standard) and make a governance proposal. +### Health computer crates + +The `mars-rover-health-{types,computer}` crates have two use cases: + +- To be imported into CosmWasm contracts as libraries. When doing this, **do not** enable the `javascript` feature: + + ```toml + [dependencies] + # the `javascript` feature is disabled by default. do not enable unless needed! + mars-rover-health-types = { path = "./packages/health-types" } + ``` + +- To be compiled into a Wasm binary and run in the browser. To do this, use the following command; **do** enable the `javascript` feature. The compiled Wasm binary can be found in the `./target/wasm32-unknown-unknown/release/` folder. + + ```shell + cargo build --release -p mars-rover-health-computer --target wasm32-unknown-unknown --features javascript + ``` + ### Additional thoughts A generalized credit protocol would enable leveraged trading or yield farming capabilities that are largely available only on centralized exchanges today. End users would be able to borrow more than they've deposited into the lending protocol while still ensuring all credit accounts are fully collateralized. Mars Hub's decentralized architecture would also give third parties the ability to propose and write new trading and yield farming strategies that could tap Red Bank deposits on any supported blockchain. The proposed protocol would increase utility for traders. This new source of demand may generate higher yields for depositors and more fees for Mars Hub and MARS stakers. @@ -97,6 +115,7 @@ A generalized credit protocol would enable leveraged trading or yield farming ca ### Environment Setup - [Install rustup](https://rustup.rs/). Once installed, make sure you have the wasm32 target: + ```shell rustup default stable rustup update stable @@ -120,6 +139,7 @@ cargo install --force cargo-make ### Build Pull down Rover repo locally + ```shell git clone https://github.com/mars-protocol/rover cd rover @@ -127,23 +147,25 @@ cd rover Run `cargo build` to ensure it compiles fine. - ### Test Requires building the wasm binaries for the contracts: + ```shell docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.12.10 + cosmwasm/rust-optimizer:0.13.0 ``` For Rust cw-multi tests + osmosis-testing suite (requires mars_swapper_osmosis.wasm from previous step): + ```shell cargo test ``` For Typescript testnet deployment with the deployer address being the owner and admin of the contracts & end-to-end tests: + ```shell cd scripts yarn install @@ -151,6 +173,7 @@ yarn deploy:osmosis:testnet-deployer ``` For Typescript testnet deployment with the multisig address being the owner and admin of the contracts & end-to-end tests: + ```shell cd scripts yarn install @@ -158,6 +181,7 @@ yarn deploy:osmosis:testnet-multisig ``` For mainnet: + ```shell cd scripts yarn install diff --git a/packages/health-computer/Cargo.toml b/packages/health-computer/Cargo.toml index f9a06515c..de264713c 100644 --- a/packages/health-computer/Cargo.toml +++ b/packages/health-computer/Cargo.toml @@ -13,7 +13,7 @@ doctest = false [features] backtraces = ["cosmwasm-std/backtraces"] default = ["console_error_panic_hook"] -javascript = [] +javascript = ["tsify", "wasm-bindgen", "mars-rover-health-types/javascript"] [dependencies] cosmwasm-schema = { workspace = true } @@ -25,8 +25,8 @@ mars-rover-health-types = { workspace = true } schemars = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -tsify = { workspace = true } -wasm-bindgen = { workspace = true } +tsify = { workspace = true, optional = true } +wasm-bindgen = { workspace = true, optional = true } # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index d5635c3fe..a3773d4e6 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -15,6 +15,7 @@ use mars_rover_health_types::{ }, HealthResult, SwapKind, }; +#[cfg(feature = "javascript")] use tsify::Tsify; use crate::{CollateralValue, DenomsData, VaultsData}; @@ -22,8 +23,8 @@ use crate::{CollateralValue, DenomsData, VaultsData}; /// `HealthComputer` is a shared struct with the frontend that gets compiled to wasm. /// For this reason, it uses a dependency-injection-like pattern where all required data is needed up front. #[cw_serde] -#[derive(Tsify)] -#[tsify(into_wasm_abi, from_wasm_abi)] +#[cfg_attr(feature = "javascript", derive(Tsify))] +#[cfg_attr(feature = "javascript", tsify(into_wasm_abi, from_wasm_abi))] pub struct HealthComputer { pub kind: AccountKind, pub positions: Positions, diff --git a/packages/health-types/Cargo.toml b/packages/health-types/Cargo.toml index ee0afc2fd..faeed237b 100644 --- a/packages/health-types/Cargo.toml +++ b/packages/health-types/Cargo.toml @@ -15,6 +15,7 @@ doctest = false [features] backtraces = ["cosmwasm-std/backtraces"] +javascript = ["tsify", "wasm-bindgen"] [dependencies] cosmwasm-schema = { workspace = true } @@ -26,5 +27,5 @@ schemars = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } -tsify = { workspace = true } -wasm-bindgen = { workspace = true } +tsify = { workspace = true, optional = true } +wasm-bindgen = { workspace = true, optional = true } diff --git a/packages/health-types/src/account.rs b/packages/health-types/src/account.rs index f2bf0bea9..6c09a2a7a 100644 --- a/packages/health-types/src/account.rs +++ b/packages/health-types/src/account.rs @@ -2,6 +2,7 @@ use std::fmt; use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; +#[cfg(feature = "javascript")] use tsify::Tsify; #[cw_serde] @@ -17,8 +18,8 @@ impl fmt::Display for AccountKind { } #[cw_serde] -#[derive(Tsify)] -#[tsify(into_wasm_abi, from_wasm_abi)] +#[cfg_attr(feature = "javascript", derive(Tsify))] +#[cfg_attr(feature = "javascript", tsify(into_wasm_abi, from_wasm_abi))] pub enum BorrowTarget { Deposit, Wallet, @@ -27,8 +28,8 @@ pub enum BorrowTarget { }, } #[cw_serde] -#[derive(Tsify)] -#[tsify(into_wasm_abi, from_wasm_abi)] +#[cfg_attr(feature = "javascript", derive(Tsify))] +#[cfg_attr(feature = "javascript", tsify(into_wasm_abi, from_wasm_abi))] pub enum SwapKind { Default, Margin, diff --git a/packages/health-types/src/health.rs b/packages/health-types/src/health.rs index ba9bab0f9..ef6781322 100644 --- a/packages/health-types/src/health.rs +++ b/packages/health-types/src/health.rs @@ -3,6 +3,7 @@ use std::fmt; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Decimal, Uint128}; use mars_health::health::Health as RedBankHealth; +#[cfg(feature = "javascript")] use tsify::Tsify; #[cw_serde] @@ -53,8 +54,8 @@ pub fn is_below_one(health_factor: &Option) -> bool { } #[cw_serde] -#[derive(Tsify)] -#[tsify(into_wasm_abi, from_wasm_abi)] +#[cfg_attr(feature = "javascript", derive(Tsify))] +#[cfg_attr(feature = "javascript", tsify(into_wasm_abi, from_wasm_abi))] pub struct HealthValuesResponse { pub total_debt_value: Uint128, pub total_collateral_value: Uint128, From 60a15a59e3fe71febf767551f6ee0fe091e12eec Mon Sep 17 00:00:00 2001 From: larry <26318510+larry0x@users.noreply.github.com> Date: Sat, 5 Aug 2023 23:10:04 +0100 Subject: [PATCH 191/218] Shared deposit cap (#170) * implement deposit cap check and add relevant tests * fix tests * update schemas * update typescript types * fix clippy warnings * update red bank dependency to latest develop branch * stricter test conditions * Remove fixme. * Fix params schema. --------- Co-authored-by: Piotr Babel --- Cargo.lock | 255 +++++++++++------- Cargo.toml | 20 +- contracts/credit-manager/Cargo.toml | 22 +- contracts/credit-manager/src/deposit.rs | 44 ++- contracts/credit-manager/src/execute.rs | 52 +++- .../credit-manager/tests/helpers/contracts.rs | 9 + .../credit-manager/tests/helpers/mock_env.rs | 119 ++++++-- .../credit-manager/tests/helpers/types.rs | 2 +- .../credit-manager/tests/test_deposit_cap.rs | 145 ++++++++++ .../credit-manager/tests/test_instantiate.rs | 18 +- .../tests/test_liquidate_lend.rs | 1 - contracts/health/tests/helpers/defaults.rs | 2 +- .../health/tests/helpers/mock_env_builder.rs | 1 + contracts/health/tests/test_health_values.rs | 6 +- .../health/tests/test_liquidation_pricing.rs | 2 +- contracts/mock-red-bank/src/contract.rs | 13 +- contracts/mock-red-bank/src/execute.rs | 33 ++- contracts/mock-red-bank/src/query.rs | 11 +- contracts/mock-red-bank/src/state.rs | 3 + .../tests/helpers/mock_coin_info.rs | 12 +- .../tests/helpers/prop_test_strategies.rs | 2 +- .../tests/test_max_borrow_vault.rs | 4 +- packages/rover/src/adapters/params.rs | 15 +- packages/rover/src/error.rs | 6 + packages/rover/src/msg/execute.rs | 7 + .../mars-credit-manager.json | 26 ++ .../mars-mock-red-bank.json | 107 ++++++++ schemas/mars-params/mars-params.json | 79 +++++- .../mars-rover-health-computer.json | 8 +- scripts/codegen/index.ts | 2 +- .../MarsCreditManager.types.ts | 5 + .../MarsMockRedBank.client.ts | 13 + .../MarsMockRedBank.react-query.ts | 31 +++ .../MarsMockRedBank.types.ts | 5 + .../mars-params/MarsParams.client.ts | 10 + .../MarsParams.message-composer.ts | 1 + .../mars-params/MarsParams.react-query.ts | 27 ++ .../generated/mars-params/MarsParams.types.ts | 14 +- .../MarsRoverHealthComputer.types.ts | 2 +- 39 files changed, 938 insertions(+), 196 deletions(-) create mode 100644 contracts/credit-manager/tests/test_deposit_cap.rs diff --git a/Cargo.lock b/Cargo.lock index c649793d7..93d5b6481 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,7 +82,7 @@ checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -135,6 +135,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bindgen" version = "0.60.1" @@ -256,9 +262,12 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" +dependencies = [ + "libc", +] [[package]] name = "cexpr" @@ -386,9 +395,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d076a08ec01ed23c4396aca98ec73a38fa1fee5f310465add52b4108181c7a8" +checksum = "7e272708a9745dad8b591ef8a718571512130f2b39b33e3d7a27c558e3069394" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -399,18 +408,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec361f3c09d7b41221948fc17be9b3c96cb58e55a02f82da36f888a651f2584" +checksum = "296db6a3caca5283425ae0cf347f4e46999ba3f6620dbea8939a0e00347831ce" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb6b2fb76758ef59cddc77f2e2ae91c22f77da49037e9f182e9c2833f0e959b1" +checksum = "63c337e097a089e5b52b5d914a7ff6613332777f38ea6d9d36e1887cd0baa72e" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -421,9 +430,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfa39422f0d9f1c9a6fd3711573258495314dfa3aae738ea825ecd9964bc659" +checksum = "766cc9e7c1762d8fc9c0265808910fcad755200cd0e624195a491dd885a61169" dependencies = [ "proc-macro2", "quote", @@ -432,9 +441,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f6dc2ee23313add5ecacc3ccac217b9967ad9d2d11bd56e5da6aa65a9da6138" +checksum = "eb5e05a95fd2a420cca50f4e94eb7e70648dac64db45e90403997ebefeb143bd" dependencies = [ "base64", "bnum", @@ -886,9 +895,9 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -1018,7 +1027,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -1417,9 +1426,9 @@ checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "log" @@ -1441,12 +1450,26 @@ dependencies = [ "cw721-base 0.16.0", "cw721-base 0.18.0", "mars-mock-rover-health", - "mars-red-bank-types 1.1.0-ntrn-2", + "mars-red-bank-types", "mars-rover-health-types", "serde_json", "thiserror", ] +[[package]] +name = "mars-address-provider" +version = "1.2.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" +dependencies = [ + "bech32", + "cosmwasm-std", + "cw-storage-plus 1.1.0", + "cw2 1.1.0", + "mars-owner", + "mars-red-bank-types", + "thiserror", +] + [[package]] name = "mars-credit-manager" version = "2.0.0" @@ -1464,6 +1487,7 @@ dependencies = [ "cw721-base 0.18.0", "itertools 0.11.0", "mars-account-nft", + "mars-address-provider", "mars-liquidation", "mars-mock-incentives", "mars-mock-oracle", @@ -1471,29 +1495,41 @@ dependencies = [ "mars-mock-vault", "mars-owner", "mars-params", - "mars-red-bank-types 1.1.0-ntrn-2", + "mars-red-bank-types", "mars-rover", "mars-rover-health", "mars-rover-health-types", "mars-swapper-mock", "mars-v2-zapper-mock", + "test-case", ] [[package]] name = "mars-health" -version = "1.1.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=cf28835abb717b00a2f30eef06db4ba32d3eeae2#cf28835abb717b00a2f30eef06db4ba32d3eeae2" +version = "1.2.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" dependencies = [ "cosmwasm-std", "mars-params", - "mars-red-bank-types 1.1.0", + "mars-red-bank-types", "thiserror", ] +[[package]] +name = "mars-interest-rate" +version = "1.2.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "mars-red-bank-types", + "mars-utils", +] + [[package]] name = "mars-liquidation" version = "1.0.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=cf28835abb717b00a2f30eef06db4ba32d3eeae2#cf28835abb717b00a2f30eef06db4ba32d3eeae2" +source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" dependencies = [ "cosmwasm-std", "mars-health", @@ -1521,7 +1557,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", - "mars-red-bank-types 1.1.0-ntrn-2", + "mars-red-bank-types", ] [[package]] @@ -1531,7 +1567,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", - "mars-red-bank-types 1.1.0-ntrn-2", + "mars-red-bank-types", ] [[package]] @@ -1542,7 +1578,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "mars-red-bank-types 1.1.0-ntrn-2", + "mars-red-bank-types", ] [[package]] @@ -1564,7 +1600,7 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard", - "mars-red-bank-types 1.1.0-ntrn-2", + "mars-red-bank-types", "mars-rover", "thiserror", ] @@ -1585,14 +1621,16 @@ dependencies = [ [[package]] name = "mars-params" version = "1.0.7" -source = "git+https://github.com/mars-protocol/red-bank?rev=cf28835abb717b00a2f30eef06db4ba32d3eeae2#cf28835abb717b00a2f30eef06db4ba32d3eeae2" +source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", "cw2 1.1.0", + "mars-interest-rate", "mars-owner", - "mars-utils 1.1.0", + "mars-red-bank-types", + "mars-utils", "schemars", "serde", "thiserror", @@ -1600,27 +1638,13 @@ dependencies = [ [[package]] name = "mars-red-bank-types" -version = "1.1.0-ntrn-2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95aa8267e5681a101153d1ebc5b2a57a8d9f704c6aae519646ea5288d7fab8b5" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "mars-owner", - "mars-utils 1.0.0", - "strum", - "thiserror", -] - -[[package]] -name = "mars-red-bank-types" -version = "1.1.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=cf28835abb717b00a2f30eef06db4ba32d3eeae2#cf28835abb717b00a2f30eef06db4ba32d3eeae2" +version = "1.2.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "mars-owner", - "mars-utils 1.1.0", + "mars-utils", "strum", "thiserror", ] @@ -1641,7 +1665,7 @@ dependencies = [ "mars-liquidation", "mars-owner", "mars-params", - "mars-red-bank-types 1.1.0-ntrn-2", + "mars-red-bank-types", "mars-rover-health-types", "mars-v2-zapper-base", "schemars", @@ -1666,7 +1690,7 @@ dependencies = [ "mars-mock-vault", "mars-owner", "mars-params", - "mars-red-bank-types 1.1.0-ntrn-2", + "mars-red-bank-types", "mars-rover", "mars-rover-health-computer", "mars-rover-health-types", @@ -1700,7 +1724,7 @@ dependencies = [ "cosmwasm-std", "mars-health", "mars-owner", - "mars-red-bank-types 1.1.0-ntrn-2", + "mars-red-bank-types", "schemars", "serde", "serde_json", @@ -1718,7 +1742,7 @@ dependencies = [ "cw-paginate", "cw-storage-plus 1.1.0", "mars-owner", - "mars-red-bank-types 1.1.0-ntrn-2", + "mars-red-bank-types", "schemars", "serde", "thiserror", @@ -1731,23 +1755,13 @@ dependencies = [ "anyhow", "cosmwasm-std", "cw-multi-test", - "mars-red-bank-types 1.1.0-ntrn-2", -] - -[[package]] -name = "mars-utils" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bae572eda20842ade4bf8ab09ce0856cae5cff89dbeb7c51e9123489e48256" -dependencies = [ - "cosmwasm-std", - "thiserror", + "mars-red-bank-types", ] [[package]] name = "mars-utils" -version = "1.1.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=cf28835abb717b00a2f30eef06db4ba32d3eeae2#cf28835abb717b00a2f30eef06db4ba32d3eeae2" +version = "1.2.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" dependencies = [ "cosmwasm-std", "thiserror", @@ -1774,7 +1788,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "mars-red-bank-types 1.1.0-ntrn-2", + "mars-red-bank-types", "mars-rover", "mars-v2-zapper-base", "thiserror", @@ -1945,9 +1959,9 @@ dependencies = [ [[package]] name = "osmosis-test-tube" -version = "16.0.1" +version = "16.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527375c01396e7e4de4ccc18a0141aeb6b342dc089d30c57282025f3a8753e72" +checksum = "4929047d1dcec5d7d02fd0a00ecdfca78918d3a33bffc193bf57b3eeb4d407ab" dependencies = [ "base64", "bindgen", @@ -2032,7 +2046,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -2063,6 +2077,30 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.66" @@ -2230,9 +2268,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" dependencies = [ "aho-corasick", "memchr", @@ -2311,9 +2349,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.4" +version = "0.38.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "1ee020b1716f0a80e2ace9b03441a749e402e86712f15f16fe8a8f75afac732f" dependencies = [ "bitflags 2.3.3", "errno", @@ -2468,9 +2506,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.175" +version = "1.0.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b" +checksum = "6d3e73c93c3240c0bda063c239298e633114c69a888c3e37ca8bb33f343e9890" dependencies = [ "serde_derive", ] @@ -2504,13 +2542,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.175" +version = "1.0.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4" +checksum = "be02f6cb0cd3a5ec20bbcfbcbd749f57daddb1a0882dc2e46a6c236c90b977ed" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -2532,14 +2570,14 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] name = "serde_json" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -2548,13 +2586,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e168eaaf71e8f9bd6037feb05190485708e019f4fd87d161b3c0a0d37daf85e5" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -2709,9 +2747,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.27" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -2836,6 +2874,41 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-case" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1d6e7bde536b0412f20765b76e921028059adfd1b90d8974d33fd3c91b25df" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d10394d5d1e27794f772b6fc854c7e91a2dc26e2cbf807ad523370c2a59c0cee" +dependencies = [ + "cfg-if", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "test-case-macros" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb9a44b1c6a54c1ba58b152797739dba2a83ca74e18168a68c980eb142f9404" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", + "test-case-core", +] + [[package]] name = "test-tube" version = "0.1.5" @@ -2875,7 +2948,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -2936,7 +3009,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -3027,7 +3100,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.28.0", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -3147,7 +3220,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", "wasm-bindgen-shared", ] @@ -3169,7 +3242,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3334,5 +3407,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] diff --git a/Cargo.toml b/Cargo.toml index f7dca12ed..911b31c27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,26 +58,28 @@ schemars = "0.8.12" serde = { version = "1.0.175", default-features = false, features = ["derive"] } serde_json = "1.0.103" serde-wasm-bindgen = "0.5.0" +test-case = "3.1.0" thiserror = "1.0.44" tsify = "0.4.5" wasm-bindgen = "0.2.87" # mars packages -mars-health = { git = "https://github.com/mars-protocol/red-bank", rev = "cf28835abb717b00a2f30eef06db4ba32d3eeae2" } -mars-liquidation = { git = "https://github.com/mars-protocol/red-bank", rev = "cf28835abb717b00a2f30eef06db4ba32d3eeae2" } -mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "00301d60c38af09d8eb7980355009e2f00c6f41f" } +mars-health = { git = "https://github.com/mars-protocol/red-bank", rev = "18e9c56" } +mars-liquidation = { git = "https://github.com/mars-protocol/red-bank", rev = "18e9c56" } +mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "18e9c56" } mars-owner = { version = "1.2.0", features = ["emergency-owner"] } -mars-red-bank-types = "1.1.0-ntrn-2" +mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank", rev = "18e9c56" } mars-rover-health-computer = { version = "2.0.0", path = "./packages/health-computer" } mars-rover-health-types = { version = "2.0.0", path = "./packages/health-types" } mars-rover = { version = "2.0.0", path = "./packages/rover" } # contracts -mars-account-nft = { version = "2.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-params = { git = "https://github.com/mars-protocol/red-bank", rev = "cf28835abb717b00a2f30eef06db4ba32d3eeae2", features = ["library"] } -mars-rover-health = { version = "2.0.0", path = "./contracts/health", features = ["library"] } -mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base", features = ["library"] } -mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base", features = ["library"] } +mars-address-provider = { git = "https://github.com/mars-protocol/red-bank", rev = "18e9c56", features = ["library"] } +mars-account-nft = { version = "2.0.0", path = "./contracts/account-nft", features = ["library"] } +mars-params = { git = "https://github.com/mars-protocol/red-bank", rev = "18e9c56", features = ["library"] } +mars-rover-health = { version = "2.0.0", path = "./contracts/health", features = ["library"] } +mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base", features = ["library"] } +mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base", features = ["library"] } # mocks mars-mock-credit-manager = { version = "2.0.0", path = "./contracts/mock-credit-manager", features = ["library"] } diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index bb68169eb..9e67a4a8d 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -37,13 +37,15 @@ mars-rover = { workspace = true } mars-rover-health-types = { workspace = true } [dev-dependencies] -anyhow = { workspace = true } -cw-multi-test = { workspace = true } -itertools = { workspace = true } -mars-mock-incentives = { workspace = true } -mars-mock-oracle = { workspace = true } -mars-mock-red-bank = { workspace = true } -mars-mock-vault = { workspace = true } -mars-rover-health = { workspace = true } -mars-swapper-mock = { workspace = true } -mars-v2-zapper-mock = { workspace = true } +anyhow = { workspace = true } +cw-multi-test = { workspace = true } +itertools = { workspace = true } +mars-address-provider = { workspace = true } +mars-mock-incentives = { workspace = true } +mars-mock-oracle = { workspace = true } +mars-mock-red-bank = { workspace = true } +mars-mock-vault = { workspace = true } +mars-rover-health = { workspace = true } +mars-swapper-mock = { workspace = true } +mars-v2-zapper-mock = { workspace = true } +test-case = { workspace = true } diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index e50387c09..7ef3d0178 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -1,10 +1,16 @@ -use cosmwasm_std::{Coin, DepsMut, Response, Uint128}; +use std::collections::BTreeSet; + +use cosmwasm_std::{Coin, Deps, DepsMut, Response, Uint128}; +use mars_params::msg::TotalDepositResponse; use mars_rover::{ coins::Coins, error::{ContractError, ContractResult}, }; -use crate::utils::{assert_coin_is_whitelisted, increment_coin_balance}; +use crate::{ + state::PARAMS, + utils::{assert_coin_is_whitelisted, increment_coin_balance}, +}; pub fn deposit( deps: &mut DepsMut, @@ -43,3 +49,37 @@ fn assert_sent_fund(expected: &Coin, received_coins: &Coins) -> ContractResult<( Ok(()) } + +/// Given a list of denoms, assert that the total deposited amount of each denom +/// across Red Bank and Rover does not exceed its deposit cap recorded in the +/// params contract. +pub fn assert_deposit_caps(deps: Deps, denoms: BTreeSet) -> ContractResult { + let params = PARAMS.load(deps.storage)?; + + let mut response = Response::new().add_attribute("action", "callback/assert_deposit_caps"); + + for denom in denoms { + let TotalDepositResponse { + denom, + amount, + cap, + } = params.query_total_deposit(&deps.querier, &denom)?; + + if amount > cap { + return Err(ContractError::AboveAssetDepositCap { + new_value: Coin { + denom, + amount, + }, + maximum: cap, + }); + } + + response = response + .add_attribute("denom", denom) + .add_attribute("amount", amount) + .add_attribute("cap", cap); + } + + Ok(response) +} diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 1bbc2bd84..ee07a8156 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use cosmwasm_std::{ to_binary, Addr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdResult, WasmMsg, }; @@ -13,7 +15,7 @@ use mars_rover_health_types::AccountKind; use crate::{ borrow::borrow, claim_rewards::claim_rewards, - deposit::deposit, + deposit::{assert_deposit_caps, deposit}, health::{assert_max_ltv, query_health_state}, hls::assert_account_requirements, lend::lend, @@ -74,10 +76,34 @@ pub fn dispatch_actions( let mut received_coins = Coins::try_from(info.funds)?; let prev_health_state = query_health_state(deps.as_ref(), account_id, ActionKind::Default)?; + // We use a Set to record all denoms whose deposited amount may go up as the + // result of any action. We invoke the AssertDepositCaps callback in the end + // to make sure that none of the deposit cap is exceeded. + // + // Additionally, we use a BTreeSet (instead of a Vec or HashSet) to ensure + // uniqueness and determininism. + // + // There are a few actions that may result in an asset's deposit amount + // going up: + // - Deposit: we check the deposited denom + // - SwapExactIn: we check the output denom + // - ClaimRewards: we don't check here; the reward amount is likely small so + // won't have much impact; this is also difficult to handle given that now + // we have multi-rewards + // - ExitVault/ExitVaultUnlocked: we don't check here; it isn't reasonable + // to not allow a user to exit a vault because deposit cap will be exceeded + // + // Note that Borrow/Lend/Reclaim does not impact total deposit amount, + // because they simply move assets between Red Bank and Rover. We don't + // check these actions. + let mut denoms_for_cap_check = BTreeSet::new(); + for action in actions { match action { Action::Deposit(coin) => { response = deposit(&mut deps, response, account_id, &coin, &mut received_coins)?; + // check the deposit cap of the deposited denom + denoms_for_cap_check.insert(coin.denom); } Action::Withdraw(coin) => callbacks.push(CallbackMsg::Withdraw { account_id: account_id.to_string(), @@ -158,12 +184,16 @@ pub fn dispatch_actions( coin_in, denom_out, slippage, - } => callbacks.push(CallbackMsg::SwapExactIn { - account_id: account_id.to_string(), - coin_in, - denom_out, - slippage, - }), + } => { + callbacks.push(CallbackMsg::SwapExactIn { + account_id: account_id.to_string(), + coin_in, + denom_out: denom_out.clone(), + slippage, + }); + // check the deposit cap of the swap output denom + denoms_for_cap_check.insert(denom_out); + } Action::ExitVault { vault, amount, @@ -233,6 +263,11 @@ pub fn dispatch_actions( account_id: account_id.to_string(), prev_health_state, }, + // After user selected actions, we assert that the relevant deposit caps + // are not exceeded. + CallbackMsg::AssertDepositCaps { + denoms: denoms_for_cap_check, + }, // Removes guard so that subsequent action dispatches can be made CallbackMsg::RemoveReentrancyGuard {}, ]); @@ -291,6 +326,9 @@ pub fn execute_callback( account_id, prev_health_state, } => assert_max_ltv(deps.as_ref(), &account_id, prev_health_state), + CallbackMsg::AssertDepositCaps { + denoms, + } => assert_deposit_caps(deps.as_ref(), denoms), CallbackMsg::EnterVault { account_id, vault, diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index c4cc9a37a..eab31c739 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -24,6 +24,15 @@ pub fn mock_account_nft_contract() -> Box> { Box::new(contract) } +pub fn mock_address_provider_contract() -> Box> { + let contract = ContractWrapper::new( + mars_address_provider::contract::execute, + mars_address_provider::contract::instantiate, + mars_address_provider::contract::query, + ); + Box::new(contract) +} + pub fn mock_red_bank_contract() -> Box> { let contract = ContractWrapper::new( mars_mock_red_bank::contract::execute, diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index f1c1ff3eb..5acfec15c 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -35,9 +35,11 @@ use mars_params::{ }, }; use mars_red_bank_types::{ + address_provider::{self, MarsAddressType}, incentives::{ExecuteMsg::BalanceChange, QueryMsg::UserUnclaimedRewards}, oracle::ActionKind, red_bank::{ + self, InitOrUpdateAssetParams, InterestRateModel, QueryMsg::{UserCollateral, UserDebt}, UserCollateralResponse, UserDebtResponse, }, @@ -75,10 +77,10 @@ use mars_rover_health_types::{ use mars_v2_zapper_mock::msg::{InstantiateMsg as ZapperInstantiateMsg, LpConfig}; use crate::helpers::{ - lp_token_info, mock_account_nft_contract, mock_health_contract, mock_incentives_contract, - mock_oracle_contract, mock_params_contract, mock_red_bank_contract, mock_rover_contract, - mock_swapper_contract, mock_v2_zapper_contract, mock_vault_contract, AccountToFund, CoinInfo, - VaultTestInfo, + lp_token_info, mock_account_nft_contract, mock_address_provider_contract, mock_health_contract, + mock_incentives_contract, mock_oracle_contract, mock_params_contract, mock_red_bank_contract, + mock_rover_contract, mock_swapper_contract, mock_v2_zapper_contract, mock_vault_contract, + AccountToFund, CoinInfo, VaultTestInfo, }; pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000); @@ -98,6 +100,7 @@ pub struct MockEnvBuilder { pub emergency_owner: Option, pub vault_configs: Option>, pub coin_params: Option>, + pub address_provider: Option, pub oracle: Option, pub params: Option, pub red_bank: Option, @@ -120,6 +123,7 @@ impl MockEnv { emergency_owner: None, vault_configs: None, coin_params: None, + address_provider: None, oracle: None, params: None, red_bank: None, @@ -772,11 +776,44 @@ impl MockEnvBuilder { } } + fn set_address(&mut self, address_type: MarsAddressType, address: Addr) { + let address_provider_addr = self.get_address_provider(); + self.app + .execute_contract( + self.get_owner(), + address_provider_addr, + &address_provider::ExecuteMsg::SetAddress { + address_type, + address: address.into(), + }, + &[], + ) + .unwrap(); + } + fn add_params_to_contract(&mut self) { let params_to_set = self.get_coin_params(); let params_contract = self.get_params_contract(); + let red_bank_contract = self.get_red_bank(); for coin_info in params_to_set { + // initialize red bank market + self.app + .execute_contract( + Addr::unchecked("red_bank_contract_owner"), + Addr::unchecked(red_bank_contract.address()), + &red_bank::ExecuteMsg::InitAsset { + denom: coin_info.denom.clone(), + params: InitOrUpdateAssetParams { + reserve_factor: Some(Decimal::zero()), + interest_rate_model: Some(InterestRateModel::default()), + }, + }, + &[], + ) + .unwrap(); + + // save asset params to mars-params contract self.app .execute_contract( self.get_owner(), @@ -834,30 +871,64 @@ impl MockEnvBuilder { let health_contract = self.get_health_contract().into(); let params = self.get_params_contract().into(); - self.app.instantiate_contract( - code_id, - self.get_owner(), - &InstantiateMsg { - owner: self.get_owner().to_string(), - red_bank, - oracle, - max_unlocking_positions, - swapper, - zapper, - health_contract, - params, - incentives, - }, - &[], - "mock-rover-contract", - None, - ) + let addr = self + .app + .instantiate_contract( + code_id, + self.get_owner(), + &InstantiateMsg { + owner: self.get_owner().to_string(), + red_bank, + oracle, + max_unlocking_positions, + swapper, + zapper, + health_contract, + params, + incentives, + }, + &[], + "mock-rover-contract", + None, + ) + .unwrap(); + + self.set_address(MarsAddressType::CreditManager, addr.clone()); + + Ok(addr) } fn get_owner(&self) -> Addr { self.owner.clone().unwrap_or_else(|| Addr::unchecked("owner")) } + fn get_address_provider(&mut self) -> Addr { + if self.address_provider.is_none() { + let addr = self.deploy_address_provider(); + self.address_provider = Some(addr); + } + self.address_provider.clone().unwrap() + } + + fn deploy_address_provider(&mut self) -> Addr { + let contract_code_id = self.app.store_code(mock_address_provider_contract()); + let owner = self.get_owner(); + + self.app + .instantiate_contract( + contract_code_id, + owner.clone(), + &address_provider::InstantiateMsg { + owner: owner.into(), + prefix: "".into(), + }, + &[], + "mock-address-provider", + None, + ) + .unwrap() + } + fn get_oracle(&mut self) -> Oracle { if self.oracle.is_none() { let addr = self.deploy_oracle(); @@ -923,6 +994,7 @@ impl MockEnvBuilder { pub fn deploy_params_contract(&mut self) -> Params { let contract_code_id = self.app.store_code(mock_params_contract()); let owner = self.get_owner(); + let address_provider = self.get_address_provider(); let addr = self .app @@ -931,6 +1003,7 @@ impl MockEnvBuilder { owner.clone(), &ParamsInstantiateMsg { owner: owner.to_string(), + address_provider: address_provider.into(), target_health_factor: self .target_health_factor .unwrap_or(Decimal::from_str("1.2").unwrap()), @@ -1024,6 +1097,8 @@ impl MockEnvBuilder { .unwrap(); } + self.set_address(MarsAddressType::RedBank, addr.clone()); + RedBankUnchecked::new(addr.to_string()) } diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index c0287baf2..2e413b1be 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -77,12 +77,12 @@ impl From for AssetParamsUnchecked { red_bank: RedBankSettings { deposit_enabled: true, borrow_enabled: true, - deposit_cap: Uint128::MAX, }, max_loan_to_value: c.max_ltv, liquidation_threshold: c.liquidation_threshold, liquidation_bonus: c.liquidation_bonus, protocol_liquidation_fee: c.protocol_liquidation_fee, + deposit_cap: Uint128::MAX, } } } diff --git a/contracts/credit-manager/tests/test_deposit_cap.rs b/contracts/credit-manager/tests/test_deposit_cap.rs new file mode 100644 index 000000000..a2eb4424b --- /dev/null +++ b/contracts/credit-manager/tests/test_deposit_cap.rs @@ -0,0 +1,145 @@ +// must be public module so that clippy doesn't complain "dead code" +pub mod helpers; + +use std::collections::HashMap; + +use cosmwasm_std::{Addr, Coin, Coins, Decimal, StdResult, Uint128}; +use mars_params::{msg::AssetParamsUpdate, types::asset::AssetParams}; +use mars_rover::{ + error::ContractError, + msg::execute::{Action, ActionAmount, ActionCoin}, +}; +use test_case::test_case; + +use crate::helpers::{uatom_info, uosmo_info, AccountToFund, MockEnv}; + +#[test_case( + [].into(), + vec![ + Action::Deposit(Coin { + denom: "uatom".into(), + amount: Uint128::new(123), + }), + Action::Deposit(Coin { + denom: "uosmo".into(), + amount: Uint128::new(456), + }), + ], + true; + "no deposit cap" +)] +#[test_case( + [("uatom", 100)].into(), + vec![ + Action::Deposit(Coin { + denom: "uatom".into(), + amount: Uint128::new(101), // this exceeds the cap of 100 + }), + Action::Deposit(Coin { + denom: "uosmo".into(), + amount: Uint128::new(456), + }), + ], + false; + "deposit cap exceeded" +)] +#[test_case( + [("uatom", 100)].into(), + vec![ + // this first action exceeds deposit cap... + Action::Deposit(Coin { + denom: "uatom".into(), + amount: Uint128::new(101), + }), + // but we immediately does a swap to uatom, which does not exceed cap + // therefore, the tx should be successful + Action::SwapExactIn { + coin_in: ActionCoin { + denom: "uatom".into(), + amount: ActionAmount::AccountBalance, + }, + denom_out: "uosmo".into(), + slippage: Decimal::one(), + } + ], + true; + "a deposit action causes cap to be exceeded but a follow up swap action saves it" +)] +#[test_case( + // in our specific test setup, 123 uatom swaps to 1337 uosmo + // we set the cap to 1000 uosmo which should be exceeded + [("uatom", 200), ("uosmo", 1000)].into(), + vec![ + Action::Deposit(Coin { + denom: "uatom".into(), + amount: Uint128::new(123), + }), + Action::SwapExactIn { + coin_in: ActionCoin { + denom: "uatom".into(), + amount: ActionAmount::AccountBalance, + }, + denom_out: "uosmo".into(), + slippage: Decimal::one(), + } + ], + false; + "a deposit action is below cap but a follow up swap action exceeds the cap" +)] +fn asserting_deposit_cap( + deposit_caps: HashMap<&'static str, u128>, + actions: Vec, + exp_ok: bool, +) { + let user = Addr::unchecked("user"); + + // compute how much coins need to be sent to the contract in order to update + // the credit account + let send_funds = actions + .iter() + .try_fold(Coins::default(), |mut coins, action| -> StdResult<_> { + if let Action::Deposit(coin) = action { + coins.add(coin.clone())?; + } + Ok(coins) + }) + .unwrap() + .to_vec(); + + // set up mock environment + let mut mock = MockEnv::new() + .set_params(&[uosmo_info(), uatom_info()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: send_funds.clone(), + }) + .build() + .unwrap(); + + // set deposit caps for uosmo and uatom + // the `uosmo_info` and `uatom_info` functions set the cap to Uint128::MAX, + // so here we need to update them to our intended value for the purpose of + // this test + for (denom, cap) in deposit_caps { + let mut params: AssetParams = mock.query_asset_params(denom); + params.deposit_cap = cap.into(); + mock.update_asset_params(AssetParamsUpdate::AddOrUpdate { + params: params.into(), + }); + } + + // register an account + let account_id = mock.create_credit_account(&user).unwrap(); + + // attempt to execute the actions + let result = mock.update_credit_account(&account_id, &user, actions, &send_funds); + + if exp_ok { + assert!(result.is_ok()); + } else { + let err: ContractError = result.unwrap_err().downcast().unwrap(); + // if errors, we make sure the error is the AboveAssetDepositCap error + // and not any other error + assert!(matches!(err, ContractError::AboveAssetDepositCap { .. })); + } +} diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 2db295496..b940423c0 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -11,12 +11,10 @@ fn owner_set_on_instantiate() { } #[test] +#[should_panic] fn raises_on_invalid_owner_addr() { let owner = "%%%INVALID%%%"; - let res = MockEnv::new().owner(owner).params_contract("xyz").health_contract("abc").build(); - if res.is_ok() { - panic!("Should have thrown an error"); - } + MockEnv::new().owner(owner).params_contract("xyz").health_contract("abc").build().unwrap(); } #[test] @@ -35,11 +33,9 @@ fn red_bank_set_on_instantiate() { } #[test] +#[should_panic] fn raises_on_invalid_red_bank_addr() { - let mock = MockEnv::new().red_bank("%%%INVALID%%%").build(); - if mock.is_ok() { - panic!("Should have thrown an error"); - } + MockEnv::new().red_bank("%%%INVALID%%%").build().unwrap(); } #[test] @@ -67,9 +63,7 @@ fn params_set_on_instantiate() { } #[test] +#[should_panic] fn raises_on_invalid_params_addr() { - let mock = MockEnv::new().params("%%%INVALID%%%").build(); - if mock.is_ok() { - panic!("Should have thrown an error"); - } + MockEnv::new().params("%%%INVALID%%%").build().unwrap(); } diff --git a/contracts/credit-manager/tests/test_liquidate_lend.rs b/contracts/credit-manager/tests/test_liquidate_lend.rs index d3234fb10..b888584ca 100644 --- a/contracts/credit-manager/tests/test_liquidate_lend.rs +++ b/contracts/credit-manager/tests/test_liquidate_lend.rs @@ -349,7 +349,6 @@ fn lent_position_fully_liquidated() { let atom_debt = get_debt("uatom", &position.debts); assert_eq!(atom_debt.amount, Uint128::new(480)); - // FIXME: dust because of roundings, is it possible to avoid it? assert_eq!(position.lends.len(), 1); let osmo_balance = get_coin("uosmo", &position.lends); assert_eq!(osmo_balance.amount, Uint128::new(3)); diff --git a/contracts/health/tests/helpers/defaults.rs b/contracts/health/tests/helpers/defaults.rs index 6e8c7fcaa..a7482fae9 100644 --- a/contracts/health/tests/helpers/defaults.rs +++ b/contracts/health/tests/helpers/defaults.rs @@ -20,7 +20,6 @@ pub fn default_asset_params(denom: &str) -> AssetParamsUnchecked { red_bank: RedBankSettings { deposit_enabled: false, borrow_enabled: false, - deposit_cap: Default::default(), }, max_loan_to_value: Decimal::from_str("0.4523").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), @@ -31,5 +30,6 @@ pub fn default_asset_params(denom: &str) -> AssetParamsUnchecked { max_lb: Decimal::percent(10u64), }, protocol_liquidation_fee: Decimal::percent(2u64), + deposit_cap: Default::default(), } } diff --git a/contracts/health/tests/helpers/mock_env_builder.rs b/contracts/health/tests/helpers/mock_env_builder.rs index fb699e8a7..d6561be5a 100644 --- a/contracts/health/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/helpers/mock_env_builder.rs @@ -189,6 +189,7 @@ impl MockEnvBuilder { owner.clone(), &ParamsInstantiateMsg { owner: owner.to_string(), + address_provider: "n/a".to_string(), target_health_factor: Decimal::from_str("1.2").unwrap(), }, &[], diff --git a/contracts/health/tests/test_health_values.rs b/contracts/health/tests/test_health_values.rs index a6d8a4c33..58b0f152e 100644 --- a/contracts/health/tests/test_health_values.rs +++ b/contracts/health/tests/test_health_values.rs @@ -127,7 +127,6 @@ fn adds_vault_base_denoms_to_oracle_and_red_bank() { red_bank: RedBankSettings { deposit_enabled: false, borrow_enabled: false, - deposit_cap: Default::default(), }, max_loan_to_value, liquidation_threshold, @@ -138,6 +137,7 @@ fn adds_vault_base_denoms_to_oracle_and_red_bank() { max_lb: Decimal::percent(10u64), }, protocol_liquidation_fee: Decimal::percent(2u64), + deposit_cap: Default::default(), }, }; @@ -193,12 +193,12 @@ fn whitelisted_coins_work() { red_bank: RedBankSettings { deposit_enabled: false, borrow_enabled: false, - deposit_cap: Default::default(), }, max_loan_to_value, liquidation_threshold, liquidation_bonus, protocol_liquidation_fee: Decimal::percent(2u64), + deposit_cap: Default::default(), }; let update = AddOrUpdate { @@ -294,7 +294,6 @@ fn vault_whitelist_affects_max_ltv() { red_bank: RedBankSettings { deposit_enabled: false, borrow_enabled: false, - deposit_cap: Default::default(), }, max_loan_to_value: Decimal::from_str("0.4523").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), @@ -305,6 +304,7 @@ fn vault_whitelist_affects_max_ltv() { max_lb: Decimal::percent(10u64), }, protocol_liquidation_fee: Decimal::percent(2u64), + deposit_cap: Default::default(), }, }; diff --git a/contracts/health/tests/test_liquidation_pricing.rs b/contracts/health/tests/test_liquidation_pricing.rs index a040785ff..ab0a8fe9a 100644 --- a/contracts/health/tests/test_liquidation_pricing.rs +++ b/contracts/health/tests/test_liquidation_pricing.rs @@ -37,7 +37,6 @@ fn uses_liquidation_pricing() { red_bank: RedBankSettings { deposit_enabled: false, borrow_enabled: false, - deposit_cap: Default::default(), }, max_loan_to_value: Decimal::from_atomics(4523u128, 4).unwrap(), liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), @@ -48,6 +47,7 @@ fn uses_liquidation_pricing() { max_lb: Decimal::percent(10u64), }, protocol_liquidation_fee: Decimal::percent(2u64), + deposit_cap: Default::default(), }, }; diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index 7c5aaf089..6f6a69173 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -6,8 +6,8 @@ use cosmwasm_std::{ use mars_red_bank_types::red_bank; use crate::{ - execute::{borrow, deposit, repay, withdraw}, - query::{query_collateral, query_collaterals, query_debt}, + execute::{borrow, deposit, init_asset, repay, withdraw}, + query::{query_collateral, query_collaterals, query_debt, query_market}, }; #[cfg_attr(not(feature = "library"), entry_point)] @@ -23,11 +23,15 @@ pub fn instantiate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, - _env: Env, + env: Env, info: MessageInfo, msg: red_bank::ExecuteMsg, ) -> StdResult { match msg { + red_bank::ExecuteMsg::InitAsset { + denom, + params, + } => init_asset(deps, env, denom, params), red_bank::ExecuteMsg::Borrow { denom, amount, @@ -52,6 +56,9 @@ pub fn execute( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: red_bank::QueryMsg) -> StdResult { match msg { + red_bank::QueryMsg::Market { + denom, + } => to_binary(&query_market(deps, denom)?), red_bank::QueryMsg::UserDebt { user, denom, diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs index dee071319..a3cc1652e 100644 --- a/contracts/mock-red-bank/src/execute.rs +++ b/contracts/mock-red-bank/src/execute.rs @@ -1,13 +1,42 @@ use cosmwasm_std::{ - coin, BankMsg, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, Uint128, + coin, BankMsg, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, Response, StdError, StdResult, + Uint128, }; use cw_utils::one_coin; +use mars_red_bank_types::red_bank::{InitOrUpdateAssetParams, Market}; use crate::{ helpers::{load_collateral_amount, load_debt_amount}, - state::{COLLATERAL_AMOUNT, COLLATERAL_DENOMS, DEBT_AMOUNT}, + state::{COLLATERAL_AMOUNT, COLLATERAL_DENOMS, DEBT_AMOUNT, MARKETS}, }; +pub fn init_asset( + deps: DepsMut, + env: Env, + denom: String, + params: InitOrUpdateAssetParams, +) -> StdResult { + // since this is just a mock, we don't do the same checks that we do in the + // real red bank contract, such as sender == owner, validate denom, market + // not already exists... + let market = Market { + denom: denom.clone(), + borrow_index: Decimal::one(), + liquidity_index: Decimal::one(), + borrow_rate: Decimal::zero(), + liquidity_rate: Decimal::zero(), + reserve_factor: params.reserve_factor.unwrap(), + indexes_last_updated: env.block.time.seconds(), + collateral_total_scaled: Uint128::zero(), + debt_total_scaled: Uint128::zero(), + interest_rate_model: params.interest_rate_model.unwrap(), + }; + + MARKETS.save(deps.storage, &denom, &market)?; + + Ok(Response::new()) +} + pub fn borrow( deps: DepsMut, info: MessageInfo, diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index 2bedf41d9..ad87056da 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -1,7 +1,14 @@ use cosmwasm_std::{Deps, StdResult, Uint128}; -use mars_red_bank_types::red_bank::{UserCollateralResponse, UserDebtResponse}; +use mars_red_bank_types::red_bank::{Market, UserCollateralResponse, UserDebtResponse}; -use crate::helpers::{load_collateral_amount, load_collateral_denoms, load_debt_amount}; +use crate::{ + helpers::{load_collateral_amount, load_collateral_denoms, load_debt_amount}, + state::MARKETS, +}; + +pub fn query_market(deps: Deps, denom: String) -> StdResult { + MARKETS.load(deps.storage, &denom) +} pub fn query_debt(deps: Deps, user: String, denom: String) -> StdResult { let user_addr = deps.api.addr_validate(&user)?; diff --git a/contracts/mock-red-bank/src/state.rs b/contracts/mock-red-bank/src/state.rs index 29250f1df..4277cb829 100644 --- a/contracts/mock-red-bank/src/state.rs +++ b/contracts/mock-red-bank/src/state.rs @@ -1,6 +1,9 @@ use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::Map; +use mars_red_bank_types::red_bank::Market; +// Map +pub const MARKETS: Map<&str, Market> = Map::new("markets"); // Map<(DebtHolder, CoinDenom), AmountOfDebt> pub const DEBT_AMOUNT: Map<(Addr, String), Uint128> = Map::new("debt_amount"); // Map<(Addr, CmAccountId, CoinDenom), AmountOfCollateral> diff --git a/packages/health-computer/tests/helpers/mock_coin_info.rs b/packages/health-computer/tests/helpers/mock_coin_info.rs index 8e99f8c80..d02e3a6c7 100644 --- a/packages/health-computer/tests/helpers/mock_coin_info.rs +++ b/packages/health-computer/tests/helpers/mock_coin_info.rs @@ -36,9 +36,9 @@ pub fn umars_info() -> CoinInfo { red_bank: RedBankSettings { deposit_enabled: true, borrow_enabled: true, - deposit_cap: Default::default(), }, protocol_liquidation_fee: Decimal::percent(2u64), + deposit_cap: Default::default(), }, } } @@ -65,9 +65,9 @@ pub fn udai_info() -> CoinInfo { red_bank: RedBankSettings { deposit_enabled: true, borrow_enabled: true, - deposit_cap: Default::default(), }, protocol_liquidation_fee: Decimal::percent(2u64), + deposit_cap: Default::default(), }, } } @@ -94,9 +94,9 @@ pub fn uluna_info() -> CoinInfo { red_bank: RedBankSettings { deposit_enabled: true, borrow_enabled: true, - deposit_cap: Default::default(), }, protocol_liquidation_fee: Decimal::percent(2u64), + deposit_cap: Default::default(), }, } } @@ -129,9 +129,9 @@ pub fn ustars_info() -> CoinInfo { red_bank: RedBankSettings { deposit_enabled: true, borrow_enabled: true, - deposit_cap: Default::default(), }, protocol_liquidation_fee: Decimal::percent(2u64), + deposit_cap: Default::default(), }, } } @@ -158,9 +158,9 @@ pub fn ujuno_info() -> CoinInfo { red_bank: RedBankSettings { deposit_enabled: true, borrow_enabled: true, - deposit_cap: Default::default(), }, protocol_liquidation_fee: Decimal::percent(2u64), + deposit_cap: Default::default(), }, } } @@ -193,9 +193,9 @@ pub fn uatom_info() -> CoinInfo { red_bank: RedBankSettings { deposit_enabled: true, borrow_enabled: true, - deposit_cap: Default::default(), }, protocol_liquidation_fee: Decimal::percent(2u64), + deposit_cap: Default::default(), }, } } diff --git a/packages/health-computer/tests/helpers/prop_test_strategies.rs b/packages/health-computer/tests/helpers/prop_test_strategies.rs index 48f3d8375..2e382d0b7 100644 --- a/packages/health-computer/tests/helpers/prop_test_strategies.rs +++ b/packages/health-computer/tests/helpers/prop_test_strategies.rs @@ -62,7 +62,6 @@ fn random_coin_info() -> impl Strategy { red_bank: RedBankSettings { deposit_enabled: true, borrow_enabled: true, - deposit_cap: Default::default(), }, max_loan_to_value, liquidation_threshold, @@ -73,6 +72,7 @@ fn random_coin_info() -> impl Strategy { max_lb: Default::default(), }, protocol_liquidation_fee: Default::default(), + deposit_cap: Default::default(), } }, ) diff --git a/packages/health-computer/tests/test_max_borrow_vault.rs b/packages/health-computer/tests/test_max_borrow_vault.rs index 611b93bfb..d9b13c570 100644 --- a/packages/health-computer/tests/test_max_borrow_vault.rs +++ b/packages/health-computer/tests/test_max_borrow_vault.rs @@ -44,7 +44,7 @@ fn max_borrow_vault_offset_good() { .max_borrow_amount_estimate( &udai.denom, &BorrowTarget::Vault { - address: osmo_atom_1_config.addr.clone(), + address: osmo_atom_1_config.addr, }, ) .unwrap(); @@ -87,7 +87,7 @@ fn max_borrow_vault_offset_margin_of_error() { .max_borrow_amount_estimate( &umars.denom, &BorrowTarget::Vault { - address: osmo_atom_1_config.addr.clone(), + address: osmo_atom_1_config.addr, }, ) .unwrap(); diff --git a/packages/rover/src/adapters/params.rs b/packages/rover/src/adapters/params.rs index 1aa1b5186..709af98ff 100644 --- a/packages/rover/src/adapters/params.rs +++ b/packages/rover/src/adapters/params.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, Decimal, QuerierWrapper, StdResult}; use mars_params::{ - msg::QueryMsg, + msg::{QueryMsg, TotalDepositResponse}, types::{asset::AssetParams, vault::VaultConfig}, }; @@ -47,6 +47,19 @@ impl Params { ) } + pub fn query_total_deposit( + &self, + querier: &QuerierWrapper, + denom: &str, + ) -> StdResult { + querier.query_wasm_smart( + self.address().to_string(), + &QueryMsg::TotalDeposit { + denom: denom.to_string(), + }, + ) + } + pub fn query_vault_config( &self, querier: &QuerierWrapper, diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 56d3f7117..060d3ecac 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -20,6 +20,12 @@ pub enum ContractError { max_ltv_health_factor: String, }, + #[error("Asset deposit would result in exceeding limit. With deposit: {new_value:?}, maximum: {maximum}")] + AboveAssetDepositCap { + new_value: Coin, + maximum: Uint128, + }, + #[error("Vault deposit would result in exceeding limit. With deposit: {new_value:?}, Maximum: {maximum:?}")] AboveVaultDepositCap { new_value: String, diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 936ceec45..de0199da0 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; use mars_account_nft::nft_config::NftConfigUpdates; @@ -240,6 +242,11 @@ pub enum CallbackMsg { account_id: String, prev_health_state: HealthState, }, + /// Assert that the total deposit amounts of the given denoms across Red + /// Bank and Rover do not exceed their respective deposit caps. + AssertDepositCaps { + denoms: BTreeSet, + }, /// Adds coin to a vault strategy EnterVault { account_id: String, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 9dc788bfc..c0d1108f6 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -902,6 +902,32 @@ }, "additionalProperties": false }, + { + "description": "Assert that the total deposit amounts of the given denoms across Red Bank and Rover do not exceed their respective deposit caps.", + "type": "object", + "required": [ + "assert_deposit_caps" + ], + "properties": { + "assert_deposit_caps": { + "type": "object", + "required": [ + "denoms" + ], + "properties": { + "denoms": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Adds coin to a vault strategy", "type": "object", diff --git a/schemas/mars-mock-red-bank/mars-mock-red-bank.json b/schemas/mars-mock-red-bank/mars-mock-red-bank.json index a3f38b4d6..b48afb816 100644 --- a/schemas/mars-mock-red-bank/mars-mock-red-bank.json +++ b/schemas/mars-mock-red-bank/mars-mock-red-bank.json @@ -806,6 +806,28 @@ }, "additionalProperties": false }, + { + "description": "Get user position for liquidation", + "type": "object", + "required": [ + "user_position_liquidation_pricing" + ], + "properties": { + "user_position_liquidation_pricing": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Get liquidity scaled amount for a given underlying asset amount. (i.e: how much scaled collateral is added if the given amount is deposited)", "type": "object", @@ -1614,6 +1636,91 @@ ] } } + }, + "user_position_liquidation_pricing": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UserPositionResponse", + "type": "object", + "required": [ + "health_status", + "total_collateralized_debt", + "total_enabled_collateral", + "weighted_liquidation_threshold_collateral", + "weighted_max_ltv_collateral" + ], + "properties": { + "health_status": { + "$ref": "#/definitions/UserHealthStatus" + }, + "total_collateralized_debt": { + "description": "Total value of all collateralized debts. If the user has an uncollateralized loan limit in an asset, the debt in this asset will not be included in this value.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "total_enabled_collateral": { + "description": "Total value of all enabled collateral assets. If an asset is disabled as collateral, it will not be included in this value.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "weighted_liquidation_threshold_collateral": { + "$ref": "#/definitions/Uint128" + }, + "weighted_max_ltv_collateral": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "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" + }, + "UserHealthStatus": { + "oneOf": [ + { + "type": "string", + "enum": [ + "not_borrowing" + ] + }, + { + "type": "object", + "required": [ + "borrowing" + ], + "properties": { + "borrowing": { + "type": "object", + "required": [ + "liq_threshold_hf", + "max_ltv_hf" + ], + "properties": { + "liq_threshold_hf": { + "$ref": "#/definitions/Decimal" + }, + "max_ltv_hf": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + } } } } diff --git a/schemas/mars-params/mars-params.json b/schemas/mars-params/mars-params.json index 873370b7f..e9daf2f0b 100644 --- a/schemas/mars-params/mars-params.json +++ b/schemas/mars-params/mars-params.json @@ -7,10 +7,15 @@ "title": "InstantiateMsg", "type": "object", "required": [ + "address_provider", "owner", "target_health_factor" ], "properties": { + "address_provider": { + "description": "Address of the address provider contract", + "type": "string" + }, "owner": { "description": "Contract's owner", "type": "string" @@ -103,6 +108,7 @@ "required": [ "credit_manager", "denom", + "deposit_cap", "liquidation_bonus", "liquidation_threshold", "max_loan_to_value", @@ -116,6 +122,9 @@ "denom": { "type": "string" }, + "deposit_cap": { + "$ref": "#/definitions/Uint128" + }, "liquidation_bonus": { "$ref": "#/definitions/LiquidationBonus" }, @@ -479,16 +488,12 @@ "type": "object", "required": [ "borrow_enabled", - "deposit_cap", "deposit_enabled" ], "properties": { "borrow_enabled": { "type": "boolean" }, - "deposit_cap": { - "$ref": "#/definitions/Uint128" - }, "deposit_enabled": { "type": "boolean" } @@ -694,6 +699,28 @@ } }, "additionalProperties": false + }, + { + "description": "Compute the total amount deposited of the given asset across Red Bank and Credit Manager.", + "type": "object", + "required": [ + "total_deposit" + ], + "properties": { + "total_deposit": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -717,6 +744,7 @@ "required": [ "credit_manager", "denom", + "deposit_cap", "liquidation_bonus", "liquidation_threshold", "max_loan_to_value", @@ -730,6 +758,9 @@ "denom": { "type": "string" }, + "deposit_cap": { + "$ref": "#/definitions/Uint128" + }, "liquidation_bonus": { "$ref": "#/definitions/LiquidationBonus" }, @@ -893,16 +924,12 @@ "type": "object", "required": [ "borrow_enabled", - "deposit_cap", "deposit_enabled" ], "properties": { "borrow_enabled": { "type": "boolean" }, - "deposit_cap": { - "$ref": "#/definitions/Uint128" - }, "deposit_enabled": { "type": "boolean" } @@ -1067,6 +1094,7 @@ "required": [ "credit_manager", "denom", + "deposit_cap", "liquidation_bonus", "liquidation_threshold", "max_loan_to_value", @@ -1080,6 +1108,9 @@ "denom": { "type": "string" }, + "deposit_cap": { + "$ref": "#/definitions/Uint128" + }, "liquidation_bonus": { "$ref": "#/definitions/LiquidationBonus" }, @@ -1247,16 +1278,12 @@ "type": "object", "required": [ "borrow_enabled", - "deposit_cap", "deposit_enabled" ], "properties": { "borrow_enabled": { "type": "boolean" }, - "deposit_cap": { - "$ref": "#/definitions/Uint128" - }, "deposit_enabled": { "type": "boolean" } @@ -1312,6 +1339,34 @@ "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" }, + "total_deposit": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TotalDepositResponse", + "type": "object", + "required": [ + "amount", + "cap", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "cap": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "vault_config": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "VaultConfigBase_for_Addr", diff --git a/schemas/mars-rover-health-computer/mars-rover-health-computer.json b/schemas/mars-rover-health-computer/mars-rover-health-computer.json index 756314a5d..22bd8c731 100644 --- a/schemas/mars-rover-health-computer/mars-rover-health-computer.json +++ b/schemas/mars-rover-health-computer/mars-rover-health-computer.json @@ -41,6 +41,7 @@ "required": [ "credit_manager", "denom", + "deposit_cap", "liquidation_bonus", "liquidation_threshold", "max_loan_to_value", @@ -54,6 +55,9 @@ "denom": { "type": "string" }, + "deposit_cap": { + "$ref": "#/definitions/Uint128" + }, "liquidation_bonus": { "$ref": "#/definitions/LiquidationBonus" }, @@ -361,16 +365,12 @@ "type": "object", "required": [ "borrow_enabled", - "deposit_cap", "deposit_enabled" ], "properties": { "borrow_enabled": { "type": "boolean" }, - "deposit_cap": { - "$ref": "#/definitions/Uint128" - }, "deposit_enabled": { "type": "boolean" } diff --git a/scripts/codegen/index.ts b/scripts/codegen/index.ts index 33e2b26cd..61cc395b4 100644 --- a/scripts/codegen/index.ts +++ b/scripts/codegen/index.ts @@ -61,7 +61,7 @@ const fetchSchemafromGithub = async ({ void (async function () { await fetchSchemafromGithub({ githubRepo: 'https://github.com/mars-protocol/red-bank', - commit: '32ab53b3130f3fb947c908768fe22b639536b185', + commit: '18e9c565df15c2a5ea5fa92da561b0c7cb570294', pathToSchema: './red-bank/schemas/mars-params', }) await generateTypes() diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 3860aec54..694faea99 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -222,6 +222,11 @@ export type CallbackMsg = prev_health_state: HealthState } } + | { + assert_deposit_caps: { + denoms: string[] + } + } | { enter_vault: { account_id: string diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index 516d2eff5..7f35de451 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -87,6 +87,7 @@ export interface MarsMockRedBankReadOnlyInterface { user: string }) => Promise userPosition: ({ user }: { user: string }) => Promise + userPositionLiquidationPricing: ({ user }: { user: string }) => Promise scaledLiquidityAmount: ({ amount, denom }: { amount: Uint128; denom: string }) => Promise scaledDebtAmount: ({ amount, denom }: { amount: Uint128; denom: string }) => Promise underlyingLiquidityAmount: ({ @@ -121,6 +122,7 @@ export class MarsMockRedBankQueryClient implements MarsMockRedBankReadOnlyInterf this.userCollateral = this.userCollateral.bind(this) this.userCollaterals = this.userCollaterals.bind(this) this.userPosition = this.userPosition.bind(this) + this.userPositionLiquidationPricing = this.userPositionLiquidationPricing.bind(this) this.scaledLiquidityAmount = this.scaledLiquidityAmount.bind(this) this.scaledDebtAmount = this.scaledDebtAmount.bind(this) this.underlyingLiquidityAmount = this.underlyingLiquidityAmount.bind(this) @@ -259,6 +261,17 @@ export class MarsMockRedBankQueryClient implements MarsMockRedBankReadOnlyInterf }, }) } + userPositionLiquidationPricing = async ({ + user, + }: { + user: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + user_position_liquidation_pricing: { + user, + }, + }) + } scaledLiquidityAmount = async ({ amount, denom, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index 76512df99..dd73bbe56 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -87,6 +87,17 @@ export const marsMockRedBankQueryKeys = { [ { ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'user_position', args }, ] as const, + userPositionLiquidationPricing: ( + contractAddress: string | undefined, + args?: Record, + ) => + [ + { + ...marsMockRedBankQueryKeys.address(contractAddress)[0], + method: 'user_position_liquidation_pricing', + args, + }, + ] as const, scaledLiquidityAmount: (contractAddress: string | undefined, args?: Record) => [ { @@ -228,6 +239,26 @@ export function useMarsMockRedBankScaledLiquidityAmountQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsMockRedBankUserPositionLiquidationPricingQuery + extends MarsMockRedBankReactQuery { + args: { + user: string + } +} +export function useMarsMockRedBankUserPositionLiquidationPricingQuery< + TData = UserPositionResponse, +>({ client, args, options }: MarsMockRedBankUserPositionLiquidationPricingQuery) { + return useQuery( + marsMockRedBankQueryKeys.userPositionLiquidationPricing(client?.contractAddress, args), + () => + client + ? client.userPositionLiquidationPricing({ + user: args.user, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsMockRedBankUserPositionQuery extends MarsMockRedBankReactQuery { args: { diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index 8d7a95b93..4ba8e213a 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -165,6 +165,11 @@ export type QueryMsg = user: string } } + | { + user_position_liquidation_pricing: { + user: string + } + } | { scaled_liquidity_amount: { amount: Uint128 diff --git a/scripts/types/generated/mars-params/MarsParams.client.ts b/scripts/types/generated/mars-params/MarsParams.client.ts index 294fbdb36..68968aa9a 100644 --- a/scripts/types/generated/mars-params/MarsParams.client.ts +++ b/scripts/types/generated/mars-params/MarsParams.client.ts @@ -36,6 +36,7 @@ import { ArrayOfVaultConfigBaseForAddr, VaultConfigBaseForAddr, OwnerResponse, + TotalDepositResponse, } from './MarsParams.types' export interface MarsParamsReadOnlyInterface { contractAddress: string @@ -57,6 +58,7 @@ export interface MarsParamsReadOnlyInterface { startAfter?: string }) => Promise targetHealthFactor: () => Promise + totalDeposit: ({ denom }: { denom: string }) => Promise } export class MarsParamsQueryClient implements MarsParamsReadOnlyInterface { client: CosmWasmClient @@ -71,6 +73,7 @@ export class MarsParamsQueryClient implements MarsParamsReadOnlyInterface { this.vaultConfig = this.vaultConfig.bind(this) this.allVaultConfigs = this.allVaultConfigs.bind(this) this.targetHealthFactor = this.targetHealthFactor.bind(this) + this.totalDeposit = this.totalDeposit.bind(this) } owner = async (): Promise => { @@ -125,6 +128,13 @@ export class MarsParamsQueryClient implements MarsParamsReadOnlyInterface { target_health_factor: {}, }) } + totalDeposit = async ({ denom }: { denom: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + total_deposit: { + denom, + }, + }) + } } export interface MarsParamsInterface extends MarsParamsReadOnlyInterface { contractAddress: string diff --git a/scripts/types/generated/mars-params/MarsParams.message-composer.ts b/scripts/types/generated/mars-params/MarsParams.message-composer.ts index cc58c0561..b135caaac 100644 --- a/scripts/types/generated/mars-params/MarsParams.message-composer.ts +++ b/scripts/types/generated/mars-params/MarsParams.message-composer.ts @@ -37,6 +37,7 @@ import { ArrayOfVaultConfigBaseForAddr, VaultConfigBaseForAddr, OwnerResponse, + TotalDepositResponse, } from './MarsParams.types' export interface MarsParamsMessage { contractAddress: string diff --git a/scripts/types/generated/mars-params/MarsParams.react-query.ts b/scripts/types/generated/mars-params/MarsParams.react-query.ts index 520fa75dc..70179ee7d 100644 --- a/scripts/types/generated/mars-params/MarsParams.react-query.ts +++ b/scripts/types/generated/mars-params/MarsParams.react-query.ts @@ -37,6 +37,7 @@ import { ArrayOfVaultConfigBaseForAddr, VaultConfigBaseForAddr, OwnerResponse, + TotalDepositResponse, } from './MarsParams.types' import { MarsParamsQueryClient, MarsParamsClient } from './MarsParams.client' export const marsParamsQueryKeys = { @@ -65,6 +66,10 @@ export const marsParamsQueryKeys = { [ { ...marsParamsQueryKeys.address(contractAddress)[0], method: 'target_health_factor', args }, ] as const, + totalDeposit: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsParamsQueryKeys.address(contractAddress)[0], method: 'total_deposit', args }, + ] as const, } export interface MarsParamsReactQuery { client: MarsParamsQueryClient | undefined @@ -75,6 +80,28 @@ export interface MarsParamsReactQuery { initialData?: undefined } } +export interface MarsParamsTotalDepositQuery + extends MarsParamsReactQuery { + args: { + denom: string + } +} +export function useMarsParamsTotalDepositQuery({ + client, + args, + options, +}: MarsParamsTotalDepositQuery) { + return useQuery( + marsParamsQueryKeys.totalDeposit(client?.contractAddress, args), + () => + client + ? client.totalDeposit({ + denom: args.denom, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsParamsTargetHealthFactorQuery extends MarsParamsReactQuery {} export function useMarsParamsTargetHealthFactorQuery({ diff --git a/scripts/types/generated/mars-params/MarsParams.types.ts b/scripts/types/generated/mars-params/MarsParams.types.ts index 06be3ad82..04005ce37 100644 --- a/scripts/types/generated/mars-params/MarsParams.types.ts +++ b/scripts/types/generated/mars-params/MarsParams.types.ts @@ -7,6 +7,7 @@ export type Decimal = string export interface InstantiateMsg { + address_provider: string owner: string target_health_factor: Decimal } @@ -86,6 +87,7 @@ export type RedBankEmergencyUpdate = { export interface AssetParamsBaseForString { credit_manager: CmSettingsForString denom: string + deposit_cap: Uint128 liquidation_bonus: LiquidationBonus liquidation_threshold: Decimal max_loan_to_value: Decimal @@ -109,7 +111,6 @@ export interface LiquidationBonus { } export interface RedBankSettings { borrow_enabled: boolean - deposit_cap: Uint128 deposit_enabled: boolean } export interface VaultConfigBaseForString { @@ -154,6 +155,11 @@ export type QueryMsg = | { target_health_factor: {} } + | { + total_deposit: { + denom: string + } + } export type HlsAssetTypeForAddr = | { coin: { @@ -170,6 +176,7 @@ export type ArrayOfAssetParamsBaseForAddr = AssetParamsBaseForAddr[] export interface AssetParamsBaseForAddr { credit_manager: CmSettingsForAddr denom: string + deposit_cap: Uint128 liquidation_bonus: LiquidationBonus liquidation_threshold: Decimal max_loan_to_value: Decimal @@ -201,3 +208,8 @@ export interface OwnerResponse { owner?: string | null proposed?: string | null } +export interface TotalDepositResponse { + amount: Uint128 + cap: Uint128 + denom: string +} diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts index de9ab3311..20dadd57c 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts @@ -47,6 +47,7 @@ export interface DenomsData { export interface AssetParamsBaseForAddr { credit_manager: CmSettingsForAddr denom: string + deposit_cap: Uint128 liquidation_bonus: LiquidationBonus liquidation_threshold: Decimal max_loan_to_value: Decimal @@ -70,7 +71,6 @@ export interface LiquidationBonus { } export interface RedBankSettings { borrow_enabled: boolean - deposit_cap: Uint128 deposit_enabled: boolean } export interface Positions { From 595266fd47abc6d70487ad13fb1592e7538f73a4 Mon Sep 17 00:00:00 2001 From: piobab Date: Sun, 6 Aug 2023 00:50:32 +0200 Subject: [PATCH 192/218] Build consistency with red-bank. (#173) * Build consistency with red-bank. * Remove artifacts job. --- .github/workflows/artifacts.yml | 37 ------------------------ .github/workflows/coverage.yml | 21 ++++---------- .github/workflows/main.yml | 19 ++++++------ .github/workflows/scripts.yml | 16 ++++------- Makefile.toml | 51 +++++++++++++++++++++++++-------- 5 files changed, 57 insertions(+), 87 deletions(-) delete mode 100644 .github/workflows/artifacts.yml diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml deleted file mode 100644 index a732d4d45..000000000 --- a/.github/workflows/artifacts.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Artifacts - -on: - push: - branches: - - master - - main - pull_request: - -env: - RUST_BACKTRACE: 1 - CARGO_TERM_COLOR: always - -jobs: - artifacts: - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v3 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-unknown - profile: minimal - override: true - - # selecting a toolchain should happen before the plugin, as the cache uses the current rustc version as its cache key - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - - - name: Install cargo make - uses: davidB/rust-cargo-make@v1 - - - name: Compile contracts to wasm - run: cargo make rust-optimizer diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 2609790d6..95d2376c2 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -10,8 +10,6 @@ on: env: RUST_BACKTRACE: 1 CARGO_TERM_COLOR: always - # Directory with wasm files used by `tests` - ARTIFACTS_DIR_PATH: 'target/wasm32-unknown-unknown/release' jobs: coverage: @@ -20,24 +18,15 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-unknown - profile: minimal - override: true - components: llvm-tools-preview - - name: Install cargo make uses: davidB/rust-cargo-make@v1 - # Artifacts used by tests. - # Change owner of the current directory (rust-optimizer set root for artifacts / target). + - name: Install stable Rust + run: cargo make install-stable + + # Artifacts used by tests - name: Compile workspace - run: | - cargo make rust-optimizer - sudo chown -R $USER . + run: cargo make build - name: Run test coverage run: cargo make coverage-lcov diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7def88a60..c9ab64b99 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,22 +18,19 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - target: wasm32-unknown-unknown - components: rustfmt, clippy - profile: minimal - override: true + - name: Install cargo make + uses: davidB/rust-cargo-make@v1 + + - name: Install stable Rust + run: cargo make install-stable + + - name: Install nightly Rust + run: cargo make install-nightly # selecting a toolchain should happen before the plugin, as the cache uses the current rustc version as its cache key - name: Cache dependencies uses: Swatinem/rust-cache@v2 - - name: Install cargo make - uses: davidB/rust-cargo-make@v1 - - name: Format run: cargo make fmt diff --git a/.github/workflows/scripts.yml b/.github/workflows/scripts.yml index 73f8f41ec..b39d78acb 100644 --- a/.github/workflows/scripts.yml +++ b/.github/workflows/scripts.yml @@ -21,22 +21,16 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-unknown - components: rustfmt, clippy - profile: minimal - override: true + - name: Install cargo make + uses: davidB/rust-cargo-make@v1 + + - name: Install stable Rust + run: cd ../ && cargo make install-stable && cd scripts # selecting a toolchain should happen before the plugin, as the cache uses the current rustc version as its cache key - name: Cache dependencies uses: Swatinem/rust-cache@v2 - - name: Install cargo make - uses: davidB/rust-cargo-make@v1 - - uses: actions/setup-node@v3 with: node-version: 18 diff --git a/Makefile.toml b/Makefile.toml index 354f8045a..a49c7fc89 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -1,6 +1,6 @@ extend = [ - { path = "schema.Makefile.toml" }, - { path = "coverage_grcov.Makefile.toml" } + { path = "schema.Makefile.toml" }, + { path = "coverage_grcov.Makefile.toml" } ] [config] @@ -8,18 +8,40 @@ default_to_workspace = false [env] # Directory with wasm files used by integration tests (another directory can be used instead, for example 'artifacts' from rust-optimizer) -ARTIFACTS_DIR_PATH = "artifacts" +ARTIFACTS_DIR_PATH = "target/wasm32-unknown-unknown/release" +# If you bump this version, verify RUST_VERSION correctness +RUST_OPTIMIZER_VERSION = "0.13.0" +# Use rust version from rust-optimizer Dockerfile (see https://github.com/CosmWasm/rust-optimizer/blob/main/Dockerfile#L1) +# to be sure that we compile / test against the same version +RUST_VERSION = "1.69.0" + +[tasks.install-stable] +script = ''' +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain ${RUST_VERSION} +rustup target add wasm32-unknown-unknown --toolchain ${RUST_VERSION} +rustup component add rustfmt --toolchain ${RUST_VERSION} +rustup component add clippy --toolchain ${RUST_VERSION} +rustup component add llvm-tools-preview --toolchain ${RUST_VERSION} +''' + +[tasks.install-nightly] +script = ''' +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain nightly +rustup target add wasm32-unknown-unknown --toolchain nightly +rustup component add rustfmt --toolchain nightly +''' [tasks.build] +toolchain = "${RUST_VERSION}" command = "cargo" args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked"] [tasks.rust-optimizer] script = """ if [[ $(arch) == "arm64" ]]; then - image="cosmwasm/workspace-optimizer-arm64:0.13.0" + image="cosmwasm/workspace-optimizer-arm64:${RUST_OPTIMIZER_VERSION}" else - image="cosmwasm/workspace-optimizer:0.13.0" + image="cosmwasm/workspace-optimizer:${RUST_OPTIMIZER_VERSION}" fi docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ @@ -28,6 +50,7 @@ docker run --rm -v "$(pwd)":/code \ """ [tasks.test] +toolchain = "${RUST_VERSION}" command = "cargo" args = ["test", "--locked"] @@ -37,10 +60,12 @@ command = "cargo" args = ["fmt", "--all", "--check"] [tasks.clippy] +toolchain = "${RUST_VERSION}" command = "cargo" args = ["clippy", "--tests", "--", "-D", "warnings"] [tasks.audit] +toolchain = "${RUST_VERSION}" command = "cargo" args = ["audit"] @@ -52,11 +77,13 @@ alias = "coverage-grcov-lcov" [tasks.all-actions] dependencies = [ - "fmt", - "clippy", - "build", - "test", - "generate-all-schemas", - "audit", - "rust-optimizer", + "install-stable", + "install-nightly", + "fmt", + "clippy", + "build", + "test", + "audit", + "generate-all-schemas", + "rust-optimizer", ] From acd8e76f8bbfacf2af3bd90f5f8751b4b64fc74b Mon Sep 17 00:00:00 2001 From: piobab Date: Mon, 7 Aug 2023 00:51:13 +0200 Subject: [PATCH 193/218] Add accounts query. (#172) * Add accounts query. * Update tests. * Update schema. * Fix test. --- contracts/credit-manager/src/contract.rs | 11 ++- contracts/credit-manager/src/query.rs | 28 +++++++- .../credit-manager/tests/helpers/mock_env.rs | 23 ++++++- .../tests/test_enumerate_accounts.rs | 64 +++++++++++++++++ packages/rover/src/adapters/account_nft.rs | 18 +++++ packages/rover/src/msg/query.rs | 12 ++++ .../mars-credit-manager.json | 68 +++++++++++++++++++ .../mars-mock-credit-manager.json | 68 +++++++++++++++++++ .../MarsCreditManager.client.ts | 29 ++++++++ .../MarsCreditManager.message-composer.ts | 2 + .../MarsCreditManager.react-query.ts | 32 +++++++++ .../MarsCreditManager.types.ts | 12 ++++ .../MarsMockCreditManager.client.ts | 29 ++++++++ .../MarsMockCreditManager.message-composer.ts | 2 + .../MarsMockCreditManager.react-query.ts | 32 +++++++++ .../MarsMockCreditManager.types.ts | 12 ++++ 16 files changed, 435 insertions(+), 7 deletions(-) create mode 100644 contracts/credit-manager/tests/test_enumerate_accounts.rs diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index b6320503d..f34dd43a6 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -14,9 +14,9 @@ use crate::{ migrations, migrations::helpers::assert_migration_env, query::{ - query_all_coin_balances, query_all_debt_shares, query_all_total_debt_shares, - query_all_vault_positions, query_config, query_positions, query_total_debt_shares, - query_vault_position_value, query_vault_utilization, + query_accounts, query_all_coin_balances, query_all_debt_shares, + query_all_total_debt_shares, query_all_vault_positions, query_config, query_positions, + query_total_debt_shares, query_vault_position_value, query_vault_utilization, }, repay::repay_from_wallet, update_config::{update_config, update_nft_config, update_owner}, @@ -82,6 +82,11 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { QueryMsg::AccountKind { account_id, } => to_binary(&get_account_kind(deps.storage, &account_id)?), + QueryMsg::Accounts { + owner, + start_after, + limit, + } => to_binary(&query_accounts(deps, owner, start_after, limit)?), QueryMsg::Config {} => to_binary(&query_config(deps)?), QueryMsg::VaultUtilization { vault, diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index ceab2752d..bffefb7de 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -6,14 +6,15 @@ use mars_rover::{ adapters::vault::{VaultBase, VaultPosition, VaultPositionValue, VaultUnchecked}, error::ContractResult, msg::query::{ - CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, Positions, + Account, CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, Positions, SharesResponseItem, VaultPositionResponseItem, VaultUtilizationResponse, }, }; +use mars_rover_health_types::AccountKind; use crate::{ state::{ - ACCOUNT_NFT, COIN_BALANCES, DEBT_SHARES, HEALTH_CONTRACT, INCENTIVES, + ACCOUNT_KINDS, ACCOUNT_NFT, COIN_BALANCES, DEBT_SHARES, HEALTH_CONTRACT, INCENTIVES, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, RED_BANK, REWARDS_COLLECTOR, SWAPPER, TOTAL_DEBT_SHARES, VAULT_POSITIONS, ZAPPER, }, @@ -21,6 +22,29 @@ use crate::{ vault::vault_utilization_in_deposit_cap_denom, }; +pub fn query_accounts( + deps: Deps, + owner: String, + start_after: Option, + limit: Option, +) -> ContractResult> { + let account_nft = ACCOUNT_NFT.load(deps.storage)?; + + let tokens = account_nft.query_tokens(&deps.querier, owner, start_after, limit)?; + tokens + .tokens + .iter() + .map(|acc_id| { + let acc_kind = + ACCOUNT_KINDS.may_load(deps.storage, acc_id)?.unwrap_or(AccountKind::Default); + Ok(Account { + id: acc_id.clone(), + kind: acc_kind, + }) + }) + .collect() +} + pub fn query_config(deps: Deps) -> ContractResult { Ok(ConfigResponse { ownership: OWNER.query(deps.storage)?, diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 5acfec15c..96cc485ea 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -63,8 +63,8 @@ use mars_rover::{ execute::{Action, CallbackMsg}, instantiate::ConfigUpdates, query::{ - CoinBalanceResponseItem, ConfigResponse, DebtShares, Positions, SharesResponseItem, - VaultPositionResponseItem, VaultUtilizationResponse, + Account, CoinBalanceResponseItem, ConfigResponse, DebtShares, Positions, + SharesResponseItem, VaultPositionResponseItem, VaultUtilizationResponse, }, ExecuteMsg, InstantiateMsg, QueryMsg, QueryMsg::{EstimateProvideLiquidity, VaultPositionValue}, @@ -402,6 +402,25 @@ impl MockEnv { self.app.wrap().query_wasm_smart(self.rover.clone(), &QueryMsg::Config {}).unwrap() } + pub fn query_accounts( + &self, + owner: &str, + start_after: Option, + limit: Option, + ) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::Accounts { + owner: owner.to_string(), + start_after, + limit, + }, + ) + .unwrap() + } + pub fn query_account_kind(&self, account_id: &str) -> AccountKind { self.app .wrap() diff --git a/contracts/credit-manager/tests/test_enumerate_accounts.rs b/contracts/credit-manager/tests/test_enumerate_accounts.rs new file mode 100644 index 000000000..c1616ba37 --- /dev/null +++ b/contracts/credit-manager/tests/test_enumerate_accounts.rs @@ -0,0 +1,64 @@ +use cosmwasm_std::Addr; +use mars_rover::msg::query::Account; +use mars_rover_health_types::AccountKind; + +use crate::helpers::MockEnv; + +pub mod helpers; + +fn account_default(id: &str) -> Account { + Account { + id: id.to_string(), + kind: AccountKind::Default, + } +} + +fn account_hls(id: &str) -> Account { + Account { + id: id.to_string(), + kind: AccountKind::HighLeveredStrategy, + } +} + +#[test] +fn pagination_on_accounts_query_works() { + let user_a = Addr::unchecked("user_a"); + let user_b = Addr::unchecked("user_b"); + let user_c = Addr::unchecked("user_c"); + + let mut mock = MockEnv::new().build().unwrap(); + + let account_id_a_2 = mock.create_credit_account(&user_a).unwrap(); + assert_eq!(account_id_a_2, "2".to_string()); // assert starting number + mock.create_credit_account(&user_a).unwrap(); + mock.create_credit_account(&user_b).unwrap(); + mock.create_hls_account(&user_a); + mock.create_credit_account(&user_c).unwrap(); + mock.create_credit_account(&user_a).unwrap(); + mock.create_credit_account(&user_a).unwrap(); + mock.create_hls_account(&user_b); + mock.create_hls_account(&user_b); + mock.create_credit_account(&user_b).unwrap(); + mock.create_hls_account(&user_c); + + let user_a_accounts = mock.query_accounts(user_a.as_str(), None, Some(2)); + assert_eq!(user_a_accounts, vec![account_default("2"), account_default("3")]); + + let user_a_accounts = mock.query_accounts(user_a.as_str(), Some("3".to_string()), Some(2)); + assert_eq!(user_a_accounts, vec![account_hls("5"), account_default("7")]); + + let user_a_accounts = mock.query_accounts(user_a.as_str(), Some("7".to_string()), Some(2)); + assert_eq!(user_a_accounts, vec![account_default("8")]); + + let user_b_accounts = mock.query_accounts(user_b.as_str(), None, None); + assert_eq!( + user_b_accounts, + vec![account_hls("10"), account_default("11"), account_default("4"), account_hls("9")] + ); + + let user_c_accounts = mock.query_accounts(user_c.as_str(), None, None); + assert_eq!(user_c_accounts, vec![account_hls("12"), account_default("6")]); + + let user_c_accounts = mock.query_accounts(user_c.as_str(), Some("6".to_string()), None); + assert_eq!(user_c_accounts, vec![]); +} diff --git a/packages/rover/src/adapters/account_nft.rs b/packages/rover/src/adapters/account_nft.rs index 497b3d7a9..b3685822e 100644 --- a/packages/rover/src/adapters/account_nft.rs +++ b/packages/rover/src/adapters/account_nft.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, QuerierWrapper, StdResult}; +use cw721::TokensResponse; use mars_account_nft::msg::QueryMsg; #[cw_serde] @@ -34,4 +35,21 @@ impl AccountNft { pub fn query_next_id(&self, querier: &QuerierWrapper) -> StdResult { querier.query_wasm_smart(self.address().to_string(), &QueryMsg::NextId {}) } + + pub fn query_tokens( + &self, + querier: &QuerierWrapper, + owner: String, + start_after: Option, + limit: Option, + ) -> StdResult { + querier.query_wasm_smart( + self.address().to_string(), + &QueryMsg::Tokens { + owner, + start_after, + limit, + }, + ) + } } diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 5ead04e95..fd0d0b002 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -14,6 +14,12 @@ pub enum QueryMsg { AccountKind { account_id: String, }, + #[returns(Vec)] + Accounts { + owner: String, + start_after: Option, + limit: Option, + }, /// Rover contract-level config #[returns(ConfigResponse)] Config {}, @@ -162,3 +168,9 @@ pub struct ConfigResponse { pub health_contract: String, pub rewards_collector: Option, } + +#[cw_serde] +pub struct Account { + pub id: String, + pub kind: mars_rover_health_types::AccountKind, +} diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index c0d1108f6..eaa770aee 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -1808,6 +1808,41 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "accounts" + ], + "properties": { + "accounts": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Rover contract-level config", "type": "object", @@ -2258,6 +2293,39 @@ "high_levered_strategy" ] }, + "accounts": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Account", + "type": "array", + "items": { + "$ref": "#/definitions/Account" + }, + "definitions": { + "Account": { + "type": "object", + "required": [ + "id", + "kind" + ], + "properties": { + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/AccountKind" + } + }, + "additionalProperties": false + }, + "AccountKind": { + "type": "string", + "enum": [ + "default", + "high_levered_strategy" + ] + } + } + }, "all_coin_balances": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_CoinBalanceResponseItem", diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 370d85eb5..c1d0eb04c 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -264,6 +264,41 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "accounts" + ], + "properties": { + "accounts": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Rover contract-level config", "type": "object", @@ -714,6 +749,39 @@ "high_levered_strategy" ] }, + "accounts": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Account", + "type": "array", + "items": { + "$ref": "#/definitions/Account" + }, + "definitions": { + "Account": { + "type": "object", + "required": [ + "id", + "kind" + ], + "properties": { + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/AccountKind" + } + }, + "additionalProperties": false + }, + "AccountKind": { + "type": "string", + "enum": [ + "default", + "high_levered_strategy" + ] + } + } + }, "all_coin_balances": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_CoinBalanceResponseItem", diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 6262dcabd..ec409412a 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -45,6 +45,8 @@ import { VaultPosition, LockingVaultAmount, VaultUnlockingPosition, + ArrayOfAccount, + Account, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, @@ -65,6 +67,15 @@ import { export interface MarsCreditManagerReadOnlyInterface { contractAddress: string accountKind: ({ accountId }: { accountId: string }) => Promise + accounts: ({ + limit, + owner, + startAfter, + }: { + limit?: number + owner: string + startAfter?: string + }) => Promise config: () => Promise vaultUtilization: ({ vault }: { vault: VaultBaseForString }) => Promise positions: ({ accountId }: { accountId: string }) => Promise @@ -119,6 +130,7 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn this.client = client this.contractAddress = contractAddress this.accountKind = this.accountKind.bind(this) + this.accounts = this.accounts.bind(this) this.config = this.config.bind(this) this.vaultUtilization = this.vaultUtilization.bind(this) this.positions = this.positions.bind(this) @@ -139,6 +151,23 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn }, }) } + accounts = async ({ + limit, + owner, + startAfter, + }: { + limit?: number + owner: string + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + accounts: { + limit, + owner, + start_after: startAfter, + }, + }) + } config = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { config: {}, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index d60772dad..eeb49b480 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -46,6 +46,8 @@ import { VaultPosition, LockingVaultAmount, VaultUnlockingPosition, + ArrayOfAccount, + Account, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 066fc5f5e..bad5a5421 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -46,6 +46,8 @@ import { VaultPosition, LockingVaultAmount, VaultUnlockingPosition, + ArrayOfAccount, + Account, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, @@ -76,6 +78,10 @@ export const marsCreditManagerQueryKeys = { [ { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'account_kind', args }, ] as const, + accounts: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'accounts', args }, + ] as const, config: (contractAddress: string | undefined, args?: Record) => [ { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, @@ -399,6 +405,32 @@ export function useMarsCreditManagerConfigQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsCreditManagerAccountsQuery + extends MarsCreditManagerReactQuery { + args: { + limit?: number + owner: string + startAfter?: string + } +} +export function useMarsCreditManagerAccountsQuery({ + client, + args, + options, +}: MarsCreditManagerAccountsQuery) { + return useQuery( + marsCreditManagerQueryKeys.accounts(client?.contractAddress, args), + () => + client + ? client.accounts({ + limit: args.limit, + owner: args.owner, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsCreditManagerAccountKindQuery extends MarsCreditManagerReactQuery { args: { diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 694faea99..8bded4650 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -378,6 +378,13 @@ export type QueryMsg = account_id: string } } + | { + accounts: { + limit?: number | null + owner: string + start_after?: string | null + } + } | { config: {} } @@ -456,6 +463,11 @@ export interface VaultUnlockingPosition { coin: Coin id: number } +export type ArrayOfAccount = Account[] +export interface Account { + id: string + kind: AccountKind +} export type ArrayOfCoinBalanceResponseItem = CoinBalanceResponseItem[] export interface CoinBalanceResponseItem { account_id: string diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index 5ad60802c..f9edbc575 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -26,6 +26,8 @@ import { QueryMsg, VaultBaseForString, AccountKind, + ArrayOfAccount, + Account, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, @@ -44,6 +46,15 @@ import { export interface MarsMockCreditManagerReadOnlyInterface { contractAddress: string accountKind: ({ accountId }: { accountId: string }) => Promise + accounts: ({ + limit, + owner, + startAfter, + }: { + limit?: number + owner: string + startAfter?: string + }) => Promise config: () => Promise vaultUtilization: ({ vault }: { vault: VaultBaseForString }) => Promise positions: ({ accountId }: { accountId: string }) => Promise @@ -98,6 +109,7 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe this.client = client this.contractAddress = contractAddress this.accountKind = this.accountKind.bind(this) + this.accounts = this.accounts.bind(this) this.config = this.config.bind(this) this.vaultUtilization = this.vaultUtilization.bind(this) this.positions = this.positions.bind(this) @@ -118,6 +130,23 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe }, }) } + accounts = async ({ + limit, + owner, + startAfter, + }: { + limit?: number + owner: string + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + accounts: { + limit, + owner, + start_after: startAfter, + }, + }) + } config = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { config: {}, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index 8464316bd..c376f92ba 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -27,6 +27,8 @@ import { QueryMsg, VaultBaseForString, AccountKind, + ArrayOfAccount, + Account, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index 7869e8b0e..764e05de4 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -27,6 +27,8 @@ import { QueryMsg, VaultBaseForString, AccountKind, + ArrayOfAccount, + Account, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, @@ -62,6 +64,10 @@ export const marsMockCreditManagerQueryKeys = { args, }, ] as const, + accounts: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'accounts', args }, + ] as const, config: (contractAddress: string | undefined, args?: Record) => [ { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, @@ -383,6 +389,32 @@ export function useMarsMockCreditManagerConfigQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsMockCreditManagerAccountsQuery + extends MarsMockCreditManagerReactQuery { + args: { + limit?: number + owner: string + startAfter?: string + } +} +export function useMarsMockCreditManagerAccountsQuery({ + client, + args, + options, +}: MarsMockCreditManagerAccountsQuery) { + return useQuery( + marsMockCreditManagerQueryKeys.accounts(client?.contractAddress, args), + () => + client + ? client.accounts({ + limit: args.limit, + owner: args.owner, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsMockCreditManagerAccountKindQuery extends MarsMockCreditManagerReactQuery { args: { diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index ae9fd8eff..4c8d97dae 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -64,6 +64,13 @@ export type QueryMsg = account_id: string } } + | { + accounts: { + limit?: number | null + owner: string + start_after?: string | null + } + } | { config: {} } @@ -124,6 +131,11 @@ export interface VaultBaseForString { address: string } export type AccountKind = 'default' | 'high_levered_strategy' +export type ArrayOfAccount = Account[] +export interface Account { + id: string + kind: AccountKind +} export type ArrayOfCoinBalanceResponseItem = CoinBalanceResponseItem[] export interface CoinBalanceResponseItem { account_id: string From 6013cbca21a343bbea695aac6b178c7be83948b2 Mon Sep 17 00:00:00 2001 From: brimigs <85972460+brimigs@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:29:42 +0100 Subject: [PATCH 194/218] New rover deployment (#174) * update-config * new-deployment * update-tests --- .../osmo-test-5-testnet-deployer-owner.json | 10 +- scripts/deploy/base/deployer.ts | 142 +----------------- scripts/deploy/base/index.ts | 5 - scripts/deploy/base/rover.ts | 14 +- scripts/deploy/osmosis/mainnet.ts | 7 +- scripts/deploy/osmosis/testnet-config.ts | 110 +++----------- scripts/types/config.ts | 2 +- 7 files changed, 41 insertions(+), 249 deletions(-) diff --git a/scripts/deploy/addresses/osmo-test-5-testnet-deployer-owner.json b/scripts/deploy/addresses/osmo-test-5-testnet-deployer-owner.json index c2a716be1..708a6326e 100644 --- a/scripts/deploy/addresses/osmo-test-5-testnet-deployer-owner.json +++ b/scripts/deploy/addresses/osmo-test-5-testnet-deployer-owner.json @@ -1,8 +1,6 @@ { - "mockVault": "osmo1r7qzusdgpaw0f8jgeewtxquszxzd5sp63e2t6ezp72j6qd6wt4ns6ufkpd", - "swapper": "osmo1q3p82qtudu7f5edgvqyzf6hk8xanezlr0w7ntypnsea4jfpe37ps29eay3", - "zapper": "osmo1dz3ysw5sl0rvvnvatv7nu6vyam687tentfuxfa22sxqqafdcnkdqht3uw5", - "healthContract": "osmo10agkp20f6kqm8jlay760gszyg6qhz247mzk0r84pvjxaq086t0zsfmg6gt", - "creditManager": "osmo1sp7hlk78xpw6aer9wvskpz5ryknafw5lgf7f4jku4zfxkmuee6nqswkr8f", - "accountNft": "osmo1gmpua5rkzg6cmju73fz5a9x454nz4vwlnkgs4g8wjlyeqtmzsuhq5funky" + "zapper": "osmo1jwtmujld4ew2vt67qnmpdxr3fmmle2yg823de5ygvf47uvhpu76qlhf82q", + "healthContract": "osmo1xepv3c9rjp7qyjs2z088w6x9006uzure3e3n5k6fd3k72jw5fcvsrhf334", + "creditManager": "osmo1zwugj8tz9nq63m3lxcfpunp0xr5lnlxdr0yyn4gpftx3ham09m4skn73ew", + "accountNft": "osmo1hdk6nps2l2tdfgly0tegcgwl239l45etuae7nsc6s93u0yrqk4rqv489kl" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 6d3c00fe0..4c0dbc9bf 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -7,10 +7,6 @@ import { InstantiateMsgs } from '../../types/instantiateMsgs' import { InstantiateMsg as NftInstantiateMsg } from '../../types/generated/mars-account-nft/MarsAccountNft.types' import { InstantiateMsg as VaultInstantiateMsg } from '../../types/generated/mars-mock-vault/MarsMockVault.types' import { InstantiateMsg as HealthInstantiateMsg } from '../../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' -import { - ExecuteMsg as SwapperExecute, - InstantiateMsg as SwapperInstantiateMsg, -} from '../../types/generated/mars-swapper-base/MarsSwapperBase.types' import { InstantiateMsg as ZapperInstantiateMsg } from '../../types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types' import { ExecuteMsg as CreditManagerExecute, @@ -24,18 +20,12 @@ import { Coin } from '@cosmjs/amino' import { writeFile } from 'fs/promises' import { join, resolve } from 'path' import assert from 'assert' -import { - MarsSwapperBaseClient, - MarsSwapperBaseQueryClient, -} from '../../types/generated/mars-swapper-base/MarsSwapperBase.client' import { MarsAccountNftClient } from '../../types/generated/mars-account-nft/MarsAccountNft.client' import { MarsCreditManagerClient, MarsCreditManagerQueryClient, } from '../../types/generated/mars-credit-manager/MarsCreditManager.client' -import { InitOrUpdateAssetParams } from '../../types/generated/mars-mock-red-bank/MarsMockRedBank.types' import { kebabCase } from 'lodash' -import { MarsMockOracleQueryClient } from '../../types/generated/mars-mock-oracle/MarsMockOracle.client' import { MarsRoverHealthTypesClient } from '../../types/generated/mars-rover-health-types/MarsRoverHealthTypes.client' export class Deployer { @@ -90,7 +80,7 @@ export class Deployer { if (this.storage.actions.healthContractConfigUpdate) { printGray('Credit manager address') } else { - let hExec = new MarsRoverHealthTypesClient( + const hExec = new MarsRoverHealthTypesClient( this.cwClient, this.deployerAddr, this.storage.addresses.healthContract!, @@ -140,37 +130,6 @@ export class Deployer { } } - async instantiateSwapper() { - const msg: SwapperInstantiateMsg = { - owner: this.deployerAddr, - } - await this.instantiate('swapper', this.storage.codeIds.swapper!, msg) - - if (!this.storage.actions.setRoutes) { - const swapClient = new MarsSwapperBaseClient( - this.cwClient, - this.deployerAddr, - this.storage.addresses.swapper!, - ) - - for (const route of this.config.swapRoutes) { - printBlue(`Setting ${route.denomIn}-${route.denomOut} route for swapper contract`) - // @ts-expect-error ts-codegen cannot parse the generic - await swapClient.setRoute(route) - } - - const swapQuery = new MarsSwapperBaseQueryClient( - this.cwClient, - this.storage.addresses.swapper!, - ) - const routes = await swapQuery.routes({}) - assert.equal(routes.length, this.config.swapRoutes.length) - this.storage.actions.setRoutes = true - } else { - printGray("Swap contract's routes already set") - } - } - async instantiateZapper() { const msg: ZapperInstantiateMsg = {} await this.instantiate('zapper', this.storage.codeIds.zapper!, msg) @@ -183,7 +142,7 @@ export class Deployer { oracle: this.config.oracle.addr, owner: this.deployerAddr, red_bank: this.config.redBank.addr, - swapper: this.storage.addresses.swapper!, + swapper: this.config.swapper.addr, zapper: this.storage.addresses.zapper!, health_contract: this.storage.addresses.healthContract!, incentives: this.config.incentives.addr, @@ -274,75 +233,6 @@ export class Deployer { this.storage.actions.grantedCreditLines = true } - async setupOraclePrices() { - if (this.storage.actions.oraclePricesSet) { - printGray('Oracle prices already set') - return - } - - for (const coin of this.config.testActions?.allowedCoinsConfig ?? []) { - const oQuery = new MarsMockOracleQueryClient(this.cwClient, this.config.oracle.addr) - try { - await oQuery.price({ denom: coin.denom }) - printGray(`Price source already set for ${coin.denom}`) - } catch (e) { - const msg = { - set_price_source: { - denom: coin.denom, - price_source: coin.priceSource, - }, - } - printBlue(`Setting price source for ${coin.denom}: ${JSON.stringify(coin.priceSource)}`) - const { client, addr } = await this.getOutpostsDeployer() - await client.execute(addr, this.config.oracle.addr, msg, 'auto') - } - } - this.storage.actions.oraclePricesSet = true - } - - async setupRedBankMarkets() { - if (this.storage.actions.redBankMarketsSet) { - printGray('Red bank markets already set') - return - } - - const { client, addr } = await this.getOutpostsDeployer() - - for (const denom of this.config.testActions?.allowedCoinsConfig.map((c) => c.denom) ?? []) { - try { - await client.queryContractSmart(this.config.redBank.addr, { - market: { - denom, - }, - }) - printGray(`Market for ${denom} already set`) - } catch { - const msg: { - init_asset: { - denom: string - params: InitOrUpdateAssetParams - } - } = { - init_asset: { - denom, - params: { - reserve_factor: '0.2', - interest_rate_model: { - optimal_utilization_rate: '0.1', - base: '0.3', - slope_1: '0.25', - slope_2: '0.3', - }, - }, - }, - } - printBlue(`Setting market for ${denom}`) - await client.execute(addr, this.config.redBank.addr, msg, 'auto') - } - } - this.storage.actions.redBankMarketsSet = true - } - async updateCreditManagerOwner() { if (!this.config.multisigAddr) throw new Error('No multisig addresses to transfer ownership to') @@ -369,34 +259,6 @@ export class Deployer { assert.equal(creditManagerConfig.ownership.proposed, this.config.multisigAddr) } - async updateSwapperOwner() { - if (!this.config.multisigAddr) throw new Error('No multisig addresses to transfer ownership to') - - const msg: SwapperExecute = { - update_owner: { - propose_new_owner: { - proposed: this.config.multisigAddr, - }, - }, - } - await this.cwClient.execute(this.deployerAddr, this.storage.addresses.swapper!, msg, 'auto') - printGreen('Owner updated to Multisig for Swapper Contract') - - const swQuery = new MarsSwapperBaseQueryClient(this.cwClient, this.storage.addresses.swapper!) - const swapperOwner = await swQuery.owner() - assert.equal(swapperOwner.proposed, this.config.multisigAddr) - } - - private async getOutpostsDeployer() { - const wallet = await getWallet( - this.config.testActions!.outpostsDeployerMnemonic, - this.config.chain.prefix, - ) - const client = await setupClient(this.config, wallet) - const addr = await getAddress(wallet) - return { client, addr } - } - private async transferCoin(recipient: string, coin: Coin) { await this.cwClient.sendTokens(this.deployerAddr, recipient, [coin], 2) const balance = await this.cwClient.getBalance(recipient, coin.denom) diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 989971e03..54c75020c 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -14,14 +14,12 @@ export const taskRunner = async ({ config, label }: TaskRunnerProps) => { // Upload contracts await deployer.upload('accountNft', wasmFile('mars_account_nft')) await deployer.upload('mockVault', wasmFile('mars_mock_vault')) - await deployer.upload('swapper', wasmFile(config.swapperContractName)) await deployer.upload('zapper', wasmFile(config.zapperContractName)) await deployer.upload('creditManager', wasmFile('mars_credit_manager')) await deployer.upload('healthContract', wasmFile('mars_rover_health')) // Instantiate contracts await deployer.instantiateMockVault() - await deployer.instantiateSwapper() await deployer.instantiateZapper() await deployer.instantiateHealthContract() await deployer.instantiateCreditManager() @@ -33,8 +31,6 @@ export const taskRunner = async ({ config, label }: TaskRunnerProps) => { // Test basic user flows if (config.testActions) { await deployer.grantCreditLines() - await deployer.setupOraclePrices() - await deployer.setupRedBankMarkets() const rover = await deployer.newUserRoverClient(config.testActions) await rover.createCreditAccount() @@ -62,7 +58,6 @@ export const taskRunner = async ({ config, label }: TaskRunnerProps) => { // If multisig is set, transfer ownership from deployer to multisig if (config.multisigAddr) { await deployer.updateCreditManagerOwner() - await deployer.updateSwapperOwner() } printYellow('COMPLETE') diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 373210971..002b16d51 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -98,32 +98,34 @@ export class Rover { async borrow() { const amount = this.actions.borrowAmount - await this.updateCreditAccount([{ borrow: { amount, denom: this.actions.secondaryDenom } }]) + await this.updateCreditAccount([{ borrow: { amount, denom: this.config.chain.baseDenom } }]) const positions = await this.query.positions({ accountId: this.accountId! }) assert.equal(positions.debts.length, 1) - assert.equal(positions.debts[0].denom, this.actions.secondaryDenom) - printGreen(`Borrowed from RedBank: ${amount} ${this.actions.secondaryDenom}`) + assert.equal(positions.debts[0].denom, this.config.chain.baseDenom) + printGreen(`Borrowed from RedBank: ${amount} ${this.config.chain.baseDenom}`) } async repay() { const amount = this.actions.repayAmount await this.updateCreditAccount([ - { repay: { coin: { amount: { exact: amount }, denom: this.actions.secondaryDenom } } }, + { repay: { coin: { amount: { exact: amount }, denom: this.config.chain.baseDenom } } }, ]) const positions = await this.query.positions({ accountId: this.accountId! }) printGreen( `Repaid to RedBank: ${amount} ${ - this.actions.secondaryDenom + this.config.chain.baseDenom }. Debt remaining: ${JSON.stringify(positions.debts)}`, ) } async reclaim() { + const positions = await this.query.positions({ accountId: this.accountId! }) + printGreen(JSON.stringify(positions)) + const amount = this.actions.reclaimAmount await this.updateCreditAccount([ { reclaim: { amount: { exact: amount }, denom: this.config.chain.baseDenom } }, ]) - const positions = await this.query.positions({ accountId: this.accountId! }) printGreen( `User reclaimed: ${amount} ${ this.config.chain.baseDenom diff --git a/scripts/deploy/osmosis/mainnet.ts b/scripts/deploy/osmosis/mainnet.ts index 294008fa5..541114c67 100644 --- a/scripts/deploy/osmosis/mainnet.ts +++ b/scripts/deploy/osmosis/mainnet.ts @@ -25,12 +25,7 @@ export const osmosisMainnetConfig: DeploymentConfig = { redBank: { addr: 'osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg' }, incentives: { addr: 'osmo1nkahswfr8shg8rlxqwup0vgahp0dk4x8w6tkv3rra8rratnut36sk22vrm' }, params: { addr: 'TBD' }, - swapRoutes: [ - { denomIn: uosmo, denomOut: uatom, route: [{ token_out_denom: uatom, pool_id: '1' }] }, - { denomIn: uatom, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '1' }] }, - { denomIn: uosmo, denomOut: axlUSDC, route: [{ token_out_denom: axlUSDC, pool_id: '678' }] }, - { denomIn: axlUSDC, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '678' }] }, - ], + swapper: { addr: 'TBD' }, vaults: [ { addr: 'osmo1g3kmqpp8608szfp0pdag3r6z85npph7wmccat8lgl3mp407kv73qlj7qwp', diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index 213694bb6..388734435 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -1,24 +1,34 @@ -import { DeploymentConfig, VaultType } from '../../types/config' +import { DeploymentConfig } from '../../types/config' // Note: since osmo-test-5 upgrade, testnet and mainnet denoms are no longer the same. Reference asset info here: https://docs.osmosis.zone/osmosis-core/asset-info/ const uosmo = 'uosmo' -const nUSDC = 'ibc/B3504E092456BA618CC28AC671A71FB08C6CA0FD0BE7C8A5B5A3E2DD933CC9E4' // noble -const nUSDC_osmo = 'gamm/pool/6' +const aUSDC = 'ibc/6F34E1BD664C36CE49ACC28E60D62559A5F96C4F9A6CCE4FC5A67B2852E24CFE' // axelar USDC +const atom = 'ibc/A8C2D23A1E6F95DA4E48BA349667E322BD7A6C996D8A4AAE8BA72E190F3D1477' -const nUSDC_OSMO_vault_1 = 'osmo1q40xvrzpldwq5he4ftsf7zm2jf80tj373qaven38yqrvhex8r9rs8n94kv' -const nUSDC_OSMO_vault_7 = 'osmo14lu7m4ganxs20258dazafrjfaulmfxruq9n0r0th90gs46jk3tuqwfkqwn' -const nUSDC_OSMO_vault_14 = 'osmo1fmq9hw224fgz8lk48wyd0gfg028kvvzggt6c3zvnaqkw23x68cws5nd5em' +const ausdcOsmoPool = 'gamm/pool/5' +const atomOsmoPool = 'gamm/pool/12' -const nUSDC_OSMO_Config = (addr: string) => ({ +// All vaults below are ONE day vaults +const atomOsmoVault = 'osmo1m45ap4rq4m2mfjkcqu9ks9mxmyx2hvx0cdca9sjmrg46q7lghzqqhxxup5' +const ausdcOsmoVault = 'osmo1l3q4mrhkzjyernjhg8lz2t52ddw589y5qc0z7y8y28h6y5wcl46sg9n28j' + +const ATOM_OSMO_Config = (addr: string) => ({ addr, - deposit_cap: { denom: nUSDC, amount: '1000000000' }, // 1000 atom + deposit_cap: { denom: aUSDC, amount: '1000000000' }, // 1000 atom + max_loan_to_value: '0.63', + liquidation_threshold: '0.65', + whitelisted: true, +}) +const aUSDC_OSMO_Config = (addr: string) => ({ + addr, + deposit_cap: { denom: aUSDC, amount: '1000000000' }, // 1000 atom max_loan_to_value: '0.63', liquidation_threshold: '0.65', whitelisted: true, }) export const osmosisTestnetConfig: DeploymentConfig = { - allowedCoins: [uosmo, nUSDC, nUSDC, nUSDC_osmo], + allowedCoins: [uosmo, aUSDC, atom, ausdcOsmoPool, atomOsmoPool], chain: { baseDenom: uosmo, defaultGasPrice: 0.1, @@ -31,83 +41,13 @@ export const osmosisTestnetConfig: DeploymentConfig = { maxUnlockingPositions: '10', maxValueForBurn: '1000000', // Latest from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-5.json - oracle: { addr: 'osmo1khe29uw3t85nmmp3mtr8dls7v2qwsfk3tndu5h4w5g2r5tzlz5qqarq2e2' }, - redBank: { addr: 'osmo1dl4rylasnd7mtfzlkdqn2gr0ss4gvyykpvr6d7t5ylzf6z535n9s5jjt8u' }, - params: { addr: 'osmo1xvg28lrr72662t9u0hntt76lyax9zvptdvdmff4k2q9dhjm8x6ws9zym4v' }, - incentives: { addr: 'osmo1zyz57xf82963mcsgqu3hq5y0h9mrltm4ttq2qe5mjth9ezp3375qe0sm7d' }, - swapRoutes: [ - { denomIn: uosmo, denomOut: nUSDC, route: [{ token_out_denom: nUSDC, pool_id: '6' }] }, - { denomIn: nUSDC, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '6' }] }, - ], + redBank: { addr: 'osmo1hs4sm0fah9rk4mz8e56v4n76g0q9fffdkkjm3f8tjagkdx78pqcq75pk0a' }, + incentives: { addr: 'osmo1nu0k6g294jela67vyth6nwr3l42gutq2m07pg9927f7v7tuv0d4sre9fr7' }, + oracle: { addr: 'osmo1dxu93scjdnx42txdp9d4hm3snffvnzmkp4jpc9sml8xlu3ncgamsl2lx58' }, + swapper: { addr: 'osmo1ee9cq8dcknmw43znznx6vuupx5ku0tt505agccgaz5gn48mhe45s3kwwfm' }, + params: { addr: 'osmo1h334tvddn82m4apm08rm9k6kt32ws7vy0c4n30ngrvu6h6yxh8eq9l9jfh' }, // Latest from: https://api.apollo.farm/api/graph?query=query+MyQuery+%7B%0A++vaults%28network%3A+osmo_test_5%29+%7B%0A++++label%0A++++contract_address%0A++%7D%0A%7D - vaults: [ - nUSDC_OSMO_Config(nUSDC_OSMO_vault_1), - nUSDC_OSMO_Config(nUSDC_OSMO_vault_7), - nUSDC_OSMO_Config(nUSDC_OSMO_vault_14), - ], + vaults: [aUSDC_OSMO_Config(ausdcOsmoVault), ATOM_OSMO_Config(atomOsmoVault)], swapperContractName: 'mars_swapper_osmosis', zapperContractName: 'mars_v2_zapper_osmosis', - testActions: { - allowedCoinsConfig: [ - { denom: uosmo, priceSource: { fixed: { price: '1' } }, grantCreditLine: true }, - { - denom: nUSDC, - priceSource: { geometric_twap: { pool_id: 5, window_size: 1800 } }, - grantCreditLine: true, - }, - { - denom: nUSDC_osmo, - priceSource: { xyk_liquidity_token: { pool_id: 6 } }, - grantCreditLine: false, - }, - ], - vault: { - depositAmount: '1000000', - withdrawAmount: '1000000', - mock: { - config: { - deposit_cap: { denom: nUSDC, amount: '100000000' }, // 100 usdc - liquidation_threshold: '0.585', - max_loan_to_value: '0.569', - whitelisted: true, - }, - vaultTokenDenom: uosmo, - type: VaultType.LOCKED, - lockup: { time: 900 }, // 15 mins - baseToken: nUSDC_osmo, - }, - }, - outpostsDeployerMnemonic: - 'elevator august inherit simple buddy giggle zone despair marine rich swim danger blur people hundred faint ladder wet toe strong blade utility trial process', - borrowAmount: '10', - repayAmount: '11', - defaultCreditLine: '100000000000', - depositAmount: '100', - lendAmount: '10', - reclaimAmount: '5', - secondaryDenom: nUSDC, - startingAmountForTestUser: '4000000', - swap: { - slippage: '0.4', - amount: '40', - route: [ - { - token_out_denom: nUSDC, - pool_id: '1', - }, - ], - }, - unzapAmount: '1000000', - withdrawAmount: '12', - zap: { - coinsIn: [ - { - denom: nUSDC, - amount: '1', - }, - { denom: uosmo, amount: '3' }, - ], - denomOut: nUSDC_osmo, - }, - }, } diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 3b56a455d..abd3e9b3d 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -25,11 +25,11 @@ export interface DeploymentConfig { redBank: { addr: string } incentives: { addr: string } params: { addr: string } + swapper: { addr: string } vaults: VaultConfigBaseForString[] allowedCoins: string[] maxValueForBurn: string maxUnlockingPositions: string - swapRoutes: SwapRoute[] testActions?: TestActions swapperContractName: string zapperContractName: string From d77b2685824067913d3255bbfacbaef8cac3de48 Mon Sep 17 00:00:00 2001 From: brimigs <85972460+brimigs@users.noreply.github.com> Date: Wed, 9 Aug 2023 18:20:59 +0100 Subject: [PATCH 195/218] Add tests (#175) --- .../osmo-test-5-testnet-deployer-owner.json | 3 +- scripts/deploy/base/index.ts | 2 +- scripts/deploy/base/rover.ts | 5 +- scripts/deploy/osmosis/mainnet.ts | 1 + scripts/deploy/osmosis/testnet-config.ts | 66 ++++++++++++++++++- scripts/types/config.ts | 1 + 6 files changed, 74 insertions(+), 4 deletions(-) diff --git a/scripts/deploy/addresses/osmo-test-5-testnet-deployer-owner.json b/scripts/deploy/addresses/osmo-test-5-testnet-deployer-owner.json index 708a6326e..4b73121f9 100644 --- a/scripts/deploy/addresses/osmo-test-5-testnet-deployer-owner.json +++ b/scripts/deploy/addresses/osmo-test-5-testnet-deployer-owner.json @@ -2,5 +2,6 @@ "zapper": "osmo1jwtmujld4ew2vt67qnmpdxr3fmmle2yg823de5ygvf47uvhpu76qlhf82q", "healthContract": "osmo1xepv3c9rjp7qyjs2z088w6x9006uzure3e3n5k6fd3k72jw5fcvsrhf334", "creditManager": "osmo1zwugj8tz9nq63m3lxcfpunp0xr5lnlxdr0yyn4gpftx3ham09m4skn73ew", - "accountNft": "osmo1hdk6nps2l2tdfgly0tegcgwl239l45etuae7nsc6s93u0yrqk4rqv489kl" + "accountNft": "osmo1hdk6nps2l2tdfgly0tegcgwl239l45etuae7nsc6s93u0yrqk4rqv489kl", + "mockVault": "osmo1yl7rdzudtvfdmde9rn7jyu9q7malv5mk0mxpqsr4lrqtvjly8gmsq55tv6" } diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 54c75020c..17f2d433c 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -29,7 +29,7 @@ export const taskRunner = async ({ config, label }: TaskRunnerProps) => { await deployer.saveDeploymentAddrsToFile(label) // Test basic user flows - if (config.testActions) { + if (config.runTests && config.testActions) { await deployer.grantCreditLines() const rover = await deployer.newUserRoverClient(config.testActions) diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 002b16d51..39d7fd6cf 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -120,7 +120,6 @@ export class Rover { async reclaim() { const positions = await this.query.positions({ accountId: this.accountId! }) - printGreen(JSON.stringify(positions)) const amount = this.actions.reclaimAmount await this.updateCreditAccount([ @@ -202,6 +201,10 @@ export class Rover { this.storage.addresses.creditManager!, info.tokens.vault_token, ) + printBlue('testing vault deposit') + printGreen(v.addr) + printGreen(this.actions.vault.depositAmount) + printGreen(info.tokens.base_token) await this.updateCreditAccount([ { enter_vault: { diff --git a/scripts/deploy/osmosis/mainnet.ts b/scripts/deploy/osmosis/mainnet.ts index 541114c67..ed0025146 100644 --- a/scripts/deploy/osmosis/mainnet.ts +++ b/scripts/deploy/osmosis/mainnet.ts @@ -26,6 +26,7 @@ export const osmosisMainnetConfig: DeploymentConfig = { incentives: { addr: 'osmo1nkahswfr8shg8rlxqwup0vgahp0dk4x8w6tkv3rra8rratnut36sk22vrm' }, params: { addr: 'TBD' }, swapper: { addr: 'TBD' }, + runTests: false, vaults: [ { addr: 'osmo1g3kmqpp8608szfp0pdag3r6z85npph7wmccat8lgl3mp407kv73qlj7qwp', diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index 388734435..b9d2819ec 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -1,4 +1,4 @@ -import { DeploymentConfig } from '../../types/config' +import { DeploymentConfig, VaultType } from '../../types/config' // Note: since osmo-test-5 upgrade, testnet and mainnet denoms are no longer the same. Reference asset info here: https://docs.osmosis.zone/osmosis-core/asset-info/ const uosmo = 'uosmo' @@ -50,4 +50,68 @@ export const osmosisTestnetConfig: DeploymentConfig = { vaults: [aUSDC_OSMO_Config(ausdcOsmoVault), ATOM_OSMO_Config(atomOsmoVault)], swapperContractName: 'mars_swapper_osmosis', zapperContractName: 'mars_v2_zapper_osmosis', + runTests: true, + testActions: { + allowedCoinsConfig: [ + { denom: uosmo, priceSource: { fixed: { price: '1' } }, grantCreditLine: true }, + { + denom: aUSDC, + priceSource: { geometric_twap: { pool_id: 5, window_size: 1800 } }, + grantCreditLine: true, + }, + { + denom: ausdcOsmoPool, + priceSource: { xyk_liquidity_token: { pool_id: 6 } }, + grantCreditLine: false, + }, + ], + vault: { + depositAmount: '1000000', + withdrawAmount: '1000000', + mock: { + config: { + deposit_cap: { denom: aUSDC, amount: '100000000' }, // 100 usdc + liquidation_threshold: '0.585', + max_loan_to_value: '0.569', + whitelisted: true, + }, + vaultTokenDenom: uosmo, + type: VaultType.LOCKED, + lockup: { time: 900 }, // 15 mins + baseToken: ausdcOsmoPool, + }, + }, + outpostsDeployerMnemonic: + 'elevator august inherit simple buddy giggle zone despair marine rich swim danger blur people hundred faint ladder wet toe strong blade utility trial process', + borrowAmount: '10', + repayAmount: '11', + defaultCreditLine: '100000000000', + depositAmount: '100', + lendAmount: '10', + reclaimAmount: '5', + secondaryDenom: aUSDC, + startingAmountForTestUser: '4000000', + swap: { + slippage: '0.4', + amount: '40', + route: [ + { + token_out_denom: aUSDC, + pool_id: '1', + }, + ], + }, + unzapAmount: '1000000', + withdrawAmount: '12', + zap: { + coinsIn: [ + { + denom: aUSDC, + amount: '1', + }, + { denom: uosmo, amount: '3' }, + ], + denomOut: ausdcOsmoPool, + }, + }, } diff --git a/scripts/types/config.ts b/scripts/types/config.ts index abd3e9b3d..bc336f2c3 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -34,6 +34,7 @@ export interface DeploymentConfig { swapperContractName: string zapperContractName: string multisigAddr?: string + runTests: boolean } export interface SwapRoute { From fe09cc90fb79e13eea6b4f0b2d7b3f4d78f7d67d Mon Sep 17 00:00:00 2001 From: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com> Date: Mon, 14 Aug 2023 16:59:59 -0300 Subject: [PATCH 196/218] [HC] update wasm files (#176) --- scripts/health/pkg-node/index_bg.wasm | Bin 246657 -> 248533 bytes scripts/health/pkg-web/index_bg.wasm | Bin 246510 -> 248386 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index c1c46d7230a7912617302d12651a661ee262100b..340a3c0d2a2bd0e0a6add2c62c1db68e9f835b62 100644 GIT binary patch delta 84657 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 delta 83362 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

1@VGA(DQWLAtuST+& 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 VVcp{}T$$pu0i(L=ey6(i{{z+`J?;Pi diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm index 63a003f67339d4a7df76a2bf3c566e77cfade891..14db4831fd8abd55d0862cbcef8a353529981c4b 100644 GIT binary patch delta 84760 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 delta 83264 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 Qx2v4CkVPx+aVo3+59Z}G8vp Date: Mon, 21 Aug 2023 16:03:33 +0100 Subject: [PATCH 197/218] Withdraw takes action coin (#177) --- .cargo/audit.toml | 6 ++ contracts/credit-manager/src/execute.rs | 2 +- contracts/credit-manager/src/refund.rs | 12 +++- contracts/credit-manager/src/withdraw.rs | 43 +++++++++++---- contracts/credit-manager/tests/test_health.rs | 2 +- .../tests/test_liquidate_deposit.rs | 2 +- contracts/credit-manager/tests/test_repay.rs | 2 +- .../credit-manager/tests/test_withdraw.rs | 55 +++++++++++++++---- packages/rover/src/msg/execute.rs | 4 +- .../mars-credit-manager.json | 4 +- scripts/deploy/base/rover.ts | 4 +- .../MarsCreditManager.types.ts | 4 +- 12 files changed, 107 insertions(+), 33 deletions(-) create mode 100644 .cargo/audit.toml diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 000000000..80dec5a81 --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,6 @@ +# Reference: https://github.com/rustsec/rustsec/blob/main/cargo-audit/audit.toml.example + +[advisories] +# Ignore the following advisory IDs. +# RUSTSEC-2022-0093 is reported for test-tube which is only used for testing. +ignore = ["RUSTSEC-2022-0093"] \ No newline at end of file diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index ee07a8156..695a58403 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -297,7 +297,7 @@ pub fn execute_callback( account_id, coin, recipient, - } => withdraw(deps, &account_id, coin, recipient), + } => withdraw(deps, &account_id, &coin, recipient), CallbackMsg::Borrow { coin, account_id, diff --git a/contracts/credit-manager/src/refund.rs b/contracts/credit-manager/src/refund.rs index 4afec272f..519021e6d 100644 --- a/contracts/credit-manager/src/refund.rs +++ b/contracts/credit-manager/src/refund.rs @@ -1,7 +1,10 @@ use cosmwasm_std::{to_binary, Addr, CosmosMsg, DepsMut, Env, Response, WasmMsg}; use mars_rover::{ error::ContractResult, - msg::{execute::CallbackMsg, ExecuteMsg}, + msg::{ + execute::{ActionAmount, ActionCoin, CallbackMsg}, + ExecuteMsg, + }, }; use crate::{query::query_coin_balances, utils::query_nft_token_owner}; @@ -12,12 +15,17 @@ pub fn refund_coin_balances(deps: DepsMut, env: Env, account_id: &str) -> Contra let withdraw_msgs = coins .into_iter() .map(|coin| { + let action_amount = ActionAmount::Exact(coin.amount); + let action_coin = ActionCoin { + denom: coin.denom, + amount: action_amount, + }; Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: env.contract.address.to_string(), funds: vec![], msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::Withdraw { account_id: account_id.to_string(), - coin, + coin: action_coin, recipient: Addr::unchecked(account_nft_owner.clone()), }))?, })) diff --git a/contracts/credit-manager/src/withdraw.rs b/contracts/credit-manager/src/withdraw.rs index 035ba0f72..89d3f9ec2 100644 --- a/contracts/credit-manager/src/withdraw.rs +++ b/contracts/credit-manager/src/withdraw.rs @@ -1,29 +1,52 @@ -use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, DepsMut, Response}; -use mars_rover::error::{ContractError, ContractResult}; +use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, Deps, DepsMut, Response}; +use mars_rover::{ + error::{ContractError, ContractResult}, + msg::execute::{ActionAmount, ActionCoin}, +}; -use crate::utils::decrement_coin_balance; +use crate::{state::COIN_BALANCES, utils::decrement_coin_balance}; pub fn withdraw( deps: DepsMut, account_id: &str, - coin: Coin, + coin: &ActionCoin, recipient: Addr, ) -> ContractResult { - if coin.amount.is_zero() { - return Err(ContractError::NoAmount); - } + let amount_to_withdraw = get_withdraw_amount(deps.as_ref(), account_id, coin)?; - decrement_coin_balance(deps.storage, account_id, &coin)?; + decrement_coin_balance(deps.storage, account_id, &amount_to_withdraw)?; // send coin to recipient let transfer_msg = CosmosMsg::Bank(BankMsg::Send { to_address: recipient.to_string(), - amount: vec![coin.clone()], + amount: vec![amount_to_withdraw.clone()], }); Ok(Response::new() .add_message(transfer_msg) .add_attribute("action", "callback/withdraw") .add_attribute("account_id", account_id) - .add_attribute("coin_withdrawn", coin.to_string())) + .add_attribute("coin_withdrawn", amount_to_withdraw.to_string())) +} + +/// Checks if Exact or Account Balance is passed through Action Coin +/// Also asserts the amount is greater than zero. +fn get_withdraw_amount(deps: Deps, account_id: &str, coin: &ActionCoin) -> ContractResult { + let amount = match coin.amount { + ActionAmount::Exact(amount) => amount, + ActionAmount::AccountBalance => { + COIN_BALANCES.may_load(deps.storage, (account_id, &coin.denom))?.unwrap_or_default() + } + }; + + if amount.is_zero() { + return Err(ContractError::NoAmount); + } + + let coin = Coin { + denom: coin.denom.clone(), + amount, + }; + + Ok(coin) } diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index a25a94c62..e5232ce98 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -932,7 +932,7 @@ fn can_take_actions_if_ltv_does_not_weaken() { mock.update_credit_account( &account_id, &user, - vec![Deposit(uosmo_info.to_coin(1)), Withdraw(uosmo_info.to_coin(1))], + vec![Deposit(uosmo_info.to_coin(1)), Withdraw(uosmo_info.to_action_coin(1))], &[uosmo_info.to_coin(1)], ) .unwrap(); diff --git a/contracts/credit-manager/tests/test_liquidate_deposit.rs b/contracts/credit-manager/tests/test_liquidate_deposit.rs index 6c7610160..d228dd0d8 100644 --- a/contracts/credit-manager/tests/test_liquidate_deposit.rs +++ b/contracts/credit-manager/tests/test_liquidate_deposit.rs @@ -477,7 +477,7 @@ fn target_health_factor_reached_after_max_debt_repayed() { vec![ Deposit(uosmo_info.to_coin(3000)), Borrow(uatom_info.to_coin(1000)), - Withdraw(uatom_info.to_coin(400)), + Withdraw(uatom_info.to_action_coin(400)), ], &[Coin::new(3000, uosmo_info.denom.clone())], ) diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index 431ad82c9..4e4c410b1 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -179,7 +179,7 @@ fn raises_when_not_enough_assets_to_repay() { vec![ Deposit(uatom_info.to_coin(300)), Borrow(uosmo_info.to_coin(50)), - Withdraw(uosmo_info.to_coin(10)), + Withdraw(uosmo_info.to_action_coin(10)), Repay { recipient_account_id: None, coin: uosmo_info.to_action_coin(50), diff --git a/contracts/credit-manager/tests/test_withdraw.rs b/contracts/credit-manager/tests/test_withdraw.rs index 6643dd3f0..0c09a7cde 100644 --- a/contracts/credit-manager/tests/test_withdraw.rs +++ b/contracts/credit-manager/tests/test_withdraw.rs @@ -19,7 +19,7 @@ fn only_owner_of_token_can_withdraw() { let res = mock.update_credit_account( &account_id, &another_user, - vec![Action::Withdraw(coin(382, coin_info.denom))], + vec![Action::Withdraw(coin_info.to_action_coin(382))], &[], ); @@ -45,12 +45,11 @@ fn withdraw_nothing() { let res = mock.update_credit_account( &account_id, &user, - vec![Action::Withdraw(coin(0, coin_info.denom))], + vec![Action::Withdraw(coin_info.to_action_coin(0))], &[], ); assert_err(res, ContractError::NoAmount); - let res = mock.query_positions(&account_id); assert_eq!(res.deposits.len(), 0); } @@ -65,7 +64,7 @@ fn withdraw_but_no_funds() { let res = mock.update_credit_account( &account_id, &user, - vec![Action::Withdraw(coin_info.to_coin(234))], + vec![Action::Withdraw(coin_info.to_action_coin(234))], &[], ); @@ -99,7 +98,10 @@ fn withdraw_but_not_enough_funds() { let res = mock.update_credit_account( &account_id, &user, - vec![Action::Deposit(coin_info.to_coin(300)), Action::Withdraw(coin_info.to_coin(400))], + vec![ + Action::Deposit(coin_info.to_coin(300)), + Action::Withdraw(coin_info.to_action_coin(400)), + ], &[coin(300, coin_info.denom)], ); @@ -136,7 +138,7 @@ fn cannot_withdraw_more_than_healthy() { vec![ Action::Deposit(coin_info.to_coin(200)), Action::Borrow(coin_info.to_coin(400)), - Action::Withdraw(coin_info.to_coin(50)), + Action::Withdraw(coin_info.to_action_coin(50)), ], &[coin(200, coin_info.denom)], ); @@ -173,7 +175,40 @@ fn withdraw_success() { &user, vec![ Action::Deposit(coin_info.to_coin(deposit_amount)), - Action::Withdraw(coin_info.to_coin(deposit_amount)), + Action::Withdraw(coin_info.to_action_coin(deposit_amount)), + ], + &[Coin::new(deposit_amount, coin_info.denom.clone())], + ) + .unwrap(); + + let res = mock.query_positions(&account_id); + assert_eq!(res.deposits.len(), 0); + + let coin = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin.amount, Uint128::zero()) +} + +#[test] +fn withdraw_account_balance() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .set_params(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); + + let deposit_amount = 234; + mock.update_credit_account( + &account_id, + &user, + vec![ + Action::Deposit(coin_info.to_coin(deposit_amount)), + Action::Withdraw(coin_info.to_action_coin_full_balance()), ], &[Coin::new(deposit_amount, coin_info.denom.clone())], ) @@ -228,7 +263,7 @@ fn multiple_withdraw_actions() { mock.update_credit_account( &account_id, &user, - vec![Action::Withdraw(uosmo_info.to_coin(uosmo_amount.u128()))], + vec![Action::Withdraw(uosmo_info.to_action_coin(uosmo_amount.u128()))], &[], ) .unwrap(); @@ -245,7 +280,7 @@ fn multiple_withdraw_actions() { mock.update_credit_account( &account_id, &user, - vec![Action::Withdraw(uatom_info.to_coin(20))], + vec![Action::Withdraw(uatom_info.to_action_coin(20))], &[], ) .unwrap(); @@ -262,7 +297,7 @@ fn multiple_withdraw_actions() { mock.update_credit_account( &account_id, &user, - vec![Action::Withdraw(uatom_info.to_coin(5))], + vec![Action::Withdraw(uatom_info.to_action_coin(5))], &[], ) .unwrap(); diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index de0199da0..ef2d68eb8 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -117,7 +117,7 @@ pub enum Action { /// Deposit coin of specified denom and amount. Verifies if the correct amount is sent with transaction. Deposit(Coin), /// Withdraw coin of specified denom and amount - Withdraw(Coin), + Withdraw(ActionCoin), /// Borrow coin of specified amount from Red Bank Borrow(Coin), /// Lend coin to the Red Bank @@ -195,7 +195,7 @@ pub enum CallbackMsg { /// Decrement the token's asset amount; Withdraw { account_id: String, - coin: Coin, + coin: ActionCoin, recipient: Addr, }, /// Borrow specified amount of coin from Red Bank; diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index eaa770aee..d0261f1a2 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -304,7 +304,7 @@ ], "properties": { "withdraw": { - "$ref": "#/definitions/Coin" + "$ref": "#/definitions/ActionCoin" } }, "additionalProperties": false @@ -709,7 +709,7 @@ "type": "string" }, "coin": { - "$ref": "#/definitions/Coin" + "$ref": "#/definitions/ActionCoin" }, "recipient": { "$ref": "#/definitions/Addr" diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 39d7fd6cf..dfe38c3f6 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -87,7 +87,9 @@ export class Rover { const beforeWithdraw = parseFloat( positionsBefore.deposits.find((c) => c.denom === this.config.chain.baseDenom)!.amount, ) - await this.updateCreditAccount([{ withdraw: { amount, denom: this.config.chain.baseDenom } }]) + await this.updateCreditAccount([ + { withdraw: { amount: { exact: amount }, denom: this.config.chain.baseDenom } }, + ]) const positionsAfter = await this.query.positions({ accountId: this.accountId! }) const afterWithdraw = parseFloat( positionsAfter.deposits.find((c) => c.denom === this.config.chain.baseDenom)!.amount, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 8bded4650..8f2d0a9a5 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -62,7 +62,7 @@ export type Action = deposit: Coin } | { - withdraw: Coin + withdraw: ActionCoin } | { borrow: Coin @@ -176,7 +176,7 @@ export type CallbackMsg = | { withdraw: { account_id: string - coin: Coin + coin: ActionCoin recipient: Addr } } From a1d0df8c3334d7c2bc28eb822c2ddc2c641de281 Mon Sep 17 00:00:00 2001 From: piobab Date: Fri, 25 Aug 2023 21:08:30 +0200 Subject: [PATCH 198/218] MP-3292. Oak no. 1 (#180) * Add liquidation_related flag for withdraw. It is used by credit manager to indicate liquidation. * Fix cargo audit. * Use correct commit from red-bank. * Update schema. * Use --locked for grcov install. --- .cargo/audit.toml | 2 +- Cargo.lock | 32 +++++----- Cargo.toml | 14 ++--- .../credit-manager/src/liquidate_lend.rs | 2 +- contracts/credit-manager/src/reclaim.rs | 1 + .../tests/test_liquidate_lend.rs | 62 ++++++++++--------- .../credit-manager/tests/test_reclaim.rs | 37 ++++++++++- contracts/mock-red-bank/src/contract.rs | 5 +- contracts/mock-red-bank/src/execute.rs | 10 ++- coverage_grcov.Makefile.toml | 3 +- packages/rover/src/adapters/red_bank.rs | 8 ++- .../mars-mock-red-bank.json | 18 ++++++ .../MarsMockRedBank.client.ts | 33 +++++++++- .../MarsMockRedBank.message-composer.ts | 5 ++ .../MarsMockRedBank.react-query.ts | 5 ++ .../MarsMockRedBank.types.ts | 3 + 16 files changed, 175 insertions(+), 65 deletions(-) diff --git a/.cargo/audit.toml b/.cargo/audit.toml index 80dec5a81..65c70990d 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -3,4 +3,4 @@ [advisories] # Ignore the following advisory IDs. # RUSTSEC-2022-0093 is reported for test-tube which is only used for testing. -ignore = ["RUSTSEC-2022-0093"] \ No newline at end of file +ignore = ["RUSTSEC-2022-0093", "RUSTSEC-2023-0052"] \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 93d5b6481..c82fcea50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1459,7 +1459,7 @@ dependencies = [ [[package]] name = "mars-address-provider" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" +source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" dependencies = [ "bech32", "cosmwasm-std", @@ -1507,7 +1507,7 @@ dependencies = [ [[package]] name = "mars-health" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" +source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" dependencies = [ "cosmwasm-std", "mars-params", @@ -1518,7 +1518,7 @@ dependencies = [ [[package]] name = "mars-interest-rate" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" +source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1529,7 +1529,7 @@ dependencies = [ [[package]] name = "mars-liquidation" version = "1.0.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" +source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" dependencies = [ "cosmwasm-std", "mars-health", @@ -1607,9 +1607,9 @@ dependencies = [ [[package]] name = "mars-owner" -version = "1.2.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd53908ffc561da878ce5ff4f5ec9f25a193af28ec0b6e7c8e6d1a0866d9dfc" +checksum = "ab46e0b2f81a8a98036b46730fbe33a337e98e87cb3d34553b45a5ae87c5828c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1620,8 +1620,8 @@ dependencies = [ [[package]] name = "mars-params" -version = "1.0.7" -source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" +version = "1.2.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1631,15 +1631,13 @@ dependencies = [ "mars-owner", "mars-red-bank-types", "mars-utils", - "schemars", - "serde", "thiserror", ] [[package]] name = "mars-red-bank-types" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" +source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1761,7 +1759,7 @@ dependencies = [ [[package]] name = "mars-utils" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=18e9c56#18e9c565df15c2a5ea5fa92da561b0c7cb570294" +source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" dependencies = [ "cosmwasm-std", "thiserror", @@ -2699,24 +2697,24 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 911b31c27..cfa3cdd0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,19 +64,19 @@ tsify = "0.4.5" wasm-bindgen = "0.2.87" # mars packages -mars-health = { git = "https://github.com/mars-protocol/red-bank", rev = "18e9c56" } -mars-liquidation = { git = "https://github.com/mars-protocol/red-bank", rev = "18e9c56" } -mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "18e9c56" } -mars-owner = { version = "1.2.0", features = ["emergency-owner"] } -mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank", rev = "18e9c56" } +mars-health = { git = "https://github.com/mars-protocol/red-bank", rev = "6b5f55e" } +mars-liquidation = { git = "https://github.com/mars-protocol/red-bank", rev = "6b5f55e" } +mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "6b5f55e" } +mars-owner = { version = "2.0.0", features = ["emergency-owner"] } +mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank", rev = "6b5f55e" } mars-rover-health-computer = { version = "2.0.0", path = "./packages/health-computer" } mars-rover-health-types = { version = "2.0.0", path = "./packages/health-types" } mars-rover = { version = "2.0.0", path = "./packages/rover" } # contracts -mars-address-provider = { git = "https://github.com/mars-protocol/red-bank", rev = "18e9c56", features = ["library"] } +mars-address-provider = { git = "https://github.com/mars-protocol/red-bank", rev = "6b5f55e", features = ["library"] } mars-account-nft = { version = "2.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-params = { git = "https://github.com/mars-protocol/red-bank", rev = "18e9c56", features = ["library"] } +mars-params = { git = "https://github.com/mars-protocol/red-bank", rev = "6b5f55e", features = ["library"] } mars-rover-health = { version = "2.0.0", path = "./contracts/health", features = ["library"] } mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base", features = ["library"] } mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base", features = ["library"] } diff --git a/contracts/credit-manager/src/liquidate_lend.rs b/contracts/credit-manager/src/liquidate_lend.rs index c8b2a52f2..a3f1c6114 100644 --- a/contracts/credit-manager/src/liquidate_lend.rs +++ b/contracts/credit-manager/src/liquidate_lend.rs @@ -42,7 +42,7 @@ pub fn liquidate_lend( // Liquidatee's lent coin reclaimed from Red Bank let red_bank = RED_BANK.load(deps.storage)?; let reclaim_from_liquidatee_msg = - red_bank.reclaim_msg(&liquidatee_request, liquidatee_account_id)?; + red_bank.reclaim_msg(&liquidatee_request, liquidatee_account_id, true)?; // Liquidator gets portion of reclaimed lent coin increment_coin_balance(deps.storage, liquidator_account_id, &liquidator_request)?; diff --git a/contracts/credit-manager/src/reclaim.rs b/contracts/credit-manager/src/reclaim.rs index 5faf553a3..309e6547e 100644 --- a/contracts/credit-manager/src/reclaim.rs +++ b/contracts/credit-manager/src/reclaim.rs @@ -32,6 +32,7 @@ pub fn reclaim(deps: DepsMut, account_id: &str, coin: &ActionCoin) -> ContractRe amount: amount_to_reclaim, }, account_id, + false, )?; Ok(Response::new() diff --git a/contracts/credit-manager/tests/test_liquidate_lend.rs b/contracts/credit-manager/tests/test_liquidate_lend.rs index b888584ca..e037e9cfa 100644 --- a/contracts/credit-manager/tests/test_liquidate_lend.rs +++ b/contracts/credit-manager/tests/test_liquidate_lend.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{coins, Addr, Decimal, Uint128}; +use cosmwasm_std::{coins, Addr, Decimal, Event, Uint128}; use mars_mock_oracle::msg::CoinPrice; use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ @@ -206,20 +206,22 @@ fn lent_position_partially_liquidated() { let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); - mock.update_credit_account( - &liquidator_account_id, - &liquidator, - vec![ - Deposit(uatom_info.to_coin(45)), - Liquidate { - liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: uatom_info.to_coin(45), - request: LiquidateRequest::Lend(uosmo_info.denom), - }, - ], - &[uatom_info.to_coin(45)], - ) - .unwrap(); + let res = mock + .update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(uatom_info.to_coin(45)), + Liquidate { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(45), + request: LiquidateRequest::Lend(uosmo_info.denom), + }, + ], + &[uatom_info.to_coin(45)], + ) + .unwrap(); + res.assert_event(&Event::new("wasm-withdraw").add_attribute("liquidation_related", "true")); // Assert liquidatee's new position let position = mock.query_positions(&liquidatee_account_id); @@ -322,20 +324,22 @@ fn lent_position_fully_liquidated() { let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); - mock.update_credit_account( - &liquidator_account_id, - &liquidator, - vec![ - Deposit(uatom_info.to_coin(32)), - Liquidate { - liquidatee_account_id: liquidatee_account_id.clone(), - debt_coin: uatom_info.to_coin(32), - request: LiquidateRequest::Lend(uosmo_info.denom), - }, - ], - &[uatom_info.to_coin(32)], - ) - .unwrap(); + let res = mock + .update_credit_account( + &liquidator_account_id, + &liquidator, + vec![ + Deposit(uatom_info.to_coin(32)), + Liquidate { + liquidatee_account_id: liquidatee_account_id.clone(), + debt_coin: uatom_info.to_coin(32), + request: LiquidateRequest::Lend(uosmo_info.denom), + }, + ], + &[uatom_info.to_coin(32)], + ) + .unwrap(); + res.assert_event(&Event::new("wasm-withdraw").add_attribute("liquidation_related", "true")); // Assert liquidatee's new position let position = mock.query_positions(&liquidatee_account_id); diff --git a/contracts/credit-manager/tests/test_reclaim.rs b/contracts/credit-manager/tests/test_reclaim.rs index c0a957c1b..68be2ff20 100644 --- a/contracts/credit-manager/tests/test_reclaim.rs +++ b/contracts/credit-manager/tests/test_reclaim.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{coin, coins, Addr, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Event, Uint128}; use mars_rover::{ error::ContractError, msg::execute::Action::{Deposit, Lend, Reclaim}, @@ -308,3 +308,38 @@ fn reclaiming_multiple_assets() { let coin = mock.query_balance(&mock.rover, &uatom_info.denom); assert_eq!(coin.amount, Uint128::new(301)); } + +#[test] +fn reclaiming_with_correct_withdraw_liquidation_related_param() { + let coin_info = uosmo_info(); + let user = Addr::unchecked("user"); + + let mut mock = MockEnv::new() + .set_params(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(coin_info.to_coin(300)), Lend(coin_info.to_action_coin(200))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + let res = mock + .update_credit_account( + &account_id, + &user, + vec![Reclaim(coin_info.to_action_coin(100))], + &[], + ) + .unwrap(); + res.assert_event(&Event::new("wasm-withdraw").add_attribute("liquidation_related", "false")); +} diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index 6f6a69173..8d6bbc617 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -47,8 +47,11 @@ pub fn execute( denom, amount, account_id, + liquidation_related, .. - } => withdraw(deps, info, &denom, &amount, account_id), + } => { + withdraw(deps, info, &denom, &amount, account_id, liquidation_related.unwrap_or(false)) + } _ => unimplemented!("Msg not supported!"), } } diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs index a3cc1652e..8c532635b 100644 --- a/contracts/mock-red-bank/src/execute.rs +++ b/contracts/mock-red-bank/src/execute.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - coin, BankMsg, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, Response, StdError, StdResult, - Uint128, + coin, BankMsg, CosmosMsg, Decimal, DepsMut, Env, Event, MessageInfo, Response, StdError, + StdResult, Uint128, }; use cw_utils::one_coin; use mars_red_bank_types::red_bank::{InitOrUpdateAssetParams, Market}; @@ -112,6 +112,7 @@ pub fn withdraw( denom: &str, amount: &Option, account_id: Option, + liquidation_related: bool, ) -> StdResult { let total_lent = load_collateral_amount( deps.storage, @@ -145,5 +146,8 @@ pub fn withdraw( amount: vec![coin(amount_to_reclaim.u128(), denom)], }); - Ok(Response::new().add_message(transfer_msg)) + // This is only used for testing purposes to validate if 'withdraw' msg contains correct value for liquidation_related + let event = Event::new("withdraw") + .add_attribute("liquidation_related", liquidation_related.to_string()); + Ok(Response::new().add_event(event).add_message(transfer_msg)) } diff --git a/coverage_grcov.Makefile.toml b/coverage_grcov.Makefile.toml index d77fbc7bb..d127b183e 100644 --- a/coverage_grcov.Makefile.toml +++ b/coverage_grcov.Makefile.toml @@ -29,7 +29,8 @@ LLVM_PROFILE_FILE = "${COVERAGE_PROF_OUTPUT}/coverage-%p-%m.profraw" [tasks.install-grcov] condition = { env_not_set = ["SKIP_INSTALL_GRCOV"] } private = true -install_crate = { crate_name = "grcov" } +command = "cargo" +args = ["install", "grcov", "--locked"] # NOTE: ignore coverage for swapper and zapper contracts because their tests are based on `osmosis-testing` which don't work for grcov [tasks.coverage-grcov] diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index 7386f8e1f..db26dfb8d 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -81,7 +81,12 @@ impl RedBank { } /// Generate message for reclaiming a specified amount of lent coin - pub fn reclaim_msg(&self, coin: &Coin, account_id: &str) -> StdResult { + pub fn reclaim_msg( + &self, + coin: &Coin, + account_id: &str, + liquidation_related: bool, + ) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.addr.to_string(), msg: to_binary(&red_bank::ExecuteMsg::Withdraw { @@ -89,6 +94,7 @@ impl RedBank { amount: Some(coin.amount), recipient: None, account_id: Some(account_id.to_string()), + liquidation_related: Some(liquidation_related), })?, funds: vec![], })) diff --git a/schemas/mars-mock-red-bank/mars-mock-red-bank.json b/schemas/mars-mock-red-bank/mars-mock-red-bank.json index b48afb816..dcf1921d4 100644 --- a/schemas/mars-mock-red-bank/mars-mock-red-bank.json +++ b/schemas/mars-mock-red-bank/mars-mock-red-bank.json @@ -206,6 +206,12 @@ "description": "Asset to withdraw", "type": "string" }, + "liquidation_related": { + "type": [ + "boolean", + "null" + ] + }, "recipient": { "description": "The address where the withdrawn amount is sent", "type": [ @@ -797,6 +803,12 @@ "user" ], "properties": { + "account_id": { + "type": [ + "string", + "null" + ] + }, "user": { "type": "string" } @@ -819,6 +831,12 @@ "user" ], "properties": { + "account_id": { + "type": [ + "string", + "null" + ] + }, "user": { "type": "string" } diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index 7f35de451..488866643 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -86,8 +86,20 @@ export interface MarsMockRedBankReadOnlyInterface { startAfter?: string user: string }) => Promise - userPosition: ({ user }: { user: string }) => Promise - userPositionLiquidationPricing: ({ user }: { user: string }) => Promise + userPosition: ({ + accountId, + user, + }: { + accountId?: string + user: string + }) => Promise + userPositionLiquidationPricing: ({ + accountId, + user, + }: { + accountId?: string + user: string + }) => Promise scaledLiquidityAmount: ({ amount, denom }: { amount: Uint128; denom: string }) => Promise scaledDebtAmount: ({ amount, denom }: { amount: Uint128; denom: string }) => Promise underlyingLiquidityAmount: ({ @@ -254,20 +266,30 @@ export class MarsMockRedBankQueryClient implements MarsMockRedBankReadOnlyInterf }, }) } - userPosition = async ({ user }: { user: string }): Promise => { + userPosition = async ({ + accountId, + user, + }: { + accountId?: string + user: string + }): Promise => { return this.client.queryContractSmart(this.contractAddress, { user_position: { + account_id: accountId, user, }, }) } userPositionLiquidationPricing = async ({ + accountId, user, }: { + accountId?: string user: string }): Promise => { return this.client.queryContractSmart(this.contractAddress, { user_position_liquidation_pricing: { + account_id: accountId, user, }, }) @@ -401,11 +423,13 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa accountId, amount, denom, + liquidationRelated, recipient, }: { accountId?: string amount?: Uint128 denom: string + liquidationRelated?: boolean recipient?: string }, fee?: number | StdFee | 'auto', @@ -638,11 +662,13 @@ export class MarsMockRedBankClient accountId, amount, denom, + liquidationRelated, recipient, }: { accountId?: string amount?: Uint128 denom: string + liquidationRelated?: boolean recipient?: string }, fee: number | StdFee | 'auto' = 'auto', @@ -657,6 +683,7 @@ export class MarsMockRedBankClient account_id: accountId, amount, denom, + liquidation_related: liquidationRelated, recipient, }, }, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts index c4ffdbfbe..c0a17d409 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts @@ -88,11 +88,13 @@ export interface MarsMockRedBankMessage { accountId, amount, denom, + liquidationRelated, recipient, }: { accountId?: string amount?: Uint128 denom: string + liquidationRelated?: boolean recipient?: string }, _funds?: Coin[], @@ -312,11 +314,13 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { accountId, amount, denom, + liquidationRelated, recipient, }: { accountId?: string amount?: Uint128 denom: string + liquidationRelated?: boolean recipient?: string }, _funds?: Coin[], @@ -332,6 +336,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { account_id: accountId, amount, denom, + liquidation_related: liquidationRelated, recipient, }, }), diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index dd73bbe56..bd1a051b7 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -242,6 +242,7 @@ export function useMarsMockRedBankScaledLiquidityAmountQuery({ export interface MarsMockRedBankUserPositionLiquidationPricingQuery extends MarsMockRedBankReactQuery { args: { + accountId?: string user: string } } @@ -253,6 +254,7 @@ export function useMarsMockRedBankUserPositionLiquidationPricingQuery< () => client ? client.userPositionLiquidationPricing({ + accountId: args.accountId, user: args.user, }) : Promise.reject(new Error('Invalid client')), @@ -262,6 +264,7 @@ export function useMarsMockRedBankUserPositionLiquidationPricingQuery< export interface MarsMockRedBankUserPositionQuery extends MarsMockRedBankReactQuery { args: { + accountId?: string user: string } } @@ -275,6 +278,7 @@ export function useMarsMockRedBankUserPositionQuery client ? client.userPosition({ + accountId: args.accountId, user: args.user, }) : Promise.reject(new Error('Invalid client')), @@ -589,6 +593,7 @@ export interface MarsMockRedBankWithdrawMutation { accountId?: string amount?: Uint128 denom: string + liquidationRelated?: boolean recipient?: string } args?: { diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index 4ba8e213a..9ed75d987 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -46,6 +46,7 @@ export type ExecuteMsg = account_id?: string | null amount?: Uint128 | null denom: string + liquidation_related?: boolean | null recipient?: string | null } } @@ -162,11 +163,13 @@ export type QueryMsg = } | { user_position: { + account_id?: string | null user: string } } | { user_position_liquidation_pricing: { + account_id?: string | null user: string } } From 0910d94cd5ebd2a44fdb4f7c8226887151fdeff7 Mon Sep 17 00:00:00 2001 From: piobab Date: Mon, 28 Aug 2023 07:52:21 +0200 Subject: [PATCH 199/218] MP-3293. Oak no. 2. (#181) * Set correct contract names and version during migration. * Fix tests. --- contracts/account-nft/src/contract.rs | 29 ++++++------------- contracts/account-nft/src/error.rs | 6 ++-- contracts/account-nft/tests/test_migration.rs | 25 ++++++++-------- contracts/credit-manager/src/contract.rs | 6 +--- .../credit-manager/src/migrations/helpers.rs | 24 --------------- .../credit-manager/src/migrations/mod.rs | 1 - .../credit-manager/src/migrations/v2_0_0.rs | 6 ++-- .../credit-manager/tests/test_migration_v2.rs | 10 ++++--- 8 files changed, 35 insertions(+), 72 deletions(-) delete mode 100644 contracts/credit-manager/src/migrations/helpers.rs diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 2b8e0f8cc..171adb5bc 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -5,11 +5,11 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{ to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; -use cw2::{get_contract_version, set_contract_version, ContractVersion}; +use cw2::set_contract_version; use cw721_base::Cw721Contract; use crate::{ - error::{ContractError, ContractError::MigrationError}, + error::ContractError, execute::{burn, mint, update_config}, msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, nft_config::NftConfig, @@ -82,28 +82,17 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { } const FROM_VERSION: &str = "1.0.0"; -const TO_VERSION: &str = "2.0.0"; #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { - let ContractVersion { - contract, - version, - } = get_contract_version(deps.storage)?; - - if CONTRACT_NAME != contract { - return Err(MigrationError { - reason: format!("Wrong contract. Expected: {CONTRACT_NAME}, Found: {contract}"), - }); - } - - if FROM_VERSION != version { - return Err(MigrationError { - reason: format!("Wrong version. Expected: {FROM_VERSION}, Found: {version}"), - }); - } + // make sure we're migrating the correct contract and from the correct version + cw2::assert_contract_version( + deps.as_ref().storage, + &format!("crates.io:{CONTRACT_NAME}"), + FROM_VERSION, + )?; - set_contract_version(deps.storage, CONTRACT_NAME, TO_VERSION)?; + set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; Ok(cw721_base::upgrades::v0_17::migrate::(deps)?) } diff --git a/contracts/account-nft/src/error.rs b/contracts/account-nft/src/error.rs index 74600ac72..dd4cbb98f 100644 --- a/contracts/account-nft/src/error.rs +++ b/contracts/account-nft/src/error.rs @@ -21,8 +21,6 @@ pub enum ContractError { #[error("Health contract should be added to config before burns are allowed")] HealthContractNotSet, - #[error("{reason:?}")] - MigrationError { - reason: String, - }, + #[error("{0}")] + Version(#[from] cw2::VersionError), } diff --git a/contracts/account-nft/tests/test_migration.rs b/contracts/account-nft/tests/test_migration.rs index e5727d6d9..71eca7cd4 100644 --- a/contracts/account-nft/tests/test_migration.rs +++ b/contracts/account-nft/tests/test_migration.rs @@ -3,12 +3,12 @@ use cosmwasm_std::{ testing::{mock_dependencies, mock_env, mock_info}, Addr, Empty, Event, }; -use cw2::{get_contract_version, set_contract_version, ContractVersion}; +use cw2::{get_contract_version, set_contract_version, ContractVersion, VersionError}; use cw721_base::{Cw721Contract, Ownership, QueryMsg}; use cw721_base_v16::{ msg::InstantiateMsg as Cw721v16InstantiateMsg, Cw721Contract as Cw721ContractV16, }; -use mars_account_nft::{contract::migrate, error::ContractError::MigrationError}; +use mars_account_nft::{contract::migrate, error::ContractError}; pub mod helpers; @@ -31,10 +31,10 @@ fn invalid_contract_name() { let err = migrate(deps.as_mut(), env, Empty {}).unwrap_err(); assert_eq!( - MigrationError { - reason: "Wrong contract. Expected: mars-account-nft, Found: WRONG_CONTRACT_NAME" - .to_string() - }, + ContractError::Version(VersionError::WrongContract { + expected: "crates.io:mars-account-nft".to_string(), + found: "WRONG_CONTRACT_NAME".to_string() + }), err ); } @@ -45,7 +45,7 @@ fn invalid_contract_version() { let env = mock_env(); let old_contract_version = ContractVersion { - contract: "mars-account-nft".to_string(), + contract: "crates.io:mars-account-nft".to_string(), version: "4.4.5".to_string(), }; @@ -58,9 +58,10 @@ fn invalid_contract_version() { let err = migrate(deps.as_mut(), env, Empty {}).unwrap_err(); assert_eq!( - MigrationError { - reason: "Wrong version. Expected: 1.0.0, Found: 4.4.5".to_string() - }, + ContractError::Version(VersionError::WrongVersion { + expected: "1.0.0".to_string(), + found: "4.4.5".to_string() + }), err ); } @@ -72,7 +73,7 @@ fn proper_migration() { let minter = "nft-minter-abc"; let old_contract_version = ContractVersion { - contract: "mars-account-nft".to_string(), + contract: "crates.io:mars-account-nft".to_string(), version: "1.0.0".to_string(), }; @@ -102,7 +103,7 @@ fn proper_migration() { let res = migrate(deps.as_mut(), env.clone(), Empty {}).unwrap(); let new_contract_version = ContractVersion { - contract: "mars-account-nft".to_string(), + contract: "crates.io:mars-account-nft".to_string(), version: "2.0.0".to_string(), }; assert_eq!(get_contract_version(deps.as_ref().storage).unwrap(), new_contract_version); diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index f34dd43a6..767255790 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -12,7 +12,6 @@ use crate::{ execute::{create_credit_account, dispatch_actions, execute_callback}, instantiate::store_config, migrations, - migrations::helpers::assert_migration_env, query::{ query_accounts, query_all_coin_balances, query_all_debt_shares, query_all_total_debt_shares, query_all_vault_positions, query_config, query_positions, @@ -128,9 +127,6 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> ContractResult { match msg { - MigrateMsg::V1_0_0ToV2_0_0(updates) => { - assert_migration_env(deps.storage, "1.0.0", "2.0.0")?; - migrations::v2_0_0::migrate(deps, env, updates) - } + MigrateMsg::V1_0_0ToV2_0_0(updates) => migrations::v2_0_0::migrate(deps, env, updates), } } diff --git a/contracts/credit-manager/src/migrations/helpers.rs b/contracts/credit-manager/src/migrations/helpers.rs deleted file mode 100644 index 012d83798..000000000 --- a/contracts/credit-manager/src/migrations/helpers.rs +++ /dev/null @@ -1,24 +0,0 @@ -use cosmwasm_std::Storage; -use cw2::{assert_contract_version, VersionError::WrongVersion}; -use mars_rover::error::{ContractError::Version, ContractResult}; - -use crate::contract::{CONTRACT_NAME, CONTRACT_VERSION}; - -pub fn assert_migration_env( - storage: &dyn Storage, - old_version: &str, - new_version: &str, -) -> ContractResult<()> { - // Assert contract name & from-version is correct - assert_contract_version(storage, CONTRACT_NAME, old_version)?; - - // Assert to-version is correct - if CONTRACT_VERSION != new_version { - return Err(Version(WrongVersion { - expected: new_version.to_string(), - found: CONTRACT_VERSION.to_string(), - })); - } - - Ok(()) -} diff --git a/contracts/credit-manager/src/migrations/mod.rs b/contracts/credit-manager/src/migrations/mod.rs index 4ae0ec13a..7592b6f12 100644 --- a/contracts/credit-manager/src/migrations/mod.rs +++ b/contracts/credit-manager/src/migrations/mod.rs @@ -1,2 +1 @@ -pub mod helpers; pub mod v2_0_0; diff --git a/contracts/credit-manager/src/migrations/v2_0_0.rs b/contracts/credit-manager/src/migrations/v2_0_0.rs index facb15b1f..63bd13c37 100644 --- a/contracts/credit-manager/src/migrations/v2_0_0.rs +++ b/contracts/credit-manager/src/migrations/v2_0_0.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{DepsMut, Env, Response}; -use cw2::set_contract_version; +use cw2::{assert_contract_version, set_contract_version}; use mars_owner::OwnerInit; use mars_rover::{error::ContractResult, msg::migrate::V2Updates}; @@ -36,6 +36,8 @@ pub mod v1_owner { } pub fn migrate(deps: DepsMut, env: Env, updates: V2Updates) -> ContractResult { + assert_contract_version(deps.storage, &format!("crates.io:{CONTRACT_NAME}"), FROM_VERSION)?; + HEALTH_CONTRACT.save(deps.storage, &updates.health_contract.check(deps.api)?)?; PARAMS.save(deps.storage, &updates.params.check(deps.api)?)?; INCENTIVES.save(deps.storage, &updates.incentives.check(deps.api, env.contract.address)?)?; @@ -54,7 +56,7 @@ pub fn migrate(deps: DepsMut, env: Env, updates: V2Updates) -> ContractResult Date: Mon, 28 Aug 2023 07:54:21 +0200 Subject: [PATCH 200/218] MP-3300. Oak no. 9. (#182) Allow to exit vault even if vault no longer whitelisted. --- contracts/credit-manager/src/vault/exit.rs | 8 +- .../credit-manager/src/vault/exit_unlocked.rs | 8 +- .../credit-manager/tests/test_vault_exit.rs | 108 +++++++++++++---- .../tests/test_vault_exit_unlocked.rs | 112 ++++++++++++++---- 4 files changed, 178 insertions(+), 58 deletions(-) diff --git a/contracts/credit-manager/src/vault/exit.rs b/contracts/credit-manager/src/vault/exit.rs index 6fffdd92c..e1b134077 100644 --- a/contracts/credit-manager/src/vault/exit.rs +++ b/contracts/credit-manager/src/vault/exit.rs @@ -8,19 +8,15 @@ use mars_rover::{ }, }; -use crate::vault::utils::{ - assert_vault_is_whitelisted, query_withdraw_denom_balance, update_vault_position, -}; +use crate::vault::utils::{query_withdraw_denom_balance, update_vault_position}; pub fn exit_vault( - mut deps: DepsMut, + deps: DepsMut, env: Env, account_id: &str, vault: Vault, amount: Uint128, ) -> ContractResult { - assert_vault_is_whitelisted(&mut deps, &vault)?; - // Force indicates that the vault is one with a required lockup that needs to be broken // In this case, we'll need to withdraw from the locked bucket update_vault_position( diff --git a/contracts/credit-manager/src/vault/exit_unlocked.rs b/contracts/credit-manager/src/vault/exit_unlocked.rs index baf3e63d8..de1a43fbf 100644 --- a/contracts/credit-manager/src/vault/exit_unlocked.rs +++ b/contracts/credit-manager/src/vault/exit_unlocked.rs @@ -11,20 +11,16 @@ use mars_rover::{ use crate::{ state::VAULT_POSITIONS, - vault::utils::{ - assert_vault_is_whitelisted, query_withdraw_denom_balance, update_vault_position, - }, + vault::utils::{query_withdraw_denom_balance, update_vault_position}, }; pub fn exit_vault_unlocked( - mut deps: DepsMut, + deps: DepsMut, env: Env, account_id: &str, vault: Vault, position_id: u64, ) -> ContractResult { - assert_vault_is_whitelisted(&mut deps, &vault)?; - let vault_position = VAULT_POSITIONS.load(deps.storage, (account_id, vault.address.clone()))?; let matching_unlock = vault_position .get_unlocking_position(position_id) diff --git a/contracts/credit-manager/tests/test_vault_exit.rs b/contracts/credit-manager/tests/test_vault_exit.rs index 1568c5b08..847c62501 100644 --- a/contracts/credit-manager/tests/test_vault_exit.rs +++ b/contracts/credit-manager/tests/test_vault_exit.rs @@ -1,11 +1,9 @@ use cosmwasm_std::{coin, Addr, Coin, OverflowError, OverflowOperation::Sub, Uint128}; use mars_mock_vault::contract::STARTING_VAULT_SHARES; +use mars_params::msg::VaultConfigUpdate; use mars_rover::{ adapters::vault::VaultBase, - error::{ - ContractError, - ContractError::{NotTokenOwner, NotWhitelisted}, - }, + error::{ContractError, ContractError::NotTokenOwner}, msg::execute::Action::{Deposit, EnterVault, ExitVault}, }; @@ -43,26 +41,6 @@ fn only_owner_of_token_can_withdraw_from_vault() { ) } -#[test] -fn can_only_take_action_on_whitelisted_vaults() { - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().build().unwrap(); - - let account_id = mock.create_credit_account(&user).unwrap(); - - let res = mock.update_credit_account( - &account_id, - &user, - vec![ExitVault { - vault: VaultBase::new("not_allowed_vault".to_string()), - amount: STARTING_VAULT_SHARES, - }], - &[], - ); - - assert_err(res, NotWhitelisted("not_allowed_vault".to_string())) -} - #[test] fn no_unlocked_vault_coins_to_withdraw() { let uatom = uatom_info(); @@ -183,6 +161,88 @@ fn withdraw_with_unlocked_vault_coins() { assert_eq!(Uint128::zero(), lp_balance.amount); } +#[test] +fn exit_vault_if_vault_is_no_longer_whitelisted() { + let lp_token = lp_token_info(); + let leverage_vault = unlocked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .set_params(&[lp_token.clone()]) + .vault_configs(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault: vault.clone(), + coin: lp_token.to_action_coin(100), + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + // Assert token's position + let res = mock.query_positions(&account_id); + assert_eq!(res.vaults.len(), 1); + let v = res.vaults.first().unwrap(); + assert_eq!(v.amount.unlocked(), STARTING_VAULT_SHARES); + let lp = get_coin(&lp_token.denom, &res.deposits); + assert_eq!(lp.amount, Uint128::from(100u128)); + + // Assert Rover's totals + let lp = mock.query_balance(&mock.rover, &lp_token.denom); + assert_eq!(lp.amount, Uint128::from(100u128)); + + // Assert Rover has the vault tokens + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.vault_token_denom); + assert_eq!(STARTING_VAULT_SHARES, lp_balance.amount); + + // Blacklist vault + let mut config = mock.query_vault_params(&vault.address); + config.whitelisted = false; + mock.update_vault_params(VaultConfigUpdate::AddOrUpdate { + config: config.into(), + }); + + mock.update_credit_account( + &account_id, + &user, + vec![ExitVault { + vault, + amount: STARTING_VAULT_SHARES, + }], + &[], + ) + .unwrap(); + + // Assert token's updated position + let res = mock.query_positions(&account_id); + assert_eq!(res.vaults.len(), 0); + let lp = get_coin(&lp_token.denom, &res.deposits); + assert_eq!(lp.amount, Uint128::from(200u128)); + + // Assert Rover indeed has those on hand in the bank + let lp = mock.query_balance(&mock.rover, &lp_token.denom); + assert_eq!(lp.amount, Uint128::from(200u128)); + + // Assert Rover does not have the vault tokens anymore + let lp_balance = mock.query_balance(&mock.rover, &leverage_vault.vault_token_denom); + assert_eq!(Uint128::zero(), lp_balance.amount); +} + fn get_coin(denom: &str, coins: &[Coin]) -> Coin { coins.iter().find(|cv| cv.denom == denom).unwrap().clone() } diff --git a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs index 49bdca4b9..a0160c461 100644 --- a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs +++ b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs @@ -1,8 +1,8 @@ use cosmwasm_std::{Addr, Uint128}; use cw_utils::Duration; use mars_mock_vault::contract::STARTING_VAULT_SHARES; +use mars_params::msg::VaultConfigUpdate; use mars_rover::{ - adapters::vault::VaultUnchecked, error::ContractError, msg::{ execute::Action::{Deposit, EnterVault, ExitVaultUnlocked, RequestVaultUnlock}, @@ -47,27 +47,6 @@ fn only_owner_can_withdraw_unlocked_for_account() { ); } -#[test] -fn can_only_take_action_on_whitelisted_vaults() { - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().build().unwrap(); - - let vault = VaultUnchecked::new("xvault".to_string()); - let account_id = mock.create_credit_account(&user).unwrap(); - - let res = mock.update_credit_account( - &account_id, - &user, - vec![ExitVaultUnlocked { - id: 234, - vault, - }], - &[], - ); - - assert_err(res, ContractError::NotWhitelisted("xvault".to_string())); -} - #[test] fn not_owner_of_unlocking_position() { let lp_token = lp_token_info(); @@ -410,6 +389,95 @@ fn withdraw_unlock_success_block_expiring() { assert_eq!(lp.amount, Uint128::from(200u128)); } +#[test] +fn exit_vault_if_vault_is_no_longer_whitelisted() { + let lp_token = lp_token_info(); + let leverage_vault = generate_mock_vault(Some(Duration::Height(100_000))); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .set_params(&[lp_token.clone()]) + .vault_configs(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let vault = mock.get_vault(&leverage_vault); + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(200)), + EnterVault { + vault: vault.clone(), + coin: lp_token.to_action_coin(200), + }, + RequestVaultUnlock { + vault: vault.clone(), + amount: STARTING_VAULT_SHARES, + }, + ], + &[lp_token.to_coin(200)], + ) + .unwrap(); + + let Positions { + deposits, + .. + } = mock.query_positions(&account_id); + assert_eq!(deposits.len(), 0); + + mock.app.update_block(|block| { + if let Duration::Height(h) = leverage_vault.lockup.unwrap() { + block.height += h; + block.time = block.time.plus_seconds(h * 6); + } + }); + + let positions = mock.query_positions(&account_id); + let lockup_id = get_lockup_id(&positions); + + // Blacklist vault + let mut config = mock.query_vault_params(&vault.address); + config.whitelisted = false; + mock.update_vault_params(VaultConfigUpdate::AddOrUpdate { + config: config.into(), + }); + + mock.update_credit_account( + &account_id, + &user, + vec![ExitVaultUnlocked { + id: lockup_id, + vault, + }], + &[], + ) + .unwrap(); + + let Positions { + vaults, + deposits, + .. + } = mock.query_positions(&account_id); + + // Users vault position decrements + assert_eq!(vaults.len(), 0); + + // Users asset position increments + let lp = get_coin(&lp_token.denom, &deposits); + assert_eq!(lp.amount, Uint128::from(200u128)); + + // Assert Rover indeed has those on hand in the bank + let lp = mock.query_balance(&mock.rover, &lp_token.denom); + assert_eq!(lp.amount, Uint128::from(200u128)); +} + fn get_lockup_id(positions: &Positions) -> u64 { positions.vaults.first().unwrap().amount.unlocking().positions().first().unwrap().id } From fda7ee40855046f656a87bb292b688876f23324e Mon Sep 17 00:00:00 2001 From: piobab Date: Mon, 28 Aug 2023 09:20:06 +0200 Subject: [PATCH 201/218] MP-3294. Oak no. 3. (#183) * Include old rover types. * Prove that AccountNft doesn't need migration. * Migrate account nft state. --- Cargo.lock | 159 +++++++++++++----- Cargo.toml | 2 + contracts/account-nft/Cargo.toml | 1 + contracts/account-nft/src/contract.rs | 18 +- contracts/account-nft/src/lib.rs | 1 + contracts/account-nft/src/migrations/mod.rs | 1 + .../account-nft/src/migrations/v2_0_0.rs | 42 +++++ contracts/account-nft/tests/test_migration.rs | 27 ++- .../credit-manager/src/migrations/v2_0_0.rs | 9 +- .../credit-manager/tests/test_migration_v2.rs | 14 +- 10 files changed, 210 insertions(+), 64 deletions(-) create mode 100644 contracts/account-nft/src/migrations/mod.rs create mode 100644 contracts/account-nft/src/migrations/v2_0_0.rs diff --git a/Cargo.lock b/Cargo.lock index c82fcea50..5f776c1d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -646,6 +646,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-vault-standard" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793cd7de3239b1bf187a2a61c8e37d80bb9bd6e354328bfb12070323a435eee1" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 1.0.1", + "schemars", + "serde", +] + [[package]] name = "cw-vault-standard" version = "0.3.1" @@ -1450,7 +1463,8 @@ dependencies = [ "cw721-base 0.16.0", "cw721-base 0.18.0", "mars-mock-rover-health", - "mars-red-bank-types", + "mars-red-bank-types 1.2.0", + "mars-rover 1.0.0", "mars-rover-health-types", "serde_json", "thiserror", @@ -1465,8 +1479,8 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw2 1.1.0", - "mars-owner", - "mars-red-bank-types", + "mars-owner 2.0.0", + "mars-red-bank-types 1.2.0", "thiserror", ] @@ -1481,7 +1495,7 @@ dependencies = [ "cw-paginate", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "cw-vault-standard", + "cw-vault-standard 0.3.1", "cw2 1.1.0", "cw721 0.18.0", "cw721-base 0.18.0", @@ -1493,10 +1507,10 @@ dependencies = [ "mars-mock-oracle", "mars-mock-red-bank", "mars-mock-vault", - "mars-owner", + "mars-owner 2.0.0", "mars-params", - "mars-red-bank-types", - "mars-rover", + "mars-red-bank-types 1.2.0", + "mars-rover 2.0.0", "mars-rover-health", "mars-rover-health-types", "mars-swapper-mock", @@ -1504,6 +1518,15 @@ dependencies = [ "test-case", ] +[[package]] +name = "mars-health" +version = "1.0.0" +source = "git+https://github.com/mars-protocol/v2-fields-of-mars?rev=183e4c5#183e4c58b3de7389d0d7ebcacc248ea6efaa1728" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", +] + [[package]] name = "mars-health" version = "1.2.0" @@ -1511,7 +1534,7 @@ source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a5 dependencies = [ "cosmwasm-std", "mars-params", - "mars-red-bank-types", + "mars-red-bank-types 1.2.0", "thiserror", ] @@ -1522,8 +1545,8 @@ source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a5 dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "mars-red-bank-types", - "mars-utils", + "mars-red-bank-types 1.2.0", + "mars-utils 1.2.0", ] [[package]] @@ -1532,7 +1555,7 @@ version = "1.0.0" source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" dependencies = [ "cosmwasm-std", - "mars-health", + "mars-health 1.2.0", "mars-params", "thiserror", ] @@ -1545,7 +1568,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "mars-rover", + "mars-rover 2.0.0", "mars-rover-health-types", "thiserror", ] @@ -1557,7 +1580,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", - "mars-red-bank-types", + "mars-red-bank-types 1.2.0", ] [[package]] @@ -1567,7 +1590,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", - "mars-red-bank-types", + "mars-red-bank-types 1.2.0", ] [[package]] @@ -1578,7 +1601,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "mars-red-bank-types", + "mars-red-bank-types 1.2.0", ] [[package]] @@ -1599,9 +1622,22 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "cw-vault-standard", - "mars-red-bank-types", - "mars-rover", + "cw-vault-standard 0.3.1", + "mars-red-bank-types 1.2.0", + "mars-rover 2.0.0", + "thiserror", +] + +[[package]] +name = "mars-owner" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd53908ffc561da878ce5ff4f5ec9f25a193af28ec0b6e7c8e6d1a0866d9dfc" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.1.0", + "schemars", "thiserror", ] @@ -1628,9 +1664,22 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw2 1.1.0", "mars-interest-rate", - "mars-owner", - "mars-red-bank-types", - "mars-utils", + "mars-owner 2.0.0", + "mars-red-bank-types 1.2.0", + "mars-utils 1.2.0", + "thiserror", +] + +[[package]] +name = "mars-red-bank-types" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca59bb17daa753c30d3c934e0779736200708cb89a34020fa805b1cb05e7278" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "mars-owner 1.2.0", + "mars-utils 1.0.0", "thiserror", ] @@ -1641,12 +1690,32 @@ source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a5 dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "mars-owner", - "mars-utils", + "mars-owner 2.0.0", + "mars-utils 1.2.0", "strum", "thiserror", ] +[[package]] +name = "mars-rover" +version = "1.0.0" +source = "git+https://github.com/mars-protocol/v2-fields-of-mars?rev=183e4c5#183e4c58b3de7389d0d7ebcacc248ea6efaa1728" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.1.0", + "cw-utils 1.0.1", + "cw-vault-standard 0.2.0", + "cw721 0.16.0", + "cw721-base 0.16.0", + "mars-health 1.0.0", + "mars-owner 1.2.0", + "mars-red-bank-types 1.0.0", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "mars-rover" version = "2.0.0" @@ -1655,15 +1724,15 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "cw-vault-standard", + "cw-vault-standard 0.3.1", "cw2 1.1.0", "cw721 0.18.0", "cw721-base 0.18.0", "mars-account-nft", "mars-liquidation", - "mars-owner", + "mars-owner 2.0.0", "mars-params", - "mars-red-bank-types", + "mars-red-bank-types 1.2.0", "mars-rover-health-types", "mars-v2-zapper-base", "schemars", @@ -1681,15 +1750,15 @@ dependencies = [ "cw-multi-test", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "cw-vault-standard", + "cw-vault-standard 0.3.1", "cw2 1.1.0", "mars-mock-credit-manager", "mars-mock-oracle", "mars-mock-vault", - "mars-owner", + "mars-owner 2.0.0", "mars-params", - "mars-red-bank-types", - "mars-rover", + "mars-red-bank-types 1.2.0", + "mars-rover 2.0.0", "mars-rover-health-computer", "mars-rover-health-types", "thiserror", @@ -1702,9 +1771,9 @@ dependencies = [ "console_error_panic_hook", "cosmwasm-schema", "cosmwasm-std", - "cw-vault-standard", + "cw-vault-standard 0.3.1", "mars-params", - "mars-rover", + "mars-rover 2.0.0", "mars-rover-health-types", "proptest", "schemars", @@ -1720,9 +1789,9 @@ version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "mars-health", - "mars-owner", - "mars-red-bank-types", + "mars-health 1.2.0", + "mars-owner 2.0.0", + "mars-red-bank-types 1.2.0", "schemars", "serde", "serde_json", @@ -1739,8 +1808,8 @@ dependencies = [ "cosmwasm-std", "cw-paginate", "cw-storage-plus 1.1.0", - "mars-owner", - "mars-red-bank-types", + "mars-owner 2.0.0", + "mars-red-bank-types 1.2.0", "schemars", "serde", "thiserror", @@ -1753,7 +1822,17 @@ dependencies = [ "anyhow", "cosmwasm-std", "cw-multi-test", - "mars-red-bank-types", + "mars-red-bank-types 1.2.0", +] + +[[package]] +name = "mars-utils" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bae572eda20842ade4bf8ab09ce0856cae5cff89dbeb7c51e9123489e48256" +dependencies = [ + "cosmwasm-std", + "thiserror", ] [[package]] @@ -1786,8 +1865,8 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "mars-red-bank-types", - "mars-rover", + "mars-red-bank-types 1.2.0", + "mars-rover 2.0.0", "mars-v2-zapper-base", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index cfa3cdd0f..08caa8d8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,8 @@ mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank" mars-rover-health-computer = { version = "2.0.0", path = "./packages/health-computer" } mars-rover-health-types = { version = "2.0.0", path = "./packages/health-types" } mars-rover = { version = "2.0.0", path = "./packages/rover" } +# Old rover types used for migration (https://github.com/mars-protocol/v2-fields-of-mars/releases/tag/v1.0.0) +mars-rover-old = { package = "mars-rover", git = "https://github.com/mars-protocol/v2-fields-of-mars", rev = "183e4c5" } # contracts mars-address-provider = { git = "https://github.com/mars-protocol/red-bank", rev = "6b5f55e", features = ["library"] } diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 7247c0cd4..3b4338556 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -27,6 +27,7 @@ cw721-base = { workspace = true } cw-storage-plus = { workspace = true } mars-red-bank-types = { workspace = true } mars-rover-health-types = { workspace = true } +mars-rover-old = { workspace = true } thiserror = { workspace = true } [dev-dependencies] diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 171adb5bc..a77d10867 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -11,14 +11,15 @@ use cw721_base::Cw721Contract; use crate::{ error::ContractError, execute::{burn, mint, update_config}, + migrations, msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, nft_config::NftConfig, query::{query_config, query_next_id}, state::{CONFIG, NEXT_ID}, }; -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); // Extending CW721 base contract pub type Parent<'a> = Cw721Contract<'a, Empty, Empty, Empty, Empty>; @@ -81,18 +82,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { } } -const FROM_VERSION: &str = "1.0.0"; - #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { - // make sure we're migrating the correct contract and from the correct version - cw2::assert_contract_version( - deps.as_ref().storage, - &format!("crates.io:{CONTRACT_NAME}"), - FROM_VERSION, - )?; - - set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; - - Ok(cw721_base::upgrades::v0_17::migrate::(deps)?) + migrations::v2_0_0::migrate(deps) } diff --git a/contracts/account-nft/src/lib.rs b/contracts/account-nft/src/lib.rs index 5a129b1c2..0da54de7f 100644 --- a/contracts/account-nft/src/lib.rs +++ b/contracts/account-nft/src/lib.rs @@ -1,6 +1,7 @@ pub mod contract; pub mod error; pub mod execute; +pub mod migrations; pub mod msg; pub mod nft_config; pub mod query; diff --git a/contracts/account-nft/src/migrations/mod.rs b/contracts/account-nft/src/migrations/mod.rs new file mode 100644 index 000000000..7592b6f12 --- /dev/null +++ b/contracts/account-nft/src/migrations/mod.rs @@ -0,0 +1 @@ +pub mod v2_0_0; diff --git a/contracts/account-nft/src/migrations/v2_0_0.rs b/contracts/account-nft/src/migrations/v2_0_0.rs new file mode 100644 index 000000000..05e1249f3 --- /dev/null +++ b/contracts/account-nft/src/migrations/v2_0_0.rs @@ -0,0 +1,42 @@ +use cosmwasm_std::{DepsMut, Empty, Response}; +use cw2::set_contract_version; + +use crate::{ + contract::{CONTRACT_NAME, CONTRACT_VERSION}, + error::ContractError, + nft_config::NftConfig, + state::CONFIG, +}; + +const FROM_VERSION: &str = "1.0.0"; + +pub mod v1_state { + use cw_storage_plus::Item; + use mars_rover_old::adapters::account_nft::NftConfig; + + pub const CONFIG: Item = Item::new("config"); +} + +pub fn migrate(deps: DepsMut) -> Result { + // make sure we're migrating the correct contract and from the correct version + cw2::assert_contract_version( + deps.as_ref().storage, + &format!("crates.io:{CONTRACT_NAME}"), + FROM_VERSION, + )?; + + // CONFIG updated, re-initializing + let old_config_state = v1_state::CONFIG.load(deps.storage)?; + v1_state::CONFIG.remove(deps.storage); + CONFIG.save( + deps.storage, + &NftConfig { + max_value_for_burn: old_config_state.max_value_for_burn, + health_contract_addr: None, // this can be updated via update_config + }, + )?; + + set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; + + Ok(cw721_base::upgrades::v0_17::migrate::(deps)?) +} diff --git a/contracts/account-nft/tests/test_migration.rs b/contracts/account-nft/tests/test_migration.rs index 71eca7cd4..d30791819 100644 --- a/contracts/account-nft/tests/test_migration.rs +++ b/contracts/account-nft/tests/test_migration.rs @@ -1,14 +1,17 @@ use cosmwasm_std::{ attr, testing::{mock_dependencies, mock_env, mock_info}, - Addr, Empty, Event, + Addr, Empty, Event, Uint128, }; use cw2::{get_contract_version, set_contract_version, ContractVersion, VersionError}; use cw721_base::{Cw721Contract, Ownership, QueryMsg}; use cw721_base_v16::{ msg::InstantiateMsg as Cw721v16InstantiateMsg, Cw721Contract as Cw721ContractV16, }; -use mars_account_nft::{contract::migrate, error::ContractError}; +use mars_account_nft::{ + contract::migrate, error::ContractError, migrations::v2_0_0::v1_state, nft_config::NftConfig, + state::CONFIG, +}; pub mod helpers; @@ -98,6 +101,17 @@ fn proper_migration() { ) .unwrap(); + let old_max_value_for_burn = Uint128::new(1234u128); + v1_state::CONFIG + .save( + deps.as_mut().storage, + &mars_rover_old::adapters::account_nft::NftConfig { + max_value_for_burn: old_max_value_for_burn, + proposed_new_minter: Some(Addr::unchecked("minter_1234")), + }, + ) + .unwrap(); + assert_eq!(get_contract_version(deps.as_ref().storage).unwrap(), old_contract_version); let res = migrate(deps.as_mut(), env.clone(), Empty {}).unwrap(); @@ -133,4 +147,13 @@ fn proper_migration() { assert_eq!(ownership.owner, Some(Addr::unchecked(minter))); assert_eq!(ownership.pending_owner, None); assert_eq!(ownership.pending_expiry, None); + + let config = CONFIG.load(deps.as_ref().storage).unwrap(); + assert_eq!( + config, + NftConfig { + max_value_for_burn: old_max_value_for_burn, + health_contract_addr: None + } + ); } diff --git a/contracts/credit-manager/src/migrations/v2_0_0.rs b/contracts/credit-manager/src/migrations/v2_0_0.rs index 63bd13c37..a66823c3b 100644 --- a/contracts/credit-manager/src/migrations/v2_0_0.rs +++ b/contracts/credit-manager/src/migrations/v2_0_0.rs @@ -11,11 +11,12 @@ use crate::{ const FROM_VERSION: &str = "1.0.0"; /// Taken from original Owner package version: https://github.com/mars-protocol/owner/blob/e807c6b12511987577645c8bad68cc7bd6da5398/src/owner.rs#L158 -pub mod v1_owner { +pub mod v1_state { use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; use cw_storage_plus::Item; + pub const ACCOUNT_NFT: Item = Item::new("account_nft"); pub const OWNER: Item = Item::new("owner"); #[cw_serde] @@ -45,9 +46,9 @@ pub fn migrate(deps: DepsMut, env: Env, updates: V2Updates) -> ContractResult Date: Mon, 28 Aug 2023 13:04:23 -0300 Subject: [PATCH 202/218] [HC] add lends to swap/withdraw actions (#184) * [HC] add lends to swap/withdraw actions * Include lends correctly. Fix tests. * Generate new wasms. --------- Co-authored-by: piobab --- .../health-computer/src/health_computer.rs | 44 +++++++++++------- .../tests/helpers/prop_test_runner_swap.rs | 27 ++++++++--- .../tests/test_max_swap_validation.rs | 6 +-- .../tests/test_max_withdraw.rs | 4 +- .../tests/test_max_withdraw_prop_test.rs | 12 ++++- scripts/health/pkg-node/index_bg.wasm | Bin 248533 -> 248879 bytes scripts/health/pkg-web/index_bg.wasm | Bin 248386 -> 248732 bytes 7 files changed, 63 insertions(+), 30 deletions(-) diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index a3773d4e6..9ffa5070e 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -10,8 +10,7 @@ use mars_rover::msg::query::Positions; use mars_rover_health_types::{ AccountKind, BorrowTarget, Health, HealthError::{ - DenomNotPresent, MissingHLSParams, MissingParams, MissingPrice, MissingVaultConfig, - MissingVaultValues, + MissingHLSParams, MissingParams, MissingPrice, MissingVaultConfig, MissingVaultValues, }, HealthResult, SwapKind, }; @@ -71,12 +70,12 @@ impl HealthComputer { /// Note: This is an estimate. Guarantees to leave account healthy, but in edge cases, /// due to rounding, it may be slightly too conservative. pub fn max_withdraw_amount_estimate(&self, withdraw_denom: &str) -> HealthResult { - let withdraw_coin = self - .positions - .deposits - .iter() - .find(|c| c.denom == withdraw_denom) - .ok_or(DenomNotPresent(withdraw_denom.to_string()))?; + // Both deposits and lends should be considered, as the funds can automatically be un-lent and + // and also used to withdraw. + let withdraw_coin = self.get_coin_from_deposits_and_lends(withdraw_denom)?; + if withdraw_coin.amount.is_zero() { + return Ok(Uint128::zero()); + }; let params = self .denoms_data @@ -141,12 +140,12 @@ impl HealthComputer { to_denom: &str, kind: &SwapKind, ) -> HealthResult { - let from_coin = self - .positions - .deposits - .iter() - .find(|c| c.denom == *from_denom) - .ok_or(DenomNotPresent(from_denom.to_string()))?; + // Both deposits and lends should be considered, as the funds can automatically be un-lent and + // and also used to swap. + let from_coin = self.get_coin_from_deposits_and_lends(from_denom)?; + if from_coin.amount.is_zero() { + return Ok(Uint128::zero()); + }; // If no debt the total amount deposited can be swapped (only for default swaps) if kind == &SwapKind::Default && self.positions.debts.is_empty() { @@ -218,8 +217,8 @@ impl HealthComputer { let swap_to_ltv_value = from_coin_value.checked_mul_floor(to_ltv)?; let total_max_ltv_adjust_value_after_swap = total_max_ltv_adjusted_value - .checked_sub(swap_from_ltv_value)? - .checked_add(swap_to_ltv_value)?; + .checked_add(swap_to_ltv_value)? + .checked_sub(swap_from_ltv_value)?; // The total swappable amount for margin is represented by the available coin balance + the // the maximum amount that can be borrowed (and then swapped). @@ -549,4 +548,17 @@ impl HealthComputer { .max_loan_to_value), } } + + fn get_coin_from_deposits_and_lends(&self, denom: &str) -> HealthResult { + let deposited_coin = self.positions.deposits.iter().find(|c| c.denom == denom); + let deposited_amount = deposited_coin.unwrap_or(&Coin::default()).amount; + + let lent_coin = self.positions.lends.iter().find(|c| c.denom == denom); + let lent_amount = lent_coin.unwrap_or(&Coin::default()).amount; + + Ok(Coin { + denom: denom.to_string(), + amount: deposited_amount.checked_add(lent_amount)?, + }) + } } diff --git a/packages/health-computer/tests/helpers/prop_test_runner_swap.rs b/packages/health-computer/tests/helpers/prop_test_runner_swap.rs index 633cc869f..e8b9e2ac9 100644 --- a/packages/health-computer/tests/helpers/prop_test_runner_swap.rs +++ b/packages/health-computer/tests/helpers/prop_test_runner_swap.rs @@ -61,20 +61,33 @@ fn add_swap( ) -> StdResult { let mut new_h = h.clone(); - let from_coin_index = + let from_deposit_coin_index = new_h.positions.deposits.iter().position(|c| c.denom == from_denom).unwrap(); + let from_lend_coin_index = new_h.positions.lends.iter().position(|c| c.denom == from_denom); - let from_coin = new_h.positions.deposits.get_mut(from_coin_index).unwrap(); + let from_deposit_coin = new_h.positions.deposits.get_mut(from_deposit_coin_index).unwrap(); + let mut from_lend_coin = &mut Coin::default(); + if let Some(from_lend_coin_index) = from_lend_coin_index { + from_lend_coin = new_h.positions.lends.get_mut(from_lend_coin_index).unwrap(); + } let from_price = new_h.denoms_data.prices.get(from_denom).unwrap(); let to_price = new_h.denoms_data.prices.get(to_denom).unwrap(); - // Subtract the amount from current deposited balance - if amount < from_coin.amount { - from_coin.amount -= amount; + // Subtract the amount from current deposited and lent balance + let total_amount = from_deposit_coin.amount + from_lend_coin.amount; + if amount < from_deposit_coin.amount { + from_deposit_coin.amount -= amount; + } else if amount < total_amount { + let remaining_from_lends = amount - from_deposit_coin.amount; + from_deposit_coin.amount = Uint128::zero(); + from_lend_coin.amount -= remaining_from_lends; } else { // If there the amount is larger than the balance of the coin, we need to add the remaining to the debts. - let debt_amount = amount - from_coin.amount; - new_h.positions.deposits.remove(from_coin_index); + let debt_amount = amount - total_amount; + new_h.positions.deposits.remove(from_deposit_coin_index); + if let Some(idx) = from_lend_coin_index { + new_h.positions.lends.remove(idx); + } if let Some(debt_coin_index) = new_h.positions.debts.iter().position(|c| c.denom == from_denom) diff --git a/packages/health-computer/tests/test_max_swap_validation.rs b/packages/health-computer/tests/test_max_swap_validation.rs index 603c3ea5d..8fd963c0a 100644 --- a/packages/health-computer/tests/test_max_swap_validation.rs +++ b/packages/health-computer/tests/test_max_swap_validation.rs @@ -136,9 +136,9 @@ fn deposit_not_present() { vaults_data, }; - let err: HealthError = - h.max_swap_amount_estimate("xyz", &udai.denom, &SwapKind::Default).unwrap_err(); - assert_eq!(err, HealthError::DenomNotPresent("xyz".to_string())); + let max_withdraw_amount = + h.max_swap_amount_estimate("xyz", &udai.denom, &SwapKind::Default).unwrap(); + assert_eq!(max_withdraw_amount, Uint128::zero()); } #[test] diff --git a/packages/health-computer/tests/test_max_withdraw.rs b/packages/health-computer/tests/test_max_withdraw.rs index fcef8dbeb..12ee04491 100644 --- a/packages/health-computer/tests/test_max_withdraw.rs +++ b/packages/health-computer/tests/test_max_withdraw.rs @@ -132,8 +132,8 @@ fn deposit_not_present() { vaults_data, }; - let err: HealthError = h.max_withdraw_amount_estimate("xyz").unwrap_err(); - assert_eq!(err, HealthError::DenomNotPresent("xyz".to_string())); + let max_withdraw_amount = h.max_withdraw_amount_estimate("xyz").unwrap(); + assert_eq!(max_withdraw_amount, Uint128::zero()); } #[test] diff --git a/packages/health-computer/tests/test_max_withdraw_prop_test.rs b/packages/health-computer/tests/test_max_withdraw_prop_test.rs index 9b3f5342d..50817b546 100644 --- a/packages/health-computer/tests/test_max_withdraw_prop_test.rs +++ b/packages/health-computer/tests/test_max_withdraw_prop_test.rs @@ -50,8 +50,16 @@ fn withdraw_amount_renders_healthy_max_ltv() { fn decrement(h: &HealthComputer, deposit: &str, withdraw: Uint128) -> StdResult { let mut new_h = h.clone(); - let matched_coin = + let matched_deposit_coin = new_h.positions.deposits.iter_mut().find(|coin| coin.denom == deposit).unwrap(); - matched_coin.amount = matched_coin.amount.checked_sub(withdraw)?; + if matched_deposit_coin.amount >= withdraw { + matched_deposit_coin.amount = matched_deposit_coin.amount.checked_sub(withdraw)?; + } else { + let remaining_from_lends = withdraw - matched_deposit_coin.amount; + matched_deposit_coin.amount = Uint128::zero(); + let matched_lend_coin = + new_h.positions.lends.iter_mut().find(|coin| coin.denom == deposit).unwrap(); + matched_lend_coin.amount = matched_lend_coin.amount.checked_sub(remaining_from_lends)?; + } Ok(new_h) } diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index 340a3c0d2a2bd0e0a6add2c62c1db68e9f835b62..1c0d7fd8e3ab1958d0b5d1ba3ae78be2f9cf2e00 100644 GIT binary patch delta 59509 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�qpESvUPDRWCiZ6}?jMn4B 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 F`yZ(C*%<%; delta 58587 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 diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm index 14db4831fd8abd55d0862cbcef8a353529981c4b..3fdfadf4838f38454a51239e4e37734a79b2538e 100644 GIT binary patch delta 59297 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ⅈ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 N)Q%1js}9nZ{|5Z3h^YVo delta 58683 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

6Ae7wR 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 Date: Wed, 30 Aug 2023 10:08:18 -0300 Subject: [PATCH 203/218] [HC] enable margin swap when from_coin.amount is 0 (#186) * [HC] enable margin swap when from_coin.amount is 0 * [HC] run fmt * [HC] update wasm files --- .../health-computer/src/health_computer.rs | 3 --- scripts/health/pkg-node/index_bg.wasm | Bin 248879 -> 248664 bytes scripts/health/pkg-web/index_bg.wasm | Bin 248732 -> 248517 bytes 3 files changed, 3 deletions(-) diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index 9ffa5070e..1b4a67816 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -143,9 +143,6 @@ impl HealthComputer { // Both deposits and lends should be considered, as the funds can automatically be un-lent and // and also used to swap. let from_coin = self.get_coin_from_deposits_and_lends(from_denom)?; - if from_coin.amount.is_zero() { - return Ok(Uint128::zero()); - }; // If no debt the total amount deposited can be swapped (only for default swaps) if kind == &SwapKind::Default && self.positions.debts.is_empty() { diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index 1c0d7fd8e3ab1958d0b5d1ba3ae78be2f9cf2e00..c6ba8ff24733e893adfc11869886f2bf62fc2562 100644 GIT binary patch delta 27347 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 diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm index 3fdfadf4838f38454a51239e4e37734a79b2538e..3a42ecaa7a485613c2a389bebc003348f213bfa2 100644 GIT binary patch delta 27286 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 EKeac{NdN!< From 5325816847d626563d3bd6c3f636e371be2f34eb Mon Sep 17 00:00:00 2001 From: piobab Date: Thu, 31 Aug 2023 21:45:27 +0200 Subject: [PATCH 204/218] MP-3306. Oak no. 15. (#188) Assert HLS rules only if account kind = HLS. --- contracts/credit-manager/src/execute.rs | 20 +++++++++------ contracts/credit-manager/src/hls.rs | 25 ++++++------------- packages/rover/src/msg/execute.rs | 2 +- .../mars-credit-manager.json | 4 +-- .../MarsCreditManager.types.ts | 2 +- 5 files changed, 23 insertions(+), 30 deletions(-) diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 695a58403..48eb5cf7d 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -17,7 +17,7 @@ use crate::{ claim_rewards::claim_rewards, deposit::{assert_deposit_caps, deposit}, health::{assert_max_ltv, query_health_state}, - hls::assert_account_requirements, + hls::assert_hls_rules, lend::lend, liquidate::assert_not_self_liquidation, liquidate_deposit::liquidate_deposit, @@ -28,7 +28,7 @@ use crate::{ state::{ACCOUNT_KINDS, ACCOUNT_NFT, REENTRANCY_GUARD}, swap::swap_exact_in, update_coin_balances::{update_coin_balance, update_coin_balance_after_vault_liquidation}, - utils::assert_is_token_owner, + utils::{assert_is_token_owner, get_account_kind}, vault::{ enter_vault, exit_vault, exit_vault_unlocked, liquidate_vault, request_vault_unlock, update_vault_coin_balance, @@ -250,11 +250,15 @@ pub fn dispatch_actions( return Err(ContractError::ExtraFundsReceived(received_coins)); } - callbacks.extend([ - // Ensures the account state abides by the rules of the account kind - CallbackMsg::AssertAccountReqs { + // Ensures the account state abides by the rules of the HLS account kind + let kind = get_account_kind(deps.storage, account_id)?; + if kind == AccountKind::HighLeveredStrategy { + callbacks.push(CallbackMsg::AssertHlsRules { account_id: account_id.to_string(), - }, + }); + } + + callbacks.extend([ // After user selected actions, we assert LTV is either: // - Healthy, if prior to actions MaxLTV health factor >= 1 or None // - Not further weakened, if prior to actions MaxLTV health factor < 1 @@ -434,9 +438,9 @@ pub fn execute_callback( CallbackMsg::RefundAllCoinBalances { account_id, } => refund_coin_balances(deps, env, &account_id), - CallbackMsg::AssertAccountReqs { + CallbackMsg::AssertHlsRules { account_id, - } => assert_account_requirements(deps, account_id), + } => assert_hls_rules(deps.as_ref(), &account_id), CallbackMsg::RemoveReentrancyGuard {} => REENTRANCY_GUARD.try_unlock(deps.storage), } } diff --git a/contracts/credit-manager/src/hls.rs b/contracts/credit-manager/src/hls.rs index 0f0519f91..a32444f13 100644 --- a/contracts/credit-manager/src/hls.rs +++ b/contracts/credit-manager/src/hls.rs @@ -1,25 +1,11 @@ -use cosmwasm_std::{Deps, DepsMut, Response}; +use cosmwasm_std::{Deps, Response}; use mars_params::types::hls::HlsAssetType; use mars_rover::error::{ContractError, ContractResult}; use mars_rover_health_types::AccountKind; -use crate::{query::query_positions, state::PARAMS, utils::get_account_kind}; +use crate::{query::query_positions, state::PARAMS}; -pub fn assert_account_requirements(deps: DepsMut, account_id: String) -> ContractResult { - let kind = get_account_kind(deps.storage, &account_id)?; - - match kind { - AccountKind::Default => {} // No restrictions - AccountKind::HighLeveredStrategy => assert_hls_rules(deps.as_ref(), &account_id)?, - } - - Ok(Response::new() - .add_attribute("action", "callback/assert_account_requirements") - .add_attribute("account_id", account_id) - .add_attribute("account_kind", kind.to_string())) -} - -fn assert_hls_rules(deps: Deps, account_id: &str) -> ContractResult<()> { +pub fn assert_hls_rules(deps: Deps, account_id: &str) -> ContractResult { // Rule #1 - There can only be 0 or 1 debt denom in the account let positions = query_positions(deps, account_id)?; @@ -97,5 +83,8 @@ fn assert_hls_rules(deps: Deps, account_id: &str) -> ContractResult<()> { } } - Ok(()) + Ok(Response::new() + .add_attribute("action", "callback/assert_hls_rules") + .add_attribute("account_id", account_id) + .add_attribute("account_kind", AccountKind::HighLeveredStrategy.to_string())) } diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index ef2d68eb8..680e82b31 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -332,7 +332,7 @@ pub enum CallbackMsg { account_id: String, }, /// Ensures that HLS accounts abide by specific rules - AssertAccountReqs { + AssertHlsRules { account_id: String, }, /// At the end of the execution of dispatched actions, this callback removes the guard diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index d0261f1a2..68873c154 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -1332,10 +1332,10 @@ "description": "Ensures that HLS accounts abide by specific rules", "type": "object", "required": [ - "assert_account_reqs" + "assert_hls_rules" ], "properties": { - "assert_account_reqs": { + "assert_hls_rules": { "type": "object", "required": [ "account_id" diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 8f2d0a9a5..6ee7610d2 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -313,7 +313,7 @@ export type CallbackMsg = } } | { - assert_account_reqs: { + assert_hls_rules: { account_id: string } } From 96d14d97d4aa4642ac7df0de038838cdc73e9bfb Mon Sep 17 00:00:00 2001 From: piobab Date: Thu, 31 Aug 2023 21:46:16 +0200 Subject: [PATCH 205/218] MP-3308, Oak no. 17. MP-3309, Oak no. 18. (#189) * Differentiate action for config update. * DRY. --- contracts/credit-manager/src/update_config.rs | 3 ++- contracts/swapper/base/src/contract.rs | 7 +------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index 91955f3d7..c61edda5c 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -138,7 +138,8 @@ pub fn update_nft_config( updates, })?, }); - response = response.add_message(update_config_msg).add_attribute("action", "update_config") + response = + response.add_message(update_config_msg).add_attribute("action", "update_nft_config") } if let Some(action) = ownership { diff --git a/contracts/swapper/base/src/contract.rs b/contracts/swapper/base/src/contract.rs index 21408efc6..94378e500 100644 --- a/contracts/swapper/base/src/contract.rs +++ b/contracts/swapper/base/src/contract.rs @@ -162,12 +162,7 @@ where slippage: Decimal, ) -> ContractResult> { let swap_msg = self - .routes - .load(deps.storage, (coin_in.denom.clone(), denom_out.clone())) - .map_err(|_| ContractError::NoRoute { - from: coin_in.denom.clone(), - to: denom_out.clone(), - })? + .get_route(deps.as_ref(), &coin_in.denom, &denom_out)? .build_exact_in_swap_msg(&deps.querier, &env, &coin_in, slippage)?; // Check balance of result of swapper and send back result to sender From 05721bbe747a25ae9825b2152212666e9e0ef480 Mon Sep 17 00:00:00 2001 From: piobab Date: Thu, 31 Aug 2023 21:51:06 +0200 Subject: [PATCH 206/218] MP-3296. Oak no. 5. (#191) * Use paginated UserCollateralsV2 query for lents. * Add tests. * Update schema. --- Cargo.lock | 14 +- Cargo.toml | 12 +- .../tests/helpers/mock_entity_info.rs | 18 +++ contracts/credit-manager/tests/test_lend.rs | 74 +++++++++- contracts/mock-red-bank/src/contract.rs | 13 +- contracts/mock-red-bank/src/query.rs | 54 ++++++-- packages/rover/src/adapters/red_bank.rs | 49 ++++--- .../mars-mock-red-bank.json | 129 +++++++++++++++++- .../MarsMockRedBank.client.ts | 34 +++++ .../MarsMockRedBank.message-composer.ts | 2 + .../MarsMockRedBank.react-query.ts | 36 +++++ .../MarsMockRedBank.types.ts | 15 ++ 12 files changed, 401 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f776c1d8..e772cc9f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1473,7 +1473,7 @@ dependencies = [ [[package]] name = "mars-address-provider" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" +source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" dependencies = [ "bech32", "cosmwasm-std", @@ -1530,7 +1530,7 @@ dependencies = [ [[package]] name = "mars-health" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" +source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" dependencies = [ "cosmwasm-std", "mars-params", @@ -1541,7 +1541,7 @@ dependencies = [ [[package]] name = "mars-interest-rate" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" +source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1552,7 +1552,7 @@ dependencies = [ [[package]] name = "mars-liquidation" version = "1.0.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" +source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" dependencies = [ "cosmwasm-std", "mars-health 1.2.0", @@ -1657,7 +1657,7 @@ dependencies = [ [[package]] name = "mars-params" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" +source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1686,7 +1686,7 @@ dependencies = [ [[package]] name = "mars-red-bank-types" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" +source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1838,7 +1838,7 @@ dependencies = [ [[package]] name = "mars-utils" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=6b5f55e#6b5f55efc9a58b75e54b619a219c304efe3e222c" +source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" dependencies = [ "cosmwasm-std", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 08caa8d8b..949ca89b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,11 +64,11 @@ tsify = "0.4.5" wasm-bindgen = "0.2.87" # mars packages -mars-health = { git = "https://github.com/mars-protocol/red-bank", rev = "6b5f55e" } -mars-liquidation = { git = "https://github.com/mars-protocol/red-bank", rev = "6b5f55e" } -mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "6b5f55e" } +mars-health = { git = "https://github.com/mars-protocol/red-bank", rev = "05cd222" } +mars-liquidation = { git = "https://github.com/mars-protocol/red-bank", rev = "05cd222" } +mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "05cd222" } mars-owner = { version = "2.0.0", features = ["emergency-owner"] } -mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank", rev = "6b5f55e" } +mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank", rev = "05cd222" } mars-rover-health-computer = { version = "2.0.0", path = "./packages/health-computer" } mars-rover-health-types = { version = "2.0.0", path = "./packages/health-types" } mars-rover = { version = "2.0.0", path = "./packages/rover" } @@ -76,9 +76,9 @@ mars-rover = { version = "2.0.0", path = "./packages/rover" } mars-rover-old = { package = "mars-rover", git = "https://github.com/mars-protocol/v2-fields-of-mars", rev = "183e4c5" } # contracts -mars-address-provider = { git = "https://github.com/mars-protocol/red-bank", rev = "6b5f55e", features = ["library"] } +mars-address-provider = { git = "https://github.com/mars-protocol/red-bank", rev = "05cd222", features = ["library"] } mars-account-nft = { version = "2.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-params = { git = "https://github.com/mars-protocol/red-bank", rev = "6b5f55e", features = ["library"] } +mars-params = { git = "https://github.com/mars-protocol/red-bank", rev = "05cd222", features = ["library"] } mars-rover-health = { version = "2.0.0", path = "./contracts/health", features = ["library"] } mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base", features = ["library"] } mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base", features = ["library"] } diff --git a/contracts/credit-manager/tests/helpers/mock_entity_info.rs b/contracts/credit-manager/tests/helpers/mock_entity_info.rs index fa7b18c7a..bb492ebcf 100644 --- a/contracts/credit-manager/tests/helpers/mock_entity_info.rs +++ b/contracts/credit-manager/tests/helpers/mock_entity_info.rs @@ -9,6 +9,24 @@ use mars_params::types::{ use crate::helpers::{CoinInfo, VaultTestInfo}; +pub fn coin_info(denom: &str) -> CoinInfo { + CoinInfo { + denom: denom.to_string(), + price: Decimal::from_atomics(25u128, 2).unwrap(), + max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), + liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), + liquidation_bonus: LiquidationBonus { + starting_lb: Decimal::percent(1u64), + slope: Decimal::from_atomics(2u128, 0).unwrap(), + min_lb: Decimal::percent(2u64), + max_lb: Decimal::percent(10u64), + }, + protocol_liquidation_fee: Decimal::percent(2u64), + whitelisted: true, + hls: None, + } +} + pub fn uosmo_info() -> CoinInfo { CoinInfo { denom: "uosmo".to_string(), diff --git a/contracts/credit-manager/tests/test_lend.rs b/contracts/credit-manager/tests/test_lend.rs index 6904039e1..5f59296a6 100644 --- a/contracts/credit-manager/tests/test_lend.rs +++ b/contracts/credit-manager/tests/test_lend.rs @@ -1,6 +1,6 @@ use std::ops::Add; -use cosmwasm_std::{coin, coins, Addr, OverflowError, OverflowOperation, Uint128}; +use cosmwasm_std::{coin, coins, Addr, Coin, OverflowError, OverflowOperation, Uint128}; use mars_rover::{ error::ContractError, msg::execute::{ @@ -10,7 +10,8 @@ use mars_rover::{ }; use crate::helpers::{ - assert_err, blacklisted_coin, uosmo_info, AccountToFund, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, + assert_err, blacklisted_coin, coin_info, uosmo_info, AccountToFund, MockEnv, + DEFAULT_RED_BANK_COIN_BALANCE, }; pub mod helpers; @@ -292,3 +293,72 @@ fn successful_account_balance_lend() { let red_bank_collateral = mock.query_red_bank_collateral(&account_id, &coin_info.denom); assert_eq!(red_bank_collateral.amount, lent_amount); } + +#[test] +fn query_positions_successfully_with_paginated_lends() { + let coins_info = vec![ + coin_info("coin_1"), + coin_info("coin_2"), + coin_info("coin_123"), + coin_info("coin_4"), + coin_info("coin_5"), + coin_info("coin_11"), + coin_info("coin_7"), + ]; + + let user = Addr::unchecked("user"); + + let funded_amt = 300u128; + + let coins: Vec<_> = coins_info.iter().map(|coin| coin.to_coin(funded_amt)).collect(); + let mut mock = MockEnv::new() + .set_params(&coins_info) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins.clone(), + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 0); + assert_eq!(position.lends.len(), 0); + + for coin in coins.iter() { + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(coin.clone())], + &[coin.clone()], + ) + .unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![Lend(ActionCoin { + denom: coin.denom.clone(), + amount: ActionAmount::AccountBalance, + })], + &[], + ) + .unwrap(); + } + + // Assert deposits decreased + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 0); + + // Assert lends increased + assert_eq!(position.lends.len(), coins.len()); + let expected_amt = funded_amt + 1; // account balance + simulated yield + assert_eq!(position.lends[0].clone(), Coin::new(expected_amt, "coin_1")); + assert_eq!(position.lends[1].clone(), Coin::new(expected_amt, "coin_2")); + assert_eq!(position.lends[2].clone(), Coin::new(expected_amt, "coin_123")); + assert_eq!(position.lends[3].clone(), Coin::new(expected_amt, "coin_4")); + assert_eq!(position.lends[4].clone(), Coin::new(expected_amt, "coin_5")); + assert_eq!(position.lends[5].clone(), Coin::new(expected_amt, "coin_11")); + assert_eq!(position.lends[6].clone(), Coin::new(expected_amt, "coin_7")); +} diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index 8d6bbc617..84151701b 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -7,7 +7,7 @@ use mars_red_bank_types::red_bank; use crate::{ execute::{borrow, deposit, init_asset, repay, withdraw}, - query::{query_collateral, query_collaterals, query_debt, query_market}, + query::{query_collateral, query_collaterals, query_collaterals_v2, query_debt, query_market}, }; #[cfg_attr(not(feature = "library"), entry_point)] @@ -74,8 +74,15 @@ pub fn query(deps: Deps, _env: Env, msg: red_bank::QueryMsg) -> StdResult to_binary(&query_collaterals(deps, user, account_id)?), + start_after, + limit, + } => to_binary(&query_collaterals(deps, user, account_id, start_after, limit)?), + red_bank::QueryMsg::UserCollateralsV2 { + user, + account_id, + start_after, + limit, + } => to_binary(&query_collaterals_v2(deps, user, account_id, start_after, limit)?), _ => unimplemented!("Query not supported!"), } } diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index ad87056da..0b3882c4c 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -1,5 +1,8 @@ use cosmwasm_std::{Deps, StdResult, Uint128}; -use mars_red_bank_types::red_bank::{Market, UserCollateralResponse, UserDebtResponse}; +use mars_red_bank_types::{ + red_bank::{Market, PaginatedUserCollateralResponse, UserCollateralResponse, UserDebtResponse}, + Metadata, +}; use crate::{ helpers::{load_collateral_amount, load_collateral_denoms, load_debt_amount}, @@ -41,22 +44,57 @@ pub fn query_collaterals( deps: Deps, user: String, account_id: Option, + start_after: Option, + limit: Option, ) -> StdResult> { - load_collateral_denoms(deps.storage, &user, &account_id.clone().unwrap_or_default())? - .into_iter() + let res = query_collaterals_v2(deps, user, account_id, start_after, limit)?; + Ok(res.data) +} + +pub fn query_collaterals_v2( + deps: Deps, + user: String, + account_id: Option, + start_after: Option, + limit: Option, +) -> StdResult { + let denoms = + load_collateral_denoms(deps.storage, &user, &account_id.clone().unwrap_or_default())?; + let limit = limit.unwrap_or(5) as usize; // red-bank can have different value as default, we only use it to validate if pagination works as expected + + let (start_index, has_more) = match start_after { + Some(sa) => { + let start_index = denoms.iter().position(|denom| denom == &sa).unwrap_or(denoms.len()); + let has_more = start_index + 1 < denoms.len(); + (start_index + 1, has_more) + } + None => (0, denoms.len() > limit), + }; + + let collaterals = denoms + .iter() + .skip(start_index) + .take(limit) .map(|denom| { - load_collateral_amount( + let amount = load_collateral_amount( deps.storage, &user, &account_id.clone().unwrap_or_default(), - &denom, - ) - .map(|amount| UserCollateralResponse { denom, + )?; + Ok(UserCollateralResponse { + denom: denom.clone(), amount_scaled: Default::default(), amount, enabled: true, }) }) - .collect() + .collect::>>()?; + + Ok(PaginatedUserCollateralResponse { + data: collaterals, + metadata: Metadata { + has_more, + }, + }) } diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index db26dfb8d..e972d3840 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -123,26 +123,41 @@ impl RedBank { querier: &QuerierWrapper, account_id: &str, ) -> StdResult> { - let responses: Vec = - querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: self.addr.to_string(), - msg: to_binary(&red_bank::QueryMsg::UserCollaterals { - user: self.credit_manager.to_string(), - account_id: Some(account_id.to_string()), - start_after: None, - limit: None, - })?, - }))?; - let all_lent_coins = responses - .iter() - .map(|r| Coin { - denom: r.denom.clone(), - amount: r.amount, - }) - .collect(); + let mut start_after = Option::::None; + let mut has_more = true; + let mut all_lent_coins = Vec::new(); + while has_more { + let response = self.query_lents(querier, account_id, start_after, None)?; + for item in response.data { + all_lent_coins.push(Coin { + denom: item.denom, + amount: item.amount, + }); + } + start_after = all_lent_coins.last().map(|item| item.denom.clone()); + has_more = response.metadata.has_more; + } Ok(all_lent_coins) } + fn query_lents( + &self, + querier: &QuerierWrapper, + account_id: &str, + start_after: Option, + limit: Option, + ) -> StdResult { + querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.addr.to_string(), + msg: to_binary(&red_bank::QueryMsg::UserCollateralsV2 { + user: self.credit_manager.to_string(), + account_id: Some(account_id.to_string()), + start_after, + limit, + })?, + })) + } + pub fn query_debt(&self, querier: &QuerierWrapper, denom: &str) -> StdResult { let response: red_bank::UserDebtResponse = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { diff --git a/schemas/mars-mock-red-bank/mars-mock-red-bank.json b/schemas/mars-mock-red-bank/mars-mock-red-bank.json index dcf1921d4..80274d853 100644 --- a/schemas/mars-mock-red-bank/mars-mock-red-bank.json +++ b/schemas/mars-mock-red-bank/mars-mock-red-bank.json @@ -422,7 +422,7 @@ ] }, "slope_1": { - "description": "Slope parameter for interest rate model function when utilization_rate < optimal_utilization_rate", + "description": "Slope parameter for interest rate model function when utilization_rate <= optimal_utilization_rate", "allOf": [ { "$ref": "#/definitions/Decimal" @@ -430,7 +430,7 @@ ] }, "slope_2": { - "description": "Slope parameter for interest rate model function when utilization_rate >= optimal_utilization_rate", + "description": "Slope parameter for interest rate model function when utilization_rate > optimal_utilization_rate", "allOf": [ { "$ref": "#/definitions/Decimal" @@ -790,6 +790,48 @@ }, "additionalProperties": false }, + { + "description": "Get all collateral positions for a user", + "type": "object", + "required": [ + "user_collaterals_v2" + ], + "properties": { + "user_collaterals_v2": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "account_id": { + "type": [ + "string", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Get user position", "type": "object", @@ -1114,7 +1156,7 @@ ] }, "slope_1": { - "description": "Slope parameter for interest rate model function when utilization_rate < optimal_utilization_rate", + "description": "Slope parameter for interest rate model function when utilization_rate <= optimal_utilization_rate", "allOf": [ { "$ref": "#/definitions/Decimal" @@ -1122,7 +1164,7 @@ ] }, "slope_2": { - "description": "Slope parameter for interest rate model function when utilization_rate >= optimal_utilization_rate", + "description": "Slope parameter for interest rate model function when utilization_rate > optimal_utilization_rate", "allOf": [ { "$ref": "#/definitions/Decimal" @@ -1176,7 +1218,7 @@ ] }, "slope_1": { - "description": "Slope parameter for interest rate model function when utilization_rate < optimal_utilization_rate", + "description": "Slope parameter for interest rate model function when utilization_rate <= optimal_utilization_rate", "allOf": [ { "$ref": "#/definitions/Decimal" @@ -1184,7 +1226,7 @@ ] }, "slope_2": { - "description": "Slope parameter for interest rate model function when utilization_rate >= optimal_utilization_rate", + "description": "Slope parameter for interest rate model function when utilization_rate > optimal_utilization_rate", "allOf": [ { "$ref": "#/definitions/Decimal" @@ -1476,6 +1518,81 @@ } } }, + "user_collaterals_v2": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PaginationResponse_for_UserCollateralResponse", + "type": "object", + "required": [ + "data", + "metadata" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/UserCollateralResponse" + } + }, + "metadata": { + "$ref": "#/definitions/Metadata" + } + }, + "additionalProperties": false, + "definitions": { + "Metadata": { + "type": "object", + "required": [ + "has_more" + ], + "properties": { + "has_more": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "UserCollateralResponse": { + "type": "object", + "required": [ + "amount", + "amount_scaled", + "denom", + "enabled" + ], + "properties": { + "amount": { + "description": "Underlying asset amount that is actually deposited at the current block", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "amount_scaled": { + "description": "Scaled collateral amount stored in contract state", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "Asset denom", + "type": "string" + }, + "enabled": { + "description": "Wether the user is using asset as collateral or not", + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, "user_debt": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "UserDebtResponse", diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index 488866643..fc9eea564 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -24,6 +24,8 @@ import { ArrayOfUncollateralizedLoanLimitResponse, UserCollateralResponse, ArrayOfUserCollateralResponse, + PaginationResponseForUserCollateralResponse, + Metadata, UserDebtResponse, ArrayOfUserDebtResponse, UserHealthStatus, @@ -86,6 +88,17 @@ export interface MarsMockRedBankReadOnlyInterface { startAfter?: string user: string }) => Promise + userCollateralsV2: ({ + accountId, + limit, + startAfter, + user, + }: { + accountId?: string + limit?: number + startAfter?: string + user: string + }) => Promise userPosition: ({ accountId, user, @@ -133,6 +146,7 @@ export class MarsMockRedBankQueryClient implements MarsMockRedBankReadOnlyInterf this.userDebts = this.userDebts.bind(this) this.userCollateral = this.userCollateral.bind(this) this.userCollaterals = this.userCollaterals.bind(this) + this.userCollateralsV2 = this.userCollateralsV2.bind(this) this.userPosition = this.userPosition.bind(this) this.userPositionLiquidationPricing = this.userPositionLiquidationPricing.bind(this) this.scaledLiquidityAmount = this.scaledLiquidityAmount.bind(this) @@ -266,6 +280,26 @@ export class MarsMockRedBankQueryClient implements MarsMockRedBankReadOnlyInterf }, }) } + userCollateralsV2 = async ({ + accountId, + limit, + startAfter, + user, + }: { + accountId?: string + limit?: number + startAfter?: string + user: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + user_collaterals_v2: { + account_id: accountId, + limit, + start_after: startAfter, + user, + }, + }) + } userPosition = async ({ accountId, user, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts index c0a17d409..879f1e47d 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts @@ -26,6 +26,8 @@ import { ArrayOfUncollateralizedLoanLimitResponse, UserCollateralResponse, ArrayOfUserCollateralResponse, + PaginationResponseForUserCollateralResponse, + Metadata, UserDebtResponse, ArrayOfUserDebtResponse, UserHealthStatus, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index bd1a051b7..750cb0cb6 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -25,6 +25,8 @@ import { ArrayOfUncollateralizedLoanLimitResponse, UserCollateralResponse, ArrayOfUserCollateralResponse, + PaginationResponseForUserCollateralResponse, + Metadata, UserDebtResponse, ArrayOfUserDebtResponse, UserHealthStatus, @@ -83,6 +85,14 @@ export const marsMockRedBankQueryKeys = { [ { ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'user_collaterals', args }, ] as const, + userCollateralsV2: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsMockRedBankQueryKeys.address(contractAddress)[0], + method: 'user_collaterals_v2', + args, + }, + ] as const, userPosition: (contractAddress: string | undefined, args?: Record) => [ { ...marsMockRedBankQueryKeys.address(contractAddress)[0], method: 'user_position', args }, @@ -285,6 +295,32 @@ export function useMarsMockRedBankUserPositionQuery + extends MarsMockRedBankReactQuery { + args: { + accountId?: string + limit?: number + startAfter?: string + user: string + } +} +export function useMarsMockRedBankUserCollateralsV2Query< + TData = PaginationResponseForUserCollateralResponse, +>({ client, args, options }: MarsMockRedBankUserCollateralsV2Query) { + return useQuery( + marsMockRedBankQueryKeys.userCollateralsV2(client?.contractAddress, args), + () => + client + ? client.userCollateralsV2({ + accountId: args.accountId, + limit: args.limit, + startAfter: args.startAfter, + user: args.user, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsMockRedBankUserCollateralsQuery extends MarsMockRedBankReactQuery { args: { diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index 9ed75d987..0b42e550b 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -161,6 +161,14 @@ export type QueryMsg = user: string } } + | { + user_collaterals_v2: { + account_id?: string | null + limit?: number | null + start_after?: string | null + user: string + } + } | { user_position: { account_id?: string | null @@ -227,6 +235,13 @@ export interface UserCollateralResponse { enabled: boolean } export type ArrayOfUserCollateralResponse = UserCollateralResponse[] +export interface PaginationResponseForUserCollateralResponse { + data: UserCollateralResponse[] + metadata: Metadata +} +export interface Metadata { + has_more: boolean +} export interface UserDebtResponse { amount: Uint128 amount_scaled: Uint128 From e79236eab5139fa347a4c1a589e1cb06bbfa0d8a Mon Sep 17 00:00:00 2001 From: piobab Date: Fri, 1 Sep 2023 10:00:51 +0200 Subject: [PATCH 207/218] Fix Protocol Liquidation Fee calculation (#185) Fix Protocol Liquidation Fee calculation. --- Cargo.lock | 14 ++++++------ Cargo.toml | 12 +++++------ ...ver - Dynamic LB & CF test cases v1.1.xlsx | Bin 37028 -> 37039 bytes .../tests/helpers/mock_entity_info.rs | 2 +- .../tests/test_liquidate_deposit.rs | 20 +++++++++--------- .../tests/test_liquidate_lend.rs | 4 ++-- .../tests/test_liquidate_vault.rs | 8 +++---- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e772cc9f9..47c712ba6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1473,7 +1473,7 @@ dependencies = [ [[package]] name = "mars-address-provider" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" +source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" dependencies = [ "bech32", "cosmwasm-std", @@ -1530,7 +1530,7 @@ dependencies = [ [[package]] name = "mars-health" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" +source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" dependencies = [ "cosmwasm-std", "mars-params", @@ -1541,7 +1541,7 @@ dependencies = [ [[package]] name = "mars-interest-rate" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" +source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1552,7 +1552,7 @@ dependencies = [ [[package]] name = "mars-liquidation" version = "1.0.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" +source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" dependencies = [ "cosmwasm-std", "mars-health 1.2.0", @@ -1657,7 +1657,7 @@ dependencies = [ [[package]] name = "mars-params" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" +source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1686,7 +1686,7 @@ dependencies = [ [[package]] name = "mars-red-bank-types" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" +source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1838,7 +1838,7 @@ dependencies = [ [[package]] name = "mars-utils" version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=05cd222#05cd2225b1df320628c14d5a099c9337c60da1d8" +source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" dependencies = [ "cosmwasm-std", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 949ca89b4..8e9ec371d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,11 +64,11 @@ tsify = "0.4.5" wasm-bindgen = "0.2.87" # mars packages -mars-health = { git = "https://github.com/mars-protocol/red-bank", rev = "05cd222" } -mars-liquidation = { git = "https://github.com/mars-protocol/red-bank", rev = "05cd222" } -mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "05cd222" } +mars-health = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842" } +mars-liquidation = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842" } +mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842" } mars-owner = { version = "2.0.0", features = ["emergency-owner"] } -mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank", rev = "05cd222" } +mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842" } mars-rover-health-computer = { version = "2.0.0", path = "./packages/health-computer" } mars-rover-health-types = { version = "2.0.0", path = "./packages/health-types" } mars-rover = { version = "2.0.0", path = "./packages/rover" } @@ -76,9 +76,9 @@ mars-rover = { version = "2.0.0", path = "./packages/rover" } mars-rover-old = { package = "mars-rover", git = "https://github.com/mars-protocol/v2-fields-of-mars", rev = "183e4c5" } # contracts -mars-address-provider = { git = "https://github.com/mars-protocol/red-bank", rev = "05cd222", features = ["library"] } +mars-address-provider = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842", features = ["library"] } mars-account-nft = { version = "2.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-params = { git = "https://github.com/mars-protocol/red-bank", rev = "05cd222", features = ["library"] } +mars-params = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842", features = ["library"] } mars-rover-health = { version = "2.0.0", path = "./contracts/health", features = ["library"] } mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base", features = ["library"] } mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base", features = ["library"] } diff --git a/contracts/credit-manager/tests/files/Rover - Dynamic LB & CF test cases v1.1.xlsx b/contracts/credit-manager/tests/files/Rover - Dynamic LB & CF test cases v1.1.xlsx index 82b5d8e4fd34d202b9a65c46c5d5ddb2ba477f82..436dddc64a2bca66c21acf9b56ebcbdfcdecbd20 100644 GIT binary patch delta 21471 zcma&NV|3(Uy2TrH+_7!jwrzE6+nuC3ww-jxwrx8d+eXLct)7`PXYSnd;jUGm-hWlC z!m9c`d+%o#sl=w*?+!F zQUEi9r=9ID?KS6JE~NK>cc=);1eoQvXA$HgFk+Z*F6Aq~Hhj%`$mR`y))9}sy^l3j z$Tt5Py%0qqX`ZrjD_4{?zxg2Fc+5)ZST+kC7r=gg*LT*k$lTcZsQdggooO_Gi9Wnw zRKIEwx?lInO8s(K+%2C~|0a1IM>d=V!397|eGlEcb~^VOd?A+B9Hja#hj25>F+R7T z{E@l!eb6MUf@7h;^-_5YvBR~I_s%BB=>sFmwtb^A@yym&Cb7R3ay0S%lxl;Kz&%zx zFr|yA#kF~=o@9+c@{CShYBsPjwWA_&EO*3-O0Rn`-!=J@iywh}KmTs`)xMkeqXO_Y z%cPk4uIh(H8Qi;?d|^4in)ly|C6ZTp|nO@9)P5!V0?G1#RZP|ZZ5AS=cwtrSc26ty;WmdwOm zHZF7K;2fKFnO~P>J?uCPJ?PeKJtbSTP^-%893!1gzSRq+ zB{ox_DE)D5t5uX}Cw^BmmMGtyO1XA=;we0qu$r7K*!g_*e7odt%AeYTxo^8`S1t#D zO9D-059$R(s|nz5aTGKVCs+O4S}p)%I6z2{U{b%otw~lFz-PqOFt6X_u82RDs#4~k zCHY>0g_BwKw0dI>-!x}~LhDx=k32!-fN1`UwOU3AsTX49?Sm?hNfd)0qLAHE$RrH} zlP%=>=+AXEslLfjjsie4k%Tu*SrQf?mrc~79EVjT#I>0Ru^f96_9dXdC(;0tU?F&% z+Hl@LxS%r314+3j2YA+*4~jfTCY_V8eRA9rA51{ac}}<`fM9*SEq{O{{F@?(s2REd z0~}n$5aR-A1jT>8(N4$`%av}0O%H`wuUKGRcp1`-%{G7shocw2vMIb$N*h3Wf#aIl zO~QQ2f;PV-w*pZek?e~@)TQWH%Y{LYgWl|5yI{6^9q@9WmBhkhKqp8ixG^SxIxc{U zdrgjZIv)+z1P(5lVm*+8wCW_5pwHG@j#uYoZcXMXuht;nUefX5X~ZuvIJSoVrPa$O zwk49(+haV|er953gdIow7#X0tdmZfB#xBUf&N>gDy|WYlZ7oSm1+7E8)uFkpa^x$O z={S5_iyUQ93#zIoRf@9;sfzpIfH!{PPHARo`%_&L{>s!M6^lLkOESB@$(q}CK} z^XuWx^AC8lv}rQRukfXc#u!g@`yG*+x>{6SwL;P9w7BBcF=cPM5?VAPj@ntjn%xR` zI1uIr1x*AF0}HEc(XtD05cZXo86JK`4XmVVQ$2d2N!D)FhNe-=N?h5Yap0Swu9g?n zQf@GdU|-b)j?WXDM85)bz_A$Qm#1~)q=ujU(|Jr=5U_nvb8Oe|n0LAHg`U881}e>3 z-ikP|tK=aK@R(jF{h=LWO(vybk<

8nw5TBtL5{9Rn?9(}>E0~sV#)8H{F0#SxV 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 delta 21342 zcmZs?bC6^2zP{bIZBN^_t!Yi$wym^nW7@W@Y1^2#F=^X9^Y(oA*}r{izo#n6AJ0ls zsbp2I`@TNwsVe|0E&xMPk^_gp0D*yl0f`HIQBS_XLQiIdMQLE60EPJ93r)VD|GhE_ z_TMXL5dXE3TptG6BnC_TU++~8^}5ediu8{>nR$TFhu!V>BcZ5p3Z6%b9|eH+b0LEGc#~PW6=a!CTFEsN=Y+4VTDi4x zw>$6KbVPTiEftg{KJl;qyCqdy1qS3XcWs8I8yEIDx)x`uSp;z$(z)jvQb7NkdLMec z=6lHgk}kJg%(X{uZHo%gB%=Z?Ikt{*Qep&10MAca^y1+DM(bEBmMT}igium zLRzmDM0RvrjO`t8VKh41_P1ei@`38ow9Q9ITby7m@rom)#XtIB_23o0by5(kEy^#}# zrSN1HgN-y9Wh%B}&JWUl<^0gkwXG^UfTYo})^PuQe>`|HYXF_}pu4Z1&=lEL!^!|q zg3?_WXjS7j-`D`}F$4O$$DT7u{;mOPEXj&jO5%h7eF)G49)D0vcgP|3LagG+Y zW??3QFyBK8q(_zB+JFiP6B$jKE|nuIK3fXM46&Pw3sd-ezm8Mi7*a2;2B3N%dS~~M zv8|EkMugw@BSdt(>&&D%A1qSt1pwP&(znH)NRd^pQWVdXWT7or#1n((7cdy}^v zN>?gZ<$_y9LLktpMAZ?s97UD!o`!Gnnt`;npu=+!`?VY4k#TyfCswT`0HAmm*WZ!4wARE(nJ)w{We0IgElNcM`W8O%t0=6Vth*y#8Km8)1;hK)}jMhWb>L09hS8 z`HN+^4GS(lY(mn>ulv>F+UdoLL=Pt^+RN9SC4T0fm2c*0N!OaX7qF~EjXqIoM86i~ z5$xqDNFs)N%r-6qE_Rxn0VxR2b5+QNjTPlUuVivOAvs@FL@Shzng++Jn`UuhM_3Uz zVAT0yF*WL2znmJopR6_*$WJ>l+?$-p&Bq3RBpAO>{m5q>k=beru_L)xT4XEglgj>B z%qK|=PUP;GDQH^l1Te4x8LWYQYQjQaaPl`SMaI1pgW7?h#^>^j@ti>-@dYK^Ut!ht zU(7~$q6}t2%J?`IhVYCxnU;Y=%^Wdhs6)?hr^)6Gba82X@&m&SW|UmaK;WVbMjI6+ z(Dv)QQR|-|3DyiJctv*~Stt08RV7OF5r**`?T^Ey zG18R9SyD7B8Z)&*`10*Q8$@7ngqd~|25vNHAm5)B-y-tCq2twsN?|271*$%!ZXNUB>RKL4CHn^OJs++4b3G3nlyvD$^;KE3%xP8ZOn&za?+^xfE@z>+R| zMCTIurR8DQ2j+X6$fQOvGGStrgdS|f7Q^ESHkp(f3VCU)Xnh;~J*Sa8Me6(m(P!L5jxG)R&yt|(tOuIra~ph@695FGMjXEuQ!Ll!GUh_E`ttk; zxSgY2h51)P#4oOI2CzYCuvb6W0>6O0?aD$#GVmNZg`2`J@(1lvxDh0UR@mKy{dAm= zoD`X3yk`u3a1)=jq0BHfE26Q?USER9eRz<|I(!=EU%QIl=H(--tgEh8c5^8`FniD+ zV>@0Pr(`z}xI%jB@$KHweY`s=-Ojv!>RkkYD#<~^G$jg!>qCQp@DYK4{1 z`1jmAt7-isKGXL1HXvEQ@3V-ZXSK8lqZS_$Vg~m}`0S#$_FMr1@3*7V)VN5McgM{e z1ktMQ0`FM@*ivJd# z)<@Rq$^tLpFPO&~KS1soO%KiGS(xse;> zH$VpDO)QEV#WdqTe2+ZL<3Aalc}~54`h-{}RK>vu-Cu}>fPO;E;S*w4E9O;+w@-f| z_JuW@GodfF$?C>ckVtXO;V9pG!tAo1JPG-Q|S9ypdlyXefRNTUif)Q zqagCB+51;z-r3a=zaptBRPt?c#s#3 zZAU-SP`=kT++gib*KEJA5s8v9C}iLfiP6&U5e0VFO)?dp z5a$O=r`!icGB2{rk})gfPL(u%pC6wX06u(4wdyD?p`CdU9~DX=Cjut%26y`;X!8{9 z`Bc^uWi@K;ZB;-Gj*yZlnZI4!txkR(Ss?__HECTDtcc%J{46KB8PkVD|GD<;oA{L~ zdImePiv$=!4J{qP`i_<2%d6$Cq#~8xLiMKZsY$`Di@2GW!TCpoEnb3oZ6uKQ5{&W< zQGVSRE$N3Ym^`S<_(z9r)6bxVW~y(cc_^DoFbfQO@ebMFg}*?W9mQ%D=7h>B`*J-4 zz)-g|#odb0;0FJMk<-2tFjVpdWDQ(LUxnpG*OxV+F8axA=x$sn;&X8~E|4eLJieuW z#gQDA$`2C}Yd@?!-Y%c-_pt(CKC z)T{yM|8b_{)1>9ov^LFY#DJod2n-~*;YeCF`nBdGL)E#<@q3lqg#)dpM0BDQK9x#6 zE;JxsBWp;tF;N{mUxsUJO~+@*PonT=`}?o78L(LT`7zn}4AHMb(tQeEH@UZkfGH(5 zU-1AwZ#z|V87mm|W}7;dQ_&f6S)N$$cKM>R_IcIzpJnPExizF36IS1oxKDLBN>!C- z=b?$fQxIYC8&kz3M?%nZqY|E+aKDv4D~a#TsIF1hekK0X``rBdNVJogj2H`e6fWUh zVbFB)EIgeVxi5w;fr-Kw!=ZD302n6g?Ia<`J(e4nAs54wWjUtUcQPQFc8b`)Ls1~{ zv2Lo0W$sG?O`~Pi{>kUkBGmMlgG9QB#%w`K8M0n(i&1chr#F^U?z={8M#0E59etZH zuL+a6Nu;lu9<#g1crGwO*}9Wl8hAgNG)ZkPU|NcXd!{d31u{4c`<68t0=PdX&!`X& zqf6|sf-*ZWPe}Y>1rhQifPR|4+;b|0YVc@$cNwDTiqdy0&aI19+|)YRi;+IRmnNGt z6u#$xiZn1pnYe&PAxth=B+-NgsYXy3(t3P?q^rT)k*krNdyKRx(D+#}MC6MaX9!M> z;jR4e{^V~>Zq?4C#2#ruA5d|m$EzYIo1*%6{B5m$&`q^YDkDilnHAo5niKfZi7KsN(7;^{K zpuKWvv(#9~JOCq*12R-?RXc#GQJh-~W`T%x|CKD-r?x@YpBx!%5}X$cc42Ubb$OA5b_786J|iMzL`f4^V2|3pcyB2?gt87km?vbUKYtywXKm~$v({w zA|O`j<`Cb6!kQ&Z%r+{VsuS21-yE6-XI7whaqBR0wVFfgZFTo7*4m9GV(ai?#S7;g z-Zu?s*GBu=Q7{w1?8}Mi$vsygYO3(9hkD(s_0sA-mAnu5zm%uvZYityqgn7|LIbPAG+0vqLQiDJx zY)W2HZ_z!=7DrA4<=bRTS79q#eXg=<`}2OFBs^N4eI6PNY@-*zjXaY-qOcp1@k>#R z5j~MYtb-&6yJ5lIt2cI8u|gW0Vf;*+BgCXcS;dM%1JR@iuz0D;0De&k6P&gB;N4oM7Z--+-LS8Q zz5df^xH8Yi731iU_fIUUJIt$nhri#rDfNtp#^*Oq`G4oCYO2+L=PGgl`@alOzXPEb>69)hk|Vt@HWuo?9g$ej>W=ZSeO!py#nF*sB*$5OXwg1jo%@ z&4e-vaQmH5`#@u0=Y$Kc6ruQk$E!N0zwyd@k<{!e7^p7pkhs`<2L1Wq6*y$l5NmOA8$9{=7_6{{F<;Eb*8n`*2R7Qq(%H(h#7S|Za;(0vQ$0Xwn4DQ zVv!7hi;-#o*Z5kmMwG^p6(wDvod>YkWB05Lt0XqtLfpGZu+c{@SV37ptYA2fg|vD7 zo3D6<13_p&Ft7}{k$g~!kXS?`5J)5$$!^F;2#sSN9pOKG=QjSfHPNHCDVYVoBa;Ko+Vd{Ix~ zzMgU6!cnQXtJ9;A;+yP6)l}Zr`|XB9-Ymvh=s*v}$)ce~U9&LVZsQ2^vqNI#_PCv# ztd7dj#Bi(v?XKD-i4|CSB>))TQ(fuNXsz{+{-7+haEd)ejXy6iwX{oNYT`*GFNBTI z7H6*^isaIOQOIQF%g_7{kX8_HBa1SUf2&$lr#dk^FYSj;zuk+|8*cS z4bMg~Z(z2k^~3y_|H+G%=nEh2xOZ-LeGh})D37)F5sByBLo^MOd>^%4Cb!U>pQ zRtxI^6pc&(g`O|4Nv?PUgduiHEQ6HPMh&ox=CG@KZSK*0&{nsig$&V!PyVgmiR9)rn(@I@A{S~~re5~AxbdUT+MC`ZM87>c{+d7t|xGav#&HjG+ z=y6A{itmrL@p97$9D`*Zo~xI(v}7f){YkB<%9x4F0NBu&f%=mG2?fN`b|_U(gfe$M zJrTxC6s$W6a{=9B6;XjUG*8i=oD|jLX-K%Z=(MI&;tSYfD*)ij-R+uS8N!SA+1)pbgjQmn!J3SBqeF5PNI?}!`Rgl zOUQ}~0S<)I3ZeT|a0(<{&1(<1sRrdBbfCC3m0~XF7Gyq2d2Zu?X;FC?RlN;M(P0i= z)o^i+wY+Z>Gqcm;Uepzb{J^e6mgLU}6-`agyH449B4d{S&LS)G1xU`8Ii|@Ms0fLp zsHi!|Pw7X+ZqAyxCvSRV5?t9vcteq=HQyW|^dlAbV_n z!yQ;MN7y)XUQNy&h=}@87(_-ZnjZX?o1q)T_(upX=opp>(Ek(62Eu({8HS93C)F<= zIA9uY=$YyrcQ{~ce-lT=ZMO&%ImGl%?|-EnxSN8&*4l7ISSsU06fyL>ODY-mDfQ~y;4hz{<_Ng- zKKaZ^1cLm3iI18nxa3M}> zv%cFztTHj9Qg2V~XG~TZ)WEG7`)Plr@_JmDlWWO%#n2{Q433q?>Gw zA`8fQSoqt>jX_XMpc@WyA{FCsE1H6J5=aAVSt50lCH~RkI{((V!bri`z44?MfccXqG?Axoemq}At z!aY)wq+?C8JUcGH@dz)Q)8O#acHBtp&&cpoOn}bJq4hz2$&15AOu<-?0FKwH6Jfz1 zEe5tR$>)IR_pDQdRN3)UgEQ?~_kqgVDD6o(vVfKbM2~M^H|h7{sSOd@nDN5(GeGuk z>uxkF-B409<6}EaqfOJ}_&Z5H>r^UmH$+kE25{*r!=nI{brl({5wL~dPr!y+t1jU( zkmOIHbR4`+$NCdXh!S-dg?jzEl_rWYBU&lGh<8X(%Ua>3WE{|kkkCOCh73lKn&??s zc=JXoZR88e^VtUDQF4kPqG#`hX??2k@@mv4!h6PjfknDxfQzvoiK%#z=iIKg{l(Y{ z_q5@*-!t)Gx0A@sia4iwwszYe<{ACu?@y8?=Uv<%}PE|D9ulUfgsdrDfAtxI&fl0d3}i zCSWjuzL^w2#@;gSXvbRaK|fTm98UYSq#A)W!YxFU&^>JZybPz8!bk}t6_HeuK7-$) zVQyVQ)ZRiinJBT#E+6%5OrnSqp$axwwDPkbPsJ1zThrq4mf9I9wJT%9VTOu8e&d|M zU+FuVG~lf=&3mSiKa{{v5chC^%h;KBX`d(GE|dZ&d8R6$)3y|_6)#xHc=KRmas1nC z+jCD3WB>EtK2ds;_9iA%Ob#pIzn8_8tBf-q7dH^Q>P(+aK+9)Qx{fK(1U$N-#HkEP z2&}mhh1w21cM{7a1-F4l4;M`5Yl@2-tg$h&clWK0omXU>E6pmJH2E~!#fc^WD{*KP zyQ0%eLaT|m`dVKO?J=?T(H5yqEy;S%ke#|BKWCT2{pWH}IPZ~$g?t;aOIk!DR?-j2 z>4E%%oKR(~z*IvE8b?qTSx$r7^d;_;p(QUep9q*Fl5GydQYG}p5eda?yITcpe^%+RmyFjQn;M zR6+@-6*IEXEtbAF&S&9h_LJzrT1F}EBEn2K|0Bc?S3ZUKu*N?^oQEK4o6?6@-2h9w z=u|N=oedsACwfCYc*?Ubnuh~P>vInpHe*4 zPAjR)E@>+~bG$iDv26*v{nRgv9Zyu(&TK_vX;?JnKZa9kYaJnaxp#6$-N#-MQ z(L9kdDQgEk4Pcrw9eC%|CP2|=-t+Fo#Pw&?tx?XP?v>Rsb$8=&??w~m`*=ywNvhC$ zhl0M#*@-^t$e&{1)4ou&n@Lh^tX#NUPSweK#-&4&EF|!0W!(Iwn@@3Z%U}cWaHBl& zYs^IZH-q1@A}7~6Ey;Tf10m3ZgG^$CW*2S7Q#du(`T8?BVsYs|H6vCtpC-E<%AIv zk|m+RGf>0v<~WWs5cp(nhqD`B4vEfA>I6?=D|*a;vd_uFWrve663vMqf6R#>UDL&h z0F(9Bx^)BK+fQ7sebG&s=;r4}U|W5%3B9pB26?R;DPIbzHBrxX3?m2+t`+OJal z{N?am!4kE&IP4Y`ZMxLb?>noqA0S~+bGSe1%SS!{>Lt#uz8Z99tqVFjR=b1TmC}=0 zEtz9ExQfx->j43@{RWua^^(1RkhmcsnTj0m*$qa#*g-nj<$y{S!r|jXM^nT}=6tsT zQpPw?d|WGvP{cJ|(66B&B?-TwR&kYndCU2wpMj@~?Qm&{{c^nN9US}-N`y^FROfr1 zk7(QZlda;aU}-ex3v9t)&mxi#4?zx59R8pyV2HoJz^BJ!E82Hsi6-CXEAxW-h|U;w zzDbVpjWF{E*>O%QoJ819xtwyFie>Tz)Yd zc&W8*PIMLK@ukm*;Oz-7;1tZ0k}_je;6svdlfOhbH=qHSV>OIePcqMS_; zQl=6p8owx*FRm*G(vfv%@07_M$h(==Df036_e z@6Z);5KTI)Yb)p)ebmN1`DOAGL;UW zAxK9ZM@?-IA||wyUmL@8lh3f179o5mh+D4Lmn2C?S+N=UqE_&68YLZE;NRqER8&~+ z-hn~L0Kpc{;i7CKzgJZ?f+0Ef2pEm5=(r4JgoT6!!*cz_ikuu{s8_EupCS5_a0wIb z7#{1Dud8=5uBt-nk=l+?b$c3VkO@>Y?SpXq#4OjQSWuyc1ZXn#3RL#Ox8$WT}y-lzgEF|1q;_G>-ph*>$%%PRZ9wJS2PA zU{X8nUZ9&D^FbC3lLU#XKs+rsMMq+YB$f0)9z5R2KhWT z*(C}3yjV1mJ3hcvzac1&EKD`c^f*kFC+o7;K^aK}xu`06PL>I2iU2x3l9&_sjP$5> zAI1#l(=sXtoW2>=N6^Zf1TOD5U!0^x<9&T2=*^g3_oU!?CYyT@NzP6h(vd}@?}1aj zO6hFGEm<(Hxc8-{U!L2}pWe>5a?r?OLB=2eY?;2(;IA2F zF(0Y#4vZVwY2fT^q61RID~QKTY&FA!>jb^O^V}*9g|P}Y^N@RmB8SrPcG?@kH3>gy zjlK>C_$3KYESk1W`c&P&I93Zj+gQ1o#sBhD?!5wi;UGPDqa~k{=4Q-w*On~D8C_=L z;+A|lbk1QgXbTV5=iIKXdtEcu`;zsWp>DkIZzY70o_JV;!2kiFq5N-4{tx;$qW@Mx zl;lLQ-v3lWcX2*gnTpdYl`V?Yx8zQ7O@RM2an3%^SB-4hU-cg4XRk9xEqsqQAx^(> zV{E&&Y8JIx{dtNs za3!zdSN#N_mmeAOu&I8(Zp+`}_1jvBp}xZ_(~A>5OV~t=iaGs#yaDoC*h>!JdUh@L zJ1&p%l z!ot!~im_$moKBpWlr`X28)_sWi&6xr=phITGYYVLu%-YE0W}{QWbmsUuSx`}=sl?M zlIZzr)7S@81ATDej7<-~uKg;kf-xzHeXtlO8BIRRWC1KWEyFcj<|*TTE`eKCQuLwp z3~z5F7rfnUYCz=PoRbJ8esuu7RSOMXZpl1TM>AsMSZmc&7?;V8zdpl`Kfn3Xj-SMe z$aWi+_coEE;6=N2qL7!Ho%Q;y=I}=05M`upFR=ttY;-E@STX@X%6Zs^-tYfKx1+}I zbl^F&5=J~LTROG>YZd?2y^mLCjrypIihHHrS`*dy;8our9GHuAut)fgUn-p-Z}5is zvox4h2WCYnoU)-b>F}ynC_kn^c*Qa>AG{5}(W;L=#j2Ro_OPXf5y=*n+7&xqpQF^!J5n*&`eAIFhxpMlZDd&d8Np3Vy!cQp2eq`! z+?vV3RHsZ8*~#TO?Bl(2bmK66<_GktZRHemM9R=|STfisUvnbk8pAi5%4X8>h|*qy zF)EwDkG^XX39<7~ff1!Ez;(3eV870YUYCUCn-uN=E!ivp=_V#xhah&^F(-vsCafb_ zs3SXX%)c=LnF10?L!G!%Jce>QbK${vBx4PmdytD1XdwI&RJb?Y0tkQgxLf+^-3ror z`Q}leBD4YoS1r;H?j~hyGUGC?oN)C14E`#dYjyKxZnQwDz`Q{s^JcF79quF{@*sp{ zts(tzkpuAxKqWf>rC}+gh+x%7X_3R%n%|iJlgWW;;8B9^n4AX6XD*-HAo`Jufc#58XHzb0A zKEDXsRZy%~!U!LL($4;0Oh z*48X%%ar~V@%hV!EOcrPukpmDBTru41ihn8oO&-p`o zZqLcFhu~&7-SlY&S*`LVhNAx_xW*QWAaN5EbzW) zGZs8WUz7OC^Fr7ODnTL7o8ZT79%T-k*PP)NE!bV=V>1R& ztM+MPQQC;83SL98J5t1RufmMN83jU3A?5wNe-agQ+qkdPstLlQW;$yvU1HZX)Y8olCnqqjS$g{71KeS^FV4D= z2!04;X&{YrRy$9=p1M{no@mZ`IRqM1!Ff^bPO4c$Y6{?qqO{-v_dvL#c2U3{g3Uy1 zhq;%+MW^&E2Dyr*ugxIjgkjYI0iWiy9ARer!19lSF@kNNqaW$M=ftof-{J9Ui9OEu zn9$6vN*y$XSf2|jP+%vl^OjQk5g+k+{w4v5ytp^c70ffNX=!Gns;kakdkyod#Fwu& z(GNSuPwA2g>&d>@+W=c`uy;VCYPDVSrOf6+vf))Ke{9vEi2@xaCwxh|>Z}st1)|+! z*pF=_n+L<71BXlmcl+@oF#L!gVR{;cTG?GJ^g(gw+(Z0Z4g#AzJhqofRC7!aGKr_q z`vmx$K$I~D0_-?X#D;>gc;xp(VAy#9bhU}^hfwj039rJ_TVdObew<#}YgKRIio<3IM@|*)KuFz6wfO?@5&v-+%lEe3|GXT)3ZB!mjFju z@5q0XNZo)@?+6SC2-5!@L?RXb;~)Us{|t|{tz6cn(SJi;0rH8Nb8(mQl3@$MGC||d zvjTZtiRVs$Cx0r0hxZX)$7#CI)40w2v!7Dw&5qV%&HJeJ2AVbCma#HGE+_kBKc%Ei zrsnVL*tE@G@VoiiUft*HnA&RTCbIxAt&iB`2&O#X&?u(tr(vn>$SwWp_D^1GUSBHJ z!NRS_?Qttj%M*>JjP|r}3rs^23pRP`Ml{HJF$;uHU{ju zDTTLnhn!@((VN$@A)KG{;R1oK~aTBo6dA1KKH~UcLVtp(@`Q&7As0%y1^U1|-DAiN- zzQ1wSa11X@?afd3WZa#5ELpFGS1Ym6?Q$|rNN@Ac?c~oyH7^<`p(J>-7HBa9*D&Vp z2k*HG)mp{NX2tcT7huecPpFLekuq@SrkWm5u9q`OGL(nuv5LS#J_d;zDQ7f>QSyBp)4?wB<@c=uG1R9Eeq@l|hXc2Sqj#y=f6KkB21!#NBw!oD>R((>xA#FgbhMLPHd!XXzA zMKlVhloX^;zCVthG}fg)6rV|=M4dtbf~rv>tQYY@pvZ!VLPe8WjF3Q$YIl14-s8+S;#DC(roSO|${JW2<_BCKV|wf{XS5M=3p^oPLxa;QJrry&^^1YEM<4ry{W$1H zUct8B!idL_OGIXynfKh9zWt4garb+q6@^1tgw8YYl8`Jz0+NY+E-ayaq(9OS3l^ys{ z2bsQO=)Ypbs~GHL1I%yN#UuHTMpzqVn}`QUgCBYTtQ#7dqwv)H%b4+v!nDNkhpF23 ze}eC)heNk`k;zeW<>)pbHAneq&Ml&-EbYHLX{;}{GdtH=z*)3%YL#;t@(K>Hy=d?L|+(TH2tCHgeKHXPb&Bd}Ln# zX(PG+8wS8q<`>qF^}zh ziiA(&KglGSSCa+Id@0#Zvk72{lTvM9qTD#)&o7AZGI$0`u4OkI{2x zbsRA}Nm917*U_)zz2^R;?{4no7XOSzLL>f!{~e2f3~Q`n*KygjtVoZa8M=zAMaeVD zjxntrlKJspdW1jAtxOW51K;yBZUp^$&nJhzh17L*y_twOQewve(&&WVd!qAP&RpuG z53a0Ui=IcF7f~9$PRxFk%siO*%O{_;tBAbzD0J%yT*fcD{+YFR=}u?oz0D{EDCBW8 z;k*rkUv$EvzHH^Sa(Gb_nxq}k-7n62Vu;N?3u578_%HG{VSSRf5Gyx-oYlbyHk&>g3u~X#t9IkBfY|sXZ`)t;S{;0ncjH*3kDOSG zFzZR8YHBigkOVwWpN44mk*^wjSQS%rH%=F)3r{SS2q9Z*ThV-YyA=~9CI=Bc4lHkQ zEN96DF--sp+#Il`eEH~~I-Kp#6y0`o>M0X7(m{v!+tmN<02&w%6cH7IfV6D}H<_W$sT05x4>d0gYT@nBqsV4?$`AhU7 zjiWQq0v;TG*-}ly9*)cWg}-`7lq2?lfzEYkCTOT~%aVt9Dhkr)E)@{` zf%YhakyIsQJfEr|S5t}TpIM|FMI<-!OiHLhDKN^%5sh;x10h0ve)Q|w%3s~KP_|Wg zP|PBg)UsEoDKN^_NhSb@qRO!vMa*QpBNE1xN@(5Yo$%ML;nJv-(=1lX$=V}KY{N+? z3Q)`^Weoga%cRG!h787Oe-Rtlv|Jb({>F?h>ax!6uBhinri`t?g)wER5jD!UoR9I< zZS46uAkzwEId~gI@2jxE(vQODYHLx6$&I73VPRVC^rU0?L_9>82Zk>dNPaYA$bO~I z0>rCMHSN<@J!%*}$+~)k^nXgf9Pf+JG(xDDN1&~xP_nX(A!u|4v^{5ySGxW^A@!5n zEAF!njjE;wlS}ldv0Dt-afs0U8w54ZI=zRKqPjDk<0Q|IlMnT*0nuTstj>_gA#ycU zo~r3qk)LybYq%QEl-l^KKZL%z{&>>pVTTv&{62h$GgEdFR^~=?1sm)lk`4A5=}~1X zdcPjF5lVl*1R#_Q&KA2%`b+fyWf1z@RXm~9_+|avVzVZ6FuvRl;csJbt*Efw`ObBd ztvujN$VVG7%odQc14LFSr7s2pyNxD4`Vwb&_}F|-KvToLNvg5dmTd6AY`oY*YZPvt zA7^kqiUn367hL#WsoBgXg1o^yU)Xdze%v)-VJ*x9GiX%9CR6$czuvH$Oz@Ynj4*p{ zW#Y2nDA~)VA1s(%2WD@$sEMD!BO&~dzj@KjqcMn^Oz+1k2Y9}FKcC`(SY)Bnuyag0 zO|z~-^9&=k0xB(#fVNU*PB#ref6oX!dDHKie%ym2{Ua5gOCOEarhA&}zAL!lVd7ih znl9%Lowa3~ukMgs1W-N<4*a%bx($+MrWwZ)f-DSS2Uy@ad`Dnc^{+Hs0Q+G}d!Y+2 zg7rl(@`l-LfJ!|P`Ii;XqhOCFJYF?>RK-N`#0R2{kK;jwodX6xumnr*V zrbGLgGIf0M(8Cs<0UKVj^ZFHEE_Uh2VB&na=vO0FQ-DNVh-z5O1@s& z)XPs>H$nS)n%*|p`;>J2xtUx4>F;Llg@ez{+;PZ1KZO2dAphOWT@&|rGk59Fk36gS zKW4XeRX^$fDD#&BE2tm|qR)OG00H;xkKM)}8AR&C)IPHD&Su$1;mb0=*#Os3W&C<} zw55aFMZLtH;men+bzfrc;>X5XH@0|l%+5~4y!n?!Z(aL%W$vO!w^R#C2)7g32kmn{ z>$X7b%9@Bw5>|E3yFN~j_ury ze|z*@zlsB1dDVFeN}v4cmU&t`b2Ffbh^S^u0{Ade`{7 zqq||VIU{S;E7{eA5}+9)svKot@mBa+QEWBhe^t-7(x>6De5KKX7FT}kdZ#ojhKOuJ zj+JwS8YP88G$D1e3=#FEp8lRNO{pQooon7F4fP->3^@qZ=%k|=9p;0tGvnA8Ft`(9 z!x$%UxeQv1X3@%9L#g5QxIQ`gOFHo=TwGgIZNXF@Bk7g(2B)q&J?Y!;RLj8uj!DEi zP0Qd=oHRI=8q{PKQyeKlw+#ZENYJFQfT4Vb#P%;?lEx7f&Lk|h(#1tcZIY6PArN4r z3Ps3vyqstz6f7WU2WkI6{3qHZEA?r2>nFrl@;jxI6HGrZPN!J1@2o}LUUh+KiyUBoQ1cvX*tA-P<674M6~&mg``6qBv@N$aEIR#~(deN{#NC{ZL65A}Hl zO(YxytwBJ7bzl5{CI@mVtuuOSw^Vm;JN|Ei3A9Wlo3n?aS{H*T;5n!W4n0J?ZWGHl zIV&>RynWy2z3NQSl)cnmJ-8X*tn2{JJBisQlMoSWHf3z-WDGkvaI_;$Y z*5q=;BRvsBNNXbDWFB)ifYKNmtg?VMd;yD?8Cd8hDQP9pnl$D`BwXkGS*9>P#vPUD z0)?i(n1_;qeVoN-4K;1Zl0INf(9O^gZ-yWPMx8WlsQx!I1d=*DbXYhuva!!Xv%8|7 z3!XCZ|JTKp$3wY(amKX_V;LldES0qsSt8rbnh;~M?|Y0TTZoLoSjRfHQMN*sRF;`g zk!9@5rKA|yGNnOw>QcWky7#C1{`EfRdB5Lh`@Em`ob#S@QWUx6pYfxBLpU)M^pk;v zN#F&|>pS1H4P}ung$=II2t%9>LbU(yg=duSa%0xLZB-|L!dXH^YIv_mT6p;kc(V{N z8F;%=Vh|YBetFC>eRz2*j?=^%ri~T38Z8m1H9{|`Hcz}mF?koLCa;{fets6-)m>E1 zmIU}Ptxy3a7rKI}A{0AmykwIFq= z0=3=83C1$qg_R62#Fu8eW@jE)5zDztB#1j3nxEuiw@R~ z-ko3c!0d&cL62^2jsIkAIoHZs3e;K;gE&MK>XO#Sx7Ito^<25(v5OTl)-ILOyYhK) zxpWt@d|T2l@r5~5`WS#Tf=xqz!g$LXS~QIoSd%!z=2#JKsw#d^Ao(`X4c{-*Z6l#N zcT5*6jALh950DPo*Khtf*S?~}eZntpAV)1wHM<>OF5?u~j}9N@2=99xb!*{KjSi1z zztOx$qbjoBgNid#0^WR-pt?piAtcd*r zXKk(?_f`D5);OG1_zL+BsCox0toPjBCs3vq|H- zU;}ZvaOtB5PYTibZ8io2ID0}Ic2|Gm{<{vJo%z%iMwOQK&k_~z)Tz|XIO5Pk!w3j^1CT2ojTfI| zgD;1e2q1ujnb~3)F^|{*Y%x4KA2%}blp`)7q~VsYjY_vsen+6TlVA@-<3d2eqERO8@ya)=sBa#4<);q3@(F`K zgRy%;2dcs;(4BaDimGs)qAL8iKn*lV3RCR$%lJLi6Tp~axC-L?dc~s`RiFE(KkPgo zf@dyFZr&UdrE8tkXR8i+sitZl{BzIcZCZ1|UZd)x+~=F=w!sL|u$^Zkt>>mkmpp!C zo$gNMzS;An*|>Xtqq0+wjsze^I?_(>d(6%0Ug6<=jcF zXPGhs>ZeXkdE=`Qu9=VRw|i$TfxVei>k@;*Mfl;S96UmAZhO)!hqOpivMdW1)wh1S zp1EUY3gK1&O*~O|8HvR1M}(~yWDHZ>?D*%)YZjil;Emg3cY4{hL?8GKNQB0j{#*@i zeLWM>uHkVpzdfP!L@+j=)xpepiz8ar zI}rF@8~eYBlI^=s0{-HR)&lUr;0 zqo0Kx<9?jFBj+f~q$3iUy|TXkFteQRdBOg+$-aF{ZtL`$t4hWBuLre`u`8&eSuKMy z^&|wHLM4RmnR7|xysG<)-BOdlIWdLM7P8Vsvv{d9F~k;-Mz0BPe*Ns6pS;hC;CkGo zwB2ZN;wGOGX2D;%d0_O}HS>F^+*T2JS6Mbp^U=w+rVe^P%66=u_O){QST?#hkwjT) zYB@SgPj(%PHiOem*uJcx*z#mZ?1*qWgRsk4FUTI!cuJ8`*E#C+1!376S5NyQ5=%08 zPbLan0l_&JiwQLuU|ta+h<)I49<=UKbf2G<18 zC|b`;A&&ROK6cS2-bJLN=rp4go}B|dB~`P~js6%y(I!TGZI9x(bF)DE9p}EG(ArlW zr=fmDH_@2-CiA@F$VeG7x2_q(w9u{$Qv*l#;_9C9x#5Aj!IS+@`HYzTytBMQiNU>V z!pzF5OZlY%@y7jUtwL0+>Le}VSxmD?J|m{O_0RpLBR5O810@l=EXpu1eGuV8^a*-U zSC=F%Ab8iTy3D#pp0tNA$q8WS09K?pN|=#LU^TWUKEQF09&bM`$?<1?@PyMqJ4|p? zW8WXjD5W%z?8kfpJHKcDP~Ihb(z2%6+q?1cu`)0gj5dsvsuPPXVk3Ie{kgZ!o@&f8 zalM!Y`r{GrxswR{z0n^Pv9J0Fa9U~Hwue{+^vZb2CEno|m2SMdk>E@)D0n5cNvcsT z{;|zW3ytm?F!7D#8rv0+Q&d1ml)1=a=5DcNU%CAsMVke{mK;9RyI%Cn2)a`yp>t9A zQj@9z#5(D&J)SUl!CTkG3n0!d!};Y^C9SED;EUbW87v}7B#dQ=XLSkQXES7PjZPI} zI<~q;i2r2lEak|Q;LgMp-=9zG$mq!#^!I^Pf!SHUjPg865K5DnGRZt# zUsx7`azyB&Pw?_qaWRTTSEh)_8v22d9UqCvS&@OQclg$>p-MmN(_14-dY3m|n&-`2 ze+lCq+tfr#6@u?ey`h%{GFo>wh$PRgk8>;75v2u5YLzp#0Tb95U}$V0p~9W5SHy$O zU6?cF(t95~&% zw6Sj`jd};Jx+@LSN1Plfwk~uT<;J+@cls2i_~|C{ zb)A}ph0!JKH;Srylke{$liMS`WKfU8i_esUOQhx4S(k1 zM)yeBRM#8X;ngoh2e<9ep`gtxh@TE$5s_-2@7-()bsPvNlY71c=jV zDKsS*k>%X&`PCPC-$w}^rsAiB&fvxpX7yZ|&p%*(kYzqjmet6O%r1C*e%N#|nO-KO zZ=x^{TO&QN)&o*`-Y&sXx*lo#VR@nae5u~h&{AB}jWD#E;w*Na6~ZHj0+LnS5kySf zJJL0vXXU+ta*^bQg>s_YJ?yJ_F2>k~AXE3(>9g{#Pp2deH_lqkD0K16b&Q+zGZ<%1 zZhll!-ZDyaifP)Z3PdP0-=y1m z;ST1rnsg7RWc^eVdn_vlyLx0|bku8csQHO@NBw^s74|9*x+W#nPsQ^}KL0icL9I%4 zZN-}MOV8LDCZ}fG#9I6@LR=etyBQ-LMue_AW?S$sM+BFR@UIOc%J@I(4t3+0uN1Qb za@nKH*{!>PzO5tkyJyrGRHEDSOu!eeN9Q0tNB@97*Sx8Sv-ni*hR+YQ&2{QR9}{cX z0qQ`%gx5K32S)+7bfuG#-Vg8a`Qvj5V4!QkoRh)vPx$o>-fgQV+jbl3y@DA3y2*tC zN>Y=O`LqbX*H)6bdRd3gw;%MM$74Z4Rc9&s$4z_I5{mxeZVcjkBHO%^}W|ag8+v~{Ph;WZgf;{Mg6N#reAjh z;ex;JRwDHQhrKFD_^-78s_0LLqU@NVUm|Ec1cO9L9C|$t8r?@5tZ_$rU`HF+qmAjK z4bk|2F`ECUt^G$j6|Z$DouUk;o*>D3P~`95(nOe`1m(l-+5{tN*T3})Ff576Oa~96 zXt$1Gz{xy-Q9(?GI6nrF$Rm6pKbnWnOLO!Z7s6bYJ%;H^1RruwCqe*0;uNqbMmPyX z1s_Qgr1>2jCUF4fqh{`#7#z6d|6i?e4AxUdnaXiYM$!c;&C!&S-)Tf-DKrqweiG<# z4y?%#0G~2-0ouuj3pk8QJq$|y9V~c>x`4D~Fm(qeiZ&vsJD{dTC74X+5jxm`qroJ< z6KG;M(Bf3={`a^0RO~e)DmDOpIQY@uJ-=gN*QkTPL>~?=zt$m0CD=Eo5~x^EVr5ef zck!r;?ROG@C6(lQ${`6?7O99zQs+n|DNZ?@#ZkqZ@8}WIJaeKFtU57p{Pz?vw5bq) ztrwN3$cyFQ*&Ka~ekXH=Q^|_p449DN7>XGR<-s_l{et?XL1^nSNofLyk5r{WG{z!nXhOZuhxi4+o&Ega2a!Afo%Zg0 zkEl2;4UHz{MTh*h){{t#K2iW9l>UqVd^&_S19LSULOtcVkR8J`3ho3O4Gq_CxCl&4 Lx)_5Y>OlBE%_dKL diff --git a/contracts/credit-manager/tests/helpers/mock_entity_info.rs b/contracts/credit-manager/tests/helpers/mock_entity_info.rs index bb492ebcf..2106c9105 100644 --- a/contracts/credit-manager/tests/helpers/mock_entity_info.rs +++ b/contracts/credit-manager/tests/helpers/mock_entity_info.rs @@ -129,7 +129,7 @@ pub fn lp_token_info() -> CoinInfo { min_lb: Decimal::percent(2u64), max_lb: Decimal::percent(10u64), }, - protocol_liquidation_fee: Decimal::percent(2u64), + protocol_liquidation_fee: Decimal::percent(40u64), whitelisted: true, hls: Some(HlsParamsUnchecked { max_loan_to_value: Decimal::from_str("0.75").unwrap(), diff --git a/contracts/credit-manager/tests/test_liquidate_deposit.rs b/contracts/credit-manager/tests/test_liquidate_deposit.rs index d228dd0d8..38757d196 100644 --- a/contracts/credit-manager/tests/test_liquidate_deposit.rs +++ b/contracts/credit-manager/tests/test_liquidate_deposit.rs @@ -525,7 +525,7 @@ fn target_health_factor_reached_after_max_debt_repayed() { let atom_balance = get_coin("uatom", &position.deposits); assert_eq!(atom_balance.amount, Uint128::new(56)); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(2631)); + assert_eq!(osmo_balance.amount, Uint128::new(2628)); // Assert rewards-collector's new position let rewards_collector_acc_id = mock.query_rewards_collector_account(); @@ -533,7 +533,7 @@ fn target_health_factor_reached_after_max_debt_repayed() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let atom_balance = get_coin("uosmo", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(1)); + assert_eq!(atom_balance.amount, Uint128::new(4)); // Assert HF for liquidatee let account_kind = mock.query_account_kind(&liquidatee_account_id); @@ -619,7 +619,7 @@ fn debt_amount_adjusted_to_total_debt_for_denom() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(971)); + assert_eq!(osmo_balance.amount, Uint128::new(968)); // Assert rewards-collector's new position let rewards_collector_acc_id = mock.query_rewards_collector_account(); @@ -627,7 +627,7 @@ fn debt_amount_adjusted_to_total_debt_for_denom() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let atom_balance = get_coin("uosmo", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(1)); + assert_eq!(atom_balance.amount, Uint128::new(4)); // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); @@ -705,7 +705,7 @@ fn debt_amount_adjusted_to_max_allowed_by_request_coin() { let atom_balance = get_coin("uatom", &position.deposits); assert_eq!(atom_balance.amount, Uint128::new(22)); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(2975)); + assert_eq!(osmo_balance.amount, Uint128::new(2972)); // Assert rewards-collector's new position let rewards_collector_acc_id = mock.query_rewards_collector_account(); @@ -713,7 +713,7 @@ fn debt_amount_adjusted_to_max_allowed_by_request_coin() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let atom_balance = get_coin("uosmo", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(1)); + assert_eq!(atom_balance.amount, Uint128::new(4)); // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); @@ -790,7 +790,7 @@ fn debt_amount_no_adjustment() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(2391)); + assert_eq!(osmo_balance.amount, Uint128::new(2388)); // Assert rewards-collector's new position let rewards_collector_acc_id = mock.query_rewards_collector_account(); @@ -798,7 +798,7 @@ fn debt_amount_no_adjustment() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let atom_balance = get_coin("uosmo", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(1)); + assert_eq!(atom_balance.amount, Uint128::new(4)); // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); @@ -888,7 +888,7 @@ fn improve_hf_but_acc_unhealthy() { assert_eq!(position.deposits.len(), 2); assert_eq!(position.debts.len(), 0); let osmo_balance = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_balance.amount, Uint128::new(1229)); + assert_eq!(osmo_balance.amount, Uint128::new(1228)); let jake_balance = get_coin("ujake", &position.deposits); assert_eq!(jake_balance.amount, Uint128::new(18)); @@ -898,7 +898,7 @@ fn improve_hf_but_acc_unhealthy() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let atom_balance = get_coin("uosmo", &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(3)); + assert_eq!(atom_balance.amount, Uint128::new(4)); // Liq HF should improve let health = mock.query_health(&liquidatee_account_id, account_kind, ActionKind::Liquidation); diff --git a/contracts/credit-manager/tests/test_liquidate_lend.rs b/contracts/credit-manager/tests/test_liquidate_lend.rs index e037e9cfa..b53eacc2f 100644 --- a/contracts/credit-manager/tests/test_liquidate_lend.rs +++ b/contracts/credit-manager/tests/test_liquidate_lend.rs @@ -245,14 +245,14 @@ fn lent_position_partially_liquidated() { assert_eq!(position.debts.len(), 0); assert_eq!(position.deposits.len(), 1); let osmo_deposited = get_coin("uosmo", &position.deposits); - assert_eq!(osmo_deposited.amount, Uint128::new(403)); + assert_eq!(osmo_deposited.amount, Uint128::new(400)); // Assert rewards-collector's new position let rewards_collector_acc_id = mock.query_rewards_collector_account(); let position = mock.query_positions(&rewards_collector_acc_id); assert_eq!(position.deposits.len(), 1); let rc_osmo_deposited = get_coin("uosmo", &position.deposits); - assert_eq!(rc_osmo_deposited.amount, Uint128::new(1)); + assert_eq!(rc_osmo_deposited.amount, Uint128::new(4)); assert_eq!(position.lends.len(), 0); assert_eq!(position.debts.len(), 0); diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index 85a8a00e3..839208968 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -662,7 +662,7 @@ fn liquidate_unlocking_liquidation_order() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let lp_balance = get_coin(&lp_token.denom, &position.deposits); - assert_eq!(lp_balance.amount, Uint128::new(88)); + assert_eq!(lp_balance.amount, Uint128::new(86)); // Assert rewards-collector's new position let rewards_collector_acc_id = mock.query_rewards_collector_account(); @@ -670,7 +670,7 @@ fn liquidate_unlocking_liquidation_order() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); let lp_balance = get_coin(&lp_token.denom, &position.deposits); - assert_eq!(lp_balance.amount, Uint128::new(1)); + assert_eq!(lp_balance.amount, Uint128::new(3)); // Liq HF should improve let account_kind = mock.query_account_kind(&liquidatee_account_id); @@ -776,7 +776,7 @@ fn liquidation_calculation_adjustment() { let jake_balance = get_coin("ujake", &position.deposits); assert_eq!(jake_balance.amount, Uint128::new(411)); let atom_balance = get_coin(&lp_token.denom, &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(197)); + assert_eq!(atom_balance.amount, Uint128::new(191)); assert_eq!(position.debts.len(), 0); // Assert rewards-collector's new position @@ -784,7 +784,7 @@ fn liquidation_calculation_adjustment() { let position = mock.query_positions(&rewards_collector_acc_id); assert_eq!(position.deposits.len(), 1); let atom_balance = get_coin(&lp_token.denom, &position.deposits); - assert_eq!(atom_balance.amount, Uint128::new(1)); + assert_eq!(atom_balance.amount, Uint128::new(7)); assert_eq!(position.debts.len(), 0); // Liq HF should improve From 572ceff8085862e363a2b8d867f18a8db5699bff Mon Sep 17 00:00:00 2001 From: piobab Date: Fri, 1 Sep 2023 12:47:34 +0200 Subject: [PATCH 208/218] MP-3298. Oak no. 7. (#187) Oak 7. Remove rewards-collector setup in migration. --- .../credit-manager/src/migrations/v2_0_0.rs | 3 +-- contracts/credit-manager/src/query.rs | 2 +- .../credit-manager/tests/helpers/mock_env.rs | 2 +- .../credit-manager/tests/test_migration_v2.rs | 19 +++----------- .../tests/test_update_config.rs | 15 +++++++++-- packages/rover/src/msg/migrate.rs | 3 +-- packages/rover/src/msg/query.rs | 7 +++-- .../mars-credit-manager.json | 26 ++++++++++++++++--- .../mars-mock-credit-manager.json | 26 ++++++++++++++++--- .../MarsCreditManager.client.ts | 1 + .../MarsCreditManager.message-composer.ts | 1 + .../MarsCreditManager.react-query.ts | 1 + .../MarsCreditManager.types.ts | 6 ++++- .../MarsMockCreditManager.client.ts | 1 + .../MarsMockCreditManager.message-composer.ts | 1 + .../MarsMockCreditManager.react-query.ts | 1 + .../MarsMockCreditManager.types.ts | 6 ++++- 17 files changed, 87 insertions(+), 34 deletions(-) diff --git a/contracts/credit-manager/src/migrations/v2_0_0.rs b/contracts/credit-manager/src/migrations/v2_0_0.rs index a66823c3b..487d3fbff 100644 --- a/contracts/credit-manager/src/migrations/v2_0_0.rs +++ b/contracts/credit-manager/src/migrations/v2_0_0.rs @@ -5,7 +5,7 @@ use mars_rover::{error::ContractResult, msg::migrate::V2Updates}; use crate::{ contract::{CONTRACT_NAME, CONTRACT_VERSION}, - state::{HEALTH_CONTRACT, INCENTIVES, OWNER, PARAMS, REWARDS_COLLECTOR, SWAPPER}, + state::{HEALTH_CONTRACT, INCENTIVES, OWNER, PARAMS, SWAPPER}, }; const FROM_VERSION: &str = "1.0.0"; @@ -42,7 +42,6 @@ pub fn migrate(deps: DepsMut, env: Env, updates: V2Updates) -> ContractResult ContractResult { swapper: SWAPPER.load(deps.storage)?.address().into(), zapper: ZAPPER.load(deps.storage)?.address().into(), health_contract: HEALTH_CONTRACT.load(deps.storage)?.address().into(), - rewards_collector: REWARDS_COLLECTOR.may_load(deps.storage)?.map(|rc| rc.address), + rewards_collector: REWARDS_COLLECTOR.may_load(deps.storage)?, }) } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 96cc485ea..c748a51bc 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -457,7 +457,7 @@ impl MockEnv { .query_wasm_smart( config.account_nft.unwrap(), &NftQueryMsg::Tokens { - owner: config.rewards_collector.unwrap(), + owner: config.rewards_collector.unwrap().address, start_after: None, limit: None, }, diff --git a/contracts/credit-manager/tests/test_migration_v2.rs b/contracts/credit-manager/tests/test_migration_v2.rs index 8a40fb81f..37f12abc0 100644 --- a/contracts/credit-manager/tests/test_migration_v2.rs +++ b/contracts/credit-manager/tests/test_migration_v2.rs @@ -11,7 +11,7 @@ use mars_credit_manager::{ use mars_rover::{ adapters::{ health::HealthContractUnchecked, incentives::IncentivesUnchecked, params::ParamsUnchecked, - rewards_collector::RewardsCollector, swap::SwapperUnchecked, + swap::SwapperUnchecked, }, error::ContractError, msg::{migrate::V2Updates, MigrateMsg}, @@ -32,10 +32,6 @@ fn wrong_contract_name() { params: ParamsUnchecked::new("params".to_string()), incentives: IncentivesUnchecked::new("incentives".to_string()), swapper: SwapperUnchecked::new("swapper".to_string()), - rewards_collector: RewardsCollector { - address: "xyz".to_string(), - account_id: "123".to_string(), - }, }), ) .unwrap_err(); @@ -63,10 +59,6 @@ fn wrong_contract_version() { params: ParamsUnchecked::new("params".to_string()), incentives: IncentivesUnchecked::new("incentives".to_string()), swapper: SwapperUnchecked::new("swapper".to_string()), - rewards_collector: RewardsCollector { - address: "xyz".to_string(), - account_id: "123".to_string(), - }, }), ) .unwrap_err(); @@ -103,10 +95,6 @@ fn successful_migration() { let params = "params_addr_456".to_string(); let incentives = "incentives_addr_789".to_string(); let swapper = "swapper_addr_012".to_string(); - let rewards_collector = RewardsCollector { - address: "rewards_collector_addr".to_string(), - account_id: "4117".to_string(), - }; migrate( deps.as_mut(), @@ -116,7 +104,6 @@ fn successful_migration() { params: ParamsUnchecked::new(params.clone()), incentives: IncentivesUnchecked::new(incentives.clone()), swapper: SwapperUnchecked::new(swapper.clone()), - rewards_collector: rewards_collector.clone(), }), ) .unwrap(); @@ -134,8 +121,8 @@ fn successful_migration() { let set_swapper = SWAPPER.load(deps.as_ref().storage).unwrap().address().to_string(); assert_eq!(swapper, set_swapper); - let set_rewards = REWARDS_COLLECTOR.load(deps.as_ref().storage).unwrap(); - assert_eq!(rewards_collector, set_rewards); + let set_rewards = REWARDS_COLLECTOR.may_load(deps.as_ref().storage).unwrap(); + assert_eq!(None, set_rewards); let o = OWNER.query(deps.as_ref().storage).unwrap(); assert_eq!(old_owner.to_string(), o.owner.unwrap()); diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 3b57f6a55..81d63bc26 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -5,10 +5,12 @@ use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ adapters::{ health::HealthContractUnchecked, incentives::IncentivesUnchecked, oracle::OracleUnchecked, - red_bank::RedBankUnchecked, swap::SwapperBase, zapper::ZapperBase, + red_bank::RedBankUnchecked, rewards_collector::RewardsCollector, swap::SwapperBase, + zapper::ZapperBase, }, msg::instantiate::ConfigUpdates, }; +use mars_rover_health_types::AccountKind; use crate::helpers::{mock_oracle_contract, mock_red_bank_contract, MockEnv}; @@ -98,7 +100,16 @@ fn update_config_works_with_full_config() { assert_eq!(&new_config.health_contract, new_health_contract.address()); assert_ne!(new_config.health_contract, original_config.health_contract); - assert_eq!(new_config.rewards_collector.clone().unwrap(), new_rewards_collector); + let rc_accounts = mock.query_accounts(&new_rewards_collector, None, None); + let rc_account = rc_accounts.first().unwrap(); + assert_eq!(rc_account.kind, AccountKind::Default); + assert_eq!( + new_config.rewards_collector.clone().unwrap(), + RewardsCollector { + address: new_rewards_collector, + account_id: rc_account.id.clone() + } + ); assert_ne!(new_config.rewards_collector, original_config.rewards_collector); assert_eq!(&new_config.incentives, new_incentives.address()); diff --git a/packages/rover/src/msg/migrate.rs b/packages/rover/src/msg/migrate.rs index 0096f4756..cb9403fd3 100644 --- a/packages/rover/src/msg/migrate.rs +++ b/packages/rover/src/msg/migrate.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::cw_serde; use crate::adapters::{ health::HealthContractUnchecked, incentives::IncentivesUnchecked, params::ParamsUnchecked, - rewards_collector::RewardsCollector, swap::SwapperUnchecked, + swap::SwapperUnchecked, }; #[cw_serde] @@ -11,7 +11,6 @@ pub struct V2Updates { pub params: ParamsUnchecked, pub incentives: IncentivesUnchecked, pub swapper: SwapperUnchecked, - pub rewards_collector: RewardsCollector, } #[cw_serde] diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index fd0d0b002..7d0fed6a7 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -3,7 +3,10 @@ use cosmwasm_std::{Coin, Uint128}; use mars_owner::OwnerResponse; use crate::{ - adapters::vault::{Vault, VaultPosition, VaultUnchecked}, + adapters::{ + rewards_collector::RewardsCollector, + vault::{Vault, VaultPosition, VaultUnchecked}, + }, traits::Coins, }; @@ -166,7 +169,7 @@ pub struct ConfigResponse { pub swapper: String, pub zapper: String, pub health_contract: String, - pub rewards_collector: Option, + pub rewards_collector: Option, } #[cw_serde] diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 68873c154..e7b7d9eec 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -2622,9 +2622,13 @@ "type": "string" }, "rewards_collector": { - "type": [ - "string", - "null" + "anyOf": [ + { + "$ref": "#/definitions/RewardsCollector" + }, + { + "type": "null" + } ] }, "swapper": { @@ -2671,6 +2675,22 @@ }, "additionalProperties": false }, + "RewardsCollector": { + "type": "object", + "required": [ + "account_id", + "address" + ], + "properties": { + "account_id": { + "type": "string" + }, + "address": { + "type": "string" + } + }, + "additionalProperties": false + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index c1d0eb04c..9e792b32a 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -1078,9 +1078,13 @@ "type": "string" }, "rewards_collector": { - "type": [ - "string", - "null" + "anyOf": [ + { + "$ref": "#/definitions/RewardsCollector" + }, + { + "type": "null" + } ] }, "swapper": { @@ -1127,6 +1131,22 @@ }, "additionalProperties": false }, + "RewardsCollector": { + "type": "object", + "required": [ + "account_id", + "address" + ], + "properties": { + "account_id": { + "type": "string" + }, + "address": { + "type": "string" + } + }, + "additionalProperties": false + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index ec409412a..4564eb356 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -57,6 +57,7 @@ import { VaultPositionResponseItem, ConfigResponse, OwnerResponse, + RewardsCollector, ArrayOfCoin, Positions, DebtAmount, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index eeb49b480..71478e495 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -58,6 +58,7 @@ import { VaultPositionResponseItem, ConfigResponse, OwnerResponse, + RewardsCollector, ArrayOfCoin, Positions, DebtAmount, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index bad5a5421..1211afe59 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -58,6 +58,7 @@ import { VaultPositionResponseItem, ConfigResponse, OwnerResponse, + RewardsCollector, ArrayOfCoin, Positions, DebtAmount, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 6ee7610d2..8e52bac5b 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -499,7 +499,7 @@ export interface ConfigResponse { ownership: OwnerResponse params: string red_bank: string - rewards_collector?: string | null + rewards_collector?: RewardsCollector | null swapper: string zapper: string } @@ -510,6 +510,10 @@ export interface OwnerResponse { owner?: string | null proposed?: string | null } +export interface RewardsCollector { + account_id: string + address: string +} export type ArrayOfCoin = Coin[] export interface Positions { account_id: string diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index f9edbc575..fe6cb6899 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -38,6 +38,7 @@ import { VaultPositionResponseItem, ConfigResponse, OwnerResponse, + RewardsCollector, ArrayOfCoin, VaultPositionValue, CoinValue, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index c376f92ba..815a5a594 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -39,6 +39,7 @@ import { VaultPositionResponseItem, ConfigResponse, OwnerResponse, + RewardsCollector, ArrayOfCoin, VaultPositionValue, CoinValue, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index 764e05de4..f0be88f9c 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -39,6 +39,7 @@ import { VaultPositionResponseItem, ConfigResponse, OwnerResponse, + RewardsCollector, ArrayOfCoin, VaultPositionValue, CoinValue, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 4c8d97dae..54a739f7d 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -167,7 +167,7 @@ export interface ConfigResponse { ownership: OwnerResponse params: string red_bank: string - rewards_collector?: string | null + rewards_collector?: RewardsCollector | null swapper: string zapper: string } @@ -178,6 +178,10 @@ export interface OwnerResponse { owner?: string | null proposed?: string | null } +export interface RewardsCollector { + account_id: string + address: string +} export type ArrayOfCoin = Coin[] export interface VaultPositionValue { base_coin: CoinValue From 1fb8d3e953263ec1d875fc6ff84c44d8096ef6d8 Mon Sep 17 00:00:00 2001 From: piobab Date: Mon, 4 Sep 2023 11:38:43 +0200 Subject: [PATCH 209/218] MP-3301. Oak no. 10. (#193) * Add credit manager add to health contract instantiation. * Add nicer msg if no cm set. --- .../credit-manager/tests/helpers/mock_env.rs | 1 + contracts/health/src/contract.rs | 5 ++ contracts/health/src/querier.rs | 8 ++- .../health/tests/helpers/mock_env_builder.rs | 1 + contracts/health/tests/test_health_values.rs | 2 +- contracts/health/tests/test_instantiate.rs | 60 +++++++++++++++++++ packages/health-types/src/msg.rs | 3 + .../mars-rover-health-types.json | 7 +++ .../mars-rover-health/mars-rover-health.json | 7 +++ .../MarsRoverHealthTypes.types.ts | 1 + .../MarsRoverHealth.types.ts | 1 + 11 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 contracts/health/tests/test_instantiate.rs diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index c748a51bc..6528f58e8 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -1055,6 +1055,7 @@ impl MockEnvBuilder { owner.clone(), &HealthInstantiateMsg { owner: owner.to_string(), + credit_manager: None, }, &[], "mock-health-contract", diff --git a/contracts/health/src/contract.rs b/contracts/health/src/contract.rs index de496ae3c..cfa0ff359 100644 --- a/contracts/health/src/contract.rs +++ b/contracts/health/src/contract.rs @@ -31,6 +31,11 @@ pub fn instantiate( }, )?; + if let Some(cm) = msg.credit_manager { + let cm = deps.api.addr_validate(&cm)?; + CREDIT_MANAGER.save(deps.storage, &cm)?; + } + Ok(Response::default()) } diff --git a/contracts/health/src/querier.rs b/contracts/health/src/querier.rs index 0e3fe5c20..678859316 100644 --- a/contracts/health/src/querier.rs +++ b/contracts/health/src/querier.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Deps, QuerierWrapper, StdResult}; +use cosmwasm_std::{Addr, Deps, QuerierWrapper, StdError, StdResult}; use mars_params::types::vault::VaultConfig; use mars_rover::{ adapters::{oracle::Oracle, params::Params, vault::Vault}, @@ -17,7 +17,11 @@ pub struct HealthQuerier<'a> { impl<'a> HealthQuerier<'a> { pub fn new(deps: &'a Deps) -> StdResult { - let credit_manager = CREDIT_MANAGER.load(deps.storage)?; + let credit_manager = CREDIT_MANAGER.load(deps.storage).map_err(|_| { + StdError::generic_err( + "Credit Manager contract is currently not set up in the health contract", + ) + })?; let config: ConfigResponse = deps.querier.query_wasm_smart(credit_manager.to_string(), &CmQueryMsg::Config {})?; diff --git a/contracts/health/tests/helpers/mock_env_builder.rs b/contracts/health/tests/helpers/mock_env_builder.rs index d6561be5a..0d312178e 100644 --- a/contracts/health/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/helpers/mock_env_builder.rs @@ -248,6 +248,7 @@ impl MockEnvBuilder { self.deployer.clone(), &InstantiateMsg { owner: self.deployer.clone().into(), + credit_manager: self.cm_contract.clone().map(|cm| cm.into()), }, &[], "mock-health-contract", diff --git a/contracts/health/tests/test_health_values.rs b/contracts/health/tests/test_health_values.rs index 58b0f152e..57556f3e4 100644 --- a/contracts/health/tests/test_health_values.rs +++ b/contracts/health/tests/test_health_values.rs @@ -30,7 +30,7 @@ fn raises_when_credit_manager_not_set() { assert_eq!( err, StdError::generic_err( - "Querier contract error: cosmwasm_std::addresses::Addr not found".to_string() + "Querier contract error: Generic error: Credit Manager contract is currently not set up in the health contract".to_string() ) ); } diff --git a/contracts/health/tests/test_instantiate.rs b/contracts/health/tests/test_instantiate.rs new file mode 100644 index 000000000..15afdae22 --- /dev/null +++ b/contracts/health/tests/test_instantiate.rs @@ -0,0 +1,60 @@ +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; +use mars_rover_health::{ + contract::instantiate, + state::{CREDIT_MANAGER, OWNER}, +}; +use mars_rover_health_types::InstantiateMsg; + +pub mod helpers; + +#[test] +fn instantiate_without_credit_manager() { + let mut deps = mock_dependencies(); + + instantiate( + deps.as_mut(), + mock_env(), + mock_info("deployer", &[]), + InstantiateMsg { + owner: "owner".to_string(), + credit_manager: None, + }, + ) + .unwrap(); + + let o = OWNER.query(deps.as_ref().storage).unwrap(); + assert_eq!(o.owner.unwrap(), "owner".to_string()); + assert!(o.proposed.is_none()); + assert!(o.initialized); + assert!(!o.abolished); + assert!(o.emergency_owner.is_none()); + + let cm = CREDIT_MANAGER.may_load(deps.as_ref().storage).unwrap(); + assert_eq!(cm, None); +} + +#[test] +fn instantiate_with_credit_manager() { + let mut deps = mock_dependencies(); + + instantiate( + deps.as_mut(), + mock_env(), + mock_info("deployer", &[]), + InstantiateMsg { + owner: "owner".to_string(), + credit_manager: Some("credit_manager_1234".to_string()), + }, + ) + .unwrap(); + + let o = OWNER.query(deps.as_ref().storage).unwrap(); + assert_eq!(o.owner.unwrap(), "owner".to_string()); + assert!(o.proposed.is_none()); + assert!(o.initialized); + assert!(!o.abolished); + assert!(o.emergency_owner.is_none()); + + let cm = CREDIT_MANAGER.may_load(deps.as_ref().storage).unwrap(); + assert_eq!(cm.unwrap(), "credit_manager_1234".to_string()); +} diff --git a/packages/health-types/src/msg.rs b/packages/health-types/src/msg.rs index 248b2fb55..948bbe894 100644 --- a/packages/health-types/src/msg.rs +++ b/packages/health-types/src/msg.rs @@ -8,6 +8,9 @@ use crate::AccountKind; pub struct InstantiateMsg { /// The address with privileged access to update config pub owner: String, + + /// Credit Manager contract address + pub credit_manager: Option, } #[cw_serde] diff --git a/schemas/mars-rover-health-types/mars-rover-health-types.json b/schemas/mars-rover-health-types/mars-rover-health-types.json index 19acb2622..f1f78b3a5 100644 --- a/schemas/mars-rover-health-types/mars-rover-health-types.json +++ b/schemas/mars-rover-health-types/mars-rover-health-types.json @@ -10,6 +10,13 @@ "owner" ], "properties": { + "credit_manager": { + "description": "Credit Manager contract address", + "type": [ + "string", + "null" + ] + }, "owner": { "description": "The address with privileged access to update config", "type": "string" diff --git a/schemas/mars-rover-health/mars-rover-health.json b/schemas/mars-rover-health/mars-rover-health.json index 9dcdb23ae..6bf5c7279 100644 --- a/schemas/mars-rover-health/mars-rover-health.json +++ b/schemas/mars-rover-health/mars-rover-health.json @@ -10,6 +10,13 @@ "owner" ], "properties": { + "credit_manager": { + "description": "Credit Manager contract address", + "type": [ + "string", + "null" + ] + }, "owner": { "description": "The address with privileged access to update config", "type": "string" diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts index 2baa48f36..775daa1de 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts @@ -6,6 +6,7 @@ */ export interface InstantiateMsg { + credit_manager?: string | null owner: string } export type ExecuteMsg = diff --git a/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts b/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts index 2baa48f36..775daa1de 100644 --- a/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts +++ b/scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts @@ -6,6 +6,7 @@ */ export interface InstantiateMsg { + credit_manager?: string | null owner: string } export type ExecuteMsg = From 3b7dbec0d2b41efedd0477c5381bdc3964e95a6a Mon Sep 17 00:00:00 2001 From: piobab Date: Tue, 5 Sep 2023 09:30:51 +0200 Subject: [PATCH 210/218] MP-3295. Oak no. 4. Don't check HF for deposit and repay. (#192) * Don't check HF for deposit and repay. * Update schema. * Assert errors it comes from price query for HF. --- contracts/credit-manager/src/execute.rs | 33 ++- .../credit-manager/tests/helpers/mock_env.rs | 14 ++ .../tests/test_no_health_check.rs | 226 ++++++++++++++++++ contracts/mock-oracle/src/contract.rs | 12 + contracts/mock-oracle/src/msg.rs | 8 + .../mars-mock-oracle/mars-mock-oracle.json | 25 ++ .../mars-mock-oracle/MarsMockOracle.client.ts | 39 +++ .../MarsMockOracle.message-composer.ts | 38 +++ .../MarsMockOracle.react-query.ts | 23 ++ .../mars-mock-oracle/MarsMockOracle.types.ts | 13 +- 10 files changed, 423 insertions(+), 8 deletions(-) create mode 100644 contracts/credit-manager/tests/test_no_health_check.rs diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 48eb5cf7d..641eaaded 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -74,7 +74,27 @@ pub fn dispatch_actions( let mut response = Response::new(); let mut callbacks: Vec = vec![]; let mut received_coins = Coins::try_from(info.funds)?; - let prev_health_state = query_health_state(deps.as_ref(), account_id, ActionKind::Default)?; + + // deposit / repay actions don't require health check. + // It allows users to save some positions in cases of extreme volatility. + let no_health_check = actions.iter().all(|action| { + matches!( + action, + Action::Deposit(..) + | Action::Repay { + recipient_account_id: None, + .. + } + ) + }); + + // If needed (i.e. if health check is required), we query the health state + let prev_health_state = if !no_health_check { + let health_state = query_health_state(deps.as_ref(), account_id, ActionKind::Default)?; + Some(health_state) + } else { + None + }; // We use a Set to record all denoms whose deposited amount may go up as the // result of any action. We invoke the AssertDepositCaps callback in the end @@ -258,15 +278,18 @@ pub fn dispatch_actions( }); } - callbacks.extend([ + if let Some(phs) = prev_health_state { // After user selected actions, we assert LTV is either: // - Healthy, if prior to actions MaxLTV health factor >= 1 or None // - Not further weakened, if prior to actions MaxLTV health factor < 1 // Else, throw error and revert all actions - CallbackMsg::AssertMaxLTV { + callbacks.push(CallbackMsg::AssertMaxLTV { account_id: account_id.to_string(), - prev_health_state, - }, + prev_health_state: phs, + }); + } + + callbacks.extend([ // After user selected actions, we assert that the relevant deposit caps // are not exceeded. CallbackMsg::AssertDepositCaps { diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 6528f58e8..f3e2dd0fb 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -299,6 +299,20 @@ impl MockEnv { .unwrap(); } + pub fn remove_price(&mut self, denom: &str, pricing: ActionKind) { + self.app + .execute_contract( + Addr::unchecked("anyone"), + self.mars_oracle.clone(), + &OracleExecuteMsg::RemovePrice { + denom: denom.to_string(), + pricing, + }, + &[], + ) + .unwrap(); + } + pub fn execute_callback(&mut self, sender: &Addr, msg: CallbackMsg) -> AnyResult { self.app.execute_contract( sender.clone(), diff --git a/contracts/credit-manager/tests/test_no_health_check.rs b/contracts/credit-manager/tests/test_no_health_check.rs new file mode 100644 index 000000000..28c402768 --- /dev/null +++ b/contracts/credit-manager/tests/test_no_health_check.rs @@ -0,0 +1,226 @@ +use cosmwasm_std::{coin, coins, Addr, StdError, Uint128}; +use mars_red_bank_types::oracle::ActionKind; +use mars_rover::{ + error::ContractError, + msg::execute::{ + Action::{Borrow, Deposit, Repay, Withdraw}, + ActionAmount, ActionCoin, + }, +}; + +use crate::helpers::{assert_err, get_coin, get_debt, uosmo_info, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn deposit_and_repay_works_without_hf_check() { + let coin_info = uosmo_info(); + + let user = Addr::unchecked("user"); + + let mut mock = MockEnv::new() + .set_params(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(1000, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 0); + assert_eq!(position.debts.len(), 0); + + // Create a debt in the account + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(get_coin(&coin_info.denom, &position.deposits).amount, Uint128::new(350)); + assert_eq!(position.debts.len(), 1); + assert_eq!(get_debt(&coin_info.denom, &position.debts).amount, Uint128::new(51)); // +1 simulated interest + + let coin_balance = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin_balance.amount, Uint128::new(350)); + + // Simulate a problem with Default pricing. The price should be used for HF check before and after bunch of the actions, + // but because of the price problem HF check is not possible. + mock.remove_price(&coin_info.denom, ActionKind::Default); + + // Deposit should pass. HF check should be skipped + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(coin_info.to_coin(34))], + &[coin(34, &coin_info.denom)], + ) + .unwrap(); + + // Repay part of the debt. HF check should be skipped + mock.update_credit_account( + &account_id, + &user, + vec![Repay { + recipient_account_id: None, + coin: coin_info.to_action_coin(20), + }], + &[], + ) + .unwrap(); + + // Deposit and repay in the same TX. HF check should be skipped + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(coin_info.to_coin(12)), + Repay { + recipient_account_id: None, + coin: coin_info.to_action_coin(12), + }, + ], + &[coin(12, &coin_info.denom)], + ) + .unwrap(); + + // Repay for recepient should fail because of HF check + let res = mock.update_credit_account( + &account_id, + &user, + vec![Repay { + recipient_account_id: Some("random_account_id".to_string()), + coin: coin_info.to_action_coin(20), + }], + &[], + ); + assert_err(res, ContractError::Std(StdError::generic_err( + "Querier contract error: Generic error: Querier contract error: cosmwasm_std::math::decimal::Decimal not found".to_string() + ))); + + // Deposit, repay and withdraw in the same TX. Should fail because of HF check + let res = mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(coin_info.to_coin(12)), + Repay { + recipient_account_id: None, + coin: coin_info.to_action_coin(12), + }, + Withdraw(ActionCoin { + denom: coin_info.denom.clone(), + amount: ActionAmount::AccountBalance, + }), + ], + &[coin(12, &coin_info.denom)], + ); + assert_err(res, ContractError::Std(StdError::generic_err( + "Querier contract error: Generic error: Querier contract error: cosmwasm_std::math::decimal::Decimal not found".to_string() + ))); + + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(get_coin(&coin_info.denom, &position.deposits).amount, Uint128::new(364)); + assert_eq!(position.debts.len(), 1); + assert_eq!(get_debt(&coin_info.denom, &position.debts).amount, Uint128::new(19)); + + let coin_balance = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin_balance.amount, Uint128::new(364)); +} + +#[test] +fn withdraw_works_without_hf_check_if_no_debt() { + let coin_info = uosmo_info(); + + let user = Addr::unchecked("user"); + + let mut mock = MockEnv::new() + .set_params(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(1000, coin_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 0); + assert_eq!(position.debts.len(), 0); + + // Create a debt in the account + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(coin_info.to_coin(300)), Borrow(coin_info.to_coin(50))], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 1); + assert_eq!(get_coin(&coin_info.denom, &position.deposits).amount, Uint128::new(350)); + assert_eq!(position.debts.len(), 1); + assert_eq!(get_debt(&coin_info.denom, &position.debts).amount, Uint128::new(51)); // +1 simulated interest + + let coin_balance = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin_balance.amount, Uint128::new(350)); + + // Simulate a problem with Default pricing. The price should be used for HF check before and after bunch of the actions, + // but because of the price problem HF check is not possible. + mock.remove_price(&coin_info.denom, ActionKind::Default); + + // Withdraw with existing debt in the account. Should fail because of HF check + let res = mock.update_credit_account( + &account_id, + &user, + vec![Withdraw(ActionCoin { + denom: coin_info.denom.clone(), + amount: ActionAmount::AccountBalance, + })], + &[], + ); + assert_err(res, ContractError::Std(StdError::generic_err( + "Querier contract error: Generic error: Querier contract error: cosmwasm_std::math::decimal::Decimal not found".to_string() + ))); + + // Repay full debt. HF check should be skipped + mock.update_credit_account( + &account_id, + &user, + vec![Repay { + recipient_account_id: None, + coin: coin_info.to_action_coin_full_balance(), + }], + &[], + ) + .unwrap(); + + // Withdraw if no debt in the account. HF check should be skipped + mock.update_credit_account( + &account_id, + &user, + vec![Withdraw(ActionCoin { + denom: coin_info.denom.clone(), + amount: ActionAmount::AccountBalance, + })], + &[], + ) + .unwrap(); + + let position = mock.query_positions(&account_id); + assert_eq!(position.deposits.len(), 0); + assert_eq!(position.debts.len(), 0); + + let coin_balance = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(coin_balance.amount, Uint128::zero()); +} diff --git a/contracts/mock-oracle/src/contract.rs b/contracts/mock-oracle/src/contract.rs index 89f47774c..a23b7bfc8 100644 --- a/contracts/mock-oracle/src/contract.rs +++ b/contracts/mock-oracle/src/contract.rs @@ -31,6 +31,10 @@ pub fn execute( ) -> StdResult { match msg { ExecuteMsg::ChangePrice(coin) => change_price(deps, coin), + ExecuteMsg::RemovePrice { + denom, + pricing, + } => remove_price(deps, denom, pricing), } } @@ -46,6 +50,14 @@ fn change_price(deps: DepsMut, coin: CoinPrice) -> StdResult { Ok(Response::new()) } +fn remove_price(deps: DepsMut, denom: String, pricing: ActionKind) -> StdResult { + match pricing { + ActionKind::Default => DEFAULT_COIN_PRICE.remove(deps.storage, denom), + ActionKind::Liquidation => LIQUIDATION_COIN_PRICE.remove(deps.storage, denom), + } + Ok(Response::new()) +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { diff --git a/contracts/mock-oracle/src/msg.rs b/contracts/mock-oracle/src/msg.rs index 42d7f52cc..6af6e8f3e 100644 --- a/contracts/mock-oracle/src/msg.rs +++ b/contracts/mock-oracle/src/msg.rs @@ -18,6 +18,14 @@ pub struct InstantiateMsg { pub enum ExecuteMsg { // Meant to simulate price changes for tests. Not available in prod. ChangePrice(CoinPrice), + + // Used to remove a price from the store. It can be used to simulate problem with the oracle for example circuit breakers are activated + // for Default pricing. It means that the price is not available and the contract should not allow HF check. + // This message is not available in prod. + RemovePrice { + denom: String, + pricing: ActionKind, + }, } #[cw_serde] diff --git a/schemas/mars-mock-oracle/mars-mock-oracle.json b/schemas/mars-mock-oracle/mars-mock-oracle.json index bd429132e..c94b35cc6 100644 --- a/schemas/mars-mock-oracle/mars-mock-oracle.json +++ b/schemas/mars-mock-oracle/mars-mock-oracle.json @@ -68,6 +68,31 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "remove_price" + ], + "properties": { + "remove_price": { + "type": "object", + "required": [ + "denom", + "pricing" + ], + "properties": { + "denom": { + "type": "string" + }, + "pricing": { + "$ref": "#/definitions/ActionKind" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts index 25b7eacd1..d5f553b61 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts @@ -56,6 +56,18 @@ export interface MarsMockOracleInterface extends MarsMockOracleReadOnlyInterface memo?: string, _funds?: Coin[], ) => Promise + removePrice: ( + { + denom, + pricing, + }: { + denom: string + pricing: ActionKind + }, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise } export class MarsMockOracleClient extends MarsMockOracleQueryClient @@ -71,6 +83,7 @@ export class MarsMockOracleClient this.sender = sender this.contractAddress = contractAddress this.changePrice = this.changePrice.bind(this) + this.removePrice = this.removePrice.bind(this) } changePrice = async ( @@ -102,4 +115,30 @@ export class MarsMockOracleClient _funds, ) } + removePrice = async ( + { + denom, + pricing, + }: { + denom: string + pricing: ActionKind + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + remove_price: { + denom, + pricing, + }, + }, + fee, + memo, + _funds, + ) + } } diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts index 1e7c23f47..f1da73f73 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts @@ -33,6 +33,16 @@ export interface MarsMockOracleMessage { }, _funds?: Coin[], ) => MsgExecuteContractEncodeObject + removePrice: ( + { + denom, + pricing, + }: { + denom: string + pricing: ActionKind + }, + _funds?: Coin[], + ) => MsgExecuteContractEncodeObject } export class MarsMockOracleMessageComposer implements MarsMockOracleMessage { sender: string @@ -42,6 +52,7 @@ export class MarsMockOracleMessageComposer implements MarsMockOracleMessage { this.sender = sender this.contractAddress = contractAddress this.changePrice = this.changePrice.bind(this) + this.removePrice = this.removePrice.bind(this) } changePrice = ( @@ -74,4 +85,31 @@ export class MarsMockOracleMessageComposer implements MarsMockOracleMessage { }), } } + removePrice = ( + { + denom, + pricing, + }: { + denom: string + pricing: ActionKind + }, + _funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + remove_price: { + denom, + pricing, + }, + }), + ), + funds: _funds, + }), + } + } } diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts index 41bda5bb4..d6d3dd4db 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts @@ -62,6 +62,29 @@ export function useMarsMockOraclePriceQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsMockOracleRemovePriceMutation { + client: MarsMockOracleClient + msg: { + denom: string + pricing: ActionKind + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsMockOracleRemovePriceMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.removePrice(msg, fee, memo, funds), + options, + ) +} export interface MarsMockOracleChangePriceMutation { client: MarsMockOracleClient msg: { diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts index 41a4636fb..1293fbf89 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts @@ -15,9 +15,16 @@ export interface CoinPrice { price: Decimal pricing: ActionKind } -export type ExecuteMsg = { - change_price: CoinPrice -} +export type ExecuteMsg = + | { + change_price: CoinPrice + } + | { + remove_price: { + denom: string + pricing: ActionKind + } + } export type QueryMsg = { price: { denom: string From b03eb5b58d4e41826dee4546c33ebf1b995f9fde Mon Sep 17 00:00:00 2001 From: piobab Date: Mon, 11 Sep 2023 21:07:18 +0200 Subject: [PATCH 211/218] MP-3352. Query acc kind before burn (#195) * Specify account kind for health response. * Extract account-nft types to avoid circular dependency. * Add tests for AccountKind query in account-nft contract. * Use closure. * Update schema. * Update comment. --- Cargo.lock | 17 ++++- Cargo.toml | 2 + contracts/account-nft/Cargo.toml | 14 ++-- contracts/account-nft/examples/schema.rs | 2 +- contracts/account-nft/src/contract.rs | 22 +++--- contracts/account-nft/src/error.rs | 3 + contracts/account-nft/src/execute.rs | 31 +++++++-- contracts/account-nft/src/lib.rs | 2 - .../account-nft/src/migrations/v2_0_0.rs | 6 +- contracts/account-nft/src/query.rs | 6 +- contracts/account-nft/src/state.rs | 3 +- .../tests/helpers/mock_contracts.rs | 9 +++ .../account-nft/tests/helpers/mock_env.rs | 29 +++++++- .../tests/helpers/mock_env_builder.rs | 55 ++++++++++++++- .../account-nft/tests/test_burn_allowance.rs | 68 ++++++++++++++++--- contracts/account-nft/tests/test_migration.rs | 7 +- contracts/account-nft/tests/test_mint.rs | 11 ++- .../account-nft/tests/test_proposed_minter.rs | 2 +- .../account-nft/tests/test_update_config.rs | 6 +- contracts/credit-manager/Cargo.toml | 3 +- contracts/credit-manager/src/execute.rs | 2 +- contracts/credit-manager/src/update_config.rs | 8 +-- .../credit-manager/tests/helpers/mock_env.rs | 3 +- .../tests/test_update_nft_config.rs | 10 ++- contracts/mock-credit-manager/src/contract.rs | 11 ++- contracts/mock-credit-manager/src/execute.rs | 12 +++- contracts/mock-credit-manager/src/msg.rs | 5 ++ contracts/mock-credit-manager/src/query.rs | 7 +- contracts/mock-credit-manager/src/state.rs | 3 + packages/account-nft-types/Cargo.toml | 24 +++++++ packages/account-nft-types/src/lib.rs | 2 + .../account-nft-types}/src/msg/execute.rs | 7 +- .../account-nft-types}/src/msg/instantiate.rs | 2 + .../account-nft-types}/src/msg/mod.rs | 0 .../account-nft-types}/src/msg/query.rs | 0 .../account-nft-types}/src/nft_config.rs | 3 + packages/rover/Cargo.toml | 2 +- packages/rover/src/adapters/account_nft.rs | 2 +- packages/rover/src/msg/execute.rs | 2 +- .../mars-account-nft/mars-account-nft.json | 19 ++++++ .../mars-credit-manager.json | 6 ++ .../mars-mock-credit-manager.json | 32 +++++++++ .../mars-account-nft/MarsAccountNft.types.ts | 3 + .../MarsCreditManager.types.ts | 1 + .../MarsMockCreditManager.client.ts | 41 ++++++++++- .../MarsMockCreditManager.message-composer.ts | 40 ++++++++++- .../MarsMockCreditManager.react-query.ts | 26 ++++++- .../MarsMockCreditManager.types.ts | 21 ++++-- 48 files changed, 506 insertions(+), 86 deletions(-) create mode 100644 packages/account-nft-types/Cargo.toml create mode 100644 packages/account-nft-types/src/lib.rs rename {contracts/account-nft => packages/account-nft-types}/src/msg/execute.rs (96%) rename {contracts/account-nft => packages/account-nft-types}/src/msg/instantiate.rs (95%) rename {contracts/account-nft => packages/account-nft-types}/src/msg/mod.rs (100%) rename {contracts/account-nft => packages/account-nft-types}/src/msg/query.rs (100%) rename {contracts/account-nft => packages/account-nft-types}/src/nft_config.rs (77%) diff --git a/Cargo.lock b/Cargo.lock index 47c712ba6..37340cf16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1462,14 +1462,28 @@ dependencies = [ "cw721 0.18.0", "cw721-base 0.16.0", "cw721-base 0.18.0", + "mars-account-nft-types", + "mars-mock-credit-manager", "mars-mock-rover-health", + "mars-owner 2.0.0", "mars-red-bank-types 1.2.0", "mars-rover 1.0.0", + "mars-rover 2.0.0", "mars-rover-health-types", "serde_json", "thiserror", ] +[[package]] +name = "mars-account-nft-types" +version = "2.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw721 0.18.0", + "cw721-base 0.18.0", +] + [[package]] name = "mars-address-provider" version = "1.2.0" @@ -1501,6 +1515,7 @@ dependencies = [ "cw721-base 0.18.0", "itertools 0.11.0", "mars-account-nft", + "mars-account-nft-types", "mars-address-provider", "mars-liquidation", "mars-mock-incentives", @@ -1728,7 +1743,7 @@ dependencies = [ "cw2 1.1.0", "cw721 0.18.0", "cw721-base 0.18.0", - "mars-account-nft", + "mars-account-nft-types", "mars-liquidation", "mars-owner 2.0.0", "mars-params", diff --git a/Cargo.toml b/Cargo.toml index 8e9ec371d..550d8e1e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "contracts/mock-vault", # packages + "packages/account-nft-types", "packages/health-computer", "packages/health-types", "packages/rover", @@ -64,6 +65,7 @@ tsify = "0.4.5" wasm-bindgen = "0.2.87" # mars packages +mars-account-nft-types = { version = "2.0.0", path = "./packages/account-nft-types" } mars-health = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842" } mars-liquidation = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842" } mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842" } diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 3b4338556..85502dd9a 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -25,14 +25,18 @@ cw2 = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true } cw-storage-plus = { workspace = true } +mars-account-nft-types = { workspace = true } mars-red-bank-types = { workspace = true } +mars-rover = { workspace = true } mars-rover-health-types = { workspace = true } mars-rover-old = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -anyhow = { workspace = true } -cw721-base-v16 = { package = "cw721-base", version = "0.16.0" } -cw-multi-test = { workspace = true } -mars-mock-rover-health = { workspace = true } -serde_json = { workspace = true } +anyhow = { workspace = true } +cw721-base-v16 = { package = "cw721-base", version = "0.16.0" } +cw-multi-test = { workspace = true } +mars-mock-credit-manager = { workspace = true } +mars-mock-rover-health = { workspace = true } +mars-owner = { workspace = true } +serde_json = { workspace = true } diff --git a/contracts/account-nft/examples/schema.rs b/contracts/account-nft/examples/schema.rs index b41c869ad..13fbf60ff 100644 --- a/contracts/account-nft/examples/schema.rs +++ b/contracts/account-nft/examples/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use mars_account_nft::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_account_nft_types::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index a77d10867..2846ac0cb 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -1,19 +1,19 @@ -use std::convert::TryInto; - #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, + to_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; use cw2::set_contract_version; use cw721_base::Cw721Contract; +use mars_account_nft_types::{ + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + nft_config::NftConfig, +}; use crate::{ error::ContractError, execute::{burn, mint, update_config}, migrations, - msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, - nft_config::NftConfig, query::{query_config, query_next_id}, state::{CONFIG, NEXT_ID}, }; @@ -35,17 +35,19 @@ pub fn instantiate( NEXT_ID.save(deps.storage, &1)?; - let health_contract_addr = msg - .health_contract - .as_ref() - .map(|unchecked| deps.api.addr_validate(unchecked)) - .transpose()?; + let validate_func = |contract: Option<&String>| -> StdResult> { + contract.map(|unchecked| deps.api.addr_validate(unchecked)).transpose() + }; + + let health_contract_addr = validate_func(msg.health_contract.as_ref())?; + let credit_manager_contract_addr = validate_func(msg.credit_manager_contract.as_ref())?; CONFIG.save( deps.storage, &NftConfig { max_value_for_burn: msg.max_value_for_burn, health_contract_addr, + credit_manager_contract_addr, }, )?; diff --git a/contracts/account-nft/src/error.rs b/contracts/account-nft/src/error.rs index dd4cbb98f..34c1df77d 100644 --- a/contracts/account-nft/src/error.rs +++ b/contracts/account-nft/src/error.rs @@ -21,6 +21,9 @@ pub enum ContractError { #[error("Health contract should be added to config before burns are allowed")] HealthContractNotSet, + #[error("Credit manager contract should be added to config before burns are allowed")] + CreditManagerContractNotSet, + #[error("{0}")] Version(#[from] cw2::VersionError), } diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index 23eec6f63..17b3d3039 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -6,16 +6,16 @@ use cw721_base::{ ContractError::Ownership, OwnershipError::{NoOwner, NotOwner}, }; +use mars_account_nft_types::nft_config::NftConfigUpdates; use mars_red_bank_types::oracle::ActionKind; -use mars_rover_health_types::{AccountKind, HealthValuesResponse, QueryMsg::HealthValues}; +use mars_rover::msg::QueryMsg; +use mars_rover_health_types::{HealthValuesResponse, QueryMsg::HealthValues}; use crate::{ contract::Parent, - error::{ - ContractError, - ContractError::{BaseError, BurnNotAllowed, HealthContractNotSet}, + error::ContractError::{ + self, BaseError, BurnNotAllowed, CreditManagerContractNotSet, HealthContractNotSet, }, - nft_config::NftConfigUpdates, state::{CONFIG, NEXT_ID}, }; @@ -40,13 +40,24 @@ pub fn burn( let Some(health_contract_addr) = config.health_contract_addr else { return Err(HealthContractNotSet); }; + let Some(cm_contract_addr) = config.credit_manager_contract_addr else { + return Err(CreditManagerContractNotSet); + }; + + let acc_kind: mars_rover_health_types::AccountKind = + deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: cm_contract_addr.into(), + msg: to_binary(&QueryMsg::AccountKind { + account_id: token_id.clone(), + })?, + }))?; let response: HealthValuesResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: health_contract_addr.into(), msg: to_binary(&HealthValues { account_id: token_id.clone(), - kind: AccountKind::Default, + kind: acc_kind, action: ActionKind::Default, })?, }))?; @@ -92,6 +103,14 @@ pub fn update_config( .add_attribute("value", addr.to_string()); } + if let Some(unchecked) = updates.credit_manager_contract_addr { + let addr = deps.api.addr_validate(&unchecked)?; + config.credit_manager_contract_addr = Some(addr.clone()); + response = response + .add_attribute("key", "credit_manager_contract_addr") + .add_attribute("value", addr.to_string()); + } + if let Some(max) = updates.max_value_for_burn { config.max_value_for_burn = max; response = response diff --git a/contracts/account-nft/src/lib.rs b/contracts/account-nft/src/lib.rs index 0da54de7f..0c2c364c4 100644 --- a/contracts/account-nft/src/lib.rs +++ b/contracts/account-nft/src/lib.rs @@ -2,7 +2,5 @@ pub mod contract; pub mod error; pub mod execute; pub mod migrations; -pub mod msg; -pub mod nft_config; pub mod query; pub mod state; diff --git a/contracts/account-nft/src/migrations/v2_0_0.rs b/contracts/account-nft/src/migrations/v2_0_0.rs index 05e1249f3..6705347fa 100644 --- a/contracts/account-nft/src/migrations/v2_0_0.rs +++ b/contracts/account-nft/src/migrations/v2_0_0.rs @@ -1,10 +1,10 @@ use cosmwasm_std::{DepsMut, Empty, Response}; use cw2::set_contract_version; +use mars_account_nft_types::nft_config::NftConfig; use crate::{ contract::{CONTRACT_NAME, CONTRACT_VERSION}, error::ContractError, - nft_config::NftConfig, state::CONFIG, }; @@ -31,8 +31,10 @@ pub fn migrate(deps: DepsMut) -> Result { CONFIG.save( deps.storage, &NftConfig { + // health_contract_addr and credit_manager_contract_addr can be updated via update_config max_value_for_burn: old_config_state.max_value_for_burn, - health_contract_addr: None, // this can be updated via update_config + health_contract_addr: None, + credit_manager_contract_addr: None, }, )?; diff --git a/contracts/account-nft/src/query.rs b/contracts/account-nft/src/query.rs index b1f3a3335..ed4a5fa0c 100644 --- a/contracts/account-nft/src/query.rs +++ b/contracts/account-nft/src/query.rs @@ -1,9 +1,7 @@ use cosmwasm_std::{Deps, StdResult}; +use mars_account_nft_types::nft_config::UncheckedNftConfig; -use crate::{ - nft_config::UncheckedNftConfig, - state::{CONFIG, NEXT_ID}, -}; +use crate::state::{CONFIG, NEXT_ID}; pub fn query_config(deps: Deps) -> StdResult { Ok(CONFIG.load(deps.storage)?.into()) diff --git a/contracts/account-nft/src/state.rs b/contracts/account-nft/src/state.rs index b7cb2ff90..eef9bd501 100644 --- a/contracts/account-nft/src/state.rs +++ b/contracts/account-nft/src/state.rs @@ -1,6 +1,5 @@ use cw_storage_plus::Item; - -use crate::nft_config::NftConfig; +use mars_account_nft_types::nft_config::NftConfig; pub const CONFIG: Item = Item::new("config"); pub const NEXT_ID: Item = Item::new("next_id"); diff --git a/contracts/account-nft/tests/helpers/mock_contracts.rs b/contracts/account-nft/tests/helpers/mock_contracts.rs index 20c0cfa63..119bf6b47 100644 --- a/contracts/account-nft/tests/helpers/mock_contracts.rs +++ b/contracts/account-nft/tests/helpers/mock_contracts.rs @@ -18,3 +18,12 @@ pub fn mock_health_contract() -> Box> { ); Box::new(contract) } + +pub fn mock_credit_manager_contract() -> Box> { + let contract = ContractWrapper::new( + mars_mock_credit_manager::contract::execute, + mars_mock_credit_manager::contract::instantiate, + mars_mock_credit_manager::contract::query, + ); + Box::new(contract) +} diff --git a/contracts/account-nft/tests/helpers/mock_env.rs b/contracts/account-nft/tests/helpers/mock_env.rs index 4225e6764..567dfd42a 100644 --- a/contracts/account-nft/tests/helpers/mock_env.rs +++ b/contracts/account-nft/tests/helpers/mock_env.rs @@ -7,10 +7,11 @@ use cw721_base::{ Ownership, }; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; -use mars_account_nft::{ +use mars_account_nft_types::{ msg::{ExecuteMsg, ExecuteMsg::UpdateConfig, QueryMsg}, nft_config::{NftConfigUpdates, UncheckedNftConfig}, }; +use mars_mock_credit_manager::msg::ExecuteMsg::SetAccountKindResponse; use mars_mock_rover_health::msg::ExecuteMsg::SetHealthResponse; use mars_rover_health_types::{AccountKind, HealthValuesResponse}; @@ -20,6 +21,7 @@ pub struct MockEnv { pub app: BasicApp, pub minter: Addr, pub nft_contract: Addr, + pub cm_contract: Addr, pub deployer: Addr, } @@ -32,6 +34,7 @@ impl MockEnv { deployer: Addr::unchecked("deployer"), nft_contract: None, health_contract: None, + cm_contract: None, set_health_contract: true, } } @@ -76,6 +79,7 @@ impl MockEnv { &mut self, sender: &Addr, account_id: &str, + kind: AccountKind, response: &HealthValuesResponse, ) -> AppResponse { let config = self.query_config(); @@ -86,7 +90,7 @@ impl MockEnv { Addr::unchecked(config.health_contract_addr.unwrap()), &SetHealthResponse { account_id: account_id.to_string(), - kind: AccountKind::Default, + kind, response: response.clone(), }, &[], @@ -94,6 +98,27 @@ impl MockEnv { .unwrap() } + pub fn set_account_kind_response( + &mut self, + sender: &Addr, + account_id: &str, + kind: AccountKind, + ) -> AppResponse { + let config = self.query_config(); + + self.app + .execute_contract( + sender.clone(), + Addr::unchecked(config.credit_manager_contract_addr.unwrap()), + &SetAccountKindResponse { + account_id: account_id.to_string(), + kind, + }, + &[], + ) + .unwrap() + } + pub fn mint(&mut self, token_owner: &Addr) -> AnyResult { let res = self.app.execute_contract( self.minter.clone(), diff --git a/contracts/account-nft/tests/helpers/mock_env_builder.rs b/contracts/account-nft/tests/helpers/mock_env_builder.rs index 579ab9694..41ba59612 100644 --- a/contracts/account-nft/tests/helpers/mock_env_builder.rs +++ b/contracts/account-nft/tests/helpers/mock_env_builder.rs @@ -3,8 +3,12 @@ use std::mem::take; use anyhow::Result as AnyResult; use cosmwasm_std::{Addr, Empty}; use cw_multi_test::{BasicApp, Executor}; -use mars_account_nft::msg::InstantiateMsg; +use mars_account_nft_types::msg::InstantiateMsg; +use mars_mock_credit_manager::msg::InstantiateMsg as CmMockInstantiateMsg; +use mars_owner::OwnerResponse; +use mars_rover::msg::query::ConfigResponse; +use super::mock_credit_manager_contract; use crate::helpers::{mock_health_contract, mock_nft_contract, MockEnv, MAX_VALUE_FOR_BURN}; pub struct MockEnvBuilder { @@ -13,6 +17,7 @@ pub struct MockEnvBuilder { pub minter: Option, pub health_contract: Option, pub nft_contract: Option, + pub cm_contract: Option, pub set_health_contract: bool, } @@ -21,6 +26,7 @@ impl MockEnvBuilder { Ok(MockEnv { minter: self.get_minter(), nft_contract: self.get_nft_contract(), + cm_contract: self.get_cm_contract(), deployer: self.deployer.clone(), app: take(&mut self.app), }) @@ -87,6 +93,7 @@ impl MockEnvBuilder { } else { None }; + let cm_contract = self.get_cm_contract(); let addr = self .app @@ -99,6 +106,7 @@ impl MockEnvBuilder { symbol: "MOCK".to_string(), minter, health_contract, + credit_manager_contract: Some(cm_contract.to_string()), }, &[], "mock-account-nft", @@ -107,4 +115,49 @@ impl MockEnvBuilder { .unwrap(); self.nft_contract = Some(addr); } + + fn get_cm_contract(&mut self) -> Addr { + if self.cm_contract.is_none() { + self.deploy_cm_contract() + } + self.cm_contract.clone().unwrap() + } + + fn deploy_cm_contract(&mut self) { + let contract = mock_credit_manager_contract(); + let code_id = self.app.store_code(contract); + + let cm_addr = self + .app + .instantiate_contract( + code_id, + self.deployer.clone(), + &CmMockInstantiateMsg { + config: ConfigResponse { + ownership: OwnerResponse { + owner: Some(self.deployer.to_string()), + proposed: None, + emergency_owner: None, + initialized: true, + abolished: false, + }, + red_bank: "n/a".to_string(), + incentives: "n/a".to_string(), + oracle: "n/a".to_string(), + params: "n/a".to_string(), + account_nft: None, + max_unlocking_positions: Default::default(), + swapper: "n/a".to_string(), + zapper: "n/a".to_string(), + health_contract: "n/a".to_string(), + rewards_collector: None, + }, + }, + &[], + "mock-credit-manager-contract", + Some(self.deployer.clone().into()), + ) + .unwrap(); + self.cm_contract = Some(cm_addr); + } } diff --git a/contracts/account-nft/tests/test_burn_allowance.rs b/contracts/account-nft/tests/test_burn_allowance.rs index 000fdce69..a29cce899 100644 --- a/contracts/account-nft/tests/test_burn_allowance.rs +++ b/contracts/account-nft/tests/test_burn_allowance.rs @@ -3,13 +3,12 @@ use std::ops::Add; use cosmwasm_std::{Addr, Empty, StdResult, Uint128}; use cw721::NftInfoResponse; use cw721_base::{ContractError::Ownership, OwnershipError::NotOwner}; -use mars_account_nft::{ - error::{ - ContractError, - ContractError::{BaseError, BurnNotAllowed, HealthContractNotSet}, - }, - msg::QueryMsg::NftInfo, +use mars_account_nft::error::{ + ContractError, + ContractError::{BaseError, BurnNotAllowed, HealthContractNotSet}, }; +use mars_account_nft_types::msg::QueryMsg::NftInfo; +use mars_rover_health_types::AccountKind; use crate::helpers::{below_max_for_burn, generate_health_response, MockEnv, MAX_VALUE_FOR_BURN}; @@ -21,7 +20,7 @@ fn only_token_owner_can_burn() { let user = Addr::unchecked("user"); let token_id = mock.mint(&user).unwrap(); - mock.set_health_response(&user, &token_id, &below_max_for_burn()); + mock.set_health_response(&user, &token_id, AccountKind::Default, &below_max_for_burn()); let bad_guy = Addr::unchecked("bad_guy"); let res = mock.burn(&bad_guy, &token_id); @@ -47,7 +46,12 @@ fn burn_not_allowed_if_debt_balance() { let user = Addr::unchecked("user"); let token_id = mock.mint(&user).unwrap(); - mock.set_health_response(&user, &token_id, &generate_health_response(10_000, 0)); + mock.set_health_response( + &user, + &token_id, + AccountKind::Default, + &generate_health_response(10_000, 0), + ); let res = mock.burn(&user, &token_id); let error: ContractError = res.unwrap_err().downcast().unwrap(); @@ -68,6 +72,7 @@ fn burn_not_allowed_if_too_much_collateral() { mock.set_health_response( &user, &token_id, + AccountKind::Default, &generate_health_response(0, MAX_VALUE_FOR_BURN.add(Uint128::one()).into()), ); @@ -90,6 +95,7 @@ fn burn_allowance_at_exactly_max() { mock.set_health_response( &user, &token_id, + AccountKind::Default, &generate_health_response(0, MAX_VALUE_FOR_BURN.into()), ); @@ -102,7 +108,12 @@ fn burn_allowance_when_under_max() { let user = Addr::unchecked("user"); let token_id = mock.mint(&user).unwrap(); - mock.set_health_response(&user, &token_id, &generate_health_response(0, 500)); + mock.set_health_response( + &user, + &token_id, + AccountKind::Default, + &generate_health_response(0, 500), + ); // Assert no errors on calling for NftInfo let _: NftInfoResponse = mock @@ -116,7 +127,7 @@ fn burn_allowance_when_under_max() { ) .unwrap(); - mock.set_health_response(&user, &token_id, &below_max_for_burn()); + mock.set_health_response(&user, &token_id, AccountKind::Default, &below_max_for_burn()); mock.burn(&user, &token_id).unwrap(); let res: StdResult> = mock.app.wrap().query_wasm_smart( @@ -127,3 +138,40 @@ fn burn_allowance_when_under_max() { ); res.unwrap_err(); } + +#[test] +fn burn_uses_correct_account_kind_for_health_check() { + let mut mock = MockEnv::new().build().unwrap(); + + let user = Addr::unchecked("user"); + let token_id = mock.mint(&user).unwrap(); + + // Provide different health responses for different account kinds + mock.set_health_response( + &user, + &token_id, + AccountKind::Default, + &generate_health_response(10_000, 0), + ); + mock.set_health_response( + &user, + &token_id, + AccountKind::HighLeveredStrategy, + &generate_health_response(0, 0), + ); + + // Burn should fail for default account kind + mock.set_account_kind_response(&user, &token_id, AccountKind::Default); + let res = mock.burn(&user, &token_id); + let error: ContractError = res.unwrap_err().downcast().unwrap(); + assert_eq!( + error, + BurnNotAllowed { + reason: "Account has a debt balance. Value: 10000.".to_string(), + } + ); + + // Override account kind. Burn should succeed for high levered strategy account kind + mock.set_account_kind_response(&user, &token_id, AccountKind::HighLeveredStrategy); + mock.burn(&user, &token_id).unwrap(); +} diff --git a/contracts/account-nft/tests/test_migration.rs b/contracts/account-nft/tests/test_migration.rs index d30791819..354a7ef7e 100644 --- a/contracts/account-nft/tests/test_migration.rs +++ b/contracts/account-nft/tests/test_migration.rs @@ -9,9 +9,9 @@ use cw721_base_v16::{ msg::InstantiateMsg as Cw721v16InstantiateMsg, Cw721Contract as Cw721ContractV16, }; use mars_account_nft::{ - contract::migrate, error::ContractError, migrations::v2_0_0::v1_state, nft_config::NftConfig, - state::CONFIG, + contract::migrate, error::ContractError, migrations::v2_0_0::v1_state, state::CONFIG, }; +use mars_account_nft_types::nft_config::NftConfig; pub mod helpers; @@ -153,7 +153,8 @@ fn proper_migration() { config, NftConfig { max_value_for_burn: old_max_value_for_burn, - health_contract_addr: None + health_contract_addr: None, + credit_manager_contract_addr: None } ); } diff --git a/contracts/account-nft/tests/test_mint.rs b/contracts/account-nft/tests/test_mint.rs index 0a7854a16..bfc25e947 100644 --- a/contracts/account-nft/tests/test_mint.rs +++ b/contracts/account-nft/tests/test_mint.rs @@ -2,10 +2,9 @@ use cosmwasm_std::Addr; use cw721::OwnerOfResponse; use cw721_base::{ContractError::Ownership, OwnershipError::NotOwner}; use cw_multi_test::Executor; -use mars_account_nft::{ - error::{ContractError, ContractError::BaseError}, - msg::{ExecuteMsg, QueryMsg::OwnerOf}, -}; +use mars_account_nft::error::{ContractError, ContractError::BaseError}; +use mars_account_nft_types::msg::{ExecuteMsg, QueryMsg::OwnerOf}; +use mars_rover_health_types::AccountKind; use crate::helpers::{below_max_for_burn, MockEnv}; @@ -49,9 +48,9 @@ fn id_incrementer_works_despite_burns() { assert_eq!(token_id_2, "2"); mock.assert_next_id("3"); - mock.set_health_response(&user, &token_id_1, &below_max_for_burn()); + mock.set_health_response(&user, &token_id_1, AccountKind::Default, &below_max_for_burn()); mock.burn(&user, &token_id_1).unwrap(); - mock.set_health_response(&user, &token_id_2, &below_max_for_burn()); + mock.set_health_response(&user, &token_id_2, AccountKind::Default, &below_max_for_burn()); mock.burn(&user, &token_id_2).unwrap(); mock.assert_next_id("3"); diff --git a/contracts/account-nft/tests/test_proposed_minter.rs b/contracts/account-nft/tests/test_proposed_minter.rs index 1d0990f12..df4dd0765 100644 --- a/contracts/account-nft/tests/test_proposed_minter.rs +++ b/contracts/account-nft/tests/test_proposed_minter.rs @@ -1,6 +1,6 @@ use cosmwasm_std::Addr; use cw721_base::MinterResponse; -use mars_account_nft::msg::QueryMsg; +use mars_account_nft_types::msg::QueryMsg; use crate::helpers::MockEnv; diff --git a/contracts/account-nft/tests/test_update_config.rs b/contracts/account-nft/tests/test_update_config.rs index ee4e40909..33085293b 100644 --- a/contracts/account-nft/tests/test_update_config.rs +++ b/contracts/account-nft/tests/test_update_config.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{Addr, Uint128}; -use mars_account_nft::nft_config::NftConfigUpdates; +use mars_account_nft_types::nft_config::NftConfigUpdates; use crate::helpers::MockEnv; @@ -15,6 +15,7 @@ fn only_minter_can_update_config() { &NftConfigUpdates { max_value_for_burn: None, health_contract_addr: None, + credit_manager_contract_addr: None, }, ); @@ -29,10 +30,12 @@ fn minter_can_update_config() { let new_max_burn_val = Uint128::new(4918453); let new_health_contract = "new_health_contract_123".to_string(); + let new_cm_contract = "new_cm_contract_123".to_string(); let updates = NftConfigUpdates { max_value_for_burn: Some(new_max_burn_val), health_contract_addr: Some(new_health_contract.clone()), + credit_manager_contract_addr: Some(new_cm_contract.clone()), }; mock.update_config(&mock.minter.clone(), &updates).unwrap(); @@ -40,4 +43,5 @@ fn minter_can_update_config() { let config = mock.query_config(); assert_eq!(config.max_value_for_burn, new_max_burn_val); assert_eq!(config.health_contract_addr.unwrap(), new_health_contract); + assert_eq!(config.credit_manager_contract_addr.unwrap(), new_cm_contract); } diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 9e67a4a8d..0baec6e11 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -28,7 +28,7 @@ cw-paginate = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw-vault-standard = { workspace = true } -mars-account-nft = { workspace = true } +mars-account-nft-types = { workspace = true } mars-liquidation = { workspace = true } mars-params = { workspace = true } mars-owner = { workspace = true } @@ -40,6 +40,7 @@ mars-rover-health-types = { workspace = true } anyhow = { workspace = true } cw-multi-test = { workspace = true } itertools = { workspace = true } +mars-account-nft = { workspace = true } mars-address-provider = { workspace = true } mars-mock-incentives = { workspace = true } mars-mock-oracle = { workspace = true } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 641eaaded..3007df783 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -3,7 +3,7 @@ use std::collections::BTreeSet; use cosmwasm_std::{ to_binary, Addr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdResult, WasmMsg, }; -use mars_account_nft::msg::ExecuteMsg as NftExecuteMsg; +use mars_account_nft_types::msg::ExecuteMsg as NftExecuteMsg; use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ coins::Coins, diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index c61edda5c..1437d6eab 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, Env, MessageInfo, Response, WasmMsg}; use cw721_base::Action; -use mars_account_nft::{msg::ExecuteMsg as NftExecuteMsg, nft_config::NftConfigUpdates}; +use mars_account_nft_types::{msg::ExecuteMsg as NftExecuteMsg, nft_config::NftConfigUpdates}; use mars_owner::OwnerUpdate; use mars_rover::{ adapters::rewards_collector::RewardsCollector, error::ContractResult, @@ -30,15 +30,15 @@ pub fn update_config( let account_nft = unchecked.check(deps.api)?; ACCOUNT_NFT.save(deps.storage, &account_nft)?; - // Accept minter role. NFT contract minter must have proposed Rover as a new minter first. - let accept_minter_role_msg = CosmosMsg::Wasm(WasmMsg::Execute { + // Accept ownership. NFT contract owner must have proposed Rover as a new owner first. + let accept_ownership_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: account_nft.address().into(), funds: vec![], msg: to_binary(&NftExecuteMsg::UpdateOwnership(Action::AcceptOwnership))?, }); response = response - .add_message(accept_minter_role_msg) + .add_message(accept_ownership_msg) .add_attribute("key", "account_nft") .add_attribute("value", unchecked.address()); } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index f3e2dd0fb..6097ac72d 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -9,7 +9,7 @@ use cw_vault_standard::{ extensions::lockup::{LockupQueryMsg, UnlockingPosition}, msg::{ExtensionQueryMsg, VaultStandardQueryMsg::VaultExtension}, }; -use mars_account_nft::{ +use mars_account_nft_types::{ msg::{ ExecuteMsg as NftExecuteMsg, InstantiateMsg as NftInstantiateMsg, QueryMsg as NftQueryMsg, }, @@ -1384,6 +1384,7 @@ fn deploy_nft_contract(app: &mut App, minter: &Addr) -> Addr { name: "Rover Credit Account".to_string(), symbol: "RCA".to_string(), minter: minter.to_string(), + credit_manager_contract: None, }, &[], "manager-mock-account-nft", diff --git a/contracts/credit-manager/tests/test_update_nft_config.rs b/contracts/credit-manager/tests/test_update_nft_config.rs index fe8e52dd5..91ca470e9 100644 --- a/contracts/credit-manager/tests/test_update_nft_config.rs +++ b/contracts/credit-manager/tests/test_update_nft_config.rs @@ -2,7 +2,7 @@ extern crate core; use cosmwasm_std::{Addr, Uint128}; use cw_multi_test::Executor; -use mars_account_nft::{msg::ExecuteMsg, nft_config::NftConfigUpdates}; +use mars_account_nft_types::{msg::ExecuteMsg, nft_config::NftConfigUpdates}; use mars_owner::OwnerError::NotOwner; use mars_rover::error::ContractError; @@ -36,6 +36,7 @@ fn only_owner_can_update_nft_config() { updates: NftConfigUpdates { max_value_for_burn: None, health_contract_addr: None, + credit_manager_contract_addr: None, }, }, &[], @@ -82,12 +83,14 @@ fn update_config_works_with_full_config() { let new_max_value = Some(Uint128::new(1122334455)); let new_proposed = Some(Addr::unchecked("spiderman_12345")); let new_health_contract = Some("new_health_contract_xyz".to_string()); + let new_cm_contract = Some("new_cm_contract_xyz".to_string()); mock.update_nft_config( &Addr::unchecked(mock.query_config().ownership.owner.unwrap()), Some(NftConfigUpdates { max_value_for_burn: new_max_value, health_contract_addr: new_health_contract.clone(), + credit_manager_contract_addr: new_cm_contract.clone(), }), Some(cw721_base::Action::TransferOwnership { new_owner: new_proposed.clone().unwrap().into(), @@ -99,9 +102,14 @@ fn update_config_works_with_full_config() { let new_config = mock.query_nft_config(); assert_eq!(Some(new_config.max_value_for_burn), new_max_value); assert_eq!(new_config.health_contract_addr, new_health_contract); + assert_eq!(new_config.credit_manager_contract_addr, new_cm_contract); assert_ne!(new_config.max_value_for_burn, original_config.max_value_for_burn); assert_ne!(new_config.health_contract_addr, original_config.health_contract_addr); + assert_ne!( + new_config.credit_manager_contract_addr, + original_config.credit_manager_contract_addr + ); let new_ownership = mock.query_nft_ownership(); assert_eq!(new_ownership.pending_owner, new_proposed); diff --git a/contracts/mock-credit-manager/src/contract.rs b/contracts/mock-credit-manager/src/contract.rs index 98cc7a79c..494c293ff 100644 --- a/contracts/mock-credit-manager/src/contract.rs +++ b/contracts/mock-credit-manager/src/contract.rs @@ -4,9 +4,9 @@ use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, use mars_rover::msg::QueryMsg; use crate::{ - execute::set_position_response, + execute::{set_account_kind_response, set_position_response}, msg::{ExecuteMsg, InstantiateMsg}, - query::{query_config, query_positions}, + query::{query_account_kind, query_config, query_positions}, state::CONFIG, }; @@ -33,6 +33,10 @@ pub fn execute( account_id, positions, } => set_position_response(deps, account_id, positions), + ExecuteMsg::SetAccountKindResponse { + account_id, + kind, + } => set_account_kind_response(deps, account_id, kind), } } @@ -43,6 +47,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { account_id, } => to_binary(&query_positions(deps, account_id)?), QueryMsg::Config {} => to_binary(&query_config(deps)?), + QueryMsg::AccountKind { + account_id, + } => to_binary(&query_account_kind(deps, account_id)?), _ => unimplemented!("query msg not supported"), } } diff --git a/contracts/mock-credit-manager/src/execute.rs b/contracts/mock-credit-manager/src/execute.rs index a92769d49..a76ba3325 100644 --- a/contracts/mock-credit-manager/src/execute.rs +++ b/contracts/mock-credit-manager/src/execute.rs @@ -1,7 +1,8 @@ use cosmwasm_std::{DepsMut, Response, StdResult}; use mars_rover::msg::query::Positions; +use mars_rover_health_types::AccountKind; -use crate::state::POSITION_RESPONSES; +use crate::state::{ACCOUNT_KINDS, POSITION_RESPONSES}; pub fn set_position_response( deps: DepsMut, @@ -11,3 +12,12 @@ pub fn set_position_response( POSITION_RESPONSES.save(deps.storage, &account_id, &positions)?; Ok(Response::new()) } + +pub fn set_account_kind_response( + deps: DepsMut, + account_id: String, + kind: AccountKind, +) -> StdResult { + ACCOUNT_KINDS.save(deps.storage, &account_id, &kind)?; + Ok(Response::new()) +} diff --git a/contracts/mock-credit-manager/src/msg.rs b/contracts/mock-credit-manager/src/msg.rs index 1140c22d0..0b9852606 100644 --- a/contracts/mock-credit-manager/src/msg.rs +++ b/contracts/mock-credit-manager/src/msg.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::cw_serde; use mars_rover::msg::query::{ConfigResponse, Positions}; +use mars_rover_health_types::AccountKind; #[cw_serde] pub struct InstantiateMsg { @@ -12,4 +13,8 @@ pub enum ExecuteMsg { account_id: String, positions: Positions, }, + SetAccountKindResponse { + account_id: String, + kind: AccountKind, + }, } diff --git a/contracts/mock-credit-manager/src/query.rs b/contracts/mock-credit-manager/src/query.rs index db9285069..962daba9d 100644 --- a/contracts/mock-credit-manager/src/query.rs +++ b/contracts/mock-credit-manager/src/query.rs @@ -1,7 +1,8 @@ use cosmwasm_std::{Deps, StdResult}; use mars_rover::msg::query::{ConfigResponse, Positions}; +use mars_rover_health_types::AccountKind; -use crate::state::{CONFIG, POSITION_RESPONSES}; +use crate::state::{ACCOUNT_KINDS, CONFIG, POSITION_RESPONSES}; pub fn query_positions(deps: Deps, account_id: String) -> StdResult { POSITION_RESPONSES.load(deps.storage, &account_id) @@ -10,3 +11,7 @@ pub fn query_positions(deps: Deps, account_id: String) -> StdResult { pub fn query_config(deps: Deps) -> StdResult { CONFIG.load(deps.storage) } + +pub fn query_account_kind(deps: Deps, account_id: String) -> StdResult { + Ok(ACCOUNT_KINDS.may_load(deps.storage, &account_id)?.unwrap_or(AccountKind::Default)) +} diff --git a/contracts/mock-credit-manager/src/state.rs b/contracts/mock-credit-manager/src/state.rs index 473991b65..4afd64334 100644 --- a/contracts/mock-credit-manager/src/state.rs +++ b/contracts/mock-credit-manager/src/state.rs @@ -1,6 +1,9 @@ use cw_storage_plus::{Item, Map}; use mars_rover::msg::query::{ConfigResponse, Positions}; +use mars_rover_health_types::AccountKind; pub const CONFIG: Item = Item::new("config"); pub const POSITION_RESPONSES: Map<&str, Positions> = Map::new("position_responses"); // Map + +pub const ACCOUNT_KINDS: Map<&str, AccountKind> = Map::new("account_types"); diff --git a/packages/account-nft-types/Cargo.toml b/packages/account-nft-types/Cargo.toml new file mode 100644 index 000000000..974281382 --- /dev/null +++ b/packages/account-nft-types/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "mars-account-nft-types" +version = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } + +[lib] +doctest = false + +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw721 = { workspace = true } +cw721-base = { workspace = true } diff --git a/packages/account-nft-types/src/lib.rs b/packages/account-nft-types/src/lib.rs new file mode 100644 index 000000000..2d7454db0 --- /dev/null +++ b/packages/account-nft-types/src/lib.rs @@ -0,0 +1,2 @@ +pub mod msg; +pub mod nft_config; diff --git a/contracts/account-nft/src/msg/execute.rs b/packages/account-nft-types/src/msg/execute.rs similarity index 96% rename from contracts/account-nft/src/msg/execute.rs rename to packages/account-nft-types/src/msg/execute.rs index 0edcd394f..b0dce3bc3 100644 --- a/contracts/account-nft/src/msg/execute.rs +++ b/packages/account-nft-types/src/msg/execute.rs @@ -5,7 +5,7 @@ use cosmwasm_std::{Binary, Empty, StdError}; use cw721::Expiration; use cw721_base::{Action, ExecuteMsg as ParentExecuteMsg}; -use crate::{error::ContractError, nft_config::NftConfigUpdates}; +use crate::nft_config::NftConfigUpdates; #[cw_serde] pub enum ExecuteMsg { @@ -68,7 +68,7 @@ pub enum ExecuteMsg { } impl TryInto> for ExecuteMsg { - type Error = ContractError; + type Error = StdError; fn try_into(self) -> Result, Self::Error> { match self { @@ -119,8 +119,7 @@ impl TryInto> for ExecuteMsg { ExecuteMsg::UpdateOwnership(action) => Ok(ParentExecuteMsg::UpdateOwnership(action)), _ => Err(StdError::generic_err( "Attempting to convert to a non-cw721 compatible message", - ) - .into()), + )), } } } diff --git a/contracts/account-nft/src/msg/instantiate.rs b/packages/account-nft-types/src/msg/instantiate.rs similarity index 95% rename from contracts/account-nft/src/msg/instantiate.rs rename to packages/account-nft-types/src/msg/instantiate.rs index 4be27949b..c31585c35 100644 --- a/contracts/account-nft/src/msg/instantiate.rs +++ b/packages/account-nft-types/src/msg/instantiate.rs @@ -13,6 +13,8 @@ pub struct InstantiateMsg { /// Used to validate the account id's health status allows for burning. /// Can be set later, but no burning allowed until set. pub health_contract: Option, + /// Used to query the account kind + pub credit_manager_contract: Option, //-------------------------------------------------------------------------------------------------- // Base cw721 messages diff --git a/contracts/account-nft/src/msg/mod.rs b/packages/account-nft-types/src/msg/mod.rs similarity index 100% rename from contracts/account-nft/src/msg/mod.rs rename to packages/account-nft-types/src/msg/mod.rs diff --git a/contracts/account-nft/src/msg/query.rs b/packages/account-nft-types/src/msg/query.rs similarity index 100% rename from contracts/account-nft/src/msg/query.rs rename to packages/account-nft-types/src/msg/query.rs diff --git a/contracts/account-nft/src/nft_config.rs b/packages/account-nft-types/src/nft_config.rs similarity index 77% rename from contracts/account-nft/src/nft_config.rs rename to packages/account-nft-types/src/nft_config.rs index 5564b7871..ab5271cee 100644 --- a/contracts/account-nft/src/nft_config.rs +++ b/packages/account-nft-types/src/nft_config.rs @@ -5,6 +5,7 @@ use cosmwasm_std::{Addr, Uint128}; pub struct NftConfigBase { pub max_value_for_burn: Uint128, pub health_contract_addr: Option, + pub credit_manager_contract_addr: Option, } pub type NftConfig = NftConfigBase; @@ -15,6 +16,7 @@ impl From for UncheckedNftConfig { Self { max_value_for_burn: config.max_value_for_burn, health_contract_addr: config.health_contract_addr.map(Into::into), + credit_manager_contract_addr: config.credit_manager_contract_addr.map(Into::into), } } } @@ -23,4 +25,5 @@ impl From for UncheckedNftConfig { pub struct NftConfigUpdates { pub max_value_for_burn: Option, pub health_contract_addr: Option, + pub credit_manager_contract_addr: Option, } diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 3fc4e4b69..878d727fb 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -26,7 +26,7 @@ cw721-base = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw-vault-standard = { workspace = true } -mars-account-nft = { workspace = true } +mars-account-nft-types = { workspace = true } mars-liquidation = { workspace = true } mars-rover-health-types = { workspace = true } mars-red-bank-types = { workspace = true } diff --git a/packages/rover/src/adapters/account_nft.rs b/packages/rover/src/adapters/account_nft.rs index b3685822e..e54f33798 100644 --- a/packages/rover/src/adapters/account_nft.rs +++ b/packages/rover/src/adapters/account_nft.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, QuerierWrapper, StdResult}; use cw721::TokensResponse; -use mars_account_nft::msg::QueryMsg; +use mars_account_nft_types::msg::QueryMsg; #[cw_serde] pub struct AccountNftBase(T); diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 680e82b31..95fdde35f 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -2,7 +2,7 @@ use std::collections::BTreeSet; use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; -use mars_account_nft::nft_config::NftConfigUpdates; +use mars_account_nft_types::nft_config::NftConfigUpdates; use mars_owner::OwnerUpdate; use mars_rover_health_types::{AccountKind, HealthState}; diff --git a/schemas/mars-account-nft/mars-account-nft.json b/schemas/mars-account-nft/mars-account-nft.json index fcea48085..ac1b0c109 100644 --- a/schemas/mars-account-nft/mars-account-nft.json +++ b/schemas/mars-account-nft/mars-account-nft.json @@ -13,6 +13,13 @@ "symbol" ], "properties": { + "credit_manager_contract": { + "description": "Used to query the account kind", + "type": [ + "string", + "null" + ] + }, "health_contract": { "description": "Used to validate the account id's health status allows for burning. Can be set later, but no burning allowed until set.", "type": [ @@ -411,6 +418,12 @@ "NftConfigUpdates": { "type": "object", "properties": { + "credit_manager_contract_addr": { + "type": [ + "string", + "null" + ] + }, "health_contract_addr": { "type": [ "string", @@ -1271,6 +1284,12 @@ "max_value_for_burn" ], "properties": { + "credit_manager_contract_addr": { + "type": [ + "string", + "null" + ] + }, "health_contract_addr": { "type": [ "string", diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index e7b7d9eec..445a41953 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -1638,6 +1638,12 @@ "NftConfigUpdates": { "type": "object", "properties": { + "credit_manager_contract_addr": { + "type": [ + "string", + "null" + ] + }, "health_contract_addr": { "type": [ "string", diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 9e792b32a..34c39e4d2 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -36,9 +36,41 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "set_account_kind_response" + ], + "properties": { + "set_account_kind_response": { + "type": "object", + "required": [ + "account_id", + "kind" + ], + "properties": { + "account_id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/AccountKind" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { + "AccountKind": { + "type": "string", + "enum": [ + "default", + "high_levered_strategy" + ] + }, "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/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index f7ef1687e..fdf3c0d71 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -7,6 +7,7 @@ export type Uint128 = string export interface InstantiateMsg { + credit_manager_contract?: string | null health_contract?: string | null max_value_for_burn: Uint128 minter: string @@ -92,6 +93,7 @@ export type Action = | 'accept_ownership' | 'renounce_ownership' export interface NftConfigUpdates { + credit_manager_contract_addr?: string | null health_contract_addr?: string | null max_value_for_burn?: Uint128 | null } @@ -197,6 +199,7 @@ export interface ApprovalsResponse { approvals: Approval[] } export interface NftConfigBaseForString { + credit_manager_contract_addr?: string | null health_contract_addr?: string | null max_value_for_burn: Uint128 } diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 8e52bac5b..2faebde05 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -366,6 +366,7 @@ export interface ConfigUpdates { zapper?: ZapperBaseForString | null } export interface NftConfigUpdates { + credit_manager_contract_addr?: string | null health_contract_addr?: string | null max_value_for_burn?: Uint128 | null } diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index fe6cb6899..bb5c4c355 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -16,6 +16,7 @@ import { VaultAmount1, UnlockingPositions, Addr, + AccountKind, Positions, DebtAmount, Coin, @@ -25,7 +26,6 @@ import { VaultBaseForAddr, QueryMsg, VaultBaseForString, - AccountKind, ArrayOfAccount, Account, ArrayOfCoinBalanceResponseItem, @@ -280,6 +280,18 @@ export interface MarsMockCreditManagerInterface extends MarsMockCreditManagerRea memo?: string, _funds?: Coin[], ) => Promise + setAccountKindResponse: ( + { + accountId, + kind, + }: { + accountId: string + kind: AccountKind + }, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise } export class MarsMockCreditManagerClient extends MarsMockCreditManagerQueryClient @@ -295,6 +307,7 @@ export class MarsMockCreditManagerClient this.sender = sender this.contractAddress = contractAddress this.setPositionsResponse = this.setPositionsResponse.bind(this) + this.setAccountKindResponse = this.setAccountKindResponse.bind(this) } setPositionsResponse = async ( @@ -323,4 +336,30 @@ export class MarsMockCreditManagerClient _funds, ) } + setAccountKindResponse = async ( + { + accountId, + kind, + }: { + accountId: string + kind: AccountKind + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + set_account_kind_response: { + account_id: accountId, + kind, + }, + }, + fee, + memo, + _funds, + ) + } } diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index 815a5a594..5a31602cb 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -17,6 +17,7 @@ import { VaultAmount1, UnlockingPositions, Addr, + AccountKind, Positions, DebtAmount, Coin, @@ -26,7 +27,6 @@ import { VaultBaseForAddr, QueryMsg, VaultBaseForString, - AccountKind, ArrayOfAccount, Account, ArrayOfCoinBalanceResponseItem, @@ -58,6 +58,16 @@ export interface MarsMockCreditManagerMessage { }, _funds?: Coin[], ) => MsgExecuteContractEncodeObject + setAccountKindResponse: ( + { + accountId, + kind, + }: { + accountId: string + kind: AccountKind + }, + _funds?: Coin[], + ) => MsgExecuteContractEncodeObject } export class MarsMockCreditManagerMessageComposer implements MarsMockCreditManagerMessage { sender: string @@ -67,6 +77,7 @@ export class MarsMockCreditManagerMessageComposer implements MarsMockCreditManag this.sender = sender this.contractAddress = contractAddress this.setPositionsResponse = this.setPositionsResponse.bind(this) + this.setAccountKindResponse = this.setAccountKindResponse.bind(this) } setPositionsResponse = ( @@ -96,4 +107,31 @@ export class MarsMockCreditManagerMessageComposer implements MarsMockCreditManag }), } } + setAccountKindResponse = ( + { + accountId, + kind, + }: { + accountId: string + kind: AccountKind + }, + _funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + set_account_kind_response: { + account_id: accountId, + kind, + }, + }), + ), + funds: _funds, + }), + } + } } diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index f0be88f9c..5269a9599 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -17,6 +17,7 @@ import { VaultAmount1, UnlockingPositions, Addr, + AccountKind, Positions, DebtAmount, Coin, @@ -26,7 +27,6 @@ import { VaultBaseForAddr, QueryMsg, VaultBaseForString, - AccountKind, ArrayOfAccount, Account, ArrayOfCoinBalanceResponseItem, @@ -438,6 +438,30 @@ export function useMarsMockCreditManagerAccountKindQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsMockCreditManagerSetAccountKindResponseMutation { + client: MarsMockCreditManagerClient + msg: { + accountId: string + kind: AccountKind + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsMockCreditManagerSetAccountKindResponseMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.setAccountKindResponse(msg, fee, memo, funds), + options, + ) +} export interface MarsMockCreditManagerSetPositionsResponseMutation { client: MarsMockCreditManagerClient msg: { diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 54a739f7d..b5791d852 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -8,12 +8,19 @@ export interface InstantiateMsg { [k: string]: unknown } -export type ExecuteMsg = { - set_positions_response: { - account_id: string - positions: Positions - } -} +export type ExecuteMsg = + | { + set_positions_response: { + account_id: string + positions: Positions + } + } + | { + set_account_kind_response: { + account_id: string + kind: AccountKind + } + } export type Uint128 = string export type VaultPositionAmount = | { @@ -26,6 +33,7 @@ export type VaultAmount = string export type VaultAmount1 = string export type UnlockingPositions = VaultUnlockingPosition[] export type Addr = string +export type AccountKind = 'default' | 'high_levered_strategy' export interface Positions { account_id: string debts: DebtAmount[] @@ -130,7 +138,6 @@ export type QueryMsg = export interface VaultBaseForString { address: string } -export type AccountKind = 'default' | 'high_levered_strategy' export type ArrayOfAccount = Account[] export interface Account { id: string From bb5481d05247f09bcc1835f8d1f09419ee37aa09 Mon Sep 17 00:00:00 2001 From: piobab Date: Wed, 13 Sep 2023 12:49:03 +0200 Subject: [PATCH 212/218] Withdraw coins after claiming rewards for HLS account. (#196) * Withdraw coins after claiming rewards for HLS account. * Update schema. * Review comments. * Fmt --- contracts/credit-manager/src/claim_rewards.rs | 114 ++++++++++++++++-- contracts/credit-manager/src/execute.rs | 11 +- contracts/credit-manager/src/utils.rs | 18 +-- .../tests/test_claim_rewards.rs | 86 ++++++++++++- packages/rover/src/msg/execute.rs | 13 +- .../mars-credit-manager.json | 41 ++++++- .../MarsCreditManager.types.ts | 8 ++ 7 files changed, 263 insertions(+), 28 deletions(-) diff --git a/contracts/credit-manager/src/claim_rewards.rs b/contracts/credit-manager/src/claim_rewards.rs index 5ee61a823..1ba6952c9 100644 --- a/contracts/credit-manager/src/claim_rewards.rs +++ b/contracts/credit-manager/src/claim_rewards.rs @@ -1,13 +1,29 @@ -use cosmwasm_std::{DepsMut, Env, Response}; +use cosmwasm_std::{ + to_binary, Addr, BankMsg, Coin, CosmosMsg, DepsMut, Env, QuerierWrapper, Response, StdResult, + WasmMsg, +}; use mars_rover::{ error::{ContractError, ContractResult}, - msg::execute::ChangeExpected, + msg::{ + execute::{CallbackMsg, ChangeExpected}, + ExecuteMsg, + }, traits::Denoms, }; +use mars_rover_health_types::AccountKind; -use crate::{state::INCENTIVES, utils::update_balances_msgs}; +use crate::{ + state::INCENTIVES, + update_coin_balances::query_balance, + utils::{get_account_kind, update_balances_msgs}, +}; -pub fn claim_rewards(deps: DepsMut, env: Env, account_id: &str) -> ContractResult { +pub fn claim_rewards( + deps: DepsMut, + env: Env, + account_id: &str, + recipient: Addr, +) -> ContractResult { let incentives = INCENTIVES.load(deps.storage)?; let unclaimed_rewards = incentives.query_unclaimed_rewards(&deps.querier, account_id)?; @@ -15,17 +31,89 @@ pub fn claim_rewards(deps: DepsMut, env: Env, account_id: &str) -> ContractResul return Err(ContractError::NoAmount); } - let update_balances_msgs = update_balances_msgs( - &deps.querier, - &env.contract.address, - account_id, - unclaimed_rewards.to_denoms(), - ChangeExpected::Increase, - )?; + // For HLS accounts there are special requirements enforced for this account type. + // assert_hls_rules only allows assets with HLS params set in the params contract + // and where the collateral is whitelisted. + // We withdraw all claimed rewards for HLS accounts to the recipient address. + let kind = get_account_kind(deps.storage, account_id)?; + let msgs = match kind { + AccountKind::Default => update_balances_msgs( + &deps.querier, + &env.contract.address, + account_id, + unclaimed_rewards.to_denoms(), + ChangeExpected::Increase, + )?, + AccountKind::HighLeveredStrategy => { + let msg = send_rewards_msg( + &deps.querier, + &env.contract.address, + account_id, + recipient.clone(), + unclaimed_rewards.to_denoms(), + )?; + vec![msg] + } + }; Ok(Response::new() .add_message(incentives.claim_rewards_msg(account_id)?) - .add_messages(update_balances_msgs) + .add_messages(msgs) .add_attribute("action", "claim_rewards") - .add_attribute("account_id", account_id)) + .add_attribute("account_id", account_id) + .add_attribute("recipient", recipient.to_string())) +} + +fn send_rewards_msg( + querier: &QuerierWrapper, + credit_manager_addr: &Addr, + account_id: &str, + recipient: Addr, + denoms: Vec<&str>, +) -> StdResult { + let coins = denoms + .iter() + .map(|denom| query_balance(querier, credit_manager_addr, denom)) + .collect::>>()?; + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: credit_manager_addr.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::SendRewardsToAddr { + account_id: account_id.to_string(), + previous_balances: coins, + recipient, + }))?, + })) +} + +pub fn send_rewards( + deps: DepsMut, + credit_manager_addr: &Addr, + account_id: &str, + recipient: Addr, + previous_balances: Vec, +) -> ContractResult { + let coins = previous_balances + .into_iter() + .map(|coin| { + let current_balance = query_balance(&deps.querier, credit_manager_addr, &coin.denom)?; + let amount_to_withdraw = current_balance.amount.checked_sub(coin.amount)?; + Ok(Coin { + denom: coin.denom, + amount: amount_to_withdraw, + }) + }) + .collect::>>()?; + + // send coin to recipient + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: recipient.to_string(), + amount: coins, + }); + + Ok(Response::new() + .add_message(transfer_msg) + .add_attribute("action", "callback/send_rewards") + .add_attribute("account_id", account_id) + .add_attribute("recipient", recipient.to_string())) } diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 3007df783..260e52bc8 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -14,7 +14,7 @@ use mars_rover_health_types::AccountKind; use crate::{ borrow::borrow, - claim_rewards::claim_rewards, + claim_rewards::{claim_rewards, send_rewards}, deposit::{assert_deposit_caps, deposit}, health::{assert_max_ltv, query_health_state}, hls::assert_hls_rules, @@ -161,6 +161,7 @@ pub fn dispatch_actions( }), Action::ClaimRewards {} => callbacks.push(CallbackMsg::ClaimRewards { account_id: account_id.to_string(), + recipient: info.sender.clone(), }), Action::EnterVault { vault, @@ -348,7 +349,8 @@ pub fn execute_callback( } => reclaim(deps, &account_id, &coin), CallbackMsg::ClaimRewards { account_id, - } => claim_rewards(deps, env, &account_id), + recipient, + } => claim_rewards(deps, env, &account_id, recipient), CallbackMsg::AssertMaxLTV { account_id, prev_health_state, @@ -465,5 +467,10 @@ pub fn execute_callback( account_id, } => assert_hls_rules(deps.as_ref(), &account_id), CallbackMsg::RemoveReentrancyGuard {} => REENTRANCY_GUARD.try_unlock(deps.storage), + CallbackMsg::SendRewardsToAddr { + account_id, + previous_balances, + recipient, + } => send_rewards(deps, &env.contract.address, &account_id, recipient, previous_balances), } } diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 083f077ea..195db51b9 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -90,14 +90,14 @@ pub fn decrement_coin_balance( pub fn update_balance_msg( querier: &QuerierWrapper, - rover_addr: &Addr, + credit_manager_addr: &Addr, account_id: &str, denom: &str, change: ChangeExpected, ) -> StdResult { - let previous_balance = query_balance(querier, rover_addr, denom)?; + let previous_balance = query_balance(querier, credit_manager_addr, denom)?; Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: rover_addr.to_string(), + contract_addr: credit_manager_addr.to_string(), funds: vec![], msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::UpdateCoinBalance { account_id: account_id.to_string(), @@ -109,27 +109,29 @@ pub fn update_balance_msg( pub fn update_balances_msgs( querier: &QuerierWrapper, - rover_addr: &Addr, + credit_manager_addr: &Addr, account_id: &str, denoms: Vec<&str>, change: ChangeExpected, ) -> StdResult> { denoms .iter() - .map(|denom| update_balance_msg(querier, rover_addr, account_id, denom, change.clone())) + .map(|denom| { + update_balance_msg(querier, credit_manager_addr, account_id, denom, change.clone()) + }) .collect() } pub fn update_balance_after_vault_liquidation_msg( querier: &QuerierWrapper, - rover_addr: &Addr, + credit_manager_addr: &Addr, account_id: &str, denom: &str, protocol_fee: Decimal, ) -> StdResult { - let previous_balance = query_balance(querier, rover_addr, denom)?; + let previous_balance = query_balance(querier, credit_manager_addr, denom)?; Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: rover_addr.to_string(), + contract_addr: credit_manager_addr.to_string(), funds: vec![], msg: to_binary(&ExecuteMsg::Callback( CallbackMsg::UpdateCoinBalanceAfterVaultLiquidation { diff --git a/contracts/credit-manager/tests/test_claim_rewards.rs b/contracts/credit-manager/tests/test_claim_rewards.rs index 1d816d940..a51dc2977 100644 --- a/contracts/credit-manager/tests/test_claim_rewards.rs +++ b/contracts/credit-manager/tests/test_claim_rewards.rs @@ -1,7 +1,13 @@ use cosmwasm_std::{Addr, Uint128}; -use mars_rover::{error::ContractError, msg::execute::Action::ClaimRewards}; +use mars_params::{msg::AssetParamsUpdate::AddOrUpdate, types::hls::HlsAssetType}; +use mars_rover::{ + error::ContractError, + msg::execute::Action::{Borrow, ClaimRewards, Deposit}, +}; -use crate::helpers::{assert_err, get_coin, uatom_info, ujake_info, uosmo_info, MockEnv}; +use crate::helpers::{ + assert_err, get_coin, lp_token_info, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, +}; pub mod helpers; @@ -94,3 +100,79 @@ fn claiming_multiple_rewards() { let jake_balance = mock.query_balance(&mock.rover, &jake_info.denom); assert_eq!(jake_balance.amount, Uint128::new(12)); } + +#[test] +fn claiming_by_hls_account() { + let atom_info = uatom_info(); + let osmo_info = uosmo_info(); + let jake_info = ujake_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .set_params(&[atom_info.clone(), osmo_info.clone(), jake_info.clone(), lp_token.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + // Add assets to correlations of Atom in params contract + let mut asset_params = mock.query_asset_params(&atom_info.denom); + let hls = asset_params.credit_manager.hls.as_mut().unwrap(); + hls.correlations.push(HlsAssetType::Coin { + denom: jake_info.denom.clone(), + }); + hls.correlations.push(HlsAssetType::Coin { + denom: lp_token.denom.clone(), + }); + mock.update_asset_params(AddOrUpdate { + params: asset_params.into(), + }); + + let account_id = mock.create_hls_account(&user); + + let lp_deposit_amount = 300; + let atom_borrow_amount = 150; + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(lp_deposit_amount)), + Borrow(atom_info.to_coin(atom_borrow_amount)), + ], + &[lp_token.to_coin(lp_deposit_amount)], + ) + .unwrap(); + + let unclaimed = mock.query_unclaimed_rewards(&account_id); + assert!(unclaimed.is_empty()); + + mock.add_incentive_reward(&account_id, osmo_info.to_coin(123)); + mock.add_incentive_reward(&account_id, jake_info.to_coin(12)); + + let unclaimed = mock.query_unclaimed_rewards(&account_id); + assert_eq!(unclaimed.len(), 2); + + mock.update_credit_account(&account_id, &user, vec![ClaimRewards {}], &[]).unwrap(); + + // Check account id deposit balance + let positions = mock.query_positions(&account_id); + assert_eq!(positions.deposits.len(), 2); + let lp_token_coin = get_coin(&lp_token.denom, &positions.deposits); + assert_eq!(lp_token_coin.amount, Uint128::new(lp_deposit_amount)); + let atom_coin = get_coin(&atom_info.denom, &positions.deposits); + assert_eq!(atom_coin.amount, Uint128::new(atom_borrow_amount)); + + // Ensure money is in user's wallet + let osmo_balance = mock.query_balance(&mock.rover, &osmo_info.denom); + assert_eq!(osmo_balance.amount, Uint128::zero()); + let jake_balance = mock.query_balance(&mock.rover, &jake_info.denom); + assert_eq!(jake_balance.amount, Uint128::zero()); + let osmo_balance = mock.query_balance(&user, &osmo_info.denom); + assert_eq!(osmo_balance.amount, Uint128::new(123)); + let jake_balance = mock.query_balance(&user, &jake_info.denom); + assert_eq!(jake_balance.amount, Uint128::new(12)); +} diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 95fdde35f..355205971 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -229,9 +229,13 @@ pub enum CallbackMsg { account_id: String, coin: ActionCoin, }, - /// Calls incentive contract to claim all rewards and increments account balance + /// Calls incentive contract to claim all rewards and: + /// - for Default account increments account balance + /// - for HLS account withdraws claimed rewards. HLS accounts have special rules - some assets can't be in the account. + /// For simplicity we withdraw all claimed rewards. ClaimRewards { account_id: String, + recipient: Addr, }, /// Assert MaxLTV is either: /// - Healthy, if prior to actions MaxLTV health factor >= 1 or None @@ -338,6 +342,13 @@ pub enum CallbackMsg { /// At the end of the execution of dispatched actions, this callback removes the guard /// and allows subsequent dispatches. RemoveReentrancyGuard {}, + /// Send reward amounts of coin from credit manager to recipient by querying balance, claiming rewards, + /// and comparing previous balance to new balance after reward claim - send the diff to the recipient. + SendRewardsToAddr { + account_id: String, + previous_balances: Vec, + recipient: Addr, + }, } impl CallbackMsg { diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 445a41953..361ff256a 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -855,7 +855,7 @@ "additionalProperties": false }, { - "description": "Calls incentive contract to claim all rewards and increments account balance", + "description": "Calls incentive contract to claim all rewards and: - for Default account increments account balance - for HLS account withdraws claimed rewards. HLS accounts have special rules - some assets can't be in the account. For simplicity we withdraw all claimed rewards.", "type": "object", "required": [ "claim_rewards" @@ -864,11 +864,15 @@ "claim_rewards": { "type": "object", "required": [ - "account_id" + "account_id", + "recipient" ], "properties": { "account_id": { "type": "string" + }, + "recipient": { + "$ref": "#/definitions/Addr" } }, "additionalProperties": false @@ -1363,6 +1367,39 @@ } }, "additionalProperties": false + }, + { + "description": "Send reward amounts of coin from credit manager to recipient by querying balance, claiming rewards, and comparing previous balance to new balance after reward claim - send the diff to the recipient.", + "type": "object", + "required": [ + "send_rewards_to_addr" + ], + "properties": { + "send_rewards_to_addr": { + "type": "object", + "required": [ + "account_id", + "previous_balances", + "recipient" + ], + "properties": { + "account_id": { + "type": "string" + }, + "previous_balances": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "recipient": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 2faebde05..70ae05336 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -214,6 +214,7 @@ export type CallbackMsg = | { claim_rewards: { account_id: string + recipient: Addr } } | { @@ -320,6 +321,13 @@ export type CallbackMsg = | { remove_reentrancy_guard: {} } + | { + send_rewards_to_addr: { + account_id: string + previous_balances: Coin[] + recipient: Addr + } + } export type Addr = string export type HealthState = | 'healthy' From 40cf48eb318ca951a82a03358b293b2cbf19934c Mon Sep 17 00:00:00 2001 From: piobab Date: Wed, 13 Sep 2023 12:55:35 +0200 Subject: [PATCH 213/218] MP-3350. Slippage (#197) * Add max_slippage config. * Use slippage for ProvideLiquidity and WithdrawLiquidity. * Assert slippage in swap. * Update scripts. * Update error msg. --- .../tests/helpers/mock_env_builder.rs | 3 +- contracts/credit-manager/src/execute.rs | 16 ++--- contracts/credit-manager/src/instantiate.rs | 13 +++- .../credit-manager/src/migrations/v2_0_0.rs | 6 +- contracts/credit-manager/src/query.rs | 5 +- contracts/credit-manager/src/state.rs | 3 +- contracts/credit-manager/src/swap.rs | 6 +- contracts/credit-manager/src/update_config.rs | 12 +++- contracts/credit-manager/src/utils.rs | 25 ++++++- contracts/credit-manager/src/zap.rs | 52 +++++++++++--- .../credit-manager/tests/helpers/mock_env.rs | 13 ++++ .../credit-manager/tests/test_deposit_cap.rs | 4 +- .../credit-manager/tests/test_migration_v2.rs | 14 +++- contracts/credit-manager/tests/test_swap.rs | 37 ++++++++++ .../tests/test_update_config.rs | 42 +++++++++++ .../credit-manager/tests/test_zap_provide.rs | 47 +++++++----- .../credit-manager/tests/test_zap_withdraw.rs | 71 +++++++------------ .../health/tests/helpers/mock_env_builder.rs | 1 + packages/rover/src/error.rs | 8 ++- packages/rover/src/msg/execute.rs | 14 ++-- packages/rover/src/msg/instantiate.rs | 5 +- packages/rover/src/msg/migrate.rs | 2 + packages/rover/src/msg/query.rs | 3 +- .../mars-credit-manager.json | 67 +++++++++++------ .../mars-mock-credit-manager.json | 8 +++ scripts/deploy/base/deployer.ts | 1 + scripts/deploy/base/rover.ts | 4 +- scripts/deploy/osmosis/mainnet.ts | 1 + scripts/deploy/osmosis/testnet-config.ts | 1 + scripts/types/config.ts | 1 + .../MarsCreditManager.client.ts | 2 +- .../MarsCreditManager.message-composer.ts | 2 +- .../MarsCreditManager.react-query.ts | 2 +- .../MarsCreditManager.types.ts | 13 ++-- .../MarsMockCreditManager.client.ts | 1 + .../MarsMockCreditManager.message-composer.ts | 1 + .../MarsMockCreditManager.react-query.ts | 1 + .../MarsMockCreditManager.types.ts | 2 + 38 files changed, 371 insertions(+), 138 deletions(-) diff --git a/contracts/account-nft/tests/helpers/mock_env_builder.rs b/contracts/account-nft/tests/helpers/mock_env_builder.rs index 41ba59612..6d0ee334f 100644 --- a/contracts/account-nft/tests/helpers/mock_env_builder.rs +++ b/contracts/account-nft/tests/helpers/mock_env_builder.rs @@ -1,7 +1,7 @@ use std::mem::take; use anyhow::Result as AnyResult; -use cosmwasm_std::{Addr, Empty}; +use cosmwasm_std::{Addr, Decimal, Empty}; use cw_multi_test::{BasicApp, Executor}; use mars_account_nft_types::msg::InstantiateMsg; use mars_mock_credit_manager::msg::InstantiateMsg as CmMockInstantiateMsg; @@ -147,6 +147,7 @@ impl MockEnvBuilder { params: "n/a".to_string(), account_nft: None, max_unlocking_positions: Default::default(), + max_slippage: Decimal::percent(99), swapper: "n/a".to_string(), zapper: "n/a".to_string(), health_contract: "n/a".to_string(), diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 260e52bc8..77dd889e1 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -242,20 +242,20 @@ pub fn dispatch_actions( Action::ProvideLiquidity { coins_in, lp_token_out, - minimum_receive, + slippage, } => callbacks.push(CallbackMsg::ProvideLiquidity { account_id: account_id.to_string(), lp_token_out, coins_in, - minimum_receive, + slippage, }), Action::WithdrawLiquidity { lp_token, - minimum_receive, + slippage, } => callbacks.push(CallbackMsg::WithdrawLiquidity { account_id: account_id.to_string(), lp_token, - minimum_receive, + slippage, }), Action::RefundAllCoinBalances {} => { callbacks.push(CallbackMsg::RefundAllCoinBalances { @@ -453,13 +453,13 @@ pub fn execute_callback( account_id, coins_in, lp_token_out, - minimum_receive, - } => provide_liquidity(deps, env, &account_id, coins_in, &lp_token_out, minimum_receive), + slippage, + } => provide_liquidity(deps, env, &account_id, coins_in, &lp_token_out, slippage), CallbackMsg::WithdrawLiquidity { account_id, lp_token, - minimum_receive, - } => withdraw_liquidity(deps, env, &account_id, &lp_token, minimum_receive), + slippage, + } => withdraw_liquidity(deps, env, &account_id, &lp_token, slippage), CallbackMsg::RefundAllCoinBalances { account_id, } => refund_coin_balances(deps, env, &account_id), diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 21cff406a..19b5a3c2f 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -2,9 +2,12 @@ use cosmwasm_std::{DepsMut, Env}; use mars_owner::OwnerInit::SetInitialOwner; use mars_rover::{error::ContractResult, msg::InstantiateMsg}; -use crate::state::{ - HEALTH_CONTRACT, INCENTIVES, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, RED_BANK, SWAPPER, - ZAPPER, +use crate::{ + state::{ + HEALTH_CONTRACT, INCENTIVES, MAX_SLIPPAGE, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, + RED_BANK, SWAPPER, ZAPPER, + }, + utils::assert_max_slippage, }; pub fn store_config(deps: DepsMut, env: Env, msg: &InstantiateMsg) -> ContractResult<()> { @@ -21,6 +24,10 @@ pub fn store_config(deps: DepsMut, env: Env, msg: &InstantiateMsg) -> ContractRe SWAPPER.save(deps.storage, &msg.swapper.check(deps.api)?)?; ZAPPER.save(deps.storage, &msg.zapper.check(deps.api)?)?; MAX_UNLOCKING_POSITIONS.save(deps.storage, &msg.max_unlocking_positions)?; + + assert_max_slippage(msg.max_slippage)?; + MAX_SLIPPAGE.save(deps.storage, &msg.max_slippage)?; + HEALTH_CONTRACT.save(deps.storage, &msg.health_contract.check(deps.api)?)?; PARAMS.save(deps.storage, &msg.params.check(deps.api)?)?; INCENTIVES.save(deps.storage, &msg.incentives.check(deps.api, env.contract.address)?)?; diff --git a/contracts/credit-manager/src/migrations/v2_0_0.rs b/contracts/credit-manager/src/migrations/v2_0_0.rs index 487d3fbff..82880a9f6 100644 --- a/contracts/credit-manager/src/migrations/v2_0_0.rs +++ b/contracts/credit-manager/src/migrations/v2_0_0.rs @@ -5,7 +5,8 @@ use mars_rover::{error::ContractResult, msg::migrate::V2Updates}; use crate::{ contract::{CONTRACT_NAME, CONTRACT_VERSION}, - state::{HEALTH_CONTRACT, INCENTIVES, OWNER, PARAMS, SWAPPER}, + state::{HEALTH_CONTRACT, INCENTIVES, MAX_SLIPPAGE, OWNER, PARAMS, SWAPPER}, + utils::assert_max_slippage, }; const FROM_VERSION: &str = "1.0.0"; @@ -44,6 +45,9 @@ pub fn migrate(deps: DepsMut, env: Env, updates: V2Updates) -> ContractResult ContractResult { oracle: ORACLE.load(deps.storage)?.address().into(), params: PARAMS.load(deps.storage)?.address().into(), max_unlocking_positions: MAX_UNLOCKING_POSITIONS.load(deps.storage)?, + max_slippage: MAX_SLIPPAGE.load(deps.storage)?, swapper: SWAPPER.load(deps.storage)?.address().into(), zapper: ZAPPER.load(deps.storage)?.address().into(), health_contract: HEALTH_CONTRACT.load(deps.storage)?.address().into(), diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index 2f70744e9..ce8ea3e75 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Uint128}; +use cosmwasm_std::{Addr, Decimal, Uint128}; use cw_storage_plus::{Item, Map}; use mars_owner::Owner; use mars_rover::{ @@ -27,6 +27,7 @@ pub const INCENTIVES: Item = Item::new("incentives"); pub const OWNER: Owner = Owner::new("owner"); pub const MAX_UNLOCKING_POSITIONS: Item = Item::new("max_unlocking_positions"); pub const REENTRANCY_GUARD: ReentrancyGuard = ReentrancyGuard::new("reentrancy_guard"); +pub const MAX_SLIPPAGE: Item = Item::new("max_slippage"); // Positions pub const ACCOUNT_KINDS: Map<&str, AccountKind> = Map::new("account_types"); // Map diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs index a11d5d5d1..7c7e5d06f 100644 --- a/contracts/credit-manager/src/swap.rs +++ b/contracts/credit-manager/src/swap.rs @@ -6,7 +6,9 @@ use mars_rover::{ use crate::{ state::{COIN_BALANCES, SWAPPER}, - utils::{assert_coin_is_whitelisted, decrement_coin_balance, update_balance_msg}, + utils::{ + assert_coin_is_whitelisted, assert_slippage, decrement_coin_balance, update_balance_msg, + }, }; pub fn swap_exact_in( @@ -17,6 +19,8 @@ pub fn swap_exact_in( denom_out: &str, slippage: Decimal, ) -> ContractResult { + assert_slippage(deps.storage, slippage)?; + assert_coin_is_whitelisted(&mut deps, denom_out)?; let coin_in_to_trade = Coin { diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index 1437d6eab..6a76b2ce6 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -11,9 +11,10 @@ use mars_rover_health_types::AccountKind; use crate::{ execute::create_credit_account, state::{ - ACCOUNT_NFT, HEALTH_CONTRACT, INCENTIVES, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, - REWARDS_COLLECTOR, SWAPPER, ZAPPER, + ACCOUNT_NFT, HEALTH_CONTRACT, INCENTIVES, MAX_SLIPPAGE, MAX_UNLOCKING_POSITIONS, ORACLE, + OWNER, RED_BANK, REWARDS_COLLECTOR, SWAPPER, ZAPPER, }, + utils::assert_max_slippage, }; pub fn update_config( @@ -74,6 +75,13 @@ pub fn update_config( .add_attribute("value", num.to_string()); } + if let Some(num) = updates.max_slippage { + assert_max_slippage(num)?; + MAX_SLIPPAGE.save(deps.storage, &num)?; + response = + response.add_attribute("key", "max_slippage").add_attribute("value", num.to_string()); + } + if let Some(unchecked) = updates.health_contract { HEALTH_CONTRACT.save(deps.storage, &unchecked.check(deps.api)?)?; response = response diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 195db51b9..c57e4c2a3 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -16,7 +16,10 @@ use mars_rover::{ use mars_rover_health_types::AccountKind; use crate::{ - state::{ACCOUNT_KINDS, ACCOUNT_NFT, COIN_BALANCES, PARAMS, RED_BANK, TOTAL_DEBT_SHARES}, + state::{ + ACCOUNT_KINDS, ACCOUNT_NFT, COIN_BALANCES, MAX_SLIPPAGE, PARAMS, RED_BANK, + TOTAL_DEBT_SHARES, + }, update_coin_balances::query_balance, }; @@ -31,6 +34,26 @@ pub fn assert_is_token_owner(deps: &DepsMut, user: &Addr, account_id: &str) -> C Ok(()) } +pub fn assert_max_slippage(max_slippage: Decimal) -> ContractResult<()> { + if max_slippage.is_zero() || max_slippage >= Decimal::one() { + return Err(ContractError::InvalidConfig { + reason: "Max slippage must be greater than 0 and less than 1".to_string(), + }); + } + Ok(()) +} + +pub fn assert_slippage(storage: &dyn Storage, slippage: Decimal) -> ContractResult<()> { + let max_slippage = MAX_SLIPPAGE.load(storage)?; + if slippage > max_slippage { + return Err(ContractError::SlippageExceeded { + slippage, + max_slippage, + }); + } + Ok(()) +} + pub fn query_nft_token_owner(deps: Deps, account_id: &str) -> ContractResult { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; let res: OwnerOfResponse = deps.querier.query_wasm_smart( diff --git a/contracts/credit-manager/src/zap.rs b/contracts/credit-manager/src/zap.rs index c04eb88fd..5f375ca54 100644 --- a/contracts/credit-manager/src/zap.rs +++ b/contracts/credit-manager/src/zap.rs @@ -1,4 +1,6 @@ -use cosmwasm_std::{Coin, Deps, DepsMut, Env, Response, Uint128}; +use cosmwasm_std::{ + CheckedMultiplyFractionError, Coin, Decimal, Deps, DepsMut, Env, Response, Uint128, +}; use mars_rover::{ error::{ContractError, ContractResult}, msg::execute::{ActionAmount, ActionCoin, ChangeExpected}, @@ -8,8 +10,8 @@ use mars_rover::{ use crate::{ state::{COIN_BALANCES, ZAPPER}, utils::{ - assert_coin_is_whitelisted, assert_coins_are_whitelisted, decrement_coin_balance, - update_balance_msg, update_balances_msgs, + assert_coin_is_whitelisted, assert_coins_are_whitelisted, assert_slippage, + decrement_coin_balance, update_balance_msg, update_balances_msgs, }, }; @@ -19,8 +21,10 @@ pub fn provide_liquidity( account_id: &str, coins_in: Vec, lp_token_out: &str, - minimum_receive: Uint128, + slippage: Decimal, ) -> ContractResult { + assert_slippage(deps.storage, slippage)?; + assert_coin_is_whitelisted(&mut deps, lp_token_out)?; assert_coins_are_whitelisted(&mut deps, coins_in.to_denoms())?; @@ -40,9 +44,21 @@ pub fn provide_liquidity( updated_coins_in.push(updated_coin); } - // After zap is complete, update account's LP token balance let zapper = ZAPPER.load(deps.storage)?; - let zap_msg = zapper.provide_liquidity_msg(&updated_coins_in, lp_token_out, minimum_receive)?; + + // Estimate how much LP token will be received from zapper with applied slippage + let estimated_min_receive = + zapper.estimate_provide_liquidity(&deps.querier, lp_token_out, &updated_coins_in)?; + let estimated_min_receive_slippage = + estimated_min_receive.checked_mul_floor(Decimal::one() - slippage)?; + + let zap_msg = zapper.provide_liquidity_msg( + &updated_coins_in, + lp_token_out, + estimated_min_receive_slippage, + )?; + + // After zap is complete, update account's LP token balance let update_balance_msg = update_balance_msg( &deps.querier, &env.contract.address, @@ -65,8 +81,10 @@ pub fn withdraw_liquidity( env: Env, account_id: &str, lp_token_action: &ActionCoin, - minimum_receive: Vec, + slippage: Decimal, ) -> ContractResult { + assert_slippage(deps.storage, slippage)?; + let lp_token = Coin { denom: lp_token_action.denom.clone(), amount: match lp_token_action.amount { @@ -84,15 +102,27 @@ pub fn withdraw_liquidity( let zapper = ZAPPER.load(deps.storage)?; decrement_coin_balance(deps.storage, account_id, &lp_token)?; - let unzap_msg = zapper.withdraw_liquidity_msg(&lp_token, minimum_receive)?; + // Estimate how much coins will be received from zapper with applied slippage + let estimated_coins_out = zapper.estimate_withdraw_liquidity(&deps.querier, &lp_token)?; + let estimated_coins_out_slippage = estimated_coins_out + .iter() + .map(|c| { + let amount = c.amount.checked_mul_floor(Decimal::one() - slippage)?; + Ok(Coin { + denom: c.denom.clone(), + amount, + }) + }) + .collect::, CheckedMultiplyFractionError>>()?; + + let unzap_msg = zapper.withdraw_liquidity_msg(&lp_token, estimated_coins_out_slippage)?; // After unzap is complete, update account's coin balances - let coins_out = zapper.estimate_withdraw_liquidity(&deps.querier, &lp_token)?; let update_balances_msgs = update_balances_msgs( &deps.querier, &env.contract.address, account_id, - coins_out.to_denoms(), + estimated_coins_out.to_denoms(), ChangeExpected::Increase, )?; @@ -102,7 +132,7 @@ pub fn withdraw_liquidity( .add_attribute("action", "withdraw_liquidity") .add_attribute("account_id", account_id) .add_attribute("coin_in", lp_token.to_string()) - .add_attribute("coins_out", coins_out.as_slice().to_string())) + .add_attribute("coins_out", estimated_coins_out.as_slice().to_string())) } pub fn estimate_provide_liquidity( diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 6097ac72d..1fee50c03 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -110,6 +110,7 @@ pub struct MockEnvBuilder { pub accounts_to_fund: Vec, pub target_health_factor: Option, pub max_unlocking_positions: Option, + pub max_slippage: Option, pub health_contract: Option, pub evil_vault: Option, } @@ -133,6 +134,7 @@ impl MockEnv { accounts_to_fund: vec![], target_health_factor: None, max_unlocking_positions: None, + max_slippage: None, health_contract: None, evil_vault: None, } @@ -898,6 +900,7 @@ impl MockEnvBuilder { let incentives = self.get_incentives(); let swapper = self.deploy_swapper().into(); let max_unlocking_positions = self.get_max_unlocking_positions(); + let max_slippage = self.get_max_slippage(); let oracle = self.get_oracle().into(); let zapper = self.deploy_zapper(&oracle)?.into(); @@ -914,6 +917,7 @@ impl MockEnvBuilder { red_bank, oracle, max_unlocking_positions, + max_slippage, swapper, zapper, health_contract, @@ -1289,6 +1293,10 @@ impl MockEnvBuilder { self.max_unlocking_positions.unwrap_or_else(|| Uint128::new(100)) } + fn get_max_slippage(&self) -> Decimal { + self.max_slippage.unwrap_or_else(|| Decimal::percent(99)) + } + //-------------------------------------------------------------------------------------------------- // Setter functions //-------------------------------------------------------------------------------------------------- @@ -1363,6 +1371,11 @@ impl MockEnvBuilder { self } + pub fn max_slippage(&mut self, max: Decimal) -> &mut Self { + self.max_slippage = Some(max); + self + } + pub fn evil_vault(&mut self, credit_account: &str) -> &mut Self { self.evil_vault = Some(credit_account.to_string()); self diff --git a/contracts/credit-manager/tests/test_deposit_cap.rs b/contracts/credit-manager/tests/test_deposit_cap.rs index a2eb4424b..b2a53b3f1 100644 --- a/contracts/credit-manager/tests/test_deposit_cap.rs +++ b/contracts/credit-manager/tests/test_deposit_cap.rs @@ -59,7 +59,7 @@ use crate::helpers::{uatom_info, uosmo_info, AccountToFund, MockEnv}; amount: ActionAmount::AccountBalance, }, denom_out: "uosmo".into(), - slippage: Decimal::one(), + slippage: Decimal::percent(5), } ], true; @@ -80,7 +80,7 @@ use crate::helpers::{uatom_info, uosmo_info, AccountToFund, MockEnv}; amount: ActionAmount::AccountBalance, }, denom_out: "uosmo".into(), - slippage: Decimal::one(), + slippage: Decimal::percent(5), } ], false; diff --git a/contracts/credit-manager/tests/test_migration_v2.rs b/contracts/credit-manager/tests/test_migration_v2.rs index 37f12abc0..942fdc50b 100644 --- a/contracts/credit-manager/tests/test_migration_v2.rs +++ b/contracts/credit-manager/tests/test_migration_v2.rs @@ -1,12 +1,15 @@ use cosmwasm_std::{ testing::{mock_dependencies, mock_env}, - Addr, + Addr, Decimal, }; use cw2::VersionError; use mars_credit_manager::{ contract::migrate, migrations::v2_0_0::{v1_state, v1_state::OwnerSetNoneProposed}, - state::{ACCOUNT_NFT, HEALTH_CONTRACT, INCENTIVES, OWNER, PARAMS, REWARDS_COLLECTOR, SWAPPER}, + state::{ + ACCOUNT_NFT, HEALTH_CONTRACT, INCENTIVES, MAX_SLIPPAGE, OWNER, PARAMS, REWARDS_COLLECTOR, + SWAPPER, + }, }; use mars_rover::{ adapters::{ @@ -32,6 +35,7 @@ fn wrong_contract_name() { params: ParamsUnchecked::new("params".to_string()), incentives: IncentivesUnchecked::new("incentives".to_string()), swapper: SwapperUnchecked::new("swapper".to_string()), + max_slippage: Decimal::percent(1), }), ) .unwrap_err(); @@ -59,6 +63,7 @@ fn wrong_contract_version() { params: ParamsUnchecked::new("params".to_string()), incentives: IncentivesUnchecked::new("incentives".to_string()), swapper: SwapperUnchecked::new("swapper".to_string()), + max_slippage: Decimal::percent(1), }), ) .unwrap_err(); @@ -95,6 +100,7 @@ fn successful_migration() { let params = "params_addr_456".to_string(); let incentives = "incentives_addr_789".to_string(); let swapper = "swapper_addr_012".to_string(); + let max_slippage = Decimal::percent(5); migrate( deps.as_mut(), @@ -104,6 +110,7 @@ fn successful_migration() { params: ParamsUnchecked::new(params.clone()), incentives: IncentivesUnchecked::new(incentives.clone()), swapper: SwapperUnchecked::new(swapper.clone()), + max_slippage, }), ) .unwrap(); @@ -124,6 +131,9 @@ fn successful_migration() { let set_rewards = REWARDS_COLLECTOR.may_load(deps.as_ref().storage).unwrap(); assert_eq!(None, set_rewards); + let set_slippage = MAX_SLIPPAGE.load(deps.as_ref().storage).unwrap(); + assert_eq!(max_slippage, set_slippage); + let o = OWNER.query(deps.as_ref().storage).unwrap(); assert_eq!(old_owner.to_string(), o.owner.unwrap()); assert!(o.proposed.is_none()); diff --git a/contracts/credit-manager/tests/test_swap.rs b/contracts/credit-manager/tests/test_swap.rs index 3822a02a6..d232aecb1 100644 --- a/contracts/credit-manager/tests/test_swap.rs +++ b/contracts/credit-manager/tests/test_swap.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use cosmwasm_std::{coins, Addr, Coin, Decimal, OverflowError, OverflowOperation::Sub, Uint128}; use mars_rover::{ error::ContractError, @@ -121,6 +123,41 @@ fn user_has_zero_balance_for_swap_req() { ) } +#[test] +fn slippage_too_high() { + let osmo_info = uosmo_info(); + let atom_info = uatom_info(); + + let user = Addr::unchecked("user"); + let max_slippage = Decimal::percent(50); + let mut mock = MockEnv::new() + .set_params(&[osmo_info.clone(), atom_info.clone()]) + .max_slippage(max_slippage) + .build() + .unwrap(); + let account_id = mock.create_credit_account(&user).unwrap(); + + let slippage = max_slippage + Decimal::from_str("0.000001").unwrap(); + let res = mock.update_credit_account( + &account_id, + &user, + vec![SwapExactIn { + coin_in: osmo_info.to_action_coin(10_000), + denom_out: atom_info.denom, + slippage, + }], + &[], + ); + + assert_err( + res, + ContractError::SlippageExceeded { + slippage, + max_slippage, + }, + ) +} + #[test] fn user_does_not_have_enough_balance_for_swap_req() { let osmo_info = uosmo_info(); diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 81d63bc26..ff29e9336 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{Addr, Decimal, Empty, Uint128}; use cw_multi_test::{BasicApp, Executor}; +use helpers::assert_err; use mars_mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; use mars_red_bank_types::oracle::ActionKind; use mars_rover::{ @@ -8,6 +9,7 @@ use mars_rover::{ red_bank::RedBankUnchecked, rewards_collector::RewardsCollector, swap::SwapperBase, zapper::ZapperBase, }, + error::ContractError, msg::instantiate::ConfigUpdates, }; use mars_rover_health_types::AccountKind; @@ -29,6 +31,7 @@ fn only_owner_can_update_config() { red_bank: None, incentives: None, max_unlocking_positions: None, + max_slippage: None, swapper: None, zapper: None, health_contract: None, @@ -41,6 +44,40 @@ fn only_owner_can_update_config() { } } +#[test] +fn invalid_max_slippage() { + let mut mock = MockEnv::new().build().unwrap(); + let original_config = mock.query_config(); + + let res = mock.update_config( + &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), + ConfigUpdates { + max_slippage: Some(Decimal::zero()), + ..Default::default() + }, + ); + assert_err( + res, + ContractError::InvalidConfig { + reason: "Max slippage must be greater than 0 and less than 1".to_string(), + }, + ); + + let res = mock.update_config( + &Addr::unchecked(original_config.ownership.owner.unwrap()), + ConfigUpdates { + max_slippage: Some(Decimal::one()), + ..Default::default() + }, + ); + assert_err( + res, + ContractError::InvalidConfig { + reason: "Max slippage must be greater than 0 and less than 1".to_string(), + }, + ); +} + #[test] fn update_config_works_with_full_config() { let mut mock = MockEnv::new().build().unwrap(); @@ -52,6 +89,7 @@ fn update_config_works_with_full_config() { let new_incentives = IncentivesUnchecked::new("new_incentives".to_string()); let new_zapper = ZapperBase::new("new_zapper".to_string()); let new_unlocking_max = Uint128::new(321); + let new_max_slippage = Decimal::percent(12); let new_swapper = SwapperBase::new("new_swapper".to_string()); let new_health_contract = HealthContractUnchecked::new("new_health_contract".to_string()); let new_rewards_collector = "rewards_collector_contract_new".to_string(); @@ -64,6 +102,7 @@ fn update_config_works_with_full_config() { red_bank: Some(new_red_bank.clone()), incentives: Some(new_incentives.clone()), max_unlocking_positions: Some(new_unlocking_max), + max_slippage: Some(new_max_slippage), swapper: Some(new_swapper.clone()), zapper: Some(new_zapper.clone()), health_contract: Some(new_health_contract.clone()), @@ -94,6 +133,9 @@ fn update_config_works_with_full_config() { assert_eq!(new_config.max_unlocking_positions, new_unlocking_max); assert_ne!(new_config.max_unlocking_positions, original_config.max_unlocking_positions); + assert_eq!(new_config.max_slippage, new_max_slippage); + assert_ne!(new_config.max_slippage, original_config.max_slippage); + assert_eq!(&new_config.swapper, new_swapper.address()); assert_ne!(new_config.swapper, original_config.swapper); diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs index a0de1ca17..04a5d6b60 100644 --- a/contracts/credit-manager/tests/test_zap_provide.rs +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -1,6 +1,6 @@ use std::ops::Mul; -use cosmwasm_std::{Addr, OverflowError, OverflowOperation::Sub, Uint128}; +use cosmwasm_std::{Addr, Decimal, OverflowError, OverflowOperation::Sub, Uint128}; use mars_rover::{ error::ContractError as RoverError, msg::execute::{ @@ -29,7 +29,7 @@ fn only_token_owner_can_zap_for_account() { vec![ProvideLiquidity { coins_in: vec![], lp_token_out: "".to_string(), - minimum_receive: Default::default(), + slippage: Decimal::percent(5), }], &[], ); @@ -69,7 +69,7 @@ fn does_not_have_enough_tokens_to_provide_liq() { ProvideLiquidity { coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(200)], lp_token_out: lp_token.denom, - minimum_receive: Uint128::zero(), + slippage: Decimal::zero(), }, ], &[atom.to_coin(100), osmo.to_coin(50)], @@ -111,7 +111,7 @@ fn lp_token_out_must_be_whitelisted() { ProvideLiquidity { coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(200)], lp_token_out: lp_token.denom.clone(), - minimum_receive: Uint128::zero(), + slippage: Decimal::zero(), }, ], &[atom.to_coin(100), osmo.to_coin(50)], @@ -143,7 +143,7 @@ fn coins_in_must_be_whitelisted() { vec![ProvideLiquidity { coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(200)], lp_token_out: lp_token.denom, - minimum_receive: Uint128::zero(), + slippage: Decimal::zero(), }], &[], ); @@ -152,14 +152,16 @@ fn coins_in_must_be_whitelisted() { } #[test] -fn min_received_too_high() { +fn slippage_too_high() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); let user = Addr::unchecked("user"); + let max_slippage = Decimal::percent(20); let mut mock = MockEnv::new() .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .max_slippage(max_slippage) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], @@ -168,6 +170,7 @@ fn min_received_too_high() { .unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); + let slippage = max_slippage + Decimal::one(); let err = mock .update_credit_account( &account_id, @@ -178,15 +181,21 @@ fn min_received_too_high() { ProvideLiquidity { coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom, - minimum_receive: Uint128::new(100_000_000_000), + slippage, }, ], &[atom.to_coin(100), osmo.to_coin(50)], ) .unwrap_err(); - let contract_err: ContractError = err.downcast().unwrap(); - assert_eq!(contract_err, ContractError::ReceivedBelowMinimum); + let contract_err: mars_rover::error::ContractError = err.downcast().unwrap(); + assert_eq!( + contract_err, + mars_rover::error::ContractError::SlippageExceeded { + slippage, + max_slippage + } + ); } #[test] @@ -216,7 +225,7 @@ fn wrong_denom_provided() { ProvideLiquidity { coins_in: vec![atom.to_action_coin(100), jake.to_action_coin(50)], lp_token_out: lp_token.denom, - minimum_receive: Uint128::zero(), + slippage: Decimal::zero(), }, ], &[atom.to_coin(100), jake.to_coin(50)], @@ -249,7 +258,8 @@ fn successful_zap() { let account_id = mock.create_credit_account(&user).unwrap(); let estimate = mock.estimate_provide_liquidity(&lp_token.denom, &[atom.to_coin(100), osmo.to_coin(50)]); - let slippage_adjusted = estimate.multiply_ratio(Uint128::new(95), Uint128::new(100)); + let slippage = Decimal::percent(5); + let slippage_adjusted = estimate * (Decimal::one() - slippage); assert_eq!(slippage_adjusted, Uint128::new(950_000)); // 1_000_000 * .95 mock.update_credit_account( @@ -261,7 +271,7 @@ fn successful_zap() { ProvideLiquidity { coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), - minimum_receive: slippage_adjusted, + slippage, }, ], &[atom.to_coin(100), osmo.to_coin(50)], @@ -314,8 +324,6 @@ fn can_provide_unbalanced() { .unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); - let estimate = mock.estimate_provide_liquidity(&lp_token.denom, &[atom.to_coin(100)]); - let slippage_adjusted = estimate.multiply_ratio(Uint128::new(95), Uint128::new(100)); mock.update_credit_account( &account_id, @@ -325,7 +333,7 @@ fn can_provide_unbalanced() { ProvideLiquidity { coins_in: vec![atom.to_action_coin(100)], lp_token_out: lp_token.denom.clone(), - minimum_receive: slippage_adjusted, + slippage: Decimal::percent(5), }, ], &[atom.to_coin(100)], @@ -352,7 +360,7 @@ fn can_provide_unbalanced() { denom: lp_token.denom.clone(), amount: ActionAmount::Exact(STARTING_LP_POOL_TOKENS.multiply_ratio(1u128, 2u128)), }, - minimum_receive: vec![], + slippage: Decimal::zero(), }], &[], ) @@ -390,7 +398,8 @@ fn order_does_not_matter() { let account_id = mock.create_credit_account(&user).unwrap(); let estimate = mock.estimate_provide_liquidity(&lp_token.denom, &[atom.to_coin(100), osmo.to_coin(50)]); - let slippage_adjusted = estimate.multiply_ratio(Uint128::new(95), Uint128::new(100)); + let slippage = Decimal::percent(5); + let slippage_adjusted = estimate * (Decimal::one() - slippage); assert_eq!(slippage_adjusted, Uint128::new(950_000)); // 1_000_000 * .95 // order A @@ -403,7 +412,7 @@ fn order_does_not_matter() { ProvideLiquidity { coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), - minimum_receive: slippage_adjusted, + slippage, }, ], &[atom.to_coin(100), osmo.to_coin(50)], @@ -420,7 +429,7 @@ fn order_does_not_matter() { ProvideLiquidity { coins_in: vec![osmo.to_action_coin(50), atom.to_action_coin(100)], lp_token_out: lp_token.denom.clone(), - minimum_receive: slippage_adjusted, + slippage, }, ], &[atom.to_coin(100), osmo.to_coin(50)], diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index 472225f0c..a0228c6d9 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{coin, Addr, Coin, OverflowError, OverflowOperation::Sub, Uint128}; +use cosmwasm_std::{Addr, Decimal, OverflowError, OverflowOperation::Sub, Uint128}; use mars_rover::{ error::ContractError as RoverError, msg::execute::{ @@ -6,10 +6,7 @@ use mars_rover::{ ActionAmount, ActionCoin, }, }; -use mars_v2_zapper_mock::{ - contract::STARTING_LP_POOL_TOKENS, - error::{ContractError, ContractError::RequirementsNotMet}, -}; +use mars_v2_zapper_mock::contract::STARTING_LP_POOL_TOKENS; use crate::helpers::{ assert_err, get_coin, lp_token_info, uatom_info, uosmo_info, AccountToFund, MockEnv, @@ -32,7 +29,7 @@ fn only_token_owner_can_unzap_for_account() { denom: "xyz".to_string(), amount: ActionAmount::AccountBalance, }, - minimum_receive: vec![], + slippage: Decimal::zero(), }], &[], ); @@ -74,11 +71,11 @@ fn does_not_have_the_tokens_to_withdraw_liq() { ProvideLiquidity { coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), - minimum_receive: Uint128::zero(), + slippage: Decimal::zero(), }, WithdrawLiquidity { lp_token: lp_token.to_action_coin(attempted_unzap_amount), - minimum_receive: vec![], + slippage: Decimal::zero(), }, ], &[atom.to_coin(100), osmo.to_coin(50)], @@ -120,7 +117,7 @@ fn amount_zero_passed() { ProvideLiquidity { coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), - minimum_receive: Uint128::zero(), + slippage: Decimal::zero(), }, ], &[atom.to_coin(100), osmo.to_coin(50)], @@ -132,7 +129,7 @@ fn amount_zero_passed() { &user, vec![WithdrawLiquidity { lp_token: lp_token.to_action_coin(0), - minimum_receive: vec![], + slippage: Decimal::zero(), }], &[], ); @@ -163,7 +160,7 @@ fn amount_none_passed_with_no_balance() { &user, vec![WithdrawLiquidity { lp_token: lp_token.to_action_coin_full_balance(), - minimum_receive: vec![], + slippage: Decimal::zero(), }], &[], ); @@ -172,14 +169,16 @@ fn amount_none_passed_with_no_balance() { } #[test] -fn min_received_not_met() { +fn slippage_too_high() { let atom = uatom_info(); let osmo = uosmo_info(); let lp_token = lp_token_info(); let user = Addr::unchecked("user"); + let max_slippage = Decimal::percent(20); let mut mock = MockEnv::new() .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .max_slippage(max_slippage) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], @@ -189,8 +188,9 @@ fn min_received_not_met() { let account_id = mock.create_credit_account(&user).unwrap(); - let mut simulate = |minimum_receive: Vec| -> ContractError { - mock.update_credit_account( + let slippage = max_slippage + Decimal::one(); + let err = mock + .update_credit_account( &account_id, &user, vec![ @@ -199,41 +199,24 @@ fn min_received_not_met() { ProvideLiquidity { coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), - minimum_receive: Uint128::zero(), + slippage: Decimal::zero(), }, WithdrawLiquidity { lp_token: lp_token.to_action_coin(STARTING_LP_POOL_TOKENS.u128()), - minimum_receive, + slippage, }, ], &[atom.to_coin(100), osmo.to_coin(50)], ) - .unwrap_err() - .downcast() - .unwrap() - }; - - assert_eq!( - simulate(vec![atom.to_coin(200), osmo.to_coin(3)]), - RequirementsNotMet("Expected min: 200uatom. Actual: 100uatom.".to_string()) - ); - - assert_eq!( - simulate(vec![atom.to_coin(90), osmo.to_coin(51)]), - RequirementsNotMet("Expected min: 51uosmo. Actual: 50uosmo.".to_string()) - ); - - assert_eq!( - simulate(vec![atom.to_coin(101), osmo.to_coin(51)]), - RequirementsNotMet( - "Expected min: 101uatom. Actual: 100uatom.; Expected min: 51uosmo. Actual: 50uosmo." - .to_string() - ) - ); + .unwrap_err(); + let contract_err: mars_rover::error::ContractError = err.downcast().unwrap(); assert_eq!( - simulate(vec![atom.to_coin(90), coin(12, "xyz")]), - RequirementsNotMet("Expected min denom xyz not found".to_string()) + contract_err, + mars_rover::error::ContractError::SlippageExceeded { + slippage, + max_slippage + } ); } @@ -264,11 +247,11 @@ fn successful_unzap_specified_amount() { ProvideLiquidity { coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), - minimum_receive: Uint128::zero(), + slippage: Decimal::zero(), }, WithdrawLiquidity { lp_token: lp_token.to_action_coin(STARTING_LP_POOL_TOKENS.u128()), - minimum_receive: vec![atom.to_coin(90), osmo.to_coin(32)], + slippage: Decimal::percent(10), }, ], &[atom.to_coin(100), osmo.to_coin(50)], @@ -328,11 +311,11 @@ fn successful_unzap_unspecified_amount() { ProvideLiquidity { coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], lp_token_out: lp_token.denom.clone(), - minimum_receive: Uint128::zero(), + slippage: Decimal::zero(), }, WithdrawLiquidity { lp_token: lp_token.to_action_coin_full_balance(), - minimum_receive: vec![], + slippage: Decimal::zero(), }, ], &[atom.to_coin(100), osmo.to_coin(50)], diff --git a/contracts/health/tests/helpers/mock_env_builder.rs b/contracts/health/tests/helpers/mock_env_builder.rs index 0d312178e..c253ccdfc 100644 --- a/contracts/health/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/helpers/mock_env_builder.rs @@ -132,6 +132,7 @@ impl MockEnvBuilder { params, account_nft: None, max_unlocking_positions: Default::default(), + max_slippage: Decimal::percent(99), swapper: "n/a".to_string(), zapper: "n/a".to_string(), health_contract: "n/a".to_string(), diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 060d3ecac..671c9aa04 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError, Coin, + CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError, Coin, Decimal, DecimalRangeExceeded, OverflowError, StdError, Uint128, }; use cw2::VersionError; @@ -174,4 +174,10 @@ pub enum ContractError { #[error("{0}")] Liquidation(#[from] LiquidationError), + + #[error("Slippage {slippage:?} exceeded max slippage {max_slippage:?}")] + SlippageExceeded { + slippage: Decimal, + max_slippage: Decimal, + }, } diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 355205971..a489b8e89 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -172,17 +172,19 @@ pub enum Action { denom_out: String, slippage: Decimal, }, - /// Add Vec to liquidity pool in exchange for LP tokens + /// Add Vec to liquidity pool in exchange for LP tokens. + /// Slippage allowance (%) is used to calculate the minimum amount of LP tokens to receive. ProvideLiquidity { coins_in: Vec, lp_token_out: String, - minimum_receive: Uint128, + slippage: Decimal, }, /// Send LP token and withdraw corresponding reserve assets from pool. /// If `lp_token.amount: AccountBalance`, the account balance of `lp_token.denom` will be used. + /// /// Slippage allowance (%) is used to calculate the minimum amount of reserve assets to receive. WithdrawLiquidity { lp_token: ActionCoin, - minimum_receive: Vec, + slippage: Decimal, }, /// Refunds all coin balances back to user wallet RefundAllCoinBalances {}, @@ -317,19 +319,19 @@ pub enum CallbackMsg { /// Protocol fee percentage transfered to rewards-collector account protocol_fee: Decimal, }, - /// Add Vec to liquidity pool in exchange for LP tokens + /// Add Vec to liquidity pool in exchange for LP tokens. ProvideLiquidity { account_id: String, coins_in: Vec, lp_token_out: String, - minimum_receive: Uint128, + slippage: Decimal, }, /// Send LP token and withdraw corresponding reserve assets from pool. /// If `lp_token.amount: AccountBalance`, the account balance of `lp_token.denom` will be used. WithdrawLiquidity { account_id: String, lp_token: ActionCoin, - minimum_receive: Vec, + slippage: Decimal, }, /// Refunds all coin balances back to user wallet RefundAllCoinBalances { diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index ac2d36352..5aa0373bc 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::Uint128; +use cosmwasm_std::{Decimal, Uint128}; use crate::adapters::{ account_nft::AccountNftUnchecked, health::HealthContractUnchecked, @@ -19,6 +19,8 @@ pub struct InstantiateMsg { /// Note: As health checking requires looping through each, this number must not be too large. /// If so, having too many could prevent the account from being liquidated due to gas constraints. pub max_unlocking_positions: Uint128, + /// The maximum slippage allowed for swaps, provide liquidity and withdraw liquidity + pub max_slippage: Decimal, /// Helper contract for making swaps pub swapper: SwapperUnchecked, /// Helper contract for adding/removing liquidity @@ -40,6 +42,7 @@ pub struct ConfigUpdates { pub red_bank: Option, pub incentives: Option, pub max_unlocking_positions: Option, + pub max_slippage: Option, pub swapper: Option, pub zapper: Option, pub health_contract: Option, diff --git a/packages/rover/src/msg/migrate.rs b/packages/rover/src/msg/migrate.rs index cb9403fd3..f62eb8263 100644 --- a/packages/rover/src/msg/migrate.rs +++ b/packages/rover/src/msg/migrate.rs @@ -1,4 +1,5 @@ use cosmwasm_schema::cw_serde; +use cosmwasm_std::Decimal; use crate::adapters::{ health::HealthContractUnchecked, incentives::IncentivesUnchecked, params::ParamsUnchecked, @@ -11,6 +12,7 @@ pub struct V2Updates { pub params: ParamsUnchecked, pub incentives: IncentivesUnchecked, pub swapper: SwapperUnchecked, + pub max_slippage: Decimal, } #[cw_serde] diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 7d0fed6a7..f1021837b 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::{Coin, Decimal, Uint128}; use mars_owner::OwnerResponse; use crate::{ @@ -166,6 +166,7 @@ pub struct ConfigResponse { pub oracle: String, pub params: String, pub max_unlocking_positions: Uint128, + pub max_slippage: Decimal, pub swapper: String, pub zapper: String, pub health_contract: String, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 361ff256a..38304293b 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -9,6 +9,7 @@ "required": [ "health_contract", "incentives", + "max_slippage", "max_unlocking_positions", "oracle", "owner", @@ -34,6 +35,14 @@ } ] }, + "max_slippage": { + "description": "The maximum slippage allowed for swaps, provide liquidity and withdraw liquidity", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, "max_unlocking_positions": { "description": "The maximum number of unlocking positions an account can have simultaneously Note: As health checking requires looping through each, this number must not be too large. If so, having too many could prevent the account from being liquidated due to gas constraints.", "allOf": [ @@ -89,6 +98,10 @@ }, "additionalProperties": false, "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "HealthContractBase_for_String": { "type": "string" }, @@ -568,7 +581,7 @@ "additionalProperties": false }, { - "description": "Add Vec to liquidity pool in exchange for LP tokens", + "description": "Add Vec to liquidity pool in exchange for LP tokens. Slippage allowance (%) is used to calculate the minimum amount of LP tokens to receive.", "type": "object", "required": [ "provide_liquidity" @@ -579,7 +592,7 @@ "required": [ "coins_in", "lp_token_out", - "minimum_receive" + "slippage" ], "properties": { "coins_in": { @@ -591,8 +604,8 @@ "lp_token_out": { "type": "string" }, - "minimum_receive": { - "$ref": "#/definitions/Uint128" + "slippage": { + "$ref": "#/definitions/Decimal" } }, "additionalProperties": false @@ -601,7 +614,7 @@ "additionalProperties": false }, { - "description": "Send LP token and withdraw corresponding reserve assets from pool. If `lp_token.amount: AccountBalance`, the account balance of `lp_token.denom` will be used.", + "description": "Send LP token and withdraw corresponding reserve assets from pool. If `lp_token.amount: AccountBalance`, the account balance of `lp_token.denom` will be used. /// Slippage allowance (%) is used to calculate the minimum amount of reserve assets to receive.", "type": "object", "required": [ "withdraw_liquidity" @@ -611,17 +624,14 @@ "type": "object", "required": [ "lp_token", - "minimum_receive" + "slippage" ], "properties": { "lp_token": { "$ref": "#/definitions/ActionCoin" }, - "minimum_receive": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } + "slippage": { + "$ref": "#/definitions/Decimal" } }, "additionalProperties": false @@ -1241,7 +1251,7 @@ "additionalProperties": false }, { - "description": "Add Vec to liquidity pool in exchange for LP tokens", + "description": "Add Vec to liquidity pool in exchange for LP tokens.", "type": "object", "required": [ "provide_liquidity" @@ -1253,7 +1263,7 @@ "account_id", "coins_in", "lp_token_out", - "minimum_receive" + "slippage" ], "properties": { "account_id": { @@ -1268,8 +1278,8 @@ "lp_token_out": { "type": "string" }, - "minimum_receive": { - "$ref": "#/definitions/Uint128" + "slippage": { + "$ref": "#/definitions/Decimal" } }, "additionalProperties": false @@ -1289,7 +1299,7 @@ "required": [ "account_id", "lp_token", - "minimum_receive" + "slippage" ], "properties": { "account_id": { @@ -1298,11 +1308,8 @@ "lp_token": { "$ref": "#/definitions/ActionCoin" }, - "minimum_receive": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } + "slippage": { + "$ref": "#/definitions/Decimal" } }, "additionalProperties": false @@ -1459,6 +1466,16 @@ } ] }, + "max_slippage": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, "max_unlocking_positions": { "anyOf": [ { @@ -2628,6 +2645,7 @@ "required": [ "health_contract", "incentives", + "max_slippage", "max_unlocking_positions", "oracle", "ownership", @@ -2649,6 +2667,9 @@ "incentives": { "type": "string" }, + "max_slippage": { + "$ref": "#/definitions/Decimal" + }, "max_unlocking_positions": { "$ref": "#/definitions/Uint128" }, @@ -2683,6 +2704,10 @@ }, "additionalProperties": false, "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "OwnerResponse": { "description": "Returned from Owner.query()", "type": "object", diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 34c39e4d2..0e650535a 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -1073,6 +1073,7 @@ "required": [ "health_contract", "incentives", + "max_slippage", "max_unlocking_positions", "oracle", "ownership", @@ -1094,6 +1095,9 @@ "incentives": { "type": "string" }, + "max_slippage": { + "$ref": "#/definitions/Decimal" + }, "max_unlocking_positions": { "$ref": "#/definitions/Uint128" }, @@ -1128,6 +1132,10 @@ }, "additionalProperties": false, "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "OwnerResponse": { "description": "Returned from Owner.query()", "type": "object", diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 4c0dbc9bf..754e05012 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -139,6 +139,7 @@ export class Deployer { const msg: RoverInstantiateMsg = { params: this.config.params.addr, max_unlocking_positions: this.config.maxUnlockingPositions, + max_slippage: this.config.maxSlippage, oracle: this.config.oracle.addr, owner: this.deployerAddr, red_bank: this.config.redBank.addr, diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index dfe38c3f6..e27f30ebc 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -164,7 +164,7 @@ export class Rover { amount: { exact: c.amount }, })), lp_token_out, - minimum_receive: '1', + slippage: '0.05', }, }, ]) @@ -186,7 +186,7 @@ export class Rover { { withdraw_liquidity: { lp_token: { amount: { exact: lpToken.amount }, denom: lpToken.denom }, - minimum_receive: [], + slippage: '0.05', }, }, ]) diff --git a/scripts/deploy/osmosis/mainnet.ts b/scripts/deploy/osmosis/mainnet.ts index ed0025146..2d73b366e 100644 --- a/scripts/deploy/osmosis/mainnet.ts +++ b/scripts/deploy/osmosis/mainnet.ts @@ -19,6 +19,7 @@ export const osmosisMainnetConfig: DeploymentConfig = { }, deployerMnemonic: 'TO BE INSERTED AT TIME OF DEPLOYMENT', maxUnlockingPositions: '1', + maxSlippage: '0.2', maxValueForBurn: '10000', // oracle and redbank contract addresses can be found: https://github.com/mars-protocol/red-bank/blob/master/README.md#osmosis-1 oracle: { addr: 'osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g' }, diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index b9d2819ec..e8fea3d15 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -39,6 +39,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { deployerMnemonic: 'rely wonder join knock during sudden slow plate segment state agree also arrest mandate grief ordinary lonely lawsuit hurt super banana rule velvet cart', maxUnlockingPositions: '10', + maxSlippage: '0.2', maxValueForBurn: '1000000', // Latest from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-5.json redBank: { addr: 'osmo1hs4sm0fah9rk4mz8e56v4n76g0q9fffdkkjm3f8tjagkdx78pqcq75pk0a' }, diff --git a/scripts/types/config.ts b/scripts/types/config.ts index bc336f2c3..cd5555243 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -30,6 +30,7 @@ export interface DeploymentConfig { allowedCoins: string[] maxValueForBurn: string maxUnlockingPositions: string + maxSlippage: string testActions?: TestActions swapperContractName: string zapperContractName: string diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 4564eb356..ae7d52be9 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -10,6 +10,7 @@ import { StdFee } from '@cosmjs/amino' import { HealthContractBaseForString, IncentivesUnchecked, + Decimal, Uint128, OracleBaseForString, ParamsBaseForString, @@ -23,7 +24,6 @@ import { ActionAmount, LiquidateRequestForVaultBaseForString, VaultPositionType, - Decimal, AccountNftBaseForString, OwnerUpdate, CallbackMsg, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 71478e495..1321ce030 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -11,6 +11,7 @@ import { toUtf8 } from '@cosmjs/encoding' import { HealthContractBaseForString, IncentivesUnchecked, + Decimal, Uint128, OracleBaseForString, ParamsBaseForString, @@ -24,7 +25,6 @@ import { ActionAmount, LiquidateRequestForVaultBaseForString, VaultPositionType, - Decimal, AccountNftBaseForString, OwnerUpdate, CallbackMsg, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 1211afe59..81988f5c6 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -11,6 +11,7 @@ import { StdFee } from '@cosmjs/amino' import { HealthContractBaseForString, IncentivesUnchecked, + Decimal, Uint128, OracleBaseForString, ParamsBaseForString, @@ -24,7 +25,6 @@ import { ActionAmount, LiquidateRequestForVaultBaseForString, VaultPositionType, - Decimal, AccountNftBaseForString, OwnerUpdate, CallbackMsg, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 70ae05336..4b370f32d 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -7,6 +7,7 @@ export type HealthContractBaseForString = string export type IncentivesUnchecked = string +export type Decimal = string export type Uint128 = string export type OracleBaseForString = string export type ParamsBaseForString = string @@ -16,6 +17,7 @@ export type ZapperBaseForString = string export interface InstantiateMsg { health_contract: HealthContractBaseForString incentives: IncentivesUnchecked + max_slippage: Decimal max_unlocking_positions: Uint128 oracle: OracleBaseForString owner: string @@ -124,13 +126,13 @@ export type Action = provide_liquidity: { coins_in: ActionCoin[] lp_token_out: string - minimum_receive: Uint128 + slippage: Decimal } } | { withdraw_liquidity: { lp_token: ActionCoin - minimum_receive: Coin[] + slippage: Decimal } } | { @@ -155,7 +157,6 @@ export type LiquidateRequestForVaultBaseForString = } } export type VaultPositionType = 'u_n_l_o_c_k_e_d' | 'l_o_c_k_e_d' | 'u_n_l_o_c_k_i_n_g' -export type Decimal = string export type AccountNftBaseForString = string export type OwnerUpdate = | { @@ -298,14 +299,14 @@ export type CallbackMsg = account_id: string coins_in: ActionCoin[] lp_token_out: string - minimum_receive: Uint128 + slippage: Decimal } } | { withdraw_liquidity: { account_id: string lp_token: ActionCoin - minimum_receive: Coin[] + slippage: Decimal } } | { @@ -366,6 +367,7 @@ export interface ConfigUpdates { account_nft?: AccountNftBaseForString | null health_contract?: HealthContractBaseForString | null incentives?: IncentivesUnchecked | null + max_slippage?: Decimal | null max_unlocking_positions?: Uint128 | null oracle?: OracleBaseForString | null red_bank?: RedBankUnchecked | null @@ -503,6 +505,7 @@ export interface ConfigResponse { account_nft?: string | null health_contract: string incentives: string + max_slippage: Decimal max_unlocking_positions: Uint128 oracle: string ownership: OwnerResponse diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index bb5c4c355..439a2263b 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -36,6 +36,7 @@ import { DebtShares, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, + Decimal, ConfigResponse, OwnerResponse, RewardsCollector, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index 5a31602cb..4ba736f65 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -37,6 +37,7 @@ import { DebtShares, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, + Decimal, ConfigResponse, OwnerResponse, RewardsCollector, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index 5269a9599..93759791b 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -37,6 +37,7 @@ import { DebtShares, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, + Decimal, ConfigResponse, OwnerResponse, RewardsCollector, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index b5791d852..373717ece 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -165,10 +165,12 @@ export interface VaultPositionResponseItem { account_id: string position: VaultPosition } +export type Decimal = string export interface ConfigResponse { account_nft?: string | null health_contract: string incentives: string + max_slippage: Decimal max_unlocking_positions: Uint128 oracle: string ownership: OwnerResponse From a31c960cb9f698c62fa329a06d42426d148f6c2f Mon Sep 17 00:00:00 2001 From: piobab Date: Tue, 19 Sep 2023 10:13:20 +0200 Subject: [PATCH 214/218] Devnet deployment (#194) * New devnet deployment. * New Addrs and fixes to deployment. * Create new deployment with TWAP. Gran credit line for CM. * New devnet deployment with Pyth setup. * Fmt * Fix max_slippage config. * New deployment. * Update red-bank dep to latest master commit. --- Cargo.lock | 68 +++++++++---------- Cargo.toml | 12 ++-- contracts/mock-red-bank/src/contract.rs | 1 + packages/rover/src/adapters/red_bank.rs | 1 + .../mars-mock-red-bank.json | 7 ++ scripts/deploy/addresses/devnet-devnet.json | 6 ++ scripts/deploy/base/deployer.ts | 52 +++++++++++--- scripts/deploy/base/index.ts | 6 +- scripts/deploy/osmosis/devnet.ts | 52 ++++++++++++++ scripts/deploy/osmosis/mainnet.ts | 10 +-- scripts/deploy/osmosis/testnet-config.ts | 28 +++----- scripts/package.json | 1 + scripts/types/config.ts | 7 +- .../MarsMockRedBank.client.ts | 5 ++ .../MarsMockRedBank.message-composer.ts | 5 ++ .../MarsMockRedBank.react-query.ts | 1 + .../MarsMockRedBank.types.ts | 1 + scripts/types/storageItems.ts | 1 + 18 files changed, 184 insertions(+), 80 deletions(-) create mode 100644 scripts/deploy/addresses/devnet-devnet.json create mode 100644 scripts/deploy/osmosis/devnet.ts diff --git a/Cargo.lock b/Cargo.lock index 37340cf16..6be88e631 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1466,7 +1466,7 @@ dependencies = [ "mars-mock-credit-manager", "mars-mock-rover-health", "mars-owner 2.0.0", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", "mars-rover 1.0.0", "mars-rover 2.0.0", "mars-rover-health-types", @@ -1486,15 +1486,15 @@ dependencies = [ [[package]] name = "mars-address-provider" -version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" +version = "2.0.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=afaf815#afaf815b3801519d28d0eab99b2bf7cce0e3c85d" dependencies = [ "bech32", "cosmwasm-std", "cw-storage-plus 1.1.0", "cw2 1.1.0", "mars-owner 2.0.0", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", "thiserror", ] @@ -1524,7 +1524,7 @@ dependencies = [ "mars-mock-vault", "mars-owner 2.0.0", "mars-params", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", "mars-rover 2.0.0", "mars-rover-health", "mars-rover-health-types", @@ -1544,33 +1544,33 @@ dependencies = [ [[package]] name = "mars-health" -version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" +version = "2.0.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=afaf815#afaf815b3801519d28d0eab99b2bf7cce0e3c85d" dependencies = [ "cosmwasm-std", "mars-params", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", "thiserror", ] [[package]] name = "mars-interest-rate" -version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" +version = "2.0.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=afaf815#afaf815b3801519d28d0eab99b2bf7cce0e3c85d" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "mars-red-bank-types 1.2.0", - "mars-utils 1.2.0", + "mars-red-bank-types 2.0.0", + "mars-utils 2.0.0", ] [[package]] name = "mars-liquidation" version = "1.0.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" +source = "git+https://github.com/mars-protocol/red-bank?rev=afaf815#afaf815b3801519d28d0eab99b2bf7cce0e3c85d" dependencies = [ "cosmwasm-std", - "mars-health 1.2.0", + "mars-health 2.0.0", "mars-params", "thiserror", ] @@ -1595,7 +1595,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", ] [[package]] @@ -1605,7 +1605,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", ] [[package]] @@ -1616,7 +1616,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", ] [[package]] @@ -1638,7 +1638,7 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw-utils 1.0.1", "cw-vault-standard 0.3.1", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", "mars-rover 2.0.0", "thiserror", ] @@ -1671,8 +1671,8 @@ dependencies = [ [[package]] name = "mars-params" -version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" +version = "2.0.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=afaf815#afaf815b3801519d28d0eab99b2bf7cce0e3c85d" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1680,8 +1680,8 @@ dependencies = [ "cw2 1.1.0", "mars-interest-rate", "mars-owner 2.0.0", - "mars-red-bank-types 1.2.0", - "mars-utils 1.2.0", + "mars-red-bank-types 2.0.0", + "mars-utils 2.0.0", "thiserror", ] @@ -1700,13 +1700,13 @@ dependencies = [ [[package]] name = "mars-red-bank-types" -version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" +version = "2.0.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=afaf815#afaf815b3801519d28d0eab99b2bf7cce0e3c85d" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "mars-owner 2.0.0", - "mars-utils 1.2.0", + "mars-utils 2.0.0", "strum", "thiserror", ] @@ -1747,7 +1747,7 @@ dependencies = [ "mars-liquidation", "mars-owner 2.0.0", "mars-params", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", "mars-rover-health-types", "mars-v2-zapper-base", "schemars", @@ -1772,7 +1772,7 @@ dependencies = [ "mars-mock-vault", "mars-owner 2.0.0", "mars-params", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", "mars-rover 2.0.0", "mars-rover-health-computer", "mars-rover-health-types", @@ -1804,9 +1804,9 @@ version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "mars-health 1.2.0", + "mars-health 2.0.0", "mars-owner 2.0.0", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", "schemars", "serde", "serde_json", @@ -1824,7 +1824,7 @@ dependencies = [ "cw-paginate", "cw-storage-plus 1.1.0", "mars-owner 2.0.0", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", "schemars", "serde", "thiserror", @@ -1837,7 +1837,7 @@ dependencies = [ "anyhow", "cosmwasm-std", "cw-multi-test", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", ] [[package]] @@ -1852,8 +1852,8 @@ dependencies = [ [[package]] name = "mars-utils" -version = "1.2.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=8c90842#8c908426a9a472cf96acceed69857be6c6574a33" +version = "2.0.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=afaf815#afaf815b3801519d28d0eab99b2bf7cce0e3c85d" dependencies = [ "cosmwasm-std", "thiserror", @@ -1880,7 +1880,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 1.0.1", - "mars-red-bank-types 1.2.0", + "mars-red-bank-types 2.0.0", "mars-rover 2.0.0", "mars-v2-zapper-base", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 550d8e1e9..bd8b73b82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,11 +66,11 @@ wasm-bindgen = "0.2.87" # mars packages mars-account-nft-types = { version = "2.0.0", path = "./packages/account-nft-types" } -mars-health = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842" } -mars-liquidation = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842" } -mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842" } +mars-health = { git = "https://github.com/mars-protocol/red-bank", rev = "afaf815" } +mars-liquidation = { git = "https://github.com/mars-protocol/red-bank", rev = "afaf815" } +mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "afaf815" } mars-owner = { version = "2.0.0", features = ["emergency-owner"] } -mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842" } +mars-red-bank-types = { git = "https://github.com/mars-protocol/red-bank", rev = "afaf815" } mars-rover-health-computer = { version = "2.0.0", path = "./packages/health-computer" } mars-rover-health-types = { version = "2.0.0", path = "./packages/health-types" } mars-rover = { version = "2.0.0", path = "./packages/rover" } @@ -78,9 +78,9 @@ mars-rover = { version = "2.0.0", path = "./packages/rover" } mars-rover-old = { package = "mars-rover", git = "https://github.com/mars-protocol/v2-fields-of-mars", rev = "183e4c5" } # contracts -mars-address-provider = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842", features = ["library"] } +mars-address-provider = { git = "https://github.com/mars-protocol/red-bank", rev = "afaf815", features = ["library"] } mars-account-nft = { version = "2.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-params = { git = "https://github.com/mars-protocol/red-bank", rev = "8c90842", features = ["library"] } +mars-params = { git = "https://github.com/mars-protocol/red-bank", rev = "afaf815", features = ["library"] } mars-rover-health = { version = "2.0.0", path = "./contracts/health", features = ["library"] } mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base", features = ["library"] } mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base", features = ["library"] } diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index 84151701b..bd6d15e90 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -42,6 +42,7 @@ pub fn execute( } => repay(deps, info), red_bank::ExecuteMsg::Deposit { account_id, + on_behalf_of: _, } => deposit(deps, info, account_id), red_bank::ExecuteMsg::Withdraw { denom, diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index e972d3840..8d5e08929 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -75,6 +75,7 @@ impl RedBank { contract_addr: self.addr.to_string(), msg: to_binary(&red_bank::ExecuteMsg::Deposit { account_id: Some(account_id.to_string()), + on_behalf_of: None, })?, funds: vec![coin.clone()], })) diff --git a/schemas/mars-mock-red-bank/mars-mock-red-bank.json b/schemas/mars-mock-red-bank/mars-mock-red-bank.json index 80274d853..3e76d3be9 100644 --- a/schemas/mars-mock-red-bank/mars-mock-red-bank.json +++ b/schemas/mars-mock-red-bank/mars-mock-red-bank.json @@ -164,6 +164,13 @@ "string", "null" ] + }, + "on_behalf_of": { + "description": "Address that will receive the coins", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false diff --git a/scripts/deploy/addresses/devnet-devnet.json b/scripts/deploy/addresses/devnet-devnet.json new file mode 100644 index 000000000..1ce505739 --- /dev/null +++ b/scripts/deploy/addresses/devnet-devnet.json @@ -0,0 +1,6 @@ +{ + "zapper": "osmo1q4kkvuy8wc9fs8sfm7zyeh4k25vssd0l68nrph8s7unvq5jdq67swrepj4", + "healthContract": "osmo1r3gfdwv8xe9drtwaxq6t5ek32huezdeal0zq989ag007yk4cu47qpmu7z6", + "creditManager": "osmo1m83kw2vehyt9urxf79qa9rxk8chgs4464e5h8s37yhnw3pwauuqq7lux8r", + "accountNft": "osmo1pdr8mvj2ky9hzj5pjp026apfmd0pacd3xrzx3mzazy7lulnsdrkq96gzk3" +} diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 754e05012..024d9be9a 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -93,6 +93,7 @@ export class Deployer { } this.storage.actions.healthContractConfigUpdate = true } + async instantiateNftContract() { const msg: NftInstantiateMsg = { max_value_for_burn: this.config.maxValueForBurn, @@ -152,6 +153,27 @@ export class Deployer { await this.instantiate('creditManager', this.storage.codeIds.creditManager!, msg) } + async setConfigOnCreditManagerContract() { + if (this.storage.actions.creditManagerContractConfigUpdate) { + printGray('credit manager contract config already updated') + } else { + const hExec = new MarsCreditManagerClient( + this.cwClient, + this.deployerAddr, + this.storage.addresses.creditManager!, + ) + + printBlue('Setting health contract address in nft contract via credit manager contract') + await hExec.updateNftConfig({ + config: { + health_contract_addr: this.storage.addresses.healthContract!, + credit_manager_contract_addr: this.storage.addresses.creditManager!, + }, + }) + } + this.storage.actions.creditManagerContractConfigUpdate = true + } + async transferNftContractOwnership() { if (!this.storage.actions.proposedNewOwner) { const nftClient = new MarsAccountNftClient( @@ -208,25 +230,20 @@ export class Deployer { return } - const wallet = await getWallet( - this.config.testActions!.outpostsDeployerMnemonic, - this.config.chain.prefix, - ) + const wallet = await getWallet(this.config.deployerMnemonic, this.config.chain.prefix) const client = await setupClient(this.config, wallet) const addr = await getAddress(wallet) - for (const denom of this.config.testActions?.allowedCoinsConfig - .filter((c) => c.grantCreditLine) - .map((c) => c.denom) ?? []) { + for (const creditLineCoin of this.config.creditLineCoins) { const msg = { update_uncollateralized_loan_limit: { user: this.storage.addresses.creditManager, - denom, - new_limit: this.config.testActions!.defaultCreditLine, + denom: creditLineCoin.denom, + new_limit: creditLineCoin.creditLine, }, } printBlue( - `Granting credit line to Rover for: ${this.config.testActions!.defaultCreditLine} ${denom}`, + `Granting credit line to Rover for: ${creditLineCoin.creditLine} ${creditLineCoin.denom}`, ) await client.execute(addr, this.config.redBank.addr, msg, 'auto') } @@ -234,6 +251,21 @@ export class Deployer { this.storage.actions.grantedCreditLines = true } + async updateAddressProviderWithNewAddrs() { + const wallet = await getWallet(this.config.deployerMnemonic, this.config.chain.prefix) + const client = await setupClient(this.config, wallet) + const addr = await getAddress(wallet) + + const msg = { + set_address: { + address: this.storage.addresses.creditManager!, + address_type: 'credit_manager', + }, + } + printBlue('Updating address-provider contract with new CM address') + await client.execute(addr, this.config.addressProvider.addr, msg, 'auto') + } + async updateCreditManagerOwner() { if (!this.config.multisigAddr) throw new Error('No multisig addresses to transfer ownership to') diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 17f2d433c..0b3f6542d 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -26,12 +26,14 @@ export const taskRunner = async ({ config, label }: TaskRunnerProps) => { await deployer.instantiateNftContract() await deployer.setConfigOnHealthContract() await deployer.transferNftContractOwnership() + await deployer.setConfigOnCreditManagerContract() + await deployer.updateAddressProviderWithNewAddrs() await deployer.saveDeploymentAddrsToFile(label) + await deployer.grantCreditLines() + // Test basic user flows if (config.runTests && config.testActions) { - await deployer.grantCreditLines() - const rover = await deployer.newUserRoverClient(config.testActions) await rover.createCreditAccount() await rover.deposit() diff --git a/scripts/deploy/osmosis/devnet.ts b/scripts/deploy/osmosis/devnet.ts new file mode 100644 index 000000000..1dd0a170a --- /dev/null +++ b/scripts/deploy/osmosis/devnet.ts @@ -0,0 +1,52 @@ +import { taskRunner } from '../base' +import { DeploymentConfig } from '../../types/config' + +const osmo = 'uosmo' +const atom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2' +// const axl = 'ibc/903A61A498756EA560B85A85132D3AEE21B5DEDD41213725D22ABF276EA6945E' +// const stAtom = 'ibc/C140AFD542AE77BD7DCC83F13FDD8C5E5BB8C4929785E6EC2F4C636F98F17901' +const wbtc = 'ibc/D1542AA8762DB13087D8364F3EA6509FD6F009A34F00426AF9E4F9FA85CBBF1F' +const axlUSDC = 'ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858' +const eth = 'ibc/EA1D43981D5C9A1C4AAEA9C23BB1D4FA126BA9BC7020A25E0AE4AA841EA25DC5' + +const defaultCreditLine = '1000000000000' + +export const osmosisDevnetConfig: DeploymentConfig = { + // multisigAddr: 'osmo14w4x949nwcrqgfe53pxs3k7x53p0gvlrq34l5n', + creditLineCoins: [ + // AXL and stAtom has borrowing disabled + { denom: osmo, creditLine: defaultCreditLine }, + { denom: atom, creditLine: defaultCreditLine }, + { denom: wbtc, creditLine: defaultCreditLine }, + { denom: axlUSDC, creditLine: defaultCreditLine }, + { denom: eth, creditLine: '1000000000000000000000' }, + ], + chain: { + baseDenom: osmo, + defaultGasPrice: 0.1, + id: 'devnet', + prefix: 'osmo', + rpcEndpoint: 'https://rpc.devnet.osmosis.zone', + }, + deployerMnemonic: 'TODO', + maxUnlockingPositions: '1', + maxSlippage: '0.2', + maxValueForBurn: '10000', + // oracle and redbank contract addresses can be found: https://github.com/mars-protocol/red-bank/blob/master/README.md#osmosis-1 + addressProvider: { addr: 'osmo1c59a0jj0e7erenwekxhq3ylq3w3kakhwxtj9ll778n8av8tu5q4qarp2mv' }, + oracle: { addr: 'osmo156elt2tp5455q9a6vfrvnpncxyd33cxm9z2lgguwg6dgws9tedps5tq3rc' }, + redBank: { addr: 'osmo1vxpdcw092n9rngvekve8g324c2yjx56496ck98ag4sdxr4p4zd4q0wr7x6' }, + incentives: { addr: 'osmo1r9w7k774vcxeuvq6ctq0z2j6wkkxpskucgjkqt0uu7u07l03s3js6ukge4' }, + params: { addr: 'osmo1pzszwkyy0x9cu6p2uknwa3wccr79xwmqn9gj66fnjnayr28tzp6qh2n4qg' }, + swapper: { addr: 'osmo1xmhhdxgk9e83n4kmtlluzx38mya8q9r4hku5nys8cr7jg7sgpx5s8zkkg2' }, + runTests: false, + vaults: [], + zapperContractName: 'mars_v2_zapper_osmosis', +} + +void (async function () { + await taskRunner({ + config: osmosisDevnetConfig, + label: 'devnet', + }) +})() diff --git a/scripts/deploy/osmosis/mainnet.ts b/scripts/deploy/osmosis/mainnet.ts index 2d73b366e..ac476891f 100644 --- a/scripts/deploy/osmosis/mainnet.ts +++ b/scripts/deploy/osmosis/mainnet.ts @@ -2,14 +2,14 @@ import { taskRunner } from '../base' import { DeploymentConfig } from '../../types/config' const uosmo = 'uosmo' -const uatom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2' +// const uatom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2' const axlUSDC = 'ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858' -const gammPool1 = 'gamm/pool/1' -const gammPool678 = 'gamm/pool/678' +// const gammPool1 = 'gamm/pool/1' +// const gammPool678 = 'gamm/pool/678' export const osmosisMainnetConfig: DeploymentConfig = { multisigAddr: 'osmo14w4x949nwcrqgfe53pxs3k7x53p0gvlrq34l5n', - allowedCoins: [uosmo, uatom, axlUSDC, gammPool1, gammPool678], + creditLineCoins: [], chain: { baseDenom: uosmo, defaultGasPrice: 0.1, @@ -22,6 +22,7 @@ export const osmosisMainnetConfig: DeploymentConfig = { maxSlippage: '0.2', maxValueForBurn: '10000', // oracle and redbank contract addresses can be found: https://github.com/mars-protocol/red-bank/blob/master/README.md#osmosis-1 + addressProvider: { addr: 'osmo1g677w7mfvn78eeudzwylxzlyz69fsgumqrscj6tekhdvs8fye3asufmvxr' }, oracle: { addr: 'osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g' }, redBank: { addr: 'osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg' }, incentives: { addr: 'osmo1nkahswfr8shg8rlxqwup0vgahp0dk4x8w6tkv3rra8rratnut36sk22vrm' }, @@ -44,7 +45,6 @@ export const osmosisMainnetConfig: DeploymentConfig = { whitelisted: true, }, ], - swapperContractName: 'mars_swapper_osmosis', zapperContractName: 'mars_zapper_osmosis', } diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index e8fea3d15..29f605b7e 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -3,10 +3,10 @@ import { DeploymentConfig, VaultType } from '../../types/config' // Note: since osmo-test-5 upgrade, testnet and mainnet denoms are no longer the same. Reference asset info here: https://docs.osmosis.zone/osmosis-core/asset-info/ const uosmo = 'uosmo' const aUSDC = 'ibc/6F34E1BD664C36CE49ACC28E60D62559A5F96C4F9A6CCE4FC5A67B2852E24CFE' // axelar USDC -const atom = 'ibc/A8C2D23A1E6F95DA4E48BA349667E322BD7A6C996D8A4AAE8BA72E190F3D1477' +// const atom = 'ibc/A8C2D23A1E6F95DA4E48BA349667E322BD7A6C996D8A4AAE8BA72E190F3D1477' const ausdcOsmoPool = 'gamm/pool/5' -const atomOsmoPool = 'gamm/pool/12' +// const atomOsmoPool = 'gamm/pool/12' // All vaults below are ONE day vaults const atomOsmoVault = 'osmo1m45ap4rq4m2mfjkcqu9ks9mxmyx2hvx0cdca9sjmrg46q7lghzqqhxxup5' @@ -27,8 +27,14 @@ const aUSDC_OSMO_Config = (addr: string) => ({ whitelisted: true, }) +const defaultCreditLine = '100000000000' + export const osmosisTestnetConfig: DeploymentConfig = { - allowedCoins: [uosmo, aUSDC, atom, ausdcOsmoPool, atomOsmoPool], + creditLineCoins: [ + { denom: uosmo, creditLine: defaultCreditLine }, + { denom: aUSDC, creditLine: defaultCreditLine }, + { denom: ausdcOsmoPool, creditLine: defaultCreditLine }, + ], chain: { baseDenom: uosmo, defaultGasPrice: 0.1, @@ -42,6 +48,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { maxSlippage: '0.2', maxValueForBurn: '1000000', // Latest from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-5.json + addressProvider: { addr: 'osmo1wlm6dc0vnncu2v5z26rv97plmlkmalm84uwqatrlftc4gmp8ahgqs6r4py' }, redBank: { addr: 'osmo1hs4sm0fah9rk4mz8e56v4n76g0q9fffdkkjm3f8tjagkdx78pqcq75pk0a' }, incentives: { addr: 'osmo1nu0k6g294jela67vyth6nwr3l42gutq2m07pg9927f7v7tuv0d4sre9fr7' }, oracle: { addr: 'osmo1dxu93scjdnx42txdp9d4hm3snffvnzmkp4jpc9sml8xlu3ncgamsl2lx58' }, @@ -49,23 +56,9 @@ export const osmosisTestnetConfig: DeploymentConfig = { params: { addr: 'osmo1h334tvddn82m4apm08rm9k6kt32ws7vy0c4n30ngrvu6h6yxh8eq9l9jfh' }, // Latest from: https://api.apollo.farm/api/graph?query=query+MyQuery+%7B%0A++vaults%28network%3A+osmo_test_5%29+%7B%0A++++label%0A++++contract_address%0A++%7D%0A%7D vaults: [aUSDC_OSMO_Config(ausdcOsmoVault), ATOM_OSMO_Config(atomOsmoVault)], - swapperContractName: 'mars_swapper_osmosis', zapperContractName: 'mars_v2_zapper_osmosis', runTests: true, testActions: { - allowedCoinsConfig: [ - { denom: uosmo, priceSource: { fixed: { price: '1' } }, grantCreditLine: true }, - { - denom: aUSDC, - priceSource: { geometric_twap: { pool_id: 5, window_size: 1800 } }, - grantCreditLine: true, - }, - { - denom: ausdcOsmoPool, - priceSource: { xyk_liquidity_token: { pool_id: 6 } }, - grantCreditLine: false, - }, - ], vault: { depositAmount: '1000000', withdrawAmount: '1000000', @@ -86,7 +79,6 @@ export const osmosisTestnetConfig: DeploymentConfig = { 'elevator august inherit simple buddy giggle zone despair marine rich swim danger blur people hundred faint ladder wet toe strong blade utility trial process', borrowAmount: '10', repayAmount: '11', - defaultCreditLine: '100000000000', depositAmount: '100', lendAmount: '10', reclaimAmount: '5', diff --git a/scripts/package.json b/scripts/package.json index a05f9b382..7e2e16434 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -5,6 +5,7 @@ "scripts": { "deploy:osmosis-testnet": "yarn build && node build/deploy/osmosis/testnet-deployer.js", "deploy:osmosis-testnet-multisig": "yarn build && node build/deploy/osmosis/testnet-multisig.js", + "deploy:osmosis-devnet": "yarn build && node build/deploy/osmosis/devnet.js", "deploy:osmosis-mainnet": "yarn build && node build/deploy/osmosis/mainnet.js", "generate-types": "yarn rust-schema && tsc --project codegen-tsconfig.json && rm -rf types/generated && node build/codegen && node build/codegen/insertIgnores.js && yarn format", "rust-schema": "cd ../ && cargo make generate-all-schemas && cd scripts", diff --git a/scripts/types/config.ts b/scripts/types/config.ts index cd5555243..121cfa875 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -1,5 +1,4 @@ import { Duration, VaultInfoResponse } from './generated/mars-mock-vault/MarsMockVault.types' -import { PriceSource } from './priceSource' import { VaultConfigBaseForString } from './generated/mars-params/MarsParams.types' export enum VaultType { @@ -21,18 +20,18 @@ export interface DeploymentConfig { baseDenom: string } deployerMnemonic: string + addressProvider: { addr: string } oracle: { addr: string } redBank: { addr: string } incentives: { addr: string } params: { addr: string } swapper: { addr: string } vaults: VaultConfigBaseForString[] - allowedCoins: string[] + creditLineCoins: { denom: string; creditLine: String }[] maxValueForBurn: string maxUnlockingPositions: string maxSlippage: string testActions?: TestActions - swapperContractName: string zapperContractName: string multisigAddr?: string runTests: boolean @@ -45,7 +44,6 @@ export interface SwapRoute { } export interface TestActions { - allowedCoinsConfig: { denom: string; priceSource: PriceSource; grantCreditLine: boolean }[] vault: { depositAmount: string withdrawAmount: string @@ -59,7 +57,6 @@ export interface TestActions { } outpostsDeployerMnemonic: string secondaryDenom: string - defaultCreditLine: string startingAmountForTestUser: string depositAmount: string lendAmount: string diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index fc9eea564..132f8eb7e 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -445,8 +445,10 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa deposit: ( { accountId, + onBehalfOf, }: { accountId?: string + onBehalfOf?: string }, fee?: number | StdFee | 'auto', memo?: string, @@ -671,8 +673,10 @@ export class MarsMockRedBankClient deposit = async ( { accountId, + onBehalfOf, }: { accountId?: string + onBehalfOf?: string }, fee: number | StdFee | 'auto' = 'auto', memo?: string, @@ -684,6 +688,7 @@ export class MarsMockRedBankClient { deposit: { account_id: accountId, + on_behalf_of: onBehalfOf, }, }, fee, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts index 879f1e47d..69d838405 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts @@ -80,8 +80,10 @@ export interface MarsMockRedBankMessage { deposit: ( { accountId, + onBehalfOf, }: { accountId?: string + onBehalfOf?: string }, _funds?: Coin[], ) => MsgExecuteContractEncodeObject @@ -290,8 +292,10 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { deposit = ( { accountId, + onBehalfOf, }: { accountId?: string + onBehalfOf?: string }, _funds?: Coin[], ): MsgExecuteContractEncodeObject => { @@ -304,6 +308,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { JSON.stringify({ deposit: { account_id: accountId, + on_behalf_of: onBehalfOf, }, }), ), diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index 750cb0cb6..6394938f4 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -653,6 +653,7 @@ export interface MarsMockRedBankDepositMutation { client: MarsMockRedBankClient msg: { accountId?: string + onBehalfOf?: string } args?: { fee?: number | StdFee | 'auto' diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index 0b42e550b..01ea617a9 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -39,6 +39,7 @@ export type ExecuteMsg = | { deposit: { account_id?: string | null + on_behalf_of?: string | null } } | { diff --git a/scripts/types/storageItems.ts b/scripts/types/storageItems.ts index 848bde0ae..4579f9422 100644 --- a/scripts/types/storageItems.ts +++ b/scripts/types/storageItems.ts @@ -27,6 +27,7 @@ export interface StorageItems { oraclePricesSet?: boolean redBankMarketsSet?: boolean healthContractConfigUpdate?: boolean + creditManagerContractConfigUpdate?: boolean } owner?: string } From a5e071c649a77c0816c20919f772b33f5c5a989f Mon Sep 17 00:00:00 2001 From: piobab Date: Tue, 26 Sep 2023 13:40:11 +0200 Subject: [PATCH 215/218] Migration V1 -> V2 (#198) * Migrate zapper contract. * Fix zapper naming. * Migrate red-bank state in CM. --- Cargo.lock | 15 +-- Cargo.toml | 4 +- contracts/credit-manager/Cargo.toml | 2 +- .../credit-manager/src/migrations/v2_0_0.rs | 13 ++- .../credit-manager/tests/helpers/contracts.rs | 6 +- .../credit-manager/tests/helpers/mock_env.rs | 2 +- .../credit-manager/tests/test_migration_v2.rs | 10 +- .../credit-manager/tests/test_zap_provide.rs | 2 +- .../credit-manager/tests/test_zap_withdraw.rs | 2 +- contracts/v2-zapper/base/Cargo.toml | 3 +- contracts/v2-zapper/base/examples/schema.rs | 2 +- contracts/v2-zapper/base/src/error.rs | 5 +- contracts/v2-zapper/mock/Cargo.toml | 4 +- contracts/v2-zapper/mock/examples/schema.rs | 2 +- contracts/v2-zapper/mock/src/contract.rs | 2 +- contracts/v2-zapper/osmosis/Cargo.toml | 10 +- contracts/v2-zapper/osmosis/src/contract.rs | 17 +++- contracts/v2-zapper/osmosis/src/lib.rs | 1 + contracts/v2-zapper/osmosis/src/lp_pool.rs | 2 +- .../v2-zapper/osmosis/src/migrations/mod.rs | 1 + .../osmosis/src/migrations/v2_0_0.rs | 23 +++++ .../v2-zapper/osmosis/tests/helpers/utils.rs | 2 +- .../v2-zapper/osmosis/tests/test_callback.rs | 2 +- .../v2-zapper/osmosis/tests/test_migration.rs | 97 +++++++++++++++++++ .../osmosis/tests/test_provide_liquidity.rs | 2 +- .../v2-zapper/osmosis/tests/test_queries.rs | 2 +- .../osmosis/tests/test_withdraw_liquidity.rs | 2 +- packages/rover/Cargo.toml | 2 +- packages/rover/src/adapters/zapper.rs | 2 +- schema.Makefile.toml | 2 +- .../mars-zapper-base.json} | 2 +- scripts/deploy/base/deployer.ts | 2 +- scripts/deploy/osmosis/devnet.ts | 2 +- scripts/deploy/osmosis/testnet-config.ts | 2 +- .../MarsZapperBase.client.ts} | 14 +-- .../MarsZapperBase.message-composer.ts} | 6 +- .../MarsZapperBase.react-query.ts} | 68 ++++++------- .../MarsZapperBase.types.ts} | 0 .../bundle.ts | 10 +- scripts/types/instantiateMsgs.ts | 2 +- 40 files changed, 248 insertions(+), 101 deletions(-) create mode 100644 contracts/v2-zapper/osmosis/src/migrations/mod.rs create mode 100644 contracts/v2-zapper/osmosis/src/migrations/v2_0_0.rs create mode 100644 contracts/v2-zapper/osmosis/tests/test_migration.rs rename schemas/{mars-v2-zapper-base/mars-v2-zapper-base.json => mars-zapper-base/mars-zapper-base.json} (99%) rename scripts/types/generated/{mars-v2-zapper-base/MarsV2ZapperBase.client.ts => mars-zapper-base/MarsZapperBase.client.ts} (92%) rename scripts/types/generated/{mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts => mars-zapper-base/MarsZapperBase.message-composer.ts} (95%) rename scripts/types/generated/{mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts => mars-zapper-base/MarsZapperBase.react-query.ts} (57%) rename scripts/types/generated/{mars-v2-zapper-base/MarsV2ZapperBase.types.ts => mars-zapper-base/MarsZapperBase.types.ts} (100%) rename scripts/types/generated/{mars-v2-zapper-base => mars-zapper-base}/bundle.ts (50%) diff --git a/Cargo.lock b/Cargo.lock index 6be88e631..3e1bc4b0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1529,7 +1529,7 @@ dependencies = [ "mars-rover-health", "mars-rover-health-types", "mars-swapper-mock", - "mars-v2-zapper-mock", + "mars-zapper-mock", "test-case", ] @@ -1749,7 +1749,7 @@ dependencies = [ "mars-params", "mars-red-bank-types 2.0.0", "mars-rover-health-types", - "mars-v2-zapper-base", + "mars-zapper-base", "schemars", "serde", "thiserror", @@ -1860,20 +1860,21 @@ dependencies = [ ] [[package]] -name = "mars-v2-zapper-base" +name = "mars-zapper-base" version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-dex", "cw-utils 1.0.1", + "cw2 1.1.0", "schemars", "serde", "thiserror", ] [[package]] -name = "mars-v2-zapper-mock" +name = "mars-zapper-mock" version = "2.0.0" dependencies = [ "cosmwasm-schema", @@ -1882,19 +1883,19 @@ dependencies = [ "cw-utils 1.0.1", "mars-red-bank-types 2.0.0", "mars-rover 2.0.0", - "mars-v2-zapper-base", + "mars-zapper-base", "thiserror", ] [[package]] -name = "mars-v2-zapper-osmosis" +name = "mars-zapper-osmosis" version = "2.0.0" dependencies = [ "cosmwasm-std", "cw-dex", "cw-utils 1.0.1", "cw2 1.1.0", - "mars-v2-zapper-base", + "mars-zapper-base", "osmosis-std", "osmosis-test-tube", ] diff --git a/Cargo.toml b/Cargo.toml index bd8b73b82..6b26ee220 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ mars-account-nft = { version = "2.0.0", path = "./contracts/account-nft", f mars-params = { git = "https://github.com/mars-protocol/red-bank", rev = "afaf815", features = ["library"] } mars-rover-health = { version = "2.0.0", path = "./contracts/health", features = ["library"] } mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base", features = ["library"] } -mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base", features = ["library"] } +mars-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base", features = ["library"] } # mocks mars-mock-credit-manager = { version = "2.0.0", path = "./contracts/mock-credit-manager", features = ["library"] } @@ -93,7 +93,7 @@ mars-mock-red-bank = { version = "2.0.0", path = "./contracts/mock-red-ban mars-mock-vault = { version = "2.0.0", path = "./contracts/mock-vault", features = ["library"] } mars-mock-rover-health = { version = "2.0.0", path = "./contracts/mock-health", features = ["library"] } mars-swapper-mock = { version = "2.0.0", path = "./contracts/swapper/mock", features = ["library"] } -mars-v2-zapper-mock = { version = "2.0.0", path = "./contracts/v2-zapper/mock", features = ["library"] } +mars-zapper-mock = { version = "2.0.0", path = "./contracts/v2-zapper/mock", features = ["library"] } [profile.release] codegen-units = 1 diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 0baec6e11..0da39be41 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -48,5 +48,5 @@ mars-mock-red-bank = { workspace = true } mars-mock-vault = { workspace = true } mars-rover-health = { workspace = true } mars-swapper-mock = { workspace = true } -mars-v2-zapper-mock = { workspace = true } +mars-zapper-mock = { workspace = true } test-case = { workspace = true } diff --git a/contracts/credit-manager/src/migrations/v2_0_0.rs b/contracts/credit-manager/src/migrations/v2_0_0.rs index 82880a9f6..a0e34448a 100644 --- a/contracts/credit-manager/src/migrations/v2_0_0.rs +++ b/contracts/credit-manager/src/migrations/v2_0_0.rs @@ -1,11 +1,11 @@ use cosmwasm_std::{DepsMut, Env, Response}; use cw2::{assert_contract_version, set_contract_version}; use mars_owner::OwnerInit; -use mars_rover::{error::ContractResult, msg::migrate::V2Updates}; +use mars_rover::{adapters::red_bank::RedBank, error::ContractResult, msg::migrate::V2Updates}; use crate::{ contract::{CONTRACT_NAME, CONTRACT_VERSION}, - state::{HEALTH_CONTRACT, INCENTIVES, MAX_SLIPPAGE, OWNER, PARAMS, SWAPPER}, + state::{HEALTH_CONTRACT, INCENTIVES, MAX_SLIPPAGE, OWNER, PARAMS, RED_BANK, SWAPPER}, utils::assert_max_slippage, }; @@ -19,6 +19,7 @@ pub mod v1_state { pub const ACCOUNT_NFT: Item = Item::new("account_nft"); pub const OWNER: Item = Item::new("owner"); + pub const RED_BANK: Item = Item::new("red_bank"); #[cw_serde] pub enum OwnerState { @@ -42,7 +43,8 @@ pub fn migrate(deps: DepsMut, env: Env, updates: V2Updates) -> ContractResult ContractResult Box> { pub fn mock_v2_zapper_contract() -> Box> { let contract = ContractWrapper::new( - mars_v2_zapper_mock::contract::execute, - mars_v2_zapper_mock::contract::instantiate, - mars_v2_zapper_mock::contract::query, + mars_zapper_mock::contract::execute, + mars_zapper_mock::contract::instantiate, + mars_zapper_mock::contract::query, ); Box::new(contract) } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 1fee50c03..71fd0b988 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -74,7 +74,7 @@ use mars_rover_health_types::{ AccountKind, ExecuteMsg::UpdateConfig, HealthValuesResponse, InstantiateMsg as HealthInstantiateMsg, QueryMsg::HealthValues, }; -use mars_v2_zapper_mock::msg::{InstantiateMsg as ZapperInstantiateMsg, LpConfig}; +use mars_zapper_mock::msg::{InstantiateMsg as ZapperInstantiateMsg, LpConfig}; use crate::helpers::{ lp_token_info, mock_account_nft_contract, mock_address_provider_contract, mock_health_contract, diff --git a/contracts/credit-manager/tests/test_migration_v2.rs b/contracts/credit-manager/tests/test_migration_v2.rs index 942fdc50b..d409945bd 100644 --- a/contracts/credit-manager/tests/test_migration_v2.rs +++ b/contracts/credit-manager/tests/test_migration_v2.rs @@ -7,8 +7,8 @@ use mars_credit_manager::{ contract::migrate, migrations::v2_0_0::{v1_state, v1_state::OwnerSetNoneProposed}, state::{ - ACCOUNT_NFT, HEALTH_CONTRACT, INCENTIVES, MAX_SLIPPAGE, OWNER, PARAMS, REWARDS_COLLECTOR, - SWAPPER, + ACCOUNT_NFT, HEALTH_CONTRACT, INCENTIVES, MAX_SLIPPAGE, OWNER, PARAMS, RED_BANK, + REWARDS_COLLECTOR, SWAPPER, }, }; use mars_rover::{ @@ -96,6 +96,9 @@ fn successful_migration() { let old_account_nft = "account_nft_addr_123"; v1_state::ACCOUNT_NFT.save(deps.as_mut().storage, &Addr::unchecked(old_account_nft)).unwrap(); + let old_red_bank = "red-bank-addr-456"; + v1_state::RED_BANK.save(deps.as_mut().storage, &Addr::unchecked(old_red_bank)).unwrap(); + let health_contract = "health_addr_123".to_string(); let params = "params_addr_456".to_string(); let incentives = "incentives_addr_789".to_string(); @@ -143,4 +146,7 @@ fn successful_migration() { let set_acc_nft = ACCOUNT_NFT.load(deps.as_ref().storage).unwrap(); assert_eq!(old_account_nft, set_acc_nft.address().to_string()); + + let set_red_bank = RED_BANK.load(deps.as_ref().storage).unwrap(); + assert_eq!(old_red_bank, set_red_bank.addr.as_str()); } diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs index 04a5d6b60..903d7d5cf 100644 --- a/contracts/credit-manager/tests/test_zap_provide.rs +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -8,7 +8,7 @@ use mars_rover::{ ActionAmount, ActionCoin, }, }; -use mars_v2_zapper_mock::{contract::STARTING_LP_POOL_TOKENS, error::ContractError}; +use mars_zapper_mock::{contract::STARTING_LP_POOL_TOKENS, error::ContractError}; use crate::helpers::{ assert_err, get_coin, lp_token_info, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index a0228c6d9..9247463ee 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -6,7 +6,7 @@ use mars_rover::{ ActionAmount, ActionCoin, }, }; -use mars_v2_zapper_mock::contract::STARTING_LP_POOL_TOKENS; +use mars_zapper_mock::contract::STARTING_LP_POOL_TOKENS; use crate::helpers::{ assert_err, get_coin, lp_token_info, uatom_info, uosmo_info, AccountToFund, MockEnv, diff --git a/contracts/v2-zapper/base/Cargo.toml b/contracts/v2-zapper/base/Cargo.toml index 7e8a1fc1e..b33069df3 100644 --- a/contracts/v2-zapper/base/Cargo.toml +++ b/contracts/v2-zapper/base/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mars-v2-zapper-base" +name = "mars-zapper-base" version = { workspace = true } authors = { workspace = true } license = { workspace = true } @@ -21,6 +21,7 @@ library = [] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw2 = { workspace = true } cw-dex = { workspace = true } cw-utils = { workspace = true } schemars = { workspace = true } diff --git a/contracts/v2-zapper/base/examples/schema.rs b/contracts/v2-zapper/base/examples/schema.rs index 1d5ad9034..b38f8cc09 100644 --- a/contracts/v2-zapper/base/examples/schema.rs +++ b/contracts/v2-zapper/base/examples/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use mars_v2_zapper_base::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_zapper_base::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/v2-zapper/base/src/error.rs b/contracts/v2-zapper/base/src/error.rs index 7913e3d1b..daa60148e 100644 --- a/contracts/v2-zapper/base/src/error.rs +++ b/contracts/v2-zapper/base/src/error.rs @@ -3,7 +3,7 @@ use cw_dex::CwDexError; use cw_utils::PaymentError; use thiserror::Error; -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] Std(#[from] StdError), @@ -19,4 +19,7 @@ pub enum ContractError { #[error("Unauthorized")] Unauthorized {}, + + #[error("{0}")] + Version(#[from] cw2::VersionError), } diff --git a/contracts/v2-zapper/mock/Cargo.toml b/contracts/v2-zapper/mock/Cargo.toml index 1ebd5e796..a57772faa 100644 --- a/contracts/v2-zapper/mock/Cargo.toml +++ b/contracts/v2-zapper/mock/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mars-v2-zapper-mock" +name = "mars-zapper-mock" version = { workspace = true } authors = { workspace = true } license = { workspace = true } @@ -25,5 +25,5 @@ cw-storage-plus = { workspace = true } cw-utils = { workspace = true } mars-red-bank-types = { workspace = true } mars-rover = { workspace = true } -mars-v2-zapper-base = { workspace = true } +mars-zapper-base = { workspace = true } thiserror = { workspace = true } diff --git a/contracts/v2-zapper/mock/examples/schema.rs b/contracts/v2-zapper/mock/examples/schema.rs index 1d5ad9034..b38f8cc09 100644 --- a/contracts/v2-zapper/mock/examples/schema.rs +++ b/contracts/v2-zapper/mock/examples/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use mars_v2_zapper_base::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_zapper_base::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/v2-zapper/mock/src/contract.rs b/contracts/v2-zapper/mock/src/contract.rs index 860ccdf9f..38f98d8c9 100644 --- a/contracts/v2-zapper/mock/src/contract.rs +++ b/contracts/v2-zapper/mock/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; -use mars_v2_zapper_base::{ExecuteMsg, QueryMsg}; +use mars_zapper_base::{ExecuteMsg, QueryMsg}; use crate::{ error::ContractResult, diff --git a/contracts/v2-zapper/osmosis/Cargo.toml b/contracts/v2-zapper/osmosis/Cargo.toml index e7c8b5ad6..5c9dcefc2 100644 --- a/contracts/v2-zapper/osmosis/Cargo.toml +++ b/contracts/v2-zapper/osmosis/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mars-v2-zapper-osmosis" +name = "mars-zapper-osmosis" version = { workspace = true } authors = { workspace = true } license = { workspace = true } @@ -19,10 +19,10 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -cw-dex = { workspace = true } -mars-v2-zapper-base = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw-dex = { workspace = true } +mars-zapper-base = { workspace = true } [dev-dependencies] cw-utils = { workspace = true } diff --git a/contracts/v2-zapper/osmosis/src/contract.rs b/contracts/v2-zapper/osmosis/src/contract.rs index 05e9cf282..49146bf49 100644 --- a/contracts/v2-zapper/osmosis/src/contract.rs +++ b/contracts/v2-zapper/osmosis/src/contract.rs @@ -1,14 +1,16 @@ -use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cosmwasm_std::{ + entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +}; use cw2::set_contract_version; -use mars_v2_zapper_base::{ContractError, ExecuteMsg, InstantiateMsg, QueryMsg, ZapperBase}; +use mars_zapper_base::{ContractError, ExecuteMsg, InstantiateMsg, QueryMsg, ZapperBase}; -use crate::lp_pool::OsmosisLpPool; +use crate::{lp_pool::OsmosisLpPool, migrations}; /// The Osmosis zapper contract inherits logic from the base zapper contract pub type OsmosisZapper = ZapperBase; -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( @@ -35,3 +37,8 @@ pub fn execute( pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { OsmosisZapper::default().query(deps, env, msg) } + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { + migrations::v2_0_0::migrate(deps) +} diff --git a/contracts/v2-zapper/osmosis/src/lib.rs b/contracts/v2-zapper/osmosis/src/lib.rs index d2b15554e..e6ab3aff8 100644 --- a/contracts/v2-zapper/osmosis/src/lib.rs +++ b/contracts/v2-zapper/osmosis/src/lib.rs @@ -1,2 +1,3 @@ pub mod contract; pub mod lp_pool; +pub mod migrations; diff --git a/contracts/v2-zapper/osmosis/src/lp_pool.rs b/contracts/v2-zapper/osmosis/src/lp_pool.rs index 825e6b141..5dabddc33 100644 --- a/contracts/v2-zapper/osmosis/src/lp_pool.rs +++ b/contracts/v2-zapper/osmosis/src/lp_pool.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use cosmwasm_std::Deps; use cw_dex::{osmosis::OsmosisPool, traits::Pool, CwDexError}; -use mars_v2_zapper_base::LpPool; +use mars_zapper_base::LpPool; pub struct OsmosisLpPool {} diff --git a/contracts/v2-zapper/osmosis/src/migrations/mod.rs b/contracts/v2-zapper/osmosis/src/migrations/mod.rs new file mode 100644 index 000000000..7592b6f12 --- /dev/null +++ b/contracts/v2-zapper/osmosis/src/migrations/mod.rs @@ -0,0 +1 @@ +pub mod v2_0_0; diff --git a/contracts/v2-zapper/osmosis/src/migrations/v2_0_0.rs b/contracts/v2-zapper/osmosis/src/migrations/v2_0_0.rs new file mode 100644 index 000000000..63119bc20 --- /dev/null +++ b/contracts/v2-zapper/osmosis/src/migrations/v2_0_0.rs @@ -0,0 +1,23 @@ +use cosmwasm_std::{DepsMut, Response}; +use cw2::set_contract_version; +use mars_zapper_base::ContractError; + +use crate::contract::{CONTRACT_NAME, CONTRACT_VERSION}; + +const FROM_VERSION: &str = "1.0.0"; + +pub fn migrate(deps: DepsMut) -> Result { + // make sure we're migrating the correct contract and from the correct version + cw2::assert_contract_version( + deps.as_ref().storage, + &format!("crates.io:{CONTRACT_NAME}"), + FROM_VERSION, + )?; + + set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; + + Ok(Response::new() + .add_attribute("action", "migrate") + .add_attribute("from_version", FROM_VERSION) + .add_attribute("to_version", CONTRACT_VERSION)) +} diff --git a/contracts/v2-zapper/osmosis/tests/helpers/utils.rs b/contracts/v2-zapper/osmosis/tests/helpers/utils.rs index b1701f78d..a7083148e 100644 --- a/contracts/v2-zapper/osmosis/tests/helpers/utils.rs +++ b/contracts/v2-zapper/osmosis/tests/helpers/utils.rs @@ -1,6 +1,6 @@ use std::{fmt::Display, str::FromStr}; -use mars_v2_zapper_base::InstantiateMsg; +use mars_zapper_base::InstantiateMsg; use osmosis_std::types::cosmos::bank::v1beta1::QueryBalanceRequest; use osmosis_test_tube::{Bank, OsmosisTestApp, RunnerError, SigningAccount, Wasm}; diff --git a/contracts/v2-zapper/osmosis/tests/test_callback.rs b/contracts/v2-zapper/osmosis/tests/test_callback.rs index e2ffb13c0..bbb5a57f6 100644 --- a/contracts/v2-zapper/osmosis/tests/test_callback.rs +++ b/contracts/v2-zapper/osmosis/tests/test_callback.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{coin, Addr, Coin}; -use mars_v2_zapper_base::{CallbackMsg, ContractError, ExecuteMsg}; +use mars_zapper_base::{CallbackMsg, ContractError, ExecuteMsg}; use osmosis_test_tube::{Account, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract}; diff --git a/contracts/v2-zapper/osmosis/tests/test_migration.rs b/contracts/v2-zapper/osmosis/tests/test_migration.rs new file mode 100644 index 000000000..bd094d251 --- /dev/null +++ b/contracts/v2-zapper/osmosis/tests/test_migration.rs @@ -0,0 +1,97 @@ +use cosmwasm_std::{ + attr, + testing::{mock_dependencies, mock_env}, + Empty, Event, +}; +use cw2::{get_contract_version, set_contract_version, ContractVersion, VersionError}; +use mars_zapper_base::ContractError; +use mars_zapper_osmosis::contract::migrate; + +pub mod helpers; + +#[test] +fn invalid_contract_name() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + let old_contract_version = ContractVersion { + contract: "WRONG_CONTRACT_NAME".to_string(), + version: "1.0.0".to_string(), + }; + + set_contract_version( + deps.as_mut().storage, + old_contract_version.contract.clone(), + old_contract_version.version, + ) + .unwrap(); + + let err = migrate(deps.as_mut(), env, Empty {}).unwrap_err(); + assert_eq!( + ContractError::Version(VersionError::WrongContract { + expected: "crates.io:mars-zapper-osmosis".to_string(), + found: "WRONG_CONTRACT_NAME".to_string() + }), + err + ); +} + +#[test] +fn invalid_contract_version() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + let old_contract_version = ContractVersion { + contract: "crates.io:mars-zapper-osmosis".to_string(), + version: "4.4.5".to_string(), + }; + + set_contract_version( + deps.as_mut().storage, + old_contract_version.contract.clone(), + old_contract_version.version, + ) + .unwrap(); + + let err = migrate(deps.as_mut(), env, Empty {}).unwrap_err(); + assert_eq!( + ContractError::Version(VersionError::WrongVersion { + expected: "1.0.0".to_string(), + found: "4.4.5".to_string() + }), + err + ); +} + +#[test] +fn proper_migration() { + let mut deps = mock_dependencies(); + + let old_contract_version = ContractVersion { + contract: "crates.io:mars-zapper-osmosis".to_string(), + version: "1.0.0".to_string(), + }; + + set_contract_version( + deps.as_mut().storage, + old_contract_version.contract, + old_contract_version.version, + ) + .unwrap(); + + let res = migrate(deps.as_mut(), mock_env(), Empty {}).unwrap(); + + let new_contract_version = ContractVersion { + contract: "crates.io:mars-zapper-osmosis".to_string(), + version: "2.0.0".to_string(), + }; + assert_eq!(get_contract_version(deps.as_ref().storage).unwrap(), new_contract_version); + + assert_eq!(res.messages, vec![]); + assert_eq!(res.events, vec![] as Vec); + assert!(res.data.is_none()); + assert_eq!( + res.attributes, + vec![attr("action", "migrate"), attr("from_version", "1.0.0"), attr("to_version", "2.0.0")] + ); +} diff --git a/contracts/v2-zapper/osmosis/tests/test_provide_liquidity.rs b/contracts/v2-zapper/osmosis/tests/test_provide_liquidity.rs index 3b1b38b4c..f5e52a039 100644 --- a/contracts/v2-zapper/osmosis/tests/test_provide_liquidity.rs +++ b/contracts/v2-zapper/osmosis/tests/test_provide_liquidity.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; -use mars_v2_zapper_base::{ExecuteMsg, QueryMsg}; +use mars_zapper_base::{ExecuteMsg, QueryMsg}; use osmosis_test_tube::{Account, Bank, FeeSetting, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract, query_balance}; diff --git a/contracts/v2-zapper/osmosis/tests/test_queries.rs b/contracts/v2-zapper/osmosis/tests/test_queries.rs index e6b164aca..17167d2f2 100644 --- a/contracts/v2-zapper/osmosis/tests/test_queries.rs +++ b/contracts/v2-zapper/osmosis/tests/test_queries.rs @@ -2,7 +2,7 @@ use std::{ops::Div, str::FromStr}; use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; -use mars_v2_zapper_base::QueryMsg; +use mars_zapper_base::QueryMsg; use osmosis_test_tube::{Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract}; diff --git a/contracts/v2-zapper/osmosis/tests/test_withdraw_liquidity.rs b/contracts/v2-zapper/osmosis/tests/test_withdraw_liquidity.rs index 3ae4c996e..69b98e45c 100644 --- a/contracts/v2-zapper/osmosis/tests/test_withdraw_liquidity.rs +++ b/contracts/v2-zapper/osmosis/tests/test_withdraw_liquidity.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; use cw_utils::PaymentError; -use mars_v2_zapper_base::{ContractError, ExecuteMsg, QueryMsg}; +use mars_zapper_base::{ContractError, ExecuteMsg, QueryMsg}; use osmosis_test_tube::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract, query_balance}; diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 878d727fb..ec1fa251b 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -30,7 +30,7 @@ mars-account-nft-types = { workspace = true } mars-liquidation = { workspace = true } mars-rover-health-types = { workspace = true } mars-red-bank-types = { workspace = true } -mars-v2-zapper-base = { workspace = true } +mars-zapper-base = { workspace = true } mars-owner = { workspace = true } mars-params = { workspace = true } schemars = { workspace = true } diff --git a/packages/rover/src/adapters/zapper.rs b/packages/rover/src/adapters/zapper.rs index 494a452cf..b03dda135 100644 --- a/packages/rover/src/adapters/zapper.rs +++ b/packages/rover/src/adapters/zapper.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ to_binary, Addr, Api, Coin, CosmosMsg, QuerierWrapper, StdResult, Uint128, WasmMsg, }; -use mars_v2_zapper_base::{ExecuteMsg, QueryMsg}; +use mars_zapper_base::{ExecuteMsg, QueryMsg}; #[cw_serde] pub struct ZapperBase(T); diff --git a/schema.Makefile.toml b/schema.Makefile.toml index 75547d6a9..e1eae12f2 100644 --- a/schema.Makefile.toml +++ b/schema.Makefile.toml @@ -14,7 +14,7 @@ fn main() -> std::io::Result<()> { "mars-credit-manager", "mars-account-nft", "mars-swapper-base", - "mars-v2-zapper-base", + "mars-zapper-base", "mars-mock-red-bank", "mars-mock-vault", "mars-mock-oracle", diff --git a/schemas/mars-v2-zapper-base/mars-v2-zapper-base.json b/schemas/mars-zapper-base/mars-zapper-base.json similarity index 99% rename from schemas/mars-v2-zapper-base/mars-v2-zapper-base.json rename to schemas/mars-zapper-base/mars-zapper-base.json index be0530a7e..ebc4b6506 100644 --- a/schemas/mars-v2-zapper-base/mars-v2-zapper-base.json +++ b/schemas/mars-zapper-base/mars-zapper-base.json @@ -1,5 +1,5 @@ { - "contract_name": "mars-v2-zapper-base", + "contract_name": "mars-zapper-base", "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 024d9be9a..f984d2d99 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -7,7 +7,7 @@ import { InstantiateMsgs } from '../../types/instantiateMsgs' import { InstantiateMsg as NftInstantiateMsg } from '../../types/generated/mars-account-nft/MarsAccountNft.types' import { InstantiateMsg as VaultInstantiateMsg } from '../../types/generated/mars-mock-vault/MarsMockVault.types' import { InstantiateMsg as HealthInstantiateMsg } from '../../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' -import { InstantiateMsg as ZapperInstantiateMsg } from '../../types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types' +import { InstantiateMsg as ZapperInstantiateMsg } from '../../types/generated/mars-zapper-base/MarsZapperBase.types' import { ExecuteMsg as CreditManagerExecute, InstantiateMsg as RoverInstantiateMsg, diff --git a/scripts/deploy/osmosis/devnet.ts b/scripts/deploy/osmosis/devnet.ts index 1dd0a170a..2b5c77a26 100644 --- a/scripts/deploy/osmosis/devnet.ts +++ b/scripts/deploy/osmosis/devnet.ts @@ -41,7 +41,7 @@ export const osmosisDevnetConfig: DeploymentConfig = { swapper: { addr: 'osmo1xmhhdxgk9e83n4kmtlluzx38mya8q9r4hku5nys8cr7jg7sgpx5s8zkkg2' }, runTests: false, vaults: [], - zapperContractName: 'mars_v2_zapper_osmosis', + zapperContractName: 'mars_zapper_osmosis', } void (async function () { diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index 29f605b7e..d4bf2dac2 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -56,7 +56,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { params: { addr: 'osmo1h334tvddn82m4apm08rm9k6kt32ws7vy0c4n30ngrvu6h6yxh8eq9l9jfh' }, // Latest from: https://api.apollo.farm/api/graph?query=query+MyQuery+%7B%0A++vaults%28network%3A+osmo_test_5%29+%7B%0A++++label%0A++++contract_address%0A++%7D%0A%7D vaults: [aUSDC_OSMO_Config(ausdcOsmoVault), ATOM_OSMO_Config(atomOsmoVault)], - zapperContractName: 'mars_v2_zapper_osmosis', + zapperContractName: 'mars_zapper_osmosis', runTests: true, testActions: { vault: { diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts b/scripts/types/generated/mars-zapper-base/MarsZapperBase.client.ts similarity index 92% rename from scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts rename to scripts/types/generated/mars-zapper-base/MarsZapperBase.client.ts index 27526a515..78827ed69 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts +++ b/scripts/types/generated/mars-zapper-base/MarsZapperBase.client.ts @@ -16,8 +16,8 @@ import { Coin, QueryMsg, ArrayOfCoin, -} from './MarsV2ZapperBase.types' -export interface MarsV2ZapperBaseReadOnlyInterface { +} from './MarsZapperBase.types' +export interface MarsZapperBaseReadOnlyInterface { contractAddress: string estimateProvideLiquidity: ({ coinsIn, @@ -28,7 +28,7 @@ export interface MarsV2ZapperBaseReadOnlyInterface { }) => Promise estimateWithdrawLiquidity: ({ coinIn }: { coinIn: Coin }) => Promise } -export class MarsV2ZapperBaseQueryClient implements MarsV2ZapperBaseReadOnlyInterface { +export class MarsZapperBaseQueryClient implements MarsZapperBaseReadOnlyInterface { client: CosmWasmClient contractAddress: string @@ -61,7 +61,7 @@ export class MarsV2ZapperBaseQueryClient implements MarsV2ZapperBaseReadOnlyInte }) } } -export interface MarsV2ZapperBaseInterface extends MarsV2ZapperBaseReadOnlyInterface { +export interface MarsZapperBaseInterface extends MarsZapperBaseReadOnlyInterface { contractAddress: string sender: string provideLiquidity: ( @@ -97,9 +97,9 @@ export interface MarsV2ZapperBaseInterface extends MarsV2ZapperBaseReadOnlyInter _funds?: Coin[], ) => Promise } -export class MarsV2ZapperBaseClient - extends MarsV2ZapperBaseQueryClient - implements MarsV2ZapperBaseInterface +export class MarsZapperBaseClient + extends MarsZapperBaseQueryClient + implements MarsZapperBaseInterface { client: SigningCosmWasmClient sender: string diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts b/scripts/types/generated/mars-zapper-base/MarsZapperBase.message-composer.ts similarity index 95% rename from scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts rename to scripts/types/generated/mars-zapper-base/MarsZapperBase.message-composer.ts index d9e3e4049..558d72b80 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts +++ b/scripts/types/generated/mars-zapper-base/MarsZapperBase.message-composer.ts @@ -17,8 +17,8 @@ import { Coin, QueryMsg, ArrayOfCoin, -} from './MarsV2ZapperBase.types' -export interface MarsV2ZapperBaseMessage { +} from './MarsZapperBase.types' +export interface MarsZapperBaseMessage { contractAddress: string sender: string provideLiquidity: ( @@ -45,7 +45,7 @@ export interface MarsV2ZapperBaseMessage { ) => MsgExecuteContractEncodeObject callback: (callbackMsg: CallbackMsg, _funds?: Coin[]) => MsgExecuteContractEncodeObject } -export class MarsV2ZapperBaseMessageComposer implements MarsV2ZapperBaseMessage { +export class MarsZapperBaseMessageComposer implements MarsZapperBaseMessage { sender: string contractAddress: string diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts b/scripts/types/generated/mars-zapper-base/MarsZapperBase.react-query.ts similarity index 57% rename from scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts rename to scripts/types/generated/mars-zapper-base/MarsZapperBase.react-query.ts index 4a696cb73..fedd33057 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts +++ b/scripts/types/generated/mars-zapper-base/MarsZapperBase.react-query.ts @@ -17,20 +17,20 @@ import { Coin, QueryMsg, ArrayOfCoin, -} from './MarsV2ZapperBase.types' -import { MarsV2ZapperBaseQueryClient, MarsV2ZapperBaseClient } from './MarsV2ZapperBase.client' -export const marsV2ZapperBaseQueryKeys = { +} from './MarsZapperBase.types' +import { MarsZapperBaseQueryClient, MarsZapperBaseClient } from './MarsZapperBase.client' +export const marsZapperBaseQueryKeys = { contract: [ { - contract: 'marsV2ZapperBase', + contract: 'marsZapperBase', }, ] as const, address: (contractAddress: string | undefined) => - [{ ...marsV2ZapperBaseQueryKeys.contract[0], address: contractAddress }] as const, + [{ ...marsZapperBaseQueryKeys.contract[0], address: contractAddress }] as const, estimateProvideLiquidity: (contractAddress: string | undefined, args?: Record) => [ { - ...marsV2ZapperBaseQueryKeys.address(contractAddress)[0], + ...marsZapperBaseQueryKeys.address(contractAddress)[0], method: 'estimate_provide_liquidity', args, }, @@ -41,14 +41,14 @@ export const marsV2ZapperBaseQueryKeys = { ) => [ { - ...marsV2ZapperBaseQueryKeys.address(contractAddress)[0], + ...marsZapperBaseQueryKeys.address(contractAddress)[0], method: 'estimate_withdraw_liquidity', args, }, ] as const, } -export interface MarsV2ZapperBaseReactQuery { - client: MarsV2ZapperBaseQueryClient | undefined +export interface MarsZapperBaseReactQuery { + client: MarsZapperBaseQueryClient | undefined options?: Omit< UseQueryOptions, "'queryKey' | 'queryFn' | 'initialData'" @@ -56,19 +56,19 @@ export interface MarsV2ZapperBaseReactQuery { initialData?: undefined } } -export interface MarsV2ZapperBaseEstimateWithdrawLiquidityQuery - extends MarsV2ZapperBaseReactQuery { +export interface MarsZapperBaseEstimateWithdrawLiquidityQuery + extends MarsZapperBaseReactQuery { args: { coinIn: Coin } } -export function useMarsV2ZapperBaseEstimateWithdrawLiquidityQuery({ +export function useMarsZapperBaseEstimateWithdrawLiquidityQuery({ client, args, options, -}: MarsV2ZapperBaseEstimateWithdrawLiquidityQuery) { +}: MarsZapperBaseEstimateWithdrawLiquidityQuery) { return useQuery( - marsV2ZapperBaseQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), + marsZapperBaseQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), () => client ? client.estimateWithdrawLiquidity({ @@ -78,20 +78,20 @@ export function useMarsV2ZapperBaseEstimateWithdrawLiquidityQuery - extends MarsV2ZapperBaseReactQuery { +export interface MarsZapperBaseEstimateProvideLiquidityQuery + extends MarsZapperBaseReactQuery { args: { coinsIn: Coin[] lpTokenOut: string } } -export function useMarsV2ZapperBaseEstimateProvideLiquidityQuery({ +export function useMarsZapperBaseEstimateProvideLiquidityQuery({ client, args, options, -}: MarsV2ZapperBaseEstimateProvideLiquidityQuery) { +}: MarsZapperBaseEstimateProvideLiquidityQuery) { return useQuery( - marsV2ZapperBaseQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), + marsZapperBaseQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), () => client ? client.estimateProvideLiquidity({ @@ -102,8 +102,8 @@ export function useMarsV2ZapperBaseEstimateProvideLiquidityQuery, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.callback(msg, fee, memo, funds), options, ) } -export interface MarsV2ZapperBaseWithdrawLiquidityMutation { - client: MarsV2ZapperBaseClient +export interface MarsZapperBaseWithdrawLiquidityMutation { + client: MarsZapperBaseClient msg: { minimumReceive: Coin[] recipient?: string @@ -134,20 +134,20 @@ export interface MarsV2ZapperBaseWithdrawLiquidityMutation { funds?: Coin[] } } -export function useMarsV2ZapperBaseWithdrawLiquidityMutation( +export function useMarsZapperBaseWithdrawLiquidityMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.withdrawLiquidity(msg, fee, memo, funds), options, ) } -export interface MarsV2ZapperBaseProvideLiquidityMutation { - client: MarsV2ZapperBaseClient +export interface MarsZapperBaseProvideLiquidityMutation { + client: MarsZapperBaseClient msg: { lpTokenOut: string minimumReceive: Uint128 @@ -159,13 +159,13 @@ export interface MarsV2ZapperBaseProvideLiquidityMutation { funds?: Coin[] } } -export function useMarsV2ZapperBaseProvideLiquidityMutation( +export function useMarsZapperBaseProvideLiquidityMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.provideLiquidity(msg, fee, memo, funds), options, diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts b/scripts/types/generated/mars-zapper-base/MarsZapperBase.types.ts similarity index 100% rename from scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts rename to scripts/types/generated/mars-zapper-base/MarsZapperBase.types.ts diff --git a/scripts/types/generated/mars-v2-zapper-base/bundle.ts b/scripts/types/generated/mars-zapper-base/bundle.ts similarity index 50% rename from scripts/types/generated/mars-v2-zapper-base/bundle.ts rename to scripts/types/generated/mars-zapper-base/bundle.ts index bde1a9130..63f0c0b61 100644 --- a/scripts/types/generated/mars-v2-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-zapper-base/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _44 from './MarsV2ZapperBase.types' -import * as _45 from './MarsV2ZapperBase.client' -import * as _46 from './MarsV2ZapperBase.message-composer' -import * as _47 from './MarsV2ZapperBase.react-query' +import * as _44 from './MarsZapperBase.types' +import * as _45 from './MarsZapperBase.client' +import * as _46 from './MarsZapperBase.message-composer' +import * as _47 from './MarsZapperBase.react-query' export namespace contracts { - export const MarsV2ZapperBase = { ..._44, ..._45, ..._46, ..._47 } + export const MarsZapperBase = { ..._44, ..._45, ..._46, ..._47 } } diff --git a/scripts/types/instantiateMsgs.ts b/scripts/types/instantiateMsgs.ts index bcfbf4501..dc712679c 100644 --- a/scripts/types/instantiateMsgs.ts +++ b/scripts/types/instantiateMsgs.ts @@ -4,7 +4,7 @@ import { InstantiateMsg as VaultInstantiateMsg } from './generated/mars-mock-vau import { InstantiateMsg as OracleInstantiateMsg } from './generated/mars-mock-oracle/MarsMockOracle.types' import { InstantiateMsg as RoverInstantiateMsg } from './generated/mars-credit-manager/MarsCreditManager.types' import { InstantiateMsg as SwapperInstantiateMsg } from './generated/mars-swapper-base/MarsSwapperBase.types' -import { InstantiateMsg as ZapperInstantiateMsg } from './generated/mars-v2-zapper-base/MarsV2ZapperBase.types' +import { InstantiateMsg as ZapperInstantiateMsg } from './generated/mars-zapper-base/MarsZapperBase.types' import { InstantiateMsg as HealthInstantiateMsg } from './generated/mars-rover-health-types/MarsRoverHealthTypes.types' export type InstantiateMsgs = From 7c9d2f296cc6fa63dcb4ee8a1753b4fad7343b04 Mon Sep 17 00:00:00 2001 From: piobab Date: Tue, 26 Sep 2023 14:59:17 +0200 Subject: [PATCH 216/218] Clear empty accounts (#199) * Clear empty accounts. * Update schema. * Add tests. Cleanup. * Improve msg. * Update CM with rc. New deployment. --- contracts/account-nft/src/contract.rs | 9 +- .../account-nft/src/migrations/v2_0_0.rs | 100 +++++++++++- contracts/account-nft/src/state.rs | 9 ++ .../account-nft/tests/helpers/mock_env.rs | 17 +- .../tests/test_burn_empty_accounts.rs | 148 ++++++++++++++++++ packages/account-nft-types/src/msg/execute.rs | 14 ++ packages/account-nft-types/src/msg/mod.rs | 2 +- .../mars-account-nft/mars-account-nft.json | 41 +++++ scripts/deploy/addresses/devnet-devnet.json | 8 +- scripts/deploy/base/deployer.ts | 11 +- scripts/deploy/osmosis/devnet.ts | 15 +- scripts/deploy/osmosis/mainnet.ts | 1 + scripts/deploy/osmosis/testnet-config.ts | 1 + scripts/types/config.ts | 1 + .../mars-account-nft/MarsAccountNft.client.ts | 25 +++ .../MarsAccountNft.message-composer.ts | 18 +++ .../MarsAccountNft.react-query.ts | 21 +++ .../mars-account-nft/MarsAccountNft.types.ts | 8 + 18 files changed, 429 insertions(+), 20 deletions(-) create mode 100644 contracts/account-nft/tests/test_burn_empty_accounts.rs diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index 2846ac0cb..6b30cbd6e 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -6,14 +6,14 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw721_base::Cw721Contract; use mars_account_nft_types::{ - msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + msg::{ExecuteMsg, InstantiateMsg, MigrateV1ToV2, QueryMsg}, nft_config::NftConfig, }; use crate::{ error::ContractError, execute::{burn, mint, update_config}, - migrations, + migrations::{self}, query::{query_config, query_next_id}, state::{CONFIG, NEXT_ID}, }; @@ -71,6 +71,11 @@ pub fn execute( ExecuteMsg::Burn { token_id, } => burn(deps, env, info, token_id), + ExecuteMsg::Migrate(msg) => match msg { + MigrateV1ToV2::BurnEmptyAccounts { + limit, + } => migrations::v2_0_0::burn_empty_accounts(deps, limit), + }, _ => Parent::default().execute(deps, env, info, msg.try_into()?).map_err(Into::into), } } diff --git a/contracts/account-nft/src/migrations/v2_0_0.rs b/contracts/account-nft/src/migrations/v2_0_0.rs index 6705347fa..5d0fdc89e 100644 --- a/contracts/account-nft/src/migrations/v2_0_0.rs +++ b/contracts/account-nft/src/migrations/v2_0_0.rs @@ -1,11 +1,16 @@ -use cosmwasm_std::{DepsMut, Empty, Response}; +use cosmwasm_std::{ + to_binary, DepsMut, Empty, QueryRequest, Response, StdError, Storage, WasmQuery, +}; use cw2::set_contract_version; +use cw721::Cw721Query; use mars_account_nft_types::nft_config::NftConfig; +use mars_red_bank_types::oracle::ActionKind; +use mars_rover_health_types::{AccountKind, HealthValuesResponse, QueryMsg::HealthValues}; use crate::{ - contract::{CONTRACT_NAME, CONTRACT_VERSION}, - error::ContractError, - state::CONFIG, + contract::{Parent, CONTRACT_NAME, CONTRACT_VERSION}, + error::ContractError::{self, HealthContractNotSet}, + state::{BurningMarker, CONFIG, MIGRATION_BURNING_MARKER}, }; const FROM_VERSION: &str = "1.0.0"; @@ -42,3 +47,90 @@ pub fn migrate(deps: DepsMut) -> Result { Ok(cw721_base::upgrades::v0_17::migrate::(deps)?) } + +pub fn burn_empty_accounts( + mut deps: DepsMut, + limit: Option, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let Some(health_contract_addr) = config.health_contract_addr else { + return Err(HealthContractNotSet); + }; + + let burning_marker_opt = MIGRATION_BURNING_MARKER.may_load(deps.storage)?; + let start_after = match burning_marker_opt { + Some(BurningMarker::Finished) => { + return Err(StdError::generic_err( + "Migration completed. All empty accounts already burned.", + ) + .into()) + } + Some(BurningMarker::StartAfter(start_after)) => Some(start_after), + None => None, + }; + + let res = Parent::default().all_tokens(deps.as_ref(), start_after, limit)?; + + let status = if let Some(last_token) = res.tokens.last() { + // Save last token for next iteration + MIGRATION_BURNING_MARKER + .save(deps.storage, &BurningMarker::StartAfter(last_token.clone()))?; + + for token in res.tokens.into_iter() { + burn_empty_account(deps.branch(), health_contract_addr.to_string(), token)?; + } + + "in_progress".to_string() + } else { + // No more tokens. Migration finished + MIGRATION_BURNING_MARKER.save(deps.storage, &BurningMarker::Finished)?; + + "done".to_string() + }; + + Ok(Response::new() + .add_attribute("action", "burn_empty_accounts") + .add_attribute("status", status)) +} + +/// A few checks to ensure accounts are not accidentally deleted: +/// - Cannot burn if debt balance +/// - Cannot burn if collateral balance +fn burn_empty_account( + deps: DepsMut, + health_contract: String, + token_id: String, +) -> Result<(), ContractError> { + let response: HealthValuesResponse = + deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: health_contract, + msg: to_binary(&HealthValues { + account_id: token_id.clone(), + kind: AccountKind::Default, // all current accounts are default + action: ActionKind::Default, + })?, + }))?; + + // Burn only empty accounts + if response.total_debt_value.is_zero() && response.total_collateral_value.is_zero() { + burn_without_owner_check(deps.storage, token_id)?; + } + + Ok(()) +} + +fn burn_without_owner_check( + storage: &mut dyn Storage, + token_id: String, +) -> Result<(), ContractError> { + let parent = Parent::default(); + + // Original function has additional check for token owner: + // let token = parnet.tokens.load(deps.storage, &token_id)?; + // parnet.check_can_send(deps.as_ref(), &env, &info, &token)?; + + Parent::default().tokens.remove(storage, &token_id)?; + parent.decrement_tokens(storage)?; + + Ok(()) +} diff --git a/contracts/account-nft/src/state.rs b/contracts/account-nft/src/state.rs index eef9bd501..828cebfed 100644 --- a/contracts/account-nft/src/state.rs +++ b/contracts/account-nft/src/state.rs @@ -1,5 +1,14 @@ +use cosmwasm_schema::cw_serde; use cw_storage_plus::Item; use mars_account_nft_types::nft_config::NftConfig; pub const CONFIG: Item = Item::new("config"); pub const NEXT_ID: Item = Item::new("next_id"); + +/// Helper marker used during burning empty accounts. Used only for v1 -> v2 migration. +#[cw_serde] +pub enum BurningMarker { + StartAfter(String), + Finished, +} +pub const MIGRATION_BURNING_MARKER: Item = Item::new("burning_marker"); diff --git a/contracts/account-nft/tests/helpers/mock_env.rs b/contracts/account-nft/tests/helpers/mock_env.rs index 567dfd42a..631635f37 100644 --- a/contracts/account-nft/tests/helpers/mock_env.rs +++ b/contracts/account-nft/tests/helpers/mock_env.rs @@ -8,7 +8,7 @@ use cw721_base::{ }; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; use mars_account_nft_types::{ - msg::{ExecuteMsg, ExecuteMsg::UpdateConfig, QueryMsg}, + msg::{ExecuteMsg, ExecuteMsg::UpdateConfig, MigrateV1ToV2, QueryMsg}, nft_config::{NftConfigUpdates, UncheckedNftConfig}, }; use mars_mock_credit_manager::msg::ExecuteMsg::SetAccountKindResponse; @@ -152,6 +152,21 @@ impl MockEnv { ) } + pub fn burn_empty_accounts( + &mut self, + sender: &Addr, + limit: Option, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.nft_contract.clone(), + &ExecuteMsg::Migrate(MigrateV1ToV2::BurnEmptyAccounts { + limit, + }), + &[], + ) + } + pub fn propose_new_minter( &mut self, sender: &Addr, diff --git a/contracts/account-nft/tests/test_burn_empty_accounts.rs b/contracts/account-nft/tests/test_burn_empty_accounts.rs new file mode 100644 index 000000000..9d6b408b0 --- /dev/null +++ b/contracts/account-nft/tests/test_burn_empty_accounts.rs @@ -0,0 +1,148 @@ +use cosmwasm_std::{Addr, StdError}; +use mars_account_nft::error::{ContractError, ContractError::HealthContractNotSet}; +use mars_account_nft_types::msg::QueryMsg::{AllTokens, NumTokens, Tokens}; +use mars_rover_health_types::AccountKind; + +use crate::helpers::{generate_health_response, MockEnv}; + +pub mod helpers; + +#[test] +fn burning_empty_accounts_not_allowed_if_no_health_contract_set() { + let mut mock = MockEnv::new().instantiate_with_health_contract(false).build().unwrap(); + let user = Addr::unchecked("user"); + mock.mint(&user).unwrap(); + let res = mock.burn_empty_accounts(&user, None); + let error: ContractError = res.unwrap_err().downcast().unwrap(); + assert_eq!(error, HealthContractNotSet); +} + +#[test] +fn burn_empty_accounts() { + let mut mock = MockEnv::new().build().unwrap(); + + // create few accounts + let user_1 = Addr::unchecked("user_1"); + let user_1_token_id = mock.mint(&user_1).unwrap(); + mock.set_health_response( + &user_1, + &user_1_token_id, + AccountKind::Default, + &generate_health_response(10_000, 0), + ); + let user_2 = Addr::unchecked("user_2"); + let user_2_token_id_1 = mock.mint(&user_2).unwrap(); + mock.set_health_response( + &user_2, + &user_2_token_id_1, + AccountKind::Default, + &generate_health_response(0, 0), + ); + let user_2_token_id_2 = mock.mint(&user_2).unwrap(); + mock.set_health_response( + &user_2, + &user_2_token_id_2, + AccountKind::Default, + &generate_health_response(0, 1), + ); + let user_3 = Addr::unchecked("user_3"); + let user_3_token_id = mock.mint(&user_3).unwrap(); + mock.set_health_response( + &user_3, + &user_3_token_id, + AccountKind::Default, + &generate_health_response(1, 1), + ); + let user_4 = Addr::unchecked("user_4"); + let user_4_token_id = mock.mint(&user_4).unwrap(); + mock.set_health_response( + &user_4, + &user_4_token_id, + AccountKind::Default, + &generate_health_response(0, 0), + ); + let user_5 = Addr::unchecked("user_5"); + let user_5_token_id = mock.mint(&user_5).unwrap(); + mock.set_health_response( + &user_5, + &user_5_token_id, + AccountKind::Default, + &generate_health_response(0, 0), + ); + let user_6 = Addr::unchecked("user_6"); + let user_6_token_id = mock.mint(&user_6).unwrap(); + mock.set_health_response( + &user_6, + &user_6_token_id, + AccountKind::Default, + &generate_health_response(0, 100), + ); + + // check that all accounts are created + let res: cw721::NumTokensResponse = + mock.app.wrap().query_wasm_smart(mock.nft_contract.clone(), &NumTokens {}).unwrap(); + assert_eq!(res.count, 7); + + // check that for user 2 there are 2 tokens + let res: cw721::TokensResponse = mock + .app + .wrap() + .query_wasm_smart( + mock.nft_contract.clone(), + &Tokens { + owner: user_2.to_string(), + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(res.tokens, vec![user_2_token_id_1.clone(), user_2_token_id_2.clone()]); + + // burn empty accounts + let user = Addr::unchecked("random_user"); + mock.burn_empty_accounts(&user, Some(2)).unwrap(); + mock.burn_empty_accounts(&user, Some(2)).unwrap(); + mock.burn_empty_accounts(&user, Some(2)).unwrap(); + mock.burn_empty_accounts(&user, Some(2)).unwrap(); + mock.burn_empty_accounts(&user, Some(2)).unwrap(); // set flag to Finished + let res = mock.burn_empty_accounts(&user, Some(2)); + let error: ContractError = res.unwrap_err().downcast().unwrap(); + assert_eq!( + error, + ContractError::Std(StdError::generic_err( + "Migration completed. All empty accounts already burned." + )) + ); + + // check that only empty accounts are burned + let res: cw721::TokensResponse = mock + .app + .wrap() + .query_wasm_smart( + mock.nft_contract.clone(), + &AllTokens { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!( + res.tokens, + vec![user_1_token_id, user_2_token_id_2.clone(), user_3_token_id, user_6_token_id] + ); + + // check that for user 2 there is only one token, second one should be burned + let res: cw721::TokensResponse = mock + .app + .wrap() + .query_wasm_smart( + mock.nft_contract.clone(), + &Tokens { + owner: user_2.to_string(), + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(res.tokens, vec![user_2_token_id_2]); +} diff --git a/packages/account-nft-types/src/msg/execute.rs b/packages/account-nft-types/src/msg/execute.rs index b0dce3bc3..eba222047 100644 --- a/packages/account-nft-types/src/msg/execute.rs +++ b/packages/account-nft-types/src/msg/execute.rs @@ -26,6 +26,11 @@ pub enum ExecuteMsg { token_id: String, }, + //-------------------------------------------------------------------------------------------------- + // Migrate message to work in batches + //-------------------------------------------------------------------------------------------------- + Migrate(MigrateV1ToV2), + //-------------------------------------------------------------------------------------------------- // Base cw721 messages //-------------------------------------------------------------------------------------------------- @@ -67,6 +72,15 @@ pub enum ExecuteMsg { UpdateOwnership(Action), } +/// Migrate from V1 to V2 +#[cw_serde] +pub enum MigrateV1ToV2 { + /// Burns empty accounts in batches + BurnEmptyAccounts { + limit: Option, + }, +} + impl TryInto> for ExecuteMsg { type Error = StdError; diff --git a/packages/account-nft-types/src/msg/mod.rs b/packages/account-nft-types/src/msg/mod.rs index f82a48d3b..dc5e4ca97 100644 --- a/packages/account-nft-types/src/msg/mod.rs +++ b/packages/account-nft-types/src/msg/mod.rs @@ -2,6 +2,6 @@ mod execute; mod instantiate; mod query; -pub use execute::ExecuteMsg; +pub use execute::{ExecuteMsg, MigrateV1ToV2}; pub use instantiate::InstantiateMsg; pub use query::QueryMsg; diff --git a/schemas/mars-account-nft/mars-account-nft.json b/schemas/mars-account-nft/mars-account-nft.json index ac1b0c109..094802fe9 100644 --- a/schemas/mars-account-nft/mars-account-nft.json +++ b/schemas/mars-account-nft/mars-account-nft.json @@ -126,6 +126,18 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "$ref": "#/definitions/MigrateV1ToV2" + } + }, + "additionalProperties": false + }, { "description": "Transfer is a base message to move a token to another account without triggering actions", "type": "object", @@ -415,6 +427,35 @@ } ] }, + "MigrateV1ToV2": { + "description": "Migrate from V1 to V2", + "oneOf": [ + { + "description": "Burns empty accounts in batches", + "type": "object", + "required": [ + "burn_empty_accounts" + ], + "properties": { + "burn_empty_accounts": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "NftConfigUpdates": { "type": "object", "properties": { diff --git a/scripts/deploy/addresses/devnet-devnet.json b/scripts/deploy/addresses/devnet-devnet.json index 1ce505739..d7b48eb33 100644 --- a/scripts/deploy/addresses/devnet-devnet.json +++ b/scripts/deploy/addresses/devnet-devnet.json @@ -1,6 +1,6 @@ { - "zapper": "osmo1q4kkvuy8wc9fs8sfm7zyeh4k25vssd0l68nrph8s7unvq5jdq67swrepj4", - "healthContract": "osmo1r3gfdwv8xe9drtwaxq6t5ek32huezdeal0zq989ag007yk4cu47qpmu7z6", - "creditManager": "osmo1m83kw2vehyt9urxf79qa9rxk8chgs4464e5h8s37yhnw3pwauuqq7lux8r", - "accountNft": "osmo1pdr8mvj2ky9hzj5pjp026apfmd0pacd3xrzx3mzazy7lulnsdrkq96gzk3" + "zapper": "osmo1yhh8mhthj5jn5c6ty59z3tpsk554qxmlkrkcderw6jls0pcg8zxsdjdj94", + "healthContract": "osmo1kwcx728kugnakyzhqamkvzvjchtz4wal7xmefv3h062agpcs3rcsnze58q", + "creditManager": "osmo12wd0rwuvu7wwujztkww5c7sg4fw4e6t235jyftwy5ydc48uxd24q4s9why", + "accountNft": "osmo1j0m37hqpaeh79cjrdna4sep6yfyu278rrm4qta6s4hjq6fv3njxqsvhcex" } diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index f984d2d99..f9e4f50bc 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -163,13 +163,22 @@ export class Deployer { this.storage.addresses.creditManager!, ) - printBlue('Setting health contract address in nft contract via credit manager contract') + printBlue( + 'Setting health and credit-manager addresses in nft contract via credit manager contract', + ) await hExec.updateNftConfig({ config: { health_contract_addr: this.storage.addresses.healthContract!, credit_manager_contract_addr: this.storage.addresses.creditManager!, }, }) + + printBlue('Setting rewards-collector address in credit manager contract') + await hExec.updateConfig({ + updates: { + rewards_collector: this.config.rewardsCollector.addr, + }, + }) } this.storage.actions.creditManagerContractConfigUpdate = true } diff --git a/scripts/deploy/osmosis/devnet.ts b/scripts/deploy/osmosis/devnet.ts index 2b5c77a26..80e54fd8e 100644 --- a/scripts/deploy/osmosis/devnet.ts +++ b/scripts/deploy/osmosis/devnet.ts @@ -9,7 +9,7 @@ const wbtc = 'ibc/D1542AA8762DB13087D8364F3EA6509FD6F009A34F00426AF9E4F9FA85CBBF const axlUSDC = 'ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858' const eth = 'ibc/EA1D43981D5C9A1C4AAEA9C23BB1D4FA126BA9BC7020A25E0AE4AA841EA25DC5' -const defaultCreditLine = '1000000000000' +const defaultCreditLine = '100000000000000' export const osmosisDevnetConfig: DeploymentConfig = { // multisigAddr: 'osmo14w4x949nwcrqgfe53pxs3k7x53p0gvlrq34l5n', @@ -33,12 +33,13 @@ export const osmosisDevnetConfig: DeploymentConfig = { maxSlippage: '0.2', maxValueForBurn: '10000', // oracle and redbank contract addresses can be found: https://github.com/mars-protocol/red-bank/blob/master/README.md#osmosis-1 - addressProvider: { addr: 'osmo1c59a0jj0e7erenwekxhq3ylq3w3kakhwxtj9ll778n8av8tu5q4qarp2mv' }, - oracle: { addr: 'osmo156elt2tp5455q9a6vfrvnpncxyd33cxm9z2lgguwg6dgws9tedps5tq3rc' }, - redBank: { addr: 'osmo1vxpdcw092n9rngvekve8g324c2yjx56496ck98ag4sdxr4p4zd4q0wr7x6' }, - incentives: { addr: 'osmo1r9w7k774vcxeuvq6ctq0z2j6wkkxpskucgjkqt0uu7u07l03s3js6ukge4' }, - params: { addr: 'osmo1pzszwkyy0x9cu6p2uknwa3wccr79xwmqn9gj66fnjnayr28tzp6qh2n4qg' }, - swapper: { addr: 'osmo1xmhhdxgk9e83n4kmtlluzx38mya8q9r4hku5nys8cr7jg7sgpx5s8zkkg2' }, + addressProvider: { addr: 'osmo1x7udlkawmkz2u5th5x3cjxht2yvjgph7pg8l9rumaa3lak922dgsr3lmhc' }, + oracle: { addr: 'osmo1dh8f3rhg4eruc9w7c9d5e06eupqqrth7v32ladwkyphvnn66muzqxcfe60' }, + redBank: { addr: 'osmo1pvrlpmdv3ee6lgmxd37n29gtdahy4tec7c5nyer9aphvfr526z6sff9zdg' }, + incentives: { addr: 'osmo1aemnaq5x3jkttnd38g7lewh24nh90r9zwh8853qv3tkf47p2hnasaae0e4' }, + params: { addr: 'osmo1dpwu03xc45vpqur6ry69xjhltq4v0snrhaukcp4fvhucx0wypzhs978lnp' }, + swapper: { addr: 'osmo17c4retwuyxjxzv9f2q9r0272s8smktpzhjetssttxxdavarjtujsjqafa2' }, + rewardsCollector: { addr: 'osmo19fppgzdenrxwdg2k3te0a48mfee4npdrctghzrcqltwck7e4y6ts7t8428' }, runTests: false, vaults: [], zapperContractName: 'mars_zapper_osmosis', diff --git a/scripts/deploy/osmosis/mainnet.ts b/scripts/deploy/osmosis/mainnet.ts index ac476891f..2435493c2 100644 --- a/scripts/deploy/osmosis/mainnet.ts +++ b/scripts/deploy/osmosis/mainnet.ts @@ -28,6 +28,7 @@ export const osmosisMainnetConfig: DeploymentConfig = { incentives: { addr: 'osmo1nkahswfr8shg8rlxqwup0vgahp0dk4x8w6tkv3rra8rratnut36sk22vrm' }, params: { addr: 'TBD' }, swapper: { addr: 'TBD' }, + rewardsCollector: { addr: 'osmo1urvqe5mw00ws25yqdd4c4hlh8kdyf567mpcml7cdve9w08z0ydcqvsrgdy' }, runTests: false, vaults: [ { diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index d4bf2dac2..be879868e 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -54,6 +54,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { oracle: { addr: 'osmo1dxu93scjdnx42txdp9d4hm3snffvnzmkp4jpc9sml8xlu3ncgamsl2lx58' }, swapper: { addr: 'osmo1ee9cq8dcknmw43znznx6vuupx5ku0tt505agccgaz5gn48mhe45s3kwwfm' }, params: { addr: 'osmo1h334tvddn82m4apm08rm9k6kt32ws7vy0c4n30ngrvu6h6yxh8eq9l9jfh' }, + rewardsCollector: { addr: 'osmo1h334tvddn82m4apm08rm9k6kt32ws7vy0c4n30ngrvu6h6yxh8eq9l9jfh' }, // Latest from: https://api.apollo.farm/api/graph?query=query+MyQuery+%7B%0A++vaults%28network%3A+osmo_test_5%29+%7B%0A++++label%0A++++contract_address%0A++%7D%0A%7D vaults: [aUSDC_OSMO_Config(ausdcOsmoVault), ATOM_OSMO_Config(atomOsmoVault)], zapperContractName: 'mars_zapper_osmosis', diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 121cfa875..6b8865628 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -26,6 +26,7 @@ export interface DeploymentConfig { incentives: { addr: string } params: { addr: string } swapper: { addr: string } + rewardsCollector: { addr: string } vaults: VaultConfigBaseForString[] creditLineCoins: { denom: string; creditLine: String }[] maxValueForBurn: string diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts index d2c9dce82..d0a8b9406 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts @@ -11,6 +11,7 @@ import { Uint128, InstantiateMsg, ExecuteMsg, + MigrateV1ToV2, Binary, Expiration, Timestamp, @@ -306,6 +307,12 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface memo?: string, _funds?: Coin[], ) => Promise + migrate: ( + migrateV1ToV2: MigrateV1ToV2, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise transferNft: ( { recipient, @@ -403,6 +410,7 @@ export class MarsAccountNftClient this.updateConfig = this.updateConfig.bind(this) this.mint = this.mint.bind(this) this.burn = this.burn.bind(this) + this.migrate = this.migrate.bind(this) this.transferNft = this.transferNft.bind(this) this.sendNft = this.sendNft.bind(this) this.approve = this.approve.bind(this) @@ -481,6 +489,23 @@ export class MarsAccountNftClient _funds, ) } + migrate = async ( + migrateV1ToV2: MigrateV1ToV2, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + migrate: migrateV1ToV2, + }, + fee, + memo, + _funds, + ) + } transferNft = async ( { recipient, diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts index ac85511d1..bd5898915 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts @@ -13,6 +13,7 @@ import { Uint128, InstantiateMsg, ExecuteMsg, + MigrateV1ToV2, Binary, Expiration, Timestamp, @@ -64,6 +65,7 @@ export interface MarsAccountNftMessage { }, _funds?: Coin[], ) => MsgExecuteContractEncodeObject + migrate: (migrateV1ToV2: MigrateV1ToV2, _funds?: Coin[]) => MsgExecuteContractEncodeObject transferNft: ( { recipient, @@ -138,6 +140,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { this.updateConfig = this.updateConfig.bind(this) this.mint = this.mint.bind(this) this.burn = this.burn.bind(this) + this.migrate = this.migrate.bind(this) this.transferNft = this.transferNft.bind(this) this.sendNft = this.sendNft.bind(this) this.approve = this.approve.bind(this) @@ -219,6 +222,21 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }), } } + migrate = (migrateV1ToV2: MigrateV1ToV2, _funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + migrate: migrateV1ToV2, + }), + ), + funds: _funds, + }), + } + } transferNft = ( { recipient, diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts index ceea0e594..0747f0f3c 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts @@ -12,6 +12,7 @@ import { Uint128, InstantiateMsg, ExecuteMsg, + MigrateV1ToV2, Binary, Expiration, Timestamp, @@ -526,6 +527,26 @@ export function useMarsAccountNftTransferNftMutation( options, ) } +export interface MarsAccountNftMigrateMutation { + client: MarsAccountNftClient + msg: MigrateV1ToV2 + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsAccountNftMigrateMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.migrate(msg, fee, memo, funds), + options, + ) +} export interface MarsAccountNftBurnMutation { client: MarsAccountNftClient msg: { diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index fdf3c0d71..2bb28c961 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -30,6 +30,9 @@ export type ExecuteMsg = token_id: string } } + | { + migrate: MigrateV1ToV2 + } | { transfer_nft: { recipient: string @@ -70,6 +73,11 @@ export type ExecuteMsg = | { update_ownership: Action } +export type MigrateV1ToV2 = { + burn_empty_accounts: { + limit?: number | null + } +} export type Binary = string export type Expiration = | { From 1176c732a9f1a6c9bc6d97e9fa98c63b2cfe3307 Mon Sep 17 00:00:00 2001 From: piobab Date: Thu, 5 Oct 2023 13:23:10 +0200 Subject: [PATCH 217/218] Withdraw claimed rewards to user's address. (#200) --- contracts/credit-manager/src/claim_rewards.rs | 50 ++++++------------- .../tests/test_claim_rewards.rs | 35 ++++++------- 2 files changed, 33 insertions(+), 52 deletions(-) diff --git a/contracts/credit-manager/src/claim_rewards.rs b/contracts/credit-manager/src/claim_rewards.rs index 1ba6952c9..63b602803 100644 --- a/contracts/credit-manager/src/claim_rewards.rs +++ b/contracts/credit-manager/src/claim_rewards.rs @@ -4,19 +4,11 @@ use cosmwasm_std::{ }; use mars_rover::{ error::{ContractError, ContractResult}, - msg::{ - execute::{CallbackMsg, ChangeExpected}, - ExecuteMsg, - }, + msg::{execute::CallbackMsg, ExecuteMsg}, traits::Denoms, }; -use mars_rover_health_types::AccountKind; -use crate::{ - state::INCENTIVES, - update_coin_balances::query_balance, - utils::{get_account_kind, update_balances_msgs}, -}; +use crate::{state::INCENTIVES, update_coin_balances::query_balance}; pub fn claim_rewards( deps: DepsMut, @@ -31,34 +23,22 @@ pub fn claim_rewards( return Err(ContractError::NoAmount); } - // For HLS accounts there are special requirements enforced for this account type. - // assert_hls_rules only allows assets with HLS params set in the params contract - // and where the collateral is whitelisted. - // We withdraw all claimed rewards for HLS accounts to the recipient address. - let kind = get_account_kind(deps.storage, account_id)?; - let msgs = match kind { - AccountKind::Default => update_balances_msgs( - &deps.querier, - &env.contract.address, - account_id, - unclaimed_rewards.to_denoms(), - ChangeExpected::Increase, - )?, - AccountKind::HighLeveredStrategy => { - let msg = send_rewards_msg( - &deps.querier, - &env.contract.address, - account_id, - recipient.clone(), - unclaimed_rewards.to_denoms(), - )?; - vec![msg] - } - }; + // Incentive denom may not be listed in params contract. + // When rewards are claimed to the account, they are considered deposits (collateral). + // If the account requires HF check, health contract won't be able to find + // incentive denom params (such as MaxLTV) for HF calculation and the TX will be rejected. + // To address this issue we withdraw all claimed rewards to the recipient address. + let msg = send_rewards_msg( + &deps.querier, + &env.contract.address, + account_id, + recipient.clone(), + unclaimed_rewards.to_denoms(), + )?; Ok(Response::new() .add_message(incentives.claim_rewards_msg(account_id)?) - .add_messages(msgs) + .add_message(msg) .add_attribute("action", "claim_rewards") .add_attribute("account_id", account_id) .add_attribute("recipient", recipient.to_string())) diff --git a/contracts/credit-manager/tests/test_claim_rewards.rs b/contracts/credit-manager/tests/test_claim_rewards.rs index a51dc2977..f8132c04a 100644 --- a/contracts/credit-manager/tests/test_claim_rewards.rs +++ b/contracts/credit-manager/tests/test_claim_rewards.rs @@ -45,13 +45,14 @@ fn claiming_a_single_reward() { // Check account id deposit balance let positions = mock.query_positions(&account_id); - assert_eq!(positions.deposits.len(), 1); - assert_eq!(positions.deposits.first().unwrap().amount, Uint128::new(123)); - assert_eq!(positions.deposits.first().unwrap().denom, coin_info.denom); + assert_eq!(positions.deposits.len(), 0); - // Ensure money is in bank module for credit manager - let cm_balance = mock.query_balance(&mock.rover, &coin_info.denom); - assert_eq!(cm_balance.amount, Uint128::new(123)); + // Ensure money is in user's wallet + let balance = mock.query_balance(&mock.rover, &coin_info.denom); + assert_eq!(balance.amount, Uint128::zero()); + + let balance = mock.query_balance(&user, &coin_info.denom); + assert_eq!(balance.amount, Uint128::new(123)); } #[test] @@ -79,25 +80,25 @@ fn claiming_multiple_rewards() { // Check account id deposit balance let positions = mock.query_positions(&account_id); - assert_eq!(positions.deposits.len(), 3); + assert_eq!(positions.deposits.len(), 0); - let osmo_claimed = get_coin(&osmo_info.denom, &positions.deposits); - assert_eq!(osmo_claimed.amount, Uint128::new(123)); + // Ensure money is in user's wallet + let osmo_balance = mock.query_balance(&mock.rover, &osmo_info.denom); + assert_eq!(osmo_balance.amount, Uint128::zero()); - let atom_claimed = get_coin(&atom_info.denom, &positions.deposits); - assert_eq!(atom_claimed.amount, Uint128::new(555)); + let atom_balance = mock.query_balance(&mock.rover, &atom_info.denom); + assert_eq!(atom_balance.amount, Uint128::zero()); - let jake_claimed = get_coin(&jake_info.denom, &positions.deposits); - assert_eq!(jake_claimed.amount, Uint128::new(12)); + let jake_balance = mock.query_balance(&mock.rover, &jake_info.denom); + assert_eq!(jake_balance.amount, Uint128::zero()); - // Ensure money is in bank module for credit manager - let osmo_balance = mock.query_balance(&mock.rover, &osmo_info.denom); + let osmo_balance = mock.query_balance(&user, &osmo_info.denom); assert_eq!(osmo_balance.amount, Uint128::new(123)); - let atom_balance = mock.query_balance(&mock.rover, &atom_info.denom); + let atom_balance = mock.query_balance(&user, &atom_info.denom); assert_eq!(atom_balance.amount, Uint128::new(555)); - let jake_balance = mock.query_balance(&mock.rover, &jake_info.denom); + let jake_balance = mock.query_balance(&user, &jake_info.denom); assert_eq!(jake_balance.amount, Uint128::new(12)); } From 605a3c192975425584040185f32c294b9ee0e3f1 Mon Sep 17 00:00:00 2001 From: piobab Date: Wed, 11 Oct 2023 12:17:05 +0200 Subject: [PATCH 218/218] Make 'yarn build' happy. --- scripts/deploy/base/deployer.rover.ts | 6 +++--- scripts/deploy/base/index.rover.ts | 4 ++-- scripts/deploy/base/rover.ts | 4 ++-- scripts/deploy/base/setupDeployer.rover.ts | 6 +++--- scripts/deploy/base/storage.rover.ts | 2 +- scripts/deploy/osmosis/devnet.ts | 4 ++-- scripts/deploy/osmosis/mainnet.ts | 4 ++-- scripts/deploy/osmosis/testnet-config.ts | 2 +- scripts/deploy/osmosis/testnet-deployer.ts | 2 +- scripts/deploy/osmosis/testnet-multisig.ts | 2 +- scripts/tsconfig.json | 2 +- 11 files changed, 19 insertions(+), 19 deletions(-) diff --git a/scripts/deploy/base/deployer.rover.ts b/scripts/deploy/base/deployer.rover.ts index f9e4f50bc..dbfe8df1e 100644 --- a/scripts/deploy/base/deployer.rover.ts +++ b/scripts/deploy/base/deployer.rover.ts @@ -1,7 +1,7 @@ import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' -import { DeploymentConfig, TestActions } from '../../types/config' +import { DeploymentConfig, TestActions } from '../../types/config.rover' import { printBlue, printGray, printGreen } from '../../utils/chalk' -import { ARTIFACTS_PATH, Storage } from './storage' +import { ARTIFACTS_PATH, Storage } from './storage.rover' import fs from 'fs' import { InstantiateMsgs } from '../../types/instantiateMsgs' import { InstantiateMsg as NftInstantiateMsg } from '../../types/generated/mars-account-nft/MarsAccountNft.types' @@ -14,7 +14,7 @@ import { } from '../../types/generated/mars-credit-manager/MarsCreditManager.types' import { Rover } from './rover' import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing' -import { getAddress, getWallet, setupClient } from './setupDeployer' +import { getAddress, getWallet, setupClient } from './setupDeployer.rover' import { coin } from '@cosmjs/stargate' import { Coin } from '@cosmjs/amino' import { writeFile } from 'fs/promises' diff --git a/scripts/deploy/base/index.rover.ts b/scripts/deploy/base/index.rover.ts index 0b3f6542d..be9afcab8 100644 --- a/scripts/deploy/base/index.rover.ts +++ b/scripts/deploy/base/index.rover.ts @@ -1,6 +1,6 @@ -import { setupDeployer } from './setupDeployer' +import { setupDeployer } from './setupDeployer.rover' import { printRed, printYellow } from '../../utils/chalk' -import { DeploymentConfig } from '../../types/config' +import { DeploymentConfig } from '../../types/config.rover' import { wasmFile } from '../../utils/environment' export interface TaskRunnerProps { diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index e27f30ebc..56905a11d 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -1,5 +1,5 @@ -import { Storage } from './storage' -import { DeploymentConfig, TestActions, VaultInfo } from '../../types/config' +import { Storage } from './storage.rover' +import { DeploymentConfig, TestActions, VaultInfo } from '../../types/config.rover' import { difference } from 'lodash' import assert from 'assert' import { printBlue, printGreen } from '../../utils/chalk' diff --git a/scripts/deploy/base/setupDeployer.rover.ts b/scripts/deploy/base/setupDeployer.rover.ts index 6eda32256..2e365507b 100644 --- a/scripts/deploy/base/setupDeployer.rover.ts +++ b/scripts/deploy/base/setupDeployer.rover.ts @@ -1,9 +1,9 @@ import { SigningCosmWasmClient, SigningCosmWasmClientOptions } from '@cosmjs/cosmwasm-stargate' import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing' import { GasPrice } from '@cosmjs/stargate' -import { DeploymentConfig } from '../../types/config' -import { Deployer } from './deployer' -import { Storage } from './storage' +import { DeploymentConfig } from '../../types/config.rover' +import { Deployer } from './deployer.rover' +import { Storage } from './storage.rover' import { printGray } from '../../utils/chalk' export const getWallet = async (mnemonic: string, chainPrefix: string) => { diff --git a/scripts/deploy/base/storage.rover.ts b/scripts/deploy/base/storage.rover.ts index ef4e9cb08..815b7ca51 100644 --- a/scripts/deploy/base/storage.rover.ts +++ b/scripts/deploy/base/storage.rover.ts @@ -1,6 +1,6 @@ import { readFile, writeFile } from 'fs/promises' import path from 'path' -import { StorageItems as StorageItems } from '../../types/storageItems' +import { StorageItems as StorageItems } from '../../types/storageItems.rover' export const ARTIFACTS_PATH = '../artifacts/' diff --git a/scripts/deploy/osmosis/devnet.ts b/scripts/deploy/osmosis/devnet.ts index 80e54fd8e..14e464605 100644 --- a/scripts/deploy/osmosis/devnet.ts +++ b/scripts/deploy/osmosis/devnet.ts @@ -1,5 +1,5 @@ -import { taskRunner } from '../base' -import { DeploymentConfig } from '../../types/config' +import { taskRunner } from '../base/index.rover' +import { DeploymentConfig } from '../../types/config.rover' const osmo = 'uosmo' const atom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2' diff --git a/scripts/deploy/osmosis/mainnet.ts b/scripts/deploy/osmosis/mainnet.ts index 2435493c2..32e6345c1 100644 --- a/scripts/deploy/osmosis/mainnet.ts +++ b/scripts/deploy/osmosis/mainnet.ts @@ -1,5 +1,5 @@ -import { taskRunner } from '../base' -import { DeploymentConfig } from '../../types/config' +import { taskRunner } from '../base/index.rover' +import { DeploymentConfig } from '../../types/config.rover' const uosmo = 'uosmo' // const uatom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2' diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index be879868e..660055fd8 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -1,4 +1,4 @@ -import { DeploymentConfig, VaultType } from '../../types/config' +import { DeploymentConfig, VaultType } from '../../types/config.rover' // Note: since osmo-test-5 upgrade, testnet and mainnet denoms are no longer the same. Reference asset info here: https://docs.osmosis.zone/osmosis-core/asset-info/ const uosmo = 'uosmo' diff --git a/scripts/deploy/osmosis/testnet-deployer.ts b/scripts/deploy/osmosis/testnet-deployer.ts index edc58daec..10727f946 100644 --- a/scripts/deploy/osmosis/testnet-deployer.ts +++ b/scripts/deploy/osmosis/testnet-deployer.ts @@ -1,4 +1,4 @@ -import { taskRunner } from '../base' +import { taskRunner } from '../base/index.rover' import { osmosisTestnetConfig } from './testnet-config' void (async function () { diff --git a/scripts/deploy/osmosis/testnet-multisig.ts b/scripts/deploy/osmosis/testnet-multisig.ts index bf20f1349..aa2b76d2e 100644 --- a/scripts/deploy/osmosis/testnet-multisig.ts +++ b/scripts/deploy/osmosis/testnet-multisig.ts @@ -1,4 +1,4 @@ -import { taskRunner } from '../base' +import { taskRunner } from '../base/index.rover' import { osmosisTestnetConfig } from './testnet-config' void (async function () { diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index 82335d8d6..028cb51e7 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "ES2021", "lib": ["ES2021"], - "types": ["jest", "node"], + "types": ["node"], "module": "NodeNext", "declaration": true, "noImplicitAny": true,

~1ZvLsAr^0DmWwCR0jUk<$<--&_&<$8bv+kr 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 U9rEgOGru7mtZry13zZK4e=13.7.0": - version "18.14.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.0.tgz#94c47b9217bbac49d4a67a967fdcdeed89ebb7d0" - integrity sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A== + version "18.16.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.1.tgz#5db121e9c5352925bb1f1b892c4ae620e3526799" + integrity sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA== "@types/prettier@^2.1.5", "@types/prettier@^2.6.1": version "2.7.2" @@ -1930,94 +1911,94 @@ integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== "@types/yargs@^17.0.8": - version "17.0.22" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.22.tgz#7dd37697691b5f17d020f3c63e7a45971ff71e9a" - integrity sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g== + version "17.0.24" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" + integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== dependencies: "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^5.54.1": - version "5.54.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.1.tgz#0c5091289ce28372e38ab8d28e861d2dbe1ab29e" - integrity sha512-a2RQAkosH3d3ZIV08s3DcL/mcGc2M/UC528VkPULFxR9VnVPT8pBu0IyBAJJmVsCmhVfwQX1v6q+QGnmSe1bew== + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.1.tgz#9b09ee1541bff1d2cebdcb87e7ce4a4003acde08" + integrity sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg== dependencies: - "@typescript-eslint/scope-manager" "5.54.1" - "@typescript-eslint/type-utils" "5.54.1" - "@typescript-eslint/utils" "5.54.1" + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.59.1" + "@typescript-eslint/type-utils" "5.59.1" + "@typescript-eslint/utils" "5.59.1" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" natural-compare-lite "^1.4.0" - regexpp "^3.2.0" semver "^7.3.7" tsutils "^3.21.0" "@typescript-eslint/parser@^5.54.1": - version "5.54.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.54.1.tgz#05761d7f777ef1c37c971d3af6631715099b084c" - integrity sha512-8zaIXJp/nG9Ff9vQNh7TI+C3nA6q6iIsGJ4B4L6MhZ7mHnTMR4YP5vp2xydmFXIy8rpyIVbNAG44871LMt6ujg== + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.1.tgz#73c2c12127c5c1182d2e5b71a8fa2a85d215cbb4" + integrity sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g== dependencies: - "@typescript-eslint/scope-manager" "5.54.1" - "@typescript-eslint/types" "5.54.1" - "@typescript-eslint/typescript-estree" "5.54.1" + "@typescript-eslint/scope-manager" "5.59.1" + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/typescript-estree" "5.59.1" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.54.1": - version "5.54.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.54.1.tgz#6d864b4915741c608a58ce9912edf5a02bb58735" - integrity sha512-zWKuGliXxvuxyM71UA/EcPxaviw39dB2504LqAmFDjmkpO8qNLHcmzlh6pbHs1h/7YQ9bnsO8CCcYCSA8sykUg== +"@typescript-eslint/scope-manager@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.1.tgz#8a20222719cebc5198618a5d44113705b51fd7fe" + integrity sha512-mau0waO5frJctPuAzcxiNWqJR5Z8V0190FTSqRw1Q4Euop6+zTwHAf8YIXNwDOT29tyUDrQ65jSg9aTU/H0omA== dependencies: - "@typescript-eslint/types" "5.54.1" - "@typescript-eslint/visitor-keys" "5.54.1" + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/visitor-keys" "5.59.1" -"@typescript-eslint/type-utils@5.54.1": - version "5.54.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.54.1.tgz#4825918ec27e55da8bb99cd07ec2a8e5f50ab748" - integrity sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g== +"@typescript-eslint/type-utils@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.1.tgz#63981d61684fd24eda2f9f08c0a47ecb000a2111" + integrity sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw== dependencies: - "@typescript-eslint/typescript-estree" "5.54.1" - "@typescript-eslint/utils" "5.54.1" + "@typescript-eslint/typescript-estree" "5.59.1" + "@typescript-eslint/utils" "5.59.1" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.54.1": - version "5.54.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.54.1.tgz#29fbac29a716d0f08c62fe5de70c9b6735de215c" - integrity sha512-G9+1vVazrfAfbtmCapJX8jRo2E4MDXxgm/IMOF4oGh3kq7XuK3JRkOg6y2Qu1VsTRmWETyTkWt1wxy7X7/yLkw== +"@typescript-eslint/types@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.1.tgz#03f3fedd1c044cb336ebc34cc7855f121991f41d" + integrity sha512-dg0ICB+RZwHlysIy/Dh1SP+gnXNzwd/KS0JprD3Lmgmdq+dJAJnUPe1gNG34p0U19HvRlGX733d/KqscrGC1Pg== -"@typescript-eslint/typescript-estree@5.54.1": - version "5.54.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.1.tgz#df7b6ae05fd8fef724a87afa7e2f57fa4a599be1" - integrity sha512-bjK5t+S6ffHnVwA0qRPTZrxKSaFYocwFIkZx5k7pvWfsB1I57pO/0M0Skatzzw1sCkjJ83AfGTL0oFIFiDX3bg== +"@typescript-eslint/typescript-estree@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.1.tgz#4aa546d27fd0d477c618f0ca00b483f0ec84c43c" + integrity sha512-lYLBBOCsFltFy7XVqzX0Ju+Lh3WPIAWxYpmH/Q7ZoqzbscLiCW00LeYCdsUnnfnj29/s1WovXKh2gwCoinHNGA== dependencies: - "@typescript-eslint/types" "5.54.1" - "@typescript-eslint/visitor-keys" "5.54.1" + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/visitor-keys" "5.59.1" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.54.1": - version "5.54.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.54.1.tgz#7a3ee47409285387b9d4609ea7e1020d1797ec34" - integrity sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ== +"@typescript-eslint/utils@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.1.tgz#d89fc758ad23d2157cfae53f0b429bdf15db9473" + integrity sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA== dependencies: + "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.54.1" - "@typescript-eslint/types" "5.54.1" - "@typescript-eslint/typescript-estree" "5.54.1" + "@typescript-eslint/scope-manager" "5.59.1" + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/typescript-estree" "5.59.1" eslint-scope "^5.1.1" - eslint-utils "^3.0.0" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.54.1": - version "5.54.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.1.tgz#d7a8a0f7181d6ac748f4d47b2306e0513b98bf8b" - integrity sha512-q8iSoHTgwCfgcRJ2l2x+xCbu8nBlRAlsQ33k24Adj8eoVBE0f8dUeI+bAa8F84Mv05UGbAx57g2zrRsYIooqQg== +"@typescript-eslint/visitor-keys@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.1.tgz#0d96c36efb6560d7fb8eb85de10442c10d8f6058" + integrity sha512-6waEYwBTCWryx0VJmP7JaM4FpipLsFl9CvYf2foAE8Qh/Y0s+bxWysciwOs0LTBED4JCaNxTZ5rGadB14M6dwA== dependencies: - "@typescript-eslint/types" "5.54.1" + "@typescript-eslint/types" "5.59.1" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -2341,9 +2322,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001449: - version "1.0.30001457" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz#6af34bb5d720074e2099432aa522c21555a18301" - integrity sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA== + version "1.0.30001481" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz#f58a717afe92f9e69d0e35ff64df596bfad93912" + integrity sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ== case@1.6.3: version "1.6.3" @@ -2517,9 +2498,9 @@ copyfiles@^2.4.1: yargs "^16.1.0" core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.25.1: - version "3.28.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.28.0.tgz#c08456d854608a7264530a2afa281fadf20ecee6" - integrity sha512-myzPgE7QodMg4nnd3K1TDoES/nADRStM8Gpz0D6nhkwbmwEnE0ZGJgoWsvQ722FR8D7xS0n0LV556RcEicjTyg== + version "3.30.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.1.tgz#961541e22db9c27fc48bfc13a3cafa8734171dfe" + integrity sha512-d690npR7MC6P0gq4npTl5n2VQeNAmUrJ90n+MHiKS7W2+xno4o3F5GDEuylSdi6EJ3VssibSGXOa1r3YXD3Mhw== dependencies: browserslist "^4.21.5" @@ -2529,9 +2510,9 @@ core-util-is@~1.0.0: integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== cosmjs-types@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.7.1.tgz#7ad355f63206fb829b565ed3463791d33b10c3d7" - integrity sha512-qP89SGwi6YpvMTrM9CPzTfZ0JPNlXzgimqMLsa/ZjzW+L6MC8TCr6XmoWtFOT6GSfefvJLwFWq7YCtL456Bdzg== + version "0.7.2" + resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.7.2.tgz#a757371abd340949c5bd5d49c6f8379ae1ffd7e2" + integrity sha512-vf2uLyktjr/XVAgEq0DjMxeAWh1yYREe7AMHDKd7EiHVqxBPCaBS+qEEQUkXbR9ndnckqr1sUG8BQhazh4X5lA== dependencies: long "^4.0.0" protobufjs "~6.11.2" @@ -2581,9 +2562,9 @@ deepmerge@4.2.2: integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== deepmerge@^4.2.2: - version "4.3.0" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.0.tgz#65491893ec47756d44719ae520e0e2609233b59b" - integrity sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og== + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== define-properties@^1.1.3: version "1.2.0" @@ -2623,9 +2604,9 @@ dotty@0.1.2: integrity sha512-V0EWmKeH3DEhMwAZ+8ZB2Ao4OK6p++Z0hsDtZq3N0+0ZMVqkzrcEGROvOnZpLnvBg5PTNG23JEDLAm64gPaotQ== electron-to-chromium@^1.4.284: - version "1.4.306" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.306.tgz#3f16bc14e150ad79803145fffeaf0bee15d3fca7" - integrity sha512-1zGmLFfpcs2v7ELt/1HgLZF6Gm2CCHaAdNKxd9Ge4INSU/HDYWjs7fcWU6eVMmhkpwmh+52ZrGCUU+Ji9OJihA== + version "1.4.372" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.372.tgz#7888ac92ccb9556627c3a37eba3b89ee5ac345f8" + integrity sha512-MrlFq/j+TYHOjeWsWGYfzevc25HNeJdsF6qaLFrqBTRWZQtWkb1myq/Q2veLWezVaa5OcSZ99CFwTT4aF4Mung== elliptic@^6.5.4: version "6.5.4" @@ -2714,9 +2695,9 @@ escape-string-regexp@^4.0.0: integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== eslint-config-prettier@^8.7.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.7.0.tgz#f1cc58a8afebc50980bd53475451df146c13182d" - integrity sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA== + version "8.8.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348" + integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== eslint-scope@^5.1.1: version "5.1.1" @@ -2726,40 +2707,28 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== +eslint-scope@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b" + integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint-visitor-keys@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" - integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" + integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== eslint@^8.36.0: - version "8.36.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.36.0.tgz#1bd72202200a5492f91803b113fb8a83b11285cf" - integrity sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw== + version "8.39.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.39.0.tgz#7fd20a295ef92d43809e914b70c39fd5a23cf3f1" + integrity sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.1" - "@eslint/js" "8.36.0" + "@eslint/eslintrc" "^2.0.2" + "@eslint/js" "8.39.0" "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -2769,9 +2738,9 @@ eslint@^8.36.0: debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-visitor-keys "^3.3.0" - espree "^9.5.0" + eslint-scope "^7.2.0" + eslint-visitor-keys "^3.4.0" + espree "^9.5.1" esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -2797,14 +2766,14 @@ eslint@^8.36.0: strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.5.0: - version "9.5.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.0.tgz#3646d4e3f58907464edba852fa047e6a27bdf113" - integrity sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw== +espree@^9.5.1: + version "9.5.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4" + integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg== dependencies: acorn "^8.8.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.3.0" + eslint-visitor-keys "^3.4.0" esprima@^4.0.0: version "4.0.1" @@ -2868,18 +2837,7 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0: - version "29.4.3" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.4.3.tgz#5e47757316df744fe3b8926c3ae8a3ebdafff7fe" - integrity sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg== - dependencies: - "@jest/expect-utils" "^29.4.3" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.4.3" - jest-message-util "^29.4.3" - jest-util "^29.4.3" - -expect@^29.5.0: +expect@^29.0.0, expect@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== @@ -3147,9 +3105,9 @@ globby@^11.1.0: slash "^3.0.0" graceful-fs@^4.1.15, graceful-fs@^4.2.9: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== grapheme-splitter@^1.0.4: version "1.0.4" @@ -3341,10 +3299,10 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== +is-core-module@^2.11.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" + integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== dependencies: has "^1.0.3" @@ -3537,16 +3495,6 @@ jest-config@^29.5.0: slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.4.3.tgz#42f4eb34d0bf8c0fb08b0501069b87e8e84df347" - integrity sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.4.3" - jest-get-type "^29.4.3" - pretty-format "^29.4.3" - jest-diff@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63" @@ -3638,16 +3586,6 @@ jest-leak-detector@^29.5.0: jest-get-type "^29.4.3" pretty-format "^29.5.0" -jest-matcher-utils@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.4.3.tgz#ea68ebc0568aebea4c4213b99f169ff786df96a0" - integrity sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg== - dependencies: - chalk "^4.0.0" - jest-diff "^29.4.3" - jest-get-type "^29.4.3" - pretty-format "^29.4.3" - jest-matcher-utils@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5" @@ -3658,21 +3596,6 @@ jest-matcher-utils@^29.5.0: jest-get-type "^29.4.3" pretty-format "^29.5.0" -jest-message-util@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.4.3.tgz#65b5280c0fdc9419503b49d4f48d4999d481cb5b" - integrity sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.4.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.4.3" - slash "^3.0.0" - stack-utils "^2.0.3" - jest-message-util@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e" @@ -3831,18 +3754,6 @@ jest-util@^28.1.3: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.4.3.tgz#851a148e23fc2b633c55f6dad2e45d7f4579f496" - integrity sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q== - dependencies: - "@jest/types" "^29.4.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - jest-util@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" @@ -3911,9 +3822,9 @@ jest@^29.5.0: jest-cli "^29.5.0" js-sdsl@^4.1.4: - version "4.3.0" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711" - integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ== + version "4.4.0" + resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.0.tgz#8b437dbe642daa95760400b602378ed8ffea8430" + integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== js-tokens@^4.0.0: version "4.0.0" @@ -4035,9 +3946,9 @@ long@^4.0.0: integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== long@^5.2.0, long@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/long/-/long-5.2.1.tgz#e27595d0083d103d2fa2c20c7699f8e0c92b897f" - integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A== + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== lru-cache@^5.1.1: version "5.1.1" @@ -4158,9 +4069,9 @@ minipass@^3.0.0: yallist "^4.0.0" minipass@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.1.tgz#084031141113657662d40f66f9c2329036892128" - integrity sha512-KS4CHIsDfOZetnT+u6fwxyFADXLamtkPxkGScmmtTW//MlRrImV+LtbmbJpLQ86Hw7km/utbfEfndhGBrfwvlA== + version "4.2.8" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" + integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== minizlib@^2.1.1: version "2.1.2" @@ -4407,20 +4318,11 @@ prepend-file@^2.0.1: temp-write "^4.0.0" prettier@^2.6.2, prettier@^2.8.4: - version "2.8.4" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3" - integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw== + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== -pretty-format@^29.0.0, pretty-format@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.3.tgz#25500ada21a53c9e8423205cf0337056b201244c" - integrity sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA== - dependencies: - "@jest/schemas" "^29.4.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -pretty-format@^29.5.0: +pretty-format@^29.0.0, pretty-format@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a" integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw== @@ -4467,9 +4369,9 @@ punycode@^2.1.0: integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== pure-rand@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.1.tgz#31207dddd15d43f299fdcdb2f572df65030c19af" - integrity sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg== + version "6.0.2" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" + integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== queue-microtask@^1.2.2: version "1.2.3" @@ -4540,15 +4442,10 @@ regenerator-transform@^0.15.1: dependencies: "@babel/runtime" "^7.8.4" -regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - regexpu-core@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.1.tgz#66900860f88def39a5cb79ebd9490e84f17bcdfb" - integrity sha512-nCOzW2V/X15XpLsK2rlgdwrysrBq+AauCn+omItIz4R1pIcmeot5zvjdmOBRLzEH/CkC6IxMJVmxDe3QcMuNVQ== + version "5.3.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== dependencies: "@babel/regjsgen" "^0.8.0" regenerate "^1.4.2" @@ -4587,16 +4484,16 @@ resolve-from@^5.0.0: integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve.exports@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.0.tgz#c1a0028c2d166ec2fbf7d0644584927e76e7400e" - integrity sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== resolve@^1.1.6, resolve@^1.14.2, resolve@^1.20.0: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + version "1.22.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.11.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -4667,9 +4564,9 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== semver@^7.3.5, semver@^7.3.7: - version "7.3.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + version "7.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" + integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== dependencies: lru-cache "^6.0.0" @@ -4995,10 +4892,10 @@ type@^2.7.2: resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== -typescript@^4.9.5: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" + integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" @@ -5029,9 +4926,9 @@ untildify@^4.0.0: integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== update-browserslist-db@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + version "1.0.11" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" + integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== dependencies: escalade "^3.1.1" picocolors "^1.0.0" From d4ccb5569b294429203340d2a6531d3692131a04 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Mon, 1 May 2023 15:46:23 +0200 Subject: [PATCH 157/218] Support repaying debt of blacklisted asset (#135) * Support repaying from wallet * Always add refund attr --- Cargo.lock | 21 +- contracts/credit-manager/Cargo.toml | 2 +- contracts/credit-manager/src/contract.rs | 4 + contracts/credit-manager/src/repay.rs | 62 +++++- .../credit-manager/tests/helpers/mock_env.rs | 16 ++ .../tests/test_repay_from_wallet.rs | 206 ++++++++++++++++++ packages/rover/src/error.rs | 4 + packages/rover/src/msg/execute.rs | 5 + .../mars-credit-manager.json | 22 ++ .../MarsCreditManager.client.ts | 34 +++ .../MarsCreditManager.message-composer.ts | 33 +++ .../MarsCreditManager.react-query.ts | 23 ++ .../MarsCreditManager.types.ts | 5 + 13 files changed, 425 insertions(+), 12 deletions(-) create mode 100644 contracts/credit-manager/tests/test_repay_from_wallet.rs diff --git a/Cargo.lock b/Cargo.lock index 4402514db..0083cef70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,9 +24,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "apollo-cw-asset" @@ -235,9 +235,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.24" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eef2b3ded6a26dfaec672a742c93c8cf6b689220324da509ec5caa20de55dc83" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags", @@ -1685,7 +1685,7 @@ dependencies = [ [[package]] name = "osmosis-std" version = "0.15.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#f726d5f5820cd04fedf23ea336e7174acb5b44da" +source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#b0860d063c0b59cb5d5b3fb4233bb2784185bb4c" dependencies = [ "chrono", "cosmwasm-std", @@ -1712,7 +1712,7 @@ dependencies = [ [[package]] name = "osmosis-std-derive" version = "0.15.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#f726d5f5820cd04fedf23ea336e7174acb5b44da" +source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#b0860d063c0b59cb5d5b3fb4233bb2784185bb4c" dependencies = [ "itertools", "proc-macro2", @@ -1723,7 +1723,7 @@ dependencies = [ [[package]] name = "osmosis-test-tube" version = "14.1.1" -source = "git+https://github.com/osmosis-labs/test-tube?branch=main#1ba6e8021e441c7ffd1e17796ae64cb750ca57d0" +source = "git+https://github.com/osmosis-labs/test-tube?branch=main#ae48e239edcf7d0bd7fddd789d2078bd15ef9b2a" dependencies = [ "base64", "bindgen", @@ -2460,7 +2460,7 @@ dependencies = [ [[package]] name = "test-tube" version = "0.1.1" -source = "git+https://github.com/osmosis-labs/test-tube?branch=main#1ba6e8021e441c7ffd1e17796ae64cb750ca57d0" +source = "git+https://github.com/osmosis-labs/test-tube?branch=main#ae48e239edcf7d0bd7fddd789d2078bd15ef9b2a" dependencies = [ "base64", "cosmrs", @@ -2599,10 +2599,11 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.38" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9cf6a813d3f40c88b0b6b6f29a5c95c6cdbf97c1f9cc53fb820200f5ad814d" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ + "cfg-if", "pin-project-lite", "tracing-core", ] diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 51ed10940..a570ee106 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -27,6 +27,7 @@ cw721-base = { workspace = true } cw-item-set = { workspace = true } cw-paginate = { workspace = true } cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } cw-vault-standard = { workspace = true } mars-account-nft = { workspace = true } mars-owner = { workspace = true } @@ -37,7 +38,6 @@ mars-rover-health-types = { workspace = true } [dev-dependencies] anyhow = { workspace = true } cw-multi-test = { workspace = true } -cw-utils = { workspace = true } itertools = { workspace = true } mars-mock-oracle = { workspace = true } mars-mock-red-bank = { workspace = true } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index aeb7046bc..48ce67dea 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -20,6 +20,7 @@ use crate::{ query_total_vault_coin_balance, query_vault_config, query_vault_position_value, query_vault_utilization, query_vaults_config, }, + repay::repay_from_wallet, update_config::{update_config, update_nft_config, update_owner}, vault::handle_unlock_request_reply, zap::{estimate_provide_liquidity, estimate_withdraw_liquidity}, @@ -62,6 +63,9 @@ pub fn execute( actions, } => dispatch_actions(deps, env, info, &account_id, &actions), ExecuteMsg::EmergencyConfigUpdate(update) => emergency_config_update(deps, info, update), + ExecuteMsg::RepayFromWallet { + account_id, + } => repay_from_wallet(deps, env, info, account_id), } } diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index ce64a3228..3b17bc9a9 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -1,6 +1,10 @@ use std::cmp::min; -use cosmwasm_std::{to_binary, Coin, CosmosMsg, Deps, DepsMut, Env, Response, Uint128, WasmMsg}; +use cosmwasm_std::{ + to_binary, BankMsg, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, Uint128, + WasmMsg, +}; +use cw_utils::one_coin; use mars_rover::{ error::{ContractError, ContractResult}, msg::{ @@ -119,3 +123,59 @@ pub fn repay_for_recipient( .add_attribute("recipient_account_id", recipient_account_id) .add_attribute("coin_repaid", coin_to_repay.to_string())) } + +pub fn repay_from_wallet( + deps: DepsMut, + env: Env, + info: MessageInfo, + account_id: String, +) -> ContractResult { + let coin_sent = one_coin(&info)?; + + let (debt_amount, _) = + current_debt_for_denom(deps.as_ref(), &env, &account_id, &coin_sent.denom)?; + let amount_to_repay = min(debt_amount, coin_sent.amount); + let coin_to_repay = Coin { + denom: coin_sent.denom.clone(), + amount: amount_to_repay, + }; + + increment_coin_balance(deps.storage, &account_id, &coin_to_repay)?; + + let repay_callback_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::Callback(Repay { + account_id: account_id.to_string(), + coin: ActionCoin::from(&coin_to_repay), + }))?, + }); + + // if attempting to repay too much, refund back the extra + let refund_amount = if coin_sent.amount > coin_to_repay.amount { + coin_sent.amount.checked_sub(coin_to_repay.amount)? + } else { + Uint128::zero() + }; + + let mut response = Response::new() + .add_message(repay_callback_msg) + .add_attribute("action", "repay_from_wallet") + .add_attribute("from_address", info.sender.to_string()) + .add_attribute("account_id", account_id) + .add_attribute("coin_repaid", coin_to_repay.to_string()) + .add_attribute("refunded", refund_amount.to_string()); + + if !refund_amount.is_zero() { + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.to_string(), + amount: vec![Coin { + denom: coin_sent.denom, + amount: refund_amount, + }], + }); + response = response.add_message(transfer_msg); + } + + Ok(response) +} diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 8c72b8d6b..9c74683b7 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -148,6 +148,22 @@ impl MockEnv { ) } + pub fn repay_from_wallet( + &mut self, + sender: &Addr, + account_id: &str, + funds: &[Coin], + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.rover.clone(), + &ExecuteMsg::RepayFromWallet { + account_id: account_id.to_string(), + }, + funds, + ) + } + pub fn update_config( &mut self, sender: &Addr, diff --git a/contracts/credit-manager/tests/test_repay_from_wallet.rs b/contracts/credit-manager/tests/test_repay_from_wallet.rs new file mode 100644 index 000000000..68e41f6b1 --- /dev/null +++ b/contracts/credit-manager/tests/test_repay_from_wallet.rs @@ -0,0 +1,206 @@ +use cosmwasm_std::{coin, coins, Addr, Uint128}; +use cw_utils::PaymentError; +use mars_rover::{ + error::ContractError, + msg::{ + execute::Action::{Borrow, Deposit}, + instantiate::ConfigUpdates, + }, +}; + +use crate::helpers::{assert_err, uosmo_info, AccountToFund, MockEnv}; + +pub mod helpers; + +#[test] +fn raises_when_sending_incorrect_funds() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![coin(12, "abc"), coin(32, "xyz")], + }) + .build() + .unwrap(); + + let res = mock.repay_from_wallet(&user, "123", &[]); + assert_err(res, ContractError::Payment(PaymentError::NoFunds {})); + + let res = mock.repay_from_wallet(&user, "123", &[coin(12, "abc"), coin(32, "xyz")]); + assert_err(res, ContractError::Payment(PaymentError::MultipleDenoms {})); +} + +#[test] +fn no_debt_on_account() { + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(12, "abc"), + }) + .build() + .unwrap(); + + // Credit account doesn't exist + let res = mock.repay_from_wallet(&user, "123", &[coin(12, "abc")]); + assert_err(res, ContractError::NoDebt); + + // Exists but no debt + let account_id = mock.create_credit_account(&user).unwrap(); + let res = mock.repay_from_wallet(&user, &account_id, &[coin(12, "abc")]); + assert_err(res, ContractError::NoDebt); +} + +#[test] +fn repay_of_less_than_total_debt() { + let coin_info = uosmo_info(); + let debtor = Addr::unchecked("debtor"); + let repayer = Addr::unchecked("debtor"); + + let repayer_starting_amount = 300; + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: debtor.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: repayer.clone(), + funds: coins(repayer_starting_amount, coin_info.denom.clone()), + }) + .build() + .unwrap(); + let account_id = mock.create_credit_account(&debtor).unwrap(); + + mock.update_credit_account( + &account_id, + &debtor, + vec![ + Deposit(coin(300, coin_info.denom.clone())), + Borrow(coin(42, coin_info.denom.clone())), + ], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + let debt_amount = mock.query_positions(&account_id).debts.first().unwrap().amount; + assert_eq!(debt_amount, Uint128::new(43)); // simulated debt interest adds +1 + + // Now that debtor is setup, we can attempt to repay from repayor + mock.repay_from_wallet(&repayer, &account_id, &[coin(12, coin_info.denom.clone())]).unwrap(); + + // Assert new debtor position + let positions = mock.query_positions(&account_id); + assert_eq!(1, positions.debts.len()); + assert_eq!(Uint128::new(31), positions.debts.first().unwrap().amount); // 43 - 12 + + // Assert repayer wallet after repaying + let balance = mock.query_balance(&repayer, &coin_info.denom); + assert_eq!(Uint128::new(repayer_starting_amount - 12), balance.amount); +} + +#[test] +fn repay_of_more_than_total_debt() { + let coin_info = uosmo_info(); + let debtor = Addr::unchecked("debtor"); + let repayer = Addr::unchecked("debtor"); + + let repayer_starting_amount = 300; + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: debtor.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: repayer.clone(), + funds: coins(repayer_starting_amount, coin_info.denom.clone()), + }) + .build() + .unwrap(); + let account_id = mock.create_credit_account(&debtor).unwrap(); + + mock.update_credit_account( + &account_id, + &debtor, + vec![ + Deposit(coin(300, coin_info.denom.clone())), + Borrow(coin(42, coin_info.denom.clone())), + ], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + let debt_amount = mock.query_positions(&account_id).debts.first().unwrap().amount; + assert_eq!(debt_amount, Uint128::new(43)); // simulated debt interest adds +1 + + // Note that repayer is attempting to repay 50 (more than total debt) + mock.repay_from_wallet(&repayer, &account_id, &[coin(50, coin_info.denom.clone())]).unwrap(); + + // Assert debtor has debt fully paid + let positions = mock.query_positions(&account_id); + assert_eq!(0, positions.debts.len()); + + // Assert refund has taken place + let balance = mock.query_balance(&repayer, &coin_info.denom); + assert_eq!(Uint128::new(repayer_starting_amount - 43), balance.amount); +} + +#[test] +fn delisted_assets_can_be_repaid() { + let coin_info = uosmo_info(); + let debtor = Addr::unchecked("debtor"); + let repayer = Addr::unchecked("debtor"); + + let mut mock = MockEnv::new() + .allowed_coins(&[coin_info.clone()]) + .fund_account(AccountToFund { + addr: debtor.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: repayer.clone(), + funds: coins(300, coin_info.denom.clone()), + }) + .build() + .unwrap(); + let account_id = mock.create_credit_account(&debtor).unwrap(); + + mock.update_credit_account( + &account_id, + &debtor, + vec![ + Deposit(coin(300, coin_info.denom.clone())), + Borrow(coin(42, coin_info.denom.clone())), + ], + &[coin(300, coin_info.denom.clone())], + ) + .unwrap(); + + // Delist the asset + let config = mock.query_config(); + mock.update_config( + &Addr::unchecked(config.owner.unwrap()), + ConfigUpdates { + account_nft: None, + allowed_coins: Some(vec![]), + vault_configs: None, + oracle: None, + red_bank: None, + max_close_factor: None, + max_unlocking_positions: None, + swapper: None, + zapper: None, + health_contract: None, + }, + ) + .unwrap(); + + let allowed_coins = mock.query_allowed_coins(None, None); + assert_eq!(0, allowed_coins.len()); + + // There should be no error in repaying for this asset + mock.repay_from_wallet(&repayer, &account_id, &[coin(12, coin_info.denom)]).unwrap(); +} diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index e7fec8900..09e86da14 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -2,6 +2,7 @@ use cosmwasm_std::{ CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError, Coin, DecimalRangeExceeded, OverflowError, StdError, Uint128, }; +use cw_utils::PaymentError; use mars_owner::OwnerError; use thiserror::Error; @@ -119,6 +120,9 @@ pub enum ContractError { #[error("{0}")] Overflow(#[from] OverflowError), + #[error("{0}")] + Payment(#[from] PaymentError), + #[error("Reply id: {0} not valid")] ReplyIdError(u64), diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index c05446f73..b17617e87 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -20,6 +20,11 @@ pub enum ExecuteMsg { account_id: String, actions: Vec, }, + /// Repay debt on behalf of an account, funded from wallet. Must send exactly one coin in message funds. + /// Allows repaying debts of assets that have been de-listed from credit manager. + RepayFromWallet { + account_id: String, + }, //-------------------------------------------------------------------------------------------------- // Privileged messages diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index b6c780b02..619afabc3 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -235,6 +235,28 @@ }, "additionalProperties": false }, + { + "description": "Repay debt on behalf of an account, funded from wallet. Must send exactly one coin in message funds. Allows repaying debts of assets that have been de-listed from credit manager.", + "type": "object", + "required": [ + "repay_from_wallet" + ], + "properties": { + "repay_from_wallet": { + "type": "object", + "required": [ + "account_id" + ], + "properties": { + "account_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Update contract config constants", "type": "object", diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index a55efe476..897afdd52 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -406,6 +406,16 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt memo?: string, funds?: Coin[], ) => Promise + repayFromWallet: ( + { + accountId, + }: { + accountId: string + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise updateConfig: ( { updates, @@ -457,6 +467,7 @@ export class MarsCreditManagerClient this.contractAddress = contractAddress this.createCreditAccount = this.createCreditAccount.bind(this) this.updateCreditAccount = this.updateCreditAccount.bind(this) + this.repayFromWallet = this.repayFromWallet.bind(this) this.updateConfig = this.updateConfig.bind(this) this.emergencyConfigUpdate = this.emergencyConfigUpdate.bind(this) this.updateOwner = this.updateOwner.bind(this) @@ -506,6 +517,29 @@ export class MarsCreditManagerClient funds, ) } + repayFromWallet = async ( + { + accountId, + }: { + accountId: string + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + repay_from_wallet: { + account_id: accountId, + }, + }, + fee, + memo, + funds, + ) + } updateConfig = async ( { updates, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 46492154d..45bb6e135 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -81,6 +81,14 @@ export interface MarsCreditManagerMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject + repayFromWallet: ( + { + accountId, + }: { + accountId: string + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject updateConfig: ( { updates, @@ -110,6 +118,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag this.contractAddress = contractAddress this.createCreditAccount = this.createCreditAccount.bind(this) this.updateCreditAccount = this.updateCreditAccount.bind(this) + this.repayFromWallet = this.repayFromWallet.bind(this) this.updateConfig = this.updateConfig.bind(this) this.emergencyConfigUpdate = this.emergencyConfigUpdate.bind(this) this.updateOwner = this.updateOwner.bind(this) @@ -159,6 +168,30 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag }), } } + repayFromWallet = ( + { + accountId, + }: { + accountId: string + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + repay_from_wallet: { + account_id: accountId, + }, + }), + ), + funds, + }), + } + } updateConfig = ( { updates, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 7f5397022..4f6df16aa 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -732,6 +732,29 @@ export function useMarsCreditManagerUpdateConfigMutation( options, ) } +export interface MarsCreditManagerRepayFromWalletMutation { + client: MarsCreditManagerClient + msg: { + accountId: string + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsCreditManagerRepayFromWalletMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.repayFromWallet(msg, fee, memo, funds), + options, + ) +} export interface MarsCreditManagerUpdateCreditAccountMutation { client: MarsCreditManagerClient msg: { diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index aca591891..de290892e 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -52,6 +52,11 @@ export type ExecuteMsg = actions: Action[] } } + | { + repay_from_wallet: { + account_id: string + } + } | { update_config: { updates: ConfigUpdates From 0f15d89e0d80c7061b4f80c372928290617d9b7e Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 3 May 2023 11:37:42 +0200 Subject: [PATCH 158/218] V3 Zapper Contract (open positions) (#133) * V3 Zapper * rebase to master * Review updates * Update to for loop * cargo update --- Cargo.lock | 72 ++- Cargo.toml | 22 +- Makefile.toml | 4 +- contracts/credit-manager/Cargo.toml | 18 +- .../credit-manager/tests/helpers/contracts.rs | 8 +- .../credit-manager/tests/helpers/mock_env.rs | 6 +- .../credit-manager/tests/test_zap_provide.rs | 2 +- .../credit-manager/tests/test_zap_withdraw.rs | 2 +- contracts/mock-vault/src/contract.rs | 3 + contracts/mock-vault/src/unlock.rs | 2 +- .../{zapper => v2-zapper}/base/Cargo.toml | 2 +- .../base/examples/schema.rs | 2 +- .../base/src/contract.rs | 0 .../{zapper => v2-zapper}/base/src/error.rs | 0 .../{zapper => v2-zapper}/base/src/helpers.rs | 0 .../{zapper => v2-zapper}/base/src/lib.rs | 0 .../{zapper => v2-zapper}/base/src/msg.rs | 0 .../{zapper => v2-zapper}/base/src/traits.rs | 0 .../{zapper => v2-zapper}/mock/Cargo.toml | 2 +- .../mock/examples/schema.rs | 0 .../mock/src/contract.rs | 0 .../{zapper => v2-zapper}/mock/src/error.rs | 0 .../{zapper => v2-zapper}/mock/src/execute.rs | 0 .../{zapper => v2-zapper}/mock/src/lib.rs | 0 .../{zapper => v2-zapper}/mock/src/query.rs | 0 .../{zapper => v2-zapper}/mock/src/state.rs | 0 contracts/v2-zapper/osmosis/Cargo.toml | 30 ++ .../osmosis/src/contract.rs | 2 +- .../{zapper => v2-zapper}/osmosis/src/lib.rs | 0 .../osmosis/src/lp_pool.rs | 2 +- .../osmosis/tests/helpers/mod.rs | 0 .../osmosis/tests/helpers/utils.rs | 25 +- .../osmosis/tests/test_callback.rs | 2 +- .../osmosis/tests/test_provide_liquidity.rs} | 2 +- .../osmosis/tests/test_queries.rs} | 2 +- .../osmosis/tests/test_withdraw_liquidity.rs} | 2 +- contracts/v3-zapper/base/Cargo.toml | 28 + contracts/v3-zapper/base/examples/schema.rs | 10 + contracts/v3-zapper/base/src/contract.rs | 171 ++++++ contracts/v3-zapper/base/src/error.rs | 26 + contracts/v3-zapper/base/src/lib.rs | 6 + contracts/v3-zapper/base/src/msg.rs | 47 ++ contracts/v3-zapper/base/src/state.rs | 3 + contracts/v3-zapper/base/src/traits.rs | 30 ++ contracts/v3-zapper/base/src/utils.rs | 18 + .../{zapper => v3-zapper}/osmosis/Cargo.toml | 12 +- contracts/v3-zapper/osmosis/src/contract.rs | 43 ++ contracts/v3-zapper/osmosis/src/lib.rs | 2 + .../v3-zapper/osmosis/src/position_manager.rs | 54 ++ .../osmosis/tests/helpers/assertions.rs | 21 + .../osmosis/tests/helpers/generator.rs | 14 + .../osmosis/tests/helpers/mock_env.rs | 228 ++++++++ .../v3-zapper/osmosis/tests/helpers/mod.rs | 5 + .../osmosis/tests/test_add_position.rs | 372 +++++++++++++ .../v3-zapper/osmosis/tests/test_callback.rs | 24 + .../osmosis/tests/test_update_owner.rs | 54 ++ .../osmosis/tests/test_v3_add_position.rs | 125 ----- schema.Makefile.toml | 3 +- schemas/mars-mock-vault/mars-mock-vault.json | 27 + .../mars-v2-zapper-base.json} | 2 +- .../mars-v3-zapper-base.json | 307 +++++++++++ scripts/deploy/base/deployer.ts | 2 +- scripts/health/pkg-node/index.js | 10 +- scripts/health/pkg-node/index_bg.wasm | Bin 159992 -> 159992 bytes scripts/health/pkg-web/index.js | 8 +- scripts/health/pkg-web/index_bg.wasm | Bin 159068 -> 159068 bytes scripts/package.json | 26 +- .../mars-account-nft/MarsAccountNft.client.ts | 2 +- .../MarsAccountNft.message-composer.ts | 2 +- .../MarsAccountNft.react-query.ts | 2 +- .../mars-account-nft/MarsAccountNft.types.ts | 2 +- .../generated/mars-account-nft/bundle.ts | 2 +- .../MarsCreditManager.client.ts | 14 +- .../MarsCreditManager.message-composer.ts | 26 +- .../MarsCreditManager.react-query.ts | 5 +- .../MarsCreditManager.types.ts | 2 +- .../generated/mars-credit-manager/bundle.ts | 2 +- .../MarsMockCreditManager.client.ts | 2 +- .../MarsMockCreditManager.message-composer.ts | 2 +- .../MarsMockCreditManager.react-query.ts | 2 +- .../MarsMockCreditManager.types.ts | 2 +- .../mars-mock-credit-manager/bundle.ts | 2 +- .../mars-mock-oracle/MarsMockOracle.client.ts | 2 +- .../MarsMockOracle.message-composer.ts | 2 +- .../MarsMockOracle.react-query.ts | 2 +- .../mars-mock-oracle/MarsMockOracle.types.ts | 2 +- .../generated/mars-mock-oracle/bundle.ts | 2 +- .../MarsMockRedBank.client.ts | 10 +- .../MarsMockRedBank.message-composer.ts | 17 +- .../MarsMockRedBank.react-query.ts | 4 +- .../MarsMockRedBank.types.ts | 2 +- .../generated/mars-mock-red-bank/bundle.ts | 2 +- .../mars-mock-vault/MarsMockVault.client.ts | 12 +- .../MarsMockVault.message-composer.ts | 14 +- .../MarsMockVault.react-query.ts | 3 +- .../mars-mock-vault/MarsMockVault.types.ts | 7 +- .../types/generated/mars-mock-vault/bundle.ts | 2 +- .../MarsRoverHealthComputer.client.ts | 2 +- ...arsRoverHealthComputer.message-composer.ts | 2 +- .../MarsRoverHealthComputer.react-query.ts | 2 +- .../MarsRoverHealthComputer.types.ts | 2 +- .../mars-rover-health-computer/bundle.ts | 2 +- .../MarsRoverHealthTypes.client.ts | 6 +- .../MarsRoverHealthTypes.message-composer.ts | 8 +- .../MarsRoverHealthTypes.react-query.ts | 3 +- .../MarsRoverHealthTypes.types.ts | 2 +- .../mars-rover-health-types/bundle.ts | 2 +- .../MarsSwapperBase.client.ts | 6 +- .../MarsSwapperBase.message-composer.ts | 8 +- .../MarsSwapperBase.react-query.ts | 3 +- .../MarsSwapperBase.types.ts | 2 +- .../generated/mars-swapper-base/bundle.ts | 2 +- .../MarsV2ZapperBase.client.ts} | 20 +- .../MarsV2ZapperBase.message-composer.ts} | 14 +- .../MarsV2ZapperBase.react-query.ts} | 71 +-- .../MarsV2ZapperBase.types.ts} | 2 +- .../generated/mars-v2-zapper-base/bundle.ts | 14 + .../MarsV3ZapperBase.client.ts | 173 ++++++ .../MarsV3ZapperBase.message-composer.ts | 133 +++++ .../MarsV3ZapperBase.react-query.ts | 124 +++++ .../MarsV3ZapperBase.types.ts | 67 +++ .../generated/mars-v3-zapper-base/bundle.ts | 14 + .../generated/mars-zapper-base/bundle.ts | 14 - scripts/types/instantiateMsgs.ts | 2 +- scripts/yarn.lock | 505 +++++++++--------- 125 files changed, 2620 insertions(+), 627 deletions(-) rename contracts/{zapper => v2-zapper}/base/Cargo.toml (95%) rename contracts/{zapper => v2-zapper}/base/examples/schema.rs (71%) rename contracts/{zapper => v2-zapper}/base/src/contract.rs (100%) rename contracts/{zapper => v2-zapper}/base/src/error.rs (100%) rename contracts/{zapper => v2-zapper}/base/src/helpers.rs (100%) rename contracts/{zapper => v2-zapper}/base/src/lib.rs (100%) rename contracts/{zapper => v2-zapper}/base/src/msg.rs (100%) rename contracts/{zapper => v2-zapper}/base/src/traits.rs (100%) rename contracts/{zapper => v2-zapper}/mock/Cargo.toml (95%) rename contracts/{zapper => v2-zapper}/mock/examples/schema.rs (100%) rename contracts/{zapper => v2-zapper}/mock/src/contract.rs (100%) rename contracts/{zapper => v2-zapper}/mock/src/error.rs (100%) rename contracts/{zapper => v2-zapper}/mock/src/execute.rs (100%) rename contracts/{zapper => v2-zapper}/mock/src/lib.rs (100%) rename contracts/{zapper => v2-zapper}/mock/src/query.rs (100%) rename contracts/{zapper => v2-zapper}/mock/src/state.rs (100%) create mode 100644 contracts/v2-zapper/osmosis/Cargo.toml rename contracts/{zapper => v2-zapper}/osmosis/src/contract.rs (92%) rename contracts/{zapper => v2-zapper}/osmosis/src/lib.rs (100%) rename contracts/{zapper => v2-zapper}/osmosis/src/lp_pool.rs (97%) rename contracts/{zapper => v2-zapper}/osmosis/tests/helpers/mod.rs (100%) rename contracts/{zapper => v2-zapper}/osmosis/tests/helpers/utils.rs (68%) rename contracts/{zapper => v2-zapper}/osmosis/tests/test_callback.rs (93%) rename contracts/{zapper/osmosis/tests/test_v2_provide_liquidity.rs => v2-zapper/osmosis/tests/test_provide_liquidity.rs} (99%) rename contracts/{zapper/osmosis/tests/test_v2_queries.rs => v2-zapper/osmosis/tests/test_queries.rs} (99%) rename contracts/{zapper/osmosis/tests/test_v2_withdraw_liquidity.rs => v2-zapper/osmosis/tests/test_withdraw_liquidity.rs} (99%) create mode 100644 contracts/v3-zapper/base/Cargo.toml create mode 100644 contracts/v3-zapper/base/examples/schema.rs create mode 100644 contracts/v3-zapper/base/src/contract.rs create mode 100644 contracts/v3-zapper/base/src/error.rs create mode 100644 contracts/v3-zapper/base/src/lib.rs create mode 100644 contracts/v3-zapper/base/src/msg.rs create mode 100644 contracts/v3-zapper/base/src/state.rs create mode 100644 contracts/v3-zapper/base/src/traits.rs create mode 100644 contracts/v3-zapper/base/src/utils.rs rename contracts/{zapper => v3-zapper}/osmosis/Cargo.toml (76%) create mode 100644 contracts/v3-zapper/osmosis/src/contract.rs create mode 100644 contracts/v3-zapper/osmosis/src/lib.rs create mode 100644 contracts/v3-zapper/osmosis/src/position_manager.rs create mode 100644 contracts/v3-zapper/osmosis/tests/helpers/assertions.rs create mode 100644 contracts/v3-zapper/osmosis/tests/helpers/generator.rs create mode 100644 contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs create mode 100644 contracts/v3-zapper/osmosis/tests/helpers/mod.rs create mode 100644 contracts/v3-zapper/osmosis/tests/test_add_position.rs create mode 100644 contracts/v3-zapper/osmosis/tests/test_callback.rs create mode 100644 contracts/v3-zapper/osmosis/tests/test_update_owner.rs delete mode 100644 contracts/zapper/osmosis/tests/test_v3_add_position.rs rename schemas/{mars-zapper-base/mars-zapper-base.json => mars-v2-zapper-base/mars-v2-zapper-base.json} (99%) create mode 100644 schemas/mars-v3-zapper-base/mars-v3-zapper-base.json rename scripts/types/generated/{mars-zapper-base/MarsZapperBase.client.ts => mars-v2-zapper-base/MarsV2ZapperBase.client.ts} (89%) rename scripts/types/generated/{mars-zapper-base/MarsZapperBase.message-composer.ts => mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts} (87%) rename scripts/types/generated/{mars-zapper-base/MarsZapperBase.react-query.ts => mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts} (56%) rename scripts/types/generated/{mars-zapper-base/MarsZapperBase.types.ts => mars-v2-zapper-base/MarsV2ZapperBase.types.ts} (99%) create mode 100644 scripts/types/generated/mars-v2-zapper-base/bundle.ts create mode 100644 scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts create mode 100644 scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts create mode 100644 scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts create mode 100644 scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts create mode 100644 scripts/types/generated/mars-v3-zapper-base/bundle.ts delete mode 100644 scripts/types/generated/mars-zapper-base/bundle.ts diff --git a/Cargo.lock b/Cargo.lock index 0083cef70..7a6271126 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -323,9 +323,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b76d2207945b8aa3ce0735da53ab9a74f75fe3e7794754c216a9edfa04e1e627" +checksum = "75836a10cb9654c54e77ee56da94d592923092a10b369cdb0dbd56acefc16340" dependencies = [ "digest 0.10.6", "ed25519-zebra", @@ -336,18 +336,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd07af7736164d2d8126dc67fdb33b1b5c54fb5a3190395c47f46d24fc6d592" +checksum = "1c9f7f0e51bfc7295f7b2664fe8513c966428642aa765dad8a74acdab5e0c773" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9e92cdce475a91659d0dc4d17836a484fc534e80e787281aa4adde4cb1798b" +checksum = "0f00b363610218eea83f24bbab09e1a7c3920b79f068334fdfcc62f6129ef9fc" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -358,9 +358,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69681bac3dbeb0b00990279e3ed39e3d4406b29f538b16b712f6771322a45048" +checksum = "ae38f909b2822d32b275c9e2db9728497aa33ffe67dd463bc67c6a3b7092785c" dependencies = [ "proc-macro2", "quote", @@ -369,9 +369,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d39f20967baeb94709123f7bba13a25ae2fa166bc5e7813f734914df3f8f6a1" +checksum = "a49b85345e811c8e80ec55d0d091e4fcb4f00f97ab058f9be5f614c444a730cb" dependencies = [ "base64", "cosmwasm-crypto", @@ -557,9 +557,9 @@ dependencies = [ [[package]] name = "cw-vault-standard" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793cd7de3239b1bf187a2a61c8e37d80bb9bd6e354328bfb12070323a435eee1" +checksum = "c2e72c053adf399c2dc95cf07ee0c8723dfceeb13ba127cc96ba31d96e454919" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1291,7 +1291,7 @@ dependencies = [ "mars-rover-health", "mars-rover-health-types", "mars-swapper-mock", - "mars-zapper-mock", + "mars-v2-zapper-mock", ] [[package]] @@ -1516,7 +1516,7 @@ dependencies = [ ] [[package]] -name = "mars-zapper-base" +name = "mars-v2-zapper-base" version = "1.0.0" dependencies = [ "cosmwasm-schema", @@ -1529,7 +1529,7 @@ dependencies = [ ] [[package]] -name = "mars-zapper-mock" +name = "mars-v2-zapper-mock" version = "1.0.0" dependencies = [ "cosmwasm-schema", @@ -1541,14 +1541,40 @@ dependencies = [ ] [[package]] -name = "mars-zapper-osmosis" +name = "mars-v2-zapper-osmosis" version = "1.0.0" dependencies = [ "cosmwasm-std", "cw-dex", "cw-utils 1.0.1", "cw2 1.0.1", - "mars-zapper-base", + "mars-v2-zapper-base", + "osmosis-std 0.15.2", + "osmosis-test-tube", +] + +[[package]] +name = "mars-v3-zapper-base" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 1.0.1", + "mars-owner", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "mars-v3-zapper-osmosis" +version = "1.0.0" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cw-utils 1.0.1", + "mars-owner", + "mars-v3-zapper-base", "osmosis-std 0.15.2", "osmosis-test-tube", ] @@ -1685,7 +1711,7 @@ dependencies = [ [[package]] name = "osmosis-std" version = "0.15.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#b0860d063c0b59cb5d5b3fb4233bb2784185bb4c" +source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#a139d47ba298c94ebff462572b53c43fae0483e2" dependencies = [ "chrono", "cosmwasm-std", @@ -1712,7 +1738,7 @@ dependencies = [ [[package]] name = "osmosis-std-derive" version = "0.15.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#b0860d063c0b59cb5d5b3fb4233bb2784185bb4c" +source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#a139d47ba298c94ebff462572b53c43fae0483e2" dependencies = [ "itertools", "proc-macro2", @@ -1722,8 +1748,8 @@ dependencies = [ [[package]] name = "osmosis-test-tube" -version = "14.1.1" -source = "git+https://github.com/osmosis-labs/test-tube?branch=main#ae48e239edcf7d0bd7fddd789d2078bd15ef9b2a" +version = "15.1.0" +source = "git+https://github.com/osmosis-labs/test-tube?branch=main#360dd44310d0ea0a14e899c02ab3a4eb716d8404" dependencies = [ "base64", "bindgen", @@ -2459,8 +2485,8 @@ dependencies = [ [[package]] name = "test-tube" -version = "0.1.1" -source = "git+https://github.com/osmosis-labs/test-tube?branch=main#ae48e239edcf7d0bd7fddd789d2078bd15ef9b2a" +version = "0.1.2" +source = "git+https://github.com/osmosis-labs/test-tube?branch=main#360dd44310d0ea0a14e899c02ab3a4eb716d8404" dependencies = [ "base64", "cosmrs", diff --git a/Cargo.toml b/Cargo.toml index 14c622291..e4b13701e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,8 @@ members = [ "contracts/account-nft", "contracts/credit-manager", "contracts/swapper/*", - "contracts/zapper/*", + "contracts/v2-zapper/*", + "contracts/v3-zapper/*", "contracts/health", # mock contracts @@ -35,9 +36,9 @@ documentation = "https://docs.marsprotocol.io/" keywords = ["mars", "cosmos", "cosmwasm"] [workspace.dependencies] -anyhow = "1.0.70" -cosmwasm-schema = "1.2.4" -cosmwasm-std = "1.2.4" +anyhow = "1.0.71" +cosmwasm-schema = "1.2.5" +cosmwasm-std = "1.2.5" cw2 = "1.0.1" cw721 = "0.16.0" cw721-base = { version = "0.16.0", features = ["library"] } @@ -58,7 +59,7 @@ wasm-bindgen = "0.2.84" # packages cw-dex = { version = "0.1.3", features = ["osmosis"] } -cw-vault-standard = { version = "0.2.0", features = ["lockup", "force-unlock"] } +cw-vault-standard = { version = "0.3.0", features = ["lockup", "force-unlock"] } mars-rover-health-computer = { version = "1.0.0", path = "./packages/health-computer" } mars-rover-health-types = { version = "1.0.0", path = "./packages/health-types" } mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "00301d60c38af09d8eb7980355009e2f00c6f41f" } @@ -67,10 +68,11 @@ mars-owner = { version = "1.1.0", features = ["emergency-owner"] mars-rover = { version = "1.0.0", path = "./packages/rover" } # contracts -mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-rover-health = { version = "1.0.0", path = "./contracts/health" } -mars-swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } -mars-zapper-base = { version = "1.0.0", path = "./contracts/zapper/base" } +mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } +mars-rover-health = { version = "1.0.0", path = "./contracts/health" } +mars-swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } +mars-v2-zapper-base = { version = "1.0.0", path = "./contracts/v2-zapper/base" } +mars-v3-zapper-base = { version = "1.0.0", path = "./contracts/v3-zapper/base" } # mocks mars-mock-credit-manager = { version = "1.0.0", path = "./contracts/mock-credit-manager", features = ["library"] } @@ -79,7 +81,7 @@ mars-mock-red-bank = { version = "1.0.0", path = "./contracts/mock-red-ban mars-mock-vault = { version = "1.0.0", path = "./contracts/mock-vault", features = ["library"] } mars-mock-rover-health = { version = "1.0.0", path = "./contracts/mock-health", features = ["library"] } mars-swapper-mock = { version = "1.0.0", path = "./contracts/swapper/mock", features = ["library"] } -mars-zapper-mock = { version = "1.0.0", path = "./contracts/zapper/mock", features = ["library"] } +mars-v2-zapper-mock = { version = "1.0.0", path = "./contracts/v2-zapper/mock", features = ["library"] } [profile.release] codegen-units = 1 diff --git a/Makefile.toml b/Makefile.toml index 2fc4c9e8b..0d083641f 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -17,9 +17,9 @@ args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked"] [tasks.rust-optimizer] script = """ if [[ $(arch) == "arm64" ]]; then - image="cosmwasm/workspace-optimizer-arm64:0.12.11" + image="cosmwasm/workspace-optimizer-arm64:0.12.13" else - image="cosmwasm/workspace-optimizer:0.12.11" + image="cosmwasm/workspace-optimizer:0.12.13" fi docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index a570ee106..f241e2806 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -36,12 +36,12 @@ mars-rover = { workspace = true } mars-rover-health-types = { workspace = true } [dev-dependencies] -anyhow = { workspace = true } -cw-multi-test = { workspace = true } -itertools = { workspace = true } -mars-mock-oracle = { workspace = true } -mars-mock-red-bank = { workspace = true } -mars-mock-vault = { workspace = true } -mars-rover-health = { workspace = true } -mars-swapper-mock = { workspace = true } -mars-zapper-mock = { workspace = true } +anyhow = { workspace = true } +cw-multi-test = { workspace = true } +itertools = { workspace = true } +mars-mock-oracle = { workspace = true } +mars-mock-red-bank = { workspace = true } +mars-mock-vault = { workspace = true } +mars-rover-health = { workspace = true } +mars-swapper-mock = { workspace = true } +mars-v2-zapper-mock = { workspace = true } diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index 03cdff63d..81e456e1c 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -60,11 +60,11 @@ pub fn mock_swapper_contract() -> Box> { Box::new(contract) } -pub fn mock_zapper_contract() -> Box> { +pub fn mock_v2_zapper_contract() -> Box> { let contract = ContractWrapper::new( - mars_zapper_mock::contract::execute, - mars_zapper_mock::contract::instantiate, - mars_zapper_mock::contract::query, + mars_v2_zapper_mock::contract::execute, + mars_v2_zapper_mock::contract::instantiate, + mars_v2_zapper_mock::contract::query, ); Box::new(contract) } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 9c74683b7..d248c94e4 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -62,8 +62,8 @@ use mars_rover_health_types::{ use crate::helpers::{ lp_token_info, mock_account_nft_contract, mock_health_contract, mock_oracle_contract, - mock_red_bank_contract, mock_rover_contract, mock_swapper_contract, mock_vault_contract, - mock_zapper_contract, AccountToFund, CoinInfo, VaultTestInfo, + mock_red_bank_contract, mock_rover_contract, mock_swapper_contract, mock_v2_zapper_contract, + mock_vault_contract, AccountToFund, CoinInfo, VaultTestInfo, }; pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000); @@ -970,7 +970,7 @@ impl MockEnvBuilder { } fn deploy_zapper(&mut self, oracle: &OracleUnchecked) -> AnyResult { - let code_id = self.app.store_code(mock_zapper_contract()); + let code_id = self.app.store_code(mock_v2_zapper_contract()); let lp_token = lp_token_info(); let addr = self.app.instantiate_contract( code_id, diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs index 051f30f3e..af1501920 100644 --- a/contracts/credit-manager/tests/test_zap_provide.rs +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -8,7 +8,7 @@ use mars_rover::{ ActionAmount, ActionCoin, }, }; -use mars_zapper_mock::{contract::STARTING_LP_POOL_TOKENS, error::ContractError}; +use mars_v2_zapper_mock::{contract::STARTING_LP_POOL_TOKENS, error::ContractError}; use crate::helpers::{ assert_err, get_coin, lp_token_info, uatom_info, ujake_info, uosmo_info, AccountToFund, MockEnv, diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index 16d490996..85510d932 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -9,7 +9,7 @@ use mars_rover::{ instantiate::ConfigUpdates, }, }; -use mars_zapper_mock::contract::STARTING_LP_POOL_TOKENS; +use mars_v2_zapper_mock::contract::STARTING_LP_POOL_TOKENS; use crate::helpers::{ assert_err, get_coin, lp_token_info, uatom_info, uosmo_info, AccountToFund, MockEnv, diff --git a/contracts/mock-vault/src/contract.rs b/contracts/mock-vault/src/contract.rs index b49f815f6..3769519d2 100644 --- a/contracts/mock-vault/src/contract.rs +++ b/contracts/mock-vault/src/contract.rs @@ -72,6 +72,9 @@ pub fn execute( LockupExecuteMsg::Unlock { .. } => request_unlock(deps, env, info), + LockupExecuteMsg::EmergencyUnlock { + .. + } => unimplemented!(), }, ExtensionExecuteMsg::ForceUnlock(force_msg) => match force_msg { ForceUnlockExecuteMsg::ForceRedeem { diff --git a/contracts/mock-vault/src/unlock.rs b/contracts/mock-vault/src/unlock.rs index a8ed1fb83..68b9d5b22 100644 --- a/contracts/mock-vault/src/unlock.rs +++ b/contracts/mock-vault/src/unlock.rs @@ -61,7 +61,7 @@ pub fn withdraw_unlocked( let matching_position = lockups.iter().find(|p| p.id == id).ok_or(ContractError::UnlockRequired {})?.clone(); - if &matching_position.owner != sender { + if matching_position.owner != sender { return Err(ContractError::Unauthorized {}); } diff --git a/contracts/zapper/base/Cargo.toml b/contracts/v2-zapper/base/Cargo.toml similarity index 95% rename from contracts/zapper/base/Cargo.toml rename to contracts/v2-zapper/base/Cargo.toml index 4b59a42ed..7e8a1fc1e 100644 --- a/contracts/zapper/base/Cargo.toml +++ b/contracts/v2-zapper/base/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mars-zapper-base" +name = "mars-v2-zapper-base" version = { workspace = true } authors = { workspace = true } license = { workspace = true } diff --git a/contracts/zapper/base/examples/schema.rs b/contracts/v2-zapper/base/examples/schema.rs similarity index 71% rename from contracts/zapper/base/examples/schema.rs rename to contracts/v2-zapper/base/examples/schema.rs index b38f8cc09..1d5ad9034 100644 --- a/contracts/zapper/base/examples/schema.rs +++ b/contracts/v2-zapper/base/examples/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use mars_zapper_base::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_v2_zapper_base::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/zapper/base/src/contract.rs b/contracts/v2-zapper/base/src/contract.rs similarity index 100% rename from contracts/zapper/base/src/contract.rs rename to contracts/v2-zapper/base/src/contract.rs diff --git a/contracts/zapper/base/src/error.rs b/contracts/v2-zapper/base/src/error.rs similarity index 100% rename from contracts/zapper/base/src/error.rs rename to contracts/v2-zapper/base/src/error.rs diff --git a/contracts/zapper/base/src/helpers.rs b/contracts/v2-zapper/base/src/helpers.rs similarity index 100% rename from contracts/zapper/base/src/helpers.rs rename to contracts/v2-zapper/base/src/helpers.rs diff --git a/contracts/zapper/base/src/lib.rs b/contracts/v2-zapper/base/src/lib.rs similarity index 100% rename from contracts/zapper/base/src/lib.rs rename to contracts/v2-zapper/base/src/lib.rs diff --git a/contracts/zapper/base/src/msg.rs b/contracts/v2-zapper/base/src/msg.rs similarity index 100% rename from contracts/zapper/base/src/msg.rs rename to contracts/v2-zapper/base/src/msg.rs diff --git a/contracts/zapper/base/src/traits.rs b/contracts/v2-zapper/base/src/traits.rs similarity index 100% rename from contracts/zapper/base/src/traits.rs rename to contracts/v2-zapper/base/src/traits.rs diff --git a/contracts/zapper/mock/Cargo.toml b/contracts/v2-zapper/mock/Cargo.toml similarity index 95% rename from contracts/zapper/mock/Cargo.toml rename to contracts/v2-zapper/mock/Cargo.toml index 9be0a109b..81a7abbef 100644 --- a/contracts/zapper/mock/Cargo.toml +++ b/contracts/v2-zapper/mock/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mars-zapper-mock" +name = "mars-v2-zapper-mock" version = { workspace = true } authors = { workspace = true } license = { workspace = true } diff --git a/contracts/zapper/mock/examples/schema.rs b/contracts/v2-zapper/mock/examples/schema.rs similarity index 100% rename from contracts/zapper/mock/examples/schema.rs rename to contracts/v2-zapper/mock/examples/schema.rs diff --git a/contracts/zapper/mock/src/contract.rs b/contracts/v2-zapper/mock/src/contract.rs similarity index 100% rename from contracts/zapper/mock/src/contract.rs rename to contracts/v2-zapper/mock/src/contract.rs diff --git a/contracts/zapper/mock/src/error.rs b/contracts/v2-zapper/mock/src/error.rs similarity index 100% rename from contracts/zapper/mock/src/error.rs rename to contracts/v2-zapper/mock/src/error.rs diff --git a/contracts/zapper/mock/src/execute.rs b/contracts/v2-zapper/mock/src/execute.rs similarity index 100% rename from contracts/zapper/mock/src/execute.rs rename to contracts/v2-zapper/mock/src/execute.rs diff --git a/contracts/zapper/mock/src/lib.rs b/contracts/v2-zapper/mock/src/lib.rs similarity index 100% rename from contracts/zapper/mock/src/lib.rs rename to contracts/v2-zapper/mock/src/lib.rs diff --git a/contracts/zapper/mock/src/query.rs b/contracts/v2-zapper/mock/src/query.rs similarity index 100% rename from contracts/zapper/mock/src/query.rs rename to contracts/v2-zapper/mock/src/query.rs diff --git a/contracts/zapper/mock/src/state.rs b/contracts/v2-zapper/mock/src/state.rs similarity index 100% rename from contracts/zapper/mock/src/state.rs rename to contracts/v2-zapper/mock/src/state.rs diff --git a/contracts/v2-zapper/osmosis/Cargo.toml b/contracts/v2-zapper/osmosis/Cargo.toml new file mode 100644 index 000000000..e7c8b5ad6 --- /dev/null +++ b/contracts/v2-zapper/osmosis/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "mars-v2-zapper-osmosis" +version = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw-dex = { workspace = true } +mars-v2-zapper-base = { workspace = true } + +[dev-dependencies] +cw-utils = { workspace = true } +osmosis-std = { workspace = true } +osmosis-test-tube = { workspace = true } diff --git a/contracts/zapper/osmosis/src/contract.rs b/contracts/v2-zapper/osmosis/src/contract.rs similarity index 92% rename from contracts/zapper/osmosis/src/contract.rs rename to contracts/v2-zapper/osmosis/src/contract.rs index 598b3d82a..05e9cf282 100644 --- a/contracts/zapper/osmosis/src/contract.rs +++ b/contracts/v2-zapper/osmosis/src/contract.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use cw2::set_contract_version; -use mars_zapper_base::{ContractError, ExecuteMsg, InstantiateMsg, QueryMsg, ZapperBase}; +use mars_v2_zapper_base::{ContractError, ExecuteMsg, InstantiateMsg, QueryMsg, ZapperBase}; use crate::lp_pool::OsmosisLpPool; diff --git a/contracts/zapper/osmosis/src/lib.rs b/contracts/v2-zapper/osmosis/src/lib.rs similarity index 100% rename from contracts/zapper/osmosis/src/lib.rs rename to contracts/v2-zapper/osmosis/src/lib.rs diff --git a/contracts/zapper/osmosis/src/lp_pool.rs b/contracts/v2-zapper/osmosis/src/lp_pool.rs similarity index 97% rename from contracts/zapper/osmosis/src/lp_pool.rs rename to contracts/v2-zapper/osmosis/src/lp_pool.rs index 5dabddc33..825e6b141 100644 --- a/contracts/zapper/osmosis/src/lp_pool.rs +++ b/contracts/v2-zapper/osmosis/src/lp_pool.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use cosmwasm_std::Deps; use cw_dex::{osmosis::OsmosisPool, traits::Pool, CwDexError}; -use mars_zapper_base::LpPool; +use mars_v2_zapper_base::LpPool; pub struct OsmosisLpPool {} diff --git a/contracts/zapper/osmosis/tests/helpers/mod.rs b/contracts/v2-zapper/osmosis/tests/helpers/mod.rs similarity index 100% rename from contracts/zapper/osmosis/tests/helpers/mod.rs rename to contracts/v2-zapper/osmosis/tests/helpers/mod.rs diff --git a/contracts/zapper/osmosis/tests/helpers/utils.rs b/contracts/v2-zapper/osmosis/tests/helpers/utils.rs similarity index 68% rename from contracts/zapper/osmosis/tests/helpers/utils.rs rename to contracts/v2-zapper/osmosis/tests/helpers/utils.rs index 76a971414..e1145bd9b 100644 --- a/contracts/zapper/osmosis/tests/helpers/utils.rs +++ b/contracts/v2-zapper/osmosis/tests/helpers/utils.rs @@ -1,6 +1,6 @@ use std::{fmt::Display, str::FromStr}; -use mars_zapper_base::InstantiateMsg; +use mars_v2_zapper_base::InstantiateMsg; use osmosis_test_tube::{ cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest, Bank, OsmosisTestApp, RunnerError, SigningAccount, Wasm, @@ -8,17 +8,30 @@ use osmosis_test_tube::{ const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -pub fn wasm_file() -> String { +pub fn wasm_file() -> Vec { let artifacts_dir = std::env::var("ARTIFACTS_DIR_PATH").unwrap_or_else(|_| "artifacts".to_string()); let snaked_name = CONTRACT_NAME.replace('-', "_"); - format!("../../../{artifacts_dir}/{snaked_name}.wasm") + let relative_dir = format!("../../../{artifacts_dir}"); + + let wasm_file_path = format!("{relative_dir}/{snaked_name}.wasm"); + + match std::fs::read(wasm_file_path.clone()) { + Ok(bytes) => { + println!("{wasm_file_path}"); + bytes + } + // Retry if in arch64 environment + Err(_) => { + let alt_file_path = format!("{relative_dir}/{snaked_name}-aarch64.wasm"); + println!("{}", alt_file_path); + std::fs::read(alt_file_path).unwrap() + } + } } pub fn instantiate_contract(wasm: &Wasm, owner: &SigningAccount) -> String { - println!("WASM name: {}", wasm_file()); - let wasm_byte_code = std::fs::read(wasm_file()).unwrap(); - let code_id = wasm.store_code(&wasm_byte_code, None, owner).unwrap().data.code_id; + let code_id = wasm.store_code(&wasm_file(), None, owner).unwrap().data.code_id; wasm.instantiate(code_id, &InstantiateMsg {}, None, Some("zapper-osmosis-contract"), &[], owner) .unwrap() diff --git a/contracts/zapper/osmosis/tests/test_callback.rs b/contracts/v2-zapper/osmosis/tests/test_callback.rs similarity index 93% rename from contracts/zapper/osmosis/tests/test_callback.rs rename to contracts/v2-zapper/osmosis/tests/test_callback.rs index bbb5a57f6..e2ffb13c0 100644 --- a/contracts/zapper/osmosis/tests/test_callback.rs +++ b/contracts/v2-zapper/osmosis/tests/test_callback.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{coin, Addr, Coin}; -use mars_zapper_base::{CallbackMsg, ContractError, ExecuteMsg}; +use mars_v2_zapper_base::{CallbackMsg, ContractError, ExecuteMsg}; use osmosis_test_tube::{Account, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract}; diff --git a/contracts/zapper/osmosis/tests/test_v2_provide_liquidity.rs b/contracts/v2-zapper/osmosis/tests/test_provide_liquidity.rs similarity index 99% rename from contracts/zapper/osmosis/tests/test_v2_provide_liquidity.rs rename to contracts/v2-zapper/osmosis/tests/test_provide_liquidity.rs index f5e52a039..3b1b38b4c 100644 --- a/contracts/zapper/osmosis/tests/test_v2_provide_liquidity.rs +++ b/contracts/v2-zapper/osmosis/tests/test_provide_liquidity.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; -use mars_zapper_base::{ExecuteMsg, QueryMsg}; +use mars_v2_zapper_base::{ExecuteMsg, QueryMsg}; use osmosis_test_tube::{Account, Bank, FeeSetting, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract, query_balance}; diff --git a/contracts/zapper/osmosis/tests/test_v2_queries.rs b/contracts/v2-zapper/osmosis/tests/test_queries.rs similarity index 99% rename from contracts/zapper/osmosis/tests/test_v2_queries.rs rename to contracts/v2-zapper/osmosis/tests/test_queries.rs index 17167d2f2..e6b164aca 100644 --- a/contracts/zapper/osmosis/tests/test_v2_queries.rs +++ b/contracts/v2-zapper/osmosis/tests/test_queries.rs @@ -2,7 +2,7 @@ use std::{ops::Div, str::FromStr}; use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; -use mars_zapper_base::QueryMsg; +use mars_v2_zapper_base::QueryMsg; use osmosis_test_tube::{Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract}; diff --git a/contracts/zapper/osmosis/tests/test_v2_withdraw_liquidity.rs b/contracts/v2-zapper/osmosis/tests/test_withdraw_liquidity.rs similarity index 99% rename from contracts/zapper/osmosis/tests/test_v2_withdraw_liquidity.rs rename to contracts/v2-zapper/osmosis/tests/test_withdraw_liquidity.rs index 72f4f8f26..e936dbcdf 100644 --- a/contracts/zapper/osmosis/tests/test_v2_withdraw_liquidity.rs +++ b/contracts/v2-zapper/osmosis/tests/test_withdraw_liquidity.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; use cw_utils::PaymentError; -use mars_zapper_base::{ContractError, ExecuteMsg, QueryMsg}; +use mars_v2_zapper_base::{ContractError, ExecuteMsg, QueryMsg}; use osmosis_test_tube::{Account, Bank, FeeSetting, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract, query_balance}; diff --git a/contracts/v3-zapper/base/Cargo.toml b/contracts/v3-zapper/base/Cargo.toml new file mode 100644 index 000000000..ce820518f --- /dev/null +++ b/contracts/v3-zapper/base/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "mars-v3-zapper-base" +version = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +mars-owner = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/v3-zapper/base/examples/schema.rs b/contracts/v3-zapper/base/examples/schema.rs new file mode 100644 index 000000000..96558c085 --- /dev/null +++ b/contracts/v3-zapper/base/examples/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use mars_v3_zapper_base::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/v3-zapper/base/src/contract.rs b/contracts/v3-zapper/base/src/contract.rs new file mode 100644 index 000000000..521439ac7 --- /dev/null +++ b/contracts/v3-zapper/base/src/contract.rs @@ -0,0 +1,171 @@ +use std::marker::PhantomData; + +use cosmwasm_std::{ + to_binary, Addr, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Env, Event, MessageInfo, Reply, + Response, StdError, StdResult, SubMsg, WasmMsg, +}; +use cw2::set_contract_version; +use mars_owner::OwnerInit::SetInitialOwner; + +use crate::{ + error::{ContractError, ContractError::Unauthorized, ContractResult}, + msg::{CallbackMsg, ExecuteMsg, InstantiateMsg, NewPositionRequest, QueryMsg}, + state::OWNER, + traits::{OptionFilter, PositionManager}, + utils::assert_exact_funds_sent, +}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub const V3_POSITION_CREATED_EVENT_TYPE: &str = "v3_position_created"; +pub const V3_POSITION_ATTR_KEY: &str = "position_id"; +pub const REFUND_EVENT_TYPE: &str = "execute_callback_refund_coin"; +pub const REFUND_AMOUNT_ATTR_KEY: &str = "coins_returned"; +pub const REFUND_RECIPIENT_ATTR_KEY: &str = "recipient"; + +pub const CREATE_POSITION_REPLY_ID: u64 = 1001; + +pub struct V3ZapperBase +where + M: PositionManager, +{ + pub position_manager: PhantomData, +} + +impl Default for V3ZapperBase +where + M: PositionManager, +{ + fn default() -> Self { + Self { + position_manager: PhantomData, + } + } +} + +impl V3ZapperBase +where + M: PositionManager, +{ + pub fn instantiate(&self, deps: DepsMut, msg: InstantiateMsg) -> ContractResult { + set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; + + OWNER.initialize( + deps.storage, + deps.api, + SetInitialOwner { + owner: msg.owner, + }, + )?; + + Ok(Response::default()) + } + + pub fn execute( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, + ) -> ContractResult { + match msg { + ExecuteMsg::CreatePosition(p) => self.create_position(deps, env, info, p), + ExecuteMsg::UpdateOwner(update) => Ok(OWNER.update(deps, info, update)?), + ExecuteMsg::Callback(msg) => self.handle_callback(deps, env, info, msg), + } + } + + fn handle_callback( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: CallbackMsg, + ) -> ContractResult { + if info.sender != env.contract.address { + return Err(Unauthorized); + } + match msg { + CallbackMsg::RefundCoin { + recipient, + denoms, + } => self.refund_coin(deps, env, recipient, &denoms), + } + } + + pub fn query(&self, deps: Deps, _: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Owner {} => to_binary(&OWNER.query(deps.storage)?), + } + } + + pub fn reply(&self, deps: DepsMut, env: Env, reply: Reply) -> ContractResult { + let response = reply.result.into_result().map_err(StdError::generic_err)?; + let position_id = match reply.id { + CREATE_POSITION_REPLY_ID => M::parse_position_id(deps, env, response)?, + id => return Err(ContractError::ReplyError(format!("reply id {id} is not valid"))), + }; + let event = Event::new(V3_POSITION_CREATED_EVENT_TYPE.to_string()) + .add_attribute(V3_POSITION_ATTR_KEY.to_string(), position_id); + Ok(Response::new().add_event(event)) + } + + pub fn create_position( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + request: NewPositionRequest, + ) -> ContractResult { + OWNER.assert_owner(deps.storage, &info.sender)?; + + let request_coins = vec![&request.token_desired0, &request.token_desired1].only_some(); + assert_exact_funds_sent(&info, &request_coins)?; + + // Creating positions do not guarantee all funds will be used. Refund the leftovers. + let refund_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + funds: vec![], + msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::RefundCoin { + recipient: info.sender, + denoms: request_coins.iter().map(|c| c.denom.clone()).collect(), + }))?, + }); + + let create_msg = M::create_new_position(deps, env, request)?; + let create_submsg = SubMsg::reply_on_success(create_msg, CREATE_POSITION_REPLY_ID); + + Ok(Response::new().add_submessage(create_submsg).add_message(refund_msg)) + } + + pub fn refund_coin( + &self, + deps: DepsMut, + env: Env, + recipient: Addr, + denoms: &[String], + ) -> ContractResult { + let mut coins_to_return = vec![]; + for denom in denoms { + let balance = deps.querier.query_balance(env.contract.address.clone(), denom)?; + if !balance.amount.is_zero() { + coins_to_return.push(balance) + } + } + + let coins_refunded = + coins_to_return.iter().map(|c| c.to_string()).collect::>().join(", "); + + let transfer_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: recipient.to_string(), + amount: coins_to_return, + }); + + let event = Event::new(REFUND_EVENT_TYPE) + .add_attribute(REFUND_AMOUNT_ATTR_KEY, coins_refunded) + .add_attribute(REFUND_RECIPIENT_ATTR_KEY, recipient); + + Ok(Response::new().add_message(transfer_msg).add_event(event)) + } +} diff --git a/contracts/v3-zapper/base/src/error.rs b/contracts/v3-zapper/base/src/error.rs new file mode 100644 index 000000000..37e09022d --- /dev/null +++ b/contracts/v3-zapper/base/src/error.rs @@ -0,0 +1,26 @@ +use cosmwasm_std::StdError; +use mars_owner::OwnerError; +use thiserror::Error; + +pub type ContractResult = Result; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("Sent fund mismatch. Expected: {expected:?}, received {received:?}")] + FundsMismatch { + expected: String, + received: String, + }, + + #[error("{0}")] + OwnerError(#[from] OwnerError), + + #[error("Submessage Reply Error: {0}")] + ReplyError(String), + + #[error("{0}")] + Std(#[from] StdError), + + #[error("Caller not permitted to perform action")] + Unauthorized, +} diff --git a/contracts/v3-zapper/base/src/lib.rs b/contracts/v3-zapper/base/src/lib.rs new file mode 100644 index 000000000..a51ef6eb3 --- /dev/null +++ b/contracts/v3-zapper/base/src/lib.rs @@ -0,0 +1,6 @@ +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; +pub mod traits; +pub mod utils; diff --git a/contracts/v3-zapper/base/src/msg.rs b/contracts/v3-zapper/base/src/msg.rs new file mode 100644 index 000000000..88dd9445f --- /dev/null +++ b/contracts/v3-zapper/base/src/msg.rs @@ -0,0 +1,47 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Coin}; +use mars_owner::OwnerUpdate; + +#[cw_serde] +pub struct InstantiateMsg { + pub owner: String, +} + +#[cw_serde] +pub struct NewPositionRequest { + pub pool_id: u64, + pub lower_tick: i64, + pub upper_tick: i64, + pub token_desired0: Option, + pub token_desired1: Option, + pub token_min_amount0: String, + pub token_min_amount1: String, +} + +#[cw_serde] +pub enum ExecuteMsg { + /// Expects the corresponding tokens to be sent in Funds. The position created will be owned + /// by the zapper contract itself. Consumer should expect an event with + /// V3_POSITION_CREATED_EVENT_TYPE & V3_POSITION_ATTR_KEY, used to parse the id of the position + /// created. In the event not all funds are issued, the remaining is refunded to the caller. + CreatePosition(NewPositionRequest), + UpdateOwner(OwnerUpdate), + Callback(CallbackMsg), +} + +#[cw_serde] +pub enum CallbackMsg { + RefundCoin { + recipient: Addr, + denoms: Vec, + }, +} + +impl CallbackMsg {} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(mars_owner::OwnerResponse)] + Owner {}, +} diff --git a/contracts/v3-zapper/base/src/state.rs b/contracts/v3-zapper/base/src/state.rs new file mode 100644 index 000000000..0c0e31ebd --- /dev/null +++ b/contracts/v3-zapper/base/src/state.rs @@ -0,0 +1,3 @@ +use mars_owner::Owner; + +pub const OWNER: Owner = Owner::new("owner"); diff --git a/contracts/v3-zapper/base/src/traits.rs b/contracts/v3-zapper/base/src/traits.rs new file mode 100644 index 000000000..1c11985e9 --- /dev/null +++ b/contracts/v3-zapper/base/src/traits.rs @@ -0,0 +1,30 @@ +use cosmwasm_std::{Coin, CosmosMsg, DepsMut, Env, SubMsgResponse}; + +use crate::{error::ContractResult, msg::NewPositionRequest}; + +pub trait PositionManager { + /// Owner should be the zapper contract itself (not the user) + fn create_new_position( + deps: DepsMut, + env: Env, + request: NewPositionRequest, + ) -> ContractResult; + + /// Responsible for parsing the reply message to acquire the id of + /// the newly created position + fn parse_position_id( + deps: DepsMut, + env: Env, + response: SubMsgResponse, + ) -> ContractResult; +} + +pub trait OptionFilter { + fn only_some(&self) -> Vec; +} + +impl OptionFilter for Vec<&Option> { + fn only_some(&self) -> Vec { + self.iter().filter_map(|x| x.as_ref()).cloned().collect() + } +} diff --git a/contracts/v3-zapper/base/src/utils.rs b/contracts/v3-zapper/base/src/utils.rs new file mode 100644 index 000000000..db356b4c7 --- /dev/null +++ b/contracts/v3-zapper/base/src/utils.rs @@ -0,0 +1,18 @@ +use cosmwasm_std::{Coin, MessageInfo}; + +use crate::error::{ContractError, ContractResult}; + +/// Assert that fund of exactly the same type and amount was sent along with a message +pub fn assert_exact_funds_sent(info: &MessageInfo, expected: &[Coin]) -> ContractResult<()> { + let same_quantity = info.funds.len() == expected.len(); + let all_expected_in_funds = expected.iter().all(|e| info.funds.iter().any(|i| e == i)); + + if !same_quantity || !all_expected_in_funds { + return Err(ContractError::FundsMismatch { + expected: expected.iter().map(|c| c.to_string()).collect::>().join(", "), + received: info.funds.iter().map(|c| c.to_string()).collect::>().join(", "), + }); + } + + Ok(()) +} diff --git a/contracts/zapper/osmosis/Cargo.toml b/contracts/v3-zapper/osmosis/Cargo.toml similarity index 76% rename from contracts/zapper/osmosis/Cargo.toml rename to contracts/v3-zapper/osmosis/Cargo.toml index 5c9dcefc2..9025fd530 100644 --- a/contracts/zapper/osmosis/Cargo.toml +++ b/contracts/v3-zapper/osmosis/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mars-zapper-osmosis" +name = "mars-v3-zapper-osmosis" version = { workspace = true } authors = { workspace = true } license = { workspace = true } @@ -19,12 +19,12 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -cw-dex = { workspace = true } -mars-zapper-base = { workspace = true } +cosmwasm-std = { workspace = true } +mars-owner = { workspace = true } +mars-v3-zapper-base = { workspace = true } +osmosis-std = { workspace = true } [dev-dependencies] +anyhow = { workspace = true } cw-utils = { workspace = true } -osmosis-std = { workspace = true } osmosis-test-tube = { workspace = true } diff --git a/contracts/v3-zapper/osmosis/src/contract.rs b/contracts/v3-zapper/osmosis/src/contract.rs new file mode 100644 index 000000000..1cad74783 --- /dev/null +++ b/contracts/v3-zapper/osmosis/src/contract.rs @@ -0,0 +1,43 @@ +use cosmwasm_std::{ + entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, +}; +use mars_v3_zapper_base::{ + contract::V3ZapperBase, + error::ContractResult, + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, +}; + +use crate::position_manager::OsmosisPositionManager; + +/// The Osmosis v3 zapper contract inherits logic from the base v3 zapper contract +pub type OsmosisV3Zapper = V3ZapperBase; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> ContractResult { + OsmosisV3Zapper::default().instantiate(deps, msg) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> ContractResult { + OsmosisV3Zapper::default().execute(deps, env, info, msg) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + OsmosisV3Zapper::default().query(deps, env, msg) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, env: Env, reply: Reply) -> ContractResult { + OsmosisV3Zapper::default().reply(deps, env, reply) +} diff --git a/contracts/v3-zapper/osmosis/src/lib.rs b/contracts/v3-zapper/osmosis/src/lib.rs new file mode 100644 index 000000000..5bd14ddb3 --- /dev/null +++ b/contracts/v3-zapper/osmosis/src/lib.rs @@ -0,0 +1,2 @@ +pub mod contract; +pub mod position_manager; diff --git a/contracts/v3-zapper/osmosis/src/position_manager.rs b/contracts/v3-zapper/osmosis/src/position_manager.rs new file mode 100644 index 000000000..c484f77ff --- /dev/null +++ b/contracts/v3-zapper/osmosis/src/position_manager.rs @@ -0,0 +1,54 @@ +use cosmwasm_std::{Coin, CosmosMsg, DepsMut, Env, SubMsgResponse}; +use mars_v3_zapper_base::{ + error::{ContractError::ReplyError, ContractResult}, + msg::NewPositionRequest, + traits::PositionManager, +}; +use osmosis_std::types::{ + cosmos::base::v1beta1, + osmosis::concentratedliquidity::v1beta1::{MsgCreatePosition, MsgCreatePositionResponse}, +}; + +pub struct OsmosisPositionManager {} + +impl PositionManager for OsmosisPositionManager { + fn create_new_position( + _: DepsMut, + env: Env, + p: NewPositionRequest, + ) -> ContractResult { + let create_msg = MsgCreatePosition { + pool_id: p.pool_id, + sender: env.contract.address.to_string(), + lower_tick: p.lower_tick, + upper_tick: p.upper_tick, + token_desired0: p.token_desired0.to_v1beta_coin(), + token_desired1: p.token_desired1.to_v1beta_coin(), + token_min_amount0: p.token_min_amount0, + token_min_amount1: p.token_min_amount1, + }; + Ok(create_msg.into()) + } + + fn parse_position_id(_: DepsMut, _: Env, response: SubMsgResponse) -> ContractResult { + let Some(b) = response.data else { + return Err(ReplyError("No data sent back after creating position".to_string())) + }; + + let parsed_response: MsgCreatePositionResponse = b.try_into()?; + Ok(parsed_response.position_id.to_string()) + } +} + +pub trait ToV1BetaCoin { + fn to_v1beta_coin(&self) -> Option; +} + +impl ToV1BetaCoin for Option { + fn to_v1beta_coin(&self) -> Option { + self.clone().map(|c| v1beta1::Coin { + denom: c.denom, + amount: c.amount.to_string(), + }) + } +} diff --git a/contracts/v3-zapper/osmosis/tests/helpers/assertions.rs b/contracts/v3-zapper/osmosis/tests/helpers/assertions.rs new file mode 100644 index 000000000..71b73dc50 --- /dev/null +++ b/contracts/v3-zapper/osmosis/tests/helpers/assertions.rs @@ -0,0 +1,21 @@ +use std::fmt::Display; + +use osmosis_test_tube::RunnerError; + +pub fn assert_err(actual: RunnerError, expected: impl Display) { + match actual { + RunnerError::ExecuteError { + msg, + } => { + println!("ExecuteError, msg: {msg}"); + assert!(msg.contains(&format!("{expected}"))) + } + RunnerError::QueryError { + msg, + } => { + println!("QueryError, msg: {msg}"); + assert!(msg.contains(&format!("{expected}"))) + } + _ => panic!("Unhandled error"), + } +} diff --git a/contracts/v3-zapper/osmosis/tests/helpers/generator.rs b/contracts/v3-zapper/osmosis/tests/helpers/generator.rs new file mode 100644 index 000000000..26e515e8a --- /dev/null +++ b/contracts/v3-zapper/osmosis/tests/helpers/generator.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::coin; +use mars_v3_zapper_base::msg::NewPositionRequest; + +pub fn default_new_position_req() -> NewPositionRequest { + NewPositionRequest { + pool_id: 1, + lower_tick: -1, + upper_tick: 100, + token_desired0: Some(coin(100_000_000, "ujuno")), + token_desired1: Some(coin(100_000_000, "umars")), + token_min_amount0: "10000".to_string(), + token_min_amount1: "10000".to_string(), + } +} diff --git a/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs b/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs new file mode 100644 index 000000000..382502d9f --- /dev/null +++ b/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs @@ -0,0 +1,228 @@ +use std::{mem::take, str::FromStr}; + +use anyhow::Result as AnyResult; +use cosmwasm_std::{coin, Coin}; +use mars_owner::{OwnerResponse, OwnerUpdate}; +use mars_v3_zapper_base::msg::{CallbackMsg, ExecuteMsg, InstantiateMsg, QueryMsg}; +use osmosis_std::types::osmosis::{ + concentratedliquidity::v1beta1::{ + MsgCreateConcentratedPool, PositionWithUnderlyingAssetBreakdown, QueryUserPositionsRequest, + }, + tokenfactory::v1beta1::{MsgCreateDenom, MsgMint}, +}; +use osmosis_test_tube::{ + cosmrs::proto::{ + cosmos::bank::v1beta1::QueryBalanceRequest, cosmwasm::wasm::v1::MsgExecuteContractResponse, + }, + Account, Bank, ConcentratedLiquidity, Module, OsmosisTestApp, RunnerExecuteResult, + SigningAccount, TokenFactory, Wasm, +}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); + +pub struct MockEnv { + pub app: OsmosisTestApp, + pub owner: SigningAccount, + pub zapper: String, +} + +pub struct MockEnvBuilder { + pub app: OsmosisTestApp, +} + +#[allow(clippy::new_ret_no_self)] +impl MockEnv { + pub fn new() -> MockEnvBuilder { + MockEnvBuilder { + app: OsmosisTestApp::new(), + } + } + + //-------------------------------------------------------------------------------------------------- + // Execute Msgs + //-------------------------------------------------------------------------------------------------- + pub fn update_owner( + &mut self, + update: OwnerUpdate, + alt_signer: Option<&SigningAccount>, + ) -> RunnerExecuteResult { + let wasm = Wasm::new(&self.app); + wasm.execute( + &self.zapper, + &ExecuteMsg::UpdateOwner(update), + &[coin(5000, "uosmo")], + alt_signer.unwrap_or(&self.owner), + ) + } + + pub fn create_pool(&mut self, subdenom0: &str, subdenom1: &str) -> (String, String, u64) { + let cl = ConcentratedLiquidity::new(&self.app); + let token_factory = TokenFactory::new(&self.app); + + let denom0 = token_factory + .create_denom( + MsgCreateDenom { + sender: self.owner.address(), + subdenom: subdenom0.to_string(), + }, + &self.owner, + ) + .unwrap() + .data + .new_token_denom; + + let denom1 = token_factory + .create_denom( + MsgCreateDenom { + sender: self.owner.address(), + subdenom: subdenom1.to_string(), + }, + &self.owner, + ) + .unwrap() + .data + .new_token_denom; + + token_factory + .mint( + MsgMint { + sender: self.owner.address(), + amount: Some(Coin::new(100_000_000_000, &denom0).into()), + mint_to_address: self.owner.address(), + }, + &self.owner, + ) + .unwrap(); + + token_factory + .mint( + MsgMint { + sender: self.owner.address(), + amount: Some(Coin::new(100_000_000_000, &denom1).into()), + mint_to_address: self.owner.address(), + }, + &self.owner, + ) + .unwrap(); + + let pool_id = cl + .create_concentrated_pool( + MsgCreateConcentratedPool { + sender: self.owner.address(), + denom0: denom0.clone(), + denom1: denom1.clone(), + tick_spacing: 1, + exponent_at_price_one: "-4".to_string(), + swap_fee: "0".to_string(), + }, + &self.owner, + ) + .unwrap() + .data + .pool_id; + + (denom0, denom1, pool_id) + } + + pub fn callback( + &mut self, + msg: CallbackMsg, + alt_signer: Option<&SigningAccount>, + ) -> RunnerExecuteResult { + let wasm = Wasm::new(&self.app); + wasm.execute( + &self.zapper, + &ExecuteMsg::Callback(msg), + &[coin(5000, "uosmo")], + alt_signer.unwrap_or(&self.owner), + ) + } + + //-------------------------------------------------------------------------------------------------- + // Queries + //-------------------------------------------------------------------------------------------------- + pub fn query_ownership(&self) -> OwnerResponse { + let wasm = Wasm::new(&self.app); + wasm.query(&self.zapper, &QueryMsg::Owner {}).unwrap() + } + + pub fn query_balance(&self, address: &str, denom: &str) -> u128 { + let bank = Bank::new(&self.app); + let str_balance = bank + .query_balance(&QueryBalanceRequest { + address: address.to_string(), + denom: denom.to_string(), + }) + .unwrap() + .balance + .unwrap() + .amount; + u128::from_str(&str_balance).unwrap() + } + + pub fn query_positions(&self, pool_id: u64) -> Vec { + let cl = ConcentratedLiquidity::new(&self.app); + let res = cl + .query_user_positions(&QueryUserPositionsRequest { + address: self.zapper.clone(), + pool_id, + }) + .unwrap(); + res.positions + } +} + +impl MockEnvBuilder { + pub fn build(&mut self) -> AnyResult { + let owner = self.app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); + let zapper = self.instantiate_contract(&owner); + + Ok(MockEnv { + app: take(&mut self.app), + owner, + zapper, + }) + } + + pub fn wasm_file(&self) -> Vec { + let artifacts_dir = + std::env::var("ARTIFACTS_DIR_PATH").unwrap_or_else(|_| "artifacts".to_string()); + let snaked_name = CONTRACT_NAME.replace('-', "_"); + let relative_dir = format!("../../../{artifacts_dir}"); + + let wasm_file_path = format!("{relative_dir}/{snaked_name}.wasm"); + + match std::fs::read(wasm_file_path.clone()) { + Ok(bytes) => { + println!("{wasm_file_path}"); + bytes + } + // Retry if in arch64 environment + Err(_) => { + let alt_file_path = format!("{relative_dir}/{snaked_name}-aarch64.wasm"); + println!("{}", alt_file_path); + std::fs::read(alt_file_path).unwrap() + } + } + } + + pub fn instantiate_contract(&mut self, owner: &SigningAccount) -> String { + let wasm = Wasm::new(&self.app); + let wasm_byte_code = self.wasm_file(); + let code_id = wasm.store_code(&wasm_byte_code, None, owner).unwrap().data.code_id; + + wasm.instantiate( + code_id, + &InstantiateMsg { + owner: owner.address(), + }, + None, + Some("v3-zapper-osmosis-contract"), + &[], + owner, + ) + .unwrap() + .data + .address + } +} diff --git a/contracts/v3-zapper/osmosis/tests/helpers/mod.rs b/contracts/v3-zapper/osmosis/tests/helpers/mod.rs new file mode 100644 index 000000000..976b5a81f --- /dev/null +++ b/contracts/v3-zapper/osmosis/tests/helpers/mod.rs @@ -0,0 +1,5 @@ +pub use self::{assertions::*, generator::*, mock_env::*}; + +mod assertions; +mod generator; +mod mock_env; diff --git a/contracts/v3-zapper/osmosis/tests/test_add_position.rs b/contracts/v3-zapper/osmosis/tests/test_add_position.rs new file mode 100644 index 000000000..00ffd30fb --- /dev/null +++ b/contracts/v3-zapper/osmosis/tests/test_add_position.rs @@ -0,0 +1,372 @@ +use cosmwasm_std::coin; +use mars_v3_zapper_base::{ + contract::{ + REFUND_AMOUNT_ATTR_KEY, REFUND_EVENT_TYPE, V3_POSITION_ATTR_KEY, + V3_POSITION_CREATED_EVENT_TYPE, + }, + msg::{ExecuteMsg, NewPositionRequest}, +}; +use osmosis_test_tube::{Account, Module, Wasm}; + +use crate::helpers::{assert_err, default_new_position_req, MockEnv}; + +pub mod helpers; + +#[test] +fn only_owner_can_add_positions() { + let mock = MockEnv::new().build().unwrap(); + let bad_guy = mock.app.init_account(&[coin(1_000_000, "uosmo")]).unwrap(); + + let wasm = Wasm::new(&mock.app); + let err = wasm + .execute( + &mock.zapper, + &ExecuteMsg::CreatePosition(default_new_position_req()), + &[], + &bad_guy, + ) + .unwrap_err(); + + assert_err(err, "Caller is not owner"); +} + +#[test] +fn must_send_exact_funds() { + let mut mock = MockEnv::new().build().unwrap(); + let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); + + let wasm = Wasm::new(&mock.app); + + let new_position = NewPositionRequest { + pool_id: 1, + lower_tick: -1, + upper_tick: 100, + token_desired0: Some(coin(100_000_000, denom0.clone())), + token_desired1: Some(coin(100_000_000, denom1.clone())), + token_min_amount0: "10000".to_string(), + token_min_amount1: "10000".to_string(), + }; + + let err = wasm + .execute(&mock.zapper, &ExecuteMsg::CreatePosition(new_position.clone()), &[], &mock.owner) + .unwrap_err(); + assert_err(err, "Sent fund mismatch"); + + let err = wasm + .execute( + &mock.zapper, + &ExecuteMsg::CreatePosition(new_position.clone()), + &[new_position.token_desired0.clone().unwrap()], + &mock.owner, + ) + .unwrap_err(); + assert_err(err, "Sent fund mismatch"); + + let err = wasm + .execute( + &mock.zapper, + &ExecuteMsg::CreatePosition(new_position), + &[coin(1000, "uosmo")], + &mock.owner, + ) + .unwrap_err(); + assert_err(err, "Sent fund mismatch"); + + // assert with None as well + let new_position = NewPositionRequest { + pool_id: 1, + lower_tick: -1, + upper_tick: 100, + token_desired0: None, + token_desired1: Some(coin(100_000_000, denom1)), + token_min_amount0: "0".to_string(), + token_min_amount1: "10000".to_string(), + }; + + let err = wasm + .execute( + &mock.zapper, + &ExecuteMsg::CreatePosition(new_position.clone()), + &[coin(100_000_000, denom0), new_position.token_desired1.unwrap()], + &mock.owner, + ) + .unwrap_err(); + assert_err(err, "Sent fund mismatch"); +} + +#[test] +fn add_position_successfully() { + let mut mock = MockEnv::new().build().unwrap(); + let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); + + let starting_balance = 100_000_000_000; + + // assert owner funds before + let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); + assert_eq!(starting_balance, denom0_balance); + let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); + assert_eq!(starting_balance, denom1_balance); + + // assert zapper funds before + let denom0_balance = mock.query_balance(&mock.zapper, &denom0); + assert_eq!(0, denom0_balance); + let denom1_balance = mock.query_balance(&mock.zapper, &denom1); + assert_eq!(0, denom1_balance); + + let wasm = Wasm::new(&mock.app); + + let amount_sent = 100_000_000; + let new_position = NewPositionRequest { + pool_id: 1, + lower_tick: -1, + upper_tick: 100, + token_desired0: Some(coin(amount_sent, denom0.clone())), + token_desired1: Some(coin(amount_sent, denom1.clone())), + token_min_amount0: "10000".to_string(), + token_min_amount1: "10000".to_string(), + }; + let res = wasm + .execute( + &mock.zapper, + &ExecuteMsg::CreatePosition(new_position.clone()), + &[ + new_position.token_desired0.clone().unwrap(), + new_position.token_desired1.clone().unwrap(), + ], + &mock.owner, + ) + .unwrap(); + + // Assert correct event on logs + let event = res + .events + .iter() + .find(|e| e.ty == format!("wasm-{}", V3_POSITION_CREATED_EVENT_TYPE)) + .unwrap(); + let attr = event.attributes.iter().find(|a| a.key == V3_POSITION_ATTR_KEY).unwrap(); + assert_eq!("1", attr.value); + + // assert zapper has that position opened as expected + let positions = mock.query_positions(1); + assert_eq!(1, positions.len()); + let p = positions.first().unwrap(); + let position = p.position.clone().unwrap(); + assert_eq!(1, position.position_id); + assert_eq!(mock.zapper, position.address); + assert_eq!(1, position.pool_id); + assert_eq!(new_position.lower_tick, position.lower_tick); + assert_eq!(new_position.upper_tick, position.upper_tick); + assert_eq!(new_position.token_desired0.unwrap().denom, p.asset0.clone().unwrap().denom); + assert_eq!(new_position.token_desired1.unwrap().denom, p.asset1.clone().unwrap().denom); + + // assert zapper funds after + let denom0_balance = mock.query_balance(&mock.zapper, &denom0); + assert_eq!(0, denom0_balance); + let denom1_balance = mock.query_balance(&mock.zapper, &denom1); + assert_eq!(0, denom1_balance); + + // assert owner funds after + let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); + let position_amount0 = p.asset0.clone().unwrap().amount.parse::().unwrap(); + assert_eq!(starting_balance - position_amount0, denom0_balance); + + let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); + let position_amount1 = p.asset1.clone().unwrap().amount.parse::().unwrap(); + assert_eq!(starting_balance - position_amount1, denom1_balance); +} + +#[test] +fn refunds_are_issued() { + let mut mock = MockEnv::new().build().unwrap(); + let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); + + let starting_balance = 100_000_000_000; + + // assert owner funds before + let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); + assert_eq!(starting_balance, denom0_balance); + let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); + assert_eq!(starting_balance, denom1_balance); + + let wasm = Wasm::new(&mock.app); + + let amount_sent = 10_000_000; + let new_position = NewPositionRequest { + pool_id: 1, + lower_tick: -100, + upper_tick: 100, + token_desired0: Some(coin(amount_sent, denom0.clone())), + token_desired1: Some(coin(amount_sent, denom1.clone())), + token_min_amount0: "10000".to_string(), + token_min_amount1: "10000".to_string(), + }; + + let res = wasm + .execute( + &mock.zapper, + &ExecuteMsg::CreatePosition(new_position.clone()), + &[new_position.token_desired0.unwrap(), new_position.token_desired1.unwrap()], + &mock.owner, + ) + .unwrap(); + + // Zapper should not have a balance after tx + let denom0_balance = mock.query_balance(&mock.zapper, &denom0); + assert_eq!(0, denom0_balance); + let denom1_balance = mock.query_balance(&mock.zapper, &denom1); + assert_eq!(0, denom1_balance); + + // Assert refund event emitted + let refund_amount_a = 8992255u128; + let event = res.events.iter().find(|e| e.ty == format!("wasm-{}", REFUND_EVENT_TYPE)).unwrap(); + let attr = event.attributes.iter().find(|a| a.key == REFUND_AMOUNT_ATTR_KEY).unwrap(); + assert_eq!(format!("{refund_amount_a}{denom1}"), attr.value); + + // No refund on denom0 + let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); + assert_eq!(starting_balance - amount_sent, denom0_balance); + + // assert refund took place for denom1 + let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); + assert_eq!(starting_balance - amount_sent + refund_amount_a, denom1_balance); + + let new_position = NewPositionRequest { + pool_id: 1, + lower_tick: -100, + upper_tick: -20, + token_desired0: Some(coin(amount_sent, denom0.clone())), + token_desired1: Some(coin(amount_sent, denom1.clone())), + token_min_amount0: "0".to_string(), + token_min_amount1: "10000".to_string(), + }; + + let res = wasm + .execute( + &mock.zapper, + &ExecuteMsg::CreatePosition(new_position.clone()), + &[new_position.token_desired0.unwrap(), new_position.token_desired1.unwrap()], + &mock.owner, + ) + .unwrap(); + + // Assert refund event emitted + let refund_amount_b = 10000000u128; + let event = res.events.iter().find(|e| e.ty == format!("wasm-{}", REFUND_EVENT_TYPE)).unwrap(); + let attr = event.attributes.iter().find(|a| a.key == REFUND_AMOUNT_ATTR_KEY).unwrap(); + assert_eq!(format!("{refund_amount_b}{denom0}"), attr.value); + + // Full refund on denom0 + let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); + // Starting balance after first position was created + let balance_before = starting_balance - amount_sent; + assert_eq!(balance_before, denom0_balance); + + // No refund for denom1 + let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); + // Starting balance after first position was created + let balance_before = starting_balance - amount_sent + refund_amount_a; + assert_eq!(balance_before - amount_sent, denom1_balance); +} + +#[test] +fn adding_multiple_increments() { + let mut mock = MockEnv::new().build().unwrap(); + let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); + + let wasm = Wasm::new(&mock.app); + + let new_position = NewPositionRequest { + pool_id: 1, + lower_tick: -1, + upper_tick: 100, + token_desired0: Some(coin(100_000_000, denom0)), + token_desired1: Some(coin(100_000_000, denom1)), + token_min_amount0: "10000".to_string(), + token_min_amount1: "10000".to_string(), + }; + wasm.execute( + &mock.zapper, + &ExecuteMsg::CreatePosition(new_position.clone()), + &[ + new_position.token_desired0.clone().unwrap(), + new_position.token_desired1.clone().unwrap(), + ], + &mock.owner, + ) + .unwrap(); + + wasm.execute( + &mock.zapper, + &ExecuteMsg::CreatePosition(new_position.clone()), + &[ + new_position.token_desired0.clone().unwrap(), + new_position.token_desired1.clone().unwrap(), + ], + &mock.owner, + ) + .unwrap(); + + let res = wasm + .execute( + &mock.zapper, + &ExecuteMsg::CreatePosition(new_position.clone()), + &[new_position.token_desired0.unwrap(), new_position.token_desired1.unwrap()], + &mock.owner, + ) + .unwrap(); + + // Assert incrementing position id on logs + let event = res + .events + .iter() + .find(|e| e.ty == format!("wasm-{}", V3_POSITION_CREATED_EVENT_TYPE)) + .unwrap(); + let attr = event.attributes.iter().find(|a| a.key == V3_POSITION_ATTR_KEY).unwrap(); + assert_eq!("3", attr.value); +} + +#[test] +fn error_rolls_back_state() { + let mut mock = MockEnv::new().build().unwrap(); + let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); + + let starting_balance = 100_000_000_000; + + // assert owner funds before + let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); + assert_eq!(starting_balance, denom0_balance); + let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); + assert_eq!(starting_balance, denom1_balance); + + let wasm = Wasm::new(&mock.app); + + let amount_sent = 100_000_000; + let new_position = NewPositionRequest { + pool_id: 1, + lower_tick: -1, + upper_tick: 100, + token_desired0: Some(coin(amount_sent, denom0.clone())), + token_desired1: Some(coin(amount_sent, denom1.clone())), + token_min_amount0: "10000000000000000".to_string(), + token_min_amount1: "10000".to_string(), + }; + wasm.execute( + &mock.zapper, + &ExecuteMsg::CreatePosition(new_position.clone()), + &[new_position.token_desired0.clone().unwrap(), new_position.token_desired1.unwrap()], + &mock.owner, + ) + .unwrap_err(); + + // assert zapper funds after + let denom0_balance = mock.query_balance(&mock.zapper, &denom0); + assert_eq!(0, denom0_balance); + let denom1_balance = mock.query_balance(&mock.zapper, &denom1); + assert_eq!(0, denom1_balance); + + // assert owner funds after + let denom0_balance = mock.query_balance(&mock.owner.address(), &denom0); + assert_eq!(starting_balance, denom0_balance); + let denom1_balance = mock.query_balance(&mock.owner.address(), &denom1); + assert_eq!(starting_balance, denom1_balance); +} diff --git a/contracts/v3-zapper/osmosis/tests/test_callback.rs b/contracts/v3-zapper/osmosis/tests/test_callback.rs new file mode 100644 index 000000000..2dda27dc3 --- /dev/null +++ b/contracts/v3-zapper/osmosis/tests/test_callback.rs @@ -0,0 +1,24 @@ +use cosmwasm_std::{coin, Addr}; +use mars_v3_zapper_base::msg::CallbackMsg; +use osmosis_test_tube::Account; + +use crate::helpers::{assert_err, MockEnv}; + +pub mod helpers; + +#[test] +fn only_owner_can_invoke_callback() { + let mut mock = MockEnv::new().build().unwrap(); + let bad_guy = mock.app.init_account(&[coin(1_000_000, "uosmo")]).unwrap(); + let err = mock + .callback( + CallbackMsg::RefundCoin { + recipient: Addr::unchecked(bad_guy.address()), + denoms: vec!["xyz".to_string()], + }, + Some(&bad_guy), + ) + .unwrap_err(); + + assert_err(err, "Caller not permitted to perform action"); +} diff --git a/contracts/v3-zapper/osmosis/tests/test_update_owner.rs b/contracts/v3-zapper/osmosis/tests/test_update_owner.rs new file mode 100644 index 000000000..427b57f68 --- /dev/null +++ b/contracts/v3-zapper/osmosis/tests/test_update_owner.rs @@ -0,0 +1,54 @@ +use cosmwasm_std::coin; +use mars_owner::OwnerUpdate; +use osmosis_test_tube::Account; + +use crate::helpers::{assert_err, MockEnv}; + +pub mod helpers; + +#[test] +fn owner_set_on_init() { + let mock = MockEnv::new().build().unwrap(); + assert!(mock.query_ownership().owner.is_some()); +} + +#[test] +fn only_owner_can_update_ownership() { + let mut mock = MockEnv::new().build().unwrap(); + let bad_guy = mock.app.init_account(&[coin(1_000_000, "uosmo")]).unwrap(); + let err = mock + .update_owner( + OwnerUpdate::ProposeNewOwner { + proposed: bad_guy.address(), + }, + Some(&bad_guy), + ) + .unwrap_err(); + + assert_err(err, "Caller is not owner") +} + +#[test] +fn owner_can_be_updated() { + let mut mock = MockEnv::new().build().unwrap(); + + let ownership = mock.query_ownership(); + assert_eq!(ownership.proposed, None); + + let new_owner = mock.app.init_account(&[coin(1_000_000_000, "uosmo")]).unwrap(); + mock.update_owner( + OwnerUpdate::ProposeNewOwner { + proposed: new_owner.address(), + }, + None, + ) + .unwrap(); + + let ownership = mock.query_ownership(); + assert_eq!(ownership.proposed, Some(new_owner.address())); + + mock.update_owner(OwnerUpdate::AcceptProposed {}, Some(&new_owner)).unwrap(); + let ownership = mock.query_ownership(); + assert_eq!(ownership.owner, Some(new_owner.address())); + assert_eq!(ownership.proposed, None); +} diff --git a/contracts/zapper/osmosis/tests/test_v3_add_position.rs b/contracts/zapper/osmosis/tests/test_v3_add_position.rs deleted file mode 100644 index c0324e67d..000000000 --- a/contracts/zapper/osmosis/tests/test_v3_add_position.rs +++ /dev/null @@ -1,125 +0,0 @@ -use cosmwasm_std::{coin, Coin}; -use osmosis_std::types::osmosis::{ - concentratedliquidity::v1beta1::MsgCreateConcentratedPool, tokenfactory::v1beta1::MsgMint, -}; -use osmosis_test_tube::{ - osmosis_std::types::osmosis::tokenfactory::v1beta1::MsgCreateDenom, Account, - ConcentratedLiquidity, Module, OsmosisTestApp, TokenFactory, -}; - -pub mod helpers; - -#[test] -fn add_position() { - let app = OsmosisTestApp::new(); - - let cl = ConcentratedLiquidity::new(&app); - let token_factory = TokenFactory::new(&app); - - let accs = app - .init_accounts(&[coin(1_000_000_000_000, "uatom"), coin(1_000_000_000_000, "uosmo")], 2) - .unwrap(); - let signer = &accs[0]; - - let denom0 = token_factory - .create_denom( - MsgCreateDenom { - sender: signer.address(), - subdenom: "xyz".to_string(), - }, - signer, - ) - .unwrap() - .data - .new_token_denom; - - let denom1 = token_factory - .create_denom( - MsgCreateDenom { - sender: signer.address(), - subdenom: "abc".to_string(), - }, - signer, - ) - .unwrap() - .data - .new_token_denom; - - token_factory - .mint( - MsgMint { - sender: signer.address(), - amount: Some(Coin::new(100_000_000_000, &denom0).into()), - mint_to_address: signer.address(), - }, - signer, - ) - .unwrap(); - - token_factory - .mint( - MsgMint { - sender: signer.address(), - amount: Some(Coin::new(100_000_000_000, &denom1).into()), - mint_to_address: signer.address(), - }, - signer, - ) - .unwrap(); - - let pool_id = cl - .create_concentrated_pool( - MsgCreateConcentratedPool { - sender: signer.address(), - denom0, - denom1, - tick_spacing: 1, - exponent_at_price_one: "-4".to_string(), - swap_fee: "0".to_string(), - }, - signer, - ) - .unwrap() - .data - .pool_id; - - assert_eq!(1, pool_id) - - // TODO: Temporarily broken. Waiting for latest. - // let position_id = cl - // .create_position( - // MsgCreatePosition { - // pool_id, - // sender: signer.address(), - // lower_tick: -1, - // upper_tick: 100, - // token_desired0: Some(v1beta1::Coin { - // denom: denom0.to_string(), - // amount: "9999999999".to_string(), - // }), - // token_desired1: Some(v1beta1::Coin { - // denom: denom1.to_string(), - // amount: "10000000000".to_string(), - // }), - // token_min_amount0: "1".to_string(), - // token_min_amount1: "1".to_string(), - // freeze_duration: None, - // }, - // signer, - // ) - // .unwrap() - // .data - // .position_id; - // - // println!("Position id: {position_id}"); - - // let liquidity = cl - // .query_total_liquidity_for_range(&QueryTotalLiquidityForRangeRequest { - // pool_id, - // }) - // .unwrap(); - - // for l in liquidity { - // println!("liquidity_amount: {}", l.liquidity_amount); - // } -} diff --git a/schema.Makefile.toml b/schema.Makefile.toml index e66ca4e18..737eb5bc9 100644 --- a/schema.Makefile.toml +++ b/schema.Makefile.toml @@ -14,7 +14,8 @@ fn main() -> std::io::Result<()> { "mars-credit-manager", "mars-account-nft", "mars-swapper-base", - "mars-zapper-base", + "mars-v2-zapper-base", + "mars-v3-zapper-base", "mars-mock-red-bank", "mars-mock-vault", "mars-mock-oracle", diff --git a/schemas/mars-mock-vault/mars-mock-vault.json b/schemas/mars-mock-vault/mars-mock-vault.json index 0d4e4056c..8d8b9da27 100644 --- a/schemas/mars-mock-vault/mars-mock-vault.json +++ b/schemas/mars-mock-vault/mars-mock-vault.json @@ -339,6 +339,33 @@ }, "additionalProperties": false }, + { + "description": "EmergencyUnlock is called to initiate unlocking a locked position held by the vault. This call should simply unlock `amount` of vault tokens, without performing any other side effects that might cause the transaction to fail. Such as for example compoundning rewards for an LP position.", + "type": "object", + "required": [ + "emergency_unlock" + ], + "properties": { + "emergency_unlock": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "description": "The amount of vault tokens to unlock.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Withdraw an unlocking position that has finished unlocking.", "type": "object", diff --git a/schemas/mars-zapper-base/mars-zapper-base.json b/schemas/mars-v2-zapper-base/mars-v2-zapper-base.json similarity index 99% rename from schemas/mars-zapper-base/mars-zapper-base.json rename to schemas/mars-v2-zapper-base/mars-v2-zapper-base.json index a4b2c27e5..ee99422c9 100644 --- a/schemas/mars-zapper-base/mars-zapper-base.json +++ b/schemas/mars-v2-zapper-base/mars-v2-zapper-base.json @@ -1,5 +1,5 @@ { - "contract_name": "mars-zapper-base", + "contract_name": "mars-v2-zapper-base", "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { diff --git a/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json b/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json new file mode 100644 index 000000000..114c6a917 --- /dev/null +++ b/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json @@ -0,0 +1,307 @@ +{ + "contract_name": "mars-v3-zapper-base", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "string" + } + }, + "additionalProperties": false + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Expects the corresponding tokens to be sent in Funds. The position created will be owned by the zapper contract itself. Consumer should expect an event with V3_POSITION_CREATED_EVENT_TYPE & V3_POSITION_ATTR_KEY, used to parse the id of the position created. In the event not all funds are issued, the remaining is refunded to the caller.", + "type": "object", + "required": [ + "create_position" + ], + "properties": { + "create_position": { + "$ref": "#/definitions/NewPositionRequest" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_owner" + ], + "properties": { + "update_owner": { + "$ref": "#/definitions/OwnerUpdate" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "callback" + ], + "properties": { + "callback": { + "$ref": "#/definitions/CallbackMsg" + } + }, + "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" + }, + "CallbackMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "refund_coin" + ], + "properties": { + "refund_coin": { + "type": "object", + "required": [ + "denoms", + "recipient" + ], + "properties": { + "denoms": { + "type": "array", + "items": { + "type": "string" + } + }, + "recipient": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "NewPositionRequest": { + "type": "object", + "required": [ + "lower_tick", + "pool_id", + "token_min_amount0", + "token_min_amount1", + "upper_tick" + ], + "properties": { + "lower_tick": { + "type": "integer", + "format": "int64" + }, + "pool_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token_desired0": { + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "token_desired1": { + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "token_min_amount0": { + "type": "string" + }, + "token_min_amount1": { + "type": "string" + }, + "upper_tick": { + "type": "integer", + "format": "int64" + } + }, + "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" + ] + }, + { + "description": "A separate entity managed by Owner that can be used for granting specific emergency powers.", + "type": "object", + "required": [ + "set_emergency_owner" + ], + "properties": { + "set_emergency_owner": { + "type": "object", + "required": [ + "emergency_owner" + ], + "properties": { + "emergency_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove the entity in the Emergency Owner role", + "type": "string", + "enum": [ + "clear_emergency_owner" + ] + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "owner": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OwnerResponse", + "description": "Returned from Owner.query()", + "type": "object", + "required": [ + "abolished", + "initialized" + ], + "properties": { + "abolished": { + "type": "boolean" + }, + "emergency_owner": { + "type": [ + "string", + "null" + ] + }, + "initialized": { + "type": "boolean" + }, + "owner": { + "type": [ + "string", + "null" + ] + }, + "proposed": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + } +} diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 03f426ae3..d56d1b671 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -11,7 +11,7 @@ import { ExecuteMsg as SwapperExecute, InstantiateMsg as SwapperInstantiateMsg, } from '../../types/generated/mars-swapper-base/MarsSwapperBase.types' -import { InstantiateMsg as ZapperInstantiateMsg } from '../../types/generated/mars-zapper-base/MarsZapperBase.types' +import { InstantiateMsg as ZapperInstantiateMsg } from '../../types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types' import { ExecuteMsg as CreditManagerExecute, InstantiateMsg as RoverInstantiateMsg, diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js index 0b53f1b82..bfa4afdd9 100644 --- a/scripts/health/pkg-node/index.js +++ b/scripts/health/pkg-node/index.js @@ -271,17 +271,17 @@ module.exports.__wbindgen_string_get = function (arg0, arg1) { getInt32Memory0()[arg0 / 4 + 0] = ptr0 } -module.exports.__wbindgen_is_string = function (arg0) { - const ret = typeof getObject(arg0) === 'string' - return ret -} - module.exports.__wbindgen_boolean_get = function (arg0) { const v = getObject(arg0) const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 return ret } +module.exports.__wbindgen_is_string = function (arg0) { + const ret = typeof getObject(arg0) === 'string' + return ret +} + module.exports.__wbg_new_abda76e883ba8a5f = function () { const ret = new Error() return addHeapObject(ret) diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index 657f375f844355424adef3c1ed3d436ad196be82..b2e9aaa117afacf76a1500990fa0fb2ca882d34b 100644 GIT binary patch delta 1408 zcmYk33s96*6vy}culrsEWzkh2SY4Au5r`uOb!;oUinWZhjjW7Su@E*f!T!V+ww_On$eFhtZPbA6*oJ28LWBBt;4{?Y zdo;2y(T`iWiQBlNs2?A*7JQ3Zc8>h)9}Ga!Pt*uMRk>BURw!9?pBml&Spk2Pf(IJxNh_^XJB? zJbyd+kQ$OczSO4AY|mV1(qrz)9|J`v)=OggPb)lAS++g90vs@&2!WPeruj;BpXkbYY-~Z+GM4gR?Mfj zJ%_F$zNPJN>d-dt-DXmiQw}Z%b%-U=G(tOdDDw%j3L7JRluTlDj?fX579)LNLQOyh z?DU5jO+m5GMTz1IFInWKXgaL_7BSC7FHx7=>!SSsc)y!wp*$!T#Zz85AxgY7SbP*s zPFWgH!%0pbO&4SJ_6)Bo?+VINFNq;&3J4_<<&$}o<`lyU=%SoGpJqZVdtFJskX9PS z?gdKr7tl!)U6RL^Q7Tlt^*((bsn)75rW}h*;Z#p#mZz%72lcWTK`CN?B^Ah?a^10s zib_h6(*zAz$ttI9qbm-y2gIufXuO=ei`2urMfnAKL~hk6V5B1H`I;tKP+lxmN$RNo z-9ELy^k1Z0tH=*gwHO(qDWbmbt~$}HRMr(bBd>*MaU@;->NONP>4NNb;;2~+ibuBS zjl*!UGY;7@Cm!7lx{uT;k&VLY%}VN@6c0fn|O?$cdk7(0aCD_SNXE%pZ49fm|h z64cPBV`R67VZQiv0@6i#3OuoWxX*a!zK$Mk41|-VDCJI>*JIM0c0RBQL8^mFDfE6%5D`R=uoN;NZtI$Z*s6sjFaU9k1If_ql1V5mL zeSr}S;V$lASkeeipn`piF1m-0*}uqUf_|nNWMdoalkXp7tx{1daNLZ2A+I4OD|JfAAGp78bL)0aA$f~ZUno_vw0OzW9)lziH*$Oc)a zr$*f+s?1-c7qy`-M?dPhVO5*8BqiwQ(=VDWb>0?3=vywM zR+b*8cf44mnzU`pJ5_jXMH{-k`ok}6U#3RZ*2DoVx%%Z=3q{+;gAA~H$Yqm9Fxg|^ zxny`-o{Qe`_;zTlel6Lw_4SdEu&aKhT>JO>Cyiv);>2EL@6a2CA8L~&jnAVlS3A`B z4-M)EPnMhHJr|zZ0_x@)!ze=UIGywa*|?378YN;GbrD*;!eXS4Ovug=UOWA1Mp+JT za#9TcJcTUcKp1_eJ_ldzq?gGjPC9ADC^x%kIRrzDwQs;d<-%lmt<{)hdq2IoI16kqJBYMJd*35&Vkjncb@c=&-iBypmg&q@M z>4t-+xbYyLsnjMnrt(%d7Vr<;m@P)!SOs1=18$zBj#uR&&dxxIF{5dBi@{?IZG(Nh zA%;Ay3PS_9r&!{I0D9%0=&=Q`o&PV}r-~i?#B3zs92LbQ(P=lt7-As@hpw>)2lQ>? z3GtYYKJfH-Oy-;7aoDu|8`*8;`{uxD`msXliM&Q?zH<&j9N!<2#<;+X&s0hb=No0- zQBWoI*owu+B~Ic^GL^AcjkE;1s#WFK-HxdtQGx6q3L^s8sc@{gp;aQ|rE0rtYCETx tJ{P}$=81cWNH$}BvRJSH)xpx}ej0g1^LS<&>LkWI1FJ>l<1%m|`afcD&C37) diff --git a/scripts/health/pkg-web/index.js b/scripts/health/pkg-web/index.js index 009fdd4e9..46af0ba5d 100644 --- a/scripts/health/pkg-web/index.js +++ b/scripts/health/pkg-web/index.js @@ -292,15 +292,15 @@ function getImports() { getInt32Memory0()[arg0 / 4 + 1] = len0 getInt32Memory0()[arg0 / 4 + 0] = ptr0 } - imports.wbg.__wbindgen_is_string = function (arg0) { - const ret = typeof getObject(arg0) === 'string' - return ret - } imports.wbg.__wbindgen_boolean_get = function (arg0) { const v = getObject(arg0) const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 return ret } + imports.wbg.__wbindgen_is_string = function (arg0) { + const ret = typeof getObject(arg0) === 'string' + return ret + } imports.wbg.__wbg_new_abda76e883ba8a5f = function () { const ret = new Error() return addHeapObject(ret) diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm index 5178edf9a9e64369843ffeebe4f600a807b85623..c26e7e0d6c463d71ce64b69e43971740faa21b77 100644 GIT binary patch delta 1341 zcmY+BeN5F=7{__Ohx?A)dzEYVM#u$e$b|`%pahy2fz8;kLjTFi8tE2k&1}86q?8v+ zdHW#;3yF7>2_Zk~n3aN+#ugjBXvG*2AX`Dal#&ra=a=ic_S??$e4g+3e9yD}`uj}% zeWtcJC>aP+;t;4li|2LBVKA#d8TQb4Mzm3QBOD-oiC1N2!`*KI4m= z0%gX3Kh>!(T9Zhl4qFM;DXI2sno?E!$G%jnKDYETQLU2dY@#V;!g)s8)Dx~bqH47v z@H{D=aHp~_=mmLUI%o~#1&`Y*ixL?gsAPt|Dd&Ts3EKSjJ8d`T4KnDf@XusG`HI74 z?b-RXST*zRHF_<(C?ObV%TwDD3rSXG{n~C6=c<-<(OtH|WT$6sbor zm8UN2D3#X=l=ctOF_@>2_?vpw1yQ4y&YE-kfZjarc92tD-4(x1M(tYAa@E5N1BBju zkxlff9@V+xX9JBH(3Y>6-Q1Q$Mlo-t&wY(K1)V;&9U*#Nl}O9^L;%Impa^!*R_$N# zmt3?3#rdMwMVq|?K9NZNqTfZ4B-(>%Bv5l!BueLCzL-iRZbw@#myKJTOsCiY{>n2n zBF;TYnc#ifW&1}K9nkS5S+adBn|kzgRYdKf7|6P9FEv}`a>iWRXcVPIR8M3Ot4k;u zWa2L?w326)&~A}&L{rTCdLgY8O$sfT$RwJM)1)7D3f@d{IvNr^@6lNu<%xp#DZz+h zxnwg%iOEmMHhs^t{Ur2d{Hu886hjm=HvBT?pL#h8y>Y z$3yT0xFHN-e0aG$&dWnYhM`iYAFt^2Np|r52srtb2v|+IxpH8(b%S?Bz@hKu67AgS zL2Td*S^Rw5J}cnkCfgTev3TKWzR82Tc)3T0U-H1}cgr^pe%XWSu!edWWRtlTe|bH5 zv{s_ii{EOvm_HgN*VqyTE8iO_k2Klsp4TLo*Lk&7;!-bue_Eo&iye&;Z^a(a9<_T_ zUMF#}7r)fZi@aF&ubJEB4q+EHXMk6#`2V9PH8n^aioy?|RpO~=Bp47MBdX%j;3o$u Z_o0wz75^p$Z4wXO50g>Ti_*~({0~w8%?SVi delta 1300 zcmYL`eN2^A9LM>65BC-2UJ~qH3Ai9md6_^Akw6k7v=y6J;9pjjq+4V)XUkqD6>koC zeUyWR$g3z5yz)?ooG)Ontg%rU&=llJh-#KT`u*)>; zGC32VY(t=ufFSiXys2YmLx6hSu#X-;hNCzF1&45#I@ochuw$&2aW;!eb{iF}j{S+F ztcHEUs@OFCKs}qm3HC8N!m3#VD_0cLmYpp)PpxP{BhH}{t@3NbDV)R)=wMxVfP0w3 z-?%U70lLw|zQ71A-~%>~d?x5;>Oej|WRvpz`!tGQ>59C~5UO#TN^ys3P^G4rd%WQ+ zRPOp7q$c$(OA2Y!ZLOjvCBrX|Dpl3*f;Tm&Z!G_bs6olFwNa%qW$TeK^^|>%s99|d zyex~78&oRevq9@2n}Kc_&`_j>9o(8iMlo-pue^=U!d@@y?od6iPo|Z8%AaDXQiKH1cI{8_x9yaJ znIbW0r>y^l!T*Fr5?vuQ7O3TFlPQNE2qs@KmrSL8a2N5JWO9hpnH1;Gm*vu!xbzZb zgTM2tZ2!uok96F%Q?}3N(SV*tMf5(3g{(XFQ@e!>!dOUKjN)n)wGbKk0fpA`+$wrq zY%S9ilekk(X&^ICtfIA|O`!!7nMK=inqESKg11wGj>g5nSvs$yVo~xbB^fbO%xl{z zT1;Og?=TwY(oNtIZu(q4K#aNRX%oMGi|Y7y6LgKgKS?gF+rVQcs7`GEo%UMjruce} z4*Js#k>Q7L41A9r={(Dhhxt<3i8FTG(DObA0(i3n!Mt4SVnY$eSB2s+t_y{O-*aH8 zcp(%of*ZmS&PP|u<+5BvR5SzFrPW8{`uG zJUCe^F@W155r=Py?~25uK0aO!uQiZi>nl7pQqF0J#0pG;pOuvlMdCzw!9}^vF7`NHQQHNz|`H>k=uXKY?ZHL RoDvJ3gvqG+6`2?a`4_21#!3JH diff --git a/scripts/package.json b/scripts/package.json index dabaee988..24e0062c7 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -20,27 +20,27 @@ "format-check": "prettier --check ." }, "dependencies": { - "@cosmjs/cosmwasm-stargate": "^0.30.0", - "@cosmjs/stargate": "^0.30.0", - "@cosmwasm/ts-codegen": "^0.25.2", + "@cosmjs/cosmwasm-stargate": "^0.30.1", + "@cosmjs/stargate": "^0.30.1", + "@cosmwasm/ts-codegen": "^0.27.0", "chalk": "4.1.2", "copyfiles": "^2.4.1", - "cosmjs-types": "^0.7.1", + "cosmjs-types": "^0.7.2", "lodash": "^4.17.21", - "long": "^5.2.1", + "long": "^5.2.3", "prepend-file": "^2.0.1", "wasm-pack": "^0.10.3" }, "devDependencies": { - "@babel/preset-env": "^7.20.2", - "@babel/preset-typescript": "^7.18.6", - "@types/jest": "^29.4.0", - "@typescript-eslint/eslint-plugin": "^5.54.1", - "@typescript-eslint/parser": "^5.54.1", - "eslint": "^8.36.0", - "eslint-config-prettier": "^8.7.0", + "@babel/preset-env": "^7.21.5", + "@babel/preset-typescript": "^7.21.5", + "@types/jest": "^29.5.1", + "@typescript-eslint/eslint-plugin": "^5.59.2", + "@typescript-eslint/parser": "^5.59.2", + "eslint": "^8.39.0", + "eslint-config-prettier": "^8.8.0", "jest": "^29.5.0", - "prettier": "^2.8.4", + "prettier": "^2.8.8", "typescript": "^5.0.4" } } diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts index c7c7b97b8..a6616ab34 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts index 451848cd4..2bfeab8d8 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts index 5a1150611..66b91c847 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index 39b5d5754..ea30e9d1e 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/bundle.ts b/scripts/types/generated/mars-account-nft/bundle.ts index 4659022b2..6a43efab7 100644 --- a/scripts/types/generated/mars-account-nft/bundle.ts +++ b/scripts/types/generated/mars-account-nft/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 897afdd52..43f4f04f9 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -427,11 +427,13 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt funds?: Coin[], ) => Promise emergencyConfigUpdate: ( + emergencyUpdate: EmergencyUpdate, fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], ) => Promise updateOwner: ( + ownerUpdate: OwnerUpdate, fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], @@ -447,6 +449,7 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt funds?: Coin[], ) => Promise callback: ( + callbackMsg: CallbackMsg, fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], @@ -564,6 +567,7 @@ export class MarsCreditManagerClient ) } emergencyConfigUpdate = async ( + emergencyUpdate: EmergencyUpdate, fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -572,7 +576,7 @@ export class MarsCreditManagerClient this.sender, this.contractAddress, { - emergency_config_update: {}, + emergency_config_update: emergencyUpdate, }, fee, memo, @@ -580,6 +584,7 @@ export class MarsCreditManagerClient ) } updateOwner = async ( + ownerUpdate: OwnerUpdate, fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -588,7 +593,7 @@ export class MarsCreditManagerClient this.sender, this.contractAddress, { - update_owner: {}, + update_owner: ownerUpdate, }, fee, memo, @@ -619,6 +624,7 @@ export class MarsCreditManagerClient ) } callback = async ( + callbackMsg: CallbackMsg, fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -627,7 +633,7 @@ export class MarsCreditManagerClient this.sender, this.contractAddress, { - callback: {}, + callback: callbackMsg, }, fee, memo, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 45bb6e135..427bcfb79 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -97,8 +97,11 @@ export interface MarsCreditManagerMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject - emergencyConfigUpdate: (funds?: Coin[]) => MsgExecuteContractEncodeObject - updateOwner: (funds?: Coin[]) => MsgExecuteContractEncodeObject + emergencyConfigUpdate: ( + emergencyUpdate: EmergencyUpdate, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + updateOwner: (ownerUpdate: OwnerUpdate, funds?: Coin[]) => MsgExecuteContractEncodeObject updateNftConfig: ( { updates, @@ -107,7 +110,7 @@ export interface MarsCreditManagerMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject - callback: (funds?: Coin[]) => MsgExecuteContractEncodeObject + callback: (callbackMsg: CallbackMsg, funds?: Coin[]) => MsgExecuteContractEncodeObject } export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessage { sender: string @@ -216,7 +219,10 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag }), } } - emergencyConfigUpdate = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + emergencyConfigUpdate = ( + emergencyUpdate: EmergencyUpdate, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -224,14 +230,14 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - emergency_config_update: {}, + emergency_config_update: emergencyUpdate, }), ), funds, }), } } - updateOwner = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateOwner = (ownerUpdate: OwnerUpdate, funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -239,7 +245,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - update_owner: {}, + update_owner: ownerUpdate, }), ), funds, @@ -270,7 +276,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag }), } } - callback = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + callback = (callbackMsg: CallbackMsg, funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -278,7 +284,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - callback: {}, + callback: callbackMsg, }), ), funds, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 4f6df16aa..2ac8d6189 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -630,6 +630,7 @@ export function useMarsCreditManagerConfigQuery({ } export interface MarsCreditManagerCallbackMutation { client: MarsCreditManagerClient + msg: CallbackMsg args?: { fee?: number | StdFee | 'auto' memo?: string @@ -672,6 +673,7 @@ export function useMarsCreditManagerUpdateNftConfigMutation( } export interface MarsCreditManagerUpdateOwnerMutation { client: MarsCreditManagerClient + msg: OwnerUpdate args?: { fee?: number | StdFee | 'auto' memo?: string @@ -691,6 +693,7 @@ export function useMarsCreditManagerUpdateOwnerMutation( } export interface MarsCreditManagerEmergencyConfigUpdateMutation { client: MarsCreditManagerClient + msg: EmergencyUpdate args?: { fee?: number | StdFee | 'auto' memo?: string diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index de290892e..945561877 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-credit-manager/bundle.ts b/scripts/types/generated/mars-credit-manager/bundle.ts index bfd27b400..f68b3a809 100644 --- a/scripts/types/generated/mars-credit-manager/bundle.ts +++ b/scripts/types/generated/mars-credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index 8a6a05f77..b6e8d3f9a 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index 2e42564c6..fe863393b 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index b09a9940c..65e833e6d 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 1b4082298..e14c98612 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/bundle.ts b/scripts/types/generated/mars-mock-credit-manager/bundle.ts index 81f6963d5..c0d830bea 100644 --- a/scripts/types/generated/mars-mock-credit-manager/bundle.ts +++ b/scripts/types/generated/mars-mock-credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts index 88e05d8e0..feaf6aef9 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts index 45e3f7ca6..61bae67e4 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts index bbe28d32c..0ae7deed9 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts index 7539a5a7c..bea5f561e 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/bundle.ts b/scripts/types/generated/mars-mock-oracle/bundle.ts index b7754dbf9..18ab96306 100644 --- a/scripts/types/generated/mars-mock-oracle/bundle.ts +++ b/scripts/types/generated/mars-mock-oracle/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index a5c8c392b..0c394a17e 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -311,11 +311,13 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa contractAddress: string sender: string updateOwner: ( + ownerUpdate: OwnerUpdate, fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], ) => Promise updateEmergencyOwner: ( + ownerUpdate: OwnerUpdate, fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], @@ -471,6 +473,7 @@ export class MarsMockRedBankClient } updateOwner = async ( + ownerUpdate: OwnerUpdate, fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -479,7 +482,7 @@ export class MarsMockRedBankClient this.sender, this.contractAddress, { - update_owner: {}, + update_owner: ownerUpdate, }, fee, memo, @@ -487,6 +490,7 @@ export class MarsMockRedBankClient ) } updateEmergencyOwner = async ( + ownerUpdate: OwnerUpdate, fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -495,7 +499,7 @@ export class MarsMockRedBankClient this.sender, this.contractAddress, { - update_emergency_owner: {}, + update_emergency_owner: ownerUpdate, }, fee, memo, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts index c7e3873b1..579e6e4c9 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -35,8 +35,8 @@ import { export interface MarsMockRedBankMessage { contractAddress: string sender: string - updateOwner: (funds?: Coin[]) => MsgExecuteContractEncodeObject - updateEmergencyOwner: (funds?: Coin[]) => MsgExecuteContractEncodeObject + updateOwner: (ownerUpdate: OwnerUpdate, funds?: Coin[]) => MsgExecuteContractEncodeObject + updateEmergencyOwner: (ownerUpdate: OwnerUpdate, funds?: Coin[]) => MsgExecuteContractEncodeObject updateConfig: ( { config, @@ -161,7 +161,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { this.updateAssetCollateralStatus = this.updateAssetCollateralStatus.bind(this) } - updateOwner = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateOwner = (ownerUpdate: OwnerUpdate, funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -169,14 +169,17 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - update_owner: {}, + update_owner: ownerUpdate, }), ), funds, }), } } - updateEmergencyOwner = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateEmergencyOwner = ( + ownerUpdate: OwnerUpdate, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -184,7 +187,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - update_emergency_owner: {}, + update_emergency_owner: ownerUpdate, }), ), funds, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index 447e5cd31..2515135cb 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -695,6 +695,7 @@ export function useMarsMockRedBankUpdateConfigMutation( } export interface MarsMockRedBankUpdateEmergencyOwnerMutation { client: MarsMockRedBankClient + msg: OwnerUpdate args?: { fee?: number | StdFee | 'auto' memo?: string @@ -715,6 +716,7 @@ export function useMarsMockRedBankUpdateEmergencyOwnerMutation( } export interface MarsMockRedBankUpdateOwnerMutation { client: MarsMockRedBankClient + msg: OwnerUpdate args?: { fee?: number | StdFee | 'auto' memo?: string diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index d946d23b6..25807135b 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/bundle.ts b/scripts/types/generated/mars-mock-red-bank/bundle.ts index 3273e8c47..537d0a2b1 100644 --- a/scripts/types/generated/mars-mock-red-bank/bundle.ts +++ b/scripts/types/generated/mars-mock-red-bank/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts index dc4ed49b8..4032e15fb 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -33,7 +33,7 @@ export interface MarsMockVaultReadOnlyInterface { totalVaultTokenSupply: () => Promise convertToShares: ({ amount }: { amount: Uint128 }) => Promise convertToAssets: ({ amount }: { amount: Uint128 }) => Promise - vaultExtension: () => Promise + vaultExtension: (extensionQueryMsg: ExtensionQueryMsg) => Promise } export class MarsMockVaultQueryClient implements MarsMockVaultReadOnlyInterface { client: CosmWasmClient @@ -101,9 +101,9 @@ export class MarsMockVaultQueryClient implements MarsMockVaultReadOnlyInterface }, }) } - vaultExtension = async (): Promise => { + vaultExtension = async (extensionQueryMsg: ExtensionQueryMsg): Promise => { return this.client.queryContractSmart(this.contractAddress, { - vault_extension: {}, + vault_extension: extensionQueryMsg, }) } } @@ -135,6 +135,7 @@ export interface MarsMockVaultInterface extends MarsMockVaultReadOnlyInterface { funds?: Coin[], ) => Promise vaultExtension: ( + extensionExecuteMsg: ExtensionExecuteMsg, fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], @@ -211,6 +212,7 @@ export class MarsMockVaultClient ) } vaultExtension = async ( + extensionExecuteMsg: ExtensionExecuteMsg, fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -219,7 +221,7 @@ export class MarsMockVaultClient this.sender, this.contractAddress, { - vault_extension: {}, + vault_extension: extensionExecuteMsg, }, fee, memo, diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts index 1a3bd82ae..1a046efe5 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -48,7 +48,10 @@ export interface MarsMockVaultMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject - vaultExtension: (funds?: Coin[]) => MsgExecuteContractEncodeObject + vaultExtension: ( + extensionExecuteMsg: ExtensionExecuteMsg, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject } export class MarsMockVaultMessageComposer implements MarsMockVaultMessage { sender: string @@ -116,7 +119,10 @@ export class MarsMockVaultMessageComposer implements MarsMockVaultMessage { }), } } - vaultExtension = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + vaultExtension = ( + extensionExecuteMsg: ExtensionExecuteMsg, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -124,7 +130,7 @@ export class MarsMockVaultMessageComposer implements MarsMockVaultMessage { contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - vault_extension: {}, + vault_extension: extensionExecuteMsg, }), ), funds, diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts index ad8104ca1..cf84eab1f 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -235,6 +235,7 @@ export function useMarsMockVaultVaultStandardInfoQuery MsgExecuteContractEncodeObject + updateOwner: (ownerUpdate: OwnerUpdate, funds?: Coin[]) => MsgExecuteContractEncodeObject updateConfig: ( { creditManager, @@ -44,7 +44,7 @@ export class MarsRoverHealthTypesMessageComposer implements MarsRoverHealthTypes this.updateConfig = this.updateConfig.bind(this) } - updateOwner = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateOwner = (ownerUpdate: OwnerUpdate, funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -52,7 +52,7 @@ export class MarsRoverHealthTypesMessageComposer implements MarsRoverHealthTypes contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - update_owner: {}, + update_owner: ownerUpdate, }), ), funds, diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts index f68f8e43a..2e36129f5 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -108,6 +108,7 @@ export function useMarsRoverHealthTypesUpdateConfigMutation( } export interface MarsRoverHealthTypesUpdateOwnerMutation { client: MarsRoverHealthTypesClient + msg: OwnerUpdate args?: { fee?: number | StdFee | 'auto' memo?: string diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts index 4646815c0..4ba252e47 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-types/bundle.ts b/scripts/types/generated/mars-rover-health-types/bundle.ts index eb595dcc2..0f32571b6 100644 --- a/scripts/types/generated/mars-rover-health-types/bundle.ts +++ b/scripts/types/generated/mars-rover-health-types/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts index bc1140d90..e39568a67 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -112,6 +112,7 @@ export interface MarsSwapperBaseInterface extends MarsSwapperBaseReadOnlyInterfa contractAddress: string sender: string updateOwner: ( + ownerUpdate: OwnerUpdate, fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], @@ -179,6 +180,7 @@ export class MarsSwapperBaseClient } updateOwner = async ( + ownerUpdate: OwnerUpdate, fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -187,7 +189,7 @@ export class MarsSwapperBaseClient this.sender, this.contractAddress, { - update_owner: {}, + update_owner: ownerUpdate, }, fee, memo, diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts index 12569a5d0..3392363ab 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -26,7 +26,7 @@ import { export interface MarsSwapperBaseMessage { contractAddress: string sender: string - updateOwner: (funds?: Coin[]) => MsgExecuteContractEncodeObject + updateOwner: (ownerUpdate: OwnerUpdate, funds?: Coin[]) => MsgExecuteContractEncodeObject setRoute: ( { denomIn, @@ -77,7 +77,7 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { this.transferResult = this.transferResult.bind(this) } - updateOwner = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateOwner = (ownerUpdate: OwnerUpdate, funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -85,7 +85,7 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - update_owner: {}, + update_owner: ownerUpdate, }), ), funds, diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts index 1384359a8..8ef5b4d3e 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -215,6 +215,7 @@ export function useMarsSwapperBaseSetRouteMutation( } export interface MarsSwapperBaseUpdateOwnerMutation { client: MarsSwapperBaseClient + msg: OwnerUpdate args?: { fee?: number | StdFee | 'auto' memo?: string diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts index abac5d2ea..9cc56adc1 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/bundle.ts b/scripts/types/generated/mars-swapper-base/bundle.ts index b1e02ac0e..77db5ab72 100644 --- a/scripts/types/generated/mars-swapper-base/bundle.ts +++ b/scripts/types/generated/mars-swapper-base/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-zapper-base/MarsZapperBase.client.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts similarity index 89% rename from scripts/types/generated/mars-zapper-base/MarsZapperBase.client.ts rename to scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts index 3d3a07ed9..dceaee018 100644 --- a/scripts/types/generated/mars-zapper-base/MarsZapperBase.client.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -16,8 +16,8 @@ import { Coin, QueryMsg, ArrayOfCoin, -} from './MarsZapperBase.types' -export interface MarsZapperBaseReadOnlyInterface { +} from './MarsV2ZapperBase.types' +export interface MarsV2ZapperBaseReadOnlyInterface { contractAddress: string estimateProvideLiquidity: ({ coinsIn, @@ -28,7 +28,7 @@ export interface MarsZapperBaseReadOnlyInterface { }) => Promise estimateWithdrawLiquidity: ({ coinIn }: { coinIn: Coin }) => Promise } -export class MarsZapperBaseQueryClient implements MarsZapperBaseReadOnlyInterface { +export class MarsV2ZapperBaseQueryClient implements MarsV2ZapperBaseReadOnlyInterface { client: CosmWasmClient contractAddress: string @@ -61,7 +61,7 @@ export class MarsZapperBaseQueryClient implements MarsZapperBaseReadOnlyInterfac }) } } -export interface MarsZapperBaseInterface extends MarsZapperBaseReadOnlyInterface { +export interface MarsV2ZapperBaseInterface extends MarsV2ZapperBaseReadOnlyInterface { contractAddress: string sender: string provideLiquidity: ( @@ -89,14 +89,15 @@ export interface MarsZapperBaseInterface extends MarsZapperBaseReadOnlyInterface funds?: Coin[], ) => Promise callback: ( + callbackMsg: CallbackMsg, fee?: number | StdFee | 'auto', memo?: string, funds?: Coin[], ) => Promise } -export class MarsZapperBaseClient - extends MarsZapperBaseQueryClient - implements MarsZapperBaseInterface +export class MarsV2ZapperBaseClient + extends MarsV2ZapperBaseQueryClient + implements MarsV2ZapperBaseInterface { client: SigningCosmWasmClient sender: string @@ -165,6 +166,7 @@ export class MarsZapperBaseClient ) } callback = async ( + callbackMsg: CallbackMsg, fee: number | StdFee | 'auto' = 'auto', memo?: string, funds?: Coin[], @@ -173,7 +175,7 @@ export class MarsZapperBaseClient this.sender, this.contractAddress, { - callback: {}, + callback: callbackMsg, }, fee, memo, diff --git a/scripts/types/generated/mars-zapper-base/MarsZapperBase.message-composer.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts similarity index 87% rename from scripts/types/generated/mars-zapper-base/MarsZapperBase.message-composer.ts rename to scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts index 6fdf18ecf..a484f72db 100644 --- a/scripts/types/generated/mars-zapper-base/MarsZapperBase.message-composer.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -17,8 +17,8 @@ import { Coin, QueryMsg, ArrayOfCoin, -} from './MarsZapperBase.types' -export interface MarsZapperBaseMessage { +} from './MarsV2ZapperBase.types' +export interface MarsV2ZapperBaseMessage { contractAddress: string sender: string provideLiquidity: ( @@ -41,9 +41,9 @@ export interface MarsZapperBaseMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject - callback: (funds?: Coin[]) => MsgExecuteContractEncodeObject + callback: (callbackMsg: CallbackMsg, funds?: Coin[]) => MsgExecuteContractEncodeObject } -export class MarsZapperBaseMessageComposer implements MarsZapperBaseMessage { +export class MarsV2ZapperBaseMessageComposer implements MarsV2ZapperBaseMessage { sender: string contractAddress: string @@ -109,7 +109,7 @@ export class MarsZapperBaseMessageComposer implements MarsZapperBaseMessage { }), } } - callback = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + callback = (callbackMsg: CallbackMsg, funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -117,7 +117,7 @@ export class MarsZapperBaseMessageComposer implements MarsZapperBaseMessage { contract: this.contractAddress, msg: toUtf8( JSON.stringify({ - callback: {}, + callback: callbackMsg, }), ), funds, diff --git a/scripts/types/generated/mars-zapper-base/MarsZapperBase.react-query.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts similarity index 56% rename from scripts/types/generated/mars-zapper-base/MarsZapperBase.react-query.ts rename to scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts index e4d82fcef..eecaeedf8 100644 --- a/scripts/types/generated/mars-zapper-base/MarsZapperBase.react-query.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -17,20 +17,20 @@ import { Coin, QueryMsg, ArrayOfCoin, -} from './MarsZapperBase.types' -import { MarsZapperBaseQueryClient, MarsZapperBaseClient } from './MarsZapperBase.client' -export const marsZapperBaseQueryKeys = { +} from './MarsV2ZapperBase.types' +import { MarsV2ZapperBaseQueryClient, MarsV2ZapperBaseClient } from './MarsV2ZapperBase.client' +export const marsV2ZapperBaseQueryKeys = { contract: [ { - contract: 'marsZapperBase', + contract: 'marsV2ZapperBase', }, ] as const, address: (contractAddress: string | undefined) => - [{ ...marsZapperBaseQueryKeys.contract[0], address: contractAddress }] as const, + [{ ...marsV2ZapperBaseQueryKeys.contract[0], address: contractAddress }] as const, estimateProvideLiquidity: (contractAddress: string | undefined, args?: Record) => [ { - ...marsZapperBaseQueryKeys.address(contractAddress)[0], + ...marsV2ZapperBaseQueryKeys.address(contractAddress)[0], method: 'estimate_provide_liquidity', args, }, @@ -41,14 +41,14 @@ export const marsZapperBaseQueryKeys = { ) => [ { - ...marsZapperBaseQueryKeys.address(contractAddress)[0], + ...marsV2ZapperBaseQueryKeys.address(contractAddress)[0], method: 'estimate_withdraw_liquidity', args, }, ] as const, } -export interface MarsZapperBaseReactQuery { - client: MarsZapperBaseQueryClient | undefined +export interface MarsV2ZapperBaseReactQuery { + client: MarsV2ZapperBaseQueryClient | undefined options?: Omit< UseQueryOptions, "'queryKey' | 'queryFn' | 'initialData'" @@ -56,19 +56,19 @@ export interface MarsZapperBaseReactQuery { initialData?: undefined } } -export interface MarsZapperBaseEstimateWithdrawLiquidityQuery - extends MarsZapperBaseReactQuery { +export interface MarsV2ZapperBaseEstimateWithdrawLiquidityQuery + extends MarsV2ZapperBaseReactQuery { args: { coinIn: Coin } } -export function useMarsZapperBaseEstimateWithdrawLiquidityQuery({ +export function useMarsV2ZapperBaseEstimateWithdrawLiquidityQuery({ client, args, options, -}: MarsZapperBaseEstimateWithdrawLiquidityQuery) { +}: MarsV2ZapperBaseEstimateWithdrawLiquidityQuery) { return useQuery( - marsZapperBaseQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), + marsV2ZapperBaseQueryKeys.estimateWithdrawLiquidity(client?.contractAddress, args), () => client ? client.estimateWithdrawLiquidity({ @@ -78,20 +78,20 @@ export function useMarsZapperBaseEstimateWithdrawLiquidityQuery - extends MarsZapperBaseReactQuery { +export interface MarsV2ZapperBaseEstimateProvideLiquidityQuery + extends MarsV2ZapperBaseReactQuery { args: { coinsIn: Coin[] lpTokenOut: string } } -export function useMarsZapperBaseEstimateProvideLiquidityQuery({ +export function useMarsV2ZapperBaseEstimateProvideLiquidityQuery({ client, args, options, -}: MarsZapperBaseEstimateProvideLiquidityQuery) { +}: MarsV2ZapperBaseEstimateProvideLiquidityQuery) { return useQuery( - marsZapperBaseQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), + marsV2ZapperBaseQueryKeys.estimateProvideLiquidity(client?.contractAddress, args), () => client ? client.estimateProvideLiquidity({ @@ -102,27 +102,28 @@ export function useMarsZapperBaseEstimateProvideLiquidityQuery( { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsZapperBaseCallbackMutation { - client: MarsZapperBaseClient +export interface MarsV2ZapperBaseCallbackMutation { + client: MarsV2ZapperBaseClient + msg: CallbackMsg args?: { fee?: number | StdFee | 'auto' memo?: string funds?: Coin[] } } -export function useMarsZapperBaseCallbackMutation( +export function useMarsV2ZapperBaseCallbackMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.callback(msg, fee, memo, funds), options, ) } -export interface MarsZapperBaseWithdrawLiquidityMutation { - client: MarsZapperBaseClient +export interface MarsV2ZapperBaseWithdrawLiquidityMutation { + client: MarsV2ZapperBaseClient msg: { recipient?: string } @@ -132,20 +133,20 @@ export interface MarsZapperBaseWithdrawLiquidityMutation { funds?: Coin[] } } -export function useMarsZapperBaseWithdrawLiquidityMutation( +export function useMarsV2ZapperBaseWithdrawLiquidityMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.withdrawLiquidity(msg, fee, memo, funds), options, ) } -export interface MarsZapperBaseProvideLiquidityMutation { - client: MarsZapperBaseClient +export interface MarsV2ZapperBaseProvideLiquidityMutation { + client: MarsV2ZapperBaseClient msg: { lpTokenOut: string minimumReceive: Uint128 @@ -157,13 +158,13 @@ export interface MarsZapperBaseProvideLiquidityMutation { funds?: Coin[] } } -export function useMarsZapperBaseProvideLiquidityMutation( +export function useMarsV2ZapperBaseProvideLiquidityMutation( options?: Omit< - UseMutationOptions, + UseMutationOptions, 'mutationFn' >, ) { - return useMutation( + return useMutation( ({ client, msg, args: { fee, memo, funds } = {} }) => client.provideLiquidity(msg, fee, memo, funds), options, diff --git a/scripts/types/generated/mars-zapper-base/MarsZapperBase.types.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts similarity index 99% rename from scripts/types/generated/mars-zapper-base/MarsZapperBase.types.ts rename to scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts index 42512cda2..ca83cf38f 100644 --- a/scripts/types/generated/mars-zapper-base/MarsZapperBase.types.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v2-zapper-base/bundle.ts b/scripts/types/generated/mars-v2-zapper-base/bundle.ts new file mode 100644 index 000000000..1b56e7116 --- /dev/null +++ b/scripts/types/generated/mars-v2-zapper-base/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _36 from './MarsV2ZapperBase.types' +import * as _37 from './MarsV2ZapperBase.client' +import * as _38 from './MarsV2ZapperBase.message-composer' +import * as _39 from './MarsV2ZapperBase.react-query' +export namespace contracts { + export const MarsV2ZapperBase = { ..._36, ..._37, ..._38, ..._39 } +} diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts new file mode 100644 index 000000000..dab60b69a --- /dev/null +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts @@ -0,0 +1,173 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + Uint128, + OwnerUpdate, + CallbackMsg, + Addr, + NewPositionRequest, + Coin, + QueryMsg, + OwnerResponse, +} from './MarsV3ZapperBase.types' +export interface MarsV3ZapperBaseReadOnlyInterface { + contractAddress: string + owner: () => Promise +} +export class MarsV3ZapperBaseQueryClient implements MarsV3ZapperBaseReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.owner = this.owner.bind(this) + } + + owner = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + owner: {}, + }) + } +} +export interface MarsV3ZapperBaseInterface extends MarsV3ZapperBaseReadOnlyInterface { + contractAddress: string + sender: string + createPosition: ( + { + lowerTick, + poolId, + tokenDesired0, + tokenDesired1, + tokenMinAmount0, + tokenMinAmount1, + upperTick, + }: { + lowerTick: number + poolId: number + tokenDesired0?: Coin + tokenDesired1?: Coin + tokenMinAmount0: string + tokenMinAmount1: string + upperTick: number + }, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + updateOwner: ( + ownerUpdate: OwnerUpdate, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise + callback: ( + callbackMsg: CallbackMsg, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise +} +export class MarsV3ZapperBaseClient + extends MarsV3ZapperBaseQueryClient + implements MarsV3ZapperBaseInterface +{ + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.createPosition = this.createPosition.bind(this) + this.updateOwner = this.updateOwner.bind(this) + this.callback = this.callback.bind(this) + } + + createPosition = async ( + { + lowerTick, + poolId, + tokenDesired0, + tokenDesired1, + tokenMinAmount0, + tokenMinAmount1, + upperTick, + }: { + lowerTick: number + poolId: number + tokenDesired0?: Coin + tokenDesired1?: Coin + tokenMinAmount0: string + tokenMinAmount1: string + upperTick: number + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + create_position: { + lower_tick: lowerTick, + pool_id: poolId, + token_desired0: tokenDesired0, + token_desired1: tokenDesired1, + token_min_amount0: tokenMinAmount0, + token_min_amount1: tokenMinAmount1, + upper_tick: upperTick, + }, + }, + fee, + memo, + funds, + ) + } + updateOwner = async ( + ownerUpdate: OwnerUpdate, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_owner: ownerUpdate, + }, + fee, + memo, + funds, + ) + } + callback = async ( + callbackMsg: CallbackMsg, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + callback: callbackMsg, + }, + fee, + memo, + funds, + ) + } +} diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts new file mode 100644 index 000000000..450e38cc3 --- /dev/null +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts @@ -0,0 +1,133 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + InstantiateMsg, + ExecuteMsg, + Uint128, + OwnerUpdate, + CallbackMsg, + Addr, + NewPositionRequest, + Coin, + QueryMsg, + OwnerResponse, +} from './MarsV3ZapperBase.types' +export interface MarsV3ZapperBaseMessage { + contractAddress: string + sender: string + createPosition: ( + { + lowerTick, + poolId, + tokenDesired0, + tokenDesired1, + tokenMinAmount0, + tokenMinAmount1, + upperTick, + }: { + lowerTick: number + poolId: number + tokenDesired0?: Coin + tokenDesired1?: Coin + tokenMinAmount0: string + tokenMinAmount1: string + upperTick: number + }, + funds?: Coin[], + ) => MsgExecuteContractEncodeObject + updateOwner: (ownerUpdate: OwnerUpdate, funds?: Coin[]) => MsgExecuteContractEncodeObject + callback: (callbackMsg: CallbackMsg, funds?: Coin[]) => MsgExecuteContractEncodeObject +} +export class MarsV3ZapperBaseMessageComposer implements MarsV3ZapperBaseMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.createPosition = this.createPosition.bind(this) + this.updateOwner = this.updateOwner.bind(this) + this.callback = this.callback.bind(this) + } + + createPosition = ( + { + lowerTick, + poolId, + tokenDesired0, + tokenDesired1, + tokenMinAmount0, + tokenMinAmount1, + upperTick, + }: { + lowerTick: number + poolId: number + tokenDesired0?: Coin + tokenDesired1?: Coin + tokenMinAmount0: string + tokenMinAmount1: string + upperTick: number + }, + funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + create_position: { + lower_tick: lowerTick, + pool_id: poolId, + token_desired0: tokenDesired0, + token_desired1: tokenDesired1, + token_min_amount0: tokenMinAmount0, + token_min_amount1: tokenMinAmount1, + upper_tick: upperTick, + }, + }), + ), + funds, + }), + } + } + updateOwner = (ownerUpdate: OwnerUpdate, funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_owner: ownerUpdate, + }), + ), + funds, + }), + } + } + callback = (callbackMsg: CallbackMsg, funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + callback: callbackMsg, + }), + ), + funds, + }), + } + } +} diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts new file mode 100644 index 000000000..b1aaca659 --- /dev/null +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts @@ -0,0 +1,124 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + Uint128, + OwnerUpdate, + CallbackMsg, + Addr, + NewPositionRequest, + Coin, + QueryMsg, + OwnerResponse, +} from './MarsV3ZapperBase.types' +import { MarsV3ZapperBaseQueryClient, MarsV3ZapperBaseClient } from './MarsV3ZapperBase.client' +export const marsV3ZapperBaseQueryKeys = { + contract: [ + { + contract: 'marsV3ZapperBase', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...marsV3ZapperBaseQueryKeys.contract[0], address: contractAddress }] as const, + owner: (contractAddress: string | undefined, args?: Record) => + [{ ...marsV3ZapperBaseQueryKeys.address(contractAddress)[0], method: 'owner', args }] as const, +} +export interface MarsV3ZapperBaseReactQuery { + client: MarsV3ZapperBaseQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MarsV3ZapperBaseOwnerQuery + extends MarsV3ZapperBaseReactQuery {} +export function useMarsV3ZapperBaseOwnerQuery({ + client, + options, +}: MarsV3ZapperBaseOwnerQuery) { + return useQuery( + marsV3ZapperBaseQueryKeys.owner(client?.contractAddress), + () => (client ? client.owner() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsV3ZapperBaseCallbackMutation { + client: MarsV3ZapperBaseClient + msg: CallbackMsg + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsV3ZapperBaseCallbackMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.callback(msg, fee, memo, funds), + options, + ) +} +export interface MarsV3ZapperBaseUpdateOwnerMutation { + client: MarsV3ZapperBaseClient + msg: OwnerUpdate + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsV3ZapperBaseUpdateOwnerMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateOwner(msg, fee, memo, funds), + options, + ) +} +export interface MarsV3ZapperBaseCreatePositionMutation { + client: MarsV3ZapperBaseClient + msg: { + lowerTick: number + poolId: number + tokenDesired0?: Coin + tokenDesired1?: Coin + tokenMinAmount0: string + tokenMinAmount1: string + upperTick: number + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsV3ZapperBaseCreatePositionMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.createPosition(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts new file mode 100644 index 000000000..932b4f392 --- /dev/null +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts @@ -0,0 +1,67 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + owner: string +} +export type ExecuteMsg = + | { + create_position: NewPositionRequest + } + | { + update_owner: OwnerUpdate + } + | { + callback: CallbackMsg + } +export type Uint128 = string +export type OwnerUpdate = + | { + propose_new_owner: { + proposed: string + } + } + | 'clear_proposed' + | 'accept_proposed' + | 'abolish_owner_role' + | { + set_emergency_owner: { + emergency_owner: string + } + } + | 'clear_emergency_owner' +export type CallbackMsg = { + refund_coin: { + denoms: string[] + recipient: Addr + } +} +export type Addr = string +export interface NewPositionRequest { + lower_tick: number + pool_id: number + token_desired0?: Coin | null + token_desired1?: Coin | null + token_min_amount0: string + token_min_amount1: string + upper_tick: number +} +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} +export type QueryMsg = { + owner: {} +} +export interface OwnerResponse { + abolished: boolean + emergency_owner?: string | null + initialized: boolean + owner?: string | null + proposed?: string | null +} diff --git a/scripts/types/generated/mars-v3-zapper-base/bundle.ts b/scripts/types/generated/mars-v3-zapper-base/bundle.ts new file mode 100644 index 000000000..f82bf087e --- /dev/null +++ b/scripts/types/generated/mars-v3-zapper-base/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _40 from './MarsV3ZapperBase.types' +import * as _41 from './MarsV3ZapperBase.client' +import * as _42 from './MarsV3ZapperBase.message-composer' +import * as _43 from './MarsV3ZapperBase.react-query' +export namespace contracts { + export const MarsV3ZapperBase = { ..._40, ..._41, ..._42, ..._43 } +} diff --git a/scripts/types/generated/mars-zapper-base/bundle.ts b/scripts/types/generated/mars-zapper-base/bundle.ts deleted file mode 100644 index ae32e69ea..000000000 --- a/scripts/types/generated/mars-zapper-base/bundle.ts +++ /dev/null @@ -1,14 +0,0 @@ -// @ts-nocheck -/** - * This file was automatically generated by @cosmwasm/ts-codegen@0.25.2. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run the @cosmwasm/ts-codegen generate command to regenerate this file. - */ - -import * as _36 from './MarsZapperBase.types' -import * as _37 from './MarsZapperBase.client' -import * as _38 from './MarsZapperBase.message-composer' -import * as _39 from './MarsZapperBase.react-query' -export namespace contracts { - export const MarsZapperBase = { ..._36, ..._37, ..._38, ..._39 } -} diff --git a/scripts/types/instantiateMsgs.ts b/scripts/types/instantiateMsgs.ts index dc712679c..bcfbf4501 100644 --- a/scripts/types/instantiateMsgs.ts +++ b/scripts/types/instantiateMsgs.ts @@ -4,7 +4,7 @@ import { InstantiateMsg as VaultInstantiateMsg } from './generated/mars-mock-vau import { InstantiateMsg as OracleInstantiateMsg } from './generated/mars-mock-oracle/MarsMockOracle.types' import { InstantiateMsg as RoverInstantiateMsg } from './generated/mars-credit-manager/MarsCreditManager.types' import { InstantiateMsg as SwapperInstantiateMsg } from './generated/mars-swapper-base/MarsSwapperBase.types' -import { InstantiateMsg as ZapperInstantiateMsg } from './generated/mars-zapper-base/MarsZapperBase.types' +import { InstantiateMsg as ZapperInstantiateMsg } from './generated/mars-v2-zapper-base/MarsV2ZapperBase.types' import { InstantiateMsg as HealthInstantiateMsg } from './generated/mars-rover-health-types/MarsRoverHealthTypes.types' export type InstantiateMsgs = diff --git a/scripts/yarn.lock b/scripts/yarn.lock index db28924d4..b5d7465c6 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -17,10 +17,10 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.4.tgz#457ffe647c480dff59c2be092fc3acf71195c87f" - integrity sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.5": + version "7.21.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc" + integrity sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA== "@babel/core@7.18.10": version "7.18.10" @@ -44,20 +44,20 @@ semver "^6.3.0" "@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.4.tgz#c6dc73242507b8e2a27fd13a9c1814f9fa34a659" - integrity sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA== + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.8.tgz#2a8c7f0f53d60100ba4c32470ba0281c92aa9aa4" + integrity sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.21.4" - "@babel/helper-compilation-targets" "^7.21.4" - "@babel/helper-module-transforms" "^7.21.2" - "@babel/helpers" "^7.21.0" - "@babel/parser" "^7.21.4" + "@babel/generator" "^7.21.5" + "@babel/helper-compilation-targets" "^7.21.5" + "@babel/helper-module-transforms" "^7.21.5" + "@babel/helpers" "^7.21.5" + "@babel/parser" "^7.21.8" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.4" - "@babel/types" "^7.21.4" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -73,12 +73,12 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/generator@^7.18.10", "@babel/generator@^7.21.4", "@babel/generator@^7.7.2": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.4.tgz#64a94b7448989f421f919d5239ef553b37bb26bc" - integrity sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA== +"@babel/generator@^7.18.10", "@babel/generator@^7.21.5", "@babel/generator@^7.7.2": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f" + integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w== dependencies: - "@babel/types" "^7.21.4" + "@babel/types" "^7.21.5" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" @@ -91,45 +91,46 @@ "@babel/types" "^7.18.6" "@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" - integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.21.5.tgz#817f73b6c59726ab39f6ba18c234268a519e5abb" + integrity sha512-uNrjKztPLkUk7bpCNC0jEKDJzzkvel/W+HguzbN8krA+LPfC1CEobJEvAvGka2A/M+ViOqXdcRL0GqPUJSjx9g== dependencies: - "@babel/helper-explode-assignable-expression" "^7.18.6" - "@babel/types" "^7.18.9" + "@babel/types" "^7.21.5" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz#770cd1ce0889097ceacb99418ee6934ef0572656" - integrity sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366" + integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w== dependencies: - "@babel/compat-data" "^7.21.4" + "@babel/compat-data" "^7.21.5" "@babel/helper-validator-option" "^7.21.0" browserslist "^4.21.3" lru-cache "^5.1.1" semver "^6.3.0" "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz#3a017163dc3c2ba7deb9a7950849a9586ea24c18" - integrity sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q== + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.8.tgz#205b26330258625ef8869672ebca1e0dee5a0f02" + integrity sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-environment-visitor" "^7.21.5" "@babel/helper-function-name" "^7.21.0" - "@babel/helper-member-expression-to-functions" "^7.21.0" + "@babel/helper-member-expression-to-functions" "^7.21.5" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.20.7" + "@babel/helper-replace-supers" "^7.21.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/helper-split-export-declaration" "^7.18.6" + semver "^6.3.0" "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz#40411a8ab134258ad2cf3a3d987ec6aa0723cee5" - integrity sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA== + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.8.tgz#a7886f61c2e29e21fd4aaeaf1e473deba6b571dc" + integrity sha512-zGuSdedkFtsFHGbexAvNuipg1hbtitDLo2XE8/uf6Y9sOQV1xsYX/2pNbtedp/X0eU1pIt+kGvaqHCowkRbS5g== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.3.1" + semver "^6.3.0" "@babel/helper-define-polyfill-provider@^0.3.2", "@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" @@ -143,17 +144,10 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== - -"@babel/helper-explode-assignable-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" - integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== - dependencies: - "@babel/types" "^7.18.6" +"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba" + integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ== "@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0": version "7.21.0" @@ -170,33 +164,33 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-member-expression-to-functions@^7.20.7", "@babel/helper-member-expression-to-functions@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5" - integrity sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q== +"@babel/helper-member-expression-to-functions@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz#3b1a009af932e586af77c1030fba9ee0bde396c0" + integrity sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg== dependencies: - "@babel/types" "^7.21.0" + "@babel/types" "^7.21.5" -"@babel/helper-module-imports@^7.18.6": +"@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4": version "7.21.4" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af" integrity sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg== dependencies: "@babel/types" "^7.21.4" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" - integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz#d937c82e9af68d31ab49039136a222b17ac0b420" + integrity sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw== dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-environment-visitor" "^7.21.5" + "@babel/helper-module-imports" "^7.21.4" + "@babel/helper-simple-access" "^7.21.5" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.19.1" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.2" - "@babel/types" "^7.21.2" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -205,10 +199,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" - integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.21.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56" + integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg== "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" @@ -220,24 +214,24 @@ "@babel/helper-wrap-function" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331" - integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A== +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7", "@babel/helper-replace-supers@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz#a6ad005ba1c7d9bc2973dfde05a1bba7065dde3c" + integrity sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg== dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.20.7" + "@babel/helper-environment-visitor" "^7.21.5" + "@babel/helper-member-expression-to-functions" "^7.21.5" "@babel/helper-optimise-call-expression" "^7.18.6" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.7" - "@babel/types" "^7.20.7" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" -"@babel/helper-simple-access@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" - integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== +"@babel/helper-simple-access@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee" + integrity sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg== dependencies: - "@babel/types" "^7.20.2" + "@babel/types" "^7.21.5" "@babel/helper-skip-transparent-expression-wrappers@^7.20.0": version "7.20.0" @@ -253,10 +247,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-string-parser@^7.18.10", "@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== +"@babel/helper-string-parser@^7.18.10", "@babel/helper-string-parser@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" + integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" @@ -278,14 +272,14 @@ "@babel/traverse" "^7.20.5" "@babel/types" "^7.20.5" -"@babel/helpers@^7.18.9", "@babel/helpers@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e" - integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== +"@babel/helpers@^7.18.9", "@babel/helpers@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.5.tgz#5bac66e084d7a4d2d9696bdf0175a93f7fb63c08" + integrity sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA== dependencies: "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.0" - "@babel/types" "^7.21.0" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" "@babel/highlight@^7.18.6": version "7.18.6" @@ -301,10 +295,10 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.20.7", "@babel/parser@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17" - integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.20.7", "@babel/parser@^7.21.5", "@babel/parser@^7.21.8": + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8" + integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -526,7 +520,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-syntax-import-meta@^7.8.3": +"@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== @@ -610,12 +604,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-arrow-functions@^7.18.6", "@babel/plugin-transform-arrow-functions@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz#bea332b0e8b2dab3dafe55a163d8227531ab0551" - integrity sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ== +"@babel/plugin-transform-arrow-functions@^7.18.6", "@babel/plugin-transform-arrow-functions@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929" + integrity sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-transform-async-to-generator@^7.18.6", "@babel/plugin-transform-async-to-generator@^7.20.7": version "7.20.7" @@ -655,12 +649,12 @@ "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.18.9", "@babel/plugin-transform-computed-properties@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz#704cc2fd155d1c996551db8276d55b9d46e4d0aa" - integrity sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ== +"@babel/plugin-transform-computed-properties@^7.18.9", "@babel/plugin-transform-computed-properties@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz#3a2d8bb771cd2ef1cd736435f6552fe502e11b44" + integrity sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/template" "^7.20.7" "@babel/plugin-transform-destructuring@^7.18.9", "@babel/plugin-transform-destructuring@^7.21.3": @@ -693,12 +687,12 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-for-of@^7.18.8", "@babel/plugin-transform-for-of@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz#964108c9988de1a60b4be2354a7d7e245f36e86e" - integrity sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ== +"@babel/plugin-transform-for-of@^7.18.8", "@babel/plugin-transform-for-of@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc" + integrity sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-transform-function-name@^7.18.9": version "7.18.9" @@ -731,14 +725,14 @@ "@babel/helper-module-transforms" "^7.20.11" "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-modules-commonjs@^7.18.6", "@babel/plugin-transform-modules-commonjs@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz#6ff5070e71e3192ef2b7e39820a06fb78e3058e7" - integrity sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA== +"@babel/plugin-transform-modules-commonjs@^7.18.6", "@babel/plugin-transform-modules-commonjs@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz#d69fb947eed51af91de82e4708f676864e5e47bc" + integrity sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ== dependencies: - "@babel/helper-module-transforms" "^7.21.2" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-module-transforms" "^7.21.5" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-simple-access" "^7.21.5" "@babel/plugin-transform-modules-systemjs@^7.18.9", "@babel/plugin-transform-modules-systemjs@^7.20.11": version "7.20.11" @@ -795,12 +789,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-regenerator@^7.18.6", "@babel/plugin-transform-regenerator@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz#57cda588c7ffb7f4f8483cc83bdcea02a907f04d" - integrity sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ== +"@babel/plugin-transform-regenerator@^7.18.6", "@babel/plugin-transform-regenerator@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz#576c62f9923f94bcb1c855adc53561fd7913724e" + integrity sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" regenerator-transform "^0.15.1" "@babel/plugin-transform-reserved-words@^7.18.6": @@ -868,12 +862,12 @@ "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-typescript" "^7.20.0" -"@babel/plugin-transform-unicode-escapes@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" - integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== +"@babel/plugin-transform-unicode-escapes@^7.18.10", "@babel/plugin-transform-unicode-escapes@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz#1e55ed6195259b0e9061d81f5ef45a9b009fb7f2" + integrity sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-transform-unicode-regex@^7.18.6": version "7.18.6" @@ -964,14 +958,14 @@ core-js-compat "^3.22.1" semver "^6.3.0" -"@babel/preset-env@^7.20.2": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.4.tgz#a952482e634a8dd8271a3fe5459a16eb10739c58" - integrity sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw== +"@babel/preset-env@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.5.tgz#db2089d99efd2297716f018aeead815ac3decffb" + integrity sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg== dependencies: - "@babel/compat-data" "^7.21.4" - "@babel/helper-compilation-targets" "^7.21.4" - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/compat-data" "^7.21.5" + "@babel/helper-compilation-targets" "^7.21.5" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-validator-option" "^7.21.0" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.20.7" @@ -996,6 +990,7 @@ "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" "@babel/plugin-syntax-import-assertions" "^7.20.0" + "@babel/plugin-syntax-import-meta" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" @@ -1005,22 +1000,22 @@ "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.20.7" + "@babel/plugin-transform-arrow-functions" "^7.21.5" "@babel/plugin-transform-async-to-generator" "^7.20.7" "@babel/plugin-transform-block-scoped-functions" "^7.18.6" "@babel/plugin-transform-block-scoping" "^7.21.0" "@babel/plugin-transform-classes" "^7.21.0" - "@babel/plugin-transform-computed-properties" "^7.20.7" + "@babel/plugin-transform-computed-properties" "^7.21.5" "@babel/plugin-transform-destructuring" "^7.21.3" "@babel/plugin-transform-dotall-regex" "^7.18.6" "@babel/plugin-transform-duplicate-keys" "^7.18.9" "@babel/plugin-transform-exponentiation-operator" "^7.18.6" - "@babel/plugin-transform-for-of" "^7.21.0" + "@babel/plugin-transform-for-of" "^7.21.5" "@babel/plugin-transform-function-name" "^7.18.9" "@babel/plugin-transform-literals" "^7.18.9" "@babel/plugin-transform-member-expression-literals" "^7.18.6" "@babel/plugin-transform-modules-amd" "^7.20.11" - "@babel/plugin-transform-modules-commonjs" "^7.21.2" + "@babel/plugin-transform-modules-commonjs" "^7.21.5" "@babel/plugin-transform-modules-systemjs" "^7.20.11" "@babel/plugin-transform-modules-umd" "^7.18.6" "@babel/plugin-transform-named-capturing-groups-regex" "^7.20.5" @@ -1028,17 +1023,17 @@ "@babel/plugin-transform-object-super" "^7.18.6" "@babel/plugin-transform-parameters" "^7.21.3" "@babel/plugin-transform-property-literals" "^7.18.6" - "@babel/plugin-transform-regenerator" "^7.20.5" + "@babel/plugin-transform-regenerator" "^7.21.5" "@babel/plugin-transform-reserved-words" "^7.18.6" "@babel/plugin-transform-shorthand-properties" "^7.18.6" "@babel/plugin-transform-spread" "^7.20.7" "@babel/plugin-transform-sticky-regex" "^7.18.6" "@babel/plugin-transform-template-literals" "^7.18.9" "@babel/plugin-transform-typeof-symbol" "^7.18.9" - "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-escapes" "^7.21.5" "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.21.4" + "@babel/types" "^7.21.5" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" babel-plugin-polyfill-regenerator "^0.4.1" @@ -1056,15 +1051,15 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-typescript@^7.18.6": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.4.tgz#b913ac8e6aa8932e47c21b01b4368d8aa239a529" - integrity sha512-sMLNWY37TCdRH/bJ6ZeeOH1nPuanED7Ai9Y/vH31IPqalioJ6ZNFUWONsakhv4r4n+I6gm5lmoE0olkgib/j/A== +"@babel/preset-typescript@^7.18.6", "@babel/preset-typescript@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.5.tgz#68292c884b0e26070b4d66b202072d391358395f" + integrity sha512-iqe3sETat5EOrORXiQ6rWfoOg2y68Cs75B9wNxdPW4kixJxh7aXQE1KPdWLDniC24T/6dSnguF33W9j/ZZQcmA== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-validator-option" "^7.21.0" "@babel/plugin-syntax-jsx" "^7.21.4" - "@babel/plugin-transform-modules-commonjs" "^7.21.2" + "@babel/plugin-transform-modules-commonjs" "^7.21.5" "@babel/plugin-transform-typescript" "^7.21.3" "@babel/regjsgen@^0.8.0": @@ -1073,9 +1068,9 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.11.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.8.4": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" - integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" + integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q== dependencies: regenerator-runtime "^0.13.11" @@ -1104,19 +1099,19 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.18.10", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.4", "@babel/traverse@^7.7.2": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36" - integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q== +"@babel/traverse@^7.18.10", "@babel/traverse@^7.20.5", "@babel/traverse@^7.21.5", "@babel/traverse@^7.7.2": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133" + integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw== dependencies: "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.21.4" - "@babel/helper-environment-visitor" "^7.18.9" + "@babel/generator" "^7.21.5" + "@babel/helper-environment-visitor" "^7.21.5" "@babel/helper-function-name" "^7.21.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.21.4" - "@babel/types" "^7.21.4" + "@babel/parser" "^7.21.5" + "@babel/types" "^7.21.5" debug "^4.1.0" globals "^11.1.0" @@ -1129,12 +1124,12 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.21.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.4.tgz#2d5d6bb7908699b3b416409ffd3b5daa25b030d4" - integrity sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA== +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6" + integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q== dependencies: - "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-string-parser" "^7.21.5" "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" @@ -1161,7 +1156,7 @@ "@cosmjs/math" "^0.30.1" "@cosmjs/utils" "^0.30.1" -"@cosmjs/cosmwasm-stargate@^0.30.0": +"@cosmjs/cosmwasm-stargate@^0.30.1": version "0.30.1" resolved "https://registry.yarnpkg.com/@cosmjs/cosmwasm-stargate/-/cosmwasm-stargate-0.30.1.tgz#6f9ca310f75433a3e30d683bc6aa24eadb345d79" integrity sha512-W/6SLUCJAJGBN+sJLXouLZikVgmqDd9LCdlMzQaxczcCHTWeJAmRvOiZGSZaSy3shw/JN1qc6g6PKpvTVgj10A== @@ -1238,7 +1233,7 @@ ws "^7" xstream "^11.14.0" -"@cosmjs/stargate@^0.30.0", "@cosmjs/stargate@^0.30.1": +"@cosmjs/stargate@^0.30.1": version "0.30.1" resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.30.1.tgz#e1b22e1226cffc6e93914a410755f1f61057ba04" integrity sha512-RdbYKZCGOH8gWebO7r6WvNnQMxHrNXInY/gPHPzMjbQF6UatA6fNM2G2tdgS5j5u7FTqlCI10stNXrknaNdzog== @@ -1284,10 +1279,10 @@ resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.30.1.tgz#6d92582341be3c2ec8d82090253cfa4b7f959edb" integrity sha512-KvvX58MGMWh7xA+N+deCfunkA/ZNDvFLw4YbOmX3f/XBIkqrVY7qlotfy2aNb1kgp6h4B6Yc8YawJPDTfvWX7g== -"@cosmwasm/ts-codegen@^0.25.2": - version "0.25.2" - resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.25.2.tgz#68bbddbafbbc4488446cf2c2986318e18ec65446" - integrity sha512-XCrEXOgFjJvkvSWg6fXh2Ika9unHCJWH2ffbeKeGnzr6x6UX1iCH+8KDcgqMPdvpIOCBaxy9keFoaAqVFue9Ag== +"@cosmwasm/ts-codegen@^0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.27.0.tgz#dcc74eec41aa599a7a5e2a18a4f97b74c008c92f" + integrity sha512-aNZFf7MjVv0D/LX4xpPgv1nao3+G1Mq19rjnEVgRhUKoTILl8RFoyK8dCo2LOIlSg3trdl2nlk6luYpI4AHPZw== dependencies: "@babel/core" "7.18.10" "@babel/generator" "7.18.12" @@ -1315,7 +1310,7 @@ parse-package-name "1.0.0" rimraf "3.0.2" shelljs "0.8.5" - wasm-ast-types "^0.18.2" + wasm-ast-types "^0.20.0" "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" @@ -1325,9 +1320,9 @@ eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.4.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.0.tgz#f6f729b02feee2c749f57e334b7a1b5f40a81724" - integrity sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ== + version "4.5.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" + integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== "@eslint/eslintrc@^2.0.2": version "2.0.2" @@ -1817,9 +1812,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.18.4" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.4.tgz#0fa6be6c182288831be8c31c35dab6d6ccac47c5" - integrity sha512-TLG7CsGZZmX9aDF78UuJxnNTfQyRUFU0OYIVyIblr0/wd/HvsIo8wmuB90CszeD2MtLLAE9Tt4cWvk+KVkyGIw== + version "7.18.5" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.5.tgz#c107216842905afafd3b6e774f6f935da6f5db80" + integrity sha512-enCvTL8m/EHS/zIvJno9nE+ndYPh1/oNFzRYRmtUqJICG2VnCSBzMLW5VN2KCQU91f23tsNKR8v7VJJQMatl7Q== dependencies: "@babel/types" "^7.3.0" @@ -1857,7 +1852,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.4.0": +"@types/jest@^29.5.1": version "29.5.1" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.1.tgz#83c818aa9a87da27d6da85d3378e5a34d2f31a47" integrity sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ== @@ -1886,9 +1881,9 @@ integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/node@*", "@types/node@>=13.7.0": - version "18.16.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.1.tgz#5db121e9c5352925bb1f1b892c4ae620e3526799" - integrity sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA== + version "18.16.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.3.tgz#6bda7819aae6ea0b386ebc5b24bdf602f1b42b01" + integrity sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q== "@types/prettier@^2.1.5", "@types/prettier@^2.6.1": version "2.7.2" @@ -1917,15 +1912,15 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.54.1": - version "5.59.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.1.tgz#9b09ee1541bff1d2cebdcb87e7ce4a4003acde08" - integrity sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg== +"@typescript-eslint/eslint-plugin@^5.59.2": + version "5.59.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.2.tgz#684a2ce7182f3b4dac342eef7caa1c2bae476abd" + integrity sha512-yVrXupeHjRxLDcPKL10sGQ/QlVrA8J5IYOEWVqk0lJaSZP7X5DfnP7Ns3cc74/blmbipQ1htFNVGsHX6wsYm0A== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.59.1" - "@typescript-eslint/type-utils" "5.59.1" - "@typescript-eslint/utils" "5.59.1" + "@typescript-eslint/scope-manager" "5.59.2" + "@typescript-eslint/type-utils" "5.59.2" + "@typescript-eslint/utils" "5.59.2" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -1933,72 +1928,72 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.54.1": - version "5.59.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.1.tgz#73c2c12127c5c1182d2e5b71a8fa2a85d215cbb4" - integrity sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g== +"@typescript-eslint/parser@^5.59.2": + version "5.59.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.2.tgz#c2c443247901d95865b9f77332d9eee7c55655e8" + integrity sha512-uq0sKyw6ao1iFOZZGk9F8Nro/8+gfB5ezl1cA06SrqbgJAt0SRoFhb9pXaHvkrxUpZaoLxt8KlovHNk8Gp6/HQ== dependencies: - "@typescript-eslint/scope-manager" "5.59.1" - "@typescript-eslint/types" "5.59.1" - "@typescript-eslint/typescript-estree" "5.59.1" + "@typescript-eslint/scope-manager" "5.59.2" + "@typescript-eslint/types" "5.59.2" + "@typescript-eslint/typescript-estree" "5.59.2" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.59.1": - version "5.59.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.1.tgz#8a20222719cebc5198618a5d44113705b51fd7fe" - integrity sha512-mau0waO5frJctPuAzcxiNWqJR5Z8V0190FTSqRw1Q4Euop6+zTwHAf8YIXNwDOT29tyUDrQ65jSg9aTU/H0omA== +"@typescript-eslint/scope-manager@5.59.2": + version "5.59.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz#f699fe936ee4e2c996d14f0fdd3a7da5ba7b9a4c" + integrity sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA== dependencies: - "@typescript-eslint/types" "5.59.1" - "@typescript-eslint/visitor-keys" "5.59.1" + "@typescript-eslint/types" "5.59.2" + "@typescript-eslint/visitor-keys" "5.59.2" -"@typescript-eslint/type-utils@5.59.1": - version "5.59.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.1.tgz#63981d61684fd24eda2f9f08c0a47ecb000a2111" - integrity sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw== +"@typescript-eslint/type-utils@5.59.2": + version "5.59.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.2.tgz#0729c237503604cd9a7084b5af04c496c9a4cdcf" + integrity sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ== dependencies: - "@typescript-eslint/typescript-estree" "5.59.1" - "@typescript-eslint/utils" "5.59.1" + "@typescript-eslint/typescript-estree" "5.59.2" + "@typescript-eslint/utils" "5.59.2" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.59.1": - version "5.59.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.1.tgz#03f3fedd1c044cb336ebc34cc7855f121991f41d" - integrity sha512-dg0ICB+RZwHlysIy/Dh1SP+gnXNzwd/KS0JprD3Lmgmdq+dJAJnUPe1gNG34p0U19HvRlGX733d/KqscrGC1Pg== +"@typescript-eslint/types@5.59.2": + version "5.59.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.2.tgz#b511d2b9847fe277c5cb002a2318bd329ef4f655" + integrity sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w== -"@typescript-eslint/typescript-estree@5.59.1": - version "5.59.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.1.tgz#4aa546d27fd0d477c618f0ca00b483f0ec84c43c" - integrity sha512-lYLBBOCsFltFy7XVqzX0Ju+Lh3WPIAWxYpmH/Q7ZoqzbscLiCW00LeYCdsUnnfnj29/s1WovXKh2gwCoinHNGA== +"@typescript-eslint/typescript-estree@5.59.2": + version "5.59.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz#6e2fabd3ba01db5d69df44e0b654c0b051fe9936" + integrity sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q== dependencies: - "@typescript-eslint/types" "5.59.1" - "@typescript-eslint/visitor-keys" "5.59.1" + "@typescript-eslint/types" "5.59.2" + "@typescript-eslint/visitor-keys" "5.59.2" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.59.1": - version "5.59.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.1.tgz#d89fc758ad23d2157cfae53f0b429bdf15db9473" - integrity sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA== +"@typescript-eslint/utils@5.59.2": + version "5.59.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.2.tgz#0c45178124d10cc986115885688db6abc37939f4" + integrity sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.59.1" - "@typescript-eslint/types" "5.59.1" - "@typescript-eslint/typescript-estree" "5.59.1" + "@typescript-eslint/scope-manager" "5.59.2" + "@typescript-eslint/types" "5.59.2" + "@typescript-eslint/typescript-estree" "5.59.2" eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.59.1": - version "5.59.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.1.tgz#0d96c36efb6560d7fb8eb85de10442c10d8f6058" - integrity sha512-6waEYwBTCWryx0VJmP7JaM4FpipLsFl9CvYf2foAE8Qh/Y0s+bxWysciwOs0LTBED4JCaNxTZ5rGadB14M6dwA== +"@typescript-eslint/visitor-keys@5.59.2": + version "5.59.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz#37a419dc2723a3eacbf722512b86d6caf7d3b750" + integrity sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig== dependencies: - "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/types" "5.59.2" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -2322,9 +2317,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001449: - version "1.0.30001481" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz#f58a717afe92f9e69d0e35ff64df596bfad93912" - integrity sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ== + version "1.0.30001482" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001482.tgz#8b3fad73dc35b2674a5c96df2d4f9f1c561435de" + integrity sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ== case@1.6.3: version "1.6.3" @@ -2509,7 +2504,7 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cosmjs-types@^0.7.1: +cosmjs-types@^0.7.1, cosmjs-types@^0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.7.2.tgz#a757371abd340949c5bd5d49c6f8379ae1ffd7e2" integrity sha512-vf2uLyktjr/XVAgEq0DjMxeAWh1yYREe7AMHDKd7EiHVqxBPCaBS+qEEQUkXbR9ndnckqr1sUG8BQhazh4X5lA== @@ -2604,9 +2599,9 @@ dotty@0.1.2: integrity sha512-V0EWmKeH3DEhMwAZ+8ZB2Ao4OK6p++Z0hsDtZq3N0+0ZMVqkzrcEGROvOnZpLnvBg5PTNG23JEDLAm64gPaotQ== electron-to-chromium@^1.4.284: - version "1.4.372" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.372.tgz#7888ac92ccb9556627c3a37eba3b89ee5ac345f8" - integrity sha512-MrlFq/j+TYHOjeWsWGYfzevc25HNeJdsF6qaLFrqBTRWZQtWkb1myq/Q2veLWezVaa5OcSZ99CFwTT4aF4Mung== + version "1.4.380" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.380.tgz#195dc59d930c6b74efbee6f0e6a267ce4af5ed91" + integrity sha512-XKGdI4pWM78eLH2cbXJHiBnWUwFSzZM7XujsB6stDiGu9AeSqziedP6amNLpJzE3i0rLTcfAwdCTs5ecP5yeSg== elliptic@^6.5.4: version "6.5.4" @@ -2694,7 +2689,7 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-config-prettier@^8.7.0: +eslint-config-prettier@^8.8.0: version "8.8.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348" integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== @@ -2720,7 +2715,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== -eslint@^8.36.0: +eslint@^8.39.0: version "8.39.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.39.0.tgz#7fd20a295ef92d43809e914b70c39fd5a23cf3f1" integrity sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og== @@ -3945,7 +3940,7 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -long@^5.2.0, long@^5.2.1: +long@^5.2.0, long@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== @@ -4068,10 +4063,10 @@ minipass@^3.0.0: dependencies: yallist "^4.0.0" -minipass@^4.0.0: - version "4.2.8" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" - integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== minizlib@^2.1.1: version "2.1.2" @@ -4317,7 +4312,7 @@ prepend-file@^2.0.1: dependencies: temp-write "^4.0.0" -prettier@^2.6.2, prettier@^2.8.4: +prettier@^2.6.2, prettier@^2.8.8: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -4748,13 +4743,13 @@ symbol-observable@^2.0.3: integrity sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA== tar@^6.1.0: - version "6.1.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" - integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== + version "6.1.14" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.14.tgz#e87926bec1cfe7c9e783a77a79f3e81c1cfa3b66" + integrity sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" - minipass "^4.0.0" + minipass "^5.0.0" minizlib "^2.1.1" mkdirp "^1.0.3" yallist "^4.0.0" @@ -4973,10 +4968,10 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -wasm-ast-types@^0.18.2: - version "0.18.2" - resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.18.2.tgz#2161f390e7a2e4ad45a46bfdeda6d2d56801ece5" - integrity sha512-SKsuc56aYcl2UQM1Zm8IJZ2efvNAYhu/pPgH2OJZvUahGDi8W+jqnrKx9hIJeKW2GQdhboIIPFKY7PihWzgzgQ== +wasm-ast-types@^0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.20.0.tgz#93782528318620072ce9650f0e16861c8c620c08" + integrity sha512-EBEf0PaBGwK8eAKjG5SPDY6Dj1AEG4x07paGaJ+hAhltAsF2PgfBikR3i1UIdFjj8BFb2nGwlRTfWPH1ll9Dyg== dependencies: "@babel/runtime" "^7.18.9" "@babel/types" "7.18.10" @@ -5083,9 +5078,9 @@ yargs@^16.1.0: yargs-parser "^20.2.2" yargs@^17.3.1: - version "17.7.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967" - integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw== + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1" escalade "^3.1.1" From 3502d6389c0aa26022bb24c689fb79ee87426737 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 9 May 2023 12:11:14 +0200 Subject: [PATCH 159/218] Update cw-721 to 0.17.0 (#137) * update cw-721 version * updating build scripts * Adding migration endpoint --- Cargo.lock | 170 +++++++++---- Cargo.toml | 36 +-- contracts/account-nft/Cargo.toml | 2 + contracts/account-nft/src/contract.rs | 61 +++-- contracts/account-nft/src/error.rs | 5 + contracts/account-nft/src/execute.rs | 53 +---- contracts/account-nft/src/msg/execute.rs | 10 +- contracts/account-nft/src/msg/query.rs | 5 + contracts/account-nft/src/nft_config.rs | 3 - .../account-nft/tests/helpers/mock_env.rs | 37 +-- .../account-nft/tests/test_burn_allowance.rs | 19 +- .../account-nft/tests/test_instantiate.rs | 12 +- contracts/account-nft/tests/test_migration.rs | 135 +++++++++++ contracts/account-nft/tests/test_mint.rs | 27 +-- .../account-nft/tests/test_proposed_minter.rs | 12 +- .../account-nft/tests/test_update_config.rs | 4 - contracts/credit-manager/src/contract.rs | 5 +- contracts/credit-manager/src/update_config.rs | 36 ++- .../credit-manager/tests/helpers/mock_env.rs | 26 +- .../tests/test_update_nft_config.rs | 76 +++--- packages/rover/src/msg/execute.rs | 3 +- .../mars-account-nft/mars-account-nft.json | 225 +++++++++++++++--- .../mars-credit-manager.json | 32 ++- .../mars-mock-credit-manager.json | 2 +- .../mars-mock-oracle/mars-mock-oracle.json | 2 +- .../mars-mock-red-bank.json | 2 +- schemas/mars-mock-vault/mars-mock-vault.json | 2 +- .../mars-rover-health-types.json | 2 +- .../mars-swapper-base/mars-swapper-base.json | 2 +- .../mars-v2-zapper-base.json | 2 +- .../mars-v3-zapper-base.json | 2 +- scripts/deploy/base/deployer.ts | 6 +- .../mars-account-nft/MarsAccountNft.client.ts | 56 +++-- .../MarsAccountNft.message-composer.ts | 37 +-- .../MarsAccountNft.react-query.ts | 59 +++-- .../mars-account-nft/MarsAccountNft.types.ts | 28 ++- .../MarsCreditManager.client.ts | 15 +- .../MarsCreditManager.message-composer.ts | 15 +- .../MarsCreditManager.react-query.ts | 3 +- .../MarsCreditManager.types.ts | 4 +- 40 files changed, 865 insertions(+), 368 deletions(-) create mode 100644 contracts/account-nft/tests/test_migration.rs diff --git a/Cargo.lock b/Cargo.lock index 7a6271126..ad4c5bc99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,6 +446,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-address-like" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451a4691083a88a3c0630a8a88799e9d4cd6679b7ce8ff22b8da2873ff31d380" +dependencies = [ + "cosmwasm-std", +] + [[package]] name = "cw-dex" version = "0.1.3" @@ -492,6 +501,32 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-ownable" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093dfb4520c48b5848274dd88ea99e280a04bc08729603341c7fb0d758c74321" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-address-like", + "cw-ownable-derive", + "cw-storage-plus 1.0.1", + "cw-utils 1.0.1", + "thiserror", +] + +[[package]] +name = "cw-ownable-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d3bf2e0f341bb6cc100d7d441d31cf713fbd3ce0c511f91e79f14b40a889af" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "cw-paginate" version = "0.2.1" @@ -548,7 +583,7 @@ checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 1.0.1", + "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "schemars", "semver", "serde", @@ -594,6 +629,19 @@ dependencies = [ "serde", ] +[[package]] +name = "cw2" +version = "1.0.1" +source = "git+https://github.com/mars-protocol/cw-plus?rev=1a3a944#1a3a944b64cf6e9fcfada48f2b09aaa1a90aef74" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw20" version = "1.0.1" @@ -620,6 +668,18 @@ dependencies = [ "serde", ] +[[package]] +name = "cw721" +version = "0.17.0" +source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#f8600e6a760ce6ad340ce286262c55f471b2fb70" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 1.0.1", + "schemars", + "serde", +] + [[package]] name = "cw721-base" version = "0.16.0" @@ -631,7 +691,25 @@ dependencies = [ "cw-storage-plus 0.16.0", "cw-utils 0.16.0", "cw2 0.16.0", - "cw721", + "cw721 0.16.0", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw721-base" +version = "0.17.0" +source = "git+https://github.com/CosmWasm/cw-nfts/?branch=main#f8600e6a760ce6ad340ce286262c55f471b2fb70" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-ownable", + "cw-storage-plus 1.0.1", + "cw-utils 1.0.1", + "cw2 1.0.1 (git+https://github.com/mars-protocol/cw-plus?rev=1a3a944)", + "cw721 0.17.0", + "cw721-base 0.16.0", "schemars", "serde", "thiserror", @@ -1203,9 +1281,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" dependencies = [ "cpufeatures", ] @@ -1224,9 +1302,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.142" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libloading" @@ -1249,24 +1327,26 @@ dependencies = [ [[package]] name = "mars-account-nft" -version = "1.0.0" +version = "2.0.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", "cw-storage-plus 1.0.1", - "cw2 1.0.1", - "cw721", - "cw721-base", + "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cw721 0.17.0", + "cw721-base 0.16.0", + "cw721-base 0.17.0", "mars-mock-rover-health", "mars-rover-health-types", + "serde_json", "thiserror", ] [[package]] name = "mars-credit-manager" -version = "1.0.0" +version = "2.0.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -1277,9 +1357,9 @@ dependencies = [ "cw-storage-plus 1.0.1", "cw-utils 1.0.1", "cw-vault-standard", - "cw2 1.0.1", - "cw721", - "cw721-base", + "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cw721 0.17.0", + "cw721-base 0.17.0", "itertools", "mars-account-nft", "mars-mock-oracle", @@ -1296,7 +1376,7 @@ dependencies = [ [[package]] name = "mars-mock-credit-manager" -version = "1.0.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1309,7 +1389,7 @@ dependencies = [ [[package]] name = "mars-mock-oracle" -version = "1.0.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1319,7 +1399,7 @@ dependencies = [ [[package]] name = "mars-mock-red-bank" -version = "1.0.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1330,7 +1410,7 @@ dependencies = [ [[package]] name = "mars-mock-rover-health" -version = "1.0.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1340,7 +1420,7 @@ dependencies = [ [[package]] name = "mars-mock-vault" -version = "1.0.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1389,15 +1469,15 @@ dependencies = [ [[package]] name = "mars-rover" -version = "1.0.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.0.1", "cw-utils 1.0.1", "cw-vault-standard", - "cw721", - "cw721-base", + "cw721 0.17.0", + "cw721-base 0.17.0", "mars-account-nft", "mars-owner", "mars-red-bank-types", @@ -1409,7 +1489,7 @@ dependencies = [ [[package]] name = "mars-rover-health" -version = "1.0.0" +version = "2.0.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -1418,7 +1498,7 @@ dependencies = [ "cw-storage-plus 1.0.1", "cw-utils 1.0.1", "cw-vault-standard", - "cw2 1.0.1", + "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "mars-mock-credit-manager", "mars-mock-oracle", "mars-mock-red-bank", @@ -1433,7 +1513,7 @@ dependencies = [ [[package]] name = "mars-rover-health-computer" -version = "1.0.0" +version = "2.0.0" dependencies = [ "console_error_panic_hook", "cosmwasm-schema", @@ -1451,7 +1531,7 @@ dependencies = [ [[package]] name = "mars-rover-health-types" -version = "1.0.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1461,7 +1541,7 @@ dependencies = [ [[package]] name = "mars-swapper-base" -version = "1.0.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1476,7 +1556,7 @@ dependencies = [ [[package]] name = "mars-swapper-mock" -version = "1.0.0" +version = "2.0.0" dependencies = [ "anyhow", "cosmwasm-std", @@ -1488,13 +1568,13 @@ dependencies = [ [[package]] name = "mars-swapper-osmosis" -version = "1.0.0" +version = "2.0.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.0.1", - "cw2 1.0.1", + "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "mars-osmosis", "mars-owner", "mars-rover", @@ -1517,7 +1597,7 @@ dependencies = [ [[package]] name = "mars-v2-zapper-base" -version = "1.0.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1530,7 +1610,7 @@ dependencies = [ [[package]] name = "mars-v2-zapper-mock" -version = "1.0.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1542,12 +1622,12 @@ dependencies = [ [[package]] name = "mars-v2-zapper-osmosis" -version = "1.0.0" +version = "2.0.0" dependencies = [ "cosmwasm-std", "cw-dex", "cw-utils 1.0.1", - "cw2 1.0.1", + "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "mars-v2-zapper-base", "osmosis-std 0.15.2", "osmosis-test-tube", @@ -1555,11 +1635,11 @@ dependencies = [ [[package]] name = "mars-v3-zapper-base" -version = "1.0.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 1.0.1", + "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "mars-owner", "schemars", "serde", @@ -1568,7 +1648,7 @@ dependencies = [ [[package]] name = "mars-v3-zapper-osmosis" -version = "1.0.0" +version = "2.0.0" dependencies = [ "anyhow", "cosmwasm-std", @@ -1925,9 +2005,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -2144,9 +2224,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.160" +version = "1.0.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" dependencies = [ "serde_derive", ] @@ -2191,9 +2271,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6" dependencies = [ "proc-macro2", "quote", @@ -2270,9 +2350,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c2bb1a323307527314a36bfb73f24febb08ce2b8a554bf4ffd6f51ad15198c" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ "digest 0.10.6", "keccak", diff --git a/Cargo.toml b/Cargo.toml index e4b13701e..81002fc37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ ] [workspace.package] -version = "1.0.0" +version = "2.0.0" authors = [ "Gabe R. ", "Larry Engineer ", @@ -40,8 +40,8 @@ anyhow = "1.0.71" cosmwasm-schema = "1.2.5" cosmwasm-std = "1.2.5" cw2 = "1.0.1" -cw721 = "0.16.0" -cw721-base = { version = "0.16.0", features = ["library"] } +cw721 = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main" } +cw721-base = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main", features = ["library"] } cw-item-set = { version = "0.7.1", default-features = false, features = ["iterator"] } cw-multi-test = "0.16.4" cw-paginate = "0.2.1" @@ -60,28 +60,28 @@ wasm-bindgen = "0.2.84" # packages cw-dex = { version = "0.1.3", features = ["osmosis"] } cw-vault-standard = { version = "0.3.0", features = ["lockup", "force-unlock"] } -mars-rover-health-computer = { version = "1.0.0", path = "./packages/health-computer" } -mars-rover-health-types = { version = "1.0.0", path = "./packages/health-types" } +mars-rover-health-computer = { version = "2.0.0", path = "./packages/health-computer" } +mars-rover-health-types = { version = "2.0.0", path = "./packages/health-types" } mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "00301d60c38af09d8eb7980355009e2f00c6f41f" } mars-red-bank-types = "1.0.0" mars-owner = { version = "1.1.0", features = ["emergency-owner"] } -mars-rover = { version = "1.0.0", path = "./packages/rover" } +mars-rover = { version = "2.0.0", path = "./packages/rover" } # contracts -mars-account-nft = { version = "1.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-rover-health = { version = "1.0.0", path = "./contracts/health" } -mars-swapper-base = { version = "1.0.0", path = "./contracts/swapper/base" } -mars-v2-zapper-base = { version = "1.0.0", path = "./contracts/v2-zapper/base" } -mars-v3-zapper-base = { version = "1.0.0", path = "./contracts/v3-zapper/base" } +mars-account-nft = { version = "2.0.0", path = "./contracts/account-nft", features = ["library"] } +mars-rover-health = { version = "2.0.0", path = "./contracts/health" } +mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base" } +mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base" } +mars-v3-zapper-base = { version = "2.0.0", path = "./contracts/v3-zapper/base" } # mocks -mars-mock-credit-manager = { version = "1.0.0", path = "./contracts/mock-credit-manager", features = ["library"] } -mars-mock-oracle = { version = "1.0.0", path = "./contracts/mock-oracle", features = ["library"] } -mars-mock-red-bank = { version = "1.0.0", path = "./contracts/mock-red-bank", features = ["library"] } -mars-mock-vault = { version = "1.0.0", path = "./contracts/mock-vault", features = ["library"] } -mars-mock-rover-health = { version = "1.0.0", path = "./contracts/mock-health", features = ["library"] } -mars-swapper-mock = { version = "1.0.0", path = "./contracts/swapper/mock", features = ["library"] } -mars-v2-zapper-mock = { version = "1.0.0", path = "./contracts/v2-zapper/mock", features = ["library"] } +mars-mock-credit-manager = { version = "2.0.0", path = "./contracts/mock-credit-manager", features = ["library"] } +mars-mock-oracle = { version = "2.0.0", path = "./contracts/mock-oracle", features = ["library"] } +mars-mock-red-bank = { version = "2.0.0", path = "./contracts/mock-red-bank", features = ["library"] } +mars-mock-vault = { version = "2.0.0", path = "./contracts/mock-vault", features = ["library"] } +mars-mock-rover-health = { version = "2.0.0", path = "./contracts/mock-health", features = ["library"] } +mars-swapper-mock = { version = "2.0.0", path = "./contracts/swapper/mock", features = ["library"] } +mars-v2-zapper-mock = { version = "2.0.0", path = "./contracts/v2-zapper/mock", features = ["library"] } [profile.release] codegen-units = 1 diff --git a/contracts/account-nft/Cargo.toml b/contracts/account-nft/Cargo.toml index 38939890a..83c16ca20 100644 --- a/contracts/account-nft/Cargo.toml +++ b/contracts/account-nft/Cargo.toml @@ -30,5 +30,7 @@ thiserror = { workspace = true } [dev-dependencies] anyhow = { workspace = true } +cw721-base-v16 = { package = "cw721-base", version = "0.16.0" } cw-multi-test = { workspace = true } mars-mock-rover-health = { workspace = true } +serde_json = { workspace = true } diff --git a/contracts/account-nft/src/contract.rs b/contracts/account-nft/src/contract.rs index fc0ba99ba..2b8e0f8cc 100644 --- a/contracts/account-nft/src/contract.rs +++ b/contracts/account-nft/src/contract.rs @@ -5,13 +5,12 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{ to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; -use cw2::set_contract_version; -use cw721::ContractInfoResponse; +use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw721_base::Cw721Contract; use crate::{ - error::ContractError, - execute::{accept_minter_role, burn, mint, update_config}, + error::{ContractError, ContractError::MigrationError}, + execute::{burn, mint, update_config}, msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, nft_config::NftConfig, query::{query_config, query_next_id}, @@ -27,37 +26,29 @@ pub type Parent<'a> = Cw721Contract<'a, Empty, Empty, Empty, Empty>; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, - _: Env, - _: MessageInfo, + env: Env, + info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; NEXT_ID.save(deps.storage, &1)?; - let health_contract_addr = - msg.health_contract.map(|unchecked| deps.api.addr_validate(&unchecked)).transpose()?; + let health_contract_addr = msg + .health_contract + .as_ref() + .map(|unchecked| deps.api.addr_validate(unchecked)) + .transpose()?; CONFIG.save( deps.storage, &NftConfig { max_value_for_burn: msg.max_value_for_burn, - proposed_new_minter: None, health_contract_addr, }, )?; - // Parent::default().instantiate() copied below - // Cannot use given it overrides contract version - let info = ContractInfoResponse { - name: msg.name, - symbol: msg.symbol, - }; - Parent::default().contract_info.save(deps.storage, &info)?; - let minter = deps.api.addr_validate(&msg.minter)?; - Parent::default().minter.save(deps.storage, &minter)?; - - Ok(Response::default()) + Parent::default().instantiate(deps, env, info, msg.into()) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -70,11 +61,10 @@ pub fn execute( match msg { ExecuteMsg::Mint { user, - } => mint(deps, env, info, &user), + } => mint(deps, info, &user), ExecuteMsg::UpdateConfig { updates, } => update_config(deps, info, updates), - ExecuteMsg::AcceptMinterRole {} => accept_minter_role(deps, info), ExecuteMsg::Burn { token_id, } => burn(deps, env, info, token_id), @@ -90,3 +80,30 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { _ => Parent::default().query(deps, env, msg.try_into()?), } } + +const FROM_VERSION: &str = "1.0.0"; +const TO_VERSION: &str = "2.0.0"; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { + let ContractVersion { + contract, + version, + } = get_contract_version(deps.storage)?; + + if CONTRACT_NAME != contract { + return Err(MigrationError { + reason: format!("Wrong contract. Expected: {CONTRACT_NAME}, Found: {contract}"), + }); + } + + if FROM_VERSION != version { + return Err(MigrationError { + reason: format!("Wrong version. Expected: {FROM_VERSION}, Found: {version}"), + }); + } + + set_contract_version(deps.storage, CONTRACT_NAME, TO_VERSION)?; + + Ok(cw721_base::upgrades::v0_17::migrate::(deps)?) +} diff --git a/contracts/account-nft/src/error.rs b/contracts/account-nft/src/error.rs index becfc015b..74600ac72 100644 --- a/contracts/account-nft/src/error.rs +++ b/contracts/account-nft/src/error.rs @@ -20,4 +20,9 @@ pub enum ContractError { #[error("Health contract should be added to config before burns are allowed")] HealthContractNotSet, + + #[error("{reason:?}")] + MigrationError { + reason: String, + }, } diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index 77a6b123f..76a6bc71e 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -2,7 +2,10 @@ use cosmwasm_std::{ to_binary, DepsMut, Empty, Env, MessageInfo, QueryRequest, Response, WasmQuery, }; use cw721::Cw721Execute; -use cw721_base::MintMsg; +use cw721_base::{ + ContractError::Ownership, + OwnershipError::{NoOwner, NotOwner}, +}; use mars_rover_health_types::{HealthResponse, QueryMsg::Health}; use crate::{ @@ -15,22 +18,12 @@ use crate::{ state::{CONFIG, NEXT_ID}, }; -pub fn mint( - deps: DepsMut, - env: Env, - info: MessageInfo, - user: &str, -) -> Result { +pub fn mint(deps: DepsMut, info: MessageInfo, user: &str) -> Result { let next_id = NEXT_ID.load(deps.storage)?; - let mint_msg_override = MintMsg { - token_id: next_id.to_string(), - owner: user.to_string(), - token_uri: None, - extension: Empty {}, - }; NEXT_ID.save(deps.storage, &(next_id + 1))?; - - Parent::default().mint(deps, env, info, mint_msg_override).map_err(Into::into) + Parent::default() + .mint(deps, info, next_id.to_string(), user.to_string(), None, Empty {}) + .map_err(Into::into) } /// A few checks to ensure accounts are not accidentally deleted: @@ -77,9 +70,11 @@ pub fn update_config( info: MessageInfo, updates: NftConfigUpdates, ) -> Result { - let current_minter = Parent::default().minter.load(deps.storage)?; + let current_minter = + Parent::default().minter(deps.as_ref())?.minter.ok_or(BaseError(Ownership(NoOwner)))?; + if info.sender != current_minter { - return Err(BaseError(cw721_base::ContractError::Unauthorized {})); + return Err(BaseError(Ownership(NotOwner))); } let mut response = Response::new().add_attribute("action", "update_config"); @@ -100,31 +95,7 @@ pub fn update_config( .add_attribute("value", max.to_string()); } - if let Some(addr) = updates.proposed_new_minter { - let validated = deps.api.addr_validate(&addr)?; - config.proposed_new_minter = Some(validated); - response = response.add_attribute("key", "pending_minter").add_attribute("value", addr); - } - CONFIG.save(deps.storage, &config)?; Ok(response) } - -pub fn accept_minter_role(deps: DepsMut, info: MessageInfo) -> Result { - let mut config = CONFIG.load(deps.storage)?; - let previous_minter = Parent::default().minter.load(deps.storage)?; - - match config.proposed_new_minter { - Some(addr) if addr == info.sender => { - Parent::default().minter.save(deps.storage, &addr)?; - config.proposed_new_minter = None; - CONFIG.save(deps.storage, &config)?; - - Ok(Response::new() - .add_attribute("previous_minter", previous_minter) - .add_attribute("new_minter", addr)) - } - _ => Err(BaseError(cw721_base::ContractError::Unauthorized {})), - } -} diff --git a/contracts/account-nft/src/msg/execute.rs b/contracts/account-nft/src/msg/execute.rs index 859988138..0edcd394f 100644 --- a/contracts/account-nft/src/msg/execute.rs +++ b/contracts/account-nft/src/msg/execute.rs @@ -3,7 +3,7 @@ use std::convert::TryInto; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Binary, Empty, StdError}; use cw721::Expiration; -use cw721_base::ExecuteMsg as ParentExecuteMsg; +use cw721_base::{Action, ExecuteMsg as ParentExecuteMsg}; use crate::{error::ContractError, nft_config::NftConfigUpdates}; @@ -16,15 +16,10 @@ pub enum ExecuteMsg { UpdateConfig { updates: NftConfigUpdates, }, - - /// Accept the proposed minter role. Only the proposed new minter can execute. - AcceptMinterRole {}, - /// Mint a new NFT to the specified user; can only be called by the contract minter Mint { user: String, }, - /// Burn an NFT the sender has access to. Will attempt to query the Credit Manager first /// to ensure the balance is below the config set threshold. Burn { @@ -68,6 +63,8 @@ pub enum ExecuteMsg { RevokeAll { operator: String, }, + /// Propose new owner (minter) and accept new role + UpdateOwnership(Action), } impl TryInto> for ExecuteMsg { @@ -119,6 +116,7 @@ impl TryInto> for ExecuteMsg { } => Ok(ParentExecuteMsg::RevokeAll { operator, }), + ExecuteMsg::UpdateOwnership(action) => Ok(ParentExecuteMsg::UpdateOwnership(action)), _ => Err(StdError::generic_err( "Attempting to convert to a non-cw721 compatible message", ) diff --git a/contracts/account-nft/src/msg/query.rs b/contracts/account-nft/src/msg/query.rs index d37ed9126..45aeff2c1 100644 --- a/contracts/account-nft/src/msg/query.rs +++ b/contracts/account-nft/src/msg/query.rs @@ -95,6 +95,10 @@ pub enum QueryMsg { /// Return the minter #[returns(cw721_base::MinterResponse)] Minter {}, + + /// Returns ownership state of nft contract + #[returns(cw721_base::Ownership)] + Ownership {}, } impl TryInto> for QueryMsg { @@ -167,6 +171,7 @@ impl TryInto> for QueryMsg { limit, }), QueryMsg::Minter {} => Ok(ParentQueryMsg::Minter {}), + QueryMsg::Ownership {} => Ok(ParentQueryMsg::Ownership {}), _ => Err(StdError::generic_err( "Attempting to convert to a non-cw721 compatible message", )), diff --git a/contracts/account-nft/src/nft_config.rs b/contracts/account-nft/src/nft_config.rs index 6dc3c4aa2..5564b7871 100644 --- a/contracts/account-nft/src/nft_config.rs +++ b/contracts/account-nft/src/nft_config.rs @@ -4,7 +4,6 @@ use cosmwasm_std::{Addr, Uint128}; #[cw_serde] pub struct NftConfigBase { pub max_value_for_burn: Uint128, - pub proposed_new_minter: Option, pub health_contract_addr: Option, } @@ -15,7 +14,6 @@ impl From for UncheckedNftConfig { fn from(config: NftConfig) -> Self { Self { max_value_for_burn: config.max_value_for_burn, - proposed_new_minter: config.proposed_new_minter.map(Into::into), health_contract_addr: config.health_contract_addr.map(Into::into), } } @@ -24,6 +22,5 @@ impl From for UncheckedNftConfig { #[cw_serde] pub struct NftConfigUpdates { pub max_value_for_burn: Option, - pub proposed_new_minter: Option, pub health_contract_addr: Option, } diff --git a/contracts/account-nft/tests/helpers/mock_env.rs b/contracts/account-nft/tests/helpers/mock_env.rs index d3c8e54e3..efb163fc1 100644 --- a/contracts/account-nft/tests/helpers/mock_env.rs +++ b/contracts/account-nft/tests/helpers/mock_env.rs @@ -1,13 +1,14 @@ use anyhow::Result as AnyResult; -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Empty}; use cw721::OwnerOfResponse; +use cw721_base::{ + Action::{AcceptOwnership, TransferOwnership}, + ExecuteMsg::UpdateOwnership, + Ownership, +}; use cw_multi_test::{App, AppResponse, BasicApp, Executor}; use mars_account_nft::{ - msg::{ - ExecuteMsg, - ExecuteMsg::{AcceptMinterRole, UpdateConfig}, - QueryMsg, - }, + msg::{ExecuteMsg, ExecuteMsg::UpdateConfig, QueryMsg}, nft_config::{NftConfigUpdates, UncheckedNftConfig}, }; use mars_mock_rover_health::msg::ExecuteMsg::SetHealthResponse; @@ -39,6 +40,13 @@ impl MockEnv { self.app.wrap().query_wasm_smart(self.nft_contract.clone(), &QueryMsg::Config {}).unwrap() } + pub fn query_ownership(&mut self) -> Ownership { + self.app + .wrap() + .query_wasm_smart(self.nft_contract.clone(), &QueryMsg::Ownership {}) + .unwrap() + } + pub fn query_next_id(&mut self) -> u64 { self.app.wrap().query_wasm_smart(self.nft_contract.clone(), &QueryMsg::NextId {}).unwrap() } @@ -118,13 +126,14 @@ impl MockEnv { sender: &Addr, proposed_new_minter: &Addr, ) -> AnyResult { - self.update_config( - sender, - &NftConfigUpdates { - max_value_for_burn: None, - proposed_new_minter: Some(proposed_new_minter.to_string()), - health_contract_addr: None, - }, + self.app.execute_contract( + sender.clone(), + self.nft_contract.clone(), + &UpdateOwnership::(TransferOwnership { + new_owner: proposed_new_minter.to_string(), + expiry: None, + }), + &[], ) } @@ -132,7 +141,7 @@ impl MockEnv { self.app.execute_contract( sender.clone(), self.nft_contract.clone(), - &AcceptMinterRole {}, + &UpdateOwnership::(AcceptOwnership), &[], ) } diff --git a/contracts/account-nft/tests/test_burn_allowance.rs b/contracts/account-nft/tests/test_burn_allowance.rs index f13b664fe..000fdce69 100644 --- a/contracts/account-nft/tests/test_burn_allowance.rs +++ b/contracts/account-nft/tests/test_burn_allowance.rs @@ -2,10 +2,11 @@ use std::ops::Add; use cosmwasm_std::{Addr, Empty, StdResult, Uint128}; use cw721::NftInfoResponse; +use cw721_base::{ContractError::Ownership, OwnershipError::NotOwner}; use mars_account_nft::{ error::{ ContractError, - ContractError::{BurnNotAllowed, HealthContractNotSet}, + ContractError::{BaseError, BurnNotAllowed, HealthContractNotSet}, }, msg::QueryMsg::NftInfo, }; @@ -14,6 +15,22 @@ use crate::helpers::{below_max_for_burn, generate_health_response, MockEnv, MAX_ pub mod helpers; +#[test] +fn only_token_owner_can_burn() { + let mut mock = MockEnv::new().build().unwrap(); + + let user = Addr::unchecked("user"); + let token_id = mock.mint(&user).unwrap(); + mock.set_health_response(&user, &token_id, &below_max_for_burn()); + + let bad_guy = Addr::unchecked("bad_guy"); + let res = mock.burn(&bad_guy, &token_id); + let err: ContractError = res.unwrap_err().downcast().unwrap(); + assert_eq!(err, BaseError(Ownership(NotOwner))); + + mock.burn(&user, &token_id).unwrap(); +} + #[test] fn burn_not_allowed_if_no_health_contract_set() { let mut mock = MockEnv::new().instantiate_with_health_contract(false).build().unwrap(); diff --git a/contracts/account-nft/tests/test_instantiate.rs b/contracts/account-nft/tests/test_instantiate.rs index 2de246d3b..45234f7b5 100644 --- a/contracts/account-nft/tests/test_instantiate.rs +++ b/contracts/account-nft/tests/test_instantiate.rs @@ -4,13 +4,20 @@ pub mod helpers; #[test] fn instantiated_storage_vars() { - let mut mock = MockEnv::new().instantiate_with_health_contract(false).build().unwrap(); + let mut mock = MockEnv::new() + .set_minter("spiderman_1337") + .instantiate_with_health_contract(false) + .build() + .unwrap(); let config = mock.query_config(); - assert_eq!(config.proposed_new_minter, None); assert_eq!(config.health_contract_addr, None); assert_eq!(config.max_value_for_burn, MAX_VALUE_FOR_BURN); + let ownership = mock.query_ownership(); + assert_eq!("spiderman_1337", ownership.owner.unwrap()); + assert_eq!(None, ownership.pending_owner); + let next_id = mock.query_next_id(); assert_eq!(next_id, 1); } @@ -21,7 +28,6 @@ fn instantiated_storage_vars_with_health_contract() { let mut mock = MockEnv::new().set_health_contract(health_contract).build().unwrap(); let config = mock.query_config(); - assert_eq!(config.proposed_new_minter, None); assert_eq!(config.health_contract_addr, Some(health_contract.to_string())); assert_eq!(config.max_value_for_burn, MAX_VALUE_FOR_BURN); diff --git a/contracts/account-nft/tests/test_migration.rs b/contracts/account-nft/tests/test_migration.rs new file mode 100644 index 000000000..e5727d6d9 --- /dev/null +++ b/contracts/account-nft/tests/test_migration.rs @@ -0,0 +1,135 @@ +use cosmwasm_std::{ + attr, + testing::{mock_dependencies, mock_env, mock_info}, + Addr, Empty, Event, +}; +use cw2::{get_contract_version, set_contract_version, ContractVersion}; +use cw721_base::{Cw721Contract, Ownership, QueryMsg}; +use cw721_base_v16::{ + msg::InstantiateMsg as Cw721v16InstantiateMsg, Cw721Contract as Cw721ContractV16, +}; +use mars_account_nft::{contract::migrate, error::ContractError::MigrationError}; + +pub mod helpers; + +#[test] +fn invalid_contract_name() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + let old_contract_version = ContractVersion { + contract: "WRONG_CONTRACT_NAME".to_string(), + version: "1.0.0".to_string(), + }; + + set_contract_version( + deps.as_mut().storage, + old_contract_version.contract.clone(), + old_contract_version.version, + ) + .unwrap(); + + let err = migrate(deps.as_mut(), env, Empty {}).unwrap_err(); + assert_eq!( + MigrationError { + reason: "Wrong contract. Expected: mars-account-nft, Found: WRONG_CONTRACT_NAME" + .to_string() + }, + err + ); +} + +#[test] +fn invalid_contract_version() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + let old_contract_version = ContractVersion { + contract: "mars-account-nft".to_string(), + version: "4.4.5".to_string(), + }; + + set_contract_version( + deps.as_mut().storage, + old_contract_version.contract.clone(), + old_contract_version.version, + ) + .unwrap(); + + let err = migrate(deps.as_mut(), env, Empty {}).unwrap_err(); + assert_eq!( + MigrationError { + reason: "Wrong version. Expected: 1.0.0, Found: 4.4.5".to_string() + }, + err + ); +} + +#[test] +fn proper_migration() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let minter = "nft-minter-abc"; + + let old_contract_version = ContractVersion { + contract: "mars-account-nft".to_string(), + version: "1.0.0".to_string(), + }; + + let info = mock_info("creator", &[]); + Cw721ContractV16::::default() + .instantiate( + deps.as_mut(), + env.clone(), + info, + Cw721v16InstantiateMsg { + name: "nft-contract".to_string(), + symbol: "xyz".to_string(), + minter: minter.to_string(), + }, + ) + .unwrap(); + + set_contract_version( + deps.as_mut().storage, + old_contract_version.contract.clone(), + old_contract_version.version.clone(), + ) + .unwrap(); + + assert_eq!(get_contract_version(deps.as_ref().storage).unwrap(), old_contract_version); + + let res = migrate(deps.as_mut(), env.clone(), Empty {}).unwrap(); + + let new_contract_version = ContractVersion { + contract: "mars-account-nft".to_string(), + version: "2.0.0".to_string(), + }; + assert_eq!(get_contract_version(deps.as_ref().storage).unwrap(), new_contract_version); + + assert_eq!(res.messages, vec![]); + assert_eq!(res.events, vec![] as Vec); + assert!(res.data.is_none()); + assert_eq!( + res.attributes, + vec![ + attr("action", "migrate"), + attr("from_version", "0.16.0"), + attr("to_version", "0.17.0"), + attr("old_minter", minter), + attr("owner", minter), + attr("pending_owner", "none"), + attr("pending_expiry", "none"), + ] + ); + + let binary = Cw721Contract::::default() + .query(deps.as_ref(), env, QueryMsg::Ownership {}) + .unwrap(); + + let ownership = serde_json::from_slice::>(binary.as_slice()).unwrap(); + + assert_eq!(ownership.owner, Some(Addr::unchecked(minter))); + assert_eq!(ownership.pending_owner, None); + assert_eq!(ownership.pending_expiry, None); +} diff --git a/contracts/account-nft/tests/test_mint.rs b/contracts/account-nft/tests/test_mint.rs index a4fffbb9c..3e554bfab 100644 --- a/contracts/account-nft/tests/test_mint.rs +++ b/contracts/account-nft/tests/test_mint.rs @@ -1,8 +1,6 @@ -use std::fmt::Error; - use cosmwasm_std::Addr; use cw721::OwnerOfResponse; -use cw721_base::ContractError::Unauthorized; +use cw721_base::{ContractError::Ownership, OwnershipError::NotOwner}; use cw_multi_test::Executor; use mars_account_nft::{ error::{ContractError, ContractError::BaseError}, @@ -68,23 +66,7 @@ fn only_minter_can_mint() { &[], ); let err: ContractError = res.unwrap_err().downcast().unwrap(); - assert_eq!(err, BaseError(Unauthorized {})) -} - -#[test] -fn only_token_owner_can_burn() { - let mut mock = MockEnv::new().build().unwrap(); - - let user = Addr::unchecked("user"); - let token_id = mock.mint(&user).unwrap(); - mock.set_health_response(&user, &token_id, &below_max_for_burn()); - - let bad_guy = Addr::unchecked("bad_guy"); - let res = mock.burn(&bad_guy, &token_id); - let err: ContractError = res.unwrap_err().downcast().unwrap(); - assert_eq!(err, BaseError(Unauthorized {})); - - mock.burn(&user, &token_id).unwrap(); + assert_eq!(err, BaseError(Ownership(NotOwner))) } #[test] @@ -99,10 +81,7 @@ fn normal_base_cw721_actions_can_still_be_taken() { token_id: token_id.clone(), recipient: rover_user_b.clone().into(), }; - mock.app - .execute_contract(rover_user_a, mock.nft_contract.clone(), &transfer_msg, &[]) - .map_err(|_| Error::default()) - .unwrap(); + mock.app.execute_contract(rover_user_a, mock.nft_contract.clone(), &transfer_msg, &[]).unwrap(); let res: OwnerOfResponse = mock .app diff --git a/contracts/account-nft/tests/test_proposed_minter.rs b/contracts/account-nft/tests/test_proposed_minter.rs index 7738ba3d6..1d0990f12 100644 --- a/contracts/account-nft/tests/test_proposed_minter.rs +++ b/contracts/account-nft/tests/test_proposed_minter.rs @@ -25,8 +25,8 @@ fn propose_minter_stores() { let new_minter = Addr::unchecked("new_minter"); mock.propose_new_minter(&mock.minter.clone(), &new_minter).unwrap(); - let config = mock.query_config(); - assert_eq!(config.proposed_new_minter.unwrap(), new_minter); + let ownership = mock.query_ownership(); + assert_eq!(ownership.pending_owner.unwrap(), new_minter); } #[test] @@ -38,15 +38,17 @@ fn proposed_minter_can_accept_role() { mock.accept_proposed_minter(&new_minter).unwrap(); - let config = mock.query_config(); - if config.proposed_new_minter.is_some() { + let ownership = mock.query_ownership(); + if ownership.pending_owner.is_some() { panic!("Proposed minter should have been removed from storage"); } + assert_eq!(ownership.owner.unwrap().to_string(), new_minter.to_string()); + let res: MinterResponse = mock.app.wrap().query_wasm_smart(mock.nft_contract, &QueryMsg::Minter {}).unwrap(); - assert_eq!(res.minter, new_minter) + assert_eq!(res.minter.unwrap(), new_minter.to_string()); } #[test] diff --git a/contracts/account-nft/tests/test_update_config.rs b/contracts/account-nft/tests/test_update_config.rs index 2a5b51af0..ee4e40909 100644 --- a/contracts/account-nft/tests/test_update_config.rs +++ b/contracts/account-nft/tests/test_update_config.rs @@ -14,7 +14,6 @@ fn only_minter_can_update_config() { &bad_guy, &NftConfigUpdates { max_value_for_burn: None, - proposed_new_minter: None, health_contract_addr: None, }, ); @@ -29,12 +28,10 @@ fn minter_can_update_config() { let mut mock = MockEnv::new().build().unwrap(); let new_max_burn_val = Uint128::new(4918453); - let new_proposed_minter = "new_proposed_minter".to_string(); let new_health_contract = "new_health_contract_123".to_string(); let updates = NftConfigUpdates { max_value_for_burn: Some(new_max_burn_val), - proposed_new_minter: Some(new_proposed_minter.clone()), health_contract_addr: Some(new_health_contract.clone()), }; @@ -42,6 +39,5 @@ fn minter_can_update_config() { let config = mock.query_config(); assert_eq!(config.max_value_for_burn, new_max_burn_val); - assert_eq!(config.proposed_new_minter.unwrap(), new_proposed_minter); assert_eq!(config.health_contract_addr.unwrap(), new_health_contract); } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 48ce67dea..afbe72e9e 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -54,8 +54,9 @@ pub fn execute( updates, } => update_config(deps, info, updates), ExecuteMsg::UpdateNftConfig { - updates, - } => update_nft_config(deps, info, updates), + config, + ownership, + } => update_nft_config(deps, info, config, ownership), ExecuteMsg::UpdateOwner(update) => update_owner(deps, info, update), ExecuteMsg::Callback(callback) => execute_callback(deps, info, env, callback), ExecuteMsg::UpdateCreditAccount { diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index 1d738ba1d..50e010314 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -1,4 +1,5 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, MessageInfo, Response, WasmMsg}; +use cw721_base::Action; use mars_account_nft::{msg::ExecuteMsg as NftExecuteMsg, nft_config::NftConfigUpdates}; use mars_owner::OwnerUpdate; use mars_rover::{ @@ -32,7 +33,7 @@ pub fn update_config( let accept_minter_role_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: addr_str.clone(), funds: vec![], - msg: to_binary(&NftExecuteMsg::AcceptMinterRole {})?, + msg: to_binary(&NftExecuteMsg::UpdateOwnership(Action::AcceptOwnership))?, }); response = response @@ -124,19 +125,34 @@ pub fn update_owner( pub fn update_nft_config( deps: DepsMut, info: MessageInfo, - updates: NftConfigUpdates, + config: Option, + ownership: Option, ) -> ContractResult { OWNER.assert_owner(deps.storage, &info.sender)?; let nft_contract = ACCOUNT_NFT.load(deps.storage)?; + let mut response = Response::new(); - let update_config_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: nft_contract.to_string(), - funds: vec![], - msg: to_binary(&NftExecuteMsg::UpdateConfig { - updates, - })?, - }); + if let Some(updates) = config { + let update_config_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: nft_contract.to_string(), + funds: vec![], + msg: to_binary(&NftExecuteMsg::UpdateConfig { + updates, + })?, + }); + response = response.add_message(update_config_msg).add_attribute("action", "update_config") + } - Ok(Response::new().add_attribute("action", "update_nft_config").add_message(update_config_msg)) + if let Some(action) = ownership { + let update_ownership_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: nft_contract.to_string(), + funds: vec![], + msg: to_binary(&NftExecuteMsg::UpdateOwnership(action))?, + }); + response = + response.add_message(update_ownership_msg).add_attribute("action", "update_ownership") + } + + Ok(response) } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index d248c94e4..07d508adc 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -2,6 +2,7 @@ use std::mem::take; use anyhow::Result as AnyResult; use cosmwasm_std::{coins, testing::MockApi, Addr, Coin, Decimal, StdResult, Uint128}; +use cw721_base::{Action::TransferOwnership, Ownership}; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use cw_vault_standard::{ extensions::lockup::{LockupQueryMsg, UnlockingPosition}, @@ -195,13 +196,15 @@ impl MockEnv { pub fn update_nft_config( &mut self, sender: &Addr, - updates: NftConfigUpdates, + config: Option, + ownership: Option, ) -> AnyResult { self.app.execute_contract( sender.clone(), self.rover.clone(), &ExecuteMsg::UpdateNftConfig { - updates, + config, + ownership, }, &[], ) @@ -315,6 +318,14 @@ impl MockEnv { .unwrap() } + pub fn query_nft_ownership(&self) -> Ownership { + let config = self.query_config(); + self.app + .wrap() + .query_wasm_smart(config.account_nft.unwrap(), &NftQueryMsg::Ownership {}) + .unwrap() + } + pub fn query_vault_config(&self, vault: &VaultUnchecked) -> StdResult { self.app.wrap().query_wasm_smart( self.rover.clone(), @@ -1133,12 +1144,9 @@ fn deploy_nft_contract(app: &mut App, minter: &Addr) -> Addr { } fn propose_new_nft_minter(app: &mut App, nft_contract: Addr, old_minter: &Addr, new_minter: &Addr) { - let proposal_msg: NftExecuteMsg = NftExecuteMsg::UpdateConfig { - updates: NftConfigUpdates { - max_value_for_burn: None, - proposed_new_minter: Some(new_minter.into()), - health_contract_addr: None, - }, - }; + let proposal_msg: NftExecuteMsg = NftExecuteMsg::UpdateOwnership(TransferOwnership { + new_owner: new_minter.into(), + expiry: None, + }); app.execute_contract(old_minter.clone(), nft_contract, &proposal_msg, &[]).unwrap(); } diff --git a/contracts/credit-manager/tests/test_update_nft_config.rs b/contracts/credit-manager/tests/test_update_nft_config.rs index 742184896..f4d19b112 100644 --- a/contracts/credit-manager/tests/test_update_nft_config.rs +++ b/contracts/credit-manager/tests/test_update_nft_config.rs @@ -11,36 +11,48 @@ use crate::helpers::{assert_err, MockEnv}; pub mod helpers; #[test] -fn only_owner_can_update_config() { +fn only_owner_can_update_nft_config() { let mut mock = MockEnv::new().build().unwrap(); let bad_guy = Addr::unchecked("bad_guy"); // Attempt updating from Rover let res = mock.update_nft_config( &bad_guy, - NftConfigUpdates { - max_value_for_burn: None, - proposed_new_minter: Some(bad_guy.to_string()), - health_contract_addr: None, - }, + None, + Some(cw721_base::Action::TransferOwnership { + new_owner: bad_guy.to_string(), + expiry: None, + }), ); assert_err(res, ContractError::Owner(NotOwner {})); // Attempt updating directly from the NFT contract + let account_nft_contract = Addr::unchecked(mock.query_config().account_nft.unwrap()); mock.app .execute_contract( bad_guy.clone(), - Addr::unchecked(mock.query_config().account_nft.unwrap()), + account_nft_contract.clone(), &ExecuteMsg::UpdateConfig { updates: NftConfigUpdates { max_value_for_burn: None, - proposed_new_minter: Some(bad_guy.to_string()), health_contract_addr: None, }, }, &[], ) .unwrap_err(); + + mock.app + .execute_contract( + bad_guy.clone(), + account_nft_contract, + &ExecuteMsg::UpdateOwnership(cw721_base::Action::TransferOwnership { + new_owner: bad_guy.to_string(), + expiry: None, + }), + &[], + ) + .unwrap_err(); } #[test] @@ -49,15 +61,15 @@ fn raises_on_invalid_config() { let res = mock.update_nft_config( &Addr::unchecked(mock.query_config().owner.unwrap()), - NftConfigUpdates { - max_value_for_burn: None, - proposed_new_minter: Some("".to_string()), - health_contract_addr: None, - }, + None, + Some(cw721_base::Action::TransferOwnership { + new_owner: "".to_string(), + expiry: None, + }), ); if res.is_ok() { - panic!("should have thrown error due to bad proposed_new_minter input") + panic!("should have thrown error due to bad new_owner input") } } @@ -65,51 +77,59 @@ fn raises_on_invalid_config() { fn update_config_works_with_full_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_nft_config(); + let original_ownership = mock.query_nft_ownership(); let new_max_value = Some(Uint128::new(1122334455)); - let new_proposed = Some("spiderman_12345".to_string()); + let new_proposed = Some(Addr::unchecked("spiderman_12345")); let new_health_contract = Some("new_health_contract_xyz".to_string()); mock.update_nft_config( &Addr::unchecked(mock.query_config().owner.unwrap()), - NftConfigUpdates { + Some(NftConfigUpdates { max_value_for_burn: new_max_value, - proposed_new_minter: new_proposed.clone(), health_contract_addr: new_health_contract.clone(), - }, + }), + Some(cw721_base::Action::TransferOwnership { + new_owner: new_proposed.clone().unwrap().into(), + expiry: None, + }), ) .unwrap(); let new_config = mock.query_nft_config(); assert_eq!(Some(new_config.max_value_for_burn), new_max_value); - assert_eq!(new_config.proposed_new_minter, new_proposed); assert_eq!(new_config.health_contract_addr, new_health_contract); assert_ne!(new_config.max_value_for_burn, original_config.max_value_for_burn); - assert_ne!(new_config.proposed_new_minter, original_config.proposed_new_minter); assert_ne!(new_config.health_contract_addr, original_config.health_contract_addr); + + let new_ownership = mock.query_nft_ownership(); + assert_eq!(new_ownership.pending_owner, new_proposed); + assert_ne!(new_ownership.pending_owner, original_ownership.pending_owner); } #[test] fn update_config_works_with_some_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_nft_config(); + let original_ownership = mock.query_nft_ownership(); - let new_proposed = Some("spiderman_12345".to_string()); + let new_proposed = Some(Addr::unchecked("spiderman_12345")); mock.update_nft_config( &Addr::unchecked(mock.query_config().owner.unwrap()), - NftConfigUpdates { - max_value_for_burn: None, - proposed_new_minter: new_proposed.clone(), - health_contract_addr: None, - }, + None, + Some(cw721_base::Action::TransferOwnership { + new_owner: new_proposed.clone().unwrap().into(), + expiry: None, + }), ) .unwrap(); let new_config = mock.query_nft_config(); assert_eq!(new_config.max_value_for_burn, original_config.max_value_for_burn); - assert_eq!(new_config.proposed_new_minter, new_proposed); assert_eq!(new_config.health_contract_addr, original_config.health_contract_addr); - assert_ne!(new_config.proposed_new_minter, original_config.proposed_new_minter); + let new_ownership = mock.query_nft_ownership(); + assert_eq!(new_ownership.pending_owner, new_proposed); + assert_ne!(new_ownership.pending_owner, original_ownership.pending_owner); } diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index b17617e87..50205746f 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -51,7 +51,8 @@ pub enum ExecuteMsg { UpdateOwner(OwnerUpdate), /// Update nft contract config UpdateNftConfig { - updates: NftConfigUpdates, + config: Option, + ownership: Option, }, /// Internal actions only callable by the contract itself Callback(CallbackMsg), diff --git a/schemas/mars-account-nft/mars-account-nft.json b/schemas/mars-account-nft/mars-account-nft.json index 4681339cb..80b1b6554 100644 --- a/schemas/mars-account-nft/mars-account-nft.json +++ b/schemas/mars-account-nft/mars-account-nft.json @@ -1,6 +1,6 @@ { "contract_name": "mars-account-nft", - "contract_version": "1.0.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -75,20 +75,6 @@ }, "additionalProperties": false }, - { - "description": "Accept the proposed minter role. Only the proposed new minter can execute.", - "type": "object", - "required": [ - "accept_minter_role" - ], - "properties": { - "accept_minter_role": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "Mint a new NFT to the specified user; can only be called by the contract minter", "type": "object", @@ -304,9 +290,73 @@ } }, "additionalProperties": false + }, + { + "description": "Propose new owner (minter) and accept new role", + "type": "object", + "required": [ + "update_ownership" + ], + "properties": { + "update_ownership": { + "$ref": "#/definitions/Action" + } + }, + "additionalProperties": false } ], "definitions": { + "Action": { + "description": "Actions that can be taken to alter the contract's ownership", + "oneOf": [ + { + "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", + "type": "object", + "required": [ + "transfer_ownership" + ], + "properties": { + "transfer_ownership": { + "type": "object", + "required": [ + "new_owner" + ], + "properties": { + "expiry": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "new_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", + "type": "string", + "enum": [ + "accept_ownership" + ] + }, + { + "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", + "type": "string", + "enum": [ + "renounce_ownership" + ] + } + ] + }, "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" @@ -376,12 +426,6 @@ "type": "null" } ] - }, - "proposed_new_minter": { - "type": [ - "string", - "null" - ] } }, "additionalProperties": false @@ -724,6 +768,20 @@ } }, "additionalProperties": false + }, + { + "description": "Returns ownership state of nft contract", + "type": "object", + "required": [ + "ownership" + ], + "properties": { + "ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -999,7 +1057,7 @@ ], "properties": { "tokens": { - "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", + "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_after` in future queries to achieve pagination.", "type": "array", "items": { "type": "string" @@ -1221,12 +1279,6 @@ }, "max_value_for_burn": { "$ref": "#/definitions/Uint128" - }, - "proposed_new_minter": { - "type": [ - "string", - "null" - ] } }, "additionalProperties": false, @@ -1260,12 +1312,12 @@ "title": "MinterResponse", "description": "Shows who can mint these tokens", "type": "object", - "required": [ - "minter" - ], "properties": { "minter": { - "type": "string" + "type": [ + "string", + "null" + ] } }, "additionalProperties": false @@ -1431,6 +1483,113 @@ } } }, + "ownership": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Ownership_for_Addr", + "description": "The contract's ownership info", + "type": "object", + "properties": { + "owner": { + "description": "The contract's current owner. `None` if the ownership has been renounced.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "pending_expiry": { + "description": "The deadline for the pending owner to accept the ownership. `None` if there isn't a pending ownership transfer, or if a transfer exists and it doesn't have a deadline.", + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "pending_owner": { + "description": "The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "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" + } + ] + }, + "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" + } + } + }, "tokens": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "TokensResponse", @@ -1440,7 +1599,7 @@ ], "properties": { "tokens": { - "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", + "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_after` in future queries to achieve pagination.", "type": "array", "items": { "type": "string" diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 619afabc3..2f1fa9d3a 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -1,6 +1,6 @@ { "contract_name": "mars-credit-manager", - "contract_version": "1.0.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -314,12 +314,26 @@ "properties": { "update_nft_config": { "type": "object", - "required": [ - "updates" - ], "properties": { - "updates": { - "$ref": "#/definitions/NftConfigUpdates" + "config": { + "anyOf": [ + { + "$ref": "#/definitions/NftConfigUpdates" + }, + { + "type": "null" + } + ] + }, + "ownership": { + "anyOf": [ + { + "$ref": "#/definitions/Action" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -1565,12 +1579,6 @@ "type": "null" } ] - }, - "proposed_new_minter": { - "type": [ - "string", - "null" - ] } }, "additionalProperties": false diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 3860e0a93..b38ee0311 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -1,6 +1,6 @@ { "contract_name": "mars-mock-credit-manager", - "contract_version": "1.0.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-mock-oracle/mars-mock-oracle.json b/schemas/mars-mock-oracle/mars-mock-oracle.json index 8ed327af9..58f742def 100644 --- a/schemas/mars-mock-oracle/mars-mock-oracle.json +++ b/schemas/mars-mock-oracle/mars-mock-oracle.json @@ -1,6 +1,6 @@ { "contract_name": "mars-mock-oracle", - "contract_version": "1.0.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-mock-red-bank/mars-mock-red-bank.json b/schemas/mars-mock-red-bank/mars-mock-red-bank.json index 314bff7d0..d2ce8f303 100644 --- a/schemas/mars-mock-red-bank/mars-mock-red-bank.json +++ b/schemas/mars-mock-red-bank/mars-mock-red-bank.json @@ -1,6 +1,6 @@ { "contract_name": "mars-mock-red-bank", - "contract_version": "1.0.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-mock-vault/mars-mock-vault.json b/schemas/mars-mock-vault/mars-mock-vault.json index 8d8b9da27..9671ec64b 100644 --- a/schemas/mars-mock-vault/mars-mock-vault.json +++ b/schemas/mars-mock-vault/mars-mock-vault.json @@ -1,6 +1,6 @@ { "contract_name": "mars-mock-vault", - "contract_version": "1.0.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-rover-health-types/mars-rover-health-types.json b/schemas/mars-rover-health-types/mars-rover-health-types.json index c9c6a5462..711268178 100644 --- a/schemas/mars-rover-health-types/mars-rover-health-types.json +++ b/schemas/mars-rover-health-types/mars-rover-health-types.json @@ -1,6 +1,6 @@ { "contract_name": "mars-rover-health-types", - "contract_version": "1.0.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-swapper-base/mars-swapper-base.json b/schemas/mars-swapper-base/mars-swapper-base.json index 0b88dbcc9..62150a9fb 100644 --- a/schemas/mars-swapper-base/mars-swapper-base.json +++ b/schemas/mars-swapper-base/mars-swapper-base.json @@ -1,6 +1,6 @@ { "contract_name": "mars-swapper-base", - "contract_version": "1.0.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-v2-zapper-base/mars-v2-zapper-base.json b/schemas/mars-v2-zapper-base/mars-v2-zapper-base.json index ee99422c9..5c4c31a01 100644 --- a/schemas/mars-v2-zapper-base/mars-v2-zapper-base.json +++ b/schemas/mars-v2-zapper-base/mars-v2-zapper-base.json @@ -1,6 +1,6 @@ { "contract_name": "mars-v2-zapper-base", - "contract_version": "1.0.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json b/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json index 114c6a917..5ba2d9688 100644 --- a/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json +++ b/schemas/mars-v3-zapper-base/mars-v3-zapper-base.json @@ -1,6 +1,6 @@ { "contract_name": "mars-v3-zapper-base", - "contract_version": "1.0.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index d56d1b671..84beb3a2e 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -207,8 +207,10 @@ export class Deployer { this.deployerAddr, this.storage.addresses.accountNft!, ) - await nftClient.updateConfig({ - updates: { proposed_new_minter: this.storage.addresses.creditManager! }, + await nftClient.updateOwnership({ + transfer_ownership: { + new_owner: this.storage.addresses.creditManager!, + }, }) this.storage.actions.proposedNewOwner = true printBlue('Nft contract owner proposes Rover as new owner') diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts index a6616ab34..493016195 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts @@ -15,6 +15,7 @@ import { Expiration, Timestamp, Uint64, + Action, NftConfigUpdates, QueryMsg, AllNftInfoResponseForEmpty, @@ -30,6 +31,8 @@ import { ContractInfoResponse, MinterResponse, NumTokensResponse, + Addr, + OwnershipForAddr, } from './MarsAccountNft.types' export interface MarsAccountNftReadOnlyInterface { contractAddress: string @@ -96,6 +99,7 @@ export interface MarsAccountNftReadOnlyInterface { startAfter?: string }) => Promise minter: () => Promise + ownership: () => Promise } export class MarsAccountNftQueryClient implements MarsAccountNftReadOnlyInterface { client: CosmWasmClient @@ -117,6 +121,7 @@ export class MarsAccountNftQueryClient implements MarsAccountNftReadOnlyInterfac this.tokens = this.tokens.bind(this) this.allTokens = this.allTokens.bind(this) this.minter = this.minter.bind(this) + this.ownership = this.ownership.bind(this) } config = async (): Promise => { @@ -261,6 +266,11 @@ export class MarsAccountNftQueryClient implements MarsAccountNftReadOnlyInterfac minter: {}, }) } + ownership = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + ownership: {}, + }) + } } export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface { contractAddress: string @@ -275,11 +285,6 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface memo?: string, funds?: Coin[], ) => Promise - acceptMinterRole: ( - fee?: number | StdFee | 'auto', - memo?: string, - funds?: Coin[], - ) => Promise mint: ( { user, @@ -374,6 +379,12 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface memo?: string, funds?: Coin[], ) => Promise + updateOwnership: ( + action: Action, + fee?: number | StdFee | 'auto', + memo?: string, + funds?: Coin[], + ) => Promise } export class MarsAccountNftClient extends MarsAccountNftQueryClient @@ -389,7 +400,6 @@ export class MarsAccountNftClient this.sender = sender this.contractAddress = contractAddress this.updateConfig = this.updateConfig.bind(this) - this.acceptMinterRole = this.acceptMinterRole.bind(this) this.mint = this.mint.bind(this) this.burn = this.burn.bind(this) this.transferNft = this.transferNft.bind(this) @@ -398,6 +408,7 @@ export class MarsAccountNftClient this.revoke = this.revoke.bind(this) this.approveAll = this.approveAll.bind(this) this.revokeAll = this.revokeAll.bind(this) + this.updateOwnership = this.updateOwnership.bind(this) } updateConfig = async ( @@ -423,22 +434,6 @@ export class MarsAccountNftClient funds, ) } - acceptMinterRole = async ( - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - accept_minter_role: {}, - }, - fee, - memo, - funds, - ) - } mint = async ( { user, @@ -644,4 +639,21 @@ export class MarsAccountNftClient funds, ) } + updateOwnership = async ( + action: Action, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_ownership: action, + }, + fee, + memo, + funds, + ) + } } diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts index 2bfeab8d8..e946b465a 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts @@ -17,6 +17,7 @@ import { Expiration, Timestamp, Uint64, + Action, NftConfigUpdates, QueryMsg, AllNftInfoResponseForEmpty, @@ -32,6 +33,8 @@ import { ContractInfoResponse, MinterResponse, NumTokensResponse, + Addr, + OwnershipForAddr, } from './MarsAccountNft.types' export interface MarsAccountNftMessage { contractAddress: string @@ -44,7 +47,6 @@ export interface MarsAccountNftMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject - acceptMinterRole: (funds?: Coin[]) => MsgExecuteContractEncodeObject mint: ( { user, @@ -123,6 +125,7 @@ export interface MarsAccountNftMessage { }, funds?: Coin[], ) => MsgExecuteContractEncodeObject + updateOwnership: (action: Action, funds?: Coin[]) => MsgExecuteContractEncodeObject } export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { sender: string @@ -132,7 +135,6 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { this.sender = sender this.contractAddress = contractAddress this.updateConfig = this.updateConfig.bind(this) - this.acceptMinterRole = this.acceptMinterRole.bind(this) this.mint = this.mint.bind(this) this.burn = this.burn.bind(this) this.transferNft = this.transferNft.bind(this) @@ -141,6 +143,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { this.revoke = this.revoke.bind(this) this.approveAll = this.approveAll.bind(this) this.revokeAll = this.revokeAll.bind(this) + this.updateOwnership = this.updateOwnership.bind(this) } updateConfig = ( @@ -167,21 +170,6 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }), } } - acceptMinterRole = (funds?: Coin[]): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - accept_minter_role: {}, - }), - ), - funds, - }), - } - } mint = ( { user, @@ -395,4 +383,19 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }), } } + updateOwnership = (action: Action, funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_ownership: action, + }), + ), + funds, + }), + } + } } diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts index 66b91c847..2ff173a14 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts @@ -16,6 +16,7 @@ import { Expiration, Timestamp, Uint64, + Action, NftConfigUpdates, QueryMsg, AllNftInfoResponseForEmpty, @@ -31,6 +32,8 @@ import { ContractInfoResponse, MinterResponse, NumTokensResponse, + Addr, + OwnershipForAddr, } from './MarsAccountNft.types' import { MarsAccountNftQueryClient, MarsAccountNftClient } from './MarsAccountNft.client' export const marsAccountNftQueryKeys = { @@ -79,6 +82,10 @@ export const marsAccountNftQueryKeys = { ] as const, minter: (contractAddress: string | undefined, args?: Record) => [{ ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'minter', args }] as const, + ownership: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsAccountNftQueryKeys.address(contractAddress)[0], method: 'ownership', args }, + ] as const, } export interface MarsAccountNftReactQuery { client: MarsAccountNftQueryClient | undefined @@ -89,6 +96,18 @@ export interface MarsAccountNftReactQuery { initialData?: undefined } } +export interface MarsAccountNftOwnershipQuery + extends MarsAccountNftReactQuery {} +export function useMarsAccountNftOwnershipQuery({ + client, + options, +}: MarsAccountNftOwnershipQuery) { + return useQuery( + marsAccountNftQueryKeys.ownership(client?.contractAddress), + () => (client ? client.ownership() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsAccountNftMinterQuery extends MarsAccountNftReactQuery {} export function useMarsAccountNftMinterQuery({ @@ -346,6 +365,27 @@ export function useMarsAccountNftConfigQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsAccountNftUpdateOwnershipMutation { + client: MarsAccountNftClient + msg: Action + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsAccountNftUpdateOwnershipMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateOwnership(msg, fee, memo, funds), + options, + ) +} export interface MarsAccountNftRevokeAllMutation { client: MarsAccountNftClient msg: { @@ -529,25 +569,6 @@ export function useMarsAccountNftMintMutation( options, ) } -export interface MarsAccountNftAcceptMinterRoleMutation { - client: MarsAccountNftClient - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsAccountNftAcceptMinterRoleMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, args: { fee, memo, funds } = {} }) => client.acceptMinterRole(fee, memo, funds), - options, - ) -} export interface MarsAccountNftUpdateConfigMutation { client: MarsAccountNftClient msg: { diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index ea30e9d1e..e64439d97 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -19,9 +19,6 @@ export type ExecuteMsg = updates: NftConfigUpdates } } - | { - accept_minter_role: {} - } | { mint: { user: string @@ -69,6 +66,9 @@ export type ExecuteMsg = operator: string } } + | { + update_ownership: Action + } export type Binary = string export type Expiration = | { @@ -82,10 +82,18 @@ export type Expiration = } export type Timestamp = Uint64 export type Uint64 = string +export type Action = + | { + transfer_ownership: { + expiry?: Expiration | null + new_owner: string + } + } + | 'accept_ownership' + | 'renounce_ownership' export interface NftConfigUpdates { health_contract_addr?: string | null max_value_for_burn?: Uint128 | null - proposed_new_minter?: string | null } export type QueryMsg = | { @@ -154,6 +162,9 @@ export type QueryMsg = | { minter: {} } + | { + ownership: {} + } export interface AllNftInfoResponseForEmpty { access: OwnerOfResponse info: NftInfoResponseForEmpty @@ -188,15 +199,20 @@ export interface ApprovalsResponse { export interface NftConfigBaseForString { health_contract_addr?: string | null max_value_for_burn: Uint128 - proposed_new_minter?: string | null } export interface ContractInfoResponse { name: string symbol: string } export interface MinterResponse { - minter: string + minter?: string | null } export interface NumTokensResponse { count: number } +export type Addr = string +export interface OwnershipForAddr { + owner?: Addr | null + pending_expiry?: Expiration | null + pending_owner?: Addr | null +} diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 43f4f04f9..fed5b0ae3 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -440,9 +440,11 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt ) => Promise updateNftConfig: ( { - updates, + config, + ownership, }: { - updates: NftConfigUpdates + config?: NftConfigUpdates + ownership?: Action }, fee?: number | StdFee | 'auto', memo?: string, @@ -602,9 +604,11 @@ export class MarsCreditManagerClient } updateNftConfig = async ( { - updates, + config, + ownership, }: { - updates: NftConfigUpdates + config?: NftConfigUpdates + ownership?: Action }, fee: number | StdFee | 'auto' = 'auto', memo?: string, @@ -615,7 +619,8 @@ export class MarsCreditManagerClient this.contractAddress, { update_nft_config: { - updates, + config, + ownership, }, }, fee, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 427bcfb79..29eac4c4a 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -104,9 +104,11 @@ export interface MarsCreditManagerMessage { updateOwner: (ownerUpdate: OwnerUpdate, funds?: Coin[]) => MsgExecuteContractEncodeObject updateNftConfig: ( { - updates, + config, + ownership, }: { - updates: NftConfigUpdates + config?: NftConfigUpdates + ownership?: Action }, funds?: Coin[], ) => MsgExecuteContractEncodeObject @@ -254,9 +256,11 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag } updateNftConfig = ( { - updates, + config, + ownership, }: { - updates: NftConfigUpdates + config?: NftConfigUpdates + ownership?: Action }, funds?: Coin[], ): MsgExecuteContractEncodeObject => { @@ -268,7 +272,8 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag msg: toUtf8( JSON.stringify({ update_nft_config: { - updates, + config, + ownership, }, }), ), diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 2ac8d6189..70b1e938e 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -651,7 +651,8 @@ export function useMarsCreditManagerCallbackMutation( export interface MarsCreditManagerUpdateNftConfigMutation { client: MarsCreditManagerClient msg: { - updates: NftConfigUpdates + config?: NftConfigUpdates + ownership?: Action } args?: { fee?: number | StdFee | 'auto' diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 945561877..63901fd63 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -70,7 +70,8 @@ export type ExecuteMsg = } | { update_nft_config: { - updates: NftConfigUpdates + config?: NftConfigUpdates | null + ownership?: Action | null } } | { @@ -349,7 +350,6 @@ export interface ConfigUpdates { export interface NftConfigUpdates { health_contract_addr?: string | null max_value_for_burn?: Uint128 | null - proposed_new_minter?: string | null } export interface VaultBaseForAddr { address: Addr From 0989296f3da6c4b88ba30d340a8c8cf95fcabc21 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Wed, 10 May 2023 09:21:57 +0200 Subject: [PATCH 160/218] Update owner query response (#136) --- contracts/credit-manager/src/query.rs | 4 +- contracts/credit-manager/tests/test_health.rs | 6 +-- .../credit-manager/tests/test_instantiate.rs | 2 +- .../tests/test_repay_from_wallet.rs | 2 +- .../credit-manager/tests/test_update_admin.rs | 42 ++++++++-------- .../tests/test_update_config.rs | 31 ++++++------ .../tests/test_update_nft_config.rs | 6 +-- .../credit-manager/tests/test_zap_withdraw.rs | 2 +- .../health/tests/helpers/mock_env_builder.rs | 10 +++- packages/rover/src/msg/query.rs | 4 +- .../mars-credit-manager.json | 49 ++++++++++++++----- .../mars-mock-credit-manager.json | 49 ++++++++++++++----- scripts/deploy/base/deployer.ts | 2 +- .../MarsCreditManager.client.ts | 1 + .../MarsCreditManager.message-composer.ts | 1 + .../MarsCreditManager.react-query.ts | 1 + .../MarsCreditManager.types.ts | 10 +++- .../MarsMockCreditManager.client.ts | 1 + .../MarsMockCreditManager.message-composer.ts | 1 + .../MarsMockCreditManager.react-query.ts | 1 + .../MarsMockCreditManager.types.ts | 10 +++- 21 files changed, 157 insertions(+), 78 deletions(-) diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 9c86ac950..3c8f88823 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -24,10 +24,8 @@ use crate::{ const DEFAULT_LIMIT: u32 = 10; pub fn query_config(deps: Deps) -> ContractResult { - let owner_res = OWNER.query(deps.storage)?; Ok(ConfigResponse { - owner: owner_res.owner, - proposed_new_owner: owner_res.proposed, + ownership: OWNER.query(deps.storage)?, account_nft: ACCOUNT_NFT.may_load(deps.storage)?.map(|addr| addr.to_string()), red_bank: RED_BANK.load(deps.storage)?.address().into(), oracle: ORACLE.load(deps.storage)?.address().into(), diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index 610356c66..bb1e8cdc5 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -643,7 +643,7 @@ fn delisted_deposits_drop_max_ltv() { // Remove uosmo from the coin whitelist let res = mock.query_config(); mock.update_config( - &Addr::unchecked(res.owner.unwrap()), + &Addr::unchecked(res.ownership.owner.unwrap()), ConfigUpdates { allowed_coins: Some(vec![uatom_info.denom]), ..Default::default() @@ -723,7 +723,7 @@ fn delisted_vaults_drop_max_ltv() { // Blacklist vault let res = mock.query_config(); mock.update_config( - &Addr::unchecked(res.owner.unwrap()), + &Addr::unchecked(res.ownership.owner.unwrap()), ConfigUpdates { vault_configs: Some(vec![new_vault_config]), ..Default::default() @@ -791,7 +791,7 @@ fn vault_base_token_delisting_drops_max_ltv() { // Remove LP token from the coin whitelist let res = mock.query_config(); mock.update_config( - &Addr::unchecked(res.owner.unwrap()), + &Addr::unchecked(res.ownership.owner.unwrap()), ConfigUpdates { allowed_coins: Some(vec![atom.denom]), ..Default::default() diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 24a08407f..45b415c95 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -16,7 +16,7 @@ fn owner_set_on_instantiate() { let owner = "owner_addr"; let mock = MockEnv::new().owner(owner).build().unwrap(); let res = mock.query_config(); - assert_eq!(owner, res.owner.unwrap()); + assert_eq!(owner, res.ownership.owner.unwrap()); } #[test] diff --git a/contracts/credit-manager/tests/test_repay_from_wallet.rs b/contracts/credit-manager/tests/test_repay_from_wallet.rs index 68e41f6b1..a08b84f51 100644 --- a/contracts/credit-manager/tests/test_repay_from_wallet.rs +++ b/contracts/credit-manager/tests/test_repay_from_wallet.rs @@ -182,7 +182,7 @@ fn delisted_assets_can_be_repaid() { // Delist the asset let config = mock.query_config(); mock.update_config( - &Addr::unchecked(config.owner.unwrap()), + &Addr::unchecked(config.ownership.owner.unwrap()), ConfigUpdates { account_nft: None, allowed_coins: Some(vec![]), diff --git a/contracts/credit-manager/tests/test_update_admin.rs b/contracts/credit-manager/tests/test_update_admin.rs index 0198c7862..096e3b758 100644 --- a/contracts/credit-manager/tests/test_update_admin.rs +++ b/contracts/credit-manager/tests/test_update_admin.rs @@ -14,8 +14,8 @@ fn initialized_state() { let mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); - assert!(original_config.owner.is_some()); - assert!(original_config.proposed_new_owner.is_none()); + assert!(original_config.ownership.owner.is_some()); + assert!(original_config.ownership.proposed.is_none()); } #[test] @@ -36,7 +36,7 @@ fn propose_new_owner() { assert_err(res, Owner(NotOwner {})); mock.update_owner( - &Addr::unchecked(original_config.owner.clone().unwrap()), + &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), OwnerUpdate::ProposeNewOwner { proposed: new_owner.clone(), }, @@ -45,9 +45,9 @@ fn propose_new_owner() { let new_config = mock.query_config(); - assert_eq!(new_config.owner, original_config.owner); - assert_ne!(new_config.proposed_new_owner, original_config.proposed_new_owner); - assert_eq!(new_config.proposed_new_owner, Some(new_owner)); + assert_eq!(new_config.ownership.owner, original_config.ownership.owner); + assert_ne!(new_config.ownership.proposed, original_config.ownership.proposed); + assert_eq!(new_config.ownership.proposed, Some(new_owner)); } #[test] @@ -58,7 +58,7 @@ fn clear_proposed() { let new_owner = "new_owner".to_string(); mock.update_owner( - &Addr::unchecked(original_config.owner.clone().unwrap()), + &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), OwnerUpdate::ProposeNewOwner { proposed: new_owner.clone(), }, @@ -67,7 +67,7 @@ fn clear_proposed() { let interim_config = mock.query_config(); - assert_eq!(interim_config.proposed_new_owner, Some(new_owner)); + assert_eq!(interim_config.ownership.proposed, Some(new_owner)); // only owner can clear let bad_guy = Addr::unchecked("bad_guy"); @@ -75,16 +75,16 @@ fn clear_proposed() { assert_err(res, Owner(NotOwner {})); mock.update_owner( - &Addr::unchecked(original_config.owner.clone().unwrap()), + &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), OwnerUpdate::ClearProposed, ) .unwrap(); let latest_config = mock.query_config(); - assert_eq!(latest_config.owner, original_config.owner); - assert_ne!(latest_config.proposed_new_owner, interim_config.proposed_new_owner); - assert_eq!(latest_config.proposed_new_owner, None); + assert_eq!(latest_config.ownership.owner, original_config.ownership.owner); + assert_ne!(latest_config.ownership.proposed, interim_config.ownership.proposed); + assert_eq!(latest_config.ownership.proposed, None); } #[test] @@ -95,7 +95,7 @@ fn accept_owner_role() { let new_owner = "new_owner".to_string(); mock.update_owner( - &Addr::unchecked(original_config.owner.clone().unwrap()), + &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), OwnerUpdate::ProposeNewOwner { proposed: new_owner.clone(), }, @@ -104,7 +104,7 @@ fn accept_owner_role() { // Only proposed owner can accept let res = mock.update_owner( - &Addr::unchecked(original_config.owner.unwrap()), + &Addr::unchecked(original_config.ownership.owner.unwrap()), OwnerUpdate::AcceptProposed, ); assert_err(res, Owner(NotProposedOwner {})); @@ -113,8 +113,8 @@ fn accept_owner_role() { let new_config = mock.query_config(); - assert_eq!(new_config.owner.unwrap(), new_owner); - assert_eq!(new_config.proposed_new_owner, None); + assert_eq!(new_config.ownership.owner.unwrap(), new_owner); + assert_eq!(new_config.ownership.proposed, None); } #[test] @@ -128,21 +128,21 @@ fn abolish_owner_role() { assert_err(res, Owner(NotOwner {})); mock.update_owner( - &Addr::unchecked(original_config.owner.clone().unwrap()), + &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), OwnerUpdate::AbolishOwnerRole, ) .unwrap(); let new_config = mock.query_config(); - assert_eq!(new_config.owner, None); - assert_eq!(new_config.proposed_new_owner, None); + assert_eq!(new_config.ownership.owner, None); + assert_eq!(new_config.ownership.proposed, None); // No new updates can occur let res = mock.update_owner( - &Addr::unchecked(original_config.owner.clone().unwrap()), + &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), OwnerUpdate::ProposeNewOwner { - proposed: original_config.owner.unwrap(), + proposed: original_config.ownership.owner.unwrap(), }, ); assert_err(res, Owner(StateTransitionError {})); diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 8b8b3b784..17d1503c8 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -64,7 +64,7 @@ fn raises_on_invalid_vaults_config() { vault_config.config.liquidation_threshold = Decimal::from_atomics(7u128, 1).unwrap(); let res = mock.update_config( - &Addr::unchecked(original_config.owner.clone().unwrap()), + &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), ConfigUpdates { account_nft: None, allowed_coins: None, @@ -92,7 +92,7 @@ fn raises_on_invalid_vaults_config() { vault_config.config.liquidation_threshold = Decimal::from_atomics(9u128, 0).unwrap(); let res = mock.update_config( - &Addr::unchecked(original_config.owner.clone().unwrap()), + &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), ConfigUpdates { account_nft: None, allowed_coins: None, @@ -119,7 +119,7 @@ fn raises_on_invalid_vaults_config() { let vault_b = deploy_vault(&mut mock.app); let res = mock.update_config( - &Addr::unchecked(original_config.owner.unwrap()), + &Addr::unchecked(original_config.ownership.owner.unwrap()), ConfigUpdates { account_nft: None, allowed_coins: None, @@ -161,7 +161,7 @@ fn update_config_works_with_full_config() { let new_health_contract = HealthContractUnchecked::new("new_health_contract".to_string()); mock.update_config( - &Addr::unchecked(original_config.owner.clone().unwrap()), + &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), ConfigUpdates { account_nft: Some(new_nft_contract.to_string()), allowed_coins: Some(new_allowed_coins.clone()), @@ -184,7 +184,10 @@ fn update_config_works_with_full_config() { assert_eq!(new_config.account_nft, Some(new_nft_contract.to_string())); assert_ne!(new_config.account_nft, original_config.account_nft); - assert_eq!(new_config.owner.unwrap(), original_config.owner.clone().unwrap()); + assert_eq!( + new_config.ownership.owner.unwrap(), + original_config.ownership.owner.clone().unwrap() + ); assert_eq!( new_queried_vault_configs, @@ -234,7 +237,7 @@ fn update_config_works_with_some_config() { let new_max_unlocking = Uint128::new(42); mock.update_config( - &Addr::unchecked(original_config.owner.clone().unwrap()), + &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), ConfigUpdates { account_nft: Some(new_nft_contract.to_string()), max_unlocking_positions: Some(new_max_unlocking), @@ -255,8 +258,8 @@ fn update_config_works_with_some_config() { assert_ne!(new_config.max_unlocking_positions, original_config.max_unlocking_positions); // Unchanged configs - assert_eq!(new_config.owner, original_config.owner); - assert_eq!(new_config.proposed_new_owner, original_config.proposed_new_owner); + assert_eq!(new_config.ownership.owner, original_config.ownership.owner); + assert_eq!(new_config.ownership.proposed, original_config.ownership.proposed); assert_eq!(new_config.red_bank, original_config.red_bank); assert_eq!(new_config.oracle, original_config.oracle); assert_eq!(new_config.max_close_factor, original_config.max_close_factor); @@ -286,7 +289,7 @@ fn update_config_removes_properly() { assert_eq!(vault_configs.len(), 1); mock.update_config( - &Addr::unchecked(mock.query_config().owner.unwrap()), + &Addr::unchecked(mock.query_config().ownership.owner.unwrap()), ConfigUpdates { allowed_coins: Some(vec![]), vault_configs: Some(vec![]), @@ -311,7 +314,7 @@ fn update_config_does_nothing_when_nothing_is_passed() { let original_allowed_coins = mock.query_allowed_coins(None, None); mock.update_config( - &Addr::unchecked(original_config.owner.clone().unwrap()), + &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), Default::default(), ) .unwrap(); @@ -321,7 +324,7 @@ fn update_config_does_nothing_when_nothing_is_passed() { let new_queried_allowed_coins = mock.query_allowed_coins(None, None); assert_eq!(new_config.account_nft, original_config.account_nft); - assert_eq!(new_config.owner, original_config.owner); + assert_eq!(new_config.ownership, original_config.ownership); assert_eq!(new_queried_vault_configs, original_vault_configs); assert_eq!(new_queried_allowed_coins, original_allowed_coins); assert_eq!(new_config.red_bank, original_config.red_bank); @@ -337,7 +340,7 @@ fn max_close_factor_validated_on_update() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( - &Addr::unchecked(original_config.owner.unwrap()), + &Addr::unchecked(original_config.ownership.owner.unwrap()), ConfigUpdates { max_close_factor: Some(Decimal::from_atomics(42u128, 1).unwrap()), ..Default::default() @@ -357,7 +360,7 @@ fn raises_on_duplicate_vault_configs() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( - &Addr::unchecked(original_config.owner.unwrap()), + &Addr::unchecked(original_config.ownership.owner.unwrap()), ConfigUpdates { account_nft: None, allowed_coins: None, @@ -404,7 +407,7 @@ fn raises_on_duplicate_coin_configs() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); let res = mock.update_config( - &Addr::unchecked(original_config.owner.unwrap()), + &Addr::unchecked(original_config.ownership.owner.unwrap()), ConfigUpdates { account_nft: None, allowed_coins: Some(vec![ diff --git a/contracts/credit-manager/tests/test_update_nft_config.rs b/contracts/credit-manager/tests/test_update_nft_config.rs index f4d19b112..fe8e52dd5 100644 --- a/contracts/credit-manager/tests/test_update_nft_config.rs +++ b/contracts/credit-manager/tests/test_update_nft_config.rs @@ -60,7 +60,7 @@ fn raises_on_invalid_config() { let mut mock = MockEnv::new().build().unwrap(); let res = mock.update_nft_config( - &Addr::unchecked(mock.query_config().owner.unwrap()), + &Addr::unchecked(mock.query_config().ownership.owner.unwrap()), None, Some(cw721_base::Action::TransferOwnership { new_owner: "".to_string(), @@ -84,7 +84,7 @@ fn update_config_works_with_full_config() { let new_health_contract = Some("new_health_contract_xyz".to_string()); mock.update_nft_config( - &Addr::unchecked(mock.query_config().owner.unwrap()), + &Addr::unchecked(mock.query_config().ownership.owner.unwrap()), Some(NftConfigUpdates { max_value_for_burn: new_max_value, health_contract_addr: new_health_contract.clone(), @@ -116,7 +116,7 @@ fn update_config_works_with_some_config() { let new_proposed = Some(Addr::unchecked("spiderman_12345")); mock.update_nft_config( - &Addr::unchecked(mock.query_config().owner.unwrap()), + &Addr::unchecked(mock.query_config().ownership.owner.unwrap()), None, Some(cw721_base::Action::TransferOwnership { new_owner: new_proposed.clone().unwrap().into(), diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index 85510d932..80cadc89d 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -101,7 +101,7 @@ fn coins_out_must_be_whitelisted() { // update config to disallow denoms out let config = mock.query_config(); mock.update_config( - &Addr::unchecked(config.owner.unwrap()), + &Addr::unchecked(config.ownership.owner.unwrap()), ConfigUpdates { allowed_coins: Some(vec![lp_token.denom.clone(), atom.denom]), ..Default::default() diff --git a/contracts/health/tests/helpers/mock_env_builder.rs b/contracts/health/tests/helpers/mock_env_builder.rs index 2c0336bc6..c08acd1fc 100644 --- a/contracts/health/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/helpers/mock_env_builder.rs @@ -10,6 +10,7 @@ use mars_mock_credit_manager::msg::{ use mars_mock_oracle::msg::InstantiateMsg as OracleInstantiateMsg; use mars_mock_red_bank::msg::InstantiateMsg as RedBankInstantiateMsg; use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; +use mars_owner::OwnerResponse; use mars_rover::{ adapters::{oracle::OracleUnchecked, vault::VaultConfig}, msg::query::ConfigResponse, @@ -144,10 +145,15 @@ impl MockEnvBuilder { self.deployer.clone(), &CmMockInstantiateMsg { config: ConfigResponse { - owner: Some(self.deployer.to_string()), + ownership: OwnerResponse { + owner: Some(self.deployer.to_string()), + proposed: None, + emergency_owner: None, + initialized: true, + abolished: false, + }, red_bank, oracle, - proposed_new_owner: None, account_nft: None, max_close_factor: Default::default(), max_unlocking_positions: Default::default(), diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 46aff579b..46d0ba8c3 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Coin, Decimal, Uint128}; +use mars_owner::OwnerResponse; use crate::{ adapters::vault::{Vault, VaultConfig, VaultPosition, VaultUnchecked}, @@ -214,8 +215,7 @@ pub struct VaultWithBalance { #[cw_serde] pub struct ConfigResponse { - pub owner: Option, - pub proposed_new_owner: Option, + pub ownership: OwnerResponse, pub account_nft: Option, pub red_bank: String, pub oracle: String, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 2f1fa9d3a..220ed7370 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -2798,6 +2798,7 @@ "max_close_factor", "max_unlocking_positions", "oracle", + "ownership", "red_bank", "swapper", "zapper" @@ -2821,17 +2822,8 @@ "oracle": { "type": "string" }, - "owner": { - "type": [ - "string", - "null" - ] - }, - "proposed_new_owner": { - "type": [ - "string", - "null" - ] + "ownership": { + "$ref": "#/definitions/OwnerResponse" }, "red_bank": { "type": "string" @@ -2849,6 +2841,41 @@ "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" }, + "OwnerResponse": { + "description": "Returned from Owner.query()", + "type": "object", + "required": [ + "abolished", + "initialized" + ], + "properties": { + "abolished": { + "type": "boolean" + }, + "emergency_owner": { + "type": [ + "string", + "null" + ] + }, + "initialized": { + "type": "boolean" + }, + "owner": { + "type": [ + "string", + "null" + ] + }, + "proposed": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index b38ee0311..69c806569 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -1386,6 +1386,7 @@ "max_close_factor", "max_unlocking_positions", "oracle", + "ownership", "red_bank", "swapper", "zapper" @@ -1409,17 +1410,8 @@ "oracle": { "type": "string" }, - "owner": { - "type": [ - "string", - "null" - ] - }, - "proposed_new_owner": { - "type": [ - "string", - "null" - ] + "ownership": { + "$ref": "#/definitions/OwnerResponse" }, "red_bank": { "type": "string" @@ -1437,6 +1429,41 @@ "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" }, + "OwnerResponse": { + "description": "Returned from Owner.query()", + "type": "object", + "required": [ + "abolished", + "initialized" + ], + "properties": { + "abolished": { + "type": "boolean" + }, + "emergency_owner": { + "type": [ + "string", + "null" + ] + }, + "initialized": { + "type": "boolean" + }, + "owner": { + "type": [ + "string", + "null" + ] + }, + "proposed": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 84beb3a2e..49f1b9e92 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -380,7 +380,7 @@ export class Deployer { this.storage.addresses.creditManager!, ) const creditManagerConfig = await cmQuery.config() - assert.equal(creditManagerConfig.proposed_new_owner, this.config.multisigAddr) + assert.equal(creditManagerConfig.ownership.proposed, this.config.multisigAddr) } async updateSwapperOwner() { diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index fed5b0ae3..d09073fed 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -56,6 +56,7 @@ import { VaultPositionResponseItem, ArrayOfString, ConfigResponse, + OwnerResponse, ArrayOfCoin, Positions, DebtAmount, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 29eac4c4a..09fbc991d 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -57,6 +57,7 @@ import { VaultPositionResponseItem, ArrayOfString, ConfigResponse, + OwnerResponse, ArrayOfCoin, Positions, DebtAmount, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 70b1e938e..ac1324cb9 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -57,6 +57,7 @@ import { VaultPositionResponseItem, ArrayOfString, ConfigResponse, + OwnerResponse, ArrayOfCoin, Positions, DebtAmount, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 63901fd63..2ceac2ec1 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -515,12 +515,18 @@ export interface ConfigResponse { max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: string - owner?: string | null - proposed_new_owner?: string | null + ownership: OwnerResponse red_bank: string swapper: string zapper: string } +export interface OwnerResponse { + abolished: boolean + emergency_owner?: string | null + initialized: boolean + owner?: string | null + proposed?: string | null +} export type ArrayOfCoin = Coin[] export interface Positions { account_id: string diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index b6e8d3f9a..e65c0aa58 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -42,6 +42,7 @@ import { VaultPositionResponseItem, ArrayOfString, ConfigResponse, + OwnerResponse, ArrayOfCoin, VaultConfigResponse, VaultPositionValue, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index fe863393b..e09dacb93 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -43,6 +43,7 @@ import { VaultPositionResponseItem, ArrayOfString, ConfigResponse, + OwnerResponse, ArrayOfCoin, VaultConfigResponse, VaultPositionValue, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index 65e833e6d..e1125d94c 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -43,6 +43,7 @@ import { VaultPositionResponseItem, ArrayOfString, ConfigResponse, + OwnerResponse, ArrayOfCoin, VaultConfigResponse, VaultPositionValue, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index e14c98612..402af4708 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -222,12 +222,18 @@ export interface ConfigResponse { max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: string - owner?: string | null - proposed_new_owner?: string | null + ownership: OwnerResponse red_bank: string swapper: string zapper: string } +export interface OwnerResponse { + abolished: boolean + emergency_owner?: string | null + initialized: boolean + owner?: string | null + proposed?: string | null +} export type ArrayOfCoin = Coin[] export interface VaultConfigResponse { config: VaultConfig From ee0320e850a047d52621ac958707b480f2a01d5e Mon Sep 17 00:00:00 2001 From: brimigs <85972460+brimigs@users.noreply.github.com> Date: Mon, 5 Jun 2023 02:10:15 -0400 Subject: [PATCH 161/218] Mp 2484 integrate asset params contract with rover (#138) --- Cargo.lock | 256 ++--- Cargo.toml | 26 +- contracts/credit-manager/Cargo.toml | 2 +- contracts/credit-manager/src/borrow.rs | 9 +- contracts/credit-manager/src/contract.rs | 31 +- contracts/credit-manager/src/deposit.rs | 8 +- .../credit-manager/src/emergency_update.rs | 57 - contracts/credit-manager/src/execute.rs | 4 +- contracts/credit-manager/src/instantiate.rs | 76 +- contracts/credit-manager/src/lend.rs | 4 +- contracts/credit-manager/src/lib.rs | 1 - .../credit-manager/src/liquidate_deposit.rs | 12 +- contracts/credit-manager/src/query.rs | 101 +- contracts/credit-manager/src/state.rs | 15 +- contracts/credit-manager/src/swap.rs | 4 +- contracts/credit-manager/src/update_config.rs | 45 +- contracts/credit-manager/src/utils.rs | 30 +- contracts/credit-manager/src/vault/enter.rs | 11 +- contracts/credit-manager/src/vault/exit.rs | 4 +- .../credit-manager/src/vault/exit_unlocked.rs | 4 +- .../src/vault/request_unlock.rs | 4 +- contracts/credit-manager/src/vault/utils.rs | 22 +- contracts/credit-manager/src/zap.rs | 12 +- .../credit-manager/tests/helpers/builders.rs | 1 + .../credit-manager/tests/helpers/contracts.rs | 9 + .../tests/helpers/mock_entity_info.rs | 18 + .../credit-manager/tests/helpers/mock_env.rs | 375 +++---- .../credit-manager/tests/helpers/types.rs | 27 + contracts/credit-manager/tests/test_borrow.rs | 26 +- .../tests/test_coin_balances.rs | 6 +- .../credit-manager/tests/test_deposit.rs | 41 +- .../tests/test_emergency_powers.rs | 174 ---- .../tests/test_enumerate_allowed_coins.rs | 37 - .../tests/test_enumerate_coin_balances.rs | 2 +- .../tests/test_enumerate_debt_shares.rs | 2 +- .../tests/test_enumerate_lent_shares.rs | 2 +- .../tests/test_enumerate_total_debt_shares.rs | 2 +- .../tests/test_enumerate_total_lent_shares.rs | 2 +- .../test_enumerate_vault_coin_balances.rs | 101 -- .../tests/test_enumerate_vault_configs.rs | 54 - .../tests/test_enumerate_vault_positions.rs | 2 +- .../tests/test_flagged_contract.rs | 6 - contracts/credit-manager/tests/test_health.rs | 96 +- .../credit-manager/tests/test_instantiate.rs | 207 +--- contracts/credit-manager/tests/test_lend.rs | 8 +- .../tests/test_liquidate_deposit.rs | 22 +- .../tests/test_liquidate_lend.rs | 10 +- .../tests/test_liquidate_vault.rs | 18 +- .../credit-manager/tests/test_reclaim.rs | 10 +- .../tests/test_refund_balances.rs | 4 +- contracts/credit-manager/tests/test_repay.rs | 14 +- .../tests/test_repay_for_recipient.rs | 12 +- .../tests/test_repay_from_wallet.rs | 39 +- contracts/credit-manager/tests/test_swap.rs | 20 +- .../tests/test_update_config.rs | 327 +----- .../tests/test_utilization_query.rs | 23 +- .../credit-manager/tests/test_vault_enter.rs | 16 +- .../credit-manager/tests/test_vault_exit.rs | 4 +- .../tests/test_vault_exit_unlocked.rs | 10 +- .../tests/test_vault_query_config.rs | 63 -- .../tests/test_vault_query_value.rs | 4 +- .../tests/test_vault_request_unlock.rs | 6 +- .../credit-manager/tests/test_withdraw.rs | 12 +- .../credit-manager/tests/test_zap_provide.rs | 16 +- .../credit-manager/tests/test_zap_withdraw.rs | 48 +- contracts/health/Cargo.toml | 3 +- contracts/health/src/compute.rs | 28 +- contracts/health/src/contract.rs | 11 +- contracts/health/src/querier.rs | 47 +- contracts/health/src/state.rs | 1 + contracts/health/src/update_config.rs | 28 +- .../health/tests/helpers/mock_contracts.rs | 8 +- contracts/health/tests/helpers/mock_env.rs | 75 +- .../health/tests/helpers/mock_env_builder.rs | 129 ++- contracts/health/tests/test_compute_health.rs | 149 ++- contracts/health/tests/test_update_config.rs | 39 +- contracts/mock-credit-manager/src/contract.rs | 18 +- contracts/mock-credit-manager/src/execute.rs | 16 +- contracts/mock-credit-manager/src/msg.rs | 10 +- contracts/mock-credit-manager/src/query.rs | 20 +- contracts/mock-credit-manager/src/state.rs | 8 +- contracts/mock-red-bank/examples/schema.rs | 4 +- contracts/mock-red-bank/src/contract.rs | 24 +- contracts/mock-red-bank/src/execute.rs | 25 +- contracts/mock-red-bank/src/lib.rs | 1 - contracts/mock-red-bank/src/msg.rs | 15 - contracts/mock-red-bank/src/query.rs | 17 +- contracts/mock-red-bank/src/state.rs | 4 - contracts/swapper/mock/Cargo.toml | 4 - contracts/swapper/osmosis/Cargo.toml | 1 - contracts/v3-zapper/osmosis/Cargo.toml | 1 - packages/health-computer/Cargo.toml | 2 +- packages/health-computer/src/data_types.rs | 6 +- .../health-computer/src/health_computer.rs | 44 +- .../tests/helpers/mock_coin_info.rs | 98 +- .../tests/test_health_scenarios.rs | 331 +++--- .../tests/test_input_validation.rs | 55 +- packages/health-types/src/error.rs | 8 +- packages/health-types/src/msg.rs | 6 +- packages/rover/Cargo.toml | 1 + packages/rover/src/adapters/mod.rs | 1 + packages/rover/src/adapters/params.rs | 66 ++ packages/rover/src/adapters/red_bank.rs | 11 +- packages/rover/src/adapters/vault/config.rs | 27 - packages/rover/src/adapters/vault/mod.rs | 3 +- packages/rover/src/msg/execute.rs | 21 - packages/rover/src/msg/instantiate.rs | 51 +- packages/rover/src/msg/query.rs | 40 +- .../mars-credit-manager.json | 607 +---------- .../mars-mock-credit-manager.json | 446 +------- .../mars-mock-red-bank.json | 45 +- schemas/mars-params/mars-params.json | 971 ++++++++++++++++++ .../mars-rover-health-computer.json | 273 ++--- .../mars-rover-health-types.json | 22 +- scripts/codegen/index.ts | 27 +- scripts/deploy/base/deployer.ts | 13 +- scripts/deploy/base/rover.ts | 28 +- scripts/deploy/osmosis/mainnnet.ts | 25 +- scripts/deploy/osmosis/testnet-config.ts | 63 +- scripts/health/DataFetcher.ts | 39 +- scripts/health/example-node.ts | 2 +- scripts/health/pkg-node/index.js | 153 +-- scripts/health/pkg-node/index_bg.wasm | Bin 159992 -> 157988 bytes scripts/health/pkg-node/package.json | 2 +- scripts/health/pkg-web/index.d.ts | 4 +- scripts/health/pkg-web/index.js | 193 ++-- scripts/health/pkg-web/index_bg.wasm | Bin 159068 -> 157064 bytes scripts/health/pkg-web/package.json | 6 +- scripts/package.json | 15 +- scripts/types/config.ts | 10 +- .../mars-account-nft/MarsAccountNft.client.ts | 62 +- .../MarsAccountNft.message-composer.ts | 64 +- .../MarsAccountNft.react-query.ts | 2 +- .../mars-account-nft/MarsAccountNft.types.ts | 2 +- .../generated/mars-account-nft/bundle.ts | 2 +- .../MarsCreditManager.client.ts | 167 +-- .../MarsCreditManager.message-composer.ts | 84 +- .../MarsCreditManager.react-query.ts | 183 +--- .../MarsCreditManager.types.ts | 92 +- .../generated/mars-credit-manager/bundle.ts | 2 +- .../MarsMockCreditManager.client.ts | 160 +-- .../MarsMockCreditManager.message-composer.ts | 72 +- .../MarsMockCreditManager.react-query.ts | 209 +--- .../MarsMockCreditManager.types.ts | 72 +- .../mars-mock-credit-manager/bundle.ts | 2 +- .../mars-mock-oracle/MarsMockOracle.client.ts | 8 +- .../MarsMockOracle.message-composer.ts | 10 +- .../MarsMockOracle.react-query.ts | 2 +- .../mars-mock-oracle/MarsMockOracle.types.ts | 2 +- .../generated/mars-mock-oracle/bundle.ts | 2 +- .../MarsMockRedBank.client.ts | 77 +- .../MarsMockRedBank.message-composer.ts | 82 +- .../MarsMockRedBank.react-query.ts | 5 +- .../MarsMockRedBank.types.ts | 12 +- .../generated/mars-mock-red-bank/bundle.ts | 2 +- .../mars-mock-vault/MarsMockVault.client.ts | 20 +- .../MarsMockVault.message-composer.ts | 22 +- .../MarsMockVault.react-query.ts | 2 +- .../mars-mock-vault/MarsMockVault.types.ts | 2 +- .../types/generated/mars-mock-vault/bundle.ts | 2 +- .../mars-params/MarsParams.client.ts | 257 +++++ .../MarsParams.message-composer.ts | 151 +++ .../mars-params/MarsParams.react-query.ts | 289 ++++++ .../generated/mars-params/MarsParams.types.ts | 159 +++ scripts/types/generated/mars-params/bundle.ts | 14 + .../MarsRoverHealthComputer.client.ts | 10 +- ...arsRoverHealthComputer.message-composer.ts | 10 +- .../MarsRoverHealthComputer.react-query.ts | 10 +- .../MarsRoverHealthComputer.types.ts | 47 +- .../mars-rover-health-computer/bundle.ts | 12 +- .../MarsRoverHealthTypes.client.ts | 23 +- .../MarsRoverHealthTypes.message-composer.ts | 25 +- .../MarsRoverHealthTypes.react-query.ts | 5 +- .../MarsRoverHealthTypes.types.ts | 8 +- .../mars-rover-health-types/bundle.ts | 12 +- .../MarsSwapperBase.client.ts | 26 +- .../MarsSwapperBase.message-composer.ts | 28 +- .../MarsSwapperBase.react-query.ts | 2 +- .../MarsSwapperBase.types.ts | 2 +- .../generated/mars-swapper-base/bundle.ts | 12 +- .../MarsV2ZapperBase.client.ts | 20 +- .../MarsV2ZapperBase.message-composer.ts | 22 +- .../MarsV2ZapperBase.react-query.ts | 2 +- .../MarsV2ZapperBase.types.ts | 2 +- .../generated/mars-v2-zapper-base/bundle.ts | 12 +- .../MarsV3ZapperBase.client.ts | 20 +- .../MarsV3ZapperBase.message-composer.ts | 22 +- .../MarsV3ZapperBase.react-query.ts | 2 +- .../MarsV3ZapperBase.types.ts | 2 +- .../generated/mars-v3-zapper-base/bundle.ts | 12 +- scripts/types/storageItems.ts | 1 + scripts/yarn.lock | 915 ++++++++++------- 192 files changed, 4725 insertions(+), 5849 deletions(-) delete mode 100644 contracts/credit-manager/src/emergency_update.rs delete mode 100644 contracts/credit-manager/tests/test_emergency_powers.rs delete mode 100644 contracts/credit-manager/tests/test_enumerate_allowed_coins.rs delete mode 100644 contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs delete mode 100644 contracts/credit-manager/tests/test_enumerate_vault_configs.rs delete mode 100644 contracts/credit-manager/tests/test_vault_query_config.rs delete mode 100644 contracts/mock-red-bank/src/msg.rs create mode 100644 packages/rover/src/adapters/params.rs delete mode 100644 packages/rover/src/adapters/vault/config.rs create mode 100644 schemas/mars-params/mars-params.json create mode 100644 scripts/types/generated/mars-params/MarsParams.client.ts create mode 100644 scripts/types/generated/mars-params/MarsParams.message-composer.ts create mode 100644 scripts/types/generated/mars-params/MarsParams.react-query.ts create mode 100644 scripts/types/generated/mars-params/MarsParams.types.ts create mode 100644 scripts/types/generated/mars-params/bundle.ts diff --git a/Cargo.lock b/Cargo.lock index ad4c5bc99..849efa657 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,6 +22,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "anyhow" version = "1.0.71" @@ -61,7 +67,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -175,9 +181,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byteorder" @@ -214,11 +220,11 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ - "num-integer", + "android-tzdata", "num-traits", ] @@ -327,7 +333,7 @@ version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75836a10cb9654c54e77ee56da94d592923092a10b369cdb0dbd56acefc16340" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "ed25519-zebra", "k256", "rand_core 0.6.4", @@ -472,16 +478,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw-item-set" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea5a233bd67babedbe96a514178a64b0c597f1f38bc474fa8d63e3f26bdceb2" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus 1.0.1", -] - [[package]] name = "cw-multi-test" version = "0.16.4" @@ -747,9 +743,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", @@ -825,7 +821,7 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.6", + "digest 0.10.7", "ff", "generic-array", "group", @@ -956,7 +952,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -1031,9 +1027,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", @@ -1112,7 +1108,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -1259,9 +1255,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -1318,12 +1314,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "mars-account-nft" @@ -1351,7 +1344,6 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-item-set", "cw-multi-test", "cw-paginate", "cw-storage-plus 1.0.1", @@ -1366,6 +1358,7 @@ dependencies = [ "mars-mock-red-bank", "mars-mock-vault", "mars-owner", + "mars-params", "mars-red-bank-types", "mars-rover", "mars-rover-health", @@ -1443,14 +1436,32 @@ dependencies = [ [[package]] name = "mars-owner" -version = "1.1.0" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd53908ffc561da878ce5ff4f5ec9f25a193af28ec0b6e7c8e6d1a0866d9dfc" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "schemars", + "thiserror", +] + +[[package]] +name = "mars-params" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca010da465b4a5ea7274f59132d22b7c10765295c73d5744add2c1fea6c5e38" +checksum = "ed319ed66b221fcaef2a189deacc49b404f5ce9df1d03c58d8444d271fb10f22" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.0.1", + "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mars-owner", + "mars-red-bank-types", + "mars-utils", "schemars", + "serde", "thiserror", ] @@ -1480,6 +1491,7 @@ dependencies = [ "cw721-base 0.17.0", "mars-account-nft", "mars-owner", + "mars-params", "mars-red-bank-types", "mars-rover-health-types", "schemars", @@ -1501,10 +1513,9 @@ dependencies = [ "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "mars-mock-credit-manager", "mars-mock-oracle", - "mars-mock-red-bank", "mars-mock-vault", "mars-owner", - "mars-red-bank-types", + "mars-params", "mars-rover", "mars-rover-health-computer", "mars-rover-health-types", @@ -1519,7 +1530,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-vault-standard", - "mars-red-bank-types", + "mars-params", "mars-rover", "mars-rover-health-types", "schemars", @@ -1558,9 +1569,7 @@ dependencies = [ name = "mars-swapper-mock" version = "2.0.0" dependencies = [ - "anyhow", "cosmwasm-std", - "cw-multi-test", "cw-storage-plus 1.0.1", "mars-rover", "thiserror", @@ -1570,7 +1579,6 @@ dependencies = [ name = "mars-swapper-osmosis" version = "2.0.0" dependencies = [ - "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.0.1", @@ -1652,7 +1660,6 @@ version = "2.0.0" dependencies = [ "anyhow", "cosmwasm-std", - "cw-utils 1.0.1", "mars-owner", "mars-v3-zapper-base", "osmosis-std 0.15.2", @@ -1679,14 +1686,13 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1710,16 +1716,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.15" @@ -1750,9 +1746,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" [[package]] name = "opaque-debug" @@ -1791,7 +1787,7 @@ dependencies = [ [[package]] name = "osmosis-std" version = "0.15.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#a139d47ba298c94ebff462572b53c43fae0483e2" +source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#d660806226c5fb388be97e08cb397d402830f785" dependencies = [ "chrono", "cosmwasm-std", @@ -1818,7 +1814,7 @@ dependencies = [ [[package]] name = "osmosis-std-derive" version = "0.15.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#a139d47ba298c94ebff462572b53c43fae0483e2" +source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#d660806226c5fb388be97e08cb397d402830f785" dependencies = [ "itertools", "proc-macro2", @@ -1829,7 +1825,7 @@ dependencies = [ [[package]] name = "osmosis-test-tube" version = "15.1.0" -source = "git+https://github.com/osmosis-labs/test-tube?branch=main#360dd44310d0ea0a14e899c02ab3a4eb716d8404" +source = "git+https://github.com/osmosis-labs/test-tube?branch=main#e0b157e5de32d8ac80aefd15518cf0252e26ba7f" dependencies = [ "base64", "bindgen", @@ -1855,7 +1851,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -1899,22 +1895,22 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -1941,9 +1937,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] @@ -2005,9 +2001,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -2029,9 +2025,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" dependencies = [ "aho-corasick", "memchr", @@ -2040,9 +2036,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "rfc6979" @@ -2076,7 +2072,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2195,9 +2191,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.8.2" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" dependencies = [ "bitflags", "core-foundation", @@ -2208,9 +2204,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" dependencies = [ "core-foundation-sys", "libc", @@ -2224,9 +2220,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.162" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] @@ -2271,13 +2267,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.162" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -2310,7 +2306,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -2321,7 +2317,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2345,7 +2341,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2354,7 +2350,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "keccak", ] @@ -2370,7 +2366,7 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -2423,9 +2419,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "subtle-encoding" @@ -2449,9 +2445,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -2566,7 +2562,7 @@ dependencies = [ [[package]] name = "test-tube" version = "0.1.2" -source = "git+https://github.com/osmosis-labs/test-tube?branch=main#360dd44310d0ea0a14e899c02ab3a4eb716d8404" +source = "git+https://github.com/osmosis-labs/test-tube?branch=main#e0b157e5de32d8ac80aefd15518cf0252e26ba7f" dependencies = [ "base64", "cosmrs", @@ -2600,7 +2596,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -2637,9 +2633,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.0" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", @@ -2660,7 +2656,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -2716,9 +2712,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", ] @@ -2755,9 +2751,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -2825,9 +2821,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2835,24 +2831,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2860,28 +2856,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -2963,37 +2959,13 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets", ] [[package]] @@ -3112,5 +3084,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] diff --git a/Cargo.toml b/Cargo.toml index 81002fc37..6004495ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,37 +42,37 @@ cosmwasm-std = "1.2.5" cw2 = "1.0.1" cw721 = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main" } cw721-base = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main", features = ["library"] } -cw-item-set = { version = "0.7.1", default-features = false, features = ["iterator"] } +cw-dex = { version = "0.1.3", features = ["osmosis"] } cw-multi-test = "0.16.4" cw-paginate = "0.2.1" cw-utils = "1.0.1" cw-storage-plus = "1.0.1" +cw-vault-standard = { version = "0.3.0", features = ["lockup", "force-unlock"] } itertools = "0.10.5" osmosis-std = { git = "https://github.com/osmosis-labs/osmosis-rust", branch = "main" } osmosis-test-tube = { git = "https://github.com/osmosis-labs/test-tube", branch = "main" } schemars = "0.8.12" -serde = { version = "1.0.160", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", default-features = false, features = ["derive"] } serde_json = "1.0.96" serde-wasm-bindgen = "0.5.0" thiserror = "1.0.40" -wasm-bindgen = "0.2.84" +wasm-bindgen = "0.2.86" -# packages -cw-dex = { version = "0.1.3", features = ["osmosis"] } -cw-vault-standard = { version = "0.3.0", features = ["lockup", "force-unlock"] } -mars-rover-health-computer = { version = "2.0.0", path = "./packages/health-computer" } -mars-rover-health-types = { version = "2.0.0", path = "./packages/health-types" } +# mars packages mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "00301d60c38af09d8eb7980355009e2f00c6f41f" } -mars-red-bank-types = "1.0.0" mars-owner = { version = "1.1.0", features = ["emergency-owner"] } +mars-red-bank-types = "1.0.0" +mars-rover-health-computer = { version = "2.0.0", path = "./packages/health-computer" } +mars-rover-health-types = { version = "2.0.0", path = "./packages/health-types" } mars-rover = { version = "2.0.0", path = "./packages/rover" } # contracts mars-account-nft = { version = "2.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-rover-health = { version = "2.0.0", path = "./contracts/health" } -mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base" } -mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base" } -mars-v3-zapper-base = { version = "2.0.0", path = "./contracts/v3-zapper/base" } +mars-params = { version = "1.0.4", features = ["library"] } +mars-rover-health = { version = "2.0.0", path = "./contracts/health", features = ["library"] } +mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base", features = ["library"] } +mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base", features = ["library"] } +mars-v3-zapper-base = { version = "2.0.0", path = "./contracts/v3-zapper/base", features = ["library"] } # mocks mars-mock-credit-manager = { version = "2.0.0", path = "./contracts/mock-credit-manager", features = ["library"] } diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index f241e2806..3c127ec1b 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -24,7 +24,6 @@ cosmwasm-std = { workspace = true } cw2 = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true } -cw-item-set = { workspace = true } cw-paginate = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } @@ -42,6 +41,7 @@ itertools = { workspace = true } mars-mock-oracle = { workspace = true } mars-mock-red-bank = { workspace = true } mars-mock-vault = { workspace = true } +mars-params = { workspace = true } mars-rover-health = { workspace = true } mars-swapper-mock = { workspace = true } mars-v2-zapper-mock = { workspace = true } diff --git a/contracts/credit-manager/src/borrow.rs b/contracts/credit-manager/src/borrow.rs index 05a73bdf2..8b3bbcade 100644 --- a/contracts/credit-manager/src/borrow.rs +++ b/contracts/credit-manager/src/borrow.rs @@ -13,12 +13,17 @@ pub static DEFAULT_DEBT_SHARES_PER_COIN_BORROWED: Uint128 = Uint128::new(1_000_0 /// else, get debt ownership % and multiply by total existing shares /// /// increment total debt shares, token debt shares, and asset amount -pub fn borrow(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> ContractResult { +pub fn borrow( + mut deps: DepsMut, + env: Env, + account_id: &str, + coin: Coin, +) -> ContractResult { if coin.amount.is_zero() { return Err(ContractError::NoAmount); } - assert_coin_is_whitelisted(deps.storage, &coin.denom)?; + assert_coin_is_whitelisted(&mut deps, &coin.denom)?; let red_bank = RED_BANK.load(deps.storage)?; let total_debt_amount = diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index afbe72e9e..823b1fbc9 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -9,16 +9,13 @@ use mars_rover::{ }; use crate::{ - emergency_update::emergency_config_update, execute::{create_credit_account, dispatch_actions, execute_callback}, instantiate::store_config, query::{ query_all_coin_balances, query_all_debt_shares, query_all_lent_shares, - query_all_total_debt_shares, query_all_total_lent_shares, - query_all_total_vault_coin_balances, query_all_vault_positions, query_allowed_coins, + query_all_total_debt_shares, query_all_total_lent_shares, query_all_vault_positions, query_config, query_positions, query_total_debt_shares, query_total_lent_shares, - query_total_vault_coin_balance, query_vault_config, query_vault_position_value, - query_vault_utilization, query_vaults_config, + query_vault_position_value, query_vault_utilization, }, repay::repay_from_wallet, update_config::{update_config, update_nft_config, update_owner}, @@ -63,7 +60,6 @@ pub fn execute( account_id, actions, } => dispatch_actions(deps, env, info, &account_id, &actions), - ExecuteMsg::EmergencyConfigUpdate(update) => emergency_config_update(deps, info, update), ExecuteMsg::RepayFromWallet { account_id, } => repay_from_wallet(deps, env, info, account_id), @@ -82,20 +78,9 @@ pub fn reply(deps: DepsMut, _: Env, reply: Reply) -> ContractResult { pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { let res = match msg { QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::VaultConfig { - vault, - } => to_binary(&query_vault_config(deps, vault)?), - QueryMsg::VaultsConfig { - start_after, - limit, - } => to_binary(&query_vaults_config(deps, start_after, limit)?), QueryMsg::VaultUtilization { vault, } => to_binary(&query_vault_utilization(deps, env, vault)?), - QueryMsg::AllowedCoins { - start_after, - limit, - } => to_binary(&query_allowed_coins(deps, start_after, limit)?), QueryMsg::Positions { account_id, } => to_binary(&query_positions(deps, &env, &account_id)?), @@ -121,18 +106,6 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { start_after, limit, } => to_binary(&query_all_total_lent_shares(deps, start_after, limit)?), - QueryMsg::TotalVaultCoinBalance { - vault, - } => to_binary(&query_total_vault_coin_balance(deps, &vault, &env.contract.address)?), - QueryMsg::AllTotalVaultCoinBalances { - start_after, - limit, - } => to_binary(&query_all_total_vault_coin_balances( - deps, - &env.contract.address, - start_after, - limit, - )?), QueryMsg::AllVaultPositions { start_after, limit, diff --git a/contracts/credit-manager/src/deposit.rs b/contracts/credit-manager/src/deposit.rs index 3ac2ae7a9..e50387c09 100644 --- a/contracts/credit-manager/src/deposit.rs +++ b/contracts/credit-manager/src/deposit.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, Response, Storage, Uint128}; +use cosmwasm_std::{Coin, DepsMut, Response, Uint128}; use mars_rover::{ coins::Coins, error::{ContractError, ContractResult}, @@ -7,13 +7,13 @@ use mars_rover::{ use crate::utils::{assert_coin_is_whitelisted, increment_coin_balance}; pub fn deposit( - storage: &mut dyn Storage, + deps: &mut DepsMut, response: Response, account_id: &str, coin: &Coin, received_coins: &mut Coins, ) -> ContractResult { - assert_coin_is_whitelisted(storage, &coin.denom)?; + assert_coin_is_whitelisted(deps, &coin.denom)?; if coin.amount.is_zero() { return Ok(response); @@ -23,7 +23,7 @@ pub fn deposit( received_coins.deduct(coin)?; - increment_coin_balance(storage, account_id, coin)?; + increment_coin_balance(deps.storage, account_id, coin)?; Ok(response .add_attribute("action", "callback/deposit") diff --git a/contracts/credit-manager/src/emergency_update.rs b/contracts/credit-manager/src/emergency_update.rs deleted file mode 100644 index 64bf9b2a0..000000000 --- a/contracts/credit-manager/src/emergency_update.rs +++ /dev/null @@ -1,57 +0,0 @@ -use cosmwasm_std::{Decimal, DepsMut, MessageInfo, Response, Uint128}; -use mars_rover::{ - adapters::vault::VaultUnchecked, - error::{ContractError::InvalidConfig, ContractResult}, - msg::execute::EmergencyUpdate, -}; - -use crate::state::{ALLOWED_COINS, OWNER, VAULT_CONFIGS}; - -pub fn emergency_config_update( - deps: DepsMut, - info: MessageInfo, - update: EmergencyUpdate, -) -> ContractResult { - OWNER.assert_emergency_owner(deps.storage, &info.sender)?; - - match update { - EmergencyUpdate::SetZeroMaxLtv(v) => set_zero_max_ltv(deps, v), - EmergencyUpdate::SetZeroDepositCap(v) => set_zero_deposit_cap(deps, v), - EmergencyUpdate::DisallowCoin(denom) => disallow_coin(deps, &denom), - } -} - -pub fn set_zero_max_ltv(deps: DepsMut, v: VaultUnchecked) -> ContractResult { - let vault = deps.api.addr_validate(&v.address)?; - let mut config = VAULT_CONFIGS.load(deps.storage, &vault)?; - config.max_ltv = Decimal::zero(); - VAULT_CONFIGS.save(deps.storage, &vault, &config)?; - - Ok(Response::new() - .add_attribute("action", "set_zero_max_ltv") - .add_attribute("vault", v.address)) -} - -pub fn set_zero_deposit_cap(deps: DepsMut, v: VaultUnchecked) -> ContractResult { - let vault = deps.api.addr_validate(&v.address)?; - let mut config = VAULT_CONFIGS.load(deps.storage, &vault)?; - config.deposit_cap.amount = Uint128::zero(); - VAULT_CONFIGS.save(deps.storage, &vault, &config)?; - - Ok(Response::new() - .add_attribute("action", "set_zero_deposit_cap") - .add_attribute("vault", v.address)) -} - -pub fn disallow_coin(deps: DepsMut, denom: &str) -> ContractResult { - let result = ALLOWED_COINS.remove(deps.storage, denom)?; - if !result { - return Err(InvalidConfig { - reason: format!("{denom} not in config"), - }); - } - - Ok(Response::new() - .add_attribute("action", "disallow_coin") - .add_attribute("denom", denom.to_string())) -} diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 229730d8e..8684e867d 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -45,7 +45,7 @@ pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult { - response = deposit(deps.storage, response, account_id, coin, &mut received_coins)?; + response = deposit(&mut deps, response, account_id, coin, &mut received_coins)?; } Action::Withdraw(coin) => callbacks.push(CallbackMsg::Withdraw { account_id: account_id.to_string(), diff --git a/contracts/credit-manager/src/instantiate.rs b/contracts/credit-manager/src/instantiate.rs index 73a465d78..f1b8cc27f 100644 --- a/contracts/credit-manager/src/instantiate.rs +++ b/contracts/credit-manager/src/instantiate.rs @@ -1,15 +1,9 @@ -use std::collections::HashSet; - -use cosmwasm_std::{Api, Decimal, DepsMut, QuerierWrapper, StdResult}; +use cosmwasm_std::DepsMut; use mars_owner::OwnerInit::SetInitialOwner; -use mars_rover::{ - error::{ContractError::InvalidConfig, ContractResult}, - msg::{instantiate::VaultInstantiateConfig, InstantiateMsg}, -}; +use mars_rover::{error::ContractResult, msg::InstantiateMsg}; use crate::state::{ - ALLOWED_COINS, HEALTH_CONTRACT, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, - RED_BANK, SWAPPER, VAULT_CONFIGS, ZAPPER, + HEALTH_CONTRACT, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, RED_BANK, SWAPPER, ZAPPER, }; pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { @@ -27,69 +21,7 @@ pub fn store_config(deps: DepsMut, msg: &InstantiateMsg) -> ContractResult<()> { ZAPPER.save(deps.storage, &msg.zapper.check(deps.api)?)?; MAX_UNLOCKING_POSITIONS.save(deps.storage, &msg.max_unlocking_positions)?; HEALTH_CONTRACT.save(deps.storage, &msg.health_contract.check(deps.api)?)?; + PARAMS.save(deps.storage, &msg.params.check(deps.api)?)?; - assert_lte_to_one(&msg.max_close_factor)?; - MAX_CLOSE_FACTOR.save(deps.storage, &msg.max_close_factor)?; - - assert_no_duplicate_vaults(deps.api, &deps.querier, &msg.vault_configs)?; - msg.vault_configs.iter().try_for_each(|v| -> ContractResult<_> { - v.config.check()?; - let vault = v.vault.check(deps.api)?; - Ok(VAULT_CONFIGS.save(deps.storage, &vault.address, &v.config)?) - })?; - - assert_no_duplicate_coins(&msg.allowed_coins)?; - msg.allowed_coins - .iter() - .try_for_each(|denom| ALLOWED_COINS.insert(deps.storage, denom).map(|_| ()))?; - - Ok(()) -} - -pub fn assert_no_duplicate_vaults( - api: &dyn Api, - querier: &QuerierWrapper, - vaults: &[VaultInstantiateConfig], -) -> ContractResult<()> { - let set: HashSet<_> = vaults.iter().map(|v| v.vault.address.clone()).collect(); - if set.len() != vaults.len() { - return Err(InvalidConfig { - reason: "Duplicate vault configs present".to_string(), - }); - } - - let set: HashSet<_> = vaults - .iter() - .map(|v| { - let vault = v.vault.check(api)?; - let info = vault.query_info(querier)?; - Ok(info.vault_token) - }) - .collect::>()?; - if set.len() != vaults.len() { - return Err(InvalidConfig { - reason: "Multiple vaults share the same vault token".to_string(), - }); - } - - Ok(()) -} - -pub fn assert_no_duplicate_coins(denoms: &[String]) -> ContractResult<()> { - let set: HashSet<_> = denoms.iter().collect(); - if set.len() != denoms.len() { - return Err(InvalidConfig { - reason: "Duplicate coin configs present".to_string(), - }); - } - Ok(()) -} - -pub fn assert_lte_to_one(dec: &Decimal) -> ContractResult<()> { - if dec > &Decimal::one() { - return Err(InvalidConfig { - reason: "value greater than one".to_string(), - }); - } Ok(()) } diff --git a/contracts/credit-manager/src/lend.rs b/contracts/credit-manager/src/lend.rs index 34b723d93..e9c5774b8 100644 --- a/contracts/credit-manager/src/lend.rs +++ b/contracts/credit-manager/src/lend.rs @@ -8,12 +8,12 @@ use crate::{ pub static DEFAULT_LENT_SHARES_PER_COIN: Uint128 = Uint128::new(1_000_000); -pub fn lend(deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> ContractResult { +pub fn lend(mut deps: DepsMut, env: Env, account_id: &str, coin: Coin) -> ContractResult { if coin.amount.is_zero() { return Err(ContractError::NoAmount); } - assert_coin_is_whitelisted(deps.storage, &coin.denom)?; + assert_coin_is_whitelisted(&mut deps, &coin.denom)?; let red_bank = RED_BANK.load(deps.storage)?; let total_lent = red_bank.query_lent(&deps.querier, &env.contract.address, &coin.denom)?; diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 525d6aba9..00f7c016b 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -2,7 +2,6 @@ pub mod contract; pub mod borrow; pub mod deposit; -pub mod emergency_update; pub mod execute; pub mod health; pub mod instantiate; diff --git a/contracts/credit-manager/src/liquidate_deposit.rs b/contracts/credit-manager/src/liquidate_deposit.rs index 59953ec66..a00f5e0f1 100644 --- a/contracts/credit-manager/src/liquidate_deposit.rs +++ b/contracts/credit-manager/src/liquidate_deposit.rs @@ -13,7 +13,7 @@ use mars_rover::{ use crate::{ health::query_health, repay::current_debt_for_denom, - state::{COIN_BALANCES, MAX_CLOSE_FACTOR, ORACLE, RED_BANK}, + state::{COIN_BALANCES, ORACLE, PARAMS}, utils::{decrement_coin_balance, increment_coin_balance}, }; @@ -82,7 +82,8 @@ pub fn calculate_liquidation( current_debt_for_denom(deps.as_ref(), env, liquidatee_account_id, &debt_coin.denom)?; // Ensure debt amount does not exceed close factor % of the liquidatee's total debt value - let close_factor = MAX_CLOSE_FACTOR.load(deps.storage)?; + let params = PARAMS.load(deps.storage)?; + let close_factor = params.query_max_close_factor(&deps.querier)?; let max_close_value = health.total_debt_value.checked_mul_floor(close_factor)?; let oracle = ORACLE.load(deps.storage)?; let debt_res = oracle.query_price(&deps.querier, &debt_coin.denom)?; @@ -92,10 +93,9 @@ pub fn calculate_liquidation( // FORMULA: debt amount = request value / (1 + liquidation bonus %) / debt price let request_res = oracle.query_price(&deps.querier, request_coin)?; let max_request_value = request_coin_balance.checked_mul_floor(request_res.price)?; - let liq_bonus_rate = RED_BANK - .load(deps.storage)? - .query_market(&deps.querier, &debt_coin.denom)? - .liquidation_bonus; + + let denom_params = params.query_asset_params(&deps.querier, &debt_coin.denom)?; + let liq_bonus_rate = denom_params.liquidation_bonus; let request_coin_adjusted_max_debt = max_request_value .checked_div_floor(Decimal::one().add(liq_bonus_rate))? .checked_div_floor(debt_res.price)?; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index 3c8f88823..cdd989f8a 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -1,35 +1,32 @@ -use cosmwasm_std::{Addr, Coin, Deps, Env, Order, StdResult, Uint128}; +use cosmwasm_std::{Coin, Deps, Env, Order, StdResult}; use cw_paginate::paginate_map; use cw_storage_plus::Bound; use mars_rover::{ - adapters::vault::{Vault, VaultBase, VaultPosition, VaultPositionValue, VaultUnchecked}, + adapters::vault::{VaultBase, VaultPosition, VaultPositionValue, VaultUnchecked}, error::ContractResult, msg::query::{ CoinBalanceResponseItem, ConfigResponse, DebtAmount, DebtShares, LentAmount, LentShares, - Positions, SharesResponseItem, VaultConfigResponse, VaultPositionResponseItem, - VaultUtilizationResponse, VaultWithBalance, + Positions, SharesResponseItem, VaultPositionResponseItem, VaultUtilizationResponse, }, }; use crate::{ state::{ - ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, DEBT_SHARES, HEALTH_CONTRACT, LENT_SHARES, - MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, SWAPPER, - TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, VAULT_CONFIGS, VAULT_POSITIONS, ZAPPER, + ACCOUNT_NFT, COIN_BALANCES, DEBT_SHARES, HEALTH_CONTRACT, LENT_SHARES, + MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, PARAMS, RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, + TOTAL_LENT_SHARES, VAULT_POSITIONS, ZAPPER, }, utils::{debt_shares_to_amount, lent_shares_to_amount}, vault::vault_utilization_in_deposit_cap_denom, }; -const DEFAULT_LIMIT: u32 = 10; - pub fn query_config(deps: Deps) -> ContractResult { Ok(ConfigResponse { ownership: OWNER.query(deps.storage)?, account_nft: ACCOUNT_NFT.may_load(deps.storage)?.map(|addr| addr.to_string()), red_bank: RED_BANK.load(deps.storage)?.address().into(), oracle: ORACLE.load(deps.storage)?.address().into(), - max_close_factor: MAX_CLOSE_FACTOR.load(deps.storage)?, + params: PARAMS.load(deps.storage)?.address().into(), max_unlocking_positions: MAX_UNLOCKING_POSITIONS.load(deps.storage)?, swapper: SWAPPER.load(deps.storage)?.address().into(), zapper: ZAPPER.load(deps.storage)?.address().into(), @@ -144,40 +141,6 @@ pub fn query_all_lent_shares( }) } -pub fn query_vault_config( - deps: Deps, - unchecked: VaultUnchecked, -) -> ContractResult { - let vault = unchecked.check(deps.api)?; - let config = VAULT_CONFIGS.load(deps.storage, &vault.address)?; - Ok(VaultConfigResponse { - config, - vault: vault.into(), - }) -} - -pub fn query_vaults_config( - deps: Deps, - start_after: Option, - limit: Option, -) -> ContractResult> { - let vault: Vault; - let start = match &start_after { - Some(unchecked) => { - vault = unchecked.check(deps.api)?; - Some(Bound::exclusive(&vault.address)) - } - None => None, - }; - paginate_map(&VAULT_CONFIGS, deps.storage, start, limit, |addr, config| { - let vault = VaultBase::new(addr); - Ok(VaultConfigResponse { - config, - vault: vault.into(), - }) - }) -} - pub fn query_vault_utilization( deps: Deps, env: Env, @@ -227,23 +190,6 @@ pub fn query_all_vault_positions( }) } -/// NOTE: This implementation of the query function assumes the map `ALLOWED_COINS` only saves `Empty`. -/// If a coin is to be removed from the whitelist, the map must remove the corresponding key. -pub fn query_allowed_coins( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult> { - let start = start_after.as_ref().map(|denom| Bound::exclusive(denom.as_str())); - - let limit = limit.unwrap_or(DEFAULT_LIMIT) as usize; - - ALLOWED_COINS - .items(deps.storage, start, None, Order::Ascending) - .take(limit) - .collect::>>() -} - pub fn query_total_debt_shares(deps: Deps, denom: &str) -> StdResult { let shares = TOTAL_DEBT_SHARES.load(deps.storage, denom)?; Ok(DebtShares { @@ -288,39 +234,6 @@ pub fn query_all_total_lent_shares( }) } -pub fn query_total_vault_coin_balance( - deps: Deps, - unchecked: &VaultUnchecked, - rover_addr: &Addr, -) -> StdResult { - let vault = unchecked.check(deps.api)?; - vault.query_balance(&deps.querier, rover_addr) -} - -pub fn query_all_total_vault_coin_balances( - deps: Deps, - rover_addr: &Addr, - start_after: Option, - limit: Option, -) -> StdResult> { - let vault: Vault; - let start = match &start_after { - Some(unchecked) => { - vault = unchecked.check(deps.api)?; - Some(Bound::exclusive(&vault.address)) - } - None => None, - }; - paginate_map(&VAULT_CONFIGS, deps.storage, start, limit, |addr, _| { - let vault = VaultBase::new(addr); - let balance = vault.query_balance(&deps.querier, rover_addr)?; - Ok(VaultWithBalance { - vault, - balance, - }) - }) -} - pub fn query_vault_position_value( deps: Deps, vault_position: VaultPosition, diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index d71eceb11..a1a6abf6f 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -1,14 +1,9 @@ -use cosmwasm_std::{Addr, Decimal, Uint128}; -use cw_item_set::Set; +use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::{Item, Map}; use mars_owner::Owner; use mars_rover::adapters::{ - health::HealthContract, - oracle::Oracle, - red_bank::RedBank, - swap::Swapper, - vault::{VaultConfig, VaultPositionAmount}, - zapper::Zapper, + health::HealthContract, oracle::Oracle, params::Params, red_bank::RedBank, swap::Swapper, + vault::VaultPositionAmount, zapper::Zapper, }; use crate::vault::RequestTempStorage; @@ -19,14 +14,12 @@ pub const ACCOUNT_NFT: Item = Item::new("account_nft"); pub const ORACLE: Item = Item::new("oracle"); pub const RED_BANK: Item = Item::new("red_bank"); pub const SWAPPER: Item = Item::new("swapper"); -pub const VAULT_CONFIGS: Map<&Addr, VaultConfig> = Map::new("vault_configs"); pub const ZAPPER: Item = Item::new("zapper"); pub const HEALTH_CONTRACT: Item = Item::new("health_contract"); +pub const PARAMS: Item = Item::new("params"); // Config pub const OWNER: Owner = Owner::new("owner"); -pub const ALLOWED_COINS: Set<&str> = Set::new("allowed_coins"); -pub const MAX_CLOSE_FACTOR: Item = Item::new("max_close_factor"); pub const MAX_UNLOCKING_POSITIONS: Item = Item::new("max_unlocking_positions"); // Positions diff --git a/contracts/credit-manager/src/swap.rs b/contracts/credit-manager/src/swap.rs index f403e3b7e..f8f592bff 100644 --- a/contracts/credit-manager/src/swap.rs +++ b/contracts/credit-manager/src/swap.rs @@ -10,14 +10,14 @@ use crate::{ }; pub fn swap_exact_in( - deps: DepsMut, + mut deps: DepsMut, env: Env, account_id: &str, coin_in: &ActionCoin, denom_out: &str, slippage: Decimal, ) -> ContractResult { - assert_coin_is_whitelisted(deps.storage, denom_out)?; + assert_coin_is_whitelisted(&mut deps, denom_out)?; let coin_in_to_trade = Coin { denom: coin_in.denom.clone(), diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index 50e010314..ed323db36 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -2,18 +2,10 @@ use cosmwasm_std::{to_binary, CosmosMsg, DepsMut, MessageInfo, Response, WasmMsg use cw721_base::Action; use mars_account_nft::{msg::ExecuteMsg as NftExecuteMsg, nft_config::NftConfigUpdates}; use mars_owner::OwnerUpdate; -use mars_rover::{ - error::ContractResult, - msg::instantiate::ConfigUpdates, - traits::{FallbackStr, Stringify}, -}; +use mars_rover::{error::ContractResult, msg::instantiate::ConfigUpdates}; -use crate::{ - instantiate::{assert_lte_to_one, assert_no_duplicate_coins, assert_no_duplicate_vaults}, - state::{ - ACCOUNT_NFT, ALLOWED_COINS, HEALTH_CONTRACT, MAX_CLOSE_FACTOR, MAX_UNLOCKING_POSITIONS, - ORACLE, OWNER, RED_BANK, SWAPPER, VAULT_CONFIGS, ZAPPER, - }, +use crate::state::{ + ACCOUNT_NFT, HEALTH_CONTRACT, MAX_UNLOCKING_POSITIONS, ORACLE, OWNER, RED_BANK, SWAPPER, ZAPPER, }; pub fn update_config( @@ -42,29 +34,6 @@ pub fn update_config( .add_attribute("value", addr_str); } - if let Some(coins) = updates.allowed_coins { - assert_no_duplicate_coins(&coins)?; - ALLOWED_COINS.clear(deps.storage); - coins.iter().try_for_each(|denom| ALLOWED_COINS.insert(deps.storage, denom).map(|_| ()))?; - - response = response - .add_attribute("key", "allowed_coins") - .add_attribute("value", coins.join(", ").fallback("None")); - } - - if let Some(configs) = updates.vault_configs { - assert_no_duplicate_vaults(deps.api, &deps.querier, &configs)?; - VAULT_CONFIGS.clear(deps.storage); - configs.iter().try_for_each(|v| -> ContractResult<_> { - v.config.check()?; - let vault = v.vault.check(deps.api)?; - Ok(VAULT_CONFIGS.save(deps.storage, &vault.address, &v.config)?) - })?; - response = response - .add_attribute("key", "vault_configs") - .add_attribute("value", configs.to_string().fallback("None")) - } - if let Some(unchecked) = updates.oracle { ORACLE.save(deps.storage, &unchecked.check(deps.api)?)?; response = @@ -89,14 +58,6 @@ pub fn update_config( response.add_attribute("key", "zapper").add_attribute("value", unchecked.address()); } - if let Some(cf) = updates.max_close_factor { - assert_lte_to_one(&cf)?; - MAX_CLOSE_FACTOR.save(deps.storage, &cf)?; - response = response - .add_attribute("key", "max_close_factor") - .add_attribute("value", cf.to_string()); - } - if let Some(num) = updates.max_unlocking_positions { MAX_UNLOCKING_POSITIONS.save(deps.storage, &num)?; response = response diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index 3078e3ca3..fc10f60ed 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -1,8 +1,8 @@ use std::{collections::HashSet, hash::Hash}; use cosmwasm_std::{ - to_binary, Addr, Coin, CosmosMsg, Decimal, Deps, DepsMut, Empty, Order::Ascending, - QuerierWrapper, StdResult, Storage, Uint128, WasmMsg, + to_binary, Addr, Coin, CosmosMsg, Decimal, Deps, DepsMut, Empty, QuerierWrapper, StdResult, + Storage, Uint128, WasmMsg, }; use cw721::OwnerOfResponse; use cw721_base::QueryMsg; @@ -13,8 +13,8 @@ use mars_rover::{ use crate::{ state::{ - ACCOUNT_NFT, ALLOWED_COINS, COIN_BALANCES, HEALTH_CONTRACT, LENT_SHARES, ORACLE, RED_BANK, - SWAPPER, TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, VAULT_CONFIGS, ZAPPER, + ACCOUNT_NFT, COIN_BALANCES, HEALTH_CONTRACT, LENT_SHARES, ORACLE, PARAMS, RED_BANK, + SWAPPER, TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, ZAPPER, }, update_coin_balances::query_balance, }; @@ -42,19 +42,16 @@ pub fn query_nft_token_owner(deps: Deps, account_id: &str) -> ContractResult ContractResult<()> { - let is_whitelisted = ALLOWED_COINS.contains(storage, denom); - if !is_whitelisted { - return Err(ContractError::NotWhitelisted(denom.to_string())); +pub fn assert_coin_is_whitelisted(deps: &mut DepsMut, denom: &str) -> ContractResult<()> { + let params = PARAMS.load(deps.storage)?; + match params.query_asset_params(&deps.querier, denom) { + Ok(p) if p.rover.whitelisted => Ok(()), + _ => Err(ContractError::NotWhitelisted(denom.to_string())), } - Ok(()) } -pub fn assert_coins_are_whitelisted( - storage: &mut dyn Storage, - denoms: Vec<&str>, -) -> ContractResult<()> { - denoms.iter().try_for_each(|denom| assert_coin_is_whitelisted(storage, denom)) +pub fn assert_coins_are_whitelisted(deps: &mut DepsMut, denoms: Vec<&str>) -> ContractResult<()> { + denoms.iter().try_for_each(|denom| assert_coin_is_whitelisted(deps, denom)) } pub fn increment_coin_balance( @@ -192,8 +189,6 @@ pub fn lent_shares_to_amount( /// which rely on pre-post querying of bank balances of Rover. /// NOTE: https://twitter.com/larry0x/status/1595919149381079041 pub fn assert_not_contract_in_config(deps: &Deps, addr_to_flag: &Addr) -> ContractResult<()> { - let vault_addrs = - VAULT_CONFIGS.keys(deps.storage, None, None, Ascending).collect::>>()?; let config_contracts = vec![ ACCOUNT_NFT.load(deps.storage)?, RED_BANK.load(deps.storage)?.address().clone(), @@ -203,8 +198,7 @@ pub fn assert_not_contract_in_config(deps: &Deps, addr_to_flag: &Addr) -> Contra HEALTH_CONTRACT.load(deps.storage)?.address().clone(), ]; - let flagged_addr_in_config = - config_contracts.into_iter().chain(vault_addrs).any(|addr| addr == *addr_to_flag); + let flagged_addr_in_config = config_contracts.into_iter().any(|addr| addr == *addr_to_flag); if flagged_addr_in_config { return Err(ContractError::Unauthorized { diff --git a/contracts/credit-manager/src/vault/enter.rs b/contracts/credit-manager/src/vault/enter.rs index a6aca95e6..31a019965 100644 --- a/contracts/credit-manager/src/vault/enter.rs +++ b/contracts/credit-manager/src/vault/enter.rs @@ -11,7 +11,7 @@ use mars_rover::{ }; use crate::{ - state::{COIN_BALANCES, ORACLE, VAULT_CONFIGS}, + state::{COIN_BALANCES, ORACLE, PARAMS}, utils::{assert_coin_is_whitelisted, decrement_coin_balance}, vault::{ rover_vault_coin_balance_value, @@ -20,7 +20,7 @@ use crate::{ }; pub fn enter_vault( - deps: DepsMut, + mut deps: DepsMut, rover_addr: &Addr, account_id: &str, vault: Vault, @@ -37,8 +37,8 @@ pub fn enter_vault( amount, }; - assert_coin_is_whitelisted(deps.storage, &coin.denom)?; - assert_vault_is_whitelisted(deps.storage, &vault)?; + assert_coin_is_whitelisted(&mut deps, &coin.denom)?; + assert_vault_is_whitelisted(&mut deps, &vault)?; assert_denom_matches_vault_reqs(deps.querier, &vault, &coin_to_enter)?; assert_deposit_is_under_cap(deps.as_ref(), &vault, &coin_to_enter, rover_addr)?; @@ -124,7 +124,8 @@ pub fn assert_deposit_is_under_cap( let new_total_vault_value = rover_vault_balance_value.checked_add(deposit_request_value)?; - let config = VAULT_CONFIGS.load(deps.storage, &vault.address)?; + let params = PARAMS.load(deps.storage)?; + let config = params.query_vault_config(&deps.querier, &vault.address)?; let deposit_cap_value = oracle.query_total_value(&deps.querier, &[config.deposit_cap])?; if new_total_vault_value > deposit_cap_value { diff --git a/contracts/credit-manager/src/vault/exit.rs b/contracts/credit-manager/src/vault/exit.rs index 5e7a9df3a..cfe68dd4e 100644 --- a/contracts/credit-manager/src/vault/exit.rs +++ b/contracts/credit-manager/src/vault/exit.rs @@ -10,13 +10,13 @@ use crate::vault::utils::{ }; pub fn exit_vault( - deps: DepsMut, + mut deps: DepsMut, env: Env, account_id: &str, vault: Vault, amount: Uint128, ) -> ContractResult { - assert_vault_is_whitelisted(deps.storage, &vault)?; + assert_vault_is_whitelisted(&mut deps, &vault)?; // Force indicates that the vault is one with a required lockup that needs to be broken // In this case, we'll need to withdraw from the locked bucket diff --git a/contracts/credit-manager/src/vault/exit_unlocked.rs b/contracts/credit-manager/src/vault/exit_unlocked.rs index 00f0bd73d..0ae387d69 100644 --- a/contracts/credit-manager/src/vault/exit_unlocked.rs +++ b/contracts/credit-manager/src/vault/exit_unlocked.rs @@ -14,13 +14,13 @@ use crate::{ }; pub fn exit_vault_unlocked( - deps: DepsMut, + mut deps: DepsMut, env: Env, account_id: &str, vault: Vault, position_id: u64, ) -> ContractResult { - assert_vault_is_whitelisted(deps.storage, &vault)?; + assert_vault_is_whitelisted(&mut deps, &vault)?; let vault_position = VAULT_POSITIONS.load(deps.storage, (account_id, vault.address.clone()))?; let matching_unlock = vault_position diff --git a/contracts/credit-manager/src/vault/request_unlock.rs b/contracts/credit-manager/src/vault/request_unlock.rs index 39ae4e509..59cb0dafe 100644 --- a/contracts/credit-manager/src/vault/request_unlock.rs +++ b/contracts/credit-manager/src/vault/request_unlock.rs @@ -24,12 +24,12 @@ pub struct RequestTempStorage { } pub fn request_vault_unlock( - deps: DepsMut, + mut deps: DepsMut, account_id: &str, vault: Vault, amount: Uint128, ) -> ContractResult { - assert_vault_is_whitelisted(deps.storage, &vault)?; + assert_vault_is_whitelisted(&mut deps, &vault)?; vault.query_lockup_duration(&deps.querier).map_err(|_| { ContractError::RequirementsNotMet( "This vault does not require lockup. Call withdraw directly.".to_string(), diff --git a/contracts/credit-manager/src/vault/utils.rs b/contracts/credit-manager/src/vault/utils.rs index 5c37d8c3d..6f2045bfe 100644 --- a/contracts/credit-manager/src/vault/utils.rs +++ b/contracts/credit-manager/src/vault/utils.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Coin, Deps, StdResult, Storage, Uint128}; +use cosmwasm_std::{Addr, Coin, Deps, DepsMut, StdResult, Storage, Uint128}; use mars_rover::{ adapters::vault::{ LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultPosition, @@ -8,23 +8,24 @@ use mars_rover::{ }; use crate::{ - state::{MAX_UNLOCKING_POSITIONS, ORACLE, VAULT_CONFIGS, VAULT_POSITIONS}, + state::{MAX_UNLOCKING_POSITIONS, ORACLE, PARAMS, VAULT_POSITIONS}, update_coin_balances::query_balance, }; -pub fn assert_vault_is_whitelisted(storage: &dyn Storage, vault: &Vault) -> ContractResult<()> { - let is_whitelisted = vault_is_whitelisted(storage, vault)?; +pub fn assert_vault_is_whitelisted(deps: &mut DepsMut, vault: &Vault) -> ContractResult<()> { + let is_whitelisted = vault_is_whitelisted(deps, vault)?; if !is_whitelisted { return Err(ContractError::NotWhitelisted(vault.address.to_string())); } Ok(()) } -pub fn vault_is_whitelisted(storage: &dyn Storage, vault: &Vault) -> ContractResult { - let config = VAULT_CONFIGS - .may_load(storage, &vault.address)? - .and_then(|config| config.whitelisted.then_some(true)); - Ok(config.is_some()) +pub fn vault_is_whitelisted(deps: &mut DepsMut, vault: &Vault) -> ContractResult { + Ok(PARAMS + .load(deps.storage)? + .query_vault_config(&deps.querier, &vault.address) + .map(|c| c.whitelisted) + .unwrap_or(false)) } pub fn assert_under_max_unlocking_limit( @@ -84,7 +85,8 @@ pub fn vault_utilization_in_deposit_cap_denom( rover_addr: &Addr, ) -> ContractResult { let rover_vault_balance_value = rover_vault_coin_balance_value(deps, vault, rover_addr)?; - let config = VAULT_CONFIGS.load(deps.storage, &vault.address)?; + let params = PARAMS.load(deps.storage)?; + let config = params.query_vault_config(&deps.querier, &vault.address)?; let oracle = ORACLE.load(deps.storage)?; let deposit_cap_denom_price = oracle.query_price(&deps.querier, &config.deposit_cap.denom)?.price; diff --git a/contracts/credit-manager/src/zap.rs b/contracts/credit-manager/src/zap.rs index 616a74071..30e5f74c2 100644 --- a/contracts/credit-manager/src/zap.rs +++ b/contracts/credit-manager/src/zap.rs @@ -14,15 +14,15 @@ use crate::{ }; pub fn provide_liquidity( - deps: DepsMut, + mut deps: DepsMut, env: Env, account_id: &str, coins_in: Vec, lp_token_out: &str, minimum_receive: Uint128, ) -> ContractResult { - assert_coin_is_whitelisted(deps.storage, lp_token_out)?; - assert_coins_are_whitelisted(deps.storage, coins_in.to_denoms())?; + assert_coin_is_whitelisted(&mut deps, lp_token_out)?; + assert_coins_are_whitelisted(&mut deps, coins_in.to_denoms())?; // Decrement coin amounts in account for those sent to pool let mut updated_coins_in: Vec = Vec::with_capacity(coins_in.len()); @@ -56,12 +56,12 @@ pub fn provide_liquidity( } pub fn withdraw_liquidity( - deps: DepsMut, + mut deps: DepsMut, env: Env, account_id: &str, lp_token_action: &ActionCoin, ) -> ContractResult { - assert_coin_is_whitelisted(deps.storage, &lp_token_action.denom)?; + assert_coin_is_whitelisted(&mut deps, &lp_token_action.denom)?; let lp_token = Coin { denom: lp_token_action.denom.clone(), @@ -79,7 +79,7 @@ pub fn withdraw_liquidity( let zapper = ZAPPER.load(deps.storage)?; let coins_out = zapper.estimate_withdraw_liquidity(&deps.querier, &lp_token)?; - assert_coins_are_whitelisted(deps.storage, coins_out.to_denoms())?; + assert_coins_are_whitelisted(&mut deps, coins_out.to_denoms())?; decrement_coin_balance(deps.storage, account_id, &lp_token)?; diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index da1ac7696..ddf9cb977 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -11,6 +11,7 @@ pub fn build_mock_coin_infos(count: usize) -> Vec { liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), price: Decimal::from_atomics(10u128, 0).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + whitelisted: true, }) .collect() } diff --git a/contracts/credit-manager/tests/helpers/contracts.rs b/contracts/credit-manager/tests/helpers/contracts.rs index 81e456e1c..ebf4b7da9 100644 --- a/contracts/credit-manager/tests/helpers/contracts.rs +++ b/contracts/credit-manager/tests/helpers/contracts.rs @@ -77,3 +77,12 @@ pub fn mock_health_contract() -> Box> { ); Box::new(contract) } + +pub fn mock_params_contract() -> Box> { + let contract = ContractWrapper::new( + mars_params::contract::execute, + mars_params::contract::instantiate, + mars_params::contract::query, + ); + Box::new(contract) +} diff --git a/contracts/credit-manager/tests/helpers/mock_entity_info.rs b/contracts/credit-manager/tests/helpers/mock_entity_info.rs index ba22ae557..e0756a951 100644 --- a/contracts/credit-manager/tests/helpers/mock_entity_info.rs +++ b/contracts/credit-manager/tests/helpers/mock_entity_info.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use cosmwasm_std::{coin, Decimal}; use cw_utils::Duration; @@ -10,8 +12,10 @@ pub fn uosmo_info() -> CoinInfo { max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), + whitelisted: true, } } + pub fn uatom_info() -> CoinInfo { CoinInfo { denom: "uatom".to_string(), @@ -19,6 +23,7 @@ pub fn uatom_info() -> CoinInfo { max_ltv: Decimal::from_atomics(82u128, 2).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(10u128, 2).unwrap(), + whitelisted: true, } } @@ -29,6 +34,18 @@ pub fn ujake_info() -> CoinInfo { max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + whitelisted: true, + } +} + +pub fn blacklisted_coin() -> CoinInfo { + CoinInfo { + denom: "uluna".to_string(), + price: Decimal::from_str("0.01").unwrap(), + max_ltv: Decimal::from_str("0.4").unwrap(), + liquidation_threshold: Decimal::from_str("0.5").unwrap(), + liquidation_bonus: Decimal::from_str("0.33").unwrap(), + whitelisted: false, } } @@ -39,6 +56,7 @@ pub fn lp_token_info() -> CoinInfo { max_ltv: Decimal::from_atomics(63u128, 2).unwrap(), liquidation_threshold: Decimal::from_atomics(68u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), + whitelisted: true, } } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 07d508adc..f3e3af319 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -1,7 +1,7 @@ -use std::mem::take; +use std::{mem::take, str::FromStr}; use anyhow::Result as AnyResult; -use cosmwasm_std::{coins, testing::MockApi, Addr, Coin, Decimal, StdResult, Uint128}; +use cosmwasm_std::{coins, testing::MockApi, Addr, Coin, Decimal, Empty, StdResult, Uint128}; use cw721_base::{Action::TransferOwnership, Ownership}; use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use cw_vault_standard::{ @@ -17,11 +17,20 @@ use mars_account_nft::{ use mars_mock_oracle::msg::{ CoinPrice, ExecuteMsg as OracleExecuteMsg, InstantiateMsg as OracleInstantiateMsg, }; -use mars_mock_red_bank::msg::{CoinMarketInfo, InstantiateMsg as RedBankInstantiateMsg}; use mars_mock_vault::{ contract::DEFAULT_VAULT_TOKEN_PREFUND, msg::InstantiateMsg as VaultInstantiateMsg, }; use mars_owner::OwnerUpdate; +use mars_params::{ + msg::{ + ExecuteMsg::{UpdateAssetParams, UpdateVaultConfig}, + InstantiateMsg as ParamsInstantiateMsg, QueryMsg as ParamsQueryMsg, + }, + types::{ + AssetParams, AssetParamsUpdate, AssetParamsUpdate::AddOrUpdate, VaultConfig, + VaultConfigUnchecked, VaultConfigUpdate, + }, +}; use mars_red_bank_types::red_bank::{ QueryMsg::{UserCollateral, UserDebt}, UserCollateralResponse, UserDebtResponse, @@ -30,24 +39,21 @@ use mars_rover::{ adapters::{ health::HealthContract, oracle::{Oracle, OracleBase, OracleUnchecked}, + params::Params, red_bank::RedBankBase, swap::{ EstimateExactInSwapResponse, InstantiateMsg as SwapperInstantiateMsg, QueryMsg::EstimateExactInSwap, Swapper, SwapperBase, }, - vault::{ - VaultBase, VaultConfig, VaultPosition, VaultPositionValue as VPositionValue, - VaultUnchecked, - }, + vault::{Vault, VaultPosition, VaultPositionValue as VPositionValue, VaultUnchecked}, zapper::{Zapper, ZapperBase}, }, msg::{ - execute::{Action, CallbackMsg, EmergencyUpdate}, - instantiate::{ConfigUpdates, VaultInstantiateConfig}, + execute::{Action, CallbackMsg}, + instantiate::ConfigUpdates, query::{ CoinBalanceResponseItem, ConfigResponse, DebtShares, LentShares, Positions, - SharesResponseItem, VaultConfigResponse, VaultPositionResponseItem, - VaultUtilizationResponse, VaultWithBalance, + SharesResponseItem, VaultPositionResponseItem, VaultUtilizationResponse, }, zapper::{ InstantiateMsg as ZapperInstantiateMsg, LpConfig, QueryMsg::EstimateProvideLiquidity, @@ -63,8 +69,8 @@ use mars_rover_health_types::{ use crate::helpers::{ lp_token_info, mock_account_nft_contract, mock_health_contract, mock_oracle_contract, - mock_red_bank_contract, mock_rover_contract, mock_swapper_contract, mock_v2_zapper_contract, - mock_vault_contract, AccountToFund, CoinInfo, VaultTestInfo, + mock_params_contract, mock_red_bank_contract, mock_rover_contract, mock_swapper_contract, + mock_v2_zapper_contract, mock_vault_contract, AccountToFund, CoinInfo, VaultTestInfo, }; pub const DEFAULT_RED_BANK_COIN_BALANCE: Uint128 = Uint128::new(1_000_000); @@ -74,6 +80,7 @@ pub struct MockEnv { pub rover: Addr, pub mars_oracle: Addr, pub health_contract: HealthContract, + pub params: Params, } pub struct MockEnvBuilder { @@ -81,9 +88,9 @@ pub struct MockEnvBuilder { pub owner: Option, pub emergency_owner: Option, pub vault_configs: Option>, - pub pre_deployed_vaults: Option>, - pub allowed_coins: Option>, + pub coin_params: Option>, pub oracle: Option, + pub params: Option, pub red_bank: Option>, pub deploy_nft_contract: bool, pub set_nft_contract_minter: bool, @@ -101,9 +108,9 @@ impl MockEnv { owner: None, emergency_owner: None, vault_configs: None, - pre_deployed_vaults: None, - allowed_coins: None, + coin_params: None, oracle: None, + params: None, red_bank: None, deploy_nft_contract: true, set_nft_contract_minter: true, @@ -180,17 +187,28 @@ impl MockEnv { ) } - pub fn emergency_update( - &mut self, - sender: &Addr, - update: EmergencyUpdate, - ) -> AnyResult { - self.app.execute_contract( - sender.clone(), - self.rover.clone(), - &ExecuteMsg::EmergencyConfigUpdate(update), - &[], - ) + pub fn update_asset_params(&mut self, update: AssetParamsUpdate) { + let config = self.query_config(); + self.app + .execute_contract( + Addr::unchecked(config.ownership.owner.unwrap()), + Addr::unchecked(config.params), + &UpdateAssetParams(update), + &[], + ) + .unwrap(); + } + + pub fn update_vault_params(&mut self, update: VaultConfigUpdate) { + let config = self.query_config(); + self.app + .execute_contract( + Addr::unchecked(config.ownership.owner.unwrap()), + Addr::unchecked(config.params), + &UpdateVaultConfig(update), + &[], + ) + .unwrap(); } pub fn update_nft_config( @@ -326,76 +344,65 @@ impl MockEnv { .unwrap() } - pub fn query_vault_config(&self, vault: &VaultUnchecked) -> StdResult { - self.app.wrap().query_wasm_smart( - self.rover.clone(), - &QueryMsg::VaultConfig { - vault: vault.clone(), - }, - ) + pub fn query_vault_params(&self, vault_addr: &str) -> VaultConfig { + self.app + .wrap() + .query_wasm_smart( + self.params.address(), + &ParamsQueryMsg::VaultConfig { + address: vault_addr.to_string(), + }, + ) + .unwrap() } - pub fn query_vault_configs( - &self, - start_after: Option, - limit: Option, - ) -> Vec { + pub fn query_asset_params(&self, denom: &str) -> AssetParams { self.app .wrap() .query_wasm_smart( - self.rover.clone(), - &QueryMsg::VaultsConfig { - start_after, - limit, + self.params.address(), + &ParamsQueryMsg::AssetParams { + denom: denom.to_string(), }, ) .unwrap() } - pub fn query_vault_utilization( - &self, - vault: &VaultUnchecked, - ) -> StdResult { - self.app.wrap().query_wasm_smart( - self.rover.clone(), - &QueryMsg::VaultUtilization { - vault: vault.clone(), - }, - ) + pub fn query_all_vault_params(&self) -> Vec { + self.app + .wrap() + .query_wasm_smart( + self.params.address(), + &ParamsQueryMsg::AllVaultConfigs { + start_after: None, + limit: Some(30), // Max limit + }, + ) + .unwrap() } pub fn get_vault(&self, vault: &VaultTestInfo) -> VaultUnchecked { - self.query_vault_configs(None, Some(30)) // Max limit + let vault_params = self.query_all_vault_params(); + let matched_vault = vault_params .iter() .find(|v| { - let info = v - .vault - .check(&MockApi::default()) - .unwrap() - .query_info(&self.app.wrap()) - .unwrap(); + let info = Vault::new(v.addr.clone()).query_info(&self.app.wrap()).unwrap(); vault.vault_token_denom == info.vault_token }) - .unwrap() - .vault - .clone() + .unwrap(); + VaultUnchecked::new(matched_vault.addr.to_string()) } - pub fn query_allowed_coins( + pub fn query_vault_utilization( &self, - start_after: Option, - limit: Option, - ) -> Vec { - self.app - .wrap() - .query_wasm_smart( - self.rover.clone(), - &QueryMsg::AllowedCoins { - start_after, - limit, - }, - ) - .unwrap() + vault: &VaultUnchecked, + ) -> StdResult { + self.app.wrap().query_wasm_smart( + self.rover.clone(), + &QueryMsg::VaultUtilization { + vault: vault.clone(), + }, + ) } pub fn query_all_coin_balances( @@ -560,15 +567,10 @@ impl MockEnv { } pub fn query_total_vault_coin_balance(&self, vault: &VaultUnchecked) -> Uint128 { - self.app - .wrap() - .query_wasm_smart( - self.rover.clone(), - &QueryMsg::TotalVaultCoinBalance { - vault: vault.clone(), - }, - ) - .unwrap() + let info = Vault::new(Addr::unchecked(vault.address.clone())) + .query_info(&self.app.wrap()) + .unwrap(); + self.app.wrap().query_balance(self.rover.clone(), info.vault_token).unwrap().amount } pub fn query_all_vault_positions( @@ -588,23 +590,6 @@ impl MockEnv { .unwrap() } - pub fn query_all_total_vault_coin_balances( - &self, - start_after: Option, - limit: Option, - ) -> Vec { - self.app - .wrap() - .query_wasm_smart( - self.rover.clone(), - &QueryMsg::AllTotalVaultCoinBalances { - start_after, - limit, - }, - ) - .unwrap() - } - pub fn query_swap_estimate( &self, coin_in: &Coin, @@ -657,17 +642,23 @@ impl MockEnvBuilder { let mars_oracle = self.get_oracle(); + let params = self.get_params_contract(); + self.add_params_to_contract(); + let health_contract = self.get_health_contract(); - self.update_health_contract_config(rover.clone()); + self.update_health_contract_config(&rover, params.address()); self.deploy_nft_contract(&rover); self.fund_users(); + self.deploy_vaults(); + Ok(MockEnv { app: take(&mut self.app), rover, mars_oracle: mars_oracle.address().clone(), health_contract, + params, }) } @@ -704,6 +695,24 @@ impl MockEnvBuilder { } } + fn add_params_to_contract(&mut self) { + let params_to_set = self.get_coin_params(); + let params_contract = self.get_params_contract(); + + for coin_info in params_to_set { + self.app + .execute_contract( + self.get_owner(), + params_contract.address().clone(), + &UpdateAssetParams(AddOrUpdate { + params: coin_info.into(), + }), + &[], + ) + .unwrap(); + } + } + pub fn set_emergency_owner(&mut self, rover: &Addr) { if let Some(eo) = self.emergency_owner.clone() { self.app @@ -740,33 +749,25 @@ impl MockEnvBuilder { let code_id = self.app.store_code(mock_rover_contract()); let red_bank = self.get_red_bank().into(); let swapper = self.deploy_swapper().into(); - let allowed_coins = - self.get_allowed_coins().iter().map(|info| info.denom.clone()).collect(); - let max_close_factor = self.get_max_close_factor(); let max_unlocking_positions = self.get_max_unlocking_positions(); - let mut vault_configs = vec![]; - vault_configs.extend(self.deploy_vaults()); - vault_configs.extend(self.pre_deployed_vaults.clone().unwrap_or_default()); - let oracle = self.get_oracle().into(); let zapper = self.deploy_zapper(&oracle)?.into(); let health_contract = self.get_health_contract().into(); + let params = self.get_params_contract().into(); self.app.instantiate_contract( code_id, self.get_owner(), &InstantiateMsg { owner: self.get_owner().to_string(), - allowed_coins, - vault_configs, red_bank, oracle, - max_close_factor, max_unlocking_positions, swapper, zapper, health_contract, + params, }, &[], "mock-rover-contract", @@ -789,7 +790,7 @@ impl MockEnvBuilder { fn deploy_oracle(&mut self) -> Oracle { let contract_code_id = self.app.store_code(mock_oracle_contract()); let mut prices: Vec = self - .get_allowed_coins() + .get_coin_params() .iter() .map(|item| CoinPrice { denom: item.denom.clone(), @@ -829,6 +830,38 @@ impl MockEnvBuilder { OracleBase::new(addr) } + fn get_params_contract(&mut self) -> Params { + if self.params.is_none() { + let hc = self.deploy_params_contract(); + self.params = Some(hc); + } + self.params.clone().unwrap() + } + + pub fn deploy_params_contract(&mut self) -> Params { + let contract_code_id = self.app.store_code(mock_params_contract()); + let owner = self.get_owner(); + + let addr = self + .app + .instantiate_contract( + contract_code_id, + owner.clone(), + &ParamsInstantiateMsg { + owner: owner.to_string(), + max_close_factor: self + .max_close_factor + .unwrap_or(Decimal::from_str("0.5").unwrap()), + }, + &[], + "mock-params-contract", + Some(owner.to_string()), + ) + .unwrap(); + + Params::new(addr) + } + fn get_health_contract(&mut self) -> HealthContract { if self.health_contract.is_none() { let hc = self.deploy_health_contract(); @@ -839,7 +872,7 @@ impl MockEnvBuilder { pub fn deploy_health_contract(&mut self) -> HealthContract { let contract_code_id = self.app.store_code(mock_health_contract()); - let owner = Addr::unchecked("health_contract_owner"); + let owner = self.get_owner(); let addr = self .app @@ -858,16 +891,16 @@ impl MockEnvBuilder { HealthContract::new(addr) } - fn update_health_contract_config(&mut self, cm_addr: Addr) { - let owner = Addr::unchecked("health_contract_owner"); + fn update_health_contract_config(&mut self, cm_addr: &Addr, params: &Addr) { let health_contract = self.get_health_contract(); self.app .execute_contract( - owner, + self.get_owner(), health_contract.address().clone(), &UpdateConfig { - credit_manager: cm_addr.to_string(), + credit_manager: Some(cm_addr.to_string()), + params: Some(params.to_string()), }, &[], ) @@ -889,18 +922,7 @@ impl MockEnvBuilder { .instantiate_contract( contract_code_id, Addr::unchecked("red_bank_contract_owner"), - &RedBankInstantiateMsg { - coins: self - .get_allowed_coins() - .iter() - .map(|item| CoinMarketInfo { - denom: item.denom.to_string(), - max_ltv: item.max_ltv, - liquidation_threshold: item.liquidation_threshold, - liquidation_bonus: item.liquidation_bonus, - }) - .collect(), - }, + &Empty {}, &[], "mock-red-bank", None, @@ -908,12 +930,12 @@ impl MockEnvBuilder { .unwrap(); // fund red bank with whitelisted coins - if !self.get_allowed_coins().is_empty() { + if !self.get_coin_params().is_empty() { self.app .sudo(SudoMsg::Bank(BankSudo::Mint { to_address: addr.to_string(), amount: self - .get_allowed_coins() + .get_coin_params() .iter() .map(|info| info.to_coin(DEFAULT_RED_BANK_COIN_BALANCE.u128())) .collect(), @@ -924,10 +946,10 @@ impl MockEnvBuilder { RedBankBase::new(addr) } - fn deploy_vault(&mut self, vault: &VaultTestInfo) -> VaultInstantiateConfig { + fn deploy_vault(&mut self, vault: &VaultTestInfo) -> Addr { let code_id = self.app.store_code(mock_vault_contract()); let oracle = self.get_oracle().into(); - let addr = self + let vault_addr = self .app .instantiate_contract( code_id, @@ -943,16 +965,28 @@ impl MockEnvBuilder { None, ) .unwrap(); - self.fund_vault(&addr, &vault.vault_token_denom); - VaultInstantiateConfig { - vault: VaultBase::new(addr.to_string()), - config: VaultConfig { - deposit_cap: vault.deposit_cap.clone(), - max_ltv: vault.max_ltv, - liquidation_threshold: vault.liquidation_threshold, - whitelisted: vault.whitelisted, - }, - } + self.fund_vault(&vault_addr, &vault.vault_token_denom); + + let params = self.get_params_contract(); + + self.app + .execute_contract( + self.get_owner(), + params.address().clone(), + &UpdateVaultConfig(VaultConfigUpdate::AddOrUpdate { + config: VaultConfigUnchecked { + addr: vault_addr.to_string(), + deposit_cap: vault.deposit_cap.clone(), + max_loan_to_value: vault.max_ltv, + liquidation_threshold: vault.liquidation_threshold, + whitelisted: vault.whitelisted, + }, + }), + &[], + ) + .unwrap(); + + vault_addr } fn deploy_swapper(&mut self) -> Swapper { @@ -1021,7 +1055,7 @@ impl MockEnvBuilder { .unwrap(); } - fn deploy_vaults(&mut self) -> Vec { + fn deploy_vaults(&mut self) -> Vec { self.vault_configs .clone() .unwrap_or_default() @@ -1030,13 +1064,8 @@ impl MockEnvBuilder { .collect() } - fn get_allowed_coins(&self) -> Vec { - self.allowed_coins.clone().unwrap_or_default() - } - - fn get_max_close_factor(&self) -> Decimal { - self.max_close_factor.unwrap_or_else(|| Decimal::from_atomics(5u128, 1).unwrap()) - // 50% + fn get_coin_params(&self) -> Vec { + self.coin_params.clone().unwrap_or_default() } fn get_max_unlocking_positions(&self) -> Uint128 { @@ -1067,8 +1096,18 @@ impl MockEnvBuilder { self } - pub fn allowed_coins(&mut self, allowed_coins: &[CoinInfo]) -> &mut Self { - self.allowed_coins = Some(allowed_coins.to_vec()); + pub fn set_params(&mut self, coins: &[CoinInfo]) -> &mut Self { + self.coin_params = Some(coins.to_vec()); + self + } + + pub fn params_contract(&mut self, params: &str) -> &mut Self { + self.params = Some(Params::new(Addr::unchecked(params))); + self + } + + pub fn health_contract(&mut self, health: &str) -> &mut Self { + self.health_contract = Some(HealthContract::new(Addr::unchecked(health))); self } @@ -1082,6 +1121,11 @@ impl MockEnvBuilder { self } + pub fn params(&mut self, addr: &str) -> &mut Self { + self.params = Some(Params::new(Addr::unchecked(addr))); + self + } + pub fn no_nft_contract(&mut self) -> &mut Self { self.deploy_nft_contract = false; self @@ -1092,23 +1136,6 @@ impl MockEnvBuilder { self } - pub fn pre_deployed_vault( - &mut self, - info: &VaultTestInfo, - config: Option, - ) -> &mut Self { - let config = config.unwrap_or_else(|| self.deploy_vault(info)); - let new_list = match self.pre_deployed_vaults.clone() { - None => Some(vec![config]), - Some(mut curr) => { - curr.push(config); - Some(curr) - } - }; - self.pre_deployed_vaults = new_list; - self - } - pub fn max_close_factor(&mut self, cf: Decimal) -> &mut Self { self.max_close_factor = Some(cf); self diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 1896e4f46..353d2732c 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -1,6 +1,9 @@ +use std::str::FromStr; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; use cw_utils::Duration; +use mars_params::types::{AssetParams, HighLeverageStrategyParams, RedBankSettings, RoverSettings}; use mars_rover::msg::execute::{ActionAmount, ActionCoin}; #[cw_serde] @@ -16,6 +19,7 @@ pub struct CoinInfo { pub max_ltv: Decimal, pub liquidation_threshold: Decimal, pub liquidation_bonus: Decimal, + pub whitelisted: bool, } #[cw_serde] @@ -57,3 +61,26 @@ impl CoinInfo { } } } + +impl From for AssetParams { + fn from(c: CoinInfo) -> Self { + Self { + denom: c.denom, + rover: RoverSettings { + whitelisted: c.whitelisted, + hls: HighLeverageStrategyParams { + max_loan_to_value: Decimal::from_str("0.86").unwrap(), + liquidation_threshold: Decimal::from_str("0.89").unwrap(), + }, + }, + red_bank: RedBankSettings { + deposit_enabled: true, + borrow_enabled: true, + deposit_cap: Uint128::MAX, + }, + max_loan_to_value: c.max_ltv, + liquidation_threshold: c.liquidation_threshold, + liquidation_bonus: c.liquidation_bonus, + } + } +} diff --git a/contracts/credit-manager/tests/test_borrow.rs b/contracts/credit-manager/tests/test_borrow.rs index 799f41f25..3abdd91e3 100644 --- a/contracts/credit-manager/tests/test_borrow.rs +++ b/contracts/credit-manager/tests/test_borrow.rs @@ -8,7 +8,7 @@ use mars_rover::{ }; use crate::helpers::{ - assert_err, uosmo_info, AccountToFund, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, + assert_err, blacklisted_coin, uosmo_info, AccountToFund, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, }; pub mod helpers; @@ -18,7 +18,7 @@ fn only_token_owner_can_borrow() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); + let mut mock = MockEnv::new().set_params(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let another_user = Addr::unchecked("another_user"); @@ -40,16 +40,20 @@ fn only_token_owner_can_borrow() { #[test] fn can_only_borrow_what_is_whitelisted() { - let coin_info = uosmo_info(); + let blacklisted_coin = blacklisted_coin(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().allowed_coins(&[coin_info]).build().unwrap(); + let mut mock = MockEnv::new().set_params(&[blacklisted_coin.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); - let res = - mock.update_credit_account(&account_id, &user, vec![Borrow(coin(234, "usomething"))], &[]); + let res = mock.update_credit_account( + &account_id, + &user, + vec![Borrow(coin(234, blacklisted_coin.denom.clone()))], + &[], + ); - assert_err(res, ContractError::NotWhitelisted(String::from("usomething"))) + assert_err(res, ContractError::NotWhitelisted(blacklisted_coin.denom)) } #[test] @@ -57,7 +61,7 @@ fn borrowing_zero_does_nothing() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); + let mut mock = MockEnv::new().set_params(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = @@ -75,7 +79,7 @@ fn cannot_borrow_above_max_ltv() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -109,7 +113,7 @@ fn success_when_new_debt_asset() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -165,7 +169,7 @@ fn debt_shares_with_debt_amount() { let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user_a.clone(), funds: coins(300, coin_info.denom.clone()), diff --git a/contracts/credit-manager/tests/test_coin_balances.rs b/contracts/credit-manager/tests/test_coin_balances.rs index e72b9a83f..210f42fbb 100644 --- a/contracts/credit-manager/tests/test_coin_balances.rs +++ b/contracts/credit-manager/tests/test_coin_balances.rs @@ -31,7 +31,7 @@ fn user_does_not_have_enough_to_pay_diff() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[osmo_info.clone()]) + .set_params(&[osmo_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, osmo_info.denom.clone()), @@ -72,7 +72,7 @@ fn user_gets_rebalanced_down() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[osmo_info.clone()]) + .set_params(&[osmo_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, osmo_info.denom.clone()), @@ -110,7 +110,7 @@ fn user_gets_rebalanced_up() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[osmo_info.clone()]) + .set_params(&[osmo_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, osmo_info.denom.clone()), diff --git a/contracts/credit-manager/tests/test_deposit.rs b/contracts/credit-manager/tests/test_deposit.rs index 4562955e1..0c0c2097b 100644 --- a/contracts/credit-manager/tests/test_deposit.rs +++ b/contracts/credit-manager/tests/test_deposit.rs @@ -6,7 +6,8 @@ use mars_rover::{ }; use crate::helpers::{ - assert_err, uatom_info, ujake_info, uosmo_info, AccountToFund, CoinInfo, MockEnv, + assert_err, blacklisted_coin, uatom_info, ujake_info, uosmo_info, AccountToFund, CoinInfo, + MockEnv, }; pub mod helpers; @@ -38,7 +39,7 @@ fn only_owner_of_token_can_deposit() { fn deposit_nothing() { let coin_info = uosmo_info(); - let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); + let mut mock = MockEnv::new().set_params(&[coin_info.clone()]).build().unwrap(); let user = Addr::unchecked("user"); let account_id = mock.create_credit_account(&user).unwrap(); @@ -61,7 +62,7 @@ fn deposit_nothing() { fn deposit_but_no_funds() { let coin_info = uosmo_info(); - let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); + let mut mock = MockEnv::new().set_params(&[coin_info.clone()]).build().unwrap(); let user = Addr::unchecked("user"); let account_id = mock.create_credit_account(&user).unwrap(); @@ -91,7 +92,7 @@ fn deposit_but_not_enough_funds() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -118,28 +119,38 @@ fn deposit_but_not_enough_funds() { #[test] fn can_only_deposit_allowed_assets() { - let coin_info = uosmo_info(); + let blacklisted_coin = blacklisted_coin(); + let not_listed_coin = ujake_info().to_coin(234); + let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[blacklisted_coin.clone()]) .fund_account(AccountToFund { addr: user.clone(), - funds: coins(300, coin_info.denom.clone()), + funds: vec![ + coin(300, blacklisted_coin.denom.clone()), + coin(300, not_listed_coin.denom.clone()), + ], }) .build() .unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); - let not_allowed_coin = ujake_info().to_coin(234); - let res = mock.update_credit_account( &account_id, &user, - vec![Action::Deposit(not_allowed_coin.clone())], - &[coin(250, coin_info.denom)], + vec![Action::Deposit(not_listed_coin.clone())], + &[coin(250, not_listed_coin.denom.clone())], ); + assert_err(res, NotWhitelisted(not_listed_coin.denom)); - assert_err(res, NotWhitelisted(not_allowed_coin.denom)); + let res = mock.update_credit_account( + &account_id, + &user, + vec![Action::Deposit(blacklisted_coin.to_coin(250))], + &[coin(250, blacklisted_coin.denom.clone())], + ); + assert_err(res, NotWhitelisted(blacklisted_coin.denom)); let res = mock.query_positions(&account_id); assert_eq!(res.deposits.len(), 0); @@ -152,7 +163,7 @@ fn extra_funds_received() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![coin(300, uosmo_info.denom.clone()), coin(250, uatom_info.denom.clone())], @@ -181,7 +192,7 @@ fn deposit_success() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -216,7 +227,7 @@ fn multiple_deposit_actions() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![coin(300, uosmo_info.denom.clone()), coin(50, uatom_info.denom.clone())], diff --git a/contracts/credit-manager/tests/test_emergency_powers.rs b/contracts/credit-manager/tests/test_emergency_powers.rs deleted file mode 100644 index 0e36c7191..000000000 --- a/contracts/credit-manager/tests/test_emergency_powers.rs +++ /dev/null @@ -1,174 +0,0 @@ -use cosmwasm_std::{Addr, StdError::NotFound}; -use mars_owner::OwnerError::NotEmergencyOwner; -use mars_rover::{ - adapters::vault::VaultUnchecked, - error::ContractError::{InvalidConfig, Owner, Std}, - msg::execute::EmergencyUpdate, -}; - -use crate::helpers::{ - assert_err, locked_vault_info, uatom_info, unlocked_vault_info, uosmo_info, MockEnv, -}; - -pub mod helpers; - -#[test] -fn only_emergency_owner_can_invoke_emergency_powers() { - let emergency_owner = Addr::unchecked("miles_morales"); - let mut mock = MockEnv::new().emergency_owner(&emergency_owner).build().unwrap(); - let bad_guy = Addr::unchecked("bad_guy"); - let res = mock.emergency_update(&bad_guy, EmergencyUpdate::DisallowCoin("uosmo".to_string())); - assert_err(res, Owner(NotEmergencyOwner {})) -} - -#[test] -fn not_callable_if_no_emergency_role_set() { - let mut mock = MockEnv::new().build().unwrap(); - let bad_guy = Addr::unchecked("bad_guy"); - let res = mock.emergency_update(&bad_guy, EmergencyUpdate::DisallowCoin("uosmo".to_string())); - assert_err(res, Owner(NotEmergencyOwner {})) -} - -#[test] -fn emergency_owner_can_blacklist_coins() { - let emergency_owner = Addr::unchecked("miles_morales"); - let osmo_info = uosmo_info(); - let atom_info = uatom_info(); - - let mut mock = MockEnv::new() - .emergency_owner(&emergency_owner) - .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) - .build() - .unwrap(); - - let allowed_coins_before = mock.query_allowed_coins(None, None); - assert_eq!(allowed_coins_before.len(), 2); - - mock.emergency_update(&emergency_owner, EmergencyUpdate::DisallowCoin(osmo_info.denom)) - .unwrap(); - - let allowed_coins_after = mock.query_allowed_coins(None, None); - assert_eq!(allowed_coins_after.len(), 1); - assert_eq!(allowed_coins_after.first().unwrap(), &atom_info.denom) -} - -#[test] -fn raises_if_coin_does_not_exist() { - let emergency_owner = Addr::unchecked("miles_morales"); - let osmo_info = uosmo_info(); - - let mut mock = MockEnv::new() - .emergency_owner(&emergency_owner) - .allowed_coins(&[osmo_info]) - .build() - .unwrap(); - - let res = - mock.emergency_update(&emergency_owner, EmergencyUpdate::DisallowCoin("xyz".to_string())); - - assert_err( - res, - InvalidConfig { - reason: "xyz not in config".to_string(), - }, - ) -} - -#[test] -fn emergency_owner_can_drop_vault_max_ltv() { - let emergency_owner = Addr::unchecked("miles_morales"); - let vault_a = locked_vault_info(); - let vault_b = unlocked_vault_info(); - - let mut mock = MockEnv::new() - .emergency_owner(&emergency_owner) - .vault_configs(&[vault_a.clone(), vault_b.clone()]) - .build() - .unwrap(); - - let vault_a_addr = mock.get_vault(&vault_a); - let vault_b_addr = mock.get_vault(&vault_b); - - let vault_a_config_before = mock.query_vault_config(&vault_a_addr).unwrap(); - assert!(!vault_a_config_before.config.max_ltv.is_zero()); - let vault_b_config_before = mock.query_vault_config(&vault_b_addr).unwrap(); - assert!(!vault_b_config_before.config.max_ltv.is_zero()); - - mock.emergency_update(&emergency_owner, EmergencyUpdate::SetZeroMaxLtv(vault_a_addr.clone())) - .unwrap(); - - let vault_a_config_after = mock.query_vault_config(&vault_a_addr).unwrap(); - assert!(vault_a_config_after.config.max_ltv.is_zero()); // Dropped to zero ✅ - let vault_b_config_after = mock.query_vault_config(&vault_b_addr).unwrap(); - assert!(!vault_b_config_after.config.max_ltv.is_zero()); -} - -#[test] -fn raises_if_vault_does_not_exist_for_max_ltv_drop() { - let emergency_owner = Addr::unchecked("miles_morales"); - - let mut mock = MockEnv::new().emergency_owner(&emergency_owner).build().unwrap(); - - let res = mock.emergency_update( - &emergency_owner, - EmergencyUpdate::SetZeroMaxLtv(VaultUnchecked::new("vault_addr_123".to_string())), - ); - - assert_err( - res, - Std(NotFound { - kind: "mars_rover::adapters::vault::config::VaultConfig".to_string(), - }), - ) -} - -#[test] -fn emergency_owner_can_drop_deposit_cap() { - let emergency_owner = Addr::unchecked("miles_morales"); - let vault_a = locked_vault_info(); - let vault_b = unlocked_vault_info(); - - let mut mock = MockEnv::new() - .emergency_owner(&emergency_owner) - .vault_configs(&[vault_a.clone(), vault_b.clone()]) - .build() - .unwrap(); - - let vault_a_addr = mock.get_vault(&vault_a); - let vault_b_addr = mock.get_vault(&vault_b); - - let vault_a_config_before = mock.query_vault_config(&vault_a_addr).unwrap(); - assert!(!vault_a_config_before.config.deposit_cap.amount.is_zero()); - let vault_b_config_before = mock.query_vault_config(&vault_b_addr).unwrap(); - assert!(!vault_b_config_before.config.deposit_cap.amount.is_zero()); - - mock.emergency_update( - &emergency_owner, - EmergencyUpdate::SetZeroDepositCap(vault_a_addr.clone()), - ) - .unwrap(); - - let vault_a_config_after = mock.query_vault_config(&vault_a_addr).unwrap(); - assert!(vault_a_config_after.config.deposit_cap.amount.is_zero()); // Dropped to zero ✅ - let vault_b_config_after = mock.query_vault_config(&vault_b_addr).unwrap(); - assert!(!vault_b_config_after.config.deposit_cap.amount.is_zero()); -} - -#[test] -fn raises_if_vault_does_not_exist_for_deposit_cap_drop() { - let emergency_owner = Addr::unchecked("miles_morales"); - - let mut mock = MockEnv::new().emergency_owner(&emergency_owner).build().unwrap(); - - let res = mock.emergency_update( - &emergency_owner, - EmergencyUpdate::SetZeroDepositCap(VaultUnchecked::new("vault_addr_123".to_string())), - ); - - assert_err( - res, - Std(NotFound { - kind: "mars_rover::adapters::vault::config::VaultConfig".to_string(), - }), - ) -} diff --git a/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs b/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs deleted file mode 100644 index 17b8fe81a..000000000 --- a/contracts/credit-manager/tests/test_enumerate_allowed_coins.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::helpers::{build_mock_coin_infos, MockEnv}; - -pub mod helpers; - -#[test] -fn pagination_on_allowed_coins_query_works() { - let allowed_coins = build_mock_coin_infos(32); - let mock = MockEnv::new().allowed_coins(&build_mock_coin_infos(32)).build().unwrap(); - - let coins_res = mock.query_allowed_coins(None, Some(2_u32)); - - // Assert limit request is observed - assert_eq!(coins_res.len(), 2); - - let coins_res_a = mock.query_allowed_coins(None, None); - let coins_res_b = mock.query_allowed_coins(Some(coins_res_a.last().unwrap().clone()), None); - let coins_res_c = mock.query_allowed_coins(Some(coins_res_b.last().unwrap().clone()), None); - let coins_res_d = mock.query_allowed_coins(Some(coins_res_c.last().unwrap().clone()), None); - - // Assert default is observed - assert_eq!(coins_res_a.len(), 10); - assert_eq!(coins_res_b.len(), 10); - assert_eq!(coins_res_c.len(), 10); - - assert_eq!(coins_res_d.len(), 2); - - let combined: Vec = coins_res_a - .iter() - .cloned() - .chain(coins_res_b.iter().cloned()) - .chain(coins_res_c.iter().cloned()) - .chain(coins_res_d.iter().cloned()) - .collect(); - - assert_eq!(combined.len(), allowed_coins.len()); - assert!(allowed_coins.iter().all(|item| combined.contains(&item.denom))); -} diff --git a/contracts/credit-manager/tests/test_enumerate_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs index cfb07c8a5..b49a14e51 100644 --- a/contracts/credit-manager/tests/test_enumerate_coin_balances.rs +++ b/contracts/credit-manager/tests/test_enumerate_coin_balances.rs @@ -65,7 +65,7 @@ fn pagination_on_all_coin_balances_query_works() { addr: user_c.clone(), funds: user_c_coins.clone(), }) - .allowed_coins(&build_mock_coin_infos(14)) + .set_params(&build_mock_coin_infos(14)) .build() .unwrap(); diff --git a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs index e856d8be3..fe3925d5c 100644 --- a/contracts/credit-manager/tests/test_enumerate_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_debt_shares.rs @@ -66,7 +66,7 @@ fn pagination_on_all_debt_shares_query_works() { addr: user_c.clone(), funds: user_c_coins.clone(), }) - .allowed_coins(&build_mock_coin_infos(32)) + .set_params(&build_mock_coin_infos(32)) .build() .unwrap(); diff --git a/contracts/credit-manager/tests/test_enumerate_lent_shares.rs b/contracts/credit-manager/tests/test_enumerate_lent_shares.rs index 5fc519240..6e8ccd91a 100644 --- a/contracts/credit-manager/tests/test_enumerate_lent_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_lent_shares.rs @@ -66,7 +66,7 @@ fn pagination_on_all_lent_shares_query_works() { addr: user_c.clone(), funds: user_c_coins.clone(), }) - .allowed_coins(&build_mock_coin_infos(32)) + .set_params(&build_mock_coin_infos(32)) .build() .unwrap(); diff --git a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs index 7055c8f63..def318920 100644 --- a/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_total_debt_shares.rs @@ -66,7 +66,7 @@ fn pagination_on_all_total_debt_shares_query_works() { addr: user_c.clone(), funds: user_c_coins.clone(), }) - .allowed_coins(&build_mock_coin_infos(32)) + .set_params(&build_mock_coin_infos(32)) .build() .unwrap(); diff --git a/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs b/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs index 6387dda9d..9ed38a7d3 100644 --- a/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs +++ b/contracts/credit-manager/tests/test_enumerate_total_lent_shares.rs @@ -66,7 +66,7 @@ fn pagination_on_all_total_lent_shares_query_works() { addr: user_c.clone(), funds: user_c_coins.clone(), }) - .allowed_coins(&build_mock_coin_infos(32)) + .set_params(&build_mock_coin_infos(32)) .build() .unwrap(); diff --git a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs b/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs deleted file mode 100644 index 5faf0da09..000000000 --- a/contracts/credit-manager/tests/test_enumerate_vault_coin_balances.rs +++ /dev/null @@ -1,101 +0,0 @@ -use cosmwasm_std::Addr; -use mars_rover::msg::execute::Action; - -use crate::helpers::{ - assert_contents_equal, build_mock_vaults, lp_token_info, AccountToFund, MockEnv, -}; - -pub mod helpers; - -#[test] -fn pagination_on_all_vault_coin_balances_query_works() { - let lp_token = lp_token_info(); - - let user_a = Addr::unchecked("user_a"); - let user_b = Addr::unchecked("user_b"); - let user_c = Addr::unchecked("user_c"); - - let all_vaults = build_mock_vaults(22); - let mut mock = MockEnv::new() - .fund_account(AccountToFund { - addr: user_a.clone(), - funds: vec![lp_token.to_coin(1000)], - }) - .fund_account(AccountToFund { - addr: user_b.clone(), - funds: vec![lp_token.to_coin(1000)], - }) - .fund_account(AccountToFund { - addr: user_c.clone(), - funds: vec![lp_token.to_coin(1000)], - }) - .allowed_coins(&[lp_token.clone()]) - .vault_configs(&all_vaults) - .build() - .unwrap(); - - let mut actions = vec![Action::Deposit(lp_token.to_coin(220))]; - - all_vaults.iter().for_each(|v| { - actions.extend([Action::EnterVault { - vault: mock.get_vault(v), - coin: lp_token.to_action_coin(10), - }]); - }); - - let account_id_a = mock.create_credit_account(&user_a).unwrap(); - mock.update_credit_account(&account_id_a, &user_a, actions.clone(), &[lp_token.to_coin(220)]) - .unwrap(); - - let account_id_b = mock.create_credit_account(&user_b).unwrap(); - mock.update_credit_account(&account_id_b, &user_b, actions.clone(), &[lp_token.to_coin(220)]) - .unwrap(); - - let account_id_c = mock.create_credit_account(&user_c).unwrap(); - mock.update_credit_account(&account_id_c, &user_c, actions, &[lp_token.to_coin(220)]).unwrap(); - - let vaults_res = mock.query_all_total_vault_coin_balances(None, Some(58_u32)); - // Assert maximum is observed - assert_eq!(vaults_res.len(), 22); - - let vaults_res = mock.query_all_total_vault_coin_balances(None, Some(2_u32)); - // Assert limit request is observed - assert_eq!(vaults_res.len(), 2); - - let vaults_res_a = mock.query_all_total_vault_coin_balances(None, None); - let vaults_res_b = mock.query_all_total_vault_coin_balances( - Some(vaults_res_a.last().unwrap().clone().vault.into()), - None, - ); - let vaults_res_c = mock.query_all_total_vault_coin_balances( - Some(vaults_res_b.last().unwrap().clone().vault.into()), - None, - ); - let vaults_res_d = mock.query_all_total_vault_coin_balances( - Some(vaults_res_c.last().unwrap().clone().vault.into()), - None, - ); - - // Assert default is observed - assert_eq!(vaults_res_a.len(), 10); - assert_eq!(vaults_res_b.len(), 10); - assert_eq!(vaults_res_c.len(), 2); - assert_eq!(vaults_res_d.len(), 0); - - let combined = vaults_res_a - .iter() - .cloned() - .chain(vaults_res_b.iter().cloned()) - .chain(vaults_res_c.iter().cloned()) - .chain(vaults_res_d.iter().cloned()) - .map(|v| v.vault.query_info(&mock.app.wrap()).unwrap()) - .map(|info| info.vault_token) - .collect::>(); - - assert_eq!(combined.len(), all_vaults.len()); - - assert_contents_equal( - &all_vaults.iter().map(|v| v.vault_token_denom.clone()).collect::>(), - &combined, - ) -} diff --git a/contracts/credit-manager/tests/test_enumerate_vault_configs.rs b/contracts/credit-manager/tests/test_enumerate_vault_configs.rs deleted file mode 100644 index 155f10acf..000000000 --- a/contracts/credit-manager/tests/test_enumerate_vault_configs.rs +++ /dev/null @@ -1,54 +0,0 @@ -use cosmwasm_std::testing::MockApi; - -use crate::helpers::{assert_contents_equal, build_mock_vaults, MockEnv}; - -pub mod helpers; - -#[test] -fn pagination_on_vault_configs_query_works() { - let vault_configs = build_mock_vaults(32); - let mock = MockEnv::new().vault_configs(&vault_configs).build().unwrap(); - - let vaults_res = mock.query_vault_configs(None, Some(58_u32)); - - // Assert maximum is observed - assert_eq!(vaults_res.len(), 30); - - let vaults_res = mock.query_vault_configs(None, Some(2_u32)); - - // Assert limit request is observed - assert_eq!(vaults_res.len(), 2); - - let vaults_res_a = mock.query_vault_configs(None, None); - let vaults_res_b = - mock.query_vault_configs(Some(vaults_res_a.last().unwrap().vault.clone()), None); - let vaults_res_c = - mock.query_vault_configs(Some(vaults_res_b.last().unwrap().vault.clone()), None); - let vaults_res_d = - mock.query_vault_configs(Some(vaults_res_c.last().unwrap().vault.clone()), None); - - // Assert default is observed - assert_eq!(vaults_res_a.len(), 10); - assert_eq!(vaults_res_b.len(), 10); - assert_eq!(vaults_res_c.len(), 10); - - assert_eq!(vaults_res_d.len(), 2); - - let combined = vaults_res_a - .iter() - .cloned() - .chain(vaults_res_b.iter().cloned()) - .chain(vaults_res_c.iter().cloned()) - .chain(vaults_res_d.iter().cloned()) - .map(|v| v.vault.check(&MockApi::default()).unwrap()) - .map(|v| v.query_info(&mock.app.wrap()).unwrap()) - .map(|info| info.vault_token) - .collect::>(); - - assert_eq!(combined.len(), vault_configs.len()); - - assert_contents_equal( - &vault_configs.iter().map(|v| v.vault_token_denom.clone()).collect::>(), - &combined, - ) -} diff --git a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs index 6cc5f6f1f..a8fcce039 100644 --- a/contracts/credit-manager/tests/test_enumerate_vault_positions.rs +++ b/contracts/credit-manager/tests/test_enumerate_vault_positions.rs @@ -30,7 +30,7 @@ fn pagination_on_all_vault_positions_query_works() { addr: user_c.clone(), funds: vec![lp_token.to_coin(1000)], }) - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&all_vaults) .build() .unwrap(); diff --git a/contracts/credit-manager/tests/test_flagged_contract.rs b/contracts/credit-manager/tests/test_flagged_contract.rs index fb4414a42..11a2e56e8 100644 --- a/contracts/credit-manager/tests/test_flagged_contract.rs +++ b/contracts/credit-manager/tests/test_flagged_contract.rs @@ -10,11 +10,6 @@ pub mod helpers; fn addresses_in_config_cannot_execute_msgs() { let mut mock = MockEnv::new().build().unwrap(); let config = mock.query_config(); - let vault_addrs = mock - .query_vault_configs(None, None) - .iter() - .map(|v| v.vault.address.clone()) - .collect::>(); let banned = vec![ config.account_nft.unwrap(), @@ -25,7 +20,6 @@ fn addresses_in_config_cannot_execute_msgs() { config.health_contract, ] .into_iter() - .chain(vault_addrs) .collect::>(); for addr_str in banned { diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index bb1e8cdc5..a2c0fe1e3 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -3,15 +3,14 @@ use std::ops::{Add, Mul}; use cosmwasm_std::{coin, coins, Addr, Coin, Decimal, Uint128}; use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use mars_mock_oracle::msg::CoinPrice; +use mars_params::types::{AssetParamsUpdate::AddOrUpdate, VaultConfigUpdate}; use mars_rover::{ - adapters::vault::VaultConfig, error::ContractError, msg::{ execute::{ Action::{Borrow, Deposit, EnterVault, Repay, Withdraw}, ActionAmount, ActionCoin, }, - instantiate::{ConfigUpdates, VaultInstantiateConfig}, query::DebtAmount, }, }; @@ -37,7 +36,7 @@ fn only_assets_with_no_debts() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -87,11 +86,12 @@ fn terra_ragnarok() { max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + whitelisted: true, }; let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -171,7 +171,7 @@ fn debts_no_assets() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -223,7 +223,7 @@ fn cannot_borrow_more_than_healthy() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -300,6 +300,7 @@ fn cannot_borrow_more_but_not_liquidatable() { max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), + whitelisted: true, }; let uatom_info = CoinInfo { denom: "uatom".to_string(), @@ -307,11 +308,12 @@ fn cannot_borrow_more_but_not_liquidatable() { max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(75u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), + whitelisted: true, }; let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -376,6 +378,7 @@ fn assets_and_ltv_lqdt_adjusted_value() { max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), + whitelisted: true, }; let uatom_info = CoinInfo { denom: "uatom".to_string(), @@ -383,11 +386,12 @@ fn assets_and_ltv_lqdt_adjusted_value() { max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), + whitelisted: true, }; let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -482,6 +486,7 @@ fn debt_value() { max_ltv: Decimal::from_atomics(3u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), + whitelisted: true, }; let uatom_info = CoinInfo { denom: "uatom".to_string(), @@ -489,12 +494,13 @@ fn debt_value() { max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), + whitelisted: true, }; let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user_a.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -616,12 +622,12 @@ fn debt_value() { #[test] fn delisted_deposits_drop_max_ltv() { - let uosmo_info = uosmo_info(); + let mut uosmo_info = uosmo_info(); let uatom_info = uatom_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -640,16 +646,11 @@ fn delisted_deposits_drop_max_ltv() { let prev_health = mock.query_health(&account_id); - // Remove uosmo from the coin whitelist - let res = mock.query_config(); - mock.update_config( - &Addr::unchecked(res.ownership.owner.unwrap()), - ConfigUpdates { - allowed_coins: Some(vec![uatom_info.denom]), - ..Default::default() - }, - ) - .unwrap(); + // Blacklist osmo in params contract + uosmo_info.whitelisted = false; + mock.update_asset_params(AddOrUpdate { + params: uosmo_info.into(), + }); let curr_health = mock.query_health(&account_id); @@ -679,7 +680,7 @@ fn delisted_vaults_drop_max_ltv() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone()]) + .set_params(&[lp_token.clone(), atom.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -698,7 +699,7 @@ fn delisted_vaults_drop_max_ltv() { Deposit(lp_token.to_coin(200)), Borrow(atom.to_coin(100)), EnterVault { - vault, + vault: vault.clone(), coin: lp_token.to_action_coin(200), }, ], @@ -708,28 +709,12 @@ fn delisted_vaults_drop_max_ltv() { let prev_health = mock.query_health(&account_id); - let vault_configs = mock.query_vault_configs(None, None); - let v = vault_configs.first().unwrap(); - let new_vault_config = VaultInstantiateConfig { - vault: v.vault.clone(), - config: VaultConfig { - deposit_cap: v.config.deposit_cap.clone(), - max_ltv: v.config.max_ltv, - liquidation_threshold: v.config.liquidation_threshold, - whitelisted: false, - }, - }; - // Blacklist vault - let res = mock.query_config(); - mock.update_config( - &Addr::unchecked(res.ownership.owner.unwrap()), - ConfigUpdates { - vault_configs: Some(vec![new_vault_config]), - ..Default::default() - }, - ) - .unwrap(); + let mut config = mock.query_vault_params(&vault.address); + config.whitelisted = false; + mock.update_vault_params(VaultConfigUpdate::AddOrUpdate { + config: config.into(), + }); let curr_health = mock.query_health(&account_id); @@ -753,13 +738,13 @@ fn delisted_vaults_drop_max_ltv() { #[test] fn vault_base_token_delisting_drops_max_ltv() { - let lp_token = lp_token_info(); + let mut lp_token = lp_token_info(); let leverage_vault = unlocked_vault_info(); let atom = uatom_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone()]) + .set_params(&[lp_token.clone(), atom.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -788,16 +773,11 @@ fn vault_base_token_delisting_drops_max_ltv() { let prev_health = mock.query_health(&account_id); - // Remove LP token from the coin whitelist - let res = mock.query_config(); - mock.update_config( - &Addr::unchecked(res.ownership.owner.unwrap()), - ConfigUpdates { - allowed_coins: Some(vec![atom.denom]), - ..Default::default() - }, - ) - .unwrap(); + // Blacklist LP token in params contract + lp_token.whitelisted = false; + mock.update_asset_params(AddOrUpdate { + params: lp_token.into(), + }); let curr_health = mock.query_health(&account_id); @@ -827,6 +807,7 @@ fn can_take_actions_if_ltv_does_not_weaken() { max_ltv: Decimal::from_atomics(5u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), + whitelisted: true, }; let uatom_info = CoinInfo { denom: "uatom".to_string(), @@ -834,11 +815,12 @@ fn can_take_actions_if_ltv_does_not_weaken() { max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(75u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), + whitelisted: true, }; let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![coin(400, uosmo_info.denom.clone()), coin(50, uatom_info.denom.clone())], diff --git a/contracts/credit-manager/tests/test_instantiate.rs b/contracts/credit-manager/tests/test_instantiate.rs index 45b415c95..2db295496 100644 --- a/contracts/credit-manager/tests/test_instantiate.rs +++ b/contracts/credit-manager/tests/test_instantiate.rs @@ -1,13 +1,4 @@ -use cosmwasm_std::{coin, Decimal}; -use mars_rover::{ - adapters::vault::{VaultBase, VaultConfig}, - msg::instantiate::VaultInstantiateConfig, -}; - -use crate::helpers::{ - assert_contents_equal, locked_vault_info, uatom_info, ujake_info, unlocked_vault_info, - uosmo_info, CoinInfo, MockEnv, VaultTestInfo, -}; +use crate::helpers::MockEnv; pub mod helpers; @@ -22,7 +13,7 @@ fn owner_set_on_instantiate() { #[test] fn raises_on_invalid_owner_addr() { let owner = "%%%INVALID%%%"; - let res = MockEnv::new().owner(owner).build(); + let res = MockEnv::new().owner(owner).params_contract("xyz").health_contract("abc").build(); if res.is_ok() { panic!("Should have thrown an error"); } @@ -35,185 +26,6 @@ fn nft_contract_addr_not_set_on_instantiate() { assert_eq!(res.account_nft, None); } -#[test] -fn vault_configs_set_on_instantiate() { - let vault_configs = vec![ - VaultTestInfo { - vault_token_denom: "vault_contract_1".to_string(), - lockup: None, - base_token_denom: "lp_denom_123".to_string(), - deposit_cap: coin(1_000_000, "uusdc"), - max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), - whitelisted: true, - }, - VaultTestInfo { - vault_token_denom: "vault_contract_2".to_string(), - lockup: None, - base_token_denom: "lp_denom_123".to_string(), - deposit_cap: coin(1_000_000, "uusdc"), - max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), - whitelisted: true, - }, - VaultTestInfo { - vault_token_denom: "vault_contract_3".to_string(), - lockup: None, - base_token_denom: "lp_denom_123".to_string(), - deposit_cap: coin(1_000_000, "uusdc"), - max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), - whitelisted: true, - }, - ]; - - let mock = MockEnv::new().vault_configs(&vault_configs).build().unwrap(); - let res = mock.query_vault_configs(None, None); - assert_contents_equal( - &res.iter().map(|v| v.vault.clone()).collect::>(), - &vault_configs.iter().map(|info| mock.get_vault(info)).collect::>(), - ); -} - -#[test] -fn raises_on_invalid_vaults_addr() { - let mock = MockEnv::new() - .pre_deployed_vault( - &unlocked_vault_info(), - Some(VaultInstantiateConfig { - vault: VaultBase { - address: "%%%INVALID%%%".to_string(), - }, - config: VaultConfig { - deposit_cap: Default::default(), - max_ltv: Default::default(), - liquidation_threshold: Default::default(), - whitelisted: false, - }, - }), - ) - .build(); - - if mock.is_ok() { - panic!("Should have thrown an error"); - } -} - -#[test] -fn instantiate_raises_on_invalid_vaults_config() { - let mock = MockEnv::new() - .pre_deployed_vault( - &VaultTestInfo { - vault_token_denom: "uleverage".to_string(), - lockup: None, - deposit_cap: coin(10_000_000, "uusdc"), - max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), - base_token_denom: "lp_denom_123".to_string(), - whitelisted: true, - }, - None, - ) - .build(); - - if mock.is_ok() { - panic!("Should have thrown an error: max_ltv > liquidation_threshold"); - } - - let mock = MockEnv::new() - .pre_deployed_vault( - &VaultTestInfo { - vault_token_denom: "uleverage".to_string(), - lockup: None, - deposit_cap: coin(10_000_000, "uusdc"), - max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(9u128, 0).unwrap(), - base_token_denom: "lp_denom_123".to_string(), - whitelisted: true, - }, - None, - ) - .build(); - - if mock.is_ok() { - panic!("Should have thrown an error: liquidation_threshold > 1"); - } - - let mock = MockEnv::new() - .pre_deployed_vault( - &VaultTestInfo { - vault_token_denom: "uleverage".to_string(), - lockup: None, - deposit_cap: coin(10_000_000, "uusdc"), - max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(9u128, 0).unwrap(), - base_token_denom: "lp_denom_123".to_string(), - whitelisted: true, - }, - None, - ) - .pre_deployed_vault( - &VaultTestInfo { - vault_token_denom: "uleverage".to_string(), - lockup: None, - deposit_cap: coin(10_000_000, "uusdc"), - max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), - base_token_denom: "xyz".to_string(), - whitelisted: true, - }, - None, - ) - .build(); - - if mock.is_ok() { - panic!("Should have thrown an error: duplicate vault token denoms"); - } -} - -#[test] -fn duplicate_vaults_raises() { - let mock = MockEnv::new() - .pre_deployed_vault(&locked_vault_info(), None) - .pre_deployed_vault(&locked_vault_info(), None) - .build(); - if mock.is_ok() { - panic!("Should have thrown an error"); - } -} - -#[test] -fn allowed_coins_set_on_instantiate() { - let allowed_coins = vec![ - uosmo_info(), - uatom_info(), - ujake_info(), - CoinInfo { - denom: "umars".to_string(), - price: Decimal::from_atomics(25u128, 2).unwrap(), - max_ltv: Decimal::from_atomics(7u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), - liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), - }, - ]; - let mock = MockEnv::new().allowed_coins(&allowed_coins).build().unwrap(); - - let res = mock.query_allowed_coins(None, None); - assert_contents_equal( - &res, - &allowed_coins.iter().map(|info| info.denom.clone()).collect::>(), - ) -} - -#[test] -fn duplicate_coins_raises() { - let allowed_coins = vec![uosmo_info(), uosmo_info(), uatom_info()]; - let mock = MockEnv::new().allowed_coins(&allowed_coins).build(); - if mock.is_ok() { - panic!("Should have thrown an error"); - } -} - #[test] fn red_bank_set_on_instantiate() { let red_bank_addr = "mars_red_bank_contract_123".to_string(); @@ -247,18 +59,17 @@ fn raises_on_invalid_oracle_addr() { } #[test] -fn max_close_factor_set_on_instantiate() { - let mock = MockEnv::new().build().unwrap(); +fn params_set_on_instantiate() { + let params_contract = "params_contract_456".to_string(); + let mock = MockEnv::new().params(¶ms_contract).build().unwrap(); let res = mock.query_config(); - let mock_default = Decimal::from_atomics(5u128, 1).unwrap(); - assert_eq!(mock_default, res.max_close_factor); + assert_eq!(params_contract, res.params); } #[test] -fn max_close_factor_validated() { - let mock = MockEnv::new().max_close_factor(Decimal::from_atomics(1244u128, 3).unwrap()).build(); - +fn raises_on_invalid_params_addr() { + let mock = MockEnv::new().params("%%%INVALID%%%").build(); if mock.is_ok() { - panic!("Should have thrown an error: Max close factor should be below 1"); + panic!("Should have thrown an error"); } } diff --git a/contracts/credit-manager/tests/test_lend.rs b/contracts/credit-manager/tests/test_lend.rs index b274f61dd..32a6a0299 100644 --- a/contracts/credit-manager/tests/test_lend.rs +++ b/contracts/credit-manager/tests/test_lend.rs @@ -41,7 +41,7 @@ fn only_token_owner_can_lend() { fn can_only_lend_what_is_whitelisted() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().allowed_coins(&[coin_info]).build().unwrap(); + let mut mock = MockEnv::new().set_params(&[coin_info]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = @@ -54,7 +54,7 @@ fn can_only_lend_what_is_whitelisted() { fn lending_zero_raises() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); + let mut mock = MockEnv::new().set_params(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account(&account_id, &user, vec![Lend(coin_info.to_coin(0))], &[]); @@ -68,7 +68,7 @@ fn raises_when_not_enough_assets_to_lend() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -103,7 +103,7 @@ fn successful_lend() { let user_b = Addr::unchecked("user_b"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user_a.clone(), funds: coins(300, coin_info.denom.clone()), diff --git a/contracts/credit-manager/tests/test_liquidate_deposit.rs b/contracts/credit-manager/tests/test_liquidate_deposit.rs index 406dd53b6..1eb165cab 100644 --- a/contracts/credit-manager/tests/test_liquidate_deposit.rs +++ b/contracts/credit-manager/tests/test_liquidate_deposit.rs @@ -28,7 +28,7 @@ fn can_only_liquidate_unhealthy_accounts() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -79,7 +79,7 @@ fn vault_positions_contribute_to_health() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom_info.clone()]) + .set_params(&[lp_token.clone(), atom_info.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), @@ -140,7 +140,7 @@ fn liquidatee_does_not_have_requested_asset() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -194,7 +194,7 @@ fn liquidatee_does_not_have_debt_coin() { let liquidatee = Addr::unchecked("liquidatee"); let random_user = Addr::unchecked("random_user"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -260,7 +260,7 @@ fn liquidator_does_not_have_enough_to_pay_debt() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -316,7 +316,7 @@ fn liquidator_left_in_unhealthy_state() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -375,7 +375,7 @@ fn liquidation_not_profitable_after_calculations() { let liquidator = Addr::unchecked("liquidator"); let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -443,7 +443,7 @@ fn debt_amount_adjusted_to_close_factor_max() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() .max_close_factor(Decimal::from_atomics(1u128, 1).unwrap()) - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -517,7 +517,7 @@ fn debt_amount_adjusted_to_total_debt_for_denom() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() .max_close_factor(Decimal::from_atomics(1u128, 1).unwrap()) - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone(), ujake_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -595,7 +595,7 @@ fn debt_amount_adjusted_to_max_allowed_by_request_coin() { let liquidator = Addr::unchecked("liquidator"); let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -668,7 +668,7 @@ fn debt_amount_no_adjustment() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() .max_close_factor(Decimal::from_atomics(1u128, 1).unwrap()) - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: coins(300, uosmo_info.denom.clone()), diff --git a/contracts/credit-manager/tests/test_liquidate_lend.rs b/contracts/credit-manager/tests/test_liquidate_lend.rs index ac2e22bfd..2316ffb63 100644 --- a/contracts/credit-manager/tests/test_liquidate_lend.rs +++ b/contracts/credit-manager/tests/test_liquidate_lend.rs @@ -22,7 +22,7 @@ fn lent_positions_contribute_to_health() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom_info.clone(), uosmo_info.clone()]) + .set_params(&[uatom_info.clone(), uosmo_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: vec![uatom_info.to_coin(500), uosmo_info.to_coin(500)], @@ -95,7 +95,7 @@ fn liquidatee_does_not_have_requested_lent_coin() { let liquidator = Addr::unchecked("liquidator"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom_info.clone(), uosmo_info.clone(), ujake_info.clone()]) + .set_params(&[uatom_info.clone(), uosmo_info.clone(), ujake_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: vec![uatom_info.to_coin(500)], @@ -158,7 +158,7 @@ fn lent_position_partially_liquidated() { let mut mock = MockEnv::new() .max_close_factor(Decimal::from_atomics(6u128, 1).unwrap()) - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -247,7 +247,7 @@ fn lent_position_fully_liquidated() { let mut mock = MockEnv::new() .max_close_factor(Decimal::from_atomics(6u128, 1).unwrap()) - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: coins(300, uosmo_info.denom.clone()), @@ -342,7 +342,7 @@ fn liquidate_with_reclaiming() { let mut mock = MockEnv::new() .max_close_factor(Decimal::from_atomics(6u128, 1).unwrap()) - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), funds: coins(300, uosmo_info.denom.clone()), diff --git a/contracts/credit-manager/tests/test_liquidate_vault.rs b/contracts/credit-manager/tests/test_liquidate_vault.rs index b1df78d46..37e41b02b 100644 --- a/contracts/credit-manager/tests/test_liquidate_vault.rs +++ b/contracts/credit-manager/tests/test_liquidate_vault.rs @@ -29,7 +29,7 @@ fn liquidatee_must_have_the_request_vault_position() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .set_params(&[uatom.clone(), uosmo.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), @@ -80,7 +80,7 @@ fn liquidatee_is_not_liquidatable() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), @@ -140,7 +140,7 @@ fn liquidator_does_not_have_debt_coin_in_credit_account() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), ujake.clone()]) + .set_params(&[lp_token.clone(), ujake.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), @@ -206,7 +206,7 @@ fn wrong_position_type_sent_for_unlocked_vault() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), @@ -275,7 +275,7 @@ fn wrong_position_type_sent_for_locked_vault() { let liquidatee = Addr::unchecked("liquidatee"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), @@ -331,7 +331,7 @@ fn liquidate_unlocked_vault() { let liquidator = Addr::unchecked("liquidator"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), ujake.clone()]) + .set_params(&[lp_token.clone(), ujake.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), @@ -419,7 +419,7 @@ fn liquidate_locked_vault() { let liquidator = Addr::unchecked("liquidator"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone()]) + .set_params(&[lp_token.clone(), atom.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), @@ -510,7 +510,7 @@ fn liquidate_unlocking_liquidation_order() { let liquidator = Addr::unchecked("liquidator"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), ujake.clone()]) + .set_params(&[lp_token.clone(), ujake.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), @@ -625,7 +625,7 @@ fn liquidation_calculation_adjustment() { let liquidator = Addr::unchecked("liquidator"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), ujake.clone()]) + .set_params(&[lp_token.clone(), ujake.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: liquidatee.clone(), diff --git a/contracts/credit-manager/tests/test_reclaim.rs b/contracts/credit-manager/tests/test_reclaim.rs index 33207f4fc..1b11892b4 100644 --- a/contracts/credit-manager/tests/test_reclaim.rs +++ b/contracts/credit-manager/tests/test_reclaim.rs @@ -36,7 +36,7 @@ fn only_token_owner_can_reclaim() { fn reclaiming_with_zero_lent() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); + let mut mock = MockEnv::new().set_params(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); // When passing some amount @@ -65,7 +65,7 @@ fn when_trying_to_reclaim_more_than_lent() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -120,7 +120,7 @@ fn reclaiming_less_than_entire_lent_share() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -175,7 +175,7 @@ fn reclaiming_the_entire_lent_share() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -231,7 +231,7 @@ fn reclaiming_multiple_assets() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, uosmo_info.denom.clone()), diff --git a/contracts/credit-manager/tests/test_refund_balances.rs b/contracts/credit-manager/tests/test_refund_balances.rs index ec28e2ff8..fb3dc2216 100644 --- a/contracts/credit-manager/tests/test_refund_balances.rs +++ b/contracts/credit-manager/tests/test_refund_balances.rs @@ -14,7 +14,7 @@ fn refund_coin_balances_when_balances() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![coin(234, uosmo_info.denom.clone()), coin(25, uatom_info.denom.clone())], @@ -52,7 +52,7 @@ fn refund_coin_balances_when_no_balances() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index 2cb28fae1..9db6c434c 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -44,7 +44,7 @@ fn only_token_owner_can_repay() { fn repaying_with_zero_debt_raises() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); + let mut mock = MockEnv::new().set_params(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); // When passing some amount @@ -84,13 +84,14 @@ fn raises_when_repaying_what_is_not_owed() { max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), + whitelisted: true, }; let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user_a.clone(), funds: coins(300, uatom_info.denom.clone()), @@ -141,12 +142,13 @@ fn raises_when_not_enough_assets_to_repay() { max_ltv: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), + whitelisted: true, }; let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, uatom_info.denom.clone()), @@ -188,7 +190,7 @@ fn repay_less_than_total_debt() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -286,7 +288,7 @@ fn pays_max_debt_when_attempting_to_repay_more_than_owed() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -337,7 +339,7 @@ fn amount_none_repays_total_debt() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), diff --git a/contracts/credit-manager/tests/test_repay_for_recipient.rs b/contracts/credit-manager/tests/test_repay_for_recipient.rs index 21e6f696e..d11d65b74 100644 --- a/contracts/credit-manager/tests/test_repay_for_recipient.rs +++ b/contracts/credit-manager/tests/test_repay_for_recipient.rs @@ -42,7 +42,7 @@ fn raises_when_benefactor_has_no_funds() { let benefactor = Addr::unchecked("benefactor"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: recipient.clone(), funds: coins(300, coin_info.denom.clone()), @@ -91,7 +91,7 @@ fn raises_when_non_owner_of_benefactor_account_repays() { let benefactor = Addr::unchecked("benefactor"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: benefactor.clone(), funds: coins(300, coin_info.denom.clone()), @@ -151,7 +151,7 @@ fn raises_when_benefactor_repays_account_with_no_debt() { let benefactor = Addr::unchecked("benefactor"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: benefactor.clone(), funds: coins(300, coin_info.denom.clone()), @@ -205,7 +205,7 @@ fn benefactor_successfully_repays_on_behalf_of_recipient() { let benefactor = Addr::unchecked("benefactor"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: benefactor.clone(), funds: coins(300, coin_info.denom.clone()), @@ -272,7 +272,7 @@ fn benefactor_pays_some_of_recipient_debt() { let benefactor = Addr::unchecked("benefactor"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: benefactor.clone(), funds: coins(300, coin_info.denom.clone()), @@ -338,7 +338,7 @@ fn benefactor_attempts_to_pay_more_than_max_debt() { let benefactor = Addr::unchecked("benefactor"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: benefactor.clone(), funds: coins(300, coin_info.denom.clone()), diff --git a/contracts/credit-manager/tests/test_repay_from_wallet.rs b/contracts/credit-manager/tests/test_repay_from_wallet.rs index a08b84f51..af7daadb1 100644 --- a/contracts/credit-manager/tests/test_repay_from_wallet.rs +++ b/contracts/credit-manager/tests/test_repay_from_wallet.rs @@ -1,11 +1,9 @@ use cosmwasm_std::{coin, coins, Addr, Uint128}; use cw_utils::PaymentError; +use mars_params::types::AssetParamsUpdate::AddOrUpdate; use mars_rover::{ error::ContractError, - msg::{ - execute::Action::{Borrow, Deposit}, - instantiate::ConfigUpdates, - }, + msg::execute::Action::{Borrow, Deposit}, }; use crate::helpers::{assert_err, uosmo_info, AccountToFund, MockEnv}; @@ -60,7 +58,7 @@ fn repay_of_less_than_total_debt() { let repayer_starting_amount = 300; let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: debtor.clone(), funds: coins(300, coin_info.denom.clone()), @@ -109,7 +107,7 @@ fn repay_of_more_than_total_debt() { let repayer_starting_amount = 300; let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: debtor.clone(), funds: coins(300, coin_info.denom.clone()), @@ -150,12 +148,12 @@ fn repay_of_more_than_total_debt() { #[test] fn delisted_assets_can_be_repaid() { - let coin_info = uosmo_info(); + let mut coin_info = uosmo_info(); let debtor = Addr::unchecked("debtor"); let repayer = Addr::unchecked("debtor"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: debtor.clone(), funds: coins(300, coin_info.denom.clone()), @@ -180,26 +178,13 @@ fn delisted_assets_can_be_repaid() { .unwrap(); // Delist the asset - let config = mock.query_config(); - mock.update_config( - &Addr::unchecked(config.ownership.owner.unwrap()), - ConfigUpdates { - account_nft: None, - allowed_coins: Some(vec![]), - vault_configs: None, - oracle: None, - red_bank: None, - max_close_factor: None, - max_unlocking_positions: None, - swapper: None, - zapper: None, - health_contract: None, - }, - ) - .unwrap(); + coin_info.whitelisted = false; + mock.update_asset_params(AddOrUpdate { + params: coin_info.clone().into(), + }); - let allowed_coins = mock.query_allowed_coins(None, None); - assert_eq!(0, allowed_coins.len()); + let params = mock.query_asset_params(&coin_info.denom); + assert!(!params.rover.whitelisted); // There should be no error in repaying for this asset mock.repay_from_wallet(&repayer, &account_id, &[coin(12, coin_info.denom)]).unwrap(); diff --git a/contracts/credit-manager/tests/test_swap.rs b/contracts/credit-manager/tests/test_swap.rs index b4d5858ee..3822a02a6 100644 --- a/contracts/credit-manager/tests/test_swap.rs +++ b/contracts/credit-manager/tests/test_swap.rs @@ -8,7 +8,9 @@ use mars_rover::{ }; use mars_swapper_mock::contract::MOCK_SWAP_RESULT; -use crate::helpers::{assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv}; +use crate::helpers::{ + assert_err, blacklisted_coin, uatom_info, uosmo_info, AccountToFund, MockEnv, +}; pub mod helpers; @@ -44,17 +46,17 @@ fn only_token_owner_can_swap_for_account() { #[test] fn denom_out_must_be_whitelisted() { - let osmo_info = uosmo_info(); + let blacklisted_coin = blacklisted_coin(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().allowed_coins(&[osmo_info.clone()]).build().unwrap(); + let mut mock = MockEnv::new().set_params(&[blacklisted_coin.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( &account_id, &user, vec![SwapExactIn { - coin_in: osmo_info.to_action_coin(10_000), + coin_in: blacklisted_coin.to_action_coin(10_000), denom_out: "ujake".to_string(), slippage: Decimal::from_atomics(6u128, 1).unwrap(), }], @@ -71,7 +73,7 @@ fn no_amount_sent() { let user = Addr::unchecked("user"); let mut mock = - MockEnv::new().allowed_coins(&[osmo_info.clone(), atom_info.clone()]).build().unwrap(); + MockEnv::new().set_params(&[osmo_info.clone(), atom_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( @@ -95,7 +97,7 @@ fn user_has_zero_balance_for_swap_req() { let user = Addr::unchecked("user"); let mut mock = - MockEnv::new().allowed_coins(&[osmo_info.clone(), atom_info.clone()]).build().unwrap(); + MockEnv::new().set_params(&[osmo_info.clone(), atom_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( @@ -126,7 +128,7 @@ fn user_does_not_have_enough_balance_for_swap_req() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) + .set_params(&[osmo_info.clone(), atom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, osmo_info.denom.clone()), @@ -166,7 +168,7 @@ fn swap_success_with_specified_amount() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) + .set_params(&[osmo_info.clone(), atom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![Coin::new(10_000u128, atom_info.denom.clone())], @@ -213,7 +215,7 @@ fn swap_success_with_amount_none() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[osmo_info.clone(), atom_info.clone()]) + .set_params(&[osmo_info.clone(), atom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![Coin::new(10_000u128, atom_info.denom.clone())], diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 17d1503c8..9ff14f731 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -1,28 +1,15 @@ -use cosmwasm_std::{coin, Addr, Decimal, Uint128}; +use cosmwasm_std::{Addr, Decimal, Empty, Uint128}; use cw_multi_test::{BasicApp, Executor}; use mars_mock_oracle::msg::{CoinPrice, InstantiateMsg as OracleInstantiateMsg}; -use mars_mock_red_bank::msg::InstantiateMsg as RedBankInstantiateMsg; -use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; use mars_rover::{ adapters::{ - health::HealthContractUnchecked, - oracle::{OracleBase, OracleUnchecked}, - red_bank::RedBankUnchecked, - swap::SwapperBase, - vault::{VaultBase, VaultConfig}, - zapper::ZapperBase, - }, - error::ContractError::InvalidConfig, - msg::{ - instantiate::{ConfigUpdates, VaultInstantiateConfig}, - query::VaultConfigResponse, + health::HealthContractUnchecked, oracle::OracleUnchecked, red_bank::RedBankUnchecked, + swap::SwapperBase, zapper::ZapperBase, }, + msg::instantiate::ConfigUpdates, }; -use crate::helpers::{ - assert_err, locked_vault_info, mock_oracle_contract, mock_red_bank_contract, - mock_vault_contract, uatom_info, uosmo_info, MockEnv, -}; +use crate::helpers::{mock_oracle_contract, mock_red_bank_contract, MockEnv}; pub mod helpers; @@ -35,13 +22,10 @@ fn only_owner_can_update_config() { &new_owner, ConfigUpdates { account_nft: None, - allowed_coins: None, oracle: None, red_bank: None, - max_close_factor: None, max_unlocking_positions: None, swapper: None, - vault_configs: None, zapper: None, health_contract: None, }, @@ -52,110 +36,15 @@ fn only_owner_can_update_config() { } } -#[test] -fn raises_on_invalid_vaults_config() { - let mut mock = MockEnv::new().build().unwrap(); - let original_config = mock.query_config(); - - let mut vault_config = deploy_vault(&mut mock.app); - - // Invalid config. Max LTV should be lower than liquidation threshold. - vault_config.config.max_ltv = Decimal::from_atomics(8u128, 1).unwrap(); - vault_config.config.liquidation_threshold = Decimal::from_atomics(7u128, 1).unwrap(); - - let res = mock.update_config( - &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), - ConfigUpdates { - account_nft: None, - allowed_coins: None, - oracle: None, - red_bank: None, - max_close_factor: None, - max_unlocking_positions: None, - swapper: None, - vault_configs: Some(vec![vault_config]), - zapper: None, - health_contract: None, - }, - ); - - assert_err( - res, - InvalidConfig { - reason: "max ltv or liquidation threshold are invalid".to_string(), - }, - ); - - let mut vault_config = deploy_vault(&mut mock.app); - - // Invalid config. Liquidation threshold should be <= 1. - vault_config.config.liquidation_threshold = Decimal::from_atomics(9u128, 0).unwrap(); - - let res = mock.update_config( - &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), - ConfigUpdates { - account_nft: None, - allowed_coins: None, - oracle: None, - red_bank: None, - max_close_factor: None, - max_unlocking_positions: None, - swapper: None, - vault_configs: Some(vec![vault_config]), - zapper: None, - health_contract: None, - }, - ); - - assert_err( - res, - InvalidConfig { - reason: "max ltv or liquidation threshold are invalid".to_string(), - }, - ); - - // Duplicate vault tokens - let vault_a = deploy_vault(&mut mock.app); - let vault_b = deploy_vault(&mut mock.app); - - let res = mock.update_config( - &Addr::unchecked(original_config.ownership.owner.unwrap()), - ConfigUpdates { - account_nft: None, - allowed_coins: None, - oracle: None, - red_bank: None, - max_close_factor: None, - max_unlocking_positions: None, - swapper: None, - vault_configs: Some(vec![vault_a, vault_b]), - zapper: None, - health_contract: None, - }, - ); - - assert_err( - res, - InvalidConfig { - reason: "Multiple vaults share the same vault token".to_string(), - }, - ); -} - #[test] fn update_config_works_with_full_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); - let original_allowed_coins = mock.query_allowed_coins(None, None); - let original_vault_configs = mock.query_vault_configs(None, None); let new_nft_contract = mock.deploy_new_nft_contract().unwrap(); - let new_vault_configs = vec![deploy_vault(&mut mock.app)]; - let new_allowed_coins = vec!["uosmo".to_string()]; let new_oracle = deploy_new_oracle(&mut mock.app); let new_red_bank = deploy_new_red_bank(&mut mock.app); let new_zapper = ZapperBase::new("new_zapper".to_string()); - let new_close_factor = Decimal::from_atomics(32u128, 2).unwrap(); let new_unlocking_max = Uint128::new(321); let new_swapper = SwapperBase::new("new_swapper".to_string()); let new_health_contract = HealthContractUnchecked::new("new_health_contract".to_string()); @@ -164,13 +53,10 @@ fn update_config_works_with_full_config() { &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), ConfigUpdates { account_nft: Some(new_nft_contract.to_string()), - allowed_coins: Some(new_allowed_coins.clone()), oracle: Some(new_oracle.clone()), red_bank: Some(new_red_bank.clone()), - max_close_factor: Some(new_close_factor), max_unlocking_positions: Some(new_unlocking_max), swapper: Some(new_swapper.clone()), - vault_configs: Some(new_vault_configs.clone()), zapper: Some(new_zapper.clone()), health_contract: Some(new_health_contract.clone()), }, @@ -178,8 +64,6 @@ fn update_config_works_with_full_config() { .unwrap(); let new_config = mock.query_config(); - let new_queried_allowed_coins = mock.query_allowed_coins(None, None); - let new_queried_vault_configs = mock.query_vault_configs(None, None); assert_eq!(new_config.account_nft, Some(new_nft_contract.to_string())); assert_ne!(new_config.account_nft, original_config.account_nft); @@ -189,21 +73,6 @@ fn update_config_works_with_full_config() { original_config.ownership.owner.clone().unwrap() ); - assert_eq!( - new_queried_vault_configs, - new_vault_configs - .iter() - .map(|v| VaultConfigResponse { - vault: v.vault.clone(), - config: v.config.clone(), - }) - .collect::>() - ); - assert_ne!(new_queried_vault_configs, original_vault_configs); - - assert_eq!(new_queried_allowed_coins, new_allowed_coins); - assert_ne!(new_queried_allowed_coins, original_allowed_coins); - assert_eq!(&new_config.oracle, new_oracle.address()); assert_ne!(new_config.oracle, original_config.oracle); @@ -213,9 +82,6 @@ fn update_config_works_with_full_config() { assert_eq!(&new_config.zapper, new_zapper.address()); assert_ne!(new_config.zapper, original_config.zapper); - assert_eq!(new_config.max_close_factor, new_close_factor); - assert_ne!(new_config.max_close_factor, original_config.max_close_factor); - assert_eq!(new_config.max_unlocking_positions, new_unlocking_max); assert_ne!(new_config.max_unlocking_positions, original_config.max_unlocking_positions); @@ -230,8 +96,6 @@ fn update_config_works_with_full_config() { fn update_config_works_with_some_config() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); - let original_allowed_coins = mock.query_allowed_coins(None, None); - let original_vault_configs = mock.query_vault_configs(None, None); let new_nft_contract = mock.deploy_new_nft_contract().unwrap(); let new_max_unlocking = Uint128::new(42); @@ -247,8 +111,6 @@ fn update_config_works_with_some_config() { .unwrap(); let new_config = mock.query_config(); - let new_queried_allowed_coins = mock.query_allowed_coins(None, None); - let new_queried_vault_configs = mock.query_vault_configs(None, None); // Changed configs assert_eq!(new_config.account_nft, Some(new_nft_contract.to_string())); @@ -262,56 +124,16 @@ fn update_config_works_with_some_config() { assert_eq!(new_config.ownership.proposed, original_config.ownership.proposed); assert_eq!(new_config.red_bank, original_config.red_bank); assert_eq!(new_config.oracle, original_config.oracle); - assert_eq!(new_config.max_close_factor, original_config.max_close_factor); + assert_eq!(new_config.params, original_config.params); assert_eq!(new_config.swapper, original_config.swapper); assert_eq!(new_config.zapper, original_config.zapper); assert_eq!(new_config.health_contract, original_config.health_contract); - assert_eq!(original_allowed_coins, new_queried_allowed_coins); - assert_eq!(new_queried_vault_configs, original_vault_configs); -} - -#[test] -fn update_config_removes_properly() { - let uatom = uatom_info(); - let uosmo = uosmo_info(); - let leverage_vault = locked_vault_info(); - - let mut mock = MockEnv::new() - .allowed_coins(&[uatom, uosmo]) - .vault_configs(&[leverage_vault]) - .build() - .unwrap(); - - let allowed_coins = mock.query_allowed_coins(None, None); - let vault_configs = mock.query_vault_configs(None, None); - - assert_eq!(allowed_coins.len(), 2); - assert_eq!(vault_configs.len(), 1); - - mock.update_config( - &Addr::unchecked(mock.query_config().ownership.owner.unwrap()), - ConfigUpdates { - allowed_coins: Some(vec![]), - vault_configs: Some(vec![]), - ..Default::default() - }, - ) - .unwrap(); - - let allowed_coins = mock.query_allowed_coins(None, None); - let vault_configs = mock.query_vault_configs(None, None); - - // All allowed vaults and coins removed - assert_eq!(allowed_coins.len(), 0); - assert_eq!(vault_configs.len(), 0); } #[test] fn update_config_does_nothing_when_nothing_is_passed() { let mut mock = MockEnv::new().build().unwrap(); let original_config = mock.query_config(); - let original_vault_configs = mock.query_vault_configs(None, None); - let original_allowed_coins = mock.query_allowed_coins(None, None); mock.update_config( &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), @@ -320,120 +142,17 @@ fn update_config_does_nothing_when_nothing_is_passed() { .unwrap(); let new_config = mock.query_config(); - let new_queried_vault_configs = mock.query_vault_configs(None, None); - let new_queried_allowed_coins = mock.query_allowed_coins(None, None); assert_eq!(new_config.account_nft, original_config.account_nft); assert_eq!(new_config.ownership, original_config.ownership); - assert_eq!(new_queried_vault_configs, original_vault_configs); - assert_eq!(new_queried_allowed_coins, original_allowed_coins); assert_eq!(new_config.red_bank, original_config.red_bank); assert_eq!(new_config.oracle, original_config.oracle); assert_eq!(new_config.zapper, original_config.zapper); - assert_eq!(new_config.max_close_factor, original_config.max_close_factor); + assert_eq!(new_config.params, original_config.params); assert_eq!(new_config.swapper, original_config.swapper); assert_eq!(new_config.health_contract, original_config.health_contract); } -#[test] -fn max_close_factor_validated_on_update() { - let mut mock = MockEnv::new().build().unwrap(); - let original_config = mock.query_config(); - let res = mock.update_config( - &Addr::unchecked(original_config.ownership.owner.unwrap()), - ConfigUpdates { - max_close_factor: Some(Decimal::from_atomics(42u128, 1).unwrap()), - ..Default::default() - }, - ); - - assert_err( - res, - InvalidConfig { - reason: "value greater than one".to_string(), - }, - ); -} - -#[test] -fn raises_on_duplicate_vault_configs() { - let mut mock = MockEnv::new().build().unwrap(); - let original_config = mock.query_config(); - let res = mock.update_config( - &Addr::unchecked(original_config.ownership.owner.unwrap()), - ConfigUpdates { - account_nft: None, - allowed_coins: None, - oracle: None, - red_bank: None, - max_close_factor: None, - max_unlocking_positions: None, - swapper: None, - vault_configs: Some(vec![ - VaultInstantiateConfig { - vault: VaultBase::new("vault_123".to_string()), - config: VaultConfig { - deposit_cap: Default::default(), - max_ltv: Default::default(), - liquidation_threshold: Default::default(), - whitelisted: true, - }, - }, - VaultInstantiateConfig { - vault: VaultBase::new("vault_123".to_string()), - config: VaultConfig { - deposit_cap: Default::default(), - max_ltv: Default::default(), - liquidation_threshold: Default::default(), - whitelisted: false, - }, - }, - ]), - zapper: None, - health_contract: None, - }, - ); - - assert_err( - res, - InvalidConfig { - reason: "Duplicate vault configs present".to_string(), - }, - ); -} - -#[test] -fn raises_on_duplicate_coin_configs() { - let mut mock = MockEnv::new().build().unwrap(); - let original_config = mock.query_config(); - let res = mock.update_config( - &Addr::unchecked(original_config.ownership.owner.unwrap()), - ConfigUpdates { - account_nft: None, - allowed_coins: Some(vec![ - "uosmo".to_string(), - "uatom".to_string(), - "uosmo".to_string(), - ]), - oracle: None, - red_bank: None, - max_close_factor: None, - max_unlocking_positions: None, - swapper: None, - vault_configs: None, - zapper: None, - health_contract: None, - }, - ); - - assert_err( - res, - InvalidConfig { - reason: "Duplicate coin configs present".to_string(), - }, - ); -} - fn deploy_new_oracle(app: &mut BasicApp) -> OracleUnchecked { let contract_code_id = app.store_code(mock_oracle_contract()); let addr = app @@ -466,9 +185,7 @@ fn deploy_new_red_bank(app: &mut BasicApp) -> RedBankUnchecked { .instantiate_contract( contract_code_id, Addr::unchecked("red_bank_contract_owner"), - &RedBankInstantiateMsg { - coins: vec![], - }, + &Empty {}, &[], "mock-red-bank", None, @@ -476,31 +193,3 @@ fn deploy_new_red_bank(app: &mut BasicApp) -> RedBankUnchecked { .unwrap(); RedBankUnchecked::new(addr.to_string()) } - -fn deploy_vault(app: &mut BasicApp) -> VaultInstantiateConfig { - let code_id = app.store_code(mock_vault_contract()); - let addr = app - .instantiate_contract( - code_id, - Addr::unchecked("vault-instantiator"), - &VaultInstantiateMsg { - vault_token_denom: "vault_xyz".to_string(), - lockup: None, - base_token_denom: "uusdc".to_string(), - oracle: OracleBase::new("oracle".to_string()), - }, - &[], - "mock-vault", - None, - ) - .unwrap(); - VaultInstantiateConfig { - vault: VaultBase::new(addr.to_string()), - config: VaultConfig { - deposit_cap: coin(123, "uusdc"), - max_ltv: Decimal::from_atomics(3u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), - whitelisted: false, - }, - } -} diff --git a/contracts/credit-manager/tests/test_utilization_query.rs b/contracts/credit-manager/tests/test_utilization_query.rs index 075579c9a..70d6a4b38 100644 --- a/contracts/credit-manager/tests/test_utilization_query.rs +++ b/contracts/credit-manager/tests/test_utilization_query.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use cosmwasm_std::{Addr, Decimal, Uint128}; use mars_rover::{ adapters::vault::VaultUnchecked, @@ -37,10 +39,11 @@ fn utilization_if_cap_is_base_denom() { let user = Addr::unchecked("user"); let base_info = CoinInfo { denom: "base_denom".to_string(), - price: Decimal::from_atomics(1u128, 0).unwrap(), - max_ltv: Default::default(), - liquidation_threshold: Default::default(), - liquidation_bonus: Default::default(), + price: Decimal::from_str("1").unwrap(), + max_ltv: Decimal::from_str("0.6").unwrap(), + liquidation_threshold: Decimal::from_str("0.7").unwrap(), + liquidation_bonus: Decimal::from_str("0.15").unwrap(), + whitelisted: true, }; let leverage_vault = VaultTestInfo { @@ -48,13 +51,13 @@ fn utilization_if_cap_is_base_denom() { base_token_denom: base_info.denom.clone(), lockup: None, deposit_cap: base_info.to_coin(100), - max_ltv: Default::default(), - liquidation_threshold: Default::default(), + max_ltv: Decimal::from_str("0.6").unwrap(), + liquidation_threshold: Decimal::from_str("0.7").unwrap(), whitelisted: true, }; let mut mock = MockEnv::new() - .allowed_coins(&[base_info.clone()]) + .set_params(&[base_info.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -105,14 +108,14 @@ fn utilization_in_other_denom() { base_token_denom: jake_info.denom.clone(), lockup: None, deposit_cap: osmo_info.to_coin(50_000_000), - max_ltv: Default::default(), - liquidation_threshold: Default::default(), + max_ltv: Decimal::from_str("0.6").unwrap(), + liquidation_threshold: Decimal::from_str("0.7").unwrap(), whitelisted: true, }; let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[jake_info.clone(), osmo_info]) + .set_params(&[jake_info.clone(), osmo_info]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), diff --git a/contracts/credit-manager/tests/test_vault_enter.rs b/contracts/credit-manager/tests/test_vault_enter.rs index 3d75a0fc6..1da477e6d 100644 --- a/contracts/credit-manager/tests/test_vault_enter.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -88,7 +88,7 @@ fn vault_is_whitelisted() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo]) + .set_params(&[uatom.clone(), uosmo]) .vault_configs(&[leverage_vault.clone()]) .build() .unwrap(); @@ -117,7 +117,7 @@ fn deposited_coin_matches_vault_requirements() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone()]) + .set_params(&[uatom.clone()]) .vault_configs(&[leverage_vault.clone()]) .build() .unwrap(); @@ -149,7 +149,7 @@ fn fails_if_not_enough_funds_for_implied_deposit() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -185,7 +185,7 @@ fn fails_if_not_enough_funds_for_enumerated_deposit() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -223,7 +223,7 @@ fn successful_deposit_into_locked_vault() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -276,7 +276,7 @@ fn successful_deposit_into_unlocked_vault() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -327,7 +327,7 @@ fn vault_deposit_must_be_under_cap() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -405,7 +405,7 @@ fn successful_deposit_with_implied_full_balance_amount() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), diff --git a/contracts/credit-manager/tests/test_vault_exit.rs b/contracts/credit-manager/tests/test_vault_exit.rs index 4c8287373..1568c5b08 100644 --- a/contracts/credit-manager/tests/test_vault_exit.rs +++ b/contracts/credit-manager/tests/test_vault_exit.rs @@ -72,7 +72,7 @@ fn no_unlocked_vault_coins_to_withdraw() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uatom.clone(), uosmo.clone()]) + .set_params(&[uatom.clone(), uosmo.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -115,7 +115,7 @@ fn withdraw_with_unlocked_vault_coins() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), diff --git a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs index 11a40abbe..49bdca4b9 100644 --- a/contracts/credit-manager/tests/test_vault_exit_unlocked.rs +++ b/contracts/credit-manager/tests/test_vault_exit_unlocked.rs @@ -76,7 +76,7 @@ fn not_owner_of_unlocking_position() { let user_a = Addr::unchecked("user_a"); let user_b = Addr::unchecked("user_b"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user_a.clone(), @@ -147,7 +147,7 @@ fn unlocking_position_not_ready_time() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -200,7 +200,7 @@ fn unlocking_position_not_ready_blocks() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -253,7 +253,7 @@ fn withdraw_unlock_success_time_expiring() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -335,7 +335,7 @@ fn withdraw_unlock_success_block_expiring() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), diff --git a/contracts/credit-manager/tests/test_vault_query_config.rs b/contracts/credit-manager/tests/test_vault_query_config.rs deleted file mode 100644 index 1069b43bb..000000000 --- a/contracts/credit-manager/tests/test_vault_query_config.rs +++ /dev/null @@ -1,63 +0,0 @@ -use cosmwasm_std::{Addr, StdError}; -use mars_rover::{ - adapters::vault::VaultUnchecked, - msg::execute::Action::{Deposit, EnterVault}, -}; - -use crate::helpers::{lp_token_info, unlocked_vault_info, AccountToFund, MockEnv}; - -pub mod helpers; - -#[test] -fn raises_if_vault_not_in_config() { - let mock = MockEnv::new().build().unwrap(); - let err = mock.query_vault_config(&VaultUnchecked::new("abc".to_string())).unwrap_err(); - assert_eq!( - err, - StdError::generic_err( - "Querier contract error: mars_rover::adapters::vault::config::VaultConfig not found" - .to_string() - ) - ); -} - -#[test] -fn successfully_queries_with_utilization() { - let lp_token = lp_token_info(); - let leverage_vault = unlocked_vault_info(); - - let user = Addr::unchecked("user"); - let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) - .vault_configs(&[leverage_vault.clone()]) - .fund_account(AccountToFund { - addr: user.clone(), - funds: vec![lp_token.to_coin(300)], - }) - .build() - .unwrap(); - - let vault = mock.get_vault(&leverage_vault); - let account_id = mock.create_credit_account(&user).unwrap(); - - mock.update_credit_account( - &account_id, - &user, - vec![ - Deposit(lp_token.to_coin(200)), - EnterVault { - vault: vault.clone(), - coin: lp_token.to_action_coin(23), - }, - ], - &[lp_token.to_coin(200)], - ) - .unwrap(); - - let res = mock.query_vault_config(&vault).unwrap(); - assert_eq!(res.vault, vault); - assert_eq!(res.config.deposit_cap, leverage_vault.deposit_cap); - assert_eq!(res.config.max_ltv, leverage_vault.max_ltv); - assert_eq!(res.config.liquidation_threshold, leverage_vault.liquidation_threshold); - assert_eq!(res.config.whitelisted, leverage_vault.whitelisted); -} diff --git a/contracts/credit-manager/tests/test_vault_query_value.rs b/contracts/credit-manager/tests/test_vault_query_value.rs index b25ea697c..0a034810b 100644 --- a/contracts/credit-manager/tests/test_vault_query_value.rs +++ b/contracts/credit-manager/tests/test_vault_query_value.rs @@ -31,7 +31,7 @@ fn returns_zero_if_vault_empty() { let leverage_vault = unlocked_vault_info(); let mock = MockEnv::new() - .allowed_coins(&[lp_token]) + .set_params(&[lp_token]) .vault_configs(&[leverage_vault.clone()]) .build() .unwrap(); @@ -54,7 +54,7 @@ fn accurately_prices() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), diff --git a/contracts/credit-manager/tests/test_vault_request_unlock.rs b/contracts/credit-manager/tests/test_vault_request_unlock.rs index 243fe215e..4d8858e83 100644 --- a/contracts/credit-manager/tests/test_vault_request_unlock.rs +++ b/contracts/credit-manager/tests/test_vault_request_unlock.rs @@ -138,7 +138,7 @@ fn not_enough_vault_tokens_for_request() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -192,7 +192,7 @@ fn request_unlocked() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), @@ -266,7 +266,7 @@ fn cannot_request_more_than_max() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone()]) + .set_params(&[lp_token.clone()]) .vault_configs(&[leverage_vault.clone()]) .fund_account(AccountToFund { addr: user.clone(), diff --git a/contracts/credit-manager/tests/test_withdraw.rs b/contracts/credit-manager/tests/test_withdraw.rs index af2a775ac..ca861736d 100644 --- a/contracts/credit-manager/tests/test_withdraw.rs +++ b/contracts/credit-manager/tests/test_withdraw.rs @@ -39,7 +39,7 @@ fn only_owner_of_token_can_withdraw() { fn withdraw_nothing() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); + let mut mock = MockEnv::new().set_params(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( @@ -59,7 +59,7 @@ fn withdraw_nothing() { fn withdraw_but_no_funds() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); - let mut mock = MockEnv::new().allowed_coins(&[coin_info.clone()]).build().unwrap(); + let mut mock = MockEnv::new().set_params(&[coin_info.clone()]).build().unwrap(); let account_id = mock.create_credit_account(&user).unwrap(); let res = mock.update_credit_account( @@ -87,7 +87,7 @@ fn withdraw_but_not_enough_funds() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -121,7 +121,7 @@ fn cannot_withdraw_more_than_healthy() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -158,7 +158,7 @@ fn withdraw_success() { let coin_info = uosmo_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[coin_info.clone()]) + .set_params(&[coin_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: coins(300, coin_info.denom.clone()), @@ -193,7 +193,7 @@ fn multiple_withdraw_actions() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[uosmo_info.clone(), uatom_info.clone()]) + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![coin(234, uosmo_info.denom.clone()), coin(25, uatom_info.denom.clone())], diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs index af1501920..5b4484788 100644 --- a/contracts/credit-manager/tests/test_zap_provide.rs +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -51,7 +51,7 @@ fn does_not_have_enough_tokens_to_provide_liq() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], @@ -93,7 +93,7 @@ fn lp_token_out_must_be_whitelisted() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[atom.clone(), osmo.clone()]) + .set_params(&[atom.clone(), osmo.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], @@ -128,7 +128,7 @@ fn coins_in_must_be_whitelisted() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), osmo.clone()]) + .set_params(&[lp_token.clone(), osmo.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], @@ -159,7 +159,7 @@ fn min_received_too_high() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], @@ -197,7 +197,7 @@ fn wrong_denom_provided() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone(), jake.clone()]) + .set_params(&[lp_token.clone(), atom.clone(), jake.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), jake.to_coin(300)], @@ -238,7 +238,7 @@ fn successful_zap() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], @@ -305,7 +305,7 @@ fn can_provide_unbalanced() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone()]) + .set_params(&[lp_token.clone(), atom.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300)], @@ -378,7 +378,7 @@ fn order_does_not_matter() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index 80cadc89d..f882a8513 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -1,18 +1,17 @@ use cosmwasm_std::{Addr, OverflowError, OverflowOperation::Sub, Uint128}; +use mars_params::types::AssetParamsUpdate::AddOrUpdate; use mars_rover::{ error::ContractError as RoverError, - msg::{ - execute::{ - Action::{Deposit, ProvideLiquidity, WithdrawLiquidity}, - ActionAmount, ActionCoin, - }, - instantiate::ConfigUpdates, + msg::execute::{ + Action::{Deposit, ProvideLiquidity, WithdrawLiquidity}, + ActionAmount, ActionCoin, }, }; use mars_v2_zapper_mock::contract::STARTING_LP_POOL_TOKENS; use crate::helpers::{ - assert_err, get_coin, lp_token_info, uatom_info, uosmo_info, AccountToFund, MockEnv, + assert_err, blacklisted_coin, get_coin, lp_token_info, uatom_info, uosmo_info, AccountToFund, + MockEnv, }; pub mod helpers; @@ -47,7 +46,7 @@ fn only_token_owner_can_unzap_for_account() { #[test] fn lp_token_in_must_be_whitelisted() { - let lp_token = lp_token_info(); + let blacklisted = blacklisted_coin(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new().build().unwrap(); @@ -56,23 +55,23 @@ fn lp_token_in_must_be_whitelisted() { &account_id, &user, vec![WithdrawLiquidity { - lp_token: lp_token.to_action_coin(100), + lp_token: blacklisted.to_action_coin(100), }], &[], ); - assert_err(res, RoverError::NotWhitelisted(lp_token.denom)) + assert_err(res, RoverError::NotWhitelisted(blacklisted.denom)) } #[test] fn coins_out_must_be_whitelisted() { let atom = uatom_info(); - let osmo = uosmo_info(); + let mut osmo = uosmo_info(); let lp_token = lp_token_info(); let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], @@ -98,16 +97,11 @@ fn coins_out_must_be_whitelisted() { ) .unwrap(); - // update config to disallow denoms out - let config = mock.query_config(); - mock.update_config( - &Addr::unchecked(config.ownership.owner.unwrap()), - ConfigUpdates { - allowed_coins: Some(vec![lp_token.denom.clone(), atom.denom]), - ..Default::default() - }, - ) - .unwrap(); + // update params to disallow denoms out + osmo.whitelisted = false; + mock.update_asset_params(AddOrUpdate { + params: osmo.clone().into(), + }); let res = mock.update_credit_account( &account_id, @@ -129,7 +123,7 @@ fn does_not_have_the_tokens_to_withdraw_liq() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], @@ -176,7 +170,7 @@ fn amount_zero_passed() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], @@ -221,7 +215,7 @@ fn amount_none_passed_with_no_balance() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], @@ -251,7 +245,7 @@ fn successful_unzap_specified_amount() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], @@ -314,7 +308,7 @@ fn successful_unzap_unspecified_amount() { let user = Addr::unchecked("user"); let mut mock = MockEnv::new() - .allowed_coins(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) .fund_account(AccountToFund { addr: user.clone(), funds: vec![atom.to_coin(300), osmo.to_coin(300)], diff --git a/contracts/health/Cargo.toml b/contracts/health/Cargo.toml index a3abf1b2e..b4f6143f4 100644 --- a/contracts/health/Cargo.toml +++ b/contracts/health/Cargo.toml @@ -24,6 +24,7 @@ cosmwasm-std = { workspace = true } cw2 = { workspace = true } cw-storage-plus = { workspace = true } mars-owner = { workspace = true } +mars-params = { workspace = true } mars-rover-health-computer = { workspace = true } mars-rover-health-types = { workspace = true } mars-rover = { workspace = true } @@ -36,6 +37,4 @@ cw-utils = { workspace = true } cw-vault-standard = { workspace = true } mars-mock-credit-manager = { workspace = true } mars-mock-oracle = { workspace = true } -mars-mock-red-bank = { workspace = true } mars-mock-vault = { workspace = true } -mars-red-bank-types = { workspace = true } diff --git a/contracts/health/src/compute.rs b/contracts/health/src/compute.rs index 179f315b7..59a99f299 100644 --- a/contracts/health/src/compute.rs +++ b/contracts/health/src/compute.rs @@ -2,17 +2,24 @@ use std::collections::HashMap; use cosmwasm_std::{Deps, StdResult}; use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; -use mars_rover_health_types::{HealthError::CreditManagerNotSet, HealthResponse, HealthResult}; +use mars_rover_health_types::{HealthError::ContractNotSet, HealthResponse, HealthResult}; -use crate::{querier::HealthQuerier, state::CREDIT_MANAGER}; +use crate::{ + querier::HealthQuerier, + state::{CREDIT_MANAGER, PARAMS}, +}; /// Uses `mars-rover-health-computer` which is a data agnostic package given /// it's compiled to .wasm and shared with the frontend. /// This function queries all necessary data to pass to `HealthComputer`. pub fn compute_health(deps: Deps, account_id: &str) -> HealthResult { - let credit_manager_addr = - CREDIT_MANAGER.may_load(deps.storage)?.ok_or(CreditManagerNotSet {})?; - let querier = HealthQuerier::new(&deps.querier, &credit_manager_addr); + let credit_manager_addr = CREDIT_MANAGER + .may_load(deps.storage)? + .ok_or(ContractNotSet("credit_manger".to_string()))?; + let params_contract_addr = + PARAMS.may_load(deps.storage)?.ok_or(ContractNotSet("params".to_string()))?; + + let querier = HealthQuerier::new(&deps.querier, &credit_manager_addr, ¶ms_contract_addr); let positions = querier.query_positions(account_id)?; @@ -30,8 +37,8 @@ pub fn compute_health(deps: Deps, account_id: &str) -> HealthResult>>()?; let vault_base_token_denoms = vault_infos.values().map(|v| &v.base_token).collect::>(); - // Collect prices + markets - let (oracle, red_bank) = querier.query_deps()?; + // Collect prices + asset + let (oracle, params) = querier.query_deps()?; let mut denoms_data: DenomsData = Default::default(); deposit_denoms .into_iter() @@ -41,8 +48,8 @@ pub fn compute_health(deps: Deps, account_id: &str) -> HealthResult StdResult<()> { let price = oracle.query_price(&deps.querier, denom)?.price; denoms_data.prices.insert(denom.clone(), price); - let market = red_bank.query_market(&deps.querier, denom)?; - denoms_data.markets.insert(denom.clone(), market); + let params = params.query_asset_params(&deps.querier, denom)?; + denoms_data.params.insert(denom.clone(), params); Ok(()) })?; @@ -56,13 +63,10 @@ pub fn compute_health(deps: Deps, account_id: &str) -> HealthResult Ok(OWNER.update(deps, info, update)?), ExecuteMsg::UpdateConfig { credit_manager, - } => update_config(deps, info, credit_manager), + params, + } => update_config(deps, info, credit_manager, params), } } @@ -61,11 +62,13 @@ pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> HealthResult { } pub fn query_config(deps: Deps) -> HealthResult { - let credit_manager_addr = CREDIT_MANAGER.may_load(deps.storage)?.map(|a| a.into()); + let credit_manager = CREDIT_MANAGER.may_load(deps.storage)?.map(Into::into); + let params = PARAMS.may_load(deps.storage)?.map(Into::into); let owner_response = OWNER.query(deps.storage)?; Ok(ConfigResponse { - credit_manager_addr, + credit_manager, + params, owner_response, }) } diff --git a/contracts/health/src/querier.rs b/contracts/health/src/querier.rs index e99845527..9ee08d600 100644 --- a/contracts/health/src/querier.rs +++ b/contracts/health/src/querier.rs @@ -1,64 +1,55 @@ use cosmwasm_std::{Addr, QuerierWrapper}; +use mars_params::{msg::QueryMsg as ParamsQueryMsg, types::VaultConfig}; use mars_rover::{ - adapters::{ - oracle::Oracle, - red_bank::RedBank, - vault::{Vault, VaultConfig}, - }, - msg::query::{ConfigResponse, Positions, QueryMsg, VaultConfigResponse}, + adapters::{oracle::Oracle, params::Params, vault::Vault}, + msg::query::{ConfigResponse, Positions, QueryMsg as CmQueryMsg}, }; use mars_rover_health_types::HealthResult; pub struct HealthQuerier<'a> { querier: &'a QuerierWrapper<'a>, credit_manager_addr: &'a Addr, + params_contract_addr: &'a Addr, } impl<'a> HealthQuerier<'a> { - pub fn new(querier: &'a QuerierWrapper, credit_manager_addr: &'a Addr) -> Self { + pub fn new( + querier: &'a QuerierWrapper, + credit_manager_addr: &'a Addr, + params_contract_addr: &'a Addr, + ) -> Self { Self { querier, credit_manager_addr, + params_contract_addr, } } pub fn query_positions(&self, account_id: &str) -> HealthResult { Ok(self.querier.query_wasm_smart( self.credit_manager_addr.to_string(), - &QueryMsg::Positions { + &CmQueryMsg::Positions { account_id: account_id.to_string(), }, )?) } - pub fn query_deps(&self) -> HealthResult<(Oracle, RedBank)> { + pub fn query_deps(&self) -> HealthResult<(Oracle, Params)> { let config: ConfigResponse = self .querier - .query_wasm_smart(self.credit_manager_addr.to_string(), &QueryMsg::Config {})?; + .query_wasm_smart(self.credit_manager_addr.to_string(), &CmQueryMsg::Config {})?; Ok(( Oracle::new(Addr::unchecked(config.oracle)), - RedBank::new(Addr::unchecked(config.red_bank)), + Params::new(Addr::unchecked(config.params)), )) } - pub fn query_allowed_coins(&self) -> HealthResult> { - let allowed_coins: Vec = self.querier.query_wasm_smart( - self.credit_manager_addr.to_string(), - &QueryMsg::AllowedCoins { - start_after: None, - limit: Some(u32::MAX), - }, - )?; - Ok(allowed_coins) - } - pub fn query_vault_config(&self, vault: &Vault) -> HealthResult { - let vault_info: VaultConfigResponse = self.querier.query_wasm_smart( - self.credit_manager_addr.to_string(), - &QueryMsg::VaultConfig { - vault: vault.into(), + Ok(self.querier.query_wasm_smart( + self.params_contract_addr.to_string(), + &ParamsQueryMsg::VaultConfig { + address: vault.address.to_string(), }, - )?; - Ok(vault_info.config) + )?) } } diff --git a/contracts/health/src/state.rs b/contracts/health/src/state.rs index b255706b0..2236f49f9 100644 --- a/contracts/health/src/state.rs +++ b/contracts/health/src/state.rs @@ -4,3 +4,4 @@ use mars_owner::Owner; pub const OWNER: Owner = Owner::new("owner"); pub const CREDIT_MANAGER: Item = Item::new("credit_manager"); +pub const PARAMS: Item = Item::new("params"); diff --git a/contracts/health/src/update_config.rs b/contracts/health/src/update_config.rs index c11487bf5..9b52a9386 100644 --- a/contracts/health/src/update_config.rs +++ b/contracts/health/src/update_config.rs @@ -1,19 +1,31 @@ use cosmwasm_std::{DepsMut, MessageInfo, Response}; use mars_rover_health_types::HealthResult; -use crate::state::{CREDIT_MANAGER, OWNER}; +use crate::state::{CREDIT_MANAGER, OWNER, PARAMS}; pub fn update_config( deps: DepsMut, info: MessageInfo, - credit_manager: String, + credit_manager_opt: Option, + params_opt: Option, ) -> HealthResult { OWNER.assert_owner(deps.storage, &info.sender)?; - let validated = deps.api.addr_validate(&credit_manager)?; - CREDIT_MANAGER.save(deps.storage, &validated)?; - Ok(Response::new() - .add_attribute("action", "update_config") - .add_attribute("key", "credit_manager_addr") - .add_attribute("value", credit_manager)) + let mut response = Response::new().add_attribute("action", "update_config"); + + if let Some(cm) = credit_manager_opt { + let validated = deps.api.addr_validate(&cm)?; + CREDIT_MANAGER.save(deps.storage, &validated)?; + + response = response.add_attribute("key", "credit_manager_addr").add_attribute("value", cm); + } + + if let Some(params) = params_opt { + let validated = deps.api.addr_validate(¶ms)?; + PARAMS.save(deps.storage, &validated)?; + + response = response.add_attribute("key", "params_addr").add_attribute("value", params); + } + + Ok(response) } diff --git a/contracts/health/tests/helpers/mock_contracts.rs b/contracts/health/tests/helpers/mock_contracts.rs index a8a86dd35..e533e8282 100644 --- a/contracts/health/tests/helpers/mock_contracts.rs +++ b/contracts/health/tests/helpers/mock_contracts.rs @@ -37,11 +37,11 @@ pub fn mock_oracle_contract() -> Box> { Box::new(contract) } -pub fn mock_red_bank_contract() -> Box> { +pub fn mock_params_contract() -> Box> { let contract = ContractWrapper::new( - mars_mock_red_bank::contract::execute, - mars_mock_red_bank::contract::instantiate, - mars_mock_red_bank::contract::query, + mars_params::contract::execute, + mars_params::contract::instantiate, + mars_params::contract::query, ); Box::new(contract) } diff --git a/contracts/health/tests/helpers/mock_env.rs b/contracts/health/tests/helpers/mock_env.rs index 4e5ea78eb..7a223accc 100644 --- a/contracts/health/tests/helpers/mock_env.rs +++ b/contracts/health/tests/helpers/mock_env.rs @@ -4,20 +4,17 @@ use cw_multi_test::{App, AppResponse, BankSudo, BasicApp, Executor, SudoMsg}; use cw_vault_standard::{ VaultInfoResponse, VaultStandardExecuteMsg::Deposit, VaultStandardQueryMsg::Info, }; -use mars_mock_credit_manager::msg::ExecuteMsg::{ - SetAllowedCoins, SetPositionsResponse, SetVaultConfig, -}; +use mars_mock_credit_manager::msg::ExecuteMsg::SetPositionsResponse; use mars_mock_oracle::msg::{CoinPrice, ExecuteMsg::ChangePrice}; -use mars_mock_red_bank::msg::CoinMarketInfo; use mars_mock_vault::contract::STARTING_VAULT_SHARES; -use mars_red_bank_types::red_bank::{ExecuteMsg::UpdateAsset, InitOrUpdateAssetParams}; -use mars_rover::{ - adapters::vault::VaultUnchecked, +use mars_params::{ msg::{ - query::{Positions, VaultConfigResponse as CmVaultConfig}, - QueryMsg::VaultConfig, + ExecuteMsg::{UpdateAssetParams, UpdateVaultConfig}, + QueryMsg as ParamsQueryMsg, }, + types::{AssetParamsUpdate, VaultConfig, VaultConfigUpdate}, }; +use mars_rover::{adapters::vault::VaultUnchecked, msg::query::Positions}; use mars_rover_health_types::{ConfigResponse, ExecuteMsg::UpdateConfig, HealthResponse, QueryMsg}; use crate::helpers::MockEnvBuilder; @@ -29,7 +26,7 @@ pub struct MockEnv { pub cm_contract: Addr, pub vault_contract: Addr, pub oracle: Addr, - pub red_bank: Addr, + pub params: Addr, } #[allow(clippy::new_ret_no_self)] @@ -40,10 +37,11 @@ impl MockEnv { deployer: Addr::unchecked("deployer"), health_contract: None, set_cm_config: true, + set_params_config: true, cm_contract: None, vault_contract: None, oracle: None, - red_bank: None, + params: None, } } @@ -63,13 +61,13 @@ impl MockEnv { .unwrap() } - pub fn query_vault_config(&self, vault: &VaultUnchecked) -> CmVaultConfig { + pub fn query_vault_config(&self, vault: &VaultUnchecked) -> VaultConfig { self.app .wrap() .query_wasm_smart( - self.cm_contract.clone(), - &VaultConfig { - vault: vault.clone(), + self.params.clone(), + &ParamsQueryMsg::VaultConfig { + address: vault.address.to_string(), }, ) .unwrap() @@ -78,13 +76,15 @@ impl MockEnv { pub fn update_config( &mut self, sender: &Addr, - credit_manager_addr: &Addr, + credit_manager: Option, + params: Option, ) -> AnyResult { self.app.execute_contract( sender.clone(), self.health_contract.clone(), &UpdateConfig { - credit_manager: credit_manager_addr.to_string(), + credit_manager, + params, }, &[], ) @@ -163,52 +163,23 @@ impl MockEnv { .unwrap(); } - pub fn set_market(&mut self, denom: &str, market: &CoinMarketInfo) { - self.app - .execute_contract( - self.deployer.clone(), - self.red_bank.clone(), - &UpdateAsset { - denom: denom.to_string(), - params: InitOrUpdateAssetParams { - max_loan_to_value: Some(market.max_ltv), - liquidation_threshold: Some(market.liquidation_threshold), - liquidation_bonus: Some(market.liquidation_bonus), - reserve_factor: None, - interest_rate_model: None, - deposit_enabled: None, - borrow_enabled: None, - deposit_cap: None, - }, - }, - &[], - ) - .unwrap(); - } - - pub fn set_allowed_coins(&mut self, allowed_coins: &[String]) { + pub fn update_asset_params(&mut self, update: AssetParamsUpdate) { self.app .execute_contract( self.deployer.clone(), - self.cm_contract.clone(), - &SetAllowedCoins(allowed_coins.to_vec()), + self.params.clone(), + &UpdateAssetParams(update), &[], ) .unwrap(); } - pub fn vault_allowed(&mut self, vault: &VaultUnchecked, allowed: bool) { - let mut config = self.query_vault_config(vault).config; - config.whitelisted = allowed; - + pub fn update_vault_params(&mut self, update: VaultConfigUpdate) { self.app .execute_contract( self.deployer.clone(), - self.cm_contract.clone(), - &SetVaultConfig { - address: self.vault_contract.to_string(), - config, - }, + self.params.clone(), + &UpdateVaultConfig(update), &[], ) .unwrap(); diff --git a/contracts/health/tests/helpers/mock_env_builder.rs b/contracts/health/tests/helpers/mock_env_builder.rs index c08acd1fc..f5c8674f1 100644 --- a/contracts/health/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/helpers/mock_env_builder.rs @@ -1,25 +1,23 @@ -use std::mem::take; +use std::{mem::take, str::FromStr}; use anyhow::Result as AnyResult; use cosmwasm_std::{coin, Addr, Decimal}; use cw_multi_test::{BasicApp, Executor}; use cw_utils::Duration; -use mars_mock_credit_manager::msg::{ - ExecuteMsg::SetVaultConfig, InstantiateMsg as CmMockInstantiateMsg, -}; +use mars_mock_credit_manager::msg::InstantiateMsg as CmMockInstantiateMsg; use mars_mock_oracle::msg::InstantiateMsg as OracleInstantiateMsg; -use mars_mock_red_bank::msg::InstantiateMsg as RedBankInstantiateMsg; use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; use mars_owner::OwnerResponse; -use mars_rover::{ - adapters::{oracle::OracleUnchecked, vault::VaultConfig}, - msg::query::ConfigResponse, +use mars_params::{ + msg::{ExecuteMsg::UpdateVaultConfig, InstantiateMsg as ParamsInstantiateMsg}, + types::{VaultConfigUnchecked, VaultConfigUpdate::AddOrUpdate}, }; +use mars_rover::{adapters::oracle::OracleUnchecked, msg::query::ConfigResponse}; use mars_rover_health_types::{ExecuteMsg::UpdateConfig, InstantiateMsg}; use crate::helpers::{ - mock_credit_manager_contract, mock_health_contract, mock_oracle_contract, - mock_red_bank_contract, mock_vault_contract, MockEnv, + mock_credit_manager_contract, mock_health_contract, mock_oracle_contract, mock_params_contract, + mock_vault_contract, MockEnv, }; pub struct MockEnvBuilder { @@ -29,8 +27,9 @@ pub struct MockEnvBuilder { pub cm_contract: Option, pub vault_contract: Option, pub oracle: Option, - pub red_bank: Option, + pub params: Option, pub set_cm_config: bool, + pub set_params_config: bool, } impl MockEnvBuilder { @@ -39,14 +38,18 @@ impl MockEnvBuilder { self.add_cm_to_config(); } + if self.set_params_config { + self.add_params_to_config(); + } + Ok(MockEnv { deployer: self.deployer.clone(), health_contract: self.get_health_contract(), vault_contract: self.get_vault_contract(), oracle: self.get_oracle(), - red_bank: self.get_red_bank(), cm_contract: self.get_cm_contract(), app: take(&mut self.app), + params: self.get_params_contract(), }) } @@ -55,6 +58,11 @@ impl MockEnvBuilder { self } + pub fn skip_params_config(&mut self) -> &mut Self { + self.set_params_config = false; + self + } + fn add_cm_to_config(&mut self) { let health_contract = self.get_health_contract(); let cm_contract = self.get_cm_contract(); @@ -64,7 +72,25 @@ impl MockEnvBuilder { self.deployer.clone(), health_contract, &UpdateConfig { - credit_manager: cm_contract.to_string(), + credit_manager: Some(cm_contract.to_string()), + params: None, + }, + &[], + ) + .unwrap(); + } + + fn add_params_to_config(&mut self) { + let health_contract = self.get_health_contract(); + let params_contract = self.get_params_contract(); + + self.app + .execute_contract( + self.deployer.clone(), + health_contract, + &UpdateConfig { + credit_manager: None, + params: Some(params_contract.to_string()), }, &[], ) @@ -98,33 +124,6 @@ impl MockEnvBuilder { self.oracle = Some(addr); } - fn get_red_bank(&mut self) -> Addr { - if self.red_bank.is_none() { - self.deploy_red_bank() - } - self.red_bank.clone().unwrap() - } - - fn deploy_red_bank(&mut self) { - let contract = mock_red_bank_contract(); - let code_id = self.app.store_code(contract); - - let addr = self - .app - .instantiate_contract( - code_id, - self.deployer.clone(), - &RedBankInstantiateMsg { - coins: vec![], - }, - &[], - "mock-red-bank", - None, - ) - .unwrap(); - self.red_bank = Some(addr); - } - fn get_cm_contract(&mut self) -> Addr { if self.cm_contract.is_none() { self.deploy_cm_contract() @@ -135,8 +134,8 @@ impl MockEnvBuilder { fn deploy_cm_contract(&mut self) { let contract = mock_credit_manager_contract(); let code_id = self.app.store_code(contract); - let red_bank = self.get_red_bank().to_string(); let oracle = self.get_oracle().to_string(); + let params = self.get_params_contract().to_string(); let cm_addr = self .app @@ -152,10 +151,10 @@ impl MockEnvBuilder { initialized: true, abolished: false, }, - red_bank, + red_bank: "n/a".to_string(), oracle, + params, account_nft: None, - max_close_factor: Default::default(), max_unlocking_positions: Default::default(), swapper: "n/a".to_string(), zapper: "n/a".to_string(), @@ -167,28 +166,56 @@ impl MockEnvBuilder { Some(self.deployer.clone().into()), ) .unwrap(); - self.cm_contract = Some(cm_addr.clone()); + self.cm_contract = Some(cm_addr); // Set mock vault with a starting config - let vault = self.get_vault_contract().to_string(); + let vault = self.get_vault_contract(); + let params = self.get_params_contract(); self.app .execute_contract( self.deployer.clone(), - cm_addr, - &SetVaultConfig { - address: vault, - config: VaultConfig { + params, + &UpdateVaultConfig(AddOrUpdate { + config: VaultConfigUnchecked { + addr: vault.to_string(), deposit_cap: coin(10000000u128, "uusdc"), - max_ltv: Decimal::from_atomics(4u128, 1).unwrap(), + max_loan_to_value: Decimal::from_atomics(4u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(44u128, 2).unwrap(), whitelisted: true, }, - }, + }), &[], ) .unwrap(); } + fn get_params_contract(&mut self) -> Addr { + if self.params.is_none() { + let hc = self.deploy_params_contract(); + self.params = Some(hc); + } + self.params.clone().unwrap() + } + + pub fn deploy_params_contract(&mut self) -> Addr { + let contract_code_id = self.app.store_code(mock_params_contract()); + let owner = self.deployer.clone(); + + self.app + .instantiate_contract( + contract_code_id, + owner.clone(), + &ParamsInstantiateMsg { + owner: owner.to_string(), + max_close_factor: Decimal::from_str("0.5").unwrap(), + }, + &[], + "mock-params-contract", + Some(owner.to_string()), + ) + .unwrap() + } + fn get_vault_contract(&mut self) -> Addr { if self.vault_contract.is_none() { self.deploy_vault_contract() diff --git a/contracts/health/tests/test_compute_health.rs b/contracts/health/tests/test_compute_health.rs index b65215bb2..0e37086b3 100644 --- a/contracts/health/tests/test_compute_health.rs +++ b/contracts/health/tests/test_compute_health.rs @@ -1,5 +1,10 @@ +use std::str::FromStr; + use cosmwasm_std::{Coin, Decimal, StdError, Uint128}; -use mars_mock_red_bank::msg::CoinMarketInfo; +use mars_params::types::{ + AssetParams, AssetParamsUpdate::AddOrUpdate, HighLeverageStrategyParams, RedBankSettings, + RoverSettings, VaultConfigUpdate, +}; use mars_rover::{ adapters::vault::{ LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultPosition, @@ -19,8 +24,19 @@ fn raises_when_credit_manager_not_set() { assert_eq!( err, StdError::generic_err( - "Querier contract error: The credit manager address has not been set in config" - .to_string() + "Querier contract error: credit_manger address has not been set in config".to_string() + ) + ); +} + +#[test] +fn raises_when_params_contract_not_set() { + let mock = MockEnv::new().skip_params_config().build().unwrap(); + let err: StdError = mock.query_health("xyz").unwrap_err(); + assert_eq!( + err, + StdError::generic_err( + "Querier contract error: params address has not been set in config".to_string() ) ); } @@ -98,28 +114,40 @@ fn adds_vault_base_denoms_to_oracle_and_red_bank() { }, ); - mock.set_allowed_coins(&[vault_base_token.to_string()]); - - let max_ltv = Decimal::from_atomics(4523u128, 4).unwrap(); + let max_loan_to_value = Decimal::from_atomics(4523u128, 4).unwrap(); let liquidation_threshold = Decimal::from_atomics(5u128, 1).unwrap(); - mock.set_price(vault_base_token, Decimal::one()); - mock.set_market( - vault_base_token, - &CoinMarketInfo { + let update = AddOrUpdate { + params: AssetParams { denom: vault_base_token.to_string(), - max_ltv, + rover: RoverSettings { + whitelisted: true, + hls: HighLeverageStrategyParams { + max_loan_to_value: Decimal::from_str("0.8").unwrap(), + liquidation_threshold: Decimal::from_str("0.9").unwrap(), + }, + }, + red_bank: RedBankSettings { + deposit_enabled: false, + borrow_enabled: false, + deposit_cap: Default::default(), + }, + max_loan_to_value, liquidation_threshold, liquidation_bonus: Decimal::from_atomics(9u128, 2).unwrap(), }, - ); + }; + + mock.update_asset_params(update); + + mock.set_price(vault_base_token, Decimal::one()); let health = mock.query_health(account_id).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, unlocking_amount); assert_eq!( health.max_ltv_adjusted_collateral, - unlocking_amount.checked_mul_floor(max_ltv).unwrap() + unlocking_amount.checked_mul_floor(max_loan_to_value).unwrap() ); assert_eq!( health.liquidation_threshold_adjusted_collateral, @@ -132,26 +160,41 @@ fn adds_vault_base_denoms_to_oracle_and_red_bank() { } #[test] -fn allowed_coins_work() { +fn whitelisted_coins_work() { let mut mock = MockEnv::new().build().unwrap(); - mock.set_allowed_coins(&[]); - let umars = "umars"; + mock.set_price(umars, Decimal::one()); - let max_ltv = Decimal::from_atomics(4523u128, 4).unwrap(); + let max_loan_to_value = Decimal::from_atomics(4523u128, 4).unwrap(); let liquidation_threshold = Decimal::from_atomics(5u128, 1).unwrap(); - - mock.set_market( - umars, - &CoinMarketInfo { - denom: umars.to_string(), - max_ltv, - liquidation_threshold, - liquidation_bonus: Decimal::from_atomics(9u128, 2).unwrap(), + let liquidation_bonus = Decimal::from_atomics(9u128, 2).unwrap(); + + let mut asset_params = AssetParams { + denom: umars.to_string(), + rover: RoverSettings { + whitelisted: false, + hls: HighLeverageStrategyParams { + max_loan_to_value: Decimal::from_str("0.8").unwrap(), + liquidation_threshold: Decimal::from_str("0.9").unwrap(), + }, }, - ); + red_bank: RedBankSettings { + deposit_enabled: false, + borrow_enabled: false, + deposit_cap: Default::default(), + }, + max_loan_to_value, + liquidation_threshold, + liquidation_bonus, + }; + + let update = AddOrUpdate { + params: asset_params.clone(), + }; + + mock.update_asset_params(update); let deposit_amount = Uint128::new(30); @@ -184,12 +227,15 @@ fn allowed_coins_work() { assert!(!health.above_max_ltv); // Add to whitelist - mock.set_allowed_coins(&[umars.to_string()]); + asset_params.rover.whitelisted = true; + mock.update_asset_params(AddOrUpdate { + params: asset_params, + }); let health = mock.query_health(account_id).unwrap(); // Now reflects deposit value assert_eq!( health.max_ltv_adjusted_collateral, - deposit_amount.checked_mul_floor(max_ltv).unwrap() + deposit_amount.checked_mul_floor(max_loan_to_value).unwrap() ); } @@ -220,36 +266,44 @@ fn vault_whitelist_affects_max_ltv() { }], }, ); - mock.set_allowed_coins(&[vault_base_token.to_string()]); - - let max_ltv = Decimal::from_atomics(4523u128, 4).unwrap(); - let liquidation_threshold = Decimal::from_atomics(5u128, 1).unwrap(); - mock.set_price(vault_base_token, Decimal::one()); - mock.set_market( - vault_base_token, - &CoinMarketInfo { + let update = AddOrUpdate { + params: AssetParams { denom: vault_base_token.to_string(), - max_ltv, - liquidation_threshold, + rover: RoverSettings { + whitelisted: true, + hls: HighLeverageStrategyParams { + max_loan_to_value: Decimal::from_str("0.8").unwrap(), + liquidation_threshold: Decimal::from_str("0.9").unwrap(), + }, + }, + red_bank: RedBankSettings { + deposit_enabled: false, + borrow_enabled: false, + deposit_cap: Default::default(), + }, + max_loan_to_value: Decimal::from_str("0.4523").unwrap(), + liquidation_threshold: Decimal::from_str("0.5").unwrap(), liquidation_bonus: Decimal::from_atomics(9u128, 2).unwrap(), }, - ); + }; - let vault_config = mock.query_vault_config(&vault.clone().into()); - let vault_max_ltv = vault_config.config.max_ltv; - let vault_liq_threshold = vault_config.config.liquidation_threshold; + mock.update_asset_params(update); + + mock.set_price(vault_base_token, Decimal::one()); + + let mut vault_config = mock.query_vault_config(&vault.into()); let health = mock.query_health(account_id).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, base_token_amount); assert_eq!( health.max_ltv_adjusted_collateral, - base_token_amount.checked_mul_floor(vault_max_ltv).unwrap() + base_token_amount.checked_mul_floor(vault_config.max_loan_to_value).unwrap() ); assert_eq!( health.liquidation_threshold_adjusted_collateral, - base_token_amount.checked_mul_floor(vault_liq_threshold).unwrap() + base_token_amount.checked_mul_floor(vault_config.liquidation_threshold).unwrap() ); assert_eq!(health.max_ltv_health_factor, None); assert_eq!(health.liquidation_health_factor, None); @@ -257,7 +311,12 @@ fn vault_whitelist_affects_max_ltv() { assert!(!health.above_max_ltv); // After de-listing, maxLTV drops to zero - mock.vault_allowed(&vault.into(), false); + vault_config.whitelisted = false; + + mock.update_vault_params(VaultConfigUpdate::AddOrUpdate { + config: vault_config.into(), + }); + let health = mock.query_health(account_id).unwrap(); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); } diff --git a/contracts/health/tests/test_update_config.rs b/contracts/health/tests/test_update_config.rs index 4c3d4df6b..0502baf70 100644 --- a/contracts/health/tests/test_update_config.rs +++ b/contracts/health/tests/test_update_config.rs @@ -13,10 +13,10 @@ pub mod helpers; fn only_owner_can_update_config() { let mut mock = MockEnv::new().build().unwrap(); - let new_cm_addr = Addr::unchecked("xyz"); + let new_cm_addr = "xyz".to_string(); let bad_guy = Addr::unchecked("bad_guy"); let err: HealthError = - mock.update_config(&bad_guy, &new_cm_addr).unwrap_err().downcast().unwrap(); + mock.update_config(&bad_guy, Some(new_cm_addr), None).unwrap_err().downcast().unwrap(); assert_eq!(err, Owner(NotOwner {})); } @@ -25,9 +25,11 @@ fn only_owner_can_update_config() { fn raises_on_invalid_config() { let mut mock = MockEnv::new().build().unwrap(); - let new_cm_addr = Addr::unchecked(""); - let err: HealthError = - mock.update_config(&mock.deployer.clone(), &new_cm_addr).unwrap_err().downcast().unwrap(); + let err: HealthError = mock + .update_config(&mock.deployer.clone(), Some("".to_string()), None) + .unwrap_err() + .downcast() + .unwrap(); assert_eq!( err, @@ -38,15 +40,32 @@ fn raises_on_invalid_config() { } #[test] -fn update_config_works() { - let mut mock = MockEnv::new().build().unwrap(); +fn update_partial_config_works() { + let mut mock = MockEnv::new().skip_params_config().skip_cm_config().build().unwrap(); + + mock.update_config(&mock.deployer.clone(), Some("abc".to_string()), None).unwrap(); + + let new_config = mock.query_config(); + + assert_eq!(new_config.credit_manager, Some("abc".to_string())); + assert_eq!(new_config.params, None); + assert_eq!(new_config.owner_response.owner, Some(mock.deployer.to_string())); + assert_eq!(new_config.owner_response.proposed, None); + assert!(new_config.owner_response.initialized); + assert!(!new_config.owner_response.abolished); +} + +#[test] +fn update_full_config_works() { + let mut mock = MockEnv::new().skip_params_config().skip_cm_config().build().unwrap(); - let new_cm_addr = Addr::unchecked("abc"); - mock.update_config(&mock.deployer.clone(), &new_cm_addr).unwrap(); + mock.update_config(&mock.deployer.clone(), Some("abc".to_string()), Some("xyz".to_string())) + .unwrap(); let new_config = mock.query_config(); - assert_eq!(new_config.credit_manager_addr, Some("abc".to_string())); + assert_eq!(new_config.credit_manager, Some("abc".to_string())); + assert_eq!(new_config.params, Some("xyz".to_string())); assert_eq!(new_config.owner_response.owner, Some(mock.deployer.to_string())); assert_eq!(new_config.owner_response.proposed, None); assert!(new_config.owner_response.initialized); diff --git a/contracts/mock-credit-manager/src/contract.rs b/contracts/mock-credit-manager/src/contract.rs index 4de0b53b6..98cc7a79c 100644 --- a/contracts/mock-credit-manager/src/contract.rs +++ b/contracts/mock-credit-manager/src/contract.rs @@ -4,10 +4,10 @@ use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, use mars_rover::msg::QueryMsg; use crate::{ - execute::{set_allowed_coins, set_position_response, set_vault_config}, + execute::set_position_response, msg::{ExecuteMsg, InstantiateMsg}, - query::{query_allowed_coins, query_config, query_positions, query_vault_config}, - state::{ALLOWED_COINS, CONFIG}, + query::{query_config, query_positions}, + state::CONFIG, }; #[cfg_attr(not(feature = "library"), entry_point)] @@ -18,7 +18,6 @@ pub fn instantiate( msg: InstantiateMsg, ) -> StdResult { CONFIG.save(deps.storage, &msg.config)?; - ALLOWED_COINS.save(deps.storage, &vec![])?; Ok(Response::default()) } @@ -34,11 +33,6 @@ pub fn execute( account_id, positions, } => set_position_response(deps, account_id, positions), - ExecuteMsg::SetAllowedCoins(allowed_coins) => set_allowed_coins(deps, allowed_coins), - ExecuteMsg::SetVaultConfig { - address, - config, - } => set_vault_config(deps, &address, config), } } @@ -49,12 +43,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { account_id, } => to_binary(&query_positions(deps, account_id)?), QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::AllowedCoins { - .. - } => to_binary(&query_allowed_coins(deps)?), - QueryMsg::VaultConfig { - vault, - } => to_binary(&query_vault_config(deps, vault)?), _ => unimplemented!("query msg not supported"), } } diff --git a/contracts/mock-credit-manager/src/execute.rs b/contracts/mock-credit-manager/src/execute.rs index 345c87dcc..a92769d49 100644 --- a/contracts/mock-credit-manager/src/execute.rs +++ b/contracts/mock-credit-manager/src/execute.rs @@ -1,7 +1,7 @@ -use cosmwasm_std::{Addr, DepsMut, Response, StdResult}; -use mars_rover::{adapters::vault::VaultConfig, msg::query::Positions}; +use cosmwasm_std::{DepsMut, Response, StdResult}; +use mars_rover::msg::query::Positions; -use crate::state::{ALLOWED_COINS, POSITION_RESPONSES, VAULT_CONFIGS}; +use crate::state::POSITION_RESPONSES; pub fn set_position_response( deps: DepsMut, @@ -11,13 +11,3 @@ pub fn set_position_response( POSITION_RESPONSES.save(deps.storage, &account_id, &positions)?; Ok(Response::new()) } - -pub fn set_allowed_coins(deps: DepsMut, coins: Vec) -> StdResult { - ALLOWED_COINS.save(deps.storage, &coins)?; - Ok(Response::new()) -} - -pub fn set_vault_config(deps: DepsMut, address: &str, config: VaultConfig) -> StdResult { - VAULT_CONFIGS.save(deps.storage, &Addr::unchecked(address), &config)?; - Ok(Response::new()) -} diff --git a/contracts/mock-credit-manager/src/msg.rs b/contracts/mock-credit-manager/src/msg.rs index 21e50cd51..1140c22d0 100644 --- a/contracts/mock-credit-manager/src/msg.rs +++ b/contracts/mock-credit-manager/src/msg.rs @@ -1,8 +1,5 @@ use cosmwasm_schema::cw_serde; -use mars_rover::{ - adapters::vault::VaultConfig, - msg::query::{ConfigResponse, Positions}, -}; +use mars_rover::msg::query::{ConfigResponse, Positions}; #[cw_serde] pub struct InstantiateMsg { @@ -15,9 +12,4 @@ pub enum ExecuteMsg { account_id: String, positions: Positions, }, - SetAllowedCoins(Vec), - SetVaultConfig { - address: String, - config: VaultConfig, - }, } diff --git a/contracts/mock-credit-manager/src/query.rs b/contracts/mock-credit-manager/src/query.rs index 53da1d29a..db9285069 100644 --- a/contracts/mock-credit-manager/src/query.rs +++ b/contracts/mock-credit-manager/src/query.rs @@ -1,10 +1,7 @@ use cosmwasm_std::{Deps, StdResult}; -use mars_rover::{ - adapters::vault::VaultUnchecked, - msg::query::{ConfigResponse, Positions, VaultConfigResponse}, -}; +use mars_rover::msg::query::{ConfigResponse, Positions}; -use crate::state::{ALLOWED_COINS, CONFIG, POSITION_RESPONSES, VAULT_CONFIGS}; +use crate::state::{CONFIG, POSITION_RESPONSES}; pub fn query_positions(deps: Deps, account_id: String) -> StdResult { POSITION_RESPONSES.load(deps.storage, &account_id) @@ -13,16 +10,3 @@ pub fn query_positions(deps: Deps, account_id: String) -> StdResult { pub fn query_config(deps: Deps) -> StdResult { CONFIG.load(deps.storage) } - -pub fn query_allowed_coins(deps: Deps) -> StdResult> { - ALLOWED_COINS.load(deps.storage) -} - -pub fn query_vault_config(deps: Deps, vault: VaultUnchecked) -> StdResult { - let validated = deps.api.addr_validate(&vault.address)?; - let config = VAULT_CONFIGS.load(deps.storage, &validated)?; - Ok(VaultConfigResponse { - config, - vault: VaultUnchecked::new(validated.to_string()), - }) -} diff --git a/contracts/mock-credit-manager/src/state.rs b/contracts/mock-credit-manager/src/state.rs index 6f314337a..473991b65 100644 --- a/contracts/mock-credit-manager/src/state.rs +++ b/contracts/mock-credit-manager/src/state.rs @@ -1,12 +1,6 @@ -use cosmwasm_std::Addr; use cw_storage_plus::{Item, Map}; -use mars_rover::{ - adapters::vault::VaultConfig, - msg::query::{ConfigResponse, Positions}, -}; +use mars_rover::msg::query::{ConfigResponse, Positions}; pub const CONFIG: Item = Item::new("config"); -pub const ALLOWED_COINS: Item> = Item::new("allowed_coins"); // Vec -pub const VAULT_CONFIGS: Map<&Addr, VaultConfig> = Map::new("vault_configs"); pub const POSITION_RESPONSES: Map<&str, Positions> = Map::new("position_responses"); // Map diff --git a/contracts/mock-red-bank/examples/schema.rs b/contracts/mock-red-bank/examples/schema.rs index a1e7c40b8..5ffb204e9 100644 --- a/contracts/mock-red-bank/examples/schema.rs +++ b/contracts/mock-red-bank/examples/schema.rs @@ -1,10 +1,10 @@ use cosmwasm_schema::write_api; -use mars_mock_red_bank::msg::InstantiateMsg; +use cosmwasm_std::Empty; use mars_red_bank_types::red_bank::{ExecuteMsg, QueryMsg}; fn main() { write_api! { - instantiate: InstantiateMsg, + instantiate: Empty, query: QueryMsg, execute: ExecuteMsg, } diff --git a/contracts/mock-red-bank/src/contract.rs b/contracts/mock-red-bank/src/contract.rs index fc17d3c36..0150a5d1e 100644 --- a/contracts/mock-red-bank/src/contract.rs +++ b/contracts/mock-red-bank/src/contract.rs @@ -1,25 +1,22 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +}; use mars_red_bank_types::red_bank; use crate::{ - execute::{borrow, deposit, repay, update_asset, withdraw}, - msg::InstantiateMsg, - query::{query_collateral, query_debt, query_market}, - state::COIN_MARKET_INFO, + execute::{borrow, deposit, repay, withdraw}, + query::{query_collateral, query_debt}, }; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - deps: DepsMut, + _deps: DepsMut, _env: Env, _info: MessageInfo, - msg: InstantiateMsg, + _msg: Empty, ) -> StdResult { - for item in msg.coins { - COIN_MARKET_INFO.save(deps.storage, item.denom.clone(), &item)?; - } Ok(Response::default()) } @@ -47,10 +44,6 @@ pub fn execute( amount, .. } => withdraw(deps, info, &denom, &amount), - red_bank::ExecuteMsg::UpdateAsset { - denom, - params, - } => update_asset(deps, &denom, params), _ => unimplemented!("Msg not supported!"), } } @@ -62,9 +55,6 @@ pub fn query(deps: Deps, _env: Env, msg: red_bank::QueryMsg) -> StdResult to_binary(&query_debt(deps, user, denom)?), - red_bank::QueryMsg::Market { - denom, - } => to_binary(&query_market(deps, denom)?), red_bank::QueryMsg::UserCollateral { user, denom, diff --git a/contracts/mock-red-bank/src/execute.rs b/contracts/mock-red-bank/src/execute.rs index 2e61a1b45..29ab3d054 100644 --- a/contracts/mock-red-bank/src/execute.rs +++ b/contracts/mock-red-bank/src/execute.rs @@ -1,13 +1,11 @@ use cosmwasm_std::{ - coin, BankMsg, CosmosMsg, Decimal, DepsMut, MessageInfo, Response, StdError, StdResult, Uint128, + coin, BankMsg, CosmosMsg, DepsMut, MessageInfo, Response, StdError, StdResult, Uint128, }; use cw_utils::one_coin; -use mars_red_bank_types::red_bank::InitOrUpdateAssetParams; use crate::{ helpers::{load_collateral_amount, load_debt_amount, load_lent_amount}, - msg::CoinMarketInfo, - state::{COIN_MARKET_INFO, COLLATERAL_AMOUNT, DEBT_AMOUNT}, + state::{COLLATERAL_AMOUNT, DEBT_AMOUNT}, }; pub fn borrow( @@ -82,22 +80,3 @@ pub fn withdraw( Ok(Response::new().add_message(transfer_msg)) } - -pub fn update_asset( - deps: DepsMut, - denom: &str, - params: InitOrUpdateAssetParams, -) -> StdResult { - COIN_MARKET_INFO.save( - deps.storage, - denom.to_string(), - &CoinMarketInfo { - denom: denom.to_string(), - max_ltv: params.max_loan_to_value.unwrap_or(Decimal::zero()), - liquidation_threshold: params.liquidation_threshold.unwrap_or(Decimal::zero()), - liquidation_bonus: params.liquidation_bonus.unwrap_or(Decimal::zero()), - }, - )?; - - Ok(Response::new()) -} diff --git a/contracts/mock-red-bank/src/lib.rs b/contracts/mock-red-bank/src/lib.rs index 5e9df43e4..250040432 100644 --- a/contracts/mock-red-bank/src/lib.rs +++ b/contracts/mock-red-bank/src/lib.rs @@ -2,6 +2,5 @@ pub mod contract; pub mod execute; pub mod helpers; -pub mod msg; pub mod query; pub mod state; diff --git a/contracts/mock-red-bank/src/msg.rs b/contracts/mock-red-bank/src/msg.rs deleted file mode 100644 index e024cdada..000000000 --- a/contracts/mock-red-bank/src/msg.rs +++ /dev/null @@ -1,15 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Decimal; - -#[cw_serde] -pub struct InstantiateMsg { - pub coins: Vec, -} - -#[cw_serde] -pub struct CoinMarketInfo { - pub denom: String, - pub max_ltv: Decimal, - pub liquidation_threshold: Decimal, - pub liquidation_bonus: Decimal, -} diff --git a/contracts/mock-red-bank/src/query.rs b/contracts/mock-red-bank/src/query.rs index 9ba5a7d29..9302b1784 100644 --- a/contracts/mock-red-bank/src/query.rs +++ b/contracts/mock-red-bank/src/query.rs @@ -1,10 +1,7 @@ use cosmwasm_std::{Deps, StdResult, Uint128}; -use mars_red_bank_types::red_bank::{Market, UserCollateralResponse, UserDebtResponse}; +use mars_red_bank_types::red_bank::{UserCollateralResponse, UserDebtResponse}; -use crate::{ - helpers::{load_collateral_amount, load_debt_amount}, - state::COIN_MARKET_INFO, -}; +use crate::helpers::{load_collateral_amount, load_debt_amount}; pub fn query_debt(deps: Deps, user: String, denom: String) -> StdResult { let user_addr = deps.api.addr_validate(&user)?; @@ -17,16 +14,6 @@ pub fn query_debt(deps: Deps, user: String, denom: String) -> StdResult StdResult { - let market_info = COIN_MARKET_INFO.load(deps.storage, denom)?; - Ok(Market { - max_loan_to_value: market_info.max_ltv, - liquidation_threshold: market_info.liquidation_threshold, - liquidation_bonus: market_info.liquidation_bonus, - ..Default::default() - }) -} - pub fn query_collateral( deps: Deps, user: String, diff --git a/contracts/mock-red-bank/src/state.rs b/contracts/mock-red-bank/src/state.rs index b006b1b40..edc30820a 100644 --- a/contracts/mock-red-bank/src/state.rs +++ b/contracts/mock-red-bank/src/state.rs @@ -1,11 +1,7 @@ use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::Map; -use crate::msg::CoinMarketInfo; - // Map<(DebtHolder, CoinDenom), AmountOfDebt> pub const DEBT_AMOUNT: Map<(Addr, String), Uint128> = Map::new("debt_amount"); // Map<(CollateralHolder, CoinDenom), AmountOfCollateral> pub const COLLATERAL_AMOUNT: Map<(Addr, String), Uint128> = Map::new("collateral_amount"); -// Map -pub const COIN_MARKET_INFO: Map = Map::new("coin_market_info"); diff --git a/contracts/swapper/mock/Cargo.toml b/contracts/swapper/mock/Cargo.toml index 6c73a6207..7892f6183 100644 --- a/contracts/swapper/mock/Cargo.toml +++ b/contracts/swapper/mock/Cargo.toml @@ -23,7 +23,3 @@ cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } mars-rover = { workspace = true } thiserror = { workspace = true } - -[dev-dependencies] -anyhow = { workspace = true } -cw-multi-test = { workspace = true } diff --git a/contracts/swapper/osmosis/Cargo.toml b/contracts/swapper/osmosis/Cargo.toml index d73b265c6..7d209e8a3 100644 --- a/contracts/swapper/osmosis/Cargo.toml +++ b/contracts/swapper/osmosis/Cargo.toml @@ -32,5 +32,4 @@ schemars = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -anyhow = { workspace = true } osmosis-test-tube = { workspace = true } diff --git a/contracts/v3-zapper/osmosis/Cargo.toml b/contracts/v3-zapper/osmosis/Cargo.toml index 9025fd530..78a01350e 100644 --- a/contracts/v3-zapper/osmosis/Cargo.toml +++ b/contracts/v3-zapper/osmosis/Cargo.toml @@ -26,5 +26,4 @@ osmosis-std = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -cw-utils = { workspace = true } osmosis-test-tube = { workspace = true } diff --git a/packages/health-computer/Cargo.toml b/packages/health-computer/Cargo.toml index c415b6259..10747830c 100644 --- a/packages/health-computer/Cargo.toml +++ b/packages/health-computer/Cargo.toml @@ -19,7 +19,7 @@ javascript = [] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-vault-standard = { workspace = true } -mars-red-bank-types = { workspace = true } +mars-params = { workspace = true } mars-rover = { workspace = true } mars-rover-health-types = { workspace = true } schemars = { workspace = true } diff --git a/packages/health-computer/src/data_types.rs b/packages/health-computer/src/data_types.rs index c779c98be..32c550f74 100644 --- a/packages/health-computer/src/data_types.rs +++ b/packages/health-computer/src/data_types.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Decimal, Uint128}; -use mars_red_bank_types::red_bank::Market; -use mars_rover::adapters::vault::{VaultConfig, VaultPositionValue}; +use mars_params::types::{AssetParams, VaultConfig}; +use mars_rover::adapters::vault::VaultPositionValue; /// Used as storage when trying to compute Health #[cw_serde] @@ -18,7 +18,7 @@ pub struct CollateralValue { pub struct DenomsData { /// Must include data from info.base token for the vaults pub prices: HashMap, - pub markets: HashMap, + pub params: HashMap, } #[cw_serde] diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index 7685842ed..33cbfbf95 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -1,10 +1,10 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Decimal, Uint128}; -use mars_red_bank_types::red_bank::Market; +use mars_params::types::AssetParams; use mars_rover::{msg::query::Positions, traits::Coins}; use mars_rover_health_types::{ Health, - HealthError::{MissingMarket, MissingPrice, MissingVaultConfig, MissingVaultValues}, + HealthError::{MissingParams, MissingPrice, MissingVaultConfig, MissingVaultValues}, HealthResult, }; @@ -17,7 +17,6 @@ pub struct HealthComputer { pub positions: Positions, pub denoms_data: DenomsData, pub vaults_data: VaultsData, - pub allowed_coins: Vec, } impl HealthComputer { @@ -98,15 +97,16 @@ impl HealthComputer { let coin_value = c.amount.checked_mul_floor(*coin_price)?; total_collateral_value = total_collateral_value.checked_add(coin_value)?; - let &Market { + let AssetParams { + rover, max_loan_to_value, liquidation_threshold, .. - } = self.denoms_data.markets.get(&c.denom).ok_or(MissingMarket(c.denom.clone()))?; + } = self.denoms_data.params.get(&c.denom).ok_or(MissingParams(c.denom.clone()))?; // If coin has been de-listed, drop MaxLTV to zero - let checked_max_ltv = if self.allowed_coins.contains(&c.denom) { - max_loan_to_value + let checked_max_ltv = if rover.whitelisted { + *max_loan_to_value } else { Decimal::zero() }; @@ -114,7 +114,7 @@ impl HealthComputer { max_ltv_adjusted_collateral = max_ltv_adjusted_collateral.checked_add(max_ltv_adjusted)?; - let liq_adjusted = coin_value.checked_mul_floor(liquidation_threshold)?; + let liq_adjusted = coin_value.checked_mul_floor(*liquidation_threshold)?; liquidation_threshold_adjusted_collateral = liquidation_threshold_adjusted_collateral.checked_add(liq_adjusted)?; } @@ -146,9 +146,19 @@ impl HealthComputer { .ok_or(MissingVaultConfig(v.vault.address.to_string()))?; // If vault or base token has been de-listed, drop MaxLTV to zero - let base_token_whitelisted = self.allowed_coins.contains(&values.base_coin.denom); + let AssetParams { + rover, + max_loan_to_value, + liquidation_threshold, + .. + } = self + .denoms_data + .params + .get(&values.base_coin.denom) + .ok_or(MissingParams(values.base_coin.denom.clone()))?; + let base_token_whitelisted = rover.whitelisted; let checked_vault_max_ltv = if config.whitelisted && base_token_whitelisted { - config.max_ltv + config.max_loan_to_value } else { Decimal::zero() }; @@ -165,19 +175,9 @@ impl HealthComputer { .checked_mul_floor(config.liquidation_threshold)? .checked_add(liquidation_threshold_adjusted_collateral)?; - let &Market { - max_loan_to_value, - liquidation_threshold, - .. - } = self - .denoms_data - .markets - .get(&values.base_coin.denom) - .ok_or(MissingMarket(values.base_coin.denom.clone()))?; - // If base token has been de-listed, drop MaxLTV to zero let checked_base_max_ltv = if base_token_whitelisted { - max_loan_to_value + *max_loan_to_value } else { Decimal::zero() }; @@ -191,7 +191,7 @@ impl HealthComputer { liquidation_threshold_adjusted_collateral = values .base_coin .value - .checked_mul_floor(liquidation_threshold)? + .checked_mul_floor(*liquidation_threshold)? .checked_add(liquidation_threshold_adjusted_collateral)?; } diff --git a/packages/health-computer/tests/helpers/mock_coin_info.rs b/packages/health-computer/tests/helpers/mock_coin_info.rs index 4976dd249..cf958264d 100644 --- a/packages/health-computer/tests/helpers/mock_coin_info.rs +++ b/packages/health-computer/tests/helpers/mock_coin_info.rs @@ -1,74 +1,140 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Decimal; -use mars_red_bank_types::red_bank::Market; +use mars_params::types::{AssetParams, HighLeverageStrategyParams, RedBankSettings, RoverSettings}; #[cw_serde] pub struct CoinInfo { + pub denom: String, pub price: Decimal, - pub market: Market, + pub params: AssetParams, } pub fn umars_info() -> CoinInfo { + let denom = "umars".to_string(); CoinInfo { + denom: denom.clone(), price: Decimal::from_atomics(1u128, 0).unwrap(), - market: Market { - denom: "umars".to_string(), + params: AssetParams { + denom, max_loan_to_value: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(84u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), - ..Default::default() + rover: RoverSettings { + whitelisted: true, + hls: HighLeverageStrategyParams { + max_loan_to_value: Default::default(), + liquidation_threshold: Default::default(), + }, + }, + red_bank: RedBankSettings { + deposit_enabled: true, + borrow_enabled: true, + deposit_cap: Default::default(), + }, }, } } pub fn udai_info() -> CoinInfo { + let denom = "udai".to_string(); CoinInfo { + denom, price: Decimal::from_atomics(313451u128, 6).unwrap(), - market: Market { + params: AssetParams { denom: "udai".to_string(), max_loan_to_value: Decimal::from_atomics(85u128, 2).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), - ..Default::default() + rover: RoverSettings { + whitelisted: true, + hls: HighLeverageStrategyParams { + max_loan_to_value: Default::default(), + liquidation_threshold: Default::default(), + }, + }, + red_bank: RedBankSettings { + deposit_enabled: true, + borrow_enabled: true, + deposit_cap: Default::default(), + }, }, } } pub fn uluna_info() -> CoinInfo { + let denom = "uluna".to_string(); CoinInfo { + denom: denom.clone(), price: Decimal::from_atomics(100u128, 1).unwrap(), - market: Market { - denom: "uluna".to_string(), + params: AssetParams { + denom, max_loan_to_value: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), - ..Default::default() + rover: RoverSettings { + whitelisted: true, + hls: HighLeverageStrategyParams { + max_loan_to_value: Default::default(), + liquidation_threshold: Default::default(), + }, + }, + red_bank: RedBankSettings { + deposit_enabled: true, + borrow_enabled: true, + deposit_cap: Default::default(), + }, }, } } pub fn ustars_info() -> CoinInfo { + let denom = "ustars".to_string(); CoinInfo { + denom: denom.clone(), price: Decimal::from_atomics(5265478965412365487125u128, 12).unwrap(), - market: Market { - denom: "ustars".to_string(), + params: AssetParams { + denom, max_loan_to_value: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), - ..Default::default() + rover: RoverSettings { + whitelisted: true, + hls: HighLeverageStrategyParams { + max_loan_to_value: Default::default(), + liquidation_threshold: Default::default(), + }, + }, + red_bank: RedBankSettings { + deposit_enabled: true, + borrow_enabled: true, + deposit_cap: Default::default(), + }, }, } } pub fn ujuno_info() -> CoinInfo { + let denom = "ujuno".to_string(); CoinInfo { + denom: denom.clone(), price: Decimal::from_atomics(7012302005u128, 3).unwrap(), - market: Market { - denom: "ujuno".to_string(), + params: AssetParams { + denom, max_loan_to_value: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), - ..Default::default() + rover: RoverSettings { + whitelisted: true, + hls: HighLeverageStrategyParams { + max_loan_to_value: Default::default(), + liquidation_threshold: Default::default(), + }, + }, + red_bank: RedBankSettings { + deposit_enabled: true, + borrow_enabled: true, + deposit_cap: Default::default(), + }, }, } } diff --git a/packages/health-computer/tests/test_health_scenarios.rs b/packages/health-computer/tests/test_health_scenarios.rs index 6449fa56b..0d03eb4af 100644 --- a/packages/health-computer/tests/test_health_scenarios.rs +++ b/packages/health-computer/tests/test_health_scenarios.rs @@ -1,10 +1,11 @@ use std::{collections::HashMap, ops::Add, str::FromStr}; use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; +use mars_params::types::VaultConfig; use mars_rover::{ adapters::vault::{ - CoinValue, LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultConfig, - VaultPosition, VaultPositionAmount, VaultPositionValue, VaultUnlockingPosition, + CoinValue, LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultPosition, + VaultPositionAmount, VaultPositionValue, VaultUnlockingPosition, }, msg::query::{DebtAmount, LentAmount, Positions}, }; @@ -24,8 +25,8 @@ fn only_assets_with_no_debts() { let umars = umars_info(); let denoms_data = DenomsData { - prices: HashMap::from([(umars.market.denom.clone(), umars.price)]), - markets: HashMap::from([(umars.market.denom.clone(), umars.market.clone())]), + prices: HashMap::from([(umars.denom.clone(), umars.price)]), + params: HashMap::from([(umars.denom.clone(), umars.params.clone())]), }; let vaults_data = VaultsData { @@ -38,7 +39,7 @@ fn only_assets_with_no_debts() { positions: Positions { account_id: "123".to_string(), deposits: vec![Coin { - denom: umars.market.denom.clone(), + denom: umars.denom.clone(), amount: deposit_amount, }], debts: vec![], @@ -47,7 +48,6 @@ fn only_assets_with_no_debts() { }, denoms_data, vaults_data, - allowed_coins: vec![umars.market.denom.clone()], }; let health = h.compute_health().unwrap(); @@ -55,11 +55,11 @@ fn only_assets_with_no_debts() { assert_eq!(health.total_collateral_value, collateral_value); assert_eq!( health.max_ltv_adjusted_collateral, - collateral_value.checked_mul_floor(umars.market.max_loan_to_value).unwrap() + collateral_value.checked_mul_floor(umars.params.max_loan_to_value).unwrap() ); assert_eq!( health.liquidation_threshold_adjusted_collateral, - collateral_value.checked_mul_floor(umars.market.liquidation_threshold).unwrap() + collateral_value.checked_mul_floor(umars.params.liquidation_threshold).unwrap() ); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.liquidation_health_factor, None); @@ -83,8 +83,8 @@ fn terra_ragnarok() { let mut uluna = uluna_info(); let denoms_data = DenomsData { - prices: HashMap::from([(uluna.market.denom.clone(), uluna.price)]), - markets: HashMap::from([(uluna.market.denom.clone(), uluna.market.clone())]), + prices: HashMap::from([(uluna.denom.clone(), uluna.price)]), + params: HashMap::from([(uluna.denom.clone(), uluna.params.clone())]), }; let vaults_data = VaultsData { @@ -99,11 +99,11 @@ fn terra_ragnarok() { positions: Positions { account_id: "123".to_string(), deposits: vec![Coin { - denom: uluna.market.denom.clone(), + denom: uluna.denom.clone(), amount: deposit_amount, }], debts: vec![DebtAmount { - denom: uluna.market.denom.clone(), + denom: uluna.denom.clone(), amount: borrow_amount, shares: Uint128::new(100), }], @@ -112,7 +112,6 @@ fn terra_ragnarok() { }, denoms_data, vaults_data: vaults_data.clone(), - allowed_coins: vec![uluna.market.denom.clone()], }; let health = h.compute_health().unwrap(); @@ -122,24 +121,24 @@ fn terra_ragnarok() { assert_eq!(health.total_collateral_value, collateral_value); assert_eq!( health.max_ltv_adjusted_collateral, - collateral_value.checked_mul_floor(uluna.market.max_loan_to_value).unwrap() + collateral_value.checked_mul_floor(uluna.params.max_loan_to_value).unwrap() ); assert_eq!( health.liquidation_threshold_adjusted_collateral, - collateral_value.checked_mul_floor(uluna.market.liquidation_threshold).unwrap() + collateral_value.checked_mul_floor(uluna.params.liquidation_threshold).unwrap() ); assert_eq!(health.total_debt_value, borrow_amount.checked_mul_floor(uluna.price).unwrap()); assert_eq!( health.liquidation_health_factor, Some(Decimal::from_ratio( - collateral_value.checked_mul_floor(uluna.market.liquidation_threshold).unwrap(), + collateral_value.checked_mul_floor(uluna.params.liquidation_threshold).unwrap(), debts_value )) ); assert_eq!( health.max_ltv_health_factor, Some(Decimal::from_ratio( - collateral_value.checked_mul_floor(uluna.market.max_loan_to_value).unwrap(), + collateral_value.checked_mul_floor(uluna.params.max_loan_to_value).unwrap(), debts_value, )) ); @@ -150,19 +149,19 @@ fn terra_ragnarok() { uluna.price = Decimal::zero(); let denoms_data = DenomsData { - prices: HashMap::from([(uluna.market.denom.clone(), uluna.price)]), - markets: HashMap::from([(uluna.market.denom.clone(), uluna.market.clone())]), + prices: HashMap::from([(uluna.denom.clone(), uluna.price)]), + params: HashMap::from([(uluna.denom.clone(), uluna.params.clone())]), }; let h = HealthComputer { positions: Positions { account_id: "123".to_string(), deposits: vec![Coin { - denom: uluna.market.denom.clone(), + denom: uluna.denom.clone(), amount: deposit_amount, }], debts: vec![DebtAmount { - denom: uluna.market.denom.clone(), + denom: uluna.denom, amount: borrow_amount, shares: Uint128::new(100), }], @@ -171,7 +170,6 @@ fn terra_ragnarok() { }, denoms_data, vaults_data, - allowed_coins: vec![uluna.market.denom], }; let health = h.compute_health().unwrap(); @@ -198,12 +196,12 @@ fn ltv_and_lqdt_adjusted_values() { let denoms_data = DenomsData { prices: HashMap::from([ - (ustars.market.denom.clone(), ustars.price), - (ujuno.market.denom.clone(), ujuno.price), + (ustars.denom.clone(), ustars.price), + (ujuno.denom.clone(), ujuno.price), ]), - markets: HashMap::from([ - (ustars.market.denom.clone(), ustars.market.clone()), - (ujuno.market.denom.clone(), ujuno.market.clone()), + params: HashMap::from([ + (ustars.denom.clone(), ustars.params.clone()), + (ujuno.denom.clone(), ujuno.params.clone()), ]), }; @@ -220,16 +218,16 @@ fn ltv_and_lqdt_adjusted_values() { account_id: "123".to_string(), deposits: vec![ Coin { - denom: ustars.market.denom.clone(), + denom: ustars.denom.clone(), amount: deposit_amount, }, Coin { - denom: ujuno.market.denom.clone(), + denom: ujuno.denom.clone(), amount: borrow_amount, }, ], debts: vec![DebtAmount { - denom: ujuno.market.denom.clone(), + denom: ujuno.denom.clone(), shares: Uint128::new(12345), amount: borrow_amount.add(Uint128::one()), // simulated interest }], @@ -238,7 +236,6 @@ fn ltv_and_lqdt_adjusted_values() { }, denoms_data, vaults_data, - allowed_coins: vec![ustars.market.denom.clone(), ujuno.market.denom.clone()], }; let health = h.compute_health().unwrap(); @@ -256,13 +253,13 @@ fn ltv_and_lqdt_adjusted_values() { let lqdt_adjusted_assets_value = deposit_amount .checked_mul_floor(ustars.price) .unwrap() - .checked_mul_floor(ustars.market.liquidation_threshold) + .checked_mul_floor(ustars.params.liquidation_threshold) .unwrap() .add( borrow_amount .checked_mul_floor(ujuno.price) .unwrap() - .checked_mul_floor(ujuno.market.liquidation_threshold) + .checked_mul_floor(ujuno.params.liquidation_threshold) .unwrap(), ); assert_eq!( @@ -275,13 +272,13 @@ fn ltv_and_lqdt_adjusted_values() { let ltv_adjusted_assets_value = deposit_amount .checked_mul_floor(ustars.price) .unwrap() - .checked_mul_floor(ustars.market.max_loan_to_value) + .checked_mul_floor(ustars.params.max_loan_to_value) .unwrap() .add( borrow_amount .checked_mul_floor(ujuno.price) .unwrap() - .checked_mul_floor(ujuno.market.max_loan_to_value) + .checked_mul_floor(ujuno.params.max_loan_to_value) .unwrap(), ); assert_eq!( @@ -306,12 +303,12 @@ fn debt_value() { let denoms_data = DenomsData { prices: HashMap::from([ - (ustars.market.denom.clone(), ustars.price), - (ujuno.market.denom.clone(), ujuno.price), + (ustars.denom.clone(), ustars.price), + (ujuno.denom.clone(), ujuno.price), ]), - markets: HashMap::from([ - (ustars.market.denom.clone(), ustars.market.clone()), - (ujuno.market.denom.clone(), ujuno.market.clone()), + params: HashMap::from([ + (ustars.denom.clone(), ustars.params.clone()), + (ujuno.denom.clone(), ujuno.params.clone()), ]), }; @@ -329,26 +326,26 @@ fn debt_value() { account_id: "123".to_string(), deposits: vec![ Coin { - denom: ustars.market.denom.clone(), + denom: ustars.denom.clone(), amount: deposit_amount_stars, }, Coin { - denom: ujuno.market.denom.clone(), + denom: ujuno.denom.clone(), amount: borrowed_amount_juno, }, Coin { - denom: ustars.market.denom.clone(), + denom: ustars.denom.clone(), amount: borrowed_amount_stars, }, ], debts: vec![ DebtAmount { - denom: ujuno.market.denom.clone(), + denom: ujuno.denom.clone(), shares: Uint128::new(12345), amount: borrowed_amount_juno.add(Uint128::one()), // simulated interest }, DebtAmount { - denom: ustars.market.denom.clone(), + denom: ustars.denom.clone(), shares: Uint128::new(12345), amount: borrowed_amount_stars.add(Uint128::one()), // simulated interest }, @@ -358,7 +355,6 @@ fn debt_value() { }, denoms_data, vaults_data, - allowed_coins: vec![ustars.market.denom.clone(), ujuno.market.denom.clone()], }; let health = h.compute_health().unwrap(); @@ -378,20 +374,20 @@ fn debt_value() { let lqdt_adjusted_assets_value = deposit_amount_stars .checked_mul_floor(ustars.price) .unwrap() - .checked_mul_floor(ustars.market.liquidation_threshold) + .checked_mul_floor(ustars.params.liquidation_threshold) .unwrap() .add( borrowed_amount_stars .checked_mul_floor(ustars.price) .unwrap() - .checked_mul_floor(ustars.market.liquidation_threshold) + .checked_mul_floor(ustars.params.liquidation_threshold) .unwrap(), ) .add( borrowed_amount_juno .checked_mul_floor(ujuno.price) .unwrap() - .checked_mul_floor(ujuno.market.liquidation_threshold) + .checked_mul_floor(ujuno.params.liquidation_threshold) .unwrap(), ); @@ -403,20 +399,20 @@ fn debt_value() { let max_ltv_adjusted_assets_value = deposit_amount_stars .checked_mul_floor(ustars.price) .unwrap() - .checked_mul_floor(ustars.market.max_loan_to_value) + .checked_mul_floor(ustars.params.max_loan_to_value) .unwrap() .add( borrowed_amount_stars .checked_mul_floor(ustars.price) .unwrap() - .checked_mul_floor(ustars.market.max_loan_to_value) + .checked_mul_floor(ustars.params.max_loan_to_value) .unwrap(), ) .add( borrowed_amount_juno .checked_mul_floor(ujuno.price) .unwrap() - .checked_mul_floor(ujuno.market.max_loan_to_value) + .checked_mul_floor(ujuno.params.max_loan_to_value) .unwrap(), ); assert_eq!( @@ -432,12 +428,12 @@ fn above_max_ltv_below_liq_threshold() { let denoms_data = DenomsData { prices: HashMap::from([ - (umars.market.denom.clone(), umars.price), - (udai.market.denom.clone(), udai.price), + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), ]), - markets: HashMap::from([ - (umars.market.denom.clone(), umars.market.clone()), - (udai.market.denom.clone(), udai.market.clone()), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), ]), }; @@ -449,9 +445,9 @@ fn above_max_ltv_below_liq_threshold() { let h = HealthComputer { positions: Positions { account_id: "123".to_string(), - deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], debts: vec![DebtAmount { - denom: udai.market.denom.clone(), + denom: udai.denom, shares: Default::default(), amount: Uint128::new(3100), }], @@ -460,7 +456,6 @@ fn above_max_ltv_below_liq_threshold() { }, denoms_data, vaults_data, - allowed_coins: vec![umars.market.denom, udai.market.denom], }; let health = h.compute_health().unwrap(); @@ -487,12 +482,12 @@ fn liquidatable() { let denoms_data = DenomsData { prices: HashMap::from([ - (umars.market.denom.clone(), umars.price), - (udai.market.denom.clone(), udai.price), + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), ]), - markets: HashMap::from([ - (umars.market.denom.clone(), umars.market.clone()), - (udai.market.denom.clone(), udai.market.clone()), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), ]), }; @@ -504,15 +499,15 @@ fn liquidatable() { let h = HealthComputer { positions: Positions { account_id: "123".to_string(), - deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], debts: vec![ DebtAmount { - denom: udai.market.denom.clone(), + denom: udai.denom, shares: Default::default(), amount: Uint128::new(3100), }, DebtAmount { - denom: umars.market.denom.clone(), + denom: umars.denom, shares: Default::default(), amount: Uint128::new(200), }, @@ -522,7 +517,6 @@ fn liquidatable() { }, denoms_data, vaults_data, - allowed_coins: vec![umars.market.denom, udai.market.denom], }; let health = h.compute_health().unwrap(); @@ -543,18 +537,20 @@ fn liquidatable() { } #[test] -fn allowed_coins_influence_max_ltv() { +fn rover_whitelist_influences_max_ltv() { let umars = umars_info(); - let udai = udai_info(); + let mut udai = udai_info(); + + udai.params.rover.whitelisted = false; let denoms_data = DenomsData { prices: HashMap::from([ - (umars.market.denom.clone(), umars.price), - (udai.market.denom.clone(), udai.price), + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), ]), - markets: HashMap::from([ - (umars.market.denom.clone(), umars.market.clone()), - (udai.market.denom.clone(), udai.market.clone()), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), ]), }; @@ -566,15 +562,15 @@ fn allowed_coins_influence_max_ltv() { let h = HealthComputer { positions: Positions { account_id: "123".to_string(), - deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], debts: vec![ DebtAmount { - denom: udai.market.denom, + denom: udai.denom, shares: Default::default(), amount: Uint128::new(3100), }, DebtAmount { - denom: umars.market.denom.clone(), + denom: umars.denom, shares: Default::default(), amount: Uint128::new(200), }, @@ -584,7 +580,6 @@ fn allowed_coins_influence_max_ltv() { }, denoms_data, vaults_data, - allowed_coins: vec![umars.market.denom], }; let health = h.compute_health().unwrap(); @@ -611,12 +606,12 @@ fn unlocked_vault() { let denoms_data = DenomsData { prices: HashMap::from([ - (umars.market.denom.clone(), umars.price), - (udai.market.denom.clone(), udai.price), + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), ]), - markets: HashMap::from([ - (umars.market.denom.clone(), umars.market.clone()), - (udai.market.denom.clone(), udai.market.clone()), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), ]), }; @@ -632,7 +627,7 @@ fn unlocked_vault() { value: Uint128::new(5264), }, base_coin: CoinValue { - denom: udai.market.denom.clone(), + denom: udai.denom.clone(), amount: Default::default(), value: Default::default(), }, @@ -641,8 +636,9 @@ fn unlocked_vault() { vault_configs: HashMap::from([( vault.address.clone(), VaultConfig { + addr: vault.address.clone(), deposit_cap: Default::default(), - max_ltv: Decimal::from_str("0.4").unwrap(), + max_loan_to_value: Decimal::from_str("0.4").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), whitelisted: true, }, @@ -652,15 +648,15 @@ fn unlocked_vault() { let h = HealthComputer { positions: Positions { account_id: "123".to_string(), - deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], debts: vec![ DebtAmount { - denom: udai.market.denom.clone(), + denom: udai.denom, shares: Default::default(), amount: Uint128::new(3100), }, DebtAmount { - denom: umars.market.denom.clone(), + denom: umars.denom, shares: Default::default(), amount: Uint128::new(200), }, @@ -673,7 +669,6 @@ fn unlocked_vault() { }, denoms_data, vaults_data, - allowed_coins: vec![umars.market.denom, udai.market.denom], }; let health = h.compute_health().unwrap(); @@ -700,12 +695,12 @@ fn locked_vault() { let denoms_data = DenomsData { prices: HashMap::from([ - (umars.market.denom.clone(), umars.price), - (udai.market.denom.clone(), udai.price), + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), ]), - markets: HashMap::from([ - (umars.market.denom.clone(), umars.market.clone()), - (udai.market.denom.clone(), udai.market.clone()), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), ]), }; @@ -721,7 +716,7 @@ fn locked_vault() { value: Uint128::new(5264), }, base_coin: CoinValue { - denom: udai.market.denom.clone(), + denom: udai.denom.clone(), amount: Default::default(), value: Default::default(), }, @@ -730,8 +725,9 @@ fn locked_vault() { vault_configs: HashMap::from([( vault.address.clone(), VaultConfig { + addr: vault.address.clone(), deposit_cap: Default::default(), - max_ltv: Decimal::from_str("0.4").unwrap(), + max_loan_to_value: Decimal::from_str("0.4").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), whitelisted: true, }, @@ -741,15 +737,15 @@ fn locked_vault() { let h = HealthComputer { positions: Positions { account_id: "123".to_string(), - deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], debts: vec![ DebtAmount { - denom: udai.market.denom.clone(), + denom: udai.denom, shares: Default::default(), amount: Uint128::new(3100), }, DebtAmount { - denom: umars.market.denom.clone(), + denom: umars.denom, shares: Default::default(), amount: Uint128::new(200), }, @@ -765,7 +761,6 @@ fn locked_vault() { }, denoms_data, vaults_data, - allowed_coins: vec![umars.market.denom, udai.market.denom], }; let health = h.compute_health().unwrap(); @@ -792,12 +787,12 @@ fn locked_vault_with_unlocking_positions() { let denoms_data = DenomsData { prices: HashMap::from([ - (umars.market.denom.clone(), umars.price), - (udai.market.denom.clone(), udai.price), + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), ]), - markets: HashMap::from([ - (umars.market.denom.clone(), umars.market.clone()), - (udai.market.denom.clone(), udai.market.clone()), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), ]), }; @@ -813,7 +808,7 @@ fn locked_vault_with_unlocking_positions() { value: Uint128::new(5000), }, base_coin: CoinValue { - denom: udai.market.denom.clone(), + denom: udai.denom.clone(), amount: Default::default(), value: Uint128::new(264), }, @@ -822,8 +817,9 @@ fn locked_vault_with_unlocking_positions() { vault_configs: HashMap::from([( vault.address.clone(), VaultConfig { + addr: vault.address.clone(), deposit_cap: Default::default(), - max_ltv: Decimal::from_str("0.4").unwrap(), + max_loan_to_value: Decimal::from_str("0.4").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), whitelisted: true, }, @@ -833,15 +829,15 @@ fn locked_vault_with_unlocking_positions() { let h = HealthComputer { positions: Positions { account_id: "123".to_string(), - deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], debts: vec![ DebtAmount { - denom: udai.market.denom.clone(), + denom: udai.denom.clone(), shares: Default::default(), amount: Uint128::new(3100), }, DebtAmount { - denom: umars.market.denom.clone(), + denom: umars.denom, shares: Default::default(), amount: Uint128::new(200), }, @@ -854,11 +850,11 @@ fn locked_vault_with_unlocking_positions() { unlocking: UnlockingPositions::new(vec![ VaultUnlockingPosition { id: 0, - coin: coin(840, udai.market.denom.clone()), + coin: coin(840, udai.denom.clone()), }, VaultUnlockingPosition { id: 1, - coin: coin(3, udai.market.denom.clone()), + coin: coin(3, udai.denom), }, ]), }), @@ -866,7 +862,6 @@ fn locked_vault_with_unlocking_positions() { }, denoms_data, vaults_data, - allowed_coins: vec![umars.market.denom, udai.market.denom], }; let health = h.compute_health().unwrap(); @@ -893,12 +888,12 @@ fn vault_is_not_whitelisted() { let denoms_data = DenomsData { prices: HashMap::from([ - (umars.market.denom.clone(), umars.price), - (udai.market.denom.clone(), udai.price), + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), ]), - markets: HashMap::from([ - (umars.market.denom.clone(), umars.market.clone()), - (udai.market.denom.clone(), udai.market.clone()), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), ]), }; @@ -914,7 +909,7 @@ fn vault_is_not_whitelisted() { value: Uint128::new(5264), }, base_coin: CoinValue { - denom: udai.market.denom.clone(), + denom: udai.denom.clone(), amount: Default::default(), value: Default::default(), }, @@ -923,8 +918,9 @@ fn vault_is_not_whitelisted() { vault_configs: HashMap::from([( vault.address.clone(), VaultConfig { + addr: vault.address.clone(), deposit_cap: Default::default(), - max_ltv: Decimal::from_str("0.4").unwrap(), + max_loan_to_value: Decimal::from_str("0.4").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), whitelisted: false, }, @@ -934,15 +930,15 @@ fn vault_is_not_whitelisted() { let h = HealthComputer { positions: Positions { account_id: "123".to_string(), - deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], debts: vec![ DebtAmount { - denom: udai.market.denom.clone(), + denom: udai.denom, shares: Default::default(), amount: Uint128::new(3100), }, DebtAmount { - denom: umars.market.denom.clone(), + denom: umars.denom, shares: Default::default(), amount: Uint128::new(200), }, @@ -955,7 +951,6 @@ fn vault_is_not_whitelisted() { }, denoms_data, vaults_data, - allowed_coins: vec![umars.market.denom, udai.market.denom], }; let health = h.compute_health().unwrap(); @@ -980,18 +975,20 @@ fn vault_is_not_whitelisted() { fn vault_base_token_is_not_whitelisted() { let umars = umars_info(); let udai = udai_info(); - let ujuno = ujuno_info(); + let mut ujuno = ujuno_info(); + + ujuno.params.rover.whitelisted = false; let denoms_data = DenomsData { prices: HashMap::from([ - (umars.market.denom.clone(), umars.price), - (udai.market.denom.clone(), udai.price), - (ujuno.market.denom.clone(), ujuno.price), + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), + (ujuno.denom.clone(), ujuno.price), ]), - markets: HashMap::from([ - (umars.market.denom.clone(), umars.market.clone()), - (udai.market.denom.clone(), udai.market.clone()), - (ujuno.market.denom.clone(), ujuno.market.clone()), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + (ujuno.denom.clone(), ujuno.params.clone()), ]), }; @@ -1007,7 +1004,7 @@ fn vault_base_token_is_not_whitelisted() { value: Uint128::new(5000), }, base_coin: CoinValue { - denom: ujuno.market.denom.clone(), + denom: ujuno.denom.clone(), amount: Default::default(), value: Uint128::new(497873442), }, @@ -1016,8 +1013,9 @@ fn vault_base_token_is_not_whitelisted() { vault_configs: HashMap::from([( vault.address.clone(), VaultConfig { + addr: vault.address.clone(), deposit_cap: Default::default(), - max_ltv: Decimal::from_str("0.4").unwrap(), + max_loan_to_value: Decimal::from_str("0.4").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), whitelisted: true, }, @@ -1027,15 +1025,15 @@ fn vault_base_token_is_not_whitelisted() { let h = HealthComputer { positions: Positions { account_id: "123".to_string(), - deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], debts: vec![ DebtAmount { - denom: udai.market.denom.clone(), + denom: udai.denom, shares: Default::default(), amount: Uint128::new(3100), }, DebtAmount { - denom: umars.market.denom.clone(), + denom: umars.denom, shares: Default::default(), amount: Uint128::new(200), }, @@ -1048,11 +1046,11 @@ fn vault_base_token_is_not_whitelisted() { unlocking: UnlockingPositions::new(vec![ VaultUnlockingPosition { id: 0, - coin: coin(60, ujuno.market.denom.clone()), + coin: coin(60, ujuno.denom.clone()), }, VaultUnlockingPosition { id: 1, - coin: coin(11, ujuno.market.denom), + coin: coin(11, ujuno.denom), }, ]), }), @@ -1060,7 +1058,6 @@ fn vault_base_token_is_not_whitelisted() { }, denoms_data, vaults_data, - allowed_coins: vec![umars.market.denom, udai.market.denom], }; let health = h.compute_health().unwrap(); @@ -1088,14 +1085,14 @@ fn lent_coins_used_as_collateral() { let denoms_data = DenomsData { prices: HashMap::from([ - (umars.market.denom.clone(), umars.price), - (udai.market.denom.clone(), udai.price), - (uluna.market.denom.clone(), uluna.price), + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), + (uluna.denom.clone(), uluna.price), ]), - markets: HashMap::from([ - (umars.market.denom.clone(), umars.market.clone()), - (udai.market.denom.clone(), udai.market.clone()), - (uluna.market.denom.clone(), uluna.market.clone()), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + (uluna.denom.clone(), uluna.params.clone()), ]), }; @@ -1107,20 +1104,20 @@ fn lent_coins_used_as_collateral() { let h = HealthComputer { positions: Positions { account_id: "123".to_string(), - deposits: vec![coin(1200, &umars.market.denom), coin(23, &udai.market.denom)], + deposits: vec![coin(1200, &umars.denom), coin(23, &udai.denom)], debts: vec![DebtAmount { - denom: udai.market.denom.clone(), + denom: udai.denom.clone(), shares: Default::default(), amount: Uint128::new(3100), }], lends: vec![ LentAmount { - denom: udai.market.denom.clone(), + denom: udai.denom, shares: Default::default(), amount: Uint128::new(10), }, LentAmount { - denom: uluna.market.denom.clone(), + denom: uluna.denom, shares: Default::default(), amount: Uint128::new(2), }, @@ -1129,7 +1126,6 @@ fn lent_coins_used_as_collateral() { }, denoms_data, vaults_data, - allowed_coins: vec![umars.market.denom, udai.market.denom, uluna.market.denom], }; let health = h.compute_health().unwrap(); @@ -1153,18 +1149,20 @@ fn lent_coins_used_as_collateral() { fn allowed_lent_coins_influence_max_ltv() { let umars = umars_info(); let udai = udai_info(); - let uluna = uluna_info(); + let mut uluna = uluna_info(); + + uluna.params.rover.whitelisted = false; let denoms_data = DenomsData { prices: HashMap::from([ - (umars.market.denom.clone(), umars.price), - (udai.market.denom.clone(), udai.price), - (uluna.market.denom.clone(), uluna.price), + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), + (uluna.denom.clone(), uluna.price), ]), - markets: HashMap::from([ - (umars.market.denom.clone(), umars.market.clone()), - (udai.market.denom.clone(), udai.market.clone()), - (uluna.market.denom.clone(), uluna.market.clone()), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + (uluna.denom.clone(), uluna.params.clone()), ]), }; @@ -1176,20 +1174,20 @@ fn allowed_lent_coins_influence_max_ltv() { let h = HealthComputer { positions: Positions { account_id: "123".to_string(), - deposits: vec![coin(1200, &umars.market.denom), coin(23, &udai.market.denom)], + deposits: vec![coin(1200, &umars.denom), coin(23, &udai.denom)], debts: vec![DebtAmount { - denom: udai.market.denom.clone(), + denom: udai.denom.clone(), shares: Default::default(), amount: Uint128::new(3100), }], lends: vec![ LentAmount { - denom: udai.market.denom.clone(), + denom: udai.denom, shares: Default::default(), amount: Uint128::new(10), }, LentAmount { - denom: uluna.market.denom, + denom: uluna.denom, shares: Default::default(), amount: Uint128::new(2), }, @@ -1198,7 +1196,6 @@ fn allowed_lent_coins_influence_max_ltv() { }, denoms_data, vaults_data, - allowed_coins: vec![umars.market.denom, udai.market.denom], }; let health = h.compute_health().unwrap(); diff --git a/packages/health-computer/tests/test_input_validation.rs b/packages/health-computer/tests/test_input_validation.rs index 6fb62326a..7af3b31a9 100644 --- a/packages/health-computer/tests/test_input_validation.rs +++ b/packages/health-computer/tests/test_input_validation.rs @@ -1,10 +1,10 @@ use std::collections::HashMap; use cosmwasm_std::{coin, Addr, Uint128}; +use mars_params::types::VaultConfig; use mars_rover::{ adapters::vault::{ - CoinValue, Vault, VaultAmount, VaultConfig, VaultPosition, VaultPositionAmount, - VaultPositionValue, + CoinValue, Vault, VaultAmount, VaultPosition, VaultPositionAmount, VaultPositionValue, }, msg::query::{DebtAmount, Positions}, }; @@ -21,10 +21,10 @@ fn missing_price_data() { let udai = udai_info(); let denoms_data = DenomsData { - prices: HashMap::from([(umars.market.denom.clone(), umars.price)]), - markets: HashMap::from([ - (umars.market.denom.clone(), umars.market.clone()), - (udai.market.denom.clone(), udai.market.clone()), + prices: HashMap::from([(umars.denom.clone(), umars.price)]), + params: HashMap::from([ + (umars.denom.clone(), umars.params.clone()), + (udai.denom.clone(), udai.params.clone()), ]), }; @@ -36,15 +36,15 @@ fn missing_price_data() { let h = HealthComputer { positions: Positions { account_id: "123".to_string(), - deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], debts: vec![ DebtAmount { - denom: udai.market.denom.clone(), + denom: udai.denom.clone(), shares: Default::default(), amount: Uint128::new(3100), }, DebtAmount { - denom: umars.market.denom.clone(), + denom: umars.denom, shares: Default::default(), amount: Uint128::new(200), }, @@ -54,24 +54,23 @@ fn missing_price_data() { }, denoms_data, vaults_data, - allowed_coins: vec![umars.market.denom, udai.market.denom.clone()], }; let err: HealthError = h.compute_health().unwrap_err(); - assert_eq!(err, HealthError::MissingPrice(udai.market.denom)) + assert_eq!(err, HealthError::MissingPrice(udai.denom)) } #[test] -fn missing_market_data() { +fn missing_params() { let umars = umars_info(); let udai = udai_info(); let denoms_data = DenomsData { prices: HashMap::from([ - (umars.market.denom.clone(), umars.price), - (udai.market.denom.clone(), udai.price), + (umars.denom.clone(), umars.price), + (udai.denom.clone(), udai.price), ]), - markets: HashMap::from([(udai.market.denom.clone(), udai.market.clone())]), + params: HashMap::from([(udai.denom.clone(), udai.params.clone())]), }; let vaults_data = VaultsData { @@ -82,15 +81,15 @@ fn missing_market_data() { let h = HealthComputer { positions: Positions { account_id: "123".to_string(), - deposits: vec![coin(1200, &umars.market.denom), coin(33, &udai.market.denom)], + deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], debts: vec![ DebtAmount { - denom: udai.market.denom.clone(), + denom: udai.denom, shares: Default::default(), amount: Uint128::new(3100), }, DebtAmount { - denom: umars.market.denom.clone(), + denom: umars.denom.clone(), shares: Default::default(), amount: Uint128::new(200), }, @@ -100,18 +99,17 @@ fn missing_market_data() { }, denoms_data, vaults_data, - allowed_coins: vec![umars.market.denom.clone(), udai.market.denom], }; let err: HealthError = h.compute_health().unwrap_err(); - assert_eq!(err, HealthError::MissingMarket(umars.market.denom)) + assert_eq!(err, HealthError::MissingParams(umars.denom)) } #[test] fn missing_market_data_for_vault_base_token() { let denoms_data = DenomsData { prices: HashMap::default(), - markets: HashMap::default(), + params: HashMap::default(), }; let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); @@ -135,8 +133,9 @@ fn missing_market_data_for_vault_base_token() { vault_configs: HashMap::from([( vault.address.clone(), VaultConfig { + addr: vault.address.clone(), deposit_cap: Default::default(), - max_ltv: Default::default(), + max_loan_to_value: Default::default(), liquidation_threshold: Default::default(), whitelisted: false, }, @@ -156,18 +155,17 @@ fn missing_market_data_for_vault_base_token() { }, denoms_data, vaults_data, - allowed_coins: vec![], }; let err: HealthError = h.compute_health().unwrap_err(); - assert_eq!(err, HealthError::MissingMarket("base_token_xyz".to_string())) + assert_eq!(err, HealthError::MissingParams("base_token_xyz".to_string())) } #[test] fn missing_vault_value() { let denoms_data = DenomsData { prices: HashMap::default(), - markets: HashMap::default(), + params: HashMap::default(), }; let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); @@ -177,8 +175,9 @@ fn missing_vault_value() { vault_configs: HashMap::from([( vault.address.clone(), VaultConfig { + addr: vault.address.clone(), deposit_cap: Default::default(), - max_ltv: Default::default(), + max_loan_to_value: Default::default(), liquidation_threshold: Default::default(), whitelisted: false, }, @@ -198,7 +197,6 @@ fn missing_vault_value() { }, denoms_data, vaults_data, - allowed_coins: vec![], }; let err: HealthError = h.compute_health().unwrap_err(); @@ -209,7 +207,7 @@ fn missing_vault_value() { fn missing_vault_config() { let denoms_data = DenomsData { prices: HashMap::default(), - markets: HashMap::default(), + params: HashMap::default(), }; let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); @@ -246,7 +244,6 @@ fn missing_vault_config() { }, denoms_data, vaults_data, - allowed_coins: vec![], }; let err: HealthError = h.compute_health().unwrap_err(); diff --git a/packages/health-types/src/error.rs b/packages/health-types/src/error.rs index f4cfd17bc..28fa10fdf 100644 --- a/packages/health-types/src/error.rs +++ b/packages/health-types/src/error.rs @@ -12,11 +12,11 @@ pub enum HealthError { #[error("{0}")] CheckedMultiplyFraction(#[from] CheckedMultiplyFractionError), - #[error("The credit manager address has not been set in config")] - CreditManagerNotSet, + #[error("{0} address has not been set in config")] + ContractNotSet(String), - #[error("{0} was not provided a market to compute health with")] - MissingMarket(String), + #[error("{0} was not provided asset params to compute health with")] + MissingParams(String), #[error("{0} was not provided a price to compute health with")] MissingPrice(String), diff --git a/packages/health-types/src/msg.rs b/packages/health-types/src/msg.rs index 86e4defc8..8aad9cd7e 100644 --- a/packages/health-types/src/msg.rs +++ b/packages/health-types/src/msg.rs @@ -13,7 +13,8 @@ pub enum ExecuteMsg { UpdateOwner(OwnerUpdate), /// Update contract config constants UpdateConfig { - credit_manager: String, + credit_manager: Option, + params: Option, }, } @@ -30,6 +31,7 @@ pub enum QueryMsg { #[cw_serde] pub struct ConfigResponse { - pub credit_manager_addr: Option, + pub credit_manager: Option, + pub params: Option, pub owner_response: OwnerResponse, } diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index 237937f20..df7e1edab 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -29,6 +29,7 @@ mars-account-nft = { workspace = true } mars-rover-health-types = { workspace = true } mars-red-bank-types = { workspace = true } mars-owner = { workspace = true } +mars-params = { workspace = true } schemars = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } diff --git a/packages/rover/src/adapters/mod.rs b/packages/rover/src/adapters/mod.rs index d344d25d3..748c29ead 100644 --- a/packages/rover/src/adapters/mod.rs +++ b/packages/rover/src/adapters/mod.rs @@ -1,5 +1,6 @@ pub mod health; pub mod oracle; +pub mod params; pub mod red_bank; pub mod swap; pub mod vault; diff --git a/packages/rover/src/adapters/params.rs b/packages/rover/src/adapters/params.rs new file mode 100644 index 000000000..cfd1f22b5 --- /dev/null +++ b/packages/rover/src/adapters/params.rs @@ -0,0 +1,66 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Api, Decimal, QuerierWrapper, StdResult}; +use mars_params::{ + msg::QueryMsg, + types::{AssetParams, VaultConfig}, +}; + +#[cw_serde] +pub struct ParamsBase(T); + +impl ParamsBase { + pub fn new(address: T) -> ParamsBase { + ParamsBase(address) + } + + pub fn address(&self) -> &T { + &self.0 + } +} + +pub type ParamsUnchecked = ParamsBase; +pub type Params = ParamsBase; + +impl From for ParamsUnchecked { + fn from(mars_params: Params) -> Self { + Self(mars_params.0.to_string()) + } +} + +impl ParamsUnchecked { + pub fn check(&self, api: &dyn Api) -> StdResult { + Ok(ParamsBase(api.addr_validate(self.address())?)) + } +} + +impl Params { + pub fn query_asset_params( + &self, + querier: &QuerierWrapper, + denom: &str, + ) -> StdResult { + querier.query_wasm_smart( + self.address().to_string(), + &QueryMsg::AssetParams { + denom: denom.to_string(), + }, + ) + } + + pub fn query_vault_config( + &self, + querier: &QuerierWrapper, + vault_address: &Addr, + ) -> StdResult { + querier.query_wasm_smart( + self.address().to_string(), + &QueryMsg::VaultConfig { + address: vault_address.to_string(), + }, + ) + } + + pub fn query_max_close_factor(&self, querier: &QuerierWrapper) -> StdResult { + querier.query_wasm_smart(self.address().to_string(), &QueryMsg::MaxCloseFactor {}) + } +} diff --git a/packages/rover/src/adapters/red_bank.rs b/packages/rover/src/adapters/red_bank.rs index 671075ebf..7758c34ff 100644 --- a/packages/rover/src/adapters/red_bank.rs +++ b/packages/rover/src/adapters/red_bank.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{ to_binary, Addr, Api, Coin, CosmosMsg, QuerierWrapper, QueryRequest, StdResult, Uint128, WasmMsg, WasmQuery, }; -use mars_red_bank_types::{red_bank, red_bank::Market}; +use mars_red_bank_types::red_bank; #[cw_serde] pub struct RedBankBase(T); @@ -115,13 +115,4 @@ impl RedBank { }))?; Ok(response.amount) } - - pub fn query_market(&self, querier: &QuerierWrapper, denom: &str) -> StdResult { - querier.query_wasm_smart( - self.address(), - &red_bank::QueryMsg::Market { - denom: denom.to_string(), - }, - ) - } } diff --git a/packages/rover/src/adapters/vault/config.rs b/packages/rover/src/adapters/vault/config.rs deleted file mode 100644 index 4cc7fed5b..000000000 --- a/packages/rover/src/adapters/vault/config.rs +++ /dev/null @@ -1,27 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Coin, Decimal}; - -use crate::error::{ContractError, ContractError::InvalidConfig}; - -#[cw_serde] -pub struct VaultConfig { - pub deposit_cap: Coin, - pub max_ltv: Decimal, - pub liquidation_threshold: Decimal, - pub whitelisted: bool, -} - -impl VaultConfig { - pub fn check(&self) -> Result<(), ContractError> { - let max_ltv_too_big = self.max_ltv > Decimal::one(); - let lqt_too_big = self.liquidation_threshold > Decimal::one(); - let max_ltv_bigger_than_lqt = self.max_ltv > self.liquidation_threshold; - - if max_ltv_too_big || lqt_too_big || max_ltv_bigger_than_lqt { - return Err(InvalidConfig { - reason: "max ltv or liquidation threshold are invalid".to_string(), - }); - } - Ok(()) - } -} diff --git a/packages/rover/src/adapters/vault/mod.rs b/packages/rover/src/adapters/vault/mod.rs index 0fea29ca6..aadd6da4d 100644 --- a/packages/rover/src/adapters/vault/mod.rs +++ b/packages/rover/src/adapters/vault/mod.rs @@ -1,7 +1,6 @@ mod amount; mod base; -mod config; mod position; mod update; -pub use self::{amount::*, base::*, config::*, position::*, update::*}; +pub use self::{amount::*, base::*, position::*, update::*}; diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 50205746f..25265ca28 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -33,20 +33,6 @@ pub enum ExecuteMsg { UpdateConfig { updates: ConfigUpdates, }, - /// Emergency owner has a narrow amount of config changes it is allowed to do: - /// - Lower maxLTV of vault to zero - /// - Lower deposit cap of vault to zero - /// - Remove asset from ALLOWED_COINS list. This has a second order consequence disallowing of that coin: - /// - Borrow - /// - Deposit - /// - Swap into - /// - Zap with/into - /// - Unzap into - /// Coin would still be allowed to: - /// - Withdraw - /// - Swap out of - /// - Repay loan of - EmergencyConfigUpdate(EmergencyUpdate), /// Manages owner role state UpdateOwner(OwnerUpdate), /// Update nft contract config @@ -88,13 +74,6 @@ impl From<&Coin> for ActionCoin { } } -#[cw_serde] -pub enum EmergencyUpdate { - SetZeroMaxLtv(VaultUnchecked), - SetZeroDepositCap(VaultUnchecked), - DisallowCoin(String), -} - #[cw_serde] pub enum LiquidateRequest { /// Pay back debt of a liquidatable rover account for a bonus. Requires specifying 1) the debt diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index c09e94981..7ee6f1c10 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -1,33 +1,19 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Decimal, Uint128}; +use cosmwasm_std::Uint128; -use crate::{ - adapters::{ - health::HealthContractUnchecked, - oracle::OracleUnchecked, - red_bank::RedBankUnchecked, - swap::SwapperUnchecked, - vault::{VaultConfig, VaultUnchecked}, - zapper::ZapperUnchecked, - }, - traits::Stringify, +use crate::adapters::{ + health::HealthContractUnchecked, oracle::OracleUnchecked, params::ParamsUnchecked, + red_bank::RedBankUnchecked, swap::SwapperUnchecked, zapper::ZapperUnchecked, }; #[cw_serde] pub struct InstantiateMsg { /// The address with privileged access to update config pub owner: String, - /// Whitelisted coin denoms approved by governance - pub allowed_coins: Vec, - /// Vaults approved by governance that implement credit manager's vault interface - /// Includes a deposit cap that enforces a TLV limit for risk mitigation - pub vault_configs: Vec, /// The Mars Protocol money market contract where we borrow assets from pub red_bank: RedBankUnchecked, /// The Mars Protocol oracle contract. We read prices of assets here. pub oracle: OracleUnchecked, - /// The maximum percent a liquidator can decrease the debt amount of the liquidatee - pub max_close_factor: Decimal, /// The maximum number of unlocking positions an account can have simultaneously /// Note: As health checking requires looping through each, this number must not be too large. /// If so, having too many could prevent the account from being liquidated due to gas constraints. @@ -38,30 +24,8 @@ pub struct InstantiateMsg { pub zapper: ZapperUnchecked, /// Helper contract for calculating health factor pub health_contract: HealthContractUnchecked, -} - -#[cw_serde] -pub struct VaultInstantiateConfig { - pub vault: VaultUnchecked, - pub config: VaultConfig, -} - -impl Stringify for Vec { - fn to_string(&self) -> String { - self.iter() - .map(|v| { - format!( - "addr: {}, deposit_cap: {}, max_ltv: {}, liquidation_threshold: {}, whitelisted: {}", - v.vault.address, - v.config.deposit_cap, - v.config.max_ltv, - v.config.liquidation_threshold, - v.config.whitelisted - ) - }) - .collect::>() - .join(" :: ") - } + /// Contract that stores asset and vault params + pub params: ParamsUnchecked, } /// Used when you want to update fields on Instantiate config @@ -69,11 +33,8 @@ impl Stringify for Vec { #[derive(Default)] pub struct ConfigUpdates { pub account_nft: Option, - pub allowed_coins: Option>, - pub vault_configs: Option>, pub oracle: Option, pub red_bank: Option, - pub max_close_factor: Option, pub max_unlocking_positions: Option, pub swapper: Option, pub zapper: Option, diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index 46d0ba8c3..a043c3ef0 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -1,9 +1,9 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Coin, Decimal, Uint128}; +use cosmwasm_std::{Coin, Uint128}; use mars_owner::OwnerResponse; use crate::{ - adapters::vault::{Vault, VaultConfig, VaultPosition, VaultUnchecked}, + adapters::vault::{Vault, VaultPosition, VaultUnchecked}, traits::Coins, }; @@ -13,29 +13,12 @@ pub enum QueryMsg { /// Rover contract-level config #[returns(ConfigResponse)] Config {}, - /// Config & deposit caps on vault - #[returns(VaultConfigResponse)] - VaultConfig { - vault: VaultUnchecked, - }, - /// Configs & deposit caps on all vaults - #[returns(Vec)] - VaultsConfig { - start_after: Option, - limit: Option, - }, /// The amount the vault has been utilized, /// denominated in the same denom set in the vault config's deposit cap #[returns(VaultUtilizationResponse)] VaultUtilization { vault: VaultUnchecked, }, - /// Whitelisted coins - #[returns(Vec)] - AllowedCoins { - start_after: Option, - limit: Option, - }, /// All positions represented by token with value #[returns(Positions)] Positions { @@ -83,17 +66,6 @@ pub enum QueryMsg { start_after: Option<(String, String)>, limit: Option, }, - /// Get total vault coin balance in Rover for vault - #[returns(Uint128)] - TotalVaultCoinBalance { - vault: VaultUnchecked, - }, - /// Enumerate all total vault coin balances; start_after accepts vault addr - #[returns(Vec)] - AllTotalVaultCoinBalances { - start_after: Option, - limit: Option, - }, /// Estimate how many LP tokens received in exchange for coins provided for liquidity #[returns(Uint128)] EstimateProvideLiquidity { @@ -114,12 +86,6 @@ pub enum QueryMsg { }, } -#[cw_serde] -pub struct VaultConfigResponse { - pub vault: VaultUnchecked, - pub config: VaultConfig, -} - #[cw_serde] pub struct VaultUtilizationResponse { pub vault: VaultUnchecked, @@ -219,7 +185,7 @@ pub struct ConfigResponse { pub account_nft: Option, pub red_bank: String, pub oracle: String, - pub max_close_factor: Decimal, + pub params: String, pub max_unlocking_positions: Uint128, pub swapper: String, pub zapper: String, diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 220ed7370..51a3af2d7 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -7,25 +7,16 @@ "title": "InstantiateMsg", "type": "object", "required": [ - "allowed_coins", "health_contract", - "max_close_factor", "max_unlocking_positions", "oracle", "owner", + "params", "red_bank", "swapper", - "vault_configs", "zapper" ], "properties": { - "allowed_coins": { - "description": "Whitelisted coin denoms approved by governance", - "type": "array", - "items": { - "type": "string" - } - }, "health_contract": { "description": "Helper contract for calculating health factor", "allOf": [ @@ -34,14 +25,6 @@ } ] }, - "max_close_factor": { - "description": "The maximum percent a liquidator can decrease the debt amount of the liquidatee", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, "max_unlocking_positions": { "description": "The maximum number of unlocking positions an account can have simultaneously Note: As health checking requires looping through each, this number must not be too large. If so, having too many could prevent the account from being liquidated due to gas constraints.", "allOf": [ @@ -62,6 +45,14 @@ "description": "The address with privileged access to update config", "type": "string" }, + "params": { + "description": "Contract that stores asset and vault params", + "allOf": [ + { + "$ref": "#/definitions/ParamsBase_for_String" + } + ] + }, "red_bank": { "description": "The Mars Protocol money market contract where we borrow assets from", "allOf": [ @@ -78,13 +69,6 @@ } ] }, - "vault_configs": { - "description": "Vaults approved by governance that implement credit manager's vault interface Includes a deposit cap that enforces a TLV limit for risk mitigation", - "type": "array", - "items": { - "$ref": "#/definitions/VaultInstantiateConfig" - } - }, "zapper": { "description": "Helper contract for adding/removing liquidity", "allOf": [ @@ -96,31 +80,15 @@ }, "additionalProperties": false, "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "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" - }, "HealthContractBase_for_String": { "type": "string" }, "OracleBase_for_String": { "type": "string" }, + "ParamsBase_for_String": { + "type": "string" + }, "RedBankBase_for_String": { "type": "string" }, @@ -131,58 +99,6 @@ "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" }, - "VaultBase_for_String": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - }, - "VaultConfig": { - "type": "object", - "required": [ - "deposit_cap", - "liquidation_threshold", - "max_ltv", - "whitelisted" - ], - "properties": { - "deposit_cap": { - "$ref": "#/definitions/Coin" - }, - "liquidation_threshold": { - "$ref": "#/definitions/Decimal" - }, - "max_ltv": { - "$ref": "#/definitions/Decimal" - }, - "whitelisted": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "VaultInstantiateConfig": { - "type": "object", - "required": [ - "config", - "vault" - ], - "properties": { - "config": { - "$ref": "#/definitions/VaultConfig" - }, - "vault": { - "$ref": "#/definitions/VaultBase_for_String" - } - }, - "additionalProperties": false - }, "ZapperBase_for_String": { "type": "string" } @@ -279,19 +195,6 @@ }, "additionalProperties": false }, - { - "description": "Emergency owner has a narrow amount of config changes it is allowed to do: - Lower maxLTV of vault to zero - Lower deposit cap of vault to zero - Remove asset from ALLOWED_COINS list. This has a second order consequence disallowing of that coin: - Borrow - Deposit - Swap into - Zap with/into - Unzap into Coin would still be allowed to: - Withdraw - Swap out of - Repay loan of", - "type": "object", - "required": [ - "emergency_config_update" - ], - "properties": { - "emergency_config_update": { - "$ref": "#/definitions/EmergencyUpdate" - } - }, - "additionalProperties": false - }, { "description": "Manages owner role state", "type": "object", @@ -1311,15 +1214,6 @@ "null" ] }, - "allowed_coins": { - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } - }, "health_contract": { "anyOf": [ { @@ -1330,16 +1224,6 @@ } ] }, - "max_close_factor": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, "max_unlocking_positions": { "anyOf": [ { @@ -1380,15 +1264,6 @@ } ] }, - "vault_configs": { - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/VaultInstantiateConfig" - } - }, "zapper": { "anyOf": [ { @@ -1406,46 +1281,6 @@ "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" }, - "EmergencyUpdate": { - "oneOf": [ - { - "type": "object", - "required": [ - "set_zero_max_ltv" - ], - "properties": { - "set_zero_max_ltv": { - "$ref": "#/definitions/VaultBase_for_String" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "set_zero_deposit_cap" - ], - "properties": { - "set_zero_deposit_cap": { - "$ref": "#/definitions/VaultBase_for_String" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "disallow_coin" - ], - "properties": { - "disallow_coin": { - "type": "string" - } - }, - "additionalProperties": false - } - ] - }, "HealthContractBase_for_String": { "type": "string" }, @@ -1696,46 +1531,6 @@ }, "additionalProperties": false }, - "VaultConfig": { - "type": "object", - "required": [ - "deposit_cap", - "liquidation_threshold", - "max_ltv", - "whitelisted" - ], - "properties": { - "deposit_cap": { - "$ref": "#/definitions/Coin" - }, - "liquidation_threshold": { - "$ref": "#/definitions/Decimal" - }, - "max_ltv": { - "$ref": "#/definitions/Decimal" - }, - "whitelisted": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "VaultInstantiateConfig": { - "type": "object", - "required": [ - "config", - "vault" - ], - "properties": { - "config": { - "$ref": "#/definitions/VaultConfig" - }, - "vault": { - "$ref": "#/definitions/VaultBase_for_String" - } - }, - "additionalProperties": false - }, "VaultPositionType": { "type": "string", "enum": [ @@ -1767,62 +1562,6 @@ }, "additionalProperties": false }, - { - "description": "Config & deposit caps on vault", - "type": "object", - "required": [ - "vault_config" - ], - "properties": { - "vault_config": { - "type": "object", - "required": [ - "vault" - ], - "properties": { - "vault": { - "$ref": "#/definitions/VaultBase_for_String" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Configs & deposit caps on all vaults", - "type": "object", - "required": [ - "vaults_config" - ], - "properties": { - "vaults_config": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "anyOf": [ - { - "$ref": "#/definitions/VaultBase_for_String" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "The amount the vault has been utilized, denominated in the same denom set in the vault config's deposit cap", "type": "object", @@ -1845,36 +1584,6 @@ }, "additionalProperties": false }, - { - "description": "Whitelisted coins", - "type": "object", - "required": [ - "allowed_coins" - ], - "properties": { - "allowed_coins": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "All positions represented by token with value", "type": "object", @@ -2143,62 +1852,6 @@ }, "additionalProperties": false }, - { - "description": "Get total vault coin balance in Rover for vault", - "type": "object", - "required": [ - "total_vault_coin_balance" - ], - "properties": { - "total_vault_coin_balance": { - "type": "object", - "required": [ - "vault" - ], - "properties": { - "vault": { - "$ref": "#/definitions/VaultBase_for_String" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Enumerate all total vault coin balances; start_after accepts vault addr", - "type": "object", - "required": [ - "all_total_vault_coin_balances" - ], - "properties": { - "all_total_vault_coin_balances": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "anyOf": [ - { - "$ref": "#/definitions/VaultBase_for_String" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "Estimate how many LP tokens received in exchange for coins provided for liquidity", "type": "object", @@ -2581,52 +2234,6 @@ } } }, - "all_total_vault_coin_balances": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_VaultWithBalance", - "type": "array", - "items": { - "$ref": "#/definitions/VaultWithBalance" - }, - "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" - }, - "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" - }, - "VaultBase_for_Addr": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - }, - "VaultWithBalance": { - "type": "object", - "required": [ - "balance", - "vault" - ], - "properties": { - "balance": { - "$ref": "#/definitions/Uint128" - }, - "vault": { - "$ref": "#/definitions/VaultBase_for_Addr" - } - }, - "additionalProperties": false - } - } - }, "all_vault_positions": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_VaultPositionResponseItem", @@ -2781,24 +2388,16 @@ } } }, - "allowed_coins": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_String", - "type": "array", - "items": { - "type": "string" - } - }, "config": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ConfigResponse", "type": "object", "required": [ "health_contract", - "max_close_factor", "max_unlocking_positions", "oracle", "ownership", + "params", "red_bank", "swapper", "zapper" @@ -2813,9 +2412,6 @@ "health_contract": { "type": "string" }, - "max_close_factor": { - "$ref": "#/definitions/Decimal" - }, "max_unlocking_positions": { "$ref": "#/definitions/Uint128" }, @@ -2825,6 +2421,9 @@ "ownership": { "$ref": "#/definitions/OwnerResponse" }, + "params": { + "type": "string" + }, "red_bank": { "type": "string" }, @@ -2837,10 +2436,6 @@ }, "additionalProperties": false, "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, "OwnerResponse": { "description": "Returned from Owner.query()", "type": "object", @@ -3197,91 +2792,6 @@ } } }, - "total_vault_coin_balance": { - "$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" - }, - "vault_config": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VaultConfigResponse", - "type": "object", - "required": [ - "config", - "vault" - ], - "properties": { - "config": { - "$ref": "#/definitions/VaultConfig" - }, - "vault": { - "$ref": "#/definitions/VaultBase_for_String" - } - }, - "additionalProperties": false, - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "VaultBase_for_String": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - }, - "VaultConfig": { - "type": "object", - "required": [ - "deposit_cap", - "liquidation_threshold", - "max_ltv", - "whitelisted" - ], - "properties": { - "deposit_cap": { - "$ref": "#/definitions/Coin" - }, - "liquidation_threshold": { - "$ref": "#/definitions/Decimal" - }, - "max_ltv": { - "$ref": "#/definitions/Decimal" - }, - "whitelisted": { - "type": "boolean" - } - }, - "additionalProperties": false - } - } - }, "vault_position_value": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "VaultPositionValue", @@ -3386,91 +2896,6 @@ "additionalProperties": false } } - }, - "vaults_config": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_VaultConfigResponse", - "type": "array", - "items": { - "$ref": "#/definitions/VaultConfigResponse" - }, - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "VaultBase_for_String": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - }, - "VaultConfig": { - "type": "object", - "required": [ - "deposit_cap", - "liquidation_threshold", - "max_ltv", - "whitelisted" - ], - "properties": { - "deposit_cap": { - "$ref": "#/definitions/Coin" - }, - "liquidation_threshold": { - "$ref": "#/definitions/Decimal" - }, - "max_ltv": { - "$ref": "#/definitions/Decimal" - }, - "whitelisted": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "VaultConfigResponse": { - "type": "object", - "required": [ - "config", - "vault" - ], - "properties": { - "config": { - "$ref": "#/definitions/VaultConfig" - }, - "vault": { - "$ref": "#/definitions/VaultBase_for_String" - } - }, - "additionalProperties": false - } - } } } } diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 69c806569..86946cbde 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -36,46 +36,6 @@ } }, "additionalProperties": false - }, - { - "type": "object", - "required": [ - "set_allowed_coins" - ], - "properties": { - "set_allowed_coins": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "set_vault_config" - ], - "properties": { - "set_vault_config": { - "type": "object", - "required": [ - "address", - "config" - ], - "properties": { - "address": { - "type": "string" - }, - "config": { - "$ref": "#/definitions/VaultConfig" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false } ], "definitions": { @@ -128,10 +88,6 @@ }, "additionalProperties": false }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, "LentAmount": { "type": "object", "required": [ @@ -243,30 +199,6 @@ }, "additionalProperties": false }, - "VaultConfig": { - "type": "object", - "required": [ - "deposit_cap", - "liquidation_threshold", - "max_ltv", - "whitelisted" - ], - "properties": { - "deposit_cap": { - "$ref": "#/definitions/Coin" - }, - "liquidation_threshold": { - "$ref": "#/definitions/Decimal" - }, - "max_ltv": { - "$ref": "#/definitions/Decimal" - }, - "whitelisted": { - "type": "boolean" - } - }, - "additionalProperties": false - }, "VaultPosition": { "type": "object", "required": [ @@ -355,62 +287,6 @@ }, "additionalProperties": false }, - { - "description": "Config & deposit caps on vault", - "type": "object", - "required": [ - "vault_config" - ], - "properties": { - "vault_config": { - "type": "object", - "required": [ - "vault" - ], - "properties": { - "vault": { - "$ref": "#/definitions/VaultBase_for_String" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Configs & deposit caps on all vaults", - "type": "object", - "required": [ - "vaults_config" - ], - "properties": { - "vaults_config": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "anyOf": [ - { - "$ref": "#/definitions/VaultBase_for_String" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "The amount the vault has been utilized, denominated in the same denom set in the vault config's deposit cap", "type": "object", @@ -433,36 +309,6 @@ }, "additionalProperties": false }, - { - "description": "Whitelisted coins", - "type": "object", - "required": [ - "allowed_coins" - ], - "properties": { - "allowed_coins": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "All positions represented by token with value", "type": "object", @@ -731,62 +577,6 @@ }, "additionalProperties": false }, - { - "description": "Get total vault coin balance in Rover for vault", - "type": "object", - "required": [ - "total_vault_coin_balance" - ], - "properties": { - "total_vault_coin_balance": { - "type": "object", - "required": [ - "vault" - ], - "properties": { - "vault": { - "$ref": "#/definitions/VaultBase_for_String" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Enumerate all total vault coin balances; start_after accepts vault addr", - "type": "object", - "required": [ - "all_total_vault_coin_balances" - ], - "properties": { - "all_total_vault_coin_balances": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "anyOf": [ - { - "$ref": "#/definitions/VaultBase_for_String" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "Estimate how many LP tokens received in exchange for coins provided for liquidity", "type": "object", @@ -1169,52 +959,6 @@ } } }, - "all_total_vault_coin_balances": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_VaultWithBalance", - "type": "array", - "items": { - "$ref": "#/definitions/VaultWithBalance" - }, - "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" - }, - "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" - }, - "VaultBase_for_Addr": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - }, - "VaultWithBalance": { - "type": "object", - "required": [ - "balance", - "vault" - ], - "properties": { - "balance": { - "$ref": "#/definitions/Uint128" - }, - "vault": { - "$ref": "#/definitions/VaultBase_for_Addr" - } - }, - "additionalProperties": false - } - } - }, "all_vault_positions": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_VaultPositionResponseItem", @@ -1369,24 +1113,16 @@ } } }, - "allowed_coins": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_String", - "type": "array", - "items": { - "type": "string" - } - }, "config": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ConfigResponse", "type": "object", "required": [ "health_contract", - "max_close_factor", "max_unlocking_positions", "oracle", "ownership", + "params", "red_bank", "swapper", "zapper" @@ -1401,9 +1137,6 @@ "health_contract": { "type": "string" }, - "max_close_factor": { - "$ref": "#/definitions/Decimal" - }, "max_unlocking_positions": { "$ref": "#/definitions/Uint128" }, @@ -1413,6 +1146,9 @@ "ownership": { "$ref": "#/definitions/OwnerResponse" }, + "params": { + "type": "string" + }, "red_bank": { "type": "string" }, @@ -1425,10 +1161,6 @@ }, "additionalProperties": false, "definitions": { - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, "OwnerResponse": { "description": "Returned from Owner.query()", "type": "object", @@ -1785,91 +1517,6 @@ } } }, - "total_vault_coin_balance": { - "$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" - }, - "vault_config": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VaultConfigResponse", - "type": "object", - "required": [ - "config", - "vault" - ], - "properties": { - "config": { - "$ref": "#/definitions/VaultConfig" - }, - "vault": { - "$ref": "#/definitions/VaultBase_for_String" - } - }, - "additionalProperties": false, - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "VaultBase_for_String": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - }, - "VaultConfig": { - "type": "object", - "required": [ - "deposit_cap", - "liquidation_threshold", - "max_ltv", - "whitelisted" - ], - "properties": { - "deposit_cap": { - "$ref": "#/definitions/Coin" - }, - "liquidation_threshold": { - "$ref": "#/definitions/Decimal" - }, - "max_ltv": { - "$ref": "#/definitions/Decimal" - }, - "whitelisted": { - "type": "boolean" - } - }, - "additionalProperties": false - } - } - }, "vault_position_value": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "VaultPositionValue", @@ -1974,91 +1621,6 @@ "additionalProperties": false } } - }, - "vaults_config": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_VaultConfigResponse", - "type": "array", - "items": { - "$ref": "#/definitions/VaultConfigResponse" - }, - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "VaultBase_for_String": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - }, - "VaultConfig": { - "type": "object", - "required": [ - "deposit_cap", - "liquidation_threshold", - "max_ltv", - "whitelisted" - ], - "properties": { - "deposit_cap": { - "$ref": "#/definitions/Coin" - }, - "liquidation_threshold": { - "$ref": "#/definitions/Decimal" - }, - "max_ltv": { - "$ref": "#/definitions/Decimal" - }, - "whitelisted": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "VaultConfigResponse": { - "type": "object", - "required": [ - "config", - "vault" - ], - "properties": { - "config": { - "$ref": "#/definitions/VaultConfig" - }, - "vault": { - "$ref": "#/definitions/VaultBase_for_String" - } - }, - "additionalProperties": false - } - } } } } diff --git a/schemas/mars-mock-red-bank/mars-mock-red-bank.json b/schemas/mars-mock-red-bank/mars-mock-red-bank.json index d2ce8f303..abe4975ce 100644 --- a/schemas/mars-mock-red-bank/mars-mock-red-bank.json +++ b/schemas/mars-mock-red-bank/mars-mock-red-bank.json @@ -5,49 +5,8 @@ "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "InstantiateMsg", - "type": "object", - "required": [ - "coins" - ], - "properties": { - "coins": { - "type": "array", - "items": { - "$ref": "#/definitions/CoinMarketInfo" - } - } - }, - "additionalProperties": false, - "definitions": { - "CoinMarketInfo": { - "type": "object", - "required": [ - "denom", - "liquidation_bonus", - "liquidation_threshold", - "max_ltv" - ], - "properties": { - "denom": { - "type": "string" - }, - "liquidation_bonus": { - "$ref": "#/definitions/Decimal" - }, - "liquidation_threshold": { - "$ref": "#/definitions/Decimal" - }, - "max_ltv": { - "$ref": "#/definitions/Decimal" - } - }, - "additionalProperties": false - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - } - } + "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" }, "execute": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-params/mars-params.json b/schemas/mars-params/mars-params.json new file mode 100644 index 000000000..ed81be33a --- /dev/null +++ b/schemas/mars-params/mars-params.json @@ -0,0 +1,971 @@ +{ + "contract_name": "mars-params", + "contract_version": "1.0.4", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "max_close_factor", + "owner" + ], + "properties": { + "max_close_factor": { + "description": "The maximum percent a liquidator can decrease the debt amount of the liquidatee", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "owner": { + "description": "Contract's owner", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_owner" + ], + "properties": { + "update_owner": { + "$ref": "#/definitions/OwnerUpdate" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_max_close_factor" + ], + "properties": { + "update_max_close_factor": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_asset_params" + ], + "properties": { + "update_asset_params": { + "$ref": "#/definitions/AssetParamsUpdate" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_vault_config" + ], + "properties": { + "update_vault_config": { + "$ref": "#/definitions/VaultConfigUpdate" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "emergency_update" + ], + "properties": { + "emergency_update": { + "$ref": "#/definitions/EmergencyUpdate" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "AssetParams": { + "type": "object", + "required": [ + "denom", + "liquidation_bonus", + "liquidation_threshold", + "max_loan_to_value", + "red_bank", + "rover" + ], + "properties": { + "denom": { + "type": "string" + }, + "liquidation_bonus": { + "$ref": "#/definitions/Decimal" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_loan_to_value": { + "$ref": "#/definitions/Decimal" + }, + "red_bank": { + "$ref": "#/definitions/RedBankSettings" + }, + "rover": { + "$ref": "#/definitions/RoverSettings" + } + }, + "additionalProperties": false + }, + "AssetParamsUpdate": { + "oneOf": [ + { + "type": "object", + "required": [ + "add_or_update" + ], + "properties": { + "add_or_update": { + "type": "object", + "required": [ + "params" + ], + "properties": { + "params": { + "$ref": "#/definitions/AssetParams" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "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" + }, + "EmergencyUpdate": { + "oneOf": [ + { + "type": "object", + "required": [ + "rover" + ], + "properties": { + "rover": { + "$ref": "#/definitions/RoverEmergencyUpdate" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "red_bank" + ], + "properties": { + "red_bank": { + "$ref": "#/definitions/RedBankEmergencyUpdate" + } + }, + "additionalProperties": false + } + ] + }, + "HighLeverageStrategyParams": { + "type": "object", + "required": [ + "liquidation_threshold", + "max_loan_to_value" + ], + "properties": { + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_loan_to_value": { + "$ref": "#/definitions/Decimal" + } + }, + "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" + ] + }, + { + "description": "A separate entity managed by Owner that can be used for granting specific emergency powers.", + "type": "object", + "required": [ + "set_emergency_owner" + ], + "properties": { + "set_emergency_owner": { + "type": "object", + "required": [ + "emergency_owner" + ], + "properties": { + "emergency_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove the entity in the Emergency Owner role", + "type": "string", + "enum": [ + "clear_emergency_owner" + ] + } + ] + }, + "RedBankEmergencyUpdate": { + "oneOf": [ + { + "type": "object", + "required": [ + "disable_borrowing" + ], + "properties": { + "disable_borrowing": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "RedBankSettings": { + "type": "object", + "required": [ + "borrow_enabled", + "deposit_cap", + "deposit_enabled" + ], + "properties": { + "borrow_enabled": { + "type": "boolean" + }, + "deposit_cap": { + "$ref": "#/definitions/Uint128" + }, + "deposit_enabled": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "RoverEmergencyUpdate": { + "oneOf": [ + { + "type": "object", + "required": [ + "set_zero_max_ltv_on_vault" + ], + "properties": { + "set_zero_max_ltv_on_vault": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "set_zero_deposit_cap_on_vault" + ], + "properties": { + "set_zero_deposit_cap_on_vault": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "disallow_coin" + ], + "properties": { + "disallow_coin": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "RoverSettings": { + "type": "object", + "required": [ + "hls", + "whitelisted" + ], + "properties": { + "hls": { + "$ref": "#/definitions/HighLeverageStrategyParams" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VaultConfigBase_for_String": { + "type": "object", + "required": [ + "addr", + "deposit_cap", + "liquidation_threshold", + "max_loan_to_value", + "whitelisted" + ], + "properties": { + "addr": { + "type": "string" + }, + "deposit_cap": { + "$ref": "#/definitions/Coin" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_loan_to_value": { + "$ref": "#/definitions/Decimal" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "VaultConfigUpdate": { + "oneOf": [ + { + "type": "object", + "required": [ + "add_or_update" + ], + "properties": { + "add_or_update": { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "$ref": "#/definitions/VaultConfigBase_for_String" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "remove" + ], + "properties": { + "remove": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "asset_params" + ], + "properties": { + "asset_params": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "all_asset_params" + ], + "properties": { + "all_asset_params": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vault_config" + ], + "properties": { + "vault_config": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "Address of vault", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "all_vault_configs" + ], + "properties": { + "all_vault_configs": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "max_close_factor" + ], + "properties": { + "max_close_factor": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "all_asset_params": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_AssetParams", + "type": "array", + "items": { + "$ref": "#/definitions/AssetParams" + }, + "definitions": { + "AssetParams": { + "type": "object", + "required": [ + "denom", + "liquidation_bonus", + "liquidation_threshold", + "max_loan_to_value", + "red_bank", + "rover" + ], + "properties": { + "denom": { + "type": "string" + }, + "liquidation_bonus": { + "$ref": "#/definitions/Decimal" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_loan_to_value": { + "$ref": "#/definitions/Decimal" + }, + "red_bank": { + "$ref": "#/definitions/RedBankSettings" + }, + "rover": { + "$ref": "#/definitions/RoverSettings" + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "HighLeverageStrategyParams": { + "type": "object", + "required": [ + "liquidation_threshold", + "max_loan_to_value" + ], + "properties": { + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_loan_to_value": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "RedBankSettings": { + "type": "object", + "required": [ + "borrow_enabled", + "deposit_cap", + "deposit_enabled" + ], + "properties": { + "borrow_enabled": { + "type": "boolean" + }, + "deposit_cap": { + "$ref": "#/definitions/Uint128" + }, + "deposit_enabled": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "RoverSettings": { + "type": "object", + "required": [ + "hls", + "whitelisted" + ], + "properties": { + "hls": { + "$ref": "#/definitions/HighLeverageStrategyParams" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "all_vault_configs": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_VaultConfigBase_for_Addr", + "type": "array", + "items": { + "$ref": "#/definitions/VaultConfigBase_for_Addr" + }, + "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" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "VaultConfigBase_for_Addr": { + "type": "object", + "required": [ + "addr", + "deposit_cap", + "liquidation_threshold", + "max_loan_to_value", + "whitelisted" + ], + "properties": { + "addr": { + "$ref": "#/definitions/Addr" + }, + "deposit_cap": { + "$ref": "#/definitions/Coin" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_loan_to_value": { + "$ref": "#/definitions/Decimal" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, + "asset_params": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AssetParams", + "type": "object", + "required": [ + "denom", + "liquidation_bonus", + "liquidation_threshold", + "max_loan_to_value", + "red_bank", + "rover" + ], + "properties": { + "denom": { + "type": "string" + }, + "liquidation_bonus": { + "$ref": "#/definitions/Decimal" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_loan_to_value": { + "$ref": "#/definitions/Decimal" + }, + "red_bank": { + "$ref": "#/definitions/RedBankSettings" + }, + "rover": { + "$ref": "#/definitions/RoverSettings" + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "HighLeverageStrategyParams": { + "type": "object", + "required": [ + "liquidation_threshold", + "max_loan_to_value" + ], + "properties": { + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_loan_to_value": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "RedBankSettings": { + "type": "object", + "required": [ + "borrow_enabled", + "deposit_cap", + "deposit_enabled" + ], + "properties": { + "borrow_enabled": { + "type": "boolean" + }, + "deposit_cap": { + "$ref": "#/definitions/Uint128" + }, + "deposit_enabled": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "RoverSettings": { + "type": "object", + "required": [ + "hls", + "whitelisted" + ], + "properties": { + "hls": { + "$ref": "#/definitions/HighLeverageStrategyParams" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "max_close_factor": { + "$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" + }, + "owner": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OwnerResponse", + "description": "Returned from Owner.query()", + "type": "object", + "required": [ + "abolished", + "initialized" + ], + "properties": { + "abolished": { + "type": "boolean" + }, + "emergency_owner": { + "type": [ + "string", + "null" + ] + }, + "initialized": { + "type": "boolean" + }, + "owner": { + "type": [ + "string", + "null" + ] + }, + "proposed": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "vault_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VaultConfigBase_for_Addr", + "type": "object", + "required": [ + "addr", + "deposit_cap", + "liquidation_threshold", + "max_loan_to_value", + "whitelisted" + ], + "properties": { + "addr": { + "$ref": "#/definitions/Addr" + }, + "deposit_cap": { + "$ref": "#/definitions/Coin" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_loan_to_value": { + "$ref": "#/definitions/Decimal" + }, + "whitelisted": { + "type": "boolean" + } + }, + "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" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + } + } +} diff --git a/schemas/mars-rover-health-computer/mars-rover-health-computer.json b/schemas/mars-rover-health-computer/mars-rover-health-computer.json index 947402157..3d0100bf4 100644 --- a/schemas/mars-rover-health-computer/mars-rover-health-computer.json +++ b/schemas/mars-rover-health-computer/mars-rover-health-computer.json @@ -4,18 +4,11 @@ "description": "`HealthComputer` is a shared struct with the frontend that gets compiled to wasm. For this reason, it uses a dependency-injection-like pattern where all required data is needed up front.", "type": "object", "required": [ - "allowed_coins", "denoms_data", "positions", "vaults_data" ], "properties": { - "allowed_coins": { - "type": "array", - "items": { - "type": "string" - } - }, "denoms_data": { "$ref": "#/definitions/DenomsData" }, @@ -32,6 +25,38 @@ "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" }, + "AssetParams": { + "type": "object", + "required": [ + "denom", + "liquidation_bonus", + "liquidation_threshold", + "max_loan_to_value", + "red_bank", + "rover" + ], + "properties": { + "denom": { + "type": "string" + }, + "liquidation_bonus": { + "$ref": "#/definitions/Decimal" + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_loan_to_value": { + "$ref": "#/definitions/Decimal" + }, + "red_bank": { + "$ref": "#/definitions/RedBankSettings" + }, + "rover": { + "$ref": "#/definitions/RoverSettings" + } + }, + "additionalProperties": false + }, "Coin": { "type": "object", "required": [ @@ -104,14 +129,14 @@ "DenomsData": { "type": "object", "required": [ - "markets", + "params", "prices" ], "properties": { - "markets": { + "params": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/Market" + "$ref": "#/definitions/AssetParams" } }, "prices": { @@ -124,46 +149,18 @@ }, "additionalProperties": false }, - "InterestRateModel": { + "HighLeverageStrategyParams": { "type": "object", "required": [ - "base", - "optimal_utilization_rate", - "slope_1", - "slope_2" + "liquidation_threshold", + "max_loan_to_value" ], "properties": { - "base": { - "description": "Base rate", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "optimal_utilization_rate": { - "description": "Optimal utilization rate", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "slope_1": { - "description": "Slope parameter for interest rate model function when utilization_rate < optimal_utilization_rate", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" }, - "slope_2": { - "description": "Slope parameter for interest rate model function when utilization_rate >= optimal_utilization_rate", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] + "max_loan_to_value": { + "$ref": "#/definitions/Decimal" } }, "additionalProperties": false @@ -214,144 +211,6 @@ }, "additionalProperties": false }, - "Market": { - "type": "object", - "required": [ - "borrow_enabled", - "borrow_index", - "borrow_rate", - "collateral_total_scaled", - "debt_total_scaled", - "denom", - "deposit_cap", - "deposit_enabled", - "indexes_last_updated", - "interest_rate_model", - "liquidation_bonus", - "liquidation_threshold", - "liquidity_index", - "liquidity_rate", - "max_loan_to_value", - "reserve_factor" - ], - "properties": { - "borrow_enabled": { - "description": "If false cannot borrow", - "type": "boolean" - }, - "borrow_index": { - "description": "Borrow index (Used to compute borrow interest)", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "borrow_rate": { - "description": "Rate charged to borrowers", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "collateral_total_scaled": { - "description": "Total collateral scaled for the market's currency", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "debt_total_scaled": { - "description": "Total debt scaled for the market's currency", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "denom": { - "description": "Denom of the asset", - "type": "string" - }, - "deposit_cap": { - "description": "Deposit Cap (defined in terms of the asset)", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "deposit_enabled": { - "description": "If false cannot deposit", - "type": "boolean" - }, - "indexes_last_updated": { - "description": "Timestamp (seconds) where indexes and where last updated", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "interest_rate_model": { - "description": "model (params + internal state) that defines how interest rate behaves", - "allOf": [ - { - "$ref": "#/definitions/InterestRateModel" - } - ] - }, - "liquidation_bonus": { - "description": "Bonus amount of collateral liquidator get when repaying user's debt (Will get collateral from user in an amount equal to debt repayed + bonus)", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "liquidation_threshold": { - "description": "Base asset amount in debt position per \"base asset\" of asset collateral that if surpassed makes the user's position liquidatable.", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "liquidity_index": { - "description": "Liquidity index (Used to compute deposit interest)", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "liquidity_rate": { - "description": "Rate paid to depositors", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "max_loan_to_value": { - "description": "Max base asset that can be borrowed per \"base asset\" collateral when using the asset as collateral", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "reserve_factor": { - "description": "Portion of the borrow rate that is kept as protocol rewards", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - } - }, - "additionalProperties": false - }, "Positions": { "type": "object", "required": [ @@ -392,6 +251,42 @@ }, "additionalProperties": false }, + "RedBankSettings": { + "type": "object", + "required": [ + "borrow_enabled", + "deposit_cap", + "deposit_enabled" + ], + "properties": { + "borrow_enabled": { + "type": "boolean" + }, + "deposit_cap": { + "$ref": "#/definitions/Uint128" + }, + "deposit_enabled": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "RoverSettings": { + "type": "object", + "required": [ + "hls", + "whitelisted" + ], + "properties": { + "hls": { + "$ref": "#/definitions/HighLeverageStrategyParams" + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -417,22 +312,26 @@ }, "additionalProperties": false }, - "VaultConfig": { + "VaultConfigBase_for_Addr": { "type": "object", "required": [ + "addr", "deposit_cap", "liquidation_threshold", - "max_ltv", + "max_loan_to_value", "whitelisted" ], "properties": { + "addr": { + "$ref": "#/definitions/Addr" + }, "deposit_cap": { "$ref": "#/definitions/Coin" }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" }, - "max_ltv": { + "max_loan_to_value": { "$ref": "#/definitions/Decimal" }, "whitelisted": { @@ -545,7 +444,7 @@ "vault_configs": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/VaultConfig" + "$ref": "#/definitions/VaultConfigBase_for_Addr" } }, "vault_values": { diff --git a/schemas/mars-rover-health-types/mars-rover-health-types.json b/schemas/mars-rover-health-types/mars-rover-health-types.json index 711268178..9e0963062 100644 --- a/schemas/mars-rover-health-types/mars-rover-health-types.json +++ b/schemas/mars-rover-health-types/mars-rover-health-types.json @@ -43,12 +43,18 @@ "properties": { "update_config": { "type": "object", - "required": [ - "credit_manager" - ], "properties": { "credit_manager": { - "type": "string" + "type": [ + "string", + "null" + ] + }, + "params": { + "type": [ + "string", + "null" + ] } }, "additionalProperties": false @@ -187,7 +193,7 @@ "owner_response" ], "properties": { - "credit_manager_addr": { + "credit_manager": { "type": [ "string", "null" @@ -195,6 +201,12 @@ }, "owner_response": { "$ref": "#/definitions/OwnerResponse" + }, + "params": { + "type": [ + "string", + "null" + ] } }, "additionalProperties": false, diff --git a/scripts/codegen/index.ts b/scripts/codegen/index.ts index 1a51b578d..a114f39db 100644 --- a/scripts/codegen/index.ts +++ b/scripts/codegen/index.ts @@ -1,9 +1,10 @@ import codegen from '@cosmwasm/ts-codegen' import { join, resolve } from 'path' import { printGreen, printRed } from '../utils/chalk' -import { readdir } from 'fs/promises' +import { readdir, rm, rename } from 'fs/promises' +import simpleGit from 'simple-git' -void (async function () { +const generateTypes = async () => { const schemasDir = resolve(join(__dirname, '../../../schemas')) const schemas = await readdir(schemasDir) @@ -36,4 +37,26 @@ void (async function () { printRed(`Error with ${schema}: ${e}`) } } +} + +const fetchSchemafromGithub = async ({ + githubRepo, + pathToSchema, +}: { + githubRepo: string + pathToSchema: string +}) => { + await simpleGit().clone(githubRepo) + const schemaDirName = pathToSchema.split('/').pop()! + const repoDirName = githubRepo.split('/').pop()! + await rename(pathToSchema, `../schemas/${schemaDirName}`) + await rm(`./${repoDirName}`, { recursive: true, force: true }) +} + +void (async function () { + await fetchSchemafromGithub({ + githubRepo: 'https://github.com/mars-protocol/mars-common', + pathToSchema: './mars-common/schemas/mars-params', + }) + await generateTypes() })() diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index 49f1b9e92..b6e781285 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -176,27 +176,16 @@ export class Deployer { async instantiateCreditManager() { const msg: RoverInstantiateMsg = { + params: this.storage.addresses.params!, max_unlocking_positions: this.config.maxUnlockingPositions, - allowed_coins: this.config.allowedCoins, - vault_configs: this.config.vaults.map((v) => ({ config: v.config, vault: v.vault })), oracle: this.config.oracle.addr, owner: this.deployerAddr, red_bank: this.config.redBank.addr, - max_close_factor: this.config.maxCloseFactor, swapper: this.storage.addresses.swapper!, zapper: this.storage.addresses.zapper!, health_contract: this.storage.addresses.healthContract!, } - if (this.config.testActions) { - msg.vault_configs.push({ - vault: { - address: this.storage.addresses.mockVault!, - }, - config: this.config.testActions.vault.mock.config, - }) - } - await this.instantiate('creditManager', this.storage.codeIds.creditManager!, msg) } diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index ee594c7e1..18e40b93d 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -13,9 +13,9 @@ import { Action, Coin, ConfigUpdates, - VaultInstantiateConfig, } from '../../types/generated/mars-credit-manager/MarsCreditManager.types' import { MarsMockVaultQueryClient } from '../../types/generated/mars-mock-vault/MarsMockVault.client' +import { VaultConfigBaseForString } from '../../types/generated/mars-params/MarsParams.types' export class Rover { private exec: MarsCreditManagerClient @@ -184,7 +184,7 @@ export class Rover { ) } - async vaultDeposit(v: VaultInstantiateConfig, info: VaultInfo) { + async vaultDeposit(v: VaultConfigBaseForString, info: VaultInfo) { const oldRoverBalance = await this.cwClient.getBalance( this.storage.addresses.creditManager!, info.tokens.vault_token, @@ -196,13 +196,13 @@ export class Rover { amount: { exact: this.actions.vault.depositAmount }, denom: info.tokens.base_token, }, - vault: { address: v.vault.address }, + vault: { address: v.addr }, }, }, ]) const positions = await this.query.positions({ accountId: this.accountId! }) assert.equal(positions.vaults.length, 1) - const state = await this.getVaultBalance(v.vault.address) + const state = await this.getVaultBalance(v.addr) assert(state.locked > 0 || state.unlocked > 0) const newRoverBalance = await this.cwClient.getBalance( this.storage.addresses.creditManager!, @@ -220,13 +220,13 @@ export class Rover { ) } - async vaultWithdraw(v: VaultInstantiateConfig, info: VaultInfo) { + async vaultWithdraw(v: VaultConfigBaseForString, info: VaultInfo) { const oldBalance = await this.getAccountBalance(info.tokens.base_token) await this.updateCreditAccount([ { exit_vault: { amount: this.actions.vault.withdrawAmount, - vault: { address: v.vault.address }, + vault: { address: v.addr }, }, }, ]) @@ -239,17 +239,17 @@ export class Rover { ) } - async vaultRequestUnlock(v: VaultInstantiateConfig, info: VaultInfo) { - const oldBalance = await this.getVaultBalance(v.vault.address) + async vaultRequestUnlock(v: VaultConfigBaseForString, info: VaultInfo) { + const oldBalance = await this.getVaultBalance(v.addr) await this.updateCreditAccount([ { request_vault_unlock: { amount: this.actions.vault.withdrawAmount, - vault: { address: v.vault.address }, + vault: { address: v.addr }, }, }, ]) - const newBalance = await this.getVaultBalance(v.vault.address) + const newBalance = await this.getVaultBalance(v.addr) assert(newBalance.locked < oldBalance.locked) assert.equal(newBalance.unlocking.length, 1) @@ -269,17 +269,17 @@ export class Rover { printGreen(`Withdrew all balances back to wallet`) } - async getVaultInfo(v: VaultInstantiateConfig): Promise { - const client = new MarsMockVaultQueryClient(this.cwClient, v.vault.address) + async getVaultInfo(v: VaultConfigBaseForString): Promise { + const client = new MarsMockVaultQueryClient(this.cwClient, v.addr) return { tokens: await client.info(), lockup: await this.getLockup(v), } } - private async getLockup(v: VaultInstantiateConfig): Promise { + private async getLockup(v: VaultConfigBaseForString): Promise { try { - return await this.cwClient.queryContractSmart(v.vault.address, { + return await this.cwClient.queryContractSmart(v.addr, { vault_extension: { lockup: { lockup_duration: {}, diff --git a/scripts/deploy/osmosis/mainnnet.ts b/scripts/deploy/osmosis/mainnnet.ts index 8426dcdb2..4e308c86f 100644 --- a/scripts/deploy/osmosis/mainnnet.ts +++ b/scripts/deploy/osmosis/mainnnet.ts @@ -24,6 +24,7 @@ export const osmosisMainnetConfig: DeploymentConfig = { // oracle and redbank contract addresses can be found: https://github.com/mars-protocol/red-bank/blob/master/README.md#osmosis-1 oracle: { addr: 'osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g' }, redBank: { addr: 'osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg' }, + params: { addr: 'TBD' }, swapRoutes: [ { denomIn: uosmo, denomOut: uatom, route: [{ token_out_denom: uatom, pool_id: '1' }] }, { denomIn: uatom, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '1' }] }, @@ -33,22 +34,18 @@ export const osmosisMainnetConfig: DeploymentConfig = { // Latest from: https://stats.apollo.farm/api/vaults/v1/all vaults: [ { - vault: { address: 'osmo1g3kmqpp8608szfp0pdag3r6z85npph7wmccat8lgl3mp407kv73qlj7qwp' }, - config: { - deposit_cap: { denom: axlUSDC, amount: '2000000000000' }, // $2M - max_ltv: '0.63', - liquidation_threshold: '0.65', - whitelisted: true, - }, + addr: 'osmo1g3kmqpp8608szfp0pdag3r6z85npph7wmccat8lgl3mp407kv73qlj7qwp', + deposit_cap: { denom: axlUSDC, amount: '2000000000000' }, // $2M + max_loan_to_value: '0.63', + liquidation_threshold: '0.65', + whitelisted: true, }, { - vault: { address: 'osmo1jfmwayj8jqp9tfy4v4eks5c2jpnqdumn8x8xvfllng0wfes770qqp7jl4j' }, - config: { - deposit_cap: { denom: axlUSDC, amount: '750000000000' }, // $750k - max_ltv: '0.65', - liquidation_threshold: '0.66', - whitelisted: true, - }, + addr: 'osmo1jfmwayj8jqp9tfy4v4eks5c2jpnqdumn8x8xvfllng0wfes770qqp7jl4j', + deposit_cap: { denom: axlUSDC, amount: '750000000000' }, // $750k + max_loan_to_value: '0.65', + liquidation_threshold: '0.66', + whitelisted: true, }, ], swapperContractName: 'mars_swapper_osmosis', diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index 90d3e955d..83f8b6ec9 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -10,26 +10,24 @@ const gammPool497 = 'gamm/pool/497' const vaultOsmoAtom1 = 'osmo1zktjv92f76epswjvyxzzt3yyskpw7k6jsyu0kmq4zzc5fphrjumqlahctp' const vaultOsmoAtom7 = 'osmo167j3yttwzcm3785tzk4jse2qdkppcy2xxrn5u6srqv7s93wnq6yqw8zhg5' const vaultOsmoAtom14 = 'osmo1tp2m6g39h8mvhnu3plqjyen5s63023gj8w873l8wvly0cd77l6hsaa73wt' -const atomOsmoConfig = { - config: { - deposit_cap: { denom: uatom, amount: '1000000000' }, // 1000 atom - max_ltv: '0.63', - liquidation_threshold: '0.65', - whitelisted: true, - }, -} +const atomOsmoConfig = (addr: string) => ({ + addr, + deposit_cap: { denom: uatom, amount: '1000000000' }, // 1000 atom + max_loan_to_value: '0.63', + liquidation_threshold: '0.65', + whitelisted: true, +}) const vaultJunoOsmo1 = 'osmo1r6h0pafu3wq0kf6yv09qhc8qvuku2d6fua0rpwwv46h7hd8u586scxspjf' const vaultJunoOsmo7 = 'osmo1gr5epxn67q6202l3hy0mcnu7qc039v22pa6x2tsk23zwg235n9jsq6pmes' const vaultJunoOsmo14 = 'osmo1d6knwkelyr9eklewnn9htkess4ttpxpf2cze9ec0xfw7e3fj0ggssqzfpp' -const junoOsmoConfig = { - config: { - deposit_cap: { denom: uatom, amount: '500000000' }, // 500 atom - max_ltv: '0.65', - liquidation_threshold: '0.66', - whitelisted: true, - }, -} +const junoOsmoConfig = (addr: string) => ({ + addr, + deposit_cap: { denom: uatom, amount: '500000000' }, // 500 atom + max_loan_to_value: '0.65', + liquidation_threshold: '0.66', + whitelisted: true, +}) export const osmosisTestnetConfig: DeploymentConfig = { allowedCoins: [uosmo, uatom, ujuno, gammPool1, gammPool497], @@ -48,6 +46,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { // Latest from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-4.json oracle: { addr: 'osmo1dqz2u3c8rs5e7w5fnchsr2mpzzsxew69wtdy0aq4jsd76w7upmsstqe0s8' }, redBank: { addr: 'osmo1t0dl6r27phqetfu0geaxrng0u9zn8qgrdwztapt5xr32adtwptaq6vwg36' }, + params: { addr: 'xyz' }, // TODO: add with build scripts update task swapRoutes: [ { denomIn: uosmo, denomOut: uatom, route: [{ token_out_denom: uatom, pool_id: '1' }] }, { denomIn: uatom, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '1' }] }, @@ -56,30 +55,12 @@ export const osmosisTestnetConfig: DeploymentConfig = { ], // Latest from: https://stats.apollo.farm/api/vaults/v1/all vaults: [ - { - vault: { address: vaultOsmoAtom1 }, - ...atomOsmoConfig, - }, - { - vault: { address: vaultOsmoAtom7 }, - ...atomOsmoConfig, - }, - { - vault: { address: vaultOsmoAtom14 }, - ...atomOsmoConfig, - }, - { - vault: { address: vaultJunoOsmo1 }, - ...junoOsmoConfig, - }, - { - vault: { address: vaultJunoOsmo7 }, - ...junoOsmoConfig, - }, - { - vault: { address: vaultJunoOsmo14 }, - ...junoOsmoConfig, - }, + atomOsmoConfig(vaultOsmoAtom1), + atomOsmoConfig(vaultOsmoAtom7), + atomOsmoConfig(vaultOsmoAtom14), + junoOsmoConfig(vaultJunoOsmo1), + junoOsmoConfig(vaultJunoOsmo7), + junoOsmoConfig(vaultJunoOsmo14), ], swapperContractName: 'mars_swapper_osmosis', zapperContractName: 'mars_zapper_osmosis', @@ -114,7 +95,7 @@ export const osmosisTestnetConfig: DeploymentConfig = { config: { deposit_cap: { denom: uosmo, amount: '100000000' }, // 100 osmo liquidation_threshold: '0.585', - max_ltv: '0.569', + max_loan_to_value: '0.569', whitelisted: true, }, vaultTokenDenom: udig, diff --git a/scripts/health/DataFetcher.ts b/scripts/health/DataFetcher.ts index bc16fea89..880765179 100644 --- a/scripts/health/DataFetcher.ts +++ b/scripts/health/DataFetcher.ts @@ -8,15 +8,15 @@ import { VaultsData, } from '../types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types' import { MarsMockOracleQueryClient } from '../types/generated/mars-mock-oracle/MarsMockOracle.client' -import { MarsMockRedBankQueryClient } from '../types/generated/mars-mock-red-bank/MarsMockRedBank.client' import { MarsMockVaultQueryClient } from '../types/generated/mars-mock-vault/MarsMockVault.client' +import { MarsParamsQueryClient } from '../types/generated/mars-params/MarsParams.client' export class DataFetcher { constructor( private healthComputer: (h: HealthComputer) => HealthResponse, private creditManagerAddr: string, private oracleAddr: string, - private redBankAddr: string, + private paramsAddr: string, private rpcEndpoint: string, ) {} @@ -29,14 +29,17 @@ export class DataFetcher { return await cmQuery.positions({ accountId }) } - fetchMarkets = async (denoms: string[]): Promise => { - const rQuery = new MarsMockRedBankQueryClient(await this.getClient(), this.redBankAddr) - const promises = denoms.map(async (denom) => await rQuery.market({ denom })) + fetchParams = async (denoms: string[]): Promise => { + const pQuery = new MarsParamsQueryClient(await this.getClient(), this.paramsAddr) + const promises = denoms.map(async (denom) => ({ + denom: denom, + params: await pQuery.assetParams({ denom }), + })) const responses = await Promise.all(promises) return responses.reduce((acc, curr) => { - acc[curr.denom] = curr + acc[curr.denom] = curr.params return acc - }, {} as DenomsData['markets']) + }, {} as DenomsData['params']) } fetchPrices = async (denoms: string[]): Promise => { @@ -63,26 +66,24 @@ export class DataFetcher { const allDenoms = depositDenoms.concat(debtDenoms).concat(vaultBaseTokenDenoms) return { - markets: await this.fetchMarkets(allDenoms), + params: await this.fetchParams(allDenoms), prices: await this.fetchPrices(allDenoms), } } - fetchAllowedCoins = async (): Promise => { - const cmQuery = new MarsCreditManagerQueryClient(await this.getClient(), this.creditManagerAddr) - return await cmQuery.allowedCoins({}) - } - fetchVaultsData = async (positions: Positions): Promise => { const vaultsData = { vault_values: {}, vault_configs: {} } as VaultsData const cmQuery = new MarsCreditManagerQueryClient(await this.getClient(), this.creditManagerAddr) + const pQuery = new MarsParamsQueryClient(await this.getClient(), this.paramsAddr) await Promise.all( positions.vaults.map(async (v) => { - const values = await cmQuery.vaultPositionValue({ vaultPosition: v }) - vaultsData.vault_values[v.vault.address] = values + vaultsData.vault_values[v.vault.address] = await cmQuery.vaultPositionValue({ + vaultPosition: v, + }) - const res = await cmQuery.vaultConfig({ vault: v.vault }) - vaultsData.vault_configs[v.vault.address] = res.config + vaultsData.vault_configs[v.vault.address] = await pQuery.vaultConfig({ + address: v.vault.address, + }) }), ) return vaultsData @@ -91,16 +92,14 @@ export class DataFetcher { fetchHealth = async (accountId: string): Promise => { const positions = await this.fetchPositions(accountId) - const [denoms_data, vaults_data, allowed_coins] = await Promise.all([ + const [denoms_data, vaults_data] = await Promise.all([ this.fetchDenomsData(positions), this.fetchVaultsData(positions), - this.fetchAllowedCoins(), ]) let data = { positions, denoms_data, - allowed_coins, vaults_data, } return this.healthComputer(data) diff --git a/scripts/health/example-node.ts b/scripts/health/example-node.ts index 56f24cc96..8001c7254 100644 --- a/scripts/health/example-node.ts +++ b/scripts/health/example-node.ts @@ -7,7 +7,7 @@ import OsmosisAddresses from '../deploy/addresses/osmo-test-4.json' compute_health_js, OsmosisAddresses.creditManager, osmosisTestnetConfig.oracle.addr, - osmosisTestnetConfig.redBank.addr, + osmosisTestnetConfig.params.addr, osmosisTestnetConfig.chain.rpcEndpoint, ) const health = await dataFetcher.fetchHealth('9') diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js index bfa4afdd9..45ae03046 100644 --- a/scripts/health/pkg-node/index.js +++ b/scripts/health/pkg-node/index.js @@ -1,7 +1,7 @@ let imports = {} imports['__wbindgen_placeholder__'] = module.exports let wasm -const { TextDecoder, TextEncoder } = require(`util`) +const { TextEncoder, TextDecoder } = require(`util`) const heap = new Array(128).fill(undefined) @@ -25,18 +25,7 @@ function takeObject(idx) { return ret } -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1) - const idx = heap_next - heap_next = heap[idx] - - heap[idx] = obj - return idx -} - -let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) - -cachedTextDecoder.decode() +let WASM_VECTOR_LEN = 0 let cachedUint8Memory0 = null @@ -47,12 +36,6 @@ function getUint8Memory0() { return cachedUint8Memory0 } -function getStringFromWasm0(ptr, len) { - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)) -} - -let WASM_VECTOR_LEN = 0 - let cachedTextEncoder = new TextEncoder('utf-8') const encodeString = @@ -72,7 +55,7 @@ const encodeString = function passStringToWasm0(arg, malloc, realloc) { if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg) - const ptr = malloc(buf.length) + const ptr = malloc(buf.length) >>> 0 getUint8Memory0() .subarray(ptr, ptr + buf.length) .set(buf) @@ -81,7 +64,7 @@ function passStringToWasm0(arg, malloc, realloc) { } let len = arg.length - let ptr = malloc(len) + let ptr = malloc(len) >>> 0 const mem = getUint8Memory0() @@ -97,7 +80,7 @@ function passStringToWasm0(arg, malloc, realloc) { if (offset !== 0) { arg = arg.slice(offset) } - ptr = realloc(ptr, len, (len = offset + arg.length * 3)) + ptr = realloc(ptr, len, (len = offset + arg.length * 3)) >>> 0 const view = getUint8Memory0().subarray(ptr + offset, ptr + len) const ret = encodeString(arg, view) @@ -121,6 +104,24 @@ function getInt32Memory0() { return cachedInt32Memory0 } +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) + +cachedTextDecoder.decode() + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0 + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)) +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1) + const idx = heap_next + heap_next = heap[idx] + + heap[idx] = obj + return idx +} + let cachedFloat64Memory0 = null function getFloat64Memory0() { @@ -224,26 +225,6 @@ module.exports.__wbindgen_object_drop_ref = function (arg0) { takeObject(arg0) } -module.exports.__wbindgen_is_bigint = function (arg0) { - const ret = typeof getObject(arg0) === 'bigint' - return ret -} - -module.exports.__wbindgen_bigint_from_u64 = function (arg0) { - const ret = BigInt.asUintN(64, arg0) - return addHeapObject(ret) -} - -module.exports.__wbindgen_jsval_eq = function (arg0, arg1) { - const ret = getObject(arg0) === getObject(arg1) - return ret -} - -module.exports.__wbindgen_error_new = function (arg0, arg1) { - const ret = new Error(getStringFromWasm0(arg0, arg1)) - return addHeapObject(ret) -} - module.exports.__wbindgen_is_object = function (arg0) { const val = getObject(arg0) const ret = typeof val === 'object' && val !== null @@ -263,12 +244,32 @@ module.exports.__wbindgen_in = function (arg0, arg1) { module.exports.__wbindgen_string_get = function (arg0, arg1) { const obj = getObject(arg1) const ret = typeof obj === 'string' ? obj : undefined - var ptr0 = isLikeNone(ret) + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) - var len0 = WASM_VECTOR_LEN - getInt32Memory0()[arg0 / 4 + 1] = len0 - getInt32Memory0()[arg0 / 4 + 0] = ptr0 + var len1 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len1 + getInt32Memory0()[arg0 / 4 + 0] = ptr1 +} + +module.exports.__wbindgen_error_new = function (arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)) + return addHeapObject(ret) +} + +module.exports.__wbindgen_is_bigint = function (arg0) { + const ret = typeof getObject(arg0) === 'bigint' + return ret +} + +module.exports.__wbindgen_bigint_from_u64 = function (arg0) { + const ret = BigInt.asUintN(64, arg0) + return addHeapObject(ret) +} + +module.exports.__wbindgen_jsval_eq = function (arg0, arg1) { + const ret = getObject(arg0) === getObject(arg1) + return ret } module.exports.__wbindgen_boolean_get = function (arg0) { @@ -289,17 +290,21 @@ module.exports.__wbg_new_abda76e883ba8a5f = function () { module.exports.__wbg_stack_658279fe44541cf6 = function (arg0, arg1) { const ret = getObject(arg1).stack - const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) - const len0 = WASM_VECTOR_LEN - getInt32Memory0()[arg0 / 4 + 1] = len0 - getInt32Memory0()[arg0 / 4 + 0] = ptr0 + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len1 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len1 + getInt32Memory0()[arg0 / 4 + 0] = ptr1 } module.exports.__wbg_error_f851667af71bcfc6 = function (arg0, arg1) { + let deferred0_0 + let deferred0_1 try { + deferred0_0 = arg0 + deferred0_1 = arg1 console.error(getStringFromWasm0(arg0, arg1)) } finally { - wasm.__wbindgen_free(arg0, arg1) + wasm.__wbindgen_free(deferred0_0, deferred0_1) } } @@ -334,12 +339,12 @@ module.exports.__wbg_set_841ac57cff3d672b = function (arg0, arg1, arg2) { getObject(arg0)[takeObject(arg1)] = takeObject(arg2) } -module.exports.__wbg_get_27fe3dac1c4d0224 = function (arg0, arg1) { +module.exports.__wbg_get_7303ed2ef026b2f5 = function (arg0, arg1) { const ret = getObject(arg0)[arg1 >>> 0] return addHeapObject(ret) } -module.exports.__wbg_length_e498fbc24f9c1d4f = function (arg0) { +module.exports.__wbg_length_820c786973abdd8a = function (arg0) { const ret = getObject(arg0).length return ret } @@ -349,58 +354,58 @@ module.exports.__wbindgen_is_function = function (arg0) { return ret } -module.exports.__wbg_next_b7d530c04fd8b217 = function (arg0) { +module.exports.__wbg_next_f4bc0e96ea67da68 = function (arg0) { const ret = getObject(arg0).next return addHeapObject(ret) } -module.exports.__wbg_next_88560ec06a094dea = function () { +module.exports.__wbg_next_ec061e48a0e72a96 = function () { return handleError(function (arg0) { const ret = getObject(arg0).next() return addHeapObject(ret) }, arguments) } -module.exports.__wbg_done_1ebec03bbd919843 = function (arg0) { +module.exports.__wbg_done_b6abb27d42b63867 = function (arg0) { const ret = getObject(arg0).done return ret } -module.exports.__wbg_value_6ac8da5cc5b3efda = function (arg0) { +module.exports.__wbg_value_2f4ef2036bfad28e = function (arg0) { const ret = getObject(arg0).value return addHeapObject(ret) } -module.exports.__wbg_iterator_55f114446221aa5a = function () { +module.exports.__wbg_iterator_7c7e58f62eb84700 = function () { const ret = Symbol.iterator return addHeapObject(ret) } -module.exports.__wbg_get_baf4855f9a986186 = function () { +module.exports.__wbg_get_f53c921291c381bd = function () { return handleError(function (arg0, arg1) { const ret = Reflect.get(getObject(arg0), getObject(arg1)) return addHeapObject(ret) }, arguments) } -module.exports.__wbg_call_95d1ea488d03e4e8 = function () { +module.exports.__wbg_call_557a2f2deacc4912 = function () { return handleError(function (arg0, arg1) { const ret = getObject(arg0).call(getObject(arg1)) return addHeapObject(ret) }, arguments) } -module.exports.__wbg_new_f9876326328f45ed = function () { +module.exports.__wbg_new_2b6fea4ea03b1b95 = function () { const ret = new Object() return addHeapObject(ret) } -module.exports.__wbg_isArray_39d28997bf6b96b4 = function (arg0) { +module.exports.__wbg_isArray_04e59fb73f78ab5b = function (arg0) { const ret = Array.isArray(getObject(arg0)) return ret } -module.exports.__wbg_instanceof_ArrayBuffer_a69f02ee4c4f5065 = function (arg0) { +module.exports.__wbg_instanceof_ArrayBuffer_ef2632aa0d4bfff8 = function (arg0) { let result try { result = getObject(arg0) instanceof ArrayBuffer @@ -411,36 +416,36 @@ module.exports.__wbg_instanceof_ArrayBuffer_a69f02ee4c4f5065 = function (arg0) { return ret } -module.exports.__wbg_isSafeInteger_8c4789029e885159 = function (arg0) { +module.exports.__wbg_isSafeInteger_2088b01008075470 = function (arg0) { const ret = Number.isSafeInteger(getObject(arg0)) return ret } -module.exports.__wbg_entries_4e1315b774245952 = function (arg0) { +module.exports.__wbg_entries_13e011453776468f = function (arg0) { const ret = Object.entries(getObject(arg0)) return addHeapObject(ret) } -module.exports.__wbg_buffer_cf65c07de34b9a08 = function (arg0) { +module.exports.__wbg_buffer_55ba7a6b1b92e2ac = function (arg0) { const ret = getObject(arg0).buffer return addHeapObject(ret) } -module.exports.__wbg_new_537b7341ce90bb31 = function (arg0) { +module.exports.__wbg_new_09938a7d020f049b = function (arg0) { const ret = new Uint8Array(getObject(arg0)) return addHeapObject(ret) } -module.exports.__wbg_set_17499e8aa4003ebd = function (arg0, arg1, arg2) { +module.exports.__wbg_set_3698e3ca519b3c3c = function (arg0, arg1, arg2) { getObject(arg0).set(getObject(arg1), arg2 >>> 0) } -module.exports.__wbg_length_27a2afe8ab42b09f = function (arg0) { +module.exports.__wbg_length_0aab7ffd65ad19ed = function (arg0) { const ret = getObject(arg0).length return ret } -module.exports.__wbg_instanceof_Uint8Array_01cebe79ca606cca = function (arg0) { +module.exports.__wbg_instanceof_Uint8Array_1349640af2da2e88 = function (arg0) { let result try { result = getObject(arg0) instanceof Uint8Array @@ -460,10 +465,10 @@ module.exports.__wbindgen_bigint_get_as_i64 = function (arg0, arg1) { module.exports.__wbindgen_debug_string = function (arg0, arg1) { const ret = debugString(getObject(arg1)) - const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) - const len0 = WASM_VECTOR_LEN - getInt32Memory0()[arg0 / 4 + 1] = len0 - getInt32Memory0()[arg0 / 4 + 0] = ptr0 + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len1 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len1 + getInt32Memory0()[arg0 / 4 + 0] = ptr1 } module.exports.__wbindgen_throw = function (arg0, arg1) { diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index b2e9aaa117afacf76a1500990fa0fb2ca882d34b..7688f33c3b0cca97e7dbb8f069e192e1d2814f78 100644 GIT binary patch literal 157988 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νH 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%0AvrN+G~*&#~!)4?GhJ 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 diff --git a/scripts/health/pkg-node/package.json b/scripts/health/pkg-node/package.json index 0708ff70e..fe512637d 100644 --- a/scripts/health/pkg-node/package.json +++ b/scripts/health/pkg-node/package.json @@ -5,7 +5,7 @@ "Larry Engineer ", "Piotr Babel " ], - "version": "1.0.0", + "version": "2.0.0", "files": [ "index_bg.wasm", "index.js", diff --git a/scripts/health/pkg-web/index.d.ts b/scripts/health/pkg-web/index.d.ts index b52017ac2..dde69fae5 100644 --- a/scripts/health/pkg-web/index.d.ts +++ b/scripts/health/pkg-web/index.d.ts @@ -40,4 +40,6 @@ export function initSync(module: SyncInitInput): InitOutput * * @returns {Promise} */ -export default function init(module_or_path?: InitInput | Promise): Promise +export default function __wbg_init( + module_or_path?: InitInput | Promise, +): Promise diff --git a/scripts/health/pkg-web/index.js b/scripts/health/pkg-web/index.js index 46af0ba5d..884616242 100644 --- a/scripts/health/pkg-web/index.js +++ b/scripts/health/pkg-web/index.js @@ -22,18 +22,7 @@ function takeObject(idx) { return ret } -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1) - const idx = heap_next - heap_next = heap[idx] - - heap[idx] = obj - return idx -} - -const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) - -cachedTextDecoder.decode() +let WASM_VECTOR_LEN = 0 let cachedUint8Memory0 = null @@ -44,13 +33,14 @@ function getUint8Memory0() { return cachedUint8Memory0 } -function getStringFromWasm0(ptr, len) { - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)) -} - -let WASM_VECTOR_LEN = 0 - -const cachedTextEncoder = new TextEncoder('utf-8') +const cachedTextEncoder = + typeof TextEncoder !== 'undefined' + ? new TextEncoder('utf-8') + : { + encode: () => { + throw Error('TextEncoder not available') + }, + } const encodeString = typeof cachedTextEncoder.encodeInto === 'function' @@ -69,7 +59,7 @@ const encodeString = function passStringToWasm0(arg, malloc, realloc) { if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg) - const ptr = malloc(buf.length) + const ptr = malloc(buf.length) >>> 0 getUint8Memory0() .subarray(ptr, ptr + buf.length) .set(buf) @@ -78,7 +68,7 @@ function passStringToWasm0(arg, malloc, realloc) { } let len = arg.length - let ptr = malloc(len) + let ptr = malloc(len) >>> 0 const mem = getUint8Memory0() @@ -94,7 +84,7 @@ function passStringToWasm0(arg, malloc, realloc) { if (offset !== 0) { arg = arg.slice(offset) } - ptr = realloc(ptr, len, (len = offset + arg.length * 3)) + ptr = realloc(ptr, len, (len = offset + arg.length * 3)) >>> 0 const view = getUint8Memory0().subarray(ptr + offset, ptr + len) const ret = encodeString(arg, view) @@ -118,6 +108,33 @@ function getInt32Memory0() { return cachedInt32Memory0 } +const cachedTextDecoder = + typeof TextDecoder !== 'undefined' + ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) + : { + decode: () => { + throw Error('TextDecoder not available') + }, + } + +if (typeof TextDecoder !== 'undefined') { + cachedTextDecoder.decode() +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0 + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)) +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1) + const idx = heap_next + heap_next = heap[idx] + + heap[idx] = obj + return idx +} + let cachedFloat64Memory0 = null function getFloat64Memory0() { @@ -217,7 +234,7 @@ function handleError(f, args) { } } -async function load(module, imports) { +async function __wbg_load(module, imports) { if (typeof Response === 'function' && module instanceof Response) { if (typeof WebAssembly.instantiateStreaming === 'function') { try { @@ -247,28 +264,12 @@ async function load(module, imports) { } } -function getImports() { +function __wbg_get_imports() { const imports = {} imports.wbg = {} imports.wbg.__wbindgen_object_drop_ref = function (arg0) { takeObject(arg0) } - imports.wbg.__wbindgen_is_bigint = function (arg0) { - const ret = typeof getObject(arg0) === 'bigint' - return ret - } - imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) { - const ret = BigInt.asUintN(64, arg0) - return addHeapObject(ret) - } - imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) { - const ret = getObject(arg0) === getObject(arg1) - return ret - } - imports.wbg.__wbindgen_error_new = function (arg0, arg1) { - const ret = new Error(getStringFromWasm0(arg0, arg1)) - return addHeapObject(ret) - } imports.wbg.__wbindgen_is_object = function (arg0) { const val = getObject(arg0) const ret = typeof val === 'object' && val !== null @@ -285,12 +286,28 @@ function getImports() { imports.wbg.__wbindgen_string_get = function (arg0, arg1) { const obj = getObject(arg1) const ret = typeof obj === 'string' ? obj : undefined - var ptr0 = isLikeNone(ret) + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) - var len0 = WASM_VECTOR_LEN - getInt32Memory0()[arg0 / 4 + 1] = len0 - getInt32Memory0()[arg0 / 4 + 0] = ptr0 + var len1 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len1 + getInt32Memory0()[arg0 / 4 + 0] = ptr1 + } + imports.wbg.__wbindgen_error_new = function (arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)) + return addHeapObject(ret) + } + imports.wbg.__wbindgen_is_bigint = function (arg0) { + const ret = typeof getObject(arg0) === 'bigint' + return ret + } + imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) { + const ret = BigInt.asUintN(64, arg0) + return addHeapObject(ret) + } + imports.wbg.__wbindgen_jsval_eq = function (arg0, arg1) { + const ret = getObject(arg0) === getObject(arg1) + return ret } imports.wbg.__wbindgen_boolean_get = function (arg0) { const v = getObject(arg0) @@ -307,16 +324,20 @@ function getImports() { } imports.wbg.__wbg_stack_658279fe44541cf6 = function (arg0, arg1) { const ret = getObject(arg1).stack - const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) - const len0 = WASM_VECTOR_LEN - getInt32Memory0()[arg0 / 4 + 1] = len0 - getInt32Memory0()[arg0 / 4 + 0] = ptr0 + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len1 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len1 + getInt32Memory0()[arg0 / 4 + 0] = ptr1 } imports.wbg.__wbg_error_f851667af71bcfc6 = function (arg0, arg1) { + let deferred0_0 + let deferred0_1 try { + deferred0_0 = arg0 + deferred0_1 = arg1 console.error(getStringFromWasm0(arg0, arg1)) } finally { - wasm.__wbindgen_free(arg0, arg1) + wasm.__wbindgen_free(deferred0_0, deferred0_1) } } imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) { @@ -344,11 +365,11 @@ function getImports() { imports.wbg.__wbg_set_841ac57cff3d672b = function (arg0, arg1, arg2) { getObject(arg0)[takeObject(arg1)] = takeObject(arg2) } - imports.wbg.__wbg_get_27fe3dac1c4d0224 = function (arg0, arg1) { + imports.wbg.__wbg_get_7303ed2ef026b2f5 = function (arg0, arg1) { const ret = getObject(arg0)[arg1 >>> 0] return addHeapObject(ret) } - imports.wbg.__wbg_length_e498fbc24f9c1d4f = function (arg0) { + imports.wbg.__wbg_length_820c786973abdd8a = function (arg0) { const ret = getObject(arg0).length return ret } @@ -356,49 +377,49 @@ function getImports() { const ret = typeof getObject(arg0) === 'function' return ret } - imports.wbg.__wbg_next_b7d530c04fd8b217 = function (arg0) { + imports.wbg.__wbg_next_f4bc0e96ea67da68 = function (arg0) { const ret = getObject(arg0).next return addHeapObject(ret) } - imports.wbg.__wbg_next_88560ec06a094dea = function () { + imports.wbg.__wbg_next_ec061e48a0e72a96 = function () { return handleError(function (arg0) { const ret = getObject(arg0).next() return addHeapObject(ret) }, arguments) } - imports.wbg.__wbg_done_1ebec03bbd919843 = function (arg0) { + imports.wbg.__wbg_done_b6abb27d42b63867 = function (arg0) { const ret = getObject(arg0).done return ret } - imports.wbg.__wbg_value_6ac8da5cc5b3efda = function (arg0) { + imports.wbg.__wbg_value_2f4ef2036bfad28e = function (arg0) { const ret = getObject(arg0).value return addHeapObject(ret) } - imports.wbg.__wbg_iterator_55f114446221aa5a = function () { + imports.wbg.__wbg_iterator_7c7e58f62eb84700 = function () { const ret = Symbol.iterator return addHeapObject(ret) } - imports.wbg.__wbg_get_baf4855f9a986186 = function () { + imports.wbg.__wbg_get_f53c921291c381bd = function () { return handleError(function (arg0, arg1) { const ret = Reflect.get(getObject(arg0), getObject(arg1)) return addHeapObject(ret) }, arguments) } - imports.wbg.__wbg_call_95d1ea488d03e4e8 = function () { + imports.wbg.__wbg_call_557a2f2deacc4912 = function () { return handleError(function (arg0, arg1) { const ret = getObject(arg0).call(getObject(arg1)) return addHeapObject(ret) }, arguments) } - imports.wbg.__wbg_new_f9876326328f45ed = function () { + imports.wbg.__wbg_new_2b6fea4ea03b1b95 = function () { const ret = new Object() return addHeapObject(ret) } - imports.wbg.__wbg_isArray_39d28997bf6b96b4 = function (arg0) { + imports.wbg.__wbg_isArray_04e59fb73f78ab5b = function (arg0) { const ret = Array.isArray(getObject(arg0)) return ret } - imports.wbg.__wbg_instanceof_ArrayBuffer_a69f02ee4c4f5065 = function (arg0) { + imports.wbg.__wbg_instanceof_ArrayBuffer_ef2632aa0d4bfff8 = function (arg0) { let result try { result = getObject(arg0) instanceof ArrayBuffer @@ -408,30 +429,30 @@ function getImports() { const ret = result return ret } - imports.wbg.__wbg_isSafeInteger_8c4789029e885159 = function (arg0) { + imports.wbg.__wbg_isSafeInteger_2088b01008075470 = function (arg0) { const ret = Number.isSafeInteger(getObject(arg0)) return ret } - imports.wbg.__wbg_entries_4e1315b774245952 = function (arg0) { + imports.wbg.__wbg_entries_13e011453776468f = function (arg0) { const ret = Object.entries(getObject(arg0)) return addHeapObject(ret) } - imports.wbg.__wbg_buffer_cf65c07de34b9a08 = function (arg0) { + imports.wbg.__wbg_buffer_55ba7a6b1b92e2ac = function (arg0) { const ret = getObject(arg0).buffer return addHeapObject(ret) } - imports.wbg.__wbg_new_537b7341ce90bb31 = function (arg0) { + imports.wbg.__wbg_new_09938a7d020f049b = function (arg0) { const ret = new Uint8Array(getObject(arg0)) return addHeapObject(ret) } - imports.wbg.__wbg_set_17499e8aa4003ebd = function (arg0, arg1, arg2) { + imports.wbg.__wbg_set_3698e3ca519b3c3c = function (arg0, arg1, arg2) { getObject(arg0).set(getObject(arg1), arg2 >>> 0) } - imports.wbg.__wbg_length_27a2afe8ab42b09f = function (arg0) { + imports.wbg.__wbg_length_0aab7ffd65ad19ed = function (arg0) { const ret = getObject(arg0).length return ret } - imports.wbg.__wbg_instanceof_Uint8Array_01cebe79ca606cca = function (arg0) { + imports.wbg.__wbg_instanceof_Uint8Array_1349640af2da2e88 = function (arg0) { let result try { result = getObject(arg0) instanceof Uint8Array @@ -449,10 +470,10 @@ function getImports() { } imports.wbg.__wbindgen_debug_string = function (arg0, arg1) { const ret = debugString(getObject(arg1)) - const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) - const len0 = WASM_VECTOR_LEN - getInt32Memory0()[arg0 / 4 + 1] = len0 - getInt32Memory0()[arg0 / 4 + 0] = ptr0 + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len1 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len1 + getInt32Memory0()[arg0 / 4 + 0] = ptr1 } imports.wbg.__wbindgen_throw = function (arg0, arg1) { throw new Error(getStringFromWasm0(arg0, arg1)) @@ -465,11 +486,11 @@ function getImports() { return imports } -function initMemory(imports, maybe_memory) {} +function __wbg_init_memory(imports, maybe_memory) {} -function finalizeInit(instance, module) { +function __wbg_finalize_init(instance, module) { wasm = instance.exports - init.__wbindgen_wasm_module = module + __wbg_init.__wbindgen_wasm_module = module cachedBigInt64Memory0 = null cachedFloat64Memory0 = null cachedInt32Memory0 = null @@ -479,9 +500,11 @@ function finalizeInit(instance, module) { } function initSync(module) { - const imports = getImports() + if (wasm !== undefined) return wasm - initMemory(imports) + const imports = __wbg_get_imports() + + __wbg_init_memory(imports) if (!(module instanceof WebAssembly.Module)) { module = new WebAssembly.Module(module) @@ -489,14 +512,16 @@ function initSync(module) { const instance = new WebAssembly.Instance(module, imports) - return finalizeInit(instance, module) + return __wbg_finalize_init(instance, module) } -async function init(input) { +async function __wbg_init(input) { + if (wasm !== undefined) return wasm + if (typeof input === 'undefined') { input = new URL('index_bg.wasm', import.meta.url) } - const imports = getImports() + const imports = __wbg_get_imports() if ( typeof input === 'string' || @@ -506,12 +531,12 @@ async function init(input) { input = fetch(input) } - initMemory(imports) + __wbg_init_memory(imports) - const { instance, module } = await load(await input, imports) + const { instance, module } = await __wbg_load(await input, imports) - return finalizeInit(instance, module) + return __wbg_finalize_init(instance, module) } export { initSync } -export default init +export default __wbg_init diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm index c26e7e0d6c463d71ce64b69e43971740faa21b77..0284534d5fb8fc29562198a1acd61ee761204d61 100644 GIT binary patch literal 157064 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≷KB4hoi;r)=Uo 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 Jc{tnu{{Z+Xzm@<1 diff --git a/scripts/health/pkg-web/package.json b/scripts/health/pkg-web/package.json index 351494756..777dd0e5c 100644 --- a/scripts/health/pkg-web/package.json +++ b/scripts/health/pkg-web/package.json @@ -5,7 +5,7 @@ "Larry Engineer ", "Piotr Babel " ], - "version": "1.0.0", + "version": "2.0.0", "files": [ "index_bg.wasm", "index.js", @@ -13,7 +13,9 @@ ], "module": "index.js", "types": "index.d.ts", - "sideEffects": false, + "sideEffects": [ + "./snippets/*" + ], "keywords": [ "mars", "cosmos", diff --git a/scripts/package.json b/scripts/package.json index 24e0062c7..7151fc9b5 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -22,22 +22,23 @@ "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.30.1", "@cosmjs/stargate": "^0.30.1", - "@cosmwasm/ts-codegen": "^0.27.0", + "@cosmwasm/ts-codegen": "^0.30.0", "chalk": "4.1.2", "copyfiles": "^2.4.1", - "cosmjs-types": "^0.7.2", + "cosmjs-types": "^0.8.0", "lodash": "^4.17.21", "long": "^5.2.3", "prepend-file": "^2.0.1", - "wasm-pack": "^0.10.3" + "simple-git": "^3.19.0", + "wasm-pack": "^0.11.1" }, "devDependencies": { - "@babel/preset-env": "^7.21.5", + "@babel/preset-env": "^7.22.4", "@babel/preset-typescript": "^7.21.5", "@types/jest": "^29.5.1", - "@typescript-eslint/eslint-plugin": "^5.59.2", - "@typescript-eslint/parser": "^5.59.2", - "eslint": "^8.39.0", + "@typescript-eslint/eslint-plugin": "^5.59.8", + "@typescript-eslint/parser": "^5.59.8", + "eslint": "^8.41.0", "eslint-config-prettier": "^8.8.0", "jest": "^29.5.0", "prettier": "^2.8.8", diff --git a/scripts/types/config.ts b/scripts/types/config.ts index 83d778ba8..88a5199b4 100644 --- a/scripts/types/config.ts +++ b/scripts/types/config.ts @@ -1,9 +1,6 @@ import { Duration, VaultInfoResponse } from './generated/mars-mock-vault/MarsMockVault.types' -import { - VaultConfig, - VaultInstantiateConfig, -} from './generated/mars-credit-manager/MarsCreditManager.types' import { PriceSource } from './priceSource' +import { VaultConfigBaseForString } from './generated/mars-params/MarsParams.types' export enum VaultType { LOCKED, @@ -26,7 +23,8 @@ export interface DeploymentConfig { deployerMnemonic: string oracle: { addr: string } redBank: { addr: string } - vaults: VaultInstantiateConfig[] + params: { addr: string } + vaults: VaultConfigBaseForString[] allowedCoins: string[] maxCloseFactor: string maxValueForBurn: string @@ -51,7 +49,7 @@ export interface TestActions { withdrawAmount: string mock: { type: VaultType - config: VaultConfig + config: Omit vaultTokenDenom: string lockup?: Duration baseToken: string diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts index 493016195..b47215fd0 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -283,7 +283,7 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise mint: ( { @@ -293,7 +293,7 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise burn: ( { @@ -303,7 +303,7 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise transferNft: ( { @@ -315,7 +315,7 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise sendNft: ( { @@ -329,7 +329,7 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise approve: ( { @@ -343,7 +343,7 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise revoke: ( { @@ -355,7 +355,7 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise approveAll: ( { @@ -367,7 +367,7 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise revokeAll: ( { @@ -377,13 +377,13 @@ export interface MarsAccountNftInterface extends MarsAccountNftReadOnlyInterface }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise updateOwnership: ( action: Action, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise } export class MarsAccountNftClient @@ -419,7 +419,7 @@ export class MarsAccountNftClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -431,7 +431,7 @@ export class MarsAccountNftClient }, fee, memo, - funds, + _funds, ) } mint = async ( @@ -442,7 +442,7 @@ export class MarsAccountNftClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -454,7 +454,7 @@ export class MarsAccountNftClient }, fee, memo, - funds, + _funds, ) } burn = async ( @@ -465,7 +465,7 @@ export class MarsAccountNftClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -477,7 +477,7 @@ export class MarsAccountNftClient }, fee, memo, - funds, + _funds, ) } transferNft = async ( @@ -490,7 +490,7 @@ export class MarsAccountNftClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -503,7 +503,7 @@ export class MarsAccountNftClient }, fee, memo, - funds, + _funds, ) } sendNft = async ( @@ -518,7 +518,7 @@ export class MarsAccountNftClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -532,7 +532,7 @@ export class MarsAccountNftClient }, fee, memo, - funds, + _funds, ) } approve = async ( @@ -547,7 +547,7 @@ export class MarsAccountNftClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -561,7 +561,7 @@ export class MarsAccountNftClient }, fee, memo, - funds, + _funds, ) } revoke = async ( @@ -574,7 +574,7 @@ export class MarsAccountNftClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -587,7 +587,7 @@ export class MarsAccountNftClient }, fee, memo, - funds, + _funds, ) } approveAll = async ( @@ -600,7 +600,7 @@ export class MarsAccountNftClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -613,7 +613,7 @@ export class MarsAccountNftClient }, fee, memo, - funds, + _funds, ) } revokeAll = async ( @@ -624,7 +624,7 @@ export class MarsAccountNftClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -636,14 +636,14 @@ export class MarsAccountNftClient }, fee, memo, - funds, + _funds, ) } updateOwnership = async ( action: Action, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -653,7 +653,7 @@ export class MarsAccountNftClient }, fee, memo, - funds, + _funds, ) } } diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts index e946b465a..50578b8f0 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts @@ -1,12 +1,12 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { Coin } from '@cosmjs/amino' -import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { @@ -45,7 +45,7 @@ export interface MarsAccountNftMessage { }: { updates: NftConfigUpdates }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject mint: ( { @@ -53,7 +53,7 @@ export interface MarsAccountNftMessage { }: { user: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject burn: ( { @@ -61,7 +61,7 @@ export interface MarsAccountNftMessage { }: { tokenId: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject transferNft: ( { @@ -71,7 +71,7 @@ export interface MarsAccountNftMessage { recipient: string tokenId: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject sendNft: ( { @@ -83,7 +83,7 @@ export interface MarsAccountNftMessage { msg: Binary tokenId: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject approve: ( { @@ -95,7 +95,7 @@ export interface MarsAccountNftMessage { spender: string tokenId: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject revoke: ( { @@ -105,7 +105,7 @@ export interface MarsAccountNftMessage { spender: string tokenId: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject approveAll: ( { @@ -115,7 +115,7 @@ export interface MarsAccountNftMessage { expires?: Expiration operator: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject revokeAll: ( { @@ -123,9 +123,9 @@ export interface MarsAccountNftMessage { }: { operator: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject - updateOwnership: (action: Action, funds?: Coin[]) => MsgExecuteContractEncodeObject + updateOwnership: (action: Action, _funds?: Coin[]) => MsgExecuteContractEncodeObject } export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { sender: string @@ -152,7 +152,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }: { updates: NftConfigUpdates }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -166,7 +166,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -176,7 +176,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }: { user: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -190,7 +190,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -200,7 +200,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }: { tokenId: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -214,7 +214,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -226,7 +226,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { recipient: string tokenId: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -241,7 +241,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -255,7 +255,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { msg: Binary tokenId: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -271,7 +271,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -285,7 +285,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { spender: string tokenId: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -301,7 +301,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -313,7 +313,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { spender: string tokenId: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -328,7 +328,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -340,7 +340,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { expires?: Expiration operator: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -355,7 +355,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -365,7 +365,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }: { operator: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -379,11 +379,11 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { }, }), ), - funds, + funds: _funds, }), } } - updateOwnership = (action: Action, funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateOwnership = (action: Action, _funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -394,7 +394,7 @@ export class MarsAccountNftMessageComposer implements MarsAccountNftMessage { update_ownership: action, }), ), - funds, + funds: _funds, }), } } diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts index 2ff173a14..3f0b9af84 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index e64439d97..62506e83e 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-account-nft/bundle.ts b/scripts/types/generated/mars-account-nft/bundle.ts index 6a43efab7..25b6c9448 100644 --- a/scripts/types/generated/mars-account-nft/bundle.ts +++ b/scripts/types/generated/mars-account-nft/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index d09073fed..31781f9e0 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -9,28 +9,26 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/co import { StdFee } from '@cosmjs/amino' import { HealthContractBaseForString, - Decimal, Uint128, OracleBaseForString, + ParamsBaseForString, RedBankBaseForString, SwapperBaseForString, ZapperBaseForString, InstantiateMsg, - VaultInstantiateConfig, - VaultConfig, - Coin, - VaultBaseForString, ExecuteMsg, Action, ActionAmount, LiquidateRequestForVaultBaseForString, VaultPositionType, - EmergencyUpdate, + Decimal, OwnerUpdate, CallbackMsg, Addr, LiquidateRequestForVaultBaseForAddr, + Coin, ActionCoin, + VaultBaseForString, ConfigUpdates, NftConfigUpdates, VaultBaseForAddr, @@ -50,42 +48,22 @@ import { DebtShares, ArrayOfLentShares, LentShares, - ArrayOfVaultWithBalance, - VaultWithBalance, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, - ArrayOfString, ConfigResponse, OwnerResponse, ArrayOfCoin, Positions, DebtAmount, LentAmount, - VaultConfigResponse, VaultPositionValue, CoinValue, VaultUtilizationResponse, - ArrayOfVaultConfigResponse, } from './MarsCreditManager.types' export interface MarsCreditManagerReadOnlyInterface { contractAddress: string config: () => Promise - vaultConfig: ({ vault }: { vault: VaultBaseForString }) => Promise - vaultsConfig: ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: VaultBaseForString - }) => Promise vaultUtilization: ({ vault }: { vault: VaultBaseForString }) => Promise - allowedCoins: ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string - }) => Promise positions: ({ accountId }: { accountId: string }) => Promise allCoinBalances: ({ limit, @@ -131,14 +109,6 @@ export interface MarsCreditManagerReadOnlyInterface { limit?: number startAfter?: string[][] }) => Promise - totalVaultCoinBalance: ({ vault }: { vault: VaultBaseForString }) => Promise - allTotalVaultCoinBalances: ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: VaultBaseForString - }) => Promise estimateProvideLiquidity: ({ coinsIn, lpTokenOut, @@ -161,10 +131,7 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn this.client = client this.contractAddress = contractAddress this.config = this.config.bind(this) - this.vaultConfig = this.vaultConfig.bind(this) - this.vaultsConfig = this.vaultsConfig.bind(this) this.vaultUtilization = this.vaultUtilization.bind(this) - this.allowedCoins = this.allowedCoins.bind(this) this.positions = this.positions.bind(this) this.allCoinBalances = this.allCoinBalances.bind(this) this.allDebtShares = this.allDebtShares.bind(this) @@ -174,8 +141,6 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn this.totalLentShares = this.totalLentShares.bind(this) this.allTotalLentShares = this.allTotalLentShares.bind(this) this.allVaultPositions = this.allVaultPositions.bind(this) - this.totalVaultCoinBalance = this.totalVaultCoinBalance.bind(this) - this.allTotalVaultCoinBalances = this.allTotalVaultCoinBalances.bind(this) this.estimateProvideLiquidity = this.estimateProvideLiquidity.bind(this) this.estimateWithdrawLiquidity = this.estimateWithdrawLiquidity.bind(this) this.vaultPositionValue = this.vaultPositionValue.bind(this) @@ -186,27 +151,6 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn config: {}, }) } - vaultConfig = async ({ vault }: { vault: VaultBaseForString }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - vault_config: { - vault, - }, - }) - } - vaultsConfig = async ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: VaultBaseForString - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - vaults_config: { - limit, - start_after: startAfter, - }, - }) - } vaultUtilization = async ({ vault, }: { @@ -218,20 +162,6 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn }, }) } - allowedCoins = async ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - allowed_coins: { - limit, - start_after: startAfter, - }, - }) - } positions = async ({ accountId }: { accountId: string }): Promise => { return this.client.queryContractSmart(this.contractAddress, { positions: { @@ -333,27 +263,6 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn }, }) } - totalVaultCoinBalance = async ({ vault }: { vault: VaultBaseForString }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - total_vault_coin_balance: { - vault, - }, - }) - } - allTotalVaultCoinBalances = async ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: VaultBaseForString - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - all_total_vault_coin_balances: { - limit, - start_after: startAfter, - }, - }) - } estimateProvideLiquidity = async ({ coinsIn, lpTokenOut, @@ -393,7 +302,7 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt createCreditAccount: ( fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise updateCreditAccount: ( { @@ -405,7 +314,7 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise repayFromWallet: ( { @@ -415,7 +324,7 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise updateConfig: ( { @@ -425,19 +334,13 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], - ) => Promise - emergencyConfigUpdate: ( - emergencyUpdate: EmergencyUpdate, - fee?: number | StdFee | 'auto', - memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise updateOwner: ( ownerUpdate: OwnerUpdate, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise updateNftConfig: ( { @@ -449,13 +352,13 @@ export interface MarsCreditManagerInterface extends MarsCreditManagerReadOnlyInt }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise callback: ( callbackMsg: CallbackMsg, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise } export class MarsCreditManagerClient @@ -475,7 +378,6 @@ export class MarsCreditManagerClient this.updateCreditAccount = this.updateCreditAccount.bind(this) this.repayFromWallet = this.repayFromWallet.bind(this) this.updateConfig = this.updateConfig.bind(this) - this.emergencyConfigUpdate = this.emergencyConfigUpdate.bind(this) this.updateOwner = this.updateOwner.bind(this) this.updateNftConfig = this.updateNftConfig.bind(this) this.callback = this.callback.bind(this) @@ -484,7 +386,7 @@ export class MarsCreditManagerClient createCreditAccount = async ( fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -494,7 +396,7 @@ export class MarsCreditManagerClient }, fee, memo, - funds, + _funds, ) } updateCreditAccount = async ( @@ -507,7 +409,7 @@ export class MarsCreditManagerClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -520,7 +422,7 @@ export class MarsCreditManagerClient }, fee, memo, - funds, + _funds, ) } repayFromWallet = async ( @@ -531,7 +433,7 @@ export class MarsCreditManagerClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -543,7 +445,7 @@ export class MarsCreditManagerClient }, fee, memo, - funds, + _funds, ) } updateConfig = async ( @@ -554,7 +456,7 @@ export class MarsCreditManagerClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -566,31 +468,14 @@ export class MarsCreditManagerClient }, fee, memo, - funds, - ) - } - emergencyConfigUpdate = async ( - emergencyUpdate: EmergencyUpdate, - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - emergency_config_update: emergencyUpdate, - }, - fee, - memo, - funds, + _funds, ) } updateOwner = async ( ownerUpdate: OwnerUpdate, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -600,7 +485,7 @@ export class MarsCreditManagerClient }, fee, memo, - funds, + _funds, ) } updateNftConfig = async ( @@ -613,7 +498,7 @@ export class MarsCreditManagerClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -626,14 +511,14 @@ export class MarsCreditManagerClient }, fee, memo, - funds, + _funds, ) } callback = async ( callbackMsg: CallbackMsg, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -643,7 +528,7 @@ export class MarsCreditManagerClient }, fee, memo, - funds, + _funds, ) } } diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 09fbc991d..31e1dea21 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -1,37 +1,35 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { HealthContractBaseForString, - Decimal, Uint128, OracleBaseForString, + ParamsBaseForString, RedBankBaseForString, SwapperBaseForString, ZapperBaseForString, InstantiateMsg, - VaultInstantiateConfig, - VaultConfig, - Coin, - VaultBaseForString, ExecuteMsg, Action, ActionAmount, LiquidateRequestForVaultBaseForString, VaultPositionType, - EmergencyUpdate, + Decimal, OwnerUpdate, CallbackMsg, Addr, LiquidateRequestForVaultBaseForAddr, + Coin, ActionCoin, + VaultBaseForString, ConfigUpdates, NftConfigUpdates, VaultBaseForAddr, @@ -51,27 +49,22 @@ import { DebtShares, ArrayOfLentShares, LentShares, - ArrayOfVaultWithBalance, - VaultWithBalance, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, - ArrayOfString, ConfigResponse, OwnerResponse, ArrayOfCoin, Positions, DebtAmount, LentAmount, - VaultConfigResponse, VaultPositionValue, CoinValue, VaultUtilizationResponse, - ArrayOfVaultConfigResponse, } from './MarsCreditManager.types' export interface MarsCreditManagerMessage { contractAddress: string sender: string - createCreditAccount: (funds?: Coin[]) => MsgExecuteContractEncodeObject + createCreditAccount: (_funds?: Coin[]) => MsgExecuteContractEncodeObject updateCreditAccount: ( { accountId, @@ -80,7 +73,7 @@ export interface MarsCreditManagerMessage { accountId: string actions: Action[] }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject repayFromWallet: ( { @@ -88,7 +81,7 @@ export interface MarsCreditManagerMessage { }: { accountId: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject updateConfig: ( { @@ -96,13 +89,9 @@ export interface MarsCreditManagerMessage { }: { updates: ConfigUpdates }, - funds?: Coin[], - ) => MsgExecuteContractEncodeObject - emergencyConfigUpdate: ( - emergencyUpdate: EmergencyUpdate, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject - updateOwner: (ownerUpdate: OwnerUpdate, funds?: Coin[]) => MsgExecuteContractEncodeObject + updateOwner: (ownerUpdate: OwnerUpdate, _funds?: Coin[]) => MsgExecuteContractEncodeObject updateNftConfig: ( { config, @@ -111,9 +100,9 @@ export interface MarsCreditManagerMessage { config?: NftConfigUpdates ownership?: Action }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject - callback: (callbackMsg: CallbackMsg, funds?: Coin[]) => MsgExecuteContractEncodeObject + callback: (callbackMsg: CallbackMsg, _funds?: Coin[]) => MsgExecuteContractEncodeObject } export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessage { sender: string @@ -126,13 +115,12 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag this.updateCreditAccount = this.updateCreditAccount.bind(this) this.repayFromWallet = this.repayFromWallet.bind(this) this.updateConfig = this.updateConfig.bind(this) - this.emergencyConfigUpdate = this.emergencyConfigUpdate.bind(this) this.updateOwner = this.updateOwner.bind(this) this.updateNftConfig = this.updateNftConfig.bind(this) this.callback = this.callback.bind(this) } - createCreditAccount = (funds?: Coin[]): MsgExecuteContractEncodeObject => { + createCreditAccount = (_funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -143,7 +131,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag create_credit_account: {}, }), ), - funds, + funds: _funds, }), } } @@ -155,7 +143,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag accountId: string actions: Action[] }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -170,7 +158,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag }, }), ), - funds, + funds: _funds, }), } } @@ -180,7 +168,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag }: { accountId: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -194,7 +182,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag }, }), ), - funds, + funds: _funds, }), } } @@ -204,7 +192,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag }: { updates: ConfigUpdates }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -218,29 +206,11 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag }, }), ), - funds, - }), - } - } - emergencyConfigUpdate = ( - emergencyUpdate: EmergencyUpdate, - funds?: Coin[], - ): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - emergency_config_update: emergencyUpdate, - }), - ), - funds, + funds: _funds, }), } } - updateOwner = (ownerUpdate: OwnerUpdate, funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateOwner = (ownerUpdate: OwnerUpdate, _funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -251,7 +221,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag update_owner: ownerUpdate, }), ), - funds, + funds: _funds, }), } } @@ -263,7 +233,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag config?: NftConfigUpdates ownership?: Action }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -278,11 +248,11 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag }, }), ), - funds, + funds: _funds, }), } } - callback = (callbackMsg: CallbackMsg, funds?: Coin[]): MsgExecuteContractEncodeObject => { + callback = (callbackMsg: CallbackMsg, _funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -293,7 +263,7 @@ export class MarsCreditManagerMessageComposer implements MarsCreditManagerMessag callback: callbackMsg, }), ), - funds, + funds: _funds, }), } } diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index ac1324cb9..3df83ffe8 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -10,28 +10,26 @@ import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee } from '@cosmjs/amino' import { HealthContractBaseForString, - Decimal, Uint128, OracleBaseForString, + ParamsBaseForString, RedBankBaseForString, SwapperBaseForString, ZapperBaseForString, InstantiateMsg, - VaultInstantiateConfig, - VaultConfig, - Coin, - VaultBaseForString, ExecuteMsg, Action, ActionAmount, LiquidateRequestForVaultBaseForString, VaultPositionType, - EmergencyUpdate, + Decimal, OwnerUpdate, CallbackMsg, Addr, LiquidateRequestForVaultBaseForAddr, + Coin, ActionCoin, + VaultBaseForString, ConfigUpdates, NftConfigUpdates, VaultBaseForAddr, @@ -51,22 +49,17 @@ import { DebtShares, ArrayOfLentShares, LentShares, - ArrayOfVaultWithBalance, - VaultWithBalance, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, - ArrayOfString, ConfigResponse, OwnerResponse, ArrayOfCoin, Positions, DebtAmount, LentAmount, - VaultConfigResponse, VaultPositionValue, CoinValue, VaultUtilizationResponse, - ArrayOfVaultConfigResponse, } from './MarsCreditManager.types' import { MarsCreditManagerQueryClient, MarsCreditManagerClient } from './MarsCreditManager.client' export const marsCreditManagerQueryKeys = { @@ -81,14 +74,6 @@ export const marsCreditManagerQueryKeys = { [ { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, ] as const, - vaultConfig: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'vault_config', args }, - ] as const, - vaultsConfig: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'vaults_config', args }, - ] as const, vaultUtilization: (contractAddress: string | undefined, args?: Record) => [ { @@ -97,10 +82,6 @@ export const marsCreditManagerQueryKeys = { args, }, ] as const, - allowedCoins: (contractAddress: string | undefined, args?: Record) => - [ - { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'allowed_coins', args }, - ] as const, positions: (contractAddress: string | undefined, args?: Record) => [ { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'positions', args }, @@ -169,25 +150,6 @@ export const marsCreditManagerQueryKeys = { args, }, ] as const, - totalVaultCoinBalance: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], - method: 'total_vault_coin_balance', - args, - }, - ] as const, - allTotalVaultCoinBalances: ( - contractAddress: string | undefined, - args?: Record, - ) => - [ - { - ...marsCreditManagerQueryKeys.address(contractAddress)[0], - method: 'all_total_vault_coin_balances', - args, - }, - ] as const, estimateProvideLiquidity: (contractAddress: string | undefined, args?: Record) => [ { @@ -293,50 +255,6 @@ export function useMarsCreditManagerEstimateProvideLiquidityQuery - extends MarsCreditManagerReactQuery { - args: { - limit?: number - startAfter?: VaultBaseForString - } -} -export function useMarsCreditManagerAllTotalVaultCoinBalancesQuery< - TData = ArrayOfVaultWithBalance, ->({ client, args, options }: MarsCreditManagerAllTotalVaultCoinBalancesQuery) { - return useQuery( - marsCreditManagerQueryKeys.allTotalVaultCoinBalances(client?.contractAddress, args), - () => - client - ? client.allTotalVaultCoinBalances({ - limit: args.limit, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsCreditManagerTotalVaultCoinBalanceQuery - extends MarsCreditManagerReactQuery { - args: { - vault: VaultBaseForString - } -} -export function useMarsCreditManagerTotalVaultCoinBalanceQuery({ - client, - args, - options, -}: MarsCreditManagerTotalVaultCoinBalanceQuery) { - return useQuery( - marsCreditManagerQueryKeys.totalVaultCoinBalance(client?.contractAddress, args), - () => - client - ? client.totalVaultCoinBalance({ - vault: args.vault, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} export interface MarsCreditManagerAllVaultPositionsQuery extends MarsCreditManagerReactQuery { args: { @@ -525,30 +443,6 @@ export function useMarsCreditManagerPositionsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsCreditManagerAllowedCoinsQuery - extends MarsCreditManagerReactQuery { - args: { - limit?: number - startAfter?: string - } -} -export function useMarsCreditManagerAllowedCoinsQuery({ - client, - args, - options, -}: MarsCreditManagerAllowedCoinsQuery) { - return useQuery( - marsCreditManagerQueryKeys.allowedCoins(client?.contractAddress, args), - () => - client - ? client.allowedCoins({ - limit: args.limit, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} export interface MarsCreditManagerVaultUtilizationQuery extends MarsCreditManagerReactQuery { args: { @@ -571,52 +465,6 @@ export function useMarsCreditManagerVaultUtilizationQuery - extends MarsCreditManagerReactQuery { - args: { - limit?: number - startAfter?: VaultBaseForString - } -} -export function useMarsCreditManagerVaultsConfigQuery({ - client, - args, - options, -}: MarsCreditManagerVaultsConfigQuery) { - return useQuery( - marsCreditManagerQueryKeys.vaultsConfig(client?.contractAddress, args), - () => - client - ? client.vaultsConfig({ - limit: args.limit, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsCreditManagerVaultConfigQuery - extends MarsCreditManagerReactQuery { - args: { - vault: VaultBaseForString - } -} -export function useMarsCreditManagerVaultConfigQuery({ - client, - args, - options, -}: MarsCreditManagerVaultConfigQuery) { - return useQuery( - marsCreditManagerQueryKeys.vaultConfig(client?.contractAddress, args), - () => - client - ? client.vaultConfig({ - vault: args.vault, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} export interface MarsCreditManagerConfigQuery extends MarsCreditManagerReactQuery {} export function useMarsCreditManagerConfigQuery({ @@ -693,27 +541,6 @@ export function useMarsCreditManagerUpdateOwnerMutation( options, ) } -export interface MarsCreditManagerEmergencyConfigUpdateMutation { - client: MarsCreditManagerClient - msg: EmergencyUpdate - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsCreditManagerEmergencyConfigUpdateMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => - client.emergencyConfigUpdate(msg, fee, memo, funds), - options, - ) -} export interface MarsCreditManagerUpdateConfigMutation { client: MarsCreditManagerClient msg: { diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index 2ceac2ec1..f1201d91b 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -1,47 +1,27 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ export type HealthContractBaseForString = string -export type Decimal = string export type Uint128 = string export type OracleBaseForString = string +export type ParamsBaseForString = string export type RedBankBaseForString = string export type SwapperBaseForString = string export type ZapperBaseForString = string export interface InstantiateMsg { - allowed_coins: string[] health_contract: HealthContractBaseForString - max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: OracleBaseForString owner: string + params: ParamsBaseForString red_bank: RedBankBaseForString swapper: SwapperBaseForString - vault_configs: VaultInstantiateConfig[] zapper: ZapperBaseForString } -export interface VaultInstantiateConfig { - config: VaultConfig - vault: VaultBaseForString -} -export interface VaultConfig { - deposit_cap: Coin - liquidation_threshold: Decimal - max_ltv: Decimal - whitelisted: boolean -} -export interface Coin { - amount: Uint128 - denom: string - [k: string]: unknown -} -export interface VaultBaseForString { - address: string -} export type ExecuteMsg = | { create_credit_account: {} @@ -62,9 +42,6 @@ export type ExecuteMsg = updates: ConfigUpdates } } - | { - emergency_config_update: EmergencyUpdate - } | { update_owner: OwnerUpdate } @@ -171,16 +148,7 @@ export type LiquidateRequestForVaultBaseForString = } } export type VaultPositionType = 'u_n_l_o_c_k_e_d' | 'l_o_c_k_e_d' | 'u_n_l_o_c_k_i_n_g' -export type EmergencyUpdate = - | { - set_zero_max_ltv: VaultBaseForString - } - | { - set_zero_deposit_cap: VaultBaseForString - } - | { - disallow_coin: string - } +export type Decimal = string export type OwnerUpdate = | { propose_new_owner: { @@ -331,20 +299,25 @@ export type LiquidateRequestForVaultBaseForAddr = request_vault: VaultBaseForAddr } } +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} export interface ActionCoin { amount: ActionAmount denom: string } +export interface VaultBaseForString { + address: string +} export interface ConfigUpdates { account_nft?: string | null - allowed_coins?: string[] | null health_contract?: HealthContractBaseForString | null - max_close_factor?: Decimal | null max_unlocking_positions?: Uint128 | null oracle?: OracleBaseForString | null red_bank?: RedBankBaseForString | null swapper?: SwapperBaseForString | null - vault_configs?: VaultInstantiateConfig[] | null zapper?: ZapperBaseForString | null } export interface NftConfigUpdates { @@ -358,28 +331,11 @@ export type QueryMsg = | { config: {} } - | { - vault_config: { - vault: VaultBaseForString - } - } - | { - vaults_config: { - limit?: number | null - start_after?: VaultBaseForString | null - } - } | { vault_utilization: { vault: VaultBaseForString } } - | { - allowed_coins: { - limit?: number | null - start_after?: string | null - } - } | { positions: { account_id: string @@ -427,17 +383,6 @@ export type QueryMsg = start_after?: [string, string] | null } } - | { - total_vault_coin_balance: { - vault: VaultBaseForString - } - } - | { - all_total_vault_coin_balances: { - limit?: number | null - start_after?: VaultBaseForString | null - } - } | { estimate_provide_liquidity: { coins_in: Coin[] @@ -498,24 +443,18 @@ export interface LentShares { denom: string shares: Uint128 } -export type ArrayOfVaultWithBalance = VaultWithBalance[] -export interface VaultWithBalance { - balance: Uint128 - vault: VaultBaseForAddr -} export type ArrayOfVaultPositionResponseItem = VaultPositionResponseItem[] export interface VaultPositionResponseItem { account_id: string position: VaultPosition } -export type ArrayOfString = string[] export interface ConfigResponse { account_nft?: string | null health_contract: string - max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: string ownership: OwnerResponse + params: string red_bank: string swapper: string zapper: string @@ -545,10 +484,6 @@ export interface LentAmount { denom: string shares: Uint128 } -export interface VaultConfigResponse { - config: VaultConfig - vault: VaultBaseForString -} export interface VaultPositionValue { base_coin: CoinValue vault_coin: CoinValue @@ -562,4 +497,3 @@ export interface VaultUtilizationResponse { utilization: Coin vault: VaultBaseForString } -export type ArrayOfVaultConfigResponse = VaultConfigResponse[] diff --git a/scripts/types/generated/mars-credit-manager/bundle.ts b/scripts/types/generated/mars-credit-manager/bundle.ts index f68b3a809..58e3d170b 100644 --- a/scripts/types/generated/mars-credit-manager/bundle.ts +++ b/scripts/types/generated/mars-credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index e65c0aa58..e5fae28a3 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -16,7 +16,6 @@ import { VaultAmount1, UnlockingPositions, Addr, - Decimal, Positions, DebtAmount, Coin, @@ -25,7 +24,6 @@ import { LockingVaultAmount, VaultUnlockingPosition, VaultBaseForAddr, - VaultConfig, QueryMsg, VaultBaseForString, ArrayOfCoinBalanceResponseItem, @@ -36,39 +34,19 @@ import { DebtShares, ArrayOfLentShares, LentShares, - ArrayOfVaultWithBalance, - VaultWithBalance, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, - ArrayOfString, ConfigResponse, OwnerResponse, ArrayOfCoin, - VaultConfigResponse, VaultPositionValue, CoinValue, VaultUtilizationResponse, - ArrayOfVaultConfigResponse, } from './MarsMockCreditManager.types' export interface MarsMockCreditManagerReadOnlyInterface { contractAddress: string config: () => Promise - vaultConfig: ({ vault }: { vault: VaultBaseForString }) => Promise - vaultsConfig: ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: VaultBaseForString - }) => Promise vaultUtilization: ({ vault }: { vault: VaultBaseForString }) => Promise - allowedCoins: ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string - }) => Promise positions: ({ accountId }: { accountId: string }) => Promise allCoinBalances: ({ limit, @@ -114,14 +92,6 @@ export interface MarsMockCreditManagerReadOnlyInterface { limit?: number startAfter?: string[][] }) => Promise - totalVaultCoinBalance: ({ vault }: { vault: VaultBaseForString }) => Promise - allTotalVaultCoinBalances: ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: VaultBaseForString - }) => Promise estimateProvideLiquidity: ({ coinsIn, lpTokenOut, @@ -144,10 +114,7 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe this.client = client this.contractAddress = contractAddress this.config = this.config.bind(this) - this.vaultConfig = this.vaultConfig.bind(this) - this.vaultsConfig = this.vaultsConfig.bind(this) this.vaultUtilization = this.vaultUtilization.bind(this) - this.allowedCoins = this.allowedCoins.bind(this) this.positions = this.positions.bind(this) this.allCoinBalances = this.allCoinBalances.bind(this) this.allDebtShares = this.allDebtShares.bind(this) @@ -157,8 +124,6 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe this.totalLentShares = this.totalLentShares.bind(this) this.allTotalLentShares = this.allTotalLentShares.bind(this) this.allVaultPositions = this.allVaultPositions.bind(this) - this.totalVaultCoinBalance = this.totalVaultCoinBalance.bind(this) - this.allTotalVaultCoinBalances = this.allTotalVaultCoinBalances.bind(this) this.estimateProvideLiquidity = this.estimateProvideLiquidity.bind(this) this.estimateWithdrawLiquidity = this.estimateWithdrawLiquidity.bind(this) this.vaultPositionValue = this.vaultPositionValue.bind(this) @@ -169,27 +134,6 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe config: {}, }) } - vaultConfig = async ({ vault }: { vault: VaultBaseForString }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - vault_config: { - vault, - }, - }) - } - vaultsConfig = async ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: VaultBaseForString - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - vaults_config: { - limit, - start_after: startAfter, - }, - }) - } vaultUtilization = async ({ vault, }: { @@ -201,20 +145,6 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe }, }) } - allowedCoins = async ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: string - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - allowed_coins: { - limit, - start_after: startAfter, - }, - }) - } positions = async ({ accountId }: { accountId: string }): Promise => { return this.client.queryContractSmart(this.contractAddress, { positions: { @@ -316,27 +246,6 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe }, }) } - totalVaultCoinBalance = async ({ vault }: { vault: VaultBaseForString }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - total_vault_coin_balance: { - vault, - }, - }) - } - allTotalVaultCoinBalances = async ({ - limit, - startAfter, - }: { - limit?: number - startAfter?: VaultBaseForString - }): Promise => { - return this.client.queryContractSmart(this.contractAddress, { - all_total_vault_coin_balances: { - limit, - start_after: startAfter, - }, - }) - } estimateProvideLiquidity = async ({ coinsIn, lpTokenOut, @@ -383,24 +292,7 @@ export interface MarsMockCreditManagerInterface extends MarsMockCreditManagerRea }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], - ) => Promise - setAllowedCoins: ( - fee?: number | StdFee | 'auto', - memo?: string, - funds?: Coin[], - ) => Promise - setVaultConfig: ( - { - address, - config, - }: { - address: string - config: VaultConfig - }, - fee?: number | StdFee | 'auto', - memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise } export class MarsMockCreditManagerClient @@ -417,8 +309,6 @@ export class MarsMockCreditManagerClient this.sender = sender this.contractAddress = contractAddress this.setPositionsResponse = this.setPositionsResponse.bind(this) - this.setAllowedCoins = this.setAllowedCoins.bind(this) - this.setVaultConfig = this.setVaultConfig.bind(this) } setPositionsResponse = async ( @@ -431,7 +321,7 @@ export class MarsMockCreditManagerClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -444,49 +334,7 @@ export class MarsMockCreditManagerClient }, fee, memo, - funds, - ) - } - setAllowedCoins = async ( - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - set_allowed_coins: {}, - }, - fee, - memo, - funds, - ) - } - setVaultConfig = async ( - { - address, - config, - }: { - address: string - config: VaultConfig - }, - fee: number | StdFee | 'auto' = 'auto', - memo?: string, - funds?: Coin[], - ): Promise => { - return await this.client.execute( - this.sender, - this.contractAddress, - { - set_vault_config: { - address, - config, - }, - }, - fee, - memo, - funds, + _funds, ) } } diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index e09dacb93..bcb9f080c 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -1,11 +1,11 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { @@ -17,7 +17,6 @@ import { VaultAmount1, UnlockingPositions, Addr, - Decimal, Positions, DebtAmount, Coin, @@ -26,7 +25,6 @@ import { LockingVaultAmount, VaultUnlockingPosition, VaultBaseForAddr, - VaultConfig, QueryMsg, VaultBaseForString, ArrayOfCoinBalanceResponseItem, @@ -37,19 +35,14 @@ import { DebtShares, ArrayOfLentShares, LentShares, - ArrayOfVaultWithBalance, - VaultWithBalance, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, - ArrayOfString, ConfigResponse, OwnerResponse, ArrayOfCoin, - VaultConfigResponse, VaultPositionValue, CoinValue, VaultUtilizationResponse, - ArrayOfVaultConfigResponse, } from './MarsMockCreditManager.types' export interface MarsMockCreditManagerMessage { contractAddress: string @@ -62,18 +55,7 @@ export interface MarsMockCreditManagerMessage { accountId: string positions: Positions }, - funds?: Coin[], - ) => MsgExecuteContractEncodeObject - setAllowedCoins: (funds?: Coin[]) => MsgExecuteContractEncodeObject - setVaultConfig: ( - { - address, - config, - }: { - address: string - config: VaultConfig - }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject } export class MarsMockCreditManagerMessageComposer implements MarsMockCreditManagerMessage { @@ -84,8 +66,6 @@ export class MarsMockCreditManagerMessageComposer implements MarsMockCreditManag this.sender = sender this.contractAddress = contractAddress this.setPositionsResponse = this.setPositionsResponse.bind(this) - this.setAllowedCoins = this.setAllowedCoins.bind(this) - this.setVaultConfig = this.setVaultConfig.bind(this) } setPositionsResponse = ( @@ -96,7 +76,7 @@ export class MarsMockCreditManagerMessageComposer implements MarsMockCreditManag accountId: string positions: Positions }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -111,49 +91,7 @@ export class MarsMockCreditManagerMessageComposer implements MarsMockCreditManag }, }), ), - funds, - }), - } - } - setAllowedCoins = (funds?: Coin[]): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - set_allowed_coins: {}, - }), - ), - funds, - }), - } - } - setVaultConfig = ( - { - address, - config, - }: { - address: string - config: VaultConfig - }, - funds?: Coin[], - ): MsgExecuteContractEncodeObject => { - return { - typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', - value: MsgExecuteContract.fromPartial({ - sender: this.sender, - contract: this.contractAddress, - msg: toUtf8( - JSON.stringify({ - set_vault_config: { - address, - config, - }, - }), - ), - funds, + funds: _funds, }), } } diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index e1125d94c..a7d2f3053 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -17,7 +17,6 @@ import { VaultAmount1, UnlockingPositions, Addr, - Decimal, Positions, DebtAmount, Coin, @@ -26,7 +25,6 @@ import { LockingVaultAmount, VaultUnlockingPosition, VaultBaseForAddr, - VaultConfig, QueryMsg, VaultBaseForString, ArrayOfCoinBalanceResponseItem, @@ -37,19 +35,14 @@ import { DebtShares, ArrayOfLentShares, LentShares, - ArrayOfVaultWithBalance, - VaultWithBalance, ArrayOfVaultPositionResponseItem, VaultPositionResponseItem, - ArrayOfString, ConfigResponse, OwnerResponse, ArrayOfCoin, - VaultConfigResponse, VaultPositionValue, CoinValue, VaultUtilizationResponse, - ArrayOfVaultConfigResponse, } from './MarsMockCreditManager.types' import { MarsMockCreditManagerQueryClient, @@ -67,22 +60,6 @@ export const marsMockCreditManagerQueryKeys = { [ { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, ] as const, - vaultConfig: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], - method: 'vault_config', - args, - }, - ] as const, - vaultsConfig: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], - method: 'vaults_config', - args, - }, - ] as const, vaultUtilization: (contractAddress: string | undefined, args?: Record) => [ { @@ -91,14 +68,6 @@ export const marsMockCreditManagerQueryKeys = { args, }, ] as const, - allowedCoins: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], - method: 'allowed_coins', - args, - }, - ] as const, positions: (contractAddress: string | undefined, args?: Record) => [ { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'positions', args }, @@ -167,25 +136,6 @@ export const marsMockCreditManagerQueryKeys = { args, }, ] as const, - totalVaultCoinBalance: (contractAddress: string | undefined, args?: Record) => - [ - { - ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], - method: 'total_vault_coin_balance', - args, - }, - ] as const, - allTotalVaultCoinBalances: ( - contractAddress: string | undefined, - args?: Record, - ) => - [ - { - ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], - method: 'all_total_vault_coin_balances', - args, - }, - ] as const, estimateProvideLiquidity: (contractAddress: string | undefined, args?: Record) => [ { @@ -291,50 +241,6 @@ export function useMarsMockCreditManagerEstimateProvideLiquidityQuery - extends MarsMockCreditManagerReactQuery { - args: { - limit?: number - startAfter?: VaultBaseForString - } -} -export function useMarsMockCreditManagerAllTotalVaultCoinBalancesQuery< - TData = ArrayOfVaultWithBalance, ->({ client, args, options }: MarsMockCreditManagerAllTotalVaultCoinBalancesQuery) { - return useQuery( - marsMockCreditManagerQueryKeys.allTotalVaultCoinBalances(client?.contractAddress, args), - () => - client - ? client.allTotalVaultCoinBalances({ - limit: args.limit, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsMockCreditManagerTotalVaultCoinBalanceQuery - extends MarsMockCreditManagerReactQuery { - args: { - vault: VaultBaseForString - } -} -export function useMarsMockCreditManagerTotalVaultCoinBalanceQuery({ - client, - args, - options, -}: MarsMockCreditManagerTotalVaultCoinBalanceQuery) { - return useQuery( - marsMockCreditManagerQueryKeys.totalVaultCoinBalance(client?.contractAddress, args), - () => - client - ? client.totalVaultCoinBalance({ - vault: args.vault, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} export interface MarsMockCreditManagerAllVaultPositionsQuery extends MarsMockCreditManagerReactQuery { args: { @@ -521,30 +427,6 @@ export function useMarsMockCreditManagerPositionsQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsMockCreditManagerAllowedCoinsQuery - extends MarsMockCreditManagerReactQuery { - args: { - limit?: number - startAfter?: string - } -} -export function useMarsMockCreditManagerAllowedCoinsQuery({ - client, - args, - options, -}: MarsMockCreditManagerAllowedCoinsQuery) { - return useQuery( - marsMockCreditManagerQueryKeys.allowedCoins(client?.contractAddress, args), - () => - client - ? client.allowedCoins({ - limit: args.limit, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} export interface MarsMockCreditManagerVaultUtilizationQuery extends MarsMockCreditManagerReactQuery { args: { @@ -567,52 +449,6 @@ export function useMarsMockCreditManagerVaultUtilizationQuery - extends MarsMockCreditManagerReactQuery { - args: { - limit?: number - startAfter?: VaultBaseForString - } -} -export function useMarsMockCreditManagerVaultsConfigQuery({ - client, - args, - options, -}: MarsMockCreditManagerVaultsConfigQuery) { - return useQuery( - marsMockCreditManagerQueryKeys.vaultsConfig(client?.contractAddress, args), - () => - client - ? client.vaultsConfig({ - limit: args.limit, - startAfter: args.startAfter, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} -export interface MarsMockCreditManagerVaultConfigQuery - extends MarsMockCreditManagerReactQuery { - args: { - vault: VaultBaseForString - } -} -export function useMarsMockCreditManagerVaultConfigQuery({ - client, - args, - options, -}: MarsMockCreditManagerVaultConfigQuery) { - return useQuery( - marsMockCreditManagerQueryKeys.vaultConfig(client?.contractAddress, args), - () => - client - ? client.vaultConfig({ - vault: args.vault, - }) - : Promise.reject(new Error('Invalid client')), - { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, - ) -} export interface MarsMockCreditManagerConfigQuery extends MarsMockCreditManagerReactQuery {} export function useMarsMockCreditManagerConfigQuery({ @@ -625,49 +461,6 @@ export function useMarsMockCreditManagerConfigQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsMockCreditManagerSetVaultConfigMutation { - client: MarsMockCreditManagerClient - msg: { - address: string - config: VaultConfig - } - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsMockCreditManagerSetVaultConfigMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, msg, args: { fee, memo, funds } = {} }) => - client.setVaultConfig(msg, fee, memo, funds), - options, - ) -} -export interface MarsMockCreditManagerSetAllowedCoinsMutation { - client: MarsMockCreditManagerClient - args?: { - fee?: number | StdFee | 'auto' - memo?: string - funds?: Coin[] - } -} -export function useMarsMockCreditManagerSetAllowedCoinsMutation( - options?: Omit< - UseMutationOptions, - 'mutationFn' - >, -) { - return useMutation( - ({ client, args: { fee, memo, funds } = {} }) => client.setAllowedCoins(fee, memo, funds), - options, - ) -} export interface MarsMockCreditManagerSetPositionsResponseMutation { client: MarsMockCreditManagerClient msg: { diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index 402af4708..ec277576e 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -8,22 +8,12 @@ export interface InstantiateMsg { [k: string]: unknown } -export type ExecuteMsg = - | { - set_positions_response: { - account_id: string - positions: Positions - } - } - | { - set_allowed_coins: string[] - } - | { - set_vault_config: { - address: string - config: VaultConfig - } - } +export type ExecuteMsg = { + set_positions_response: { + account_id: string + positions: Positions + } +} export type Uint128 = string export type VaultPositionAmount = | { @@ -36,7 +26,6 @@ export type VaultAmount = string export type VaultAmount1 = string export type UnlockingPositions = VaultUnlockingPosition[] export type Addr = string -export type Decimal = string export interface Positions { account_id: string debts: DebtAmount[] @@ -74,38 +63,15 @@ export interface VaultUnlockingPosition { export interface VaultBaseForAddr { address: Addr } -export interface VaultConfig { - deposit_cap: Coin - liquidation_threshold: Decimal - max_ltv: Decimal - whitelisted: boolean -} export type QueryMsg = | { config: {} } - | { - vault_config: { - vault: VaultBaseForString - } - } - | { - vaults_config: { - limit?: number | null - start_after?: VaultBaseForString | null - } - } | { vault_utilization: { vault: VaultBaseForString } } - | { - allowed_coins: { - limit?: number | null - start_after?: string | null - } - } | { positions: { account_id: string @@ -153,17 +119,6 @@ export type QueryMsg = start_after?: [string, string] | null } } - | { - total_vault_coin_balance: { - vault: VaultBaseForString - } - } - | { - all_total_vault_coin_balances: { - limit?: number | null - start_after?: VaultBaseForString | null - } - } | { estimate_provide_liquidity: { coins_in: Coin[] @@ -205,24 +160,18 @@ export interface LentShares { denom: string shares: Uint128 } -export type ArrayOfVaultWithBalance = VaultWithBalance[] -export interface VaultWithBalance { - balance: Uint128 - vault: VaultBaseForAddr -} export type ArrayOfVaultPositionResponseItem = VaultPositionResponseItem[] export interface VaultPositionResponseItem { account_id: string position: VaultPosition } -export type ArrayOfString = string[] export interface ConfigResponse { account_nft?: string | null health_contract: string - max_close_factor: Decimal max_unlocking_positions: Uint128 oracle: string ownership: OwnerResponse + params: string red_bank: string swapper: string zapper: string @@ -235,10 +184,6 @@ export interface OwnerResponse { proposed?: string | null } export type ArrayOfCoin = Coin[] -export interface VaultConfigResponse { - config: VaultConfig - vault: VaultBaseForString -} export interface VaultPositionValue { base_coin: CoinValue vault_coin: CoinValue @@ -252,4 +197,3 @@ export interface VaultUtilizationResponse { utilization: Coin vault: VaultBaseForString } -export type ArrayOfVaultConfigResponse = VaultConfigResponse[] diff --git a/scripts/types/generated/mars-mock-credit-manager/bundle.ts b/scripts/types/generated/mars-mock-credit-manager/bundle.ts index c0d830bea..81f661cad 100644 --- a/scripts/types/generated/mars-mock-credit-manager/bundle.ts +++ b/scripts/types/generated/mars-mock-credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts index feaf6aef9..cee7dd1bf 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -50,7 +50,7 @@ export interface MarsMockOracleInterface extends MarsMockOracleReadOnlyInterface }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise } export class MarsMockOracleClient @@ -79,7 +79,7 @@ export class MarsMockOracleClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -92,7 +92,7 @@ export class MarsMockOracleClient }, fee, memo, - funds, + _funds, ) } } diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts index 61bae67e4..33d9badb9 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts @@ -1,12 +1,12 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { Coin } from '@cosmjs/amino' -import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { @@ -28,7 +28,7 @@ export interface MarsMockOracleMessage { denom: string price: Decimal }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject } export class MarsMockOracleMessageComposer implements MarsMockOracleMessage { @@ -49,7 +49,7 @@ export class MarsMockOracleMessageComposer implements MarsMockOracleMessage { denom: string price: Decimal }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -64,7 +64,7 @@ export class MarsMockOracleMessageComposer implements MarsMockOracleMessage { }, }), ), - funds, + funds: _funds, }), } } diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts index 0ae7deed9..68035be8c 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts index bea5f561e..6c0337bf0 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/bundle.ts b/scripts/types/generated/mars-mock-oracle/bundle.ts index 18ab96306..b31e62c90 100644 --- a/scripts/types/generated/mars-mock-oracle/bundle.ts +++ b/scripts/types/generated/mars-mock-oracle/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index 0c394a17e..41693744e 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -8,11 +8,10 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { Coin, StdFee } from '@cosmjs/amino' import { - Decimal, InstantiateMsg, - CoinMarketInfo, ExecuteMsg, OwnerUpdate, + Decimal, Uint128, CreateOrUpdateConfig, InitOrUpdateAssetParams, @@ -314,13 +313,13 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa ownerUpdate: OwnerUpdate, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise updateEmergencyOwner: ( ownerUpdate: OwnerUpdate, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise updateConfig: ( { @@ -330,7 +329,7 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise initAsset: ( { @@ -342,7 +341,7 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise updateAsset: ( { @@ -354,7 +353,7 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise updateUncollateralizedLoanLimit: ( { @@ -368,7 +367,7 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise deposit: ( { @@ -378,7 +377,7 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise withdraw: ( { @@ -392,7 +391,7 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise borrow: ( { @@ -406,7 +405,7 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise repay: ( { @@ -416,7 +415,7 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise liquidate: ( { @@ -430,7 +429,7 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise updateAssetCollateralStatus: ( { @@ -442,7 +441,7 @@ export interface MarsMockRedBankInterface extends MarsMockRedBankReadOnlyInterfa }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise } export class MarsMockRedBankClient @@ -476,7 +475,7 @@ export class MarsMockRedBankClient ownerUpdate: OwnerUpdate, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -486,14 +485,14 @@ export class MarsMockRedBankClient }, fee, memo, - funds, + _funds, ) } updateEmergencyOwner = async ( ownerUpdate: OwnerUpdate, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -503,7 +502,7 @@ export class MarsMockRedBankClient }, fee, memo, - funds, + _funds, ) } updateConfig = async ( @@ -514,7 +513,7 @@ export class MarsMockRedBankClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -526,7 +525,7 @@ export class MarsMockRedBankClient }, fee, memo, - funds, + _funds, ) } initAsset = async ( @@ -539,7 +538,7 @@ export class MarsMockRedBankClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -552,7 +551,7 @@ export class MarsMockRedBankClient }, fee, memo, - funds, + _funds, ) } updateAsset = async ( @@ -565,7 +564,7 @@ export class MarsMockRedBankClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -578,7 +577,7 @@ export class MarsMockRedBankClient }, fee, memo, - funds, + _funds, ) } updateUncollateralizedLoanLimit = async ( @@ -593,7 +592,7 @@ export class MarsMockRedBankClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -607,7 +606,7 @@ export class MarsMockRedBankClient }, fee, memo, - funds, + _funds, ) } deposit = async ( @@ -618,7 +617,7 @@ export class MarsMockRedBankClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -630,7 +629,7 @@ export class MarsMockRedBankClient }, fee, memo, - funds, + _funds, ) } withdraw = async ( @@ -645,7 +644,7 @@ export class MarsMockRedBankClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -659,7 +658,7 @@ export class MarsMockRedBankClient }, fee, memo, - funds, + _funds, ) } borrow = async ( @@ -674,7 +673,7 @@ export class MarsMockRedBankClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -688,7 +687,7 @@ export class MarsMockRedBankClient }, fee, memo, - funds, + _funds, ) } repay = async ( @@ -699,7 +698,7 @@ export class MarsMockRedBankClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -711,7 +710,7 @@ export class MarsMockRedBankClient }, fee, memo, - funds, + _funds, ) } liquidate = async ( @@ -726,7 +725,7 @@ export class MarsMockRedBankClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -740,7 +739,7 @@ export class MarsMockRedBankClient }, fee, memo, - funds, + _funds, ) } updateAssetCollateralStatus = async ( @@ -753,7 +752,7 @@ export class MarsMockRedBankClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -766,7 +765,7 @@ export class MarsMockRedBankClient }, fee, memo, - funds, + _funds, ) } } diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts index 579e6e4c9..14ea5113b 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts @@ -1,20 +1,19 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { Coin } from '@cosmjs/amino' -import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { - Decimal, InstantiateMsg, - CoinMarketInfo, ExecuteMsg, OwnerUpdate, + Decimal, Uint128, CreateOrUpdateConfig, InitOrUpdateAssetParams, @@ -35,15 +34,18 @@ import { export interface MarsMockRedBankMessage { contractAddress: string sender: string - updateOwner: (ownerUpdate: OwnerUpdate, funds?: Coin[]) => MsgExecuteContractEncodeObject - updateEmergencyOwner: (ownerUpdate: OwnerUpdate, funds?: Coin[]) => MsgExecuteContractEncodeObject + updateOwner: (ownerUpdate: OwnerUpdate, _funds?: Coin[]) => MsgExecuteContractEncodeObject + updateEmergencyOwner: ( + ownerUpdate: OwnerUpdate, + _funds?: Coin[], + ) => MsgExecuteContractEncodeObject updateConfig: ( { config, }: { config: CreateOrUpdateConfig }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject initAsset: ( { @@ -53,7 +55,7 @@ export interface MarsMockRedBankMessage { denom: string params: InitOrUpdateAssetParams }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject updateAsset: ( { @@ -63,7 +65,7 @@ export interface MarsMockRedBankMessage { denom: string params: InitOrUpdateAssetParams }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject updateUncollateralizedLoanLimit: ( { @@ -75,7 +77,7 @@ export interface MarsMockRedBankMessage { newLimit: Uint128 user: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject deposit: ( { @@ -83,7 +85,7 @@ export interface MarsMockRedBankMessage { }: { onBehalfOf?: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject withdraw: ( { @@ -95,7 +97,7 @@ export interface MarsMockRedBankMessage { denom: string recipient?: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject borrow: ( { @@ -107,7 +109,7 @@ export interface MarsMockRedBankMessage { denom: string recipient?: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject repay: ( { @@ -115,7 +117,7 @@ export interface MarsMockRedBankMessage { }: { onBehalfOf?: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject liquidate: ( { @@ -127,7 +129,7 @@ export interface MarsMockRedBankMessage { recipient?: string user: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject updateAssetCollateralStatus: ( { @@ -137,7 +139,7 @@ export interface MarsMockRedBankMessage { denom: string enable: boolean }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject } export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { @@ -161,7 +163,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { this.updateAssetCollateralStatus = this.updateAssetCollateralStatus.bind(this) } - updateOwner = (ownerUpdate: OwnerUpdate, funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateOwner = (ownerUpdate: OwnerUpdate, _funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -172,13 +174,13 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { update_owner: ownerUpdate, }), ), - funds, + funds: _funds, }), } } updateEmergencyOwner = ( ownerUpdate: OwnerUpdate, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -190,7 +192,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { update_emergency_owner: ownerUpdate, }), ), - funds, + funds: _funds, }), } } @@ -200,7 +202,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }: { config: CreateOrUpdateConfig }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -214,7 +216,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -226,7 +228,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { denom: string params: InitOrUpdateAssetParams }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -241,7 +243,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -253,7 +255,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { denom: string params: InitOrUpdateAssetParams }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -268,7 +270,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -282,7 +284,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { newLimit: Uint128 user: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -298,7 +300,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -308,7 +310,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }: { onBehalfOf?: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -322,7 +324,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -336,7 +338,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { denom: string recipient?: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -352,7 +354,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -366,7 +368,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { denom: string recipient?: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -382,7 +384,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -392,7 +394,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }: { onBehalfOf?: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -406,7 +408,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -420,7 +422,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { recipient?: string user: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -436,7 +438,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -448,7 +450,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { denom: string enable: boolean }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -463,7 +465,7 @@ export class MarsMockRedBankMessageComposer implements MarsMockRedBankMessage { }, }), ), - funds, + funds: _funds, }), } } diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index 2515135cb..118a80bcb 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -9,11 +9,10 @@ import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tan import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { StdFee, Coin } from '@cosmjs/amino' import { - Decimal, InstantiateMsg, - CoinMarketInfo, ExecuteMsg, OwnerUpdate, + Decimal, Uint128, CreateOrUpdateConfig, InitOrUpdateAssetParams, diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index 25807135b..8099cd089 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -1,19 +1,12 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -export type Decimal = string export interface InstantiateMsg { - coins: CoinMarketInfo[] -} -export interface CoinMarketInfo { - denom: string - liquidation_bonus: Decimal - liquidation_threshold: Decimal - max_ltv: Decimal + [k: string]: unknown } export type ExecuteMsg = | { @@ -92,6 +85,7 @@ export type OwnerUpdate = | 'clear_proposed' | 'accept_proposed' | 'abolish_owner_role' +export type Decimal = string export type Uint128 = string export interface CreateOrUpdateConfig { address_provider?: string | null diff --git a/scripts/types/generated/mars-mock-red-bank/bundle.ts b/scripts/types/generated/mars-mock-red-bank/bundle.ts index 537d0a2b1..b1017421b 100644 --- a/scripts/types/generated/mars-mock-red-bank/bundle.ts +++ b/scripts/types/generated/mars-mock-red-bank/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts index 4032e15fb..9f1748b32 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -120,7 +120,7 @@ export interface MarsMockVaultInterface extends MarsMockVaultReadOnlyInterface { }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise redeem: ( { @@ -132,13 +132,13 @@ export interface MarsMockVaultInterface extends MarsMockVaultReadOnlyInterface { }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise vaultExtension: ( extensionExecuteMsg: ExtensionExecuteMsg, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise } export class MarsMockVaultClient @@ -169,7 +169,7 @@ export class MarsMockVaultClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -182,7 +182,7 @@ export class MarsMockVaultClient }, fee, memo, - funds, + _funds, ) } redeem = async ( @@ -195,7 +195,7 @@ export class MarsMockVaultClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -208,14 +208,14 @@ export class MarsMockVaultClient }, fee, memo, - funds, + _funds, ) } vaultExtension = async ( extensionExecuteMsg: ExtensionExecuteMsg, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -225,7 +225,7 @@ export class MarsMockVaultClient }, fee, memo, - funds, + _funds, ) } } diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts index 1a046efe5..66988e409 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts @@ -1,12 +1,12 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { Coin } from '@cosmjs/amino' -import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { @@ -36,7 +36,7 @@ export interface MarsMockVaultMessage { amount: Uint128 recipient?: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject redeem: ( { @@ -46,11 +46,11 @@ export interface MarsMockVaultMessage { amount: Uint128 recipient?: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject vaultExtension: ( extensionExecuteMsg: ExtensionExecuteMsg, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject } export class MarsMockVaultMessageComposer implements MarsMockVaultMessage { @@ -73,7 +73,7 @@ export class MarsMockVaultMessageComposer implements MarsMockVaultMessage { amount: Uint128 recipient?: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -88,7 +88,7 @@ export class MarsMockVaultMessageComposer implements MarsMockVaultMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -100,7 +100,7 @@ export class MarsMockVaultMessageComposer implements MarsMockVaultMessage { amount: Uint128 recipient?: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -115,13 +115,13 @@ export class MarsMockVaultMessageComposer implements MarsMockVaultMessage { }, }), ), - funds, + funds: _funds, }), } } vaultExtension = ( extensionExecuteMsg: ExtensionExecuteMsg, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -133,7 +133,7 @@ export class MarsMockVaultMessageComposer implements MarsMockVaultMessage { vault_extension: extensionExecuteMsg, }), ), - funds, + funds: _funds, }), } } diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts index cf84eab1f..6b1af7ac0 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts index 9d4594421..f5354a687 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/bundle.ts b/scripts/types/generated/mars-mock-vault/bundle.ts index ea4219749..061aa5822 100644 --- a/scripts/types/generated/mars-mock-vault/bundle.ts +++ b/scripts/types/generated/mars-mock-vault/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-params/MarsParams.client.ts b/scripts/types/generated/mars-params/MarsParams.client.ts new file mode 100644 index 000000000..7348352e3 --- /dev/null +++ b/scripts/types/generated/mars-params/MarsParams.client.ts @@ -0,0 +1,257 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + Decimal, + InstantiateMsg, + ExecuteMsg, + OwnerUpdate, + AssetParamsUpdate, + Uint128, + VaultConfigUpdate, + EmergencyUpdate, + RoverEmergencyUpdate, + RedBankEmergencyUpdate, + AssetParams, + RedBankSettings, + RoverSettings, + HighLeverageStrategyParams, + VaultConfigBaseForString, + Coin, + QueryMsg, + ArrayOfAssetParams, + Addr, + ArrayOfVaultConfigBaseForAddr, + VaultConfigBaseForAddr, + OwnerResponse, +} from './MarsParams.types' +export interface MarsParamsReadOnlyInterface { + contractAddress: string + owner: () => Promise + assetParams: ({ denom }: { denom: string }) => Promise + allAssetParams: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }) => Promise + vaultConfig: ({ address }: { address: string }) => Promise + allVaultConfigs: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }) => Promise + maxCloseFactor: () => Promise +} +export class MarsParamsQueryClient implements MarsParamsReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.owner = this.owner.bind(this) + this.assetParams = this.assetParams.bind(this) + this.allAssetParams = this.allAssetParams.bind(this) + this.vaultConfig = this.vaultConfig.bind(this) + this.allVaultConfigs = this.allVaultConfigs.bind(this) + this.maxCloseFactor = this.maxCloseFactor.bind(this) + } + + owner = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + owner: {}, + }) + } + assetParams = async ({ denom }: { denom: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + asset_params: { + denom, + }, + }) + } + allAssetParams = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_asset_params: { + limit, + start_after: startAfter, + }, + }) + } + vaultConfig = async ({ address }: { address: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + vault_config: { + address, + }, + }) + } + allVaultConfigs = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + all_vault_configs: { + limit, + start_after: startAfter, + }, + }) + } + maxCloseFactor = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + max_close_factor: {}, + }) + } +} +export interface MarsParamsInterface extends MarsParamsReadOnlyInterface { + contractAddress: string + sender: string + updateOwner: ( + ownerUpdate: OwnerUpdate, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise + updateMaxCloseFactor: ( + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise + updateAssetParams: ( + assetParamsUpdate: AssetParamsUpdate, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise + updateVaultConfig: ( + vaultConfigUpdate: VaultConfigUpdate, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise + emergencyUpdate: ( + emergencyUpdate: EmergencyUpdate, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise +} +export class MarsParamsClient extends MarsParamsQueryClient implements MarsParamsInterface { + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.updateOwner = this.updateOwner.bind(this) + this.updateMaxCloseFactor = this.updateMaxCloseFactor.bind(this) + this.updateAssetParams = this.updateAssetParams.bind(this) + this.updateVaultConfig = this.updateVaultConfig.bind(this) + this.emergencyUpdate = this.emergencyUpdate.bind(this) + } + + updateOwner = async ( + ownerUpdate: OwnerUpdate, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_owner: ownerUpdate, + }, + fee, + memo, + _funds, + ) + } + updateMaxCloseFactor = async ( + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_max_close_factor: {}, + }, + fee, + memo, + _funds, + ) + } + updateAssetParams = async ( + assetParamsUpdate: AssetParamsUpdate, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_asset_params: assetParamsUpdate, + }, + fee, + memo, + _funds, + ) + } + updateVaultConfig = async ( + vaultConfigUpdate: VaultConfigUpdate, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_vault_config: vaultConfigUpdate, + }, + fee, + memo, + _funds, + ) + } + emergencyUpdate = async ( + emergencyUpdate: EmergencyUpdate, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + emergency_update: emergencyUpdate, + }, + fee, + memo, + _funds, + ) + } +} diff --git a/scripts/types/generated/mars-params/MarsParams.message-composer.ts b/scripts/types/generated/mars-params/MarsParams.message-composer.ts new file mode 100644 index 000000000..d86a856c9 --- /dev/null +++ b/scripts/types/generated/mars-params/MarsParams.message-composer.ts @@ -0,0 +1,151 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + Decimal, + InstantiateMsg, + ExecuteMsg, + OwnerUpdate, + AssetParamsUpdate, + Uint128, + VaultConfigUpdate, + EmergencyUpdate, + RoverEmergencyUpdate, + RedBankEmergencyUpdate, + AssetParams, + RedBankSettings, + RoverSettings, + HighLeverageStrategyParams, + VaultConfigBaseForString, + Coin, + QueryMsg, + ArrayOfAssetParams, + Addr, + ArrayOfVaultConfigBaseForAddr, + VaultConfigBaseForAddr, + OwnerResponse, +} from './MarsParams.types' +export interface MarsParamsMessage { + contractAddress: string + sender: string + updateOwner: (ownerUpdate: OwnerUpdate, _funds?: Coin[]) => MsgExecuteContractEncodeObject + updateMaxCloseFactor: (_funds?: Coin[]) => MsgExecuteContractEncodeObject + updateAssetParams: ( + assetParamsUpdate: AssetParamsUpdate, + _funds?: Coin[], + ) => MsgExecuteContractEncodeObject + updateVaultConfig: ( + vaultConfigUpdate: VaultConfigUpdate, + _funds?: Coin[], + ) => MsgExecuteContractEncodeObject + emergencyUpdate: ( + emergencyUpdate: EmergencyUpdate, + _funds?: Coin[], + ) => MsgExecuteContractEncodeObject +} +export class MarsParamsMessageComposer implements MarsParamsMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.updateOwner = this.updateOwner.bind(this) + this.updateMaxCloseFactor = this.updateMaxCloseFactor.bind(this) + this.updateAssetParams = this.updateAssetParams.bind(this) + this.updateVaultConfig = this.updateVaultConfig.bind(this) + this.emergencyUpdate = this.emergencyUpdate.bind(this) + } + + updateOwner = (ownerUpdate: OwnerUpdate, _funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_owner: ownerUpdate, + }), + ), + funds: _funds, + }), + } + } + updateMaxCloseFactor = (_funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_max_close_factor: {}, + }), + ), + funds: _funds, + }), + } + } + updateAssetParams = ( + assetParamsUpdate: AssetParamsUpdate, + _funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_asset_params: assetParamsUpdate, + }), + ), + funds: _funds, + }), + } + } + updateVaultConfig = ( + vaultConfigUpdate: VaultConfigUpdate, + _funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_vault_config: vaultConfigUpdate, + }), + ), + funds: _funds, + }), + } + } + emergencyUpdate = ( + emergencyUpdate: EmergencyUpdate, + _funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + emergency_update: emergencyUpdate, + }), + ), + funds: _funds, + }), + } + } +} diff --git a/scripts/types/generated/mars-params/MarsParams.react-query.ts b/scripts/types/generated/mars-params/MarsParams.react-query.ts new file mode 100644 index 000000000..c16f0644d --- /dev/null +++ b/scripts/types/generated/mars-params/MarsParams.react-query.ts @@ -0,0 +1,289 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + Decimal, + InstantiateMsg, + ExecuteMsg, + OwnerUpdate, + AssetParamsUpdate, + Uint128, + VaultConfigUpdate, + EmergencyUpdate, + RoverEmergencyUpdate, + RedBankEmergencyUpdate, + AssetParams, + RedBankSettings, + RoverSettings, + HighLeverageStrategyParams, + VaultConfigBaseForString, + Coin, + QueryMsg, + ArrayOfAssetParams, + Addr, + ArrayOfVaultConfigBaseForAddr, + VaultConfigBaseForAddr, + OwnerResponse, +} from './MarsParams.types' +import { MarsParamsQueryClient, MarsParamsClient } from './MarsParams.client' +export const marsParamsQueryKeys = { + contract: [ + { + contract: 'marsParams', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...marsParamsQueryKeys.contract[0], address: contractAddress }] as const, + owner: (contractAddress: string | undefined, args?: Record) => + [{ ...marsParamsQueryKeys.address(contractAddress)[0], method: 'owner', args }] as const, + assetParams: (contractAddress: string | undefined, args?: Record) => + [{ ...marsParamsQueryKeys.address(contractAddress)[0], method: 'asset_params', args }] as const, + allAssetParams: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsParamsQueryKeys.address(contractAddress)[0], method: 'all_asset_params', args }, + ] as const, + vaultConfig: (contractAddress: string | undefined, args?: Record) => + [{ ...marsParamsQueryKeys.address(contractAddress)[0], method: 'vault_config', args }] as const, + allVaultConfigs: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsParamsQueryKeys.address(contractAddress)[0], method: 'all_vault_configs', args }, + ] as const, + maxCloseFactor: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsParamsQueryKeys.address(contractAddress)[0], method: 'max_close_factor', args }, + ] as const, +} +export interface MarsParamsReactQuery { + client: MarsParamsQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MarsParamsMaxCloseFactorQuery + extends MarsParamsReactQuery {} +export function useMarsParamsMaxCloseFactorQuery({ + client, + options, +}: MarsParamsMaxCloseFactorQuery) { + return useQuery( + marsParamsQueryKeys.maxCloseFactor(client?.contractAddress), + () => (client ? client.maxCloseFactor() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsParamsAllVaultConfigsQuery + extends MarsParamsReactQuery { + args: { + limit?: number + startAfter?: string + } +} +export function useMarsParamsAllVaultConfigsQuery({ + client, + args, + options, +}: MarsParamsAllVaultConfigsQuery) { + return useQuery( + marsParamsQueryKeys.allVaultConfigs(client?.contractAddress, args), + () => + client + ? client.allVaultConfigs({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsParamsVaultConfigQuery + extends MarsParamsReactQuery { + args: { + address: string + } +} +export function useMarsParamsVaultConfigQuery({ + client, + args, + options, +}: MarsParamsVaultConfigQuery) { + return useQuery( + marsParamsQueryKeys.vaultConfig(client?.contractAddress, args), + () => + client + ? client.vaultConfig({ + address: args.address, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsParamsAllAssetParamsQuery + extends MarsParamsReactQuery { + args: { + limit?: number + startAfter?: string + } +} +export function useMarsParamsAllAssetParamsQuery({ + client, + args, + options, +}: MarsParamsAllAssetParamsQuery) { + return useQuery( + marsParamsQueryKeys.allAssetParams(client?.contractAddress, args), + () => + client + ? client.allAssetParams({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsParamsAssetParamsQuery + extends MarsParamsReactQuery { + args: { + denom: string + } +} +export function useMarsParamsAssetParamsQuery({ + client, + args, + options, +}: MarsParamsAssetParamsQuery) { + return useQuery( + marsParamsQueryKeys.assetParams(client?.contractAddress, args), + () => + client + ? client.assetParams({ + denom: args.denom, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsParamsOwnerQuery extends MarsParamsReactQuery {} +export function useMarsParamsOwnerQuery({ + client, + options, +}: MarsParamsOwnerQuery) { + return useQuery( + marsParamsQueryKeys.owner(client?.contractAddress), + () => (client ? client.owner() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsParamsEmergencyUpdateMutation { + client: MarsParamsClient + msg: EmergencyUpdate + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsParamsEmergencyUpdateMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.emergencyUpdate(msg, fee, memo, funds), + options, + ) +} +export interface MarsParamsUpdateVaultConfigMutation { + client: MarsParamsClient + msg: VaultConfigUpdate + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsParamsUpdateVaultConfigMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateVaultConfig(msg, fee, memo, funds), + options, + ) +} +export interface MarsParamsUpdateAssetParamsMutation { + client: MarsParamsClient + msg: AssetParamsUpdate + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsParamsUpdateAssetParamsMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateAssetParams(msg, fee, memo, funds), + options, + ) +} +export interface MarsParamsUpdateMaxCloseFactorMutation { + client: MarsParamsClient + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsParamsUpdateMaxCloseFactorMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.updateMaxCloseFactor(msg, fee, memo, funds), + options, + ) +} +export interface MarsParamsUpdateOwnerMutation { + client: MarsParamsClient + msg: OwnerUpdate + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsParamsUpdateOwnerMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateOwner(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mars-params/MarsParams.types.ts b/scripts/types/generated/mars-params/MarsParams.types.ts new file mode 100644 index 000000000..e7f4dfdec --- /dev/null +++ b/scripts/types/generated/mars-params/MarsParams.types.ts @@ -0,0 +1,159 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export type Decimal = string +export interface InstantiateMsg { + max_close_factor: Decimal + owner: string +} +export type ExecuteMsg = + | { + update_owner: OwnerUpdate + } + | { + update_max_close_factor: Decimal + } + | { + update_asset_params: AssetParamsUpdate + } + | { + update_vault_config: VaultConfigUpdate + } + | { + emergency_update: EmergencyUpdate + } +export type OwnerUpdate = + | { + propose_new_owner: { + proposed: string + } + } + | 'clear_proposed' + | 'accept_proposed' + | 'abolish_owner_role' + | { + set_emergency_owner: { + emergency_owner: string + } + } + | 'clear_emergency_owner' +export type AssetParamsUpdate = { + add_or_update: { + params: AssetParams + } +} +export type Uint128 = string +export type VaultConfigUpdate = + | { + add_or_update: { + config: VaultConfigBaseForString + } + } + | { + remove: { + addr: string + } + } +export type EmergencyUpdate = + | { + rover: RoverEmergencyUpdate + } + | { + red_bank: RedBankEmergencyUpdate + } +export type RoverEmergencyUpdate = + | { + set_zero_max_ltv_on_vault: string + } + | { + set_zero_deposit_cap_on_vault: string + } + | { + disallow_coin: string + } +export type RedBankEmergencyUpdate = { + disable_borrowing: string +} +export interface AssetParams { + denom: string + liquidation_bonus: Decimal + liquidation_threshold: Decimal + max_loan_to_value: Decimal + red_bank: RedBankSettings + rover: RoverSettings +} +export interface RedBankSettings { + borrow_enabled: boolean + deposit_cap: Uint128 + deposit_enabled: boolean +} +export interface RoverSettings { + hls: HighLeverageStrategyParams + whitelisted: boolean +} +export interface HighLeverageStrategyParams { + liquidation_threshold: Decimal + max_loan_to_value: Decimal +} +export interface VaultConfigBaseForString { + addr: string + deposit_cap: Coin + liquidation_threshold: Decimal + max_loan_to_value: Decimal + whitelisted: boolean +} +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} +export type QueryMsg = + | { + owner: {} + } + | { + asset_params: { + denom: string + } + } + | { + all_asset_params: { + limit?: number | null + start_after?: string | null + } + } + | { + vault_config: { + address: string + } + } + | { + all_vault_configs: { + limit?: number | null + start_after?: string | null + } + } + | { + max_close_factor: {} + } +export type ArrayOfAssetParams = AssetParams[] +export type Addr = string +export type ArrayOfVaultConfigBaseForAddr = VaultConfigBaseForAddr[] +export interface VaultConfigBaseForAddr { + addr: Addr + deposit_cap: Coin + liquidation_threshold: Decimal + max_loan_to_value: Decimal + whitelisted: boolean +} +export interface OwnerResponse { + abolished: boolean + emergency_owner?: string | null + initialized: boolean + owner?: string | null + proposed?: string | null +} diff --git a/scripts/types/generated/mars-params/bundle.ts b/scripts/types/generated/mars-params/bundle.ts new file mode 100644 index 000000000..a61c4e4f5 --- /dev/null +++ b/scripts/types/generated/mars-params/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _24 from './MarsParams.types' +import * as _25 from './MarsParams.client' +import * as _26 from './MarsParams.message-composer' +import * as _27 from './MarsParams.react-query' +export namespace contracts { + export const MarsParams = { ..._24, ..._25, ..._26, ..._27 } +} diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts index b62b4f36e..50d5d7e04 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -15,8 +15,10 @@ import { Addr, HealthComputer, DenomsData, - Market, - InterestRateModel, + AssetParams, + RedBankSettings, + RoverSettings, + HighLeverageStrategyParams, Positions, DebtAmount, Coin, @@ -26,7 +28,7 @@ import { VaultUnlockingPosition, VaultBaseForAddr, VaultsData, - VaultConfig, + VaultConfigBaseForAddr, VaultPositionValue, CoinValue, } from './MarsRoverHealthComputer.types' diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts index b62b4f36e..50d5d7e04 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -15,8 +15,10 @@ import { Addr, HealthComputer, DenomsData, - Market, - InterestRateModel, + AssetParams, + RedBankSettings, + RoverSettings, + HighLeverageStrategyParams, Positions, DebtAmount, Coin, @@ -26,7 +28,7 @@ import { VaultUnlockingPosition, VaultBaseForAddr, VaultsData, - VaultConfig, + VaultConfigBaseForAddr, VaultPositionValue, CoinValue, } from './MarsRoverHealthComputer.types' diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts index 70c016a8d..46d56842a 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -15,8 +15,10 @@ import { Addr, HealthComputer, DenomsData, - Market, - InterestRateModel, + AssetParams, + RedBankSettings, + RoverSettings, + HighLeverageStrategyParams, Positions, DebtAmount, Coin, @@ -26,7 +28,7 @@ import { VaultUnlockingPosition, VaultBaseForAddr, VaultsData, - VaultConfig, + VaultConfigBaseForAddr, VaultPositionValue, CoinValue, } from './MarsRoverHealthComputer.types' diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts index a150cf2cf..4c77aeee9 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -19,42 +19,38 @@ export type VaultAmount1 = string export type UnlockingPositions = VaultUnlockingPosition[] export type Addr = string export interface HealthComputer { - allowed_coins: string[] denoms_data: DenomsData positions: Positions vaults_data: VaultsData } export interface DenomsData { - markets: { - [k: string]: Market + params: { + [k: string]: AssetParams } prices: { [k: string]: Decimal } } -export interface Market { - borrow_enabled: boolean - borrow_index: Decimal - borrow_rate: Decimal - collateral_total_scaled: Uint128 - debt_total_scaled: Uint128 +export interface AssetParams { denom: string - deposit_cap: Uint128 - deposit_enabled: boolean - indexes_last_updated: number - interest_rate_model: InterestRateModel liquidation_bonus: Decimal liquidation_threshold: Decimal - liquidity_index: Decimal - liquidity_rate: Decimal max_loan_to_value: Decimal - reserve_factor: Decimal + red_bank: RedBankSettings + rover: RoverSettings +} +export interface RedBankSettings { + borrow_enabled: boolean + deposit_cap: Uint128 + deposit_enabled: boolean +} +export interface RoverSettings { + hls: HighLeverageStrategyParams + whitelisted: boolean } -export interface InterestRateModel { - base: Decimal - optimal_utilization_rate: Decimal - slope_1: Decimal - slope_2: Decimal +export interface HighLeverageStrategyParams { + liquidation_threshold: Decimal + max_loan_to_value: Decimal } export interface Positions { account_id: string @@ -95,16 +91,17 @@ export interface VaultBaseForAddr { } export interface VaultsData { vault_configs: { - [k: string]: VaultConfig + [k: string]: VaultConfigBaseForAddr } vault_values: { [k: string]: VaultPositionValue } } -export interface VaultConfig { +export interface VaultConfigBaseForAddr { + addr: Addr deposit_cap: Coin liquidation_threshold: Decimal - max_ltv: Decimal + max_loan_to_value: Decimal whitelisted: boolean } export interface VaultPositionValue { diff --git a/scripts/types/generated/mars-rover-health-computer/bundle.ts b/scripts/types/generated/mars-rover-health-computer/bundle.ts index ac1e53cd7..9dd788e81 100644 --- a/scripts/types/generated/mars-rover-health-computer/bundle.ts +++ b/scripts/types/generated/mars-rover-health-computer/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _24 from './MarsRoverHealthComputer.types' -import * as _25 from './MarsRoverHealthComputer.client' -import * as _26 from './MarsRoverHealthComputer.message-composer' -import * as _27 from './MarsRoverHealthComputer.react-query' +import * as _28 from './MarsRoverHealthComputer.types' +import * as _29 from './MarsRoverHealthComputer.client' +import * as _30 from './MarsRoverHealthComputer.message-composer' +import * as _31 from './MarsRoverHealthComputer.react-query' export namespace contracts { - export const MarsRoverHealthComputer = { ..._24, ..._25, ..._26, ..._27 } + export const MarsRoverHealthComputer = { ..._28, ..._29, ..._30, ..._31 } } diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts index 677031c1c..2ddbb46db 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -54,17 +54,19 @@ export interface MarsRoverHealthTypesInterface extends MarsRoverHealthTypesReadO ownerUpdate: OwnerUpdate, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise updateConfig: ( { creditManager, + params, }: { - creditManager: string + creditManager?: string + params?: string }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise } export class MarsRoverHealthTypesClient @@ -88,7 +90,7 @@ export class MarsRoverHealthTypesClient ownerUpdate: OwnerUpdate, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -98,18 +100,20 @@ export class MarsRoverHealthTypesClient }, fee, memo, - funds, + _funds, ) } updateConfig = async ( { creditManager, + params, }: { - creditManager: string + creditManager?: string + params?: string }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -117,11 +121,12 @@ export class MarsRoverHealthTypesClient { update_config: { credit_manager: creditManager, + params, }, }, fee, memo, - funds, + _funds, ) } } diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts index a5b436b2f..c02672dc8 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts @@ -1,12 +1,12 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { Coin } from '@cosmjs/amino' -import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { @@ -23,14 +23,16 @@ import { export interface MarsRoverHealthTypesMessage { contractAddress: string sender: string - updateOwner: (ownerUpdate: OwnerUpdate, funds?: Coin[]) => MsgExecuteContractEncodeObject + updateOwner: (ownerUpdate: OwnerUpdate, _funds?: Coin[]) => MsgExecuteContractEncodeObject updateConfig: ( { creditManager, + params, }: { - creditManager: string + creditManager?: string + params?: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject } export class MarsRoverHealthTypesMessageComposer implements MarsRoverHealthTypesMessage { @@ -44,7 +46,7 @@ export class MarsRoverHealthTypesMessageComposer implements MarsRoverHealthTypes this.updateConfig = this.updateConfig.bind(this) } - updateOwner = (ownerUpdate: OwnerUpdate, funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateOwner = (ownerUpdate: OwnerUpdate, _funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -55,17 +57,19 @@ export class MarsRoverHealthTypesMessageComposer implements MarsRoverHealthTypes update_owner: ownerUpdate, }), ), - funds, + funds: _funds, }), } } updateConfig = ( { creditManager, + params, }: { - creditManager: string + creditManager?: string + params?: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -76,10 +80,11 @@ export class MarsRoverHealthTypesMessageComposer implements MarsRoverHealthTypes JSON.stringify({ update_config: { credit_manager: creditManager, + params, }, }), ), - funds, + funds: _funds, }), } } diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts index 2e36129f5..1f0dfed8b 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -86,7 +86,8 @@ export function useMarsRoverHealthTypesHealthQuery({ export interface MarsRoverHealthTypesUpdateConfigMutation { client: MarsRoverHealthTypesClient msg: { - creditManager: string + creditManager?: string + params?: string } args?: { fee?: number | StdFee | 'auto' diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts index 4ba252e47..f4f47c529 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -14,7 +14,8 @@ export type ExecuteMsg = } | { update_config: { - credit_manager: string + credit_manager?: string | null + params?: string | null } } export type OwnerUpdate = @@ -42,8 +43,9 @@ export type QueryMsg = config: {} } export interface ConfigResponse { - credit_manager_addr?: string | null + credit_manager?: string | null owner_response: OwnerResponse + params?: string | null } export interface OwnerResponse { abolished: boolean diff --git a/scripts/types/generated/mars-rover-health-types/bundle.ts b/scripts/types/generated/mars-rover-health-types/bundle.ts index 0f32571b6..fad7ea982 100644 --- a/scripts/types/generated/mars-rover-health-types/bundle.ts +++ b/scripts/types/generated/mars-rover-health-types/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _28 from './MarsRoverHealthTypes.types' -import * as _29 from './MarsRoverHealthTypes.client' -import * as _30 from './MarsRoverHealthTypes.message-composer' -import * as _31 from './MarsRoverHealthTypes.react-query' +import * as _32 from './MarsRoverHealthTypes.types' +import * as _33 from './MarsRoverHealthTypes.client' +import * as _34 from './MarsRoverHealthTypes.message-composer' +import * as _35 from './MarsRoverHealthTypes.react-query' export namespace contracts { - export const MarsRoverHealthTypes = { ..._28, ..._29, ..._30, ..._31 } + export const MarsRoverHealthTypes = { ..._32, ..._33, ..._34, ..._35 } } diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts index e39568a67..a1274dd1c 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -115,7 +115,7 @@ export interface MarsSwapperBaseInterface extends MarsSwapperBaseReadOnlyInterfa ownerUpdate: OwnerUpdate, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise setRoute: ( { @@ -129,7 +129,7 @@ export interface MarsSwapperBaseInterface extends MarsSwapperBaseReadOnlyInterfa }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise swapExactIn: ( { @@ -143,7 +143,7 @@ export interface MarsSwapperBaseInterface extends MarsSwapperBaseReadOnlyInterfa }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise transferResult: ( { @@ -157,7 +157,7 @@ export interface MarsSwapperBaseInterface extends MarsSwapperBaseReadOnlyInterfa }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise } export class MarsSwapperBaseClient @@ -183,7 +183,7 @@ export class MarsSwapperBaseClient ownerUpdate: OwnerUpdate, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -193,7 +193,7 @@ export class MarsSwapperBaseClient }, fee, memo, - funds, + _funds, ) } setRoute = async ( @@ -208,7 +208,7 @@ export class MarsSwapperBaseClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -222,7 +222,7 @@ export class MarsSwapperBaseClient }, fee, memo, - funds, + _funds, ) } swapExactIn = async ( @@ -237,7 +237,7 @@ export class MarsSwapperBaseClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -251,7 +251,7 @@ export class MarsSwapperBaseClient }, fee, memo, - funds, + _funds, ) } transferResult = async ( @@ -266,7 +266,7 @@ export class MarsSwapperBaseClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -280,7 +280,7 @@ export class MarsSwapperBaseClient }, fee, memo, - funds, + _funds, ) } } diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts index 3392363ab..777fb67c7 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts @@ -1,11 +1,11 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { @@ -26,7 +26,7 @@ import { export interface MarsSwapperBaseMessage { contractAddress: string sender: string - updateOwner: (ownerUpdate: OwnerUpdate, funds?: Coin[]) => MsgExecuteContractEncodeObject + updateOwner: (ownerUpdate: OwnerUpdate, _funds?: Coin[]) => MsgExecuteContractEncodeObject setRoute: ( { denomIn, @@ -37,7 +37,7 @@ export interface MarsSwapperBaseMessage { denomOut: string route: Empty }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject swapExactIn: ( { @@ -49,7 +49,7 @@ export interface MarsSwapperBaseMessage { denomOut: string slippage: Decimal }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject transferResult: ( { @@ -61,7 +61,7 @@ export interface MarsSwapperBaseMessage { denomOut: string recipient: Addr }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject } export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { @@ -77,7 +77,7 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { this.transferResult = this.transferResult.bind(this) } - updateOwner = (ownerUpdate: OwnerUpdate, funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateOwner = (ownerUpdate: OwnerUpdate, _funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -88,7 +88,7 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { update_owner: ownerUpdate, }), ), - funds, + funds: _funds, }), } } @@ -102,7 +102,7 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { denomOut: string route: Empty }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -118,7 +118,7 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -132,7 +132,7 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { denomOut: string slippage: Decimal }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -148,7 +148,7 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { }, }), ), - funds, + funds: _funds, }), } } @@ -162,7 +162,7 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { denomOut: string recipient: Addr }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -178,7 +178,7 @@ export class MarsSwapperBaseMessageComposer implements MarsSwapperBaseMessage { }, }), ), - funds, + funds: _funds, }), } } diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts index 8ef5b4d3e..40e90df32 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts index 9cc56adc1..30e4d4f09 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/bundle.ts b/scripts/types/generated/mars-swapper-base/bundle.ts index 77db5ab72..c61982f6f 100644 --- a/scripts/types/generated/mars-swapper-base/bundle.ts +++ b/scripts/types/generated/mars-swapper-base/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _32 from './MarsSwapperBase.types' -import * as _33 from './MarsSwapperBase.client' -import * as _34 from './MarsSwapperBase.message-composer' -import * as _35 from './MarsSwapperBase.react-query' +import * as _36 from './MarsSwapperBase.types' +import * as _37 from './MarsSwapperBase.client' +import * as _38 from './MarsSwapperBase.message-composer' +import * as _39 from './MarsSwapperBase.react-query' export namespace contracts { - export const MarsSwapperBase = { ..._32, ..._33, ..._34, ..._35 } + export const MarsSwapperBase = { ..._36, ..._37, ..._38, ..._39 } } diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts index dceaee018..4d42464fd 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -76,7 +76,7 @@ export interface MarsV2ZapperBaseInterface extends MarsV2ZapperBaseReadOnlyInter }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise withdrawLiquidity: ( { @@ -86,13 +86,13 @@ export interface MarsV2ZapperBaseInterface extends MarsV2ZapperBaseReadOnlyInter }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise callback: ( callbackMsg: CallbackMsg, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise } export class MarsV2ZapperBaseClient @@ -125,7 +125,7 @@ export class MarsV2ZapperBaseClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -139,7 +139,7 @@ export class MarsV2ZapperBaseClient }, fee, memo, - funds, + _funds, ) } withdrawLiquidity = async ( @@ -150,7 +150,7 @@ export class MarsV2ZapperBaseClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -162,14 +162,14 @@ export class MarsV2ZapperBaseClient }, fee, memo, - funds, + _funds, ) } callback = async ( callbackMsg: CallbackMsg, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -179,7 +179,7 @@ export class MarsV2ZapperBaseClient }, fee, memo, - funds, + _funds, ) } } diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts index a484f72db..ee46a37f0 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts @@ -1,11 +1,11 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { @@ -31,7 +31,7 @@ export interface MarsV2ZapperBaseMessage { minimumReceive: Uint128 recipient?: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject withdrawLiquidity: ( { @@ -39,9 +39,9 @@ export interface MarsV2ZapperBaseMessage { }: { recipient?: string }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject - callback: (callbackMsg: CallbackMsg, funds?: Coin[]) => MsgExecuteContractEncodeObject + callback: (callbackMsg: CallbackMsg, _funds?: Coin[]) => MsgExecuteContractEncodeObject } export class MarsV2ZapperBaseMessageComposer implements MarsV2ZapperBaseMessage { sender: string @@ -65,7 +65,7 @@ export class MarsV2ZapperBaseMessageComposer implements MarsV2ZapperBaseMessage minimumReceive: Uint128 recipient?: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -81,7 +81,7 @@ export class MarsV2ZapperBaseMessageComposer implements MarsV2ZapperBaseMessage }, }), ), - funds, + funds: _funds, }), } } @@ -91,7 +91,7 @@ export class MarsV2ZapperBaseMessageComposer implements MarsV2ZapperBaseMessage }: { recipient?: string }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -105,11 +105,11 @@ export class MarsV2ZapperBaseMessageComposer implements MarsV2ZapperBaseMessage }, }), ), - funds, + funds: _funds, }), } } - callback = (callbackMsg: CallbackMsg, funds?: Coin[]): MsgExecuteContractEncodeObject => { + callback = (callbackMsg: CallbackMsg, _funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -120,7 +120,7 @@ export class MarsV2ZapperBaseMessageComposer implements MarsV2ZapperBaseMessage callback: callbackMsg, }), ), - funds, + funds: _funds, }), } } diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts index eecaeedf8..92d34c254 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts index ca83cf38f..73bac53eb 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v2-zapper-base/bundle.ts b/scripts/types/generated/mars-v2-zapper-base/bundle.ts index 1b56e7116..b1bae2705 100644 --- a/scripts/types/generated/mars-v2-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-v2-zapper-base/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _36 from './MarsV2ZapperBase.types' -import * as _37 from './MarsV2ZapperBase.client' -import * as _38 from './MarsV2ZapperBase.message-composer' -import * as _39 from './MarsV2ZapperBase.react-query' +import * as _40 from './MarsV2ZapperBase.types' +import * as _41 from './MarsV2ZapperBase.client' +import * as _42 from './MarsV2ZapperBase.message-composer' +import * as _43 from './MarsV2ZapperBase.react-query' export namespace contracts { - export const MarsV2ZapperBase = { ..._36, ..._37, ..._38, ..._39 } + export const MarsV2ZapperBase = { ..._40, ..._41, ..._42, ..._43 } } diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts index dab60b69a..8e27da25b 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -62,19 +62,19 @@ export interface MarsV3ZapperBaseInterface extends MarsV3ZapperBaseReadOnlyInter }, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise updateOwner: ( ownerUpdate: OwnerUpdate, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise callback: ( callbackMsg: CallbackMsg, fee?: number | StdFee | 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ) => Promise } export class MarsV3ZapperBaseClient @@ -115,7 +115,7 @@ export class MarsV3ZapperBaseClient }, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -133,14 +133,14 @@ export class MarsV3ZapperBaseClient }, fee, memo, - funds, + _funds, ) } updateOwner = async ( ownerUpdate: OwnerUpdate, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -150,14 +150,14 @@ export class MarsV3ZapperBaseClient }, fee, memo, - funds, + _funds, ) } callback = async ( callbackMsg: CallbackMsg, fee: number | StdFee | 'auto' = 'auto', memo?: string, - funds?: Coin[], + _funds?: Coin[], ): Promise => { return await this.client.execute( this.sender, @@ -167,7 +167,7 @@ export class MarsV3ZapperBaseClient }, fee, memo, - funds, + _funds, ) } } diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts index 450e38cc3..79567708d 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts @@ -1,11 +1,11 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import { MsgExecuteContractEncodeObject } from 'cosmwasm' +import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' import { toUtf8 } from '@cosmjs/encoding' import { @@ -41,10 +41,10 @@ export interface MarsV3ZapperBaseMessage { tokenMinAmount1: string upperTick: number }, - funds?: Coin[], + _funds?: Coin[], ) => MsgExecuteContractEncodeObject - updateOwner: (ownerUpdate: OwnerUpdate, funds?: Coin[]) => MsgExecuteContractEncodeObject - callback: (callbackMsg: CallbackMsg, funds?: Coin[]) => MsgExecuteContractEncodeObject + updateOwner: (ownerUpdate: OwnerUpdate, _funds?: Coin[]) => MsgExecuteContractEncodeObject + callback: (callbackMsg: CallbackMsg, _funds?: Coin[]) => MsgExecuteContractEncodeObject } export class MarsV3ZapperBaseMessageComposer implements MarsV3ZapperBaseMessage { sender: string @@ -76,7 +76,7 @@ export class MarsV3ZapperBaseMessageComposer implements MarsV3ZapperBaseMessage tokenMinAmount1: string upperTick: number }, - funds?: Coin[], + _funds?: Coin[], ): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', @@ -96,11 +96,11 @@ export class MarsV3ZapperBaseMessageComposer implements MarsV3ZapperBaseMessage }, }), ), - funds, + funds: _funds, }), } } - updateOwner = (ownerUpdate: OwnerUpdate, funds?: Coin[]): MsgExecuteContractEncodeObject => { + updateOwner = (ownerUpdate: OwnerUpdate, _funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -111,11 +111,11 @@ export class MarsV3ZapperBaseMessageComposer implements MarsV3ZapperBaseMessage update_owner: ownerUpdate, }), ), - funds, + funds: _funds, }), } } - callback = (callbackMsg: CallbackMsg, funds?: Coin[]): MsgExecuteContractEncodeObject => { + callback = (callbackMsg: CallbackMsg, _funds?: Coin[]): MsgExecuteContractEncodeObject => { return { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ @@ -126,7 +126,7 @@ export class MarsV3ZapperBaseMessageComposer implements MarsV3ZapperBaseMessage callback: callbackMsg, }), ), - funds, + funds: _funds, }), } } diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts index b1aaca659..5403e5684 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts index 932b4f392..1289af769 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v3-zapper-base/bundle.ts b/scripts/types/generated/mars-v3-zapper-base/bundle.ts index f82bf087e..2e869ed1a 100644 --- a/scripts/types/generated/mars-v3-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-v3-zapper-base/bundle.ts @@ -1,14 +1,14 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.27.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _40 from './MarsV3ZapperBase.types' -import * as _41 from './MarsV3ZapperBase.client' -import * as _42 from './MarsV3ZapperBase.message-composer' -import * as _43 from './MarsV3ZapperBase.react-query' +import * as _44 from './MarsV3ZapperBase.types' +import * as _45 from './MarsV3ZapperBase.client' +import * as _46 from './MarsV3ZapperBase.message-composer' +import * as _47 from './MarsV3ZapperBase.react-query' export namespace contracts { - export const MarsV3ZapperBase = { ..._40, ..._41, ..._42, ..._43 } + export const MarsV3ZapperBase = { ..._44, ..._45, ..._46, ..._47 } } diff --git a/scripts/types/storageItems.ts b/scripts/types/storageItems.ts index a25a009c9..848bde0ae 100644 --- a/scripts/types/storageItems.ts +++ b/scripts/types/storageItems.ts @@ -16,6 +16,7 @@ export interface StorageItems { zapper?: string healthContract?: string creditManager?: string + params?: string } actions: { proposedNewOwner?: boolean diff --git a/scripts/yarn.lock b/scripts/yarn.lock index b5d7465c6..4adefea6d 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -17,10 +17,10 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.5": - version "7.21.7" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc" - integrity sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.0", "@babel/compat-data@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.3.tgz#cd502a6a0b6e37d7ad72ce7e71a7160a3ae36f7e" + integrity sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ== "@babel/core@7.18.10": version "7.18.10" @@ -44,20 +44,20 @@ semver "^6.3.0" "@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.21.8" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.8.tgz#2a8c7f0f53d60100ba4c32470ba0281c92aa9aa4" - integrity sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ== + version "7.22.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.1.tgz#5de51c5206f4c6f5533562838337a603c1033cfd" + integrity sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.21.5" - "@babel/helper-compilation-targets" "^7.21.5" - "@babel/helper-module-transforms" "^7.21.5" - "@babel/helpers" "^7.21.5" - "@babel/parser" "^7.21.8" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.5" - "@babel/types" "^7.21.5" + "@babel/generator" "^7.22.0" + "@babel/helper-compilation-targets" "^7.22.1" + "@babel/helper-module-transforms" "^7.22.1" + "@babel/helpers" "^7.22.0" + "@babel/parser" "^7.22.0" + "@babel/template" "^7.21.9" + "@babel/traverse" "^7.22.1" + "@babel/types" "^7.22.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -73,12 +73,12 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/generator@^7.18.10", "@babel/generator@^7.21.5", "@babel/generator@^7.7.2": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f" - integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w== +"@babel/generator@^7.18.10", "@babel/generator@^7.22.0", "@babel/generator@^7.22.3", "@babel/generator@^7.7.2": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.3.tgz#0ff675d2edb93d7596c5f6728b52615cfc0df01e" + integrity sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A== dependencies: - "@babel/types" "^7.21.5" + "@babel/types" "^7.22.3" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" @@ -91,42 +91,42 @@ "@babel/types" "^7.18.6" "@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.21.5.tgz#817f73b6c59726ab39f6ba18c234268a519e5abb" - integrity sha512-uNrjKztPLkUk7bpCNC0jEKDJzzkvel/W+HguzbN8krA+LPfC1CEobJEvAvGka2A/M+ViOqXdcRL0GqPUJSjx9g== + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.3.tgz#c9b83d1ba74e163e023f008a3d3204588a7ceb60" + integrity sha512-ahEoxgqNoYXm0k22TvOke48i1PkavGu0qGCmcq9ugi6gnmvKNaMjKBSrZTnWUi1CFEeNAUiVba0Wtzm03aSkJg== dependencies: - "@babel/types" "^7.21.5" + "@babel/types" "^7.22.3" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366" - integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.1": + version "7.22.1" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz#bfcd6b7321ffebe33290d68550e2c9d7eb7c7a58" + integrity sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ== dependencies: - "@babel/compat-data" "^7.21.5" + "@babel/compat-data" "^7.22.0" "@babel/helper-validator-option" "^7.21.0" browserslist "^4.21.3" lru-cache "^5.1.1" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0": - version "7.21.8" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.8.tgz#205b26330258625ef8869672ebca1e0dee5a0f02" - integrity sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.1": + version "7.22.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.1.tgz#ae3de70586cc757082ae3eba57240d42f468c41b" + integrity sha512-SowrZ9BWzYFgzUMwUmowbPSGu6CXL5MSuuCkG3bejahSpSymioPmuLdhPxNOc9MjuNGjy7M/HaXvJ8G82Lywlw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.21.5" + "@babel/helper-environment-visitor" "^7.22.1" "@babel/helper-function-name" "^7.21.0" - "@babel/helper-member-expression-to-functions" "^7.21.5" + "@babel/helper-member-expression-to-functions" "^7.22.0" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.21.5" + "@babel/helper-replace-supers" "^7.22.1" "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/helper-split-export-declaration" "^7.18.6" semver "^6.3.0" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5": - version "7.21.8" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.8.tgz#a7886f61c2e29e21fd4aaeaf1e473deba6b571dc" - integrity sha512-zGuSdedkFtsFHGbexAvNuipg1hbtitDLo2XE8/uf6Y9sOQV1xsYX/2pNbtedp/X0eU1pIt+kGvaqHCowkRbS5g== +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.1": + version "7.22.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.1.tgz#a7ed9a8488b45b467fca353cd1a44dc5f0cf5c70" + integrity sha512-WWjdnfR3LPIe+0EY8td7WmjhytxXtjKAEpnAxun/hkNiyOaPlvGK+NZaBFIdi9ndYV3Gav7BpFvtUwnaJlwi1w== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.3.1" @@ -144,10 +144,22 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba" - integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ== +"@babel/helper-define-polyfill-provider@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz#487053f103110f25b9755c5980e031e93ced24d8" + integrity sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg== + dependencies: + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.1": + version "7.22.1" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz#ac3a56dbada59ed969d712cf527bd8271fe3eba8" + integrity sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA== "@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0": version "7.21.0" @@ -164,12 +176,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-member-expression-to-functions@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz#3b1a009af932e586af77c1030fba9ee0bde396c0" - integrity sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg== +"@babel/helper-member-expression-to-functions@^7.22.0": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.3.tgz#4b77a12c1b4b8e9e28736ed47d8b91f00976911f" + integrity sha512-Gl7sK04b/2WOb6OPVeNy9eFKeD3L6++CzL3ykPOWqTn08xgYYK0wz4TUh2feIImDXxcVW3/9WQ1NMKY66/jfZA== dependencies: - "@babel/types" "^7.21.5" + "@babel/types" "^7.22.3" "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4": version "7.21.4" @@ -178,19 +190,19 @@ dependencies: "@babel/types" "^7.21.4" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz#d937c82e9af68d31ab49039136a222b17ac0b420" - integrity sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw== +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5", "@babel/helper-module-transforms@^7.22.1": + version "7.22.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.1.tgz#e0cad47fedcf3cae83c11021696376e2d5a50c63" + integrity sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw== dependencies: - "@babel/helper-environment-visitor" "^7.21.5" + "@babel/helper-environment-visitor" "^7.22.1" "@babel/helper-module-imports" "^7.21.4" "@babel/helper-simple-access" "^7.21.5" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.5" - "@babel/types" "^7.21.5" + "@babel/template" "^7.21.9" + "@babel/traverse" "^7.22.1" + "@babel/types" "^7.22.0" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -214,17 +226,17 @@ "@babel/helper-wrap-function" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7", "@babel/helper-replace-supers@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz#a6ad005ba1c7d9bc2973dfde05a1bba7065dde3c" - integrity sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg== +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7", "@babel/helper-replace-supers@^7.22.1": + version "7.22.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.1.tgz#38cf6e56f7dc614af63a21b45565dd623f0fdc95" + integrity sha512-ut4qrkE4AuSfrwHSps51ekR1ZY/ygrP1tp0WFm8oVq6nzc/hvfV/22JylndIbsf2U2M9LOMwiSddr6y+78j+OQ== dependencies: - "@babel/helper-environment-visitor" "^7.21.5" - "@babel/helper-member-expression-to-functions" "^7.21.5" + "@babel/helper-environment-visitor" "^7.22.1" + "@babel/helper-member-expression-to-functions" "^7.22.0" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.5" - "@babel/types" "^7.21.5" + "@babel/template" "^7.21.9" + "@babel/traverse" "^7.22.1" + "@babel/types" "^7.22.0" "@babel/helper-simple-access@^7.21.5": version "7.21.5" @@ -272,14 +284,14 @@ "@babel/traverse" "^7.20.5" "@babel/types" "^7.20.5" -"@babel/helpers@^7.18.9", "@babel/helpers@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.5.tgz#5bac66e084d7a4d2d9696bdf0175a93f7fb63c08" - integrity sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA== +"@babel/helpers@^7.18.9", "@babel/helpers@^7.22.0": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.3.tgz#53b74351da9684ea2f694bf0877998da26dd830e" + integrity sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w== dependencies: - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.5" - "@babel/types" "^7.21.5" + "@babel/template" "^7.21.9" + "@babel/traverse" "^7.22.1" + "@babel/types" "^7.22.3" "@babel/highlight@^7.18.6": version "7.18.6" @@ -295,10 +307,10 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.20.7", "@babel/parser@^7.21.5", "@babel/parser@^7.21.8": - version "7.21.8" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8" - integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.20.7", "@babel/parser@^7.21.9", "@babel/parser@^7.22.0", "@babel/parser@^7.22.4": + version "7.22.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.4.tgz#a770e98fd785c231af9d93f6459d36770993fb32" + integrity sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -307,16 +319,16 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz#d9c85589258539a22a901033853101a6198d4ef1" - integrity sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.3.tgz#a75be1365c0c3188c51399a662168c1c98108659" + integrity sha512-6r4yRwEnorYByILoDRnEqxtojYKuiIv9FojW2E8GUKo9eWBwbKcd9IiZOZpdyXc64RmyGGyPu3/uAcrz/dq2kQ== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/plugin-proposal-optional-chaining" "^7.20.7" + "@babel/plugin-transform-optional-chaining" "^7.22.3" -"@babel/plugin-proposal-async-generator-functions@^7.18.10", "@babel/plugin-proposal-async-generator-functions@^7.20.7": +"@babel/plugin-proposal-async-generator-functions@^7.18.10": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326" integrity sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA== @@ -334,7 +346,7 @@ "@babel/helper-create-class-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-proposal-class-static-block@^7.18.6", "@babel/plugin-proposal-class-static-block@^7.21.0": +"@babel/plugin-proposal-class-static-block@^7.18.6": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz#77bdd66fb7b605f3a61302d224bdfacf5547977d" integrity sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw== @@ -375,7 +387,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-proposal-logical-assignment-operators@^7.18.9", "@babel/plugin-proposal-logical-assignment-operators@^7.20.7": +"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz#dfbcaa8f7b4d37b51e8bfb46d94a5aea2bb89d83" integrity sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug== @@ -410,7 +422,7 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.18.8" -"@babel/plugin-proposal-object-rest-spread@^7.18.9", "@babel/plugin-proposal-object-rest-spread@^7.20.7": +"@babel/plugin-proposal-object-rest-spread@^7.18.9": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== @@ -429,7 +441,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.18.9", "@babel/plugin-proposal-optional-chaining@^7.20.7", "@babel/plugin-proposal-optional-chaining@^7.21.0": +"@babel/plugin-proposal-optional-chaining@^7.18.9": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== @@ -520,6 +532,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.19.0" +"@babel/plugin-syntax-import-attributes@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.3.tgz#d7168f22b9b49a6cc1792cec78e06a18ad2e7b4b" + integrity sha512-i35jZJv6aO7hxEbIWQ41adVfOzjm9dcYDNeWlBMd8p0ZQRtNUCBrmGwZt+H5lb+oOC9a3svp956KP0oWGA1YsA== + dependencies: + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" @@ -597,13 +616,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.20.0", "@babel/plugin-syntax-typescript@^7.7.2": +"@babel/plugin-syntax-typescript@^7.21.4", "@babel/plugin-syntax-typescript@^7.7.2": version "7.21.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz#2751948e9b7c6d771a8efa59340c15d4a2891ff8" integrity sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA== dependencies: "@babel/helper-plugin-utils" "^7.20.2" +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-arrow-functions@^7.18.6", "@babel/plugin-transform-arrow-functions@^7.21.5": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929" @@ -611,6 +638,16 @@ dependencies: "@babel/helper-plugin-utils" "^7.21.5" +"@babel/plugin-transform-async-generator-functions@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.3.tgz#3ed99924c354fb9e80dabb2cc8d002c702e94527" + integrity sha512-36A4Aq48t66btydbZd5Fk0/xJqbpg/v4QWI4AH4cYHBXy9Mu42UOupZpebKFiCFNT9S9rJFcsld0gsv0ayLjtA== + dependencies: + "@babel/helper-environment-visitor" "^7.22.1" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-transform-async-to-generator@^7.18.6", "@babel/plugin-transform-async-to-generator@^7.20.7": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354" @@ -634,6 +671,23 @@ dependencies: "@babel/helper-plugin-utils" "^7.20.2" +"@babel/plugin-transform-class-properties@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.3.tgz#3407145e513830df77f0cef828b8b231c166fe4c" + integrity sha512-mASLsd6rhOrLZ5F3WbCxkzl67mmOnqik0zrg5W6D/X0QMW7HtvnoL1dRARLKIbMP3vXwkwziuLesPqWVGIl6Bw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.1" + "@babel/helper-plugin-utils" "^7.21.5" + +"@babel/plugin-transform-class-static-block@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.3.tgz#e352cf33567385c731a8f21192efeba760358773" + integrity sha512-5BirgNWNOx7cwbTJCOmKFJ1pZjwk5MUfMIwiBBvsirCJMZeQgs5pk6i1OlkVg+1Vef5LfBahFOrdCnAWvkVKMw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.1" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-transform-classes@^7.18.9", "@babel/plugin-transform-classes@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665" @@ -679,6 +733,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-dynamic-import@^7.22.1": + version "7.22.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.1.tgz#6c56afaf896a07026330cf39714532abed8d9ed1" + integrity sha512-rlhWtONnVBPdmt+jeewS0qSnMz/3yLFrqAP8hHC6EDcrYRSyuz9f9yQhHvVn2Ad6+yO9fHXac5piudeYrInxwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-exponentiation-operator@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" @@ -687,6 +749,14 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-export-namespace-from@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.3.tgz#9b8700aa495007d3bebac8358d1c562434b680b9" + integrity sha512-5Ti1cHLTDnt3vX61P9KZ5IG09bFXp4cDVFJIAeCZuxu9OXXJJZp5iP0n/rzM2+iAutJY+KWEyyHcRaHlpQ/P5g== + dependencies: + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-transform-for-of@^7.18.8", "@babel/plugin-transform-for-of@^7.21.5": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc" @@ -703,6 +773,14 @@ "@babel/helper-function-name" "^7.18.9" "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-json-strings@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.3.tgz#a181b8679cf7c93e9d0e3baa5b1776d65be601a9" + integrity sha512-IuvOMdeOOY2X4hRNAT6kwbePtK21BUyrAEgLKviL8pL6AEEVUVcqtRdN/HJXBLGIbt9T3ETmXRnFedRRmQNTYw== + dependencies: + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-transform-literals@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" @@ -710,6 +788,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-logical-assignment-operators@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.3.tgz#9e021455810f33b0baccb82fb759b194f5dc36f0" + integrity sha512-CbayIfOw4av2v/HYZEsH+Klks3NC2/MFIR3QR8gnpGNNPEaq2fdlVCRYG/paKs7/5hvBLQ+H70pGWOHtlNEWNA== + dependencies: + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-transform-member-expression-literals@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" @@ -734,14 +820,14 @@ "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-simple-access" "^7.21.5" -"@babel/plugin-transform-modules-systemjs@^7.18.9", "@babel/plugin-transform-modules-systemjs@^7.20.11": - version "7.20.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz#467ec6bba6b6a50634eea61c9c232654d8a4696e" - integrity sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw== +"@babel/plugin-transform-modules-systemjs@^7.18.9", "@babel/plugin-transform-modules-systemjs@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.3.tgz#cc507e03e88d87b016feaeb5dae941e6ef50d91e" + integrity sha512-V21W3bKLxO3ZjcBJZ8biSvo5gQ85uIXW2vJfh7JSWf/4SLUSr1tOoHX3ruN4+Oqa2m+BKfsxTR1I+PsvkIWvNw== dependencies: "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.20.11" - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-module-transforms" "^7.22.1" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-validator-identifier" "^7.19.1" "@babel/plugin-transform-modules-umd@^7.18.6": @@ -752,20 +838,47 @@ "@babel/helper-module-transforms" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6", "@babel/plugin-transform-named-capturing-groups-regex@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8" - integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA== +"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6", "@babel/plugin-transform-named-capturing-groups-regex@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.3.tgz#db6fb77e6b3b53ec3b8d370246f0b7cf67d35ab4" + integrity sha512-c6HrD/LpUdNNJsISQZpds3TXvfYIAbo+efE9aWmY/PmSRD0agrJ9cPMt4BmArwUQ7ZymEWTFjTyp+yReLJZh0Q== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.20.5" - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-create-regexp-features-plugin" "^7.22.1" + "@babel/helper-plugin-utils" "^7.21.5" -"@babel/plugin-transform-new-target@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8" - integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== +"@babel/plugin-transform-new-target@^7.18.6", "@babel/plugin-transform-new-target@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.3.tgz#deb0377d741cbee2f45305868b9026dcd6dd96e2" + integrity sha512-5RuJdSo89wKdkRTqtM9RVVJzHum9c2s0te9rB7vZC1zKKxcioWIy+xcu4OoIAjyFZhb/bp5KkunuLin1q7Ct+w== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.21.5" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.3.tgz#8c519f8bf5af94a9ca6f65cf422a9d3396e542b9" + integrity sha512-CpaoNp16nX7ROtLONNuCyenYdY/l7ZsR6aoVa7rW7nMWisoNoQNIH5Iay/4LDyRjKMuElMqXiBoOQCDLTMGZiw== + dependencies: + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-transform-numeric-separator@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.3.tgz#02493070ca6685884b0eee705363ee4da2132ab0" + integrity sha512-+AF88fPDJrnseMh5vD9+SH6wq4ZMvpiTMHh58uLs+giMEyASFVhcT3NkoyO+NebFCNnpHJEq5AXO2txV4AGPDQ== + dependencies: + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-transform-object-rest-spread@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.3.tgz#da6fba693effb8c203d8c3bdf7bf4e2567e802e9" + integrity sha512-38bzTsqMMCI46/TQnJwPPpy33EjLCc1Gsm2hRTF6zTMWnKsN61vdrpuzIEGQyKEhDSYDKyZHrrd5FMj4gcUHhw== + dependencies: + "@babel/compat-data" "^7.22.3" + "@babel/helper-compilation-targets" "^7.22.1" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.22.3" "@babel/plugin-transform-object-super@^7.18.6": version "7.18.6" @@ -775,12 +888,47 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-replace-supers" "^7.18.6" -"@babel/plugin-transform-parameters@^7.18.8", "@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.21.3": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz#18fc4e797cf6d6d972cb8c411dbe8a809fa157db" - integrity sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ== +"@babel/plugin-transform-optional-catch-binding@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.3.tgz#e971a083fc7d209d9cd18253853af1db6d8dc42f" + integrity sha512-bnDFWXFzWY0BsOyqaoSXvMQ2F35zutQipugog/rqotL2S4ciFOKlRYUu9djt4iq09oh2/34hqfRR2k1dIvuu4g== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-transform-optional-chaining@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.3.tgz#5fd24a4a7843b76da6aeec23c7f551da5d365290" + integrity sha512-63v3/UFFxhPKT8j8u1jTTGVyITxl7/7AfOqK8C5gz1rHURPUGe3y5mvIf68eYKGoBNahtJnTxBKug4BQOnzeJg== + dependencies: + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.18.8", "@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.3.tgz#24477acfd2fd2bc901df906c9bf17fbcfeee900d" + integrity sha512-x7QHQJHPuD9VmfpzboyGJ5aHEr9r7DsAsdxdhJiTB3J3j8dyl+NFZ+rX5Q2RWFDCs61c06qBfS4ys2QYn8UkMw== + dependencies: + "@babel/helper-plugin-utils" "^7.21.5" + +"@babel/plugin-transform-private-methods@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.3.tgz#adac38020bab5047482d3297107c1f58e9c574f6" + integrity sha512-fC7jtjBPFqhqpPAE+O4LKwnLq7gGkD3ZmC2E3i4qWH34mH3gOg2Xrq5YMHUq6DM30xhqM1DNftiRaSqVjEG+ug== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.1" + "@babel/helper-plugin-utils" "^7.21.5" + +"@babel/plugin-transform-private-property-in-object@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.3.tgz#031621b02c7b7d95389de1a3dba2fe9e8c548e56" + integrity sha512-C7MMl4qWLpgVCbXfj3UW8rR1xeCnisQ0cU7YJHV//8oNBS0aCIVg1vFnZXxOckHhEpQyqNNkWmvSEWnMLlc+Vw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.22.1" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-transform-property-literals@^7.18.6": version "7.18.6" @@ -853,14 +1001,14 @@ "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-typescript@^7.21.3": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.3.tgz#316c5be579856ea890a57ebc5116c5d064658f2b" - integrity sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw== + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.3.tgz#8f662cec8ba88c873f1c7663c0c94e3f68592f09" + integrity sha512-pyjnCIniO5PNaEuGxT28h0HbMru3qCVrMqVgVOz/krComdIrY9W6FCLBq9NWHY8HDGaUlan+UhmZElDENIfCcw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.21.0" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-typescript" "^7.20.0" + "@babel/helper-create-class-features-plugin" "^7.22.1" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-syntax-typescript" "^7.21.4" "@babel/plugin-transform-unicode-escapes@^7.18.10", "@babel/plugin-transform-unicode-escapes@^7.21.5": version "7.21.5" @@ -869,6 +1017,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.21.5" +"@babel/plugin-transform-unicode-property-regex@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.3.tgz#597b6a614dc93eaae605ee293e674d79d32eb380" + integrity sha512-5ScJ+OmdX+O6HRuMGW4kv7RL9vIKdtdAj9wuWUKy1wbHY3jaM/UlyIiC1G7J6UJiiyMukjjK0QwL3P0vBd0yYg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.1" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/plugin-transform-unicode-regex@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" @@ -877,6 +1033,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-unicode-sets-regex@^7.22.3": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.3.tgz#7c14ee33fa69782b0101d0f7143d3fc73ce00700" + integrity sha512-hNufLdkF8vqywRp+P55j4FHXqAX2LRUccoZHH7AFn1pq5ZOO2ISKW9w13bFZVjBoTqeve2HOgoJCcaziJVhGNw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.1" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/preset-env@7.18.10": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.10.tgz#83b8dfe70d7eea1aae5a10635ab0a5fe60dfc0f4" @@ -958,38 +1122,25 @@ core-js-compat "^3.22.1" semver "^6.3.0" -"@babel/preset-env@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.5.tgz#db2089d99efd2297716f018aeead815ac3decffb" - integrity sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg== +"@babel/preset-env@^7.22.4": + version "7.22.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.4.tgz#c86a82630f0e8c61d9bb9327b7b896732028cbed" + integrity sha512-c3lHOjbwBv0TkhYCr+XCR6wKcSZ1QbQTVdSkZUaVpLv8CVWotBMArWUi5UAJrcrQaEnleVkkvaV8F/pmc/STZQ== dependencies: - "@babel/compat-data" "^7.21.5" - "@babel/helper-compilation-targets" "^7.21.5" + "@babel/compat-data" "^7.22.3" + "@babel/helper-compilation-targets" "^7.22.1" "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-validator-option" "^7.21.0" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.20.7" - "@babel/plugin-proposal-async-generator-functions" "^7.20.7" - "@babel/plugin-proposal-class-properties" "^7.18.6" - "@babel/plugin-proposal-class-static-block" "^7.21.0" - "@babel/plugin-proposal-dynamic-import" "^7.18.6" - "@babel/plugin-proposal-export-namespace-from" "^7.18.9" - "@babel/plugin-proposal-json-strings" "^7.18.6" - "@babel/plugin-proposal-logical-assignment-operators" "^7.20.7" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" - "@babel/plugin-proposal-numeric-separator" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.20.7" - "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" - "@babel/plugin-proposal-optional-chaining" "^7.21.0" - "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.3" "@babel/plugin-proposal-private-property-in-object" "^7.21.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" "@babel/plugin-syntax-class-static-block" "^7.14.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" "@babel/plugin-syntax-import-assertions" "^7.20.0" + "@babel/plugin-syntax-import-attributes" "^7.22.3" "@babel/plugin-syntax-import-meta" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" @@ -1000,28 +1151,43 @@ "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.21.5" + "@babel/plugin-transform-async-generator-functions" "^7.22.3" "@babel/plugin-transform-async-to-generator" "^7.20.7" "@babel/plugin-transform-block-scoped-functions" "^7.18.6" "@babel/plugin-transform-block-scoping" "^7.21.0" + "@babel/plugin-transform-class-properties" "^7.22.3" + "@babel/plugin-transform-class-static-block" "^7.22.3" "@babel/plugin-transform-classes" "^7.21.0" "@babel/plugin-transform-computed-properties" "^7.21.5" "@babel/plugin-transform-destructuring" "^7.21.3" "@babel/plugin-transform-dotall-regex" "^7.18.6" "@babel/plugin-transform-duplicate-keys" "^7.18.9" + "@babel/plugin-transform-dynamic-import" "^7.22.1" "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-export-namespace-from" "^7.22.3" "@babel/plugin-transform-for-of" "^7.21.5" "@babel/plugin-transform-function-name" "^7.18.9" + "@babel/plugin-transform-json-strings" "^7.22.3" "@babel/plugin-transform-literals" "^7.18.9" + "@babel/plugin-transform-logical-assignment-operators" "^7.22.3" "@babel/plugin-transform-member-expression-literals" "^7.18.6" "@babel/plugin-transform-modules-amd" "^7.20.11" "@babel/plugin-transform-modules-commonjs" "^7.21.5" - "@babel/plugin-transform-modules-systemjs" "^7.20.11" + "@babel/plugin-transform-modules-systemjs" "^7.22.3" "@babel/plugin-transform-modules-umd" "^7.18.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.20.5" - "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.3" + "@babel/plugin-transform-new-target" "^7.22.3" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.3" + "@babel/plugin-transform-numeric-separator" "^7.22.3" + "@babel/plugin-transform-object-rest-spread" "^7.22.3" "@babel/plugin-transform-object-super" "^7.18.6" - "@babel/plugin-transform-parameters" "^7.21.3" + "@babel/plugin-transform-optional-catch-binding" "^7.22.3" + "@babel/plugin-transform-optional-chaining" "^7.22.3" + "@babel/plugin-transform-parameters" "^7.22.3" + "@babel/plugin-transform-private-methods" "^7.22.3" + "@babel/plugin-transform-private-property-in-object" "^7.22.3" "@babel/plugin-transform-property-literals" "^7.18.6" "@babel/plugin-transform-regenerator" "^7.21.5" "@babel/plugin-transform-reserved-words" "^7.18.6" @@ -1031,13 +1197,15 @@ "@babel/plugin-transform-template-literals" "^7.18.9" "@babel/plugin-transform-typeof-symbol" "^7.18.9" "@babel/plugin-transform-unicode-escapes" "^7.21.5" + "@babel/plugin-transform-unicode-property-regex" "^7.22.3" "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/plugin-transform-unicode-sets-regex" "^7.22.3" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.21.5" - babel-plugin-polyfill-corejs2 "^0.3.3" - babel-plugin-polyfill-corejs3 "^0.6.0" - babel-plugin-polyfill-regenerator "^0.4.1" - core-js-compat "^3.25.1" + "@babel/types" "^7.22.4" + babel-plugin-polyfill-corejs2 "^0.4.3" + babel-plugin-polyfill-corejs3 "^0.8.1" + babel-plugin-polyfill-regenerator "^0.5.0" + core-js-compat "^3.30.2" semver "^6.3.0" "@babel/preset-modules@^0.1.5": @@ -1068,20 +1236,20 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.11.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.8.4": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" - integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q== + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb" + integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ== dependencies: regenerator-runtime "^0.13.11" -"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" - integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== +"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.21.9", "@babel/template@^7.3.3": + version "7.21.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.21.9.tgz#bf8dad2859130ae46088a99c1f265394877446fb" + integrity sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ== dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" + "@babel/code-frame" "^7.21.4" + "@babel/parser" "^7.21.9" + "@babel/types" "^7.21.5" "@babel/traverse@7.18.11": version "7.18.11" @@ -1099,19 +1267,19 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.18.10", "@babel/traverse@^7.20.5", "@babel/traverse@^7.21.5", "@babel/traverse@^7.7.2": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133" - integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw== +"@babel/traverse@^7.18.10", "@babel/traverse@^7.20.5", "@babel/traverse@^7.22.1", "@babel/traverse@^7.7.2": + version "7.22.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.4.tgz#c3cf96c5c290bd13b55e29d025274057727664c0" + integrity sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ== dependencies: "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.21.5" - "@babel/helper-environment-visitor" "^7.21.5" + "@babel/generator" "^7.22.3" + "@babel/helper-environment-visitor" "^7.22.1" "@babel/helper-function-name" "^7.21.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.21.5" - "@babel/types" "^7.21.5" + "@babel/parser" "^7.22.4" + "@babel/types" "^7.22.4" debug "^4.1.0" globals "^11.1.0" @@ -1124,10 +1292,10 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6" - integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q== +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3", "@babel/types@^7.22.4", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.22.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.4.tgz#56a2653ae7e7591365dabf20b76295410684c071" + integrity sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA== dependencies: "@babel/helper-string-parser" "^7.21.5" "@babel/helper-validator-identifier" "^7.19.1" @@ -1279,10 +1447,10 @@ resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.30.1.tgz#6d92582341be3c2ec8d82090253cfa4b7f959edb" integrity sha512-KvvX58MGMWh7xA+N+deCfunkA/ZNDvFLw4YbOmX3f/XBIkqrVY7qlotfy2aNb1kgp6h4B6Yc8YawJPDTfvWX7g== -"@cosmwasm/ts-codegen@^0.27.0": - version "0.27.0" - resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.27.0.tgz#dcc74eec41aa599a7a5e2a18a4f97b74c008c92f" - integrity sha512-aNZFf7MjVv0D/LX4xpPgv1nao3+G1Mq19rjnEVgRhUKoTILl8RFoyK8dCo2LOIlSg3trdl2nlk6luYpI4AHPZw== +"@cosmwasm/ts-codegen@^0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.30.0.tgz#9910c87471801e05475806b18428400444830160" + integrity sha512-BR2HuHUx+PgMj6KNLTwH1HNVQ5SYveJst5XxkeAGMuOSe4Rbe+0XDmq+jEbrLXK0ZRkCFY0OwcLKMhjsJOo4jA== dependencies: "@babel/core" "7.18.10" "@babel/generator" "7.18.12" @@ -1310,7 +1478,7 @@ parse-package-name "1.0.0" rimraf "3.0.2" shelljs "0.8.5" - wasm-ast-types "^0.20.0" + wasm-ast-types "^0.23.0" "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" @@ -1324,14 +1492,14 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== -"@eslint/eslintrc@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02" - integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ== +"@eslint/eslintrc@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331" + integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.5.1" + espree "^9.5.2" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -1339,10 +1507,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.39.0": - version "8.39.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.39.0.tgz#58b536bcc843f4cd1e02a7e6171da5c040f4d44b" - integrity sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng== +"@eslint/js@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.41.0.tgz#080321c3b68253522f7646b55b577dd99d2950b3" + integrity sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA== "@humanwhocodes/config-array@^0.11.8": version "0.11.8" @@ -1653,6 +1821,18 @@ resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== +"@kwsites/file-exists@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" + integrity sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw== + dependencies: + debug "^4.1.1" + +"@kwsites/promise-deferred@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz#8ace5259254426ccef57f3175bc64ed7095ed919" + integrity sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw== + "@noble/hashes@^1", "@noble/hashes@^1.0.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" @@ -1771,24 +1951,24 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== -"@sinonjs/commons@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" - integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== +"@sinonjs/commons@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" + integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== dependencies: type-detect "4.0.8" "@sinonjs/fake-timers@^10.0.2": - version "10.0.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz#d10549ed1f423d80639c528b6c7f5a1017747d0c" - integrity sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw== + version "10.2.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.2.0.tgz#b3e322a34c5f26e3184e7f6115695f299c1b1194" + integrity sha512-OPwQlEdg40HAj5KNF8WW6q2KG4Z+cBCZb3m4ninfTZKaBmbIJodviQsDBoYMPHkOyJJMHnOJo5j2+LKDOhOACg== dependencies: - "@sinonjs/commons" "^2.0.0" + "@sinonjs/commons" "^3.0.0" "@types/babel__core@^7.1.14": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891" - integrity sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ== + version "7.20.1" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b" + integrity sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw== dependencies: "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" @@ -1812,11 +1992,11 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.5.tgz#c107216842905afafd3b6e774f6f935da6f5db80" - integrity sha512-enCvTL8m/EHS/zIvJno9nE+ndYPh1/oNFzRYRmtUqJICG2VnCSBzMLW5VN2KCQU91f23tsNKR8v7VJJQMatl7Q== + version "7.20.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.0.tgz#4709d34d3eba3e1dad1950d40e80c6b5e0b81fc9" + integrity sha512-TBOjqAGf0hmaqRwpii5LLkJLg7c6OMm4nHLmpsUxwk9bBHtoTC6dAHdVWdGv4TBxj2CZOZY8Xfq8WmfoVi7n4Q== dependencies: - "@babel/types" "^7.3.0" + "@babel/types" "^7.20.7" "@types/glob@^7.1.3": version "7.2.0" @@ -1861,14 +2041,14 @@ pretty-format "^29.0.0" "@types/json-schema@^7.0.11", "@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + version "7.0.12" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" + integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== "@types/lodash@^4.14.182": - version "4.14.194" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.194.tgz#b71eb6f7a0ff11bff59fc987134a093029258a76" - integrity sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g== + version "4.14.195" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632" + integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg== "@types/long@^4.0.1": version "4.0.2" @@ -1881,9 +2061,9 @@ integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/node@*", "@types/node@>=13.7.0": - version "18.16.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.3.tgz#6bda7819aae6ea0b386ebc5b24bdf602f1b42b01" - integrity sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q== + version "20.2.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.2.5.tgz#26d295f3570323b2837d322180dfbf1ba156fefb" + integrity sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ== "@types/prettier@^2.1.5", "@types/prettier@^2.6.1": version "2.7.2" @@ -1891,9 +2071,9 @@ integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== "@types/semver@^7.3.12": - version "7.3.13" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" - integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== + version "7.5.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" + integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== "@types/stack-utils@^2.0.0": version "2.0.1" @@ -1912,15 +2092,15 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.59.2": - version "5.59.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.2.tgz#684a2ce7182f3b4dac342eef7caa1c2bae476abd" - integrity sha512-yVrXupeHjRxLDcPKL10sGQ/QlVrA8J5IYOEWVqk0lJaSZP7X5DfnP7Ns3cc74/blmbipQ1htFNVGsHX6wsYm0A== +"@typescript-eslint/eslint-plugin@^5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.8.tgz#1e7a3e5318ece22251dfbc5c9c6feeb4793cc509" + integrity sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.59.2" - "@typescript-eslint/type-utils" "5.59.2" - "@typescript-eslint/utils" "5.59.2" + "@typescript-eslint/scope-manager" "5.59.8" + "@typescript-eslint/type-utils" "5.59.8" + "@typescript-eslint/utils" "5.59.8" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -1928,72 +2108,72 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.59.2": - version "5.59.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.2.tgz#c2c443247901d95865b9f77332d9eee7c55655e8" - integrity sha512-uq0sKyw6ao1iFOZZGk9F8Nro/8+gfB5ezl1cA06SrqbgJAt0SRoFhb9pXaHvkrxUpZaoLxt8KlovHNk8Gp6/HQ== +"@typescript-eslint/parser@^5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.8.tgz#60cbb00671d86cf746044ab797900b1448188567" + integrity sha512-AnR19RjJcpjoeGojmwZtCwBX/RidqDZtzcbG3xHrmz0aHHoOcbWnpDllenRDmDvsV0RQ6+tbb09/kyc+UT9Orw== dependencies: - "@typescript-eslint/scope-manager" "5.59.2" - "@typescript-eslint/types" "5.59.2" - "@typescript-eslint/typescript-estree" "5.59.2" + "@typescript-eslint/scope-manager" "5.59.8" + "@typescript-eslint/types" "5.59.8" + "@typescript-eslint/typescript-estree" "5.59.8" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.59.2": - version "5.59.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz#f699fe936ee4e2c996d14f0fdd3a7da5ba7b9a4c" - integrity sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA== +"@typescript-eslint/scope-manager@5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.8.tgz#ff4ad4fec6433647b817c4a7d4b4165d18ea2fa8" + integrity sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig== dependencies: - "@typescript-eslint/types" "5.59.2" - "@typescript-eslint/visitor-keys" "5.59.2" + "@typescript-eslint/types" "5.59.8" + "@typescript-eslint/visitor-keys" "5.59.8" -"@typescript-eslint/type-utils@5.59.2": - version "5.59.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.2.tgz#0729c237503604cd9a7084b5af04c496c9a4cdcf" - integrity sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ== +"@typescript-eslint/type-utils@5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.8.tgz#aa6c029a9d7706d26bbd25eb4666398781df6ea2" + integrity sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA== dependencies: - "@typescript-eslint/typescript-estree" "5.59.2" - "@typescript-eslint/utils" "5.59.2" + "@typescript-eslint/typescript-estree" "5.59.8" + "@typescript-eslint/utils" "5.59.8" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.59.2": - version "5.59.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.2.tgz#b511d2b9847fe277c5cb002a2318bd329ef4f655" - integrity sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w== +"@typescript-eslint/types@5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.8.tgz#212e54414733618f5d0fd50b2da2717f630aebf8" + integrity sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w== -"@typescript-eslint/typescript-estree@5.59.2": - version "5.59.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz#6e2fabd3ba01db5d69df44e0b654c0b051fe9936" - integrity sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q== +"@typescript-eslint/typescript-estree@5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.8.tgz#801a7b1766481629481b3b0878148bd7a1f345d7" + integrity sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg== dependencies: - "@typescript-eslint/types" "5.59.2" - "@typescript-eslint/visitor-keys" "5.59.2" + "@typescript-eslint/types" "5.59.8" + "@typescript-eslint/visitor-keys" "5.59.8" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.59.2": - version "5.59.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.2.tgz#0c45178124d10cc986115885688db6abc37939f4" - integrity sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ== +"@typescript-eslint/utils@5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.8.tgz#34d129f35a2134c67fdaf024941e8f96050dca2b" + integrity sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.59.2" - "@typescript-eslint/types" "5.59.2" - "@typescript-eslint/typescript-estree" "5.59.2" + "@typescript-eslint/scope-manager" "5.59.8" + "@typescript-eslint/types" "5.59.8" + "@typescript-eslint/typescript-estree" "5.59.8" eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.59.2": - version "5.59.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz#37a419dc2723a3eacbf722512b86d6caf7d3b750" - integrity sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig== +"@typescript-eslint/visitor-keys@5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz#aa6a7ef862add919401470c09e1609392ef3cc40" + integrity sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ== dependencies: - "@typescript-eslint/types" "5.59.2" + "@typescript-eslint/types" "5.59.8" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -2114,13 +2294,20 @@ ast-stringify@0.1.0: dependencies: "@babel/runtime" "^7.11.2" -axios@^0.21.1, axios@^0.21.2: +axios@^0.21.2: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: follow-redirects "^1.14.0" +axios@^0.26.1: + version "0.26.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== + dependencies: + follow-redirects "^1.14.8" + babel-jest@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.5.0.tgz#3fe3ddb109198e78b1c88f9ebdecd5e4fc2f50a5" @@ -2155,7 +2342,7 @@ babel-plugin-jest-hoist@^29.5.0: "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" -babel-plugin-polyfill-corejs2@^0.3.2, babel-plugin-polyfill-corejs2@^0.3.3: +babel-plugin-polyfill-corejs2@^0.3.2: version "0.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== @@ -2164,6 +2351,15 @@ babel-plugin-polyfill-corejs2@^0.3.2, babel-plugin-polyfill-corejs2@^0.3.3: "@babel/helper-define-polyfill-provider" "^0.3.3" semver "^6.1.1" +babel-plugin-polyfill-corejs2@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz#75044d90ba5043a5fb559ac98496f62f3eb668fd" + integrity sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw== + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-define-polyfill-provider" "^0.4.0" + semver "^6.1.1" + babel-plugin-polyfill-corejs3@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7" @@ -2172,21 +2368,28 @@ babel-plugin-polyfill-corejs3@^0.5.3: "@babel/helper-define-polyfill-provider" "^0.3.2" core-js-compat "^3.21.0" -babel-plugin-polyfill-corejs3@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a" - integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== +babel-plugin-polyfill-corejs3@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz#39248263c38191f0d226f928d666e6db1b4b3a8a" + integrity sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.3" - core-js-compat "^3.25.1" + "@babel/helper-define-polyfill-provider" "^0.4.0" + core-js-compat "^3.30.1" -babel-plugin-polyfill-regenerator@^0.4.0, babel-plugin-polyfill-regenerator@^0.4.1: +babel-plugin-polyfill-regenerator@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== dependencies: "@babel/helper-define-polyfill-provider" "^0.3.3" +babel-plugin-polyfill-regenerator@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz#e7344d88d9ef18a3c47ded99362ae4a757609380" + integrity sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.4.0" + babel-preset-current-node-syntax@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" @@ -2228,14 +2431,14 @@ bech32@^1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -binary-install@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/binary-install/-/binary-install-0.1.1.tgz#c1b22f174581764e5c52cd16664cf1d287e38bd4" - integrity sha512-DqED0D/6LrS+BHDkKn34vhRqOGjy5gTMgvYZsGK2TpNbdPuz4h+MRlNgGv5QBRd7pWq/jylM4eKNCizgAq3kNQ== +binary-install@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/binary-install/-/binary-install-1.1.0.tgz#61195349acabf5a043f3805b03f96e506cc96d6e" + integrity sha512-rkwNGW+3aQVSZoD0/o3mfPN6Yxh3Id0R/xzTVBVVpGNlVz8EGwusksxRlbk/A5iKTZt9zkMn3qIqmAt3vpfbzg== dependencies: - axios "^0.21.1" + axios "^0.26.1" rimraf "^3.0.2" - tar "^6.1.0" + tar "^6.1.11" bn.js@^4.11.9: version "4.12.0" @@ -2275,14 +2478,14 @@ brorand@^1.1.0: integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== browserslist@^4.21.3, browserslist@^4.21.5: - version "4.21.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" - integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== + version "4.21.7" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.7.tgz#e2b420947e5fb0a58e8f4668ae6e23488127e551" + integrity sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA== dependencies: - caniuse-lite "^1.0.30001449" - electron-to-chromium "^1.4.284" - node-releases "^2.0.8" - update-browserslist-db "^1.0.10" + caniuse-lite "^1.0.30001489" + electron-to-chromium "^1.4.411" + node-releases "^2.0.12" + update-browserslist-db "^1.0.11" bser@2.1.1: version "2.1.1" @@ -2316,10 +2519,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001449: - version "1.0.30001482" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001482.tgz#8b3fad73dc35b2674a5c96df2d4f9f1c561435de" - integrity sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ== +caniuse-lite@^1.0.30001489: + version "1.0.30001491" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001491.tgz#eab0e0f392de6f7411751d148de9b5bd6b203e46" + integrity sha512-17EYIi4TLnPiTzVKMveIxU5ETlxbSO3B6iPvMbprqnKh4qJsQGk5Nh1Lp4jIMAE0XfrujsJuWZAM3oJdMHaKBA== case@1.6.3: version "1.6.3" @@ -2492,10 +2695,10 @@ copyfiles@^2.4.1: untildify "^4.0.0" yargs "^16.1.0" -core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.25.1: - version "3.30.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.1.tgz#961541e22db9c27fc48bfc13a3cafa8734171dfe" - integrity sha512-d690npR7MC6P0gq4npTl5n2VQeNAmUrJ90n+MHiKS7W2+xno4o3F5GDEuylSdi6EJ3VssibSGXOa1r3YXD3Mhw== +core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.30.1, core-js-compat@^3.30.2: + version "3.30.2" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.2.tgz#83f136e375babdb8c80ad3c22d67c69098c1dd8b" + integrity sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA== dependencies: browserslist "^4.21.5" @@ -2504,7 +2707,7 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cosmjs-types@^0.7.1, cosmjs-types@^0.7.2: +cosmjs-types@^0.7.1: version "0.7.2" resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.7.2.tgz#a757371abd340949c5bd5d49c6f8379ae1ffd7e2" integrity sha512-vf2uLyktjr/XVAgEq0DjMxeAWh1yYREe7AMHDKd7EiHVqxBPCaBS+qEEQUkXbR9ndnckqr1sUG8BQhazh4X5lA== @@ -2512,6 +2715,14 @@ cosmjs-types@^0.7.1, cosmjs-types@^0.7.2: long "^4.0.0" protobufjs "~6.11.2" +cosmjs-types@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.8.0.tgz#2ed78f3e990f770229726f95f3ef5bf9e2b6859b" + integrity sha512-Q2Mj95Fl0PYMWEhA2LuGEIhipF7mQwd9gTQ85DdP9jjjopeoGaDxvmPa5nakNzsq7FnO1DMTatXTAx6bxMH7Lg== + dependencies: + long "^4.0.0" + protobufjs "~6.11.2" + cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2598,10 +2809,10 @@ dotty@0.1.2: resolved "https://registry.yarnpkg.com/dotty/-/dotty-0.1.2.tgz#512d44cc4111a724931226259297f235e8484f6f" integrity sha512-V0EWmKeH3DEhMwAZ+8ZB2Ao4OK6p++Z0hsDtZq3N0+0ZMVqkzrcEGROvOnZpLnvBg5PTNG23JEDLAm64gPaotQ== -electron-to-chromium@^1.4.284: - version "1.4.380" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.380.tgz#195dc59d930c6b74efbee6f0e6a267ce4af5ed91" - integrity sha512-XKGdI4pWM78eLH2cbXJHiBnWUwFSzZM7XujsB6stDiGu9AeSqziedP6amNLpJzE3i0rLTcfAwdCTs5ecP5yeSg== +electron-to-chromium@^1.4.411: + version "1.4.413" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.413.tgz#0067c3122946ae234cbefb9401ecefde851cdcf2" + integrity sha512-Gd+/OAhRca06dkVxIQo/W7dr6Nmk9cx6lQdZ19GvFp51k5B/lUAokm6SJfNkdV8kFLsC3Z4sLTyEHWCnB1Efbw== elliptic@^6.5.4: version "6.5.4" @@ -2710,20 +2921,20 @@ eslint-scope@^7.2.0: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" - integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" + integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== -eslint@^8.39.0: - version "8.39.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.39.0.tgz#7fd20a295ef92d43809e914b70c39fd5a23cf3f1" - integrity sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og== +eslint@^8.41.0: + version "8.41.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.41.0.tgz#3062ca73363b4714b16dbc1e60f035e6134b6f1c" + integrity sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.2" - "@eslint/js" "8.39.0" + "@eslint/eslintrc" "^2.0.3" + "@eslint/js" "8.41.0" "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -2734,8 +2945,8 @@ eslint@^8.39.0: doctrine "^3.0.0" escape-string-regexp "^4.0.0" eslint-scope "^7.2.0" - eslint-visitor-keys "^3.4.0" - espree "^9.5.1" + eslint-visitor-keys "^3.4.1" + espree "^9.5.2" esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -2743,13 +2954,12 @@ eslint@^8.39.0: find-up "^5.0.0" glob-parent "^6.0.2" globals "^13.19.0" - grapheme-splitter "^1.0.4" + graphemer "^1.4.0" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" is-path-inside "^3.0.3" - js-sdsl "^4.1.4" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" @@ -2761,14 +2971,14 @@ eslint@^8.39.0: strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.5.1: - version "9.5.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4" - integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg== +espree@^9.5.2: + version "9.5.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b" + integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw== dependencies: acorn "^8.8.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.0" + eslint-visitor-keys "^3.4.1" esprima@^4.0.0: version "4.0.1" @@ -2958,7 +3168,7 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -follow-redirects@^1.14.0: +follow-redirects@^1.14.0, follow-redirects@^1.14.8: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== @@ -3001,12 +3211,13 @@ get-caller-file@^2.0.5: integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-intrinsic@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" - integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== dependencies: function-bind "^1.1.1" has "^1.0.3" + has-proto "^1.0.1" has-symbols "^1.0.3" get-package-type@^0.1.0: @@ -3109,6 +3320,11 @@ grapheme-splitter@^1.0.4: resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -3133,6 +3349,11 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.1.1" +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" @@ -3295,9 +3516,9 @@ is-arrayish@^0.2.1: integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-core-module@^2.11.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" - integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== + version "2.12.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" + integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== dependencies: has "^1.0.3" @@ -3816,11 +4037,6 @@ jest@^29.5.0: import-local "^3.0.2" jest-cli "^29.5.0" -js-sdsl@^4.1.4: - version "4.4.0" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.0.tgz#8b437dbe642daa95760400b602378ed8ffea8430" - integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== - js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -4120,10 +4336,10 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.8: - version "2.0.10" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" - integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== +node-releases@^2.0.12: + version "2.0.12" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039" + integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ== noms@0.0.0: version "0.0.0" @@ -4559,9 +4775,9 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== semver@^7.3.5, semver@^7.3.7: - version "7.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" - integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== + version "7.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" + integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== dependencies: lru-cache "^6.0.0" @@ -4591,6 +4807,15 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +simple-git@^3.19.0: + version "3.19.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.19.0.tgz#fe8d0cd86a0e68372b75c0c44a0cb887201c3f7d" + integrity sha512-hyH2p9Ptxjf/xPuL7HfXbpYt9gKhC1yWDh3KYIAYJJePAKV7AEjLN4xhp7lozOdNiaJ9jlVvAbBymVlcS2jRiA== + dependencies: + "@kwsites/file-exists" "^1.1.1" + "@kwsites/promise-deferred" "^1.1.1" + debug "^4.3.4" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -4742,10 +4967,10 @@ symbol-observable@^2.0.3: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-2.0.3.tgz#5b521d3d07a43c351055fa43b8355b62d33fd16a" integrity sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA== -tar@^6.1.0: - version "6.1.14" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.14.tgz#e87926bec1cfe7c9e783a77a79f3e81c1cfa3b66" - integrity sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw== +tar@^6.1.11: + version "6.1.15" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69" + integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -4920,7 +5145,7 @@ untildify@^4.0.0: resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== -update-browserslist-db@^1.0.10: +update-browserslist-db@^1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== @@ -4968,10 +5193,10 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -wasm-ast-types@^0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.20.0.tgz#93782528318620072ce9650f0e16861c8c620c08" - integrity sha512-EBEf0PaBGwK8eAKjG5SPDY6Dj1AEG4x07paGaJ+hAhltAsF2PgfBikR3i1UIdFjj8BFb2nGwlRTfWPH1ll9Dyg== +wasm-ast-types@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.23.0.tgz#fa4f674d9b9c7141d685d70f7adcd52a6f20c68c" + integrity sha512-WNGOmIBgIQefGOJWZQ4dKpASpnb7+SNnJNlyvcXpYel7ozjhpgIhWw0+udCquNHzRAA6KApMFBHMa4k2HIzbUw== dependencies: "@babel/runtime" "^7.18.9" "@babel/types" "7.18.10" @@ -4980,12 +5205,12 @@ wasm-ast-types@^0.20.0: case "1.6.3" deepmerge "4.2.2" -wasm-pack@^0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/wasm-pack/-/wasm-pack-0.10.3.tgz#2d7dd78ba539c34b3817e2249c3f30c646c84b69" - integrity sha512-dg1PPyp+QwWrhfHsgG12K/y5xzwfaAoK1yuVC/DUAuQsDy5JywWDuA7Y/ionGwQz+JBZVw8jknaKBnaxaJfwTA== +wasm-pack@^0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/wasm-pack/-/wasm-pack-0.11.1.tgz#ca3eb5099b0e9f700ffc3b3f5ec4b956a521e808" + integrity sha512-0BKEioKJY/SMqahDEoaUUR8jrRkHO0cdYhRqqHKQMY3Bac6Eep3ZRsTlpFSSwS4LYPxd+Tb5KFFNhUikCkq8Yg== dependencies: - binary-install "^0.1.0" + binary-install "^1.0.1" which@^2.0.1: version "2.0.2" From b9f1082ccc62fa47c19dda69c2e8f3a27a453825 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Thu, 8 Jun 2023 21:07:49 +0200 Subject: [PATCH 162/218] Add min_receive to zapper withdraw (#140) * Add min_receive to zapper withdraw * fix pipeline * temp ignore tests --- .github/workflows/coverage.yml | 7 +- Cargo.lock | 66 +++--- Cargo.toml | 8 +- Makefile.toml | 2 +- contracts/credit-manager/src/execute.rs | 5 +- contracts/credit-manager/src/zap.rs | 3 +- .../credit-manager/tests/helpers/mock_env.rs | 6 +- .../credit-manager/tests/test_zap_provide.rs | 1 + .../credit-manager/tests/test_zap_withdraw.rs | 81 ++++++- contracts/v2-zapper/base/src/contract.rs | 11 +- contracts/v2-zapper/base/src/msg.rs | 1 + contracts/v2-zapper/mock/Cargo.toml | 13 +- contracts/v2-zapper/mock/examples/schema.rs | 2 +- contracts/v2-zapper/mock/src/contract.rs | 7 +- contracts/v2-zapper/mock/src/execute.rs | 31 ++- contracts/v2-zapper/mock/src/lib.rs | 1 + contracts/v2-zapper/mock/src/msg.rs | 14 ++ .../osmosis/tests/test_withdraw_liquidity.rs | 224 ++++++++++++------ .../osmosis/tests/test_add_position.rs | 7 + packages/rover/Cargo.toml | 1 + packages/rover/src/adapters/zapper.rs | 10 +- packages/rover/src/msg/execute.rs | 2 + packages/rover/src/msg/mod.rs | 1 - packages/rover/src/msg/zapper.rs | 44 ---- .../mars-credit-manager.json | 18 +- .../mars-v2-zapper-base.json | 9 + scripts/deploy/base/rover.ts | 1 + .../MarsCreditManager.types.ts | 2 + .../MarsV2ZapperBase.client.ts | 5 + .../MarsV2ZapperBase.message-composer.ts | 5 + .../MarsV2ZapperBase.react-query.ts | 1 + .../MarsV2ZapperBase.types.ts | 1 + 32 files changed, 408 insertions(+), 182 deletions(-) create mode 100644 contracts/v2-zapper/mock/src/msg.rs delete mode 100644 packages/rover/src/msg/zapper.rs diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 123b4cc52..2609790d6 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -32,9 +32,12 @@ jobs: - name: Install cargo make uses: davidB/rust-cargo-make@v1 - # artifacts used by tests + # Artifacts used by tests. + # Change owner of the current directory (rust-optimizer set root for artifacts / target). - name: Compile workspace - run: cargo make build + run: | + cargo make rust-optimizer + sudo chown -R $USER . - name: Run test coverage run: cargo make coverage-lcov diff --git a/Cargo.lock b/Cargo.lock index 849efa657..120d05b6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] @@ -329,9 +329,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75836a10cb9654c54e77ee56da94d592923092a10b369cdb0dbd56acefc16340" +checksum = "41c0e41be7e6c7d7ab3c61cdc32fcfaa14f948491a401cbc1c74bb33b6f4b851" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -342,18 +342,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c9f7f0e51bfc7295f7b2664fe8513c966428642aa765dad8a74acdab5e0c773" +checksum = "3a7ee2798c92c00dd17bebb4210f81d5f647e5e92d847959b7977e0fd29a3500" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f00b363610218eea83f24bbab09e1a7c3920b79f068334fdfcc62f6129ef9fc" +checksum = "407aca6f1671a08b60db8167f03bb7cb6b2378f0ddd9a030367b66ba33c2fd41" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -364,9 +364,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae38f909b2822d32b275c9e2db9728497aa33ffe67dd463bc67c6a3b7092785c" +checksum = "e6d1e00b8fd27ff923c10303023626358e23a6f9079f8ebec23a8b4b0bfcd4b3" dependencies = [ "proc-macro2", "quote", @@ -375,9 +375,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49b85345e811c8e80ec55d0d091e4fcb4f00f97ab058f9be5f614c444a730cb" +checksum = "92d5fdfd112b070055f068fad079d490117c8e905a588b92a5a7c9276d029930" dependencies = [ "base64", "cosmwasm-crypto", @@ -463,9 +463,9 @@ dependencies = [ [[package]] name = "cw-dex" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c4c002da51161e832615de09aa796e6915507418a7e4658204cd6c91ce89e7" +checksum = "25ab6ff93c73a386948fa0f834d029a7b29b350b7b622043506cefd2c81e5716" dependencies = [ "apollo-cw-asset", "apollo-utils", @@ -883,9 +883,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -997,9 +997,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "js-sys", @@ -1214,9 +1214,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1298,9 +1298,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "libloading" @@ -1494,6 +1494,7 @@ dependencies = [ "mars-params", "mars-red-bank-types", "mars-rover-health-types", + "mars-v2-zapper-base", "schemars", "serde", "thiserror", @@ -1625,6 +1626,7 @@ dependencies = [ "cw-storage-plus 1.0.1", "cw-utils 1.0.1", "mars-rover", + "mars-v2-zapper-base", "thiserror", ] @@ -1746,9 +1748,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.2" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -1825,7 +1827,7 @@ dependencies = [ [[package]] name = "osmosis-test-tube" version = "15.1.0" -source = "git+https://github.com/osmosis-labs/test-tube?branch=main#e0b157e5de32d8ac80aefd15518cf0252e26ba7f" +source = "git+https://github.com/osmosis-labs/test-tube?branch=main#8acf993ea5bf58dc043ee71fa057646b4e61ab0b" dependencies = [ "base64", "bindgen", @@ -1889,9 +1891,9 @@ checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" @@ -2025,9 +2027,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.3" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", @@ -2562,7 +2564,7 @@ dependencies = [ [[package]] name = "test-tube" version = "0.1.2" -source = "git+https://github.com/osmosis-labs/test-tube?branch=main#e0b157e5de32d8ac80aefd15518cf0252e26ba7f" +source = "git+https://github.com/osmosis-labs/test-tube?branch=main#8acf993ea5bf58dc043ee71fa057646b4e61ab0b" dependencies = [ "base64", "cosmrs", @@ -2772,9 +2774,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", diff --git a/Cargo.toml b/Cargo.toml index 6004495ff..044abe798 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,12 +37,12 @@ keywords = ["mars", "cosmos", "cosmwasm"] [workspace.dependencies] anyhow = "1.0.71" -cosmwasm-schema = "1.2.5" -cosmwasm-std = "1.2.5" +cosmwasm-schema = "1.2.6" +cosmwasm-std = "1.2.6" cw2 = "1.0.1" cw721 = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main" } cw721-base = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main", features = ["library"] } -cw-dex = { version = "0.1.3", features = ["osmosis"] } +cw-dex = { version = "0.2.0", features = ["osmosis"] } cw-multi-test = "0.16.4" cw-paginate = "0.2.1" cw-utils = "1.0.1" @@ -60,7 +60,7 @@ wasm-bindgen = "0.2.86" # mars packages mars-osmosis = { git = "https://github.com/mars-protocol/red-bank", rev = "00301d60c38af09d8eb7980355009e2f00c6f41f" } -mars-owner = { version = "1.1.0", features = ["emergency-owner"] } +mars-owner = { version = "1.2.0", features = ["emergency-owner"] } mars-red-bank-types = "1.0.0" mars-rover-health-computer = { version = "2.0.0", path = "./packages/health-computer" } mars-rover-health-types = { version = "2.0.0", path = "./packages/health-types" } diff --git a/Makefile.toml b/Makefile.toml index 0d083641f..44c20ef17 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -8,7 +8,7 @@ default_to_workspace = false [env] # Directory with wasm files used by integration tests (another directory can be used instead, for example 'artifacts' from rust-optimizer) -ARTIFACTS_DIR_PATH = "target/wasm32-unknown-unknown/release" +ARTIFACTS_DIR_PATH = "artifacts" [tasks.build] command = "cargo" diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 8684e867d..139d439c4 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -182,9 +182,11 @@ pub fn dispatch_actions( }), Action::WithdrawLiquidity { lp_token, + minimum_receive, } => callbacks.push(CallbackMsg::WithdrawLiquidity { account_id: account_id.to_string(), lp_token: lp_token.clone(), + minimum_receive: minimum_receive.clone(), }), Action::RefundAllCoinBalances {} => { callbacks.push(CallbackMsg::RefundAllCoinBalances { @@ -347,7 +349,8 @@ pub fn execute_callback( CallbackMsg::WithdrawLiquidity { account_id, lp_token, - } => withdraw_liquidity(deps, env, &account_id, &lp_token), + minimum_receive, + } => withdraw_liquidity(deps, env, &account_id, &lp_token, minimum_receive), CallbackMsg::RefundAllCoinBalances { account_id, } => refund_coin_balances(deps, env, &account_id), diff --git a/contracts/credit-manager/src/zap.rs b/contracts/credit-manager/src/zap.rs index 30e5f74c2..f3767c9f3 100644 --- a/contracts/credit-manager/src/zap.rs +++ b/contracts/credit-manager/src/zap.rs @@ -60,6 +60,7 @@ pub fn withdraw_liquidity( env: Env, account_id: &str, lp_token_action: &ActionCoin, + minimum_receive: Vec, ) -> ContractResult { assert_coin_is_whitelisted(&mut deps, &lp_token_action.denom)?; @@ -84,7 +85,7 @@ pub fn withdraw_liquidity( decrement_coin_balance(deps.storage, account_id, &lp_token)?; // After unzap is complete, update account's coin balances - let zap_msg = zapper.withdraw_liquidity_msg(&lp_token)?; + let zap_msg = zapper.withdraw_liquidity_msg(&lp_token, minimum_receive)?; let update_balances_msgs = update_balances_msgs( &deps.querier, &env.contract.address, diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index f3e3af319..75322f1a8 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -55,17 +55,15 @@ use mars_rover::{ CoinBalanceResponseItem, ConfigResponse, DebtShares, LentShares, Positions, SharesResponseItem, VaultPositionResponseItem, VaultUtilizationResponse, }, - zapper::{ - InstantiateMsg as ZapperInstantiateMsg, LpConfig, QueryMsg::EstimateProvideLiquidity, - }, ExecuteMsg, InstantiateMsg, QueryMsg, - QueryMsg::VaultPositionValue, + QueryMsg::{EstimateProvideLiquidity, VaultPositionValue}, }, }; use mars_rover_health_types::{ ExecuteMsg::UpdateConfig, HealthResponse, InstantiateMsg as HealthInstantiateMsg, QueryMsg::Health, }; +use mars_v2_zapper_mock::msg::{InstantiateMsg as ZapperInstantiateMsg, LpConfig}; use crate::helpers::{ lp_token_info, mock_account_nft_contract, mock_health_contract, mock_oracle_contract, diff --git a/contracts/credit-manager/tests/test_zap_provide.rs b/contracts/credit-manager/tests/test_zap_provide.rs index 5b4484788..a0de1ca17 100644 --- a/contracts/credit-manager/tests/test_zap_provide.rs +++ b/contracts/credit-manager/tests/test_zap_provide.rs @@ -352,6 +352,7 @@ fn can_provide_unbalanced() { denom: lp_token.denom.clone(), amount: ActionAmount::Exact(STARTING_LP_POOL_TOKENS.multiply_ratio(1u128, 2u128)), }, + minimum_receive: vec![], }], &[], ) diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index f882a8513..57df796ce 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, OverflowError, OverflowOperation::Sub, Uint128}; +use cosmwasm_std::{coin, Addr, Coin, OverflowError, OverflowOperation::Sub, Uint128}; use mars_params::types::AssetParamsUpdate::AddOrUpdate; use mars_rover::{ error::ContractError as RoverError, @@ -7,7 +7,10 @@ use mars_rover::{ ActionAmount, ActionCoin, }, }; -use mars_v2_zapper_mock::contract::STARTING_LP_POOL_TOKENS; +use mars_v2_zapper_mock::{ + contract::STARTING_LP_POOL_TOKENS, + error::{ContractError, ContractError::RequirementsNotMet}, +}; use crate::helpers::{ assert_err, blacklisted_coin, get_coin, lp_token_info, uatom_info, uosmo_info, AccountToFund, @@ -31,6 +34,7 @@ fn only_token_owner_can_unzap_for_account() { denom: "xyz".to_string(), amount: ActionAmount::AccountBalance, }, + minimum_receive: vec![], }], &[], ); @@ -56,6 +60,7 @@ fn lp_token_in_must_be_whitelisted() { &user, vec![WithdrawLiquidity { lp_token: blacklisted.to_action_coin(100), + minimum_receive: vec![], }], &[], ); @@ -108,6 +113,7 @@ fn coins_out_must_be_whitelisted() { &user, vec![WithdrawLiquidity { lp_token: lp_token.to_action_coin(100_000), + minimum_receive: vec![], }], &[], ); @@ -147,6 +153,7 @@ fn does_not_have_the_tokens_to_withdraw_liq() { }, WithdrawLiquidity { lp_token: lp_token.to_action_coin(attempted_unzap_amount), + minimum_receive: vec![], }, ], &[atom.to_coin(100), osmo.to_coin(50)], @@ -200,6 +207,7 @@ fn amount_zero_passed() { &user, vec![WithdrawLiquidity { lp_token: lp_token.to_action_coin(0), + minimum_receive: vec![], }], &[], ); @@ -230,6 +238,7 @@ fn amount_none_passed_with_no_balance() { &user, vec![WithdrawLiquidity { lp_token: lp_token.to_action_coin_full_balance(), + minimum_receive: vec![], }], &[], ); @@ -237,6 +246,72 @@ fn amount_none_passed_with_no_balance() { assert_err(res, RoverError::NoAmount) } +#[test] +fn min_received_not_met() { + let atom = uatom_info(); + let osmo = uosmo_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .set_params(&[lp_token.clone(), atom.clone(), osmo.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![atom.to_coin(300), osmo.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + + let mut simulate = |minimum_receive: Vec| -> ContractError { + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom.to_coin(100)), + Deposit(osmo.to_coin(50)), + ProvideLiquidity { + coins_in: vec![atom.to_action_coin(100), osmo.to_action_coin(50)], + lp_token_out: lp_token.denom.clone(), + minimum_receive: Uint128::zero(), + }, + WithdrawLiquidity { + lp_token: lp_token.to_action_coin(STARTING_LP_POOL_TOKENS.u128()), + minimum_receive, + }, + ], + &[atom.to_coin(100), osmo.to_coin(50)], + ) + .unwrap_err() + .downcast() + .unwrap() + }; + + assert_eq!( + simulate(vec![atom.to_coin(200), osmo.to_coin(3)]), + RequirementsNotMet("Expected min: 200uatom. Actual: 100uatom.".to_string()) + ); + + assert_eq!( + simulate(vec![atom.to_coin(90), osmo.to_coin(51)]), + RequirementsNotMet("Expected min: 51uosmo. Actual: 50uosmo.".to_string()) + ); + + assert_eq!( + simulate(vec![atom.to_coin(101), osmo.to_coin(51)]), + RequirementsNotMet( + "Expected min: 101uatom. Actual: 100uatom.; Expected min: 51uosmo. Actual: 50uosmo." + .to_string() + ) + ); + + assert_eq!( + simulate(vec![atom.to_coin(90), coin(12, "xyz")]), + RequirementsNotMet("Expected min denom xyz not found".to_string()) + ); +} + #[test] fn successful_unzap_specified_amount() { let atom = uatom_info(); @@ -268,6 +343,7 @@ fn successful_unzap_specified_amount() { }, WithdrawLiquidity { lp_token: lp_token.to_action_coin(STARTING_LP_POOL_TOKENS.u128()), + minimum_receive: vec![atom.to_coin(90), osmo.to_coin(32)], }, ], &[atom.to_coin(100), osmo.to_coin(50)], @@ -331,6 +407,7 @@ fn successful_unzap_unspecified_amount() { }, WithdrawLiquidity { lp_token: lp_token.to_action_coin_full_balance(), + minimum_receive: vec![], }, ], &[atom.to_coin(100), osmo.to_coin(50)], diff --git a/contracts/v2-zapper/base/src/contract.rs b/contracts/v2-zapper/base/src/contract.rs index ef610668d..338c652cb 100644 --- a/contracts/v2-zapper/base/src/contract.rs +++ b/contracts/v2-zapper/base/src/contract.rs @@ -61,7 +61,8 @@ where ), ExecuteMsg::WithdrawLiquidity { recipient, - } => Self::execute_withdraw_liquidity(deps, env, info, recipient), + minimum_receive, + } => Self::execute_withdraw_liquidity(deps, env, info, recipient, minimum_receive), ExecuteMsg::Callback(msg) => { // Can only be called by the contract itself if info.sender != env.contract.address { @@ -138,6 +139,7 @@ where env: Env, info: MessageInfo, recipient: Option, + minimum_receive: Vec, ) -> Result { // Make sure only one coin is sent one_coin(&info)?; @@ -153,7 +155,12 @@ where pool.simulate_withdraw_liquidity(deps.as_ref(), &lp_token.clone().into())?; let coins_returned_str = coins_returned.to_string(); - let response = pool.withdraw_liquidity(deps.as_ref(), &env, lp_token.clone().into())?; + let response = pool.withdraw_liquidity( + deps.as_ref(), + &env, + lp_token.clone().into(), + minimum_receive.into(), + )?; // Query current contract coin balances let mut coin_balances: Vec = Vec::with_capacity(coins_returned.len() + 1); // coins returned + lp token diff --git a/contracts/v2-zapper/base/src/msg.rs b/contracts/v2-zapper/base/src/msg.rs index 9642f3cdd..93f0fbb65 100644 --- a/contracts/v2-zapper/base/src/msg.rs +++ b/contracts/v2-zapper/base/src/msg.rs @@ -13,6 +13,7 @@ pub enum ExecuteMsg { }, WithdrawLiquidity { recipient: Option, + minimum_receive: Vec, }, Callback(CallbackMsg), } diff --git a/contracts/v2-zapper/mock/Cargo.toml b/contracts/v2-zapper/mock/Cargo.toml index 81a7abbef..c17c57cf3 100644 --- a/contracts/v2-zapper/mock/Cargo.toml +++ b/contracts/v2-zapper/mock/Cargo.toml @@ -19,9 +19,10 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -mars-rover = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +mars-rover = { workspace = true } +mars-v2-zapper-base = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/v2-zapper/mock/examples/schema.rs b/contracts/v2-zapper/mock/examples/schema.rs index 51f782654..1d5ad9034 100644 --- a/contracts/v2-zapper/mock/examples/schema.rs +++ b/contracts/v2-zapper/mock/examples/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use mars_rover::msg::zapper::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_v2_zapper_base::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/v2-zapper/mock/src/contract.rs b/contracts/v2-zapper/mock/src/contract.rs index b5dc09641..860ccdf9f 100644 --- a/contracts/v2-zapper/mock/src/contract.rs +++ b/contracts/v2-zapper/mock/src/contract.rs @@ -1,11 +1,12 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; -use mars_rover::msg::zapper::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_v2_zapper_base::{ExecuteMsg, QueryMsg}; use crate::{ error::ContractResult, execute::{provide_liquidity, withdraw_liquidity}, + msg::InstantiateMsg, query::{estimate_provide_liquidity, estimate_withdraw_liquidity}, state::{COIN_BALANCES, COIN_CONFIG, ORACLE}, }; @@ -59,8 +60,10 @@ pub fn execute( .. } => provide_liquidity(deps, info, lp_token_out, minimum_receive), ExecuteMsg::WithdrawLiquidity { + minimum_receive, .. - } => withdraw_liquidity(deps, info), + } => withdraw_liquidity(deps, info, minimum_receive), + ExecuteMsg::Callback(_) => unimplemented!("msg not supported"), } } diff --git a/contracts/v2-zapper/mock/src/execute.rs b/contracts/v2-zapper/mock/src/execute.rs index 74fa313d1..30a71fa09 100644 --- a/contracts/v2-zapper/mock/src/execute.rs +++ b/contracts/v2-zapper/mock/src/execute.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ use cw_utils::one_coin; use crate::{ - error::{ContractError, ContractResult}, + error::{ContractError, ContractError::RequirementsNotMet, ContractResult}, query::{estimate_provide_liquidity, estimate_withdraw_liquidity}, state::{COIN_BALANCES, COIN_CONFIG, LP_TOKEN_SUPPLY}, }; @@ -56,9 +56,14 @@ pub fn provide_liquidity( Ok(Response::new().add_message(transfer_msg)) } -pub fn withdraw_liquidity(deps: DepsMut, info: MessageInfo) -> ContractResult { +pub fn withdraw_liquidity( + deps: DepsMut, + info: MessageInfo, + minimum_receive: Vec, +) -> ContractResult { let lp_token_sent = one_coin(&info)?; let underlying_coins = estimate_withdraw_liquidity(deps.storage, &lp_token_sent)?; + assert_min_receive(minimum_receive, underlying_coins.clone())?; for coin in &underlying_coins { COIN_BALANCES.update( @@ -80,6 +85,28 @@ pub fn withdraw_liquidity(deps: DepsMut, info: MessageInfo) -> ContractResult, actuals: Vec) -> ContractResult<()> { + let mut errors: Vec = vec![]; + + for expected_min in expected_mins { + match actuals.iter().find(|c| c.denom == expected_min.denom) { + Some(actual) if actual.amount < expected_min.amount => { + errors.push(format!("Expected min: {}. Actual: {}.", expected_min, actual)); + } + None => { + errors.push(format!("Expected min denom {} not found", expected_min.denom)); + } + _ => {} + } + } + + if errors.is_empty() { + Ok(()) + } else { + Err(RequirementsNotMet(errors.join("; "))) + } +} + fn mock_lp_token_mint( storage: &mut dyn Storage, amount: Uint128, diff --git a/contracts/v2-zapper/mock/src/lib.rs b/contracts/v2-zapper/mock/src/lib.rs index 512e6c212..b1acd54e1 100644 --- a/contracts/v2-zapper/mock/src/lib.rs +++ b/contracts/v2-zapper/mock/src/lib.rs @@ -1,5 +1,6 @@ pub mod contract; pub mod error; pub mod execute; +pub mod msg; pub mod query; pub mod state; diff --git a/contracts/v2-zapper/mock/src/msg.rs b/contracts/v2-zapper/mock/src/msg.rs new file mode 100644 index 000000000..173fb4a83 --- /dev/null +++ b/contracts/v2-zapper/mock/src/msg.rs @@ -0,0 +1,14 @@ +use cosmwasm_schema::cw_serde; +use mars_rover::adapters::oracle::OracleUnchecked; + +#[cw_serde] +pub struct LpConfig { + pub lp_token_denom: String, + pub lp_pair_denoms: (String, String), +} + +#[cw_serde] +pub struct InstantiateMsg { + pub oracle: OracleUnchecked, + pub lp_configs: Vec, +} diff --git a/contracts/v2-zapper/osmosis/tests/test_withdraw_liquidity.rs b/contracts/v2-zapper/osmosis/tests/test_withdraw_liquidity.rs index e936dbcdf..3ae4c996e 100644 --- a/contracts/v2-zapper/osmosis/tests/test_withdraw_liquidity.rs +++ b/contracts/v2-zapper/osmosis/tests/test_withdraw_liquidity.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{coin, Coin, Uint128}; use cw_dex::CwDexError; use cw_utils::PaymentError; use mars_v2_zapper_base::{ContractError, ExecuteMsg, QueryMsg}; -use osmosis_test_tube::{Account, Bank, FeeSetting, Gamm, Module, OsmosisTestApp, Wasm}; +use osmosis_test_tube::{Account, Bank, Gamm, Module, OsmosisTestApp, Wasm}; use crate::helpers::{assert_err, instantiate_contract, query_balance}; @@ -14,7 +14,11 @@ fn withdraw_liquidity_without_funds() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[coin(1_000_000_000_000, "gamm/pool/1"), coin(1_000_000_000_000, "uosmo")]) + .init_account(&[ + coin(1_000_000_000_000, "gamm/pool/1"), + coin(1_000_000_000_000, "ustars"), + coin(1_000_000_000_000, "uosmo"), // for gas + ]) .unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); @@ -24,6 +28,7 @@ fn withdraw_liquidity_without_funds() { &contract_addr, &ExecuteMsg::WithdrawLiquidity { recipient: None, + minimum_receive: vec![], }, &[], &signer, @@ -38,7 +43,11 @@ fn withdraw_liquidity_with_more_than_one_coin_sent() { let wasm = Wasm::new(&app); let signer = app - .init_account(&[coin(1_000_000_000_000, "gamm/pool/1"), coin(1_000_000_000_000, "uosmo")]) + .init_account(&[ + coin(1_000_000_000_000, "gamm/pool/1"), + coin(1_000_000_000_000, "ustars"), + coin(1_000_000_000_000, "uosmo"), // for gas + ]) .unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); @@ -48,8 +57,9 @@ fn withdraw_liquidity_with_more_than_one_coin_sent() { &contract_addr, &ExecuteMsg::WithdrawLiquidity { recipient: None, + minimum_receive: vec![], }, - &[coin(1_000_000, "gamm/pool/1"), coin(2_000_000, "uosmo")], + &[coin(1_000_000, "gamm/pool/1"), coin(2_000_000, "ustars")], &signer, ) .unwrap_err(); @@ -61,7 +71,12 @@ fn withdraw_liquidity_with_invalid_lp_token() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let signer = app.init_account(&[coin(1_000_000_000_000, "uosmo")]).unwrap(); + let signer = app + .init_account(&[ + coin(1_000_000_000_000, "ustars"), + coin(1_000_000_000_000, "uosmo"), // for gas + ]) + .unwrap(); let contract_addr = instantiate_contract(&wasm, &signer); @@ -70,46 +85,108 @@ fn withdraw_liquidity_with_invalid_lp_token() { &contract_addr, &ExecuteMsg::WithdrawLiquidity { recipient: None, + minimum_receive: vec![], }, - &[coin(1_000_000, "uosmo")], + &[coin(1_000_000, "ustars")], &signer, ) .unwrap_err(); assert_err(res_err, CwDexError::NotLpToken {}); } +#[test] +fn withdraw_liquidity_does_not_meet_min_out() { + let app = OsmosisTestApp::new(); + let wasm = Wasm::new(&app); + + let accs = app + .init_accounts( + &[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "ustars"), + coin(1_000_000_000_000, "uosmo"), // for gas + ], + 2, + ) + .unwrap(); + let owner = &accs[0]; + let user = &accs[1]; + + let gamm = Gamm::new(&app); + let pool_id = gamm + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "ustars")], owner) + .unwrap() + .data + .pool_id; + let pool_denom = format!("gamm/pool/{pool_id}"); + let contract_addr = instantiate_contract(&wasm, owner); + let bank = Bank::new(&app); + + wasm.execute( + &contract_addr, + &ExecuteMsg::ProvideLiquidity { + lp_token_out: pool_denom.clone(), + recipient: None, + minimum_receive: Uint128::one(), + }, + &[coin(5_000_000, "uatom"), coin(10_000_000, "ustars")], + user, + ) + .unwrap(); + + let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); + + let res_err = wasm + .execute( + &contract_addr, + &ExecuteMsg::WithdrawLiquidity { + recipient: None, + minimum_receive: vec![coin(50_000_000, "uatom"), coin(10_000_000, "ustars")], + }, + &[coin(user_pool_balance, &pool_denom)], + user, + ) + .unwrap_err(); + + assert_err( + res_err, + "Exit pool returned 5000000uatom,10000000ustars , minimum tokens out specified as 50000000uatom,10000000ustars", + ); +} + #[test] fn withdraw_liquidity_successfully() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let uatom_acc_balance = 1_000_000_000_000u128; - let uosmo_acc_balance = 1_000_000_000_000u128; - let starting_coins = &[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")]; - let owner = app.init_account(starting_coins).unwrap(); - let tx_fee = 1000000u128; - let user = app.init_account(starting_coins).unwrap().with_fee_setting(FeeSetting::Custom { - amount: Coin::new(tx_fee, "uosmo"), - gas_limit: tx_fee as u64, - }); + let accs = app + .init_accounts( + &[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "ustars"), + coin(1_000_000_000_000, "uosmo"), // for gas + ], + 2, + ) + .unwrap(); + let owner = &accs[0]; + let user = &accs[1]; let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], &owner) + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "ustars")], owner) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{pool_id}"); - let contract_addr = instantiate_contract(&wasm, &owner); + let contract_addr = instantiate_contract(&wasm, owner); let bank = Bank::new(&app); let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); - assert_eq!(user_pool_balance, 0u128); + assert_eq!(user_pool_balance, 0); - let uatom_liquidity_amount = 5_000_000u128; - let uosmo_liquidity_amount = 10_000_000u128; wasm.execute( &contract_addr, &ExecuteMsg::ProvideLiquidity { @@ -117,15 +194,15 @@ fn withdraw_liquidity_successfully() { recipient: None, minimum_receive: Uint128::one(), }, - &[coin(uatom_liquidity_amount, "uatom"), coin(uosmo_liquidity_amount, "uosmo")], - &user, + &[coin(5_000_000, "uatom"), coin(10_000_000, "ustars")], + user, ) .unwrap(); let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); - assert_eq!(user_pool_balance, 25000000000000000000u128); + assert_eq!(user_pool_balance, 25000000000000000000); let user_uatom_balance_before = query_balance(&bank, &user.address(), "uatom"); - let user_uosmo_balance_before = query_balance(&bank, &user.address(), "uosmo"); + let user_ustars_balance_before = query_balance(&bank, &user.address(), "ustars"); let estimate_coins: Vec = wasm .query( @@ -137,34 +214,35 @@ fn withdraw_liquidity_successfully() { .unwrap(); let uatom_estimate_amount = estimate_coins.iter().find(|c| c.denom == "uatom").unwrap().amount.u128(); - let uosmo_estimate_amount = - estimate_coins.iter().find(|c| c.denom == "uosmo").unwrap().amount.u128(); - assert_eq!(uatom_estimate_amount, 5_000_000u128); - assert_eq!(uosmo_estimate_amount, 10_000_000u128); + let ustars_estimate_amount = + estimate_coins.iter().find(|c| c.denom == "ustars").unwrap().amount.u128(); + assert_eq!(uatom_estimate_amount, 5_000_000); + assert_eq!(ustars_estimate_amount, 10_000_000); wasm.execute( &contract_addr, &ExecuteMsg::WithdrawLiquidity { recipient: None, + minimum_receive: vec![], }, &[coin(user_pool_balance, &pool_denom)], - &user, + user, ) .unwrap(); let contract_pool_balance = query_balance(&bank, &contract_addr, &pool_denom); - assert_eq!(contract_pool_balance, 0u128); + assert_eq!(contract_pool_balance, 0); let contract_uatom_balance = query_balance(&bank, &contract_addr, "uatom"); - assert_eq!(contract_uatom_balance, 0u128); - let contract_uosmo_balance = query_balance(&bank, &contract_addr, "uosmo"); - assert_eq!(contract_uosmo_balance, 0u128); + assert_eq!(contract_uatom_balance, 0); + let contract_ustars_balance = query_balance(&bank, &contract_addr, "ustars"); + assert_eq!(contract_ustars_balance, 0); let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); - assert_eq!(user_pool_balance, 0u128); + assert_eq!(user_pool_balance, 0); let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); assert_eq!(user_uatom_balance, user_uatom_balance_before + uatom_estimate_amount); - let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); - assert_eq!(user_uosmo_balance, user_uosmo_balance_before + uosmo_estimate_amount - tx_fee); + let user_ustars_balance = query_balance(&bank, &user.address(), "ustars"); + assert_eq!(user_ustars_balance, user_ustars_balance_before + ustars_estimate_amount); } #[test] @@ -172,34 +250,35 @@ fn withdraw_liquidity_with_different_recipient_successfully() { let app = OsmosisTestApp::new(); let wasm = Wasm::new(&app); - let uatom_acc_balance = 1_000_000_000_000u128; - let uosmo_acc_balance = 1_000_000_000_000u128; - let starting_coins = &[coin(uatom_acc_balance, "uatom"), coin(uosmo_acc_balance, "uosmo")]; - let owner = app.init_account(starting_coins).unwrap(); - let tx_fee = 1000000u128; - let user = app.init_account(starting_coins).unwrap().with_fee_setting(FeeSetting::Custom { - amount: Coin::new(tx_fee, "uosmo"), - gas_limit: tx_fee as u64, - }); - let recipient = app.init_account(starting_coins).unwrap(); + let accs = app + .init_accounts( + &[ + coin(1_000_000_000_000, "uatom"), + coin(1_000_000_000_000, "ustars"), + coin(1_000_000_000_000, "uosmo"), // for gas + ], + 3, + ) + .unwrap(); + let owner = &accs[0]; + let user = &accs[1]; + let recipient = &accs[2]; let gamm = Gamm::new(&app); let pool_id = gamm - .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "uosmo")], &owner) + .create_basic_pool(&[coin(20_000_000, "uatom"), coin(40_000_000, "ustars")], owner) .unwrap() .data .pool_id; let pool_denom = format!("gamm/pool/{pool_id}"); - let contract_addr = instantiate_contract(&wasm, &owner); + let contract_addr = instantiate_contract(&wasm, owner); let bank = Bank::new(&app); let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); - assert_eq!(user_pool_balance, 0u128); + assert_eq!(user_pool_balance, 0); - let uatom_liquidity_amount = 5_000_000u128; - let uosmo_liquidity_amount = 10_000_000u128; wasm.execute( &contract_addr, &ExecuteMsg::ProvideLiquidity { @@ -207,20 +286,20 @@ fn withdraw_liquidity_with_different_recipient_successfully() { recipient: None, minimum_receive: Uint128::one(), }, - &[coin(uatom_liquidity_amount, "uatom"), coin(uosmo_liquidity_amount, "uosmo")], - &user, + &[coin(5_000_000, "uatom"), coin(10_000_000, "ustars")], + user, ) .unwrap(); let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); - assert_eq!(user_pool_balance, 25000000000000000000u128); + assert_eq!(user_pool_balance, 25000000000000000000); let user_uatom_balance_before = query_balance(&bank, &user.address(), "uatom"); - let user_uosmo_balance_before = query_balance(&bank, &user.address(), "uosmo"); + let user_ustars_balance_before = query_balance(&bank, &user.address(), "ustars"); let recipient_pool_balance = query_balance(&bank, &recipient.address(), &pool_denom); - assert_eq!(recipient_pool_balance, 0u128); + assert_eq!(recipient_pool_balance, 0); let recipient_uatom_balance_before = query_balance(&bank, &recipient.address(), "uatom"); - let recipient_uosmo_balance_before = query_balance(&bank, &recipient.address(), "uosmo"); + let recipient_ustars_balance_before = query_balance(&bank, &recipient.address(), "ustars"); let estimate_coins: Vec = wasm .query( @@ -232,39 +311,40 @@ fn withdraw_liquidity_with_different_recipient_successfully() { .unwrap(); let uatom_estimate_amount = estimate_coins.iter().find(|c| c.denom == "uatom").unwrap().amount.u128(); - let uosmo_estimate_amount = - estimate_coins.iter().find(|c| c.denom == "uosmo").unwrap().amount.u128(); - assert_eq!(uatom_estimate_amount, 5_000_000u128); - assert_eq!(uosmo_estimate_amount, 10_000_000u128); + let ustars_estimate_amount = + estimate_coins.iter().find(|c| c.denom == "ustars").unwrap().amount.u128(); + assert_eq!(uatom_estimate_amount, 5_000_000); + assert_eq!(ustars_estimate_amount, 10_000_000); wasm.execute( &contract_addr, &ExecuteMsg::WithdrawLiquidity { recipient: Some(recipient.address()), + minimum_receive: vec![], }, &[coin(user_pool_balance, &pool_denom)], - &user, + user, ) .unwrap(); let contract_pool_balance = query_balance(&bank, &contract_addr, &pool_denom); - assert_eq!(contract_pool_balance, 0u128); + assert_eq!(contract_pool_balance, 0); let contract_uatom_balance = query_balance(&bank, &contract_addr, "uatom"); - assert_eq!(contract_uatom_balance, 0u128); - let contract_uosmo_balance = query_balance(&bank, &contract_addr, "uosmo"); - assert_eq!(contract_uosmo_balance, 0u128); + assert_eq!(contract_uatom_balance, 0); + let contract_ustars_balance = query_balance(&bank, &contract_addr, "ustars"); + assert_eq!(contract_ustars_balance, 0); let user_pool_balance = query_balance(&bank, &user.address(), &pool_denom); - assert_eq!(user_pool_balance, 0u128); + assert_eq!(user_pool_balance, 0); let user_uatom_balance = query_balance(&bank, &user.address(), "uatom"); assert_eq!(user_uatom_balance, user_uatom_balance_before); - let user_uosmo_balance = query_balance(&bank, &user.address(), "uosmo"); - assert_eq!(user_uosmo_balance, user_uosmo_balance_before - tx_fee); + let user_ustars_balance = query_balance(&bank, &user.address(), "ustars"); + assert_eq!(user_ustars_balance, user_ustars_balance_before); let recipient_pool_balance = query_balance(&bank, &recipient.address(), &pool_denom); - assert_eq!(recipient_pool_balance, 0u128); + assert_eq!(recipient_pool_balance, 0); let recipient_uatom_balance = query_balance(&bank, &recipient.address(), "uatom"); assert_eq!(recipient_uatom_balance, recipient_uatom_balance_before + uatom_estimate_amount); - let recipient_uosmo_balance = query_balance(&bank, &recipient.address(), "uosmo"); - assert_eq!(recipient_uosmo_balance, recipient_uosmo_balance_before + uosmo_estimate_amount); + let recipient_ustars_balance = query_balance(&bank, &recipient.address(), "ustars"); + assert_eq!(recipient_ustars_balance, recipient_ustars_balance_before + ustars_estimate_amount); } diff --git a/contracts/v3-zapper/osmosis/tests/test_add_position.rs b/contracts/v3-zapper/osmosis/tests/test_add_position.rs index 00ffd30fb..80ab3f5a2 100644 --- a/contracts/v3-zapper/osmosis/tests/test_add_position.rs +++ b/contracts/v3-zapper/osmosis/tests/test_add_position.rs @@ -12,6 +12,8 @@ use crate::helpers::{assert_err, default_new_position_req, MockEnv}; pub mod helpers; +// TODO: Remove ignores when Test-Tube fixed: https://github.com/osmosis-labs/test-tube/commit/f9c1e6ba9b69432d9a19ee9d6454819e57903327 + #[test] fn only_owner_can_add_positions() { let mock = MockEnv::new().build().unwrap(); @@ -31,6 +33,7 @@ fn only_owner_can_add_positions() { } #[test] +#[ignore = "pending concentrated liquidity type update"] fn must_send_exact_funds() { let mut mock = MockEnv::new().build().unwrap(); let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); @@ -95,6 +98,7 @@ fn must_send_exact_funds() { } #[test] +#[ignore = "pending concentrated liquidity type update"] fn add_position_successfully() { let mut mock = MockEnv::new().build().unwrap(); let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); @@ -176,6 +180,7 @@ fn add_position_successfully() { } #[test] +#[ignore = "pending concentrated liquidity type update"] fn refunds_are_issued() { let mut mock = MockEnv::new().build().unwrap(); let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); @@ -269,6 +274,7 @@ fn refunds_are_issued() { } #[test] +#[ignore = "pending concentrated liquidity type update"] fn adding_multiple_increments() { let mut mock = MockEnv::new().build().unwrap(); let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); @@ -326,6 +332,7 @@ fn adding_multiple_increments() { } #[test] +#[ignore = "pending concentrated liquidity type update"] fn error_rolls_back_state() { let mut mock = MockEnv::new().build().unwrap(); let (denom0, denom1, _) = mock.create_pool("ujuno", "umars"); diff --git a/packages/rover/Cargo.toml b/packages/rover/Cargo.toml index df7e1edab..49cba23cd 100644 --- a/packages/rover/Cargo.toml +++ b/packages/rover/Cargo.toml @@ -28,6 +28,7 @@ cw-vault-standard = { workspace = true } mars-account-nft = { workspace = true } mars-rover-health-types = { workspace = true } mars-red-bank-types = { workspace = true } +mars-v2-zapper-base = { workspace = true } mars-owner = { workspace = true } mars-params = { workspace = true } schemars = { workspace = true } diff --git a/packages/rover/src/adapters/zapper.rs b/packages/rover/src/adapters/zapper.rs index 7a3623dd6..494a452cf 100644 --- a/packages/rover/src/adapters/zapper.rs +++ b/packages/rover/src/adapters/zapper.rs @@ -2,8 +2,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ to_binary, Addr, Api, Coin, CosmosMsg, QuerierWrapper, StdResult, Uint128, WasmMsg, }; - -use crate::msg::zapper::{ExecuteMsg, QueryMsg}; +use mars_v2_zapper_base::{ExecuteMsg, QueryMsg}; #[cw_serde] pub struct ZapperBase(T); @@ -79,11 +78,16 @@ impl Zapper { })) } - pub fn withdraw_liquidity_msg(&self, lp_token: &Coin) -> StdResult { + pub fn withdraw_liquidity_msg( + &self, + lp_token: &Coin, + minimum_receive: Vec, + ) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.address().to_string(), msg: to_binary(&ExecuteMsg::WithdrawLiquidity { recipient: None, + minimum_receive, })?, funds: vec![lp_token.clone()], })) diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 25265ca28..7bd35c997 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -170,6 +170,7 @@ pub enum Action { /// If `lp_token.amount: AccountBalance`, the account balance of `lp_token.denom` will be used. WithdrawLiquidity { lp_token: ActionCoin, + minimum_receive: Vec, }, /// Refunds all coin balances back to user wallet RefundAllCoinBalances {}, @@ -291,6 +292,7 @@ pub enum CallbackMsg { WithdrawLiquidity { account_id: String, lp_token: ActionCoin, + minimum_receive: Vec, }, /// Refunds all coin balances back to user wallet RefundAllCoinBalances { diff --git a/packages/rover/src/msg/mod.rs b/packages/rover/src/msg/mod.rs index 38a6d2030..50cb8a531 100644 --- a/packages/rover/src/msg/mod.rs +++ b/packages/rover/src/msg/mod.rs @@ -1,7 +1,6 @@ pub mod execute; pub mod instantiate; pub mod query; -pub mod zapper; pub use execute::ExecuteMsg; pub use instantiate::InstantiateMsg; diff --git a/packages/rover/src/msg/zapper.rs b/packages/rover/src/msg/zapper.rs deleted file mode 100644 index 72fb9ca03..000000000 --- a/packages/rover/src/msg/zapper.rs +++ /dev/null @@ -1,44 +0,0 @@ -// TODO: should be removed when liquidity-helper is finalized and published to crates.io - -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Coin, Uint128}; - -use crate::adapters::oracle::OracleUnchecked; - -#[cw_serde] -pub struct LpConfig { - pub lp_token_denom: String, - pub lp_pair_denoms: (String, String), -} - -#[cw_serde] -pub struct InstantiateMsg { - pub oracle: OracleUnchecked, - pub lp_configs: Vec, -} - -#[cw_serde] -pub enum ExecuteMsg { - ProvideLiquidity { - lp_token_out: String, - recipient: Option, - minimum_receive: Uint128, - }, - WithdrawLiquidity { - recipient: Option, - }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(Uint128)] - EstimateProvideLiquidity { - lp_token_out: String, - coins_in: Vec, - }, - #[returns(Vec)] - EstimateWithdrawLiquidity { - coin_in: Coin, - }, -} diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 51a3af2d7..5d29e254f 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -575,11 +575,18 @@ "withdraw_liquidity": { "type": "object", "required": [ - "lp_token" + "lp_token", + "minimum_receive" ], "properties": { "lp_token": { "$ref": "#/definitions/ActionCoin" + }, + "minimum_receive": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } } }, "additionalProperties": false @@ -1150,7 +1157,8 @@ "type": "object", "required": [ "account_id", - "lp_token" + "lp_token", + "minimum_receive" ], "properties": { "account_id": { @@ -1158,6 +1166,12 @@ }, "lp_token": { "$ref": "#/definitions/ActionCoin" + }, + "minimum_receive": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } } }, "additionalProperties": false diff --git a/schemas/mars-v2-zapper-base/mars-v2-zapper-base.json b/schemas/mars-v2-zapper-base/mars-v2-zapper-base.json index 5c4c31a01..be0530a7e 100644 --- a/schemas/mars-v2-zapper-base/mars-v2-zapper-base.json +++ b/schemas/mars-v2-zapper-base/mars-v2-zapper-base.json @@ -51,7 +51,16 @@ "properties": { "withdraw_liquidity": { "type": "object", + "required": [ + "minimum_receive" + ], "properties": { + "minimum_receive": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, "recipient": { "type": [ "string", diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 18e40b93d..6d67cdde2 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -173,6 +173,7 @@ export class Rover { { withdraw_liquidity: { lp_token: { amount: { exact: lpToken.amount }, denom: lpToken.denom }, + minimum_receive: [], }, }, ]) diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index f1201d91b..ad995ccf8 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -124,6 +124,7 @@ export type Action = | { withdraw_liquidity: { lp_token: ActionCoin + minimum_receive: Coin[] } } | { @@ -278,6 +279,7 @@ export type CallbackMsg = withdraw_liquidity: { account_id: string lp_token: ActionCoin + minimum_receive: Coin[] } } | { diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts index 4d42464fd..5c7c301c9 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts @@ -80,8 +80,10 @@ export interface MarsV2ZapperBaseInterface extends MarsV2ZapperBaseReadOnlyInter ) => Promise withdrawLiquidity: ( { + minimumReceive, recipient, }: { + minimumReceive: Coin[] recipient?: string }, fee?: number | StdFee | 'auto', @@ -144,8 +146,10 @@ export class MarsV2ZapperBaseClient } withdrawLiquidity = async ( { + minimumReceive, recipient, }: { + minimumReceive: Coin[] recipient?: string }, fee: number | StdFee | 'auto' = 'auto', @@ -157,6 +161,7 @@ export class MarsV2ZapperBaseClient this.contractAddress, { withdraw_liquidity: { + minimum_receive: minimumReceive, recipient, }, }, diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts index ee46a37f0..d24202539 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts @@ -35,8 +35,10 @@ export interface MarsV2ZapperBaseMessage { ) => MsgExecuteContractEncodeObject withdrawLiquidity: ( { + minimumReceive, recipient, }: { + minimumReceive: Coin[] recipient?: string }, _funds?: Coin[], @@ -87,8 +89,10 @@ export class MarsV2ZapperBaseMessageComposer implements MarsV2ZapperBaseMessage } withdrawLiquidity = ( { + minimumReceive, recipient, }: { + minimumReceive: Coin[] recipient?: string }, _funds?: Coin[], @@ -101,6 +105,7 @@ export class MarsV2ZapperBaseMessageComposer implements MarsV2ZapperBaseMessage msg: toUtf8( JSON.stringify({ withdraw_liquidity: { + minimum_receive: minimumReceive, recipient, }, }), diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts index 92d34c254..37f1409e5 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts @@ -125,6 +125,7 @@ export function useMarsV2ZapperBaseCallbackMutation( export interface MarsV2ZapperBaseWithdrawLiquidityMutation { client: MarsV2ZapperBaseClient msg: { + minimumReceive: Coin[] recipient?: string } args?: { diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts index 73bac53eb..c83f91adb 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts @@ -16,6 +16,7 @@ export type ExecuteMsg = } | { withdraw_liquidity: { + minimum_receive: Coin[] recipient?: string | null } } From c1f68342feba2c1b37745db2618fbc7c0d68eded Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 16 Jun 2023 18:44:40 +0200 Subject: [PATCH 163/218] HLS accounts (#142) * Adding new account type * update packages * review updates --- Cargo.lock | 55 +- Cargo.toml | 10 +- Makefile.toml | 9 +- contracts/account-nft/src/execute.rs | 3 +- contracts/account-nft/src/msg/query.rs | 2 +- contracts/account-nft/src/query.rs | 4 +- .../account-nft/tests/helpers/mock_env.rs | 10 +- .../account-nft/tests/test_instantiate.rs | 6 +- contracts/account-nft/tests/test_mint.rs | 9 + contracts/credit-manager/Cargo.toml | 2 +- contracts/credit-manager/src/contract.rs | 6 +- contracts/credit-manager/src/execute.rs | 31 +- contracts/credit-manager/src/health.rs | 5 +- contracts/credit-manager/src/hls.rs | 105 ++ contracts/credit-manager/src/lib.rs | 1 + contracts/credit-manager/src/query.rs | 2 +- contracts/credit-manager/src/state.rs | 8 +- contracts/credit-manager/src/update_config.rs | 14 +- contracts/credit-manager/src/utils.rs | 15 +- .../credit-manager/tests/helpers/builders.rs | 2 + .../tests/helpers/mock_entity_info.rs | 39 +- .../credit-manager/tests/helpers/mock_env.rs | 45 +- .../credit-manager/tests/helpers/types.rs | 18 +- contracts/credit-manager/tests/test_health.rs | 48 +- .../credit-manager/tests/test_hls_accounts.rs | 381 ++++++ .../tests/test_liquidate_deposit.rs | 13 +- .../tests/test_liquidate_lend.rs | 13 +- contracts/credit-manager/tests/test_repay.rs | 2 + .../tests/test_repay_from_wallet.rs | 4 +- .../tests/test_update_config.rs | 8 +- .../tests/test_utilization_query.rs | 3 + .../credit-manager/tests/test_vault_enter.rs | 1 + .../credit-manager/tests/test_zap_withdraw.rs | 2 +- contracts/health/src/compute.rs | 11 +- contracts/health/src/contract.rs | 3 +- contracts/health/src/querier.rs | 2 +- contracts/health/tests/helpers/defaults.rs | 29 + contracts/health/tests/helpers/mock_env.rs | 12 +- .../health/tests/helpers/mock_env_builder.rs | 16 +- contracts/health/tests/helpers/mod.rs | 3 +- contracts/health/tests/test_compute_health.rs | 57 +- contracts/health/tests/test_hls.rs | 77 ++ contracts/mock-health/src/contract.rs | 15 +- contracts/mock-health/src/msg.rs | 3 +- contracts/mock-health/src/state.rs | 2 +- .../osmosis/tests/helpers/mock_env.rs | 3 +- packages/health-computer/src/data_types.rs | 2 +- .../health-computer/src/health_computer.rs | 106 +- .../tests/helpers/mock_coin_info.rs | 48 +- .../tests/test_health_scenarios.rs | 33 +- packages/health-computer/tests/test_hls.rs | 308 +++++ .../tests/test_input_validation.rs | 46 +- packages/health-types/src/account.rs | 15 + packages/health-types/src/error.rs | 5 + packages/health-types/src/lib.rs | 2 + packages/health-types/src/msg.rs | 3 + packages/rover/src/adapters/account_nft.rs | 37 + packages/rover/src/adapters/health.rs | 4 +- packages/rover/src/adapters/mod.rs | 1 + packages/rover/src/adapters/params.rs | 2 +- packages/rover/src/error.rs | 5 + packages/rover/src/msg/execute.rs | 7 +- packages/rover/src/msg/instantiate.rs | 7 +- packages/rover/src/msg/query.rs | 4 + .../mars-account-nft/mars-account-nft.json | 6 +- .../mars-credit-manager.json | 75 +- .../mars-mock-credit-manager.json | 30 + schemas/mars-params/mars-params.json | 609 +++++++--- .../mars-rover-health-computer.json | 127 +- .../mars-rover-health-types.json | 17 +- scripts/health/DataFetcher.ts | 2 + scripts/package.json | 16 +- .../mars-account-nft/MarsAccountNft.client.ts | 7 +- .../MarsAccountNft.message-composer.ts | 3 +- .../MarsAccountNft.react-query.ts | 9 +- .../mars-account-nft/MarsAccountNft.types.ts | 3 +- .../generated/mars-account-nft/bundle.ts | 2 +- .../MarsCreditManager.client.ts | 13 +- .../MarsCreditManager.message-composer.ts | 4 +- .../MarsCreditManager.react-query.ts | 33 +- .../MarsCreditManager.types.ts | 18 +- .../generated/mars-credit-manager/bundle.ts | 2 +- .../MarsMockCreditManager.client.ts | 12 +- .../MarsMockCreditManager.message-composer.ts | 3 +- .../MarsMockCreditManager.react-query.ts | 33 +- .../MarsMockCreditManager.types.ts | 8 +- .../mars-mock-credit-manager/bundle.ts | 2 +- .../mars-mock-oracle/MarsMockOracle.client.ts | 2 +- .../MarsMockOracle.message-composer.ts | 2 +- .../MarsMockOracle.react-query.ts | 2 +- .../mars-mock-oracle/MarsMockOracle.types.ts | 2 +- .../generated/mars-mock-oracle/bundle.ts | 2 +- .../MarsMockRedBank.client.ts | 2 +- .../MarsMockRedBank.message-composer.ts | 2 +- .../MarsMockRedBank.react-query.ts | 2 +- .../MarsMockRedBank.types.ts | 2 +- .../generated/mars-mock-red-bank/bundle.ts | 2 +- .../mars-mock-vault/MarsMockVault.client.ts | 2 +- .../MarsMockVault.message-composer.ts | 2 +- .../MarsMockVault.react-query.ts | 2 +- .../mars-mock-vault/MarsMockVault.types.ts | 2 +- .../types/generated/mars-mock-vault/bundle.ts | 2 +- .../mars-params/MarsParams.client.ts | 25 +- .../MarsParams.message-composer.ts | 17 +- .../mars-params/MarsParams.react-query.ts | 29 +- .../generated/mars-params/MarsParams.types.ts | 76 +- scripts/types/generated/mars-params/bundle.ts | 2 +- .../MarsRoverHealthComputer.client.ts | 12 +- ...arsRoverHealthComputer.message-composer.ts | 12 +- .../MarsRoverHealthComputer.react-query.ts | 12 +- .../MarsRoverHealthComputer.types.ts | 41 +- .../mars-rover-health-computer/bundle.ts | 2 +- .../MarsRoverHealthTypes.client.ts | 14 +- .../MarsRoverHealthTypes.message-composer.ts | 3 +- .../MarsRoverHealthTypes.react-query.ts | 5 +- .../MarsRoverHealthTypes.types.ts | 4 +- .../mars-rover-health-types/bundle.ts | 2 +- .../MarsSwapperBase.client.ts | 2 +- .../MarsSwapperBase.message-composer.ts | 2 +- .../MarsSwapperBase.react-query.ts | 2 +- .../MarsSwapperBase.types.ts | 2 +- .../generated/mars-swapper-base/bundle.ts | 2 +- .../MarsV2ZapperBase.client.ts | 2 +- .../MarsV2ZapperBase.message-composer.ts | 2 +- .../MarsV2ZapperBase.react-query.ts | 2 +- .../MarsV2ZapperBase.types.ts | 2 +- .../generated/mars-v2-zapper-base/bundle.ts | 2 +- .../MarsV3ZapperBase.client.ts | 2 +- .../MarsV3ZapperBase.message-composer.ts | 2 +- .../MarsV3ZapperBase.react-query.ts | 2 +- .../MarsV3ZapperBase.types.ts | 2 +- .../generated/mars-v3-zapper-base/bundle.ts | 2 +- scripts/yarn.lock | 1078 +++++++++++++---- 133 files changed, 3345 insertions(+), 827 deletions(-) create mode 100644 contracts/credit-manager/src/hls.rs create mode 100644 contracts/credit-manager/tests/test_hls_accounts.rs create mode 100644 contracts/health/tests/helpers/defaults.rs create mode 100644 contracts/health/tests/test_hls.rs create mode 100644 packages/health-computer/tests/test_hls.rs create mode 100644 packages/health-types/src/account.rs create mode 100644 packages/rover/src/adapters/account_nft.rs diff --git a/Cargo.lock b/Cargo.lock index 120d05b6e..562b87b0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -480,9 +480,9 @@ dependencies = [ [[package]] name = "cw-multi-test" -version = "0.16.4" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a18afd2e201221c6d72a57f0886ef2a22151bbc9e6db7af276fde8a91081042" +checksum = "127c7bb95853b8e828bdab97065c81cb5ddc20f7339180b61b2300565aaa99d1" dependencies = [ "anyhow", "cosmwasm-std", @@ -1314,9 +1314,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.18" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "mars-account-nft" @@ -1449,16 +1449,15 @@ dependencies = [ [[package]] name = "mars-params" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed319ed66b221fcaef2a189deacc49b404f5ce9df1d03c58d8444d271fb10f22" +checksum = "8edfc032b3aedac656da1f90f5f1bc3b735c5de373b761279ff6b0de90346bd6" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.0.1", "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "mars-owner", - "mars-red-bank-types", "mars-utils", "schemars", "serde", @@ -1588,7 +1587,7 @@ dependencies = [ "mars-owner", "mars-rover", "mars-swapper-base", - "osmosis-std 0.15.2", + "osmosis-std 0.15.3", "osmosis-test-tube", "schemars", "thiserror", @@ -1639,7 +1638,7 @@ dependencies = [ "cw-utils 1.0.1", "cw2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "mars-v2-zapper-base", - "osmosis-std 0.15.2", + "osmosis-std 0.15.3", "osmosis-test-tube", ] @@ -1664,7 +1663,7 @@ dependencies = [ "cosmwasm-std", "mars-owner", "mars-v3-zapper-base", - "osmosis-std 0.15.2", + "osmosis-std 0.15.3", "osmosis-test-tube", ] @@ -1766,9 +1765,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "os_str_bytes" -version = "6.5.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "osmosis-std" @@ -1788,12 +1787,13 @@ dependencies = [ [[package]] name = "osmosis-std" -version = "0.15.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#d660806226c5fb388be97e08cb397d402830f785" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87725a7480b98887167edf878daa52201a13322ad88e34355a7f2ddc663e047e" dependencies = [ "chrono", "cosmwasm-std", - "osmosis-std-derive 0.15.2", + "osmosis-std-derive 0.15.3", "prost 0.11.9", "prost-types", "schemars", @@ -1815,8 +1815,9 @@ dependencies = [ [[package]] name = "osmosis-std-derive" -version = "0.15.2" -source = "git+https://github.com/osmosis-labs/osmosis-rust?branch=main#d660806226c5fb388be97e08cb397d402830f785" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4d482a16be198ee04e0f94e10dd9b8d02332dcf33bc5ea4b255e7e25eedc5df" dependencies = [ "itertools", "proc-macro2", @@ -1827,13 +1828,14 @@ dependencies = [ [[package]] name = "osmosis-test-tube" version = "15.1.0" -source = "git+https://github.com/osmosis-labs/test-tube?branch=main#8acf993ea5bf58dc043ee71fa057646b4e61ab0b" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aba931600343de65be9cbf9ed5a98a611bbf1fc9c45994eb980cc5afb18404" dependencies = [ "base64", "bindgen", "cosmrs", "cosmwasm-std", - "osmosis-std 0.15.2", + "osmosis-std 0.15.3", "prost 0.11.9", "serde", "serde_json", @@ -1939,9 +1941,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -2222,9 +2224,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] @@ -2269,9 +2271,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", @@ -2564,7 +2566,8 @@ dependencies = [ [[package]] name = "test-tube" version = "0.1.2" -source = "git+https://github.com/osmosis-labs/test-tube?branch=main#8acf993ea5bf58dc043ee71fa057646b4e61ab0b" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4807f0de0b84340e20a6ef1353a5c9db4543e1c74cd507aedd8b52e35d80020" dependencies = [ "base64", "cosmrs", diff --git a/Cargo.toml b/Cargo.toml index 044abe798..d141c398c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,16 +43,16 @@ cw2 = "1.0.1" cw721 = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main" } cw721-base = { git = "https://github.com/CosmWasm/cw-nfts/", branch = "main", features = ["library"] } cw-dex = { version = "0.2.0", features = ["osmosis"] } -cw-multi-test = "0.16.4" +cw-multi-test = "0.16.5" cw-paginate = "0.2.1" cw-utils = "1.0.1" cw-storage-plus = "1.0.1" cw-vault-standard = { version = "0.3.0", features = ["lockup", "force-unlock"] } itertools = "0.10.5" -osmosis-std = { git = "https://github.com/osmosis-labs/osmosis-rust", branch = "main" } -osmosis-test-tube = { git = "https://github.com/osmosis-labs/test-tube", branch = "main" } +osmosis-std = "0.15.3" +osmosis-test-tube = "15.1.0" schemars = "0.8.12" -serde = { version = "1.0.163", default-features = false, features = ["derive"] } +serde = { version = "1.0.164", default-features = false, features = ["derive"] } serde_json = "1.0.96" serde-wasm-bindgen = "0.5.0" thiserror = "1.0.40" @@ -68,7 +68,7 @@ mars-rover = { version = "2.0.0", path = "./packages/rover" } # contracts mars-account-nft = { version = "2.0.0", path = "./contracts/account-nft", features = ["library"] } -mars-params = { version = "1.0.4", features = ["library"] } +mars-params = { version = "1.0.6", features = ["library"] } mars-rover-health = { version = "2.0.0", path = "./contracts/health", features = ["library"] } mars-swapper-base = { version = "2.0.0", path = "./contracts/swapper/base", features = ["library"] } mars-v2-zapper-base = { version = "2.0.0", path = "./contracts/v2-zapper/base", features = ["library"] } diff --git a/Makefile.toml b/Makefile.toml index 44c20ef17..e92b24905 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -12,7 +12,8 @@ ARTIFACTS_DIR_PATH = "artifacts" [tasks.build] command = "cargo" -args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked"] +# TODO: After stable osmosis-test-tube CL version, remove the exclusion +args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--locked", "--exclude", "mars-v3-zapper-osmosis", "--workspace"] [tasks.rust-optimizer] script = """ @@ -29,7 +30,8 @@ docker run --rm -v "$(pwd)":/code \ [tasks.test] command = "cargo" -args = ["test", "--locked"] +# TODO: After stable osmosis-test-tube CL version, remove the exclusion +args = ["test", "--locked", "--exclude", "mars-v3-zapper-osmosis", "--workspace"] [tasks.fmt] toolchain = "nightly" @@ -38,7 +40,8 @@ args = ["fmt", "--all", "--check"] [tasks.clippy] command = "cargo" -args = ["clippy", "--tests", "--", "-D", "warnings"] +# TODO: After stable osmosis-test-tube CL version, remove the exclusion +args = ["clippy", "--tests", "--exclude", "mars-v3-zapper-osmosis", "--workspace", "--", "-D", "warnings"] [tasks.audit] command = "cargo" diff --git a/contracts/account-nft/src/execute.rs b/contracts/account-nft/src/execute.rs index 76a6bc71e..8dd3d1df3 100644 --- a/contracts/account-nft/src/execute.rs +++ b/contracts/account-nft/src/execute.rs @@ -6,7 +6,7 @@ use cw721_base::{ ContractError::Ownership, OwnershipError::{NoOwner, NotOwner}, }; -use mars_rover_health_types::{HealthResponse, QueryMsg::Health}; +use mars_rover_health_types::{AccountKind, HealthResponse, QueryMsg::Health}; use crate::{ contract::Parent, @@ -44,6 +44,7 @@ pub fn burn( contract_addr: health_contract_addr.into(), msg: to_binary(&Health { account_id: token_id.clone(), + kind: AccountKind::Default, })?, }))?; diff --git a/contracts/account-nft/src/msg/query.rs b/contracts/account-nft/src/msg/query.rs index 45aeff2c1..9e8e7691b 100644 --- a/contracts/account-nft/src/msg/query.rs +++ b/contracts/account-nft/src/msg/query.rs @@ -13,7 +13,7 @@ pub enum QueryMsg { #[returns(crate::nft_config::UncheckedNftConfig)] Config {}, - #[returns(u64)] + #[returns(String)] NextId {}, //-------------------------------------------------------------------------------------------------- diff --git a/contracts/account-nft/src/query.rs b/contracts/account-nft/src/query.rs index 0ebf69e2a..b1f3a3335 100644 --- a/contracts/account-nft/src/query.rs +++ b/contracts/account-nft/src/query.rs @@ -9,6 +9,6 @@ pub fn query_config(deps: Deps) -> StdResult { Ok(CONFIG.load(deps.storage)?.into()) } -pub fn query_next_id(deps: Deps) -> StdResult { - NEXT_ID.load(deps.storage) +pub fn query_next_id(deps: Deps) -> StdResult { + Ok(NEXT_ID.load(deps.storage)?.to_string()) } diff --git a/contracts/account-nft/tests/helpers/mock_env.rs b/contracts/account-nft/tests/helpers/mock_env.rs index efb163fc1..ea92c7c0e 100644 --- a/contracts/account-nft/tests/helpers/mock_env.rs +++ b/contracts/account-nft/tests/helpers/mock_env.rs @@ -12,7 +12,7 @@ use mars_account_nft::{ nft_config::{NftConfigUpdates, UncheckedNftConfig}, }; use mars_mock_rover_health::msg::ExecuteMsg::SetHealthResponse; -use mars_rover_health_types::HealthResponse; +use mars_rover_health_types::{AccountKind, HealthResponse}; use crate::helpers::MockEnvBuilder; @@ -47,7 +47,7 @@ impl MockEnv { .unwrap() } - pub fn query_next_id(&mut self) -> u64 { + pub fn query_next_id(&mut self) -> String { self.app.wrap().query_wasm_smart(self.nft_contract.clone(), &QueryMsg::NextId {}).unwrap() } @@ -67,6 +67,11 @@ impl MockEnv { assert_eq!(user.to_string(), owner_res.owner) } + pub fn assert_next_id(&mut self, expected_next_id: &str) { + let actual_next_id = self.query_next_id(); + assert_eq!(expected_next_id, &actual_next_id) + } + pub fn set_health_response( &mut self, sender: &Addr, @@ -81,6 +86,7 @@ impl MockEnv { Addr::unchecked(config.health_contract_addr.unwrap()), &SetHealthResponse { account_id: account_id.to_string(), + kind: AccountKind::Default, response: response.clone(), }, &[], diff --git a/contracts/account-nft/tests/test_instantiate.rs b/contracts/account-nft/tests/test_instantiate.rs index 45234f7b5..b9fb88008 100644 --- a/contracts/account-nft/tests/test_instantiate.rs +++ b/contracts/account-nft/tests/test_instantiate.rs @@ -18,8 +18,7 @@ fn instantiated_storage_vars() { assert_eq!("spiderman_1337", ownership.owner.unwrap()); assert_eq!(None, ownership.pending_owner); - let next_id = mock.query_next_id(); - assert_eq!(next_id, 1); + mock.assert_next_id("1"); } #[test] @@ -31,6 +30,5 @@ fn instantiated_storage_vars_with_health_contract() { assert_eq!(config.health_contract_addr, Some(health_contract.to_string())); assert_eq!(config.max_value_for_burn, MAX_VALUE_FOR_BURN); - let next_id = mock.query_next_id(); - assert_eq!(next_id, 1); + mock.assert_next_id("1"); } diff --git a/contracts/account-nft/tests/test_mint.rs b/contracts/account-nft/tests/test_mint.rs index 3e554bfab..0a7854a16 100644 --- a/contracts/account-nft/tests/test_mint.rs +++ b/contracts/account-nft/tests/test_mint.rs @@ -14,42 +14,51 @@ pub mod helpers; #[test] fn id_incrementer() { let mut mock = MockEnv::new().build().unwrap(); + mock.assert_next_id("1"); let user_1 = Addr::unchecked("user_1"); let token_id = mock.mint(&user_1).unwrap(); assert_eq!(token_id, "1"); mock.assert_owner_is_correct(&user_1, &token_id); + mock.assert_next_id("2"); let user_2 = Addr::unchecked("user_2"); let token_id = mock.mint(&user_2).unwrap(); assert_eq!(token_id, "2"); mock.assert_owner_is_correct(&user_2, &token_id); + mock.assert_next_id("3"); let user_3 = Addr::unchecked("user_3"); let token_id = mock.mint(&user_3).unwrap(); assert_eq!(token_id, "3"); mock.assert_owner_is_correct(&user_3, &token_id); + mock.assert_next_id("4"); } #[test] fn id_incrementer_works_despite_burns() { let mut mock = MockEnv::new().build().unwrap(); + mock.assert_next_id("1"); let user = Addr::unchecked("user"); let token_id_1 = mock.mint(&user).unwrap(); assert_eq!(token_id_1, "1"); + mock.assert_next_id("2"); let token_id_2 = mock.mint(&user).unwrap(); assert_eq!(token_id_2, "2"); + mock.assert_next_id("3"); mock.set_health_response(&user, &token_id_1, &below_max_for_burn()); mock.burn(&user, &token_id_1).unwrap(); mock.set_health_response(&user, &token_id_2, &below_max_for_burn()); mock.burn(&user, &token_id_2).unwrap(); + mock.assert_next_id("3"); let token_id_3 = mock.mint(&user).unwrap(); assert_eq!(token_id_3, "3"); mock.assert_owner_is_correct(&user, &token_id_3); + mock.assert_next_id("4"); } #[test] diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 3c127ec1b..d037bf2e3 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -29,6 +29,7 @@ cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw-vault-standard = { workspace = true } mars-account-nft = { workspace = true } +mars-params = { workspace = true } mars-owner = { workspace = true } mars-red-bank-types = { workspace = true } mars-rover = { workspace = true } @@ -41,7 +42,6 @@ itertools = { workspace = true } mars-mock-oracle = { workspace = true } mars-mock-red-bank = { workspace = true } mars-mock-vault = { workspace = true } -mars-params = { workspace = true } mars-rover-health = { workspace = true } mars-swapper-mock = { workspace = true } mars-v2-zapper-mock = { workspace = true } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index 823b1fbc9..fd750b059 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -19,6 +19,7 @@ use crate::{ }, repay::repay_from_wallet, update_config::{update_config, update_nft_config, update_owner}, + utils::get_account_kind, vault::handle_unlock_request_reply, zap::{estimate_provide_liquidity, estimate_withdraw_liquidity}, }; @@ -46,7 +47,7 @@ pub fn execute( msg: ExecuteMsg, ) -> ContractResult { match msg { - ExecuteMsg::CreateCreditAccount {} => create_credit_account(deps, info.sender), + ExecuteMsg::CreateCreditAccount(kind) => create_credit_account(deps, info.sender, kind), ExecuteMsg::UpdateConfig { updates, } => update_config(deps, info, updates), @@ -77,6 +78,9 @@ pub fn reply(deps: DepsMut, _: Env, reply: Reply) -> ContractResult { #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { let res = match msg { + QueryMsg::AccountKind { + account_id, + } => to_binary(&get_account_kind(deps.storage, &account_id)?), QueryMsg::Config {} => to_binary(&query_config(deps)?), QueryMsg::VaultUtilization { vault, diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index 139d439c4..fef97ef47 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -7,18 +7,20 @@ use mars_rover::{ error::{ContractError, ContractResult}, msg::execute::{Action, CallbackMsg, LiquidateRequest}, }; +use mars_rover_health_types::AccountKind; use crate::{ borrow::borrow, deposit::deposit, health::{assert_max_ltv, query_health}, + hls::assert_account_requirements, lend::lend, liquidate_deposit::liquidate_deposit, liquidate_lend::liquidate_lend, reclaim::reclaim, refund::refund_coin_balances, repay::{repay, repay_for_recipient}, - state::ACCOUNT_NFT, + state::{ACCOUNT_KINDS, ACCOUNT_NFT}, swap::swap_exact_in, update_coin_balances::update_coin_balance, utils::{assert_is_token_owner, assert_not_contract_in_config}, @@ -30,18 +32,28 @@ use crate::{ zap::{provide_liquidity, withdraw_liquidity}, }; -pub fn create_credit_account(deps: DepsMut, user: Addr) -> ContractResult { - let contract_addr = ACCOUNT_NFT.load(deps.storage)?; +pub fn create_credit_account( + deps: DepsMut, + user: Addr, + kind: AccountKind, +) -> ContractResult { + let account_nft = ACCOUNT_NFT.load(deps.storage)?; + + let next_id = account_nft.query_next_id(&deps.querier)?; + ACCOUNT_KINDS.save(deps.storage, &next_id, &kind)?; let nft_mint_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_addr.to_string(), + contract_addr: account_nft.address().into(), funds: vec![], msg: to_binary(&NftExecuteMsg::Mint { user: user.to_string(), })?, }); - Ok(Response::new().add_message(nft_mint_msg).add_attribute("action", "create_credit_account")) + Ok(Response::new() + .add_message(nft_mint_msg) + .add_attribute("action", "create_credit_account") + .add_attribute("kind", kind.to_string())) } pub fn dispatch_actions( @@ -203,7 +215,11 @@ pub fn dispatch_actions( } callbacks.extend([ - // after user selected actions, we assert LTV is either: + // Ensures the account state abides by the rules of the account kind + CallbackMsg::AssertAccountReqs { + account_id: account_id.to_string(), + }, + // After user selected actions, we assert LTV is either: // - Healthy, if prior to actions MaxLTV health factor >= 1 or None // - Not further weakened, if prior to actions MaxLTV health factor < 1 // Else, throw error and revert all actions @@ -354,5 +370,8 @@ pub fn execute_callback( CallbackMsg::RefundAllCoinBalances { account_id, } => refund_coin_balances(deps, env, &account_id), + CallbackMsg::AssertAccountReqs { + account_id, + } => assert_account_requirements(deps, env, account_id), } } diff --git a/contracts/credit-manager/src/health.rs b/contracts/credit-manager/src/health.rs index 1a7c6fe6a..62f976b14 100644 --- a/contracts/credit-manager/src/health.rs +++ b/contracts/credit-manager/src/health.rs @@ -5,11 +5,12 @@ use mars_rover::{ }; use mars_rover_health_types::{is_below_one, HealthResponse}; -use crate::state::HEALTH_CONTRACT; +use crate::{state::HEALTH_CONTRACT, utils::get_account_kind}; pub fn query_health(deps: Deps, account_id: &str) -> ContractResult { let hc = HEALTH_CONTRACT.load(deps.storage)?; - Ok(hc.query_health(&deps.querier, account_id)?) + let kind = get_account_kind(deps.storage, account_id)?; + Ok(hc.query_health(&deps.querier, account_id, kind)?) } pub fn assert_max_ltv( diff --git a/contracts/credit-manager/src/hls.rs b/contracts/credit-manager/src/hls.rs new file mode 100644 index 000000000..78e97473f --- /dev/null +++ b/contracts/credit-manager/src/hls.rs @@ -0,0 +1,105 @@ +use cosmwasm_std::{Deps, DepsMut, Env, Response}; +use mars_params::types::hls::HlsAssetType; +use mars_rover::error::{ContractError, ContractResult}; +use mars_rover_health_types::AccountKind; + +use crate::{query::query_positions, state::PARAMS, utils::get_account_kind}; + +pub fn assert_account_requirements( + deps: DepsMut, + env: Env, + account_id: String, +) -> ContractResult { + let kind = get_account_kind(deps.storage, &account_id)?; + + match kind { + AccountKind::Default => {} // No restrictions + AccountKind::HighLeveredStrategy => assert_hls_rules(deps.as_ref(), &env, &account_id)?, + } + + Ok(Response::new() + .add_attribute("action", "callback/assert_account_requirements") + .add_attribute("account_id", account_id) + .add_attribute("account_kind", kind.to_string())) +} + +fn assert_hls_rules(deps: Deps, env: &Env, account_id: &str) -> ContractResult<()> { + // Rule #1 - There can only be 0 or 1 debt denom in the account + let positions = query_positions(deps, env, account_id)?; + + if positions.debts.len() > 1 { + return Err(ContractError::HLS { + reason: "Account has more than one debt denom".to_string(), + }); + } + + if let Some(debt) = positions.debts.first() { + let params = PARAMS.load(deps.storage)?.query_asset_params(&deps.querier, &debt.denom)?; + + // Rule #2: Debt denom must have HLS params set in the Mars-Param contract + let Some(hls) = params.credit_manager.hls else { + return Err(ContractError::HLS { + reason: format!("{} does not have HLS parameters", debt.denom), + }); + }; + + // Rule #3: For that debt denom, verify all collateral assets are only those + // within the correlated list for that debt denom + + // === Deposits === + for deposit in positions.deposits.iter() { + hls.correlations + .iter() + .find(|h| match h { + HlsAssetType::Coin { + denom, + } => &deposit.denom == denom, + _ => false, + }) + .ok_or_else(|| ContractError::HLS { + reason: format!( + "{} deposit is not a correlated asset to debt {}", + deposit.denom, debt.denom + ), + })?; + } + + // === Lends === + for lend in positions.lends.iter() { + hls.correlations + .iter() + .find(|h| match h { + HlsAssetType::Coin { + denom, + } => &lend.denom == denom, + _ => false, + }) + .ok_or_else(|| ContractError::HLS { + reason: format!( + "{} lend is not a correlated asset to debt {}", + lend.denom, debt.denom + ), + })?; + } + + // === Vault positions === + for v in positions.vaults.iter() { + hls.correlations + .iter() + .find(|h| match h { + HlsAssetType::Vault { + addr, + } => v.vault.address == addr, + _ => false, + }) + .ok_or_else(|| ContractError::HLS { + reason: format!( + "{} vault is not a correlated asset to debt {}", + v.vault.address, debt.denom + ), + })?; + } + } + + Ok(()) +} diff --git a/contracts/credit-manager/src/lib.rs b/contracts/credit-manager/src/lib.rs index 00f7c016b..e8ea540da 100644 --- a/contracts/credit-manager/src/lib.rs +++ b/contracts/credit-manager/src/lib.rs @@ -4,6 +4,7 @@ pub mod borrow; pub mod deposit; pub mod execute; pub mod health; +pub mod hls; pub mod instantiate; pub mod lend; pub mod liquidate_deposit; diff --git a/contracts/credit-manager/src/query.rs b/contracts/credit-manager/src/query.rs index cdd989f8a..07d84620f 100644 --- a/contracts/credit-manager/src/query.rs +++ b/contracts/credit-manager/src/query.rs @@ -23,7 +23,7 @@ use crate::{ pub fn query_config(deps: Deps) -> ContractResult { Ok(ConfigResponse { ownership: OWNER.query(deps.storage)?, - account_nft: ACCOUNT_NFT.may_load(deps.storage)?.map(|addr| addr.to_string()), + account_nft: ACCOUNT_NFT.may_load(deps.storage)?.map(|a| a.address().into()), red_bank: RED_BANK.load(deps.storage)?.address().into(), oracle: ORACLE.load(deps.storage)?.address().into(), params: PARAMS.load(deps.storage)?.address().into(), diff --git a/contracts/credit-manager/src/state.rs b/contracts/credit-manager/src/state.rs index a1a6abf6f..f0a9c3331 100644 --- a/contracts/credit-manager/src/state.rs +++ b/contracts/credit-manager/src/state.rs @@ -2,15 +2,16 @@ use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::{Item, Map}; use mars_owner::Owner; use mars_rover::adapters::{ - health::HealthContract, oracle::Oracle, params::Params, red_bank::RedBank, swap::Swapper, - vault::VaultPositionAmount, zapper::Zapper, + account_nft::AccountNft, health::HealthContract, oracle::Oracle, params::Params, + red_bank::RedBank, swap::Swapper, vault::VaultPositionAmount, zapper::Zapper, }; +use mars_rover_health_types::AccountKind; use crate::vault::RequestTempStorage; // Contract dependencies // NOTE: Ensure assert_not_contract_in_config() is updated when an external contract is added here -pub const ACCOUNT_NFT: Item = Item::new("account_nft"); +pub const ACCOUNT_NFT: Item = Item::new("account_nft"); pub const ORACLE: Item = Item::new("oracle"); pub const RED_BANK: Item = Item::new("red_bank"); pub const SWAPPER: Item = Item::new("swapper"); @@ -23,6 +24,7 @@ pub const OWNER: Owner = Owner::new("owner"); pub const MAX_UNLOCKING_POSITIONS: Item = Item::new("max_unlocking_positions"); // Positions +pub const ACCOUNT_KINDS: Map<&str, AccountKind> = Map::new("account_types"); // Map pub const COIN_BALANCES: Map<(&str, &str), Uint128> = Map::new("coin_balance"); // Map<(AccountId, Denom), Amount> pub const DEBT_SHARES: Map<(&str, &str), Uint128> = Map::new("debt_shares"); // Map<(AccountId, Denom), Shares> pub const TOTAL_DEBT_SHARES: Map<&str, Uint128> = Map::new("total_debt_shares"); // Map diff --git a/contracts/credit-manager/src/update_config.rs b/contracts/credit-manager/src/update_config.rs index ed323db36..e5417bd99 100644 --- a/contracts/credit-manager/src/update_config.rs +++ b/contracts/credit-manager/src/update_config.rs @@ -17,13 +17,13 @@ pub fn update_config( let mut response = Response::new().add_attribute("action", "update_config"); - if let Some(addr_str) = updates.account_nft { - let validated = deps.api.addr_validate(&addr_str)?; - ACCOUNT_NFT.save(deps.storage, &validated)?; + if let Some(unchecked) = updates.account_nft { + let account_nft = unchecked.check(deps.api)?; + ACCOUNT_NFT.save(deps.storage, &account_nft)?; // Accept minter role. NFT contract minter must have proposed Rover as a new minter first. let accept_minter_role_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: addr_str.clone(), + contract_addr: account_nft.address().into(), funds: vec![], msg: to_binary(&NftExecuteMsg::UpdateOwnership(Action::AcceptOwnership))?, }); @@ -31,7 +31,7 @@ pub fn update_config( response = response .add_message(accept_minter_role_msg) .add_attribute("key", "account_nft") - .add_attribute("value", addr_str); + .add_attribute("value", unchecked.address()); } if let Some(unchecked) = updates.oracle { @@ -96,7 +96,7 @@ pub fn update_nft_config( if let Some(updates) = config { let update_config_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: nft_contract.to_string(), + contract_addr: nft_contract.address().into(), funds: vec![], msg: to_binary(&NftExecuteMsg::UpdateConfig { updates, @@ -107,7 +107,7 @@ pub fn update_nft_config( if let Some(action) = ownership { let update_ownership_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: nft_contract.to_string(), + contract_addr: nft_contract.address().into(), funds: vec![], msg: to_binary(&NftExecuteMsg::UpdateOwnership(action))?, }); diff --git a/contracts/credit-manager/src/utils.rs b/contracts/credit-manager/src/utils.rs index fc10f60ed..0522c874b 100644 --- a/contracts/credit-manager/src/utils.rs +++ b/contracts/credit-manager/src/utils.rs @@ -10,11 +10,12 @@ use mars_rover::{ error::{ContractError, ContractResult}, msg::{execute::CallbackMsg, ExecuteMsg}, }; +use mars_rover_health_types::AccountKind; use crate::{ state::{ - ACCOUNT_NFT, COIN_BALANCES, HEALTH_CONTRACT, LENT_SHARES, ORACLE, PARAMS, RED_BANK, - SWAPPER, TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, ZAPPER, + ACCOUNT_KINDS, ACCOUNT_NFT, COIN_BALANCES, HEALTH_CONTRACT, LENT_SHARES, ORACLE, PARAMS, + RED_BANK, SWAPPER, TOTAL_DEBT_SHARES, TOTAL_LENT_SHARES, ZAPPER, }, update_coin_balances::query_balance, }; @@ -33,7 +34,7 @@ pub fn assert_is_token_owner(deps: &DepsMut, user: &Addr, account_id: &str) -> C pub fn query_nft_token_owner(deps: Deps, account_id: &str) -> ContractResult { let contract_addr = ACCOUNT_NFT.load(deps.storage)?; let res: OwnerOfResponse = deps.querier.query_wasm_smart( - contract_addr, + contract_addr.address(), &QueryMsg::::OwnerOf { token_id: account_id.to_string(), include_expired: None, @@ -45,7 +46,7 @@ pub fn query_nft_token_owner(deps: Deps, account_id: &str) -> ContractResult ContractResult<()> { let params = PARAMS.load(deps.storage)?; match params.query_asset_params(&deps.querier, denom) { - Ok(p) if p.rover.whitelisted => Ok(()), + Ok(p) if p.credit_manager.whitelisted => Ok(()), _ => Err(ContractError::NotWhitelisted(denom.to_string())), } } @@ -54,6 +55,10 @@ pub fn assert_coins_are_whitelisted(deps: &mut DepsMut, denoms: Vec<&str>) -> Co denoms.iter().try_for_each(|denom| assert_coin_is_whitelisted(deps, denom)) } +pub fn get_account_kind(storage: &dyn Storage, account_id: &str) -> ContractResult { + Ok(ACCOUNT_KINDS.may_load(storage, account_id)?.unwrap_or(AccountKind::Default)) +} + pub fn increment_coin_balance( storage: &mut dyn Storage, account_id: &str, @@ -190,7 +195,7 @@ pub fn lent_shares_to_amount( /// NOTE: https://twitter.com/larry0x/status/1595919149381079041 pub fn assert_not_contract_in_config(deps: &Deps, addr_to_flag: &Addr) -> ContractResult<()> { let config_contracts = vec![ - ACCOUNT_NFT.load(deps.storage)?, + ACCOUNT_NFT.load(deps.storage)?.address().clone(), RED_BANK.load(deps.storage)?.address().clone(), ORACLE.load(deps.storage)?.address().clone(), SWAPPER.load(deps.storage)?.address().clone(), diff --git a/contracts/credit-manager/tests/helpers/builders.rs b/contracts/credit-manager/tests/helpers/builders.rs index ddf9cb977..facc20911 100644 --- a/contracts/credit-manager/tests/helpers/builders.rs +++ b/contracts/credit-manager/tests/helpers/builders.rs @@ -12,6 +12,7 @@ pub fn build_mock_coin_infos(count: usize) -> Vec { price: Decimal::from_atomics(10u128, 0).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), whitelisted: true, + hls: None, }) .collect() } @@ -28,6 +29,7 @@ pub fn build_mock_vaults(count: usize) -> Vec { max_ltv: lp_token.max_ltv, liquidation_threshold: lp_token.liquidation_threshold, whitelisted: true, + hls: None, } }) .collect() diff --git a/contracts/credit-manager/tests/helpers/mock_entity_info.rs b/contracts/credit-manager/tests/helpers/mock_entity_info.rs index e0756a951..95ed30f38 100644 --- a/contracts/credit-manager/tests/helpers/mock_entity_info.rs +++ b/contracts/credit-manager/tests/helpers/mock_entity_info.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use cosmwasm_std::{coin, Decimal}; use cw_utils::Duration; +use mars_params::types::hls::{HlsAssetType, HlsParamsUnchecked}; use crate::helpers::{CoinInfo, VaultTestInfo}; @@ -13,6 +14,7 @@ pub fn uosmo_info() -> CoinInfo { liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), whitelisted: true, + hls: None, } } @@ -24,6 +26,21 @@ pub fn uatom_info() -> CoinInfo { liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(10u128, 2).unwrap(), whitelisted: true, + hls: Some(HlsParamsUnchecked { + max_loan_to_value: Decimal::from_str("0.86").unwrap(), + liquidation_threshold: Decimal::from_str("0.93").unwrap(), + correlations: vec![ + HlsAssetType::Coin { + denom: "uatom".to_string(), + }, + HlsAssetType::Coin { + denom: "stAtom".to_string(), + }, + HlsAssetType::Coin { + denom: lp_token_info().denom, + }, + ], + }), } } @@ -35,6 +52,11 @@ pub fn ujake_info() -> CoinInfo { liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), whitelisted: true, + hls: Some(HlsParamsUnchecked { + max_loan_to_value: Decimal::from_str("0.7").unwrap(), + liquidation_threshold: Decimal::from_str("0.8").unwrap(), + correlations: vec![], + }), } } @@ -46,6 +68,7 @@ pub fn blacklisted_coin() -> CoinInfo { liquidation_threshold: Decimal::from_str("0.5").unwrap(), liquidation_bonus: Decimal::from_str("0.33").unwrap(), whitelisted: false, + hls: None, } } @@ -57,6 +80,11 @@ pub fn lp_token_info() -> CoinInfo { liquidation_threshold: Decimal::from_atomics(68u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), whitelisted: true, + hls: Some(HlsParamsUnchecked { + max_loan_to_value: Decimal::from_str("0.75").unwrap(), + liquidation_threshold: Decimal::from_str("0.82").unwrap(), + correlations: vec![], + }), } } @@ -79,10 +107,15 @@ pub fn generate_mock_vault(lockup: Option) -> VaultTestInfo { VaultTestInfo { vault_token_denom, lockup, - base_token_denom: lp_token.denom, + base_token_denom: lp_token.denom.clone(), deposit_cap: coin(10_000_000, "uusdc"), - max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), + max_ltv: Decimal::from_str("0.6").unwrap(), + liquidation_threshold: Decimal::from_str("0.7").unwrap(), whitelisted: true, + hls: Some(HlsParamsUnchecked { + max_loan_to_value: lp_token.hls.as_ref().unwrap().max_loan_to_value, + liquidation_threshold: lp_token.hls.unwrap().liquidation_threshold, + correlations: vec![], + }), } } diff --git a/contracts/credit-manager/tests/helpers/mock_env.rs b/contracts/credit-manager/tests/helpers/mock_env.rs index 75322f1a8..d0815aed3 100644 --- a/contracts/credit-manager/tests/helpers/mock_env.rs +++ b/contracts/credit-manager/tests/helpers/mock_env.rs @@ -1,4 +1,4 @@ -use std::{mem::take, str::FromStr}; +use std::{default::Default, mem::take, str::FromStr}; use anyhow::Result as AnyResult; use cosmwasm_std::{coins, testing::MockApi, Addr, Coin, Decimal, Empty, StdResult, Uint128}; @@ -23,12 +23,14 @@ use mars_mock_vault::{ use mars_owner::OwnerUpdate; use mars_params::{ msg::{ + AssetParamsUpdate, + AssetParamsUpdate::AddOrUpdate, ExecuteMsg::{UpdateAssetParams, UpdateVaultConfig}, - InstantiateMsg as ParamsInstantiateMsg, QueryMsg as ParamsQueryMsg, + InstantiateMsg as ParamsInstantiateMsg, QueryMsg as ParamsQueryMsg, VaultConfigUpdate, }, types::{ - AssetParams, AssetParamsUpdate, AssetParamsUpdate::AddOrUpdate, VaultConfig, - VaultConfigUnchecked, VaultConfigUpdate, + asset::AssetParams, + vault::{VaultConfig, VaultConfigUnchecked}, }, }; use mars_red_bank_types::red_bank::{ @@ -37,6 +39,7 @@ use mars_red_bank_types::red_bank::{ }; use mars_rover::{ adapters::{ + account_nft::AccountNftUnchecked, health::HealthContract, oracle::{Oracle, OracleBase, OracleUnchecked}, params::Params, @@ -60,7 +63,7 @@ use mars_rover::{ }, }; use mars_rover_health_types::{ - ExecuteMsg::UpdateConfig, HealthResponse, InstantiateMsg as HealthInstantiateMsg, + AccountKind, ExecuteMsg::UpdateConfig, HealthResponse, InstantiateMsg as HealthInstantiateMsg, QueryMsg::Health, }; use mars_v2_zapper_mock::msg::{InstantiateMsg as ZapperInstantiateMsg, LpConfig}; @@ -226,7 +229,7 @@ impl MockEnv { ) } - pub fn deploy_new_nft_contract(&mut self) -> AnyResult { + pub fn deploy_new_nft_contract(&mut self) -> AnyResult { let nft_minter = Addr::unchecked("original_nft_minter"); let nft_contract = deploy_nft_contract(&mut self.app, &nft_minter); propose_new_nft_minter( @@ -235,14 +238,22 @@ impl MockEnv { &nft_minter.clone(), &self.rover.clone(), ); - Ok(nft_contract) + Ok(AccountNftUnchecked::new(nft_contract.to_string())) } pub fn create_credit_account(&mut self, sender: &Addr) -> AnyResult { + self._create_credit_account(sender, AccountKind::Default) + } + + pub fn create_hls_account(&mut self, sender: &Addr) -> String { + self._create_credit_account(sender, AccountKind::HighLeveredStrategy).unwrap() + } + + fn _create_credit_account(&mut self, sender: &Addr, kind: AccountKind) -> AnyResult { let res = self.app.execute_contract( sender.clone(), self.rover.clone(), - &ExecuteMsg::CreateCreditAccount {}, + &ExecuteMsg::CreateCreditAccount(kind), &[], )?; Ok(self.get_account_id(res)) @@ -306,13 +317,14 @@ impl MockEnv { .unwrap() } - pub fn query_health(&self, account_id: &str) -> HealthResponse { + pub fn query_health(&self, account_id: &str, kind: AccountKind) -> HealthResponse { self.app .wrap() .query_wasm_smart( self.health_contract.clone().address(), &Health { account_id: account_id.to_string(), + kind, }, ) .unwrap() @@ -326,6 +338,18 @@ impl MockEnv { self.app.wrap().query_wasm_smart(self.rover.clone(), &QueryMsg::Config {}).unwrap() } + pub fn query_account_kind(&self, account_id: &str) -> AccountKind { + self.app + .wrap() + .query_wasm_smart( + self.rover.clone(), + &QueryMsg::AccountKind { + account_id: account_id.to_string(), + }, + ) + .unwrap() + } + pub fn query_nft_config(&self) -> UncheckedNftConfig { let config = self.query_config(); self.app @@ -685,7 +709,7 @@ impl MockEnvBuilder { self.update_config( rover, ConfigUpdates { - account_nft: Some(nft_contract.to_string()), + account_nft: Some(AccountNftUnchecked::new(nft_contract.to_string())), ..Default::default() }, ) @@ -978,6 +1002,7 @@ impl MockEnvBuilder { max_loan_to_value: vault.max_ltv, liquidation_threshold: vault.liquidation_threshold, whitelisted: vault.whitelisted, + hls: vault.hls.clone(), }, }), &[], diff --git a/contracts/credit-manager/tests/helpers/types.rs b/contracts/credit-manager/tests/helpers/types.rs index 353d2732c..8525d5648 100644 --- a/contracts/credit-manager/tests/helpers/types.rs +++ b/contracts/credit-manager/tests/helpers/types.rs @@ -1,9 +1,10 @@ -use std::str::FromStr; - use cosmwasm_schema::cw_serde; use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; use cw_utils::Duration; -use mars_params::types::{AssetParams, HighLeverageStrategyParams, RedBankSettings, RoverSettings}; +use mars_params::types::{ + asset::{AssetParamsUnchecked, CmSettings, RedBankSettings}, + hls::HlsParamsUnchecked, +}; use mars_rover::msg::execute::{ActionAmount, ActionCoin}; #[cw_serde] @@ -20,6 +21,7 @@ pub struct CoinInfo { pub liquidation_threshold: Decimal, pub liquidation_bonus: Decimal, pub whitelisted: bool, + pub hls: Option, } #[cw_serde] @@ -40,6 +42,7 @@ pub struct VaultTestInfo { pub max_ltv: Decimal, pub liquidation_threshold: Decimal, pub whitelisted: bool, + pub hls: Option, } impl CoinInfo { @@ -62,16 +65,13 @@ impl CoinInfo { } } -impl From for AssetParams { +impl From for AssetParamsUnchecked { fn from(c: CoinInfo) -> Self { Self { denom: c.denom, - rover: RoverSettings { + credit_manager: CmSettings { whitelisted: c.whitelisted, - hls: HighLeverageStrategyParams { - max_loan_to_value: Decimal::from_str("0.86").unwrap(), - liquidation_threshold: Decimal::from_str("0.89").unwrap(), - }, + hls: c.hls, }, red_bank: RedBankSettings { deposit_enabled: true, diff --git a/contracts/credit-manager/tests/test_health.rs b/contracts/credit-manager/tests/test_health.rs index a2c0fe1e3..563e9ebb6 100644 --- a/contracts/credit-manager/tests/test_health.rs +++ b/contracts/credit-manager/tests/test_health.rs @@ -3,7 +3,7 @@ use std::ops::{Add, Mul}; use cosmwasm_std::{coin, coins, Addr, Coin, Decimal, Uint128}; use mars_credit_manager::borrow::DEFAULT_DEBT_SHARES_PER_COIN_BORROWED; use mars_mock_oracle::msg::CoinPrice; -use mars_params::types::{AssetParamsUpdate::AddOrUpdate, VaultConfigUpdate}; +use mars_params::msg::{AssetParamsUpdate::AddOrUpdate, VaultConfigUpdate}; use mars_rover::{ error::ContractError, msg::{ @@ -14,6 +14,7 @@ use mars_rover::{ query::DebtAmount, }, }; +use mars_rover_health_types::AccountKind; use crate::helpers::{ assert_err, lp_token_info, uatom_info, ujake_info, unlocked_vault_info, uosmo_info, @@ -58,7 +59,7 @@ fn only_assets_with_no_debts() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 0); - let health = mock.query_health(&account_id); + let health = mock.query_health(&account_id, AccountKind::Default); let assets_value = deposit_amount.checked_mul_floor(coin_info.price).unwrap(); assert_eq!(health.total_collateral_value, assets_value); assert_eq!(health.total_debt_value, Uint128::zero()); @@ -87,6 +88,7 @@ fn terra_ragnarok() { liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), whitelisted: true, + hls: None, }; let user = Addr::unchecked("user"); @@ -118,7 +120,7 @@ fn terra_ragnarok() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 1); - let health = mock.query_health(&account_id); + let health = mock.query_health(&account_id, AccountKind::Default); let assets_value = (deposit_amount + borrow_amount).checked_mul_floor(coin_info.price).unwrap(); assert_eq!(health.total_collateral_value, assets_value); // Note: Simulated yield from mock_red_bank makes debt position more expensive @@ -152,7 +154,7 @@ fn terra_ragnarok() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 1); - let health = mock.query_health(&account_id); + let health = mock.query_health(&account_id, AccountKind::Default); assert_eq!(health.total_collateral_value, Uint128::zero()); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.liquidation_health_factor, None); @@ -196,7 +198,7 @@ fn debts_no_assets() { assert_eq!(position.deposits.len(), 0); assert_eq!(position.debts.len(), 0); - let health = mock.query_health(&account_id); + let health = mock.query_health(&account_id, AccountKind::Default); assert_eq!(health.total_collateral_value, Uint128::zero()); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.liquidation_health_factor, None); @@ -245,7 +247,7 @@ fn cannot_borrow_more_than_healthy() { assert_eq!(position.deposits.len(), 1); assert_eq!(position.debts.len(), 1); - let health = mock.query_health(&account_id); + let health = mock.query_health(&account_id, AccountKind::Default); let assets_value = Uint128::new(827); assert_eq!(health.total_collateral_value, assets_value); let debts_value = Uint128::new(120); @@ -270,7 +272,7 @@ fn cannot_borrow_more_than_healthy() { ); // All valid on step 2 as well (meaning step 3 did not go through) - let health = mock.query_health(&account_id); + let health = mock.query_health(&account_id, AccountKind::Default); let assets_value = Uint128::new(1064); assert_eq!(health.total_collateral_value, assets_value); let debts_value = Uint128::new(359); @@ -301,6 +303,7 @@ fn cannot_borrow_more_but_not_liquidatable() { liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), whitelisted: true, + hls: None, }; let uatom_info = CoinInfo { denom: "uatom".to_string(), @@ -309,6 +312,7 @@ fn cannot_borrow_more_but_not_liquidatable() { liquidation_threshold: Decimal::from_atomics(75u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), whitelisted: true, + hls: None, }; let user = Addr::unchecked("user"); @@ -330,7 +334,7 @@ fn cannot_borrow_more_but_not_liquidatable() { ) .unwrap(); - let health = mock.query_health(&account_id); + let health = mock.query_health(&account_id, AccountKind::Default); assert!(!health.liquidatable); assert!(!health.above_max_ltv); @@ -339,7 +343,7 @@ fn cannot_borrow_more_but_not_liquidatable() { price: Decimal::from_atomics(24u128, 0).unwrap(), }); - let health = mock.query_health(&account_id); + let health = mock.query_health(&account_id, AccountKind::Default); assert!(!health.liquidatable); assert!(health.above_max_ltv); @@ -359,7 +363,7 @@ fn cannot_borrow_more_but_not_liquidatable() { price: Decimal::from_atomics(35u128, 0).unwrap(), }); - let health = mock.query_health(&account_id); + let health = mock.query_health(&account_id, AccountKind::Default); assert!(health.liquidatable); assert!(health.above_max_ltv); } @@ -379,6 +383,7 @@ fn assets_and_ltv_lqdt_adjusted_value() { liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), whitelisted: true, + hls: None, }; let uatom_info = CoinInfo { denom: "uatom".to_string(), @@ -387,6 +392,7 @@ fn assets_and_ltv_lqdt_adjusted_value() { liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), whitelisted: true, + hls: None, }; let user = Addr::unchecked("user"); @@ -418,7 +424,7 @@ fn assets_and_ltv_lqdt_adjusted_value() { assert_eq!(position.deposits.len(), 2); assert_eq!(position.debts.len(), 1); - let health = mock.query_health(&account_id); + let health = mock.query_health(&account_id, AccountKind::Default); assert_eq!( health.total_collateral_value, deposit_amount @@ -487,6 +493,7 @@ fn debt_value() { liquidation_threshold: Decimal::from_atomics(5u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), whitelisted: true, + hls: None, }; let uatom_info = CoinInfo { denom: "uatom".to_string(), @@ -495,6 +502,7 @@ fn debt_value() { liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), whitelisted: true, + hls: None, }; let user_a = Addr::unchecked("user_a"); @@ -551,7 +559,7 @@ fn debt_value() { assert_eq!(position_a.deposits.len(), 2); assert_eq!(position_a.debts.len(), 2); - let health = mock.query_health(&account_id_a); + let health = mock.query_health(&account_id_a, AccountKind::Default); assert!(!health.above_max_ltv); assert!(!health.liquidatable); @@ -644,7 +652,7 @@ fn delisted_deposits_drop_max_ltv() { ) .unwrap(); - let prev_health = mock.query_health(&account_id); + let prev_health = mock.query_health(&account_id, AccountKind::Default); // Blacklist osmo in params contract uosmo_info.whitelisted = false; @@ -652,7 +660,7 @@ fn delisted_deposits_drop_max_ltv() { params: uosmo_info.into(), }); - let curr_health = mock.query_health(&account_id); + let curr_health = mock.query_health(&account_id, AccountKind::Default); // Values should be the same assert_eq!(prev_health.total_debt_value, curr_health.total_debt_value); @@ -707,7 +715,7 @@ fn delisted_vaults_drop_max_ltv() { ) .unwrap(); - let prev_health = mock.query_health(&account_id); + let prev_health = mock.query_health(&account_id, AccountKind::Default); // Blacklist vault let mut config = mock.query_vault_params(&vault.address); @@ -716,7 +724,7 @@ fn delisted_vaults_drop_max_ltv() { config: config.into(), }); - let curr_health = mock.query_health(&account_id); + let curr_health = mock.query_health(&account_id, AccountKind::Default); // Values should be the same assert_eq!(prev_health.total_debt_value, curr_health.total_debt_value); @@ -771,7 +779,7 @@ fn vault_base_token_delisting_drops_max_ltv() { ) .unwrap(); - let prev_health = mock.query_health(&account_id); + let prev_health = mock.query_health(&account_id, AccountKind::Default); // Blacklist LP token in params contract lp_token.whitelisted = false; @@ -779,7 +787,7 @@ fn vault_base_token_delisting_drops_max_ltv() { params: lp_token.into(), }); - let curr_health = mock.query_health(&account_id); + let curr_health = mock.query_health(&account_id, AccountKind::Default); // Values should be the same assert_eq!(prev_health.total_debt_value, curr_health.total_debt_value); @@ -808,6 +816,7 @@ fn can_take_actions_if_ltv_does_not_weaken() { liquidation_threshold: Decimal::from_atomics(55u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), whitelisted: true, + hls: None, }; let uatom_info = CoinInfo { denom: "uatom".to_string(), @@ -816,6 +825,7 @@ fn can_take_actions_if_ltv_does_not_weaken() { liquidation_threshold: Decimal::from_atomics(75u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(2u128, 1).unwrap(), whitelisted: true, + hls: None, }; let user = Addr::unchecked("user"); @@ -842,7 +852,7 @@ fn can_take_actions_if_ltv_does_not_weaken() { price: Decimal::from_atomics(24u128, 0).unwrap(), }); - let health = mock.query_health(&account_id); + let health = mock.query_health(&account_id, AccountKind::Default); assert!(!health.liquidatable); assert!(health.above_max_ltv); diff --git a/contracts/credit-manager/tests/test_hls_accounts.rs b/contracts/credit-manager/tests/test_hls_accounts.rs new file mode 100644 index 000000000..fa763053b --- /dev/null +++ b/contracts/credit-manager/tests/test_hls_accounts.rs @@ -0,0 +1,381 @@ +use cosmwasm_std::{coins, Addr, Decimal, Uint128}; +use mars_params::{msg::AssetParamsUpdate::AddOrUpdate, types::hls::HlsAssetType}; +use mars_rover::{ + error::ContractError, + msg::execute::Action::{Borrow, Deposit, EnterVault, Lend}, +}; +use mars_rover_health_types::{AccountKind, HealthResponse}; + +use crate::helpers::{ + assert_err, lp_token_info, uatom_info, ujake_info, unlocked_vault_info, AccountToFund, MockEnv, +}; + +pub mod helpers; + +#[test] +fn queries_return_the_expected_kind() { + let mut mock = MockEnv::new().build().unwrap(); + let user = Addr::unchecked("user"); + + let account_id = mock.create_hls_account(&user); + let kind = mock.query_account_kind(&account_id); + assert_eq!(AccountKind::HighLeveredStrategy, kind); + + let account_id = mock.create_credit_account(&user).unwrap(); + let kind = mock.query_account_kind(&account_id); + assert_eq!(AccountKind::Default, kind); +} + +#[test] +fn more_than_one_debt_does_not_qualify() { + let atom_info = uatom_info(); + let jake_info = ujake_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .set_params(&[atom_info.clone(), jake_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, atom_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id = mock.create_hls_account(&user); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(atom_info.to_coin(300)), + Borrow(atom_info.to_coin(10)), + Borrow(jake_info.to_coin(1)), + ], + &[atom_info.to_coin(300)], + ); + + assert_err( + res, + ContractError::HLS { + reason: "Account has more than one debt denom".to_string(), + }, + ) +} + +#[test] +fn hls_allows_zero_debts_is_ok() { + let atom_info = uatom_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .set_params(&[atom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, atom_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id = mock.create_hls_account(&user); + + mock.update_credit_account( + &account_id, + &user, + vec![Deposit(atom_info.to_coin(300))], + &[atom_info.to_coin(300)], + ) + .unwrap(); + + // No error raised +} + +#[test] +fn debt_denom_is_not_an_hls_asset() { + let mut atom_info = uatom_info(); + atom_info.hls = None; + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .set_params(&[atom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, atom_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id = mock.create_hls_account(&user); + + let res = mock.update_credit_account( + &account_id, + &user, + vec![Deposit(atom_info.to_coin(300)), Borrow(atom_info.to_coin(10))], + &[atom_info.to_coin(300)], + ); + + assert_err( + res, + ContractError::HLS { + reason: format!("{} does not have HLS parameters", atom_info.denom), + }, + ) +} + +#[test] +fn wrong_correlations_does_not_qualify() { + let atom_info = uatom_info(); + let jake_info = ujake_info(); + let lp_token = lp_token_info(); + let leverage_vault = unlocked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .set_params(&[atom_info.clone(), jake_info.clone(), lp_token.clone()]) + .vault_configs(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![jake_info.to_coin(300), atom_info.to_coin(300), lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_hls_account(&user); + + // Case #1 - Collateral asset is not in correlations list + + let res = mock.update_credit_account( + &account_id, + &user, + vec![Deposit(jake_info.to_coin(300)), Borrow(atom_info.to_coin(1))], + &[jake_info.to_coin(300)], + ); + + assert_err( + res, + ContractError::HLS { + reason: format!( + "{} deposit is not a correlated asset to debt {}", + jake_info.denom, atom_info.denom + ), + }, + ); + + // Case #2 - Some collateral assets are not in correlations list + + let res = mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(jake_info.to_coin(300)), + Deposit(atom_info.to_coin(300)), + Borrow(atom_info.to_coin(1)), + ], + &[jake_info.to_coin(300), atom_info.to_coin(300)], + ); + + assert_err( + res, + ContractError::HLS { + reason: format!( + "{} deposit is not a correlated asset to debt {}", + jake_info.denom, atom_info.denom + ), + }, + ); + + // Case #3 - Lend asset types are checked + + let res = mock.update_credit_account( + &account_id, + &user, + vec![ + Lend(jake_info.to_coin(50)), + Deposit(jake_info.to_coin(50)), + Deposit(atom_info.to_coin(300)), + Borrow(atom_info.to_coin(1)), + ], + &[atom_info.to_coin(300), jake_info.to_coin(50)], + ); + + assert_err( + res, + ContractError::HLS { + reason: format!( + "{} lend is not a correlated asset to debt {}", + jake_info.denom, atom_info.denom + ), + }, + ); + + // Case #4 - Vault asset types are checked + + let vault = mock.get_vault(&leverage_vault); + let res = mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(300)), + EnterVault { + vault: vault.clone(), + coin: lp_token.to_action_coin(23), + }, + Borrow(atom_info.to_coin(1)), + ], + &[lp_token.to_coin(300)], + ); + + assert_err( + res, + ContractError::HLS { + reason: format!( + "{} vault is not a correlated asset to debt {}", + vault.address, atom_info.denom + ), + }, + ); +} + +#[test] +fn successful_with_asset_correlations() { + let atom_info = uatom_info(); + let lp_token = lp_token_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .set_params(&[atom_info.clone(), lp_token.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + let account_id = mock.create_hls_account(&user); + + let lp_deposit_amount = 300; + let atom_borrow_amount = 150; + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(lp_deposit_amount)), + Borrow(atom_info.to_coin(atom_borrow_amount)), + ], + &[lp_token.to_coin(lp_deposit_amount)], + ) + .unwrap(); + + let hls_health = mock.query_health(&account_id, AccountKind::HighLeveredStrategy); + let total_debt_value = atom_info.price * Uint128::new(atom_borrow_amount) + Uint128::one(); + let lp_collateral_value = lp_token.price * Uint128::new(lp_deposit_amount); + let atom_collateral_value = atom_info.price * Uint128::new(atom_borrow_amount); + let lp_hls_max_ltv = lp_collateral_value * lp_token.hls.as_ref().unwrap().max_loan_to_value; + let atom_hls_max_ltv = + atom_collateral_value * atom_info.hls.as_ref().unwrap().max_loan_to_value; + let lp_hls_liq = lp_collateral_value * lp_token.hls.unwrap().liquidation_threshold; + let atom_hls_liq = atom_collateral_value * atom_info.hls.unwrap().liquidation_threshold; + + assert_eq!( + HealthResponse { + total_debt_value, + total_collateral_value: lp_collateral_value + atom_collateral_value, + max_ltv_adjusted_collateral: lp_hls_max_ltv + atom_hls_max_ltv, + liquidation_threshold_adjusted_collateral: lp_hls_liq + atom_hls_liq, + max_ltv_health_factor: Some( + Decimal::checked_from_ratio(lp_hls_max_ltv + atom_hls_max_ltv, total_debt_value) + .unwrap() + ), + liquidation_health_factor: Some( + Decimal::checked_from_ratio(lp_hls_liq + atom_hls_liq, total_debt_value).unwrap() + ), + liquidatable: false, + above_max_ltv: false, + }, + hls_health + ); + + let default_health = mock.query_health(&account_id, AccountKind::Default); + assert_ne!(hls_health, default_health); +} + +#[test] +fn successful_with_vault_correlations() { + let atom_info = uatom_info(); + let lp_token = lp_token_info(); + let leverage_vault = unlocked_vault_info(); + + let user = Addr::unchecked("user"); + let mut mock = MockEnv::new() + .set_params(&[atom_info.clone(), lp_token.clone()]) + .vault_configs(&[leverage_vault.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: vec![lp_token.to_coin(300)], + }) + .build() + .unwrap(); + + // Add vault to correlations of Atom in params contract + let vault = mock.get_vault(&leverage_vault); + let mut asset_params = mock.query_asset_params(&atom_info.denom); + asset_params.credit_manager.hls.as_mut().unwrap().correlations.push(HlsAssetType::Vault { + addr: Addr::unchecked(vault.address), + }); + mock.update_asset_params(AddOrUpdate { + params: asset_params.into(), + }); + + let account_id = mock.create_hls_account(&user); + + let lp_deposit_amount = 300; + let atom_borrow_amount = 150; + + let vault = mock.get_vault(&leverage_vault); + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(lp_token.to_coin(lp_deposit_amount)), + EnterVault { + vault, + coin: lp_token.to_action_coin(lp_deposit_amount), + }, + Borrow(atom_info.to_coin(atom_borrow_amount)), + ], + &[lp_token.to_coin(lp_deposit_amount)], + ) + .unwrap(); + + let hls_health = mock.query_health(&account_id, AccountKind::HighLeveredStrategy); + let total_debt_value = atom_info.price * Uint128::new(atom_borrow_amount) + Uint128::one(); + let lp_collateral_value = lp_token.price * Uint128::new(lp_deposit_amount); + let atom_collateral_value = atom_info.price * Uint128::new(atom_borrow_amount); + let lp_hls_max_ltv = lp_collateral_value * lp_token.hls.as_ref().unwrap().max_loan_to_value; + let atom_hls_max_ltv = + atom_collateral_value * atom_info.hls.as_ref().unwrap().max_loan_to_value; + let lp_hls_liq = lp_collateral_value * lp_token.hls.unwrap().liquidation_threshold; + let atom_hls_liq = atom_collateral_value * atom_info.hls.unwrap().liquidation_threshold; + + assert_eq!( + HealthResponse { + total_debt_value, + total_collateral_value: lp_collateral_value + atom_collateral_value, + max_ltv_adjusted_collateral: lp_hls_max_ltv + atom_hls_max_ltv, + liquidation_threshold_adjusted_collateral: lp_hls_liq + atom_hls_liq, + max_ltv_health_factor: Some( + Decimal::checked_from_ratio(lp_hls_max_ltv + atom_hls_max_ltv, total_debt_value) + .unwrap() + ), + liquidation_health_factor: Some( + Decimal::checked_from_ratio(lp_hls_liq + atom_hls_liq, total_debt_value).unwrap() + ), + liquidatable: false, + above_max_ltv: false, + }, + hls_health + ); + + let default_health = mock.query_health(&account_id, AccountKind::Default); + assert_ne!(hls_health, default_health); +} diff --git a/contracts/credit-manager/tests/test_liquidate_deposit.rs b/contracts/credit-manager/tests/test_liquidate_deposit.rs index 1eb165cab..d0cbdf656 100644 --- a/contracts/credit-manager/tests/test_liquidate_deposit.rs +++ b/contracts/credit-manager/tests/test_liquidate_deposit.rs @@ -10,6 +10,7 @@ use mars_rover::{ LiquidateRequest, }, }; +use mars_rover_health_types::AccountKind; use crate::helpers::{ assert_err, get_coin, get_debt, lp_token_info, uatom_info, ujake_info, unlocked_vault_info, @@ -45,7 +46,7 @@ fn can_only_liquidate_unhealthy_accounts() { ) .unwrap(); - let health = mock.query_health(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(!health.liquidatable); let liquidator = Addr::unchecked("liquidator"); @@ -106,7 +107,7 @@ fn vault_positions_contribute_to_health() { ) .unwrap(); - let health = mock.query_health(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(!health.liquidatable); let liquidator = Addr::unchecked("liquidator"); @@ -157,7 +158,7 @@ fn liquidatee_does_not_have_requested_asset() { ) .unwrap(); - let health = mock.query_health(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(!health.liquidatable); mock.price_change(CoinPrice { @@ -215,7 +216,7 @@ fn liquidatee_does_not_have_debt_coin() { ) .unwrap(); - let health = mock.query_health(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(!health.liquidatable); // Seeding a jakecoin borrow @@ -277,7 +278,7 @@ fn liquidator_does_not_have_enough_to_pay_debt() { ) .unwrap(); - let health = mock.query_health(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(!health.liquidatable); mock.price_change(CoinPrice { @@ -333,7 +334,7 @@ fn liquidator_left_in_unhealthy_state() { ) .unwrap(); - let health = mock.query_health(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(!health.liquidatable); mock.price_change(CoinPrice { diff --git a/contracts/credit-manager/tests/test_liquidate_lend.rs b/contracts/credit-manager/tests/test_liquidate_lend.rs index 2316ffb63..82b7d228b 100644 --- a/contracts/credit-manager/tests/test_liquidate_lend.rs +++ b/contracts/credit-manager/tests/test_liquidate_lend.rs @@ -7,6 +7,7 @@ use mars_rover::{ ActionAmount, ActionCoin, LiquidateRequest, }, }; +use mars_rover_health_types::AccountKind; use crate::helpers::{ assert_err, get_coin, get_debt, get_lent, uatom_info, ujake_info, uosmo_info, AccountToFund, @@ -40,7 +41,7 @@ fn lent_positions_contribute_to_health() { ) .unwrap(); - let health_1 = mock.query_health(&liquidatee_account_id); + let health_1 = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(!health_1.liquidatable); mock.update_credit_account( @@ -52,7 +53,7 @@ fn lent_positions_contribute_to_health() { .unwrap(); // Collateral should be the same after Lend - let health_2 = mock.query_health(&liquidatee_account_id); + let health_2 = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(!health_2.liquidatable); // health_2.total_collateral_value bigger (+1) because of simulated yield assert_eq!(health_1.total_collateral_value, health_2.total_collateral_value - Uint128::one()); @@ -126,7 +127,7 @@ fn liquidatee_does_not_have_requested_lent_coin() { price: Decimal::from_atomics(20u128, 0).unwrap(), }); - let health = mock.query_health(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(health.liquidatable); let liquidator_account_id = mock.create_credit_account(&liquidator).unwrap(); @@ -189,7 +190,7 @@ fn lent_position_partially_liquidated() { price: Decimal::from_atomics(55u128, 1).unwrap(), }); - let health = mock.query_health(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(health.liquidatable); assert_eq!(health.total_collateral_value, Uint128::new(624u128)); assert_eq!(health.total_debt_value, Uint128::new(555u128)); @@ -283,7 +284,7 @@ fn lent_position_fully_liquidated() { price: Decimal::from_atomics(50u128, 1).unwrap(), }); - let health = mock.query_health(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(health.liquidatable); assert_eq!(health.total_collateral_value, Uint128::new(2801u128)); assert_eq!(health.total_debt_value, Uint128::new(2505u128)); @@ -373,7 +374,7 @@ fn liquidate_with_reclaiming() { price: Decimal::from_atomics(55u128, 1).unwrap(), }); - let health = mock.query_health(&liquidatee_account_id); + let health = mock.query_health(&liquidatee_account_id, AccountKind::Default); assert!(health.liquidatable); assert_eq!(health.total_collateral_value, Uint128::new(624u128)); assert_eq!(health.total_debt_value, Uint128::new(555u128)); diff --git a/contracts/credit-manager/tests/test_repay.rs b/contracts/credit-manager/tests/test_repay.rs index 9db6c434c..2de2a10b3 100644 --- a/contracts/credit-manager/tests/test_repay.rs +++ b/contracts/credit-manager/tests/test_repay.rs @@ -85,6 +85,7 @@ fn raises_when_repaying_what_is_not_owed() { liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), whitelisted: true, + hls: None, }; let user_a = Addr::unchecked("user_a"); @@ -143,6 +144,7 @@ fn raises_when_not_enough_assets_to_repay() { liquidation_threshold: Decimal::from_atomics(85u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(1u128, 1).unwrap(), whitelisted: true, + hls: None, }; let user = Addr::unchecked("user"); diff --git a/contracts/credit-manager/tests/test_repay_from_wallet.rs b/contracts/credit-manager/tests/test_repay_from_wallet.rs index af7daadb1..89524310e 100644 --- a/contracts/credit-manager/tests/test_repay_from_wallet.rs +++ b/contracts/credit-manager/tests/test_repay_from_wallet.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{coin, coins, Addr, Uint128}; use cw_utils::PaymentError; -use mars_params::types::AssetParamsUpdate::AddOrUpdate; +use mars_params::msg::AssetParamsUpdate::AddOrUpdate; use mars_rover::{ error::ContractError, msg::execute::Action::{Borrow, Deposit}, @@ -184,7 +184,7 @@ fn delisted_assets_can_be_repaid() { }); let params = mock.query_asset_params(&coin_info.denom); - assert!(!params.rover.whitelisted); + assert!(!params.credit_manager.whitelisted); // There should be no error in repaying for this asset mock.repay_from_wallet(&repayer, &account_id, &[coin(12, coin_info.denom)]).unwrap(); diff --git a/contracts/credit-manager/tests/test_update_config.rs b/contracts/credit-manager/tests/test_update_config.rs index 9ff14f731..440e962c8 100644 --- a/contracts/credit-manager/tests/test_update_config.rs +++ b/contracts/credit-manager/tests/test_update_config.rs @@ -52,7 +52,7 @@ fn update_config_works_with_full_config() { mock.update_config( &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), ConfigUpdates { - account_nft: Some(new_nft_contract.to_string()), + account_nft: Some(new_nft_contract.clone()), oracle: Some(new_oracle.clone()), red_bank: Some(new_red_bank.clone()), max_unlocking_positions: Some(new_unlocking_max), @@ -65,7 +65,7 @@ fn update_config_works_with_full_config() { let new_config = mock.query_config(); - assert_eq!(new_config.account_nft, Some(new_nft_contract.to_string())); + assert_eq!(new_config.account_nft, Some(new_nft_contract.address().clone())); assert_ne!(new_config.account_nft, original_config.account_nft); assert_eq!( @@ -103,7 +103,7 @@ fn update_config_works_with_some_config() { mock.update_config( &Addr::unchecked(original_config.ownership.owner.clone().unwrap()), ConfigUpdates { - account_nft: Some(new_nft_contract.to_string()), + account_nft: Some(new_nft_contract.clone()), max_unlocking_positions: Some(new_max_unlocking), ..Default::default() }, @@ -113,7 +113,7 @@ fn update_config_works_with_some_config() { let new_config = mock.query_config(); // Changed configs - assert_eq!(new_config.account_nft, Some(new_nft_contract.to_string())); + assert_eq!(new_config.account_nft, Some(new_nft_contract.address().clone())); assert_ne!(new_config.account_nft, original_config.account_nft); assert_eq!(new_config.max_unlocking_positions, new_max_unlocking); diff --git a/contracts/credit-manager/tests/test_utilization_query.rs b/contracts/credit-manager/tests/test_utilization_query.rs index 70d6a4b38..1b1d6f9bb 100644 --- a/contracts/credit-manager/tests/test_utilization_query.rs +++ b/contracts/credit-manager/tests/test_utilization_query.rs @@ -44,6 +44,7 @@ fn utilization_if_cap_is_base_denom() { liquidation_threshold: Decimal::from_str("0.7").unwrap(), liquidation_bonus: Decimal::from_str("0.15").unwrap(), whitelisted: true, + hls: None, }; let leverage_vault = VaultTestInfo { @@ -54,6 +55,7 @@ fn utilization_if_cap_is_base_denom() { max_ltv: Decimal::from_str("0.6").unwrap(), liquidation_threshold: Decimal::from_str("0.7").unwrap(), whitelisted: true, + hls: None, }; let mut mock = MockEnv::new() @@ -111,6 +113,7 @@ fn utilization_in_other_denom() { max_ltv: Decimal::from_str("0.6").unwrap(), liquidation_threshold: Decimal::from_str("0.7").unwrap(), whitelisted: true, + hls: None, }; let user = Addr::unchecked("user"); diff --git a/contracts/credit-manager/tests/test_vault_enter.rs b/contracts/credit-manager/tests/test_vault_enter.rs index 1da477e6d..0dc8cc39e 100644 --- a/contracts/credit-manager/tests/test_vault_enter.rs +++ b/contracts/credit-manager/tests/test_vault_enter.rs @@ -84,6 +84,7 @@ fn vault_is_whitelisted() { max_ltv: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), whitelisted: false, + hls: None, }; let user = Addr::unchecked("user"); diff --git a/contracts/credit-manager/tests/test_zap_withdraw.rs b/contracts/credit-manager/tests/test_zap_withdraw.rs index 57df796ce..c7ce12349 100644 --- a/contracts/credit-manager/tests/test_zap_withdraw.rs +++ b/contracts/credit-manager/tests/test_zap_withdraw.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{coin, Addr, Coin, OverflowError, OverflowOperation::Sub, Uint128}; -use mars_params::types::AssetParamsUpdate::AddOrUpdate; +use mars_params::msg::AssetParamsUpdate::AddOrUpdate; use mars_rover::{ error::ContractError as RoverError, msg::execute::{ diff --git a/contracts/health/src/compute.rs b/contracts/health/src/compute.rs index 59a99f299..01dbbe4e0 100644 --- a/contracts/health/src/compute.rs +++ b/contracts/health/src/compute.rs @@ -2,7 +2,9 @@ use std::collections::HashMap; use cosmwasm_std::{Deps, StdResult}; use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; -use mars_rover_health_types::{HealthError::ContractNotSet, HealthResponse, HealthResult}; +use mars_rover_health_types::{ + AccountKind, HealthError::ContractNotSet, HealthResponse, HealthResult, +}; use crate::{ querier::HealthQuerier, @@ -12,7 +14,11 @@ use crate::{ /// Uses `mars-rover-health-computer` which is a data agnostic package given /// it's compiled to .wasm and shared with the frontend. /// This function queries all necessary data to pass to `HealthComputer`. -pub fn compute_health(deps: Deps, account_id: &str) -> HealthResult { +pub fn compute_health( + deps: Deps, + account_id: &str, + kind: AccountKind, +) -> HealthResult { let credit_manager_addr = CREDIT_MANAGER .may_load(deps.storage)? .ok_or(ContractNotSet("credit_manger".to_string()))?; @@ -64,6 +70,7 @@ pub fn compute_health(deps: Deps, account_id: &str) -> HealthResult HealthResult { let res = match msg { QueryMsg::Health { account_id, - } => to_binary(&compute_health(deps, &account_id)?), + kind, + } => to_binary(&compute_health(deps, &account_id, kind)?), QueryMsg::Config {} => to_binary(&query_config(deps)?), }; res.map_err(Into::into) diff --git a/contracts/health/src/querier.rs b/contracts/health/src/querier.rs index 9ee08d600..2d1b0ee3f 100644 --- a/contracts/health/src/querier.rs +++ b/contracts/health/src/querier.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{Addr, QuerierWrapper}; -use mars_params::{msg::QueryMsg as ParamsQueryMsg, types::VaultConfig}; +use mars_params::{msg::QueryMsg as ParamsQueryMsg, types::vault::VaultConfig}; use mars_rover::{ adapters::{oracle::Oracle, params::Params, vault::Vault}, msg::query::{ConfigResponse, Positions, QueryMsg as CmQueryMsg}, diff --git a/contracts/health/tests/helpers/defaults.rs b/contracts/health/tests/helpers/defaults.rs new file mode 100644 index 000000000..b4004eb9e --- /dev/null +++ b/contracts/health/tests/helpers/defaults.rs @@ -0,0 +1,29 @@ +use std::str::FromStr; + +use cosmwasm_std::Decimal; +use mars_params::types::{ + asset::{AssetParamsUnchecked, CmSettings, RedBankSettings}, + hls::HlsParamsUnchecked, +}; + +pub fn default_asset_params(denom: &str) -> AssetParamsUnchecked { + AssetParamsUnchecked { + denom: denom.to_string(), + credit_manager: CmSettings { + whitelisted: true, + hls: Some(HlsParamsUnchecked { + max_loan_to_value: Decimal::from_str("0.8").unwrap(), + liquidation_threshold: Decimal::from_str("0.9").unwrap(), + correlations: vec![], + }), + }, + red_bank: RedBankSettings { + deposit_enabled: false, + borrow_enabled: false, + deposit_cap: Default::default(), + }, + max_loan_to_value: Decimal::from_str("0.4523").unwrap(), + liquidation_threshold: Decimal::from_str("0.5").unwrap(), + liquidation_bonus: Decimal::from_atomics(9u128, 2).unwrap(), + } +} diff --git a/contracts/health/tests/helpers/mock_env.rs b/contracts/health/tests/helpers/mock_env.rs index 7a223accc..d259ccaec 100644 --- a/contracts/health/tests/helpers/mock_env.rs +++ b/contracts/health/tests/helpers/mock_env.rs @@ -9,13 +9,16 @@ use mars_mock_oracle::msg::{CoinPrice, ExecuteMsg::ChangePrice}; use mars_mock_vault::contract::STARTING_VAULT_SHARES; use mars_params::{ msg::{ + AssetParamsUpdate, ExecuteMsg::{UpdateAssetParams, UpdateVaultConfig}, - QueryMsg as ParamsQueryMsg, + QueryMsg as ParamsQueryMsg, VaultConfigUpdate, }, - types::{AssetParamsUpdate, VaultConfig, VaultConfigUpdate}, + types::vault::VaultConfig, }; use mars_rover::{adapters::vault::VaultUnchecked, msg::query::Positions}; -use mars_rover_health_types::{ConfigResponse, ExecuteMsg::UpdateConfig, HealthResponse, QueryMsg}; +use mars_rover_health_types::{ + AccountKind, ConfigResponse, ExecuteMsg::UpdateConfig, HealthResponse, QueryMsg, +}; use crate::helpers::MockEnvBuilder; @@ -45,11 +48,12 @@ impl MockEnv { } } - pub fn query_health(&self, account_id: &str) -> StdResult { + pub fn query_health(&self, account_id: &str, kind: AccountKind) -> StdResult { self.app.wrap().query_wasm_smart( self.health_contract.clone(), &QueryMsg::Health { account_id: account_id.to_string(), + kind, }, ) } diff --git a/contracts/health/tests/helpers/mock_env_builder.rs b/contracts/health/tests/helpers/mock_env_builder.rs index f5c8674f1..1125b07d9 100644 --- a/contracts/health/tests/helpers/mock_env_builder.rs +++ b/contracts/health/tests/helpers/mock_env_builder.rs @@ -9,8 +9,11 @@ use mars_mock_oracle::msg::InstantiateMsg as OracleInstantiateMsg; use mars_mock_vault::msg::InstantiateMsg as VaultInstantiateMsg; use mars_owner::OwnerResponse; use mars_params::{ - msg::{ExecuteMsg::UpdateVaultConfig, InstantiateMsg as ParamsInstantiateMsg}, - types::{VaultConfigUnchecked, VaultConfigUpdate::AddOrUpdate}, + msg::{ + ExecuteMsg::UpdateVaultConfig, InstantiateMsg as ParamsInstantiateMsg, + VaultConfigUpdate::AddOrUpdate, + }, + types::{hls::HlsParamsUnchecked, vault::VaultConfigUnchecked}, }; use mars_rover::{adapters::oracle::OracleUnchecked, msg::query::ConfigResponse}; use mars_rover_health_types::{ExecuteMsg::UpdateConfig, InstantiateMsg}; @@ -179,9 +182,14 @@ impl MockEnvBuilder { config: VaultConfigUnchecked { addr: vault.to_string(), deposit_cap: coin(10000000u128, "uusdc"), - max_loan_to_value: Decimal::from_atomics(4u128, 1).unwrap(), - liquidation_threshold: Decimal::from_atomics(44u128, 2).unwrap(), + max_loan_to_value: Decimal::from_str("0.4").unwrap(), + liquidation_threshold: Decimal::from_str("0.44").unwrap(), whitelisted: true, + hls: Some(HlsParamsUnchecked { + max_loan_to_value: Decimal::from_str("0.6").unwrap(), + liquidation_threshold: Decimal::from_str("0.7").unwrap(), + correlations: vec![], + }), }, }), &[], diff --git a/contracts/health/tests/helpers/mod.rs b/contracts/health/tests/helpers/mod.rs index 98f82a33a..105a548ee 100644 --- a/contracts/health/tests/helpers/mod.rs +++ b/contracts/health/tests/helpers/mod.rs @@ -1,5 +1,6 @@ -pub use self::{mock_contracts::*, mock_env::*, mock_env_builder::*}; +pub use self::{defaults::*, mock_contracts::*, mock_env::*, mock_env_builder::*}; +mod defaults; mod mock_contracts; mod mock_env; mod mock_env_builder; diff --git a/contracts/health/tests/test_compute_health.rs b/contracts/health/tests/test_compute_health.rs index 0e37086b3..afee3488d 100644 --- a/contracts/health/tests/test_compute_health.rs +++ b/contracts/health/tests/test_compute_health.rs @@ -1,9 +1,12 @@ use std::str::FromStr; use cosmwasm_std::{Coin, Decimal, StdError, Uint128}; -use mars_params::types::{ - AssetParams, AssetParamsUpdate::AddOrUpdate, HighLeverageStrategyParams, RedBankSettings, - RoverSettings, VaultConfigUpdate, +use mars_params::{ + msg::{AssetParamsUpdate::AddOrUpdate, VaultConfigUpdate}, + types::{ + asset::{AssetParamsUnchecked, CmSettings, RedBankSettings}, + hls::HlsParamsUnchecked, + }, }; use mars_rover::{ adapters::vault::{ @@ -12,6 +15,7 @@ use mars_rover::{ }, msg::query::Positions, }; +use mars_rover_health_types::AccountKind; use crate::helpers::MockEnv; @@ -20,7 +24,7 @@ pub mod helpers; #[test] fn raises_when_credit_manager_not_set() { let mock = MockEnv::new().skip_cm_config().build().unwrap(); - let err: StdError = mock.query_health("xyz").unwrap_err(); + let err: StdError = mock.query_health("xyz", AccountKind::Default).unwrap_err(); assert_eq!( err, StdError::generic_err( @@ -32,7 +36,7 @@ fn raises_when_credit_manager_not_set() { #[test] fn raises_when_params_contract_not_set() { let mock = MockEnv::new().skip_params_config().build().unwrap(); - let err: StdError = mock.query_health("xyz").unwrap_err(); + let err: StdError = mock.query_health("xyz", AccountKind::Default).unwrap_err(); assert_eq!( err, StdError::generic_err( @@ -44,7 +48,7 @@ fn raises_when_params_contract_not_set() { #[test] fn raises_with_non_existent_account_id() { let mock = MockEnv::new().build().unwrap(); - let err: StdError = mock.query_health("xyz").unwrap_err(); + let err: StdError = mock.query_health("xyz", AccountKind::Default).unwrap_err(); assert_eq!( err, StdError::generic_err( @@ -70,7 +74,7 @@ fn computes_correct_position_with_zero_assets() { }, ); - let health = mock.query_health(account_id).unwrap(); + let health = mock.query_health(account_id, AccountKind::Default).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, Uint128::zero()); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); @@ -118,14 +122,15 @@ fn adds_vault_base_denoms_to_oracle_and_red_bank() { let liquidation_threshold = Decimal::from_atomics(5u128, 1).unwrap(); let update = AddOrUpdate { - params: AssetParams { + params: AssetParamsUnchecked { denom: vault_base_token.to_string(), - rover: RoverSettings { + credit_manager: CmSettings { whitelisted: true, - hls: HighLeverageStrategyParams { + hls: Some(HlsParamsUnchecked { max_loan_to_value: Decimal::from_str("0.8").unwrap(), liquidation_threshold: Decimal::from_str("0.9").unwrap(), - }, + correlations: vec![], + }), }, red_bank: RedBankSettings { deposit_enabled: false, @@ -142,7 +147,7 @@ fn adds_vault_base_denoms_to_oracle_and_red_bank() { mock.set_price(vault_base_token, Decimal::one()); - let health = mock.query_health(account_id).unwrap(); + let health = mock.query_health(account_id, AccountKind::Default).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, unlocking_amount); assert_eq!( @@ -171,14 +176,15 @@ fn whitelisted_coins_work() { let liquidation_threshold = Decimal::from_atomics(5u128, 1).unwrap(); let liquidation_bonus = Decimal::from_atomics(9u128, 2).unwrap(); - let mut asset_params = AssetParams { + let mut asset_params = AssetParamsUnchecked { denom: umars.to_string(), - rover: RoverSettings { + credit_manager: CmSettings { whitelisted: false, - hls: HighLeverageStrategyParams { + hls: Some(HlsParamsUnchecked { max_loan_to_value: Decimal::from_str("0.8").unwrap(), liquidation_threshold: Decimal::from_str("0.9").unwrap(), - }, + correlations: vec![], + }), }, red_bank: RedBankSettings { deposit_enabled: false, @@ -213,7 +219,7 @@ fn whitelisted_coins_work() { }, ); - let health = mock.query_health(account_id).unwrap(); + let health = mock.query_health(account_id, AccountKind::Default).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, deposit_amount); // price of 1 assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); // coin not in whitelist @@ -227,11 +233,11 @@ fn whitelisted_coins_work() { assert!(!health.above_max_ltv); // Add to whitelist - asset_params.rover.whitelisted = true; + asset_params.credit_manager.whitelisted = true; mock.update_asset_params(AddOrUpdate { params: asset_params, }); - let health = mock.query_health(account_id).unwrap(); + let health = mock.query_health(account_id, AccountKind::Default).unwrap(); // Now reflects deposit value assert_eq!( health.max_ltv_adjusted_collateral, @@ -268,14 +274,15 @@ fn vault_whitelist_affects_max_ltv() { ); let update = AddOrUpdate { - params: AssetParams { + params: AssetParamsUnchecked { denom: vault_base_token.to_string(), - rover: RoverSettings { + credit_manager: CmSettings { whitelisted: true, - hls: HighLeverageStrategyParams { + hls: Some(HlsParamsUnchecked { max_loan_to_value: Decimal::from_str("0.8").unwrap(), liquidation_threshold: Decimal::from_str("0.9").unwrap(), - }, + correlations: vec![], + }), }, red_bank: RedBankSettings { deposit_enabled: false, @@ -294,7 +301,7 @@ fn vault_whitelist_affects_max_ltv() { let mut vault_config = mock.query_vault_config(&vault.into()); - let health = mock.query_health(account_id).unwrap(); + let health = mock.query_health(account_id, AccountKind::Default).unwrap(); assert_eq!(health.total_debt_value, Uint128::zero()); assert_eq!(health.total_collateral_value, base_token_amount); assert_eq!( @@ -317,6 +324,6 @@ fn vault_whitelist_affects_max_ltv() { config: vault_config.into(), }); - let health = mock.query_health(account_id).unwrap(); + let health = mock.query_health(account_id, AccountKind::Default).unwrap(); assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); } diff --git a/contracts/health/tests/test_hls.rs b/contracts/health/tests/test_hls.rs new file mode 100644 index 000000000..21e90b173 --- /dev/null +++ b/contracts/health/tests/test_hls.rs @@ -0,0 +1,77 @@ +use std::str::FromStr; + +use cosmwasm_std::{Decimal, Uint128}; +use mars_params::msg::AssetParamsUpdate::AddOrUpdate; +use mars_rover::{ + adapters::vault::{Vault, VaultAmount, VaultPosition, VaultPositionAmount}, + msg::query::{DebtAmount, Positions}, +}; +use mars_rover_health_types::AccountKind; + +use crate::helpers::{default_asset_params, MockEnv}; + +pub mod helpers; + +#[test] +fn hls_account_kind_passed_along() { + let mut mock = MockEnv::new().build().unwrap(); + + let vault_base_token = "base_token_abc"; + let debt_token = "umars"; + let account_id = "123"; + + let vault_token_amount = Uint128::new(1_000_000); + let base_token_amount = Uint128::new(100); + + mock.deposit_into_vault(base_token_amount); + + let vault = Vault::new(mock.vault_contract.clone()); + + let positions = Positions { + account_id: account_id.to_string(), + deposits: vec![], + debts: vec![DebtAmount { + denom: debt_token.to_string(), + shares: Uint128::new(10_000_000), + amount: Uint128::new(50), + }], + lends: vec![], + vaults: vec![VaultPosition { + vault: vault.clone(), + amount: VaultPositionAmount::Unlocked(VaultAmount::new(vault_token_amount)), + }], + }; + mock.set_positions_response(account_id, &positions); + mock.set_price(debt_token, Decimal::one()); + mock.update_asset_params(AddOrUpdate { + params: default_asset_params(debt_token), + }); + + mock.update_asset_params(AddOrUpdate { + params: default_asset_params(vault_base_token), + }); + + mock.set_price(vault_base_token, Decimal::one()); + + let vault_config = mock.query_vault_config(&vault.into()); + + let health = mock.query_health(account_id, AccountKind::HighLeveredStrategy).unwrap(); + assert_eq!(health.total_debt_value, positions.debts.first().unwrap().amount); + assert_eq!(health.total_collateral_value, base_token_amount); + assert_eq!( + health.max_ltv_adjusted_collateral, + base_token_amount + .checked_mul_floor(vault_config.hls.as_ref().unwrap().max_loan_to_value) + .unwrap() + ); + assert_eq!( + health.liquidation_threshold_adjusted_collateral, + base_token_amount + .checked_mul_floor(vault_config.hls.unwrap().liquidation_threshold) + .unwrap() + ); + assert_eq!(health.max_ltv_health_factor, Some(Decimal::from_str("1.2").unwrap())); // Default would have been 0.8 + assert_eq!(health.liquidation_health_factor, Some(Decimal::from_str("1.4").unwrap())); // Default would have been 1.2 + assert!(!health.above_max_ltv); // Default would have been above max_ltv + assert!(!health.liquidatable); +} diff --git a/contracts/mock-health/src/contract.rs b/contracts/mock-health/src/contract.rs index 8e6bb56dd..c94e9d3f9 100644 --- a/contracts/mock-health/src/contract.rs +++ b/contracts/mock-health/src/contract.rs @@ -3,7 +3,7 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{ to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, }; -use mars_rover_health_types::{HealthResponse, HealthResult, QueryMsg}; +use mars_rover_health_types::{AccountKind, HealthResponse, HealthResult, QueryMsg}; use crate::{msg::ExecuteMsg, state::HEALTH_RESPONSES}; @@ -18,16 +18,18 @@ pub fn execute(deps: DepsMut, _: Env, _: MessageInfo, msg: ExecuteMsg) -> Health ExecuteMsg::SetHealthResponse { account_id, response, - } => set_health_response(deps, account_id, response), + kind, + } => set_health_response(deps, account_id, kind, response), } } pub fn set_health_response( deps: DepsMut, account_id: String, + kind: AccountKind, response: HealthResponse, ) -> HealthResult { - HEALTH_RESPONSES.save(deps.storage, &account_id, &response)?; + HEALTH_RESPONSES.save(deps.storage, (&account_id, &kind.to_string()), &response)?; Ok(Response::new()) } @@ -36,12 +38,13 @@ pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> HealthResult { let res = match msg { QueryMsg::Health { account_id, - } => to_binary(&query_health(deps, &account_id)?), + kind, + } => to_binary(&query_health(deps, &account_id, kind)?), _ => unimplemented!("query msg not supported"), }; res.map_err(Into::into) } -pub fn query_health(deps: Deps, account_id: &str) -> StdResult { - HEALTH_RESPONSES.load(deps.storage, account_id) +pub fn query_health(deps: Deps, account_id: &str, kind: AccountKind) -> StdResult { + HEALTH_RESPONSES.load(deps.storage, (account_id, &kind.to_string())) } diff --git a/contracts/mock-health/src/msg.rs b/contracts/mock-health/src/msg.rs index 070ba6435..8f224d78f 100644 --- a/contracts/mock-health/src/msg.rs +++ b/contracts/mock-health/src/msg.rs @@ -1,10 +1,11 @@ use cosmwasm_schema::cw_serde; -use mars_rover_health_types::HealthResponse; +use mars_rover_health_types::{AccountKind, HealthResponse}; #[cw_serde] pub enum ExecuteMsg { SetHealthResponse { account_id: String, + kind: AccountKind, response: HealthResponse, }, } diff --git a/contracts/mock-health/src/state.rs b/contracts/mock-health/src/state.rs index 1dcd921a3..21d6f69cb 100644 --- a/contracts/mock-health/src/state.rs +++ b/contracts/mock-health/src/state.rs @@ -1,4 +1,4 @@ use cw_storage_plus::Map; use mars_rover_health_types::HealthResponse; -pub const HEALTH_RESPONSES: Map<&str, HealthResponse> = Map::new("health_responses"); // Map +pub const HEALTH_RESPONSES: Map<(&str, &str), HealthResponse> = Map::new("health_responses"); // Map<(account_id, AccountKind string), HealthResponse> diff --git a/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs b/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs index 382502d9f..d802d5a14 100644 --- a/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs +++ b/contracts/v3-zapper/osmosis/tests/helpers/mock_env.rs @@ -14,8 +14,7 @@ use osmosis_test_tube::{ cosmrs::proto::{ cosmos::bank::v1beta1::QueryBalanceRequest, cosmwasm::wasm::v1::MsgExecuteContractResponse, }, - Account, Bank, ConcentratedLiquidity, Module, OsmosisTestApp, RunnerExecuteResult, - SigningAccount, TokenFactory, Wasm, + Account, Bank, Module, OsmosisTestApp, RunnerExecuteResult, SigningAccount, TokenFactory, Wasm, }; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); diff --git a/packages/health-computer/src/data_types.rs b/packages/health-computer/src/data_types.rs index 32c550f74..c2396fd57 100644 --- a/packages/health-computer/src/data_types.rs +++ b/packages/health-computer/src/data_types.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Decimal, Uint128}; -use mars_params::types::{AssetParams, VaultConfig}; +use mars_params::types::{asset::AssetParams, vault::VaultConfig}; use mars_rover::adapters::vault::VaultPositionValue; /// Used as storage when trying to compute Health diff --git a/packages/health-computer/src/health_computer.rs b/packages/health-computer/src/health_computer.rs index 33cbfbf95..4be2ccb0f 100644 --- a/packages/health-computer/src/health_computer.rs +++ b/packages/health-computer/src/health_computer.rs @@ -1,10 +1,15 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Decimal, Uint128}; -use mars_params::types::AssetParams; +use mars_params::types::{ + asset::{AssetParams, CmSettings}, + vault::VaultConfig, +}; use mars_rover::{msg::query::Positions, traits::Coins}; use mars_rover_health_types::{ - Health, - HealthError::{MissingParams, MissingPrice, MissingVaultConfig, MissingVaultValues}, + AccountKind, Health, + HealthError::{ + MissingHLSParams, MissingParams, MissingPrice, MissingVaultConfig, MissingVaultValues, + }, HealthResult, }; @@ -14,6 +19,7 @@ use crate::{CollateralValue, DenomsData, VaultsData}; /// For this reason, it uses a dependency-injection-like pattern where all required data is needed up front. #[cw_serde] pub struct HealthComputer { + pub kind: AccountKind, pub positions: Positions, pub denoms_data: DenomsData, pub vaults_data: VaultsData, @@ -98,15 +104,24 @@ impl HealthComputer { total_collateral_value = total_collateral_value.checked_add(coin_value)?; let AssetParams { - rover, + credit_manager: + CmSettings { + whitelisted, + hls, + }, max_loan_to_value, liquidation_threshold, .. } = self.denoms_data.params.get(&c.denom).ok_or(MissingParams(c.denom.clone()))?; // If coin has been de-listed, drop MaxLTV to zero - let checked_max_ltv = if rover.whitelisted { - *max_loan_to_value + let checked_max_ltv = if *whitelisted { + match self.kind { + AccountKind::Default => *max_loan_to_value, + AccountKind::HighLeveredStrategy => { + hls.as_ref().ok_or(MissingHLSParams(c.denom.clone()))?.max_loan_to_value + } + } } else { Decimal::zero() }; @@ -114,7 +129,13 @@ impl HealthComputer { max_ltv_adjusted_collateral = max_ltv_adjusted_collateral.checked_add(max_ltv_adjusted)?; - let liq_adjusted = coin_value.checked_mul_floor(*liquidation_threshold)?; + let checked_liquidation_threshold = match self.kind { + AccountKind::Default => *liquidation_threshold, + AccountKind::HighLeveredStrategy => { + hls.as_ref().ok_or(MissingHLSParams(c.denom.clone()))?.liquidation_threshold + } + }; + let liq_adjusted = coin_value.checked_mul_floor(checked_liquidation_threshold)?; liquidation_threshold_adjusted_collateral = liquidation_threshold_adjusted_collateral.checked_add(liq_adjusted)?; } @@ -131,34 +152,42 @@ impl HealthComputer { let mut liquidation_threshold_adjusted_collateral = Uint128::zero(); for v in &self.positions.vaults { + // Step 1: Calculate Vault coin values let values = self .vaults_data .vault_values .get(&v.vault.address) .ok_or(MissingVaultValues(v.vault.address.to_string()))?; - total_collateral_value = total_collateral_value.checked_add(values.total_value()?)?; + total_collateral_value = total_collateral_value.checked_add(values.vault_coin.value)?; - let config = self + let VaultConfig { + addr, + max_loan_to_value, + liquidation_threshold, + whitelisted, + hls, + .. + } = self .vaults_data .vault_configs .get(&v.vault.address) .ok_or(MissingVaultConfig(v.vault.address.to_string()))?; - // If vault or base token has been de-listed, drop MaxLTV to zero - let AssetParams { - rover, - max_loan_to_value, - liquidation_threshold, - .. - } = self + let base_params = self .denoms_data .params .get(&values.base_coin.denom) .ok_or(MissingParams(values.base_coin.denom.clone()))?; - let base_token_whitelisted = rover.whitelisted; - let checked_vault_max_ltv = if config.whitelisted && base_token_whitelisted { - config.max_loan_to_value + + // If vault or base token has been de-listed, drop MaxLTV to zero + let checked_vault_max_ltv = if *whitelisted && base_params.credit_manager.whitelisted { + match self.kind { + AccountKind::Default => *max_loan_to_value, + AccountKind::HighLeveredStrategy => { + hls.as_ref().ok_or(MissingHLSParams(addr.to_string()))?.max_loan_to_value + } + } } else { Decimal::zero() }; @@ -169,30 +198,31 @@ impl HealthComputer { .checked_mul_floor(checked_vault_max_ltv)? .checked_add(max_ltv_adjusted_collateral)?; - liquidation_threshold_adjusted_collateral = values - .vault_coin - .value - .checked_mul_floor(config.liquidation_threshold)? - .checked_add(liquidation_threshold_adjusted_collateral)?; - - // If base token has been de-listed, drop MaxLTV to zero - let checked_base_max_ltv = if base_token_whitelisted { - *max_loan_to_value - } else { - Decimal::zero() + let checked_liquidation_threshold = match self.kind { + AccountKind::Default => *liquidation_threshold, + AccountKind::HighLeveredStrategy => { + hls.as_ref().ok_or(MissingHLSParams(addr.to_string()))?.liquidation_threshold + } }; - max_ltv_adjusted_collateral = values - .base_coin - .value - .checked_mul_floor(checked_base_max_ltv)? - .checked_add(max_ltv_adjusted_collateral)?; - liquidation_threshold_adjusted_collateral = values - .base_coin + .vault_coin .value - .checked_mul_floor(*liquidation_threshold)? + .checked_mul_floor(checked_liquidation_threshold)? .checked_add(liquidation_threshold_adjusted_collateral)?; + + // Step 2: Calculate Base coin values + let res = self.calculate_coins_value(&[Coin { + denom: values.base_coin.denom.clone(), + amount: v.amount.unlocking().total(), + }])?; + total_collateral_value = + total_collateral_value.checked_add(res.total_collateral_value)?; + max_ltv_adjusted_collateral = + max_ltv_adjusted_collateral.checked_add(res.max_ltv_adjusted_collateral)?; + liquidation_threshold_adjusted_collateral = + liquidation_threshold_adjusted_collateral + .checked_add(res.liquidation_threshold_adjusted_collateral)?; } Ok(CollateralValue { diff --git a/packages/health-computer/tests/helpers/mock_coin_info.rs b/packages/health-computer/tests/helpers/mock_coin_info.rs index cf958264d..f765d0702 100644 --- a/packages/health-computer/tests/helpers/mock_coin_info.rs +++ b/packages/health-computer/tests/helpers/mock_coin_info.rs @@ -1,6 +1,11 @@ +use std::str::FromStr; + use cosmwasm_schema::cw_serde; use cosmwasm_std::Decimal; -use mars_params::types::{AssetParams, HighLeverageStrategyParams, RedBankSettings, RoverSettings}; +use mars_params::types::{ + asset::{AssetParams, CmSettings, RedBankSettings}, + hls::{HlsAssetType, HlsParams}, +}; #[cw_serde] pub struct CoinInfo { @@ -19,12 +24,9 @@ pub fn umars_info() -> CoinInfo { max_loan_to_value: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(84u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), - rover: RoverSettings { + credit_manager: CmSettings { whitelisted: true, - hls: HighLeverageStrategyParams { - max_loan_to_value: Default::default(), - liquidation_threshold: Default::default(), - }, + hls: None, }, red_bank: RedBankSettings { deposit_enabled: true, @@ -45,12 +47,9 @@ pub fn udai_info() -> CoinInfo { max_loan_to_value: Decimal::from_atomics(85u128, 2).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), - rover: RoverSettings { + credit_manager: CmSettings { whitelisted: true, - hls: HighLeverageStrategyParams { - max_loan_to_value: Default::default(), - liquidation_threshold: Default::default(), - }, + hls: None, }, red_bank: RedBankSettings { deposit_enabled: true, @@ -71,12 +70,9 @@ pub fn uluna_info() -> CoinInfo { max_loan_to_value: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(78u128, 2).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), - rover: RoverSettings { + credit_manager: CmSettings { whitelisted: true, - hls: HighLeverageStrategyParams { - max_loan_to_value: Default::default(), - liquidation_threshold: Default::default(), - }, + hls: None, }, red_bank: RedBankSettings { deposit_enabled: true, @@ -97,12 +93,15 @@ pub fn ustars_info() -> CoinInfo { max_loan_to_value: Decimal::from_atomics(6u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(7u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(15u128, 2).unwrap(), - rover: RoverSettings { + credit_manager: CmSettings { whitelisted: true, - hls: HighLeverageStrategyParams { - max_loan_to_value: Default::default(), - liquidation_threshold: Default::default(), - }, + hls: Some(HlsParams { + max_loan_to_value: Decimal::from_str("0.75").unwrap(), + liquidation_threshold: Decimal::from_str("0.8").unwrap(), + correlations: vec![HlsAssetType::Coin { + denom: "stStars".to_string(), + }], + }), }, red_bank: RedBankSettings { deposit_enabled: true, @@ -123,12 +122,9 @@ pub fn ujuno_info() -> CoinInfo { max_loan_to_value: Decimal::from_atomics(8u128, 1).unwrap(), liquidation_threshold: Decimal::from_atomics(9u128, 1).unwrap(), liquidation_bonus: Decimal::from_atomics(12u128, 2).unwrap(), - rover: RoverSettings { + credit_manager: CmSettings { whitelisted: true, - hls: HighLeverageStrategyParams { - max_loan_to_value: Default::default(), - liquidation_threshold: Default::default(), - }, + hls: None, }, red_bank: RedBankSettings { deposit_enabled: true, diff --git a/packages/health-computer/tests/test_health_scenarios.rs b/packages/health-computer/tests/test_health_scenarios.rs index 0d03eb4af..a4e6f5c5c 100644 --- a/packages/health-computer/tests/test_health_scenarios.rs +++ b/packages/health-computer/tests/test_health_scenarios.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, ops::Add, str::FromStr}; use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; -use mars_params::types::VaultConfig; +use mars_params::types::vault::VaultConfig; use mars_rover::{ adapters::vault::{ CoinValue, LockingVaultAmount, UnlockingPositions, Vault, VaultAmount, VaultPosition, @@ -10,6 +10,7 @@ use mars_rover::{ msg::query::{DebtAmount, LentAmount, Positions}, }; use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; +use mars_rover_health_types::AccountKind; use crate::helpers::{udai_info, ujuno_info, uluna_info, umars_info, ustars_info}; @@ -36,6 +37,7 @@ fn only_assets_with_no_debts() { let deposit_amount = Uint128::new(300); let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![Coin { @@ -96,6 +98,7 @@ fn terra_ragnarok() { let borrow_amount = Uint128::new(3); let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![Coin { @@ -154,6 +157,7 @@ fn terra_ragnarok() { }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![Coin { @@ -214,6 +218,7 @@ fn ltv_and_lqdt_adjusted_values() { let borrow_amount = Uint128::new(49); let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![ @@ -322,6 +327,7 @@ fn debt_value() { let borrowed_amount_stars = Uint128::new(30); let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![ @@ -443,6 +449,7 @@ fn above_max_ltv_below_liq_threshold() { }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], @@ -497,6 +504,7 @@ fn liquidatable() { }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], @@ -541,7 +549,7 @@ fn rover_whitelist_influences_max_ltv() { let umars = umars_info(); let mut udai = udai_info(); - udai.params.rover.whitelisted = false; + udai.params.credit_manager.whitelisted = false; let denoms_data = DenomsData { prices: HashMap::from([ @@ -560,6 +568,7 @@ fn rover_whitelist_influences_max_ltv() { }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], @@ -641,11 +650,13 @@ fn unlocked_vault() { max_loan_to_value: Decimal::from_str("0.4").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), whitelisted: true, + hls: None, }, )]), }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], @@ -730,11 +741,13 @@ fn locked_vault() { max_loan_to_value: Decimal::from_str("0.4").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), whitelisted: true, + hls: None, }, )]), }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], @@ -822,11 +835,13 @@ fn locked_vault_with_unlocking_positions() { max_loan_to_value: Decimal::from_str("0.4").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), whitelisted: true, + hls: None, }, )]), }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], @@ -923,11 +938,13 @@ fn vault_is_not_whitelisted() { max_loan_to_value: Decimal::from_str("0.4").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), whitelisted: false, + hls: None, }, )]), }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], @@ -977,7 +994,7 @@ fn vault_base_token_is_not_whitelisted() { let udai = udai_info(); let mut ujuno = ujuno_info(); - ujuno.params.rover.whitelisted = false; + ujuno.params.credit_manager.whitelisted = false; let denoms_data = DenomsData { prices: HashMap::from([ @@ -1000,12 +1017,12 @@ fn vault_base_token_is_not_whitelisted() { VaultPositionValue { vault_coin: CoinValue { denom: "leverage_vault_123".to_string(), - amount: Default::default(), + amount: Uint128::new(40330000), value: Uint128::new(5000), }, base_coin: CoinValue { denom: ujuno.denom.clone(), - amount: Default::default(), + amount: Uint128::new(71), value: Uint128::new(497873442), }, }, @@ -1018,11 +1035,13 @@ fn vault_base_token_is_not_whitelisted() { max_loan_to_value: Decimal::from_str("0.4").unwrap(), liquidation_threshold: Decimal::from_str("0.5").unwrap(), whitelisted: true, + hls: None, }, )]), }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], @@ -1102,6 +1121,7 @@ fn lent_coins_used_as_collateral() { }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![coin(1200, &umars.denom), coin(23, &udai.denom)], @@ -1151,7 +1171,7 @@ fn allowed_lent_coins_influence_max_ltv() { let udai = udai_info(); let mut uluna = uluna_info(); - uluna.params.rover.whitelisted = false; + uluna.params.credit_manager.whitelisted = false; let denoms_data = DenomsData { prices: HashMap::from([ @@ -1172,6 +1192,7 @@ fn allowed_lent_coins_influence_max_ltv() { }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![coin(1200, &umars.denom), coin(23, &udai.denom)], diff --git a/packages/health-computer/tests/test_hls.rs b/packages/health-computer/tests/test_hls.rs new file mode 100644 index 000000000..a555070f9 --- /dev/null +++ b/packages/health-computer/tests/test_hls.rs @@ -0,0 +1,308 @@ +use std::{collections::HashMap, str::FromStr}; + +use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; +use mars_params::types::{hls::HlsParams, vault::VaultConfig}; +use mars_rover::{ + adapters::vault::{ + CoinValue, Vault, VaultAmount, VaultPosition, VaultPositionAmount, VaultPositionValue, + }, + msg::query::{DebtAmount, Positions}, +}; +use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; +use mars_rover_health_types::AccountKind; + +use crate::helpers::{udai_info, ustars_info}; + +pub mod helpers; + +#[test] +fn hls_deposit() { + let ustars = ustars_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(ustars.denom.clone(), ustars.price)]), + params: HashMap::from([(ustars.denom.clone(), ustars.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let deposit_amount = Uint128::new(300); + let h = HealthComputer { + kind: AccountKind::HighLeveredStrategy, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![Coin { + denom: ustars.denom.clone(), + amount: deposit_amount, + }], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let health = h.compute_health().unwrap(); + let collateral_value = deposit_amount.checked_mul_floor(ustars.price).unwrap(); + assert_eq!(health.total_collateral_value, collateral_value); + assert_eq!( + health.max_ltv_adjusted_collateral, + collateral_value + .checked_mul_floor(ustars.params.credit_manager.hls.as_ref().unwrap().max_loan_to_value) + .unwrap() + ); + assert_eq!( + health.liquidation_threshold_adjusted_collateral, + collateral_value + .checked_mul_floor(ustars.params.credit_manager.hls.unwrap().liquidation_threshold) + .unwrap() + ); + assert_eq!(health.total_debt_value, Uint128::zero()); + assert_eq!(health.liquidation_health_factor, None); + assert_eq!(health.max_ltv_health_factor, None); + assert!(!health.is_liquidatable()); + assert!(!health.is_above_max_ltv()); +} + +#[test] +fn hls_vault() { + let ustars = ustars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (ustars.denom.clone(), ustars.price), + (udai.denom.clone(), udai.price), + ]), + params: HashMap::from([ + (ustars.denom.clone(), ustars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + ]), + }; + + let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); + + let vaults_data = VaultsData { + vault_values: HashMap::from([( + vault.address.clone(), + VaultPositionValue { + vault_coin: CoinValue { + denom: "leverage_vault_123".to_string(), + amount: Uint128::new(5264), + value: Uint128::new(5264), + }, + base_coin: CoinValue { + denom: ustars.denom.clone(), + amount: Default::default(), + value: Default::default(), + }, + }, + )]), + vault_configs: HashMap::from([( + vault.address.clone(), + VaultConfig { + addr: vault.address.clone(), + deposit_cap: Default::default(), + max_loan_to_value: Decimal::from_str("0.4").unwrap(), + liquidation_threshold: Decimal::from_str("0.5").unwrap(), + whitelisted: true, + hls: Some(HlsParams { + max_loan_to_value: Decimal::from_str("0.75").unwrap(), + liquidation_threshold: Decimal::from_str("0.8").unwrap(), + correlations: vec![], + }), + }, + )]), + }; + + let h = HealthComputer { + kind: AccountKind::HighLeveredStrategy, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &ustars.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom, + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: ustars.denom, + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![VaultPosition { + vault, + amount: VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::new(5264))), + }], + }, + denoms_data, + vaults_data, + }; + + let health = h.compute_health().unwrap(); + assert_eq!(health.total_collateral_value, Uint128::new(6318574763758)); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(4738931072818)); + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(5054859811006)); + assert_eq!(health.total_debt_value, Uint128::new(1053095794053)); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_str("4.499999999600701092").unwrap()) + ); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_str("4.799999999574207776").unwrap()) + ); + assert!(!health.is_above_max_ltv()); + assert!(!health.is_liquidatable()); +} + +#[test] +fn hls_on_blacklisted_asset() { + let mut ustars = ustars_info(); + ustars.params.credit_manager.whitelisted = false; + + let denoms_data = DenomsData { + prices: HashMap::from([(ustars.denom.clone(), ustars.price)]), + params: HashMap::from([(ustars.denom.clone(), ustars.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let deposit_amount = Uint128::new(300); + let h = HealthComputer { + kind: AccountKind::HighLeveredStrategy, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![Coin { + denom: ustars.denom.clone(), + amount: deposit_amount, + }], + debts: vec![], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let health = h.compute_health().unwrap(); + let collateral_value = deposit_amount.checked_mul_floor(ustars.price).unwrap(); + assert_eq!(health.total_collateral_value, collateral_value); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::zero()); + assert_eq!( + health.liquidation_threshold_adjusted_collateral, + collateral_value + .checked_mul_floor(ustars.params.credit_manager.hls.unwrap().liquidation_threshold) + .unwrap() + ); + assert_eq!(health.total_debt_value, Uint128::zero()); + assert_eq!(health.liquidation_health_factor, None); + assert_eq!(health.max_ltv_health_factor, None); + assert!(!health.is_liquidatable()); + assert!(!health.is_above_max_ltv()); +} + +#[test] +fn hls_on_blacklisted_vault() { + let ustars = ustars_info(); + let udai = udai_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([ + (ustars.denom.clone(), ustars.price), + (udai.denom.clone(), udai.price), + ]), + params: HashMap::from([ + (ustars.denom.clone(), ustars.params.clone()), + (udai.denom.clone(), udai.params.clone()), + ]), + }; + + let vault = Vault::new(Addr::unchecked("vault_addr_123".to_string())); + + let vaults_data = VaultsData { + vault_values: HashMap::from([( + vault.address.clone(), + VaultPositionValue { + vault_coin: CoinValue { + denom: "leverage_vault_123".to_string(), + amount: Uint128::new(5264), + value: Uint128::new(5264), + }, + base_coin: CoinValue { + denom: ustars.denom.clone(), + amount: Default::default(), + value: Default::default(), + }, + }, + )]), + vault_configs: HashMap::from([( + vault.address.clone(), + VaultConfig { + addr: vault.address.clone(), + deposit_cap: Default::default(), + max_loan_to_value: Decimal::from_str("0.4").unwrap(), + liquidation_threshold: Decimal::from_str("0.5").unwrap(), + whitelisted: false, + hls: Some(HlsParams { + max_loan_to_value: Decimal::from_str("0.75").unwrap(), + liquidation_threshold: Decimal::from_str("0.8").unwrap(), + correlations: vec![], + }), + }, + )]), + }; + + let h = HealthComputer { + kind: AccountKind::HighLeveredStrategy, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &ustars.denom)], + debts: vec![ + DebtAmount { + denom: udai.denom, + shares: Default::default(), + amount: Uint128::new(3100), + }, + DebtAmount { + denom: ustars.denom, + shares: Default::default(), + amount: Uint128::new(200), + }, + ], + lends: vec![], + vaults: vec![VaultPosition { + vault, + amount: VaultPositionAmount::Unlocked(VaultAmount::new(Uint128::new(5264))), + }], + }, + denoms_data, + vaults_data, + }; + + let health = h.compute_health().unwrap(); + assert_eq!(health.total_collateral_value, Uint128::new(6318574763758)); + assert_eq!(health.max_ltv_adjusted_collateral, Uint128::new(4738931068870)); + assert_eq!(health.liquidation_threshold_adjusted_collateral, Uint128::new(5054859811006)); + assert_eq!(health.total_debt_value, Uint128::new(1053095794053)); + assert_eq!( + health.max_ltv_health_factor, + Some(Decimal::from_str("4.499999995851754394").unwrap()) + ); + assert_eq!( + health.liquidation_health_factor, + Some(Decimal::from_str("4.799999999574207776").unwrap()) + ); + assert!(!health.is_above_max_ltv()); + assert!(!health.is_liquidatable()); +} diff --git a/packages/health-computer/tests/test_input_validation.rs b/packages/health-computer/tests/test_input_validation.rs index 7af3b31a9..644d24b13 100644 --- a/packages/health-computer/tests/test_input_validation.rs +++ b/packages/health-computer/tests/test_input_validation.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use cosmwasm_std::{coin, Addr, Uint128}; -use mars_params::types::VaultConfig; +use mars_params::types::vault::VaultConfig; use mars_rover::{ adapters::vault::{ CoinValue, Vault, VaultAmount, VaultPosition, VaultPositionAmount, VaultPositionValue, @@ -9,7 +9,7 @@ use mars_rover::{ msg::query::{DebtAmount, Positions}, }; use mars_rover_health_computer::{DenomsData, HealthComputer, VaultsData}; -use mars_rover_health_types::HealthError; +use mars_rover_health_types::{AccountKind, HealthError}; use crate::helpers::{udai_info, umars_info}; @@ -34,6 +34,7 @@ fn missing_price_data() { }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], @@ -79,6 +80,7 @@ fn missing_params() { }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![coin(1200, &umars.denom), coin(33, &udai.denom)], @@ -138,11 +140,13 @@ fn missing_market_data_for_vault_base_token() { max_loan_to_value: Default::default(), liquidation_threshold: Default::default(), whitelisted: false, + hls: None, }, )]), }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![], @@ -180,11 +184,13 @@ fn missing_vault_value() { max_loan_to_value: Default::default(), liquidation_threshold: Default::default(), whitelisted: false, + hls: None, }, )]), }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![], @@ -232,6 +238,7 @@ fn missing_vault_config() { }; let h = HealthComputer { + kind: AccountKind::Default, positions: Positions { account_id: "123".to_string(), deposits: vec![], @@ -249,3 +256,38 @@ fn missing_vault_config() { let err: HealthError = h.compute_health().unwrap_err(); assert_eq!(err, HealthError::MissingVaultConfig(vault.address.to_string())) } + +#[test] +fn missing_hls_params() { + let umars = umars_info(); + + let denoms_data = DenomsData { + prices: HashMap::from([(umars.denom.clone(), umars.price)]), + params: HashMap::from([(umars.denom.clone(), umars.params.clone())]), + }; + + let vaults_data = VaultsData { + vault_values: Default::default(), + vault_configs: Default::default(), + }; + + let h = HealthComputer { + kind: AccountKind::HighLeveredStrategy, + positions: Positions { + account_id: "123".to_string(), + deposits: vec![coin(1200, &umars.denom)], + debts: vec![DebtAmount { + denom: umars.denom.clone(), + shares: Default::default(), + amount: Uint128::new(200), + }], + lends: vec![], + vaults: vec![], + }, + denoms_data, + vaults_data, + }; + + let err: HealthError = h.compute_health().unwrap_err(); + assert_eq!(err, HealthError::MissingHLSParams(umars.denom)) +} diff --git a/packages/health-types/src/account.rs b/packages/health-types/src/account.rs new file mode 100644 index 000000000..fa49c32f3 --- /dev/null +++ b/packages/health-types/src/account.rs @@ -0,0 +1,15 @@ +use std::fmt; + +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub enum AccountKind { + Default, + HighLeveredStrategy, +} + +impl fmt::Display for AccountKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/packages/health-types/src/error.rs b/packages/health-types/src/error.rs index 28fa10fdf..ee673c34d 100644 --- a/packages/health-types/src/error.rs +++ b/packages/health-types/src/error.rs @@ -15,6 +15,11 @@ pub enum HealthError { #[error("{0} address has not been set in config")] ContractNotSet(String), + #[error( + "Account is an HLS account, but {0} was not provided HLS params to compute health with" + )] + MissingHLSParams(String), + #[error("{0} was not provided asset params to compute health with")] MissingParams(String), diff --git a/packages/health-types/src/lib.rs b/packages/health-types/src/lib.rs index 106067349..bd7160733 100644 --- a/packages/health-types/src/lib.rs +++ b/packages/health-types/src/lib.rs @@ -1,7 +1,9 @@ +mod account; mod error; mod health; mod msg; +pub use account::*; pub use error::*; pub use health::*; pub use msg::*; diff --git a/packages/health-types/src/msg.rs b/packages/health-types/src/msg.rs index 8aad9cd7e..2006ffac1 100644 --- a/packages/health-types/src/msg.rs +++ b/packages/health-types/src/msg.rs @@ -1,6 +1,8 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use mars_owner::{OwnerResponse, OwnerUpdate}; +use crate::AccountKind; + #[cw_serde] pub struct InstantiateMsg { /// The address with privileged access to update config @@ -24,6 +26,7 @@ pub enum QueryMsg { #[returns(crate::HealthResponse)] Health { account_id: String, + kind: AccountKind, }, #[returns(ConfigResponse)] Config {}, diff --git a/packages/rover/src/adapters/account_nft.rs b/packages/rover/src/adapters/account_nft.rs new file mode 100644 index 000000000..497b3d7a9 --- /dev/null +++ b/packages/rover/src/adapters/account_nft.rs @@ -0,0 +1,37 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Api, QuerierWrapper, StdResult}; +use mars_account_nft::msg::QueryMsg; + +#[cw_serde] +pub struct AccountNftBase(T); + +impl AccountNftBase { + pub fn new(address: T) -> AccountNftBase { + AccountNftBase(address) + } + + pub fn address(&self) -> &T { + &self.0 + } +} + +pub type AccountNftUnchecked = AccountNftBase; +pub type AccountNft = AccountNftBase; + +impl From for AccountNftUnchecked { + fn from(account_nft: AccountNft) -> Self { + Self(account_nft.0.to_string()) + } +} + +impl AccountNftUnchecked { + pub fn check(&self, api: &dyn Api) -> StdResult { + Ok(AccountNftBase(api.addr_validate(self.address())?)) + } +} + +impl AccountNft { + pub fn query_next_id(&self, querier: &QuerierWrapper) -> StdResult { + querier.query_wasm_smart(self.address().to_string(), &QueryMsg::NextId {}) + } +} diff --git a/packages/rover/src/adapters/health.rs b/packages/rover/src/adapters/health.rs index e83db35d5..125424c92 100644 --- a/packages/rover/src/adapters/health.rs +++ b/packages/rover/src/adapters/health.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, QuerierWrapper, StdResult}; -use mars_rover_health_types::{HealthResponse, QueryMsg}; +use mars_rover_health_types::{AccountKind, HealthResponse, QueryMsg}; #[cw_serde] pub struct HealthContractBase(T); @@ -35,11 +35,13 @@ impl HealthContract { &self, querier: &QuerierWrapper, account_id: &str, + kind: AccountKind, ) -> StdResult { querier.query_wasm_smart( self.address().to_string(), &QueryMsg::Health { account_id: account_id.to_string(), + kind, }, ) } diff --git a/packages/rover/src/adapters/mod.rs b/packages/rover/src/adapters/mod.rs index 748c29ead..5bd68402e 100644 --- a/packages/rover/src/adapters/mod.rs +++ b/packages/rover/src/adapters/mod.rs @@ -1,3 +1,4 @@ +pub mod account_nft; pub mod health; pub mod oracle; pub mod params; diff --git a/packages/rover/src/adapters/params.rs b/packages/rover/src/adapters/params.rs index cfd1f22b5..2759b4bac 100644 --- a/packages/rover/src/adapters/params.rs +++ b/packages/rover/src/adapters/params.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api, Decimal, QuerierWrapper, StdResult}; use mars_params::{ msg::QueryMsg, - types::{AssetParams, VaultConfig}, + types::{asset::AssetParams, vault::VaultConfig}, }; #[cw_serde] diff --git a/packages/rover/src/error.rs b/packages/rover/src/error.rs index 09e86da14..a03491613 100644 --- a/packages/rover/src/error.rs +++ b/packages/rover/src/error.rs @@ -68,6 +68,11 @@ pub enum ContractError { new_hf: String, }, + #[error("{reason:?}")] + HLS { + reason: String, + }, + #[error("{reason:?}")] InvalidConfig { reason: String, diff --git a/packages/rover/src/msg/execute.rs b/packages/rover/src/msg/execute.rs index 7bd35c997..e5f6be358 100644 --- a/packages/rover/src/msg/execute.rs +++ b/packages/rover/src/msg/execute.rs @@ -2,6 +2,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, StdResult, Uint128, WasmMsg}; use mars_account_nft::nft_config::NftConfigUpdates; use mars_owner::OwnerUpdate; +use mars_rover_health_types::AccountKind; use crate::{ adapters::vault::{Vault, VaultPositionType, VaultUnchecked}, @@ -14,7 +15,7 @@ pub enum ExecuteMsg { // Public messages //-------------------------------------------------------------------------------------------------- /// Mints NFT representing a credit account for user. User can have many. - CreateCreditAccount {}, + CreateCreditAccount(AccountKind), /// Update user's position on their credit account UpdateCreditAccount { account_id: String, @@ -298,6 +299,10 @@ pub enum CallbackMsg { RefundAllCoinBalances { account_id: String, }, + /// Ensures that HLS accounts abide by specific rules + AssertAccountReqs { + account_id: String, + }, } impl CallbackMsg { diff --git a/packages/rover/src/msg/instantiate.rs b/packages/rover/src/msg/instantiate.rs index 7ee6f1c10..606317f63 100644 --- a/packages/rover/src/msg/instantiate.rs +++ b/packages/rover/src/msg/instantiate.rs @@ -2,8 +2,9 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Uint128; use crate::adapters::{ - health::HealthContractUnchecked, oracle::OracleUnchecked, params::ParamsUnchecked, - red_bank::RedBankUnchecked, swap::SwapperUnchecked, zapper::ZapperUnchecked, + account_nft::AccountNftUnchecked, health::HealthContractUnchecked, oracle::OracleUnchecked, + params::ParamsUnchecked, red_bank::RedBankUnchecked, swap::SwapperUnchecked, + zapper::ZapperUnchecked, }; #[cw_serde] @@ -32,7 +33,7 @@ pub struct InstantiateMsg { #[cw_serde] #[derive(Default)] pub struct ConfigUpdates { - pub account_nft: Option, + pub account_nft: Option, pub oracle: Option, pub red_bank: Option, pub max_unlocking_positions: Option, diff --git a/packages/rover/src/msg/query.rs b/packages/rover/src/msg/query.rs index a043c3ef0..0ab238469 100644 --- a/packages/rover/src/msg/query.rs +++ b/packages/rover/src/msg/query.rs @@ -10,6 +10,10 @@ use crate::{ #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { + #[returns(mars_rover_health_types::AccountKind)] + AccountKind { + account_id: String, + }, /// Rover contract-level config #[returns(ConfigResponse)] Config {}, diff --git a/schemas/mars-account-nft/mars-account-nft.json b/schemas/mars-account-nft/mars-account-nft.json index 80b1b6554..fcea48085 100644 --- a/schemas/mars-account-nft/mars-account-nft.json +++ b/schemas/mars-account-nft/mars-account-nft.json @@ -1324,10 +1324,8 @@ }, "next_id": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "uint64", - "type": "integer", - "format": "uint64", - "minimum": 0.0 + "title": "String", + "type": "string" }, "nft_info": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index 5d29e254f..ce14a0d35 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -116,8 +116,7 @@ ], "properties": { "create_credit_account": { - "type": "object", - "additionalProperties": false + "$ref": "#/definitions/AccountKind" } }, "additionalProperties": false @@ -259,6 +258,16 @@ } ], "definitions": { + "AccountKind": { + "type": "string", + "enum": [ + "default", + "high_levered_strategy" + ] + }, + "AccountNftBase_for_String": { + "type": "string" + }, "Action": { "description": "The list of actions that users can perform on their positions", "oneOf": [ @@ -1200,6 +1209,28 @@ } }, "additionalProperties": false + }, + { + "description": "Ensures that HLS accounts abide by specific rules", + "type": "object", + "required": [ + "assert_account_reqs" + ], + "properties": { + "assert_account_reqs": { + "type": "object", + "required": [ + "account_id" + ], + "properties": { + "account_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -1223,9 +1254,13 @@ "type": "object", "properties": { "account_nft": { - "type": [ - "string", - "null" + "anyOf": [ + { + "$ref": "#/definitions/AccountNftBase_for_String" + }, + { + "type": "null" + } ] }, "health_contract": { @@ -1562,6 +1597,27 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "QueryMsg", "oneOf": [ + { + "type": "object", + "required": [ + "account_kind" + ], + "properties": { + "account_kind": { + "type": "object", + "required": [ + "account_id" + ], + "properties": { + "account_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Rover contract-level config", "type": "object", @@ -2086,6 +2142,15 @@ "migrate": null, "sudo": null, "responses": { + "account_kind": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AccountKind", + "type": "string", + "enum": [ + "default", + "high_levered_strategy" + ] + }, "all_coin_balances": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_CoinBalanceResponseItem", diff --git a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json index 86946cbde..65b9f793e 100644 --- a/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json +++ b/schemas/mars-mock-credit-manager/mars-mock-credit-manager.json @@ -273,6 +273,27 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "QueryMsg", "oneOf": [ + { + "type": "object", + "required": [ + "account_kind" + ], + "properties": { + "account_kind": { + "type": "object", + "required": [ + "account_id" + ], + "properties": { + "account_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Rover contract-level config", "type": "object", @@ -797,6 +818,15 @@ "migrate": null, "sudo": null, "responses": { + "account_kind": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AccountKind", + "type": "string", + "enum": [ + "default", + "high_levered_strategy" + ] + }, "all_coin_balances": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_CoinBalanceResponseItem", diff --git a/schemas/mars-params/mars-params.json b/schemas/mars-params/mars-params.json index ed81be33a..63588f009 100644 --- a/schemas/mars-params/mars-params.json +++ b/schemas/mars-params/mars-params.json @@ -1,6 +1,6 @@ { "contract_name": "mars-params", - "contract_version": "1.0.4", + "contract_version": "1.0.6", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -98,17 +98,20 @@ } ], "definitions": { - "AssetParams": { + "AssetParamsBase_for_String": { "type": "object", "required": [ + "credit_manager", "denom", "liquidation_bonus", "liquidation_threshold", "max_loan_to_value", - "red_bank", - "rover" + "red_bank" ], "properties": { + "credit_manager": { + "$ref": "#/definitions/CmSettings_for_String" + }, "denom": { "type": "string" }, @@ -123,9 +126,6 @@ }, "red_bank": { "$ref": "#/definitions/RedBankSettings" - }, - "rover": { - "$ref": "#/definitions/RoverSettings" } }, "additionalProperties": false @@ -145,7 +145,7 @@ ], "properties": { "params": { - "$ref": "#/definitions/AssetParams" + "$ref": "#/definitions/AssetParamsBase_for_String" } }, "additionalProperties": false @@ -155,6 +155,68 @@ } ] }, + "CmEmergencyUpdate": { + "oneOf": [ + { + "type": "object", + "required": [ + "set_zero_max_ltv_on_vault" + ], + "properties": { + "set_zero_max_ltv_on_vault": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "set_zero_deposit_cap_on_vault" + ], + "properties": { + "set_zero_deposit_cap_on_vault": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "disallow_coin" + ], + "properties": { + "disallow_coin": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "CmSettings_for_String": { + "type": "object", + "required": [ + "whitelisted" + ], + "properties": { + "hls": { + "anyOf": [ + { + "$ref": "#/definitions/HlsParamsBase_for_String" + }, + { + "type": "null" + } + ] + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + }, "Coin": { "type": "object", "required": [ @@ -179,11 +241,11 @@ { "type": "object", "required": [ - "rover" + "credit_manager" ], "properties": { - "rover": { - "$ref": "#/definitions/RoverEmergencyUpdate" + "credit_manager": { + "$ref": "#/definitions/CmEmergencyUpdate" } }, "additionalProperties": false @@ -202,13 +264,67 @@ } ] }, - "HighLeverageStrategyParams": { + "HlsAssetType_for_String": { + "oneOf": [ + { + "type": "object", + "required": [ + "coin" + ], + "properties": { + "coin": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "HlsParamsBase_for_String": { "type": "object", "required": [ + "correlations", "liquidation_threshold", "max_loan_to_value" ], "properties": { + "correlations": { + "description": "Given this asset is debt, correlations are the only allowed collateral which are permitted to fulfill the HLS strategy", + "type": "array", + "items": { + "$ref": "#/definitions/HlsAssetType_for_String" + } + }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" }, @@ -330,62 +446,6 @@ }, "additionalProperties": false }, - "RoverEmergencyUpdate": { - "oneOf": [ - { - "type": "object", - "required": [ - "set_zero_max_ltv_on_vault" - ], - "properties": { - "set_zero_max_ltv_on_vault": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "set_zero_deposit_cap_on_vault" - ], - "properties": { - "set_zero_deposit_cap_on_vault": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "disallow_coin" - ], - "properties": { - "disallow_coin": { - "type": "string" - } - }, - "additionalProperties": false - } - ] - }, - "RoverSettings": { - "type": "object", - "required": [ - "hls", - "whitelisted" - ], - "properties": { - "hls": { - "$ref": "#/definitions/HighLeverageStrategyParams" - }, - "whitelisted": { - "type": "boolean" - } - }, - "additionalProperties": false - }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -406,6 +466,16 @@ "deposit_cap": { "$ref": "#/definitions/Coin" }, + "hls": { + "anyOf": [ + { + "$ref": "#/definitions/HlsParamsBase_for_String" + }, + { + "type": "null" + } + ] + }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" }, @@ -440,27 +510,6 @@ } }, "additionalProperties": false - }, - { - "type": "object", - "required": [ - "remove" - ], - "properties": { - "remove": { - "type": "object", - "required": [ - "addr" - ], - "properties": { - "addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false } ] } @@ -604,23 +653,30 @@ "responses": { "all_asset_params": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_AssetParams", + "title": "Array_of_AssetParamsBase_for_Addr", "type": "array", "items": { - "$ref": "#/definitions/AssetParams" + "$ref": "#/definitions/AssetParamsBase_for_Addr" }, "definitions": { - "AssetParams": { + "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" + }, + "AssetParamsBase_for_Addr": { "type": "object", "required": [ + "credit_manager", "denom", "liquidation_bonus", "liquidation_threshold", "max_loan_to_value", - "red_bank", - "rover" + "red_bank" ], "properties": { + "credit_manager": { + "$ref": "#/definitions/CmSettings_for_Addr" + }, "denom": { "type": "string" }, @@ -635,9 +691,28 @@ }, "red_bank": { "$ref": "#/definitions/RedBankSettings" + } + }, + "additionalProperties": false + }, + "CmSettings_for_Addr": { + "type": "object", + "required": [ + "whitelisted" + ], + "properties": { + "hls": { + "anyOf": [ + { + "$ref": "#/definitions/HlsParamsBase_for_Addr" + }, + { + "type": "null" + } + ] }, - "rover": { - "$ref": "#/definitions/RoverSettings" + "whitelisted": { + "type": "boolean" } }, "additionalProperties": false @@ -646,13 +721,67 @@ "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" }, - "HighLeverageStrategyParams": { + "HlsAssetType_for_Addr": { + "oneOf": [ + { + "type": "object", + "required": [ + "coin" + ], + "properties": { + "coin": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "HlsParamsBase_for_Addr": { "type": "object", "required": [ + "correlations", "liquidation_threshold", "max_loan_to_value" ], "properties": { + "correlations": { + "description": "Given this asset is debt, correlations are the only allowed collateral which are permitted to fulfill the HLS strategy", + "type": "array", + "items": { + "$ref": "#/definitions/HlsAssetType_for_Addr" + } + }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" }, @@ -682,22 +811,6 @@ }, "additionalProperties": false }, - "RoverSettings": { - "type": "object", - "required": [ - "hls", - "whitelisted" - ], - "properties": { - "hls": { - "$ref": "#/definitions/HighLeverageStrategyParams" - }, - "whitelisted": { - "type": "boolean" - } - }, - "additionalProperties": false - }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -735,6 +848,76 @@ "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" }, + "HlsAssetType_for_Addr": { + "oneOf": [ + { + "type": "object", + "required": [ + "coin" + ], + "properties": { + "coin": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "HlsParamsBase_for_Addr": { + "type": "object", + "required": [ + "correlations", + "liquidation_threshold", + "max_loan_to_value" + ], + "properties": { + "correlations": { + "description": "Given this asset is debt, correlations are the only allowed collateral which are permitted to fulfill the HLS strategy", + "type": "array", + "items": { + "$ref": "#/definitions/HlsAssetType_for_Addr" + } + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_loan_to_value": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -755,6 +938,16 @@ "deposit_cap": { "$ref": "#/definitions/Coin" }, + "hls": { + "anyOf": [ + { + "$ref": "#/definitions/HlsParamsBase_for_Addr" + }, + { + "type": "null" + } + ] + }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" }, @@ -771,17 +964,20 @@ }, "asset_params": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AssetParams", + "title": "AssetParamsBase_for_Addr", "type": "object", "required": [ + "credit_manager", "denom", "liquidation_bonus", "liquidation_threshold", "max_loan_to_value", - "red_bank", - "rover" + "red_bank" ], "properties": { + "credit_manager": { + "$ref": "#/definitions/CmSettings_for_Addr" + }, "denom": { "type": "string" }, @@ -796,24 +992,101 @@ }, "red_bank": { "$ref": "#/definitions/RedBankSettings" - }, - "rover": { - "$ref": "#/definitions/RoverSettings" } }, "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" + }, + "CmSettings_for_Addr": { + "type": "object", + "required": [ + "whitelisted" + ], + "properties": { + "hls": { + "anyOf": [ + { + "$ref": "#/definitions/HlsParamsBase_for_Addr" + }, + { + "type": "null" + } + ] + }, + "whitelisted": { + "type": "boolean" + } + }, + "additionalProperties": false + }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, - "HighLeverageStrategyParams": { + "HlsAssetType_for_Addr": { + "oneOf": [ + { + "type": "object", + "required": [ + "coin" + ], + "properties": { + "coin": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "HlsParamsBase_for_Addr": { "type": "object", "required": [ + "correlations", "liquidation_threshold", "max_loan_to_value" ], "properties": { + "correlations": { + "description": "Given this asset is debt, correlations are the only allowed collateral which are permitted to fulfill the HLS strategy", + "type": "array", + "items": { + "$ref": "#/definitions/HlsAssetType_for_Addr" + } + }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" }, @@ -843,22 +1116,6 @@ }, "additionalProperties": false }, - "RoverSettings": { - "type": "object", - "required": [ - "hls", - "whitelisted" - ], - "properties": { - "hls": { - "$ref": "#/definitions/HighLeverageStrategyParams" - }, - "whitelisted": { - "type": "boolean" - } - }, - "additionalProperties": false - }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -926,6 +1183,16 @@ "deposit_cap": { "$ref": "#/definitions/Coin" }, + "hls": { + "anyOf": [ + { + "$ref": "#/definitions/HlsParamsBase_for_Addr" + }, + { + "type": "null" + } + ] + }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" }, @@ -961,6 +1228,76 @@ "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" }, + "HlsAssetType_for_Addr": { + "oneOf": [ + { + "type": "object", + "required": [ + "coin" + ], + "properties": { + "coin": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "HlsParamsBase_for_Addr": { + "type": "object", + "required": [ + "correlations", + "liquidation_threshold", + "max_loan_to_value" + ], + "properties": { + "correlations": { + "description": "Given this asset is debt, correlations are the only allowed collateral which are permitted to fulfill the HLS strategy", + "type": "array", + "items": { + "$ref": "#/definitions/HlsAssetType_for_Addr" + } + }, + "liquidation_threshold": { + "$ref": "#/definitions/Decimal" + }, + "max_loan_to_value": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/mars-rover-health-computer/mars-rover-health-computer.json b/schemas/mars-rover-health-computer/mars-rover-health-computer.json index 3d0100bf4..b0640c9af 100644 --- a/schemas/mars-rover-health-computer/mars-rover-health-computer.json +++ b/schemas/mars-rover-health-computer/mars-rover-health-computer.json @@ -5,6 +5,7 @@ "type": "object", "required": [ "denoms_data", + "kind", "positions", "vaults_data" ], @@ -12,6 +13,9 @@ "denoms_data": { "$ref": "#/definitions/DenomsData" }, + "kind": { + "$ref": "#/definitions/AccountKind" + }, "positions": { "$ref": "#/definitions/Positions" }, @@ -21,21 +25,31 @@ }, "additionalProperties": false, "definitions": { + "AccountKind": { + "type": "string", + "enum": [ + "default", + "high_levered_strategy" + ] + }, "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" }, - "AssetParams": { + "AssetParamsBase_for_Addr": { "type": "object", "required": [ + "credit_manager", "denom", "liquidation_bonus", "liquidation_threshold", "max_loan_to_value", - "red_bank", - "rover" + "red_bank" ], "properties": { + "credit_manager": { + "$ref": "#/definitions/CmSettings_for_Addr" + }, "denom": { "type": "string" }, @@ -50,9 +64,28 @@ }, "red_bank": { "$ref": "#/definitions/RedBankSettings" + } + }, + "additionalProperties": false + }, + "CmSettings_for_Addr": { + "type": "object", + "required": [ + "whitelisted" + ], + "properties": { + "hls": { + "anyOf": [ + { + "$ref": "#/definitions/HlsParamsBase_for_Addr" + }, + { + "type": "null" + } + ] }, - "rover": { - "$ref": "#/definitions/RoverSettings" + "whitelisted": { + "type": "boolean" } }, "additionalProperties": false @@ -136,7 +169,7 @@ "params": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/AssetParams" + "$ref": "#/definitions/AssetParamsBase_for_Addr" } }, "prices": { @@ -149,13 +182,67 @@ }, "additionalProperties": false }, - "HighLeverageStrategyParams": { + "HlsAssetType_for_Addr": { + "oneOf": [ + { + "type": "object", + "required": [ + "coin" + ], + "properties": { + "coin": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "vault" + ], + "properties": { + "vault": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "HlsParamsBase_for_Addr": { "type": "object", "required": [ + "correlations", "liquidation_threshold", "max_loan_to_value" ], "properties": { + "correlations": { + "description": "Given this asset is debt, correlations are the only allowed collateral which are permitted to fulfill the HLS strategy", + "type": "array", + "items": { + "$ref": "#/definitions/HlsAssetType_for_Addr" + } + }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" }, @@ -271,22 +358,6 @@ }, "additionalProperties": false }, - "RoverSettings": { - "type": "object", - "required": [ - "hls", - "whitelisted" - ], - "properties": { - "hls": { - "$ref": "#/definitions/HighLeverageStrategyParams" - }, - "whitelisted": { - "type": "boolean" - } - }, - "additionalProperties": false - }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -328,6 +399,16 @@ "deposit_cap": { "$ref": "#/definitions/Coin" }, + "hls": { + "anyOf": [ + { + "$ref": "#/definitions/HlsParamsBase_for_Addr" + }, + { + "type": "null" + } + ] + }, "liquidation_threshold": { "$ref": "#/definitions/Decimal" }, diff --git a/schemas/mars-rover-health-types/mars-rover-health-types.json b/schemas/mars-rover-health-types/mars-rover-health-types.json index 9e0963062..d9f2488b5 100644 --- a/schemas/mars-rover-health-types/mars-rover-health-types.json +++ b/schemas/mars-rover-health-types/mars-rover-health-types.json @@ -155,11 +155,15 @@ "health": { "type": "object", "required": [ - "account_id" + "account_id", + "kind" ], "properties": { "account_id": { "type": "string" + }, + "kind": { + "$ref": "#/definitions/AccountKind" } }, "additionalProperties": false @@ -180,7 +184,16 @@ }, "additionalProperties": false } - ] + ], + "definitions": { + "AccountKind": { + "type": "string", + "enum": [ + "default", + "high_levered_strategy" + ] + } + } }, "migrate": null, "sudo": null, diff --git a/scripts/health/DataFetcher.ts b/scripts/health/DataFetcher.ts index 880765179..2e2d8b498 100644 --- a/scripts/health/DataFetcher.ts +++ b/scripts/health/DataFetcher.ts @@ -3,6 +3,7 @@ import { MarsCreditManagerQueryClient } from '../types/generated/mars-credit-man import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient' import { HealthResponse } from '../types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' import { + AccountKind, DenomsData, HealthComputer, VaultsData, @@ -101,6 +102,7 @@ export class DataFetcher { positions, denoms_data, vaults_data, + kind: 'default' as AccountKind, } return this.healthComputer(data) } diff --git a/scripts/package.json b/scripts/package.json index 7151fc9b5..a4b0c94ca 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -22,7 +22,7 @@ "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.30.1", "@cosmjs/stargate": "^0.30.1", - "@cosmwasm/ts-codegen": "^0.30.0", + "@cosmwasm/ts-codegen": "^0.30.1", "chalk": "4.1.2", "copyfiles": "^2.4.1", "cosmjs-types": "^0.8.0", @@ -33,15 +33,15 @@ "wasm-pack": "^0.11.1" }, "devDependencies": { - "@babel/preset-env": "^7.22.4", - "@babel/preset-typescript": "^7.21.5", - "@types/jest": "^29.5.1", - "@typescript-eslint/eslint-plugin": "^5.59.8", - "@typescript-eslint/parser": "^5.59.8", - "eslint": "^8.41.0", + "@babel/preset-env": "^7.22.5", + "@babel/preset-typescript": "^7.22.5", + "@types/jest": "^29.5.2", + "@typescript-eslint/eslint-plugin": "^5.59.9", + "@typescript-eslint/parser": "^5.59.9", + "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "jest": "^29.5.0", "prettier": "^2.8.8", - "typescript": "^5.0.4" + "typescript": "^5.1.3" } } diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts index b47215fd0..60f35edf1 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -30,6 +30,7 @@ import { NftConfigBaseForString, ContractInfoResponse, MinterResponse, + String, NumTokensResponse, Addr, OwnershipForAddr, @@ -37,7 +38,7 @@ import { export interface MarsAccountNftReadOnlyInterface { contractAddress: string config: () => Promise - nextId: () => Promise + nextId: () => Promise ownerOf: ({ includeExpired, tokenId, @@ -129,7 +130,7 @@ export class MarsAccountNftQueryClient implements MarsAccountNftReadOnlyInterfac config: {}, }) } - nextId = async (): Promise => { + nextId = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { next_id: {}, }) diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts index 50578b8f0..a18854cc9 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -32,6 +32,7 @@ import { NftConfigBaseForString, ContractInfoResponse, MinterResponse, + String, NumTokensResponse, Addr, OwnershipForAddr, diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts index 3f0b9af84..67af28fd2 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -31,6 +31,7 @@ import { NftConfigBaseForString, ContractInfoResponse, MinterResponse, + String, NumTokensResponse, Addr, OwnershipForAddr, @@ -342,12 +343,12 @@ export function useMarsAccountNftOwnerOfQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } -export interface MarsAccountNftNextIdQuery extends MarsAccountNftReactQuery {} -export function useMarsAccountNftNextIdQuery({ +export interface MarsAccountNftNextIdQuery extends MarsAccountNftReactQuery {} +export function useMarsAccountNftNextIdQuery({ client, options, }: MarsAccountNftNextIdQuery) { - return useQuery( + return useQuery( marsAccountNftQueryKeys.nextId(client?.contractAddress), () => (client ? client.nextId() : Promise.reject(new Error('Invalid client'))), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, diff --git a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts index 62506e83e..2d35e803f 100644 --- a/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts +++ b/scripts/types/generated/mars-account-nft/MarsAccountNft.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -207,6 +207,7 @@ export interface ContractInfoResponse { export interface MinterResponse { minter?: string | null } +export type String = string export interface NumTokensResponse { count: number } diff --git a/scripts/types/generated/mars-account-nft/bundle.ts b/scripts/types/generated/mars-account-nft/bundle.ts index 25b6c9448..8dc6478e4 100644 --- a/scripts/types/generated/mars-account-nft/bundle.ts +++ b/scripts/types/generated/mars-account-nft/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts index 31781f9e0..aee3b352d 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -17,11 +17,13 @@ import { ZapperBaseForString, InstantiateMsg, ExecuteMsg, + AccountKind, Action, ActionAmount, LiquidateRequestForVaultBaseForString, VaultPositionType, Decimal, + AccountNftBaseForString, OwnerUpdate, CallbackMsg, Addr, @@ -62,6 +64,7 @@ import { } from './MarsCreditManager.types' export interface MarsCreditManagerReadOnlyInterface { contractAddress: string + accountKind: ({ accountId }: { accountId: string }) => Promise config: () => Promise vaultUtilization: ({ vault }: { vault: VaultBaseForString }) => Promise positions: ({ accountId }: { accountId: string }) => Promise @@ -130,6 +133,7 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn constructor(client: CosmWasmClient, contractAddress: string) { this.client = client this.contractAddress = contractAddress + this.accountKind = this.accountKind.bind(this) this.config = this.config.bind(this) this.vaultUtilization = this.vaultUtilization.bind(this) this.positions = this.positions.bind(this) @@ -146,6 +150,13 @@ export class MarsCreditManagerQueryClient implements MarsCreditManagerReadOnlyIn this.vaultPositionValue = this.vaultPositionValue.bind(this) } + accountKind = async ({ accountId }: { accountId: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + account_kind: { + account_id: accountId, + }, + }) + } config = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { config: {}, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts index 31e1dea21..2527d1b08 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -18,11 +18,13 @@ import { ZapperBaseForString, InstantiateMsg, ExecuteMsg, + AccountKind, Action, ActionAmount, LiquidateRequestForVaultBaseForString, VaultPositionType, Decimal, + AccountNftBaseForString, OwnerUpdate, CallbackMsg, Addr, diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts index 3df83ffe8..3b893a008 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -18,11 +18,13 @@ import { ZapperBaseForString, InstantiateMsg, ExecuteMsg, + AccountKind, Action, ActionAmount, LiquidateRequestForVaultBaseForString, VaultPositionType, Decimal, + AccountNftBaseForString, OwnerUpdate, CallbackMsg, Addr, @@ -70,6 +72,10 @@ export const marsCreditManagerQueryKeys = { ] as const, address: (contractAddress: string | undefined) => [{ ...marsCreditManagerQueryKeys.contract[0], address: contractAddress }] as const, + accountKind: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'account_kind', args }, + ] as const, config: (contractAddress: string | undefined, args?: Record) => [ { ...marsCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, @@ -477,6 +483,28 @@ export function useMarsCreditManagerConfigQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsCreditManagerAccountKindQuery + extends MarsCreditManagerReactQuery { + args: { + accountId: string + } +} +export function useMarsCreditManagerAccountKindQuery({ + client, + args, + options, +}: MarsCreditManagerAccountKindQuery) { + return useQuery( + marsCreditManagerQueryKeys.accountKind(client?.contractAddress, args), + () => + client + ? client.accountKind({ + accountId: args.accountId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsCreditManagerCallbackMutation { client: MarsCreditManagerClient msg: CallbackMsg @@ -626,7 +654,8 @@ export function useMarsCreditManagerCreateCreditAccountMutation( >, ) { return useMutation( - ({ client, args: { fee, memo, funds } = {} }) => client.createCreditAccount(fee, memo, funds), + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.createCreditAccount(msg, fee, memo, funds), options, ) } diff --git a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts index ad995ccf8..56970e776 100644 --- a/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts +++ b/scripts/types/generated/mars-credit-manager/MarsCreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -24,7 +24,7 @@ export interface InstantiateMsg { } export type ExecuteMsg = | { - create_credit_account: {} + create_credit_account: AccountKind } | { update_credit_account: { @@ -54,6 +54,7 @@ export type ExecuteMsg = | { callback: CallbackMsg } +export type AccountKind = 'default' | 'high_levered_strategy' export type Action = | { deposit: Coin @@ -150,6 +151,7 @@ export type LiquidateRequestForVaultBaseForString = } export type VaultPositionType = 'u_n_l_o_c_k_e_d' | 'l_o_c_k_e_d' | 'u_n_l_o_c_k_i_n_g' export type Decimal = string +export type AccountNftBaseForString = string export type OwnerUpdate = | { propose_new_owner: { @@ -287,6 +289,11 @@ export type CallbackMsg = account_id: string } } + | { + assert_account_reqs: { + account_id: string + } + } export type Addr = string export type LiquidateRequestForVaultBaseForAddr = | { @@ -314,7 +321,7 @@ export interface VaultBaseForString { address: string } export interface ConfigUpdates { - account_nft?: string | null + account_nft?: AccountNftBaseForString | null health_contract?: HealthContractBaseForString | null max_unlocking_positions?: Uint128 | null oracle?: OracleBaseForString | null @@ -330,6 +337,11 @@ export interface VaultBaseForAddr { address: Addr } export type QueryMsg = + | { + account_kind: { + account_id: string + } + } | { config: {} } diff --git a/scripts/types/generated/mars-credit-manager/bundle.ts b/scripts/types/generated/mars-credit-manager/bundle.ts index 58e3d170b..f9684e57d 100644 --- a/scripts/types/generated/mars-credit-manager/bundle.ts +++ b/scripts/types/generated/mars-credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts index e5fae28a3..3709a1ebd 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -26,6 +26,7 @@ import { VaultBaseForAddr, QueryMsg, VaultBaseForString, + AccountKind, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, @@ -45,6 +46,7 @@ import { } from './MarsMockCreditManager.types' export interface MarsMockCreditManagerReadOnlyInterface { contractAddress: string + accountKind: ({ accountId }: { accountId: string }) => Promise config: () => Promise vaultUtilization: ({ vault }: { vault: VaultBaseForString }) => Promise positions: ({ accountId }: { accountId: string }) => Promise @@ -113,6 +115,7 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe constructor(client: CosmWasmClient, contractAddress: string) { this.client = client this.contractAddress = contractAddress + this.accountKind = this.accountKind.bind(this) this.config = this.config.bind(this) this.vaultUtilization = this.vaultUtilization.bind(this) this.positions = this.positions.bind(this) @@ -129,6 +132,13 @@ export class MarsMockCreditManagerQueryClient implements MarsMockCreditManagerRe this.vaultPositionValue = this.vaultPositionValue.bind(this) } + accountKind = async ({ accountId }: { accountId: string }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + account_kind: { + account_id: accountId, + }, + }) + } config = async (): Promise => { return this.client.queryContractSmart(this.contractAddress, { config: {}, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts index bcb9f080c..d9b3e812f 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -27,6 +27,7 @@ import { VaultBaseForAddr, QueryMsg, VaultBaseForString, + AccountKind, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts index a7d2f3053..5ee09ccd2 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -27,6 +27,7 @@ import { VaultBaseForAddr, QueryMsg, VaultBaseForString, + AccountKind, ArrayOfCoinBalanceResponseItem, CoinBalanceResponseItem, ArrayOfSharesResponseItem, @@ -56,6 +57,14 @@ export const marsMockCreditManagerQueryKeys = { ] as const, address: (contractAddress: string | undefined) => [{ ...marsMockCreditManagerQueryKeys.contract[0], address: contractAddress }] as const, + accountKind: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], + method: 'account_kind', + args, + }, + ] as const, config: (contractAddress: string | undefined, args?: Record) => [ { ...marsMockCreditManagerQueryKeys.address(contractAddress)[0], method: 'config', args }, @@ -461,6 +470,28 @@ export function useMarsMockCreditManagerConfigQuery({ { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, ) } +export interface MarsMockCreditManagerAccountKindQuery + extends MarsMockCreditManagerReactQuery { + args: { + accountId: string + } +} +export function useMarsMockCreditManagerAccountKindQuery({ + client, + args, + options, +}: MarsMockCreditManagerAccountKindQuery) { + return useQuery( + marsMockCreditManagerQueryKeys.accountKind(client?.contractAddress, args), + () => + client + ? client.accountKind({ + accountId: args.accountId, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} export interface MarsMockCreditManagerSetPositionsResponseMutation { client: MarsMockCreditManagerClient msg: { diff --git a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts index ec277576e..b20203986 100644 --- a/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts +++ b/scripts/types/generated/mars-mock-credit-manager/MarsMockCreditManager.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -64,6 +64,11 @@ export interface VaultBaseForAddr { address: Addr } export type QueryMsg = + | { + account_kind: { + account_id: string + } + } | { config: {} } @@ -138,6 +143,7 @@ export type QueryMsg = export interface VaultBaseForString { address: string } +export type AccountKind = 'default' | 'high_levered_strategy' export type ArrayOfCoinBalanceResponseItem = CoinBalanceResponseItem[] export interface CoinBalanceResponseItem { account_id: string diff --git a/scripts/types/generated/mars-mock-credit-manager/bundle.ts b/scripts/types/generated/mars-mock-credit-manager/bundle.ts index 81f661cad..77553d891 100644 --- a/scripts/types/generated/mars-mock-credit-manager/bundle.ts +++ b/scripts/types/generated/mars-mock-credit-manager/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts index cee7dd1bf..5b0c1636d 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts index 33d9badb9..cc61324fd 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts index 68035be8c..88fb79839 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts index 6c0337bf0..238619450 100644 --- a/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts +++ b/scripts/types/generated/mars-mock-oracle/MarsMockOracle.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-oracle/bundle.ts b/scripts/types/generated/mars-mock-oracle/bundle.ts index b31e62c90..5c78f5e26 100644 --- a/scripts/types/generated/mars-mock-oracle/bundle.ts +++ b/scripts/types/generated/mars-mock-oracle/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts index 41693744e..d569a8ff0 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts index 14ea5113b..283884871 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts index 118a80bcb..d0f614991 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts index 8099cd089..7b992d5b3 100644 --- a/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts +++ b/scripts/types/generated/mars-mock-red-bank/MarsMockRedBank.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-red-bank/bundle.ts b/scripts/types/generated/mars-mock-red-bank/bundle.ts index b1017421b..a3d47dd39 100644 --- a/scripts/types/generated/mars-mock-red-bank/bundle.ts +++ b/scripts/types/generated/mars-mock-red-bank/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts index 9f1748b32..1a768125a 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts index 66988e409..d2ba7f57a 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts index 6b1af7ac0..a0b667cb5 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts index f5354a687..a5f8fab3e 100644 --- a/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts +++ b/scripts/types/generated/mars-mock-vault/MarsMockVault.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-mock-vault/bundle.ts b/scripts/types/generated/mars-mock-vault/bundle.ts index 061aa5822..09436699b 100644 --- a/scripts/types/generated/mars-mock-vault/bundle.ts +++ b/scripts/types/generated/mars-mock-vault/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-params/MarsParams.client.ts b/scripts/types/generated/mars-params/MarsParams.client.ts index 7348352e3..ba1628600 100644 --- a/scripts/types/generated/mars-params/MarsParams.client.ts +++ b/scripts/types/generated/mars-params/MarsParams.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -13,20 +13,25 @@ import { ExecuteMsg, OwnerUpdate, AssetParamsUpdate, + HlsAssetTypeForString, Uint128, VaultConfigUpdate, EmergencyUpdate, - RoverEmergencyUpdate, + CmEmergencyUpdate, RedBankEmergencyUpdate, - AssetParams, + AssetParamsBaseForString, + CmSettingsForString, + HlsParamsBaseForString, RedBankSettings, - RoverSettings, - HighLeverageStrategyParams, VaultConfigBaseForString, Coin, QueryMsg, - ArrayOfAssetParams, + HlsAssetTypeForAddr, Addr, + ArrayOfAssetParamsBaseForAddr, + AssetParamsBaseForAddr, + CmSettingsForAddr, + HlsParamsBaseForAddr, ArrayOfVaultConfigBaseForAddr, VaultConfigBaseForAddr, OwnerResponse, @@ -34,14 +39,14 @@ import { export interface MarsParamsReadOnlyInterface { contractAddress: string owner: () => Promise - assetParams: ({ denom }: { denom: string }) => Promise + assetParams: ({ denom }: { denom: string }) => Promise allAssetParams: ({ limit, startAfter, }: { limit?: number startAfter?: string - }) => Promise + }) => Promise vaultConfig: ({ address }: { address: string }) => Promise allVaultConfigs: ({ limit, @@ -72,7 +77,7 @@ export class MarsParamsQueryClient implements MarsParamsReadOnlyInterface { owner: {}, }) } - assetParams = async ({ denom }: { denom: string }): Promise => { + assetParams = async ({ denom }: { denom: string }): Promise => { return this.client.queryContractSmart(this.contractAddress, { asset_params: { denom, @@ -85,7 +90,7 @@ export class MarsParamsQueryClient implements MarsParamsReadOnlyInterface { }: { limit?: number startAfter?: string - }): Promise => { + }): Promise => { return this.client.queryContractSmart(this.contractAddress, { all_asset_params: { limit, diff --git a/scripts/types/generated/mars-params/MarsParams.message-composer.ts b/scripts/types/generated/mars-params/MarsParams.message-composer.ts index d86a856c9..ce31164c8 100644 --- a/scripts/types/generated/mars-params/MarsParams.message-composer.ts +++ b/scripts/types/generated/mars-params/MarsParams.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -14,20 +14,25 @@ import { ExecuteMsg, OwnerUpdate, AssetParamsUpdate, + HlsAssetTypeForString, Uint128, VaultConfigUpdate, EmergencyUpdate, - RoverEmergencyUpdate, + CmEmergencyUpdate, RedBankEmergencyUpdate, - AssetParams, + AssetParamsBaseForString, + CmSettingsForString, + HlsParamsBaseForString, RedBankSettings, - RoverSettings, - HighLeverageStrategyParams, VaultConfigBaseForString, Coin, QueryMsg, - ArrayOfAssetParams, + HlsAssetTypeForAddr, Addr, + ArrayOfAssetParamsBaseForAddr, + AssetParamsBaseForAddr, + CmSettingsForAddr, + HlsParamsBaseForAddr, ArrayOfVaultConfigBaseForAddr, VaultConfigBaseForAddr, OwnerResponse, diff --git a/scripts/types/generated/mars-params/MarsParams.react-query.ts b/scripts/types/generated/mars-params/MarsParams.react-query.ts index c16f0644d..6f034c5a8 100644 --- a/scripts/types/generated/mars-params/MarsParams.react-query.ts +++ b/scripts/types/generated/mars-params/MarsParams.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -14,20 +14,25 @@ import { ExecuteMsg, OwnerUpdate, AssetParamsUpdate, + HlsAssetTypeForString, Uint128, VaultConfigUpdate, EmergencyUpdate, - RoverEmergencyUpdate, + CmEmergencyUpdate, RedBankEmergencyUpdate, - AssetParams, + AssetParamsBaseForString, + CmSettingsForString, + HlsParamsBaseForString, RedBankSettings, - RoverSettings, - HighLeverageStrategyParams, VaultConfigBaseForString, Coin, QueryMsg, - ArrayOfAssetParams, + HlsAssetTypeForAddr, Addr, + ArrayOfAssetParamsBaseForAddr, + AssetParamsBaseForAddr, + CmSettingsForAddr, + HlsParamsBaseForAddr, ArrayOfVaultConfigBaseForAddr, VaultConfigBaseForAddr, OwnerResponse, @@ -128,18 +133,18 @@ export function useMarsParamsVaultConfigQuery({ ) } export interface MarsParamsAllAssetParamsQuery - extends MarsParamsReactQuery { + extends MarsParamsReactQuery { args: { limit?: number startAfter?: string } } -export function useMarsParamsAllAssetParamsQuery({ +export function useMarsParamsAllAssetParamsQuery({ client, args, options, }: MarsParamsAllAssetParamsQuery) { - return useQuery( + return useQuery( marsParamsQueryKeys.allAssetParams(client?.contractAddress, args), () => client @@ -152,17 +157,17 @@ export function useMarsParamsAllAssetParamsQuery({ ) } export interface MarsParamsAssetParamsQuery - extends MarsParamsReactQuery { + extends MarsParamsReactQuery { args: { denom: string } } -export function useMarsParamsAssetParamsQuery({ +export function useMarsParamsAssetParamsQuery({ client, args, options, }: MarsParamsAssetParamsQuery) { - return useQuery( + return useQuery( marsParamsQueryKeys.assetParams(client?.contractAddress, args), () => client diff --git a/scripts/types/generated/mars-params/MarsParams.types.ts b/scripts/types/generated/mars-params/MarsParams.types.ts index e7f4dfdec..aa046b17d 100644 --- a/scripts/types/generated/mars-params/MarsParams.types.ts +++ b/scripts/types/generated/mars-params/MarsParams.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -43,29 +43,34 @@ export type OwnerUpdate = | 'clear_emergency_owner' export type AssetParamsUpdate = { add_or_update: { - params: AssetParams + params: AssetParamsBaseForString } } -export type Uint128 = string -export type VaultConfigUpdate = +export type HlsAssetTypeForString = | { - add_or_update: { - config: VaultConfigBaseForString + coin: { + denom: string } } | { - remove: { + vault: { addr: string } } +export type Uint128 = string +export type VaultConfigUpdate = { + add_or_update: { + config: VaultConfigBaseForString + } +} export type EmergencyUpdate = | { - rover: RoverEmergencyUpdate + credit_manager: CmEmergencyUpdate } | { red_bank: RedBankEmergencyUpdate } -export type RoverEmergencyUpdate = +export type CmEmergencyUpdate = | { set_zero_max_ltv_on_vault: string } @@ -78,30 +83,32 @@ export type RoverEmergencyUpdate = export type RedBankEmergencyUpdate = { disable_borrowing: string } -export interface AssetParams { +export interface AssetParamsBaseForString { + credit_manager: CmSettingsForString denom: string liquidation_bonus: Decimal liquidation_threshold: Decimal max_loan_to_value: Decimal red_bank: RedBankSettings - rover: RoverSettings } -export interface RedBankSettings { - borrow_enabled: boolean - deposit_cap: Uint128 - deposit_enabled: boolean -} -export interface RoverSettings { - hls: HighLeverageStrategyParams +export interface CmSettingsForString { + hls?: HlsParamsBaseForString | null whitelisted: boolean } -export interface HighLeverageStrategyParams { +export interface HlsParamsBaseForString { + correlations: HlsAssetTypeForString[] liquidation_threshold: Decimal max_loan_to_value: Decimal } +export interface RedBankSettings { + borrow_enabled: boolean + deposit_cap: Uint128 + deposit_enabled: boolean +} export interface VaultConfigBaseForString { addr: string deposit_cap: Coin + hls?: HlsParamsBaseForString | null liquidation_threshold: Decimal max_loan_to_value: Decimal whitelisted: boolean @@ -140,12 +147,41 @@ export type QueryMsg = | { max_close_factor: {} } -export type ArrayOfAssetParams = AssetParams[] +export type HlsAssetTypeForAddr = + | { + coin: { + denom: string + } + } + | { + vault: { + addr: Addr + } + } export type Addr = string +export type ArrayOfAssetParamsBaseForAddr = AssetParamsBaseForAddr[] +export interface AssetParamsBaseForAddr { + credit_manager: CmSettingsForAddr + denom: string + liquidation_bonus: Decimal + liquidation_threshold: Decimal + max_loan_to_value: Decimal + red_bank: RedBankSettings +} +export interface CmSettingsForAddr { + hls?: HlsParamsBaseForAddr | null + whitelisted: boolean +} +export interface HlsParamsBaseForAddr { + correlations: HlsAssetTypeForAddr[] + liquidation_threshold: Decimal + max_loan_to_value: Decimal +} export type ArrayOfVaultConfigBaseForAddr = VaultConfigBaseForAddr[] export interface VaultConfigBaseForAddr { addr: Addr deposit_cap: Coin + hls?: HlsParamsBaseForAddr | null liquidation_threshold: Decimal max_loan_to_value: Decimal whitelisted: boolean diff --git a/scripts/types/generated/mars-params/bundle.ts b/scripts/types/generated/mars-params/bundle.ts index a61c4e4f5..41fb67b93 100644 --- a/scripts/types/generated/mars-params/bundle.ts +++ b/scripts/types/generated/mars-params/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts index 50d5d7e04..aed0db28a 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.client.ts @@ -1,24 +1,26 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { + HlsAssetTypeForAddr, + Addr, Decimal, Uint128, + AccountKind, VaultPositionAmount, VaultAmount, VaultAmount1, UnlockingPositions, - Addr, HealthComputer, DenomsData, - AssetParams, + AssetParamsBaseForAddr, + CmSettingsForAddr, + HlsParamsBaseForAddr, RedBankSettings, - RoverSettings, - HighLeverageStrategyParams, Positions, DebtAmount, Coin, diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts index 50d5d7e04..aed0db28a 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.message-composer.ts @@ -1,24 +1,26 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { + HlsAssetTypeForAddr, + Addr, Decimal, Uint128, + AccountKind, VaultPositionAmount, VaultAmount, VaultAmount1, UnlockingPositions, - Addr, HealthComputer, DenomsData, - AssetParams, + AssetParamsBaseForAddr, + CmSettingsForAddr, + HlsParamsBaseForAddr, RedBankSettings, - RoverSettings, - HighLeverageStrategyParams, Positions, DebtAmount, Coin, diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts index 46d56842a..9935e4df5 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.react-query.ts @@ -1,24 +1,26 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ import { + HlsAssetTypeForAddr, + Addr, Decimal, Uint128, + AccountKind, VaultPositionAmount, VaultAmount, VaultAmount1, UnlockingPositions, - Addr, HealthComputer, DenomsData, - AssetParams, + AssetParamsBaseForAddr, + CmSettingsForAddr, + HlsParamsBaseForAddr, RedBankSettings, - RoverSettings, - HighLeverageStrategyParams, Positions, DebtAmount, Coin, diff --git a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts index 4c77aeee9..4227dd32f 100644 --- a/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts +++ b/scripts/types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types.ts @@ -1,12 +1,25 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ +export type HlsAssetTypeForAddr = + | { + coin: { + denom: string + } + } + | { + vault: { + addr: Addr + } + } +export type Addr = string export type Decimal = string export type Uint128 = string +export type AccountKind = 'default' | 'high_levered_strategy' export type VaultPositionAmount = | { unlocked: VaultAmount @@ -17,41 +30,42 @@ export type VaultPositionAmount = export type VaultAmount = string export type VaultAmount1 = string export type UnlockingPositions = VaultUnlockingPosition[] -export type Addr = string export interface HealthComputer { denoms_data: DenomsData + kind: AccountKind positions: Positions vaults_data: VaultsData } export interface DenomsData { params: { - [k: string]: AssetParams + [k: string]: AssetParamsBaseForAddr } prices: { [k: string]: Decimal } } -export interface AssetParams { +export interface AssetParamsBaseForAddr { + credit_manager: CmSettingsForAddr denom: string liquidation_bonus: Decimal liquidation_threshold: Decimal max_loan_to_value: Decimal red_bank: RedBankSettings - rover: RoverSettings -} -export interface RedBankSettings { - borrow_enabled: boolean - deposit_cap: Uint128 - deposit_enabled: boolean } -export interface RoverSettings { - hls: HighLeverageStrategyParams +export interface CmSettingsForAddr { + hls?: HlsParamsBaseForAddr | null whitelisted: boolean } -export interface HighLeverageStrategyParams { +export interface HlsParamsBaseForAddr { + correlations: HlsAssetTypeForAddr[] liquidation_threshold: Decimal max_loan_to_value: Decimal } +export interface RedBankSettings { + borrow_enabled: boolean + deposit_cap: Uint128 + deposit_enabled: boolean +} export interface Positions { account_id: string debts: DebtAmount[] @@ -100,6 +114,7 @@ export interface VaultsData { export interface VaultConfigBaseForAddr { addr: Addr deposit_cap: Coin + hls?: HlsParamsBaseForAddr | null liquidation_threshold: Decimal max_loan_to_value: Decimal whitelisted: boolean diff --git a/scripts/types/generated/mars-rover-health-computer/bundle.ts b/scripts/types/generated/mars-rover-health-computer/bundle.ts index 9dd788e81..efba31329 100644 --- a/scripts/types/generated/mars-rover-health-computer/bundle.ts +++ b/scripts/types/generated/mars-rover-health-computer/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts index 2ddbb46db..1ce2076f1 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -12,6 +12,7 @@ import { ExecuteMsg, OwnerUpdate, QueryMsg, + AccountKind, ConfigResponse, OwnerResponse, Decimal, @@ -20,7 +21,7 @@ import { } from './MarsRoverHealthTypes.types' export interface MarsRoverHealthTypesReadOnlyInterface { contractAddress: string - health: ({ accountId }: { accountId: string }) => Promise + health: ({ accountId, kind }: { accountId: string; kind: AccountKind }) => Promise config: () => Promise } export class MarsRoverHealthTypesQueryClient implements MarsRoverHealthTypesReadOnlyInterface { @@ -34,10 +35,17 @@ export class MarsRoverHealthTypesQueryClient implements MarsRoverHealthTypesRead this.config = this.config.bind(this) } - health = async ({ accountId }: { accountId: string }): Promise => { + health = async ({ + accountId, + kind, + }: { + accountId: string + kind: AccountKind + }): Promise => { return this.client.queryContractSmart(this.contractAddress, { health: { account_id: accountId, + kind, }, }) } diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts index c02672dc8..1c8ce1020 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -14,6 +14,7 @@ import { ExecuteMsg, OwnerUpdate, QueryMsg, + AccountKind, ConfigResponse, OwnerResponse, Decimal, diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts index 1f0dfed8b..196536783 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -13,6 +13,7 @@ import { ExecuteMsg, OwnerUpdate, QueryMsg, + AccountKind, ConfigResponse, OwnerResponse, Decimal, @@ -65,6 +66,7 @@ export interface MarsRoverHealthTypesHealthQuery extends MarsRoverHealthTypesReactQuery { args: { accountId: string + kind: AccountKind } } export function useMarsRoverHealthTypesHealthQuery({ @@ -78,6 +80,7 @@ export function useMarsRoverHealthTypesHealthQuery({ client ? client.health({ accountId: args.accountId, + kind: args.kind, }) : Promise.reject(new Error('Invalid client')), { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, diff --git a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts index f4f47c529..3ef810450 100644 --- a/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts +++ b/scripts/types/generated/mars-rover-health-types/MarsRoverHealthTypes.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ @@ -37,11 +37,13 @@ export type QueryMsg = | { health: { account_id: string + kind: AccountKind } } | { config: {} } +export type AccountKind = 'default' | 'high_levered_strategy' export interface ConfigResponse { credit_manager?: string | null owner_response: OwnerResponse diff --git a/scripts/types/generated/mars-rover-health-types/bundle.ts b/scripts/types/generated/mars-rover-health-types/bundle.ts index fad7ea982..3da1bcb78 100644 --- a/scripts/types/generated/mars-rover-health-types/bundle.ts +++ b/scripts/types/generated/mars-rover-health-types/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts index a1274dd1c..0f0f8b72d 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts index 777fb67c7..87590c048 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts index 40e90df32..0c7423ac8 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts index 30e4d4f09..cb948d183 100644 --- a/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts +++ b/scripts/types/generated/mars-swapper-base/MarsSwapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-swapper-base/bundle.ts b/scripts/types/generated/mars-swapper-base/bundle.ts index c61982f6f..4562d5fd9 100644 --- a/scripts/types/generated/mars-swapper-base/bundle.ts +++ b/scripts/types/generated/mars-swapper-base/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts index 5c7c301c9..76c6d1909 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts index d24202539..2c09e9b6d 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts index 37f1409e5..6f09e0720 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts index c83f91adb..d367530a7 100644 --- a/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts +++ b/scripts/types/generated/mars-v2-zapper-base/MarsV2ZapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v2-zapper-base/bundle.ts b/scripts/types/generated/mars-v2-zapper-base/bundle.ts index b1bae2705..548293c21 100644 --- a/scripts/types/generated/mars-v2-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-v2-zapper-base/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts index 8e27da25b..2055d8d69 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.client.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts index 79567708d..0324d5e01 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.message-composer.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts index 5403e5684..b1bf6aff3 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.react-query.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts index 1289af769..18457c4f4 100644 --- a/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts +++ b/scripts/types/generated/mars-v3-zapper-base/MarsV3ZapperBase.types.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/types/generated/mars-v3-zapper-base/bundle.ts b/scripts/types/generated/mars-v3-zapper-base/bundle.ts index 2e869ed1a..34eb13dff 100644 --- a/scripts/types/generated/mars-v3-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-v3-zapper-base/bundle.ts @@ -1,6 +1,6 @@ // @ts-nocheck /** - * This file was automatically generated by @cosmwasm/ts-codegen@0.30.0. + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 4adefea6d..4a0fc7c8c 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -17,11 +17,23 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.0", "@babel/compat-data@^7.22.3": +"@babel/code-frame@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" + integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== + dependencies: + "@babel/highlight" "^7.22.5" + +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.0": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.3.tgz#cd502a6a0b6e37d7ad72ce7e71a7160a3ae36f7e" integrity sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ== +"@babel/compat-data@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.5.tgz#b1f6c86a02d85d2dd3368a2b67c09add8cd0c255" + integrity sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA== + "@babel/core@7.18.10": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.10.tgz#39ad504991d77f1f3da91be0b8b949a5bc466fb8" @@ -83,6 +95,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.5.tgz#1e7bf768688acfb05cf30b2369ef855e82d984f7" + integrity sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA== + dependencies: + "@babel/types" "^7.22.5" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" @@ -90,6 +112,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.3.tgz#c9b83d1ba74e163e023f008a3d3204588a7ceb60" @@ -97,6 +126,13 @@ dependencies: "@babel/types" "^7.22.3" +"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz#a3f4758efdd0190d8927fcffd261755937c71878" + integrity sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.1": version "7.22.1" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz#bfcd6b7321ffebe33290d68550e2c9d7eb7c7a58" @@ -108,6 +144,17 @@ lru-cache "^5.1.1" semver "^6.3.0" +"@babel/helper-compilation-targets@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz#fc7319fc54c5e2fa14b2909cf3c5fd3046813e02" + integrity sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw== + dependencies: + "@babel/compat-data" "^7.22.5" + "@babel/helper-validator-option" "^7.22.5" + browserslist "^4.21.3" + lru-cache "^5.1.1" + semver "^6.3.0" + "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.1": version "7.22.1" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.1.tgz#ae3de70586cc757082ae3eba57240d42f468c41b" @@ -123,6 +170,21 @@ "@babel/helper-split-export-declaration" "^7.18.6" semver "^6.3.0" +"@babel/helper-create-class-features-plugin@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz#2192a1970ece4685fbff85b48da2c32fcb130b7c" + integrity sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.22.5" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.5" + semver "^6.3.0" + "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.1": version "7.22.1" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.1.tgz#a7ed9a8488b45b467fca353cd1a44dc5f0cf5c70" @@ -132,6 +194,15 @@ regexpu-core "^5.3.1" semver "^6.3.0" +"@babel/helper-create-regexp-features-plugin@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz#bb2bf0debfe39b831986a4efbf4066586819c6e4" + integrity sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + regexpu-core "^5.3.1" + semver "^6.3.0" + "@babel/helper-define-polyfill-provider@^0.3.2", "@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" @@ -161,6 +232,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz#ac3a56dbada59ed969d712cf527bd8271fe3eba8" integrity sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA== +"@babel/helper-environment-visitor@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" + integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== + "@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" @@ -169,6 +245,14 @@ "@babel/template" "^7.20.7" "@babel/types" "^7.21.0" +"@babel/helper-function-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" + integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== + dependencies: + "@babel/template" "^7.22.5" + "@babel/types" "^7.22.5" + "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" @@ -176,6 +260,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-member-expression-to-functions@^7.22.0": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.3.tgz#4b77a12c1b4b8e9e28736ed47d8b91f00976911f" @@ -183,6 +274,13 @@ dependencies: "@babel/types" "^7.22.3" +"@babel/helper-member-expression-to-functions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz#0a7c56117cad3372fbf8d2fb4bf8f8d64a1e76b2" + integrity sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4": version "7.21.4" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af" @@ -190,6 +288,13 @@ dependencies: "@babel/types" "^7.21.4" +"@babel/helper-module-imports@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" + integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5", "@babel/helper-module-transforms@^7.22.1": version "7.22.1" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.1.tgz#e0cad47fedcf3cae83c11021696376e2d5a50c63" @@ -204,6 +309,20 @@ "@babel/traverse" "^7.22.1" "@babel/types" "^7.22.0" +"@babel/helper-module-transforms@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz#0f65daa0716961b6e96b164034e737f60a80d2ef" + integrity sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.5" + "@babel/types" "^7.22.5" + "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" @@ -211,11 +330,23 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-optimise-call-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.21.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56" integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg== +"@babel/helper-plugin-utils@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" @@ -226,6 +357,16 @@ "@babel/helper-wrap-function" "^7.18.9" "@babel/types" "^7.18.9" +"@babel/helper-remap-async-to-generator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz#14a38141a7bf2165ad38da61d61cf27b43015da2" + integrity sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-wrap-function" "^7.22.5" + "@babel/types" "^7.22.5" + "@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7", "@babel/helper-replace-supers@^7.22.1": version "7.22.1" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.1.tgz#38cf6e56f7dc614af63a21b45565dd623f0fdc95" @@ -238,6 +379,18 @@ "@babel/traverse" "^7.22.1" "@babel/types" "^7.22.0" +"@babel/helper-replace-supers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz#71bc5fb348856dea9fdc4eafd7e2e49f585145dc" + integrity sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg== + dependencies: + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.22.5" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.5" + "@babel/types" "^7.22.5" + "@babel/helper-simple-access@^7.21.5": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee" @@ -245,6 +398,13 @@ dependencies: "@babel/types" "^7.21.5" +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers@^7.20.0": version "7.20.0" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" @@ -252,6 +412,13 @@ dependencies: "@babel/types" "^7.20.0" +"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" + integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-split-export-declaration@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" @@ -259,21 +426,43 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-split-export-declaration@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz#88cf11050edb95ed08d596f7a044462189127a08" + integrity sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-string-parser@^7.18.10", "@babel/helper-string-parser@^7.21.5": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" + integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== + "@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== +"@babel/helper-validator-option@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" + integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== + "@babel/helper-wrap-function@^7.18.9": version "7.20.5" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz#75e2d84d499a0ab3b31c33bcfe59d6b8a45f62e3" @@ -284,6 +473,16 @@ "@babel/traverse" "^7.20.5" "@babel/types" "^7.20.5" +"@babel/helper-wrap-function@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz#44d205af19ed8d872b4eefb0d2fa65f45eb34f06" + integrity sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw== + dependencies: + "@babel/helper-function-name" "^7.22.5" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.5" + "@babel/types" "^7.22.5" + "@babel/helpers@^7.18.9", "@babel/helpers@^7.22.0": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.3.tgz#53b74351da9684ea2f694bf0877998da26dd830e" @@ -302,6 +501,15 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" + integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== + dependencies: + "@babel/helper-validator-identifier" "^7.22.5" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@7.18.11": version "7.18.11" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" @@ -312,6 +520,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.4.tgz#a770e98fd785c231af9d93f6459d36770993fb32" integrity sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA== +"@babel/parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.5.tgz#721fd042f3ce1896238cf1b341c77eb7dee7dbea" + integrity sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -319,7 +532,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.3": +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e" + integrity sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.3.tgz#a75be1365c0c3188c51399a662168c1c98108659" integrity sha512-6r4yRwEnorYByILoDRnEqxtojYKuiIv9FojW2E8GUKo9eWBwbKcd9IiZOZpdyXc64RmyGGyPu3/uAcrz/dq2kQ== @@ -328,6 +548,15 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/plugin-transform-optional-chaining" "^7.22.3" +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz#fef09f9499b1f1c930da8a0c419db42167d792ca" + integrity sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.22.5" + "@babel/plugin-proposal-async-generator-functions@^7.18.10": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326" @@ -458,7 +687,12 @@ "@babel/helper-create-class-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-proposal-private-property-in-object@^7.18.6", "@babel/plugin-proposal-private-property-in-object@^7.21.0": +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + +"@babel/plugin-proposal-private-property-in-object@^7.18.6": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz#19496bd9883dd83c23c7d7fc45dcd9ad02dfa1dc" integrity sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw== @@ -525,19 +759,26 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-import-assertions@^7.18.6", "@babel/plugin-syntax-import-assertions@^7.20.0": +"@babel/plugin-syntax-import-assertions@^7.18.6": version "7.20.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== dependencies: "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-syntax-import-attributes@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.3.tgz#d7168f22b9b49a6cc1792cec78e06a18ad2e7b4b" - integrity sha512-i35jZJv6aO7hxEbIWQ41adVfOzjm9dcYDNeWlBMd8p0ZQRtNUCBrmGwZt+H5lb+oOC9a3svp956KP0oWGA1YsA== +"@babel/plugin-syntax-import-assertions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz#07d252e2aa0bc6125567f742cd58619cb14dce98" + integrity sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg== dependencies: - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-import-attributes@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz#ab840248d834410b829f569f5262b9e517555ecb" + integrity sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" @@ -560,6 +801,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.20.2" +"@babel/plugin-syntax-jsx@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" + integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -623,6 +871,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.20.2" +"@babel/plugin-syntax-typescript@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" + integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" @@ -631,24 +886,31 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-arrow-functions@^7.18.6", "@babel/plugin-transform-arrow-functions@^7.21.5": +"@babel/plugin-transform-arrow-functions@^7.18.6": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929" integrity sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA== dependencies: "@babel/helper-plugin-utils" "^7.21.5" -"@babel/plugin-transform-async-generator-functions@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.3.tgz#3ed99924c354fb9e80dabb2cc8d002c702e94527" - integrity sha512-36A4Aq48t66btydbZd5Fk0/xJqbpg/v4QWI4AH4cYHBXy9Mu42UOupZpebKFiCFNT9S9rJFcsld0gsv0ayLjtA== +"@babel/plugin-transform-arrow-functions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958" + integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw== dependencies: - "@babel/helper-environment-visitor" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-async-generator-functions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz#7336356d23380eda9a56314974f053a020dab0c3" + integrity sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg== + dependencies: + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.5" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-transform-async-to-generator@^7.18.6", "@babel/plugin-transform-async-to-generator@^7.20.7": +"@babel/plugin-transform-async-to-generator@^7.18.6": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354" integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q== @@ -657,6 +919,15 @@ "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-remap-async-to-generator" "^7.18.9" +"@babel/plugin-transform-async-to-generator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775" + integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ== + dependencies: + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.5" + "@babel/plugin-transform-block-scoped-functions@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" @@ -664,31 +935,45 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-block-scoping@^7.18.9", "@babel/plugin-transform-block-scoping@^7.21.0": +"@babel/plugin-transform-block-scoped-functions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024" + integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-block-scoping@^7.18.9": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz#e737b91037e5186ee16b76e7ae093358a5634f02" integrity sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ== dependencies: "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-class-properties@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.3.tgz#3407145e513830df77f0cef828b8b231c166fe4c" - integrity sha512-mASLsd6rhOrLZ5F3WbCxkzl67mmOnqik0zrg5W6D/X0QMW7HtvnoL1dRARLKIbMP3vXwkwziuLesPqWVGIl6Bw== +"@babel/plugin-transform-block-scoping@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz#8bfc793b3a4b2742c0983fadc1480d843ecea31b" + integrity sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-class-static-block@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.3.tgz#e352cf33567385c731a8f21192efeba760358773" - integrity sha512-5BirgNWNOx7cwbTJCOmKFJ1pZjwk5MUfMIwiBBvsirCJMZeQgs5pk6i1OlkVg+1Vef5LfBahFOrdCnAWvkVKMw== +"@babel/plugin-transform-class-properties@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz#97a56e31ad8c9dc06a0b3710ce7803d5a48cca77" + integrity sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ== dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-class-static-block@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz#3e40c46f048403472d6f4183116d5e46b1bff5ba" + integrity sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-transform-classes@^7.18.9", "@babel/plugin-transform-classes@^7.21.0": +"@babel/plugin-transform-classes@^7.18.9": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665" integrity sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ== @@ -703,7 +988,22 @@ "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.18.9", "@babel/plugin-transform-computed-properties@^7.21.5": +"@babel/plugin-transform-classes@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz#635d4e98da741fad814984639f4c0149eb0135e1" + integrity sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.5" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.18.9": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz#3a2d8bb771cd2ef1cd736435f6552fe502e11b44" integrity sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q== @@ -711,13 +1011,28 @@ "@babel/helper-plugin-utils" "^7.21.5" "@babel/template" "^7.20.7" -"@babel/plugin-transform-destructuring@^7.18.9", "@babel/plugin-transform-destructuring@^7.21.3": +"@babel/plugin-transform-computed-properties@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869" + integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/template" "^7.22.5" + +"@babel/plugin-transform-destructuring@^7.18.9": version "7.21.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz#73b46d0fd11cd6ef57dea8a381b1215f4959d401" integrity sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA== dependencies: "@babel/helper-plugin-utils" "^7.20.2" +"@babel/plugin-transform-destructuring@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz#d3aca7438f6c26c78cdd0b0ba920a336001b27cc" + integrity sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" @@ -726,6 +1041,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-dotall-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165" + integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-transform-duplicate-keys@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" @@ -733,12 +1056,19 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-dynamic-import@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.1.tgz#6c56afaf896a07026330cf39714532abed8d9ed1" - integrity sha512-rlhWtONnVBPdmt+jeewS0qSnMz/3yLFrqAP8hHC6EDcrYRSyuz9f9yQhHvVn2Ad6+yO9fHXac5piudeYrInxwQ== +"@babel/plugin-transform-duplicate-keys@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285" + integrity sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw== dependencies: - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-dynamic-import@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz#d6908a8916a810468c4edff73b5b75bda6ad393e" + integrity sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-transform-exponentiation-operator@^7.18.6": @@ -749,21 +1079,36 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-export-namespace-from@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.3.tgz#9b8700aa495007d3bebac8358d1c562434b680b9" - integrity sha512-5Ti1cHLTDnt3vX61P9KZ5IG09bFXp4cDVFJIAeCZuxu9OXXJJZp5iP0n/rzM2+iAutJY+KWEyyHcRaHlpQ/P5g== +"@babel/plugin-transform-exponentiation-operator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a" + integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g== dependencies: - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-export-namespace-from@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz#57c41cb1d0613d22f548fddd8b288eedb9973a5b" + integrity sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-transform-for-of@^7.18.8", "@babel/plugin-transform-for-of@^7.21.5": +"@babel/plugin-transform-for-of@^7.18.8": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc" integrity sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ== dependencies: "@babel/helper-plugin-utils" "^7.21.5" +"@babel/plugin-transform-for-of@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz#ab1b8a200a8f990137aff9a084f8de4099ab173f" + integrity sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-transform-function-name@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" @@ -773,12 +1118,21 @@ "@babel/helper-function-name" "^7.18.9" "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-json-strings@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.3.tgz#a181b8679cf7c93e9d0e3baa5b1776d65be601a9" - integrity sha512-IuvOMdeOOY2X4hRNAT6kwbePtK21BUyrAEgLKviL8pL6AEEVUVcqtRdN/HJXBLGIbt9T3ETmXRnFedRRmQNTYw== +"@babel/plugin-transform-function-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143" + integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg== dependencies: - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-json-strings@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz#14b64352fdf7e1f737eed68de1a1468bd2a77ec0" + integrity sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-transform-literals@^7.18.9": @@ -788,12 +1142,19 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-logical-assignment-operators@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.3.tgz#9e021455810f33b0baccb82fb759b194f5dc36f0" - integrity sha512-CbayIfOw4av2v/HYZEsH+Klks3NC2/MFIR3QR8gnpGNNPEaq2fdlVCRYG/paKs7/5hvBLQ+H70pGWOHtlNEWNA== +"@babel/plugin-transform-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920" + integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g== dependencies: - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-logical-assignment-operators@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz#66ae5f068fd5a9a5dc570df16f56c2a8462a9d6c" + integrity sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-transform-member-expression-literals@^7.18.6": @@ -803,7 +1164,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-modules-amd@^7.18.6", "@babel/plugin-transform-modules-amd@^7.20.11": +"@babel/plugin-transform-member-expression-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def" + integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-modules-amd@^7.18.6": version "7.20.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz#3daccca8e4cc309f03c3a0c4b41dc4b26f55214a" integrity sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g== @@ -811,6 +1179,14 @@ "@babel/helper-module-transforms" "^7.20.11" "@babel/helper-plugin-utils" "^7.20.2" +"@babel/plugin-transform-modules-amd@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz#4e045f55dcf98afd00f85691a68fc0780704f526" + integrity sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ== + dependencies: + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-transform-modules-commonjs@^7.18.6", "@babel/plugin-transform-modules-commonjs@^7.21.5": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz#d69fb947eed51af91de82e4708f676864e5e47bc" @@ -820,7 +1196,16 @@ "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-simple-access" "^7.21.5" -"@babel/plugin-transform-modules-systemjs@^7.18.9", "@babel/plugin-transform-modules-systemjs@^7.22.3": +"@babel/plugin-transform-modules-commonjs@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz#7d9875908d19b8c0536085af7b053fd5bd651bfa" + integrity sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA== + dependencies: + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" + +"@babel/plugin-transform-modules-systemjs@^7.18.9": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.3.tgz#cc507e03e88d87b016feaeb5dae941e6ef50d91e" integrity sha512-V21W3bKLxO3ZjcBJZ8biSvo5gQ85uIXW2vJfh7JSWf/4SLUSr1tOoHX3ruN4+Oqa2m+BKfsxTR1I+PsvkIWvNw== @@ -830,6 +1215,16 @@ "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-validator-identifier" "^7.19.1" +"@babel/plugin-transform-modules-systemjs@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz#18c31410b5e579a0092638f95c896c2a98a5d496" + integrity sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ== + dependencies: + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" + "@babel/plugin-transform-modules-umd@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" @@ -838,7 +1233,15 @@ "@babel/helper-module-transforms" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6", "@babel/plugin-transform-named-capturing-groups-regex@^7.22.3": +"@babel/plugin-transform-modules-umd@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98" + integrity sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ== + dependencies: + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.3.tgz#db6fb77e6b3b53ec3b8d370246f0b7cf67d35ab4" integrity sha512-c6HrD/LpUdNNJsISQZpds3TXvfYIAbo+efE9aWmY/PmSRD0agrJ9cPMt4BmArwUQ7ZymEWTFjTyp+yReLJZh0Q== @@ -846,39 +1249,54 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.1" "@babel/helper-plugin-utils" "^7.21.5" -"@babel/plugin-transform-new-target@^7.18.6", "@babel/plugin-transform-new-target@^7.22.3": +"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" + integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-new-target@^7.18.6": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.3.tgz#deb0377d741cbee2f45305868b9026dcd6dd96e2" integrity sha512-5RuJdSo89wKdkRTqtM9RVVJzHum9c2s0te9rB7vZC1zKKxcioWIy+xcu4OoIAjyFZhb/bp5KkunuLin1q7Ct+w== dependencies: "@babel/helper-plugin-utils" "^7.21.5" -"@babel/plugin-transform-nullish-coalescing-operator@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.3.tgz#8c519f8bf5af94a9ca6f65cf422a9d3396e542b9" - integrity sha512-CpaoNp16nX7ROtLONNuCyenYdY/l7ZsR6aoVa7rW7nMWisoNoQNIH5Iay/4LDyRjKMuElMqXiBoOQCDLTMGZiw== +"@babel/plugin-transform-new-target@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d" + integrity sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw== dependencies: - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz#f8872c65776e0b552e0849d7596cddd416c3e381" + integrity sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-transform-numeric-separator@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.3.tgz#02493070ca6685884b0eee705363ee4da2132ab0" - integrity sha512-+AF88fPDJrnseMh5vD9+SH6wq4ZMvpiTMHh58uLs+giMEyASFVhcT3NkoyO+NebFCNnpHJEq5AXO2txV4AGPDQ== +"@babel/plugin-transform-numeric-separator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz#57226a2ed9e512b9b446517ab6fa2d17abb83f58" + integrity sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g== dependencies: - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-transform-object-rest-spread@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.3.tgz#da6fba693effb8c203d8c3bdf7bf4e2567e802e9" - integrity sha512-38bzTsqMMCI46/TQnJwPPpy33EjLCc1Gsm2hRTF6zTMWnKsN61vdrpuzIEGQyKEhDSYDKyZHrrd5FMj4gcUHhw== +"@babel/plugin-transform-object-rest-spread@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz#9686dc3447df4753b0b2a2fae7e8bc33cdc1f2e1" + integrity sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ== dependencies: - "@babel/compat-data" "^7.22.3" - "@babel/helper-compilation-targets" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/compat-data" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.22.3" + "@babel/plugin-transform-parameters" "^7.22.5" "@babel/plugin-transform-object-super@^7.18.6": version "7.18.6" @@ -888,12 +1306,20 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-replace-supers" "^7.18.6" -"@babel/plugin-transform-optional-catch-binding@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.3.tgz#e971a083fc7d209d9cd18253853af1db6d8dc42f" - integrity sha512-bnDFWXFzWY0BsOyqaoSXvMQ2F35zutQipugog/rqotL2S4ciFOKlRYUu9djt4iq09oh2/34hqfRR2k1dIvuu4g== +"@babel/plugin-transform-object-super@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c" + integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw== dependencies: - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.5" + +"@babel/plugin-transform-optional-catch-binding@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz#842080be3076703be0eaf32ead6ac8174edee333" + integrity sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-transform-optional-chaining@^7.22.3": @@ -905,29 +1331,45 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-transform-parameters@^7.18.8", "@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.22.3": +"@babel/plugin-transform-optional-chaining@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz#1003762b9c14295501beb41be72426736bedd1e0" + integrity sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.18.8", "@babel/plugin-transform-parameters@^7.20.7": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.3.tgz#24477acfd2fd2bc901df906c9bf17fbcfeee900d" integrity sha512-x7QHQJHPuD9VmfpzboyGJ5aHEr9r7DsAsdxdhJiTB3J3j8dyl+NFZ+rX5Q2RWFDCs61c06qBfS4ys2QYn8UkMw== dependencies: "@babel/helper-plugin-utils" "^7.21.5" -"@babel/plugin-transform-private-methods@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.3.tgz#adac38020bab5047482d3297107c1f58e9c574f6" - integrity sha512-fC7jtjBPFqhqpPAE+O4LKwnLq7gGkD3ZmC2E3i4qWH34mH3gOg2Xrq5YMHUq6DM30xhqM1DNftiRaSqVjEG+ug== +"@babel/plugin-transform-parameters@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz#c3542dd3c39b42c8069936e48717a8d179d63a18" + integrity sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-private-property-in-object@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.3.tgz#031621b02c7b7d95389de1a3dba2fe9e8c548e56" - integrity sha512-C7MMl4qWLpgVCbXfj3UW8rR1xeCnisQ0cU7YJHV//8oNBS0aCIVg1vFnZXxOckHhEpQyqNNkWmvSEWnMLlc+Vw== +"@babel/plugin-transform-private-methods@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz#21c8af791f76674420a147ae62e9935d790f8722" + integrity sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-private-property-in-object@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz#07a77f28cbb251546a43d175a1dda4cf3ef83e32" + integrity sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-transform-property-literals@^7.18.6": @@ -937,7 +1379,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-regenerator@^7.18.6", "@babel/plugin-transform-regenerator@^7.21.5": +"@babel/plugin-transform-property-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766" + integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-regenerator@^7.18.6": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz#576c62f9923f94bcb1c855adc53561fd7913724e" integrity sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w== @@ -945,6 +1394,14 @@ "@babel/helper-plugin-utils" "^7.21.5" regenerator-transform "^0.15.1" +"@babel/plugin-transform-regenerator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz#cd8a68b228a5f75fa01420e8cc2fc400f0fc32aa" + integrity sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + regenerator-transform "^0.15.1" + "@babel/plugin-transform-reserved-words@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" @@ -952,6 +1409,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-reserved-words@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb" + integrity sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-transform-runtime@7.18.10": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz#37d14d1fa810a368fd635d4d1476c0154144a96f" @@ -971,7 +1435,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-spread@^7.18.9", "@babel/plugin-transform-spread@^7.20.7": +"@babel/plugin-transform-shorthand-properties@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624" + integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-spread@^7.18.9": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz#c2d83e0b99d3bf83e07b11995ee24bf7ca09401e" integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw== @@ -979,6 +1450,14 @@ "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" +"@babel/plugin-transform-spread@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b" + integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-sticky-regex@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" @@ -986,6 +1465,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-sticky-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa" + integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-transform-template-literals@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" @@ -993,6 +1479,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-template-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff" + integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-transform-typeof-symbol@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" @@ -1000,6 +1493,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-typeof-symbol@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34" + integrity sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-transform-typescript@^7.21.3": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.3.tgz#8f662cec8ba88c873f1c7663c0c94e3f68592f09" @@ -1010,20 +1510,37 @@ "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-syntax-typescript" "^7.21.4" -"@babel/plugin-transform-unicode-escapes@^7.18.10", "@babel/plugin-transform-unicode-escapes@^7.21.5": +"@babel/plugin-transform-typescript@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.5.tgz#5c0f7adfc1b5f38c4dbc8f79b1f0f8074134bd7d" + integrity sha512-SMubA9S7Cb5sGSFFUlqxyClTA9zWJ8qGQrppNUm05LtFuN1ELRFNndkix4zUJrC9F+YivWwa1dHMSyo0e0N9dA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-typescript" "^7.22.5" + +"@babel/plugin-transform-unicode-escapes@^7.18.10": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz#1e55ed6195259b0e9061d81f5ef45a9b009fb7f2" integrity sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg== dependencies: "@babel/helper-plugin-utils" "^7.21.5" -"@babel/plugin-transform-unicode-property-regex@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.3.tgz#597b6a614dc93eaae605ee293e674d79d32eb380" - integrity sha512-5ScJ+OmdX+O6HRuMGW4kv7RL9vIKdtdAj9wuWUKy1wbHY3jaM/UlyIiC1G7J6UJiiyMukjjK0QwL3P0vBd0yYg== +"@babel/plugin-transform-unicode-escapes@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz#ce0c248522b1cb22c7c992d88301a5ead70e806c" + integrity sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-property-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz#098898f74d5c1e86660dc112057b2d11227f1c81" + integrity sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-unicode-regex@^7.18.6": version "7.18.6" @@ -1033,13 +1550,21 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-unicode-sets-regex@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.3.tgz#7c14ee33fa69782b0101d0f7143d3fc73ce00700" - integrity sha512-hNufLdkF8vqywRp+P55j4FHXqAX2LRUccoZHH7AFn1pq5ZOO2ISKW9w13bFZVjBoTqeve2HOgoJCcaziJVhGNw== +"@babel/plugin-transform-unicode-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183" + integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-sets-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz#77788060e511b708ffc7d42fdfbc5b37c3004e91" + integrity sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/preset-env@7.18.10": version "7.18.10" @@ -1122,25 +1647,25 @@ core-js-compat "^3.22.1" semver "^6.3.0" -"@babel/preset-env@^7.22.4": - version "7.22.4" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.4.tgz#c86a82630f0e8c61d9bb9327b7b896732028cbed" - integrity sha512-c3lHOjbwBv0TkhYCr+XCR6wKcSZ1QbQTVdSkZUaVpLv8CVWotBMArWUi5UAJrcrQaEnleVkkvaV8F/pmc/STZQ== - dependencies: - "@babel/compat-data" "^7.22.3" - "@babel/helper-compilation-targets" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/helper-validator-option" "^7.21.0" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.3" - "@babel/plugin-proposal-private-property-in-object" "^7.21.0" +"@babel/preset-env@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.5.tgz#3da66078b181f3d62512c51cf7014392c511504e" + integrity sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A== + dependencies: + "@babel/compat-data" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.5" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.5" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.5" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" "@babel/plugin-syntax-class-static-block" "^7.14.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.20.0" - "@babel/plugin-syntax-import-attributes" "^7.22.3" + "@babel/plugin-syntax-import-assertions" "^7.22.5" + "@babel/plugin-syntax-import-attributes" "^7.22.5" "@babel/plugin-syntax-import-meta" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" @@ -1152,56 +1677,56 @@ "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.21.5" - "@babel/plugin-transform-async-generator-functions" "^7.22.3" - "@babel/plugin-transform-async-to-generator" "^7.20.7" - "@babel/plugin-transform-block-scoped-functions" "^7.18.6" - "@babel/plugin-transform-block-scoping" "^7.21.0" - "@babel/plugin-transform-class-properties" "^7.22.3" - "@babel/plugin-transform-class-static-block" "^7.22.3" - "@babel/plugin-transform-classes" "^7.21.0" - "@babel/plugin-transform-computed-properties" "^7.21.5" - "@babel/plugin-transform-destructuring" "^7.21.3" - "@babel/plugin-transform-dotall-regex" "^7.18.6" - "@babel/plugin-transform-duplicate-keys" "^7.18.9" - "@babel/plugin-transform-dynamic-import" "^7.22.1" - "@babel/plugin-transform-exponentiation-operator" "^7.18.6" - "@babel/plugin-transform-export-namespace-from" "^7.22.3" - "@babel/plugin-transform-for-of" "^7.21.5" - "@babel/plugin-transform-function-name" "^7.18.9" - "@babel/plugin-transform-json-strings" "^7.22.3" - "@babel/plugin-transform-literals" "^7.18.9" - "@babel/plugin-transform-logical-assignment-operators" "^7.22.3" - "@babel/plugin-transform-member-expression-literals" "^7.18.6" - "@babel/plugin-transform-modules-amd" "^7.20.11" - "@babel/plugin-transform-modules-commonjs" "^7.21.5" - "@babel/plugin-transform-modules-systemjs" "^7.22.3" - "@babel/plugin-transform-modules-umd" "^7.18.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.3" - "@babel/plugin-transform-new-target" "^7.22.3" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.3" - "@babel/plugin-transform-numeric-separator" "^7.22.3" - "@babel/plugin-transform-object-rest-spread" "^7.22.3" - "@babel/plugin-transform-object-super" "^7.18.6" - "@babel/plugin-transform-optional-catch-binding" "^7.22.3" - "@babel/plugin-transform-optional-chaining" "^7.22.3" - "@babel/plugin-transform-parameters" "^7.22.3" - "@babel/plugin-transform-private-methods" "^7.22.3" - "@babel/plugin-transform-private-property-in-object" "^7.22.3" - "@babel/plugin-transform-property-literals" "^7.18.6" - "@babel/plugin-transform-regenerator" "^7.21.5" - "@babel/plugin-transform-reserved-words" "^7.18.6" - "@babel/plugin-transform-shorthand-properties" "^7.18.6" - "@babel/plugin-transform-spread" "^7.20.7" - "@babel/plugin-transform-sticky-regex" "^7.18.6" - "@babel/plugin-transform-template-literals" "^7.18.9" - "@babel/plugin-transform-typeof-symbol" "^7.18.9" - "@babel/plugin-transform-unicode-escapes" "^7.21.5" - "@babel/plugin-transform-unicode-property-regex" "^7.22.3" - "@babel/plugin-transform-unicode-regex" "^7.18.6" - "@babel/plugin-transform-unicode-sets-regex" "^7.22.3" + "@babel/plugin-transform-arrow-functions" "^7.22.5" + "@babel/plugin-transform-async-generator-functions" "^7.22.5" + "@babel/plugin-transform-async-to-generator" "^7.22.5" + "@babel/plugin-transform-block-scoped-functions" "^7.22.5" + "@babel/plugin-transform-block-scoping" "^7.22.5" + "@babel/plugin-transform-class-properties" "^7.22.5" + "@babel/plugin-transform-class-static-block" "^7.22.5" + "@babel/plugin-transform-classes" "^7.22.5" + "@babel/plugin-transform-computed-properties" "^7.22.5" + "@babel/plugin-transform-destructuring" "^7.22.5" + "@babel/plugin-transform-dotall-regex" "^7.22.5" + "@babel/plugin-transform-duplicate-keys" "^7.22.5" + "@babel/plugin-transform-dynamic-import" "^7.22.5" + "@babel/plugin-transform-exponentiation-operator" "^7.22.5" + "@babel/plugin-transform-export-namespace-from" "^7.22.5" + "@babel/plugin-transform-for-of" "^7.22.5" + "@babel/plugin-transform-function-name" "^7.22.5" + "@babel/plugin-transform-json-strings" "^7.22.5" + "@babel/plugin-transform-literals" "^7.22.5" + "@babel/plugin-transform-logical-assignment-operators" "^7.22.5" + "@babel/plugin-transform-member-expression-literals" "^7.22.5" + "@babel/plugin-transform-modules-amd" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.22.5" + "@babel/plugin-transform-modules-systemjs" "^7.22.5" + "@babel/plugin-transform-modules-umd" "^7.22.5" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" + "@babel/plugin-transform-new-target" "^7.22.5" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.5" + "@babel/plugin-transform-numeric-separator" "^7.22.5" + "@babel/plugin-transform-object-rest-spread" "^7.22.5" + "@babel/plugin-transform-object-super" "^7.22.5" + "@babel/plugin-transform-optional-catch-binding" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.22.5" + "@babel/plugin-transform-parameters" "^7.22.5" + "@babel/plugin-transform-private-methods" "^7.22.5" + "@babel/plugin-transform-private-property-in-object" "^7.22.5" + "@babel/plugin-transform-property-literals" "^7.22.5" + "@babel/plugin-transform-regenerator" "^7.22.5" + "@babel/plugin-transform-reserved-words" "^7.22.5" + "@babel/plugin-transform-shorthand-properties" "^7.22.5" + "@babel/plugin-transform-spread" "^7.22.5" + "@babel/plugin-transform-sticky-regex" "^7.22.5" + "@babel/plugin-transform-template-literals" "^7.22.5" + "@babel/plugin-transform-typeof-symbol" "^7.22.5" + "@babel/plugin-transform-unicode-escapes" "^7.22.5" + "@babel/plugin-transform-unicode-property-regex" "^7.22.5" + "@babel/plugin-transform-unicode-regex" "^7.22.5" + "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.22.4" + "@babel/types" "^7.22.5" babel-plugin-polyfill-corejs2 "^0.4.3" babel-plugin-polyfill-corejs3 "^0.8.1" babel-plugin-polyfill-regenerator "^0.5.0" @@ -1219,7 +1744,7 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-typescript@^7.18.6", "@babel/preset-typescript@^7.21.5": +"@babel/preset-typescript@^7.18.6": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.5.tgz#68292c884b0e26070b4d66b202072d391358395f" integrity sha512-iqe3sETat5EOrORXiQ6rWfoOg2y68Cs75B9wNxdPW4kixJxh7aXQE1KPdWLDniC24T/6dSnguF33W9j/ZZQcmA== @@ -1230,6 +1755,17 @@ "@babel/plugin-transform-modules-commonjs" "^7.21.5" "@babel/plugin-transform-typescript" "^7.21.3" +"@babel/preset-typescript@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz#16367d8b01d640e9a507577ed4ee54e0101e51c8" + integrity sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.5" + "@babel/plugin-syntax-jsx" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.22.5" + "@babel/plugin-transform-typescript" "^7.22.5" + "@babel/regjsgen@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" @@ -1251,6 +1787,15 @@ "@babel/parser" "^7.21.9" "@babel/types" "^7.21.5" +"@babel/template@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" + integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== + dependencies: + "@babel/code-frame" "^7.22.5" + "@babel/parser" "^7.22.5" + "@babel/types" "^7.22.5" + "@babel/traverse@7.18.11": version "7.18.11" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.11.tgz#3d51f2afbd83ecf9912bcbb5c4d94e3d2ddaa16f" @@ -1283,6 +1828,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.5.tgz#44bd276690db6f4940fdb84e1cb4abd2f729ccd1" + integrity sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ== + dependencies: + "@babel/code-frame" "^7.22.5" + "@babel/generator" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.5" + "@babel/parser" "^7.22.5" + "@babel/types" "^7.22.5" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@7.18.10": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.10.tgz#4908e81b6b339ca7c6b7a555a5fc29446f26dde6" @@ -1301,6 +1862,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" + integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1447,10 +2017,10 @@ resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.30.1.tgz#6d92582341be3c2ec8d82090253cfa4b7f959edb" integrity sha512-KvvX58MGMWh7xA+N+deCfunkA/ZNDvFLw4YbOmX3f/XBIkqrVY7qlotfy2aNb1kgp6h4B6Yc8YawJPDTfvWX7g== -"@cosmwasm/ts-codegen@^0.30.0": - version "0.30.0" - resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.30.0.tgz#9910c87471801e05475806b18428400444830160" - integrity sha512-BR2HuHUx+PgMj6KNLTwH1HNVQ5SYveJst5XxkeAGMuOSe4Rbe+0XDmq+jEbrLXK0ZRkCFY0OwcLKMhjsJOo4jA== +"@cosmwasm/ts-codegen@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@cosmwasm/ts-codegen/-/ts-codegen-0.30.1.tgz#8b8a635273065261c608a76f8a50cb5045e23980" + integrity sha512-6ATbmtuK2MwG9fJxIi0M+Rwd0SQhsko2nA8qVXC9MRHpZJKNaXYYcof1fel/L5HJCjotmQVsoxons3rGg6dRnw== dependencies: "@babel/core" "7.18.10" "@babel/generator" "7.18.12" @@ -1478,7 +2048,7 @@ parse-package-name "1.0.0" rimraf "3.0.2" shelljs "0.8.5" - wasm-ast-types "^0.23.0" + wasm-ast-types "^0.23.1" "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" @@ -1507,15 +2077,15 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.41.0": - version "8.41.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.41.0.tgz#080321c3b68253522f7646b55b577dd99d2950b3" - integrity sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA== +"@eslint/js@8.42.0": + version "8.42.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6" + integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw== -"@humanwhocodes/config-array@^0.11.8": - version "0.11.8" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" - integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== +"@humanwhocodes/config-array@^0.11.10": + version "0.11.10" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" + integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" @@ -2032,10 +2602,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.5.1": - version "29.5.1" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.1.tgz#83c818aa9a87da27d6da85d3378e5a34d2f31a47" - integrity sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ== +"@types/jest@^29.5.2": + version "29.5.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.2.tgz#86b4afc86e3a8f3005b297ed8a72494f89e6395b" + integrity sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -2092,15 +2662,15 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.59.8": - version "5.59.8" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.8.tgz#1e7a3e5318ece22251dfbc5c9c6feeb4793cc509" - integrity sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ== +"@typescript-eslint/eslint-plugin@^5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.9.tgz#2604cfaf2b306e120044f901e20c8ed926debf15" + integrity sha512-4uQIBq1ffXd2YvF7MAvehWKW3zVv/w+mSfRAu+8cKbfj3nwzyqJLNcZJpQ/WZ1HLbJDiowwmQ6NO+63nCA+fqA== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.59.8" - "@typescript-eslint/type-utils" "5.59.8" - "@typescript-eslint/utils" "5.59.8" + "@typescript-eslint/scope-manager" "5.59.9" + "@typescript-eslint/type-utils" "5.59.9" + "@typescript-eslint/utils" "5.59.9" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -2108,72 +2678,72 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.59.8": - version "5.59.8" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.8.tgz#60cbb00671d86cf746044ab797900b1448188567" - integrity sha512-AnR19RjJcpjoeGojmwZtCwBX/RidqDZtzcbG3xHrmz0aHHoOcbWnpDllenRDmDvsV0RQ6+tbb09/kyc+UT9Orw== +"@typescript-eslint/parser@^5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.9.tgz#a85c47ccdd7e285697463da15200f9a8561dd5fa" + integrity sha512-FsPkRvBtcLQ/eVK1ivDiNYBjn3TGJdXy2fhXX+rc7czWl4ARwnpArwbihSOHI2Peg9WbtGHrbThfBUkZZGTtvQ== dependencies: - "@typescript-eslint/scope-manager" "5.59.8" - "@typescript-eslint/types" "5.59.8" - "@typescript-eslint/typescript-estree" "5.59.8" + "@typescript-eslint/scope-manager" "5.59.9" + "@typescript-eslint/types" "5.59.9" + "@typescript-eslint/typescript-estree" "5.59.9" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.59.8": - version "5.59.8" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.8.tgz#ff4ad4fec6433647b817c4a7d4b4165d18ea2fa8" - integrity sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig== +"@typescript-eslint/scope-manager@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.9.tgz#eadce1f2733389cdb58c49770192c0f95470d2f4" + integrity sha512-8RA+E+w78z1+2dzvK/tGZ2cpGigBZ58VMEHDZtpE1v+LLjzrYGc8mMaTONSxKyEkz3IuXFM0IqYiGHlCsmlZxQ== dependencies: - "@typescript-eslint/types" "5.59.8" - "@typescript-eslint/visitor-keys" "5.59.8" + "@typescript-eslint/types" "5.59.9" + "@typescript-eslint/visitor-keys" "5.59.9" -"@typescript-eslint/type-utils@5.59.8": - version "5.59.8" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.8.tgz#aa6c029a9d7706d26bbd25eb4666398781df6ea2" - integrity sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA== +"@typescript-eslint/type-utils@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.9.tgz#53bfaae2e901e6ac637ab0536d1754dfef4dafc2" + integrity sha512-ksEsT0/mEHg9e3qZu98AlSrONAQtrSTljL3ow9CGej8eRo7pe+yaC/mvTjptp23Xo/xIf2mLZKC6KPv4Sji26Q== dependencies: - "@typescript-eslint/typescript-estree" "5.59.8" - "@typescript-eslint/utils" "5.59.8" + "@typescript-eslint/typescript-estree" "5.59.9" + "@typescript-eslint/utils" "5.59.9" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.59.8": - version "5.59.8" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.8.tgz#212e54414733618f5d0fd50b2da2717f630aebf8" - integrity sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w== +"@typescript-eslint/types@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.9.tgz#3b4e7ae63718ce1b966e0ae620adc4099a6dcc52" + integrity sha512-uW8H5NRgTVneSVTfiCVffBb8AbwWSKg7qcA4Ot3JI3MPCJGsB4Db4BhvAODIIYE5mNj7Q+VJkK7JxmRhk2Lyjw== -"@typescript-eslint/typescript-estree@5.59.8": - version "5.59.8" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.8.tgz#801a7b1766481629481b3b0878148bd7a1f345d7" - integrity sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg== +"@typescript-eslint/typescript-estree@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.9.tgz#6bfea844e468427b5e72034d33c9fffc9557392b" + integrity sha512-pmM0/VQ7kUhd1QyIxgS+aRvMgw+ZljB3eDb+jYyp6d2bC0mQWLzUDF+DLwCTkQ3tlNyVsvZRXjFyV0LkU/aXjA== dependencies: - "@typescript-eslint/types" "5.59.8" - "@typescript-eslint/visitor-keys" "5.59.8" + "@typescript-eslint/types" "5.59.9" + "@typescript-eslint/visitor-keys" "5.59.9" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.59.8": - version "5.59.8" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.8.tgz#34d129f35a2134c67fdaf024941e8f96050dca2b" - integrity sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg== +"@typescript-eslint/utils@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.9.tgz#adee890107b5ffe02cd46fdaa6c2125fb3c6c7c4" + integrity sha512-1PuMYsju/38I5Ggblaeb98TOoUvjhRvLpLa1DoTOFaLWqaXl/1iQ1eGurTXgBY58NUdtfTXKP5xBq7q9NDaLKg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.59.8" - "@typescript-eslint/types" "5.59.8" - "@typescript-eslint/typescript-estree" "5.59.8" + "@typescript-eslint/scope-manager" "5.59.9" + "@typescript-eslint/types" "5.59.9" + "@typescript-eslint/typescript-estree" "5.59.9" eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.59.8": - version "5.59.8" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz#aa6a7ef862add919401470c09e1609392ef3cc40" - integrity sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ== +"@typescript-eslint/visitor-keys@5.59.9": + version "5.59.9" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.9.tgz#9f86ef8e95aca30fb5a705bb7430f95fc58b146d" + integrity sha512-bT7s0td97KMaLwpEBckbzj/YohnvXtqbe2XgqNvTl6RJVakY5mvENOTPvw5u66nljfZxthESpDozs86U+oLY8Q== dependencies: - "@typescript-eslint/types" "5.59.8" + "@typescript-eslint/types" "5.59.9" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -2926,16 +3496,16 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== -eslint@^8.41.0: - version "8.41.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.41.0.tgz#3062ca73363b4714b16dbc1e60f035e6134b6f1c" - integrity sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q== +eslint@^8.42.0: + version "8.42.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.42.0.tgz#7bebdc3a55f9ed7167251fe7259f75219cade291" + integrity sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" "@eslint/eslintrc" "^2.0.3" - "@eslint/js" "8.41.0" - "@humanwhocodes/config-array" "^0.11.8" + "@eslint/js" "8.42.0" + "@humanwhocodes/config-array" "^0.11.10" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" ajv "^6.10.0" @@ -5112,10 +5682,10 @@ type@^2.7.2: resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== -typescript@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" - integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== +typescript@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" + integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" @@ -5193,10 +5763,10 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -wasm-ast-types@^0.23.0: - version "0.23.0" - resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.23.0.tgz#fa4f674d9b9c7141d685d70f7adcd52a6f20c68c" - integrity sha512-WNGOmIBgIQefGOJWZQ4dKpASpnb7+SNnJNlyvcXpYel7ozjhpgIhWw0+udCquNHzRAA6KApMFBHMa4k2HIzbUw== +wasm-ast-types@^0.23.1: + version "0.23.1" + resolved "https://registry.yarnpkg.com/wasm-ast-types/-/wasm-ast-types-0.23.1.tgz#b849629aa9f7a56dbb52c581ddbcb6b5ed7e2a07" + integrity sha512-igLcEk8VHZq62ZEwn4Jp+WRTp2D9yvTeiQd2Pc+s7LZouzSn3CwRpD42sHK2wV0UlSt2/cNbV6QywFm9Z6eM/A== dependencies: "@babel/runtime" "^7.18.9" "@babel/types" "7.18.10" From 4e7bdece8b8da87ac70e0d52361f905ec7979e09 Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 16 Jun 2023 18:51:52 +0200 Subject: [PATCH 164/218] Add swapper osmosis types (#143) --- contracts/swapper/osmosis/examples/schema.rs | 11 + schema.Makefile.toml | 1 + .../mars-swapper-osmosis.json | 513 ++++++++++++++++++ .../MarsSwapperOsmosis.client.ts | 288 ++++++++++ .../MarsSwapperOsmosis.message-composer.ts | 187 +++++++ .../MarsSwapperOsmosis.react-query.ts | 246 +++++++++ .../MarsSwapperOsmosis.types.ts | 105 ++++ .../generated/mars-swapper-osmosis/bundle.ts | 14 + .../generated/mars-v2-zapper-base/bundle.ts | 10 +- .../generated/mars-v3-zapper-base/bundle.ts | 10 +- 10 files changed, 1375 insertions(+), 10 deletions(-) create mode 100644 contracts/swapper/osmosis/examples/schema.rs create mode 100644 schemas/mars-swapper-osmosis/mars-swapper-osmosis.json create mode 100644 scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client.ts create mode 100644 scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.message-composer.ts create mode 100644 scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.react-query.ts create mode 100644 scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.types.ts create mode 100644 scripts/types/generated/mars-swapper-osmosis/bundle.ts diff --git a/contracts/swapper/osmosis/examples/schema.rs b/contracts/swapper/osmosis/examples/schema.rs new file mode 100644 index 000000000..136a9f4a3 --- /dev/null +++ b/contracts/swapper/osmosis/examples/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use mars_rover::adapters::swap::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use mars_swapper_osmosis::route::OsmosisRoute; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/schema.Makefile.toml b/schema.Makefile.toml index 737eb5bc9..b8cea69c8 100644 --- a/schema.Makefile.toml +++ b/schema.Makefile.toml @@ -14,6 +14,7 @@ fn main() -> std::io::Result<()> { "mars-credit-manager", "mars-account-nft", "mars-swapper-base", + "mars-swapper-osmosis", "mars-v2-zapper-base", "mars-v3-zapper-base", "mars-mock-red-bank", diff --git a/schemas/mars-swapper-osmosis/mars-swapper-osmosis.json b/schemas/mars-swapper-osmosis/mars-swapper-osmosis.json new file mode 100644 index 000000000..83be6070f --- /dev/null +++ b/schemas/mars-swapper-osmosis/mars-swapper-osmosis.json @@ -0,0 +1,513 @@ +{ + "contract_name": "mars-swapper-osmosis", + "contract_version": "2.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "description": "The contract's owner, who can update config", + "type": "string" + } + }, + "additionalProperties": false + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Manges owner role state", + "type": "object", + "required": [ + "update_owner" + ], + "properties": { + "update_owner": { + "$ref": "#/definitions/OwnerUpdate" + } + }, + "additionalProperties": false + }, + { + "description": "Configure the route for swapping an asset\n\nThis is chain-specific, and can include parameters such as slippage tolerance and the routes for multi-step swaps", + "type": "object", + "required": [ + "set_route" + ], + "properties": { + "set_route": { + "type": "object", + "required": [ + "denom_in", + "denom_out", + "route" + ], + "properties": { + "denom_in": { + "type": "string" + }, + "denom_out": { + "type": "string" + }, + "route": { + "$ref": "#/definitions/OsmosisRoute" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Perform a swapper with an exact-in amount. Requires slippage allowance %.", + "type": "object", + "required": [ + "swap_exact_in" + ], + "properties": { + "swap_exact_in": { + "type": "object", + "required": [ + "coin_in", + "denom_out", + "slippage" + ], + "properties": { + "coin_in": { + "$ref": "#/definitions/Coin" + }, + "denom_out": { + "type": "string" + }, + "slippage": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Send swapper results back to swapper. Also refunds extra if sent more than needed. Internal use only.", + "type": "object", + "required": [ + "transfer_result" + ], + "properties": { + "transfer_result": { + "type": "object", + "required": [ + "denom_in", + "denom_out", + "recipient" + ], + "properties": { + "denom_in": { + "type": "string" + }, + "denom_out": { + "type": "string" + }, + "recipient": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "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" + }, + "OsmosisRoute": { + "type": "array", + "items": { + "$ref": "#/definitions/SwapAmountInRoute" + } + }, + "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" + ] + }, + { + "description": "A separate entity managed by Owner that can be used for granting specific emergency powers.", + "type": "object", + "required": [ + "set_emergency_owner" + ], + "properties": { + "set_emergency_owner": { + "type": "object", + "required": [ + "emergency_owner" + ], + "properties": { + "emergency_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove the entity in the Emergency Owner role", + "type": "string", + "enum": [ + "clear_emergency_owner" + ] + } + ] + }, + "SwapAmountInRoute": { + "type": "object", + "required": [ + "pool_id", + "token_out_denom" + ], + "properties": { + "pool_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token_out_denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Query contract owner config", + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Get route for swapping an input denom into an output denom", + "type": "object", + "required": [ + "route" + ], + "properties": { + "route": { + "type": "object", + "required": [ + "denom_in", + "denom_out" + ], + "properties": { + "denom_in": { + "type": "string" + }, + "denom_out": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Enumerate all swapper routes", + "type": "object", + "required": [ + "routes" + ], + "properties": { + "routes": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return current spot price swapping In for Out Warning: Do not use this as an oracle price feed. Use Mars-Oracle for pricing.", + "type": "object", + "required": [ + "estimate_exact_in_swap" + ], + "properties": { + "estimate_exact_in_swap": { + "type": "object", + "required": [ + "coin_in", + "denom_out" + ], + "properties": { + "coin_in": { + "$ref": "#/definitions/Coin" + }, + "denom_out": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "migrate": null, + "sudo": null, + "responses": { + "estimate_exact_in_swap": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "EstimateExactInSwapResponse", + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "owner": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OwnerResponse", + "description": "Returned from Owner.query()", + "type": "object", + "required": [ + "abolished", + "initialized" + ], + "properties": { + "abolished": { + "type": "boolean" + }, + "emergency_owner": { + "type": [ + "string", + "null" + ] + }, + "initialized": { + "type": "boolean" + }, + "owner": { + "type": [ + "string", + "null" + ] + }, + "proposed": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "route": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RouteResponse_for_Empty", + "type": "object", + "required": [ + "denom_in", + "denom_out", + "route" + ], + "properties": { + "denom_in": { + "type": "string" + }, + "denom_out": { + "type": "string" + }, + "route": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false, + "definitions": { + "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" + } + } + }, + "routes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_RouteResponse_for_Empty", + "type": "array", + "items": { + "$ref": "#/definitions/RouteResponse_for_Empty" + }, + "definitions": { + "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" + }, + "RouteResponse_for_Empty": { + "type": "object", + "required": [ + "denom_in", + "denom_out", + "route" + ], + "properties": { + "denom_in": { + "type": "string" + }, + "denom_out": { + "type": "string" + }, + "route": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + } + } + } + } +} diff --git a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client.ts b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client.ts new file mode 100644 index 000000000..42a2e06e5 --- /dev/null +++ b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client.ts @@ -0,0 +1,288 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + OwnerUpdate, + OsmosisRoute, + Uint128, + Decimal, + Addr, + SwapAmountInRoute, + Coin, + QueryMsg, + EstimateExactInSwapResponse, + OwnerResponse, + RouteResponseForEmpty, + Empty, + ArrayOfRouteResponseForEmpty, +} from './MarsSwapperOsmosis.types' +export interface MarsSwapperOsmosisReadOnlyInterface { + contractAddress: string + owner: () => Promise + route: ({ + denomIn, + denomOut, + }: { + denomIn: string + denomOut: string + }) => Promise + routes: ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }) => Promise + estimateExactInSwap: ({ + coinIn, + denomOut, + }: { + coinIn: Coin + denomOut: string + }) => Promise +} +export class MarsSwapperOsmosisQueryClient implements MarsSwapperOsmosisReadOnlyInterface { + client: CosmWasmClient + contractAddress: string + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client + this.contractAddress = contractAddress + this.owner = this.owner.bind(this) + this.route = this.route.bind(this) + this.routes = this.routes.bind(this) + this.estimateExactInSwap = this.estimateExactInSwap.bind(this) + } + + owner = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + owner: {}, + }) + } + route = async ({ + denomIn, + denomOut, + }: { + denomIn: string + denomOut: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + route: { + denom_in: denomIn, + denom_out: denomOut, + }, + }) + } + routes = async ({ + limit, + startAfter, + }: { + limit?: number + startAfter?: string[][] + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + routes: { + limit, + start_after: startAfter, + }, + }) + } + estimateExactInSwap = async ({ + coinIn, + denomOut, + }: { + coinIn: Coin + denomOut: string + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + estimate_exact_in_swap: { + coin_in: coinIn, + denom_out: denomOut, + }, + }) + } +} +export interface MarsSwapperOsmosisInterface extends MarsSwapperOsmosisReadOnlyInterface { + contractAddress: string + sender: string + updateOwner: ( + ownerUpdate: OwnerUpdate, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise + setRoute: ( + { + denomIn, + denomOut, + route, + }: { + denomIn: string + denomOut: string + route: OsmosisRoute + }, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise + swapExactIn: ( + { + coinIn, + denomOut, + slippage, + }: { + coinIn: Coin + denomOut: string + slippage: Decimal + }, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise + transferResult: ( + { + denomIn, + denomOut, + recipient, + }: { + denomIn: string + denomOut: string + recipient: Addr + }, + fee?: number | StdFee | 'auto', + memo?: string, + _funds?: Coin[], + ) => Promise +} +export class MarsSwapperOsmosisClient + extends MarsSwapperOsmosisQueryClient + implements MarsSwapperOsmosisInterface +{ + client: SigningCosmWasmClient + sender: string + contractAddress: string + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress) + this.client = client + this.sender = sender + this.contractAddress = contractAddress + this.updateOwner = this.updateOwner.bind(this) + this.setRoute = this.setRoute.bind(this) + this.swapExactIn = this.swapExactIn.bind(this) + this.transferResult = this.transferResult.bind(this) + } + + updateOwner = async ( + ownerUpdate: OwnerUpdate, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + update_owner: ownerUpdate, + }, + fee, + memo, + _funds, + ) + } + setRoute = async ( + { + denomIn, + denomOut, + route, + }: { + denomIn: string + denomOut: string + route: OsmosisRoute + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + set_route: { + denom_in: denomIn, + denom_out: denomOut, + route, + }, + }, + fee, + memo, + _funds, + ) + } + swapExactIn = async ( + { + coinIn, + denomOut, + slippage, + }: { + coinIn: Coin + denomOut: string + slippage: Decimal + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + swap_exact_in: { + coin_in: coinIn, + denom_out: denomOut, + slippage, + }, + }, + fee, + memo, + _funds, + ) + } + transferResult = async ( + { + denomIn, + denomOut, + recipient, + }: { + denomIn: string + denomOut: string + recipient: Addr + }, + fee: number | StdFee | 'auto' = 'auto', + memo?: string, + _funds?: Coin[], + ): Promise => { + return await this.client.execute( + this.sender, + this.contractAddress, + { + transfer_result: { + denom_in: denomIn, + denom_out: denomOut, + recipient, + }, + }, + fee, + memo, + _funds, + ) + } +} diff --git a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.message-composer.ts b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.message-composer.ts new file mode 100644 index 000000000..44949f44f --- /dev/null +++ b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.message-composer.ts @@ -0,0 +1,187 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate' +import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' +import { toUtf8 } from '@cosmjs/encoding' +import { + InstantiateMsg, + ExecuteMsg, + OwnerUpdate, + OsmosisRoute, + Uint128, + Decimal, + Addr, + SwapAmountInRoute, + Coin, + QueryMsg, + EstimateExactInSwapResponse, + OwnerResponse, + RouteResponseForEmpty, + Empty, + ArrayOfRouteResponseForEmpty, +} from './MarsSwapperOsmosis.types' +export interface MarsSwapperOsmosisMessage { + contractAddress: string + sender: string + updateOwner: (ownerUpdate: OwnerUpdate, _funds?: Coin[]) => MsgExecuteContractEncodeObject + setRoute: ( + { + denomIn, + denomOut, + route, + }: { + denomIn: string + denomOut: string + route: OsmosisRoute + }, + _funds?: Coin[], + ) => MsgExecuteContractEncodeObject + swapExactIn: ( + { + coinIn, + denomOut, + slippage, + }: { + coinIn: Coin + denomOut: string + slippage: Decimal + }, + _funds?: Coin[], + ) => MsgExecuteContractEncodeObject + transferResult: ( + { + denomIn, + denomOut, + recipient, + }: { + denomIn: string + denomOut: string + recipient: Addr + }, + _funds?: Coin[], + ) => MsgExecuteContractEncodeObject +} +export class MarsSwapperOsmosisMessageComposer implements MarsSwapperOsmosisMessage { + sender: string + contractAddress: string + + constructor(sender: string, contractAddress: string) { + this.sender = sender + this.contractAddress = contractAddress + this.updateOwner = this.updateOwner.bind(this) + this.setRoute = this.setRoute.bind(this) + this.swapExactIn = this.swapExactIn.bind(this) + this.transferResult = this.transferResult.bind(this) + } + + updateOwner = (ownerUpdate: OwnerUpdate, _funds?: Coin[]): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + update_owner: ownerUpdate, + }), + ), + funds: _funds, + }), + } + } + setRoute = ( + { + denomIn, + denomOut, + route, + }: { + denomIn: string + denomOut: string + route: OsmosisRoute + }, + _funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + set_route: { + denom_in: denomIn, + denom_out: denomOut, + route, + }, + }), + ), + funds: _funds, + }), + } + } + swapExactIn = ( + { + coinIn, + denomOut, + slippage, + }: { + coinIn: Coin + denomOut: string + slippage: Decimal + }, + _funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + swap_exact_in: { + coin_in: coinIn, + denom_out: denomOut, + slippage, + }, + }), + ), + funds: _funds, + }), + } + } + transferResult = ( + { + denomIn, + denomOut, + recipient, + }: { + denomIn: string + denomOut: string + recipient: Addr + }, + _funds?: Coin[], + ): MsgExecuteContractEncodeObject => { + return { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: MsgExecuteContract.fromPartial({ + sender: this.sender, + contract: this.contractAddress, + msg: toUtf8( + JSON.stringify({ + transfer_result: { + denom_in: denomIn, + denom_out: denomOut, + recipient, + }, + }), + ), + funds: _funds, + }), + } + } +} diff --git a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.react-query.ts b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.react-query.ts new file mode 100644 index 000000000..08701f352 --- /dev/null +++ b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.react-query.ts @@ -0,0 +1,246 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import { UseQueryOptions, useQuery, useMutation, UseMutationOptions } from '@tanstack/react-query' +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { StdFee } from '@cosmjs/amino' +import { + InstantiateMsg, + ExecuteMsg, + OwnerUpdate, + OsmosisRoute, + Uint128, + Decimal, + Addr, + SwapAmountInRoute, + Coin, + QueryMsg, + EstimateExactInSwapResponse, + OwnerResponse, + RouteResponseForEmpty, + Empty, + ArrayOfRouteResponseForEmpty, +} from './MarsSwapperOsmosis.types' +import { + MarsSwapperOsmosisQueryClient, + MarsSwapperOsmosisClient, +} from './MarsSwapperOsmosis.client' +export const marsSwapperOsmosisQueryKeys = { + contract: [ + { + contract: 'marsSwapperOsmosis', + }, + ] as const, + address: (contractAddress: string | undefined) => + [{ ...marsSwapperOsmosisQueryKeys.contract[0], address: contractAddress }] as const, + owner: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsSwapperOsmosisQueryKeys.address(contractAddress)[0], method: 'owner', args }, + ] as const, + route: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsSwapperOsmosisQueryKeys.address(contractAddress)[0], method: 'route', args }, + ] as const, + routes: (contractAddress: string | undefined, args?: Record) => + [ + { ...marsSwapperOsmosisQueryKeys.address(contractAddress)[0], method: 'routes', args }, + ] as const, + estimateExactInSwap: (contractAddress: string | undefined, args?: Record) => + [ + { + ...marsSwapperOsmosisQueryKeys.address(contractAddress)[0], + method: 'estimate_exact_in_swap', + args, + }, + ] as const, +} +export interface MarsSwapperOsmosisReactQuery { + client: MarsSwapperOsmosisQueryClient | undefined + options?: Omit< + UseQueryOptions, + "'queryKey' | 'queryFn' | 'initialData'" + > & { + initialData?: undefined + } +} +export interface MarsSwapperOsmosisEstimateExactInSwapQuery + extends MarsSwapperOsmosisReactQuery { + args: { + coinIn: Coin + denomOut: string + } +} +export function useMarsSwapperOsmosisEstimateExactInSwapQuery({ + client, + args, + options, +}: MarsSwapperOsmosisEstimateExactInSwapQuery) { + return useQuery( + marsSwapperOsmosisQueryKeys.estimateExactInSwap(client?.contractAddress, args), + () => + client + ? client.estimateExactInSwap({ + coinIn: args.coinIn, + denomOut: args.denomOut, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsSwapperOsmosisRoutesQuery + extends MarsSwapperOsmosisReactQuery { + args: { + limit?: number + startAfter?: string[][] + } +} +export function useMarsSwapperOsmosisRoutesQuery({ + client, + args, + options, +}: MarsSwapperOsmosisRoutesQuery) { + return useQuery( + marsSwapperOsmosisQueryKeys.routes(client?.contractAddress, args), + () => + client + ? client.routes({ + limit: args.limit, + startAfter: args.startAfter, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsSwapperOsmosisRouteQuery + extends MarsSwapperOsmosisReactQuery { + args: { + denomIn: string + denomOut: string + } +} +export function useMarsSwapperOsmosisRouteQuery({ + client, + args, + options, +}: MarsSwapperOsmosisRouteQuery) { + return useQuery( + marsSwapperOsmosisQueryKeys.route(client?.contractAddress, args), + () => + client + ? client.route({ + denomIn: args.denomIn, + denomOut: args.denomOut, + }) + : Promise.reject(new Error('Invalid client')), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsSwapperOsmosisOwnerQuery + extends MarsSwapperOsmosisReactQuery {} +export function useMarsSwapperOsmosisOwnerQuery({ + client, + options, +}: MarsSwapperOsmosisOwnerQuery) { + return useQuery( + marsSwapperOsmosisQueryKeys.owner(client?.contractAddress), + () => (client ? client.owner() : Promise.reject(new Error('Invalid client'))), + { ...options, enabled: !!client && (options?.enabled != undefined ? options.enabled : true) }, + ) +} +export interface MarsSwapperOsmosisTransferResultMutation { + client: MarsSwapperOsmosisClient + msg: { + denomIn: string + denomOut: string + recipient: Addr + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsSwapperOsmosisTransferResultMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => + client.transferResult(msg, fee, memo, funds), + options, + ) +} +export interface MarsSwapperOsmosisSwapExactInMutation { + client: MarsSwapperOsmosisClient + msg: { + coinIn: Coin + denomOut: string + slippage: Decimal + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsSwapperOsmosisSwapExactInMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.swapExactIn(msg, fee, memo, funds), + options, + ) +} +export interface MarsSwapperOsmosisSetRouteMutation { + client: MarsSwapperOsmosisClient + msg: { + denomIn: string + denomOut: string + route: OsmosisRoute + } + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsSwapperOsmosisSetRouteMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.setRoute(msg, fee, memo, funds), + options, + ) +} +export interface MarsSwapperOsmosisUpdateOwnerMutation { + client: MarsSwapperOsmosisClient + msg: OwnerUpdate + args?: { + fee?: number | StdFee | 'auto' + memo?: string + funds?: Coin[] + } +} +export function useMarsSwapperOsmosisUpdateOwnerMutation( + options?: Omit< + UseMutationOptions, + 'mutationFn' + >, +) { + return useMutation( + ({ client, msg, args: { fee, memo, funds } = {} }) => client.updateOwner(msg, fee, memo, funds), + options, + ) +} diff --git a/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.types.ts b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.types.ts new file mode 100644 index 000000000..0046c3fb7 --- /dev/null +++ b/scripts/types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.types.ts @@ -0,0 +1,105 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + owner: string +} +export type ExecuteMsg = + | { + update_owner: OwnerUpdate + } + | { + set_route: { + denom_in: string + denom_out: string + route: OsmosisRoute + } + } + | { + swap_exact_in: { + coin_in: Coin + denom_out: string + slippage: Decimal + } + } + | { + transfer_result: { + denom_in: string + denom_out: string + recipient: Addr + } + } +export type OwnerUpdate = + | { + propose_new_owner: { + proposed: string + } + } + | 'clear_proposed' + | 'accept_proposed' + | 'abolish_owner_role' + | { + set_emergency_owner: { + emergency_owner: string + } + } + | 'clear_emergency_owner' +export type OsmosisRoute = SwapAmountInRoute[] +export type Uint128 = string +export type Decimal = string +export type Addr = string +export interface SwapAmountInRoute { + pool_id: number + token_out_denom: string + [k: string]: unknown +} +export interface Coin { + amount: Uint128 + denom: string + [k: string]: unknown +} +export type QueryMsg = + | { + owner: {} + } + | { + route: { + denom_in: string + denom_out: string + } + } + | { + routes: { + limit?: number | null + start_after?: [string, string] | null + } + } + | { + estimate_exact_in_swap: { + coin_in: Coin + denom_out: string + } + } +export interface EstimateExactInSwapResponse { + amount: Uint128 +} +export interface OwnerResponse { + abolished: boolean + emergency_owner?: string | null + initialized: boolean + owner?: string | null + proposed?: string | null +} +export interface RouteResponseForEmpty { + denom_in: string + denom_out: string + route: Empty +} +export interface Empty { + [k: string]: unknown +} +export type ArrayOfRouteResponseForEmpty = RouteResponseForEmpty[] diff --git a/scripts/types/generated/mars-swapper-osmosis/bundle.ts b/scripts/types/generated/mars-swapper-osmosis/bundle.ts new file mode 100644 index 000000000..053948fa8 --- /dev/null +++ b/scripts/types/generated/mars-swapper-osmosis/bundle.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.30.1. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +import * as _40 from './MarsSwapperOsmosis.types' +import * as _41 from './MarsSwapperOsmosis.client' +import * as _42 from './MarsSwapperOsmosis.message-composer' +import * as _43 from './MarsSwapperOsmosis.react-query' +export namespace contracts { + export const MarsSwapperOsmosis = { ..._40, ..._41, ..._42, ..._43 } +} diff --git a/scripts/types/generated/mars-v2-zapper-base/bundle.ts b/scripts/types/generated/mars-v2-zapper-base/bundle.ts index 548293c21..41be88329 100644 --- a/scripts/types/generated/mars-v2-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-v2-zapper-base/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _40 from './MarsV2ZapperBase.types' -import * as _41 from './MarsV2ZapperBase.client' -import * as _42 from './MarsV2ZapperBase.message-composer' -import * as _43 from './MarsV2ZapperBase.react-query' +import * as _44 from './MarsV2ZapperBase.types' +import * as _45 from './MarsV2ZapperBase.client' +import * as _46 from './MarsV2ZapperBase.message-composer' +import * as _47 from './MarsV2ZapperBase.react-query' export namespace contracts { - export const MarsV2ZapperBase = { ..._40, ..._41, ..._42, ..._43 } + export const MarsV2ZapperBase = { ..._44, ..._45, ..._46, ..._47 } } diff --git a/scripts/types/generated/mars-v3-zapper-base/bundle.ts b/scripts/types/generated/mars-v3-zapper-base/bundle.ts index 34eb13dff..0f80d612e 100644 --- a/scripts/types/generated/mars-v3-zapper-base/bundle.ts +++ b/scripts/types/generated/mars-v3-zapper-base/bundle.ts @@ -5,10 +5,10 @@ * and run the @cosmwasm/ts-codegen generate command to regenerate this file. */ -import * as _44 from './MarsV3ZapperBase.types' -import * as _45 from './MarsV3ZapperBase.client' -import * as _46 from './MarsV3ZapperBase.message-composer' -import * as _47 from './MarsV3ZapperBase.react-query' +import * as _48 from './MarsV3ZapperBase.types' +import * as _49 from './MarsV3ZapperBase.client' +import * as _50 from './MarsV3ZapperBase.message-composer' +import * as _51 from './MarsV3ZapperBase.react-query' export namespace contracts { - export const MarsV3ZapperBase = { ..._44, ..._45, ..._46, ..._47 } + export const MarsV3ZapperBase = { ..._48, ..._49, ..._50, ..._51 } } From ea7adf7b53240b8398585dfbd01490ac9a20ef9a Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 16 Jun 2023 18:57:34 +0200 Subject: [PATCH 165/218] Dispatch actions takes ownership (#139) remove borrow from actions vec --- contracts/credit-manager/src/contract.rs | 2 +- contracts/credit-manager/src/execute.rs | 54 ++++++++++++------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index fd750b059..e5c613792 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -60,7 +60,7 @@ pub fn execute( ExecuteMsg::UpdateCreditAccount { account_id, actions, - } => dispatch_actions(deps, env, info, &account_id, &actions), + } => dispatch_actions(deps, env, info, &account_id, actions), ExecuteMsg::RepayFromWallet { account_id, } => repay_from_wallet(deps, env, info, account_id), diff --git a/contracts/credit-manager/src/execute.rs b/contracts/credit-manager/src/execute.rs index fef97ef47..c6bfc9a50 100644 --- a/contracts/credit-manager/src/execute.rs +++ b/contracts/credit-manager/src/execute.rs @@ -61,7 +61,7 @@ pub fn dispatch_actions( env: Env, info: MessageInfo, account_id: &str, - actions: &[Action], + actions: Vec, ) -> ContractResult { assert_is_token_owner(&deps, &info.sender, account_id)?; assert_not_contract_in_config(&deps.as_ref(), &info.sender)?; @@ -74,16 +74,16 @@ pub fn dispatch_actions( for action in actions { match action { Action::Deposit(coin) => { - response = deposit(&mut deps, response, account_id, coin, &mut received_coins)?; + response = deposit(&mut deps, response, account_id, &coin, &mut received_coins)?; } Action::Withdraw(coin) => callbacks.push(CallbackMsg::Withdraw { account_id: account_id.to_string(), - coin: coin.clone(), + coin, recipient: info.sender.clone(), }), Action::Borrow(coin) => callbacks.push(CallbackMsg::Borrow { account_id: account_id.to_string(), - coin: coin.clone(), + coin, }), Action::Repay { recipient_account_id, @@ -92,23 +92,23 @@ pub fn dispatch_actions( if let Some(recipient) = recipient_account_id { callbacks.push(CallbackMsg::RepayForRecipient { benefactor_account_id: account_id.to_string(), - recipient_account_id: recipient.clone(), - coin: coin.clone(), + recipient_account_id: recipient, + coin, }) } else { callbacks.push(CallbackMsg::Repay { account_id: account_id.to_string(), - coin: coin.clone(), + coin, }) } } Action::Lend(coin) => callbacks.push(CallbackMsg::Lend { account_id: account_id.to_string(), - coin: coin.clone(), + coin, }), Action::Reclaim(coin) => callbacks.push(CallbackMsg::Reclaim { account_id: account_id.to_string(), - coin: coin.clone(), + coin, }), Action::EnterVault { vault, @@ -116,7 +116,7 @@ pub fn dispatch_actions( } => callbacks.push(CallbackMsg::EnterVault { account_id: account_id.to_string(), vault: vault.check(deps.api)?, - coin: coin.clone(), + coin, }), Action::Liquidate { liquidatee_account_id, @@ -126,14 +126,14 @@ pub fn dispatch_actions( LiquidateRequest::Deposit(denom) => callbacks.push(CallbackMsg::Liquidate { liquidator_account_id: account_id.to_string(), liquidatee_account_id: liquidatee_account_id.to_string(), - debt_coin: debt_coin.clone(), - request: LiquidateRequest::Deposit(denom.clone()), + debt_coin, + request: LiquidateRequest::Deposit(denom), }), LiquidateRequest::Lend(denom) => callbacks.push(CallbackMsg::Liquidate { liquidator_account_id: account_id.to_string(), liquidatee_account_id: liquidatee_account_id.to_string(), - debt_coin: debt_coin.clone(), - request: LiquidateRequest::Lend(denom.clone()), + debt_coin, + request: LiquidateRequest::Lend(denom), }), LiquidateRequest::Vault { request_vault, @@ -141,10 +141,10 @@ pub fn dispatch_actions( } => callbacks.push(CallbackMsg::Liquidate { liquidator_account_id: account_id.to_string(), liquidatee_account_id: liquidatee_account_id.to_string(), - debt_coin: debt_coin.clone(), + debt_coin, request: LiquidateRequest::Vault { request_vault: request_vault.check(deps.api)?, - position_type: position_type.clone(), + position_type, }, }), }, @@ -154,9 +154,9 @@ pub fn dispatch_actions( slippage, } => callbacks.push(CallbackMsg::SwapExactIn { account_id: account_id.to_string(), - coin_in: coin_in.clone(), - denom_out: denom_out.clone(), - slippage: *slippage, + coin_in, + denom_out, + slippage, }), Action::ExitVault { vault, @@ -164,7 +164,7 @@ pub fn dispatch_actions( } => callbacks.push(CallbackMsg::ExitVault { account_id: account_id.to_string(), vault: vault.check(deps.api)?, - amount: *amount, + amount, }), Action::RequestVaultUnlock { vault, @@ -172,7 +172,7 @@ pub fn dispatch_actions( } => callbacks.push(CallbackMsg::RequestVaultUnlock { account_id: account_id.to_string(), vault: vault.check(deps.api)?, - amount: *amount, + amount, }), Action::ExitVaultUnlocked { id, @@ -180,7 +180,7 @@ pub fn dispatch_actions( } => callbacks.push(CallbackMsg::ExitVaultUnlocked { account_id: account_id.to_string(), vault: vault.check(deps.api)?, - position_id: *id, + position_id: id, }), Action::ProvideLiquidity { coins_in, @@ -188,17 +188,17 @@ pub fn dispatch_actions( minimum_receive, } => callbacks.push(CallbackMsg::ProvideLiquidity { account_id: account_id.to_string(), - lp_token_out: lp_token_out.clone(), - coins_in: coins_in.clone(), - minimum_receive: *minimum_receive, + lp_token_out, + coins_in, + minimum_receive, }), Action::WithdrawLiquidity { lp_token, minimum_receive, } => callbacks.push(CallbackMsg::WithdrawLiquidity { account_id: account_id.to_string(), - lp_token: lp_token.clone(), - minimum_receive: minimum_receive.clone(), + lp_token, + minimum_receive, }), Action::RefundAllCoinBalances {} => { callbacks.push(CallbackMsg::RefundAllCoinBalances { From c8a4ca3103afd6761dc232ca1fff02753294def3 Mon Sep 17 00:00:00 2001 From: brimigs <85972460+brimigs@users.noreply.github.com> Date: Mon, 19 Jun 2023 05:15:04 -0400 Subject: [PATCH 166/218] Mp 2829 update build scripts and deploy to testnet (#145) * updates * Fix params types * fee change * format * update gas and remove axelar * add params contract to health contract * Update config --------- Co-authored-by: Gabe Rodriguez --- schema.Makefile.toml | 1 + .../mars-rover-health/mars-rover-health.json | 329 ++++++ scripts/codegen/index.ts | 12 +- .../osmo-test-5-testnet-deployer-owner.json | 8 + scripts/deploy/base/deployer.ts | 13 +- scripts/deploy/base/index.ts | 2 +- scripts/deploy/base/rover.ts | 9 +- scripts/deploy/osmosis/mainnnet.ts | 1 - scripts/deploy/osmosis/testnet-config.ts | 94 +- scripts/health/pkg-node/index.js | 22 +- scripts/health/pkg-node/index_bg.wasm | Bin 157988 -> 166551 bytes scripts/health/pkg-node/index_bg.wasm.d.ts | 1 + scripts/health/pkg-web/index.d.ts | 1 + scripts/health/pkg-web/index.js | 18 +- scripts/health/pkg-web/index_bg.wasm | Bin 157064 -> 165627 bytes scripts/health/pkg-web/index_bg.wasm.d.ts | 1 + scripts/package.json | 2 +- scripts/types/config.ts | 1 - .../mars-rover-health-computer/bundle.ts | 10 +- .../mars-rover-health-types/bundle.ts | 10 +- .../MarsRoverHealth.client.ts | 140 +++ .../MarsRoverHealth.message-composer.ts | 92 ++ .../MarsRoverHealth.react-query.ts | 125 +++ .../MarsRoverHealth.types.ts | 70 ++ .../generated/mars-rover-health/bundle.ts | 14 + .../generated/mars-swapper-base/bundle.ts | 10 +- .../generated/mars-swapper-osmosis/bundle.ts | 10 +- .../generated/mars-v2-zapper-base/bundle.ts | 10 +- .../generated/mars-v3-zapper-base/bundle.ts | 10 +- scripts/yarn.lock | 935 ++++-------------- 30 files changed, 1077 insertions(+), 874 deletions(-) create mode 100644 schemas/mars-rover-health/mars-rover-health.json create mode 100644 scripts/deploy/addresses/osmo-test-5-testnet-deployer-owner.json create mode 100644 scripts/types/generated/mars-rover-health/MarsRoverHealth.client.ts create mode 100644 scripts/types/generated/mars-rover-health/MarsRoverHealth.message-composer.ts create mode 100644 scripts/types/generated/mars-rover-health/MarsRoverHealth.react-query.ts create mode 100644 scripts/types/generated/mars-rover-health/MarsRoverHealth.types.ts create mode 100644 scripts/types/generated/mars-rover-health/bundle.ts diff --git a/schema.Makefile.toml b/schema.Makefile.toml index b8cea69c8..7a1ffc2d6 100644 --- a/schema.Makefile.toml +++ b/schema.Makefile.toml @@ -23,6 +23,7 @@ fn main() -> std::io::Result<()> { "mars-mock-credit-manager", "mars-rover-health-computer", "mars-rover-health-types", + "mars-rover-health", ]; for contract in contracts { diff --git a/schemas/mars-rover-health/mars-rover-health.json b/schemas/mars-rover-health/mars-rover-health.json new file mode 100644 index 000000000..181685a9a --- /dev/null +++ b/schemas/mars-rover-health/mars-rover-health.json @@ -0,0 +1,329 @@ +{ + "contract_name": "mars-rover-health", + "contract_version": "2.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "description": "The address with privileged access to update config", + "type": "string" + } + }, + "additionalProperties": false + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Manages owner role state", + "type": "object", + "required": [ + "update_owner" + ], + "properties": { + "update_owner": { + "$ref": "#/definitions/OwnerUpdate" + } + }, + "additionalProperties": false + }, + { + "description": "Update contract config constants", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "credit_manager": { + "type": [ + "string", + "null" + ] + }, + "params": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "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" + ] + }, + { + "description": "A separate entity managed by Owner that can be used for granting specific emergency powers.", + "type": "object", + "required": [ + "set_emergency_owner" + ], + "properties": { + "set_emergency_owner": { + "type": "object", + "required": [ + "emergency_owner" + ], + "properties": { + "emergency_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove the entity in the Emergency Owner role", + "type": "string", + "enum": [ + "clear_emergency_owner" + ] + } + ] + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "health" + ], + "properties": { + "health": { + "type": "object", + "required": [ + "account_id", + "kind" + ], + "properties": { + "account_id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/AccountKind" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "AccountKind": { + "type": "string", + "enum": [ + "default", + "high_levered_strategy" + ] + } + } + }, + "migrate": null, + "sudo": null, + "responses": { + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "type": "object", + "required": [ + "owner_response" + ], + "properties": { + "credit_manager": { + "type": [ + "string", + "null" + ] + }, + "owner_response": { + "$ref": "#/definitions/OwnerResponse" + }, + "params": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "OwnerResponse": { + "description": "Returned from Owner.query()", + "type": "object", + "required": [ + "abolished", + "initialized" + ], + "properties": { + "abolished": { + "type": "boolean" + }, + "emergency_owner": { + "type": [ + "string", + "null" + ] + }, + "initialized": { + "type": "boolean" + }, + "owner": { + "type": [ + "string", + "null" + ] + }, + "proposed": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + } + }, + "health": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HealthResponse", + "type": "object", + "required": [ + "above_max_ltv", + "liquidatable", + "liquidation_threshold_adjusted_collateral", + "max_ltv_adjusted_collateral", + "total_collateral_value", + "total_debt_value" + ], + "properties": { + "above_max_ltv": { + "type": "boolean" + }, + "liquidatable": { + "type": "boolean" + }, + "liquidation_health_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "liquidation_threshold_adjusted_collateral": { + "$ref": "#/definitions/Uint128" + }, + "max_ltv_adjusted_collateral": { + "$ref": "#/definitions/Uint128" + }, + "max_ltv_health_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "total_collateral_value": { + "$ref": "#/definitions/Uint128" + }, + "total_debt_value": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "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/scripts/codegen/index.ts b/scripts/codegen/index.ts index a114f39db..298c54b84 100644 --- a/scripts/codegen/index.ts +++ b/scripts/codegen/index.ts @@ -1,7 +1,7 @@ import codegen from '@cosmwasm/ts-codegen' import { join, resolve } from 'path' import { printGreen, printRed } from '../utils/chalk' -import { readdir, rm, rename } from 'fs/promises' +import { readdir, rename, rm } from 'fs/promises' import simpleGit from 'simple-git' const generateTypes = async () => { @@ -42,13 +42,18 @@ const generateTypes = async () => { const fetchSchemafromGithub = async ({ githubRepo, pathToSchema, + commit, }: { githubRepo: string pathToSchema: string + commit: string }) => { - await simpleGit().clone(githubRepo) - const schemaDirName = pathToSchema.split('/').pop()! + const git = simpleGit() + await git.clone(githubRepo) const repoDirName = githubRepo.split('/').pop()! + await git.cwd({ path: `./${repoDirName}`, root: true }) + await git.checkout(commit) + const schemaDirName = pathToSchema.split('/').pop()! await rename(pathToSchema, `../schemas/${schemaDirName}`) await rm(`./${repoDirName}`, { recursive: true, force: true }) } @@ -56,6 +61,7 @@ const fetchSchemafromGithub = async ({ void (async function () { await fetchSchemafromGithub({ githubRepo: 'https://github.com/mars-protocol/mars-common', + commit: '1449b4b1cd21d318a48310345ea8c99a0c3cf16c', pathToSchema: './mars-common/schemas/mars-params', }) await generateTypes() diff --git a/scripts/deploy/addresses/osmo-test-5-testnet-deployer-owner.json b/scripts/deploy/addresses/osmo-test-5-testnet-deployer-owner.json new file mode 100644 index 000000000..c2a716be1 --- /dev/null +++ b/scripts/deploy/addresses/osmo-test-5-testnet-deployer-owner.json @@ -0,0 +1,8 @@ +{ + "mockVault": "osmo1r7qzusdgpaw0f8jgeewtxquszxzd5sp63e2t6ezp72j6qd6wt4ns6ufkpd", + "swapper": "osmo1q3p82qtudu7f5edgvqyzf6hk8xanezlr0w7ntypnsea4jfpe37ps29eay3", + "zapper": "osmo1dz3ysw5sl0rvvnvatv7nu6vyam687tentfuxfa22sxqqafdcnkdqht3uw5", + "healthContract": "osmo10agkp20f6kqm8jlay760gszyg6qhz247mzk0r84pvjxaq086t0zsfmg6gt", + "creditManager": "osmo1sp7hlk78xpw6aer9wvskpz5ryknafw5lgf7f4jku4zfxkmuee6nqswkr8f", + "accountNft": "osmo1gmpua5rkzg6cmju73fz5a9x454nz4vwlnkgs4g8wjlyeqtmzsuhq5funky" +} diff --git a/scripts/deploy/base/deployer.ts b/scripts/deploy/base/deployer.ts index b6e781285..1723edaa5 100644 --- a/scripts/deploy/base/deployer.ts +++ b/scripts/deploy/base/deployer.ts @@ -86,7 +86,7 @@ export class Deployer { await this.instantiate('healthContract', this.storage.codeIds.healthContract!, msg) } - async setCmOnHealthContract() { + async setConfigOnHealthContract() { if (this.storage.actions.healthContractConfigUpdate) { printGray('Credit manager address') } else { @@ -96,8 +96,11 @@ export class Deployer { this.storage.addresses.healthContract!, ) - printBlue('Setting credit manager address on health contract config') - await hExec.updateConfig({ creditManager: this.storage.addresses.creditManager! }) + printBlue('Setting credit manager address & params on health contract config') + await hExec.updateConfig({ + creditManager: this.storage.addresses.creditManager!, + params: this.config.params.addr, + }) } this.storage.actions.healthContractConfigUpdate = true } @@ -176,7 +179,7 @@ export class Deployer { async instantiateCreditManager() { const msg: RoverInstantiateMsg = { - params: this.storage.addresses.params!, + params: this.config.params.addr, max_unlocking_positions: this.config.maxUnlockingPositions, oracle: this.config.oracle.addr, owner: this.deployerAddr, @@ -401,7 +404,7 @@ export class Deployer { } private async transferCoin(recipient: string, coin: Coin) { - await this.cwClient.sendTokens(this.deployerAddr, recipient, [coin], 'auto') + await this.cwClient.sendTokens(this.deployerAddr, recipient, [coin], 2) const balance = await this.cwClient.getBalance(recipient, coin.denom) printBlue(`New balance: ${balance.amount} ${balance.denom}`) } diff --git a/scripts/deploy/base/index.ts b/scripts/deploy/base/index.ts index 6f21a0316..989971e03 100644 --- a/scripts/deploy/base/index.ts +++ b/scripts/deploy/base/index.ts @@ -26,7 +26,7 @@ export const taskRunner = async ({ config, label }: TaskRunnerProps) => { await deployer.instantiateHealthContract() await deployer.instantiateCreditManager() await deployer.instantiateNftContract() - await deployer.setCmOnHealthContract() + await deployer.setConfigOnHealthContract() await deployer.transferNftContractOwnership() await deployer.saveDeploymentAddrsToFile(label) diff --git a/scripts/deploy/base/rover.ts b/scripts/deploy/base/rover.ts index 6d67cdde2..1446888a6 100644 --- a/scripts/deploy/base/rover.ts +++ b/scripts/deploy/base/rover.ts @@ -13,6 +13,7 @@ import { Action, Coin, ConfigUpdates, + ExecuteMsg, } from '../../types/generated/mars-credit-manager/MarsCreditManager.types' import { MarsMockVaultQueryClient } from '../../types/generated/mars-mock-vault/MarsMockVault.client' import { VaultConfigBaseForString } from '../../types/generated/mars-params/MarsParams.types' @@ -41,7 +42,13 @@ export class Rover { async createCreditAccount() { const before = await this.nft.tokens({ owner: this.userAddr }) - await this.exec.createCreditAccount() + const executeMsg = { create_credit_account: 'default' } satisfies ExecuteMsg + await this.cwClient.execute( + this.userAddr, + this.storage.addresses.creditManager!, + executeMsg, + 'auto', + ) const after = await this.nft.tokens({ owner: this.userAddr }) const diff = difference(after.tokens, before.tokens) assert.equal(diff.length, 1) diff --git a/scripts/deploy/osmosis/mainnnet.ts b/scripts/deploy/osmosis/mainnnet.ts index 4e308c86f..10676712e 100644 --- a/scripts/deploy/osmosis/mainnnet.ts +++ b/scripts/deploy/osmosis/mainnnet.ts @@ -18,7 +18,6 @@ export const osmosisMainnetConfig: DeploymentConfig = { rpcEndpoint: 'https://rpc.osmosis.zone', }, deployerMnemonic: 'TO BE INSERTED AT TIME OF DEPLOYMENT', - maxCloseFactor: '0.5', maxUnlockingPositions: '1', maxValueForBurn: '10000', // oracle and redbank contract addresses can be found: https://github.com/mars-protocol/red-bank/blob/master/README.md#osmosis-1 diff --git a/scripts/deploy/osmosis/testnet-config.ts b/scripts/deploy/osmosis/testnet-config.ts index 83f8b6ec9..fccece58d 100644 --- a/scripts/deploy/osmosis/testnet-config.ts +++ b/scripts/deploy/osmosis/testnet-config.ts @@ -1,36 +1,24 @@ import { DeploymentConfig, VaultType } from '../../types/config' +// Note: since osmo-test-5 upgrade, testnet and mainnet denoms are no longer the same. Reference asset info here: https://docs.osmosis.zone/osmosis-core/asset-info/ const uosmo = 'uosmo' -const uatom = 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2' -const udig = 'ibc/307E5C96C8F60D1CBEE269A9A86C0834E1DB06F2B3788AE4F716EDB97A48B97D' -const ujuno = 'ibc/46B44899322F3CD854D2D46DEEF881958467CDD4B3B10086DA49296BBED94BED' -const gammPool1 = 'gamm/pool/1' -const gammPool497 = 'gamm/pool/497' +const nUSDC = 'ibc/B3504E092456BA618CC28AC671A71FB08C6CA0FD0BE7C8A5B5A3E2DD933CC9E4' // noble +const nUSDC_osmo = 'gamm/pool/6' -const vaultOsmoAtom1 = 'osmo1zktjv92f76epswjvyxzzt3yyskpw7k6jsyu0kmq4zzc5fphrjumqlahctp' -const vaultOsmoAtom7 = 'osmo167j3yttwzcm3785tzk4jse2qdkppcy2xxrn5u6srqv7s93wnq6yqw8zhg5' -const vaultOsmoAtom14 = 'osmo1tp2m6g39h8mvhnu3plqjyen5s63023gj8w873l8wvly0cd77l6hsaa73wt' -const atomOsmoConfig = (addr: string) => ({ +const nUSDC_OSMO_vault_1 = 'osmo1q40xvrzpldwq5he4ftsf7zm2jf80tj373qaven38yqrvhex8r9rs8n94kv' +const nUSDC_OSMO_vault_7 = 'osmo14lu7m4ganxs20258dazafrjfaulmfxruq9n0r0th90gs46jk3tuqwfkqwn' +const nUSDC_OSMO_vault_14 = 'osmo1fmq9hw224fgz8lk48wyd0gfg028kvvzggt6c3zvnaqkw23x68cws5nd5em' + +const nUSDC_OSMO_Config = (addr: string) => ({ addr, - deposit_cap: { denom: uatom, amount: '1000000000' }, // 1000 atom + deposit_cap: { denom: nUSDC, amount: '1000000000' }, // 1000 atom max_loan_to_value: '0.63', liquidation_threshold: '0.65', whitelisted: true, }) -const vaultJunoOsmo1 = 'osmo1r6h0pafu3wq0kf6yv09qhc8qvuku2d6fua0rpwwv46h7hd8u586scxspjf' -const vaultJunoOsmo7 = 'osmo1gr5epxn67q6202l3hy0mcnu7qc039v22pa6x2tsk23zwg235n9jsq6pmes' -const vaultJunoOsmo14 = 'osmo1d6knwkelyr9eklewnn9htkess4ttpxpf2cze9ec0xfw7e3fj0ggssqzfpp' -const junoOsmoConfig = (addr: string) => ({ - addr, - deposit_cap: { denom: uatom, amount: '500000000' }, // 500 atom - max_loan_to_value: '0.65', - liquidation_threshold: '0.66', - whitelisted: true, -}) - export const osmosisTestnetConfig: DeploymentConfig = { - allowedCoins: [uosmo, uatom, ujuno, gammPool1, gammPool497], + allowedCoins: [uosmo, nUSDC, nUSDC, nUSDC_osmo], chain: { baseDenom: uosmo, defaultGasPrice: 0.1, @@ -40,51 +28,35 @@ export const osmosisTestnetConfig: DeploymentConfig = { }, deployerMnemonic: 'rely wonder join knock during sudden slow plate segment state agree also arrest mandate grief ordinary lonely lawsuit hurt super banana rule velvet cart', - maxCloseFactor: '0.6', maxUnlockingPositions: '10', maxValueForBurn: '1000000', - // Latest from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-4.json - oracle: { addr: 'osmo1dqz2u3c8rs5e7w5fnchsr2mpzzsxew69wtdy0aq4jsd76w7upmsstqe0s8' }, - redBank: { addr: 'osmo1t0dl6r27phqetfu0geaxrng0u9zn8qgrdwztapt5xr32adtwptaq6vwg36' }, - params: { addr: 'xyz' }, // TODO: add with build scripts update task + // Latest from: https://github.com/mars-protocol/outposts/blob/master/scripts/deploy/addresses/osmo-test-5.json + oracle: { addr: 'osmo1khe29uw3t85nmmp3mtr8dls7v2qwsfk3tndu5h4w5g2r5tzlz5qqarq2e2' }, + redBank: { addr: 'osmo1dl4rylasnd7mtfzlkdqn2gr0ss4gvyykpvr6d7t5ylzf6z535n9s5jjt8u' }, + params: { addr: 'osmo1xvg28lrr72662t9u0hntt76lyax9zvptdvdmff4k2q9dhjm8x6ws9zym4v' }, swapRoutes: [ - { denomIn: uosmo, denomOut: uatom, route: [{ token_out_denom: uatom, pool_id: '1' }] }, - { denomIn: uatom, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '1' }] }, - { denomIn: uosmo, denomOut: ujuno, route: [{ token_out_denom: ujuno, pool_id: '497' }] }, - { denomIn: ujuno, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '497' }] }, + { denomIn: uosmo, denomOut: nUSDC, route: [{ token_out_denom: nUSDC, pool_id: '6' }] }, + { denomIn: nUSDC, denomOut: uosmo, route: [{ token_out_denom: uosmo, pool_id: '6' }] }, ], - // Latest from: https://stats.apollo.farm/api/vaults/v1/all + // Latest from: https://api.apollo.farm/api/graph?query=query+MyQuery+%7B%0A++vaults%28network%3A+osmo_test_5%29+%7B%0A++++label%0A++++contract_address%0A++%7D%0A%7D vaults: [ - atomOsmoConfig(vaultOsmoAtom1), - atomOsmoConfig(vaultOsmoAtom7), - atomOsmoConfig(vaultOsmoAtom14), - junoOsmoConfig(vaultJunoOsmo1), - junoOsmoConfig(vaultJunoOsmo7), - junoOsmoConfig(vaultJunoOsmo14), + nUSDC_OSMO_Config(nUSDC_OSMO_vault_1), + nUSDC_OSMO_Config(nUSDC_OSMO_vault_7), + nUSDC_OSMO_Config(nUSDC_OSMO_vault_14), ], swapperContractName: 'mars_swapper_osmosis', - zapperContractName: 'mars_zapper_osmosis', + zapperContractName: 'mars_v2_zapper_osmosis', testActions: { allowedCoinsConfig: [ { denom: uosmo, priceSource: { fixed: { price: '1' } }, grantCreditLine: true }, { - denom: uatom, - priceSource: { geometric_twap: { pool_id: 1, window_size: 1800 } }, + denom: nUSDC, + priceSource: { geometric_twap: { pool_id: 5, window_size: 1800 } }, grantCreditLine: true, }, { - denom: ujuno, - priceSource: { geometric_twap: { pool_id: 497, window_size: 1800 } }, - grantCreditLine: true, - }, - { - denom: gammPool1, - priceSource: { xyk_liquidity_token: { pool_id: 1 } }, - grantCreditLine: false, - }, - { - denom: gammPool497, - priceSource: { xyk_liquidity_token: { pool_id: 497 } }, + denom: nUSDC_osmo, + priceSource: { xyk_liquidity_token: { pool_id: 6 } }, grantCreditLine: false, }, ], @@ -93,15 +65,15 @@ export const osmosisTestnetConfig: DeploymentConfig = { withdrawAmount: '1000000', mock: { config: { - deposit_cap: { denom: uosmo, amount: '100000000' }, // 100 osmo + deposit_cap: { denom: nUSDC, amount: '100000000' }, // 100 usdc liquidation_threshold: '0.585', max_loan_to_value: '0.569', whitelisted: true, }, - vaultTokenDenom: udig, + vaultTokenDenom: uosmo, type: VaultType.LOCKED, lockup: { time: 900 }, // 15 mins - baseToken: gammPool1, + baseToken: nUSDC_osmo, }, }, outpostsDeployerMnemonic: @@ -112,14 +84,14 @@ export const osmosisTestnetConfig: DeploymentConfig = { depositAmount: '100', lendAmount: '10', reclaimAmount: '5', - secondaryDenom: uatom, - startingAmountForTestUser: '2500000', + secondaryDenom: nUSDC, + startingAmountForTestUser: '4000000', swap: { slippage: '0.4', amount: '40', route: [ { - token_out_denom: uatom, + token_out_denom: nUSDC, pool_id: '1', }, ], @@ -129,12 +101,12 @@ export const osmosisTestnetConfig: DeploymentConfig = { zap: { coinsIn: [ { - denom: uatom, + denom: nUSDC, amount: '1', }, { denom: uosmo, amount: '3' }, ], - denomOut: gammPool1, + denomOut: nUSDC_osmo, }, }, } diff --git a/scripts/health/pkg-node/index.js b/scripts/health/pkg-node/index.js index 45ae03046..4bdb06bac 100644 --- a/scripts/health/pkg-node/index.js +++ b/scripts/health/pkg-node/index.js @@ -257,6 +257,17 @@ module.exports.__wbindgen_error_new = function (arg0, arg1) { return addHeapObject(ret) } +module.exports.__wbindgen_boolean_get = function (arg0) { + const v = getObject(arg0) + const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 + return ret +} + +module.exports.__wbindgen_is_string = function (arg0) { + const ret = typeof getObject(arg0) === 'string' + return ret +} + module.exports.__wbindgen_is_bigint = function (arg0) { const ret = typeof getObject(arg0) === 'bigint' return ret @@ -272,17 +283,6 @@ module.exports.__wbindgen_jsval_eq = function (arg0, arg1) { return ret } -module.exports.__wbindgen_boolean_get = function (arg0) { - const v = getObject(arg0) - const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 - return ret -} - -module.exports.__wbindgen_is_string = function (arg0) { - const ret = typeof getObject(arg0) === 'string' - return ret -} - module.exports.__wbg_new_abda76e883ba8a5f = function () { const ret = new Error() return addHeapObject(ret) diff --git a/scripts/health/pkg-node/index_bg.wasm b/scripts/health/pkg-node/index_bg.wasm index 7688f33c3b0cca97e7dbb8f069e192e1d2814f78..eff3224d9c8d721fbef0b1d5863dce2ec97531e8 100644 GIT binary patch literal 166551 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 M(!!dulIl_a2U_X-3IG5A delta 64081 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 number readonly allocate: (a: number) => number readonly deallocate: (a: number) => void + readonly requires_stargate: () => void readonly requires_iterator: () => void readonly interface_version_8: () => void readonly __wbindgen_malloc: (a: number) => number diff --git a/scripts/health/pkg-web/index.js b/scripts/health/pkg-web/index.js index 884616242..4db53e976 100644 --- a/scripts/health/pkg-web/index.js +++ b/scripts/health/pkg-web/index.js @@ -297,6 +297,15 @@ function __wbg_get_imports() { const ret = new Error(getStringFromWasm0(arg0, arg1)) return addHeapObject(ret) } + imports.wbg.__wbindgen_boolean_get = function (arg0) { + const v = getObject(arg0) + const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 + return ret + } + imports.wbg.__wbindgen_is_string = function (arg0) { + const ret = typeof getObject(arg0) === 'string' + return ret + } imports.wbg.__wbindgen_is_bigint = function (arg0) { const ret = typeof getObject(arg0) === 'bigint' return ret @@ -309,15 +318,6 @@ function __wbg_get_imports() { const ret = getObject(arg0) === getObject(arg1) return ret } - imports.wbg.__wbindgen_boolean_get = function (arg0) { - const v = getObject(arg0) - const ret = typeof v === 'boolean' ? (v ? 1 : 0) : 2 - return ret - } - imports.wbg.__wbindgen_is_string = function (arg0) { - const ret = typeof getObject(arg0) === 'string' - return ret - } imports.wbg.__wbg_new_abda76e883ba8a5f = function () { const ret = new Error() return addHeapObject(ret) diff --git a/scripts/health/pkg-web/index_bg.wasm b/scripts/health/pkg-web/index_bg.wasm index 0284534d5fb8fc29562198a1acd61ee761204d61..ab6b44af2bb4c4080a53755d5706cc809ea354c8 100644 GIT binary patch literal 165627 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